一、动态内存管理的意义
在我们敲代码的时候,是否会有着这样的想法,为什么我创建的这个数组arr[10]它就只能存储10个元素呢,它为什么不能在程序的运行时跟随我们的需要,扩大它对应的空间呢?这样子不仅能够节省不必要的浪费,而且能够随着我们的需求弹性地满足我们。可能有的小伙伴不是很理解为什么会造成浪费,举个例子,我有一个能存放100个整型的数组,但我只放了10个元素进去,但计算机它已经把这400个字节的空间分配给了这个数组,这400个字节的空间在这个数组销毁之前,也就是走出它的生命周期前,这400字节的空间只能它用,别人都用不了,这就造成了浪费,
二、动态内存管理函数
1.malloc和free
这两个函数,分开来讲太割裂了,于是这里笔者就把它们放在一起
1.1认识malloc和free
关于函数定义的图片均出自cplusplus.com - The C++ Resources Network
从返回类型来看,malloc的返回类型是空指针,也就是说我们在接收它的时候很可能要用到强制类型转换,从参数来看,是一个无符号整型
从返回类型来看free的返回类型为空类型,也就是说,free不返回任何值回来,不可使用链式法则,从参数来看,是个无类型的指针
1.2malloc和free的作用
malloc函数的作用是分配所需的内存空间,并返回一个指向它的空类型的指针,这个内存空间的大小跟你传给它的参数大小一致,注意:开辟空间的单位是字节。
free的用法则是释放之前申请的动态内存空间,注意,是动态内存空间,也就是说,free只能释放动态内存函数开辟的空间
1.3使用malloc和free时应注意的事项
1.3.1使用malloc注意事项
malloc申请下来的空间,只有两种情况会返回给操作系统,第一种就是程序结束了,第二种就是程序设计者在用完这个malloc申请下来的空间后使用free将其释放掉了。有联想能力的小伙伴恐怕就想到了,那如果我写了个循环一直在开辟空间,且我通过特殊的方式让程序无法结束,那是否会占用计算机的大量内存,是的,这可能会使计算机没法正常工作。因此,在使用malloc的时候我们应该秉承着用完就释放的原则 这样不仅能大大提高计算机的工作效率,还能增加内存空间的利用率。还应注意的一点,那便是malloc函数开辟空间是有可能失败的 毕竟计算机的空间不是无穷无尽的,当你开辟的空间过大时,计算机没法提供,就malloc就会返回一个空指针(NULL)
1.3.2使用free注意事项
(1)不要对同一块空间多次释放,因为当你释放完这一块当时开辟的空间后,后面经过一系列的操作,计算机可能已经把这一块空间使用到了,而你又对它进行了一次释放,很可能就会出现未知的问题。
(2)free只能够释放动态内存函数开辟的空间
(3)使用free释放一部分动态内存空间也是有问题的。什么是只释放一部分,上个代码你就知道了
#include<stdio.h> #include<stdlib.h> int main() { int* a = (int*)malloc(100); //创建1个大小为100个字节的空间,并用a来接收 a++;//改变地址,使其指向首地址的下一个地址 free(a);//不再指向动态内存的起始地址,还有一个字节没被释放 }
1.4使用free和malloc函数
#include<stdio.h> #include<stdlib.h>//malloc,free所在的头文件 int main() { int* a = NULL;//初始化指针 int num = 0; printf("你想要一个多大的数组\n"); scanf("%d", &num); a= (int*)malloc((sizeof(int)) * num); //malloc返回的值类型为无符号指针,因此在用a接收的时候需要将其强制类型转换为同一类型 int i = 0; if (a == NULL)//避免开辟空间失败 { perror("malloc");//perror函数的作用是提示你所犯的错误 return 0;//开辟空间失败,中止程序 } for (i = 0; i < num; i++) { a[i] = i;//以数组的方式给开辟的内容赋值 } for (i = 0; i < num; i++) { printf("%d ", a[i]);//打印出数组内容 if ((i+1)% 10 == 0)//没10个换一次行,美观 { printf("\n"); } } free(a);//使用free释放掉之前申请的空间 a = NULL; //此时a是野指针,因此将a重新变为空指针,避免后面再使用的时候出现问题 }
开辟成功:
开辟失败:
2.calloc
2.1认识calloc
calloc函数的返回类型为空指针 因此在接收它的时候我们应该要使用强制类型转换才能接受 有两个参数,均是无符号整型
2.2calloc的作用
calloc的作用是开辟num个size大小的动态内存空间,并将里面的内容初始化为0 从这里看,它与malloc的作用几乎是一模一样,只是多了一步将内容初始化为0,不过讲真的,即使calloc不比malloc多这一步,
我也更愿意使用calloc而不是malloc,因为calloc函数的两个参数可以让你很好的知道你当时开辟这个空间的用途,可以大大提高代码的可读性。注意:开辟空间的单位一样是字节
2.3使用calloc
#include<stdio.h> #include<stdlib.h> int main() { int* a = (int*)calloc(100,sizeof(int)); //创建了一个大小为100个整型的空间,并用a来接收 if (a == NULL)//避免开辟失败 { perrof("calloc");//报相应错误 return 0;//开辟失败,中止程序 } free(a);//释放开辟的空间 a = NULL;//a现在是野指针,将其变为空指针 }
3.realloc
3.1认识realloc
realloc函数的返回类型为空指针,因此我们在接收它返回的地址时要用到强制类型转换,将其转换为我们需要的类型。realloc函数有两个参数,一个是无类型的指针变量,一个是无符号整型
3.2realloc的用法
realloc可以对给定指针所给的空间进行扩大或缩小 ptr为你所给的指针,size为目标空间被操作完后的大小。这个函数调整完大小之后会将之前在这个空间里存储的数据再存放到这个新的空间,当然如果你是缩小空间的话,可能会出现数据丢失。注意:扩大或缩小空间的大小的单位也是字节
3.3realloc函数扩大空间的多种情况
情况1:
以这张图来举例,假设我已经开辟了100个字节的空间,后面剩余的空间已经只剩下50个,而我现在想要再开辟30个字节的空间,也就是将原来100个字节的空间扩大到130个字节。而realloc一看,后面还有50呢,随便开辟,没事,大方的很,那么此时就会直接在原有数据之后直接追加
情况2:
那么假设我想把这100个字节的空间开辟成200个字节的怎么办呢,realloc一看,后面的空间不够大了啊,总共就150㎡的房子,给你住满了,realloc总不可能把隔壁人家的房子给你敲掉让你住不是,它只能够再给你找你个新的更大的房子,也就是在内存中寻找到足够的空间,重新开辟一个空间给你,而之前的旧的空间它会自动帮你释放掉,不用手动释放
情况3:
计算机内存不够,不能开辟这么大的空间,那就会开辟失败,返回一个空指针给你。
3.4使用realloc
#include<stdio.h> #include<stdlib.h> int main() { int*a=(int*)malloc(100); int num = 0; printf("修改之前a的地址%p\n", a); printf("你要多大的空间\n"); scanf("%d", &num); //输入想要的空间大小 a=realloc(a,num); if (a == NULL)//避免开辟失败 { perror("realloc");//报错 return 0;//中止程序 } printf("修改成功,a的地址为%p\n",a); free(a);//释放开辟的空间 a = NULL; //a此时是野指针,安全起见,给它变为空指针 }
后面的空间充足,地址不变
后面的空间不足,地址改变
计算机没法给那么大的空间
三、柔性数组
顾名思义,柔性数组就是可以变换大小的数组,在c++上面数组可以传变量,而在c上数组只能传常量,我们要怎么开辟柔性数组呢?
1.柔性数组的特点
(1)开辟的数组前至少要有一个结构体成员
(2)sizeof计算柔性数组所在的结构体时,柔性数组不会被计算在其中
(3)包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
2.柔性数组的开辟和使用
#include<stdio.h> #include<stdlib.h> //struct abc //{ // int a; // int b[0]; //}; struct abc //有的编译器只能够使用上面的方法,但这两个至少有一个可以 { int a; int b[]; }; int main() { struct abc* x = NULL; x = (struct abc*)malloc(sizeof(struct abc) + 40); //创建一个字节数为40即10个字节的数组 if (x == NULL) { perror("malloc");//报错提示 return 0; } int i = 0; for (i = 0; i < 10; i++) { x->b[i] = i;//将可以存放10个元素的数组初始化 } for (i = 0; i < 10; i++) { printf("%d ", x->b[i]);//打印出来 } x = realloc(x,104); //4个字节的空间是给a的,100个字节的空间是给b的,25个字节 if (x == NULL) { perror("realloc");//报错提示 return 0; } printf("\n"); for (i = 0; i < 25; i++) { x->b[i] = i;//将可以存放25元素的数组初始化 } for (i = 0; i < 25; i++) { printf("%d ", x->b[i]);//打印出来 } free(x);//释放x的空间 x = NULL; //将野指针置为空指针,避免在后面使用 }
3.指针可以替代柔性数组的作用
#include<stdio.h> #include<stdlib.h> struct abc { int a; int *b;//通过指针实现柔性数组 }; int main() { struct abc* x = NULL; x = (struct abc*)malloc(sizeof(struct abc)); if (x == NULL) { perror("malloc");//报错提示 return 0; } x->b=malloc(40); if (x == NULL) { perror("malloc");//报错提示 return 0; } int i = 0; for (i = 0; i < 10; i++) { x->b[i]= i;//将可以存放10个元素的数组初始化 } for (i = 0; i < 10; i++) { printf("%d ", x->b[i]);//打印出来 } printf("\n"); x->b=realloc(x->b, 60); if (x->b==NULL) { perror("realloc");//报错提示 return 0; } for (i = 0; i < 15; i++) { x->b[i] = i;//将可以存放15个元素的数组初始化 } for (i = 0; i < 15; i++) { printf("%d ", x->b[i]);//打印出来 } free(x->b); x->b = NULL; free(x); x = NULL; }
4.柔性数组的优势
方便内存释放,用指针得两次,柔性数组一次就搞定。访问速度快,因为空间连续。
好了,今天的分享就到这里结束了,感谢各位友友的来访,祝各位友友前程似锦O(∩_∩)O