将数组作为参数传递给 C 中的函数
Posted
技术标签:
【中文标题】将数组作为参数传递给 C 中的函数【英文标题】:Passing an array as an argument to a function in C 【发布时间】:2011-09-27 21:44:52 【问题描述】:我写了一个包含数组作为参数的函数, 并通过传递数组的值来调用它,如下所示。
void arraytest(int a[])
// changed the array a
a[0] = a[0] + a[1];
a[1] = a[0] - a[1];
a[0] = a[0] - a[1];
void main()
int arr[] = 1, 2;
printf("%d \t %d", arr[0], arr[1]);
arraytest(arr);
printf("\n After calling fun arr contains: %d\t %d", arr[0], arr[1]);
我发现虽然我通过传递值调用arraytest()
函数,但int arr[]
的原始副本已更改。
你能解释一下原因吗?
【问题讨论】:
您正在通过引用传递数组,但您正在修改其内容 - 因此您会看到数据发生变化main()
必须返回 int
。
这是这个问题的自然延伸:How to pass a multidimensional array to a function in C and C++。这里是several of my approaches 这个问题。
【参考方案1】:
当将数组作为参数传递时,this
void arraytest(int a[])
意思完全一样
void arraytest(int *a)
所以你正在修改 main 中的值。
由于历史原因,数组不是一等公民,不能按值传递。
【讨论】:
哪种符号在哪种情况下更好? @Ramon - 我会使用第二个选项,因为它看起来不那么令人困惑,并且更好地表明您没有得到数组的副本。 你能解释一下“历史原因”吗?我想通过值传递需要一个副本,所以浪费内存..谢谢 @lucapozzobon - 最初 C 没有任何值传递,除了单个值。直到struct
被添加到语言中,这才被改变。然后认为改变数组规则为时已晚。已经有10个用户了。 :-)
...与void arraytest(int a[1000])
等完全一样。扩展答案在这里:***.com/a/51527502/4561887。【参考方案2】:
对于传递二维(或更高的多维)数组,请在此处查看我的其他答案:How to pass a multidimensional array to a function in C and C++
在 C(和 C++)中将一维数组作为函数参数传递
1。 C 中的标准数组使用,具有从数组到 ptr 的自然类型衰减(调整)
@Bo Persson 在他的精彩回答here 中正确陈述:
当将数组作为参数传递时,this
void arraytest(int a[])
意思完全一样
void arraytest(int *a)
让我添加一些 cmets 以使这两个代码 sn-ps 更加清晰:
// param is array of ints; the arg passed automatically "adjusts" (frequently said
// informally as "decays") from `int []` (array of ints) to `int *`
// (ptr to int)
void arraytest(int a[])
// ptr to int
void arraytest(int *a)
不过,我还要补充一点,以上两种形式也有:
意思完全一样
// array of 0 ints; automatically adjusts (decays) from `int [0]`
// (array of zero ints) to `int *` (ptr to int)
void arraytest(int a[0])
意思完全一样
// array of 1 int; automatically adjusts (decays) from `int [1]`
// (array of 1 int) to `int *` (ptr to int)
void arraytest(int a[1])
意思完全一样
// array of 2 ints; automatically adjusts (decays) from `int [2]`
// (array of 2 ints) to `int *` (ptr to int)
void arraytest(int a[2])
意思完全一样
// array of 1000 ints; automatically adjusts (decays) from `int [1000]`
// (array of 1000 ints) to `int *` (ptr to int)
void arraytest(int a[1000])
等等
在上面的每一个数组示例中,正如下面代码中的示例调用所示,输入参数类型调整(衰减)为int *
,并且可以是即使打开了构建选项-Wall -Wextra -Werror
,调用时也没有警告和错误(有关这三个构建选项的详细信息,请参阅my repo here),如下所示:
int array1[2];
int * array2 = array1;
// works fine because `array1` automatically decays from an array type
// to a pointer type: `int *`
arraytest(array1);
// works fine because `array2` is already an `int *`
arraytest(array2);
事实上,这里的数组参数中的“大小”值([0]
、[1]
、[2]
、[1000]
等)显然只是出于审美/自我记录目的,并且可以是您想要的任何正整数(我认为是size_t
类型)!
然而,在实践中,您应该使用它来指定您希望函数接收的数组的最小大小,以便在编写代码时可以轻松跟踪和验证。 @ 987654324@ 标准 (buy/download the 236-pg 2012-version PDF of the standard for £15.00 here) 甚至声明(强调):
规则 17.5 对应于声明为数组类型的参数的函数参数应具有适当数量的元素。
...
如果将参数声明为具有指定大小的数组,则每个函数调用中的相应参数应指向一个对象,该对象至少具有与数组一样多的元素。
...
对函数参数使用数组声明器比使用指针更清楚地指定函数接口。函数期望的最小元素数量已明确说明,而指针则无法做到这一点。
换句话说,他们建议使用显式大小格式,尽管 C 标准在技术上并未强制执行它——它至少有助于向作为开发人员的您以及使用代码的其他人阐明,什么函数期望你传入的大小数组。
2。在 C 中强制数组类型安全
(不推荐(更正:sometimes recommended, especially for fixed-size multi-dimensional arrays),但可能。请参阅我最后反对这样做的简短论点。另外,对于我的多维数组[例如:二维数组]版本,请参阅@987654327 @.)
正如@Winger Sendon 在我的答案下方的评论中指出的那样,我们可以强制 C 根据数组 size 将数组 type 视为不同! p>
首先,您必须认识到,在我上面的示例中,像这样使用int array1[2];
:arraytest(array1);
会导致array1
自动衰减为int *
。但是,如果您使用 array1
的地址并调用 arraytest(&array1)
,则会得到完全不同的行为!现在,它不会衰减为 int *
!这是因为如果您将的地址 设为一个数组,那么您已经 具有指针类型,并且指针类型不会适应其他指针类型。只有数组类型才能适应指针类型。因此,&array1
的类型是 int (*)[2]
,这意味着 “指向大小为 2 的 int 数组的指针”,或 “指向int 类型的大小为 2 的数组”,或者也称为 “指向 2 个 int 数组的指针”。 因此,您可以通过将显式指针传递给数组来强制 C 检查数组的类型安全性,如下所示:
// `a` is of type `int (*)[2]`, which means "pointer to array of 2 ints";
// since it is already a ptr, it can NOT automatically decay further
// to any other type of ptr
void arraytest(int (*a)[2])
// my function here
这种语法很难阅读,但类似于function pointer。在线工具cdecl 告诉我们int (*a)[2]
的意思是:“将a 声明为指向int 数组2 的指针”(指向数组2 的指针int
s)。不要将此与不带括号的版本混淆:int * a[2]
,这意味着:“将 a 声明为指向 int 的指针的数组 2”(AKA:2 个指针到int
,又名:2 个数组 int*
s)。
现在,这个函数需要你像这样使用地址运算符 (&
) 调用它,使用指向正确大小的数组的指针作为输入参数!:
int array1[2];
// ok, since the type of `array1` is `int (*)[2]` (ptr to array of
// 2 ints)
arraytest(&array1); // you must use the & operator here to prevent
// `array1` from otherwise automatically decaying
// into `int *`, which is the WRONG input type here!
然而,这会产生一个警告:
int array1[2];
// WARNING! Wrong type since the type of `array1` decays to `int *`:
// main.c:32:15: warning: passing argument 1 of ‘arraytest’ from
// incompatible pointer type [-Wincompatible-pointer-types]
// main.c:22:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
arraytest(array1); // (missing & operator)
你可以test this code here。
要强制 C 编译器将此警告转化为错误,因此您必须始终仅使用大小正确的输入数组调用 arraytest(&array1);
和(在本例中为 int array1[2];
),将-Werror
添加到您的构建选项中。如果在 onlinegdb.com 上运行上面的测试代码,请单击右上角的齿轮图标并单击“Extra Compiler Flags”以输入此选项。现在,此警告:
main.c:34:15: warning: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Wincompatible-pointer-types] main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
会变成这个构建错误:
main.c: In function ‘main’: main.c:34:15: error: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Werror=incompatible-pointer-types] arraytest(array1); // warning! ^~~~~~ main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’ void arraytest(int (*a)[2]) ^~~~~~~~~ cc1: all warnings being treated as errors
请注意,您还可以创建指向给定大小数组的“类型安全”指针,如下所示:
int array[2]; // variable `array` is of type `int [2]`, or "array of 2 ints"
// `array_p` is a "type safe" ptr to array of size 2 of int; ie: its type
// is `int (*)[2]`, which can also be stated: "ptr to array of 2 ints"
int (*array_p)[2] = &array;
...但我并不一定推荐这个(在 C 中使用这些“类型安全”的数组),因为它让我想起了很多用于在任何地方强制类型安全的 C++ 滑稽动作,在语言语法复杂性、冗长性和难以构建代码的异常高成本,我不喜欢这些,并且之前曾多次抱怨过(例如:参见"My Thoughts on C++" here)。
有关其他测试和实验,另请参阅下面的链接。
参考文献
请参阅上面的链接。另外:
-
我的代码在线实验:https://onlinegdb.com/B1RsrBDFD
另见:
-
我对多维数组(例如:二维数组)的回答对上述内容进行了阐述,并在有意义的情况下对多维数组使用“类型安全”方法:How to pass a multidimensional array to a function in C and C++
【讨论】:
void arraytest(int (*a)[1000])
更好,因为如果大小错误,编译器会出错。
@WingerSendon,我知道我需要在这里验证一些微妙之处,并且语法令人困惑(就像函数 ptr 语法令人困惑),所以我花时间最终更新了我的答案一个名为Forcing type safety on arrays in C
的大型新部分,涵盖了您的观点。
@GabrielStaples,谢谢。你的回答很有帮助。你能给我推荐一个以这种方式学习高级c的参考吗?
@daryooosh,不幸的是,我不能。我没有任何很好的参考。经过多年的深入挖掘,我在这里一点一点,那里一点一点。我能做的最好的就是告诉你,我偶尔会把我学到的一些东西放到我的eRCaGuy_hello_world repo 中。请记住,我上面使用的 C 类型的安全材料应该非常谨慎地使用。它会使您的代码复杂化并大大降低可读性,而且不值得。尽可能专注于简单的语法,并使内容具有可读性。
请注意,经典的经典 C 教科书是这本 K&R The C Programming Language 书:en.wikipedia.org/wiki/The_C_Programming_Language。【参考方案3】:
如果您想将一维数组作为参数传递给函数,则必须通过以下三种方式之一声明形参,并且所有三种声明方法都会产生相似的结果,因为each 告诉编译器将要接收一个整数指针。
int func(int arr[], ...)
.
.
.
int func(int arr[SIZE], ...)
.
.
.
int func(int* arr, ...)
.
.
.
所以,您正在修改原始值。
谢谢!!!
【讨论】:
我在找你的第二个例子,你能详细说明每种方法的优点是什么吗?【参考方案4】:您没有将数组作为副本传递。它只是一个指向数组第一个元素在内存中的地址的指针。
【讨论】:
【参考方案5】:您正在传递数组第一个元素的地址
【讨论】:
【参考方案6】:您正在传递数组第一个成员的内存位置的值。
因此,当您开始修改函数内部的数组时,您正在修改原始数组。
记住a[1]
是*(a+1)
。
【讨论】:
我想*a+1 缺少 () 应该是 *(a+1) @Shin 谢谢,我玩 C 已经有一段时间了。【参考方案7】:在大多数情况下,C 中的数组被转换为指向数组本身第一个元素的指针。传递给函数的更详细的数组总是被转换为指针。
这里引用K&R2nd:
当一个数组名被传递给一个函数时,传递的是 初始元素的位置。在被调用函数中,这个 参数是局部变量,因此数组名称参数是 指针,即包含地址的变量。
写作:
void arraytest(int a[])
与写作同义:
void arraytest(int *a)
因此,尽管您没有明确地编写它,但它就像您传递一个指针一样,因此您正在修改 main 中的值。
我真的建议阅读this。
此外,您可以在 SO here 上找到其他答案
【讨论】:
【参考方案8】:将多维数组作为参数传递给函数。
传递一个暗淡的数组作为参数或多或少是微不足道的。
让我们看一个更有趣的传递 2 dim 数组的案例。
在 C 中,您不能使用指向指针构造 (int **
) 的指针来代替 2 暗数组。
我们举个例子:
void assignZeros(int(*arr)[5], const int rows)
for (int i = 0; i < rows; i++)
for (int j = 0; j < 5; j++)
*(*(arr + i) + j) = 0;
// or equivalent assignment
arr[i][j] = 0;
这里我指定了一个函数,它的第一个参数是一个指向 5 个整数数组的指针。 我可以将任何具有 5 列的 2 个暗淡数组作为参数传递:
int arr1[1][5]
int arr1[2][5]
...
int arr1[20][5]
...
您可能会想到定义一个更通用的函数,该函数可以接受任何 2 dim 数组并更改函数签名如下:
void assignZeros(int ** arr, const int rows, const int cols)
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
*(*(arr + i) + j) = 0;
此代码可以编译,但在尝试以与第一个函数相同的方式分配值时会出现运行时错误。
因此,在 C 中,多维数组与指向指针的指针......指向指针不同。 int(*arr)[5]
是一个指向 5 个元素的数组的指针,
int(*arr)[6]
是一个指向 6 个元素的数组的指针,它们是指向不同类型的指针!
那么,如何为更高维度定义函数参数?很简单,我们只要按照模式! 这是调整为采用 3 维数组的相同函数:
void assignZeros2(int(*arr)[4][5], const int dim1, const int dim2, const int dim3)
for (int i = 0; i < dim1; i++)
for (int j = 0; j < dim2; j++)
for (int k = 0; k < dim3; k++)
*(*(*(arr + i) + j) + k) = 0;
// or equivalent assignment
arr[i][j][k] = 0;
如您所料,它可以将任何 3 个在第二维中具有 4 个元素和在第三维中具有 5 个元素的暗淡数组作为参数。像这样的任何事情都可以:
arr[1][4][5]
arr[2][4][5]
...
arr[10][4][5]
...
但我们必须指定直到第一个的所有尺寸。
【讨论】:
【参考方案9】:在 C 中,除了少数特殊情况外,数组引用总是“衰减”到指向数组第一个元素的指针。因此,不可能“按值”传递数组。函数调用中的数组将作为指针传递给函数,类似于通过引用传递数组。
编辑:在三种特殊情况下,数组不会衰减到指向其第一个元素的指针:
sizeof a
与 sizeof (&a[0])
不同。
&a
与 &(&a[0])
不同(与 &a[0]
也不完全相同)。
char b[] = "foo"
与 char b[] = &("foo")
不同。
【讨论】:
如果我将一个数组传递给一个函数。例如,我创建了一个数组int a[10]
并为每个元素分配了随机值。现在,如果我使用 int y[]
或 int y[10]
或 int *y
将这个数组传递给函数。然后在该函数中我使用 sizeof(y)
答案将是字节指针已被分配。所以在这种情况下,它会作为一个指针衰减,如果你也包含它会很有帮助。看到这个postimg.org/image/prhleuezd
如果我在最初我们定义的数组中的函数中使用sizeof
操作,那么它将衰减为一个数组,但是如果我传入其他函数,那么使用sizeof
操作符它将衰减为一个指针。
***.com/questions/8269048/…
我知道这是旧的。如果有人碰巧看到这个,有两个问题:) 1.@ThomSmith 写道,&a
与&a[0]
不完全相同,而a
是一个数组。为何如此?在我的测试程序中,无论是在声明数组的函数中,还是在传递给不同的函数时,两者都显示相同。 2.作者写道“char b[] = "foo"
与char b[] = &("foo")
不一样”。对我来说,后者甚至不编译。只有我吗?【参考方案10】:
如果你使用a[]
或*a
,数组总是通过引用传递:
int* printSquares(int a[], int size, int e[])
for(int i = 0; i < size; i++)
e[i] = i * i;
return e;
int* printSquares(int *a, int size, int e[])
for(int i = 0; i < size; i++)
e[i] = i * i;
return e;
【讨论】:
我赞成这个。我不知道为什么它被否决了。 @GabrielStaples 我不是投反对票的人,但可能是因为“通过引用传递”在这里是一个非常模棱两可(不是说错)的术语。 reference 是只存在于 C++ 中的东西,在 C++ 中的意思正好相反(即对函数中的非指针引用参数所做的更改会反映在函数之外)。因此,当我们在标准 C 中讨论 pointers 或 decay to pointers 时,我们真的不应该使用术语 reference。【参考方案11】:数组也可以称为衰减指针。
通常当我们在 printf 语句中放置一个变量名时,值会被打印出来,以防数组衰减到第一个元素的地址,因此将其称为衰减指针。
而且我们只能将衰减指针传递给函数。
像博先生所说的数组作为形参 int arr[] 或 int arr[10] 等价于 int *arr;
它们将有自己的 4 字节内存空间并存储接收到的衰减指针。我们对它们进行指针运算。
【讨论】:
以上是关于将数组作为参数传递给 C 中的函数的主要内容,如果未能解决你的问题,请参考以下文章