内 容
- 2次元配列
- 2次元配列とポインタ
- 関数への2次元配列の受け渡し
2次元配列
2次元配列を宣言する
1次元配列を一つの行として、複数の行からなる配列を2次元配列と言います。
2次元配列の宣言
要素のデータ型 配列名[行要素数][列要素数];
2次元配列宣言の例)
int a[2][3];
2次元配列の初期化
要素のデータ型 配列名[行要素数][列要素数] = {{値,値,・・・},{値,値,・・・},・・・};
2次元配列の初期化の例)
int a[2][3] = {{1,2,3},{4,5,6}};
2次元配列の初期化と各要素の参照の例)
array2d.c
#include <stdio.h>
int main(void)
{
int a[2][3] = {{1,2,3},{4,5,6}}; //2次元配列の初期化
int i,j;
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("a[%d][%d] = %2d\n",i,j,a[i][j]);
}
}
return 0;
}
演習
- 次は、2行3列からなるint型の行列maとmbの各要素の値を入力して、その和を行列mcに格納した結果を表示するプログラムです。空欄を埋めてプログラムを完成させなさい。
array2add.c
#include <stdio.h>
int main(void)
{
int [[空欄ア]];
int i,j;
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("[[空欄イ]] = ",i,j);
scanf("%d",[[空欄ウ]]);
}
}
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("[[空欄エ]] = ",i,j);
scanf("%d",[[空欄オ]]);
}
}
for(i=0; i<2; i++){
for(j=0; j<3; j++){
mc[i][j] = [[空欄カ]];
}
}
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("%4d",[[空欄キ]]);
}
printf("\n");
}
return 0;
}
実行結果の例
ma[0][0] = 1
ma[0][1] = 2
ma[0][2] = 3
ma[1][0] = 4
ma[1][1] = 5
ma[1][2] = 6
mb[0][0] = 7
mb[0][1] = 8
mb[0][2] = 9
mb[1][0] = 10
mb[1][1] = 11
mb[1][2] = 12
8 10 12
14 16 18
二次元配列とポインタ
二次元配列のメモリ配置
二次元配列はメモリにどのように配置されるのでしょうか。例えば、int型の2行3列の配列aは下図のように0行目の要素を連続で配置し、続けて1行目の要素を連続して配置します。2次元の配列もメモリには1次元の構造として格納されるわけです。

二次元配列の要素のアドレス
&配列名[添字][添字]
二次元配列は配列の配列
C言語には厳密な意味で二次元配列(多次元配列)は存在しません。2行3列の配列の場合なら下図のように、二次元配列は0行目と1行目の2つの1次元配列を2つポインタからなる配列で指し示す構造をしています。さらに、配列名は二次元配列を示すポインタであり、開始アドレス示します。

