为什么从零开始的数组是常态?


112

这里提出的一个问题使我想起了我与一位程序员的讨论。他认为,从零开始的数组应该替换为从一开始的数组,因为从零开始的数组是一个实现细节,它起源于数组和指针以及计算机硬件的工作方式,但是这些东西不应在更高层次上反映出来。语言。

现在我不是很擅长辩论,所以除了提供感觉更合适之外,我无法提供任何充分的理由坚持使用从零开始的数组。为什么零的共同出发点阵列?


在n元素数组中,元素'n'不存在。一个n元素数组具有仅从0到n-1的成员。因此,如果我们有一个从1开始的数组,这样n个元素的数组实际上代表了该数组中存在的n个元素,那就更好了。

我喜欢基于零的数组,因为字节的第一位是2 ^ 0,而不是2 ^ 1。有时候这对我有帮助:)
e-MEE

例如,如果有人查看此列表,例如en.wikipedia.org/wiki/…,则会注意到大多数特定于域的语言的索引从1开始,而大多数来自cs思维学校的语言的索引都从0开始。 ,他将注意到,在这个问题上已经进行了许多愚蠢的讨论,试图谈论两者中的任何一个以改变其方式可能是毫无意义的。为了捍卫“ 1”,世界上大多数人都使用那个。大多数程序员使用“ 0”。大多数人不了解程序员,因此您会想...
Rook

我不敢相信这个问题是从StackOverflow迁移到Programmers,然后以非建设性的方式结束的。真傻
paercebal

Answers:


105

我认为我们谁都不能提供比Edsger W. Dijkstra的文章“为什么编号应从零开始”更强有力的论据。


他确实提出了一些统计数据并使用了数学样式证明。但是我敢肯定有人还会争论。尽管我同意,但我不会。

6
Dijkstra的文章是关于风格的,但是,他的论点是关于简单和易用的... +1。
paercebal

80

权威论点

好吧……显然,大多数语言,包括最近的语言,都是从零开始的。由于这些语言是由相当熟练的人编写的,因此您的朋友一定是错的。

为什么一个?

为什么1会比零更好的起始索引?为什么不2或10?答案本身很有趣,因为它显示了人们捍卫这一想法的过程。

第一个说法是,它更自然,因为第一次通常是一个所有在别人面前,至少对于大多数人...

一个参数是最后一个索引也是数组的大小...

我通常对这种争论所听到的原因的“品质”给我留下深刻的印象……当我被提醒时,更是如此……

为什么不为零?

……“基于基础的”表示法是西方文化遗留下来的遗物,即使有更多的世纪,它也忽略了零的存在。

信不信由你,原始的公历从-3,-2,-1,1,2,3 ...想象一下它对西方科学造成的问题(例如,从1月1日开始-2年到1月2日,就可以看到原始的公历与减法之类的简单冲突……)。

保持基于一的数组就像(嗯,我会为此而被下调... ^ _ ^ ...),保持到21世纪的英里和码。

为什么零?因为这是数学!

首先(糟糕,抱歉,我再试一次)

,零什么都不是,一是什么。一些宗教文献认为,“一开始什么都没有。” 一些与计算机有关的讨论可能像宗教辩论一样炙手可热,因此,这一点似乎并没有超出主题范围……^ _ ^

首先,使用基于零的数组并忽略其第零值比使用基于零的数组并四处寻找其零值要容易得多。这个理由几乎和以前一样愚蠢,但是后来,支持基于一个的数组的原始论点也很谬误。

第二,让我们记住,在处理数字时,您一次或一次地处理数学的机会很高,而当您处理数学时,机会是好的,您不会因为愚蠢的黑客而绕过过时的习惯。基于One的表示法也困扰着数个世纪的数学和日期,并且通过从错误中吸取教训,我们应努力在面向未来的科学(包括计算机语言)中避免使用它。

