程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++回顧之深淺拷貝、禁止拷貝、空類的默認成員

C++回顧之深淺拷貝、禁止拷貝、空類的默認成員

編輯:C++入門知識

個人見解,先談談淺拷貝與深拷貝之間的區別,區分它們的最大區別就是在調用完拷貝構造函數後,兩個對象之間是否還存在一定的聯系,如果兩個對象能夠完全獨立,則說明是深拷貝,否則是淺拷貝。以各種教材的String類的深拷貝實現為例,下面進行說明。

為了實現深拷貝操作,我們需要自己來構建拷貝構造函數,因為我們一旦構建了自己的拷貝構造函數,系統就不再提供默認的拷貝構造函數了。

下面是String.h

#ifndef  _STRING_H_
#define _STRING_H_
class String
{
public:
    String(char *str=""); //帶一個參數的構造函數,可傳入參數如“ABC”
    ~String();

    void Display();

    String( const String &other);    //拷貝構造函數
    String & operator=(const String &other);  //等號賦值運算符重載

private:
    char *str_;          //String需要維護str_數據成員
    char *AllocAndCpy(char *str);
};

#endif

下面是String類的具體實現,String.cpp

#include "String.h"
#include 
#include 
using namespace std;

char *String::AllocAndCpy(char *str) //實施深拷貝的函數
{
    int len = strlen(str) + 1;
    char *tmp = new char[len];
    memset( tmp, 0, len);
    strncpy(tmp, str, len );
    return tmp;
}

String::String( const String & other) /* : str_(other.str_) 這種方式還是淺拷貝*/
{
        str_ = AllocAndCpy(other.str_); //函數內的實現才是真正的深拷貝,它的處理使得str_與原對象脫離了聯系
}

String & String::operator=(const String &other)
{
    if( this == &other)
    {
        return *this;
    }

    //銷毀原有空間
    delete [] str_;
    str_ = AllocAndCpy(other.str_);

    return *this;
}

String::String( char *str)
{
    str_ = AllocAndCpy(str);
}

String::~String()
{
    delete [] str_;
}

void String::Display()
{
     cout << str_ << endl;
}
上面代碼中的AllocAndCpy函數是非常重要的函數,它重新分配了一塊內存空間,然後將原內存的數據復制至新內存空間中,這樣兩個類對象之間就有各自獨立的內存空間,對象之間就沒有聯系,這就是深拷貝。如果不這麼做,直接通過str_ = other.str_實施拷貝,只是簡單的復制指針的地址,這樣兩個類對象實際指向的是同一塊內存,當兩上對象的生命周期結束後,均需要釋放內存空間,這樣就造成了同一塊內存單元被釋放了兩次。因為它們僅僅拷貝了“形”,而沒有拷貝“本質”的數據,所以拷貝構造函數需要我們自己來重新編寫。由於等號賦值重載函數需要賦值操作,實際上也是復制,所以也需要自己編寫operator=函數。

下面是測試代碼,代碼中也有詳細的注釋:

int main(void)
{
	String s1("AAA");
	s1.Display();
	
	String s2 = s1;//調用默認的拷貝構造函數,系統提供的默認拷貝構造函數實施的是淺拷貝s2.str_ = s1.str_;相同於s1,s2兩個對象的str_指針指向相同的內存,當兩個對象生存期結束時,都要調用析構函數,導致同一塊內存被,釋放了兩次,故而產生錯誤.解決方法實施深拷貝,自己提供拷貝構造函數


    //等號運算符的重載
    String s3;
    s3.Display();
    s3 = s2; // = 號運算符,它調用的是系統默認的等號運算符,實施的也是淺拷貝,s3.str_ = s2.str_;仍然會出現同一塊內存被銷毀兩次的情況,所以要自己提供等號運算符實施深拷貝。等價於s3.operator=(s2);

    return 0;
}

下面說明關於禁止拷貝的情形。在某些場合,比如設計模式中有個Singleton單例模式,即一個類只能有一個實例,就是說這個類的對象是要獨一無二的,它不允許被拷貝,也不允許賦值,也就是說我們要提供禁止拷貝的功能,以下就是將一個類實施為禁止拷貝的步驟,其實很簡單:

(1)將拷貝構造函數與operator=(等號運算符重載函數)均聲明為private私有訪問權限

(2)在CPP文件中不提供他們的實現

這樣,當在主程序進行拷貝復制或賦值操作時,編譯將出錯,因為它們沒有拷貝構造函數或賦值操作的實現。只有聲明,而且還是私有的。


下面總結一下,空類默認的成員函數有6個:

class Empty{}; //這是一個空類。

Empty(); //默認構造函數
Empty(const Empty &);//默認拷貝構造
~Empty(); //默認析構函數
Empty & operator=(const Empty &)//默認賦值運算符
Empty *operator&();//取地址運算符
const Empty *operator &() const; //取地址運算符const
下面舉例說明operator&()與operator &() const的用法:

#include 
using namespace std;

class Empty
{
public:
    Empty * operator&()
    {
        cout << "AAA"<< endl;
        return *this;
    }

    const Empty *operator&() const
    {
        cout << "BBB"<空類的大小是1個字節,編譯器將為它生成一個字生的空間
    cout << sizoef(Empty) << endl;

    return 0;
}






















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