数组大概是每种编程语言里出场率最高的数据结构,但“会用”和“真正理解”之间,往往隔着一道很深的认知鸿沟。前阵子帮人 review 代码,连着遇到两个特别典型的错误:一个是把二维数组写成 int a[][] 当函数形参,编译直接不通过;另一个是在 C++ 里用 memset 想给 int 数组全部置 1,结果打印出来全是 16843009。这类问题真不是手误,而是对数组的理论基础没吃透。这篇就把数组这件事完整捋一遍:从内存模型和下标原理,到一维、多维数组的正确初始化和传参,再到指针数组和数组指针的辨析,最后落到不同语言里数组的实际操作和算法题里的高频用法。适合刚开始学数据结构的朋友,也适合写了好几年代码但偶尔被数组“摆一道”的开发者。
1. 数组的底层契约:从内存模型理解“连续”与“下标”
1.1 数组定义到机器实现:一个地址公式解决所有问题
数组的教科书定义是“相同类型元素的有限集合”,这个定义本身没什么信息量。真正重要的是机器层面的实现约定:元素按声明顺序一字排开,存放在一段连续的内存里,每个元素占用相同的字节数。
只要记住首地址 base 和元素类型大小 size,任意下标的地址都可以直接算出来:
c复制addr(a[i]) = base + i * size
这个公式看起来平平无奇,但它解释了两件最重要的事。
第一,为什么数组访问是 O(1) 的?因为不需要遍历、不需要查找,一次乘法一次加法就能定位到任意元素。链表做不到这一点,它必须从头结点一个个跳过去。
第二,为什么数组下标从 0 开始?因为从 0 开始,下标本身就等于偏移量,a[i] 的地址就是 base + i * size,不需要任何额外减法。如果从 1 开始,每次都要算 base + (i - 1) * size,多一次运算,指令上不划算。C 语言从 0 开始不是拍脑袋,是直接对应机器寻址的天然逻辑。很多从 Fortran 或 MATLAB 转过来的同学不习惯这一点,但理解了地址模型就会明白,这不是“规定”,是“最优解”。
1.2 连续内存带来的两个红利:随机访问与缓存局部性
连续内存除了让访问变快,还承包了性能优化里一个很核心的东西——缓存局部性。
CPU 读内存不是一次读一个字节,而是按缓存行(常见 64 字节)
