程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++構造函數和拷貝構造函數詳解

C++構造函數和拷貝構造函數詳解

編輯:C++入門知識

 

  構造函數、析構函數與賦值函數是每個類最基本的函數。它們太普通以致讓人容易麻痺大意,其實這些貌似簡單的函數就象沒有頂蓋的下水道那樣危險。

 

每個類只有一個析構函數和一個賦值函數,但可以有多個構造函數(包含一個拷貝構造函數,其它的稱為普通構造函數)。

 

對於任意一個類A,如果不想編寫上述函數,C++編譯器將自動為A 產生四個缺省的函數,例如:

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

C++ 默認構造函數 :

 

1、每個類必須有一個構造函數,否則沒法創建對象;

2、若 程序員沒有提供任何構造函數,則C++提供一個默認的構造函數,該默認構造函數是無參構造函數,它僅負責創建對象,不做任何初始化的工作;

3、只要programer 定義了一個構造函數(不管是無參還是有參構造),C++就不再提供默認的默認構造函數。即如果為類定義了一個帶參的構造函數,還想要無參構造函數,就必須自己定義;

4、與變量定義類似,在用默認構造函數創建對象時,如果創建的是全局對象或靜態對象,則對象的位模式全為0,否則,對象值是隨機的。

 

C++默認拷貝構造函數:

1、默認的拷貝構造函數執行的順序與其他用戶定義的構造函數相同,執行先父類後子類的構造.

2、拷貝構造函數對類中每一個數據成員執行成員拷貝(memberwise Copy)的動作.

3、如果數據成員為某一個類的實例,那麼調用此類的拷貝構造函數.

4、如果數據成員是一個數組,對數組的每一個執行按位拷貝.

5、如果數據成員是一個數量,如int,double,那麼調用系統內建的賦值運算符對其進行賦值.

 

請看下面代碼:

 

//============================================================================ 

// Name        : main.cpp 

// Author      : ShiGuang 

// Version     : 

// Copyright   : [email protected] 

// Description : Hello World in C++, Ansi-style 

//============================================================================ 

 

#include <iostream> 

#include <string> 

using namespace std; 

 

class Student 

public: 

    Student() 

    { 

        cout << "構造函數1" << endl; 

    } 

    Student(int k) 

    { 

        cout << "構造函數2" << endl; 

        i = k; 

    } 

    Student(Student const &m) 

    { 

        cout << "拷貝構造函數" << endl; 

        i = m.i * (-1); 

    } 

 

    void p() 

    { 

        cout << i << endl; 

    } 

    ~Student() 

    { 

        cout << "析構函數" << endl; 

    } 

protected: 

    int i; 

}; 

 

int main(int argc, char **argv) 

    Student s(9818); 

    // 調用構造函數2 

    s.p(); 

 

    Student t(s); 

    // 調用拷貝構造函數 

    t.p(); 

 

    Student k = s; 

    // 調用拷貝構造函數 

    k.p(); 

 

    Student *p = new Student(s); 

    // 調用拷貝構造函數 

    p->p(); 

 

    Student m; 

    // 調用構造函數1 

    m = s;// 賦值運算 

    m.p(); 

 

    return 0; 

運行結果:

 

構造函數2 

9818 

拷貝構造函數 

-9818 

拷貝構造函數 

-9818 

拷貝構造函數 

-9818 

構造函數1 

9818 

析構函數 

析構函數 

析構函數 

析構函數 

