次のコードをコンパイルする場合:
void DoSomething(int Numbers[])
{
int SomeArray[] = Numbers;
}
VS2005コンパイラはエラーC2440で文句を言います:「初期化中」:「int[]」から「int[]」に変換できません
実際には、機能しない配列へのポインターをキャストしようとしていることを理解しています。しかし、C ++を学んでいる人にエラーをどのように説明しますか?
次のコードをコンパイルする場合:
void DoSomething(int Numbers[])
{
int SomeArray[] = Numbers;
}
VS2005コンパイラはエラーC2440で文句を言います:「初期化中」:「int[]」から「int[]」に変換できません
実際には、機能しない配列へのポインターをキャストしようとしていることを理解しています。しかし、C ++を学んでいる人にエラーをどのように説明しますか?
型と不完全型があるとしましょう:
struct A;
A という構造体の不完全型です。
struct A { };
A と呼ばれる構造体の完全な型です。最初のサイズはまだわかっていませんが、2 番目のサイズはわかっています。
上記の構造体のような不完全なクラス型があります。しかし、不完全な配列型もあります:
typedef int A[];
それは A という不完全な配列型です。そのサイズはまだわかっていません。コンパイラは配列の大きさを認識していないため、配列から配列を作成することはできません。ただし、すぐに初期化する場合にのみ、配列を作成するために使用できます。
A SomeArray = { 1, 2, 3 };
これで、コンパイラは配列が 3 つの要素を持つ int 配列であることを認識します。ポインターで配列を初期化しようとすると、コンパイラーは以前よりも賢くならず、拒否します。これは、作成される配列のサイズが得られないためです。
エラー メッセージをよりわかりやすくするために、コンパイラは実際には混乱を招きます。パラメーターが配列として宣言されていてもNumbers
、C/C++ は実際には配列を渡しません (できません) Numbers
。パラメーターは実際にはポインターです。
したがって、エラーは本当に言うべきです"cannot convert from 'int *' to 'int []'"
しかし、そうなると混乱が生じます。「おい、int*
表現には関係ない」と誰かが言うかもしれません。
このため、配列パラメーターを避ける方が実際には良いです - とにかくそれが本当に得られるものなので、それらをポインターとして宣言します。そして、C/C++ を学んでいる人への説明は、配列パラメータはフィクションであり、実際にはポインタであるという事実を教育する必要があります。
助けようとしている人に説明する必要があるのは、次の 3 点です。
C++ の関数に配列を値で渡すことはできません。 あなたがやろうとしていることを行うには、配列の開始アドレスを に渡す必要があります。また、DoSomething()
配列のサイズを別の引数int
(まあ、size_t
でも言いません) に渡す必要があります。myArray
式を使用して、配列の先頭のアドレスを取得できます&(myArray[0])
。これはよくあることなので、C++ では、配列の名前だけを使用して、たとえばmyArray
、最初の要素のアドレスを取得できます。(これは、見方によっては、役に立ったり混乱したりします。)さらに混乱させるために、C++ では配列型を指定できます (例:int Numbers[]
) 関数へのパラメーターとして、しかし密かにそのパラメーターをポインターとして宣言されているかのように扱います (int *Numbers
この場合)。Numbers += 5
内部で実行DoSomething()
して、6 番目の位置から始まる配列を指すようにすることもできます!
C++などで配列変数を宣言するときはSomeArray
、サイズを明示的に指定するか、中かっこで囲まれたコンマ区切りの値のリストである "initialiser list" を指定する必要があります。初期化しようとしている別の配列に基づいてコンパイラが配列のサイズを推測することはできません...
C++ では、ある配列を別の配列にコピーしたり、ある配列を別の配列から初期化したりすることはできません。 したがって、パラメーターNumbers
が実際には配列 (サイズ 1000 など) であり、ポインターではなく、サイズを指定した場合SomeArray
(ここでも 1000 など)、行int SomeArray[1000] = Numbers;
は不正になります。
でやりたいことを実行するにはDoSomething()
、まず次のことを自問してください。
Numbers
か?どちらかの質問に対する答えが「いいえ」の場合、そもそも のコピーを作成する必要はありません。そのまま使用し、別の配列Numbers
を作成することは忘れてください。SomeArray
両方の質問に対する答えが「はい」の場合は、 のコピーを作成して、代わりに作業する必要がありNumbers
ますSomeArray
。この場合、別の配列の代わりにSomeArray
C++ を実際に作成する必要があります。これにより、物事が本当に簡単になります。vector<int>
(手動の動的メモリ割り当てに対するベクトルの利点を説明します。これには、他の配列またはベクトルから初期化できるという事実と、C スタイルとは異なり、必要に応じて要素コンストラクターを呼び出すという事実が含まれますmemcpy()
。)
私が何かを説明しようとしているとき、私は常に最低レベルに降りて、そこから積み上げようとします。それが私が物事を学ぶのが好きな方法です。彼らが知っている基本から始めて、そこから積み上げていくと、人々はより快適になると思います。
この場合、私はおそらく次のようなものから始めます:
代入演算を記述したため、コンパイラは代入を実行しようとしています。C ++では、組み込みの代入演算子がないため、配列に直接代入することはできません(どのような種類でも、配列では初期化子とインデックス付けのみがサポートされています)。C ++は型のオーバーロードされた演算子をサポートしているため、コンパイラは「assigned-from」型を引数として取る「assigned-to」型のオーバーロードされた代入演算子を探します。int []を引数として取るint[]のオーバーロードされた演算子もないため、コンパイラーは行でエラーを起こし、エラーはコンパイラーが行を処理できない理由を示しています。
はい、それはおそらくやり過ぎですが、サイズや不完全なタイプなどの知識について何かを言うだけです。私はそれも完全ではないことを認識しています(たとえば、初期化子の割り当てと通常の割り当てなどの議論はありません)。しかし、私の目標は通常、人々が次の答えを自分で理解できる場所に到達することです。そのためには、通常、答えに到達するための思考プロセスをレイアウトする必要があります。
おそらくあなたの答えは、「コンパイラは配列の大きさを知らないからです」かもしれません。
あなたの例は、明示的な配列サイズ(おそらく明確にするためにtypedefを使用)がある場合に機能する可能性があり、変数サイズの割り当てを導入しながらポインターを説明できます。