实验四
1.试验任务1
设计性实验任务:用组合实现成绩计算器类。

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);//初始化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);//先分配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>());///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;// std::min_element返回迭代器,需要解引用才是需要的成绩 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 77 return avg; 78 } 79 80 void GradeCalc::info() { 81 if(is_dirty) //更改成绩 82 compute();//重新成绩统计 83 84 std::cout << "课程名称:\t" << course_name << std::endl; 85 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 86 std::cout << "最高分:\t" << max() << std::endl; 87 std::cout << "最低分:\t" << min() << std::endl; 88 89 const std::array<std::string, 5> grade_range{"[0, 60) ", 90 "[60, 70)", 91 "[70, 80)", 92 "[80, 90)", 93 "[90, 100]"}; 94 95 for(int i = static_cast<int>(grade_range.size())-1; i >= 0; --i) 96 //atic_cast<int>():将 size_t 类型强制转换为 int(避免无符号整数减 1 时的溢出风险,转换为 int 可正常处理负数)*/ 97 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" //输出制表符 \t(对齐格式) 98 << std::fixed << std::setprecision(2) << rates[i]*100 << "%\n"; 99 } 100 101 void GradeCalc::compute() { 102 if(grades.empty()) 103 return; 104 105 counts.fill(0); ///填充初始值 106 rates.fill(0.0); 107 108 // 统计各分数段人数 109 for(auto grade:grades) { 110 if(grade < 60) 111 ++counts[0]; // [0, 60) 112 else if (grade < 70) 113 ++counts[1]; // [60, 70) 114 else if (grade < 80) 115 ++counts[2]; // [70, 80) 116 else if (grade < 90) 117 ++counts[3]; // [80, 90) 118 else 119 ++counts[4]; // [90, 100] 120 } 121 122 // 统计各分数段比例 123 for(size_t i = 0; i < rates.size(); ++i) 124 rates[i] = counts[i] * 1.0 / grades.size(); 125 //.size() 的返回值类型就是 size_t用 size_t 作为循环变量以避免类型不匹配”的警告,同时保证下标是非负的。 126 is_dirty = false; // 更新脏标记 127 }
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:组合关系识别
GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
1.std::string course_name;组合了string类型,字符串类型存储课程名称
2.std::vector<int> grades;组合了vector类型,动态存储整数型课程成绩,
3.std::array<int, 5> counts;组合了array类型,静态存储整数类型的各个分数段的人数
4.std::array<double, 5> rates;组合了array类型静态存储各个分数段的人数百分比
5.bool is_dirty;组合了bool类型,标记成绩是否有变更。
问题2:接口暴露理解
如在 test 模块中这样使用,是否合法?如不合法,解释原因。

不合法,因为push_back 使vector类型中在尾部增加元素的,但是vector类型的grades是私有成员,无法通过外部接口访问
问题3:架构设计分析
当前设计方案中, compute 在 info 模块中调用:
(1)连续打印3次统计信息, compute 会被调用几次?标记 is_dirty 起到什么作用?
0或1次;脏标记,当标记更改时表明成绩更改/更新过,需要重新进行成绩统计。
(2)如新增 update_grade(index, new_grade) ,这种设计需要更改 compute 调用位置吗?简洁说明理由。
不需要,因为新增函数只要在函数体内部更改了 is_dirty ,就会在info模块中调用compute,无需重新更改位置
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
在类里增加公有成员函数double medium()const ;

