• C++ 多态


    C/C++总述:Study C/C++-CSDN博客 

    目录

    多态概念

    多态分类

    多态实现 

    虚函数&虚函数表

    虚函数的重写(覆盖)

    多态的构成条件 

    虚函数重写的两个特例

    协变

    析构

    关键字final和override(C++11)

    抽象类

    纯虚函数

    实现继承与接口继承


    多态概念

    对于同一个行为对于不同的对象,有不同的表现

    eg:买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。 

    多态分类

    多态在 c++ 中分为 静态多态动态多态,也称 编译期多态 和 运行时多态 。

    静态多态:是基于 函数重载 与 泛型编程 实现的。

    动态多态:是基于虚函数实现的。

    多态实现 

    虚函数&虚函数表

    • 虚函数:即被 virtual 修饰的类成员函数称为虚函数。
    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl;}
    4. };
    • 虚函数表本质是一个存虚函数指针 的 指针数组,一般情况这个数组最后面放了一个nullptr。
    • 虚函数表:虚函数表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚函数表中。
    • 一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表
    • 注意:对象的前四个字节就是虚表的地址,虚表存放在常量区(虚表是不能人为更改的)

    虚函数的重写(覆盖)

    • 虚函数的重写(覆盖): 派生类中有一个跟基类完全相同的虚函数 (即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同,称子类的虚函数 重写 了基类的虚函数。

    多态的构成条件 

    1. 必须通过 基类的指针 来 引用 调用虚函数
    2. 被调用的函数 必须是虚函数,且 派生类必须对基类的虚函数进行重写
    1. class Person { //多态条件2:被调用的函数 必须是虚函数
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public: //多态条件2:派生类必须对基类的虚函数进行重写
    7. virtual void BuyTicket() { cout << "买票-半价" << endl; }
    8. /*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
    9. 为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
    10. 这样使用*/
    11. /*void BuyTicket() { cout << "买票-半价" << endl; }*/
    12. };
    13. void Func(Person& p) //多态条件1:必须通过基类的指针来“引用”调用虚函数
    14. {
    15. p.BuyTicket();
    16. }
    17. int main()
    18. {
    19. Person ps;
    20. Student st;
    21. Func(ps); //输出:买票-全价
    22. Func(st); //输出:买票-半价
    23. return 0;
    24. }

    函数重写的两个特例

    协变

    派生类重写基类虚函数时 ,与基类虚函数 返回值类型不同 。即如下代码所示:【基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时】,称为协变。

    1. class A{};
    2. class B : public A {};
    3. class Person
    4. {
    5. public:
    6. virtual A* f() {return new A;}
    7. };
    8. class Student : public Person
    9. {
    10. public:
    11. virtual B* f() {return new B;}
    12. };
    析构

    如果 基类的析构函数为虚函数 ,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。 虽然函数名不相同~Person() ,~Student() ,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成 destructor

    1. class Person
    2. {
    3. public: //基类的析构函数为虚函数
    4. virtual ~Person() {cout << "~Person()" << endl;}
    5. };
    6. class Student : public Person
    7. {
    8. public:
    9. virtual ~Student() { cout << "~Student()" << endl; }
    10. };
    11. //只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
    12. int main()
    13. {
    14. Person* p1 = new Person;
    15. Person* p2 = new Student;
    16. delete p1;
    17. delete p2;
    18. return 0;
    19. }

    关键字final和override(C++11)

    final:修饰虚函数,使虚函数不能被覆盖。(加在父类中)
               final修饰类时,表示这个类不能被继承。

    override:修饰虚函数,检测是否正确覆盖。(加在子类中)

    1. class A
    2. {
    3. public:
    4. virtual void test()final //使该虚函数不能覆盖,若被覆盖就报错
    5. {}
    6. }
    7. class B: public A
    8. {
    9. public:
    10. virtual void test()override //检查虚函数是否正确覆盖,若未覆盖就报错
    11. {}
    12. }

    抽象类

    含纯虚函数的类,称为抽象类

    纯虚函数

    • 在虚函数后面加上 =0,则这个函数为纯虚函数。
    • 包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。
    • 子类继承抽象类后也不能实例化出对象,只有覆盖(重写)纯虚函数,子类才能实例化出对象。
    • 纯虚函数强制子类必须覆盖函数,更加体现出接口继承。
    • 纯虚函数不需要实现功能,只需要声明(重写时再实现功能)
    1. class Car // 抽象类
    2. {
    3. public:
    4. virtual void Drive() = 0; //在虚函数的后面写上 =0,这个函数为 纯虚函数
    5. };
    6. class Benz :public Car
    7. {
    8. public:
    9. virtual void Drive()
    10. {
    11. cout << "Benz-舒适" << endl; //只有 重写纯虚函数 ,派生类才能实例化出对象
    12. }
    13. };
    14. class BMW :public Car
    15. {
    16. public:
    17. virtual void Drive()
    18. {
    19. cout << "BMW-操控" << endl;
    20. }
    21. };
    22. void Test()
    23. {
    24. Car* pBenz = new Benz;
    25. pBenz->Drive();
    26. Car* pBMW = new BMW;
    27. pBMW->Drive();
    28. }

    实现继承与接口继承

    • 普通函数的继承是一种 实现继承 ,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
    • 虚函数的继承是一种 接口继承 ,派生类继承的是基类虚函数的接口, 目的是为了重写,达成多态 ,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
  • 相关阅读:
    Swift--字符、字符串与集合类型
    近世代数——Part1 整数和等价关系
    论文:Bottom-Up Constituency Parsing+document error analysis
    Java中的泛型
    基于springboot vue elementui的物品租赁系统源码
    为了学明白中断机制,我努力了
    基于PYQT5的截图翻译工具
    全志R128芯片应用开发案例——驱动 WS2812 流水灯
    【Vue】集成百度地图
    python用selenium网页模拟时xpath无法定位元素解决方法2
  • 原文地址:https://blog.csdn.net/weixin_73225182/article/details/138185614