• C++ 之 C++11新特性


    简介


    在了解cocos2d-x引擎的时候,或者对项目的底层做一些修改的时候,经常会碰到C++ 11特性相关的东西。

    所以对C++ 11的一些特性进行了记录和汇总。

    更多的内容参考的是微信公众号程序喵大人(微信号:chengxumiaodaren)

    在此感谢原作者的分享。

    主要的C++11特性有:

    • 对原有的一些语言特性改进,比如nullptr,auto,delctype类型推导,for范围循环
    • 稳定性和兼容性相关增加了新特性, 比如静态断言,外部模版
    • 安全性方面的改变,比如枚举类型安全
    • 提升了C++性能相关,比如多线程并行编程
    • 对C++的类特性拓展,比如继承构造委托构造列表初始化相关
    • 增加了颠覆C++设计思想的新特性,比如lambda表达式
    简介关键字相关
    基础nullptr, long long, auto, decltype, char16_t, char32_t, constexptr, static_assert, 随机数
    enum, sizeof, 委托构造,继承构造, override, final, , default, delete, explicit
    STLstd::array, std::tuple, std::forward_list, std::unordered_map, std::unordered_set, cbegin/cend
    智能指针std::shared_ptr, std::weak_ptr, std::unique_ptr
    线程std::thread, std::mutex, std::lock, std:atomic, std::call_once, std::future, std::condition_variable, volatile, async
    其他std::function, std::bind, lamada表达式

    基础


    nullptr

    主要用于代替NULLNULL实质上就是int整形的0,不算是指针。新增的类型:

    typedef decltype(nullptr) nullptr_t;
    
    • 1

    这样使用,代码安全性更高。

    char16_t | char32_t

    用于表示Unicode字符,被用于做国际化和多语言文本处理。分别表示16位和32位。

    // 使用前缀'u'来表示char16_t类型的字符
    char16_t myChar = u'A';
    // 使用前缀'U'来表示char32_t类型的字符
    char32_t myChar = U'🌟'; 
    
    • 1
    • 2
    • 3
    • 4

    long long

    对整数类型的拓展,支持更大范围的数字

    long long int value = 100000;
    
    • 1

    auto

    自动推导变量类型,可用于简化代码,增强可读性。

    示例1:

    // 自动推导a为int类型
    auto a = 10;				
    
    int i = 10;
    // 自动推导b为int类型
    auto b = i;					
    
    int i = 10;
    // 自动推导a为int, b为i的引用, c为i的指针
    auto a = i, &b = i, *c = &i;	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    示例2:

    vector<int> vec;
    
    // old 
    for(vector<int>::iterator iter = vec.begin(); iter != vec.end(); iter++) {
    	cout << *iter << endl;
    }
    
    // new
    for (auto iter = vec.begin(); iter != vec.end(); iter++) {
      cout << *iter << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用auto注意:

    • 必须初始化,否则无法推导

    • 不能用做函数参数

    • 若一行定义多个变量,不能产生二义性,否则编译报错

    • 注意精度

    • 不能推导模版参数

    // 二义性 error: declaration of ‘auto a’ has no initializer
    auto a, b = 1, 1.0;
    
    // 精度相关,越界了
    unsigned int a = 4294967295; // unsigned int 能够存储的最大数据
    unsigned int b = 1;
    auto c = a+b;
    cout << c << endl; // 输出c为0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    decltype

    auto类似,但用于推导表达式。 注意:仅是推导,不会进行运算

    int func() {return 0;}
    decltype(func()) i;			// 推导i为int类型
    
    int x = 0;
    decltype(x) y;				// 推导y是int类型
    decltype(x + y) z;		// 推导z是int类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    for范围循环

    用于简化for循环程序,在STL迭代器中遍历较为明显,比如:

    vector<int> vec;
    
    // before
    for (auto iter = vec.begin(); iter != vec.end(); iter++) {
      cout << *iter << endl;
    }
    // new 
    for(int i:vec) {
      cout << i << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    constexpr

    它同const一样,都用于定义常量, 但:const 在运行时计算值, 而constexpr 在编译时计算值。

    在编译期间计算,避免了运行时的计算开销,提高了程序的执行效率。

    // 阶乘
    constexpr int factorial(int n) {
        return n == 0 ? 1 : n * factorial(n - 1);
    }
    // 编译期计算5的阶乘作为数组大小
    int arr[factorial(5)]; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    因在编译时计算,故此使用constexpr注意:

    • 在声明时进行初始化,且初始化表达式必须是常量表达式
    • 不能使用运行时的随机数或用户输入来初始化 constexpr 变量
    • 不支持修饰virtual成员方法

    在C++ 11增加constexpr后,const可以理解为只读属性的变量,而真正的常量用constexprt表示。

    static_assert

    新增的关键字,静态断言。可用于在编译时对表达式检查错误。

    // expression 断言的表达式,如果为false,则编译错误
    // message 错误的信息字符串
    static_assert(expression, message);
    
    • 1
    • 2
    • 3

    随机数


    对随机数进行了拓展,在丰富原有功能的同时,引入了新的随机数库, 并新增了一些随机数生成器和分布函数相关。

    新增的随机数生成器(生成随机数序列), 主要有:

    • std::mt19937
    • std::minstd_rand
    • std::linear_congruential_engine

    新增的随机分布函数:

    • 均匀分布
    • 正态分布
    • 伯努利分布

    简单的实例:

    #include 
    #include 
    #include 
    using namespace std;
    
    int main() {
      std::default_radom_engine random(time(nullptr));
      // 整数均匀分布
      std::uniform_int_distribution<int> int_dis(0, 100);
      // 浮点数均匀分布
      std::uniform_read_distribution<float> float_dis(0.0f, 1.0f);
      
      for (int i=0; i <10; ++i) {
        cout << int_dis(random) << float_dis(random)<< endl;
      }
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17


    C++ 11 对类相关的主要修改有:

    • enum 增加类作用域
    • sizeof 获取类成员对象的大小,不需要再定义对象后计算大小了
    • 增加了委托构造, 继承构造用于简化程序代码的重复编写
    • 增加了关键字overide, final, default, delete, explicit等。

    下面将列举各个示例相关:

    • enum 类中使用enum枚举类型,增加了类作用域,以及可设置类型,默认为int
    // before
    enum AColor {
      kRed, kGreen, kBlue
    };
    enum BColor {
      kWhite, kBlack, kYellow
    };
    
    if(kRed == kWhite) {}
    
    // new
    enum class AColor: int {
       kRed, kGreen, kBlue
    };
    
    enum class BColor: int {
       kWhite, kBlack, kYellow
    };
    
    // 不同作用域的比较,会导致编译失败,消除潜在的bug
    if(AColor::kRed == BColor::kWhite) {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • sizeof 在类成员对象中使用,不需要通过定义对象后,再计算对象的成员大小
    struct A {
       int data[10];
       int a;
    };
    
    // before
    int main() {
       A a;
       cout << "size " << sizeof(a.data) << endl;
       return 0;
    }
    // new
    int main() {
       cout << "size " << sizeof(A::data) << endl;
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 委托构造函数 它允许同一个类中的构造函数调用另外一个构造函数,可用于简化操作
    // before
    class A {
       A(){}
       A(int a) { a_ = a; }
    
       A(int a, int b) { // 好麻烦
           a_ = a;
           b_ = b;
       }
    
       A(int a, int b, int c) { // 好麻烦
           a_ = a;
           b_ = b;
           c_ = c;
       }
       int a_, b_, c_;
    };
    
    // new 
    class A {
       A(){}
       A(int a) { a_ = a; }
       A(int a, int b) : A(a) { b_ = b; }
       A(int a, int b, int c) : A(a, b) { c_ = c; }
    	 int a_, b_, c_;
    };
    
    • 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
    • 继承构造函数 可以让派生类直接使用基类的构造函数
    class Base {
       Base() {}
       Base(int a) { a_ = a; }
       Base(int a, int b) : Base(a) { b_ = b; }
       Base(int a, int b, int c) : Base(a, b) { c_ = c; }
    	 int a_, b_, c_;
    };
    
    // before
    class Derived : Base {
       Derived() {}
       Derived(int a) : Base(a) {} // 好麻烦
       Derived(int a, int b) : Base(a, b) {} // 好麻烦
       Derived(int a, int b, int c) : Base(a, b, c) {} // 好麻烦
    };
    
    // new 
    class Derived : Base {
       using Base::Base;
    };
    
    int main() {
       Derived a(1, 2, 3);
       return 0;
    }
    
    • 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
    • override 用于修饰派生类中的成员函数,标明要重写基类函数。
    // overide主要用于避免程序在重写基类函数时无意产生的错误
    // 如果基类中没有声明,在子类中编写的时候就会报错
    class Base {
       virtual void func() {
           cout << "base" << endl;
      }
    };
    
    class Derived : public Base{
       void func() override { // 确保func被重写
           cout << "derived" << endl;
       }
    
       // error,基类没有fu(),不可以被重写
       void fu() override {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • final 用于修饰一个类,表示禁止该类被继承和虚函数的进一步重载, 否则将会报错
    struct Base final {
       virtual void func() {
           cout << "base" << endl;
      }
    };
    
    struct Derived : public Base{ // 编译失败,final修饰的类不可以被继承
       void func() override {
           cout << "derived" << endl;
       }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • default 用于声明构造函数为默认构造函数,如果有自定义的构造函数,编译器就不会隐式生成默认构造函数
    class CC_STUDIO_DLL ILocalizationManager {
    public:
        // 在函数声明后加上“=default;”,就可将该函数声明为 defaulted 函数
      	// 编译器将为显式声明的 defaulted 函数自动生成函数体
    		virtual ~ILocalizationManager() = default;
    		virtual bool initLanguageData(std::string file) = 0;
    		virtual std::string getLocalizationString(std::string key) = 0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • delete 如果没有定义特殊成员函数,那么编译器在需要特殊成员函数时候会隐式自动生成一个默认的特殊成员函数,例如拷贝构造函数或者拷贝赋值操作符

      我们有时候想禁止对象的拷贝与赋值,可以使用delete修饰

    class A {
       A() = default;
       A(const A&) = delete;
       A& operator=(const A&) = delete;
       int a;
       A(int i) { a = i; }
    };
    
    int main() {
       A a1;
       A a2 = a1;  // 错误,拷贝构造函数被禁用
       A a3;
       a3 = a1;  // 错误,拷贝赋值操作符被禁用
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • explicit 用于修饰构造函数,表示只能显式构造,不可以被隐式转换
    class A {
       explicit A(int value) {
           cout << "value" << endl;
      }
    };
    
    int main() {
       A a = 1; // error,不可以隐式转换
       A aa(2); // ok
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    STL


    STL实现了许多通用的数据结构及处理这些结构的算法。包含三大组件:

    • 容器,用于管理某一类对象的集合。比如deque, list, vector, map
    • 迭代器,用于便利对象集合的元素。
    • 算法, 主要作用于容器,提供了各种操作的方式, 比如对容器内容的排序,搜索和转换等

    C++11新增的一些容器有:

    • std::array 替代数组的固定大小容器
    • std::forward_list 新增的单项链表,比std::list性能更高
    • std::unordered_map 新增的使用哈希表存储的map容器
    • std::unordered_set 新增的使用哈希表存储的set容器
    • std::tuple 新增的元组

    一些简单的示例:

    • std::array 封装了固定大小的容器,可替代普通的数组,支持获取容器大小、赋值、随机访问等。
    #include 
    #include 			// 声明头文件
    
    int main()
    {
    	std::array<int, 4> data = {1, 2, 3, 4};
    	for(auto value: data) {
    		std::cout << value << std::endl;
    	}
    	std::cout << sizeof(data) << std::endl;
       	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • std::forward_list 新增,接口少,与std::list相比为单项链表,运行时比std::list有更好的性能。
    #include 
    int main()
    {
        std::forward_list<int> numbers = {1,2,3,4,5,4,4};
        std::cout << "numbers:" << std::endl;
        for (auto number : numbers){
            std::cout << number << std::endl;
        }
        numbers.remove(4);
        std::cout << "numbers after remove:" << std::endl;
        for (auto number : numbers){
            std::cout << number << std::endl;
        }
      	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • std::unordered_map std::map使用类似,采用的是哈希表方式
    /*
    std::map 内部采用红黑树(一种近似平衡的二叉树)存储,为有序容器
    std::unordered_map 内部采用哈希表存储,无序容器
    
    unordered_map优点:查找元素快,内存占用低,插入删除快
    */
    #include 
    #include 
    #include 			// 头文件引用
    using namespace std;
    int main() {
        std::unordered_map<std::string, std::string> mymap ={
            { "house","maison" },
            { "apple","pomme" },
            { "tree","arbre" },
            { "book","livre" },
            { "door","porte" },
            { "grapefruit","pamplemousse" }
        };
        unsigned n = mymap.bucket_count();
        cout << "mymap has " << n << " buckets.\n";
        for (int i = 0; i<n; ++i) {
            cout << "bucket #" << i << " contains: ";
            for (auto it = mymap.begin(i); it != mymap.end(i); ++it)
                cout << "[" << it->first << ":" << it->second << "] ";
        }
        return 0;
    }
    
    • 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
    • std::unordered_set 基于哈希表的无序结合,具有快速查找、删除和增加的特点。
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    int main()
    {
        std::unordered_set<int> unorder_set;
        unorder_set.insert(7);
        unorder_set.insert(5);
        unorder_set.insert(3);
        unorder_set.insert(4);
        unorder_set.insert(6);
        std::cout << "unorder_set:" << std::endl;
        for (auto itor : unorder_set) {
            cout << itor << endl;
        }
     
        std::set<int> set;
        set.insert(7);
        set.insert(5);
        set.insert(3);
        set.insert(4);
        set.insert(6);
        std::cout << "set:" << std::endl;
        for (auto itor : set) {
            std::cout << itor << std::endl;
        }
    }
    
    • 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

    智能指针


    智能指针是一个类,用于管理申请的空间在函数结束后忘记释放,导致内存泄漏的问题。

    在超出类的作用域后,它会自动调用析构函数。主要有:

    • shared_ptr 用于处理忘记释放导致的内存泄漏,及悬空指针的问题。它实现了共享式拥有的概念,多个智能指针指向相同的对象, 当最后一个对象呗销毁的时候被释放。采用计数机制表明资源被几个指针共享。
    • unique_ptr 独占式拥有,对象对其有唯一所有权。
    • weak_ptrshard_ptr搭配,不会增加引用计数,用于避免循环引用(a对象有b, b对象有a)。它对对象的引用是弱引用,可以绑定到shared_ptr, 但不能增加对象的引用计数

    其他


    std::function

    可调用对象的函数封装器, 可调用对象需要满足如下条件:

    • 是函数指针
    • 是具有operator()成员函数的类对象,lambda表达式
    • 是可被转换为函数指针的类对象
    • 是类成员(函数)的指针
    • 被用于std::bind表达式或其他函数对象

    std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标。

    若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常

    std::function<void (cocos2d::Ref *)> arg3;
    
    • 1

    std::bind

    可调用对象和参数一起绑定,绑定后的结果使用std::function进行保存。

    并延迟调用到任何我们需要的时候, 通常有两大作用:

    1. 将可调用对象与参数一起绑定为另一个std::function供调用
    2. 将可调用对象转成m(m < n)元可调用对象,绑定一部分参数,这里需要使用std::placeholders
    MenuItemFont * MenuItemFont::create(const std::string& value, Ref* target, SEL_MenuHandler selector) {
        MenuItemFont *ret = new (std::nothrow) MenuItemFont();
        ret->initWithString(value, std::bind(selector, target, std::placeholders::_1));
        ret->autorelease();
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    lambda表达式

    用以定义了一个匿名函数,可以捕获一定范围的变量在函数内部使用, 语法形式:

    /*
    func 可以当作lambda表达式的名字,作为一个函数使用,
    capture 捕获列表
    params 参数表
    opt 是函数选项(mutable之类)
    ret 是返回值类型
    func_body 是函数体
    */
    auto func = [capture] (params) opt -> ret { func_body; };
    
    // 例子
    auto func1 = [](int a) -> int { return a + 1; };
    auto func2 = [](int a) { return a + 2; };
    cout << func1(1) << " " << func2(2) << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    lambda表达式允许捕获一定范围内的变量:

    1. []不捕获任何变量

    2. [&]引用捕获,捕获外部作用域所有变量,在函数体内当作引用使用

    3. [=]值捕获,捕获外部作用域所有变量,在函数内内有个副本使用

    4. [=, &a]值捕获外部作用域所有变量,按引用捕获a变量

    5. [a]只值捕获a变量,不捕获其它变量

    6. [this]捕获当前类中的this指针


    C++ 11的特性还在学习使用中,更多内容参考大神:程序喵大人(微信号:chengxumiaodaren)

    祝大家学习生活愉快!

  • 相关阅读:
    2-10.基金管理人的内部控制
    JavaWeb---HTML
    fiddler 手机抓包
    【鸿蒙 HarmonyOS 4.0】路由router
    【随记】在WSL2下安装ROS
    Redis三种模式——主从复制,哨兵模式,集群
    详解SOAP简单对象访问协议
    83.(cesium之家)cesium示例如何运行
    正大国际期货:2022正大期货在国际市场热度不减的几点原因
    树状DP(记忆化搜索)PAT甲级 1079 1090 1106
  • 原文地址:https://blog.csdn.net/qq_24726043/article/details/133833540