文章目录

  • 多态
    • 问题抛出
    • 面向对象新需求
    • 多态的意义探究
      • 面向对象三大概念:
      • 多态成立的三要素:
    • 虚析构
    • 函数的重载、重写、重定义
      • 函数重载
      • 函数重定义
      • 虚函数重写
    • 纯虚函数和抽象类
      • 纯虚函数
      • 抽象类
        • 概念理解
        • 抽象类特征
      • 关键字
        • abstract
        • final
    • 多态探究
      • 多态的理论基础
      • 多态的本质(原理)
      • 如何证明vptr指针存在
      • 如何找到vptr指针呢

多态

问题抛出

如果子类定义了与父类中原型相同的函数会发生什么?

面向对象新需求

对于上面这种现象,编译器的做法不是我们期望的,我们期望的是

  • 根据实际的对象类型来判断重写函数的调用

  • 如果父类指针指向的是父类对象,则调用父类中定义的函数

  • 如果父类指针指向的是子类对象,则调用子类中定义的重写函数

    解决方案:

    • 在父类中,在能让子类重写的函数的前面加上virtual关键字(必须)
    • 在子类中,在重写的父类的虚函数后面加上override,表示是虚函数重写(非必须,但是加上可以防止重写的虚函数写错)

多态的意义探究

面向对象三大概念:

封装:突破了C语言函数的概念
继承:代码复用——可以用以前人写的代码
多态:可以使用未来人写的代码

案例:打印矩形和圆形坐标和面积

矩形:x,y,length,width

圆形:x,y,radius

思路:

  1. 矩形和圆形都有共同的属性(x,y),那么可以把坐标抽取出来,定义一个形状类为父类,形状类中包含坐标,并提设置和访问方法以及获取面积的方法。
  2. 定义一个矩形类,继承自形状类;并增加length和width属性
  3. 定义一个圆形类,继承自形状类;并增加radius属性
  4. 写一个测试类,定义相应的方法来实现功能

多态成立的三要素:

  1. 要有继承:多态发生在父子类之间

2. 要有虚函数重写:重写了虚函数,才能进行动态绑定

3. 要有父类指针(引用)指向子类对象

多态是设计模式的基础,多态是框架的基础

虚析构

构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数

析构函数可以是虚的。通过父类指针释放所有的子类资源

class Base
{public:Base(){cout << __FUNCSIG__ << endl;}virtual ~Base(){cout << __FUNCSIG__ << endl;}
};class Derive : public Base
{private:char* _str;
public:Derive(){_str = new char[10]{ "顽石老师" };cout << __FUNCSIG__ << endl;}~Derive(){delete _str;cout << __FUNCSIG__ << endl;}
};int main()
{Base* base = new Derive;delete base;          //父类写了虚析构,通过父类指针释放对象的时候,才会调用子类的析构函数return 0;
}

函数的重载、重写、重定义

函数重载

  • 必须在同一个类中进行(作用域相同)

  • 子类无法重载父类的函数,父类同名函数将被名称覆盖

  • 重载是在编译期间根据参数类型和个数决定函数调用

函数重定义

  • 发生于父类和子类之间,如果子类写了个和父类函数原型一样的函数,并且父类中的函数没有声明为虚函数,则子类会直接覆盖掉父类的函数
  • 通过父类指针或引用执行子类对象时,会调用父类的函数

虚函数重写

  • 必须发生于父类和子类之间

  • 并且父类与子类中的函数必须有完全相同的原型

  • 使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)

  • 多态是在运行期间根据具体对象的类型决定函数调用

纯虚函数和抽象类

纯虚函数

纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。

纯虚函数也可以叫抽象函数,一般来说它只有函数名、参数和返回值类型,不需要函数体。这意味着它没有函数的实现,需要让派生类去实现。
C++中的纯虚函数,一般在函数签名后使用=0作为此类函数的标志。Java、C#等语言中,则直接使用abstract作为关键字修饰这个函数签名,表示这是抽象函数(纯虚函数)。

class Animal
{public:virtual void cry() = 0;
}

一个简单的图形纯虚函数的抽象类的例子:

//形状类:周长,面积
class Shape
{protected:int x;int y;
public:Shape() = default;Shape(int x, int y):x(x), y(y){}//访问器int getX() { return x; }int getY() { return y; }//设置器void setX(int x) { this->x = x; }void setY(int y) { this->y = y; }//有没有实际意义,唯一的作用就是让子类来重写,实现具体得功能//声明为纯虚函数virtual double getArea() = 0;
};//矩形类
class Rect :public Shape
{protected:int width;int length;
public:Rect() = default;Rect(int x, int y, int w, int len):Shape(x, y), width(w), length(len){}//error C3668: “Rect::getArea”: 包含重写说明符“override”的方法没有重写任何基类方法double getArea() override{return width * length;}
};//圆形类
class Circle :public Shape
{double radius; //半径
public:Circle() = default;Circle(int x, int y, int r):Shape(x, y), radius(r){}double getArea() override{return 3.14159265359 * radius * radius;}
};
//三角形类
class Triangle : public Rect
{public:using Rect::Rect;double getArea()override{return width * length / 2;}
};void dance(Shape& shape)
{cout << "area " << shape.getArea();
}

