目录
一、头文件信息框架构建
一、程序目的
实现对职工信息的增删改查,以及文件写入读取的交互,基于B黑马程序员所授职工管理系统的进一步扩展。
二、对类的构建
首先构造一个职工类,类中成员为对职工姓名,序号,电话...之类的属性,其次对不同的职工分别派生出老板,经理,员工这三个子类,用于重写基类中的虚函数,实现各自不同的功能。
#pragma once
#define FILENAME "职工信息.mxy"//定义文件名宏常量,便于修改
#include<iostream>
#include<fstream>
#include<string>
#include<algorithm>//快速排序所需头文件
using namespace std;
class Staff
{
public:
virtual void ShowInfo() = 0;//显示信息的虚构函数
virtual string GetWork() = 0;//获取职工职位的虚函数
int m_id;//职工编号
string m_tel;//电话
int m_salary;//薪水
string m_name;//名字
string m_sex;//性别
int m_type;//这里使用编号代表不同职位
};
class Boss :public Staff
{
public:
Boss(int id, string name, string sex, string tel, int type, int salary);//子了构造函数,创建对象并赋值
void ShowInfo();//对虚函数的重新声明
string GetWork();
};
class Manager :public Staff
{
public:
Manager(int id, string name, string sex, string tel, int type, int salary);
void ShowInfo();
string GetWork();
};
class Employee :public Staff
{
public:
Employee(int id, string name, string sex, string tel, int type, int salary);
void ShowInfo();
string GetWork();
};
这只是搭建了基本框架,我将函数实现写在一个单独的源文件中,便于理解。
在职工的类构建好之后,需要对职工信息的管理构建一个管理类。
class StaffManagement
{
public:
StaffManagement();//构造函数,初始化存储空间
void Menu();//菜单
void Exit();//退出
bool CheckId(int n);//检查职工编号是否重复
void AddEmployee();//添加员工
void Save();//保存
bool CheckFile;//表明文件状态,文件不存在或信息为空为真,反之为假
int GetFileSize();//若需载入,取得职工个数
void InitFile();//初始化文件,将文件信息读取到内存
void ShowEmployee();//显示所有职工信息
int GetRank(int n);//取得职工的当前排序数
void NameSearch(string name);//姓名查找
void DeleteEmployee();//删除职工
void ModifyEmployee();//修改职工
void SearchEmployee();//查找职工并显示信息
void SortEmployee();//对职工进行排序
void DestoryedInfo();//清除所有文件信息
int P_n;//计数器
Staff**P_arr;//指向职工数据内存的二级指针
~StaffManagement();//析构函数,释放内存
};
管理类提供各种函数用以实现各种操作,在此做了个枚举常量,用于对应switch选择,便于后期更改,更新。
enum
{
EXIT,
ADD,
DISPLAY,
DELETE,
MODIFY,
SEARCH,
SAVE,
SORT,
DESTORY
};
以上文件均被作为一个头文件,分文件操作对代码阅读,后续修改,扩展都有好处。
二、程序执行流程的设计
这块比较简单,你可以将操作结束后对屏幕的刷新放在各功能函数末尾(这里采用放在末尾)。也可以放在switch选择函数中。
#include"StaffManagement.h"//引用头文件
void operate()
{
StaffManagement sm;//实例化对象
sm.Menu();//调用菜单
int choice;
//do while()是这类选择进入型流程常用的手段,为直到型循环。
do
{
cout << "请选择:" << endl;
cin >> choice;//输入选择
switch (choice)//根据选项值,进入不同函数模块
{
case EXIT:
sm.Exit();
break;
case ADD:
sm.AddEmployee();
break;
case DISPLAY:
sm.ShowEmployee();
break;
case DELETE:
sm.DeleteEmployee();
break;
case MODIFY:
sm.ModifyEmployee();
break;
case SEARCH:
sm.SearchEmployee();
break;
case SAVE:
sm.Save();
break;
case SORT:
sm.SortEmployee();
break;
case DESTORY:
sm.DestoryedInfo();
break;
default:
cout << "输入无效,请重新输入" << endl;
break;
}
} while (choice);//以0作为结束选项
}
int main()
{
operate();
return 0;
}
main函数起一个开头作用,告诉程序从这里开始执行,我们大可以把程序写在外部。对于时间戳(目前就知道这个)或其他特殊定义,需要定义在main()函数中才有效。
三、各函数模块的实现
一、职工类的类中函数实现
基类职工类作为公共属性,不设置构造函数,只是用来提供数据对象和函数。需要在派生类中创建构造函数,重写基类虚函数。
一、构造函数
Boss::Boss(int id, string name, string sex, string tel, int type, int salary)
{
this->m_id = id;
this->m_name = name;
this->m_type = type;
this->m_sex = sex;
this->m_tel = tel;
this->m_salary = salary;
}//老板类
Manager::Manager(int id, string name, string sex, string tel, int type, int salary)
{
this->m_id = id;
this->m_name = name;
this->m_type = type;
this->m_sex = sex;
this->m_tel = tel;
this->m_salary = salary;
}//经理类
Employee::Employee(int id, string name, string sex, string tel, int type, int salary)
{
this->m_id = id;
this->m_name = name;
this->m_type = type;
this->m_sex = sex;
this->m_tel = tel;
this->m_salary = salary;
}//员工类
可以看到,构造函数用于对对象赋值,避免繁琐的cin>>A.b这样的赋值。
二、展示函数
由于三个都差不多,就拿一个来举例
void Boss::ShowInfo()
{
cout << "职工编号:" << this->m_id
<< "\t职工姓名:" << this->m_name
<< "\t职工性别:" << this->m_sex
<< "\t职工电话:" << this->m_tel
<< "\t职工薪资:" << this->m_salary
<< "\t职工岗位:" << this->GetWork()//因为基类采用编号代表职位,这里要根据编号取得职位名
<< "\t岗位职责:管理公司所有事务" << endl;//这句是对职工职能的描述
}
经理职责:"\t岗位职责:完成老板布置的任务,并下发任务给员工";
员工职责:"\t岗位职责:完成经理布置的任务"
三、取得职位名的函数
string Boss::GetWork()
{
return string("老板");
}
string Manager::GetWork()
{
return string("经理");
}
string Employee::GetWork()
{
return string("员工");
}
返回一个名字即可。
二、管理类的类中函数实现
一、构造函数
因为需要读取文件,构造时要先读取文件中的数据。
StaffManagement::StaffManagement()
{
ifstream in;
in.open(FILENAME, ios::in | ios::binary);//我采用二进制数据(ios::binary),可以把这去掉
if (!in.is_open())//如果文件未打成功
{
this->CheckFile = true;
this->P_arr = NULL;
this->P_n = 0;
in.close();
return;
}
//如果文件为空,则将EOF读走
char ret;
in >> ret;
if (in.eof())
{
this->CheckFile = true;
this->P_arr = NULL;
this->P_n = 0;
in.close();
return;
}
//这里表明文件打开成功且存有数据
this->P_n = this->GetFileSize();//获取职工数
this->P_arr = new Staff*[this->P_n];//开辟存储空间
this->InitFile();//跳转初始化函数
}
二、取得文件职工数量的函数
int StaffManagement::GetFileSize()
{
ifstream in;
in.open(FILENAME, ios::in | ios::binary);
int count = 0, id, type ,salary;
string name, sex, tel;//创建临时变量接收读取文件
while (in >> id && in >> name && in >> sex && in >> tel && in && in >> salary&&in>>type)//循环读取,直到读到文件尾
{
count++;
}
return count;//返回计数值
}
三、初始化函数
void StaffManagement::InitFile()//就是把文件中的数据读取到内存中
{
ifstream in;
in.open(FILENAME, ios::in | ios::binary);
int index = 0, id, type,salary;
string name, sex, tel;
while (in >> id && in >> name && in >> sex && in >> tel && in >> salary && in >> type )
{
Staff*staff = NULL;//创建临时指针,接收创建的对应的职工
switch (type)//根据职位编号选择
{
case 1:
staff = new Employee(id, name, sex, tel, type, salary);
break;
case 2:
staff = new Manager(id, name, sex, tel, type, salary);
break;
case 3:
staff = new Boss(id, name, sex, tel, type, salary);
break;
default:
cout << "初始化错误" << endl;
break;
}
this->P_arr[index] = staff;//将创建的数据地址存給二级指
index++;
}
}
四、菜单函数
这块可以根据自己设计,好像可以再设个登录系统,输入密码,屏幕上显示*等操作。可以自己试试。
void StaffManagement::Menu()
{
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 << "************* 8.清空所有职工信息 ***********" << endl;
cout << "******************************************************" << endl;
}//上面的保存功能已内置于各功能函数中,实际上不需要手动操作,这里是为了拿出来看
五、职工添加函数
这块比较复杂,涉及很多操作,要检查输入ID是否重复,重新开辟内存空间,以及流程设置
void StaffManagement::AddEmployee()
{
int add_n;
while (true)
{
cout << "请输入添加员工的数量" << endl;
cin >> add_n;//可以一次添加多名员工
if (add_n > 0)
{
int newsize = this->P_n + add_n;
Staff**newspace = new Staff*[newsize];//创建新的数量及内存
if (!this->P_arr)//将旧的内存数据拷贝到新开辟的上面
{
for (int i = 0; i < this->P_n; i++)
newspace[i] = this->P_arr[i];
}
for (int i = 0; i < add_n; i++)
{
int id;
string name;
string sex;
string tel;
int salary;
int choice;
while (true)//对错误输入采取循环,直到全部正确
{
cout << "请输入第" << i + 1 << "个新职工的编号:" << endl;
cin >> id;
if (!this->CheckId(id))//检查Id是否重复
{
cout << "职工编号重复,请重新输入" << endl;
}
else
break;
}
cout << "请输入第" << i + 1 << "个新职工的姓名:" << endl;
cin >> name;
cout << "请输入第" << i + 1 << "个新职工的性别:" << endl;
cin >> sex;
cout << "请输入第" << i + 1 << "个新职工的电话:" << endl;
cin >> tel;
cout << "请输入第" << i + 1 << "个新职工的薪资:" << endl;
cin >> salary;
cout << "请选择以下职工岗位:" << endl
<< "1.员工" << endl
<< "2.经理" << endl
<< "3.老板" << endl;
cin >> choice;
Staff*staff = NULL;
switch (choice)
{
case 1:
staff = new Employee(id, name,sex,tel,choice,salary);
break;
case 2:
staff = new Manager(id, name, sex, tel,choice, salary);
break;
case 3:
staff = new Boss(id, name, sex, tel, choice, salary);
break;
default:
cout << "输入无效,请从新输入" << endl;
break;
}
newspace[this->P_n + i] = staff;//与初始化函数相同,不过这是直接从输入中读取
//存入新空间中
}
delete[] this->P_arr;//释放掉旧的内存空间
this->P_arr = newspace;
this->P_n = newsize;
this->CheckFile = false;//让指针指向新的内存,并赋上新的值
cout << "成功添加" << add_n << "名新职工" << endl;
this->Save();//自动调用保存函数
system("pause");//进行屏幕刷新
system("cls");
this->Menu();
break;
}
else
cout << "输入无效,请求重新输入" << endl;
}
}
六、ID检查函数
bool StaffManagement::CheckId(int n)
{
for (int i = 0; i < this->P_n; i++)
{
if (this->P_arr[i]->m_id == n)//遍历查找
return false;
}
return true;
}
七、保存函数
一个简单的文件写入操作
void StaffManagement::Save()
{
ofstream out;
out.open(FILENAME, ios::out | ios::binary);
for (int i = 0; i < this->P_n; i++)
//注意,写入要打空格和换行,一是便于人看,二是避免数字字符读成一个数,或一个字符串
{
out << this->P_arr[i]->m_id << " "
<< this->P_arr[i]->m_name << " "
<< this->P_arr[i]->m_sex << " "
<< this->P_arr[i]->m_tel << " "
<< this->P_arr[i]->m_salary << " "
<< this->P_arr[i]->m_type << endl;
}
out.close();
cout << "保存成功" << endl;
}
八、职工删除函数
void StaffManagement::DeleteEmployee()
{
if (!this->P_n)//计数为零,无人员信息
cout << "删除失败,文件不存在或文件内容为空" << endl;
else
{
cout << "请输入职工编号" << endl;
int n;
cin >> n;
int index = this->GetRank(n);//查找下标的函数
if (index == -1)
cout << "职工信息不存在" << endl;
else
{
for (int i = 0; i < this->P_n - 1; i++)
this->P_arr[i] = this->P_arr[i + 1];//进行数据移动
}
this->P_n--;//计数减一
this->Save();
cout << "删除成功" << endl;
}
}
九、下标查找函数与姓名查找函数
int StaffManagement::GetRank(int n)
{
int index = -1;
for (int i = 0; i < this->P_n; i++)
{
if (this->P_arr[i]->m_id == n)
{
index = i;
break;
}
}
return index;
}
void StaffManagement::NameSearch(string name)//这用于查找职工
{
for (int i = 0; i < this->P_n; i++)
{
if (this->P_arr[i]->m_name == name)//string可以直接比较
this->P_arr[i]->ShowInfo();//直接打印信息
}
}
十、职工修改函数
这部分我们通常做全部修改,选择改的话代码就太复杂了。代码与之前的添加差不多,少了开辟等操作,在原基础上修改。
void StaffManagement::ModifyEmployee()
{
if (!this->P_n)
cout << "修改失败,文件不存在或文件内容为空" << endl;
else
{
cout << "请输入职工编号" << endl;
int n;
cin >> n;
int index = this->GetRank(n);
if (index == -1)
cout << "职工信息不存在" << endl;
else
{
delete this->P_arr[index];
int newid, newtype, newsalary;
string newname, newsex, newtel;
while (true)
{
cout << "请输入新的职工编号" << endl;
cin >> newid;
if (!this->CheckId(newid))
cout << "职工编号重复,请重新输入" << endl;
else
break;
}
cout << "请输入新的职工姓名:" << endl;
cin >> newname;
cout << "请输入新的职工职工性别:" << endl;
cin >> newsex;
cout << "请输入新的职工电话:" << endl;
cin >> newtel;
cout << "请输入新的职工薪资:" << endl;
cin >> newsalary;
cout << "请选择以下职工岗位:" << endl
<< "1.员工" << endl
<< "2.经理" << endl
<< "3.老板" << endl;
cin >> newtype;
Staff*staff = NULL;
switch (newtype)
{
case 1:
staff = new Employee(newid, newname,newsex,newtel, newtype,newsalary);
break;
case 2:
staff = new Manager(newid, newname, newsex, newtel, newtype, newsalary);
break;
case 3:
staff = new Boss(newid, newname, newsex, newtel, newtype, newsalary);
break;
default:
cout << "输入无效,请从新输入" << endl;
break;
}
this->P_arr[index] = staff;
this->Save();
cout << "修改成功" << endl;
}
}
system("pause");
system("cls");
this->Menu();
}
十一、查找职工
void StaffManagement::SearchEmployee()
{
if (!this->P_n)
cout << "文件不存在或文件为空" << endl;
else
{
while (true)
{
cout << "请选择查找方式:" << endl//设置两种查找方式
<< "1.职工编号" << endl
<< "2.职工姓名" << endl;
int choice;
cin >> choice;
if (choice == 1)
{
cout << "请输入职工编号:" << endl;
int id;
cin >> id;
if (this->GetRank(id) == -1)
cout << "无此职工信息" << endl;
else
{
cout << "查找成功,职工信息如下:" << endl;
this->P_arr[this->GetRank(id)]->ShowInfo();
}
break;
}
else if (choice == 2)
{
cout << "请输入职工姓名" << endl;
string name;
cin >> name;
cout << "查找结果如下:" << endl;
this->NameSearch(name);
break;
}
else
cout << "选择无效,请重新选择" << endl;
}
}
system("pause");
system("cls");
this->Menu();
}
十二、职工排序
我设置了好几种排序,这里用的是选择排序,还可以使用algorithm内置的快排函数sort.
void StaffManagement::SortEmployee()
{
if (!this->P_n)
cout << "文件不存在或文件为空" << endl;
else
{
while (true)//分了很多类排序
{
cout << "请选择排序方式:" << endl
<< "1.按职工编号升序" << endl
<< "2.按职工编号降序" << endl
<< "3.按职工姓名升序" << endl
<< "4.按职工职工降序" << endl
<< "5.按职工电话升序" << endl
<< "6.按职工电话降序" << endl
<< "7.按职工薪资升序" << endl
<< "8.按职工薪资降序" << endl;
int choice;
cin >> choice;
for (int i = 0; i < this->P_n; i++)
{
int minormax = i;//选择排序,设置下标
for (int j = i + 1; j < this->P_n; j++)
{
switch (choice)//嵌套选择函数
{
case 1:
if (this->P_arr[minormax]->m_id > this->P_arr[j]->m_id)
minormax = j;
break;
case 2:
if (this->P_arr[minormax]->m_id < this->P_arr[j]->m_id)
minormax = j;
break;
case 3:
//string字符串比较函数
if (this->P_arr[minormax]->m_name.compare(P_arr[j]->m_name) > 0)
minormax = j;
break;
case 4:
if (this->P_arr[minormax]->m_name.compare(P_arr[j]->m_name) < 0)
minormax = j;
break;
case 5:
if (this->P_arr[minormax]->m_tel.compare(P_arr[j]->m_tel)>0)
minormax = j;
case 6:
if (this->P_arr[minormax]->m_tel.compare(P_arr[j]->m_tel)<0)
minormax = j;
break;
case 7:
if (this->P_arr[minormax]->m_salary > this->P_arr[j]->m_salary)
minormax = j;
break;
case 8:
if (this->P_arr[minormax]->m_salary < this->P_arr[j]->m_salary)
minormax = j;
break;
default:
cout << "选择无效,请重新输入" << endl;
choice = 0;
break;
}
}
if (i != minormax)//改变则交换
{
Staff*temp = this->P_arr[i];
this->P_arr[i] = this->P_arr[minormax];
this->P_arr[minormax] = temp;
}
}
if (choice)
{
cout << "排序完成,结果如下>:" << endl;
this->Save();
this->ShowEmployee();//打印排序后的数据
break;
}
}
}
}
十三、职工信息清除
与删库跑路有些相像
void StaffManagement::DestoryedInfo()
{
cout << "确认清除所有信息?" << endl//这里设置Y/N也好
<< "确认请输入1" << endl
<< "返回请输入其它任意数字" << endl;
int choice;
cin >> choice;
if (choice == 1)
{
ofstream out(FILENAME, ios::trunc);//如果文件存在则删除文件(ios::trunc)
//所以那个check
out.close();
if (this->P_arr)
{
for (int i = 0; i < this->P_n; i++)//释放内存空间,要分级释放
{
delete this->P_arr[i];
this->P_arr[i] = NULL;
}
delete[]this->P_arr;
this->P_n = 0;
this->P_arr = NULL;
this->CheckFile = true;
cout << "所有信息已被清除!" << endl;
}
}
else
{
system("cls");
this->Menu();
return;
}
system("pause");
system("cls");
this->Menu();
}
十四、析构函数
因为C++的类会自动调用析构函数释放内存,这里写个个空实现就行
StaffManagement::~StaffManagement(){}
四、总结
可以看到,单个模块的设计并不复杂,都是可以想到,推出来的,关键在于设计思路要流畅