18

所以我首先学习了 Java,现在我正在尝试切换到 C++。我很难让数组正常工作。

现在我只是想创建一个对象“Player”的数组并用一个填充它。但我得到一个错误。

Player* players = new Player[1];
players[0] = new Player(playerWidth, playerHeight, 20, 1);

错误说:操作数“=”匹配这些操作数。操作数类型为: Player = Player *

我不明白为什么这不起作用?

4

7 回答 7

53

错误的意思是您试图将错误类型的值分配给变量。当错误说Player = Player *这意味着左侧的变量是 aPlayer而右侧的值是 a Player *

players[0] = new Player(playerWidth, playerHeight, 20, 1);

问题类似于您要这样做:

int x;
x = "Hello, World!";

左右手类型不匹配,没有自然转换,所以会报错。


第一个问题是您来自 Java 背景,Java 经常使用指针,但对您隐藏了它们。C++ 根本不隐藏它们。结果是 C++ 有不同的语法来显式处理指针。Java 摆脱了所有这些,主要使用 C++ 中的常规非指针语法来处理指针。

Java:                                  C++:

Player player = new Player();          Player *player = new Player();

Player player2;                        Player *player2 = nullptr;

** no equivalent in java **            Player player3;

player.foo();                          player->foo();

** no equivalent in java **            player3.foo();

** no equivalent in java **            *player;

** no equivalent in java **            &player2;

理解使用指针和直接使用对象之间的区别非常重要:

Java:                                  C++:

Player a = new Player();               Player *a = new Player();
Player b = a;                          Player *b = a;
b.foo();                               b->foo();

在这段代码中只有一个对象,您可以通过aor访问它b,它没有任何区别,a并且b都是指向同一个对象的指针。

C++:

Player c = Player();
Player d = c;
d.foo();

在这段代码中有两个对象。它们是不同的,做某事d不影响c

如果在 Java 中您了解了“原始”类型int和对象类型之间的区别,String那么考虑它的一种方法是在 C++ 中所有对象都是原始的。如果我们回顾您的代码并使用此“C++ 对象就像 Java 原语”规则,您可能会更好地看到问题所在:

Java:
int[] players = new int[1];
players[0] = new int(playerWidth); // huh???

这应该清楚地表明,分配的右侧应该只是一个 Player 值,而不是一个新的 player 对象的动态分配。对于 java 中的 int,这看起来像players[0] = 100;. 由于 Java 中的 Object 类型不同,Java 无法像编写值那样编写Objectint。但是 C++ 可以;players[0] = Player(playerWidth, playerHeight, 20, 1);


第二个问题是 C 中的数组很奇怪,而 C++ 继承了它。

C 和 C++ 中的指针允许“指针算术”。如果你有一个指向对象的指针,你可以添加或减去它并获得指向不同对象的指针。Java 没有类似的东西。

int x[2]; // create an array of two ints, the ints are 'adjacent' to one another
// if you take the address for the first one and 'increment' it
// then you'll have a pointer to the second one.

int *i = &x[0]; // i is a pointer to the first element
int *j = &x[1]; // j is a pointer to the second element

// i + 1 equals j
// i equals j - 1

此外,数组索引运算符[]适用于指针。x[5]相当于*(x+5)。这意味着指针可以用作数组,这在 C 和 C++ 中是惯用的和预期的。事实上,它甚至已经融入了 C++。

在 C++ 中,当您使用new动态分配对象时,例如new Player,您通常会得到一个指向您指定的类型的指针。在这个例子中,你得到Player *. 但是当你动态分配一个数组时,例如new Player[5],它就不同了。您实际上不是取回一个指向五数组的指针,而是Players取回一个指向第一个元素的指针。这就像任何其他Player *

Player *p   = new Player;    // not an array
Player *arr = new Player[5]; // an array

唯一使这个指针不同的是,当你对它进行指针运算时,你会得到指向有效Player对象的指针:

