GeekBand極客班C++面向對象高級編程(上)第二周筆記

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

. Class with pointer member


#ifndef __MYSTRING__

#define __MYSTRING__

class String

{

...

} ;

String :: function(...)...

Global-function(...)...

#endif

int main()

{

? String s1() ;

? String s2("hello") ;

? String s3(s1) ; ? ? ? ? ? ? //拷貝構造

? cout << s3 << endl ;

? s3 = s2 ; ? ? ? ? ? ? ? ? ? ? ?//拷貝賦值

? cout << s3 << endl ;

}


. 拷貝構造與拷貝賦值如果沒有定義,編譯器會默認一套給你

. 當class with pointer 時,要自己寫拷貝構造與拷貝賦值,不能用編譯器默認函數


class String

{

public :

? ? String (const char* cstr = 0 ) ; ? ? ? ? ? ? ? ? ? ? ?//構造函數

? ? String (const String& str) ; ? ? ? ? ? ? ? ? ? ? ? //拷貝構造函數

? ? String& operator = (const String& str) ; ?//拷貝賦值操作符重載

? ? ~String () ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//析構函數

? ? char* get_c_str() const { return m_data } ; ?

private :

? ? char* m_data ; ? ? ? ? ? ?//指向字符的指針,要動態分配,不能直接放數組

}


. ctor 和 dtor 構造函數和析構函數


inline

String :: String(const char* cstr = 0 ) ? ? ? ? ? ? ? //構造函數,參數是有默認值的指針

{

? ? if(cstr){ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//先檢查傳進來的指針是否為空

? ? ? ? m_data = new char[strlen(cstr)+1] ; ? ? ? ? //分配空間+1給結束符號\0留位置

? ? ? ? strcpy(m_data , cstr) ;

? ? }else{

? ? ? ? m_data = new char[1] ; ? ? ? ? ? ? ? ? ? ? ? ? ? //分配1個字符空間

? ? ? ? *m_data = '\0' ;

? ? }

}

inline

String :: ~String() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//析構函數

{

? ? delete[] m_data ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //關門清理之前new的空間

}


. 有指針的class一定要做動態分配,用完之后要釋放

. class with pointer members 必須要有copy ctor 和copy op= 拷貝構造和拷貝賦值

拷貝構造函數

. 如果使用編譯器默認淺拷貝,會造成memory leak以及alias,兩個指針指向同一位置,疊名


inline

String :: String(const String& str) ? ? ? ? ? ? ? ? ? ? ? //拷貝構造函數

{

? ? m_data = new char[strlen(str.m_data)+1] ; ? ?//創建足夠空間存放藍本

? ? strcpy(m_data , str.m_data) ; ? ? ? ? ? ? ? ? ? ? ? ?//深拷貝

}


copy assignment operator拷貝賦值函數

. 一定要在operator中檢查是否self assignment,不然在對象刪掉原來數據之后再拷貝藍本時會產生不確定行為


inline String&

String :: operator = (const String& str)

{

? ? if(this == &str) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//self assignment檢測自我賦值,防止出錯

? ? ? ? return *this ;

? ? delete[] m_data ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //1.刪掉原來數據

? ? m_data = new char[strlen(str.m_data)+1] ; ?//2.創建和藍本一樣大的空間

? ? strcpy(m_data , str.m_data) ; ? ? ? ? ? ? ? ? ? ? ? //3.拷貝藍本

? ? return *this ;

}


8.堆、棧 與內存管理

output函數

. output operator操作符重載

. 只能寫成全局函數,如果寫為成員函數會改變操作符使用方向把cout放到右邊去


#include <iostream>

ostream& operator << (ostream& os , const String& str)

{

? ? os << str.get_c_str() ;

? ? return os ;

}

{

? ? String s1(“hello ”) ;

? ? return os ;

}


stack棧,heap堆

. stack是存在于某作用域scope的一塊內存空間memory space。調用函數時,會形成一個stack來存放所接收的參數和返回的地址

. 在函數本體function body內聲明的任何變量所使用的內存塊都取自stack

. Heap,又稱system heap 是操作系統提供的global全局內存空間,程序動態分配dynamic allocated 從中獲得若干區塊blocks,用new來動態取得

. 離開scope后Stack中創建的數據生命結束,在Heap中new的數據離開作用域后依然存在需要手動delete掉

. stack objects的生命期,在scope結束之后動調用析構函數,又稱auto object,被自動清理

. static local object 生命期 ,在scope結束后依然存在,直到整個程序結束,析構函數在程序結束調用

. global object 的生命期,在scope外定義,生命直至main結束

. heap object的生命期,new之后要delete,防止內存泄漏。

new和delete

. new:先分配memory,再調用ctor構造函數

. new編譯時被分解為三個動作:分配內存、轉換存儲類型、通過指針調用構造函數

. delete:先調用dtor析構函數,再釋放memory

. delete編譯時分解為兩個動作:調用析構函數、釋放內存

. 如果類中沒有定義析構函數,沒有指針時編譯器會在local結束自動清理內存,有指針時必須定義析構函數釋放動態分配的內存,不然會產生memory leak

動態分配所得的內存塊memory block,(in VC)

. 例如new complex,會獲得2個double數據位置,調試時候會在數據前后多獲得一些內存和cookies,分配內存要為16的倍數,不足16會用pad補齊。不在調試模式下不需要debugger header,于是剛好為16個byte。上下cookies用來記錄整塊大小,一個cookie占用4byte。malloc和free函數以cookies為前后標記,分配時cookies后一位是1,由于大小為16整數倍,所以cookies最后四位是0用來記錄分配還是收回。

