手把手教你写通讯录(含动态版)

news2024/11/28 22:46:02

目录

一、框架

二、实现

1.初始化通讯录

2.增加联系人

3.打印通讯录

4.删除联系人

5.修改联系人

6.查找联系人

7.退出通讯录

8.拓展:通讯录排序

 9.全代码

三、动态版

1.结构体修改

2.初始化修改 

3.扩容实现

 4.善后函数

 5.全代码


一、框架

实现通讯录之前,我们要想一下,我们这个通讯录需要有什么功能。从手机自带的通讯录借鉴,通讯录的功能首先要能存放联系人的各种信息如:姓名,性别,年龄,联系方式,地址等等。此次要在这个基础上实现对通讯录存放的联系人的删除,查找,修改等等。

思路有了,那么我们先来打一个框架    先用printf实现一个菜单    这个菜单可以帮助我们更方便地使用通讯录,可理解为指引功能。接着,功能的选择,我们可以用switch实现   其次,这个通讯录,我们想让它一直进行下去,直到使用者不想用了,所以写一个循环    综上所述,代码实现我们可以写成这般

#include<stdio.h>
void menu()
{
		printf("**********************************************\n");
		printf("**********************************************\n");
		printf("*******1.增加联系人******2.删除联系人*********\n");
		printf("*******3.修改联系人******4.查找联系人*********\n");
		printf("*******5.打印通讯录******0.退出通讯录*********\n");
		printf("**********************************************\n");
		printf("**********************************************\n");
}
int main()
{
	int choose = 0;
	do
	{
        menu();
		scanf("%d", &choose);
		switch (choose)
		{
		case 1:
			//调用增加联系人的函数
			break;
		case 2:
			//调用删除联系人的函数
			break;
		case 3:
			//调用修改联系人信息的函数
			break;
		case 4:
			//调用查找联系人的函数
			break;
        case 5:
             //调用打印通讯录的函数
            break;
		case 0:
			//调用退出函数
			break;
		default:
			printf("语法错误,请重新输入\n");
			break;
		}
	} while (1);
	//写成无限循环,只能通过输入0退出循环
}

 功能还未具体实现,只是个框架,接着我们想一想,联系人不可能只有一个,会有很多个  那么我们应该要用数组存放,而联系人所包含的信息也很多   有姓名,年龄,性别,联系方式,地址等等,一个普通的数组根本放不下,而创建多个数组分别存放又显得非常繁琐。所以我们应该创建一个联系人结构体   这样就能创建出对应的结构体数组,即联系人数组,操作起来就会方便很多。

#include<stdio.h>
struct people
{
	char name[10];//姓名存放的是字符串,故用char
	int age;     //年龄存放的是一个数字,故用int
	char sex[5];//与名字的原因相同
	char tel[12];//与名字的原因相同
	char address[15];//与名字的原因相同
};

但是,如果未来我们想要一个能存放15个元素的名字,我们就得从头把10给改到尾,非常不方便,所以这里采用了宏定义的方式,定义了几个全局常量,这样便能实现牵一发而动全身

#include<stdio.h>
#define name_max 10
#define sex_max 5
#define tel_max 12
#define address_max 15
struct people
{
	char name[name_max];//姓名存放的是字符串,故用char
	int age;     //年龄存放的是一个数字,故用int
	char sex[sex_max];//与名字的原因相同
	char tel[tel_max];//与名字的原因相同
	char address[address_max];//与名字的原因相同
};

继续思考,联系人的结构体被定义完了,我们接下来是不是就应该创建一个对应的结构体数组然后思考怎么对着这个结构体数组增删改差啊,差不多到这一步了。但我们的框架还差一点。一开始我们的通讯录肯定是一个联系人都没有的,我们要往里边存放联系人,而每当你存放一个联系人就会使对应的联系人数组里边的联系人增多一个,下次再存放联系人的时候,对应数组的下标肯定要往后波动一个,我们是否可以创建一个变量专门来存放存放了多少个联系人   一开始没有联系人,那么这个变量就为0,对应数组第一个元素的下标,进行存放操作时就直接取它,而存放完一个后,变量就+1,对应下一次要存放的目标下标。可是单独创建一个这样的变量,到时函数传参的时候一定会多一个步骤   似乎不是很方便,要是我们传一个变量,这个变量不单单是联系人数组,而是联系人数组和已存放联系人的数量就好了   所以我们可以再创建一个叫通讯录的结构体,把这两个都包含进去   如此便可以实现在调用函数传参时传一个就可以实现我们需要的功能。

#include<stdio.h>
#define name_max 10
#define sex_max 5
#define tel_max 12
#define address_max 15
#define people_max 100//最多能存放100个联系人
typedef struct people
{
	char name[name_max];//姓名存放的是字符串,故用char
	int age;     //年龄存放的是一个数字,故用int
	char sex[sex_max];//与名字的原因相同
	char tel[tel_max];//与名字的原因相同
	char address[address_max];//与名字的原因相同
}peo;//来个别名,使用方便
typedef struct contact
{
	peo data[people_max];
	//结构体成员名为data,它是可以存放100个struct people类型元素的数组
	int sz;
	//已经存放了几个联系人
}con;//别名,使用方便

框架到这里基本就打好了,接下来便是实现

二、实现

1.初始化通讯录

在做一切操作之前我们都应该先创建一个通讯录类型(我们之前创建的那个结合了成员数组和存放个数)的变量,然后对它初始化,可以把初始化通讯录这个功能单独分装成一个函数,我们这个函数的返回类型笔者给的是空类型的,因为我们只是用它来初始化通讯录,当然你想的话可以给它的返回类型设计成通讯录的指针变量,这样到时就可以实现返回通讯录的首地址,实现链式法则,进行操作。

