第 13.6 節

polymorphism

0瀏覽次數0訪問次數--跳出率--平均停留

polymorphism

The basic concept of polymorphism

Polymorphism is one of the three major features of C++ object-oriented programming

Polymorphism is divided into two categories.

  • Static polymorphism: Function overloading and operator overloading are forms of static polymorphism, reusing function names.
  • Dynamic polymorphism: implementing runtime polymorphism with derived classes and virtual functions

Difference between static polymorphism and dynamic polymorphism:

  • Static polymorphism uses early binding - function addresses are determined during compilation.
  • Dynamic polymorphism's late binding of function addresses - Determining function address at runtime

Here is a case study to explain polymorphism.

class Animal
{
public:
    //Speak函数就是虚函数
    //函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat :public Animal
{
public:
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

class Dog :public Animal
{
public:

    void speak()
    {
        cout << "小狗在说话" << endl;
    }

};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

void DoSpeak(Animal & animal)
{
    animal.speak();
}
//
//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
    Cat cat;
    DoSpeak(cat);

    Dog dog;
    DoSpeak(dog);
}

int main() {

    test01();


    return 0;
}

Running/Observing the results: After running, the example will print variable values or addresses; address values depend on the runtime environment, so focus on observing the relative positioning and pointer changes of similar objects.

Summary:

Polymorphism satisfies conditions.

  • has inheritance
  • Subclasses override virtual functions in parent classes.

Conditions for Using Polymorphism

  • Base class pointer or reference pointing to a derived class object

Override means the return type, function name, and parameter list are completely identical.

Polymorphism Example One - Calculator Class

Case description:

Here are two implementations: one using conditional logic, the other using polymorphism.

Approach 1: Conditional Logic (Regular Coding)

public class Calculator {
    public double calculate(double num1, double num2, char operator) {
        switch (operator) {
            case '+':
                return num1 + num2;
            case '-':
                return num1 - num2;
            case '*':
                return num1 * num2;
            case '/':
                if (num2 != 0) {
                    return num1 / num2;
                } else {
                    throw new ArithmeticException("Cannot divide by zero");
                }
            default:
                throw new IllegalArgumentException("Invalid operator: " + operator);
        }
    }
}

Approach 2: Polymorphism

// Define an Operation interface
interface Operation {
    double execute(double num1, double num2);
}

// Implement concrete operations
class Addition implements Operation {
    @Override
    public double execute(double num1, double num2) {
        return num1 + num2;
    }
}

class Subtraction implements Operation {
    @Override
    public double execute(double num1, double num2) {
        return num1 - num2;
    }
}

class Multiplication implements Operation {
    @Override
    public double execute(double num1, double num2) {
        return num1 * num2;
    }
}

class Division implements Operation {
    @Override
    public double execute(double num1, double num2) {
        if (num2 != 0) {
            return num1 / num2;
        } else {
            throw new ArithmeticException("Cannot divide by zero");
        }
    }
}

// Calculator class using polymorphism
public class Calculator {
    private Operation operation;

    // Set the operation dynamically
    public void setOperation(Operation operation) {
        this.operation = operation;
    }

    // Perform the calculation
    public double calculate(double num1, double num2) {
        if (operation == null) {
            throw new IllegalStateException("Operation not set");
        }
        return operation.execute(num1, num2);
    }
}

Key Differences

  • Conditional Logic:
    Uses switch/if-else to handle operations. Adding new operations requires modifying existing code.
  • Polymorphism:
    Uses an interface (Operation) with separate classes for each operation. New operations can be added by implementing new classes, following the Open/Closed Principle (open for extension, closed for modification).

The advantages of polymorphism:

  • clear code organization structure
  • highly readable
  • facilitates early and late-stage expansion and maintenance

Example:

//普通实现
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;
}

//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public :

    virtual int getResult()
    {
        return 0;
    }

    int m_Num1;
    int m_Num2;
};

//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 + m_Num2;
    }
};

//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 - m_Num2;
    }
};

//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 * m_Num2;
    }
};

