C++11新特性

auto关键字

C语言中其实就有auto关键字,修饰可变化的量,但是由于平时我们直接使用int a = 10;也是声明变量,编译器已经自动帮我们加上了auto关键字,是C语言中应用最广泛的一种类型,也就是说,省去类型说明符auto的都是自动变量! 随着时代进步,Java10中有一个新特性,就是使用var来定义变量,当然前提是类型可推导,语言总是在演化,C++11也是支持了这个新特性,不过在C++11中是auto关键字:使用auto的时候,编译器根据上下文情况,确定auto变量的真正类型! 接下来演示一下auto的使用:

 1int main() {
 2	auto a = 10;
 3	auto b = 20;
 4
 5	list<string> s;
 6
 7	list<string>::iterator be = s.begin();
 8	list<string>::iterator en = s.end();
 9
10	auto be2 = s.begin(); //很显然使用auto可以减少很多不必要的代码
11	auto en2 = s.end();
12
13	return 0;
14}

很显然使用auto可以减少很多不必要的代码,但是:

  • auto不能作为函数参数
  • auto不能直接用来声明数组
  • auto不能定义类的非静态成员变量
  • 实例化模板时不能使用auto作为模板参数
  • auto作为函数返回值时,只能用于定义函数,不能用于声明函数
  • 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

for-each

新式风格的for循环,在Java中叫做增强for循环,这个特性从JDK1.5开始被支持,随后C++11也支持了这种for循环

1int arr[] = { 1,3,5,7,9,11 };
2for(int i:arr){
3	cout << i << " ";
4}

for循环迭代的范围必须是确定的:对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围 不能对参数中的数组进行for-each,因为长度不确定

指针空值nullptr

在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。 NULL实际是一个宏,在传统的C头文件(stddef.h)中

1#ifndef NULL
2#ifdef __cplusplus
3#define NULL 0
4#else
5#define NULL ((void *)0)
6#endif
7#endif

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦:

 1void f(int){
 2	cout<<"f(int)"<<endl;
 3}
 4
 5void f(int*){
 6	cout<<"f(int*)"<<endl;
 7}
 8
 9int main(){
10	f(0);
11	f(NULL);
12	f((int*)NULL);
13	return 0;
14}

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0

为了考虑兼容性,C++11并没有消除常量0的二义性,C++11给出了全新nullptr表示空值指针。C++11为什么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器厂商对于NULL的实现可能不太相同,而且直接扩展NULL,可能会影响以前旧的程序。因此:为了避免混淆,C++11提供了 nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:

1typedef decltype(nullptr) nullptr_t;

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

long long 类型

long long 类型实际上没有在C++ 98中存在,而之后被C99标准收录,其实现在市面上大多数编译器是支持 long long 的,但是这个类型正式成为C++的标准类型是在C++11中。标准要求long long至少是64位也就是8个字节。一个字面常量使用LL后缀表示long long类型,使用ULL后缀表示unsigned long long 类型

constexpr

定义常量的时候一般使用const来定义,一个常量必须在定义的时候进行初始化,并且之后不可更改。一个常量必须使用一个常量表达式进行初始化,并且在编译期间就可以得到常量的值,但是如何确定一个表达式就是常量表达式呢,这个通常是由程序员自己确定的,所以C++11提供了一个新的关键字constexpr,使用该关键字定义的常量,由编译器检查为其赋值的表达式是否是常量表达式:

1int a = 10;
2const int i = a;//OK
3constexpr int i2 = a;//error

编译器编译的时候就会报错说a并不是常量。显然constexpr关键字将常量表达式的检查转交给编译器处理,而不是程序员自己,所以使用constexpr定义常量要比const安全!

普通的函数一般是不能用来为constexpr常量赋值的,但是C++11允许定义一种constexpr的函数,这种函数在编译期间就可以计算出结果,这样的函数是可以用来为constexpr赋值的。定义constexpr函数需要遵守一些约定,函数的返回类型以及所有形参的类型都应该是字面值,一般情况下函数体中必须有且只有一条return语句。

 1int fun(){	//error
 2	return 0;
 3}
 4
 5constexpr int fun(){	//OK
 6	return 0;
 7}
 8
 9int main(){
10	constexpr int ret = fun();
11	return 0;
12}

执行初始化的时候编译器将函数的调用替换成结果值,constexpr函数体中也可以出现除了return之外的其他语句,但是这些语句在运行时不应该执行任何操作,例如空语句,using声明等。constexpr函数允许其返回值并非是一个字面值:

 1constexpr int size(int s)
 2{
 3	return s * 4;
 4}
 5
 6int a = 20;
 7const int b = 30;
 8constexpr int c = 40;
 9constexpr int si = size(a);  //error a是一个变量所以函数返回的是一个可变的值
10constexpr int si1 = size(20); //ok 函数返回的实际上是一个常量
11constexpr int si2 = size(b);  //ok
12constexpr int si3 = size(c);  //ok

由上可知constexpr函数并不一定返回常量,如果应用于函数的参数是一个常量表达式则返回常量,否则返回变量,而该函数调用到底是一个常量表达式还是非常量表达式则由编译器来判断。这就是constexpr的好处!

