OOP-实验四

实验任务一

源代码

GradeCalc.hpp

点击查看代码
#pragma once

#include <vector>
#include <array>
#include <string>

class GradeCalc {
public:
    GradeCalc(const std::string &cname);      
    void input(int n);                         // 录入n个成绩
    void output() const;                      // 输出成绩
    void sort(bool ascending = false);        // 排序 (默认降序)
    int min() const;                          // 返回最低分(如成绩未录入,返回-1)
    int max() const;                          // 返回最高分 (如成绩未录入,返回-1)
    double average() const;                   // 返回平均分 (如成绩未录入,返回0.0)
    void info();                      // 输出课程成绩信息 

private:
    void compute();     // 成绩统计

private:
    std::string course_name;     // 课程名
    std::vector<int> grades;     // 课程成绩
    std::array<int, 5> counts;      // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100]
    std::array<double, 5> rates;    // 保存各分数段人数占比 
    bool is_dirty;      // 脏标记,记录是否成绩信息有变更
};

GradeCalc.cpp

点击查看代码
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

#include "GradeCalc.hpp"

GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true} {
    counts.fill(0);
    rates.fill(0);   
}

void GradeCalc::input(int n) {
    if(n < 0) {
        std::cerr << "无效输入! 人数不能为负数\n";
        std::exit(1);
    }

    grades.reserve(n);

    int grade;

    for(int i = 0; i < n;) {
        std::cin >> grade;

        if(grade < 0 || grade > 100) {
            std::cerr << "无效输入! 分数须在[0,100]\n";
            continue;
        }
        
        grades.push_back(grade);
        ++i;
    }

    is_dirty = true;  // 设置脏标记:成绩信息有变更
}

void GradeCalc::output() const {
    for(auto grade: grades)
        std::cout << grade << ' ';
    std::cout << std::endl;
}
    
void GradeCalc::sort(bool ascending) {
    if(ascending)
        std::sort(grades.begin(), grades.end());
    else
        std::sort(grades.begin(), grades.end(), std::greater<int>());
}

int GradeCalc::min() const {
    if(grades.empty())
        return -1;

    auto it = std::min_element(grades.begin(), grades.end());
    return *it;
}

int GradeCalc::max() const {
    if(grades.empty()) 
        return -1;

    auto it = std::max_element(grades.begin(), grades.end());
    return *it;
}

double GradeCalc::average() const {
    if(grades.empty())
        return 0.0;

    double avg = std::accumulate(grades.begin(), grades.end(), 0.0)/grades.size();
    return avg;
}

void GradeCalc::info() {
    if(is_dirty) 
       compute();

    std::cout << "课程名称:\t" << course_name << std::endl;
    std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl;
    std::cout << "最高分:\t" << max() << std::endl;
    std::cout << "最低分:\t" << min() << std::endl;

    const std::array<std::string, 5> grade_range{"[0, 60) ", 
                                           "[60, 70)", 
                                           "[70, 80)",
                                           "[80, 90)", 
                                           "[90, 100]"};
    
    for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i)
        std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"
                  << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n";
}

void GradeCalc::compute() {
    if(grades.empty())
        return;

    counts.fill(0); 
    rates.fill(0.0);

    // 统计各分数段人数
    for(auto grade:grades) {
        if(grade < 60)
            ++counts[0];        // [0, 60)
        else if (grade < 70)
            ++counts[1];        // [60, 70)
        else if (grade < 80)
            ++counts[2];        // [70, 80)
        else if (grade < 90)
            ++counts[3];        // [80, 90)
        else
            ++counts[4];        // [90, 100]
    }

    // 统计各分数段比例
    for(size_t i = 0; i < rates.size(); ++i)
        rates[i] = counts[i] * 1.0 / grades.size();
    
    is_dirty = false;  // 更新脏标记
}

task1.cpp

点击查看代码
#include <iostream>
#include <string>
#include "GradeCalc.hpp"

void test() {
    GradeCalc c1("OOP");

    std::cout << "录入成绩:\n";
    c1.input(5);

    std::cout << "输出成绩:\n";
    c1.output();

    std::cout << "排序后成绩:\n";
    c1.sort(); c1.output();

    std::cout << "*************成绩统计信息*************\n";
    c1.info();

}

int main() {
    test();
}

运行结果

image

实验结论

问题1:组合关系识别GradeCalc类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。

