こんにちは、ナナです。
C言語の理解できない機能としてランキング上位に位置するであろう、「ポインタのポインタ」に関して解説していきます。
「ポインタのポインタ」ってC言語の中でちょくちょく出てくるんです。ですので、やっぱり理解しておきたい機能の1つなんですね。
しかし、この機能がなかなか理解できない方って結構多いんです。
ポインタはわかったけど、「ポインタのポインタ」ってどういうこと?「ポインタのポインタ」って言葉としておかしくない?意味わかんないんだけど‼
と多くの人が戸惑います。しかし、ポインタを正しく理解していれば、実は簡単なんです。
理解するために必要なのは「ポインタのポインタ」を理解するためのイメージと、定義の意味を正しく知ることです。
ポインタの全貌を学びたい方は『C言語 ポインタを使いこなせ【身に付けるための9の極意】』の記事から順に読むことをお勧めします。
本記事では次の疑問点を解消する内容となっています。
では、ポインタのポインタを学んでいきましょう。
ポインタのポインタを理解するためのイメージ
はい、はい、はーーーい。僕の出番がやってまいりましたっ!
なるほど「ポインタのポインタ」、2つ繰り返してますね。つまり、反復王子の僕の出番ってことですねっ!
ずばり、「ポインタのポインタ」は反復処理ってことですよね?
うん、違うよ。「ポインタ」って言葉が2つ繋がってるだけで反復処理とはあんまり関係ないね。でも、繰り返しているという状況としては遠くないね。
じゃあ、「ポインタのポインタ」を理解するために、まずは「ポインタ」のおさらいをしようね。
「ポインタ」を理解するにはイメージが大切です。「ポインタのポインタ」もイメージとして理解することです。
では、「ポインタのポインタ」をイメージの図で理解していきますよ。
まずは「ポインタ」をおさらいしよう!これ大事!
もう一度、ポインタを軽くおさらいしましょう。このイメージがすごく大事なんです。
「変数」に対して遠距離アクセスしたい場合は、「ポインタ変数」を使用しました。関係性は次のようになります。
「変数」を的、「ポインタ変数」を弓矢とし、遠距離アクセスを可能にしました。これが変数とポインタ変数の関係性ですね。
つまり、
「変数」を参照しているのが「ポインタ変数」という関係性になります。
「ポインタのポインタ」を理解するには、ポインタのイメージが基本となります。これが理解できていれば「ポインタのポインタ」もイメージできます。
「ポインタ」と「ポインタのポインタ」の関係性を図解
「ポインタのポインタ」が何かを知るために、ここで視点と考え方を切り替えます。
「ポインタ変数」という変数も、変数の一種なわけです。であれば、「ポインタ変数に対して遠距離アクセスしたい!」というニーズがあってもよさそうです。
ここで皆さんに質問です!「ポインタ変数を的」として見た場合、弓矢はいったい何になるのでしょうか?
そうです!ここで登場するのが「ポインタのポインタ(ダブルポインタ変数)」なんです。
このように「ポインタ変数を的」としたときに、弓矢に相当するのが「ダブルポインタ変数」になります。
つまり、
「ポインタのポインタ」とは、「ポインタ変数を参照しているポインタ変数」ということです。
変数・ポインタ変数・ダブルポインタ変数の関係性のイメージ図
変数・ポインタ変数・ダブルポインタ変数の関係性は、まとめると次にものになります。
「的」と「弓矢」の関係性とは、着目している2つの変数によって変化することがわかります。
「ポインタ変数」は状況により、的にも弓矢にもなりえるとわかりますね。
ポインタのポインタの変数定義と理解するための正しい解釈
ほいほ、ほーーい。「ポインタのポインタ」が「ポインタ変数」を見て、「ポインタ変数」が「変数」を見る。誰かが誰かを見守ってるんですね…
僕のことは誰が見守ってくれるんでしょうか?老後が不安なんです。
これから頑張って見つけようね。
じゃあ、次は「ポインタのポインタ」の変数定義の解釈を理解しようね。複雑に見えるけど、ちゃんと理解すれば理にかなってるんだよ。
「ポインタのポインタ」変数の定義はルールは簡単ですが、正しい解釈を知らない方って結構多いと思います。
使い方を知っている方でさえ「じゃあ、どうしてこんな風に書くと思う?」 って聞いても、なかなか答えられません。
ダブルポインタ変数の定義方法
「ポインタのポインタ」は通称「ダブルポインタ」とも呼ばれます。なぜそう呼ばれるかは定義をみると一目瞭然ですね。
書き方
データ型 ** 変数名;
使用例
char ** ppnum;
long ** ppmoney;
変数名の頭に「pp」と付けているのは「ダブルポインタ」を示しているという意味です。名前の付け方は自由ですので、もちろん付けなくても大丈夫です。
ポインタ変数とダブルポインタ変数の定義方法の違いは「*」がもう一つ付くかどうかです。作り方は覚えてしまえば簡単ですね。
ダブルポインタ変数定義の正しい解釈
ポインタで必ず出てくる「*」、これが2つ連なることで多くの人が戸惑います。考えることを放棄して、とりあえず「*」を付ければよいと思っている方もいます。
しかし、「**」には意味とルールが存在します。「**」の意味をしっかりと理解しましょう。
もういちど、「ポインタ変数」と「ダブルポインタ変数」の定義を比較してみます。
この変数定義の解釈ですが、各部品を分離して差を比べてみましょう。下図左のように捉えるのは間違いであり、右側の見方が正しいです。
ダブルポインタという名称から「**」をくっつけるイメージを抱きますが、分離して解釈するのが正しいです。
この解釈は変数とポインタ変数のときに解説した内容と同じ内容です。
部品①
変数につけるラベル名を示す。皆さんが自由に名前を与えることができる。
部品②
部品①に対してのデータ型を示す。データ型をポインタにしたい場合は「*」を指定することにより、「ポインタ型」であることを示すことができる。
部品③
ポインタが参照する先のデータの「データ型」を示す。
「ダブルポインタ変数」において、部品③の参照先のデータ型が「ポインタ型」として定義されるということなんです。
つまり、「ポインタ変数」と「ダブルポインタ変数」の違いは、参照先メモリのデータ型が異なることしか違いがありません。
ポインタのポインタが登場するシーンとは
はーーい。ダブルポインタ変数の作り方と定義の意味はばっちりです!
で、で、で、で、これを使う場面が知りたいんでーす。それ知らないといざって時に使えないじゃないですかっ!
そうだね、いいこと言うね。
実践的に使用するケースを知らなければ活用できないよね。まずは、こんな時に利用するよっていうのを紹介しようね。
ダブルポインタ変数は、実際のプログラムの中ではそれほど多用されるわけではありません。
しかし、「これをしたい時は、ダブルポインタ変数がいるよね!」といった特定のシーンにおいてやはり出てくるので、しっかりと知識としては身に付けておく必要があります。
ダブルポインタ変数は、やはり関数の引数で登場する
まずは、「ポインタ変数」が出てくる代表的な例を示しましょう。
次のように関数の引数で登場しましたね。
仕事を依頼する側が保有している変数の設定を、別の関数へ依頼する場合に「ポインタ変数」が引数で登場します。
これが代表的なポインタの登場シーンです。
ダブルポインタ変数も同様のシーンでよく登場します。
「ポインタ変数」の番地の設定を、別の関数へ依頼する場合に「ダブルポインタ変数」が引数として登場します。
これが代表的なダブルポインタ変数の登場シーンです。ポインタ変数と一緒ですね。
ポインタのポインタを実践的に使用するシーンは?
ここまで解説したシーンとは「こんな場面で使われるよ」というものであり、実践的に使われるシーンをまだ解説してません。
ポインタ変数の番地の設定を、別の関数に依頼するってどんなシーンなの?
このシーンはあるにはあるのですが、現時点ではまだ紹介していない機能でよく使うんです。知りたい方は次の記事を参照してください。
C言語入門カリキュラムを進めている方は、気にせず続けてください。もう少し先の記事で実戦的に使用する場面を具体的に紹介します。
Q&A:「ポインタのポインタ」に関するよくある質問
ポインタのポインタを皆さん理解できましたか?質問どうぞ!
Q:ダブルポインタの「**」があるということは、ひょっとしてトリプルポインタなんてあるの?
はい、はい、はい。「ポインタ」には「ダブルポインタ」がありますね。ま、ま、まさか、ポインタに「トリプルポインタ」なんてものはないですよね?
僕は反復王子と配列王子の2冠を持っているんですよ、3つ目の冠を狙ってるんです。絶対にポインタには負けません!3冠王に僕はなるっ!
あるよ。使うかどうかは置いておいて、トリプルポインタは存在するんだよ!残念ながら、君は現時点でポインタに負けてるんだよ!
はい、トリプルポインタはありますっ!
C言語の文法的にはトリプルポインタの作成も、さらに先のクアドラプルポインタすら作成可能です。
次のプログラムは問題なくビルドが通ります。
#include <stdio.h>
int main(void)
{
// 変数定義
short num1 = 10;
// ポインタ変数の定義
short * pnum; // ポインタ変数
short ** ppnum; // ダブルポインタ変数
short *** pppnum; // トリプルポインタ変数
// ポインタの照準設定
pnum = &num1; // pnum --> num1
ppnum = &pnum; // ppnum --> pnum
pppnum = &ppnum; // pppnum --> ppnum
return 0;
}
こんな風に「*」を増やせばいいだけですね。
ただ、実践でトリプルポインタが使われているシーンを、私は見たことがありません。