抽象类,虚函数,纯虚函数的意义
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函数。而你主程序里所有关于相机的操作,都来调用这些个接口函数,这时问题迎刃而解!