【问题标题】:Why does the indexing start with zero in 'C'?为什么索引在“C”中从零开始?
【发布时间】:2011-11-11 08:25:06
【问题描述】:

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

【问题讨论】:

  • 都是关于指针的!
  • Defend zero-based arrays 的可能重复项
  • 指针(数组)是一个内存方向,索引是该内存方向的偏移量,所以指针(数组)的第一个元素是偏移量等于0的那个。
  • @drhirsch 因为当我们计算一组对象时,我们首先指着一个对象说“一个”。
  • 美国人从一楼数一楼的楼层数;英国人从零开始计数(一楼),上到一楼,然后是二楼,依此类推。

标签: c arrays


【解决方案1】:

在 C 中,数组的名称本质上是一个指针[但参见 cmets],是对内存位置的引用,因此表达式 array[n] 指的是内存位置 @987654324 @ 元素远离起始元素。这意味着索引被用作偏移量。数组的第一个元素正好包含在数组引用的内存位置(0 个元素之外),所以它应该表示为array[0]

更多信息:

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

【讨论】:

  • 数组名就是数组名;与常见的误解相反,数组在任何意义上都不是指针。数组表达式(例如数组对象的名称)通常但不总是被转换为指向第一个元素的指针。示例:sizeof arr 产生数组对象的大小,而不是指针的大小。
  • 虽然您显然没有对@KeithThompson 的评论做出反应,但我希望您使用更冒犯的课程:“在 C 中,数组的名称本质上是一个指针,一个引用到内存位置" - 不,它不是。至少不是从一般的角度来看。虽然您的答案完美地以 0 作为索引开始的方式回答了这个问题,但第一句话显然是不正确的。数组并不总是衰减为指向其第一个元素的指针。
  • 引用自 C 标准 (C18), 6.3.2.1/4: "除非它是 sizeof 运算符或一元 & 运算符的操作数,或是用于初始化数组的字符串字面量,类型为“类型数组”的表达式将转换为类型为“类型指针”的表达式,该类型指向数组对象的初始元素且不是左值。如果数组对象有寄存器存储类,行为未定义。"
  • 此外,这种衰减以比这里建议的更“隐含”或“正式”的方式发生;所涉及的内存中的指针对象没有衰减。这是这个问题的对象:Is the array to pointer decay changed to a pointer object? - 请编辑您的答案以完全正确。
【解决方案2】:

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


关于以上原因

虽然 Dijkstra's article(之前在现已删除的 answer 中引用)从数学角度来看是有意义的,但在编程方面它并不那么相关

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


可能的原因

引自 Danny Cohen 的a Plea for Peace

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

这可以很容易地测试。在 base-2 中,取2^3 = 8 第8个数字是:

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

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


为什么这是相关的

计算机内存地址具有由N 位寻址的2^N 单元。现在,如果我们从 1 开始计数,2^N 单元将需要N+1 地址行。需要额外的位才能访问恰好 1 个地址。 (在上述情况下为1000。)。另一种解决方法是让最后一个地址不可访问,并使用N 地址行。

两者都是次优解决方案,与从 0 开始计数相比,这将保持所有地址都可访问,完全使用 N 地址行!


结论

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


引自论文:

【讨论】:

  • 如果他们刚刚删除了位 0.. 那么第 8 个数字仍然是 111...
  • 您是否真的建议修改基本算术以使其适应?您不认为我们今天拥有的解决方案要好得多吗?
  • 几年后我的 2 美分价值。以我的经验(大约 35 年的编程经验),以一种或其他形式出现的模或模加法运算经常出人意料地出现。以零为基数的下一个序列是 (i+1)%n,但以 1 为基数的则是 (i-1)%n)+1,所以我认为以 0 为基础是首选。这在数学和编程中经常出现。也许只有我自己或我工作的领域。
  • 虽然所有充分的理由我认为它更简单:a[b] 在早期的编译器中被实现为*(a+b)。即使在今天,您仍然可以写 2[a] 而不是 a[2]。现在,如果索引不是从 0 开始,那么 a[b] 将变成 *(a+b-1)。这将需要在 CPU 上增加 2 次而不是 0 次,这意味着速度减半。显然不可取。
  • 仅仅因为你想要 8 个状态,并不意味着你必须在其中包含数字 8。我家的电灯开关很乐意代表“开灯”、“关灯”状态,从不奇怪为什么它们不代表数字 2。
【解决方案3】:

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

考虑:

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

要访问 0,我们这样做:

foo[0] 

但是foo分解成指针,上面的访问有类似的指针算术访问方式

*(foo + 0)

这些天指针算术没有被频繁使用。回到过去,这是一种方便的方式来获取地址并将 X“整数”从该起点移开。当然,如果你想留在原地,你只需加 0!

