数组类型 - 分配/用作函数参数的规则

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 个 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() 对大小一无所知。

【讨论】:

以上是关于数组类型 - 分配/用作函数参数的规则的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript 通用函数 - 为数组应用函数时出现“参数不可分配错误”

如何初始化用作函数参数的类类型

在 PostgreSQL 中使用自定义类型并将它们用作函数中的参数

如何返回地理位置响应对象以用作另一个函数的参数

如何返回地理位置响应对象以用作另一个函数的参数

将数组动态分配到带有指针参数的函数中