项目要求
从文件中读取特定格式的文件内容,然后创建结构链表输出,并实现删除后读入文件的功能。
读取文件结构格式:
结构为:
名:LIsa
姓:Sanders
SNN:111-11-1111
课程数:3
课程ID 编号 部分
ENEE 114 0103
CMSC 412 0101
ENME 515 0123
结构分析:
实现需要两个结构: 一个课程结构,一个学生结构,其中,学生结构中包括课程结构数组,数量与所选课程数挂钩,通过学生结构创建链表。
因此,设定结构如下:
struct course
{
char course_name[5];
int course_ID;
int course_Section;
};
struct stu
{
char first_name[100];
char last_name[100];
char SSN[15];
int course_number;
struct course course_a[10];
struct stu* next;
};
实现步骤
由于博主选择使用类方法来实现项目,因此有部分内容与常规方法有所差别。
创建类对象
首先,类中的私有结构是抽象数据,即,并不是具体的实例。因此,要想创建有一条结构链表的对象,可以采用类似于队列寻址的方法。
如下所示:
/// <summary>
///队列使用的内存空间最大数
/// </summary>
stu* front;
stu* rear;
static const int MAX = 10;
int Num_stu;
其中:
front 为指向链表的头指针,保存了链表的首地址。
rear 为指向链表的尾指针,由链表的增长,不断向后延伸。
Num_stu 记录了链表包含的结构数。
然后是类创建的构造与析构函数:
//构造函数
Student_course();
//析构函数
~Student_course();
类方法构思
思考需要实现的功能:
1.读取文件并输出链表
//从文件中读取数据
void ReadInputFile(char* filename);
2.根据其他文件中条件进行删除
其中,删除有两种要求,因此要设立两个辅助函数与两个删除函数
//从文件中读取SNN输出
char* Snn(char* filename);
//删除SNN学生
void DeleteStudent(char* SNN);
//辅助函数:对比
bool Compare(stu*, stu*);
//删除文件中的学生列表
void ProcessDeleteFile(char* filename);
3.显示链表与写入文件
由于,显示链表与写入文件,其实是同一方法(都是输出流方法),只是作用有些许差别,因此博主选择较为简易的方法,将两者一同实现。
当然,在代码实现上并不简便,显得臃肿而不美观。其实如果用输出流对象引用,(ostream& os),来创建函数其实更好。
//打印链表
void PrintToFile(char* filename);
实现
根据头文件确定函数的功能后,可以在主函数里构思大致的代码思路:(下述主函数代码为最终代码,可以看到,创建类对象后,基本是使用类方法,得到成果)。
int main()
{
//创建初始链表
SC sca;
//引入目标文件名
char filename[13] = "students.txt";
char file_name[11] = "output.txt";
char file_delSNN[14] = "deleteSNN.txt";
char file_final[16] = "outputFinal.txt";
char file_delstu[11] = "delete.txt";
std::cout << "\tThe Initially student liat is:\n";
//得到链表
sca.ReadInputFile(filename);
//打印
sca.PrintToFile(file_name);
//删除文件内学生
sca.ProcessDeleteFile(file_delstu);
//删除snn目标
char* snn = sca.Snn(file_delSNN);
sca.DeleteStudent(snn);
//得到最后链表
std::cout << "\n\tThe Final student list is:\n";
//得到删除后链表
sca.PrintToFile(file_final);
//sca.~Student_course();
return 0;
}
函数功能
下面对函数功能简易讲解:
构造函数与析构函数
由于,实际上的私有成员变量其实都是指针类型,因此,只能使用默认构造函数进行类对象的初始化。
SC::Student_course()
{
front = NULL;
rear = NULL;
Num_stu = 0;
}
SC::~Student_course()
{
stu* temp;
while (front != NULL)
{
temp = front;
front = front->next;
delete temp;
}
std::cout << "done.\n";
}
补充:析构函数其实没必要如此设立,博主设立此函数的目的是想实验析构函数提前使用是否会提前结束类对象的生命周期,结果表示:是博主想多了,析构函数人为提前调用并不会影响周期,甚至可能会出现一些错误。
读取文件创建链表
由于之前已经创建了指向为NULL的两个头尾指针,因此,现在要分别对读取文件和链表进行讨论。
根据题目要求,在上述已经设立了满足数据读取条件的结构成员。
因此,这里需要完成的功能为:将文件中一个结构的数据读取到链表的一个节点中,然后链表末尾指针向下移动,最后读取到“*****”时,文件结束。
首先,创建文件输入流对象(ifstream)并且创建移动指针:
using namespace std;
ifstream InFile;
InFile.open(filename);
if (!InFile.is_open())
{
cout << "Could not open the file " << filename << endl;
cout << "The Program teminating.\n";
exit(EXIT_FAILURE);
}
stu* addstudent = new stu();
stu* pf = new stu();
pf->next= addstudent;
front = pf;
然后,开始读取循环,直到读取到相应字符,循环停止, 所以采用while循环:
InFile.get(addstudent->first_name, 100).get();
static const char b[6] = "*****";
//如果文件不读取完毕,则持续读取
while (strcmp(addstudent->first_name, b) != 0)
{
InFile.get(addstudent->last_name, 100).get();
InFile.get(addstudent->SSN, 15).get();
InFile >> addstudent->course_number;
//循环输入结构内的课程结构
for (int i = 0; i < addstudent->course_number; i++)
{
InFile >> addstudent->course_a[i].course_name;
InFile >> addstudent->course_a[i].course_ID;
InFile >> addstudent->course_a[i].course_Section;
}
InFile.get();
//释放内存,重置输入
addstudent->next = new stu();
addstudent = addstudent->next;
//delete pf;
rear = addstudent;
Num_stu++;
InFile.get(addstudent->first_name, 100).get();
}
最后,关闭文件:
InFile.close();
删除函数
函数要求
项目要求使用两种方式,使用两个文件,对学生列表进行删除。因此,设立了四个函数,完成这一功能。
第一个删除函数:
要求在文件中读取要删除学生的SNN,然后在输出的结构链表中删除。
文件内容如下:
因此,设立一个函数读取字符串,另一个根据字符串进行链表对比删除。
//从文件中读取SNN输出
char* Snn(char* filename);
//删除SNN学生
void DeleteStudent(char* SNN);
第二个删除函数:
要求读取删除学生的信息,然后根据学生名字进行删除。
文件内容如下:
因此设立一个判断函数,与一个删除函数,结合进行删除。
//辅助函数:对比
bool Compare(stu*, stu*);
//删除文件中的学生列表
void ProcessDeleteFile(char* filename);
函数代码
1.输出SNN的字符串指针的函数:
char* SC::Snn(char* filename)
{
using namespace std;
ifstream InFile;
InFile.open(filename);
if (!InFile.is_open())
{
cout << "Could not open the file " << filename << endl;
cout << "The Program teminating.\n";
exit(EXIT_FAILURE);
}
stu* addstudent = new stu();
InFile.get(addstudent->SSN, 15).get();
return addstudent->SSN;
***思路:***创建结构体指针,使用结构体指针指向需要输出的值。
这样做的好处是,不必再次创建字符串指针,直接使用更加便捷。但要注意的是,指针回收后,动态分配的内存依旧存在,消耗空间较多。
2.删除对应的SNN学生的函数:
//删除与文件内SNN相同的学生
void SC::DeleteStudent(char* SNN)
{
stu* pf = front;
while (pf->next != rear)
{
if (strcmp(SNN, pf->next->SSN) == 0)
{
stu* p1 = pf->next;
pf->next = p1->next;
delete p1;
Num_stu--;
};
if (pf->next == rear)
break;
pf = pf->next;
}
}
思路:创建移动指针,用字符串对比的库函数“strcmp”对比字符串,相同则删去并向下移动,不同则直接向下移动。
3.对比函数
//辅助函数(对比链表名字是否相同)
bool SC::Compare(stu* del, stu* pH)
{
int a = 0;
if (strcmp(del->first_name, pH->first_name) == 0)
if (0 == strcmp(del->last_name, pH->last_name))
a = 1;
return a;
}
思路:嵌套判断,确保删除目标的姓,名完全相同,再进行返回。
4.删除学生函数
//删除文件内学生
void SC::ProcessDeleteFile(char* filename)
{
using namespace std;
ifstream InFile;
InFile.open(filename);
if (!InFile.is_open())
{
cout << "Could not open the file " << filename << endl;
cout << "The Program teminating.\n";
exit(EXIT_FAILURE);
}
stu* delS = new stu();
stu* pf;
//移动指针
pf = front;
InFile.get(delS->first_name, 100).get();
static const char b[6] = "*****";
//如果文件不读取完毕,则持续读取
while (strcmp(delS->first_name, b) != 0)
{
InFile.get(delS->last_name, 100).get();
InFile.get(delS->SSN, 15).get();
InFile >> delS->course_number;
//循环输入结构内的课程结构
for (int i = 0; i < delS->course_number; i++)
{
InFile >> delS->course_a[i].course_name;
InFile >> delS->course_a[i].course_ID;
InFile >> delS->course_a[i].course_Section;
}
//吃掉回车键
InFile.get();
//删除目标节点
while (pf->next != rear)
{
if (Compare(delS, pf->next))
{
stu* p1 = pf->next;
pf->next = p1->next;
delete p1;
};
if (pf->next == rear)
break;
pf = pf->next;
}//pf指向完毕
//重置
pf = front;
//释放内存,重置输入
//释放内存,重置输入
stu* pd = delS;
delS->next = new stu();
delS = delS->next;
delete pd;
InFile.get(delS->first_name, 100).get();
}
InFile.close();
}
**思路:**删除函数的思路,前半部分与读取函数相同:都需要从文件中读取学生信息,然后创建链表。 因此,问题的本质实际上是,读取链表,然后两链表对比删除相同项。
后半部分则是,对对比部分进行删除,思路是,对比输出的结构信息,然后遍历一次链表,进行删除,然后再次循环,直至文件读取结束。
注意点:
可以看到,博主选择了在读取删除信息的同时,对目标链表进行了删除。
这样做好处是,节省了空间,因为每一次读取创造结构后,移动指针向下移动的同时,释放了上一个链表的结构内存。这样做的前提是,我们并不需要输出删除信息。
如下所示:(释放内存)
pf = front;
//释放内存,重置输入
//释放内存,重置输入
stu* pd = delS;
delS->next = new stu();
delS = delS->next;
delete pd;
InFile.get(delS->first_name, 100).get();
打印链表函数
上文提到过,输出流类(ostream,ofstream,osstream等)的基类是ostream,即它们都可以使用基类的方法,所以,输出流类的格式都十分相似。
因此,如果不熟悉ofstream,我们可以先单方面创造一个cout(控制台输出)函数。再使用ofstream对象仿造cout创建即可。
//打印链表元素
void SC::PrintToFile(char* filename)
{
using namespace std;
ofstream OutFile;
//排除打开错误
OutFile.open(filename);
if (!OutFile.is_open())
{
cout << "Could not open the file " << filename << endl;
cout << "The Program teminating.\n";
exit(EXIT_FAILURE);
};
//OutFile.open(filename, ios::out | ios::app);//持续输入
//从程序输出到文件中 OutFile
//创建移动指针
stu* pf = front->next;
while (pf ->next!=rear )
{
pf = pf->next;
OutFile << "firstname: " << pf->first_name << endl;
OutFile << "lastname: " << pf->last_name << endl;
OutFile << "SNN: " << pf->SSN << endl;
OutFile << "choice course_number: " << pf->course_number << endl;
OutFile << "number\t "<<"course_Name\tID" << endl;
cout << "firstname: " << pf->first_name << endl;
cout << "lastname: " << pf->last_name << endl;
cout << "SNN: " << pf->SSN << endl;
cout << "choice course_number: " << pf->course_number << endl;
cout << "number\t " << "course_Name\tID" << endl;
for (int i = 0; i < pf->course_number; i++)
{
OutFile << i + 1 << "\t- " << pf->course_a[i].course_name
<< pf->course_a[i].course_ID << "- "
<< setw(4)<< setfill('0')//使输出位数保证为4
<< pf->course_a[i].course_Section << endl << endl;
cout << i + 1 << "\t- " << pf->course_a[i].course_name
<< pf->course_a[i].course_ID << "- "
<<setw(4)<<setfill('0')
<< pf->course_a[i].course_Section << endl << endl;
}
}
}
需要注意的是: cout是ofstream类型创造的一个对象,是由编译器在内部创造的,所以我们可以直接使用,cin也是这个道理。但ofstream对象,我们必须自己创造,然后才能类似于cout一样的去使用。
函数运行结果
输出文件结果:
控制台输出结果:
如有错误或改正建议,可以在评论区留言或指正。