将数组作为参数传递给C中的函数


86

我编写了一个包含数组作为参数的函数,并通过如下传递数组的值来调用它。

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[]已更改。

你能解释为什么吗?


1
您正在通过引用传递数组,但是您正在修改其内容-因此,为什么看到数据有变化
Shaun Wilde

main()必须返回int
underscore_d

Answers:


137

当传递数组作为参数时,这

void arraytest(int a[])

的意思与

void arraytest(int *a)

所以你修改main中的值。

由于历史原因,数组不是一等公民,因此不能按值传递。


3
在哪种情况下哪种表示法更好?
拉蒙·马丁内斯

20
@Ramon-我将使用第二个选项,因为它看起来不那么混乱,并且更好地表明您没有得到数组的副本。
Bo Persson

2
您能否解释“历史原因”?我想按值传递将需要一个副本,因此浪费了内存..谢谢
Jacquelyn.Marquardt

5
@lucapozzobon-最初C没有通过值传递,除了单个值。直到将struct其添加到语言中后,它才被更改。然后认为更改数组规则为时已晚。已经有10个用户。:-)
Bo Persson

1
...的含义与void arraytest(int a[1000])etc等完全相同。此处扩展了答案:stackoverflow.com/a/51527502/4561887
加布里埃尔·斯台普斯

9

如果要将单维数组作为函数的参数传递,则必须使用以下三种方式之一声明一个形式参数,并且所有三种声明方法都将产生相似的结果,因为每种方法都告诉编译器整数指针在运行被接收

int func(int arr[], ...){
    .
    .
    .
}

int func(int arr[SIZE], ...){
    .
    .
    .
}

int func(int* arr, ...){
    .
    .
    .
}

因此,您正在修改原始值。

谢谢 !!!


我正在寻找您的第二个示例,能否详细说明每种方法的优点?
帕克

8

您没有将数组作为副本传递。它只是指向数组第一个元素在内存中的地址的指针。



7

在大多数情况下,C语言中的数组会转换为指向数组本身第一个元素的指针。传递给函数的更多详细信息始终会转换为指针。

这是K&R2nd的报价:

将数组名称传递给函数时,传递的是初始元素的位置。在被调用的函数中,此参数是局部变量,因此数组名称参数是指针,即包含地址的变量。

写作:

void arraytest(int a[])

与写作具有相同的含义:

void arraytest(int *a)

因此,尽管您没有明确地编写它,但正如您传递指针一样,因此您正在修改main中的值。

欲了解更多我真的建议阅读

此外,您可以在此处找到其他答案


6

您正在传递数组第一个成员的内存位置的值。

因此,当您开始修改函数内部的数组时,就是在修改原始数组。

记住那a[1]*(a+1)


1
我想*(a + 1)应该缺少()(*)(a + 1)
ShinTakezou 2011年

@Shin谢谢,自从我玩C以来已经有一段时间了。–
Alex

6

在C语言中,除少数特殊情况外,数组引用始终“衰减”到指向数组第一个元素的指针。因此,不可能“按值”传递数组。函数调用中的数组将作为指针传递给函数,类似于通过引用传递数组。

编辑:在三种特殊情况下,数组不会衰减到指向其第一个元素的指针:

  1. sizeof a与相同sizeof (&a[0])
  2. &a&(&a[0])(不完全相同&a[0])。
  3. char b[] = "foo"与相同char b[] = &("foo")

如果我将数组传递给函数。例如,我创建了一个数组int a[10]并为每个元素分配了随机值。现在,如果我使用int y[]int y[10]或将此数组传递给函数int *y,然后在该函数中使用sizeof(y)Answer,将分配字节指针。因此,在这种情况下,它将作为指针衰减,如果您也包括它,则将很有帮助。请参阅此postimg.org/image/prhleuezd
Suraj Jain

如果我sizeof在最初定义的数组中的函数中使用sizeofoperator,则它将衰减为一个数组,但是如果我传递其他函数,则使用use运算符,它将衰减为指针。
Suraj Jain


我知道这很旧。如果有人碰巧看到这两个问题:) 1. @ThomSmith写道&a&a[0]whena是一个数组不太一样。为何如此?在我的测试程序中,无论是在声明数组的函数中,还是在传递给其他函数时,两者都显示相同。2.作者写道“char b[] = "foo"char b[] = &("foo")”不同。对我来说,后者甚至不编译。只有我吗?
艾维夫·科恩

6

将多维数组作为参数传递给函数。 将一个dim数组作为参数传递或多或少是微不足道的。让我们看一下传递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个dim数组作为参数传递:

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语言中,多维数组与指向指针的指针...指向的指针不同。anint(*arr)[5]是指向5个元素的数组的指针,anint(*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;
            }
        }
    }
}

如您所料,可以将第二维4个元素和三维5个元素中的任何3个昏暗数组作为参数。像这样的任何事情都可以:

