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

news2024/11/23 9:32:16

在这里插入图片描述

通讯录的实现及动态内存管理

  • 1.通讯录实现要求
  • 2.静态版通讯录实现
    • 2.1 contact.h文件实现
    • 2.2 contact.c文件实现
    • 2.3 main.c文件实现
    • 2.4 静态版通讯录全部文件代码
  • 3.动态内存管理
    • 3.1 为什么存在动态内存分配
    • 3.2 动态内存函数的介绍
    • 3.3 常见的动态内存错误
    • 3.4 C/C++程序的内存开辟
    • 3.5 柔性数组
  • 4.动态版通讯录实现
    • 4.1 contact.h文件改动
    • 4.2 contact.c文件改动
    • 4.3 main.c文件改动
    • 4.4 动态版通讯录全部文件代码
  • 5.结语

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们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!
在这里插入图片描述

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

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

相关文章

海盗搜身-2022年全国青少年信息素养大赛Python国赛第9题

[导读]&#xff1a;超平老师计划推出《全国青少年信息素养大赛Python编程真题解析》50讲&#xff0c;这是超平老师解读Python编程挑战赛真题系列的第11讲。 全国青少年信息素养大赛&#xff08;原全国青少年电子信息智能创新大赛&#xff09;是“世界机器人大会青少年机器人设…

vue3+ts+element-plus管理系统实际开发业务之增删改查

文章目录 功能一&#xff1a; 实现一个表格增、删、改、查功能✏️ 1. 表格、添加按钮、删除按钮、搜索框和搜索按钮、编辑按钮添加&#x1f4d2; 运行后页面效果&#xff1a;如下 ✏️ 2. 使用添加按钮添加一条新纪录&#xff0c;逻辑点击按钮弹出带有表单的弹窗进行输入添加。…

Elasticsearch--查询(nested、join)

nested 嵌套类型 数据的某个值是json、object对象&#xff1b;不再是简单的数据类型&#xff0c;或者简单数据类型的数组&#xff1b;那么还用之前的查询方式就有问题了。因为ES在存储复杂类型的时候会把对象的复杂层次结果扁平化为一个键值对列表 。此时&#xff0c;需要用n…

一文了解OLED拼接屏优点

随着科技的不断发展&#xff0c;显示技术也在不断地更新换代。其中&#xff0c;OLED技术是目前最为先进的显示技术之一。 OLED拼接屏是一种利用OLED技术制作的大屏幕显示设备&#xff0c;可以将多个OLED屏幕拼接在一起&#xff0c;形成一个更大的显示屏幕。济南OLED拼接屏是一…

分布式锁的实现方式

文章目录 一、分布式锁概述1.1 为什么需要分布式锁1.2 概述分布式锁1.3 分布式锁的特性1.4 分布式锁的类型1.5 实现重点 二、Mysql数据库实现分布式锁2.1 表结构2.2 加锁2.3 解锁2.4 锁超时2.5 实现重入锁 三、Redis实现分布式锁3.1 加锁3.2 解锁3.3 锁超时3.4 redlock的容错性…

设计模式-简单工厂模式

文章目录 简单工厂设计模式什么是简单工厂?为什么使用简单工厂工厂模式代码实现简单工厂优缺点优点&#xff1a; 简单工厂设计模式 学习视频 什么是简单工厂? 简单工厂模式属于类的创建型模式&#xff0c;又叫做静态工厂方法模式。通过专门定义一个类来负责创建其他类的实…

GitLab CICD Day 08 - 环境变量

1.局部/全局环境变量 stages:- testing # stage编排- build- deployvariables:global_var: "全部变量" #全部变量build_image:stage: buildvariables: #局部环境变量my_name: "局部环境变量" tags:- shell script:- …

14款奔驰R400升级ACC自适应巡航系统,增加您的行车安全性

有的时候你是否厌倦了不停的刹车、加油&#xff1f;是不是讨厌急刹车&#xff0c;为掌握不好车距而烦恼&#xff1f;如果是这样&#xff0c;那么就升级奔驰原厂ACC自适应式巡航控制系统&#xff0c;带排队自动辅助和行车距离警报功能&#xff0c;感受现代科技带给你的舒适安全和…

window.getComputedStyle

遇见一个问题&#xff0c;一个元素样式用的 固定定位。但是 top 属性没有在 元素树&#x1f332;种显示。js获取不到。这个方法可以获取到 style 里面设置的样式

OI中好用的技巧——c++快读快写

