7.Big Three:拷貝構(gòu)造,拷貝賦值,析構(gòu)
(1)什么時候需要自己寫拷貝構(gòu)造和拷貝賦值函數(shù)
當(dāng)編譯器提供的默認(rèn)拷貝構(gòu)造和拷貝賦值函數(shù)不再滿足要求的時候驻谆,比方說類里面帶指針,必須自己寫拷貝構(gòu)造和拷貝賦值函數(shù);
String(constString&?str);
String&?operator=(constString&?str);
如果不這么做昼浦,會怎么樣?如圖1所示筒主,使用默認(rèn)的拷貝構(gòu)造和拷貝賦值函數(shù)关噪,是一種淺拷貝,只會把指針拷貝過來物舒,會造成內(nèi)存泄漏色洞,同時兩個指針指向同一個地方,以后改動任何一個變量冠胯,會造成另外一個的改變。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 1
(2)怎么寫拷貝構(gòu)造和拷貝賦值函數(shù)
拷貝構(gòu)造:①創(chuàng)造自己锦针;②拷貝
拷貝構(gòu)造函數(shù)荠察,顧名思義就是拷貝和構(gòu)造∧嗡眩拷貝構(gòu)造函數(shù),是一種特殊的構(gòu)造函數(shù)焕盟,它由編譯器調(diào)用來完成一些基于同一類的其他對象的構(gòu)建及初始化灼卢。其唯一的參數(shù)(對象的引用)是不可變的(const類型)。
如果一個類中沒有定義拷貝構(gòu)造函數(shù)沃于,那么編譯器會自動產(chǎn)生一個默認(rèn)的拷貝構(gòu)造函數(shù)繁莹,這個默認(rèn)的參數(shù)可能為X::X(const?X&)或X::X(X&),
由編譯器根據(jù)上下文決定選擇哪一個,默認(rèn)拷貝構(gòu)造函數(shù)的行為如下:默認(rèn)的拷貝構(gòu)造函數(shù)執(zhí)行的順序與其他用戶定義的構(gòu)造函數(shù)相同
,執(zhí)行先父類后子類的構(gòu)造.拷貝構(gòu)造函數(shù)對類中每一個數(shù)據(jù)成員執(zhí)行成員拷貝(memberwise?Copy)的動作.
a)如果數(shù)據(jù)成員為某一個類的實例,那么調(diào)用此類的拷貝構(gòu)造函數(shù).
b)如果數(shù)據(jù)成員是一個數(shù)組,對數(shù)組的每一個執(zhí)行按位拷貝.
c)如果數(shù)據(jù)成員是一個數(shù)量,如int,double,那么調(diào)用系統(tǒng)內(nèi)建的賦值運算符對其進(jìn)行賦值
舉例:
inline
String::String(constString&?str)
{
m_data?=newchar[strlen(str.m_data)?+?1];
strcpy(m_data,?str.m_data);
}
拷貝賦值:①delete自己;②重新創(chuàng)造自己零院;③拷貝
拷貝賦值函數(shù)參數(shù)跟拷貝構(gòu)造函數(shù)相同,兩者的區(qū)別在于:構(gòu)造函數(shù)是對象創(chuàng)建并用另一個已經(jīng)存在的對象來初始化它。
賦值函數(shù)只能把一個對象賦值給另一個已經(jīng)存在的對象募疮。
注意:考慮自我拷貝的情況
舉例:
inline
String&?String::operator?=(constString&?str)
{
if(this==?&str)
return*this;
delete[]?m_data;
m_data?=newchar[strlen(str.m_data)?+?1];
strcpy(m_data,?str.m_data);
return*this;
}
(3)如果class里面有指針,多半要做動態(tài)分配
做了動態(tài)分配,則在創(chuàng)建的對象死亡之前析構(gòu)函數(shù)會被調(diào)用起來;
Stack(堆),是存在于某作用域(scope)的一塊內(nèi)存空間(memory space)钝域。例如當(dāng)你調(diào)用函數(shù)网梢,函數(shù)本身會形成一個Stack用來放置它所接受的參數(shù)烦感,以及返回地址手趣。
在函數(shù)本體(function body)內(nèi)聲明的任何變量(local object),其所使用的內(nèi)存塊都取自上述Stack淀散。
Heap(堆)晨抡,或者說system heap,是指由操作系統(tǒng)提供的一塊global內(nèi)存空間则剃,程序可動態(tài)分配從某種獲得若干區(qū)塊凄诞。
(1)stack objects的生命期
classComplex{...}
...
{
Complex?c1(1,2);
}
c1便是所謂的Stack object,其生命在作用于(scope)結(jié)束之際結(jié)束忍级。這種作用域內(nèi)的object,又稱為auto object伪朽,因為它會被自動清理轴咱;
(2)static local objects的生命期
classComplex?{...}
...
{
staticComplex?c2(1,2);
}
c2便是static object,其生命在作用域(scope)結(jié)束之后仍然存在,直到整個程序結(jié)束朴肺。
(3)global objects的生命期
classComplex?{...}
...
Complex?c3(1,2);
intmain()
{
...
}
c3便是所謂global object窖剑,其生命在整個程序結(jié)束之后才結(jié)束。也可以把它視為一種static object戈稿,其作用域是整個程序西土。
(4)heap objects的生命期
classComplex?{...}
...
{
Complex*?p=newComplex;
...
deletep;
}
p指的便是heap object,其生命在它被delete掉之后結(jié)束鞍盗。如果沒有delete p;會出現(xiàn)內(nèi)存泄漏(memory leak)需了,因為當(dāng)作業(yè)域結(jié)束,p所致的heap object仍然存在般甲,但指針p的生命卻結(jié)束了肋乍,作用域之外再也看不到p(也就沒有機(jī)會delete p)。
(5)new:先分配memory敷存,再調(diào)用ctor
絕大部分編譯器對調(diào)用new墓造,轉(zhuǎn)化為三步,詳見圖2
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 2
(6)delete:先調(diào)用dtor锚烦,再釋放memory
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 3
(7)動態(tài)分配所得的array
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 4
(8)array new一定要搭配array delete
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 5
10.擴(kuò)展補充:類模板觅闽,函數(shù)模板及其他
(1)static
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 6
類complex成員函數(shù)只有一份(不可能創(chuàng)建了幾個對象就有幾個函數(shù)),但是要處理很多對象涮俄,那就得靠this pointer來處理不同的對象蛉拙。
而static的部分就和對象脫離了,它存在于內(nèi)存的一部分禽拔,只有一份刘离。
什么時候會使用static對象呢?就是和對象無關(guān)的部分睹栖。
staic成員函數(shù)和一般成員函數(shù)的特征就是static成員函數(shù)沒有this pointer硫惕,既然沒有this pointer,那static 成員函數(shù)不能和一般的函數(shù)一樣去訪問處理non-static data members野来,那只能處理static members
例如:
classAccount
{
public:
staticdoublem_rate;
staticvoidset_rate(constdouble&?x)?{m_data?=?x?};
};
doubleAccount::m_rate?=?8.0;//靜態(tài)數(shù)據(jù)必須要這樣定義恼除,因為脫離對象,
intmain()
{
Account::set_rate(5.0);
Account?a;
a.set_rate(7.0);
}
調(diào)用static函數(shù)有兩種方法:
①通過object調(diào)用曼氛;
②通過class name 調(diào)用豁辉;
(2)Singleton模式(把ctors放在private區(qū))
classA
{
public:
staticA&?getInstance();
setup()?{...}
private:
A();
A(constA&?rhs);
staticA?a;
...
};
A&?A::getInstance()
{
returna;
}
a本來就存在,和對象無關(guān)舀患,然后不想其他人創(chuàng)建徽级,那就把構(gòu)造函數(shù)放在private里,那怎么取得a呢聊浅,就用個static A& getInstance()取得a餐抢,這是與外界的接口现使。但這不是最好的寫法,因為不管你用不用旷痕,a都存在碳锈。所以更好的寫法如下:
classA
{
public:
staticA&?getInstance();
setup()?{...}
private:
A();
A(constA&?rhs);
...
};
A&?A::getInstance()
{
staticA?a;//只有當(dāng)有人掉用這個函數(shù),a才會存在
returna;
}
(3)cout
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 7
可以看出cout就是一種ostream欺抗,實際上是重載了<<運算符的函數(shù)售碳,用于打印不同類型的數(shù)。
(4)class template绞呈,類模板
template
classcomplex
{
public:
complex?(T?r?=?0,?T?i?=?0)
:?re?(r),?im(i)
{}
complex&?operator?+=?(constcomplex&);
T?real()const{returnre;?}
T?imag()const{returnim;?}
private:
T?re,?im;
friendcomplex&?_doapl?(complex*,constcomplex&);
};
//調(diào)用如下
{
complex?c1(2.5,1.5);
complex?c2(2,6);
}
(5)function template函數(shù)模板
classstone
{
public:
stone(intw,inth,intwe)
:?_w(w),?_h(h),?_weight(we)
{}
booloperator?<?(conststone&?rhs)const
{return_weight?<?rhs._weight;?}
private:
int_w,?_h,?_weight;
};
template
inlineconstT&?min(constT&?a,constT&?b)
{
returnb?<?a???b?:?a;
}
//使用
stone?r1(2,3),?r2(3,3),?r3;
r3?=?min(r1,r2);//則會調(diào)用min函數(shù)贸人,函數(shù)里面會接著調(diào)用stone::operator<函數(shù)
(6)namespace
以防和別人寫的東西重名。
使用方法有兩種:
①using directive
usingnamespacestd;//把namspace空間的東西全打開
cin?>>?i;
cout?<<"hello"<<?endl;
②using declaration
usingstd::cout;
std::cin?>>?i;
cout?<<"hello"<<?endl;
不要在頭文件中使用using namespace std报强;灸姊,容易造成命名空間污染;