第三,对于与硬件绑定的计算机语言数组,分配一个由21个整数组成的C数组,并将指针向右移10个索引,您将获得一个自然的[-10至10]数组。这对于硬件而言是不自然的。但这是为了数学。当然,数学可能已过时,但我上次检查时,世界上大多数人都认为不是。

,正如在其他地方已经指出的,即使对于离散位置(或距离减小为离散值),第一个索引也将为零,例如建筑物的地板(从零开始),递减的倒数计数(3、2、1,零!),地面高度,图像的第一个像素,温度(零开氏度,表示绝对零,或零摄氏度,如273 K的水冻结温度)。实际上,唯一真正开始的是传统的“ 第一第二第三等”方式。迭代符号,这自然地将我引向了一点...

五个下一个点(这自然遵循以前)是高层次的容器应被访问,而不是指数,而是通过迭代器,除非指数本身具有内在价值。我很惊讶您的“高级语言”拥护者没有提到这一点。在索引本身很重要的情况下,您可以将一半时间押在与数学有关的问题上。因此,您希望您的容器对数学友好,而不是像从1开始的“您的旧公历”那样禁用数学,并且需要反省的技巧才能使其工作。

结论

程序员的说法是谬论,因为它不必要地将口头/书面语言习惯与自然地模糊的计算机语言(您不希望使指令模糊的)联系在一起,并且由于错误地归因于硬件他。她希望说服您,因为随着语言的抽象化程度越来越高,从零开始的数组已成为过去。

由于数学相关的原因,从零开始的数组也从零开始。不是出于与硬件相关的原因。

现在,如果这对您的同伴程序员是一个问题,请让他开始使用真正的高级构造进行编程,例如迭代器和foreach循环。


零摄氏度为273,15K;)

2
我知道(我有物理学硕士文凭),但我觉得小数点玩法的意义要小于幽默的一面,我试图用... ^ _ ^ ...
paercebal

20
您的段落被标记为“零,第一,第二,第三,四个,五个”。为了保持一致,您应该使用基数(“零,一,二,三,四,五”)或序数(“零,第一,第二,第三,第四,第五”)。:-)
ShreevatsaR 2010年

10
同样,对于我们生命的第一年,我们不是一岁而是零岁

3
@Nikita Rybak:令人惊讶的是,您错过了所有评论员在您面前看到的内容:当然,蜥蜴的比尔是正确的答案。这就是为什么我给他投票+1,这就是为什么它被选为问题的最佳答案的原因。我的答案更多是关于嘲笑基于1的数组背后的谬误原因,并提供一些具体的案例,其中基于1的数组会令人讨厌。尽管如此,令我惊讶的是,即使考虑到混杂着讽刺意味的原因,您也发现“没有一个令人信服的人”
paercebal 2010年

47

半开间隔构成良好。 如果您要交易0 <= i < lim并且想要按n元素扩展,则新元素的索引范围为lim <= i < lim + n。从零开始的数组工作使拆分或连接数组或对元素计数使算术更加容易。人们希望更简单的算法可以减少栅栏错误


半开时间间隔为+1,这使一切变得更容易。

29

某些类型的数组操作会因基于1的数组而变得复杂,而对于基于0的数组则更为简单。

我曾经做过一些数值分析编程。我正在研究用FORTRAN和C ++编写的压缩稀疏矩阵算法。

FORTRAN算法具有很多a[i + j + k - 2],而C ++具有a[i + j + k],因为FORTRAN数组基于1,而C ++数组基于0。


我同意。我发现基于1的数组很有用的唯一时刻就是我想为空项目索引腾出空间。例如,如果我有一个对象数组,并使用它们的索引作为句柄,并且想要有一个空句柄。
Fabio Ceconello

我也遇到了不必要的基于1的数组的麻烦,以我有限的经验,基于0的数组总是为数组索引生成更清晰的代码,但很少见。

如果FORTRAN和C ++索引各自的偏移量仅偏移1,则它们之间的差值将如何相差2?另外,为什么要 2?如果FORTRAN是基于1的,那么您不添加2(或1)吗?
RexE