问题5:数据状态管理
GradeCalc 和 compute 中都包含代码: counts.fill(0); rates.fill(0); 。
compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
不可以,在成绩数据变化并且compute被多次调用的时候会引发统计错误
问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
没有
(2)对性能有影响吗?如有影响,用一句话陈述具体影响
有,提前分配vector空间,避免多次push_back造成的多次扩容
2.试验任务二
设计性实验任务:用继承实现成绩计算器类。
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:继承关系识别
写出 GradeCalc 类声明体现"继承"关系的完整代码行。
class GradeCalc: private std::vector<int> {
问题2:接口暴露理解
当前继承方式下,基类 vector<int> 的接口会自动成为 GradeCalc 的接口吗?
如在 test 模块中这样用,能否编译通过?用一句话解释原因。

不会,继承方式时private私有继承,vector内部的的接口不能在外部被调用
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。

1.组合:通过grade直接迭代vector类的grades
2.继承:通过继承了vector接口的*this来迭代
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由
组合,因为成绩计算类包含存储成绩的成员,是包含has a 的关系,符合组合,而不是范围大小is a 的关系,不大符合传统使用继承的场景
3.试验任务三
设计性实验任务:综合运用组合、继承、虚函数实现用一个接口打印不同图形。

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 Graph* g = make_graph(type); 20 if (g) 21 graphs.push_back(g); 22 } 23 24 void Canvas::paint() const { 25 for (Graph* g : graphs) 26 g->draw(); 27 } 28 29 Canvas::~Canvas() { 30 for (Graph* g : graphs) 31 delete g; 32 } 33 34 // 工具函数实现 35 // 字符串 → 枚举转换 36 GraphType str_to_GraphType(const std::string& s) { 37 std::string t = s; 38 std::transform(s.begin(), s.end(), t.begin(), 39 [](unsigned char c) { return std::tolower(c);}); 40 41 if (t == "circle") 42 return GraphType::circle; 43 44 if (t == "triangle") 45 return GraphType::triangle; 46 47 if (t == "rectangle") 48 return GraphType::rectangle; 49 50 return GraphType::circle; // 缺省返回 51 } 52 53 // 创建图形,返回堆对象指针 54 Graph* make_graph(const std::string& type) { 55 switch (str_to_GraphType(type)) { 56 case GraphType::circle: return new Circle; 57 case GraphType::triangle: return new Triangle; 58 case GraphType::rectangle: return new Rectangle; 59 default: return nullptr; 60 } 61 }
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 Graph* g = make_graph(type); 20 if (g) 21 graphs.push_back(g); 22 } 23 24 void Canvas::paint() const { 25 for (Graph* g : graphs) 26 g->draw(); 27 } 28 29 Canvas::~Canvas() { 30 for (Graph* g : graphs) 31 delete g; 32 } 33 34 // 工具函数实现 35 // 字符串 → 枚举转换 36 GraphType str_to_GraphType(const std::string& s) { 37 std::string t = s; 38 std::transform(s.begin(), s.end(), t.begin(), 39 [](unsigned char c) { return std::tolower(c);}); 40 41 if (t == "circle") 42 return GraphType::circle; 43 44 if (t == "triangle") 45 return GraphType::triangle; 46 47 if (t == "rectangle") 48 return GraphType::rectangle; 49 50 return GraphType::circle; // 缺省返回 51 } 52 53 // 创建图形,返回堆对象指针 54 Graph* make_graph(const std::string& type) { 55 switch (str_to_GraphType(type)) { 56 case GraphType::circle: return new Circle; 57 case GraphType::triangle: return new Triangle; 58 case GraphType::rectangle: return new Rectangle; 59 default: return nullptr; 60 } 61 }
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 }

