【C语言】动态内存管理及通讯录(动态内存版)

目录

为什么存在动态内存分配

动态内存函数的介绍

malloc

定义

calloc 函数

定义

realloc函数

定义

free函数

定义

常见的使用动态内存的错误

对空指针的解引用操作(没有判断内存是否开辟成功)

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

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

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

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

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

C\C++内存的开辟 

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

 柔性数组

定义

特点

使用

意义

通讯录(动态内存版本)

整段代码

主函数部分

函数的实现部分

函数的声明部分

改动的部分

联系人添加函数的修改

大数组版:

动态内存版:

退出函数的修改

大数组版:

动态内存版:

Destroy函数的实现

contact结构体类型的修改

大数组版:

动态内存版:

初始化函数的修改 

 大数组版:

动态内存版:

新增加的内容 

销毁函数

扩容函数 


为什么存在动态内存分配

在学这块知识以前,我们开辟内存的方式为直接定义变量,或者定义一个数组,这两种内存开辟的方式有个共同的缺点,就是开辟的内存空间是固定的,进而导致比较尴尬的情况:一是开辟的空间大了,浪费内存;二是开辟的空间小了,不够用。所以我们需要一种方法来解决这个问题,那就是动态内存分配。

动态内存函数的介绍

malloc

定义

 

形参为要开辟的内存空间大小(字节),返回值有两种,当内存开辟成功时,返回的是所开辟内存空间的地址,当内存开辟失败的时候,返回的是空指针。这就要求我们,在使用malloc函数的时候,需要检查内存开辟是否成功。另外,由于返回值类型是void*,在实际应用过程中应该根据自身需求强制转换一下。例如:

开辟失败:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int *p=(int *)malloc(9999999999999999999);
	if (p == NULL)
	{
		printf("开辟失败");
	}
	else
	{
		printf("开辟成功");
	}
	return 0;
}

 

 开辟成功

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int *p=(int *)malloc(99);
	if (p == NULL)
	{
		printf("开辟失败");
	}
	else
	{
		printf("开辟成功");
	}
	return 0;
}

 

calloc 函数

定义

 

形参为(个数,该类型大小),该函数与malloc函数的区别就是,calloc会将所开辟的内存全部初始化成0。与malloc函数一样,使用的时候需要判断内存是否开辟成功,并且根据需求强制类型转换。

realloc函数

定义

形参为(需要重新分配的内存地址,重新分配的内存空间)上面两个函数在开辟是都是新开一个空间,realloc能实现已开辟空间的放大以及缩小。返回的是调整过后的内存地址。

不过调整也分成功与失败两种可能,调整成功的话,返回的便是调整的地址。调整失败了,realloc会在新的地方重新开辟一段空间,来满足调整需求,这时候返回的是新开辟的空间的地址。 

free函数

定义

 

没有返回值,形参是要释放的空间地址。使用完开辟的空间之后,要记得将内存释放,否则会造成内存泄漏。

free只能释放动态内存开辟的空间,不能释放数组这种。

常见的使用动态内存的错误

对空指针的解引用操作(没有判断内存是否开辟成功)

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;
 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);
}

C\C++内存的开辟 

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

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

 柔性数组

定义

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;

特点

一、结构中的柔性数组成员前面必须只要一个其他成员。

二、sizeof不计算该结构中柔性数组的内存

三、包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

使用

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);

100*sizeof(int)便是柔性数组预期的成员大小。

意义

为什么要用柔性数组呢?

原因是:我们想给一个结构体内的数据分配一个连续的内存。

这样做的意义有两个好处:

第一个意义是,方便内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。(读到这里,你一定会觉得C++的封闭中的析构函数会让这事容易和干净很多)

第二个原因是,这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。

了解完了动态内存分配,我们来应用一下吧~

通讯录(动态内存版本)

通讯录的数组版本在我的上一篇文章里,数组版本对内存的利用比较死板,不够灵活,我们这次用动态内存分配来改善一下吧!

整段代码

详细的思路参考我的上一篇文章:https://blog.csdn.net/ZHENGZJM/article/details/128511139?spm=1001.2014.3001.5501icon-default.png?t=MBR7https://blog.csdn.net/ZHENGZJM/article/details/128511139?spm=1001.2014.3001.5501

主函数部分

#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
int main()
{
	contact con;
	Init_contact(&con);
	int input = 0;
	do
	{
		//打印菜单
		menu_print();
		printf("请选择您需要的功能:>\n");
		scanf("%d", &input);
		menu_option(input, &con);
	} while (input);
	return 0;
}

函数的实现部分