@RexE:这就是它的工作原理,这就是为什么基于1的数组如此复杂的原因。
杰伊·巴祖兹

@RexE:假设您模拟一个平面的3d阵列。然后,以0为底,元素(0 0 0)对应于平面数组中的元素0。OTOH,如果基于1,则元素(1 1 1)对应于平面数组中的元素1:1 + 1 + 1-2。

22

数组中的索引实际上不是索引。它只是一个偏移量,它是距数组开头的距离。第一个元素在数组的开头,因此没有距离。因此,偏移量为0。


3
对于当今大多数设计的语言,这实际上是实现细节,不应在语言中出现(除非有其他更好的理由这样做)
Jens Schauder 2009年

17

原因不仅是历史原因:C和C ++仍然存在并且被广泛使用,并且指针算术是使数组从索引0开始的非常有效的原因。

对于缺少指针算术的其他语言,第一个元素是位于索引0还是1只是一种约定,而不是其他任何约定。
问题在于,使用索引1作为其第一个元素的语言不是真空存在的,它们通常必须与经常用C或C ++编写的库进行交互(您猜对了)。

VB及其派生的样式一直遭受数组从0或1开始的困扰,并且长期以来一直是问题的根源。

底线是:您的语言认为第一个元素索引并不重要,只要它在整个过程中保持一致即可。问题在于,将1视为第一索引会使实际操作更困难。


同意 一致性很重要,除非您完全避免使用低级代码(包括C / C ++),否则使用基于1的数组只会带来麻烦。
Shog9

当我们讨论它时,一个问题:您是否曾经以非平台特定的方式使用低级代码?换句话说,您总是在一个或另一个平台上,并且您必须知道哪个,对不对?
丹·罗森斯塔克

1
就像认为VB .NET通常受到不公正对待的人一样,我不得不说VB .NET在数组上的实践很糟糕。他们将差异分开,并使之更加混乱:数组从0开始,但是Dim a作为Integer(5)创建具有6个位置的数组。理由似乎是,拥有额外的位置要好于解决超出数组长度的错误。不幸的是,在这方面(以及其他问题,例如And和Or被按位划分),它们屈服于许多VB6程序员的要求,他们最终都没有使用VB .NET。
Kyralessa

1
@Kyralessa:不,其理由是要向后兼容VB6(自动升级助手…),尽管他们很清楚该表示法是违反直觉且容易出错的。在另一方面,And并且Or是按位已经什么都没有做与VB6,它是一个VB型语言的唯一合乎逻辑的解决方案。你AndAlsoOrElse你的逻辑运算。
Konrad Rudolph

And并且Or按位与VB6有关,因为它们在VB6中是按位的。丑陋的运算符AndAlsoOrElse应该按位进行运算,因为按位运算比逻辑运算要少得多。由于“向后兼容性”,语言上留下了许多丑陋的疣,例如即使默认情况下,ByVal仍然遍布整个地方。
Kyralessa

10

从零开始的数组起源于C甚至汇编程序。使用C,指针数学基本上像这样工作:

  • 数组的每个元素占用一定数量的字节。32位整数(显然)是4个字节;
  • 数组的第一个元素占用数组的地址,其后的元素位于大小相等的连续块中。

为了说明这一点,假定int a[4]位于0xFF00,地址为:

  • a [0]-> 0xFF00;
  • a [1]-> 0xFF04;
  • a [2]-> 0xFF08;
  • a [3]-> 0xFF0C。

因此,对于从零开始的索引,addres数学很简单:

元素地址=数组地址+索引* sizeof(type)

实际上,C中的表达式都是等效的:

  • a2];
  • 2 [a]; 和
  • *(a + 2)。

对于基于1的数组,数学(甚至是这样)稍微复杂一些。

因此,原因很大程度上是历史原因。


1
最初的问题已经指出“基于零的数组是一种实现细节,它源于数组和指针以及计算机硬件的工作方式,但是这类内容不应该反映在高级语言中。”

值得一提的是,允许基于N的数组的语言通常会生成具有自动以零运行时间成本计算的数组“偏移”的代码。
罗迪

