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

10

我对在C/C++中传递数组有点困惑。我看到一些情况下签名是这样的:

void f(int arr[])

有些是这样的

void f(int arr[], int size)

有谁能详细说明一下它们的区别以及在什么情况下如何使用它们?


可能是如何在C++中使用数组?的重复问题。 - fredoverflow
8个回答

10

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

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("%d\n", change_me);

使用

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

没问题: f() 并没有操作数组。


1
另一种情况是,如果调用方和被调用方就数组的长度达成一致,则不需要传递长度。 - Richard J. Ross III

2
你可以编写代码。
void f( int *arr, int size )

同时,拥有后面的大小值可以避免在读取/写入数组时越界。

2

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 之前声明。因此最好总是按照这个顺序声明函数的参数。


2

在C或C++中传递数组时只传递其地址。因此第二种情况非常常见,其中第二个参数是数组中元素的数量。函数仅通过查看数组的地址无法知道它应该包含多少个元素。


1

第一个签名只是传递数组,没有办法知道数组的大小,可能会导致越界错误和/或安全漏洞。

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

除非这是作业,否则原始数组有点过时。使用std::vector代替。它允许在不必手动传递大小的情况下传递向量,因为它会自动处理。


1

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

问题在于,有些函数隐含地理解数组的大小。因此,它们不需要明确指定大小。例如,如果您的函数操作包含3个浮点数的数组,则无需用户告诉您它是包含3个浮点数的数组。只需取一个数组即可。

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

这就是为什么您永远不应该使用sprintf的原因;根据您的编译器使用snprintf或_snprintf。


1

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

有时候,数组本身保存有关其大小的信息。例如,在您的第一个示例中,也许 arr[0] 设置为数组的大小,实际数据从 arr[1] 开始。或者考虑 C 字符串的情况... 您只提供一个 char[],并且假定数组在第一个等于 \0 的元素处结束。在您的示例中,负值可能充当类似的标记。或者,该函数根本不关心数组的大小,并且将简单地假定它足够大。

这些方法本质上是不安全的,因为很容易忘记设置arr[0]或者意外覆盖空终止符。然后,f突然就不知道有多少可用空间了。始终优先显式提供大小,可以通过像您展示的size参数或使用指向数组末尾的第二个指针来实现。后者是C++标准库函数通常采用的方法。但是,您仍然存在提供错误大小的问题,这就是为什么在C++中不建议首先使用此类数组...而应该使用实际的容器来跟踪该信息。


0

区别在于第二个包括指示数组大小的参数。逻辑上的结论是,如果您不使用这样的参数,函数就不知道数组大小是多少。事实证明确实如此。实际上,它不知道您有一个数组。事实上,您不必拥有一个数组来调用该函数。

这里的数组语法,方括号内没有指定大小,是一种欺骗行为。该参数实际上是一个指针。有关更多信息,请参见http://c-faq.com/aryptr/index.html,特别是第4节。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接