通常の配列のように、関数で2D配列の引数を宣言できないのはなぜですか?
void F(int bar[]){} //Ok
void Fo(int bar[][]) //Not ok
void Foo(int bar[][SIZE]) //Ok
列のサイズを宣言する必要があるのはなぜですか?
通常の配列のように、関数で2D配列の引数を宣言できないのはなぜですか?
void F(int bar[]){} //Ok
void Fo(int bar[][]) //Not ok
void Foo(int bar[][SIZE]) //Ok
列のサイズを宣言する必要があるのはなぜですか?
静的配列:
あなたは完全に要点を理解していないようです。少し説明しようと思いました。上記の回答のいくつかが説明しているように、2D Array
inC++
はメモリに。として格納されます1D Array
。
int arr[3][4] ; //consider numbers starting from zero are stored in it
メモリ内ではこのように見えます。
1000 //ignore this for some moments 1011
^ ^
^ ^
0 1 2 3 4 5 6 7 8 9 10 11
|------------| |-----------| |-------------|
First Array Second Array Third Array
|----------------------------------------------|
Larger 2D Array
ここで、Bigger2D Array
は連続したメモリユニットとして格納されていることを考慮してください。からまでの合計12
要素で構成さ0
れ11
ます。行は3
で、列は4
です。3番目の配列にアクセスする場合は、1番目と2番目の配列全体をスキップする必要があります。つまり、スキップするcols
配列の数を掛けた数に等しい要素をスキップする必要があります。であることがわかりますcols * 2
。
ここで、配列の単一のインデックスにアクセスするための次元を指定する場合、スキップする要素の正確な量を事前にコンパイラーに通知する必要があります。したがってcols
、残りの計算を実行するための正確な数を指定します。
では、どのように計算を実行するのでしょうか。で機能するとしましょうcolumn major order
。つまり、スキップする列の数を知る必要があります。この配列の1つの要素を...として指定すると
arr[i][j] ;
コンパイラはこの計算を自動的に実行します。
Base Address + (i * cols + j) ;
その信憑性をテストするために、1つのインデックスの式を試してみましょう。3rd
配列の要素にアクセスしたいと思います2nd
。このようにします...
arr[1][2] ; //access third element of second array
数式に入れます...
1000 + ( 1 * 4 + 2 )
= 1000 + ( 6 )
= 1006 //destination address
そして、私たちはが位置するアドレス1006
に到達します。一言で言えば、この計算6
の数をコンパイラに伝える必要があります。cols
そのため、関数のパラメーターとして送信します。
私たちがこのように取り組んでいる場合3D Array
...
int arr[ROWS][COLS][HEIGHT] ;
関数内の配列の最後の2次元を送信する必要があります。
void myFunction (int arr[][COLS][HEIGHT]) ;
数式はこれになります。
Base Address + ( (i * cols * height) + (j * height) + k ) ;
このようにアクセスするには...
arr[i][j][k] ;
COLS
の数をスキップするようにコンパイラーに指示し、の数をスキップするようにコンパイラーに2D Array
指示HEIGHT
し1D Arrays
ます。など、任意の次元についても同様です。
動的配列:
このように宣言された動的配列の場合のさまざまな動作について質問すると、
int ** arr ;
Dynamic 2D Array
の各インデックスは別のへのアドレスで構成されているため、コンパイラはそれらを異なる方法で処理します1D Array
。それらは、ヒープ上の隣接する場所に存在する場合と存在しない場合があります。それらの要素は、それぞれのポインターによってアクセスされます。上記の静的配列の動的な対応物は、次のようになります。
1000 //2D Pointer
^
^
2000 2001 2002
^ ^ ^
^ ^ ^
0 4 8
1 5 9
2 6 10
3 7 11
1st ptr 2nd ptr 3rd ptr
これが状況だとしましょう。ここでは2D Pointer
、場所のまたは配列1000
。それ2000
自体がメモリ位置のアドレスを保持するアドレスを保持します。ここで、ポインタ演算は、要素の正しい位置を判断するためにコンパイラによって実行されます。
にメモリを割り当てるために2D Pointer
、それを行います。
arr = new int *[3] ;
そして、この方法で、各インデックスポインタにメモリを割り当てます。
for (auto i = 0 ; i < 3 ; ++i)
arr[i] = new int [4] ;
最後に、それぞれptr
は2D Array
それ自体が配列です。要素にアクセスするには...
arr[i][j] ;
コンパイラはこれを行います...
*( *(arr + i) + j ) ;
|---------|
1st step
|------------------|
2nd step
最初のステップでは、は適切なインデックスに逆2D Array
参照さ1D Array
れ、2番目のステップでは1D Array
、適切なインデックスに到達するために逆参照されます。Dynamic 2D Arrays
これが、行や列について言及せずに関数に送信される理由です。
注: 多くの詳細は無視されており、説明では多くのことが想定されています。特に、アイデアを提供するためのメモリマッピングです。
ポインタに減衰するvoid Foo(int bar[][])
ため、を書くことはできません。bar
次のコードを想像してみてください。
void Foo(int bar[][]) // pseudocode
{
bar++; // compiler can't know by how much increase the pointer
// as it doesn't know size of *bar
}
したがって、コンパイラはのサイズを認識している必要が*bar
あるため、右端の配列のサイズを指定する必要があります。
コンパイラは、オフセットを計算するために2番目の次元の長さを知る必要があります。2D配列は、実際には1D配列として格納されます。既知の次元のない配列を送信する場合は、ポインターへのポインターと、次元を自分で知るための何らかの方法を使用することを検討してください。
これは、たとえばjavaとは異なります。これは、javaではデータ型にディメンションも含まれているためです。
配列を渡すと、ポインタに減衰するため、最も外側の次元を除外しても問題ありません。除外できる次元はそれだけです。
void Foo(int bar[][SIZE])
と同等です:
void Foo(int (*bar)[SIZE])
静的2D配列は、データへのアクセスを改善するための砂糖を含む1D配列のようなものであるため、ポインターの演算について考える必要があります。
コンパイラが要素array[x][y]にアクセスしようとすると、要素のアドレスメモリ、つまりarray + x * NUM_COLS+yを計算する必要があります。したがって、行の長さ(含まれる要素の数)を知る必要があります。
さらに情報が必要な場合は、このリンクをお勧めします。
C /C++で2D配列を割り当てるには基本的に3つの方法があります
malloc
次のように使用して、ヒープに2D配列を割り当てることができます。
const int row = 5;
const int col = 10;
int **bar = (int**)malloc(row * sizeof(int*));
for (size_t i = 0; i < row; ++i)
{
bar[i] = (int*)malloc(col * sizeof(int));
}
これは実際には配列の配列として格納されるため、必ずしもメモリ内で連続しているとは限りません。これは、各配列に余分なメモリ使用量がかかるポインタがあることも意味することに注意してください(この例では5つのポインタ、逆に割り当てる場合は10のポインタ)。この配列を署名付きの関数に渡すことができます。
void foo(int **baz)
さまざまな理由(キャッシュの最適化、メモリ使用量など)のために、2D配列を1D配列として格納することが望ましい場合があります。
const int row = 5;
const int col = 10;
int *bar = (int*)malloc(row * col * sizeof(int));
2番目の次元を知っていると、次を使用して要素にアクセスできます。
bar[1 + 2 * col] // corresponds semantically to bar[2][1]
プリプロセッサの魔法(または()
C ++ではメソッドのオーバーロード)を使用して、次のようにこれを自動的に処理する人もいます。
#define BAR(i,j) bar[(j) + (i) * col]
..
BAR(2,1) // is actually bar[1 + 2 * col]
関数のシグネチャが必要です。
void foo(int *baz)
この配列を関数に渡すため。
次のようなものを使用して、スタックに2D配列を割り当てることができます。
int bar[5][10];
これはスタック上の1d配列として割り当てられるため、コンパイラは2番目の例で行ったように、必要な要素に到達するために2番目の次元を知る必要があります。したがって、次のことも当てはまります。
bar[2][1] == (*bar)[1 + 2 * 10]
この配列の関数シグネチャは次のようになります。
void foo(int baz[][10])
コンパイラがメモリ内のどこに到達するかを認識できるように、2番目の次元を提供する必要があります。C / C ++はこの点で安全な言語ではないため、最初の次元を指定する必要はありません。
行と列をどこかで混同した場合はお知らせください。