void init_contact(con* c1)//传址调用,实现修改
{
	assert(c1);
	//断言,避免传空指针,使用这个函数需要引assert.h头文件
	memset(c1->data,0,sizeof(c1->data));
	//c1->data,指向的是数组名,代表着数组首元素的地址
	//sizeof(c1->data)代表计算整个数组所占的字节数
	//0代表把给定地址往后的sizeof(c1->data)个字节都初始化为0
	//memset位于stdlib.h头文件中
	c1->sz = 0;//把c1指向的sz初始化为0
}

2.增加联系人

初始化完联系人,我们的所有前置准备动作算是终于做完,接下来就讲一下如何添加联系人。其实很简单,下标有了(sz,也就是当前通讯录存放的联系人个数)自然就能找到被操作的目标。通过箭头访问结构体中的成员    再通过sz找到目标,找到目标之后,使用scanf对它们修改就行   要注意的一点就是,当我们增加联系人的时候,通讯录已经放满了很显然就不能再放了,再放就属于是越界访问了所以我们应该来个判断,当存放的联系人达到上限了就别存了。

void add(con*c1)
{
	assert(c1);//断言防止传空指针
	if (c1->sz == people_max)
	{
		printf("通讯录已满,存放失败\n");
		//存放失败就直接返回,由于是空类型,所以直接return
		return;
	}
	else//通讯录没满就往里存
	{
		printf("请输入联系人姓名\n");
		scanf("%s",c1->data[c1->sz].name);//字符串变量名就是首地址
		printf("请输入联系人年龄\n");
		scanf("%d", &(c1->data[c1->sz].age));//年龄是整型,得取地址
		printf("请输入联系人性别\n");
		scanf("%s", c1->data[c1->sz].sex);
		printf("请输入联系人联系方式\n");
		scanf("%s", c1->data[c1->sz].tel);
		printf("请输入联系人家庭住址\n");
		scanf("%s", c1->data[c1->sz].address);
		printf("添加成功\n");
		c1->sz += 1;//联系人增多一名
	}
}

写好的函数别忘了放在switch语句中调用 

3.打印通讯录

实现增加联系人的功能之后你肯定会感觉怪怪的,我这添加了跟没添加一样,我又看不到,那么我们就一起来实现一个打印通讯录的功能,这样就能够看到我们添加进去的信息了,先思考返回类型,只是打印通讯录的内容,没有做别的操作,继续用空类型的,参数部分还是传通讯录结构体变量指针(有这个东西,通讯录的所有内容都可以找的出来),如此便已经可以实现我们的功能了,但我们可以设计的更完美一些,我们的目标只是打印通讯录的内容,并不会对内容进行修改,故我们可以在*号前加一个const修饰,这样通讯录结构体变量指针所指向的内容就不可能被修改了。

void print_contact(const con* c1)
{
	assert(c1);//断言避免传空指针
	int i = 0;
	if (c1->sz == 0)
	{
		printf("通讯录未存放联系人\n");
		return;
	}
	printf("%-10s\t%-5s\t%-5s\t%-12s\t%-15s\t\n", "姓名", "年龄", "性别", "联系方式", "家庭住址");
	//根据自己的喜好对齐,使信息更加明了
	for (i = 0; i < c1->sz; i++)//写个循环,因为可能不止一个联系人
	{
		printf("%-10s\t%-5d\t%-5s\t%-12s\t%-15s\t\n",
			c1->data[i].name,
			c1->data[i].age,
			c1->data[i].sex,
			c1->data[i].tel,
			c1->data[i].address);
	}
}

写好的函数别忘了放到switch语句中调用  

4.删除联系人

删除联系人,我们一样的返回类型一样采用空类型参数一样是通讯录结构体变量的指针,但由于要修改,故不用const修饰。我们可以通过输入姓名的方式,再通过strcmp和循环,循环次数显然是当前有多少个联系人就循环几次,相当于是把通讯录遍历一遍来寻找是否存在目标人物,存在就把它对应的下标存储起来,不存在就直接返回就行。找到目标后,下一步就是删除,删除的话可以通过覆盖的方式来删除,比方说我要删除的这个联系人的下标为2即第三个元素,而我已经存放了5个成员,那么我们就将第四个元素覆盖到第三个元素上,将第五个元素覆盖到第四个元素上,再对sz--就可以实现删除,理论存在,开始实践

void con_del(con* c1)//删除联系人
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	char a1[name_max] = { 0 };//初始化
	printf("请输入你要删除联系人的姓名\n");
	scanf("%s", &a1);
	int i = 0; int flaw = 0;
	for (i = 0; i < c1->sz; i++)
	{
		if (strcmp(a1, c1->data[i].name) == 0)
		{
			flaw = 1;//用来分析是否查找到目标
			break;//找到下标,退出循环
		}
	}
	if (flaw == 0)
	{
		printf("查无此人,删除失败\n");
		return;
	}
	for (; i < c1->sz-1; i++)
	//为什么是sz-1呢,避免越界操作,最后一个元素其实我们是可以不用管它的
    //因为sz--之后你下一次增加联系人的时候就会把它覆盖掉
	//且你不增加联系人的话,你肯定再也访问不到这个下标对应的元素了
	{
		c1->data[i] = c1->data[i+1];
	}
	printf("删除成功\n");
	c1->sz--;
}

 实现完这个部分之后,笔者又觉得这个查找目标下标的功能啊,它很方便,很有用啊,到时我们做查找并打印功能时就可以用到,于是我们把这部分功能分装成一个函数,让它通过姓名的方式为我们查找目标的下标

