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_ptr
、shared_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_ptr
和unique_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]; }