实验四
任务一
1 #pragma once 2 3 #include <vector> 4 #include <array> 5 #include <string> 6 7 class GradeCalc { 8 public: 9 GradeCalc(const std::string &cname); 10 void input(int n); // 录入n个成绩 11 void output() const; // 输出成绩 12 void sort(bool ascending = false); // 排序 (默认降序) 13 int min() const; // 返回最低分(如成绩未录入,返回-1) 14 int max() const; // 返回最高分 (如成绩未录入,返回-1) 15 double average() const; // 返回平均分 (如成绩未录入,返回0.0) 16 void info(); // 输出课程成绩信息 17 18 private: 19 void compute(); // 成绩统计 20 21 private: 22 std::string course_name; // 课程名 23 std::vector<int> grades; // 课程成绩 24 std::array<int, 5> counts; // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100] 25 std::array<double, 5> rates; // 保存各分数段人数占比 26 bool is_dirty; // 脏标记,记录是否成绩信息有变更 27 };
1 #include <algorithm> 2 #include <array> 3 #include <cstdlib> 4 #include <iomanip> 5 #include <iostream> 6 #include <numeric> 7 #include <string> 8 #include <vector> 9 10 #include "GradeCalc.hpp" 11 12 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true} { 13 counts.fill(0); 14 rates.fill(0); 15 } 16 17 void GradeCalc::input(int n) { 18 if(n < 0) { 19 std::cerr << "无效输入! 人数不能为负数\n"; 20 std::exit(1); 21 } 22 23 grades.reserve(n); 24 25 int grade; 26 27 for(int i = 0; i < n;) { 28 std::cin >> grade; 29 30 if(grade < 0 || grade > 100) { 31 std::cerr << "无效输入! 分数须在[0,100]\n"; 32 continue; 33 } 34 35 grades.push_back(grade); 36 ++i; 37 } 38 39 is_dirty = true; // 设置脏标记:成绩信息有变更 40 } 41 42 void GradeCalc::output() const { 43 for(auto grade: grades) 44 std::cout << grade << ' '; 45 std::cout << std::endl; 46 } 47 48 void GradeCalc::sort(bool ascending) { 49 if(ascending) 50 std::sort(grades.begin(), grades.end()); 51 else 52 std::sort(grades.begin(), grades.end(), std::greater<int>()); 53 } 54 55 int GradeCalc::min() const { 56 if(grades.empty()) 57 return -1; 58 59 auto it = std::min_element(grades.begin(), grades.end()); 60 return *it; 61 } 62 63 int GradeCalc::max() const { 64 if(grades.empty()) 65 return -1; 66 67 auto it = std::max_element(grades.begin(), grades.end()); 68 return *it; 69 } 70 71 double GradeCalc::average() const { 72 if(grades.empty()) 73 return 0.0; 74 75 double avg = std::accumulate(grades.begin(), grades.end(), 0.0)/grades.size(); 76 return avg; 77 } 78 79 void GradeCalc::info() { 80 if(is_dirty) 81 compute(); 82 83 std::cout << "课程名称:\t" << course_name << std::endl; 84 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 85 std::cout << "最高分:\t" << max() << std::endl; 86 std::cout << "最低分:\t" << min() << std::endl; 87 88 const std::array<std::string, 5> grade_range{"[0, 60) ", 89 "[60, 70)", 90 "[70, 80)", 91 "[80, 90)", 92 "[90, 100]"}; 93 94 for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i) 95 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" 96 << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n"; 97 } 98 99 void GradeCalc::compute() { 100 if(grades.empty()) 101 return; 102 103 counts.fill(0); 104 rates.fill(0.0); 105 106 // 统计各分数段人数 107 for(auto grade:grades) { 108 if(grade < 60) 109 ++counts[0]; // [0, 60) 110 else if (grade < 70) 111 ++counts[1]; // [60, 70) 112 else if (grade < 80) 113 ++counts[2]; // [70, 80) 114 else if (grade < 90) 115 ++counts[3]; // [80, 90) 116 else 117 ++counts[4]; // [90, 100] 118 } 119 120 // 统计各分数段比例 121 for(size_t i = 0; i < rates.size(); ++i) 122 rates[i] = counts[i] * 1.0 / grades.size(); 123 124 is_dirty = false; // 更新脏标记 125 }
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 5 void test() { 6 GradeCalc c1("OOP"); 7 8 std::cout << "录入成绩:\n"; 9 c1.input(5); 10 11 std::cout << "输出成绩:\n"; 12 c1.output(); 13 14 std::cout << "排序后成绩:\n"; 15 c1.sort(); c1.output(); 16 17 std::cout << "*************成绩统计信息*************\n"; 18 c1.info(); 19 20 } 21 22 int main() { 23 test(); 24 }

