文章目录

  • 八、 C语言入门大作业——信息管理系统(多文件版)
    • 一、 功能模块的划分:
    • 二、 多文件的编写
    • 三、 基本函数的实现(重点)
    • 四、文件操作函数
    • 五、函数的辅助函数
    • 六、多文件编程和联系
    • 七、文件编译
    • 八、全部代码:

八、 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();#endifSave.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,大家可以在下面评论,我能改的尽量改(我也不是高手……)。

C语言总结项目和入门大作业——信息管理系统(多文件版)相关推荐

  1. 学生管理系统c语言的作用,C语言实现学生信息管理系统(文件版)

    本文实例为大家分享了C语言实现学生信息管理系统的具体代码,供大家参考,具体内容如下 下面是我自己用写的学生信息管理系统,为了增加数据的利用率,分为学生端和教师端,同时实现账号密码登录,以文件的形式将其 ...

  2. C语言大作业:车辆管理系统

    C语言大作业:车辆管理系统 声明 此代码使用VS2019编译器进行编译 使用 vc 和 dev-c 的有可能会出现编译警告,需要自己去网上查找相关的编译环境的问题 其次使用vs编译器也可能会报 C49 ...

  3. HTML5期末大作业:管理系统网站设计——蓝色OA企业员工管理系统(10页) HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机毕设网页设计源

    HTML5期末大作业:管理系统网站设计--蓝色OA企业员工管理系统(10页) HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机毕设网页设计源码 常 ...

  4. MySQL数据库大作业——学生管理系统GUI

    MySQL数据库大作业--学生管理系统GUI 原程序链接: https://www.bbsmax.com/A/kmzL3WQBdG/ 为了完成数据库大作业, 我在其基础上进行了一定的修改和扩充. 如果 ...

  5. Java大作业-考试管理系统(GUI)无数据库-Java课程设计

    Java课程设计-Java大作业-考试管理系统(GUI)无数据库 题目要求 开发环境 : 程序总功能模块 程序详细设计 使用情况 第一次写这东西有不少错误的地方,题目要求的功能也没有完全实现,以上内容 ...

  6. HTML5期末大作业:管理系统网站设计——学生信息管理系统模板 (13页) HTML+CSS+JavaScript html网页设计期末大作业_网页设计平时作业

    HTML5期末大作业:管理系统网站设计--学生信息管理系统模板 (13页) HTML+CSS+JavaScript html网页设计期末大作业_网页设计平时作业 常见网页设计作业题材有 个人. 美食. ...

  7. HTML5期末大作业:管理系统后台网站设计——代理商销售管理系统后台(8页) HTML+CSS+JavaScript web前端设计与开发期末作品/期末大作业

    HTML5期末大作业:管理系统后台网站设计--代理商销售管理系统后台(8页) HTML+CSS+JavaScript web前端设计与开发期末作品/期末大作业 常见网页设计作业题材有 个人. 美食. ...

  8. C6软件测试大乐,软件测试大作业——社团管理系统

    <软件测试大作业--社团管理系统>由会员分享,可在线阅读,更多相关<软件测试大作业--社团管理系统(19页珍藏版)>请在人人文库网上搜索. 1.山东科技大学软件测试技术与工具课 ...

  9. c语言程序设计超市会员卡管理系统,C语言程序设计课程设计报告超市水果信息管理系统...

    <C语言程序设计课程设计报告超市水果信息管理系统>由会员分享,可在线阅读,更多相关<C语言程序设计课程设计报告超市水果信息管理系统(28页珍藏版)>请在人人文库网上搜索. 1. ...

最新文章

  1. Java Socket 客户端使用指定端口多次连接服务器引发 BindException
  2. webclient 和 webrequest获取网页源码的
  3. JVM实用参数(一)JVM类型以及编译器模式
  4. IntelliJ IDEA代码常用的快捷键(自查)
  5. 最长公共子上升序列(信息学奥赛一本通-T1306)
  6. mysql中示例库安装_MySQL 官方示例数据库安装
  7. 字符设备驱动笔记——中断方式按键驱动之linux中断处理结构(五)
  8. Code[VS]1997 守卫者的挑战
  9. 基于JAVA乐居租房网的设计与实现计算机毕业设计源码+系统+lw文档+部署
  10. 纯净PE推荐——优启通 v3.3.2019.0605
  11. python概述ppt_江红-第1章-Python概述ppt
  12. 网络安全职业_如何开始网络安全职业
  13. html5中translate,css3 中translate和transition的使用方法
  14. windows terminal使用管理员权限打开
  15. chrome控制台如何把vw显示成px_Python + selenium + Chrome 模拟登陆QQ邮箱,批量下载附件,本地重命名
  16. 前端开发:npm install 报错npm ERR! Cannot read property ‘extraneous‘ of undefined的解决方法
  17. [noip2005]篝火晚会
  18. jy-12-SPRINGMYBATIS02——云笔记07-刘苍松
  19. DDR3 SPEC
  20. 冒泡排序法定向冒泡排序法的Python实现

热门文章

  1. 使用Linux桌面系统的5大巨头竟是他们!!!
  2. 京东科技成立保险事业部,能否成为“赶超”蚂蚁集团突破口?
  3. 2022-2028年全球与中国5G信号分析仪行业竞争格局与投资战略研究
  4. Java模拟抽奖。奖池有以下几个奖项:【2,1888,588,388,2888】打印出抽奖结果,要求随机且不重复。两种方法(代码和优化后的代码)
  5. journal of neuroscience:面孔的神经表征与眼动模式相协调
  6. Ocean wave
  7. CAUC数据结构与算法期末复习归纳(二)
  8. Switch版初音 mega39去渲染 获得PS4版初音街机效果说明
  9. [转帖]输出宏的内容
  10. SIMD优化之ARM纯汇编开发