C++之深淺拷貝

在C++中當
- 1.用已經存在的對象去構造出另一個新的對象。
- 2.當函數的形參是類的對象時。
- 3.當函數的返回值時類的對象時會用到拷貝構造函數。

會用到拷貝函數。

淺拷貝

class Test
{
public:
    //構造函數
    Test(int data)
        :_data(data)
    {}
    //拷貝構造函數
    Test(const Test& c) 
    {
        _a = c._a;
    }

private:
    int _data;
}; 
int main()
{ 
    Test b(10);
    Test c(b);
    return 0; 
}

淺拷貝指的就是當在進行對象的複製的時候,知識對類對象的數據成員的拷貝,其默認的拷貝構造函數也是淺拷貝。

淺拷貝的缺陷:

大多數情況下,淺拷貝時沒有問題的。但對於指針成員不可行。多個對象共用同一塊空間,同一內存地址,但是在調用析構函數釋放空間的時候,多次調用析構函數,這塊空間被釋放了多次,此時程序就會崩潰。

引用計數的拷貝

1.概念:爲了解決多個指針同時指向同一塊空間可能導致的內存空間的多次釋放這個問題,我們引入了帶引用計數的拷貝。(是用來解決淺拷貝的問題的,這也是一種淺拷貝)

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;
class String
{
public:
    //構造函數
    String(const char* ptr = "")
    {
        if (ptr == NULL)
        {
            _ptr = new char[1];
            _pcount = 1;
            *_ptr = '\0';
        }
        else
        {
            _pcount = 1;
            _ptr = new char[strlen(ptr) + 1];
            strcpy(_ptr, ptr);
        }
    }
    //拷貝構造函數
    String(String& s)
        :_ptr(s._ptr)
        , _pcount(s._pcount)
    {
        _pcount++;
    }
    //賦值運算符重載
    String& operator=(const String& s)
    {
        if (this != &s)
        {
            if (--_pcount == 0)
            {
                delete[] _ptr;
                //delete _pcount;
            }
            else
            {
                _ptr = s._ptr;
                _pcount = s._pcount;
                (_pcount)++;
            }
        }
        return *this;
    }
    //析構函數
    ~String()
    {
        if ((0 == --_pcount) && _ptr != NULL)
        {
            delete[]_ptr;
            //delete _pcount;
            _ptr = NULL;
        }

    }
    //重載[]
    char& operator[](size_t size)
    {
        if (--_pcount >1)
        {
            char* ptemp = new char[strlen(_ptr) + 1];
            int pcount = 1;
            strcpy(ptemp, _ptr);
            _pcount--;
            _ptr = ptemp;
            _pcount = pcount;
        }
        return _ptr[size];
    }
private:
    char* _ptr;
    int _pcount;
};

void FunTest()
{
    String s1("hello");
    String s2(s1);
    String s3(s2);
    s3 = s2;
}
int main()
{
    FunTest();
    system("pause");
    return 0;
}

如何來實現這個引用計數呢?

我們爲每個內存的字符數組添加一個引用計數pcount,,表示有多少個對象使用這塊內存,每多個對象使用,就讓pcount值加1,當對象被析構的時候,讓pcount值減1,當pcount值爲0的時候,將這塊內存釋放掉。當然pcount也要實現內存共享,所以它也是一個堆中的數據,每個對象都有一個指向它的指針。

這裏寫圖片描述
使用整形來作爲引用計數,結果如圖所示,在三個對象指向這個空間的時候,引用計數分別爲1,2,3,說明引用計數不同步,所以並不能用整形來作爲引用計數。

