实验4
1. 实验任务1
源代码
1 #pragma once 2 #include<vector> 3 #include<array> 4 #include<string> 5 class GradeCalc{ 6 public: 7 GradeCalc(const std::string &cname); 8 void input(int n); 9 void output() const; 10 void sort(bool ascending=false); 11 int min() const; 12 int max() const; 13 double average() const; 14 void info(); 15 private: 16 void compute(); 17 private: 18 std::string course_name; 19 std::vector<int> grades; 20 std::array<int,5> counts; 21 std::array<double,5> rates; 22 bool is_dirty; 23 }; GradeCalc.hpp
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 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true}{ 11 counts.fill(0); 12 rates.fill(0); 13 } 14 void GradeCalc::input(int n){ 15 if(n<0){ 16 std::cerr<<"无效输入!人数不能为负数\n"; 17 std::exit(1); 18 } 19 grades.reserve(n); 20 int grade; 21 for(int i=0;i<n;){ 22 std::cin>>grade; 23 if(grade<0||grade>100){ 24 std::cerr<<"无效输入!分数须在[0,100]\n"; 25 continue; 26 } 27 grades.push_back(grade); 28 ++i; 29 } 30 is_dirty=true; 31 } 32 void GradeCalc::output() const{ 33 for(auto grade:grades) 34 std::cout<<grade<<' '; 35 std::cout<<std::endl; 36 } 37 void GradeCalc::sort(bool ascending){ 38 if(ascending) 39 std::sort(grades.begin(),grades.end()); 40 else 41 std::sort(grades.begin(),grades.end(),std::greater<int>()); 42 } 43 int GradeCalc::min() const{ 44 if(grades.empty()) 45 return -1; 46 auto it=std::min_element(grades.begin(),grades.end()); 47 return *it; 48 } 49 int GradeCalc::max() const{ 50 if(grades.empty()) 51 return -1; 52 auto it=std::max_element(grades.begin(),grades.end()); 53 return *it; 54 } 55 double GradeCalc::average() const{ 56 if(grades.empty()) 57 return 0.0; 58 double avg=std::accumulate(grades.begin(),grades.end(),0.0)/grades.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()<<std::endl; 66 std::cout<<"最高分:\t"<<max()<<std::endl; 67 std::cout<<"最低分:\t"<<min()<<std::endl; 68 const std::array<std::string,5> grade_range{"[0,60)","[60,70)","[70,80)","[80,90)","[90,100]"}; 69 for(int i=grade_range.size()-1;i>=0;i--) 70 std::cout<<grade_range[i]<<"\t: "<<counts[i]<<"人\t" 71 <<std::fixed<<std::setprecision(2)<<rates[i]*100<<"%\n"; 72 } 73 void GradeCalc::compute(){ 74 if(grades.empty()) 75 return; 76 counts.fill(0); 77 rates.fill(0.0); 78 for(auto grade:grades){ 79 if(grade<60) 80 ++counts[0]; 81 else if(grade<70) 82 ++counts[1]; 83 else if(grade<80) 84 ++counts[2]; 85 else if(grade<90) 86 ++counts[3]; 87 else 88 ++counts[4]; 89 } 90 for(int i=0;i<rates.size();i++) 91 rates[i]=counts[i]*1.0/grades.size(); 92 is_dirty=false; 93 } GradeCalc.cpp
1 #include<iostream> 2 #include<string> 3 #include"GradeCalc.hpp" 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:组合关系识别
GradeCalc类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。
答:1.std::string course_name;记录课程的名称
2.std::vector<int> grades;记录下每个人的成绩
3.std::array<int,5> counts;记录5个分数段各自的人数
4.std::array<double,5> rates;记录5个分数段各自的人数占比
问题2:接口暴露理解
如在test模块中这样使用,是否合法?如不合法,解释原因。

答:不合法。因为push_back是标准库函数,它可以在类内被vector成员直接调用。但在类的声明中std::vector<int> grades是私有类型的,所以不能在类外部的test模块中直接访问。
问题3:架构设计分析
当前设计方案中,compute在info模块中调用:
(1)连续打印3次统计信息,compute会被调用几次?标记is_dirty起到什么作用?
答:compute会被调用1次。标记is_dirty可以用来判断整体的打印信息有没有被修改过,如果每次打印的信息相同,就不用重复调用compute进行统计,提高效率。
(2)如新增update_grade(index,new_grade),这种设计需要更改compute调用位置吗?简洁说明理由。
答:需要更改。因为新增的接口会改变打印的信息(这时is_dirty标记也会变成true),所以需要调用compute重新统计各项数据。
问题4:功能扩展设计
要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。
答: 可以直接在info函数中加,与平均分之类的一起统计。因为中位数是一组数据中大小在最中间的(奇数情况下是最中间,偶数情况下是取中间两个数的平均值),正常情况需要先对一组成绩数据排序,在根据奇数还是偶数来取中间值,而在test函数中,先对成绩排序后才调用info统计信息,所以可以省去排序的步骤。


问题5:数据状态管理
GradeCalc和compute中都包含代码:counts.fill(0);rates.fill(0);。compute中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
答: 不能去掉。因为当is_false标记为true时会再次调用compute进行数据统计,如果不把counts和rates都归零,再次统计时会在原基础上继续加,导致错误。
问题6:内存管理理解
input 模块中代码 grades.reserve(n); 如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
答:没有影响。
(2)对性能有影响吗?如有影响,用一句话陈述具体影响。
答:有影响。reserve函数用来预留容器vector中的元素数量(通常在需要插入大量元素时使用),使用reserve可以避免插入一次新数据就要重新分配内存和复制元素,所以去掉后会降低性能。
2.实验任务二
源代码
1 #pragma once 2 #include<vector> 3 #include<array> 4 #include<string> 5 class GradeCalc:private std::vector<int>{ 6 public: 7 GradeCalc(const std::string &cname); 8 void input(int n); 9 void output() const; 10 void sort(bool ascending=false); 11 int min() const; 12 int max() const; 13 double average() const; 14 void info(); 15 private: 16 void compute(); 17 private: 18 std::string course_name; 19 std::array<int,5> counts; 20 std::array<double,5> rates; 21 bool is_dirty; 22 };
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 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true}{ 11 counts.fill(0); 12 rates.fill(0); 13 } 14 void GradeCalc::input(int n){ 15 if(n<0){ 16 std::cerr<<"无效输入!人数不能为负数\n"; 17 return; 18 } 19 this->reserve(n); 20 int grade; 21 for(int i=0;i<n;){ 22 std::cin>>grade; 23 if(grade<0||grade>100){ 24 std::cerr<<"无效输入!分数须在[0,100]\n"; 25 continue; 26 } 27 this->push_back(grade); 28 ++i; 29 } 30 is_dirty=true; 31 } 32 void GradeCalc::output() const{ 33 for(auto grade:*this) 34 std::cout<<grade<<' '; 35 std::cout<<std::endl; 36 } 37 void GradeCalc::sort(bool ascending){ 38 if(ascending) 39 std::sort(this->begin(),this->end()); 40 else 41 std::sort(this->begin(),this->end(),std::greater<int>()); 42 } 43 int GradeCalc::min() const{ 44 if(this->empty()) 45 return -1; 46 return *std::min_element(this->begin(),this->end()); 47 } 48 int GradeCalc::max() const{ 49 if(this->empty()) 50 return -1; 51 return *std::max_element(this->begin(),this->end()); 52 } 53 double GradeCalc::average() const{ 54 if(this->empty()) 55 return 0.0; 56 double avg=std::accumulate(this->begin(),this->end(),0.0)/this->size(); 57 return avg; 58 } 59 void GradeCalc::info(){ 60 if(is_dirty) 61 compute(); 62 std::cout<<"课程名称:\t"<<course_name<<std::endl; 63 std::cout<<"平均分:\t"<<std::fixed<<std::setprecision(2)<<average()<<std::endl; 64 std::cout<<"最高分:\t"<<max()<<std::endl; 65 std::cout<<"最低分:\t"<<min()<<std::endl; 66 const std::array<std::string,5> grade_range{"[0,60)", 67 "[60,70)","[70,80)","[80,90)","[90,100]"}; 68 for(int i=grade_range.size()-1;i>=0;i--) 69 std::cout<<grade_range[i]<<"\t: "<<counts[i]<<"人\t" 70 <<std::fixed<<std::setprecision(2)<<rates[i]*100<<"%\n"; 71 } 72 void GradeCalc::compute(){ 73 if(this->empty()) 74 return; 75 counts.fill(0); 76 rates.fill(0.0); 77 for(int grade:*this){ 78 if(grade<60) 79 ++counts[0]; 80 else if(grade<70) 81 ++counts[1]; 82 else if(grade<80) 83 ++counts[2]; 84 else if(grade<90) 85 ++counts[3]; 86 else 87 ++counts[4]; 88 } 89 for(int i=0;i<rates.size();i++) 90 rates[i]=counts[i]*1.0/this->size(); 91 is_dirty=false; 92 }
1 #include<iostream> 2 #include<string> 3 #include"GradeCalc.hpp" 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:继承关系识别
写出GradeCalc类声明体现"继承"关系的完整代码行。
答:class GradeCalc:private std::vector<int>
问题2:接口暴露理解
当前继承方式下,基类vector的接口会自动成为GradeCalc的接口吗?
如在test模块中这样用,能否编译通过?用一句话解释原因。

答:基类vector的接口会自动成为GradeCalc的接口,但在test模块中编译不能通过。因为是私有继承,所以基类的公有和保护成员在派生类中变为私有,只能在派生类内部访问。
问题3:数据访问差异
对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。

答:组合方式是在内部通过组合类中的vector成员对象grades的接口进行访问,继承方式是在内部用this指针直接通过使用基类的接口来进行访问。在组合中,封装性更强一些,组合类只能通过特定接口访问,看不到内部细节;在继承中,当公有继承时,内部、外部可访问基类接口,当保护和私有继承时,只能在内部对基类接口进行访问,继承可以访问基类内部成员。
问题4:组合 vs. 继承方案选择
你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
答:我觉得组合方案更适合。1.本身vector容器像一个成绩条,而GradeCalc成绩计算器是具有一个成绩条,然后对这些成绩进行处理,符合has-a的关系。2.在组合方式中,虽然不能直接使用this指针进行调用,但我觉得使用grades.begin()反而看起来更加直观,表示是成绩。3.组合的封装性更好,看不到内部的成绩细节,但有接口可以使用,当std::vector<int> grades设为私有时,外部无法直接访问成绩,更安全。
4.实验任务三
源代码
#pragma once #include <string> #include <vector> enum class GraphType {circle, triangle, rectangle}; // Graph类定义 class Graph { public: virtual void draw() {} virtual ~Graph() = default; }; // Circle类声明 class Circle : public Graph { public: void draw(); }; // Triangle类声明 class Triangle : public Graph { public: void draw(); }; // Rectangle类声明 class Rectangle : public Graph { public: void draw(); }; // Canvas类声明 class Canvas { public: void add(const std::string& type); // 根据字符串添加图形 void paint() const; // 使用统一接口绘制所有图形 ~Canvas(); // 手动释放资源 private: std::vector<Graph*> graphs; }; // 4. 工具函数 GraphType str_to_GraphType(const std::string& s); // 字符串转枚举类型 Graph* make_graph(const std::string& type); // 创建图形,返回堆对象指针
#include <algorithm> #include <cctype> #include <iostream> #include <string> #include "Graph.hpp" // Circle类实现 void Circle::draw() { std::cout << "draw a circle...\n"; } // Triangle类实现 void Triangle::draw() { std::cout << "draw a triangle...\n"; } // Rectangle类实现 void Rectangle::draw() { std::cout << "draw a rectangle...\n"; } // Canvas类实现 void Canvas::add(const std::string& type) { Graph* g = make_graph(type); if (g) graphs.push_back(g); } void Canvas::paint() const { for (Graph* g : graphs) g->draw(); } Canvas::~Canvas() { for (Graph* g : graphs) delete g; } // 工具函数实现 // 字符串 → 枚举转换 GraphType str_to_GraphType(const std::string& s) { std::string t = s; std::transform(s.begin(), s.end(), t.begin(), [](unsigned char c) { return std::tolower(c);}); if (t == "circle") return GraphType::circle; if (t == "triangle") return GraphType::triangle; if (t == "rectangle") return GraphType::rectangle; return GraphType::circle; // 缺省返回 } // 创建图形,返回堆对象指针 Graph* make_graph(const std::string& type) { switch (str_to_GraphType(type)) { case GraphType::circle: return new Circle; case GraphType::triangle: return new Triangle; case GraphType::rectangle: return new Rectangle; default: return nullptr; } }
#include <string> #include "Graph.hpp" void test() { Canvas canvas; canvas.add("circle"); canvas.add("triangle"); canvas.add("rectangle"); canvas.paint(); } int main() { test(); }
运行截图

问题一:std::vector<Graph*> graphs; 声明Graph类指针数组
class Circle : public Graph,class Triangle : public Graph,class Rectangle : public Graph
问题二:1:未声明为虚函数会导致始终调用基类的draw函数
2:push_back的数据是Graph*型,类型不匹配报错
3:只有基类的析构函数会被调用,导致内存泄漏
问题三:GraphType枚举列表内要添加星型
str_to_GraphType和make_graph中添加星型的对应分类
添加星型的类声明
问题四:1:会在~Canvas中被释放
2:可以自由分配和释放内存但是安全性差容易内存泄漏
4.实验任务四
实验截图

源代码
#ifndef TOY_HPP #define TOY_HPP #include <string> class Toy { protected: std::string name; std::string type; public: Toy(const std::string& n, const std::string& t); virtual ~Toy() = default; virtual void ShowInfo() const; virtual void ShowSpecialSkill() const = 0; }; #endif
#ifndef TEDDY_BEAR_HPP #define TEDDY_BEAR_HPP #include "Toy.hpp" class TeddyBear : public Toy { public: TeddyBear(const std::string& name); void ShowSpecialSkill() const override; }; #endif
#include "TeddyBear.hpp" #include <iostream> TeddyBear::TeddyBear(const std::string& name) : Toy(name, "泰迪熊") {} void TeddyBear::ShowSpecialSkill() const { std::cout << name << "一只泰迪熊" << std::endl; }
#include "Rabbit.hpp" #include <iostream> Rabbit::Rabbit(const std::string& name) : Toy(name, "大白兔") {} void Rabbit::ShowSpecialSkill() const { std::cout << name << "一只大白兔" << std::endl; }
#ifndef CAT_HPP #define CAT_HPP #include "Toy.hpp" class Cat : public Toy { public: Cat(const std::string& name); void ShowSpecialSkill() const override; }; #endif
#include "Cat.hpp" #include <iostream> Cat::Cat(const std::string& name) : Toy(name, "maomao") {} void Cat::ShowSpecialSkill() const { std::cout << name << "玩具猫猫,哈气了" << std::endl;
#include "Toy.hpp" #include <iostream> Toy::Toy(const std::string& n, const std::string& t) : name(n), type(t) {} void Toy::ShowInfo() const { std::cout << "玩具名: " << name << ", 类型: " << type << std::endl; }
#include "ToyFactory.hpp" #include <iostream> ToyFactory::~ToyFactory() { for (auto t : toys) delete t; } void ToyFactory::AddToy(Toy* toy) { toys.push_back(toy); } void ToyFactory::ShowAllToys() const { std::cout << "——玩具工厂所有玩具——" << std::endl; for (auto t : toys) { t->ShowInfo(); t->ShowSpecialSkill(); std::cout << std::endl; } }
#ifndef TOY_FACTORY_HPP #define TOY_FACTORY_HPP #include <vector> #include "Toy.hpp" class ToyFactory { private: std::vector<Toy*> toys; public: ToyFactory() = default; ~ToyFactory(); void AddToy(Toy* toy); void ShowAllToys() const; }; #endif
}
#ifndef RABBIT_HPP #define RABBIT_HPP #include "Toy.hpp" class Rabbit : public Toy { public: Rabbit(const std::string& name); void ShowSpecialSkill() const override; };

浙公网安备 33010602011771号