69、C++ 多态与流输入输出深度解析

C++ 多态与流输入输出深度解析

1. 多态概述

多态性使我们能够 “泛型编程” 而非 “具体编程”。它允许我们编写程序来处理同一类层次结构中的类对象,就好像它们都是该层次结构的基类对象一样。借助多态,我们可以设计和实现易于扩展的系统,新类可以在很少或无需修改程序通用部分的情况下添加进来。只有那些需要直接了解新添加到层次结构中的类的程序部分才需要进行修改。

1.1 多态的应用场景

在多态视频游戏中,一个函数调用可以根据调用该函数的对象类型导致不同的操作发生。这使得设计和实现更具扩展性的系统成为可能,程序可以处理在开发时可能不存在的类型对象。

1.2 继承层次结构中对象的关系

C++ 支持多态性,即通过继承相关的不同类的对象对同一个成员函数调用做出不同响应的能力。多态性通过虚函数和动态绑定来实现。
- 虚函数调用 :当使用基类指针或引用调用虚函数时,C++ 会选择与对象关联的适当派生类中正确的重写函数。
- 静态绑定与动态绑定 :如果通过对象名和点成员选择运算符引用特定对象来调用虚函数,则引用在编译时解析(这称为静态绑定);调用的虚函数是为该特定对象的类定义的函数。
- 虚析构函数 :如果类包含虚函数,则应将基类析构函数声明为虚函数。这样可以确保当通过基类指针或引用删除派生类对象时,继承层次结构中所有适当的析构函数都会运行。

1.3 类型字段和 switch 语句

使用虚函数的多态编程可以消除对 switch 逻辑的需求。可以使用虚函数机制自动执行等效的逻辑,从而避免与 switch 逻辑通常相关的错误。

1.4 抽象类和纯虚函数

  • 抽象类 :通常用作基类,不能实例化抽象类的对象。
  • 具体类 :可以实例化对象的类。
  • 纯虚函数 :通过在声明中使用纯说明符(= 0)声明一个或多个纯虚函数来创建抽象类。如果一个类从包含纯虚函数的类派生,并且该派生类没有为该纯虚函数提供定义,则该虚函数在派生类中仍然是纯虚函数,派生类也是抽象类。虽然不能实例化抽象基类的对象,但可以声明指向抽象基类对象的指针和引用,这些指针和引用可用于对从具体派生类实例化的派生类对象进行多态操作。

1.5 多态、虚函数和动态绑定的底层原理

动态绑定要求在运行时将对虚成员函数的调用路由到适合该类的虚函数版本。虚函数表(vtable)实现为一个包含函数指针的数组。每个具有虚函数的类都有一个 vtable。对于类中的每个虚函数,vtable 都有一个条目,其中包含一个函数指针,指向用于该类对象的虚函数版本。特定类使用的虚函数可以是该类中定义的函数,也可以是从层次结构中较高基类直接或间接继承的函数。

1.6 案例研究:使用多态和运行时类型信息的工资系统

  • dynamic_cast 运算符 :检查指针所指向对象的类型,然后确定该类型与指针要转换的类型是否存在 “is-a” 关系。如果是,则 dynamic_cast 返回对象的地址;否则返回 nullptr。
  • typeid 运算符 :返回一个对 type_info 对象的引用,该对象包含操作数类型的信息,包括类型名称。要使用 typeid,程序必须包含头文件 。

1.7 自我回顾练习

1.7.1 填空题
  • a) 将基类对象视为派生类对象可能会导致错误。
  • b) 多态有助于消除 switch 逻辑。
  • c) 如果一个类包含至少一个纯虚函数,则它是抽象类。
  • d) 可以实例化对象的类称为具体类。
  • e) 可以使用 dynamic_cast 运算符安全地将基类指针向下转换。
  • f) typeid 运算符返回一个对 type_info 对象的引用。
  • g) 多态涉及使用基类指针或引用在基类和派生类对象上调用虚函数。
  • h) 可重写的函数使用关键字 virtual 声明。
  • i) 将基类指针转换为派生类指针称为向下转换。
