まふゆちゃんの技術ブログ。

情報系の高校生・高専生・大学生を全力で応援するブログです。

【C言語】ポインタが分からない人のためのポインタ講座。

f:id:Mafuyu7se:20181111085643p:plain
さてさて、まふゆブログの記念すべき1回目の技術的な記事になります・:*+.\( °ω° )/.:+
今回のテーマは"ポインタ"です!
ちょっと長い記事になるから、初心者の人は休みながら読むといいかも。

ポインタ…
それはC言語初心者にとって最大の壁。

こいつを理解できなくて、C言語の習得を諦めた人間の多いこと多いこと…
そして、ポインタを制する者はC言語を制するといっても過言ではないと言われてます。
それぐらいよく使われるし、使いこなせると強力な機能なんだよね。

でもでも、学校の教科書にかいてあるポインタの説明ってすっごーくわかりにくいよね。
そんなあなたに、まふゆちゃんが分かりやすくポインタを解説します!
とってもわかりやすいから安心してね。絶対に理解できるよ!!
もし理解できなかったら、桜の木の下に埋めてもらっても構わないよ!!!

■ 本記事の対象読者

・学校の授業でポインタを習ったけど、意味が全くわからん!
・会社の研修でC言語習ったけど、ポインタの意味がわからん!
・ポインタの概念は一応知ってるけど、具体的な使い方がわからん!
・ポインタなんもわからん!

といった人たちに、ポインタの概念、具体的な使い方をやんわりと理解してもらうのが目的です。

■ 注意!!

わかりやすさを重視しているので、若干正確性に欠ける内容になってるかも。
あと、コードが無駄に冗長だったりとか、要らなそうなコメントが書いてあったりとか…
17歳JKが書いたブログだから、そのへんは許してね...。

ポインタについて理解できてる人にとっては退屈な内容だから、ブラウザバックしてもいいよ!
でも、できれば最後まで読んでほしいな。
改善すべき点とか、ツッコミどころがあればぜひコメントしてください!

あと、スマホでは若干ソースコードの表示が崩れるし、行数も表示されないっぽいからPCでの閲覧を推奨します!

それじゃ本編開始!!
いくぜ!!!

■ コードの実行環境

・OS:Windows 10
・開発環境:Visual Studio 2017

Visual Studioのインストール手順とかは他のサイトを当たってね!
私はそこまで面倒は見れません!


スポンサーリンク



■ まず、ポインタって何だよ…

まずはポインタに関する基本的な説明から。
「んなもん知っとるわ!!」って人はガンガン読み飛ばしちゃえ!

結論から言います。
ポインタってのは、ズバリ、アドレスのことです。
以下のコードをみてね。

#include <stdio.h>
 
int main(void)
{
	int Mafuyu;        // 普通の変数だよ。 
	int *MafuyuPtr;    // ポインタだよ。 (1)

	Mafuyu = 114514;        // 普通の変数に、適当な数字を入れるよ。 (2)
	MafuyuPtr = &Mafuyu;    // ポインタに、変数Mafuyuのアドレスを入れるよ。 (3)

	printf("Mafuyuの値は、%dです!\n", Mafuyu);
	printf("MafuyuPtrの値は、%dです!\n", MafuyuPtr);

	return 0;
}

注目してほしいのはコード内の(1)。
ポインタを宣言するときは、変数名の前に"*"を付けます。

そしてコード内の(3)。
ポインタにint型変数Mafuyuのアドレスを代入してます。
このとき、アドレスを示したい変数名の前に必ず"&"をつけること。
例えば、変数"Mafuyu"のアドレスを示したいなら"&Mafuyu"になります。
なんで?とか考えてはいけません。そういう決まりだから。

それではいざ、実行!!
f:id:Mafuyu7se:20181106182048p:plain

出ました、おなじみの黒い画面だね。

実行結果を見てみよう。
Mafuyuの値は114514だね。コード内の(2)で代入しているのでこれは当然。

そして、問題はMafuyuPtrの値。7600628とかいう意味わかんない値になってるね!
これが、メモリ上の変数Mafuyuが存在する場所(アドレス)を示しています。
これはコンピュータさんが勝手に決める値なので、実行環境によって違う数値になるよ。