问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
std::vector<Graph*> graphs; 通过vector类型的多个graphs成员组合存储了多个Graph*对象。存储所需要绘制的图形们
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
class Circle : public Graph {
class Triangle : public Graph {
class Rectangle : public Graph {
问题2:多态机制观察
(1) Graph 中的 draw 若未声明成虚函数, Canvas::paint() 中 g->draw() 运行结果会有何不同?
会调用 Graph 中的 draw从而空实现
(2)若 Canvas 类 std::vector<Graph*> 改成 std::vector<Graph> ,会出现什么问题?
Graph是抽象类,不可以直接创建对象,会导致编译失败
(3)若 ~Graph() 未声明成虚函数,会带来什么问题?
子类Circle Triangle Rectangle无法析构,释放内存,导致内存泄漏
问题3:扩展性思考
若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。
1.hpp文件中
新增枚举类型star enum class GraphType {circle, triangle, rectangle,star};
新增class Star :public Graph {public :void draw();};
2.cpp文件
新增void Star ::draw(){std::cout<<"draw a star..\n";}
str_to_GraphType中新增if(t =="star")return GraphType::star;
make_graph中switch新增case GraphType::star:return new Star;
3.demo3.cpp
void test()新增canvas.add(“star”);
问题4:资源管理
观察 make_graph 函数和 Canvas 析构函数:
(1) make_graph 返回的对象在什么地方被释放?
在canvas析构函数遍历graphs对每个指针释放内存
(2)使用原始指针管理内存有何利弊?
优点:性能好
缺点:需要手动new delete ,容易导致内存泄露
4.试验任务四
应用的问题场景描述:
在本实验中,我们需要设计一个电子毛绒玩具系统。这个系统需要能够创建和管理不同类型的毛绒玩具,并能够通过一个工厂类来生成这些玩具。每个玩具有其特有的功能,例如跳舞、唱歌、发光等。
陈述各类之间的关系(继承、组合等)及设计理由:
组合:毛绒玩具类 Toy和玩具工厂类 ToyFactory 理由:是has a 关系,工厂包含玩具
继承:DanceToy、MusicToy、LightToy继承Toy 理由:是is a 关系,范围大小不同
源码
1 #include<iostream> 2 #include<vector> 3 #include<cstring> 4 5 6 using namespace std; 7 8 class Toy{ 9 private : 10 string name; 11 string type; 12 double price; 13 int num; 14 15 public: 16 Toy(string n,string t,double p,int m):name(n),type(t),price(p),num(m){ 17 } 18 virtual ~Toy()=default; 19 20 virtual void basic()const {cout<<"Name:"<<name<<" Type:"<<type<<" Price:" 21 <<price<<"元 Number:"<<num<<"个"<<endl;}; 22 virtual void special()const =0; 23 24 25 }; 26 27 class DanceToy:public Toy{ 28 public: 29 DanceToy(string name,double price,int num):Toy(name,"Dance",price,num){ 30 } 31 void special()const override{cout<<"Special Function: Graceful dance."<<endl; 32 } 33 }; 34 35 36 class MusicToy:public Toy{ 37 public: 38 MusicToy(string name,double price,int num):Toy(name,"Music",price,num){ 39 } 40 void special()const override{cout<<"Special Function: Play music."<<endl; 41 } 42 }; 43 44 45 class LightToy:public Toy{ 46 public: 47 LightToy(string name,double price,int num):Toy(name,"Light",price,num){ 48 } 49 void special()const override{cout<<"Special Function: Light up."<<endl; 50 } 51 }; 52 53 class ToyFactory{ 54 public: 55 void add(Toy* toy); 56 void info()const; 57 ~ToyFactory(); 58 59 private: 60 vector<Toy*> toys; 61 bool is_dirty=0; 62 };
1 #include<iostream> 2 #include<vector> 3 #include<string> 4 #include <algorithm> 5 #include <cstdlib> 6 #include <iomanip> 7 #include <numeric> 8 #include "Toy.hpp" 9 10 using namespace std; 11 12 void ToyFactory::add(Toy* toy) 13 { 14 toys.push_back(toy); 15 } 16 17 void ToyFactory::info() const 18 { 19 cout<<"\n工厂内玩具:\n"; 20 for(auto* toy:toys){ 21 toy->basic(); 22 toy->special(); 23 cout<<endl; 24 } 25 } 26 27 ToyFactory::~ToyFactory() 28 { 29 for(auto &toy:toys)delete toy; 30 }
1 #include<iostream> 2 #include <string> 3 #include "Toy.hpp" 4 5 int main() 6 { 7 ToyFactory f; 8 f.add(new DanceToy("bear",88,6)); 9 f.add(new MusicToy("bird",66,10)); 10 f.add(new LightToy("cat",77,15)); 11 f.add(new LightToy("dog",77,19)); 12 13 f.info(); 14 return 0; 15 }
运行测试截图


浙公网安备 33010602011771号