C++11是自C++98十余年来发布的一个新特性,扩充了很多C++的功能和特性,而C++14是对C++11的又一次补充和优化,这些新特性使得C++更贴近于一种现代化的变成语言。gcc版本大于5(clang版本大于3.8)已经全面支持C++14,并且在编译时需要开启-std=c++14选项用来支持c++14的特性,推荐在代码中直接使用c++14特性而不是c++11。
类型推导和判断
类型推导
auto关键字早已存在于C++当中,假如一个变量类型不是register,那么就是auto。但是随着register的弃用(编译器自动优化变量存放位置),auto的语意也显得多此一举了,因此c++11赋予关键字auto新的语意实现自动类型推导,其推导阶段在编译期实现,而且由于编译期间需要判断左右值是否匹配,所以不会对编译和运行速度带来影响:
1 auto i = 1; //OK
2 auto i; //ERR,auto需要一个初始化值完成推导
3
4 int max(int, int)
5 auto fun = max; //OK
6
7 auto str = "ABC"; //OK
8
9 int max(auto a, auto b); //ERR,因为重载的原因,不能这么使用
10
11 for (auto iter = vec.cbegin(); iter != vec.cend(); iter++){}; //OK
12
13 template <typename T1, typename T2, typename T3>
14 auto add(T2 a, T3 b) { //仅在C++14中合法,c++11不支持
15 return a+b;
16 }
类型判断
类型判断的引入主要是为了获取变量的类型,使用decltype()
可以在编译期间获取变量的类型:
1 auto a = 1;
2 auto b = 2;
3 decltype(a+b) c; //ok
序列迭代
C++11引入了一种简单的for语法用于快速迭代序列:
1 std::vector<int> a = {1, 2, 3, 4};
2 for (auto item : a) {
3 std::cout << item << std::endl;
4 }
初始化列表扩展
我们知道,c++可以使用{}
实现对数组、普通的结构体(没有构造函数和析构函数)的初始化,但是初始化列表并不能对对象和函数使用,因此c++11使用std::initializer_list
对这一特性进行了拓展:
1 #include <initializer_list>
2 class Test {
3 public:
4 Test(std::initializer_list<int>){};
5 };
6
7 Test a = {1, 2, 3}; //初始化类
8
9 int fun(std::initializer_list<int> list) {};
10
11 fun({1, 2, 3}); //作为函数形参
其次,c++提供了统一的形式,完成对任意类型对象的初始化:
1 class Person {
2 public:
3 Person(std::string _name, int _age, std::string _id): name(_name), age(_age), id(_id){};
4 private:
5 std::string name;
6 int age;
7 std::string id;
8 };
9
10 struct Person_{
11 Person_(std::string _name, int _age, std::string _id): name(_name), age(_age), id(_id){};
12 private:
13 std::string name;
14 int age;
15 std::string id;
16 };
17 //统一的初始化语法
18 Person c_person {"xiaoming", 18, "1234567"};
19 Person_ s_person {"xiaohong", 17, "7654321"};
类特性修改
类中默认函数行为
我们知道在没有指定的情况下,c++会对类设置默认的构造函数、拷贝构造函数、赋值函数以及析构函数,但是有时候我们并不需要这些默认函数,因此在C++11中引入了对这些特性进行精确控制的特性:default指定生成默认函数,delete指定禁用默认函数。如果禁用了默认的构造函数和析构函数,必须指定一个自定义的函数。
1 class Test {
2 public:
3 Test() = default; //指定为Test类生成默认构造函数,如果设置为delete,就是禁用默认构造函数,如果禁用了
4 ~Test() = default; //默认析构函数
5 Test(const Test&) = delete; //禁用拷贝构造函数
6 Test& operator=(const Test&) = delete; //禁用类赋值函数
7 };
8
9 Test a;
10 Test b(a); //error,因为已经被禁用
11 Test c = a; //error,因为已经被禁用
构造函数特性
C++11提供了两种新的构造函数特性,用于提升类构造的效率,分别是委托构造和继承构造,前者主要用于多构造函数的情况,而后者用在类继承方面:
- 委托构造
委托构造的本质为了简化函数代码,做到复用其他构造函数代码的目的。
1 class Test {
2 public:
3 Test() {
4 a = 1;
5 }
6 Test(int _b) : Test() {
7 b = _b;
8 }
9
10 int a,b;
11 };
12
13 Test t(2); //会调用Test()将a赋值为1
- 继承构造
c++在继承的时候,需要将构造函数的参数逐个传递到积父类的构造函数中完成父类的构造,这种效率是很低下的,因此c++11引入了继承构造的特性,使用using
关键字:
1 class Test {
2 public:
3 Test(int _a, int _b) : a(_a), b(_b) {};
4 int a,b;
5 };
6
7 class Test2 : public Test {
8 using Test::Test;
9 }
10
11 Test2 t(2, 3); //会调用父类的构造函数
显式控制虚函数重载
由于虚函数的特性,可能会被意外进行重写,为了做到精确对虚函数重载的控制,c++11使用了override
和final
关键字完成对这一特性的实现,下面看例子:
1 class Test {
2 public:
3 Test() {};
4 virtual int fun(int);
5 };
6
7 class Test2 : public Test {
8 using Test::Test;
9 int fun(int) override; //显式声明对虚函数进行重载
10 int fun(float) override; //错误,父类没有这个虚函数
11 }
而final
关键字是为了显式终结类的继承和虚函数的重载使用:
1 class Test {
2 public:
3 virtual int fun(int) final;
4 };
5
6 class Test2 final: public Test {
7 int fun(int) override; //非法,因为该虚函数已经设置为finale,禁止重载
8 };
9
10 class Test3 : public Test2 {}; //非法,Test2已经设置为final,禁止作为父类
nullptr
和constexpr
之所以引入nullptr
是为了解决NULL
的诟病,在之前的c++中,NULL
可以被定义为int
0或者一个0值的指针,这就带来一个问题:
1 void fun(int);
2 void fun(void *);
3
4 fun(NULL); //无法确定使用的是哪一个重载函数,需要视NULL的定义而定
而nullptr
现在定义为一个空指针,避免了NULL带来的问题。constexpr
定义了一个用户显式的声明函数或对象构造函数在编译期间会成为常数,从 C++14 开始,constexptr
函数可以在内部使用局部变量、循环和分支等简单语句:
1 //这个函数会在编译期间进行计算
2 constexpr int fun(const int n) {
3 if (1 == n) return 1;
4 else if (2 == n) return 1;
5 else return fun(n - 1) + fun(n - 2);
6 }
强枚举类型
c++11引入了enum class
来保证枚举不会被隐式转换:
1 enum class test : int {
2 v1 = 0,
3 v2 = 1
4 };
5
6 if (test::v1 == 0) //错误,不能把test类型与int做隐式转换
7
8 if (test::v1 == test(0)) //正确,显示转换后进行比较
模板增强
类型别名
使用using
关键字更加直观的定义别名:
1 typedef int (*fun)(int *); //以前c++的做法,声明一个参数为`int *`,返回值为int的函数指针,名字叫fun
2 using fun = int (*)(int *); //c++11,这样更加直观
3
4 template <typename T>
5 using newType = std::pair<T, T>;
变长模板和默认模板参数
c++11可以在定义模板时,给与模板一个默认的类型,这样可以在不设置类型的时候使用默认类型:
template <typename T = int, typename U = std::string>
同时c++11可以设置模板参数为任意个数:
1 template <typename T1, typename... TS> //可以接受至少一个模板类型
2 template <typename... TS> //至少0个
Lamda表达式
lamda表达式的语法如下:
1 [捕获列表] (函数参数) 异常属性 -> 返回类型 {
2 //函数体
3 }
捕获列表是lamda表达式内部使用的外部参数列表。
标准库扩充
c++11对多个标准库进行了扩充,包括:
1. 新增加容器
- 数组容器:std::array
- 单项链表容器:std::forward_list
- 无序容器:std::unordered_set
- 无序映射:std::unordered_map
- 元组:std::tuple
2. 正则表达式
3. 语言级的线程支持
4. 智能指针和引用计数
5. 函数绑定和包装