関数を自分で作る問題を、初級から上級まで13問そろえました。多くの問題では入力と表示を行う main 関数があらかじめ書いてあり、あなたが作るのは指定された関数の中身だけです。「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。
※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。
int max3(int a, int b, int c) を作れ。入力を読んで結果を表示する main 関数は用意してある。出力は「最大値: 値」。「実行」で入力を与えて動かす/「採点する」で 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番目のテスト)。H時間M分S秒」の形で1行表示する関数 void print_time(int sec) を作れ(0 ≤ sec ≤ 1000000)。値は返さない(void)。main 関数は用意してある。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
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 分)になってしまいます。int is_leap(int year) を作れ(1 ≤ year ≤ 9999)。うるう年は「4 で割り切れる年。ただし 100 で割り切れる年は除く。ただし 400 で割り切れる年はうるう年」。main 関数は用意してある。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
(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 年を間違えます。c × 9 ÷ 5 + 32)を返す関数 double c_to_f(double c) を作れ。main 関数は用意してあり、結果を小数第1位まで表示する。「実行」で入力を与えて動かす/「採点する」で 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 にすると小数部分が切り捨てられる点にも注意しましょう。int reverse_num(int n) を作れ(例: 1230 → 321)。main 関数は用意してあり、関数を呼んだあとで元の n も表示する。「実行」で入力を与えて動かす/「採点する」で 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; }
n は呼び出し元の n のコピーなので、関数の中で 0 まで減らしても main の n は変わらず、元の数を表示できます。逆に言えば、関数の中で引数を書き換えて呼び出し元に結果を伝えることはできないので、結果は return で返します。int sum_array(const int a[], int n) と、最大値を返す関数 int max_array(const int a[], int n) を作れ。main 関数は用意してあり、最初に n(1 ≤ n ≤ 100)、続いて n 個の整数(絶対値 10000 以下)を読み、合計・最大値・平均(小数第2位まで)を表示する。「実行」で入力を与えて動かす/「採点する」で 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番目のテスト)に間違えます。void reverse_array(int a[], int n) を作れ。main 関数は用意してあり、n(1 ≤ n ≤ 100)と n 個の整数を読んで関数を呼び、配列を空白区切りで表示する。「実行」で入力を与えて動かす/「採点する」で 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; }
a[i] を書き換えると main の配列が変わります。ループを i < n まで回すと、入れ替えたものをもう一度入れ替えて元に戻ってしまいます。int sum_divisors(int n) を作れ。この問題では関数の定義を main の後ろに書くので、先頭にプロトタイプ宣言が置いてある。main 関数は用意してあり、約数の和が n と等しければ完全数、n より大きければ過剰数、小さければ不足数と表示する。「実行」で入力を与えて動かす/「採点する」で 6 個のテストを自動で試します
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; }
; を忘れないようにしましょう。char grade(int score) を作れ。90 点以上は A、80 点以上は B、70 点以上は C、60 点以上は D、それ未満は F。main 関数は用意してあり、人数 n(1 ≤ n ≤ 50)と n 人の点数を読んで、1人1行で「点数点: 成績」を表示する。「実行」で入力を与えて動かす/「採点する」で 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 点ちょうどをテストで確かめています。int is_new_max(int x) を作れ。これまでの最大値は関数の中の static 変数で覚えておくこと(グローバル変数は使わない)。main 関数は用意してあり、n 個(1 ≤ n ≤ 50)の整数を順に関数に渡し、「値: 更新」または「値: -」を1行ずつ表示する。「実行」で入力を与えて動かす/「採点する」で 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; }
balance(初期値 0)を使って、入金する関数 void deposit(int amount) と、出金する関数 int withdraw(int amount) を作れ。withdraw は残高が足りれば残高を減らして 1 を返し、足りなければ残高を変えずに 0 を返す。main 関数は用意してあり、操作の数 n(1 ≤ n ≤ 20)と、n 行の「d 金額」(入金)または「w 金額」(出金)を読む(金額は 1〜100000)。「実行」で入力を与えて動かす/「採点する」で 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 です。グローバル変数はどこからでも変更できて便利な反面、どこで変わったか追いにくいので、使う場面は絞りましょう。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)とする。「実行」で入力を与えて動かす/「採点する」で 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; }
days[m - 1] です。ループを i <= m まで回すと、その月の日数まで足してしまいます。void sort_array(int a[], int n) を作れ。並べ替えの方法は自由(バブルソートや選択ソートなど)。配列を表示する関数 print_array と main 関数は用意してあり、n(1 ≤ n ≤ 100)と n 個の整数を読んで、並べ替える前と後を表示する。「実行」で入力を与えて動かす/「採点する」で 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; }
j < n まで回すと a[j + 1] が配列の範囲外(a[n])を読んでしまうので、j < n - 1 - i とします。