实验4 组合与继承

实验任务1

代码:

GradeCalc.hpp

#pragma once
#include <vector>
#include <array>
#include <string>
class GradeCalc {
public:
	GradeCalc(const std::string& cname);
	void input(int n);
	void output() const;
	void sort(bool ascending = false);
	int min() const; 
	int max() const; 
	double average() const; 
	void info();
private:
	void compute();
private:
	std::string course_name; 
	std::vector<int> grades;
	std::array<int, 5> counts;
	std::array<double, 5> rates;
	bool is_dirty; 
};

GradeCalc.cpp

#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
#include "GradeCalc.hpp"
GradeCalc::GradeCalc(const std::string& cname) :course_name{ cname }, is_dirty{ true } {
	counts.fill(0);
	rates.fill(0);
}
void GradeCalc::input(int n) {
	if (n < 0) {
		std::cerr << "无效输入! 人数不能为负数\n";
		std::exit(1);
	}

	grades.reserve(n);
	int grade;
	for (int i = 0; i < n;) {
		std::cin >> grade;
		if (grade < 0 || grade > 100) {
			std::cerr << "无效输入! 分数须在[0,100]\n";
			continue;
		}

		grades.push_back(grade);
		++i;
	}
	is_dirty = true; 
}
void GradeCalc::output() const {
	for (auto grade : grades)
		std::cout << grade << ' ';
	std::cout << std::endl;
}

void GradeCalc::sort(bool ascending) {
	if (ascending)
		std::sort(grades.begin(), grades.end());
	else
		std::sort(grades.begin(), grades.end(), std::greater<int>());
}
int GradeCalc::min() const {
	if (grades.empty())
		return -1;
	auto it = std::min_element(grades.begin(), grades.end());
	return *it;
}
int GradeCalc::max() const {
	if (grades.empty())
		return -1;
	auto it = std::max_element(grades.begin(), grades.end());
	return *it;
}
double GradeCalc::average() const {
	if (grades.empty())
		return 0.0;
	double avg = std::accumulate(grades.begin(), grades.end(), 0.0) / grades.size();
	return avg;
}
void GradeCalc::info() {
	if (is_dirty)
		compute();
	std::cout << "课程名称:\t" << course_name << std::endl;
	std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() <<
		std::endl;
	std::cout << "最高分:\t" << max() << std::endl;
	std::cout << "最低分:\t" << min() << std::endl;
	const std::array<std::string, 5> grade_range{ "[0, 60) ",
										   "[60, 70)",
										   "[70, 80)",
										   "[80, 90)",
										   "[90, 100]" };

	for (int i = grade_range.size() - 1; i >= 0; --i)
		std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"
		<< std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n";
}
void GradeCalc::compute() {
	if (grades.empty())
		return;
	counts.fill(0);
	rates.fill(0.0);
	for (auto grade : grades) {
		if (grade < 60)
			++counts[0];
		else if (grade < 70)
			++counts[1];
		else if (grade < 80)
			++counts[2];
		else if (grade < 90)
			++counts[3];
		else
			++counts[4];
	}
		for (int i = 0; i < rates.size(); ++i)
			rates[i] = counts[i] * 1.0 / grades.size();

		is_dirty = false;
	}

demo1.cpp

#include <iostream>
#include <string>
#include "GradeCalc.hpp"
void test() {
	GradeCalc c1("OOP");
	std::cout << "录入成绩:\n";
	c1.input(5);
	std::cout << "输出成绩:\n";
	c1.output();
	std::cout << "排序后成绩:\n";
	c1.sort(); c1.output();
	std::cout << "*************成绩统计信息*************\n";
	c1.info();
}
int main() {
	test();
}

运行测试截图:
image
回答问题:

问题1:

在 GradeCalc 类中,体现“组合关系”的成员声明及功能:

std::string course_name; :存储课程名称。

std::vector grades; :存储课程的所有成绩数据。

std::array<int, 5> counts; :存储各分数段的人数统计结果。

std::array<double, 5> rates; :存储各分数段的人数占比。

问题2:

c.push_back(97); 不合法。
原因: grades 是 GradeCalc 的私有成员,外部无法直接调用其 push_back 方法;类只对外提供了 input 接口来录入成绩,需通过 input 完成成绩添加。

问题3:

(1) 连续打印3次统计信息时, compute 只会被调用1次。
标记 is_dirty 的作用:标记成绩数据是否有变更,只有当 is_dirty 为 true (成绩变更)时, info 才会调用 compute 重新统计,避免重复计算。

(2) 需要修改 compute 调用位置。
理由:新增 update_grade 会修改成绩数据,需在 update_grade 中设置 is_dirty = true ,确保后续 info 调用时触发 compute 重新统计。

问题4:

不需要新增数据成员,在 GradeCalc 类中添加成员函数:

double median() ;

double GradeCalc::median() {
   if (grades.empty()) return 0.0;
   std::vector<int> temp = grades; 
   std::sort(temp.begin(), temp.end());
   int n = temp.size();
   if (n % 2 == 1) {
       return temp[n / 2]; 
   } else {
       return (temp[n/2 - 1] + temp[n/2]) / 2.0; 
   }
}

问题5:

不能去掉 counts.fill(0); rates.fill(0.0); 。
当多次调用 compute 时, counts 和 rates 会保留之前的统计结果,导致重复累加。

问题6:

(1) 对功能无影响
image

(2) 对性能有影响:去掉 reserve(n) 后,当 input 中多次 push_back 时, vector 会频繁触发内存扩容,导致性能下降。

实验任务2

代码:

GradeCalc.hpp

#pragma once
#include <array>
#include <string>
#include <vector>
class GradeCalc : private std::vector<int> {
public:
	GradeCalc(const std::string& cname);
	void input(int n); 
	void output() const;
	void sort(bool ascending = false); 
	int min() const;
	int max() const; 
	double average() const;
	void info(); 
	void compute();
private:
	std::string course_name; 
	std::array<int, 5> counts; 
	std::array<double, 5> rates; 
	bool is_dirty;
};

GradeCalc.cpp

#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
#include "GradeCalc.hpp"
GradeCalc::GradeCalc(const std::string& cname) : course_name{ cname }, is_dirty{ true } {
	counts.fill(0);
	rates.fill(0);
}
void GradeCalc::input(int n) {
	if (n < 0) {
		std::cerr << "无效输入! 人数不能为负数\n";
		return;
	}
	this->reserve(n);
	int grade;
	for (int i = 0; i < n;) {
		std::cin >> grade;
		if (grade < 0 || grade > 100) {
			std::cerr << "无效输入! 分数须在[0,100]\n";
			continue;
		}
		this->push_back(grade);
		++i;
	}
	is_dirty = true;
}
void GradeCalc::output() const {
	for (auto grade : *this)
		std::cout << grade << ' ';
	std::cout << std::endl;
}
void GradeCalc::sort(bool ascending) {
	if (ascending)
		std::sort(this->begin(), this->end());
	else
		std::sort(this->begin(), this->end(), std::greater<int>());
}
int GradeCalc::min() const {
	if (this->empty())
		return -1;
	return *std::min_element(this->begin(), this->end());
}
int GradeCalc::max() const {
	if (this->empty())
		return -1;
	return *std::max_element(this->begin(), this->end());
}
double GradeCalc::average() const {
	if (this->empty())
		return 0.0;
	double avg = std::accumulate(this->begin(), this->end(), 0.0) / this->size();
	return avg;
}
void GradeCalc::info() {
	if (is_dirty)
		compute();
	std::cout << "课程名称:\t" << course_name << std::endl;
	std::cout << "平均分:\t" << std::fixed << std::setprecision(2) << average() <<
		std::endl;
	std::cout << "最高分:\t" << max() << std::endl;
	std::cout << "最低分:\t" << min() << std::endl;
	const std::array<std::string, 5> grade_range{ "[0, 60) ",
										   "[60, 70)",
										   "[70, 80)",
										   "[80, 90)",
										   "[90, 100]" };

	for (int i = grade_range.size() - 1; i >= 0; --i)
		std::cout << grade_range[i] << "\t: " << counts[i] << "人\t"
		<< std::fixed << std::setprecision(2) << rates[i] * 100 << "%\n";
}
void GradeCalc::compute() {
	if (this->empty())
		return;

	counts.fill(0);
	rates.fill(0);

	for (int grade : *this) {
		if (grade < 60)
			++counts[0];
		else if (grade < 70)
			++counts[1]; 
		else if (grade < 80)
			++counts[2]; 
		else if (grade < 90)
			++counts[3];
		else
			++counts[4];
	}

	for (int i = 0; i < rates.size(); ++i)
		rates[i] = counts[i] * 1.0 / this->size();

	is_dirty = false;
}

demo2.cpp

#include <iostream>
#include <string>
#include "GradeCalc.hpp"
void test() {
	GradeCalc c1("OOP");
	std::cout << "录入成绩:\n";
	c1.input(5);
	std::cout << "输出成绩:\n";
	c1.output();
	std::cout << "排序后成绩:\n";
	c1.sort(); c1.output();
	std::cout << "*************成绩统计信息*************\n";
	c1.info();
}
int main() {
	test();
}

运行测试截图:
image
回答问题:

问题1:

class GradeCalc: private std::vector {

问题2:

基类 vector 的接口不会自动成为 GradeCalc 的接口。

c.push_back(97); 无法编译通过,原因: GradeCalc 采用 private 继承,基类的成员在外部不可见,只能通过 GradeCalc 自身提供的 input 接口录入成绩。

问题3

组合方式  for(auto grade: grades)  通过类的私有成员变量 grades访问数据。

继承方式  for(int grade: *this)  通过自身直接访问数据。

问题4:

组合方式更适合成绩统计场景。

理由:组合方式能更好地封装数据,更符合“成绩统计类包含成绩数据”的逻辑关系。

实验任务3

代码:

Graph.hpp

#pragma once
#include <string>
#include <vector>
enum class GraphType { circle, triangle, rectangle };

class Graph {
public:
	virtual void draw() {}
	virtual ~Graph() = default;
};

class Circle : public Graph {
public:
	void draw();
};

class Triangle : public Graph {
public:
	void draw();
};

class Rectangle : public Graph {
public:
	void draw();
};

class Canvas {
public:
	void add(const std::string& type); 
	void paint() const;
	~Canvas(); 
private:
	std::vector<Graph*> graphs;
};

GraphType str_to_GraphType(const std::string& s);
Graph* make_graph(const std::string& type);

Graph.cpp

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>
#include "Graph.hpp"

void Circle::draw() { std::cout << "draw a circle...\n"; }

void Triangle::draw() { std::cout << "draw a triangle...\n"; }

void Rectangle::draw() { std::cout << "draw a rectangle...\n"; }

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;
	}
}

