1 指针的本质
1.1-指针的定义
- 如果在程序中定义了一个变量,那么在对程序进行编译时,系统就会给这个变量分配内存单元。在C语言中,指针变量是一种特殊的变量,它用来存放变量地址。指针变量的定义格式如下:
基类型 *指针变量名
。
另外注意:指针变量加1后,偏移的长度是其基类型的长度,例如int *p;
,整型指针变量p,p+1
是偏移sizeof(int)
。
- 按变量地址存取变量值的方式称为直接访问,如
printf("%d\n", i); scanf("%d", &i);
等。
- 将变量i的地址存放到另一个变量中,这一种存取变量值的方式称为间接访问。
- 指针与指针变量是两个概念,一个变量的地址称为该变量的“指针”。如果有一个变量专门用来存放另一个变量的地址(即指针),那么称它为“指针变量”。
1.2-取地址操作符与取值操作符
- 取地址操作符为&,也称引用,通过该操作符我们可以获取一个变量的地址值。
- 取值操作符为*,也称解引用,通过该操作符我们可以得到一个地址对应的数据。
| #include <stdio.h> |
| |
| int main() |
| { |
| int i = 10; |
| |
| int *p; |
| p = &i; |
| printf("i=%d\n",i); |
| printf("*p=%d\n",*p); |
| } |
2 指针的使用场景
指针的使用场景只有两个,即传递和偏移,记住只有在这两种场景下使用指针。
2.1-指针的传递
- C语言的函数调用均为值传递,所以为了能够在子函数中修改main函数内某个变量的值,可以传递该变量的地址。
2.2-指针的偏移
| #include <stdio.h> |
| |
| int main() |
| { |
| int a[5] = { 1,2,3,4,5 }; |
| |
| int* p; |
| p = a; |
| printf("*p=%d\n", *p); |
| for (int i = 0; i < 5; i++) |
| { |
| printf("%d\n", *(p + i)); |
| } |
| return 0; |
| } |
指针变量加1后,偏移的长度是其基类型的长度,如上述例子中,(p+0)即a[0],(p+1)即a[1],加括号是为了运算符的优先级原因。
2.3-指针与自增、自减运算符
- 当遇到后增
++
运算符时,就要分两步来看。第一步是暂时忽略++操作。例如int *p; int j=*p++;
,先看j = *p
。第二步是对p进行++操作。这里注意是对p进行++操作,因为*操作符和++操作符的优先级相同,只有比++
优先级更高的操作符才会当成一个整体,比如()
、[]
。
2.4-指针与一维数组
- 当数组名作为实参传递给子函数时,是弱化为指针的。一维数组的数组名中存储的是数组的首地址,所以传递到子函数时,高手形参写的是
char *d
而不是char d[]
,因为更符合C语言值传递的原理,并且能少打一个字符。
| #include <stdio.h> |
| |
| |
| |
| void change(char *d) |
| { |
| *d = 'H'; |
| d[1] = 'E'; |
| *(d + 2) = 'L'; |
| } |
| int main() |
| { |
| char c[10] = "hello"; |
| change(c); |
| puts(c); |
| return 0; |
| } |
2.5-指针与动态内存申请
2.5.1-动态内存申请
- C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的,如果使用的空间大小不确定,那么就要使用堆空间,如下所示。
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| |
| int main() |
| { |
| int i; |
| scanf("%d", &i); |
| char *p; |
| |
| p = (char*)malloc(i); |
| |
| |
| strcpy(p,"malloc success"); |
| puts(p); |
| |
| free(p); |
| printf("free success\n"); |
| |
| return 0; |
| } |
- 通过单步调试,在
free(p);
之前设置断点,可以看到free函数执行之后,p的值并没有改变,仍然是堆空间的这个地址,但是空间中的值被清空了。这时,p指针被称为野指针,为后续代码执行正确,最好加上p = NULL;
,把p的值清空。
- 首先看malloc函数。在执行
#include <stdlib.h> void* malloc(size_t size);
时,需要给malloc传递的参数是一个整型变量,因为这里的size_t
即为int;返回值为void*
类型的指针,因为malloc并不知道我们申请的空间用来存放什么类型的数据,所以确定要用来存储什么类型的数据后,都会将void*强制转换为对应的类型。
void
和void*
的区别:void表示什么都不返回,void*表示一个空指针,用来存储一个地址,可强制转换为对应类型。
- 栈空间由系统自动管理,而堆空间的申请和释放需要自行管理,所以在具体例子中需要通过free函数释放堆空间。free函数的头文件及格式为
| #include <stdlib.h> |
| void free(void *ptr); |
- 其传入的参数是void类型的指针,任何指针均可自动转为void*类型指针,所以我们把p传递给free函数时,不需要强制类型转换。
- p的地址值必须是malloc当时返回的地址值,不能进行偏移,也就是在malloc和free之间不能进行
p++
等改变变量p的操作,原因是申请一段堆内存空间时,内核帮我们记录的是起始地址和大小,所以释放时内核用对应的首地址进行匹配,匹配不上时,进程就会崩溃。如果需要偏移,那么可以定义两个指针变量解决,赋值然后偏移。
2.5.2-栈空间与堆空间的差异
print_stack()
子函数中的字符串存放在栈空间中,函数执行结束后,栈空间会被释放,字符数组c的原有空间已被分配给其他函数使用,所以从子函数返回后,再打印就会出现打印乱码。而print_malloc()
函数中的字符串存放在堆空间中,并不会因为子函数返回而释放,只有在执行free函数才会释放,否则在进程执行期间一直有效。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)