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
...?
.