文章目录
八、 C语言入门大作业——信息管理系统(多文件版)
C语言大作业学过的都知道这是一个对C语言综合应用的考核,也是对项目开发的实践,信息管理系统是C语言历史悠久的压轴,因为它用来综合使用C语言在合适不过了,今天我也用这个来做为这一系列的结尾,希望大家都能用C语言实现自己期望的目标和项目。
信息管理只要4个核心功能——增删查改。
信息管理系统的东西还是很多的,我这里精简一下,我们只要来实现5大功能:新建、查找、删除、修改、清空,并在文件中保存。同时我们将再次使用多文件编程(DevC++版),补充前面对多文件编程的漏洞,详细讲述条件编译和外部变量在多文件中的使用,我们将接触更多的系统函数,以及很多新鲜的用法。
大家或许会觉得程序之间的关联性很强,一下子理不清关系,这是正常的,因为这个程序是我写完之后我知道什么是什么,不同函数遵循什么的规定,这些是我规划了很长时间才出来的,我会尽量讲的详细一点,大家多看肯定能看懂的。
一、 功能模块的划分:
大型项目首先要知道我们需要实现什么功能,再把这些功能向下划分,形成一个个可重复利用的函数,这也是封装的一种(自顶向下)。
首先,我们有5个功能,最起码5个函数就有了吧,其次我们还要保存在文件中,文件的读写函数也要有,2+5=7个了,这是第一级划分,还可以再向下细分,这里就因人而异了,当然随时把现成的代码块封装成函数也是可以的,功能细分只是可以让你对这个项目有更好的认识。
二、 多文件的编写
一般来说,同一类功能的函数(math.h)或者同一文件中的函数放在同一个文件中。我这里使用了2个文件,一个文件放和文件读写相关的函数,另一个放和这个项目有关的所有函数。当然这样还是笼统的分,分多少文件也看个人习惯,这里做简单处理,就分2个吧,再加上对应的头文件和main函数文件,本项目共5个文件。
Dev c++多文件编写:
首先,打开。
文件—新建—项目(所有的多文件工程都要新建项目)
我们选第二个:控制台程序,并选择C语言
选择一个保存项目的地方。最好新建一个属于这个项目的文件夹,方便后续添加文件。
保存,OK,项目就好了。
它会自动提供一个main函数模板
咱们用自己写的,不用它这个。
左侧项目管理可以管理这个项目的文件,咱们先把它自带的这个移除,右键移除就OK。
然后就可以添加自己的文件了,我们先新建一个文件
第一个,然后把main函数写进去,保存为main.c(前面的章节讲过了,这里略)
右键项目(就上面这个5(你自己命名的项目名))-添加,找到刚刚的文件就OK了。
用这种方法把需要的5个文件添加进去。
这里我已经规划好了,所以文件是直接全部新建导入的,实际在开发中文件是随时整理,随时编写的,什么时候觉得需要就可以搞。
三、 基本函数的实现(重点)
我们把所有函数都进行封装,使main函数得到精简。
int main()
{
int temp=1;
while(temp)
{
switch(Menu())
{
case 1:
Build();
break;
case 2:
Seek();
break;
case 3:
Amend();
break;
case 4:
Delete();
break;
case 5:
Clear();
break;
case 6:
temp = 0;
}
getchar();
}
return 0;
}
这是最终main函数的样子。其余所有功能都靠函数实现,这些东西都会讲到,莫急莫急。
- 菜单函数
首先我们来实现菜单函数。
菜单函数很简单,显示选择项,输入选项,OK了,
int Menu()
{
int n;
do{
system("cls");
printf("\n\n\n\n");
printf("\t\t\t \n");
printf("\t\t|----------信息管理系统----------|\n");
printf("\t\t|\t1.新建联系人 |\n");
printf("\t\t|\t2.查看联系人 |\n");
printf("\t\t|\t3.修改联系人 |\n");
printf("\t\t|\t4.删除联系人 |\n");
printf("\t\t|\t5.清空联系人 |\n");
printf("\t\t|\t6.退出 |\n");
printf("\t\t|--------------------------------|\n");
printf("\t\t请输入选项<1~6>:");
scanf("%d",&n);
printf("\n");
if(n>6||n<1)
{
printf("输入有误,请重新输入\a");
getchar();
getchar();
system("cls");
}
}while(n>6||n<1);
getchar();
return n;
}
需要注意的是,我们通过菜单函数的返回值来判断用户输入了几,同时对于非法输入,应当提示重新输入,保证完整性。
这个system(“cls”);是什么,这是一个系统函数,看system就知道了,cls是它的参数,实现的功能是小黑窗清屏,就是清除之前小黑窗(控制台命令行)的所有内容,由于我们这个程序会不断的循环执行,时间长了东西会很多,这里采用每次显示菜单的时候就进行清屏刷新的方式。
大家会发现这里用了很多好像没啥用的getchar();前面说了,这是读取一个字符的函数,但没有接收它的返回值,所以它相当于白读,实际上它起到的作用是阻碍程序进行,当程序运行到这里,它必须等待一个输入才能继续,相当于按任意键继续的感觉,由于我们有清屏函数的存在,如果程序不停的话,它printf的东西我们还没看呢,清屏了,啥也没有,就很难受。
有时候会连续出现2个getchar();这是因为上一次的键盘输入会产生输入的缓存,需要先用一个来消除,然后才能正确接收,只用一个的话就直接从缓存中读了一个字符,而和我们的输不输入没啥关系了,所以要先消耗缓存中的字符,这一点在输入字符串的时候尤为关键。
菜单函数和main函数放在一起了。
好了,接下来重头戏来了,我们写一下主体功能的实现
首先,我们写一下我们管理的东西的一些信息,我这里是用的人。
typedef struct{
char phone[15]; //手机
char name[100]; //名字
char num[20]; //工号
char ad[50]; //地址
char sex; //性别
int age; //年龄
}People;
同时用typedef重新定义这个结构体为People,现在我们就有了People这个数据类型了。
我们需要不断输入,通过输入判断是否结束,这意味着我们需要用链表来存储,我们再写一个链表的结构体。
typedef struct node{
People peo;
int id;
struct node* p_next;
}Node;
这是链表中的节点结构体,包含这个节点存放的信息peo,这个节点的id,下一个节点的指针。并重定义为Node。
- 新建函数
接下来,我们将编写“增”——新建函数,在此之前先来说说我的思路。编写程序最重要的就是思路,思路有了编不出来就是对这个语言的了解不够,当你不知道如何实现,没有思路的时候,数据结构和算法就是时候去学习一下了。
我们用链表结构存放我们输入的数据,如果我们没有输入特定的结束字符,就一直新建,同时我们用一个全局变量来记录我们生成了多少个节点(数据),这个数字将存在文件中表示文件中存放了多少个数据。
Ps:虽然按道理我们只要一块一块的写入,再一块一块的读出,是能够正好读完的,用文件的结束标志就能完全读入,本应没必要再记录,但我写的程序总是会多载入一些乱码数据,以我的实力难以解决,只能出此下策。如有大神能指点迷津,甚是感谢!
需要注意的是,数据新建的时候是分为2种情况的,之前有数据和之前没有数据,要分开处理。我们做如下规定:当没有数据时,head指针为空,当存在数据时,head指针指向数据.
//新建函数,返回头节点
Node* Create(Node* head)
{
Node* p_old,*p_new;
People t;
if(!Input(&t))
{
return head; //没有新建
}
if(!head)
{
head = (Node*)malloc(LEN);
p_old = head;
p_new = head;
p_new->id = 0;
p_new->peo = t;
p_new->p_next = NULL;
}else{
p_new = (Node*)malloc(LEN);
for(p_old = head;p_old->p_next != NULL;p_old = p_old->p_next);
p_old->p_next = p_new;
p_new->id = p_old->id+1;
p_old = p_new;
p_new->peo = t;
p_new->p_next = NULL;
}
Number++;
while(1)
{
if(!Input(&t))
{
return head;
}
p_new = (Node*)malloc(LEN);
Number++;
p_new->id = p_old->id+1;
p_new->peo = t;
p_old->p_next = p_new;
p_new->p_next = NULL;
p_old = p_new;
}
}
我们用一个局部变量t来接收每次的输入。
我还编了一个输入函数,实现每次的输入
//输入函数
int Input(People* p)
{
printf("\n输入姓名(输入#中止):");
scanf("%s",&p->name);
if(p->name[0] == '#')
{
return 0;
}
printf("输入年龄:");
scanf("%d",&p->age);
printf("输入性别(m-男,f-女):");
getchar();
scanf("%c",&p->sex);
printf("输入手机号:");
scanf("%s",&p->phone);
printf("输入地址:");
scanf("%s",&p->ad);
printf("输入工号:(没有输入-1)");
scanf("%s",&p->num);
printf("\n");
return 1;
}
通过一个People的指针的传入,直接对对应位置的结构体赋值。并规定退出符号——名字输入#,则返回0,表示输入结束符号,否则返回1,表示正常输入。其他的都是小玩意,可有可无的。我这里是考虑到不是人人都有编号,所以对编号的输入进行了规定,这些都是小问题。
当输入结束时,就返回头指针。对于第一次输入就退出的话,相当于没有新建,就把头指针原封不动的返回去。对于有新输入的话,我们会在后面进行合适的处理,保证头指针指向的链表是完整的。
当输入有效时,我们就可以建立空间存放这个新数据了,这里分2中情况,第一次新建和之前有数据,这个可以根据头指针来区分。
对于新建的,就好说了,我们直接新建一个空间,放刚刚输入的数据(结构体可以直接赋值p_new->peo = t;)并让id=0。对于之前存在数据的话,我们先新建一个节点空间,并通过
for(p_old = head;p_old->p_next != NULL;p_old = p_old->p_next);
这一个for循环定位原来数据的最后一个,这是一个循环体为空的for循环,大家知道for循环的机制应该是可以明白的,当p_old指向的下一个节点为NULL时,即为最后一个节点,此时p_old就定位在最后一个节点上。
之后就是关联,id递加,赋值,老节点跟进,和前面链表的时候一样的。
由于我们开始的时候新建了一个,全局变量Number+1,表示多了一个,很好理解。
之后循环输入,用死循环+条件退出的形式。每次新建,关联,id递加,赋值,老节点跟进,并更新Number,没啥好说的。
重点是:每次新建的节点,我都会让它的下一个节点指向默认为NULL,这样如果输入结束的话,直接返回头指针,也可以保证链表最后一个指针指向NULL,从而保证链表的完整性。
- 查找函数:
我们提供除id以外的全因素查找,我这里id是当作内部资源不对外开放的,虽然也有id的查找函数但是内部使用的。缺点是只能返回查找到的第一个,原本我希望的是能返回所有符合结果的id号构成的数组,但这样的话需要一个动态数组来实现,工程量又大了不少,等我以后有空再更新吧。
//查找函数,head-头指针,k-方式选择
int Find(Node* head,int k)
{
char n[50];
int m;
Node *p = head;
switch(k)
{
case 0: //名字查
printf("输入查找姓名:");
scanf("%s",n);
FindName(n,head);
break;
case 1: //年龄查
printf("输入查看的年龄:");
scanf("%d",&m);
FindAge(m,head);
break;
case 2: //手机号查
printf("输入查找的手机号:");
scanf("%s",n);
FindPhone(n,head);
break;
case 3: //地址查
printf("输入查找地址:");
scanf("%s",n);
FindAd(n,head);
break;
case 4: //性别查
printf("输入查找性别m-男,f-女:");
getchar();
scanf("%c",&n[0]);
FindSex(n[0],head);
break;
case 5: //工号查
printf("输入查找工号:");
scanf("%s",n);
FindNum(n,head);
break;
case 6: //全部显示
ShowAll(head);
break;
default:
printf("输入错误\a\n");
}
}
这里我对于每种方式的查找都封装了自己函数,其实它们大同小异。
//想实现能全部找到返回各个序号的,但这需要动态数组,比较麻烦
int FindName(char name[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.name,name))
{
Print(&p->peo);
return 1;
}
}
printf("查无此人\a");
return 0;
}
void FindAge(int age,Node *head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(p->peo.age == age)
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindPhone(char Phone[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.phone,Phone))
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindAd(char ad[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.ad,ad))
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindSex(char sex,Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(sex == p->peo.sex)
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindNum(char num[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.num,num))
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
//id查找
Node* FindId(int id,Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(p->id == id)
{
return p;
}
}
if(!p)
{
return NULL;
}
}
通过for循环遍历,找到就打印,没有找到就输出提示,大体都是这个思路,只不过字符串比较要用strcmp函数。
所有查找函数中,只有姓名查找和id查找有返回值,姓名查找返回是否存在,id查找返回节点指针,为什么这样设计,这是和我之后的安排相关的,因为之后的改和删都需要这两个查找函数。所以它们要顶一点。
同时,我也编写了打印函数,作为基础的输入输出函数使用:
//打印函数
void Print(People *p)
{
printf("\n姓名:%s\n",p->name);
printf("年龄:%d\n",p->age);
printf("性别:%c\n",p->sex);
printf("电话:%s\n",p->phone);
printf("地址:%s\n",p->ad);
if((p->num)[0] == '-')
{
printf("\n");
return;
}
printf("工号:%s\n",p->num);
printf("\n");
}
void ShowAll(Node* head)
{
Node* temp = head;
while(temp)
{
Print(&temp->peo);
temp = temp->p_next;
}
}
Print函数是对一个People类型的打印,ShowAll是输出所有people的数据,按照前面规定的,如果没有工号,就不输出。
- 修改函数:
这个有了前面查找函数的思路很简单,就是找到对应的节点数据,然后重新写值就OK,如果没有找到,就提示并退出。
//现在只能支持直接查找,等动态数组出来就可以随意处理了
//修改函数 (名字)
void Correct(char a[],Node *head)
{
Node *p = head;
int choice;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.name,a))
{
Print(&p->peo);
break;
}
}
if(!p)
{
printf("查无此人\a");
return;
}
printf("查到这个人,要修改什么:");
printf("1.姓名\t2.性别\t3.年龄\t4.地址\t5.工号\t6.电话7.全部重写\n请输入:");
scanf("%d",&choice);
switch(choice)
{
case 1:
printf("输入新姓名:");
scanf("%s",&p->peo.name);
break;
case 2:
printf("输入新性别:");
getchar();
scanf("%c",&p->peo.sex);
break;
case 3:
printf("输入新年龄:");
scanf("%d",&p->peo.age);
break;
case 4:
printf("输入新地址:");
scanf("%s",&p->peo.ad);
break;
case 5:
printf("输入新工号:");
scanf("%s",&p->peo.num);
break;
case 6:
printf("输入新电话:");
scanf("%s",&p->peo.phone);
break;
case 7:
Input(&p->peo);
break;
default:
printf("输入错误\a");
}
}
这个函数需要头指针指向的链表数据,和待查找的名字。
- 删除函数:
通过查找,找寻待删除的信息,如果删除,就进行链表的操作。
链表的删除前面讲过了,这里再说一下,如果是头节点,就让头节点的下一个节点当头节点,如果是尾节点,就让倒数第二个节点的下一个节点指向空,如果是中间,就让前一个结点的下一节点指向后一个结点,需要注意的是,最后一定要释放删除的结点。
每删除一个节点,Number就--。
//删除函数_目前只能看人名
void Det(Node* head,char name[])
{
Node *p = head;
char temp;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.name,name))
{
Print(&p->peo);
break;
}
}
printf("确认删除吗:y-是,n-否");
getchar();
scanf("%c",&temp);
if(temp == 'y')
{
if(p == head)
{
head = p->p_next;
}else if(p->p_next == NULL){
Node *p1 = FindId(p->id-1,head);
p1->p_next =NULL;
}else{
Node *p1 = FindId(p->id-1,head);
p1->p_next = p->p_next;
}
free(p);
printf("OK\a\n");
Number--;
}
}
我们还是通过和名字查找一样的方法得到指向待删除元素的指针。
我们首先提示一下,防止误操作,当得到确定的删除命令后,对上面的3中情况判断,然后释放删除空间,Number--,Ok。
大家可能会发现我这里没有查找失败,没有找到的情况,这是因为现在介绍的都是功能的核心程序,真正使用的话还要一个辅助程序,这个我们后面会讲。
- 清空函数:
大家或许有很多想法,但这个实现其实很简单,我们运用w方式打开文件时会格式化文件这个特性,就很方便了。
void Clear()
{
char temp;
printf("确定吗\a:y-确定,n-手滑了:");
scanf("%c",&temp);
if(temp == 'y')
{
FILE* fp ;
if(!(fp = fopen(TXT,"wb")))
{
printf("Error\a\n");
exit(0);
}
fclose(fp);
printf("OK");
Number = 0;
getchar();
}
}
我们打开,什么都不写立刻关闭文件,文件就清空了,是不是很方便。
这里的TXT是一个宏定义,表示文件名,后面文件操作函数会介绍。
要注意的是,Number同时置0(这个BUG我找了好久……)。
大家或许发现我只是把文件清空了,但程序运行的时候,其实数据已经从文件中读到程序里了,这就意味着程序中仍然可以通过头指针访问这些数据。实际上,我的构想是每次进行数据操作时,都是先从文件中读出,再修改,再写入。所以下次的任何操作都会先读取文件,这样将会刷新head。
注:这样的构造思路存在一个BUG,就是每次刷新都会开辟新内存空间,而原来的空间得不到释放,造成内存的严重浪费,虽然好像不怎么影响程序运行,但这对于C语言程序其实比较BUG的,虽然现在电脑性能有很大的提升,这点空间不足以挂齿,但这种思维的疏忽应当是写C程序的人所应当注意的,或许更好的解决方法是每次只在打开程序时读入,在结束程序时写入,而不是在每次操作中刷新,后面有时间我会更新这个BUG。
四、文件操作函数
好了,这些基本核心函数就完成了,下面是文件操作函数。
文件的操作,最简单的就是读写,所以我们来编写这两个函数。
- 文件加载
首先是文件加载,将文件中的数据加载到程序中。
我们用#define TXT "dat.txt"这个宏定义语句来定义打开的文件。
Node* Load()//失败返回空,没有返回空,有返回首地址
{
FILE *fp;
int i;
Node *head = (Node*)malloc(LEN);
Node *p_old = head;
Node *p_new = head;
if(access(TXT,0))
{
fp = fopen(TXT,"w");
fclose(fp);
if(!(fp = fopen(TXT,"rb")))
{
printf("error");
return NULL;
}
}else if(!(fp = fopen(TXT,"rb")))//存在,打开
{
printf("Error\a\n");
return NULL;
}
//空文件?
rewind(fp);
if(fgetc(fp) == EOF)
{
free(head);
return NULL;
}else
{
rewind(fp);
}
fread(&Number,sizeof(int),1,fp);
if(!Number)
{
free(head);
return NULL;
}
for(i=1;i<=Number;i++)
{
fread(p_new,LEN,1,fp);
p_new = (Node*)malloc(LEN);
p_old->p_next = p_new;
p_old->id = i;
p_old = p_new;
}
free(p_new);
p_old = head;
for(;p_old->id != Number;p_old = p_old->p_next);
p_old->p_next = NULL;
fclose(fp);
return head;
}
Load函数遵循规定,没有数据时就返回空。
access(TXT,0)这是一个新函数,其声明在头文件io.h中,这个函数用来判断文件的状态,我们这里用参数0的功能,来判断文件是否存在,如果文件不存在,就用w方式打开不存在文件时会新建文件这个功能来新建。如果文件本身存在,直接打开就OK。
之后我们判断是否是空文件,先将文件位置标志归位(文件开头)(其实这一步可以不用,但这样写更好理解),然后读取一个字符,判断是否是文件结束标志,如果是,说明文件是空的,就释放刚新建的空间, 返回空指针,如果不是,就归位文件标志。
首先读入文件中数据个数Number(我们写的时候也是先写这个),如果个数为0,说明没有数据,就返回空指针。(文件空和没有数据是两种情况,因为我们的文件中还有一个数据个数参数)
之后就读入Number个数据,并用链表存放。
读完后,由于我们是开辟的下一个待读取的单元,所以会多开一个,我们把它释放掉,并找到读取的最后一个结点,让它的下一个指向空,关闭文件,返回头指针。
- 保存函数:
先保存个数,再保存数据
void Save(Node* head)
{
FILE *fp;
Node* p = head;
if(!head) //没有
{
printf("kong");
return;
}
if(!(fp = fopen(TXT,"wb")))//打开文件
{
printf("error");
return;
}
fwrite(&Number,sizeof(int),1,fp);//写
for(;p;p = p->p_next)//写
{
fwrite(p,LEN,1,fp);
}
fclose(fp);
}
如果头文件为空说明没有数据,就不报错,并提示,有数据的话,打开文件,写个数,循环写结点信息,OK。
五、函数的辅助函数
我们对核心函数提供外围服务,使之成为可以真正使用的函数。
- 新建函数:
void Build()
{
Node* head = Load();
head = Create(head);
Save(head);
printf("\nOK\n");
getchar();
}
这个很简单,先加载,再新建刷新头指针,之后再保存。
- 查找函数:
void Seek()
{
Node* head = Load();
int temp;
if(!head)
{
printf("列表空");
return;
}
printf("输入查找方式:\n\t1.姓名\t2.年龄\t3.手机号\n\t4.地址\t5.性别\t6.工号\t7.全部显示:\n");
scanf("%d",&temp);
Find(head,temp-1);
getchar();
}
同样的先加载,如果为空,还找啥,输出提示完事,否则选择查找方式,调用查找函数,OK
- 修改函数:
void Amend()
{
char a[50];
Node* head = Load();
if(!head)
{
printf("列表空");
return;
}
printf("输入修改的姓名:");
scanf("%s",a);
Correct(a,head);
Save(head);
printf("\nOK\n");
getchar();
}
和查找函数大同小异,载入,获取修改姓名,调用函数,保存,OK
- 删除函数:
void Delete()
{
Node* head = Load();
char a[50];
if(!head)
{
printf("列表空");
return;
}
printf("输入删除的姓名:");
scanf("%s",a);
if(FindName(a,head))
{
Det(head,a);
Save(head);
}
getchar();
}
和查找函数一样,载入,获取姓名,如果没有找到(这里就用到了姓名查找函数的返回值),就提示(这个提示在查找函数中就有了),啥也不干。如果找到了,就删除,保存,OK。
修改函数好像没有对未找到函数的判断,这是因为在correct函数中内含了,而det函数没有内涵没有名字的处理,所以要用外围辅助判断。这是两种函数的区别。
清空函数本身就能干大事,不用外围函数。
六、多文件编程和联系
这一块我们将编写头文件,并对各个头文件之间进行关联。
先来写fun的头文件,头文件中包含所有fun中的函数的声明,这个就不用说,头文件中还可以包含结构体的定义,宏定义。
这里我们先介绍头文件编写的格式,
#ifndef BASE
#define BASE
#include<stdio.h>
#endif
#ifndef XXX……#endif,这是预处理指令,预处理指令是用来协调多文件的编译工作的,这个格式的意思是如果没有编译过XXX,就编译下面的指令。
这个XXX是一个标志,要符合命名规则,我这里用BASE这个标志来标注下面这段代码(只有一个头文件包含指令)
这个的用途是什么,当我们有多个文件都要用的时候,如这个头文件,很常用,每个文件都要用,如果你不包含这个头文件,你这个文件的函数就出错了,但如果直接包含,多个文件一起编译的时候,由于每个都包含一遍这个头文件,就会出现头文件重复包含的错误,这个结构就是解决这个问题的,对于每个单个文件,我本身没有定义BASE,当然会包含下面的头文件,对于多文件编译,当编译器编译了第一个BASE后,后面的BASE就会由于编译过而不在编译这一块,也是实现单次编译,OK。
应当说明,实现这个效果,同一块编译的标志应该一样,不然你前面是BASE,后面编程BASS,编译器集体编译,发现没讲过BASS,一编译,发现里面一样,重复定义,没有用了。
这个标志也有讲究,一般来说,会把本文件对应的所有编译块用和本文件相关的标志框起来。对于通用的编译文件,用统一的标识标注(上面的BASE)。
说起来比较难理解,大家看实例就OK。
fun.h头文件:
#ifndef BASE
#define BASE
#include<stdio.h>
#endif
#ifndef FUN_H
#define FUN_H
#include<stdlib.h>
#define LEN sizeof(Node)
#define TXT "dat.txt"
extern int Number;
typedef struct{
char phone[15]; //手机
char name[100]; //名字
char num[20]; //工号
char ad[50]; //地址
char sex; //性别
int age; //年龄
}People;
typedef struct node{
People peo;
int id;
struct node* p_next;
}Node;
Node* New(Node *);
int Input(People* p);
void Print(People *p);
int FindName(char name[],Node* head);
void FindAge(int age,Node *head);
void FindPhone(char Phone[],Node* head);
void FindAd(char ad[],Node* head);
void FindSex(char sex,Node* head);
void FindNum(char num[],Node* head);
Node* FindId(int id,Node* head);
void ShowAll(Node* head);
void Det(Node* head,char name[]);
void Correct(char a[],Node *head);
Node* Create(Node* head);
void Seek();
void Build();
void Clear();
void Delete();
void Amend();
#endif
我们用FUN_H标注这个fun文件的编译代码。
大家发现一个问题:我们哪里都用到了Number这个变量,fun和save的文件中的函数都有用到,但这个变量定义在哪里呢?我这里把它定义在了main函数的文件中,这意味着,如果不做处理,fun和save的文件中的函数将无法识别到这个变量。
这个变量跨过多个文件,还要保证是同一个变量,这是什么,外部变量。
关于外部变量的多文件使用,比较麻烦,首先我们可以在头文件中声明外部变量,如上面的extern int Number;这就是对Number进行外部声明,但头文件中不能定义变量,所以不能对Number赋值,赋值操作必须在c文件中,这也是我们为什么要定义在main文件中的一个原因。
Save.h头文件
#ifndef BASE
#define BASE
#include<stdio.h>
#endif
#ifndef SAVE_H
#define SAVE_H
#include"fun.h"
extern int Number;
Node* Load();//失败返回空,没有返回空,有返回首地址
void Save(Node* head);
#endif
由于我们文件操作中也有用到fun文件中的函数,所以干脆包含进来吧。
在main函数文件中:
虽然我们save.h文件包含了fun.h文件,但由于我们的预编译的作用,它们可以同时在main函数文件中包含而不会发生重定义,预编译标志的重要性可见一斑。
当然我们知道fun头文件在save中,我们就直接包含save就OK了。
大家会发现,Number外部变量声明其实是声明了两次,一次在fun中,一次在save中,但没有问题,因为这是声明,只有声明不冲突,声明几次都没啥问题。
七、文件编译
用了这么久,我们终于用到了最后一个编译按钮,全部重新编译,这个就是针对多文件项目的。点击这个按钮,就可以对所有文件进行编译。
编译之后运行,一个成功的程序,信息管理系统就OK了。
运行情况:
这个系统还是很简陋的,高级的系统更为复杂。
八、全部代码:
希望大家能有所收获,如果是做作业的话,尽量不要CV大法,看看前面的分析过程,自己写出来的代码更有成就感!
Main.c
#include"save.h"
int Menu();
int Number = 0;
int main()
{
int temp=1;
while(temp)
{
switch(Menu())
{
case 1:
Build();
break;
case 2:
Seek();
break;
case 3:
Amend();
break;
case 4:
Delete();
break;
case 5:
Clear();
break;
case 6:
temp = 0;
}
getchar();
}
return 0;
}
int Menu()
{
int n;
do{
system("cls");
printf("\n\n\n\n");
printf("\t\t\t \n");
printf("\t\t|----------信息管理系统----------|\n");
printf("\t\t|\t1.新建联系人 |\n");
printf("\t\t|\t2.查看联系人 |\n");
printf("\t\t|\t3.修改联系人 |\n");
printf("\t\t|\t4.删除联系人 |\n");
printf("\t\t|\t5.清空联系人 |\n");
printf("\t\t|\t6.退出 |\n");
printf("\t\t|--------------------------------|\n");
printf("\t\t请输入选项<1~6>:");
scanf("%d",&n);
printf("\n");
if(n>6||n<1)
{
printf("输入有误,请重新输入\a");
getchar();
getchar();
system("cls");
}
}while(n>6||n<1);
getchar();
return n;
}
Fun.c
#include"fun.h"
#include<string.h>
//查找函数,head-头指针,k-方式选择
int Find(Node* head,int k)
{
char n[50];
int m;
Node *p = head;
switch(k)
{
case 0: //名字查
printf("输入查找姓名:");
scanf("%s",n);
FindName(n,head);
break;
case 1: //年龄查
printf("输入查看的年龄:");
scanf("%d",&m);
FindAge(m,head);
break;
case 2: //手机号查
printf("输入查找的手机号:");
scanf("%s",n);
FindPhone(n,head);
break;
case 3: //地址查
printf("输入查找地址:");
scanf("%s",n);
FindAd(n,head);
break;
case 4: //性别查
printf("输入查找性别m-男,f-女:");
getchar();
scanf("%c",&n[0]);
FindSex(n[0],head);
break;
case 5: //工号查
printf("输入查找工号:");
scanf("%s",n);
FindNum(n,head);
break;
case 6: //全部显示
ShowAll(head);
break;
default:
printf("输入错误\a\n");
}
}
//想实现能全部找到返回各个序号的,但这需要动态数组,比较麻烦
int FindName(char name[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.name,name))
{
Print(&p->peo);
return 1;
}
}
printf("查无此人\a");
return 0;
}
void FindAge(int age,Node *head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(p->peo.age == age)
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindPhone(char Phone[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.phone,Phone))
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindAd(char ad[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.ad,ad))
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindSex(char sex,Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(sex == p->peo.sex)
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
void FindNum(char num[],Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.num,num))
{
Print(&p->peo);
return;
}
}
printf("查无此人\a");
}
//id查找
Node* FindId(int id,Node* head)
{
Node *p = head;
for(;p;p = p->p_next)
{
if(p->id == id)
{
return p;
}
}
if(!p)
{
return NULL;
}
}
//输入函数
int Input(People* p)
{
printf("\n输入姓名(输入#中止):");
scanf("%s",&p->name);
if(p->name[0] == '#')
{
return 0;
}
printf("输入年龄:");
scanf("%d",&p->age);
printf("输入性别(m-男,f-女):");
getchar();
scanf("%c",&p->sex);
printf("输入手机号:");
scanf("%s",&p->phone);
printf("输入地址:");
scanf("%s",&p->ad);
printf("输入工号:(没有输入-1)");
scanf("%s",&p->num);
printf("\n");
return 1;
}
//打印函数
void Print(People *p)
{
printf("\n姓名:%s\n",p->name);
printf("年龄:%d\n",p->age);
printf("性别:%c\n",p->sex);
printf("电话:%s\n",p->phone);
printf("地址:%s\n",p->ad);
if((p->num)[0] == '-')
{
printf("\n");
return;
}
printf("工号:%s\n",p->num);
printf("\n");
}
void Amend()
{
char a[50];
Node* head = Load();
if(!head)
{
printf("列表空");
return;
}
printf("输入修改的姓名:");
scanf("%s",a);
Correct(a,head);
Save(head);
printf("\nOK\n");
getchar();
}
//现在只能支持直接查找,等动态数组出来就可以随意处理了
//修改函数 (名字)
void Correct(char a[],Node *head)
{
Node *p = head;
int choice;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.name,a))
{
Print(&p->peo);
break;
}
}
if(!p)
{
printf("查无此人\a");
return;
}
printf("查到这个人,要修改什么:");
printf("1.姓名\t2.性别\t3.年龄\t4.地址\t5.工号\t6.电话7.全部重写\n请输入:");
scanf("%d",&choice);
switch(choice)
{
case 1:
printf("输入新姓名:");
scanf("%s",&p->peo.name);
break;
case 2:
printf("输入新性别:");
getchar();
scanf("%c",&p->peo.sex);
break;
case 3:
printf("输入新年龄:");
scanf("%d",&p->peo.age);
break;
case 4:
printf("输入新地址:");
scanf("%s",&p->peo.ad);
break;
case 5:
printf("输入新工号:");
scanf("%s",&p->peo.num);
break;
case 6:
printf("输入新电话:");
scanf("%s",&p->peo.phone);
break;
case 7:
Input(&p->peo);
break;
default:
printf("输入错误\a");
}
}
//删除函数_目前只能看人名
void Det(Node* head,char name[])
{
Node *p = head;
char temp ;
for(;p;p = p->p_next)
{
if(!strcmp(p->peo.name,name))
{
Print(&p->peo);
break;
}
}
printf("确认删除吗:y-是,n-否");
getchar();
scanf("%c",&temp);
if(temp == 'y')
{
if(p == head)
{
head = p->p_next;
}else if(p->p_next == NULL){
Node *p1 = FindId(p->id-1,head);
p1->p_next =NULL;
}else{
Node *p1 = FindId(p->id-1,head);
p1->p_next = p->p_next;
}
free(p);
printf("OK\a\n");
Number--;
}
}
void Build()
{
Node* head = Load();
head = Create(head);
Save(head);
printf("\nOK\n");
getchar();
}
void ShowAll(Node* head)
{
Node* temp = head;
while(temp)
{
Print(&temp->peo);
temp = temp->p_next;
}
}
Node* Create(Node* head)
{
Node* p_old,*p_new;
People t;
if(!Input(&t))
{
return head; //没有新建
}
if(!head)
{
head = (Node*)malloc(LEN);
p_old = head;
p_new = head;
p_new->id = 0;
p_new->peo = t;
p_new->p_next = NULL;
}else{
p_new = (Node*)malloc(LEN);
for(p_old = head;p_old->p_next != NULL;p_old = p_old->p_next);
p_old->p_next = p_new;
p_new->id = p_old->id+1;
p_old = p_new;
p_new->peo = t;
p_new->p_next = NULL;
}
Number++;
while(1)
{
if(!Input(&t))
{
return head;
}
p_new = (Node*)malloc(LEN);
Number++;
p_new->id = p_old->id+1;
p_new->peo = t;
p_old->p_next = p_new;
p_new->p_next = NULL;
p_old = p_new;
}
}
void Seek()
{
Node* head = Load();
int temp;
if(!head)
{
printf("列表空");
return;
}
printf("输入查找方式:\n\t1.姓名\t2.年龄\t3.手机号\n\t4.地址\t5.性别\t6.工号\t7.全部显示:\n");
scanf("%d",&temp);
Find(head,temp-1);
getchar();
}
void Clear()
{
char temp;
printf("确定吗\a:y-确定,n-手滑了:");
scanf("%c",&temp);
if(temp == 'y')
{
FILE* fp ;
if(!(fp = fopen(TXT,"wb")))
{
printf("Error\a\n");
exit(0);
}
fclose(fp);
printf("OK");
Number = 0;
getchar();
}
}
void Delete()
{
Node* head = Load();
char a[50];
if(!head)
{
printf("列表空");
return;
}
printf("输入删除的姓名:");
scanf("%s",a);
if(FindName(a,head))
{
Det(head,a);
Save(head);
}
getchar();
}
Fun.h
#ifndef BASE
#define BASE
#include<stdio.h>
#endif
#ifndef FUN_H
#define FUN_H
#include<stdlib.h>
#define LEN sizeof(Node)
#define TXT "dat.txt"
extern int Number;
typedef struct{
char phone[15]; //手机
char name[100]; //名字
char num[20]; //工号
char ad[50]; //地址
char sex; //性别
int age; //年龄
}People;
typedef struct node{
People peo;
int id;
struct node* p_next;
}Node;
Node* New(Node *);
int Input(People* p);
void Print(People *p);
int FindName(char name[],Node* head);
void FindAge(int age,Node *head);
void FindPhone(char Phone[],Node* head);
void FindAd(char ad[],Node* head);
void FindSex(char sex,Node* head);
void FindNum(char num[],Node* head);
Node* FindId(int id,Node* head);
void ShowAll(Node* head);
void Det(Node* head,char name[]);
void Correct(char a[],Node *head);
Node* Create(Node* head);
void Seek();
void Build();
void Clear();
void Delete();
void Amend();
#endif
Save.c
#include"save.h"
#include<io.h>
void Save(Node* head)
{
FILE *fp;
Node* p = head;
if(!head) //没有
{
printf("kong");
return;
}
if(!(fp = fopen(TXT,"wb")))//打开文件
{
printf("error");
return;
}
fwrite(&Number,sizeof(int),1,fp);//写
for(;p;p = p->p_next)//写
{
fwrite(p,LEN,1,fp);
}
fclose(fp);
}
Node* Load()//失败返回空,没有返回空,有返回首地址
{
FILE *fp;
int i;
Node *head = (Node*)malloc(LEN);
Node *p_old = head;
Node *p_new = head;
if(access(TXT,0))
{
fp = fopen(TXT,"w");
fclose(fp);
if(!(fp = fopen(TXT,"rb")))
{
printf("error");
return NULL;
}
}else if(!(fp = fopen(TXT,"rb")))//存在,打开
{
printf("Error\a\n");
return NULL;
}
//空文件?
rewind(fp);
if(fgetc(fp) == EOF)
{
free(head);
return NULL;
}else
{
rewind(fp);
}
fread(&Number,sizeof(int),1,fp);
if(!Number)
{
free(head);
return NULL;
}
for(i=1;i<=Number;i++)
{
fread(p_new,LEN,1,fp);
p_new = (Node*)malloc(LEN);
p_old->p_next = p_new;
p_old->id = i;
p_old = p_new;
}
free(p_new);
p_old = head;
for(;p_old->id != Number;p_old = p_old->p_next);
p_old->p_next = NULL;
fclose(fp);
return head;
}
Save.h
#ifndef BASE
#define BASE
#include<stdio.h>
#endif
#ifndef SAVE_H
#define SAVE_H
#include"fun.h"
extern int Number;
Node* Load();//失败返回空,没有返回空,有返回首地址
void Save(Node* head);
#endif
OK,C语言入门总结就到这里结束了,感谢大家的观看,希望大家能有所收获,如果有空我会更新其他系列的东西,欢迎大家观看,如果本栏目有什么BUG,大家可以在下面评论,我能改的尽量改(我也不是高手……)。