实验4
task 1
GradeCalc.h
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); 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::vector<int> grades; 24 std::array<int, 5> counts; 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.h" 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)","[60,70)","[70,80)","[80,90)","[90,100]" }; 89 90 for (int i = grade_range.size() - 1;i >= 0;--i) 91 std::cout << grade_range[i] << "\t:" << counts[i] << "人\t" << std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n"; 92 } 93 94 void GradeCalc::compute() { 95 if (grades.empty()) 96 return; 97 98 counts.fill(0); 99 rates.fill(0); 100 101 for (auto grade : grades) { 102 if (grade < 60) 103 ++counts[0]; 104 else if (grade < 70) 105 ++counts[1]; 106 else if (grade < 80) 107 ++counts[2]; 108 else if (grade < 90) 109 ++counts[3]; 110 else 111 ++counts[4]; 112 } 113 114 for (int i = 0;i < rates.size();++i) 115 rates[i] = counts[i] * 1.0 / grades.size(); 116 117 is_dirty = false; 118 }
demo1.cpp
1 #include<iostream> 2 #include<string> 3 #include"GradeCalc.h" 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 int main() { 22 test(); 23 }

Q1:
std::string course_name:存储课程名称,字符串对象被GradeCale包含。
std::vector<int> grades:动态存储成绩,支持成绩增删与遍历,动态数组对象被GradeCalc包含。
std::array<int,5> counts:统计各个分段的人数,固定大小数组对象被GradeCalc包含。
std::arrar<double,5> rates:储存各个分数段人员的占比,固定大小数组对象被GradeCalc包含。
Q2:不合法。std::vector<int> grades是GradeCalc中的私有成员,外部的push_back无法调用。
Q3:
(1)compute被调用1次,只有第一次is_dirty为true的时候被调用;is_dirty用于标识成绩是否发生变化,当成绩被修改时,is_dirty为true,否则为false。
(2)不需要。update_grade(index, new_grade)的功能时变更成绩,改变is_dirty的值,compute只是通过is_dirty判断要不要调用。
Q4:
1 double GradeCalc::middle() const { 2 if (grades.empty()) { 3 return 0; 4 } 5 6 std::vector<int> t_grades = grades; 7 std::sort(t_grades.begin(), t_grades.end()); 8 9 int size = t_grades.size(); 10 if (size % 2 == 1) 11 return t_grades[size / 2]; 12 else 13 return (t_grades[size / 2 - 1] + t_grades[size / 2]) / 2; 14 }
Q5:不能去掉。若第一次调用input输入成绩,然后再调用info,此时is_dirty为true,需要调用compute,counts和rates就有了各自数组对应的值;第二次调用input新增成绩,再调用info,因为新增了数据is_dirty还是true,调用compute,此时counts和rates没有被置零,还是保留了第一次的数据和第二次的新数据叠加,会得出错误数据。
Q6:
(1)没有影响。grades.reverse(n)的作用是提前为vector分配n个元素的内存空间。
(2)有影响。如果n较大时,没有提前分配内存空间,会增加数据分配和拷贝的开销。
task 2
GradeCalc.h
1 #pragma once 2 3 #include<vector> 4 #include<array> 5 #include<string> 6 7 class GradeCalc:private std::vector<int>{ 8 public: 9 GradeCalc(const std::string& cname); 10 void input(int 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; 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 10 #include "GradeCalc.h" 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 this->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 this->push_back(grade); 36 ++i; 37 } 38 39 is_dirty = true; 40 } 41 42 void GradeCalc::output() const { 43 for (auto grade : *this) 44 std::cout << grade << ' '; 45 std::cout << std::endl; 46 } 47 48 void GradeCalc::sort(bool ascending) { 49 if (ascending) 50 std::sort(this->begin(), this->end()); 51 else 52 std::sort(this->begin(), this->end(), std::greater<int>()); 53 } 54 55 int GradeCalc::min() const { 56 if (this->empty()) 57 return -1; 58 59 auto it = std::min_element(this->begin(), this->end()); 60 return *it; 61 } 62 63 int GradeCalc::max() const { 64 if (this->empty()) 65 return -1; 66 67 auto it = std::max_element(this->begin(), this->end()); 68 return *it; 69 } 70 71 double GradeCalc::average() const { 72 if (this->empty()) 73 return 0.0; 74 75 double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->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)","[60,70)","[70,80)","[80,90)","[90,100]" }; 89 90 for (int i = grade_range.size() - 1;i >= 0;--i) 91 std::cout << grade_range[i] << "\t:" << counts[i] << "人\t" << std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n"; 92 } 93 94 void GradeCalc::compute() { 95 if (this->empty()) 96 return; 97 98 counts.fill(0); 99 rates.fill(0); 100 101 for (auto grade : *this) { 102 if (grade < 60) 103 ++counts[0]; 104 else if (grade < 70) 105 ++counts[1]; 106 else if (grade < 80) 107 ++counts[2]; 108 else if (grade < 90) 109 ++counts[3]; 110 else 111 ++counts[4]; 112 } 113 114 for (int i = 0;i < rates.size();++i) 115 rates[i] = counts[i] * 1.0 / this->size(); 116 117 is_dirty = false; 118 }
demo2.cpp
1 #include<iostream> 2 #include<string> 3 #include"GradeCalc.h" 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 int main() { 22 test(); 23 }

