C++如何設(shè)計一個類2(含指針的類)
本文預(yù)覽:
- BigThree:拷貝構(gòu)造补憾、拷貝復(fù)制陵吸、析構(gòu)
- Stack(棧) Heap(堆)及生命周期
- new delete 操作符內(nèi)部實現(xiàn)
- 物理內(nèi)存模型 in VC
BigThree:拷貝構(gòu)造术陶、拷貝復(fù)制莱褒、析構(gòu)
帶有指針的類必須要有拷貝構(gòu)造右遭、拷貝復(fù)制
C++ STL中String的實現(xiàn)是典型的帶指針類茵宪,成員只有一根char類型的指針蟹倾,為什么不用char類型的數(shù)組,這個考量在于猖闪,我們不能確定用戶創(chuàng)建的字符串到底有多少個字符鲜棠,數(shù)組在分配內(nèi)存空間的時候必須是確定的,多了造成浪費培慌,少了空間不足豁陆,所以數(shù)組不適合這樣的需求。使用指針的好處在于吵护,我們是動態(tài)分配的內(nèi)存空間盒音,大小可控。
- 接口設(shè)計
class String{
public:
String(const char* cstr=0);
String(const String& str); //拷貝構(gòu)造
String& operator=(const String& str); //拷貝復(fù)制
~String(); //析構(gòu)
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
- 構(gòu)造和析構(gòu)函數(shù)的實現(xiàn)
我們在每一個函數(shù)定義上面加了inline馅而,這樣是否合適祥诽,有人說復(fù)雜的函數(shù)加了inline多此一舉,是的瓮恭,因為是否是inline這是由編譯器決定的雄坪,我們可以把所有的成員函數(shù)都加上inline,這樣寫是沒有問題的屯蹦,至于是不是维哈,讓編譯器去決定
#include <cstring> //使用C的函數(shù)
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data; //注意,析構(gòu)函數(shù)delete動態(tài)分配的數(shù)組
}
- 拷貝構(gòu)造函數(shù)的實現(xiàn)
根據(jù)傳入的字符串長度開辟相同大小的內(nèi)存空間登澜,然后執(zhí)行拷貝阔挠,由于包含‘\0’結(jié)束符,所以長度需要加1
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ]; //m_data雖然是private的脑蠕,同類之間的對象互為friend
strcpy(m_data, str.m_data);
}
- 拷貝復(fù)制
- 從右值復(fù)到左值购撼,清空左值之前的數(shù)據(jù),然后開辟新的內(nèi)存空間谴仙,執(zhí)行拷貝
- 自檢為什么是必須的份招,當(dāng)是同一個對象的時候,而沒有自檢狞甚,那么會發(fā)生什么锁摔?
兩個指針指向同一塊內(nèi)存空間,這一塊內(nèi)存空間先delete掉了哼审,再取的時候另一個就變成了野指針
inline
String& String::operator=(const String& str)
{
if (this == &str) //自檢不是多余的谐腰,一定要有
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
Stack(棧) Heap(堆)及生命周期
- Stack
Stack 是存在于某作用域的一塊內(nèi)存空間孕豹。例如當(dāng)你調(diào)用一個函數(shù),函數(shù)本身就會形成一個stack十气,用來存放接收的參數(shù)以及返回地址励背。在函數(shù)本體內(nèi)聲明的任何變量,其所使用的內(nèi)存塊都取自上述 Stack
- Heap
Heap *或者叫做默認(rèn)System Heap砸西, 是指由操作系統(tǒng)提供的一塊global內(nèi)存空間叶眉,程序可動態(tài)分配,從中獲取若干區(qū)塊(blocks) *
Stack objects的生命期
- 分析一段代碼
class Complex {...};
...
Complex a3(5,6);
int main()
{
Complex a1(1,2); //a1所占用的內(nèi)存來之stack
static Complex a2(1,3);
Complex* p = new Complex(2,3); //Complex(2,3)是個臨時對象芹枷,它所占用的內(nèi)存是以new自Heap動態(tài)分配獲得衅疙,并由p指向
}
- Stack Objects的生命期
上述代碼示例中,a1便是所謂的Stack Object鸳慈, 其生命在作用域結(jié)束之際結(jié)束饱溢。這種作用域的Object,又被稱為auto object走芋, 因為他們會被自動清理绩郎。
- Stack local Object的生命期
a2便是 static object,它的生命在作用域結(jié)束之后仍然存在翁逞,直到整個程序結(jié)束肋杖。
- global objects的生命期
a3便是所謂的 global object,其生命在整個程序結(jié)束之后才結(jié)束挖函,也可以把它理解為一種static兽愤,其作用域是整個程序
Heap Objects的生命期
- 代碼
class Complex {...};
...
{
Complex* p = new Complex();
delete p;
}
p所指的便是Heap Object,其生命期在被deleted之際結(jié)束挪圾。如果不寫delete p浅萧,會出現(xiàn)內(nèi)存泄漏,p所指向的Heap object仍然存在哲思,但p的生命期結(jié)束了洼畅,作用域外再也看不到p了,也就沒有機(jī)會delete p了棚赔。
new delete操作符內(nèi)部實現(xiàn)
內(nèi)存分配這塊非常重要帝簇,其分析的內(nèi)存模型在很多資料上都是找不到的,內(nèi)存模型是基于VC的靠益,其他編譯器也應(yīng)該大同小異吧丧肴。
new:先分配memory,再調(diào)用ctor()
- 不包含指針
new內(nèi)部分解為三個步驟:
- 調(diào)用 operator new函數(shù)(內(nèi)部malloc)分配內(nèi)存
- 轉(zhuǎn)型
- 調(diào)用構(gòu)造函數(shù)胧后,賦初始值
- 包含指針
三個步驟是相同的芋浮,在這個例子里,operator new分配了4個字節(jié)的空間給指針壳快,然后轉(zhuǎn)型纸巷,第三步調(diào)用構(gòu)造的函數(shù)的時候镇草,又動態(tài)分配了6個字節(jié)的空間給hello并把地址返回給指針ps
delete:先調(diào)用析構(gòu),再釋放內(nèi)存
delete內(nèi)部實現(xiàn)分為兩步:
- 調(diào)用析構(gòu)函數(shù)
- operator delete釋放內(nèi)存
物理內(nèi)存塊模型 in VC
- 動態(tài)內(nèi)存分配的對象
在實際的VC編譯器中瘤旨,一個Complex對象是8個字節(jié)梯啤,需要包含4*2個字節(jié)的cookie(delete回收的時候是根據(jù)cookie來進(jìn)行回收的),一共是16字節(jié)存哲,在調(diào)試模式下因宇,需要額外的32+4個字節(jié)的信息。
- 動態(tài)內(nèi)存分配的array
Complex數(shù)組連續(xù)分配三個對象空間祟偷,并且多了一個4字節(jié)存放數(shù)組的大小內(nèi)存察滑,在沒有調(diào)試模式下,83 + 42 +4 = 36肩袍,序列化必須是16的倍數(shù),所以婚惫,在實際的內(nèi)存中是48字節(jié)氛赐。String的數(shù)組看起來會更小一點,但是還要在堆里面的內(nèi)存先舷。
- 為什么array new 一定要搭配 array delete
這個問題就在于delete[] 會多次調(diào)用析構(gòu)函數(shù)艰管,而不加[]只會調(diào)用一次析構(gòu)函數(shù),所以蒋川,在這個例子中牲芋,最后兩個對象內(nèi)部動態(tài)分配的內(nèi)存是被泄漏了,這個內(nèi)存模型分為兩部分捺球,對象數(shù)組部分和對象動態(tài)內(nèi)存缸浦,他們都是在堆里的,那么我們調(diào)用delete p的時候到底泄漏了多少內(nèi)存呢氮兵?答案是后兩個對象的動態(tài)內(nèi)存裂逐,對象數(shù)組本身的內(nèi)存是delete根據(jù)cookie進(jìn)行釋放的。所以泣栈,如果我的類是沒有指針的卜高,那么我直接調(diào)用delete p是不會造成內(nèi)存泄漏的。