1.7.2 判断题
  • a) 错误。抽象基类可以包含具有实现的虚函数。
  • b) 错误。使用派生类句柄引用基类对象是危险的。
  • c) 错误。类永远不会被声明为虚类。相反,通过在类中包含至少一个纯虚函数来使类成为抽象类。
  • d) 正确。
  • e) 正确。

1.8 练习题

  • 编程的一般性 :多态如何使你能够 “泛型编程” 而非 “具体编程”?讨论 “泛型编程” 的关键优势。
  • 多态与 switch 逻辑 :讨论使用 switch 逻辑编程的问题。解释为什么多态可以是使用 switch 逻辑的有效替代方案。
  • 继承接口与实现 :区分继承接口和继承实现。为继承接口设计的继承层次结构与为继承实现设计的继承层次结构有何不同?
  • 虚函数 :什么是虚函数?描述一个适合使用虚函数的情况。
  • 动态绑定与静态绑定 :区分静态绑定和动态绑定。解释虚函数和 vtable 在动态绑定中的使用。
  • 虚函数与纯虚函数 :区分虚函数和纯虚函数。
  • 抽象基类 :为形状层次结构建议一个或多个抽象基类级别。
  • 多态与可扩展性 :多态如何促进可扩展性?
  • 多态应用 :开发一个具有复杂图形输出的飞行模拟器,解释为什么多态编程对于这类问题特别有效。
  • 工资系统修改 :修改工资系统,包括 Employee 类中的私有数据成员 birthDate。使用 Date 类表示员工的生日。假设每月处理一次工资单。创建一个 Employee 引用向量来存储各种员工对象。在循环中,多态地计算每个员工的工资,如果当前月份是员工生日所在的月份,则向该员工的工资金额中添加 100 美元的奖金。
  • 包裹继承层次结构 :使用包裹继承层次结构创建一个程序,显示地址信息并计算几个包裹的运输成本。程序应包含一个指向 TwoDayPackage 和 OvernightPackage 类对象的 Package 指针向量。遍历向量以多态地处理包裹。对于每个包裹,调用 get 函数获取发件人和收件人的地址信息,然后打印这两个地址,就像它们出现在邮寄标签上一样。此外,调用每个包裹的 calculateCost 成员函数并打印结果。跟踪向量中所有包裹的总运输成本,并在循环结束时显示该总数。
  • 多态银行程序 :开发一个使用账户层次结构的多态银行程序。创建一个指向 SavingsAccount 和 CheckingAccount 对象的 Account 指针向量。对于向量中的每个账户,允许用户指定要从账户中提取的金额和要存入账户的金额。在处理每个账户时,确定其类型。如果账户是 SavingsAccount,则使用成员函数 calculateInterest 计算应支付给该账户的利息,然后使用成员函数 credit 将利息添加到账户余额中。处理完一个账户后,通过调用基类成员函数 getBalance 打印更新后的账户余额。
  • 工资系统修改 :修改工资系统,包括额外的 Employee 子类 PieceWorker 和 HourlyWorker。PieceWorker 表示根据生产的商品件数支付工资的员工。HourlyWorker 表示根据小时工资和工作小时数支付工资的员工。小时工对于超过 40 小时的所有工作时间支付加班费(小时工资的 1.5 倍)。在 main 函数的 Employee 指针向量中添加每个新类的对象指针。对于每个员工,显示其字符串表示形式和收入。
  • 碳足迹抽象类 :使用仅包含纯虚函数的抽象类,可以为可能不同的类指定相似的行为。创建三个不相关的小类:Building、Car 和 Bicycle。为每个类赋予一些独特的适当属性和行为。编写一个抽象类 CarbonFootprint,仅包含一个纯虚函数 getCarbonFootprint。让每个类从该抽象类继承并实现 getCarbonFootprint 方法来计算该类的适当碳足迹。编写一个应用程序,创建每个类的对象,将指向这些对象的指针放入一个 CarbonFootprint 指针向量中,然后遍历该向量,多态地调用每个对象的 getCarbonFootprint 方法。对于每个对象,打印一些识别信息和该对象的碳足迹。

2. C++ 流输入输出

2.1 流输入输出概述

