实验四

实验四:组合与继承

实验任务1

运行截图

实验截图1

问题回答

问题1:组合关系识别 GradeCalc类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。

  • 等级 std::vector<int> grades;:用来存放原始的成绩,动态数组,意味着在不受限制的情况下,grades数组可以存放无限长度的成绩;
  • 计数std::array<int, 5> counts;:用来存放各个区间的成绩段人数;
  • 比例std::array<double, 5> rates;:用来存放各个区间的人数比例;

问题2:接口暴露理解 如在test模块中这样使用,是否合法?如不合法,解释原因

 GradeCalc c("OOP");
 c.input(5);
 c.push_back(97);  // 合法吗?
  • 不合法,在GradeCalc类中,公开了input()的程序接口,但是没有公开任何一个组合类,而方法push_back()是容器类尾部加入(入栈)的方法,属于私有接口,在test.cpp中,main函数无法访问,程序会报错。此外指出,第二行也没办法跑,因为input拼错了,但是考虑到笔误,不再赘述。

问题3:架构设计分析 当前设计方案中,compute在info模块中调用:

(1)连续打印3次统计信息,compute会被调用几次?标记is_dirty起到什么作用?

  • compute一共调用1次;
  • is_dirty标记提示数据是否发生改动,当它的值为true的时候数据则需要进行更改

(2)如新增update_grade(index, new_grade),这种设计需要更改compute调用位置吗?简洁说明理由

  • 个人认为不需要更改,update_grade(index, new_grade)的请求来源用户的需求,所以compute的调用次数一定是 \(O(n)\) ,所以,在信息输出之前统一进行计算是很有必要的,因此不需要进行额外处理。

问题4:功能扩展设计 要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码

  • 在此代码的基础上:
void GradeCalc::sort(bool ascending) {
    if(ascending)
        std::sort(grades.begin(), grades.end());
    else
        std::sort(grades.begin(), grades.end(), std::greater<int>());
}
double GradeCalc::sort_cal(bool ascending) {
    if(ascending)
        std::sort(grades.begin(), grades.end());
    else
        std::sort(grades.begin(), grades.end(), std::greater<int>());
    int t = grades.size();
    if(t%2){
        return (grades[t/2]+grades[t/2+1])/2
    }
    else{
        return grades[t/2+1]
    }
}

问题5:数据状态管理 GradeCalccompute中都包含代码:counts.fill(0); rates.fill(0);compute中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?

  • 不能去掉,去掉了意味着数组没有正确的初始化,首先,在对脏数据进行处理的时候不会初始化数组,导致数据直接累加;此外,如果单个区间没有人数则会输出一个随机大数而不是0

问题6:内存管理理解 input模块中代码1grades.reserve(n);如果去掉:

(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)

  • 去掉 grades.reserve(n); 不会改变程序的功能或输出结果,程序仍能正常运行并正确读入和显示成绩。原因是 std::vector::push_back 在容量不足时会自动增长。

(2)对性能有影响吗?如有影响,用一句话陈述具体影响

  • 但性能会下降,涉及大量元素拷贝等操作

实验任务2

运行截图

实验截图2

问题回答

问题1:继承关系识别 写出GradeCalc类声明体现"继承"关系的完整代码行

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

问题2:接口暴露理解 当前继承方式下,基类vector的接口会自动成为GradeCalc的接口吗? 如在test模块中这样用,能否编译通过?用一句话解释原因。

GradeCalc c("OOP");
 c.input(5);
 c.push_back(97);  // 合法吗?
  • 对于两个问题都是可以。
  • 对于问题二,原因是:GraedeCalcvector的派生类,继承vector的一切功能。

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

 // 组合方式
for(auto grade: grades)  // 通过什么接口访问数据
// 略
// 继承方式
for(int grade: *this)    
// 略
  • 前者是类内的组合容器,后者是类本身;

问题4:组合 vs. 继承方案选择 你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。

  • 我认为:组合更适合。
    1. 首先这个类是较为抽象的类,需要多个辅助容器,作为类的子集
    2. 使用任意容器继承得到的类,不符合正常的思维逻辑,而组合的has-a逻辑更加适合这种情况
    3. 继承会出现继承出来私有函数被外部调用的情况。