動態分配所得array

. 在良好的變成習慣中,new array[]一定要搭配delete[]


Complex* p = new Complex[3] ;

...

delete[] p ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//如果new的是array,delete一定要加[]

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //加[]會調用三次dtor析構函數釋放所以指針

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//如果不加[],只調用一次dtor,則會造成memory leak


9.String類的實現過程總結


class String

{

public :

? ? String(const char* cstr = 0) ; ? ? ? ? ? ? ? ? ? ? ?//接受一個指針為初值,默認值為0

? ? String(const String& str) ; ? ? ? ? ? ? ? ? ? ? ? ? ?//拷貝構造

? ? String& operator = (const String& str) ; ? ? //返回如果不是local object,則返回reference

? ? ~String() ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //析構函數

? ? char* get_c_str() const { return m_data} ; ?//返回字符串的輔助函數

private :

? ? char* m_data ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //數據為動態分配數組

}

inline ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//盡量讓函數成為inline

String :: String(const char* cstr = 0) ? ? ? ? ? ? ? ? ? ? ? ?//構造函數

{

? ? if(cstr){

? ? ? ? m_data = new char[strlen(cstr)+1] ; ? ? ? ? ? ? ? ?//調用其他函數記得include該頭文件

? ? ? ? strcpy(m_data , cstr) ;

? ? }else{

? ? ? ? m_data = new char[1] ;

? ? ? ? *m_data = '\0' ;

? ? }

}

inline

String :: ~String() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//析構函數一定要寫

{

? ? delete[] m_data ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//前面有用array new,這里也要array delete

}

inline

String :: String(const String& str)

{

? ? m_data = new char[strlen(str.m_data)+1] ; ? ? ?//分配足夠大的空間

? ? strcpy(m_data , str.m_data) ; ? ? ? ? ? ? ? ? ? ? ? ? ? //把初值拷貝進來

}

inline ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //復雜函數也寫成inline是沒有關系的

String& String :: operator = (const String& str) ? ? ?//&符號放在typename后表示引用

? ? 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 ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 返回值以支持連續賦值


10.類模版、函數模板、及其他

補充一:static?

. 靜態:static加在數據或者函數前面、

. 調用相同函數時使用了不同的地址,this指的調用函數的object的地址

. 一個函數要被很多對象調用時,由this pointer來告訴函數調用哪個地址.

. 數據加上static之后,數據與對象脫離,在內存某區域單獨純在處理,只有一份

. 函數加上static之后,成為靜態函數,沒有this pointer,不能處理對象,用來存取靜態數據

. 靜態數據一定要在函數外給出初值


class Account

{

public :

static double m_rate ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //靜態數據

static void set_rate(const double& x){m_rate = x ;} ? ? ? ? ? ?//靜態函數

double Account :: m_rate = 8.0 ; ? ? ? ? ? ? ? ? ? ? ? ? ??//靜態數據一定要在class外給出定義

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //定義可使變量獲得內存,可同時給出初值

int main()

{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //調用靜態函數方式有兩種

Account :: set_rate(5.0) ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//一種,通過object調用

Account a ;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //二種,通過class name調用

a.set_rate(7.0) ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

}


補充二:把ctors放在private區

. 只希望產生一個對象的class,如singleton


class A

{

public :

static A& getInstance {return a ;} ;

setup() {...}

private :

A() ;

A(const A& rhs) ;

static A a ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //一個A本身a已經存在于static,外界創建不了A

...

}



class A{

public :

static A& getInstance() ;

setup() {...}

private :

A() ;

A(const A& rhs);

} ;

A& A :: getInstance() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

{

static A a ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//也可將靜態A創建寫進函數中,防止空間浪費

return a; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//有人使用函數時才會被創建,離開繼續存在

}


補充三:cout

. ostream中將cout的<<操作符進行各種類型重載,cout即可接受各種不同類型數據

補充四:class template ,類模版


template <typename T> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //告訴編譯器模板名稱T

class complex

{

public :

complex(T r=0 ; T i=0) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //數據類型寫為T

: re(r) , im(i)

{}

...

T real() const {return re ;}?

T imag() const {return im ;}

private :

T re , im ;

...

}

{

complex<double> c1(2.5 , 1.5) ; ? ? ? ? ? ? ? ? ? ? ? ? ? ?//模板使用方法

complex<int> c2(2 , 6) ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//T會被替換為<>中的類型

}


補充五:function template , 函數模板

. 當


template <class T>

inline const T&

min(const T& a , const T& b)

{

return b<a?b:a ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//引數推導結果調用類中<操作符重載函數

}


. 編譯器會對function template進行引數(實參)推導argument deduction

. c++中算法都為function template 形式

補充六: namespace


namespace std ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //std會被全部包在一起使用

{

... ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //其中內容會被包在一起

}


. using directive


#include <iostream>

using namespace std ; ? ? ? ? ? ? ? ? ? ? ?//將全部std包進去

int main()

{

cin << ...;

cout <<...;

return 0 ;

}


. using declarating


#include <iostream>

using std::cout ; ? ? ? ? ? ? ? ? ? ? //僅包入cout

int main()

{

std::cin <<...;

cout <<...;

return 0 ;

}


. 不使用namespace


include <iostream>

int main()

{

std::cin <<...;

std::cout <<...;

return 0;

}


補充七:其他

. operator type 轉換函數

. explicit?

. Namespace

. template specialization

. Standard Library

. auto

...?



.

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

推薦閱讀更多精彩內容