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

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。

#1 楼

当将数组作为参数传递时,此

void arraytest(int a[])




void arraytest(int *a)


完全相同,所以您正在修改main中的值。

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

评论


在哪种情况下哪种表示法更好?

–拉蒙·马丁内斯(Ramon Martinez)
2015年9月4日在12:07

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

– Bo Persson
15年9月4日在12:20

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

–雅克琳·马夸特
16年11月1日在20:15

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

– Bo Persson
16年2月2日,9:26

...的含义与void arraytest(int a [1000])等完全相同。此处扩展了答案:stackoverflow.com/a/51527502/4561887。

–加百利·斯台普斯
18年7月25日在20:56

#2 楼

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

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

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

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


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

谢谢!!!

评论


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

–冰球
6月16日7:16

#3 楼

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

#4 楼

您正在传递数组第一个元素的地址

#5 楼

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

以下是K&R2nd的引号:


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


写作:

void arraytest(int a[])


含义与写作:

void arraytest(int *a)


因此,尽管您未明确编写它,但仍在传递指针,因此您正在修改main中的值。

有关更多信息,我真的建议您阅读。
/>
此外,您还可以在此处找到其他答案

#6 楼

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

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

请记住,a[1]*(a+1)

评论


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

–ShinTakezou
2011年7月4日在6:17

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

– alex
2011年7月4日在6:18

#7 楼

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

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



sizeof asizeof (&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

–苏拉杰(Suraj Jain)
16-09-30在5:08



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

–苏拉杰(Suraj Jain)
16-09-30在5:09



stackoverflow.com/questions/8269048/…

–苏拉杰(Suraj Jain)
16-09-30在5:14

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

–阿维夫·科恩(Aviv Cohn)
4月13日21:51

#8 楼

将多维数组作为参数传递给函数。
将一个暗数组作为参数传递或多或少是不重要的。
让我们来看看传递2个暗数组的更有趣的情况。
在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语言中,多维数组与指向指针的指针...并不相同。一个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;
            }
        }
    }
}


您怎么期望的,它可以将在第二维中具有4个元素并且在第三维中具有5个元素的任何3个暗数组作为参数。这样的事情就可以了:

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


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

#9 楼

C中自然类型从数组衰减到ptr的标准数组用法
@Bo Persson在此处的正确答案中正确指出:

将数组作为参数传递时,此
void arraytest(int a[])


void arraytest(int *a)
完全相同

但是,让我补充一下,上述两种形式也是:


 void arraytest(int a[0])



完全相同,
 void arraytest(int a[1])




完全相同
/>
 void arraytest(int a[2])



,其含义与
 void arraytest(int a[1000])



等完全相同。


/>在上面的每个数组示例中,输入参数类型都会衰减为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]等)此处的array参数显然仅出于美观/自我记录的目的,并且可以是您想要的任何正整数(我认为是size_t类型)!
实际上,您应该使用它指定最小尺寸您希望函数接收的数组,因此在编写代码时很容易跟踪和验证。 MISRA-C-2012标准(此处以15.00英镑的价格购买/下载该标准的236页2012年版本的PDF,价格为15.00英镑),甚至声明(强调):

规则17.5功能与声明为具有数组类型的参数相对应的参数应具有适当数量的元素。
...
如果将参数声明为具有指定大小的数组,则每个函数调用中的相应参数应该指向至少具有与数组一样多的元素的对象。
...
使用数组声明符作为函数参数比使用指针更清楚地指定函数接口。该函数期望的最小元素数已明确说明,而使用指针是不可能的。

换句话说,尽管C标准在技术上没有规定,但他们还是建议使用显式大小格式强制执行它–至少有助于您作为开发人员和使用该代码的其他人弄清该函数希望您传入的大小数组。

在C中强制对数组进行类型安全
正如@Winger Sendon在我的回答下方的注释中指出的那样,我们可以强制C根据数组大小将数组类型视为不同!
首先,您必须在上面的示例中意识到这一点,使用int array1[2];像这样:arraytest(array1);导致array1自动衰减为int *。但是,如果改用array1的地址并调用arraytest(&array1),则会得到完全不同的行为!现在,它不会衰减为int *!相反,&array1的类型是int (*)[2],这意味着“指向int大小为2的数组的指针”,或“指向int类型为2的大小的数组的指针”。因此,您可以强制C来检查数组上的类型安全性,如下所示:
void arraytest(int (*a)[2])
{
    // my function here
}

该语法很难阅读,但与函数指针的语法相似。在线工具cdecl告诉我们int (*a)[2]的意思是:“将a声明为指向int数组2的指针”(指向2个int数组的指针)。不要将此与带有OUT括号的版本混淆:int * a[2],这意味着:“将a声明为指向int的指针的数组2”(包含指向int的2个指针的数组)。
现在,此函数需要您使用像这样的地址运算符(&),将指向正确大小数组的指针用作输入参数!:
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 ++的看法”)。

有关其他测试和实验,另请参见下面的链接。
参考文献
请参见上面的链接。另外:

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



评论


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

– Winger Sendon
18年7月31日在15:10

@WingerSendon,我知道我需要在这里验证一些细节,并且语法令人困惑(就像函数ptr语法令人困惑),所以我花了时间,最后用一个新的大标题“强制类型安全”更新了我的答案。在C中的数组上,涵盖了您的观点。

–加百利·斯台普斯
11月9日22:35

@GabrielStaples,谢谢。您的回答非常有帮助。您可以参考我以这种方式学习高级c的参考吗?

– daryooosh
12月15日4:39



@daryooosh,很遗憾,我不能。我没有很好的参考。通过多年的深入研究,我在这里,那里,那里都选择了一些东西。我能做的最好的就是告诉你,我偶尔会在这里把我学到的一些东西放到我的eRCaGuy_hello_world仓库中。请记住,虽然我在上面使用的C型安全设备应该非常谨慎地使用。这会使您的代码复杂化,并大量降低可读性,这是不值得的。着重于在可能的情况下使简单易懂的语法。

–加百利·斯台普斯
12月15日下午5:29



另请注意,经典的C语言经典教材是《 K&R C编程语言》这本书:en.wikipedia.org/wiki/The_C_Programming_Language。

–加百利·斯台普斯
12月15日下午5:30

#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;
}


评论


我对此表示赞同。我不确定为什么它被否决了。

–加百利·斯台普斯
11月8日22:04