实验四
任务一:
GradeCalc.hpp
1 #pragma once 2 #include<array> 3 #include<string> 4 #include<vector> 5 class GradeCalc 6 { 7 public: 8 GradeCalc(const std::string &cname); 9 void input (int n); 10 void output() const; 11 void sort(bool ascending=false); 12 int min() const; 13 int max() const; 14 double average() const; 15 void info(); 16 private: 17 void compute(); 18 private: 19 std::string course_name; 20 std::vector<int>grades; 21 std::array<int, 5> counts; 22 std::array<double, 5> rates; 23 bool is_dirty; 24 }; 25
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 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true} 11 { 12 counts.fill(0); 13 rates.fill(0); 14 } 15 void GradeCalc::input(int n) 16 { 17 if(n<0) 18 { 19 std::cerr<<"无效输入!人数不能为负数\n"; 20 std::exit(1); 21 } 22 grades.reserve(n); 23 int grade; 24 for(int i=0;i<n;) 25 { 26 std::cin>>grade; 27 if(grade<0||grade>100) 28 { 29 std::cerr<<"无效输入!分数须在[0,100]\n"; 30 continue; 31 } 32 grades.push_back(grade); 33 ++i; 34 } 35 is_dirty=true; 36 } 37 void GradeCalc::output() const 38 { 39 for(auto grade:grades) 40 std::cout<<grade<<' '; 41 std::cout<<std::endl; 42 } 43 44 void GradeCalc::sort(bool ascending) 45 { 46 if(ascending) 47 std::sort(grades.begin(),grades.end()); 48 else 49 std::sort(grades.begin(),grades.end(),std::greater<int>()); 50 } 51 int GradeCalc::min() const 52 { 53 if(grades.empty()) 54 return -1; 55 auto it=std::min_element(grades.begin(),grades.end()); 56 return*it; 57 } 58 int GradeCalc::max() const 59 { 60 if(grades.empty()) 61 return -1; 62 auto it=std::max_element(grades.begin(),grades.end()); 63 return *it; 64 } 65 double GradeCalc::average() const 66 { 67 if(grades.empty()) 68 return 0.0; 69 double avg=std::accumulate(grades.begin(),grades.end(),0.0)/grades.size(); 70 return avg; 71 } 72 void::GradeCalc::info() 73 { 74 if(is_dirty) 75 compute(); 76 std::cout<<"课程名称:\t"<<course_name<<std::endl; 77 std::cout<<"平均分:\t"<<std::fixed<<std::setprecision(2)<<average()<<std::endl; 78 std::cout<<"最高分:\t" << max() << std::endl; 79 std::cout<<"最低分:\t" << min() << std::endl; 80 const std::array<std::string, 5>grade_range{"[0 , 60)","[60, 70)","[70, 80)","[80, 90)","[90, 100]"}; 81 for(int i=grade_range.size()-1;i>=0;--i) 82 std::cout<<grade_range[i]<<"\t :"<<counts[i]<<"人\t"<<std::fixed<<std::setprecision(2)<<rates[i]*100<<"%\n"; 83 } 84 void GradeCalc::compute() 85 { 86 if(grades.empty()) 87 return; 88 counts.fill(0); 89 rates.fill(0.0); 90 for(auto grade:grades) 91 { 92 if(grade<60) 93 ++counts[0]; 94 else if(grade<70) 95 ++counts[1]; 96 else if(grade<80) 97 ++counts[2]; 98 else if(grade<90) 99 ++counts[3]; 100 else 101 ++counts[4]; 102 } 103 for(int i=0;i<rates.size();++i) 104 rates[i]=counts[i]*1.0/grades.size(); 105 is_dirty=false; 106 }
demo1.cpp
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 void test() 5 { 6 GradeCalc c1("OOP"); 7 std::cout << "录入成绩:\n"; 8 c1.input(5); 9 std::cout << "输出成绩:\n"; 10 c1.output(); 11 std::cout << "排序后成绩:\n"; 12 c1.sort(); c1.output(); 13 std::cout << "*************成绩统计信息*************\n"; 14 c1.info(); 15 } 16 int main() 17 { 18 test(); 19 }
运行结果截图

