C语言结合文件操作的链表实例
一、 功能
设计一个学生成绩管理系统,要求:
录入学生成绩
查询学生成绩
删除学生成绩
修改学生成绩
二、 ADT接口
/* 初始化一个链表 */
void InitializeList(List * plist);
/* 确定链表是否为空 */
bool ListIsEmpty(const List *plist);
/* 确定链表是否已满 */
bool ListIsFull(const List *plist);
/* 确定链表中的项数 */
unsigned int ListItemCount(const List *plist);
/* 在链表的末尾添加项 */
bool AddItem(Item item, List * plist);
/* 把函数作用于链表中的每一项 */
void Traverse(const List *plist, void(*pfun)(Item item));
/* 释放已分配的内存(如果有的话) */
void EmptyTheList(List * plist);
下面的代码实例需要用到上述部分函数,此处只展示函数名,具体接口函数很容易在网上或书上找到,此例程是套用《C Primer Plus(第六版)》的链表接口源码(程序清单17.3、程序清单17.5)。
三、 使用链表
链表结构
struct grade
{
int code;
int grade;
} Item;
typedef struct node
{
Item item;
struct node * next;
} Node;
typedef Node * List;
链表功能
添加成绩:添加成绩本质上是给链表添加一个项。代码逻辑是根据用户输入初始化一个项然后添加在链表上。
/* 添加成绩 */
void addg(List * plist)
{
Item temp;
if (ListIsFull(plist))
puts(" 成绩已满");
else
{
printf(" 请输入学生编号(数字):");
while (scanf("%d", &temp.code) != 1)
{
while (getchar() != '\n')
continue;
printf(" 请输入正确的学生编号(数字):");
}
while (getchar() != '\n')
continue;
printf(" 请输入学生成绩(数字):");
while (scanf("%d", &temp.grade) != 1)
{
while (getchar() != '\n')
continue;
printf(" 请输入正确的学生成绩(数字):");
}
while (getchar() != '\n')
continue;
if (AddItem(temp, plist) == false)
{
fprintf(stderr, " 系统出错\n");
exit(EXIT_FAILURE);
}
}
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
}
成绩查询:成绩查询本质上是在链表中寻找一个项(并且在找到后显示该项相关信息)。代码逻辑是根据用户输入初始化一个项的部分数据域(本例是学生编号),然后遍历链表寻找与其匹配的项,若找到输出该项信息(本例是输出学生成绩)。
/* 成绩查询 */
void findg(List * plist)
{
Item temp;
List p = *plist;
if (ListIsEmpty(plist))
puts(" 没有成绩录入");
else
{
printf(" 请输入学生编号(数字):");
while (scanf("%d", &temp.code) != 1)
{
while (getchar() != '\n')
continue;
printf(" 请输入正确的学生编号(数字):");
}
while (getchar() != '\n')
continue;
while (p)
{
if (p->item.code == temp.code)
{
printf(" 该学生成绩为%d\n", p->item.grade);
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
return;
}
if (p->next)
p = p->next;
else
break;
}
printf(" 查无此人\n");
}
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
}
成绩修改:成绩修改本质上是在链表中寻找所需项,并且在找到后更改该项信息。代码逻辑是根据用户输入初始化一个项的部分数据域(本例是学生编号),然后遍历链表寻找与其匹配的项,若找到可更改该项信息(本例是更改学生成绩)。
/* 成绩修改 */
void revisiong(List * plist)
{
Item temp;
List p = *plist;
if (ListIsEmpty(plist))
puts(" 没有成绩录入");
else
{
printf(" 请输入学生编号(数字):");
while (scanf("%d", &temp.code) != 1)
{
while (getchar() != '\n')
continue;
printf(" 请输入正确的学生编号(数字):");
}
while (getchar() != '\n')
continue;
while (p)
{
if (p->item.code == temp.code)
{
printf(" 该学生成绩为%d\n", p->item.grade);
printf(" 请输入修改后的成绩(数字):");
while (scanf("%d", &p->item.grade) != 1)
{
while (getchar() != '\n')
continue;
printf(" 请输入正确的学生成绩(数字):");
}
while (getchar() != '\n')
continue;
puts(" 修改完成");
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
return;
}
if (p->next)
p = p->next;
else
break;
}
printf(" 查无此人\n");
}
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
}
成绩删除:成绩删除本质上是在链表中删除指定项。代码逻辑是根据用户输入初始化一个项的部分数据域(本例是学生编号),然后遍历链表寻找与其匹配的项,若找到可从链表中删除该项。
/* 成绩删除 */
void deleteg(List * plist)
{
Item temp;
List p, q;
p = q = *plist;
if (ListIsEmpty(plist))
puts(" 没有成绩录入");
else
{
printf(" 请输入学生编号(数字):");
while (scanf("%d", &temp.code) != 1)
{
while (getchar() != '\n')
continue;
printf(" 请输入正确的学生编号(数字):");
}
while (getchar() != '\n')
continue;
if (p->item.code == temp.code) // 第一个项为查找目标项时
{
q = p;
p = q->next;
free(q);
puts(" 成功删除");
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
return;
}
while (p)
{
if (p->next->item.code == temp.code)
{
q = p->next;
p->next = q->next;
free(q);
puts(" 成功删除");
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
return;
}
if (p->next)
p = p->next;
else
break;
}
printf(" 查无此人\n");
}
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
}
浏览所有:浏览所有本质上是依次展示链表的项的信息。代码逻辑是运用ADT接口的void Traverse(const List *plist, void(*pfun)(Item item));函数,该函数可以实现对链表的项依次使用函数void(*pfun)(Item item)。
/* 浏览所有 */
void seeg(List * plist)
{
if (ListIsEmpty(plist))
puts(" 没有成绩录入");
else
{
printf(" 学生成绩如下:\n");
Traverse(plist, showg);
}
puts(" 请按回车键返回菜单界面");
getchar();
system("CLS"); // 清屏
}
/* 浏览成绩函数 */
void showg(Item item)
{
printf(" %d: %d\n", item.code, item.grade);
}
代码量多是判断用户输入,真正的链表操作代码量少且接口函数的使用让逻辑非常清晰。此外,为了让用户界面更加美观,此例程用到了清屏函数system("CLS");。
四、 文件操作
为了使该系统使用之后数据得以保留,本例程需要用到文件操作。由于链表的一个项有两个变量,分别存储学生编号和学生成绩,故使用两个文件来分别储存学生编号和学生成绩。假设文件非空,那么程序刚开始运行的时候得把文件的数据读出来给链表。本例程使用最笨的方法:
先计算文件的数据量(此量即之后初始化链表的项数);
把文件内容存到字符串数组中,同时把字符串数组转换为整型数组(因为本例链表的项的变量的类型都是整型);
把数组按顺序赋给链表(此步骤本质上是链表的初始化,即添加项给链表);
链表部分的操作,就是本系统的功能部分:添加成绩、成绩查询、成绩修改、成绩删除、浏览所有等;
链表操作完后把链表的项一个一个重新赋给数组,同时计算链表的项数;
把整型数组转换为字符串数组同时存入文件;
把创建的链表free掉
5、6步骤其实是3、2步骤的逆向操作,此设计对文件为空的情况也适用,前提是文件存在,文件是ANSI的文本文件。值得注意的是,数组和链表赋值部分容易忽略项数或者错误计算项数,这会导致文件存入部分出错从而产生乱码。
FILE *fp1, *fp2;
int n, i;
n = i = 0;
char cr[SLEN];
/* 计算文件的数据量n */
if ((fp1 = fopen("c.txt", "r")) == NULL)
{
fprintf(stdout, " 打开系统失败\n");
exit(EXIT_FAILURE);
}
while (fscanf(fp1, "%s", cr) != EOF)
n++; // 计算文件的数据量a
rewind(fp1);
if (fclose(fp1) != 0)
printf(" 关闭系统失败\n");
/* ***************** */
char c1[SLEN]; int c2[n];
char gs1[SLEN]; int gs2[n];
/* 读取文件 */
if ((fp1 = fopen("c.txt", "r")) == NULL)
{
fprintf(stdout, " 打开系统失败\n");
exit(EXIT_FAILURE);
}
while (fscanf(fp1, "%s", cr) != EOF)
{
sprintf(c1, "%s", cr);
c2[i++] = atoi(c1); // 把文件1中的数据一个一个存到c数组
}
i = 0;
rewind(fp1);
if (fclose(fp1) != 0)
printf(" 关闭系统失败\n");
if ((fp2 = fopen("gs.txt", "r")) == NULL)
{
fprintf(stdout, " 打开系统失败\n");
exit(EXIT_FAILURE);
}
while (fscanf(fp2, "%s", cr) != EOF)
{
sprintf(gs1, "%s", cr);
gs2[i++] = atoi(gs1); // 把文件2中的数据一个一个存到gs数组
}
rewind(fp2);
if (fclose(fp2) != 0)
printf(" 关闭系统失败\n");
/* ******** */
int choice;
List grades;
Item temp;
/* 更新链表 */
InitializeList(&grades);
if (ListIsFull(&grades))
{
fprintf(stderr, " 系统出错\n");
exit(1);
}
for (i = 0; i < n; i++)
{
temp.code = c2[i];
temp.grade = gs2[i];
if (AddItem(temp, &grades) == false)
{
fprintf(stderr, " 系统出错\n");
exit(EXIT_FAILURE);
}
system("CLS"); // 清屏
}
/* ******** */
/* 系统菜单 */
puts("= = = = = = = = = = = = = = = = = = =");
puts("| 学生信息管理系统 |");
puts("= = = = = = = = = = = = = = = = = = =\n");
while ((choice = menu()) != 0)
{
switch (choice)
{
case 1: addg(&grades); // 添加成绩
break;
case 2: findg(&grades); // 成绩查询
break;
case 3: revisiong(&grades); // 成绩修改
break;
case 4: deleteg(&grades); // 成绩删除
break;
case 5: seeg(&grades); // 浏览所有
break;
default: puts(" 系统出错,请重新输入:");
}
}
/* ******** */
/* 更新数组 */
List pre = grades;
n = i = 0;
while (pre)
{
c2[i] = pre->item.code;
gs2[i++] = pre->item.grade;
n = i; // 计算链表的数据量n
pre = pre->next;
}
pre = grades;
/* ******** */
/* 存入文件 */
if ((fp1 = fopen("c.txt", "w")) == NULL)
{
fprintf(stdout, " 打开系统失败\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < n; i++)
{
sprintf(c1, "%d", c2[i]);
fprintf(fp2, "%s\n", c1); // 把c数组的数据一个一个存到文件1中(更新文件)
}
rewind(fp1);
if (fclose(fp1) != 0)
printf(" 关闭系统失败\n");
if ((fp2 = fopen("gs.txt", "w")) == NULL)
{
fprintf(stdout, " 打开系统失败\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < n; i++)
{
sprintf(gs1, "%d", gs2[i]);
fprintf(fp2, "%s\n", gs1); // 把gs数组的数据一个一个存到文件2中(更新文件)
}
rewind(fp2);
if (fclose(fp2) != 0)
printf(" 关闭系统失败\n");
/* ******** */
EmptyTheList(&grades);
puts(" 成功退出系统");