C++(1)C++類四個默認函數---構造函數、析構函數、拷貝函數、賦值函數


C++構造函數和析構函數

默認構造函數指不帶參數或者所有參數都有缺省值的構造函數!!!

(1)構造函數、析構函數與賦值函數

構造函數、析構函數與賦值函數是每個類最基本的函數。它們太普通以致讓人容易麻痹大意,

其實這些貌似簡單的函數就象沒有頂蓋的下水道那樣危險。

每個類只有一個析構函數和一個賦值函數,但可以有多個構造函數(包含一個拷貝構造函數,其它的稱為普通構造函數)。對於任意一個類A,如果不想編寫上述函數,C++編譯器將自動為A 產生四個缺省的函數,例如:

A(void); // 缺省的無參數構造函數

A(const A &a); // 缺省的拷貝構造函數

~A(void); // 缺省的析構函數

A & operate =(const A &a); // 缺省的賦值函數

這不禁讓人疑惑,既然能自動生成函數,為什么還要程序員編寫?原因如下:

<1>如果使用“缺省的無參數構造函數”和“缺省的析構函數”,等於放棄了自主“初始化”和“清除”的機會,C++發明人Stroustrup 的好心好意白費了。

<2>“缺省的拷貝構造函數”和“缺省的賦值函數”均采用“位拷貝”而非“值拷貝”的方式來實現,倘若類中含有指針變量,這兩個函數注定將出錯。

對於那些沒有吃夠苦頭的C++程序員,如果他說編寫構造函數、析構函數與賦值函數很容易,可以不用動腦筋,表明他的認識還比較膚淺,水平有待於提高。

下面以類String 的設計與實現為例,深入闡述被很多教科書忽視了的道理。String的結構如下:

class String

{ 

public:   

    String(const char *str = NULL); // 普通構造函數

    String(const String &other); // 拷貝構造函數

    ~ String(void); // 析構函數   

    String & operate =(const String &other); // 賦值函數

private: 

    char *m_data; // 用於保存字符串

};

2)構造函數是一種特殊的成員函數,無返回值,函數名與類同名。它提供了對成員變量進行初始化的方法,使得在聲明對象時能自動地初始化對象。因為當程序創建一個對象時,系統會自動調用該對象所屬類的構造函數。

例一:class Student

{

     Student()//默認無參無賦值操作構造函數

     {

     }

}

Student stu;//聲明對象

以上代碼中的無參無操作構造函數即為系統自動提供一個默認的構造函數,該默認構造函數沒有參數,它僅僅負責創建對象而不做任何賦值操作。

例二:class Student

{   

     Student()//無參帶賦值操作構造函數

     {

         memberVariable1=constValue1;

         memberVariable2=constValue2;

     }

}

Student stu;//聲明對象

以上代碼中,在默認構造函數添加賦值初始化操作,該構造函數將覆蓋默認構造函數。該構造函數沒有參數,它不僅負責創建對象而還負責成員變量的狀態初始化。

例三:class Student

{   

     Student(type1 value1,type2 value2) //含參帶賦值操作構造函數

     {

         memberVariable1=value1;

         memberVariable2=value2;

     }

}

Student stu(value1,value2);//聲明對象

以上代碼中,在默認構造函數中添加參數和賦值初始化操作,該構造函數將覆蓋默認構造函數。該構造函數沒有參數,它不僅負責創建對象而還負責傳值對成員變量進行狀態初始化。

一旦類中有了一個帶參數的構造函數而又沒無參數構造函數的時候系統將無法創建不帶參數的對象,此時以下三種聲明都是錯誤的:

Student stu;

Student *stu = new Student;

Student *stu = new Student();

例四:class Student

{

     Student()

     {

     }

     /*Student()

     {

         memberVariable1=constValue1;

         memberVariable2=constValue2;

     }*/

     Student(type1 value1,type2 value2)

     {

         memberVariable1=value1;

         memberVariable2=value2;

     }

};

Student stu; // 聲明對象—棧對象

Student *stu; // 類指針變量—棧對象

Student *stu = new Student; // çèStudent *stu = new Student();—堆對象

Student stu(value1,value2); // 聲明對象—棧對象

Student *stu = new Student(value1,value2); // 聲明對象—堆對象 

    以上代碼中,既有無參(默認)構造函數,又有含參和賦值操作的構造函數;既可聲明無參對象,也可聲明含參初始化對象。注意new是在堆上動態創建的。 

由於構造函數和普通函數一樣具有重載特性所以編寫程序的人可以給一個類添加任意多個構造函數,來使用不同的參數來進行初始化對象!

類一旦定義就可以當作一種新的數據類型,可作為另一個類的數據成員,即類可以嵌套定義。

類是一個抽象的概念,並不是一個實體,並不能包含屬性值(這里來說也就是構造函數的參數了),只有對象才占有一定的內存空間,含有明確的屬性值!

