C言語を学ぶ上で、非常につまづきやすいポインタについて説明していきます。
ポインタ
ポインタを説明する前に、変数について復習します。
変数は、値を入れておく入れ物のようなものです。例えば int型なら整数、double型なら小数を入れておくことができる入れ物です。
次に、コンピュータについて、一部だけ説明します。
プログラムを実行するのは、コンピュータです。
コンピュータでプログラムを実行している時、変数など、プログラムの実行に欠かせないデータは、コンピュータの「メモリ」という部分に確保されています。
これらのことをわかった上で、ポインタとは何かと言うと、メモリ上の変数などの位置を指し示すものです。
アドレス
メモリ上の位置を表すものとして、「アドレス」があります。
アドレスは、日本語に訳すと住所という意味で、ひとつのプログラムで違う変数同士が同じアドレスを示すことはありません。
変数のアドレスを表示してみましょう。
#include <stdio.h>
int main(void)
{
int a;
printf("aのアドレスは%pです\n", a);
return 0;
}
アドレスの表示には、%p を用います。
これで、変数a がメモリのどの場所に確保されているのかが分かります。
アドレスとして、0xから始まり、0〜9、a〜fの16個の数字と文字から構成されているものが表示されます。
そして、プログラムを実行するたびに、アドレスの位置が変わっていると思います。
ポインタを理解する上で、アドレスの位置が重要ではありません。
重要なのは、このように変数は、アドレスを持っているということです。
ポインタを使った変数の値の変更
ポインタを使って、変数の値を書き換えてみます。
#include <stdio.h>
int main(void)
{
int a;
int *p;
a = 5;
printf("aの値は%dです\n", a);
p = &a;
*p = 10;
printf("aの値は%dです\n", a);
return 0;
}
このソースコードを実行すると、「aの値は5です」と表示された後に、「aの値は10です」と表示されます。
最初の「aの値は5です」は、変数a に5を代入して表示しているので、問題ないと思いますが、次に表示する時には、変数a に10を代入していないのに、「aの値は10です」と表示されました。
これは、ポインタを使って変数a の値を書き換えたことによって、このような結果になりました。
ポインタとは、変数のアドレスを指し示すものです。アドレスを代入しておくものとも言えます。
「int *p;」は、ポインタ変数と呼ばれるアドレスを格納することができる、ポインタ変数p を用意しています。
ポインタ変数は、通常の変数とほとんど同じように用意するのですが、唯一の違いは、変数名の前に「*」をつけるということです。
ポインタ変数へのアドレスの代入は、「p = &a;」で行なっています。
変数のアドレスは、変数名の前にアンパサンド(&)をつけることで取得でき、それをポインタ変数p に代入しています。
これで、ポインタ変数p に変数a の場所を示すアドレスを代入できたので、後は変数a のアドレスを利用して値を書き換えます。
値の書き換えは、「*p = 10;」で行なっています。
ここで、ポインタ変数p は、*p のように、ポインタ変数名の前に * をつけています。
これは、代入されているアドレスを使って、そのアドレスの変数の値を指し示します。
なので、「*p = 10;」とした場合は、代入されているアドレスの変数に10を代入するということになります。
ちなみに、最後に「printf("*pの値は%dです\n", *p);」を追加した場合、ポインタ変数p は変数a のアドレスが格納されているので、*pとした場合、変数a の値を参照するので、「*pの値は10です」と表示されます。
このようにポインタは、変数のアドレスを使って、直接変数に変更を加えなくても、ポインタで操作することができます。
しかし、直接変数に変更を加えなくてもいいということなので、ポインタを使いすぎると、分かりにくいソースコードになります。
なので、まだ分からないと思いますが、適当なところで使うように心がけましょう。
scanf関数とポインタ
scanf関数を利用する時に、例えば、int型の変数a があった際に、「scanf("%d", &a);」として、変数a に入力をします。
この時、変数a という指定は、「&a」というようにしています。
実は、これは既に説明したように変数のアドレスを示しており、scanf関数には変数のアドレスを指定しているということになります。
このように、既にアドレスを利用していました。
配列とポインタ
実は、配列はポインタです。
変数などのデータは、メモリにあるということを既に説明しました。
配列も同じようにメモリにデータがあるのですが、配列は連続したメモリの場所に値があります。
連続しているということなので、1つ先、2つ先、3つ先のように、隣のデータなどの位置が簡単に分かります。
通常、配列は添字を使ってデータにアクセスしますが、ここではポインタを使ってアクセスしてみます。
#include <stdio.h>
int main(void)
{
int a[] = { 1, 2, 3, 4, 5 };
int i;
*a = 10;
*(a + 1) = 20;
for (i = 0; i < 5; i++) {
printf("%d番目の要素: %d\n", i, *(a + i));
}
return 0;
}
実行結果は、最初に「0番目の要素: 10」、次に「1番目の要素: 20」となり、あとは初期化に指定した値が表示されます。
0番目の要素は、「*a = 10;」で変更しています。
配列はポインタであり、そのポインタに格納されているアドレスは、連続したメモリのアドレスの先頭です。
*a は、先頭である0番目の要素を指しているので、*a = 10 で0番目の値が変わりました。
1番目の要素は、「*(a + 1) = 20;」で変更しています。
1番目というのは、0番目に対してメモリではひとつ隣ということなので、1を足して「a + 1」として隣を表し、値を参照する * をつけます。
この時 a + 1 の方にかっこをつけないと、演算子の優先順位により、「(*a) +1」のようになってしまい、意味が変わるので、「*(a + 1)」のようにかっこをつけます。
このように、配列はポインタを使っており、配列のポインタは、連続したメモリ空間の先頭を指し示すとものです。
驚くようなことではないと思いますが、配列はポインタであることから、このような配列の代入によって、値をコピーすることはできません。
もし、配列の中身をコピーしたいのであれば、ひとつひとつ for 文などの繰り返しを使って代入する必要があります。
#include <stdio.h>
int main(void)
{
int a[] = { 1, 2, 3, 4, 5 };
int b[5];
int i;
for (i = 0; i < 5; i++) {
b[i] = a[i];
}
return 0;
}
ポインタの代入
ポインタを2つ使ってこのようなプログラムを書いた場合、結果はどのようになるでしょうか。
#include <stdio.h>
int main(void)
{
int a;
int *p, *q;
a = 10;
p = &a;
q = p;
*q = 15;
printf("aの値は%dです\n", a);
return 0;
}
結果は「aの値は15です」と表示されます。
このようにポインタ同士の代入をした場合、代入される側のポインタも代入する側のポインタに代入されているアドレスを指すので、この場合、q は aのアドレスが代入されていることになります。
まとめ
配列で、気づかないうちにポインタを使っていましたが、まだまだポインタの使いどころが分からないと思います。
ポインタは、これから説明する「関数」で使うことが多くなるので、それまではこのような機能があるんだというくらいで覚えておきましょう。