C言語でユーザー入力を扱う際、scanf
は簡単に使える反面、空白や改行、誤入力に弱いという特性があります。
本記事では、基本の使い方から、初心者がつまずきやすい落とし穴と回避策、そして安全な実装レシピまで、段階を踏んで詳しく解説します。
実行例付きのサンプルコードを用意し、確実に動く書き方を身につけます。
C言語のscanf 基本の使い方
標準入力と&演算子の意味
標準入力(stdin)とは
プログラムは通常、キーボードからの入力を標準入力(stdin)
として受け取ります。
scanf
はこの標準入力からデータを読み取り、指定した変数に格納します。
重要なポイントは、scanf
に渡すのは「格納先のアドレス」ということです。
変数の先頭アドレスを渡すために&
(アンパサンド)演算子を使います。
配列名arr
やchar s[100]
のような文字配列は先頭アドレスとして振る舞うため&
は不要です。
#include <stdio.h>
int main(void) {
int a; // 整数を入れる変数
printf("整数を入力してください: "); // ユーザーへプロンプト表示
// %d は int、&a は a のアドレスを渡す
if (scanf("%d", &a) == 1) { // 1件読めたかどうかを確認
printf("読み取った整数は %d です\n", a);
} else {
printf("整数として読み取れませんでした\n");
}
return 0;
}
整数を入力してください: 42
読み取った整数は 42 です
書式指定子の基本(%d %f %c %s)
よく使う指定子
書式指定子は、入力の型とメモリレイアウトをscanf
に教える約束です。
間違えると未定義動作になり得るため注意します。
代表的な指定子を次の表にまとめます。
指定子 | 対応する型 | 説明 |
---|---|---|
%d | int* | 10進整数を読み取ります。先頭の空白はスキップされます。 |
%f | float* | 小数を読み取ります。doubleを読むときは%lfです。 |
%lf | double* | 倍精度浮動小数点を読み取ります。 |
%c | char* | 1文字を読み取ります。空白や改行も1文字として読みます。 |
%s | char* | 1単語を読み取ります。空白で途切れます。幅指定でバッファを守ります。 |
%u/%ld/%lld | unsigned int/long/long long* | 整数の別型。型に応じて使い分けます。 |
%[…] | char* | 指定集合の文字列を読みます。 |
%n | int* | これまでに消費した文字数を格納します(入力はしません)。 |
#include <stdio.h>
int main(void) {
int i;
float f;
char ch;
char word[16]; // 単語用バッファ(終端を含め最大15文字)
printf("int, float, char, word を順に入力してください: ");
// %15s で最大長を制限し、バッファオーバーフローを防ぐ
if (scanf("%d %f %c %15s", &i, &f, &ch, word) == 4) {
printf("i=%d, f=%.2f, ch=%c, word=%s\n", i, f, ch, word);
} else {
printf("入力の形式が正しくありません\n");
}
return 0;
}
int, float, char, word を順に入力してください: 10 3.14 X hello
i=10, f=3.14, ch=X, word=hello
複数の値をまとめて読む
まとめて読むコツ
複数の値を続けて読むときは、フォーマット文字列の空白が柔軟に働くため、"%d %d"
のように書いておけば、スペースや改行で区切られた入力をまとめて扱えます。
#include <stdio.h>
int main(void) {
int a, b;
printf("2つの整数を入力してください(空白区切り): ");
if (scanf("%d %d", &a, &b) == 2) {
printf("合計: %d, 差: %d\n", a + b, a - b);
} else {
printf("2件読み取れませんでした\n");
}
return 0;
}
2つの整数を入力してください(空白区切り): 8 5
合計: 13, 差: 3
戻り値(読み取れた個数)の確認
なぜ戻り値が大事か
scanf
の戻り値は「読み取れた項目数」です。
指定子の数と一致しないなら、入力形式が違う、または途中で失敗しています。
戻り値を無視すると、未初期化の変数を使う危険があります。
#include <stdio.h>
int main(void) {
int x, y;
printf("x y を入力: ");
int n = scanf("%d %d", &x, &y);
if (n != 2) {
printf("エラー: %d 件しか読み取れませんでした\n", n);
return 1;
}
printf("x=%d, y=%d\n", x, y);
return 0;
}
x y を入力: 12 ab
エラー: 1 件しか読み取れませんでした
空白と改行の扱い(スキップの規則)
スキップ規則の要点
数値や%sは、先頭の空白(スペース、タブ、改行)を自動でスキップします。
一方で%c
や%[]
はスキップしません。
これが多くのバグの根本原因です。
- %d, %f, %s は先頭空白を飛ばす
- %c は空白を飛ばさないため、直前の改行をそのまま読んでしまいます
#include <stdio.h>
int main(void) {
int n;
char c;
printf("数値と1文字を順に入力: ");
// 例: ユーザーが「123<Enter>Y<Enter>」のつもりで入力
if (scanf("%d", &n) != 1) return 1;
// "%c" は改行も1文字として読むので、直前の Enter を拾う
if (scanf("%c", &c) != 1) return 1;
printf("n=%d, c=%c(コード=%d)\n", n, c, (int)c);
return 0;
}
数値と1文字を順に入力: 123
Y
n=123, c=
(コード=10)
上の例では%c
が改行(コード10)を読んでしまっています。
対策は後述の「%cが直前の改行を読む問題」で説明します。
scanfの注意点と落とし穴
%cが直前の改行を読む問題
症状と原因
%c は空白をスキップしないため、直前の Enter の改行を読みます。
続けて文字を取りたいときに空文字のように見える現象が起きます。
解決策1: フォーマット前にスペース
フォーマット文字列で" %c"
のように先頭に空白を入れると、scanf
が「任意個の空白」を読み飛ばしてから1文字を読みます。
#include <stdio.h>
int main(void) {
int n;
char c;
printf("数値の後に1文字を入力: ");
if (scanf("%d", &n) != 1) return 1;
// 先頭スペースに注目: 直前の改行や空白をスキップしてから1文字読む
if (scanf(" %c", &c) != 1) return 1;
printf("n=%d, c=%c\n", n, c);
return 0;
}
数値の後に1文字を入力: 123
Y
n=123, c=Y
解決策2: 改行を明示的に捨てる
getchar
で改行を捨てる、あるいは"%*c"
で1文字を読み捨てます。
#include <stdio.h>
int main(void) {
int n;
char c;
printf("数値の後に1文字を入力: ");
if (scanf("%d", &n) != 1) return 1;
// 入力バッファの改行(1文字)を読み捨てる
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* 捨てる */ }
// これで %c が期待通り次の文字を読む
if (scanf("%c", &c) != 1) return 1;
printf("n=%d, c=%c\n", n, c);
return 0;
}
%sは空白で途切れる
症状
%s
は空白で途切れるため、フルネームや文の入力には不向きです。
例えば「Alice Bob」は「Alice」しか入りません。
対策
1行を読みたいならfgets
を使い、必要ならsscanf
で解析します。
単語で十分なら%Ns
で幅指定を行います。
#include <stdio.h>
#include <string.h>
int main(void) {
char name[64];
// 単語でよければ %63s
printf("あなたの名前(単語): ");
if (scanf("%63s", name) == 1) {
printf("単語としての名前: %s\n", name);
}
// 行全体が必要なら fgets
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* 改行まで捨てる */ }
printf("あなたのフルネーム(空白可): ");
if (fgets(name, sizeof(name), stdin)) {
name[strcspn(name, "\n")] = '#include <stdio.h>
#include <string.h>
int main(void) {
char name[64];
// 単語でよければ %63s
printf("あなたの名前(単語): ");
if (scanf("%63s", name) == 1) {
printf("単語としての名前: %s\n", name);
}
// 行全体が必要なら fgets
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* 改行まで捨てる */ }
printf("あなたのフルネーム(空白可): ");
if (fgets(name, sizeof(name), stdin)) {
name[strcspn(name, "\n")] = '\0'; // 改行除去
printf("フルネーム: %s\n", name);
}
return 0;
}
'; // 改行除去
printf("フルネーム: %s\n", name);
}
return 0;
}
あなたの名前(単語): Alice
単語としての名前: Alice
あなたのフルネーム(空白可): Alice Bob
フルネーム: Alice Bob
バッファオーバーフロー対策(幅指定%Ns)
原則
%s
には必ず最大文字数を付けるべきです。
char s[10]
なら"%9s"
とし、終端の'\0'
のために1引くのが原則です。
幅指定なしの%sは危険です。
#include <stdio.h>
int main(void) {
char word[10];
printf("最大9文字の単語を入力: ");
if (scanf("%9s", word) == 1) {
printf("受け取った単語: %s\n", word);
} else {
printf("読み取り失敗\n");
}
return 0;
}
数値以外で無限ループになる
症状
scanf("%d", &x)
で文字などが入力されると、読み取りに失敗して入力が消費されず、同じ位置で繰り返し失敗します。
ループで再入力を促すコードが無限ループに陥ることがあります。
対策
失敗時は戻り値を確認して入力バッファを捨てる
処理を入れます。
#include <stdio.h>
int main(void) {
int x;
while (1) {
printf("整数を入力してください: ");
int n = scanf("%d", &x);
if (n == 1) break;
// 失敗したトークンを捨てる: 改行まで読み捨て
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* 捨てる */ }
printf("整数ではありません。もう一度。\n");
}
printf("OK: %d\n", x);
return 0;
}
整数を入力してください: abc
整数ではありません。もう一度。
整数を入力してください: 123
OK: 123
fflush(stdin)は非標準
誤解と事実
入力ストリームstdinに対するfflush(stdin)
はC標準では未定義動作です。
MSVCなど一部処理系で拡張として動くことがありますが、移植性がなく避けるべきです。
代わりにgetchar
やscanf("%*c")
などで明示的に消費します。
入力バッファの残りを捨てる方法(%c %[^\n])
パターン別の捨て方
1文字捨てたいなら"%c"
、改行まで捨てたいなら"%[^\n]"
と"%*c"
の組み合わせが便利です。
#include <stdio.h>
int main(void) {
int a;
char rest[8];
printf("整数の後に余計な文字を入力してみてください: ");
if (scanf("%d", &a) != 1) return 1;
// 改行までの残りを丸ごと読み捨てる
// %*[^\n] で改行以外を全て捨て、続けて %*c で改行を捨てる
scanf("%*[^\n]"); // 0個でもマッチする可能性あり
scanf("%*c"); // 改行1文字を捨てる
printf("整数だけを採用しました: %d\n", a);
return 0;
}
整数の後に余計な文字を入力してみてください: 55xyz
整数だけを採用しました: 55
安全な入力のレシピ
intを安全に読む(%dと戻り値チェック)
安全パターン
戻り値チェックと「改行まで捨てる」をセットにするのが基本です。
#include <stdio.h>
int main(void) {
int val;
while (1) {
printf("整数(0〜100)を入力: ");
int ok = scanf("%d", &val);
if (ok == 1 && 0 <= val && val <= 100) break;
// 読み捨て
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* discard */ }
printf("無効な入力です。もう一度。\n");
}
printf("受理: %d\n", val);
return 0;
}
doubleを安全に読む(%lf)
%fと%lfの違い
入力でdoubleを読むときは%lf
です。
%f
はfloat*
向けです(出力のprintf
では%f
がdoubleに対応する点と混同しないよう注意)。
#include <stdio.h>
int main(void) {
double d;
printf("実数を入力: ");
if (scanf("%lf", &d) == 1) {
printf("倍精度として取得: %.6f\n", d);
} else {
printf("実数として読み取れませんでした\n");
}
return 0;
}
1文字を読む( %c の前に空白)
失敗しない定型
ほぼ常に" %c"
を使うと、直前の空白や改行を吸収できます。
#include <stdio.h>
int main(void) {
char ans;
printf("続行しますか? (y/n): ");
if (scanf(" %c", &ans) == 1) {
printf("入力: %c\n", ans);
} else {
printf("読み取り失敗\n");
}
return 0;
}
単語を読む(%Nsで長さ制限)
バッファ保護
配列長-1を幅指定するのが鉄則です。
#include <stdio.h>
int main(void) {
char user[32];
printf("ユーザー名(単語): ");
if (scanf("%31s", user) == 1) {
printf("ようこそ %s さん\n", user);
} else {
printf("読み取り失敗\n");
}
return 0;
}
1行を読む(fgets + sscanf)
行入力の標準解
fgets
で行単位に受け、必要ならsscanf
で分解します。
これにより混在した空白や改行に強くなります。
#include <stdio.h>
#include <string.h>
int main(void) {
char line[128];
int a, b;
printf("2つの整数を同じ行に入力してください: ");
if (!fgets(line, sizeof(line), stdin)) return 1;
// 行から2整数を抽出
int n = sscanf(line, "%d %d", &a, &b);
if (n == 2) {
printf("合計=%d, 積=%d\n", a + b, a * b);
} else {
printf("2つの整数を正しく認識できませんでした\n");
}
return 0;
}
複数入力を確実に読む(期待件数を検証)
まとめ読みの検証
期待件数と戻り値を一致確認し、足りなければ再入力を促します。
#include <stdio.h>
int main(void) {
int y, m, d;
while (1) {
printf("日付を入力してください(YYYY MM DD): ");
int n = scanf("%d %d %d", &y, &m, &d);
if (n == 3) break;
// 失敗時は行末まで捨てる
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* discard */ }
printf("形式が違います。やり直してください。\n");
}
printf("入力された日付: %04d-%02d-%02d\n", y, m, d);
return 0;
}
実践例で学ぶscanf
よくある入出力パターン(プロンプト付き)
名前と年齢を聞く
プロンプトを丁寧に出し、入力ごとに検証します。
異常系では必ずバッファを掃除します。
#include <stdio.h>
#include <string.h>
int main(void) {
char name[64];
int age;
// 名前は行で受け取る
printf("お名前を教えてください: ");
if (!fgets(name, sizeof(name), stdin)) return 1;
name[strcspn(name, "\n")] = '#include <stdio.h>
#include <string.h>
int main(void) {
char name[64];
int age;
// 名前は行で受け取る
printf("お名前を教えてください: ");
if (!fgets(name, sizeof(name), stdin)) return 1;
name[strcspn(name, "\n")] = '\0'; // 改行除去
// 年齢は数値として受け取り、検証
while (1) {
printf("年齢を教えてください: ");
int n = scanf("%d", &age);
if (n == 1 && age >= 0 && age <= 150) break;
// 行末まで捨てる
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* discard */ }
printf("年齢は0〜150の整数で入力してください。\n");
}
printf("%s さんは %d 歳ですね。\n", name, age);
return 0;
}
'; // 改行除去
// 年齢は数値として受け取り、検証
while (1) {
printf("年齢を教えてください: ");
int n = scanf("%d", &age);
if (n == 1 && age >= 0 && age <= 150) break;
// 行末まで捨てる
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* discard */ }
printf("年齢は0〜150の整数で入力してください。\n");
}
printf("%s さんは %d 歳ですね。\n", name, age);
return 0;
}
お名前を教えてください: Tanaka Taro
年齢を教えてください: 20
Tanaka Taro さんは 20 歳ですね。
エラー時の再入力ループ
項目ごとに堅牢に
各ステップで検証し、失敗時にやり直す構造にすると、入力不正でプログラムが止まりません。
#include <stdio.h>
#include <string.h>
static void discard_line(void) {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* discard */ }
}
int main(void) {
char email[64];
int qty;
// メールアドレスは空白なしを想定し、幅指定付きで受け取る
while (1) {
printf("メールアドレス: ");
int n = scanf("%63s", email);
if (n == 1) break;
discard_line();
printf("もう一度入力してください。\n");
}
// 個数は整数で受け取り、範囲チェックも実施
while (1) {
printf("個数(1〜100): ");
int n = scanf("%d", &qty);
if (n == 1 && 1 <= qty && qty <= 100) break;
discard_line();
printf("1〜100の整数を入力してください。\n");
}
printf("受付完了: %s, 個数=%d\n", email, qty);
return 0;
}
メールアドレス: user@example.com
個数(1〜100): 250
1〜100の整数を入力してください。
個数(1〜100): 3
受付完了: user@example.com, 個数=3
失敗例の直し方(改行/空白/範囲)
失敗例1: %cが改行を読む
悪い例:
// 悪い: 直前の改行を拾う
scanf("%c", &ch);
良い例:
// 良い: 先頭空白でスキップ
scanf(" %c", &ch);
失敗例2: %sで長文が落ちる
悪い例:
// 悪い: 幅指定なし。空白も不可
char buf[16];
scanf("%s", buf);
良い例:
// 良い: 幅指定で保護
scanf("%15s", buf);
// さらに行を受けたいなら
// fgets(buf, sizeof(buf), stdin);
失敗例3: 無限ループ
悪い例:
// 悪い: 失敗時に入力を消費しない
while (scanf("%d", &x) != 1) {
printf("もう一度: ");
}
良い例:
// 良い: 行末まで捨てる
while (1) {
int ok = scanf("%d", &x);
if (ok == 1) break;
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) { /* discard */ }
printf("もう一度: ");
}
失敗例4: doubleに%fを使う
悪い例:
double d;
scanf("%f", &d); // 型不一致で未定義動作
良い例:
double d;
scanf("%lf", &d); // 正しい
上記の修正原則は「型合わせ」「幅指定」「戻り値チェック」「入力消費」の4点です。
この4点を守るだけで、多くの入出力問題は回避できます。
まとめ
scanf
は強力ですが、空白や改行、誤入力で簡単に破綻します。
本記事では、基本の指定子とアドレス渡し、複数入力と戻り値チェック、空白スキップの規則から、%cの改行問題や%sの空白問題、幅指定でのバッファ保護、無限ループ回避までを体系的に解説しました。
実装では以下を習慣にしてください。
- 指定子と型を正しく対応させる(
double
は%lf
) - 戻り値を必ず検証する
- 必要に応じて入力を捨てる(
getchar
や%*[^\n]
+%*c
) - %sには幅指定を付ける
- %cの前には空白を置く
- fflush(stdin)は使わない
これらを守れば、初心者でも堅牢なコンソール入力を実装できます。
慣れてきたらfgets
+sscanf
や文字列処理を活用し、より複雑な入力にも対応できるようにしましょう。