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();

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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值