0

健全性チェックの質問:

私は少しグーグルをして、Cで1次元整数配列を返す正しい方法を発見しました。

int * function(args);
  1. これを行うと、関数はポインターを返しますよね?そして、戻り値がrの場合、r [n]と入力して、配列のn番目の要素を見つけることができますか?

  2. 関数に数値「3」を返させた場合、それはアドレス「3」へのポインタとして解釈されますか?

  3. 私の機能は次のようなものだったとしましょう

    int * function(int * a);
    

    これは法的な機能機関でしょうか?

    int * b;
    b = a;
    return b;
    

    そのような他の配列に配列を割り当てることはできますか?

  4. ポインターと配列が実際に同じものである場合、配列のサイズを指定せずにポインターを宣言することはできますか?のような気がする

    int a[10];
    

    より多くの情報を伝える

    int * a;
    

    しかし、それらは両方とも配列を宣言する方法ではありませんか?後者の宣言を使用する場合、a [10000000]に値を割り当てることはできますか?

主な質問:

  1. Cで2次元配列を返すにはどうすればよいですか?配列の次元がわからないため、配列の先頭へのポインタを返すことはできないと思います。

ご協力ありがとうございます!

4

3 に答える 3

3
  1. はい
  2. はい。ただし、キャストが必要です。return(int *)3;
  3. はい。ただし、配列を別の配列に割り当てているのではなく、ポインターをポインターに割り当てています。
  4. ポインタと配列は同じものではありません。inta[10]は10intのスペースを予約します。int * aは、誰が何を知っているかを示す初期化されていない変数です。[10000000]にアクセスすると、アクセスできないメモリまたは存在しないメモリにアクセスしようとしているため、プログラムがクラッシュする可能性があります。
  5. 2次元配列を返すには、ポインタからポインタを返します。int ** f(){}
于 2012-11-11T04:44:22.567 に答える
2
  1. はい; 配列のインデックス付けは、ポインタ演算の観点から行われます。次のa[i]ように定義され*(a + i)ます。i後の'番目の要素のアドレスを見つけてa、結果を逆参照します。したがってa、ポインタまたは配列として宣言できます。

  2. はい(おそらく無効なアドレス)のアドレスとして解釈されます。タイプとの値には互換性がない 3ため、リテラルをポインタとしてキャストする必要があります。intint *

  3. はい、それは合法です。無意味ですが、合法です。

  4. ポインタと配列は同じものではありません。ほとんどの場合、配列型のはポインタ型の式に変換(「減衰」)され、その値は配列の最初の要素のアドレスになります。ポインタをそれ自体で宣言するだけでは不十分です。メモリのブロック(malloc呼び出しの結果または別の配列)を指すようにポインタを初期化しない限り、その値は不確定であり、有効なメモリを指さない可能性があるためです。

  5. あなたは本当に配列を返したくありません。配列式はポインタ式に変換されるため、最初の要素のアドレスを返すことに注意してください。ただし、関数が終了すると、その配列は存在しなくなり、ポインター値は無効になります。変更する配列を関数の引数として渡すことをお勧めします。

    void foo(int * a、size_t asize){size_t i; for(i = 0; i <asize; i ++)a [i] = some_value(); }

ポインターには、ポインターが指す要素の数に関するメタデータが含まれていないため、それを別のパラメーターとして渡す必要があります。

2D配列の場合、次のようにします。

void foo(size_t rows, size_t columns, int (*a)[columns])
{
   size_t i, j;
   for (i = 0; i < rows; i++)
     for (j = 0; j < columns; j++)
        a[i][j] = some_value;
}

これは、可変長配列をサポートするC99コンパイラまたはC2011コンパイラを使用していることを前提としています。それ以外の場合、列の数は定数式である必要があります(つまり、コンパイル時に既知です)。

于 2012-11-11T05:20:03.440 に答える
0

これらの答えは確かにもう少し深さを要求します。ポインタをよく理解すればするほど、書く悪いコードは少なくなります。

配列とポインタは、同じである場合を除いて、同じではありません。頭のてっぺんから:

int a[2][2] = { 1, 2, 3, 4 }; int (* p)[2] = a; ASSERT (p[1][1] == a[1][1]);

配列「a」は、ポインタ「p」とまったく同じように機能します。そしてコンパイラは、それぞれから、具体的にはアドレスと、インデックス付きアドレスを計算する方法を知っています。ただし、配列aは実行時に新しい値を取ることができないのに対し、pは取ることができることに注意してください。したがって、aの「ポインタ」の側面は、プログラムが実行されるまでになくなり、配列のみが残ります。逆に、p自体は単なるポインタであり、実行時に何でも何も指し示すことができません。

ポインタ宣言の構文は複雑であることに注意してください。(それが、今日私が最初にstackoverflowに来た理由です。)しかし、必要性は単純です。最初の列を超える要素のアドレスを計算する方法をコンパイラーに指示する必要があります。(右端のインデックスには「列」を使用しています。)この場合、インデックス[1] [1]にアドレス((2 * 1)+ 1)をインクリメントする必要があると想定できます。

ただし、コンパイラーが知っていること(願わくば)が他にもいくつかありますが、知らないかもしれません。

コンパイラは2つのことを知っています:1)要素がメモリに順番に格納されているかどうか、2)ポインタの追加の配列があるかどうか、または配列の先頭へのポインタ/アドレスが1つだけあるかどうか。

一般に、コンパイル時の配列は、次元に関係なく、追加のポインターなしで順番に格納されます。ただし、確かに、コンパイラのドキュメントを確認してください。したがって、コンパイラがa [0] [2]のインデックス付けを許可している場合、それは実際にはa [1] [0]などです。ただし、実行時配列は作成します。選択した長さの1次元配列を作成し、それらのアドレスを、選択した長さの他の配列に配置できます。

そしてもちろん、これらのいずれかをいじくりまわす理由の1つは、実行時の乗算、シフト、またはポインターの逆参照を使用して配列にインデックスを付けることを選択しているためです。ポインターの逆参照が最も安価な場合は、ポインターの配列を作成する必要があるため、行アドレスを計算するために算術演算を行う必要はありません。1つの欠点は、追加のポインタを格納するためにメモリが必要になることです。また、列の長さが2の累乗である場合、アドレスは乗算ではなくシフトで計算できることに注意してください。したがって、これは長さを埋める良い理由かもしれません-そしてコンパイラは、少なくとも理論的には、あなたに言わずにこれを行うことができます!また、速度とスペースのどちらの最適化を選択するかによって異なる場合があります。

「モダン」および「パワフル」と表現されているアーキテクチャは、おそらく間接参照と同じ速さで乗算され、コードが正しいかどうかを除いて、これらの問題は完全に解消されます。

于 2019-01-11T22:53:54.777 に答える