• C++11新特性介绍,源码测试


    12

    关键字

    auto

    自动类型推导,auto声明变量的类型必须由编译器在编译时期推导而得。

        //1.复杂类型变量声明时简化代码
        std::vector<int> v_int;
        v_int.push_back(101);
    //    std::vector::iterator iter = v_int.begin();//迭代器类型复杂
        auto iter = v_int.begin();//使用auto关键字自动推导类型,简介易读
        while(iter != v_int.end())
        {
            printf("value=%d\n",*iter);
            iter++;
        }
    
        //2.自适应泛型编程,根据不同的编译环境,或者引用动态库版本升级,导致表达式返回值类型变化,auto可以自适应类型,无需改动代码
        auto v_len = strlen("hello world!");//32位返回4字节整型,64位返回8字节整型
        printf("v_len=%ld,sizeof(v_len)=%ld\n",v_len,sizeof(v_len));
    
        //3.宏定义的性能提升
        //MAX1是传统写法,a和b是表达式时,无论返回a还是b,a或b都会被运算两次
        //MAX2中先把a和b计算出来,再进行比较,a和b都只计算了一次
    #define MAX1(a, b) ((a) > (b)) ? (a) : (b)
    #define MAX2(a, b) ({\
            auto _a = (a);\
            auto _b = (b);\
            (_a > _b) ? _a : _b;})
        int num1 = MAX1(1*2*3*4, 5+6+7+8);
        int num2 = MAX2(1*2*3*4, 5+6+7+8);
        printf("num1=%d\n",num1);
        printf("num2=%d\n",num2);
    
    • 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

    打印

    value=101
    v_len=12,sizeof(v_len)=8
    num1=26
    num2=26
    
    • 1
    • 2
    • 3
    • 4

    decltype

    和auto的功能类似,decltype用来在编译时期进行自动类型推导,引入decltype是因为auto并不适用于所有的自动类型推导场景。

    1、 auto根据=右边的初始值推导出变量的类型,decltype根据exp表达式推导出变量的类型,跟=右边的value没有关系。
    2、auto要求变量必须初始化,这是因为auto根据变量的初始值来推导变量类型的,如果不初始化,变量的类型也就无法推导,而decltype不要求。

    auto varName=value;
    decltype(exp) varName=value;
    decltype(exp) varName;//可以不初始化
    
    • 1
    • 2
    • 3

    decltype的推导规则可以简单概述如下:

    1、如果exp是一个不被括号()包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,decltype(exp)的类型和exp一致。
    2、如果exp是函数调用,则decltype(exp)的类型就和函数返回值的类型一致。
    3、如果exp是一个左值,或被括号()包围,decltype(exp)的类型就是exp的引用,假设exp的类型为T,则decltype(exp)的类型为T&。

    int num2 = 2;
    int& func1(int num,char c)//返回值为int&
    {
        printf("num=%d\n",num);
    
        return num2;
    }
    
        int n=0;
        const int &r=n;
        decltype(n) x=n;    //n为Int,x被推导为Int
        decltype(r) y=n;    //r为const int &,y被推导为const int &
    
        decltype(func1(100,'A')) a=n;//a的类型为int&,func1函数不会真正执行,只是形式
    
        int n2=0,m2=0;
        decltype(m2+n2) c=0;//n+m得到一个右值,c的类型为int
        decltype(n2=n2+m2) d=c;//n=n+m得到一个左值,d的类型为int &
    
        c = 102;
        printf("c=%d,d=%d\n",c,d);//d是c的引用
        //左值:表达式执行结束后依然存在的数据,即持久性数据;
        //右值是指那些在表达式执行结束不再存在的数据,即临时性数据。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    打印

    c=102,d=102
    
    • 1

    nullptr

    C++11使用nullptr取代NULL表示空指针.
    NULL缺陷:在VS等编译环境下NULL是宏定义,值为0,也就是0x0000 0000这个内存空间,NULL即表示空指针,又表示0,在某些代码场景存在二意性。

    void Func_nul(void* ){
           printf("I am void fucntion!\n");
    }
    
    void Func_nul(int ){
           printf("I am zero fucntion!\n");
    }
    
        Func_nul(NULL);//编译错误:error: call to 'Func_nul' is ambiguous
        Func_nul(0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    nullptr是nullptr_t类型的右值常量,专门用于初始化空类型的指针。nullptr_t是在C++11新增加的数据类型,nullptr_t是指针空值类型。

    nullptr可以被隐式转换成任意的指针类型, 不同类型的指针变量都可以使用nullptr类初始化,编译器会将nullptr隐式转换成int*、char*、double*指针类型。

    final

    final 关键字限制某个类不能被继承,或者某个虚函数不能被重写。如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面。

    final修饰类
    被final修饰的类不能作为基类,也就是说不能被其他类所继承。
    举例:类A使用final修饰,类B继承于A时报错:error: base ‘A’ is marked ‘final’。

    class A final
    {
    public:
        virtual void TestFunc()
        {
            printf("A Class\n");
        }
    };
    
    class B :A//error: base 'A' is marked 'final'
    {
    public:
        virtual  void TestFunc()
        {
            printf("B Class\n");
        }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    final修饰函数
    被修饰的虚函数禁止了子类对虚函数的重写。

    class A
    {
    public:
        virtual void TestFunc() final
        {
            printf("A Class\n");
        }
    };
    
    class B:A
    {
    public:
        virtual  void TestFunc()//error: declaration of 'TestFunc' overrides a 'final' function
        {
            printf("B Class\n");
        }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    override

    在成员函数声明或定义中, override 确保该函数为虚函数并覆盖来自基类的虚函数。
    override 显示地表明,这个函数是重写基类的虚函数,编译器可以帮助验证 override 对应的方法名是否是基类中所有的,如果没有则报错。
    下面例子,类B虚函数TestFunc使用override 修饰,但基类A中并没有这个虚函数,因此报错。

    class A
    {
    public:
    };
    
    class B :public A
    {
    public:
        //error: 'TestFunc' marked 'override' but does not override any member functions
        //如果不使用override ,则不会编译报错
        virtual  void TestFunc() override = 0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    override带来的好处
    1、如果基类中没有该虚函数,则编译会报错。
    2、防止在重写基类虚函数时,函数名写错,如果没有override 修饰,函数名写错了编译并不会报错。

    default

    C++类有4个特殊成员函数:默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符。如果程序没有定义这些特殊成员函数,则编译器会隐式的为这个类生成一个默认的特殊成员函数。

    这几种特殊成员函数使用default关键字,可以快速生成一个默认成员函数,而不需要写函数体。

    default的好处
    1、写法简单,节省开发时间。
    2、代码执行效率高,当我们使用这个关键字定义的构造函数,在声明变量时,编译器不会去调用构造函数,也不会生成构造函数的代码,高效率提高声明变量的时间。

    class School{
    public:
        School() = default;
        virtual ~School() = default;
    private:
        std::string name;
    };
    
    class college : public School{
    public:
        int age;
        ~college()
        {
            printf("~college\n");
        }
    };
    
        School *pSchool = new college;
        delete pSchool;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上面例子,如果School的析构函数不是virtual的,则delete时不会执行college类的析构函数,造成隐式的内存泄漏。

    delete

    C++11可以使用delete关键字显示地删除默认生成的函数,比如删除一个函数模板的实例,删除类的默认拷贝构造函数等。

    template <class T>
    T sum(T t1, T t2)
    {
        return t1 + t2;
    }
    int sum(int, int) = delete ;//删除函数模板的一个实例
    
    class Student
    {
    public:
        Student() = default;
        Student(const Student & c) = delete ;//删除拷贝构造函数
    
        int age;
    };
    
    //    sum(1,2);//error: call to deleted function 'sum'
        float num = sum(1.2, 3.5);
        printf("num=%.2f\n",num);//num=4.70
    
        Student s1;
    //    Student s2 = s1;//error: call to deleted constructor of 'Student'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    右值引用和std::move

    传统的C++语法中就有引用,C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

    1、左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体;
    2、右值当在赋值号右边取出值赋给其他变量的值;右值可以在内存也可以在CPU寄存器。
    3、一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。
    4、左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象 。
    5、右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字。

    左值引用和右值引用

    左值引用:type &引用名 = 左值表达式;对左值的引用,是给左值取别名。
    右值引用:type &&引用名 = 右值表达式;对右值的引用,是给右值取别名。

        int a1=10;//a1 是左值
        int & a2=a1;//引用左值,是一个左值引用
    
        int&& b2 = 100;//右值引用
        printf("a2=%d,b2=%d\n",a2,b2);//a2=10,b2=100
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    std::move
    将一个左值引用强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。

    std::move语句可以将左值变为右值而避免拷贝构造。
    std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。

        int a = 1;
        std::vector<int> vec;
        vec.push_back(std::move(a));
        printf("a=%d\n",a);//基本数据类型不存在拷贝构造,a的值不变
    
        std::string str = "Hello";
        std::vector<std::string> v;
        //调用常规的拷贝构造函数,新建字符数组,拷贝数据,str的值不变
        v.push_back(str);
        printf("str=%s\n",str.c_str());
    
        //调用移动构造函数,会把原str的数据据为己有,最好不要使用str
        v.push_back(std::move(str));
        printf("str=%s\n",str.c_str());
    
        auto iter = v.begin();
        while(iter != v.end())
        {
            printf("v.str=%s\n",iter->c_str());
            iter++;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    打印

    a=1
    str=Hello
    str=
    v.str=Hello
    v.str=Hello
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Lambda表达式

    Lambda(匿名函数)表达式是C++11最重要的特性之一,来源于函数式编程的概念。

    声明式编程风格:就地匿名定义目标函数或函数对象,有更好的可读性和可维护性。
    简洁:不需要额外写一个命名函数或函数对象,,避免了代码膨胀和功能分散。
    更加灵活:在需要的时间和地点实现功能闭包。

    lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。语法形式如下:

    [ capture ] ( params ) opt -> ret { body; };
    
    auto f = [](int a) -> int {return a + 1;};
    auto f = [](int a) {return a + 1;};//省略返回值的定义
    
    • 1
    • 2
    • 3
    • 4

    capture:捕获列表
    params:参数列表
    opt:函数选项
    ret:返回值类型
    body:函数体

    在实际的使用中,可以省略其返回值的定义(opt -> ret),使用auto自动推导返回值类型。

    捕获变量规则

    [] 不捕获任何变量
    [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)
    [=]捕获外部作用域中所有变量,并作为副本在函数体重使用(按值捕获)
    [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量
    [bar] 按值捕获bar变量,同时不捕获其他变量
    [this] 捕获当前类中的this指针,让表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员变量和成员函数。

    mutable
    若( )后面使用mutable修饰,在=传值时,可以在表达式内修改参数,但只是修改了行参,并未修改参数本身的值。

    传参示例

    class A 
    {
    public:
    	int i_ = 0;
    
    	void func(int x,int y)
    	{
    		auto x1 = []{return i_;};  // error,没有捕获外部变量
    		auto x2 = [=]{return i_ + x + y;}; //ok,按值捕获所有外部变量
    		auto x3 = [&]{return i_ + x + y;}; //ok,按引用捕获所有外部变量
    		auto x4 = [this]{return i_;}; //ok,捕获this指针
    		auto x5 = [this]{return i_ + x + y;}; //error,没有捕获x和y变量
    		auto x6 = [this,x,y]{return i_ + x + y;}; //ok,捕获了this指针和x、y变量
    		auto x7 = [this]{return i_++;}; //ok,捕获了this指针,修改成员变量的值
    	}
    };
    
    int a = 0 , b = 0 ;
    auto f5 = [a]{return a+b;}; //error,没有捕获b变量
    auto f6 = [a,&b]{return a+ (b++);}; //ok,捕获a以及b的引用,对b进行自加
    auto f7 = [=,&b]{return a+ (b++);}; //ok, 捕获所有外部变量和b的引用,对b进行自加
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    实际应用

        int a = 1;
        int b = 2;
        auto fun1 = [=](int c)mutable{
            b = a + c;
            return b;};
        printf("fun1(10)=%d\n",fun1(10));
        printf("b=%d\n",b);//值传递,并未修改b的值
    
        std::vector<int> v = {1,2,3,4,5,6,7};
        int sum = 0;
        for_each(v.begin(),v.end(),[&sum](int val){
                sum += val;
        });
        printf("sum=%d\n",sum);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    打印

    fun1(10)=11
    b=2
    sum=28
    
    • 1
    • 2
    • 3

    for循环(基于范围的循环)

    C++ 11 标准中,为 for 循环添加了一种全新的语法格式,如下所示:

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

    declaration:表示此处要定义一个变量,该变量的类型为要遍历序列中存储元素的类型,通常使用auto自动推导变量的类型。
    expression:表示要遍历的序列,常见的可以为事先定义好的普通数组或者容器,还可以是用 {} 大括号初始化的序列。

        //1.for循环遍历普通数组,尾部\0也会被遍历
        char arc[] = "HelloWorld";
        for (char ch : arc)
        {
            if(ch == '\0')
                printf(" ");
            else
                printf("%c",ch);
        }
        printf(".\n");
    
        //2.for循环遍历 vector 容器
        std::vector<char>v1(arc, arc + 5);
        for (auto ch : v1)
        {
            printf("%c",ch);
        }
        printf(".\n");
    
        //3.遍历用{ }大括号初始化的列表
        for (int num : {1, 2, 3, 4, 5})
        {
            printf("%d",num);
        }
        printf(".\n");
    
        //4.引用遍历,可修改容器中的值
        char arc2[] = "abcde";
        std::vector<char>v2(arc2, arc2 + 5);
        for (auto &ch : v2)
        {
            ch++;
        }
        //for循环遍历输出容器中各个字符
        for (auto ch : v2)
        {
            if(ch == '\0')
                printf(" ");
            else
                printf("%c",ch);
        }
        printf(".\n");
    
    • 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

    打印

    HelloWorld .
    Hello.
    12345.
    bcdef.
    
    • 1
    • 2
    • 3
    • 4

    统一初始化initializer_list

    统一初始化,也叫做大括号初始化。就是使用大括号进行初始化的方式。
    编译器看到{t1, t2, …, tn}便会做出一个initializer_list,它关联到一个array。调用构造函数的时候,该array内的元素会被编译器分解逐一传给函数。但若函数的参数就是initializer_list,则不会逐一分解,而是直接调用该参数的函数。

        //初始化示例
        int values[]{ 1, 2, 3 };
        std::vector<int> v{ 2, 3, 6, 7 };
        std::vector<std::string> cities{
            "Berlin", "New York", "London",  "Braunschweig"
        };
        
        //显示使用std::initializer_list
        class P
        {
        public:
            P(int, int){printf("call P::P(int,int)\n");}
            P(std::initializer_list<int>){
                printf("call P::P(initializer_list)\n");
            }
        };
        P p(77,5);     // call P::P(int,int)
        P q{77,5};     // call P::P(initializer_list)
        P r{77,5,42};  // call P::P(initializer_list)
        P s = {77, 5}; // call P::P(initializer_list)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    打印

    call P::P(int,int)
    call P::P(initializer_list)
    call P::P(initializer_list)
    call P::P(initializer_list)
    
    • 1
    • 2
    • 3
    • 4

    静态断言static_assert

    与assert不同的是,static_assert是在编译期进行检查,而不是在运行期进行检查。static_assert的原理是在编译期检查一个条件是否满足,如果不满足则编译器会报错并输出错误信息。

    static_assert通常用于编译期检查一些常量表达式或类型特性,可以帮助程序员在编译期发现一些错误,提高程序的健壮性和可维护性。

    static_assert(expression, message);
    
    
    • 1
    • 2

    其中,expression是一个常量表达式,用于检查某个条件是否满足;message是一个字符串,用于描述错误信息。

    template <int N>
    struct check_num
    {
        static_assert(N > 0, "N must be greater than 0"); // 检查N是否大于0
        static const bool value = (N % 8) == 0;           // 检查N是否是8的整数倍
    };
    
        int arr[8];//比如是7,则编译不通过
        // 检测arr占用字节数是否是8的整数倍
        static_assert(check_num<sizeof(arr)>::value, "The array size must be an integer multiple of 8");
        printf("Array size = %ld\n",sizeof(arr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    函数返回类型后置

    把函数返回值写的(参数)的后面,下面例子,auto是占位符,->后面才是返回值类型。

    auto Fun(int a, int b) ->int//等同于下面的写法
    //int Fun(int a, int b)
    {
        return a + b;
    }
    
    int sum = Fun(1,2);
    printf("sum=%d\n",sum);//sum=3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    用法一:返回一个函数指针类型
    1、使用传统函数声明语法无法将函数指针类型作为返回类型直接使用,所以需要使用typedef给函数指针类型创建别名 bar,再使用别名作为函数的返回类型。
    2、使用函数返回类型后置语法则没有这个问题。

    int bar_impl(int x)
    {
        return x*2;
    }
    
    typedef int(*bar)(int);
    //传统前置返回值,需要用typedef定义别名
    bar foo1()
    {
        return bar_impl;
    }
    
    //使用后置返回值则不需要定义别名,直接使用即可
    auto foo2()->int(*)(int)
    {
        return bar_impl;
    }
    
    auto func = foo2();
    printf("func(16)=%d\n",func(16));//func(16)=32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    用法二:推导函数模板返回类型
    配合decltype说明符,自动推导函数模板返回值类型。

    template<class T1, class T2>
    auto sum(T1 t1, T2 t2)->decltype(t1 + t2)
    {
        return t1 + t2;
    }
    
    auto num = sum(4, 2);
    printf("num=%d\n",num);//num=6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    强类型枚举(枚举类)

    枚举起源于C,所以C++的枚举直接从C语言继承过来。
    C枚举不足
    1、由于C语言是没有命名空间的,所以枚举的成员在C++中的作用域也是全局的。
    2、由于枚举本质是常量数值,所以枚举成员会被隐式转换为整型。这提供了一些便捷性同样也带来了一定的风险。
    3、枚举为int类型4字节,当超出时会溢出。(和操作系统相关,部分操作系统当超出4字节int范围时,枚举会自动扩容为8字节)。

    C++枚举类
    为了解决上述问题,C++11引入了枚举类(enum class)也叫强类型枚举(strong-typed enum)。
    而枚举类的声明:在enum 的后面添加class 关键字。
    优点:

    强作用域:枚举类的成员会严格按照作用域空间。
    隐式转换限制:枚举类的成员不可以和整型进行转换(可以强制类型转换)。
    指定底层类型:枚举类默认的底层类型是int,还支持显式的指定底层类型,语法: enum_name : type。需要注意的是type是处理wchar_t(宽字符)之外的所有整型类型。

    //原始枚举,从C语言继承,枚举值是全局变量
    enum Enum
    {
        A = 10,        //10
        B,             //11
        C = 10,        //10
        D,             //11
        //如果枚举整型大于int,枚举类型自动由int4字节升级为long int8字节,ubuntu20.04环境
    };
    
    enum class Enum1: long long int//指定枚举数据类型
    {
        //强作用域,如果Enum1是普通枚举,则下面的枚举和Enum是重复定义
        A = 4,          //4
        B = 0,          //0
        C ,             //1
        D ,             //2
    };
    
    enum class Enum2: long long int
    {
        //和Enum1不会重复定义
        A = 5,          //5
        B = 11,         //11
        C = LONG_MAX,   //9223372036854775807
        D = LLONG_MAX,  //9223372036854775807
    };
    
        printf("sizeof(int)=%ld\n",sizeof(int));
        printf("sizeof(long int)=%ld\n",sizeof(long int));
        printf("sizeof(long long int)=%ld\n",sizeof(long long int));
        printf("sizeof(Enum::A)=%ld\n" ,sizeof(A));//4字节,如果定义了E = LONG_MAX,则是8字节
        printf("sizeof(Enum1::A)=%ld\n" ,sizeof(Enum1::A));
    
        printf("Enum::A=%d\n",A);//no warning
        printf("Enum2::A=%lld\n",Enum2::A);//warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘Enum2’ [-Wformat=]
    
        //原枚举,支持和整型的隐式转换
        int num = A;
    
        //枚举类,不支持和整型的隐式转换
    //    long long int num1 = Enum1::A;//error: cannot initialize a variable of type 'long long' with an rvalue of type 'Enum1'
        long long int num1 = (long long int)Enum1::A;//强制类型转换,ok
    
        printf("num=%d\n",num);
        printf("num1=%lld\n",num1);
    
    • 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

    打印

    sizeof(int)=4
    sizeof(long int)=8
    sizeof(long long int)=8
    sizeof(Enum::A)=4
    sizeof(Enum1::A)=8
    Enum::A=10
    Enum2::A=5
    num=10
    num1=4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    前置枚举声明

    C++开发中通常会把类、结构体、枚举定义在.h头文件中,有时会出现A.h包含了B.h,B.h也包含了A.h,交叉包含的问题。

    使用前置声明的好处
    1、防止头文件相互交叉包含。
    2、C++中头文件不会单独编译,在cpp文件编译时会同时编译依赖的头文件,不使用前置声明会添加多余的头文件依赖,产生额外的编译开销。
    3、一句话:尽量不要把包含头文件写在.h头文件,而是写在cpp文件。

    在mainwindow.h中定义了结构体、类、枚举类,C++11支持枚举类前置声明。

    struct Country{
        int  area;
        std::string name;
    };
    
    class Student
    {
    public:
        int age;
        std::string name;
    };
    
    enum class Enum1: long long int//指定枚举数据类型
    {
        A = 4,          //4
        B = 0,          //0
        C ,             //1
        D ,             //2
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    TestEnum.h

    #ifndef TESTENUM_H
    #define TESTENUM_H
    
    //前置声明
    class Student;
    struct Country;
    enum class Enum1: long long int;
    class TestEnum
    {
    public:
        TestEnum();
    private:
        Enum1 m_Enum1;
        Student *pStu;
        Country *pCountry;
    };
    
    #endif // TESTENUM_H
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    TestEnum.cpp

    #include "TestEnum.h"
    
    #include "mainwindow.h"
    
    TestEnum::TestEnum()
    {
        m_Enum1 = Enum1::A;
    
        pStu = new Student;
        pStu->age = 10;
    
        pCountry = new Country;
        pCountry->area = 1000;
    
        printf("m_Enum1=%lld\n",m_Enum1);
        printf("pStu->age=%d\n",pStu->age);
        printf("pCountry->area=%d\n",pCountry->area);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在头文件使用前置声明,在cpp文件保护依赖头文件。
    打印

    m_Enum1=4
    pStu->age=10
    pCountry->area=1000
    
    • 1
    • 2
    • 3

    内联命名空间(Inline namespaces)

    在namespace加inline关键字即可。
    内联命名空间的特点时,不需要使用using语句,也不需使用命名空间前缀,就可以直接在外层命名空间使用该命名空间内部的内容。

    当然既不使用using,也不使用命名空间前缀,则不同的内联命名空间不能定义相同的内容(比如同名类)。

    内联命名空间带来的好处
    1、省事,省的写using和命名空间前缀就可以直接使用空间里的内容。
    2、在库开发中,库的版本迭代升级提供新的接口,同时保留旧的接口,就可以使用内联命名空间,为库的调用者提供便利。

    //老接口
    namespace inline_ns1{
        class AA{
        public:
            void testFunc()
            {
                printf("old class AA\n");
            }
        };
    }
    
    //新接口
    inline namespace inline_ns2{
        class AA{
        public:
            void testFunc()
            {
                printf("class AA\n");
            }
        };
    }
    
        //新接口,不使用命名空间前缀
        AA aa;
        aa.testFunc();
    
        //如果想使用老接口,加上命名空间前缀即可
        inline_ns1::AA aa2;
        aa2.testFunc();
    
    • 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

    打印

    class AA
    old class AA
    
    • 1
    • 2

    变参宏(Variadic macros)

    #include 
    #include 
    
    #define ONE_PARM(parm1)  printf("first=%-2c\n",(parm1))
    #define TWO_PARM(parm1, parm2)  printf("first=%-2c,second=%-5d\n",(parm1),(parm2))
    #define THREE_PARM(parm1, parm2, parm3) printf("first=%-2c,second=%-5d,third=%-20s\n",(parm1),(parm2),(parm3))
    
    #define GET_PARM(_1,_2,_3,func,...) func
    
    #define PRINTF(...) GET_PARM(__VA_ARGS__, THREE_PARM, TWO_PARM, ONE_PARM,...)(__VA_ARGS__)
    
        int num=1;
        char ch='A';
        std::string str = "string";
    
        PRINTF(ch);
        PRINTF(ch,num);
        PRINTF(ch,num,str.c_str());
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    打印

    first=A 
    first=A ,second=1    
    first=A ,second=1    ,third=string   
    
    • 1
    • 2
    • 3
  • 相关阅读:
    java使用正则提取数据
    SystemServer进程
    锐捷交换机系统安装与升级
    Cocos Creator 3.x 原生 TS 交互
    缓冲区、通道、选择器
    Temu如何提高销量,Temu能做无货源吗?——站斧浏览器
    spring boot过滤器实现项目内接口过滤
    ssm+vue的汽车站车辆运营管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。
    Lua中如何实现类似gdb的断点调试--05优化断点信息数据结构
    力扣2379.得到k个黑块的最少涂色次数
  • 原文地址:https://blog.csdn.net/weixin_40355471/article/details/131789906