int find(const con* c1)
//要有返回值来判断是否找到目标
//只是寻找不修改,用const修饰
{
	char a1[name_max] = { 0 };//初始化
	scanf("%s", &a1);
	int i = 0;
	for (i = 0; i < c1->sz; i++)
	{
		if (strcmp(a1, c1->data[i].name) == 0)
		{
			return i;//找到目标,直接返回下标,注意下标可能为0
		}
	}
	return -1;//找不到目标,返回-1
}
void con_del(con* c1)//删除联系人
{	
    assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	printf("请输入你要删除的联系人的姓名\n");
	int i = find(c1);
	if (i!=-1)
	{//找到的话返回非0的数,为真,进入删除,没找到返回0,为假,不进入删除
		for (; i < c1->sz - 1; i++)
			//为什么是sz-1呢,避免越界操作,最后一个元素其实我们是可以不用管它的
			//因为sz--之后你下一次增加联系人的时候就会把它覆盖掉
			//且你不增加联系人的话,你肯定再也访问不到这个下标对应的元素了
		{
			c1->data[i] = c1->data[i + 1];
		}
		printf("删除成功\n");
		c1->sz--;
	}
	else
	{
		printf("查无此人,操作失败\n");
		return;
	}
}

5.修改联系人

修改联系人就简单了,有了之前那个寻找对应联系人的函数,我们已经能够快速的找到目标下标,也就是说,我们只要对着相应的东西改就行

void dif(con* c1)
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	else {
		printf("请输入你要修改的联系人的姓名\n");
		int i = find(c1);
		if (i != -1)
		{
			printf("请输入被修改后的姓名\n");
			scanf("%s", c1->data[i].name);
			printf("请输入被修改后的年龄\n");
			scanf("%d", &c1->data[i].age);
			printf("请输入被修改后的性别\n");
			scanf("%s", c1->data[i].sex);
			printf("请输入被修改后的联系方式\n");
			scanf("%s", c1->data[i].tel);
			printf("请输入被修改后的地址\n");
			scanf("%s", c1->data[i].address);
			printf("修改成功\n");
			return;
		}
		else
		{
			printf("查无此人,操作失败\n");
			return;
		}
	}
}

6.查找联系人

这个用我们的下标查找函数,找到下标再多一步打印即可

void search(con* c1)
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	else
	{
		printf("请输入你要查找的联系人姓名\n");
		int i=find(c1);
		if(i!=-1)
		{
			printf("%-10s\t%-5s\t%-5s\t%-12s\t%-15s\t\n", "姓名", "年龄", "性别", "联系方式", "家庭住址");
			printf("%-10s\t%-5d\t%-5s\t%-12s\t%-15s\t\n",
				c1->data[i].name,
				c1->data[i].age,
				c1->data[i].sex,
				c1->data[i].tel,
				c1->data[i].address);
			return;
		}
		else
		{
			printf("查无此人,操作失败\n");
			return;
		}
	}
}

7.退出通讯录

最简单的来了,exit()函数即可

8.拓展:通讯录排序

我们存放联系人的时候很可能是随便来的,就是想到谁就放谁,久而久之,通讯录就会变得杂乱无章,因此这里笔者特意写了一个通讯录排序的功能,这里面使用到了qsort函数,如果你对qsort函数不是很理解的话那可以去看笔者之前写的文章手把手教你使用qsort函数http://t.csdn.cn/wSMoW

别忘了将函数放进switch语句中,并重新规划菜单指引操作。 

基础款

int cmp_age(const void*ptr1,const void*ptr2 )
{
	return ((peo*)ptr1)->age - ((peo*)ptr2)->age;//升序排序
}
void sort(const con*c1)
{
	qsort(c1->data,c1->sz, sizeof(peo),cmp_age);
	//c1->data是存放联系人的结构体数组首元素的地址
	//c1->sz是要排的人数//sizeof(peo)为联系人结构体数组一个元素的大小
	//cmp_age是需要我们自己设计的函数
	//如何设计看笔者之前的文章,这里就不再赘述
	printf("排序成功\n");
}

 升级版

int cmp_age(const void*ptr1,const void*ptr2 )
{
	return ((peo*)ptr1)->age - ((peo*)ptr2)->age;//升序排序
}
int cmp_name(const void* ptr1, const void* ptr2)
{
	return strcmp(((peo*)ptr1)->name,((peo*)ptr2)->name);
}
int cmp_tel(const void* ptr1, const void* ptr2)
{
	return strcmp(((peo*)ptr1)->tel,((peo*)ptr2)->tel);
}
void sort(const con* c1)
{
	int choose = 0;
	do {
		printf("输入0退出排序\n");
		printf("输入1按照年龄排序\n");
		printf("输入2按照姓名排序\n");
		printf("输入3按照电话号码排序\n");
		scanf("%d", &choose);
		switch (choose)
		{
		case 1:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_age);
			//c1->data是存放联系人的结构体数组首元素的地址
			//c1->sz是要排的人数//sizeof(peo)为联系人结构体数组一个元素的大小
			//cmp_age是需要我们自己设计的函数
			//如何设计看笔者之前的文章,这里就不再赘述
			printf("排序成功\n");
			break;
		case 2:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_name);
			printf("排序成功\n");
			break;
		case 3:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_tel);
			printf("排序成功\n");
			break;
		case 0:
			return;
			break;
		default:
			printf("语法错误,请重新输入\n");
			break;
		}
	} while (1);//无限循环除非choose为0
}

 9.全代码