问题1:组合关系识别
GradeCalc 类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
答:std::string course_name,功能是存储课程名
std::vector<int> grades,功能是存储成绩
std::array<int, 5> counts,功能是统计人数
std::array<double, 5> rates,存各分段百分率
问题2:接口暴露理解
当前继承方式下,基类vector<int> 的接口会自动成为GradeCalc 的接口吗?
答:不会
如在test模块中这样使用,是否合法?如不合法,解释原因。
答:不合法,因为GradeCalc与vector<int>是组合关系,不是继承关系,不可以直接调用push_back.
问题3:架构设计分析
当前设计方案中,compute 在info 模块中调用:
(1)连续打印3次统计信息,compute 会被调用几次?标记is_dirty 起到什么作用?
答:调用1次,避免重复做一样的计算
(2)如新增update_grade(index, new_grade) ,这种设计需要更改compute 调用位置吗?简洁说明理由。
答:要,因为更改了数据,要重新计算
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
答:在sort()中增加伪代码:
int n=grades.size();
if(n%2)
min=grades[n/2];
else
min=(grades[n/2]+grades[n/2-1])/2.0
问题5:数据状态管理
GradeCalc 和compute 中都包含代码:counts.fill(0); rates.fill(0); 。
compute 中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
答:不能,在多次调用input()情况下会发生错误
问题6:内存管理理解
input 模块中代码grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
答:不会有影响
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。
答:会。reserve()是预分配内存,没有的话,当vector容量不足时需重新分配,降低效率。
任务二
GradeCalc2.hpp
1 #pragma once 2 #include<array> 3 #include<string> 4 #include<vector> 5 class GradeCalc:private std::vector<int> 6 { 7 public: 8 GradeCalc(const std::string &cname); 9 void input(int n); 10 void output() const; 11 void sort(bool ascending=false); 12 int min() const; 13 int max() const; 14 double average() const; 15 void info(); 16 private: 17 void compute(); 18 private: 19 std::string course_name; 20 std::array<int, 5> counts; 21 std::array<double, 5> rates; 22 bool is_dirty; 23 };
GradeCalc2.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 "GradeCalc2.hpp" 10 GradeCalc ::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true} 11 { 12 counts.fill(0); 13 rates.fill(0); 14 } 15 void GradeCalc::input(int n) 16 { 17 if(n<0) 18 { 19 std::cerr<<"无效输入! 人数不能为负数\n"; 20 return; 21 } 22 this->reserve(n); 23 int grade; 24 for(int i=0;i<n;) 25 { 26 std::cin>>grade; 27 if(grade<0||grade>100) 28 { 29 std::cerr<< "无效输入! 分数须在[0,100]\n"; 30 continue; 31 } 32 this->push_back(grade); 33 ++i; 34 } 35 is_dirty=true; 36 } 37 void GradeCalc::output() const 38 { 39 for(auto grade:*this) 40 std::cout<<grade<<' '; 41 std::cout<<std::endl; 42 } 43 void GradeCalc::sort(bool ascending) 44 { 45 if(ascending) 46 std::sort(this->begin(),this->end()); 47 else 48 std::sort(this->begin(),this->end(),std::greater<int>()); 49 } 50 int GradeCalc::min() const 51 { 52 if(this->empty()) 53 return -1; 54 return *std::min_element(this->begin(),this->end()); 55 } 56 int GradeCalc::max() const 57 { 58 if(this->empty()) 59 return -1; 60 return *std::max_element(this->begin(),this->end()); 61 } 62 double GradeCalc::average() const 63 { 64 if(this->empty()) 65 return 0.0; 66 double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size(); 67 return avg; 68 } 69 void GradeCalc::info() 70 { 71 if(is_dirty) 72 compute(); 73 74 std::cout << "课程名称:\t" << course_name << std::endl; 75 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 76 std::cout << "最高分:\t" << max() << std::endl; 77 std::cout << "最低分:\t" << min() << std::endl; 78 const std::array<std::string, 5> grade_range{"[0 , 60)","[60, 70)","[70, 80)","[80, 90)","[90,100]"}; 79 for(int i = grade_range.size()-1; i >= 0; --i) 80 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"<< std::fixed << std::setprecision(2) <<rates[i]*100 << "%\n"; 81 } 82 void GradeCalc::compute() 83 { 84 if(this->empty()) 85 return; 86 counts.fill(0); 87 rates.fill(0); 88 for(int grade: *this) 89 { 90 if(grade < 60) 91 ++counts[0]; 92 else if (grade < 70) 93 ++counts[1]; 94 else if (grade < 80) 95 ++counts[2]; 96 else if (grade < 90) 97 ++counts[3]; 98 else 99 ++counts[4]; 100 } 101 for(int i=0;i<rates.size();++i) 102 rates[i]=counts[i]*1.0/this->size(); 103 is_dirty=false; 104 }
demo2.cpp
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 void test() { 5 GradeCalc c1("OOP"); 6 std::cout << "录入成绩:\n"; 7 c1.input(5); 8 std::cout << "输出成绩:\n"; 9 c1.output(); 10 std::cout << "排序后成绩:\n"; 11 c1.sort(); c1.output(); 12 std::cout << "*************成绩统计信息*************\n"; 13 c1.info(); 14 } 15 int main() { 16 test(); 17 } 18
运行结果截图

问题1:继承关系识别
写出GradeCalc 类声明体现"继承"关系的完整代码行。
答:class GradeCalc:private std::vector<int>
问题2:接口暴露理解
当前继承方式下,基类vector<int> 的接口会自动成为GradeCalc 的接口吗?
答:不会,因为是私有继承
如在test模块中这样用,能否编译通过?用一句话解释原因。
答:不能,私有继承vector,push_back()在GradeCalc中变为private 外部无法访问
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访
问接口差异。
答:组合方式通过grades访问数据,继承方式通过this指针访问
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
答:组合更适合。组合方式只需修改内部实现,不影响公共接口,继承更加复杂,影响接口
任务三
Graph.hpp
1 #pragma once 2 #include<string> 3 #include<vector> 4 enum class GraphType{circle,triangle,rectangle}; 5 class Graph 6 { 7 public: 8 virtual void draw(){} 9 virtual ~Graph()=default; 10 }; 11 class Circle:public Graph 12 { 13 public: 14 void draw(); 15 }; 16 class Triangle : public Graph 17 { 18 public: 19 void draw(); 20 }; 21 class Rectangle:public Graph 22 { 23 public: 24 void draw(); 25 }; 26 class Canvas 27 { 28 public: 29 void add(const std::string& type); 30 void paint() const; 31 ~Canvas(); 32 private: 33 std::vector<Graph*>graphs; 34 }; 35 GraphType str_to_GraphType(const std::string& s); 36 Graph* make_graph(const std::string& type);
Graph.cpp
1 #include <algorithm> 2 #include <cctype> 3 #include <iostream> 4 #include <string> 5 #include "Graph.hpp" 6 void Circle::draw() 7 { 8 std::cout<<"draw a circle...\n"; 9 } 10 void Triangle::draw() 11 { 12 std::cout<<"draw a triangle...\n"; 13 } 14 void Rectangle::draw() 15 { 16 std::cout<<"draw a rectangle...\n"; 17 } 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 void Canvas::paint() const 25 { 26 for(Graph* g:graphs) 27 g->draw(); 28 } 29 Canvas::~Canvas() 30 { 31 for(Graph* g:graphs) 32 delete g; 33 } 34 GraphType str_to_GraphType(const std::string& s) 35 { 36 std::string t=s; 37 std::transform(s.begin(),s.end(),t.begin(),[](unsigned char c){return std::tolower(c);}); 38 if(t=="circle") 39 return GraphType::circle; 40 if (t == "triangle") 41 return GraphType::triangle; 42 if (t == "rectangle") 43 return GraphType::rectangle; 44 return GraphType::circle; 45 } 46 Graph* make_graph(const std::string& type) 47 { 48 switch(str_to_GraphType(type)) 49 { 50 case GraphType::circle: 51 return new Circle; 52 case GraphType::triangle: 53 return new Triangle; 54 case GraphType::rectangle: 55 return new Rectangle; 56 default:return nullptr; 57 } 58 }
demo3.cpp
1 #include <string> 2 #include "Graph.hpp" 3 void test() { 4 Canvas canvas; 5 canvas.add("circle"); 6 canvas.add("triangle"); 7 canvas.add("rectangle"); 8 canvas.paint(); 9 } 10 int main() 11 { 12 test(); 13 }
运行结果截图

问题1:对象关系识别
(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。
答:std::vector<Graph*>graphs;功能存储多个图形对象的指针
(2)写出Graph.hpp中体现"继承"关系的类声明代码行。
答:class Circle : public Graph
class Triangle : public Graph
class Rectangle : public Graph
问题2:多态机制观察
(1)Graph 中的draw 若未声明成虚函数,Canvas::paint() 中g->draw() 运行结果会有何不同?
答:Canvas::paint() 中g->draw() 调用基类的draw(),而不是实际派生类,会导致结果相同
(2)若Canvas 类std::vector<Graph*> 改成std::vector<Graph> ,会出现什么问题?
答:破坏多态,draw()调用都输出为空
(3)若~Graph() 未声明成虚函数,会带来什么问题?
答:删除派生类对象时,调用基类Graph的析构函数,而不会调用派生类的析构函数,~Graph() 未声明成虚函数时无法释放派生类对象
问题3:扩展性思考
若要新增星形Star ,需在哪些文件做哪些改动?逐一列出。
答:在GraphType枚举中添加新类型star
添加Star类声明
添加Star::draw()
修改str_to_GraphType函数,添加对"star"字符串的处理
修改make_graph函数,添加star分支
问题4:资源管理
观察make_graph 函数和Canvas 析构函数:
(1)make_graph 返回的对象在什么地方被释放?
答:Canvas的析构函数中被释放
(2)使用原始指针管理内存有何利弊
答:利:可以直接控制:可以精确控制内存分配和释放时机
弊端:容易产生内存泄漏,可能对同一指针多次调用delete
Toy.hpp
1 #pragma once 2 #include<string> 3 #include<vector> 4 enum class Material {wool,plastic,metal}; 5 class Toy 6 { 7 public: 8 Toy(const std::string& name, Material material, int age); 9 std::string getName() const ; 10 Material getMaterial() const; 11 int getRecage() const; 12 std::string getMaterialString() const; 13 virtual void display() const; 14 virtual void spefunction()=default; 15 virtual ~Toy()=default; 16 private: 17 std::string toy_name; 18 Material material; 19 int recage; 20 }; 21 class TeddyBear : public Toy 22 { 23 public: 24 TeddyBear(const std::string& name); 25 void spefunction() ; 26 }; 27 28 class Car: public Toy 29 { 30 public: 31 Car(const std::string& name); 32 void spefunction() ; 33 }; 34 35 class Spinner : public Toy 36 { 37 public: 38 Spinner(const std::string& name); 39 void spefunction() ; 40 }; 41 class ToyFactory 42 { 43 public: 44 void add(const std::string& toy_name); 45 void show() const; 46 ~ToyFactory(); 47 private: 48 std::vector<Toy*> toys; 49 }; 50 Toy* make_toy(const std::string& toy_name);
toyfactory.cpp
1 #include<algorithm> 2 #include<cctype> 3 #include<iostream> 4 #include<string> 5 #include"toy.hpp" 6 Toy::Toy(const std::string& name, Material material, int age): toy_name{name},material{material}, recage{age} {} 7 std::string Toy:: getName() const 8 { 9 return toy_name; 10 } 11 Material Toy::getMaterial() const 12 { 13 return material; 14 } 15 std::string Toy::getMaterialString() const 16 { 17 switch(material) { 18 case Material::wool: return "毛绒"; 19 case Material::plastic: return "塑料"; 20 case Material::metal: return "金属"; 21 22 } 23 } 24 int Toy::getRecage() const 25 { 26 return recage; 27 } 28 void Toy::display() const 29 { 30 std::cout << "玩具名称: " << toy_name << ", 材质:" << getMaterialString() << ", 推荐年龄: " << recage << "+" << std::endl; 31 } 32 TeddyBear::TeddyBear(const std::string& name) : Toy(name, Material::wool, 3) {} 33 void TeddyBear::spefunction() 34 { 35 std::cout << "特异功能: 给予陪伴!\n"; 36 } 37 Car::Car(const std::string& name) : Toy(name, Material::plastic, 6) {} 38 void Car::spefunction() 39 { 40 std::cout << "特异功能:遥控行驶!\n"; 41 } 42 Spinner::Spinner(const std::string& name) : Toy(name, Material::metal,10) {} 43 void Spinner::spefunction() 44 { 45 std::cout << "特异功能:缓解压力!\n"; 46 } 47 Toy* make_toy(const std::string& toy_name) 48 { 49 std::string s=toy_name; 50 if(s=="TeddyBear") 51 return new TeddyBear("泰迪熊"); 52 else if(s=="Car") 53 return new Car("小汽车"); 54 else if(s=="Spinner") 55 return new Spinner("指尖陀螺"); 56 else 57 return nullptr; 58 } 59 void ToyFactory::add(const std::string& toy_name) 60 { 61 Toy* t = make_toy(toy_name); 62 if (t) 63 64 toys.push_back(t); 65 } 66 void ToyFactory::show() const 67 { 68 for (Toy* t : toys) 69 { 70 t->display(); 71 t->spefunction(); 72 std::cout <<std::endl; 73 } 74 } 75 ToyFactory::~ToyFactory() 76 { 77 for (Toy* t: toys) 78 delete t; 79 }
demo4..cpp
1 #include<iostream> 2 #include <string> 3 #include "Toy.hpp" 4 void test() 5 { 6 ToyFactory toys; 7 toys.add("TeddyBear"); 8 toys.add("Car"); 9 toys.add("Spinner"); 10 toys.show(); 11 } 12 int main() 13 { 14 std::cout<<" *************玩具工厂信息*************\n"; 15 test(); 16 }
运行结果截图

场景描述
答:记录各种玩具的基本信息(名称、材质、适用年龄)
展示不同玩具的特有功能
可以进行添加玩具
陈述各类之间的关系(继承、组合等)及设计理由
答:继承关系:class TeddyBear : public Toy
class Car : public Toy
class Spinner : public Toy可以避免重复代码
组合关系:std::vector<Toy*> toys;
运用Toy* make_toy(const std::string& toy_name);所有玩具都通过统一接口创建,新 增玩具只需修改工厂函数
枚举材料 enum class Material {wool, plastic, metal};统一集中材料类型
纯虚函数:spefunction(),~Toy()在不同派生类中有不同实现

浙公网安备 33010602011771号