首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《C++ 程序设计》第 8 章 - 多态性

《C++ 程序设计》第 8 章 - 多态性

作者头像
啊阿狸不会拉杆
发布2026-01-21 12:38:54
发布2026-01-21 12:38:54
50
举报
        大家好!今天我们来深入学习 C++ 中的核心特性之一 —— 多态性。多态是面向对象编程的三大支柱(封装、继承、多态)之一,掌握多态性对于编写灵活、可扩展的 C++ 程序至关重要。本文将按照《C++ 程序设计》第 8 章的结构,详细讲解多态性的各个知识点,并提供完整可运行的代码示例。

思维导图

8.1 多态性概述

        多态性(Polymorphism)是面向对象编程中的一个重要概念,它允许同一名称的实体在不同的场景下表现出不同的行为。简单来说,就是 "一个接口,多种实现"。

8.1.1 多态的类型

C++ 中的多态主要分为两类:

  1. 静态多态(编译时多态):在编译阶段就确定了调用哪个函数,主要通过函数重载和运算符重载实现。
  2. 动态多态(运行时多态):在程序运行时才确定调用哪个函数,主要通过虚函数和继承关系实现。
8.1.2 多态的实现

C++ 中实现多态的机制主要有以下几种:

  • 函数重载:在同一作用域内,允许存在多个同名函数,只要它们的参数列表不同。
  • 运算符重载:赋予运算符新的含义,使其能作用于自定义类型。
  • 虚函数:在基类中声明为 virtual 的函数,允许派生类重新定义,实现动态绑定。
  • 纯虚函数与抽象类:纯虚函数是没有实现的虚函数,包含纯虚函数的类为抽象类,不能实例化,只能作为基类使用。

8.2 运算符重载

        运算符重载是 C++ 的重要特性,它允许我们为自定义类型重新定义运算符的行为,使代码更直观、更易读。

8.2.1 运算符重载的规则

运算符重载需要遵循以下规则:

  1. 不能重载的运算符:..*::? :sizeof
  2. 重载运算符不能改变其原有的优先级和结合性。
  3. 重载运算符不能改变操作数的个数。
  4. 运算符重载函数可以是类的成员函数或非成员函数(友元函数)。
  5. 对于一元运算符,成员函数重载时没有参数,非成员函数重载时有一个参数。
  6. 对于二元运算符,成员函数重载时有一个参数,非成员函数重载时有两个参数。
8.2.2 运算符重载为成员函数

        当运算符重载为类的成员函数时,左操作数是当前对象,右操作数是函数参数。

下面是一个复数类的例子,展示如何将+-*<<运算符重载为成员函数:

代码语言:javascript
复制
#include <iostream>
using namespace std;

class Complex {
private:
    double real;  // 实部
    double imag;  // 虚部

public:
    // 构造函数
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // 运算符+重载为成员函数
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

    // 运算符-重载为成员函数
    Complex operator-(const Complex& other) const {
        return Complex(real - other.real, imag - other.imag);
    }

    // 运算符*重载为成员函数
    Complex operator*(const Complex& other) const {
        // (a+bi)*(c+di) = (ac-bd) + (ad+bc)i
        return Complex(
            real * other.real - imag * other.imag,
            real * other.imag + imag * other.real
        );
    }

    // 为了能访问私有成员,声明<<运算符为友元
    friend ostream& operator<<(ostream& os, const Complex& c);
};

// 8.2.3 运算符重载为非成员函数
// <<运算符重载为非成员函数
ostream& operator<<(ostream& os, const Complex& c) {
    os << c.real;
    if (c.imag >= 0) os << "+";
    os << c.imag << "i";
    return os;
}

int main() {
    Complex c1(3, 4), c2(1, 2);
    
    Complex c3 = c1 + c2;
    Complex c4 = c1 - c2;
    Complex c5 = c1 * c2;
    
    cout << "c1 = " << c1 << endl;
    cout << "c2 = " << c2 << endl;
    cout << "c1 + c2 = " << c3 << endl;
    cout << "c1 - c2 = " << c4 << endl;
    cout << "c1 * c2 = " << c5 << endl;
    
    return 0;
}
8.2.2 运算符重载为成员函数

        在上面的例子中,+-*运算符被重载为Complex类的成员函数。当我们写c1 + c2时,编译器会将其转换为c1.operator+(c2)的调用。

成员函数形式的运算符重载语法如下:

代码语言:javascript
复制
返回类型 operator运算符(参数列表) {
    // 实现代码
}
8.2.3 运算符重载为非成员函数

        有些运算符更适合重载为非成员函数,特别是当左操作数不是类的对象时,如输入输出运算符<<>>

非成员函数形式的运算符重载语法如下:

代码语言:javascript
复制
返回类型 operator运算符(参数列表) {
    // 实现代码
}

如果需要访问类的私有成员,非成员运算符重载函数通常会被声明为类的友元。

在上面的复数类例子中,<<运算符被重载为非成员函数,并声明为Complex类的友元,以便访问其私有成员realimag

8.3 虚函数

        虚函数是实现动态多态的核心机制,它允许派生类重写基类的函数,并在运行时根据对象的实际类型调用相应的函数。

8.3.1 一般虚函数成员

        虚函数的声明方式是在基类的函数声明前加上virtual关键字。派生类可以重写该函数,重写时可以省略virtual关键字,但建议加上以提高可读性。

下面是一个展示虚函数用法的例子:

代码语言:javascript
复制
#include <iostream>
using namespace std;

// 基类:形状
class Shape {
public:
    // 虚函数:计算面积
    virtual double area() const {
        cout << "Shape::area() called" << endl;
        return 0;
    }
    
    // 虚函数:计算周长
    virtual double perimeter() const {
        cout << "Shape::perimeter() called" << endl;
        return 0;
    }
    
    // 普通函数:打印形状信息
    void printInfo() const {
        cout << "This is a shape." << endl;
    }
};

// 派生类:圆形
class Circle : public Shape {
private:
    double radius;  // 半径

public:
    // 构造函数
    Circle(double r) : radius(r) {}
    
    // 重写基类的虚函数:计算面积
    virtual double area() const override {
        cout << "Circle::area() called" << endl;
        return 3.14159 * radius * radius;
    }
    