二次元配列を示すポインタ(二次元配列の開始アドレス)
配列名
配列とポインタ
配列のポインタ
ポインタとしての配列名aの値を+1すると、そのアドレスは1行のあたりの要素数分だけ先に進みます。例えば、int型で2行3列の配列ならば1行が3つの要素からなっているので、4バイト(int型)×要素3個分=12バイト分だけ先に進みます。仮に配列の開始アドレスaが1000番地ならばa+1は100C番地となります。
配列を指し示すためのポインタを利用する場合は、その示す先の配列のサイズに合わせて用意しなければなりません。以下のコード例では配列aを指し示すためのポインタが、int型で要素3個分のサイズを持つように、
int (*p)[3]と宣言されています。
int a[2][3] = {{11,12,13},{21,22,23}};
int (*p)[3];
p = a;
配列を指し示すポインタの宣言
型 (*ポインタ名)[1行当たりの要素数]
各行の開始アドレス
列部分の添字がない配列の要素にアドレス演算子&を適用したものは、その配列の行を指し示すポインタになります。すわなち行の開始アドレスを示します。例えば、先の例では配列名aと0行目の開始アドレス&a[0]は同じアドレスを示すことになります。
二次元配列の各行のポインタ
&配列名[添字]
ポインタの配列
一つのポインタを一つの要素として複数の要素からなる配列として扱うこともできます。
ポインタの配列の宣言
型 *配列名[要素数]
ダブルポインタ
ポインタを指し示すポインタをダブルポインタと言います。
ダブルポインタの宣言
型 **配列名
プログラミング実験
- [実験1] 配列のメモリアドレスを表示する
次のプログラムを入力、コンパイル、実行して配列の各要素の先頭アドレスを調べて、メモリ配置図を描きなさい。
apointer1.c
#include <stdio.h>
int main(void)
{
int a[2][3] = {{1,2,3},{4,5,6}};
int i,j;
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("a[%d][%d] = %2d (%p)\n",i,j,a[i][j], &a[i][j]);
}
}
return 0;
}
- [実験2] 配列名が配列の開始アドレスであり配列を示すポインタであることを確認する
二次元配列の配列名が配列の開始アドレスを示していること、配列名に+1することで次の行の先頭アドレスを示すこと、すなわちポインタであることを次のプログラムを入力(追加)、コンパイル、実行することで確認しなさい。先のメモリ配置図に結果をわかりやすく追加記入しなさい。
apointer2.c
#include <stdio.h>
int main(void)
{
int a[2][3] = {{1,2,3},{4,5,6}};
int i,j;
printf("配列名a -> (%p)\n",a); //配列の開始アドレス(0行目の先頭アドレス)
printf(" a+1 -> (%p)\n",a+1); //1行目の先頭アドレス
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("a[%d][%d] = %2d (%p)\n",i,j,a[i][j], &a[i][j]);
}
}
return 0;
}
配列名は配列の開始アドレスを示すポインタです。このポインタを+1するとその値が次の行の先頭アドレスを示すことがわかります。このことから、配列名は配列の開始アドレスを示すと同時に0行目の先頭アドレスを示していることがわかります。さらに、このポインタを+1すると4バイト×3要素分=12バイト分だけアドレスが増えることから、このポインタ(配列名)のサイズが1行当たりの要素数分(列数分)のサイズを持つことがわかります。
- [実験3] 行の先頭アドレス
二次元配列の要素の列を示す添字を省略しアドレス演算子を適用した&a[0]、&a[1]がそれぞれ0行目、1行目の先頭アドレスを示すことを、次のプログラムを入力、コンパイル、実行して確認しなさい。その結果を先のメモリ配置図にわかりやすく追加記入しなさい。
apointer3.c
#include <stdio.h>
int main(void)
{
int a[2][3] = {{1,2,3},{4,5,6}};
int i,j;
printf("配列名a -> (%p)\n",a); //配列の開始アドレス(0行目の先頭アドレス)
printf(" a+1 -> (%p)\n",a+1); //1行目の先頭アドレス
for(i=0; i<2; i++){
printf("%d行目の先頭アドレス %p :\n", i,&a[i]);
for(j=0; j<3; j++){
printf("a[%d][%d] = %2d (%p)\n",i,j,a[i][j], &a[i][j]);
}
}
return 0;
}
- [実験4] ポインタ変数の型と配列へのポインタ
int型へのポインタpに配列の開始アドレスaを代入し、このポインタを使って要素を参照するプログラムです。しかし、入力(追加・修正)し、コンパイルすると警告(Warning)が発生します。なぜ、警告が出されたのかコンパイラのWarningを参考に考察しなさい。
apointer4.c
#include <stdio.h>
int main(void)
{
int a[2][3] = {{1,2,3},{4,5,6}};
int *p; //ポインタ
int i,j;
printf("配列名a -> (%p)\n",a); //配列の開始アドレス(0行目の先頭アドレス)
printf(" a+1 -> (%p)\n",a+1); //1行目の先頭アドレス
p = a; //ポインタに配列の開始アドレスを代入
for(i=0; i<2; i++){
printf("%d行目の先頭アドレス %p :\n", i,&a[i]);
for(j=0; j<3; j++){
printf("a[%d][%d] = %2d (%p)\n", i, j, *(p+i*3+j), p+i*3+j);
}
}
return 0;
}
ポインタpは int *型(4バイト)で宣言されています。一方、配列名であるポインタaは行の先頭を示すポインタであり、12バイトのサイズを持つ int (*)[3]型でした。そのため、コンパイラは型が異なるとのWarningを発したわけです。
では、この場合どのように修正したらよいでしょうか。各要素の参照を *(p+i*3+j)、先頭アドレスから1バイトずつ増加するようにして実現しています。これに合わせて、ポインタpは int *型のままにして、配列aの先頭要素のアドレス&a[0][0]を代入することにします。
p = a ---修正---> p=&a[0][0]
- [実験5] 配列の要素へのポインタ
ポインタを int (*)[3]型に変更した場合の配列の参照方法について確認しましょう。配列へのポインタpは要素3個分の大きさなので、*(p+1)は1行目の先頭の要素、配列の先頭からは4つ目の要素を示すことになります。では、ポインタpを使って配列の先頭から2つ目の要素を示すためにはどうしたらよいでしょうか?
apointer5.c
#include <stdio.h>
int main(void)
{
int a[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3]; //サイズがint型3個分のポインタ
int i,j;
printf("配列名a -> (%p)\n",a); //配列の開始アドレス(0行目の先頭アドレス)
printf(" a+1 -> (%p)\n",a+1); //1行目の先頭アドレス
p = a; //ポインタに配列の開始アドレスを代入
for(i=0; i<2; i++){
printf("%d行目の先頭アドレス %p :\n", i, p+i);
for(j=0; j<3; j++){
printf("a[%d][%d] = %2d (%p)\n", i, j, *(*(p+i)+j), *(p+i)+j);
}
}
return 0;
}
ポインタpの実体である*pはポインタpが示すアドレスになり、その型はint型(4バイト)になります。
- [実験6]ポインタの配列
ポインタを要素とする配列を使って、2つの配列を扱う方法を確認しましょう。次の例では、2個のint型ポインタからなる配列*p[]を使って、それぞれの要素に、一次元配列aとbを指し示すように初期化しています。こうすることで、ポインタpを使って2つの配列をあたかも2次元配列のように扱うことができます。
apointer6.c
#include <stdio.h>
int main(void)
{
int a[3] = {11,12,13};
int b[3] = {21,22,23};
int *p[2] = {a,b}; //int型ポインタ2個からなる配列を初期化
int i,j;
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("p[%d][%d] = %2d (%p)\n",i,j,p[i][j],&p[i][j]);
//printf("p[%d][%d] = %2d (%p)\n",i,j,*(p[i]+j),p[i]+j) //上記と同じ
//printf("p[%d][%d] = %2d (%p)\n",i,j,*(*(p+i)+j),*(p+i)+j); //上記と同じ
}
}
return 0;
}
- [実験7]ダブルポインタはポインタへのポインタ
ダブルポインタはポインタ名の前に2つのアスタリスク*を付けて宣言する変数です。ダブルポインタはポインタを指し示す役割を担うことができます。すなわち、ポインタが格納されているアドレスを格納するための変数です。次のプログラムを入力、コンパイル、実行して、ダブルポインタの使い方の確認をしなさい。また、結果を簡単な図を使って示しなさい。
dpointer.c
#include <stdio.h>
int main(void)
{
int a;
int *p;
int **pp;
a = 1000;
p = &a;
pp = &p;
printf(" a = %d (%p)\n", a,&a);
printf(" p-> %d (%p)(%p)\n", *p,p,&p);
printf("pp-> %d (%p)(%p)\n", **pp,pp,&pp);
return 0;
}
まとめ
変数宣言とその意味
int p | int型サイズの変数 |
int *p | int型サイズの変数を指し示すポインタ変数 |
int **p | int型サイズの変数を指し示すポインタ変数へのポインタ変数 |
int p[N] | int型サイズの変数をN個持つ配列 |
int *p[N] | int型サイズの変数を指し示すポインタ変数をN個持つ配列 |
int (*p)[N] | int型サイズN個分の要素からなる配列を指し示すポインタ変数 |
Nは任意の自然数
添字演算子のポインタへの読み替え
配列の行と列の位置を示すために使われる角括弧を添字演算子と言います。但し、配列の宣言の角括弧とは異なります。この添字演算子は以下の例のようにポインタを含む形式に変換することができます。
a[2] -> *(a+2)
a[2][3] -> (*(a+2))[3] -> *((*(a+2))+3) -(カッコの省略)--> *(*(a+2) + 3)
関数への二次元配列の受け渡し
関数が引数を使って二次元配列を受け渡しする際の引数の記述方法を見てみましょう。int型の2行3列の二次元配列を例にその記述方法を示します。1)と2)記述方法は異なりますが、いずれも(int *)[3]型で、配列の先頭アドレスと1行のサイズの情報を引き渡すことができます。3)は(int *)型で、配列の先頭アドレスはわかりますが1行のサイズを引き渡すことができません。呼び出し側は引数の型に合わせて、実引数を設定しなければならない点に注意が必要です。
1)2次元配列の開始アドレスを受け取る。列数がわかるので、
void func(int a[2][3])
{
・・・
}
行数を省略した記述も可能です。1行のサイズが
void func(int a[][3])
{
・・・
}
3) 2次元配列の開始アドレスを受け取る
void func(int (*a)[3])
{
・・・
}
4) 2次元配列の先頭要素アドレスを受け取る(但し、行サイズがわからない)
void func(int *a, int row, int col)
{
・・・
}
演習
- 次のプログラムは2行3列の配列の各要素を2倍した値を別の配列に格納した結果を表示するプログラムです。空欄を埋めて完成させなさい。この際、空欄には複数の記述方法が当てはまることを確認しなさい。関数twiceは、2行3列の配列aを受け取って、その各要素を2倍した結果を配列bに格納する関数です。
twice.c
#include <stdio.h>
void twice([[空欄ア]], [[空欄イ]]);
void print_array(int a[][3]);
int main(void)
{
int a[2][3] = {{0,1,2},{3,-1,5}};
int b[2][3];
twice(a,b);
print_array(a);
print_array(b);
return 0;
}
void twice([[空欄ア]], [[空欄イ]])
{
int i,j;
for(i=0; i<2; i++){
for(j=0; j<3; j++){
b[i][j] = a[i][j] * 2;
}
}
}
void print_array(int a[][3])
{
int i,j;
puts("\n------------");
for(i=0; i<2; i++){
for(j=0; j<3; j++){
printf("%3d",a[i][j]);
}
printf("\n");
}
puts("------------");
}
expand_lessBack to TOP