C++11新特性

@TOC


一、“原始”字符串

可以解决读取文件时的文件路径问题:

"C:\\aaa\\a"; //法一:通过转义字符进行读取
"R"(C:\aaa\a)""; //法二
"R"+*(C:\aaa\a)+*""; //法三

【注】R指的是原生字符串(Raw String),指的是不进行转义,“所见即所得”的字符串。

二、大括号初始化列表

struct A {
    int a;
    float b;
};

struct B {
public:
    B(int _a, float _b): a(_a), b(_b) {}
private:
    int a;
    float b;
};

A a {1, 1.1};    // 统一的初始化语法(大括号初始化列表)
B b {2, 2.2};

三、声明

1.auto

使用 auto 进行类型推导的一个最为常见而且显著的例子就是迭代器。在以前我们需要这样来书写一个迭代器:

for(vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it)

而有了 auto 之后可以:

for(auto it = vec.begin(); it != vec.end(); ++it);

【注】auto 不能用于函数传参;auto 不能用于推导数组类型;auto不能用于函数声明

2.decltype

decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。

//获取expression的类型 作为返回值
decltype(expression)

【注】decltype有一个核对表,根据这个核对表得到返回类型:
与标识符类型相同;
与函数的返回类型相同;
与expression类型相同。

3.返回类型后置

//使用auto作为占位,返回类型后置
auto eff(int x, int y) -> decltype(x*y)

4.模板别名

//可以用于模板部分具体化,但typedef不能
using xx = ......

四、nullptr

在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。

C++11 引入了 nullptr 关键字,专门用来区分空指针、0

nullptr  // 空指针
NULL  // 0

五、智能指针

在编写程序过程中,使用new时,用完时候就要使用delete将其释放,而智能指针则是帮助自动完成这个过程

它是一个类,通过构造函数将普通指针传进去,
传给成员变量,那么之后在析构的时候该指针就会被释放了。
同时该类中要重载`*`, `->`, `=`运算符。

1.头文件

#include <memory>

2.思想

将常规指针进行包装,当智能指针对象过期时,让它的析构函数对常规指针进行内存释放。
【注】智能指针类都有explicit构造函数,不能自动将指针转换为智能指针对象

3.分类

智能指针有三种auto_ptr(C++98)、unique_ptrshared_ptr。还有一个是weak_ptr,但它只是shared_ptr的一种辅助工具。
【注】C++11摒弃了auto_ptr

4.智能指针的选择

(1)需要使用多个指向同一对象的指针,使用shared_ptr
(2)不需要使用多个指向同一对象的指针,使用unique_ptr
(3)使用new[]分配内存,使用unique_ptr
(4)用new分配内存,返回指向该内存的指针,则应将其返回类型声明为unique_ptr

(5)weak_ptr只是shared_ptr的一种辅助工具,不能单独使用;它可以获取shared_ptr的一些状态信息,比如计数个数,指向的堆内存是否已被释放等等;它不会影响计数个数(引用计数)。

【注】将一个`unique_ptr`赋给另一个时:
若源为临时右值,则可以这样做;
若源会存在一段时间,则不可以这样做。

5.一个问题及解决方案

当两个指针指向同一个对象时,当它们过期时,会释放同一块内存两次,解决办法:
(1)重载=运算符,执行深拷贝,这样两个指针就会指向不同的对象(两块不同的内存区域)。

(2)建立所有权ownership)概念,对于特定对象,只能有一个智能指针可以拥有它只有拥有对象的智能指针的析构函数会删除该对象,之后转让所有权。(这是auto_ptrunique_ptr的策略)

(3)创建智能更高的指针,跟踪引用特定对象的智能指针计数(引用计数)。赋值的时候计数加一,过期的时候计数减一。仅当最后一个指针过期时才会调用delete。(这是shared_ptr的策略)

六、异常规范方面的修改

以前:throw抛出异常
C++11:不引发异常的关键字——noexcept

七、作用域内枚举、显式转换运算符(explicit)、类内成员初始化

八、右值引用

右值,就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值
右值引用,就是绑定到右值的引用,通过&&来获得右值引用。
可以将右值引用归纳为:非常量右值引用只能绑定到非常量右值上;常量右值引用可以绑定到非常量右值、常量右值上。

可以指向右值的引用,不能对其应用地址运算符&。
语句执行完之后,左值会保存在内存中一段时间,右值不会。
两个&&。
【注】常量引用:

//实际上编译器执行的代码:
//int tmp = 10;
//const int& rd = tmp;
const int& rd = 10; //常量引用

右值引用的目的:实现移动语义完美转发

1.移动语义

