参照とポインタ

 いよいよポインタです。今回は参照でできた事をポインタで試してみましょう。でも別段難しいってことはありません。ちょっと使い方が違うくらいです。

ポインタとは
 ポインタも参照と同じく、何かを指し示すものという意味です。学校の先生が使う銀色の伸び縮みできる棒やレーザー光線(ドットサイト)のことを「ポインタ」といいます。C言語のポインタも、これらと同じ、ある一点を指し示す機能を持ちます。

参照とポインタ  ポインタも他の変数を表す変数です。しかし、ポインタは参照ほど便利ではありません。想像するとしたら、水源を指し示す「住所」といったところでしょう。

 参照の場合には、ホースが水源に直接継ながっていたので、あたかも「ホースが水源そのもの」のように扱えました。
 ところが、ポインタの場合にはそうはいきません。ポインタには「住所」が書かれているだけです。たとえば「○○町1−2−3」とかいう風にです。火事になったときにそこにあるであろう放水栓へと行って水を汲むことになるわけです。つまり、ポインタは「確かに指し示すものを意味しているが、指し示すものそのものではない」のです。
 実際にプログラム上で使ってみて、ポインタとどのように違うのか調べてみましょう。


	int i1 = 3;

	// この変数のアドレス。
	TRACE( "&i1: %d\n", &i1 );

	// ベーシックな使用法。
	int *pi1 = &i1;
	TRACE( "i1: %d, *pi1: %d\n", i1, *pi1 );
	// i1: 3, *pi1: 3

	++i1;
	TRACE( "i1: %d, *pi1: %d\n", i1, *pi1 );
	// i1: 4, *pi1: 4

	++*pi1;
	TRACE( "i1: %d, *pi1: %d\n", i1, *pi1 );
	// i1: 5, *pi1: 5

	// 参照にはできなくて、ポインタにはできること。
	int *pi2;	//ポインタだけの宣言。
	TRACE( "pi2: %d\n", pi2 );	//アドレスのチェック。
//	*pi2 = 3;	//実行時に例外発生。
	// 例外処理 (初回) は 06.exe にあります: 0xC0000005: Access Violation。

	int i2 = 6;
	pi1 = &i2;	//参照先の変更。
	TRACE( "i1: %d, *pi1: %d\n", i1, *pi1 );
	// i1: 5, *pi1: 6
	

 前回の参照のプログラムと比べるとかなり違っていることが分かると思います。まずポインタの宣言が、参照と違います。

ポインタする変数の型 *ポインタの変数名 = &ポインタする変数;

 ポインタでは、型の隣にを入れます。参照は&でした。
 ところが右辺も違います。ポインタでは指し示す先の変数の左にが付いています。これは参照で使うものとはまったく別物で、変数のアドレスを取り出すための演算子です。
 先ほど説明したとおり、ポインタは住所を格納します。たとえば「あそこの給水栓」にはもちろん「○○町1−2−3」という住所が割り振られています。この住所を取り出すことができるのが&というわけです。実際にその「住所」を取り出して表示しているので、見ておいてください。ウィンドウズ95では32ビットの整数値です。

指し示す変数へのアクセス  作製されたポインタは、指し示す先の変数とまったく同じにはなりません。ポインタはあくまで住所を格納する変数です。そのため、そのままでは指し示す先の変数を操作することができません。
 ポインタに「指し示す先の変数」を意味させるには、ポインタの前に*を付ける必要があります。これがあればポインタは指し示す先の変数と同じになり、なければ単なる住所を格納する変数になるというわけです。

 このように使い方は違いますが、参照と同じく一方の値を変えれば、もう一方の値も変わる、つまりポインタも指し示す変数がそこにあるかのように使えるというわけです。ポインタは遠回りながらも、参照同様、指し示す変数と一心同体になれるわけです。ただその使い方が面倒だということです。

ポインタならできること  ポインタは参照よりもフレキシブルです。そのため、参照ではできないことがポインタではできてしまいます。
 ポインタは「指し示す先の変数」を指定しなくても宣言できてしまいます。ふつうの変数と同様、初期化されてないポインタは、むちゃくちゃな値が入っています。この値、つまり「住所」はでたらめなので、この住所を使ったらもちろんまずいことになります。給水栓から水を汲みに行ったつもりが、全然関係ない家の金魚鉢から汲むことになってしまうかもしれません。それはもちろん迷惑な話なので、実行時に例外が発生します
 また、ポインタは指し示す先を自由に変えられます。ある段階まであっちの給水栓を使っていた、けど効率を考えてこっちの給水栓にしよう、といったフレキシブルな使い方ができるのが、ポインタです。

 以上を見れば分かるとおり、参照との違いは、「ポインタと指し示す先の変数の間に溝がある」ということです。参照はまさに一心同体でした。が、ポインタはいくつかの演算子を使用しなければなりませんし、とっかえひっかえもできます。ポインタのフレキシブルさ、危うさが感じ取れればと思います。

 さらに、ポインタを参照のように、関数の引数で使用してみましょう。

ポインタを関数の引数で使う
 ポインタを参照の代わりに引数で使ってみます(このときのようにポインタを使って渡すときも「参照渡し」と言うことがあります)。ほとんど参照と同じ、いくつかの演算子が付いているだけです。


