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

问题
问题1:
std::string course_name; // 记录课程名称
std::vector<int> grades; // 保存所有学生的成绩
std::array<int, 5> counts; // 统计五个分数段的学生人数
std::array<double, 5> rates; // 保存各分数段人数占总人数的比例
问题2:
不合法。原因是`push_back`是`std::vector<int>`的成员函数,而`grades`是`GradeCalc`类的私有成员,外部代码无法直接访问私有成员,且`GradeCalc`类未提供对应的公有接口,因此不能直接调用`push_back`添加成绩。
问题3:
(1)compute会被调用1次。`is_dirty`是脏标记,用于判断成绩数据是否发生变更,只有当数据变更(`is_dirty = true`)时,调用`info`才会重新执行`compute`计算统计信息,避免重复计算以提升性能。
(2)不需要更改`compute`调用位置。理由是`compute`的调用由`is_dirty`的状态决定,新增`update_grade(index, new_grade)`时,只需在该方法中设置`is_dirty = true`,后续调用`info`时会自动检测状态并触发`compute`,无需调整调用位置。
问题4
不新增数据成员的实现方式:新增`median`成员函数,利用已有的`grades`数据,通过复制成绩并排序的方式计算中位数,在`info`函数中调用该函数并输出结果。代码如下:
double GradeCalc::median() const {
if (grades.empty())
return 0.0;
std::vector<int> sorted_grades = grades; // 复制成绩,避免修改原数据
std::sort(sorted_grades.begin(), sorted_grades.end());
size_t n = sorted_grades.size();
if (n % 2 == 1) {
return sorted_grades[n / 2]; // 奇数个数据取中间值
} else {
return (sorted_grades[n / 2 - 1] + sorted_grades[n / 2]) / 2.0; // 偶数个数据取中间两值平均
}
}
```
在`info`函数中添加输出逻辑:`std::cout << "中位数:\t" << std::fixed << std::setprecision(2) << median() << std::endl;`
问题5
不能去掉这两行。如果去掉,当存在成绩更新场景(如先录入成绩计算统计信息,后修改部分成绩再重新计算)时,`counts`和`rates`会在原有统计结果的基础上叠加新数据,导致统计结果错误,无法反映修改后成绩的真实分布。
问题6
(1)对程序功能没有影响,去掉后程序仍能正常编译运行,成绩录入和统计功能不受影响。
(2)对性能有影响。去掉`grades.reserve(n)`后,当录入大量成绩时,`vector`会因元素数量超过当前容量频繁触发扩容,每次扩容需重新分配内存并拷贝原有数据,导致程序运行效率下降。
任务二
代码
1 #pragma once 2 #include <array> 3 #include <string> 4 #include <vector> 5 6 class GradeCalc : private std::vector<int> { 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 17 private: 18 void compute(); 19 20 private: 21 std::string course_name; 22 std::array<int, 5> counts; 23 std::array<double, 5> rates; 24 bool is_dirty; 25 };
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 GradeCalc::GradeCalc(const std::string& cname) : course_name{ cname }, is_dirty{ true } { 12 counts.fill(0); 13 rates.fill(0); 14 } 15 16 void GradeCalc::input(int n) { 17 if (n < 0) { 18 std::cerr << "无效输入! 人数不能为负数\n"; 19 return; 20 } 21 this->reserve(n); 22 int grade; 23 for (int i = 0; i < n;) { 24 std::cin >> grade; 25 if (grade < 0 || grade > 100) { 26 std::cerr << "无效输入! 分数须在[0,100]\n"; 27 continue; 28 } 29 this->push_back(grade); 30 ++i; 31 } 32 is_dirty = true; 33 } 34 35 void GradeCalc::output() const { 36 for (auto grade : *this) 37 std::cout << grade << ' '; 38 std::cout << std::endl; 39 } 40 41 void GradeCalc::sort(bool ascending) { 42 if (ascending) 43 std::sort(this->begin(), this->end()); 44 else 45 std::sort(this->begin(), this->end(), std::greater<int>()); 46 } 47 48 int GradeCalc::min() const { 49 if (this->empty()) 50 return -1; 51 return *std::min_element(this->begin(), this->end()); 52 } 53 54 int GradeCalc::max() const { 55 if (this->empty()) 56 return -1; 57 return *std::max_element(this->begin(), this->end()); 58 } 59 60 double GradeCalc::average() const { 61 if (this->empty()) 62 return 0.0; 63 double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size(); 64 return avg; 65 } 66 67 void GradeCalc::info() { 68 if (is_dirty) 69 compute(); 70 std::cout << "课程名称:\t" << course_name << std::endl; 71 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 72 std::cout << "最高分:\t" << max() << std::endl; 73 std::cout << "最低分:\t" << min() << std::endl; 74 const std::array<std::string, 5> grade_range{ "[0, 60) ", 75 "[60, 70)", 76 "[70, 80)", 77 "[80, 90)", 78 "[90, 100]" }; 79 for (int i = static_cast<int>(grade_range.size()) - 1; i >= 0; --i) 80 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" 81 << std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n"; 82 } 83 84 void GradeCalc::compute() { 85 if (this->empty()) 86 return; 87 counts.fill(0); 88 rates.fill(0); 89 for (int grade : *this) { 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 (size_t i = 0; i < rates.size(); ++i) 102 rates[i] = counts[i] * 1.0 / this->size(); 103 is_dirty = false; 104 }
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.hpp" 4 5 void test() { 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(); 13 c1.output(); 14 std::cout << "*************成绩统计信息*************\n"; 15 c1.info(); 16 } 17 18 int main() { 19 test(); 20 }
效果

问题1
体现“继承”关系的完整代码行是:
`class GradeCalc: private std::vector<int>`
问题2
基类`std::vector<int>`的接口不会自动成为`GradeCalc`的接口。
`c.push_back(97);`不能编译通过,原因是:私有继承方式下,基类`std::vector<int>`的所有公有和保护成员在派生类`GradeCalc`中会成为私有成员,外部代码无法直接访问基类的接口。
问题3
组合方式的典型代码:`for(auto grade: grades)`,通过类内部的成员对象接口访问数据;
继承方式的典型代码:`for(int grade: *this)`,通过派生类自身(复用基类)的接口访问数据。
两者的接口差异:
1. 组合方式中,数据被封装在类内部,外部无法直接访问数据成员,只能通过类提供的公共接口操作数据;
2. 继承方式中,派生类内部可直接使用基类的接口,但外部仍受访问控制限制,无法直接调用基类接口。
问题4
结论:组合方案更适合成绩计算的场景。
理由:
1. 组合的设计更符合实际逻辑:成绩计算器是“使用”成绩数据进行计算,而非“成为”成绩数据本身;
2. 组合可自主控制接口暴露,比如仅开放`input()`录入成绩,避免外部直接修改数据,提升了程序安全性;
3. 组合的灵活性更强,若后续更换成绩的存储方式(不用`vector`),仅需修改类内的成员对象,无需调整类的继承关系。
任务三
1 #pragma once 2 #include <string> 3 #include <vector> 4 5 enum class GraphType { circle, triangle, rectangle }; 6 7 class Graph { 8 public: 9 virtual void draw() {} 10 virtual ~Graph() = default; 11 }; 12 13 class Circle : public Graph { 14 public: 15 void draw() override; 16 }; 17 18 class Triangle : public Graph { 19 public: 20 void draw() override; 21 }; 22 23 class Rectangle : public Graph { 24 public: 25 void draw() override; 26 }; 27 28 class Canvas { 29 public: 30 void add(const std::string& type); 31 void paint() const; 32 ~Canvas(); 33 34 private: 35 std::vector<Graph*> graphs; 36 }; 37 38 GraphType str_to_GraphType(const std::string& s); 39 Graph* make_graph(const std::string& type);
1 #include <algorithm> 2 #include <cctype> 3 #include <iostream> 4 #include <string> 5 #include "Graph.hpp" 6 7 void Circle::draw() { std::cout << "draw a circle...\n"; } 8 9 void Triangle::draw() { std::cout << "draw a triangle...\n"; } 10 11 void Rectangle::draw() { std::cout << "draw a rectangle...\n"; } 12 13 void Canvas::add(const std::string& type) { 14 Graph* g = make_graph(type); 15 if (g) 16 graphs.push_back(g); 17 } 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(), 32 [](unsigned char c) { return std::tolower(c); }); 33 if (t == "circle") 34 return GraphType::circle; 35 if (t == "triangle") 36 return GraphType::triangle; 37 if (t == "rectangle") 38 return GraphType::rectangle; 39 return GraphType::circle; 40 } 41 42 Graph* make_graph(const std::string& type) { 43 switch (str_to_GraphType(type)) { 44 case GraphType::circle: return new Circle; 45 case GraphType::triangle: return new Triangle; 46 case GraphType::rectangle: return new Rectangle; 47 default: return nullptr; 48 } 49 }
1 #include <iostream> 2 #include <string> 3 #include "Graph.hpp" 4 5 void test() { 6 Canvas canvas; 7 canvas.add("circle"); 8 canvas.add("triangle"); 9 canvas.add("rectangle"); 10 canvas.paint(); 11 } 12 13 int main() { 14 test(); 15 return 0; 16 }
效果

问题1:对象关系识别
(1)体现“组合”关系的成员声明代码行:`std::vector<Graph*> graphs;`
被组合对象的功能:用于存储和管理所有图形对象的指针。
(2)体现“继承”关系的类声明代码行:
`class Circle : public Graph`
`class Triangle : public Graph`
`class Rectangle : public Graph`
问题2:多态机制观察
(1)若Graph中的draw未声明为虚函数,Canvas::paint()中`g->draw()`会始终调用Graph基类的draw函数,运行时不会输出子类的绘制信息,仅执行基类的空draw逻辑。
(2)若Canvas类的`std::vector<Graph*>`改为`std::vector<Graph>`,会发生“对象切片”,派生类对象的特有部分被截断,只能存储基类对象,多态功能会完全丢失。
(3)若`~Graph()`未声明为虚函数,通过基类指针删除派生类对象时,仅会调用基类析构函数,派生类的资源无法被正确释放,会造成内存泄漏。
问题3:扩展性思考
新增星形Star需在以下文件做改动:
1. 在Graph.hpp中:
- 在`enum class GraphType`中添加`star`枚举值;
- 新增`Star`类的声明,使其继承自`Graph`。
2. 在Graph.cpp中:
- 实现`Star`类的`draw`函数;
- 在`str_to_GraphType`函数中添加对“star”字符串的处理;
- 在`make_graph`函数的switch语句中,添加`case GraphType::star`的分支,返回新的`Star`对象。
问题4:资源管理
(1)`make_graph`返回的对象,是在Canvas类的析构函数中被释放的。
(2)使用原始指针管理内存的利弊:
利:
- 使用方式简单直接;
- 可明确管理内存生命周期,对指针的控制更精确。
弊:
- 容易忘记释放内存,导致内存泄漏;
- 可能出现重复释放同一块内存的问题。
任务四
代码
1 #pragma once 2 #include <string> 3 #include <iostream> 4 #include <vector> 5 6 class Toy { 7 protected: 8 std::string name_; // 三丽鸥玩具名称(含角色昵称) 9 std::string type_; // 玩具类型(贴合角色特色) 10 double price_; // 玩具价格(符合三丽鸥周边定价) 11 std::string material_; // 材质(精致感适配三丽鸥产品) 12 std::string brand_; // 品牌固定为"三丽鸥(Sanrio)" 13 std::string ageRange_; // 适用年龄(三丽鸥核心受众) 14 15 public: 16 Toy(const std::string& name, const std::string& type, double price, 17 const std::string& material, const std::string& brand, const std::string& ageRange) 18 : name_(name), type_(type), price_(price), material_(material), 19 brand_(brand), ageRange_(ageRange) {} 20 21 virtual void specialAbility() const = 0; 22 virtual void displayInfo() const; 23 virtual ~Toy() = default; 24 25 std::string getName() const { return name_; } 26 std::string getType() const { return type_; } 27 double getPrice() const { return price_; } 28 }; 29 30 // Hello Kitty音乐盒(三丽鸥经典角色:Hello Kitty) 31 class HelloKittyMusicBox : public Toy { 32 public: 33 HelloKittyMusicBox(const std::string& name, double price, const std::string& material, const std::string& ageRange) 34 : Toy(name, "Hello Kitty主题音乐盒", price, material, "三丽鸥(Sanrio)", ageRange) {} 35 36 void specialAbility() const override; 37 }; 38 39 // My Melody夜灯(三丽鸥角色:美乐蒂) 40 class MyMelodyLamp : public Toy { 41 public: 42 MyMelodyLamp(const std::string& name, double price, const std::string& material, const std::string& ageRange) 43 : Toy(name, "My Melody感应夜灯", price, material, "三丽鸥(Sanrio)", ageRange) {} 44 45 void specialAbility() const override; 46 }; 47 48 // Kuromi互动玩偶(三丽鸥角色:库洛米) 49 class KuromiDoll : public Toy { 50 public: 51 KuromiDoll(const std::string& name, double price, const std::string& material, const std::string& ageRange) 52 : Toy(name, "Kuromi语音互动玩偶", price, material, "三丽鸥(Sanrio)", ageRange) {} 53 54 void specialAbility() const override; 55 }; 56 57 // Cinnamoroll毛绒玩具(三丽鸥角色:玉桂狗) 58 class CinnamorollPlush : public Toy { 59 public: 60 CinnamorollPlush(const std::string& name, double price, const std::string& material, const std::string& ageRange) 61 : Toy(name, "Cinnamoroll软萌毛绒玩具", price, material, "三丽鸥(Sanrio)", ageRange) {} 62 63 void specialAbility() const override; 64 }; 65 66 class ToyFactory { 67 private: 68 std::vector<Toy*> toys_; 69 70 public: 71 ToyFactory() = default; 72 ~ToyFactory(); 73 74 void addToy(Toy* toy); 75 void displayAllToys() const; 76 void tryAllAbilities() const; 77 };
1 #include "Toy.hpp" 2 #include <iomanip> 3 4 void Toy::displayInfo() const { 5 std::cout << std::fixed << std::setprecision(2); 6 std::cout << "【三丽鸥玩具信息】" << std::endl; 7 std::cout << "名称: " << name_ << "\t类型: " << type_ << "\t价格: ¥" << price_ << std::endl; 8 std::cout << "材质: " << material_ << "\t品牌: " << brand_ << "\t适用年龄: " << ageRange_ << "\n" << std::endl; 9 } 10 11 // Hello Kitty音乐盒功能:播放三丽鸥经典旋律 12 void HelloKittyMusicBox::specialAbility() const { 13 std::cout << "[Hello Kitty-" << name_ << "] 启动功能: 打开顶盖自动播放《Hello Kitty主题曲》, Kitty耳朵随旋律轻晃~\n" << std::endl; 14 } 15 16 // My Melody夜灯功能:感应亮灯+渐变光效 17 void MyMelodyLamp::specialAbility() const { 18 std::cout << "[My Melody-" << name_ << "] 启动功能: 手靠近自动亮灯,支持粉/白/浅紫三色渐变,睡前模式10分钟后自动熄灭~\n" << std::endl; 19 } 20 21 // Kuromi互动玩偶功能:语音对话+录音变声 22 void KuromiDoll::specialAbility() const { 23 std::cout << "[Kuromi-" << name_ << "] 启动功能: 按压头顶说'Kuromi'会回应'嘿!要和我一起捣蛋吗~',还能录下你的声音变声成可爱语调~\n" << std::endl; 24 } 25 26 // Cinnamoroll毛绒玩具功能:触摸发声+柔软震动 27 void CinnamorollPlush::specialAbility() const { 28 std::cout << "[Cinnamoroll-" << name_ << "] 启动功能: 摸肚子发出'汪汪~'软萌叫声,抱紧时会轻轻震动,模拟撒娇动作~\n" << std::endl; 29 } 30 31 ToyFactory::~ToyFactory() { 32 for (Toy* toy : toys_) { 33 delete toy; 34 } 35 toys_.clear(); 36 } 37 38 void ToyFactory::addToy(Toy* toy) { 39 if (toy != nullptr) { 40 toys_.push_back(toy); 41 } 42 } 43 44 void ToyFactory::displayAllToys() const { 45 std::cout << "==================================== 三丽鸥玩具工厂产品清单 ====================================\n" << std::endl; 46 if (toys_.empty()) { 47 std::cout << "当前工厂暂无三丽鸥玩具产品!" << std::endl; 48 return; 49 } 50 for (const Toy* toy : toys_) { 51 toy->displayInfo(); 52 } 53 } 54 55 void ToyFactory::tryAllAbilities() const { 56 std::cout << "==================================== 三丽鸥玩具特异功能测试 ====================================\n" << std::endl; 57 if (toys_.empty()) { 58 std::cout << "当前工厂暂无三丽鸥玩具,无法测试功能!" << std::endl; 59 return; 60 } 61 for (const Toy* toy : toys_) { 62 toy->specialAbility(); 63 } 64 }
1 #include "Toy.hpp" 2 3 void testSanrioToySystem() { 4 ToyFactory sanrioFactory; 5 6 // 添加三丽鸥经典角色玩具 7 sanrioFactory.addToy(new HelloKittyMusicBox("Kitty酱", 299.99, "ABS树脂+亚克力", "3-12岁")); 8 sanrioFactory.addToy(new MyMelodyLamp("Melody甜", 189.80, "硅胶+PC透明壳", "2-10岁")); 9 sanrioFactory.addToy(new KuromiDoll("Kuromi酷", 349.00, "短绒布+硅胶", "4-14岁")); 10 sanrioFactory.addToy(new CinnamorollPlush("Cinnamoroll软", 259.99, "云朵绒+PP棉", "2-11岁")); 11 sanrioFactory.addToy(new HelloKittyMusicBox("Kitty甜", 319.99, "实木+水晶", "5-15岁")); 12 13 // 展示产品+测试功能 14 sanrioFactory.displayAllToys(); 15 sanrioFactory.tryAllAbilities(); 16 } 17 18 int main() { 19 testSanrioToySystem(); 20 return 0; 21 }
效果


浙公网安备 33010602011771号