答:std::string course_name;存储课程名称;std::vector<int> grades;存储学生成绩的容器;std::array<int, 5> counts;存储五个分数段([0,60)、[60,70)、[70,80)、[80,90)、[90,100])的学生人数;std::array<double, 5> rates;存储五个分数段的学生人数所占比例。

问题2:接口暴露理解,如在test模块中这样使用,是否合法?如不合法,解释原因

GradeCalc c("OOP");
c.inupt(5);
c.push_back(97);  // 合法吗?

答:不合法,类GradeCalc并没有提供push_back的接口;如果直接使用push_back,也会因为缺少输入值检查导致脏数据,并且is_dirty赃位也被污染了。

问题3:架构设计分析
当前设计方案中,compute在info模块中调用:
(1)连续打印3次统计信息,compute会被调用几次?标记is_dirty起到什么作用?

答:1次。is_dirty用来记录成绩是否有变化,是否需要重新计算,减少了重复计算的性能损耗

(2)如新增update_grade(index, new_grade),这种设计需要更改compute调用位置吗?简洁说明理由。

答:不用。可以在update_grade(index, new_grade)函数中将is_dirty置1,在输出时会调用compute的,不用重复调用。

问题4:功能扩展设计,要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。

答:在compute函数里加,在计算各分段后加入

if(!grades.empty()) {
        std::vector<int> sorted_grades = grades;
        std::sort(sorted_grades.begin(), sorted_grades.end());
        size_t n = sorted_grades.size();
        double median = 0.0;
        if(n % 2 == 1) {
            median = sorted_grades[n/2];
        } else {
            median = (sorted_grades[n/2 - 1] + sorted_grades[n/2]) / 2.0;
        }
    }

问题5:数据状态管理,GradeCalc和compute中都包含代码:counts.fill(0); rates.fill(0);.compute中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?

答:不能。去掉的话,在更新成绩之后再次计算的时候,会因为counts和rates数组没归零导致结果不正确

问题6:内存管理理解,input模块中代码grades.reserve(n);如果去掉:

(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)

答:没影响

(2)对性能有影响吗?如有影响,用一句话陈述具体影响。

答:有影响;去掉reserve(n)会导致在输入成绩过程中,vector可能发生多次内存重新分配数据拷贝,增加运行时间开销。

实验任务二

源代码

GradeCalc.hpp

点击查看代码
#pragma once

#include <array>
#include <string>
#include <vector>

class GradeCalc: private std::vector<int> {
public:
    GradeCalc(const std::string &cname);      
    void input(int n);                        // 录入n个成绩
    void output() const;                      // 输出成绩
    void sort(bool ascending = false);        // 排序 (默认降序)
    int min() const;                          // 返回最低分
    int max() const;                          // 返回最高分
    double average() const;                   // 返回平均分
    void info();                              // 输出成绩统计信息 

private:
    void compute();               // 计算成绩统计信息

private:
    std::string course_name;     // 课程名
    std::array<int, 5> counts;   // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100]
    std::array<double, 5> rates; // 保存各分数段占比
    bool is_dirty;      // 脏标记,记录是否成绩信息有变更
};

GradeCalc.cpp

点击查看代码
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
#include "GradeCalc.hpp"


GradeCalc::GradeCalc(const std::string &cname): course_name{cname}, is_dirty{true}{
    counts.fill(0);
    rates.fill(0);
}   

void GradeCalc::input(int n) {
    if(n < 0) {
        std::cerr << "无效输入! 人数不能为负数\n";
        return;
    }

    this->reserve(n);

    int grade;

    for(int i = 0; i < n;) {
        std::cin >> grade;
        if(grade < 0 || grade > 100) {
            std::cerr << "无效输入! 分数须在[0,100]\n";
            continue;
        }

        this->push_back(grade);
        ++i;
    } 

    is_dirty = true;
}  

void GradeCalc::output() const {
    for(auto grade: *this)
        std::cout << grade << ' ';
    std::cout << std::endl;
} 

void GradeCalc::sort(bool ascending) {
    if(ascending)
        std::sort(this->begin(), this->end());
    else
        std::sort(this->begin(), this->end(), std::greater<int>());
}  

int GradeCalc::min() const {
    if(this->empty())
        return -1;

    return *std::min_element(this->begin(), this->end());
}  

int GradeCalc::max() const {
    if(this->empty())
        return -1;

    return *std::max_element(this->begin(), this->end());
}    

double GradeCalc::average() const {
    if(this->empty())
        return 0.0;

    double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size();
    return avg;
}   

