C++中的虚函数、虚函数表、虚函数指针

发布于:2025-06-18 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、虚函数

虚函数是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();  // 输出:刀客使出了小李飞刀
  1. ptr1和ptr2虽然都是武林高手类型的指针,但它们分别指向剑客和刀客对象。
  2. 当调用使出绝招()时,程序会通过对象的虚函数指针找到对应的虚函数表。​
  3. 从虚函数表中获取并调用实际的函数实现。

网站公告

今日签到

点亮在社区的每一天
去签到