全都被笔者写在了一个文件中,其实最好还是分一下,比如说一个文件放函数的声明,一个文件来实现函数,一个文件来实现操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#define name_max 10
#define sex_max 5
#define tel_max 12
#define address_max 15
#define people_max 100//最多能存放100个联系人
typedef struct people
{
	char name[name_max];//姓名存放的是字符串,故用char
	int age;     //年龄存放的是一个数字,故用int
	char sex[sex_max];//与名字的原因相同
	char tel[tel_max];//与名字的原因相同
	char address[address_max];//与名字的原因相同
}peo;//来个别名,使用方便
typedef struct contact
{
	peo data[people_max];
	//结构体成员名为data,它是可以存放100个struct people类型元素的数组
	int sz;
	//已经存放了几个联系人
}con;//别名,使用方便
void menu()
{
		printf("**********************************************\n");
		printf("**********************************************\n");
		printf("*******1.增加联系人******2.删除联系人*********\n");
		printf("*******3.修改联系人******4.查找联系人*********\n");
		printf("*******5.打印通讯录******6.通讯录排序*********\n");
		printf("*******7.退出通讯录***************************\n");
		printf("**********************************************\n");
}
void init_contact(con* c1)//初始化通讯录
{
	assert(c1);
	//断言,避免传空指针,使用这个函数需要引assert.h头文件
	memset(c1->data,0,sizeof(c1->data));
	//c1->data,指向的是数组名,代表着数组首元素的地址
	//sizeof(c1->data)代表计算整个数组的字节数
	//0代表把给定地址往后的sizeof(c1->data)个字节都初始化为0
	//memset位于stdlib.h头文件中
	c1->sz = 0;//把c1指向的sz初始化为0
}
void add(con*c1)//增加联系人
{
	assert(c1);//断言防止传空指针
	if (c1->sz == people_max)
	{
		printf("通讯录已满,存放失败\n");
		//存放失败就直接返回,由于是空类型,所以直接return
		return;
	}
	else//通讯录没满就往里存
	{
		printf("请输入联系人姓名\n");
		scanf("%s",c1->data[c1->sz].name);//字符串变量名就是首地址
		printf("请输入联系人年龄\n");
		scanf("%d", &(c1->data[c1->sz].age));//年龄是整型,得取地址
		printf("请输入联系人性别\n");
		scanf("%s", c1->data[c1->sz].sex);
		printf("请输入联系人联系方式\n");
		scanf("%s", c1->data[c1->sz].tel);
		printf("请输入联系人家庭住址\n");
		scanf("%s", c1->data[c1->sz].address);
		printf("添加成功\n");
		c1->sz += 1;//联系人增多一名
	}
}
void print_contact(const con* c1)//打印通讯录
{
	assert(c1);//断言避免传空指针
	int i = 0;
	if (c1->sz == 0)
	{
		printf("通讯录未存放联系人\n");
		return;
	}
	printf("%-10s\t%-5s\t%-5s\t%-12s\t%-15s\t\n", "姓名", "年龄", "性别", "联系方式", "家庭住址");
	//根据自己的喜好对齐,使信息更加明了
	for (i = 0; i < c1->sz; i++)//写个循环,因为可能不止一个联系人
	{
		printf("%-10s\t%-5d\t%-5s\t%-12s\t%-15s\t\n",
			c1->data[i].name,
			c1->data[i].age,
			c1->data[i].sex,
			c1->data[i].tel,
			c1->data[i].address);
	}
}
int find(const con* c1)
//要有返回值来判断是否找到目标
//只是寻找不修改,用const修饰
{
	char a1[name_max] = { 0 };//初始化
	scanf("%s", &a1);
	int i = 0;
	for (i = 0; i < c1->sz; i++)
	{
		if (strcmp(a1, c1->data[i].name) == 0)
		{
			return i;//找到目标,直接返回下标,注意下标可能为0
		}
	}
	return -1;//找不到目标,返回-1
}
void con_del(con* c1)//删除联系人
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	printf("请输入你要删除的联系人的姓名\n");
	int i = find(c1);
	if (i!=-1)
	{//找到的话返回非0的数,为真,进入删除,没找到返回0,为假,不进入删除
		for (; i < c1->sz - 1; i++)
			//为什么是sz-1呢,避免越界操作,最后一个元素其实我们是可以不用管它的
			//因为sz--之后你下一次增加联系人的时候就会把它覆盖掉
			//且你不增加联系人的话,你肯定再也访问不到这个下标对应的元素了
		{
			c1->data[i] = c1->data[i + 1];
		}
		printf("删除成功\n");
		c1->sz--;
	}
	else
	{
		printf("查无此人,操作失败\n");
		return;
	}
}
void dif(con* c1)
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	else {
		printf("请输入你要修改的联系人的姓名\n");
		int i = find(c1);
		if (i != -1)
		{
			printf("请输入被修改后的姓名\n");
			scanf("%s", c1->data[i].name);
			printf("请输入被修改后的年龄\n");
			scanf("%d", &c1->data[i].age);
			printf("请输入被修改后的性别\n");
			scanf("%s", c1->data[i].sex);
			printf("请输入被修改后的联系方式\n");
			scanf("%s", c1->data[i].tel);
			printf("请输入被修改后的地址\n");
			scanf("%s", c1->data[i].address);
			printf("修改成功\n");
			return;
		}
		else
		{
			printf("查无此人,操作失败\n");
			return;
		}
	}
}
void search(con* c1)
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	else
	{
		printf("请输入你要查找的联系人姓名\n");
		int i=find(c1);
		if(i!=-1)
		{
			printf("%-10s\t%-5s\t%-5s\t%-12s\t%-15s\t\n", "姓名", "年龄", "性别", "联系方式", "家庭住址");
			printf("%-10s\t%-5d\t%-5s\t%-12s\t%-15s\t\n",
				c1->data[i].name,
				c1->data[i].age,
				c1->data[i].sex,
				c1->data[i].tel,
				c1->data[i].address);
			return;
		}
		else
		{
			printf("查无此人,操作失败\n");
			return;
		}
	}
}
int cmp_age(const void*ptr1,const void*ptr2 )
{
	return ((peo*)ptr1)->age - ((peo*)ptr2)->age;//升序排序
}
int cmp_name(const void* ptr1, const void* ptr2)
{
	return strcmp(((peo*)ptr1)->name,((peo*)ptr2)->name);
}
int cmp_tel(const void* ptr1, const void* ptr2)
{
	return strcmp(((peo*)ptr1)->tel,((peo*)ptr2)->tel);
}
void sort(const con* c1)
{
	int choose = 0;
	do {
		printf("输入0退出排序\n");
		printf("输入1按照年龄排序\n");
		printf("输入2按照姓名排序\n");
		printf("输入3按照电话号码排序\n");
		scanf("%d", &choose);
		switch (choose)
		{
		case 1:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_age);
			//c1->data是存放联系人的结构体数组首元素的地址
			//c1->sz是要排的人数//sizeof(peo)为联系人结构体数组一个元素的大小
			//cmp_age是需要我们自己设计的函数
			//如何设计看笔者之前的文章,这里就不再赘述
			printf("排序成功\n");
			break;
		case 2:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_name);
			printf("排序成功\n");
			break;
		case 3:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_tel);
			printf("排序成功\n");
			break;
		case 0:
			return;
			break;
		default:
			printf("语法错误,请重新输入\n");
			break;
		}
	} while (1);//无限循环除非choose为0
}
int main()
{
	int choose = 0;
	con c1;
	init_contact(&c1);
	do
	{
	    menu();
		scanf("%d", &choose);
		switch (choose)
		{
		case 1:
			add(&c1);//调用增加联系人的函数
			break;
		case 2:
			con_del(&c1);//调用删除联系人的函数
			break;
		case 3:
			dif(&c1);//调用修改联系人信息的函数
			break;
		case 4:
			search(&c1);//调用查找联系人的函数
			break;
		case 5:
			print_contact(&c1);//调用打印通讯录的函数
			break;
		case 6:
			sort(&c1);//调用排序函数
			break;
		case 0:
			exit(1);//调用退出函数
			break;
		default:
			printf("语法错误,请重新输入\n");
			break;
		}
	} while (1);
	//写成无限循环,只能通过输入0退出循环
}

