4.ポインタと配列

次に配列においてのポインタの使い方を見ていきましょう。
配列は以前学習したように要素を連続したメモリ領域に格納します。つまり、各要素のアドレスも連続していることになります。例えば、配列 wday が1000番地から確保されたとすると次のような感じになります。

 char wday[ ] = "Thursday"; /* 配列の宣言と初期化 */
配列のメモリ上での様子

ここで配列の先頭のアドレスはどうやって表せるでしょうか?
アドレスは「&」を使うと取得できました。配列 wday の先頭の要素は wday[0] です。つまり、配列の先頭のアドレスは &wday[0] となるわけです。実はもう一つ方法があるのです。それは単に配列名をしてするだけ、という方法です。つまり、 wday は配列 wday の先頭のアドレスを指し示しているのです。
よって、下の二つの文は同じことをしていることになります。

 char *pary; /* ポインタの宣言 */

 pary = &wday[0]; /* A.先頭の要素のアドレスを&で指定 */
 pary = wday; /* B.配列名を指定 */

同様に他の要素のアドレスも取得できます。Aのように演算子「&」を使うと次のようになります。

&wday[0] → 添字 0 の要素のアドレス
&wday[1] →添字 1 の要素のアドレス




&wday[8] →添字 8 の要素のアドレス

また、ポインタ pary を使って表すと次のようになります。

pary → ポインタ pary の値 = 添字 0 の要素のアドレス
pary + 1 →ポインタ pary に + 1 した値 = 添字 1 の要素のアドレス




pary + 8 →ポインタ pary に + 8 した値 = 添字 8 の要素のアドレス

演算子「*」をポインタの前に付けるとポインタの参照しているアドレス上にある値が分かりました。配列でもおなじように値を参照できます。

アドレスデータ配列で指定した場合ポインタで指定した場合
1000' T 'wday[0]*( pary )    
1001' h 'wday[1]*( pary + 1 )
1002' u 'wday[2]*( pary + 2 )
1003' r 'wday[3]*( pary + 3 )
1004' s 'wday[4]*( pary + 4 )
1005' d 'wday[5]*( pary + 5 )
1006' a 'wday[6]*( pary + 6 )
1007' y 'wday[7]*( pary + 7 )
1008' \0 'wday[8]*( pary + 8 )

ここで一つ注意しないといけないことがあります。それは上のように ( ) を使う、ということです。なぜ *pary + 1 ではダメなのでしょう?
これは演算子の優先順位が関係しています。ポインタ演算子の * のほうが加算の + よりも優先順位が高いので先に演算されます。つまり *pary + 1 の形だと *pary が先に演算され *paryの値 ' T 'に1を加算するという意味の、 ' T ' + 1(=' U ') となってしまうのです。
( )をつけるとその中が先に演算されるので、pary のアドレスに +1 したアドレスの内容を参照することになるのです。

5.ポインタ演算


最初のあたりでポインタも変数である、という話をしました。つまり普通の変数と同じように加算や減算もできます。このようにポインタに対して加算や減算などの演算を行うことポインタ演算と言います。(そのままですね)
例えば、pary の値をインクリメント(+1すること)してみましょう。これは変数と同じわけだから

pary++

とするだけですね。こうするとポインタ pary の値自体が1増加するので、paryの指し示すアドレスは「1001番地」となるわけです。
ポインタを+1したとき

この状態で前節と同じように参照するには以下の表のようになるわけです。

アドレスデータ配列で指定した場合ポインタで指定した場合
1000' T 'wday[0]*( pary - 1 )
1001' h 'wday[1]*( pary )    
1002' u 'wday[2]*( pary + 1 )
1003' r 'wday[3]*( pary + 2 )
1004' s 'wday[4]*( pary + 3 )
1005' d 'wday[5]*( pary + 4 )
1006' a 'wday[6]*( pary + 5 )
1007' y 'wday[7]*( pary + 6 )
1008' \0 'wday[8]*( pary + 7 )

このポインタ演算、とても便利な特徴を持っています。それは、変数の型を意識しなくてもよい、ということです。上の例は char型の配列で説明しました。char型はバイト幅が1バイトでした。つまり、次の要素へ移動するにはアドレスを1つだけ進めればいい、ということですね。ではこれが int型の配列だったとしましょう(ここでは int型は2バイトとします)。int型配列だとすると最初の要素が1000番地ならば、次の要素は1002番地、というように2バイトステップになります。ポインタを使ったときも2ずつ増やさないといけないのでしょうか?もし、そうならば型を意識してポインタを使わないといけないことになり、とても分かりづらくなってしまうでしょう。
しかし、実際は1づつ増やせばいいのです。これはすべての型にあてはまります。また、後の章で出てくる「構造体」とよばれる配列のようなものにもあてはまります。この特徴のおかげでポインタを配列の添字のような感覚で使うことができます。

アドレスデータ配列で指定した場合ポインタで指定した場合
1000100dat[0]*( pary )    
1001
100235dat[1]*( pary + 1 )
1003
1004-68dat[2]*( pary + 2 )
1005
1006238dat[3]*( pary + 3 )
1007

下のプログラムで型の違いでポインタの増え方が違うことを確認してみましょう。

[ 例 ] ポインタ演算でポインタの増え方の違い  ex9-2.cGet! ソースファイル
short dat1[4], *ps; /* 変数・ポインタの宣言 */
char  dat2[4], *pch;
int   i;

/* 配列を初期化 */
for (i=0; i<4; i++) {
 dat1[i] = 100 + i;
 dat2[i] = i;
}

/* ポインタをそれぞれ配列の先頭にセット */
ps = dat1;
pch = dat2;

for (i=0; i<4; i++) {
 /* ポインタのアドレスと、配列の内容を表示 */
 printf ("ps = %p, pch = %p --- ", ps, pch);
 printf ("dat1[%d] = %d, dat2[%d] = %d\n", i, *ps, i, *pch);

 /* ポインタをそれぞれインクリメント */
 ps++;
 pch++;
}


Jump to Phase #09-1Phase #09-1: ポインタ(1) へ
Phase #09-3: ポインタ(3) へJump to Phase #09-3

△戻る
▲トップに戻る