図に書くとこんな感じかな。
f:id:Mafuyu7se:20181106182148p:plain

どう?簡単でしょ?

繰り返しになるけど、ポインタは、メモリ上の変数が存在するアドレス(場所)を示すときに使うものです。
ここまで理解できたら、次の項目に進もう!

■ で、どうやって使うの?

それでは、ポインタさんが実際にどのような場面で活躍するかを見てみよう。
多分、一般的な教科書に載ってるコードとかはこんな感じじゃないかな。

#include <stdio.h>
 
// 関数plus1()の宣言だよ。
int plus1(int *a); // (1)
 
int main(void)
{
	int num;
	int result;

	num = 8;
	result = plus1(&num);  // 渡すのはnumのアドレスだから、"&num"になるよ。

	printf("%d を1増やすと、%d になります。\n", num, result);

	return 0;
}
 
// 値を1増やして返す関数だよ。
int plus1(int *a)  // (2)
{
	return *a + 1;  // (3)
}

で、実行結果がこちら。
f:id:Mafuyu7se:20181106182413p:plain

まずコードを見てほしいんだけど、plus1()っていう関数があるよね。
名前のとおり、受け取った値を1増やして返す機能の関数です。

注目はコード内の(1)と(2)。
引数aに"*"が付いています。
これが何を示しているかっていうと、ポインタを渡すよってことです。

渡しているのは値そのものではありません。
ポインタを渡しているんです。
大事なことなので2回言いました。

言い方を変えると、引数aで値そのものを渡しているんじゃなく、aが存在している場所(アドレス)を渡しているんですね。
つまり、この使い方では変数numに入っている値(8)ではなく、変数numが存在するアドレスがaとして渡されることになります。

ちょっとしつこいかもしれませんが、渡されるのは8という数値ではなく、数値8が置いてある場所だからね!
ここ、めっちゃくちゃ大事だよ。ほんとに。

で、次に見てほしいのがコード内の(3)。
return *a + 1;ってところ。aに"*"が付いているね。
これは、ポインタaのアドレスに入っている値を示しています。
今回の場合は、8になるね。

ちょっとややこしいんだけど、ポインタとして宣言された変数は、関数の中で"*"を付けることで、そのアドレスに存在する"値そのもの"を表現できるんです。
このとき、"*"を付けなかったら、"値そのもの"ではなく"アドレス"を意味します。

まとめると、
"*a"を宣言する → aさんはポインタだよ!という宣言。
"a" → 入っているのは、コンピュータさんが割り当てたアドレス。
"*a" → 入ってるのは、アドレスaに存在する値。

ということになるのだ!

コードの処理内容を図にまとめると、こんな感じです。
f:id:Mafuyu7se:20181106182455p:plain

理解できたら、ちょっと冷静になって考えてみてほしい。
"これ、ポインタ使う必要なくね?"
って思いませんか?

そのとおり!!
こういう処理をするときは、ポインタ使う意味なんてないです。
自分でそれに気づけたあなたは、将来優秀なプログラマになれるよ。

アドレスを渡すんじゃなく、普通に変数numの値(8)をそのまま渡せばいいよね。
わざわざ回りくどい方法を使う必要はないし、現場でもこういう使い方をすることはまずありえません。
わかりにくいだけで、なんのメリットもないからね。

だから、以下のコードでもさっきと全く同じ結果が得られます。

#include <stdio.h>
 
// 関数plus1()の宣言だよ。
// 渡すのはポインタ(アドレス)じゃないから、*は不要。
int plus1(int a);
 
int main(void)
{
	int num;
	int result;

	num = 8;

	// 渡すのはnumの値そのもので、アドレスじゃないから&は不要。
	result = plus1(num);

	printf("%d を1増やすと、%d になります。\n", num, result);

	return 0;
}
 
// 値を1増やして返す関数だよ。
// 渡すのはポインタ(アドレス)じゃないから、*は不要。
int plus1(int a)
{
	return a + 1;
}

実行結果はこちら。
f:id:Mafuyu7se:20181106182413p:plain

ここで大事なのは、
・引数として"値そのもの"を渡した場合
・引数として"ポインタ(アドレス)"を渡した場合
上記2点において、全く同じ結果が得られるということです。