1.
std::vector<int> grades; // 存储课程的所有成绩分数。
std::array<int, 5> counts; // 缓存五个分数段的人数统计。
std::array<double, 5> rates; // 缓存五个分数段的人数占比。
std::string course_name; // 存储课程名称。
2.
不合法。因为 push_back 是 std::vector 的成员函数,而在组合模式下,GradeCalc 并没有继承 vector,也没有公开 grades 成员或提供 push_back 接口。外部代码无法直接调用 push_back。
3.
调用次数:只会调用 1次。第一次调用 info() 时,is_dirty 为 true,会触发 compute() 并将 is_dirty 置为 false。后续两次调用 info() 时,is_dirty 为 false,不会再次调用 compute()。
is_dirty的作用:避免在数据未变更的情况下重复进行昂贵的统计计算,是一种性能优化(懒加载/惰性求值)。
新增 update_grade:需要更改。因为 update_grade 会修改 grades 中的数据,导致统计信息失效。因此,在 update_grade 函数的末尾,必须设置 is_dirty = true;,以确保下次调用 info() 时能重新计算
4.
不新增数据成员:可以在需要时(例如在 info() 函数中)临时对 grades 的副本进行排序并计算中位数。
加在哪个函数:加在 info() 函数里最合理,因为它负责输出所有统计信息。
5.
不能去掉 compute 中的 fill(0)。
错误场景:假设先录入 [90, 80, 70],调用 info(),此时 counts 为 [0,0,1,1,1]。然后清空成绩(或录入新成绩 [60, 50]),如果 compute 不重置 counts,那么旧的计数 [0,0,1,1,1] 会和新的计数 [1,1,0,0,0] 叠加,得到错误的 [1,1,1,1,1]。
6.
对功能有影响吗? 没有。reserve 只是预分配内存容量,不影响逻辑功能。即使去掉,push_back 依然能正常工作。
对性能有影响吗? 有。如果不去掉 reserve(n),vector 在 push_back 过程中很可能不需要重新分配内存(reallocate)。如果去掉,当输入大量成绩时,vector 可能会多次进行内存重新分配和元素拷贝,导致性能下降。
任务二
1 #pragma once 2 3 #include <array> 4 #include <string> 5 #include <vector> 6 7 class GradeCalc: private std::vector<int> { 8 public: 9 GradeCalc(const std::string &cname); 10 void input(int n); // 录入n个成绩 11 void output() const; // 输出成绩 12 void sort(bool ascending = false); // 排序 (默认降序) 13 int min() const; // 返回最低分 14 int max() const; // 返回最高分 15 double average() const; // 返回平均分 16 void info(); // 输出成绩统计信息 17 18 private: 19 void compute(); // 计算成绩统计信息 20 21 private: 22 std::string course_name; // 课程名 23 std::array<int, 5> counts; // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100] 24 std::array<double, 5> rates; // 保存各分数段占比 25 bool is_dirty; // 脏标记,记录是否成绩信息有变更 26 };
1 #include <algorithm> 2 #include <array> 3 #include <cstdlib> 4 #include <iomanip> 5 #include <iostream> 6 #include <numeric> 7 #include <string> 8 #include <vector> 9 #include "GradeCalc.hpp" 10 11 12 GradeCalc::GradeCalc(const std::string &cname): course_name{cname}, is_dirty{true}{ 13 counts.fill(0); 14 rates.fill(0); 15 } 16 17 void GradeCalc::input(int n) { 18 if(n < 0) { 19 std::cerr << "无效输入! 人数不能为负数\n"; 20 return; 21 } 22 23 this->reserve(n); 24 25 int grade; 26 27 for(int i = 0; i < n;) { 28 std::cin >> grade; 29 if(grade < 0 || grade > 100) { 30 std::cerr << "无效输入! 分数须在[0,100]\n"; 31 continue; 32 } 33 34 this->push_back(grade); 35 ++i; 36 } 37 38 is_dirty = true; 39 } 40 41 void GradeCalc::output() const { 42 for(auto grade: *this) 43 std::cout << grade << ' '; 44 std::cout << std::endl; 45 } 46 47 void GradeCalc::sort(bool ascending) { 48 if(ascending) 49 std::sort(this->begin(), this->end()); 50 else 51 std::sort(this->begin(), this->end(), std::greater<int>()); 52 } 53 54 int GradeCalc::min() const { 55 if(this->empty()) 56 return -1; 57 58 return *std::min_element(this->begin(), this->end()); 59 } 60 61 int GradeCalc::max() const { 62 if(this->empty()) 63 return -1; 64 65 return *std::max_element(this->begin(), this->end()); 66 } 67 68 double GradeCalc::average() const { 69 if(this->empty()) 70 return 0.0; 71 72 double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size(); 73 return avg; 74 } 75 76 void GradeCalc::info() { 77 if(is_dirty) 78 compute(); 79 80 std::cout << "课程名称:\t" << course_name << std::endl; 81 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 82 std::cout << "最高分:\t" << max() << std::endl; 83 std::cout << "最低分:\t" << min() << std::endl; 84 85 const std::array<std::string, 5> grade_range{"[0, 60) ", 86 "[60, 70)", 87 "[70, 80)", 88 "[80, 90)", 89 "[90, 100]"}; 90 91 for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i) 92 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" 93 << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n"; 94 } 95 96 void GradeCalc::compute() { 97 if(this->empty()) 98 return; 99 100 counts.fill(0); 101 rates.fill(0); 102 103 // 统计各分数段人数 104 for(int grade: *this) { 105 if(grade < 60) 106 ++counts[0]; // [0, 60) 107 else if (grade < 70) 108 ++counts[1]; // [60, 70) 109 else if (grade < 80) 110 ++counts[2]; // [70, 80) 111 else if (grade < 90) 112 ++counts[3]; // [80, 90) 113 else 114 ++counts[4]; // [90, 100] 115 } 116 117 // 统计各分数段比例 118 for(size_t i = 0; i < rates.size(); ++i) 119 rates[i] = counts[i] * 1.0 / this->size(); 120 121 is_dirty = false; 122 }
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 5 void test() { 6 GradeCalc c1("OOP"); 7 8 std::cout << "录入成绩:\n"; 9 c1.input(5); 10 11 std::cout << "输出成绩:\n"; 12 c1.output(); 13 14 std::cout << "排序后成绩:\n"; 15 c1.sort(); c1.output(); 16 17 std::cout << "*************成绩统计信息*************\n"; 18 c1.info(); 19 20 } 21 22 int main() { 23 test(); 24 }