void test02()
{
    //创建加法计算器
    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() {

    //test01();

    test02();


    return 0;
}

Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.

Summary: C++ development encourages leveraging polymorphism to design program architectures, as it offers numerous advantages.

Pure virtual functions and abstract classes

In polymorphism, the implementation of a virtual function in a parent class is often meaningless, as it primarily serves to call the overridden version in the subclass.

Therefore, the virtual function can be changed to a pure virtual function.

Pure virtual function syntax: virtual 返回值类型 函数名 (参数列表)= 0 ;

When a class has a pure virtual function, it is also called an ==abstract class==.

Abstract class characteristics

  • Cannot instantiate object
  • Derived classes must override the pure virtual functions in abstract classes; otherwise, they remain abstract classes.

Example:

class Base
{
public:
    //纯虚函数
    //类中只要有一个纯虚函数就称为抽象类
    //抽象类无法实例化对象
    //子类必须重写父类中的纯虚函数,否则也属于抽象类
    virtual void func() = 0;
};

class Son :public Base
{
public:
    virtual void func() 
    {
        cout << "func调用" << endl;
    };
};

void test01()
{
    Base * base = NULL;
    //base = new Base; // 错误,抽象类无法实例化对象
    base = new Son;
    base->func();
    delete base;//记得销毁
}

int main() {

    test01();


    return 0;
}

Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.

Polymorphism Case Two - Preparing Beverages

Case Description:

The general process for making drinks is: boil water - brew - pour into a cup - add ingredients.

Implement this case study using polymorphism by providing an abstract base class for making beverages, with subclasses for preparing coffee and tea.

// Abstract base class for beverage preparation
abstract class Beverage {
    public abstract void prepare();
}

// Subclass for coffee
class Coffee extends Beverage {
    @Override
    public void prepare() {
        System.out.println("Preparing coffee...");
    }
}

// Subclass for tea
class Tea extends Beverage {
    @Override
    public void prepare() {
        System.out.println("Preparing tea...");
    }
}

1545985945198

Example:

//抽象制作饮品
class AbstractDrinking {
public:
    //烧水
    virtual void Boil() = 0;
    //冲泡
    virtual void Brew() = 0;
    //倒入杯中
    virtual void PourInCup() = 0;
    //加入辅料
    virtual void PutSomething() = 0;
    //规定流程
    void MakeDrink() {
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};

//制作咖啡
class Coffee : public AbstractDrinking {
public:
    //烧水
    virtual void Boil() {
        cout << "煮农夫山泉!" << endl;
    }
    //冲泡
    virtual void Brew() {
        cout << "冲泡咖啡!" << endl;
    }
    //倒入杯中
    virtual void PourInCup() {
        cout << "将咖啡倒入杯中!" << endl;
    }
    //加入辅料
    virtual void PutSomething() {
        cout << "加入牛奶!" << endl;
    }
};

//制作茶水
class Tea : public AbstractDrinking {
public:
    //烧水
    virtual void Boil() {
        cout << "煮自来水!" << endl;
    }
    //冲泡
    virtual void Brew() {
        cout << "冲泡茶叶!" << endl;
    }
    //倒入杯中
    virtual void PourInCup() {
        cout << "将茶水倒入杯中!" << endl;
    }
    //加入辅料
    virtual void PutSomething() {
        cout << "加入枸杞!" << endl;
    }
};

//业务函数
void DoWork(AbstractDrinking* drink) {
    drink->MakeDrink();
    delete drink;
}

void test01() {
    DoWork(new Coffee);
    cout << "--------------" << endl;
    DoWork(new Tea);
}

int main() {

    test01();


    return 0;
}

Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.

Virtual Destructors and Pure Virtual Destructors

When utilizing polymorphism, if a derived class allocates properties on the heap, the parent class pointer cannot invoke the destructor of the derived class upon deallocation.

Solution: Change the destructor in the base class to a virtual destructor or a pure virtual destructor.

Common characteristics of virtual destructors and pure virtual destructors:

  • This can be achieved by releasing a derived class object through a base class pointer, typically by ensuring the base class destructor is declared virtual. This allows proper destruction of the derived object when deleted via a base pointer, invoking the correct destructor in the derived class.
  • All must have specific function implementations.

Difference between virtual destructors and pure virtual destructors:

  • If a class has a pure virtual destructor, it is considered an abstract class and cannot be instantiated.

Virtual destructor syntax:

virtual ~类名(){}

Pure virtual destructor syntax:

virtual ~类名() = 0;

类名::~类名(){}

Example:

class Animal {
public:

    Animal()
    {
        cout << "Animal 构造函数调用!" << endl;
    }
    virtual void Speak() = 0;

    //析构函数加上virtual关键字,变成虚析构函数
    //virtual ~Animal()
    //{
    //  cout << "Animal虚析构函数调用!" << endl;
    //}

    virtual ~Animal() = 0;
};

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

//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。

class Cat : public Animal {
public:
    Cat(string name)
    {
        cout << "Cat构造函数调用!" << endl;
        m_Name = new string(name);
    }
    virtual void Speak()
    {
        cout << *m_Name <<  "小猫在说话!" << endl;
    }
    ~Cat()
    {
        cout << "Cat析构函数调用!" << endl;
        if (this->m_Name != NULL) {
            delete m_Name;
            m_Name = NULL;
        }
    }

public:
    string *m_Name;
};

void test01()
{
    Animal *animal = new Cat("Tom");
    animal->Speak();

    //通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
    //怎么解决?给基类增加一个虚析构函数
    //虚析构函数就是用来解决通过父类指针释放子类对象
    delete animal;
}

int main() {

    test01();


    return 0;
}

Running/Observing the results: After running, the example will print variable values or addresses; address values depend on the runtime environment, so focus on observing the relative positioning and pointer changes of similar objects.

Summary:

  1. Virtual or pure virtual destructors are used to solve the problem of releasing a subclass object through a parent class pointer.
  2. If there is no heap data in the subclass, you don't need to declare a virtual destructor or a pure virtual destructor.
  3. A class with a pure virtual destructor is also considered an abstract class.

Polymorphism Case 3-Computer Assembly

Case Description:

The main components of a computer are the CPU (for calculation), the GPU (for display), and RAM (for storage).

Encapsulate each component into an abstract base class, and provide different manufacturers to produce different components, such as Intel and Lenovo.

Create a computer class that provides functions to make the computer work and call the interfaces for each component to operate.

During testing, three different computers were assembled to perform the work.

Example:

#include<iostream>
using namespace std;

//抽象CPU类
class CPU
{
public:
    //抽象的计算函数
    virtual void calculate() = 0;
};

//抽象显卡类
class VideoCard
{
public:
    //抽象的显示函数
    virtual void display() = 0;
};

//抽象内存条类
class Memory
{
public:
    //抽象的存储函数
    virtual void storage() = 0;
};

//电脑类
class Computer
{
public:
    Computer(CPU * cpu, VideoCard * vc, Memory * mem)
    {
        m_cpu = cpu;
        m_vc = vc;
        m_mem = mem;
    }

    //提供工作的函数
    void work()
    {
        //让零件工作起来,调用接口
        m_cpu->calculate();

        m_vc->display();

        m_mem->storage();
    }

    //提供析构函数 释放3个电脑零件
    ~Computer()
    {

        //释放CPU零件
        if (m_cpu != NULL)
        {
            delete m_cpu;
            m_cpu = NULL;
        }

        //释放显卡零件
        if (m_vc != NULL)
        {
            delete m_vc;
            m_vc = NULL;
        }

        //释放内存条零件
        if (m_mem != NULL)
        {
            delete m_mem;
            m_mem = NULL;
        }
    }

private:

    CPU * m_cpu; //CPU的零件指针
    VideoCard * m_vc; //显卡零件指针
    Memory * m_mem; //内存条零件指针
};

//具体厂商
//Intel厂商
class IntelCPU :public CPU
{
public:
    virtual void calculate()
    {
        cout << "Intel的CPU开始计算了!" << endl;
    }
};

class IntelVideoCard :public VideoCard
{
public:
    virtual void display()
    {
        cout << "Intel的显卡开始显示了!" << endl;
    }
};

class IntelMemory :public Memory
{
public:
    virtual void storage()
    {
        cout << "Intel的内存条开始存储了!" << endl;
    }
};

//Lenovo厂商
class LenovoCPU :public CPU
{
public:
    virtual void calculate()
    {
        cout << "Lenovo的CPU开始计算了!" << endl;
    }
};

class LenovoVideoCard :public VideoCard
{
public:
    virtual void display()
    {
        cout << "Lenovo的显卡开始显示了!" << endl;
    }
};

class LenovoMemory :public Memory
{
public:
    virtual void storage()
    {
        cout << "Lenovo的内存条开始存储了!" << endl;
    }
};

void test01()
{
    //第一台电脑零件
    CPU * intelCpu = new IntelCPU;
    VideoCard * intelCard = new IntelVideoCard;
    Memory * intelMem = new IntelMemory;

    cout << "第一台电脑开始工作:" << endl;
    //创建第一台电脑
    Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
    computer1->work();
    delete computer1;

    cout << "-----------------------" << endl;
    cout << "第二台电脑开始工作:" << endl;
    //第二台电脑组装
    Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
    computer2->work();
    delete computer2;

    cout << "-----------------------" << endl;
    cout << "第三台电脑开始工作:" << endl;
    //第三台电脑组装
    Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
    computer3->work();
    delete computer3;

}

Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.

音乐页