所以我首先学习了 Java,现在我正在尝试切换到 C++。我很难让数组正常工作。
现在我只是想创建一个对象“Player”的数组并用一个填充它。但我得到一个错误。
Player* players = new Player[1];
players[0] = new Player(playerWidth, playerHeight, 20, 1);
错误说:操作数“=”匹配这些操作数。操作数类型为: Player = Player *
我不明白为什么这不起作用?
错误的意思是您试图将错误类型的值分配给变量。当错误说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();
在这段代码中只有一个对象,您可以通过a
or访问它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 无法像编写值那样编写Object值int
。但是 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>
将管理指向int
s 的指针分配有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) };
你的类型不匹配。难怪,您正试图将 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
是一个很好的起点。
原因是,变量的类型
players[0]
是玩家(对象)。但是,运算符“new”(新玩家)返回一个指针(玩家*)
如果您只想拥有一个对象,正确的方法是:
Player* player = new Player(playerWidth, playerHeight, 20, 1);
并且不要忘记在 C++ 中你需要自己清理混乱 - 在最后调用的某个地方
delete player;
对于您创建的每个对象。C++ 没有垃圾收集器——这意味着所有手动创建的(通过“新”)对象都会保留,直到您手动删除它们。
在 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.
在 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
除非你真的需要,否则不要使用。祝你好运!
在这里,您分配了一些内存来存储一个播放器的数组(不是很有用,但这是第一步)。
您的变量“players”现在存储该数组中第一个(也是唯一一个)插槽的地址。然后通过使用 player[0] 访问第一个 Player,您可以直接在其内存中写入/读取,而无需进行更多分配。
我想建议将此作为设置索引 0 值问题的解决方案:
Player* players = new Player[1];
players[0] = *(new Player(playerWidth, playerHeight, 20, 1));