C++Primer學習筆記(類的動態內存)

一、動態內存和類

首先要強調的一點是,C++中的類是描述如何使用內存的,但並不參與分配內存的工作,所以,在類的聲明中,不能出現給類成員賦值的情況,即使是類中有靜態類型成員的時候,也不能夠這樣做,所有的對類成員的初始化以及賦值工作都必須拿到類的外部來實現,或者寫入到構造函數裏面。

一般來說,靜態成員的初始化都是要在類的外部進行的,但是如果靜態數據成員是const或者是枚舉類型的話,那麼就 可以在類的聲明的內部進行初始化。


其實,當我們設計一個類的時候,類的實例化將會創建出對象,對象本身就是有內存的,但是刪除對象並不能刪除對象在實例化時所分配的內存空間。假設一個類中有一個用new申請的一個數組,那麼每次創建一個對象,也都會初始化一個屬於這個對象的數組,但是這個數組並不跟對象存儲在一起,他只是把這個數組的地址存放在這個對象的內存裏,方便尋找,所以在執行析構函數刪除一個對象的時候,必須利用delete刪除這個屬於對象的數組,不然,就會造成內存泄露。


一般來說,析構函數在定義對象的代碼塊執行完畢後會自動調用。


當我們在進行對象直接賦值的時候,我們的編譯器將會爲我們創造一個默認的構造函數,這個構造函數就是

類名(const 類名 &)

這個函數被稱爲複製構造函數。


二、特殊的成員函數-------複製構造函數。

在C++中,自動提供了下列這些成員函數:

1、當沒有創建構造函數和析構函數的時候,編譯器會默認爲我們提供不帶任何參數的構造函數以及析構 函數。

2、複製構造函數

3、賦值運算符

4、取地址符


複製構造函數用於將一個對象複製到另外一個對象中。

當我們新建一個對象,想把這個新建的對象初始化爲同類已經存在的對象時,這時候,複製構造函數就會調用。另外,在函數傳參的時候,如果是兩個類對象進行值的傳遞的時候,還有函數的返回值的類型是對象的時候,都會調用複製構造函數。

複製函數在產生對象副本然後賦值給新對象的時候,只會複製非靜態成員,因爲靜態成員是被所有的對象公有的,不屬於哪一個對象。複製函數在複製對象的時候,基本上屬於對象中成員的複製,而且是單純的值的複製,這種複製方式也成爲淺拷貝。


淺拷貝在程序當中容易出現的問題有這樣一種情況,因爲它是單純的值的傳遞:

class A()

{

char *p;

A(const char *ptr)

{

p = ptr;

}

}


上面的這個類有一個成員還有一個構造函數,作用就是在創建對象的時候,給一個字符串的參數,然後把他的地址賦值給成員變量p;

如果我現在 創建一個對象a: A a = A("haha");

之後我執行這樣的一句代碼 A b = a;

你可能感覺這樣的賦值是可行的,我們前面說到了,每一個對象都有一套屬於他自己的成員變量,也就是說,a和b兩個對象都有自己的一個成員變量p然後指向自己所屬的字符串。但是事實上,上面的A b = a的這個語句並沒有實現這種功能,這個語句將會隱式的調用複製構造函數,也就是前面說的淺拷貝。淺拷貝的原則就是隻拷貝值,不管其他的東西。a對象裏面的P成員變量明明存的是他的字符串的地址,在淺拷貝的時候,b對象裏面的p成員,存的不是自己所屬字符串的地址,存的是a所屬字符串的地址。你再輸出的時候可能感覺一切正常,但是假如你一旦覺得a對象沒用了,刪掉a對象,反正b裏面也是存的a的東西沒有關係,那麼當你再輸出b對象中的字符串的時候,你就應該知道,b對象中p變量存的字符串地址標識的那一段內存空間的內容早已經被刪掉了。這就是淺拷貝的問題。

所以說,有了以上的問題之後,我們就要想出一個叫做深拷貝的辦法,真正的做一次對象的複製操作,用來初始化另外一個新對象,使得新的對象也能夠擁有獨立的數據成員。


深拷貝和淺拷貝都有他們合適的用處,其中一個就是,當類中的成員有指針類型的時候,那麼應該添加一個顯式的賦值構造函數用於實現深拷貝,爲了避免因爲默認的淺拷貝而造成的兩個對象的數據出現重疊的情況。


