C言語 ポインタ

はじめてC言語を勉強したほとんどの方がポインタを理解できずに苦しんでいます。


なにをかくそう僕もC言語のポインタが分からず、はじめて学習したときは3日3晩苦しみました。

C言語のポインタが分かりにくいのには2つ理由があります。
  • 使い道がわからない。
  • そもそもC言語の文法が悪い。


ゆっくり説明いたしますので、一つずつ解決していきましょう。


使い道がわからない
まず、ポインタとはなんのためにある機能なのでしょうか、
実はポインタは、ロード時間を短縮することができるのです。

~ポインタの活用物語(あとで詳しく説明するので用語など分からなくても読み飛ばして下さい)~


背景_機械1

例えば、開発しているアプリケーションで、容量の大きいムービーを再生するとしましょう。わかりやすくするため、なじみ深いint 型変数で説明します。

int movefile = 99999;
と、こんな感じでしょうか、


ユーザー「あ、ムービーが入るからロードするのか……おっきたきた、かっこいいムービーだなぁ」


さて、ムービーの再生が終わったので

movefile = 0;

と、もう使う予定のないムービーは破棄しました。

しかしここで事件発生! ユーザーは戻るボタンを押してもう一度ムービーが再生される画面へ行ってしまったのです!
ムービーは破棄してしまいましたからこれではまたロード時間を挟むことになります。

ユーザーはいらいらしてアプリケーションをアンインストールし、PCごと燃やしてしまいました。(じょうだんです)

しかしこの事件、ポインタで解決できるんです。
ムービーをポインタで保持すればロード時間がなくなります

int movefile = 99999;

int* MoveP = &movefile; // ①

①の行をご覧ください。ここでポインタ変数MovePにmovefileのアドレス(住所)を代入していま
す。(あとで詳しく解説しますので今はふんわり理解しておいて下さい)

これにより、movefileを直接操作しなくてもMovePを使って間接的に操作できます。

再生したいときは

*MoveP; と書けばいいですね。

背景_機械2


ユーザー「おっ、ムービー入ったか、見よ~」

ムービーが終わったので破棄します。この時、ムービーデータそのものを消すのではなく、ムービーのアドレス(住所)を保持しているMovePのデータを破棄します。

MoveP = 0;

ユーザー「かっこいいムービーだったなあ、もう一回見よ」

さあ事件発生です。ユーザーは戻るボタンを押しました。
しかし今回は大丈夫。ムービーデータそのものは消していません。消したのはアドレス(住所)だけなので、もう一度アドレス(住所)を渡してあげればよいのです。アドレス(住所)はせいぜい数桁程度の数値データなのですぐに渡せます。

MoveP = &movefile; // すぐに渡せる
*MoveP; // 再生

背景_機械2


ユーザー「やっぱりかっこいいムービーだなあ」

ユーザーは待たされることなくムービーを楽しむことができました。 おしまい。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

と、このようにポインタを活用することで、重たいデータもすばやく扱うことができるのです。

では講義に移ります。

アドレスについて


まず、ポインタを理解する前にアドレスについて学習しましょう。
コンピュータ内部では、全てのデータにアドレス(住所)が割り振られています。
これは、私たち人間の世界で言うところの住所です。

私のPCのマイピクチャにはブログ用の画像が入っていますが、もちろんこれら一枚一枚にアドレスが割り振られています。
addr


C言語プログラム上で作成したデータ(int charなど)も全く同じです。
そして、作成したデータのアドレスは&演算子で取得できます。

では実際に変数のアドレスを見てみましょう。


実行結果
addr_anp


ご覧のように、アドレスとはただの数値です。コンピュータが判別できさえすればいいので、数値でいいのです。

そして、ここからが超重要です。

ポインタ変数とは、アドレスを格納する変数である。

これがポインタ変数の正体にして全てです。
しかし、それがなぜこんなにも難しい難しいと各所で言われているのでしょうか、その答えは意外なものです。下記コラム参照
コラム
int *number;って書くのはやめよう

タイトルの通りです。これがややこしくしている元凶です。
そもそもC言語はフリーフォーマットなので、スペースの使い方は自由です。
今後はint* number;と書いて下さい。

そもそも

int *number;

と宣言する癖に、ポインタ変数として使うときは

number;

その中身にアクセスするときは

*number;

と使う時の順序が逆になっているのが問題です。
もし宣言を

int* number;

とすれば、

ポインタ変数として使うときは

number;

その中身にアクセスするときは

*number;

と直感的に考えることができます。
なので今後は int型のポインタ変数を使うときは int* と宣言して下さい。


では実際に打ち込んでみましょう。手を動かすことは学習の助けになりますよ。


実行結果
no title


このプログラムでは、ポインタ変数numberPに普通の変数numberのアドレスを代入しています。
numberP とそのまま書くことで、numberのアドレスにアクセスできます。
*numberP と書くことで、numberの中身にアクセスできます。

これがポインタの使い方です。
どうでしょうか、ポインタの意味は分かったでしょうか。

……しかし、こんなに小さなプログラムでは実際に役に立つのかイメージがしにくいですね。
そこで、少し実践的な使い方をしてみます。

333


ちょっと変わった関数を書いてみました。引数に渡した値を変更するというものです。

void func(int* n) と書くことで、int型変数のアドレスを受け取るという意味になります。

関数内部を見てみましょう。
アドレス変数nにアスタリスクをつけることによって、その変数の中身を表現しています。
アドレス変数はその変数がある場所をまさに示しているので、引数として受け取った値を書き換えることができるのです。

これにより、戻り値が2つ以上必要な関数も疑似的に作成することができます。
それじゃあ最初から戻り値が2つ以上作れる機能を用意してくれと思うかもしれませんが、なにせ48年前の言語なのでいろいろと欠陥があるのです。
しかし今更変更すると、昔のシステムに対する保証ができなくなるのでだましだまし使ってきたのです。

これがポインタの正体です。どうでしょうか、理解できたでしょうか。
ポインタは実際に自分で使わないと理解が深まりません。時間がある方はこのページのソースコードを自分の手を動かして写経(丸写し)することをおすすめします。
実用的なアプリケーションを作る際に、どうしても関数内に引数のコピーではなく実態を渡したくなるときがくると思います。そのようなときにこのページに戻ってきて、もう一度確認してみて下さい。