C语言小项目-《通讯录》(超详细讲解)

加油加油!!!


前言

本文主要利用C语言结构体和指针的知识实现通讯录。我们先将静态的通讯录实现,再进行改良,用动态内存的知识再将通讯录改造一边,将动态内容的知识也运用一下,最后再用文件操作的方式在改造一下通讯录。

1:静态版本通讯录
2:动态版本通讯录
3:文件版本通讯录


一:静态库通讯录

1:通讯录介绍

在写代码前我们需要考虑两件事:1:代码的框架2:要实现的功能

1.1代码主体框架的介绍

代码的主题框架包含三个部分test.c contact.ccontact.h

test:测试通讯录的实现,也就是主函数,整个代码的操作逻辑
contact.c:通讯录中函数的实现
contact.h:通讯录中函数和结构体等的声明

1.2 要实现的功能

1:我们要实现一个可以存放100个人的信息的通讯录,每个人的信息包括(姓名,性别,电话,年龄,住址
2:通讯录还要有以下功能,
(1)增加联系人
(2)删除指定联系人
(3)修改指定联系人
(4)查找指定联系人
(5)排序
(6)显示通讯录的信息

2:通讯录实现

2.1 通讯录的外部封装

这一步很常规,和之前的三子棋、扫雷一样均采用do-while循环结构内嵌套switch-case语句,同时需要写一个菜单函数提示用户进行选择。

这类小项目通用的外部封装代码如下:

#include<stdio.h>
//菜单函数,根据需求进行修改即可
void menu()
{
	printf("*************************************\n");
	printf("******   1.         2.         ******\n");
	printf("******   3.         4.         ******\n");
	printf("******   5.         6.         ******\n");
	printf("******   0.ecit                ******\n");
	printf("*************************************\n");
}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择操作->");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 5:
			break;
		case 6:
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);
	return 0;
}

对于本通讯录而言,外部封装代码如下在(test.c中完成):

#include<stdio.h>
void menu()
{
	printf("*************************************\n");
	printf("******   1.添加      2.删除    ******\n");
	printf("******   3.修改      4.查找    ******\n");
	printf("******   5.排序      6.展示    ******\n");
	printf("******   0.退出                ******\n");
	printf("*************************************\n");
}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 5:
			break;
		case 6:
			break;
		case 0:
			printf("退出通讯录\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);
	return 0;
}

2.1 创建通讯录

<创建一个结构体>
想要创建一个通讯录,就要创建一个结构体,用来表示一个人的信息(姓名,性别,电话,年龄,住址),而通讯录就想相当于众多人的集合,所以我们要创建一个结构体数组来存放这些人

而对于这个表示一个人信息的结构体,因为也只是一个结构体,我们就放在通讯录声明的文件当中,也就是 contact.h文件。

//创建一个结构体,表示一个人的信息
struct PeoInfo
{
	char name[20];
	char sex[20];
	char tele[12];
	int age;
	char addr[30];
};

<创建一个通讯录>
1:创建好这个记录每个人信息的结构体,我们现在就可以创建通讯录了。通讯录就是这个结构体数组。放到代码就是 struct PeoInfo data[100];这样就创建了可以存放100个人信息的通讯录。
2:但是这样就完事了吗?显然不不行。例如如果你想要增加一个信息,你要增加那个位置,你是不知道的,所以对于通讯录来说,我们不仅要创建这个数组,也要知道这个通讯录有几个信息,这时我们创建一个int sz; 来记录通讯录里现在有几个信息。
比如如果现在由sz个人,那我就把信息放在sz+1的位置上。这两个加起来才是我们完整的通讯录,故需要再创建一个结构体Contact

代码如下,因为这是封装一个通讯录结构体,所以也放在 contact.h文件中。

//创建通讯录
struct Contact
{
	struct PeoInfo data[100];  //存放每个人信息的数组
	int sz;					//要存放的位置 这两个加起来才是我们完整的通讯录,故再创建一个结构体
};

故在主函数中构建一个通讯录

在这里插入图片描述
<创建宏>
为了便于后续添加和代码的一直,我们统一将各个数组的大小进行宏定义

#define MAX			100
#define MAX_NAME	20
#define MAX_SEX		5
#define MAX_TELE	12
#define MAX_ADDR	30

2.2 初始化通讯录

对于创建好了通讯录,对于通讯录的内容,应该先初始化,之后才可以进行修改。这就涉及了结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为要对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参。

方式1:一个一个初始化(有点呆,不推荐)

void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
{
	assert(pc);
	int i = 0;
	//初始化姓名
	for(i=0;i<20;i++)
	{
		(pc->data)->name[i] = '0';
	}

	//初始化性别
	for (i = 0; i < 5; i++)
	{
		(pc->data)->sex[i] = '0';
	}

	//初始化电话
	for (i = 0; i < 12; i++)
	{
		(pc->data)->tele[i] = 0;
	}

	//初始化年龄
	(pc->data)->age = 0;

	//初始化地址
	for (i = 0; i < 30; i++)
	{
		(pc->data)->addr[i] = 0;
	}
	//初始化sz
	pc->sz = 0;
}

方式2:采用memset初始化(推荐)

void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
{
	assert(pc);
	memset(pc->data, 0, sizeof(struct PeoInfo) * MAX); 	//data是一个结构体,初始化可以采用memset
	pc->sz = 0;
}

2.3 增加联系人(功能1)

注意:增加联系人前要判断通讯录有没有满

void AddContact(struct Contact* pc)
{
	assert(pc);  //断言,防止pc为空指针
	if (pc->sz == MAX)
	{
		printf("通讯录满了,无法添加数据\n");
		return;
	}
	printf("请输入名字:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入电话:>");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
	printf("请输入地址:>");
	scanf("%s", pc->data[pc->sz].addr);

	pc->sz++;
	printf("成功添加联系人\n");
}

2.4 展示通讯录(功能6)

我们对通讯录进行操作后想看一看是否对通讯录操作成功,所以需要展示一下通讯录
但是为了美观我们希望在展示前加上表头姓名,性别,电话,年龄,住址),同时为了使之间有空隙我们采用水平制表符\t

//展示通讯录
void ShowContact(const struct Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[i].name,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].age,
			pc->data[i].addr);
	}
}

注意:第一个printf()的年龄那里也是%s,因为我们打印的是字符串"年龄"。

结果如下:
在这里插入图片描述

2.5 删除指定联系人(功能2)

:不管是删除、查找、更改。都需要确定输入的联系人是否存在,故将其封装成一个函数,存在则返回位置,不存在返回-1。

//删除联系人
void DelContact(struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];   //存放输出删除的名字
	printf("请输入要删除的联系人姓名:>");
	scanf("%s", name);

	//不管是删除、查找、更改。都需要找输入的联系人是否存在,故将其封装成一个函数,存在返回位置,不存在返回-1
	//1:查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要删除的联系人不存在\n");
	else
	{
		//删除
		int j = 0;
		for (j = ret; j < (pc->sz - 1); j++)   //100个数据的数组,下标最大为99
		{
			pc->data[j] = pc->data[j + 1];  //将返回值后面的数据整体往前覆盖一位。使用memmove也可以 
		}
		//memmove(&(pc->data[ret]), &(pc->data[ret + 1]), pc->sz - (ret + 1));

		printf("成功删除指定联系人\n");
		pc->sz--;
	}
}

2.6 查找指定联系人(功能4)

