C言語 関数の練習問題

関数を自分で作る問題を、初級から上級まで13問そろえました。多くの問題では入力と表示を行う main 関数があらかじめ書いてあり、あなたが作るのは指定された関数の中身だけです。「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。

この練習問題の使い方
  1. 問題文と実行例を読み、下の欄にコードを書く
  2. 「▶ 実行」で自分で入力を与えて動かしてみる
  3. 「✔ 採点する」で、用意したいくつかの入力に対して出力が正しいかを判定
  4. 行き詰まったら「ヒント」、解けたら「模範解答と解説」で確認
採点のルール
  • 出力は実行例と同じ形で表示する。「数を入力してください」のような入力を促すメッセージは表示しない
  • 行末の空白と、最後の空行の有無は区別しない
  • 書いたコードはこのブラウザに自動保存されます

※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。

全13問(初級 4・中級 5・上級 4)

関数の練習問題(13問)

F01 初級

問1. 3つの数の最大値

3つの整数を受け取り、その中で最も大きい値を返す関数 int max3(int a, int b, int c) を作れ。入力を読んで結果を表示する main 関数は用意してある。出力は「最大値: 値」。
実行例(下線が入力)
3 9 5 最大値: 9
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
変数 m に a を入れておき、b、c の順に「m より大きければ m を置き換える」と比べます。最後に return m; で呼び出し元に値を返します。
#include <stdio.h>

int max3(int a, int b, int c) {
    int m = a;
    if (b > m) m = b;
    if (c > m) m = c;
    return m;
}

int main(void) {
    int a, b, c;
    scanf("%d %d %d", &a, &b, &c);
    printf("最大値: %d\n", max3(a, b, c));
    return 0;
}
解説:関数の結果は return で返した値が呼び出し元の max3(a, b, c) の部分に置き換わります。関数の中で printf しても戻り値にはなりません。最大値の初期値を 0 にすると、全部が負の数のときに 0 が返ってしまいます(4番目のテスト)。
F02 初級

問2. 秒を時間・分・秒で表示(void関数)

秒数を受け取り、それを「H時間M分S秒」の形で1行表示する関数 void print_time(int sec) を作れ(0 ≤ sec ≤ 1000000)。値は返さない(void)。main 関数は用意してある。
実行例(下線が入力)
3661 1時間1分1秒
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
1時間は 3600 秒、1分は 60 秒です。時間は sec / 3600、分は sec % 3600 / 60、秒は sec % 60 で求まります。表示まで関数の中で行います。
#include <stdio.h>

void print_time(int sec) {
    int h = sec / 3600;
    int m = sec % 3600 / 60;
    int s = sec % 60;
    printf("%d時間%d分%d秒\n", h, m, s);
}

int main(void) {
    int sec;
    scanf("%d", &sec);
    print_time(sec);
    return 0;
}
解説:戻り値の型を void にした関数は「値を返さず、仕事(ここでは表示)だけをする」関数です。return は書かなくても、関数の最後まで来れば呼び出し元に戻ります。分を sec / 60 とすると、時間の分まで含んだ分数(3661 秒なら 61 分)になってしまいます。
F03 初級

問3. うるう年の判定関数

西暦 year がうるう年なら 1、そうでなければ 0 を返す関数 int is_leap(int year) を作れ(1 ≤ year ≤ 9999)。うるう年は「4 で割り切れる年。ただし 100 で割り切れる年は除く。ただし 400 で割り切れる年はうるう年」。main 関数は用意してある。
実行例(下線が入力)
2024 2024年はうるう年です
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
条件を1つの式にすると (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 です。C言語では比較や論理演算の結果は 1(真)か 0(偽)なので、この式をそのまま return できます。
#include <stdio.h>

int is_leap(int year) {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

int main(void) {
    int year;
    scanf("%d", &year);
    if (is_leap(year)) {
        printf("%d年はうるう年です\n", year);
    } else {
        printf("%d年はうるう年ではありません\n", year);
    }
    return 0;
}
解説:is_xxx という名前で「真なら 1、偽なら 0」を返す判定関数にしておくと、if (is_leap(year)) のように文章のように読めます。「4 で割り切れればうるう年」だけで判定すると 1900 年を間違えます。
F04 初級

問4. 摂氏を華氏に変換(double の戻り値)

摂氏温度 c を受け取り、華氏温度(c × 9 ÷ 5 + 32)を返す関数 double c_to_f(double c) を作れ。main 関数は用意してあり、結果を小数第1位まで表示する。
実行例(下線が入力)
36.5 36.5℃ = 97.7°F
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
戻り値の型が double なので、return c * 9.0 / 5.0 + 32.0; のように計算結果をそのまま返せます。
#include <stdio.h>

double c_to_f(double c) {
    return c * 9.0 / 5.0 + 32.0;
}

int main(void) {
    double c;
    scanf("%lf", &c);
    printf("%.1f℃ = %.1f°F\n", c, c_to_f(c));
    return 0;
}
解説:c * 9 / 5 なら c が double なので正しく計算されますが、9 / 5 * c と書くと 9 / 5 が先に整数どうしの割り算になって 1 になり、結果がずれます。戻り値の型を int にすると小数部分が切り捨てられる点にも注意しましょう。
F05 中級

問5. 数字を逆から読む(値渡し)

0 以上の整数 n(0 ≤ n ≤ 99999999)を受け取り、各桁を逆に並べた数を返す関数 int reverse_num(int n) を作れ(例: 1230 → 321)。main 関数は用意してあり、関数を呼んだあとで元の n も表示する。
実行例(下線が入力)
1234 1234 を逆から読むと 4321
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
結果 r を 0 で始め、n が 0 になるまで「r = r * 10 + n % 10; → n /= 10;」を繰り返します。関数の中で n を書き換えてかまいません。
#include <stdio.h>

int reverse_num(int n) {
    int r = 0;
    while (n > 0) {
        r = r * 10 + n % 10;
        n /= 10;
    }
    return r;
}

int main(void) {
    int n;
    scanf("%d", &n);
    int r = reverse_num(n);
    printf("%d を逆から読むと %d\n", n, r);
    return 0;
}
解説:C言語の引数は値渡しです。関数が受け取る n は呼び出し元の n のコピーなので、関数の中で 0 まで減らしても main の n は変わらず、元の数を表示できます。逆に言えば、関数の中で引数を書き換えて呼び出し元に結果を伝えることはできないので、結果は return で返します。
F06 中級

問6. 配列の合計と最大値

要素数 n の int 配列を受け取り、合計を返す関数 int sum_array(const int a[], int n) と、最大値を返す関数 int max_array(const int a[], int n) を作れ。main 関数は用意してあり、最初に n(1 ≤ n ≤ 100)、続いて n 個の整数(絶対値 10000 以下)を読み、合計・最大値・平均(小数第2位まで)を表示する。
実行例(下線が入力)
5 3 8 -2 7 4 合計: 20 最大: 8 平均: 4.00
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
配列を受け取る引数は const int a[] のように書き、要素数は別の引数 n で受け取ります。最大値は a[0] を仮の最大として、a[1] 以降と比べていきます。
#include <stdio.h>

int sum_array(const int a[], int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += a[i];
    }
    return sum;
}

int max_array(const int a[], int n) {
    int max = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > max) max = a[i];
    }
    return max;
}

int main(void) {
    int a[100];
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    int sum = sum_array(a, n);
    printf("合計: %d\n", sum);
    printf("最大: %d\n", max_array(a, n));
    printf("平均: %.2f\n", (double)sum / n);
    return 0;
}
解説:関数の中では配列の大きさが分からないので、要素数を一緒に渡すのが決まりごとです(関数の中の sizeof(a) は配列全体の大きさにはなりません)。const を付けると「この関数は配列を書き換えない」ことを表せます。最大値の初期値を 0 にすると、全部負の数のとき(3番目のテスト)に間違えます。
F07 中級

問7. 配列を逆順に並べ替える

要素数 n の int 配列の並びを、その場で逆順に入れ替える関数 void reverse_array(int a[], int n) を作れ。main 関数は用意してあり、n(1 ≤ n ≤ 100)と n 個の整数を読んで関数を呼び、配列を空白区切りで表示する。
実行例(下線が入力)
5 1 2 3 4 5 5 4 3 2 1
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
先頭 a[i] と末尾 a[n - 1 - i] を入れ替えるのを、真ん中まで(i < n / 2)繰り返します。入れ替えには一時変数 tmp を使います。
#include <stdio.h>

void reverse_array(int a[], int n) {
    for (int i = 0; i < n / 2; i++) {
        int tmp = a[i];
        a[i] = a[n - 1 - i];
        a[n - 1 - i] = tmp;
    }
}

int main(void) {
    int a[100];
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    reverse_array(a, n);
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}
解説:int などの普通の引数はコピーが渡されますが、配列を渡すと実際には先頭の場所(アドレス)が渡されるので、関数の中で a[i] を書き換えると main の配列が変わります。ループを i < n まで回すと、入れ替えたものをもう一度入れ替えて元に戻ってしまいます。
F08 中級

問8. 完全数の判定(プロトタイプ宣言)

正の整数 n(1 ≤ n ≤ 10000)について、n 自身を除く約数の和を返す関数 int sum_divisors(int n) を作れ。この問題では関数の定義を main の後ろに書くので、先頭にプロトタイプ宣言が置いてある。main 関数は用意してあり、約数の和が n と等しければ完全数、n より大きければ過剰数、小さければ不足数と表示する。
実行例(下線が入力)
28 約数の和: 28 28 は完全数です
コードを書く
「実行」で入力を与えて動かす/「採点する」で 6 個のテストを自動で試します
1 から n / 2 までの i で n % i == 0 なら足します(n 自身を除く約数は n/2 以下にしかありません)。n = 1 のときはループが1回も回らず 0 になります。
#include <stdio.h>

