【问题标题】:Passing an array as an argument to a function in C将数组作为参数传递给 C 中的函数
【发布时间】:2021-09-15 22:52:34
【问题描述】:

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

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[] 的原始副本已更改。

你能解释一下原因吗?

【问题讨论】:

标签: c arrays function parameters parameter-passing


【解决方案1】:

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

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

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

【讨论】:

  • 我想*a+1有()缺失应该是*(a+1)
  • @Shin 谢谢,我玩 C 已经有一段时间了。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    void arraytest(int a[])
    

    意思完全一样

    void arraytest(int *a)
    

    所以您正在修改 main 中的值。

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

    【讨论】:

    • 哪种符号在哪种情况下更好?
    • @Ramon - 我会使用第二个选项,因为它看起来不那么令人困惑,并且更好地表明您没有得到数组的副本。
    • 你能解释一下“历史原因”吗?我想通过值传递需要一个副本,所以浪费内存..谢谢
    • @lucapozzobon - 最初 C 没有任何值传递,除了单个值。直到 struct 被添加到语言中,这才被改变。然后认为改变数组规则为时已晚。已经有10个用户了。 :-)
    • ...与void arraytest(int a[1000])等完全一样。扩展答案在这里:stackoverflow.com/a/51527502/4561887
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

      1. sizeof asizeof (&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) 答案将是字节指针已被分配。所以在这种情况下,它会作为一个指针衰减,如果你也包含它会很有帮助。看到这个postimg.org/image/prhleuezd
      • 如果我在最初我们定义的数组中的函数中使用sizeof操作,那么它将衰减为一个数组,但是如果我传入其他函数,那么使用sizeof操作符它将衰减为一个指针。
      • 我知道这是旧的。如果有人碰巧看到这个,有两个问题:) 1.@ThomSmith 写道,&a&a[0] 不完全相同,而a 是一个数组。为何如此?在我的测试程序中,无论是在声明数组的函数中,还是在传递给不同函数时,两者都显示相同。 2.作者写道“char b[] = "foo"char b[] = &("foo")不一样”。对我来说,后者甚至不编译。只有我吗?
      【解决方案6】:

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

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

      所以,您正在修改原始值。

      谢谢!!!

      【讨论】:

      • 我在找你的第二个例子,你能详细说明每种方法的优点是什么吗?
      【解决方案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】:

          对于传递二维(或更高的多维)数组,请在此处查看我的其他答案: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)
          

          不过,我还要补充一点,以上两种形式也有:

          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])
            
          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])
            
          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])
            
          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])
            
          5. 等等

          在上面的每一个数组示例中,正如下面代码中的示例调用所示,输入参数类型调整(衰减)为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(&amp;array1),则会得到完全不同的行为!现在,它不会衰减为 int *!这是因为如果您将的地址 设为一个数组,那么您已经 具有指针类型,并且指针类型不会适应其他指针类型。只有数组类型才能适应指针类型。因此,&amp;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)。

          现在,这个函数需要你像这样使用地址运算符 (&amp;) 调用它,使用指向正确大小的数组的指针作为输入参数!:

          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(&amp;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)。


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

          参考文献

          请参阅上面的链接。另外:

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

          另见:

          1. 我对多维数组(例如:二维数组)的回答对上述内容进行了阐述,并在有意义的情况下对多维数组使用了“类型安全”方法: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
          【解决方案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 字节内存空间并存储接收到的衰减指针。我们对它们进行指针运算。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-12-07
            • 2018-09-17
            • 2013-01-25
            • 1970-01-01
            • 1970-01-01
            • 2013-01-27
            相关资源
            最近更新 更多