三、动态版

1.结构体修改

动态版本我们通过malloc,realloc和free等一系列动态内存管理函数来实现,如果你对这方面不是很了解的话你可以参考一下笔者之前的文章手把手教你玩转内存函数http://t.csdn.cn/2hntB​​​​​​

首先要修改的是我们的结构体成员, 我们之前采用的是数组的方式实现成员的多少的,但在c环境下数组的定义要求是常量,才能创建数组因此它是不可变的现在我们采用指针的方式来实现一个能随着我们的需要动态分配内存的“数组”,再然后就是容量的大小很重要,它可以判断是否需要扩容

typedef struct contact
{
	peo* data;
	//使用指针的方式
	int sz;
	//已经存放了几个联系人
	int capacity;
	//总容量的大小
}con;//别名,使用方便

2.初始化修改 

void init_contact(con* c1)//初始化通讯录
{

	c1->sz = 0;
	c1->capacity = people_max;
	c1->data =(peo*)malloc(sizeof(peo)*people_max);
	//开辟数组
	if (c1->data == NULL)
	{
		perror("init_contact");
		//开辟失败,错误提示
		exit(1);
	}
}

3.扩容实现

void if_bigger(con* c1)
{
	if (c1->capacity == c1->sz)
   //当容量和存放的个数相等,空间不够,得扩容
	{
		c1->data = (peo*)realloc(c1->data,sizeof(peo)*(c1->capacity + 2));
		//扩容之后比之前的容量大2,按自己喜欢调节
		if (c1->data == NULL)
		{
			perror("if_bigger");
			//开辟失败,错误提示
			return;
		}
		else
		{
			c1->capacity += 2;//开辟成功容量+2
			printf("开辟成功,当前可存放联系人个数为%d\n", c1->capacity);
		}
	}
}

在增加联系人那里添加

void add(con*c1)//增加联系人
{
	assert(c1);//断言防止传空指针
	if_bigger(c1);//放在前面,每次来都先检测一下容量是否够
	if (c1->sz == c1->capacity)
 //这个也要修改,现在是和容量比,之前是和people_max比
	{
		printf("通讯录已满,存放失败\n");
		//存放失败就直接返回,由于是空类型,所以直接return
		return;
	}
	else//通讯录没满就往里存
	{
		printf("请输入联系人姓名\n");
		scanf("%s",c1->data[c1->sz].name);//字符串变量名就是首地址
		printf("请输入联系人年龄\n");
		scanf("%d", &(c1->data[c1->sz].age));//年龄是整型,得取地址
		printf("请输入联系人性别\n");
		scanf("%s", c1->data[c1->sz].sex);
		printf("请输入联系人联系方式\n");
		scanf("%s", c1->data[c1->sz].tel);
		printf("请输入联系人家庭住址\n");
		scanf("%s", c1->data[c1->sz].address);
		printf("添加成功\n");
		c1->sz += 1;//联系人增多一名
	}
}

这里笔者把people_max调成了3使初始容量减少,这样方便测试扩容效果

 4.善后函数

这个东西是用来摧毁通讯录的,放在退出那块就行

void destroy_contact(con*c1)
{
	c1->capacity = 0;
	c1->sz = 0;
	free(c1->data);//释放空间
	c1->data = NULL;//野指针变空指针
}

 5.全代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#define name_max 10
