博覽網:C++開發工程師之面向對象高級編程(上)第二周筆記

7.Big Three:拷貝構造,拷貝賦值,析構

(1)什么時候需要自己寫拷貝構造和拷貝賦值函數

當編譯器提供的默認拷貝構造和拷貝賦值函數不再滿足要求的時候,比方說類里面帶指針,必須自己寫拷貝構造和拷貝賦值函數;

String(constString&?str);

String&?operator=(constString&?str);

如果不這么做,會怎么樣?如圖1所示,使用默認的拷貝構造和拷貝賦值函數,是一種淺拷貝,只會把指針拷貝過來,會造成內存泄漏,同時兩個指針指向同一個地方,以后改動任何一個變量,會造成另外一個的改變。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 1

(2)怎么寫拷貝構造和拷貝賦值函數

拷貝構造:①創造自己;②拷貝

拷貝構造函數,顧名思義就是拷貝和構造。拷貝構造函數,是一種特殊的構造函數,它由編譯器調用來完成一些基于同一類的其他對象的構建及初始化。其唯一的參數(對象的引用)是不可變的(const類型)。

如果一個類中沒有定義拷貝構造函數,那么編譯器會自動產生一個默認的拷貝構造函數,這個默認的參數可能為X::X(const?X&)或X::X(X&),

由編譯器根據上下文決定選擇哪一個,默認拷貝構造函數的行為如下:默認的拷貝構造函數執行的順序與其他用戶定義的構造函數相同

,執行先父類后子類的構造.拷貝構造函數對類中每一個數據成員執行成員拷貝(memberwise?Copy)的動作.

a)如果數據成員為某一個類的實例,那么調用此類的拷貝構造函數.

b)如果數據成員是一個數組,對數組的每一個執行按位拷貝.

c)如果數據成員是一個數量,如int,double,那么調用系統內建的賦值運算符對其進行賦值

舉例:

inline

String::String(constString&?str)

{

m_data?=newchar[strlen(str.m_data)?+?1];

strcpy(m_data,?str.m_data);

}

拷貝賦值:①delete自己;②重新創造自己;③拷貝

拷貝賦值函數參數跟拷貝構造函數相同,兩者的區別在于:構造函數是對象創建并用另一個已經存在的對象來初始化它。

賦值函數只能把一個對象賦值給另一個已經存在的對象。

注意:考慮自我拷貝的情況

舉例:

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里面有指針,多半要做動態分配

做了動態分配,則在創建的對象死亡之前析構函數會被調用起來;

8.堆,棧與內存管理

Stack(堆),是存在于某作用域(scope)的一塊內存空間(memory space)。例如當你調用函數,函數本身會形成一個Stack用來放置它所接受的參數,以及返回地址。

在函數本體(function body)內聲明的任何變量(local object),其所使用的內存塊都取自上述Stack。

Heap(堆),或者說system heap,是指由操作系統提供的一塊global內存空間,程序可動態分配從某種獲得若干區塊。

(1)stack objects的生命期

classComplex{...}

...

{

Complex?c1(1,2);

}

c1便是所謂的Stack object,其生命在作用于(scope)結束之際結束。這種作用域內的object,又稱為auto object,因為它會被自動清理;

(2)static local objects的生命期

classComplex?{...}

...

{

staticComplex?c2(1,2);

}

c2便是static object,其生命在作用域(scope)結束之后仍然存在,直到整個程序結束。

(3)global objects的生命期

classComplex?{...}

...

Complex?c3(1,2);

intmain()

{

...

}

c3便是所謂global object,其生命在整個程序結束之后才結束。也可以把它視為一種static object,其作用域是整個程序。

(4)heap objects的生命期

classComplex?{...}

...

{

Complex*?p=newComplex;

...

deletep;

}

p指的便是heap object,其生命在它被delete掉之后結束。如果沒有delete p;會出現內存泄漏(memory leak),因為當作業域結束,p所致的heap object仍然存在,但指針p的生命卻結束了,作用域之外再也看不到p(也就沒有機會delete p)。

(5)new:先分配memory,再調用ctor

絕大部分編譯器對調用new,轉化為三步,詳見圖2

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 2

(6)delete:先調用dtor,再釋放memory

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 3

(7)動態分配所得的array

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 4

(8)array new一定要搭配array delete

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 5

10.擴展補充:類模板,函數模板及其他

(1)static

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 6

類complex成員函數只有一份(不可能創建了幾個對象就有幾個函數),但是要處理很多對象,那就得靠this pointer來處理不同的對象。

而static的部分就和對象脫離了,它存在于內存的一部分,只有一份。

什么時候會使用static對象呢?就是和對象無關的部分。

staic成員函數和一般成員函數的特征就是static成員函數沒有this pointer,既然沒有this pointer,那static 成員函數不能和一般的函數一樣去訪問處理non-static data members,那只能處理static members

例如:

classAccount

{

public:

staticdoublem_rate;

staticvoidset_rate(constdouble&?x)?{m_data?=?x?};

};

doubleAccount::m_rate?=?8.0;//靜態數據必須要這樣定義,因為脫離對象,

intmain()

{

Account::set_rate(5.0);

Account?a;

a.set_rate(7.0);

}

調用static函數有兩種方法:

①通過object調用;

②通過class name 調用;

(2)Singleton模式(把ctors放在private區)

classA

{

public:

staticA&?getInstance();

setup()?{...}

private:

A();

A(constA&?rhs);

staticA?a;

...

};

A&?A::getInstance()

{

returna;

}

a本來就存在,和對象無關,然后不想其他人創建,那就把構造函數放在private里,那怎么取得a呢,就用個static A& getInstance()取得a,這是與外界的接口。但這不是最好的寫法,因為不管你用不用,a都存在。所以更好的寫法如下:

classA

{

public:

staticA&?getInstance();

setup()?{...}

private:

A();

A(constA&?rhs);

...

};

A&?A::getInstance()

{

staticA?a;//只有當有人掉用這個函數,a才會存在

returna;

}

(3)cout

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 7

可以看出cout就是一種ostream,實際上是重載了<<運算符的函數,用于打印不同類型的數。

(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&);

};

//調用如下

{

complex?c1(2.5,1.5);

complex?c2(2,6);

}

(5)function template函數模板

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);//則會調用min函數,函數里面會接著調用stone::operator<函數

(6)namespace

以防和別人寫的東西重名。

使用方法有兩種:

①using directive

usingnamespacestd;//把namspace空間的東西全打開

cin?>>?i;

cout?<<"hello"<<?endl;

②using declaration

usingstd::cout;

std::cin?>>?i;

cout?<<"hello"<<?endl;

不要在頭文件中使用using namespace std;,容易造成命名空間污染;

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

推薦閱讀更多精彩內容