Подтвердить что ты не робот

Смутно с массивами объектов в С++

Итак, я впервые изучил Java, и теперь я пытаюсь перейти на С++. Я немного затрудняюсь с тем, чтобы массивы работали правильно.

Сейчас я просто пытаюсь создать массив объекта "Player" и заполнить его одним. Но я получаю сообщение об ошибке.

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

Ошибка говорит: операнд "=" соответствует этим операндам. Типы операндов: Player = Player *

Я не понимаю, почему это не работает?

4b9b3361

Ответ 1

Что означает ошибка, так это то, что вы пытаетесь присвоить переменную значение неправильного типа. Когда ошибка говорит Player = Player *, это означает, что переменная с левой стороны является Player, а значение с правой стороны - Player *.

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

Проблема похожа на то, что вы должны делать:

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

Типы слева и справа не совпадают, и нет естественного преобразования, поэтому вы получаете ошибку.


Первая проблема заключается в том, что вы исходите из фона Java, а Java часто использует указатели, но скрывает их от вас. С++ не скрывает их вообще. Следствием этого является то, что С++ имеет различный синтаксис для явного обращения с указателями. Java избавился от всего этого и в основном использовал обычный синтаксис не указателя из С++ для работы с указателями.

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, то один из способов подумать об этом заключается в том, что в С++ все объекты примитивны. Если мы оглянемся назад на ваш код и используем это: "Объекты С++ похожи на правило Java-примитивов, вы можете увидеть лучше, что не так:

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

Это должно дать понять, что правая сторона задания должна быть просто значением игрока, а не динамическим распределением нового объекта игрока. Для int в java это выглядит как players[0] = 100;. Поскольку типы объектов в Java различаются, у Java нет способа записать значения Object так, как вы можете написать значения int. Но С++ делает; players[0] = Player(playerWidth, playerHeight, 20, 1);


Вторая проблема заключается в том, что массивы в 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 и С++. На самом деле он даже запек в С++.

В С++, когда вы используете 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 просачивается.

В С++ всякий раз, когда вы получаете ответственность, например, когда вы вызываете new, вы получаете ответственность за вызов delete в более позднее время, вы должны помнить

R.A.I.I.
   Ответственность * Приобретение является инициализацией

* Чаще всего люди говорят, что "сбор ресурсов - это инициализация", но ресурсы - это только один вид ответственности. Я был убежден, что использовал последний термин Jon Kalb в одном из своих Exception Safe С++.

R.A.I.I. означает, что всякий раз, когда вы приобретаете ответственность, это должно выглядеть так, как будто вы инициализируете объект; в частности, вы инициализируете специальный объект, целью которого является управление этой ответственностью за вас. Одним из примеров такого типа является std::unique_ptr<int>, который будет управлять указателями на int, выделенными 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 самостоятельно. (N.B., когда вы выделяете массив, вы должны указать unique_ptr тип массива; std::unique_ptr<Player[]>, а при распределении чего-либо другого вы используете тип без массива, std::unique_ptr<Player>.)

Конечно, С++ имеет еще более специализированный R.A.I.I. тип для управления массивами, std::vector, и вы должны предпочесть использовать std::unique_ptr:

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

Или в С++ 11:

std::vector<Player> players { Player(playerWidth, playerHeight, 20, 1) };

Ответ 2

Ваши типы не совпадают. И неудивительно, что вы пытаетесь сохранить 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 является хорошей отправной точкой.

Ответ 3

Причина в том, что тип переменной

players[0]

- Player (объект). Однако оператор "новый" (новый проигрыватель) возвращает указатель (Player *)

Если вы хотите иметь только один объект, правильный способ сделать это будет:

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

И не забывайте, что на С++ вам нужно очистить беспорядок после себя - где-то в конце вызова

delete player;

для каждого созданного вами объекта. С++ не содержит сборщика мусора - все объекты, созданные вручную (с помощью "новых" ) остаются до тех пор, пока вы их не удалите вручную.

Ответ 4

В Java, когда вы используете ключевое слово "новый", вы фактически возвращаете указатель на объект. Это единственный способ создать экземпляр типа объекта в Java. Поэтому, когда вы говорите, что у вас есть "Массив объектов" в Java, правильнее сказать, что у вас есть массив указателей на объекты.

С++ не скрывает, что объекты являются указателями. Вы можете иметь переменную, ссылающуюся на объект, или вы можете иметь переменную, ссылающуюся на указатель на объект.

В вашем примере вам нужно явно объявить его как массив указателей на объекты.

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

И хотя С++ позволяет вам явно создавать объекты с использованием ключевого слова new, вы должны обязательно очистить свои объекты, как только вы закончите, иначе они никогда не будут освобождены (известно как утечка памяти),

Это одно из основных различий между С++ и Java; Java-объекты собирают мусор, и программисту не нужно беспокоиться об управлении временем жизни объекта.

Как только вы закончите, вам нужно будет очистить как отдельный игрок, который вы выделили, так и массив. Хорошее эмпирическое правило состоит в том, что каждый вызов нового должен соответствовать вызову удалить.

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

Однако интересно отметить, что в отличие от Java, где объекты выделены в куче, вы можете создавать объекты в стеке на С++, как если бы они были примитивными типами (например, 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 вместо массива, вероятно, проще в вашем случае (нет необходимости беспокоиться о распределении памяти) и находится в том же порядке производительности как массив; однако я думаю, что очень важно устроиться с понятием указателей на С++, поскольку они помогают понять, как организована память.

Вот синтаксис для создания вектора указателей игрока.

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.

Ответ 5

В Java вы выполняете Foo f = new Foo();, предоставляя вам динамически выделенный объект, управление жизненным циклом которого происходит сборщиком мусора.

Теперь, в С++, Foo* f = new Foo; выглядит аналогичным, а также дает вам динамически выделенный объект (доступ к которому вы можете получить с помощью указателя f), но С++ не имеет встроенного сборщика мусора. В большинстве случаев функциональный эквивалент С++ - это Foo f;, который дает вам локальный объект, который уничтожается, когда вы покидаете текущую функцию (через возврат или выброс).

Если вам нужно динамическое распределение, используйте "умные указатели", которые на самом деле являются классами, которые ведут себя как указатели. В С++ 98 есть только std::auto_ptr, и люди часто используют boost::shared_ptr для дополнения. В новом С++ 11 есть std::unique_ptr и std::shared_ptr, которые достигают того же самого.

Надеюсь, это даст вам несколько указателей в направлениях, где вам нужно немного почитать, но в целом Juanchopanza дал хороший совет: не используйте new, если вам действительно не нужно. Удачи!

Ответ 6

Здесь вы выделили некоторую память для хранения массива одного игрока (не очень полезно, но это первый шаг).

Теперь ваши переменные "игроки" сохраняют адрес первого (и единственного) слота в этом массиве. Затем, получив доступ к первому Игроку с игроками [0], вы можете напрямую писать/читать в своей памяти, и больше не требуется выделение.