实验4
源代码:
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); 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 }
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 5 void test() { 6 GradeCalc c1("OOP"); 7 int n; 8 std::cout << "请输入您要录入的成绩数目:\n"; 9 std::cin >>n; 10 11 std::cout << "录入成绩:\n"; 12 c1.input(n); 13 14 std::cout << "输出成绩:\n"; 15 c1.output(); 16 17 std::cout << "排序后成绩:\n"; 18 c1.sort(); c1.output(); 19 20 std::cout << "*************成绩统计信息*************\n"; 21 c1.info(); 22 23 } 24 25 int main() { 26 test(); 27 }
截图:

Q1:
std::vector<int> grades;
std::array<int, 5> counts;
std::array<double, 5> rates;
grades负责存储所有学生成绩,而counts和rates则分别用于在统计时临时存放五个分数段的人数和占比。
Q2:
不合法。因为push_back是std::vector的成员函数,而grades是GradeCalc类的私有成员变量,外部代码无法直接访问,GradeCalc类自身也并未提供任何名为push_back的公有接口来修改内部成绩列表。
Q3:
(1) compute()只会被调用1次。is_dirty确保仅在数据被修改后才执行统计计算,在数据未变的情况下直接使用缓存结果,提高效率。
(2) 无需更改compute的调用位置;只需在该函数内更新成绩并设置is_dirty = true,之后info()在需要时会自动触发重算。
Q4:
应在info()函数内添加逻辑。
伪代码为:
先调用sort(true)对成绩进行升序排序;若成绩数量为奇数,则中位数为正中间成绩grades[n/2];若为偶数,则中位数为中间两个成绩的平均值(grades[n/2-1] + grades[n/2]) / 2.0。
Q5:
不能去掉。如果去掉,在连续多次调用info()且中间有成绩数据更新的场景下,compute()函数会在旧的统计结果基础上累加新数据,引发统计错误。
Q6:
(1) 对程序功能没有影响。
(2) 对性能有影响:去掉后,vector在动态增长过程中可能需要多次重新分配内存,当成绩数量n较大时,会导致额外的开销。
任务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 int n; 8 std::cout << "请输入您要录入的成绩数目:\n"; 9 std::cin >>n; 10 11 std::cout << "录入成绩:\n"; 12 c1.input(n); 13 14 std::cout << "输出成绩:\n"; 15 c1.output(); 16 17 std::cout << "排序后成绩:\n"; 18 c1.sort(); c1.output(); 19 20 std::cout << "*************成绩统计信息*************\n"; 21 c1.info(); 22 23 } 24 25 int main() { 26 test(); 27 }
截图:

Q1:GradeCalc类声明中体现"继承"关系的完整代码行:class GradeCalc : private std::vector<int>。
Q2:基类vector<int>的接口不会自动成为GradeCalc的接口。测试代码不能编译通过,因为push_back()方法在私有继承后变成了GradeCalc的私有成员,外部无法直接访问。
Q3:在组合方式中,通过包含对象的公有接口访问数据,而在继承方式中,通过继承关系直接访问基类成员。
Q4:在成绩计算这个场景下,组合方案更为合适。因为成绩GradeCalc与向量vector之间是"has-a"关系而非"is-a"关系,组合方式能提供更好的封装性,避免意外暴露不必要的向量接口。
任务3:
源代码:
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); // 创建图形,返回堆对象指针
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 }
截图:

