【问题标题】:Array length in C languageC语言中的数组长度
【发布时间】:2020-06-18 16:34:20
【问题描述】:

我以前是 C# 程序员,对于 C 语言有一些我不明白的地方(具体来说,我正在使用 C99 标准进行编码)

我被告知无法知道 C 中数组的长度,我需要将其长度作为参数发送给我愿意使用的函数,但这是为什么呢? 例如,在 C# 中,我们可以输入 array_name.lenght

在二维数组中,为什么我必须指定数组的列数?我的意思是为什么这样做:

void test1 (int arr[][m])
{
}

虽然不是这样:

void test2 (int arr[][])
{
}

【问题讨论】:

  • “我需要将它的长度作为参数发送给我愿意使用的函数,但这是为什么呢?”因为该语言不支持其他方式。
  • 这实际上是一个很好的问题——他问的是运行时环境的差异和历史视角,这比大多数人思考一门语言的深度要深。他不只是在说“为什么 C 很臭?”
  • 您当然可以确定数组的长度:sizeof array / sizeof array[0]。问题是 C 不允许数组作为函数参数,而看起来像数组类型的参数实际上是指针类型的。强烈推荐阅读:comp.lang.c FAQ 的第 6 节。

标签: c arrays function parameters c99


【解决方案1】:

以 C# 为例,我们可以输入 array_name.length

我不使用 C#,但是,如果在子例程中可以获取在其他地方创建的数组的长度,那么有关该长度的信息必须存储在内存中并与数组一起传递。某些东西必须将该长度放入内存中,并且当数组作为参数传递时,某些东西必须包含的信息不仅仅是数组的长度。所以 C# 正在使用内存和计算时间。

这样做的结果是您无法直接控制计算机。你不能编写一个更简单更高效的程序,只要有东西传递额外的信息。这必然是浪费。只要您在有大量可用资源的情况下编写程序,那就没问题了。

C 不会做这种额外的努力。传递数组时,只传递其位置,这就是访问其元素所需的全部内容。如果一个特定的子例程需要它的长度,您可以手动传递它——您可以选择在需要时进行,但您也可以选择在不需要时不浪费资源。您可以编写更高效的程序。

在二维数组中为什么要指定数组的列数?

如果我们知道arrint 的数组,我们知道元素arr[0] 位于开头,arr[1] 紧随其后,arr[2] 位于其后,依此类推。要使用一维数组,我们唯一需要知道的是它从哪里开始。

如果我们知道arrayint 的二维数组,我们知道a[0][0] 在开头,arr[0][1] 在之后,依此类推,但我们不知道arr[1][0] 在哪里是。它在一些元素arr[0][i] 之后,但我们不知道有多少,除非我们知道第二维。因此,为了使用二维数组,您必须知道第二维的长度。这是一个合乎逻辑的要求,而不是一个选择。

补充

一般来说,例程只需要知道它应该使用数组的哪些元素。它不需要知道数组中有多少元素。

例程不需要指定数组长度的情况包括:

  • 要计算缓冲区中字符串的长度,例程(如strlen)只需要检查缓冲区中的每个字节,直到找到一个空字节。它不需要知道整个缓冲区有多大。 (示例:一个程序创建一个 100 字节的缓冲区。它从终端读取字节,直到找到一个换行符。用户只键入 12 个字符,然后输入一个换行符。缓冲区填充了 12 个字节和一个空字符. 检查字符串的子程序只需要 13 个字节,而不是 100 个字节。)
  • 例程可能在固定数量的元素上工作。例如,一个帮助数值积分的子程序可能一次取三个函数值,将曲线拟合到它们,并返回曲线下的面积。主例程可能有一个完整的函数值数组,它反复调用子例程来评估数组中的不同点,向子例程传递一个指向要处理的位置的指针。在每次调用中,子程序只需要知道给定地址的三个值。它不需要知道整个数组中有多少。
  • 一个例程可能对多个数组中相同数量的元素起作用。例如,执行离散傅立叶变换的例程可能需要处理多个元素N 和四个数组:一个用于输入实部,一个用于输入虚部,一个用于输出一个实部,一个用于输出虚部。对于每个数组,例程使用 N 个元素。这个数字N只需要在一个参数中传递给例程。将其存储在多个位置(每个阵列一个位置)会很浪费。

另一个考虑是有时我们只将数组的一部分传递给例程。如果我在缓冲区中有一些字符串,我可能希望一个子例程只处理该字符串的一部分,也许只是一个已解析命令中的一个单词。为此,我可以只传递一个指向该单词开头的指针和要处理的单词的长度。在这种情况下,子程序不仅不需要知道数组的长度,甚至不需要知道数组从哪里开始。它只需要知道它被要求做什么。传递任何其他信息将是一种浪费。

