第8章 アドレスを返す関数

 前回、代入式は「代入される変数の参照を返す感じだ」と言いました。今回は、まさしく参照を返す関数について話します。


 では、今回の要点です。


 では、いってみましょう。


 前回の代入式の動作を、関数を使ってエミュレートすることを考えてみましょう。例として、int 型の変数に対する += をエミュレートしてみます。

 先ず、引数はどうしますか? そうですね。左辺と、右辺の2つになりますね。しかし、型はどうしましょう? これはちょっと置いておきます。

 次に、戻り値の型はどうしますか? 代入式は左辺にある変数の代わりに使えないといけないので、左辺のアドレスを返すようにしましょう。となると、左辺の代わりとなる引数はポインタにしなければいけないことが分かるでしょう。

 なぜかは分かりますか? 仮引数と実引数は別の変数だからですね。関数にそのまま渡してしまえば、そのアドレスはもう関数内からは分からなくなってしまうのでした。これがよく分からないという人は第1部第34章を読み返すことをすすめます。

 以上のことから、+= 演算子をエミュレートした関数 AddAndAssign は次のようになります。

int* AddAndAssign(int* pnLeft, int nRight)
{
    *pnLeft = *pnLeft + nRight;
    return pnLeft;
}

 += 演算子のエミュレートということで、+= をわざと使わないようにしました。

 関数の動作自体は簡単で、足し算をして、「左辺」のアドレスを返しているだけです。実際にこの関数を使うときは、

AddAndAssign(AddAndAssign(&a, b) , c);
cout << *AddAndAssign(&a, b) << endl;

のようにします。これらは

(a += b) += c;
cout << (a += b) << endl;

をエミュレートしています。

 ここで実際にやった通り、アドレスを返す関数では返すアドレスにある変数の種類と寿命に注意します。

int* Func(int a)
{
    return &a;
}

int* Func()
{
    int a = 0;
    return &a;
}

のようなことは絶対にしてはいけません。この理由が分からない人は第1部第68章を読んでみて下さい。それでもよく分からない人も、エラーを恐れずにいろいろ試してみて下さい。そのうちだんだん分かってくるようになるでしょう。


 と、+= のエミュレートを行ってみましたが、* や & をつけなければいけないのはちょっと不格好です。実際に += では * も & もつけないのですから、ここまで真似して初めてエミュレートが完成したと言えるでしょう。

 何か、前にもこういう悩みがありました。こういうときは何を使えばいいのでしたか? そうですね。参照です。

 第1部第38章で「参照とポインタは内部的には全く同じ動作を行っている」と言いました。ということは、上のポインタで書いてある部分を参照に直すだけで良さそうです。

 となると、引数の型だけでなく戻り値の型も参照型になります。これって、きちんと動くのでしょうかね?

 と、考えるだけではいつまでたっても推測の域を出ません。ということで、やはりプログラムを作って確かめてみましょう。

プログラム実行結果
// RetRef1.cpp
#include <iostream.h>

int* AddAndAssign(int* pnLeft, int nRight)
{
    *pnLeft = *pnLeft + nRight;
    return pnLeft;
}

// 上の関数と引数の型が違うので
// オーバーロードできます(第1部第64章参照)
int& AddAndAssign(int& rnLeft, int nRight)
{
    rnLeft = rnLeft + nRight;
    return rnLeft;
}

int main()
{
    int a;

    a = 0;
    (a += 3) += 5;
    cout << "演算子  : " << a << endl;

    a = 0;
    AddAndAssign(AddAndAssign(&a, 3), 5);
    cout << "ポインタ : " << a << endl;

    a = 0;
    AddAndAssign(AddAndAssign(a, 3), 5);
    cout << "参照   : " << a << endl;

    a = 8;
    cout << "演算子  : " << (a += 2) << endl;

    a = 8;
    cout << "ポインタ : " << *AddAndAssign(&a, 2) << endl;

    a = 8;
    cout << "参照   : " << AddAndAssign(a, 2) << endl;

    return 0;
}
演算子  : 8
ポインタ : 8
参照   : 8
演算子  : 10
ポインタ : 10
参照   : 10

 (a += 3) += 5; と cout << (a += 2); で実験してみました。演算子を使ったときの結果と、ポインタを使った関数の結果と、参照を使った関数の結果、全てが同じになりました。完璧にエミュレートできてますね。

 このように、参照も戻り値にすることができます。その動作はポインタを使ったときのそれと全く同じです。違うのは文法だけです。

 ただ、ポインタを使うと簡単に無効なアドレス、例えば NULL を渡すことができてしまいます。*(int*)NULL と書くと参照でも同じことができますが、こんなことはしないで下さい。これは戻り値も同じことですね。

 このことは欠点であるときもあれば、利点であるときもあります。なので、一概にどちらがいいかは言えないでしょう。うまく使い分けて下さい。


 では、今回の要点です。


 それでは、次回まで。


第7章 代入文のリサイクル | 第9章 関数ポインタ

Last update was done on 2000.8.5

この講座の著作権はロベールが保有しています