    // 重写基类的虚函数:计算周长
    virtual double perimeter() const override {
        cout << "Circle::perimeter() called" << endl;
        return 2 * 3.14159 * radius;
    }
};

// 派生类:矩形
class Rectangle : public Shape {
private:
    double length;  // 长度
    double width;   // 宽度

public:
    // 构造函数
    Rectangle(double l, double w) : length(l), width(w) {}
    
    // 重写基类的虚函数:计算面积
    virtual double area() const override {
        cout << "Rectangle::area() called" << endl;
        return length * width;
    }
    
    // 重写基类的虚函数:计算周长
    virtual double perimeter() const override {
        cout << "Rectangle::perimeter() called" << endl;
        return 2 * (length + width);
    }
    
    // 新增函数:获取长宽比
    double aspectRatio() const {
        return length / width;
    }
};

// 函数:打印形状的面积和周长
void printShapeInfo(const Shape& shape) {
    cout << "Area: " << shape.area() << endl;
    cout << "Perimeter: " << shape.perimeter() << endl;
    shape.printInfo();  // 调用的是基类的普通函数
    cout << "-------------------------" << endl;
}

int main() {
    // 创建基类对象
    Shape shape;
    // 创建派生类对象
    Circle circle(5.0);
    Rectangle rectangle(4.0, 6.0);
    
    // 直接调用函数
    cout << "直接调用函数:" << endl;
    shape.area();
    circle.area();
    rectangle.area();
    cout << "-------------------------" << endl;
    
    // 通过基类指针调用函数
    cout << "通过基类指针调用函数:" << endl;
    Shape* shapePtr1 = &shape;
    Shape* shapePtr2 = &circle;
    Shape* shapePtr3 = &rectangle;
    
    shapePtr1->area();  // 调用Shape的area()
    shapePtr2->area();  // 调用Circle的area()
    shapePtr3->area();  // 调用Rectangle的area()
    cout << "-------------------------" << endl;
    
    // 通过基类引用调用函数
    cout << "通过基类引用调用函数:" << endl;
    printShapeInfo(shape);
    printShapeInfo(circle);
    printShapeInfo(rectangle);
    
    return 0;
}

        上面的代码展示了虚函数的核心特性:当通过基类指针或引用调用虚函数时,会根据指针或引用所指向的对象的实际类型来调用相应的函数版本,而不是指针或引用本身的类型。

        C++11 引入了override关键字,用于显式指定派生类的函数是重写基类的虚函数,这样可以在编译时检测出潜在的错误,如函数签名不匹配等问题。

8.3.2 虚析构函数

        析构函数也可以声明为虚函数,称为虚析构函数。当基类指针指向派生类对象并通过基类指针删除对象时,虚析构函数确保派生类的析构函数会被调用,从而正确释放资源。

下面是一个展示虚析构函数重要性的例子:

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

// 基类
class Base {
public:
    // 构造函数
    Base() {
        cout << "Base constructor called" << endl;
    }
    
    // 虚析构函数
    virtual ~Base() {
        cout << "Base destructor called" << endl;
    }
};

// 派生类
class Derived : public Base {
private:
    string* data;  // 动态分配的资源

public:
    // 构造函数
    Derived(const string& s) {
        cout << "Derived constructor called" << endl;
        data = new string(s);  // 分配资源
    }
    
    // 析构函数
    ~Derived() override {
        cout << "Derived destructor called" << endl;
        delete data;  // 释放资源
    }
};

int main() {
    cout << "创建Base对象:" << endl;
    Base* baseObj = new Base();
    delete baseObj;
    cout << endl;
    
    cout << "创建Derived对象,用Derived指针指向:" << endl;
    Derived* derivedObj1 = new Derived("Test string 1");
    delete derivedObj1;
    cout << endl;
    
    cout << "创建Derived对象,用Base指针指向:" << endl;
    Base* derivedObj2 = new Derived("Test string 2");
    delete derivedObj2;  // 如果Base的析构函数不是虚函数,这里只会调用Base的析构函数
    
    return 0;
}

        如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,可能导致资源泄漏。因此,通常建议将基类的析构函数声明为虚函数

8.4 纯虚函数与抽象类

8.4.1 纯虚函数

        纯虚函数是一种特殊的虚函数,它在基类中声明但没有实现,其声明方式是在函数原型后加上= 0

纯虚函数的语法如下:

代码语言:javascript
复制
virtual 返回类型 函数名(参数列表) const = 0;

纯虚函数的作用是为派生类提供一个统一的接口,强制派生类实现该函数。

8.4.2 抽象类

 包含纯虚函数的类称为抽象类。抽象类不能实例化对象,只能作为基类被继承。派生类必须实现抽象类中的所有纯虚函数才能成为非抽象类,否则它仍然是抽象类。

下面是一个展示纯虚函数和抽象类用法的例子:

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

// 抽象类:图形接口
class Graphic {
public:
    // 纯虚函数:绘制图形
    virtual void draw() const = 0;
    
    // 纯虚函数:计算面积
    virtual double area() const = 0;
    
    // 纯虚函数:获取图形描述
    virtual string description() const = 0;
    
    // 普通成员函数
    void printInfo() const {
        cout << description() << ", Area: " << area() << endl;
    }
    
    // 虚析构函数
    virtual ~Graphic() = default;
};

// 具体类:圆形
class Circle : public Graphic {
private:
    double radius;  // 半径
    string name;    // 名称

public:
    // 构造函数
    Circle(double r, const string& n) : radius(r), name(n) {}
    
    // 实现纯虚函数:绘制图形
    void draw() const override {
        cout << "Drawing circle: " << name << endl;
        // 实际绘制逻辑...
    }
    
    // 实现纯虚函数:计算面积
    double area() const override {
        return 3.14159 * radius * radius;
    }
    
    // 实现纯虚函数:获取图形描述
    string description() const override {
        return "Circle '" + name + "' (radius: " + to_string(radius) + ")";
    }
};

// 具体类:矩形
class Rectangle : public Graphic {
private:
    double width;   // 宽度
    double height;  // 高度
    string name;    // 名称

public:
    // 构造函数
    Rectangle(double w, double h, const string& n) 
        : width(w), height(h), name(n) {}
    
