C++11的新特性

1、初始化列表

 1 #include <iostream>
 2 #include <vector>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     std::vector<int> v1{1, 2, 3};
 9     
10     std::cout << "v1 size : " << v1.size() << std::endl;
11     
12     return 0;
13 }

上述程序用C++03标准是编不过去的。

2、auto 自动类型推导

 1 #include <iostream>
 2 #include <vector>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     std::vector<int> v1{1, 2, 3, 4};
 9     
10     //c++ 03
11     for(std::vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
12         std::cout << *it << " ";
13     std::cout << std::endl;
14     
15     // c++ 11
16     for(auto it = v1.begin(); it != v1.end(); it++)
17         std::cout << *it << " ";
18     std::cout << std::endl;
19     
20     
21     return 0;
22 }

如果不使用auto自动类型推导的话,则需要自己写复杂的类型。

3、foreach 写法

 1 #include <iostream>
 2 #include <vector>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     std::vector<int> v1{1, 2, 3, 4};
 9     
10     //c++ 03
11     for(std::vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
12         std::cout << *it << " ";
13     std::cout << std::endl;
14     
15     //c++ 11 read only
16     for(auto i : v1)
17         std::cout << i << " ";
18     std::cout << std::endl;
19     
20     for(const auto& i : v1)
21         std::cout << i << " ";
22     std::cout << std::endl;
23     
24     for(auto& i : v1)
25         i = 3;
26     for(auto i : v1)
27         std::cout << i << " ";
28     std::cout << std::endl;
29     
30     
31     return 0;
32 }

foreach写法。

4、nullptr代替了C++03 的NULL

5、enum class 代替了C++03的enum

 1 #include <iostream>
 2 #include <vector>
 3 
 4 using namespace std;
 5 
 6 enum type1{a, b, c};
 7 
 8 
 9 int main()
10 {
11     // 虽然a的类型被定义为type1,但是引用a的时候可以直接引用,也就是说这里的enum是弱类型的,加不加都一样
12     if(a == 0)
13         std::cout << "type1::a == 0" << std::endl;
14     
15     return 0;
16 }

弱类型测试,运行结果:

 

 可以看到我们在main函数中直接使用a也是可以的,也就是说enum定义的type1是弱类型,所以如果多个enum中定义了相同的符号就会报符号冲突的错误。

 1 #include <iostream>
 2 #include <vector>
 3 
 4 using namespace std;
 5 
 6 // 非强类型的作用域
 7 // C中的匿名enum, 两种枚举类型都有a枚举常量,而且作用域相同,发生了冲突
 8 //enum type1{a, b, c};
 9 //enum type2{a, d, e};
10 
11 enum type1{a, b, c};
12 enum type2{d, e, f};
13 
14 enum type3{h=1, i=0xFFFFFFFFFLL};
15 
16 int main()
17 {
18     // 不同的enum类型可以互相比较,因为编译器首先将enum转换为int类型,再做的比较
19     if(type1::a == type2::d)
20         std::cout << "type1::a == type2::d" << std::endl;
21     
22     // 占用的存储空间及符号不确定
23     std::cout << sizeof(type3::h) << std::endl;
24     std::cout << sizeof(type3::i) << std::endl;
25     
26     return 0;
27 }

如果type1和type2中定义相同名字的成员则会报错,因为enum是弱类型的。非强类型作用域。

两种不同enum常量比较没有意义,但是编译器先将enum常量转换成int类型后再比较,不会报错

运行结果:

 

 可以看出打印的大小都是8,因为b是一个比较大的数,所以按最大的来。

 C++11中enum的强作用域:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 enum class type4:int{a, b, c}; // 指定底层类型
 6 enum class type5{a, b, c};   // 不指定底层类型
 7 
 8 
 9 int main()
10 {
11     type4 t = type4::a;  //必须指定强类型名称
12     type4 p = a;
13     
14     return 0;
15 }

