ぼざろで分かるC++のポインタと参照
使ってはいるもののいまだにちょっとあいまいなC言語(++)のポインタと参照、を自分で言葉にして説明することで頭に叩き込む、そんな記事です。自分で説明できたら自分でも理解するっていうじゃないですか、それです。
普通に説明している記事は山ほどあるのでここではしません。そこで、ぼざろで例えれば分かりやすいんじゃないかと思ったので、ぼっちちゃんで例えて(ごめんなさい)、自分なりに分かりやすく説明したつもりです。ぼざろについてでもC++についてでも間違っている所があれば教えてください。私はC言語が苦手なのでC++のiostreamを使いますが、C言語でもほぼ同じことが出来ます。
一応ぼざろのネタバレ注意(?)超オタクみたいな記事になってはしまいますがそこは突っ込んではいけません
そもそもなんでそんな機能があるの?
C(++)を使ったことがない人にとってポインタという概念を知っている人はあまりいないかと思います。一応JavaやVisual basicなどで似たような機能はあるらしいのですが、あまり使わない印象です。C系統特有の機能で、パソコンのメモリを管理する上で非常に重要になってきます。Cはメモリを直接管理しやすいという話を聞いたことがあると思うのですが、理由はこれですね。
アドレスって何?
ポインタを理解するうえで絶対に切り捨てられない言葉にアドレスというものがあります。変数が格納されている場所のことです。よく住所で例えられるのですが、ここで早速ぼざろで例えてみましょう。
こんなところでしょうか。左が例えで、右は実際のところに似せたとような感じです。アドレスは住んでいるところで、そこに住んでいる人の名前が変数名、その人の情報が変数に格納されている値と覚えるといいでしょう。コードでも見てみましょう。
string bocchi = "introvert";//ぼっちを召喚(?)、陰キャという情報をもつ
cout <<"About bocchi : " << bocchi << endl;//陰キャという情報を得られる
cout <<"Where is bocchi : " << &bocchi << endl;//ぼっちの場所を取得、例えるなら押し入れ、プログラムならメモリのアドレス
//printfでアドレスを出力するときは%pを使う
実行結果
順番に見ていきましょう。
一行目で変数ぼっちを宣言、英語でいう陰キャにあたる、"introvert"という情報を持たせます。(日本語をそのまま書くと文字化けすることが多い)
二行目で、ぼっちについて聞きます。そうすると答えとして"introvert"が返ってきます。それをcoutで出力します。
三行目にて、異物が出てきました。&は何者でしょうか。こいつはまた後で深堀りするのですが、前につけることで変数のアドレスを取得することができます。&ぼっち とすることで、居場所の押し入れが返ってきます。コードでは十六進数が出てきましたね。パソコンは空いているメモリのどこかを自動で選ぶので、この十六進数、つまりメモリの場所は実行する時や場所によって変わります。ぼっちは学校にいる時だってありますからね。ほぼずっと押し入れだろなんて突っ込みはやめてください
ポインタ
さて、また異物の解析をしていきましょう。&も出てきますが、しばらくは*(アステリスク)に集中して見ていきましょう。頑張って下さい。C++では主に3つの場面でこの二つの記号を使います。別の機能3つを一つの記号で使う設計のせいでかなりこんがらがるのですが、Cの仕様なのでどうしようもないですね。頑張って覚えましょう。
機能一覧
こんがらがらないように今ここで機能を箇条書きで軽く説明します。ここで理解出来なくても問題ないです。
*を使う機能
掛け算(関係ない!)
アドレスが示す場所の値を示す
ポインタ変数として
&を使う機能
AND(関係ない!)
変数のアドレスを返す
参照変数として
深堀りしよう
一つ目は掛け算とANDです。これはポインタとは全く関係ないので一旦忘れましょう。
2*3 //掛け算
if(a==2&&b==1){}//AND
二つ目は受け取ったアドレスの情報を処理する機能です。&は前で説明した通り変数のアドレスを返す機能です。*はそれの逆(?)で、前につけることでアドレスが示す場所の値を取得できます。ぼっちちゃんと一緒に見ていきましょう。
string bocchi = "introvert";//ぼっちを召喚(?)、陰キャという情報をもつ
cout <<"Where is bocchi : " << &bocchi << endl;//ぼっちの場所を取得、例えるなら押し入れ、プログラムならメモリのアドレス
cout << *(&bocchi)<< endl;//押し入れにいる人の情報を取得
実行結果
最初の二行は前でも見ましたね。三行目はどういうことでしょうか。
三行目で、前で述べたアドレスが示す場所の値を示す機能を使っています。例えてみましょう。
&bocchiで「ぼっちの居場所は?」と聞きます。押し入れですね。3行目の回りくどいコードを言葉で書き換えたら*(押し入れ)となりますね。*で示された場所にいる人の情報を取得できるので、「押し入れにいる人の情報は?」と聞くみたいな感じです。押し入れにいる人、つまりぼっちの情報は陰キャなので、introvertと答えてくれます。
これだけだと普通に取得しているのと変わらず、回りくどいだけで何のために使っているのか分からないと思いますが、この後説明する機能で真価を発揮します。C言語のほとんどの機能はポインタでなりたっているなんていいますからね。
ポインタ変数
前では*(&bocchi)というように回りくどく書いてしまいましたが、&bocchiを格納できる変数があります。これをポインタ変数と呼びます。早速使ってみましょう。
string bocchi = "introvert";//ぼっちを召喚(?)、陰キャという情報をもつ
string* location = &bocchi;
cout <<"Where is bocchi : " << location << endl;//ぼっちの場所を取得、例えるなら押し入れ、プログラムならメモリのアドレス
cout << *location<< endl;//押し入れにいる人の情報を取得
*location = "guitarhero";//押し入れの人の情報をguitarheroに書き換える
cout << bocchi << endl;//ぼっちの情報が書き換えられている
理解するうえで大事なのが、一旦アドレスが示す場所の値を取得する機能と切り離して考えることです。二つとも似てはいますが別物です。これを一緒にして考えてしまうと理解が追い付きません。自分もこれでかなり苦戦しました。
説明に戻りましょう。二行目にて出現したポインタ変数。 型の後ろに*を付けることでポインタ変数を宣言することが出来ます。string* locationといった感じです。例えるなら場所を覚えられる変数「location」に情報"押し入れ"を、&を使って変数のアドレスを示すことで格納されています。コードでは16進数、格納されている場所が出力されていますね。
locationは場所を格納しているので二つ目の機能としての*を使ってアドレスが示す場所の値を取得できます。4行目を例えると「押し入れの人の情報は?」、前で見た回りくどいコードと同じ意味になりますね。
取得ができるだけではありません。値の変更も出来るのです。5行目を見てください。「押し入れの人の情報を"guitarhero"に書き換えて」といったところです。これこそがC系統特有の機能ではないでしょうか。ポインタ変数を使って別の変数の値を変更できてしまうのです。6行目でbocchiの情報がguitarheroに変わっていることが分かります。
最強機能
これだけではまだあまりメリットが感じられないかもしれませんが、この機能を応用した関数が最強です。見てみましょう。
#include <iostream>
using namespace std;
void change_attribute(string* starry){//ポインタ変数を使った関数
*starry = "Member of Kessoku band";//ポインタ変数starryが示すアドレスにある情報を書き換える
}
int main(){
string kita = "extrovert";//喜多ちゃんを召喚(?)、陽キャだね
change_attribute(&kita);// string* starry = &kita と考えよう
cout << kita << endl;//変わってる
}
実行結果
何が起きたのでしょうか。見てみましょう。今回は喜多ちゃんに出演(?)してもらいます。
main関数を見てください。まずは喜多ちゃんを召喚(?)し、情報として英語で言う陽キャにあたるextrovertを与えます。二行目の関数change_attributeを実行したら、情報がextrovertから"Member of Kessoku band"に変わりました。なぜでしょう。
これは自分の語彙力では説明が少し難しいのですが、前でのポインタ変数を使って別の変数を書き換えることが出来るという特徴を応用した機能です。関数での引数に何かを渡すのは変数の代入とそこまで変わらないので、string* starry = &kitaと考えれば分かりやすいと思います。前で出した法則に従って読み解いたら、これで引数に示した変数の値を読み取るだけでなく、書き換えることが出来ることが分かると思います。実質複数の戻り値を設けることが出来てしまうのです。
このように、関数の引数にポインタ変数を使うことをポインタ渡しと言います。逆に普通に使う引数は値渡しと言います。覚えておきましょう。後でメリットなどをまとめて説明します。
もっとある
ポインタ渡しだけでなく、もう一つ使用頻度の高い使い道があります。ポインタは配列との相性がかなり良いのです。コードを見てみましょう。
string kessoku_band[4] = {"Kita","Ryo","Bocchi","Nijika"};//宣言する
string* ptr = kessoku_band;//ポインタstrにアドレスを代入
cout << kessoku_band << endl;//実はアドレスが出力される
cout << ptr << endl;//同じ
for(int i=0;i<4;i++){
cout << *ptr << endl;
ptr++;//配列の要素は隣り合うという特徴を使う
}
実行結果
1行目で配列「結束バンド」を宣言し、メンバーを書き込みました。二行目がキーとなります。配列を全部出力しようとしたら謎の十六進数が出力されてしまった、そんな経験をしたことがある人はいると思います。実はこれの正体は配列の一番前の要素のアドレスなのです。そして、配列の要素は隣りあうという特徴を利用して、ポインタを1ずつ足していくことで隣の要素を取得することが出来ます。この四人には隣にいてほしいですもんね(?)
C言語のChar型などで少なからず出番があるでしょう。
ポインタについてはかなり理解できたでしょう。では、まだ説明ができていない最後の機能に移りましょう。
参照変数
ポインタ変数と似たような機能として、参照変数というものがあります。これはC++でのみ使える機能です。見ていきましょう。前で出てきた機能と切り離して考えることを忘れずに!別の機能です!
string kita = "extrovert";//宣言
string& ikuyo = kita;//kitaに別名ikuyoを宣言
cout << ikuyo << endl;//別名ってだけなので変わらない
ikuyo = "maid";//郁代(kita)の情報をメイドに変える
cout << kita << endl;//変わってる
2行目にてまた化け物が出てきました。string&こそが参照変数というものです。参照変数は簡単に言うと変数に別の名前を付けることです。ここでは喜多ちゃんの別名(?)として郁代を宣言しています。
三行目を見てください。「喜多ちゃんの情報を教えて」と聞くのと「郁代の情報を教えて」と聞くのでは意味は変わりませんからね。そんなことしたら喜多ちゃんが死んでしまいますが目を瞑りましょう取得するだけでなく、値を変更するのも同じです。四行目では「郁代の情報をメイドに変えて」と言っているようなものですね。喜多ちゃんのことなので変数kitaの値が変わります。そんなことしたら(ry
ポインタと似たような機能を持ちながら簡潔になりましたね。
参照渡し
ポインタ渡しと同じで、参照変数を関数に応用することももちろんできます。コードを見てみましょう。
#include <iostream>
using namespace std;
void change_to_maid(string& person){
person = "maid";
}
int main(){
string kita = "extrovert";
change_to_maid(kita);
cout << kita << endl;
return 0;
}
ポインタ変数と同じように、string& person = kita;と考えると分かりやすいと思います。似たような機能を持ちながらより簡潔に、複数の戻り値を用意することが出来るようになります。これを参照渡しと言います。積極的に使いましょう。
値渡し、ポインタ渡し、参照渡しのどれがいい?
ここまで3つの渡し方を説明してきましたが、結局どれを使えばいいのでしょうか?
値渡し
メリット
コードが読みやすい
安全
デメリット
処理が重い
引数を入れた変数の値を変えられない
値渡しは変数の値をコピーして、そのコピーを関数内に入れることで成り立っています。コードが読みやすいのは言うまでもないと思います。ぼっちちゃんをコピーするので、どんなことをしても元のぼっちちゃんには被害はないですよね。なので安全です。クローンはどうなるんだって?しかし、コピーなので元のぼっちちゃんの情報を変えることは出来ません。
しかも、ぼっちちゃんをコピーするというどう聞いても面倒な処理を行うことになります。ただの数字などならあまり問題ないのですが、巨大な配列などになるとパフォーマンスに大きな影響を与えてしまいます。よってあまり使うべき手法ではないとよく言われます。
ポインタ渡し
メリット
処理が速い
元の変数の値を変更できる
デメリット
コードが読みにくくなる
メモリリークが起きやすい
値渡しよりも安全さに多少欠ける
嫌というほど説明したのであまり説明する必要はないと思いますが一応。ポインタ渡しはアドレス、つまり住んでいる場所(?)を教えるだけなので値渡しのような手間はありません。元の変数の変更も出来るのはメリットではありますし、逆にデメリットでもあります。ぼっちちゃんの情報をそのまま変えられちゃいますからね。皆さんも痛感したであろうコードの読みにくさがあります。メモリリークについては自分で調べてください(汗)
参照渡し
メリット
処理が速い
元の変数の値を変更できる
コードが簡潔
デメリット
値渡しよりも安全さに多少欠ける
こいつは結構優秀ですね。完熟マンゴー(ぼっちちゃんの変なあだ名)を変更して、といった感じです。コピーも作らず、教えるのは名前だけなので高速で、さらにコードも簡潔にまとまります。あえてデメリットを上げるとしたら変数を書き換えられるのが少しデメリットになることがあるぐらいです。
終わり
他にも機能は色々ありますが、この記事はここで終わりにしたいと思います。少しでも理解が進むと幸いです。
ぼっちちゃんは変形したりするから説明に都合がいいと思ったけど正直別キャラでも良かったな、あとQiitaで書けよってツッコミも禁止ね
なんでQiitaじゃないかというと、出来るだけフレンドリーな書き方になっているからですね、ゴリゴリプログラミングの話するならQiita使ってます
この記事が気に入ったらサポートをしてみませんか?