C++如何理解和使用虚函数和运行时多态?(用法示例+图解)|S1

2021年3月27日15:26:11 发表评论 603 次浏览

本文概述

本文介绍了多态性和虚函数的概念, 以及它们在继承中的使用。

我们将介绍以下内容

  • 定义
  • 没有虚函数
  • 使用虚函数

定义:

  1. 虚函数是在基类中使用关键字virtual声明并由派生类重新定义(重写)的成员函数。
  2. 术语多态性意味着采取多种形式的能力。如果存在通过继承相互关联的类的层次结构,则会发生这种情况。
C++中的虚函数和运行时多态(简介)1

类层次结构

注意:在C++中, 这意味着如果我们调用成员函数, 则可能导致执行不同的函数, 具体取决于调用它的对象类型。

考虑将下面的简单程序作为运行时多态性的示例。关于这个程序要注意的主要事情是,派生类的函数是使用基类指针调用的。

虚函数的调用是根据指向或引用的对象实例的类型,而不是根据指针或引用的类型。

换句话说, 虚函数在运行时被延迟解析。

现在, 我们来看一个使用这两个概念的示例, 以阐明你的理解。

C++

#include <iostream>
using namespace std;
 
// Base class
class Shape
{
public :
     Shape( int l, int w)
     {
         length = l;
         width = w;
     } // default constructor
     int get_Area()
     {
         cout << "This is call to parent class area" << endl;
     }
 
protected :
     int length, width;
};
 
// Derived class
class Square : public Shape
{
public :
     Square( int l = 0, int w = 0)
         : Shape(l, w)
     {
     } // declaring and initializing derived class
       // constructor
     int get_Area()
     {
         cout << "Square area: " << length * width << endl;
         return (length * width);
     }
};
// Derived class
class Rectangle : public Shape
{
public :
     Rectangle( int l = 0, int w = 0)
         : Shape(l, w)
     {
     } // declaring and initializing derived class
       // constructor
     int get_Area()
     {
         cout << "Rectangle area: " << length * width
              << endl;
         return (length * width);
     }
};
 
int main( void )
{
     Shape* s;
     Square sq(5, 5); // making object of child class Sqaure
     Rectangle rec(
         4, 5); // making object of child class Rectangle
 
     s = &sq;
     s->get_Area();
     s = &rec;
     s->get_Area();
 
     return 0;
}

输出如下

This is call to parent class area
This is call to parent class area

在以上函数中:

  • 我们存储每个子类的地址长方形和广场对象在s和
  • 然后我们称get_Area()函数,
  • 理想情况下, 它应该已经get_Area()子类的函数, 但
  • 相反, 它称为get_Area()在基类中定义。
  • 发生这种情况是由于静态链接, 这意味着调用get_Area()由基类中的编译器仅设置一次。

有什么用?

虚函数使我们可以创建基类指针列表和任何派生类的调用方法, 而无需知道派生类对象的种类。

例如:考虑组织的员工管理软件。

让代码有一个简单的基类Employee,该类包含虚拟函数,如raiseSalary()、transfer()、promote()等。不同类型的雇员,如Manager、Engineer等,可能有自己的基类Employee中虚拟函数的实现。

在我们完整的软件中,我们只需要到处传递一个员工列表,并调用适当的函数,甚至不知道员工的类型。例如,我们可以通过遍历员工列表轻松地提高所有员工的工资。每种类型的雇员在其类中都可能有自己的逻辑,但我们不需要担心它们,因为如果针对特定的雇员类型出现了raiseSalary(),则只会调用该函数。

CPP

class Employee {
public :
     virtual void raiseSalary()
     {
         /* common raise salary code */
     }
 
     virtual void promote() { /* common promote code */ }
};
 
class Manager : public Employee {
     virtual void raiseSalary()
     {
         /* Manager specific raise salary code, may contain
           increment of manager specific incentives*/
     }
 
     virtual void promote()
     {
         /* Manager specific promote */
     }
};
 
// Similarly, there may be other types of employees
 
// We need a very simple function
// to increment the salary of all employees
// Note that emp[] is an array of pointers
// and actual pointed objects can
// be any type of employees.
// This function should ideally
// be in a class like Organization, // we have made it global to keep things simple
void globalRaiseSalary(Employee* emp[], int n)
{
     for ( int i = 0; i < n; i++)
 
         // Polymorphic Call: Calls raiseSalary()
         // according to the actual object, not
         // according to the type of pointer
         emp[i]->raiseSalary();
}

与globalRaiseSalary()类似,可以在不知道对象实例类型的情况下对一组员工执行许多其他操作。

虚函数非常有用,Java等后来的语言默认将所有方法都保持为虚函数。

编译器如何执行运行时解析?

编译器维护两件事来实现此目的:

  1. vtable:每个类维护的函数指针表。
  2. vptr:指向vtable的指针, 针对每个对象实例进行维护(请参见这个例如)。
C++中的虚函数和运行时多态(简介)2

编译器在两个地方添加了额外的代码来维护和使用vptr。

1)每个构造函数中的代码。这段代码设置要创建的对象的vptr。这段代码将vptr设置为指向类的虚函数表。

2)带有多态函数调用的代码(如上面代码中的bp->show())。无论在哪里进行多态调用,编译器都会先插入代码,使用基类指针或引用来查找vptr(在上面的例子中,因为指向或引用的对象是派生类型的,所以要访问派生类的vptr)。一旦获取了vptr,就可以访问派生类的虚函数表。使用vtable访问和调用派生类函数show()的地址。

这是在C++中实现运行时多态性的标准方法吗?

C++标准没有明确规定必须如何实现运行时多态性, 但是编译器通常在同一基本模型上使用较小的变体。

参考文献:

http://en.wikipedia.org/wiki/Virtual_method_table

http://en.wikipedia.org/wiki/Virtual_function

http://www.drbio.cornell.edu/pl47/programming/TICPP-2nd-ed-Vol-one-html/Frames.html

如果发现任何不正确的地方, 或者想分享有关上述主题的更多信息, 请发表评论。

被认为是行业中最受欢迎的技能之一, 我们拥有自己的编码基础C++ STL通过激烈的问题解决过程来训练和掌握这些概念。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: