程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++編程雜談之三:面向對象(續)

C++編程雜談之三:面向對象(續)

編輯:關於C++

上一篇我們涉及了面向對象的一個基本概念--封裝,封裝是一個相對比較簡單的概念,也很容易接受,但是很多的場合下面,僅僅是封裝並不能很好的解決很多問題,考慮下面的例子:

假設我們需要設計一個對戰游戲的戰斗細節,在最初的版本中我們將支持一種動作--fight。假設我們有三種角色:fighter、knight和warrior,每種角色的health、hit point不同,基於封裝的基本想法,我們很自然的想到對每個對象使用類的封裝,首先明顯的元素有2個:health、hit point,另外還有name(我們不能只有三個角色)和戰斗的速度speed,方法有:hit、isalive。基於這樣的想法,我們給出下面的類:

class fighter
{
  private:
    int        m_iHealth;
    int       m_iSpeed;//為了方便,這個值使用延時值,越大表示速度越慢
    const int     m_iHitPoint;
    char       m_strName[260];
  public:
    fighter(const char* strName);
    void hit(fighter* pfighter);
    bool isAlive();
};

上面的類可以清楚的抽象出我們所需要表達的數據類型之一,這裡也許你已經發現了一些問題:

成員函數hit用來處理戰斗事件,但是我們有不同的角色,fighter不可能只跟自己同樣的對手作戰,我們希望它能和任何角色戰斗,當然,使用模板函數可以簡單的解決這個問題。另外,我們必須去實現三個不同的類,並且這些類必須都實現這些屬性和方法。即使這些問題我們都解決了,現在我們要組織兩個隊伍作戰,我們希望使用一種群體類型來描述它們,問題是我們必須針對每一種類建立相應的群體結構,當然你可以認為3個不同的類型不是很多,完全可以應付,那麼如果有一天系統升級,你需要管理上百種類型的時候,會不會頭大呢?

在C++中,繼承就可以很好的解決這個問題,在C++中,繼承表示的是一種IS-A關系,即派生類IS-A基類,很多現實世界中的關系可以這樣來描述,如:dog is-a animal,dog是animal的派生(繼承),繼承產生的對象擁有父(基)對象的所有屬性和行為,如animal的所有屬性和行為在dog身上都會有相應的表現。在UML的描述中,這種關系被稱為泛化(generalization)。一般情況下,當我們需要實現一系列相似(具有一定的共性)然而有彼此不同的類別的時候,使用繼承都是很好的解決辦法,例如前面的代碼雖然也能夠實現我們的目標,但是顯然很難管理,下面給出使用繼承後的實現:

class actor//基類
{
  protected:
    int        m_iHealth;
    const int     m_iSpeed;//為了方便,這個值使用延時值,越大表示速度越慢
    const int     m_iHitPoint;
    char       m_strName[260];
  public:
    actor(const char* strName,const int iHealth,const int iSpeed,const int iHitpoint);

    int& Health(){ return m_iHealth; };
    const char* getName(){ return m_strName; };
    virtual void hit(actor *Actor) = 0;
    bool isAlive();
}; 
actor::actor(const char* strName,const int iHealth,const int iSpeed,const int iHitpoint):
m_iHealth(iHealth),
m_iSpeed(iSpeed),
m_iHitPoint(iHitpoint)
{
  strcpy(m_strName,strName);
}
bool actor::isAlive()
{
  return (m_iHealth>0);
}
/////////////////////////////////////////////////////////
//類fighter
class fighter :public actor
{
  public:
    fighter(const char* strName);
    virtual void hit(actor *Actor);
  private:
};
fighter::fighter(const char* strName):
actor(strName,100,20,20)
{
}
void fighter::hit(actor *Actor)
{
  Sleep(m_iSpeed);
  if(!isAlive())
  {
    return ;
  }
  if(Actor&&Actor->isAlive())
  {
     //這裡我們用一個函數做了左值,因為函數返回的是引用
    if(isAlive())
      Actor->Health() = Actor->Health() - m_iHitPoint;
  }
}
/////////////////////////////////////////////////////////
//類knight
class knight :public actor
{
  public:
    knight(const char* strName);
    virtual void hit(actor *Actor);
  private:
};
knight::knight(const char* strName):
actor(strName,150,20,25)
{
}
void knight::hit(actor *Actor)
{
  Sleep(m_iSpeed);
  if(!isAlive())
  {
    return ;
  }
  if(Actor&&Actor->isAlive())
  {
    if(isAlive())
      Actor->Health() = Actor->Health() - m_iHitPoint;
  }
}
/////////////////////////////////////////////////////////
//類warrior
class warrior :public actor
{
  public:
    warrior(const char* strName);
    virtual void hit(actor *Actor);
  private:
};
warrior::warrior(const char* strName):
actor(strName,150,20,25)
{
}
void warrior::hit(actor *Actor)
{
  Sleep(m_iSpeed);
  if(!isAlive())
  {
    return ;
  }
  if(Actor&&Actor->isAlive())
  {
    if(isAlive())
      Actor->Health() = Actor->Health() - m_iHitPoint;
  }
}