C++ 提供了一系列足以执行大多数常见 I/O 操作的功能,并概述了其余功能。许多 I/O 功能是面向对象的,这种 I/O 风格利用了 C++ 的其他特性,如引用、函数重载和运算符重载。

2.2 流的类型

  • 经典流与标准流 :需要区分经典流和标准流的概念。
  • iostream 库头文件 :使用相关的头文件来支持流输入输出操作。
  • 流输入输出类和对象 :了解流输入输出类及其对象的使用。

2.3 流输出

  • char * 变量的输出 :可以直接输出 char * 变量。
  • 使用成员函数 put 输出字符 :通过 put 成员函数可以输出单个字符。

2.4 流输入

  • get 和 getline 成员函数 :用于获取输入的字符或字符串。
  • istream 成员函数 peek、putback 和 ignore :peek 函数用于查看下一个字符,putback 函数用于将字符放回输入流,ignore 函数用于忽略输入流中的字符。
  • 类型安全的 I/O :C++ 使用类型安全的 I/O,每个 I/O 操作都根据数据类型执行。如果 I/O 函数已定义用于处理特定数据类型,则调用该成员函数来处理该数据类型。如果实际数据类型与处理该数据类型的函数不匹配,则编译器会生成错误。

2.5 无格式 I/O

使用 read、write 和 gcount 函数进行无格式 I/O 操作。

2.6 流操纵符

  • 整数流基 :使用 dec、oct、hex 和 setbase 来设置整数的进制。
  • 浮点精度 :使用 precision 和 setprecision 来设置浮点数的精度。
  • 字段宽度 :使用 width 和 setw 来设置字段宽度。
  • 用户定义的输出流操纵符 :可以自定义输出流操纵符。

2.7 流格式状态和流操纵符

  • 尾随零和小数点 :使用 showpoint 来显示尾随零和小数点。
  • 对齐方式 :使用 left、right 和 internal 来设置对齐方式。
  • 填充 :使用 fill 和 setfill 来设置填充字符。
  • 整数流基 :使用 dec、oct、hex、showbase 来设置整数的进制和显示进制前缀。
  • 浮点数;科学和固定表示法 :使用 scientific 和 fixed 来设置浮点数的表示法。
  • 大小写控制 :使用 uppercase 来控制大小写。
  • 指定布尔格式 :使用 boolalpha 来指定布尔值的输出格式。
  • 通过成员函数 flags 设置和重置格式状态 :可以使用 flags 成员函数来设置和重置流的格式状态。

2.8 流错误状态

需要了解流的错误状态,以便在输入输出操作失败时进行处理。

2.9 绑定输出流到输入流

可以将输出流绑定到输入流,以确保输出操作在输入操作之前完成。

2.10 总结

C++ 的流输入输出提供了丰富的功能,包括类型安全的 I/O、流操纵符和格式控制等。通过合理使用这些功能,可以更方便地进行输入输出操作,并提高程序的健壮性和可维护性。

多态和流输入输出的关系

多态和流输入输出是 C++ 中两个重要的特性,它们可以相互结合使用。例如,在处理不同类型的对象时,可以使用多态来调用不同对象的输入输出函数,从而实现统一的输入输出接口。同时,流输入输出的类型安全特性可以确保在多态操作中输入输出的数据类型正确。

以下是一个简单的多态和流输入输出结合的示例代码:

#include <iostream>
#include <vector>

// 抽象基类
class Shape {
public:
    virtual void draw() const = 0;
    virtual ~Shape() {}
};

// 派生类:圆形
class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

// 派生类:矩形
class Rectangle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a rectangle." << std::endl;
    }
};

int main() {
    std::vector<Shape*> shapes;
    shapes.push_back(new Circle());
    shapes.push_back(new Rectangle());

    for (auto shape : shapes) {
        shape->draw();
    }

    // 释放内存
    for (auto shape : shapes) {
        delete shape;
    }

    return 0;
}

在这个示例中,Shape 是抽象基类,Circle 和 Rectangle 是派生类。通过多态,我们可以使用基类指针来调用不同派生类的 draw 函数。同时,使用流输出语句将结果输出到控制台。

