深入浅析C++多态性与虚函数

派生一个类的原因并非总是为了继承或是添加新的成员,有时是为了重新定义基类的成员,使得基类成员“获得新生”。面向对象的程序设计真正的力量不仅仅是继承,而且还在于允许派生类对象像基类对象一样处理,其核心机制就是多态和动态联编。

(一)多态性

  多态是指同样的消息被不同的对象接收时导致不同的行为。所谓消息是指对类成员函数的调用,不同的行为是指的不同的实现,也就是调用了不同的函数。

1)多态的分类

  广义上说,多态性是指一段程序能够处理多种类型对象的能力。在C++中,这种多态性可以通过重载多态(函数和运算符重载),强制重载(类型强制转换),类型参数化多态(模板)

,包含多态(继承与虚函数)四种方式来实现。类型参数化多态和包含多态称为一般多态性,是用来系统地刻画语义上相关的一组类型;重载多态和强制多态性称为特殊多态性,用来刻画语义上无关连的类型间关系。

  C++中采用虚函数实现包含多态。虚函数为C++提供了更为灵活的多态机制,这种多态性在程序运行时才能够确定,因此虚函数是多态性的精华,至少含有一个虚函数的类称为多态类。包含多态在面向对象的程序设计中使用很频繁。

2)静态联编

  联编又称为绑定,就是将模块或函数合并在一起生成可执行代码的处理过程,同时对每个模块或函数分配内存地址,对外部访问也提供正确的内存地址。

  在编译阶段就将函数实现与函数调用绑定起来称为静态联编。静态联编在编译阶段就必须了解所有函数与模块执行所需要的信息,它对函数的选择是基于指向对象的指针(或引用)的类型。在C语言中所有的联编都是静态联编;C++中一般情况也是静态联编。

class Point{
  public:
    void area(){cout<<"point";}
  };
  class Circle:public Point{
  public:
    void area(){cout<<"circle";}
  };
  Point a; Circle c;
  a.area();   //调用a.Point::area()
  c.area();   //调用c.Circle::area(),名字支配规则
  Point * pc=&c,&rc=c;  //上篇所讲的赋值兼容性规则
  pc->area();  //调用pc->Point::area()
  rc.area();   //调用rc.Point::area()

3)动态联编

  如果程序在运行时候才进行函数实现和函数调用的绑定称为动态联编。以上面的例子为例,在编译时如果只根据兼容性规则检查它的合理性,即检查它是否符合派生类对象地址可以赋值给基类指针变量的条件。至于pc->area()调用哪个函数等到程序运行到这里才做决定。如果希望其调用Circle::area(),那么需要将Point类的area()函数指定为虚函数。定义形式为:

  virtual void area(){cout<<"point";}

  当编译器编译含有虚函数的类时候,将为他建立一个虚函数表VTABLE,它相当于一个指针数组,存放每一个虚函数的入口地址。编译器为该类增减一个额外的数据成员,这个数据成员是一个指向虚函数表的指针,称为vptr。

  如果派生类没有重写这个虚函数,则派生类的虚函数列表里元素指向的地址就是基函数area()的地址,即派生类仅仅继承基类的虚函数

  如果派生类重新写这个虚函数如下:

  virtual void area() {cout<<"circle";}

  那么这时编译器将派生类虚函数表里的元素指向Circle::area()

   编译器为含有虚函数的对象先建立一个函数入口地址,这个地址用来存放指向虚函数表的指针vptr,然后按照类中虚函数的声明次序一一填入函数指针。当调用虚函数时候,先通过vptr找到虚函数表,然后找出虚函数真正的地址。

  派生类能够继承基类的虚函数表,而且只要是和基类同名(参数也相同)的成员函数,无论是否使用virtual声明,它们都自动生成虚函数。如果派生类没有改写继承基类的虚函数,则函数指针将调用基类的虚函数。 

(二)虚函数

1)虚函数定义

  虚函数只是类中的一个成员函数,且不能是静态的。在成员函数定义或声明之前加上关键字virtual,即定义了虚函数:

class类名{
    ...
    virtual 返回类型 函数名 (形式参数列表)//虚函数
    ...
  };
  class Point
  {
    virtual void area ();    //虚函数声明
    virtual double volumn(){}  //虚函数定义 
  };

  需要注意virtual关键字只在类体中使用。

  利用虚函数可以在基类和派生类中使用相同的函数名定义函数不同的实现,从而实现“一个接口,多种方式”。当基类指针或引用对虚函数进行访问时,系统将根据运行时指针或引用所指向或引用的实际对象来自动确定调用对象所在类的虚函数版本。

2)虚函数实现多态的条件

  关键字virtual指示C++编译器对调用虚函数进行动态联编。这种多态性是程序运行到相应语句才动态确定的,称为运行时的多态。不过,使用虚函数不一定产生多态性,也不一定使用动态联编。例如,在调用中对虚函数使用成员名限定,可以强制C++对该函数的调用使用静态联编。

  虚函数产生运行时的多态性必须有2个条件。

  a)派生类改写了同名的虚函数

  b)根据赋值兼容性规则使用指针或引用

Point *p=new Circle;  //基类指针指向派生类
  cout<<p->area();   //动态联编
  void fun(Point *p)
  {cout<<p->area();}   //动态联编

3)在一个派生类中,当一个指向基类成员函数的指针指向一个虚函数,并且通过指向对象的指针或引用访问这个虚函数时候将发生多态性。

#include<iostream>
using namespace std;
class Base{
public: virtual void print(){cout<<"base"<<endl;}
};

class Derived :public Base{
public:
 void print(){cout<<"derive"<<endl;}
};

