实验四
1.实验任务一
GradeCalc.hpp代码如下
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 };
GradeCalc.cpp代码如下
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 }
demo1.cpp代码如下
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 功能是存5个分数段人数
std::array<double, 5> rates 功能是存5个分数段占比
问题2:我觉得不合法,因为push_back是std::vector这个类的函数,而std::vector<int> grades是GradeCalc类的私有成员,所以在GradeCalc类的外部无法使用这个函数,所以不合法
问题3:(1)会被调用1次,是在执行c1.info的时候,is_dirty为true的时候,调用了一次,调用后,is_dirty变为false,就不调用了。
is_dirty 作用是设置脏标记,当它是true的时候说明成绩信息有变更,要重新统计一次,为false就没有变更
(2)我觉得不用改位置,只要在update_grade函数末尾把is_dirty置为true就行
问题4:中位数是在排好的数据里面找中间位置的,所以我觉得在sort里面加
void GradeCalc::sort(bool ascending) {
if(ascending)
std::sort(grades.begin(), grades.end());
else
std::sort(grades.begin(), grades.end(), std::greater<int>()); //降序
double mid;
size_t n=grades.size();
if(n%2==0){
mid=(grades[n/2]+grades[n/2+1])/2;
}
else{
mid=grades[n/2];
}
std::cout<<"中位数是:"<<mid;
}
问题5:我觉得不能去掉,当数据更改的时候,没有这两行会出错。因为在第一次调用compute的时候存了不同分数段的人数,当数据变更,要重新统计的时候,没有这两行把人数清零,就会在第一次统计的基础上去继续统计,统计结果就会出错
问题6:(1)功能没有影响,去掉后运行结果如下

(2)在性能上应该有影响。reverse的功能是提前预订好大空间,不会频繁出现内存不够的情况,如果去掉,当数据很多的时候,就要频繁的重新分配内存,移动数据
2.实验任务二
GradeCalc.hpp代码如下
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 };
GradeCalc.cpp代码如下
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 }
demo2.cpp
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<int> 的接口不会自动成为 GradeCalc 的接口,因为基类是以私有继承的方式继承过来的,在GradeCalc类里,它属于私有,无法自动成为接口。
我觉得无法编译通过。因为push_back是基类 vector<int>的成员函数,基类以私有方式继承过来,在类的外部无法访问,因此会报错
问题3:组合类是通过私有成员grades来访问数据,但因为grades是私有成员,在类的外部无法直接访问,只能通过一些共有接口比如output等来间接访问。而继承是通过当前的这个对象自己来访问数据,因为继承就相当于这个对象本身是vector类型,所以在类内部,可以直接通过自己访问数据,但因为继承方式是私有继承,所以在类外部还是只能通过一些共有接口访问。
问题4:我觉得组合类更适合。首先是我觉得组合类在编写过程中逻辑很清晰,很分明,就是形成一个装任意多个成绩的动态列表,然后使用它,对它进行排序,找最值,而继承是本身是一个容器列表,在理解上有些奇怪,会让思路逻辑没那么清晰。其次我觉得组合类灵活性更强一点,如果不需要用vector了,直接把私有成员类型替换一下,然后操作修改一下就行,但是继承可能就要去掉继承,然后类里面还要重新添加成员,把原先的this指针进行替换
3.实验任务三
Graph.hpp 代码如下
1 #pragma once 2 3 #include <string> 4 #include <vector> 5 6 enum class GraphType {circle, triangle, rectangle}; 7 8 // Graph类定义 9 class Graph { 10 public: 11 virtual void draw() {} 12 virtual ~Graph() = default; 13 }; 14 15 // Circle类声明 16 class Circle : public Graph { 17 public: 18 void draw(); 19 }; 20 21 // Triangle类声明 22 class Triangle : public Graph { 23 public: 24 void draw(); 25 }; 26 27 // Rectangle类声明 28 class Rectangle : public Graph { 29 public: 30 void draw(); 31 }; 32 33 // Canvas类声明 34 class Canvas { 35 public: 36 void add(const std::string& type); // 根据字符串添加图形 37 void paint() const; // 使用统一接口绘制所有图形 38 ~Canvas(); // 手动释放资源 39 40 private: 41 std::vector<Graph*> graphs; 42 }; 43 44 // 4. 工具函数 45 GraphType str_to_GraphType(const std::string& s); // 字符串转枚举类型 46 Graph* make_graph(const std::string& type); // 创建图形,返回堆对象指针
Graph.cpp代码如下
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 }
demo3.cpp
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)std::vector<Graph*> graphs; 功能是存放Graph指针的动态数组
(2)class Circle : public Graph ; class Triangle : public Graph ; class Rectangle : public Graph ;
问题2:(1)若没有声明为虚函数,运行结果如下,无运行结果。因为没有声明为虚函数,那么g->draw()就是指向基类的draw函数,基类的draw函数没有执行语句,所以没有输出