总结

多态和流输入输出是 C++ 中非常重要的特性,它们可以帮助我们编写更灵活、可扩展和健壮的程序。通过合理使用多态和流输入输出的功能,可以提高程序的开发效率和质量。在实际应用中,我们可以根据具体的需求选择合适的方法来实现多态和流输入输出操作。

未来展望

随着 C++ 语言的不断发展,多态和流输入输出的功能可能会进一步增强和优化。例如,可能会出现更多的流操纵符和更强大的类型安全机制。同时,多态的应用场景也可能会更加广泛,为软件开发带来更多的便利。我们需要不断学习和掌握这些新特性,以跟上技术的发展步伐。

流程图

graph TD;
    A[开始] --> B[创建基类指针向量];
    B --> C[添加派生类对象到向量];
    C --> D[遍历向量调用虚函数];
    D --> E[输出结果];
    E --> F[释放内存];
    F --> G[结束];

表格

特性 描述
多态 允许不同类的对象对同一函数调用做出不同响应
流输入输出 提供类型安全的输入输出操作
虚函数 实现多态的关键机制
流操纵符 用于控制输入输出的格式

通过以上内容,我们对 C++ 中的多态和流输入输出有了更深入的了解。在实际编程中,我们可以根据具体需求灵活运用这些特性,提高程序的质量和可维护性。

3. 多态与流输入输出的实际应用案例

3.1 工资系统案例扩展

在之前提到的工资系统中,我们可以进一步展示多态和流输入输出的结合应用。以下是一个更完整的示例代码:

#include <iostream>
#include <vector>
#include <string>

// 基类 Employee
class Employee {
public:
    Employee(const std::string& name, int birthMonth) : name(name), birthMonth(birthMonth) {}
    virtual double earnings() const = 0;
    virtual void print() const = 0;
    int getBirthMonth() const { return birthMonth; }
    virtual ~Employee() {}
protected:
    std::string name;
    int birthMonth;
};

// 派生类 CommissionEmployee
class CommissionEmployee : public Employee {
public:
    CommissionEmployee(const std::string& name, int birthMonth, double sales, double rate)
        : Employee(name, birthMonth), sales(sales), rate(rate) {}
    double earnings() const override {
        return sales * rate;
    }
    void print() const override {
        std::cout << "Commission Employee: " << name << std::endl;
    }
private:
    double sales;
    double rate;
};

// 派生类 BasePlusCommissionEmployee
class BasePlusCommissionEmployee : public CommissionEmployee {
public:
    BasePlusCommissionEmployee(const std::string& name, int birthMonth, double sales, double rate, double base)
        : CommissionEmployee(name, birthMonth, sales, rate), baseSalary(base) {}
    double earnings() const override {
        return CommissionEmployee::earnings() + baseSalary;
    }
    void print() const override {
        std::cout << "Base Plus Commission Employee: " << name << std::endl;
    }
    double getBaseSalary() const { return baseSalary; }
    void setBaseSalary(double base) { baseSalary = base; }
private:
    double baseSalary;
};

// 处理工资系统的函数
void processPayroll(const std::vector<Employee*>& employees, int currentMonth) {
    double totalPayroll = 0;
    for (auto employee : employees) {
        double pay = employee->earnings();
        if (employee->getBirthMonth() == currentMonth) {
            pay += 100;
        }
        employee->print();
        std::cout << "Pay: " << pay << std::endl;
        totalPayroll += pay;
    }
    std::cout << "Total Payroll: " << totalPayroll << std::endl;
}

int main() {
    std::vector<Employee*> employees;
    employees.push_back(new CommissionEmployee("John", 3, 1000, 0.1));
    employees.push_back(new BasePlusCommissionEmployee("Jane", 6, 2000, 0.15, 500));

    int currentMonth = 6;
    processPayroll(employees, currentMonth);

    // 释放内存
    for (auto employee : employees) {
        delete employee;
    }

    return 0;
}

在这个示例中, Employee 是抽象基类, CommissionEmployee BasePlusCommissionEmployee 是派生类。通过多态,我们可以使用基类指针 Employee* 来调用不同派生类的 earnings print 函数。同时,使用流输出语句将员工信息和工资信息输出到控制台。