int sum_divisors(int n);   // プロトタイプ宣言

int main(void) {
    int n;
    scanf("%d", &n);
    int s = sum_divisors(n);
    printf("約数の和: %d\n", s);
    if (s == n) {
        printf("%d は完全数です\n", n);
    } else if (s > n) {
        printf("%d は過剰数です\n", n);
    } else {
        printf("%d は不足数です\n", n);
    }
    return 0;
}

int sum_divisors(int n) {
    int sum = 0;
    for (int i = 1; i <= n / 2; i++) {
        if (n % i == 0) sum += i;
    }
    return sum;
}
解説:C言語のコンパイラはソースを上から読むので、関数を使う場所より後ろで定義するときは、先に「戻り値の型・名前・引数の型」を知らせるプロトタイプ宣言が必要です。宣言を消すと、C99 以降の規格では誤りとなり、コンパイラによってエラーか警告になります。宣言の末尾の ; を忘れないようにしましょう。
F09 中級

問9. 点数から成績を返す(char の戻り値)

点数 score(0〜100)を受け取り、成績の文字を返す関数 char grade(int score) を作れ。90 点以上は A、80 点以上は B、70 点以上は C、60 点以上は D、それ未満は F。main 関数は用意してあり、人数 n(1 ≤ n ≤ 50)と n 人の点数を読んで、1人1行で「点数点: 成績」を表示する。
実行例(下線が入力)
3 95 72 58 95点: A 72点: C 58点: F
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
if (score >= 90) return 'A'; のように、大きい方の基準から順に判定して、当てはまったらすぐ return します。文字は 'A' のようにシングルクォートで書きます。
#include <stdio.h>

char grade(int score) {
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    if (score >= 70) return 'C';
    if (score >= 60) return 'D';
    return 'F';
}

int main(void) {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int score;
        scanf("%d", &score);
        printf("%d点: %c\n", score, grade(score));
    }
    return 0;
}
解説:return を実行するとその場で関数を抜けるので、else を書かなくても後ろの判定は行われません。"A"(ダブルクォート)は文字列なので char 型の戻り値にはできません。境界の 90・80・70・60 点ちょうどをテストで確かめています。
F10 上級

問10. 最大値を更新したか(static 変数)

呼ばれるたびに整数 x を受け取り、x がこれまでに渡されたどの値よりも大きい(または初めての呼び出しである)なら 1、そうでなければ 0 を返す関数 int is_new_max(int x) を作れ。これまでの最大値は関数の中の static 変数で覚えておくこと(グローバル変数は使わない)。main 関数は用意してあり、n 個(1 ≤ n ≤ 50)の整数を順に関数に渡し、「値: 更新」または「値: -」を1行ずつ表示する。
実行例(下線が入力)
5 3 1 4 1 5 3: 更新 1: - 4: 更新 1: - 5: 更新
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
static int max; と static int first = 1; の2つを関数の中に置きます。static 変数は関数を抜けても値が残り、初期化はプログラム開始時の1回だけです。first が 1 なら「初めて」として扱います。
#include <stdio.h>

int is_new_max(int x) {
    static int first = 1;
    static int max;

    if (first || x > max) {
        first = 0;
        max = x;
        return 1;
    }
    return 0;
}

int main(void) {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int x;
        scanf("%d", &x);
        if (is_new_max(x)) {
            printf("%d: 更新\n", x);
        } else {
            printf("%d: -\n", x);
        }
    }
    return 0;
}
解説:static を付けない普通のローカル変数は、呼び出しのたびに作り直されるので、前回の最大値を覚えておけません。また、最大値の初期値を 0 にすると、負の数ばかりのとき(2番目のテスト)に最初の値が「更新」になりません。そこで「初めてかどうか」を別の static 変数で管理します。static 変数はその関数の中からしか見えないので、グローバル変数より安全です。
F11 上級

問11. 口座の入出金(グローバル変数)

残高を表すグローバル変数 balance(初期値 0)を使って、入金する関数 void deposit(int amount) と、出金する関数 int withdraw(int amount) を作れ。withdraw は残高が足りれば残高を減らして 1 を返し、足りなければ残高を変えずに 0 を返す。main 関数は用意してあり、操作の数 n(1 ≤ n ≤ 20)と、n 行の「d 金額」(入金)または「w 金額」(出金)を読む(金額は 1〜100000)。
実行例(下線が入力)
3 d 1000 w 300 w 800 入金 1000: 残高 1000 出金 300: 残高 700 出金 800: 残高不足 (残高 700) 最終残高: 700
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
グローバル変数は関数の外で宣言されているので、どの関数からも balance += amount; のように直接読み書きできます。withdraw は if (amount > balance) return 0; で先に失敗の場合を処理します。
#include <stdio.h>

