C++中為什么構造函數初始化列表


已經有個構造函數負責初始化,為什么還需要構造函數初始化表呢?
在以下三種情況下需要使用初始化成員列表:
一,需要初始化的數據成員是對象的情況;
二,需要初始化const修飾的類成員;
三,需要初始化引用成員數據;
需要初始化引用成員數據
最近才發現C++可以定義引用類型的成員變量,引用類型的成員變量必須在構造函數的初始化列表中進行初始化。對於類成員是const修飾,或是引用類型的情況,是不允許賦值操作的,(顯然嘛,const就是防止被錯誤賦值的,引用類型必須定義賦值在一起)因此只能用初始化列表對齊進行初始化。
#include <iostream>
using namespace std;
class Test
{
private:
    int &a;
public:
    Test(int &b) : a(b)
    {
    }
    void Modify(int value)
    {
        a = value;
    }
};
int main()
{
    int b = 3;
    Test test(b);
    cout <<"b="<<b<<endl;
    test.Modify(4);
    cout <<"b="<<b<<endl;
    return 0;
}
需要初始化const修飾的類成員
#include <iostream>
using namespace std;
class base
{
public:
 const int a;
 int& b;
public:
// base(int m, int n)
// {
//  a = m;
//  b = n;
// }
 base(int m, int n):a(m),b(n)
 {}
};
 
int main()
{
 base ba(1,2);
 cout << ba.a <<endl;
 cout << ba.b <<endl;
}
需要初始化的數據成員是對象的情況
#include <iostream>
using namespace std;
class point
{
protected:
 int m_x,m_y;
public:
 point(int m=0,int n=0)
 {
  m_x = m;
  m_y = n;
  printf("constructor called!\n");
 }
 point(point& p)
 {
  m_x = p.GetX();
  m_y = p.GetY();
  printf("copy constructor called!\n");
 }
 int GetX()
 {
  return m_x;
 }
 int GetY()
 {
  return m_y;
 }
};
 
class point3d
{
private:
 point m_p;
 int m_z;
public:
 point3d(point p, int k)
 {
  m_p = p;                              //這里是對m_p的賦值
  m_z=k;
 }
 point3d(int i,int j, int k):m_p(i,j)   // 相當於 point m_p(i,j)這樣對m_p初始化
 {
  m_z=k;
 }
 void Print()
 {
  printf("%d,%d,%d \n",m_p.GetX(),m_p.GetY(),m_z);
 }
};
上述代碼中Point3d是一個3D坐標,他有一個point的2D坐標和一個成員組成。
我們現在想定義一個3D坐標p3d,可以這樣實現:
int main()
{
 point p(1,2);    //先定義一個2D坐標
 point3d p3d(p,3);
 p3d.Print();
}
從point3d實現體可以看出,我們是通過對m_p進行賦值,這樣不僅調用copy constructor產生臨時對象而且是對m_p的一個賦值操作。
而如果使用成員初始化列表,我們則可以這樣:
int main()
{
 point p(1,2);
 point3d p3d(1,2,3);
 p3d.Print();
}
p3d中的point型成員是通過調用初始化的方式構建的。由於對象賦值比初始化要麻煩的多,因此也帶來的性能上的消耗。這也是我們在對成員數據是對象成員的采用初始化列表進行初始始化的主要原因。
 
------------------------------------------------------------------------------------------------------------------------------------------------------------------
一 C++必須用帶有初始化列表的情況:
(1)成員類型是沒有默認構造函數的類。若沒有提供顯示初始化式,則編譯器隱式使用成員類型的默認構造函數,若類沒有默認構造函數,則編譯器嘗試使用默認構造函數將會失敗。
(2)const成員或引用類型的成員。因為const對象或引用類型只能初始化,不能對他們賦值。
 

我們定義一個如下的Person類:
class Person {
public:
    Person() { }  //default constructor function
    Person(string name, string phone, string addr)
    {
        m_name = name;   //想采用賦值初始化數據成員
        m_phone = phone;
        m_addr = addr;
    }
    
private:
    const string m_name; 
    const string m_phone;
    const string m_addr;
}; 編譯后發現這個類的第二個帶參數的構造函數是錯誤的。我們創建一個Person對象:
Person p("marcky", "13233232", "cqupt"); //調用帶參數的構造函數創建一個Person對象 創建對象的過程分為了兩步:
      一、從內存中分配實際的空間給對象p,其三個字符串對象的數據成員是調用的默認構造函數初始化為空。也就說,此時為止,對象p的三個數據成員都是一個空的字符串。
      二、執行調用的構造函數的函數體語句,完成對數據成員的賦值,以此達到我們期望的創建一個指定Person對象,而不是空對象。

