本文最后更新于:3 年前
八、 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函数得到精简。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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了,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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函数放在一起了。
好了,接下来重头戏来了,我们写一下主体功能 的实现 首先,我们写一下我们管理的东西的一些信息,我这里是用的人。
1 2 3 4 5 6 7 8 9 typedef struct { char phone[15 ]; char name[100 ]; char num[20 ]; char ad[50 ]; char sex; int age; }People;
同时用typedef重新定义这个结构体为People,现在我们就有了People这个数据类型了。 我们需要不断输入,通过输入判断是否结束,这意味着我们需要用链表来存储,我们再写一个链表的结构体。
1 2 3 4 5 6 7 typedef struct node { People peo; int id; struct node * p_next ; }Node;
这是链表中的节点结构体,包含这个节点存放的信息peo,这个节点的id,下一个节点的指针。并重定义为Node。
接下来,我们将编写“增”——新建函数,在此之前先来说说我的思路。编写程序最重要的就是思路,思路有了编不出来就是对这个语言的了解不够,当你不知道如何实现,没有思路的时候,数据结构和算法就是时候去学习一下了。我们用链表结构存放我们输入的数据,如果我们没有输入特定的结束字符,就一直新建,同时我们用一个全局变量来记录我们生成了多少个节点(数据),这个数字将存在文件中表示文件中存放了多少个数据。 Ps:虽然按道理我们只要一块一块的写入,再一块一块的读出,是能够正好读完的,用文件的结束标志就能完全读入,本应没必要再记录,但我写的程序总是会多载入一些乱码数据,以我的实力难以解决,只能出此下策。如有大神能指点迷津,甚是感谢! 需要注意的是,数据新建的时候是分为2种情况的,之前有数据和之前没有数据,要分开处理。我们做如下规定:当没有数据时,head指针为空,当存在数据时,head指针指向数据.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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来接收每次的输入。 我还编了一个输入函数 ,实现每次的输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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号构成的数组,但这样的话需要一个动态数组来实现,工程量又大了不少,等我以后有空再更新吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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" ); } }
这里我对于每种方式的查找都封装了自己函数,其实它们大同小异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 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" ); } 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查找返回节点指针,为什么这样设计,这是和我之后的安排相关的,因为之后的改和删都需要这两个查找函数。所以它们要顶一点。 同时,我也编写了打印函数,作为基础的输入输出函数使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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,如果没有找到,就提示并退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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就--。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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方式打开文件时会格式化文件这个特性,就很方便了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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”这个宏定义语句来定义打开的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 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个数据,并用链表存放。 读完后,由于我们是开辟的下一个待读取的单元,所以会多开一个,我们把它释放掉,并找到读取的最后一个结点,让它的下一个指向空,关闭文件,返回头指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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。
五、函数的辅助函数 我们对核心函数提供外围服务,使之成为可以真正使用的函数。
1 2 3 4 5 6 7 8 9 10 void Build () { Node* head = Load(); head = Create(head); Save(head); printf ("\nOK\n" ); getchar(); }
这个很简单,先加载,再新建刷新头指针,之后再保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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中的函数的声明,这个就不用说,头文件中还可以包含结构体的定义,宏定义。
这里我们先介绍头文件编写的格式,
1 2 3 4 5 6 7 #ifndef BASE #define BASE #include <stdio.h> #endif
#ifndef XXX……#endif,这是预处理指令,预处理指令是用来协调多文件的编译工作的,这个格式的意思是如果没有编译过XXX,就编译下面的指令。
这个XXX是一个标志,要符合命名规则,我这里用BASE这个标志来标注下面这段代码(只有一个头文件包含指令)
这个的用途是什么,当我们有多个文件都要用的时候,如这个头文件,很常用,每个文件都要用,如果你不包含这个头文件,你这个文件的函数就出错了,但如果直接包含,多个文件一起编译的时候,由于每个都包含一遍这个头文件,就会出现头文件重复包含的错误,这个结构就是解决这个问题的 ,对于每个单个文件,我本身没有定义BASE,当然会包含下面的头文件,对于多文件编译,当编译器编译了第一个BASE后,后面的BASE就会由于编译过而不在编译这一块,也是实现单次编译,OK。
应当说明,实现这个效果,同一块编译的标志应该一样,不然你前面是BASE,后面编程BASS,编译器集体编译,发现没讲过BASS,一编译,发现里面一样,重复定义,没有用了 。
这个标志也有讲究,一般来说,会把本文件对应的所有编译块用和本文件相关的标志框起来。对于通用的编译文件,用统一的标识标注(上面的BASE)。
说起来比较难理解,大家看实例就OK。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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文件中的一个原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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大法,看看前面的分析过程,自己写出来的代码更有成就感!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 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> 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" ); } 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,大家可以在下面评论,我能改的尽量改(我也不是高手……)。