1. 程式人生 > 其它 >類和動態記憶體分配:靜態類成員、複製建構函式

類和動態記憶體分配:靜態類成員、複製建構函式

1.靜態類成員 P349

靜態類成員的特點:無論建立了多少物件,程式都只建立一個靜態類變數的副本。即類的所有物件共享同一個靜態成員。

不能在類宣告中初始化靜態成員變數;類的靜態成員必須在類內宣告,在類宣告之外使用單獨的語句來進行初始化,且在類外初始化時使用作用域運算子,但不使用關鍵字static

但是類的靜態const成員或列舉型別可以在類宣告中初始化。P303

如:

class A
{  
private:
    static int count ; // 類內宣告
};
...
int A::count = 0 ; // 類外初始化,不必再加static關鍵字

但類的靜態const成員或列舉型別可以在類宣告中初始化:

class Bakery{
private:
    enum {Months = 12};
    double costs[Months];
    ...
};
class Bakery{
private:
    static const int Months = 12;
    double costs[Months];
    ...
};

一篇解釋如下:

https://blog.csdn.net/jiayi_yao/article/details/50998765

 

2.複製建構函式(拷貝建構函式)與類的預設的過載賦值運算子 P353

參考:

https://blog.csdn.net/hui2702/article/details/106089097

https://blog.csdn.net/xiaozhidian/article/details/114377907

 

複製建構函式的原型:

Class_name(const Class_name &);

如:

Student(const Student &);//複製建構函式拷貝建構函式

一)何時呼叫:

複製建構函式和預設的過載賦值運算子的最大區別即是賦值運算子沒有新的物件生成,而拷貝建構函式會生成新的物件。

呼叫複製建構函式的場景:

1)物件通過另外一個物件進行初始化
2)物件作為函式的引數,以值傳遞的方式傳給函式。
3)當物件以值傳遞的方式從函式返回, 且接受返回值的物件沒有初始化

呼叫預設的過載的賦值運算子的場景:P356

1)物件以值傳遞方式從函式返回,且接受返回值的物件已經初始化過
2)物件直接賦值給另一個物件,且接受值的物件已經初始化過

二)預設複製建構函式和類的預設賦值過載運算子的功能:

預設的賦值建構函式逐個賦值非靜態成員(成員賦值也稱淺複製,只複製指標值 p353,p355),複製的是成員的值;如果類成員本身就是類物件,則將使用這個類的複製建構函式來複製成員物件。靜態成員不受影響,因為它們屬於整個類,而不是各個物件。

與預設的複製建構函式類似,預設的過載賦值運算子的實現也對成員進行逐個賦值;如果類成員本身就是類物件,則使用這個類定義的賦值運算子來複制該成員,但靜態資料成員不受影響。

三)建構函式中使用new關鍵字的類,應: P375

1)若解構函式通過對指標類成員使用delete來釋放記憶體,則每個建構函式都應該使用new來初始化指標,或將它設定為空指標nullptr(因為delete(無論是帶[]或者不帶[]的)都可以用於空指標。p364)

2)建構函式中要麼使用new[],要麼使用new,不能混用。如果建構函式使用new[],則解構函式使用delete[];如果建構函式使用new,則解構函式使用delete。

3)應定義一個複製建構函式,通過深度複製(即複製指標指向的資料,而不是隻複製指標值 p355)將一個物件初始化為另一個物件。p364

該複製建構函式應該與下面類似:

String::String(const String & st)
{
    num_strings++;
    len = st.len;
    str = new char [len + 1];
    std::strcpy(str, st.str);//深拷貝,而不是使用str = st.str,這樣會使str 和 st.str指向同一個字串,在呼叫它們的解構函式時會釋放同一個字串
}

4)應定義一個過載賦值運算子,通過深度複製將一個物件複製給另一個物件。 p364

該過載的賦值運算子應該與下面類似:

String & String::operator=(const String & st)
{
    if(this == &st)
        return this;
    delete [] str;//str是類的私有成員物件,用於儲存指向字串的指標
    len = st.len;
    str = new char [len + 1];
    std::strcpy(str, st.str);//深拷貝,而不是使用str = st.str,這樣會使str 和 st.str指向同一個字串,在呼叫它們的解構函式時會釋放同一個字串
    return * this;
}

 

三)中關於重新定義複製建構函式和過載賦值運算子的原因:p355

當類中包含了使用new初始化的指標成員時,若將一個類物件a賦值(使用複製建構函式或者過載的賦值運算子)給物件b時,會呼叫預設的複製建構函式或者過載的賦值運算子;此舉會導致物件a、b指向同一個存放在new申請的動態記憶體中的字串(資料),而當物件過期,呼叫a、b的解構函式時,兩個解構函式會將該動態空間中的字串(資料)釋放兩次,從而引發錯誤。

三)中關於delete的問題:p358
解構函式中若包含 delete [] str; 則建構函式中應使用 new []初始化指標,或使用空指標初始化指標;因為delete [] 和使用 new []初始化的指標和空指標 nullptr 初始化的指標都相容。因此若解構函式中使用 delete [],則建構函式中應該使用

String::String()
{
    len = 0;
    str = new char[1];
    str[0] = '\0';
}

而不是

String::String()
{
    len = 0;
    str = new char;
    str[0] = '\0';
}

 

3.靜態類成員函式 p360

靜態類成員函式的函式宣告必須包含關鍵字static,若函式定義是獨立的,則函式定義中不包含關鍵字static。

靜態類成員函式不能通過物件呼叫(且靜態類成員函式不能使用this指標,因為this指標指向呼叫類成員函式的物件);

如果靜態成員函式是在類的公有部分宣告的,則可以使用類名和作用域解析運算子來呼叫它;

//若String類有一個公有的靜態成員函式HowMany()
class String{
...
public:
    static int HowMany(){return num_strings;}
};
...
int main()
{
...
int count = String::HowMany();//呼叫類的公有靜態成員函式
...
}

 

4.指向物件的指標

1)使用new初始化物件時,若Class_name是類,value的型別為Type_name,則

Class_name * p = new Class_name(value);

將呼叫如下建構函式:

Class_name(Type_name &);

下列語句:

Class_name * ptr = new Class_name;

將呼叫預設建構函式。

 

5.再談定位new運算子 p371

將定位new運算子用於物件

 

6.