什么是指针
指针是一个变量,但它存的不是普通数据,而是一块内存的地址。理解了地址,你就理解了指针为什么既能高效传递数据,又特别容易出错。
本文讲清四件事:指针和内存地址的关系、怎么读写指针、它在哪些场景下不可替代,以及新手最常踩的几个坑。

指针与内存地址的关系
程序运行时,所有变量都住在内存里。内存可以想象成一排编了号的储物柜,每个柜子有固定编号,这个编号就是地址。
你写一句 int a = 10,系统就分配几个字节,把 10 存进去,同时这块空间有了自己的地址。

平时我们写 a,编译器其实帮你做了两件事:找到 a 的地址,再从那个地址把值取出来。这个过程被隐藏了,所以你感觉不到地址的存在。
指针就是把这一层隐藏撕开,让你直接拿着地址去操作。

怎么读写指针:取地址与解引用
C 语言里用 & 取地址,用 * 解引用。
声明一个指向 int 的指针 p,让它等于 &a,意思是 p 里面装的就是 a 的地址。想改 a 的值,不必写 a = 20,写 *p = 20 也一样,因为 *p 的意思就是顺着 p 里的地址找到那块内存。
这是指针最基本的两个动作:取地址和解引用,一个向外拿地址,一个向里访问内容。

指针本身也有成本
指针本身也是变量,也要占空间,64 位系统上通常是 8 个字节,和它指向的类型无关。
所以指针不是零成本的抽象,它换来的是一次间接寻址。

指针不可替代的三个场景
1. 避免拷贝
函数传参默认是值传递,传一个结构体进去等于复制一份,结构体越大越慢。
改成传指针,只复制一个地址,几个字节就够了,函数里通过地址直接操作原来那份数据。

2. 让函数修改外部变量
C 没有引用传递。你想在函数里交换两个数,只传值做不到,因为函数拿到的是副本。
传 &x 和 &y,函数里用指针交换,外面的 x 和 y 才会真的换。
这就是 scanf 里那个 & 的由来:它要的不是你的值,而是你的地址。

3. 动态内存
数组大小在编译时就定死了,但很多场景你不知道到底要多少数据。
用 malloc 在堆上临时申请一块内存,它返回的正是这块内存的首地址,你只能拿指针接住。
用完还得 free 还回去,否则就是内存泄漏。

指针与数组的关系
数组名在多数表达式里会退化成首元素地址,所以声明一个长度为 5 的 int 数组,数组名和 &arr[0] 是同一个值。
指针还能做加减。p + 1 不是加一个字节,而是跳过一个元素的大小:int 指针跳 4 个字节,这就是指针算术。
![5 格 int 数组首格同时标注 arr 与 &arr[0],指针箭头跳到第二格并标注 p 加 1 跳过 4 个字节](/blog/%E4%BB%80%E4%B9%88%E6%98%AF%E6%8C%87%E9%92%88-%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80-%E5%8F%96%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%A7%A3%E5%BC%95%E7%94%A8%E7%9A%84%E5%AE%8C%E6%95%B4%E5%85%A5%E9%97%A8/scene-9-9.png)
常见误区:四类典型的指针错误
第一是空指针:指针没指向任何有效内存就解引用,程序直接崩。
第二是野指针:指针指向的内存已经被释放,或者指向了函数里已经销毁的局部变量。这时它看着还有值,其实已经不能用了。
比如释放之后继续用 p,程序可能照常跑,直到某次内存被别的数据覆盖才出问题。

第三是声明了没初始化就直接用。第四是越界访问:p 加多了跑到数组外面,读写的就是别人的内存。
这几种错误有个共同点:编译器不一定报错,程序可能正常跑几次才崩,排查起来特别费劲。

常见问题
指针和普通变量有什么区别? 普通变量存的是数据,指针存的是内存地址;指针本身也是变量,也占空间。
& 和 * 分别做什么? & 取地址,* 解引用,一个向外拿地址,一个向里访问内容。
为什么 scanf 要写 &? 只传值函数拿到的是副本,改不了外部变量,所以它要的不是你的值,而是你的地址。
为什么传指针能修改外部变量? 因为函数通过地址直接操作原来那份数据,而不是副本。
指针为什么容易出错? 空指针、野指针、未初始化、越界访问这几类错误,编译器不一定报错,程序可能跑几次才崩。
其他语言中的指针
C++ 里的引用可以当作指针的一层封装,更安全,不能为空,也不用写星号。
Java、Python、Go 干脆把指针藏起来,你操作的变量名背后其实就是引用。
但底层该有的地址、该有的间接访问一点没少,只是不再让你手动管理。

总结
指针是一个装着地址的变量,取地址用 &,解引用用 *。
它带来三样东西:少拷贝、能改外部、能动态申请内存。
它的代价是把内存的控制权交到你手上,安全边界得你自己守。


