实验4 组合与继承
实验任务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;
};
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];
else if (grade < 70)
++counts[1];
else if (grade < 80)
++counts[2];
else if (grade < 90)
++counts[3];
else
++counts[4];
}
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::string course_name; :存储课程名称。
std::vector
std::array<int, 5> counts; :存储各分数段的人数统计结果。
std::array<double, 5> rates; :存储各分数段的人数占比。
问题2:
c.push_back(97); 不合法。
原因: grades 是 GradeCalc 的私有成员,外部无法直接调用其 push_back 方法;类只对外提供了 input 接口来录入成绩,需通过 input 完成成绩添加。
问题3:
(1) 连续打印3次统计信息时, compute 只会被调用1次。
标记 is_dirty 的作用:标记成绩数据是否有变更,只有当 is_dirty 为 true (成绩变更)时, info 才会调用 compute 重新统计,避免重复计算。
(2) 需要修改 compute 调用位置。
理由:新增 update_grade 会修改成绩数据,需在 update_grade 中设置 is_dirty = true ,确保后续 info 调用时触发 compute 重新统计。
问题4:
不需要新增数据成员,在 GradeCalc 类中添加成员函数:
double median() ;
double GradeCalc::median() {
if (grades.empty()) return 0.0;
std::vector<int> temp = grades;
std::sort(temp.begin(), temp.end());
int n = temp.size();
if (n % 2 == 1) {
return temp[n / 2];
} else {
return (temp[n/2 - 1] + temp[n/2]) / 2.0;
}
}
问题5:
不能去掉 counts.fill(0); rates.fill(0.0); 。
当多次调用 compute 时, counts 和 rates 会保留之前的统计结果,导致重复累加。
问题6:
(1) 对功能无影响

(2) 对性能有影响:去掉 reserve(n) 后,当 input 中多次 push_back 时, 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();
void compute();
private:
std::string course_name;
std::array<int, 5> counts;
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];
else if (grade < 70)
++counts[1];
else if (grade < 80)
++counts[2];
else if (grade < 90)
++counts[3];
else
++counts[4];
}
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:
class GradeCalc: private std::vector
问题2:
基类 vector
c.push_back(97); 无法编译通过,原因: GradeCalc 采用 private 继承,基类的成员在外部不可见,只能通过 GradeCalc 自身提供的 input 接口录入成绩。
问题3
组合方式 for(auto grade: grades) 通过类的私有成员变量 grades访问数据。
继承方式 for(int grade: *this) 通过自身直接访问数据。
问题4:
组合方式更适合成绩统计场景。
理由:组合方式能更好地封装数据,更符合“成绩统计类包含成绩数据”的逻辑关系。
实验任务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);
Graph.cpp
#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;
}
}
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) 组合关系的成员声明行: std::vector<Graph*> graphs;
功能: Canvas 通过该容器组合多个图形对象,实现图形的统一管理。
(2) 继承关系的类声明行:
class Circle : public Graph {
class Triangle : public Graph {
class Rectangle : public Graph {
问题2:
(1) 运行结果差异:
若 Graph::draw 未声明为虚函数: g->draw() 会调用 Graph 的空实现,无绘制输出;
(2) 若 vector<Graph*> 改为 vector
子类对象存入容器时成为为 Graph 类型,无法实现多态。
(3) 若 ~Graph 未声明为虚函数:
Canvas 析构时 delete g 仅调用 Graph 的析构函数,不会调用子类析构,导致内存泄漏。
问题3:
在 Graph.hpp 中添加 Star 类声明( class Star : public Graph { public: void draw(); }; );
在 Graph.cpp 中实现 Star::draw() ;
在 str_to_GraphType 函数中添加 "star" 的类型判断;
在 make_graph 的 switch 中添加 case graphType:⭐ return new Star;
问题4:
(1) make_graph 返回的对象在Canvas 的析构函数中释放。
(2) 原始指针管理的问题:易出现内存泄漏、重复释放。
实验任务4
代码:
Toy.hpp
#pragma once
#include <string>
#include <vector>
class Toy {
public:
Toy(std::string n, std::string t, int p) : name(n), type(t), price(p) {}
virtual void specialFunc() const = 0;
virtual void showInfo() const =0;
protected:
std::string name;
std::string type;
int price;
};
class DialogToy : public Toy {
public:
DialogToy(std::string n, int p) : Toy(n, "对话玩具", p) {}
void specialFunc() const ;
void showInfo() const ;
};
class MusicToy : public Toy {
public:
MusicToy(std::string n, int p) : Toy(n, "音乐玩具", p) {}
void specialFunc() const ;
void showInfo() const ;
};
class LightToy : public Toy {
public:
LightToy(std::string n, int p) : Toy(n, "发光玩具", p) {}
void specialFunc() const;
void showInfo() const ;
};
class ToyFactory {
private:
std::vector<Toy*> toys;
public:
void addToy(Toy* t);
void showAllToys() const;
~ToyFactory();
};
Toy.cpp
#include "Toy.hpp"
#include <iostream>
void DialogToy::specialFunc() const {
std::cout << "[特异功能] " << name << ":你好呀!" << std::endl;
}
void DialogToy::showInfo() const { std::cout << "名称:" << name << " | 类型:" << type << " | 价格:" << price << "元" << std::endl; }
void MusicToy::specialFunc() const {
std::cout << "[特异功能] " << name << ":播放儿歌~" << std::endl;
}
void MusicToy::showInfo() const { std::cout << "名称:" << name << " | 类型:" << type << " | 价格:" << price << "元" << std::endl; }
void LightToy::specialFunc() const {
std::cout << "[特异功能] " << name << ":灯光闪烁中~" << std::endl;
}
void LightToy::showInfo() const { std::cout << "名称:" << name << " | 类型:" << type << " | 价格:" << price << "元" << std::endl; }
void ToyFactory::addToy(Toy* t) { toys.push_back(t); }
void ToyFactory::showAllToys() const {
std::cout << "===== 玩具工厂清单 =====" << std::endl;
for (auto t : toys) {
t->showInfo();
t->specialFunc();
std::cout << "----------------" << std::endl;
}
}
ToyFactory::~ToyFactory() {
for (auto t : toys) delete t;
}
demo4.cpp
#include "Toy.hpp"
void test() {
ToyFactory factory;
factory.addToy(new DialogToy("小熊", 99));
factory.addToy(new MusicToy("小兔", 89));
factory.addToy(new LightToy("小恐龙", 109));
factory.showAllToys();
}
int main() {
test();
return 0;
}
运行测试截图:

浙公网安备 33010602011771号