【问题标题】:Why are zero-based arrays the norm?为什么从零开始的数组是常态?
【发布时间】:2008-12-26 04:12:05
【问题描述】:

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

现在我不太擅长辩论,所以我真的无法提供任何充分的理由来坚持使用从零开始的数组,除非它们感觉更合适。为什么 零是数组的共同起点?

【问题讨论】:

  • 在 n 元素数组中,元素 'n' 不存在。一个 n 元素数组的成员编号仅从 0 到 n-1。那么,如果我们有一个从 1 开始的数组,那么一个 n 元素数组实际上代表了数组中存在的 n 个元素,这不是更好吗?
  • 我喜欢从零开始的数组,因为字节中的第一位是 2^0,而不是 2^1。这有时对我有帮助:)
  • 如果您查看此列表,例如en.wikipedia.org/wiki/…,您会注意到大多数特定领域的语言从 1 开始索引,而来自 cs school of thinking 的大多数语言从 0 开始。如果搜索再过一会儿,他就会注意到,关于这个问题已经进行了如此多的愚蠢讨论,试图谈论这两者中的任何一个来改变他们的方式可能是毫无意义的。为了捍卫“1”,世界上大多数人都使用那个。大多数程序员使用“0”。大多数人不懂程序员,所以这让你觉得......

标签: array


【解决方案1】:

我认为没有人能提供比 Edsger W. Dijkstra 的文章 "Why numbering should start at zero" 更有力的论据。