Player *x = p + 1;   // not pointing at a valid Player
Player *y = arr + 3; // pointing at the fourth array element

new如果您在没有保护的情况下使用它们,delete则很难正确使用。为了证明这一点:

int *x = new int;
foo();
delete x;

此代码容易出错并且可能是错误的。具体来说,如果foo()抛出异常x则被泄露。

在 C++ 中,每当您获得责任时,例如当您调用时,new您获得了稍后调用的责任delete,您应该记住

RAII
责任* 采集是初始化

* 更多的人说“资源获取是初始化”,但资源只是一种责任。Jon Kalb 在他的一次异常安全 C++演讲中说服我使用后一个术语。

RAII 意味着每当你承担责任时,它应该看起来像你正在初始化一个对象;具体来说,您正在初始化一个特殊对象,其目的是为您管理该责任。这种类型的一个示例是std::unique_ptr<int>将管理指向ints 的指针分配有new

C++:

std::unique_ptr<int> x(new int);
foo();
// no 'delete x;'

要管理您的Player阵列,您可以这样使用std::unqiue_ptr

std::unique_ptr<Player[]> players(new Player[1]);
players[0] = Player(playerWidth, playerHeight, 20, 1);

现在,unique_ptr将为您处理该分配,您无需delete自己打电话。(注意,当你分配一个数组时,你应该给出unique_ptr一个数组类型;std::unique_ptr<Player[]>,当你分配其他任何东西时,你使用非数组类型,std::unique_ptr<Player>。)

当然,C++ 有一个更专业的 RAII 类型来管理数组,std::vector你应该更喜欢使用它std::unique_ptr

std::vector<Player> players(1);
players[0] = Player(playerWidth, playerHeight, 20, 1);

或者在 C++11 中:

std::vector<Player> players { Player(playerWidth, playerHeight, 20, 1) };
于 2013-01-15T20:50:28.723 回答
8

你的类型不匹配。难怪,您正试图将 a 存储Player*到已经分配的Player

Player* players = new Player[1];

这将创建一个长度为 1 的数组,其中包含一个实例化的Player,并将整个事物存储到一个Player*. 的类型players[0]将是Player

players[0] = new Player(...)

这会尝试创建一个新的Player*并将其存储在数组中。但是数组包含Player对象。你应该说

players[0] = Player(...)

或者,我猜这更适合你,你应该new完全停止使用,并使用std::vector.

std::vector<Player> players;
players.push_back(Player(playerWidth, playerHeight, 20, 1));
// or players.emplace_back(playerWidth, playerHeight, 20, 1);

这不仅更容易使用,而且您以后也不必记住delete它。当std::vector超出范围时,它将自动销毁。此外,与您的阵列不同,std::vector它可以包含任意数量的对象,因此您可以随意添加新玩家或删除现有玩家。

还有其他数据结构可能更适合您,具体取决于您的确切用途,但这std::vector是一个很好的起点。

于 2013-01-15T19:11:23.867 回答
5

原因是,变量的类型

players[0]

是玩家(对象)。但是,运算符“new”(新玩家)返回一个指针(玩家*)

如果您只想拥有一个对象,正确的方法是:

Player* player = new Player(playerWidth, playerHeight, 20, 1);

并且不要忘记在 C++ 中你需要自己清理混乱 - 在最后调用的某个地方

delete player;

对于您创建的每个对象。C++ 没有垃圾收集器——这意味着所有手动创建的(通过“新”)对象都会保留,直到您手动删除它们。

于 2013-01-15T19:10:25.017 回答
3

在 Java 中,当您使用关键字“new”时,您实际上会得到一个指向对象的指针。这是在 Java 中实例化对象类型的唯一方法。所以当你说你在Java中有一个“对象数组”时,说你有一个指向对象的指针数组更正确。

C++ 并没有隐藏对象是指针的事实。您可以让变量引用对象,也可以让变量引用指向对象的指针。