1.
class GradeCalc: private std::vector<int>
2.
不合法。虽然是继承自 vector,但是 私有继承 (private)。这使得基类 vector 的所有公有和保护成员在 GradeCalc 中都变成了私有成员,因此在类外部无法访问 push_back。
3.
组合方式: for(auto grade: grades) // 直接访问私有成员变量 grades。
继承方式: for(int grade: *this) // 将 *this(即当前 GradeCalc 对象)当作一个 vector<int> 来使用。
封装差异: 组合提供了更强的封装性,GradeCalc 完全控制对其内部 vector 的访问。继承(即使是私有继承)在类内部实现时,可以像使用基类一样使用 this 指针,但对外部用户而言,两种方式都隐藏了 vector 的接口。
4.
更推荐组合方案。
理由:
语义更清晰:一个成绩计算器 包含 一组成绩,而不是 是一种 vector。组合符合 “has-a” 的自然语义。
封装性更强:组合可以完全控制对 vector 的访问,避免了意外暴露不需要的接口(即使私有继承也可能在类内部误用)。
灵活性更高:如果未来需要更换底层容器(比如换成 list 或自定义容器),组合方案只需修改成员变量类型;而继承方案则需要改变继承关系,改动更大。
任务三
#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); // 创建图形,返回堆对象指针
1 #include <string> 2 #include "Graph.hpp" 3 4 void test() { 5 Canvas canvas; 6 7 canvas.add("circle"); 8 canvas.add("triangle"); 9 canvas.add("rectangle"); 10 canvas.paint(); 11 } 12 13 int main() { 14 test(); 15 }
#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; } }

