C++構造函數詳解(復制構造函數)


構造函數是干什么的

該類對象被創建時,編譯系統對象分配內存空間,並自動調用該構造函數,由構造函數完成成員的初始化工作,故:構造函數的作用:初始化對象的數據成員。

 

構造函數的種類

class Complex
{

private :
    double m_real;
    double m_imag;

public:

    // 無參數構造函數
    // 如果創建一個類你沒有寫任何構造函數,則系統會自動生成默認的無參構造函數,函數為空,什么都不做
    // 只要你寫了一個下面的某一種構造函數,系統就不會再自動生成這樣一個默認的構造函數,如果希望有一個這樣的無參構造函數,則需要自己顯示地寫出來
    Complex(void)
    {
         m_real = 0.0;
         m_imag = 0.0;
    }

    // 一般構造函數(也稱重載構造函數)
    // 一般構造函數可以有各種參數形式,一個類可以有多個一般構造函數,前提是參數的個數或者類型不同(基於c++的重載函數原理)
    // 例如:你還可以寫一個 Complex( int num)的構造函數出來
    // 創建對象時根據傳入的參數不同調用不同的構造函數
    Complex(double real, double imag)
    {
         m_real = real;
         m_imag = imag;
     }

    // 復制構造函數(也稱為拷貝構造函數)
    // 復制構造函數參數為類對象本身的引用,用於根據一個已存在的對象復制出一個新的該類的對象,一般在函數中會將已存在對象的數據成員的值復制一份到新創建的對象中
    // 若沒有顯示的寫復制構造函數,則系統會默認創建一個復制構造函數,但當類中有指針成員時,由系統默認創建該復制構造函數會存在風險,具體原因請查詢有關 “淺拷貝” 、“深拷貝”的文章論述
    Complex(const Complex & c)
    {
        // 將對象c中的數據成員值復制過來
        m_real = c.m_real;
        m_img  = c.m_img;
    }

    // 類型轉換構造函數,根據一個指定的類型的對象創建一個本類的對象
    // 例如:下面將根據一個double類型的對象創建了一個Complex對象
    Complex::Complex(double r)
    {
        m_real = r;
        m_imag = 0.0;
    }

    // 等號運算符重載
    // 注意,這個類似復制構造函數,將=右邊的本類對象的值復制給等號左邊的對象,它不屬於構造函數,等號左右兩邊的對象必須已經被創建
    // 若沒有顯示的寫=運算符重載,則系統也會創建一個默認的=運算符重載,只做一些基本的拷貝工作
    Complex &operator=(const Complex &rhs)
    {
        // 首先檢測等號右邊的是否就是左邊的對象本,若是本對象本身,則直接返回
        if ( this == &rhs )
        {
            return *this;
        }

        // 復制等號右邊的成員到左邊的對象中
        this->m_real = rhs.m_real;
        this->m_imag = rhs.m_imag;

        // 把等號左邊的對象再次傳出
        // 目的是為了支持連等 eg:    a=b=c 系統首先運行 b=c
        // 然后運行 a= ( b=c的返回值,這里應該是復制c值后的b對象)
        return *this;
    }
};

下面使用上面定義的類對象來說明各個構造函數的用法:

void main()
{
    // 調用了無參構造函數,數據成員初值被賦為0.0
    Complex c1,c2;

    // 調用一般構造函數,數據成員初值被賦為指定值
    Complex c3(1.0,2.5);
    // 也可以使用下面的形式
    Complex c3 = Complex(1.0,2.5);

    // 把c3的數據成員的值賦值給c1
    // 由於c1已經事先被創建,故此處不會調用任何構造函數
    // 只會調用 = 號運算符重載函數
    c1 = c3;

    // 調用類型轉換構造函數
    // 系統首先調用類型轉換構造函數,將5.2創建為一個本類的臨時對象,然后調用等號運算符重載,將該臨時對象賦值給c1
    c2 = 5.2;

    // 調用拷貝構造函數( 有下面兩種調用方式)
    Complex c5(c2);
    Complex c4 = c2;  // 注意和 = 運算符重載區分,這里等號左邊的對象不是事先已經創建,故需要調用拷貝構造函數,參數為c2

}

參考:http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html

復制構造函數

這是清華大學的教程中講解,參考

=============================================================
對一個簡單變量的初始化方法是用一個常量或變量初始化另一個變量,例如:
  int m = 80;
  int n = m;
  我們已經會用構造函數初始化對象,那么我們能不能象簡單變量的初始化一樣,直接用一個對象來初始化另一個對象呢?答案是肯定的。我們以前面定義的Point類為例:
  Point pt1(15, 25);
  Point pt2 = pt1;
后一個語句也可以寫成:
  Point pt2( pt1);
它是用pt1初始化pt2,此時,pt2各個成員的值與pt1各個成員的值相同,也就是說,pt1各個成員的值被復制到pt2相應的成員當中。在這個初始化過程當中,實際上調用了一個復制構造函數。當我們沒有顯式定義一個復制構造函數時,編譯器會隱式定義一個缺省的復制構造函數,它是一個內聯的、公有的成員,它具有下面的原型形式:
  Point:: Point (const Point &);
