什么时候需要将数组的大小作为参数传递?
when do we need to pass the size of array as a parameter
我对在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]);
这里唯一需要注意的是,n
和m
在参数列表中出现在A
之前。所以最好总是按照这个顺序声明函数的参数。
第一个签名只是传递数组,没有办法告诉数组有多大,可能导致越界错误和/或安全漏洞的问题。
第二个签名是一个更安全的版本,因为它允许函数检查数组的大小,以防止第一个版本的缺点。
除非这是作业,否则原始数组有点过时了。使用std::vector代替。它允许在不需要手动传递大小的情况下传递矢量。
数组的大小不随数组本身传递。因此,如果另一个函数需要的大小,它将把它作为参数。
问题是,一些函数隐式地理解数组具有一定的大小。所以他们不需要明确地指定它。例如,如果你的函数操作一个3个浮点数的数组,你不需要用户告诉你它是一个3个浮点数的数组。取一个数组
然后是那些函数(让我们称它们为"糟糕",因为它们确实如此),它们将用任意数据填充数组,直到该数据定义的点。sprintf
可能是"最好"的例子。它会一直把字符放进那个数组,直到写完为止。这很糟糕,因为用户和函数之间没有明确或隐含的协议来决定数组的大小。sprintf
将写入一定数量的字符,但用户无法确切知道写入了多少字符(在一般情况下)。
这就是为什么你应该永远不要使用sprintf
;使用snprintf
或_snprintf
,这取决于你的编译器。
当您需要知道数组的大小时,需要提供它。传递数组本身的两种形式没有什么特别之处;第一个参数无论如何都是相同的。第二个方法只提供了知道数组大小所需的信息,而第一个方法则不提供。
但是,有时数组本身保存着有关其大小的信息。例如,在第一个示例中,可能将arr[0]
设置为数组的大小,而实际数据从arr[1]
开始。或者考虑c字符串的情况…您只提供了一个char[]
,并且假定数组在第一个等于