C言語基礎|scanf関数:書式付きの入力

scanfは便利だけど“クセも強い”!読み取りの仕組みと失敗パターンを押さえて、安全に入力を扱えるようになろう。

scanf は、キーボード入力を「型に合わせて変換しながら」読み取れる、とても強力な関数です。
でもその分、書式の書き方・空白の扱い・戻り値チェックをサボると、思わぬバグが起きやすいのも事実です。

この記事では、scanf の動き方を「指令」「変換指定」「失敗の種類」「戻り値」の観点で整理して、
“何が起きているか”をイメージできるように、表や図で丁寧に解説していきます。

scanf の役割(何をしてくれる関数?)

scanf は、第1引数の書式文字列(format)に書かれたルールに従って入力を読み取り、変換した値を第2引数以降の変数へ代入します。

書式(scanf 関数の形式)

int scanf(const char * restrict format, ...);
  • 第1引数 format:入力をどう読むか(指令)を書く
  • 第2引数以降:読み取った値を入れる場所(だいたいポインタ)

サンプルプログラム

「年齢(整数)と身長(小数)」を読み取って、結果を表示する例です。
ポイントは 戻り値のチェック入力失敗時の扱い です。

プロジェクト名:chap13-14-1 ソースファイル名:chap13-14-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

int main(void)
{
    int age;
    double height;

    printf("年齢と身長を入力してください(例:20 170.5)\n");
    printf("入力:");

    /* scanfは成功した“項目数”を返すので、2なら両方読めたことになる */
    if (scanf("%d%lf", &age, &height) == 2) {
        printf("読み取り成功:年齢=%d、身長=%.1f\n", age, height);
    } else {
        printf("読み取り失敗:入力形式を確認してください。\n");
    }

    return 0;
}

scanf の「指令」ってなに?

scanf の書式文字列に書ける“指令”は、大きく3種類です。

scanf の指令の種類

指令の種類書式内の例役割よくある注意点
空白類文字(スペースや改行など)入力側の空白類文字を読み飛ばす書式内に空白を入れると、入力側の改行なども飛ばす
通常の文字, や : など入力に“その文字が来ること”を要求する一致しないとそこで失敗し、残りは読まれない
変換指定%d, %lf など入力を数値や文字列として変換し代入引数の型(ポインタ)を間違えると危険

表の説明

  • scanf は、書式を左から順に“指令として実行”します。
  • 途中で失敗したら、その時点で中断して戻ります(だから戻り値チェックが超大事)。

変換指定の構造

scanf の変換指定は、次の部品でできています。

図:scanf の変換指定の構造

図の説明

  • 代入抑止 * を付けると「読み取るけど代入しない」になります。
  • 最大フィールド幅は「読む最大文字数」を制限します(特に文字列で重要)。
  • 長さ修飾子は「どの型に入れるか」を指定します。
  • 変換指定子は「どう解釈するか(整数/小数/文字列など)」を決めます。

代入抑止文字 *(読み捨ての道具)

代入抑止 * の使いどころ

目的書式例どう動く?例のイメージ
途中の区切りを無視したい%d%*c%d1個文字を読み取るが代入しない10,20 のカンマを捨てる
不要な値を読み飛ばしたい%*d%d最初の整数を捨てて次だけ読む先頭の番号を無視

表の説明

  • 入力を“消費だけして捨てる”ことができます。
  • ただし「何文字捨てるか」は変換指定子次第なので、設計は慎重に。

最大フィールド幅(入力を読みすぎないためのブレーキ)

scanf の怖いところの1つが「文字列の読み込みで、バッファを超える」事故です。
そこで最大フィールド幅が役立ちます。

最大フィールド幅の効果(特に s で重要)

意味メリット
%5s最大5文字まで読む(終端ナル文字は別)配列サイズに合わせて事故を防ぐ
%3d最大3文字の整数として読む想定外の長い数字を抑える

