• C++学习笔记(十六)


    在完成对C语言的学习后,我最近开始了对C++和Java的学习,目前跟着视频学习了一些语法,也跟着敲了一些代码,有了一定的掌握程度。现在将跟着视频做的笔记进行整理。本篇博客是整理C++知识点的第十六篇博客。

    本篇博客介绍了C++的多态。

    本系列博客所有C++代码都在Visual Studio 2022环境下编译运行。程序为64位。

    目录

    多态

    多态的基本语法

    多态的原理

    计算器类

    纯虚函数和抽象类

    制作饮品

    虚析构和纯虚析构


    多态

    多态的基本语法

    多态是C++面向对象的三大特征之一。多态分为静态多态动态多态

    函数重载和运算符重载属于静态多态,静态多态的函数地址早绑定,编译阶段就确定了函数地址。

    子类和虚函数实现属于动态多态,动态多态的函数地址晚绑定,运行阶段才确定函数地址。

    在函数前加virtual表示虚函数。

    多态满足的条件是有继承关系,子类重写父类的虚函数。多态的使用条件是父类指针或引用指向子类对象。

    函数的返回值类型,函数名,参数列表完全一致称为重写。

    1. #include
    2. using namespace std;
    3. class animal
    4. {
    5. public:
    6. void eat(void) {
    7. cout << "Animal can eat something" << endl;
    8. }
    9. };
    10. class dog :public animal
    11. {
    12. public:
    13. void eat(void) {
    14. cout << "Dog can eat something" << endl;
    15. }
    16. };
    17. class cat :public animal
    18. {
    19. public:
    20. void eat(void) {
    21. cout << "Cat can eat something" << endl;
    22. }
    23. };
    24. int main(void)
    25. {
    26. animal an;
    27. cat c;
    28. dog d;
    29. animal& a = c;
    30. a.eat();
    31. animal& b = d;
    32. b.eat();
    33. return 0;
    34. }

    父类animal的成员函数eat输出Animal can eat something,两个子类dog和cat重写了eat函数,分别输出Dog can eat something和Cat can eat something。main函数实现了多态,并分别调用eat成员函数。

    程序的输出是:

    Animal can eat something
    Animal can eat something

    此处animal类的eat函数未用virtual修饰,因此实现多态会调用父类的成员函数。

    1. #include
    2. using namespace std;
    3. class animal
    4. {
    5. public:
    6. virtual void eat(void) {
    7. cout << "Animal can eat something" << endl;
    8. }
    9. };
    10. class dog :public animal
    11. {
    12. public:
    13. void eat(void) {
    14. cout << "Dog can eat something" << endl;
    15. }
    16. };
    17. class cat :public animal
    18. {
    19. public:
    20. void eat(void) {
    21. cout << "Cat can eat something" << endl;
    22. }
    23. };
    24. int main(void)
    25. {
    26. animal an;
    27. cat c;
    28. dog d;
    29. animal& a = c;
    30. a.eat();
    31. animal& b = d;
    32. b.eat();
    33. return 0;
    34. }

    这段代码和上面的差不多,就是把animal类的eat函数用了virtual修饰,变成了虚函数。

    程序的输出是:

    Cat can eat something
    Dog can eat something

    父类的函数变成虚函数,因此调用的是子类成员。

    多态的原理

    每个类有虚函数表,记录虚函数的地址。子类的虚函数表内部会替换成子类的虚函数地址。

    1. #include
    2. using namespace std;
    3. class animal
    4. {
    5. public:
    6. virtual void eat(void) {
    7. cout << "Animal can eat something" << endl;
    8. }
    9. };
    10. class dog :public animal
    11. {
    12. public:
    13. void eat(void) {
    14. cout << "Dog can eat something" << endl;
    15. }
    16. };
    17. class cat
    18. {
    19. public:
    20. void eat(void) {
    21. cout << "Cat can eat something" << endl;
    22. }
    23. };
    24. int main(void)
    25. {
    26. cout << sizeof(cat) << endl;
    27. cout << sizeof(animal) << endl;
    28. return 0;
    29. }

    这段代码的类和上面两个例子一样,程序输出父类和子类的大小。程序的输出是:

    1
    8
    子类存放了一个虚函数表指针,大小为8字节。(32位下为4字节)

    计算器类

    多态有很多优点,可以使代码组织结构清晰,可读性强,利于扩展和维护。

    下面需要实现一个计算器类,实现简单的加减乘除。

    1. #include
    2. using namespace std;
    3. class calculate
    4. {
    5. public:
    6. int a;
    7. int b;
    8. int add(void) {
    9. return a + b;
    10. }
    11. int subtraction(void) {
    12. return a - b;
    13. }
    14. int multiple(void) {
    15. return a * b;
    16. }
    17. int divide(void) {
    18. return a / b;
    19. }
    20. };
    21. int main(void)
    22. {
    23. calculate cal;
    24. cal.a = 8;
    25. cal.b = 6;
    26. cout << cal.add() << endl;
    27. cout << cal.subtraction() << endl;
    28. cout << cal.multiple() << endl;
    29. cout << cal.divide() << endl;
    30. return 0;
    31. }

    这段代码不使用多态。calculate类有两个成员变量a和b,用于运算。下面是四个成员函数,执行加减乘除。main函数创建了一个calculate类对象cal,并执行加减乘除操作。

    程序的输出是:

    14
    2
    48
    1
    但是这一段代码不便于维护,因为如果要增删功能,需要在类中增删函数。下面的代码使用多态实现。

    1. #include
    2. using namespace std;
    3. class calculator {
    4. public:
    5. int a;
    6. int b;
    7. virtual int getresult(void) {
    8. return 0;
    9. }
    10. };
    11. class add:public calculator
    12. {
    13. public:
    14. int getresult(void) {
    15. return a + b;
    16. }
    17. };
    18. class subtraction :public calculator
    19. {
    20. public:
    21. int getresult(void) {
    22. return a - b;
    23. }
    24. };
    25. class multiple :public calculator
    26. {
    27. public:
    28. int getresult(void) {
    29. return a * b;
    30. }
    31. };
    32. class divide :public calculator
    33. {
    34. public:
    35. int getresult(void) {
    36. return a / b;
    37. }
    38. };
    39. int main(void)
    40. {
    41. calculator* cal = new add();
    42. cal->a = 6;
    43. cal->b = 8;
    44. cout << cal->getresult() << endl;
    45. delete cal;
    46. cal = new subtraction();
    47. cal->a = 6;
    48. cal->b = 8;
    49. cout << cal->getresult() << endl;
    50. delete cal;
    51. cal = new multiple();
    52. cal->a = 6;
    53. cal->b = 8;
    54. cout << cal->getresult() << endl;
    55. delete cal;
    56. cal = new divide();
    57. cal->a = 6;
    58. cal->b = 8;
    59. cout << cal->getresult() << endl;
    60. delete cal;
    61. return 0;
    62. }

    calculator类有两个成员变量a和b,和一个虚函数getresult,该函数返回0。add subtraction multiple divide类都以public的方式继承了calculator类,并重写getresult函数实现加减乘除。main函数通过多态进行加减乘除。程序的输出是:

    14
    -2
    48
    0

    这样如果要增删功能,通过增删继承calculator的类即可。

    纯虚函数和抽象类

    多态中,通常父类虚函数的实现是无意义的,用的都是子类中的内容。因此可以将虚函数改为纯虚函数

    纯虚函数的语法是 virtual 返回值类型 函数名(参数列表)  = 0;

    如果类中有了纯虚函数,则称为抽象类。抽象类无法实例化对象。子类必须重写抽象类中的纯虚函数,否则子类也是抽象类。

    1. #include
    2. using namespace std;
    3. class father
    4. {
    5. public:
    6. virtual void show() = 0;
    7. };
    8. class son:public father
    9. {
    10. public:
    11. void show() {
    12. cout << "This is a func" << endl;
    13. }
    14. };
    15. int main(void)
    16. {
    17. father* f = new son();
    18. f->show();
    19. delete f;
    20. return 0;
    21. }

    父类father的show函数是纯虚函数,子类son重写了show函数,输出This is a func。main函数使用了多态,并调用show函数。程序的输出是:

    This is a func
     

    制作饮品

    1. #include
    2. using namespace std;
    3. class drink
    4. {
    5. public:
    6. virtual void first(void) = 0;
    7. virtual void second(void) = 0;
    8. virtual void third(void) = 0;
    9. virtual void fourth(void) = 0;
    10. void show(void) {
    11. first();
    12. second();
    13. third();
    14. fourth();
    15. }
    16. };
    17. class coffee :public drink
    18. {
    19. void first(void) {
    20. cout << "This is the first step to make a coffee" << endl;
    21. }
    22. void second(void) {
    23. cout << "This is the second step to make a coffee" << endl;
    24. }
    25. void third(void) {
    26. cout << "This is the third step to make a coffee" << endl;
    27. }
    28. void fourth(void) {
    29. cout << "This is the fourth step to make a coffee" << endl;
    30. }
    31. };
    32. class tea :public drink
    33. {
    34. void first(void) {
    35. cout << "This is the first step to make a tea" << endl;
    36. }
    37. void second(void) {
    38. cout << "This is the second step to make a tea" << endl;
    39. }
    40. void third(void) {
    41. cout << "This is the third step to make a tea" << endl;
    42. }
    43. void fourth(void) {
    44. cout << "This is the fourth step to make a tea" << endl;
    45. }
    46. };
    47. int main(void)
    48. {
    49. drink* makedrink = new coffee();
    50. makedrink->show();
    51. delete makedrink;
    52. makedrink = new tea();
    53. makedrink->show();
    54. delete makedrink;
    55. return 0;
    56. }

    drink类有first second third fourth四个虚函数,show成员函数依次调用这四个函数。两个子类coffee和tea重写了这四个虚函数。main函数通过drink类指针指向coffee和tea类对象,并调用show函数。程序的输出是:

    This is the first step to make a coffee
    This is the second step to make a coffee
    This is the third step to make a coffee
    This is the fourth step to make a coffee
    This is the first step to make a tea
    This is the second step to make a tea
    This is the third step to make a tea
    This is the fourth step to make a tea
     

    虚析构和纯虚析构

    在使用多态时,如果子类有属性开辟在堆区,那么父类指针释放时无法调用子类的析构函数。解决方法是将父类的虚函数改为虚析构或者纯虚析构。这两个都可以解决父类指针释放时无法析构子类的问题。如果子类没有堆区的数据,可以不写虚析构或者纯虚析构。

    如果用纯虚析构,那么父类是抽象类。纯虚析构需要类外实现。

    虚析构的语法是:

    virtual ~类名(){}

    纯虚析构的语法是:

    virtual ~类名() = 0;

    类名::~类名(){}

    1. #include
    2. using namespace std;
    3. class father
    4. {
    5. public:
    6. father(void) {
    7. cout << "A" << endl;
    8. }
    9. virtual ~father(void) {
    10. cout << "Z" << endl;
    11. }
    12. };
    13. class son : public father
    14. {
    15. int* number = new int(0);
    16. public:
    17. son(void) {
    18. cout << "a" << endl;
    19. }
    20. ~son(void) {
    21. delete number;
    22. cout << "z" << endl;
    23. }
    24. };
    25. int main(void)
    26. {
    27. father* s = new son();
    28. delete s;
    29. return 0;
    30. }

    子类son有数据开辟在堆区。son类的构造函数输出a,析构函数输出z,并释放堆区数据。父类father的构造函数输出A,虚析构函数输出Z。main函数实现了多态,程序的输出是:

    A
    a
    z
    Z
     

    1. #include
    2. using namespace std;
    3. class father
    4. {
    5. public:
    6. father(void) {
    7. cout << "A" << endl;
    8. }
    9. virtual ~father(void) = 0;
    10. };
    11. father::~father(void)
    12. {
    13. cout << "Z" << endl;
    14. }
    15. class son : public father
    16. {
    17. int* number = new int(0);
    18. public:
    19. son(void) {
    20. cout << "a" << endl;
    21. }
    22. ~son(void) {
    23. delete number;
    24. cout << "z" << endl;
    25. }
    26. };
    27. int main(void)
    28. {
    29. father* s = new son();
    30. delete s;
    31. return 0;
    32. }

    这段代码和上一段大致一样,但是将father类的析构函数实现为纯虚析构,将实现移至类外。程序的输出是:

    A
    a
    z
    Z

  • 相关阅读:
    杭州某国企 Java 面经
    Apache Airflow (十一) :HiveOperator及调度HQL
    CDN的优势,为什么各大站点都选择接入CDN
    Selenium实现原理
    Verifiable Secret Sharing
    Java 8 和 11 开始提供的新特性面试
    Java中使用BigDecimal类相除保留两位小数
    基于内存的分布式NoSQL数据库Redis(一)介绍与安装
    网络安全管理制度
    AngularJS学习的代码仓库地址
  • 原文地址:https://blog.csdn.net/m0_71007572/article/details/126353034