欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

C++中的虚函数

时间:2023-06-06

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

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

虚拟功能规则

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

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

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

// CPP program to illustrate// concept of Virtual Functions#includeusing namespace std;class base {public:virtual void print(){cout << "print base classn";}void show(){cout << "show base classn";}};class derived : public base {public:void print(){cout << "print derived classn";}void show(){cout << "show derived classn";}};int main(){base *bptr;derived d;bptr = &d;// Virtual function, binded at runtimebptr->print();// Non-virtual function, binded at compile timebptr->show();return 0;}

输出:

打印派生类显示基类

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

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

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

考虑下面的例子:

// CPP program to illustrate// working of Virtual Functions#includeusing namespace std;class base {public:void fun_1() { cout << "base-1n"; }virtual void fun_2() { cout << "base-2n"; }virtual void fun_3() { cout << "base-3n"; }virtual void fun_4() { cout << "base-4n"; }};class derived : public base {public:void fun_1() { cout << "derived-1n"; }void fun_2() { cout << "derived-2n"; }void fun_4(int x) { cout << "derived-4n"; }};int main(){base *p;derived obj1;p = &obj1;// Early binding because fun1() is non-virtual// in basep->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() 不同,因为这两个函数的原型不同。

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。