いよいよポインタです。今回は参照でできた事をポインタで試してみましょう。でも別段難しいってことはありません。ちょっと使い方が違うくらいです。
|
ポインタとは
ポインタも参照と同じく、何かを指し示すものという意味です。学校の先生が使う銀色の伸び縮みできる棒やレーザー光線(ドットサイト)のことを「ポインタ」といいます。C言語のポインタも、これらと同じ、ある一点を指し示す機能を持ちます。
ポインタも他の変数を表す変数です。しかし、ポインタは参照ほど便利ではありません。想像するとしたら、水源を指し示す「住所」といったところでしょう。
参照の場合には、ホースが水源に直接継ながっていたので、あたかも「ホースが水源そのもの」のように扱えました。 |
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
前回の参照のプログラムと比べるとかなり違っていることが分かると思います。まずポインタの宣言が、参照と違います。
ポインタする変数の型 *ポインタの変数名 = &ポインタする変数;
ポインタでは、型の隣に*を入れます。参照は&でした。
作製されたポインタは、指し示す先の変数とまったく同じにはなりません。ポインタはあくまで住所を格納する変数です。そのため、そのままでは指し示す先の変数を操作することができません。
このように使い方は違いますが、参照と同じく一方の値を変えれば、もう一方の値も変わる、つまりポインタも指し示す変数がそこにあるかのように使えるというわけです。ポインタは遠回りながらも、参照同様、指し示す変数と一心同体になれるわけです。ただその使い方が面倒だということです。
ポインタは参照よりもフレキシブルです。そのため、参照ではできないことがポインタではできてしまいます。
以上を見れば分かるとおり、参照との違いは、「ポインタと指し示す先の変数の間に溝がある」ということです。参照はまさに一心同体でした。が、ポインタはいくつかの演算子を使用しなければなりませんし、とっかえひっかえもできます。ポインタのフレキシブルさ、危うさが感じ取れればと思います。
さらに、ポインタを参照のように、関数の引数で使用してみましょう。 |
ポインタを関数の引数で使う
ポインタを参照の代わりに引数で使ってみます(このときのようにポインタを使って渡すときも「参照渡し」と言うことがあります)。ほとんど参照と同じ、いくつかの演算子が付いているだけです。 |
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の使い方があります。 |
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. |