【C言語】 scanf入門: 使い方の基本とよくある失敗の直し方

C言語でキーボード入力を扱うならscanfの仕組みを正しく理解することが大切です。

便利な反面、使い方を誤ると改行の食べ残しやバッファオーバーフロー、無限ループなどの厄介な不具合を招きます。

ここでは基本からよくある失敗の直し方まで、入門者向けに丁寧に解説します。

scanf入門(キーボード入力の基本)

scanfの書式と&演算子

scanfはフォーマット文字列と、読み取った値を書き込むためのアドレス(格納先)を渡して使います。

変数のアドレスは&演算子で渡します。

配列(特にchar name[ ]のような文字配列)は配列名が先頭要素へのポインタに暗黙変換されるため、%sでは通常&を付けません。

  • 典型形: scanf("フォーマット", &変数1, &変数2, ...);
  • 例外: 文字列%schar buf[100]; scanf("%99s", buf);のように配列名をそのまま渡す

簡単な例を見てみます。

C言語
#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文字をそのまま取り、空白や改行も読みます。

表: よく使う指定子の要点

指定子対象(読み取る値)引数の型(渡すべきポインタ型)空白の扱い・特徴
%d10進の整数int*先頭の空白は無視、符号(+/-)可
%f浮動小数float*先頭の空白は無視、指数表記可
%lf浮動小数double*doubleを読む時はこちら
%c1文字char*空白・改行もそのまま1文字として読む
%s単語(空白まで)char*最初の空白で終了。幅指定で安全に

注意: printfでは%fdoubleを渡しますが、scanfではdouble%lffloat%fを使います。

混同に注意してください。

戻り値で入力成功をチェック

scanfの戻り値は「成功して代入できた項目数」です。

期待数に満たなければ読み取り失敗です。

失敗時は不正入力を捨てないと同じ位置で詰まります。

C言語
#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)と注意点

整数と浮動小数を読む

整数は%dint、より大きい範囲を扱うなら%ld(long)、%lld(long long)を使います。

浮動小数はdouble%lfを使うのが実務では一般的です。

C言語
#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

複数入力と空白の扱い

フォーマット文字列中の「空白文字」は入力中の任意個の空白(スペース、タブ、改行)にマッチします。

区切りがカンマなど固定なら、その記号をフォーマットに入れます。

C言語
#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なら範囲エラーや不正文字を検知できます。

C言語
#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の前に空白を入れます。

空白は「任意個の空白を読み飛ばす」指示になります。

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バイトを残しましょう。

C言語
#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で解析します。

scanffgetsを混在させるときは、scanf後に残る改行を捨ててからfgetsするのが安全です。

C言語
#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でよくある失敗と直し方

&を付け忘れる/指定子と型の不一致

&を忘れると「値」ではなく「その値だと思い込んだポインタ」に書き込もうとしてクラッシュの原因になります。

また指定子と型が一致しないと未定義動作です。

C言語
#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

入力バッファに残る改行の対処

数値の直後に%cfgetsで読むと、前の入力で押したEnterの改行が残っていて即座に空読みになることがあります。

対処は以下の通りです。

  • scanf(" %c", &ch);のように%cの前に空白を入れる
  • ヘルパ関数で改行まで読み捨ててから次の入力を行う
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で読み取り、必要ならsscanfstrtol/strtodで解析する
  • scanfで破棄指定子を使って残りを捨てる方法もありますが、状況によりブロックの可能性があるため注意が必要です(例: scanf("%*[^\n]"); scanf("%*c");)

無限ループや詰まりを防ぐ

scanfがマッチしない入力に遭遇すると項目数0を返し、入力位置は進みません。

この状態で「同じscanfを繰り返すだけ」のループを書くと、同じ不正文字を何度も読み直して無限ループになります。

必ず不正な入力を消費しましょう。

C言語悪い例(不正文字を消費していない)
// 入力が"abc"のとき、ずっとrc==0で同じ場所に留まり続ける
while (scanf("%d", &n) != 1) {
    printf("整数を入力してください: ");
}

C言語良い例(不正入力を破棄する)
#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行を読み取り、sscanfstrtolで解析する方法は、失敗時に再入力しやすく、安全性も高いのでおすすめです。

C言語
#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点です。

  • 第一にフォーマット指定子と渡す型(特に%lfdouble)を厳密に合わせ、%sには必ず幅指定を入れます。
  • 第二に戻り値で成否を確認し、失敗時は必ず不正入力を消費してから再試行します。
  • 第三に%cの改行対策やfgets併用など、目的に応じた読み取り手段を使い分けます。

入力の安全性を優先するなら「行で受けてから解析」が有力な選択肢です。

これらの基本を押さえれば、入門段階でも堅牢で読みやすいコンソール入力が実現できます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!