(2)如果改为std::vector<Graph>之后,存储的都是Graph类型的数据,那么派生类存入进去的时候,只会保留属于Graph的特点,而自身的draw功能没有被保存,所以最后调用draw的时候,调用的都是基类的draw函数,派生类的draw功能无法实现
(3)如果没有声明为虚函数,会内存泄漏,因为如果析构函数不是虚函数。每次调用的都是基类的析构函数,派生类对象的资源没有得到释放
问题3:首先是在强枚举里面加入star,然后是在Graph.hpp里面加入star类的声明,class Circle : public Graph...,在Graph.cpp里面加入star类的实现void Star::draw() { std::cout << "draw a star...\n"; },在GraphType str_to_GraphType(const std::string& s)里面加入star的字符串枚举转换,最后在Graph* make_graph(const std::string& type)中,加入case GraphType::star: return new Star;
问题4:(1)在Canvas的析构函数里面一一被释放
(2)利:非常简单方便,可以直接创建对象和释放资源
弊:很有可能会误操作,比如忘记释放资源,或者在修改数据之前就把旧数据删除了等等,比较危险
4.实验任务4
应用的问题描述:这是玩具工厂对不同类型的玩具的管理系统,工厂需要创造不同类型的玩具(音乐类,故事类,算术类等),每种玩具都有基本属性比如名称,材质,适用年龄,以及特别的功能,系统统一管理玩具的创造,存储,信息展示等。
各类之间的关系及设计理由:StoryToy,SingToy,PinyinToy,MathToy都是基类Toy的派生类,因为这些派生类玩具都是Toy类玩具,是is-a的关系,同时每个种类玩具都有自己特别的功能,具有多态性,所有选用继承;‘
ToyFactory和vector类组合,因为我觉得玩具工厂与动态容器,是玩具工厂拥有一个装玩具的动态容器进行存储管理,而不是玩具工厂是一种容器,他们之间是has-a的关系,因此,选用了组合
然后仿照实验三任务,选用了指针进行管理。
Toy.h代码如下
1 #include<string> 2 #include<iostream> 3 4 using namespace std; 5 class Toy{ 6 protected: 7 string name; 8 string type; 9 string material; 10 float price; 11 string agerange; 12 public: 13 Toy(string n,string t,string m,float p,string age):name(n),type(t),material(m),price(p),agerange(age){} 14 virtual void Fuction()=0; 15 string get_name(){return name;} 16 string get_type(){return type;} 17 string get_material(){return material;} 18 float get_price(){return price;} 19 string get_agerange(){return agerange;} 20 virtual ~Toy()=default; 21 }; 22 23 class StoryToy:public Toy{ 24 private: 25 string storyname; 26 public: 27 StoryToy(string n,string t,string m,float p,string age,string stn):Toy(n,t,m,p,age),storyname(stn){} 28 void Fuction(){ 29 cout<<"【"<<name<<"】"<<"正在讲"<<"《"<<storyname<<"》的故事哦~"<<endl; 30 } 31 }; 32 33 class SingToy:public Toy{ 34 private: 35 string songname; 36 public: 37 SingToy(string n,string t,string m,float p,string age,string soname):Toy(n,t,m,p,age),songname(soname){} 38 void Fuction(){ 39 cout<<"【"<<name<<"】"<<"正在唱"<<"《"<<songname<<"》歌曲哦~"<<endl; 40 } 41 }; 42 43 44 45 class PinyinToy:public Toy{ 46 private: 47 string pinyin; 48 public: 49 PinyinToy(string n,string t,string m,float p,string age,string py):Toy(n,t,m,p,age),pinyin(py){} 50 void Fuction(){ 51 cout<<"【"<<name<<"】"<<"教拼音:"<<pinyin<<"跟我读哦~"<<endl; 52 } 53 }; 54 55 class MathToy:public Toy{ 56 private: 57 int num1,num2; 58 char operate; 59 public: 60 MathToy(string n,string t,string m,float p,string age,int n1,int n2,char op):Toy(n,t,m,p,age),num1(n1),num2(n2),operate(op){} 61 void Fuction(){ 62 cout<<"【"<<name<<"】"<<"正在运算:"<<num1<<operate<<num2<<"="; 63 switch(operate){ 64 case '+':cout<<num1+num2;break; 65 case '-':cout<<num1-num2;break; 66 case '*':cout<<num1*num2;break; 67 case '/': 68 if(num2==0){ 69 cout<<"不能运算哦,除数不能是零"; 70 }else{ 71 cout<<num1/num2; 72 } 73 break; 74 default: cout<<"没有这种运算符"; 75 } 76 cout<<endl; 77 } 78 };
ToyFactory.h代码如下
1 #pragma once 2 #include "Toy.h" 3 #include<vector> 4 #include<string> 5 class ToyFactory{ 6 private: 7 vector<Toy*> ToyList; 8 public: 9 ToyFactory()=default; 10 ~ToyFactory(); 11 void addToy(Toy* toy); 12 void showalltoys(); 13 void createToy(const string& name,const string& toytype,const string& material,float price,const string& agerange,const string& func,int num1=0,int num2=0,char op=0); 14 15 16 };
ToyFactory.cpp代码如下
1 #include"ToyFactory.h" 2 #include<iostream> 3 using namespace std; 4 void ToyFactory::addToy(Toy* toy){ 5 if(toy!=nullptr){ 6 ToyList.push_back(toy); 7 } 8 } 9 10 void ToyFactory::showalltoys(){ 11 std::cout<<"\n=====玩具工厂产品清单=====\n"; 12 for(size_t i=0;i<ToyList.size();i++){ 13 cout<<"["<<i+1<<"]"<<"产品信息:"<<endl 14 <<"名称:"<<ToyList[i]->get_name()<<endl 15 <<"类型:"<<ToyList[i]->get_type()<<endl 16 <<"材质:"<<ToyList[i]->get_material()<<endl 17 <<"价格:"<<ToyList[i]->get_price()<<"元"<<endl 18 <<"适用年龄:"<<ToyList[i]->get_agerange()<<"岁"<<endl 19 <<"功能:"; 20 ToyList[i]->Fuction(); 21 cout<<"------------------------\n"; 22 } 23 24 } 25 26 void ToyFactory::createToy(const string& name,const string& toytype,const string& material,float price,const string& agerange,const string& func,int num1,int num2,char op){ 27 Toy* toy=nullptr; 28 if(toytype=="story"){ 29 toy=new StoryToy(name,"故事类",material,price,agerange,func); 30 }else if(toytype=="sing"){ 31 toy=new SingToy(name,"唱歌类",material,price,agerange,func); 32 }else if(toytype=="pinyin"){ 33 toy=new PinyinToy(name,"拼音类",material,price,agerange,func); 34 }else if(toytype=="math"){ 35 toy=new MathToy(name,"算数类",material,price,agerange,num1,num2,op); 36 }else{ 37 cout<<"暂无该类型玩具"<<endl; 38 return; 39 } 40 addToy(toy); 41 } 42 43 ToyFactory::~ToyFactory(){ 44 for(Toy* toy:ToyList){ 45 delete toy; 46 } 47 ToyList.clear(); 48 }
test代码如下
1 #include "ToyFactory.h" 2 int main(){ 3 ToyFactory factory; 4 factory.createToy("读绘本的小熊","story","棉布",89.9,"4~7","白雪公主"); 5 factory.createToy("会唱歌的小鸟","sing","毛绒",69.9,"3~6","ABCD单词歌"); 6 factory.createToy("教拼音的鹦鹉","pinyin","软胶",169.9,"7~12","a、o、e"); 7 factory.createToy("会算数的小猴子","math","毛绒",199.9,"7~13","",3,4,'+'); 8 factory.showalltoys() ; 9 return 0; 10 }
运行结果如下

浙公网安备 33010602011771号