实验四
实验4 组合与继承
一、实验目的
- 理解组合(has-a):会用 C++ 写组合类,完成成员对象的构造、初始化与复用
- 理解继承(is-a):会用 C++ 写单继承派生类,掌握公有继承、重写与多态
- 对比深化:通过实践对比,领悟组合与继承在设计思想、用法上的差异
- 面向问题:能根据对象关系选型,完成可扩展、易维护的类设计与实现
二、实验准备
系统浏览/复习以下教材章节:类的抽象与设计(第4-6章)
- 组合:解决的问题场景、定义和用法(第4章)
- 继承:解决的问题场景、定义和用法(第7-8章)
三、实验内容
1. 实验任务1
说明
设计性实验任务:用组合实现成绩计算器类。运行、理解代码,回答问题。
- 问题场景描述

实现成绩计算器类 GradeCalc ,采用组合方式内嵌 vector<int> 存放一门课程成绩,提供接口:
-
成绩录入
-
成绩输出
-
成绩排序(默认降序)
-
最高分/最低分/平均分
-
统计信息输出(包含分数段统计:[0, 60), [60, 70), [70, 80), [80, 90), [90, 100])
-
代码组织
- GradeCalc.hpp 类
GradeCalc声明 - GradeCalc.cpp 类
GradeCalc实现 - demo1.cpp 测试代码 + main.cpp
- GradeCalc.hpp 类
代码
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 = 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(int i = 0; i < rates.size(); ++i)
rates[i] = counts[i] * 1.0 / grades.size();
is_dirty = false; // 更新脏标记
}
demo1.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();
}
运行结果

回答问题
问题1:组合关系识别
GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
答:std::vector<int> grades;用于存储学生的课程成绩;
std::array<int, 5> counts;用来保存每个分数段的人数
std::array<double, 5> rates;用来保存每个分数段人数的占比
问题2:接口暴露理解
如在 test 模块中这样使用,是否合法?如不合法,解释原因。
GradeCalc c("OOP");
c.inupt(5);
c.push_back(97); // 合法吗?
答:不合法,push_back不是GradeCalc的成员方法,不能调用,除非能拿到了对象中的数组属性(无const的时候)才能继续使用。
问题3:架构设计分析
当前设计方案中, compute 在 info 模块中调用:
(1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
(2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
答:(1)最多一次。is_dirty起到检测数据是否更改,如果更改则下次展示信息的时候会先调用一次compute重新计算数据信息。
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
答:增加一个函数double middle(),然后在info函数里添加输出中位数。
double GradeCalc::middle(){
if(grades.empty){
return 0;
}
std::vector<int> temp = grades;
std::nth_element(temp.begin(), temp.begin() + temp.size() / 2, temp.end());
if(temp.size() & 1){
return temp[temp.size() / 2];
}
int left_max = std::max_element(temp.begin(), temp.begin() + temp.size() / 2);
return (temp[temp.size() / 2] + *left_max) / 2.0;
}
//info()
//....
//std::cout << "中位数:\t" << std::fixed << std::setprecision(2) << middle() << std::endl;
//....
问题5:数据状态管理
GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。
compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
答:不能。去掉之后多次调用compute方法的时候会在原基础上累加,导致错误。
问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。
答:(1)对功能无影响。(2)对性能有影响。就是个简单的内存池,避免大量数据的时候重新分配内存,提升性能。
2. 实验任务2
说明
设计性实验任务:用继承实现成绩计算器类。运行、理解代码,回答问题。
- 问题场景描述

实现成绩计算器类 GradeCalc ,采用继承方式基于 vector<int> 创建,提供接口:
-
成绩录入
-
成绩输出
-
成绩排序(默认降序)
-
最高分/最低分/平均分
-
统计信息输出(包含分数段统计:[0, 60), [60, 70), [70, 80), [80, 90), [90, 100])
-
代码组织
- GradeCalc.hpp 类
GradeCalc声明 - GradeCalc.cpp 类
GradeCalc实现 - demo2.cpp 测试代码 + main.cpp
- GradeCalc.hpp 类
代码
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 = 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(int i = 0; i < rates.size(); ++i)
rates[i] = counts[i] * 1.0 / this->size();
is_dirty = false;
}
demo2.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();
}
运行结果

回答问题
问题1:继承关系识别
写出 GradeCalc 类声明体现"继承"关系的完整代码行。
答:class GradeCalc:private std::vector<int>
问题2:接口暴露理解
当前继承方式下,基类 vector<int> 的接口会自动成为 GradeCalc 的接口吗?
如在 test 模块中这样用,能否编译通过?用一句话解释原因。
GradeCalc c("OOP");
c.input(5);
c.push_back(97); // 合法吗?
答:不能,因为当前的对象是继承自std::vector的,但是是private继承,这就导致无法在类外调用push_back方法。
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。
// 组合方式
for(auto grade: grades) // 通过什么接口访问数据
// 略
// 继承方式
for(int grade: *this) // 通过什么接口访问数据
// 略
答:组合方式通过直接调用grades数组成员变量访问。继承方式则是通过基类的迭代器接口访问。
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
答:继承方案感觉更好。这里的成员对象之间的关系相对简单,基本上都是直接操作和获取数组,直接采用继承的方式处理类会更加简便,比如可以直接添加数据而不必自己处理动态内存分配等。
3. 实验任务3
说明
设计性实验任务:综合运用组合、继承、虚函数实现用一个接口打印不同图形。运行、理解代码,回答问题。
-
问题场景描述
综合运用组合、继承、虚函数,实现用一个接口打印各种图形。
-
代码组织
- Graph.hpp
Graph类、Cirle类、Rectangle类、Triangle类、Canvas类声明 - Graph.cpp 类实现
- demo3.cpp 测模块 + main
- Graph.hpp
代码
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();
}
运行结果

回答问题
问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
答:(1)Canvas类中的std::vector<Graph*>graphs;体现了组合关系。用来存储图形。
(2)class Circle:public Graph,class Triangle:public Graph,class Rectangle:public Graph体现了继承关系。
问题2:多态机制观察
(1) Graph 中的 draw 若未声明成虚函数, Canvas::paint() 中 g->draw() 运行结果会有何不同?
(2)若 Canvas 类 std::vector<Graph*> 改成 std::vector<Graph> ,会出现什么问题?
(3)若 ~Graph() 未声明成虚函数,会带来什么问题?
答:(1)当draw未声明为虚函数的时候,后续试图通过多态的方式调用这个函数会直接指向调用基类的draw函数。(2)改为Graph之后就是直接存储的对象,但是由于对象是确定的,派生类行为丢失,编译不通过。(3)基类析构函数不声明为虚函数会导致子类析构时不能正确析构基类,可能导致内存泄露。
问题3:扩展性思考
若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。
答:首先在GraphType里添加star枚举,然后声明新类如下:
class Star : public Graph{
public:
void draw();
};
然后实现方法
void Star::draw() {std::cout<<"draw a star...\n";}
接着在str_to_GraphType函数内添加
if (t == "star")
return GraphType::star;
之后在make_graph添加新分支
case GraphType::star: return new Star;
问题4:资源管理
观察 make_graph 函数和 Canvas 析构函数:
(1) make_graph 返回的对象在什么地方被释放?
(2)使用原始指针管理内存有何利弊?
答:(1)在Canvas析构函数中被释放。(2)可能忘记释放内存导致泄露。
4. 实验任务4
说明
设计性实验:综合运用组合、继承、虚函数实现用一个接口尝试所有玩具特异功能。
具体要求如下:
- 设计毛绒玩具类
Toy- 数据成员:玩具名称、玩具类型等(更多数据成员请自行调研、扩充设计)
- 接口:特异功能等(更多函数成员请自行调研、扩充设计)
- 设计玩具工厂类
ToyFactory,包含一组毛绒玩具。- 接口:显示工厂所有玩具信息(名称、类型、特异功能等)
- 编写测试模块、运行测试
- 代码组织
- 类声明保存在xx.hpp, 类实现保存在xx.cpp, 测试模块和主体代码保存在demo4.cpp
说明*:
- 本任务是设计性实验任务,题目只给出最小化、粗略描述,具体请调研市面上电子毛绒玩具并基于个人创造力
做细化和拓展设计,包括:
(1)方案确定(组合/继承)
(2)类的数据成员设计
(3)函数成员设计(接口和私有工具等)
- 在实验结论中,提供你设计这个应用的问题描述、对象关系、源码、测试截图
代码
Toy.h
#ifndef HEAD_TOY
#define HEAD_TOY
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
enum class ToyType{
UNKNOWN,
BEAR,
TEDDY,
KITTY
};
const std::vector<std::string> toy_type_info{
"unknown",
"bear",
"teddy",
"kitty"
};
class Toy{
protected:
std::string name;
ToyType type;
public:
Toy(const std::string& _name, const std::string& _type)
:name(_name){
std::string str = _type;
std::transform(_type.begin(), _type.end(),
str.begin(), [](unsigned char c){
return std::tolower(c);
});
if(str == "bear"){
type = ToyType::BEAR;
}
else if(str == "teddy"){
type = ToyType::TEDDY;
}
else if(str == "kitty"){
type = ToyType::KITTY;
}
else{
type = ToyType::UNKNOWN;
}
}
virtual ~Toy() = default;
public:
const std::string GetName()const{
return name;
}
const std::string GetType() const{
return toy_type_info[static_cast<int>(type)];
}
void SetName(const std::string& new_name){
name = new_name;
}
void SetType(const std::string& new_type){
std::string str = new_type;
std::transform(new_type.begin(), new_type.end(),
str.begin(), [](unsigned char c){
return std::tolower(c);
});
if(str == "bear"){
type = ToyType::BEAR;
}
else if(str == "teddy"){
type = ToyType::TEDDY;
}
else if(str == "kitty"){
type = ToyType::KITTY;
}
else{
type = ToyType::UNKNOWN;
}
}
void Show(){
std::cout<<"玩具名:"<<name<<", "<<
"玩具类型:"<<
toy_type_info[static_cast<int>(type)]<<std::endl;
}
virtual void SpecialFunction() = 0;
};
#endif
BearToy.h
#ifndef HEAD_BEAR
#define HEAD_BEAR
#include "Toy.h"
class BearToy:public Toy{
public:
BearToy(const std::string& _name = "");
void SpecialFunction() override;
};
#endif
BearToy.cpp
#include "BearToy.h"
BearToy::BearToy(const std::string& _name):Toy(_name, "bear")
{ }
void BearToy::SpecialFunction(){
std::cout<<"我是熊玩具\n";
}
TeddyToy.h
#ifndef HEAD_TEDDY
#define HEAD_TEDDY
#include "Toy.h"
class TeddyToy: public Toy{
public:
TeddyToy(const std::string& _name = "");
void SpecialFunction() override;
};
#endif
TeddyToy.cpp
#include "TeddyToy.h"
TeddyToy::TeddyToy(const std::string& _name):Toy(_name, "teddy")
{ }
void TeddyToy::SpecialFunction(){
std::cout<<"我是teddy玩具\n";
}
KittyToy.h
#ifndef HEAD_KITTY
#define HEAD_KITTY
#include "Toy.h"
class KittyToy: public Toy{
public:
KittyToy(const std::string& _name = "");
void SpecialFunction() override;
};
#endif
KittyToy.cpp
#include "KittyToy.h"
KittyToy::KittyToy(const std::string& _name): Toy(_name, "kitty")
{ }
void KittyToy::SpecialFunction(){
std::cout<<"我是kitty玩具\n";
}
UnknownToy.h
#ifndef HEAD_UNKNOWN
#define HEAD_UNKNOWN
#include "Toy.h"
class UnknownToy: public Toy{
public:
UnknownToy(const std::string& _name = "");
void SpecialFunction() override;
};
#endif
UnknownToy.cpp
#include "UnknownToy.h"
UnknownToy::UnknownToy(const std::string& _name): Toy(_name, "unknown")
{ }
void UnknownToy::SpecialFunction(){
std::cout<<"未知玩具\n";
}
ToyFactory.h
#ifndef HEAD_TOY_FACTORY
#define HEAD_TOY_FACTORY
#include <string>
#include <vector>
#include <map>
class Toy;
class ToyFactory{
private:
std::vector<Toy*> toys;
std::map<std::string, unsigned int> info;
public:
~ToyFactory();
public:
void CreateToy(const std::string&,
const std::string& _name = "");
void EraseByName(const std::string& _name);
void EraseByType(const std::string& _type);
void Info();
void Show();
};
#endif
ToyFactory.cpp
#include "ToyFactory.h"
#include "BearToy.h"
#include "KittyToy.h"
#include "TeddyToy.h"
#include "UnknownToy.h"
#include <algorithm>
ToyFactory::~ToyFactory(){
for(auto t:toys){
delete t;
}
}
void ToyFactory::CreateToy(const std::string& new_type,
const std::string& _name){
std::string str = new_type;
std::transform(new_type.begin(), new_type.end(),
str.begin(), [](unsigned char c){
return std::tolower(c);
});
Toy* toy = nullptr;
if(str == "bear"){
toy = new BearToy(_name);
}
else if(str == "teddy"){
toy = new TeddyToy(_name);
}
else if(str == "kitty"){
toy = new KittyToy(_name);
}
else{
str = "unknown";
toy = new UnknownToy(_name);
}
if(toy){
toys.push_back(toy);
}
else{
std::cout<<"error\n";
return;
}
if(info.find(str) != info.end()){
++info[str];
}
else{
info[str] = 1;
}
}
void ToyFactory::EraseByName(const std::string& _name){
auto d_end = std::remove_if(toys.begin(), toys.end(),
[&](Toy* t)->bool{
return t->GetName() == _name;
});
if(d_end == toys.end()){
std::cout<<"未找到要删除的玩具\n";
return;
}
for(auto it = d_end; it != toys.end(); ++it){
auto key = (*it)->GetType();
--info[key];
if(info[key] == 0)
delete *it;
}
toys.erase(d_end, toys.end());
std::cout<<"删除成功\n";
}
void ToyFactory::EraseByType(const std::string& _type){
auto d_end = std::remove_if(toys.begin(), toys.end(),
[&](Toy* t)->bool{
return t->GetType() == _type;
});
if(d_end == toys.end()){
std::cout<<"未找到要删除的玩具\n";
return;
}
for(auto it = d_end; it != toys.end(); ++it){
delete *it;
}
info.erase(_type);
toys.erase(d_end, toys.end());
std::cout<<"删除失败\n";
}
void ToyFactory::Show(){
if(toys.size() == 0){
std::cout<<"工厂中暂无玩具\n";
return;
}
for(auto &toy:toys){
toy->Show();
std::cout<<"其功能是:\n";
toy->SpecialFunction();
}
}
void ToyFactory::Info(){
if(toys.size() == 0){
std::cout<<"工厂中暂无玩具\n";
return;
}
for(auto it = info.begin(); it != info.end(); ++it){
std::cout<<"类型:"<<it->first<<",数量:"<<it->second<<std::endl;
}
}
demo4.cpp
#include "ToyFactory.h"
#include <iostream>
int main(){
ToyFactory tf;
int n;
bool running = true;
while(running){
std::cout<<"==============选择命令==============\n"<<
"1.创建新玩具\n"<<
"2.通过名称删除\n"<<
"3.通过类型删除\n"<<
"4.显示所有玩具\n"<<
"5.显示当前玩具信息\n"<<
"others.退出\n"<<
"====================================\n";
std::cin>>n;
switch(n){
case 1:{
std::string t_name, t_type;
std::cout<<"请输入玩具信息:(玩具名,玩具类型)\n";
std::cin>>t_name>>t_type;
tf.CreateToy(t_type, t_name);
break;
}
case 2:{
std::string t_name;
std::cout<<"请输入玩具信息:(玩具名)\n";
std::cin>>t_name;
tf.EraseByName(t_name);
break;
}
case 3:{
std::string t_type;
std::cout<<"请输入玩具信息:(类型名)\n";
std::cin>>t_type;
tf.EraseByType(t_type);
break;
}
case 4:{
tf.Show();
break;
}
case 5:{
tf.Info();
break;
}
default:{
running = false;
break;
}
}
}
return 0;
}
运行结果



描述
Toy作为基类,成员属性有名称和种类(枚举类型)。其构造函数给名称和种类赋值,其中种类会经过小写转换后再映射到枚举类型赋值。这两个成员属性各包含Set和Get方法。然后包含一个纯虚函数SpecialFunction()用以表示特殊方法。Toy有四个派生类:BearToy, KittyToy, TeddyToy, UnknownToy(为bear, teddy, kitty以外其他所有类型)。这些派生类都会重写SpecialFunction(),主要用来输出一些特殊信息。接着是ToyFactory这个工厂类,其与Toy为组合关系,有两个成员变量,toys数组和当前信息info,info保存目前存在的玩具类型和数量。工厂类包含以下方法:
CreateToy,直接接收类型和名称创建玩具并保存到toys里。
EraseByName,通过名称删除玩具(所有同名都会删除)。
EraseByType,通过类型删除玩具(所有同类型都会删除)。
Info,展示info表。
Show,展示所有玩具的信息。
四、实验结论
任务1(组合版 GradeCalc)
组合方式把 vector 作为普通数据成员嵌入,类外完全看不见容器接口,封装最干净;测试里试图 c.push_back(97) 会因“无此成员”直接编译失败。compute 只在第一次 info 时真正运行,is_dirty 保证后续连续打印不再重复统计;若新增 update_grade 也只需置脏即可,无需改动 compute 调用点。中位数可在 meddle() 里临时拷贝数组再排序后返回,直接插入 info() 打印。去掉 counts/rates.fill 会导致多次调用 info 时旧数据累加出错;去掉 reserve 功能没区别,但可能会多次分配内存带来的性能问题。
任务2(继承版 GradeCalc)
私有继承让 GradeCalc 本身就是一个 vector,类内能用 push_back/sort 等全部接口,类外仍被屏蔽,测试代码 c.push_back(97) 依旧编译失败,遍历数据时组合写 for(auto g:grades),继承写 for(int g:*this),前者通过成员名访问,后者通过基类迭代器访问,差异仅在于语法,不影响功能。
任务3
Canvas 里 vector<Graph*> 保存基类指针,通过虚函数 draw 实现运行期多态;若 draw 非虚,则 paint() 只能调到 Graph::draw,所有图形输出相同字符串。若把容器改成 vector<Graph>派生类行为丢失,且无法编译通过。析构函数若非虚,delete Graph* 时派生类部分得不到调用,造成资源泄漏。新增星形只需在 GraphType 枚举加 star,在 str_to_GraphType 补 "star" 分支,在 make_graph 里 case GraphType::star: return new Star; 并实现 Star::draw() 。
任务4
采用组合 + 多态模式:Toy 为抽象基类,定义 SpecialFunction 等纯虚接口;具体玩具派生实现各自特异功能;ToyFactory 组合 vector<Toy*> 管理库存,对外提供 CreateToy/两种Erase/Show/Info 接口。工厂类不暴露内部容器,新增玩具类型只需再写一个派生类并在工厂注册即可,同时析构时会正确释放所有指针的对象,保证了内存安全。

浙公网安备 33010602011771号