《劍指offer》:[48]不能被繼承的類-單例模式
題目:不能被繼承的類
不能繼承,一般我們會對建構函式做手腳。不能繼承,繼承會發生什麼,繼承的類在建立物件的時候,會自動呼叫父類的建構函式,如果我們在這裡限制讓子類不能呼叫父類的構造和析構就是實現了不能繼承,但是也不能影響自己的使用。
方案一:思想:設定建構函式和解構函式為私有+新增兩方法建立和銷燬物件。
原因:
(1)把建構函式和解構函式設定為私有函式,這樣可以防止子類呼叫建構函式和解構函式,這樣也就實現了防止繼承;
(2)新增兩個方法來建立物件和銷燬物件是為了不影響自己建立和銷燬物件,不能為了限制別人把自己也坑了,這樣就不划算了。所以我們採用了靜態方法建立和銷燬物件。
缺點:會影響類物件的建立,並且只能建立堆上的物件,不能建立棧上的物件(私有嘛)!
具體程式碼實現測試:
方法:虛基類+虛基類建構函式和解構函式私有化+不能派生的SealClass變成基類友員類
(1)另外設定一個基類Base,並把基類Base的建構函式和解構函式設定為私有。
(2)把要設定SealClass類變成基類的友元類
(3)SealClass類虛擬繼承Base。
原因:
a、把基類Base的建構函式和解構函式設定為私有化,主要目的也是為了防止讓其他類從基類base中派生,當然此時該類也不能定義物件;
b、設定SealClass類為基類Base的友元類,主要目的是根據友員的性質,該類可以自由訪問基類的私有的建構函式和解構函式;
c、基類的友員不能被派生類繼承,此時之後建立的Driver就不能使用父類的友元了。
d、為什麼是虛擬函式?
主要原因:如果是虛繼承的話,假設類Driver要從SealClass繼承,由於是虛擬函式,該派生類Driver會直接呼叫虛基類Base的建構函式,而不會通過基類SealClass,此時就會報錯。
虛繼承還有一個優點:就是解決多繼承的二義性問題,如果B和C都繼承A,D繼承B和C,如果D要用A的一個變數,就會出現二義性,是DBA還是DCA呢:
如果是虛繼承,呼叫的時候就會越過BC,直接呼叫A的資料,解決了這種二義性的問題。
缺點:不易擴充套件,主要是因為編譯器對虛基類和友元類friend型別的支援和要求不一樣。
測試程式碼如下:
程式碼如下:
注意(2):在有虛繼承的情況下, 派生類先呼叫虛基類的建構函式,再呼叫非虛基類的建構函式。虛基類的子物件在整個初始化過程中只呼叫一次。
不能繼承,一般我們會對建構函式做手腳。不能繼承,繼承會發生什麼,繼承的類在建立物件的時候,會自動呼叫父類的建構函式,如果我們在這裡限制讓子類不能呼叫父類的構造和析構就是實現了不能繼承,但是也不能影響自己的使用。
方案一:思想:設定建構函式和解構函式為私有+新增兩方法建立和銷燬物件。
原因:
(1)把建構函式和解構函式設定為私有函式,這樣可以防止子類呼叫建構函式和解構函式,這樣也就實現了防止繼承;
(2)新增兩個方法來建立物件和銷燬物件是為了不影響自己建立和銷燬物件,不能為了限制別人把自己也坑了,這樣就不划算了。所以我們採用了靜態方法建立和銷燬物件。
缺點:會影響類物件的建立,並且只能建立堆上的物件,不能建立棧上的物件(私有嘛)!
具體程式碼實現測試:
#include <iostream>
using namespace std;
class SealClass
{
public:
static SealClass *GetInstance()//<span style="font-family: Arial; font-size: 14px; line-height: 26px;">靜態的全域性訪問介面;</span>
{
if(NULL==m_pInstance)
{
m_pInstace = new SealClass();
}
return m_pInstace;
}
static void DeleteInstance(SealClass *pInstance)
{
delete pInstance;
}
int GetNum()
{
return number;
}
private:
static SealClass *m_pInstance; //<span style="font-family: Arial; font-size: 14px; line-height: 26px;">靜態的私有例項化指標;</span>
SealClass(){ }
~SealClass() //其實解構函式可以不用寫到這裡;
{
cout<<"private: ~SealClass!"<<endl;
}
};
class Driver:public SealClass
{
public:
Driver(){ }
};
int main()
{
//SealClass ss(11);//ERROR,不能訪問私有的建構函式;
//SealClass *s=new SealClass(11);//ERROR,不能訪問私有的建構函式;
SealClass *ss=SealClass::GetInstance(11); //OK,只能是堆上的物件;
int result=ss->GetNum();
cout<<"number: "<<result<<endl;
SealClass::DeleteInstance(ss);
//建立子類:
//Driver d; //ERROR,不能訪問構造和解構函式;
//Driver *d=new Driver;//ERROR,不能訪問構造和解構函式;
system("pause");
return 0;
}
執行結果:
將函式及例項化指標設定為靜態的主要有兩點考慮:首先,類的靜態成員變數就是指的類共享的物件,而單例模式的物件設成靜態就是為了讓該類所有成員共享同一個物件,所以從語義上是合適的;其次,從語法考慮,常見的單例模式都是通過一個靜態方法(如getInstance)返回其單例,因為靜態方法的內部不能直接使用非靜態變數,所以返回的這個例項就是靜態的。
方法:虛基類+虛基類建構函式和解構函式私有化+不能派生的SealClass變成基類友員類
(1)另外設定一個基類Base,並把基類Base的建構函式和解構函式設定為私有。
(2)把要設定SealClass類變成基類的友元類
(3)SealClass類虛擬繼承Base。
原因:
a、把基類Base的建構函式和解構函式設定為私有化,主要目的也是為了防止讓其他類從基類base中派生,當然此時該類也不能定義物件;
b、設定SealClass類為基類Base的友元類,主要目的是根據友員的性質,該類可以自由訪問基類的私有的建構函式和解構函式;
c、基類的友員不能被派生類繼承,此時之後建立的Driver就不能使用父類的友元了。
d、為什麼是虛擬函式?
主要原因:如果是虛繼承的話,假設類Driver要從SealClass繼承,由於是虛擬函式,該派生類Driver會直接呼叫虛基類Base的建構函式,而不會通過基類SealClass,此時就會報錯。
虛繼承還有一個優點:就是解決多繼承的二義性問題,如果B和C都繼承A,D繼承B和C,如果D要用A的一個變數,就會出現二義性,是DBA還是DCA呢:
如果是虛繼承,呼叫的時候就會越過BC,直接呼叫A的資料,解決了這種二義性的問題。
缺點:不易擴充套件,主要是因為編譯器對虛基類和友元類friend型別的支援和要求不一樣。
測試程式碼如下:
#include <iostream>
using namespace std;
class Base
{
public:
friend class SealClass;
private:
Base()
{
cout<<"private: Base!"<<endl;
}
~Base()
{
cout<<"private: ~Base!"<<endl;
}
};
class SealClass:virtual public Base
{
public:
SealClass()
{
cout<<"SealClass!"<<endl;
}
~SealClass()
{
cout<<"~SealClass!"<<endl;
}
};
//class Driver:public SealClass
//{
//public:
// Driver()
// {
// cout<<"Driver!"<<endl;
// }
// ~Driver()
// {
// cout<<"~Driver!"<<endl;
// }
//};
int main()
{
//Driver d;//ERROR,不能訪問私有的構造和析構:
//Driver *d=new Driver;//ERROR,不能訪問私有的構造和析構:
SealClass s; //OK;
SealClass *ss=new SealClass; //OK
delete ss;
system("pause");
return 0;
}
執行結果:由上得來的模板類,只要是某個類不想派生出子類,就可以使用此模板。
具體實現程式碼如下:
template<typename T>
class Base
{
public:
friend T;
private:
Base(){}
~Base(){}
};
class SealClass : virtual public Base<SealClass>
{
public:
SealClass(){}
~SealClass(){}
};
注意(1):一定要設定為虛繼承:如果不是虛繼承的話,類Driver的物件會呼叫父類的建構函式,而且父類SealClass可以呼叫Base的建構函式和解構函式,則此時是可以有類從SealClass繼承的。程式碼如下:
#include <iostream>
using namespace std;
class Base
{
public:
friend class SealClass;
private:
Base()
{
cout<<"private: Base!"<<endl;
}
~Base()
{
cout<<"private: ~Base!"<<endl;
}
};
class SealClass: public Base
{
public:
SealClass()
{
cout<<"SealClass!"<<endl;
}
~SealClass()
{
cout<<"~SealClass!"<<endl;
}
};
class Driver:public SealClass
{
public:
Driver()
{
cout<<"Driver!"<<endl;
}
~Driver()
{
cout<<"~Driver!"<<endl;
}
};
int main()
{
Driver d;
system("pause");
return 0;
}
建構函式的順序:注意(2):在有虛繼承的情況下, 派生類先呼叫虛基類的建構函式,再呼叫非虛基類的建構函式。虛基類的子物件在整個初始化過程中只呼叫一次。
例如:
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout<<"private: Base!"<<endl;
}
~Base()
{
cout<<"private: ~Base!"<<endl;
}
};
class SealClass: public Base
{
public:
SealClass()
{
cout<<"SealClass!"<<endl;
}
~SealClass()
{
cout<<"~SealClass!"<<endl;
}
};
class Driver:public SealClass
{
public:
Driver()
{
cout<<"Driver!"<<endl;
}
~Driver()
{
cout<<"~Driver!"<<endl;
}
};
int main()
{
Driver d;
system("pause");
return 0;
}
執行結果:
相關文章
- 劍指 Offer 48. 最長不含重複字元的子字串字元字串
- 類的繼承_子類繼承父類繼承
- 【劍指offer】字串的排列字串
- leetcode 劍指 Offer 48. 最長不含重複字元的子字串LeetCode字元字串
- javascript類式繼承設計模式簡單介紹JavaScript繼承設計模式
- 【劍指offer】【2】字串的空格字串
- 【劍指offer】字串的組合字串
- python_類繼承例題Python繼承
- 類的繼承繼承
- 劍指offer-JavaScript版JavaScript
- 【劍指offer】左旋轉字串字串
- 劍指Offer題解合集
- 劍指offer-例題 連續子陣列的最大和陣列
- 劍指 Offer 38. 字串的排列字串
- 劍指Offer 表示數值的字串字串
- 【劍指offer】樹的子結構
- 抽象類是不能被例項化的抽象
- 劍指 offer(1) -- 陣列篇陣列
- Leetcode劍指offer(八)LeetCode
- 【劍指offer】字串轉整數字串
- 劍指offer刷題記錄
- ES6中的類繼承和ES5中的繼承模式詳解繼承模式
- 劍指offer——包含min函式的棧函式
- 《劍指offer》:[54]表示數值的字串字串
- 【劍指offer】陣列中的逆序對陣列
- 【劍指offer】包含min函式的棧函式
- 【劍指offer】替換字串中的空格字串
- JS原型繼承和類式繼承JS原型繼承
- javascript物件導向繼承的簡單例項JavaScript物件繼承單例
- javascript類繼承JavaScript繼承
- 實現一個無法被繼承的C++類繼承C++
- 《劍指 Offer》棧實現佇列佇列
- 劍指offer解析-下(Java實現)Java
- 劍指offer解析-上(Java實現)Java
- 劍指offer——重建二叉樹二叉樹
- 【劍指offer】二叉樹深度二叉樹
- 【劍指offer】員工年齡排序排序
- Python類的繼承Python繼承