C++的面向对象学习(8):面向对象编程的三大特性之:多态

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


一、多态是什么?

多态是面向对象编程中的一个重要概念,指的是同一个函数在不同的对象上具有不同的行为。具体来说,多态是通过虚函数实现的。

二、多态的种类:静态多态和动态多态

①静态多态:如函数重载与运算符重载

静态多态是指在编译时就确定了函数的调用方式,也称为编译时多态。C++中的函数重载就是一种静态多态,编译器在编译时就根据函数参数的类型和数量来确定调用哪个函数。

②动态多态:派生类与虚函数

动态多态是指在运行时根据对象的类型来确定函数的调用方式,也称为运行时多态。C++中的虚函数就是一种动态多态,当通过基类指针或引用调用虚函数时,实际上会根据指针或引用所指向的对象类型来调用相应的函数实现。

语法:基类对该多态函数用virtual声明。

三、函数重写与函数重载的区别

函数重写(override)和函数重载(overload)是两种不同的概念。

函数重载是指在同一个作用域内,可以定义多个同名函数,但是它们的参数列表不同。编译器会根据调用时提供的参数类型和数量,自动匹配合适的函数进行调用。函数重载可以提高代码的可读性和可维护性,但是需要注意避免过度使用,以免造成混淆。

函数重写是指在派生类中重新定义基类中已有的虚函数,使得派生类对象在调用该函数时,会优先调用派生类中的函数,而不是基类中的函数。函数重写可以实现运行时多态,提高代码的灵活性和可扩展性。

c 复制代码
class animal {
public:
    virtual void print() {
        cout << "hello world" << endl;
    }
};

class cat :public animal {
public:
    void print() {//重写基类的成员函数
        cout << "hello world cat" << endl;
    }

    void print(int n) {//重载成员函数
        for (int i = 0; i < n; i++) {
            cout << "hello world cat" << endl;
        }
    }
};

int main() {
    animal a;
    cat cat1;

    a.print(); // 输出 "hello world"
    cat1.print(); // 输出 "hello world cat"
    cat1.print(3); // 输出三次 "hello world cat"

    return 0;
}

四、通过基类的指针或引用来访问派生类对象(重要)

多态需要通过基类的指针或引用来访问派生类对象。通过将派生类对象赋值给基类指针或引用,可以实现对派生类对象的统一访问

什么意思呢?就是说基类会有很多个派生类,我不想通过一个个实例化派生类的对象来调用其各自的函数,而是想直接通过基类来统一的管理与调用。

c 复制代码
class animal {
public:
    virtual void sound() {
        cout << "animal sound" << endl;
    }
};

class cat :public animal {
public:
    void sound() {
        cout << "meow" << endl;
    }
};

class dog :public animal {
public:
    void sound() {
        cout << "woof" << endl;
    }
};

int main() {
    animal* animals[3];
    animals[0] = new animal();
    animals[1] = new cat();
    animals[2] = new dog();

    for (int i = 0; i < 3; i++) {
        animals[i]->sound();
    }

    return 0;
}

mian函数是核心,我直接建立了一个类animal类型的指针数组,指针0指向类animal,指针1和2分别指向类cat与dog。这样的话,才能保证不同指针调用的是不同类的多态函数。如果不加virtual修饰,则三个指针都会调用基类函数。

五、多态与虚函数的内部实现原理

多态的实现依赖于虚函数的内部实现原理。在面向对象编程中,虚函数是通过虚函数表(vtable)和虚函数指针(vptr)来实现多态的。

虚函数表是一个存储了虚函数地址的表格,每个包含虚函数的类都有一个对应的虚函数表。虚函数表中的每个条目都指向相应虚函数的地址。当一个类被定义为包含虚函数时,编译器会在该类的对象中插入一个隐藏的指针,称为虚函数指针(vptr)。虚函数指针指向该类的虚函数表。

当通过基类指针或引用调用虚函数时,编译器会使用虚函数指针来查找虚函数表,并根据对象的实际类型找到对应的虚函数地址进行调用。这样就实现了在运行时根据对象的实际类型来确定调用的函数,从而实现了多态。