    // 实现纯虚函数:绘制图形
    void draw() const override {
        cout << "Drawing rectangle: " << name << endl;
        // 实际绘制逻辑...
    }
    
    // 实现纯虚函数:计算面积
    double area() const override {
        return width * height;
    }
    
    // 实现纯虚函数:获取图形描述
    string description() const override {
        return "Rectangle '" + name + "' (width: " + to_string(width) + 
               ", height: " + to_string(height) + ")";
    }
};

// 函数:绘制图形并显示信息
void processGraphic(const Graphic& graphic) {
    graphic.draw();
    graphic.printInfo();
    cout << "-------------------------" << endl;
}

int main() {
    // 不能实例化抽象类
    // Graphic graphic;  // 编译错误:Cannot declare variable 'graphic' to be of abstract type 'Graphic'
    
    // 创建具体类的对象
    Circle circle(5.0, "My Circle");
    Rectangle rectangle(4.0, 6.0, "My Rectangle");
    
    // 通过基类引用处理不同的图形对象
    processGraphic(circle);
    processGraphic(rectangle);
    
    // 通过基类指针数组管理不同的图形对象
    Graphic* graphics[] = {&circle, &rectangle};
    const int numGraphics = sizeof(graphics) / sizeof(graphics[0]);
    
    cout << "Total area of all graphics: ";
    double totalArea = 0;
    for (int i = 0; i < numGraphics; ++i) {
        totalArea += graphics[i]->area();
    }
    cout << totalArea << endl;
    
    return 0;
}

        上面的代码中,Graphic类是一个抽象类,它包含三个纯虚函数:draw()area()description()CircleRectangle类继承自Graphic并实现了所有纯虚函数,因此它们是具体类,可以实例化对象。

抽象类的主要作用是定义接口,为一组相关的类提供统一的对外接口,同时强制派生类实现这些接口,保证了接口的一致性。

8.5 程序实例 —— 变步长梯形积分算法求解函数的定积分

8.5.1 算法基本原理
8.5.2 程序设计分析

为了实现变步长梯形积分算法,我们可以设计如下类结构:

  1. 定义一个抽象基类Function,包含纯虚函数operator(),用于计算函数值
  2. 定义具体函数类继承自Function,如SinFunctionPolynomialFunction
  3. 定义积分计算器类Integrator,包含计算积分的方法

这样的设计利用了多态性,使得积分计算器可以处理任何继承自Function的函数对象。

代码语言:javascript
复制
@startuml
abstract class Function {
    + {abstract} operator()(double x): double
}

class SinFunction {
    + operator()(double x): double
}

class PolynomialFunction {
    - coefficients: vector<double>
    + PolynomialFunction(coefficients: vector<double>)
    + operator()(double x): double
}

class Integrator {
    - eps: double
    + Integrator(eps: double = 1e-6)
    + integrate(f: Function&, a: double, b: double): double
    - trapezoidal(f: Function&, a: double, b: double, n: int): double
}

Function <|-- SinFunction
Function <|-- PolynomialFunction
Integrator --> Function
@enduml
8.5.3 源程序及说明
代码语言:javascript
复制
#include <iostream>
#include <cmath>
#include <vector>
#include <iomanip>
using namespace std;

// 抽象基类:函数接口
class Function {
public:
    // 纯虚函数:计算函数值
    virtual double operator()(double x) const = 0;
    // 虚析构函数
    virtual ~Function() = default;
};

// 具体函数:正弦函数 f(x) = sin(x)
class SinFunction : public Function {
public:
    double operator()(double x) const override {
        return sin(x);
    }
};

// 具体函数:多项式函数 f(x) = a0 + a1*x + a2*x^2 + ... + an*x^n
class PolynomialFunction : public Function {
private:
    vector<double> coefficients;  // 多项式系数,coefficients[i] 对应 x^i 的系数

public:
    // 构造函数:接受多项式系数
    PolynomialFunction(const vector<double>& coeffs) : coefficients(coeffs) {}
    
    // 计算多项式在x处的值
    double operator()(double x) const override {
        double result = 0.0;
        double xPower = 1.0;  // x^0 = 1
        for (double coeff : coefficients) {
            result += coeff * xPower;
            xPower *= x;  // 计算 x^1, x^2, ..., x^n
        }
        return result;
    }
};

// 具体函数:指数函数 f(x) = e^x
class ExpFunction : public Function {
public:
    double operator()(double x) const override {
        return exp(x);
    }
};

// 积分计算器类
class Integrator {
private:
    double eps;  // 精度要求
    
    // 梯形法计算积分
    // a, b: 积分区间
    // n: 区间等分数
    double trapezoidal(const Function& f, double a, double b, int n) const {
        double h = (b - a) / n;  // 步长
        double sum = 0.5 * (f(a) + f(b));  // 首末项之和的一半
        
        // 累加中间项
        for (int i = 1; i < n; ++i) {
            double x = a + i * h;
            sum += f(x);
        }
        
        return sum * h;  // 乘以步长得到积分值
    }

public:
    // 构造函数:设置精度,默认1e-6
    Integrator(double precision = 1e-6) : eps(precision) {}
    
    // 变步长梯形积分算法
    // f: 被积函数
    // a, b: 积分区间
    double integrate(const Function& f, double a, double b) const {
        int n = 1;  // 初始区间等分数
        double T1 = trapezoidal(f, a, b, n);  // 初始积分值
        
        while (true) {
            n *= 2;  // 区间二分
            double T2 = trapezoidal(f, a, b, n);  // 计算新的积分值
            
            // 判断精度是否满足要求
            if (abs(T2 - T1) < eps) {
                return T2;
            }
            
            T1 = T2;  // 更新积分值,继续迭代
        }
    }
};

