多次元配列とポインターをいじっています。私は、単純な配列の内容とアドレスを出力するプログラムを見てきました。これが私の配列宣言です:
int zippo[4][2] = { {2,4},
{6,8},
{1,3},
{5,7} };
私の現在の理解では、zippo
はポインターであり、他のいくつかのポインターのアドレスを保持できます。デフォルトでは、zippo
はポインタzippo[0]
のアドレスを保持し、ポインタzippo[1]
、zippo[2]
、およびzippo[3]
のアドレスも保持できます。
ここで、次のステートメントを考えます。
printf("zippo[0] = %p\n", zippo[0]);
printf(" *zippo = %p\n", *zippo);
printf(" zippo = %p\n", zippo);
私のマシンでは、次の出力が得られます。
zippo[0] = 0x7fff170e2230
*zippo = 0x7fff170e2230
zippo = 0x7fff170e2230
zippo[0]
と*zippo
の値が同じである理由を完全に理解しています。どちらもポインタであり、どちらも整数2のアドレス(デフォルト)またはzippo[0][0]
を格納します。しかし、同じメモリアドレスを共有しているzippo
はどうなっているのでしょうか。 zippo
はポインタのアドレスを格納するべきではありませんzippo[0]
?えっ?
多次元配列を宣言すると、コンパイラーはそれを1次元配列として扱います。多次元配列は、私たちの生活を容易にするための単なる抽象化です。誤解があります。これは4つの配列を指す1つの配列ではなく、常に1つの連続したメモリブロックにすぎません。
あなたの場合、次のようにします:
int zippo[4][2]
することと本当に同じです
int zippo[8]
2Dアドレッシングに必要な数学は、コンパイラーによって処理されます。
詳細については、このC++の 配列に関するチュートリアル を参照してください。
これは次のこととは大きく異なります。
int** zippo
または
int* zippo[4]
この場合、他の配列に割り当てることができる4つのポインターの配列を作成しています。
ほとんどのコンテキストで配列式が現れると、その型は暗黙的に「TのN要素配列」から「Tへのポインター」に変換され、その値は配列の最初の要素を指すように設定されます。この規則の例外は、配列式がsizeof
またはアドレスの(_&
_)演算子のオペランドである場合、または配列が文字列リテラルであり、文字列リテラルが宣言。
したがって、式zippo
は、タイプ_int [4][2]
_(intの2要素配列の4要素配列)からint (*)[2]
(intの2要素配列へのポインター)から「減衰」します。 )。同様に、_zippo[0]
_の型は_int [2]
_であり、暗黙的に_int *
_に変換されます。
宣言_int zippo[4][2]
_が与えられた場合、次の表は、zippoおよび暗黙的な変換を含むさまざまな配列式のタイプを示しています。
暗黙的に等価な式に変換された式のタイプ ---------- ---- ------------------- ---- --------------------- zippo int [4] [2] int(*)[2] &zippo int(*)[4] [2] * zippo int [2] int * zippo [0] zippo [i] int [2] int * &zippo [ i] int(*)[2] * zippo [i] int zippo [i] [0] zippo [i] [j] int &zippo [i] [ j] int * * zippo [i] [j] invalid
zippo
、_&zippo
_、_*zippo
_、_zippo[0]
_、_&zippo[0]
_、_&zippo[0][0]
_はすべて同じ値です。それらはすべて配列のベースを指します(配列のアドレスは配列の最初の要素のアドレスと同じです)。ただし、さまざまな式のタイプはすべて異なります。
zippo
はポインターではありません。これは配列値の配列です。 zippo
、および_zippo[i]
_ for i
for 0..4は、特定の場合(特に、値のコンテキスト)で、ポインターへの「減衰」が可能です。値以外のコンテキストでのzippo
の使用例については、_sizeof zippo
_を印刷してみてください。この場合、sizeof
は、ポインターのサイズではなく、配列のサイズを報告します。
配列の名前値のコンテキストは、最初の要素へのポインタに減衰します。したがって、値のコンテキストでは、zippo
は_&zippo[0]
_と同じであるため、「ポインター[int
の配列[2]へのポインター」」というタイプになります。値コンテキストの_*zippo
_は_&zippo[0][0]
_と同じです。つまり、「int
へのポインタ」です。値は同じですが、タイプが異なります。
2番目の質問への回答については、 配列とポインタ をお勧めします。ポインターは同じ「値」を持っていますが、異なる量のスペースを指しています。 _zippo+1
_と_*zippo+1
_を印刷して、より明確に確認してください。
_#include <stdio.h>
int main(void)
{
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
printf("%lu\n", (unsigned long) (sizeof zippo));
printf("%p\n", (void *)(zippo+1));
printf("%p\n", (void *)(*zippo+1));
return 0;
}
_
私の実行では、それは出力します:
_32
0xbffede7c
0xbffede78
_
私のマシンのsizeof(int)
は4であり、2番目と3番目のポインターの値が(期待どおり)等しくないことを教えてください。
また、_"%p"
_書式指定子には*printf()
関数で_void *
_が必要なので、printf()
呼び出しで_void *
_へのポインターをキャストする必要があります(printf()
は可変個の関数なので、コンパイラーはここで自動変換を行うことができません)。
編集:配列がポインタに「減衰」すると、値のコンテキストでの配列の名前はポインタと同じになります。したがって、一部の型T
に_T pt[100];
_がある場合、名前pt
は値のコンテキストで_T *
_型になります。 sizeof
演算子と単項_&
_演算子の場合、pt
という名前はポインターに還元されません。しかし、あなたは_T *p = pt;
_を実行できます。これは、このコンテキストではpt
が_T *
_型であるため、完全に有効です。
この「減衰」は1回だけ発生することに注意してください。それで、私たちが持っているとしましょう:
_int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
_
次に、値のコンテキスト内のzippo
は、タイプのポインターに減衰します:int
のarray [2]へのポインター。コードで:
_int (*p1)[2] = zippo;
_
は有効ですが、
_int **p2 = zippo;
_
「互換性のないポインタの割り当て」の警告が表示されます。
zippo
を上記のように定義すると、
_int (*p0)[4][2] = &zippo;
int (*p1)[2] = zippo;
int *p2 = zippo[0];
_
すべて有効です。 printf("%p\n", (void *)name);
を使用して印刷すると、同じ値を印刷する必要がありますが、ポインターは、行列全体、行、および単一の整数をそれぞれ指すという点で異なります。
ここで重要なことは、int zippy[4][2]
はint **zippo
と同じタイプのオブジェクトではないということです。
int zippi[5]
と同様に、zippy
はメモリブロックのアドレスです。 Butコンパイラは、2次元構文でzippy
で始まる8つのメモリ位置をアドレス指定したいが、zippi
で始まる5つのメモリ位置をアドレス指定したいことを知っています。 1次元構文で。
zippo
はまったく別のものです。 Itは、2つのポインタを格納するのに十分な大きさのメモリブロックのアドレスを保持します。themを整数の配列に指定すると、2次元でそれらを逆参照できます配列アクセス構文。
リードは非常によく説明しましたが、簡単にするためにもう少しポイントを追加します。zippo
またはzippo[0]
またはzippo[0][0]
を参照するときは、配列zippo
。配列である理由は、常にメモリの連続したブロックであり、多次元配列は、連続的に配置された複数の単一次元配列です。
行ごとにインクリメントする必要がある場合は、ポインタint *p = &zippo[0][0]
が必要です。p++
を実行すると、ポインタが行ごとにインクリメントされます。あなたの例のidでは、4 X 2の配列です。p++
を実行すると、ポインターは現在、4つの要素の2番目のセットを指しています。