この記事はc言語初心者でポインタに対して躓いてしまった方、基本情報等の資格試験の問題でポインタがでてきてわからなくなってしまった方に向けての記事です。
そもそも変数やメモリとは?
ポインタは、変数の住所(場所)を保持します。
そもそも変数とは値を入れておく箱のようなものです。宣言するとメモリ上に変数が配置されます。メモリとはPCが作業する机のようなものと思えば良いです。変数はその机の上に置かれるのです。ですので、変数のイメージとしてはコップだったり、ポーチであると思いましょう。変数の種類(型)によって入れることの出来る値が変わるのです。コップには液体を入れますが、ポーチには入れませんよね?同じようにint型の変数には少数点つきの値をいれることはできないのです。
本題 ポインタとは?
さて、本題に入りましょう。ポインタはその変数の場所を保持している、変数の1種です。
さっきのイメージだと、オレンジジュースが入っているコップは机の左上、りんごジュースが入っているコップは右上にあるよ〜みたいな情報が入ってる入れ物です。ポインタ型の変数1つ1つに(左上にカップ)、(左下にポーチ)という情報が入っているのです。ですので、ポインタは矢印のような図で書かれることが多いです。どこの変数の場所を指していると明示しやすいためですね。ポインタは変数の値が”変数の場所(矢印)"であるとイメージすればポインタを使って変数へアクセスできる(矢印をたどっているイメージ)のも自然でしょう。
ここからはc言語の知識を少しだけ必要とします。
実際にポインタを作成する時
実際にポインタを使う時は、場所だけ渡せばいいのではなく、その場所にある変数の種類を教えてあげる必要があります(*1)。ですので、具体的に宣言する時は下記のようになります。
//普通の変数の宣言
int a;
//ポインタの宣言,int型の変数を指すポインタとなる
int *a;
int num = 10;
//変数numを指すポインタの宣言方法
int *b = # //宣言と同時に代入
//宣言と代入を別々に
int *c;
c = #
&という記号は、変数の前につけてあげることで、その変数が配置されている場所(アドレス)を取り出すことが出来ます。その場所の情報をポインタに渡してあげてるのです。
ポインタを宣言するとき、int *
型に変数のアドレスを代入しているとイメージを持っておきましょう。
このイメージはポインタを使い続ける上で重要です。*(アスタリスク)という記号はポインタ型であると示しているだけで、変数名ではないことに注意です。もちろんint*
型以外のも色々なポインタ(char*
型など)があります。
*1 : 理由としては変数によってメモリの使用領域が違うため、先頭アドレスだけ渡しても、どこまでがポインタで指された変数の値であるかわからないためです。
注意点 ~ポインタの宣言には2種類ある~
int num = 10;
int* a; //①
int *a; //②
int *b,*c,*d; //複数のポインタを宣言
int *e = # //宣言と同時にアドレスを代入することもできる
私個人としては②で宣言することが多いです。②のやり方でしたら、同時に複数のポインタを宣言したり、ポインタと普通の変数を混ぜて宣言することができますが、①では出来ないのです。ですが、前者のやり方はintポインタ型と明示的です。
宣言する時は後者を使うのですが、先ほども述べた通りイメージはint*
(intポインタ型)であると理解した方が良いでしょう。
*(アスタリスク)の注意
この記号はポインタ関連で2つの意味があります。どちらもポインタ関連で使うので最初は混乱するのですが、全く別物であると考えて差し支えありません。ポインタを使用する場合のアスタリスクの種類は2つあります。
1.宣言する時のアスタリスク
2.ポインタが指している値を参照したい時に演算子として使うアスタリスク
まず 1. についてですが、これは楽勝です。先程も述べた通り
int *a;
このようにポインタを、ポインタ型であると宣言する時に使います。
2.の方は、ポインタが指している変数の値を参照する時に使う参照演算子と呼ばれるものです。これは具体例を通して確認していきましょう。
int num1 = 10;
int num2 = 20;
int *a = &num1; //宣言する時の使い方
printf("%d\n",num1);
printf("%d\n",*a); //参照する時の使い方
printf("%d\n",*a+num2);
実行結果
% gcc -o tmp 211.c
% ./tmp
10
10
このようにポインタ(ここではint*型の変数 a)が指す変数(ここではnum1)を、ポインタがまるで変数自身かのように扱える(aがnum1自身かのように扱える)のです。
これを見れば、 1. とは明らかに使い方が違うと分かるはずです。
いつつかうの?
多くの使用用途がありますが、わかりやすい例として、関数に渡す引数を参照して使って欲しいときに使います。
これも具体例で確認しましょう。今回は2つの変数に入っている値を入れ替えるswap関数を作ってみたいと思います。
#include<stdio.h>
void swap(int a,int b){
printf("----- swap関数に入る -----\n");
printf("入れ替え前 : %d %d\n",a,b);
int tmp = a;
a = b;
b = tmp;
printf("入れ替え後 : %d %d\n",a,b);
printf("----- swap関数から出る -----\n");
}
void swap_pnt(int *a,int *b){
printf("----- swap_pnt関数に入る -----\n");
printf("入れ替え前 : %d %d\n",*a,*b);
int tmp = *a;
*a = *b;
*b = tmp;
printf("入れ替え後 : %d %d\n",*a,*b);
printf("----- swap_pnt関数から出る -----\n");
}
int main(){
int num1 = 10;
int num2 = 20;
swap(num1,num2);
printf("swap関数呼び出し後 : %d %d\n",num1,num2);
printf("\n");
swap_pnt(&num1,&num2);
printf("swap_pnt関数呼び出し後 : %d %d\n",num1,num2);
return 0;
}
実行結果
% gcc -o tmp tmp.c
% ./tmp
----- swap関数に入る -----
入れ替え前 : 10 20
入れ替え後 : 20 10
----- swap関数から出る -----
swap関数呼び出し後 : 10 20
----- swap_pnt関数に入る -----
入れ替え前 : 10 20
入れ替え後 : 20 10
----- swap_pnt関数から出る -----
swap_pnt関数呼び出し後 : 20 10
実行結果からわかるようにポインタを使わなかった swap
関数を呼び出しても関数内では入れ替えが行われますが、num1,num2自身は入れ替えができていません。この結果から関数は渡した引数自身(この例ではnum1,num2)を用いているわけでないと分かります。
swap_pnt
関数を使った場合はしっかりとnum1,num2の入れ替えが行われました。これはswap_pnt
関数の引数がポインタ型になっており、アドレスを引数として渡しているので、num1,num2自身を参照してくれているのです。
まとめ
今回はポインタを解説してみました。ポインタは色々な用途に使うことができ、今回は書ききれなかった部分がたくさんあります。ポインタは奥が深くおもしろいものなのでぜひ習得してみて下さい。