实验5链表操作:
题目:
编写一个链表综合程序,实现以下功能:
- 编写一个链表的构建函数输入任意多条的长度不等的字符串,结束的条件是
直接回车,用动态链表方式处理保存各个字符串, - 编写一个显示函数, 调用即显示前面所有输入过的字符串,
- 编写一个查询函数用以查询前面输入的字符串里是否有某字符串。
- 编写一个文件保存函数, 调用后链表上的所有字符串保存在文件data.txt里.
- 编写一个读文件添加链表数据的函数, 调用后,能把文件data.txt里的字符添加
到当前的链表上 - 构建功能选择菜单, 通过菜单选择运行以上哪个函数的功能.
(广东工业大学计算机)
也可以前往博客观看
c语言菜单实现链表操作和文件读写实验(广工)
第一部分:创建链表与打印
首先我立马打了几个框架,第一次测试:失败
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct strings{
char s[100];
strings* next;
};
strings* head=NULL;//设置头指针,指向NULL;
strings *create(int n)//创建链表
{
strings *head=NULL,*node=NULL,*end=NULL;
head=(strings*)malloc(sizeof(strings));
end=head;//初始链表只有一个头指针,所以尾等于头
while(gets(node->s)&&strcmp(node->s,"")!=0)
//for(int i=0;i<n;i++)
{
node=(strings*)malloc(sizeof(strings));//每一次开辟一个空间
scanf("%s",node->s);
//node->s=a;
end->next=node;
end=node;
}
end->next=NULL;
return head;
}
void print(strings* p)//打印
{
//strings *p=head;
int i=1;
p = p->next;
while(p!=NULL)
{
printf("%d %s",i,p->s);
p=p->next;
i++;
}
}
int main()
{
int n;
scanf("%d",n);
head=create(n);
print(head);
}
运行结果1:
- 无输出
思考:
1、经过测试打印和创建都有问题
修改:
1、更换表达方式
while(gets(node->s)&&strcmp(node->s,"")!=0)
改成
while(gets(node->s)&&strlen(node->s)!=0)
2、更改打印的代码
void print(strings* p)//打印
{
int i=1;
while(p!=NULL)
{
printf("%d %s",i,p->s);
p=p->next;
i++;
}
}
删掉了一行p = p->next;
这样才能输出第一个节点
3、参考运行成功代码
发现原来看的别人的博客写的代码是错的,根本运行不了(流汗)。
第二次测试
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct strings{
char s[100];
strings* next;
};
strings* head=NULL;//设置头指针,指向NULL;
strings * create()//创建链表
{
int n=0;
strings *head=NULL,*node=NULL,*end=NULL;
node=(strings*)malloc(sizeof(strings));
end=node;//初始链表只有一个头指针,所以尾等于头
while(gets(node->s)&&strlen(node->s)!=0)
{
n++;
if(n==1)head=node;
else
end->next=node;
end=node;
node=(strings*)malloc(sizeof(strings));
}
end->next=NULL;
return (head);
}
struct strings *creat()
{
struct strings *head,*p1,*p2;
int n=0;
head=NULL;
p1=( struct strings *) malloc(sizeof(struct strings));
p2=p1;
gets(p1->s);
while(strlen(p1->s)!=0)
{
n=n+1;
if (n==1)
head=p1;
else
p2->next=p1;
p2=p1;
p1=(struct strings *)malloc(sizeof(struct strings));
gets(p1->s);
}
p2->next=NULL;
return(head);
}
void print(strings* p)//打印
{
int i=1;
while(p!=NULL)
{
printf("%d %s ",i,p->s);
p=p->next;
i++;
}
}
int main()
{
head=create();
print(head);
}
运行成功。
打印和创建函数完成。
第二部分:文件读写和添加节点
添加节点放在这里是因为在添加节点的同时顺便写入文件。
1、读取文件
FILE *fp=fopen(data,"r");//data是文件名
if(fp==NULL)
{
printf("Error\n");//如果读取失败输出失败
return;
}
和创建链表相同,每次打开文件都要重新读取重新创建链表;不同的是,输入不一样,变成了从文件读取,使用fscanf()
函数,完整函数如下
解释:
fscanf()
:从文件中读取字符串,但是在遇到第一个空格和换行符时,它会停止读取
struct strings *readfile(char *data)//读取文件并且创建链表
{
FILE *fp=fopen(data,"r");
if(fp==NULL)
{
printf("Error\n");
return NULL;
}//和上面一样
struct strings *head,*node,*end;
node=(struct strings*)malloc(sizeof(struct strings));
end=node;
int n=0;
while(fscanf(fp,"%s",node->s)!=EOF)//和前面不同的是这里,从文件读取
{
n++;
if(n==1)
head=node;
else
end->next=node;
end=node;
node=(struct strings*)malloc(sizeof(struct strings));
}
end->next=NULL;
return head;
}
2、保存到文件
这里要了解几个函数(菜鸟教程)
①fputc()
函数
函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。
②fputs()
函数
函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。
③fprintf()
函数
int fprintf(FILE *fp,const char *format, …) 函数把一个字符串写入到文件中。
void save(struct strings *p,char *data)//保存到文件
{
FILE *fp=fopen(data,"w");
if (fp==NULL)
{
printf("Error\n");
return;
}
//----一模一样的如果读取失败输出失败-----
while(p!=NULL)
{
fprintf(fp,"%s\n",p->s);
p=p->next;
}
fclose(fp);
}
3、添加节点
添加手动输入的字符
struct strings *addtofile(struct strings *head)//添加节点
{
struct strings *ne;
for (;;){
ne=(struct strings *) malloc(sizeof(struct strings));//先开辟空间
gets(ne->s);//输入手动输入的字符
if (strlen(ne->s)!=0)
{
head=addtolist(head,ne);//添加节点函数在下面
}
else{
free(ne);//如果没了就释放掉
break;
}
}
return head;
}
struct strings *addtolist(struct strings *head,struct strings* news)//添加节点
{
struct strings* p=head;
if (head==NULL){//似乎一定要写这个if,不然就无输出
head=news;
news->next=NULL;
return head;
}
while (p->next!=NULL){
p=p->next;
}
p->next=news;
news->next=NULL;
return head;
}
4、程序主体
这是实现文件读写的链表程序,read file读取文件并且输出,在add:后面输入字符,如果回车两次,即没有输入,则会打印整个链表
程序如下
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct strings{
char s[100];
struct strings* next;
};
struct strings* head=NULL;//设置头指针,指向NULL;
struct strings * create()//创建链表
{
int n=0;
struct strings *head=NULL,*node=NULL,*end=NULL;
node=(struct strings*)malloc(sizeof(struct strings));
end=node;//初始链表只有一个头指针,所以尾等于头
while(gets(node->s)&&strlen(node->s)!=0)
{
n++;
if(n==1)head=node;
else
end->next=node;
end=node;
node=(struct strings*)malloc(sizeof(struct strings));
}
end->next=NULL;
return (head);
}
void print(struct strings* p)//打印
{
int i=1;
while(p!=NULL)
{
printf("%s\n",p->s);
p=p->next;
i++;
}
}
void save(struct strings *p,char *filename)//保存到文件
{
FILE *fp=fopen(filename,"w");
if (fp==NULL)
{
printf("Error\n");
return;
}
while(p!=NULL)
{
fprintf(fp,"%s\n",p->s);
p=p->next;
}
fclose(fp);
}
struct strings *readfile(char *data)//读取文件并且创建链表
{
FILE *fp=fopen(data,"r");
if(fp==NULL)
{
printf("Error\n");
return NULL;
}
struct strings *head,*node,*end;
node=(struct strings*)malloc(sizeof(struct strings));
end=node;
int n=0;
while(fscanf(fp,"%s",node->s)!=EOF)//和前面不同的是这里
{
n++;
if(n==1)
head=node;
else
end->next=node;
end=node;
node=(struct strings*)malloc(sizeof(struct strings));
}
end->next=NULL;
return head;
}
struct strings *addtolist(struct strings *head,struct strings* news)//添加节点
{
struct strings* p=head;
if (head==NULL){
head=news;
news->next=NULL;
return head;
}
while (p->next!=NULL){
p=p->next;
}
p->next=news;
news->next=NULL;
return head;
}
struct strings *addtofile(struct strings *head)//添加节点
{
struct strings *ne;
for (;;){
ne=(struct strings *) malloc(sizeof(struct strings));
gets(ne->s);
if (strlen(ne->s)!=0)
{
head=addtolist(head,ne);
}
else{
free(ne);
break;
}
}
return head;
}
int main()
{
printf("read file:\n");
head=readfile("data.txt");//读取文件并创建链表
print(head);//打印
printf("add:\n");
addtofile(head);//添加字符串到链表
printf("display:\n");
print(head);
save(head,"data.txt");//保存
//head=create();
}
第三部分:菜单实现
一、两个键盘读取函数:
1、检测输入按键函数getch()
函数返回按键的数值;
UP = 72
DOWN = 80
LEFT = 75
RIGHT = 77
ENTER = 13
ESC = 27
//按键数值
2、函数kbhit()
检查当前是否有键盘输入,若有则返回一个非0值,否则返回0 。
函数名:kbhit()(VC++6.0下为_kbhit())
用法:int kbhit(void);
包含头文件: include <conio.h>
两者区别:
kbhit() 在执行时,检测是否有按键按下,有按下返回非0值,一般是1;没有按下返回0;
是非阻塞函数。
getch() 在执行时,检测按下什么键,如果不按键该函数不返回,是阻塞函数
二、设置文本样式
我们需要制作一个菜单,使用方向键选择选项,并且高亮显示,这里就需要设置字体格式,使用SetConsoleTextAttribute()
函数可以设置文字颜色和高亮。
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x70);//高亮显示
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x07);//正常显示
这里是简短的测试程序
#include<stdlib.h>//测试读取键盘和设置高亮
#include<conio.h>
#include<windows.h>
#include<stdio.h>
int main()
{
while(true){
if(kbhit()){
char c=getch();
if(GetAsyncKeyState(VK_UP))
printf("up,");
if(GetAsyncKeyState(VK_DOWN))
printf("Down,");
//读取到上或者下就对应输出
if(GetAsyncKeyState(VK_LEFT)){
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x70);
printf("\nHighlight,");
}
if(GetAsyncKeyState(VK_RIGHT)){
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x07);
printf("\nNormal,");
//读取到左或者右就高亮或恢复(如果高亮则恢复,正常则不变)
}
Sleep(100);
}
}
return 0;
}
程序结果
三、显示菜单并且高亮
读取键盘完成了,就需要显示出菜单界面,我们设置一个char数组来储存。
char MenuText[7][30]={
"[A] Create a List ",
"[B] Display the List ",
"[C] Find the Word ",
"[D] Save to File ",
"[E] Read to List ",
"[F] Add to List ",
"[Q] Exit "
};
将他打印出来的话,就是正常的样子。但是我们需要让他高亮,这里就需要思考,如何让特定的选项高亮呢?这里我们在打印菜单函数coutmenu()
函数里面设置了一个变量highlight
,他的值是多少代表是第几行需要高亮。接着我们使用for循环7次(有7行)一次输出,这样菜单里面就有一个选项是高亮的。
void coutmenu(int highlight)//打印菜单函数
{
int i;
printf("\n======================\n");//只是输出边界线
for (i=0;i<7;i++){
if (i==highlight-1)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x70);
//设置高亮
else
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);
//设置正常
printf("%s\n",MenuText[i]);//设置完成后输出
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
printf("======================\n");
}
如图所示(假设这里highlight=2,那么第二行就会高亮)
不知道大家有没有想到,怎么让他按照我们的意愿高亮。想不到也没关系,我也想不到,这里我们通过反复清空屏幕,再反复输出的方式,来实现上下移动。
四、实现选项移动
因为我们要反复输出我们就需要清空屏幕,这里我们用system("cls");
来清空屏幕。
同时我们需要使用sleep()
函数来让程序暂停,如果数值设置太短,我们按方向键的时候,可能已经经过了好多个循环,相当于你按了n多次方向键,会跳的很快。
for(;;){
if (kbhit()){
c=getch();
if (GetAsyncKeyState(VK_UP)){
high=(high>1)?high-1:7;//程序的巧妙之处,超过7则回退到1
Sleep(140);
system("cls");
coutmenu(high);//打印菜单函数
}
if (GetAsyncKeyState(VK_DOWN)){
high=(high<7)?high+1:1;
Sleep(140);
system("cls");
coutmenu(high);
}}}
(这里是另一个函数,所以高亮显示的行号用high
表示)
这个程序的巧妙之处在于每次收到上下的信号就会对变量high做出运算,DOWN的情况是大于7则变成1,小于7则加1。这样在第七行(最后一行),再按一次down就会回到第一行。不过这个回退不是必要的,也可以不加。
五、按回车执行对应程序
在前面的if语句后面接上 if 语句,判断是否回车,回车符是'\r'
,接着用swich语句来判断情况执行语句。
else if(c=='\r'){//判断回车
system("cls");//清空屏幕
coutmenu(high);//再打印
printf("command is [%c]",'A'+high-1);//同时输出在执行的选项(测试用,后面可以删)
switch (high)
{
case 1:printf("1");//后面需要加上代码
break;
case 2:printf("2");
break;
case 3:printf("3");
break;
case 4:printf("4");
break;
case 5:printf("5");
break;
case 6:printf("6");
break;
case 7:printf("7");
break;
}
}
效果如图,按回车后才回显示
第四部分:加上查找功能
没有太大难度,基础的查找操作
void find(struct strings *head)//查找
{
char* word;
gets(word);
struct strings *p=head;
int i=1;
//p=p->next;
while(p!=NULL)
{
if(strcmp(p->s,word)==0)
{
printf("Search successfully\n");
return ;
}
p=p->next;
}
printf("Search unsuccessfully!\n");
return ;
}
第五部分:把菜单整合到链表程序中
所有工作都分工做完了,现在就剩下整合合微调了。
一、判断语句设置
这是整合后的Switch函数,这里的函数一定要调用对,别搞错了,所以函数命名的时候一定要清楚。
switch (high)
{
system("cls");
coutmenu(high);
case 1:
printf("add:\n");//创建链表,并且手动输入
head=create();
break;
case 2:
print(head);//打印
break;
case 3:
printf("Input your word which you want to find:");//查找
find(head);
break;
case 4:
save(head,"data.txt");//保存到文件(会清空原本)
break;
case 5:
//head=readfile("data.txt");
head=addinlist(head,"data.txt");//(读取文件的内容添加到链表后面)
break;
case 6:
printf("add:");//手动输入添加字符串到链表后面
addtofile(head);
break;
case 7:
return 0;//退出
break;
}
//对应处理的程序段
printf("command %c finished",'A'+high-1);
}
二、修改一个函数
这里前面一个函数需要修改才能使用,因为实现的功能是读取文件内容,把内容添加到链表后面,不是手动输入,所以前面的addtofile函数需要修改。
修改前:
struct strings *addtofile(struct strings *head)//添加节点
{
struct strings *ne;
for (;;){
ne=(struct strings *) malloc(sizeof(struct strings));
gets(ne->s);
if (strlen(ne->s)!=0)
{
head=addtolist(head,ne);
}
else{
free(ne);
break;
}
}
return head;
}
先把文件读写的代码加上去FILE *fp=fopen(data,"r");
同样加上是否读取文件成功的判断,gets
改成fscanf(fp,"%s",ne->s)
。
修改后:
struct strings *addinlist(struct strings *head,char* data)
{
struct strings *ne;
FILE *fp=fopen(data,"r");
if(fp==NULL)
{
printf("Error\n");
return NULL;
}
for (;;){
ne=(struct strings *) malloc(sizeof(struct strings));
if (fscanf(fp,"%s",ne->s)!=EOF)
{
head=addtolist(head,ne);
}
else{
free(ne);
break;
}
}
return head;
}
最后就完成了。
附加:按字母键跳转选项
菜单前面有ABCDEF,其实还可以通过按字母跳转,原理相同,关键在于求出高亮的是第几行。
if (c<='z'&& c>='a')c=c-('a'-'A');
if (c<='F' && c>='A'){
//对应处理的程序段
high=c-'A'+1;
system("cls");
coutmenu(high);
printf("command [%c] ",c);
}
完整代码
(运行平台vscode)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <conio.h>
#include <windows.h>
struct strings{
char s[100];
struct strings* next;
};
char MenuText[7][40]={
"|[A] Create a List |",
"|[B] Display the List |",
"|[C] Find the Word |",
"|[D] Save to File |",
"|[E] Read to List |",
"|[F] Add to List |",
"|[Q] Exit |"
};
void coutmenu(int highlight)
{
int i;
printf("\n---------------------------\n");
for (i=0;i<7;i++){
if (i==highlight-1)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x70);
else
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);
printf("%s\n",MenuText[i]);
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
printf("---------------------------\n");
}
struct strings* head=NULL;//设置头指针,指向NULL;
struct strings * create()//创建链表
{
int n=0;
struct strings *head=NULL,*node=NULL,*end=NULL;
node=(struct strings*)malloc(sizeof(struct strings));
end=node;//初始链表只有一个头指针,所以尾等于头
while(gets(node->s)&&strlen(node->s)!=0)
{
n++;
if(n==1)head=node;
else
end->next=node;
end=node;
node=(struct strings*)malloc(sizeof(struct strings));
}
end->next=NULL;
return (head);
}
void print(struct strings* p)//打印
{
int i=1;
while(p!=NULL)
{
printf("%s\n",p->s);
p=p->next;
i++;
}
}
void find(struct strings *head)//查找
{
char* word;
gets(word);
struct strings *p=head;
int i=1;
//p=p->next;
while(p!=NULL)
{
if(strcmp(p->s,word)==0)
{
printf("Search successfully\n");
return ;
}
p=p->next;
}
printf("Search unsuccessfully!\n");
return ;
}
void save(struct strings *p,char *filename)//保存到文件
{
FILE *fp=fopen(filename,"w");
if (fp==NULL)
{
printf("Error\n");
return;
}
while(p!=NULL)
{
fprintf(fp,"%s\n",p->s);
p=p->next;
}
fclose(fp);
}
struct strings *readfile(char *data)//读取文件并且创建链表
{
FILE *fp=fopen(data,"r");
if(fp==NULL)
{
printf("Error\n");
return NULL;
}
struct strings *head,*node,*end;
node=(struct strings*)malloc(sizeof(struct strings));
end=node;
int n=0;
while(fscanf(fp,"%s",node->s)!=EOF)//和前面不同的是这里
{
n++;
if(n==1)
head=node;
else
end->next=node;
end=node;
node=(struct strings*)malloc(sizeof(struct strings));
}
end->next=NULL;
return head;
}
struct strings *addtolist(struct strings *head,struct strings* news)//添加节点
{
struct strings* p=head;
if (head==NULL){
head=news;
news->next=NULL;
return head;
}
while (p->next!=NULL){
p=p->next;
}
p->next=news;
news->next=NULL;
return head;
}
struct strings *addtofile(struct strings *head)//添加节点
{
struct strings *ne;
for (;;){
ne=(struct strings *) malloc(sizeof(struct strings));
gets(ne->s);
if (strlen(ne->s)!=0)
{
head=addtolist(head,ne);
}
else{
free(ne);
break;
}
}
return head;
}
struct strings *addinlist(struct strings *head,char* data)
{
struct strings *ne;
FILE *fp=fopen(data,"r");
if(fp==NULL)
{
printf("Error\n");
return NULL;
}
for (;;){
ne=(struct strings *) malloc(sizeof(struct strings));
if (fscanf(fp,"%s",ne->s)!=EOF)
{
head=addtolist(head,ne);
}
else{
free(ne);
break;
}
}
return head;
}
int main()
{
char c;
int high=1;
coutmenu(high);
for(;;){
if (kbhit()){
c=getch();
if (GetAsyncKeyState(VK_UP)){
high=(high>1)?high-1:7;
Sleep(140);
system("cls");
coutmenu(high);
}
if (GetAsyncKeyState(VK_DOWN)){
high=(high<7)?high+1:1;
Sleep(140);
system("cls");
coutmenu(high);
}
if (c<='z'&& c>='a')c=c-('a'-'A');
if (c<='F' && c>='A'){
//对应处理的程序段
high=c-'A'+1;
system("cls");
coutmenu(high);
printf("command [%c] ",c);
}
else if (c=='\r'){
switch (high)
{
system("cls");
coutmenu(high);
case 1:
printf("Add strings:\n");
head=create();
break;
case 2:
print(head);
break;
case 3:
printf("Input your word which you want to find:");
find(head);
break;
case 4:
save(head,"data.txt");
break;
case 5:
//head=readfile("data.txt");
head=addinlist(head,"data.txt");
break;
case 6:
printf("Add strings:");
addtofile(head);
break;
case 7:
return 0;
break;
}
//对应处理的程序段
printf("command %c finished",'A'+high-1);
}
else if (c=='Q') return 0;
/*printf("read file:\n");
head=readfile("data.txt");//读取文件并创建链表
print(head);//打印
printf("add:\n");
addtofile(head);
printf("display:\n");
print(head);
save(head,"data.txt");
*/
//head=create();
}
}
}