3

関数に 2 次元配列を渡そうとしています。関数に渡すのに問題はありません。しかし、この背後にあるロジックを理解するのに苦労しています。関数と主な定義は次のようになります。

// Function to print the two-dimensional array
void print(int x, int y, int a[x][y]){
    printf("\n");
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++)
            printf("%d     ", a[i][j]);
        printf("\n");
    }
}

// Function to initialize the two-dimensional array
void init_2d(int *a, int x, int y){
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++){
            a[i*y + j] = i + j;
        }
        printf("\n");
    }
}

int main(){
    int m = 2, n = 3;
    int a[m][n];  // a two dimensional whose size has been defined using m and n
    init_2d(a, m, n);
    print(m, n, a);
}

皮肉なことに、すべてが正常に機能しています。私はその論理を消化できないので、それは私の問題です。

主な問題は次のとおりです。

  1. 私が本で読んだのは、2 次元配列のサイズは定数または記号定数を使用して定義する必要があるということでした。私のメインでは、変数mを使用して 2 次元配列を定義していますがn、それでも問題なく動作します。なんで?
  2. また、2 次元配列を (関数内で int へのポインターとして定義することによって) 1 次元配列に分解して渡すように言われました。つまり、 function で行った方法ですinit_2d。しかし、関数では、変数とprintを使用してサイズが定義された 2 次元配列を使用して実行しています。そうしてもいいですか?xy
  3. ポインターへのポインターを使用して 2 次元配列をトラバースすることもできますか?

私のすべての概念をクリアできる、このトピックに関する良い読み物を誰かに提案してもらえますか?

コードブロックを使用してコードをコンパイルしていますが、コンパイラはGNU GCC Compilerです。

4

6 に答える 6

7
  1. はいといいえ。本で読んだことは、C 言語仕様の古い元のバージョンである C89/90 にも当てはまります。C 言語コンパイラの C99 バージョンでは、いわゆる可変長配列 (VLA) がサポートされているため、そのサイズは実行時の値で指定できます。言語の C99 固有の機能を誤って使用しました。これは、コンパイラがデフォルト モードでサポートしているようです。厳密な C89/90 モードに切り替えるようにコンパイラに要求すると、特に配列サイズの指定に非定数を使用したため、コードはコンパイルに失敗します。

    C99 でも、VLA はローカル配列や関数パラメーター宣言などの特定のコンテキストでのみサポートされていることに注意してください。静的 VLA または構造体型のメンバーである VLA を宣言することはできません。これらのコンテキストでは、C99 でも一定の配列サイズを使用する必要があります。

  2. 2D 配列を 1D 配列に分解するように言った人は誰であれ、非常に間違っていました。あなたのプログラムでの使用方法init_2dは無効です: 自分で 2D 配列を 1D 配列として「再解釈」することは許可されていません。どの C コンパイラーも、すぐに文句を言います。

    init_2d(a, m, n);
    

    これは、この呼び出しがパラメーターintの 2D 配列引数を渡そうとするためint *です。これは違法です。コンパイラを沈黙させるには、あなたがしなければならないでしょう

    init_2d((int *) a, m, n);
    

    「期待どおりに機能する」可能性はinit_2d十分にありますが、それでもそのようなハックを使用する正当な理由はありません。

    ここで実際にできることは、2D 配列を1D 配列へのポインターに減衰させることです。信じられないかもしれませんが、これはprint宣言ですでに行ったこととまったく同じです。君の

    void print(int x, int y, int a[x][y])
    

    宣言は実際には同等です

    void print(int x, int y, int (*a)[y])
    

    宣言。つまりa、実際には型int [y]へのポインタ、つまり size の 1Dint配列へのポインタyです。上記の 2 つの宣言は、同じことを表面的に異なる 2 つの方法で表現しているだけです。

    また、2D 配列を「分解」して関数に渡す必要がないことにも注意してください。次のように、2D 配列全体へのポインターによって 2D 配列を渡すことができます。

    void print(int x, int y, int (*a)[x][y])
    

    この場合、配列を渡すには、&演算子を使用する必要があります

    print(m, n, &a);
    

    関数内の配列にアクセスするには、*演算子を使用する必要があります

    printf("%d     ", (*a)[i][j]);
    
  3. いいえ、あなたの場合はあり得ません。組み込みの 2D 配列は、ポインター ツー ポインターでトラバースできません。ところで、init_2d実装ですでにわかっているように (これは違法ですが、「機能します」)、2D 配列は 1D 配列として内部的に実装され、2D から 1D への自動インデックス再計算が行われます。ポインターツーポインターは、そのような「フラットな」線形データをトラバースするのに役立ちません。

    ポインターからポインターへの型 (または、より一般的にはマルチレベル ポインター型) は、まったく異なる種類の多次元配列を操作するときによく使用されます。 (N-1) 次元の配列。この種の多次元配列は、C プログラムでもよく使用されますが、手動で実装および管理する必要があります (SO でかなりの数の例を見つけることができます)。一方、上で述べたように、組み込み言語配列はまったく異なる方法で実装されており、マルチレベル ポインターによってトラバースすることはできません。

于 2013-06-19T03:46:25.757 に答える
4
  1. ISO C90 は可変長配列を禁止していますが、コンパイラは新しいので問題ありません。-ansi -pedantic -Wall フラグを付けてコンパイルすると失敗します
  2. -ansi -pedantic -Wall フラグを付けてコンパイルすると、これも失敗します (ISO C90 もこれを好みません)。ただし、新しいコンパイラは問題ありません。
  3. はい、ポインターへのポインターを使用して 2 次元配列をトラバースできます (実際、char** argv パラメーターmainは一例です)

あなたが読んでいる本はC90規格に準拠しているようです。ただし、gcc の新しいバージョンは問題ありません。

于 2013-06-19T01:41:21.383 に答える
4
  1. VLA (可変長配列) は、C の一部のバージョンに存在します。VLA を使用すると、実行時に割り当てられた変数を使用してスタックに固定長配列を割り当てることができますmalloc()

  2. 以前に使用された配列宣言構文を見たprint()ことがないので、答えられません。通常、括弧を空のままにするか、減衰したポインターを使用します。

  3. はい。

于 2013-06-19T01:39:16.640 に答える
1

C 配列のメモリ割り当ては連続しているため、式a[i][j](init_2d) を (print) に同化できますa[i*y + j]

aas int**(2-dim : N x M) はint*1 次元配列 (size : N*M) と見なすこともできます...

1. 2. および 3. については、他の人の良い回答を参照してください。

See : Row-major order (C-style)なので 2. でいいです。

注 : どうやら、ここで言っていることは、最近のバージョンの C プログラミング言語および/または特定のコンテキストにのみ当てはまり、絶対に一般化することはできません。したがって、他のいくつかの回答を最初に読んでください。

于 2013-06-19T01:45:36.923 に答える
0

1)メインの m と n にはすでに値があるため、配列宣言は問題ありません。数値定数として使用されます。2)配列はポインタです。そう

void foo(int array[]);
void foo(int* array);

はまったく同じです。

配列 int a[2][2] は、int への 2 つのポインターと、それらの 2 つのポインターが 2 つの整数を指す 1 次元配列です。したがって、最初のポインター(あなたの場合はa)を正しく操作すると、これらの整数にアクセスできます。

3) ウィキペディアを参照してください。よりよく理解するための優れたアルゴリズムがあります。

于 2013-06-19T01:43:57.170 に答える