12行的a没有指定强类型,编译报错。

 

 不同类型之间也不能直接赋值。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 enum class type4:int{a, b, c}; // 指定底层类型
 6 enum class type5{a, b, c};   // 不指定底层类型
 7 
 8 
 9 int main()
10 {
11     type4 t = type4::a;  //必须指定强类型名称
12     //type4 p = a;       //这里a没有写作用域,编译报错
13     
14     // 相同类型可以比较
15     if(t < type4::b)
16         std::cout << "t < type4::b" << std::endl;
17     
18     // 非相同类型,不可隐式类型转换,编译报错
19     //if(t < type5::b)
20     //    std::cout << "t < type5::b" << std::endl;
21     
22     //可以强制类型转换
23     if((int)t < int(type5::b) )
24         std::cout << "(int)t < int(type5::b)" << std::endl;
25     
26     return 0;
27 }

上面是强类型的测试,不会有隐式类型转换。

指定底层类型:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 enum class type6:char{a=1, b=2}; 
 6 enum class type7:unsigned int{a=0xFFFFFFFF};  
 7 
 8 int main()
 9 {
10     std::cout << "sizeof(type6::a) = " << sizeof(type6::a) << std::endl;
11     std::cout << "sizeof(type7::a) = " << sizeof(type7::a) << std::endl;
12     
13     return 0;
14 }

运行结果:

 

 6、override关键字标识符

在子类中如果想重写某个虚函数,那么后面最好跟上override关键字,这样如果函数签名不小心写错的话,编译器会检查出来。

如果不写override关键字那么编译器检查不出来,这样的话相当于重新定义了一个函数,可能会引发bug。

7、final关键字

指示一个类或者虚函数不能再被重写

8、关键字default标识

强制编译器生成默认构造函数。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class test
 6 {
 7 public:
 8     test(int a)
 9     {
10         data = a;
11     }
12     
13     test() = default;
14     
15 private:
16     int data;
17 };
18 
19 int main()
20 {
21     test t;
22     
23     return 0;
24 }

我们定义了一个类并且定义了构造函数,这个时候默认构造函数是会被隐藏的。如果不写第13行,那么第21行是编译不过去的,因为第21行会调用默认

构造函数,但是默认构造函数被隐藏了,所以会报错。 加上第13行强制生成一个默认构造函数就好了。

 9、关键标识delete

delete放在函数后面,便是函数不能再被调用

 1 #include <iostream>
 2 
 3 class dog {
 4 public:
 5     dog(int age_) {
 6         age = age_;
 7     }
 8     
 9     dog(double age_) = delete;
10     
11     int age;
12 };
13 
14 int main(){    
15     dog(2);   
16     dog(4.5); 
17     
18     return 0;
19 }

如果不加第9行,第16行是可以编译过的,因为4.5这个double类型会被强制转换为int类型而调用第5行的构造函数。

如果加上第9行,表示删掉这个构造函数,这样就不会进行隐式类型转换了,而是编译直接报错。

10、lambda 函数

1 #include <iostream>
2 
3 int main(){    
4     std::cout << [](int x, int y) {return x + y; }(3, 5) << std::endl;
5 }

lambda表达式方便定义一些小函数,使程序更优雅。

11、智能指针

shared_ptr、unique_ptr、weak_ptr

12、tuple元组

 tuple容器,表示元组容器,可以用于函数返回多个返回值。

tuple容器,可以使用直接初始化和make_tuple初始化,访问元素使用get<>()方法,注意,get里面的位置信息必须是常量表达式。

可以通过std::tuple_size<decltype(t)>::value获取元素数量,std::tuple_element<0, decltype(t)>::type获取元素类型。

如果tuple类型进行比较,则需要保持元素数量相同,类型可以比较(相同类型,或者可以相互转换的类型)。

无法使用for类似的方法遍历tuple容器,因为get<>()方法无法使用变量获取值。

 1 #include <iostream>
 2 #include <tuple>
 3 
 4 using namespace std;
 5 
 6 std::tuple<std::string, int>  get_tuple()
 7 {
 8     std::string s("test_tuple1");
 9     int a{2020};
10     
11     std::tuple<std::string, int> t = make_tuple(s, a);
12     
13     return t;
14 }
15 
16 
17 int main()
18 {
19     std::tuple<int, double, std::string> t1(64, 128.0, "test_tuple2");
20     std::tuple<std::string, std::string, int> t2 = std::make_tuple("test_tuple3", "test_tuple4", 200);
21     
22     // 测试返回元素个数
23     size_t num = std::tuple_size<decltype(t1)>::value;
24     std::cout << "t1 size : " << num << std::endl;
25     
26     // 获取第一个值的元素类型
27     std::tuple_element<1, decltype(t1)>::type v1 = std::get<1>(t1);
28     auto v2 = std::get<1>(t1);
29     std::cout << "v1 : " << v1 << std::endl;
30     std::cout << "v2 : " << v2 << std::endl;
31     
32     // tuple的比较
33     std::tuple<int, int> ti(24, 48);
34     std::tuple<double, double> td(28.0, 56.0);
35     
36     if(ti < td)
37         std::cout << "ti < td" << std::endl;
38     else
39         std::cout << "ti >= td" << std::endl;
40     
41     
42     // 测试tuple的返回值
43     auto ret = get_tuple();
44     
45     std::cout << "first : " << std::get<0>(ret) << "  , second : " << std::get<1>(ret) << std::endl;
46     
47     return 0;
48 }

运行结果:

 get也可以用在pair和array上。

get可能返回引用和常引用。这个跟参数的类型有关,使用时仔细查官方文档。

13、pair的使用

pair是将两个数据合成一个数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。

 1 #include <iostream>
 2 #include <utility>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     std::pair<int, float> pair1 = make_pair(18, 3.14f);
 9     
10     std::cout << pair1.first << " " << pair1.second << std::endl;
11     pair1.first = 20;
12     pair1.second = 6.28f;
13     
14     std::cout << pair1.first << " " << pair1.second << std::endl;
15     
16     std::pair<float, double> pair2(3.14f, 6.28f);
17     std::cout << pair2.first << " " << pair2.second << std::endl;
18     
19     return 0;
20 }

运行结果:

 

 上面程序中可以看到pair的两种构造方式。

 14、定义常量 constexpr

  对于修饰object来说,const并未区分出编译期常量和运行期常量, 而constexpr限定在了编译期常量

  对于修饰函数来说,constexpr修饰的函数,返回值不一定是编译期常量。 # It is not a bug, It is a feature。

程序:

 1 #include <iostream>
 2 #include <array>
 3 
 4 using namespace std;
 5 
 6 constexpr int foo(int i)
 7 {
 8     return i + 5;
 9 }
10 
11 int main()
12 {
13     int i = 10;
14     std::array<int, foo(5)> arr; // Ok
15     
16     std::cout << "arr size : " << arr.size() << std::endl;
17     
18     foo(i); // Call is Ok
19     
20     //std::array<int, foo(i)> arr1; // Error
21     
22     return 0;
23 }

上面的程序中14行的foo(5)可以在编译期确定,而array的第二个模板参数正好需要常量,所以可以编译通过。

第20行foo(i)无法在编译期确定,所以编译会报错。

constexpr修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,那么这个函数就会产生编译时期的值。但是,传入的参数如果不能在编译时期计算出来,那么constexpr修饰的函数就和普通函数一样了。不过,我们不必因此写两个版本,所以如果函数体适用于constexprh函数的条件,可以尽量加上constexpr。

而检测constexpr函数是否产生编译时期值的方法很简单,就是利用std::array需要编译期常值才能编译通过的小技巧。这样的话,即可检测你所写的函数是否真的产生编译期常值了。

15、decltype和auto

auto能让编译器通过初始值来进行类型推演,从而获得定义变量的类型,所以auto定义的变量必须有初始值。

auto会忽略引用,会忽略顶层const,如果希望推断出auto类型是一个顶层const,需要明确指出,例:const auto f = xxx;

