NUIST-OOP-LAB04

🧪 实验报告

一、实验目的

理解组合(has-a):会用 C++ 写组合类,完成成员对象的构造、初始化与复用
理解继承(is-a):会用 C++ 写单继承派生类,掌握公有继承、重写与多态
对比深化:通过实践对比,领悟组合与继承在设计思想、用法上的差异
面向问题:能根据对象关系选型,完成可扩展、易维护的类设计与实现

二、实验准备

系统浏览/复习以下教材章节:类的抽象与设计(第4-6章)
组合:解决的问题场景、定义和用法(第4章)
继承:解决的问题场景、定义和用法(第7-8章)

三、实验内容

  1. 实验任务1
    设计性实验任务:用组合实现成绩计算器类。运行、理解代码,回答问题。
    问题场景描述
    实现成绩计算器类 GradeCalc ,采用组合方式内嵌 vector 存放一门课程成绩,提供接口:
    成绩录入
    成绩输出
    成绩排序(默认降序)
    最高分/最低分/平均分
    统计信息输出(包含分数段统计:[0, 60), [60, 70), [70, 80), [80, 90), [90, 100])
    代码组织
    GradeCalc.hpp
    类 GradeCalc 声明
    GradeCalc.cpp类 GradeCalc 实现
    demo1.cpp测试代码 + main.cpp
  2. 实验任务2
    设计性实验任务:用继承实现成绩计算器类。运行、理解代码,回答问题。
    问题场景描述
    实现成绩计算器类 GradeCalc ,采用继承方式基于 vector 创建,提供接口:
    成绩录入成绩输出
    成绩排序(默认降序)
    最高分/最低分/平均分
    统计信息输出(包含分数段统计:[0, 60), [60, 70), [70, 80), [80, 90), [90, 100])
    代码组织
    GradeCalc.hpp
    类 GradeCalc 声明
    GradeCalc.cpp类 GradeCalc 实现
    demo2.cpp测试代码 + main.cpp
  3. 实验任务3
    设计性实验任务:综合运用组合、继承、虚函数实现用一个接口打印不同图形。运行、理解代码,回答问题。
    问题场景描述
    综合运用组合、继承、虚函数,实现用一个接口打印各种图形。
    代码组织
    Graph.hppGraph 类、 Cirle 类、 Rectangle 类、 Triangle 类、 Canvas 类声明
    Graph.cpp类实现
    demo3.cpp测模块 + main
  4. 实验任务4
    设计性实验:综合运用组合、继承、虚函数实现用一个接口尝试所有玩具特异功能。
    具体要求如下:
    设计毛绒玩具类 Toy
    数据成员:玩具名称、玩具类型等(更多数据成员请自行调研、扩充设计)
    接口:特异功能等(更多函数成员请自行调研、扩充设计)
    设计玩具工厂类 ToyFactory ,包含一组毛绒玩具。
    接口:显示工厂所有玩具信息(名称、类型、特异功能等)
    编写测试模块、运行测试
    代码组织
    类声明保存在xx.hpp, 类实现保存在xx.cpp, 测试模块和主体代码保存在demo4.cpp
    说明*:
  5. 本任务是设计性实验任务,题目只给出最小化、粗略描述,具体请调研市面上电子毛绒玩具并基于个人创造力
    做细化和拓展设计,包括:
    (1)方案确定(组合/继承)(2)类的数据成员设计
    (3)函数成员设计(接口和私有工具等)
  6. 在实验结论中,提供你设计这个应用的问题描述、对象关系、源码、测试截图

四、实验结论

1、实验任务1

//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();
}
//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; // 更新脏标记
}

image
Q1:
std::string course_name;
std::vector grades;
std::array<int, 5> counts;
std::array<double, 5> rates;
Q2:
不合法,GradeCalc类没有这个接口
Q3:
1次,判断是否命中缓存数据,避免重复计算
Q4:
不需要,只要把is_dirty置位就行了,会自动判断是否要更新缓存的
Q5:
不能,成绩未录入情况下会发生误判
Q6:
功能没有影响
可能存在影响,vector根据已有元素数目进行动态扩容,直接扩容相较于多次扩容带来更少的时间开销

2.实验任务2

#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();
}
#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;               // 脏标记,记录是否成绩信息有变更
};
#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;
}

image

Q1:
class GradeCalc : private std::vector
Q2:
未被显示覆盖就会,显示覆盖也可以调用
可以编译通过,该类已经继承了vector的push_back接口
Q3:
通过副本迭代器,通过this指针调用自身迭代器
Q4:
组合方案,扩展更加灵活,可维护性好

3.实验任务3

#include <string>
#include "Graph.hpp"

void test() {
    Canvas canvas;

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

int main() {
    test();
}
#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);       // 创建图形,返回堆对象指针
#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;
    }
}

image

