实验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 }
task1.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.std::string course_name; 功能:存储课程名称;
std::vector<int> grades; 功能:动态存储多个学生的成绩,是统计分析的核心数据来源;
std::array<int,5> counts; 功能:固定存储5个分数段的人数,用于统计各分数段分数;
std::array<double> rates; 功能:固定存储5个分数段的人数占比,用于展示分数段分布比例;
bool is_dirty; 功能:标记成绩数据是否变更,控制compute的触发时机,避免重复计算。
#2.不合法 第2行:inupt拼写错误,应该是input;
第3行:push_back 是 std::vector<int>grades 的成员函数,而 grades 是 GradeCalc 的私有成员,外部无法直接访问,只能通过类提供的 input 接口间接添加成绩。
#3.(1)调用1次; is_dirty 作用:通过标记成绩数据grades是否发生变更,若变更,调用info时会触发compute重新统计,若未变更,则直接复用之前的统计结果,避免重复计算。
(2)不需要,理由:只需在函数内修改grades[index]后,将is_dirty 的值设为true即可,我认为也可以修改,在update_grade(index, new_grade);这行代码后面调用compute也行。
#4.直接在info函数中添加
if (!grades.empty()) {
std::vector<int> temp = grades;
std::sort(temp.begin(), temp.end());
double a;
int size = temp.size();
if (size % 2 == 1) {
a = temp[size / 2];
} else {
a = (temp[size/2 - 1] + temp[size/2]) / 2.0;
}
std::cout << "中位数:\t" << a << std::endl;
}
5.不能;再次统计时。
6.(1)不会有影响;
(2)会。reserve()是预分配内存,没有的话,当vector容量不足时需重新分配,降低效率。
##实验任务2
#代码
GradeCalc.hpp
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 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 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 void GradeCalc::output() const { 35 for (auto grade : *this) 36 std::cout << grade << ' '; 37 std::cout << std::endl; 38 } 39 void GradeCalc::sort(bool ascending) { 40 if (ascending) 41 std::sort(this->begin(), this->end()); 42 else 43 std::sort(this->begin(), this->end(), std::greater<int>()); 44 } 45 int GradeCalc::min() const { 46 if (this->empty()) 47 return -1; 48 return *std::min_element(this->begin(), this->end()); 49 } 50 int GradeCalc::max() const { 51 if (this->empty()) 52 return -1; 53 return *std::max_element(this->begin(), this->end()); 54 } 55 double GradeCalc::average() const { 56 if (this->empty()) 57 return 0.0; 58 double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size(); 59 return avg; 60 } 61 void GradeCalc::info() { 62 if (is_dirty) 63 compute(); 64 std::cout << "课程名称:\t" << course_name << std::endl; 65 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << 66 std::endl; 67 std::cout << "最高分:\t" << max() << std::endl; 68 std::cout << "最低分:\t" << min() << std::endl; 69 const std::array<std::string, 5> grade_range{ "[0, 60) ", 70 "[60, 70)", 71 "[70, 80)", 72 "[80, 90)", 73 "[90, 100]" }; 74 for (int i = grade_range.size() - 1; i >= 0; --i) 75 std::cout << grade_range[i] << "\t: " << counts[i] << "人\t" 76 << std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n"; 77 } 78 void GradeCalc::compute() { 79 if (this->empty()) 80 return; 81 counts.fill(0); 82 rates.fill(0); 83 // 统计各分数段人数 84 for (int grade : *this) { 85 if (grade < 60) 86 ++counts[0]; // [0, 60) 87 else if (grade < 70) 88 ++counts[1]; // [60, 70) 89 else if (grade < 80) 90 ++counts[2]; // [70, 80) 91 else if (grade < 90) 92 ++counts[3]; // [80, 90) 93 else 94 ++counts[4]; // [90, 100] 95 } 96 // 统计各分数段比例 97 for (int i = 0; i < rates.size(); ++i) 98 rates[i] = counts[i] * 1.0 / this->size(); 99 is_dirty = false; 100 }
demo2.cpp
1 #include <iostream> 2 #include <string> 3 #include "GradeCalc.h" 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 }
##运行结果

##问题
1.class GradeCalc : private std::vector<int>;
2.(1)不会(2)不能,采用私有继承。
3.继承:直接复用基类的接口
组合:通过成员变量调用其接口
4.组合;组合方式只需修改内部实现,不影响公共接口
##实验任务3
#代码
Graph.hpp
1 #pragma once 2 3 #include <string> 4 #include <vector> 5 6 enum class GraphType { circle, triangle, rectangle }; 7 8 class Graph 9 { 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 // Triangle类声明 21 class Triangle : public Graph { 22 public: 23 void draw(); 24 }; 25 // Rectangle类声明 26 class Rectangle : public Graph { 27 public: 28 void draw(); 29 }; 30 31 32 // Canvas类声明 33 class Canvas { 34 public: 35 void add(const std::string& type); // 根据字符串添加图形 36 void paint() const; // 使用统一接口绘制所有图形 37 ~Canvas(); // 手动释放资源 38 private: 39 std::vector<Graph*> graphs; 40 }; 41 42 // 4. 工具函数 43 GraphType str_to_GraphType(const std::string& s); // 字符串转枚举类型 44 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 // Circle类实现 9 void Circle::draw() { std::cout << "draw a circle...\n"; } 10 // Triangle类实现 11 void Triangle::draw() { std::cout << "draw a triangle...\n"; } 12 // Rectangle类实现 13 void Rectangle::draw() { std::cout << "draw a rectangle...\n"; } 14 15 // Canvas类实现 16 void Canvas::add(const std::string& type) { 17 Graph* g = make_graph(type); 18 if (g) 19 graphs.push_back(g); 20 } 21 22 void Canvas::paint() const { 23 for (Graph* g : graphs) 24 g->draw(); 25 } 26 Canvas::~Canvas() { 27 for (Graph* g : graphs) 28 delete g; 29 } 30 31 // 工具函数实现 32 // 字符串 → 枚举转换 33 GraphType str_to_GraphType(const std::string& s) { 34 std::string t = s; 35 std::transform(s.begin(), s.end(), t.begin(), 36 [](unsigned char c) { return std::tolower(c); }); 37 if (t == "circle") 38 return GraphType::circle; 39 if (t == "triangle") 40 return GraphType::triangle; 41 if (t == "rectangle") 42 return GraphType::rectangle; 43 return GraphType::circle; // 缺省返回 44 } 45 // 创建图形,返回堆对象指针 46 Graph* make_graph(const std::string& type) { 47 switch (str_to_GraphType(type)) { 48 case GraphType::circle: return new Circle; 49 case GraphType::triangle: return new Triangle; 50 case GraphType::rectangle: return new Rectangle; 51 default: return nullptr; 52 } 53 }
demo3.cpp
1 #include <string> 2 #include "Graph.h" 3 4 void test() { 5 Canvas canvas; 6 canvas.add("circle"); 7 canvas.add("triangle"); 8 canvas.add("rectangle"); 9 canvas.paint(); 10 } 11 12 int main() { 13 test(); 14 }
#运行结果

##问题
1.组合:std::vector<Graph*>graphs;功能存储多个图形对象的指针;
继承:class Circle : public Graph
class Triangle : public Graph
class Rectangle : public Graph
2.(1)无论g指向哪个派生类对象,都会调用基类Graph中的draw(),导致结果相同;
(2)始终调用Graph::draw(),无法调用派生类中的draw()函数;
(3)删除派生类对象时,调用基类Graph的析构函数,而不会调用派生类的析构函数,~Graph() 未声明成虚函数时无法释放派生类对象;
3. Graph.hpp类声明、enum添加 Graph.cpp类定义
4.(1)Canvas调用析构函数时;
(2)利:可以直接控制:可以精确控制内存分配和释放时机; 弊端:容易产生内存泄漏,可能对同一指针多次调用delete。
##实验任务4
#代码
toy.hpp
1 #ifndef TOY_H 2 #define TOY_H 3 4 #include <string> 5 #include <vector> 6 #include <iostream> // 仅用于声明中cout的依赖 7 8 // 毛绒玩具基类 9 class Toy { 10 protected: 11 std::string name; 12 std::string type; 13 std::string color; 14 float price; 15 16 public: 17 Toy(std::string n, std::string t, std::string c, float p); 18 virtual void specialFunction() const = 0; // 纯虚函数(无需实现) 19 20 // 成员函数声明 21 std::string getName() const; 22 std::string getType() const; 23 std::string getColor() const; 24 float getPrice() const; 25 }; 26 27 // 会说话的玩具子类 28 class TalkingToy : public Toy { 29 private: 30 std::string voiceContent; 31 32 public: 33 TalkingToy(std::string n, std::string t, std::string c, float p, std::string vc); 34 void specialFunction() const override; 35 }; 36 37 // 发光的玩具子类 38 class LightToy : public Toy { 39 private: 40 std::string lightColor; 41 42 public: 43 LightToy(std::string n, std::string t, std::string c, float p, std::string lc); 44 void specialFunction() const override; 45 }; 46 47 // 玩具工厂类 48 class ToyFactory { 49 private: 50 std::string factoryName; 51 std::vector<Toy*> toyList; 52 53 public: 54 ToyFactory(std::string fn); 55 void addToy(Toy* toy); 56 void showAllToys() const; 57 ~ToyFactory(); 58 }; 59 60 #endif
Toy.cpp
1 #include "Toy.hpp" 2 3 // ========== Toy类 实现 ========== 4 Toy::Toy(std::string n, std::string t, std::string c, float p) 5 : name(n), type(t), color(c), price(p) {} 6 7 std::string Toy::getName() const { return name; } 8 std::string Toy::getType() const { return type; } 9 std::string Toy::getColor() const { return color; } 10 float Toy::getPrice() const { return price; } 11 12 13 // ========== TalkingToy类 实现 ========== 14 TalkingToy::TalkingToy(std::string n, std::string t, std::string c, float p, std::string vc) 15 : Toy(n, t, c, p), voiceContent(vc) {} 16 17 void TalkingToy::specialFunction() const { 18 std::cout << "【" << name << "】特异功能:播放语音 -> \"" << voiceContent << "\"" << std::endl; 19 } 20 21 22 // ========== LightToy类 实现 ========== 23 LightToy::LightToy(std::string n, std::string t, std::string c, float p, std::string lc) 24 : Toy(n, t, c, p), lightColor(lc) {} 25 26 void LightToy::specialFunction() const { 27 std::cout << "【" << name << "】特异功能:发出" << lightColor << "色光芒" << std::endl; 28 } 29 30 31 // ========== ToyFactory类 实现 ========== 32 ToyFactory::ToyFactory(std::string fn) : factoryName(fn) {} 33 34 void ToyFactory::addToy(Toy* toy) { 35 toyList.push_back(toy); 36 } 37 38 void ToyFactory::showAllToys() const { 39 std::cout << "\n===== " << factoryName << " 玩具列表 =====" << std::endl; 40 for (const auto& toy : toyList) { 41 std::cout << "\n名称:" << toy->getName() 42 << " | 类型:" << toy->getType() 43 << " | 颜色:" << toy->getColor() 44 << " | 价格:" << toy->getPrice() << "元" << std::endl; 45 toy->specialFunction(); 46 } 47 } 48 49 ToyFactory::~ToyFactory() { 50 for (auto toy : toyList) { 51 delete toy; 52 } 53 }
demo4.cpp
1 #include "Toy.hpp" 2 #include <iostream> 3 4 int main() { 5 // 创建玩具工厂 6 ToyFactory factory("可爱毛绒玩具工厂"); 7 8 // 添加不同类型的玩具 9 factory.addToy(new TalkingToy("小维尼", "毛绒公仔", "黄色", 59.9, "你好呀,我是维尼!")); 10 factory.addToy(new LightToy("发光兔子", "夜光毛绒", "白色", 79.9, "暖白")); 11 factory.addToy(new TalkingToy("皮卡丘", "电动毛绒", "黄色", 99.9, "皮卡皮卡!")); 12 factory.addToy(new LightToy("星空独角兽", "闪光毛绒", "紫色", 89.9, "七彩渐变")); 13 14 // 显示所有玩具信息并尝试特异功能 15 factory.showAllToys(); 16 17 return 0; 18 }
#运行结果

场景描述及各类的关系
1. 继承与虚函数:
- 基类 Toy 定义纯虚函数 specialFunction() ,作为统一接口;
- 子类 TalkingToy 、 LightToy 继承 Toy 并重写该虚函数,实现各自的特异功能。
2. 组合关系:
- ToyFactory 类通过 vector<Toy*> 组合多个 Toy 对象,体现“工厂包含一组毛绒玩具”的设计。
3. 统一接口调用:
- ToyFactory::showAllToys() 通过基类指针调用 specialFunction() ,利用多态性实现“一个接口尝试所有玩具特异功能”。

浙公网安备 33010602011771号