zoukankan      html  css  js  c++  java
  • 带你掌握C++中三种类成员初始化方式

    摘要:在C++11之后,声明时初始化->初始化列表->构造函数初始化。

    本文分享自华为云社区《如何编写高效、优雅、可信代码系列(3)——类成员初始化的三种方式》,原文作者:我是一颗大西瓜。

    首先,先得了解一下C++支持哪几种类成员初始化的方式,你常用的又是哪一种。

    • 初始化方式一:初始化列表
    class A
    {
    public:
        int a; // 初始化列表
        A(int a_):a(a_){}
    };
    • 初始化方式二:构造函数初始化
    class A
    {
    public:
        int a; // 初始化列表
        A(int a_, bool b) { a = a_; }
    };
    • 初始化方式三:声明时初始化(也称就地初始化,c++11后支持)
    class A
    {
    public:
        int a = 1; // 声明时初始化
        A() {}
    };

    在C++98中,支持了在类声明中使用等号“=”加初始值的方式,来初始化类中静态成员常量。这种声明方式我们也称之为“就地”声明。就地声明在代码编写时非常便利,不过C++98对类中就地声明的要求却非常高。如果静态成员不满足常量性,则不可以就地声明,而且即使常量的静态成员也只能是整型或者枚举型才能就地初始化。而非静态成员变量的初始化则必须在构造函数中进行。比如,如下代码在c++98中编译

    class Init
    {
    public:
        Init(): a(0) []
        Init(int d): a(d) {}
    private:
        int a;
        const static int b = 0;
    
        int c = 1;           // member, cannot pass build
        static int d = 0;    // member, cannot pass build
    
        static const double e = 1.3;      // not int or enum type, cannot pass build
        stati const char* const f = "e";  // not int or enum type, cannot pass build
    }

    这非常不方便,所以在C++11中,标准允许非静态成员变量的初始化有多种形式。具体而言,除了初始化列表外,在C++11中,标准还允许使用等号= 或者 花括号{} 进行就地的非静态成员变量初始化。

    struct init {
        int a = 1;
        double b {1.2};
    };

    大家知道,有几种情况下推荐优先使用列表初始化

    • const成员变量只能用成员初始化列表来完成初始化,而不能在构造函数内赋值
    • 初始化的数据成员是对象
    • 需要初始化引用成员数据

    具体的原因这里不细述,大家可以去看一下《C++ Primer》。

    构造函数初始化的本质是赋值操作("="),这个方法存在两个问题,一个是比起初始化列表和就地初始化,此方式的效率偏低;第二个是可能存在错误隐患。

    先说第一个,赋值过程中会产生临时对象,临时对象的构造析构会造成效率损耗,初始化列表的方式就避免了产生临时对象缩带来的问题。

    第二个是,如果你没有重写或者禁止赋值构造函数,c++会悄悄的加上默认的赋值构造函数,这个时候也有可能带来问题。

    从C++11之后,这三种初始化的方法都可以使用,并不会存在冲突,但是,他们之间是有优先级顺序的,这个优先级来源于他们在初始化的时间顺序,后面初始化的会把前面的覆盖掉,成员变量的初始化顺序是

    声明时初始化->初始化列表->构造函数初始化

    因此假如三种初始化方式同时存在的话,那么最后保留的成员变量值肯定是构造函数中初始化的值。

    #include <iostream>
    using namespace std;
    class A 
    {
    public:
        int a = 1;
        A(int a_) :a(2) { a = 3; }
    };
    
    int main()
    {
        A a;
        cout << "a.a=" << a.a << endl;
        return 0;
    }
    
    // a.a=3

    既然初始化方式这么多,那么什么时候适用哪种呢?

    1. 声明时初始化的使用场景

    • 一个优点是直观,你在声明的时候顺便给一个初始值,bravo,别人在看你代码的时候,点一下调到声明也能看到你赋予的初始值,不用再去看构造函数那里给的什么值
    • 第二个优点更有用了,比如你要定义多个构造函数,每个构造函数都用列表初始化的方法初始化,多麻烦呀,请看下面的例子,妈妈看了再也不用担心我想用其他初始化方法了
    class Group {
    public:
        Group() {}
        Group(int a): data(a) {}
        Group(Mem m): mem(m) {}
        Group(int a, Mem m, string n): data(a), mem(m), name(n) {}
    private:
        int data = 1;
        Mem mem{0};
        string name{"Group"};
    };

    2. 列表初始化的使用场景

    前面说过了三个场景,这里赘述一下

    • const成员变量只能用成员初始化列表来完成初始化,而不能在构造函数内赋值
    • 初始化的数据成员是对象
    • 需要初始化引用成员数据

    但是,需要注意列表初始化的顺序,不过IDE会提示你的

    3. 构造函数初始化的使用场景

    • 第一个就是拷贝和赋值构造函数里(不然怎么叫赋值构造函数呢)
    • 第二个就是比较无聊的情况了,比如你想把几个成员函数都初始化成一个值,请看下面例子
    class Group {
    public:
        Group() {data1 = data2 = data3 = 0;}
    private:
        int data1;
        int data2;
        int data3;
    };

    一言以蔽之,优先就地初始化和列表初始化。

     

    点击关注,第一时间了解华为云新鲜技术~

  • 相关阅读:
    log4js 2.X版本配置详解
    ping -c 3 localhost
    children_process.exec 执行 ping命令报错
    淘宝双十一为什么会出现通道拥挤?
    'ascii' codec can't decode byte 0xd6 in position 0
    window 安装grunt
    display:none与visible:hidden的区别 slideDown与
    ztree-demo 2
    ztree-demo
    技术总结PHP+微信
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/14948676.html
Copyright © 2011-2022 走看看