#define sex_max 5
#define tel_max 12
#define address_max 15
#define people_max 3
typedef struct people
{
	char name[name_max];//姓名存放的是字符串,故用char
	int age;     //年龄存放的是一个数字,故用int
	char sex[sex_max];//与名字的原因相同
	char tel[tel_max];//与名字的原因相同
	char address[address_max];//与名字的原因相同
}peo;//来个别名,使用方便
//typedef struct contact
//{
//	peo data[people_max];
//	//结构体成员名为data,它是可以存放100个struct people类型元素的数组
//	int sz;
//	//已经存放了几个联系人
//}con;//别名,使用方便
typedef struct contact
{
	peo* data;
	//使用指针的方式
	int sz;
	//已经存放了几个联系人
	int capacity;
	//总容量的大小
}con;//别名,使用方便
void destroy_contact(con*c1)
{
	c1->capacity = 0;
	c1->sz = 0;
	free(c1->data);//释放空间
	c1->data = NULL;//野指针变空指针
}
void menu()
{
		printf("**********************************************\n");
		printf("**********************************************\n");
		printf("*******1.增加联系人******2.删除联系人*********\n");
		printf("*******3.修改联系人******4.查找联系人*********\n");
		printf("*******5.打印通讯录******6.通讯录排序*********\n");
		printf("*******7.退出通讯录***************************\n");
		printf("**********************************************\n");
}
void init_contact(con* c1)//初始化通讯录
{
	//assert(c1);
	断言,避免传空指针,使用这个函数需要引assert.h头文件
	//memset(c1->data,0,sizeof(c1->data));
	c1->data,指向的是数组名,代表着数组首元素的地址
	sizeof(c1->data)代表计算整个数组的字节数
	0代表把给定地址往后的sizeof(c1->data)个字节都初始化为0
	memset位于stdlib.h头文件中
	//c1->sz = 0;//把c1指向的sz初始化为0
	c1->sz = 0;
	c1->capacity = people_max;
	c1->data =(peo*)malloc(sizeof(peo)*people_max);
	//开辟数组
	if (c1->data == NULL)
	{
		perror("init_contact");
		//开辟失败,错误提示
		exit(1);
	}
}
void if_bigger(con* c1)
{
	if (c1->capacity == c1->sz)//容量和存放的个数相等,显然空间不够
	{
		c1->data = (peo*)realloc(c1->data,sizeof(peo)*(c1->capacity + 2));
		//扩容之后比之前的容量大2
		if (c1->data == NULL)
		{
			perror("if_bigger");
			//开辟失败,错误提示
			return;
		}
		else
		{
			c1->capacity += 2;//开辟成功容量+2
			printf("开辟成功,当前可存放联系人个数为%d\n", c1->capacity);
		}
	}
}
void add(con*c1)//增加联系人
{
	assert(c1);//断言防止传空指针
	if_bigger(c1);//放在前面,每次来都先检测一下容量是否够
	if (c1->sz == c1->capacity)//这个也要修改,现在是和容量比
	{
		printf("通讯录已满,存放失败\n");
		//存放失败就直接返回,由于是空类型,所以直接return
		return;
	}
	else//通讯录没满就往里存
	{
		printf("请输入联系人姓名\n");
		scanf("%s",c1->data[c1->sz].name);//字符串变量名就是首地址
		printf("请输入联系人年龄\n");
		scanf("%d", &(c1->data[c1->sz].age));//年龄是整型,得取地址
		printf("请输入联系人性别\n");
		scanf("%s", c1->data[c1->sz].sex);
		printf("请输入联系人联系方式\n");
		scanf("%s", c1->data[c1->sz].tel);
		printf("请输入联系人家庭住址\n");
		scanf("%s", c1->data[c1->sz].address);
		printf("添加成功\n");
		c1->sz += 1;//联系人增多一名
	}
}
void print_contact(const con* c1)//打印通讯录
{
	assert(c1);//断言避免传空指针
	int i = 0;
	if (c1->sz == 0)
	{
		printf("通讯录未存放联系人\n");
		return;
	}
	printf("%-10s\t%-5s\t%-5s\t%-12s\t%-15s\t\n", "姓名", "年龄", "性别", "联系方式", "家庭住址");
	//根据自己的喜好对齐,使信息更加明了
	for (i = 0; i < c1->sz; i++)//写个循环,因为可能不止一个联系人
	{
		printf("%-10s\t%-5d\t%-5s\t%-12s\t%-15s\t\n",
			c1->data[i].name,
			c1->data[i].age,
			c1->data[i].sex,
			c1->data[i].tel,
			c1->data[i].address);
	}
}
int find(const con* c1)
//要有返回值来判断是否找到目标
//只是寻找不修改,用const修饰
{
	char a1[name_max] = { 0 };//初始化
	scanf("%s", &a1);
	int i = 0;
	for (i = 0; i < c1->sz; i++)
	{
		if (strcmp(a1, c1->data[i].name) == 0)
		{
			return i;//找到目标,直接返回下标,注意下标可能为0
		}
	}
	return -1;//找不到目标,返回-1
}
void con_del(con* c1)//删除联系人
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	printf("请输入你要删除的联系人的姓名\n");
	int i = find(c1);
	if (i!=-1)
	{//找到的话返回非0的数,为真,进入删除,没找到返回0,为假,不进入删除
		for (; i < c1->sz - 1; i++)
			//为什么是sz-1呢,避免越界操作,最后一个元素其实我们是可以不用管它的
			//因为sz--之后你下一次增加联系人的时候就会把它覆盖掉
			//且你不增加联系人的话,你肯定再也访问不到这个下标对应的元素了
		{
			c1->data[i] = c1->data[i + 1];
		}
		printf("删除成功\n");
		c1->sz--;
	}
	else
	{
		printf("查无此人,操作失败\n");
		return;
	}
}
void dif(con* c1)
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	else {
		printf("请输入你要修改的联系人的姓名\n");
		int i = find(c1);
		if (i != -1)
		{
			printf("请输入被修改后的姓名\n");
			scanf("%s", c1->data[i].name);
			printf("请输入被修改后的年龄\n");
			scanf("%d", &c1->data[i].age);
			printf("请输入被修改后的性别\n");
			scanf("%s", c1->data[i].sex);
			printf("请输入被修改后的联系方式\n");
			scanf("%s", c1->data[i].tel);
			printf("请输入被修改后的地址\n");
			scanf("%s", c1->data[i].address);
			printf("修改成功\n");
			return;
		}
		else
		{
			printf("查无此人,操作失败\n");
			return;
		}
	}
}
void search(con* c1)
{
	assert(c1);
	if (c1->sz == 0)
	{
		printf("通讯录为空,操作失败\n");
		return;
	}
	else
	{
		printf("请输入你要查找的联系人姓名\n");
		int i=find(c1);
		if(i!=-1)
		{
			printf("%-10s\t%-5s\t%-5s\t%-12s\t%-15s\t\n", "姓名", "年龄", "性别", "联系方式", "家庭住址");
			printf("%-10s\t%-5d\t%-5s\t%-12s\t%-15s\t\n",
				c1->data[i].name,
				c1->data[i].age,
				c1->data[i].sex,
				c1->data[i].tel,
				c1->data[i].address);
			return;
		}
		else
		{
			printf("查无此人,操作失败\n");
			return;
		}
	}
}
int cmp_age(const void*ptr1,const void*ptr2 )
{
	return ((peo*)ptr1)->age - ((peo*)ptr2)->age;//升序排序
}
int cmp_name(const void* ptr1, const void* ptr2)
{
	return strcmp(((peo*)ptr1)->name,((peo*)ptr2)->name);
}
int cmp_tel(const void* ptr1, const void* ptr2)
{
	return strcmp(((peo*)ptr1)->tel,((peo*)ptr2)->tel);
}
void sort(const con* c1)
{
	int choose = 0;
	do {
		printf("输入0退出排序\n");
		printf("输入1按照年龄排序\n");
		printf("输入2按照姓名排序\n");
		printf("输入3按照电话号码排序\n");
		scanf("%d", &choose);
		switch (choose)
		{
		case 1:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_age);
			//c1->data是存放联系人的结构体数组首元素的地址
			//c1->sz是要排的人数//sizeof(peo)为联系人结构体数组一个元素的大小
			//cmp_age是需要我们自己设计的函数
			//如何设计看笔者之前的文章,这里就不再赘述
			printf("排序成功\n");
			break;
		case 2:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_name);
			printf("排序成功\n");
			break;
		case 3:
			qsort(c1->data, c1->sz, sizeof(peo), cmp_tel);
			printf("排序成功\n");
			break;
		case 0:
			return;
			break;
		default:
			printf("语法错误,请重新输入\n");
			break;
		}
	} while (1);//无限循环除非choose为0
}
int main()
{
	int choose = 0;
	con c1;
	init_contact(&c1);
	do
	{
	    menu();
		scanf("%d", &choose);
		switch (choose)
		{
		case 1:
			add(&c1);//调用增加联系人的函数
			break;
		case 2:
			con_del(&c1);//调用删除联系人的函数
			break;
		case 3:
			dif(&c1);//调用修改联系人信息的函数
			break;
		case 4:
			search(&c1);//调用查找联系人的函数
			break;
		case 5:
			print_contact(&c1);//调用打印通讯录的函数
			break;
		case 6:
			sort(&c1);
			break;
		case 0:
			destroy_contact(&c1);//摧毁通讯录
			exit(1);//调用退出函数
			break;
		default:
			printf("语法错误,请重新输入\n");
			break;
		}
	} while (1);
	//写成无限循环,只能通过输入0退出循环
}

