1、類定義
類成員:可以包括 數據、函數、類型別名等;
構造函數:初始化列表,在構造函數的形參列表後,由冒號起始,參數間以逗號隔開;
成員函數:成員函數必須在類內聲明,定義是可選的,類內定義的成員函數默認爲inline,成員函數後 添加 const 表示本函數不改變類對象的數據成員,即this指針爲指向const對象的指針。
另外this永遠是一個 const 指針即只能指向類對象本身,不能人爲改變。
2、static類成員
2.1 static成員函數
static關鍵字只能出現在類定義內的成員聲明處,類似於explicit。 static成員函數沒有this參數所以不能聲明爲const
2.2 static數據成員
staic數據成員必須在類的定義體外部定義(only once),static數據成員不是通過構造函數初始化,而是應該在定義時進行初始化,原因是當類頭文件包含在多個源文件中時防止變量的重複定義;
特殊的整型const static成員:整型const static成員可以在類定義體中初始化,該成員仍然必須在類外部進行定義,不過不用使用初始化式。也就是說類內即使有初始化式也只是聲明和初始化,只有類外才是定義,纔會產生內存空間;
static成員不是類對象的組成部分
所以static成員類型可以是所屬類的類型本身,但是普通成員不可以,普通成員被限定爲類類型的指針或引用、或者其他變量類型。
栗子:
class Singleton
{
public:
static Singleton* getInstance();
private:
Singleton();
//把複製構造函數和=操作符也設爲私有,防止被複制
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* instance;
};
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance()
{
return instance;
}
3、 複製控制
複製控制包括:複製構造函數、析構函數、賦值操作
如果沒有定義他們,編譯器會自動合成一個,但是編譯器合成的複製控制函數屬於位複製(淺度複製),如果類包含指針類型,就需要定義自己的複製控制函數,此時應當實現值複製(深度複製)。
只有單個形參,而且該形參爲本類類型的引用(常用const修飾),這樣的構造函數稱爲複製構造函數。
1. 對象的定義形式
string null_book = "9-999-99999-9";
// copy-initialization 調用接收C風格字符串的構造函數構造一個string對象
// 然後使用複製構造函數將null_book初始化爲臨時對象
string null_book2 = string() ;
// copy-initialization 調用默認構造函數構造一個string對象
// 然後使用複製構造函數將 null_book2初始化爲該臨時對象
2.形參與返回值
當形參或返回值類型爲類類型時由複製構造函數進行復制。
3.初始化容器元素
vector<string> svec(5);
編譯器首先使用默認構造函數,構造一個臨時string對象,然後使用複製構造函數將臨時值複製到svec每個元素。
4.構造函數與數組元素
如果沒有爲類類型提供數組初始化式,將使用默認構造函數初始化每個元素,如果使用花括號來提供顯示初始化式,使用複製構造函數複製到對應元素。
3.1 合成的複製構造函數
如果沒有定義複製構造函數,編譯器就會合成一個,與默認構造函數不同的是,即使提供了其他構造函數,只要沒有定義複製構造函數,都會合成一個。合成的複製構造函數其行爲是:逐個成員初始化。
3.2 定義自己的複製構造函數
當類中具有指針類型,應當自己定義複製構造函數,而且複製構造函數一般不指定爲explict,指定了explicit不能使用等號進行隱式調用複製構造函數。
3.3 禁止複製
爲了禁止複製,類必須顯式聲明覆制構造函數爲private,然而類的友元和成員仍可以進行復制;
如果想要連友元和成員也禁止,可以聲明一個private複製構造函數而不對其定義,此時用戶代碼中的複製嘗試將會標記爲編譯錯誤,友元和成員的複製嘗試將在鏈接期導致錯誤。
3.4 賦值操作符
賦值時,對於一般的都是直接使用合成賦值函數。但是對於成員變量爲指針、對象或複製時希望完成其他附加操作的均需自己來處理賦值行爲。
3.5 析構函數
析構函數與複製構造函數或賦值操作符重要區別,即使定義了自己的析構函數,合成的析構函數仍然運行,在自定義函數之後運行。
三法則:需要析構函數,就需要複製構造函數和賦值操作符,因爲需要析構函數的場景通常爲成員變量爲指針、對象或希望完成其他附加操作的處理行爲。
- 常見三種複製是一起出現的,即需要自定義一種複製行爲時,往往另外兩種也需要自定義
- 需要禁止複製時,必須顯式地聲明其複製構造函數爲 private(其友元和成員依然可以複製)
- 如果要連友元和成員的複製也禁止,就可以聲明一個private的複製構造函數但不對它進行定義。(如果複製類對象會提示編譯錯誤,如果成員和友元嘗試複製就會導致鏈接錯誤)
- 在實現時其實賦值重載函數是包含複製構造函數和析構函數功能的,此時可以將複製和析構功能單獨做到兩個私有函數中,如演示代碼中的CopyData和DeleteData
定義了複製控制行爲的栗子:
class CCopyControl
{
public:
CCopyControl(int nData=0);
CCopyControl(const CCopyControl& c); //複製構造函數
CCopyControl& operator=(const CCopyControl& c); //賦值重載函數
~CCopyControl(); //析構函數
private:
int m_nData;
int *m_pData;
void CopyData(const CCopyControl& c);
void DeleteData();
};
//默認構造函數
CCopyControl::CCopyControl( int nData/*=0*/ )
{
m_nData = nData;
m_pData = new int(nData);
}
//複製構造函數
CCopyControl::CCopyControl( const CCopyControl& c )
{
CopyData(c);
}
//賦值重載函數
CCopyControl& CCopyControl::operator=( const CCopyControl& c )
{
if (this != &c)
{
DeleteData();
CopyData(c);
}
return *this;
}
//析構函數
CCopyControl::~CCopyControl()
{
DeleteData();
}
//複製數據
void CCopyControl::CopyData( const CCopyControl& c )
{
this->m_nData = c.m_nData;
this->m_pData = new int(c.m_nData); //深拷貝
}
//刪除數據
void CCopyControl::DeleteData()
{
if (!m_pData)
{
delete m_pData;
m_pData = NULL;
}
}
4 、智能指針
智能指針的使用栗子(如下定義中,也可以使指針計數維護類增加封裝類爲友元):
// CMySmartPtr指針封裝類,將普通指針封裝爲智能指針
template <class T>
class CMySmartPtr
{
public:
/*構造函數*/
CMySmartPtr(T* pT)
{
pCountT = new CCountT(pT);
}
CMySmartPtr()
{
pCountT = NULL;//默認指針爲空
}
/*複製控制*/
~CMySmartPtr()
{
DeleteData();
}
CMySmartPtr(const CMySmartPtr& p)
{
CopyData(p);
}
CMySmartPtr& operator=(const CMySmartPtr& p)
{
if (this != &p) //注意自身對自身賦值的情況
{
DeleteData();
CopyData(p);
}
return *this;
}
/*指針*和->解引用*/
T& operator*()
{
return *(this->pCountT->pT);
}
T* operator->()
{
return this->pCountT->pT;
}
private:
//裝飾類,爲待管理的指針維護引用計數
class CCountT
{
public:
CCountT(T* pT)
{
this->pT = pT;
this->nCount = 1;
}
~CCountT()
{
delete pT;
}
T* pT;
int nCount;
};
//統一共享的指針,依靠引用計數來釋放
private:
CCountT *pCountT;
void CopyData(const CMySmartPtr& p)
{
p.pCountT->nCount++;
this->pCountT = p.pCountT;
}
void DeleteData()
{
if (pCountT && --pCountT->nCount==0)
{
delete pCountT;
pCountT = NULL;
}
}
};