[C言語]初心者向けにとにかくポインタを分かりやすく解説してみる[コード付]

C言語

どうも〜 シュモクザメです。
今日はC言語を学ぶ者たちが必ずぶち当たる壁、ポインタをできるだけ分かりやすく解説してみたいと思います。
僕自身も最初は理解するのにとても時間がかかりました、、
なのでその経験を踏まえた上で記事を書いてみます!!

ポインタの定義

まずは定義からです。
ポインタとは、変数のアドレスを格納している変数
のことです。

?????

まあそうなりますよね、急にアドレスとかどうとか。
具体的に考えてみましょう。

int a;
a=1;

上のコードでは、int型の変数aを宣言してそこに1を代入してますね。
この時、処理的にどうなっているかを簡単に考えてみましょう。

当たり前の話ですが、コードを処理するときには常に記録が行われています。
今だったらaの情報を記録してますよね、ていうか記録されていないとaを参照できませんしね。
そのときに使用可能なメモリの中で場所が割り振られます。
下がイメージ図です。

この割り振られる場所のことをアドレスといいます。
(上の画像では適当にX_3っていうアドレスにしましたが、実際にはXXXXXXX的なやつです)

このイメージ図の状況を例えていうと、とある町の中(使用可能なメモリ)の、特定の住所(アドレス)に特定の人(a=1)が住んでいるみたいなもんです。

ここでポインタの定義に戻ってみましょう。
ポインタの定義はある変数のアドレスを格納する変数だったので、、、
aのポインタ = aのアドレスを格納している値 ( = X_3番地という土地そのもの)
とりあえず今覚えて欲しいのは、
ポインタは実際の値を表すものではない!!っていうことですね。

ポインタの宣言

いきなりですが使い方にいきます(実際に使ってみたほうが解説しやすい)
とりあえずポインタの宣言の仕方です。

int a=1;
int *b = &a;

?????

はい、じっくりやっていきましょう。
まあ1行目は分かりますよね、ただaを宣言して1を代入してるだけです。

問題の2行目ですが、
左辺のint ✳︎b
int型の変数が入っているアドレスを格納するポインタのbを宣言したよって意味です。
ここでは2点ポイントがあります

  • ポインタには型があって、対応させなければならない
  • ポインタ変数として宣言するときには、変数の前に✳︎をつける

です。
まず型の対応について。
ポインタ変数はアドレスを格納する変数ですが、「そのアドレスにある変数の型のポインタ」として宣言しなければなりません。
上の例ではint型aが存在するアドレスを格納したいので、int型のポインタを宣言したというわけです。
もちろんdoubleならdouble型のポインタにしなければなりません。

次に✳︎(アスタリスク)について。
ポインタを宣言するときは必ず、◯◯(型) ✳□□(変数名)とします。
このとき、変数名はあくまで□□です。✳︎□□ではないので注意です

んで、コードに戻って、2行目
右辺の&aは
変数a存在する場所の値、つまりアドレスを取得してるという意味です
&は変数の前につけることで、その変数が格納されているアドレスを取得する役割があります。

つまり&aでaのアドレスっていう意味です。
下のイメージ図のアドレスの場合なら&a = X_3が成り立つってことです。(なんども言うけど実際のアドレスはこんな単純な英数字ではないよ!)

はい、以上より2行目
int型のポインタ変数bを宣言して、そこにint型の変数aアドレスを代入した。って意味の文になります。
イメージ図はこんな感じになります。

何度も言う通り、bはaそのものでなく、aのアドレスを格納しているだけです。
この状態をbはaを指しているといいます。
この表現はめちゃめちゃ大事です。(多用するって意味ね)
絶対に覚えましょう!!
ちなみにaはbに指されているとも言えます。

ポインタの実体化

ここまでで、ポインタはコード内で取り扱う値そのものではなく、その値が格納されているアドレスを格納する変数だと言うことは分かったと思います。
次はさらに
ポインタの格納しているアドレスに存在する値を取得する
ということをやっていきます。ちなみにこのことをここでは実体化と呼びます(正式ではないけど)
とりあえずコードです。

int a=1;
int *b;
b = &a;
int c = *b;
printf("%d", c);

3行目まではおkですね。さっきのまんまです。

問題は4行目ですね。
左辺のint cは
int型の変数cを宣言してるだけですね、これは簡単。

