第22章 call itself 2

 今回も、前回に続いて再帰関数のお話です。静的内部変数と自動変数の違いと、引数と戻り値の引継についてのお話です。


 今回の要点です。


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


 第1部第67章でやった静的内部変数のことを思い出してみましょう。

 静的内部変数は、関数をいつ、どこで、何回呼んでも同じアドレスのメモリを使っている、というものでした。平たく言えば、変数は1つしか作られないということです。

 これに対して、自動変数は関数を呼ぶたびにアドレスが変わりました(同じ事もありますが)。平たく言えば、変数は関数に入るたびに作られるということです。

 このことを踏まえると、こんなことができるわけです。

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

void Func()
{
    static const int INIT = 5;
    static int i = INIT;
    cout << i << endl;
    i--;
    if(i > 0)
        Func();
    else
        i = INIT;
}

int main()
{
    Func();
    Func();

    return 0;
}
5
4
3
2
1
5
4
3
2
1

 前回のグローバル変数 i を、静的内部変数にしました。

 先ず、i は INIT によって初期化されます。実は、これは初めて関数に入ったときに1回だけ行われるということになっているのですが、実際はプログラムの初めと考えて構いません。ここではコンストラクタがないからです。コンストラクタがあれば、それが呼ばれたか、呼ばれていないかという大きな違いがありますね。

 で、後は前回と同じ事をやります。やはり中で Func を呼ぶわけですが、呼ぶ前の i と呼んだ後の i は同じ i です。ということで、前回と同じような結果になるわけです。

 最後、関数を終えるときに i を INIT に戻しておきます。もうこれ以降に中で Func を呼ぶことはないので問題ありません。

 もし、これを自動変数にしたらどうなるでしょうか?

実行結果
5
5
5
5
5
5
:
:
:
(エラーが出るまで続く)

 関数に入る毎に i が作られます。その時さらに i は INIT で初期化されてしまうので、無限ループになってしまいます。

 このように、再帰関数の結果には内部変数が静的かどうかが大きく影響します


 そして、関数の内部で使える変数といえば、もう1つありました。そうです。仮引数です。

 仮引数も自動変数と同じく、関数を呼ぶたびに新しい変数が作られます。しかし、この利用価値はとても大きいです。次のプログラムを見て下さい。

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

void Func(int nCount)
{
    cout << nCount << endl;
    if(nCount > 1)
        Func(nCount - 1);
}

int main()
{
    Func(3);
    Func(7);

    return 0;
}
3
2
1
7
6
5
4
3
2
1

 先ず、nCount を表示します。次に、これが1より大きければ Func を呼ぶわけですが、このとき nCount - 1 を渡しています。つまり、呼ばれるたびに nCount は1つずつ小さくなっていくのです。

 nCount 自体は Func が呼ばれるたびに別の変数を指すのですが、nCount を実引数に取ることで値を引き継ぐことが出来るのです。

 このことを利用すると、階乗を求める関数というのも作れますね。階乗というのは、数学でn!と表し、1からnまでの整数を掛けた答になります。

 上で表示したのは最初に入れた数から1までの数でした。これをうまく使うことができれば、階乗が求められそうです。

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

int Factorial(int num)
{
    if(num > 1)
        return Factorial(num - 1) * num;
    else
        return 1;
}

void InputAndOutput()
{
    int num;

    cout << "何の階乗を求めますか? > " << flush;
    cin >> num;

    cout << num << " の階乗は "
         << Factorial(num) << "です。" << endl;
}

int main()
{
    InputAndOutput();
    InputAndOutput();

    return 0;
}
何の階乗を求めますか? > 3
3 の階乗は 6です。
何の階乗を求めますか? > 10
10 の階乗は 3628800です。

 今度は戻り値も利用してます。簡単なので、3を入れたときの動作を例にとって考えてみます。

 先ず、num(1) は3です。(カッコに入っているのは、何回目の Factorial の num であるかです。)これは1よりも大きいので、Factorial をまた呼び出します。このとき、num(2) は num(1) - 1 なので、になります。

 で、やはりこれも1より大きいので、Factorial をまた呼び出します。このとき、num(3) は num(2) - 1 なので、になります。

 すると、ここで1以下になったので、Factorial を呼ばずに1を返します

 そして、2回目の Factorial の呼び出しに戻りました。ここで、次は Factorial(num(2) - 1) * num(2) を返します。Factorial(num(2) - 1) の結果は1でした。そして num(2) は2でした。なので、この2回目の Factorial は1×2を返します

 そして、いよいよ1回目の Factorial の呼び出しに戻りました。ここで、最後に Factorial(num(1) - 1) * num(1) を返します。Factorial(num(1) - 1) の結果は1×2でした。そして num(1) は3でした。なので、結局この1回目の Factorial は1×2×3、つまり3の階乗を返すことになります。

 このようにすれば、再帰関数を使って階乗を求める関数が作れます。引数だけでなく、戻り値も引き継いでいくことができるわけですね。


 しかし、これらの関数は相変わらず for 文を使えば簡単に作ることができます。

int Factorial(int num)
{
    int nFactorial = 1;
    for(int i = num; i > 1; i--)
        nFactorial *= i;
    return nFactorial;
}

 次回こそは、再帰関数を使った方が便利なことをやってみたいと思います。


 それでは、今回の要点です。


 では、次回まで。


第21章 call itself | 第23章 call itself 3

Last update was done on 2000.8.25

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