トラック7・🧩関数とスコープ

再帰の入口——自分を呼ぶ関数

読む目安 約13分・ゴール:再帰の2要素(停止条件と縮小)を理解し、階乗を再帰で書ける

アプリでこのトピックを学ぶ(無料・登録不要)

このトピックの要点

合わせ鏡をのぞくと、鏡の中にまた鏡が映り、その中にもまた鏡が映ります。関数も同じことができます。自分自身を呼び出す関数、それが再帰です。

教科書的な題材は階乗です。5の階乗は 5×4×3×2×1 ですが、これは「5 × 4の階乗」と言い換えられます。同じ形の、少しだけ小さい問題が中に隠れている——この構造が見えたら再帰の出番です。

#include <stdio.h>

int fact(int n)
{
    if (n <= 1) {
        return 1;        // ① 停止条件(ここで止まる)
    }
    return n * fact(n - 1);   // ② 自分を呼ぶ(問題を1つ小さくする)
}

int main(void)
{
    printf("%d\n", fact(5));   // 120
    return 0;
}

再帰に必要な部品は必ずこの2つです。停止条件と、問題が確実に小さくなること。どちらが欠けても止まりません。

中で何が起きているか

fact(3) を呼ぶと、コールスタックにはこう積まれます。

fact(3) … 3 * fact(2) の答え待ち
  fact(2) … 2 * fact(1) の答え待ち
    fact(1) … 1 を返す ← 停止条件

fact(1) が 1 を返すと、fact(2) が 2×1=2 を返し、fact(3) が 3×2=6 を返します。行きは積むだけ、計算は帰り道で起きるのがポイントです。同じ関数でも、呼び出しごとに別のフレームがあるので、n の値はそれぞれ独立しています。

書くときのコツ

再帰を書くときは、途中の動きを全部追いかけようとしないでください。頭が破裂します。確かめるのは次の2点だけです。

  1. いちばん小さい場合に正しい答えを返すか(fact(1) は 1 か)
  2. fact(n - 1) が正しく動くと仮定したとき、n * fact(n - 1) は正しいか

この2つが言えれば、残りは自動的に正しくなります。数学の帰納法とまったく同じ考え方です。

ループで書くとどうなるか

int fact_loop(int n)
{
    int r = 1;
    for (int i = 2; i <= n; i++) {
        r *= i;
    }
    return r;
}

同じ答えが得られ、しかもこちらのほうが速く、スタックも使いません。階乗のように「1本道の繰り返し」で表せる問題は、実務ではループで書くのがふつうです。それでも再帰を学ぶ価値があるのは、フォルダの中のフォルダをすべてたどる、迷路を分岐ごとに探索する——といった入れ子が自然な問題では、再帰のほうが圧倒的に短く書けるからです。

罠・注意

まとめ: 再帰=停止条件+小さくして自分を呼ぶ。行きに積んで帰りに計算する動きをコールスタックで思い描ければ、もう入口は通過です。

アプリでこのトピックを学ぶ(無料・登録不要)

確認問題

確認問題 1

階乗を求める再帰関数です。空欄に入るものを選んでください。

int fact(int n)
{
    if (n <= 1) {
        return 1;
    }
    return ____;
}
  1. n * fact(n - 1)
  2. n * fact(n)
  3. fact(n - 1)
  4. n - 1 * fact(n)
答えを見る

正解:A(n * fact(n - 1))

階乗は「n × (n-1)の階乗」と表せます。fact(n) をそのまま呼ぶと問題が小さくならず無限再帰になり、fact(n - 1) だけでは n を掛け忘れて常に 1 が返ります。再帰は「停止条件」と「1つ小さくして自分を呼ぶ」の2点セットです。

確認問題 2

fact(4) が 24 にならず、実行するとプログラムが異常終了します。原因の行はどれですか。

1#include <stdio.h>
2
3int fact(int n)
4{
5    if (n < 0) return 1;
6    return n * fact(n - 1);
7}
8
9int main(void)
10{
11    printf("%d\n", fact(4));
12    return 0;
13}
答えを見る

バグのある行:5行目

停止条件が間違っています。n は 4, 3, 2, 1, 0, -1 と減っていき、n < 0 に達したときには既に 0 を掛けているため答えは 0 になり、さらに条件を書き間違えて到達しない場合は無限に積み上がります。5行目を if (n <= 1) return 1; に直します。

再帰の停止条件は「そこで確実に止まり、かつ正しい値を返す」必要があります。n < 0 では 0 を掛ける段階を通ってしまい、深く積み上がった末にスタックオーバーフローや誤った結果を招きます。

「関数とスコープ」の目次

  1. 関数とは——処理に名前を付ける
  2. 引数と戻り値
  3. プロトタイプ宣言
  4. 変数のスコープ——見える範囲
  5. 変数の寿命とstatic
  6. コールスタック——関数呼び出しの舞台裏
  7. 値渡しの本質
  8. グローバル変数との付き合い方
  9. 関数分割の設計センス
  10. 再帰の入口——自分を呼ぶ関数

もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。

完全版の販売ページは準備中です。

ほかのトラック

📚 姉妹教材:手を動かして覚える Linux 教材 — Linuxとインフラの仕組みを地図で学ぶ

Web版