右辺の✳︎bは
ポインタ変数bに格納されているアドレス存在する値を取得して、実体化している。という意味です

あれ?上でも説明していた通り、ポインタ変数として「✳︎b」と宣言しても、変数名は「b」のはずじゃないの?
だとしたらここの✳︎はいらないんじゃないの?

はい、ここからがややこしいんですよね。
もちろん変数名は「b」です。
ここで行なっているのは「ポインタ変数bの前に✳︎を付けている」と言うことです。
この✳︎は、文の中のポインタ変数の前につけることで、そのポインタを実体化する役割があります(そのアドレスに存在する値を取得する)

ポインタ変数として宣言するときにも✳︎で、実体化するときにも✳︎をつける、、、ってコト?

そうなんです。やばいですよね。
これがポインタが理解しづらくなっている主な要因のひとつだと思います。

とりあえず、

  • 宣言のときに✳︎□□なら□□をポインタ変数として宣言したよ!
  • それ以外で✳︎□□なら□□の値を実体化して扱うよ!

って言う意味で捉えれば良いですってことです。
上の場合ならアドレス、下の場合なら実際の値を扱う変数と真逆の性質を持っているのでやばいですよね。
ここに関しては両方✳︎じゃなくて、片方は違う記号にしとけばよかったやろ、、ってマジで思います。

まあ慣れるしかないです。

コードに戻りましょう。
以上より4行目
int型変数cに、ポインタ変数bに格納しているアドレス存在するint型の値を代入した、つまりaの値を代入したって意味の文章になります
3行目でbはaを指しているのでわかると思います。
⬇︎イメージ図

そしてこのコードを実行すると
1
と出力されます。aの値がcに代入されたことが確認できますね。

ポインタを用いた値の変更

ここからはさらにポインタの踏み込んだ使い方を解説します。
一旦コードをどうぞ

    int A = 2;
    int *B, *C;
    B = &A;
    C = &A;
    printf("%d %d\n",*B,*C);
    *C = 1;
    printf("%d %d\n",*B,*C);

まず4行目までについて、
ここまで読んでいればわかると思います。int型のポインタ変数のBとCが2つともint型のAを指しているって状況ですね。

5行目ではBとCを実体化した値をprintfで表示してます。
もちろんここでの出力結果は
2 2
となります。Aが2なので当たり前ですよね。

そして6行目
✳︎C = 1についてですが、
これはポインタ変数Cに格納されているアドレス存在する値に1を代入する。という意味の文となっています。
ここでCはAを指していることを思い出しましょう。
つまりCに格納されているアドレスにはすでに値(A=2)が存在しているということです
この場合は普段のC言語の処理と同じく、後から代入する値に上書きされます。
つまりCに格納されているアドレス存在している値(A=2)を代入する(A=1)という意味の文と解釈できます。

この文の中には一度もAが登場していないけれどAの値が変更されたってこと?

そういうことです。不思議ですよね。
まあこの性質がポインタの強みだったりもします。
⬇︎イメージ図

そして7行目
ここでは5行目と同じくBとCを実体化した値をprintfで表示してます。
ここでの出力結果は
1 1
となります。

あれ?✳︎Bの値も2から1に変更されている!

そうなんです。
6行目でAの値を変更した影響がここに現れています。
B自体はあくまでAのアドレスを持っている変数でしかないということが大事な点です。
BAそのものの値を持っているわけではないので、外部からAが変更された場合B実体化した値も変更されるということです。
まあこれでポインタ変数は本当にアドレスしか格納していないイメージが湧くと思います。(中身が変更されようが知ったこっちゃない的なノリ)

まとめ

以上がポインタの性質と基本的な使い方になります。
ただここまでわかっても

いや わざわざこんなややこしいモノ使わんわ

って思いますよね。僕も思いました。
でも実はいっぱいメリットがあります。

  • アドレスによって直接的に値を扱える
  • 一つの関数で複数の値を変更できる
  • メモリの節約になる
  • 線形リストの実現

などなどたくさんの実用的なメリットがあります。というかポインタがないと不便!!ってくらいです。
ここら辺のメリットを解説する記事も近いうちに書きたいと思います。

ポインタはややこしいですが一度理解すればヤバい武器になり得るものです。というか使えたらかっこいいよね。

てことで今回の記事はここでおしまいです!




コメント

タイトルとURLをコピーしました