void GradeCalc::info() {
    if(is_dirty) 
        compute();

    std::cout << "课程名称:\t" << course_name << std::endl;
    std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl;
    std::cout << "最高分:\t" << max() << std::endl;
    std::cout << "最低分:\t" << min() << std::endl;

    const std::array<std::string, 5> grade_range{"[0, 60) ", 
                                           "[60, 70)", 
                                           "[70, 80)",
                                           "[80, 90)", 
                                           "[90, 100]"};
    
    for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i)
        std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"
                  << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n";
}

void GradeCalc::compute() {
    if(this->empty())
        return;
    
    counts.fill(0);
    rates.fill(0);

    // 统计各分数段人数
    for(int grade: *this) {
        if(grade < 60)
            ++counts[0];        // [0, 60)
        else if (grade < 70)
            ++counts[1];        // [60, 70)
        else if (grade < 80)
            ++counts[2];        // [70, 80)
        else if (grade < 90)
            ++counts[3];        // [80, 90)
        else
            ++counts[4];        // [90, 100]
    }

    // 统计各分数段比例
    for(size_t i = 0; i < rates.size(); ++i)
        rates[i] = counts[i] * 1.0 / this->size();
    
    is_dirty = false;
}

task2.cpp

点击查看代码
#include <iostream>
#include <string>
#include "GradeCalc.hpp"

void test() {
    GradeCalc c1("OOP");

    std::cout << "录入成绩:\n";
    c1.input(5);

    std::cout << "输出成绩:\n";
    c1.output();

    std::cout << "排序后成绩:\n";
    c1.sort(); c1.output();

    std::cout << "*************成绩统计信息*************\n";
    c1.info();

}

int main() {
    test();
}

运行截图

image

实验结论

问题1:继承关系识别,写出GradeCalc类声明体现"继承"关系的完整代码行。

答:

class GradeCalc: private std::vector<int>

问题2:接口暴露理解
当前继承方式下,基类vector的接口会自动成为GradeCalc的接口吗?
如在test模块中这样用,能否编译通过?用一句话解释原因。

 GradeCalc c("OOP");
 c.input(5);
 c.push_back(97);  // 合法吗?

答:会;不能通过编译,GradeCalc作为vector派生类,会继承他的接口,但是这里是private继承,无法在类外使用vector的接口。

问题3:数据访问差异,对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。

// 组合方式
for(auto grade: grades)  // 通过什么接口访问数据
// 略
// 继承方式
for(int grade: *this)    
// 略

答:组合方式通过类的数据成员grades直接访问,继承方式通过继承获得vector的所有功能,使用*this作为vector对象,即GradeCalc作为一个vector

问题4:组合 vs. 继承方案选择,你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。

答:组合。在这里GradeCalc与grade的成绩应该是has a的关系,即拥有,而不是is a的关系,逻辑结构上组合关系更加清晰。

实验任务三

源代码

Graph.hpp

点击查看代码
#pragma once

#include <string>
#include <vector>

enum class GraphType {circle, triangle, rectangle};

// Graph类定义
class Graph {
public:
    virtual void draw() {}
    virtual ~Graph() = default;
};

// Circle类声明
class Circle : public Graph {
public:
    void draw();
};

// Triangle类声明
class Triangle : public Graph {
public:
    void draw();
};

// Rectangle类声明
class Rectangle : public Graph {
public:
    void draw();
};

// Canvas类声明
class Canvas {
public:
    void add(const std::string& type);   // 根据字符串添加图形
    void paint() const;                  // 使用统一接口绘制所有图形
    ~Canvas();                           // 手动释放资源

private:
    std::vector<Graph*> graphs;          
};

// 4. 工具函数
GraphType str_to_GraphType(const std::string& s);  // 字符串转枚举类型
Graph* make_graph(const std::string& type);  // 创建图形,返回堆对象指针

Graph.cpp

点击查看代码
#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

#include "Graph.hpp"

// Circle类实现
void Circle::draw()     { std::cout << "draw a circle...\n"; }

// Triangle类实现
void Triangle::draw()   { std::cout << "draw a triangle...\n"; }

// Rectangle类实现
void Rectangle::draw()  { std::cout << "draw a rectangle...\n"; }

// Canvas类实现
void Canvas::add(const std::string& type) {
    Graph* g = make_graph(type);
    if (g) 
        graphs.push_back(g);
}

void Canvas::paint() const {
    for (Graph* g : graphs) 
        g->draw();   
}

Canvas::~Canvas() {
    for (Graph* g : graphs) 
        delete g;
}

