深入解析C++中的虚函数与多态

1.C++中的虚函数
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)和一个指向虚函数表的指针(vptr)来实现的。虚函数表,简称为vtbl,虚函数表表对实现多态起着至关重要的作用。在这个表中,主要保存了一个类中的虚函数的地址,这张表解决了继承、覆盖的问题,保证其内容能真实反应实际的函数。每一个包含虚函数的类的实例都包含一个cptr指针,指向虚函数表的首地址。我们可以通过这个指针找到要访问的虚函数的,完成虚函数的调用主要包括:找到虚函数表的首地址(vptr),通过cptr找到要使用虚函数地址,调用虚函数。那么使用虚函数大家总要考虑效率的问题,实际上为了提高效率,C++的编译器是保证虚函数表的指针存在于对象实例中最前面的位置,这是为了保证取到虚函数表的有最高的性能,这意味着我们通过对象实例的地址得到这张虚函数表,然后通过遍历表就可以找到其中的虚函数的地址,然后调用相应的函数。不妨看看下面的代码:


代码如下:

#include <iostream>

using namespace std;

class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};

typedef void(*Fun)(void);

int main()
{
    Base b;
    Fun pFun = NULL;
    cout << "虚函数表地址:" << (int*)(&b) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
    pFun = (Fun)*((int*)*(int*)(&b));
    pFun();
    return 0;
}

通过这个示例,可以看到,通过强行把&b转成int *,取得虚函数表的地址(vptr),然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:


代码如下:

(Fun)*((int*)*(int*)(&b)+0); // Base::f()

(Fun)*((int*)*(int*)(&b)+1); // Base::g()

(Fun)*((int*)*(int*)(&b)+2); // Base::h()

可以看看虚函数表的图是怎么画的:

            

大家都知道,多态是通过继承实现的,那么我们要说说虚函数继承的问题。继承就涉及到了虚函数的覆盖了,实际上不被覆盖的虚函数和多态又有什么联系呢?这里我们讨论有覆盖的虚函数表是什么样的,假设存在下面的继承关系:

        

看看虚函数表示什么样的:

        

可以发现,Base::f()被覆盖了,这样若把Derive的实例赋值给一个基类Base指针pBase,通过pBase->f();则访问的是子类中的f()也就是完成了多态。那么虚函数表中的内容到底是怎么样的呢?可以看看下面的四句话就会明白!

1.虚函数按照其声明顺序放于表中。

2.父类的虚函数在子类的虚函数前面。

3.覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

4.没有被覆盖的函数依旧。

2.用虚函数实现多态
看看下面的多态的代码:


代码如下:

#include <iostream>

using namespace std;

class Base
{
public:
    virtual void Print()
    {
        cout<<"Base::Print()"<<endl;
    }
};
class Derive : public Base
{
public:
    virtual void Print()
    {
        cout<<"Derive::Print()"<<endl;
    }
};
int main()
{
    Derive derive;
    Base *pBase = &derive;
    pBase->Print();
    return 0;
}
//多态代码

实现虚函数的代码,一定要切记:一定是基类的指针指向子类的对象的地址。首先试着理解一下用虚函数实现多态的原理,如果实在没理解为什么虚函数能实现多态,又为什么这样实现多态,上网再搜一搜相关的资料!!!

(0)

相关推荐

  • 解析C++编程中virtual声明的虚函数以及单个继承

    虚函数 虚函数是应在派生类中重新定义的成员函数. 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本. 虚函数确保为该对象调用正确的函数,这与用于进行函数调用的表达式无关. 假定基类包含声明为 virtual 的函数,并且派生类定义了相同的函数. 为派生类的对象调用派生类中的函数,即使它是使用指针或对基类的引用来调用的. 以下示例显示了一个基类,它提供了 PrintBalance 函数和两个派生类的实现 // deriv_VirtualFunctions

  • C++中继承与多态的基础虚函数类详解

    前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

  • C++中的多态与虚函数的内部实现方法

    1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.

  • 深入解析C++中的虚函数与多态

    1.C++中的虚函数C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Tab

  • 详解C++编程中的虚函数

    我们知道,在同一类中是不能定义两个名字相同.参数个数和类型都相同的函数的,否则就是"重复定义".但是在类的继承层次结构中,在不同的层次中可以出现名字相同.参数个数和类型都相同而功能不同的函数. 人们提出这样的设想,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数.在程序中不是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针调用它们.例如,用同一个语句"pt->display( );"可以调用不同派生层次中的display函数,只需在调用前

  • 浅析C++中的虚函数

    一.定义定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数. 语法:virtual 函数返回类型函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public. 定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数. 二.作用虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函

  • c++ 构造函数中调用虚函数的实现方法

    我们知道:C++中的多态使得可以根据对象的真实类型(动态类型)调用不同的虚函数.这种调用都是对象已经构建完成的情况.那如果在构造函数中调用虚函数,会怎么样呢? 有这么一段代码: class A { public: A ():m_iVal(0){test();} virtual void func() { std::cout<<m_iVal<<' ';} void test(){func();} public: int m_iVal; }; class B : public A {

  • 实例讲解C++编程中的虚函数与虚基类

    虚函数 ① #include "stdafx.h" #include <iostream> using namespace std; class B0//基类B0声明 { public: void display(){cout<<"B0::display()"<<endl;}//公有成员函数 }; class B1: public B0//公有派生类B1声明 { public: void display(){cout<<

  • c++语言中虚函数实现多态的原理详解

    前言 自上一个帖子之间跳过了一篇总结性的帖子,之后再发,今天主要研究了c++语言当中虚函数对多态的实现,感叹于c++设计者的精妙绝伦 c++中虚函数表的作用主要是实现了多态的机制.首先先解释一下多态的概念,多态是c++的特点之一,关于多态,简而言之就是 用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种方法呢,可以让父类的指针具有多种形态,也就是说不需要改动很多的代码就可以让父类这一种指针,干一些很多子类指针的事情,这里是从虚函数的实现机制层面进行研究 在写这篇帖子之前

  • C++ 中virtual 虚函数用法深入了解

    一.virtual修饰基类中的函数,派生类重写该函数: #include using namespace std; class A{ public: virtual void display(){ cout<<"A"<<ENDL; } }; class B : public A{ public: void display(){ cout<<"B"<<ENDL; } }; void doDisplay(A *p) { p

  • C++中纯虚函数的实例详解

    目录 虚函数和纯虚函数 附:纯虚函数的应用 总结 虚函数和纯虚函数 之前学过虚函数,语法:virtual 返回值类型 函数名(参数列表),然后这个类也就变成的虚基类,然后子类重写父类的虚函数. 纯虚函数,语法:virtual 返回值类型 函数名(参数列表)=0,当类中有了纯虚函数,这个类也称为抽象类.抽象类特点:无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类. class Base { public: virtual void Examp() = 0;//纯虚函数 ~Base(

  • 简单解读C++中的虚函数

    虚函数 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略.下面来看一段简单的代码 class A{ public: void print(){ cout<<"This is A"<<endl;} }; class B:public A{ public: void print()

  • C#中的虚函数virtual

    一.简介 虚拟函数从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的,也就是写死了的!而虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运行时期对象实例来动态判断要调用的函数,其中那个申明时定义的类叫申明类,那个执行时实例化的类叫实例类.如:飞禽 bird = new 麻雀();那么飞禽就是申明类,麻雀是实例类. 具体的检查的流程如下: 1.当调用一个对象的函数时,系统会直接去检查这个对

随机推荐