• c++11的一些新特性


    1. {}初始化

    在C++中,使用花括号初始化的方式被称为列表初始化。列表初始化可以用于数组、结构体、类等类型的初始化。在C++11之前,列表初始化仅能用于数组和POD类型的初始化。C++11新标准将列表初始化应用于所有对象的初始化。以下是一些使用列表初始化的例子:

    struct Point 
    {
        int _x;
        int _y;
    }; 
    
    class foo 
    {
    public:
        foo(int i, double d) :_i(i), _d(d) {} // 构造函数
    private:
        int _i;
        double _d;
    };
    int main()
    {
        int arr[]{ 1, 2, 3, 4, 5 }; // 数组
        Point p{ 1,2 }; // 结构体
        foo f{ 1, 3.14 }; // 类
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    以上代码中,arr 是一个整型数组,使用列表初始化方式进行了初始化;p 是一个结构体,使用列表初始化方式对其成员变量进行了初始化;f 是一个类对象,使用列表初始化方式对其成员变量进行了初始化。

    2. 范围for循环

    在C++中,范围for循环是一种用于遍历数组、容器、初始化列表等类型的语法结构。它的语法格式如下:

    for (declaration : expression)
    {
        // 循环体
    }
    
    • 1
    • 2
    • 3
    • 4

    其中,declaration 表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression 是要遍历的对象,它可以是表达式、容器、数组、初始化列表等。

    以下是一个使用范围for循环的例子:

    #include 
    #include 
    
    int main()
    {
        std::vector<int> v = {1, 2, 3, 4, 5};
        for (auto value : v)
        {
            std::cout << value << " ";
        }
        std::cout << std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上代码中,v 是一个整型向量,使用范围for循环方式进行了遍历。
    需要注意的是,在使用范围for循环遍历容器时,循环会自动以容器为范围展开,并且循环中也屏蔽掉了迭代器的遍历细节,直接抽取容器中的元素进行运算,使用这种方式进行循环遍历会让编码和维护变得更加简便。

    其中还有一个点需要注意,上述value相当于一个形参,也就是说value改变,不影响数组v的改变,那么怎么在遍历的时候又能修改数组v?其实可以使用引用访问元素,如下:

    #include 
    #include 
    
    int main()
    {
        std::vector<int> v = { 1, 2, 3, 4, 5 };
        cout << "没有使用引用访问:";
        for (auto value : v)
        {
            value *= 2;
        }
        for (auto value : v)
        {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    
    
        cout << "使用引用访问:";
        for (auto &value : v)
        {
            value *= 2;
        }
        for (auto value : v)
        {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    
        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
    • 29
    • 30
    • 31

    在这里插入图片描述
    在C++中,使用范围for循环时,如果使用引用访问元素,可以避免对元素进行拷贝,从而提高程序的效率。当使用值访问元素时,会对元素进行一次拷贝,而使用引用访问元素时,则不会进行拷贝。因此,在不改变元素的情况下,使用引用访问元素可以减少一次拷贝,提高程序的效率。

    3. final与override

    在C++11中,override 和 final 是两个新的关键字,用于增强代码的安全性和可读性。

    override 用于在派生类中重写基类的虚函数时,显式地告诉编译器此函数是重写基类的虚函数。如果重写时函数名、参数列表和返回类型都和基类的虚函数一致,但是没有加上 override 关键字,那么编译器无法判断是否是故意的重写,容易导致程序出错。加上 override 关键字后,编译器会在编译时检查是否真的重写了基类的虚函数,如果没有则会报错,从而避免了这种错误。

    final 用于修饰类、函数或者变量,表示它们是终态的,不能被派生类、重写或者修改。使用 final 关键字可以防止子类再覆写父类的虚函数。如果一个虚函数被声明为 final,则派生类不能再重写它。

    4. 右值引用

    4.1 左值引用和右值引用

    什么是左值?什么是左值引用?
    左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

    int main()
    {
        // 以下的p、b、c、*p都是左值
        int* p = new int(0);
        int b = 1;
        const int c = 2;
        // 以下几个是对上面左值的左值引用
        int*& rp = p;
        int& rb = b;
        const int& rc = c;
        int& pvalue = *p;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    什么是右值?什么是右值引用?
    右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

    int main()
    {
        double x = 1.1, y = 2.2;
        // 以下几个都是常见的右值
        10;
        x + y;
        fmin(x, y);
        // 以下几个都是对右值的右值引用
        int&& rr1 = 10;
        double&& rr2 = x + y;
        double&& rr3 = fmin(x, y);
        // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
        //10 = 1;
        //x + y = 1;
        //fmin(x, y) = 1;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。

    4.2 左值引用与右值引用比较

    1. 左值引用是用符号 & 声明的,它只能绑定到左值,即可以取地址、有名字、非临时的对象。左值引用可以用来修改或读取所绑定的对象的值。
    2. 右值引用是用符号 && 声明的,它只能绑定到右值,即不能取地址、没有名字、临时的对象。右值引用可以用来延长所绑定的对象的生命周期,或者实现移动语义,即将对象的资源从一个所有者转移到另一个所有者,而不需要进行拷贝。
    3. 一个例外是,const左值引用可以绑定到右值,这样可以实现对右值的只读访问,而不改变其生命周期。
    4. 另一个例外是,在函数重载时,如果有一个参数既可以接受左值引用,又可以接受右值引用,那么编译器会优先选择左值引用。这是为了避免对左值进行不必要的移动操作。

    左值引用和右值引用的优缺点如下:

    1. 左值引用的优点是可以对所引用的对象进行修改或读取,而不需要拷贝或移动。左值引用的缺点是不能绑定到右值,如果需要绑定到右值,必须使用常量左值引用,但这样就不能修改所引用的对象了。
    2. 右值引用的优点是可以实现移动语义,减少拷贝或赋值操作的开销,提高程序的效率。右值引用的缺点是不能修改所引用的对象,而且会改变所引用对象的状态,使其失去资源的所有权。

    5. lambda表达式

    在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法,但是如果排序的是自定义类型元素,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

    在C++11中,Lambda表达式是一种用于定义匿名函数的语法结构。Lambda表达式可以用于任何需要函数对象的地方,例如函数参数、返回值、STL算法等。Lambda表达式的语法格式如下:

    [capture-list] (parameters) mutable -> return-type { statement };
    
    • 1

    lambda表达式各部分说明:

    1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
    2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
    3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
    4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
    5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

    注意:
    在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

    int main()
    {
        // 最简单的lambda表达式, 该lambda表达式没有任何意义
        [] {};
        // 省略参数列表和返回值类型,返回值类型由编译器推导为int
        int a = 3, b = 4;
        [=] {return a + 3; };
        // 省略了返回值类型,无返回值类型
        auto fun1 = [&](int c) {b = a + c; };
        fun1(10);
        cout << a << " " << b << endl;
        // 各部分都很完善的lambda函数
        auto fun2 = [=, &b](int c)->int {return b += a + c; };
        cout << fun2(10) << endl;
        // 复制捕捉x
        int x = 10;
        auto add_x = [x](int a) mutable { x *= 2; return a + x; };
        cout << add_x(10) << endl;
        
        return 0;
    }
    
    int main()
    {
        int x = 10, y = 20;
        auto func1 = [](int x = 1,int y = 2) //当捕捉列表和参数列表都有x,y,优先用参数列表中的值。
        {
            cout << x + y << endl; // 3
        };
        func1();
    
        return 0;
    }
    
    int main()
    {
    	int x = 0, y = 1;
    	int m = 0, n = 1;
    
    	auto swap1 = [](int& rx, int& ry)
    	{
    		int tmp = rx;
    		rx = ry;
    		ry = tmp;
    	};
    
    	swap1(x, y);
    	cout << x << " "<< y << endl;
    
    	// 引用捕捉
    	auto swap2 = [&x, &y]()
    	{
    		int tmp = x;
    		x = y;
    		y = tmp;
    	};
    
    	swap2();
    	cout << x << " " << y << endl;
    
    	// 混合捕捉
    	auto func1 = [&x, y]()
    	{
    		//...
    	};
    
    	// 全部引用捕捉
    	auto func2 = [&]()
    	{
    		//...
    	};
    
    	// 全部传值捕捉
    	auto func3 = [=]()
    	{
    		//...
    	};
    
    	// 全部引用捕捉,x传值捕捉
    	auto func4 = [&, x]()
    	{
    		//...
    	};
    
    	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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    lambda表达式的优点是可以使代码更加简洁紧凑,并且可以避免定义不必要的函数对象。Lambda表达式的缺点是可能会降低代码的可读性和可维护性。

    6. 声明

    6.1 auto

    auto是C++11引入的一个关键字,用于在声明变量时自动推导变量的类型。auto的使用可以让编译器在编译期间自动推算出变量的类型,这样就可以更加方便的编写代码了。auto还可以用于定义函数返回值类型,但此时auto仍然使用的是模板实参推断的机制,因此返回类型为auto的函数如果返回一个初始化列表,则会出错。

    6.2 decltype

    在C++11中,decltype 是一种用于推导表达式类型的关键字。decltype 可以用于推导变量、函数返回值、表达式等的类型。decltype 的语法格式如下:

    decltype(expression)
    
    • 1

    其中,expression 是要推导类型的表达式。

    以下是一个使用 decltype 的例子:

    #include 
    
    int main()
    {
        int i = 42;
        decltype(i) j = i + 1;
        std::cout << "i = " << i << ", j = " << j << std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    以上代码中,使用了 decltype 推导了变量 j 的类型。

    decltype 的优点是可以在编译期间推导出表达式的类型,从而提高程序的效率。decltype 的缺点是可能会降低代码的可读性和可维护性。

    6.3 nullptr

    C++11中,nullptr是一个用于表示空指针的关键字,它可以替代C++03中的0或NULL。nullptr的类型是std::nullptr_t,它可以隐式转换为任意类型的指针或成员指针,但不能转换为整数类型或布尔类型。nullptr的优点是可以避免一些类型推导的歧义,例如在函数重载或模板参数推导时。nullptr的缺点是可能会与一些旧代码不兼容,例如使用NULL作为整数常量的代码。

    在C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

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

    7. 可变参数模版

    在C++11中,可变参数模板是一种用于定义可变数量参数的模板。它允许模板函数或类接受任意数量的参数,包括类型、非类型和模板参数。可变参数模板的语法格式如下:

    template<typename... Args>
    void foo(Args... args)
    {
        // 函数体
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中,Args 是一个模板参数包,可以接受任意数量的模板参数。在函数体中,可以使用 args… 来展开参数包,以便对每个参数进行操作。

    可变参数模版中有一点需要注意,在使用sizeof()求可变参数的个数时,应该这样书写:sizeof…(args),//错误格式sizeof(args…);如下所示:

    template <class ...Args>
    void ShowList(Args... args)
    {
        cout << sizeof...(args) << endl; //求可变参数的个数
    }
    int main()
    {
        ShowList(1);
        ShowList(1, 2.2);
        ShowList(1, 2.2, "hello");
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    如何解析可变参数包?这里使用递归来解决。以下是一个使用可变参数模板的例子:

    void ShowList() //函数重载,当参数个数为0时,没有该函数,就会找不到匹配的函数
    {
        cout << endl;
    }
    template <class T, class ...Args>
    void ShowList(const T& val, Args... args) //每次从参数包中解析一个
    {
        cout << val << " ";
        ShowList(args...);
    }
    int main()
    {
        ShowList(1);
        ShowList(1, 2.2);
        ShowList(1, 2.2, "three");
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    可变参数模板的优点是可以使代码更加灵活和通用,可以接受任意数量和类型的参数。可变参数模板的缺点是可能会降低代码的可读性和可维护性。

  • 相关阅读:
    180B参数的Falcon登顶Hugging Face,vs chatGPT 最好开源大模型使用体验
    【 java 面向对象】包装类的使用
    Oracle Database 12c升级到19c(Redhat Linux12.2.0.1 Upgrade to 19.3.0.0)
    79、SpringBoot 整合 R2DBC --- R2DBC 就是 JDBC 的 反应式版本, R2DBC 是 JDBC 的升级版。
    另眼旁观 Linkerd 2.12 的发布:服务网格标准的曙光?
    angular2+ 集成系统利用服务作为模块通信中间件
    go 下划线
    学生管理系统学生分数查询系统
    Android8.1 MTK 浏览器下载的apk点击无反应不能安装
    Spring的一些专业术语
  • 原文地址:https://blog.csdn.net/weixin_68278653/article/details/132918260