下面我們來討論一下關於淺拷貝和深拷貝的問題。

 

        深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候(復制指針所指向的值),這個過程就可以叫做深拷貝,反之對象存在資源但復制過程並未復制資源(只復制了指針所指的地址)的情況視為淺拷貝。

  很多人會問到,既然系統會自動提供一個默認的拷貝構造函數來處理復制,那麼我們沒有必要去自定義拷貝構造函數呀,對,就普通情況而言這的確是沒有必要的,但在某些狀況下,類體內的成員是需要開辟動態堆內存的,如果我們不自定義拷貝構造函數而讓系統自己處理,那麼就會導致堆內存的所屬權產生混亂。試想一下,已經開辟的一端堆地址原來是屬於對象a的,由於復制過程發生,b對象取得是a已經開辟的堆地址,一旦程序產生析構,釋放堆的時候,計算機不清楚這段地址是真正屬於誰的,當連續發生兩次析構的時候就出現了運行錯誤。

  為了更詳細的說明問題,請看如下的代碼。

 

 

//============================================================================ 

// Name        : main.cpp 

// Author      : ShiGuang 

// Version     : 

// Copyright   : [email protected] 

// Description : Hello World in C++, Ansi-style 

//============================================================================ 

 

#include <iostream> 

#include <string> 

using namespace std; 

 

class aa 

public: 

    aa() 

    { 

        cout << "調用構造函數" << endl; 

        f = new char[10]; 

    } 

    ~aa() 

    { 

        cout << "調用析構函數" << endl; 

        delete[] f; 

    } 

    char * f; 

}; 

 

int main(int argc, char **argv) 

    aa p; 

    printf("p.f=%p\n",p.f); 

    strcpy(p.f, "Computer"); 

    cout << p.f << endl; 

    aa q(p);// 調用默認的拷貝構造函數 

    printf("q.f=%p\n",q.f); 

    cout << q.f << endl; 

 

    strcpy(p.f, "Software"); 

    cout << p.f << endl; 

    cout << q.f << endl; 

 

    strcpy(q.f, "Software"); 

    cout << p.f << endl; 

    cout << q.f << endl; 

 

    return 0; 

運行結果:

 

調用構造函數 

p.f=003F1048 

Computer 

q.f=003F1048 

Computer 

Software 

Software 

Software 

Software 

調用析構函數 

調用析構函數 

通過上面的例子,我們能清楚的看到,第二個對象調用拷貝構造函數,q.f獲得的地址值與p.f相同,即指向了同一片內存區域。程序結束時,會兩次調用析構函數,即同一個指針執行兩遍delete操作,會發生什麼呢?這可能是一場災難,可能會破壞該堆及自由內存表。

那我們該如何避免呢?這裡我們就需要使用深拷貝。

 

 

//============================================================================ 

// Name        : main.cpp 

// Author      : ShiGuang 

// Version     : 

// Copyright   : [email protected] 

// Description : Hello World in C++, Ansi-style 

//============================================================================ 

 

#include <iostream> 

#include <string> 

using namespace std; 

 

class aa 

public: 

    aa() 

    { 

        cout << "調用構造函數" << endl; 

        f = new char[10]; 

    } 

    aa(aa const & s) 

    { 

        cout << "調用拷貝構造函數" << endl; 

        f = new char[10]; 

        strcpy(f, s.f); 

    } 

    ~aa() 

    { 

        cout << "調用析構函數" << endl; 

        delete[] f; 

    } 

    char * f; 

}; 

 

int main(int argc, char **argv) 

    aa p; 

    printf("p.f=%p\n", p.f); 

    strcpy(p.f, "Computer"); 

    cout << p.f << endl; 

    aa q(p);// 調用用戶的拷貝構造函數 

    printf("q.f=%p\n", q.f); 

    cout << q.f << endl; 

 

    strcpy(p.f, "Software"); 

    cout << p.f << endl; 

    cout << q.f << endl; 

 

    strcpy(q.f, "Software"); 

    cout << p.f << endl; 

    cout << q.f << endl; 

 

    return 0; 

運行結果:

 

 

 

調用構造函數 

p.f=00351048 

Computer 

調用拷貝構造函數 

q.f=00351060 

Computer 

Software 

Computer 

Software 

Software 

調用析構函數 

調用析構函數 

現在我們可以看到,p.f和q.f分別指向不同的內存區域。

 

作者 sg131971的學習筆記

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved