我们何时需要将数组的大小作为参数传递

关于在C / C ++中传递数组我有点困惑。 我看到一些签名就像这样的情况

void f(int arr[]) 

有些是这样的

 void f(int arr[], int size) 

任何人都可以详细说明有什么区别以及何时以及如何使用它?

首先,传递给函数的数组实际上将指针传递给数组的第一个元素,例如,如果你有的话

 int a[] = { 1, 2, 3 }; f(a); 

然后, f()得到&a[0]传递给它。 因此,在编写函数原型时,以下内容是等效的:

 void f(int arr[]); void f(int *arr); 

这意味着数组的大小会丢失,而f()通常无法确定大小。 (这就是我更喜欢void f(int *arr)forms而不是void f(int arr[]) 。)

有两种情况, f()不需要信息,在这两种情况下,没有额外的参数是可以的。

首先,在arr中有一些特殊的,同意的值,调用者和f()都意味着“结束”。 例如,人们可以同意值0表示“完成”。

然后人们可以写:

 int a[] = { 1, 2, 3, 0 }; /* make sure there is a 0 at the end */ int result = f(a); 

并定义f()类似于:

 int f(int *a) { size_t i; int result = 0; for (i=0; a[i]; ++i) /* loop until we see a 0 */ result += a[i]; return result; } 

显然,只有当调用者和被调用者同意一个约定并且遵循它时,上述方案才有效。 一个例子是C库中的strlen()函数。 它通过查找0来计算字符串的长度。 如果你传递了最后没有0东西,那么所有的赌注都会关闭,你处于未定义的行为领域。

第二种情况是你没有真正的arrays。 在这种情况下, f()接受一个指向对象的指针(在您的示例中为int )。 所以:

 int change_me = 10; f(&change_me); printf("%d\n", change_me); 

 void f(int *a) { *a = 42; } 

很好:无论如何f()不在数组上运行。

如果数组在C或C ++中传递,则只传递其地址。 这就是为什么第二种情况很常见,其中第二个参数是数组中元素的数量。 该函数不知道,只能通过查看数组的地址,它应该包含多少元素。

你可以写

 void f( int *arr, int size ) 

同样,后者(大小)允许在读/写时不跨越数组边界

第一个签名只是传递数组,无法分辨数组的大小,并且可能导致出现越界错误和/或安全漏洞的问题。

第二个签名是一个更安全的版本,因为它允许函数检查数组的大小,以防止第一个版本的缺点。

除非这是作业,否则原始数组有点过时了。 请改用std :: vector。 它允许传递向量而不必手动传递大小,因为它为您执行此操作。

数组的大小不随数组本身传递。 因此,如果其他函数需要大小,则将其作为参数。

问题是,某些函数隐式地将数组理解为具有特定大小。 所以他们不需要明确指定它。 例如,如果您的函数在3个浮点数组上运行,则您不需要用户告诉您它是一个包含3个浮点数的数组。 只需要一个arrays。

然后有那些函数(让它们称之为“可怕”,因为它们是)将用任意数据填充数组,直到由该数据定义的点。 sprintf可能是“最好的”例子。 它将继续将字符放在该数组中,直到完成编写它们为止。 这非常糟糕,因为用户和函数之间没有关于这个数组有多大的明确或隐含的协议。 sprintf会写一些字符,但用户无法确切地知道有多少字符(在一般情况下)。

这就是为什么你永远不应该使用sprintf ; 使用snprintf_snprintf ,具体取决于您的编译器。

只要您需要知道arrays的大小,就需要提供它。 传递数组本身的两种forms没有什么特别之处; 第一个参数是相同的方式。 第二种方法只提供了解数组大小所需的信息,而第一种方法却没有。

但有时数组本身会保存有关其大小的信息。 例如,在您的第一个示例中,可能将arr[0]设置为数组的大小,实际数据从arr[1]开始。 或者考虑c-strings的情况……你只提供一个char[] ,并假设数组在第一个元素处结束,等于\0 。 在您的示例中,负值可能充当类似的标记。 或者函数可能根本不关心数组的大小,而只是假设它足够大。

这些方法本质上是不安全的,但是很容易忘记设置arr[0]或意外地覆盖空终止符。 然后, f突然无法知道它有多少空间。 始终更喜欢通过您显示的size参数或使用指向数组末尾的第二个指针显式提供大小。 后者是C ++中标准库函数通常采用的方法。 但是,您仍然存在提供不正确大小的问题,这就是为什么在C ++中不建议您首先使用这样的数组…使用实际容器来跟踪您的信息。

C和C ++不是一回事。 但是,他们有一些共同的子集。 你在这里观察到的是,传递给函数时的“第一个”数组维度总是会导致传递指针。 声明为的函数的“签名”(C不使用该术语)

 void toto(double A[23]); 

永远只是

 void toto(double *A); 

也就是说,上面的23有些多余,编译器没有使用。 Modern C(又名C99)在这里有一个扩展,让你声明A总是有23个元素:

 void toto(double A[static 23]); 

或者指针是const限定的

  void toto(double A[const 23]); 

如果添加其他尺寸图片更改,则使用数组大小​​:

 void toto(double A[23][7]); 

在C和C ++中都是

 void toto(double (*A)[7]); 

这是一个指向7元素数组的指针。 在C ++中,这些数组边界必须是整数常量。 在C中它可以是动态的。

 void toto(size_t n, size_t m, double A[n][m]); 

他们唯一需要注意的是,这里nm在参数列表中位于A之前。 因此,总是使用该顺序的参数声明函数。

不同之处在于第二个包含指示数组大小的参数。 逻辑结论是,如果您不使用这样的参数,该函数不知道数组大小是什么。 事实certificate确实如此。 实际上,它不知道你有一个数组。 实际上,您不必使用数组来调用该函数。

这里的数组语法在方括号内没有指定的大小,是假的。 该参数实际上是一个指针。 有关详细信息,请参阅http://c-faq.com/aryptr/index.html ,尤其是第4节。