Week2 Notes
A.三大函數(shù):拷貝構(gòu)造,拷貝賦值,析構(gòu)
string class這個不是標(biāo)準(zhǔn)庫里的string,標(biāo)準(zhǔn)庫里的太復(fù)雜了。
首先也要有防衛(wèi)式聲明言疗。Ifndef define endif
測試代碼
int main(){
? ? strings1();
? ? string s2(“hello”);
? ? string s3(s1);//拷貝
? ? cout << s3 << endl;//操作符重載
? ? s3 = s2;//賦值動作
? ? cout << s3<< endl;
}
上面的賦值和拷貝的不同是第一個s3是第一次出現(xiàn),第二個s3是賦值颂砸。所以第一個是拷貝構(gòu)造噪奄,第二個叫拷貝賦值死姚。如果你沒有寫,編譯器會自動給你一套勤篮,是一位一位地拷貝知允。那我們還要自己寫一份嗎?要考慮編譯器給的這一套夠不夠用叙谨,比如復(fù)數(shù)只要給實(shí)部虛部就夠用温鸽,但是這種帶指針的如果還使用編譯器給的拷貝構(gòu)造和拷貝賦值,就不夠用手负,深拷貝和淺拷貝涤垫??竟终?蝠猬?
Class string{
Public:
? ? String(constchar* cstr = 0);
? ? String(conststring& str);
? ? String&operator = (const string& str);
? ? ~string();
? ? char*get_C_str() const {return m_data};
private:
? ? char*m_data;
}
字符串的指針要寫在private里,我們要自己寫一套拷貝统捶,第一個函數(shù)函數(shù)名稱和類相同榆芦,所以是構(gòu)造函數(shù),第二個函數(shù)名字也一樣喘鸟,也是構(gòu)造函數(shù)匆绣,但它的接收類型是自己,所以是拷貝構(gòu)造什黑。第三個操作符=的輸入也是string崎淳,是拷貝賦值,注意愕把,只要你的類帶著指針拣凹,你一定要寫出這兩個拷貝函數(shù)!:藁怼O怠!
第四個波浪線開頭的是析構(gòu)函數(shù)橘蜜。當(dāng)它死亡的時候析構(gòu)函數(shù)就會被調(diào)用菊匿,新增加的234被成為big three,三個特殊函數(shù)扮匠。
第五個復(fù)習(xí)加的const是因為直接返回data不改變所以要加捧请。
Ctor和dtor(構(gòu)造函數(shù)和析構(gòu)函數(shù))
一個字符串多長有兩種想法,一種是不知道長度但最后有結(jié)束符號棒搜,另一種是沒有結(jié)束符號,但多一個lenth活箕,長度力麸。C和c++用的是有結(jié)束符號的設(shè)計方法。
Inline
String::string(const char* cstr = 0){
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;
}
{
? ? stirngs1(),
? ? strings2(“hello”);
? ? string*p = new string(“hello”);
? ? delete p;
}
傳進(jìn)來先判斷是不是空字符串,如果是空的分配一個字符克蚂,為結(jié)束符闺鲸,如果不為空,分配的空間大小是傳進(jìn)來的長度還要加上一個1埃叭,結(jié)束符號\0摸恍,分配完了之后,再用strcpy拷貝進(jìn)我們的m_data里赤屋。加入傳進(jìn)來的是hello的話立镶,長度就是5加1,結(jié)束符號类早,面對字符串都要想到結(jié)束符號媚媒,新創(chuàng)建的對象就擁有內(nèi)容了。
對應(yīng)于構(gòu)造函數(shù)涩僻,有下面的析構(gòu)函數(shù)缭召,做的事情是關(guān)門清理,clean up,在之前講過的復(fù)數(shù)時逆日,不需要清理嵌巷,如果你的class里有指針,多半要用動態(tài)分配室抽,new晴竞,在使用動態(tài)分配的時候,我們要用析構(gòu)函數(shù)把動態(tài)分配的空間釋放掉狠半,否則會發(fā)生內(nèi)存泄漏噩死。
String *p = new string(“hello”);
Delete p;
動態(tài)分配后再調(diào)用。
在這個作用域里頭有三個字符串神年,離開的時候要調(diào)用三次析構(gòu)函數(shù)已维。
Class with pointer members必須有copy ctr和copy op=
因為如果不這么做,編譯器自己做的是一個位一個位的拷貝已日,對于b= a來說垛耳,a的內(nèi)容只有一個指針,如果不這么做飘千,b = a就會把b里的指針也指向a指針指向的內(nèi)容堂鲜,這對于兩個內(nèi)容都很危險,對于b的內(nèi)容來說护奈,他指向的內(nèi)容還在可是沒有指針指向他缔莲,會發(fā)生內(nèi)存泄漏,memory leak.對于a來說霉旗,兩個指針指向他也很危險痴奏,一個改變了另一個也改變蛀骇,所以這個叫淺拷貝,我們寫的函數(shù)是為了深拷貝读拆。
Copy ctor拷貝構(gòu)造函數(shù)
Inline
String::string(const string& str){
? ? M_data= new char[strlen(str.m_data) + 1];
? ? Strcpy(m_Data,str.mdata);
}
{
? ? string s1(“hello”);
? ? string s2(s1);
}
在對m_data賦值的時候直接取另一個object的private擅憔,(兄弟之間互為friend)
在把指針拷過來的同時把內(nèi)容也拷過來,叫深拷貝檐晕,只把指針拷貝過來叫淺拷貝暑诸。
Copy assignment operator拷貝賦值函數(shù)
為了把右邊的東西賦值給左邊,本來兩邊都有東西辟灰,需要先把左邊清空个榕,分配一塊和右邊一樣大的空間,再把右邊拷貝過來伞矩。
Inline
String& string::operator = (conststring& 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;
}
{
string s1(“hello”);
string s2(s1);
s2 = s1;
}
自我賦值的檢測判斷條件是兩個指針相比笛洛,自我賦值如果不寫,好像也不會出錯乃坤,只是效率高低嗎苛让,不是,如果不寫自我賦值檢測湿诊,有可能會出錯狱杰。當(dāng)左右兩邊一樣的時候,賦值的第一步是殺掉左邊的m_data厅须,在做第二個動作的時候string已經(jīng)不見了仿畸,會出錯。所以自我賦值不只是為了效率朗和。
B.堆棧與內(nèi)存管理
Output函數(shù)错沽,是不是可以加在自己的rectangle類里?
#include
ostream& operator <<(ostream& os, const String str){
? ? os<< str.get_c_str();
? ? returnos;
}
stack和heap
函數(shù)本身會形成一個stack用來防止它接收到的參數(shù)眶拉,以及返回地址千埃。
在函數(shù)本體內(nèi)的聲明的任何變量,其所使用的內(nèi)存塊都取自上述stack忆植。
System heap是指操作系統(tǒng)所提供的一塊global的內(nèi)存空間放可,程序可以動態(tài)分配從某種獲得若干區(qū)塊〕可以在程序的任何地方動態(tài)獲得耀里,并有責(zé)任區(qū)釋放他。
Class Complex();
{
? ? complexc1(c1,c2);
? ? complex*p= new complex(3);
}
C1叫做aotu object拾氓,stackobject;
Static complex c2(1, 2);
C2是static object,聲明在作用域結(jié)束之后依然存在冯挎,它的析構(gòu)函數(shù)不會再大括號結(jié)束的時候被調(diào)用,會在整個程序結(jié)束之后被調(diào)用痪枫。
還有一種對象叫全局對象织堂,寫在任何大括號之外的叫做全局對象叠艳,global object,聲明在整個程序結(jié)束之后才消失奶陈,也可以視為一種static object..
{
? ? complex*p = new Complex;
}
上面的程序會發(fā)生內(nèi)存泄漏易阳,在作用域結(jié)束后指針就死亡了,但指針指的對象還存在吃粒,就會發(fā)生memory leak;
new:先分配memory,再調(diào)用ctor;
complex *pc = new complex(1, 2);
編譯器會*pc;
void * mem = operator
new(sizeof(complex));//分配內(nèi)存,里面調(diào)用malloc
pc = static_cast(mem);
pc->complex::complex(1,2);
delete:先調(diào)用dtor,再釋放memory
在復(fù)數(shù)的時候我們沒有寫析構(gòu)函數(shù)潦俺,寫了也是白寫,馬上就要死亡了徐勃。
String ps = new string(“hello”);
Delete ps;
String::~string(ps);//析構(gòu)函數(shù)
Operator delete(ps);//釋放內(nèi)存
使用malloc和free到底分配多少內(nèi)存事示。
Comple:兩個double是8個字節(jié),在調(diào)試模式下上面會多出32個字節(jié)僻肖,下面會多出4個字節(jié)肖爵,上下還有cookie,是4個字節(jié)臀脏。
一共有8+(32+4)+4*2 = 52
在vc下面分配的內(nèi)存塊一定是16字節(jié)的倍數(shù)劝堪,所以分配64個字節(jié)。
在非調(diào)試模式底下揉稚,一個復(fù)數(shù)的大小要去掉灰色的秒啦,所以是8+4*2 = 16個字節(jié)。上下cookie是用來記錄整個的大小搀玖,因為在釋放的時候只給一個指針余境,要知道釋放的內(nèi)存大小cookie head記錄將大小的最后一位記為1.因為大小是16字節(jié)的倍數(shù),所以最后一位肯定是1.
String內(nèi)含一個指針灌诅,分配的內(nèi)存大小為:
4+(32+4)+(4*2) = 48是16的倍數(shù)芳来,在非調(diào)試模式下,大小為4+4*2 = 12猜拾,16
如果分配的是數(shù)組array會怎么樣即舌?
Array new要搭配array delete不然會出錯。
Complex*p = new complex[3];
(8*3) + (32+4)+(4*2)+4 = 72
給80
非調(diào)試模式下关带,(8*3) + (4*2) + 4 = 36給48
如果array new沒有搭配array delete侥涵,會造成內(nèi)存泄漏。
String *p = new string[3];
Delete[] p;
寫不寫中括號都不影響指針這一整塊的刪除宋雏,因為分配的內(nèi)存的大小就記錄在cookie上芜飘,問題出在沒加中括號只調(diào)用了一次dtor,只有寫了中括號編譯器才知道你下面是數(shù)組磨总,會喚起三次dtor嗦明,這三個dtor負(fù)責(zé)把各自動態(tài)分配的內(nèi)存殺掉。
C.復(fù)習(xí)String類的實(shí)現(xiàn)
動態(tài)分配的內(nèi)存塊蚪燕。(memory block)
下面來寫一個字符串的class娶牌,編程實(shí)例:
class String{
public:
? ? String(constchar* cstr = 0);
? ? String(constString& str);
? ? String&operator = (const String& str);
? ? ~String();
? ? char*get_c_str() const {return m_data;}
private:
? ? char* m_data;
};
構(gòu)造函數(shù)和析構(gòu)函數(shù)
inline
String::String(const char* cstr = 0){
If(cstr){
? ? m_data= new char[strlen(cstr) + 1];
? ? strcpy(m_data,cstr);
}
else{//未設(shè)定初值
? ? m_data = new char[1];
? ? *m_data = ‘\0’;
}
}
inline
String::~String(){
? ? delete[]m_data;
}
由于上面是用array new,所以下面用array delete奔浅。
拷貝構(gòu)造函數(shù)copy cstr
inline
String::String(const String& str){
? ? m_data= new char[strlen(str.m_data) + 1];
? ? strcpy(m_data,str.m_data);
}
copy assignment operator拷貝賦值函數(shù)
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;
}
返回類型不是void,因為當(dāng)連續(xù)=時可能會出錯。
拷貝賦值要去檢驗是否是自我賦值诗良,如果是自我賦值汹桦,就可以直接返回。如果沒有做這種檢查鉴裹,不只是效率的問題舞骆,是正誤的問題。在這里const String& str中的&符號和this == &str有不同的意義径荔,前面是傳引用督禽,后面是取地址。前面是出現(xiàn)在typename的后面总处,叫引用狈惫,后面的是出現(xiàn)在object的前面,叫取地址鹦马,得到的是指針胧谈。
D.擴(kuò)展補(bǔ)充,類模板菠红,函數(shù)模板及其他
對象經(jīng)典有帶指針的和不帶指針的第岖。有很多細(xì)節(jié)需要補(bǔ)充。
進(jìn)一步補(bǔ)充:static靜態(tài)
當(dāng)完成基于對象后要做面向?qū)ο笫运荩诓煌念悆?nèi)做時要知道this pointer
不管是數(shù)據(jù)還是函數(shù)前面都可以加static
complex c1, c2, c3;
cout << c1.real();
cout << c2.real();
c1調(diào)用real函數(shù)從另一個角度看就是
complex c1, c2, c3;
cout << complex::real(&c1);
cout << complex::real(&c2);
在沒有導(dǎo)入static的時候蔑滓,函數(shù)只有一份,但是它要來處理很多個對象遇绞,一定要有人告訴它你要處理誰键袱,靠的就是this pointer,通過這個指針找到它要處理的對象在哪里摹闽。成員函數(shù)有一個this pointer但是我們不能寫進(jìn)去蹄咖,這是編譯器自動會幫我們寫。
加了靜態(tài)static后它和對象就脫離了付鹿,他在內(nèi)存中有單獨(dú)一部分區(qū)域澜汤,靜態(tài)數(shù)。
靜態(tài)函數(shù)的身份和一般的成員函數(shù)一樣在內(nèi)存中只有一份舵匾。靜態(tài)的數(shù)據(jù)只有一份俊抵,什么時候會使用呢?比如在設(shè)計一個銀行的賬戶體系坐梯,有一百萬個人來開戶徽诲,我們需要設(shè)計一百萬個人的對象,但利率需要一個同一個東西,一百萬個人同一個利率谎替,此時我們需要將利率設(shè)為靜態(tài)偷溺,在內(nèi)存中只有一份,靜態(tài)函數(shù)的特征和一般成員函數(shù)不同在它沒有this pointer,這樣它不能去訪問去處理钱贯,那它有什么用挫掏,靜態(tài)函數(shù)要處理數(shù)據(jù)的話它只能處理靜態(tài)的數(shù)據(jù)。例子:
class account{
public:
? ? staticdouble m_rate;
? ? staticvoid set_rate(const double& x) {m_rate = x;}
};
double account::m_rate = 8.0;
int main(){
? ? account::set_rate(5.0);
? ? accounta;
? ? a.set_rate(7.0);
}
靜態(tài)的數(shù)據(jù)一般在類外面加定義,要不要給初值喷舀?都可以砍濒。
靜態(tài)函數(shù)只能處理靜態(tài)的數(shù)據(jù)淋肾,調(diào)用static函數(shù)的方式有兩種硫麻,一種是通過object調(diào)用,第二種是通過class name來調(diào)用樊卓。
進(jìn)一步補(bǔ)充:把ctors放在private區(qū)
Singleton
Class A{
Public:
StaticA& getInstance{return a;}
Setup(){…}
Private:
? ? A();
? ? A(const A&rhs);
? ? Static A a;
}
A::getInstance().setup();
這個寫法還不是最完美拿愧,雖然寫好了a,但當(dāng)外界都不需要用到碌尔,a仍然存在浇辜,更好的寫法:
Meyers Singleton
class A{
public:
? ? staticA& get Instance();
? ? setup(){…}
private:
? ? A();
? ? A(constA& rhs);
…
};
A& A::getInstance(){
? ? staticA a;
? ? returna;
}
只有調(diào)用過getInstance后單例才開始存在。
進(jìn)一步補(bǔ)充:cout
重載了各種數(shù)據(jù)的<<操作符
進(jìn)一步補(bǔ)充:class template唾戚,類模板
template
把double都換成T柳洋,使用時
complex c1(2.5,1.5);
complex c2(2, 6);
模板可能會造成代碼的膨脹,以上會產(chǎn)生兩份相似的代碼叹坦,但這個是必要的熊镣。
進(jìn)一步補(bǔ)充:function template函數(shù)模板
stone r1(2, 3), r2(3, 3), r3;
r3 = min(r1, r2);
template
inline
const T& min(const T& a, constT& b){
? ? returnb < a? b:a;
}
class stone{
public:
stone(intw, int h, int we)
:_w(w), _h(h), _weight(we) {}
booloperator < (const stone& rhs) const
{return_weight < rhs.weight;}
private:
int_w, _h, _weight;
};
進(jìn)一步補(bǔ)充:namespace
namespace std{
…
}
using directive
#include
using namespace std;
int main(){
? ? cin<< …;
? ? cout<< …;
? ? return0;
}
using declaration
using std::cout;
int main(){
? ? std::cin<< …;
? ? cout<< …;
? ? return0;
}
#include
int main(){
? ? std::cin<< …;
? ? std::cout<< …;
? ? return 0;
}
在這周的作業(yè)中我遇到了一個類公有繼承了另一個類,并且在子類中含有一個指針指向一個類募书,在寫這個類的拷貝構(gòu)造函數(shù)時绪囱,需要將輸入對象中指針下面的值賦給this對象中的指針,這里由于需要將輸入對象leftup指針中的值取出莹捡,我在類point中寫了兩個取數(shù)據(jù)的公有方法叫g(shù)etX和getY鬼吵,對于賦值,我采用的方法是借用構(gòu)造函數(shù)對新對象中的數(shù)據(jù)進(jìn)行賦值篮赢。另一方面齿椅,對于類rectangle的父類中no的處理,我的理解是no用于記錄每一個生成的shape的編號启泣,這樣就需要一個靜態(tài)的全局?jǐn)?shù)據(jù)count用于記錄一共擁有的shape數(shù)涣脚,并在每生成一個新的shape時將對應(yīng)的編號分給no,這個過程我在shape的構(gòu)造函數(shù)中實(shí)現(xiàn),對應(yīng)的种远,在shape的析構(gòu)函數(shù)中涩澡,我將count值減一,保證shape總數(shù)和現(xiàn)有的shape對象個數(shù)相同。在測試中妙同,我先測試了拷貝構(gòu)造和拷貝賦值的正常使用另外在刪除其中一個對象后射富,再新生成一個rectangle,它對應(yīng)的no應(yīng)該是正確的粥帚。