int main() {
    // 创建积分器,设置精度为1e-8
    Integrator integrator(1e-8);
    
    // 测试1:计算 sin(x) 在 [0, π] 上的积分,理论值为 2
    SinFunction sinFunc;
    double result1 = integrator.integrate(sinFunc, 0, M_PI);
    cout << "∫ sin(x) dx from 0 to π = " << fixed << setprecision(6) << result1 
         << ", 理论值 = 2.0" << endl;
    
    // 测试2:计算多项式 f(x) = 1 + x + x^2 在 [0, 1] 上的积分,理论值为 1 + 0.5 + 1/3 ≈ 1.833333
    vector<double> coeffs = {1.0, 1.0, 1.0};  // 1 + x + x^2
    PolynomialFunction polyFunc(coeffs);
    double result2 = integrator.integrate(polyFunc, 0, 1);
    cout << "∫ (1 + x + x^2) dx from 0 to 1 = " << fixed << setprecision(6) << result2 
         << ", 理论值 ≈ 1.833333" << endl;
    
    // 测试3:计算 e^x 在 [0, 1] 上的积分,理论值为 e - 1 ≈ 1.7182818
    ExpFunction expFunc;
    double result3 = integrator.integrate(expFunc, 0, 1);
    cout << "∫ e^x dx from 0 to 1 = " << fixed << setprecision(6) << result3 
         << ", 理论值 ≈ 1.718282" << endl;
    
    // 自定义函数测试:计算 f(x) = x^3 在 [0, 2] 上的积分,理论值为 4
    class CubeFunction : public Function {
    public:
        double operator()(double x) const override {
            return x * x * x;
        }
    };
    
    CubeFunction cubeFunc;
    double result4 = integrator.integrate(cubeFunc, 0, 2);
    cout << "∫ x^3 dx from 0 to 2 = " << fixed << setprecision(6) << result4 
         << ", 理论值 = 4.0" << endl;
    
    return 0;
}
8.5.4 运行结果与分析

程序运行结果如下:

结果分析:

  1. 所有测试用例的计算结果都非常接近理论值,说明算法实现正确。
  2. 利用多态性,积分器可以处理任何继承自Function的函数对象,包括预定义的和用户自定义的函数。
  3. 变步长算法能够自动调整步长,在满足精度要求的前提下,尽量减少计算量。
  4. 程序结构清晰,通过类的封装和继承,实现了良好的可扩展性,如需添加新的函数类型,只需继承Function类并实现operator()即可。

8.6 综合实例 —— 对个人银行账户管理程序的改进

        我们将利用多态性对个人银行账户管理程序进行改进,支持不同类型的账户(如储蓄账户、支票账户等),每种账户有不同的利息计算方式和手续费规则。

代码语言:javascript
复制
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
using namespace std;

// 日期类,用于记录交易日期
class Date {
private:
    int year, month, day;
public:
    Date(int y = 2000, int m = 1, int d = 1) : year(y), month(m), day(d) {}
    
    // 简单的日期输出
    string toString() const {
        return to_string(year) + "-" + 
               (month < 10 ? "0" : "") + to_string(month) + "-" + 
               (day < 10 ? "0" : "") + to_string(day);
    }
    
    // 获取当前日期(简化版)
    static Date today() {
        // 实际应用中应该从系统获取当前日期
        return Date(2023, 10, 1);
    }
};

// 交易记录类
class Transaction {
private:
    Date date;       // 交易日期
    double amount;   // 交易金额
    string type;     // 交易类型
    string note;     // 交易备注

public:
    Transaction(Date d, double a, const string& t, const string& n)
        : date(d), amount(a), type(t), note(n) {}
    
    // 输出交易记录
    void print() const {
        cout << left << setw(12) << date.toString()
             << setw(10) << type
             << setw(12) << fixed << setprecision(2) << amount
             << note << endl;
    }
};

// 银行账户抽象基类
class BankAccount {
protected:
    string accountNumber;  // 账号
    string ownerName;      // 账户名
    double balance;        // 余额
    vector<Transaction> transactions;  // 交易记录
    Date lastInterestDate; // 上次计息日期

public:
    // 构造函数
    BankAccount(const string& accNum, const string& name, double initialBalance = 0)
        : accountNumber(accNum), ownerName(name), balance(initialBalance) {
        Date today = Date::today();
        lastInterestDate = today;
        if (initialBalance > 0) {
            transactions.emplace_back(today, initialBalance, "Deposit", "Initial deposit");
        }
    }
    
    // 析构函数
    virtual ~BankAccount() = default;
    
    // 获取账号
    string getAccountNumber() const { return accountNumber; }
    
    // 获取账户名
    string getOwnerName() const { return ownerName; }
    
    // 获取余额
    double getBalance() const { return balance; }
    
    // 存款
    virtual void deposit(double amount, const string& note = "") {
        if (amount <= 0) {
            cout << "Error: Deposit amount must be positive." << endl;
            return;
        }
        
        balance += amount;
        transactions.emplace_back(Date::today(), amount, "Deposit", note);
        cout << "Deposit successful. New balance: " << fixed << setprecision(2) << balance << endl;
    }
    
    // 取款(纯虚函数,强制派生类实现)
    virtual bool withdraw(double amount, const string& note = "") = 0;
    
    // 计算利息(纯虚函数,不同账户类型计算方式不同)
    virtual void calculateInterest() = 0;
    
    // 显示账户信息
    virtual void displayAccountInfo() const {
        cout << "Account Information:" << endl;
        cout << "Account Number: " << accountNumber << endl;
        cout << "Account Owner: " << ownerName << endl;
        cout << "Current Balance: " << fixed << setprecision(2) << balance << endl;
        cout << "Account Type: " << getAccountType() << endl;
    }
    
    // 显示交易记录
    void displayTransactions() const {
        cout << "\nTransaction History:" << endl;
        cout << left << setw(12) << "Date"
             << setw(10) << "Type"
             << setw(12) << "Amount"
             << "Note" << endl;
        cout << string(50, '-') << endl;
        
        for (const auto& trans : transactions) {
            trans.print();
        }
    }
    
    // 获取账户类型(纯虚函数)
    virtual string getAccountType() const = 0;
    
    // 显示账户摘要
    void printSummary() const {
        cout << left << setw(15) << accountNumber
             << setw(20) << ownerName
             << setw(15) << getAccountType()
             << fixed << setprecision(2) << balance << endl;
    }
};

// 储蓄账户类
class SavingsAccount : public BankAccount {
private:
    double interestRate;  // 年利率
    double minBalance;    // 最低余额要求

public:
    // 构造函数
    SavingsAccount(const string& accNum, const string& name, 
                  double rate = 0.02, double minBal = 100,
                  double initialBalance = 0)
        : BankAccount(accNum, name, initialBalance), 
          interestRate(rate), minBalance(minBal) {}
    
