目录
为什么存在动态内存分配
在学这块知识以前,我们开辟内存的方式为直接定义变量,或者定义一个数组,这两种内存开辟的方式有个共同的缺点,就是开辟的内存空间是固定的,进而导致比较尴尬的情况:一是开辟的空间大了,浪费内存;二是开辟的空间小了,不够用。所以我们需要一种方法来解决这个问题,那就是动态内存分配。
动态内存函数的介绍
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++程序内存分配的几个区域:
柔性数组
定义
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++的封闭中的析构函数会让这事容易和干净很多)
第二个原因是,这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。
了解完了动态内存分配,我们来应用一下吧~
通讯录(动态内存版本)
通讯录的数组版本在我的上一篇文章里,数组版本对内存的利用比较死板,不够灵活,我们这次用动态内存分配来改善一下吧!
整段代码
主函数部分
#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个函数及其用法,柔性数组及其意义,动态内存版的通讯录。动态内存分配能够让我们实现按需分配(少了加,多了减),本文以动态内存版和大数组版的通讯录做了对比,体现出动态内存的优势。希望能给同志们一点帮助,下一篇便是文件版的通讯录,实现数据的存储。