というわけで参照です。参照は今回だけ、1回切りしか説明しませんが、たぶんそれだけで分かると思います。使い方とかは、簡単なんで。 |
参照の作り方
前回バケツリレーの欠点を説明しました。それを解決するのがホース、つまり参照です。 よく「この本の32ページ24行目を参照してください」とか言うように、「参照」とは何かを指し示すものという意味です。英語ではReferenceと呼ばれます。
具体的には、参照は他の変数を表す変数です。ホースを想像してみましょう。片側から水を入れれば、もう片方にも同じ量だけ水が出てきます。当然逆からでも、これは言えます。このようにホースを使えば、水源がホースの先まで来てるかのごとく使用できるわけです。 |
とりあえず使ってみましょう
というわけで、次のコードを試してみてください。 |
int i1 = 3;
// ベーシックな使用法。
int &ri1 = i1;
TRACE( "i1: %d, ri1: %d\n", i1, ri1 );
// i1: 3, ri1: 3
++i1;
TRACE( "i1: %d, ri1: %d\n", i1, ri1 );
// i1: 4, ri1: 4
++ri1;
TRACE( "i1: %d, ri1: %d\n", i1, ri1 );
// i1: 5, ri1: 5
//////////////////////////////////
// 以下、できないこと。
// int &ri2; //参照だけの宣言。
// 「error C2530: 'ri2' : 参照が初期化されずに宣言されています。」
// int &ri3 = 4; //定数値への参照。
// 「error C2440: 'initializing' : 'const int' から
// 'int &' に変換することはできません。」
int i2 = 6;
ri1 = i2; //参照先の変更(ただの代入)。
TRACE( "i1: %d, ri1: %d\n", i1, ri1 );
// i1: 6, ri1: 6
参照を宣言するときには、必ず次のような形になります。
参照先の型 &参照の変数名 = 参照先の変数;
普通の変数宣言に似ていますが、型の隣に&を入れる必要があります。
また、このとき必ず参照先の変数を指定する必要があります。参照とは「他の変数を指し示す変数」ですので、その参照先の変数がすでに存在している必要があります。
作製された変数は、参照先の変数とまったく同じになります。一方の値を変えれば、もう一方の値も変わります。つまりふたつの変数は、ホースによって常に一致するようになっているわけです。参照先の値がホースで送られ、参照に水が入れられればそれはホースで参照先に送られるというわけです。 |
参照渡し
データの中身ではなく参照を渡す方法を、「参照渡し」と呼びます。「値渡し」との違いは、ほんのちょっと。引数の型に&を入れるだけです。 |
void Func()
{
CString cStr = "おえいう";
TestFunc( cStr );
TRACE( "cStr: %s\n", (LPCTSTR)cStr );
}
void TestFunc( CString &p_rcStr )
{
p_rcStr += "あ";
return;
}
引数に参照を渡すということは、ホースを使ってスコープの壁を乗り越えるということです。上のコードを実行すれば分かるとおり、cStrとp_rcStrはまったく同じ変数として扱えます。つまり、このふたつの変数はホースで継ながっているのです!! そのため、参照を操作することで、本来スコープの外にある変数を操作できるというわけです。
これで、「バケツリレー」のみっつの問題をクリアできます。 |
やっちゃまずいこと
参照は、基本的に引数でのみ使用するのがいいでしょう。なぜなら、次のようなミスをする可能性があるからです。 |
void Func()
{
CString &rcStr = TestFunc();
rcStr += "いうえお"; //例外発生。
}
CString &TestFunc( )
{
CString cStr = "あ";
CString &rcStr = cStr;
return rcStr;
}
参照を引数に使わず戻り値に使うと、関数内の変数への参照を返すことができます。ところが、この変数は関数が終了すると削除されてしまいます。関数の外へと出て、返された参照を使ってこの「削除された変数」にアクセスすれば、もちろんその変数は存在しないので例外が発生します。
このコードの問題点は戻り値に参照を使うことです。実際には使用しても問題ない場合もありますが(例えばCString::operator +=()とか)、基本的に参照は、引数に使うのが一番安全で確実な方法と言えるでしょう。
また、もうひとつ問題があります。前述のみっつの問題のうち、「ふたつの変数が作製される」という問題がクリアされることで、新たな問題が出てくる場合もあるでしょう。
このように参照先を不用意に変えかねないという問題は、グローバル変数のように大きな問題になります。他の人が作った関数に何気なく変数を渡していたらいつの間にか値が変わっていた、なんてシャレになりません。 |
constを使う
参照には「参照先の変数のデータを変化させない」ことができます。そのためのキーワードがconst(「コンスト」もしくは「コンスタント」)です。 constは「定数」という意味です。「参照先の変数は定数だから変えちゃダメだよ」という宣言になるわけです。 constは次のように簡単に使えます。 |
void Func()
{
CString cStr = "おえいう";
TestFunc( cStr );
TRACE( "cStr: %s\n", (LPCTSTR)cStr );
}
void TestFunc( const CString &p_rcStr )
{
// p_rcStr += "あ"; //コンパイルエラー。
TRACE( "cStr: %s\n", (LPCTSTR)p_rcStr );
return;
}
このように、引数の型の前にconstを付けるだけで、参照先のデータを操作しようとするとコンパイルエラーが発生するようになります。ですが、操作しない限りはどんなことでもできます。
これに関連して、こんなconstの使われ方があります。CString::GetLength()のリファレンスを見ると、次のように書かれています。 |
constの使いどころ
このconstは、「値を返す」場合以外で引数に参照を使う場合には必ず付けるようにしましょう。つまり「コピーしたくない」という目的の時のみには、勝手にデータを書き換えないようにconstを付けてしまった方がいいというわけです。 これは、そういうクセを着けてしまいましょう。コードが増大・複雑化すると、隅々まで目が行き届かなくなってきます。そんなとき、constが役に立ちます。値が勝手に変えられてないかどうか、自分の代わりにコンパイラがチェックしてくれるのですから。
それに、constを付けることで、関数のプロトタイプ宣言を見れば、参照の引数がどういう意味合いのものかばっちり分かります。例えば、 |
|
というわけで、constを付けられる場合は、すべて付けてしまいましょう。
|
参照の使いどころ
順序が逆になってしまいましたが、「参照をいつ使うべきか」について解説しておきましょう。 参照は、実際には次回から解説する「ポインタ」というものを使用しています。ポインタは32ビットのデータです。これをふまえて考えてみましょう。
まず、引数を使って値を返したい時には、有無をいわさず参照を使用します。値渡しのバケツリレーではこれは無理ですから。 |
|
この分類と、constの使い方を組み合わせて、適切な引数型を使用してください。
|
「参照」に封じ込まれしもの
さて、以上で参照の解説は終わりです。簡単でしょう。ホント、簡単に使えます。実際、参照は他の多くの言語でサポートされ、とてもメジャーな存在です。ごくごく自然なものなのです。 ところが、実はC言語には参照はありません。参照が実装されたのはC++からです。 「ええっ、じゃあどうやって構造体渡すの? 値はひとつしか返せないの?」いえいえ、そんなことはないんです。Cでも参照と同じ機能が実現できるのです。そしてそれは参照以上に高い機能を持ち、フレキシブルであり、それ故危険な存在でした。参照は、その機能の中の一部を使いやすくしたものなのです。
その機能は、他の言語にはない能力を持ち、それゆえ「C言語」は他の言語とは一目置かれる存在になりました。しかし、その機能はあまりにも高機能すぎ、また、「ローレベル」、つまり根本的な機能のため、システムを根底から揺るがすほど能力を持っています。その危険度故「初心者キラー」の名を欲しいままにしています。そのあまりの難しさ、そして危険度に、機能の一部を封じ込まれたものが「参照」としてC++に登場したくらいです。
では、その姿を少しずつお見せすることにいたしましょう。 |
(C)KAB-studio 1998 ALL RIGHTS RESERVED. |