当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


C++ Virtual用法及代码示例


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

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

Rules for Virtual Functions

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

Compile-time(早期绑定)与虚拟函数的运行时(后期绑定)行为

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

// CPP program to illustrate 
// concept of Virtual Functions 
  
#include <iostream> 
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(); 
}

输出:

print derived class
show base class

说明:运行时多态只能通过基类类型的指针(或引用)来实现。同样,基类指针可以指向基类的对象以及派生类的对象。在上面的代码中,基类指针“ bptr”包含派生类的对象“ d”的地址。

延迟绑定(运行时)是根据指针的内容(即指针指向的位置)完成的,而早期绑定(编译时)是根据指针的类型完成的,因为print()函数是用virtual关键字声明的,因此它将在运行时绑定(输出是打印派生类,因为指针指向派生类的对象),并且show()是非虚拟的,因此它将在编译时绑定(输出是显示基类,因为指针是基类型的)。

注意:如果我们在基类中创建了一个虚函数,并且在派生类中对其进行了覆盖,那么在派生类中就不需要virtual关键字,则函数会自动被视为派生类中的虚函数。

Working of virtual functions(concept of VTABLE and VPTR)

如此处所讨论的,如果一个类包含一个虚函数,那么编译器本身会做两件事:

  1. 如果创建了该类的对象,则将虚拟指针(VPTR)作为该类的数据成员插入,以指向该类的VTABLE。对于创建的每个新对象,将插入一个新的虚拟指针作为该类的数据成员。
  2. 不管是否创建对象,函数指针的静态数组都称为VTABLE,其中每个单元格包含该类中包含的每个虚函数的地址。

考虑下面的示例:

// 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) 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()不会被覆盖,因此将调用基类版本。

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




注:本文由纯净天空筛选整理自 Virtual Function in C++。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。