前兩篇中钞澳,我們分別介紹了面向?qū)ο蟮姆庋b和繼承兩個特性怠惶,今天我們來說最后一個特性:多態(tài)。
什么是多態(tài)
用一句話來概括多態(tài):允許父類的指針指向子類對象略贮。
為什么要用父類的指針去指向子類對象呢甚疟?我們想象一個最簡單的場景,如果我們需要一個函數(shù)的參數(shù)是可變數(shù)據(jù)類型逃延,那如何實現(xiàn)呢览妖?C++是不允許模糊數(shù)據(jù)類型存在的,這個需求聽起來幾乎不可能實現(xiàn)揽祥。不過有了多態(tài)讽膏,我們可以把參數(shù)類型設(shè)置為父類的指針類型,這樣在參數(shù)傳遞的時候我們就可以傳遞這個父類的任意一個子類的對象了拄丰。聽起來比較亂府树,我們用一個例子來解釋。
先看看下面這段代碼:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void Run() = 0;
virtual void Cry() = 0;
};
class Dog : public Animal
{
void Run()
{
cout << "Dog is running ..." << endl;
}
void Cry()
{
cout << "Dog is crying ..." << endl;
}
};
class Cat : public Animal
{
void Run()
{
cout << "Cat is running ..." << endl;
}
void Cry()
{
cout << "Cat is crying ..." << endl;
}
};
void Functions(Animal* pAnimal)
{
pAnimal->Run();
pAnimal->Cry();
}
int main()
{
Dog dog;
Cat cat;
Functions(&dog);
Functions(&cat);
}
運行結(jié)果:
先看Functions()函數(shù)料按,它的參數(shù)是一個Animal類指針奄侠,Animal是一個抽象類,派生了兩個子類:Dog和Cat载矿。在main函數(shù)中垄潮,我們可以很方便地把這兩個子類的對象傳給Animal指針。這就是多態(tài)闷盔。
多態(tài)的背后隱藏著偉大的設(shè)計模式思想弯洗,希望大家慢慢體會。
抽象類
Animal是個抽象類逢勾。它的特點是:
- 包含虛函數(shù)
- 不能實例化
為什么要用抽象類呢牡整?它的意義僅僅是定義成員函數(shù)的外觀。在這個例子中溺拱,我們定義了兩個虛函數(shù):Run()和Cry()逃贝。有了這樣一個抽象類,我們才能保證Dog和Cat兩個子類都擁有一模一樣的兩個成員函數(shù)迫摔。否則秋泳,F(xiàn)unctions()函數(shù)就沒法正常工作了。
星空問題的新需求
回到星空那段代碼攒菠,假如我們這個項目需要A迫皱,B,C,三個人同時開發(fā)卓起。A開發(fā)點狀星星和敬,B開發(fā)矩形的星星,C開發(fā)X形的星星戏阅。我們要確保三個人能同時開發(fā)昼弟,完成后能很容易的把三份代碼加入項目中,該怎么辦呢奕筐?
先分析一下代碼:
class Star
{
public:
Star(){}
~Star(){}
void Init();
void Move();
protected:
void Draw();
void NewPos();
void Remove();
double m_x = 0;
int m_y;
double m_step;
int m_color;
};
Star類中舱痘,影響星星形狀的成員函數(shù)是Draw()和Remove(),我們可以利用多態(tài)把這兩個函數(shù)抽象出來成為一個抽象類离赫,之后讓A芭逝,B,C三個人分別實現(xiàn)一個子類渊胸,這樣就OK了旬盯。
來看看代碼吧:
#include <graphics.h>
#include <time.h>
#include <conio.h>
#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768
#define MAXSTAR 400
class StarType
{
public:
virtual void Draw(int x, int y, int color) = 0;
virtual void Remove(int x, int y) = 0;
};
class PointStar : public StarType
{
void Draw(int x, int y, int color)
{
putpixel((int)x, y, color);
setcolor(color);
circle(x, y, 1);
}
void Remove(int x, int y)
{
putpixel((int)x, y, 0);
setcolor(0);
circle(x, y, 1);
}
};
class RectStar : public StarType
{
void Draw(int x, int y, int color)
{
putpixel((int)x, y, color);
setcolor(color);
rectangle(x - 1, y - 1, x + 1, y + 1);
}
void Remove(int x, int y)
{
putpixel((int)x, y, 0);
setcolor(0);
rectangle(x - 1, y - 1, x + 1, y + 1);
}
};
class XStar : public StarType
{
void Draw(int x, int y, int color)
{
settextcolor(color);
outtextxy(x, y, _T("x"));
}
void Remove(int x, int y)
{
settextcolor(0);
outtextxy(x, y, _T("x"));
}
};
class Star
{
public:
Star(){}
~Star(){}
void Init();
void Init(StarType* pStarType);
void Move();
protected:
void NewPos();
double m_x = 0;
int m_y;
double m_step;
int m_color;
StarType* m_pStarType;
};
void Star::Init()
{
if (m_x == 0)
{
m_x = rand() % SCREEN_WIDTH;
}
else
{
m_x = 0;
}
m_y = rand() % SCREEN_HEIGHT;
m_step = (rand() % 5000) / 1000.0 + 1;
m_color = (int)(m_step * 255 / 6.0 + 0.5); // 速度越快,顏色越亮
m_color = RGB(m_color, m_color, m_color);
}
void Star::Init(StarType* pStarType)
{
this->Init();
m_pStarType = pStarType;
}
void Star::Move()
{
m_pStarType->Remove(m_x, m_y);
NewPos();
m_pStarType->Draw(m_x, m_y, m_color);
}
void Star::NewPos()
{
m_x += m_step;
if (m_x > SCREEN_WIDTH)
this->Init();
}
void main()
{
srand((unsigned)time(NULL));
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
Star star[MAXSTAR];
PointStar pointStar;
RectStar rectStar;
XStar xStar;
for (int i = 0; i < MAXSTAR; i++)
{
switch (i % 3)
{
case 0:
star[i].Init(&pointStar);
break;
case 1:
star[i].Init(&rectStar);
break;
case 2:
star[i].Init(&xStar);
break;
default:
break;
}
}
while (!kbhit())
{
for (int i = 0; i < MAXSTAR; i++)
{
star[i].Move();
}
Sleep(50);
}
closegraph();
}
效果如下:
程序中翎猛,我們定義了一個抽象類StarType胖翰,之后三個類分別派生與這個抽象類。
我們重載了Star類的Init()方法切厘,當我們要實現(xiàn)不同樣子的星星時萨咳,就把相應(yīng)的對象地址通過Init()方法傳給Star類保存在m_pStarType屬性中。當執(zhí)行Move函數(shù)時疫稿,就會通過這個屬性調(diào)用不同的方法來完成某弦。是不是很神奇呢?
這其實是一種設(shè)計模式而克,假如我們有一天需要再多加幾種星星,只需要再多實現(xiàn)幾個StarType的派生類就行了怔毛,其他代碼幾乎不用改變员萍。
這部分重在理解編程的思想,希望大家多多思考拣度,有問題歡迎討論碎绎。
到此,我們這個項目就告一段落了抗果,對于一個稍大的項目而言筋帖,文件劃分非常重要。我會將劃分好的代碼放在GitHub上冤馏,歡迎大家下載源碼學習日麸。
我是天花板,讓我們一起在軟件開發(fā)中自我迭代。
如有任何問題代箭,歡迎與我聯(lián)系墩划。
上一篇:C++代碼訓練營 | 另一片星空