// 工具函数实现
// 字符串 → 枚举转换
GraphType str_to_GraphType(const std::string& s) {
    std::string t = s;
    std::transform(s.begin(), s.end(), t.begin(),
                   [](unsigned char c) { return std::tolower(c);});

    if (t == "circle")   
        return GraphType::circle;

    if (t == "triangle") 
        return GraphType::triangle;

    if (t == "rectangle")
        return GraphType::rectangle;

    return GraphType::circle;   // 缺省返回
}

// 创建图形,返回堆对象指针
Graph* make_graph(const std::string& type) {
    switch (str_to_GraphType(type)) {
    case GraphType::circle:     return new Circle;
    case GraphType::triangle:   return new Triangle;
    case GraphType::rectangle:  return new Rectangle;
    default: return nullptr;
    }
}

demo3.cpp

点击查看代码
#include <string>
#include "Graph.hpp"

void test() {
    Canvas canvas;

    canvas.add("circle");
    canvas.add("triangle");
    canvas.add("rectangle");
    canvas.paint();
}

int main() {
    test();
}

运行截图

image

实验结论

问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
答:std::vector<Graph*> graphs;记录和管理画布上所有的图形对象指针。
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
答:class Circle : public Graph class Triangle : public Graphclass Rectangle : public Graph

问题2:多态机制观察
(1)Graph中的draw若未声明成虚函数,Canvas::paint()中g->draw()运行结果会有何不同?
答:如果Graph中的draw未声明成虚函数,Canvas::paint()中g->draw()将始终调用Graph基类的draw方法
(2)若Canvas类std::vector<Graph*>改成std::vector,会出现什么问题?
答:存储的是Graph副本,不是实际派生类对象,调用draw()时始终调用Graph::draw(),无法调用派生类的重写方法。
(3)若~Graph()未声明成虚函数,会带来什么问题?
答:会导致派生类对象的析构函数不被调用,造成资源泄漏;

问题3:扩展性思考,若要新增星形Star,需在哪些文件做哪些改动?逐一列出。
答:

  • 在枚举类型中添加star
  • 新增Star类声明(继承Graph)
  • 添加Star的draw方法实现

问题4:资源管理,观察make_graph函数和Canvas析构函数:
(1)make_graph返回的对象在什么地方被释放?
答:make_graph返回的对象在Canvas的析构函数中被释放。
(2)使用原始指针管理内存有何利弊?
答:可以精确控制对象的创建和销毁时机,但容易造成内存泄漏及悬空指针。

实验任务四

设计性实验:综合运用组合、继承、虚函数实现用一个接口尝试所有玩具特异功能。
具体要求如下:
设计毛绒玩具类Toy

  • 数据成员:玩具名称、玩具类型等(更多数据成员请自行调研、扩充设计)
  • 接口:特异功能等(更多函数成员请自行调研、扩充设计)
  • 设计玩具工厂类ToyFactory,包含一组毛绒玩具。
  • 接口:显示工厂所有玩具信息(名称、类型、特异功能等)
    编写测试模块、运行测试
    代码组织
    类声明保存在xx.hpp, 类实现保存在xx.cpp, 测试模块和主体代码保存在demo4.cpp
    说明
  1. 本任务是设计性实验任务,题目只给出最小化、粗略描述,具体请调研市面上电子毛绒玩具并基于个人创造力
    做细化和拓展设计,包括:
    (1)方案确定(组合/继承)
    (2)类的数据成员设计
    (3)函数成员设计(接口和私有工具等)
  2. 在实验结论中,提供你设计这个应用的问题描述、对象关系、源码、测试截图

问题描述

我们有一个玩具工厂系统,可以创建不同类型的玩具,并且每个玩具有自己的特性和行为。
设计一个面向对象的玩具工厂系统,可以创建不同类型的玩具(如会说话的熊、发光的兔子、跳舞的企鹅、唱歌的鸟)。
每个玩具都有名称、电池电量,并且可以显示信息、玩耍和充电。同时,工厂可以管理所有玩具,包括展示所有玩具、测试所有功能、充电和统计。

对象关系

基类:Toy
继承:TalkingBear (会说话的熊)、GlowingRabbit (发光的兔子)、DancingPenguin (跳舞的企鹅)、SingingBird (唱歌的鸟)
组合:ToyFactory

源代码

toy.hpp

点击查看代码
#include <string>
#include <vector>

enum class ToyType {TalkingBear, GlowingRabbit, DancingPenguin, SingingBird};

