我们何时需要将数组的大小作为参数传递
关于在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]);
他们唯一需要注意的是,这里n
和m
在参数列表中位于A
之前。 因此,总是使用该顺序的参数声明函数。
不同之处在于第二个包含指示数组大小的参数。 逻辑结论是,如果您不使用这样的参数,该函数不知道数组大小是什么。 事实certificate确实如此。 实际上,它不知道你有一个数组。 实际上,您不必使用数组来调用该函数。
这里的数组语法在方括号内没有指定的大小,是假的。 该参数实际上是一个指针。 有关详细信息,请参阅http://c-faq.com/aryptr/index.html ,尤其是第4节。