#include"contact.h"
//扩容函数
void capacity_expansion(contact* p1) 
{
	perinf* tmp = (perinf*)realloc(p1->arr, (p1->num+default_inc)* sizeof(perinf));
	if (tmp != NULL)
	{
		p1->arr = tmp;
		p1->num+=2;
		printf("扩容成功!\n");
	}
	else
	{
		printf("扩容失败\n");
	}
}
//销毁函数
void Destroy(contact* p1)
{
	free(p1->arr);
	p1->arr = NULL;
	p1->sz = 0;
	p1->num = 0;
}
//初始化函数
void Init_contact(contact* p1)
{
	perinf* tmp= (perinf*)malloc(default_len *sizeof(perinf));
	if (tmp != NULL)
	{
		p1->arr = tmp;
	}
	else
	{
		printf("初始化内存开辟失败,请调整默认值");
	}
	p1->num = default_len;
	p1->sz = 0;
}
//打印菜单函数
void menu_print()
{
	printf("-------------------------------------\n");
	printf("=====================================\n");
	printf("=========1.添加      2.删除==========\n");
	printf("=========3.查询      4.修改==========\n");
	printf("=========5.排序      6.显示通讯录====\n");
	printf("=========      0.退出      ==========\n");
	printf("=====================================\n");
	printf("-------------------------------------\n");
}
//查找内的菜单函数
void menu_seek()
{
	printf("请选择要修改的内容\n");
	printf("-------------------------------------\n");
	printf("=====================================\n");
	printf("=========1.姓名      2.性别==========\n");
	printf("=========3.电话号码  4.家庭住址======\n");
	printf("=========5.年龄      0.退出修改======\n");
	printf("=====================================\n");
	printf("-------------------------------------\n");
}
//查找内的菜单选择函数
void seekmenu_option(int a, int ret, contact* p1)
{
	switch (a)
	{
	case NAME:
		printf("请输入新姓名\n");
		scanf("%s", p1->arr[ret].name);
		printf("修改成功\n");
		break;
	case SEX:
		printf("请输入新性别\n");
		scanf("%s", p1->arr[ret].sex);
		printf("修改成功\n");
		break;
	case TELE:
		printf("请输入新电话号码\n");
		scanf("%s", p1->arr[ret].tele);
		printf("修改成功\n");
		break;
	case ADDRESS:
		printf("请输入新家庭住址\n");
		scanf("%s", p1->arr[ret].address);
		printf("修改成功\n");
		break;
	case AGE:
		printf("请输入新年龄\n");
		scanf("%d", &p1->arr[ret].age);
		printf("修改成功\n");
		break;
	case EXIT1:
		printf("退出修改\n");
		break;
	default:
		printf("请选择正确的选项\n");
		break;
	}
}
//菜单选择函数
void menu_option(int input, contact* p1)
{
	switch (input)
	{
	case ADD:
		perinf_add(p1);
		break;
	case DEL:
		perinf_del(p1);
		break;
	case SEEK:
		perinf_SEEK(p1);
		break;
	case MODIFY:
		perinf_MODIFY(p1);
		break;
	case SORT:
		perinf_sort(p1);
		break;
	case DISPLAY:
		perinf_dispay(p1);
		break;
	case EXIT:
		Destroy(p1);
		printf("退出成功,感谢使用。\n祝您生活美满,家庭幸福,身体健康\n");
		break;
	default:
		printf("请重新选择正确的选项");
		break;
	}
}
//用户添加函数
void perinf_add(contact* p1)
{
	if (p1->sz == p1->num)
	{
		capacity_expansion(p1);
	}
	printf("请输入姓名:>\n");
	scanf("%s", p1->arr[p1->sz].name);
	printf("请输入性别:>\n");
	scanf("%s", p1->arr[p1->sz].sex);
	printf("请输入电话号码:>\n");
	scanf("%s", p1->arr[p1->sz].tele);
	printf("请输入年龄:>\n");
	scanf("%d", &(p1->arr[p1->sz].age));
	printf("请输入家庭住址:>\n");
	scanf("%s", p1->arr[p1->sz].address);
	p1->sz++;
	printf("添加成功QvQ\n");
	return;
}
//查找函数
int perinf_seek(contact* p1)
{
	int i = 0;
	char brr[MAX_NAME] = { 0 };
	printf("请输入要操作的人名:>\n");
	scanf("%s", brr);
	//遍历查找人名
	for (i = 0; i < p1->sz; i++)
	{
		if ((strcmp(p1->arr[i].name, brr) == 0))
		{
			return i;
		}
	}
	return -1;
}
//删除函数
void perinf_del(contact* p1)
{
	if (p1->sz == 0)
	{
		printf("当前通讯录尚未存入联系人,无法删除\n");
		return;
	}
	int ret = perinf_seek(p1);
	if (ret == -1)
	{
		printf("查无此人,无法删除\n");
		return;
	}
	else
	{
		memmove(&(p1->arr[ret]), &(p1->arr[ret + 1]), (long long)(p1->sz - ret) * sizeof(p1->arr[0]));
		printf("删除成功\n");
		p1->sz--;
		return;
	}
}
//查询函数
void perinf_SEEK(contact* p1)
{
	int ret = perinf_seek(p1);
	if (ret == -1)
	{
		printf("查无此人\n");
		return;
	}
	else
	{
		printf("%-20s\t%-20s\t%-20s\t%-20s\t%-20s\n", "姓名", "性别", "电话号码", "家庭住址", "年龄");
		printf("%-20s\t%-20s\t%-20s\t%-20s\t%-20d\n", p1->arr[ret].name, p1->arr[ret].sex, p1->arr[ret].tele, p1->arr[ret].address, p1->arr[ret].age);
		return;
	}
}
//修改函数
void perinf_MODIFY(contact* p1)
{
	int ret = perinf_seek(p1);
	if (ret == -1)
	{
		printf("查无此人\n");
		return;
	}
	else
	{
		int a = 0;
		do
		{
			menu_seek();
			scanf("%d", &a);
			seekmenu_option(a, ret, p1);
		} while (a);

	}
}
//显示整个通讯录函数
void perinf_dispay(contact* p1)
{
	int i = 0;
	printf("%-20s\t%-20s\t%-20s\t%-20s\t%-20s\n", "姓名", "性别", "电话号码", "家庭住址", "年龄");
	for (i = 0; i < p1->sz; i++)
	{
		printf("%-20s\t%-20s\t%-20s\t%-20s\t%-20d\n", p1->arr[i].name, p1->arr[i].sex, p1->arr[i].tele, p1->arr[i].address, p1->arr[i].age);
	}
}
//排序函数
void perinf_sort(contact* p1)
{
	int a = 0;
	menu_sort();
	printf("请选择排序方式\n");
	scanf("%d", &a);
	sortmenu_option(a, p1);
	printf("排序完成\n");
}
//排序菜单函数
void menu_sort()
{
	printf("请选择要修改的内容\n");
	printf("-------------------------------------\n");
	printf("=====================================\n");
	printf("===1.按名字排序       2.按年龄排序===\n");
	printf("===          0.退出排序           ===\n");
	printf("-------------------------------------\n");
}
//排序菜单选择函数
void sortmenu_option(int a, contact* p1)
{
	int i = 0; int j = 0;
	perinf tmp;
	switch (a)
	{
	case SORTBYNAME:
		for (i = 0; i < p1->sz; i++)
		{
			for (j = 0; j < p1->sz - 1; j++)
			{
				if (strcmp(p1->arr[j].name, p1->arr[j + 1].name) > 0)
				{
					tmp = p1->arr[j + 1];
					p1->arr[j + 1] = p1->arr[j];
					p1->arr[j] = tmp;
				}
			}
		}
		break;
	case SORTBYAGE:
		for (i = 0; i < p1->sz; i++)
		{
			for (j = 0; j < p1->sz - 1; j++)
			{
				if (p1->arr[j].age > p1->arr[j + 1].age)
				{
					tmp = p1->arr[j + 1];
					p1->arr[j + 1] = p1->arr[j];
					p1->arr[j] = tmp;
				}
			}
		}
		break;
	case EXIT2:
	{
		printf("退出排序\n");
	}
	break;
	default:
		printf("请选择正确的选项\n");
		break;
	}
}

