美文网首页
C++初始化列表

C++初始化列表

作者: wenmingxing | 来源:发表于2018-04-19 12:34 被阅读41次

    本文主要说明成员初始化列表的注意事项。

    I、上帝视角看初始化列表

    构造函数可以有两种构造形式,一是在构造函数体内对成员进行赋值,二是使用初始化列表

    class Test {
    public:
        Test() = default;
        /*
        Test(int x) {           //在函数体内完成
            Test_int = x;
        }
        */
    
        Test(int x) : Test_int(x) {}    //以初始化列表形式完成
    
    private:
        int Test_int = 0;
    };
    

    II、构造函数的两个阶段

    构造函数执行分为两个阶段,初始化阶段计算阶段

    2.1 初始化阶段

    所有类类型的成员都在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中。

    2.2 计算阶段

    用于执行函数体内的赋值操作。

    要理解这两个阶段可以观察下面的实例:

    #include<iostream>
    using namespace std;
    
    
    class Test1 {
    public:
        Test1() {cout << "Construct Test1" << endl;}
    
        //拷贝构造函数
        Test1(const Test1& other) {
            Test1_int = other.Test1_int;
            cout << "Copy constructor Test1" << endl;
        }
    
        //拷贝赋值运算符
        Test1& operator=(const Test1& other) {
            //判断自赋值问题,执行swap
            if (this != &other) {
                Test1 temp(other);  //执行一次拷贝构造函数,输出"Copy constructor Test1"
    
                swap(temp.Test1_int, Test1_int);
            }
            cout << "assignment Test1" << endl;
            return *this;
        }
    
    private:
        int Test1_int = 0;
    
    };
    
    class Test2 {
    public:
        //自定义构造函数
        Test2(const Test1& t1) {
            cout << "Construct Test2" << endl;
            test1 = t1;     //执行计算操作
        }
    private:
        Test1 test1;
    };
    
    int main() {
        Test1 t1;      //输出"Construct Test1"
    
        cout << "===============================" << endl;
    
        Test2 t2(t1);   //首先构造Test2中的t1(输出"Construct Test1")
                        //然后调用Test2的自定义构造函数,其中需要调用Test1的拷贝赋值运算符(输出"assignment ...")
                        //在调用拷贝赋值运算符的时候,会调用拷贝构造函数(输出Copy"")
    }
    
    

    执行结果说明:

    输出结果

    III、初始化列表优势

    使用初始化列表主要是考虑性能问题,对于内置类型,如int,float等,使用初始化列表和构造函数体内初始化差别不大,但对于类类型来说,最好使用初始化列表。

    看下面例子:

    #include<iostream>
    using namespace std;
    
    
    class Test1 {
    public:
        Test1() {cout << "Construct Test1" << endl;}
    
        //拷贝构造函数
        Test1(const Test1& other) {
            Test1_int = other.Test1_int;
            cout << "Copy constructor Test1" << endl;
        }
    
        //拷贝赋值运算符
        Test1& operator=(const Test1& other) {
            //判断自赋值问题,执行swap
            if (this != &other) {
                Test1 temp(other);  //执行一次拷贝构造函数,输出"Copy constructor Test1"
    
                swap(temp.Test1_int, Test1_int);
            }
            cout << "assignment Test1" << endl;
            return *this;
        }
    
    private:
        int Test1_int = 0;
    
    };
    
    class Test2 {
    public:
        //自定义构造函数
        Test2(const Test1& t1) {
            cout << "Construct Test2" << endl;
            test1 = t1;     //执行计算操作
        }
    private:
        Test1 test1;
    };
    
    class Test3 {
    public:
        Test3(const Test1& t1):test1(t1){
            cout << "Construct Test3" << endl;
        }
    private:
        Test1 test1;
    };
    
    int main() {
        Test1 t1;      //输出"Construct Test1"
    
        cout << "==============Test2=================" << endl;
    
        Test2 t2(t1);   //首先构造Test2中的t1(输出"Construct Test1")
                        //然后调用Test2的自定义构造函数,其中需要调用Test1的拷贝赋值运算符(输出"assignment ...")
                        //在调用拷贝赋值运算符的时候,会调用拷贝构造函数(输出Copy"")
    
        cout << "==============Test3==================" << endl;
        Test3 t3(t1);
    
        return 0;
    }
    
    

    执行结果说明:

    执行结果

    可以看出相比于Test2的函数体内初始化,Test3使用初始化列表可以少调用Test1的构造函数。这对于数据密集型的类来说,是非常高效的。

    IV、必须使用初始化列表的情况

    除了III中说明的性能原因之外,有些情况下初始化列表是不可或缺的:
    1、常量成员引用类型成员:这两种成员变量只能在定义时初始化,而不能重写赋值,所以要写在初始化列表中。
    2、没有默认构造函数的类类型成员。由上面的例子可以看出,使用初始化列表不用调用类类型成员的默认构造函数来初始化,而是直接使用拷贝构造函数初始化。

    如下面例子:

    #include<iostream>
    
    using namespace std;
    
    class Test1 {
    public:
        Test1(int a) :Test1_int(a) {}
    
    private:
        int Test1_int;
    };
    
    class Test2 {
    public:
        Test2(const Test1& t1) {
            test1 = t1;
        }
    private:
        Test1 test1;
    };
    
    int main() {
        Test1 t1;
        Test2 t2(t1);
    
        return 0;
    }
    //无法完成编译
    

    上述代码无法完成编译,因为Test2使用函数体内完成初始化,需要调用Test1的默认初始化函数,而Test1中没有默认初始化函数。

    为Test1中增加默认初始化函数,则可完成Test2的初始化:

    #include<iostream>
    
    using namespace std;
    
    class Test1 {
    public:
        Test1(int a):Test1_int(a) {}
        Test1() = default;
    
    private:
        int Test1_int;
    };
    
    class Test2 {
    public:
        Test2(const Test1& t1) {
            test1 = t1;
        }
    private:
        Test1 test1;
    };
    
    int main() {
        Test1 t1;
        Test2 t2(t1);
    
        return 0;
    }
    
    

    V 、初始化列表中初始化成员的顺序

    初始化列表中初始化成员的顺序是按照成员变量声明的顺序完成的,而不是以初始化列表中的顺序。

    例如,下列示例:

    #include<iostream>
    
    using namespace std;
    
    class Test1 {
    public:
        Test1(int a):j(a), i(j) {}    //i未定义
    
        int i;
        int j;
    };
    
    int main() {
        Test1 t1(100);
    
        cout << t1.i << " " << t1.j << endl;
    
        return 0;
    }
    
    

    输出结果为:

    输出结果

    这是由于i未定义的原因。

    【参考】
    [1] C++ 初始化列表

    欢迎转载,转载请注明出处wenmingxing C++ 初始化列表

    相关文章

      网友评论

          本文标题:C++初始化列表

          本文链接:https://www.haomeiwen.com/subject/cnzukftx.html