一個類可能需要在構造函數內動態分配資源,那么這些動態開辟的資源就需要在對象不復存在之前被銷毀掉,那么c++類的析構函數就提供了這個方便。

(3)構造函數的初始化表

構造函數有個特殊的初始化方式叫“初始化表達式表”(簡稱初始化表)。初始化表位於函數參數表之后,卻在函數體 {} 之前。這說明該表里的初始化工作發生在函數體內的任何代碼被執行之前。

構造函數初始化表的使用規則:

<1> 如果類存在繼承關系,派生類必須在其初始化表里調用基類的構造函數。例如:

class A

{   …

    A(int x); // A 的構造函數

};

class B : public A

{   …

    B(int x, int y);// B 的構造函數

};

B::B(int x, int y): A(x) // 在初始化表里調用A 的構造函數

{   …

}

<2>類的 const 常量只能在初始化表里被初始化,因為它不能在函數體內用賦值的方式來初始化。

  注:不要在類定義.h文件里聲明const常量時直接初始化,會有警告,只有在c++11中才有效

如:

//A.h

class A
{
    const int n =1;               
}

報警告:

 

這個與C語言中const定義常對象時,同樣要進行初始化,並且該對象不能再被更新寫法上有所區別,但是其實意義是一樣的,只不過c++中再初始表中初始,同樣不可更新。

 

<3>類的數據成員的初始化可以采用初始化表或函數體內賦值兩種方式,這兩種方式的效率不完全相同。

[1]非內部數據類型的成員對象應當采用第一種方式初始化,以獲取更高的效率。例如:

class A
{   …
    A(void); // 無參數構造函數

    A(const A &other); // 拷貝構造函數

    A & operate =( const A &other); // 賦值函數
};

class B
{
public:  
    B(const A &a); // B 的構造函數 

private: 
    A m_a; // 成員對象

};

示例 9-2(a)中,類B 的構造函數在其初始化表里調用了類A的拷貝構造函數,從而將成員對象m_a 初始化。

示例 9-2 (b)中,類B 的構造函數在函數體內用賦值的方式將成員對象m_a 初始化。我們看到的只是一條賦值語句,但實際上B 的構造函數干了兩件事:先暗地里創建m_a對象(調用了A 的無參數構造函數),再調用類A 的賦值函數,將參數a 賦給m_a。

示例 9-2(a) 成員對象在初始化表中被初始化:

B::B(const A &a) : m_a(a)
{  
    …
}

示例9-2(b) 成員對象在函數體內被初始化:

B::B(const A &a)
{      
    m_a = a;
    … 
}

[2]對於內部數據類型的數據成員而言,兩種初始化方式的效率幾乎沒有區別,但后者的程序版式似乎更清晰些。若類F的聲明如下:

class F
{    
public:  
    F(int x, int y); // 構造函數

private:          
    int m_x, m_y;  
    int m_i, m_j;

}

示例9-2(c)中F 的構造函數采用了第一種初始化方式,示例9-2(d)中F 的構造函數采用了第二種初始化方式。

示例 9-2(c) 數據成員在初始化表中被初始化:

F::F(int x, int y) : m_x(x), m_y(y)
{
    m_i = 0;
    m_j = 0;
}

示例9-2(d) 數據成員在函數體內被初始化:

F::F(int x, int y)
{     
    m_x = x;
    m_y = y; 
    m_i = 0;   
    m_j = 0;
}

(4)拷貝構造函數和賦值函數的區別

拷貝構造函數和賦值函數非常容易混淆,常導致錯寫、錯用。拷貝構造函數是在對象被創建時調用的,而賦值函數只能被已經存在了的對象調用。以下程序中,第三個語句和第四個語句很相似,你分得清楚哪個調用了拷貝構造函數,哪個調用了賦值函數嗎?

String a(“hello”);
String b(“world”);
String c = a; // 調用了拷貝構造函數,最好寫成c(a);
c = b; // 調用了賦值函數

5)析構函數也是特殊的類成員函數,它沒有返回類型,沒有參數,不能隨意調用,也沒有重載,只有在類對象的生命期結束的時候,由系統自動調用,用來在系統釋放對象前做一些清理工作,如利用delete運算符釋放臨時分配的內存、清零某些內存單元等。

定義析構函數因使用"~"符號加類名(邏輯非運算符),表示它為逆構造函數,它不能帶任何參數。

轉自:http://allchange.blog.sohu.com/155795465.html

 

注:內部數據類型 非內部數據類型

內部數據類型是編譯器本來就認識的,不需要用戶自己定義,如int 、char
非內部數據類型不是編譯器本來就認識的,需要用戶自己定義才能讓編譯器識別

運算符使用是否正確,編譯器在編譯掃描分析時就可以判定
庫函數是已編譯的代碼,編譯器不會編譯檢查,由鏈接器將庫同用戶寫的代碼合成exe文件


免責聲明!

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



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