从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)

上一篇:从0开始学c语言-33-动态内存管理_阿秋的阿秋不是阿秋的博客-CSDN博客

        我发现,理论的学习文章没几个人看,如果是这种实践类型的文章,看的人很多,偏偏我之前都是扔完代码就走了,也没认真写这种类型文章的思考过程,所以这篇打算好好写下。

先放上网盘链接,(我设置的永久有效)

链接:https://pan.baidu.com/s/1Renm5M2NRlXkbb-IX3r5QA?pwd=1f9i 
提取码:1f9i

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第1张图片

代码中还有个优化没做,是输入年龄那里判断是否整型并重新输入的问题

我在新的文章里写了解决方法,链接给上

学习小发现 - 03 - 如何判断输入的是不是整型数据并重新输入,纠错(异常)scanf与strcmp,_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

通讯录版本介绍

静态通讯录

*大概思路

*文件

1·功能入口书写 test.c

2·结构体思路 contact.h

联系人结构体

通讯录结构体

3·完善主函数内容 test.c

创建与初始化通讯录

确定分支语句函数

4·初始化通讯录函数 contact.h - contact.c

5·添加信息 contact.h - contact.c

演示

6·打印信息 contact.h - contact.c

演示

7·删除信息  contact.h - contact.c

找人 (名字查找)      

打印信息

确认删除

演示

8·查找信息 contact.h - contact.c

演示

9·修改信息 contact.h - contact.c

演示

动态通讯录

1·创建和初始化通讯录 

2·检查容量 contact.h - contact.c

3·归还空间 contact.h - contact.c

文件通讯录

1·读取文件

2·保存信息

排序功能

1·菜单

2·enum枚举类型

3·switch分支

4·排序


通讯录版本介绍

注:跟着写,只看文章会忘记前面写了什么。

功能:

1·存放信息        2·增加信息        

3·删除信息        4·修改信息

5·查找信息        6·排序信息

信息:名字 + 年龄 + 性别 + 电话 + 地址

静态版本:存放1000个人的信息

动态版本:增设容量,根据有效信息拓容

文件版本:程序开始读取文件,程序结束把信息写到文件中。

注:功能实现的过程都放在了静态通讯录版本中进行介绍。

静态通讯录

*大概思路

功能:

1·存放信息        2·增加信息        

3·删除信息        4·修改信息

5·查找信息        6·排序信息

信息:名字 + 年龄 + 性别 + 电话 + 地址

        首先,我看到这个功能就想到了switch分支语句。

        其次,又想到了enum枚举类型,把分支的数字和功能名字对应起来。

        最后,因为是通讯录,所以通讯录中有以联系人为单位的集合,那么就应该有对应的结构体来保存联系人的信息。

        这是最初的大概思路。

*文件

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第2张图片

        头文件contact.h是用来声明函数和定义一些变量的。

        源文件contact.c是用来写函数实现功能的具体过程的。

        test.c可以理解为功能的入口。

1·功能入口书写 test.c

        想要进入不同的功能区,要先书写相应的菜单。

void menu()
{
	printf(" ***** 0.EXIT 1.ADD 2.DEL *****\n");
	printf(" ***** 3.SEARCH  4.MODIFY *****\n");
	printf(" ***** 5.SORT    6.PRINT  *****\n");
}

        其次,把这些 菜单名字与数字 用enum类型对应起来。

enum Option
{
	EXIT,  //0
	ADD,   //1
	DEL,   //2
	SEARCH,//3
	MODIFY,//4
	SORT,  //5
	PRINT  //6
};

        在主函数中书写switch分支语句实现不同功能的入口。

int main()
{
	int input = 0; 
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case EXIT: //EXIT就代表数字0

			break;
		case ADD: //ADD就代表数字1

			break;
		case DEL: //后面的以此类推,和enum中的数字对应起来

			break;
		case SEARCH:
			
			break;
		case MODIFY:
			
			break;
		case SORT:
			
			break;
		case PRINT:
			
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

2·结构体思路 contact.h

        因为通讯录和联系人都会跨文件使用,所以写在头文件中更好一些。

联系人结构体

        信息:名字 + 年龄 + 性别 + 电话 + 地址

        判断每个信息对应的类型,以及是否需要数组来储存就可以写出来了。

#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TEL 12
#define MAX_ADDR 30
//定义一个人的结构体信息
struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	int age;
	char tel[MAX_TEL];
	char addr[MAX_ADDR];
};

        后又因为之后会多次使用这个结构体类型,为了书写方便,把struct PeoInfo重命名为PeoInfo

