C++多态性与虚函数

Wesley13
• 阅读 635

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

(一)多态性

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

  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<area();           //动态联编

  void fun(Point *p)

  {cout<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.cpplzb@lzb:~/classic_lib/C++_learning$ ./a.out derivebase

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
C++课程第五次博客——多态
\TOC\多态性Part1多态性概述多态是指同样的消息被不同类型的对象接收时导致不同的行为。在C中,所谓信息是指对类的成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数。1)多态的类型分为四类:重载多态,强制多态,包含多态和参数多态。前两者为专用多态,而后者称为通用多态。2)
Stella981 Stella981
3年前
C++primer学习笔记(六)
1.virtual函数是基类希望派生类重新定义的函数,希望派生类继承的函数不能为虚函数。根类一般要定义虚析构函数。2.派生类只能通过派生类对象访问protected成员,不能用基类对象访问。基类定义为virtual就一直为虚函数,派生类写不写virtual都是虚函数。用做基类的类必须是已定义的。3.存在虚函数指针或引用
Wesley13 Wesley13
3年前
C++基类的析构函数定义为虚函数的原因
1:每个析构函数只会清理自己的成员(成员函数前没有virtual)。2:可能是基类的指针指向派生类的对象,当析构一个指向派生类的成员的基类指针,这时程序不知道这么办,可能会造成内存的泄露,因此此时基类的析构函数要定义为虚函数;基类指针可以指向派生类的对象(多态),如果删除该指针delete\\p,就会调用该指针指向的派生类的析构函数,而派生类
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这