從上面的第二步就可以看到,我們在對三個const對象進行賦值操作,這顯然是不允許的操作,因此利用這個構造函數創建Person將以失敗告終。要想成功的創建一個特定的Person對象,我們需要構造函數初始化列表:
   Person(string name, string phone, string addr)
        :m_name(name), m_phone(phone), m_addr(addr){ } //冒號開始定義初始化列表 使用初始化列表創建對象的構造函數同樣是通過上述的兩個步驟來完成的,不同之處在於創建對象的數據成員時使用的不是默認構造函數,而是根據指定參數調用了相應的構造函數,以此創建特定的對象,而不是空對象。這樣一來,對象的數據成員的特定值在創建對象的時候就被賦予了相應的成員,而不是在創建對象完成之后再通過賦值語句去修改數據成員,因此利用構造函數初始化列表就可以成功的創建具有const數據成員的對對象了。

除了const的數據成員外,沒有默認構造函數的類類型或者是引用類型的成員,都必須在構造函數的初始化列表中進行初始化。

沒有默認構造函數的類類型成員,如果不在初始化列表中初始化的話,那么創建該對象的時候,由於沒有指定相應的“實參”,編譯器就會去調用默認構造函數來創建對象,必然會以失敗而告終。
引用類型的成員和const類型成員一樣,因為引用必須初始化,初始化后就不能修改,所以后期通過賦值來修改其值是錯誤的。

ps:數據成員被初始化的順序與構造函數初始化列表中的次序無關,而是與成員的定義順序一致。

二 范例

 

為了更好地理解構造函數初始化列表的使用規則,我們再來看下面的例子。
前面我們已經說了類的構造函數和析構函數,我們知道一個類的成員可以是另外一個類的對象,構造函數允許帶參數,那么我們可能會想到在程序中我們可以這樣做:在Student類中把它的teacher成員用帶參數的形式調用Student類的構造函數,不必要再在Teacher類中進行操作,由於這一點構想我們把在2.1中提及的程序修改成如下形式:
#include <iostream>
using namespace std;

class Teacher  
{   
    char *director; 
public: 
    Teacher(char *temp)  
    {  
  cout << "class Teacher:";
  
        director = new char[10];    
        strcpy(director, temp);  
    }  
    ~Teacher()
 {
  cout << "釋放堆區director內存空間\n";  
        delete[] director;  
        cin.get();  
 }  
    char* GetMember()
 {  
     return director;  
 }  
 void Show() 
 {
  cout << "director = " << director << endl; 
 }
};  
  
class Student  
{  
 int number;  
    int score;  
    Teacher teacher("王大力");//錯誤,一個類的成員如果是另外一個類的對象的話,
       //不能在類中使用帶參數的構造函數進行初始化  ;
public:  
    Student()  
    {  
  cout << "class Student:";
        number = 1;  
        score = 100;  
    }  
    ~Student()  
    {
  cout << "釋放class Student 內存空間\n"; 
  cin.get();  
 }
    void Show() 
 {  
     cout << "\nteacher = " << teacher.GetMember() << endl;
  cout << "number = " << number << endl;
  cout << "score = " << score << endl;    
 }  
};  
  
int main()  
{  
    Student a;  
    Teacher b("內存空間");
    
    a.Show();  
    b.Show();  
    
    getchar();
 return 0;
}   
  可是很遺憾,程序不能夠被編譯成功,為什么呢? 
  因為:類是一個抽象的概念,並不是一個實體,並不能包含屬性值(這里來說也就是構造函數的參數了),只有對象才占有一定的內存空間,含有明確的屬性值! 
  這一個問題是類成員初始化比較尷尬的一個問題,是不是就沒有辦法解決了呢?呵呵。。。。。。 
  c++為了解決此問題,有一個很獨特的方法,下面我們來看。
對於上面的那個尷尬問題,我們可以在構造函數頭的后面加上冒號並指定調用那個類成員的構造函數來解決!
代碼如下: 
#include <iostream>
using namespace std;

