詳解c++中類的六個默認的成員函數

類的6個默認的成員函數包括:

構造函數、析構函數、拷貝構造函數、賦值運算符重載函數、取地址操作符重載、const

修飾的取地址操作符重載。

這篇文章重點解釋前四個。

(一)構造函數

構造函數,顧名思義,爲對象分配空間,進行初始化。它是一種特殊的成員函數,具有

下特點:

1.函數名與類名相同。

2.無返回值。

3.構造對象的時候系統會自動調用構造函數。

4.可以重載。

5.可以在類中定義,也可以在類外定義。

6.如果類中沒有給出構造函數,編譯器會自動產生一個缺省的構造函數,如果類中有構

造函數,編譯器就不會產生缺省構造函數。

7.全缺省的構造函數和無參的構造函數只能有一個,否則調用的時候就會產生衝突。

8.沒有this指針。因爲構造函數纔是創建對象的,沒有創建對象就不會有對象的首地址。

構造函數,說來就是給成員變量進行初始化。而初始化卻有兩種方法:

初始化列表、構造函數函數體內賦值。

舉例:依然使用日期類來說明:

[cpp] view plain copy
  1. #define _CRT_SECURE_NO_WARNINGS 1  
  2.   
  3. #include<iostream>  
  4. using namespace std;  
  5.   
  6. class Date   
  7. {  
  8. public:  
  9.     Date()//無參構造函數  
  10.     {  
  11.         m_year = 2016;  
  12.         m_month = 7;  
  13.         m_day = 6;  
  14.     }  
  15.     Date(int year = 1900, int month = 1, int day = 1)//全缺省的構造函數  
  16.     {  
  17.         m_year = year;  
  18.         m_month = month;  
  19.         m_day = day;  
  20.     }  
  21.     Date(int year, int month, int day) :m_year(year), m_month(month), m_day(day)  
  22.         //初始化列表初始化成員變量  
  23.     {  
  24.     }  
  25.     void print()  
  26.     {  
  27.         cout << m_year << "-" << m_month << "-" << m_day << endl;  
  28.     }  
  29. private:  
  30.     int m_year;  
  31.     int m_month;  
  32.     int m_day;  
  33. };  
  34. int main()  
  35. {  
  36.     Date date(2016,7,4);  
  37.     date.print();  
  38.     system("pause");  
  39.     return 0;  
  40. }  


上邊這段代碼只是爲了解釋初始化列表初始化成員變量和在構造函數體內初始化,也解

釋了無參構造函數和全缺省的構造函數。聲明:由於上邊的代碼同時給出無參和全缺省

的構造函數,產生調用衝突,編譯不通過。

既然有兩種初始化的方法,我們究竟該怎樣選擇呢??

儘量使用初始化列表,因爲它更高效。下邊用代碼說明它是怎麼個高效法。

[cpp] view plain copy
  1. <pre name="code" class="cpp">class Time  
  2. {  
  3. public:  
  4.     Time(int hour= 1, int minute=1, int second=1)  
  5.     {  
  6.         m_hour = hour;  
  7.         m_minute = minute;  
  8.         m_second = second;  
  9.         cout << "構造時間類對象中..." << endl;  
  10.     }  
  11. private:  
  12.     int m_hour;  
  13.     int m_minute;  
  14.     int m_second;  
  15. };  
  16. class Date  
  17. {  
  18. public:  
  19.     Date(int year, int month, int day,Time t)  
  20.         /*:m_year(year),  
  21.         m_month(month), 
  22.         m_day(day) 
  23.         */  
  24.     {  
  25.         m_year = year;  
  26.         m_month = month;  
  27.         m_day = day;  
  28.         m_t = t;  
  29.     }  
  30.     void print()  
  31.     {  
  32.         cout << m_year << "-" << m_month << "-" << m_day << endl;  
  33.     }  
  34. private:  
  35.     int m_year;  
  36.     int m_month;  
  37.     int m_day;  
  38.     Time m_t;  
  39. };  
  40. int main()  
  41. {  
  42.     Time t(10,36,20);  
  43.     Date d(2016,7,6,t);  
  44.     system("pause");  
  45.     return 0;  
  46. }  


上邊給出不使用初始化列表初始化日期類中的時間類對象的 辦法,會導致時間類構造兩

次,一次在主函數中定義時間類對象時,一次在參數列表中調用。而如果我們將所有

的成員變量都用初始化列表初始化,時間類構造函數只會被調用一次,這就是提高效率

所在。

有些成員變量必須再初始化列表中初始化,比如:

1. 常量成員變量。(常量創建時必須初始化,因爲對於一個常量,我們給它賦值,是不

                              對的)2. 引用類型成員變量。(引用創建時必須初始化)

3. 沒有缺省構造函數的類成員變量。(如果構造函數的參數列表中有一個類的對象,並

且該對象的類裏沒有缺省參數的構造函數時,要是不使用初始化列表,參數中會調用無

參或者全缺省的構造函數,而那個類中又沒有。)

注意:在上邊的main函數中要是有這樣一句:Date d2();這不是定義一個對象,而是聲

明瞭一個函數名爲d2,無參,返回值爲Date的函數。

(二)析構函數

析構函數是一種特殊的成員函數,具有以下特點:

1. 析構函數函數名是在類名加上字符~。2. 無參數無返回值(但有this指針)。3. 一個類有且只有一個析構函數,所以肯定不能重載。若未顯示定義,系統會自動生成

    缺省的析構函數。

4. 對象生命週期結束時,C++編譯系統系統自動調用析構函數。

