C++学习 之 类的继承中的虚函数(笔记)
1.多态行为
多态是面向对象语言的一种特征,让我们能够以类似的方式处理不同类型的对象。在C++中我们可以通过继承层次结构实现子类型多态。
我们可以通过下面的代码进一步了解多态:
#include<iostream> using namespace std; class Fish { public: void FishSwim() { cout << "Fish swim in..." << endl; } }; class Tuna :public Fish { public: void FishSwim() { cout << "Tuna swim in..." << endl; } }; void MakeFishSwim(Fish &InputFish) { InputFish.FishSwim(); } int main() { Tuna myDinner;
myDinner.FishSwim(); MakeFishSwim(myDinner); return 0; }
从上面代码的运行结果来看我们定义了Tuna类并继承了Fish类的同名方法FishSwim;我们在函数MakeFishSwim中调用FishSwim并希望调用的是Tuna类中的该方法,但结果却调用了Fish类中的(因为在函数MakeFishSwim的参数是Fish类的)。尽管MakeFishSwim的参数类型是Fish类,为了能够完成Tuna类对象调用就执行Tuna类的方法Fish类的对象调用就执行Fish类的方法的多态行为,我们引入虚函数。
2.使用虚函数实现多态行为
虚函数实现多态行为代码示例:
#include<iostream> using namespace std; class Fish { public: virtual void FishSwim() { cout << "Fish swim in..." << endl; } }; class Tuna :public Fish { public: void FishSwim() { cout << "Tuna swim in..." << endl; } }; class Carp :public Fish { public: void FishSwim() { cout << "Carp swim in..." << endl; } }; void MakeFishSwim(Fish &InputFish) { InputFish.FishSwim(); } int main() { Fish myFish; Tuna myDinner; Carp myLunch; MakeFishSwim(myFish); MakeFishSwim(myDinner); MakeFishSwim(myLunch); return 0; }
由上面的代码和执行结果可以得出:引入虚函数实现了Fish类的对象调用MakeFishSwim函数就执行Fish类的方法、Tuna类对象调用就执行Tuna类的方法、Carp类的对象调用就执行Carp类的方法的多态行为。
3.虚构造函数
当某个函数为基类指针参数,其派生类的指针对象调用该函数并在该函数中使用delete释放使用new为派生类的指针对象分配的空间时,便可能导致内存泄漏、资源未释放等问题。
我们可以采用基类虚析构函数来实现调用派生类对象本身的析构函数,代码如下:
#include<iostream> using namespace std; class Fish { public: Fish() { cout << "construct Fish"<< endl; } virtual ~Fish() { cout << "destroy Fish" << endl<<endl; } }; class Tuna :public Fish { public: Tuna() { cout << "construct Tuna" << endl<<endl; } ~Tuna() { cout << "destroy Tuna" << endl; } }; void DeleteFishSwim(Fish *InputFish) { delete InputFish; } int main() { cout << "create:"<<endl; Tuna* myDinner = new Tuna; cout << "delete:" << endl; DeleteFishSwim(myDinner); cout << "create:"<<endl; Tuna Dinner; cout << "delete:"<<endl; return 0; }
当我们把基类Fish里的析构函数前去掉虚函数关键字virtual,就会发现结果中使用delete释放*myDinner所指向的空间时只调用了Fish的析构函数(因为函数DeleteFishSwim认为InputFish为基类Fish的对象)。这样便会导致Tuna对象*myDinner所指向的空间中属于Tuna类的内存块部分没有被释放,所以我们在这种情况下需要采用虚析构函数。
4.抽象基类和虚函数
虚函数的声明语法类似如下:
class Base { public: virtual void DoSomething()=0; }; class Drived:public Base { public: void DoSomething() { cout<<"virtual function"<<endl; } };
语法中"virtual void DoSomething()=0;"纯虚函数的声明语句。我们在基类中声明了纯虚函数DoSomething后,就要求其每个派生类中必须有DoSomething的实现。抽象基类使得我们能够声明每个派生类都必须实现的函数。
5.虚继承解决继承中菱形问题
继承中菱形问题:Animal类派生出Mammal、Bird、Reptile类;Platypus类由Mammal、Bird、Reptile派生出来;这便形成了继承中的菱形问题。Platypus类继承了Mammal类、Bird类、Reptile类;如果我们在Mammal类、Bird类、Reptile类继承Animal类时没有声明为虚继承时就会导致Mammal类、Bird类、Reptile类分别继承一个属于自己的Animal类(关系为非菱形);如果我们在Mammal类、Bird类、Reptile类继承Animal类时声明为虚继承时就会使得Mammal类、Bird类、Reptile类继承了同一个Animal类(关系为菱形)。
非菱形关系继承:
#include<iostream> using namespace std; class Animal { public: int Age=0; Animal() { cout << "Animal constructor" << endl; } }; class Mammal : public Animal { }; class Bird : public Animal { }; class Reptile : public Animal { }; class Platypus : public Mammal, public Bird, public Reptile { public: Platypus() { cout << "Platypus constructor" << endl; } }; int main() { Platypus duckBilledP; //duckBilledP.Age = 25; return 0; }
非菱形关系继承不仅导致内存占用提升,还使得Platypus的对象存在三个Age值,要想访问Platypus的Age值要使用"::"说明是从Mammal类、Bird类、Reptile类中的哪个类继承的Age(如果去掉main函数里的注释符号,会有编译错误)。实际上Platypus拥有三个Age值一般也是我们不想看到的。
菱形关系继承:
#include<iostream> using namespace std; class Animal { public: int Age=0; Animal() { cout << "Animal constructor" << endl; } }; class Mammal :virtual public Animal { }; class Bird :virtual public Animal { }; class Reptile :virtual public Animal { }; class Platypus :public Mammal, public Bird, public Reptile { public: Platypus() { cout << "Platypus constructor" << endl; } }; int main() { Platypus duckBilledP; duckBilledP.Age = 25; return 0; }
菱形关系继承可以使得Platypus从Animal类中继承唯一的Age值。