Q1:class GradeCalc:private std::vector<int>
Q2:不会自动成为接口,代码编译无法通过。push.back是vector的公有成员,而基类vector是私有继承,仅在GradeCalc内部可以访问,test作为外部代码无法访问。
Q3:
组合方式:通过类的私有成员变量grades访问数据。仅类内部的接口可以直接访问,外部代码无法直接访问。
继承方式:通过派生类自身对象访问数据。私有继承下基类接口仅内部可以使用,外部不可用。
Q4:组合方式更适。成绩计算主要功能是管理与统计成绩数据,组合方式将vector作为成绩存储组件,耦合度低、封装性更优,通过接口的修改便于控制成绩的访问逻辑,也可以根据需求替换存储容器;而私有继承派生类与vector强绑定,耦合度高,容易因继承特性隐含接口暴露风险。
task 3
Graph.h
1 #pragma once 2 3 #include<string> 4 #include<vector> 5 6 enum class GraphType{circle,triangle,rectangle}; 7 8 class Graph{ 9 public: 10 virtual void draw() {}; 11 virtual ~Graph() {}; 12 }; 13 14 class Circle :public Graph { 15 public: 16 void draw(); 17 }; 18 19 class Triangle :public Graph { 20 public: 21 void draw(); 22 }; 23 24 class Rectangle :public Graph { 25 public: 26 void draw(); 27 }; 28 29 class Canvas { 30 public: 31 void add(const std::string& type); 32 void paint() const; 33 ~Canvas(); 34 35 private: 36 std::vector<Graph*> graphs; 37 }; 38 39 GraphType str_to_GraphType(const std::string& s); 40 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.h" 7 8 void Circle::draw() { std::cout << "draw a circle...\n"; } 9 10 void Triangle::draw() { std::cout << "draw a triangle...\n"; } 11 12 void Rectangle::draw() { std::cout << "draw a rectangle...\n"; } 13 14 void Canvas::add(const std::string& type) { 15 Graph* g = make_graph(type); 16 if (g) 17 graphs.push_back(g); 18 } 19 void Canvas::paint() const { 20 for (Graph* g : graphs) 21 g->draw(); 22 } 23 24 Canvas::~Canvas() { 25 for (Graph* g : graphs) 26 delete g; 27 } 28 29 GraphType str_to_GraphType(const std::string& s) { 30 std::string t = s; 31 std::transform(s.begin(), s.end(), t.begin(), [](unsigned char c) {return std::tolower(c);}); 32 33 if (t == "circle") 34 return GraphType::circle; 35 36 if (t == "triangle") 37 return GraphType::triangle; 38 39 if (t == "rectangle") 40 return GraphType::rectangle; 41 42 return GraphType::circle; 43 } 44 45 Graph* make_graph(const std::string& type) { 46 switch (str_to_GraphType(type)) { 47 case GraphType::circle: return new Circle; 48 case GraphType::triangle : return new Triangle; 49 case GraphType::rectangle: return new Rectangle; 50 default:return nullptr; 51 } 52 }
demo3.cpp
1 #include<string> 2 #include"Graph.h" 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 }