将一个对象中资源移动到另一个对象的方式:文件保留在原本的地方(不需要重新拷贝并删除旧的),将新的与其进行关联。(移动构造函数(用右值进行初始化)),节省了空间,提高了程序的运行效率。

//将左值a强制转换为右值
std::move(a)

2.完美转发

在函数模板中,完全依照模板的参数类型,将参数传递给函数模板中调用的另外一个函数,而不产生额外的开销。

//template<typename T>
//void PerfectForward(T &&t) { fun(std::forward<T>(t)); }
std::forward<T>(t)

九、Lambda表达式

Lambda 表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。

Lambda 表达式的基本语法如下:

[ caputrue ] ( params ) opt -> ret { body; };

// capture是捕获列表;
捕获列表有一下几种形式
[var]表示值传递方式捕捉变量var;
[=]表示值传递方式捕捉所有父作用域的变量(包括this);
[&var]表示引用传递捕捉变量var;
[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
[this]表示值传递方式捕捉当前的this指针。

// params是参数表;(选填)

// opt是函数选项;可以填mutable,exception,attribute(选填)
// mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。
// exception说明lambda表达式是否抛出异常以及何种异常。
// attribute用来声明属性。

// ret是返回值类型(拖尾返回类型)。(选填)
// body是函数体。
for_each(vec.begin(), vec.end(), [](int v){cout << v < endl;})

int a = 10;
auto func = [&a](int v){cout << v + a << endl;};
for (auto i : vec) {
    func(i);
    ++a;
}

捕获列表:lambda表达式的捕获列表精细控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。

1) []不捕获任何变量。

2) [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(**按引用捕获**)。

3) [=]捕获外部作用域中所有变量,并作为副本在函数体中使用(**按值捕获**)。
注意值捕获的前提是变量可以拷贝,且被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝。
如果希望lambda表达式在调用时能即时访问外部变量,我们应当使用引用方式捕获。

4) [=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。

5) [bar]按值捕获bar变量,同时不捕获其他变量。

6) [this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。
如果已经使用了&或者=,就默认添加此选项。
捕获this的目的是可以在lamda中使用当前类的成员函数和成员变量。

lambda表达式的大致原理:每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,是一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,与具体实现有关。

十、类型转换运算符

1.C风格的类型转换

(int)a;
(double)b;

2.static_cast

(1)用于基类和子类之间的类型转换。
上行转换(子类到基类)是安全的;
下行转换,没有动态类型检查,不是安全的。

(2)用于基本类型之间的转换。(不是很安全,可能会损失精度)

char a = 'a';
double b = static_cast<double>(a);

3.dynamic_cast

(1)用于基类和子类之间的类型转换;
上行转换(子类到基类)是安全的;
下行转换,有动态类型检查,是安全的(产生多态才可以向下转换)。

(2)不能用于基本类型之间的转换

【注】dynamic_cast怎么实现运行时的类型转换?
其通过运行期类型检查的功能,也就是RTTI (Run-Time Type Identification)只能用在含有虚函数的机制体系中
具体做法是通过Vptr访问到虚函数表,而虚函数表中头部存放着指向类型信息结构体的指针,通过该指针访问到类型信息结构体,由此确定实际类型

因此,支持RTTI以及虚函数,就需要额外的空间和时间开销。大量使用dynamic_cast造成性能下降,在确定实际类型时使用static_cast将更加高效。

4.const_cast

(1)用于常量和非常量之间的转换。

(2)只能用于指针和引用的转换,不能用于基本数据类型。

5.reinterpret_cast

重新解释转换(任何类型都可以转换,最不安全)。

十一、正则表达式

正则表达式描述了一种字符串匹配的模式。一般使用正则表达式主要是实现下面三个需求:

1) 检查一个串是否包含某种形式的子串;
2) 将匹配的子串替换;
3) 从某个串中取出符合条件的子串。

C++11 提供的正则表达式库操作 std::string 对象,对模式 std::regex (本质是 std::basic_regex)进行初始化,通过 std::regex_match 进行匹配,从而产生 std::smatch (本质是 std::match_results 对象)。

在这里插入图片描述
使用:

#include <regex>

string buf; 
regex patten("^([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*)$");
smatch subMatch;
if (regex_match(buf, subMatch, patten)) {
    string timeStamp = subMatch[1];
    string timeS = subMatch[2];
    string timeMs = subMatch[3];
    string targetNum = subMatch[4];
}
全部评论
讲得非常清楚,学到了
点赞
送花
回复
分享
发布于 2022-10-14 20:56 陕西

相关推荐

7 47 评论
分享
牛客网
牛客企业服务