C++11 構造函數的列表初始化

感謝原作者分享,轉自: C++類成員的初始化  建議查看原文

1、關於構造函數

1)用構造函數確保初始化

對於一個空類

class Empty { };

編譯器會自動聲明4個默認函數:構造函數,拷貝構造函數,賦值函數,析構函數(當然,如果不想使用自動生成的函數,就應該明確拒絕),這些生成的函數都是public且inline。構造函數對數據成員進行初始化,使用未初始化值可能導致無法預知的錯誤,所以,確保每一個構造函數都將每一個成員初始化。

2)爲什麼構造函數不能有返回值

如果有返回值,要麼編譯器必須知道怎麼處理返回值,要麼就客戶程序員顯式調用構造函數和析構函數,這樣,還有安全性麼?

3)爲什麼構造函數不能爲虛函數

簡單來說,虛函數調用的機制,是知道接口而不知道其準確對象類型的函數,但是創建一個對象,必須知道對象的準確類型;當一個構造函數被調用時,它做的首要事情之一就是初始化它的VPTR來指向VTABLE。

4)構造函數的一個面試題:

 

   #include <iostream>
    using namespace std;
     
    class Base
    {
    private:
        int i;
    public:
        Base(int x)
        {
            i = x;
        }
    };
     
    class Derived : public Base
    {
    private:
        int i;
    public:
        Derived(int x, int y)
        {
            i = x;
        }
        void print()
        {
            cout << i + Base::i << endl;
        }
    };
     
    int main()
    {
        Derived A(2,3);
        A.print();
        return 0;
    }

首先,是訪問權限問題,子類中直接訪問Base::i是不允許的,應該將父類的改爲protected或者public(最好用protected)

其次,統計父類和子類i的和,但是通過子類構造函數沒有對父類變量進行初始化;此處編譯會找不到構造函數,因爲子類調用構造函數會先找父類構造函數,但是沒有2個參數的,所以可以在初始化列表中調用父類構造函數

最後個問題,是單參數的構造函數,可能存在隱式轉換的問題,因爲單參數構造函數,和拷貝構造函數形式類似,調用時很可能會發生隱式轉換,應加上explicit關鍵字,修改後如下(程序員面試寶典上只改了前2個)

    #include <iostream>
    using namespace std;
     
    class Base
    {
    protected:
        int i;
    public:
        explicit Base(int x)
        {
            i = x;
        }
    };
     
    class Derived : public Base
    {
    private:
        int i;
    public:
        Derived(int x, int y):Base(x)
        {
            i = y;
        }
        void print()
        {
            cout << i + Base::i << endl;
        }
    };
     
    int main()
    {
        Derived A(2,3);
        A.print();
        return 0;
    }

2、初始化列表

1)使用初始化列表提高效率

常用的初始化可能如下:

    class Student
    {
    public:
        Student(string in_name, int in_age)
        {
            name = in_name;
            age = in_age;
        }
    private :
        string name;
        int    age;
    };

    以前樓主也習慣這麼寫,可以達到預期效果,不過不是最佳做法,因爲在構造函數中,是對name進行賦值,不是初始化,而string對象會先調用它的默認構造函數,再調用string類(貌似是basic_string類)的賦值構造函數;對於上例的age,因爲int是內置類型,應該是賦值的時候獲得了初值。

    要對成員進行初始化,而不是賦值,可以採用初始化列表(member initialization list)改寫爲如下:

 

   class Student
    {
    public:
        Student(string in_name, int in_age):name(in_name),age(in_age) {}
    private :
        string name;
        int    age;
    };

    結果與上例相同,不過在初始化的時候調用的是string的拷貝構造函數,而上例會調用兩次構造函數,從性能上會有不小提升

    有的情況下,是必須使用初始化列表進行初始化的:const對象、引用對象

2)初始化列表初始順序

考慮以下代碼:

    #include <iostream>
    using namespace std;
     
    class Base
    {
    public:
        Base(int i) : m_j(i), m_i(m_j) {}
        Base() : m_j(0), m_i(m_j) {}
        int get_i() const
        {
            return m_i;
        }
        int get_j() const
        {
            return m_j;
        }
     
    private:
        int m_i;
        int m_j;
     
    };
     
    int main()
    {
        Base obj(98);
        cout << obj.get_i() << endl << obj.get_j() << endl;
        return 0;
    }

     
輸出爲一個隨機數和98,爲什麼呢?因爲對於初始化列表而言,對成員變量的初始化,是嚴格按照聲明次序,而不是在初始化列表中的順序進行初始化,如果改爲賦值初始化則不會出現這個問題,當然,爲了使用初始化列表,還是嚴格注意聲明順序吧,比如先聲明數組大小,再聲明數組這樣。

再次感謝原作者分享。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章