1.组合: std::vector<Graph*> graphs; // Canvas 类组合了一个图形指针的动态数组,用于存储和管理多个图形对象。
继承:
class Circle: public Graphclass Triangle: public Graphclass Rectangle: public Graph
2.draw 不是虚函数:g->draw() 将静态绑定到 Graph::draw()(即什么都不做),无法实现多态,所有图形都不会被正确绘制。
vector<Graph> (对象切片):会发生 对象切片(Object Slicing)。添加到 vector 中的 Circle、Triangle 等对象会被强制转换成 Graph 基类对象,丢失所有派生类特有的数据和行为。paint() 调用的永远是 Graph::draw()。
xi构函数不是虚函数:通过基类指针 delete g 时,只会调用 Graph 的析构函数,而不会调用派生类(如 Circle)的析构函数,导致派生类部分资源无法被正确释放,造成内存泄漏或其他未定义行为。
3.Graph.hpp: 声明新类 class Star: public Graph { public: void draw(); };,并在 GraphType 枚举中添加 star。
Graph.cpp: 实现 Star::draw() 方法,并在 str_to_GraphType 和 make_graph 函数中添加对 "star" 字符串的支持。
demo3.cpp (测试文件): 可以添加 canvas.add("star"); 来测试新功能。
4.释放位置:在 Canvas 的析构函数 ~Canvas() 中,通过 delete g; 释放。
原始指针利弊:
弊: 手动管理内存容易出错(忘记 delete 导致泄漏,重复 delete 导致崩溃),不符合现代 C++ 的 RAII 原则。
利: 在这个简单示例中,控制权明确,易于理解底层机制。
改进建议:应使用智能指针,如 std::vector<std::unique_ptr<Graph>>,可以自动管理内存,避免上述问题。
任务四
设计一个毛绒玩具工厂系统,能生产不同类型的电子毛绒玩具(如会唱歌的熊、会发光的兔子、会讲故事的猫)。
用户可以通过工厂统一接口查看所有玩具的信息和特异功能继承 (is-a): 定义抽象基类 Toy,所有具体玩具(SingingBear, GlowingRabbit, StorytellingCat)都继承自它。Toy 包含纯虚函数 virtual void specialFeature() = 0;。
组合 (has-a): ToyFactory 类组合一个 std::vector<std::unique_ptr<Toy>> toys 来管理生产的玩具。
1 #pragma once 2 #include <string> 3 #include <memory> 4 #include <vector> 5 #include <iostream> 6 7 // 抽象基类 8 class Toy { 9 protected: 10 std::string name; 11 std::string type; 12 13 public: 14 Toy(const std::string& n, const std::string& t) : name(n), type(t) {} 15 virtual ~Toy() = default; 16 17 std::string getName() const { return name; } 18 std::string getType() const { return type; } 19 20 // 纯虚函数,定义特异功能接口 21 virtual void specialFeature() const = 0; 22 virtual void displayInfo() const { 23 std::cout << "名称: " << name << ", 类型: " << type << std::endl; 24 specialFeature(); 25 } 26 }; 27 28 // 具体玩具类 29 class SingingBear : public Toy { 30 public: 31 SingingBear(const std::string& name) : Toy(name, "会唱歌的熊") {} 32 void specialFeature() const override { 33 std::cout << " 特异功能: 播放《生日快乐歌》\n"; 34 } 35 }; 36 37 class GlowingRabbit : public Toy { 38 public: 39 GlowingRabbit(const std::string& name) : Toy(name, "会发光的兔子") {} 40 void specialFeature() const override { 41 std::cout << " 特异功能: 身体发出柔和的蓝光\n"; 42 } 43 }; 44 45 // 工厂类 46 class ToyFactory { 47 private: 48 std::vector<std::unique_ptr<Toy>> toys; 49 50 public: 51 void addToy(std::unique_ptr<Toy> toy) { 52 toys.push_back(std::move(toy)); 53 } 54 55 void displayAllToys() const { 56 for (const auto& toy : toys) { 57 toy->displayInfo(); 58 std::cout << "-------------------\n"; 59 } 60 } 61 };
1 #include "Toy.hpp" 2 3 int main() { 4 ToyFactory factory; 5 6 factory.addToy(std::make_unique<SingingBear>("泰迪")); 7 factory.addToy(std::make_unique<GlowingRabbit>("小白")); 8 9 std::cout << "=== 工厂所有玩具信息 ===\n"; 10 factory.displayAllToys(); 11 12 return 0; 13 }

浙公网安备 33010602011771号