🇯🇵 日本語

STEP 5 総合復習 ― 関数・スコープ・配列引数・再帰

第24〜33回までの要点を チートシート でおさらいし、コード読み取り6問「よくあるミス」チェックリスト で定着度を確認します。

① 要点チートシート

STEP 5 で押さえておきたい要点を、表で一気に 見直しましょう。

🔧 関数の骨格

// 戻り値型 関数名(引数の型 仮引数, ...)
int square(int x) {
    return x * x;
}

int main(void) {
    printf("%d\n", square(5)); // 25
}
  • 戻り値型 / 名前 / 引数 / 本体 の 4 点セット
  • 戻り値が無い関数は void
  • return で値を返す(途中で抜けることもできる)

🎚️ 引数は「値渡し(コピー)」

void add10(int x) {
    x += 10; // この x はコピー
}
int main(void) {
    int a = 5;
    add10(a);
    printf("%d\n", a); // 5 のまま!
}
  • 仮引数は 値のコピー なので、関数内で変えても呼出側に 影響しない
  • 呼出側を変えたいなら ポインタ戻り値 を使う

🔭 スコープ(変数の有効範囲)

int g = 100;       // グローバル

void f(void) {
    int x = 1;     // f だけのローカル
}
int main(void) {
    int x = 2;     // main の x(別物)
    f();
    printf("%d %d\n", x, g); // 2 100
}
  • 関数内で宣言した変数は その関数だけ で有効(ローカル)
  • 関数の外で宣言した変数は どこからでも 使える(グローバル)
  • 同名でもスコープが違えば別物

📐 配列を引数に渡す

void fill(int a[], int n) {
    for (int i = 0; i < n; i++) a[i] = 7;
}
int main(void) {
    int a[3] = {0};
    fill(a, 3);
    // a は {7, 7, 7} に変わる
}
  • 配列引数は 先頭アドレス が渡る(コピーされない)
  • 関数内の変更は 呼出側にも反映される
  • 配列内では sizeof で長さを取れない(別途長さを渡す)

🔄 再帰関数(自分自身を呼ぶ)

int factorial(int n) {
    if (n <= 1) return 1; // 停止条件!
    return n * factorial(n - 1);
}
// factorial(4) = 4*3*2*1 = 24
  • 停止条件 を必ず用意(無いと無限再帰でクラッシュ)
  • 「より小さい問題」に分解して自分を呼ぶ

📋 プロトタイプ宣言

// 先頭で宣言(実装は後でも OK)
int add(int, int);

int main(void) {
    printf("%d\n", add(2, 3));
}

int add(int a, int b) { return a + b; }
  • 使う前にコンパイラに 関数の形 を教える
  • main より下に書く関数は、上にプロトタイプを書く

② コード読み取り 6問

頭の中で実行 してから選択肢をクリック。間違えても解説を読めば必ず腑に落ちます。

Q1. 戻り値の活用

int square(int x) { return x * x; }

int main(void) {
    printf("%d\n", square(3) + square(4));
}

出力は?

12
25
7
49
解説: square(3) = 9、square(4) = 16。
9 + 16 = 25
※ 三平方の定理「3-4-5」は 9 + 16 = 25 から来ている。

Q2. 値渡しの確認

void add10(int x) { x += 10; }

int main(void) {
    int a = 5;
    add10(a);
    printf("%d\n", a);
}

出力は?

5
10
15
不定
解説: 引数は 値のコピー
add10 内の x は a とは 別の変数。x に 10 を足しても a は変わらない。
5
a 自身を変えたければ、ポインタ渡し(void add10(int *p))か、戻り値で受け取る(a = add10(a))。

Q3. 配列を引数に

void setZero(int a[], int n) {
    for (int i = 0; i < n; i++) a[i] = 0;
}

int main(void) {
    int a[3] = {7, 8, 9};
    setZero(a, 3);
    printf("%d %d %d\n", a[0], a[1], a[2]);
}

出力は?

