为什么数组是从0开始的

背景

我们知道大部分编程语言中的数组都是从0开始编号的,即array[0]是数组的第一个元素。这个和我们平时生活中从1开始编号的习惯相比显得很反人类。那么究竟是什么样的原因让大部分编程语言数组都遵从了这个神奇的习惯呢?本文最初是受stackoverflow上的一个问题的启发,通过搜集和阅读了一些资料在这里做个总结。当然,本文摘录较多的过程结论,如果你想把这篇文章当做快餐享用的话,可以直接跳到文章末尾看结论。


最早的原因

在回答大部分我们无法解释的诡异问题时,我们最常用的辩词通常是历史原因。那么,历史又是出于什么原因,使用了0标号数组呢?Mike Hoye就是本着这么一种追根刨地的科学精神为我们找到了解答。以下是一些他的重要结论的摘录翻译:

据作者的说法,C语言中从0开始标号的做法是沿用了BCPL这门编程语言的做法。而BCPL中如果一个变量是指针的话,那么该指针可以指向一系列连续的相同类型的数值。那么p+0就代表了这一串数值的第一个。在BCPL中数组第5个元素的写法是p!5,而C语言中把写法改成了p[5],也就是现在的数组。具体原文摘录如下:

If a BCPL variable represents a pointer, it points to one or more consecutive words of memory. These words are the same size as BCPL variables. Just as machine code allows address arithmetic so does BCPL, so if p is a pointer p+1 is a pointer to the next word after the one p points to. Naturally p+0 has the same value as p. The monodic indirection operator ! takes a pointer as it’s argument and returns the contents of the word pointed to. If v is a pointer !(v+I) will access the word pointed to by v+I.

至于为什么C语言中为什么使用[]方括号来表示数组下标,这个设计也有一定来历。据C语言作者的说法是方括号是现代键盘上唯一较为容易输入的成对符号(不用shift)不信你对着键盘找找?


为什么这个反人类设计在一段时间内一直没有被改变

根据Mike的说法,BCPL是被设计在IBM硬件环境下编译运行的。在1960后的很长一段时间内,服务器硬件几乎被IBM统治。一个城市内也许至于一台超级计算机,还需要根据时间配额使用。当你当天的配额用完以后,你的程序就被完全清出计算队列。甚至连计算结果都不给你保留,死无全尸。这个时候写一段高效的程序,就显得比什么都重要了。而这时0下标数组又体现了出了它的另一个优势,就是:相较于1下标数组,它的编译效率更高。原文摘录如下:

So: the technical reason we started counting arrays at zero is that in the mid-1960’s, you could shave a few cycles off of a program’s compilation time on an IBM 7094. The social reason is that we had to save every cycle we could, because if the job didn’t finish fast it might not finish at all and you never know when you’re getting bumped off the hardware because the President of IBM just called and fuck your thesis, it’s yacht-racing time.

此外,还有另外一种说法。在C语言中有指针的概念,而指针数组标号实际上是一个偏移量而不是计数作用。例如对于指针p,第N个元素是*(p+N),指针指向数组的第一个元素就是*(p+0)


一些现代语言为什么仍然使用这种做法

上文中提到的为了计较分秒的编译时间而使用0下标数组,在硬件飞速发展的今天显然是不必要的。那么为什么一些新兴语言,如Python依然选择以0作为数组第一个元素呢?难道也是历史原因?对于这个问题,Python的作者Guido van Rossum也有自己的答案。这里大致概括一下作者的用意:从0开始的半开放数组写法在表示子数组(或者子串)的时候格外的便捷。例如:a[0:n]表示了a中前n个元素组成的新数组。如果我们使用1开始的数组写法,那么就要写成a[1:n+1]。这样就显得不是很优雅。那么问题来了,Python数组为什么使用半开放,即[m,n)左闭合右开发的写法呢?这个理解起来就比较简单,读者可以参考http://www.cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF作为扩展阅读。下面摘录一段Python作者的原话:

Using 0-based indexing, half-open intervals, and suitable defaults (as Python ended up having), they are beautiful: a[:n] and a[i:i+n]; the former is long for a[0:n].
Using 1-based indexing, if you want a[:n] to mean the first n elements, you either have to use closed intervals or you can use a slice notation that uses start and length as the slice parameters. Using half-open intervals just isn't very elegant when combined with 1-based indexing. Using closed intervals, you'd have to write a[i:i+n-1] for the n items starting at i. So perhaps using the slice length would be more elegant with 1-based indexing? Then you could write a[i:n]. And this is in fact what ABC did -- it used a different notation so you could write a@i|n.(Seehttp://homepages.cwi.nl/~steven/abc/qr.html#EXPRESSIONS.)


总结

从0标号的数组传统,沿用了这么长时间的原因主要列举如下:

  • 在计算资源缺乏的过去,0标号的写法可以节省编译时间
  • 现代语言中0标号可以更优雅的表示数组字串
  • 在支持指针的语言中,标号被视作是偏移量,因此从0开始更符合逻辑

参考文献

  1. (http://developeronline.blogspot.com/2008/04/why-array-index-should-start-from-0.html)
  2. [http://exple.tive.org/blarg/2013/10/22/citation-needed/]
  3. [https://plus.google.com/115212051037621986145/posts/YTUxbXYZyfi]
  4. [http://stackoverflow.com/questions/7320686/why-does-the-indexing-start-with-zero-in-c]
<think> 首先,用户的问题是关于动态数组的索引起点:是从1开始是从0开始?用户提到了动态数组,但在提供的引用中,主要讨论的是数组索引的一般起点(0 vs 1),并没有特别指定动态数组。 回顾引用: - 引用[1]:讨论了索引数组在PHP中,键是隐式数字,自动维护。 - 引用[2]:展示了NumPy数组索引,使用整数数组索引,索引是从0开始的(如x[np.array([3,...])]表示索引3的元素)。 - 引用[3]:明确指出在C语言中,索引是从0开始的。 - 引用[4]:解释了为什么索引从0开始,涉及到内存地址计算。 - 引用[5]:一个轶事,讨论索引从0开始的原因。 用户的问题焦点是动态数组。我应该解释动态数组的索引起点。动态数组(如Python的列表、Java的ArrayList)通常继承自编程语言的标准数组索引约定。 关键点: - 在大多数编程语言中,包括C、C++、Java、Python、JavaScript等,数组索引都是从0开始的。 - 动态数组(如Python的list或Java的ArrayList)也不例外,索引从0开始。 - 例如,在Python中,定义一个列表,索引从0开始:`my_list = [1,2,3]`;`my_list[0]` 是1。 - 引用[3]和[4]支持这一点:在C语言中索引从0开始,计算内存地址更直观。 用户提到的引用中,引用[1]是关于PHP索引数组,PHP数组索引默认从0开始。引用[2]是NumPy,索引从0开始。 所以,针对用户的问题:动态数组的索引通常也是从0开始。 结构化回答: - 先回答问题:动态数组的索引通常是从0开始的。 - 解释为什么:这源于历史原因和在内存中的高效访问(如引用[4]所述)。 - 提供例子:例如,在Python中,动态数组列表索引从0开始。 - 参考引用:自然地融入引用信息。 最后,根据系统指令: - 使用中文回答。 - 生成相关问题。 - 在引用段落末尾添加引用标识。 - 确保行内数学表达式用$...$格式(但这个问题可能不需要数学)。 - 独立公式用$$...$$(同样,可能不需要)。 回答结构: 1. 直接回答用户的问题。 2. 解释原因。 3. 提供例子。 4. 引用相关引用。 5. 生成相关问题。 相关问题: - 根据主题,生成关于数组索引、动态数组实现等的问题。 </think> ### 动态数组的索引起点分析 动态数组(也称为可变数组或列表)是一种在运行时可以自动调整大小的数据结构,常见于现代编程语言(如 Python 的 `list`、Java 的 `ArrayList` 或 C++ 的 `std::vector`)。关于其索引起点,**动态数组的索引通常从 0 开始,而不是从 1 开始**。这与静态数组的索引约定一致,主要源于底层内存计算的效率和语言设计的一致性。以下我将逐步解释原因,并提供示例说明。 #### 1. **索引从 0 开始是主流约定** - 在绝大多数编程语言中,数组(无论是静态还是动态)的索引都从 0 开始。这包括 C、Java、Python、JavaScript 等语言。索引从 0 开始的设计,主要是为了简化内存地址的计算: - 数组元素在内存中是连续存储的。第一个元素的地址(基地址)直接对应索引 0,后续元素的地址通过公式计算:$ \text{address} = \text{base\_address} + (\text{index} \times \text{data\_type\_size}) $。 - 如果索引从 1 开始,公式会变为 $ \text{address} = \text{base\_address} + ((\text{index} - 1) \times \text{data\_type\_size}) $,这会增加一次减法运算,降低访问效率[^4]。 - 例如,在 C 语言中,静态数组索引从 0 开始,动态数组的实现(如指针操作)也遵循这一规则。引用中的示例显示:`array[0]` 访问第一个元素,`array[2]` 访问第三个元素,这避免了冗余计算[^3]。 #### 2. **动态数组的具体表现** - 动态数组虽然支持大小变化(例如通过重新分配内存),但索引机制与静态数组相同: - **Python 示例**:Python 的 `list` 是动态数组。索引从 0 开始: ```python my_list = [10, 20, 30] # 定义一个动态数组 print(my_list[0]) # 输出第一个元素: 10(索引为 0) print(my_list[2]) # 输出第三个元素: 30(索引为 2) ``` 如果尝试 `my_list[3]`,会引发 `IndexError`,因为索引超出范围。 - **NumPy 动态数组索引**:在引用[2]中,NumPy 数组(可视为动态)使用整数数组索引时,索引也是从 0 开始。例如: ```python import numpy as np arr = np.array([10, 9, 8, 7, 6]) # 动态数组 print(arr[0]) # 输出: 10(索引 0) print(arr[np.array([1, 3])]) # 输出数组: [9, 7](索引 1 和 3) ``` 这证实了动态数组索引的从 0 规则[^2]。 - 其他语言如 PHP(引用[1])的索引数组默认也从 0 开始,键值自动维护为隐式数字索引。 #### 3. **为什么不从 1 开始?** - **效率考量**:索引从 0 开始简化了编译器和解释器的实现。如引用[4]所述,计算元素地址时省去了额外的减法操作,这在内存密集型应用中至关重要。 - **历史与一致性**:早期语言(如 C)的设计影响了后续语言的规范。动态数组库(如 Python 的 `list`)继承这一约定,确保代码兼容性和开发者习惯。 - **实际影响**:如果索引从 1 开始,会增加边界错误的风险(例如,混淆“第一个元素”和“索引 1”),并导致算法实现更复杂[^5]。 #### 4. **例外情况** - 少数语言或特定库可能允许索引从 1 开始(如 MATLAB 或 Lua),但这并非动态数组的通用规则。主流动态数组实现始终优先从 0 开始索引。 总之,动态数组的索引几乎总是从 0 开始,这是基于性能优化和语言设计的一致性。如果您在使用特定语言或库时遇到疑问,建议查阅官方文档以确认细节。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值