今天的分享到这里就结束了,感谢各位友友的来访,祝各位前程似锦O(∩_∩)O

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/763681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IntelliJ IDEA 版本控制

IntelliJ IDEA 版本控制&#xff08;VCS&#xff09;日常使用方法备忘 1、搁置更改 2、移至另一个更改列表 对于工程项目中的配置文件&#xff0c;已经在本地修改但是不能提交&#xff0c;如果在提交项目代码时全选变更的文件&#xff0c;可能会误提交配置文件&#xff0c;此…

语音芯片播放消耗电流过大,导致MCU复位

1、问题 在多次遇到播放声音时突然黑屏&#xff0c;单片机复位或者语音播放不全现象&#xff0c;之前使用示波器测量播放声音时供电电池电压&#xff0c;电池电压的确被拉低。之前的解决方案是使用南孚电池&#xff0c;先把供电电压给高一些&#xff0c;这样即使把电压拉低&am…

Mybatis中表关系查询结果集映射

文章目录 前言1. 实体类设计1.1 用户表1.2 地址表1.3 博客表1.4 粉丝互关表 2.插入数据3.表关联查询3.1 一对一关系3.2 一对多关系3.3 多对多关系 前言 resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来&#xff…

人工智能数学基础--概率与统计15:多维随机变量/向量

一、多维随机变量定义 一般地,设X(X1,X2,,X,)为一个n维向量&#xff0c;其每个分量,即X1、、Xn都是一维随机变量&#xff0c;则称X是一个n维随机向量或n 维随机变量。 与随机变量一样&#xff0c;随机向量也有离散型和连续型之分。 二、离散型多维随机向量 一个随机向量X(X…

程序环境和预处理超详细讲解

目录 程序的翻译环境和执行环境 详解编译链接 翻译环境 编译本身也分为几个阶段 运行环境 预处理&#xff08;预编译&#xff09;详解 预定义符号 #define #define 定义标识符 #define 定义宏 #define 替换规则 #和## ## 的作用 带副作用的宏参数 宏和函数对比 …

