2.5
2次元配列とポインタ (1999/03/28初版)
2.5.1 2次元配列をポインタ形式で扱う
2次元配列を考えます。はじめに調べたように2次元配列は配列の配列、つまり行ベク
トル(横ベクトル)を要素にした配列でした。4×5の整数型2次元配列の場合、
int a[4][5];
では、各要素はそれぞれ要素数5(列の数)の配列 a[0], a[1]. a[2],a[3]となります。
メモリ配置を図に描くと次のようになります。
添字が1つだけの a[i]は配列名を意味します(惑わされないでください)。従って各行
の先頭アドレスです。すると、配列を次のようにポインタのように書き表すことができま
す。
a[i][j] は *( a[i] + j ) と同じ。
要素a[i][j]は、その行の先頭アドレスa[i]からjだけ後ろの位置にあります。そしてア
ドレス (a[i] + j)の内容は間接参照演算、*(a[i]+j)で参照できるからです。さらに配
列a[i]は、*( a + i )と書けるので、次のような等号(同一性)が成り立ちます。
a[i][j] == *( a[i] + j ) == *( *(a+i) + j ),
以上をプログラムで確かめましょう。
// matrix03.cpp
// 4×5行列を2次元配列で表す
// 配列要素をポインタ形式に扱う
#include <iostream.h>
int main()
{
const int ROW = 4; //行の次元
const int COL = 5; //列の次元
int mat[][COL] = { {11,12,13,14,15},
{21,22,23,24,25},
{31,32,33,34,35},
{41,42,43,44,45} };
cout << "\n行列の表示(1)\n";
int i,j;
for(i = 0; i < ROW; i++){
for(j = 0; j < COL; j++)
cout << " " << *( mat[i] + j); // mat[i][j]と同じ
cout << endl; //行の終わりに改行
}
cout << "\n行列の表示(2)\n";
for(i = 0; i < ROW; i++){
for(j = 0; j < COL; j++)
cout << " " << *( *( mat + i ) + j); // mat[i][j]と同じ
cout << endl; //行の終わりに改行
}
return 0;
}
|
ソースファイル matrix03.cpp
[実行結果]
行列の表示(1)
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
行列の表示(2)
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45 |
2.5.2 2次元配列を指すポインタ(配列へのポインタ)
この2次元配列を指すポインタはどのような形になるでしょうか。このテーマはポイン
タの学習で、もっとも難しくて悩ましい部類に入ります。でもそれを理解するための準備
は整っています。ここで何をやりたいのかを明確にしておきましょう。あるポインタ ptr
が2次元配列を指しているときに、これを2次元配列とまったく同じように使いたい。配
列の要素をptr[i][j]という風に使うためには、ptrをどのように定義すればよいのかとい
うことです。
いくつかの方法があります。その1つは、配列全体を指すポインタを定義することです。
それは次のように宣言します。
int (*ptr)[5];
これは、ptrが“要素数が5の整数型の配列”へのポインタであることを意味します。ポ
インタptrは宣言(定義)で指定した大きさ(ここでは5)の配列全体を指します。これ
を「配列へのポインタ」と呼びます。
この宣言で、括弧()は必要です。()がないとこれは別物の「ポインタの配列」(す
ぐ後で現れます)になります。
配列へのポインタを使うと2次元配列が扱えます。
const int ROW = 4; //行の次元
const int COL = 5; //列の次元
int a[ROW][COL] = { {11,12,13,14,15},
{21,22,23,24,25},
{31,32,33,34,35},
{41,42,43,44,45} };
で定義された2次元配列に対して、行ベクトル(横ベクトル)を指すポインタを次のよう
に定義します。
int (*ptr)[COL]; // 配列へのポインタ
ptr = a; // 行列の最初の行ベクトル(配列)を指す
ここで、ptrの値は2次元配列の先頭つまり要素数5の整数配列a[0]の先頭アドレスで、
a[0]全体を指しています。図式的に書くと次のようになります。
ptr --> { 11, 12, 13, 14, 15 }
このポインタの型は 'int (*)[5]'型です。
ポインタ演算 ptr + 1 は(int型のサイズ)×5=4×5=20バイト後ろのアドレス
値で、( ptr + 1 )が指しているのは2番目の配列{21, 22, 23, 24, 25 }全体ですか
ら、*( ptr + 1 )は ptr[1]と同じで、それは配列a[1]を表します。
さて、ptr[1]は2行目の横並びの配列を意味します。ptr[1] + 3は行の先頭アドレス
ptr[1]からint型の要素3個分先のアドレスになるので、値24が格納されている要素の
アドレスとなります。
別の言い方をすると、ptr[1]は要素数5の整数型の配列です。従って、配列の要素は添
字演算ptr[1][3]で参照できます。これは、a[1][3]です。もしこれが理解しづらければ、
配列ptr[1]を別のシンボルXと置き換えて考えるとよいです。Xは配列で、その4番目の
要素はX[3]、これを元のシンボルに置き戻すと ptr[1][3]ということになります。
従って、*( ptr + i )はptr[i]と同じで、さらに配列a[i]と同じです。同様に要素
a[i][j]は *(ptr[i] + j)、および ptr[i][j]と同一です。
このように2次元配列を「配列へのポインタ」で指すことで、ポインタを元の配列と同
じように添字操作することができます。その仕組みは、ポインタは2次元配列を行単位で
指しているので、ポインタは各行の区切り場所の情報を持っているからです。1次元配列
(ベクトル)と2次元配列(行列)の対応をまとめると、
[1次元配列(ベクトル)]
int vec[7];
は “int型”の配列である。
これに対応するポインタは、“int型”へのポインタ
int *pV = vec;
であって、pV[i]でベクトルの要素を添字操作できる。
(ポインタpVはベクトルの要素がint型であることを知っているが、
ベクトルのサイズ7は知らない)
[2次元配列(行列)]
int mat[5][7];
は “int型の要素数が7の配列”の配列である。
これに対応するポインタは“int型の要素数が7の配列”へのポインタ
int (*pM)[7] = mat;
であって、pM[i][j]で行列の要素を添字操作できる。
(ポインタpMは行列の要素が行ベクトル(横ベクトル)であることを知っているが、
行列の行のサイズ5は知らない)
となります。この意味で、配列へのポインタは2次元配列と素直に対応します。
2.5.3 2次元配列を指すポインタ(ポインタの配列)
もう1つのアイデアは、「ポインタの配列」を使う方法です。2次元配列をポインタで
添字操作するためには、行の区切りの情報が必要です。そのために各行の先頭を指すポイ
ンタ変数を行の数だけ用意して、この集合を配列として扱います。つまりポインタの配列
を考えます。
const int ROW = 4; //行の次元
const int COL = 5; //列の次元
int a[ROW][COL] = { {11,12,13,14,15},
{21,22,23,24,25},
{31,32,33,34,35},
{41,42,43,44,45} };
の2次元配列に対して、ポインタの配列を次のように宣言します。
int *ptr[ROW]; // ポインタは行の個数分ある
これはポインタが配列になっていて、その要素 ptr[0], ptr[1], ptr[2], ptr[3]はint
型へのポインタです。つまり各要素 ptr[i]の型は (int *)型のポインタです。
2次元配列の各行の先頭要素のアドレスをセットすることで2次元配列を指すことがで
きます。
for(int i = 0; i < ROW; i++)
ptr[i] = a[i]; //各行(1次元配列)の先頭アドレスをセット
ptr[i]はa[i]の先頭要素a[i][0]のアドレスを指しています。つまり指しているのはint
型のデータです。
たとえばptr[2]は2次元配列の3行目の先頭アドレスですから、ptr[2]+3 は3行目の
先頭アドレスからint型の要素3個分先のアドレスになるので、*( ptr[2] + 3 ) は
ptr[2][3]と書けます。これは a[2][3]と同じ中身の値34です。従って、ptr[i][j]と
a[i][j]は同一です。
前の「配列へのポインタ」と、ここでの「ポインタの配列」との違いに注意しましょう。
配列へのポインタの定義は、
型名 (*ポインタ名)[ 要素数(列数) ];
で()が必要です。一方ポインタの配列は、
型名 *ポインタ名 [ 要素数(行数) ];
となります。「配列へのポインタ」は配列全体を指す変数で、メモリ内にはアドレスを記
憶する領域が1つだけ確保されます。「ポインタの配列」は、ポインタ配列の要素数(こ
の場合は行の数)分の領域が確保されます。
もっとも大きな違いは、「配列へのポインタ」が2次元配列を指すとき列の数は固定さ
れていますが、「ポインタの配列」は列の数(行ベクトルのサイズ)が固定されている必
要がありません。次のように不揃いな要素(行ベクトル)を指すことができます。
int *ptr[3]; // ポインタ配列
ptr[0] ---> {1, 2, 3, 4, 5, 6}
ptr[1] ---> {7 }
ptr[2] ---> {8, 9 }
これは、文字列の処理で使われます。
次のプログラムは、「配列へのポインタ」と「ポインタの配列」の使い方を示していま
す。
// matrix04.cpp
//4×5行列を2元配列で表す
// 行列へのポインタ
#include <iostream.h>
int main()
{
const int ROW = 4; //行の次元
const int COL = 5; //列の次元
int mat[][COL] = { {11,12,13,14,15},
{21,22,23,24,25},
{31,32,33,34,35},
{41,42,43,44,45} };
cout << "もとの行列 mat[4][5]:2元配列(配列の配列)\n";
cout << "サイズ: " << sizeof(mat) << endl;
int i,j;
for(i = 0; i < ROW; i++){
for(j = 0; j < COL; j++)
cout << " " << mat[i][j];
cout << endl; //行の終わりに改行
}
// 配列へのポインタ
int (*ptr1)[COL];
ptr1 = &mat[0]; //行列の先頭アドレスで初期化
cout << "\nポインタ (*ptr1)[5]:配列へのポインタ\n";
cout << "ポインタのサイズ: sizeof(ptr1) = "
<< sizeof(ptr1) << endl;
cout << "ポインタが指しているサイズ: sizeof(*ptr1) = "
<< sizeof(*ptr1) << endl;
for(i = 0; i < ROW; i++){
for(j = 0; j < COL; j++)
cout << " " << ptr1[i][j];
cout << endl; //行の終わりに改行
}
// ポインタ変数の配列
int *ptr2[ROW];
for(i = 0; i < ROW; i++)
ptr2[i] = &mat[i][0]; //行の先頭アドレスで初期化
cout << "\nポインタ *ptr2[4]:ポインタ変数の配列\n";
cout << "配列ポインタのサイズ: sizeof(ptr2) = "
<< sizeof(ptr2) << endl;
cout << "要素ポインタのサイズ: sizeof(ptr2[0]) = "
<< sizeof(ptr2[0]) << endl;
cout << "要素ポインタが指しているサイズ: sizeof(*ptr2[0]) = "
<< sizeof(*ptr2[0]) << endl;
for(i = 0; i < ROW; i++){
for(j = 0; j < COL; j++)
cout << " " << ptr2[i][j];
cout << endl; //行の終わりに改行
}
return 0;
}
|
ソースファイル matrix04.cpp
[実行結果]
もとの行列 mat[4][5]:2元配列(配列の配列)
サイズ: 80
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
ポインタ (*ptr1)[5]:配列へのポインタ
ポインタのサイズ: sizeof(ptr1) = 4
ポインタが指しているサイズ: sizeof(*ptr1) = 20
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
ポインタ *ptr2[4]:ポインタ変数の配列
配列ポインタのサイズ: sizeof(ptr2) = 16
要素ポインタのサイズ: sizeof(ptr2[0]) = 4
要素ポインタが指しているサイズ: sizeof(*ptr2[0]) = 4
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45 |
繰り返しになりますが、それぞれのポインタのサイズ(メモリ領域の大きさ)と、ポイン
タが指しているサイズ(データのサイズ)の違いに注意しましょう。
【演習問題 2.6】上のプログラムを、double型のデータ型に書き換えなさい。
ポインタのサイズはいくらになるか考え、実行結果を確かめなさい。
【演習問題 2.7】上のプログラムで、配列へのポインタ ptr1を次のように初期化すると
エラーになります。理由を考えなさい。
int (*ptr1)[COL] = &mat[0][0]; // 先頭要素のアドレスで初期化
2.5.4 補足:2次元配列を(int *)型のポインタで指す
整数型の2次元配列は、メモリ内でリニアに並んでいるわけですから、これを1次元配
列と見なして(int *)型のポインタで指すこともできるはずです。次のプログラムを見て
ください。
// lst02_06.cpp
//4×5行列を2元配列で表す
// (int *)型のポインタを使う
#include <iostream.h>
int main()
{
const int ROW = 4; //行の次元
const int COL = 5; //列の次元
int mat[][COL] = { {11,12,13,14,15},
{21,22,23,24,25},
{31,32,33,34,35},
{41,42,43,44,45} };
int *ptr;
ptr = &mat[0][0]; //先頭要素のアドレスで初期化
for(int i = 0; i < ROW; i++){
for(int j = 0; j < COL; j++)
cout << " " << ptr[ i * COL + j];
cout << endl; //行の終わりに改行
}
return 0;
} |
ソースファイル lst02_06.cpp
[実行結果]
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45 |
ポインタは整数型へのポインタで、行の区切りに関する情報を持ちません。そのために
各要素を参照するためには、
ptr[ i * COL + j ] または、*( ptr + i * COL + j )
と、行と列の位置を計算して与える必要があります。あまり賢い方法ではありません。
| 目次 | 前のページ
| 次のページ |
Copyright(c) 1999 Yamada,K