C++11 新特性

auto 关键字

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
auto a = 10;
auto b = 20;

list<string> s;

list<string>::iterator be = s.begin();
list<string>::iterator en = s.end();

auto be2 = s.begin(); // 很显然使用 auto 可以减少很多不必要的代码
auto en2 = s.end();

return 0;
}

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

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

for-each

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

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

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

指针空值 nullptr

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

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

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

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

void f(int*){
cout<<"f (int*)"<<endl;
}

int main(){
f(0);
f(NULL);
f((int*)NULL);
return 0;
}

程序本意是想通过 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 被定义在头文件中:

1
typedef 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,使用该关键字定义的常量,由编译器检查为其赋值的表达式是否是常量表达式:

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

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

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

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

constexpr int fun(){ //OK
return 0;
}

int main(){
constexpr int ret = fun();
return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
constexpr int size(int s)
{
return s * 4;
}

int a = 20;
const int b = 30;
constexpr int c = 40;
constexpr int si = size(a); //error a 是一个变量所以函数返回的是一个可变的值
constexpr int si1 = size(20); //ok 函数返回的实际上是一个常量
constexpr int si2 = size(b); //ok
constexpr int si3 = size(c); //ok

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

using 类型别名

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

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

INT a = 20;
INT2 b = 30;

列表初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 列表初始化还可以用结构体 
typedef struct Str {
int x;
int y;
}Str;
Str s = { 10,20 };

// 列表初始化类,必须是 public 成员,如果含有私有成员会失败
class Cls {
public:
int x;
int y;
};
Cls c = { 10,20 };

//vector 不仅可以使用列表初始化,还可以使用列表进行赋值,数组不能用列表赋值
vector<int>v1={1,2,3,4,5,6,7,8,9}; // 初始化
vector<int>v2;
v2={3,4,5,6,7}; // 赋值

//map 列表初始化
map<string ,int> m = {{"x",1},{"y",2},{"z",3}};

// 用函数返回初始化列表只展示关键代码,相关头文件自行添加
// 同理结构体,类,map 的返回也可以使用初始化列表返回
vector<int> getVector()
{
return {1,2,3,4,5};
}

int main(){
vector<int> v = getVector();
cout<<v [0]<<v [1]<<v.size()<<endl;
return 0 ;
}

decltype 类型指示符

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

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

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

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

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

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

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

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

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

尾置返回类型

看看下面这个函数声明:

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

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

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

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

=default 生成默认构造函数

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

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

类对象成员的类内初始化

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

lambda 表达式

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

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

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

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

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