●はじめに
こんにちは。
今回は、C言語で皆さんが最もつまずくとされているポインタについて説明していきたいと思います。
私もポインタは概念が難しく理解するのに時間がかかりましたがわかりやすく解説していきます。
「え、そもそもC言語とか初めてなんだけど。。。」
「プログラミングの基礎がわからない。」
という方向けの記事ではないので以下のサイトの基本編を学んでからこの記事を読んでください。
→一週間で身につくC言語の基本
「C言語の関数くらいまでならできるよー」
って方でポインタにつまずいている方は是非、読んでください。
●アドレスとは
ポインタを理解するためにアドレスから理解する必要があります。
「アドレスといえば住所でしょ」
そうです、その通りです。普段、変数には値を格納しますがコンピュータのメモリの中にあるため、その位置を表す数値であるアドレスが存在します。例えば、aという変数があるときに、&aとすることで、変数aのアドレスを取得することができます。
実際に変数のアドレスを取得するプログラムを作成してみましょう。以下のプログラムを入力し、実行してみてください。
※C言語を座学で学んだけど、プログラムを実行したことがない方は以下の記事を参考にしてください
→C言語 VisualStudio【開発環境の構築手順を優しく図解】
#include <stdio.h>
int main(int argc, char** argv) {
int a = 100; // int型の変数
double b = 123.4; // double型の変数
char c = 'a'; // char型の変数
printf("aの値は%d、アドレスは0x%x\n", a, &a);
printf("bの値は%d、アドレスは0x%x\n", b, &b);
printf("cの値は%d、アドレスは0x%x\n", c, &c);
return 0;
}
実行すると以下のような結果になると思います。(アドレス部分は実行する環境によって異なります)
aの値は100、アドレスは0xd7fc94
bの値は123.400000、アドレスは0xd7fc84
cの値はa、アドレスは0xd7fc6f
値(a, b, c)は変数に代入した数値や文字がそのまま表示されましたが、アドレス(&a, &b, &c)は謎の数字の羅列になっています。これが値の格納場所を数値として表しています。アドレスは16進数で表すので頭に0xをつけています。
※printfの書式において%dは10進数の整数、%xは16進数の整数を表します。
また、他の箇所でつまずいている場合は以下のサイトの基本編を学んでからこの記事を読んでください。
→一週間で身につくC言語の基本
●ポインタとは
アドレスとはどういうものか理解したことでようやくポインタに入る準備がととのいました。変数には値の他に、その値を格納するアドレスがあることがわかりました。つまり、変数には値とアドレスという2つの側面があります。通常の変数は値を入れることを前提としています。
この他にC言語には、アドレスを入れることを前提とした変数が存在します。それをポインタ変数もしくは、単にポインタと言います。
「この人何言ってんの」
と思う方我慢してください。ポインタはただでさえ難しいのにまだ文字面でしか説明していないので意味不明なのは当然です。徐々にわかるようになるので安心してください。理解というのは使ってみることでその境地に達することもあるのです。ですので利用方法を説明します。ポインタ変数は、例えば次のように定義します。
int *p;
もしくは
int* p;
このように、変数名の先頭か変数の型の後に「*」を付けると、その変数がポインタ変数であることを示すことができます。
そもそも、ポインタ変数と普通の変数はどう違うのでしょうか。その違いをまとめておきます。
・通常の変数とポインタ変数の比較(intの場合)
形態 | 通常の変数 | ポインタ変数 | 解説 |
---|---|---|---|
宣言 | int a; | int* p; | ポインタ変数は、変数の先頭か型の後に*を付ける |
値 | a | *p | ポインタ変数で値を示すには、先頭に*を付ける |
アドレス | &a | p | ポインタ変数はアドレスを入れる |
この表からわかる通り、ポインタ変数は通常の変数と違い、他の変数のアドレスなどを入れることを前提としています。
では、実際にこのポインタ変数はどのように利用すれば良いのでしょうか?簡単なサンプルを以下に示しますので、入力して実行してみてください。
#include <stdio.h>
void show(int, int, int);
int main(int argc, char** argv) {
int a = 100; // 整数型変数a
int b = 200; // 整数型変数b
int* p = NULL; // 整数型のポインタ変数p
p = &a; // pにaのアドレスを代入
show(a, b, *p);
*p = 300; // *pに値を代入
show(a, b, *p);
p = &b; // pにbのアドレスを代入
show(a, b, *p);
*p = 400; // *pに値を代入
show(a, b, *p);
return 0;
}
void show(int n1, int n2, int n3) {
printf("a = %d b = %d *p = %d\n", n1, n2, n3);
}
実行すると以下のような結果になると思います。
a = 100 b = 200 *p = 100
a = 300 b = 200 *p = 300
a = 300 b = 200 *p = 200
a = 300 b = 400 *p = 400
このプログラムでは、a、bという整数型変数と、ポインタ変数pを宣言しています。show関数でそれらの値を表示していますが、変数a、bの値を変更していないにもかかわらず、その値が変わっていることがわかります。
そうなんです、実を言うとポインタ変数は忍法写し身の術を発動できるのです!
良くゲームで写し身をすることで同じキャラを複製して、写し身された側でダメージを受ければ写し身した側もダメージを受けるように一蓮托生なのです。
ではなぜこのようなことができるのでしょうか?実はここがポインタ変数の大事な点なのです。
●NULLによる初期化
では、このプログラムを解説していきましょう。まず、整数型変数a、bにそれぞれ100、200という値を代入して初期化しています。
続いてポインタ変数を宣言しています。この時、同時にNULLを入れて初期化しています。NULLは、C言語で標準的に使われる定数で、数値で言えば0を意味しますが、通常、ポインタ変数はNULLで初期化する慣例になっていますので、覚えておきましょう。
int *p = NULL;
●ポインタ変数に変数のアドレスを代入
このプログラムには、a、b、pという3つのint型の変数が使われています。ただ、a、bが通常の値をとる変数であるのに対し、pはポインタ変数です。そのため、このプログラムの代入処理の流れをまとめると、以下のようになります。
・a、bとpの変化
番号 | 処理 | 処理内容 | 意味 | a | b | *p |
---|---|---|---|---|---|---|
① | p = &a; | pにaのアドレスを代入 | *pはaと同じものになる | 100 | 200 | 100 |
② | *p = 300; | *pに300を代入 | *pはaに等しいので、aが変わる | 300 | 200 | 300 |
③ | p = &b; | pにbのアドレスを代入 | *pはbと同じものになる | 300 | 200 | 200 |
④ | *p = 400; | *pに400を代入 | *pはbに等しいので、bが変わる | 300 | 400 | 400 |
まず、①で、aのアドレスを取得することにより、pは、aとして振る舞うことが可能になります。そのため、値であるpは、aと同じ値をとります。
次に、②で、*pに値を入れると、pはaのアドレスが入っていることから、その値はaに反映されます。
同様に、③で、pにbのアドレスを代入すると、今度は、pはbとして振る舞うことが可能になります。値であるpは、今度はbと同じ値をとっています。
さらに、④で、*pに値を入れると、今度はbの値が変わります。これは②のときと同様で、*pが実際にはbの値となるからです。
このように、ポインタ変数は、初期状態では値を持ちませんが、変数のアドレスを与えることで、その変数として振る舞うことができるのです。したがって、このサンプルのように、ポインタ変数pは変数a、bといったほかの変数として扱えるのです。これがC言語の写し身の術の強みです。
●独り言
ポインタ変数をint *p;と定義するのになんで値が*pでアドレスがpで表すのですかね。
値がpでアドレスが*pの方が直感的でわかりやすいでしょうに。
【投稿者】
エンジニアファーストの会社 株式会社CRE-CO 田渕浩之
【参考文献】
→1週間でC言語の基礎が学べる本