//定义一个人的结构体信息
typedef struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	int age;
	char tel[MAX_TEL];
	char addr[MAX_ADDR];
}PeoInfo; //把struct PeoInfo定义为PeoInfo

通讯录结构体

        首先通讯录结构体中肯定要有联系人结构体,而我们要实现的是存放1000个人的信息,所以把这个联系人结构体设为数组。

        其次,为了方便添加信息,删除信息之类的,我们需要一个变量来记录目前有几个有效信息。

        同样,我们也进行了重命名,把struct Contact定义为Contact。

#define MAX 1000
//定义通讯录——静态
typedef struct Contact
{
	PeoInfo data[MAX]; //存放过来的个人信息集合(数组
	int sz; //记录已经有几个信息,相当于data数组的下标
	//比如已经有一个人的信息,那么下一个要增加信息对应下标就是1
}Contact; 

3·完善主函数内容 test.c

创建与初始化通讯录

        之前我们在test.c中写了switch分支语句来实现不同功能的入口,但我们还没有创建一个通讯录结构体变量以及初始化通讯录。

int main()
{
	int input = 0; 
	do {
        //创建通讯录
	    Contact con;
	    //初始化通讯录
	    IniContact(&con);
	    //这里要取地址传参,因为我们要
	    //通过这个函数改变通讯录
	    //单纯传数值没办法改编数据
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
//后面先省略

确定分支语句函数

        我们知道不同分支的情况对应不同的功能,这个功能我们通过函数实现。所以这里我们先要确定好函数的命名与参数。参数都是通讯录的地址,因为无论是改变通讯录信息还是不改变,传地址都效率高且节省空间。命名方面我可能不是很规范,第二个单词没大写,太懒了哈哈哈。

int main()
{
	//创建通讯录
	Contact con;
	//初始化通讯录
	IniContact(&con);

	int input = 0; 
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case EXIT:
			Destroycontact(&con); //销毁信息,动态版本用
			break;
		case ADD:
			Addcontact(&con); //添加信息
			break;
		case DEL:
			Delcontact(&con); //删除信息
			break;
		case SEARCH:
			Searcontact(&con); //删除信息
			break;
		case MODIFY:
			Modifcontact(&con); //修改信息
			break;
		case SORT:
			Sortcontact(&con); //分类信息
			break;
		case PRINT:
			Princontact(&con); //打印信息
			//虽然打印不需要改变通讯录内容
			//但是传地址节省空间
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

4·初始化通讯录函数 contact.h - contact.c

        我们在头文件中进行这个函数的声明,在contact.c中书写这个函数的定义。

声明:

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

定义:

        通讯录结构体中就两个变量,一个是记录有效信息的 int sz 变量,这个初始化就很简单,等于0就好了。

        另一个是   联系人信息结构体 数组 ,数组初始化可以使用循环,或者用之前在这里

从0开始学c语言-31-关于字符串的各种函数+内存函数+字符串旋转判断_阿秋的阿秋不是阿秋的博客-CSDN博客

学过的memset函数,以字节为单位设置内存。 

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第3张图片

//初始化通讯录_静态
void IniContact(Contact* pc)
{
	pc->sz = 0;
	//memset 内存设置
	memset(pc->data, 0, sizeof(pc->data));
}

5·添加信息 contact.h - contact.c

        我们在头文件中进行这个函数的声明,在contact.c中书写这个函数的定义。

声明:

 //添加信息
void Addcontact(Contact* pc);

定义:

        这里要明确的就是 sz 的值就是下一个添加信息的下标,比如,现在通讯录中有一个联系人的信息,这个信息对应下标是0,此时的sz是1,把sz当做 pc->data[pc->sz] 的下标,正好是下一个添加信息的下标。

void Addcontact(Contact* pc)
{
//因为都是数组,便没加&符号
	printf("请输入名字:>");
	scanf("%s", pc->data[pc->sz].name);

	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);

	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
//这里只有age是int类型,所以需要取地址

	printf("请输入电话:>");
	scanf("%s", pc->data[pc->sz].tel);

	printf("请输入地址:>");
	scanf("%s", pc->data[pc->sz].addr);

	pc->sz++;
	printf("增加成功\n");
}

演示

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第4张图片

为了确认是否有我们想要的信息,现在进行打印信息的设计。

6·打印信息 contact.h - contact.c

        我们在头文件中进行这个函数的声明,在contact.c中书写这个函数的定义。

声明:

//打印信息
void Princontact(const Contact* pc);

定义:

        因为我们要实现这样的打印效果,便需要打印标题,以及对应的数组信息。

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第5张图片

        首先我考虑到,如果通讯录为空,那便不需要打印。

        其次,标题之间的大空格该如何设定。

        最后,要打印的信息该如何访问。

这些都在代码里。

void Princontact(const Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,没有需要打印的。\n");
		return;
	}
	//打印标题
	printf("%-15s\t%-5s\t%-s\t%-10s\t%-20s\n", "名字", "性别", "年龄", "电话", "地址");
	//加上负号是左对齐
	//打印信息
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
        //这个打印格式和前面要对应起来,整齐
		printf("%-15s\t%-5s\t%-d\t%-10s\t%-20s\n", 
			  pc->data[i].name,
			  pc->data[i].sex,
			  pc->data[i].age,
			  pc->data[i].tel,
			  pc->data[i].addr
			);
	}
}