函数的声明部分

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//重定义最大名字,最大性别,最大电话,最大
#define MAX_NAME 100
#define MAX_SEX 100
#define MAX_TELE 100
#define MAX_PERSON 1000
#define MAX_ADDRESS 100
#define default_len 1
#define default_inc 2 
//定以个人信息结构体
typedef struct perinf
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char address[MAX_ADDRESS];
	int age;
}perinf;
//定义通讯录类型
typedef struct contact
{
	perinf* arr;
	int sz;
	int num;
}contact;
//定义菜单枚举类型
enum menu
{
	EXIT,
	ADD,
	DEL,
	SEEK,
	MODIFY,
	SORT,
	DISPLAY
};
//定义查找内的菜单枚举类型
enum menu_seek
{
	EXIT1,
	NAME,
	SEX,
	TELE,
	ADDRESS,
	AGE
};
//定义排序菜单枚举类型
enum menu_sort
{
	EXIT2,
	SORTBYNAME,
	SORTBYAGE
};
//声明打印菜单函数
void menu_print();
//声明菜单选择函数
void menu_option(int input, contact* p1);
//声明初始化通讯录函数
void Init_contact(contact* p1);
//声明添加联系人函数
void perinf_add(contact* p1);
//声明删除联系人函数
void perinf_del(contact* p1);
//声明遍历对比函数
int perinf_seek(contact* p1);
//声明查询函数
void perinf_SEEK(contact* p1);
//声明查找内的菜单函数
void menu_seek();
//声明查找内的菜单选择函数
void seekmenu_option(int a, int ret, contact* p1);
//声明修改函数
void perinf_MODIFY(contact* p1);
//声明显示通讯录函数
void perinf_dispay(contact* p1);
//声明排序菜单选择函数
void sortmenu_option(int a, contact* p1);
//声明排序菜单函数
void menu_sort();
//声明排序函数
void perinf_sort(contact* p1);
//扩容函数的声明
void capacity_expansion(contact* p1);
//销毁函数的声明
void Destroy(contact* p1);