Q1:
(1)std::vector<Graph*> graphs;管理和存储所有图形对象的指针;
(2)class Circle : public Graph ||| class Triangle : public Graph ||| class Rectangle : public Graph
Q2:
(1)将无法实现多态,只会调用基类 Graph的 draw方法
(2)会导致仅保留基类部分而丢失派生类信息
(3)通过基类指针删除派生类对象时,派生类的析构函数不会被调用,导致资源泄漏。
Q3:需Graph.hpp中增加 star枚举值、声明 Star类,在实现文件中实现 Star::draw()方法,并修改 str_to_GraphType和 make_graph两个函数以支持新类型。
Q4:
(1)make_graph返回的对象在 Canvas的析构函数中被释放;
(2)利在于直接、高效,但弊端是需手动管理释放,极易因疏忽导致内存泄漏或重复释放。
任务4:
源代码:
1 #pragma once 2 3 #include <string> 4 #include <vector> 5 6 // 玩具类型枚举 7 enum class ToyType { talking_bear, glowing_rabbit, singing_bird }; 8 9 // 抽象基类:毛绒玩具 10 class Toy { 11 public: 12 virtual void specialAbility() = 0; // 特异功能 13 virtual void showInfo() const; // 显示基本信息 14 virtual ~Toy() = default; 15 16 protected: 17 std::string name; 18 ToyType type; 19 std::string color; 20 }; 21 22 // 会说话的熊 23 class TalkingBear : public Toy { 24 public: 25 TalkingBear(); 26 void specialAbility(); 27 }; 28 29 // 会发光的兔子 30 class GlowingRabbit : public Toy { 31 public: 32 GlowingRabbit(); 33 void specialAbility(); 34 }; 35 36 // 会唱歌的小鸟 37 class SingingBird : public Toy { 38 public: 39 SingingBird(); 40 void specialAbility(); 41 }; 42 43 // 玩具工厂 44 class ToyFactory { 45 public: 46 void addToy(const std::string& type); // 添加玩具 47 void showAllToys() const; // 显示所有玩具信息 48 ~ToyFactory(); // 析构函数 49 50 private: 51 std::vector<Toy*> toys; 52 }; 53 54 // 工具函数 55 ToyType strToToyType(const std::string& s); 56 Toy* makeToy(const std::string& type);
1 #include <algorithm> 2 #include <cctype> 3 #include <iostream> 4 #include <string> 5 #include "toy.hpp" 6 7 // TalkingBear 实现 8 TalkingBear::TalkingBear() { 9 name = "说话小熊"; 10 type = ToyType::talking_bear; 11 color = "棕色"; 12 } 13 14 void TalkingBear::specialAbility() { 15 std::cout << " 说:你好!今天过得怎么样?\n"; 16 } 17 18 // GlowingRabbit 实现 19 GlowingRabbit::GlowingRabbit() { 20 name = "发光白兔"; 21 type = ToyType::glowing_rabbit; 22 color = "白色"; 23 } 24 25 void GlowingRabbit::specialAbility() { 26 std::cout <<" 发出柔和的彩虹光芒!\n"; 27 } 28 29 // SingingBird 实现 30 SingingBird::SingingBird() { 31 name = "唱歌小鸟"; 32 type = ToyType::singing_bird; 33 color = "黄色"; 34 } 35 36 void SingingBird::specialAbility() { 37 std::cout <<" 唱起美妙的歌曲!\n"; 38 } 39 40 // Toy 基类实现 41 void Toy::showInfo() const { 42 std::cout << "玩具名称:" << name << std::endl <<"颜色:" << color << std::endl; 43 } 44 45 // ToyFactory 实现 46 void ToyFactory::addToy(const std::string& type) { 47 Toy* toy = makeToy(type); 48 if (toy) 49 toys.push_back(toy); 50 } 51 52 void ToyFactory::showAllToys() const { 53 std::cout << "=== 玩具工厂中的所有玩具 ===\n"; 54 for (auto toy : toys) { 55 toy->showInfo(); 56 std::cout << "特异功能:"; 57 toy->specialAbility(); 58 std::cout<<std::endl; 59 } 60 } 61 62 ToyFactory::~ToyFactory() { 63 for (Toy* toy : toys) 64 delete toy; 65 } 66 67 // 工具函数实现 68 ToyType strToToyType(const std::string& s) { 69 std::string t = s; 70 std::transform(s.begin(), s.end(), t.begin(), 71 [](unsigned char c) { return std::tolower(c); }); 72 73 if (t == "talkingbear") return ToyType::talking_bear; 74 if (t == "glowingrabbit") return ToyType::glowing_rabbit; 75 if (t == "singingbird") return ToyType::singing_bird; 76 77 return ToyType::talking_bear; // 默认类型 78 } 79 80 Toy* makeToy(const std::string& type) { 81 switch (strToToyType(type)) { 82 case ToyType::talking_bear: return new TalkingBear; 83 case ToyType::glowing_rabbit: return new GlowingRabbit; 84 case ToyType::singing_bird: return new SingingBird; 85 default: return nullptr; 86 } 87 }
1 #include <iostream> 2 #include <string> 3 #include "toy.hpp" 4 5 void testToyFactory() { 6 ToyFactory factory; 7 8 // 添加各种玩具 9 factory.addToy("talkingbear"); 10 factory.addToy("glowingrabbit"); 11 factory.addToy("singingbird"); 12 13 // 显示所有玩具信息 14 factory.showAllToys(); 15 } 16 17 int main() { 18 testToyFactory(); 19 return 0; 20 }
截图:

采用了基于继承的多态设计方案,通过抽象基类Toy定义了统一的玩具接口,三个具体玩具类(TalkingBear、GlowingRabbit、SingingBird)继承并实现了各自的特异功能。
方案采用继承关系实现多态,通过虚函数specialAbility()为不同玩具提供统一的功能调用接口。数据成员包括玩具名称、类型和颜色等基本信息,函数成员设计包含显示信息的showInfo()和展示特异功能的specialAbility()等公共接口。玩具工厂类ToyFactory采用工厂模式管理玩具对象集合,通过addToy()添加玩具、showAllToys()展示所有玩具信息,并利用工具函数strToToyType()和makeToy()实现字符串到枚举的转换及对象创建。
实验总结:
本次实验让我深刻体会到面向对象设计中多态的强大威力:通过虚函数实现"一个接口,多种行为",新加玩具类型只需继承基类而无需修改现有代码。
浙公网安备 33010602011771号