C++為我們提供了非常優秀的繼承體系(其實這是面向對象的一個非常重要的特征),上面的類圖中我們可以很清楚的看到他們的關系,在繼承中,基類(有些地方也稱為超類)其實是所有派生類的共性提煉的結果,有時候在開發過程中,是先有派生類,然後再提出共性,產生基類的。

就象遺傳一樣,派生類擁有基類的所有屬性和方法,如基類actor的成員函數和成員變量在每一個派生類中都存在,即fighter、knight和warrior中都存在,在繼承關系中還存在一個非常重要的關鍵字protected,它提供一個界於public和private 之間的一種可見性,與private相同的地方是對於外部來說,它不可見,與public相同的地方是對與繼承體系來說,是向下可見的(其派生出來的類可以直接使用),這裡有一點是很容易讓人迷惑的,就是派生類中基類的成員可見性。對派生類來說,如果使用public繼承,那麼從基類中繼承的所有成員的可見性不變(如果是protected繼承,降一級,public變成protected,private繼承再降),那麼對於一個從基類繼承來的private成員來說,派生類是無法訪問的(很迷惑,是麼?),雖然派生類含有這個變量,但是這是一種間接的擁有關系,在派生類中,含有一個基類的子對象,派生類對基類成員的訪問正是通過這個子對象進行的。在思想中,永遠不要把繼承來的成員看做是自己真正擁有的,雖然使用this可以直接"看到"它們,在派生體系中private和public與其它情況沒有任何區別,而protected在體系的內部就和public完全等同。

由於派生類擁有基類的成員,所以我們可以通過派生而簡單的"重用"我們已有的代碼,從而大大減少重復勞動。

關於繼承的另外一個重要的特征就是虛函數,虛函數是形成類的多態的基礎,在上面的類中:

void knight::hit(actor *Actor)

比如下面的偽碼:

actor* pA;
knight* pKnight = new knight;
pA = pKnight;
pA->hit(…);

這裡pA是一個基類的指針,而它指向的是一個派生類knight的指針,調用它應該是怎麼樣的情況呢?答案是hit會調用knight的方法,而不是基類的,因為它是一個虛函數,虛函數的特點是它永遠忠實與實際的對象(前提是正確的使用),通過這種多態的特性,我們可以使用基類來對派生類進行正確的操作,這就解決了一個上面的問題:使用一種群體類型來描述它們,所以我們可以這樣來遍歷數據,而不需要關心裡面究竟是一些什麼樣的數據:

vector troop1;
vector troop2;
vector::iterator it_tp1 = troop1.begin();
vector::iterator it_tp2 = troop2.begin();
while( (it_tp1!=troop1.end()) && (it_tp2!=troop2.end()) )
{
  while(((*it_tp1)->isAlive())&&(it_tp2!=troop2.end()))
  {
    (*it_tp1)->hit(*it_tp2);
    if(!(*it_tp2)->isAlive())
      it_tp2++;
  }
  it_tp1++;
}

面向對象思想中的繼承是非常重要的概念,正確的運用它,會為軟件的開發過程帶來很多便利,同時,COM的核心技術是使用了多重繼承來實現的。同時,繼承也是面向對象的思想中較難理解的一個概念,這片文章我只能大致的講述其運用,而真正的融會貫通還需要長時間的學習和實踐。

下面我們給出上面的例子的完整代碼,這段代碼完成了隨機建立兩個隊伍,並進行戰斗,直到有一個隊伍全軍覆沒,最後輸出結果。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved