实验四

实验4 组合与继承

一、实验目的

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

二、实验准备

系统浏览/复习以下教材章节:类的抽象与设计(第4-6章)

  • 组合:解决的问题场景、定义和用法(第4章)
  • 继承:解决的问题场景、定义和用法(第7-8章)

三、实验内容

1. 实验任务1

说明

设计性实验任务:用组合实现成绩计算器类。运行、理解代码,回答问题。

  • 问题场景描述

屏幕截图 2025-11-26 151123

实现成绩计算器类 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
#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();
}

运行结果

image

回答问题

问题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:架构设计分析

当前设计方案中, computeinfo 模块中调用:

(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:数据状态管理

GradeCalccompute 中都包含代码: counts.fill(0); rates.fill(0);

compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?

答:不能。去掉之后多次调用compute方法的时候会在原基础上累加,导致错误。

问题6:内存管理理解

input 模块中代码 grades.reserve(n); 如果去掉:

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

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

答:(1)对功能无影响。(2)对性能有影响。就是个简单的内存池,避免大量数据的时候重新分配内存,提升性能。

2. 实验任务2

说明

设计性实验任务:用继承实现成绩计算器类。运行、理解代码,回答问题。

  • 问题场景描述

image

实现成绩计算器类 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
#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();
}

运行结果

image

回答问题

问题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
#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中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。

(2)写出Graph.hpp中体现"继承"关系的类声明代码行。

答:(1)Canvas类中的std::vector<Graph*>graphs;体现了组合关系。用来存储图形。

(2)class Circle:public Graphclass Triangle:public Graphclass Rectangle:public Graph体现了继承关系。

问题2:多态机制观察

1) Graph 中的 draw 若未声明成虚函数, Canvas::paint()g->draw() 运行结果会有何不同?

(2)若 Canvasstd::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. 本任务是设计性实验任务,题目只给出最小化、粗略描述,具体请调研市面上电子毛绒玩具并基于个人创造力

做细化和拓展设计,包括:

(1)方案确定(组合/继承)

(2)类的数据成员设计

(3)函数成员设计(接口和私有工具等)

  1. 在实验结论中,提供你设计这个应用的问题描述、对象关系、源码、测试截图

代码

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

运行结果

image
image
image

描述

Toy作为基类,成员属性有名称和种类(枚举类型)。其构造函数给名称和种类赋值,其中种类会经过小写转换后再映射到枚举类型赋值。这两个成员属性各包含Set和Get方法。然后包含一个纯虚函数SpecialFunction()用以表示特殊方法。Toy有四个派生类:BearToy, KittyToy, TeddyToy, UnknownToy(为bear, teddy, kitty以外其他所有类型)。这些派生类都会重写SpecialFunction(),主要用来输出一些特殊信息。接着是ToyFactory这个工厂类,其与Toy为组合关系,有两个成员变量,toys数组和当前信息infoinfo保存目前存在的玩具类型和数量。工厂类包含以下方法:

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
Canvasvector<Graph*> 保存基类指针,通过虚函数 draw 实现运行期多态;若 draw 非虚,则 paint() 只能调到 Graph::draw,所有图形输出相同字符串。若把容器改成 vector<Graph>派生类行为丢失,且无法编译通过。析构函数若非虚,delete Graph* 时派生类部分得不到调用,造成资源泄漏。新增星形只需在 GraphType 枚举加 star,在 str_to_GraphType"star" 分支,在 make_graphcase GraphType::star: return new Star; 并实现 Star::draw()

任务4
采用组合 + 多态模式:Toy 为抽象基类,定义 SpecialFunction 等纯虚接口;具体玩具派生实现各自特异功能;ToyFactory 组合 vector<Toy*> 管理库存,对外提供 CreateToy/两种Erase/Show/Info 接口。工厂类不暴露内部容器,新增玩具类型只需再写一个派生类并在工厂注册即可,同时析构时会正确释放所有指针的对象,保证了内存安全。

posted @ 2025-11-26 17:39  cuupe  阅读(13)  评论(0)    收藏  举报