//查找联系人
void SearchContact(const struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];
	printf("请输入要查找的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要查找的联系人不存在\n");
	else
	{
		//打印
		printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[ret].name,
			pc->data[ret].sex,
			pc->data[ret].tele,
			pc->data[ret].age,
			pc->data[ret].addr);
	}
}

2.7 修改指定联系人信息(功能3)

修改通讯录,就是先找到要修改的信息位置,然后再像增加通讯录一样重新录入一遍信息。

void ModifyContact(struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];
	printf("请输入要修改的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要修改的联系人不存在\n");
	else
	{
		printf("请输入名字:>");
		scanf("%s", pc->data[ret].name);   //将ret位置的数据改了,重新输入一遍新的数据
		printf("请输入性别:>");
		scanf("%s", pc->data[ret].sex);
		printf("请输入电话:>");
		scanf("%s", pc->data[ret].tele);
		printf("请输入年龄:>");
		scanf("%d", &(pc->data[ret].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
		printf("请输入地址:>");
		scanf("%s", pc->data[ret].addr);

		printf("修改联系人成功\n");
	}
}

注意:上述代码是是对这个人的信息全部都重新输入了一遍,有时候我们只需要修改某一个地方,比如:只修改地址,如果全部输入的话,有些麻烦。于是,我们可以再创建一个二级菜单,提示用户需要修改的信息,只需要选择并且输入修改信息即可。

优化修改功能代码如下:

//可以指定修改信息!!!
//人物信息菜单
static void PeoMenu()
{
	printf("*************************************\n");
	printf("******   1.姓名      2.性别     ******\n");
	printf("******   3.电话      4.年龄     ******\n");
	printf("******   5.地址      0.退出    ******\n");
	printf("*************************************\n");
}

void ModifyContact(struct Contact* pc)
{
	assert(pc);
	int input1;   //需要修改的数据

	char name[MAX_NAME];
	printf("请输入要修改的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要修改的联系人不存在\n");
	else
	{
		do
		{
			PeoMenu();
			printf("请选择:>");
			scanf("%d", &input1);
			switch (input1)
			{
			case 1:
				printf("请输入名字:>");
				scanf("%s", pc->data[ret].name);
				break;
			case 2:
				printf("请输入性别:>");
				scanf("%s", pc->data[ret].sex);
				break;
			case 3:
				printf("请输入电话:>");
				scanf("%s", pc->data[ret].tele);
				break;
			case 4:
				printf("请输入年龄:>");
				scanf("%d", &(pc->data[ret].age));
				break;
			case 5:
				printf("请输入地址:>");
				scanf("%s", pc->data[ret].addr);
				break;
			case 0:
				printf("修改完成,退出修改\n");
				break;
			default:
				printf("输入信息有误,请重新输入\n");
				break;
			}
		} while (input1);
		printf("修改联系人成功\n");
	}
}

2.8 排序通讯录(功能5)

排序我们使用库函数qsort(),关于此函数的详细用法,可参考这篇文章
参考链接:《用冒泡排序模拟qsort库函数》

在这里插入图片描述

//按照年龄排序
static int CmpByAge(const void* e1, const void* e2)
{
	return ((struct PeoInfo*)e1)->age - ((struct PeoInfo*)e2)->age;
}

//排序指定方式,可以排序任意字符串类型,修改宏即可
static int CmpAnyWay(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->SortWay,((struct PeoInfo*)e2)->SortWay);
}


//排序通讯录
void SortContact(struct Contact* pc)
{
	//qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByAge);  	//按照年龄排序

	qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpAnyWay); 	//按照名字排序
	ShowContact(pc);
	printf("按照名字排序成功\n");
}

对于任意方式排序只需要修改contact.h中的宏即可!
在这里插入图片描述

2.9 功能组装

将所有的功能函数对应放到主函数的对应位置

int main()
{
	int input;
	//1:创建通讯录
	struct Contact con;  //通讯录			//struct PeoInfo data[100];  //存放每个人信息的数组
											//int sz = 0;  //记录通讯录里现在有几个信息。比如如果现在由n个人,那我就把信息放在n+1的位置上。这两个加起来才是我们完整的通讯录,故需要再创建一个结构体Contact
	//2:初始化通讯录
	InitContact(&con);
	printf("\t这是一个通讯录的小程序\n");
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			AddContact(&con);
			break;
		case 2:
			DelContact(&con);
			break;
		case 3:
			ModifyContact(&con);
			break;
		case 4:
			SearchContact(&con);
			break;
		case 5:
			SortContact(&con);
			break;
		case 6:
			ShowContact(&con);
			break;
		case 0:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
		//Sleep(1000);
		//system("cls");  //清屏
	} while (input);
};

3: 源码展示

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"


//中文菜单
void menu()
{
	printf("*************************************\n");
	printf("******   1.添加      2.删除    ******\n");
	printf("******   3.修改      4.查找    ******\n");
	printf("******   5.排序      6.展示    ******\n");
	printf("******   0.退出                ******\n");
	printf("*************************************\n");
}



int main()
{
	int input;
	//1:创建通讯录
	struct Contact con;  //通讯录			//struct PeoInfo data[100];  //存放每个人信息的数组
											//int sz = 0;  //记录通讯录里现在有几个信息。比如如果现在由n个人,那我就把信息放在n+1的位置上。这两个加起来才是我们完整的通讯录,故需要再创建一个结构体Contact
	//2:初始化通讯录
	InitContact(&con);
	printf("\t这是一个通讯录的小程序\n");
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			AddContact(&con);
			break;
		case 2:
			DelContact(&con);
			break;
		case 3:
			ModifyContact(&con);
			break;
		case 4:
			SearchContact(&con);
			break;
		case 5:
			SortContact(&con);
			break;
		case 6:
			ShowContact(&con);
			break;
		case 0:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
		//Sleep(1000);
		//system("cls");  //清屏
	} while (input);
};

contact.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"


//方式1:一个一个初始化,对于数组则需要遍历才能初始化,方法很呆!!!!!
//void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
//{
//	int i = 0;
//	//初始化姓名
//	for(i=0;i<20;i++)
//	{
//		(pc->data)->name[i] = '0';
//	}
//
//	//初始化性别
//	for (i = 0; i < 5; i++)
//	{
//		(pc->data)->sex[i] = '0';
//	}
//
//	//初始化电话
//	for (i = 0; i < 12; i++)
//	{
//		(pc->data)->tele[i] = 0;
//	}
//
//	//初始化年龄
//	(pc->data)->age = 0;
//
//	//初始化地址
//	for (i = 0; i < 30; i++)
//	{
//		(pc->data)->addr[i] = 0;
//	}
//
//	//初始化sz
//	pc->sz = 0;
//}


//初始化通讯录
void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
{
	assert(pc);
	memset(pc->data, 0, sizeof(struct PeoInfo) * MAX); 	//data是一个结构体,初始化可以采用memset
	pc->sz = 0;
}

//增加联系人
void AddContact(struct Contact* pc)
{
	assert(pc);  //断言,防止pc为空指针
	if (pc->sz == MAX)
	{
		printf("通讯录满了,无法添加数据\n");
		return;
	}
	printf("请输入名字:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入电话:>");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
	printf("请输入地址:>");
	scanf("%s", pc->data[pc->sz].addr);

	pc->sz++;
	printf("成功添加联系人\n");
}