    // 取款
    bool withdraw(double amount, const string& note = "") override {
        if (amount <= 0) {
            cout << "Error: Withdrawal amount must be positive." << endl;
            return false;
        }
        
        // 检查余额是否足够,包括可能的手续费
        double needed = amount;
        if (balance - amount < minBalance) {
            needed += 10;  // 低于最低余额,收取10元手续费
            cout << "Warning: Withdrawal will result in balance below minimum. $10 fee applies." << endl;
        }
        
        if (balance < needed) {
            cout << "Error: Insufficient funds for withdrawal." << endl;
            return false;
        }
        
        balance -= needed;
        transactions.emplace_back(Date::today(), -amount, "Withdrawal", note);
        if (needed > amount) {
            transactions.emplace_back(Date::today(), -10, "Fee", "Below minimum balance");
        }
        
        cout << "Withdrawal successful. New balance: " << fixed << setprecision(2) << balance << endl;
        return true;
    }
    
    // 计算利息(假设每月计算一次利息)
    void calculateInterest() override {
        Date today = Date::today();
        // 简化:假设每月计算一次利息
        double interest = balance * interestRate / 12;
        if (interest > 0) {
            balance += interest;
            transactions.emplace_back(today, interest, "Interest", "Monthly interest");
            lastInterestDate = today;
            cout << "Interest calculated: " << fixed << setprecision(2) << interest 
                 << ". New balance: " << balance << endl;
        }
    }
    
    // 获取账户类型
    string getAccountType() const override {
        return "Savings";
    }
    
    // 显示账户信息(重写,增加储蓄账户特有信息)
    void displayAccountInfo() const override {
        BankAccount::displayAccountInfo();
        cout << "Interest Rate: " << fixed << setprecision(2) << (interestRate * 100) << "%" << endl;
        cout << "Minimum Balance: " << fixed << setprecision(2) << minBalance << endl;
        cout << "Last Interest Date: " << lastInterestDate.toString() << endl;
    }
};

// 支票账户类
class CheckingAccount : public BankAccount {
private:
    double monthlyFee;       // 月费
    double transactionFee;   // 每笔交易手续费
    int freeTransactions;    // 每月免费交易次数
    int transactionCount;    // 当月交易次数

public:
    // 构造函数
    CheckingAccount(const string& accNum, const string& name,
                   double fee = 5.0, double transFee = 0.5,
                   int freeTrans = 10, double initialBalance = 0)
        : BankAccount(accNum, name, initialBalance),
          monthlyFee(fee), transactionFee(transFee),
          freeTransactions(freeTrans), transactionCount(0) {}
    
    // 存款(重写,增加交易计数)
    void deposit(double amount, const string& note = "") override {
        BankAccount::deposit(amount, note);
        transactionCount++;
        applyTransactionFees();
    }
    
    // 取款
    bool withdraw(double amount, const string& note = "") override {
        if (amount <= 0) {
            cout << "Error: Withdrawal amount must be positive." << endl;
            return false;
        }
        
        // 计算所需金额,包括可能的手续费
        double needed = amount;
        if (transactionCount >= freeTransactions) {
            needed += transactionFee;
        }
        
        if (balance < needed) {
            cout << "Error: Insufficient funds for withdrawal." << endl;
            return false;
        }
        
        balance -= needed;
        transactions.emplace_back(Date::today(), -amount, "Withdrawal", note);
        transactionCount++;
        
        // 如果超过免费交易次数,记录手续费
        if (transactionCount > freeTransactions) {
            transactions.emplace_back(Date::today(), -transactionFee, "Fee", "Transaction fee");
        }
        
        cout << "Withdrawal successful. New balance: " << fixed << setprecision(2) << balance << endl;
        return true;
    }
    
    // 计算利息(支票账户利息较低)
    void calculateInterest() override {
        Date today = Date::today();
        // 支票账户利息较低,假设年利率0.5%
        double interestRate = 0.005;
        double interest = balance * interestRate / 12;
        
        if (interest > 0) {
            balance += interest;
            transactions.emplace_back(today, interest, "Interest", "Monthly interest");
            
            // 扣除月费
            if (monthlyFee > 0 && balance >= monthlyFee) {
                balance -= monthlyFee;
                transactions.emplace_back(today, -monthlyFee, "Fee", "Monthly service fee");
            }
            
            lastInterestDate = today;
            transactionCount = 0;  // 重置每月交易计数
            cout << "Interest calculated: " << fixed << setprecision(2) << interest 
                 << ". Monthly fee: " << monthlyFee
                 << ". New balance: " << balance << endl;
        }
    }
    
    // 获取账户类型
    string getAccountType() const override {
        return "Checking";
    }
    
    // 显示账户信息(重写,增加支票账户特有信息)
    void displayAccountInfo() const override {
        BankAccount::displayAccountInfo();
        cout << "Monthly Fee: " << fixed << setprecision(2) << monthlyFee << endl;
        cout << "Transaction Fee: " << fixed << setprecision(2) << transactionFee << endl;
        cout << "Free Transactions: " << freeTransactions << endl;
        cout << "Transactions this month: " << transactionCount << endl;
        cout << "Last Statement Date: " << lastInterestDate.toString() << endl;
    }

private:
    // 应用交易手续费
    void applyTransactionFees() {
        if (transactionCount > freeTransactions) {
            balance -= transactionFee;
            transactions.emplace_back(Date::today(), -transactionFee, "Fee", "Transaction fee");
            cout << "Transaction fee applied: " << fixed << setprecision(2) << transactionFee << endl;
        }
    }
};

// 银行类,管理多个账户
class Bank {
private:
    string bankName;
    vector<BankAccount*> accounts;  // 账户集合

public:
    // 构造函数
    Bank(const string& name) : bankName(name) {}
    
    // 析构函数,释放账户
    ~Bank() {
        for (auto account : accounts) {
            delete account;
        }
    }
    
    // 添加账户
    void addAccount(BankAccount* account) {
        if (account) {
            accounts.push_back(account);
            cout << "Account " << account->getAccountNumber() << " added to " << bankName << endl;
        }
    }
    
    // 查找账户
    BankAccount* findAccount(const string& accountNumber) const {
        for (auto account : accounts) {
            if (account->getAccountNumber() == accountNumber) {
                return account;
            }
        }
        return nullptr;
    }
    
