• C++中的多态


    抽象类

    概念

    在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口
    类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
    类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
    1. class Car
    2. {
    3. public:
    4. virtual void Drive() = 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. }

    接口继承和实现继承

    普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
    现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成
    多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

     多态的原理

    虚函数表

    1. class A
    2. {
    3. public:
    4. virtual void Func()
    5. {
    6. cout << "Func()" << endl;
    7. }
    8. private:
    9. int _a = 1;
    10. };
    11. // 在这个题目中:sizeof(A)是多少?

    通过观察测试我们发现_a对象是8byte,除了_a成员,还多一个__vfptr放在对象的前面(注意有些
    平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代
    表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数
    的地址要被放到虚函数表中,虚函数表也简称虚表。那么派生类中这个表放了些什么呢?我们
    接着往下分析。
    针对上面的代码我们做出以下改变
    1. 我们增加一个派生类B去继承A
    2. B中重写Func1
    3. A再增加一个虚函数Func2和一个普通函数Func3
    1. class A
    2. {
    3. public:
    4. virtual void Func1()
    5. {
    6. cout << "A::Func1()" << endl;
    7. }
    8. virtual void Func2()
    9. {
    10. cout << "A::Func2()" << endl;
    11. }
    12. void Func3()
    13. {
    14. cout << "A::Func3()" << endl;
    15. }
    16. private:
    17. int _a = 1;
    18. };
    19. class B : public A
    20. {
    21. public:
    22. virtual void Func1()
    23. {
    24. cout << "B::Func1()" << endl;
    25. }
    26. private:
    27. int _b = 2;
    28. };
    29. int main()
    30. {
    31. A a;
    32. B b;
    33. return 0;
    34. }
    通过观察和测试,我们发现了以下几点问题:
    1. 派生类对象b中也有一个虚表指针,b对象由两部分构成,一部分是父类继承下来的成员,虚 表指针也就是存在部分的另一部分是自己的成员。
    2. 基类a对象和派生类b对象虚表是不一样的,这里我们发现Func1完成了重写,所以b的虚表中存的是重写的B::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数 的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
    3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。
    4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
    5. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中;b.如果派生 类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数; c.派生类自己 新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
    6. 这里还有一个问题:虚函数存在哪?虚表存在哪? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答是错的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。

    动态绑定与静态绑定

    1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
    2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。 

    1. //关于多态的题目
    2. class A
    3.   {
    4.   public:
    5.       virtual void func(int val = 1){ std::cout<<"A->"<< val <
    6.       virtual void test(){ func();}
    7.   };
    8.  
    9.   class B : public A
    10.   {
    11.   public:
    12.       void func(int val=0){ std::cout<<"B->"<< val <
    13.   };
    14.  
    15.   int main(int argc ,char* argv[])
    16.   {
    17.       B*p = new B;
    18.       p->test();
    19.       return 0;
    20.   }
    上述程序输出结果是什么?
    A 与 B 中的 func 函数构成了多态,B 中的 func 函数是对 A 中的重写,函数的声明部分(如virtual 和 缺省值等)保持不变,所以结果是B->1 。
  • 相关阅读:
    go的orm框架-Gorm
    如何做知识沉淀?我有什么知识沉淀?
    建一个chrome插件crx所需步骤
    实现不同局域网文件共享的解决方案:使用Python自带HTTP服务和端口映射
    【Qt6.3基础教程01】 Qt简介与环境搭建
    git新建仓库上传项目步骤
    分分钟教你读取 resources 目录下的文件路径
    wps/word中字体安装教程
    Sunwing.ca requests下单 请求参数介绍
    ARM汇编学习录 3 - 调试/编译
  • 原文地址:https://blog.csdn.net/2302_80190174/article/details/140317493