六、多态有什么用?应用场合?

我举一个例子:

实际工作中,需要开发一个计算器的项目,如果我只定义一个计数器类,在该类里面写函数,可以使用多态来处理不同类型的数据对象。

开发中的原则上:要求修改是闭合的,拓展是开放的。

所以,对于一个基类,最好只是声明一个大纲,而正式的各种不同功能的实现,需要通过多态,在派生类中完成。

c 复制代码
#include <iostream>

// 基类 Calculator
class Calculator {
public:
    virtual double calculate(double num1, double num2) {
	return 0;
}
};

// 加法计算器类
class AddCalculator : public Calculator {
public:
    double calculate(double num1, double num2)  {
        return num1 + num2;
    }
};

// 减法计算器类
class SubCalculator : public Calculator {
public:
    double calculate(double num1, double num2) {
        return num1 - num2;
    }
};

int main() {
    // 创建加法计算器对象
    Calculator* addCalc = new AddCalculator();
    double result1 = addCalc->calculate(5.0, 3.0);
    std::cout << "加法计算结果: " << result1 << std::endl;

    // 创建减法计算器对象
    Calculator* subCalc = new SubCalculator();
    double result2 = subCalc->calculate(5.0, 3.0);
    std::cout << "减法计算结果: " << result2 << std::endl;

    delete addCalc;
    delete subCalc;

    return 0;
}

我如果后续想对加法、减法进行修改或者拓展,就可以完全不管其他乘或除部分的代码,只负责这一个派生类即可。可以通过多态的方式,统一地操作不同类型的计算器对象,可读性更强了。

七、纯虚函数和抽象类

1.在多态中,父类的虚函数实现是没有意义的,调用的主要是子类中重写的内容,所以父类虚函数可改写为纯虚函数。

纯虚函数是在基类中声明但没有实现的虚函数。它的声明形式为在函数声明后面加上= 0。纯虚函数没有函数体,因此派生类必须实现它们才能被实例化。

例:

c 复制代码
// 抽象类 Shape
class Shape {
public:
    virtual double getArea() const = 0; // 纯虚函数
    virtual double getPerimeter() const = 0; // 纯虚函数
};

2.只要一个类里有了纯虚函数,这个类就是抽象类了。抽象类无法被实例化对象。

3.子类必须重写抽象类的纯虚函数,否则也属于抽象类。

c 复制代码
// 派生类 Circle
class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double getArea() const override {
        return 3.14 * radius * radius;
    }

    double getPerimeter() const override {
        return 2 * 3.14 * radius;
    }
};

// 派生类 Rectangle
class Rectangle : public Shape {
private:
    double length;
    double width;

public:
    Rectangle(double l, double w) : length(l), width(w) {}

    double getArea() const override {
        return length * width;
    }

    double getPerimeter() const override {
        return 2 * (length + width);
    }
};

主程序:

c 复制代码
int main() {
    Circle circle(5.0);
    Rectangle rectangle(4.0, 6.0);

    std::cout << "圆的面积: " << circle.getArea() << std::endl;
    std::cout << "圆的周长: " << circle.getPerimeter() << std::endl;

    std::cout << "矩形的面积: " << rectangle.getArea() << std::endl;
    std::cout << "矩形的周长: " << rectangle.getPerimeter() << std::endl;

    return 0;
}
相关推荐
th新港几秒前
CCF201909_1
数据结构·c++·算法·ccf
意如流水任东西几秒前
[C++]类和对象(上)
开发语言·c++
孤寂大仙v6 分钟前
【C++】STL----stack和queue常见用法
开发语言·c++
Monodye19 分钟前
【Java】网络编程:TCP_IP协议详解(IP协议数据报文及如何解决IPv4不够的状况)
java·网络·数据结构·算法·系统架构
一丝晨光25 分钟前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
无名指的等待7121 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
Tatakai251 小时前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis
武子康1 小时前
大数据-133 - ClickHouse 基础概述 全面了解
java·大数据·分布式·clickhouse·flink·spark
晓幂1 小时前
CTFShow-信息搜集
笔记·学习
.生产的驴1 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq