实验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 }

运行结果

屏幕截图 2025-11-26 082744

 

 问题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模块中这样使用,是否合法?如不合法,解释原因。 

image

 答:不合法。因为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统计信息,所以可以省去排序的步骤。

image

image

 问题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 }

运行截图

屏幕截图 2025-11-26 083556

 

 问题1:继承关系识别

写出GradeCalc类声明体现"继承"关系的完整代码行。

答:class GradeCalc:private std::vector<int>

问题2:接口暴露理解

当前继承方式下,基类vector的接口会自动成为GradeCalc的接口吗?

如在test模块中这样用,能否编译通过?用一句话解释原因。

image

 答:基类vector的接口会自动成为GradeCalc的接口,但在test模块中编译不能通过。因为是私有继承,所以基类的公有和保护成员在派生类中变为私有,只能在派生类内部访问。

问题3:数据访问差异

对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。

image

 答:组合方式是在内部通过组合类中的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();
}

运行截图

屏幕截图 2025-11-26 084723

 

问题一: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.实验任务四

实验截图

屏幕截图 2025-11-26 092501

 

源代码

#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;
};
posted @ 2025-12-02 22:16  交界地第一深情  阅读(0)  评论(0)    收藏  举报