如何理解错误:无法将'int []'转换为'int []'

4

当编译以下代码时:

void DoSomething(int Numbers[])
{
    int SomeArray[] = Numbers;
}

VS2005编译器报错:C2440: 'initializing' : 无法将'int []'转换为'int []'。

我理解实际上它试图将指针转换为数组,这是行不通的。但是如何向学习C++的人解释这个错误呢?

5个回答

12

假设存在类型和不完整类型:

struct A;

一个被称为A的结构体的不完全类型。

struct A { };

一个名为A的结构体被称为完整类型。第一个结构体的大小尚未知道,而第二个结构体的大小已知。

像上面的结构体一样,存在不完整的类类型。但也存在不完整的数组类型:

typedef int A[];

这是一个名为A的不完整数组类型。它的大小尚未确定。您不能创建一个数组,因为编译器不知道数组有多大。但是您可以使用它来创建一个数组,只有在立即初始化它的情况下才能这样做:

A SomeArray = { 1, 2, 3 };

现在,编译器知道该数组是一个有3个元素的int数组。如果你尝试用指针初始化该数组,编译器不会比之前聪明多少,并且会拒绝,因为这样无法给它创建数组的大小。


-1 抱歉。这个回答虽然正确,但可能过于抽象,对提问者没有太大帮助。根据他的示例代码,我的猜测是他可能认为数组作为 int[] 参数传递给函数时是按值传递的。 - j_random_hacker
我的意思是问问题者试图帮助的那个人... :) - j_random_hacker
1
我认为有两个问题。一个是使用指针初始化数组,另一个是为什么会出现“无法从T转换为T”的错误提示。你说得对,我只解释了其中的一小部分。写好长篇答案更难,所以我简短地概括了一下,否则我可能会搞砸 :) - Johannes Schaub - litb

8
试图使错误信息更有帮助性,编译器实际上却让事情更加混乱。尽管Numbers参数被声明为一个数组,但C/C++实际上不会(也不能)传递一个数组——Numbers参数实际上是一个指针。
因此,错误信息真正应该说成"无法将'int *'转换为'int []'"
但这样会引起混淆——"嘿,表达式中没有int*啊",有人可能会说。
因此,最好避免使用数组参数——将它们声明为指针,因为你实际上得到的就是指针。对于学习C/C++的人来说,解释应该教育他们一个事实:数组参数是虚构的——它们实际上是指针。

1
你表达得非常好 :) 我试图同样直接地阐述有关数组参数的业务,但是我在解释初始化方面受到了限制。无论如何,你得到了一个+1 :p - Johannes Schaub - litb

5
有三件事情你需要向想要帮助的人解释清楚:
  1. C++中无法将数组以值传递给函数。 为了实现你想做的事情,你需要将数组开始的地址和数组大小(用一个单独的int参数表示,实际上应该使用size_t,但是我不会特别强调)一起传递给DoSomething()。你可以通过表达式&(myArray[0])获取某个数组myArray的开始地址。由于这是一个非常普遍的需求,C++允许你只使用数组的名称,例如myArray,来获取它的第一个元素的地址。(根据你的角度看,这可能会有所帮助或者混淆。)更令人困惑的是,C++允许你将数组类型(例如int Numbers[])指定为函数的参数,但是在内部,它将该参数视为一个指针(在本例中为int *Numbers)--你甚至可以在DoSomething()内部使用Numbers += 5,使其指向从第六个位置开始的数组!

  2. 在C++中声明数组变量时,必须提供显式大小或“初始化器列表”,后者是用花括号括起来的逗号分隔值的列表。编译器无法根据你正在尝试初始化它的另一个数组来推断数组的大小,因为...

  3. 在C++中无法将一个数组复制到另一个数组中,也无法从另一个数组初始化一个数组。 因此,即使参数Numbers实际上是一个数组(例如大小为1000),而不是一个指针,并且你指定了SomeArray的大小(例如同样为1000),行int SomeArray[1000] = Numbers;仍然是非法的。


要在DoSomething()中实现你想要的功能,请首先问自己:

  1. 我需要更改Numbers中的任何值吗?
  2. 如果需要,我是否希望防止调用者看到这些更改?

如果两个问题的答案都是“否”,那么实际上你根本不需要首先复制Numbers--只需直接使用它,并忘记创建一个单独的SomeArray数组。

如果两个问题的答案都是“是”,你需要将Numbers复制到SomeArray中并在其上进行操作。在这种情况下,你应该真正地将SomeArray作为C++vector<int>而不是另一个数组,因为这真的简化了事情。(解释向量相对于手动动态内存分配的好处,包括它们可以从其他数组或向量初始化,并且当必要时它们会调用元素构造函数,而不像C风格的memcpy()。)

4
当我试图解释某些内容时,我总是尝试从最基础的层面开始,并从那里逐步构建。这是我喜欢学习东西的方式,我发现如果你从人们已知的基础知识开始,并从那里逐步构建,人们会更加舒适。
在这种情况下,我可能会从以下内容开始:
编译器正在尝试执行赋值操作,因为您编写了一个赋值操作。在C ++中,您不能直接对数组进行赋值,因为它没有内置的赋值运算符(任何类型都不支持数组的初始化和索引)。由于C ++支持类型的重载运算符,因此编译器会查找一个针对“被分配到”的类型的重载赋值运算符,该运算符以“分配自”类型作为其参数。由于int []也没有接受int []作为参数的重载运算符,所以编译器会在该行上出错,并告诉您为什么编译器无法处理该行。
是的,与只说一些关于大小、不完整类型等知识相比,这可能有些过度。我意识到它也不完整(例如:没有讨论初始化程序赋值与普通赋值之间的区别等)。然而,我的目标通常是让人们能够自己找出下一个答案,并为此您通常需要阐述到达答案的思维过程。

我喜欢这个答案,因为它真正解释了错误的措辞。 - Gorpik
是的,这是一个很好的方法来处理它。可惜在这种情况下编译器的错误信息真的没有什么帮助... - j_random_hacker

3
也许你的回答是:“因为编译器不知道数组的大小。”如果有明确的数组大小(可能通过typedef来明确),您的示例就可以工作,然后您可以在介绍变量大小分配时解释指针。

这只是其中的一半。在C/C++中,看起来像数组类型的参数类型实际上是指针类型(令人困惑)--编译器将忽略顶部行中[]之间给定的任何大小,并且即使为数组提供大小,它仍会抱怨您正在尝试分配地址到数组。 - j_random_hacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接