面向对象编程:职工管理系统之 C++ 实现


前言

面向对象是十分常见的一种编程思想,本文将通过一个实际项目,带大家走进面向对象,体会面向对象在实际开发中的应用,给我们带来的便捷。

本文将通过对实例进行笔者自己的分析与实现方式,向大家展示面向对象的三大特点,尽可能包含面向对象中的绝大多数知识点,希望对大家有所帮助。

闲话少说,我们这就进入项目实例,开始吧!


一、项目概述

本次开发一个职工管理操作系统,该系统能够实现对职工信息的有序管理,包含增删改查排等常用操作,同时代码应具备较好的完善性、鲁棒性以及可扩展性,内存分配应当严谨且合理,对应户的非法操作及不合理输入作出有效应对,总而言之,应当是一个健壮的管理系统。

项目难点主要在于如何利用面向对象的知识实现题目要求的功能:

在这里插入图片描述

首先我们要实现这几个功能函数完成对职工信息容器的对应操作,如增删改查等,相似操作曾在通讯录管理系统中实现过,但是它的处理方式基于结构体数组,因此也存在容量有限、操作复杂等等问题,次数,笔者计划采用C++提供的数组容器存储职工信息,使用迭代器操作数组元素,从而简化代码,提高代码可读性。

其次,我们使用何种数据类型存储职工相关信息,每位职工信息包括姓名、编号、职位以及薪水等信息,要将这些信息综合在一起,我们不难想到使用“类”来实现,但是所谓的“职工类”如何实现?他的相关函数如何实现?如何基于面向对象的思想实现?

整体架构已经明了,那么解决上面两个难题?

二、项目详解

1.职工类 Staff 的实现

笔者计划充分利用面向对象三大特性:封装,继承,多态来实现 Staff 类,那我,我们不妨首先建立一个基类 Worker ,包含职工的基本信息,如姓名、编号、职位,为了程序中职工信息的安全性,我们将这三个数据设置为 protected ,在保证子类继承的同时避免无意识修改。那么,我们如何获取和设置这三个数据的值呢?

这里就要用到面向对象的第一个特性:封装。我们通过成员函数对外提供设置和修改这三个属性的接口,方便操作。

Worker 基类声明如下:

/* Alkaid#3529 */

class worker
{
public:
	//对外提供获取与更改私有属性的接口
	void set_name(string name);

	string get_name();

	void set_id(string id);

	string get_id();

	void set_post(string post);

	string get_post();

	//纯虚析构,释放子类中开辟在堆区的数据
	virtual ~worker() = 0;

protected:
	string m_name;
	string m_id;
	string m_post;

};

Worker 基类实现如下:

/* Alkaid#3529 */

//对外提供获取与更改私有属性的接口
void worker::set_name(string name)
{
	this->m_name = name;
}

string worker::get_name()
{
	return this->m_name;
}

void worker::set_id(string id)
{
	this->m_id = id;
}

string worker::get_id()
{
	return this->m_id;
}

void worker::set_post(string post)
{
	this->m_post = post;
}

string worker::get_post()
{
	return this->m_post;
}

worker::~worker() {}

细心的读者可能发现,我们将 Worker 基类的析构函数设置为了虚函数,这样写有什么用呢?

实现基类后,我们开始编写职工 Staff 派生类,也就是我们最终要使用的类,用它继承已经准备好的 Worker 基类,同时添加一个数据,薪水,特别的是,我们将薪水值开辟在堆区。

Staff 派生类代码声明如下:

class staff :public worker
{
public:

	//有参构造,添加默认参数以兼容无参构造
	//添加 explicit 关键字,杜绝隐式转换
	explicit staff(string name = " ", string id = "000000", string post = "Employee", int salary = 500);

	//拷贝构造,即便不使用也必须重写,解决潜在问题
	explicit staff(staff& s);

	//重写析构函数,正确释放开辟在堆区的数据
	~staff();


	//对外提供接口获取和修改薪水值
	void set_salary(int salary);

	int get_salary();


	//运算符重载

	//左移运算符重载,直接输出工作者的姓名、编号、职位及其薪水
	friend ostream& operator<<(ostream& cout, staff& s);

	//右移运算符重载,简化后期读入职工信息的代码
	friend istream& operator>>(istream& cin, staff& s);

	//赋值运算符重载,方便类之间相互赋值
	staff& operator=(staff& s);

	//关系运算符重载,用于后序排序
	bool operator<(staff& s);

private:
	//设置整形指针,指向开辟在堆区的整型变量
	int* m_salary;
};

首先,我们在 Staff 派生类中声明了他的构造函数,包括有参构造和拷贝构造,同时实现他的虚构函数,此处需要特别声明:因为子类Staff存在开辟在堆区的数据,那么当父类的引用指向子类的对象时,就会导致父类析构函数无法彻底释放子类开辟在堆区中的数据,导致内存泄露,存在安全隐患,因此,我们有必要将父类的析构函数声明为纯虚函数,使得子类必须重写父类的析构函数,保证堆区数据的安全。

其次,就是子类拷贝构造的实现。因为子类存在堆区数据,从而产生了深浅拷贝的问题,普通的拷贝是浅拷贝,在拷贝过程中只是简单的复制数据,容易产生地址浅拷贝,堆区数据重复被释放的问题,因此我们有必要考虑到拷贝构造,解决深浅拷贝的问题。

最后,我们声明了 Staff 派生类的一系列重载运算符,为了简化后续代码,我选择再此处进行运算符重载,从而降低后续读入、输出数据的代码复杂度。

Staff 派生类代码实现如下:

//有参构造,添加默认参数以兼容无参构造
staff::staff(string name, string id, string post, int salary)
{

	this->m_name = name;
	this->m_id = id;
	this->m_post = post;

	//在堆区开辟空间存放薪水值,带来了深浅拷贝与析构函数的相关问题
	this->m_salary = new int(salary);
}

//拷贝构造,即便不使用也必须重写,解决潜在问题
staff::staff(staff& s)
{
	this->m_name = s.m_name;
	this->m_id = s.m_id;
	this->m_post = s.m_post;

	//深拷贝,否则只会将指针地址进行复制
	*this->m_salary = *s.m_salary;
}

//重写析构函数,正确释放开辟在堆区的数据
staff::~staff()
{
	//确保指针不为空后,再释放
	if (m_salary != NULL)
	{
		delete m_salary;
		m_salary = NULL;
	}
}

//对外提供接口获取和修改薪水值
void staff::set_salary(int salary)
{
	*this->m_salary = salary;
}

int staff::get_salary()
{
	return *m_salary;
}

//赋值运算符重载,方便类之间相互赋值
staff& staff::operator=(staff& s)
{
	this->set_name(s.m_name);
	this->set_id(s.m_id);
	this->set_post(s.m_post);
	this->set_salary(*s.m_salary);

	return *this;
}