【讨论】:

    【解决方案4】:

    因为从 0 开始的索引允许...

    array[index]
    

    ...实现为...

    *(array + index)
    

    如果索引从 1 开始,编译器需要生成:*(array + index - 1),而这个“-1”会影响性能。

    【讨论】:

    • 你提出了一个有趣的观点。它可能会损害性能。但是,性能损失是否足以证明使用 0 作为起始索引是合理的?我对此表示怀疑。
    • @FirstNameLastName 基于 1 的索引与基于 0 的索引相比没有优势,但它们的性能(稍微)差一些。无论增益有多“小”,这都证明了基于 0 的索引是合理的。即使基于 1 的索引提供了一些优势,但 C++ 的精神是选择性能而不是便利。 C++ 有时用在每一个性能都很重要的环境中,这些“小”的东西可以很快加起来。
    • 是的,我知道小事可以加起来,有时会变成大事。例如,每年 1 美元并不是很多钱。但是,如果有 20 亿人捐赠它,那么我们可以为人类做很多好事。我正在寻找一个类似的可能导致性能不佳的编码示例。
    • 应该使用array-1的地址作为基地址,而不是减1。那就是我们在我曾经工作过的编译器中所做的。这消除了运行时减法。当您编写编译器时,这些额外的指令非常重要。编译器将用于生成数千个程序,每个程序可能会被使用数千次,而额外的 1 条指令可能会在 n 平方循环内的几行中出现。它可能会浪费数十亿个周期。
    • 不,编译后不会影响性能,只会增加一点构建时间,因为最终会被翻译成机器码,只会伤害编译器设计者。
    【解决方案5】:

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

    Reference

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

    "...这使得实现更简单..."

    【讨论】:

    • +1 不知道为什么投反对票。虽然它没有直接回答这个问题,但基于 0 的索引对于人或数学家来说并不自然——这样做的唯一原因是因为实现在逻辑上是一致的(简单)。
    • @phkahler:错误在于作者和语言将数组索引称为索引;如果您将其视为偏移量,那么从 0 开始对于外行人来说也很自然。考虑时钟,第一分钟写成 00:00,不是 00:01,不是吗?
    • +1 -- 这可能是最正确的答案。 C 早于 Djikistras 论文,是最早的“从 0 开始”的语言之一。 C 最初是“作为高级汇编程序”的,K & R 很可能希望尽可能地坚持在汇编程序中完成的方式,在这种方式下,您通常会有一个基地址和一个从零开始的偏移量。
    • 我认为问题是为什么使用基于 0,而不是哪个更好。
    • 我不会投反对票,但正如 progrmr 在上面评论的那样,可以通过调整数组地址来处理基础,因此无论基础执行时间如何,这在编译器或解释器中实现都很简单,所以它并没有真正实现更简单的实现。见证 Pascal,您可以使用任何范围来索引 IIRC,它已经 25 年了;)
    【解决方案6】:

    出于同样的原因,当周三有人问你离周三还有多少天时,你说 0 而不是 1,当周三有人问你离周四还有多少天时,你说 1 而不是 2 .

    【讨论】:

    • 您的回答似乎只是见仁见智。
    • 嗯,这就是添加索引/偏移量的原因。例如,如果“今天”为 0,“明天”为 1,则“明天的明天”为 1+1=2。但是如果“今天”是 1,“明天”是 2,那么“明天的明天”就不是 2+2。在数组中,只要您想将数组的子范围本身视为一个数组,就会发生这种现象。
    • 将 3 个事物的集合称为“3 个事物”并将它们编号为 1,2,3 并不是缺陷。即使在数学中,用从第一个偏移量对它们进行编号也是不自然的。唯一一次在数学中从零开始索引是当您想在多项式中包含诸如零次方(常数项)之类的内容时。
    • 回复:“以 1 而不是 0 开头的数组编号适用于严重缺乏数学思维的人。”我的 CLR 的“算法简介”版本使用基于 1 的数组索引;我不认为作者在数学思维上有缺陷。
    • 不,我会说第七个在索引 6 处,或者距离第一个有 6 个位置。
    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      我来自 Java 背景。我已经在下图中给出了这个问题的答案,我写在一篇不言自明的论文中

      主要步骤:

      1. 创建参考
      2. 数组实例化
      3. 向数组分配数据

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

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

      【讨论】:

        【解决方案9】:

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

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

        现在让我们考虑地址的开头是12element 的大小是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,从技术上讲,array 中的第一个元素地址将是 16,这是错误的,因为它的位置是 12

        【讨论】:

          【解决方案10】:

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

          虽然根据上面的数字来识别项目并不可怕,但将 (X,Y) 范围内的第一个项目定义为 X 之上的项目通常比将其定义为下面的项目更好(X+1)。

          【讨论】:

            【解决方案11】:

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

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

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

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

            因为整数的大小是 4 位
            因此 --> 考虑到索引 1 的位置将是
            索引大小 (1) * 整数大小 (4) = 4
            所以它会显示给我们的实际位置是

            100 + 4 = 104

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

            现在假设我们已经从 0
            然后
            第一个元素的位置应该是
            索引大小 (0) * 整数大小 (4) = 0

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

            那是元素的实际位置
            这就是索引从 0 开始的原因;

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

            【讨论】:

              【解决方案12】:

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

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                  【解决方案14】:

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

                  【讨论】:

                    【解决方案15】:

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

                    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 开始。

                    参考:所有细节都写在《The C programming language by brian kerninghan and dennis ritchie》一书中

                    【讨论】:

                      【解决方案16】:

                      数组名是一个指向基地址的常量指针。当你使用 arr[i] 时,编译器将其操作为 *(arr+i)。由于 int 范围是 -128 到 127,编译器认为 -128 到-1 是负数,0 到 128 是正数。所以数组索引总是从零开始。

                      【讨论】:

                      • 'int range is -128 to 127'是什么意思?需要int 类型才能支持至少 16 位范围,而如今在大多数系统上都支持 32 位。我认为您的逻辑有缺陷,并且您的答案确实没有改善其他人已经提供的其他答案。我建议删除它。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-05-29
                      • 1970-01-01
                      相关资源
                      最近更新 更多