一、虚函数
虚函数是C++中多态的一种表现形式,具体表现如下,Virtual+函数名
class KuMaster {
public:
virtual void UniqueSkill() {
cout << "武林高手使出了基础招式" << endl;
}
};
然后再写出两个子类来继承上述的基类,虚函数的目的之一:可以通过基类指针来调用子类函数
class SwordsMain : public KuMaster {
public:
void UniqueSkill() override {
cout << "剑客使出了苍龙出海" << endl;
}
};
class KnifeMan : public KuMaster {
public:
void UniqueSkill() override {
cout << "刀客使出了小李飞刀" << endl;
}
};
关于虚函数的调用,可以参考如下的栗子,测试下效果:
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base constructor" << endl;
}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual ~Base()//虚析构函数
{
cout <<"Base destructor"<< endl;
}
};
class Derived :public Base
{
public:
Derived()
{
cout << "Derived constructor" << endl;
}
virtual void func1()
{
cout << "Derived::func1()" << endl;
}
virtual void show()
{
cout << "Derived class show" << endl;
}
~Derived()
{
cout << "Derived destructor" << endl;;
}
};
int main()
{
Base* b = new Derived;
b->func1();
delete b;//正确调用派生类析构函数
return 0;
}
这里发现实现的结果是下面这样,(顺便复习下继承中构造函数与析构函数的调用顺序)
Base constructor
Derived constructor
Derived::func1()
Derived destructor
Base destructor
二、虚函数表
虚函数表可以看成一本“武功秘籍”。这个表里存储了一个类中所有虚函数的地址(注意是一个类中),所以这里要关注下,注意:同一类的所有对象共享这张虚函数表
可以举例说明下:
class Animal {
public:
virtual void speak() {
std::cout << "Animal makes a sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Dog barks" << std::endl;
}
};
class Cat : public Animal {
public:
void speak() override {
std::cout << "Cat meows" << std::endl;
}
};
int main()
{
Cat cat1;
Cat cat2;
Animal* ptr1 = &cat1;
Animal* ptr2 = &cat2;
ptr1->speak();
ptr2->speak();
return 0;
}
cat1、cat2 虚函数指针都指向同一张Cat类的虚函数表,当通过基类指针ptr1、ptr2调用speak函数时,程序会依据对象中的虚函数指针找到共享的虚函数表,
进而调用到Cat::speak()函数
虚函数表的具体实现,可以举例说明:
当派生类(牛肉类)重写基类(肉类)的虚函数时,派生类(牛肉类)的虚函数表中相应位置的函数指针会被更新为
指向派生类(牛肉类)的函数。
如果派生类(牛肉类)没有重写虚函数,则派生类(牛肉类)的虚函数表中会保留指向基类(肉类)虚函数的指针
#include<iostream>
using namespace std;
class Meat {
public:
virtual void cookMeat() { cout << "将肉用锅煮" << endl; }
virtual void sautedMeat() { cout << "将肉用火炒" << endl; }
};
class Beef : public Meat {
public:
virtual void cookMeat() override { cout << "番茄土豆炖牛腩" << endl; }
virtual void beefSteak() { cout << "三分熟牛排" << endl; }
};
//定义一种 返回值为void,形参为void,这种类型的函数指针
//并为这种指针取别名为pCook
//typedef 返回值类型 (*别名)(形参类型);
typedef void(*pCook)(void);//定义函数指针类型,指向函数后可直接调用
int main()
{
Meat pb;
Beef pd;
//获取虚表指针,类对象第一个指针大小的内存的值
unsigned long* vptr_Meat = (unsigned long*)(*((unsigned long*)(&pb)));
unsigned long* vptr_Beef = (unsigned long*)*(unsigned long*)&pd;
//打印虚函数表各个虚函数的地址
cout << "父类虚表指针:" << vptr_Meat << endl;
for (int i = 0; i < 2; i++)
{
cout << "Meat虚函数表第" << i + 1 << "个虚函数地址:" << *vptr_Meat << endl;
pCook pcook = (pCook)*vptr_Meat;
pcook();
vptr_Meat++;
}
cout << endl;
cout << "子类虚表指针:" << vptr_Beef << endl;
for (int i = 0; i < 3; i++)
{
cout << "Beef虚函数表第" << i + 1 << "个虚函数地址:" << *vptr_Beef << endl;
pCook pcook = (pCook)*vptr_Beef;
pcook();
vptr_Beef++;
}
return 0;
}
最终实现效果为:
父类虚表指针:00219B34
Meat虚函数表第1个虚函数地址:2168109
将肉用锅煮
Meat虚函数表第2个虚函数地址:2168119
将肉用火炒
子类虚表指针:00219CDC
Beef虚函数表第1个虚函数地址:2168124 (由于重写之后,直接指向的就是子类的)
番茄土豆炖牛腩
Beef虚函数表第2个虚函数地址:2168119(子类没有重写的父类的虚函数依然会,虚函数表会保留指向基类)
将肉用火炒
Beef虚函数表第3个虚函数地址:2168104
三分熟牛排
三、运行时的多态性
具体调用如下:
KuMaster* ptr1 = new SwordsMain();
KuMaster* ptr2 = new KnifeMan();
ptr1->UniqueSkill(); // 输出:剑客使出了苍龙出海
ptr2->UniqueSkill(); // 输出:刀客使出了小李飞刀
- ptr1和ptr2虽然都是武林高手类型的指针,但它们分别指向剑客和刀客对象。
- 当调用使出绝招()时,程序会通过对象的虚函数指针找到对应的虚函数表。
- 从虚函数表中获取并调用实际的函数实现。