//void(Base::*pf)();
void display(Base *p,void(Base::*pf)())
{
(p->*pf)();

}

int main()
{
 Derived d;
 Base b;
 display(&d,&Base::print);
 display(&b,&Base::print);
 return 0;
}

lzb@lzb:~/classic_lib/C++_learning$ g++ 427.cpp
lzb@lzb:~/classic_lib/C++_learning$ ./a.out
derive
base

  display有两个函数,第一个参数是基类指针,第二个参数是指向类成员函数的指针。display使用基类指针调用指向成员函数的指针所指向的成员函数。是调用基类的虚函数还是派生类的虚函数,取决于基类指针指向的对象。 

补充:

面向对象的三个特征:封装、继承和多态。

什么是多态

多态的意思是一个事物有多种形态,英文单词为polymorphism,向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(方法)。也就是说,每个对象可以用自己的方式去相应共同的消息。

例如函数的重载、运算符的重载都是多态现象。

一个生活中的例子,比如学生开学,校长发布一条哪一天开学的信息,不同的对象会产生不同的反应,学生就要准备上学,家长要准备学费,老师也要开始备课,学校食堂开始采购食材,这就是多态性,如果没有多态性的话,校长就需要分别对学生、教师和家长等不同的对象单独发通知。

在C++中,多态性表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。

从系统实现的角度来看,多态性分为两类:静多态性和动多态性
静多态性是通过函数重载实现的,动多态性是通过虚函数实现的。

什么是虚函数

C++中的虚函数就是用来解决动态多态问题的,所谓虚函数,就是在基类声明函数是虚拟的,并不是实际存在的,然后在派生类中才正式定义此函数,在程序运行期间,用指针指向某一派生类对象,这样就能调用指针指向的派生类对象中的函数,而不对调用其他派生类中的函数。

总结

以上所述是小编给大家介绍的C++多态性与虚函数,希望对大家有所帮助!  

(0)

相关推荐

  • C++ 中const修饰虚函数实例详解

    C++ 中const修饰虚函数实例详解 [1]程序1 #include <iostream> using namespace std; class Base { public: virtual void print() const = 0; }; class Test : public Base { public: void print(); }; void Test::print() { cout << "Test::print()" << end

  • 详解C++编程的多态性概念

    多态性(polymorphism)是面向对象程序设计的一个重要特征.如果一种语言只支持类而不支持多态,是不能被称为面向对象语言的,只能说是基于对象的,如Ada.VB就属此类.C++支持多态性,在C++程序设计中能够实现多态性.利用多态性可以设计和实现一个易于扩展的系统. 顾名思义,多态的意思是一个事物有多种形态.多态性的英文单词polymorphism来源于希腊词根poly(意为"很多")和morph(意为"形态").在C ++程序设计中,多态性是指具有不同功能的函

  • 浅谈C++基类的析构函数为虚函数

    1.原因: 在实现多态时, 当用基类指针操作派生类, 在析构时候防止只析构基类而不析构派生类. 2.例子: (1). #include<iostream> using namespace std; class Base{ public: Base() {}; ~Base() {cout << "Output from the destructor of class Base!" << endl;}; void DoSomething() { cout

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

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

  • 深入理解C++的多态性

    C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言.我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握. 多态性可以简单地概括为"一个接口,多种方法",程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念.多态(polymorphisn),字面意思多种形状. C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写.(这里我觉得要补充,重

  • 深入浅析C++多态性与虚函数

    派生一个类的原因并非总是为了继承或是添加新的成员,有时是为了重新定义基类的成员,使得基类成员"获得新生".面向对象的程序设计真正的力量不仅仅是继承,而且还在于允许派生类对象像基类对象一样处理,其核心机制就是多态和动态联编. (一)多态性 多态是指同样的消息被不同的对象接收时导致不同的行为.所谓消息是指对类成员函数的调用,不同的行为是指的不同的实现,也就是调用了不同的函数. 1)多态的分类 广义上说,多态性是指一段程序能够处理多种类型对象的能力.在C++中,这种多态性可以通过重载多态(函

  • 浅析C++中的虚函数

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

  • C++浅析虚函数使用方法

    目录 一.虚函数 二.虚函数与纯虚函数的区别 三.动态多态 阐述虚函数作用和原理.纯虚函数和虚函数的区别. 一.虚函数 首先来看下面这一段代码,首先创建两个类,一个是Dog,另一个是Cat,他们有一个共同的属性:Run.在定义中每个动物都需要创建一个类,比较繁琐,所以在下面的例子中,我们可以把他们简化. #include <iostream> using namespace std; class Dog{ public: void Run(){ cout<<"Dog-&g

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

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

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

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

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

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

  • C++中虚函数与纯虚函数的用法

    本文较为深入的分析了C++中虚函数与纯虚函数的用法,对于学习和掌握面向对象程序设计来说是至关重要的.具体内容如下: 首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用

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

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

  • C++虚函数及虚函数表简析

    C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 关于虚函数的使用方法,我在这里不做过多的阐述.大家可以看看相关的C++的书籍.在这篇文章中,我只想从虚函数的实现机制上面为大家 一个

  • 深入解析C++编程中的纯虚函数和抽象类

    C++纯虚函数详解 有时在基类中将某一成员函数定为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义. 纯虚函数是在声明虚函数时被"初始化"为0的函数.声明纯虚函数的一般形式是 virtual 函数类型 函数名 (参数表列) = 0; 关于纯虚函数需要注意的几点: 纯虚函数没有函数体: 最后面的"=0"并不表示函数返回值为0,它只起形式上的作用,告诉编译系统"这是纯虚函数"; 这是一个

随机推荐