实验4
实验任务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 }
运行测试结果:

std::string course_name; // 课程名
组合了一个字符串对象,用来记录课程名。
std::vector<int> grades; // 课程成绩
组合了一个int类型的vector对象用来记录这个课程的成绩。
std::array<int, 5> counts; // 保存各分数段人数([0, 60), [60, 70), [70, 80), [80, 90), [90, 100]
组合了一个int类型的长度为5的一维数组,用来记录各分段的人数。
std::array<double, 5> rates; // 保存各分数段人数占比
组合了一个double类型的长度为5的一维数组,用来记录各分段的人数占比。
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 #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 #pragma once 2 3 #include<string> 4 5 #include<vector> 6 7 enum class ToyType {DancingToy, SingingToy, ReadingToy}; 8 9 //基类Toy定义 10 class Toy { 11 public: 12 //构造函数 13 Toy(const std::string &name , const ToyType &type):name{name},type{type}{} 14 //析构函数 15 virtual ~Toy()=default; 16 //公开接口展示信息 17 void info(); 18 //特异功能 19 virtual void specialability(){} 20 21 private: 22 const std::string name; 23 const ToyType type; 24 }; 25 26 //舞蹈玩具定义 27 class DancingToy :public Toy { 28 public: 29 DancingToy(const ToyType type , const std::string& name = "bear") : Toy(name, type) 30 { 31 dance_music = { "Gee", "不潮不用花钱", "明月几时有" }; 32 } 33 void specialability(); 34 private: 35 std::vector<std::string> dance_music; 36 }; 37 38 //唱歌玩具定义 39 class SingingToy :public Toy { 40 public: 41 SingingToy(const ToyType type , const std::string& name = "dolphin") : Toy(name, type) 42 { 43 songs = { "Love story", "Remember us this way", "Love me harder" }; 44 } 45 void specialability(); 46 private: 47 std::vector<std::string> songs; 48 }; 49 50 //读书玩具定义 51 class ReadingToy :public Toy { 52 public: 53 ReadingToy(const ToyType type , const std::string& name = "monkey") : Toy(name, type) 54 { 55 books = { "小王子","白雪公主","勇者斗恶龙" }; 56 } 57 void specialability(); 58 private: 59 std::vector<std::string> books; 60 }; 61 62 //玩具工厂定义 63 class ToyFactory { 64 public: 65 void add(const std::string& type); //根据字符串添加玩具 66 void show() const; //展示所有玩具的信息 67 void ableshow() const; //展示所有玩具的特殊功能 68 ~ToyFactory(); 69 70 private: 71 std::vector<Toy*> toys; 72 }; 73 74 ToyType str_to_ToyType(const std::string& s); //字符串转枚举类型 75 Toy* make_toy(const std::string& type); //创建玩具时,返回堆对象指针
Toy.cpp源代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <cctype> 4 #include <string> 5 6 #include"Toy.hpp" 7 8 // 工具函数实现 9 // 字符串 → 枚举转换 10 ToyType str_to_ToyType(const std::string& s) { 11 std::string t = s; 12 13 //转换成小写 14 std::transform(s.begin(), s.end(), t.begin(), 15 [](unsigned char c) { return std::tolower(c); }); 16 17 if (t == "dancingtoy") 18 return ToyType::DancingToy; 19 20 if (t == "singingtoy") 21 return ToyType::SingingToy; 22 23 if (t == "readingtoy") 24 return ToyType::ReadingToy; 25 26 return ToyType::DancingToy; // 缺省返回 27 } 28 29 // 创建图形,返回堆对象指针 30 Toy* make_toy(const std::string& type) { 31 32 ToyType t = str_to_ToyType(type); 33 34 switch (t) { 35 case ToyType::DancingToy: return new DancingToy(t); 36 case ToyType::SingingToy: return new SingingToy(t); 37 case ToyType::ReadingToy: return new ReadingToy(t); 38 default: return nullptr; 39 } 40 } 41 42 //添加玩具 43 void ToyFactory::add(const std::string& type) 44 { 45 Toy* t = make_toy(type); 46 47 if (t) 48 { 49 toys.push_back(t); 50 } 51 } 52 53 //展示玩具信息 54 void Toy::info() 55 { 56 std::cout << name << std::endl; 57 } 58 59 //特异功能 60 void DancingToy::specialability() 61 { 62 std::cout << "bear会跳"; 63 for (auto i : dance_music) 64 { 65 std::cout << i << " "; 66 } 67 std::cout << std::endl; 68 } 69 70 void SingingToy::specialability() 71 { 72 std::cout << "dolphin会唱"; 73 for (auto i : songs) 74 { 75 std::cout << i << " "; 76 } 77 std::cout << std::endl; 78 } 79 80 void ReadingToy::specialability() 81 { 82 std::cout << "monkey会读"; 83 for (auto i : books) 84 { 85 std::cout << i << " "; 86 } 87 std::cout << std::endl; 88 } 89 90 //展示工厂玩具 91 void ToyFactory::show() const 92 { 93 for (auto i : toys) 94 { 95 i->info(); 96 } 97 } 98 99 void ToyFactory::ableshow() const 100 { 101 for (auto i : toys) 102 { 103 i->specialability(); 104 } 105 } 106 107 ToyFactory::~ToyFactory() 108 { 109 for (auto i : toys) 110 { 111 delete i; 112 } 113 }
demo4.cpp源代码:
1 #include "Toy.hpp" 2 #include <string> 3 #include <iostream> 4 5 void test() 6 { 7 ToyFactory t; 8 9 std::cout << "共有以下玩具" << std::endl; 10 t.add("DancingToy"); 11 t.add("SingingToy"); 12 t.add("ReadingToy"); 13 14 t.show(); 15 t.ableshow(); 16 } 17 18 int main() 19 { 20 test(); 21 22 return 0; 23 }
设计思路:
我的设计思路大致与实验任务3类似,我将玩具名称和类型作为基类的private成员,因此派生类无法直接使用,所以我添加了一个公开的info接口可以调用私有成员,关于特异功能的设计,我使用了虚函数同时在每个玩具内部添加了私有成员vector来存储他们能表演的歌曲/舞蹈/书,通过虚函数的设计和堆对象指针的设计,只需要通过简短的代码就可以调用所有玩具的功能。关于类的设计,我既使用了组合也使用了继承,每个特别的玩具类都继承自基类Toy,每个派生类内部都组合了一个私有成员vector用于存储能力。
实验总结:本次实验前三个都是验证性实验,因此代码方面比较顺利,通过实验,我理解了继承和组合的区别,堆对象指针,多态,虚函数等知识。但是到实验任务4需要我自己实现一个项目时,编码方面就会有点困难,一开始我不理解为什么需要写const限定,但是当我编码到const std::string& name = "bear",也就是我想给这个玩具类的名字赋初值时,如果没有const限定就开始报错,这也是我第一次使用堆对象指针,使用之后才能感受到比较方便。代码设计和实验任务3差距不是特别大,所以更多的是让我熟悉这种代码组织方式。

浙公网安备 33010602011771号