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

Moh*_*jan 71 c arrays

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

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]);
}
Run Code Online (Sandbox Code Playgroud)

我发现虽然我arraytest()通过传递值调用函数,但原始副本int arr[]已更改.

你能解释一下原因吗?

Bo *_*son 108

将数组作为参数传递时,这个

void arraytest(int a[])
Run Code Online (Sandbox Code Playgroud)

意思是完全一样的

void arraytest(int *a)
Run Code Online (Sandbox Code Playgroud)

所以你修改main中的值.

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

  • @Ramon - 我会使用第二个选项,因为它似乎不那么令人困惑,更好地表明你没有得到数组的副本. (11认同)
  • 在哪种情况下哪种符号更好? (4认同)
  • 好,知道了!所以数组根本没有传递值! (3认同)
  • @lucapozzobon-最初,C没有通过值传递,除了单个值。直到将“ struct”添加到语言中后,这种情况才被更改。然后认为更改数组规则为时已晚。已经有10个用户。:-) (3认同)
  • 你能解释一下“历史原因”吗?我想通过值传递需要一个副本,所以浪费内存..谢谢 (2认同)

Gab*_*les 21

1. C 中的标准数组使用,从数组到 ptr 的自然类型衰减(调整)

@Bo佩尔松正确他的伟大的回答指出这里

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

void arraytest(int a[])
Run Code Online (Sandbox Code Playgroud)

意思完全一样

void arraytest(int *a)
Run Code Online (Sandbox Code Playgroud)

让我添加一些注释以增加这两个代码片段的清晰度:

// 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)
Run Code Online (Sandbox Code Playgroud)

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

  1. 意思完全一样

     // array of 0 ints; automatically adjusts (decays) from `int [0]`
     // (array of zero ints) to `int *` (ptr to int)
     void arraytest(int a[0])
    
    Run Code Online (Sandbox Code Playgroud)
  2. 这意味着完全相同

     // array of 1 int; automatically adjusts (decays) from `int [1]`
     // (array of 1 int) to `int *` (ptr to int)
     void arraytest(int a[1])
    
    Run Code Online (Sandbox Code Playgroud)
  3. 这意味着完全相同

     // array of 2 ints; automatically adjusts (decays) from `int [2]`
     // (array of 2 ints) to `int *` (ptr to int)
     void arraytest(int a[2])
    
    Run Code Online (Sandbox Code Playgroud)
  4. 这意味着完全相同

     // array of 1000 ints; automatically adjusts (decays) from `int [1000]`
     // (array of 1000 ints) to `int *` (ptr to int)
     void arraytest(int a[1000])
    
    Run Code Online (Sandbox Code Playgroud)
  5. 等等。

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

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);
Run Code Online (Sandbox Code Playgroud)

事实上,这里数组参数中的“大小”值([0][1][2][1000]等)显然只是为了美观/自我记录的目的,并且可以是size_t您想要的任何正整数(我认为是类型)!

然而,在实践中,您应该使用它来指定您希望函数接收的数组的最小大小,以便在编写代码时很容易跟踪和验证。MISRA-C-2012标准(购买/下载标准的236-PG 2012版本的PDF为£15.00这里)走得更远,国家(重点):

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

...

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

...

对函数参数使用数组声明符比使用指针更清楚地指定函数接口。函数所期望的最小元素数是明确规定的,而这对于指针是不可能的。

换句话说,他们建议使用显式大小格式,即使 C 标准在技术上没有强制执行它 -它至少有助于向您作为开发人员以及使用代码的其他人澄清函数期望的数组大小你传入。


2. 在 C 中强制数组类型安全

(不推荐(更正:有时推荐,特别是对于固定大小的多维数组),但可能。请参阅我最后反对这样做的简短论点。另外,对于我的多维数组 [例如:2D 数组] 版本对此,请在此处查看我的答案。)

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