關於複製構造函數還有 一點要明確的就是:對象初始化用的是複製構造函數,但是 對象的賦值用的 是重載的賦值運算符。



三、賦值運算符

一般來講,C++的編譯器會根據賦值的對象和被複制 的對象選擇相應功能的賦值運算符。默認的賦值運算符和默認的複製構造函數一樣,都是屬於淺拷貝。所以出現的問題也就是一樣的。

賦值運算符要注意的是:

首先要避免不能自己給自己賦值,其次,要刪除目標對象裏面原有的數據,也就是相當於一個清空操作。另外賦值運算符的重載只能作爲成員函數來進行定義。


四、new和delete

delete一般來說經常出現於析構函數當中,delete只刪除空指針,或者new函數返回的指針,如果將delete用於除了這兩種指針類型之外,都會造成不確定的後果。


在構造函數中使用new,那麼在析構函數中一定要出現對應的delete。new和delete必須相互兼容,new[]對應於delete[]。如果有多個構造函數,那麼他們在使用new的時候,必須要保持一致,因爲任何一個構造函數都必須與他的析構函數兼容,然而析構函數只有一個。delete只適用於new返回的指針以及空指針。


在C++中,空指針的表示有三種 形式:NULL , 0, nullptr。最後一種是c++11的最新添加進來的。


五、構造函數中使用new的注意事項


在類的成員中有數組等元素需要在構造函數裏面利用new申請內存空間的時候,一定要記得定義一個參數爲該類對象類型引用的一個構造函數,作爲這個類的複製構造函數來進行深拷貝操作。否則,默認的複製構造函數很有可能在內存的使用上出現問題。並且要保證類中需要的靜態數據成員不受影響。要定義類似操作的還有賦值運算符,在類中有必要進行重載,功能類似於複製構造函數。複製構造函數一般在新對象被初始化爲另一個對象的時候比較常用。而賦值運算符則在出了初始化之外的賦值操作中調用。


六、關於函數返回值爲對象的問題

類中的一些成員函數通常來 說,返回值可能會爲對象,常量對象,對象引用,常量對象引用。

在返回值爲對象的時候,將會調用複製構造函數。

但是返回值爲對象的引用則不會調用。


七、類與New

如果類中的構造函數使用new來對類成員進行初始化,那麼在析構函數中一定要記得用delete刪除。delete只對new返回的指針以及空指針有刪除的作用,其餘的結果不確定。應該顯示的定義一個複製構造函數來防止用一箇舊的對象初始化另外一個新的對象而造成的內存泄露。

還要重載一個賦值運算符,防止對象之間的賦值也因爲內存管理的問題出現錯誤。



八、構造函數的執行

從概念上說,在創建對象的時候要調用相應的構造函數,對象在執行構造函數代碼之前就會被創建。也就是說,在調用之後,執行該函數代碼之前,對象就已經被創建了,這個對象所需要的成員變量的內存就會被分配。其實構造函數內部代碼的主要作用就是爲對象所屬的類成員進行賦值操作。 但是,如果類的成員裏面有常量的話,常量是不能夠被賦值的,只能夠初始化,所以在構造函數裏面給常量成員賦值是錯誤的。因爲在執行這些代碼的時候,常量就已經被創建好了。所以說,我們就需要在執行構造函數代碼之前給常量初始化,也就是在創建對象的同時。

C++提供了一個特殊的工具,叫做成員初始化列表來完成這個工作。成員初始化列表是在構造函數後面加上一個分號,然後寫上要初始化的成員,如果有多個成員可以依次用逗號隔開。在初始化成員列表中給成員初始化的值可以是常量也可以是構造函數裏面的參數,並且初始化列表可以用於任意成員的賦值。出了常量成員在創建對象的時候必須使用這種初始化成員列表來進行初始化之外還有一種就是引用變量的成員,因爲引用必須在初始化的時候就爲他指定一個明確的數據。但是要明確,這種 初始化成員列表的形式只能用於構造函數。另外,成員被初始化的順序和她們在類中被聲明的順序相同,也就是說,初始化列表 中的順序是無關緊要的。




在使用定位new運算符創建對象的時候,要刪除對象的時候,需要顯式的調用對象的析構函數

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