    // 显示所有账户摘要
    void displayAllAccounts() const {
        cout << "\n" << bankName << " Account Summary:" << endl;
        cout << string(60, '-') << endl;
        cout << left << setw(15) << "Account Number"
             << setw(20) << "Account Owner"
             << setw(15) << "Account Type"
             << "Balance" << endl;
        cout << string(60, '-') << endl;
        
        for (auto account : accounts) {
            account->printSummary();
        }
    }
    
    // 对所有账户计算利息
    void calculateAllInterest() {
        cout << "\nCalculating interest for all accounts..." << endl;
        for (auto account : accounts) {
            cout << "\nProcessing account " << account->getAccountNumber() << ":" << endl;
            account->calculateInterest();
        }
    }
};

// 主函数
int main() {
    // 创建银行
    Bank myBank("City Bank");
    
    // 创建不同类型的账户并添加到银行
    BankAccount* savings = new SavingsAccount("SA12345", "John Doe", 0.025, 500, 1000);
    BankAccount* checking = new CheckingAccount("CA67890", "Jane Smith", 8.0, 0.75, 15, 500);
    BankAccount* savings2 = new SavingsAccount("SA54321", "Bob Johnson", 0.02, 300, 2000);
    
    myBank.addAccount(savings);
    myBank.addAccount(checking);
    myBank.addAccount(savings2);
    
    // 显示所有账户摘要
    myBank.displayAllAccounts();
    
    // 进行一些交易
    cout << "\n=== Performing transactions ===" << endl;
    
    // John 的储蓄账户存款和取款
    savings->deposit(500, "Salary deposit");
    savings->withdraw(300, "Cash withdrawal");
    
    // Jane 的支票账户交易
    checking->deposit(1500, "Monthly salary");
    checking->withdraw(200, "Grocery shopping");
    checking->withdraw(100, "Gas");
    checking->deposit(300, "Freelance payment");
    
    // 显示Jane账户的详细信息和交易记录
    cout << "\n=== Jane's account details ===" << endl;
    checking->displayAccountInfo();
    checking->displayTransactions();
    
    // 计算所有账户的利息
    myBank.calculateAllInterest();
    
    // 显示更新后的所有账户摘要
    myBank.displayAllAccounts();
    
    // Bob 的储蓄账户进行一些操作
    cout << "\n=== Bob's transactions ===" << endl;
    savings2->withdraw(1000, "Home improvement");
    savings2->deposit(2000, "Bonus");
    savings2->displayAccountInfo();
    
    return 0;
}

这个改进后的银行账户管理程序利用了多态性,主要特点包括:

  1. 定义了BankAccount抽象基类,包含纯虚函数withdraw()calculateInterest()getAccountType()
  2. 实现了两种具体账户类型:SavingsAccountCheckingAccount,它们各自实现了基类的纯虚函数
  3. 使用Bank类管理多个账户,通过基类指针BankAccount*可以统一处理不同类型的账户
  4. 每个账户类型有不同的利息计算方式和手续费规则,但通过统一的接口进行操作

这种设计的优点是:

  • 扩展性好:如需添加新的账户类型,只需继承BankAccount并实现纯虚函数
  • 接口统一:无论何种账户类型,都通过相同的接口进行存款、取款等操作
  • 易于管理:银行可以统一管理所有账户,无需关心具体账户类型

8.7 深度探索

8.7.1 多态类型与非多态类型

在 C++ 中,类型可以分为多态类型和非多态类型:

  • 多态类型:包含至少一个虚函数的类类型,或继承自多态类型的类。多态类型支持动态绑定和运行时类型识别。
  • 非多态类型:不包含任何虚函数的类类型,以及基本数据类型(如 int、double 等)。非多态类型不支持动态绑定。

下面的代码展示了多态类型和非多态类型的区别:

代码语言:javascript
复制
#include <iostream>
#include <typeinfo>
using namespace std;

// 非多态基类
class NonPolymorphicBase {
public:
    void print() const {
        cout << "NonPolymorphicBase::print()" << endl;
    }
};

// 非多态派生类
class NonPolymorphicDerived : public NonPolymorphicBase {
public:
    void print() const {
        cout << "NonPolymorphicDerived::print()" << endl;
    }
};

// 多态基类
class PolymorphicBase {
public:
    virtual void print() const {
        cout << "PolymorphicBase::print()" << endl;
    }
    
    // 虚析构函数
    virtual ~PolymorphicBase() = default;
};

// 多态派生类
class PolymorphicDerived : public PolymorphicBase {
public:
    void print() const override {
        cout << "PolymorphicDerived::print()" << endl;
    }
};

// 函数模板,打印对象类型和调用print方法
template <typename T>
void processObject(T& obj) {
    cout << "Type: " << typeid(obj).name() << endl;
    obj.print();
    cout << "-------------------------" << endl;
}

int main() {
    // 非多态类型示例
    cout << "=== Non-polymorphic types ===" << endl;
    NonPolymorphicBase npBase;
    NonPolymorphicDerived npDerived;
    
    processObject(npBase);
    processObject(npDerived);
    
    // 通过基类指针指向派生类对象(非多态)
    NonPolymorphicBase* npPtr = &npDerived;
    cout << "Non-polymorphic via base pointer:" << endl;
    cout << "Type: " << typeid(*npPtr).name() << endl;  // 仍然是基类类型
    npPtr->print();  // 调用基类的print()
    cout << "-------------------------" << endl;
    
    // 多态类型示例
    cout << "\n=== Polymorphic types ===" << endl;
    PolymorphicBase pBase;
    PolymorphicDerived pDerived;
    
    processObject(pBase);
    processObject(pDerived);
    
    // 通过基类指针指向派生类对象(多态)
    PolymorphicBase* pPtr = &pDerived;
    cout << "Polymorphic via base pointer:" << endl;
    cout << "Type: " << typeid(*pPtr).name() << endl;  // 正确识别为派生类类型
    pPtr->print();  // 调用派生类的print()
    cout << "-------------------------" << endl;
    
    return 0;
}

运行结果显示:

  • 对于非多态类型,即使通过基类指针指向派生类对象,typeid仍然返回基类类型,调用的也是基类的方法。
  • 对于多态类型,通过基类指针指向派生类对象时,typeid能够正确识别出实际的派生类类型,调用的也是派生类的方法。
