什么时候需要将数组的大小作为参数传递?

when do we need to pass the size of array as a parameter

本文关键字:参数传递 数组 什么时候      更新时间:2023-10-16

我对在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)形式而不是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,那么所有的赌注都是无效的,并且你处于未定义行为领域。

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

int change_me = 10;
f(&change_me);
printf("%dn", change_me);

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

很好:f()无论如何都不是对数组进行操作。

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

你可以写

void f( int *arr, int size )

也是,有了后者(size)允许在读写它时不跨出数组边界

C和c++不是一回事。不过,它们有一些共同的子集。您在这里观察到的是,当传递给函数时,"第一个"数组维度总是只导致传递一个指针。声明为

的函数的"签名"(C语言不使用这个术语)
void toto(double A[23]);

总是

void toto(double *A);

那就是上面的23有点冗余,编译器不使用它。现代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之前。所以最好总是按照这个顺序声明函数的参数。

第一个签名只是传递数组,没有办法告诉数组有多大,可能导致越界错误和/或安全漏洞的问题。

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

除非这是作业,否则原始数组有点过时了。使用std::vector代替。它允许在不需要手动传递大小的情况下传递矢量。

数组的大小不随数组本身传递。因此,如果另一个函数需要的大小,它将把它作为参数。

问题是,一些函数隐式地理解数组具有一定的大小。所以他们不需要明确地指定它。例如,如果你的函数操作一个3个浮点数的数组,你不需要用户告诉你它是一个3个浮点数的数组。取一个数组

然后是那些函数(让我们称它们为"糟糕",因为它们确实如此),它们将用任意数据填充数组,直到该数据定义的点。sprintf可能是"最好"的例子。它会一直把字符放进那个数组,直到写完为止。这很糟糕,因为用户和函数之间没有明确或隐含的协议来决定数组的大小。sprintf将写入一定数量的字符,但用户无法确切知道写入了多少字符(在一般情况下)。

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

当您需要知道数组的大小时,需要提供它。传递数组本身的两种形式没有什么特别之处;第一个参数无论如何都是相同的。第二个方法只提供了知道数组大小所需的信息,而第一个方法则不提供。

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

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

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

这里的数组语法在方括号中没有指定大小,这是一种错误。形参实际上是一个指针。欲了解更多信息,请参阅http://c-faq.com/aryptr/index.html,特别是第4节。