演示

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第6张图片

然后进行删除信息的书写。 

7·删除信息  contact.h - contact.c

        我们在头文件中进行这个函数的声明,在contact.c中书写这个函数的定义。

声明:

//删除信息
void Delcontact(Contact* pc);

定义:

整体逻辑介绍:

1·查人

2·输入下标,检查输入下标是否正确(因有重名)

3·确认删除

        首先要知道的是,你删除一个人的信息,大多是通过名字来寻找的,所以这里主要设置名字搜索来删除信息。

void Delcontact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,不需要删除\n");
		return;
	}
	char name[MAX_NAME] = { 0 };
	//1·找人并打印  找到\找不到
	printf("请输入删除人的名字:>");
	scanf("%s", name);
	int ret = NameFind(pc, name); //通过名字寻找的函数
	//找到返回不为0的数,找不到返回-1
	if (ret == -1)
	{
		printf("找不到\n");
		return;
	}
	return;
}

找人 (名字查找)      

         所以我们现在要书写 NameFind(pc, name); 这个函数的实现过程。因为我们设计的函数是有返回值的,所以函数的返回类型设为了int。

        我们需要设一个循环,来比对 有无 和我们输入的 名字相同的联系人。(这里我考虑到了重名问题,所以又设计了打印信息的函数。)

//按照名字查找
//static表示该函数不能跨文件使用
static int NameFind(const Contact* pc, char* name)
{
	int i = 0;
	int flag = 0;
	for (i = 0; i sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			Prin_del(pc, i);
			flag++;
		}
	}
	if (flag)
		return flag;
	return -1;
}

打印信息

这个很简单,所以不再说。

//打印查找到的
 void Prin_del(const Contact* pc, int i)
{
	printf("************************************************\n");
	printf("%-15s\t%-5s\t%-s\t%-10s\t%-20s\n", "名字", "性别", "年龄", "电话", "地址");
	//加上负号是左对齐
	//打印信息
	printf("%-15s\t%-5s\t%-d\t%-10s\t%-20s\n",
		pc->data[i].name,
		pc->data[i].sex,
		pc->data[i].age,
		pc->data[i].tel,
		pc->data[i].addr
	);
	printf("名字是%s的人标号为%d\n", pc->data[i].name, i);
	printf("************************************************\n");
}

确认删除

