我们已经掌握的内存开辟方式有:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {
0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。
函数原型,头文件stdlib.h
void *malloc( size_t size );
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
代码示例
#include
#include
int main()
{
int arr[10] = {
0 };//在栈区上开辟40个字节
int* p = (int*)malloc(40);//动态内存开辟的空间在堆区上
if (p == NULL) //判断是否为空指针,如果是空指针,则空间开辟失败
{
//开辟空间失败。
printf("动态内存开辟失败");
return 1;//开辟失败提前返回
}
else
{
//开辟空间成功,使用空间
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 0;
}
}
return 0;
}
根据上面代码我们调试看到,开辟成功后p是指向这块动态内存的,并且内存放的是随机值
我们可以将它看作一个数组,解引用p+i来使用这段内存。
函数原型,头文件stdlib.h
void free (void* ptr);
代码示例
#include
#include
int main()
{
int* ptr = NULL;
int num = 0;
scanf("%d", & num);
ptr = (int*)malloc(num * sizeof(int));
if (NULL != ptr)//判断ptr指针是否为空
{
int i = 0;
for (i = 0; i < num; i++)
{
*(ptr + i) = 0;
}
}
//当不在使用ptr指向的动态内存时需要释放动态内存
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;//是否有必要?
return 0;
}
关于最后ptr置NULL,ptr指向的空间被释放后,ptr成了一个野指针,这时如果不小心使用了ptr指向的空间,后果是不可知,所以说ptr的指向的空间被释放后,就没有打算在使用ptr这个指针了,不管是为了方便也好,安全也罢,这里都推荐动态内存释放后,指针置NULL。
切记: 动态开辟的空间一定要释放,并且正确释放 。忘记释放不再使用的动态开辟的空间会造成内存泄漏。
函数原型
void* calloc (size_t num, size_t size);
使用方法
#include
#include
int main()
{
int* p = (int*)calloc(10, sizeof(int));//开辟十个整型大小
if (p != NULL)//判断是否开辟失败
{
//使用空间
}
free(p);
p = NULL;
return 0;
}
其实calloc函数跟malloc函数没多大区别,唯一的区别就是,calloc内存开辟成功后会把内存初始化为0。
所以以后碰到动态开辟内存,如果内存需要初始化就用calloc,不需要初始化就用malloc。
函数原型
void* realloc (void* ptr, size_t size);
代码示例
#include
#include
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
//开辟空间失败。
printf("动态内存开辟失败");
return 1;//开辟失败提前返回
}
else
{
//开辟空间成功,使用空间
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
}
//追加空间
int* pa = (int*)realloc(p, 80);
if (pa == NULL)//调整空间失败
{
return 1;
}
else
{
p = pa;
//使用调整空间
for (int i = 10; i < 20; i++)
{
*(p + i) = i;
}
}
//释放空间
free(p);
p = NULL;
return 0;
}
realloc在调整内存空间的是存在两种情况
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。 由于上述的两种情况,realloc函数的使用就要注意一些。
示例
#include
#include
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
//开辟空间失败。
printf("动态内存开辟失败");
return 1;//开辟失败提前返回
}
else
{
}
int* pa = (int*)realloc(p, 80);//使用辅助指针接受调整的空间
if (pa == NULL)//判断调整空间是否失败
{
return 1;
}
else
{
p = pa;//调整空间成功后在把指向的空间赋给原指针
}
//释放空间
free(p);
p = NULL;
return 0;
}
使用realloc的时,需注意,调整空间最好使用一个辅助指针来接受,调整成功在把辅助指针指向的空间赋给原指针,因为如果用原指针直接接受调整空间的话,万一调整失败,原指针被置为NULL,这样就找不到原先开辟好的那段内存了。
上次写了一篇静态版通讯录的博客,在学习动态内存开辟后就可以来改造一下通讯录了,我们来看看动态版通讯录有哪些改动。
没看过静态版通讯录的读者可以先去我这篇博客了解一下静态版通讯录
改动一: 通讯录结构体
静态版
struct Contact
{
struct PeoInfo data[DATA_MAX];
int sz;//记录通讯录中里有几个人的信息
};
静态版使用了固定大小的内存
动态版改动
//通讯录的结构体
struct Contact
{
struct PeoInfo* data;
int sz;//记录通讯录中里有几个人的信息
int space;//当前最大内存
};
把数组改成指针方便接受开辟的动态内存,以及增加了一个space的变量,记录当前的最大内存是多少。
改动二:初始化通讯录
静态版
//初始化通讯录
void InitContact(struct Contact* con)
{
con->sz = 0;
//memset -设置内存的函数
memset(con->data, 0, sizeof(con->data));
}
静态版把数组的所有元素置0。
动态版改动
#define MEMORY_SZ 3 //初始内存
void InitContact(struct Contact* con)
{
con->sz = 0;
con->space = MEMORY_SZ;
struct PeoInfo* tmp = (struct PeoInfo*)malloc(MEMORY_SZ * sizeof(struct PeoInfo));
if (tmp == NULL)//判断是否开辟失败
{
printf("%s\n", strerror(errno));
exit(1);
}
else
{
con->data = tmp;
}
}
在初始通讯录的时候设定最大内存为3(自己想设多少都行),使用通讯录指针开辟3个存放联系人的空间。
改动三:增加联系人
静态版
//增加联系人
void Addcontact(struct Contact* con)
{
if (con->sz == DATA_MAX)
{
printf("通讯录已满,无法继续添加\n");
}
else
{
printf("添加联系人\n");
printf("请输入姓名:");
scanf("%s", con->data[con->sz].name);
printf("请输入年龄:");
scanf("%d", &con->data[con->sz].age);
printf("请输入性别:");
scanf("%s", con->data[con->sz].sex);
printf("请输入电话:");
scanf("%s", con->data[con->sz].tele);
printf("请输入地址:");
scanf("%s", con->data[con->sz].addr);
con->sz++;
printf("添加成功\n");
}
}
静态版添加联系人,假设满人了则无法继续添加
动态版改动
//增加联系人
void Addcontact(struct Contact* con)
{
if (con->sz == con->space)
{
struct PeoInfo* tmp = realloc(con->data,(con->space + 2) * sizeof(struct PeoInfo));//使用辅助指针接受调整的空间
if (tmp == NULL)//判断增容是否失败
{
printf("%s\n", strerror(errno));
return;
}
else
{
con->data = tmp;
con->space += 2;
printf("增容成功\n");//增容成功打印提示一下
}
}
printf("添加联系人\n");
printf("请输入姓名:");
scanf("%s", con->data[con->sz].name);
printf("请输入年龄:");
scanf("%d", &con->data[con->sz].age);
printf("请输入性别:");
scanf("%s", con->data[con->sz].sex);
printf("请输入电话:");
scanf("%s", con->data[con->sz].tele);
printf("请输入地址:");
scanf("%s", con->data[con->sz].addr);
con->sz++;
printf("添加成功\n");
}
动态版如果达到了最大内存,就使用realloc增加空间,增加多少空间看需求,我这里是增加的2个联系人的空间方便调试,并且把最大内存加2。
运行截图
emmmm,动态版通讯录就改动了这些东西,以及添加了退出程序的时候释放动态内存。
void FreeMemory(struct Contact* con)//释放开辟的内存
{
free(con->data);
con->data = NULL;
con->space = 0;
con->sz = 0;
}
释放动态开辟的内存,并把指针置NULL。
动态版通讯录可以按需调整空间大小,相比静态版通讯录而言,更能合理的运用内存。
动态版缺点就是无法保存联系人信息到一个文件当中去,每次执行程序都需要重新录入联系人。
文件操作可以弥补动态通讯录的缺点,保存在文件当中,下篇博客我将会讲文件版通讯录改造。