どっちも同じ結果なら、値そのものを渡したほうが楽だよね。
ポインタ使う意味がない処理なのに無理やりポインタを使ってるせいで、無駄に理解しにくくなっているんだろうね。

ちなみに、
"値そのもの"を渡す方法:値渡し
"ポインタ(アドレス)"を渡す方法:参照渡し または ポインタ渡し
と呼びます。余裕があったら覚えておいてね。


スポンサーリンク



■ それじゃ、意味のある使い方って?

ポインタが実際に役に立つパターンの一例として、"サイズの大きいデータを他の関数に渡す場面"が挙げられます。

char型とint型とかじゃなく、もっと大きい構造体とかを渡すときに使います。
例えば、以下みたいなバカでかい構造体があったとしよう。

// バカでかい構造体。
typedef struct {
	int num0;
	int num1;
	int num2;
	int num3;
	.
	.
	.
	int num998;
	int num999;
} BAKA_DEKAI_KOUZOUTAI;

現場ではこういうアホみたいなネーミングはしないし、int型変数を無意味に1000個も構造体にぶち込むような真似はしないけど、これはあくまで一例ということで。

で、この構造体をmain関数から別の関数に渡したいな~ってとき、あなたはどうするかな?

まず思いつくのが、構造体の値そのものを渡す方法。
さっき言った"値渡し"のことだね。

int main(void)
{
	BAKA_DEKAI_KOUZOUTAI bakaDeka;
	subFunc(bakaDeka);

	return 0;
}
 
void subFunc(BAKA_DEKAI_KOUZOUTAI deka)
{
	// 適当な処理。
}

これ、一見問題なさそうだよね。
ちなみにちゃんと動きます。

しかしこういう書き方をされると、コンピュータさんは非常に辛い思いをします。

それは、関数に値を渡すときに、引数の値をそのままコピーする必要があるからです。
つまり、main関数で宣言されたbakadekaと、subFunc関数の引数dekaは、値は同じでも別のものになります。

BAKA_DEKAI_KOUZOUTAIは名前の通りバカでかいから、同じものをコピーするのは大変だってのはなんとなく分かるでしょ?

もっとわかりやすくするために、
main関数・subFunc関数 → 人
渡されるデータ → ボール
渡されるデータの大きさ → ボールの重さ
で例えてみようか。
mainさんとsubFuncさんが、2人でキャッチボールしてる姿をイメージしてほしい。
f:id:Mafuyu7se:20181106183234p:plain
f:id:Mafuyu7se:20181106183224p:plain

つまり、こういうことです。
渡すデータが大きいと大変そうでしょ?

それで、どうすればいいのかって話だけど、この問題をアッサリ解決してくれるのがポインタ様なのだ。

ポインタを使ったコードを以下に示すよ。

int main(void)
{
	BAKA_DEKAI_KOUZOUTAI bakaDeka;
	subFunc(&bakaDeka);  // アドレスを渡すので、&を付けるよ。

	return 0;
}
 
// ポインタ(アドレス)を受け取るので、*を付けるよ。
void subFunc(BAKA_DEKAI_KOUZOUTAI *deka)
{
	// 適当な処理。
}

これで渡されるのは、構造体そのものではなくて、
構造体が存在するアドレスになります。

つまり、鉄球をコピーして投げるんじゃなく、
"あのクソ重い鉄球、この場所に置いといたから使ってな!"
っていうことになるんですね。
このとき、鉄球をコピーする必要はないので、コンピュータさんにとっては非常にラクです。
f:id:Mafuyu7se:20181106183547p:plain

ポインタを使うメリット、わかってくれたかな?

■ まとめ

・ポインタは、変数とかのアドレスを示すときに使うものだよ!!
・構造体とか、大きいデータを関数間で受け渡すときに使うと便利だよ!!

ポインタの便利な使い方は他にもありますが、長くなったので今日はここまで。

君はいま、ポインタマスターへの第一歩を踏み出した!!
もしこの記事を気に入ってくれたら、ポインタがわからなくて悩んでる友達や同僚に是非勧めてみてね。

ではでは、今日はここまで。
お疲れ様でした~・:*+.\( °ω° )/.:+