第24回
データ構造(3)~ポインタの基本

ポインタと配列

ポインタと配列は密接な関係にあります。配列の各要素にアクセスする場合、ポインタを使うと非常に効率的です。

配列をポインタで扱う

先に掲げた配列の初期化処理をポインタを使って書くと以下のようになります。
int i; -------------------------- カウンタ変数を宣言
unsigned short int *np; --------- ポインタを宣言
np = num; -------- (1) 配列numの先頭アドレスを代入(ポインタの初期化)
for (i=0; i<6; i++) {
  *np = 0; ------- (2) 現在示している配列の要素に「0」を代入
  np++; ---------- (3) ポインタの値を1増加

(1)でポインタnpに配列numの先頭アドレス(配列名)を代入して、ポインタを初期化しています。この段階で、ポインタnpは配列numの先頭アドレス=先頭の要素を示しています。

(2)でポインタnpの示す先(配列の要素)に定数の0を代入しています。配列の要素を示す[]はありません。

(3)でポインタの値を1増加しています。このときポインタの値は「アドレス+1」ではなく、「アドレス+型の示すサイズ」だけ増加されます。これが、ポインタの大きな特徴です。

ポインタは単に変数のアドレスを保持しているだけではなく、その型の値が占有するバイト数も保持しているため、インクリメント演算子「++」を使って『配列の次の要素』を示すことができるのです。

ポインタの方が速い

配列の添字を直接扱う場合に比べて、ポインタを使った場合はソースの行数が増えています。ということは、処理が冗長になって遅くなるのでしょうか?そうではありません。

配列の各要素を添字で示す場合、[]による添字が示されるたびに「先頭から何個目の要素か」が計算されます。たとえforループのカウンタ変数で添字を0、1、2、3……と順に増加していった場合でも、num[0]をアクセスした次にnum[1]をアクセスするときには、再び先頭の0から数えて1番目、num[2]をアクセスするときにはまたまた先頭の0から数えて2番目……という具合に、常に先頭に戻って添字の示す位置の要素をアクセスするのです(図1)。

一方ポインタを使った場合は、ポインタの示すアドレスに対する+/−の加減算演算子によって『その型のバイト数分の増減』が計算されます。つまり、『今アクセスしている要素の次または1つ前』という指定が簡単に行えるのです(図2)。

常に先頭から始まる添字を使ったアクセスは『絶対的』で、今アクセスしている場所から前後に移動できるポインタによるアクセスは『相対的』です。いちいち先頭に戻らないで済む分、ポインタによるアクセスは高速になります。



インクリメントの簡略表記

先のポインタを使ったソースは、以下のように簡略表記できます。
int i;
int *p;
p = num;
for (i=0; i<6; i++) {
  *p++ = 0; -------- 要素への代入とポインタの増加を1つにまとめる
}

"*p++ = 0;"の1行は
*p(pの示す要素)に0を代入した後
ポインタpのアドレスを1つ進める
という動作を表しています。演算子の優先順位を利用して、以下の2行を1行にまとめた訳です。
  *p = 0;
  p++;