7

通常の配列のように、関数で2D配列の引数を宣言できないのはなぜですか?

 void F(int bar[]){} //Ok
 void Fo(int bar[][]) //Not ok
 void Foo(int bar[][SIZE]) //Ok

列のサイズを宣言する必要があるのはなぜですか?

4

6 に答える 6

19

静的配列:

あなたは完全に要点を理解していないようです。少し説明しようと思いました。上記の回答のいくつかが説明しているように、2D ArrayinC++はメモリに。として格納されます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要素で構成さ011ます。行は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指示HEIGHT1D 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] ;

最後に、それぞれptr2D Arrayそれ自体が配列です。要素にアクセスするには...

arr[i][j] ;

コンパイラはこれを行います...

*( *(arr + i) + j ) ;
   |---------|
     1st step
|------------------|
      2nd step

最初のステップでは、は適切なインデックスに逆2D Array参照さ1D Arrayれ、2番目のステップでは1D Array、適切なインデックスに到達するために逆参照されます。Dynamic 2D Arraysこれが、行や列について言及せずに関数に送信される理由です。

注: 多くの詳細は無視されており、説明では多くのことが想定されています。特に、アイデアを提供するためのメモリマッピングです。

于 2012-09-29T14:23:34.143 に答える
6

ポインタに減衰する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あるため、右端の配列のサイズを指定する必要があります。

于 2012-09-29T12:25:14.997 に答える
1

コンパイラは、オフセットを計算するために2番目の次元の長さを知る必要があります。2D配列は、実際には1D配列として格納されます。既知の次元のない配列を送信する場合は、ポインターへのポインターと、次元を自分で知るための何らかの方法を使用することを検討してください。

これは、たとえばjavaとは異なります。これは、javaではデータ型にディメンションも含まれているためです。

于 2012-09-29T12:23:54.337 に答える
1

配列を渡すと、ポインタに減衰するため、最も外側の次元を除外しても問題ありません。除外できる次元はそれだけです。

 void Foo(int bar[][SIZE]) 

と同等です:

 void Foo(int (*bar)[SIZE]) 
于 2012-09-29T12:16:35.113 に答える
1

静的2D配列は、データへのアクセスを改善するための砂糖を含む1D配列のようなものであるため、ポインターの演算について考える必要があります。
コンパイラが要素array[x][y]にアクセスしようとすると、要素のアドレスメモリ、つまりarray + x * NUM_COLS+yを計算する必要があります。したがって、行の長さ(含まれる要素の数)を知る必要があります。
さらに情報が必要な場合は、このリンクをお勧めします。

于 2012-09-29T12:32:46.610 に答える
1

C /C++で2D配列を割り当てるには基本的に3つの方法があります

2次元配列としてヒープに割り当てます

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)

1d配列としてヒープに割り当てます

さまざまな理由(キャッシュの最適化、メモリ使用量など)のために、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 ++はこの点で安全な言語ではないため、最初の次元を指定する必要はありません。

行と列をどこかで混同した場合はお知らせください。

于 2012-09-29T14:15:42.867 に答える