• CPP 核心编程6-多态


     

    1. #include "iostream"
    2. using namespace std;
    3. //多态
    4. class Animal
    5. {
    6. public:
    7. void speak()
    8. {
    9. cout << "动物在说话" << endl;
    10. }
    11. };
    12. class Cat : public Animal
    13. {
    14. public:
    15. void speak()
    16. {
    17. cout << "cat在说话" << endl;
    18. }
    19. };
    20. //地址早绑定 在编辑阶段确定函数地址
    21. //如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
    22. void speak(Animal &animal) // Animal & animal = cat;
    23. {
    24. animal.speak();
    25. }
    26. int main()
    27. {
    28. Cat cat;
    29. speak(cat); //输出的是动物在说话
    30. return 0;
    31. }
    32. 动物在说话

    想要让猫说话就需要将Animal 的speak方法定义为虚函数

    1. #include "iostream"
    2. using namespace std;
    3. //多态
    4. class Animal
    5. {
    6. public:
    7. //虚函数
    8. virtual void speak()
    9. {
    10. cout << "动物在说话" << endl;
    11. }
    12. };
    13. class Cat : public Animal
    14. {
    15. public:
    16. void speak()
    17. {
    18. cout << "cat在说话" << endl;
    19. }
    20. };
    21. //地址早绑定 在编辑阶段确定函数地址
    22. //如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
    23. void speak(Animal &animal) // Animal & animal = cat;
    24. {
    25. animal.speak();
    26. }
    27. int main()
    28. {
    29. Cat cat;
    30. speak(cat); //输出的cat在说话
    31. return 0;
    32. }
    33. cat在说话

    多态满足条件

    1 有继承关系

    2 子类要重写父类的虚函数

    动态多态的使用

    父类的指针或引用指向子类对象

     多态的原理:

    1. #include "iostream"
    2. using namespace std;
    3. class Animal {
    4. public:
    5. virtual void speak(){
    6. cout << "动物在说话" << endl;
    7. }
    8. };
    9. class Animal2 {
    10. public:
    11. void speak(){
    12. cout << "动物在说话" << endl;
    13. }
    14. };
    15. void test1(){
    16. //空对象的大小为1
    17. cout << "sizeof Animal2:"<<sizeof(Animal2) << endl;
    18. //64位系统下,虚函数占8个字节,也就是一个指针的大小
    19. cout << "sizeof Animal:"<<sizeof(Animal) << endl;
    20. }
    21. int main() {
    22. test1();
    23. return 0;
    24. }
    25. sizeof Animal:8
    26. sizeof Animal2:1

    空对象内存分布:

     

     

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

    原理:

    由于Animal些了个虚函数,类结构发生了改变,多了一个指针,这个指针叫做虚函数表指针,虚函数表的内部记录了虚函数的入口地址,当子类重写了这个虚函数的时候,会把自身虚函数表中的函数给替换掉,替换为子类的函数,所以当你用父类的引用指向子类对象的时候,由于你本身是一个子类对象,所以当你去调用speak时,会在子类去找真正的函数地址。

    Cat没发生重写时Cat的内存分布:

     

    多态案例:

    开闭原则:对修改进行关闭,对扩展进行开放

    1. #include "iostream"
    2. using namespace std;
    3. class AbstractCalculator {
    4. public:
    5. virtual int getResult() = 0;
    6. int m_Num1;
    7. int m_Num2;
    8. };
    9. class AddCalculator : public AbstractCalculator {
    10. public:
    11. virtual int getResult() {
    12. return m_Num1 + m_Num2;
    13. }
    14. };
    15. class SubCalculator : public AbstractCalculator {
    16. public:
    17. virtual int getResult() {
    18. return m_Num1 - m_Num2;
    19. }
    20. };
    21. class MulCalculator : public AbstractCalculator {
    22. public:
    23. virtual int getResult() {
    24. return m_Num1 * m_Num2;
    25. }
    26. };
    27. void test() {
    28. //多态使用:父类指针或者引用指向子类对象
    29. AbstractCalculator *ac = new MulCalculator;
    30. ac->m_Num1 = 10;
    31. ac->m_Num2 = 20;
    32. cout << ac->getResult() << endl;
    33. //开辟在堆区,用完记得销毁
    34. delete ac;
    35. }
    36. int main() {
    37. test();
    38. return 0;
    39. }

    总结:多态的优点:

    代码组织结构清晰

    可读性强

    利于前期和后期的扩展以及维护

    1. #include "iostream"
    2. using namespace std;
    3. //纯虚函数和抽象类
    4. class Base {
    5. public:
    6. //纯虚函数
    7. //只要有一个纯虚函数,这个类就称为抽象类
    8. //抽象类特点:
    9. //1 无法实例化对象
    10. //2 抽象类的子类,必须重写父类中的纯虚函数,否则也属于抽象类
    11. virtual void func() = 0;
    12. };
    13. class Son : public Base {
    14. public:
    15. void func() {
    16. cout << "son func函数调用" << endl;
    17. }
    18. };
    19. class Son2 : public Base {
    20. public:
    21. void func() {
    22. cout << "son2 func函数调用" << endl;
    23. }
    24. };
    25. void test() {
    26. // Base b;//抽象类时无法实例化对象
    27. // new Base;//抽象类时无法实例化对象
    28. //Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象
    29. Base *base = new Son;
    30. base->func();
    31. Base *base2 = new Son2;
    32. base2->func();
    33. }
    34. int main() {
    35. test();
    36. return 0;
    37. }
    38. son func函数调用
    39. son2 func函数调用

     

    1. #include "iostream"
    2. using namespace std;
    3. //多态案例2 制作饮品
    4. class AbstractDrink {
    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. Boil();
    17. Brew();
    18. PourInCup();
    19. PutSomething();
    20. }
    21. };
    22. class Coffee : public AbstractDrink {
    23. public:
    24. //煮水
    25. virtual void Boil() {
    26. cout << "咖啡煮水" << endl;
    27. }
    28. //冲泡
    29. virtual void Brew() {
    30. cout << "咖啡冲泡" << endl;
    31. }
    32. //倒入杯中
    33. virtual void PourInCup() {
    34. cout << "咖啡倒入杯中" << endl;
    35. };
    36. //加入辅料
    37. virtual void PutSomething() {
    38. cout << "咖啡加入辅料" << endl;
    39. }
    40. };
    41. class Tea : public AbstractDrink {
    42. public:
    43. //煮水
    44. virtual void Boil() {
    45. cout << "茶叶煮水" << endl;
    46. }
    47. //冲泡
    48. virtual void Brew() {
    49. cout << "茶叶冲泡" << endl;
    50. }
    51. //倒入杯中
    52. virtual void PourInCup() {
    53. cout << "茶叶倒入杯中" << endl;
    54. };
    55. //加入辅料
    56. virtual void PutSomething() {
    57. cout << "茶叶加入辅料" << endl;
    58. }
    59. };
    60. void doWork(AbstractDrink *ad) {
    61. ad->makeDrink();
    62. delete ad;//释放
    63. }
    64. int main() {
    65. doWork(new Coffee);
    66. cout << "------------------------" << endl;
    67. doWork(new Tea);
    68. return 0;
    69. }
    70. 咖啡煮水
    71. 咖啡冲泡
    72. 咖啡倒入杯中
    73. 咖啡加入辅料
    74. ------------------------
    75. 茶叶煮水
    76. 茶叶冲泡
    77. 茶叶倒入杯中
    78. 茶叶加入辅料

     

    虚析构和纯虚析构

    下面有问题的代码:

    1. #include "iostream"
    2. using namespace std;
    3. //虚析构和纯虚析构
    4. class Animal {
    5. public:
    6. Animal() {
    7. cout << "Animal构造函数调用" << endl;
    8. }
    9. ~Animal() {
    10. cout << "Animal的析构函数调用" << endl;
    11. }
    12. //纯虚函数
    13. virtual void speak() = 0;
    14. };
    15. class Cat : public Animal {
    16. public:
    17. Cat(string name) {
    18. cout << "Cat的构造函数调用" << endl;
    19. m_Name = new string(name);
    20. }
    21. ~Cat() {
    22. if (m_Name != nullptr) {
    23. cout << "Cat析构函数调用" << endl;
    24. delete m_Name;
    25. m_Name = nullptr;
    26. }
    27. }
    28. virtual void speak() {
    29. cout << *m_Name << "小猫在说话" << endl;
    30. }
    31. string *m_Name;
    32. };
    33. void test1() {
    34. Animal *animal = new Cat("Tom");
    35. animal->speak();
    36. //父类指针在析构的时候,不会调用子类中析构函数,
    37. //导致子类如果有堆区的属性,会出现内存泄漏
    38. delete animal;
    39. }
    40. int main() {
    41. test1();
    42. return 0;
    43. }
    44. Animal构造函数调用
    45. Cat的构造函数调用
    46. Tom小猫在说话
    47. Animal的析构函数调用

    上面没有输出Cat的析构函数

    解决上面的办法也很简单,就是把父类Animal的析构函数改为虚析构函数

    代码如下:

    1. #include "iostream"
    2. using namespace std;
    3. //虚析构和纯虚析构
    4. class Animal {
    5. public:
    6. Animal() {
    7. cout << "Animal构造函数调用" << endl;
    8. }
    9. //利用虚析构可以解决 父类指针释放子类对象时不干净的问题
    10. virtual ~Animal() {
    11. cout << "Animal的析构函数调用" << endl;
    12. }
    13. //纯虚函数
    14. virtual void speak() = 0;
    15. };
    16. class Cat : public Animal {
    17. public:
    18. Cat(string name) {
    19. cout << "Cat的构造函数调用" << endl;
    20. m_Name = new string(name);
    21. }
    22. ~Cat() {
    23. if (m_Name != nullptr) {
    24. cout << "Cat析构函数调用" << endl;
    25. delete m_Name;
    26. m_Name = nullptr;
    27. }
    28. }
    29. virtual void speak() {
    30. cout << *m_Name << "小猫在说话" << endl;
    31. }
    32. string *m_Name;
    33. };
    34. void test1() {
    35. Animal *animal = new Cat("Tom");
    36. animal->speak();
    37. //父类指针在析构的时候,不会调用子类中析构函数,
    38. // 导致子类如果有堆区的属性,会出现内存泄漏
    39. delete animal;
    40. }
    41. int main() {
    42. test1();
    43. return 0;
    44. }
    45. Animal构造函数调用
    46. Cat的构造函数调用
    47. Tom小猫在说话
    48. Cat析构函数调用
    49. Animal的析构函数调用

    纯虚析构,直接执行会报错,大概提示就是无法解析的外部命令

    1. #include "iostream"
    2. using namespace std;
    3. //虚析构和纯虚析构
    4. class Animal {
    5. public:
    6. Animal() {
    7. cout << "Animal构造函数调用" << endl;
    8. }
    9. //利用虚析构可以解决 父类指针释放子类对象时不干净的问题
    10. // virtual ~Animal() {
    11. // cout << "Animal的析构函数调用" << endl;
    12. // }
    13. //纯虚析构
    14. virtual ~Animal() = 0;
    15. //纯虚函数
    16. virtual void speak() = 0;
    17. };
    18. class Cat : public Animal {
    19. public:
    20. Cat(string name) {
    21. cout << "Cat的构造函数调用" << endl;
    22. m_Name = new string(name);
    23. }
    24. ~Cat() {
    25. if (m_Name != nullptr) {
    26. cout << "Cat析构函数调用" << endl;
    27. delete m_Name;
    28. m_Name = nullptr;
    29. }
    30. }
    31. virtual void speak() {
    32. cout << *m_Name << "小猫在说话" << endl;
    33. }
    34. string *m_Name;
    35. };
    36. void test1() {
    37. Animal *animal = new Cat("Tom");
    38. animal->speak();
    39. //父类指针在析构的时候,不会调用子类中析构函数,
    40. // 导致子类如果有堆区的属性,会出现内存泄漏
    41. delete animal;
    42. }
    43. int main() {
    44. test1();
    45. return 0;
    46. }

    虚析构的时候,是有函数的实现的,

    但是上面的纯虚析构没有实现,比如父类中也有一些数据开辟到了堆区,析构函数实现就有用了,父类堆区实现在父类析构函数中实现。

    解决方案,在类外实现析构函数

    1. Animal::~Animal() {
    2. cout << "Animal纯虚析构函数调用" << endl;
    3. }
    1. #include "iostream"
    2. using namespace std;
    3. //虚析构和纯虚析构
    4. class Animal {
    5. public:
    6. Animal() {
    7. cout << "Animal构造函数调用" << endl;
    8. }
    9. //利用虚析构可以解决 父类指针释放子类对象时不干净的问题
    10. // virtual ~Animal() {
    11. // cout << "Animal的析构函数调用" << endl;
    12. // }
    13. //纯虚析构 需要声明,也需要实现(类外实现)
    14. //有了纯虚析构之后,这个类属于抽象类,无法实例化
    15. virtual ~Animal() = 0;
    16. //纯虚函数
    17. virtual void speak() = 0;
    18. };
    19. Animal::~Animal() {
    20. cout << "Animal纯虚析构函数调用" << endl;
    21. }
    22. class Cat : public Animal {
    23. public:
    24. Cat(string name) {
    25. cout << "Cat的构造函数调用" << endl;
    26. m_Name = new string(name);
    27. }
    28. ~Cat() {
    29. if (m_Name != nullptr) {
    30. cout << "Cat析构函数调用" << endl;
    31. delete m_Name;
    32. m_Name = nullptr;
    33. }
    34. }
    35. virtual void speak() {
    36. cout << *m_Name << "小猫在说话" << endl;
    37. }
    38. string *m_Name;
    39. };
    40. void test1() {
    41. Animal *animal = new Cat("Tom");
    42. animal->speak();
    43. //父类指针在析构的时候,不会调用子类中析构函数,
    44. // 导致子类如果有堆区的属性,会出现内存泄漏
    45. delete animal;
    46. }
    47. int main() {
    48. test1();
    49. return 0;
    50. }
    51. Animal构造函数调用
    52. Cat的构造函数调用
    53. Tom小猫在说话
    54. Cat析构函数调用
    55. Animal纯虚析构函数调用

    多态案例3 电脑组装

    1. #include "iostream"
    2. using namespace std;
    3. class CPU {
    4. public:
    5. virtual void calculator() = 0;
    6. };
    7. class VideoCard {
    8. public:
    9. virtual void display() = 0;
    10. };
    11. class Memory {
    12. public:
    13. virtual void storage() = 0;
    14. };
    15. class Computer {
    16. public:
    17. Computer(CPU *cpu, VideoCard *vCard, Memory *mem) {
    18. m_cpu = cpu;
    19. m_vCard = vCard;
    20. m_mem = mem;
    21. }
    22. void work() {
    23. m_cpu->calculator();
    24. m_vCard->display();
    25. m_mem->storage();
    26. }
    27. //提供析构函数来释放3个零件
    28. ~Computer() {
    29. if (m_cpu != nullptr) {
    30. delete m_cpu;
    31. m_cpu = nullptr;
    32. }
    33. if (m_vCard != nullptr) {
    34. delete m_vCard;
    35. m_vCard = nullptr;
    36. }
    37. if (m_mem != nullptr) {
    38. delete m_mem;
    39. m_mem = nullptr;
    40. }
    41. }
    42. private:
    43. CPU *m_cpu;
    44. VideoCard *m_vCard;
    45. Memory *m_mem;
    46. };
    47. //具体厂商 Intel
    48. class IntelCPU : public CPU {
    49. public:
    50. virtual void calculator() {
    51. cout << "Intel CPU" << endl;
    52. }
    53. };
    54. class IntelVideoCard : public VideoCard {
    55. public:
    56. virtual void display() {
    57. cout << "Intel VideoCard" << endl;
    58. }
    59. };
    60. class IntelMemory : public Memory {
    61. public:
    62. virtual void storage() {
    63. cout << "Intel Memory" << endl;
    64. }
    65. };
    66. //具体厂商 Lenovo
    67. class LenovoCPU : public CPU {
    68. public:
    69. virtual void calculator() {
    70. cout << "Lenovo CPU" << endl;
    71. }
    72. };
    73. class LenovoVideoCard : public VideoCard {
    74. public:
    75. virtual void display() {
    76. cout << "Lenovo VideoCard" << endl;
    77. }
    78. };
    79. class LenovoMemory : public Memory {
    80. public:
    81. virtual void storage() {
    82. cout << "Lenovo Memory" << endl;
    83. }
    84. };
    85. void test() {
    86. //创建第一台电脑
    87. Computer *computer = new Computer(new LenovoCPU, new IntelVideoCard, new IntelMemory);
    88. computer->work();
    89. cout << "------------------" << endl;
    90. //创建第二台电脑
    91. CPU *cpu2 = new IntelCPU;
    92. VideoCard *vc2 = new IntelVideoCard;
    93. Memory *mem2 = new LenovoMemory;
    94. Computer *computer2 = new Computer(cpu2, vc2, mem2);
    95. computer2->work();
    96. }
    97. int main() {
    98. test();
    99. return 0;
    100. }
    101. Lenovo CPU
    102. Intel VideoCard
    103. Intel Memory
    104. ------------------
    105. Intel CPU
    106. Intel VideoCard
    107. Lenovo Memory

  • 相关阅读:
    python 文本文件的编码格式:ASCII编码和UNICODE编码
    排序算法——希尔排序
    递推算法刷题
    树莓派4B(Ubuntu20.04)使用LCD1602液晶屏开机自动显示IP及其他信息
    Java 中的全部锁
    如何为 Python 应用选择最好的 Docker 镜像?
    Java 方法中循环调用具有事务的方法
    如何读懂火焰图?+ 实例讲解程序性能优化
    【驱动开发】LED灯的亮灭——通过字符设备驱动的分步实现编写LED驱动,实现设备文件和设备的绑定
    大数据打造六维车险评估体系,律商风险再出发
  • 原文地址:https://blog.csdn.net/wade1010/article/details/128191902