3.2 包裹继承层次结构案例

以下是一个包裹继承层次结构的示例代码,展示了多态和流输入输出的应用:

#include <iostream>
#include <vector>
#include <string>

// 基类 Package
class Package {
public:
    Package(const std::string& sender, const std::string& recipient, double weight, double costPerOunce)
        : sender(sender), recipient(recipient), weight(weight), costPerOunce(costPerOunce) {}
    virtual double calculateCost() const = 0;
    void printAddresses() const {
        std::cout << "Sender: " << sender << std::endl;
        std::cout << "Recipient: " << recipient << std::endl;
    }
    virtual ~Package() {}
protected:
    std::string sender;
    std::string recipient;
    double weight;
    double costPerOunce;
};

// 派生类 TwoDayPackage
class TwoDayPackage : public Package {
public:
    TwoDayPackage(const std::string& sender, const std::string& recipient, double weight, double costPerOunce, double flatFee)
        : Package(sender, recipient, weight, costPerOunce), flatFee(flatFee) {}
    double calculateCost() const override {
        return weight * costPerOunce + flatFee;
    }
private:
    double flatFee;
};

// 派生类 OvernightPackage
class OvernightPackage : public Package {
public:
    OvernightPackage(const std::string& sender, const std::string& recipient, double weight, double costPerOunce, double extraFeePerOunce)
        : Package(sender, recipient, weight, costPerOunce), extraFeePerOunce(extraFeePerOunce) {}
    double calculateCost() const override {
        return weight * (costPerOunce + extraFeePerOunce);
    }
private:
    double extraFeePerOunce;
};

// 处理包裹的函数
void processPackages(const std::vector<Package*>& packages) {
    double totalCost = 0;
    for (auto package : packages) {
        package->printAddresses();
        double cost = package->calculateCost();
        std::cout << "Shipping Cost: " << cost << std::endl;
        totalCost += cost;
    }
    std::cout << "Total Shipping Cost: " << totalCost << std::endl;
}

int main() {
    std::vector<Package*> packages;
    packages.push_back(new TwoDayPackage("Alice", "Bob", 5, 2, 10));
    packages.push_back(new OvernightPackage("Charlie", "David", 3, 3, 1));

    processPackages(packages);

    // 释放内存
    for (auto package : packages) {
        delete package;
    }

    return 0;
}

在这个示例中, Package 是抽象基类, TwoDayPackage OvernightPackage 是派生类。通过多态,我们可以使用基类指针 Package* 来调用不同派生类的 calculateCost 函数。同时,使用流输出语句将包裹的地址信息和运输成本输出到控制台。

4. 多态和流输入输出的操作步骤总结

4.1 多态的实现步骤
  1. 定义抽象基类 :包含纯虚函数,作为派生类的公共接口。
  2. 定义派生类 :继承自抽象基类,并实现纯虚函数。
  3. 使用基类指针或引用 :通过基类指针或引用调用虚函数,实现多态调用。
  4. 释放内存 :如果使用动态内存分配,需要在适当的时候释放内存,避免内存泄漏。
4.2 流输入输出的操作步骤
  1. 包含必要的头文件 :例如 <iostream> 用于基本的输入输出操作, <iomanip> 用于流操纵符。
  2. 使用流对象 :如 std::cout 用于输出, std::cin 用于输入。
  3. 使用流操纵符 :根据需要设置输出格式,如设置精度、宽度、对齐方式等。
  4. 处理流错误状态 :检查流的错误状态,处理输入输出操作失败的情况。

5. 总结与展望

5.1 总结

多态和流输入输出是 C++ 中非常强大的特性,它们可以极大地提高程序的灵活性、可扩展性和健壮性。多态允许我们以统一的方式处理不同类型的对象,而流输入输出提供了类型安全的输入输出操作和丰富的格式控制功能。通过结合使用这两个特性,我们可以编写更加优雅和高效的代码。

5.2 展望

