实验4
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 grades;
std::array<int, 5> counts;
std::array<double, 5> rates;
course_name保存课程名,grades保存课程成绩,counts用数组保存各分数段人数,rates用数组保存各分数段人数占比。
问题2:不合法。push_back 不是 GradeCalc 类的公有接口,它是 std::vector 的成员函数,而 grades 是 GradeCalc 的 private 成员,在类外部无法直接访问 grades ,这违反了封装原则。
问题3:(1)compute()只会被调用一次,is_dirty标记用于显示成绩是否被更改,只有检测到成绩被更改才会调用compute()。
(2)不需要更改compute()调用位置,update_grade(index, new_grade)在被调用时只需要更改脏标记,在info()被调用时,compute()会自动被调用。
问题4:要增加"中位数"统计功能而不新增数据成员,我们需要在现有的函数中添加计算逻辑 在info()函数中添加
1 double median() const; 2 double GradeCalc::median() const { 3 if (grades.empty()) { 4 return 0.0; 5 } 6 std::vector<int> sorted_grades = grades; 7 std::sort(sorted_grades.begin(), sorted_grades.end()); 8 size_t n = sorted_grades.size(); 9 if (n % 2 == 1) { // 奇数个成绩:取中间值 10 return sorted_grades[n / 2]; 11 } 12 else { // 偶数个成绩:取中间两个值的平均 13 return (sorted_grades[n / 2 - 1] + sorted_grades[n / 2]) / 2.0; 14 } 15 } // 修改info()函数,添加中位数显示 16 void GradeCalc::info() { 17 if (is_dirty) { 18 compute(); 19 20 } 21 std::cout << "课程名称:\t" << course_name << std::endl; 22 std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() << std::endl; 23 std::cout << "中位数:\t" << std::fixed << std::setprecision(2) << median() << std::endl; 24 std::cout << "最高分:\t" << max() << std::endl; 25 std::cout << "最低分:\t" << min() << std::endl; 26 }
问题5:compute中不能去掉这两行。如果去掉,当第二次和之后调用compute()时,前几次向counts和rates填入的人数数据会影响运算结果。
问题6:(1)无影响。程序仍然能正确运行。
(2)有影响。去掉grades.reserve(n)后,会触发vector频繁进行自动扩容,增加了运行开销。
实验二
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 #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 }
task2.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:继承关系:
class GradeClac : private vector
问题2:当前继承方式下,基类 vector<int> 的接口不会自动成为 GradeCalc 的接口,因为是私有继承。
不能编译通过。因为私有继承使基类的所有公有接口在派生类中都变为私有,外部无法直接调用
问题3:for(auto grade : grades)直接通过vector成员自带的接口,调用容器配套的vector::iterator迭代器,遍历容器。
for(int grade : *this)使用时,begin()、end()需要类自行提供,通过类的begin()、end()转发底层的vector容器的迭代器来遍历容器。
问题4:组合方案更适合。vector只是用来存储数据,应该是组合而不是用于继承;组合方案耦合度低,封装性更好。
实验3
Graph.hpp:
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 }
task3.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:
(1)std::vector<Graph *> graphs;用于存储所有图形对象的指针集合。
(2)class Circle : public Graph;class Triangle : public Graph;class Rectangle : public Graph。
问题2:
继承:
class Circle : public Graph
class Triangle : public Graph
class Rectangle : public Graph
问题3:
(2)优点:可以将继承自基类的不同派生类存放在一起,并且可以用基类指针进行批量管理,操作便捷。
缺点:用new分配的内存空间,如果没有delete,会导致内存泄漏;重复delete或者调用前被误释放,会产生野指针。必须通过Graph指针访问,不能直接访问派生类的特有成员。析构函数若为非虚函数,析构时会导致内存泄漏。
1 #ifndef TOY_HPP 2 #define TOY_HPP 3 4 #include <string> 5 6 class Toy { 7 protected: 8 std::string name; 9 std::string material; 10 std::string type; 11 std::string sound; 12 13 public: 14 Toy(const std::string& n, const std::string& m, const std::string& t, const std::string& s); 15 virtual ~Toy() = default; 16 17 virtual void doAction() const = 0; 18 19 void playSound() const; 20 void displayInfo() const; 21 22 std::string getName() const; 23 std::string getMaterial() const; 24 std::string getType() const; 25 std::string getSound() const; 26 }; 27 28 #endif
Toy.cpp
1 #include "Toy.hpp" 2 #include <iostream> 3 4 Toy::Toy(const std::string& n, const std::string& m, const std::string& t, const std::string& s) 5 : name(n), material(m), type(t), sound(s) {} 6 7 void Toy::playSound() const { 8 std::cout << "Playing: " << sound << std::endl; 9 } 10 11 void Toy::displayInfo() const { 12 std::cout << name << "\t" << material << "\t" << type << "\t" << sound << std::endl; 13 } 14 15 std::string Toy::getName() const { return name; } 16 std::string Toy::getMaterial() const { return material; } 17 std::string Toy::getType() const { return type; } 18 std::string Toy::getSound() const { return sound; }
CatToy.hpp
1 #ifndef CATTOY_HPP 2 #define CATTOY_HPP 3 4 #include "Toy.hpp" 5 6 class CatToy : public Toy { 7 public: 8 CatToy(const std::string& name, const std::string& material, const std::string& sound); 9 void doAction() const override; 10 }; 11 12 #endif
CatToy.cpp
1 #include "CatToy.hpp" 2 #include <iostream> 3 4 CatToy::CatToy(const std::string& name, const std::string& material, const std::string& sound) 5 : Toy(name, material, "Cat", sound) {} 6 7 void CatToy::doAction() const { 8 std::cout << name << " is licking its fur!" << std::endl; 9 }
DogToy.hpp
1 #ifndef DOGTOY_HPP 2 #define DOGTOY_HPP 3 4 #include "Toy.hpp" 5 6 class DogToy : public Toy { 7 public: 8 DogToy(const std::string& name, const std::string& material, const std::string& sound); 9 void doAction() const override; 10 }; 11 12 #endif
DogToy.cpp
1 #include "DogToy.hpp" 2 #include <iostream> 3 4 DogToy::DogToy(const std::string& name, const std::string& material, const std::string& sound) 5 : Toy(name, material, "Dog", sound) {} 6 7 void DogToy::doAction() const { 8 std::cout << name << " is wagging tail!" << std::endl; 9 }
BearToy.hpp
1 #ifndef BEARTOY_HPP 2 #define BEARTOY_HPP 3 4 #include "Toy.hpp" 5 6 class BearToy : public Toy { 7 public: 8 BearToy(const std::string& name, const std::string& material, const std::string& sound); 9 void doAction() const override; 10 }; 11 12 #endif
BearToy.cpp
1 #include "BearToy.hpp" 2 #include <iostream> 3 4 BearToy::BearToy(const std::string& name, const std::string& material, const std::string& sound) 5 : Toy(name, material, "Bear", sound) {} 6 7 void BearToy::doAction() const { 8 std::cout << name << " is waving its hands!" << std::endl; 9 }
ToyFactory.hpp
1 #ifndef TOYFACTORY_HPP 2 #define TOYFACTORY_HPP 3 4 #include <vector> 5 #include <string> 6 #include "Toy.hpp" 7 8 class ToyFactory { 9 private: 10 std::vector<Toy*> toys; 11 12 public: 13 ~ToyFactory(); 14 15 bool addToy(Toy* toy); 16 bool deleteToy(const std::string& name); 17 18 void displayAllToys() const; 19 void playAllSounds() const; 20 void doAllActions() const; 21 22 int getToyCount() const; 23 }; 24 25 #endif
ToyFactory.cpp
1 #include "ToyFactory.hpp" 2 #include <iostream> 3 #include <algorithm> 4 5 ToyFactory::~ToyFactory() { 6 for (Toy* toy : toys) { 7 delete toy; 8 } 9 } 10 11 bool ToyFactory::addToy(Toy* toy) { 12 toys.push_back(toy); 13 std::cout << "Success To Add Toy: " << toy->getName() << std::endl; 14 return true; 15 } 16 17 bool ToyFactory::deleteToy(const std::string& name) { 18 for (auto it = toys.begin(); it != toys.end(); ++it) { 19 if ((*it)->getName() == name) { 20 delete *it; 21 toys.erase(it); 22 std::cout << "Del Success" << std::endl; 23 return true; 24 } 25 } 26 return false; 27 } 28 29 void ToyFactory::displayAllToys() const { 30 std::cout << "Toys Information:" << std::endl; 31 for (const Toy* toy : toys) { 32 toy->displayInfo(); 33 } 34 } 35 36 void ToyFactory::playAllSounds() const { 37 for (const Toy* toy : toys) { 38 toy->playSound(); 39 } 40 } 41 42 void ToyFactory::doAllActions() const { 43 for (const Toy* toy : toys) { 44 toy->doAction(); 45 } 46 } 47 48 int ToyFactory::getToyCount() const { 49 return toys.size(); 50 }
task4.cpp
1 #include <iostream> 2 #include "ToyFactory.hpp" 3 #include "CatToy.hpp" 4 #include "DogToy.hpp" 5 #include "BearToy.hpp" 6 7 int main() { 8 ToyFactory factory; 9 10 factory.addToy(new CatToy("kitty", "cotton", "MorningMeow")); 11 factory.addToy(new DogToy("Bruce", "Plastic", "Erica")); 12 factory.addToy(new BearToy("Teddy", "Cotton", "Hotel California")); 13 14 std::cout << std::endl; 15 16 factory.displayAllToys(); 17 18 std::cout << std::endl; 19 20 factory.doAllActions(); 21 22 std::cout << std::endl; 23 24 factory.playAllSounds(); 25 26 std::cout << std::endl; 27 28 factory.deleteToy("kitty"); 29 30 std::cout << std::endl; 31 32 factory.displayAllToys(); 33 34 return 0; 35 }
设计理由:
继承 + 虚函数(多态性):
Toy 是抽象基类,包含纯虚函数 doAction()
派生类(CatToy, DogToy, BearToy)覆盖 doAction() 实现特定行为
允许通过 Toy* 指针统一处理所有玩具类型
ToyFactory(组合):
包含 std::vector<Toy*> 管理所有玩具对象
演示组合关系:工厂"拥有"玩具集合
提供统一的管理接口(添加、删除、显示、播放)
关键设计决策:
抽象基类:确保所有玩具实现必需的接口
虚函数:为特殊动作实现运行时多态性
工厂模式:集中对象创建和管理
关注点分离:显示逻辑、声音播放、动作执行是独立的方法
可扩展性:
可以通过继承 Toy 添加新的玩具类型
工厂方法无需修改即可处理新类型(开闭原则)


浙公网安备 33010602011771号