编程/C++
虚函数| modified | Tuesday 1 July 2025 |
|---|
C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要再派生类中声明该方法为虚方法。
在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接(又称早绑定:基类定义的函数没有使用 virtual 关键字,调用的函数被编译器设置为基类中的版本)到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
虚函数只能借助于指针或者引用来达到多态的效果。
当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,[即 B b; A a = b;] 父类指针根据赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数(不使用 virtual 方法,如果使用了virtual关键字,程序将根据引用或指针指向的 对象类型来选择方法,否则使用引用类型或指针类型来选择方法。),且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编。
1class A
2{
3public:
4void fee()
5{
6 cout<<"Parent"<<endl;
7}
8virtual void foo()
9{
10 cout<<"A::foo() is called"<<endl;
11}
12};
13class B:public A
14{
15public:
16void fee()
17{
18 cout<<"Child"<<endl;
19}
20virtual void foo()
21{
22 cout<<"B::foo() is called"<<endl;
23}
24};
25
26int main(void)
27{
28A *a = new B();
29a->fee(); // 输出为:Parent
30a->foo(); // 输出为:B::foo() is called
31return 0;
32}
编译器处理虚函数的方法是:
为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。
基类对象包含一个虚表指针,指向基类中所有虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表。
如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。
如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址。注意,如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”:virtual void funtion1()=0
声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。派生类仅仅只是继承函数的接口,必须实现函数才能使用。
象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
抽象类的定义:称带有纯虚函数的类为抽象类。
抽象类的作用:抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以抽象类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
使用抽象类时注意:
在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。