在您的示例中,您需要将其显式声明为指向对象的指针数组。

Players **players = new (Player*)[1];                         // Create an array of player pointers
players[0] = new Player(playerWidth, playerHeight, 20, 1);    // Create a single player

虽然 C++ 允许您使用关键字new显式创建对象,但您必须确保在完成后清理您的对象,否则它们将永远不会被释放(称为内存泄漏)。

这是 C++ 和 Java 之间的主要区别之一。Java 的对象是垃圾回收的,程序员不必担心管理对象的生命周期。

完成后,您将需要清理分配的单个播放器以及数组。好的经验法则是每次调用new都应该对应一次调用delete

delete players[0];  // delete the player pointed to by players[0]
delete[] players;   // syntax for deleting arrays

然而,有趣的是,与 Java 不同,对象是在堆上分配的,您可以在 C++ 中在堆栈上创建对象,就好像它们是原始类型(如 int、float、char)一样。这允许您拥有本地范围内的对象,以及在内存中连续对齐的对象。在 Java 中没有办法做到这一点。

如果您以这种方式分配对象数组,则会为数组中的每个对象调用默认构造函数。

Player p;                           // This calls the default constructor and returns a Player object

Players *players = new Player[5];   // Create an array of player objects
players[0].playerWidth = 8;         // valid because the object has already been constructed

delete[] players; // don't forget to cleanup the array.
                  // no need to cleanup individual player objects, as they are locally scoped.

编辑:正如其他人所提到的,在您的情况下使用 std::vector 而不是数组可能更容易(无需担心内存分配)并且与数组的性能顺序相同;但是我认为熟悉 C++ 中指针的概念非常重要,因为它们可以帮助您了解内存的组织方式。

这是创建 Player 指针向量的语法。

std::vector<Player*> players(1); // Creates a vector of pointer to player with length 1
players[0] = new Player(playerWidth, playerHeight, 20, 1); // Create a new player object
delete players[0];                                         // delete the player

以及创建实际 Player 对象实例向量的语法(这是最首选的解决方案):

std::vector<Player> players(5); // Creates a vector of five player objects
players[0].playerWidth = 8; //already constructed, so we can edit immediately
//no cleanup required for the vector _or_ the players.
于 2013-01-15T19:16:59.877 回答
2

在 Java 中,您可以这样做Foo f = new Foo();,为您提供一个动态分配的对象,该对象的生命周期由垃圾收集器管理。

现在,在 C++ 中,Foo* f = new Foo;看起来很相似,并且还为您提供了一个动态分配的对象(您可以通过指针访问它f),但是 C++ 没有内置的垃圾收集器。在大多数情况下,功能 C++ 等价物是Foo f;,它为您提供了一个本地对象,当您离开当前函数(通过 return 或 throw)时该对象被销毁。

如果您需要动态分配,请使用“智能指针”,它们实际上是行为类似于指针的类。在 C++ 98 中,只有std::auto_ptr并且人们经常使用boost::shared_ptr它来补充它。std::unique_ptr在较新的 C++ 11 中,有一些std::shared_ptr实现相同的功能。

我希望这能给你一些需要阅读的方向的指导,但总体而言,Juanchopanza 给出了一个很好的建议:new除非你真的需要,否则不要使用。祝你好运!

于 2013-01-15T19:16:30.173 回答
0

在这里,您分配了一些内存来存储一个播放器的数组(不是很有用,但这是第一步)。

您的变量“players”现在存储该数组中第一个(也是唯一一个)插槽的地址。然后通过使用 player[0] 访问第一个 Player,您可以直接在其内存中写入/读取,而无需进行更多分配。

于 2013-01-15T19:11:26.860 回答
0

我想建议将此作为设置索引 0 值问题的解决方案:

Player* players = new Player[1];
players[0] = *(new Player(playerWidth, playerHeight, 20, 1));
于 2020-07-26T19:58:34.540 回答