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

问题回答:
1.std::string course_name; 被组合对象:记录本课程的名字;
std::vector<int> grades; 被组合对象:保存所有学生成绩;
std::array<int,5> counts; 被组合对象:保存5个分数段的人数;
std::array<double,5> rates; 被组合对象:保存5个分数段的人数占比。
2.不合法。push_back 不是 GradeCalc 类的公有接口,它是 std::vector 的成员函数,而 grades 是 GradeCalc 的 private 成员,在类外部无法直接访问 grades ,这违反了封装原则。
(input也写成了inupt)。
3.(1)连续打印3次统计信息, compute 只会被调用1次;
标记 is_dirty可以防止重复compute,保证只有发现数据已变时重算。
(2)不需要。但是要在update_grade(index, new_grade) 中增加is_dirty = true,之后info()会调用compete,进行相关操作。
4.
1 void GradeCalc::compute() 2 { 3 if (grades.empty()) 4 return; 5 6 counts.fill(0); 7 rates.fill(0.0); 8 9 // 统计各分数段人数 10 for (auto grade : grades) 11 { 12 if (grade < 60) 13 ++counts[0]; // [0, 60) 14 else if (grade < 70) 15 ++counts[1]; // [60, 70) 16 else if (grade < 80) 17 ++counts[2]; // [70, 80) 18 else if (grade < 90) 19 ++counts[3]; // [80, 90) 20 else 21 ++counts[4]; // [90, 100] 22 } 23 24 // 统计各分数段比例 25 for (size_t i = 0; i < rates.size(); ++i) 26 rates[i] = counts[i] * 1.0 / grades.size(); 27 28 29 double mid; 30 size_t n = grades.size(); 31 if (n % 2 == 0) 32 mid = (grades[n / 2 - 1] + grades[n / 2]) / 2; 33 else 34 mid = grades[n / 2]; 35 std::cout << "中位数:\t" << mid << std::endl; 36 37 is_dirty = false; // 更新脏标记 38 }
没有先排序后再求中位数,是因为sort在compete前执行了,如果没有先sort,可以先拷贝一个副本,排序后求中位数。
5.不能。当去掉这两行后,如果后续数据发生修改,会在原有基础上进行累加,无法正确进行初始化。
6.(1)无影响。程序仍然能正确运行。
(2)有影响。去掉grades.reserve(n)后,会触发vector频繁进行自动扩容,增加了运行开销。
试验任务2:
1 #pragma once 2 3 #include <array> 4 #include <string> 5 #include <vector> 6 7 class GradeCalc : private std::vector<int> 8 { 9 public: 10 GradeCalc(const std::string &cname); 11 void input(int n); // 录入n个成绩 12 void output() const; // 输出成绩 13 void sort(bool ascending = false); // 排序 (默认降序) 14 int min() const; // 返回最低分 15 int max() const; // 返回最高分 16 double average() const; // 返回平均分 17 void info(); // 输出成绩统计信息 18 19 private: 20 void compute(); // 计算成绩统计信息 21 22 private: 23 std::string course_name; // 课程名 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 #include "GradeCalc.hpp" 10 11 GradeCalc::GradeCalc(const std::string &cname) : course_name{cname}, is_dirty{true} 12 { 13 counts.fill(0); 14 rates.fill(0); 15 } 16 17 void GradeCalc::input(int n) 18 { 19 if (n < 0) 20 { 21 std::cerr << "无效输入! 人数不能为负数\n"; 22 return; 23 } 24 25 this->reserve(n); 26 27 int grade; 28 29 for (int i = 0; i < n;) 30 { 31 std::cin >> grade; 32 if (grade < 0 || grade > 100) 33 { 34 std::cerr << "无效输入! 分数须在[0,100]\n"; 35 continue; 36 } 37 38 this->push_back(grade); 39 ++i; 40 } 41 42 is_dirty = true; 43 } 44 45 void GradeCalc::output() const 46 { 47 for (auto grade : *this) 48 std::cout << grade << ' '; 49 std::cout << std::endl; 50 } 51 52 void GradeCalc::sort(bool ascending) 53 { 54 if (ascending) 55 std::sort(this->begin(), this->end()); 56 else 57 std::sort(this->begin(), this->end(), std::greater<int>()); 58 } 59 60 int GradeCalc::min() const 61 { 62 if (this->empty()) 63 return -1; 64 65 return *std::min_element(this->begin(), this->end()); 66 } 67 68 int GradeCalc::max() const 69 { 70 if (this->empty()) 71 return -1; 72 73 return *std::max_element(this->begin(), this->end()); 74 } 75 76 double GradeCalc::average() const 77 { 78 if (this->empty()) 79 return 0.0; 80 81 double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size(); 82 return avg; 83 } 84 85 void GradeCalc::info() 86 { 87 if (is_dirty) 88 compute(); 89 90 std::cout << "课程名称:\t" << course_name << std::endl; 91 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 92 std::cout << "最高分:\t" << max() << std::endl; 93 std::cout << "最低分:\t" << min() << std::endl; 94 95 const std::array<std::string, 5> grade_range{"[0, 60) ", 96 "[60, 70)", 97 "[70, 80)", 98 "[80, 90)", 99 "[90, 100]"}; 100 101 for (int i = static_cast<int>(grade_range.size()) - 1; i >= 0; --i) 102 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" 103 << std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n"; 104 } 105 106 void GradeCalc::compute() 107 { 108 if (this->empty()) 109 return; 110 111 counts.fill(0); 112 rates.fill(0); 113 114 // 统计各分数段人数 115 for (int grade : *this) 116 { 117 if (grade < 60) 118 ++counts[0]; // [0, 60) 119 else if (grade < 70) 120 ++counts[1]; // [60, 70) 121 else if (grade < 80) 122 ++counts[2]; // [70, 80) 123 else if (grade < 90) 124 ++counts[3]; // [80, 90) 125 else 126 ++counts[4]; // [90, 100] 127 } 128 129 // 统计各分数段比例 130 for (size_t i = 0; i < rates.size(); ++i) 131 rates[i] = counts[i] * 1.0 / this->size(); 132 133 is_dirty = false; 134 }
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 #include <windows.h> 5 6 void test() 7 { 8 SetConsoleOutputCP(CP_UTF8); 9 GradeCalc c1("OOP"); 10 11 std::cout << "录入成绩:\n"; 12 c1.input(5); 13 14 std::cout << "输出成绩:\n"; 15 c1.output(); 16 17 std::cout << "排序后成绩:\n"; 18 c1.sort(); 19 c1.output(); 20 21 std::cout << "*************成绩统计信息*************\n"; 22 c1.info(); 23 } 24 25 int main() 26 { 27 test(); 28 }
运行结果:

问题回答:
1.class GradeCalc : private std::vector<int>。
2.当前继承方式下,基类 vector<int> 的接口不会自动成为 GradeCalc 的接口,因为是私有继承。
不能编译通过。因为私有继承使基类的所有公有接口在派生类中都变为私有,外部无法直接调用(input也写成inupt了)。
3.for(auto grade: grades)是通过成员变量grades来访问;for(int grade: *this)是通过this指针来访问。
差异:组合方式封装性更强,外部只能用公有接口访问;继承方式封装性较差,但可以直接用基类接口。
4.组合方案更适合。vector只是用来存储数据,应该是组合而不是用于继承;组合方案耦合度低,封装性更好。
试验任务3:
1 #pragma once 2 3 #include <string> 4 #include <vector> 5 6 enum class GraphType 7 { 8 circle, 9 triangle, 10 rectangle 11 }; 12 13 // Graph类定义 14 class Graph 15 { 16 public: 17 virtual void draw() {} 18 virtual ~Graph() = default; 19 }; 20 21 // Circle类声明 22 class Circle : public Graph 23 { 24 public: 25 void draw(); 26 }; 27 28 // Triangle类声明 29 class Triangle : public Graph 30 { 31 public: 32 void draw(); 33 }; 34 35 // Rectangle类声明 36 class Rectangle : public Graph 37 { 38 public: 39 void draw(); 40 }; 41 42 // Canvas类声明 43 class Canvas 44 { 45 public: 46 void add(const std::string &type); // 根据字符串添加图形 47 void paint() const; // 使用统一接口绘制所有图形 48 ~Canvas(); // 手动释放资源 49 50 private: 51 std::vector<Graph *> graphs; 52 }; 53 54 // 4. 工具函数 55 GraphType str_to_GraphType(const std::string &s); // 字符串转枚举类型 56 Graph *make_graph(const std::string &type); // 创建图形,返回堆对象指针
1 #include <algorithm> 2 #include <cctype> 3 #include <iostream> 4 #include <string> 5 6 #include "Graph.hpp" 7 8 // Circle类实现 9 void Circle::draw() { std::cout << "draw a circle...\n"; } 10 11 // Triangle类实现 12 void Triangle::draw() { std::cout << "draw a triangle...\n"; } 13 14 // Rectangle类实现 15 void Rectangle::draw() { std::cout << "draw a rectangle...\n"; } 16 17 // Canvas类实现 18 void Canvas::add(const std::string &type) 19 { 20 Graph *g = make_graph(type); 21 if (g) 22 graphs.push_back(g); 23 } 24 25 void Canvas::paint() const 26 { 27 for (Graph *g : graphs) 28 g->draw(); 29 } 30 31 Canvas::~Canvas() 32 { 33 for (Graph *g : graphs) 34 delete g; 35 } 36 37 // 工具函数实现 38 // 字符串 → 枚举转换 39 GraphType str_to_GraphType(const std::string &s) 40 { 41 std::string t = s; 42 std::transform(s.begin(), s.end(), t.begin(), 43 [](unsigned char c) 44 { return std::tolower(c); }); 45 46 if (t == "circle") 47 return GraphType::circle; 48 49 if (t == "triangle") 50 return GraphType::triangle; 51 52 if (t == "rectangle") 53 return GraphType::rectangle; 54 55 return GraphType::circle; // 缺省返回 56 } 57 58 // 创建图形,返回堆对象指针 59 Graph *make_graph(const std::string &type) 60 { 61 switch (str_to_GraphType(type)) 62 { 63 case GraphType::circle: 64 return new Circle; 65 case GraphType::triangle: 66 return new Triangle; 67 case GraphType::rectangle: 68 return new Rectangle; 69 default: 70 return nullptr; 71 } 72 }
1 #include <string> 2 #include "Graph.hpp" 3 #include <windows.h> 4 5 void test() 6 { 7 SetConsoleOutputCP(CP_UTF8); 8 Canvas canvas; 9 10 canvas.add("circle"); 11 canvas.add("triangle"); 12 canvas.add("rectangle"); 13 canvas.paint(); 14 } 15 16 int main() 17 { 18 test(); 19 }
运行结果:

问题回答:
1.(1)std::vector<Graph *> graphs;用于存储所有图形对象的指针集合。
(2)class Circle : public Graph;class Triangle : public Graph;class Rectangle : public Graph。
2.(1)会始终调用基类Graph的draw函数,而不是重写后的。
(2) 会发生对象切片,只保留基类的的数据,而丢失派生类的数据信息,导致多态失效。
(3) 只会调用基类的析构函数,不会调用派生类的析构函数,会造成内存泄漏。
试验任务4:
Toy.hpp:
1 #pragma once 2 3 #include <string> 4 #include <vector> 5 6 enum class ToyType 7 { 8 Dog, 9 Cat, 10 Fox, 11 Rabbit 12 }; 13 14 class Toy 15 { 16 public: 17 Toy(const std::string &name, ToyType type, const std::string &color); 18 virtual ~Toy() = default; 19 virtual void act() const = 0; 20 std::string getname() const; 21 std::string getcolor() const; 22 23 private: 24 std::string name; 25 std::string color; 26 ToyType type; 27 }; 28 29 class Dog : public Toy 30 { 31 public: 32 Dog(const std::string &name, const std::string &color) : Toy{name, ToyType::Dog, color} {}; 33 void act() const override; 34 }; 35 36 class Cat : public Toy 37 { 38 public: 39 Cat(const std::string &name, const std::string &color) : Toy{name, ToyType::Cat, color} {}; 40 void act() const override; 41 }; 42 43 class Fox : public Toy 44 { 45 public: 46 Fox(const std::string &name, const std::string &color) : Toy{name, ToyType::Fox, color} {}; 47 void act() const override; 48 }; 49 50 class Rabbit : public Toy 51 { 52 public: 53 Rabbit(const std::string &name, const std::string &color) : Toy{name, ToyType::Rabbit, color} {}; 54 void act() const override; 55 }; 56 57 class ToyFactory 58 { 59 private: 60 std::vector<Toy*> toys; 61 public: 62 void add(const std::string &name, ToyType type, const std::string &color); 63 void display() const; 64 ~ToyFactory(); 65 }; 66 67 Toy* creat(const std::string &name, ToyType type, const std::string &color);
Toy.cpp:
1 #include "Toy.hpp" 2 #include <iostream> 3 #include <string> 4 5 Toy::Toy(const std::string &name, ToyType type,const std::string &color) : name{name}, type{type}, color{color}{} 6 7 std::string Toy::getname() const 8 { 9 return name; 10 } 11 12 std::string Toy::getcolor() const 13 { 14 return color; 15 } 16 17 void Dog::act() const 18 { 19 std::cout << "[" << "Dog " << getcolor() << "]" << getname() << " is protecting his owner!" << std::endl; 20 } 21 22 void Cat::act() const 23 { 24 std::cout << "[" << "Cat " << getcolor() << "]" << getname() << " is sleeping!" << std::endl; 25 } 26 27 void Fox::act() const 28 { 29 std::cout << "[" << "Fox " << getcolor() << "]" << getname() << " says that It's called a hustle, sweetheart." << std::endl; 30 } 31 32 void Rabbit::act() const 33 { 34 std::cout << "[" << "Rabbit " << getcolor() << "]" << getname() << " says that I am not a dumb bunny." << std::endl; 35 } 36 37 void ToyFactory::display() const 38 { 39 for (const auto& toy : toys) 40 { 41 toy->act(); 42 } 43 } 44 45 void ToyFactory::add(const std::string &name, ToyType type, const std::string &color) 46 { 47 Toy* new_toy = creat(name, type, color); 48 if (new_toy) 49 { 50 toys.push_back(new_toy); 51 } 52 } 53 54 Toy *creat(const std::string &name, ToyType type, const std::string &color) 55 { 56 switch (type) 57 { 58 case ToyType::Dog: 59 return new Dog(name, color); 60 case ToyType::Cat: 61 return new Cat(name, color); 62 case ToyType::Fox: 63 return new Fox(name, color); 64 case ToyType::Rabbit: 65 return new Rabbit(name, color); 66 default: 67 return nullptr; 68 } 69 } 70 71 ToyFactory::~ToyFactory() 72 { 73 for (Toy* toy : toys) 74 { 75 delete toy; 76 } 77 }
demo4.cpp:
1 #include <string> 2 #include <iostream> 3 #include "Toy.hpp" 4 5 void test() 6 { 7 ToyFactory factory; 8 factory.add("Bruce", ToyType::Dog, "Brown"); 9 factory.add("Garfield", ToyType::Cat, "orange"); 10 factory.add("Nick Wilde", ToyType::Fox, "orange"); 11 factory.add("Judy Hopps", ToyType::Rabbit, "Gray"); 12 13 factory.display(); 14 } 15 16 int main() 17 { 18 test(); 19 }
运行结果:

说明:
问题描述:
设计一款电子毛绒玩具工厂管理系统,支持多种动物造型电子毛绒玩具(狗、猫、狐狸、兔子)的创建、添加与特定功能,系统需通过统一接口调用所有玩具的特异功能。而这里实现的是种类、颜色、名称、动作的显示。
对象关系:
继承关系:Toy封装了名称、种类、颜色和核心接口act,dog、cat、fox、rabbit均继承自Toy,是Toy的派生,并且对act重写;
组合关系:ToyFactory中包含std::vector<Toy*> toys;
多态关系:通过act指针,动态绑定到act的实现,进而实现了一个接口调用所有特异功能。

浙公网安备 33010602011771号