C言語でキーボード入力を扱うならscanfの仕組みを正しく理解することが大切です。
便利な反面、使い方を誤ると改行の食べ残しやバッファオーバーフロー、無限ループなどの厄介な不具合を招きます。
ここでは基本からよくある失敗の直し方まで、入門者向けに丁寧に解説します。
scanf入門(キーボード入力の基本)
scanfの書式と&演算子
scanf
はフォーマット文字列と、読み取った値を書き込むためのアドレス(格納先)を渡して使います。
変数のアドレスは&
演算子で渡します。
配列(特にchar name[ ]
のような文字配列)は配列名が先頭要素へのポインタに暗黙変換されるため、%s
では通常&
を付けません。
- 典型形:
scanf("フォーマット", &変数1, &変数2, ...);
- 例外: 文字列
%s
でchar buf[100]; scanf("%99s", buf);
のように配列名をそのまま渡す
簡単な例を見てみます。
#include <stdio.h>
int main(void) {
int age; // 整数を入れる変数
double height; // 浮動小数を入れる変数(後述の通り%lfを使う)
printf("年齢(整数)と身長(m)を入力してください: ");
// %dはint用、%lfはdouble用。&でアドレスを渡すことに注意。
if (scanf("%d %lf", &age, &height) == 2) {
printf("あなたは%d歳で、身長は%.2f mですね。\n", age, height);
} else {
printf("入力を正しく読み取れませんでした。\n");
}
return 0;
}
年齢(整数)と身長(m)を入力してください: 20 1.72
あなたは20歳で、身長は1.72 mですね。
フォーマット指定子(%d/%f/%c/%s)
主要な指定子と対応する型、空白の扱いを整理します。
scanf
では数値系の前後の空白は自動で無視されますが、%c
は1文字をそのまま取り、空白や改行も読みます。
表: よく使う指定子の要点
指定子 | 対象(読み取る値) | 引数の型(渡すべきポインタ型) | 空白の扱い・特徴 |
---|---|---|---|
%d | 10進の整数 | int* | 先頭の空白は無視、符号(+/-)可 |
%f | 浮動小数 | float* | 先頭の空白は無視、指数表記可 |
%lf | 浮動小数 | double* | doubleを読む時はこちら |
%c | 1文字 | char* | 空白・改行もそのまま1文字として読む |
%s | 単語(空白まで) | char* | 最初の空白で終了。幅指定で安全に |
注意: printf
では%f
にdouble
を渡しますが、scanf
ではdouble
に%lf
、float
に%f
を使います。
混同に注意してください。
戻り値で入力成功をチェック
scanf
の戻り値は「成功して代入できた項目数」です。
期待数に満たなければ読み取り失敗です。
失敗時は不正入力を捨てないと同じ位置で詰まります。
#include <stdio.h>
static void discard_line(void) {
// 改行まで読み飛ばす。EOFでも終了。
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* 空ループ */ }
}
int main(void) {
int n;
while (1) {
printf("整数を入力してください: ");
int rc = scanf("%d", &n);
if (rc == 1) {
printf("読み取り成功: %d\n", n);
break;
} else if (rc == EOF) {
printf("入力が終了(EOF)しました。\n");
return 1;
} else {
printf("入力エラー: 数字を入力してください。\n");
discard_line(); // 不正な残りを捨てることが重要
}
}
return 0;
}
整数を入力してください: abc
入力エラー: 数字を入力してください。
整数を入力してください: 42
読み取り成功: 42
数値入力の使い方(%d/%f)と注意点
整数と浮動小数を読む
整数は%d
でint
、より大きい範囲を扱うなら%ld
(long)、%lld
(long long)を使います。
浮動小数はdouble
に%lf
を使うのが実務では一般的です。
#include <stdio.h>
int main(void) {
int a;
long long big;
double x;
printf("int, long long, double を順に入力(例: 10 9000000000 3.14): ");
if (scanf("%d %lld %lf", &a, &big, &x) == 3) {
printf("a=%d, big=%lld, x=%.3f\n", a, big, x);
} else {
printf("どれかの読み取りに失敗しました。\n");
}
return 0;
}
int, long long, double を順に入力(例: 10 9000000000 3.14): 10 9000000000 3.14
a=10, big=9000000000, x=3.140
複数入力と空白の扱い
フォーマット文字列中の「空白文字」は入力中の任意個の空白(スペース、タブ、改行)にマッチします。
区切りがカンマなど固定なら、その記号をフォーマットに入れます。
#include <stdio.h>
int main(void) {
int x, y;
// 空白区切り。1つ以上の空白や改行でもOK。
printf("2つの整数を空白区切りで入力: ");
if (scanf("%d %d", &x, &y) == 2) {
printf("[空白区切り] x=%d, y=%d\n", x, y);
} else {
printf("空白区切りの読み取り失敗。\n");
return 0;
}
// カンマ区切り。「,」が入力に必須になる
printf("2つの整数をカンマ区切りで入力(例: 10,20): ");
if (scanf("%d,%d", &x, &y) == 2) {
printf("[カンマ区切り] x=%d, y=%d\n", x, y);
} else {
printf("カンマ区切りの読み取り失敗。\n");
}
return 0;
}
2つの整数を空白区切りで入力: 7 9
[空白区切り] x=7, y=9
2つの整数をカンマ区切りで入力(例: 10,20): 10,20
[カンマ区切り] x=10, y=20
ポイントとして、scanf("%d %d", ...)
は「スペース1個」ではなく「任意個の空白」を意味します。
逆に「カンマ区切り」と決めると、ユーザは正確に10,20
のように入力しないと失敗します。
範囲外入力とエラー処理
入力が変数の範囲を超えると未定義動作になり得ます。
堅牢にするには「文字列で読み取ってから変換」する方法が有効です。
strtol
なら範囲エラーや不正文字を検知できます。
#include <stdio.h>
#include <stdlib.h> // strtol
#include <errno.h> // errno, ERANGE
#include <limits.h> // INT_MIN, INT_MAX
#include <string.h> // strlen
// 1行読み込んで末尾の改行を落とす
static int readline(char *buf, size_t size) {
if (!fgets(buf, (int)size, stdin)) return 0; // EOF/エラー
size_t len = strlen(buf);
if (len && buf[len - 1] == '\n') buf[len - 1] = '\0';
return 1;
}
int main(void) {
char buf[128];
int value;
printf("intの範囲の整数を入力してください: ");
if (!readline(buf, sizeof buf)) {
printf("入力がありませんでした。\n");
return 1;
}
errno = 0;
char *end;
long v = strtol(buf, &end, 10); // 10進として解釈
if (errno == ERANGE || v < INT_MIN || v > INT_MAX) {
printf("範囲外の数値です。\n");
return 1;
}
// 1文字も読めていない、または末尾に不正な文字がある場合の判定
while (*end == ' ' || *end == '\t') ++end; // 末尾の空白は許容
if (end == buf || *end != '\0') {
printf("不正な形式です(数字以外の文字が含まれる可能性)。\n");
return 1;
}
value = (int)v;
printf("正しく読み取れました: %d\n", value);
return 0;
}
intの範囲の整数を入力してください: 9223372036854775807
範囲外の数値です。
浮動小数でも同様にstrtod
を使うと、NaN
や範囲外の検知がしやすくなります。
文字・文字列入力(%c/%s)のコツと注意点
%cの前に空白で改行をスキップ
%c
は空白や改行も1文字として読みます。
そのため直前に%d
などで数値を読んだあとに%c
を使うと、バッファに残っていた改行(\n
)をそのまま拾ってしまいます。
これを避けるにはフォーマットの%c
の前に空白を入れます。
空白は「任意個の空白を読み飛ばす」指示になります。
#include <stdio.h>
int main(void) {
int n;
char ans;
printf("好きな数字を入力: ");
if (scanf("%d", &n) != 1) return 1;
// 前に空白を置いた" %c"で、改行などの空白をスキップ
printf("もう一度続けますか(y/n)? ");
if (scanf(" %c", &ans) == 1) {
printf("n=%d, ans=%c\n", n, ans);
} else {
printf("読み取り失敗\n");
}
return 0;
}
好きな数字を入力: 5
もう一度続けますか(y/n)? y
n=5, ans=y
%sの幅指定でバッファオーバーフロー防止
%s
は空白(スペースや改行)までを読み取ります。
最も危険なのは幅指定をしないことです。
常に配列サイズより1小さい幅を指定して、終端のヌル文字(\0
)のための1バイトを残しましょう。
#include <stdio.h>
int main(void) {
char name[16]; // 最大15文字+終端\0まで
printf("名前を入力してください(15文字まで): ");
// %15s で最大15文字まで受け取る
if (scanf("%15s", name) == 1) {
printf("こんにちは、%sさん。\n", name);
} else {
printf("読み取り失敗。\n");
}
return 0;
}
名前を入力してください(15文字まで): Alice
こんにちは、Aliceさん。
多バイト文字(日本語)を含めた文字数制御はやや複雑になります。
%s
の幅指定は「バイト数上限」であり「文字数」ではない点にも注意してください。
行全体を読むならfgetsを併用
空白を含む1行全体を読みたい場合はfgets
を使います。
fgets
で取った1行をそのまま使うか、sscanf
で解析します。
scanf
とfgets
を混在させるときは、scanf
後に残る改行を捨ててからfgets
するのが安全です。
#include <stdio.h>
#include <string.h>
static void discard_line(void) {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { }
}
int main(void) {
int id;
char line[128];
printf("ID(整数)を入力: ");
if (scanf("%d", &id) != 1) return 1;
discard_line(); // 改行を捨てる
printf("自己紹介を1行で入力: ");
if (!fgets(line, sizeof line, stdin)) return 1;
// 末尾の改行を落とす
line[strcspn(line, "\n")] = '\0';
printf("ID=%d, 自己紹介=\"%s\"\n", id, line);
return 0;
}
ID(整数)を入力: 101
自己紹介を1行で入力: C言語を勉強中です
ID=101, 自己紹介="C言語を勉強中です"
scanfでよくある失敗と直し方
&を付け忘れる/指定子と型の不一致
&
を忘れると「値」ではなく「その値だと思い込んだポインタ」に書き込もうとしてクラッシュの原因になります。
また指定子と型が一致しないと未定義動作です。
#include <stdio.h>
int main(void) {
int n;
double x;
// 悪い例:
// scanf("%d", n); // &がない
// scanf("%f", &x); // doubleに%fは間違い(%lfが正しい)
// 良い例:
printf("整数と小数を入力: ");
if (scanf("%d %lf", &n, &x) == 2) {
printf("n=%d, x=%.2f\n", n, x);
} else {
printf("読み取り失敗\n");
}
return 0;
}
整数と小数を入力: 3 2.5
n=3, x=2.50
入力バッファに残る改行の対処
数値の直後に%c
やfgets
で読むと、前の入力で押したEnterの改行が残っていて即座に空読みになることがあります。
対処は以下の通りです。
scanf(" %c", &ch);
のように%c
の前に空白を入れる- ヘルパ関数で改行まで読み捨ててから次の入力を行う
#include <stdio.h>
static void discard_line(void) {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { }
}
int main(void) {
int n;
char c1, c2;
printf("整数を入力: ");
scanf("%d", &n);
// 改行を捨てないと、ここでc1に'\n'が入る
discard_line();
printf("1文字を入力: ");
scanf("%c", &c1); // ここで待機して正しく1文字読める
// あるいは前に空白を入れて改行スキップ
printf("もう1文字を入力: ");
scanf(" %c", &c2);
printf("n=%d, c1='%c', c2='%c'\n", n, c1, c2);
return 0;
}
整数を入力: 10
1文字を入力: A
もう1文字を入力: Z
n=10, c1='A', c2='Z'
fflush(stdin)が未定義な理由と代替
fflush
は「出力ストリームのバッファを吐き出す」関数として定義されています。
入力ストリームstdin
に対するfflush(stdin)
はC標準では未定義で、処理系依存です(MSVCなど一部で拡張として動くことはありますが移植性がありません)。
代わりに次のようにしましょう。
- 改行まで読み飛ばす:
int ch; while ((ch = getchar()) != '\n' && ch != EOF) { }
- 1行を
fgets
で読み取り、必要ならsscanf
やstrtol/strtod
で解析する scanf
で破棄指定子を使って残りを捨てる方法もありますが、状況によりブロックの可能性があるため注意が必要です(例:scanf("%*[^\n]"); scanf("%*c");
)
無限ループや詰まりを防ぐ
scanf
がマッチしない入力に遭遇すると項目数0を返し、入力位置は進みません。
この状態で「同じscanf
を繰り返すだけ」のループを書くと、同じ不正文字を何度も読み直して無限ループになります。
必ず不正な入力を消費しましょう。
// 入力が"abc"のとき、ずっとrc==0で同じ場所に留まり続ける
while (scanf("%d", &n) != 1) {
printf("整数を入力してください: ");
}
#include <stdio.h>
static void discard_line(void) {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { }
}
int main(void) {
int n;
while (1) {
printf("整数を入力してください: ");
int rc = scanf("%d", &n);
if (rc == 1) {
printf("OK: %d\n", n);
break;
} else if (rc == EOF) {
printf("入力終了\n");
return 1;
} else {
printf("数字ではありません。やり直してください。\n");
discard_line(); // ここが肝心
}
}
return 0;
}
整数を入力してください: abc
数字ではありません。やり直してください。
整数を入力してください: -12
OK: -12
別アプローチとして、常にfgets
で1行を読み取り、sscanf
やstrtol
で解析する方法は、失敗時に再入力しやすく、安全性も高いのでおすすめです。
#include <stdio.h>
#include <string.h>
int main(void) {
char line[64];
int n;
while (1) {
printf("整数を入力してください: ");
if (!fgets(line, sizeof line, stdin)) return 1;
// 行頭にスペースがあってもよいように"%d%*[^\n]"で末尾のゴミを許容
if (sscanf(line, " %d%*[^\n]", &n) == 1) {
printf("OK: %d\n", n);
break;
} else {
printf("数値として解釈できませんでした。再入力をお願いします。\n");
}
}
return 0;
}
整数を入力してください: 12xyz
OK: 12
上の例のように「許容する形式」を意図的に設計できるのは、行単位で扱う利点です。
まとめ
scanf
は正しく使えば簡潔に入力を扱える一方、少しの不注意で思わぬ挙動を招きます。
基本は次の3点です。
- 第一にフォーマット指定子と渡す型(特に
%lf
とdouble
)を厳密に合わせ、%s
には必ず幅指定を入れます。 - 第二に戻り値で成否を確認し、失敗時は必ず不正入力を消費してから再試行します。
- 第三に
%c
の改行対策やfgets
併用など、目的に応じた読み取り手段を使い分けます。
入力の安全性を優先するなら「行で受けてから解析」が有力な選択肢です。
これらの基本を押さえれば、入門段階でも堅牢で読みやすいコンソール入力が実現できます。