表の説明

  • 文字列入力(%s)は、幅指定がないと危険になりやすいです。
  • 数値でも幅指定は使えますが、まずは戻り値チェックと組み合わせるのが王道です。

長さ修飾子(どの型に入れるかを決める)

scanf は「代入先の型」を正しく伝えないと危険です。
そのために長さ修飾子が用意されています。

よく使う長さ修飾子と組み合わせ(超実用寄り)

入れたい型変換指定代入先引数の型
int%dint へのポインタ
long%ldlong へのポインタ
long long%lldlong long へのポインタ
double%lfdouble へのポインタ
long double%Lflong double へのポインタ
unsigned int%uunsigned int へのポインタ

表の説明

  • 特に覚えやすいのはこれです:
    ・double は %lf(printfと違うので注意!)
    ・long は l、long long は ll
  • 型と書式がズレると、読み取り結果が壊れたり未定義動作になったりします。

変換指定子(conversion specifier)の要点

質問文に出てきた代表格を、実務で使う観点でまとめます。

主要な変換指定子のまとめ

指定子読み取るもの例入力注意点
d10進の符号付き整数-12代入先は符号付き整数型
i基数を自動判定する整数012, 0x100で始まると8進、0xで16進になる
u10進の符号なし整数42負数を入れると期待通りにならない
x16進整数FF, 0x2a代入先は符号なしが基本
f, e, g, a浮動小数点3.14代入先は float/double/long double(長さ修飾子で決まる)
s文字列(空白まで)hello配列のサイズ管理が重要(幅指定推奨)
c文字(1文字)A空白も読む(改行も読む)ので注意
nここまでに読んだ文字数-代入のみ、入力は消費しない
%%文字そのもの%%% と書く

表の説明

  • c は空白を読み飛ばさないので、直前の改行を拾ってしまう事故がよくあります。
  • s は空白で止まるので「空白込みの行」を取りたいときは別の手段(fgetsなど)が向きます。

scanf の実行手順(本文の「手順」を図でつかむ)

scanf がどう進むかを、イメージ図にします。

図:scanf が入力を処理する流れ

書式文字列を左から読む
   ↓
指令を1つ実行する
   ↓
成功 → 次の指令へ
失敗 → そこで中断して戻る
   ↓
戻り値=代入できた項目数(0,1,2,...)
(1個も代入できない状態で入力が尽きた等の場合はEOFの可能性)

図の説明

  • “全部成功するまで進む”のではなく、“途中で止まる”のが重要ポイントです。
  • だから、読み取りが必要な個数ぶん成功したかを戻り値で判断します。

入力誤りと照合誤り(失敗の種類)

本文にある2種類の失敗は、原因と対処の考え方が違います。

失敗の種類

種類何が起きた?対応の考え方
入力誤り入力が取れない/エラーEOF、読み取りエラー入力終了として扱うことが多い
照合誤り入力の形が期待と違う数字が欲しいのに abc入力を捨てる・再入力を促す

表の説明

  • “照合誤り”は「入力が残ったまま」になりがちです。
    だからループで再入力させるなら、入力バッファの処理も考える必要があります。

scanf の戻り値(最重要ポイント)

scanf は 代入できた項目数 を返します。

戻り値の意味

戻り値意味
期待した個数(例:2)その個数ぶん代入できた(成功)
01個も代入できない(照合誤りの可能性)
EOF入力が尽きた/読み取り不可能(入力誤り側)

表の説明

  • 例:scanf("%d%lf", &age, &height) なら、成功は戻り値 2。
  • 戻り値チェックをしないと、変数が更新されていないのに処理が進むことがあります。

restrict ポインタ(この記事では“気にしすぎなくてOK”)

scanf の宣言に出てくる restrict は、コンパイラ最適化のためのヒントです。
学習段階では「標準ライブラリの宣言に付いていることがある」くらいで十分です。
今大事なのは、scanf の書式・引数・戻り値を正しく扱うことです。