虚函数是在基类中声明并由派生类重新定义(重写)的成员函数。当您使用指针或对基类的引用来引用派生类对象时,可以为该对象调用一个虚函数并执行该派生类的函数版本。
- 虚函数可确保为对象调用正确的函数,而与用于函数调用的引用(或指针)的类型无关。
- 它们主要用于实现运行时多态
- 函数在基类中使用虚拟关键字声明。
- 函数调用的解析在运行时完成。
虚拟功能规则
- 虚函数不能是静态的。
- 虚函数可以是另一个类的朋友函数。
- 应该使用基类类型的指针或引用来访问虚拟函数,以实现运行时多态。
- 虚拟函数的原型在基类和派生类中都应相同。
- 它们始终在基类中定义,并在派生类中被覆盖。不必强制派生类重写(或重新定义虚函数),在这种情况下,将使用该函数的基类版本。
- 一个类可能具有虚拟析构函数,但不能具有虚拟构造函数。
虚函数的编译时(早期绑定)与运行时(后期绑定)行为
考虑以下显示虚拟功能运行时行为的简单程序。
CPP
// CPP program to illustrate
// concept of Virtual Functions
#include
using namespace std;
class base {
public:
virtual void print()
{
cout << "print base class" << endl;
}
void show()
{
cout << "show base class" << endl;
}
};
class derived : public base {
public:
void print()
{
cout << "print derived class" << endl;
}
void show()
{
cout << "show derived class" << endl;
}
};
int main()
{
base* bptr;
derived d;
bptr = &d;
// virtual function, binded at runtime
bptr->print();
// Non-virtual function, binded at compile time
bptr->show();
}
CPP
// CPP program to illustrate
// working of Virtual Functions
#include
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) becasue pointer
// is of base type and function is of
// derived class
// p->fun_4(5);
}
输出:
print derived class
show base class
说明:运行时多态只能通过基类类型的指针(或引用)来实现。同样,基类指针可以指向基类的对象以及派生类的对象。在上面的代码中,基类指针bptr包含派生类的对象d的地址。
后期绑定(运行时)是根据指针的内容(即指针指向的位置)完成的,而早期绑定(编译时)是根据指针的类型完成的,因为print()函数是使用virtual关键字声明的,因此将在运行时绑定(输出是打印派生类,因为指针指向派生类的对象),而show()是非虚拟的,因此它将在编译时绑定(输出是显示基类,因为指针是基类的)类型 )。
注意:如果我们在基类中创建了虚函数,并且在派生类中将其重写,则在派生类中不需要virtual关键字,则函数会自动视为派生类中的虚函数。
虚拟功能的工作(VTABLE和VPTR的概念)
如此处所讨论的,如果一个类包含一个虚函数,那么编译器本身会做两件事:
- 如果创建了该类的对象,则将虚拟指针(VPTR)作为该类的数据成员插入,以指向该类的VTABLE。对于创建的每个新对象,将插入一个新的虚拟指针作为该类的数据成员。
- 无论是否创建对象,函数指针的静态数组都称为VTABLE ,其中每个单元格包含该类中包含的每个虚函数的地址。
考虑下面的示例:
CPP
// CPP program to illustrate
// working of Virtual Functions
#include
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) becasue pointer
// is of base type and function is of
// derived class
// p->fun_4(5);
}
输出:
base-1
derived-2
base-3
base-4
说明:最初,我们创建一个基类类型的指针,并使用派生类对象的地址对其进行初始化。当我们创建派生类的对象时,编译器会创建一个指针,作为包含该派生类的VTABLE地址的类的数据成员。
上例中使用了后期绑定和早期绑定的类似概念。对于fun_1()函数调用,函数基类版本被调用,fun_2()被覆盖在派生类所以派生类版本被调用,fun_3()不是在派生类中重写和是虚函数所以基类版本被调用时,同样,fun_4()不会被覆盖,因此将调用基类版本。