【讨论】:

  • 另外两个问题:您提到“但您也可以选择在不需要时不浪费资源。您可以编写更高效的程序。”但是我怎么可能需要和使用一个数组而不知道它的尺寸呢?你能举个例子吗?我也认为这根本不是一项繁重的任务,因为我们节省的只是一小部分,我们真的需要那么高效吗?为什么?
  • @Daniel98:您无需知道数组的大小即可使用它。您只需要知道应该使用多少个元素(以及哪些)。如果我要求一个子程序在一个指针处对三个元素求和,我将它放在一个数组的中间,它只需要知道它应该使用三个元素。它不需要知道整个数组中有多少元素。
  • @Daniel98:是的,效率很重要。指针在程序中经常被传递。我们制作链表,将文本读入缓冲区,解析文本并将指针传递给子程序,我们使用指针构造树和其他数据结构。与图形窗口一起使用的例程具有指向窗口的指针和指向其中子结构的指针,例如有关存在哪些按钮以及单击按钮时要调用哪些例程的信息,指向其中包含哪些文本的指针等等。如果每个指针都必须包含有关事物大小的信息……
  • …指出,很多程序占用的空间会增加相当多。
【解决方案2】:

在大多数编程语言中,数据类型是抽象:也就是说,如果您要求一个数字列表,它将在内存中创建用于存储数字列表的结构,并用于跟踪它的容量,有多少元素已满,以及元素是否为“空”或包含值等。

C 是一种不处理抽象的低级语言;它直接处理物理内存。如果您要求空间来放置 5 个整数,它会为 5 个整数分配内存。您希望它在某处跟踪数字“5”以记住您分配了 5 个整数?这不是你要求的——你必须自己做。

【讨论】:

  • 里奇语言处理物理内存。 clang 和 gcc 优化器处理的语言要求程序员将其内存使用限制为与优化器抽象匹配的内存,并且如果程序需要执行这些抽象不适应的任何事情,则其行为将不可靠。
【解决方案3】:

在 C 中,作为参数传递给函数的数组被转换为指向数组第一个元素的指针。数组的大小不会隐式传递给函数。作为程序员,您负责将正确的数组大小传递给您的函数。

int sum(int *num, size_t length)
{
   int total = 0;
   int i;
   for (i = 0; i < length; i++)
   {
      total += num[i];
   }
}

这种方法的一个问题是数组的参数只假定指向一个数组。它可以指向任何 int,无论该 int 是否是数组的元素。如果犯了这个错误,就会发生典型的缓冲区溢出。

【讨论】:

    【解决方案4】:

    C 是一种过程语言(并且比大多数过程语言更接近汇编程序),而不是面向对象的语言。 IOW、Algol(和 C)在 Smalltalk(和 C#)之前出现,Smalltalk 教会了我们一些重要的教训。

    有时您可以在 C 中使用以下内容:

    #define num_elements(array) (sizeof(array) / sizeof(array[0]))
    

    ...但是当一个数组被传递给一个函数时,这通常不再起作用了。

    另一个几乎适用于 C 语言任何情况的好方法是:

    #define MY_ARRAY_ELEMENTS 1000
    int a[MY_ARRAY_ELEMENTS];
    foo(a, MY_ARRAY_ELEMENTS);
    

    IOW,为特定数组的长度定义一个符号常量,并使用它来代替硬编码常量。

    OO 语言无论如何都有与对象相关联的元数据,那么为什么不在元数据中存储一个长度呢?不过,C 并没有做那种事情——它是在字节很宝贵的时候创建的,而元数据被视为过多的开销。

    为什么你必须部分定义一个 n 维数组的大小?因为在幕后 C 正在做一些数学运算来乘以内存中存在 a[x][y] 的位置,而且它没有存储元数据来帮助您跟踪这些维度。

    考虑一下 Pascal,另一种过程语言,使数组维度成为数组类型的一部分。那是一种相反的极端——在类型系统中会跟踪大小和形状,但实际上在实践中使用起来非常严格。因此,编写一个函数来对两个不同长度的两个不同数组中的浮点数求和是不切实际的。

    【讨论】:

    • 但是如果只知道列数而不知道行数,它怎么知道 a[x][y] 存在于哪里?
    • 除最后一个维度外,您需要乘以该轴上的值数。最后一个值(不是大小,而是偏移量)只是相加,而不是相乘。考虑 a + x * num_columns + y。 num_rows 不需要进入等式,除非你打开了边界检查。
    猜你喜欢
    • 2022-11-27
    • 2013-03-26
    • 2012-03-01
    • 1970-01-01
    • 2021-08-29
    • 2013-10-26
    • 2012-10-05
    • 2014-07-20
    • 1970-01-01
    相关资源
    最近更新 更多