C语言进阶之通讯录的实现(静态版和动态版)以及动态内存管理

在这里插入图片描述

1.通讯录实现要求

通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址

提供方法

  1. 添加联系人信息
  2. 删除指定联系人信息
  3. 查找指定联系人信息
  4. 修改指定联系人信息
  5. 显示所有联系人信息
  6. 清空所有联系人
  7. 以名字排序所有联系人

在这里插入图片描述

2.静态版通讯录实现

整个程序的实现需要建立三个文件

contact.h 通讯录的结构体定义及函数的声明

contact.c 函数的实现

main.c 主函数调用

2.1 contact.h文件实现

头文件引用和宏定义

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10 //名字的长度(单位:字节)
#define SEX_MAX 6   //性别长度
#define ADDR_MAX 20 //住址长度
#define TELE_MAX 12 //电话长度
#define N 100       //通讯录的长度(单位为个人结构体大小)

结构体定义

typedef struct people //个人结构体定义
{
    
    
	char name[NAME_MAX]; //姓名
	char sex[SEX_MAX];   //性别
	int age;             //年龄
	char tele[TELE_MAX]; //电话
	char addr[ADDR_MAX]; //住址

}peo;

typedef struct Contact //通讯录结构体定义
{
    
    
	peo data[N]; //个人结构体数组
	int sz;      //结构体长度
}Con;

枚举定义,方便主函数switch语句调用,易分辨。

enum
{
    
    
	EXIT,//退出
	ADD,//增加
	DEL,//删除
	MOD,//修改
	SEARCH,//查找
	SHOW,//打印
	DISTORY,//删除全部
	QSORT//以名称排序
};

函数声明

void InitCon(Con* cont);//初始化函数

void add(Con* cont);//增加联系人

void show(const Con* cont);//打印联系人

int FindName(const Con* cont, const char* name);//查找联系人(内含)

void del(Con* cont);//删除联系人

void mod(Con* cont);//修改联系人

void sel(const Con* cont);//查找联系人打印

void Distory(Con* cont);//删除所有联系人

int cmp_stu_by_name(const void* p1, const void* p2);//排序调用的回调函数

下面我们来看最关键的部分,即为函数定义

2.2 contact.c文件实现

首先我们要包含头文件#include "contact.h"

初始化函数InitCon

void InitCon(Con* cont)
{
    
    
	assert(cont);
	cont->sz = 0;
	memset(cont->data, 0, sizeof(cont->data));
}

开头我们先断言,防止传NULL指针,第二步初始化长度为0,最后将结构体数组中的元素均初始化为0即可。

添加联系人函数add

void add(Con* cont)
{
    
    
	assert(cont);
	if (cont->sz == N)
	{
    
    
		printf("添加失败,通讯录已满");
		return;
	}
	printf("请输入要添加联系人的信息:\n");
	printf("请输入姓名:");
	scanf("%s", cont->data[cont->sz].name);
	printf("请输入性别:");
	scanf("%s", cont->data[cont->sz].sex);
	printf("请输入年龄:");
	scanf("%d", &cont->data[cont->sz].age);
	printf("请输入电话:");
	scanf("%s", cont->data[cont->sz].tele);
	printf("请输入地址:");
	scanf("%s", cont->data[cont->sz].addr);
	cont->sz++;
	printf("添加成功\n");
}

开始一样,我们先断言防止传NULL指针,第二就是判断空间有没有满,如果满了,直接返回退出并提示,如果没满,则继续添加联系人信息,添加完之后提示添加成功,再将sz++。
在这里插入图片描述
显示联系人函数show

