zoukankan      html  css  js  c++  java
  • 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、可变参数模板

     

  • 相关阅读:
    20170926-构建之法:现代软件工程-阅读笔记
    我的swift的ui标签
    内存管理:内存泄漏和空悬指针
    闭包
    泛型,修饰符和异常处理
    类型转换,接口和扩展
    初始化2
    类的继承和初始化1
    枚举与可选值
    swift中的类和结构
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/13552146.html
Copyright © 2011-2022 走看看