将数组作为参数传递给 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 的指针ints)。不要将此与不带括号的版本混淆: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 asizeof (&amp;a[0]) 不同。 &amp;a&amp;(&amp;a[0]) 不同(与 &amp;a[0] 也不完全相同)。 char b[] = "foo"char b[] = &amp;("foo") 不同。

【讨论】:

如果我将一个数组传递给一个函数。例如,我创建了一个数组 int a[10] 并为每个元素分配了随机值。现在,如果我使用 int y[]int y[10]int *y 将这个数组传递给函数。然后在该函数中我使用 sizeof(y) 答案将是字节指针已被分配。所以在这种情况下,它会作为一个指针衰减,如果你也包含它会很有帮助。看到这个postimg.org/image/prhleuezd 如果我在最初我们定义的数组中的函数中使用sizeof操作,那么它将衰减为一个数组,但是如果我传入其他函数,那么使用sizeof操作符它将衰减为一个指针。 ***.com/questions/8269048/… 我知道这是旧的。如果有人碰巧看到这个,有两个问题:) 1.@ThomSmith 写道,&amp;a&amp;a[0] 不完全相同,而a 是一个数组。为何如此?在我的测试程序中,无论是在声明数组的函数中,还是在传递给不同的函数时,两者都显示相同。 2.作者写道“char b[] = "foo"char b[] = &amp;("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 中讨论 pointersdecay to pointers 时,我们真的不应该使用术语 reference【参考方案11】:

数组也可以称为衰减指针。

通常当我们在 printf 语句中放置一个变量名时,值会被打印出来,以防数组衰减到第一个元素的地址,因此将其称为衰减指针。

而且我们只能将衰减指针传递给函数。

像博先生所说的数组作为形参 int arr[] 或 int arr[10] 等价于 int *arr;

它们将有自己的 4 字节内存空间并存储接收到的衰减指针。我们对它们进行指针运算。

【讨论】:

以上是关于将数组作为参数传递给 C 中的函数的主要内容,如果未能解决你的问题,请参考以下文章

将数组中的所有值作为参数传递给函数

将对象作为参数传递给函数

Excel VBA:将计算结果数组作为参数传递给函数

如何将数组的值作为第二个参数传递给 awk 的 split 函数?

数组名作函数参数时,实参与形参变量之间的数据传递是?

如何将 URL 中的数组作为参数传递给 Promise.all