8

如果使用从零开始的数组,则数组的长度是有效索引的集合。至少,这就是Peano算法所说的:

0 = {}
1 = 0 U {0} = {0}
2 = 1 U {1} = {0,1}
3 = 2 U {2} = {0,1,2}
...
n = n-1 U {n-1} = {0,1,2...n-1}

在某种意义上,这是最自然的表示法。


7

因为C中的数组和指针之间有很强的相关性

char* p = "hello";
char q[] = "hello";

assert(p[1] == q[1]);

assert(*p == *q)

* p与*(p + 0)相同

起始索引为1会使您以后头痛


5

堆是基于1的阵列的优点之一。给定索引ii的父级和左子级的索引为

PARENT[ i ] = i ÷2

LCHILD[ i ] = i ×2

但仅适用于基于1的阵列。对于从0开始的数组

PARENT[ i ] =(i +1)÷2-1

LCHILD[ i ] =(i +1)×2-1

然后,您具有i也是该索引(即[1,i ] 范围内的索引)的子数组大小的属性。

但最后没关系,因为您可以通过分配比平常多的一个元素并忽略第零个元素,将基于0的数组变成基于1的数组。因此,您可以选择在适当的时候获得基于1的数组的好处,并在几乎所有其他情况下保留基于0的数组以进行更清晰的算术运算。


4

我的感觉是,它是完全任意的。基于零或一的数组没有什么特别的。自从Visual Basic中解放自己(大多数情况下,有时我在Excel中做些小事情)以来,我还没有使用基于1的数组,而且... 是相同的。事实是,如果您需要数组的第三个元素,那么它只是一个实现细节,称为3或2。但是,处理数组的99%的工作只对两个绝对要点感兴趣:第一个元素和数量或长度。同样,这只是一个实现细节,第一个元素称为零而不是一个,或者最后一个元素称为count-1或count。

编辑:一些回答者提到基于1的数组更容易出现击剑错误。以我的经验,现在考虑一下,这是事实。我记得在VB中思考过,“这将奏效或会因为我被淘汰而失败。” 在Java中,这永远不会发生。尽管我以为自己会好起来,但一些回答者指出,从0开始的数组会导致更好的算术,甚至在您不必处理较低级别的lang时也是如此。


在PHP中,大多数在字符串函数内部的搜索都在未找到时返回FALSE。不是-1。
jmucchiello

您正在将计数与最后一个元素的索引混淆。空数组的计数始终为0,无论您使用的是基于零的数组还是基于一的数组。基于整数的数组的优点是,计数是最后一个元素的位置(但这仅是唯一的优点)。

关于这两点,都是正确的:后半部分已删除,因为对于从零开始的数组或从一开始的数组而言,这是相同的:如果元素为零,则计数为0。
丹·罗森斯塔克

我的意思是回答的最后一半...
Dan Rosenstark

4

作为一个十岁以上的C / C ++程序员,在Pascal和Delphi方面有着非常深厚的背景,我仍然想念Pascal强大的数组绑定和索引类型检查,以及随之而来的灵活性和安全性。一个明显的例子是每个月的数组数据保存值。

帕斯卡:

 Type Month = (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec);

  Var Days[Month] of integer;

  ... 
  if Year mod 4 = 0 then // yes this is vastly simplified for leap years and yes i don't know what the comment marker is in pascal and no i won't go look it up
    Days[Feb] := 29
  else
    Days[Feb] := 28;

在不使用+/- 1或“魔数”的情况下用C语言编写类似的代码非常具有挑战性。注意,像Days [2]和Days [Jan + Dec]这样的表达式根本无法编译,这对于仍在C或汇编语言中思考的人们来说可能是残酷的。

我不得不说Pascal / Delphi语言的很多方面我都不会错过,但是相比之下,基于C零的数组看起来确实只是“愚蠢”。


可能值得注意的是,您的算法在2100年之前是不正确的。en.wikipedia.org/ wiki/Leap_year#

