• C++基础——多态


    1 概述

    多态是面向对象的三大特性之一,说的是同一个事物有不同的行为。
    多态可分为:

    • 静态多态:函数重载和运算符重载
    • 动态多态:派生类和虚函数实现
      静态多态和动态多态的区别:
    • 静态多态:函数地址早期绑定 - 编译阶段确定函数地址
    • 动态多态:函数地址晚期绑定 - 运行阶段确定函数地址

    2 多态基本概念

    #include 
    
    using namespace std;
    
    class Father {
    public:
        virtual void func() {
            cout << "father func" << endl;
        }
        //void func() {
        //    cout << "father func" << endl;
        //}
    };
    
    class Son : public Father {
    public:
        void func() {
            cout << "son func" << endl;
        }
    };
    
    void test(Father &father) {
        father.func();
    }
    
    int main() {
        Father father;
        father.func();
        Son son;
        son.func();
        test(father);
        test(son);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    输出

    未使用virtual
    father func
    son func
    father func
    father func
    使用virtual
    father func
    son func
    father func
    son func
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用自己的对象调用方法,则是和继承里面的处理相同。如果存在同名函数,子类直接调用为子类的函数,使用作用域限定符调用父类的函数。
    如果没有virtual修饰,即使有父类引用指向子类对象,调用的也是父类的同名函数,而不会体现出多态行为。
    而将父类的函数标记为virtual之后,就能使继承中存在多态关系,多态需满足条件:

    • 有继承关系
    • 子类重写父类中的虚函数
      多态使用条件:
      父类指针指向子类对象
      多态最常用的场景就是用父类的引用作为参数,传入不同的子类对象,能够实现不同的行为。

    3 多态的使用

    使用多态实现一个计算器类
    普通实现:

    #include 
    
    using namespace std;
    
    class Calculator {
    public:
        int getResult(string oper) {
            if (oper == "+") {
                return m_Num1 + m_Num2;
            } else if (oper == "-") {
                return m_Num1 - m_Num2;
            } else if (oper == "*") {
                return m_Num1 * m_Num2;
            }
            //如果要提供新的运算,需要修改源码
        }
    
    public:
        int m_Num1;
        int m_Num2;
    };
    
    void test01() {
        Calculator c;
        c.m_Num1 = 10;
        c.m_Num2 = 10;
        cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
    
        cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
    
        cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
    }
    
    int main() {
        test01();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    普通实现,通过判断来实现,如果需要新增算法,则需要改动类,对于类的封装性和扩展性都不好。
    如果有了多态,可以这样实现:

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

    通过继承和多态来实现,封装性和扩展性都要更强一些。

    4 纯虚函数和抽象类

    在多态中,通常父类中的虚函数没有实现,提供给子类重写,所以可以设置成纯虚函数
    当类中有纯虚函数,这个类称为抽象类
    抽象类的特点:

    • 无法实例化对象
    • 子类必须重写抽象类中的纯虚函数,否则也是抽象类
    #include 
    
    using namespace std;
    
    class Father {
    public:
        virtual void func() = 0;
    };
    
    class Son : public Father {
    public:
        virtual void func() {
            cout << "Son func" << endl;
        }
    };
    
    void test() {
        // Father father;
        Father *father;
        father = new Son;
        father->func();
        delete father;
    }
    
    int main() {
        test();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    输出

    Son func
    
    • 1

    5 虚析构和纯虚析构

    多态使用时,如果子类有属性开辟到堆区,那么父类指针释放的时候无法调用到子类的析构函数

    #include 
    
    using namespace std;
    
    class Father {
    public:
        virtual void func() = 0;
    };
    
    class Son : public Father {
    public:
        ~Son() {
            cout << "Son deconstructor" << endl;
        }
    
        virtual void func() {
            cout << "Son func" << endl;
        }
    };
    
    void test() {
        Father *father;
        father = new Son;
        father->func();
        delete father;
    }
    
    int main() {
        test();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    输出:

    Son func
    
    • 1

    没有子类析构函数的打印
    对于此问题,可以将父类中的析构函数改为虚析构或者纯虚析构
    虚析构和纯虚析构共性:

    • 可以解决父类指针释放子类对象
    • 都需要有具体的函数实现

    虚析构和纯虚析构区别:

    • 如果是纯虚析构,该类属于抽象类,无法实例化对象
    #include 
    
    using namespace std;
    
    class Father {
    public:
        virtual void func() = 0;
        virtual ~Father() {
            cout << "Father deconstructor" << endl;
        }
        // virtual ~Father() = 0;
    };
    
    // Father::~Father() {
    //     cout << "Father deconstructor" << endl;
    // }
    
    class Son : public Father {
    public:
        ~Son() {
            cout << "Son deconstructor" << endl;
        }
    
        virtual void func() {
            cout << "Son func" << endl;
        }
    };
    
    void test() {
        Father *father;
        father = new Son;
        father->func();
        delete father;
    }
    
    int main() {
        test();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    输出

    Son func
    Son deconstructor
    Father deconstructor
    
    • 1
    • 2
    • 3

    此时子类析构函数被调用,可以在子类析构中进行动态内存释放。

    • 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
    • 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
    • 拥有纯虚析构函数的类也属于抽象类
  • 相关阅读:
    SPARKSQL3.0-源码剖析全流程导读
    【栈、树、字符串、二叉树-中等】331. 验证二叉树的前序序列化
    Java Spring Cloud XVIII 之 Kafka I
    《无与伦比》Centos7 安装nginx
    使用jackson将xml和对象、List相互转换
    hive 中正则表表达式使用
    java反序列化漏洞详解
    .NET周刊【7月第1期 2024-07-07】
    LintCode 3210: Train access (栈好题)
    JVM调优
  • 原文地址:https://blog.csdn.net/weixin_49274713/article/details/134387779