在写一些题时&#xff0c;发现不对读写进行处理会 TLE or WA 此时就需要降低读写的时间 c读写速度 &#xff1a; 普通写法 < scanf和printf写法 ≈ 缓存禁用写法 < 字符处理写法 普通写法&#xff08;读写最慢&#xff09; #include<iostream> using namespace…

车载调频发射机-德阳广播电台应用机关车队车载电台移动解说系统

车载调频发射机-德阳广播电台应用机关车队车载电台移动解说系统 北京海特伟业科技任洪卓发布于2023年7月13日 一、车载调频发射机-机关车队车载电台移动解说系统用户需求 机关车队主要承担领导干部公务用车以及重要会议、公务接待、调研应急等公务用车的服务保障工作。为认真…

前段开发概述

目录 网站概述 网站&#xff08;web site&#xff09;&#xff1a; 网页概述 网站开发流程&#xff1a; HTML基本概念 开发工具的选择 网站概述 网站&#xff08;web site&#xff09;&#xff1a; 按照一定规则&#xff0c;使用HTML超文本标记语言等工具制作的。用于展示…

FPGA——静态数码管

文章目录 一、实验环境二、实验原理三、实验任务四、实验过程4.1 time_count模块4.2 seg_led_static模块4.3 top_seg_led_static模块4.4 引脚配置 五、仿真5.1 仿真代码5.2 仿真结果 六、实验结果七、总结 一、实验环境 quartus 18.1 modelsim vscode Cyclone IV开发板 二、实…

大坝安全监测中需要做好检查监测

大坝安全监测是人们了解大坝运行状态和安全状况的有效手段和方法。它的目的主要是了解大坝安全状况及其发展态势&#xff0c;是一个包括由获取各种环境、水文、结构、安全信息到经过识别、计算、判断等步骤&#xff0c;最终给出一个大坝安全 程度的全过程。 此过程包括&#xf…

阿里云无影云电脑和服务器有啥区别?

阿里云无影云电脑和云服务器有什么区别&#xff1f;云电脑是作为个人或企业办公电脑使用&#xff0c;云服务器是对外提供24小时高可用服务&#xff0c;云电脑是桌面服务&#xff0c;云服务器是提供背后的计算服务&#xff0c;阿里云百科分享阿里云无影云电脑和云服务器的区别&a…

互联网医院源码|互联网医院系统源码|互联网医院诊疗系统

互联网医院系统开发可以提供许多好用的功能&#xff0c;以下是一些常见的功能&#xff1a;   在线挂号预约&#xff1a;用户可以通过系统在线选择医生、科室和就诊时间&#xff0c;并进行挂号预约&#xff0c;避免了传统排队等候的麻烦。   问诊咨询&#xff1a;用户可以通…

Fiddler 抓包工具 手机抓包配置

1. 下载Fiddler 工具阿里云盘分享 2. 安装后进行设置 Tools -->Options 这些设置完后开始手机WLAN 设置 1. 打开手机的“设置” ->“WLAN”&#xff0c;找到你要连接的网络&#xff0c;在上面长按&#xff0c;然后选择“修改网络”&#xff0c;弹出网络设置对话框&…

探索Web自动化测试工具的特点

Web应用程序的快速发展使得自动化测试在软件开发生命周期中变得至关重要。Web自动化测试工具为开发人员和测试人员提供了一种高效、准确且可重复的方法来验证Web应用程序的功能和稳定性&#xff0c;我们一起来探索Web自动化测试工具的特点。 1.多浏览器兼容性&#xff1a; Web自…

Notes中使用邮件合并功能

大家好&#xff0c;才是真的好。 很久很久以前&#xff0c;就实现了Notes客户机的邮件合并功能&#xff0c;老实说&#xff0c;早得我都忘记当时是如何实现的了。 对了&#xff0c;我记起来一点&#xff0c;就是Excel 2003和之前的版本&#xff0c;还可以导出为Lotus 1-2-3格…

< 每日算法 - JavaScript解析:跳跃游戏 Ⅰ/ Ⅱ - 贪心 >

每日算法 - JavaScript解析&#xff1a;跳跃游戏 Ⅰ/ Ⅱ - 贪心 跳跃游戏 Ⅰ① 任务描述&#xff1a;> 示例一> 示例二 ② 题意解析③ 解决方案&#xff1a; 跳跃游戏 Ⅱ① 任务描述&#xff1a;> 示例一> 示例二 ② 题意解析③ 解决方案 往期内容 &#x1f4a8; 跳…