//展示通讯录
void ShowContact(const struct Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[i].name,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].age,
			pc->data[i].addr);
	}
}

//找名字
static int FindByName(const struct Contact* pc, char name[])  //因为不需要修改,所以采用const修饰
{
	assert(pc);

	//查找,要数组元素遍历
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}


//删除联系人
void DelContact(struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];   //存放输出删除的名字
	printf("请输入要删除的联系人姓名:>");
	scanf("%s", name);

	//不管是删除、查找、更改。都需要找输入的联系人是否存在,故将其封装成一个函数,存在返回位置,不存在返回-1
	//1:查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要删除的联系人不存在\n");
	else
	{
		//删除
		int j = 0;
		for (j = ret; j < (pc->sz - 1); j++)   //100个数据的数组,下标最大为99
		{
			pc->data[j] = pc->data[j + 1];  //将返回值后面的数据整体往前覆盖一位。使用memmove也可以 
		}
		//memmove(&(pc->data[ret]), &(pc->data[ret + 1]), pc->sz - (ret + 1));

		printf("成功删除指定联系人\n");
		pc->sz--;
	}
}

//查找联系人
void SearchContact(const struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];
	printf("请输入要查找的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要查找的联系人不存在\n");
	else
	{
		//打印
		printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[ret].name,
			pc->data[ret].sex,
			pc->data[ret].tele,
			pc->data[ret].age,
			pc->data[ret].addr);
	}
}

//修改联系人
//void ModifyContact(struct Contact* pc)
//{
//	assert(pc);
//
//	char name[MAX_NAME];
//	printf("请输入要修改的联系人姓名:>");
//	scanf("%s", name);
//
//	//查找
//	int ret = FindByName(pc, name);
//	if (ret == -1)
//		printf("要修改的联系人不存在\n");
//	else
//	{
//		printf("请输入名字:>");
//		scanf("%s", pc->data[ret].name);   //将ret位置的数据改了,重新输入一遍新的数据
//		printf("请输入性别:>");
//		scanf("%s", pc->data[ret].sex);
//		printf("请输入电话:>");
//		scanf("%s", pc->data[ret].tele);
//		printf("请输入年龄:>");
//		scanf("%d", &(pc->data[ret].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
//		printf("请输入地址:>");
//		scanf("%s", pc->data[ret].addr);
//
//		printf("修改联系人成功\n");
//	}
//}


/***************************************************************************************************************************/
//可以指定修改信息!!!
//人物信息菜单
static void PeoMenu()
{
	printf("*************************************\n");
	printf("******   1.姓名      2.性别     ******\n");
	printf("******   3.电话      4.年龄     ******\n");
	printf("******   5.地址      0.退出    ******\n");
	printf("*************************************\n");
}

void ModifyContact(struct Contact* pc)
{
	assert(pc);
	int input1;   //需要修改的数据

	char name[MAX_NAME];
	printf("请输入要修改的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要修改的联系人不存在\n");
	else
	{
		do
		{
			PeoMenu();
			printf("请选择:>");
			scanf("%d", &input1);
			switch (input1)
			{
			case 1:
				printf("请输入名字:>");
				scanf("%s", pc->data[ret].name);
				break;
			case 2:
				printf("请输入性别:>");
				scanf("%s", pc->data[ret].sex);
				break;
			case 3:
				printf("请输入电话:>");
				scanf("%s", pc->data[ret].tele);
				break;
			case 4:
				printf("请输入年龄:>");
				scanf("%d", &(pc->data[ret].age));
				break;
			case 5:
				printf("请输入地址:>");
				scanf("%s", pc->data[ret].addr);
				break;
			case 0:
				printf("修改完成,退出修改\n");
				break;
			default:
				printf("输入信息有误,请重新输入\n");
				break;
			}
		} while (input1);
		printf("修改联系人成功\n");
	}
}
/***************************************************************************************************************************/

//按照年龄排序
static int CmpByAge(const void* e1, const void* e2)
{
	return ((struct PeoInfo*)e1)->age - ((struct PeoInfo*)e2)->age;
}

//排序指定方式,可以排序任意字符串类型,修改宏即可
static int CmpAnyWay(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->SortWay,((struct PeoInfo*)e2)->SortWay);
}


//排序通讯录
void SortContact(struct Contact* pc)
{
	//qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByAge);  	//按照年龄排序

	qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpAnyWay); 	//按照名字排序
	ShowContact(pc);
	printf("按照名字排序成功\n");
}




contact.h

#pragma once

#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <assert.h>
#include <stdlib.h>


#define MAX			100
#define MAX_NAME	20
#define MAX_SEX		5
#define MAX_TELE	12
#define MAX_ADDR	30

//修改宏,可以按照任意方式排序
#define SortWay	name



//每个人的信息
struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];   //汉字占两个字节,还有一个\0占一个字节
	char tele[MAX_TELE];
	int age;
	char addr[MAX_ADDR];
};

//创建通讯录
struct Contact
{
	struct PeoInfo data[MAX];  //存放每个人信息的数组
	int sz;					//要存放的位置  ,这两个加起来才是我们完整的通讯录,故再创建一个结构体
};


//初始化通讯录
void InitContact(struct Contact* pc);

//增加联系人
void AddContact(struct Contact* pc);

//展示通讯录
void ShowContact(const struct Contact* pc);  //只是为了展示,不会通过pc来改变数据

//删除联系人
void DelContact(struct Contact* pc);

//查找联系人
void SearchContact(const struct Contact* pc);

//修改联系人
void ModifyContact(struct Contact* pc);


//排序通讯录
void SortContact(struct Contact* pc);

以上就是静态库版本通讯录的全部内容了。

二:动态库通讯录

  • 为什么要设计动态库通讯录?

    静态库不够灵活,比如如果我想加入101个人,但是静态通讯录的大小是固定的,只能存放100个人,这个时候还需要手动改大小。又比如,我只想存放10个人,但是你给通讯录开辟的大小是100人的,就会造成空间上的浪费

  • 设计动态通讯录的思路

    首先创建一个通讯了,这里的创建和之前的有所区别,下面会一一讲解,其次使用malloc申请可以存放3个人的空间,作为通讯录起始的默认空间大小,存放满后,使用realloc增加两个人的空间,如此往复。这样就更加灵活,并且不会浪费空间。

    对于malloc创建空间,这里需要改变一下,因为malloc返回的是void*的指针,所以将 data[]变为指针。同时,需要多增加一个参数,容量参数 capacity,当 szcapacity相同时,增加2个空间。

1 创建通讯录

//动态库版本
//创建通讯录
struct Contact
{
	struct PeoInfo *data;  //存放每个人信息
	int sz;					//已经放进去的人数
	int capacity;			//容量
};

2 初始化通讯录

void InitContact(struct Contact* pc) 
{
	assert(pc);
	pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo));
	if (pc->data == NULL)
	{
		perror("InitContact");
		return;
	}
	else
	{
		//使用
		pc->sz = 0;
		pc->capacity = DEFAULT_SZ;
	}
}

3 增加通讯录


//检测容量够不够,不够了就扩容,返回1,够的话,也返回1,不需要扩容
//扩容失败,返回0
static int check_capacity(struct Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		//扩容
		//这里不能直接赋值给pc->data1,因为可能会增容失败
		struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (INC_SZ + pc->capacity) * sizeof(struct PeoInfo));  
		if (ptr != NULL)
		{
			pc->data = ptr;
			pc->capacity += INC_SZ;
			printf("扩容成功\n");
			return 1;
		}
		else
		{
			perror("AddContact");
			return 0;
		}
	}
	else
		return 1;  //不需要扩容
}

