程序最终能直接改写内存里的值,输出也变成了预期那样——靠的就是指针。代码里一行 *p = 20 就把原来存放在某个地址的数给改了,程序省了复制,速度也顺了不少。写到这里,说实话,指针真是既能救急也能添乱,得用对地方。
先讲结果是怎么体现到程序里的。一个常见场景是函数里需要修改调用者的变量,直接把地址传进去就行。举例:有个函数接收 int *p,函数里写 *p = 42;主函数把某个变量的地址传进来,函数执行完,主函数里的变量就变了。这样不用把整个结构体拷贝进去,节省开销,尤其数据量大的时候特别明显。这个用法在工程里很常见,效率提升直观。
再往前推一步,动态分配的内存和指针是朋友关系。你用 malloc 或 calloc 向操作系统要一块内存,返回的是地址,必须用指针来接收。常见写法是 int *arr = malloc(n * sizeof(int)); 记得检查返回值是不是 NULL,确认内存申请成功。用完后来要 free(arr) 归还,别忘了,不然就内存泄漏。这里细节多:分配的字节数要根据数据类型算,别只写 malloc(n) 然后当成 int 用;还要思考对齐和 sizeof 的返回值。
再往回看,指针并不只是存地址那么简单。它的数据类型决定了解引用时的解释方式。声明一个 int *p 就说明 p 存放的是 int 类型数据的地址,用 *p 可以读写那个地址里的 int。写法像这样:int a = 10; int *p = &a; *p = 20; 这三句把地址取出来、放到指针里,再通过指针改了原变量。& 是取地址操作,* 被称为解引用,用来访问指针指向处的内容。类型必须匹配,否则访问的字节解释会错,容易出错。
指针还能算数。指针加减不是按字节走,而是按所指类型的大小走。列如一个 int *p 执行 p++,指针会跳过 sizeof(int) 个字节,指向下一个 int 元素;char *q 做 q++ 则只往后一个字节。这在遍历数组或内存缓冲区时很方便,但要格外小心边界。越界访问是未定义行为,可能看着没事儿,实则埋雷。还有,两个指针可以比较地址大小或相等,列如 if (p1 == p2),也可以判断是否指向同一块内存。但比较的前提一般是两个指针都指向同一数组或同一内存块,否则也没太大意义。
说到数组和指针,许多人会混淆。数组名在表达式里会退化成指向首元素的指针,所以可以用指针来遍历数组:int arr[5]; int *p = arr; 用 p[i] 或 *(p + i) 都能拿到元素。但数组本身和指针还是有差别,数组的内存是连续且固定的,而指针只是个变量,可以被重新赋值去指向别处。另一个细节是 sizeof(arr) 在数组上的结果和在指针上的结果不一样,这会影响内存计算,写代码时要注意。
还有一些安全相关的细节不能丢。解引用一个 NULL 指针立刻会导致段错误,程序崩溃。滥用野指针(未初始化的指针)会让程序行为不可预测。动态分配的内存如果没有释放,就会造成泄露;重复释放会带来更糟糕的后果。写指针代码时常见的防护手段是初始化指针,检查返回值,和在释放后把指针设为 NULL。
类型转换也是常遇到的问题。有时候需要把 void * 的返回值转换为具体类型,列如 int *p = (int *)malloc(…); 在 C 里显式转换不是必须,但在某些编译器或风格下会加上。强转时要小心对齐和数据解释,别让不同类型的指针去读同一块内存,除非你确切知道数据布局。
再往回倒,指针支持比较和相等判断。用 == 检查是否指向同一地址,用 < > 可以比较地址顺序(一般用于同一块内存里的元素),但这种比较的意义有限且受限于实现。许多逻辑错误来源于对指针边界和生命周期判断不到位,指针指向的内存不该在你使用时已经被释放或超出作用域。
最后,指针的基本概念说清楚:它是个变量,保存的是另一块内存的地址。通过取地址符 & 得到地址,通过解引用符 * 访问或修改地址处的数据。掌握了这些,后面的数组遍历、动态内存管理、函数间传递数据等都好理解了。写指针代码像开车,得同时看前后左右,踩刹车时也得知道车的反应速度。
这几天折腾指针,反复试验和犯错,总算把这些细节摸清了。每天坚持学一点点,不求有回报,只愿可以丰富自己。
收藏了,感谢分享