class Toy {
public:
    Toy(const std::string& name, const std::string& type);
    virtual ~Toy() = default;
    
    virtual void showInfo() const;
    virtual void play() const = 0;  
    
    std::string getName() const { return name; }
    std::string getType() const { return type; }
    int getBatteryLevel() const { return battery_level; }
    
    // 充电功能
    virtual void charge(int minutes);

protected:
    std::string name;
    std::string type;
    int battery_level;  // 电池电量 0-100
    bool is_charged;    // 是否已充电
};

// 会说话的熊
class TalkingBear : public Toy {
public:
    TalkingBear(const std::string& name);
    void play() const override;
    void setPhrase(const std::string& phrase);
    
private:
    std::string phrase;
};

// 发光兔子
class GlowingRabbit : public Toy {
public:
    GlowingRabbit(const std::string& name);
    void play() const override;
    void setColor(const std::string& color);
    void charge(int minutes) override;  // 特殊充电逻辑
    
private:
    std::string light_color;
    int brightness;  // 亮度级别
};

// 跳舞企鹅
class DancingPenguin : public Toy {
public:
    DancingPenguin(const std::string& name);
    void play() const override;
    void setDanceStyle(const std::string& style);
    
private:
    std::string dance_style;
    int dance_duration;  // 舞蹈时长
};

// 唱歌小鸟
class SingingBird : public Toy {
public:
    SingingBird(const std::string& name);
    void play() const override;
    void setSong(const std::string& song);
    
private:
    std::string song;
    int volume;  // 音量级别
};

class ToyFactory {
public:
    ToyFactory();
    ~ToyFactory();

    void addToy(const std::string& type, const std::string& name);
    
    void showAllToys() const;
    
    void testAllFunctions() const;
    
    void chargeAllToys(int minutes);
    
    Toy* findToy(const std::string& name) const;
    
    void showStatistics() const;

private:
    std::vector<Toy*> toys;
    int total_toys;
};

// 4. 工具函数
ToyType str_to_ToyType(const std::string& s);  // 字符串转枚举类型
Toy* make_toy(const std::string& type, const std::string& name);  // 创建玩具,返回堆对象指针

toy.cpp

点击查看代码
#include "toy.hpp"
#include <iostream>
#include <algorithm>
#include <numeric>
#include <cctype>

using namespace std;

// 基类
Toy::Toy(const string& name_, const string& type_)
	: name(name_), type(type_), battery_level(50), is_charged(false) {}

void Toy::showInfo() const {
	cout << "类型: " << type << ", 名称: " << name
		 << ", 电量: " << battery_level << "%"
		 << ", 充电状态: " << (is_charged ? "是" : "否") << '\n';
}

void Toy::charge(int minutes) {
	if (minutes <= 0) return;
	battery_level = min(100, battery_level + minutes);
	is_charged = (battery_level > 0);
}

// TalkingBear
TalkingBear::TalkingBear(const string& name)
	: Toy(name, "会说话的熊"), phrase("你好!") {}

void TalkingBear::play() const {
	cout << "[会说话的熊] " << name << " 说: '" << phrase << "'\n";
}

void TalkingBear::setPhrase(const string& p) {
	phrase = p;
}


GlowingRabbit::GlowingRabbit(const string& name)
	: Toy(name, "发光兔子"), light_color("白色"), brightness(5) {}

void GlowingRabbit::play() const {
	cout << "[发光兔子] " << name << " 发光: " << light_color
		 << " , 亮度: " << brightness << "\n";
}

void GlowingRabbit::setColor(const string& color) {
	light_color = color;
}

void GlowingRabbit::charge(int minutes) {
	if (minutes <= 0) return;
	battery_level = min(100, battery_level + minutes * 2);
	brightness = min(10, brightness + minutes / 5);
	is_charged = (battery_level > 0);
}

DancingPenguin::DancingPenguin(const string& name)
	: Toy(name, "跳舞企鹅"), dance_style("华尔兹"), dance_duration(10) {}

void DancingPenguin::play() const {
	cout << "[跳舞企鹅] " << name << " 跳舞 '" << dance_style
		 << "' 持续 " << dance_duration << " 秒" << "\n";
}

void DancingPenguin::setDanceStyle(const string& style) {
	dance_style = style;
}

SingingBird::SingingBird(const string& name)
	: Toy(name, "唱歌小鸟"), song("啦啦"), volume(5) {}

void SingingBird::play() const {
	cout << "[唱歌小鸟] " << name << " 唱歌 '" << song
		 << "' 音量 " << volume << "\n";
}

void SingingBird::setSong(const string& s) {
	song = s;
}

ToyFactory::ToyFactory() : total_toys(0) {}

ToyFactory::~ToyFactory() {
	for (auto p : toys) delete p;
	toys.clear();
}

void ToyFactory::addToy(const string& type, const string& name) {
	Toy* t = make_toy(type, name);
	if (t) {
		toys.push_back(t);
		++total_toys;
	}
}

void ToyFactory::showAllToys() const {
	cout << "--- 所有玩具 ---\n";
	for (const auto& t : toys) {
		if (t) t->showInfo();
	}
}

void ToyFactory::testAllFunctions() const {
	cout << "--- 测试所有玩具 ---\n";
	for (const auto& t : toys) {
		if (t) {
			t->showInfo();
			t->play();
		}
	}
}

void ToyFactory::chargeAllToys(int minutes) {
	for (auto& t : toys) {
		if (t) t->charge(minutes);
	}
}

Toy* ToyFactory::findToy(const string& name_) const {
	for (auto t : toys) {
		if (t && t->getName() == name_) return t;
	}
	return nullptr;
}

void ToyFactory::showStatistics() const {
	cout << "--- 统计 ---\n";
	cout << "玩具总数: " << total_toys << "\n";
	if (toys.empty()) return;
	double avg = accumulate(toys.begin(), toys.end(), 0.0,
		[](double acc, Toy* t){ return acc + (t ? t->getBatteryLevel() : 0); }) / toys.size();
	cout << "平均电量: " << avg << "%\n";
}

// Helpers
ToyType str_to_ToyType(const string& s) {
	string low;
	low.reserve(s.size());
	for (char c : s) low.push_back(tolower((unsigned char)c));
	if (low.find("talk") != string::npos || low.find("bear") != string::npos) return ToyType::TalkingBear;
	if (low.find("glow") != string::npos || low.find("rabbit") != string::npos) return ToyType::GlowingRabbit;
	if (low.find("dance") != string::npos || low.find("penguin") != string::npos) return ToyType::DancingPenguin;
	if (low.find("sing") != string::npos || low.find("bird") != string::npos) return ToyType::SingingBird;

	return ToyType::TalkingBear;
}

Toy* make_toy(const string& type, const string& name) {
	switch (str_to_ToyType(type)) {
		case ToyType::TalkingBear: return new TalkingBear(name);
		case ToyType::GlowingRabbit: return new GlowingRabbit(name);
		case ToyType::DancingPenguin: return new DancingPenguin(name);
		case ToyType::SingingBird: return new SingingBird(name);
	}
	return nullptr;
}

demo4.cpp

点击查看代码
#include "toy.hpp"
#include <iostream>

using namespace std;

int main() {
	ToyFactory factory;

	factory.addToy("TalkingBear", "Ted");
	factory.addToy("GlowingRabbit", "Luna");
	factory.addToy("DancingPenguin", "Penny");
	factory.addToy("SingingBird", "Chirpy");

	cout << "初始化 玩具:\n";
	factory.showAllToys();

	cout << "\n玩所有玩具:\n";
	factory.testAllFunctions();

	// 定制部分:根据类型设置特有属性
	if (auto t = factory.findToy("Ted")) {
		if (auto bear = dynamic_cast<TalkingBear*>(t)) bear->setPhrase("我爱 C++!");
	}
	if (auto t = factory.findToy("Luna")) {
		if (auto rab = dynamic_cast<GlowingRabbit*>(t)) rab->setColor("紫色");
	}
	if (auto t = factory.findToy("Penny")) {
		if (auto pen = dynamic_cast<DancingPenguin*>(t)) pen->setDanceStyle("嘻哈");
	}
	if (auto t = factory.findToy("Chirpy")) {
		if (auto bird = dynamic_cast<SingingBird*>(t)) bird->setSong("小星星");
	}

	cout << "\n定制后:\n";
	factory.testAllFunctions();

	cout << "\n为所有玩具充电10分钟...\n";
	factory.chargeAllToys(10);
	factory.showAllToys();

	factory.showStatistics();

	return 0;
}

运行截图

image

实验总结

了解熟悉了virtual实现多态,体会到了组合和继承的适用场景以及该如何设计类对象,灵活使用组合继承关系和多态

posted @ 2025-11-26 10:19  MEIYBAO  阅读(0)  评论(0)    收藏  举报