为什么索引在“ C”中从零开始?


154

为什么数组中的索引从C中的零开始而不是1?


7
全部与指针有关!
medopal 2011年


3
的指针(数组)是存储器方向和索引的偏移存储器方向的,使指针(数组)的第一个元素是一个谁偏移等于0
D33pN16h7

3
@drhirsch,因为当我们计算一组对象时,我们首先指向一个对象并说“一个”。
phoog,2012年

1
美国人从一楼开始计算建筑物的楼层(楼层)。英国人从零开始(一楼),然后上升到一楼,然后是二楼,等等
。–乔纳森·莱夫勒

Answers:


116

在C语言中,数组的名称本质上是一个指针[但是请参见注释],它是对内存位置的引用,因此表达式表示的array[n]n远离起始元素的内存位置元素。这意味着将索引用作偏移量。数组的第一个元素正好包含在数组所指的存储位置中(相距0个元素),因此应将其表示为array[0]

有关更多信息:

http://developeronline.blogspot.com/2008/04/why-array-index-should-start-from-0.html


20
数组的名称就是数组的名称;与常见的误解相反,数组在任何意义上都不是指针。数组表达式(例如数组对象的名称)通常但不总是转换为指向第一个元素的指针。示例:sizeof arr产生数组对象的大小,而不是指针的大小。
基思·汤普森

虽然您显然对@KeithThompson的评论没有反应,但我想向您使用更多的冒犯方法:“ 在C中,数组的名称本质上是一个指针,是对内存位置的引用 ”-不,不是。至少不是从通用的角度来看。尽管您的答案以某种方式很好地回答了如何以索引开始为0很重要,但第一句话显然是错误的。数组并不总是衰减到指向其第一个元素的指针。
RobertS支持Monica Cellio

引用C标准,(C18),6.3.2.1 / 4:“ 除非它是运算sizeof符或一元运算&符的操作数,或者是用于初始化数组的字符串文字,否则其类型为” array of type“将转换为类型为” pointer to type“的表达式,该表达式指向数组对象的初始元素,而不是左值。如果数组对象具有寄存器存储类,则行为未定义。
RobertS支持Monica大提琴

同样,这种衰减以比此处建议的更“隐式”或“正式”的方式发生。所涉及的内存中的指针对象没有衰减。这是此问题的对象:指向指针衰减的数组是否已更改为指针对象?-请修改您的答案以完全正确。
RobertS支持Monica Cellio

103

这个问题是一年多以前发布的,但是这里...


关于以上原因

虽然Dijkstra的文章(以前在现在已删除的答案中引用过)从数学角度讲是有意义的,但在编程方面却不那么相关

语言规范和编译器设计者所做的决定基于计算机系统设计者作出的从0开始计数的决定。


可能的原因

丹尼·科恩(Danny Cohen)摘自《和平呼吁》。

对于任何基数b,仅当编号从0开始时,才 由正好N个数字(包括前导零)表示前b ^ N个非负整数。

这可以很容易地进行测试。在以2为底2^3 = 8 的第8个数字是:

  • 如果我们从1开始计数,则为8(二进制:1000)
  • 如果我们从0开始计数,则为7(二进制:111)

111可以使用3位表示,而1000需要额外的位(4位)。


为什么这很重要

计算机存储器地址具有2^NN位寻址的单元。现在,如果我们从1开始计数,则2^N单元格将需要N+1地址线。需要额外位才能访问1个地址。(1000以上情况)。解决该问题的另一种方法是使最后一个地址不可访问,并使用N地址线。

与从0开始计数相比,这都是次优的解决方案,这将使使用精确的N地址线可访问所有地址!


结论

0从此开始计算的决定已经渗透到所有数字系统中,包括在其上运行的软件,因为它使代码转换为底层系统可以解释的内容更加容易。如果不是这样,则对于每次数组访问,机器和编程器之间都将进行一次不必要的转换操作。它使编译更加容易。


引用本文:

