实验四
实验任务1
源代码如下:
GradeCalc.hpp
点击查看代码
#pragma once
#include <vector>
#include <array>
#include <string>
class GradeCalc {
public:
GradeCalc(const std::string &cname);
void input(int 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::vector<int> grades;
std::array<int, 5> counts;
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";
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;
}
点击查看代码
#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();
}
运行截图:

问题回答:
问题1:组合关系识别
GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
std::vector
std::array<int, 5> counts;:保存5个分数段的人数统计结果。
std::array<double, 5> rates;:保存5个分数段的人数占比。
问题2:接口暴露理解
如在 test 模块中这样使用,是否合法?如不合法,解释原因。
GradeCalc c("OOP");
c.inupt(5);
c.push_back(97); // 合法吗?
不合法。grades是GradeCalc的私有成员,push_back是vector的成员函数,未通过GradeCalc的公有接口暴露,外部无法直接调用。
问题3:架构设计分析
当前设计方案中, compute 在 info 模块中调用:
(1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
compute会被调用1次。标记is_dirty用于判断成绩数据是否发生变更,仅在数据变更时重新计算统计信息,避免重复计算提升效率。
(2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
不需要更改。新增update_grade时,只需在函数内设置is_dirty = true,info函数会自动检测并调用compute。
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
在average函数旁新增median函数,利用已有grades数据计算。
伪代码:
double GradeCalc::median() const {
如果成绩列表(grades)为空,返回 0.0
std::vector
std::sort(temp.begin(), temp.end());
int size = temp.size();
如果 size 是奇, 返回 temp[size / 2]
否则 中间左元素 = temp[size/2 - 1]
中间右元素 = temp[size/2]
返回 (中间左元素 + 中间右元素) / 2.0
问题5:数据状态管理
GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。
compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
不能去掉。若去掉,当成绩数据变更时,counts和rates会保留上一次的统计结果,导致新统计结果与历史数据叠加,出现计算错误。
问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
对程序功能无影响,成绩仍能正常录入、统计和输出。
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。
有影响。当录入大量成绩时,vector会频繁触发内存重新分配和数据拷贝,降低程序运行效率。
实验任务2
源代码如下:
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);
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;
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;
}
点击查看代码
#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();
}
运行截图:

问题回答:
问题1:继承关系识别
写出 GradeCalc 类声明体现"继承"关系的完整代码行。
class GradeCalc: private std::vector
问题2:接口暴露理解
当前继承方式下,基类 vector
如在 test 模块中这样用,能否编译通过?用一句话解释原因。
GradeCalc c("OOP");
c.input(5);
c.push_back(97); // 合法吗?
不会自动成为 GradeCalc 的接口。不能编译通过,因为采用私有继承,基类 vector 的 public 接口在派生类中变为 private,外部无法直接调用push_back。
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。
// 组合方式
for(auto grade: grades) // 通过什么接口访问数据
// 略
// 继承方式
for(int grade: *this) // 通过什么接口访问数据
// 略
组合方式:通过私有成员变量grades直接访问,接口由GradeCalc明确提供,封装性强。
继承方式:通过*this派生类对象本身访问,可直接使用基类的成员函数,封装性较弱。
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
组合方案更适合。成绩计算器与成绩存储是 “has-a” 关系,组合方式封装性更强,可灵活控制暴露的接口,且后续扩展时不会受基类接口变更的影响。
实验任务3
源代码如下:
Graph.hpp
点击查看代码
#pragma once
#include <string>
#include <vector>
enum class GraphType {circle, triangle, rectangle};
class Graph {
public:
virtual void draw() {}
virtual ~Graph() = default;
};
class Circle : public Graph {
public:
void draw();
};
class Triangle : public Graph {
public:
void draw();
};
class Rectangle : public Graph {
public:
void draw();
};
class Canvas {
public:
void add(const std::string& type);
void paint() const;
~Canvas();
private:
std::vector<Graph*> graphs;
};
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"
void Circle::draw() { std::cout << "draw a circle...\n"; }
void Triangle::draw() { std::cout << "draw a triangle...\n"; }
void Rectangle::draw() { std::cout << "draw a rectangle...\n"; }
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;
}
}
点击查看代码
#include <string>
#include "Graph.hpp"
void test() {
Canvas canvas;
canvas.add("circle");
canvas.add("triangle");
canvas.add("rectangle");
canvas.paint();
}
int main() {
test();
}
运行截图:

问题回答:
问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
std::vector<Graph*> graphs;
存储多个图形对象的指针,支持批量添加和绘制图形。
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
class Circle : public Graph {
class Triangle : public Graph {
class Rectangle : public Graph {
问题2:多态机制观察
(1)Graph 中的 draw 若未声明成虚函数, Canvas::paint() 中 g->draw() 运行结果会有何不同?
所有调用都会执行基类Graph的draw函数,无法实现多态,无法调用派生类的具体绘制功能。
(2)若 Canvas 类 std::vector<Graph*> 改成 std::vector
会发生对象切片,vector 中仅存储基类部分,派生类的独有数据和行为丢失,无法实现多态绘制。
(3)若 ~Graph() 未声明成虚函数,会带来什么问题?
析构时仅调用基类Graph的析构函数,派生类对象的堆内存无法释放,导致内存泄漏。
问题3:扩展性思考
若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。
Graph.hpp:新增Star类声明,继承Graph并声明draw函数;更新GraphType枚举添加star。
Graph.cpp:实现Star::draw函数;修改str_to_GraphType支持 "star" 字符串转换;修改make_graph函数添加case GraphType:: star: return new Star;。
问题4:资源管理
观察 make_graph 函数和 Canvas 析构函数:
(1)make_graph 返回的对象在什么地方被释放?
在Canvas类的析构函数中被释放,通过遍历graphs调用delete释放每个图形对象。
(2)使用原始指针管理内存有何利弊?
利:直接控制内存分配和释放,灵活高效;
弊:容易遗漏释放导致内存泄漏,且手动管理成本高,易出错。
实验任务4
源代码如下:
Toy.hpp
点击查看代码
#pragma once
#include <string>
#include <vector>
// 玩具类型枚举
enum class ToyType { Singing, Glowing, StoryTelling };
// 抽象玩具基类
class Toy {
public:
Toy(const std::string& name, ToyType type);
virtual ~Toy() = default;
std::string getName() const;
ToyType getType() const;
virtual std::string getFeature() const = 0; // 特异功能描述
virtual void activateFeature() const = 0; // 激活特异功能
protected:
std::string name; // 玩具名称
ToyType type; // 玩具类型
};
// 唱歌玩具类
class SingingToy : public Toy {
public:
SingingToy(const std::string& name, const std::string& song);
std::string getFeature() const override;
void activateFeature() const override;
private:
std::string song; // 可演唱的歌曲
};
// 发光玩具类
class GlowingToy : public Toy {
public:
GlowingToy(const std::string& name, const std::string& color);
std::string getFeature() const override;
void activateFeature() const override;
private:
std::string color; // 发光颜色
};
// 讲故事玩具类
class StoryToy : public Toy {
public:
StoryToy(const std::string& name, const std::string& story);
std::string getFeature() const override;
void activateFeature() const override;
private:
std::string story; // 可讲述的故事
};
// 玩具工厂类
class ToyFactory {
public:
~ToyFactory();
void addToy(Toy* toy); // 添加玩具
void showAllToys() const; // 展示所有玩具信息
void activateAllFeatures() const; // 激活所有玩具的特异功能
// 按数字选择玩具并执行操作
void selectToyByNumber();
//单独查看某个玩具详情
void showSingleToy(size_t index) const;
// 删除指定索引的玩具
void deleteToy(size_t index);
private:
std::vector<Toy*> toys; // 组合多个玩具对象
// 辅助函数:校验输入的编号是否合法
bool isValidIndex(int input) const;
};
// 工具函数:ToyType转字符串
std::string toyTypeToString(ToyType type);
// 工具函数:清理输入缓冲区
void clearInputBuffer();
点击查看代码
#include <iostream>
#include <string>
#include <limits>
#include "Toy.hpp"
// Toy基类实现
Toy::Toy(const std::string& name, ToyType type) : name(name), type(type) {}
std::string Toy::getName() const {
return name;
}
ToyType Toy::getType() const {
return type;
}
// 唱歌玩具实现
SingingToy::SingingToy(const std::string& name, const std::string& song)
: Toy(name, ToyType::Singing), song(song) {}
std::string SingingToy::getFeature() const {
return "演唱歌曲:《" + song + "》";
}
void SingingToy::activateFeature() const {
std::cout << "【" << name << "】开始唱歌:" << song << "~\n";
}
// 发光玩具实现
GlowingToy::GlowingToy(const std::string& name, const std::string& color)
: Toy(name, ToyType::Glowing), color(color) {}
std::string GlowingToy::getFeature() const {
return "发出" + color + "色光芒";
}
void GlowingToy::activateFeature() const {
std::cout << "【" << name << "】亮起" << color << "色灯光,一闪一闪~\n";
}
// 讲故事玩具实现
StoryToy::StoryToy(const std::string& name, const std::string& story)
: Toy(name, ToyType::StoryTelling), story(story) {}
std::string StoryToy::getFeature() const {
return "讲述故事:《" + story + "》";
}
void StoryToy::activateFeature() const {
std::cout << "【" << name << "】开始讲故事:" << story << "\n";
}
// 玩具工厂实现:析构函数
ToyFactory::~ToyFactory() {
for (Toy* toy : toys)
delete toy; // 释放玩具对象内存
}
// 玩具工厂:添加玩具
void ToyFactory::addToy(Toy* toy) {
if (toy)
toys.push_back(toy);
}
// 玩具工厂:展示所有玩具信息
void ToyFactory::showAllToys() const {
if (toys.empty()) {
std::cout << "玩具工厂为空,暂无玩具!\n";
return;
}
std::cout << "======= 玩具工厂所有玩具(共" << toys.size() << "个) =======\n";
for (size_t i = 0; i < toys.size(); ++i) {
std::cout << i + 1 << ". 名称:" << toys[i]->getName()
<< " | 类型:" << toyTypeToString(toys[i]->getType())
<< " | 特异功能:" << toys[i]->getFeature() << "\n";
}
std::cout << "====================================\n";
}
// 玩具工厂:激活所有玩具特异功能
void ToyFactory::activateAllFeatures() const {
if (toys.empty()) {
std::cout << "玩具工厂为空,无法激活功能!\n";
return;
}
std::cout << "\n======= 激活所有玩具特异功能 =======\n";
for (Toy* toy : toys) {
toy->activateFeature();
}
std::cout << "====================================\n";
}
// 辅助函数:清理输入缓冲区
void clearInputBuffer() {
std::cin.clear(); // 重置输入状态
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略缓冲区所有字符
}
// 玩具工厂:校验输入的编号是否合法
bool ToyFactory::isValidIndex(int input) const {
if (std::cin.fail()) { // 输入非数字
clearInputBuffer();
std::cout << "输入错误!请输入数字。\n";
return false;
}
if (input < 1 || input > static_cast<int>(toys.size())) { // 数字超出范围
std::cout << "编号无效!请输入1~" << toys.size() << "之间的数字。\n";
return false;
}
return true;
}
// 玩具工厂:展示单个玩具的详细信息
void ToyFactory::showSingleToy(size_t index) const {
if (index >= toys.size()) return;
Toy* toy = toys[index];
std::cout << "\n======= 玩具详情 =======\n";
std::cout << "编号:" << index + 1 << "\n";
std::cout << "名称:" << toy->getName() << "\n";
std::cout << "类型:" << toyTypeToString(toy->getType()) << "\n";
std::cout << "特异功能:" << toy->getFeature() << "\n";
std::cout << "=======================\n";
}
// 玩具工厂:删除指定编号的玩具
void ToyFactory::deleteToy(size_t index) {
if (index >= toys.size()) return;
delete toys[index]; // 释放玩具对象内存
toys.erase(toys.begin() + index); // 从向量中移除指针
std::cout << "已成功删除编号" << index + 1 << "的玩具!\n";
// 删除后更新列表
if (!toys.empty()) {
std::cout << "\n更新后的玩具列表:\n";
showAllToys();
} else {
std::cout << "\n玩具工厂已无玩具!\n";
}
}
// 玩具工厂:按数字选择玩具并执行操作
void ToyFactory::selectToyByNumber() {
if (toys.empty()) {
std::cout << "玩具工厂为空,无法选择玩具!\n";
return;
}
int toyNum;
// 1. 输入玩具编号并校验
do {
std::cout << "\n请输入要操作的玩具编号(1~" << toys.size() << "):";
std::cin >> toyNum;
} while (!isValidIndex(toyNum));
size_t selectedIndex = toyNum - 1; // 转换为向量索引(0开始)
char choice;
// 2. 单个玩具操作菜单
do {
std::cout << "\n======= 玩具操作菜单(当前选择:" << toys[selectedIndex]->getName() << ")=======\n";
std::cout << "1. 查看该玩具详情\n";
std::cout << "2. 激活该玩具特异功能\n";
std::cout << "3. 删除该玩具\n";
std::cout << "4. 退出菜单\n";
std::cout << "请输入操作编号(1~4):";
std::cin >> choice;
clearInputBuffer(); // 清理输入缓冲区
//执行选择的操作
switch (choice) {
case '1':
showSingleToy(selectedIndex);
break;
case '2':
std::cout << "\n======= 激活玩具功能 =======\n";
toys[selectedIndex]->activateFeature();
std::cout << "==========================\n";
break;
case '3':
deleteToy(selectedIndex);
return; // 删除后退出当前选择流程
case '4':
std::cout << "已返回上一级!\n";
break;
default:
std::cout << "操作无效!请输入1~4之间的数字。\n";
}
} while (choice != '4'); // 输入4时退出菜单
}
// 工具函数:ToyType转字符串
std::string toyTypeToString(ToyType type) {
switch (type) {
case ToyType::Singing: return "唱歌玩具";
case ToyType::Glowing: return "发光玩具";
case ToyType::StoryTelling: return "讲故事玩具";
default: return "未知类型";
}
}
点击查看代码
#include <iostream>
#include "Toy.hpp"
// 显示主菜单
void showMainMenu() {
std::cout << "\n======= 电子毛绒玩具工厂系统 =======\n";
std::cout << "1. 查看所有玩具列表\n";
std::cout << "2. 选择单个玩具操作(按编号)\n";
std::cout << "3. 激活所有玩具特异功能\n";
std::cout << "4. 退出系统\n";
std::cout << "====================================\n";
}
int main() {
ToyFactory factory;
// 初始化玩具工厂(添加3个示例玩具)
factory.addToy(new SingingToy("音乐小熊", "小星星"));
factory.addToy(new GlowingToy("荧光小兔", "粉紫"));
factory.addToy(new StoryToy("智慧小鹿", "三只小猪"));
showMainMenu();
char mainChoice;
// 主循环
do {
std::cout << "请输入操作编号(1~4):";
std::cin >> mainChoice;
clearInputBuffer(); // 清理输入缓冲区
// 执行主菜单操作
switch (mainChoice) {
case '1':
factory.showAllToys();
break;
case '2':
factory.selectToyByNumber();
break;
case '3':
factory.activateAllFeatures();
break;
case '4':
std::cout << "已退出系统,感谢使用!\n";
break;
default:
std::cout << "操作无效!请输入1~4之间的数字。\n";
}
} while (mainChoice != '4'); // 输入4时退出系统
return 0;
}
运行截图:

问题描述:
设计电子毛绒玩具系统,包含多种具有特异功能的玩具(如唱歌、发光、讲故事),通过玩具工厂类管理所有玩具,支持统一展示玩具信息和调用特异功能。
对象关系:
继承:Toy为抽象基类,SingingToy、GlowingToy、StoryToy为派生类,体现 “is-a” 关系。
组合:ToyFactory包含vector<Toy*>成员,管理多个玩具对象,体现 “has-a” 关系。
浙公网安备 33010602011771号