与c++中的对象数组混淆
Confused with object arrays in C++
所以我首先学习Java,现在我正试图切换到c++。我有一点困难,使数组正确工作。
现在我只是想创建一个对象"Player"的数组,并填充它。但是我得到一个错误。
Player* players = new Player[1];
players[0] = new Player(playerWidth, playerHeight, 20, 1);
错误提示:操作数"="匹配这些操作数。操作数类型为:Player = Player *
我不明白为什么这不起作用?
错误的意思是您试图将错误类型的值分配给变量。当错误显示为Player = Player *
时,这意味着左边的变量是Player
,右边的值是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
或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值,而不是一个新播放器对象的动态分配。对于java中的int,它看起来像players[0] = 100;
。由于Java中的对象类型是不同的,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]
,情况就不同了。返回的不是指向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
的职责,你应该记住
R.A.I.I.
责任*获取是初始化
*人们更常说"资源获取是初始化",但资源只是一种责任。我是在Jon Kalb的一次异常安全c++演讲中被说服使用后一个术语的。
R.A.I.I.意味着无论何时你获得一个职责,它应该看起来像你在初始化一个对象;具体来说,你是在初始化一个特殊的对象,它的目的是为你管理那个职责。这种类型的一个例子是std::unique_ptr<int>
,它将管理由new
分配的指向int
的指针:
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++有一个更专门的用于管理数组的R.A.I.I.类型,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) };
你的类型不匹配。毫无疑问,您试图将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]
是Player (object)。然而,操作符"new" (new Player)返回一个指针(Player*)
如果你想只有一个对象,正确的做法是:
Player* player = new Player(playerWidth, playerHeight, 20, 1);
不要忘记在c++中你需要自己清理这些乱七八糟的东西——在末尾某处调用
delete player;
为您创建的每个对象。c++没有垃圾收集器——这意味着所有手工创建的(通过"new")对象都会保留,直到你手动删除它们。
在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.
EDIT:正如其他人提到的,在您的情况下,使用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
创建实际玩家对象实例向量的语法(这是最受欢迎的解决方案):
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
来补充它。在较新的c++ 11中,std::unique_ptr
和std::shared_ptr
实现了相同的功能。
我希望这能给你一些你需要阅读的指示,但总的来说,Juanchopanza给出了一个很好的建议:除非你真的需要,否则不要使用new
。好运!
这里你已经分配了一些内存来存储一个播放器的数组(不是很有用,但这是第一步)。
你的变量"players"现在存储了这个数组中第一个(也是唯一一个)插槽的地址。然后通过使用players[0]访问第一个Player,您可以直接在其内存中写入/读取,而不需要更多的分配。
对于索引0的值设置问题,我提出如下解决方案:
Player* players = new Player[1];
players[0] = *(new Player(playerWidth, playerHeight, 20, 1));
- 销毁C++中动态分配的内存(数组对象)
- 数组对象的生存期是否在重用其元素存储时结束?
- 为什么顶点数组对象会导致错误?
- 具有纯虚函数和指针数组对象类型的父类的指针数组
- 这是使用构造函数初始化数组对象的最佳方法吗?
- OpenGL 顶点数组对象与 tinyobjloader
- 将数组/对象/结构列表从C#库中传递给C MFC应用程序
- C++ RapidJson 帮助反序列化数组对象
- ptrdiff_t可以表示指向同一数组对象元素的指针的所有减法吗?
- 检查成员函数是否返回临时对象或数组对象
- 为什么 std::variant 不能容纳数组对象类型,而联合可以?
- 当数组对象以函数参数传递时,为什么复制构造函数会自称
- 如何使用箭头指针打印出一类数组对象,这些对象中有多个分数
- C++17 std::shared_ptr<> 类数组对象的重载运算符 []
- 添加两个具有运算符重载的数组对象,从而导致分段错误
- opengl:两个不同的矢量可以绑定到同一个顶点数组对象吗
- 使用相同的数据填充数组对象或使用指针
- 方法用于最快的分配,并且不需要将动态大小的数组对象作为局部变量
- 如何将2d数组对象传递给c++中的函数
- ReferenceTable溢出(jni-android),数组对象释放