为什么数组索引从0开始:背后的真实原因
Source: Dev.to
如果你曾经写过代码,至少会问过一次这个问题:
“为什么数组的索引是从 0 开始,而不是 1 ?”
乍一看,从 1 开始似乎更自然——人们的计数是 1、2、3……——但几乎所有主流编程语言(C、C++、Java、Python、JavaScript、Go、Rust 等)都使用零基索引。这并不是随意的决定。下面我们将从硬件内存到编译器设计层面,拆解真实原因,并看看为什么索引 0 实际上是最高效的选择。
Source: …
内存布局和偏移
数组是连续的内存块。
arr = [10, 20, 30, 40]
Memory address:
1000 → 10
1004 → 20
1008 → 30
1012 → 40
- 基地址 – 第一个元素的地址(这里是 1000)。
- 元素大小 – 每个元素占用的字节数(这里是 4 字节)。
元素的地址计算公式为:
address = base_address + (index * element_size);
arr[0]→1000 + (0 * 4) = 1000arr[1]→1000 + (1 * 4) = 1004arr[2]→1000 + (2 * 4) = 1008
索引 0 表示 没有偏移——元素正好位于基地址处。
如果数组从 1 开始,公式将变为:
address = base_address + ((index - 1) * element_size);
额外的 “‑1” 会在每次访问时增加一次减法,降低执行速度并使编译器逻辑更加复杂。
C 语言中的指针算术
零基索引与指针算术天然契合,这也是 C 语言推广它的原因。
arr[i] == *(arr + i);
arr是指向第一个元素的指针。i是偏移量(向前移动多少个元素)。
因此:
arr[0] = *(arr + 0); // 第一个元素
arr[1] = *(arr + 1); // 第二个元素
继承了 C 语义的语言(Java、JavaScript、Python、Go、Rust 等)为了保持一致性和性能,仍然采用零基索引。
清晰的循环结构
Zero‑based indexing makes loops concise and eliminates off‑by‑one errors at the machine level.
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
- 从 0 开始。
- 在
length之前 停止。
因为 length 是元素的 计数,而不是最后的索引,所以循环条件 i < arr.length 本身就是正确的。
如果索引从 1 开始,循环逻辑就需要额外的减法或调整终止条件,从而增加不必要的比较。
半开区间与切片
许多语言采用 半开区间 约定 [start, end) 来表示切片:
arr = [10, 20, 30, 40, 50]
sub = arr[0:3] # → [10, 20, 30]
- 起始索引 = 相对于基址的偏移量。
- 结束索引 = 要包含的元素数量。
这带来了:
- 连续切片之间没有重叠。
- 对切片长度没有歧义(
end - start)。
半开区间在数学上简洁,并且能够实现高效的切片、范围循环和迭代器算术。
数学和理论基础
在数学和理论计算机科学中,计数通常从 0 开始:
- 序列的索引从 0 开始(第一个元素的偏移量为 0)。
- 图遍历、有限状态机和自动机理论使用基于零的编号来表示与起始状态的距离。
将索引视为距离而非面向人的序数,与这些模型保持一致,并简化了证明和算法设计。
性能影响规模
单个额外的减法(index - 1)看似微不足道,但考虑以下情况:
- 数百万 次数组访问在紧密循环中(例如,图形引擎、科学仿真)。
- 缓存友好性:零基计算直接映射到地址算术,减少指令数量。
- 编译器优化:更简洁的地址计算能够生成更高效的代码。
在大规模场景下,消除哪怕是极小的每次访问开销,都可能转化为可观的性能提升。
非零基索引的语言
一些语言选择更符合人类习惯的索引方式:
- MATLAB – 基于 1 的数组。
- Lua – 默认基于 1(但可以更改)。
- Fortran – 历史上基于 1。
即使在这些语言中,编译器最终也会在内部将数组访问转换为基于 0 的内存偏移,从而产生一定的转换开销。
Bottom line
- 内存使用相对于基地址的offsets。
- 零基索引与pointer arithmetic完美对应。
- 它产生simpler, faster loops以及half‑open interval语义。
- 它符合序列起始点的mathematical notion of distance。
理解硬件层面的原理会让索引 0 显得必然,而非随意。