2
我知道;-)但是,这在2000年是正确的。我只是在玩“发现学步车” ...
罗迪

当场学徒!大声笑。
jcollum

是。避免整个问题,根据需要建立阵列。
罗伦·佩希特尔

如果您的平均Pascal编译器在生成机器代码时分配Jan = 0,Dec = 11,我不会感到惊讶:-)

4

它以0而不是1开头的原因是,您可以将偏移量视为此元素距数组内存的起始位置有多远。这并不是说给我第0个元素,而是说给我一个从一开始就是0个元素的元素。

另一种看待它们的方式是(在大多数情况下)它们是等效的:

array[n]

*(array + n)

该标准永远不会更改的原因是因为C已有大约40年的历史了。没有令人信服的理由来更改它,如果进行了更改,则依赖于数组开头为0的所有现有代码都会被破坏。


事实上,你可以重写array[n]n[array]在C.这不是一个好主意,做它,它的混乱!但这是合法的(至少要达到C89),因为上面的标识和加法是可交换的。
Donal Fellows 2010年

那是一种疯狂的编写方式-如果您在必须维护的任何代码中看到的内容,那将是一个巨大的警告信号。值得庆幸的是,我还没有遇到过...):)
丹尼斯·蒙西

4

包含一些原始位置/相对位置信息的代码使用从0开始的数组更加清晰。

例如:将向量复制到较大向量中定义位置的代码很麻烦,数组从1开始:

function copyAtPos (dest, vect, i):
    for i from 1 -> vect.length do
        dest[pos+i-1] = vect[i]

通过与以0开始的数组相对:

function copyAtPos (dest, vect, i):
    for i from 0 -> vect.length-1 do
        dest[pos+i] = vect[i]

如果您开始编写大卷积公式,则必须使用它。


3

为什么不2或3或20?它不像从零开始的数组那样容易理解或基于1的数组。为了切换到基于1的数组,那里的每个程序员都必须重新学习如何使用数组。

而且,当您处理现有数组中的偏移量时,它也更有意义。如果您从数组中读取了115个字节,则知道下一个块从115开始。依此类推,下一个字节始终是您读取的字节的大小。从1开始,您需要一直添加一个。

而且有时您确实需要处理数组中的数据块,即使是没有“真正”指针算法的语言。在Java中,您可以在内存映射文件或缓冲区中存储数据。在这种情况下,您知道块i的大小为* i。对于基于1的索引,它将位于块* i + 1。

使用基于1的索引,很多技术将要求整个位置+1。


为什么不2或3或20?因为0是加法身份,而1是乘法身份。他们最有意义。

3

使用基于1的数组,将一维数组转换为多维数组:

int w = 5, h = 5, d = 5;

int[] a1 = new int[w * h * d], new a2 = int[w,h,d];

for (int z = 1; z <= d; z++)

  for (int y = 1; y <= h; y++)

    for (int x = 1; x <= w; x++)

      a1[x + (y - 1) * w + (z - 1) * h] = a2[x,y,z];

请注意,即使您的数组是基于1的,您的y和z索引也是基于0的(y-1,z-1)。在某些情况下,您无法避免从0开始的索引。为了保持一致性,为什么不总是使用基于0的索引?


3

为什么要数组从一个开始?

当您说时a[x][y],编译器将此转换为:a+(x*num_cols+y)。如果数组从1开始,则将变为a+(x*num_cols+y-1)每当您要访问数组元素时,这都是一个额外的算术运算。您为什么要减慢程序速度?


1
实际上,它会成为一个+((X - 1)* NUM_COLS)+ Y - 1) - x和y将从1开始
丹尼斯Munsie

2

我将在这里走出来,提出一些与整数“键控”数组不同的建议。

我认为您的同事正在物理上创建“集合”的一对一映射,而我们总是从1开始计数。我可以理解这一点,当您不花哨的时候,很容易理解一些代码当您在软件和物理世界之间一对一映射时。

我的建议

