1. 程式人生 > >抽象類,虛函數,純虛函數的意義

抽象類,虛函數,純虛函數的意義

virt 性能 using 容易出錯 知識 浪費 中比 應該 public

  C語言是面向過程的語言,C++是面向對象的語言,區分它們面向什麽的重要區別在於C++比C多個類。那麽在我看來,抽象就是類的升華。

  一般剛學習C++的時候,抽象這個東西給人最大的感覺就是太抽象,很難理解。心裏總是想著,其實這樣或那樣就能解決這個問題了,為什麽要學這個?增加一個抽象類還增加一段代碼,費事不說還不容易理解,所以當時我對抽象還是很抗拒的。但是當工作中真正用到這個的時候,就覺得這個東西真是太好了,任何其它的方案都無法代替抽象。

  為什麽這樣說呢?首先C++是強類型語言,對於一個數組或鏈表來講,它們都只能是一種類型。它不像Python那種弱類型語言,一個列表裏想放什麽類型就放什麽類型。

  假如我有一個小農場,農場裏有5只雞,3只鴨,2頭牛,4頭豬,1只狗。我希望表示出每一只動物的當天的生理狀況和飲食起居。

 1 #include "stdafx.h"
 2 #include <iostream>
 3 #include <list>
 4 using namespace std;
 5 
 6 //每個小動物都有吃飯這個動作,Eat()這個函數判斷當前是否是飯點,如果是的話就吃飯
 7 class Chook
 8 {
 9 public:
10     void Eat() {}
11 };
12 class Duck
13 {
14 public:
15 void Eat() {} 16 }; 17 class Pig 18 { 19 public: 20 void Eat() {} 21 }; 22 class Cow 23 { 24 public: 25 void Eat() {} 26 }; 27 class Dog 28 { 29 public: 30 void Eat() {} 31 }; 32 33 34 int main() 35 { 36 list<Chook> chookList; 37 list<Duck> duckList; 38 list<Pig> pigList;
39 list<Cow> cowList; 40 list<Dog> dogList; 41 42 /* 43 Chook chook; 44 chookList.push_back(chook); 45 …… 46 將每一種小動物添加到對應的鏈表中 47 */ 48 49 while (1) 50 { 51 for (auto p = chookList.begin();p != chookList.end();p++) 52 { 53 p->Eat(); 54 } 55 for (auto p = duckList.begin();p != duckList.end();p++) 56 { 57 p->Eat(); 58 } 59 for (auto p = pigList.begin();p != pigList.end();p++) 60 { 61 p->Eat(); 62 } 63 for (auto p = cowList.begin();p != cowList.end();p++) 64 { 65 p->Eat(); 66 } 67 for (auto p = dogList.begin();p != dogList.end();p++) 68 { 69 p->Eat(); 70 } 71 } 72 73 return 0; 74 }

  在這個循環中,每一個鏈表都得遍歷一次,並調用當前小動物的Eat()函數,麻煩不說,還容易出錯。在這個例子中,無論是什麽動物,他們的本質都是動物,他們都有吃喝拉撒睡的動作,C++給我們提供了一個抽象的功能,可以把這些具體動物的類抽象成一個概括這些動物的父類。

  以下為知識點:

  在C++中,如果一個類中包含純虛函數的話,那麽這個類就是抽象類。純虛函數用下面的方法來表示。

1 class A
2 {
3 public:
4     virtual void Fun(int a, double b) = 0;
5 };

  成員函數Fun的返回值和參數都是任意的,需要用virtual來修飾變成虛函數,最後加上“=0”變成了純虛函數。純虛函數不能實現具體功能,只能由繼承它的子類來實現。抽象類不能實例化對象,也就是說下面的寫法是錯誤的。