改动的部分

联系人添加函数的修改

大数组版:

//用户添加函数
void perinf_add(contact* p1)
{
	if (p1->sz == MAX_PERSON)
	{
		printf("通讯录已满,添加失败\n");
		return;
	}
	else
	{
		printf("请输入姓名:>\n");
		scanf("%s", p1->arr[p1->sz].name);
		printf("请输入性别:>\n");
		scanf("%s", p1->arr[p1->sz].sex);
		printf("请输入电话号码:>\n");
		scanf("%s", p1->arr[p1->sz].tele);
		printf("请输入年龄:>\n");
		scanf("%d",&(p1->arr[p1->sz].age));
		printf("请输入家庭住址:>\n");
		scanf("%s", p1->arr[p1->sz].address);
	}
	p1->sz++;
	printf("添加成功QvQ\n");
	return;
}

动态内存版:

void perinf_add(contact* p1)
{
	if (p1->sz == p1->num)
	{
		capacity_expansion(p1);
	}
	printf("请输入姓名:>\n");
	scanf("%s", p1->arr[p1->sz].name);
	printf("请输入性别:>\n");
	scanf("%s", p1->arr[p1->sz].sex);
	printf("请输入电话号码:>\n");
	scanf("%s", p1->arr[p1->sz].tele);
	printf("请输入年龄:>\n");
	scanf("%d", &(p1->arr[p1->sz].age));
	printf("请输入家庭住址:>\n");
	scanf("%s", p1->arr[p1->sz].address);
	p1->sz++;
	printf("添加成功QvQ\n");
	return;
}

退出函数的修改

大数组版:

case EXIT1:
		printf("退出修改\n");
		break;

动态内存版:

case EXIT:
		Destroy(p1);
		printf("退出成功,感谢使用。\n祝您生活美满,家庭幸福,身体健康\n");
		break;

Destroy函数的实现

//销毁函数
void Destroy(contact* p1)
{
	free(p1->arr);
	p1->arr = NULL;
	p1->sz = 0;
	p1->num = 0;
}

contact结构体类型的修改

大数组版:

//定义通讯录类型
typedef struct contact
{
	perinf arr[MAX_PERSON];
	int sz;
}contact;

动态内存版:

//定义通讯录类型
typedef struct contact
{
	perinf* arr;
	int sz;
	int num;
}contact;

初始化函数的修改 

 大数组版:

//初始化函数
void Init_contact(contact* p1)
{
	p1->sz = 0;
	memset(p1->arr, 0, sizeof(p1->arr));
}

动态内存版:

//初始化函数
void Init_contact(contact* p1)
{
	perinf* tmp= (perinf*)malloc(default_len *sizeof(perinf));
	if (tmp != NULL)
	{
		p1->arr = tmp;
	}
	else
	{
		printf("初始化内存开辟失败,请调整默认值");
	}
	p1->num = default_len;
	p1->sz = 0;
}

新增加的内容 

销毁函数

//销毁函数
void Destroy(contact* p1)
{
	free(p1->arr);
	p1->arr = NULL;
	p1->sz = 0;
	p1->num = 0;
}

扩容函数 

//扩容函数
void capacity_expansion(contact* p1) 
{
	perinf* tmp = (perinf*)realloc(p1->arr, (p1->num+default_inc)* sizeof(perinf));
	if (tmp != NULL)
	{
		p1->arr = tmp;
		p1->num+=2;
		printf("扩容成功!\n");
	}
	else
	{
		printf("扩容失败\n");
	}
}

总结

本文介绍了动态内存的4个函数及其用法,柔性数组及其意义,动态内存版的通讯录。动态内存分配能够让我们实现按需分配(少了加,多了减),本文以动态内存版和大数组版的通讯录做了对比,体现出动态内存的优势。希望能给同志们一点帮助,下一篇便是文件版的通讯录,实现数据的存储。

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值