源码:Makefilmlist()函数
源码:Getin()函数
源码:Insert()函数
源码:Search()函数
源码:Buy()函数
源码:Modify()函数
源码:Refund()函数
源码:SaveMan()函数
部分效果图
注意!注意!注意!!以下这篇文章会有点长,用到的知识涉及到简单的算法:链表操作。还有各种C库函数的使用:fwrite、fread、fopen、strcmp、malloc等等,虽然代码有点长但是细细研究之后会发现并不是很难,相信大家坚持看完都会对链表的构建、删除、插入有比较深刻的认识。另外,需要说明的一点是:流程图,和链表操作图解仅抓住主要矛盾画出关键步骤,一些没有意义的分析就忽略了。本文文字较少,代码图片居多,千言万语都在注释中,废话太多反而妨碍了我们的分析思考。
完整源码压缩包链接:
环境为codeblocks 17.12 的gcc编译,该程序能实现的功能如下:
源码:man_film_print.h头文件
//malloc申请空间
#define MALLOC_MANNODE(p) do{ \
p = (struct ManNode*) malloc(sizeof(struct ManNode)); \
if(p == NULL) {printf("malloc ManNode error!!"); exit(-1);} \
}while(0)
#define MALLOC_FILMNODE(p) do{ \
p = (struct FilmNode*) malloc(sizeof(struct FilmNode)); \
if(p == NULL) {printf("malloc FilmNode error!!"); exit(-1);} \
}while(0)
//电影,订票人信息输出格式
#define HEAD1 "*******************************Film Ticket Buying Center***********************************\n"
#define HEAD2 "^Name^\t\t^Place^\t\t^StateTime^\t ^FinishTime^\t ^Price^\t^RemainNum^\n"
#define HEAD3 "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
#define head1 "****************Booking information center******************\n"
#define head2 "^Name^\t ^ID^\t\t^Sex^ ^FilmName^ ^FilmNum^\t\n"
#define head3 "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
#define FORMAT1 "[%-14s][%-14s][%-10s][%-10s][%-5sRMB][%d]\n"
#define DATA1 data->fData.fName,data->fData.fPlace,data->fData.fStartTime,data->fData.fFinishTime,data->fData.fPrice,data->fData.fNum
#define FORMAT2 "[%-10s][%-10s][%-4s][%-14s][%4d]\n"
#define DATA2 data->mData.mName,data->mData.mID,data->mData.mSex,data->mData.mBuyFilm,data->mData.mBuyNum
extern int iSave;
extern int menu_choice; //接收用户的输入菜单选项
extern char user_in[10]; //接收用户是否保存文件的变量
struct Film{
char fName[30];
char fPlace[20];
char fStartTime[20];
char fFinishTime[20];
char fPrice[10];
int fNum;
};
//结构体嵌套
struct FilmNode{
struct Film fData;
struct FilmNode *Next;
};
struct Man{
char mName[20];
char mID[20];
char mSex[4];
int mBuyNum;
char mBuyFilm[30];
};
struct ManNode{
struct Man mData;
struct ManNode *Next;
};
malloc动态内存分配通过MALLOC_宏定义实现,简化代码。电影票结构体基本成员包括:电影名、地点、开始时间、结束时间、价格、数量,该结构体指针;客户结构体基本成员包括:姓名、注册ID、性别、购买数量、购买电影票名、该结构体指针。另外,在代码容错方面做得还不是很完善,有些输入格式必须要保持一致,如果输入其他的内容有可能导致程序运行出错!
源码:main()函数
#include "man_film.h"
int iSave; //全局变量 相关订票信息是否储存标志
int menu_choice; //接收用户的输入菜单选项
char user_in[10]; //接收用户是否保存文件的变量
int main()
{
//新建订票人信息,影片信息链表指针
struct FilmNode *FilmNodeHead,*FilmNodeCur,*FilmNodeTemp = NULL;
struct ManNode *ManNodeHead,*ManNodeCur,*ManNodeTemp = NULL;
MALLOC_FILMNODE(FilmNodeHead); //malloc申请空间
FilmNodeHead->Next = NULL; //构建结点
FilmNodeCur = FilmNodeHead; //Cur和FilmNodeHead指针指向同一个地方
MALLOC_MANNODE(ManNodeHead);
ManNodeHead->Next = NULL;
ManNodeCur = ManNodeHead;
//构造链表
Makefilmlist(FilmNodeTemp,FilmNodeCur);
Makemanlist(ManNodeTemp,ManNodeCur);
//主菜单打印
Menu();
while(1)
{
Getin(ManNodeHead,FilmNodeHead);
}
return 0;
}
main.c流程图: Makefilmlist函数流程图:
源码:Makefilmlist()函数
(Makemanlist与之类似,不再重复)
/*
**函数名:Makefilmlist
**功能:将film.txt的文件内容按字节大小构建链表
**输入参数:FilmNode结构指针NodeTemp,NodeCur
**返回值:无
*/
void Makefilmlist(struct FilmNode *NodeTemp,struct FilmNode *NodeCur)
{
FILE *pfFilm;
pfFilm = fopen("film.txt","ab+");
if(pfFilm == NULL)
{
printf("Can't open the film.txt!!");
exit(-1);
}
while(!feof(pfFilm)) //当文件位置指针没有到结尾
{
MALLOC_FILMNODE(NodeTemp);
//从pfFilm所指的文件中,每次读出sizeof()个字节保存在NodeTemp的地址中,连续读一次,同时移动文件指针
//若成功读取就返回第三个参数的值
if(fread(NodeTemp,sizeof(struct FilmNode),1,pfFilm) == 1)
{
NodeTemp->Next = NULL; //新结点Temp指针为空
NodeCur->Next = NodeTemp; //原未结点指向新结点Temp
NodeCur = NodeTemp; //Cur指向新结点(跟踪新加入的结点)
}
}
free(NodeTemp); //释放临时指针
fclose(pfFilm); //关闭文件
}
为什么Makefilmlist能构造链表呢??废话不多说,上图:
源码:Getin()函数
/*
**函数名:Getin
**功能:根据用户输入调用相应函数
**输入参数:无
**返回值:无
*/
void Getin(struct ManNode *ManNodeHead,struct FilmNode *FilmNodeHead)
{
system("cls"); //清屏
Menu();
printf("please choose 0~7 to start:");
scanf("%d",&menu_choice);
getchar();
system("cls");
if(menu_choice == 0)
{
char user_in;
if(iSave == 1)
{
printf("Do you want to save the files? <y/n>:");
scanf("%c",&user_in);
getchar();
if(user_in == 'y' || user_in == 'Y')
{
SaveFilm(FilmNodeHead);
SaveMan(ManNodeHead);
}
}
exit(1); //如果输入n或者其他错误输入,直接退出
}
switch(menu_choice)
{
case 1:Insert(FilmNodeHead);
break;
case 2:Search(FilmNodeHead);
break;
case 3:Buy(ManNodeHead,FilmNodeHead);
break;
case 4:Modify(FilmNodeHead);
break;
case 5:Show_Film(FilmNodeHead);
break;
case 6:Show_Man(ManNodeHead);
break;
case 7:Refund(ManNodeHead,FilmNodeHead);
break;
case 8:SaveMan(ManNodeHead);
SaveFilm(FilmNodeHead);
break;
case 0:
exit(1);
default:
printf("Are you kidding me? ?Input the num in the range!Right now!");
}
printf("\nPlease press any key to continue...\n");
//getch输入无显示,不需要回车,直接可以接受
getch();
}
scanf()之后为什么要加getchar呢??我当时是在这上面栽过一个大跟头的额。其实是为了处理回车字符,详细分析看
博客
源码:Insert()函数
/*
**函数名:Insert
**功能:添加影片信息到链表中
**输入参数:电影票链表头指针FilmNodeHead
**返回值:无
*/
void Insert(struct FilmNode *FilmNodeHead)
{
struct FilmNode *fTail,*fCur,*fNew; //fCur指针的作用是判断电影名是否重复
char fName[20]; //接收输入影片名称的变量
fTail = FilmNodeHead; //fTail和头指针指向同一个地方
//让fTail指针指向最后一个节点
while(fTail->Next != NULL)
{
fTail = fTail->Next;
}
while(1)
{
printf("Input the new film name(-1 to end):\n");
scanf("%s",fName);
getchar(); //接收回车字符
if(strcmp(fName,"-1") == 0) //判断输入是否为-1,如果是就退出
break;
fCur = FilmNodeHead->Next; //fCur指针就是头指针Next指向的结点的地址
while(fCur != NULL)
{
if(strcmp(fName,fCur->fData.fName) == 0) //比较每个结点fName是否和输入的fName一样,如果相同则退出
{
printf("This film %s exists!\n",fName);
return;
}
fCur = fCur->Next; //指针Cur指向下一个结点
}
//如果电影名没有和现记录里的电影名重复,就新建一个链表节点
MALLOC_FILMNODE(fNew);
//填充相应信息到新节点成员
strcpy(fNew->fData.fName,fName);
printf("Please input the Start place:\n");
scanf("%s",fNew->fData.fPlace);
printf("Please input the Start time:(format2017/01/01(12:00))\n");
scanf("%s",fNew->fData.fStartTime);
printf("Please input the finish time:(format2017/01/01(12:00))\n");
scanf("%s",fNew->fData.fFinishTime);
printf("Please input the price of ticket:(format0.00)\n");
scanf("%s",fNew->fData.fPrice);
printf("Please input the number of the tickets:\n");
scanf("%d",&fNew->fData.fNum);
//新结点从最后插进,最后结点中的next指向新fNex结点,fNew结点指向NULL
//重新移动fTail指向最后结点
fNew->Next = NULL;
fTail->Next = fNew;
fTail = fNew;
iSave = 1; //全局变量 信息是否储存标志
}
}
Insert流程图: 链表操作如下:
源码:Search()函数
/*
**函数名:Search
**功能:根据电影名称查找对应影片信息
**输入参数:电影票链表头指针FilmNodeHead
**返回值:无
*/
void Search(struct FilmNode *FilmNodeHead)
{
struct FilmNode *FilmNodeSea;
char fName[10];
FilmNodeSea = FilmNodeHead->Next; //Sea指针就是头指针Next指向的结点的地址
if(FilmNodeSea == NULL) //空链表就退出
{
printf("No films record!\n");
return;
}
printf("Input the film name:"); //链表非空
gets(fName); //接收电影名称
Print_FilmHead(); //打印栏目
while(FilmNodeSea != NULL) //Sea指向的结点存在时候
{
if(strcmp(FilmNodeSea->fData.fName,fName) == 0) //找到了对应的影片
{
Print_FilmData(FilmNodeSea); //输出影片相关信息
break;
}
else
FilmNodeSea = FilmNodeSea->Next; //该结点没有符合要求的电影名称,Sea指针指向下一个结点
}
if(FilmNodeSea == NULL) //整个链表都找完啦
printf("Sorry,can't find record!\n");
}
该函数主要在遍历Film链表中,对比各个结点中的FilmNodeSea->fData.fName电影名和要查找的电影名字fName是否匹配,找到就输出电影成员信息。
源码:Buy()函数
/*
**函数名:Buy
**功能:创建订票人新结点、相应影片剩余影票数量减少
**输入参数:电影票链表头指针FilmNodeHead,订票人链表头结点ManNodeHead
**返回值:无
*/
void Buy(struct ManNode *ManNodeHead,struct FilmNode *FilmNodeHead)
{
struct FilmNode *FilmNodeCur,*FilmNodeFer[10];
struct ManNode *ManNodeCur,*ManNodeTemp = 0;
char mName[20],mID[20],mSex[4],mBuyFilm[30],mChoice[2];
int mBuyNum = 0,mRecord = 0,mFlag = 0; //iNUm表示预订票数,iRecord记录符合要求的航班数
ManNodeCur = ManNodeHead; //Cur指针地址和头指针地址一样
while(ManNodeCur->Next != NULL) //循环直到ManNodeCur指向最后一个结点
ManNodeCur = ManNodeCur->Next;
printf("Input the film name:\n");
scanf("%s",mBuyFilm); //用mBuyFilm存储顾客想买的电影名称
FilmNodeCur = FilmNodeHead->Next; //Cur指针就是头指针Next指向的结点的地址
while(FilmNodeCur != NULL) //当电影票信息链表非空时候循环查找
{
if(strcmp(FilmNodeCur->fData.fName,mBuyFilm) == 0) //找到匹配电影
{
FilmNodeFer[mRecord++] = FilmNodeCur; //找到一样的记录在Fer指针数组中,并且记录条数mRecord
break;
}
FilmNodeCur = FilmNodeCur->Next;
}
printf("\nthere are %d flims you can choose!\n",mRecord);
Print_FilmHead(); //打印栏目,为输出影片数据做准备
Print_FilmData(FilmNodeFer[0]);
if(mRecord == 0) //若记录为0,表示没有符合条件的影片
printf("Sorry,no film you can choose!\n");
else //记录条数为1
{
printf("do you want to buy it<y(Y)/n(N)>?\n");
scanf("%s",mChoice); //接收用户选择
getchar(); //提取回车符、否则下面输入姓名的gets函数将获得一个回车符
if(strcmp(mChoice,"y") == 0 || strcmp(mChoice,"Y") == 0)
{
printf("Input your information!\n"); //提示顾客输入信息
MALLOC_MANNODE(ManNodeTemp);
printf("Input your name:");
gets(mName); //接收顾客名字(可以有空格)
strcpy(ManNodeTemp->mData.mName,mName); //复制顾客名字信息到链表新节点的mName
printf("Input your id:");
scanf("%s",mID); //以字符串的方式接收身份证号
strcpy(ManNodeTemp->mData.mID,mID); //将身份证号信息复制到链表新结点的mID
printf("Input your sex(M/F):");
scanf("%s",mSex); //接收性别
strcpy(ManNodeTemp->mData.mSex,mSex);
strcpy(ManNodeTemp->mData.mBuyFilm,mBuyFilm);
if(strcmp(FilmNodeFer[0]->fData.fName,mBuyFilm) == 0) //和顾客要求电影名一致的影片地址都放在Fer数组内
{
if(FilmNodeFer[0]->fData.fNum<1) //判断电影票链表里是否有剩余的票
{
printf("no ticket!");
return;
}
printf("remain %d tickets\n",FilmNodeFer[0]->fData.fNum);
mFlag = 1;
}
if(mFlag == 0)
{
printf("error");
return;
}
printf("Input the film numbers that you want:\n");
scanf("%d",&mBuyNum); //输入预订的票数,剩余票数相应减少
FilmNodeFer[0]->fData.fNum = FilmNodeFer[0]->fData.fNum - mBuyNum; //修改剩余票数
ManNodeTemp->mData.mBuyNum = mBuyNum; //订票人票数信息录入
ManNodeCur->Next = ManNodeTemp; //原来的尾结点指向新结点
ManNodeTemp->Next = NULL; //新结点(未结点)指向NULL
ManNodeCur = ManNodeTemp; //Cur跟踪结点
printf("success!\n");
iSave = 1;
}
}
}
void类型的返回值若用return应 “return;”,不能返回一个数字,这样会产生警告。
Buy()函数流程图: || 链表操作如下:
源码:Modify()函数
/*
**函数名:Modify
**功能:修改对应影片信息
**输入参数:电影票链表头指针FilmNodeHead
**返回值:无
*/
void Modify(struct FilmNode *FilmNodeHead)
{
struct FilmNode *FilmNodeCur;
char fName[30];
FilmNodeCur = FilmNodeHead->Next; //头指针的next指向Cur(Cur指针就是头指针Next指向的结点的地址)
if(FilmNodeCur == NULL) //空链表
{
printf("No film to modify!\n");
return;
}
else //非空链表(意味着有结点有数据筛选)
{
printf("Input the film name you want to modify:");
scanf("%s",fName); //接收电影名称输入
getchar();
while(FilmNodeCur != NULL) //单结点非空
{
if(strcmp(FilmNodeCur->fData.fName,fName) == 0) //如果找到的结点存储的fName和输入的电影名称一样就退出
break;
else
FilmNodeCur = FilmNodeCur->Next; //没有找着指针指向下一个结点接着找
}
if(FilmNodeCur) //找到一样的电影名称(Cur指针非空)
{ //录入更改后的数据
printf("Please input the film name:\n");
gets(FilmNodeCur->fData.fName);
printf("Please input the Start place:\n");
gets(FilmNodeCur->fData.fPlace);
printf("Please input the Start time:(format2017/01/01 12:00)\n");
gets(FilmNodeCur->fData.fStartTime);
printf("Please input the finish time:(format2017/01/01 12:00)\n");
gets(FilmNodeCur->fData.fFinishTime);
printf("Please input the price of ticket:(RMB)\n");
scanf("%s",FilmNodeCur->fData.fPrice);
printf("Please input the number of the tickets:\n");
scanf("%d",&FilmNodeCur->fData.fNum);
printf("You had succeed to change!!\n");
iSave = 1; //全局变量 相关订票信息是否储存标志
return;
}
else //没有找到形同的电影名称
printf("Can't find the ticket!\n");
}
}
其实该函数操作和Search很类似,Search找到目标结点之后直接输出结点信息就完事了,但是Modify函数将输出变为修改罢了。还有Show_Film、Show_Man函数也是同样的套路,所以这两函数以下仅给出代码。
源码:Show_Film()函数
/*
**函数名:Show_Film
**功能:输出全部影片动态信息
**输入参数:电影信息链表头指针FilmNodeHead
**返回值:无
*/
void Show_Film(struct FilmNode *FilmNodeHead)
{
struct FilmNode *FilmNodeCur;
FilmNodeCur = FilmNodeHead->Next; //头指针的next指向Cur(Cur指针就是头指针Next指向的结点的地址)
Print_FilmHead(); //输出栏目
if(FilmNodeHead->Next == NULL) //空链表
{
printf("No film tickets!\n");
}
else
{
while(FilmNodeCur != NULL) //非空链表
{
Print_FilmData(FilmNodeCur); //输出各个影片信息(输出各个链表数据)
FilmNodeCur = FilmNodeCur->Next; //输出当前结点数据后,Cur指针指向下一个结点
}
}
}
源码:Show_Man()函数
/*
**函数名:Show_Man
**功能:输出全部订票人动态信息
**输入参数:订票人信息链表头指针ManNodeHead
**返回值:无
*/
void Show_Man(struct ManNode *ManNodeHead)
{
struct ManNode *ManNodeCur;
ManNodeCur = ManNodeHead->Next; //头指针的next指向Cur(Cur指针就是头指针Next指向的结点的地址)
Print_ManHead(); //输出栏目
if(ManNodeHead->Next == NULL) //空链表
{
printf("No Man !\n");
}
else
{
while(ManNodeCur != NULL) //非空链表
{
Print_ManData(ManNodeCur); //输出各个订票人信息(输出各个链表数据)
ManNodeCur = ManNodeCur->Next; //输出当前结点数据后,Cur指针指向下一个结点
}
}
}
源码:Refund()函数
/*
**函数名:Refund
**功能:删除退票人信息并且对应影片的剩余票数相应增加
**输入参数:订票人信息头指针ManNodeHead和影片信息头指针FilmNodeHead
**返回值:无
*/
void Refund(struct ManNode *ManNodeHead,struct FilmNode *FilmNodeHead)
{
struct ManNode *ManNodeCur,*ManNodeFind = 0;
struct FilmNode *FilmNodeFind = 0;
char mID[20],mChoice[2];
int iNum,mBuyNum;
printf("\nInput your ID:");
scanf("%s",mID); //接收用户输入ID
ManNodeFind = FindMan(ManNodeHead,mID); //Find地址为符合ID要求的结点地址或者为NULL(即没有找到符合要求的结点)
if(ManNodeFind == NULL)
printf("Sorry,can't find the truth!\n");
else //如果找到了就通过地址输出订票人信息
{
printf("\nThis is your tickets:\n");
printf("ID number:%s\n",ManNodeFind->mData.mID);
printf("Name is:%s\n",ManNodeFind->mData.mName);
printf("Sex is:%s\n",ManNodeFind->mData.mSex);
printf("Buy film is:%s\n",ManNodeFind->mData.mBuyFilm);
printf("Buy number is:%d\n",ManNodeFind->mData.mBuyNum);
printf("do you want to cannel it<y/n>:");
scanf("%s",mChoice); //接收用户的选择
if(strcmp(mChoice,"y") == 0 || strcmp(mChoice,"Y") == 0) //判断输入的字符是否符合要求
{
ManNodeCur = ManNodeHead; //ManCur指针和头指针指向同一个地方
while(ManNodeCur->Next != ManNodeFind) //找到ManFind的前一个结点,此时Cur->next指向ManFind结点
ManNodeCur = ManNodeCur->Next;
FilmNodeFind = FindFilm(FilmNodeHead,ManNodeFind->mData.mBuyFilm); //找到和退票的影片名字一样的结点地址
if(FilmNodeFind != NULL)
{
iNum = FilmNodeFind->fData.fNum;
mBuyNum = ManNodeFind->mData.mBuyNum;
FilmNodeFind->fData.fNum = iNum + mBuyNum; //把退掉的票加回到电影结点中
}
ManNodeCur->Next = ManNodeFind->Next; //用Cur的Next指向Find的下一结点(将Find从链表中腾出来)
free(ManNodeFind); //释放该乘客订票信息结点空间
printf("success!\n");
iSave = 1;
}
}
}
Refund流程图: 链表操作:
struct ManNode *FindMan(struct ManNode *ManNodeHead,char mID[]);该声明函数返回的是一个结构体指针,在Refund函数中用于定位找出要退票的客户结点,和对应要退掉的电影结点则通过FindFilm函数找出并将退掉的票数加回到票库中。
源码:SaveMan()函数
/*
**函数名:SaveMan
**功能:保存订票人信息到文件中
**输入参数:订票人信息链表头指针ManNodeHead
**返回值:无
*/
void SaveMan(struct ManNode*ManNodeHead)
{
FILE *pfMan;
int mCount = 0;
int iFlag = 1;
struct ManNode *ManNodeCur;
pfMan = fopen("man.txt","wb");
if(pfMan == NULL)
{
printf("Error!!The man.txt file can't be opened!");
exit(-1);
}
ManNodeCur = ManNodeHead->Next;
while(ManNodeCur != NULL)
{
if(fwrite(ManNodeCur,sizeof(struct ManNode),1,pfMan) == 1)
{
ManNodeCur = ManNodeCur->Next;
mCount++;
}
else
{
iFlag = 0;
printf("Error occurred when saving the man messages\n");
break;
}
}
if(iFlag && mCount) //成功保存
{
printf("Success!You have save %d man messages.\n",mCount);
iSave = 0;
}
fclose(pfMan);
}
(SaveFilm与之类似,不再重复)
部分效果图
1、Insert films
2、Search films
3、Buy tickets
4、Change film data
5、Show films
6、show man
8、save to files