class String  
{  
public:  
    //構造函數  
    String(const char* ptr = "")  
    {  
        if(ptr == NULL)  
        {  
            _ptr = new char[1];  
            _pcount = 1;  
            *_ptr = '\0';  
        }  
        else  
        {  
            _pcount = 1;  
            _ptr = new char[strlen(ptr)+1];  
            strcpy(_ptr,ptr);  
        }  
    }  
    //拷貝構造函數  
    String(String& s)  
        :_ptr(s._ptr)  
    {  
        _pcount++;  //因爲是靜態的,所以直接進行計數的增值就可以了  
    }  
    //賦值運算符重載  
    String& operator=(const String& s)  
    {  
        if(this != &s)  
        {  
            if(--_pcount == 0)  
            {  
                delete[] _ptr;  
                //delete _pcount;  
            }  
            else  
            {  
                _ptr = s._ptr;  
                _pcount = s._pcount;  
                (_pcount)++;  
            }  
        }  
        return *this;  
    }  
    //析構函數  
    ~String()  
    {  
        if((0 == --_pcount) && _ptr!= NULL)  
        {  
            delete[]_ptr;  
            //delete _pcount;  
            _ptr = NULL;  
        }  

    }  
    //重載[]  
    char& operator[](size_t size)  
    {  
        if(--_pcount >1)  
        {  
            char* ptemp = new char[strlen(_ptr)+1];  
            int pcount = 1;  
            strcpy(ptemp,_ptr);  
            _pcount--;  
            _ptr = ptemp;  
            _pcount = pcount;   
        }  
        return _ptr[size];  
    }  
private:  
    char*_ptr;  
    static int _pcount;  
};  
int String::_pcount = 0;  
void FunTest()  
{  
    String s1("hello");  
    String s2(s1);  
    String s3(s2);  
    s3 = s2;  
    String s4("world");  
    String s5(s4);  
}  
int main()  
{  
    FunTest();  
    return 0;  
}

如果一個對象第一次開闢空間存放字符串再開闢一塊新的空間存放新的額引用計數,當它拷貝構造其它對象時讓其它對象的引用計數都指向存放引用計數的同一塊空間,pcount設置成int*就可以啦,但是這種方式有缺陷。

缺陷一:每次new兩塊空間,創建多個對象的時候效率比較低

缺陷二:它多次分配小塊空間,容易造成內存碎片化,導致分配不出來大塊內存

深拷貝

深拷貝也就是地址拷貝,在堆中申請新的空間來存取數據,這樣數據之間相互獨立。

String(const String& s)
{
      _ptr = new char[strlen(s._ptr)+1];
      strcpy(_ptr,s._ptr);
}

寫時拷貝

在多個指針同時指向一個空間的時候,我們會遇到一個問題,在改變一個指針所指向內容的時候,其他指針指向的內容也是隨之改變,所以我們引入了寫時拷貝。

給要改變值的那個對象重新new出一塊內存,然後先把之前的引用的字符數據複製到新的字符數組中,這就是寫時拷貝。注意,同時還要把之前指向的內存的引用計數減1(因爲它指向了新的堆中的字符數組),並在堆中重新new一個塊內存,用於保存新的引用計數,同時把新的字符數組的引用計數置爲1。因爲此時只有一個對象(就是改變值的對象)在使用這個內存。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string.h>

using namespace std;

class String
{
    friend ostream& operator<<(ostream &os, String &str);

public:
    String(const char *str = NULL); //通用構造函數
    String(const String &str);      //拷貝構造函數
    ~String();                     //析構函數

    String& operator=(const String &str);       //重載=
    char& operator[](int n) const;              //重載[]

    friend ostream& operator<<(ostream &os, String &str);//輸出

private:
    char *data;     //字符串
    size_t length;  //長度
};

String::String(const char *str)
{
    if (!str)
    {
        length = 0;
        data = new char[1];
        data = '\0';
    }
    else
    {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
    }
}
String::String(const String &str)
{
    length = str.length;
    data = new char[length + 1];
    strcpy(data, str.data);
}
String::~String()
{
    delete[] data;
    length = 0;
}
String& String::operator=(const String &str)
{
    if (this == &str)   
        return *this;
    delete[]data;
    length = str.length;
    data = new char[length + 1];
    strcpy(data, str.data);
    return *this;
}

inline char& String::operator[](int n) const//重載[]
{
    if (n >= length)
        return data[length - 1]; //限定範圍
    else
        return data[n];
}
ostream& operator<<(ostream &os, String &str)
{
    os << str.data;
    return os;
}

void test()
{
    String s1("hello");
    String s2(s1);

    String s3;
    s3 = s2;

    s1[0] = 'a';
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;

}

int main()
{
    test();
    system("pause");
    return 0;
}

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章