class Teacher  
{   
    char *director; 
public: 
    Teacher(char *temp)  
    {  
  cout << "class Teacher:";
  
        director = new char[10];    
        strcpy(director, temp);  
    }  
    ~Teacher()
 {
  cout << "釋放堆區director內存空間\n";  
        delete[] director;  
        cin.get();  
 }  
    char* GetMember()
 {  
     return director;  
 }  
 void Show() 
 {
  cout << "director = " << director << endl; 
 }
};  
  
class Student  
{  
 int number;  
    int score;  
    Teacher teacher;
public:  
    Student(char *temp): teacher(temp)  //冒號后指定調用某成員構造函數
    {  
  cout << "class Student:";
        number = 1;  
        score = 100;  
    }  
    
    ~Student()  
    {
  cout << "釋放class Student 內存空間\n"; 
  cin.get();  
 }
    void Show() 
 {  
     cout << "\nteacher = " << teacher.GetMember() << endl;
  cout << "number = " << number << endl;
  cout << "score = " << score << endl;    
 }  
};  
  
int main()  
{  
    Student a("王大力");  
    Teacher b("內存空間");
    
    a.Show();  
    b.Show();  
    
    getchar();
 return 0;
}   
程序將正確運行並輸出:
class Teacher:: class Student: class Teacher:
teacher = 王大力
number = 1
score = 100
director = 內存空間
釋放堆區director內存空間
釋放class Student 內存空間
釋放堆區director內存空間

大家可以發現最明顯的改變在這里 :Student(char *temp): teacher(temp) 
冒號后的teacher就是告訴調用Student類的構造函數的時候把參數傳遞給成員teacher的Teacher類的構造函數,這樣一來我們就成功的在類體外對teacher成員進行了初始化,既方便也高效,這種冒號后指定調用某成員構造函數的方式,可以同時指定多個成員,這一特性使用逗號方式,例如: 
Student(char* temp):teacher(temp),abc(temp),def(temp) 
由冒號后可指定調用那個類成員的構造函數的特性,使得我們可以給類的常量和引用成員進行初始化成為可能。 
我們修改上面的程序,得到如下代碼:
#include <iostream>
using namespace std;

class Teacher  
{   
    char *director; 
public: 
    Teacher(char *temp)  
    {  
  cout << "class Teacher:";
  
        director = new char[10];    
        strcpy(director, temp);  
    }  
    ~Teacher()
 {
  cout << "釋放堆區director內存空間\n";  
        delete[] director;  
        cin.get();  
 }  
    char* GetMember()
 {  
     return director;  
 }  
 void Show() 
 {
  cout << "director = " << director << endl; 
 }
};  
  
class Student  
{  
 int number;  
    int score;  
    Teacher teacher;
    int &pk;  
    const int ps;  
public:  
    Student(char* temp, int &k): teacher(temp), pk(k), ps(10)  
    {  
  cout << "class Student:";
        number = 1;  
        score = 100;  
    }  
    
    ~Student()  
    {
  cout << "釋放class Student 內存空間\n"; 
  cin.get();  
 }
    void Show() 
 {  
     cout << "\nteacher = " << teacher.GetMember() << endl;
  cout << "number = " << number << endl;
  cout << "score = " << score << endl; 
  cout << "pk = " << pk << endl;  
  cout << "ps = " << ps << endl;     
 }  
};  
  
int main()  
{  
 char *name = "王大力";  
    int b = 99;  
    
    Student a(name, b);  
    a.Show();  
    
    getchar();
 return 0;
}   
程序將正確運行並輸出:
class Teacher:: class Student: teacher = 王大力
number = 1
score = 100
pk = 99
ps = 10
釋放class Student 內存空間
釋放堆區director內存空間
  
改變之處最重要的在這里Student(char* temp, int &k): teacher(temp), pk(k), ps(10)  
調用的時候我們使用 
Student a(name, b);  
我們將b的地址傳遞給了int &k這個引用,使得Student類的引用成員pk和常量成員ps進行了成功的初始化。 
 但是細心的人會發現,我們在這里使用的初始化方式並不是在構造函數內進行的,而是在外部進行初始化的。的確,在冒號后和在構造函數括號內的效果是一樣的,但和teacher(temp)所不同的是,pk(pk)的括號不是調用函數的意思,而是賦值的意思,我想有些讀者可能不清楚新標准的c++對變量的初始化是允許使用括號方式的,int a=10和int a(10)的等價的,但冒號后是不允許使用=方式只允許()括號方式,所以這里只能使用pk(pk)而不能是pk=pk了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM