提示:本文记录了学习完C++之后使用一个“职工信息管理系统”训练的感想和总结。
基于C++的职工信息管理系统总结
一、项目的总体架构
本项目是对职工的信息进行记录,并通过文件的读写操作将数据保存在文本文件中,方便以后查看。规划了一个Manage类,用来对信息的输入、显示、删除、查找等功能进行实现,而具体的员工方面,由于员工只是所在岗位有区别,其他的信息如年龄、姓名等都一样,所以可以使用继承和多态的思想,创建一个Workman类,作为员工类的抽象类(基类),而具体的员工如:大老板、小老板、普通员工等可以继承基类实现。
在Manage类中,建立一个Workman类型的二级指针,用来作为数组存放公司的每一个员工。在这里,之所以使用二级指针,是因为数组中只能存放类型一致的元素,但是职员类型分为三种:大老板、小老板、普通员工,所以必须用他们的指针进行存放。而他们的指针都是继承的基类Workman,因此可以存放到一个二维数组中。在系统中添加新员工时,可以根据待添加的员工类型,在堆区new出一个同样类型的数据,并保存到上述的二维数组中。
二、关键代码
1.创建空间及内存拷贝
代码如下:
//更新公司中总的人数
int old_num = this->workers_num;
int new_num = old_num + in_num;
this->workers_num = new_num;
//为新成员开辟新空间
if (this->Workers_arr != NULL)
{
Workman** tmp_workers = new Workman * [new_num]; //重新分配空间
//注意:memcpy函数中第三个参数必须为 sizeof(Workman*) 而不可以是sizeof(Workman),否则会带来错误!
memcpy(tmp_workers, this->Workers_arr, (old_num) * sizeof(Workman*) ); //复制原数组数据
//for (int i = 0; i < old_num; i++)
//{
// tmp_workers[i] = this->Workers_arr[i]; //本段循环用来替代memcpy函数
//}
delete[] this->Workers_arr; //释放原有空间
this->Workers_arr = tmp_workers; //重新更新指针指向
}
else
{
this->Workers_arr = new Workman * [new_num]; //空数组直接创建空间
}
2.析构函数实现内存的回收
代码如下:
Manage::~Manage()
{
cout << "Manage析构函数调用!" << endl;
//if (this->Workers_arr != NULL) //这种方式仅仅将堆区的数组指针释放了,没有将用户创建的每一个堆区对象释放!是不合理的!
//{
// delete[] this->Workers_arr;
// this->Workers_arr = NULL;
//}
for (int i = 0; i < this->workers_num; i++) //堆区内存释放
{
if (this->Workers_arr[i] != NULL)//如果数据不为空
{
delete this->Workers_arr[i]; //先释放每一个堆区的对象
this->Workers_arr[i] = NULL;
}
}
delete[] this->Workers_arr; //再释放堆区数组
this->Workers_arr = NULL;
}
注意在析构函数中实现内存回收时,一定要注意深浅拷贝的问题!!。
三、调试过程中的相关问题
1.编译器报错:访问权限冲突:this指针是0xCCCCCCCC或是:0xCDCDCDCD
这种错误在百度上可以查到很多条相关信息,具体的原因就是指针的内存出错,要么就是使用了没有进行初始化的指针,或者是结构体等,必须要仔细查看程序中是否有指针创建出来后,程序员没有new出一片空间给这个指针分配内存!
我在debug时,出现了好多次这一问题:
原因就在于我直接加载了下面的Get_info()函数,而此时this->Workers_arr只是被定义了,而没有手动为他开辟内存空间,从而带来上述报错!
//重新加载文本中已有的数据
void Manage::Get_info()
{
string tmp;
int line_num = 1; //行数
int count = 0;
string id;
string name;
string age;
string sex;
string department; //1 大老板 2 小老板 3 员工
Workman* tmp_worker = NULL; //临时变量在程序结束后系统自动释放,不用手动delete
ifstream ifs;
ifs.open(FILENAME, ios::in);
while (ifs >> name && ifs >> id && ifs >> sex && ifs >> age && ifs >> department)
{
if (department == "大老板")
tmp_worker = new BigBoss(id, name, String_to_short(age.c_str()), (sex == "男" ? 0 : 1), 1);
else if (department == "小老板")
tmp_worker = new SmallBoss(id, name, String_to_short(age.c_str()), (sex == "男" ? 0 : 1), 2);
else
tmp_worker = new Employee(id, name, String_to_short(age.c_str()), (sex == "男" ? 0 : 1), 3);
this->Workers_arr[count++] = tmp_worker;
}
ifs.close();
}
正确的方法:必须先为this->Workers_arr开辟空间,再调用Get_info()函数:
//为原有数据分配空间
this->Workers_arr = new Workman * [num];
//将文本中的数据加载到系统内存中
this->Get_info();
2.使用函数memcpy进行内存复制带来的运行错误
函数memcpy是C++中在 string.h 中(C++是 cstring)声明的函数。
函数原型:
void *memcpy(void *destin, void *source, unsigned n);
作用:
以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。
函数有三个参数,第一个是目标地址,第二个是源地址,第三个是数据长度。
注意事项:
- 数据长度(第三个参数)的单位是字节(1byte = 8bit)。
- 该函数有一个返回值,类型是void*,是一个指向destin的指针。
- 使用这一函数务必确保第三个参数(待复制第二个数据)的长度,否则会带来内存泄漏!
这里我原来使用的代码:
memcpy(tmp_workers, this->Workers_arr, (old_num) * sizeof(Workman) ); //复制原数组数据
运行时总会出现意外的错误,因为有时候添加人员时没有问题,但是有时候会出现意想不到,摸不到头脑的错误,排查了好久后发现:
tmp_workers是一个Workman**类型的指针,在拷贝数据的时候,必须拷贝类型相同的字节长度,因此修改为:
memcpy(tmp_workers, this->Workers_arr, (old_num) * sizeof(Workman**) );
之后,问题就得到了解决!
使用memcpy函数时,特别要注意数据长度。
如果复制的数据类型是char,那么数据长度就等于元素的个数。而如果数据类型是其他(如int, double, 自定义结构体等),就要特别注意数据长度的值。
好的习惯是,无论拷贝何种数据类型,都用 n * sizeof(type_name)的写法。
四、C++项目中文件管理经验
- 对于自定义的头文件要使用双引号包含;而对于系统给定的头文件要使用<>进行包含
- 先创建一个main.cpp文件,作为程序的核心代码(可以更换为其他的名字,中文也可!)
- 对于每一个类,创建一个.cpp和.h文件,类的声明(及内部的成员函数和成员变量)都放在.h文件中,而类的具体函数实现需要在.cpp文件中进行。
- **需要注意的是,**由于类的声明和实现不在同一个文件中进行,因此在对自己写的源文件(.cpp中)成员函数进行具体实现时,必须加上作用域:即类型::。
- 在自己写的类中,对自己写的源文件(.cpp中)成员函数进行具体实现时,如果需要调用到该类的其他成员变量及成员函数,必须使用this指针指向这一变量或函数,如:this->name等。
- **注意:**在每一个自己写的.h下,都要包含iostream.h和using namespace std;
五、总结
代码的经验必须多写才能总结,之前我一直比较懒,以为看看书就可以了,现在才发现必须要多动手实践才可以将知识转换为实际!加油吧!
具体的工程文件代码详见我的博客C++专栏。