//动态版本
//增加联系人
void AddContact(struct Contact* pc)
{
	assert(pc);  //断言,防止pc为空指针
	int ret = check_capacity(pc);
	if (ret == 0)
	{
		return;  //扩容失败,直接返回,后面的就不执行了
	}
	else
	{
		printf("请输入名字:>");
		scanf("%s", pc->data[pc->sz].name);
		printf("请输入性别:>");
		scanf("%s", pc->data[pc->sz].sex);
		printf("请输入电话:>");
		scanf("%s", pc->data[pc->sz].tele);
		printf("请输入年龄:>");
		scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
		printf("请输入地址:>");
		scanf("%s", pc->data[pc->sz].addr);

		pc->sz++;
		printf("成功添加联系人\n");
	}
}

4 销毁通讯录

既然是动态内存,如果用完了,我们就要将内存还给操作系统。我们知道callocfree是同时出现的,申请了空间,就肯定要销毁空间,所以在退出函数就可以添加一个函数,销毁函数。

//销毁通讯录-EXIT
void DestoryContact(struct Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
}

5: 源码展示

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"

void menu()
{
	printf("**************************************\n");
	printf("*****   1. add      2. del       *****\n");
	printf("*****   3. modify   4. search    *****\n");
	printf("*****   5. sort     6. show      *****\n");
	printf("*****   0. exit                  *****\n");
	printf("**************************************\n");
}

enum Option
{
	EXIT,   //默认值为0,一次递增
	ADD,
	DEL,
	MODIFY,
	SEARCH,
	SORT,
	SHOW
};


//int main()
//{
//	int input;
//	//1:创建通讯录
//	struct Contact con;  //通讯录			//struct PeoInfo data[100];  //存放每个人信息的数组
//											//int sz = 0;  //记录通讯录里现在有几个信息。比如如果现在由n个人,那我就把信息放在n+1的位置上。这两个加起来才是我们完整的通讯录,故需要再创建一个结构体Contact
//	//2:初始化通讯录
//	InitContact(&con);
//	printf("\t这是一个通讯录的小程序\n");
//	do
//	{
//		menu();
//		printf("请选择:>");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			AddContact(&con);
//			break;
//		case 2:
//			DelContact(&con);
//			break;
//		case 3:
//			ModifyContact(&con);
//			break;
//		case 4:
//			SearchContact(&con);
//			break;
//		case 5:
//			SortContact(&con);
//			break;
//		case 6:
//			ShowContact(&con);
//			break;
//		case 0:
//			printf("退出通讯录\n");
//			break;
//		default:
//			printf("选择错误,请重新选择\n");
//			break;
//		}
//		//Sleep(1000);
//		//system("cls");  //清屏
//	} while (input);
//};

//使用枚举将数字0123456替换后
int main()
{
	int input;
	//1:创建通讯录
	struct Contact con;  //通讯录			//struct PeoInfo data[100];  //存放每个人信息的数组
											//int sz = 0;  //记录通讯录里现在有几个信息。比如如果现在由n个人,那我就把信息放在n+1的位置上。这两个加起来才是我们完整的通讯录,故需要再创建一个结构体Contact
	//2:初始化通讯录
	InitContact(&con);
	printf("\t这是一个通讯录的小程序\n");
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case EXIT:
			DestoryContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
		//Sleep(1000);
		//system("cls");  //清屏
	} while (input);
};

contact.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"


//方式1:一个一个初始化,对于数组则需要遍历才能初始化,方法很呆!!!!!
//void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
//{
//	int i = 0;
//	//初始化姓名
//	for(i=0;i<20;i++)
//	{
//		(pc->data)->name[i] = '0';
//	}
//
//	//初始化性别
//	for (i = 0; i < 5; i++)
//	{
//		(pc->data)->sex[i] = '0';
//	}
//
//	//初始化电话
//	for (i = 0; i < 12; i++)
//	{
//		(pc->data)->tele[i] = 0;
//	}
//
//	//初始化年龄
//	(pc->data)->age = 0;
//
//	//初始化地址
//	for (i = 0; i < 30; i++)
//	{
//		(pc->data)->addr[i] = 0;
//	}
//
//	//初始化sz
//	pc->sz = 0;
//}

//静态版本
//方式2:初始化通讯录,使用memset
//void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
//{
//	assert(pc);
//	memset(pc->data, 0, sizeof(struct PeoInfo) * MAX); 	//data是一个结构体,初始化可以采用memset
//	pc->sz = 0;
//}

//动态版本
//初始化通讯录
void InitContact(struct Contact* pc) 
{
	assert(pc);
	pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo));
	if (pc->data == NULL)
	{
		perror("InitContact");
		return;
	}
	else
	{
		//使用
		pc->sz = 0;
		pc->capacity = DEFAULT_SZ;
	}
}


//销毁通讯录-EXIT
void DestoryContact(struct Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
}






//静态版本
//增加联系人
//void AddContact(struct Contact* pc)
//{
//	assert(pc);  //断言,防止pc为空指针
//	if (pc->sz == MAX)
//	{
//		printf("通讯录满了,无法添加数据\n");
//		return;
//	}
//	printf("请输入名字:>");
//	scanf("%s", pc->data[pc->sz].name);
//	printf("请输入性别:>");
//	scanf("%s", pc->data[pc->sz].sex);
//	printf("请输入电话:>");
//	scanf("%s", pc->data[pc->sz].tele);
//	printf("请输入年龄:>");
//	scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
//	printf("请输入地址:>");
//	scanf("%s", pc->data[pc->sz].addr);
//
//	pc->sz++;
//	printf("成功添加联系人\n");
//}



//检测容量够不够,不够了就扩容,返回1,够的话,也返回1,不需要扩容
//扩容失败,返回0
static int check_capacity(struct Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		//扩容
		//这里不能直接赋值给pc->data1,因为可能会增容失败
		struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (INC_SZ + pc->capacity) * sizeof(struct PeoInfo));  
		if (ptr != NULL)
		{
			pc->data = ptr;
			pc->capacity += INC_SZ;
			printf("扩容成功\n");
			return 1;
		}
		else
		{
			perror("AddContact");
			return 0;
		}
	}
	else
		return 1;  //不需要扩容
}



//动态版本
//增加联系人
void AddContact(struct Contact* pc)
{
	assert(pc);  //断言,防止pc为空指针
	int ret = check_capacity(pc);
	if (ret == 0)
	{
		return;  //扩容失败,直接返回,后面的就不执行了
	}
	else
	{
		printf("请输入名字:>");
		scanf("%s", pc->data[pc->sz].name);
		printf("请输入性别:>");
		scanf("%s", pc->data[pc->sz].sex);
		printf("请输入电话:>");
		scanf("%s", pc->data[pc->sz].tele);
		printf("请输入年龄:>");
		scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
		printf("请输入地址:>");
		scanf("%s", pc->data[pc->sz].addr);

		pc->sz++;
		printf("成功添加联系人\n");
	}
}

//展示通讯录
void ShowContact(const struct Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[i].name,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].age,
			pc->data[i].addr);
	}
}