void show(const Con* cont)
{
    
    
	assert(cont);
	int i = 0;
	printf("				通讯录\n");
	printf("	+---------------------------------------------------------------+\n");
	printf("	|姓名		性别	年龄	电话		地址		|\n");
	for (i = 0; i < cont->sz; i++)
	{
    
    
		printf("	|%-10s	%-5s	%-5d	%-11s	%-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
	}
	printf("	+---------------------------------------------------------------+\n");
}

首先一样,我们先断言防止传NULL指针,第二就是打印题头,然后对结构体数组进行遍历打印,注意对齐,根据不同情况可自行调整。
在这里插入图片描述

查找联系人函数(内含)FindName

int FindName(const Con* cont, const char* name)//查找人函数
{
    
    
	assert(cont && name);//防止传入空指针
	int i = 0;
	for (i = 0; i < cont->sz; i++)
	{
    
    
		if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
		{
    
    
			return i;//有就返回下标
		}
	}
	return -1;//没有返回一个负数.
}

实现这个函数主要是为了后面的删除、修改和查找并打印函数,方便复用,减少代码冗余。
首先同样,我们先断言防止传NULL指针,然后遍历使用strcmp函数进行字符匹配,查看是否有这个名字,有就返回下标,没有就返回负数。

删除联系人函数del

void del(Con* cont)//删除联系人函数
{
    
    
	assert(cont);
	int i = 0;
	char name[NAME_MAX];
	printf("请输入需要删除的联系人的姓名:\n");
	scanf("%s", name);
	i = FindName(cont, name);
	if (i == -1)
	{
    
    
		printf("通讯录中没有该联系人,删除失败\n");
		return;
	}
	for (; i < cont->sz - 1; i++)
	{
    
    
		cont->data[i] = cont->data[i + 1];
	}
	cont->sz--;
	printf("删除成功,姓名为%s的联系人已删除\n", name);
}

首先同样,我们先断言防止传NULL指针,然后创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则按照其返回的下标向后遍历依次向前覆盖。最后sz–。
在这里插入图片描述

修改联系人函数mod

void mod(Con* cont)//修改联系人函数
{
    
    
	assert(cont);
	int ret = 0;
	char name[NAME_MAX];
	printf("请输入要修改的联系人的姓名:\n");
	scanf("%s", name);
	ret = FindName(cont, name);
	if (ret == -1)
	{
    
    
		printf("通讯录中没有该联系人,修改失败\n");
		return;
	}
	printf("请输入修改后联系人的信息:\n");
	printf("请输入姓名:");
	scanf("%s", cont->data[ret].name);
	printf("请输入性别:");
	scanf("%s", cont->data[ret].sex);
	printf("请输入年龄:");
	scanf("%d", &cont->data[ret].age);
	printf("请输入电话:");
	scanf("%s", cont->data[ret].tele);
	printf("请输入地址:");
	scanf("%s", cont->data[ret].addr);
	printf("修改成功\n");
}

首先同样,我们先断言防止传NULL指针,然后创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则同增加联系人依次修改,当然你也可以再进行优化,修改某一项。
在这里插入图片描述

查找联系人函数sel

void sel(const Con* cont)//查询联系人函数
{
    
    

	char name[NAME_MAX];
	printf("请输入要查询的联系人的名字:\n");
	scanf("%s", name);
	int ret = FindName(cont, name);
	if (ret == -1)
	{
    
    
		printf("通讯录中没有该联系人,查询失败.\n");
		return;
	}
	printf("查询成功,该联系信息如下:\n");
	printf("	+---------------------------------------------------------------+\n");
	printf("	|姓名		性别	年龄	电话		地址		|\n");
	printf("	|%-10s	%-5s	%-5d	%-11s	%-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
	printf("	+---------------------------------------------------------------+\n");
}

首先我们创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则同show函数打印,只不过这里只打印一个。
在这里插入图片描述

删除所有联系人函数Distory

void Distory(Con* cont)
{
    
    
	assert(cont);
	cont->sz = 0;
	printf("全部联系人删除成功!\n");
}

删除全部联系人,只需要将sz归0即可,下一次添加就会直接覆盖,而且也不能再访问之前的联系人数据了。

排序联系人函数的回调函数cmp_stu_by_name

int cmp_stu_by_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}

这是调用库函数qsort给通讯录以姓名排序的一个回调函数,不理解的可以看我之前指针进阶那篇文章,其中就包含了qsort函数的使用及其回调函数的定义。

2.3 main.c文件实现

#include"contact.h"

void menu()
{
    
    
	printf("\n				欢迎使用通讯录:\n");
	printf("	+-------------------------------------------------------------+\n");
	printf("	|	1.添加联系人			2.删除联系人	      |\n");
	printf("	|	3.修改联系人			4.查询联系人	      |\n");
	printf("	|	5.展示通讯录			6.删除全部联系人      |\n");
	printf("	|	7.排序联系人		        0.退出通讯录	      |\n");
	printf("	+-------------------------------------------------------------+\n");
	printf("请选择:");
}
int main()
{
    
    
	int input = 0;
	Con cont;
	InitCon(&cont);
	while (1) {
    
    
		do
		{
    
    
			menu();

			scanf("%d", &input);
			switch (input)
			{
    
    
			case EXIT:
				printf("退出通讯录");
				break;
			case ADD:
				add(&cont);
				break;
			case DEL:
				del(&cont);
				break;
			case MOD:
				mod(&cont);
				break;
			case SEARCH:
				sel(&cont);
				break;
			case SHOW:
				show(&cont);
				break;
			case DISTORY:
				Distory(&cont);
				break;
			case QSORT:
				qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
				printf("排序成功\n");
				break;
			default:
				printf("输入错误");
				break;
			}
		} while (input);
		break;
	}

	return 0;
}

这里首先就是一个菜单排版,然后就是switch语句对各函数调用,这里的每个case选项用的是前面枚举定义的各个名称方便辨别。
在这里插入图片描述

2.4 静态版通讯录全部文件代码

//contact.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10
#define SEX_MAX 6
#define ADDR_MAX 20
#define TELE_MAX 12
#define N 100

typedef struct people
{
    
    
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tele[TELE_MAX];
	char addr[ADDR_MAX];

}peo;

typedef struct Contact
{
    
    
	peo data[N];
	int sz;
}Con;

enum
{
    
    
	EXIT,
	ADD,
	DEL,
	MOD,
	SEARCH,
	SHOW,
	DISTORY,
	QSORT
};

InitCon(Con* cont);

void add(Con* cont);

void show(const Con* cont);

int FindName(const Con* cont, const char* name);

void del(Con* cont);

void mod(Con* cont);

void sel(const Con* cont);

void Distory(Con* cont);

int cmp_stu_by_name(const void* p1, const void* p2);

//contact.c

#include "contact.h"

InitCon(Con* cont)
{
    
    
	assert(cont);
	cont->sz = 0;
	memset(cont->data, 0, sizeof(cont->data));
}



void add(Con* cont)
{
    
    
	assert(cont);
	if (cont->sz == N)
	{
    
    
		printf("添加失败,通讯录已满");
		return;
	}
	printf("请输入要添加联系人的信息:\n");
	printf("请输入姓名:");
	scanf("%s", cont->data[cont->sz].name);
	printf("请输入性别:");
	scanf("%s", cont->data[cont->sz].sex);
	printf("请输入年龄:");
	scanf("%d", &cont->data[cont->sz].age);
	printf("请输入电话:");
	scanf("%s", cont->data[cont->sz].tele);
	printf("请输入地址:");
	scanf("%s", cont->data[cont->sz].addr);
	cont->sz++;
	printf("添加成功\n");
}


void show(const Con* cont)
{
    
    
	assert(cont);
	int i = 0;
	printf("				通讯录\n");
	printf("	+---------------------------------------------------------------+\n");
	printf("	|姓名		性别	年龄	电话		地址		|\n");
	for (i = 0; i < cont->sz; i++)
	{
    
    
		printf("	|%-10s	%-5s	%-5d	%-11s	%-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
	}
	printf("	+---------------------------------------------------------------+\n");
}


int FindName(const Con* cont, const char* name)//查找人函数
{
    
    
	assert(cont && name);//防止传入空指针
	int i = 0;
	for (i = 0; i < cont->sz; i++)
	{
    
    
		if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
		{
    
    
			return i;//有就返回下标
		}
	}
	return -1;//没有返回一个负数.
}

void del(Con* cont)//删除联系人函数
{
    
    
	assert(cont);//防止传入空指针
	int i = 0;
	char name[NAME_MAX];
	printf("请输入需要删除的联系人的姓名:\n");
	scanf("%s", name);
	i = FindName(cont, name);
	if (i == -1)
	{
    
    
		printf("通讯录中没有该联系人,删除失败\n");
		return;
	}
	for (; i < cont->sz - 1; i++)//注意这里sz要-1,因为下面用到了i+1下标
	{
    
    
		cont->data[i] = cont->data[i + 1];
	}
	cont->sz--;
	printf("删除成功,姓名为%s的联系人已删除\n", name);
}


void mod(Con* cont)//修改联系人函数
{
    
    
	assert(cont);//防止传入空指针
	int ret = 0;
	char name[NAME_MAX];
	printf("请输入要修改的联系人的姓名:\n");
	scanf("%s", name);
	ret = FindName(cont, name);
	if (ret == -1)
	{
    
    
		printf("通讯录中没有该联系人,修改失败\n");
		return;
	}
	printf("请输入修改后联系人的信息:\n");
	printf("请输入姓名:");
	scanf("%s", cont->data[ret].name);
	printf("请输入性别:");
	scanf("%s", cont->data[ret].sex);
	printf("请输入年龄:");
	scanf("%d", &cont->data[ret].age);
	printf("请输入电话:");
	scanf("%s", cont->data[ret].tele);
	printf("请输入地址:");
	scanf("%s", cont->data[ret].addr);
	printf("修改成功.\n");
}


void sel(const Con* cont)//查询联系人函数
{
    
    

	char name[NAME_MAX];
	printf("请输入要查询的联系人的名字:\n");
	scanf("%s", name);
	int ret = FindName(cont, name);
	if (ret == -1)
	{
    
    
		printf("通讯录中没有该联系人,查询失败.\n");
		return;
	}
	printf("查询成功,该联系信息如下:\n");
	printf("	+---------------------------------------------------------------+\n");
	printf("	|姓名		性别	年龄	电话		地址		|\n");
	printf("	|%-10s	%-5s	%-5d	%-11s	%-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
	printf("	+---------------------------------------------------------------+\n");
}

void Distory(Con* cont)
{
    
    
	assert(cont);
	cont->sz = 0;
	printf("全部联系人删除成功!\n");
}

int cmp_stu_by_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}

//main.c

#include"contact.h"

void menu()
{
    
    
	printf("\n				欢迎使用通讯录:\n");
	printf("	+-------------------------------------------------------------+\n");
	printf("	|	1.添加联系人			2.删除联系人	      |\n");
	printf("	|	3.修改联系人			4.查询联系人	      |\n");
	printf("	|	5.展示通讯录			6.删除全部联系人      |\n");
	printf("	|	7.排序联系人		        0.退出通讯录	      |\n");
	printf("	+-------------------------------------------------------------+\n");
	printf("请选择:");
}
int main()
{
    
    
	int input = 0;
	Con cont;
	InitCon(&cont);
	while (1) {
    
    
		do
		{
    
    
			menu();

			scanf("%d", &input);
			switch (input)
			{
    
    
			case EXIT:
				printf("退出通讯录");
				break;
			case ADD:
				add(&cont);
				break;
			case DEL:
				del(&cont);
				break;
			case MOD:
				mod(&cont);
				break;
			case SEARCH:
				sel(&cont);
				break;
			case SHOW:
				show(&cont);
				break;
			case DISTORY:
				Distory(&cont);
				break;
			case QSORT:
				qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
				printf("排序成功\n");
				break;
			default:
				printf("输入错误");
				break;
			}
		} while (input);
		break;
	}
	
	return 0;
}

3.动态内存管理

3.1 为什么存在动态内存分配

我们已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {
    
    0};//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有两个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
    但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。

3.2 动态内存函数的介绍

malloc和free

C语言提供了一个动态内存开辟的函数:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  1. 如果开辟成功,则返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  3. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。

  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  2. 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 stdlib.h 头文件中。

calloc

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);
  1. 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  2. 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

举个例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL != p)
	{
    
    
	}
	free(p);
	p = NULL;
	return 0;
}