7 8 9(変わらない)
0 0 0
不定(未初期化扱い)
コンパイルエラー
解説: 配列を関数に渡すと 先頭アドレスが渡る(コピーではない)。
関数内で a[i] = 0 と書くと、呼出側の配列も そのまま書き換わる
0 0 0
値渡しの常識(Q2 のような)と挙動が違うので注意。

Q4. ローカルとグローバル

int g = 100;

void f(void) {
    int g = 1; // 同名のローカル
    printf("f: %d\n", g);
}

int main(void) {
    f();
    printf("main: %d\n", g);
}

出力は?

f: 100
main: 100
f: 1
main: 100
f: 1
main: 1
コンパイルエラー(同名宣言)
解説: f の中の int g = 1;ローカル。グローバル g とは別物で、関数内ではローカルが優先(シャドーイング)。
f 内: ローカル g (=1) を表示 → f: 1
main: グローバル g (=100) を表示 → main: 100

Q5. 再帰の停止条件

int fact(int n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
}
printf("%d\n", fact(4));

出力は?

10
24
4
無限再帰でクラッシュ
解説: 4! = 4 × 3 × 2 × 1 = 24
fact(4) = 4 * fact(3) = 4 * 3 * fact(2) = 4 * 3 * 2 * fact(1) = 4 * 3 * 2 * 1 = 24。
停止条件 n <= 1 で return 1 が再帰を止める。

Q6. 関数内で配列の長さを取る

void show(int a[]) {
    printf("%lu\n", sizeof a);
}

int main(void) {
    int a[5] = {0};
    show(a);
}

show 関数内の sizeof a は何を返す?

20(= 5 × 4 バイト)
5
ポインタのサイズ(多くの環境で 8 バイト)
コンパイルエラー
解説: 関数引数の配列 int a[]ポインタ扱い
sizeof は ポインタのサイズ(多くの環境で 8 バイト)になり、配列全体のバイト数にはならない。
関数内で配列の長さを知りたければ、長さを別の引数で渡す のが定石(void show(int a[], int n))。

③ よくあるミス・難所チェックリスト

読みながら「やったことある…」と思ったら、その項目はもう一度元のページに戻って確認しておきましょう。
  1. 1
    引数を関数内で変えても、呼出側に反映されないと知らなかった
    引数は 値渡し(コピー)。呼出側の値を変えるなら 戻り値ポインタ渡し
  2. 2
    戻り値の型と return の型が合っていない
    int 戻り値の関数で double を返すと暗黙変換される。void なのに return x; はエラー。
  3. 3
    関数内で配列の長さを sizeof で取ろうとした
    関数引数の配列は ポインタ 扱いで、長さの情報は失われる。長さは別途引数で渡す
  4. 4
    再帰の停止条件を書き忘れた
    無限再帰で スタックオーバーフロー によりクラッシュ。最初に「終わる場合」を必ず書く。
  5. 5
    main より下の関数を、プロトタイプ宣言なしで使った
    コンパイラは「上から順」に読むので、main で使う関数は 前にプロトタイプ が必要。
  6. 6
    同名のローカル変数とグローバル変数で混乱した
    関数内で同名を宣言すると ローカルが優先(シャドーイング)。意図しないバグの原因。同名は避ける。
  7. 7
    グローバル変数を多用してコードがぐちゃぐちゃになった
    どこから書き換わっているか追跡困難。引数と戻り値で受け渡し するのが基本。

④ 戻るページ ― つまずいたらここに戻る

特定の項目で迷ったら、対応する元ページにすぐ戻れるよう一覧にしました。
第24回 関数(基本)骨格・引数・戻り値が曖昧な時に 第25回 関数の深掘り値渡し・スコープを確認したい時に 第28回 プロトタイプ・マクロ関数の前方宣言が曖昧な時に 第29回 グローバル変数スコープと有効範囲の確認に 第30回 配列を引数にポインタ扱い・sizeof の罠を確認 第31回 確認問題(関数)類題で別アングルから確認 第32回 再帰関数停止条件と分解の仕方を再確認 チートシート(早見表)書式・関数まわりを1ページで確認