在此处输入图片说明


2
如果他们只是删除了位0,该怎么办。那么第8个数字仍然是111 ...
DanMatlin 2013年

2
您实际上是在建议修改基本算术以使其适合吗?您不认为我们今天拥有的解决方案更好吗?
2013年

几年后,我的价值为2英镑。以我的经验(〜35年的编程经验),一种或多种形式的模或模加法运算经常令人惊讶地出现。在基数为零的情况下,下一个序列是(i + 1)%n,但在基数为1的情况下,序列为(i-1)%n)+1,因此我认为以0为基础是首选。这在数学和编程中经常出现。也许只是我或我工作的领域。
nyholku

尽管有很多很好的理由,但我认为它要简单得多:a[b]*(a+b)早期编译器一样实现。即使在今天,您仍然可以2[a]代替a[2]。现在,如果索引不是从0开始,a[b]则将变为*(a+b-1)。这将需要在该时间的CPU上加2而不是0,这意味着速度的一半。显然是不可取的。
Goswin von Brederlow

1
仅仅因为您想要8个州,并不意味着您必须在其中包含数字8。我家的电灯开关很高兴地表示“灯亮”,“灯关”状态,而不用奇怪,为什么他们并不代表数字2
Spyryto

27

因为0是从指针到数组开头到数组第一个元素的距离。

考虑:

int foo[5] = {1,2,3,4,5};

要访问0,请执行以下操作:

foo[0] 

但是foo分解为一个指针,并且上述访问具有类似的指针算法访问它的方式

*(foo + 0)

如今,指针算术已不再被频繁使用。不过,回想起来,这是一种获取地址并将X“ ints”从该起点移开的便捷方法。当然,如果您只想呆在原地,只需加0!


23

由于基于0的索引允许...

array[index]

...实施为...

*(array + index)

如果索引是基于1的,则编译器将需要生成:*(array + index - 1),并且此“ -1”将损害性能。


4
您提出了一个有趣的观点。它会损害性能。但是性能的提高是否对使用0作为起始索引的意义重大?我对此表示怀疑。
FirstName LastName

3
@FirstNameLastName基于1的索引比基于0的索引没有优势,但它们的性能(略)差。不管增益多么“小”,这都证明了从0开始的索引是正确的。即使基于1的索引提供了一些优势,选择性能而不是方便也符合C ++的精神。有时,在性能至关重要的每个上下文中都使用C ++,并且这些“小”事情可以很快加起来。
Branko Dimitrijevic

是的,我知道小事会加起来,有时会变成大事。例如,每年1美元并不多。但是,如果有20亿人捐赠,那么我们可以为人类做很多事。我正在寻找可能会导致性能下降的类似编码示例。
FirstName LastName

2
而不是减去1,您应该使用array-1的地址作为基地址。那是我们曾经在编译器中所做的工作。这样就消除了运行时减法。在编写编译器时,这些额外的指令非常重要。该编译器将用于生成数千个程序,每个程序可以使用数千次,并且在n平方环内的多行中可能会出现额外的1条指令。它可能会增加多达数十亿的浪费周期。
progrmr

不,它一旦编译就不会损害性能,只会增加一个很小的构建时间,因为最终它将被翻译成机器代码,只会损害编译器设计者。
哈萨克·阿克巴尔

12

因为它使编译器和链接器更简单(更易于编写)。

参考

“ ...在几乎所有计算机体系结构上,都直接在硬件中表示通过地址和偏移量引用的内存,因此C中的这种设计细节使编译更加容易”

“ ...这使实现更简单...”


1
+1不知道为什么要投反对票。尽管它不能直接回答问题,但是基于0的索引对于人或数学家来说并不自然-这样做的唯一原因是实现在逻辑上是一致的(简单)。
phkahler 2011年

4
@phkahler:错误发生在作者和语言中,将数组索引称为索引;如果您将其视为偏移量,那么从零开始对非专业人士也很自然。考虑一下时钟,第一分钟写为00:00,不是00:01吗?
Lie Ryan