不要将基于整数的数组用于存储的内容,而应使用其他种类的字典或键值对。由于您不受任意整数的束缚,因此它们可以更好地映射到现实生活。它具有它的位置,由于在软件和物理世界之间映射概念1到1的好处,因此我建议您尽可能多地使用它。

kvp['Name Server'] = "ns1.example.com"; (这只是一百万个可能示例中的一个)。

免责声明

当您使用基于数学的概念时,这绝对不起作用,主要是因为数学更接近于计算机的实际实现。在这里使用kvp集将无济于事,但实际上会使事情变得混乱,并使问题更加严重。我还没有考虑过某些情况,例如kvp或数组可能会更好。

最终的想法是在有意义的地方使用从零开始的数组或键值对,请记住,当您只有一把锤子时,每个问题开始看起来像钉子...


2

就个人而言,一个参数是将数组索引视为偏移量。这很有意义。

可以说它是第一个元素,但是第一个元素相对于数组原点的偏移为零。这样,取数组原点并加零将产生第一个元素。

因此,在计算中,添加零来查找第一个元素比添加一个然后删除一个元素更容易。

我认为做过一些低级工作的人总是以零为底。而且那些开始或习惯于更高层次的人经常不使用算法编程,他们可能希望使用基本的系统。也许我们只是对过去的经验有偏见。


确实-从根本上讲,这是一种底层语言的约定。

2

使用基于0的索引而不是基于1的索引的仅有两个(非常严重的理由)似乎避免了重新培训大量程序员并且避免了向后兼容性

在收到的所有答案中,我都没有看到针对基于1的索引的任何其他严重争论。

实际上,索引自然是从1开始的,这就是为什么。

首先,我们必须问:数组是从哪里来的?他们有真实世界的对等物吗?答案是肯定的:它们是我们在计算机科学中对向量和矩阵进行建模的方式。但是,向量和矩阵是在计算机时代之前就使用基于1的索引的数学概念(如今仍大多使用基于1的索引)。

在现实世界中,索引是以1为基数的。

正如Thomas所说,使用0基索引的语言实际上是使用offset而不是索引。使用这些语言的开发人员会考虑偏移量,而不是索引。如果事情清楚地说明了,那不是问题,但事实并非如此。许多使用偏移量的开发人员仍在谈论索引。而且许多使用索引的开发人员仍然不知道C,C ++,C#,...使用偏移量。

这是一个措辞问题

(关于Diskstra的论文的注释- 正是我在上面所说的:数学家确实使用基于1的索引。但是Diskstra认为matematicians 不应该使用它们,因为这样某些表达式会很丑陋(例如:1 <= n <= 0 )。好吧,不确定他是否正确-进行这样的范式转换以避免那些异常的空序列似乎很麻烦,但最终结果还是...)


2
数学家并不总是使用基于1的索引。我已经看到x0花费了很多时间作为序列的初始值。这取决于哪个更方便。

2

您是否曾经被真正提到1900年代的“ 20世纪”所困扰?好吧,这是您在使用基于1的数组时一直处理的繁琐事情的一个很好的类比。

考虑一个常见的数组任务,如.net IO.stream read方法:

int Read(byte[] buffer, int offset, int length)

我建议您这样做,以说服基于0的数组更好:

在每种索引样式中,编写一个支持读取的BufferedStream类。您可以为基于1的数组更改Read函数的定义(例如,使用下限代替偏移量)。不需要任何花哨的东西,只需使其简单即可。

现在,这些实现中哪一个更简单?哪一个位置的偏移量为+1和-1?那正是我所想。实际上,我认为,索引样式无关紧要的唯一情况是何时应使用非数组(如Set)之类的东西。


不好的类比将整数逻辑与浮点数混淆了。

2

这是因为数组的构造方式。让他们从一个开始就没有多大意义。数组是内存中的基址,大小和索引。要访问第n个元素,它是:

base + n * element_size

所以0显然是第一个偏移量。


1