//找名字
static int FindByName(const struct Contact* pc, char name[])  //因为不需要修改,所以采用const修饰
{
	assert(pc);

	//查找,要数组元素遍历
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}


//删除联系人
void DelContact(struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];   //存放输出删除的名字
	printf("请输入要删除的联系人姓名:>");
	scanf("%s", name);

	//不管是删除、查找、更改。都需要找输入的联系人是否存在,故将其封装成一个函数,存在返回位置,不存在返回-1
	//1:查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要删除的联系人不存在\n");
	else
	{
		//删除
		int j = 0;
		for (j = ret; j < (pc->sz - 1); j++)   //100个数据的数组,下标最大为99
		{
			pc->data[j] = pc->data[j + 1];  //将返回值后面的数据整体往前覆盖一位。使用memmove也可以 
		}
		//memmove(&(pc->data[ret]), &(pc->data[ret + 1]), pc->sz - (ret + 1));

		printf("成功删除指定联系人\n");
		pc->sz--;
	}
}

//查找联系人
void SearchContact(const struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];
	printf("请输入要查找的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要查找的联系人不存在\n");
	else
	{
		//打印
		printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[ret].name,
			pc->data[ret].sex,
			pc->data[ret].tele,
			pc->data[ret].age,
			pc->data[ret].addr);
	}
}

//修改联系人
//void ModifyContact(struct Contact* pc)
//{
//	assert(pc);
//
//	char name[MAX_NAME];
//	printf("请输入要修改的联系人姓名:>");
//	scanf("%s", name);
//
//	//查找
//	int ret = FindByName(pc, name);
//	if (ret == -1)
//		printf("要修改的联系人不存在\n");
//	else
//	{
//		printf("请输入名字:>");
//		scanf("%s", pc->data[ret].name);   //将ret位置的数据改了,重新输入一遍新的数据
//		printf("请输入性别:>");
//		scanf("%s", pc->data[ret].sex);
//		printf("请输入电话:>");
//		scanf("%s", pc->data[ret].tele);
//		printf("请输入年龄:>");
//		scanf("%d", &(pc->data[ret].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
//		printf("请输入地址:>");
//		scanf("%s", pc->data[ret].addr);
//
//		printf("修改联系人成功\n");
//	}
//}


/***************************************************************************************************************************/
//可以指定修改信息!!!
//人物信息菜单
static void PeoMenu()
{
	printf("*************************************\n");
	printf("******   1.姓名      2.性别     ******\n");
	printf("******   3.电话      4.年龄     ******\n");
	printf("******   5.地址      0.退出    ******\n");
	printf("*************************************\n");
}

void ModifyContact(struct Contact* pc)
{
	assert(pc);
	int input1;   //需要修改的数据

	char name[MAX_NAME];
	printf("请输入要修改的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要修改的联系人不存在\n");
	else
	{
		do
		{
			PeoMenu();
			printf("请选择:>");
			scanf("%d", &input1);
			switch (input1)
			{
			case 1:
				printf("请输入名字:>");
				scanf("%s", pc->data[ret].name);
				break;
			case 2:
				printf("请输入性别:>");
				scanf("%s", pc->data[ret].sex);
				break;
			case 3:
				printf("请输入电话:>");
				scanf("%s", pc->data[ret].tele);
				break;
			case 4:
				printf("请输入年龄:>");
				scanf("%d", &(pc->data[ret].age));
				break;
			case 5:
				printf("请输入地址:>");
				scanf("%s", pc->data[ret].addr);
				break;
			case 0:
				printf("修改完成,退出修改\n");
				break;
			default:
				printf("输入信息有误,请重新输入\n");
				break;
			}
		} while (input1);
		printf("修改联系人成功\n");
	}
}
/***************************************************************************************************************************/

//按照年龄排序
static int CmpByAge(const void* e1, const void* e2)
{
	return ((struct PeoInfo*)e1)->age - ((struct PeoInfo*)e2)->age;
}

//排序指定方式,可以排序任意字符串类型,修改宏即可
static int CmpAnyWay(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->SortWay,((struct PeoInfo*)e2)->SortWay);
}


//排序通讯录
void SortContact(struct Contact* pc)
{
	//qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByAge);  	//按照年龄排序

	qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpAnyWay); 	//按照名字排序
	ShowContact(pc);
	printf("按照名字排序成功\n");
}




contact.h

#pragma once

#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <assert.h>
#include <stdlib.h>


#define MAX			100
#define MAX_NAME	20
#define MAX_SEX		5
#define MAX_TELE	12
#define MAX_ADDR	30

//修改宏,可以按照任意方式排序
#define SortWay	name


#define DEFAULT_SZ 3   //初始化后,默认通讯录的的大小
#define INC_SZ 3   //通讯录每次增加的大小


//每个人的信息
struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];   //汉字占两个字节,还有一个\0占一个字节
	char tele[MAX_TELE];
	int age;
	char addr[MAX_ADDR];
};




静态库版本
创建通讯录
//struct Contact
//{
//	struct PeoInfo data[MAX];  //存放每个人信息的数组
//	int sz;					//要存放的位置  ,这两个加起来才是我们完整的通讯录,故再创建一个结构体
//};

//动态库版本
//创建通讯录
struct Contact
{
	struct PeoInfo *data;  //存放每个人信息
	int sz;					//已经放进去的人数
	int capacity;			//容量
};


//初始化通讯录
void InitContact(struct Contact* pc);

//增加联系人
void AddContact(struct Contact* pc);

//展示通讯录
void ShowContact(const struct Contact* pc);  //只是为了展示,不会通过pc来改变数据

//删除联系人
void DelContact(struct Contact* pc);

//查找联系人
void SearchContact(const struct Contact* pc);

//修改联系人
void ModifyContact(struct Contact* pc);


//排序通讯录
void SortContact(struct Contact* pc);

//销毁通讯录
void DestoryContact(struct Contact* pc);

以上就是动态通讯录的全部内容了

三:文件通讯录

  • 文件通讯录需求
    通讯录退出后,之前输入的信息可以保存下来,下一次重新运行通讯库还可以看到上一次的信息
  • 分析需求
    推出的时候将通讯录保存下来,当下一次运行的时候,再从文件中加载信息

1:保存通信录

主要使用fopenfwrite,fclose函数

//保存通讯录
void SaveContact(struct Contact* pc)
{
	FILE* pfwrite = fopen("test.txt", "wb");
	if (pfwrite == NULL)
	{
		perror("SaveContact:fopen");
		return;
	}

	//写数据
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		//二进制写
		fwrite(pc->data+i, sizeof(struct PeoInfo), 1, pfwrite);   //每次写入一个结构体数据
	}

	//关闭文件
	fclose(pfwrite);
	pfwrite = NULL;

}

2:加载通信录

要是的下次打开前通讯录就已经要保存的文件里读取信息了,需要将函数设置在初始化通讯录里

//加载通讯录
void LoadContact(struct Contact* pc)
{
	FILE* pfread = fopen("test.txt", "rb");
	if (pfread == NULL)
	{
		perror("LoadContact:fopen");
		return;
	}

	//读数据
	//fread的返回值是size_t,返回成功读取的元素总数,失败则可能是发生了读取错误,不进入循环
	//二进制读
	struct PeoInfo temp = { 0 };
	while (fread(&temp, sizeof(struct PeoInfo), 1, pfread))    //使用fread需要用一个结构体来接收
	{
		//考虑增容
		check_capacity(pc);
		pc->data[pc->sz] = temp;   //将每次读取的数据放到指定位置上,方便查找和维护
		pc->sz++;
	}

	//关闭文件
	fclose(pfread);
	pfread = NULL;
}