首先,您必须认识到,在我上面的示例中,使用int array1[2];类似这样的方法:arraytest(array1);原因array1自动衰减为int *. 但是,如果您使用替代地址 array1并调用arraytest(&array1),则会得到完全不同的行为!现在,它不会衰减为int *! 这是因为如果您获取数组的地址,那么您已经拥有一个指针类型,并且指针类型不会调整为其他指针类型。只有数组类型适应指针类型。所以相反,&array1is的类型int (*)[2],这意味着“指向一个大小为 2 的 int数组的指针,或“指向一个大小为 2 的 int 类型数组的指针”,或者也说为“指向 2 个整数数组的指针”因此,您可以通过向数组传递显式指针来强制 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
}
Run Code Online (Sandbox Code Playgroud)

这种语法很难阅读,但类似于函数指针的语法。在线工具cdecl告诉我们这int (*a)[2]意味着:“将 a 声明为 int 数组 2 的指针(指向 2 数组的指针int)。:不与版本没有括号混淆int * a[2],这是指:“声明一个作为指针的阵列2至整数”(又名: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!
Run Code Online (Sandbox Code Playgroud)

但是,这会产生警告:

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)
Run Code Online (Sandbox Code Playgroud)

您可以在此处测试此代码

要强制 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 *’    
Run Code Online (Sandbox Code Playgroud)

将变成这个构建错误:

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
Run Code Online (Sandbox Code Playgroud)

请注意,您还可以创建指向给定大小数组的“类型安全”指针,如下所示:

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;
Run Code Online (Sandbox Code Playgroud)

...但我不一定推荐这个(在 C 中使用这些“类型安全”数组),因为它让我想起了很多 C++ 的滑稽动作,用于在任何地方强制类型安全,代价是语言语法复杂性和冗长性的代价非常高,并且难以构建代码,我不喜欢并且之前已经多次咆哮(例如:请参阅此处的“我对 C++ 的想法”)。


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

参考

请参阅上面的链接。还:

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

也可以看看:

  1. 我对多维数组(例如:2D 数组)的回答对上述内容进行了阐述,并在有意义的情况下对多维数组使用了“类型安全”方法:如何将多维数组传递给 C 和 C++ 中的函数

  • `void arraytest(int (*a)[1000])` 更好,因为如果大小错误,编译器会出错。 (2认同)

fyr*_*fyr 7

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


obo*_*obo 7

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


And*_*der 7

将多维数组作为参数传递给函数。 传递一个暗数组作为参数或多或少是微不足道的。让我们来看看传递一个 2dim 数组的更有趣的案例。在 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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

在这里,我指定了一个函数,该函数将指向 5 个整数数组的指针作为第一个参数。我可以将任何具有 5 列的 2 个暗淡数组作为参数传递:

int arr1[1][5]
int arr1[2][5]
...
int arr1[20][5]
...
Run Code Online (Sandbox Code Playgroud)

您可能会想到定义一个更通用的函数,该函数可以接受任何 2dim 数组并按如下方式更改函数签名:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码可以编译,但在尝试以与第一个函数相同的方式分配值时,您将收到运行时错误。因此,在 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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所期望的那样,它可以将任何 3 个在第二维中具有 4 个元素而在第三维中具有 5 个元素的暗淡数组作为参数。任何这样的事情都可以:

arr[1][4][5]
arr[2][4][5]
...
arr[10][4][5]
...
Run Code Online (Sandbox Code Playgroud)

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


Tho*_*ith 6

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

编辑:有三种这样的特殊情况,其中数组不会衰减到指向它的第一个元素的指针:

  1. sizeof a是不一样的sizeof (&a[0]).
  2. &a&(&a[0])(并不完全相同&a[0])不一样.
  3. char b[] = "foo"是不一样的char b[] = &("foo").


gra*_*upa 6

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

这是K&R2nd的报价:

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

写作:

void arraytest(int a[])
Run Code Online (Sandbox Code Playgroud)

与写作具有相同的含义:

void arraytest(int *a)
Run Code Online (Sandbox Code Playgroud)

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

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

此外,您可以在此处找到关于SO的其他答案


ale*_*lex 5

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

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

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


小智 5

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

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

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

int func(int* arr, ...){
    .
    .
    .
}
Run Code Online (Sandbox Code Playgroud)

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

谢谢 !!!

  • 我正在寻找你的第二个例子,你能详细说明每种方法的优点是什么吗? (3认同)