实验任务3

运行截图

实验截图3

问题1:对象关系识别

(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。

  • std::vector<Graph*> graphs;——体现组合,功能是存储图形信息

(2)写出Graph.hpp中体现"继承"关系的类声明代码行

  • class Circle : public Graph
    class Triangle : public Graph 
    class Rectangle : public Graph 
    

​ ——体现继承

问题2:多态机制观察

(1)Graph中的draw若未声明成虚函数,Canvas::paint()g->draw()运行结果会有何不同?

  • 会直接使用基类输出

(2)若Canvas类std::vector<Graph*>改成std::vector<Graph>,会出现什么问题?

  • 由储存对象的指针变为直接储存对象,由于Graph类固定,存储派生对象时会导致派生部分无法存入;

(3)若~Graph()未声明成虚函数,会带来什么问题?

  • 派生类无法删除

问题3:扩展性思考 若要新增星形Star,需在哪些文件做哪些改动?逐一列出。

  • enum后面追加star;
  • 实现Star::draw()
  • str_to_GraphType中添加if (t == "star") return GraphType::star;
  • make_graphswitch中添加case GraphType::star: return new Star;

问题4:资源管理

观察make_graph函数和Canvas析构函数:

(1)make_graph返回的对象在什么地方被释放?

  • canvas析构函数

(2)使用原始指针管理内存有何利弊

  • 无需额外封装,开销小,但内存的管理与释放很麻烦。

实验任务4

源码

//toy.hpp
#include<iostream>
#include <string>
#include <vector>

#pragma once

class Toy {
public:
    Toy() = default;
    Toy(const std::string& name, const std::string& type, const std::string& color)
        : name(name), type(type), color(color) {}
    virtual ~Toy() = default;
    void displayInfo() const {
        std::cout << "Toy Name: " << name << "\n"
                  << "Toy Type: " << type << "\n"
                  << "Toy Color: " << color << std::endl;
    }

    virtual void specialfunc() const = 0;
private:
    std::string name;
    std::string type;
    std::string color;
};

class ToyFactory{
public:
    ToyFactory() = default;
    ~ToyFactory() = default;
    void addtoy(Toy *toy);
    void displayalltoys() const;
private:
    std::vector<class Toy*> toystore;
};

class Dog : public Toy {
public:
    Dog(const std::string& name, const std::string& color)
        : Toy(name, "Dog", color) {}
    ~Dog()= default;
    void specialfunc() const;
};

class Cat : public Toy {
public:
    Cat(const std::string& name, const std::string& color)
        : Toy(name, "Cat", color) {}
    ~Cat()= default;
    void specialfunc() const;
};
//toy.cpp
#include "toy.hpp"
#include <iostream>

void Dog::specialfunc() const {
    std::cout <<"特异功能:"<< "行走" << std::endl;
}

void Cat::specialfunc() const {
    std::cout << "特异功能:"<< "发出猫叫" << std::endl;
}

void ToyFactory::addtoy(Toy *toy) {
    toystore.push_back(toy);
}

void ToyFactory::displayalltoys() const {
    for (const auto& toy : toystore) {
        toy->displayInfo();
        toy->specialfunc();
        std::cout << "------------------" << std::endl;
    }
}
//main.cpp
#include "toy.hpp"
#include <iostream>

int main() {
    ToyFactory factory;
    Toy* dog1 = new Dog("Buddy", "Brown");
    Toy* cat1 = new Cat("Whiskers", "White");
     factory.addtoy(dog1);
    factory.addtoy(cat1);
    factory.displayalltoys();
    delete dog1;
    delete cat1;

    return 0;
}

运行截图

image-20251129171101800

总结

本次实验任务4挑战性较强,涉及纯虚函数与抽象类,编写过程中编译出错几十余次,已经基本掌握相关知识

posted @ 2025-11-29 17:14  As_Val  阅读(18)  评论(0)    收藏  举报