類的動態內存分配

1.靜態類成員:

private:
        char * str;
        int length;
        static int objectNum;    //靜態數據成員,爲所有的對象所共享
上例中的objectNum就是靜態類成員,它是所有對象所共享的,如下圖:


在上例中,創建了三個對象,內存會給每個對象都分配數據單元用來存儲Str和Len,但是不會爲每個對象都創建num_strings,在內存中只會創建一個num_strings,供所有對象共享。靜態類數據成員的初始化不是在類的聲明文件中進行的,而是在類的函數定義文件中進行的。如在StringBad.cpp文件中初始化,而不是在StringBad.h文件中初始化。

2.特殊成員函數:

●默認構造函數:如果沒有定義構造函數,就會自動生成。

●默認析構函數:如果沒有定義析構函數,就會自動生成。

●複製構造函數:當用一個對象初始化另外一個對象時,如果我們沒有專門定義複製構造函數,則會自動生成複製構造函數,詳解見下面。

●賦值運算符:當用一個對象初始化另外一個對象時,如果我們沒有專門定義賦值運算符,則會自動生成賦值運算符,詳解見下面。

●地址運算符

3.複製構造函數:

class StringBad
{
    private:
        char * str;
        int length;
        static int objectNum;    //靜態數據成員,爲所有的對象所共享
    public:
        StringBad();  //默認構造函數
        StringBad(const char * s); //構造函數
        ~StringBad();   //析構函數

        friend ostream & operator<<(ostream & os, const StringBad & sb);
};
int StringBad::objectNum = 0;    //靜態數據成員

StringBad::StringBad()           //默認構造函數
{
    length = 4;
    str = new char[4];
    strcpy(str, "c++");
    objectNum++;
    cout << "Object" << objectNum << ": " << str << " is created." << endl;
}

StringBad::StringBad(const char * s)   //構造函數
{
    length = strlen(s);
    str = new char[length + 1];
    strcpy(str, s);
    objectNum++;
    cout << "Object" << objectNum << ": " << str << " is created." << endl;
}

StringBad::~StringBad()   //析構函數
{
    objectNum--;
    cout << str << " is deleted." << endl;
    delete [] str;
    cout << objectNum << " objects left." << endl;
}

ostream & operator<<(ostream & os, const StringBad & sb)   //重載<<運算符
{
    os << sb.str << endl;
    return os;
}
int main()
{
    StringBad sb1("abcde"), sb2 = sb1;
    cout << sb1;
    cout << sb2;
    cout << endl;
    return 0;
}
在上例中,定義了一個類StringBad,在類中定義了一些成員函數,然後使用sb2 = sb1,用sb1來初始化sb2,當用一個對象初始化另外一個對象時,就會調用複製構造函數,此時就調用了複製構造函數。

默認的複製構造函數逐個複製非靜態成員(成員複製也成爲淺拷貝),複製的是成員的值。如在上例中,其實是將sb1.length賦給sb2.length,sb1.str賦給sb2.str,而對象的str成員存儲的是字符串的地址,而不是字符串本身,所以這種淺拷貝是將sb1.str和sb2.str指向同一個地方,所以這種淺拷貝容易出問題,當將sb1.str指向的內容刪除時,其實也將sb2.str指向的內容也刪除了,再刪除sb2.str所指向的內容時就會出錯。

     所以,此時我們就需要自己定義一個複製構造函數來解決這種問題,將sb1.str和sb2.str指向不同的地方,但這兩個地方存儲的字符串內容是一樣的(也就是深拷貝)。

    淺拷貝和深拷貝的區別:淺拷貝是僅僅只拷貝字符串的地址,使兩個指針指向同一個地方。深拷貝是拷貝內容到另外一個地方,使另一個指針指向拷貝內容的地方,通過深拷貝兩個指針指向的地址就不同,但字符串內容仍是相同的。

淺拷貝示意圖:


深拷貝示意圖:


此例中我們自己定義一個複製構造函數,使得通過對象初始化,也能使兩個對象的str指針指向不同的地址:

StringBad::StringBad(const StringBad & st)   //複製構造函數,用於將一個對象用於初始化另外一個對象,如object1 = object2
{
    objectNum++;
    length = st.length;
    str = new char[length + 1];
    strcpy(str, st.str);
    cout << "Object" << objectNum << ": " << str << " is created." << endl;
}
警告:如果類中包含了使用new初始化的指針成員,則我們自己應定義一個複製構造函數,以複製指向的數據,而不是複製指針,這被成爲深拷貝。淺拷貝僅僅只是複製指針的值。

4.賦值運算符:

object2 = object1;
當我們使用以上語句用object1對object2進行初始化時,實際是分兩步來處理:先使用複製構造函數創建一個臨時對象,然後通過複製運算符將臨時對象複製到新對象中。在第一步中,我們使用我們自己定義的複製構造函數來進行深拷貝;在第二步中,如果我們不自己定義賦值運算符時,我們進行的會是跟默認的複製構造函數一樣的淺拷貝,所以我們在這裏需要自己定義賦值運算符進行深拷貝。

這裏定義的賦值運算符如下:

StringBad & StringBad::operator=(const StringBad & sb)
{
    if(this == &sb)
        return *this;
    delete [] str;
    length = sb.length;
    str = new char[length + 1];
    strcpy(str, sb.str);
    return *this;
}
5.整個項目實例代碼:

文件結構:


din.h代碼:

#ifndef DIN_H_INCLUDED
#define DIN_H_INCLUDED

#include <iostream>
using namespace std;

class StringBad
{
    private:
        char * str;
        int length;
        static int objectNum;    //靜態數據成員,爲所有的對象所共享
    public:
        StringBad();  //默認構造函數
        StringBad(const char * s); //構造函數
        ~StringBad();   //析構函數
        StringBad(const StringBad & st);  //複製構造函數,用於將一個對象用令一個對象來初始化,如object1 = object2
        StringBad & operator=(const StringBad & sb);

        friend ostream & operator<<(ostream & os, const StringBad & sb);
};


#endif // DIN_H_INCLUDED
din.cpp代碼:

#include <iostream>
#include <cstring>
#include "din.h"
using namespace std;

int StringBad::objectNum = 0;    //靜態數據成員

StringBad::StringBad()           //默認構造函數
{
    length = 4;
    str = new char[4];
    strcpy(str, "c++");
    objectNum++;
    cout << "Object" << objectNum << ": " << str << " is created." << endl;
}

StringBad::StringBad(const StringBad & st)   //複製構造函數,用於將一個對象用於初始化另外一個對象,如object1 = object2
{
    objectNum++;
    length = st.length;
    str = new char[length + 1];
    strcpy(str, st.str);
    cout << "Object" << objectNum << ": " << str << " is created." << endl;
}

StringBad::StringBad(const char * s)   //構造函數
{
    length = strlen(s);
    str = new char[length + 1];
    strcpy(str, s);
    objectNum++;
    cout << "Object" << objectNum << ": " << str << " is created." << endl;
}

StringBad & StringBad::operator=(const StringBad & sb)
{
    if(this == &sb)
        return *this;
    delete [] str;
    length = sb.length;
    str = new char[length + 1];
    strcpy(str, sb.str);
    return *this;
}

StringBad::~StringBad()   //析構函數
{
    objectNum--;
    cout << str << " is deleted." << endl;
    delete [] str;
    cout << objectNum << " objects left." << endl;
}

ostream & operator<<(ostream & os, const StringBad & sb)   //重載<<運算符
{
    os << sb.str << endl;
    return os;
}
main.cpp代碼:

#include <iostream>
#include "din.h"
using namespace std;

int main()
{
    StringBad sb1, sb2("abcde"), sb4;
    StringBad sb3 = sb2;
    sb4 = sb2;
    cout << sb1;
    cout << sb2;
    cout << sb3;
    cout << sb4;
    cout << endl;
    return 0;
}

運行結果:




發佈了71 篇原創文章 · 獲贊 16 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章