void Delcontact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,不需要删除\n");
		return;
	}
	char name[MAX_NAME] = { 0 };
	//1·找人并打印  找到\找不到
	printf("请输入删除人的名字:>");
	scanf("%s", name);
	int ret = NameFind(pc, name); //通过名字寻找的函数
	//找到返回不为0的数,找不到返回-1
	if (ret == -1)
	{
		printf("找不到\n");
		return;
	}
	return;
}

        我们之前写了这样的代码,假设现在找到了,那么现在要进行确认是否删除的代码书写。

        因为之前设计的找人函数并没有设置为带回来数组对应的下标,所以在打印信息中设计了打印下标。就像这样张三的下标是1。 

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第7张图片

        所以我们需要一个变量来储存下标,并加入 if 语句确定输入下标是否正确,来决定是否进入删除环节。

    printf("请输入删除人的标号:>");
	int input = 0;
	scanf("%d", &input);
	if (strcmp(pc->data[input].name, name) == 0)
	{
		printf("请确认删除(1/0):>");
		int a = 0;
		scanf("%d", &a);
		if (a == 1)
		{
			
		}
		else if (a == 0)
			printf("未删除\n");
		else
			printf("输入错误,未删除\n");
	}
	else
	{
		printf("输入标号不对\n");
	}

        如果输入确认删除,那么就要进行 if (a == 1) 语句中的书写。 

        首先删除的话,我们不能简单的把有效信息 sz 变量 减一,这样只会删除最后一个联系人。

        其次,我们要明确这是一个联系人数组,那就可以类比,如果你想删除数组中的一个数据,并且保留其他数据,数组的有效信息-1,该如何做呢?

        如图,我们要删除数组中的2,就需要3向前挪,4向前挪,再删除最后一个4的空间。

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第8张图片

 而这其中,最最关键的步骤就是确定好向前移动数据的范围,如上图,数据的有效信息 sz是5,假设 2 对应下标是 i ,那么向前挪动应该这么写,pc->data[i] = pc->data[i + 1];

所以我们的下标 i,走到sz -2,也就是3对应的下标,就可以实现3挪到2位置,4挪到3位置。

if (a == 1)
{
	//删除
	int i = 0;
	//要确定好向前移动的数据范围
	for (i = input; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	printf("删除成功!\n");
	pc->sz--;
}

演示

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第9张图片

 现在打印确认是否删除成功。

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第10张图片

已经删除了。

8·查找信息 contact.h - contact.c

        我们在头文件中进行这个函数的声明,在contact.c中书写这个函数的定义。

声明:

//查找信息
void Searcontact(Contact* pc);

定义:

        实际上,在书写删除信息函数的时候就已经写了查找 打印查找到的信息 的函数了。这里直接用就行。

//查找
void Searcontact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,查不到\n");
		return;
	}
	printf("请输入查找人姓名:>");
	char name[MAX_NAME] = { 0 };
	scanf("%s",name);
	int ret = NameFind(pc, name);
	if (ret == -1)
	{
		printf("找不到\n");
		return;
	}
	printf("找到了\n");
	return;
}

演示

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第11张图片

OK,找到了。 

9·修改信息 contact.h - contact.c

声明:

//修改信息
void Modifcontact(Contact* pc);

定义:

        其实上,这个修改信息的逻辑和删除信息的逻辑差不多。

        都是查找,是否查到,查到输入下标确认修改,下标不正确退出函数,下标正确确认下标,确认下标后进行输入信息修改覆盖的操作。

//修改信息
void Modifcontact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,没有需要修改的。\n");
		return;
	}
	printf("请输入修改人姓名:>");
	char name[MAX_NAME] = { 0 };
	scanf("%s", name);
	int ret = NameFind(pc, name);
	if (ret == -1)
	{
		printf("找不到\n");
		return;
	}
	printf("请输入修改人下标:>");
	int input = 0;
	scanf("%d", &input);
	if (strcmp(pc->data[input].name, name) == 0)
	{
		printf("请确定下标(1/0):>");
		int a = 0;
		scanf("%d", &a);
		if (a == 1)
		{
            printf("***进行修改***\n");
			printf("请输入名字:>");
			scanf("%s", pc->data[input].name);
			printf("请输入性别:>");
			scanf("%s", pc->data[input].sex);
			printf("请输入年龄:>");
			scanf("%d", &(pc->data[input].age));
			printf("请输入电话:>");
			scanf("%s", pc->data[input].tel);
			printf("请输入地址:>");
			scanf("%s", pc->data[input].addr);
			printf("修改成功\n");
		}
		else if(a == 0)
			printf("未修改\n");
		else
			printf("输入错误,未修改\n");
	}
	else
	{
		printf("输入下标不正确,修改失败\n");
	}
	return;
}

演示

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第12张图片

         现在我们要修改名字为1的那个。

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第13张图片

 从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第14张图片

 修改成功。

包括输入不正确的检查也成功了。

从0开始学c语言 - 34 - 通讯录 -静态、动态、存到文件(三种版本)_第15张图片

 静态版本到此为止。

下面介绍动态版本。

动态通讯录

        动态通讯录是需要有动态内存分配基础的。