arr[1][4][5]
arr[2][4][5]
...
arr[10][4][5]
...

但是我们必须指定所有尺寸,直到第一个。


5

在C中使用自然类型从数组到ptr衰减的标准数组用法

@Bo Persson在此处的正确答案中正确指出:

当传递数组作为参数时,这

void arraytest(int a[])

的意思与

void arraytest(int *a)

但是,我还要补充一点,上述两种形式也是:

  1. 意思与

     void arraytest(int a[0])
    
  2. 意思和

     void arraytest(int a[1])
    
  3. 意思和

     void arraytest(int a[2])
    
  4. 意思和

     void arraytest(int a[1000])
    
  5. 等等

在上面的每个数组示例中int *,即使-Wall -Wextra -Werror打开了构建选项,输入参数的类型也会衰减为,并且可以在没有警告和错误的情况下进行调用(例如,有关这3个构建选项的详细信息,请参见我的仓库),例如这个:

int array1[2];
int * array2 = array1;

// works fine because `array1` automatically decays from an array type
// to `int *`
arraytest(array1);
// works fine because `array2` is already an `int *` 
arraytest(array2);

作为事实上,“大小”值([0][1][2][1000],等)的阵列参数这里面显然只是为了美观/自文件的目的,并且可以是任意正整数(size_t键入我觉得)你想!

但是,实际上,您应该使用它来指定期望函数接收的数组的最小大小,以便在编写代码时可以轻松地跟踪和验证。MISRA-C-2012标准(购买/下载标准的236-PG 2012版本的PDF为£15.00这里)走得更远,国家(重点):

规则17.5与声明为具有数组类型的参数相对应的函数自变量应具有适当数量的元素。

...

如果将一个参数声明为具有指定大小的数组,则每个函数调用中的相应参数应指向一个对象,该对象至少具有与数组一样多的元素。

...

使用数组声明符作为函数参数比使用指针更清楚地指定函数接口。该函数期望的最小元素数已明确说明,而使用指针则不可能。

换句话说,即使C标准在技术上没有强制执行,他们还是建议使用显式的大小格式-至少可以帮助您作为开发人员和使用代码的其他人澄清该函数期望的大小数组你通过


在C中对数组强制类型安全

正如@Winger Sendon在我的答案下方的评论中指出的那样,我们可以强制C根据数组大小将数组类型视为不同

首先,您必须在上面的示例中认识到,使用int array1[2];如下方法:arraytest(array1);导致array1自动衰减为int *。但是,如果您取而代之的地址 array1并致电arraytest(&array1),则会得到完全不同的行为!现在,它不会衰减为int *!而是类型为&array1is int (*)[2],表示“指向int的2大小的数组的指针“指向int类型的2的数组的指针”。因此,您可以强制C检查数组上的类型安全性,如下所示:

void arraytest(int (*a)[2])
{
    // my function here
}

这种语法很难阅读,但是类似于函数指针的语法。在线工具cdecl告诉我们,这int (*a)[2]意味着:“将a声明为指向int数组2的指针(指向2 ints数组的指针)。:不与版本没有括号混淆int * a[2],这是指:“声明一个作为指针整型阵列2”(2点数组的指针int)。

现在,此函数要求您使用这样的地址运算符(&)来调用它,将POINTER TO CORRECT SIZE SIZE!用作输入参数:

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)

您可以在此处测试此代码

为了强制C编译器将此警告变成错误,以便您必须始终arraytest(&array1);仅使用正确大小类型的输入数组(int array1[2];在这种情况下)进行调用,请添加-Werror到构建选项中。如果在onlinegdb.com上运行上面的测试代码,请单击右上角的齿轮图标,然后单击“额外的编译器标志”以键入此选项,以执行此操作。现在,此警告:

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];
// "type safe" ptr to array of size 2 of int:
int (*array_p)[2] = &array;

...但是我并不一定推荐这样做,因为它使我想起了很多C ++滑稽动作,它们在各处强加了类型安全性,但付出的代价是语言语法复杂性,冗长性和构造代码的难度非常高,我不喜欢这样做,并且之前已经抱怨了很多次(例如:参见“我的C ++思想”)。


有关其他测试和实验,请参见下面的链接。

参考文献

请参阅上面的链接。也:

  1. 我的在线代码实验:https//onlinegdb.com/B1RsrBDFD

2
void arraytest(int (*a)[1000])更好,因为如果大小错误,则编译器将出错。
温格·森顿

@WingerSendon,我知道我需要在这里验证一些细节,并且语法令人困惑(就像一个函数ptr语法令人困惑),所以我花了时间,最后用一个大的新部分(标题为Forcing type safety on arrays in C,点。
加布里埃尔·斯台普斯

0

如果使用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;
}

我对此表示赞同。我不确定为什么它被否决了。
加布里埃尔·斯台普斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.