7.三大函數(shù):拷貝構(gòu)造税迷,拷貝賦值骏融,析構(gòu)
String s3(s1);//拷貝構(gòu)造函數(shù)(s3剛剛出現(xiàn))
String s4 = s1;//這種情況也是拷貝構(gòu)造(雖然用的'='南誊,但是S4剛剛出現(xiàn))
s3 = s2;//拷貝賦值(s3已經(jīng)出現(xiàn))
無指針的類颓帝,不需要寫拷貝構(gòu)造和拷貝賦值瞻离。類內(nèi)帶指針饺汹,一定要寫拷貝構(gòu)造和拷貝賦值肴裙,不能用編譯器自動生成的宅静。
- 構(gòu)造函數(shù)章蚣,參數(shù)類型是自身類型,則為拷貝構(gòu)造函數(shù)姨夹。
- 拷貝賦值纤垂,重載=操作符,參數(shù)類型是自身類型磷账。
- 和構(gòu)造函數(shù)名稱相同峭沦,前面加~,是析構(gòu)函數(shù)逃糟,當(dāng)類的對象死亡的時候吼鱼,析構(gòu)函數(shù)會被調(diào)用蓬豁。
構(gòu)造函數(shù)
inline
String::String(const char* cstr = 0)
{
if(cstr){
m_data = new char[strlen(cstr) + 1];
strcpt(m_data, cstr);
}else{
m_data = new char[1];
*m_data = '\0';
}
}
- c語言 字符串,以'\0'結(jié)尾菇肃,字符串長度地粪,以'\0'標(biāo)記來計算。另一種在字符串的前面有長度標(biāo)示琐谤,后面沒有結(jié)束符蟆技。
- 字符串長度為0,也要用一個字符笑跛,來保存'\0'付魔,為了析構(gòu)函數(shù)統(tǒng)一析構(gòu),一個字符用 new char[1] 來創(chuàng)建飞蹂。
- 當(dāng)字符串長度不為0,則用strlen()計算出長度+1翻屈,用來保存最后的'\0'陈哑。
析構(gòu)函數(shù)
inline
String::~String(){
delete[] m_data;//array delete配合 array new
}
- 析構(gòu)函數(shù)的作用,清理伸眶,cleanup惊窖。
- 離開作用域時要釋放內(nèi)存。
big three
class with pointer members 必須有 copy ctor 和 copy operator=厘贼。如果沒有使用界酒,則極易造成內(nèi)存泄露,且兩個類中的指針指向同一塊內(nèi)存嘴秸,改變A毁欣,則B也被改變。
copy ctor
inline
構(gòu)造函數(shù)岳掐,接受參數(shù)類型為本身凭疮,則為拷貝構(gòu)造函數(shù)。
深拷貝:首先創(chuàng)造足夠的空間串述,然后把內(nèi)容拷貝到新的對象中执解。
淺拷貝:則會造成兩個‘人’在‘看’同一個東西。
copy assignment operator 拷貝賦值函數(shù)
右邊的對象拷貝的左邊纲酗,左右兩邊原本都有內(nèi)容
- 1.首先要清空左邊衰腌。
- 2.然后在左邊分配和右邊一樣的空間。
- 3.再把右邊的內(nèi)容拷貝到左邊觅赊。
- 4.特別要注意右蕊,要檢測自我賦值,如果不檢測茉兰,則自身在拷貝之前就被干掉了尤泽,造成內(nèi)存錯誤。如果檢測到自我賦值,則直接返回坯约。不單單是為了效率熊咽,更是為了安全。
8.堆闹丐,棧與內(nèi)存管理
8.1.Stack和Heap
Stack
是存在于某作用于(scope)的一塊內(nèi)存空間(memory space)横殴。調(diào)用函數(shù)時,函數(shù)本身即會形成一個stack用來放置它所接收的參數(shù)卿拴,以及返回地址衫仑,以及l(fā)ocal object。
Heap
是由操作系統(tǒng)提供的一塊global內(nèi)存空間堕花,程序可以動態(tài)分配從其中獲得若干區(qū)塊文狱,new出來的,必須手動delete掉缘挽。
stack objects的生命周期
stack object瞄崇,即為local object,又稱為 auto object壕曼,生命在作用域結(jié)束之后就結(jié)束了苏研。對象的析構(gòu)函數(shù)會被調(diào)用。
static local objects的生命周期
static object腮郊,其生命在作用域結(jié)束后仍然存在摹蘑,直到整個程序結(jié)束。
global objects的生命周期
任何寫在大括號之外的對象轧飞,其生命在main函數(shù)之前就存在衅鹿,在程序結(jié)束之后才結(jié)束,作用域是“整個程序”踪少。
heap objects的生命周期
new得到的對象塘安,在使用完畢之后要delete掉。如果沒有delete援奢,則會造成內(nèi)存泄露兼犯。
8.2 new delete
new:先分配memory,再調(diào)用ctor
Complex *pc = new Complex(1, 2);
編譯器把new分解為三個動作:
- 1.分配內(nèi)存 void* mem = operator new(sizeof(Complex)); // 內(nèi)部調(diào)用malloc(n);
- 2.轉(zhuǎn)型pc = static_cast<Complex*>(mem);
- 3.構(gòu)造函數(shù)pc->Complex::Complex(1,2); // 其實(shí)際參數(shù)列表為 Complex::Complex(pc, 1, 2);
delete:先調(diào)用dtor集漾,在釋放memory
String *ps = new String("Hello");
...
delete ps;
編譯器把delete分解為兩個動作:
- 1.析構(gòu)函數(shù) String::~String(ps); // 析構(gòu)函數(shù)會delete掉String類內(nèi)部動態(tài)分配的空間
- 2.釋放內(nèi)存 operator delete(ps); // 其內(nèi)部調(diào)用free(ps)切黔;
共計兩次delete。
8.3 動態(tài)分配所得的內(nèi)存塊(memory block)
1.動態(tài)分配所得的對象
1.Complex *pc = new Complex(1, 2);
在debug模式下具篇,class的前面有32字節(jié)纬霞,后面有4字節(jié),前后cooky各4字節(jié)驱显,cooky為0x41诗芜,共計:
8+(32+4)+(4*2)=52 -> 64
在release模式下瞳抓,class本身8個字節(jié),前后cooky各4個字節(jié)伏恐,cooky為0x11孩哑,共計:
8+(4*2)=16 -> 16
上下cooky的作用,記錄整塊給你的大小翠桦。采用16進(jìn)制横蜒,如果是0,則代表系統(tǒng)回收销凑,如果是1丛晌,則代表系統(tǒng)給出。在vs的編譯器下斗幼,給的內(nèi)存的大小為16的倍數(shù)澎蛛,所以cooky在16進(jìn)制時最后一位一直為0,所以可以用來標(biāo)記內(nèi)存的方向蜕窿。
2.String *ps = new String("Hello");
在debug模式下瓶竭,cooky為0x31,共計:
4+(32+4)+(4*2)=48 -> 48
在release模式下渠羞,cooky為0x11,共計:
4+(4*2)=12 -> 16
2.動態(tài)分配所得的 array
array new 要搭配 array delete智哀,不然會出錯次询。
1.Complex *p = new Complex[3];
在debug模式下,cooky為0x51瓷叫,共計:
(8*3)+(32+4)+(4*2)+4=72 -> 80
在release模式下屯吊,cooky為0x31,共計:
(8*3)+(4*2)+4=36 -> 48
2.String *p = new String[3];
在debug模式下摹菠,cooky為0x41盒卸,共計:
(4*3)+(32+4)+(4*2)+4=60 -> 64
在release模式下,cooky為0x31次氨,共計:
(4*3)+(4*2)+4=24 -> 32
3.array new 一定要搭配 array delete
String *p = new String[3];
...
delete[] p; // 調(diào)用3次dtor
memory | 解釋 |
---|---|
21h | cooky記錄內(nèi)存大小 |
3 | 數(shù)組的大小 |
String[0] | 調(diào)用dtor |
String[1] | 調(diào)用dtor |
String[2] | 調(diào)用dtor |
000000000(pad) | 填充內(nèi)存 |
21h | cooky記錄內(nèi)存大小 |
String *p = new String[3];
...
delete p; // 調(diào)用1次dtor
memory | 解釋 |
---|---|
21h | cooky記錄內(nèi)存大小 |
3 | 數(shù)組的大小 |
String[0] | 調(diào)用dtor |
String[1] | 未調(diào)用dtor |
String[2] | 未調(diào)用dtor |
000000000(pad) | 填充內(nèi)存 |
21h | cooky記錄內(nèi)存大小 |
對比發(fā)現(xiàn)蔽介,整塊的內(nèi)存并沒有發(fā)生內(nèi)存泄露,因?yàn)檎麎K內(nèi)存的大小記錄在cooky當(dāng)中煮寡。如果沒有寫array delete而寫的是delete虹蓄,編譯器不知道下面有幾個對象,因此只有第一個也就是String[0]調(diào)用了dtor幸撕,其余的對象并沒有調(diào)用dtor薇组。當(dāng)調(diào)用玩dtor之后,再釋放掉整塊的內(nèi)存坐儿。由此可以發(fā)現(xiàn)律胀,如果此時的例子是Complex類的話宋光,那么由于類內(nèi)部沒有指針,所以即使用array new炭菌,但沒用array delete罪佳,也不會產(chǎn)生內(nèi)存泄露。
但是在寫代碼時娃兽,我們應(yīng)養(yǎng)成好的編碼習(xí)慣菇民,array new 一定要搭配 array delete。
9.復(fù)習(xí)String類的實(shí)現(xiàn)
- 1.防衛(wèi)式聲明
#ifndef _MYSTRING_ #define _MYSTRING_ class String{ ... }; #endif
- 2.如何去定義內(nèi)部變量
- 放數(shù)組投储,但是數(shù)組的大小無法確定第练。
- 放指針,當(dāng)需要時玛荞,動態(tài)分配(new)字符串的大小,在32位的系統(tǒng)中娇掏,一個指針是4byte,放在private中。
char *m_data;
- 3.ctor勋眯,放在public婴梧;
String(const char* cstr = 0);
- 只是接受字符串,不會改變客蹋,要加上const塞蹭。
- 4.class with point member:
- copy ctor:
String(cosnt String& str);
- copy assignment operator:
String& operator=(const String& str);
- 對于copy ctor 和copy assignment函數(shù),不會改變被拷貝的對象讶坯,所以要加上const番电。
- 返回拷貝的對象,因?yàn)榉祷亟Y(jié)果不是放在local object中辆琅,目標(biāo)本來存在漱办,因此使用return by reference。
- dtor:
~String();
- copy ctor:
- 5.輔助函數(shù)
- 為了能夠cout字符串婉烟,因此需要一個函數(shù)能夠取出String類中的字符串娩井。
char* get_c_str() cosnt { return m_data; }
- 因?yàn)楹瘮?shù)簡單,直接使用inline的方式似袁。因?yàn)椴粫淖儗ο蟮某蓡T變量洞辣,因此需要加上const。
- 為了能夠cout字符串婉烟,因此需要一個函數(shù)能夠取出String類中的字符串娩井。
- 6.ctor叔营,copy ctor屋彪,copy assignment 都不需要加const
- 7.ctor和dtor
- ctor
inline //建議編譯器 String::String(cosnt char* cstr = 0){ if(cstr){ //以下兩個函數(shù)為C函數(shù),需要相應(yīng)頭文件 m_data = new char[strlen(cstr) + 1]; strcpy(m_data, cstr); }else{ m_data = new char[1]; *m_data = '\0'; } }
- dtor
inline //建議編譯器 String::~String(){ delete[] m_data; //由于ctor使用了array new绒尊,因此這里也要使用array delete }
- copy ctor
inline String::String(cosnt String& str){ m_data = new char[strlen(str.m_data) + 1]; strcpy(m_data, str.m_data); }
- inline只是建議畜挥,不能inline的話也沒關(guān)系。
- copy assignment operator
inline String& String::operator= (cosnt String& str){//此時&為reference //首先判斷是否自我賦值,不單單是效率問題婴谱,更是正確與否的問題蟹但。 if(this == &str)//此時的&為取地址 return *this; //在進(jìn)行拷貝賦值 delete[] m_data; m_data = new char[strlen(str.m_data) + 1]; strcpy(m_data, str.m_data); return *this; //傳出去值不在乎用何種方式接受 }
- 關(guān)于返回值躯泰,當(dāng)不需要連續(xù)賦值時,則不需要返回值华糖,當(dāng)需要連續(xù)賦值時麦向,則需要返回值。
String s1, s2, s3("Hello"); s1 = s2 = s3;
- ctor
10.擴(kuò)展補(bǔ)充:類模板客叉,函數(shù)模板及其他
1.static
Class complex{
public:
double real() const{
return this->re;
}
private:
double re;
double im;
};
- C++的習(xí)慣寫法
complex c1, c2, c3;
cout << c1.real();
cout << c2.real();
- 從C的角度考慮完成同上功能的寫法
complex c1, c2 ,c3;
cout << complex::real(&c1);
cout << complex::real(&c2);
同一個函數(shù)real(),之所以能處理不同對象的數(shù)據(jù)诵竭,靠的就是this point。
static data members 在內(nèi)存的單獨(dú)位置兼搏,有且只有一份卵慰。
static member functions
同樣在內(nèi)存的單獨(dú)位置,函數(shù)本身也僅僅只有一份佛呻。但是跟一般的成員函數(shù)有個區(qū)別裳朋,它沒有this point。它只能去處理靜態(tài)的數(shù)據(jù)吓著。
靜態(tài)的變量鲤嫡,在類的內(nèi)部只是生命,需要在類外部定義绑莺。類型 類名稱::變量名(初始化操作);
調(diào)用靜態(tài)函數(shù)的方法有兩種:
- 1)通過object調(diào)用暖眼。但是this指針不會被作為參數(shù)傳入函數(shù)中。
- 2)通過class name調(diào)用纺裁。
單例模式罢荡,把ctors放在private區(qū)域。
class A{
public:
static A& getInstance(){ return a; }
setup()
private:
A();
A(const A& rhs);
static A a;
...
};
A::getInstance().setup();
外界想要使用a对扶,只能用過:getInstance()獲得。
meyers Singleton:
class A{
public:
static A& getInstance()惭缰;
setup()
private:
A();
A(const A& rhs);
...
};
A& A::getInstance(){
static A a;
return a;
}
當(dāng)外界不需要使用這個類時浪南,a不會被創(chuàng)建,只有當(dāng)外界需要使用這個類漱受,調(diào)用了getInstance()函數(shù)络凿,a才會被創(chuàng)建。
2.關(guān)于cout
查看標(biāo)準(zhǔn)庫ostream代碼昂羡,重載了很多的operator<<
3.class template絮记,類模板
在類的前面加上:
template<typename T>
class complex{
...
};
用T吧具體的類代替,當(dāng)實(shí)際使用時虐先,根據(jù)實(shí)際的需要怨愤,生成具體的類代碼。
{
complex<double> c1(2.5, 1.5); //用double代替T生成一份類的代碼
complex<double> c2(2, 6); //用int代替T生成一份類的代碼
}
4.function template蛹批,函數(shù)模板
template<class T>
inline
const T& min(const T& a, const T& b){
retuen a < b ? a : b;
}
所有比較大小都是這么操作撰洗,因此可以使用函數(shù)模板篮愉。實(shí)際比較時如何去比較,則依賴于需要比較大小的類差导。類似于這種函數(shù)试躏,稱之為算法。
{
complex c1(1, 2), c2(3, 4), c3;
c3 = min(c1, c2);//當(dāng)調(diào)用min()函數(shù)時设褐,編譯器會進(jìn)行實(shí)參推導(dǎo)(argument deduction)颠蕴,不必再使用的時候指定類型。
}
5.namespace
避免全局變量助析,函數(shù)以及類的同名犀被,則需要namespace,如果每個人自己頂一個namespace貌笨,則不會造成沖突弱判。
- using directive
using namespace std; { cin >> ...; cout << ...; }
- using declaration
using std::cout; { std::cin >> ...; cout << ...; }
- not use
{ std::cin >> ...; std::cout << ...; }