面向對象之數據抽象_代碼大全筆記(一)

代碼大全裏有句話:“在一種語言上編程”的程序員將他們的思想限制於“語言直接支持的那些構件”。如果語言工具是初級的,那麼程序員的思想也是初級的。“深入一種語言去編程”的程序員首先決定他要表達的思想是什麼,然後決定如何使用特定語言提供的工具來表達這些思想。

俗一點的說就是,軟件開發,重要的是思想,語言僅是工具。有了好的創意,算法、方案和架構,可以選擇任何語言來實現。

OOP離不開數據抽象,爲了更好的進行數據抽象,需要掌握一些基本的技術,當所有最基本的技術都可信手拈來時就可以發揮強大的創造力。萬變不離其宗,基礎紮實才能遊刃有餘。

1、隱藏抽象的實現細節

一般情況下,我們都會在C++的頭文件中聲明類的所有成員(public,protected,private),對於這種情況,用戶並不能訪問私有和保護成員,但是能看到。有時候我們希望用戶無法訪問的還是不顯示爲好,尤其是庫的提供者。--不能用你還讓我看,調戲還是找罵?

用戶在創建類的對象之前,C++的編譯器需要知道類的所有成員的細節,以便爲對象分配足夠的內存,一般用戶都僅僅只有頭文件,因此在類的頭文件中需要聲明類的私有和保護成員。這樣同時也會增加編譯的成本,因爲類的大小改變時,類的所有用戶必須重新編譯它們的程序。(類加入或刪除數據成員時,類大小改變,而普通成員函數的增減需要重編,虛函數,若加在現存虛函數的末尾(現存函數在VTABLE中的偏移不變),可以不重編)

爲了解決以上問題,可以通過增加一個單獨的實現類來解決。(Qt中大量使用了這種方法,大多是由一個對應XXXPrivate後綴的類提供具體的實現)--其實就是外包出去

在導出的類中僅僅包含實現類的指針,而不是將所有的數據都保留在導出類的頭文件中,這樣XXXImpl的任何更改,都不會影響到XXX類。如下:

class XXXImpl;//此類中包含具體的數據成員,這裏利用了前置聲明,XXXImpl不需要導出給用戶,在XXX的構造函數中創建XXXImpl並讓m_pHandle指向它
calss XXX
{
   public:
         XXX();
   private:
         XXXImpl*    m_pHandle;
}
利用指針的好處:
  1. XXXImpl的改動不影響XXX,用戶不需要重新編譯,只需重新鏈接即可,極大的加快的編譯程序的速度。--外包商換了,但我不告訴你
  2. XXX的用戶看不到數據成員。--在外包商那裏,你在我這裏看瞎眼啊
利用指針的缺點:
  1. XXX的任何使用m_pHandle的成員函數不能是內聯的。(因爲內聯會在用戶代碼展開,展開時需要知道細節)--缺點再多該用還得用啊
  2. XXX每個對象都增加了一個指針的大小(32位機器是4字節)
  3. 對XXXImpl成員的訪問是通過m_pHandle進行的,增加了間接環節。
  4. 創建和刪除XXXImpl也需要成本

大型工程重新編譯的成本是非常高的,鏈接的主要任務是地址映射(重定位函數調用的地址等),和編譯相比,鏈接所花費的時間相當少。當修改類的實現,而接口不變時,用戶只需要重新鏈接。類中的任何下列改動都將導致用戶重新編譯代碼:

  1. 添加數據成員
  2. 修改數據成員大小或類型
  3. 修改數據成員順序
  4. 刪除數據成員
  5. 添加成員函數
  6. 修改函數聲明
  7. 修改函數順序
  8. 刪除函數
  9. 修改類內部任何聲明(枚舉、結構體等)
  10. 修改內聯函數

僅僅修改函數實現(實現重編),用戶只需重新連接(和新函數地址連接)。

2、指針、引用、值

按照按需創建的概念,即對象在需要的時候才創建。