3:程序源码

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"

//中文菜单
void menu()
{
	printf("*************************************************\n");
	printf("******   1.添加联系人      2.删除联系人    ******\n");
	printf("******   3.修改联系人      4.查找联系人    ******\n");
	printf("******   5.排序联系人      6.展示联系人    ******\n");
	printf("******   0.退出                            ******\n");
	printf("*************************************************\n");
}

enum Option
{
	EXIT,   //默认值为0,一次递增
	ADD,
	DEL,
	MODIFY,
	SEARCH,
	SORT,
	SHOW
};


//int main()
//{
//	int input;
//	//1:创建通讯录
//	struct Contact con;  //通讯录			//struct PeoInfo data[100];  //存放每个人信息的数组
//											//int sz = 0;  //记录通讯录里现在有几个信息。比如如果现在由n个人,那我就把信息放在n+1的位置上。这两个加起来才是我们完整的通讯录,故需要再创建一个结构体Contact
//	//2:初始化通讯录
//	InitContact(&con);
//	printf("\t这是一个通讯录的小程序\n");
//	do
//	{
//		menu();
//		printf("请选择:>");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			AddContact(&con);
//			break;
//		case 2:
//			DelContact(&con);
//			break;
//		case 3:
//			ModifyContact(&con);
//			break;
//		case 4:
//			SearchContact(&con);
//			break;
//		case 5:
//			SortContact(&con);
//			break;
//		case 6:
//			ShowContact(&con);
//			break;
//		case 0:
//			printf("退出通讯录\n");
//			break;
//		default:
//			printf("选择错误,请重新选择\n");
//			break;
//		}
//		//Sleep(1000);
//		//system("cls");  //清屏
//	} while (input);
//};

//使用枚举将数字0123456替换后
int main()
{
	int input;
	//1:创建通讯录
	struct Contact con;  //通讯录			//struct PeoInfo data[100];  //存放每个人信息的数组
											//int sz = 0;  //记录通讯录里现在有几个信息。比如如果现在由n个人,那我就把信息放在n+1的位置上。这两个加起来才是我们完整的通讯录,故需要再创建一个结构体Contact
	//2:初始化通讯录
	InitContact(&con);
	printf("\t这是一个通讯录的小程序\n");
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case EXIT:
			SaveContact(&con);
			DestoryContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
		//Sleep(1000);
		//system("cls");  //清屏
	} while (input);
};

contact.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"


//方式1:一个一个初始化,对于数组则需要遍历才能初始化,方法很呆!!!!!
//void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
//{
//	int i = 0;
//	//初始化姓名
//	for(i=0;i<20;i++)
//	{
//		(pc->data)->name[i] = '0';
//	}
//
//	//初始化性别
//	for (i = 0; i < 5; i++)
//	{
//		(pc->data)->sex[i] = '0';
//	}
//
//	//初始化电话
//	for (i = 0; i < 12; i++)
//	{
//		(pc->data)->tele[i] = 0;
//	}
//
//	//初始化年龄
//	(pc->data)->age = 0;
//
//	//初始化地址
//	for (i = 0; i < 30; i++)
//	{
//		(pc->data)->addr[i] = 0;
//	}
//
//	//初始化sz
//	pc->sz = 0;
//}

//静态版本
//方式2:初始化通讯录,使用memset
//void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
//{
//	assert(pc);
//	memset(pc->data, 0, sizeof(struct PeoInfo) * MAX); 	//data是一个结构体,初始化可以采用memset
//	pc->sz = 0;
//}

//加载通讯录
void LoadContact(struct Contact* pc)
{
	FILE* pfread = fopen("test.txt", "rb");
	if (pfread == NULL)
	{
		perror("LoadContact:fopen");
		return;
	}

	//读数据
	//fread的返回值是size_t,返回成功读取的元素总数,失败则可能是发生了读取错误,不进入循环
	//二进制读
	struct PeoInfo temp = { 0 };
	while (fread(&temp, sizeof(struct PeoInfo), 1, pfread))    //使用fread需要用一个结构体来接收
	{
		//考虑增容
		check_capacity(pc);
		pc->data[pc->sz] = temp;   //将每次读取的数据放到指定位置上,方便查找和维护
		pc->sz++;
	}

	//关闭文件
	fclose(pfread);
	pfread = NULL;
}






//动态版本
//初始化通讯录
void InitContact(struct Contact* pc) 
{
	assert(pc);
	pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo));
	if (pc->data == NULL)
	{
		perror("InitContact");
		return;
	}
	else
	{
		//使用
		pc->sz = 0;
		pc->capacity = DEFAULT_SZ;
	}
	LoadContact(pc);
}


//销毁通讯录-EXIT
void DestoryContact(struct Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
}






//静态版本
//增加联系人
//void AddContact(struct Contact* pc)
//{
//	assert(pc);  //断言,防止pc为空指针
//	if (pc->sz == MAX)
//	{
//		printf("通讯录满了,无法添加数据\n");
//		return;
//	}
//	printf("请输入名字:>");
//	scanf("%s", pc->data[pc->sz].name);
//	printf("请输入性别:>");
//	scanf("%s", pc->data[pc->sz].sex);
//	printf("请输入电话:>");
//	scanf("%s", pc->data[pc->sz].tele);
//	printf("请输入年龄:>");
//	scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
//	printf("请输入地址:>");
//	scanf("%s", pc->data[pc->sz].addr);
//
//	pc->sz++;
//	printf("成功添加联系人\n");
//}



//检测容量够不够,不够了就扩容,返回1,够的话,也返回1,不需要扩容
//扩容失败,返回0
int check_capacity(struct Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		//扩容
		//这里不能直接赋值给pc->data1,因为可能会增容失败
		struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (INC_SZ + pc->capacity) * sizeof(struct PeoInfo));  
		if (ptr != NULL)
		{
			pc->data = ptr;
			pc->capacity += INC_SZ;
			//printf("扩容成功\n");
			return 1;
		}
		else
		{
			perror("AddContact");
			return 0;
		}
	}
	else
		return 1;  //不需要扩容
}



//动态版本
//增加联系人
void AddContact(struct Contact* pc)
{
	assert(pc);  //断言,防止pc为空指针
	int ret = check_capacity(pc);
	if (ret == 0)
	{
		return;  //扩容失败,直接返回,后面的就不执行了
	}
	else
	{
		printf("请输入名字:>");
		scanf("%s", pc->data[pc->sz].name);
		printf("请输入性别:>");
		scanf("%s", pc->data[pc->sz].sex);
		printf("请输入电话:>");
		scanf("%s", pc->data[pc->sz].tele);
		printf("请输入年龄:>");
		scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
		printf("请输入地址:>");
		scanf("%s", pc->data[pc->sz].addr);

		pc->sz++;
		printf("成功添加联系人\n");
	}
}

//展示通讯录
void ShowContact(const struct Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[i].name,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].age,
			pc->data[i].addr);
	}
}

//找名字
static int FindByName(const struct Contact* pc, char name[])  //因为不需要修改,所以采用const修饰
{
	assert(pc);

	//查找,要数组元素遍历
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}


//删除联系人
void DelContact(struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];   //存放输出删除的名字
	printf("请输入要删除的联系人姓名:>");
	scanf("%s", name);

	//不管是删除、查找、更改。都需要找输入的联系人是否存在,故将其封装成一个函数,存在返回位置,不存在返回-1
	//1:查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要删除的联系人不存在\n");
	else
	{
		//删除
		int j = 0;
		for (j = ret; j < (pc->sz - 1); j++)   //100个数据的数组,下标最大为99
		{
			pc->data[j] = pc->data[j + 1];  //将返回值后面的数据整体往前覆盖一位。使用memmove也可以 
		}
		//memmove(&(pc->data[ret]), &(pc->data[ret + 1]), pc->sz - (ret + 1));

		printf("成功删除指定联系人\n");
		pc->sz--;
	}
}

//查找联系人
void SearchContact(const struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];
	printf("请输入要查找的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要查找的联系人不存在\n");
	else
	{
		//打印
		printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[ret].name,
			pc->data[ret].sex,
			pc->data[ret].tele,
			pc->data[ret].age,
			pc->data[ret].addr);
	}
}

//修改联系人
//void ModifyContact(struct Contact* pc)
//{
//	assert(pc);
//
//	char name[MAX_NAME];
//	printf("请输入要修改的联系人姓名:>");
//	scanf("%s", name);
//
//	//查找
//	int ret = FindByName(pc, name);
//	if (ret == -1)
//		printf("要修改的联系人不存在\n");
//	else
//	{
//		printf("请输入名字:>");
//		scanf("%s", pc->data[ret].name);   //将ret位置的数据改了,重新输入一遍新的数据
//		printf("请输入性别:>");
//		scanf("%s", pc->data[ret].sex);
//		printf("请输入电话:>");
//		scanf("%s", pc->data[ret].tele);
//		printf("请输入年龄:>");
//		scanf("%d", &(pc->data[ret].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
//		printf("请输入地址:>");
//		scanf("%s", pc->data[ret].addr);
//
//		printf("修改联系人成功\n");
//	}
//}


/***************************************************************************************************************************/
//可以指定修改信息!!!
//人物信息菜单
static void PeoMenu()
{
	printf("*************************************\n");
	printf("******   1.姓名      2.性别     ******\n");
	printf("******   3.电话      4.年龄     ******\n");
	printf("******   5.地址      0.退出    ******\n");
	printf("*************************************\n");
}

void ModifyContact(struct Contact* pc)
{
	assert(pc);
	int input1;   //需要修改的数据

	char name[MAX_NAME];
	printf("请输入要修改的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要修改的联系人不存在\n");
	else
	{
		do
		{
			PeoMenu();
			printf("请选择:>");
			scanf("%d", &input1);
			switch (input1)
			{
			case 1:
				printf("请输入名字:>");
				scanf("%s", pc->data[ret].name);
				break;
			case 2:
				printf("请输入性别:>");
				scanf("%s", pc->data[ret].sex);
				break;
			case 3:
				printf("请输入电话:>");
				scanf("%s", pc->data[ret].tele);
				break;
			case 4:
				printf("请输入年龄:>");
				scanf("%d", &(pc->data[ret].age));
				break;
			case 5:
				printf("请输入地址:>");
				scanf("%s", pc->data[ret].addr);
				break;
			case 0:
				printf("修改完成,退出修改\n");
				break;
			default:
				printf("输入信息有误,请重新输入\n");
				break;
			}
		} while (input1);
		printf("修改联系人成功\n");
	}
}
/***************************************************************************************************************************/

//按照年龄排序
static int CmpByAge(const void* e1, const void* e2)
{
	return ((struct PeoInfo*)e1)->age - ((struct PeoInfo*)e2)->age;
}

//排序指定方式,可以排序任意字符串类型,修改宏即可
static int CmpAnyWay(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->SortWay,((struct PeoInfo*)e2)->SortWay);
}


//排序通讯录
void SortContact(struct Contact* pc)
{
	//qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByAge);  	//按照年龄排序

	qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpAnyWay); 	//按照名字排序
	ShowContact(pc);
	printf("按照名字排序成功\n");
}


//保存通讯录
void SaveContact(struct Contact* pc)
{
	FILE* pfwrite = fopen("test.txt", "wb");
	if (pfwrite == NULL)
	{
		perror("SaveContact:fopen");
		return;
	}

	//写数据
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		//二进制写
		fwrite(pc->data+i, sizeof(struct PeoInfo), 1, pfwrite);   //每次写入一个结构体数据
	}

	//关闭文件
	fclose(pfwrite);
	pfwrite = NULL;

}



contact.h

#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"


//方式1:一个一个初始化,对于数组则需要遍历才能初始化,方法很呆!!!!!
//void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
//{
//	int i = 0;
//	//初始化姓名
//	for(i=0;i<20;i++)
//	{
//		(pc->data)->name[i] = '0';
//	}
//
//	//初始化性别
//	for (i = 0; i < 5; i++)
//	{
//		(pc->data)->sex[i] = '0';
//	}
//
//	//初始化电话
//	for (i = 0; i < 12; i++)
//	{
//		(pc->data)->tele[i] = 0;
//	}
//
//	//初始化年龄
//	(pc->data)->age = 0;
//
//	//初始化地址
//	for (i = 0; i < 30; i++)
//	{
//		(pc->data)->addr[i] = 0;
//	}
//
//	//初始化sz
//	pc->sz = 0;
//}

//静态版本
//方式2:初始化通讯录,使用memset
//void InitContact(struct Contact* pc) //结构体传参,这里不能使用结构体来接收,应该用结构体指针,因为对其内容进行修改,形参只是实参的一份临时拷贝,改变形参无法修改实参
//{
//	assert(pc);
//	memset(pc->data, 0, sizeof(struct PeoInfo) * MAX); 	//data是一个结构体,初始化可以采用memset
//	pc->sz = 0;
//}

//加载通讯录
void LoadContact(struct Contact* pc)
{
	FILE* pfread = fopen("test.txt", "rb");
	if (pfread == NULL)
	{
		perror("LoadContact:fopen");
		return;
	}

	//读数据
	//fread的返回值是size_t,返回成功读取的元素总数,失败则可能是发生了读取错误,不进入循环
	//二进制读
	struct PeoInfo temp = { 0 };
	while (fread(&temp, sizeof(struct PeoInfo), 1, pfread))    //使用fread需要用一个结构体来接收
	{
		//考虑增容
		check_capacity(pc);
		pc->data[pc->sz] = temp;   //将每次读取的数据放到指定位置上,方便查找和维护
		pc->sz++;
	}

	//关闭文件
	fclose(pfread);
	pfread = NULL;
}






//动态版本
//初始化通讯录
void InitContact(struct Contact* pc)
{
	assert(pc);
	pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo));
	if (pc->data == NULL)
	{
		perror("InitContact");
		return;
	}
	else
	{
		//使用
		pc->sz = 0;
		pc->capacity = DEFAULT_SZ;
	}
	LoadContact(pc);
}


//销毁通讯录-EXIT
void DestoryContact(struct Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->capacity = 0;
}






