数组类型 - 分配/用作函数参数的规则
Posted
技术标签:
【中文标题】数组类型 - 分配/用作函数参数的规则【英文标题】: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上使用是错误的呢?
【问题讨论】:
题名需要改写,目前题名过于笼统,小写。 【参考方案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 个 int
s 的存储空间,并将其命名为 b
。 a
也被分配了类似的存储空间。因此,实际上,上述分配意味着“我想让存储位置与之前的位置相同”。这没有意义。
如果你想复制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(&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() 对大小一无所知。
【讨论】:
以上是关于数组类型 - 分配/用作函数参数的规则的主要内容,如果未能解决你的问题,请参考以下文章
TypeScript 通用函数 - 为数组应用函数时出现“参数不可分配错误”