1.C++三個(gè)特殊的函數(shù)(Big Three)
拷貝構(gòu)造函數(shù),賦值構(gòu)造函數(shù)和析構(gòu)函數(shù)。一般情況下,一個(gè)類里面只要有指針,就一定要定義拷貝構(gòu)造函數(shù)和賦值構(gòu)造函數(shù)。系統(tǒng)默認(rèn)的拷貝構(gòu)造函數(shù)只能對(duì)對(duì)象進(jìn)行淺復(fù)制,淺復(fù)制只是將對(duì)象的數(shù)據(jù)進(jìn)行拷貝,而沒有生成新的內(nèi)存空間,可以理解為兩個(gè)對(duì)象同時(shí)指向同一個(gè)內(nèi)存地址,只是拷貝了指針,這是一種危險(xiǎn)的行為,容易造成內(nèi)存泄漏。而深復(fù)制是建立一塊新的內(nèi)存地址,然后將數(shù)據(jù)拷貝到這個(gè)新的內(nèi)存之中。以拷貝復(fù)制為例,進(jìn)行
String a = b;的深復(fù)制行為
Sting &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;
}
以上就是一個(gè)拷貝賦值函數(shù),首先檢測(cè)是否為自我賦值(a = a),然后清除要賦值對(duì)象里面的數(shù)據(jù),然后喂對(duì)象分配一塊內(nèi)存,最后將數(shù)據(jù)賦值到這塊內(nèi)存。這里需要注意的是檢測(cè)自我賦值,檢測(cè)自我賦值不僅僅可以提高效率,更重要的一點(diǎn)是能夠避免程序出錯(cuò)。如果沒有這個(gè)功能,當(dāng)進(jìn)行 a=a是,由于這是同一個(gè)對(duì)象,指向的是同一塊內(nèi)存,當(dāng)執(zhí)行
delete []m_data時(shí),會(huì)將對(duì)象內(nèi)存清除,里面就沒有數(shù)據(jù),會(huì)造成程序運(yùn)行時(shí)出錯(cuò)。
析構(gòu)函數(shù)可以理解為對(duì)對(duì)象進(jìn)行收尾工作,當(dāng)對(duì)象所在的作用域結(jié)束之后,酒會(huì)調(diào)用析構(gòu)函數(shù),系統(tǒng)會(huì)有默認(rèn)的析構(gòu)函數(shù),但值得注意的是,當(dāng)構(gòu)造函數(shù)使用了new時(shí),析構(gòu)函數(shù)一定要使用delete,在程序中,一個(gè)new,一定要對(duì)應(yīng)一個(gè)delete,不然就會(huì)造成內(nèi)存泄漏。
2.堆,棧與內(nèi)存管理
以前對(duì)堆和棧的概念與區(qū)別,以及它們要干什么都是混亂的,現(xiàn)在才總算有了一些了解。
棧(Stack),是存在于某個(gè)作用域的一塊內(nèi)存,當(dāng)你調(diào)用相應(yīng)的函數(shù)時(shí),函數(shù)體本身就會(huì)形成一個(gè)棧,來放置需要接收的函數(shù),在函數(shù)體內(nèi)聲明的仁和變量,其內(nèi)存塊都是取自于棧,但函數(shù)調(diào)用結(jié)束后,系統(tǒng)會(huì)自動(dòng)釋放棧的內(nèi)存,也就是說棧所占用的內(nèi)存又系統(tǒng)自動(dòng)管理,當(dāng)相應(yīng)的作用域結(jié)束后,系統(tǒng)就會(huì)自動(dòng)清理?xiàng)5膬?nèi)存。也可以理解為棧內(nèi)存放的是auto object,系統(tǒng)自動(dòng)管理對(duì)象。
堆(Heap),堆是一塊全局的內(nèi)存空間,可以動(dòng)態(tài)分配內(nèi)存,是用new,在程序的任意地方都可以得到,在這里再次強(qiáng)調(diào),使用new,就一定要用delete來釋放內(nèi)存,不然將會(huì)造成內(nèi)存泄漏。static local object是存放在堆中,此對(duì)象在作用域結(jié)束后任然存在,之道程序結(jié)束。還有就是global object全局對(duì)象。
在這里來簡(jiǎn)單說一下new和delete的運(yùn)行機(jī)制
new(Complex pc = new Complex(1, 2)):
首先根據(jù)數(shù)據(jù)內(nèi)容創(chuàng)建一塊內(nèi)存,其實(shí)就是在內(nèi)部調(diào)用malloc函數(shù),
void mem = operator new (sizeof(Complex))
然后就是轉(zhuǎn)型,也就是強(qiáng)制類型轉(zhuǎn)換
pc = static_cast<Complex>(mem)
最后就是將數(shù)據(jù)內(nèi)容傳入到內(nèi)存中。new簡(jiǎn)單來說,就是先創(chuàng)建內(nèi)存,然后調(diào)用構(gòu)造函數(shù)。
而delete就恰恰相反,首先調(diào)用析構(gòu)函數(shù),將內(nèi)存塊里面的數(shù)據(jù)清除,然后再釋放內(nèi)存塊。
需要注意的是,動(dòng)態(tài)分配內(nèi)存塊并不是你的數(shù)據(jù)需要多少,就會(huì)給你分配多少,實(shí)際是分配的內(nèi)存塊遠(yuǎn)多于數(shù)據(jù)所需的內(nèi)存,用于回收和調(diào)試。
還要注意的是,使用new和delete來創(chuàng)建和刪除數(shù)組時(shí)的用法,delete []p,不然將無法釋放數(shù)組的內(nèi)存。
還要注意的是,在如果在一個(gè)類里面定義了static函數(shù)或者數(shù)據(jù),那么static數(shù)據(jù)將與對(duì)象脫離,就可以說static數(shù)據(jù)不受對(duì)象限制,當(dāng)對(duì)象被銷毀后,static數(shù)據(jù)還將存在,直到整個(gè)程序結(jié)束為止。同時(shí),static數(shù)據(jù)可以直接通過classname來調(diào)用,比如
class Account
{
public:
static double m_rate;
static void set_rate(const double &x)
{
m_rate = x;
}
};
int main
{
Account::set_rate(5.0);
Account a;
a.set_rate(7.0);
return 0;
}
3.單例(Singleton)設(shè)計(jì)模式
單例設(shè)計(jì)模式在編程中是一種簡(jiǎn)單,但也是我么常用的一種設(shè)計(jì)模式,我以前習(xí)慣將其理解為就是對(duì)對(duì)象里的數(shù)據(jù)進(jìn)行初始化,現(xiàn)在聽了老師講解后才發(fā)現(xiàn)自己的理解太狹隘。
單例模式保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn),也就是說,外界不能創(chuàng)建對(duì)象,單例類只有一個(gè)自己,只有一個(gè)唯一的接口,比如:
class A
{
public:
static A &getInstance() { return a;}
setup() {}
private:
A();
A(const A &rth);
static A a;
}
注意,如果 A &A::getInstance{return a;}創(chuàng)建在外部的話,當(dāng)沒人使用A時(shí),既不存在對(duì)象a, 一旦有人使用,那么a就會(huì)一直存在,直到整個(gè)程序結(jié)束。static數(shù)據(jù)只能用static函數(shù)來獲取,在上面的class A中,就是用static函數(shù)來獲取static數(shù)據(jù),要注意的是,static函數(shù)只能處理static數(shù)據(jù),而且static函數(shù)沒有this 指針。
在程序中,我們通過A::getInstance()來獲得對(duì)象a,A::getInstance().setup(),來獲得數(shù)據(jù)。
3.類模版 和 函數(shù)模版
類模版就是一個(gè)類通過綁定不同類型的數(shù)據(jù),進(jìn)行數(shù)據(jù)處理,比如
template<typename T>
complex<double>c1(2.5, 1.5)
complex<int>c2(2, 6)
通過綁定不同的數(shù)據(jù)類型,來處理相應(yīng)的數(shù)據(jù)。
函數(shù)模版無需綁定數(shù)據(jù)類型,它可以對(duì)傳進(jìn)來的數(shù)據(jù)進(jìn)行實(shí)參推導(dǎo),比如
const T &min(const T &a, T &b){ return b < a ? a:b;}
4.虛函數(shù)
虛函數(shù)就是最直接的理解就是在函數(shù)前面加上virtual關(guān)鍵字的函數(shù),虛函數(shù)數(shù)的作用在我看來,就是方便子類對(duì)父類中函數(shù)的修改,方便子類對(duì)象和父類對(duì)象對(duì)同名函數(shù)的調(diào)用。比如下面的這個(gè)例子:
class A
{
public:
void print(){ cout<<"A...... "<<endl;}
}
class B:public A
{
public:
void print(){cout<<"B......"<<endl;}
}
5.繼承
繼承是體現(xiàn)C++多態(tài)性的一個(gè)重要特性,它是is-a的關(guān)系,我的理解它就是一種包含的關(guān)系,也就是說,子類包含類基類的特性,同時(shí)在基類的基礎(chǔ)上可以添加有別于基類的行為。
比如下面的UML圖
Rectangle類繼承了Shape類,那么Retangle繼承了Shape類的行為,注意Shape類里面有兩個(gè)純虛函數(shù),只要類里面有純虛函數(shù),那么這個(gè)類就是抽象類,抽象類不能定義對(duì)象,而且繼承了抽象類的子類必須要復(fù)寫基類里面的純虛函數(shù),不然子類也會(huì)是抽象類。
這里要注意一個(gè),基類里面的非虛函數(shù)就是不希望子類復(fù)寫的函數(shù),而虛函數(shù)就是基類希望子類去復(fù)寫的函數(shù)。非虛函數(shù)也可以復(fù)寫,但在父類和子類對(duì)象的使用過程中會(huì)帶來不便,所以建議不要復(fù)寫基類的非虛函數(shù),如果一定要復(fù)寫基類里面的函數(shù),就將此函數(shù)定義為虛函數(shù)。
6.Composition
Composition模式我就把他理解為就是一個(gè)組合模式,老師說這是一種has-a 的關(guān)系,也就是包含關(guān)系,其實(shí)上面的UML中Retangle類就包含類一個(gè)Point類,完整的UML應(yīng)該如下:
(由于用的軟件問題,組合關(guān)系就用黑色箭頭代替一下)
7.delagetion(委托設(shè)計(jì)模式)
委托設(shè)計(jì)模式就是通過一個(gè)中介來實(shí)現(xiàn)自己要完成的事。比如你現(xiàn)在租房,你會(huì)到58,趕集等網(wǎng)站去尋找房源,然后通過這個(gè)中介來同房東交流,當(dāng)然你也可以直接找房東,但會(huì)浪費(fèi)你的時(shí)間,結(jié)構(gòu)圖如下
房客通過中介來找房,中介可以同時(shí)處理多個(gè)房客的需要,這就大量節(jié)省了房客的時(shí)間,但中介也要收取一定的費(fèi)用,在委托模式中也一樣,消耗一定的性能,但能大大提高程序的效率。