可見,復制構造函數與構造函數的不同之處在於形參,前者的形參是Point對象的引用,其功能是將一個對象的每一個成員復制到另一個對象對應的成員當中。
  雖然沒有必要,我們也可以為Point類顯式定義一個復制構造函數:
  Point:: Point (const Point &pt)
  {
   xVal=pt. xVal;
   yVal=pt. yVal;
  } 
  如果一個類中有指針成員,使用缺省的復制構造函數初始化對象就會出現問題。為了說明存在的問題,我們假定對象A與對象B是相同的類,有一個指針成員,指向對象C。當用對象B初始化對象A時,缺省的復制構造函數將B中每一個成員的值復制到A的對應的成員當中,但並沒有復制對象C。也就是說,對象A和對象B中的指針成員均指向對象C

 

 

復制構造函數遵循以下幾個原則:

C++ primer p406 :復制構造函數是一種特殊的構造函數,具有單個形參,該形參(常用const修飾)是對該類類型的引用。當定義一個新對象並用一個同類型的對象對它進行初始化時,將顯示使用復制構造函數。當該類型的對象傳遞給函數或從函數返回該類型的對象時,將隱式調用復制構造函數。

 

C++支持兩種初始化形式:復制初始化(int a = 5;)和直接初始化(int a(5);)對於其他類型沒有什么區別,對於類類型直接初始化直接調用實參匹配的構造函數,復制初始化總是調用復制構造函數,也就是說:

A x(2);  //直接初始化,調用構造函數
A y = x;  //復制初始化,調用復制構造函數

 

必須定義復制構造函數的情況:

只包含類類型成員或內置類型(但不是指針類型)成員的類,無須顯式地定義復制構造函數也可以復制;有的類有一個數據成員是指針,或者是有成員表示在構造函數中分配的其他資源,這兩種情況下都必須定義復制構造函數。

 

什么情況使用復制構造函數:

類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:
(1)一個對象以值傳遞的方式傳入函數體
(2)一個對象以值傳遞的方式從函數返回
(3)一個對象需要通過另外一個對象進行初始化。

 

深拷貝和淺拷貝:

所謂淺拷貝,指的是在對象復制時,只對對象中的數據成員進行簡單的賦值,默認拷貝構造函數執行的也是淺拷貝。在“深拷貝”的情況下,對於對象中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間

如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝

上面提到,如果沒有自定義復制構造函數,則系統會創建默認的復制構造函數,但系統創建的默認復制構造函數只會執行“淺拷貝”,即將被拷貝對象的數據成員的值一一賦值給新創建的對象,若該類的數據成員中有指針成員,則會使得新的對象的指針所指向的地址與被拷貝對象的指針所指向的地址相同,delete該指針時則會導致兩次重復delete而出錯。下面是示例:

#include <iostream.h>
#include <string.h>
class Person
{
public :

    // 構造函數
    Person(char * pN)
    {
        cout << "一般構造函數被調用 !\n";
        m_pName = new char[strlen(pN) + 1];
        //在堆中開辟一個內存塊存放pN所指的字符串
        if(m_pName != NULL)
        {
           //如果m_pName不是空指針,則把形參指針pN所指的字符串復制給它
             strcpy(m_pName ,pN);
        }
    }

    // 系統創建的默認復制構造函數,只做位模式拷貝
    Person(Person & p)
    {
        //使兩個字符串指針指向同一地址位置
        m_pName = p.m_pName;
    }

    ~Person( )
    {
        delete m_pName;
    }

private :
    char * m_pName;
};

void main( )
{
    Person man("lujun");
    Person woman(man);

    // 結果導致   man 和    woman 的指針都指向了同一個地址

    // 函數結束析構時
    // 同一個地址被delete兩次
}


// 下面自己設計復制構造函數,實現“深拷貝”,即不讓指針指向同一地址,而是重新申請一塊內存給新的對象的指針數據成員
Person(Person & chs);
{
     // 用運算符new為新對象的指針數據成員分配空間
     m_pName=new char[strlen(p.m_pName)+ 1];

     if(m_pName)
     {
             // 復制內容
            strcpy(m_pName ,chs.m_pName);
     }

    // 則新創建的對象的m_pName與原對象chs的m_pName不再指向同一地址了
}

重載賦值操作符:

通過定義operate=的函數,可以對賦值進行定義。像其他任何函數一樣,操作符函數有一個返回值和形參表。形參表必須具有與該操作符操作數書目相同的形參(如果操作符是一個成員,則包括隱式this形參)。賦值是二元運算,所以該操作符函數有兩個形參:第一個形參(隱含的this指針)對應着左操作數,第二個形參對應右操作數。

 一個應用了對賦值號重載的拷貝構造函數的例子:

#include <iostream>

using namespace std;

class A
{
public:
    A(int);//構造函數
    A(const A &);//拷貝構造函數
    ~A();
    void print();
    int *point;
    A &operator=(const A &);
};

A::A(int p)
{
    point = new int;
    *point = p;
}

A::A(const A &b)
{
    *this = b;
    cout<<"調用拷貝構造函數"<<endl;
}

A::~A()
{
    delete point;
}

void A::print()
{
    cout<<"Address:"<<point<<" value:"<<*point<<endl;
}

A &A::operator=(const A &b)
{
    if( this != &b)
    {
        delete point;
        point = new int;
        *point = *b.point;
    }
}


int main()
{
    A x(2);
    A y = x;
    x.print();
    delete x.point;
    y.print();

    return 0;
}

參見:C++拷貝構造函數詳解: http://blog.csdn.net/lwbeyond/article/details/6202256


免責聲明!

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



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