从0开始学c语言-33-动态内存管理_阿秋的阿秋不是阿秋的博客-CSDN博客

        首先回顾我们的动态通讯录的功能,(静态通讯录已经实现了大多数功能)。

动态版本:增设容量,根据有效信息拓容

        我们具体化这句话,比如我们设定一开始的容量是5个人,然后在添加第六个人的时候发现满了,那就需要拓容,拓容也要设计好是一次拓容几个。后面的整体思路是这样的。

1·创建和初始化通讯录 

        之前那个通讯录的结构体需要再加一个变量,叫做capacity(容量)。以及需要把数组换为指针,指向堆区上开辟的动态内存分配空间。

//动态定义通讯录
typedef struct Contact
{
	PeoInfo* data; //指向动态开辟的空间,存放个人信息
	int sz; //记录当前通讯录中有效信息个数
	int capacity; //记录当前通讯录最大容量
}Contact;

        开辟动态内存空间,我们使用malloc函数,并用define定义最初的容量和每次拓容的大小。

#define START 2
#define INC 2
//这俩在头文件中

//动态初始化通讯录 -contact.c
void IniContact(Contact* pc)
{
	pc->sz = 0;
	PeoInfo*ptr = malloc(START * (sizeof(PeoInfo)));
	if (ptr == NULL)
	{
		perror("Addcontact");
		printf("初始化失败\n");
		return;
	}
	pc->data = ptr; //指针指向动态开辟的空间
	pc->capacity = START;
}

2·检查容量 contact.h - contact.c

声明(头文件):

//增容
void Checkcapacity(Contact* pc);

定义:

        首先要明确,我们什么时候需要拓容。

//动态定义通讯录
typedef struct Contact
{
	PeoInfo* data; //指向动态开辟的空间,存放个人信息
	int sz; //记录当前通讯录中有效信息个数
	int capacity; //记录当前通讯录最大容量
}Contact;

        当然是  sz有效信息 == 最大容量capacity  的时候了。

//检查容量
void Checkcapacity(Contact* pc)
{
	if ((pc->sz) == (pc->capacity))
	{
		//扩容
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (INC + pc->capacity) * sizeof(PeoInfo));
		if (ptr != NULL)
		{
			printf("增容成功\n");
			pc->data = ptr; //指针指向动态开辟的空间
			pc->capacity += INC; //变量capacity也需要跟着变大
		}
		else
		{
			printf("拓容失败\n");
			perror("Addcontact");
			return;
		}
	}
}

        很有可能这句不太懂。

//扩容
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (INC + pc->capacity) * sizeof(PeoInfo));

        其实就是对这个realloc函数的返回值进行了强制类型转换,转换为了联系人结构体指针类型。

        第一个参数 pc->data 是是要调整的内存地址,

        第二个参数(INC + pc->capacity) * sizeof(PeoInfo)是调整后的空间大小。因为是调整后的空间大小,所以千万别忘了加上原有的capacity大小。

注:这个函数使用在ADD(添加信息)函数中,记得自己要在这个add函数里调用。

动态开辟的空间要尤其注意释放空间,所以在EXIT这个功能中要进行新函数的书写。

3·归还空间 contact.h - contact.c

声明(头文件):

//退出,释放空间
void Destroycontact(Contact* pc);

定义:

        除了释放动态空间外,也要把有效信息 sz和 容量 capacity进行归0。

        这个函数需要在EXIT功能入口处调用。

//退出销毁信息
void Destroycontact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

文件通讯录

        这个需要有文件基础,不过我还没写,后续补上链接。

回顾功能:

文件版本:程序开始读取文件,程序结束把信息写到文件中。

1·读取文件

注意每次读取是以联系人结构体大小为单位进行读取的就行。

看注释。这个函数要在初始化通讯录的函数中调用,自己记得写。

//读取文件
void Readcontact(Contact* pc)
{
//1·打开文件
	FILE* pf = fopen("contact.dat", "r");
//以只读形式打开文件contact.dat
	if (pf == NULL)
	{
		perror("Readcontact");
		return;
	}
//2·读取文件
	PeoInfo tmp = { 0 };//创一个存信息的中间量
//从pf中读取1个sizeof(PeoInfo)大小的数据到&tmp中
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		Checkcapacity(pc); //看是否需要增容
		pc->data[pc->sz] = tmp;

		//sz是有效信息
		//sz=0,tmp从文件读取信息放进去后sz=1
		//而sz=1正好是下一个读取tmp要放进去的下标

		pc->sz++; //这个千万不能丢
	}
