写在前面
这几天我一直在做我们《C++与数据结构》课程的大作业——c++人事管理系统。在写的过程中遇到了很多困难,也让我深刻地体会到“纸上得来终觉浅,绝知此事要躬行”。特别是编程,虽然我们看数据结构书感觉其中的很多数据结构很简单,但是实际写起来就会发现很多意想不到的问题,唯有不断地练习不断地思考才能取得长久的进步。
项目要求
编写一个简单的人事管理系统,要求功能如下
用链表存储、文件输入、输出。
具有建立、插入、删除、查询和打印功能。
平台不限、链表可用公共模板类。
管理的人员有:
本科生:姓名、性别、年龄、身份证号码、高考总分
脱产研究生:姓名、性别、年龄、身份证号码、专业
在职研究生:姓名、性别、年龄、身份证号码、学号、专业、工资
职工:姓名、性别、年龄、身份证号码、工资、岗位
教师:姓名、性别、年龄、身份证号码、工资、专业
项目思路
我最开始想的是:
- 建立一个基类Person类,其中包含最基本的姓名、性别、年龄、身份证号码;
- 从Person类中派生出本科生Student类,脱产研究生ReleasedUndergraduate类,在职研究生ServingUndergraduate类,职工Staff类以及教师Teacher类,其中每个类在继承了基类的成员基础上,各自添加自己特有的成员;
- 在每一个类中定义getInfo函数,用来从获取从控制台输入的各种信息;
- 为了更加熟悉链表这一数据类型,我没有使用STL中的list容器,而是自己写了一个LinkList链表类,包含了Node结点和链表LinkList,其中Node结点包含了data数据域和* next指针;Linklist包含了* head头结点和链表长度size
- 为了能够一次性处理五种不同的人群(本科生、脱产、在职、职工、老师),所以我想把LinkList写成一个模板类,使用template< typename T>
- 项目要求的建立、查找、删除、打印等功能在链表中实现
后来我发现一个很严峻的问题:不同于网上大部分涉及到链表结构的源码,我如果要用这个LinkList的模板的话,我传进去的参数本身是一个数据结构体,而不是单一的数据类型变量(如int,string);而且这些不同的数据结构体其本身包含的数据成员也是不同的。 换句话说,编译器是不知道我传进去的结构体具体有哪些成员。这样使得我不知道有些功能怎么通过类的成员函数来实现。比如我想写一个类的成员函数实现通过姓名查找的功能,我起初的写法是
void LinkList<T>::findByName(const string& name) {
Node<T>* pcur = head->next;
while (pcur != NULL) {
if (name == pcur->data.name)
……
}
}
很明显这样是非法的。
后来我采用的方法是在LinkList类外写一个查找函数,然后将链表和要查找的姓名字符串作为参数传入,再进行比较,比如
void findStudentName(LinkList<Student>& listForStudent, Student stu)
{
Node<Student>* pcur = listForStudent.head->next;
bool flag = false; //查找成功标志
while (pcur != NULL) {
if (stu.name == pcur->data.name) {
cout << "\t\t*-----------------------------------------------------*" << endl;
cout << "查找到姓名为 " << stu.name << " 的相关信息: " << endl;
cout << pcur->data;
flag = true;
break;
}
pcur = pcur->next; //下一数据
}
if (flag == false) {
cout << "没有找到相关人员信息!" << endl;
}
}
但这样写的弊端,我自认为有以下几点:
- 为了五种不同的数据类型,要写五个类似的函数;不够泛型化
- 没有在数据结构类内部完成,不够简洁;在以后的调用中不够方便
通过这样在类外写函数的方法,我实现了查找和删除两大功能。
其余功能的实现要点:
- 数组长度:类成员函数,直接通过size成员可以得到
- 末尾添加数据append:类成员函数,通过操作指针得到;
- 在指定位置添加数据:类成员函数,先遍历至指定位置,再操作指针,改变相关结点的next指针得到;
- 删除remove:先遍历判断,再设置相关操作指针并改变相关结点的next,注意要delete释放内存
- 打印print: 类成员函数,data虽然有很多项,不能通过成员运算符进行逐个输出(和上述情况是一个原因),但是可以通过重载输出运算符<<直接print整个data数据域
除此之外,还要做一个菜单。一级菜单选定要处理的人员类型;二级菜单是针对链表的操作
感想心得
这是我到目前为止写的行数最长的一个demo,虽然难度比较小,网上相关的代码也比较多,但是“麻雀虽小,五脏俱全”。使我对c++的模板编程、链表数据结构有了更深的了解,实现功能的喜悦与成就感也愈发强烈。
项目源码
/*用所学过的知识编写一个简单的人事管理系统,要求功能如下
用链表存储、文件输入、输出。
具有建立、插入、删除、查询和打印功能。
平台不限、链表可用公共模板类。
管理的人员有:
本科生:姓名、性别、年龄、身份证号码、高考总分
脱产研究生:姓名、性别、年龄、身份证号码、专业
在职研究生:姓名、性别、年龄、身份证号码、学号、专业、工资
职工:姓名、性别、年龄、身份证号码、工资、岗位
教师:姓名、性别、年龄、身份证号码、工资、专业
*/
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int first, second; //一、二级菜单选项
//Person基类
class Person {
public:
string name;
char sex;
int age;
string idNum;
public:
Person() = default;
Person(const string& nam, char s, int ag, const string& id) : name(nam), sex(s), age(ag), idNum(id) {
}
void getInfo(); //获取相关信息
};
//Person类获取相关信息getInfo
void Person::getInfo()
{
cout << "请输入姓名:";
cin >> name;
cout << "性别(m/f):";
cin >> sex;
cout << "年龄: ";
cin >> age;
cout << "身份证号码: ";
cin >> idNum;
}
//本科生类
class Student : public Person{
private:
int score;
public:
Student() = default;
Student(const string& nam, char s, int ag, const string& id, int sc):Person(nam, s, ag, id), score(sc) {
}
void getInfo(); //获取相关信息
friend ostream & operator<<(ostream &, Student &); //重载<<
};
//Student类重载<<
ostream & operator<<(ostream &os, Student &stu)
{
os << "姓名: " << stu.name << endl;
os << "性别: " << stu.sex << endl;
os << "年龄: " << stu.age << endl;
os << "身份证号码: " << stu.idNum << endl;
os << "高考总分: " << stu.score << endl;
return os;
}
//Student类获取相关信息getInfo
void Student::getInfo()
{
Person::getInfo();
cout << "高考总分: ";
cin >> score;
}
//脱产研究生类
class ReleasedUndergraduate : public Person{
private:
string major;
public:
ReleasedUndergraduate() = default;
ReleasedUndergraduate(const string& nam, char s, int ag, const string& id, const string& maj) : Person(nam, s, ag, id), major(maj) {
}
void getInfo(); //获取相关信息
friend ostream & operator<<(ostream &, ReleasedUndergraduate &); //重载<<
};
//ReleasedUndergraduate类重载<<
ostream & operator<<(ostream &os, ReleasedUndergraduate &ru)
{
os << "姓名: " << ru.name << endl;
os << "性别: " << ru.sex << endl;
os << "年龄: " << ru.age << endl;
os << "身份证号码: " << ru.idNum << endl;
os << "专业: " << ru.major << endl;
return os;
}
//ReleasedUndergraduate类获取相关信息getInfo
void ReleasedUndergraduate::getInfo()
{
Person::getInfo();
cout << "专业: ";
cin >> major;
}
//在职研究生类
class ServingUndergraduate : public Person {
private:
string major;
int studentNum;
int wage;
public:
ServingUndergraduate() = default;
ServingUndergraduate(const string& nam, char s, int ag, const string& id, const string& maj, int stuNum, int wag) : Person(nam, s, ag, id), major(maj), studentNum(stuNum), wage(wag) {
}
void getInfo();
friend ostream & operator<<(ostream &, ServingUndergraduate &); //重载<<
};
//ServingUndergraduate类重载<<
ostream & operator<<(ostream &os, ServingUndergraduate &su)
{
os << "姓名: " << su.name << endl;
os << "性别: " << su.sex << endl;
os << "年龄: " << su.age << endl;
os << "身份证号码: " << su.idNum << endl;
os << "学号: " << su.sex << endl;
os << "专业: " << su.major << endl;
os << "工资: " << su.wage << endl;
return os;
}
//ServingUndergraduate类获取相关信息
void ServingUndergraduate::getInfo()
{
Person::getInfo();
cout << "学号: ";
cin >> studentNum;
cout << "专业: ";
cin >> major;
cout << "工资: ";
cin >> wage;
}
//职工类
class Staff : public Person{
private:
int wage;
string job;
public:
Staff() = default;
Staff(const string& nam, char s, int ag, const