【问题标题】:Array Type - Rules for assignment/use as function parameter数组类型 - 分配/用作函数参数的规则
【发布时间】:2011-01-03 08:27:24
【问题描述】:

当我需要将数组传递给函数时,似乎函数的以下所有声明都可以工作

void f(int arr[])  
void f(int arr[4]) // is this one correct?

为此:

int a[]={1,2,3,4};
f(a);

但是当我将一个数组分配给另一个数组时,它会失败

int a[]={1,2,3,4};
int b[4] = a; // error: array must be initialized with a brace-enclosed initializer

那么为什么作为函数的参数传递的数组是可以的,但在简单赋值的rhs上使用是错误的呢?

【问题讨论】:

  • 题名需要改写,目前题名过于笼统,小写。

标签: c arrays initialization variable-assignment


【解决方案1】:
void f(int arr[]);
void f(int arr[4]);

语法具有误导性。它们都和这个一样:

void f(int *arr);

即,您正在传递一个指向数组开头的指针。你不是在复制数组。

【讨论】:

  • 是的,但是(在 C99 中)void f(int arr[static 4]) { ... } 是特殊的,因为这允许编译器假定 arr 不是NULL 并且大小至少为 4。
【解决方案2】:

C 不支持数组的赋值。在函数调用的情况下,数组衰减为指针。 C确实支持指针的分配。几乎每天都会有人问这个问题——你们在读什么 C 教科书没有解释这个问题?

【讨论】:

    【解决方案3】:

    试试 memcpy。

    int a[]={1,2,3,4};
    int b[4];
    memcpy(b, a, sizeof(b));
    

    感谢您指出这一点,Steve,我已经有一段时间没有使用 C 语言了。

    【讨论】:

    • memcpy(b, a, 4*sizeof(int)。或sizeof(b).
    【解决方案4】:

    为了理解区别,我们需要了解两个不同的上下文

    • value 上下文中,T 类型数组的名称等价于指向 T 类型的指针,并且等于指向数组第一个元素的指针。
    • object 上下文中,T 类型的数组的名称不会简化为指针。

    什么是对象上下文?

    a = b; 中,a 在对象上下文中。当您获取变量的地址时,它将在对象上下文中使用。最后,当您在变量上使用 sizeof 运算符时,它会在对象上下文中使用。在所有其他情况下,在值上下文中使用变量。

    既然我们已经掌握了这些知识,那么当我们这样做时:

    void f(int arr[4]);
    

    正好等价于

    void f(int *arr);
    

    如您所见,我们可以在函数声明中省略大小(上面的 4)。这意味着您无法知道传递给f() 的“数组”的大小。稍后,当你这样做时:

    int a[]={1,2,3,4};
    f(a);
    

    在函数调用中,名称a 在值上下文中,因此它简化为指向int 的指针。这很好,因为f 需要一个指向int 的指针,所以函数定义和使用匹配。传递给f() 的是指向a (&a[0]) 的第一个元素的指针。

    如果是

    int a[]={1,2,3,4};
    int b[4] = a;
    

    名称b 用于对象上下文中,不会简化为指针。 (顺便提一下,这里的a 在一个值上下文中,并简化为一个指针。)

    现在,int b[4]; 分配了 4 个 ints 的存储空间,并将其命名为 ba 也被分配了类似的存储空间。因此,实际上,上述分配意味着“我想让存储位置与之前的位置相同”。这没有意义。

    如果你想复制a的内容到b,那么你可以这样做:

    #include <string.h>
    int b[4];
    memcpy(b, a, sizeof b);
    

    或者,如果您想要一个指向 a 的指针 b

    int *b = a;
    

    这里,a 在值上下文中,并简化为指向int 的指针,因此我们可以将a 分配给int *

    最后,当初始化一个数组时,你可以给它分配明确的值:

    int a[] = {1, 2, 3, 4};
    

    这里,a 有 4 个元素,分别初始化为 1、2、3 和 4。你也可以这样做:

    int a[4] = {1, 2, 3, 4};
    

    如果列表中的元素少于数组中的元素个数,则其余的值都取为0:

    int a[4] = {1, 2};
    

    a[2]a[3] 设置为 0。

    【讨论】:

    • 您能否提供一个参考,说明您在哪里阅读了有关对象和值上下文的信息?我有兴趣阅读更多内容。
    • 我从未听说过“对象上下文”与“值上下文”。
    • @w00te, torek.net/torek/c/expr.html#therule 是我第一次了解对象和值上下文的地方。
    • 从形式上讲,“对象上下文”基本上是“期望右值的表达式”,“值上下文”是“期望左值的表达式”。 C 中的所有函数调用都将其参数作为右值(=“按值”)并返回一个右值,并且运算符会有所不同。 IE。 op= 需要左值 LHS 和右值 RHS,1-arg op* 需要右值并返回左值,2-arg op+ 和 op- 需要并返回右值等等。
    【解决方案5】:

    要获得直觉,您必须了解机器级别发生了什么。

    初始化语义 (= {1,2,3,4}) 的意思是“完全以这种方式将它放在二进制图像上”,因此可以编译。

    数组赋值会有所不同:编译器必须将其转换成一个循环,这实际上会迭代元素。 C 编译器(或 C++,就此而言)从不做这样的事情。它理所当然地希望你自己做。为什么?因为你能。所以,它应该是一个子程序,用 C (memcpy) 编写。这一切都是为了简单和接近你的武器,这就是 C 和 C++。

    【讨论】:

      【解决方案6】:

      注意int a[4]a的类型是int [4]

      但是TypeOf(&amp;a) == int (*)[4] != int [4]

      还要注意a的类型是int *,这和上面的都不同!

      这是一个您可以尝试的示例程序:

      int main() {
        // All of these are different, incompatible types!      
        printf("%d\n", sizeof (int[4]));  // 16
        // These two may be the same size, but are *not* interchangeable!
        printf("%d\n", sizeof (int (*)[4]));  // 4
        printf("%d\n", sizeof (int *));  // 4
      }
      

      【讨论】:

      • 您应该使用“%zu”来打印 size_t(在 C99 中)。
      【解决方案7】:

      我想澄清一下。答案中有一些误导性提示...以下所有函数都可以采用整数数组:

      void f(int arr[])
      void f(int arr[4])
      void f(int *arr) 
      

      形式参数不一样。所以编译器可以以不同的方式处理它们。在内部内存管理的意义上,所有参数都指向指针。

      void f(int arr[])
      

      ... f() 接受任意大小的数组。

      void f(int arr[4])
      

      ...形式参数表示数组大小。

      void f(int *arr)
      

      ... 你也可以传递一个整数指针。 f() 对大小一无所知。

      【讨论】:

        猜你喜欢
        • 2013-11-03
        • 1970-01-01
        • 2014-04-17
        • 2018-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多