//3·关闭文件
	fclose(pf);
	pf = NULL;
}

2·保存信息

        其实上面两个版本都有个缺陷,就是每次运行程序都是重新添加信息,上一次的信息不会被保存起来,所以我们需要书写这样一个函数,来保存每次输入的信息到初始化通讯录需要读取的文件中。

        这个函数需要在EXIT功能入口中调用。在Destroycontact函数前把数据保存好。

void Savecontact(Contact* pc)
{
	//以输出方式打开文件
	FILE* pf = fopen("contact.dat", "w"); //注意这是'w'
	if (pf == NULL)
	{
		perror("Savecontact");
		return;
	}
	//写文件
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
		//从pc->data中传1个sizeof(PeoInfo)到pf中
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
}

排序功能

        基本功能已经完善了,但是我还没写排序功能的实现,因为比较长,所以我放最后说。

        要排序,就需要明确我们按照什么来排序。

        这个的思路和我们一开始的菜单设计思路差不多,通过menu函数、enum枚举类型、switch分支语句来实现不同选项的分类。

1·菜单

        这个菜单的排序设置,是根据联系人结构体的成员变量来设计的。

void menus()
{
	printf(" ***** 0.EXIT 1.NAME  *****\n");
	printf(" ***** 2.SEX  3.AGE   *****\n");
	printf(" ***** 4.TEL  5.ADDR   *****\n");
}

2·enum枚举类型

        注意要和上面的菜单对应起来。

enum Option2
{
	EXIT,  //0
	name,  //1
	sex,   //2
	age,   //3
	tel,   //4
	addr,   //5
};

3·switch分支

        注意我在每个分支设计的函数参数,这是为了方便后续的排序有根据。

//排序
void Sortcontact(Contact* pc)
{
	printf("请选择排序方式\n");
	menus();
	int input = 0;
	scanf("%d", &input);
	switch (input)
	{
	case EXIT:
		printf("退出排序\n");
		return;
	case name:
		sort(pc,name);
		break;
	case sex:
		sort(pc,sex);
		break;
	case age:
		sort(pc,age);
		break;
	case tel:
		sort(pc,tel);
		break;
	case addr:
		sort(pc,addr);
		break;
	}
	printf("排序成功\n");
}

4·排序

其实在之前我们就学过冒泡排序和qosort函数进行排序,这两个各有好处。

相应的文章学习链接给上

从0开始学c语言-28-qsort函数、 数组和指针参数、函数指针数组(转移表)、回调函数_阿秋的阿秋不是阿秋的博客-CSDN博客

从0开始学c语言-16-数组以及数组传参应用:冒泡排序_阿秋的阿秋不是阿秋的博客-CSDN博客

void Change(Contact* pc, int j)
{
	PeoInfo A= pc->data[j];
	pc->data[j] = pc->data[j + 1];
	pc->data[j + 1] = A;
}
void sort(Contact* pc,int opt)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < pc->sz - 1; i++)
	{
		for (j = 0; j < pc->sz - i - 1 ; j++)
		{
			if (opt == name)
			{
				int tmp = strcmp(pc->data[j].name, pc->data[j + 1].name);
				if (tmp > 0)
					Change(pc, j);
			}
			else if (opt == sex)
			{
				int tmp = strcmp(pc->data[j].sex, pc->data[j + 1].sex);
				if (tmp > 0)
					Change(pc, j);
			}
			else if (opt == age)
			{
				int tmp = pc->data[j].age - pc->data[j + 1].age;
				if (tmp > 0)
					Change(pc, j);
			}
			else if (opt == tel)
			{
				int tmp = strcmp(pc->data[j].tel, pc->data[j + 1].tel);
				if (tmp > 0)
					Change(pc, j);
			}
			else if (opt == addr)
			{
				int tmp = strcmp(pc->data[j].addr, pc->data[j + 1].addr);
				if (tmp > 0)
					Change(pc, j);
			}
			else
			{
				printf("传参错误\n");
			}
		}		
	}
	
}

这是我写的冒泡排序方法,如果你想用qsort函数也行,那个更简便。

现在,这个通讯录就完整了,

相信很少有人能看完,不过我还是会认真写的。

毕竟咱就爱这口~

你可能感兴趣的:(从0开始学c语言,c语言,visualstudio,学习)