当我们希望从表达式中推断出要定义的变量,但却不想用表达式的值去初始化变量,还有可能函数的返回类型为某表达式的值类型。这时候用auto就显得无力了。这时需要使用decltype,它的作用是选择并返回操作数的数据类型,在此过程中,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。

decltype和auto在推导类型时还有一些深坑,需要查阅文档。

decltype还有一个用途就是在c++11引入的后置返回类型。

https://www.cnblogs.com/Excr/p/12516095.html

decltype推导规则:

  1. 如果e是一个没有带括号的标记符表达式或者类成员访问表达式,那么的decltype(e)就是e所命名的实体的类型。此外,如果e是一个被重载的函数,则会导致编译错误。
  2. 否则 ,假设e的类型是T,如果e是一个将亡值,那么decltype(e)为T&&
  3. 否则,假设e的类型是T,如果e是一个左值,那么decltype(e)为T&。
  4. 否则,假设e的类型是T,则decltype(e)为T。

16、后置返回类型

 1 #include <iostream>
 2 using namespace std;
 3 
 4 template <typename _Tx, typename _Ty>
 5 auto multiply1(_Tx x, _Ty y)
 6 {
 7     return x*y;
 8 }
 9 
10 
11 /*
12 template <typename _Tx, typename _Ty>
13 auto multiply1(_Tx x, _Ty y)->decltype(x * y)
14 {
15     return x*y;
16 }
17 */
18 
19 
20 int main()
21 {
22     auto ret = multiply1(30, 50);
23     
24     std::cout << "ret : " << ret << std::endl;
25     
26     return 0;
27 }

上面的代码用c++11标准是编译不过去的。报错如下:

 

 使用C++14标准是可以编译通过的。

如果非要使用C++11标准编译,那么需要使用后置返回类型。

 1 #include <iostream>
 2 using namespace std;
 3 
 4 /*
 5 template <typename _Tx, typename _Ty>
 6 auto multiply1(_Tx x, _Ty y)
 7 {
 8     return x*y;
 9 }
10 */
11 
12 template <typename _Tx, typename _Ty>
13 auto multiply1(_Tx x, _Ty y)->decltype(x * y)
14 {
15     return x*y;
16 }
17 
18 int main()
19 {
20     auto ret = multiply1(30, 50);
21     
22     std::cout << "ret : " << ret << std::endl;
23     
24     return 0;
25 }

这样使用C++11标准就可以编译通过了。

17、完美转发

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 template<typename T>
 6 void PrintT(T& t)
 7 {
 8     cout << "lvaue" << endl;
 9 }
10 
11 template<typename T>
12 void PrintT(T && t)
13 {
14     cout << "rvalue" << endl;
15 }
16 
17 template<typename T>
18 void TestForward(T && v)
19 {
20     PrintT(v);
21     PrintT(std::forward<T>(v));
22     PrintT(std::move(v));
23 }
24 
25 void Test()
26 {
27     TestForward(1);
28     std::cout << std::endl;
29     int x = 1;
30     TestForward(x);
31     std::cout << std::endl;
32     TestForward(std::forward<int>(x));
33 }
34 
35 int main()
36 {
37     Test();
38     
39     return 0;
40 }

forward作用于一个变量,非左值引用,就返回右值引用。如果是左值引用,则返回原来的类型,也就是左值引用。

运行结果:

 

 第32行的forward<int>(x),因为x是非左值引用,所以调用完forward之后表达式就变成了右值引用。

参考:https://www.cnblogs.com/qicosmos/p/3376241.html

 引用折叠:

https://blog.csdn.net/douzhq/article/details/89504508

完美转发是目标,也就是保证传入函数的参数是什么类型,转发给子函数时就是什么类型。

引用折叠是人为定义出来的规则,具体由forward来实现,最终达到完美转发的目标。

 https://blog.csdn.net/beyongwang/article/details/54025439?utm_source=blogxgwz6

 

18、可变参数模板

 

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/13552146.html