【讨论】:

    【解决方案2】:

    权威论证

    嗯...显然,大多数语言,包括最近的语言,都是从零开始的。由于那些语言是由相当熟练的人编写的,所以你的朋友一定是错的......

    为什么是一个?

    为什么 1 比 0 是更好的起始索引?为什么不是 2 或 10?答案本身很有趣,因为它展示了很多人为这个想法辩护的过程。

    first 参数更自然,因为 1st 通常是 one 在所有其他参数之前,至少对于大多数人来说人...

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

    我仍然对我经常听到的这类论点的“质量”印象深刻......当我被提醒时更是如此......

    为什么不为零?

    ...“以一为基础”的符号是西方文化的遗留物,几个世纪以来一直忽视零的存在。

    信不信由你,最初的公历是从 -3、-2、-1、1、2、3... 试着想象一下它对西方科学的贡献(例如,从 1 日算起多少年) 1月-2日到1月2日看到比原来的公历和减法这么简单的东西冲突...)。

    保持基于 1 的数组就像(好吧,我会为此被降级...^_^ ...),在 21 世纪保持英里和码...

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

    首先(哎呀……对不起……我会再试一次)

    ,零什么都不是,一是什么。而一些宗教典籍认为“起初,什么都没有”。一些与计算机相关的讨论可能像宗教辩论一样激烈,所以这一点并不像看起来那么脱离主题...... ^_^

    首先,使用从零开始的数组并忽略其第零值比使用从一开始的数组并四处寻找其第零值更容易。这个理由几乎和前一个一样愚蠢,但是,最初支持基于 1 的数组的论点也是相当谬误的。

    第二,让我们记住,在处理数字时,你很可能会偶尔处理数学,而当你处理数学时,很有可能你没有心情让愚蠢的黑客绕过过时的约定。几个世纪以来,从一为基础的符号也一直困扰着数学和日期,通过从我们的错误中吸取教训,我们应该努力在面向未来的科学(包括计算机语言)中避免它。

    第三,至于与硬件绑定的计算机语言数组,分配一个21个整数的C数组,并将指针向右移动10个索引,你就会有一个自然的[-10到 10] 数组。这对于硬件来说是不自然的。但它适用于数学。当然,数学可能已经过时了,但我上次检查时,世界上大多数人都认为不是。

    ,正如已经在别处指出的那样,即使对于离散位置(或减少为离散值的距离),第一个索引也是零,就像建筑物中的地板(从零开始),递减倒计时(3、2、1、零!)、地面高度、图像的第一个像素、温度(零开尔文,对于绝对零或零摄氏度,水结冰温度为 273 K)。事实上,真正从一个开始的只有传统的“firstsecondthird等方式。 iteration 表示法,它自然地将我引向 next 点...

    下一个点(自然跟在上一个之后)是应该访问高级容器,而不是通过索引,而是通过迭代器,除非索引本身具有内在价值。我很惊讶您的“高级语言”倡导者没有提到这一点。在索引本身很重要的情况下,您可以打赌您有一半的时间会想到一个与数学相关的问题。因此,您希望您的容器是数学友好型的,而不是像从 1 开始的“你的旧公历”那样数学禁用,并且需要反刍的 hack 才能使其工作。

    结论

    你的程序员同事给出的论点是一个谬误,因为它不必要地将口语/书面语言习惯与计算机语言(本质上是模糊的)联系在一起(你不希望你的指令模糊),并且因为归因于这个问题的硬件原因是错误的,他希望让你相信,随着语言在抽象方面越来越高,从零开始的数组已经成为过去。

    从零开始的数组是从零开始的,因为数学相关的原因。不是硬件相关的原因。

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

    【讨论】:

    • 零摄氏度是 273,15K ;)
    • 我知道(我有物理学硕士文凭),但我觉得玩小数比我试图用幽默的一面来着色我的论点更重要...... ^_^ ...
    • 您的段落标记为“零、一、二、三、四、五”。为了保持一致性,您应该使用基数(“零、一、二、三、四、五”)或序数(“零、一、二、三、四、五”)。 :-)
    • 同样,我们人生的第一年,不是一岁,而是零岁
    • @Nikita Rybak:令人惊奇的是,您错过了之前所有评论员所看到的内容:当然,蜥蜴比尔的答案是正确的。这就是为什么我给他投票+1,这就是为什么它被选为问题的最佳答案。我的回答更多是为了取笑基于 1 的数组背后的错误原因,并提供基于 1 的数组会令人讨厌的具体案例。尽管如此,我很惊讶你发现“没有一个令人信服的”,即使考虑到其中的原因也带有讽刺意味......
    【解决方案3】:

    半开区间组合得很好。如果您正在处理 0 <= i < lim 并且您想扩展 n 元素,则新元素的索引范围为 lim <= i < lim + n。在拆分或连接数组或计算元素时,使用从零开始的数组使算术更容易。人们希望更简单的算法能够减少栅栏错误

    【讨论】:

      【解决方案4】:

      某些类型的数组操作在使用基于 1 的数组时会变得非常复杂,但使用基于 0 的数组仍然会更简单。

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

      FORTRAN 算法有很多 a[i + j + k - 2],而 C++ 有很多 a[i + j + k],因为 FORTRAN 数组是从 1 开始的,而 C++ 数组是从 0 开始的。

      【讨论】:

      • 我同意。我发现基于 1 的数组有用的唯一时刻是当我想为空项索引腾出空间时。例如,如果我有一个对象数组并将它们的索引用作句柄,并且想要一个空句柄。
      • 我也遇到了基于 1 的数组的不必要的复杂性,根据我有限的经验,基于 0 的数组总是为数组索引生成更清晰的代码,但很少有例外。
      • 如果 FORTRAN 和 C++ 索引各自的索引仅偏移 1,它们将如何相差 2?另外,为什么 2?如果 FORTRAN 是基于 1 的,那么你不会加 2(或 1)吗?
      • @RexE:这就是它的工作原理,这就是为什么它对基于 1 的数组如此复杂。
      • @RexE:假设你用一个平面模拟一个 3d 数组。然后,在 0 基中,元素 (0 0 0) 对应于平面数组中的元素 0。 OTOH,如果是基于1的,则元素(1 1 1)对应于平面数组中的元素1:1+1+1-2。
      【解决方案5】:

      数组中的索引并不是真正的索引。它只是一个偏移量,即距数组起点的距离。第一个元素位于数组的开头,因此没有距离。因此偏移量为 0。

      【讨论】:

      • 对于现在设计的大多数语言来说,这实际上是一个实现细节,不应该出现在语言中(除非有其他更好的理由这样做)
      【解决方案6】:

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

      对于其他缺乏指针算法的语言,第一个元素是在索引 0 还是 1 更多的是一种约定,而不是其他任何东西。
      问题是使用索引 1 作为其第一个元素的语言并不存在于真空中,并且通常必须与通常用 C 或 C++ 编写的库进行交互...

      VB 和它的派生版本因数组从 0 或 1 开始而受到影响,长期以来这一直是问题的根源。

      底线是:只要始终保持一致,您的语言将第一个元素索引视为什么并不重要。问题在于,将 1 作为第一个索引会使其在实践中更难使用。

      【讨论】:

      • 同意。一致性很重要,除非您有幸完全避免低级代码(包括 C/C++),否则使用基于 1 的数组只是自找麻烦。
      • 当我们在这里时,一个问题:您是否曾经以非平台特定的方式使用低级代码?换句话说,你总是在一个或另一个平台上,你必须知道哪个,对吧?
      • 作为一个认为 VB .NET 通常受到不公平的诽谤的人,我不得不说 VB .NET 在数组上的实践很糟糕。他们将差异分开并使其更加混乱:数组从 0 开始,但 Dim a as Integer(5) 创建了一个具有 6 位置的数组。理由似乎是拥有一个额外的位置比解决超出数组长度的错误要好。不幸的是,在这方面(以及其他问题,如 And 和 Or 是按位计算),他们屈服于许多 VB6 程序员的要求,他们最终并没有使用 VB .NET。
      • @Kyralessa:不,基本原理是向后兼容 VB6(自动升级助手……),尽管他们很清楚这种表示法违反直觉且容易出错。另一方面,AndOr 按位与 VB6 无关,它是 VB 类型语言的唯一合乎逻辑的解决方案。您确实AndAlsoOrElse 用于您的逻辑操作。
      • AndOr 按位与 VB6 有关,因为它们在 VB6 中是按位的。丑陋的运算符AndAlsoOrElse 应该按位进行,因为按位运算远不如逻辑运算常见。由于“向后兼容性”,语言上留下了很多丑陋的疣,例如 ByVal 被贴满整个地方,即使它是默认设置。
      【解决方案7】:

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

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

      为了说明,假设int a[4]在0xFF00,地址是:

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

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

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

      其实C中的表达式都是等价的:

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

      对于从 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}
      

      所以从某种意义上说,这是最自然的符号。

      【讨论】:

        【解决方案9】:

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

        char* p = "hello";
        char q[] = "hello";
        
        assert(p[1] == q[1]);
        
        assert(*p == *q)
        

        *p 等同于 *(p + 0)

        起始索引为 1 会在以后让您头疼

        【讨论】:

          【解决方案10】:

          堆是基于 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 开始的数组以实现更简洁的算术。

          【讨论】:

            【解决方案11】:

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

            编辑:一些回答者提到基于 1 的数组更容易出现栅栏错误。以我的经验,现在想想,这是真的。我记得在 VB 中我曾想过,“这要么会奏效,要么会因为我落后一个而崩溃。”在 Java 中,这永远不会发生。尽管我认为我变得更好了,但一些回答者指出了基于 0 的数组会产生更好的算术的情况,即使您不必处理较低级别的语言也是如此。

            【讨论】:

            • 在 PHP 中,字符串函数内部的大多数搜索在未找到时返回 FALSE。不是 -1。
            • 您将计数与最后一个元素的索引混淆了。无论您使用从零开始的数组还是从一开始的数组,空数组的计数始终为 0。基于 1 的数组的优点是计数是最后一个元素的位置(但这是唯一的优点)。
            • 这两点都是正确的:删除后半部分,因为它对于从零开始或从一开始的数组是相同的:如果你有零个元素,则计数为 0。
            • 我的意思是我回答的后半部分......
            【解决方案12】:

            作为一名 10 年以上的 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#Algorithm
            • 我知道 ;-) 但是,它在 2000 年是正确的。我只是在玩“spot the pedant”...
            • 发现书呆子!哈哈。
            • 是的。避免整个问题,根据您的需要设置数组。
            • 如果您的普通 Pascal 编译器在生成机器代码时分配 Jan = 0,Dec = 11,我不会感到惊讶 :-)
            【解决方案13】:

            它从 0 而不是 1 开始的原因是,您可以将偏移量视为该元素距数组内存开头的距离。不是说给我第 0 个元素——而是说,给我一个从一开始就是 0 个元素的元素。

            另一种看待它的方式是,它们(大部分)是等价的:

            array[n]
            
            *(array + n)
            

            标准永远不会改变的原因是因为 C 已经存在了大约 40 年。没有令人信服的理由来改变它,如果他们这样做了,所有依赖于数组开头为 0 的现有代码都会被破坏。

            【讨论】:

            • 其实可以在C语言中将array[n]改写为n[array]。这样做可不是什么好主意,很混乱!但它是合法的(至少在 C89 之前),因为上面的身份以及加法是可交换的。
            • 这是一种疯狂的写法——如果你在任何代码中看到你必须维护的东西,那将是一个巨大的警告信号。谢天谢地,我还没有遇到过…… :)
            【解决方案14】:

            包含一些原始位置/相对位置信息的代码在数组从 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]
            

            如果您开始编写大卷积公式,则必须这样做。

            【讨论】:

              【解决方案15】:

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

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

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

              使用基于 1 的索引,很多技术都需要 +1。

              【讨论】:

              • 为什么不是 2 或 3 或 20?因为 0 是加法恒等式,而 1 是乘法恒等式。它们是最有意义的。
              【解决方案16】:

              使用从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 开始的索引?

              【讨论】:

                【解决方案17】:

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

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

                【讨论】:

                • 实际上,它必须变成 + ((x - 1) * num_cols) + y - 1) -- x 和 y 都从 1 开始。
                【解决方案18】:

                我将在这里跳出一个肢体,并提出与整数“键控”数组不同的建议。

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

                我的建议

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

                kvp['Name Server'] = "ns1.example.com";(这只是百万分之一的例子)。

                免责声明

                当您使用基于数学的概念时,这绝对行不通,主要是因为数学更接近计算机的实际实现。在这里使用 kvp 集不会有任何帮助,但实际上会使事情变得混乱并使其更成问题。我还没有考虑过所有可能作为 kvp 或数组更好地工作的极端情况。

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

                【讨论】:

                  【解决方案19】:

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

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

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

                  我认为任何做一些较低级别的事情的人总是认为从零开始。并且开始或习惯于更高级别的人通常不是算法编程可能希望有一个基本的系统。或者,也许我们只是被过去的经验所左右。

                  【讨论】:

                  • 完全正确 - 它基本上是一种来自低级语言的约定。
                  【解决方案20】:

                  使用基于 0 的索引而不是基于 1 的索引的唯一两个(非常)严重的原因似乎是避免重新培训大量程序员向后兼容

                  在您收到的所有答案中,我没有看到任何其他反对基于 1 的索引的严肃论据。

                  事实上,指数自然是从 1 开始的,这就是原因。

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

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

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

                  这是一个措辞问题

                  (请注意关于 Diskstra 的论文 - 它完全符合我上面所说的内容:数学家 确实使用基于 1 的索引。但 Diskstra 认为数学家不应该 使用它们是因为某些表达式会很丑陋(例如:1

                  【讨论】:

                  • 数学家并不总是使用从 1 开始的索引。我已经看到 x0 多次用于序列的初始值。这取决于哪个更方便。
                  【解决方案21】:

                  您是否曾经对“20 世纪”实际上指的是 1900 年代感到恼火?嗯,这是一个很好的类比,你在使用基于 1 的数组时一直处理的乏味事情。

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

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

                  我建议你这样做来说服自己基于 0 的数组更好:

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

                  现在,哪一种实现更简单?哪一个有 +1 和 -1 偏移量?那正是我所想。事实上,我认为索引样式无关紧要的唯一情况是您应该使用不是数组的东西,例如 Set。

                  【讨论】:

                  • 将整数逻辑与浮点数混淆是一个糟糕的类比。
                  【解决方案22】:

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

                  base + n * element_size
                  

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

                  【讨论】:

                    【解决方案23】:

                    实际上有几种不同的实现方式:

                    • 基于 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
                    

                    【讨论】:

                    • 哈哈哈我记得,那个烂人……
                    • 我想我记得甚至有以负数开头的数组。这只是我远离 VB 的众多原因之一。
                    【解决方案24】:

                    当谈论线性集合中项目的位置时,零是很自然的。

                    想想一个装满书的架子 - 第一本书与书架的侧壁齐平 - 这是位置零。

                    所以我想这取决于您是否将数组索引视为查找事物或引用事物的手段。

                    【讨论】:

                      【解决方案25】:

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

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

                      int blah = array[i & 0xff];
                      

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

                      【讨论】:

                        【解决方案26】:

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

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

                        一个有点牵强的例子是在解析或打印表格中的小计中的前瞻概念。 “常识”方法说 1) 获取下一个字符、标记或表格行,以及 2) 决定如何处理它。前瞻方法说 1) 假设您可以看到它,并决定您是否想要它,以及 2) 如果您确实想要它,“接受”它(这允许您看到下一个)。那么如果把伪代码写出来,就简单多了。

                        还有一个例子是如何在你别无选择的语言中使用“goto”,例如 MS-DOS 批处理文件。 “常识”方法是将标签附加到要完成的代码块上,并将它们标记为这样。通常更好的方法是将标签放在代码块的末尾,以便跳过它们。这使它“结构化”并且更容易修改。

                        【讨论】:

                          【解决方案27】:

                          就是这样,而且已经很多年了。改变它,甚至争论它,就像改变或争论改变红绿灯一样毫无意义。让我们让 blue=stop,red=go。

                          查看随着时间的推移在 C++ 的数值配方中所做的更改。他们曾使用宏来伪造基于 1 的索引,但在 2001 年版中放弃并加入了这一行列。在他们的网站 www.nr.com 上可能有关于这背后原因的启发性材料

                          顺便说一句,从数组中指定范围的变体也很烦人。示例:python 与 IDL; a[100:200] 与 a[100:199] 得到 100 个元素。只需要学习每种语言的怪癖。改变一种以一种方式来匹配另一种方式的语言会导致这种诅咒和咬牙切齿,并不能解决任何实际问题。

                          【讨论】:

                            【解决方案28】:

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

                            从 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 开始的数组作为标准似乎是合乎逻辑的。

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

                            也许更好的论点是,在使用从 1 开始的数组的语言中,最小化数组索引操作的数量需要使用上限函数执行整数除法。但是,从数学角度来看,整数除法应该返回 d 余数 r,其中 d 和 r 都是正数。因此,应该使用从 0 开始的数组来简化数学运算。

                            例如,如果您正在生成一个包含 N 个元素的查找表,那么在值 x 的数组中的当前值之前最近的索引将是(近似地,在舍入之前忽略结果为整数的值): 0-based with floor: floor((N - 1) * x / xRange) 1-based with floor: floor((N - 1) * x / xRange) + 1 1-based with ceil : ceil ((N - 1) * x / xRange)

                            请注意,如果使用向下舍入的标准约定,则基于 1 的数组需要额外的操作,这是不可取的。编译器无法隐藏这种数学运算,因为它需要了解幕后发生的事情的底层知识。

                            【讨论】:

                            • 在您拥有支持多维数组的高级语言之前,这是一个很好的理由。
                            【解决方案29】:

                            我敢打赌,程序员只是对日常思考中基于 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 的数组的特殊用途。我不太关心计算机是如何解释它的,只要它是一种更简单、更直观的方式来描述我想要的内容,并且随着时间的推移会出现更少的错误。

                            所以,这是我的两分钱。我严重怀疑他所说的或解释的内容是他的意思。他的意思可能是,“我讨厌用不那么直观的方式告诉计算机我想要什么。” :) 我们不是吗?哈哈。

                            【讨论】:

                              【解决方案30】:

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

                              关于标准,Borealid 有很大的论据。

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 2016-04-07
                                • 1970-01-01
                                • 2014-04-19
                                • 1970-01-01
                                • 2011-07-04
                                • 2013-03-09
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多