我们已经了解了C++面向对象的编程思想。现在我们利用多态的技术,来实现一个职工管理系统,对这些知识加以巩固和提升。
公司里面职工分为三类:
- 普通员工:完成经理交给的任务;
- 经理:完成老板交给的任务,并下发任务给员工;
- 老板:管理公司中所有事务。
管理系统需要实现的功能如下:
- 退出系统:退出当前管理系统;
- 增加职工信息:实现批量添加职工的功能,将职工编号、姓名、部门编号等信息录入到文件中去;
- 显示职工信息:显示公司内部所有的职工信息;
- 删除职工信息:按照编号删除指定的员工;
- 修改职工信息:按照编号修改职工信息;
- 查找职工信息:按照职工编号或者职工姓名进行查找相关人员的信息;
- 按照编号排序:按照职工编号进行排序,排序规则由用户指定;
- 清空所有文档:清空文件中记录的所有职工信息。
创建主文件
- MainSystem.cpp 存放main函数。
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main(void)
{
return 0;
}
创建管理类
管理类负责内容如下:
- 与用户沟通的菜单界面
- 对职工增删改查的操作
- 与文件的读写交互
创建文件
分别创建workerManager.h和workerManager.cpp
- workerManager.h
#pragma once
#include <iostream>
using namespace std;
class WorkerManager
{
public:
//头文件只有声明,在源文件中实现
WorkerManager();//构造函数
~WorkerManager(); //析构函数
};
- workerManager.cpp
#include "workerManager.h"
//源文件中实现
WorkerManager::WorkerManager()
{
}
WorkerManager::~WorkerManager()
{
}
菜单功能
在管理类WorkerManager中添加成员函数void showManu()
。并在对应源文件实现,代码如下:
void WorkerManager::showManu()
{
cout << "********************************************" << endl;
cout << "************欢迎进入职工管理系统************" << endl;
cout << "**************0.退出管理系统***************" << endl;
cout << "**************1.添加员工信息***************" << endl;
cout << "**************2.显示职工信息***************" << endl;
cout << "**************3.删除职工信息***************" << endl;
cout << "**************4.修改职工信息***************" << endl;
cout << "**************5.查找职工信息***************" << endl;
cout << "**************6.按编照号排序***************" << endl;
cout << "**************7.清空所有数据***************" << endl;
cout << "*******************************************" << endl;
}
测试:
下面我们在主函数中写入相应代码:
#include <iostream>
#include <fstream>
#include <string>
#include"workerManager.h"
using namespace std;
int main(void)
{
WorkerManager wm; //实例化管理者对象
wm.showManu(); //调用显示菜单函数
system("pause");
return 0;
}
测试结果如下:退出功能
我们本着先易后难的原则,先实现一个最容易实现的功能,退出功能。
在main函数中提供分支选择,提供每个功能接口:
int main(void)
{
WorkerManager wm; //实例化管理者对象
int choice = 0;
while (true)
{
wm.showManu(); //调用显示菜单函数
cout << "请输入您的选择:" << endl;
cin >> choice;
switch (choice)
{
case 0: //退出系统
break;
case 1: //增加职工
break;
case 2: //显示
break;
case 3: //删除
break;
case 4: //修改
break;
case 5: //查找
break;
case 6: //排序
break;
case 7: //清空文档
break;
default:
system("cls");
break;
}
}
system("pause");
return 0;
}
退出功能实现
在workerManager类中声明函数:void exitSystem();
并在对应.cpp源文件中实现:
//退出功能
void WorkerManager::exitSystem()
{
cout << "欢迎下次使用:" << endl;
system("pause");
exit(0);
}
测试
在主函数中分支0选项中,调用该退出函数。
创建职工类
职工分为普通员工,经理,老板。我们将三种职工抽象到一个类(worker)中,利用多态管理不同职工的种类。职工的属性为:职工编号,姓名,部门ID.行为有:岗位职责描述,获取岗位名称。
创建worker.h
Worker类作为抽象基类,没有必要创建对应的cpp文件。代码如下:
#pragma once
#include <iostream>
#include <string>
using namespace std;
//职工类基类
class Worker
{
public:
int m_ID; //职工编号
string m_Name; //职工姓名
int m_DeptID; //部门编号
//显示个人信息
virtual void showInfo() = 0;
//获取岗位名称
virtual string getDeptName() = 0;
};
创建普通员工类
普通职工类继承职工抽象类,重写父类虚函数。分别创建employee.h和employee.cpp。
- employee.h
//普通员工文件 继承worker类
#pragma once
#include "worker.h"
using namespace std;
class Employee : public Worker
{
public:
Employee(int id, string name,int dId);//构造函数
//显示个人信息
virtual void showInfo();
//获取岗位名称
virtual string getDeptName();
};
- employee.cpp
#include "employee.h"
Employee::Employee(int id, string name, int dId) //构造函数
{
this->m_DeptID = dId;
this->m_Name = name;
this->m_ID = id;
}
//显示个人信息
void Employee::showInfo()
{
cout << "职工编号:" << this->m_ID
<< "\t职工姓名:" << this->m_Name
<< "\t岗位:" << this->getDeptName()
<< "\t岗位职责:完成经理交给的任务" << endl;
}
//获取岗位名称
string Employee::getDeptName()
{
return string("员工");
}
创建经理类
经理类继承职工抽象类,重写父类虚函数。分别创建manager.h和manager.cpp。
- manager.h
#pragma once
#include <iostream>
using namespace std;
#include "worker.h"
//经理类
class Manager : public Worker
{
public:
Manager(int id, string name, int dId); //构造函数
//显示个人信息
virtual void showInfo();
//获取岗位名称
virtual string getDeptName();
};
- manager.cpp
#include "manager.h"
Manager::Manager(int id, string name, int dId) //构造函数
{
this->m_DeptID = dId;
this->m_Name = name;
this->m_ID = id;
}
//显示个人信息
void Manager::showInfo()
{
cout << "职工编号:" << this->m_ID
<< "\t职工姓名:" << this->m_Name
<< "\t岗位:" << this->getDeptName()
<< "\t岗位职责:完成老板交给的任务,并下发任务给员工" << endl;
}
//获取岗位名称
string Manager::getDeptName()
{
return string("经理");
}
创建老板类
与前者一样,老板继类承职工抽象类,重写父类虚函数。分别创建boss.h和boss.cpp。
- boss.h
#pragma once
#include <iostream>
using namespace std;
#include "worker.h"
//经理类
class Boss : public Worker
{
public:
Boss(int id, string name, int dId); //构造函数
//显示个人信息
virtual void showInfo();
//获取岗位名称
virtual string getDeptName();
};
- boss. cpp
#include "boss.h"
Boss::Boss(int id, string name, int dId) //构造函数
{
this->m_DeptID = dId;
this->m_Name = name;
this->m_ID = id;
}
//显示个人信息
void Boss::showInfo()
{
cout << "职工编号:" << this->m_ID
<< "\t职工姓名:" << this->m_Name
<< "\t岗位:" << this->getDeptName()
<< "\t岗位职责:管理公司所有事物" << endl;
}
//获取岗位名称
string Boss::getDeptName()
{
return string("总裁");
}
测试
在主函数写如下代码,进行测试:
#include <string>
#include "workerManager.h"
#include "worker.h"
#include "employee.h"
#include "manager.h"
#include "boss.h"
using namespace std;
int main(void)
{
//测试代码
Worker *worker = NULL;
worker = new Employee(1, "张三", 1);
worker->showInfo();
delete worker;
worker = new Manager(2, "李四", 2);
worker->showInfo();
delete worker;
worker = new Boss(3, "王五", 3);
worker->showInfo();
delete worker;
system("pause");
return 0;
}
测试结果:可以看到,多态测试成功。
添加职工
功能分析:
在用户批量创建的时候,可能会创建不同种类的员工。我们可以在堆区开辟一个动态的指针数组,存放Worker*
类型的指针,并用一个Worker**
类型的指针进行维护。Worker*
类型指针是一个父类指针,是可以指向不同的子类的对象的。
如下图:
功能实现
在WorkerManager类中添加成员属性:
//记录职工人数
int m_EmpNum;
//职工数组指针
Worker **m_EmpArray;
在WorkerManager构造函数中初始化属性
WorkerManager::WorkerManager()
{
//初始化属性
this->m_EmpNum = 0;
this->m_EmpArray = NULL;
}
在WorkerManager类中添加成员函数
//添加职工
void add_Emp();
在workerManager.cpp中实现成员函数
/添加职工
void WorkerManager::add_Emp()
{
cout << "请输入添加职工的数量:" << endl;
int addNum = 0; //保存用户的输入数量
cin >> addNum;
if (addNum > 0)
{
//添加
//计算新添加的空间的大小
int newSize = this->m_EmpNum + addNum;
//开辟新空间
Worker **newSpace = new Worker *[newSize];
//将原来的数据内容拷贝到新空间下
if (this->m_EmpArray != NULL)
{
for (int i = 0; i < this->m_EmpNum; i++)
{
newSpace[i] = this->m_EmpArray[i];
}
}
for (int i = 0; i < addNum; i++)
{
int id; //职工编号
string name; //职工新名
int dSelect; //部门选择
cout << "请输入第" << i + 1 << "个新职工的编号" << endl;
cin >> id;
cout << "请输入第" << i + 1 << "个新职工的姓名" << endl;
cin >> name;
cout << "请选择该职工的岗位:" << endl;
cout << "1、普通职工" << endl;
cout << "2、经理" << endl;
cout << "3、老板" << endl;
cin >> dSelect;
Worker *worker = NULL;
switch (dSelect)
{
case 1:
worker = new Employee(id, name, 1);
break;
case 2:
worker = new Manager(id, name, 2);
case 3:
worker = new Boss(id, name, 3);
default:
break;
}
//将创建的职工指针保存到数组中
newSpace[this->m_EmpNum + i] = worker;
}
//释放原有的空间
delete[] this->m_EmpArray;
//更改新空间的指向
this->m_EmpArray = newSpace;
//更新职工人数
this->m_EmpNum = newSize;
//提示添加成功
cout << "成功添加" << addNum <<"名新职工"<< endl;
}
else
{
cout << "输入有误!" << endl;
}
}
释放空间
在析构函数中把在堆区开辟的数据给释放掉。不能仅仅释放数组,要先释放每一个数组内的指针所维护的开辟在堆区的对对象,最后再释放数组,算彻底释放空间。
//析构函数,释放堆区数据
WorkerManager::~WorkerManager()
{
if (this->m_EmpArray != NULL)
{
//删除堆区每一个职工对象
for (int i = 0; i < this->m_EmpNum; i++)
{
delete this->m_EmpArray[i];
this->m_EmpArray[i] = NULL;
}
delete[] this->m_EmpArray;
this->m_EmpArray = NULL;
}
}
测试
我们注意到当前文件夹下多了一个worker.txt文件:
可以看到,我们的写文件功能已经实现。
读文件
到目前为止,我们虽然创建了文件,但是程序打开后的数据是为空的。因为我们并没有把文件数据从硬盘中读回来。这就要求在程序开始运行的时候要有一个初始化的操作,把数据读到内存中来。因而就要分以下几种情况来考虑:
(一)文件未创建
在WorkerManager类中添加成员属性m_FileIsEmpty标志文件是否为空:
//文件是否为空标志
bool m_FileIsEmpty;
一旦判断文件不存在,初始化人数为0、文件标志为空、数组为空。在workerManager.cpp中修改构造函数:
WorkerManager::WorkerManager()
{
ifstream ifs;
//1、文件不存在
ifs.open(FILE_NAME, ios::in);
if (ifs.is_open() == NULL)
{
cout << "文件不存在!" << endl;
//初始化人数为0
this->m_EmpNum = 0;
//初始化文件为空
this->m_FileIsEmpty = true;
ifs.close();
return;
}
}
(二)文件数据为空
文件存在,但数据已经被用户清空,这种情况,我们该怎么判断呢?可以尝试着从文件中读取一个字符,如果读到的是文件结尾标志EOF,说明文件内容为空。在构造函数中追加以下代码:
//2、文件存在 数据为空
char ch;
ifs >> ch; //从文件中读取一个字符
if(ifs.eof()) //判断读取到的是否为EOF
{
//文件为空
cout << "文件为空!" << endl;
//初始化人数为0
this->m_EmpNum = 0;
//初始化文件为空
this->m_FileIsEmpty = true;
ifs.close();
return;
}
这里要有一个注意事项:我们添加了文件是否为空的一个标记变量,要注意每次在添加完员工信息之后,要将其值修改为false,否则在后续的操作过程中会出现bug。
(三)文件中已经有数据
- 获取职工人数
在WorkerManager类中添加成员函数int get_EmpNum();
,统计文件中记录的员工个数
//统计文件中人数
int get_EmpNum();
workerManager.cpp中实现
//统计文件中人数
int WorkerManager::get_EmpNum()
{
ifstream ifs;
ifs.open(FILE_NAME, ios::in);
int id;
string name;
int dId;
int num = 0; //人数,初始化为0
while (ifs >> id && ifs >> name && ifs >> dId)
{
num++; //每读取一行,人数加一
}
ifs.close();
return num;
}
构造函数中追加代码
//3、文件存在,并且已经有数据
this->m_EmpNum = this->get_EmpNum(); //统计职工人数
this->m_FileIsEmpty = false;
cout << "职工人数为:" << this->m_EmpNum << endl
- 初始化数组
根据职工的人数,初始化workerManager类中的Worker** m_EmpArray指针;
在WorkerManager类中添加成员函数void init_Emp()
//初始化员工
void init_Emp();
在workerManager.cpp中实现
//统计文件中人数
int WorkerManager::get_EmpNum()
{
ifstream ifs;
ifs.open(FILE_NAME, ios::in);
int id;
string name;
int dId;
int num = 0;
while (ifs >> id && ifs >> name && ifs >> dId)
{
num++;
}
ifs.close();
return num;
}
//初始化员工
void WorkerManager::init_Emp()
{
ifstream ifs;
ifs.open(FILE_NAME, ios::in);
int id;
string name;
int dId;
int index = 0;
while (ifs >> id && ifs >> name && ifs >> dId)
{
Worker *worker = NULL;
if (dId == 1) //普通职工
{
worker = new Employee(id, name, dId);
}
else if (dId == 2) //经理
{
worker = new Manager(id, name, dId);
}
else
{
worker = new Boss(id, name, dId);
}
this->m_EmpArray[index] = worker;
index++;
}
//关闭文件
ifs.close();
}
在构造函数中追加一下代码:
//开辟空间
this->m_EmpArray = new Worker *[this->m_EmpNum];
//数据保存到数组中
this->init_Emp();
//测试代码,将读到的数据打印输出
for (int i = 0; i < this->m_EmpNum; i++)
{
cout << "职工编号:" << this->m_EmpArray[i]->m_ID
<< " 姓名:" << this->m_EmpArray[i]->m_Name
<< " 部门编号:" << this->m_EmpArray[i]->m_DeptID << endl;
}
测试功能
-
文件不存在
我们删除已经创建好的文件,运行程序:
-
文件为空
从回收站中恢复文件,并清空内容:
-
文件存不为空
我们手动添加一些数据:
显示职工
在WorkerManager类中添加函数void show_Emp();
//显示职工
void show_Emp();
在workerManager.cpp中实现
//显示职工
void WorkerManager::show_Emp()
{
//判断文件是否为空
if(this->m_FileIsEmpty == true)
{
cout << "文件不存在,或者记录为空!" << endl;
}
else
{
for (int i = 0; i < m_EmpNum; i++)
{
//利用对多态调用程序接口
this->m_EmpArray[i]->showInfo();
}
}
system("pause");
system("cls");
}
- 测试
-
文件不为空:
-
文件为空:
删除职工
- 功能:传入一个ID号,按ID来删除员工
在删除职工之前,我们先来实现一下一个判断职工是否存在的函数
在WorkerManager类中添加成员函数int isExit(int Id)
//判断职工是否存在
int isExit(int Id);
在workerManager.cpp中实现该函数。遍历数组,找到与传入Id一致的职工,返回该值职工在数组中的位置,否则返回-1;
//判断职工是否存在
int WorkerManager::isExit(int Id)
{
int index = -1;
for (int i = 0; i < this->m_EmpNum; i++)
{
if (this->m_EmpArray[i]->m_ID == Id)
{
index = i; //找到返回 i
return index;
}
}
return index; //没找到返回-1;
}
下面我们来考虑删除职工的问题。
在WorkerManager类中添加成员函数 void del_Emp()
//删除职工
void del_Emp();
在workerManager.cpp中实现该函数。我们维护的是一个数组,对于数组的删除,我们需要找到要删除的元素的位置,从这一个位置到倒数第二个位置结束,后面的元素依次向前一个元素移动。最后记录数组元素个数的变量值减一即可。
//删除职工
void WorkerManager::del_Emp()
{
if(this->m_FileIsEmpty)
{
cout << "文件不存在,或者记录为空!" << endl;
}
else
{
//按照职工编号删除
cout << "请输入要删除的职工编号:" << endl;
int id;
cin >> id;
int index = this->isExit(id);
if (index != -1)//说明职工存在,接下来要删除掉index位置上的职工
{
//从第index个数据开始,之后的数据往前移动一个位置
for (int i = index; i < this->m_EmpNum - 1; i++)
{
this->m_EmpArray[i] = this->m_EmpArray[i + 1];
}
//更新一下数组中的人员个数
this->m_EmpNum--;
//同步更新到文件中
this->save();
cout<< "删除成功!" << endl;
}
else
{
cout << "该职工不存在!" << endl;
}
}
system("pause");
system("cls");
}
测试
1.删除已存在的员工:
-
删除不存在的员工
我们已经把1号员工删除了,我们再来删除一次,看看会不会按我们预期输出:
修改职工
按照职工的编号对职工的信息进行修改并保存
在WorkerManager类中添加成员函数void mod_Emp();
//修改职工
void mod_Emp();
在workerManager.cpp中实现该函数。根据用户输入的ID号,调用isExit()
函数找到它在数组中的位置,删除它。然后再new出来一个新的员工,输入对应的信息之后,放到数组中。
//修改职工
void WorkerManager::mod_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空!" << endl;
}
else
{
cout << "请输入修改的职工编号:" << endl;
int id;
cin >> id;
int ret = this->isExit(id);
if (ret != -1)
{
//查找到编号的职工
delete this->m_EmpArray[ret];
int newId = 0;
string newName = "";
int dSelect = 0;
cout << "查找到:" << id << "号职工,请输入新职工编号:" << endl;
cin >> id;
cout << "请输入姓名:" << endl;
cin >> newName;
cout << "请输入新的岗位:" << endl;
cin >> dSelect;
Worker *worker = NULL;
switch (dSelect)
{
case 1:
worker = new Employee(newId, newName, dSelect);
break;
case 2:
worker = new Manager(newId, newName, dSelect);
break;
case 3:
worker = new Boss(newId, newName, dSelect);
break;
default:
break;
}
//更新数据到数组中
this->m_EmpArray[ret] = worker;
cout << "修改成功!" << endl;
this->save();
}
else
{
cout << "查无此人!" << endl;
}
}
system("pause");
system("cls");
}
测试:
会提示我们“查无此人!”。
再来看看文件里面的内容:
查找职工
我们提供两种方式来查找职工:按照编号查询和按照姓名查找。按照姓名查找要查出所有的同名的信息出来
在WorkerManager中添加void find_Emp();
函数
//查找职工
void find_Emp();
在WorkerManager.cpp中实现
//查找职工
void WorkerManager::find_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或者记录为空!" << endl;
}
else
{
cout << "请输入查找的方式!" << endl;
cout << "1、按照职工编号查找" << endl;
cout << "2、按照职工姓名查找" << endl;
int select = 0;
cin >> select;
if (select == 1)
{
//按照编号查找
int id;
cout << "请输入查找的职工编号:" << endl;
cin >> id;
int ret = this->isExit(id);
if (ret != -1)
{
//找到职工
cout << "查找成功!该职工信息如下" << endl;
this->m_EmpArray[ret]->showInfo();
}
else
{
cout << "查无此人!" << endl;
}
}
else if (select == 2)
{
//按照姓名查找
string name;
cout << "请输入查找的姓名:" << endl;
cin >> name;
bool flag = false; //判断是否查找到 默认没有查到
for (int i = 0; i < m_EmpNum; i++)
{
if (this->m_EmpArray[i]->m_Name == name)
{
flag = true;
cout << "查找成功!职工编号为" << this->m_EmpArray[i]->m_ID << "的员工信息如下:" << endl;
this->m_EmpArray[i]->showInfo();
}
}
if(flag==false)
{
cout << "查无此人!" << endl;
}
}
else
{
cout << "输入有误!" << endl;
}
}
system("pause");
system("cls");
}
测试
- 按工号查找:
-
查找的员工存在
-
查找的员工不存在
- 按姓名查找
我们在我文件里手动加入一个名为“张全蛋”的员工,用于检测按照姓名查找时聪明的情况。
-
查找的人存在
-
查找的人不存在
测试结果与预期一致。
排序
采用选足择排序的思路,提供按职工工号排序的方式,可以进行升序和降序排序,并显示出来。
在WorkerManager类中添加成员函数void sort_Emp();
//排序
void sort_Emp();
在WorkerManager.cpp中实现
//排序
void WorkerManager::sort_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或者记录为空!" << endl;
system("pause");
system("cls");
}
else
{
cout << "请选择排序方式:" << endl;
cout << "1、按职工编号进行升序" << endl;
cout << "2、按职工编号进行降序" << endl;
int select = 0;
cin >> select;
for (int i = 0; i < m_EmpNum; i++)
{
int minOrMax = i;
for (int j = i + 1; j < this->m_EmpNum; j++)
{
if (select == 1) //升序
{
if (this->m_EmpArray[i]->m_ID > this->m_EmpArray[j]->m_ID)
{
minOrMax = j;
}
}
else //降序
{
if (this->m_EmpArray[i]->m_ID < this->m_EmpArray[j]->m_ID)
{
minOrMax = j;
}
}
}
//判断一开始认定的最小值或最大值是不是计算的最小值或者最大值
if (i != minOrMax)
{
Worker *temp = this->m_EmpArray[i];
this->m_EmpArray[i] = this->m_EmpArray[minOrMax];
this->m_EmpArray[minOrMax] = temp;
}
}
cout << "排序成功!" << endl;
//this->save();
this->show_Emp();//排序后显示所有职工
}
}
测试
我们在网文件里手动添加两个员工数据,用于测试排序函数。添加后文件内容如下:-
升序
-
降序
-
文件为空
下面我们把文件内容清空,再来做一个测试:
测试结果于预期一致。
清空数据
清空数据要给用户一个后悔的语余地,一旦清空数据,就再也找不回来了,所以要有一个再次确认的操作。
在WorkerManager类中添加一个成员函数void claen_File();
//清空文件
void claen_File();
在头文件中实现该成员函数。清空的时候不能仅仅把文件清空,要把内存中开辟的所有空间都要清空。与析构函数类似,要先清空数组中每个指针维护的对象,然后在清空数据组。才算彻底清空。
//清空文件
void WorkerManager::claen_File()
{
cout << "确认清空?" << endl;
cout << "1、确认" << endl;
cout << "2、返回" << endl;
int select = 0;
cin >> select;
if (select == 1)
{
//清空文件
ofstream ofs(FILE_NAME, ios::trunc);
ofs.close();
if (this->m_EmpArray != NULL)
{
//删除堆区每一个职工对象
for (int i = 0; i < this->m_EmpNum; i++)
{
delete this->m_EmpArray[i];
this->m_EmpArray[i] = NULL;
}
delete[] this->m_EmpArray;
this->m_EmpArray = NULL;
this->m_EmpNum = 0; //人数清零
this->m_FileIsEmpty = true; //文件为空标志为空
}
cout << "清空成功!" << endl;
}
system("pause");
system("cls");
}
测试
我们先来看一下文件内容:可以看到清空成功了。