//静态版本
//增加联系人
//void AddContact(struct Contact* pc)
//{
//	assert(pc);  //断言,防止pc为空指针
//	if (pc->sz == MAX)
//	{
//		printf("通讯录满了,无法添加数据\n");
//		return;
//	}
//	printf("请输入名字:>");
//	scanf("%s", pc->data[pc->sz].name);
//	printf("请输入性别:>");
//	scanf("%s", pc->data[pc->sz].sex);
//	printf("请输入电话:>");
//	scanf("%s", pc->data[pc->sz].tele);
//	printf("请输入年龄:>");
//	scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
//	printf("请输入地址:>");
//	scanf("%s", pc->data[pc->sz].addr);
//
//	pc->sz++;
//	printf("成功添加联系人\n");
//}



//检测容量够不够,不够了就扩容,返回1,够的话,也返回1,不需要扩容
//扩容失败,返回0
int check_capacity(struct Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		//扩容
		//这里不能直接赋值给pc->data1,因为可能会增容失败
		struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (INC_SZ + pc->capacity) * sizeof(struct PeoInfo));
		if (ptr != NULL)
		{
			pc->data = ptr;
			pc->capacity += INC_SZ;
			//printf("扩容成功\n");
			return 1;
		}
		else
		{
			perror("AddContact");
			return 0;
		}
	}
	else
		return 1;  //不需要扩容
}



//动态版本
//增加联系人
void AddContact(struct Contact* pc)
{
	assert(pc);  //断言,防止pc为空指针
	int ret = check_capacity(pc);
	if (ret == 0)
	{
		return;  //扩容失败,直接返回,后面的就不执行了
	}
	else
	{
		printf("请输入名字:>");
		scanf("%s", pc->data[pc->sz].name);
		printf("请输入性别:>");
		scanf("%s", pc->data[pc->sz].sex);
		printf("请输入电话:>");
		scanf("%s", pc->data[pc->sz].tele);
		printf("请输入年龄:>");
		scanf("%d", &(pc->data[pc->sz].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
		printf("请输入地址:>");
		scanf("%s", pc->data[pc->sz].addr);

		pc->sz++;
		printf("成功添加联系人\n");
	}
}

//展示通讯录
void ShowContact(const struct Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[i].name,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].age,
			pc->data[i].addr);
	}
}

//找名字
static int FindByName(const struct Contact* pc, char name[])  //因为不需要修改,所以采用const修饰
{
	assert(pc);

	//查找,要数组元素遍历
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}


//删除联系人
void DelContact(struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];   //存放输出删除的名字
	printf("请输入要删除的联系人姓名:>");
	scanf("%s", name);

	//不管是删除、查找、更改。都需要找输入的联系人是否存在,故将其封装成一个函数,存在返回位置,不存在返回-1
	//1:查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要删除的联系人不存在\n");
	else
	{
		//删除
		int j = 0;
		for (j = ret; j < (pc->sz - 1); j++)   //100个数据的数组,下标最大为99
		{
			pc->data[j] = pc->data[j + 1];  //将返回值后面的数据整体往前覆盖一位。使用memmove也可以 
		}
		//memmove(&(pc->data[ret]), &(pc->data[ret + 1]), pc->sz - (ret + 1));

		printf("成功删除指定联系人\n");
		pc->sz--;
	}
}

//查找联系人
void SearchContact(const struct Contact* pc)
{
	assert(pc);

	char name[MAX_NAME];
	printf("请输入要查找的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要查找的联系人不存在\n");
	else
	{
		//打印
		printf("%-20s\t%-5s\t%-12s\t%-2s\t%-30s\n", "姓名", "性别", "电话", "年龄", "地址");  //\t相当于tab,%-20s左对齐,多的后面补零
		printf("%-20s\t%-5s\t%-12s\t%-2d\t%-30s\n", pc->data[ret].name,
			pc->data[ret].sex,
			pc->data[ret].tele,
			pc->data[ret].age,
			pc->data[ret].addr);
	}
}

//修改联系人
//void ModifyContact(struct Contact* pc)
//{
//	assert(pc);
//
//	char name[MAX_NAME];
//	printf("请输入要修改的联系人姓名:>");
//	scanf("%s", name);
//
//	//查找
//	int ret = FindByName(pc, name);
//	if (ret == -1)
//		printf("要修改的联系人不存在\n");
//	else
//	{
//		printf("请输入名字:>");
//		scanf("%s", pc->data[ret].name);   //将ret位置的数据改了,重新输入一遍新的数据
//		printf("请输入性别:>");
//		scanf("%s", pc->data[ret].sex);
//		printf("请输入电话:>");
//		scanf("%s", pc->data[ret].tele);
//		printf("请输入年龄:>");
//		scanf("%d", &(pc->data[ret].age));  //由于其他的都是数组。直接写数组名就可以了,但是age是一个数据,需要取地址
//		printf("请输入地址:>");
//		scanf("%s", pc->data[ret].addr);
//
//		printf("修改联系人成功\n");
//	}
//}


/***************************************************************************************************************************/
//可以指定修改信息!!!
//人物信息菜单
static void PeoMenu()
{
	printf("*************************************\n");
	printf("******   1.姓名      2.性别     ******\n");
	printf("******   3.电话      4.年龄     ******\n");
	printf("******   5.地址      0.退出    ******\n");
	printf("*************************************\n");
}

void ModifyContact(struct Contact* pc)
{
	assert(pc);
	int input1;   //需要修改的数据

	char name[MAX_NAME];
	printf("请输入要修改的联系人姓名:>");
	scanf("%s", name);

	//查找
	int ret = FindByName(pc, name);
	if (ret == -1)
		printf("要修改的联系人不存在\n");
	else
	{
		do
		{
			PeoMenu();
			printf("请选择:>");
			scanf("%d", &input1);
			switch (input1)
			{
			case 1:
				printf("请输入名字:>");
				scanf("%s", pc->data[ret].name);
				break;
			case 2:
				printf("请输入性别:>");
				scanf("%s", pc->data[ret].sex);
				break;
			case 3:
				printf("请输入电话:>");
				scanf("%s", pc->data[ret].tele);
				break;
			case 4:
				printf("请输入年龄:>");
				scanf("%d", &(pc->data[ret].age));
				break;
			case 5:
				printf("请输入地址:>");
				scanf("%s", pc->data[ret].addr);
				break;
			case 0:
				printf("修改完成,退出修改\n");
				break;
			default:
				printf("输入信息有误,请重新输入\n");
				break;
			}
		} while (input1);
		printf("修改联系人成功\n");
	}
}
/***************************************************************************************************************************/

//按照年龄排序
static int CmpByAge(const void* e1, const void* e2)
{
	return ((struct PeoInfo*)e1)->age - ((struct PeoInfo*)e2)->age;
}

//排序指定方式,可以排序任意字符串类型,修改宏即可
static int CmpAnyWay(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->SortWay, ((struct PeoInfo*)e2)->SortWay);
}


//排序通讯录
void SortContact(struct Contact* pc)
{
	//qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByAge);  	//按照年龄排序

	qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpAnyWay); 	//按照名字排序
	ShowContact(pc);
	printf("按照名字排序成功\n");
}


//保存通讯录
void SaveContact(struct Contact* pc)
{
	FILE* pfwrite = fopen("test.txt", "wb");
	if (pfwrite == NULL)
	{
		perror("SaveContact:fopen");
		return;
	}

	//写数据
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		//二进制写
		fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pfwrite);   //每次写入一个结构体数据
	}

	//关闭文件
	fclose(pfwrite);
	pfwrite = NULL;

}



以上就是文件通讯录的全部内容了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值