【C语言】通讯录——示例以及详解

目录

前言

代码部分

功能介绍

1.浏览信息

联系人结构体定义

 初始化id信息函数

初始化Name信息函数

初始化Tel信息函数

 开辟节点空间

 添加节点函数

信息初始化封装函数

 页数结构体定义

计算页数函数

展示页小菜单

 信息展示

翻页功能

 浏览功能封装

2.添加信息

 字符串获取函数(注意)

 手动输入函数

3.查询信息

 4.主函数

总结

后记


前言

对于刚接触c语言的小伙伴们,通讯录是第一个综合性我们所学的知识的程序,存在一定难度,那么本篇是我写的通讯录,一共300多行,虽然功能少以及依然存在些许问题,但所用到的知识点我会为大家逐一注释并讲解,如果有写错的也欢迎各位大佬指出,我会及时改正(叠盾)

代码部分

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>


//全局变量定义
int g_Menutype;
char g_Key;
//节点结构体
typedef struct Node
{
	int nID;
	char* Name;
	char* Tel;
	struct Node* pNext;
}List;
//页数结构体
typedef struct PAGE
{
	int nCurrentPage;	//当前页
	int OnePageInfo;	//每页信息数量
	int nTotalInfo;		//信息总数
	int nTotalPage;		//总页数
}Page;
//字符串获取
char* Getstring()
{
	int nSize = 5;
	char* str = malloc(nSize);
	int nCount = 0;
	char c;
	scanf_s(" ");//过滤空白字符!!!
	if (str)
	{
		while ((c = getchar()) != '\n')
		{
			str[nCount] = c;
			nCount++;
			if (nCount == nSize)
				str = realloc(str, nSize += 5);
		}
		str[nCount] = '\0';
	}
	return str;
}
//自动获取id
int GetID()
{
	static int a = 0;//静态变量只会初始化一次
	a++;
	return a;
}
//自动生成测试姓名
char* GetName()
{
	char* str = malloc(sizeof(char) * 6);
	if (str)
	{
		for (int i = 0; i < 5; i++)
			str[i] = rand() % 26 + 'a';
		str[5] = '\0';
	}
	return str;
}
//自动生成电话号码
char* GetTel()
{
	char* str = malloc(12);
	if (str)
	{
		switch (rand() % 4)
		{
		case 0:
			str[0] = '1';
			str[1] = '3';
			str[2] = '3';
			break;//133
		case 1:
			str[0] = '1';
			str[1] = '5';
			str[2] = '5';
			break;//155
		case 2:
			str[0] = '1';
			str[1] = '7';
			str[2] = '7';
			break;//177
		case 3:
			str[0] = '1';
			str[1] = '8';
			str[2] = '8';
			break;//188
		}
		for (int i = 3; i < 11; i++)
			str[i] = rand() % 10 + '0';
		str[11] = '\0';
	}
	return str;
}
//开辟空间
List* GetNode()
{
	List* pTemp = malloc(sizeof(List));
	if (pTemp)
	{
		pTemp->nID = GetID();
		pTemp->Name = GetName();
		pTemp->Tel = GetTel();
		pTemp->pNext = NULL;
	}
	return pTemp;
}
//添加节点
void AddNode(List** pHead, List** pEnd, List* pNode)
{
	if (*pHead == NULL)*pHead = pNode;
	else (*pEnd)->pNext = pNode;
	*pEnd = pNode;
}
//信息初始化封装
void InitInfo(List** pHead, List** pEnd, int n)
{
	srand((unsigned int)time(NULL));
	for (int i = 0; i < n; i++)
		AddNode(pHead, pEnd, GetNode());
}
//计算页数
Page* GetPage(List* pHead)
{
	Page* pPage = malloc(sizeof(Page));
	if (pPage)
	{
		pPage->nCurrentPage = 0;
		pPage->OnePageInfo = 10;
		pPage->nTotalInfo = 0;
		List* pMark = pHead;
		while (pMark != NULL)
		{
			pPage->nTotalInfo++;
			pMark = pMark->pNext;
		}
		if (pPage->nTotalInfo % pPage->OnePageInfo == 0)
			pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo;
		else pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo + 1;
	}
	return pPage;
}
//信息展示
void ShowInfo(List* pHead, Page* pPage)
{
	int nBegin = (pPage->nCurrentPage - 1) * pPage->OnePageInfo + 1;
	int nEnd = pPage->nCurrentPage * pPage->OnePageInfo;
	int nCount = 0;

	while (pHead != NULL)
	{
		nCount++;
		if (nCount >= nBegin && nCount <= nEnd)
			printf("%d  %s  %s\n", pHead->nID, pHead->Name, pHead->Tel);
		pHead = pHead->pNext;
	}
}
//菜单
void ShowMenu(Page* pPage)
{
	switch (g_Menutype)
	{
	case 1:
		printf("\n当前第%d页   共%d页    共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
		printf("\nw上一页     s下一页   b返回\n");
		break;
	case 3:
		printf("\n当前第%d页   共%d页    共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
		printf("\nw上一页     s下一页   b返回   c重新查询\n");
		break;
	}
}
//翻页功能
void TurnPage(List* pHead, Page* pPage)
{
	char c = 's';//默认向下翻一页,因为一开始当前页为0
	while (1)
	{
		switch (c)
		{
		case 'w':
			if (pPage->nCurrentPage > 1)
			{
				system("cls");
				pPage->nCurrentPage--;
				ShowInfo(pHead, pPage);
				ShowMenu(pPage);
			}
			else printf("已经是第一页了...\n");
			break;
		case 's':
			if (pPage->nCurrentPage < pPage->nTotalPage)
			{
				system("cls");
				pPage->nCurrentPage++;
				ShowInfo(pHead, pPage);
				ShowMenu(pPage);
			}
			else printf("已经是最后一页了...\n");
			break;
		case 'b':
			return;
			break;
		case 'c':
			return;
			break;
		default:
			printf("按错了哦...\n");
			break;
		}
		scanf_s(" %c", &c);//多加空格为过滤空白字符如\n
		g_Key = c;
	}
}
//浏览封装
void Browse(List* pHead)
{
	Page* pPage = GetPage(pHead);
	TurnPage(pHead, pPage);
	free(pPage);
	pPage = NULL;
}
//手动输入
List* GetNodeIn()
{
	List* pTemp = (List*)malloc(sizeof(List));
	if (pTemp)
	{
		pTemp->nID = GetID();
		printf("请输入姓名:\n");
		pTemp->Name = Getstring();
		printf("请输入电话号:\n");
		pTemp->Tel = Getstring();
		pTemp->pNext = NULL;
	}
	return pTemp;
}
//查询
void Search(List* pHead)
{
	List* pMark = pHead;
	List* New_pHead = NULL;
	List* New_pEnd = NULL;
	while (1)
	{
		char* str = NULL;
		while (1)
		{
			printf("请输入关键字:\n");
			str = Getstring();
			printf("按a确认  任意键重新输入\n");
			char a;
			scanf_s("%c", &a);
			if (a == 'a')break;
			else
			{
				free(str);
				str = NULL;
			}
		}
		//匹配
		pHead = pMark;
		while (pHead != NULL)
		{
			if (strncmp(str, pHead->Name, strlen(str)) == 0 || strncmp(str, pHead->Tel, strlen(str)) == 0)
			{
				List* pTemp = malloc(sizeof(List));
				if (pTemp)
				{
					pTemp->nID = pHead->nID;
					pTemp->Name = pHead->Name;
					pTemp->Tel = pHead->Tel;
					pTemp->pNext = NULL;
					AddNode(&New_pHead, &New_pEnd, pTemp);
				}
			}
			pHead = pHead->pNext;
		}
		//显示
		Browse(New_pHead);
		//释放新链表
		while (New_pHead != NULL)
		{
			List* pTemp = New_pHead;
			if (pTemp)
			{
				New_pHead = New_pHead->pNext;
				free(pTemp);
				pTemp = NULL;
			}
		}
		New_pEnd = NULL;
		free(str);
		str = NULL;

		if (g_Key == 'b')
			break;
	}
}
//主函数
int main()
{
	List* pHead = NULL;
	List* pEnd = NULL;

	InitInfo(&pHead, &pEnd, 100);


	while (1)
	{
		system("cls");
		printf("1.浏览信息\n");
		printf("2.添加信息\n");
		printf("3.查询信息\n");
		printf("q.退出\n");
		char a;
		scanf_s(" %c", &a);
		switch (a)
		{
		case '1':
			g_Menutype = 1;
			Browse(pHead);
			break;
		case '2':
			AddNode(&pHead, &pEnd, GetNodeIn());
			break;
		case '3':
			g_Menutype = 3;
			Search(pHead);
			break;
		case 'q':
			return;
			break;
		}
	}


	return 0;
}

功能介绍

根据主函数我们可以得知我们一共要实现3个大功能:

1.浏览信息

联系人结构体定义

首先定义一个节点的结构体,我们把一个联系人作为一个节点,假定一个节点有id,Name,Tel以及指向下个节点的指针pNext,元素分别代表人数id需要自动生成,Name姓名,Tel电话号,并typedef节点名为List。

//节点结构体
typedef struct Node
{
	int nID;
	char* Name;
	char* Tel;
	struct Node* pNext;
}List;

 初始化id信息函数

这里我们需要用到静态局部变量,因为静态局部变量在一次程序中只会初始化一次,故可以很方便的自动计算出人数

//自动获取id
int GetID()
{
	static int a = 0;//静态变量只会初始化一次
	a++;
	return a;
}

初始化Name信息函数

作为测试数据,人名随机生成即可,那么我们以5个字母为一个人名,每个人名随机生成,这里需要用到rand()函数,并且为了保证接近真随机需要用srand函数以time为种子,会在封装时设置srand,if(str)的作用是用于检验防止str为空,防止后续调用内存出现问题

//自动生成测试姓名
char* GetName()
{
	char* str = malloc(sizeof(char) * 6);      
    //申请6个字节(char型占一字节)堆区空间

	if (str)        //检验申请的堆区空间是否为空,以防下边出现越界等情况
	{
		for (int i = 0; i < 5; i++)
			str[i] = rand() % 26 + 'a';     //对26取余,可以随机到0-25
                                            //此处字符a会转换成ASCII码值从97开始
                                            //即最小为97+0=97,代表字母a
                                            //最大为97+25=122,代表字母z
		str[5] = '\0';    //将下标为5的位置即第六个字节设置为'\0',代表字符串结束
	}
	return str;    //返回str
}

初始化Tel信息函数

对于自动生成的电话号码,我们假定有133、155、177、188,从而便于我们模糊搜索测试功能,此处我们要随机在这四个号码里生成,依然用到rand()函数,同时由于电话号码也是字符串,我们假设电话号码都是11位,所以定义一个长度为12的堆区空间,来保证最后一位放‘\0’

//自动生成电话号码
char* GetTel()
{
	char* str = malloc(12);    //同理如上
	if (str)
	{
		switch (rand() % 4)    //随机一个0-3的数字
                               //以便选择是那个前缀开头的电话号
		{
		case 0:                //也可以用strncpy函数,但记得写头文件
			str[0] = '1';
			str[1] = '3';
			str[2] = '3';
			break;        //133
		case 1:
			str[0] = '1';
			str[1] = '5';
			str[2] = '5';
			break;        //155
		case 2:
			str[0] = '1';
			str[1] = '7';
			str[2] = '7';
			break;        //177
		case 3:
			str[0] = '1';
			str[1] = '8';
			str[2] = '8';
			break;        //188
		}
		for (int i = 3; i < 11; i++)        
        //其余的从下标为3的位置,即第四个字节。开始继续随机填入数字0-10
			str[i] = rand() % 10 + '0';
		str[11] = '\0';
	}
	return str;
}

 开辟节点空间

需要测试生成用的数据的函数写完了,那么我们开始为每个节点开辟空间,如下

//开辟空间
List* GetNode()
{
	List* pTemp = malloc(sizeof(List));    
        //c语言可以不用强制类型转换,c++记得加

	if (pTemp)
	{
		pTemp->nID = GetID();    //分别自动通过函数获取数据
		pTemp->Name = GetName();
		pTemp->Tel = GetTel();
		pTemp->pNext = NULL;
	}
	return pTemp;    //最终返回节点
}

 添加节点函数

此处需要重点注意,这里比较难理解,第二种情况我进行了画图解释,我们使用了二重指针,我们需要调用头指针尾指针的地址而不改变他们本身指向的数据,所以我们使用二重指针,利用地址的地址调用指针的地址

同时,我们要考虑到节点在添加时有两种情况,一种是链表中任何数据都没有,二是链表中已经有数据了

情况1(没有数据):让头结点等于新来节点即可,然后让尾结点也等于新来节点

情况2:在已经存在数据的情况下如何插入结尾呢?我们在写链表时一般会保持一个原则:先连接,后断开。那么根据这个原则,我们只能把新来节点连接到尾结点后边,即(*pEnd)->pNext = pNode;为什么要这样写呢,因为我们知道  ->  符号代表 间接引用 + .  的作用,所以这行代码相当于对pEnd进行两次间接引用所以也可以写成(**pEnd)->pNext=pNode; 则直接使用的就是 4 对应的节点,那么这时完成连接之后我们可以断开pEnd与最后一个节点的联系,让pEnd指向新来节点,让其成为尾结点,而指向原来节点的指针不会因为pEnd指向改变而改变

//添加节点
void AddNode(List** pHead, List** pEnd, List* pNode)
{
	if (*pHead == NULL)*pHead = pNode;
	else (*pEnd)->pNext = pNode;
	*pEnd = pNode;
}

信息初始化封装函数

由于此处需要用到前边写的AddNode函数所以为了正常传参直接使用二重指针,则使用时参数直接写pHead,pEnd就可以,已经是需要传入的参数类型了,如果参数类型不会看,在之后的文章里会再做讲解,此处不过多赘述,至于新的节点直接开辟使用GetNode()函数,开辟多少个我们自己输入就可以,传参n即可

此外此处还涉及到srand()函数,因为我们在开辟空间初始化信息时会用到rand函数,但是需要设置种子来保证接近真随机,则我们设置为time函数,为了防止内存泄漏,需要强制类型转换为unsigned int,对于srand函数的使用方法之后我也会在其他文章中写到,但也需要time.h函数才可使用time函数

//信息初始化封装
void InitInfo(List** pHead, List** pEnd, int n)
{
	srand((unsigned int)time(NULL));
	for (int i = 0; i < n; i++)
		AddNode(pHead, pEnd, GetNode());
}

 页数结构体定义

接下来我们要制作翻页功能,首先是定义结构体如下: 

//页数结构体
typedef struct PAGE
{
	int nCurrentPage;	//当前页
	int OnePageInfo;	//每页信息数量
	int nTotalInfo;		//信息总数
	int nTotalPage;		//总页数
}Page;

计算页数函数

为了防止数据量过大导致输出数据的时候过于多,不清晰,我们需要进行分页展示,那我们就我们设置10名联系人为一页当前页数设置为0,随着用户操作翻页增长总信息数随着节点增加而增加总页数随着节点数量变化而变化,即总页数=总信息数/每页信息数量

这时又有小伙伴要问了:为什么当前页设置为0呢,此处埋下一个包袱,请接下来看下去,后边我会解答,都看到这了,你肯定也很想了解在这个通讯录运行原理吧AwA

然后我们开始遍历链表,计算有多少节点,即多少条信息,同时为了防止由于pHead向下移动而导致无法后退找不到前边节点所以我们选择在该作用域内创建一个临时的标记用指针来遍历链表,通过保存链表中pHead来实现

最后我们计算总页数,注意除法是整除,若存在余数需要再加一页以保证展示的数据不会缺少,此处我们使用判断语句就好,如果会使用三目运算符也可以使用,只不过会有亿点点长

此处代码不进行注释

//计算页数
Page* GetPage(List* pHead)
{
	Page* pPage = malloc(sizeof(Page));
	if (pPage)
	{
		pPage->nCurrentPage = 0;
		pPage->OnePageInfo = 10;
		pPage->nTotalInfo = 0;
		List* pMark = pHead;
		while (pMark != NULL)
		{
			pPage->nTotalInfo++;
			pMark = pMark->pNext;
		}
		if (pPage->nTotalInfo % pPage->OnePageInfo == 0)
			pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo;
		else pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo + 1;
	}
	return pPage;
}

展示页小菜单

 此处会有一个选择函数是因为之后搜索我们会需要使用重新搜索功能,故需要另一个菜单来展示不同页面的选项(虽然现在的通讯录随时都能查询,已经不需要了,为了练习代码能力还是要写一下的)对于条件的判断,我们需要先创建一个全局变量,即g_Menutype,来保存我们在第一次主菜单的选择,在主菜单我们若是从浏览信息进入的则选择1,若是查询口进入我们则选择3,从而展示不同页面

//菜单
void ShowMenu(Page* pPage)
{
	switch (g_Menutype)
	{
	case 1:
		printf("\n当前第%d页   共%d页    共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
		printf("\nw上一页     s下一页   b返回\n");
		break;
	case 3:
		printf("\n当前第%d页   共%d页    共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
		printf("\nw上一页     s下一页   b返回   c重新查询\n");
		break;
	}
}

 信息展示

 下面则是正片,我们写了那么多函数,终于可以看到一个具有雏形的通讯录了

首先我们我们既然要只展示10条信息,我们会发现一件事情,我们的当前页是不确定的,因为我们需要跟着用户翻页而展示不同信息,换句话说,我们需要获取当前页是多少来确定我们输出多少到多少条信息,而当前页是用户操作,所以我们打不过就加入的原则(梗),我们直接使用用户翻到的当前页来计算我们从哪里开始输出,也就是第几个节点开始输出,通过简单推理得出这个数学式子,即初始位置=(当前页-1)*每页信息书+1,那么下界我们更好确定,直接用当前页*10即是要输出的最后一条信息

接下来我们选择while循环遍历链表来输出,判断头结点是否在上下界内,如果在则输出,,注意不要自作聪明填else break;会导致如果不是第一页的直接不输出任何节点,因为如果到第二页还会从头遍历,但却不在上下界内,判断条件不符合直接向下进行else break,退出循环,这是无意义的(我犯过错的说)

//信息展示
void ShowInfo(List* pHead, Page* pPage)
{
	int nBegin = (pPage->nCurrentPage - 1) * pPage->OnePageInfo + 1;
	int nEnd = pPage->nCurrentPage * pPage->OnePageInfo;
	int nCount = 0;

	while (pHead != NULL)
	{
		nCount++;
		if (nCount >= nBegin && nCount <= nEnd)    
            //判断头结点走到的位置是否在上下界内
			printf("%d  %s  %s\n", pHead->nID, pHead->Name, pHead->Tel);
		pHead = pHead->pNext;
	}
}

翻页功能

 既然写完了展示功能,光画饼的翻页功能依然要实现

首先我们把包袱抖出来,之前我们在设置当前页为0是因为我们选择完浏览信息需要直接展示出第一页的信息,但是我们为了避免过于麻烦(如果不信可以在此基础上自己改一改看看哪个麻烦,我也没写过,跟人感觉另一种会有些麻烦),合并入翻页系统,从0页自动先翻到第一页,在进行下一步用户操作

同时此处用到第二个全局变量g_Key,用于保存c的值,以防退出到上一级函数因为c已经是局部变量并不能使用而,使程序运行出问题(一般出问题的就在查询函数出的多一点)

以下代码我会写注释

//翻页功能
void TurnPage(List* pHead, Page* pPage)
{
	char c = 's';//默认向下翻一页,因为一开始当前页为0
	while (1)    //保证该次翻页之后还能再次翻页
	{
		switch (c)
		{
		case 'w':
			if (pPage->nCurrentPage > 1)    //大于第一页就可以翻页
			{
				system("cls");    //清屏指令
				pPage->nCurrentPage--;    //向前翻当前页-1
				ShowInfo(pHead, pPage);   //展示数据
				ShowMenu(pPage);          //展示小菜单
			}
			else printf("已经是第一页了...\n");    //否则输出该句话,且不翻页
			break;
		case 's':
			if (pPage->nCurrentPage < pPage->nTotalPage)    
                //小于最后一页就可以翻页
                //由于最后一页不确定是多少,所以选择直接使用总页数
                //以下同理向前翻页
			{
				system("cls");
				pPage->nCurrentPage++;
				ShowInfo(pHead, pPage);
				ShowMenu(pPage);
			}
			else printf("已经是最后一页了...\n");
			break;
		case 'b':    //返回主页
			return;
			break;
		case 'c':    //返回查询函数
			return;
			break;
		default:     //以上选择都没有满足则默认选择运行
			printf("按错了哦...\n");
			break;
		}
		scanf_s(" %c", &c);    //多加空格为过滤空白字符如\n
		g_Key = c;    //记录c的字符,g_Key是全局变量
        //以防退出循环到上一级函数,由于c已经在该作用域消亡而导致运行出问题
	}
}

 浏览功能封装

至此,我们的浏览功能已经完成,看到这里的朋友,恭喜你,你已完成了这个小程序的80%

这里我们传参只要传一个头结点就好,因为我们封装的功能只用得到头结点

//浏览封装
void Browse(List* pHead)
{
	Page* pPage = GetPage(pHead);    //定义一个pPage节点
	TurnPage(pHead, pPage);    //可以调出第一页,直接调用该函数
	free(pPage);            //释放开辟的pPage堆区空间
	pPage = NULL;           //赋空
}

2.添加信息

 字符串获取函数(注意)

到了这里一定要注意,有小伙伴会问,为什么不用scanf函数?

因为我们下边在搜索或输入信息的时候往往长度是不固定的,而字符串的输入方式我们首先想到的是常见的两种,一是定义数组,通过循环函数不断用下标一个个输入数据,而且数据长度有限;二是使用char*指针,而我们为了防止出现内存泄露等问题,写一个可以随我们数据的长度自动增长的字符串获取函数,此处我们除了开辟堆区空间外,还需用到realloc函数,用来扩展堆区空间,realloc函数本质上是对已有的堆区空间重新开辟,但在这里我们则可以用它这个性质来扩展已有的堆区空间

在堆区空间如何输入字符串呢?我们采取while循环,,由于输入长度并不确定,所以我们采取现将输入的字符串放进缓存区,然后从缓存区将字符在一个个拿出来放进定义的char*变量里,即c=getchar(),这里我们发现在while循环判断条件里,对于c的getchar多用了一个括号,这里要注意优先级,赋值符号优先级要低于判断符号,所以如果不加括号就会有限进行getchar与'\n'进行比较,就会导致c为乱码,自动认为判断c的值真假,从而使程序出问题

另外scanf(“ ”);这句话作用是用来过滤空白字符,在我们重新查询信息的时候,回车会被当做字符进入缓存区,为了过滤掉整个回车我们选择这样的方式来解决

//字符串获取
char* Getstring()
{
	int nSize = 5;
	char* str = malloc(nSize);    //要输入的字符串
	int nCount = 0;
	char c;
	scanf_s(" ");    //过滤空白字符!!!以防下次输入出现问题
	if (str)
	{
		while ((c = getchar()) != '\n')    //注意优先级
		{
			str[nCount] = c;    //将缓冲区的字符一个个放进去
			nCount++;           //下标移动
			if (nCount == nSize)
				str = realloc(str, nSize += 5);    //扩展堆区空间
		}
		str[nCount] = '\0';    //使其变成字符串
	}
	return str;
}

 手动输入函数

该函数跟初始化节点时只把初始化姓名函数GetName()和初始化电话号函数GetTel()变成了Getstring()函数,以便我们添加信息,很简单,不进行注释

//手动输入
List* GetNodeIn()
{
	List* pTemp = (List*)malloc(sizeof(List));
	if (pTemp)
	{
		pTemp->nID = GetID();
		printf("请输入姓名:\n");
		pTemp->Name = Getstring();
		printf("请输入电话号:\n");
		pTemp->Tel = Getstring();
		pTemp->pNext = NULL;
	}
	return pTemp;
}

3.查询信息

我们可以看到这个函数,对于刚学完c的我们来说它十——分——的长,且复杂

这个函数要实现对于姓名和电话号码的模糊查询

那么我来拆分一下该函数,一一讲解

首先,我们函数要实现模糊查询,需要输入我们要查询的字符串,我们先定义一个char*型变量str,然后鉴于我们用户的手不是很好,有的时候可能会打错要输入的数据,要做一个保险,问用户是否确定要搜索该内容,如果否则重新输入,那么我们使用死循环while(1)来保证,如果确认按下a则退出循环,否则释放已输入的str重新输入

其次,匹配用户输入的关键字,为了防止遍历一次之后下次遍历找不到第一个结点,依然使用标记指针pMark来存pHead,同时由于我们需要查询之后输出对应关键字的信息,我们需要重新建立一个新的链表,那么我们设置New_pHead,New_End,即新头结点,新尾结点。此外还要注意我们是要写能进行多次查询的查询功能,这才是好查询,所以每次匹配关键词我们都要重置pHead,所以要掏出我们函数开头存pHead的pMark,这回赋给已经走完的pHead,即重置完成

再次,我们在输出形成新链表的时候,遍历链表时,需要判断是否满足与姓名或电话号其中一个的部分相同,所以我们选择使用strncmp函数,该函数如果判断相同,则得出0,判断条件则写该函数计算结果是否等于0,中间或  ||  符号连接,满足一个即可将该节点放进新的链表中

最后,我们展示信息,调用Browse函数,展示完我们将新的链表释放掉已经没有用了,通过遍历链表将每个节点释放掉

有人会问:为什么不直接释放掉新链表的链表头呢,这样不就都没了吗?

解答:因为每一个节点都是一个堆区空间,所以我们只是放了头结点,其他的依然在,占用空间,一旦数据量过大,查询次数多会导致死机cpu占用率过高,所以我们常常要释放掉每一个不用的堆区空间

同时释放掉关键词字符串str,为下一次查询做好准备以防造成内存泄露,最后在我们进行查询的是,会出现一个问题就是我们会发现我们退不出去了,卡在最外层while死循环中,然后程序卡出bug,这里g_Key的用处即是如果输入b退出,而在Browse函数中TurnPage由于无返回,c只是局部变量并不可以在外部使用,所以我们的g_Key的作用便是存下c的值如果是字符b就退出最外层循环,从而不会卡在外层循环,而且最重要的是注意好大括号范围,break只能退出一层循环

//查询
void Search(List* pHead)
{
	List* pMark = pHead;        //存头结点位置
	List* New_pHead = NULL;     //新链表头尾结点定义
	List* New_pEnd = NULL;
	while (1)                //死循环保证能多次查询
	{
		char* str = NULL;
		while (1)            //死循环保证能多次输入,给用户容错
		{
			printf("请输入关键字:\n");
			str = Getstring();        //使用字符串获取要输入的字符串
                                      //因为模糊搜索输入长度并不固定
			printf("按a确认  任意键重新输入\n");
			char a;
			scanf_s("%c", &a);
			if (a == 'a')break;
			else
			{
				free(str);
				str = NULL;
			}
		}
		//匹配用户输入的关键字    依然是GetNode翻版,然后使用AddNode填进新链表
		pHead = pMark;
		while (pHead != NULL)
		{
			if (strncmp(str, pHead->Name, strlen(str)) == 0 || strncmp(str, pHead->Tel, strlen(str)) == 0)
			{
				List* pTemp = malloc(sizeof(List));
				if (pTemp)
				{
					pTemp->nID = pHead->nID;
					pTemp->Name = pHead->Name;
					pTemp->Tel = pHead->Tel;
					pTemp->pNext = NULL;
					AddNode(&New_pHead, &New_pEnd, pTemp);
				}
			}
			pHead = pHead->pNext;
		}
		//显示
		Browse(New_pHead);
		//释放新链表
		while (New_pHead != NULL)
		{
			List* pTemp = New_pHead;
			if (pTemp)
			{
				New_pHead = New_pHead->pNext;
				free(pTemp);
				pTemp = NULL;
			}
		}
		New_pEnd = NULL;
		free(str);
		str = NULL;

		if (g_Key == 'b')    //用于退出外层循环
			break;
	}
}

 4.主函数

 至此主函数写完,整个程序结束,感谢你看到这里,朋友,后面还有总结,如果需要可以看一下,麻烦给个赞和关注,多谢!

//主函数
int main()
{
	List* pHead = NULL;
	List* pEnd = NULL;

	InitInfo(&pHead, &pEnd, 100);    //自动生成数据100条


	while (1)
	{
		system("cls");
		printf("1.浏览信息\n");    //主菜单展示
		printf("2.添加信息\n");
		printf("3.查询信息\n");
		printf("q.退出\n");
		char a;
		scanf_s(" %c", &a);        //输入选择
		switch (a)
		{
		case '1':
			g_Menutype = 1;
			Browse(pHead);
			break;
		case '2':
			AddNode(&pHead, &pEnd, GetNodeIn());
			break;
		case '3':
			g_Menutype = 3;
			Search(pHead);
			break;
		case 'q':
			return;
			break;
		}
	}


	return 0;
}

总结

 在这个程序中,我们能看得到功能不多,函数功能却很复杂,十分考验初学者的综合能力,在这里一些建议

1.多写多练习这个程序

2.Getstring函数尽量背下来

3.要能充分理解每一块函数的作用

4.联系各种函数的关系,要能理解透彻

做到以上四点,你的编程能力会提升一大截,然后可以再去尝试入门其他语言,因为此时你对编程语言的体系结构已经基本了解,其余细化知识点还需自己慢慢深入学习

后记

给个点赞关注谢谢!!!!!爱你们!!!

一、题目:通讯录管理 二、目的与要求 1. 目的: (1)基本掌握面向过程程序设计的基本思路和方法; (2)达到熟练掌握C语言的基本知识和技能; (3)能够利用所学的基本知识和技能,解决简单的程序设计问题 2. 要求 基本要求: 1. 要求利用C语言面向过程的编程思想来完成系统的设计; 2. 突出C语言的函数特征,以多个函数实现每一个子功能; 3. 画出功能模块图; 4. 具有清晰的程序流程图和数据结构的详细定义; 5. 熟练掌握C语言对文件的各种操作。 创新要求: 在基本要求达到后,可进行创新设计,如系统用户功能控制,对管理员级和一般级别的用户系统功能操作不同 三、信息描述 有关该系统基本信息的描述,如:姓名、电话、城市和邮编等。 四、功能描述 1. 名单基本信息(姓名,城市,电话,邮编等)的录入,并存放在文件当中。 2. 基本信息的查询与修改。 3. 记录的添加和删除。 4. 对同一类型记录的查找:如查找同一城市的记录或同一省份的记录。 五、解决方案 1. 分析程序的功能要求,划分程序功能模块。 2. 画出系统流程图。 3. 代码的编写。定义数据结构和各个功能子函数。 4. 程序的功能调试。 5. 完成系统总结报告以及使用说明书 六、进度安排 此次课程设计时间为一周或两周,分四个阶段完成: 1. 分析设计阶段。指导教师应积极引导学生自主学习和钻研问题,明确设计要求,找出实现方法,按照需求分析、总体设计、详细设计这几个步骤进行。 2. 编码调试阶段:根据设计分析方案编写C代码,然后调试该代码,实现课题要求的功能。 3. 总结报告阶段:总结设计工作,写出课程设计说明书,要求学生写出需求分析、总体设计、详细设计、编码、测试的步骤和内容。 4. 考核阶段。 七、撰写课程设计报告或课程设计总结 课程设计报告要求: 总结报告包括需求分析、总体设计、详细设计、编码(详细写出编程步骤)、测试的步骤和内容、课程设计总结、参考资料等,不符合以上要求者,则本次设计以不及格记。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰柠_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值