实际上,有几种不同的方法可以实现此目的:

  • 基于0的数组索引
  • 基于1的数组索引
  • 基于0或1的数组(例如VB 6.0 ...这确实很可怕)

最终,我认为语言使用基于0还是基于1的数组都没有关系。但是,我认为最好的选择是使用基于0的数组,原因很简单,因为大多数程序员都习惯于该约定,并且它与绝大多数已编写的代码一致。

但是,真正出错的唯一方法是与Visual Basic不一致。我当前正在维护的代码库分为基于0和1的数组。而且很难找出哪个是哪个。这将导致烦人的for循环:

dim i as integer, lb as integer, ub as integer
lb = LBound(array)
ub = UBound(array)
for i = lb to ub
       '...
next

哈哈哈,我记得那个,那个吸吮的人……
Dan Rosenstark

我想我记得甚至有以负数开头的数组。我远离VB的众多原因之一。

1

在讨论线性集合中某个项目的位置时,零是很自然的。

考虑一下装满书架的书架-第一本书的位置与书架的侧壁齐平-位置为零。

因此,我想这取决于您是否将数组索引视为查找事物或引用事物的一种方式。


1

我更喜欢基于0的索引,因为模(以及用于模的AND运算符)对于某些值总是返回0。

我经常发现自己使用这样的数组:

int blah = array[i & 0xff];

使用基于1的索引时,我经常会出错。


1

如果不编写很多基于数组的代码(例如字符串搜索和各种排序/合并算法)或在单维数组中模拟多维数组,则很难保护0基。Fortran是基于1的,因此您需要大量的咖啡才能正确完成这种代码。

但这远不止于此。能够思考事物的长度而不是其元素的索引是一种非常有用的心理习惯。例如,在制作基于像素的图形时,将坐标视为落在像素之间而不是落在像素上更为清楚。这样,一个3x3矩形包含9个像素,而不是16个像素。

一个更牵强的示例是在解析或在表中打印小计之前的超前思想。“常识”方法说:1)​​获取下一个字符,令牌或表行,以及2)决定如何处理它。前瞻性方法表示1)假定您可以看到它,并确定是否需要它,以及2)如果您确实想要它,则“接受”它(使您可以看到下一个)。然后,如果您写出伪代码,则要简单得多。

另一个示例是如何在您无法选择的语言(例如MS-DOS批处理文件)中使用“ goto”。“常识”方法是将标签附加到要完成的代码块上,并对其进行标签。通常,更好的方法是将标签放在代码块的末尾,以跳过它们。这使其成为“结构化的”并且易于修改。


1

就是这样,已经有很多年了。更改甚至辩论,就像更改或辩论更改交通信号灯一样没有意义。让我们使blue = stop,red = go。

查看《 C ++数值食谱》中随着时间的变化。他们曾使用宏伪造基于1的索引,但在2001年版中放弃了并加入了这一行。在其网站www.nr.com上可能有启发性的材料说明了背后的原因。

顺便说一句,烦人的是在数组之外指定范围的变体。例如:python vs. IDL; a [100:200] vs a [100:199]获得100个元素。只是要学习每种语言的怪癖。更改一种语言以使其匹配另一种语言会导致这种咬牙切齿的咬牙切齿,而不是解决任何实际问题。


1

我更喜欢基于0的数组,因为正如其他人所提到的,它使数学变得更容易。例如,如果我们有一个包含100个元素的一维数组,它们模拟一个10x10网格,那么在r行col c中该元素的数组索引i是什么:

从0开始:i = 10 * r + c
从1开始:i = 10 *(r-1)+ c

并且,给定索引i,返回到行和列为:

从0开始:c = i%10
         r =地板(i / 10)
从1开始:c =(i-1)%10 +1
         r = ceil(i / 10)

鉴于上述数学在使用基于1的数组时显然更复杂,因此选择基于0的数组作为标准似乎是合乎逻辑的。

