C语言学习笔记(13)
1. 动态内存分配
先来介绍三个动态内存分配的函数:malloc,calloc和realloc。说来惭愧,以前只知道malloc。现在来看下他们的区别:
malloc:最常用的分配内存块,但是不对内存进行初始化。
calloc:分配内存块,但是对内存块进行清零操作,这就造成此函数的效率要比malloc要低。
realloc:调整(增加或者减少)之前分配内存块的大小。
由于上面的函数只是开辟了一段内存,因此无法知道你要利用这段内存来存储什么类型的数据,因此只是返回一个void *类型的值,当然,void *可以和任何指针类型互相转换。
当分配内存失败时(可能是内存不足或者其他原因),以上的函数都会返回一个空指针,那么我们安全期间,在我们使用这块分配的内存前,都应该进行一次空指针的验证。
int main (void) { int *p; p=(int *)malloc(sizeof(*p)*1000); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
注意上面的两点,一个是空指针的验证,另外一个是分配内存的大小,这里我们常常是用类型的大小乘以存储成员的数量来计算分配。如果是字符串,我们就不要忘记了加1,来存储\0。
#include <stdio.h> #include <stdlib.h> int main (void) { char *p; p=(char *)malloc(sizeof(*p)*1000+1); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
接下来我们看一下calloc元素的原型:
void * __cdecl calloc(_In_ size_t _Count, _In_ size_t _Size);
从上面我们可以看到calloc函数有两个参数,分配是,数量和大小。由此可以说明,calloc是C语言用来分配数组空间最好的选择。那么我们就把第一段代码改一下:
int main (void) { int *p; p=(int *)calloc(1000,sizeof(int)); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
这样就更合适了。
最后是realloc,我们也来看一下realloc元素的原型:
void * __cdecl realloc(_Post_ptr_invalid_ void * _Memory, _In_ size_t _NewSize);
当我们之前分配了一个数组的大小,但是后来我们却发现这个大小不够用了,或者是太大了,那么我们就可以利用realloc来调整我们的占用内存大小:
int main (void) { int *p; p=(int *)calloc(1000,sizeof(int)); if(p==NULL) { exit(EXIT_FAILURE); } realloc(p,sizeof(int)*100); if(p==NULL) { exit(EXIT_FAILURE); } printf("success"); return 0; }
也别忘了检验p是否为空指针的情况。
在C标准中,并没有对realloc的实现做以规定,但是对于大部分编译器来说,如果是把原地址空间缩小,他会尽量地不去移动原来的数据。如果是把空间增大,那么他会尽量首先在原地址的末尾去分配内存,如果不足以分配,那么编译器才会去寻找新的地址块,并且把原地址空间内的数据转移到新的地址上。
2. 释放空间
习惯了Java/C#的我们,似乎已经忘记了要回收垃圾的习惯,在C/C++中,是没有GC的,因此我们要记得,当我们在堆上分配了一块内存,并且不在使用时,我们要使用free函数来释放掉空间。看下free的原型:
void __cdecl free(_Post_ptr_invalid_ void * _Memory);
很简单,不再赘述。
3. 指向指针的指针
在读大学时,我一直对这个概念不是很理解,现在我更愿意这样去理解指针。
当我们声明了int *p=malloc(1000)的时候,我们可以这样来理解:
其实我更愿意把p就理解成一个地址的值,p=0x1111(0x1111是分配的1000字节内存的首地址)。那么什么是指向指针的指针呢?
这里的q就是指向指针的指针,q的值就是0x0004,也就是p所在的地址。
以此类推,我们还可以知道指向指针的指针的指针。
4. 函数指针
我们来看C语言里提供了qsort函数:
_CRTIMP void __cdecl qsort(_Inout_bytecap_x_(_NumOfElements * _SizeOfElements) void * _Base, _In_ size_t _NumOfElements, _In_ size_t _SizeOfElements, _In_ int (__cdecl * _PtFuncCompare)(const void *, const void *));
最后一个参数就是一个函数指针,其实不用这么麻烦,我们来看个简单的函数指针的原型:
double (*function)(int);
这个就是最简单的函数指针的原型,与返回指针类型的函数相比,他们相差的只是*和函数名之间要用括号括起来。
当传进来一个函数指针时,我们便可以在函数中适用这个传进来的参数(函数指针)了。例如在qsort里,我们便可以自己制定比较规则,不再多说。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· [AI/GPT/综述] AI Agent的设计模式综述
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!