using类型别名

类型别名其实早在C语言中就有了,一般情况下我们使用关键字typedef来声明一个类型的别名,在C++11中增加了另一种声明类型别名的方法就是使用using关键字,using关键字在C++11以前一般用来引用命名空间。

1typedef int INT;  // 右侧符号代表左侧
2using INT2 = int; // 左侧符号代表右侧
3
4INT a = 20;
5INT2 b = 30;

列表初始化

 1//列表初始化还可以用结构体
 2typedef struct Str {
 3	int x;
 4	int y;
 5}Str;
 6Str s = { 10,20 };
 7
 8//列表初始化类,必须是public成员,如果含有私有成员会失败
 9class Cls {
10public:
11	int x;
12	int y;
13};
14Cls c = { 10,20 };
15
16//vector不仅可以使用列表初始化,还可以使用列表进行赋值,数组不能用列表赋值
17vector<int>v1={1,2,3,4,5,6,7,8,9}; // 初始化
18vector<int>v2;
19v2={3,4,5,6,7}; //赋值
20 
21//map列表初始化
22map<string ,int> m = {{"x",1},{"y",2},{"z",3}};
23 
24//用函数返回初始化列表只展示关键代码,相关头文件自行添加
25//同理结构体,类,map的返回也可以使用初始化列表返回
26vector<int> getVector()
27{
28  return {1,2,3,4,5};
29}
30 
31int main(){
32  vector<int> v = getVector();
33  cout<<v[0]<<v[1]<<v.size()<<endl;
34  return 0 ;
35}

decltype类型指示符

decltype作用于一个表达式,并且返回该表达式的类型,在此过程中编译器分析表达式的类型,并不会计算表达式的值:

1int a = 10;
2int b = 20;
3decltype(a+b) c = 50; // OK c的类型就是 a+b 的类型int

对于引用类型decltype有一些特别的地方:

1int a = 20 ;
2int &b = a;
3decltype(b) c ;  // Error c是引用类型必须赋值
4decltype(b) d = a; // OK  d是引用类型,指向a

可以看到decltype如果作用于一个引用类型,其得到的还是一个引用类型 如果一个表达式是一个解指针引用的操作,decltype得到的也是一个引用类型:

1a = 20 ;
2int *p = &a;
3decltype(*p) c = a;  // c的类型是int&
4c = 50;
5cout << a << endl;  // 输出50

当decltype作用于一个变量的时候,变量加不加括号是有区别的,例如:

1int a = 20;
2decltype(a) b = 30; //ok b的类型是 int
3decltype((a)) c = a ; // ok c的类型是int& 其关联变量 a

加上括号之后编译器会把(a)当作是一个表达式处理,而变量是一种可以作为赋值语句左值的表达式,所以会解释成引用类型。

尾置返回类型

看看下面这个函数声明:

1int (*func(char x))[10];

很显然,func函数的参数是一个char类型的x,返回值是一个指向10个int类型的指针数组的指针(就是一个10个元素的数组,每个元素分别指向一个int类型,返回的就是数组首元素地址—>数组指针),这样的定义实在是晦涩难懂,于是C++11新特性中出现了尾置返回类型:

1auto func(char x) -> int(*) [10];

这种形式将函数的返回类型写在函数声明的最后面,并且在函数形参列表后面加上 -> 符号,然后紧接着是函数需要返回的类型,由于函数的返回类型被放在了形参列表之后,所以在函数名前面使用一个 auto替代。

=default 生成默认构造函数

在C++的类中,如果我们没有定义构造函数,编译器会为我们合成默认的无参构造函数,如果我们定义了构造函数,则编译器就不生成默认构造函数了,但是如果我们定义构造函数同时也希望编译器生成默认构造函数呢? C++11中可以通过在构造函数的声明中直接 =default的方式要求编译器生成构造函数。

1class ClassName{
2    public:
3        ClassName(int x);
4        ClassName()=default; // 显示要求编译器生成构造函数
5};

类对象成员的类内初始化

1class ClassName{
2public:
3	int x = 10; //C++11 之前是不允许的
4};

lambda表达式

1int main()
2{
3	auto add = [](int a, int b)->int {
4         return a + b;
5    };
6    int ret = add(1, 2);
7	std::cout << "ret:" << ret << std::endl;
8    return 0;
9}

缺省参数<不在C++11新特性内>

这个只是为了我做一些笔记,这个并不属于C++11性特性

  • 半缺省参数必须从右往左依次来给出,不能间隔着给,站在编译器的角度考虑这个问题
  • 缺省参数不能在函数声明和定义中同时出现
  • 缺省参数在实现与头文件分离的时候缺省参数定义只能出现在头文件中,如果头文件未定义缺省参数,那么即使在实现的时候定义缺省参数也是无法编译的
  • 综上:头文件定义了缺省参数实现的时候就不能写缺省参数,头文件没有定义缺省参数,实现的时候更不能自己加缺省参数!

参考链接: 《C++11常用特性的使用经验总结》 《C++11新特性梳理》 《C++11新特性整理》