【C++复习】:多态

多态(******)

概念

同一事物在不同场景下表现出的多种形态
多态 = 一个接口,多种实现

分类

静态的多态->函数重载

编译时是参数匹配和函数名修饰规则

cpp 复制代码
void func(int a);
void func(double a);
void func(int a, int b);

动态的多态->运行时,跟指向对象有关

三要素(缺一不可)
  1. 继承关系(父子类)
  2. 虚函数重写(virtual + 三同)
  3. 父类指针 / 引用 调用虚函数
虚函数

子类中虚函数可以不加virtual

静态函数、全局函数不能是虚函数

构造函数不能是虚函数,析构函数建议写成虚函数

重写(覆盖)规则:三同->例外协变

函数名相同

参数相同

返回值相同

父类指针或者引用去调用虚函数

指向父类调用父类虚函数,指向子类调子类虚函数

条件

  1. 基类必须有虚函数,派生类对虚函数重写
  2. 使用基类的指针或引用调用虚函数
    根据指针和引用调用指向不同类的对象,选择对应虚函数
cpp 复制代码
class Father {
public:
    virtual void show() { cout << "父类" << endl; }
};

class Son : public Father {
public:
    // 子类可写 virtual,也可以不写
    void show() override { cout << "子类" << endl; } 
};
Father f;
Son s;

Father* p = &f; 
p->show(); // 父类

p = &s;      
p->show(); // 子类

重载/重写(覆盖)/隐藏(重定义)

重载

重载也叫做静态多态或者静态联编

  1. 同一作用域(同一个类里 / 同一个命名空间)
  2. 函数名相同,参数不同(类型 / 个数 / 顺序)

重写

条件
  1. 在继承体系中
  2. 基类是虚函数
  3. 派生类虚函数必须和基类虚函数完全一致(三同)
  4. 必须通过父类指针 / 引用调用才体现多态
    两个例外
    协变
    基类虚函数返回基类的指针引用
    派生类返回派生类指针引用
    这种也算合法重写。
    析构
    底层统一解释为destructor
隐藏

父类函数不是虚函数

子类的成员和父类相同,会把父类成员隐藏

override和final

override:我要重写,编译器你帮我检查!

final:到此为止,不许继承 / 不许重写!

override

表示这个函数是重写的,编译器要检查该函数父类是否是虚函数,以及是否可以访问

final

  1. 修饰类
    该类不能被继承
  2. 修饰函数
    该函数不能被重写

析构函数建议是虚函数?为什么?

cpp 复制代码
A* ptr = new B;
delete ptr;

结果(不加 virtual)

只调用~A (),不调用~B ()

纯虚函数

  1. 纯虚函数写法
cpp 复制代码
virtual void func() = 0;

包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象 不能 A a; 或 new A;

间接强制子类重写,不重写,子类依旧是抽象类,依旧不能实例化

虚表构建规则

基类虚表

按照声明次序 加入到虚表中

表里存的是:各个虚函数的入口地址

派生类虚表

  1. 先拷贝基类的整个虚表内容
  2. 派生类重写了哪个虚函数,就进行虚表当中地址的替换
  3. 如果新增了虚函数,按声明顺序追加到虚表末尾

多态调用过程

对象里有虚表指针(vfptr)

父类对象 → 指向父类虚表

子类对象 → 指向子类虚表(重写过的函数已替换)

用父类指针 / 引用调用虚函数时

不看指针类型,看指向的对象

虚函数调用步骤

  1. 从对象的前4/8个字节拿出虚表地址
  2. 从虚表中找到具体的虚函数地址
  3. 函数参数压栈
  4. 跳转到该地址执行函数

多态的原理

基类指针可以指向子类对象,通过基类指针访问前四个字节拿到子类虚表,子类虚表存放的是子类的虚函数,此时调用就会调用当前虚表下的虚函数

转型问题

  1. 向上转型
    子类 → 父类(子类转父类 ) 的指针 / 引用转换
    安全,编译器直接允许,不需要强转
cpp 复制代码
B b;//子类
A* pa = &b;   // 隐式转换,安全
A& ra = b;
  1. 向下转型
    父类 → 子类(父类转子类 ) 的指针 / 引用转换
    本身不安全
    因为父类指针可能真指向父类对象,强行转成子类会越界 / 乱访问
cpp 复制代码
A a;
B* pb = (B*)&a;  // 语法过,但运行极危险

想安全向下转:用 dynamic_cast + 虚函数

考点

1. 什么是多态