我们创建了形状类抽象基类,然后继承此抽象基类,其中把共同的方法定义为纯虚函数,即加 virtual 函数=0
使用overide声明子类的函数表示这个函数是纯虚函数

抽象类

抽象类是对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。

通常在编程语句中用 abstract 修饰的类是抽象类。在C++中,含有纯虚拟函数的类称为抽象类,它不能生成对象;在java中,含有抽象方法的类称为抽象类,同样不能生成对象。

抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色。

概念理解

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

比如,在一个图形编辑软件的分析设计过程中,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域并不是直接存在的,它就是一个抽象概念。而正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

抽象类特征

  1. 抽象类不能实例化
  2. 抽象类和包含抽象方法(纯虚函数)、非抽象方法和属性
  3. 从抽象类派生的非抽象类,必须对继承过来的所有抽象方法实现

关键字

abstract

MSVC独有的关键字,申明类为抽象类

class  Animal abstract
{};int main()
{Animal a;  //error C3622: “Animal”: 声明为“abstract”的类不能被实例化return 0;
}

final

C++标准关键字,结束的意思

  • 禁用虚函数重写

    class  Animal
    {protected:virtual void show() final{}
    };class Dog final :public Animal
    {public:void show()override  //error C3248: “Animal::show”: 声明为“final”的函数无法被“Dog::show”重写{}
    };
    
  • 禁止该类被继承

    class  Animal  final
    {};class Dog final :public Animal //error C3246: "Dog": 无法从 "Animal" 继承,因为它已声明为 "final"
    {};
    

多态探究

多态的理论基础

静态联编和动态联编:联编是指一个程序模块、代码之间互相关联的过程。

  • 静态联编(关联),是程序的匹配、连接在编译阶段实现,也称为早期匹配。
    重载函数使用静态联编。

  • 动态联编(关联),是指程序联编推迟到运行时进行,所以又称为动态联编(迟绑定),将函数体和函数调用关联起来,就叫绑定
    switch 语句和 if 语句是动态联编的例子。

    那么C++中的动态联编是如何实现的呢?
    如果我们声明了类中的成员函数为虚函数,那么C++编译器会为类生成一个虚函数表,通过这个表即可实现动态联编

多态的本质(原理)

虚函数表是顺序存放虚函数地址的,虚表是顺序表(数组),依次存放着类里面的虚函数。
虚函数表是由编译器自动生成与维护的,相同类的不同对象的虚函数表是一样的。

既然虚函数表,是一个顺序表,那么它的首地址存放在哪里呢?其实当我们在类中定义了virtual函数时,C++编译器会偷偷的给对象添加一个vptr指针,vptr指针就是存的虚函数表的首地址。

如何证明vptr指针存在

我们可以通过求出类的大小判断是否有vptr的存在

class Dog
{void show() {}
};class Cat
{virtual void show() {}
};int main()
{cout << "Dog size:" << sizeof(Dog) << " Cat size:" << sizeof(Cat) << endl;return 0;
}
output: Dog size:1 Cat size:8

通过调试确实能看到vptr指针的存在,而且存放在对象的第一个元素

如何找到vptr指针呢

既然vptr指针存在,那么能不能拿到vptr指针,手动来调用函数呢?

答案是可以的,但是操作起来比较麻烦!下面我们就来挖一挖

  1. 因为vptr指针在对象的第一个元素(通过证明vptr指针的存在可以看出),所以对对象t取地址可以拿到对象的地址

    Object* p = &obj;
    
  2. 现在拿到的指针的步长是对象的大小,因为vptr是指针,只有4/8个字节,所以需要把p强转成int*指针,这样对(int*)&t就得到了vptr指针

    int vptr = *(int*)p;   //拿到了vptr指针的指针
    int* pvptr = (int*)vptr; //把vptr的值转成指针
    
  3. 因为vptr指针是指向的存储指针数组的首地址,所以拿到vptr指针后先把vptr转成int*指针,这样进行取值的话,刚好是每个指针

    FUN foo = (FUN)*(pvptr+0)
    
  4. 接着把得到的数组里面的元素(指针)转成函数指针,即可直接使用了

    using FUN = void (*)();Parent* p = &obj;
    long long vptr = *(long long*)p;
    long long* pvptr = (long long*)vptr;
    auto foo = (FUN)*(pvptr + 1);
    foo();
    //output:Parent::fun(int i)
    

