这是一个很好的经典问题。
数组下标从 0 开始 的设计主要源于以下几个原因:
1. 历史与硬件背景
早期的编程语言(如 C 语言)深受汇编语言和机器模型的影响。
在汇编语言中,计算数组元素的地址时使用公式:
[
\text{地址} = \text{基地址} + \text{索引} \times \text{元素大小}
]
如果下标从 0 开始,那么:
- 第 0 个元素的地址 = 基地址 + 0 × 元素大小 = 基地址
- 第 i 个元素的地址 = 基地址 + i × 元素大小
如果下标从 1 开始,则地址公式变成:
[
\text{地址} = \text{基地址} + (i - 1) \times \text{元素大小}
]
每次访问都要多做一次减法,降低了效率。
2. 与指针的一致性
在 C 语言中,数组名本质上是一个指向数组首元素的指针。
a[i] 等价于 *(a + i),如果下标从 0 开始,那么 a[0] 就是 *a,非常直接。
如果从 1 开始,那么 a[1] 才是 *a,逻辑上不太自然。
3. 简化循环与边界计算
循环从 0 到 N-1 时,用 0 起始下标更容易写出优雅的循环:
for (int i = 0; i < N; i++) {
// 访问 a[i]
}
循环次数正好是 N 次。如果从 1 开始,就变成:
for (int i = 1; i <= N; i++) {
// 访问 a[i]
}
这里数组需要定义成 a[N+1],多浪费一个元素空间,并且 <= 比较比 < 稍微容易出错(如差一错误)。
4. 模运算与数学上的便利
- 在模运算、位运算等场合,从 0 开始与计算机的二进制表示(0 ~ 2^n - 1)更一致。
- 多维数组的偏移量计算也更简单。比如二维数组
a[M][N] 中,a[i][j] 的位置是:
[
\text{基地址} + i \times N + j
]
这里如果 i, j 都从 0 开始,公式最简洁。
5. 影响与习惯
C 语言从 0 开始索引影响了后续大多数主流语言:C++、Java、C#、Python、JavaScript、Go 等。
这形成了一种编程习惯和标准,减少切换语言时的认知负担。
少数从 1 开始的语言
也有一些语言选择从 1 开始,比如:
- Fortran(早期科学计算)
- Lua(灵活,默认 1 开始但可改)
- MATLAB(数学建模常用)
- R(统计计算)
它们往往更多关注数学表达的自然性(数学中序列常从 1 开始),而不是硬件效率。
小结
数组下标从 0 开始 主要是出于:
- 硬件/效率原因:地址计算更直接
- 指针运算一致性:数组与指针的关系更清晰
- 简化循环与边界:避免差一错误
- 历史惯性:C 语言的广泛影响
在理解了 0-based indexing 背后的原因后,大多数程序员会逐渐习惯并欣赏它的简洁性。