多态就是同一事物在不同场景下表现出的多种形态
2. 什么是重载、重写、重定义(隐藏)

  1. 重载是在同一作用域下,函数名相同,函数参数的类型,数量,顺序不同,即构成函数重载
  2. 重写是指在继承体系中,基类的某一成员函数是虚函数,在子类中对该虚函数进行重新实现就是重写
  3. 重定义是指在继承体系中,不满足重写的条件,并且子类含有一个和基类相同的成员,就构成了重定义
    3. 多态的实现原理
    当一个类中含有虚函数时,会为该类创建一个虚函数表,保存的是虚函数的地址
    当派生类继承基类时,也会有对应的虚函数表
    当定义一个派生类对象时,编译器检测到有虚函数,就会给该派生类对象创建一个虚函数表指针,指向这个虚函数表,这是在构造函数完成的
    后续如果有基类的指针指向派生类,那么调用函数时,虚函数表指针调用的就是该派生类的虚函数,即使是基类的指针
    4. inline函数可以是虚函数吗
    可以,但是会丧失inline属性
    编译器会把虚函数的地址放到虚函数表中,如果inline函数被当做虚函数,会自动忽略inline属性
    5. 静态成员可以是虚函数吗
    不可以
    静态成员函数没有this指针,访问静态成员函数的方法是采用类名::函数名
    而这样的做法不可以访问虚函数表,因此不可以把静态成员函数放到虚函数表当中
    6. 构造函数可以是虚函数吗
    构造函数不可以是虚函数
    虚函数调用是通过虚函数表指针来进行调用的,而虚函数表指针的初始化工作是在构造函数中完成的,因此无法通过虚函数表指针调用构造函数
    7. 析构函数可以是虚函数吗
    析构函数一般都建议写成虚函数
    当调用派生类对象的析构函数时,如果基类的析构函数没有定义为虚函数,那么默认该派生类的析构函数是派生类的析构函数,不会调用基类的析构函数
    导致只会释放派生类的资源和对象,而基类的资源对象会造成内存泄漏
    8. 多态的缺点
    1. 性能开销
      虚函数调用需要到虚函数表中寻找,开销大
      每一个对象还要包含一个虚函数表指针
      动态绑定导致编译时不能进行优化
    2. 没有静态检查
      由于是动态绑定,所以可能有些错误在运行时才会出现
      通过基类的指针和引用无法访问子类的某些成员变量和成员函数,需要进行转换
    3. 代码设计更加复杂
      设计复杂
      调试复杂
    4. 代码膨胀
      虚函数表的存在,会增加代码和数据区的大小
      9. 虚函数表是在什么阶段生成的,在哪存放
      虚函数表是在编译期间就生成的
      一般存放在常量区
      10. 同一个类的不同对象,用的是同一张虚表吗
      是的
      在编译期间会为含有虚函数的类生成一个虚函数表,虚函数表中存放的是虚函数地址
      创建不同的对象,会在构造函数初始化虚函数表指针,但是指针指向的是同一个表
      11. 一个类的对象可以包含多张虚表吗
      一般不会,除非是多继承会间接包含
      如果是单继承,会先继承基类的虚函数表,再进行重写,再把新增的虚函数加入到虚函数表中
      如果是多继承,会为每一个基类生成一个独立的虚函数表,并在派生类中为每一个基类生成一个虚函数表指针,看起来是一个指针,实际上内部是有多个指针,每个基类对应一个
      12. 抽象类的作用是什么
    5. 声明接口
      通过抽象类派生出的派生类,都要提供抽象类的这些接口,是一种标准化的模式
    6. 强制实现
      抽象类强制要求,派生类必须要实现抽象类当中的实现细节,否则也是抽象类
    7. 实现多态
      抽象类是实现多态的基础,借助抽象类可以实现多态,进而形成一个接口,多种实现的效果
相关推荐
啊我不会诶2 小时前
最小生成树
c++·笔记·学习·算法
淀粉肠kk3 小时前
【C++】C++11 Lambda表达式
开发语言·c++
南境十里·墨染春水3 小时前
CMake核心用法(贴合C++编译场景)
开发语言·c++
liuyao_xianhui3 小时前
优选算法_栈_删除字符中的所有相邻重复项_C++
开发语言·数据结构·c++·python·算法·leetcode·链表
tankeven3 小时前
HJ154 kotori和素因子
c++·算法
!停3 小时前
C++入门基础—类和对象3
java·数据库·c++
qq_283720054 小时前
C++ 基础:STL 原理介绍 + 实用技巧
c++·stl·c·模板库
量子炒饭大师4 小时前
【C++模板进阶】——【非类型模板参数 / 模板的特化 / 模板分离编译】
开发语言·c++·dubbo·模板·非类型模板·模板的特化·模板分离编译
白藏y4 小时前
【脚手架】Protobuf基础使用
c++·protobuf