C++多态与虚函数的实现相关推荐

  1. C++中类的多态与虚函数的使用(转)

    C++中类的多态与虚函数的使用 http://www.cnblogs.com/fangyukuan/archive/2010/05/30/1747449.html 类的多态特性是支持面向对象的语言最主 ...

  2. C++中类的多态与虚函数的使用

    C++中类的多态与虚函数的使用 http://www.cnblogs.com/fangyukuan/archive/2010/05/30/1747449.html 类的多态特性是支持面向对象的语言最主 ...

  3. 复习笔记(五)——C++多态和虚函数

    静态绑定与动态绑定 静态绑定: -编译时就能确定一条函数调用语句要调用的函数 -在程序编译时多态性体现在函数和运算符的重载上 动态绑定: -运行时才能确定函数调用语句调用的函数 -程序运行时的多态性通 ...

  4. C++初步之核心编程篇五:多态与虚函数类

    C++初步之核心编程篇五:多态与虚函数类 文章目录 C++初步之核心编程篇五:多态与虚函数类 1. 多态的概要 2. 多态案例一-计算器类 3. 纯虚函数和抽象类 4. 多态案例二-制作饮品 5. 虚 ...

  5. 【阿里面试】C++多态和虚函数

    文章目录 一.C++的面试常考点 二.阿里真题 2.1 现在假设有一个编译好的C++程序,编译没有错误,但是运行时报错,报错如下:你正在调用一个纯虚函数(Pure virtual function c ...

  6. C++ 多态、虚函数、虚方法

    子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override).通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写 ...

  7. PKU C++程序设计实习 学习笔记3 多态与虚函数

    第六章 多态与虚函数 6.1 多态和虚函数的基本概念 引言 多态是面向对象程序设计里面非常重要的这个机制.它能很有效的提高程序的可扩充性. 有些程序设计语言有被对象继承的概念,但是没有多态的概念,那这 ...

  8. C++学习之第十一天-多态、虚函数

    一.选择题 1.下列关于动态联编的描述中,错误的是(D). A.动态联编是以虚函数为基础 B.动态联编是运行时确定所调用的函数代码的 C.动态联编调用函数操作是指向对象的指针或对象引用 D.动态联编是 ...

  9. C++多态中虚函数的深入理解

    c++中动态多态性是通过虚函数来实现的.静态多态性是通过函数的重载来实现的,在程序运行前的一种早绑定,动态多态性则是程序运行过程中的一种后绑定.根据下面的例子进行说明. #include <io ...

  10. C++中多态与虚函数的学习例子

    多态(Polymorphism):在面向对象语言中,接口的多种不同的实现方式.也可以这样理解:在运行时,可以基类的指针来调用实现派生类中的方法.简单的一句话:允许将子类类型的指针赋值给父类类型的指针. ...

最新文章

  1. [跟我学中小企业架构部署]之三:数据库多实例部署
  2. javascript关于检测浏览器和操作系统的问题
  3. MySQL8与PG10:新版本下的较量谁更胜一筹?
  4. 程序设计语言编译原理_编译原理学习笔记(二):高级程序设计语言
  5. 分布式系统的可靠协调系统——Zookeeper
  6. 奇异值分解(Singular Value Decomposition,SVD)
  7. Unity 后处理 性能优化
  8. 材料成形计算机辅助设计,材料成形及控制工程
  9. 十分钟掌握Nodejs下载和安装
  10. 初生牛犊不怕虎!开发不足一年的Android实习生在大厂横冲直撞后,手握多份offer,特此分享!
  11. 89---狄拉克 delta 函数
  12. python之父:一个合格的python程序员,应该从这三本书入手!
  13. RJS 模板的另种用法
  14. 《es6标准入门》 阮一峰
  15. 处理R安装后出现You're using a non-UTF8 locale, therefore only ASCII characters will work.的情况
  16. 计算机教案三维目标,教学设计的三维目标
  17. 【英语考研词汇训练营】Day 17 —— espresso,ultimate,gradually,detect,dimension
  18. VS2017项目配置X86改配置x64位
  19. BurpSuit抓包软件字体设置方法
  20. 一次p3故障总结(沟通及流程篇)

热门文章

  1. Espressif IDE 及其 v2.4.0 新增功能—第二部分
  2. 知识到底如何变现?多种模式你选哪一个
  3. 简单密码---python
  4. 这个很NB,html在线编辑器
  5. 境界死神激斗怎么在电脑上玩 境界死神激斗电脑版玩法教程
  6. 连载《叁芯智能fpga设计与研发-第15天》 【状态机、自动售货机】 【原理及verilog仿真】
  7. 【幻灯片软件】Focusky教程 | FS软件能支持多大分辨率的图片?
  8. thinkphp5.0.24反序列化漏洞分析
  9. 【发射极接地 共射级放大电路 原理讲解与元器件取值 (详细参数说明)+multisim仿真】
  10. IPV6ND协议之NUD与DAD机制