• 类和对象 —— 多态


    一、多态概念

            多态,通俗来讲就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。静态多态主要就是函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态,运行时归为动态。

            而运行时多态,通俗来讲就是一个行为(函数),当传递不同的对象时会进行不同的操作。就比如买票时,普通人买时全价,学生买是半价,军人则是优先买票。同样是买票的行为,不同的人对应的操作却不一样,这就是运行时多态。

    二、多态的定义及实现

    (1)构成条件

            多态是⼀个继承关系的下的类对象,去调⽤同⼀函数,产⽣了不同的⾏为。⽐如Student继承了Person。Person对象买票全价,Student对象优惠买票。

    (2)实现条件

            1、必须是指针或者引用调用虚函数

            2、被调用的函数必须是虚函数

    说明:要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向派⽣类对象;第⼆派⽣类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派⽣类才能有不同的函数,多态的不同形态效果才能达到。

            虚函数的定义

            那什么是虚函数呢?在类成员函数前加关键字virtual,该成员函数就称为虚函数,注意,非类成员函数不能加virtual修饰。

    1. class Person
    2. {
    3. public:
    4. virtual void BuyTicket() { cout << "买票-全价" << endl;}
    5. };
            虚函数的重写

            虚函数的重写/覆盖:派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称派⽣类的虚函数重写了基类的虚函数。这样才能根据不同的对象执行不同的行为。

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public:
    7. virtual void BuyTicket() { cout << "买票-打折" << endl; }
    8. };

    在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承
    后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使⽤。        

            完整代码:

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public:
    7. virtual void BuyTicket() { cout << "买票-打折" << endl; }
    8. };
    9. void Func(Person* ptr)//这里是基类的指针调用虚函数
    10. {
    11. // 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket
    12. // 但是跟ptr没关系,⽽是由ptr指向的对象决定的。
    13. ptr->BuyTicket();
    14. }
    15. int main()
    16. {
    17. Person ps;
    18. Student st;
    19. Func(&ps);
    20. Func(&st);
    21. return 0;
    22. }

    此时运行结果就为:买票—全价(换行)买票——打折        

    其他问题

            关于虚函数的重写,还有一点需要注意的是基类的析构函数最好也写成虚函数。这样不管子类的析构是否加virtual,都构成重写。虽然两个类的析构函数名称好像不太一样,但是经过编译器统一处理后,析构函数名称统一编程destructor,构成重写。

            下⾯的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调⽤的A的析构函数,没有调⽤B的析构函数,就会导致内存泄漏问题,因为~B()中在释放资源。

    1. class A
    2. {
    3. public:
    4. virtual ~A()
    5. {
    6. cout << "~A()" << endl;
    7. }
    8. };
    9. class B : public A {
    10. public:
    11. ~B()
    12. {
    13. cout << "~B()->delete:"<<_p<< endl;
    14. delete _p;
    15. }
    16. protected:
    17. int* _p = new int[10];
    18. };
    19. // 只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能
    20. 构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。
    21. int main()
    22. {
    23. A* p1 = new A;
    24. A* p2 = new B;
    25. delete p1;
    26. delete p2;
    27. return 0
    28. }

    三、纯虚函数和抽象类

            在虚函数的后⾯写上 =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. int main()
    23. {
    24. // 编译报错:error C2259: “Car”: ⽆法实例化抽象类
    25. Car car;
    26. Car* pBenz = new Benz;
    27. pBenz->Drive();
    28. Car* pBMW = new BMW;
    29. pBMW->Drive();
    30. return 0;
    31. }

                  

  • 相关阅读:
    Android OpenGL ES 3.0 FBO 离屏渲染
    【35kJava开发岗:基础篇】
    【华为机试真题 Python实现】计算矩形面积
    Dubbo高手之路6,Dubbo 服务治理详解
    如何快速高效的掌握一门新技术
    第十五届蓝桥杯题解-好数
    事件总线 EventBus
    微信小程序(四十五)登入界面-简易版
    无法在CentOS7安装docker
    MySQL 数据库常用命令大全(完整版)
  • 原文地址:https://blog.csdn.net/2301_79873388/article/details/142305035