C++中的虚函数

虚函数是在基类中声明并由派生类重新定义(覆盖)的成员函数。当您使用指针或对基类的引用来引用派生类对象时,您可以为该对象调用虚函数并执行派生类的函数版本。

  • 虚函数确保为对象调用正确的函数,而不管用于函数调用的引用(或指针)的类型。
  • 它们主要用于实现运行时多态性
  • 函数在基类中使用virtual关键字声明。
  • 函数调用的解析是在运行时完成的。

虚拟功能规则

  1. 虚函数不能是静态的。
  2. 虚函数可以是另一个类的友元函数。
  3. 应该使用基类类型的指针或引用来访问虚函数,以实现运行时多态性。
  4. 虚函数的原型在基类和派生类中应该是相同的。
  5. 它们总是在基类中定义并在派生类中被覆盖。派生类不需要重写(或重新定义虚函数),在这种情况下,使用函数的基类版本。
  6. 一个类可以有虚拟析构函数,但不能有虚拟构造函数。

Virtual Functions 的编译时(早期绑定)VS 运行时(后期绑定)行为

考虑以下显示虚函数运行时行为的简单程序。

// CPP program to illustrate
// concept of Virtual Functions

#include<iostream>
using namespace std;

class base {
public:
	virtual void print()
	{
		cout << "print base class\n";
	}

	void show()
	{
		cout << "show base class\n";
	}
};

class derived : public base {
public:
	void print()
	{
		cout << "print derived class\n";
	}

	void show()
	{
		cout << "show derived class\n";
	}
};

int main()
{
	base *bptr;
	derived d;
	bptr = &d;

	// Virtual function, binded at runtime
	bptr->print();

	// Non-virtual function, binded at compile time
	bptr->show();

	return 0;
}

输出:

打印派生类
显示基类

**说明:**运行时多态性只能通过基类类型的指针(或引用)来实现。此外,基类指针既可以指向基类的对象,也可以指向派生类的对象。在上面的代码中,基类指针“bptr”包含派生类的对象“d”的地址。
后期绑定(运行时)根据指针的内容(即指针指向的位置)进行,早期绑定(编译时)根据指针的类型进行,因为 print() 函数是用 virtual 关键字声明的,所以它将在运行时绑定(输出是打印派生类,因为指针指向派生类的对象)并且 show() 是非虚拟的,因此它将在编译时绑定(输出是显示基类因为指针是基本类型)。
**注意:**如果我们在基类中创建了一个虚函数,并且它在派生类中被覆盖,那么我们不需要在派生类中使用 virtual 关键字,函数在派生类中被自动视为虚函数。

虚函数的工作(VTABLE 和 VPTR 的概念)正如这里
所讨论的,如果一个类包含一个虚函数,那么编译器本身会做两件事。

  1. 如果创建了该类的对象,则插入一个**虚拟指针 (VPTR)**作为该类的数据成员以指向该类的 VTABLE。对于创建的每个新对象,都会插入一个新的虚拟指针作为该类的数据成员。
  2. 无论是否创建了对象,类都包含一个名为 VTABLE 的静态函数指针数组作为成员。该表的单元存储该类中包含的每个虚函数的地址。

考虑下面的例子:

img

// CPP program to illustrate
// working of Virtual Functions
#include<iostream>
using namespace std;

class base {
public:
	void fun_1() { cout << "base-1\n"; }
	virtual void fun_2() { cout << "base-2\n"; }
	virtual void fun_3() { cout << "base-3\n"; }
	virtual void fun_4() { cout << "base-4\n"; }
};

class derived : public base {
public:
	void fun_1() { cout << "derived-1\n"; }
	void fun_2() { cout << "derived-2\n"; }
	void fun_4(int x) { cout << "derived-4\n"; }
};

int main()
{
	base *p;
	derived obj1;
	p = &obj1;

	// Early binding because fun1() is non-virtual
	// in base
	p->fun_1();

	// Late binding (RTP)
	p->fun_2();

	// Late binding (RTP)
	p->fun_3();

	// Late binding (RTP)
	p->fun_4();

	// Early binding but this function call is
	// illegal (produces error) because pointer
	// is of base type and function is of
	// derived class
	// p->fun_4(5);

	return 0;
}

输出:

基数 1
派生 2
基数 3
基数 4

**说明:**最初,我们创建一个基类类型的指针,并用派生类对象的地址对其进行初始化。当我们创建派生类的对象时,编译器会创建一个指针作为类的数据成员,其中包含派生类的 VTABLE 的地址。

与上面的示例一样,使用了类似的后期和早期绑定概念。对于 fun_1() 函数调用,函数的基类版本被调用, fun_2() 在派生类中被覆盖,因此派生类版本被调用, fun_3() 在派生类中未被覆盖并且是虚函数,因此被调用基类版本,同样 fun_4() 没有被覆盖,所以基类版本被调用。

**注意:**派生类中的 fun_4(int) 与基类中的虚函数 fun_4() 不同,因为这两个函数的原型不同。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

糖果Autosar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值