3
+1-这可能是最正确的答案。C早于Djikistras论文,并且是最早的“从0开始”语言之一。C开始“作为高级汇编程序”的生活,并且K&R可能希望坚持使用汇编程序中的方式,在这种情况下,通常您会有一个基址加一个从零开始的偏移量。
James Anderson

我以为问题是为什么要使用基于0的值,而不是更好。
progrmr 2011年

2
我不会拒绝投票,但是正如上面的progrmr所言,可以通过调整数组地址来照顾基础,因此无论基础执行时间是相同的,这在编译器或解释器中实现都是微不足道的,因此实际上并不能简化实现。Pascal见证人,您可以使用任何范围的索引IIRC,已有25年了;)
nyholku

5

数组索引始终以零开始。假设基址为2000 arr[i] = *(arr+i)。现在if i= 0,这意味着*(2000+0)等于基地址或数组中第一个元素的地址。该索引被视为偏移量,因此bydeafault索引从零开始。


5

出于同样的原因,当是星期三,有人问您到星期三有多少天时,您说的是0而不是1;而当是星期三,有人问您直到星期四有多少天时,您说的是1而不是2。


6
您的回答似乎只是一个见解。
heltonbiker 2011年

6
好的,这就是使添加索引/偏移量起作用的原因。例如,如果“今天”为0,“明天”为1,“明天的明天”为1 + 1 = 2。但是,如果“今天”为1,“明天”为2,则“明天的明天”不是2 + 2。在数组中,只要您想将数组的子范围本身视为数组,就会发生这种现象。
R .. GitHub停止帮助ICE,

7
将3个事物的集合称为“ 3个事物”并对其进行编号1,2,3并不是不足。即使在数学上也不自然地对它们进行编号。在数学中唯一从零索引的时间是当您想要在多项式中包含诸如零次幂(常数项)之类的内容时。
phkahler 2011年

9
回复:“对以1而不是0开头的数组进行编号是针对数学思维严重不足的人的。” 我的CLR版本的“算法简介”使用基于1的数组索引;我认为作者在数学思维上没有不足。
RexE 2011年

不,我要说第七个在索引6处,或者比第一个离索引6个位置。
R .. GitHub停止帮助ICE 2014年

2

对于基于零的编号,我读过的最优雅的解释是,观察值不是存储在数字行的标记位置,而是存储在它们之间的空格中。第一项存储在零到一个之间,第二项存储在零到两个之间,依此类推。第N个项目存储在N-1和N之间。可以使用任一侧的数字来描述一系列项目。按照惯例,使用下面的数字描述各个项目。如果给定一个范围(X,Y),则使用下面的数字标识单个数字意味着无需使用任何算术运算符(它是X项)就可以标识第一个项目,但是必须从Y中减去一个来标识最后一个项目(Y -1)。使用上面的数字识别项目将使识别范围中的最后一个项目(即项目Y)更加容易,

尽管根据上面的数字来标识项目并不可怕,但是将范围(X,Y)中的第一个项目定义为X之上的项目通常比定义为以下的项目(X + 1)。


1

技术原因可能源于以下事实:指向数组存储位置的指针是数组第一个元素的内容。如果声明索引为1的指针,则程序通常会将该值加1来访问当然不是您想要的内容。


1

尝试在基于1的矩阵上使用X,Y坐标访问像素屏幕。该公式非常复杂。为什么复杂?因为最终将X,Y坐标转换为一个数字,即偏移量。为什么需要将X,Y转换为偏移量?因为这就是内存在计算机内部的组织方式,所以它们是连续的存储单元(阵列)流。计算机如何处理阵列单元?使用偏移量(从第一个单元格开始的位移,从零开始的索引模型)。

因此,在代码中的某些时候,您需要(或编译器需要)将基于1的公式转换为基于0的公式,因为这是计算机处理内存的方式。


