第十一回-01 配列を指すポインタ

第十回-02 アドレスとポインタ (1)からポインタというものについて学んでいる。

ポインタとは をまず学び、それを導入することで ことを第十回課題により知った。

ポインタは、C言語を学ぶ上で最も理解が難しい所であると一般に言われる。
それを反映して、例えば amazon のような書店サイトで「C ポインタ」などをキーワードに検索すると、
ポインタを学ぶことだけに特化した C 言語の教科書がたくさんヒットするくらいである。

とは言え、ポインタを理解するために何百ページもあるような書籍が必要かというと、必ずしもそうとは言えない。
ポインタを理解するために必要な「知識」は、第八回から今回までの4回の演習でほぼ網羅しているはずだからである。

にもかかわらずポインタが理解しにくいと言われるのは、ポインタの習得には「知識」よりも「経験」が必要であるからだと思われる。
(多分、車の運転技術のようなもの)
そのようなわけで、ポインタは最初に分からないからといって投げ出さず、 何度も繰り返し学習して身につけることが重要である。

今回はポインタに関する総仕上げとして、ポインタと配列との非常に密接な関係について学ぶ。
ここまでで、C 言語の範囲の概略はほぼ網羅することになる。
ひとつ「構造体」と呼ばれる内容には触れていないが、これは C++ にて「クラス」について学べば自然に理解できるようになる。


配列を指すポインタ

前回、第十回-02 にてポインタを初めて導入した際
ということを学んだ。この考え方はポインタの基本となるものなので、しっかりと理解しておこう。

この例のようにポインタが一つの変数を指すことも多いが、それと同程度かあるいはそれ以上に多いのが
ポインタが配列 (の先頭) を指す」という場合である。

まずは次の例を実行してみよう。



上記のプログラムを実行するには、「第一回-03 初めてのC/C++ プログラミング(コンソールアプリケーション編)」を参考に
コンソールアプリケーションのプロジェクトを作成し、冒頭に

#include <iostream>

を記述してから main 関数内にプログラムを記述すれば良い。

まず、この例の最初のポイントは という点である。ここまでは実は前回学んだことと大差がなく、
この時点でポインタ p は下図のように配列の先頭である array[0] を指すようになる。



今の例でもう一つ注目して欲しいところは という点である。これがポインタが配列の先頭を指している時の最大の特色である。

実際、p[i] に対して p[i] = 2*i で値を代入しているのに対し、 それを array[i] に対してアクセスすることで読み出せている
その理由は、上の図の様にポインタ p が配列 array の先頭を指しており、 array[i] と p[i] が同一視できるためである。

このように、 がこのページのポイントである。

「それが何の役に立つのだ?」と思うかもしれないが、この性質により 次ページで学ぶ「配列の動的な確保」が可能になる

その前にポインタと配列の関係についてもう少し学ぼう。


ポインタの演算 (1)

先程の例の「配列に2の倍数を格納している部分」を以下のように変更してみよう。 これは全く同じ動作を別の記法で書いている。

/*
// コメントアウトする
for(int i=0 ; i<n ; i++){
	p[i] = 2*i;
}
*/

// こちらを用いる
for(int i=0 ; i<n ; i++){
	*p = 2*i;
	p++;
}

実質変更されたのは2行であるが、1行目の「*p = 2*i;」については 第十回-02 アドレスとポインタ (1)で既に学んでいる。
「ポインタ p の指す値に 2*i を代入する」という意味であった。

問題は2行目の「p++;」である。「++」はインクリメントという記号であり、第四回-02 C/C++ における演算子にて既に学んでいる。
「p = p + 1;」という意味であった。

問題は「ポインタに 1 を足すと言う操作は何を表しているか?」である。
結論を言えば、図のように「ポインタが指す先が、配列上でひとつずれる」ということである。



つまり、ポインタに1を足すという演算は、配列上での読み取り位置が一つずれるということを表す。
もちろん、「p = p + 2 ;」と書けば二つずれることになる。
このようなポインタの演算はポインタを使用する際には良く用いられるものであるので、見慣れておこう。


ポインタの演算 (2)

さらに「配列に2の倍数を格納している部分」についての話を進めよう。
for 文の中身を以下のように書いても同じ動作をする。試してみて欲しい。

for(int i=0 ; i<n ; i++){
	*p++ = 2*i;       // この行が変更点
}

なんとなく「先程の2行を1行にまとめたのだろう」ということは想像はつくだろうか。
ただ、真面目に考えるとこの「*p++ = 2*i;」という行には幾つか重要な要素が詰まっているので丁寧に解説しておこう。

まず、演算子の優先順位と結合の向きについて。
「 *p++ 」という記述には、「 p 」というポインタ変数に「 * 」という値を取り出す演算子と「 ++ 」というインクリメントの演算子が同時についている。
「 *p++ 」を理解するには、まずこの2つの演算子のどちらが優先して実行されるかを知らなければならない。
優先順位というと難しそうだが、「四則演算のうち、足し算、引き算よりもかけ算、割り算の方が優先順位が高い」、という事実と関連づけると分かりやすいだろうか。

C言語で用いられる演算子にも優先順位がある。詳細は教科書などに必ず書いてあるが、
結論は「 * 」と「 ++ 」は優先順位は同じだが、右から左へと結合される、ということになる。
つまり、「 *p++ 」は右から結合され「 *(p++) 」と解釈される。
「 p++ 」が出て来るので上で学んだ「ポインタに1を足す」ということと関連しているというわけである。

なぜこれが重要かというと、もしこれが「 (*p)++ 」と解釈されるとすると、
これは「p の指す値 (*p) に1を足す」ということを表し、意味が変わってきてしまうからである。

さらに話を進める。
「 *(p++) 」と解釈されたとして、これははどう展開されるかであるが、 ここで第四回-02 C/C++ における演算子にて学んだことを思い出して欲しい。
後置のインクリメント「 s = x++; 」が「 s = x; x = x + 1; 」の順で展開されて計算される、と学んだ。

それと同様に「 *p++ = 2*i; 」は「 *p = 2*i; 」→「 p++; 」と展開される。

もしこれが前置の「 *++p = 2*i; 」であれば「 p++; 」→「 *p = 2*i; 」と展開される。
(ただし、これでは配列の範囲外の array[5] にアクセスしようとするのでエラーになるが)

以上、やや長くなったが重要なことであるのでじっくりと解説した。
「 *p++ = 2*i; 」を見て即座に意味を理解することは最初は難しいかもしれないが、
時間をかけてでもいいから理解するようにして欲しい。



←第十回課題第十一回-02 new 演算子によるメモリの動的確保→

非情報系学生のための C/C++ 入門に戻る