天天看點

C++: 06---構造函數析構函數

拷貝構造函數:

         用一個已經存在的對象來生成一個相同類型的新對象。(淺拷貝)

預設的拷貝構造函數:

         如果自定義了拷貝構造函數,編譯器就不在生成預設的拷貝構造函數。 

         如果沒有自定義拷貝構造函數,但在代碼中用到了拷貝構造函數,編譯器會生成預設的拷貝構造函數。

深拷貝&淺拷貝:

         系統預設的拷貝構造函數是淺拷貝,類中含有指針類型的變量,須自定義拷貝構造函數用深拷貝來實作。

         淺拷貝隻是對指針的拷貝,拷貝後兩個指針指向同一個記憶體空間,所指向的空間内容并沒有複制,而是由兩個對象共用。深拷貝不但對指針進行拷貝,而且對指針指向的内容進行拷貝,經深拷貝後的指針是指向兩個不同位址的指針。

如圖:   

思考:

      當對象中存在指針成員時,為什麼需要自己實作拷貝構造函數?如果不,會出現怎樣的問題?

看代碼:

#include<iostream>
 
class CGoods
 
{
 
public:
 
  CGoods(const char* name, double price, int amount)//帶有三個參數的構造函數
 
  {
 
    std::cout << this << " :CGoods::CGoods(char*,float,int)" << std::endl;
 
    mname = new char[strlen(name) + 1]();
 
    strcpy(mname, name);
 
    mprice = price;
 
    mamount = amount;
 
  }
 
  CGoods()//不帶參數的構造函數
 
  {
 
    std::cout << this << " :CGoods::CGoods()" << std::endl;
 
    mname = new char[1]();
 
  }
 
~CGoods()
 
  {
 
    std::cout << this << " :CGoods::~CGoods()" << std::endl;
 
    delete[] mname;
 
  }
 
private:
 
  char*  mname;
 
  double mprice;
 
  int mamount;
 
};
 
int main()
 
{
 
  CGoods good1("car1", 10.1, 10);
 
  CGoods good2 = good1;
 
  return 0;
 
}      

程式運作結果:調一次構造函數,調兩次析構函數。

分析:兩個對象的指針成員所指記憶體相同,這會導緻什麼問題呢?

           mname指針被配置設定一次記憶體,但是程式結束時該記憶體卻被釋放了兩次,會造成記憶體洩漏問題,這是一個不容忽視的問題。我們不得不自己寫一個拷貝構造函數。

CGoods(const CGoods &rhs)
 
{
 
  std::cout << this << " :CGoods::CGoods(const CGoods &rhs)" << std::endl;
 
  mname = new char[strlen(rhs.mname) + 1]();
 
  strcpy(mname, rhs.mname);
 
  mprice = rhs.mprice;
 
  mamount = rhs.mamount;
 
}      

程式運作結果

注意:拷貝構造函數傳參必須傳引用。若正常傳,則會不斷地遞歸去生成新形參對象,最後導緻棧溢出。也不能用*,若寫成 CGoods(const CGoods* rhs),就會變成一個構造函數,CGoods*傳的是已存在對象的位址。

指派運算符的重載函數:

        用一個已存在的對象指派給相同類型的已存在對象。(淺拷貝)

預設指派運算符的重載函數:

        指派運算符重載函數用于類對象的指派操作,當我們未實作該函數時,編譯器會自動為我們實作該函數。同拷貝構造函數一樣,系統預設的指派運算符重載函數是淺拷貝,類中含有指針類型的變量,須自定義指派運算符重載函數用深拷貝來實作。

CGoods& operator=(const CGoods& rhs)
 
{
 
  std::cout << this << " CGoods::operator=(const CGoods&)" << std::endl;
 
  if (this != &rhs)//判斷是否給自己指派
 
  {
 
    delete[] mname;//防止記憶體洩漏
 
    mname = new char[strlen(rhs.mname) + 1]();
 
    strcpy(mname, rhs.mname);
 
    mprice = rhs.mprice;
 
    mamount = rhs.mamount;
 
  }
 
  return *this;
 
}
 
int main()
 
{
 
  CGoods good1("car1", 10.1, 10);
 
  CGoods good2;
 
  good2 = good1;
 
  return 0;
 
}      

程式運作結果:

思考:

為什麼要避免自指派呢?1)自己給自己指派完全是毫無意義,為了效率。

2)如果類的資料成員中含有指針,自指派有時會導緻災難性的後果。對于指針間的指派,先要将p所指向的空間delete掉,然後再為p重新配置設定空間,将被拷貝指針所指的内容拷貝到p所指的空間。如果是自指派,那麼p和被拷貝指針是同一指針,在指派操作前對p的delete操作,将導緻p所指的資料同時被銷毀。

拷貝構造函數與指派函數的差別?

    在看到“=”操作符為對象指派的時候,

            如果是在對象定義時(Test B = (Test)c),此時調用拷貝構造函數;

            如果不是在對象定義指派時(B = c),此時調用指派函數。

    注:構造函數、拷貝構造函數,帶有構造兩個字,顧名思義,就是在對象聲明或定義時才會使用。

拷貝構造函數與指派函數定義的差別?

    記憶體空間角度:

      1)拷貝構造函數的使用,是在建立對象時;當時對象沒有占有記憶體,故不需要釋放記憶體,不重建立立記憶體空間。

      2)指派函數的使用,是在對象建立後;當時對象已經占有記憶體,故需要釋放先前記憶體,然後重新擷取記憶體空間。

下面我們來看一個題目:

class String
{
public:
    String(const char *str = NULL); // 普通構造函數    
    String(const String &other);    // 拷貝構造函數
    ~String(void);                    // 析構函數    
    String & operator = (const String &other);    // 指派函數
private:
char *m_data;                    // 用于儲存字元串
};      

各個解析分别完成下上面String類聲明的函數:

1、構造函數    

/* 
   1、構造函數在構造對象時使用;
   2、傳入參數的判斷;
   3、對象的初始化問題。
*/
String::String(const char *str)
{
    if ( NULL == str)
    {
        m_data = new char[1]
        *m_data = '\0';
    }
    else
    {
        int len = strlen(str);
        m_data = new char[len + 1];
        strcpy(m_data,str);
    }
}      

2、拷貝構造函數                                   

/*
   1、拷貝構造函數必須在構造對象時使用,即定義對象時;
   2、對象初始化問題。
*/
String::String(const String &other)
{
    int len = strlen(other.m_data);
    m_data = new char[len+1];
    strcpy(m_data,other.m_data);
}      

3、指派函數                                       

/*
   1、指派函數使用時,對象肯定已經建立;
   2、指派前,判斷是否是自我指派;
   3、指派前,記憶體空間的準備:
       由于指派前,對象已占有一定大小記憶體,但是指派對象所占記憶體大小與
       對象已占的記憶體大小不一定一緻;
       先釋放對象已占的記憶體,然後配置設定心記憶體。
   4、正常指派
*/
String & String::operator = (const String &other)
{
    if (&other == this)
    {
        return *this;
    }
    
    delete [] m_data;
    int len = strlen(other.m_data);
    m_data = new char[len+1];
    strcpy(m_data,other.m_data);
    
    return *this;
}      

4、析構函數                                        

/*
   資源的釋放
*/
String::~String(void)
{
    delete []m_data;
}