但是,我认为有人可以声称我的逻辑有缺陷,因为我认为会有理由在1D数组中表示2D数据。我在C / C ++中遇到过许多这样的情况,但是我必须承认,执行这样的计算需要某种程度上取决于语言。如果数组确实一直在为客户端执行所有索引数学运算,则编译器可以在编译时将基于M的数组访问简单地转换为基于0的访问,并向用户隐藏所有这些实现细节。实际上,任何编译时常量都可以用于执行相同的操作集,尽管这种构造可能只会导致难以理解的代码。

也许有一个更好的论点是,要使基于1的数组的语言中的数组索引操作数量最少,就需要使用上限函数执行整数除法。但是,从数学角度来看,整数除法应返回d个余数r,其中d和r均为正。因此,应使用基于0的数组来简化数学。

例如,如果要生成具有N个元素的查找表,则值x的当前值在数组中的当前值之前最近的索引为(大约,忽略结果是四舍五入之前的整数的值):

基于0且带下限:floor((N-1)* x / xRange)
基于1的楼层:楼层((N-1)* x / xRange)+ 1
基于1的ceil:ceil((N-1)* x / xRange)

请注意,如果使用四舍五入的标准约定,则基于1的数组需要附加操作,这是不希望的。编译器无法隐藏这种数学运算,因为它需要有关幕后情况的低级知识。


在拥有支持多维数组的高级语言之前,这是一个很好的理由。

1

我敢打赌,程序员只是在日常思考中对基于0的数组的反直觉感到恼火,并且在争辩一种更直观的描述数组的方法。具有讽刺意味的是,作为人类,我们花了很多时间来提出“类”,以便我们可以在代码中以更人性化的方式描述事物,但是当查看0对1数组时,我们似乎挂了起来逻辑本身。

就计算机而言,从数学上讲0可能会更好,但是我觉得这里有一点遗漏。如果我们想用更人性化的方式来描述事物(例如类),为什么我们不希望在语言的其他部分也是如此?是不是同样不合逻辑或无效(或对此事优先考虑...)以使一种语言更易于人类理解和使用,因此,从广义上讲,它不那么容易产生逻辑错误,更容易加快生产可用作品的速度。PHP示例:

array(1 => 'January', 'February', 'March');

根据我们的要求给出基于1的数组。

为什么没有规范:

array('January', 'February', 'March');

例外是:

array(0 => 'Value for scenario where 0 *has* to be used as the key',
      'value2', 'value3');

以PHP为例,我的赌注是80%的时间是基于1的数组作为默认语法,这将减少现实世界中的用例中的逻辑错误,或者至少不会平均导致更多的逻辑错误,同时使它变得更多在编码器上更容易更快地生成可用代码。记住,我假设在需要时仍可以选择array(0 =>'value'),但同时也假设在大多数情况下,有一些更接近真实世界的描述是可行的。

从该角度来看,这听起来确实不是太过牵强。当使用界面时,无论是操作系统还是程序员的语言,我们都更接近人类的思维和习惯来设计界面,在大多数情况下我们会更快乐,而人类与计算机之间的误解也就更少了(人类逻辑-错误),更快的生产等。如果在现实世界中有80%的时间在进行列表或计数时我用1来描述事物,那么计算机理想情况下应该以很少的信息来理解我的意思,或者与我通常的描述方式有所不同。简而言之,我们可以对现实世界建模的越近,抽象的质量就越高。因此,他想要的绝不是愚蠢的,因为这是最终目标,并且将证明需要更多的抽象。计算机仍然可以最终将其视为基于0的数组的特殊用法。只要计算机以一种更简单,更直观的方式描述我想要的东西,并且随着时间的推移减少错误,我就不必在乎计算机如何解释它。

所以,那是我的两分钱。我严重怀疑他在说什么或被解释为他的意思。他可能的意思是,“我讨厌用一种不太直观的方式告诉计算机我想要什么。” :)我们不是所有人吗?大声笑。


1

如果在编写“自己的”代码时要小心,则有可能。您可以假设所有n> = 0的索引都从n开始,并进行相应编程。

关于标准,Borealid有一个很好的论据。

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.