NUIST-OOP-LAB04
🧪 实验报告
一、实验目的
理解组合(has-a):会用 C++ 写组合类,完成成员对象的构造、初始化与复用
理解继承(is-a):会用 C++ 写单继承派生类,掌握公有继承、重写与多态
对比深化:通过实践对比,领悟组合与继承在设计思想、用法上的差异
面向问题:能根据对象关系选型,完成可扩展、易维护的类设计与实现
二、实验准备
系统浏览/复习以下教材章节:类的抽象与设计(第4-6章)
组合:解决的问题场景、定义和用法(第4章)
继承:解决的问题场景、定义和用法(第7-8章)
三、实验内容
- 实验任务1
设计性实验任务:用组合实现成绩计算器类。运行、理解代码,回答问题。
问题场景描述
实现成绩计算器类 GradeCalc ,采用组合方式内嵌 vector存放一门课程成绩,提供接口:
成绩录入
成绩输出
成绩排序(默认降序)
最高分/最低分/平均分
统计信息输出(包含分数段统计:[0, 60), [60, 70), [70, 80), [80, 90), [90, 100])
代码组织
GradeCalc.hpp
类 GradeCalc 声明
GradeCalc.cpp类 GradeCalc 实现
demo1.cpp测试代码 + main.cpp - 实验任务2
设计性实验任务:用继承实现成绩计算器类。运行、理解代码,回答问题。
问题场景描述
实现成绩计算器类 GradeCalc ,采用继承方式基于 vector创建,提供接口:
成绩录入成绩输出
成绩排序(默认降序)
最高分/最低分/平均分
统计信息输出(包含分数段统计:[0, 60), [60, 70), [70, 80), [80, 90), [90, 100])
代码组织
GradeCalc.hpp
类 GradeCalc 声明
GradeCalc.cpp类 GradeCalc 实现
demo2.cpp测试代码 + main.cpp - 实验任务3
设计性实验任务:综合运用组合、继承、虚函数实现用一个接口打印不同图形。运行、理解代码,回答问题。
问题场景描述
综合运用组合、继承、虚函数,实现用一个接口打印各种图形。
代码组织
Graph.hppGraph 类、 Cirle 类、 Rectangle 类、 Triangle 类、 Canvas 类声明
Graph.cpp类实现
demo3.cpp测模块 + main - 实验任务4
设计性实验:综合运用组合、继承、虚函数实现用一个接口尝试所有玩具特异功能。
具体要求如下:
设计毛绒玩具类 Toy
数据成员:玩具名称、玩具类型等(更多数据成员请自行调研、扩充设计)
接口:特异功能等(更多函数成员请自行调研、扩充设计)
设计玩具工厂类 ToyFactory ,包含一组毛绒玩具。
接口:显示工厂所有玩具信息(名称、类型、特异功能等)
编写测试模块、运行测试
代码组织
类声明保存在xx.hpp, 类实现保存在xx.cpp, 测试模块和主体代码保存在demo4.cpp
说明*: - 本任务是设计性实验任务,题目只给出最小化、粗略描述,具体请调研市面上电子毛绒玩具并基于个人创造力
做细化和拓展设计,包括:
(1)方案确定(组合/继承)(2)类的数据成员设计
(3)函数成员设计(接口和私有工具等) - 在实验结论中,提供你设计这个应用的问题描述、对象关系、源码、测试截图
四、实验结论
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; // 更新脏标记
}

Q1:
std::string course_name;
std::vector
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;
}

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;
}
}

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();
}

应用场景:
模拟毛绒玩具生产工厂,维护一批玩具的基础信息(名称、类型、材质、颜色、尺寸、价格),
以及消费者关心的特性(是否可水洗、是否带发声装置、特异功能)。工厂需要展示所有产品信息,
并向用户演示每个玩具的特色功能,便于选购或验收。
类关系与设计理由:
- Toy:表示单个毛绒玩具,封装属性与行为。独立类便于扩展字段或功能。
- ToyFactory:组合(
std::vector<Toy>)一组 Toy,用于集中管理和展示。采用组合而非继承,
因为工厂职责是聚合与调度玩具实例,而非“是一种”玩具。 - 暂无继承层次:当前关注点在产品管理,Toy 已足够表达单个实体;如需区分更多玩具大类,可后续以继承或策略扩展。

浙公网安备 33010602011771号