• C++ 多态


    引例:

    1. #include
    2. using namespace std;
    3. class Animal
    4. {
    5. public:
    6. void speak()
    7. {
    8. cout<<"动物在说话"<
    9. }
    10. };
    11. class Cat:public Animal
    12. {
    13. public:
    14. void speak()
    15. {
    16. cout<<"小猫在说话"<
    17. }
    18. };
    19. void DoSpeak(Animal &animal)
    20. {
    21. animal.speak();
    22. }
    23. void test01()
    24. {
    25. Cat cat;
    26. DoSpeak(cat);
    27. }
    28. int main()
    29. {
    30. test01();
    31. }

    这段代码会显示动物在说话,但函数中的本意是想显示小猫在说话。

    因为

    void DoSpeak(Animal &animal)

    的地址在编译阶段已经被绑定了,

    如果想执行小猫在说话,那么就要使用动态多态的技术,使函数的地址在运行时绑定。

    零、什么是多态?

    多态是面向对象编程中的一个概念,指同一个方法或操作可以被不同的对象调用,产生不同的结果。也可以理解为同一个接口,不同的实现方式。

    在多态的概念中,通过继承,子类可以重写父类的方法,从而实现多态。例如,一个父类有一个某方法,子类可以继承该父类,并重写该方法,从而实现不同的行为。

    多态的好处在于,可以增强代码的灵活性和可扩展性,让代码更加面向对象。

    一、满足动态多态的条件:

    1.有继承关系

    2.子类重写父类的虚函数(重写:函数除了函数体,函数头一模一样)

    二 、动态多态的使用:

    父类的指针或引用 执行子类对象

    父类中的被重写函数前面加上virtual,变成虚函数。

    例如:void DoSpeak(Animal &animal) 中的 Animal &animal

    1. #include
    2. using namespace std;
    3. class Animal
    4. {
    5. public:
    6. virtual void speak()//虚函数
    7. {
    8. cout<<"动物在说话"<
    9. }
    10. };
    11. class Cat:public Animal
    12. {
    13. public:
    14. void speak()
    15. {
    16. cout<<"小猫在说话"<
    17. }
    18. };
    19. void DoSpeak(Animal &animal)
    20. {
    21. animal.speak();
    22. }
    23. void test01()
    24. {
    25. Cat cat;
    26. DoSpeak(cat);
    27. }
    28. int main()
    29. {
    30. test01();
    31. }

    三、多态的原理

    1. #include
    2. using namespace std;
    3. class Animal
    4. {
    5. public:
    6. void speak()
    7. {
    8. cout<<"动物在说话"<
    9. }
    10. };
    11. class Cat:public Animal
    12. {
    13. public:
    14. void speak()
    15. {
    16. cout<<"小猫在说话"<
    17. }
    18. };
    19. void DoSpeak(Animal &animal)
    20. {
    21. animal.speak();
    22. }
    23. void test01()
    24. {
    25. Cat cat;
    26. DoSpeak(cat);
    27. }
    28. void test02()
    29. {
    30. cout<<"sizeof(Animal) = "<<sizeof(Animal)<
    31. }
    32. int main()
    33. {
    34. //test01();
    35. test02();
    36. }

    父类中的speak没有变成虚函数前,父类的大小时1字节,也就是一个空类

    1. #include
    2. using namespace std;
    3. class Animal
    4. {
    5. public:
    6. void speak()
    7. {
    8. cout<<"动物在说话"<
    9. }
    10. };
    11. class Cat:public Animal
    12. {
    13. public:
    14. void speak()
    15. {
    16. cout<<"小猫在说话"<
    17. }
    18. };
    19. void DoSpeak(Animal &animal)
    20. {
    21. animal.speak();
    22. }
    23. void test01()
    24. {
    25. Cat cat;
    26. DoSpeak(cat);
    27. }
    28. void test02()
    29. {
    30. cout<<"sizeof(Animal) = "<<sizeof(Animal)<
    31. }
    32. int main()
    33. {
    34. //test01();
    35. test02();
    36. }

    加了virtual变成虚函数后,父类大小是8字节,多了一个指针。

    1. #include
    2. using namespace std;
    3. class Animal
    4. {
    5. public:
    6. virtual void speak()
    7. {
    8. cout<<"动物在说话"<
    9. }
    10. };
    11. class Cat:public Animal
    12. {
    13. public:
    14. void speak()
    15. {
    16. cout<<"小猫在说话"<
    17. }
    18. };
    19. void DoSpeak(Animal &animal)
    20. {
    21. animal.speak();
    22. }
    23. void test01()
    24. {
    25. Cat cat;
    26. DoSpeak(cat);
    27. }
    28. void test02()
    29. {
    30. cout<<"sizeof(Animal) = "<<sizeof(Animal)<
    31. }
    32. int main()
    33. {
    34. //test01();
    35. test02();
    36. }

    有虚函数的类会包含一个虚函数指针vfptr

    vfptr会指向一个vftable(虚函数表),vftalble中会存放该虚函数的地址。子类中重写虚函数时会将子类的vftable中的地址覆盖为子类中的虚函数地址。

    四、多态案例(计算器)

    不用多态的版本:

    1. #include
    2. using namespace std;
    3. class Calculator
    4. {
    5. public:
    6. int getResult(string oper)
    7. {
    8. if(oper=="+")
    9. return m_Nums1+m_Nums2;
    10. else if(oper=="-")
    11. return m_Nums1-m_Nums2;
    12. else if(oper=="*")
    13. return m_Nums1*m_Nums2;
    14. }
    15. int m_Nums1;
    16. int m_Nums2;
    17. };
    18. void test01()
    19. {
    20. Calculator c;
    21. c.m_Nums1=10;
    22. c.m_Nums2=10;
    23. cout<"+"<"="<getResult("+")<
    24. cout<"-"<"="<getResult("-")<
    25. cout<"*"<"="<getResult("*")<
    26. }
    27. int main()
    28. {
    29. test01();
    30. }

    如果想要对计算器的操作方式有拓展,需要修改源码。

    真正开发中提倡 “开闭原则”

    对拓展进行开放,对修改进行关闭

    用多态的版本:

    1. #include
    2. using namespace std;
    3. class AbstractCalculator
    4. {
    5. public:
    6. virtual int getResult()
    7. {
    8. return 0;
    9. }
    10. int m_Nums1;
    11. int m_Nums2;
    12. };
    13. class AddCalculator:public AbstractCalculator
    14. {
    15. public:
    16. int getResult()
    17. {
    18. return m_Nums1+m_Nums2;
    19. }
    20. };
    21. class SubCalculator:public AbstractCalculator
    22. {
    23. public:
    24. int getResult()
    25. {
    26. return m_Nums1-m_Nums2;
    27. }
    28. };
    29. class MulCalculator:public AbstractCalculator
    30. {
    31. public:
    32. int getResult()
    33. {
    34. return m_Nums1*m_Nums2;
    35. }
    36. };
    37. void test01()
    38. {
    39. AbstractCalculator *p=new AddCalculator;
    40. p->m_Nums1=100;
    41. p->m_Nums2=100;
    42. cout<m_Nums1<<"+"<m_Nums2<<"="<getResult()<
    43. delete p;
    44. p=new SubCalculator;
    45. p->m_Nums1=100;
    46. p->m_Nums2=100;
    47. cout<m_Nums1<<"*"<m_Nums2<<"="<getResult()<
    48. delete p;
    49. p=new MulCalculator;
    50. p->m_Nums1=100;
    51. p->m_Nums2=100;
    52. cout<m_Nums1<<"*"<m_Nums2<<"="<getResult()<
    53. delete p;
    54. }
    55. int main()
    56. {
    57. test01();
    58. }

     五、纯虚函数与抽象类

    六、多态案例(制作饮品)

    1. #include
    2. using namespace std;
    3. class AbstractDrinking
    4. {
    5. public:
    6. //煮水
    7. virtual void Boil()=0;
    8. //冲泡
    9. virtual void Brew()=0;
    10. //倒入杯中
    11. virtual void PourInCup()=0;
    12. //加入辅料
    13. virtual void PutSomething()=0;
    14. //制作饮品
    15. void makeDrink()
    16. {
    17. Boil();
    18. Brew();
    19. PourInCup();
    20. PutSomething();
    21. }
    22. };
    23. class coffee:public AbstractDrinking
    24. {
    25. public:
    26. //煮水
    27. virtual void Boil()
    28. {
    29. cout<<"煮农夫山泉"<
    30. }
    31. //冲泡
    32. virtual void Brew()
    33. {
    34. cout<<"冲泡咖啡"<
    35. }
    36. //倒入杯中
    37. virtual void PourInCup()
    38. {
    39. cout<<"倒入杯中"<
    40. }
    41. //加入辅料
    42. virtual void PutSomething()
    43. {
    44. cout<<"加入糖与牛奶"<
    45. }
    46. };
    47. class tea:public AbstractDrinking
    48. {
    49. public:
    50. //煮水
    51. virtual void Boil()
    52. {
    53. cout<<"煮矿泉水"<
    54. }
    55. //冲泡
    56. virtual void Brew()
    57. {
    58. cout<<"冲茶叶"<
    59. }
    60. //倒入杯中
    61. virtual void PourInCup()
    62. {
    63. cout<<"倒入杯中"<
    64. }
    65. //加入辅料
    66. virtual void PutSomething()
    67. {
    68. cout<<"加入枸杞"<
    69. }
    70. };
    71. void doWork(AbstractDrinking *abs)
    72. {
    73. abs->makeDrink();
    74. delete abs;
    75. }
    76. void test01()
    77. {
    78. doWork(new coffee);
    79. cout<<"------------------"<
    80. doWork(new tea);
    81. }
    82. int main()
    83. {
    84. test01();
    85. }

    七、虚析构与纯虚析构

    父类指针在析构时候,不会调用子类中的析构函数,导致子类如果右堆区属性,出现内存泄露

    1. #include
    2. using namespace std;
    3. class Animal
    4. {
    5. public:
    6. Animal()
    7. {
    8. cout<<"Animal的构造函数调用"<
    9. }
    10. virtual void speak()=0;
    11. ~Animal()
    12. {
    13. cout<<"Animal的析构函数调用"<
    14. }
    15. };
    16. class Cat:public Animal
    17. {
    18. public:
    19. Cat(string name)
    20. {
    21. cout<<"Cat的构造函数调用"<
    22. m_Name=new string(name);
    23. }
    24. void speak()
    25. {
    26. cout<<*m_Name<<"小猫在说话"<
    27. }
    28. ~Cat()
    29. {
    30. if(m_Name!=nullptr)
    31. {
    32. cout<<"Cat的析构函数调用"<
    33. delete m_Name;
    34. m_Name=nullptr;
    35. }
    36. }
    37. string *m_Name;
    38. };
    39. void test01()
    40. {
    41. Animal *animal=new Cat("tom");
    42. animal->speak();
    43. delete animal;
    44. }
    45. int main()
    46. {
    47. test01();
    48. }

    在父类的析构函数前加上virtual,就可以解决问题。

    纯虚析构:virtual ~Animal()=0;

    类外

    Animal::~Animal()
    {
        cout<<"Animal纯虚析构函数调用"< }

    纯虚析构必须要有具体的函数实现

    1. #include
    2. using namespace std;
    3. class Animal
    4. {
    5. public:
    6. Animal()
    7. {
    8. cout<<"Animal的构造函数调用"<
    9. }
    10. virtual void speak()=0;
    11. /*virtual~Animal()
    12. {
    13. cout<<"Animal的虚析构函数调用"<
    14. }
    15. */
    16. virtual~Animal()=0;
    17. };
    18. Animal::~Animal()
    19. {
    20. cout<<"Animal纯虚析构函数调用"<
    21. }
    22. class Cat:public Animal
    23. {
    24. public:
    25. Cat(string name)
    26. {
    27. cout<<"Cat的构造函数调用"<
    28. m_Name=new string(name);
    29. }
    30. void speak()
    31. {
    32. cout<<*m_Name<<"小猫在说话"<
    33. }
    34. ~Cat()
    35. {
    36. if(m_Name!=nullptr)
    37. {
    38. cout<<"Cat的析构函数调用"<
    39. delete m_Name;
    40. m_Name=nullptr;
    41. }
    42. }
    43. string *m_Name;
    44. };
    45. void test01()
    46. {
    47. Animal *animal=new Cat("tom");
    48. animal->speak();
    49. delete animal;
    50. }
    51. int main()
    52. {
    53. test01();
    54. }

  • 相关阅读:
    嵌入式系统日志轮转:实现与性能考量
    vs code 添加vue3代码模板方法
    redis主从
    面向切面编程的一些概念
    linux内存空间深度清理
    Springboot建筑造价师资格考试应试网站毕业设计源码260839
    Selenium——利用input标签上传文件
    【Pytorch学习】Transforms
    WSL+vscode配置miniob环境
    仿真测试断开服务器公网连接
  • 原文地址:https://blog.csdn.net/ghblk/article/details/132699598