5. 注意析構函數體內並不是刪除對象,而是做一些清理工作。(比如我們在構造函數

   中動態開闢過一段空間,函數結束後需要釋放,而系統自動生成的析構函數纔不管內

  存釋放呢,所以需要人爲地寫出析構函數)

注意:對象生命週期結束後,後構造的對象先釋放。

(三)拷貝構造函數:用已有的對象創建一個新的對象。仍然使用上邊的日期類舉例:

[cpp] view plain copy
  1. int main()  
  2. {  
  3.        Date d1(2016,7,6);  
  4.        Date d2(d1);  
  5.         system("pause");  
  6.         return 0;  
  7. }  


上邊是用d1創建一個d2,系統會給出默認的拷貝構造函數,並且該函數的參數是一個常

引用,我們想象爲什麼必須是引用呢,如果不是又會發生什麼。

如果不是引用,形參是實參的一份臨時拷貝,由於兩者都是對象,此時就會調用自己的

拷貝構造函數,陷入無限遞歸中.......

上邊的代碼,我們用默認的拷貝構造函數可以得到正確的結果,有時就不會。實例:

[cpp] view plain copy
  1. class Person  
  2. {  
  3. public:  
  4.     Person(char *name,int age)  
  5.     {  
  6.         m_name = (char *)malloc(sizeof(char)*10);  
  7.         if (NULL == m_name)  
  8.         {  
  9.             cout << "out of memory" << endl;  
  10.             exit(1);  
  11.         }  
  12.         strcpy(m_name,name);  
  13.         m_age = age;  
  14.     }  
  15.     ~Person()  
  16.     {  
  17.         free(m_name);  
  18.         m_name = 0;  
  19.     }  
  20. private:  
  21.     char *m_name;  
  22.     int m_age;  
  23. };  
  24. int main()  
  25. {  
  26.     Person p1("yang",20);  
  27.     Person p2= p1;  
  28.     system("pause");  
  29.     return 0;  
  30. }  


上邊的代碼會出錯,原因見圖片。

在析構時,同一塊空間釋放兩次就會有問題。

這種僅僅只是值的拷貝的拷貝方式就是淺拷貝。

深拷貝就是爲對象重新分配空間之後,然後將值拷貝的拷貝方式。

下邊自己給出拷貝構造函數。

[cpp] view plain copy
  1. Person(const Person &p)  
  2. {  
  3.     m_name = new char[strlen(p.m_name)+1];  
  4.     if (m_name != 0)  
  5.     {  
  6.         strcpy(m_name,p.m_name);  
  7.         m_age = p.m_age;  
  8.     }  
  9. }  


下邊用圖給出實現機理。

調用拷貝構造函數的3種情況:

1.當用類的一個對象去初始化該類的另一個對象時。

2.當函數的形參是類的對象,調用函數時進行形參和實參的結合時。

3.當函數的返回值是對象,函數執行完返回調用者時。(函數運行結束後,返回的對象

會複製到一個無名對象中,然後返回的對象會消失,當調用語句執行完之後,無名對

象就消失了)

調用拷貝構造函數的兩種方法:

1.代入法:

Person p2(p1);

2.賦值法:

Person p2 = p1;

(四)賦值運算符重載函數

它是兩個已有對象一個給另一個賦值的過程。它不同於拷貝構造函數,拷貝構造函數是

用已有對象給新生成的對象賦初值的過程。

默認的賦值運算符重載函數實現的數據成員的逐一賦值的方法是一種淺層拷貝。

淺層拷貝會導致的指針懸掛的問題:

[cpp] view plain copy
  1. class Person  
  2. {  
  3. public:  
  4.     Person(char *name,int age)  
  5.     {  
  6.         m_name = (char *)malloc(sizeof(char)*10);  
  7.         if (NULL == m_name)  
  8.         {  
  9.             cout << "out of memory" << endl;  
  10.             exit(1);  
  11.         }  
  12.         strcpy(m_name,name);  
  13.         m_age = age;  
  14.     }  
  15.     ~Person()  
  16.     {  
  17.         free(m_name);  
  18.         m_name = 0;  
  19.     }  
  20. private:  
  21.     char *m_name;  
  22.     int m_age;  
  23. };  
  24. int main()  
  25. {  
  26.     Person p1("yang",20);  
  27.     Person p2("yao",20);  
  28.     p2 = p1;  
  29.     system("pause");  
  30.     return 0;  
  31. }  


看圖:

使用深層拷貝來解決指針懸掛的問題:

[cpp] view plain copy
  1. <pre name="code" class="cpp">Person & operator=(const Person &p)  
  2.     {  
  3.         if (this == &p)  
  4.             return *this;  
  5.         delete[]m_name;  
  6.         m_name = new char[strlen(p.m_name) + 1];  
  7.         strcpy(m_name,p.m_name);  
  8.                 m_age= p.m_age;  
  9.         return *this;  
  10.     }  


這樣先將p2對象裏指針指向的舊區域,然後再分配新的空間,再拷貝內容。當然,對於

那些成員變量裏沒有指針變量就不會涉及到指針懸掛問題。

(五)取地址操作符重載

[cpp] view plain copy
  1. Person * operator&()  
  2. {  
  3.         return this;  
  4. }  


(六)const修飾的取地址操作符的重載

[cpp] view plain copy
  1. const Person * operator&()  const  
  2. {  
  3.         return this;  
  4. }  


函數後邊的const表明在函數體中不能改變對象的成員,當然可以改變mutable變量。函

數的返回值是指向常對象的指針。



本文轉自 http://blog.csdn.net/peiyao456/article/details/51834981

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