使用指針作爲數據成員的優點:
  1. 創建包含指針的對象時,不需要立即給指針綁定對象,可以給NULL,讓對象創建速度更快。
  2. 在對象生存期內,指針可以指向不同對象,提供了更大的靈活性。
使用指針作爲數據成員的缺點:
  1. 成員函數使用指針之前需要判空,否則崩潰。
  2. 若在成員函數中new創建了對象並賦給指針,則析構時需要刪除指針所指對象,否則內存泄漏。

對比引用和值,使用指針作爲數據成員只在需要靈活性的地方纔有用,如在創建類對象時,就需要使用類對象的數據成員,那麼使用引用和值會比指針容易。使用指針和引用時,可以使用多態。

3、控制對象的創建

只允許用new()創建對象

不讓用戶在棧上創建對象(局部),技巧是讓析構函數成爲私有。C++中用戶在棧上創建對象時,編譯器會查找匹配的構造和析構函數,如果其中一個無法訪問,則出現編譯錯誤。如此就只能通過new來創建對象了,但是問題是我們如何刪除對象呢?(析構已經是私有的了),一個簡單的辦法是提供一個Delete成員函數(delete this)。--怎麼看着像一損招?有用沒用,先記着

防止用new()創建對象

實現一個不可訪問new運算符。

private:
 void* operator new(size_t size);
 void operator delete(void* p);

4、避免大型數組作爲局部變量或數據成員

由於局部變量是在棧上分配的,棧通常有預設值的大小,且依賴具體平臺,在棧上創建大型數組可能導致程序崩潰,尤其是函數是遞歸的,問題更嚴重。數據成員同理。創建大型數組應該考慮使用堆。

使用數組作爲數據成員時,考慮使用指針代替數組。
使用對象數組和指針數

我們通常避免創建對象數組,使用對象數組有一些缺點:

XXX  obArray[10]; //這樣只能調用默認構造函數

希望調用不同的構造函數時,得使用下面的方式:

XXX obArray[10] = { 11, XXX("test")}--對於剩下的8個,還是調用了默認構造函數,這種方法也只適用於小型數組,如果是10000個XXX對象的數組,估計要自殺了

較好的方案是使用對象指針數組,可以自由使用特定構造函數。

XXX* pObArray[10];

for()

  pobArray[i] = 0;//初始化

pObArray[0] = new XXX("test");//保存

更靈活一點,可以創建一個指向指針數組的指針,即指針的指針。XXX ** ppArray; ppArray = new XXX*[size];但使用指針數組,需要判空,切記。-都用STL容器多一些

5、成員函數返回值,數據成員,首選對象,而不是簡單類型的指針

有時候我們會設置類似 const char* GetName() const;這樣的成員函數,返回簡單類型的指針。這樣做並不能防止用戶將指針轉爲char*並修改它。這種時候,最好使用抽象數據類型代替char*來保存字符,如CString。--在可能的地方,使用對象代替原始指針類型

6、避免臨時對象

如果對象做常量使用,則最好創建1次,然後重複使用。

void f(const XXX& x);

void g()
{
   f(XXX("test"));
}
//每次調用g的時候,都將創建臨時對象
可以在全局
XXX Test("test");
g(){ f(Test); }
//或者在內部聲明static對象
void g()
{
   static XXX test("Test");
   f(test);
}


7、使用複製構造函數初始化對象

很多時候,我們希望用現存對象創建新對象,此時,複製構造函數是最好的選擇。

8、有效的使用代理對象

代理對象的作用:

  1. 用於安全共享的代理對象--寫時複製,進程共享內存
  2. 爲方便使用的代理對象
  3. 爲遠程對象替身的代理對象
  4. 提供其他功能的智能代理--智能指針
  5. 解決語法/語義問題的代理--操作符重載[]
  6. 通用下標代理

9、使用簡單的抽象建立更復雜的抽象

給定一個Fish和Fly,則很容易實現一個FlyFish抽象。

10、抽象必須允許用戶用各種不同的方式使用類

數據抽象和麪向對象編程的威力在於創建簡單的類,並用許多方法擴展它們,以創建更加強大有用的抽象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章