この記事はあくあたん工房お盆休みアドベントカレンダー4日目の記事です.
C++と僕が出会ったのは小4の夏休みでした。しかし、それから2か月後、僕とC++の関係はポインタのせいで破局を迎えることになります。
父の部屋にあった分厚い専門書で小学四年生がポインタを理解するのは非常に困難でした。
最終的に、僕はC++を理解することあきらめて型とかポインタの存在しない()webプログラミングに乗り換えました。
それからの僕はすっかりweb系の人間になってしまい、触る言語といえばJavaScript,PHP,Pythonがほとんどだったため、ポインタの概念を理解したことがありませんでした。しかし最近色々とC系の言語を触ることが多くなってきたので、この機会にポインタを学んでみようと思います。
そもそもポインタって何なの?
ポインタは、値が格納されているメモリの位置を表すものなのだそうです。なんのこっちゃ?
まず、aという変数を作成したとします。
このとき、aという変数はパソコン内部のメモリに保存されます。
このaという変数を取得したり代入したりしようと思ったらどうすればよいのでしょうか?
次の方法はとても一般的なものです。
//int型の変数aを宣言し、2を代入
int a=2;
//変数の値の取得方法
//aの値を取得し、bという変数に代入している。
int b=a;
//変数の代入方法
//aに1を代入している
a=1;
この場合、プログラムは"a"という名前を付けられた変数を取得したり代入したりしています。
実は別の方法でも同じことができてしまいます。
上で書いたようにプログラムはコンピュータのメモリ上に保存されていますので、「〇番のメモリに保存されている変数に△△して」と命令することが可能なのです。
わかりやすく例えると、子供(コンピュータ)にお使い(処理)を頼む時に、前者は「山田さんちに行ってきて」と言っているのに対し、後者は「××県の##市〇ー△ー◇の家に行ってきて」と言っている感じです
個のメモリ所の住所のことをアドレスといい、変数aのアドレスは以下のようにして取得することができます。
//int型の変数aを宣言し、2を代入
int a=2;
//aのアドレスを保存するための変数を宣言する
int* adress;
//aのアドレスをadressという変数に保存
adress = &a;
ポインタとアドレス
ここで、intという謎の型が出てきました。
上記のプログラムで変数aのアドレスは変数adressに保存されています。
この、変数のアドレスを保存した変数(この場合ではadress)をポインタもしくはポインタ変数と呼びます。
ところで変数adressの型は「int」となっています。これはadressがint型であるということではなく、adressが「int型の変数のアドレスを保存できるポインタである」という意味です。
つまり
int* adress=&a;
のとき、変数aはint型である必要があるわけです。
同様にchar* 、long* 等も可能です。
次に、先ほどから時々出てくる
&a
についてです。
&はアドレス演算子と呼ばれるもので、変数のアドレスを返します。
実際に
//変数aのアドレスを出力
cout << &a <<endl
というプログラムを実行してみると、
0x7e014
変数aが保存されているメモリのアドレスが返ってきます。
ポインタを使う
次に、ポインタに保存したアドレスにある変数(上のプログラムでは変数a)の値を取得したり代入したりしてみましょう。
ポインタに保存したアドレスにある変数(ややこしい...)にアクセスするためには*を使用します。
//int型の変数aを宣言し、2を代入
int a=2;
//aのアドレスを保存するための変数を宣言する
int* adress;
//aのアドレスをadressという変数に保存
adress = &a;
//adressに保存されたアドレスの変数に3を代入
*address=3
//aを出力
cout << a <<endl
//adressに保存されたアドレスの変数を出力
cout << *adress <<endl
これを実行すると
3
3
という結果になり、aに3が代入されていることがわかります。
このようにポインタを介して値を参照することを間接参照といいます。
ポインタを利用する
参照
ポインタの利用ケースとして、関数に変数を渡すというものがあります。プログラムにはスコープというものがあって、以下のプログラムだと、hoge関数の中でmain関数で作成した変数piyoにはアクセスすることができません。
void main(void){
int piyo=1;
hoge();
}
void hoge(void){
cout<<piyo<<endl;
//関数hoge内からはpiyoにアクセスできないのでエラーが出る
}
こういう場合、関数の引数としてポインタを渡し、関数内から間接参照をすると、piyoにアクセスできるようになります。
void main(void){
int piyo=1;
//関数hogeの引数としてpiyoのアドレスを渡す
hoge(&piyo);
}
//関数hogeの第一引数をfugaという名前のポインタとして受け取る。
void hoge(int* fuga){
//ポインタfugaを間接参照する。
cout<<*fuga<<endl;
}
このようにポインタを使用することでプログラム内の値の受け渡しが楽になります。
関数ポインタ
変数と同じように関数にもポインタを使用することができます。
関数のポインタの宣言は少し特殊です。
//戻り値void、引数voidの関数を作成
void(*hogehoge)(void);
C++では関数を()をつけずに代入すると、関数のアドレスが取得できます。そこで、ポインタに関数fugafugaを覚えさせて実行させたいときには、
void fugafuga(void){
cout<<"hello"<<endl;
}
void main(void){
//戻り値void、引数voidの関数ポインタhogehogeを作成
void(*hogehoge)(void);
//ポインタhogehogeにfugafugaをセット
hogehoge=fugafuga;
//fugafugaが実行される
hogehoge();
}
とできます。
しかし、関数ポインタについてはもろもろの事情からstd::functionを代わりに使用するのが一般的になっているようなので、覚えておく程度でいいでしょう。