git如何撤销commit(未push)

文章目录 前言undo commitreset current branch to here Undo Commit&#xff0c;Revert Commit&#xff0c;Drop Commit的区别 是否删除对代码的修改是否删除Commit记录是否会新增Commit记录Undo Commit不会未Push会&#xff0c;已Push不会不会Revert Commit会不会会Drop Com…

看看去年蓝桥考了什么,第十三届蓝桥杯省赛(C/C++ 大学B组)题解

文章目录 A&#xff1a;九进制转十进制问题描述运行限制题目思路代码演示 B&#xff1a;顺子日期问题描述运行限制题目思路代码演示 C&#xff1a;刷题统计问题描述评测用例规模与约定运行限制题目思路代码演示 D&#xff1a;修剪灌木问题描述评测用例规模与约定运行限制题目思…

【广州华锐互动】VR地铁消防逃生路线演练系统

随着城市轨道交通的不断发展&#xff0c;事故应急演练的重要性也越来越受到重视。而VR技术的应用&#xff0c;为地铁消防逃生路线演练带来了许多亮点&#xff0c;包括以下几个方面&#xff1a; 首先&#xff0c;VR技术可以提供高度真实的模拟场景。在传统的事故应急演练中&…

t-date-time-picker如何默认当前年月

打开小程序展示当前年月&#xff0c;效果图如下 实现方法&#xff1a;使用new Date().toISOString().slice(0, 7)截取7位即可

卡尔曼滤波:再也不用瑟瑟发抖了

本文来自公众号“AI大道理” —————— 目标跟踪中&#xff0c;在数据关联后往往要进行卡尔曼滤波。 数据关联算法得到了每个目标的观测数据。 卡尔曼滤波使用关联的观测数据来估计目标的状态&#xff0c;并预测目标的未来位置和速度等信息。 目标跟踪过程中&#xff0c;…

0基础学习VR全景平台篇 第62篇:基本功能-如何发布VR视频

戳我先了解“全景视频上传规范” 1、点击【上传】按钮&#xff0c;打开本地文件夹&#xff0c;上传符合要求的全景视频素材&#xff0c;可以选择单个或多个视频同时上传。 2、视频上传成功以后&#xff0c;需要处理一段时间&#xff0c;请耐心等待。 视频处理好以后&#xff0…

编程语言有哪些?介绍常见的编程语言

&#xff08;又是水文章的一天&#xff09;&#xff1a;&#xff09; 在当今数字化时代&#xff0c;编程语言成为了连接人类与计算机的关键工具。无论是网页开发、移动应用程序还是大规模软件开发&#xff0c;选择合适的编程语言对于开发人员来说至关重要。本文将介绍一些常见的…

Cesium-源码打包1.106

在有Cesium源码打包的需求下&#xff0c;可以这样进行&#xff0c; 1.106的源码目录结构如下&#xff1a; 1.在下载的源码目录中运行 npm install 出现node_modules文件夹&#xff0c;然后我们就可以根据需求去修改源码&#xff0c;本文用的版本是1.106&#xff0c; packag…

C语言--动态内存管理(图解)

文章目录 C程序的内存开辟为什么存在动态内存分配动态内存分配函数malloc和freecallocrealloc 常见的动态内存错误对空指针的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一块动态开辟内存的一部分对同一块动态内存多次释放动态开辟内存忘记释…

浅析电力企业一体化云运维管理平台

摘要&#xff1a;电力的发展,关系着我国社会和谐和稳定,在当今科学技术不断向前发展的时代,在电力企业发展中须要结合现今的科学技术,保证电力企业的信息化建设水平能够符合时代的发展趋势。本文主要分析当前电力企业一体化云运维管理的重要性,并就云运维管理中存在的问题进行有…

Spring 6【BeanFactory代码演示、实例化Bean的两种方式】(三)-全面详解(学习总结---从入门到深化)

目录 六、BeanFactory代码演示 七、实例化Bean的两种方式 六、BeanFactory代码演示 上面的案例代码就是我们平时使用Spring Framework的代码。 为了让小伙伴们能感受到BeanFactory&#xff0c;我们还是用实际代码来进行演示一下。毕竟 ApplicationContext在牛&#xff0c;对…

hadoop学习之hdfs学习

HDFS 文件系统,可以说是分布式数据库吧 结构是 目录树 适用场景:一次写入,多次读出.好像不太支持改删 优点: 1.高容错: 因为他会备份,所以一份出问题了,并不影响其他几份 如果副本丢失后,定时恢复.应该是定时检查然后恢复 每次启动,DN向NN汇报备份的存储情况.默认每个6个小时重…

波奇学Linux:git和gdb调试

git用来版本控制&#xff0c;同样是版本控制的软件还有svn等。 git的特定是具有网络功能的版本控制器&#xff0c;开源&#xff0c;client和server是一体的。(去中心化分布式管理) client和server一体意味着远程仓库和本地仓库是平等地位&#xff0c;远程仓库是特殊的仓库而已…

rtmp推流

目录 1、解压代码工程2、进入工程文件夹3、修改Makefile中的交叉编译路径4、编译5、板子上6、window上打开ffplay进行拉流注意:推流之前要先搭建好nginx服务器 1、解压代码工程 sudo unzip ffmpeg_rv1126_network_project_mark_finally.zip 2、进入工程文件夹 cd ffmpeg_rv…

AudioFocus源码分析

使用情景 在音视频app开发中一般会遵循音频焦点的机制&#xff0c;播放时申请音频焦点&#xff0c;丢失焦点后暂停播放&#xff0c;恢复焦点后继续播放等。尤其在车载开发时&#xff0c;涉及到三方应用和自研应用&#xff0c;导致经常出现音频焦点混乱混音等问题。 private f…