demo3.cpp

#include <string>
#include "Graph.hpp"
void test() {
	Canvas canvas;
	canvas.add("circle");
	canvas.add("triangle");
	canvas.add("rectangle");
	canvas.paint();
}
int main() {
	test();
}

运行测试截图:
image

回答问题:

问题1:

(1) 组合关系的成员声明行: std::vector<Graph*> graphs;

功能: Canvas 通过该容器组合多个图形对象,实现图形的统一管理。

(2) 继承关系的类声明行:

class Circle : public Graph {

class Triangle : public Graph {

class Rectangle : public Graph {

问题2:

(1) 运行结果差异:

若 Graph::draw 未声明为虚函数: g->draw() 会调用 Graph 的空实现,无绘制输出;

(2) 若 vector<Graph*> 改为 vector :

子类对象存入容器时成为为 Graph 类型,无法实现多态。

(3) 若 ~Graph 未声明为虚函数:
 
Canvas 析构时 delete g 仅调用 Graph 的析构函数,不会调用子类析构,导致内存泄漏。

问题3:

在 Graph.hpp 中添加 Star 类声明( class Star : public Graph { public: void draw(); }; );

在 Graph.cpp 中实现 Star::draw() ;

在 str_to_GraphType 函数中添加 "star" 的类型判断;

在 make_graph 的 switch 中添加 case graphType:⭐ return new Star;

问题4:

(1)  make_graph 返回的对象在Canvas 的析构函数中释放。

(2) 原始指针管理的问题:易出现内存泄漏、重复释放。

实验任务4

代码:

Toy.hpp

#pragma once
#include <string>
#include <vector>


class Toy {
public:
    Toy(std::string n, std::string t, int p) : name(n), type(t), price(p) {}
    virtual void specialFunc() const = 0;  
    virtual void showInfo() const =0;                    
protected:
    std::string name;  
    std::string type; 
    int price;         

};


class DialogToy : public Toy {
public:
    DialogToy(std::string n, int p) : Toy(n, "对话玩具", p) {}
    void specialFunc() const ;
    void showInfo() const ;
};


class MusicToy : public Toy {
public:
    MusicToy(std::string n, int p) : Toy(n, "音乐玩具", p) {}
    void specialFunc() const ;
    void showInfo() const ;
};


class LightToy : public Toy {
public:
    LightToy(std::string n, int p) : Toy(n, "发光玩具", p) {}
    void specialFunc() const;
    void showInfo() const ;
};


class ToyFactory {
private:
    std::vector<Toy*> toys;  
public:
    void addToy(Toy* t);         
    void showAllToys() const;     
    ~ToyFactory();                
};

Toy.cpp

#include "Toy.hpp"
#include <iostream>


void DialogToy::specialFunc() const {
    std::cout << "[特异功能] " << name << ":你好呀!" << std::endl;
}
void DialogToy::showInfo() const { std::cout << "名称:" << name << " | 类型:" << type << " | 价格:" << price << "元" << std::endl; }


void MusicToy::specialFunc() const {
    std::cout << "[特异功能] " << name << ":播放儿歌~" << std::endl;
}
void MusicToy::showInfo() const { std::cout << "名称:" << name << " | 类型:" << type << " | 价格:" << price << "元" << std::endl; }


void LightToy::specialFunc() const {
    std::cout << "[特异功能] " << name << ":灯光闪烁中~" << std::endl;
}
void LightToy::showInfo() const { std::cout << "名称:" << name << " | 类型:" << type << " | 价格:" << price << "元" << std::endl; }


void ToyFactory::addToy(Toy* t) { toys.push_back(t); }


void ToyFactory::showAllToys() const {
    std::cout << "===== 玩具工厂清单 =====" << std::endl;
    for (auto t : toys) {
        t->showInfo();
        t->specialFunc();
        std::cout << "----------------" << std::endl;
    }
}


ToyFactory::~ToyFactory() {
    for (auto t : toys) delete t;
}

demo4.cpp

#include "Toy.hpp"

void test() {
   
    ToyFactory factory;
    factory.addToy(new DialogToy("小熊", 99));
    factory.addToy(new MusicToy("小兔", 89));
    factory.addToy(new LightToy("小恐龙", 109));

   
    factory.showAllToys();
}

int main() {
    test();
    return 0;
}

运行测试截图:
image

posted @ 2025-12-01 19:18  l栗l  阅读(4)  评论(0)    收藏  举报