C でポインターと多次元配列をマスターする
C 言語では、配列は連続したメモリ位置に格納される同様の型の値のコレクションです。配列 (1 次元または多次元) 内の各要素は、1 つ以上の一意の整数インデックスによって識別されます。
一方、ポインタは変数のアドレスを格納します。配列の 0 番目の要素のアドレスは配列のポインタです。 「逆参照演算子」を使用すると、ポインターが参照する値にアクセスできます。
C では、1 次元、2 次元、または多次元の配列を宣言できます。「次元」という用語は、コレクション内の要素を識別するために必要なインデックスの数を指します。
ポインタと 1 次元配列
1 次元配列では、各要素は単一の整数によって識別されます。
int a[5] = {1, 2, 3, 4, 5};
ここで、数値「1」は 0 番目のインデックスにあり、「2」はインデックス 1 にあり、以下同様です。
0番目の要素のアドレスを格納する変数はそのポインタです-
int *x = &a[0];
単純に、配列の名前も 0 番目の要素のアドレスを指します。したがって、この式を使用することもできます-
int *x = a;
例
ポインタの値はデータ型のサイズだけ増加するため、「x++」はポインタを配列内の次の要素に移動します。
#include <stdio.h>
int main(){
int arr[] = {1, 2, 3, 4, 5};
int length = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int *ptr = arr;
while (i < length){
printf("arr[%d]: %d \n", i, *(ptr + i));
i++;
}
return 0;
}
出力
このコードを実行すると、次の出力が生成されます-
arr[0]: 1 arr[1]: 2 arr[2]: 3 arr[3]: 4 arr[4]: 5
ポインタと 2 次元配列
1 次元配列が要素のリストに似ている場合、2 次元配列はテーブルまたは行列に似ています。
2D 配列内の要素は、行と列に論理的に配置されていると考えることができます。したがって、要素の位置は 2 つのインデックス、行番号と列番号によって決まります。行インデックスと列インデックスは両方とも「0」から始まります。
int arr[2][2];
このような配列は −
として表されます。表形式の配置は論理的な表現にすぎないことに注意してください。コンパイラは連続バイトのブロックを割り当てます。 C では、配列の割り当ては行優先の方法で行われます。これは、要素が行ごとの方法で配列に読み込まれることを意味します。
ここでは、3 行 4 列の 2D 配列 (最初の角括弧内の数字は常に行数を指します) を −
として宣言します。
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
コンパイラは、上記の 2D 配列に行方向の順序でメモリを割り当てます。配列の最初の要素がアドレス 1000 にあり、「int」型のサイズが 4 バイトであると仮定すると、配列の要素は次の割り当てられたメモリ位置を取得します -
&演算子のアドレスを使用して、配列 num の最初の要素のアドレスをポインタ ptr に割り当てます。
int *ptr = &arr[0][0];
例 1
ポインタが 1 増加すると、次のアドレスに移動します。 「34」配列内の 12 個の要素はすべて、次のようにループでアクセスできます-
#include <stdio.h>
int main(){
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
// pointer ptr pointing at array num
int *ptr = &arr[0][0];
int i, j, k = 0;
// print the elements of the array num via pointer ptr
for (i = 0; i < 3; i++){
for (j = 0; j < 4; j++){
printf("%d ", *(ptr + k));
k++;
}
printf("\n");
}
return 0;
}
出力
このコードを実行すると、次の出力が生成されます-
1 2 3 4 5 6 7 8 9 10 11 12
一般に、配列の任意の要素のアドレスは次の式を使用します-
add of element at ith row and jth col = baseAddress + [(i * no_of_cols + j) * sizeof(array_type)]
34 個の配列では、
add of arr[2][4] = 1000 + (2*4 + 2)*4 = 1044
上の図を参照すると、「arr[3][4]」のアドレスが 1044 であることが確認できます。
例 2
逆参照ポインタを使用して、アドレスの値をフェッチします。この式を使用して、ポインタの助けを借りて配列を走査してみましょう -
#include <stdio.h>
int main(){
// 2d array
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int ROWS = 3, COLS = 4;
int i, j;
// pointer
int *ptr = &arr[0][0];
// print the element of the array via pointer ptr
for (i = 0; i < ROWS; i++){
for (j = 0; j < COLS; j++) {
printf("%4d ",*(ptr + (i * COLS + j)));
}
printf("\n");
}
return 0;
}
出力
このコードを実行すると、次の出力が生成されます-
1 2 3 4 5 6 7 8 9 10 11 12
ポインタと 3 次元配列
3 次元配列は 2 次元配列の配列です。このような配列は 3 つの添え字で宣言されます -
int arr [x] [y] [j];
この配列は、「x」個のテーブルのレイヤーとして考えることができ、各テーブルには「x」個の行と「y」個の列があります。
3D 配列の例は次のとおりです -
int arr[3][3][3] ={
{ {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
{ {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
{ {31, 32, 33}, {34, 35, 36}, {37, 38, 39} },
};
3D 配列へのポインタは次のように宣言できます。
int * ptr = &arr[0][0][0];
配列の名前自体が 0 番目の要素のアドレスであることがわかっているため、3D 配列のポインタを次のように書くことができます。 −
int * ptr = arr;
"x" 行と "y" 列の各層は −
を占めます。x * y * sizeof(data_type)
バイト数。上で宣言したように 3D 配列「arr」に割り当てられたメモリがアドレス 1000 から始まると仮定すると、2 番目の層 (「i =1」) は 1000 + (3 3) 4 =1036 バイト位置から始まります。
ptr = Base address of 3D array arr
JMAX が行数、KMAX が列数の場合、最初のスライスの 0 行目、0 列目の要素のアドレスは -
arr[1][0][0] = ptr + (1 * JMAX * KMAX)
i 番目のスライスの j 行 k 列の要素の値を取得する式は、次のように指定できます。 −
arr[i][j][k] = *(ptr + (i * JMAX*KMAX) + (j*KMAX + k))
例:ポインタ逆参照を使用した 3D 配列の印刷
この式を使用して、ポインターの逆参照を利用して 3D 配列を出力してみましょう −
#include <stdio.h>
int main(){
int i, j, k;
int arr[3][3][3] = {
{ {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
{ {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
{ {31, 32, 33}, {34, 35, 36}, {37, 38, 39} },
};
int JMAX = 3, KMAX = 3;
int *ptr = arr; // &arr[0][0][0];
for(i = 0; i < 3; i++){
for(j = 0; j < 3; j++){
for(k = 0; k < 3; k++){
printf("%d ",*(ptr+(i*JMAX*KMAX)+(j*KMAX+k)));
}
printf("\n");
}
printf("\n");
}
return 0;
}
出力
このコードを実行すると、次の出力が生成されます-
11 12 13 14 15 16 17 18 19 21 22 23 24 25 26 27 28 29 31 32 33 34 35 36 37 38 39
一般に、ポインターを使用して配列にアクセスすることは、添え字表現を使用して配列にアクセスすることと非常に似ています。 2 つの主な違いは、配列の添え字付き宣言では静的にメモリが割り当てられるのに対し、動的なメモリ割り当てにはポインタを使用できることです。
多次元配列を関数に渡すには、添字の代わりにポインターを使用する必要があります。ただし、添字付きの配列を使用する方が、新しい学習者にとっては難しいポインタを使用するよりも便利です。
C言語