1     A a;    //錯!抽象類不能實例化對象

  如果子類A1想繼承A,就跟正常的繼承方法是一樣的。有一點需要註意,如果A1沒有全部實現A中的純虛函數,那麽A1也是抽象類,也不能實例化。所以如果想讓A1能夠實例化,那麽就必須全部實現A中的純虛函數。

  說完了純虛函數,那麽虛函數的作用是什麽呢?虛函數就是現在父類中實現好了,如果子類能用的上就用,用不上自己重新實現一個就好了。

  了解了抽象,虛函數和純虛函數的意義,我們看一下上邊的農場應該怎麽用這個方法來實現。

 1 #include "stdafx.h"
 2 #include <iostream>
 3 #include <list>
 4 using namespace std;
 5 
 6 //每個小動物都有吃飯這個動作,Eat()這個函數判斷當前是否是飯點,如果是的話就吃飯
 7 class Animal
 8 {
 9 public:
10     Animal();
11     virtual ~Animal() {}
12 
13     virtual void Eat() = 0;
14     virtual void Drink() = 0;
15     virtual void Sleep() 
16     {
17         //每天晚上睡
18     }
19 
20 protected:
21     int weight; //體重
22 
23 private:
24 };
25 
26 class Chook :public Animal
27 {
28 public:
29     virtual void Eat() {}
30     virtual void Drink() {}
31 };
32 class Duck :public Animal
33 {
34 public:
35     virtual void Eat() {}
36     virtual void Drink() {}
37 };
38 class Pig :public Animal
39 {
40 public:
41     virtual void Eat() {}
42     virtual void Drink() {}
43     virtual void Sleep()
44     {
45         //吃完了就睡
46     }
47 };
48 class Cow :public Animal
49 {
50 public:
51     virtual void Eat() {}
52     virtual void Drink() {}
53 };
54 class Dog :public Animal
55 {
56 public:
57     virtual void Eat() {}
58     virtual void Drink() {}
59 };
60 
61 int main()
62 {
63     list<Animal*> animalList;
64 
65     for (int i = 0; i < 5; i++)
66     {
67         Animal* p = new Chook();
68         animalList.push_back(p);
69     }
70     /*
71     將每一種小動物添加到對應的鏈表中
72     */
73 
74     while (1)
75     {
76         for (auto pNode = animalList.begin(); pNode != animalList.end(); pNode++)
77         {
78             (*pNode)->Eat();
79             (*pNode)->Drink();
80             (*pNode)->Sleep();
81         }
82     }
83 
84     for ( auto pNode = animalList.begin(); pNode != animalList.end(); pNode++)
85     {
86         delete *pNode;
87     }
88 
89     return 0;
90 }

  從第7行開始,我們定義了一個Animal的抽象類,因為每個動物吃的喝的都不一樣,所以把Eat和Drink定義成了純虛函數,等到它的子類去實現。但是睡覺的話,大多數動物應該都是晚上睡的,所以定義了一個虛函數Sleep,實現為晚上睡覺,只有豬那個類裏重新定義了Sleep這個函數,因為豬是吃完了就睡。

  使用的時候也很簡單,定義一個Animal*類型的鏈表(也可以定義為數組),可以裝載不同小動物的指針,通過指針調用函數的時候,你的程序會自動的分辨出它屬於哪個類,並調用了對應的函數。

  其實在這個簡短的例子當中,並不能真切的感受到抽象的好處,因為這個例子給人最直觀的感覺就是調用的代碼沒有少多少,抽象出來的父類卻增加了一些麻煩。但是我使用C++這些年的感覺就是,C++沒有一個特性是多余的。如果你一開始因為不了解這些特性而使用自己認為還可以的方法去實現一個功能,慢慢的你就會為這個做法浪費越來越多的時間去彌補。

  舉個在工業控制軟件中比較常見的例子。你的設備中有一個工業相機,你的軟件就需要調用這個相機的API來使相機工作。但是你的領導突然有一天說,他發現另一款相機性能差不多,但是價格便宜許多,要換相機!如果你一直在你的主程序中直接調用原相機的API函數,那換相機對你來講將是一個噩夢,改代碼的面積大不說,還會增加不少的bug。使用抽象來解決這個問題就容易很多,可以單獨為相機這個抽象類做一個接口,然後讓它不同的子類具體去調用不同相機的API函數。而你主程序裏所有關於相機的操作,都來調用這些個接口函數,這時問題迎刃而解!

抽象類,虛函數,純虛函數的意義