实验四
实验四:组合与继承
实验任务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:数据状态管理 GradeCalc和compute中都包含代码:counts.fill(0);、 rates.fill(0);。 compute中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?
- 不能去掉,去掉了意味着数组没有正确的初始化,首先,在对脏数据进行处理的时候不会初始化数组,导致数据直接累加;此外,如果单个区间没有人数则会输出一个随机大数而不是0
问题6:内存管理理解 input模块中代码1grades.reserve(n);如果去掉:
(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)
- 去掉
grades.reserve(n);不会改变程序的功能或输出结果,程序仍能正常运行并正确读入和显示成绩。原因是std::vector::push_back在容量不足时会自动增长。
(2)对性能有影响吗?如有影响,用一句话陈述具体影响
- 但性能会下降,涉及大量元素拷贝等操作
实验任务2
运行截图

问题回答
问题1:继承关系识别 写出GradeCalc类声明体现"继承"关系的完整代码行
class GradeCalc: private std::vector<int>
问题2:接口暴露理解 当前继承方式下,基类vector的接口会自动成为GradeCalc的接口吗? 如在test模块中这样用,能否编译通过?用一句话解释原因。
GradeCalc c("OOP");
c.input(5);
c.push_back(97); // 合法吗?
- 对于两个问题都是可以。
- 对于问题二,原因是:
GraedeCalc是vector的派生类,继承vector的一切功能。
问题3:数据访问差异 对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。
// 组合方式
for(auto grade: grades) // 通过什么接口访问数据
// 略
// 继承方式
for(int grade: *this)
// 略
- 前者是类内的组合容器,后者是类本身;
问题4:组合 vs. 继承方案选择 你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。
- 我认为:组合更适合。
- 首先这个类是较为抽象的类,需要多个辅助容器,作为类的子集
- 使用任意容器继承得到的类,不符合正常的思维逻辑,而组合的has-a逻辑更加适合这种情况
- 继承会出现继承出来私有函数被外部调用的情况。
实验任务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_graph的switch中添加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;
}
运行截图

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

浙公网安备 33010602011771号