GeekBand C++ Week2 Notes

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ù)只要給實部虛部就夠用,但是這種帶指針的如果還使用編譯器給的拷貝構(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ù)!!!!

第四個波浪線開頭的是析構(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類的實現(xiàn)

動態(tài)分配的內(nèi)存塊。(memory block)

下面來寫一個字符串的class,編程實例:

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.擴展補充,類模板,函數(shù)模板及其他

對象經(jīng)典有帶指針的和不帶指針的。有很多細(xì)節(jié)需要補充。

進(jìn)一步補充: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)存中有單獨一部分區(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)一步補充:把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)一步補充:cout

重載了各種數(shù)據(jù)的<<操作符

進(jìn)一步補充:class template,類模板

template

把double都換成T,使用時

complex c1(2.5,1.5);

complex c2(2, 6);

模板可能會造成代碼的膨脹,以上會產(chǎn)生兩份相似的代碼,但這個是必要的。

進(jìn)一步補充: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)一步補充: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ù)中實現(xiàn),對應(yīng)的,在shape的析構(gòu)函數(shù)中,我將count值減一,保證shape總數(shù)和現(xiàn)有的shape對象個數(shù)相同。在測試中,我先測試了拷貝構(gòu)造和拷貝賦值的正常使用另外在刪除其中一個對象后,再新生成一個rectangle,它對應(yīng)的no應(yīng)該是正確的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容