C++面向对象综合实践:图形类的多态实现与文件操作

c++小项目

题目

C++面向对象综合实践:图形类的多态实现与文件操作

本项目通过实现一个图形(Shape)类及其派生类(圆形Circle、矩形Rectangle),综合运用C++面向对象编程的核心特性,包括:

  1. 抽象类与纯虚函数:定义抽象基类Shape,包含计算面积和显示信息的纯虚函数。
  2. 继承与多态:通过虚继承实现圆形和矩形类,并重写基类虚函数。
  3. 友元函数:实现计算图形总面积的功能,演示友元函数对保护成员的访问。
  4. 静态成员:使用静态变量统计当前存在的图形对象数量。
  5. 文件操作:将图形信息写入文件,并从文件中读取并显示。
  6. 动态内存管理:在堆上创建对象数组,正确使用new/delete和new\[\]/delete\[\]。
  7. 类型转换:使用dynamic_cast进行安全的向下类型转换。

通过这个项目,可以深入理解C++面向对象设计、多态机制、内存管理以及文件I/O操作的综合应用。

代码实现

javascript 复制代码
#include<iostream>
#include<string>
#include<fstream>
#define pi 3.1415
using namespace std;
class shape {
public:
	virtual double getarea() = 0;
	virtual void show() = 0;
	shape(string n) {
		name = n;
		count++;
	}
	virtual ~shape() {
		count--;
	};
	friend void printtotalarea(shape* arr[], int n);
	//这里传入函数的形参,类型 数组[]=类型* 数组
	//所以shape* arr[]=shape**arr
	//C++传参规则,将数组名传给参数后,会自动退化为首元素地址
	//数组里的每个元素都是shape*,首元素地址就是shape**
	//传进函数等价于shape**
	static int count;
protected:
	string name;
	double area=0.0;
};
int shape::count = 0;


class circle :virtual public shape {
private:
	double radius;
public:
	circle(string n,double r):shape(n) {
		radius = r;
	}
	double getarea() {
		this->area = pi * radius * radius;
		return this->area;
	}
	void show() {
		cout << "图形"<< name << endl;
		cout << "面积" << getarea() << endl;
		cout << "半径" << radius << endl;
	}
	double getR() {
		return radius;
	}
};
class rectangle :virtual public shape {
private:
	double width;
	double height;
public:
	rectangle(string name,double w, double h):shape(name) {
		 width=w;
		 height=h;
	}
	double getarea() {
		this-> area = width * height;
		return this->area;
	}
	void show() {
		cout << "图形" <<name<< endl;
		cout << "面积" << getarea() << endl;
	}
	double getW() {
		return width;
	}
	double getH() {
		return height;
	}
};
void printtotalarea(shape* arr[], int n) {
	double total = 0.0;
	for (int i = 0; i < n; i++) {
		arr[i]->getarea();
		total += arr[i]->area;//友元函数调用shape里面的保护数据area
		//也可以写成 total+=arr[i]->getarea();
	}
	cout << "总面积是" << total << endl;
}
void writeshapefile(shape* arr[], int n) {
	ofstream ofs;
	ofs.open("test.txt", ios::out);
	if (!ofs.is_open()) {
		cout << "文件打开失败" << endl;
		return;
	}
	for (int i = 0; i < n; i++) {
		circle* c = dynamic_cast<circle*>(arr[i]);
		if (c != NULL) {
			ofs << "circle"
				<< "半径" << c->getR()
				<< "面积" << c->getarea() << endl;
		}
		else {
			rectangle* r = dynamic_cast<rectangle*>(arr[i]);
			ofs << "rectangle"
				<< "长" << r->getH()
				<< "宽" << r->getW()
				<< "面积" << r->getarea();
		}
	}
	ofs.close();
	cout << "数据以写进文件test.txt" << endl;
}
void readshapefile() {
	ifstream ifs;
	ifs.open("test.txt", ios::in);
	if (!ifs.is_open()) {
		cout << "文件打开失败" << endl;
		return;
	}
	string type;
	while (ifs>>type) {
		if (type == "circle") {
			double r;//需要再定义一个变量
			ifs >> r;//用这个变量来存储
			cout << "圆形 半径" << r << " " << "面积" << pi * r * r << endl;
		}
		else if (type == "rectangle") {
			double w, h;
			ifs >> h >> w;
			cout << "长方形 长" << h << " " << "宽" << w << " " << "面积" << h * w << endl;
		}
	}
	ifs.close();
}
int main() {
	shape* c1 = new circle("圆",3);
	shape* r1 = new rectangle("长方形", 3, 4);
	shape* c2 = new circle("圆", 4);
	shape* r2 = new rectangle("长方形", 5, 6);
	//shape* arr[4] = { c1,c2,r1,r2 };
	//for(int i=0;i<4;i++){
	//delete arr[i];
	//}这是在栈内存上面开辟
	shape** arr = new shape* [4]{ c1,c2,r1,r2 };
	printtotalarea(arr, 4);
	for (int i = 0; i < 4; i++) {
		arr[i]->show();
	}
	writeshapefile(arr, 4);
	readshapefile();
	cout << "当前图形数量" << shape::count << endl;
	for (int i = 0; i < 4; i++) {
		delete arr[i];//释放数组里面的每一个元素
	}
	delete[]arr;//释放整个数组
	return 0;
}

遇到的问题

1.抽象类无法实例化

在写基类shape里面写有无省纯虚函数virtual double getarea()=0;但是派生类写的是double getarea(double r),带参数,函数签名不匹配。此时的circle没有实现纯虚函数,仍是抽象类。抽象类不能new对象

2.需要写无省构造函数的两个情景

1.定义了无省对象:circle c;

2.子类构造没有写初始化列表

3.友元函数的访问权限

友元函数不受public,protected,private的限制,可以访问protected成员,普通外部函数做不到。

4.new新数组

复制代码
shape*c1=new circle("圆",3);
在堆上开辟了数组元素
我现在想让开辟的元素构成一个数组有两种方法
1.shape*arr[4]={c1,c2....}
这是在栈上创建shape*类型数组,里面存的元素都是shape*类型的
2.shape**arr=new shape*[4]{c1,c2...}
这是在堆上创建,最后记得要释放
delete []arr;
delete arr[i];//for循环

在给函数传实参时候,由于形参类型是shape*arr\[\],最后退化为数组首元素地址,也就是shape**类型。

构造,析构函数规则

1.初始化列表可以给父类构造传参

2.多态场景的基类析构必须用virtual,防止内存泄漏

2.析构不能重载,一个类只能有一个析构函数,构造可以重载

文件打开模式

ios::in读取 ios::out写入

javascript 复制代码
#include<iostream>
#include<string>
#include<fstream>
#define pi 3.1415
using namespace std;
class shape {
public:
	shape() {};
	virtual double getarea() = 0;
	virtual ~shape() {};
};
class circle :public shape {
public :
	circle(double r) {
		radius = r;
	}
	double getarea() {
		return pi * radius * radius;
	}
	double getr() {
		return radius;
	}
private:
	double radius;
};
class rect :public shape {
public:
	rect(double w, double h) {
		width = w;
		height = h;
	}
	double getarea() {
		return width * height;
	}
	double getw() {
		return width;
	}
	double geth() {
		return height;
	}
private:
	double width;
	double height;
};
void writefile(circle c,rect r) {
	ofstream ofs;
	ofs.open("shape.txt", ios::out);
	ofs << "circle" << " "<<c.getr() << endl;
	ofs << "矩形" << " " << r.getw() <<" " << r.geth() << endl;//这些数据是写在记事本里面的
	ofs.close();
	cout << "数据已经写入文件" << endl;
}
void readfile() {	
	ifstream ifs;
	ifs.open("shape.txt", ios::in);
	if (!ifs.is_open()) {
		return;
	}
	string type;	
	while (ifs >> type) {
		if (type == "circle") {
			double r;
			ifs >> r;
			cout << "圆形" << " " << "面积" << pi*r*r << endl;//这是显示到屏幕上面的
		}
		else {
			double w;
			double h;
			ifs >> w >> h;
			cout << "矩形" << " " << "面积" << w*h << endl;
		}
	}
	ifs.close();
}
int main() {
	circle c(3);
	rect r(4, 5);
	//writefile(c,r);
	readfile();
	return 0;
}

文件读写操作

可以直接在记事本中修改,不用用C++代码来修改

如果想在记事本里面写东西,需要先把ios::out屏蔽之后再运行,因为ios::out会先清空再写。

文件读取时候应该保存到新建的临时图形对象里面