//关系运算符重载,用于后序排序
bool staff::operator<(staff& s)
{
	if (this->m_id < s.m_id)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

ostream& operator<<(ostream& cout, staff& s)
{
	cout << "Name : " << s.m_name;

	for (unsigned int i = 0; i < 13 - s.m_name.length(); i++)
	{
		cout << " ";
	}

	cout << "ID : " << s.m_id << "    Post : " << s.m_post;

	return cout;
}

istream& operator>>(istream& cin, staff& s)
{
	cout << "请输入职工姓名:";
	while (1)
	{
		cin >> s.m_name;

		if (s.m_name.length() > 13)
		{
			cout << "名称超出指定长度,请重新输入:";
		}
		else
		{
			break;
		}
	}

	cout << "请输入职工岗位(Boss, Manager, Employee):";
	while (1)
	{
		cin >> s.m_post;

		string str_Boss = "Boss";
		string str_Manager = "Manager";
		string str_Employee = "Employee";

		if (s.m_post == str_Boss || s.m_post == str_Manager || s.m_post == str_Employee)
		{
			break;
		}
		else
		{
			cout << "无该职工岗位,请重新输入:";
		}
	}

	cout << "请输入职工编号(六位数字,如:000001):";
	while (1)
	{
		cin >> s.m_id;

		if (s.m_id.length() < 6)
		{
			cout << "编号长度不足,请重新输入:";
			continue;
		}
		else if (s.m_id.length() > 6)
		{
			cout << "编号长度过长,请重新输入:";
			continue;
		}

		int i = 0;
		while (i < s.m_id.length())
		{
			if (!(s.m_id[i] >= '0' && s.m_id[i] <= '9'))
			{
				break;
			}
			i++;
		}

		if (i != 6)
		{
			cout << "编号必须由纯数字组成,不包含特殊字符,请重新输入:";
		}
		else
		{
			break;
		}
		
	}

	return cin;
}

值得一提的是,为了应对用户可能的非法输入,在读入过程中笔者特意添加了错误处理,对用户的错误输入进行提示,保证代码流畅运行。

到此,我们就完成了第一大任务,类的实现,接下来,我们马不停蹄,实现系统整体架构。

2.系统架构实现

系统涉及功能不少,笔者此处选择对每个功能涉及单独的函数实现对应功能,首先我们来看一下整体架构。

架构代码如下:

/* Alkaid#3529 */

// 菜单函数,显示系统菜单,供用户选择,并根据用户选择的功能进入对应的函数
void menu(vector<staff*>& s);

// 根据指定条件定位对应元素所在位置
int Locate_staff(vector<staff*>& s, string clue);

// key == 1 新增职工
void Add_staff(vector<staff*>& s);

// key == 2 显示职工
void Display_staff(vector<staff*>& s);

// key == 3 查找并显示职工信息
void Find_staff(vector<staff*>& s);

// key == 4 修改职工信息
void Revise_staff(vector<staff*>& s);

// key == 5 删除职工信息
void Remove_staff(vector<staff*>& s);

// key == 6 排序职工信息
void Sort_staff(vector<staff*>& s);

// key == 7 清空职工
void Empty_staff(vector<staff*>& s);

// 交换指定位置的两个元素
void swap(vector<staff*>& s, int index1, int index2);

在实现 Staff 子类后,我选择了使用vector容器来存储职工信息,但是大量职工信息在后续功能实现中会导致数据量处理过大,因此我们思考能不能选择一种更高效、便捷的存储方式呢?

没错,就是指针!

将每个Staff对象的指针共同存储在vector容器当中,每个指针维护一个Staff对象,这样,无论是后续交换容器元素还是删除元素都变得极为简单。

首先我们创立整个系统的入口:

/* Alkaid#3529 */

#include"head.h"

int main()
{
	vector<staff*>s;

	menu(s);

	return 0;
}

我们用一个菜单函数作为指引,在菜单函数中实现打印系统界面,功能选择等操作:

void menu(vector<staff*>& s)
{
	int key = 0;

	while (1)
	{
		cout << "欢迎使用由 Alkaid#3529 开发的职工管理系统,本系统提供以下功能:" << endl;
		cout << "1. 新增职工信息" << endl;
		cout << "2. 显示全体职工" << endl;
		cout << "3. 查找职工信息" << endl;
		cout << "4. 修改职工信息" << endl;
		cout << "5. 删除职工信息" << endl;
		cout << "6. 进行系统排序" << endl;
		cout << "7. 清空管理系统" << endl;
		cout << "0. 退出管理系统" << endl;

		cout << "请选择您需要的功能 (以数字 0 - 7 表示):";

		// 读入 key
		while (1)
		{
			cin >> key;

			if (cin.fail() || key < 0 || key>7)
			{
				cin.clear();
				cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

				cout << "你的输入有误,请重新输入 (以数字 0 - 7 表示):";
			}
			else
			{
				break;
			}
		}
		cout << endl;

		if (key == 1)
		{
			Add_staff(s);
		}
		else if (key == 2)
		{
			Display_staff(s);
		}
		else if (key == 3)
		{
			Find_staff(s);
		}
		else if (key == 4)
		{
			Revise_staff(s);
		}
		else if (key == 5)
		{
			Remove_staff(s);
		}
		else if (key == 6)
		{
			Sort_staff(s);
		}
		else if (key == 7)
		{
			Empty_staff(s);
		}
		else if (key == 0)
		{
			cout << endl << "系统已退出" << endl;
			break;
		}

		system("pause");
		system("cls");
	}

}

循环打印选择界面并读入用户选择的功能,同时对用户的功能选择输入作出处理判断,如果用户正确输入,就进入对应的功能函数。

首先是新增职工函数:

void Add_staff(vector<staff*>& s)
{
	staff* s_temp = new (staff);

	cin >> *s_temp;

	s.push_back(s_temp);

	cout << endl << "新增职工信息成功,";

}

值得一提的是,此处我们选择使用new从堆区申请空间来创建职工,那么删除职工时要注意堆区空间的释放。

接下来是展示全体职工信息的功能函数,因为在类中我们已经对右移运算符进行重载,因此直接输出即可:

void Display_staff(vector<staff*>& s)
{
	for (int i = 0; i < s.size(); i++)
	{
		cout << *s[i] << endl;
	}
	cout << endl;

}

在后续功能实现之前,我们有必要先实现一个功能函数用来查找职工,从而才能为后续一系列功能提供基础,函数功能为根据职工姓名或编号查找对应索引并返回,否则返回 -1:

int Locate_staff(vector<staff*>& s, string clue)
{
	for (int i = 0; i < s.size(); i++)
	{
		if (s[i]->get_name() == clue)
		{
			return i;
		}
	}

	for (int i = 0; i < s.size(); i++)
	{
		if (s[i]->get_id() == clue)
		{
			return i;
		}
	}

	return -1;
}

接下来就是简单的增删改查功能的实现了,并无新奇之处:

void Find_staff(vector<staff*>& s)
{
	cout << "请输入您要查找的职工的姓名或编号:";
	string clue = "";
	cin >> clue;
	cout << endl;

	int pos = Locate_staff(s, clue);

	if (pos == -1)
	{
		cout << "抱歉,无查找结果" << endl << endl;
	}
	else
	{
		cout << *s[pos] << endl << endl;
	}

}

void Revise_staff(vector<staff*>& s)
{
	cout << "请输入你要修改的职工的姓名或编号:";
	string clue = "";
	cin >> clue;
	cout << endl;

	int pos = Locate_staff(s, clue);

	if (pos == -1)
	{
		cout << "抱歉,无查找结果" << endl << endl;
		return;
	}

	cout << *s[pos] << endl << endl;

	staff* s_temp = new(staff);
	cin >> *s_temp;

	delete s[pos];
	s[pos] = s_temp;

	cout << endl << "修改成功,";
}

void Remove_staff(vector<staff*>& s)
{
	cout << "请输入你要删除的职工的姓名或编号:";
	string clue = "";
	cin >> clue;
	cout << endl;

	int pos = Locate_staff(s, clue);

	if (pos == -1)
	{
		cout << "抱歉,无查找结果" << endl << endl;
		return;
	}

	delete s[pos];

	s.erase(s.begin() + pos);

	cout << "删除成功,";
}

对职工进行排序,方便起见笔者选择了选择排序,如果对相关算法感兴趣的话不妨移步笔者的另一篇专栏算法之美,会专门对各种算法做出笔者自己的见解,此处就不赘述了:

void Sort_staff(vector<staff*>& s)
{
	cout << "请选择排序规则(0 - 升序,1 - 降序):";

	string str_0 = "0";
	string str_1 = "1";

	string str_k = "0";

	while (1)
	{
		cin >> str_k;

		if (str_k != str_0 && str_k != str_1)
		{
			cout << "输入有误,请重新输入:";
		}
		else
		{
			break;
		}
	}

	if (str_k == str_0)
	{
		for (int i = 0; i < s.size(); i++)
		{
			int min = i;
			for (int j = i + 1; j < s.size(); j++)
			{
				if (*s[j] < *s[min])
				{
					min = j;
				}
			}
			if (i != min)
			{
				swap(s, i, min);
			}
		}
	}
	else
	{
		for (int i = 0; i < s.size(); i++)
		{
			int max = i;
			for (int j = i + 1; j < s.size(); j++)
			{
				if (*s[max] < *s[j])
				{
					max = j;
				}
			}
			if (i != max)
			{
				swap(s, i, max);
			}
		}
	}

	cout << endl << "排序完成,";

}

在清空容器时需要注意,不只是清空容器,应当首先释放容器中指针指向的堆区中的空间,如果只是单纯的clear的话会导致内存泄漏的致命问题,后续笔者也会专门对此类相关问题作出详细阐述:

void Empty_staff(vector<staff*>& s)
{

	// 逐个释放指针指向空间
	for (int i = 0; i < s.size(); i++)
	{
		delete s[i];
	}

	// 清空容器存储的数据
	s.clear();
}

到此,整个项目的思路就解释完毕了,整个项目笔者尽量融合了面向对象的相关知识,诸如封装、继承、多态,虚函数、纯虚析构、运算符重载等,但笔者能力有限,不足之处,欢迎指正。


总结

面向对象还有很多细节是需要实践才能get到的,本项目也只能作为一个练习供大家参考试用,当然项目设计也有很多弊病有待解决,比这水平有限,但仍追求进步!

后续还会坚持更新新的 C++项目,欢迎大家订阅我的频道,我们下期再见。

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alkaid3529

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值