C++構造函數 & 拷貝構造函數 & 派生類的構造函數 & 虛繼承的構造函數
構造函數 ,是一種特殊的方法 。主要用來在創建對象時初始化對象, 即為對象成員變量賦初始值,總與new運算符一起使用在創建對象的語句中 。特別的一個類可以有多個構造函數 ,可根據其參數個數的不同或參數類型的不同來區分它們 即構造函數的重載。(摘自百度百科構造函數)。
一、最基本的構造函數
復制代碼
1 class Base
2 {
3 public:
4 Base(int var) : m_Var(var)
5 {
6 }
7 private:
8 int m_Var;
9 };
復制代碼
以上構造函數的執行過程:
1)傳參 2)給類數據成員開辟空間 3)執行冒號語法給數據成員初始化 4)執行構造函數括號裡面的內容
這裡需要說明的是:冒號語法後面的內容相當於int a = 10;(初始化),而構造函數括號裡面則是相當於是int a; a = 10;(賦初值)
二、拷貝構造函數
復制代碼
1 class Base
2 {
3 public:
4 Base(int var) : m_Var(var)
5 {
6 }
7 //拷貝構造函數
8 Base(Base &ref) : m_Var(ref.m_Var)
9 {
10 }
11 private:
12 int m_Var;
13 };
復制代碼
為什麼拷貝構造函數的參數只能用引用呢?
這就要從拷貝構造函數式數碼時候觸發開始說起了,以下幾種情況都會自動調用拷貝構造函數:
1)用一個已有的對象初始化一個新對象的時候
2)將一個對象以值傳遞的方式傳給形參的時候
3)函數返回一個對象的時候
所以當一個對象以傳遞值的方式傳一個函數的時候,拷貝構造函數自動的被調用來生成函數中的對象。如果一個對象是被傳入自己的拷貝構造函數,它的拷貝構造函數將會被調用來拷貝這個對象這樣復制才可以傳入它自己的拷貝構造函數,這會導致無限循環直至棧溢出除了當對象傳入函數的時候被隱式調用以外,拷貝構造函數在對象被函數返回的時候也同樣的被調用。(摘自百度百科拷貝構造函數)。
拷貝構造函數,一般不需要自己編寫,系統默認的拷貝構造函數就能抗住了,但是有些情況需要在構造的時候開辟空間,這時候就需要拷貝構造函數了,如下代碼是摘自林銳博士的高質量C++編程指南一文。
復制代碼
1 class String
2 {
3 public:
4 String(const char *str = NULL); // 普通構造函數
5 String(const String &other); // 拷貝構造函數
6 ~ String(void); // 析構函數
7 private:
8 char *m_data; // 用於保存字符串
9 };
10 // String 的析構函數
11 String::~String(void)
12 {
13 delete [] m_data;
14 // 由於m_data 是內部數據類型,也可以寫成 delete m_data;
15 }
16
17 // String 的普通構造函數
18 String::String(const char *str)
19 {
20 if(str==NULL)
21 {
22 m_data = new char[1]; // 若能加 NULL 判斷則更好
23 *m_data = '\0';
24 }
25 else
26 {
27 int length = strlen(str);
28 m_data = new char[length+1]; // 若能加 NULL 判斷則更好
29 strcpy(m_data, str);
30 }
31 }
32 // 拷貝構造函數
33 String::String(const String &other)
34 {
35 int length = strlen(other.m_data);
36 m_data = new char[length+1]; // 若能加 NULL 判斷則更好
37 strcpy(m_data, other.m_data);
38 }
復制代碼
三、普通派生類構造函數的寫法
定義派生類對象的時候,會按如下步驟執行構造操作:
1)傳參 2)根據繼承時的聲明順序構造基類 3)給類數據成員開辟空間 4)執行冒號語法後面的語句 5)執行構造函數函數體語句
復制代碼
1 class Base
2 {
3 public:
4 Base(int b) : m_b(b)
5 {
6 }
7 private:
8 int m_b;
9 };
10
11 class Derived : public Base
12 {
13 public:
14 //普通派生類構造函數的寫法
15 Derived(int b, int d) : Base(b), m_d(d)
16 {
17 }
18 private:
19 int m_d;
20 };
復制代碼
再寫一個多繼承的示例:
復制代碼
1 class Base1
2 {
3 public:
4 Base1(int b1) : m_b1(b1)
5 {
6 }
7 private:
8 int m_b1;
9 };
10
11 class Base2
12 {
13 public:
14 Base2(int b2) : m_b2(b2)
15 {
16 }
17 private:
18 int m_b2;
19 };
20
21 class Derived : public Base1, public Base2
22 {
23 public:
24 Derived(int b1, int b2, int d) : Base1(b1), Base2(b2), m_d(d)
25 { //注意冒號語法後面的順序無所謂,創造基類是按照上面的繼承聲明順序來進行的...
26 }
27 private:
28 int m_d;
29 };
復制代碼
四、含有虛繼承的派生類構造函數的寫法
為何要用到虛繼承?
虛繼承主要是針對多繼承時,出現二義性問題而提出的。比如,如下代碼就需要用到虛繼承,否則的話Derived類繼承時,Base類就會不明確。
虛繼承構造函數的執行按照如下步驟:
1)傳參 2)創建基類,注意這時候需要顯示創建所有“有參構造函數”的基類,包括直接基類,間接基類。 3)給類數據成員開辟空間 4)執行冒號語法 5)執行構造函數函數體
注:你可能會疑惑,如下代碼不是將Base間接基類創建了3次嗎?其實不是這樣的,編譯器是這樣處理的,當最遠的派生類Derived創建了基類Base之後,其直接基類創建Base類的語句將會被忽略掉。
復制代碼
1 class Base
2 {
3 public:
4 Base(int b) : m_b(b)
5 {
6 }
7 private:
8 int m_b;
9 };
10
11 class Base1 : virtual public Base
12 {
13 public:
14 Base1(int b, int b1) : Base(b), m_b1(b1)
15 {
16 }
17 private:
18 int m_b1;
19 };
20
21 class Base2 : virtual public Base
22 {
23 public:
24 Base2(int b, int b2) : Base(b), m_b2(b2)
25 {
26 }
27 private:
28 int m_b2;
29 };
30 //虛繼承,避免二義性
31 class Derived : public Base1, public Base2
32 {
33 public:
34 Derived(int b, int b1, int b2, int d) : Base(b), Base1(b, b1), Base2(b, b2), m_d(d)
35 { //注意冒號語法後面的順序無所謂,創造基類是按照上面的繼承聲明順序來進行的...
36 }
37 private:
38 int m_d;
39 };
復制代碼
五、關於虛析構
虛析構一般伴隨著多態而產生,多態主要方式就是用基類的指針或引用指向或引用派生類,而形成多態。
但是這樣就會存在一個問題,當我們析構的時候,由於是基類的指針,就會調用的是基類的構造函數,從而造成派生內存溢出。為了解決這個問題,引入了虛析構的概念。將基類的構造函數聲明為虛,從而使其在調用析構函數的時候能夠准確的調用派生類的析構函數。
如下代碼必須用到虛析構才能准確的析構派生類,並釋放其占有內存。
復制代碼
1 class Base
2 {
3 public:
4 Base(int b) : m_b(b)
5 {
6 }
7 //虛析構,使基類指針能准確的釋放所指向的派生類裡面的內容
8 virtual ~Base()
9 {
10 }
11 private:
12 int m_b;
13 };
14
15 class Derived : public Base
16 {
17 public:
18 Derived(int b, char *pStr) : Base(b)
19 {
20 m_pStr = new char[strlen(pStr)+1];
21 strcpy(m_pStr,pStr);
22 }
23 ~Derived()
24 {
25 delete m_pStr;
26 m_pStr = NULL;
27 }
28 private:
29 char *m_pStr;
30 };
31
32 int main(void)
33 {
34 char *pStr = "abcdefg";
35 Base *b = new Derived(1,pStr);
36 delete b;
37
38 return 0;
39 }