Q1:
组合:
std::vector<Graph *> graphs;
继承:
class Circle : public Graph
class Triangle : public Graph
class Rectangle : public Graph
Q2:
会调用父类的draw,然后就没有任何输出了
以Graph类作为数组元素将无法利用多态
无法利用多态正确释放子类申请的资源
Q3:
需要在Graph.hpp中添加Star类与枚举类型
在Graph.cpp中为其添加对应的实现
为make_graph增加实现
Q4:
在Canvas的析构函数中被手动释放
可维护性很低,出现中断就会直接memleak

4、实验任务4

#include <iostream>
#include <string>

#include "Toy.hpp"

int main()
{
    ToyFactory factory;

    factory.add(Toy("Buddy", "熊", "有机棉", "棕色", 35.0, 199.0, true, true, "拥抱时会轻微发热"));
    factory.add(Toy("Luna", "兔子", "天鹅绒", "灰色", 28.5, 169.0, false, true, "耳朵会跟随音乐摆动"));
    factory.add(Toy("Rex", "恐龙", "摇粒绒", "绿色", 40.0, 249.0, true, false, "夜晚会发出柔和荧光"));
    factory.add(Toy("Milo", "猫咪", "棉麻混纺", "奶油色", 30.0, 189.0, true, true, "可录制并播放一句问候"));

    factory.list_products();
    std::cout << std::endl;
    factory.demo_features();
}
#pragma once

#include <string>
#include <vector>

class Toy
{
public:
    Toy(const std::string &name,
        const std::string &type,
        const std::string &material,
        const std::string &color,
        double height_cm,
        double price,
        bool washable,
        bool has_sound_box,
        const std::string &special_feature);

    std::string summary() const;
    void show_info() const;
    void activate_feature() const;

private:
    std::string name;
    std::string type;
    std::string material;
    std::string color;
    double height_cm;
    double price;
    bool washable;
    bool has_sound_box;
    std::string special_feature;
};

class ToyFactory
{
public:
    void add(const Toy &toy);
    void add(Toy &&toy);
    void list_products() const;
    void demo_features() const;

private:
    std::vector<Toy> toys;
};
#include <iomanip>
#include <iostream>
#include <sstream>
#include <utility>

#include "Toy.hpp"

Toy::Toy(const std::string &name,
         const std::string &type,
         const std::string &material,
         const std::string &color,
         double height_cm,
         double price,
         bool washable,
         bool has_sound_box,
         const std::string &special_feature)
    : name(name),
      type(type),
      material(material),
      color(color),
      height_cm(height_cm),
      price(price),
      washable(washable),
      has_sound_box(has_sound_box),
      special_feature(special_feature)
{
}

std::string Toy::summary() const
{
    std::ostringstream oss;
    oss << "名称: " << name << " | 类型: " << type
        << " | 材质: " << material << " | 颜色: " << color
        << " | 高度: " << height_cm << "cm"
        << " | 价格: ¥" << std::fixed << std::setprecision(2) << price
        << " | 可水洗: " << (washable ? "是" : "否")
        << " | 发声: " << (has_sound_box ? "是" : "否");
    if (!special_feature.empty())
        oss << " | 特异功能: " << special_feature;
    return oss.str();
}

void Toy::show_info() const
{
    std::cout << summary() << std::endl;
}

void Toy::activate_feature() const
{
    std::cout << name << " 激活功能: ";
    if (!special_feature.empty())
        std::cout << special_feature;
    else
        std::cout << "暂无特异功能";

    if (has_sound_box)
        std::cout << "(伴随柔和发声)";

    std::cout << std::endl;
}

void ToyFactory::add(const Toy &toy)
{
    toys.push_back(toy);
}

void ToyFactory::add(Toy &&toy)
{
    toys.push_back(std::move(toy));
}

void ToyFactory::list_products() const
{
    std::cout << "工厂现有毛绒玩具:" << std::endl;
    for (const Toy &toy : toys)
        toy.show_info();
}

void ToyFactory::demo_features() const
{
    std::cout << "展示玩具特异功能:" << std::endl;
    for (const Toy &toy : toys)
        toy.activate_feature();
}

image
应用场景:
模拟毛绒玩具生产工厂,维护一批玩具的基础信息(名称、类型、材质、颜色、尺寸、价格),
以及消费者关心的特性(是否可水洗、是否带发声装置、特异功能)。工厂需要展示所有产品信息,
并向用户演示每个玩具的特色功能,便于选购或验收。

类关系与设计理由:

  • Toy:表示单个毛绒玩具,封装属性与行为。独立类便于扩展字段或功能。
  • ToyFactory:组合(std::vector<Toy>)一组 Toy,用于集中管理和展示。采用组合而非继承,
    因为工厂职责是聚合与调度玩具实例,而非“是一种”玩具。
  • 暂无继承层次:当前关注点在产品管理,Toy 已足够表达单个实体;如需区分更多玩具大类,可后续以继承或策略扩展。
posted @ 2025-11-26 14:05  witlethe  阅读(0)  评论(0)    收藏  举报