int balance = 0;   // 残高(グローバル変数)

void deposit(int amount) {
    balance += amount;
}

int withdraw(int amount) {
    if (amount > balance) {
        return 0;
    }
    balance -= amount;
    return 1;
}

int main(void) {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        char cmd;
        int amount;
        scanf(" %c %d", &cmd, &amount);
        if (cmd == 'd') {
            deposit(amount);
            printf("入金 %d: 残高 %d\n", amount, balance);
        } else if (withdraw(amount)) {
            printf("出金 %d: 残高 %d\n", amount, balance);
        } else {
            printf("出金 %d: 残高不足 (残高 %d)\n", amount, balance);
        }
    }
    printf("最終残高: %d\n", balance);
    return 0;
}
解説:関数の中で int balance = 0; と書いてしまうと、同じ名前のローカル変数が新しく作られ、グローバル変数は変わりません(名前の隠蔽)。残高ちょうどの出金(2番目の操作 w 500)は成功させるので、条件は amount > balance です。グローバル変数はどこからでも変更できて便利な反面、どこで変わったか追いにくいので、使う場面は絞りましょう。
F12 上級

問12. 1年の何日目か(関数の組み合わせ)

年 y と月 m(1〜12)を受け取りその月の日数を返す関数 int days_in_month(int y, int m) と、年月日を受け取りその日が1月1日から数えて何日目かを返す関数 int day_of_year(int y, int m, int d) を作れ。うるう年の判定関数 is_leap と main 関数は用意してある。入力は正しい日付(1 ≤ y ≤ 9999)とする。
実行例(下線が入力)
2024 3 1 2024年3月の日数: 31 2024年3月1日は61日目
コードを書く
「実行」で入力を与えて動かす/「採点する」で 7 個のテストを自動で試します
月ごとの日数は配列 {31, 28, 31, 30, ...} にしておき、2月でうるう年なら 29 を返します。day_of_year は「1 月から m−1 月までの日数の合計 + d」なので、days_in_month を繰り返し呼べば作れます。
#include <stdio.h>

int is_leap(int y) {
    return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
}

int days_in_month(int y, int m) {
    const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if (m == 2 && is_leap(y)) {
        return 29;
    }
    return days[m - 1];
}

int day_of_year(int y, int m, int d) {
    int total = d;
    for (int i = 1; i < m; i++) {
        total += days_in_month(y, i);
    }
    return total;
}

int main(void) {
    int y, m, d;
    scanf("%d %d %d", &y, &m, &d);
    printf("%d年%d月の日数: %d\n", y, m, days_in_month(y, m));
    printf("%d年%d月%d日は%d日目\n", y, m, d, day_of_year(y, m, d));
    return 0;
}
解説:小さな関数を作って別の関数から呼び出すと、うるう年の処理を1か所にまとめられます。配列の添字は 0 から始まるので、m 月の日数は days[m - 1] です。ループを i <= m まで回すと、その月の日数まで足してしまいます。
F13 上級

問13. 配列を小さい順に並べ替える

要素数 n の int 配列を小さい順(昇順)に並べ替える関数 void sort_array(int a[], int n) を作れ。並べ替えの方法は自由(バブルソートや選択ソートなど)。配列を表示する関数 print_array と main 関数は用意してあり、n(1 ≤ n ≤ 100)と n 個の整数を読んで、並べ替える前と後を表示する。
実行例(下線が入力)
6 5 2 9 1 5 6 前: 5 2 9 1 5 6 後: 1 2 5 5 6 9
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
バブルソートなら、i を 0〜n−2 と回す外側のループの中で、j を 0〜n−2−i と回し、a[j] > a[j + 1] なら隣どうしを入れ替えます。1周ごとに一番大きい値が右端に決まります。
#include <stdio.h>

void print_array(const char label[], const int a[], int n) {
    printf("%s:", label);
    for (int i = 0; i < n; i++) {
        printf(" %d", a[i]);
    }
    printf("\n");
}

void sort_array(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (a[j] > a[j + 1]) {
                int tmp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = tmp;
            }
        }
    }
}

int main(void) {
    int a[100];
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    print_array("前", a, n);
    sort_array(a, n);
    print_array("後", a, n);
    return 0;
}
解説:配列は関数の中で書き換えると呼び出し元にも反映されるので、戻り値なし(void)で「並べ替える」関数が作れます。内側のループを j < n まで回すと a[j + 1] が配列の範囲外(a[n])を読んでしまうので、j < n - 1 - i とします。

復習する講座

← 練習問題の一覧へ