8.7.2 运行时类型识别

        运行时类型识别(RTTI,Run-Time Type Information)是 C++ 提供的一种机制,允许在程序运行时获取对象的类型信息。主要通过以下两个操作符实现:

  1. dynamic_cast:用于在继承层次中进行安全的类型转换
  2. typeid:用于获取对象的类型信息

下面是展示 RTTI 用法的示例:

代码语言:javascript
复制
#include <iostream>
#include <typeinfo>
#include <string>
using namespace std;

// 基类:动物
class Animal {
protected:
    string name;

public:
    Animal(const string& n) : name(n) {}
    virtual void makeSound() const = 0;  // 纯虚函数
    virtual void move() const {
        cout << name << " is moving." << endl;
    }
    virtual ~Animal() = default;
    
    string getName() const { return name; }
};

// 派生类:狗
class Dog : public Animal {
private:
    string breed;

public:
    Dog(const string& n, const string& b) : Animal(n), breed(b) {}
    
    void makeSound() const override {
        cout << name << " barks: Woof! Woof!" << endl;
    }
    
    void move() const override {
        cout << name << " is running." << endl;
    }
    
    // 狗特有的方法
    void fetch() const {
        cout << name << " is fetching the ball." << endl;
    }
    
    string getBreed() const { return breed; }
};

// 派生类:猫
class Cat : public Animal {
public:
    Cat(const string& n) : Animal(n) {}
    
    void makeSound() const override {
        cout << name << " meows: Meow! Meow!" << endl;
    }
    
    void move() const override {
        cout << name << " is walking silently." << endl;
    }
    
    // 猫特有的方法
    void climbTree() const {
        cout << name << " is climbing the tree." << endl;
    }
};

// 派生类:鸟
class Bird : public Animal {
public:
    Bird(const string& n) : Animal(n) {}
    
    void makeSound() const override {
        cout << name << " sings: Chirp! Chirp!" << endl;
    }
    
    void move() const override {
        cout << name << " is flying." << endl;
    }
    
    // 鸟特有的方法
    void flyHigh() const {
        cout << name << " is flying high in the sky." << endl;
    }
};

// 处理动物的函数
void processAnimal(const Animal& animal) {
    cout << "\nProcessing " << animal.getName() << ":" << endl;
    cout << "Type: " << typeid(animal).name() << endl;
    
    animal.makeSound();
    animal.move();
    
    // 使用dynamic_cast检查并转换为特定类型
    // 检查是否是狗
    if (const Dog* dog = dynamic_cast<const Dog*>(&animal)) {
        cout << "Breed: " << dog->getBreed() << endl;
        dog->fetch();
    }
    // 检查是否是猫
    else if (const Cat* cat = dynamic_cast<const Cat*>(&animal)) {
        cat->climbTree();
    }
    // 检查是否是鸟
    else if (const Bird* bird = dynamic_cast<const Bird*>(&animal)) {
        bird->flyHigh();
    }
    else {
        cout << "Unknown animal type." << endl;
    }
}

// 使用typeid比较类型
void compareTypes(const Animal& a1, const Animal& a2) {
    cout << "\nComparing types:" << endl;
    cout << a1.getName() << " is type: " << typeid(a1).name() << endl;
    cout << a2.getName() << " is type: " << typeid(a2).name() << endl;
    
    if (typeid(a1) == typeid(a2)) {
        cout << "They are the same type of animal." << endl;
    }
    else {
        cout << "They are different types of animals." << endl;
    }
}

int main() {
    // 创建不同类型的动物
    Dog dog("Buddy", "Golden Retriever");
    Cat cat("Whiskers");
    Bird bird("Tweety");
    
    // 处理各种动物
    processAnimal(dog);
    processAnimal(cat);
    processAnimal(bird);
    
    // 通过基类指针数组处理动物
    Animal* animals[] = {&dog, &cat, &bird};
    cout << "\n=== Processing animals through base pointers ===" << endl;
    for (const auto& animal : animals) {
        animal->makeSound();
        
        // 使用dynamic_cast进行向下转换
        if (Dog* d = dynamic_cast<Dog*>(animal)) {
            d->fetch();
        }
    }
    
    // 比较动物类型
    compareTypes(dog, cat);
    compareTypes(cat, *animals[1]);  // 比较猫和通过指针访问的猫
    
    // 错误的类型转换示例
    cout << "\n=== Testing invalid cast ===" << endl;
    Animal* animalPtr = &dog;
    Cat* catPtr = dynamic_cast<Cat*>(animalPtr);
    
    if (catPtr) {
        cout << "Cast succeeded: " << catPtr->getName() << endl;
    }
    else {
        cout << "Cast failed: Cannot cast Dog to Cat" << endl;
    }
    
    return 0;
}

上面的代码展示了 RTTI 的主要用法:

  1. dynamic_cast用于安全地将基类指针或引用转换为派生类指针或引用。如果转换失败,对于指针会返回nullptr,对于引用会抛出bad_cast异常。
  2. typeid用于获取对象的类型信息,可以:
    • 获取类型名称:typeid(obj).name()
    • 比较两个对象的类型:typeid(a) == typeid(b)

使用 RTTI 时需要注意:

  • RTTI 只适用于多态类型(包含虚函数的类)
  • 过度使用 RTTI 可能表明设计存在问题,通常应该优先使用虚函数多态而非显式的类型检查
  • RTTI 会带来一定的性能开销
8.7.3 虚函数动态绑定的实现原理

        C++ 中虚函数的动态绑定(也称为迟绑定)是通过虚函数表(vtable)和虚表指针(vptr)实现的,这是编译器层面的实现机制。

基本原理:

  1. 每个包含虚函数的类(或其基类包含虚函数)都有一个虚函数表(vtable),这是一个存储虚函数地址的静态数组。
  2. 类的每个对象都包含一个指向该类虚函数表的指针(vptr),通常位于对象内存布局的最开始位置。
  3. 当派生类重写基类的虚函数时,派生类的虚函数表中会用新的函数地址替换对应的基类虚函数地址。
  4. 当通过基类指针或引用调用虚函数时,编译器会生成代码,通过对象的 vptr 找到对应的 vtable,然后调用表中相应的函数。