1

假设我们要创建一个大小为5的数组
int array [5] = [2,3,5,9,8]

让该数组的第一个元素指向位置100

,让我们考虑从1开始而不是从索引开始0。

现在我们必须借助索引找到第一个元素
的位置(记住第一个元素的位置是100),

因为整数的大小是4位,
因此->考虑索引1的位置将是
size (1)的整数*整数(4)的大小= 4,
因此它将向我们显示的实际位置是

100 + 4 = 104

这是不正确的,因为初始位置在100。
它应该指向100而不是在104。
这是错误的。

现在假设我们从0开始进行索引,
那么
第一个元素的位置应该是
index(0)*整数的大小(4)= 0

因此->
第一个元素的位置是100 + 0 = 100

,这就是元素的实际位置,
这就是为什么索引从0开始的原因;

我希望它能澄清您的观点。


1

我来自Java背景。我在下图中给出了这个问题的答案,下图我写在一张纸上,这是不言自明的

主要步骤:

  1. 创建参考
  2. 数组实例化
  3. 将数据分配到阵列

  • 还要注意数组刚刚被实例化时的情况。默认情况下,零分配给所有块,直到我们为其分配值
  • 数组从零开始,因为第一个地址将指向引用(即图像中的X102 + 0):

在此处输入图片说明

注意:图像中显示的块是内存表示形式


0

首先,您需要知道数组在内部被视为指针,因为“数组本身的名称包含数组第一个元素的地址”

ex. int arr[2] = {5,4};

考虑到数组从地址100开始,因此元素第一个元素将位于地址100,第二个元素现在位于地址104,请考虑如果数组索引从1开始,则

arr[1]:-

可以这样写在指针表达式中

 arr[1] = *(arr + 1 * (size of single element of array));

考虑int的大小是4bytes,现在,

arr[1] = *(arr + 1 * (4) );
arr[1] = *(arr + 4);

我们知道数组名称包含其第一个元素的地址,所以现在arr = 100,

arr[1] = *(100 + 4);
arr[1] = *(104);

这使,

arr[1] = 4;

由于此表达式,我们无法访问地址100的元素,这是官方的第一个元素,

现在考虑数组索引从0开始,所以

arr[0]:-

这将解决为

arr[0] = *(arr + 0 + (size of type of array));
arr[0] = *(arr + 0 * 4);
arr[0] = *(arr + 0);
arr[0] = *(arr);

现在,我们知道数组名称包含其第一个元素的地址,因此,

arr[0] = *(100);

给出正确的结果

arr[0] = 5;

因此数组索引始终从c中的0开始。

参考:所有详细信息都写在“ Brian kerninghan和Dennis Ritchie的C编程语言”一书中


0

在数组中,索引指示距起始元素的距离。因此,第一个元素与起始元素的距离为0。因此,这就是数组从0开始的原因。


0

这是因为address必须指向element数组中的右侧。让我们假设以下数组:

let arr = [10, 20, 40, 60]; 

现在让我们考虑地址be 的开始12elementbe 的大小4 bytes

address of arr[0] = 12 + (0 * 4) => 12
address of arr[1] = 12 + (1 * 4) => 16
address of arr[2] = 12 + (2 * 4) => 20
address of arr[3] = 12 + (3 * 4) => 24

如果不是 zero-based,从技术上讲,我们在中的第一个元素地址array16是错误的,因为它的位置是12


-2

数组名称是指向基地址的常量指针。当您使用arr [i]时,编译器会将其作为*(arr + i)进行处理。由于int范围为-128至127,因此编译器认为-128至-1是负数和0到128为正数,因此数组索引始终从零开始。


1
“ int范围是-128到127”是什么意思?int需要一种类型来支持至少16位范围,并且如今在大多数系统上,如今都支持32位。我认为您的逻辑是有缺陷的,您的答案确实不会比其他人已经提供的其他答案有所改善。我建议删除它。
乔纳森·勒夫勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.