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();

aこのコードにはオブジェクトが 1 つしかなく、どちらからでもアクセスできますがb、違いはなく、aどちらbも同じオブジェクトへのポインターです。

C++:

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

このコードには 2 つのオブジェクトがあります。それらは別個のものであり、何かを行っても にdは影響しませんc

Java で「プリミティブ」型intと Object 型の違いについて学んだ場合、Stringそれについて考える 1 つの方法は、C++ ではすべてのオブジェクトがプリミティブであるということです。あなたのコードを振り返って、この「C++ オブジェクトは Java プリミティブのようなものである」というルールを使用すると、何が問題なのかがよくわかるかもしれません。

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

これにより、割り当ての右側は、新しいプレーヤー オブジェクトの動的割り当てではなく、単純に Player 値にする必要があることが明確になります。Java の int の場合、これはplayers[0] = 100;. Java のオブジェクト型は異なるため、Java には、値を書き込む方法でオブジェクト値を書き込む方法がありませint。しかし、C++ はそうです。players[0] = Player(playerWidth, playerHeight, 20, 1);


2 番目の問題は、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に、次のことを覚えておく必要があります。

RAII の
責任* 取得は初期化です

* 「リソースの取得は初期化です」とよく言われますが、リソースは 1 つの責任にすぎません。Jon Kalb のException Safe C++の講演の 1 つで、後者の用語を使用するよう説得されました。

RAII は、責任を取得するたびに、オブジェクトを初期化しているように見える必要があることを意味します。具体的には、その責任を管理することを目的とする特別なオブジェクトを初期化しています。このような型の 1 つの例は、 で割り当てられたstd::unique_ptr<int>へのポインタを管理するものです。intnew

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。(NB 配列を割り当てるときは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

あなたのタイプは一致しません。Player*当然のことながら、 aを既に割り当てられているに格納しようとしています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]

Player(オブジェクト)です。ただし、演​​算子 "new" (new Player) はポインター (Player*) を返します。

オブジェクトを1つだけにしたい場合、それを行う正しい方法は次のとおりです。

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 の主な違いの 1 つです。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

そして、実際の 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それを補完するためによく使用します。新しい C++ 11 には、同じことを実現する とstd::unique_ptrがあります。std::shared_ptr

これにより、少し読む必要がある方向へのヒントが得られることを願っていますが、全体として、Juanchopanza は適切なアドバイスを提供newしました。本当に必要でない限り使用しないでください。幸運を!

于 2013-01-15T19:16:30.173 に答える
0

ここでは、1 つの Player の配列を格納するためにいくらかのメモリを割り当てています (あまり役に立ちませんが、これは最初のステップです)。

変数「players」は、この配列の最初の (そして唯一の) スロットのアドレスを格納しています。次に、players[0] で最初の Player にアクセスすることで、そのメモリに直接書き込み/読み取りを行うことができ、それ以上の割り当ては必要ありません。

于 2013-01-15T19:11:26.860 に答える