下面的代码可以帮助理解虚函数表的概念(注意:这只是概念演示,实际实现可能因编译器而异):

代码语言:javascript
复制
#include <iostream>
using namespace std;

// 基类
class Base {
public:
    virtual void func1() { cout << "Base::func1()" << endl; }
    virtual void func2() { cout << "Base::func2()" << endl; }
    void nonVirtualFunc() { cout << "Base::nonVirtualFunc()" << endl; }
};

// 派生类
class Derived : public Base {
public:
    void func1() override { cout << "Derived::func1()" << endl; }  // 重写func1
    virtual void func3() { cout << "Derived::func3()" << endl; }    // 新增虚函数
};

// 打印虚函数表(仅作演示,依赖于特定编译器实现)
void printVTable(void* obj) {
    // 假设vptr是对象的第一个成员
    void** vtable = *reinterpret_cast<void***>(obj);
    
    cout << "VTable address: " << vtable << endl;
    
    // 打印前几个虚函数(实际数量未知,这里只打印几个)
    for (int i = 0; i < 3; ++i) {
        cout << "VTable[" << i << "]: " << vtable[i];
        
        // 尝试调用函数(不安全,仅作演示)
        using FuncType = void(*)();
        FuncType func = reinterpret_cast<FuncType>(vtable[i]);
        if (func) {
            cout << " -> ";
            func();  // 注意:这样调用没有this指针,实际中会出错
        }
        cout << endl;
    }
}

int main() {
    Base base;
    Derived derived;
    
    cout << "=== Base object vtable ===" << endl;
    printVTable(&base);
    
    cout << "\n=== Derived object vtable ===" << endl;
    printVTable(&derived);
    
    cout << "\n=== Polymorphic behavior ===" << endl;
    Base* basePtr = &derived;
    basePtr->func1();  // 调用Derived::func1()
    basePtr->func2();  // 调用Base::func2(),未被重写
    
    // 下面的代码无法编译,因为基类中没有func3()
    // basePtr->func3();
    
    // 需要显式转换才能调用派生类特有函数
    if (Derived* dPtr = dynamic_cast<Derived*>(basePtr)) {
        dPtr->func3();
    }
    
    return 0;
}

虚函数动态绑定的优点:

  • 实现了多态性,提高了代码的灵活性和可扩展性
  • 基类可以定义接口,派生类可以提供具体实现
  • 可以通过基类指针或引用统一处理不同派生类的对象

虚函数的开销:

  • 每个对象需要额外存储一个 vptr 指针,增加了内存开销
  • 调用虚函数需要通过 vtable 间接访问,比非虚函数调用多了一次指针间接引用,有轻微的性能开销
  • 类需要维护一个 vtable,增加了程序的静态存储开销

8.8 小结

本章详细介绍了 C++ 中的多态性,主要内容总结如下:

  1. 多态性概述:多态是面向对象编程的重要特性,允许同一接口有多种实现。C++ 中的多态分为静态多态(编译时)和动态多态(运行时)。
  2. 运算符重载:允许为自定义类型重新定义运算符的行为,可重载为类的成员函数或非成员函数(友元)。运算符重载不能改变运算符的优先级、结合性和操作数个数。
  3. 虚函数:在基类中声明为virtual的函数,允许派生类重写。通过基类指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数版本,实现动态绑定。
  4. 虚析构函数:当基类指针指向派生类对象并删除对象时,虚析构函数确保派生类的析构函数会被调用,避免资源泄漏。
  5. 纯虚函数与抽象类:纯虚函数是没有实现的虚函数(声明为virtual 返回类型 函数名() = 0)。包含纯虚函数的类为抽象类,不能实例化,只能作为基类使用,强制派生类实现接口。
  6. 变步长梯形积分算法实例:展示了如何利用多态性设计灵活的数值计算程序,通过抽象基类定义函数接口,派生类实现具体函数。
  7. 银行账户管理程序改进:利用多态性设计了可扩展的银行账户系统,支持不同类型的账户(储蓄账户、支票账户),每种账户有不同的行为。
  8. 深度探索
    • 多态类型与非多态类型的区别
    • 运行时类型识别(RTTI)的使用,包括dynamic_casttypeid
    • 虚函数动态绑定的实现原理,通过虚函数表(vtable)和虚表指针(vptr)实现

        多态性是 C++ 中非常强大的特性,合理使用多态可以提高代码的可读性、可维护性和可扩展性。掌握多态性的概念和实现机制,对于编写高质量的面向对象 C++ 程序至关重要。

        通过本章的学习和实例练习,应该能够理解多态性的原理,并能在实际编程中灵活运用虚函数、纯虚函数和抽象类等特性,设计出更加优雅和高效的 C++ 程序。

        希望本章的内容对您有所帮助!如有任何问题或建议,欢迎在评论区留言讨论。

        以上就是《C++ 程序设计》第 8 章 - 多态性的全部内容。本文提供的所有代码都经过测试,可以直接编译运行。建议读者动手实践这些示例,加深对多态性的理解和应用能力。

        祝大家学习愉快,编程顺利!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 思维导图
  • 8.1 多态性概述
    • 8.1.1 多态的类型
    • 8.1.2 多态的实现
  • 8.2 运算符重载
    • 8.2.1 运算符重载的规则
    • 8.2.2 运算符重载为成员函数
    • 8.2.2 运算符重载为成员函数
    • 8.2.3 运算符重载为非成员函数
  • 8.3 虚函数
    • 8.3.1 一般虚函数成员
    • 8.3.2 虚析构函数
  • 8.4 纯虚函数与抽象类
    • 8.4.1 纯虚函数
    • 8.4.2 抽象类
  • 8.5 程序实例 —— 变步长梯形积分算法求解函数的定积分
    • 8.5.1 算法基本原理
    • 8.5.2 程序设计分析
    • 8.5.3 源程序及说明
    • 8.5.4 运行结果与分析
  • 8.6 综合实例 —— 对个人银行账户管理程序的改进
  • 8.7 深度探索
    • 8.7.1 多态类型与非多态类型
    • 8.7.2 运行时类型识别
    • 8.7.3 虚函数动态绑定的实现原理
  • 8.8 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档