转载至:https://blog.csdn.net/jiange_zh/article/details/79356417
1.nullptr
nullptr 出现的目的是为了替代 NULL。
在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。
C++ 不允许直接将 void * 隐式转换到其他类型,但如果 NULL 被定义为 ((void*)0),那么当编译char *ch = NULL;时,NULL 只好被定义为 0。
而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,考虑:
1 void foo(char *); 2 void foo(int);
对于这两个函数来说,如果 NULL 又被定义为了 0 那么 foo(NULL); 这个语句将会去调用 foo(int),从而导致代码违反直观。
为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。
nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
当需要使用 NULL 时候,养成直接使用 nullptr的习惯。
2.类型推导
C++11 引入了 auto 和 decltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。
auto
auto 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 register 并存。在传统 C++ 中,如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。而随着 register 被弃用,对 auto 的语义变更也就非常自然了。
使用 auto 进行类型推导的一个最为常见而且显著的例子就是迭代器。在以前我们需要这样来书写一个迭代器:
1 for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)
而有了 auto 之后可以:
1 //由于cbegin()将返回vector<int>::const_iterator 2 //所以itr也应该是vector<int>::const_iterator类型 3 for(auto itr = vec.cnegin(); itr != vec.cend(); ++itr);
注意:auto 不能用于函数传参,因此下面的做法是无法通过编译的(考虑重载的问题,我们应该使用模板):
1 int add(auto x, auto y);
此外,auto 还不能用于推导数组类型
1 #include <iostream> 2 3 int main() { 4 auto i = 5; 5 6 int arr[10] = {0}; 7 auto auto_arr = arr; //错误 8 auto auto_arr2[10] = arr; //错误 9 10 return 0; 11 }
decltype
decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似:
1 decltype(表达式)
在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
有时候,我们可能需要计算某个表达式的类型,例如:
1 auto x=1; 2 auto y=2; 3 decltype(x+y) z;
你可能会思考,auto 能不能用于推导函数的返回类型。考虑这样一个例子加法函数的例子,在传统 C++ 中我们必须这么写
1 template<typename R,typename T,typename U> 2 R add(T x,U y){ 3 return x+y; 4 }
这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 add() 这个函数会做什么样的操作,获得一个什么样的返回类型。
在 C++11 中这个问题得到解决。虽然你可能马上回反应出来使用 decltype 推导 x+y 的类型,写出这样的代码:
1 decltype(x+y) add(T x, U y);
但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,x 和 y 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做拖尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:
1 template<typename T, typename U> 2 auto add(T x, U y)->decltype(x+y){ 3 return x+y; 4 }
从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:
1 template<typename T, typename U> 2 auto add(T x, U y){ 3 return x+y; 4 }
C++11 引入了基于范围的迭代写法,我们拥有了能够写出像 Python 一样简洁的循环语句。
最常用的 std::vector 遍历将从原来的样子:
1 std::vector<int> arr(5,100); 2 for(std::vector<int>::iterator i=arr.begin();i!=arr.end();++i){ 3 std::cout<<*i<<std::endl; 4 }
变得非常简单:
1 //&启用了引用 2 for(auto &i:arr){ 3 std::cout<<i<<std::endl; 4 }
C++11 提供了统一的语法来初始化任意的对象,例如:
1 struct A{ 2 int a; 3 float b; 4 }; 5 struct B{ 6 B(int _a,float _b):a(_a),b(_b){} 7 private: 8 int a; 9 float b; 10 }; 11 12 A a{1,1.1}; //统一初始化语法 13 B b{2,2.2};
C++11 还把初始化列表的概念绑定到了类型上,并将其称之为 std::initializer_list,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:
1 #include<initializer_list> 2 class Magic{ 3 public: 4 Magic(std::initializer_list<int> list){} 5 }; 6 Magic magic={1,2,3,4,5}; 7 std::vector<int>v={1,2,3,4};
传统 C++ 中,模板只有在使用时才会被编译器实例化。只要在每个编译单元(文件)中编译的代码中遇到了被完整定义的模板,都会实例化。这就产生了重复实例化而导致的编译时间的增加。并且,我们没有办法通知编译器不要触发模板实例化。
C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使得能够显式的告诉编译器何时进行模板的实例化:
1 template class std::vector<bool>; //强行实例化 2 extren template class std::vector<double>;//不在编译文件中实例化模板
在传统 C++ 的编译器中,>>一律被当做右移运算符来进行处理。但实际上我们很容易就写出了嵌套模板的代码:
1 std::vector<std::vector<int>> wow;
这在传统C++编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。
在传统 C++中,typedef 可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称。因为,模板不是类型。例如:
1 template< typename T, typename U, int value> 2 class SuckType { 3 public: 4 T a; 5 U b; 6 SuckType():a(value),b(value){} 7 }; 8 template< typename U> 9 typedef SuckType<std::vector<int>, U, 1> NewType; // 不合法
C++11 使用 using 引入了下面这种形式的写法,并且同时支持对传统 typedef 相同的功效:
1 template <typename T> 2 using NewType = SuckType<int, T, 1>; // 合法
我们可能定义了一个加法函数:
1 template<typename T,typename U> 2 auto add(T x,U y)->decltype(x+y){ 3 return x+y; 4 }
但在使用时发现,要使用 add,就必须每次都指定其模板参数的类型。
在 C++11 中提供了一种便利,可以指定模板的默认参数
1 template<typename T=int,typename U=int> 2 auto add(T x,U y)->decltype(x+y){ 3 return x+y; 4 }
C++11 引入了委托构造的概念,这使得构造函数可以在同一个类中一个构造函数调用另一个构造函数,从而达到简化代码的目的:
1 class Base{ 2 public: 3 int value1; 4 int value2; 5 Base(){ 6 value1=1; 7 } 8 Base(int value):Base(){//委托Base()构造函数 9 value2=2; 10 } 11 }
在继承体系中,如果派生类想要使用基类的构造函数,需要在构造函数中显式声明。
假若基类拥有为数众多的不同版本的构造函数,这样,在派生类中得写很多对应的“透传”构造函数。如下:
1 struct A{ 2 A(int i){} 3 A(double d,int i){} 4 A(float f,int i,const char* c){} 5 //....等等系列的构造函数版本 6 }; 7 struct B:A{ 8 B(int i):A(i){} 9 B(double d,int i):A(d,i){} 10 B(float f,int i,const char* c):A(f,i,e){} 11 //....等等好多个和基类构造函数对应的构造函数 12 };
C++11的继承构造:
1 struct A{ 2 A(int i){} 3 A(double d,int i){} 4 A(float f,int i,const char* c){} 5 //....等等系列的构造函数版本 6 }; 7 struct B:A{ 8 using A::A; 9 //关于基类各构造函数的继承一句话搞定 10 //..... 11 };
如果一个继承构造函数不被相关的代码使用,编译器不会为之产生真正的函数代码,这样比透传基类各种构造函数更加节省目标代码空间。
Lambda表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。
Lambda表达式的基本语法如下:
1 [caputrue](params)opt->ret{body;};
1) capture是捕获列表;
2) params是参数表;(选填)
3) opt是函数选项;可以填mutable,exception,attribute(选填)
mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。
exception说明lambda表达式是否抛出异常以及何种异常。
attribute用来声明属性。
4) ret是返回值类型(拖尾返回类型)。(选填)
5) body是函数体。
捕获列表:lambda表达式的捕获列表精细控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。
1) []不捕获任何变量。
2) [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
3) [=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。注意值捕获的前提是变量可以拷贝,且被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝。如果希望lambda表达式在调用时能即时访问外部变量,我们应当使用引用方式捕获。
按值捕获
1 int a=0; 2 auto f=[=]{return a;}; 3 a+=1; 4 cout<<f()<<endl; //输出0
按引用捕获
1 int a=0; 2 auto f=[&a]{return a;}; 3 a+=1; 4 cout<<f()<<endl; //输出1
4) [=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。
5) [bar]按值捕获bar变量,同时不捕获其他变量。
6) [this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lamda中使用当前类的成员函数和成员变量。
原文:https://www.cnblogs.com/yichengming/p/11182215.html