在这里插入图片描述
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

realloc

  1. realloc函数的出现让动态内存管理更加灵活。
  2. 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

函数原型如下:

void* realloc (void* ptr, size_t size);
  1. ptr 是要调整的内存地址
  2. size 调整之后新大小
  3. 返回值为调整之后的内存起始位置。
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
  5. realloc在调整内存空间的是存在两种情况:

情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
在这里插入图片描述

情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

由于上述的两种情况,realloc函数的使用就要注意一些。

3.3 常见的动态内存错误

对NULL指针的解引用操作

void test()
{
    
    
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

对动态开辟空间的越界访问

void test()
{
    
    
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
    
    
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
    
    
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
}

对非动态开辟内存使用free释放

void test()
{
    
    
 	int a = 10;
	int *p = &a;
 	free(p);//ok?
}

使用free释放一块动态开辟内存的一部分

void test()
{
    
    
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

对同一块动态内存多次释放

void test()
{
    
    
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
}

动态开辟内存忘记释放(内存泄漏)

void test()
{
    
    
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
    
    
		*p = 20;
	}
}
int main()
{
    
    
	test();
	while (1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏

切记
动态开辟的空间一定要释放,并且正确释放。

3.4 C/C++程序的内存开辟

C/C++程序内存分配的几个区域

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。

3.5 柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如:

typedef struct st_type
{
    
    
 	int i;
 	int a[0];//柔性数组成员
}type_a;

或有些编译器会报错无法编译可以改成:

typedef struct st_type
{
    
    
 	int i;
 	int a[];//柔性数组成员
}type_a;

柔性数组的特点

  1. 结构中的柔性数组成员前面必须至少一个其他成员。
  2. sizeof 返回的这种结构大小不包括柔性数组的内存。
  3. 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
    例如:
typedef struct st_type
{
    
    
 	int i;
 	int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

柔性数组的使用

int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
    
    
 	p->a[i] = i;
}
free(p);

这个柔性数组成员a,相当于获得了100个整型元素的连续空间。

柔性数组的优势

上述的 type_a 结构也可以设计为:

typedef struct st_type
{
    
    
	int i;
	int* p_a;
}type_a;
type_a* p = (type_a*)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int*)malloc(p->i * sizeof(int));
//业务处理
for (i = 0; i < 100; i++)
{
    
    
	p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

上述 代码1代码2 可以完成同样的功能,但是 代码1 的实现有两个好处:

第一个好处是:方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

第二个好处是:这样有利于访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片。

4.动态版通讯录实现

我们就在原有的静态表上改动即可。

4.1 contact.h文件改动

#define N 3
#define INC 2

typedef struct Contact
{
    
    
	peo* data;
	int capacity;
	int sz;
}Con;

void DistoryCon(Con* cont);

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

4.2 contact.c文件改动

通讯录初始化函数InitCon修改

void InitCon(Con* cont)
{
    
    
	assert(cont);
	cont->data = (peo*)malloc(N * sizeof(peo));
	if (cont->data == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	cont->sz = 0;
	cont->capacity = N;
}

首先断言,之后我们给结构体数组分配初始值3个结构体大小空间,然后判断是否开辟成功,sz初始值为0,capacity初始值为我们开始设定的N也就是3。

增加检查容量函数

int CheckCapacity(Con* cont)
{
    
    
	if (cont->sz == cont->capacity)
	{
    
    
		peo* tmp = (peo*)realloc(cont->data, (cont->capacity + INC) * sizeof(peo));
		if (tmp == NULL)
		{
    
    
			perror("relloc");
			return 0;
		}
		else
		{
    
    
			cont->data = tmp;
			cont->capacity += INC;
			printf("增容成功");
			return 1;
		}
	}
	return 1;
}

这里我们用于判断容量是否已满,如果已满,我们则使用realloc函数进行扩容,这时我们不能直接扩容,而是要先用临时指针,如果空间申请成功,再将地址赋给通讯录,如果未申请成功,则为NULL指针,提示后返回0,增容成功,tmp地址传给data,容量+2,并提示增容成功,返回1,无需增容也返回1.

增加联系人函数add修改

void add(Con* cont)
{
    
    
	assert(cont);
	if (0 == CheckCapacity(cont))
		return;
		
	printf("请输入要添加联系人的信息:\n");
	printf("请输入姓名:");
	scanf("%s", cont->data[cont->sz].name);
	printf("请输入性别:");
	scanf("%s", cont->data[cont->sz].sex);
	printf("请输入年龄:");
	scanf("%d", &cont->data[cont->sz].age);
	printf("请输入电话:");
	scanf("%s", cont->data[cont->sz].tele);
	printf("请输入地址:");
	scanf("%s", cont->data[cont->sz].addr);
	cont->sz++;
	printf("添加成功\n");
}

把之前的条件判断改为容量函数的复用,若为0,直接返回退出。
在这里插入图片描述

增加函数销毁动态申请的空间DistoryCon

void DistoryCon(Con* cont)
{
    
    
	free(cont->data);
	cont->data = NULL;
	cont->capacity = 0;
	cont->sz = 0;
}

这个函数是为了将已申请的空间还给内存,防止内存泄漏。

4.3 main.c文件改动

这个文件只有一处改动,就是在switch语句中的case 0处增加了销毁动态申请的空间函数DistoryCon调用
在这里插入图片描述

4.4 动态版通讯录全部文件代码

//contact.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10
#define SEX_MAX 6
#define ADDR_MAX 20
#define TELE_MAX 12
#define N 3
#define INC 2

typedef struct people
{
    
    
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tele[TELE_MAX];
	char addr[ADDR_MAX];

}peo;

typedef struct Contact
{
    
    
	peo* data;
	int capacity;
	int sz;
}Con;

enum
{
    
    
	EXIT,
	ADD,
	DEL,
	MOD,
	SEARCH,
	SHOW,
	DISTORY,
	QSORT
};

void InitCon(Con* cont);

void add(Con* cont);

void show(const Con* cont);

int FindName(const Con* cont, const char* name);

void del(Con* cont);

void mod(Con* cont);

void sel(const Con* cont);

void Distory(Con* cont);

int cmp_stu_by_name(const void* p1, const void* p2);

void DistoryCon(Con* cont);

//contact.c

#include "contact.h"

void InitCon(Con* cont)
{
    
    
	assert(cont);
	cont->data = (peo*)malloc(N * sizeof(peo));
	if (cont->data == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	cont->sz = 0;
	cont->capacity = N;
}

int CheckCapacity(Con* cont)
{
    
    
	if (cont->sz == cont->capacity)
	{
    
    
		peo* tmp = (peo*)realloc(cont->data, (cont->capacity + INC) * sizeof(peo));
		if (tmp == NULL)
		{
    
    
			perror("relloc");
			return 0;
		}
		else
		{
    
    
			cont->data = tmp;
			cont->capacity += INC;
			printf("增容成功");
			return 1;
		}
	}
	return 1;
}

void add(Con* cont)
{
    
    
	assert(cont);
	if (0 == CheckCapacity(cont))
		return;
		
	printf("请输入要添加联系人的信息:\n");
	printf("请输入姓名:");
	scanf("%s", cont->data[cont->sz].name);
	printf("请输入性别:");
	scanf("%s", cont->data[cont->sz].sex);
	printf("请输入年龄:");
	scanf("%d", &cont->data[cont->sz].age);
	printf("请输入电话:");
	scanf("%s", cont->data[cont->sz].tele);
	printf("请输入地址:");
	scanf("%s", cont->data[cont->sz].addr);
	cont->sz++;
	printf("添加成功\n");
}


void show(const Con* cont)
{
    
    
	assert(cont);
	int i = 0;
	printf("				通讯录\n");
	printf("	+---------------------------------------------------------------+\n");
	printf("	|姓名		性别	年龄	电话		地址		|\n");
	for (i = 0; i < cont->sz; i++)
	{
    
    
		printf("	|%-10s	%-5s	%-5d	%-11s	%-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
	}
	printf("	+---------------------------------------------------------------+\n");
}


int FindName(const Con* cont, const char* name)//查找人函数
{
    
    
	assert(cont && name);//防止传入空指针
	int i = 0;
	for (i = 0; i < cont->sz; i++)
	{
    
    
		if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
		{
    
    
			return i;//有就返回下标
		}
	}
	return -1;//没有返回一个负数.
}

void del(Con* cont)//删除联系人函数
{
    
    
	assert(cont);//防止传入空指针
	int i = 0;
	char name[NAME_MAX];
	printf("请输入需要删除的联系人的姓名:\n");
	scanf("%s", name);
	i = FindName(cont, name);
	if (i == -1)
	{
    
    
		printf("通讯录中没有该联系人,删除失败\n");
		return;
	}
	for (; i < cont->sz - 1; i++)//注意这里sz要-1,因为下面用到了i+1下标
	{
    
    
		cont->data[i] = cont->data[i + 1];
	}
	cont->sz--;
	printf("删除成功,姓名为%s的联系人已删除\n", name);
}


void mod(Con* cont)//修改联系人函数
{
    
    
	assert(cont);//防止传入空指针
	int ret = 0;
	char name[NAME_MAX];
	printf("请输入要修改的联系人的姓名:\n");
	scanf("%s", name);
	ret = FindName(cont, name);
	if (ret == -1)
	{
    
    
		printf("通讯录中没有该联系人,修改失败\n");
		return;
	}
	printf("请输入修改后联系人的信息:\n");
	printf("请输入姓名:");
	scanf("%s", cont->data[ret].name);
	printf("请输入性别:");
	scanf("%s", cont->data[ret].sex);
	printf("请输入年龄:");
	scanf("%d", &cont->data[ret].age);
	printf("请输入电话:");
	scanf("%s", cont->data[ret].tele);
	printf("请输入地址:");
	scanf("%s", cont->data[ret].addr);
	printf("修改成功.\n");
}


void sel(const Con* cont)//查询联系人函数
{
    
    

	char name[NAME_MAX];
	printf("请输入要查询的联系人的名字:\n");
	scanf("%s", name);
	int ret = FindName(cont, name);
	if (ret == -1)
	{
    
    
		printf("通讯录中没有该联系人,查询失败.\n");
		return;
	}
	printf("查询成功,该联系信息如下:\n");
	printf("	+---------------------------------------------------------------+\n");
	printf("	|姓名		性别	年龄	电话		地址		|\n");
	printf("	|%-10s	%-5s	%-5d	%-11s	%-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
	printf("	+---------------------------------------------------------------+\n");
}

void Distory(Con* cont)
{
    
    
	assert(cont);
	cont->sz = 0;
	printf("全部联系人删除成功!\n");
}

int cmp_stu_by_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}

void DistoryCon(Con* cont)
{
    
    
	free(cont->data);
	cont->data = NULL;
	cont->capacity = 0;
	cont->sz = 0;
}

//main.c

#include"contact.h"

void menu()
{
    
    
	printf("\n				欢迎使用通讯录:\n");
	printf("	+-------------------------------------------------------------+\n");
	printf("	|	1.添加联系人			2.删除联系人	      |\n");
	printf("	|	3.修改联系人			4.查询联系人	      |\n");
	printf("	|	5.展示通讯录			6.删除全部联系人      |\n");
	printf("	|	7.排序联系人		        0.退出通讯录	      |\n");
	printf("	+-------------------------------------------------------------+\n");
	printf("请选择:");
}
int main()
{
    
    
	int input = 0;
	Con cont;
	InitCon(&cont);
	while (1) {
    
    
		do
		{
    
    
			menu();

			scanf("%d", &input);
			switch (input)
			{
    
    
			case EXIT:
				DistoryCon(&cont);
				printf("退出通讯录");
				break;
			case ADD:
				add(&cont);
				break;
			case DEL:
				del(&cont);
				break;
			case MOD:
				mod(&cont);
				break;
			case SEARCH:
				sel(&cont);
				break;
			case SHOW:
				show(&cont);
				break;
			case DISTORY:
				Distory(&cont);
				break;
			case QSORT:
				qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
				printf("排序成功\n");
				break;
			default:
				printf("输入错误");
				break;
			}
		} while (input);
		break;
	}
	
	return 0;
}


5.结语

有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/kingxzq/article/details/131723328