void Func()
{
	CString cStr = "おえいう";
	TestFunc( &cStr );	//ここが違う!!
	TRACE( "cStr: %s\n", (LPCTSTR)cStr );
}

void TestFunc( CString *p_pcStr )
{
	*p_pcStr += "あ";
	TRACE( "Len: %d\n", p_pcStr->GetLength() );
	TRACE( "Len: %d\n", ( *p_pcStr ).GetLength() );
	// この2行は同じ意味です。
	return;
}
	

 これも参照の時のコードと比べてみてください。まず関数を呼び出すとき、引数にを付けてることに注意してください。これは先ほど説明したとおり、アドレスを渡す必要があるからです。

 次に呼び出される関数を見てみましょう。引数にが付いていますね。これがポインタの証です。
 ポインタが指し示す先にアクセスするためには、ポインタの頭にを付ける、これはクラスのオーバーロードされた演算子にも当てはまります。
 また、クラスや構造体のメンバにアクセスする時には、*を付ける必要はありませんが、その代わり、普段使う.(ピリオド)ではなく->を使います。その下の行にあるように、この演算子に*の意味も含まれていると考えてください。

やっちゃまずいこと
 ポインタも、参照と同じく指し示す先がいつの間にかなくなっているというミスをする可能性があります。


void Func()
{
	CString *pcStr = TestFunc();
	*pcStr += "いうえお";	//例外発生。
}

CString *TestFunc( )
{
	CString cStr = "あ";
	return &cStr;	//警告発生。
	// warning C4172: ローカル変数またはテンポラリのアドレスを返します。
}
	

 このようにポインタを戻り値に使うと、関数内の変数を指し示すポインタを返すことができます。とうぜんこの変数は関数が終了すると削除されてしまうので、先ほどの「初期化してないポインタを使ったとき」と同じようにまったくデタラメなアドレスを使ってしまい、アクセス違反が起きてしまうというわけです。今度は目薬かもしれません。
 ただし、参照と同じように戻り値にポインタを使うことはありますし、むしろポインタはそういった使い方がかなり多いと言えます。が、それはまだ先の話。今は参照との使い方の違いについて認識しておいてください。

 参照と同じく、もうひとつ問題があります。指し示す変数の中身を不用意に変えかねないという問題は、ポインタでも発生します。ここで活躍するのは、参照と同じく「const」です。

constを使う
 ポインタでのconstの使い方は、参照の時とまったく同じです。


void Func()
{
	CString cStr = "おえいう";
	TestFunc( &cStr );	//ここが違う!!
	TRACE( "cStr: %s\n", (LPCTSTR)cStr );
}

void TestFunc( const CString *p_pcStr )
{
//	*p_pcStr += "あ";	//コンパイルエラー。
	// error C2678: 二項演算子 '+=' : 型 'const class CString' 
	//の左オペランドを扱う演算子は定義されていません。(または変換できません)
	TRACE( "Len: %d\n", p_pcStr->GetLength() );	//こちらは通ります。
	return;
}
	

 このように、参照と同じく引数の型の前にconstを付けるだけで、指し示す先のデータは操作できなくなります。また、前回説明したようにCString::GetLength()int GetLength( ) const;と最後にconstが付いているので問題なく呼べます。この辺は参照もポインタも同じですね。当然、constいつ使うべきかも同じです。

 また、ポインタでは参照では必要のないconstの使い方があります。
 参照は指し示す変数を変えられませんが、ポインタは変えられます。ポインタを使う時でも参照のように変えたくない、という場合には指し示す先に対するconstを指定することができます。


	int i1 = 2;
	int i2 = 3;

	int *const pi1 = &i1;
	pi1 = &i2;	//コンパイルエラー。
	// error C2166: 左辺値は const オブジェクト
	//に指定されています。
	

 このようにのあとにconstを付けることで、ポインタが指し示す先を変更できなくなります。と言っても、実際にはあまり使うことはないので頭の片隅くらいに入れておいてください。

ポインタと参照の違い
 と、ここまでで「参照でできること」をポインタを使って試してみました。以上を表にまとめてみましょう。

  参照 ポインタ
宣言 型の後に&
参照先必要
型の後に*
ポインタ単体で可能
指し示す変数
のセット
変数そのまま 変数の前に&
指し示す変数
の変更
不可能 可能
指し示す先への
アクセス
参照そのまま ポインタの前に*
指し示す先の
メンバにアクセス
.(ピリオド) −>

 おそらく「がーっ、ポインタってめんどくて危険なだけやん!!」と思われることでしょう。実際、多くの部分はポインタではなく参照を使う方が簡単で安全でしょう。
 でもそれなら、ポインタはなくなってることでしょう。でも、ポインタはC++でも欠かせない存在です。それは、ポインタにしかできないことがあるからです。

参照にできないこと、ポインタにできること
 ポインタと参照の違いは、指し示す先の変数との溝です。この溝が存在することで、ポインタは参照よりフレキシブルな存在となっています。そして、そのフレキシブルさを利用する必要の出てくる場面が少なからずあるのです。

 これから3回、このフレキシブルさについて見ていきましょう。まずは配列を、次にその応用編になる文字列操作、最後に変数の動的確保を解説します。
 まだ結構長い道のりです。ゆっくりと行きましょう。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.