Q1:
(1)组合关系:std::vector<Graph*> graphs;用于动态存储和管理所有图形对象的指针。
(2)继承关系:class Circle:public Graph; class Triangle:public Graph; class Rectangle:public Graph;
Q2:
(1)若未声明则会运行失败。g->draw()会调用基类Graph的draw()函数,最终没有任何输出。
(2)若失去指针,vector<Graph>存储的式Graph类的实体对象,无法存储派生类对象(Circle、Triangle和Rectangle),最终g->draw()只能调用基类的空实现,无法绘制不同图形。
(3)若未声明成虚函数,delete g会调用基类的析构函数不会调用派生类的析构函数,使资源无法释放,导致内存泄露。
Q3:
enum class GraphType{circle , triangle , rectangle , star };
class Star: public Graph{
public:
void draw();
}
str_to_GraphType辅助函数中添加:if( t == "star" ) return GraphType:: star;
make_graph辅助函数中(switch下)添加:case GraphType::star: return new Star;
Q4:
(1)make_graph的返回对象在Canvas的析构函数中被释放。
(2)优点:基类指针可以指向任意派生类,并且可以触发多态调用;原始指针可以手动控制对象的创建和释放,集中管理对象的生命周期。
缺点:需要手动匹配new与delete,如果忘记将导致内存泄漏。
task 4
Toy.h
1 #pragma once 2 #include<string> 3 #include<vector> 4 5 enum class ToyType{edog,robot,care_rabbit}; 6 7 class Toy { 8 public: 9 Toy(const std::string& name,const std::string& type,double price); 10 virtual void info() ; 11 virtual ~Toy() = default; 12 13 private: 14 std::string NAME; 15 std::string TYPE; 16 double PRICE; 17 }; 18 19 class Edog :public Toy { 20 public: 21 Edog(const std::string& name, const std::string& type, double price); 22 void info(); 23 }; 24 25 class Robot :public Toy { 26 public: 27 Robot(const std::string& name, const std::string& type, double price); 28 void info(); 29 }; 30 31 class Care_rabbit :public Toy { 32 public: 33 Care_rabbit(const std::string& name, const std::string& type, double price); 34 void info(); 35 }; 36 37 class ToyFactory { 38 public: 39 void add(const std::string &name,const std::string& type,double price); 40 void showinfo() const; 41 ~ToyFactory(); 42 43 private: 44 std::vector<Toy*> toys; 45 }; 46 47 ToyType str_to_ToyType(const std::string& s); 48 Toy* make_toy(const std::string& name, const std::string& type, double price);
Toy.cpp
1 #include "Toy.h" 2 #include<iostream> 3 #include<string> 4 #include<cctype> 5 #include<algorithm> 6 7 Toy::Toy(const std::string& name, const std::string& type, double price) :NAME{ name }, TYPE{ type }, PRICE{ price }{} 8 9 void Toy::info() { 10 std::cout << "名字:" << NAME << std::endl; 11 std::cout << "类型:" << TYPE << std::endl; 12 std::cout << "价格:" << PRICE << std::endl; 13 } 14 15 Edog::Edog(const std::string& name, const std::string& type, double price):Toy(name,type,price) {} 16 17 void Edog::info() { 18 Toy::info(); 19 std::cout << "特异功能:摇头、摆尾,并发出“汪汪”的叫声" << std::endl; 20 std::cout<<std::endl; 21 } 22 23 Robot::Robot(const std::string& name, const std::string& type, double price) :Toy(name, type, price) {} 24 25 void Robot::info() { 26 Toy::info(); 27 std::cout << "特异功能:与它对话发,它发出语音:“你好,你想问我什么问题呢?”" << std::endl; 28 std::cout << std::endl; 29 } 30 31 Care_rabbit::Care_rabbit(const std::string& name, const std::string& type, double price) :Toy(name, type, price) {} 32 33 void Care_rabbit::info() { 34 Toy::info(); 35 std::cout << "特异功能:放出轻柔的音乐,陪伴入睡" << std::endl; 36 std::cout << std::endl; 37 } 38 39 void ToyFactory::add(const std::string& name, const std::string& type, double price) { 40 Toy* g = make_toy(name,type,price); 41 if (g) 42 toys.push_back(g); 43 } 44 45 void ToyFactory::showinfo() const { 46 for (Toy* g : toys) 47 g->info(); 48 } 49 50 ToyFactory::~ToyFactory() { 51 for (Toy* g : toys) 52 delete g; 53 } 54 55 ToyType str_to_ToyType(const std::string& s) { 56 std::string t = s; 57 std::transform(s.begin(), s.end(), t.begin(),[](unsigned char c) { return std::tolower(c);}); 58 59 if (t == "edog") 60 return ToyType::edog; 61 62 if (t == "robot") 63 return ToyType::robot; 64 65 if (t == "care_rabbit") 66 return ToyType::care_rabbit; 67 68 return ToyType::edog; 69 } 70 71 Toy* make_toy(const std::string &name,const std::string& type,double price) { 72 std::string t_name = name; 73 std::string t_type = type; 74 75 switch (str_to_ToyType(type)) { 76 case ToyType::edog: return new Edog(t_name,t_type,price); 77 case ToyType::robot: return new Robot(t_name, t_type, price); 78 case ToyType::care_rabbit: return new Care_rabbit(t_name, t_type, price); 79 default: return nullptr; 80 } 81 }
demo4.cpp
1 #include"Toy.h" 2 #include<iostream> 3 4 void test() { 5 ToyFactory toyfactory; 6 7 toyfactory.add("puppy", "edog", 120.0); 8 toyfactory.add("罗伯特", "robot", 398.8); 9 toyfactory.add("兔兔", "care_rabbit", 199.9); 10 toyfactory.showinfo(); 11 } 12 13 int main() { 14 test(); 15 }
这个代码里有不同类型的电子毛绒玩具(电子狗、智能机器人、陪伴兔),通过统一接口展示所有玩具的基础信息:名称、类型、价格和专属特异功能。
继承关系:Edog/Robot/Care_rabbit都是派生类,基类是Toy。
组合关系:ToyFactory是整体,std::vector<Toy*> toys是部分。
浙公网安备 33010602011771号