随着 C++ 标准的不断发展,多态和流输入输出的功能可能会进一步增强。未来可能会有更多的流操纵符和更强大的类型安全机制出现,同时多态的应用场景也可能会更加广泛。作为开发者,我们需要不断学习和掌握这些新特性,以适应不断变化的技术环境。

流程图

graph TD;
    A[开始] --> B[创建包裹指针向量];
    B --> C[添加不同类型包裹对象到向量];
    C --> D[遍历向量处理包裹];
    D --> E[输出包裹地址信息];
    E --> F[计算并输出运输成本];
    F --> G[累加总运输成本];
    G --> H[输出总运输成本];
    H --> I[释放内存];
    I --> J[结束];

表格

特性 操作步骤
多态 定义抽象基类 -> 定义派生类并实现纯虚函数 -> 使用基类指针或引用调用虚函数 -> 释放内存
流输入输出 包含必要头文件 -> 使用流对象 -> 使用流操纵符设置格式 -> 处理流错误状态

通过以上的案例和总结,我们对 C++ 中的多态和流输入输出有了更全面的认识。在实际编程中,我们可以根据具体需求灵活运用这些特性,提高程序的质量和可维护性。

欢迎使用“可调增益放大器 Multisim”设计资源包!本资源专为电子爱好者、学生以及工程师设计,旨在展示如何在著名的电路仿真软件Multisim环境下,实现一个具有创新性的数字控制增益放大器项目。 项目概述 在这个项目中,我们通过巧妙结合模拟电路数字逻辑,设计出一款独特且实用的放大器。该放大器的特点在于其增益可以被精确调控,并非固定不变。用户可以通过控制键,轻松地改变放大器的增益状态,使其在1到8倍之间平滑切换。每一步增益的变化都直观地通过LED数码管显示出来,为观察和调试提供了极大的便利。 技术特点 数字控制: 使用数字输入来调整模拟放大器的增益,展示了数字信号对模拟电路控制的应用。 动态增益调整: 放大器支持8级增益调节(1x至8x),满足不同应用场景的需求。 可视化的增益指示: 利用LED数码管实时显示当前的放大倍数,增强项目的交互性和实用性。 Multisim仿真环境: 所有设计均在Multisim中完成,确保了设计的仿真准确性和学习的便捷性。 使用指南 软件准备: 确保您的计算机上已安装最新版本的Multisim软件。 打开项目: 导入提供的Multisim项目文件,开始查看或修改设计。 仿真体验: 在仿真模式下测试放大器的功能,观察增益变化及LED显示是否符合预期。 实验调整: 根据需要调整电路参数以优化性能。 实物搭建 (选做): 参考设计图,在真实硬件上复现实验。
【数据融合】【状态估计】基于KF、UKF、EKF、PF、FKF、DKF卡尔曼滤波KF、无迹卡尔曼滤波UKF、拓展卡尔曼滤波数据融合研究(Matlab代码实现)内容概要:本文围绕状态估计数据融合技术展开,重点研究了基于卡尔曼滤波(KF)、无迹卡尔曼滤波(UKF)、扩展卡尔曼滤波(EKF)、粒子滤波(PF)、固定区间卡尔曼滤波(FKF)和分布式卡尔曼滤波(DKF)等多种滤波算法的理论Matlab实现,涵盖了非线性系统状态估计、多源数据融合、目标跟踪及传感器优化等应用场景。文中通过Matlab代码实例演示了各类滤波方法在动态系统中的性能对比适用条件,尤其强调在复杂噪声环境和非线性系统中的实际应用价值。; 适合人群:具备一定信号处理、控制理论基础的研究生、科研人员及从事自动化、导航、机器人、电力电子等相关领域的工程技术人员。; 使用场景及目标:①用于动态系统的状态估计噪声抑制,如目标跟踪、无人机姿态估计、电池SOC估算等;②为科研项目提供主滤波算法的Matlab实现参考,支持算法复现性能对比;③辅助教学课程设计,帮助理解滤波算法的核心原理编程实现。; 阅读建议:建议结合Matlab代码实践操作,重点关注不同滤波算法在非线性、非高斯环境下的表现差异,建议读者按章节顺序学习,并参考文档中提供的网盘资源获取完整代码仿真模型以加深理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值