参照変数
- 参照の宣言 : 「 参照先の型& 参照変数名 = 参照先の変数; 」
- 参照変数は宣言と同時に代入する必要がある。初期化で必ず参照先の変数を指定する必要がある。参照とは「他の変数を指し示す変数」ですので、その参照先の変数がすでに存在している必要があります。参照先を用意せずに参照だけ宣言すると、コンパイルエラーが発生します。
- 代入された参照変数は、元の変数の別名として機能します。その意味でも参照はポインタとよく似ている。
- 参照変数には "NULL" を代入することはできません(ポインタのように宣言するのみが出来ない。宣言と同時に代入する必要がある)。よって、引数の参照渡しにおいて、引数の値がNULLになることはない(ポインタのようにNULLチェック不要)。
- 参照は参照先を変更できません。一度参照を宣言したら、参照先を変えることは出来ない。そもそも参照先を変更するための書き方が存在しない。
- 参照は参照先アドレスが記録されているメモリ・アドレスを獲得出来ない。そもそもそのための書き方が存在しない。
- 参照変数は、参照先の変数とまったく同じになります。一方の値を変えれば、もう一方の値も変わります。
- 参照変数を変数に代入すると(ディープ)コピーされる。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
string a = "123"; string &b = a; //ベーシックな利用法 string c = b; //変数cに(ディープ)コピーされる。 b += "abc"; //aとbが変更される cout << a << endl; //"123abc"が表示 cout << b << endl; //"123abc"が表示 cout << C << endl; //"123"が表示 c += "xyz"; //cのみが変更される cout << a << endl; //"123abc"が表示 cout << b << endl; //"123abc"が表示 cout << C << endl; //"123xyz"が表示 |
1 2 3 4 5 6 7 8 9 10 11 |
int aData=100; int* aPointer = &aData; int& aReference = aData; // ポインタはポインタ変数のアトレスになる cout << &aPointer << endl; //100を表示 cout << aPointer << endl; //aDataアドレスを表示 // 参照は参照先のアドレスになる cout << aReference << endl; //100を表示 cout << &aReference << endl; //aDataのアドレスを表示。&aDataと同じ値。 |
参照渡し
- 引数の型に&を入れる。
- 値渡しのように、引数の受け渡しにコピーが発生しない。そのため、引数のコンストラクタやデストラクタは呼び出されない。
- 関数内での変更が呼出し側に反映される。
- 関数内で変更しない場合はconst属性を付ける。
- 参照渡しの変数をローカル変数に代入すると(ディープ)コピーになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
string mstrData1; string& mstrData2 void func(string& pstr) { mstrData1 = pstr; //値のコピー mstrData2 = pstr; //参照のコピー pstr += "abc"; } int main(void) { string a="123"; func(a); cout << a << endl; ; //"123abc"が表示される cout << mstrData1 << endl; //"123"が表示される cout << mstrData2 << endl; //"123abc"が表示される } |
参照の戻り値
- 呼び元で破棄されたオブジェクトを参照しているダメな例
- 関数内のローカル変数の参照を返すと、この変数は関数が終了すると削除されるので、返された参照を使ってアクセスすれば、その変数は存在しないので例外が発生する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
string& Func() { string a = "123"; string& b = a; return b; } int main(void) { string& a = Func(); a += "abc"; //例外が発生(コンパイルはOK) string b = Func(); b += "abc"; //例外が発生(コンパイルはOK) } |
- ポインタで戻すダメな例
- 問題ないコードだけど使用者にメモリ解放の責任を負わせる
1 2 3 4 5 |
string* Func() { string *str = new string("test"); return str; } |
- 参照の戻り値を変数に代入すると(ディープ)コピーされる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
string mstrData1; string& mstrData2 string& Func(string& pstr) { pstr += "abc"; return pstr; } int main(void) { string a = "123"; string& b = func(a); //aとbは同じもの string c = func(a); //変数cはディープコピーされ、cはa,bとは異なるもの。 cout << a << endl; //"123abc"が表示される cout << b << endl; //"123abc"が表示される cout << c << endl; //"123abc"が表示される b += "xyz"; cout << a << endl; //"123abcxyz"が表示される cout << b << endl; //"123abcxyz"が表示される cout << c << endl; //"123abc"が表示される。cは別物なので、影響をうけない。 } |
値渡し
1 2 3 4 5 6 7 8 9 10 11 |
string TestFunc(string pstr) { pstr += "あ"; return pstr; } int main() { string str = "おえいう"; str = TestFunc(str); } |
- 関数の呼出しでデータを渡す引数:pstr が作成され、このときにstringのコンストラクタが呼び出されます(正確には「コピーコンストラクタ」。データのコピー専用のコンストラクタ)。
- 関数が終了する直前に戻り値が新規に作成され、コンストラクタが呼び出されます。関数が戻り値と入れ替わるように、戻り値のために新しく変数が作成される。これを一時オブジェクトといいます。この一時オブジェクトを__tempとすると、str = __temp; という風に関数が置き換えられ、このとき__tempのコピーコンストラクタが呼び出されて、returnの右辺値のpstrがコンストラクタ引数として渡されます。つまりreturnの右辺値がそのまま使われるわけではなく、わざわざ一時オブジェクトを作成する。
- その後、関数内の変数がすべて削除され、このとき引数pstrのデストラクタも呼び出されます。
- さらに、一時オブジェクトの寿命はその行のみなので、str = __temp;の次の行に移った瞬間、__tempのデストラクタが呼び出され、削除されます。
- まとめると、引数のコンストラクタ->一時オブジェクトのコンストラクタ->引数のデストラクタ->一時オブジェクトのデストラクタという順番でコンストラクタとデストラクタが呼ばれてしまうので、時には大きな負担になってしまいます。
参考にさせて頂いたページ
・C++でstd::stringをどう返すべきか Part1