C言語の基本|scanfで改行が残る問題

見えない改行の正体が分かれば、scanfの文字入力はもう怖くない。入力バッファの仕組みを理解して、安定した入力処理を書こう。

C言語でキーボード入力を練習していると、数値は正しく入力できたのに、その直後の文字入力が飛ばされてしまうことがあります。

たとえば、次のような順番で入力するプログラムです。

  • 整数を入力する
  • 1文字を入力する
  • もう一度整数を入力する

本来なら3回入力するはずなのに、1文字を入力する場面でプログラムが勝手に次へ進んでしまうことがあります。

この現象は、scanfが故障しているわけでも、プログラムの実行環境がおかしいわけでもありません。数値を入力したあと、Enterによって入力された改行文字 \n が入力バッファに残り、それを次の %c が読み取っていることが原因です。

この記事では、次の内容を順番に確認していきます。

  • 入力バッファに何が入るのか
  • 数値を読み取ったあとに改行が残る理由
  • %cが改行の影響を受けやすい理由
  • getcharを使って改行を読み捨てる方法
  • %cの前に半角スペースを入れる方法

入力バッファの動きを理解すると、文字入力が飛ばされたように見える理由が分かり、落ち着いて対処できるようになります。

入力バッファとは

キーボードから入力した文字は、入力すると同時に変数へ格納されるわけではありません。通常は、いったん入力バッファと呼ばれる一時的な領域に保存されます。

たとえば、キーボードから次のように入力したとします。

27

画面上では27だけが入力されたように見えますが、実際には数字を入力したあとにEnterを押しています。そのため、入力バッファには次の内容が並びます。

2  7  \n

最後の \n は、Enterによって入力された改行文字です。

ここで、次のscanfを実行します。

scanf("%d", &quantity);

%dは整数として読み取れる27の部分を取り出し、変数quantityへ格納します。

scanfが読み取る部分:2  7
入力バッファに残る部分:\n

数字の読み取りには改行文字が必要ないため、改行文字が入力バッファに残ることがあります。この残った改行文字が、次の文字入力に影響します。

図1:数値入力後に改行文字が残る仕組み

この図から分かること

キーボードから27を入力してEnterを押すと、数字だけでなく改行文字も入力バッファに入ります。

scanf("%d", &quantity); が読み取るのは、整数として必要な27の部分です。読み取り後も改行文字 \n が残るため、続けて %c を使用すると、その改行文字が読み取られる可能性があります。

文字入力が飛ばされる典型的なプログラム

数値を入力した直後に %c で1文字を読み取ると、入力バッファに残っている改行文字の影響を受けます。

次のプログラムで、どのような動きになるのか確認してみましょう。

商品数、区分記号、割引率を順番に入力するプログラム

ファイル名:5_10_1.c

#include <stdio.h>

int main(void)
{
    int quantity;
    int discount;
    char category;

    printf("商品数を入力してください > ");
    scanf("%d", &quantity);

    printf("区分記号を1文字入力してください > ");
    scanf("%c", &category);  /* 残っている改行を読むことがある */

    printf("割引率を入力してください > ");
    scanf("%d", &discount);

    printf("\n入力内容:商品数=%d、区分=%c、割引率=%d%%\n",
           quantity, category, discount);

    return 0;
}
期待する実行結果
商品数を入力してください > 12
区分記号を1文字入力してください > A
割引率を入力してください > 5

入力内容:商品数=12、区分=A、割引率=5%
実際に起こりやすい実行結果の例
商品数を入力してください > 12
区分記号を1文字入力してください > 割引率を入力してください > 5

入力内容:商品数=12、区分=
、割引率=5%

本来は、区分記号としてAなどの文字を入力したいところです。しかし、区分記号の入力を待たずに、割引率の入力へ進んでいます。

最初の入力で12と入力してEnterを押すと、入力バッファには次の内容が入ります。

1  2  \n

最初のscanfは、整数として読み取れる12を変数quantityに格納します。

scanf("%d", &quantity);

この時点では、入力バッファに \n が残っています。

続いて、次のscanfが実行されます。

scanf("%c", &category);

%cは、入力バッファにある次の1文字をそのまま読み取ります。そのため、新しい文字が入力されるのを待たず、すでに残っている \n を変数categoryに格納します。

処理の流れは次のとおりです。

  1. 12とEnterが入力される
  2. scanf("%d", &quantity); が12を読み取る
  3. 入力バッファに \n が残る
  4. scanf("%c", &category); が残っていた \n を読み取る
  5. 区分記号の入力を待たず、次の処理へ進む

実行結果では、categoryに改行文字が格納されています。そのため、区分を表示する場所で改行が発生しています。

なぜ%cは改行の影響を受けやすいのか

scanfで使う変換指定によって、入力前の空白文字に対する扱いが異なります。

%dや%lfは、数値を読み取る前に空白類を読み飛ばします。ここでいう空白類には、主に次の文字が含まれます。

  • 半角スペース
  • 改行文字
  • タブ文字

一方、%cは空白類を自動的に読み飛ばしません。入力バッファにある次の1文字を、そのまま文字として読み取ります。

変換指定読み取る内容入力前の空白類改行が残っている場合
%d10進整数読み飛ばす改行を越えて整数を読み取れる
%lfdouble型の実数読み飛ばす改行を越えて実数を読み取れる
%c1文字読み飛ばさない残っている改行を1文字として読み取る

この違いにより、数値を連続して入力するときは問題が見えにくく、数値の直後に %c を使ったときに問題が表れやすくなります。

図2:%d・%lfと%cの空白文字の扱い

この図から分かること

%dと%lfは、入力の先頭に改行やスペースがあっても、それらを読み飛ばしてから数値を探します。

それに対して、%cは次にある1文字をそのまま読み取ります。改行文字も1文字として扱われるため、入力バッファの先頭に \n が残っていると、その \n が文字変数へ格納されます。

getcharで残った改行文字を読み捨てる方法

改行文字への対処方法の1つが、getcharを使って入力バッファから1文字を取り出す方法です。

数値を読み取った直後に、次の処理を追加します。

(void)getchar();

getcharは、入力バッファから1文字を読み取ります。数値入力の直後に改行文字だけが残っていれば、この処理によって \n が取り除かれます。

getcharで改行を取り除いてから区分記号を入力するプログラム

ファイル名:5_10_2.c

#include <stdio.h>

int main(void)
{
    int quantity;
    int discount;
    char category;

    printf("商品数を入力してください > ");
    scanf("%d", &quantity);

    (void)getchar();  /* 入力バッファに残った改行を読み捨てる */

    printf("区分記号を1文字入力してください > ");
    scanf("%c", &category);

    printf("割引率を入力してください > ");
    scanf("%d", &discount);

    printf("\n入力内容:商品数=%d、区分=%c、割引率=%d%%\n",
           quantity, category, discount);

    return 0;
}
実行結果の例
商品数を入力してください > 12
区分記号を1文字入力してください > A
割引率を入力してください > 5

入力内容:商品数=12、区分=A、割引率=5%

このプログラムでは、最初のscanfを実行した直後にgetcharを呼び出しています。

scanf("%d", &quantity);
(void)getchar();

12とEnterを入力したあとの状態を追うと、次のようになります。

入力直後:
1  2  \n

%dで12を読み取ったあと:
\n

getcharを実行したあと:
入力バッファは空

入力バッファから改行文字が取り除かれたため、次の %c は新しい文字が入力されるまで待ちます。

そこでAを入力すると、変数categoryにはAが格納されます。

(void)は、getcharが読み取った文字を今回は利用しないことを明確にするために付けています。getcharの目的は、入力バッファに残っている改行文字を読み捨てることです。

この方法には、次の特徴があります。

  • 改行を1文字読み捨てる処理が分かりやすい
  • 入力バッファの仕組みを確認しながら対処できる
  • 入力バッファの先頭から1文字だけを読み取る

今回のように、数値の直後に改行文字だけが残る入力では、getcharによって問題を解消できます。

%cの前に半角スペースを入れる方法

もう1つの方法は、scanfの書式文字列で %c の前に半角スペースを入れる方法です。

scanf(" %c", &category);

%cの直前にある半角スペースは、単なる見た目の調整ではありません。

scanfの書式文字列に空白を記述すると、入力バッファにある空白類を読み飛ばすという指示になります。

そのため、次のような空白類を読み飛ばしてから、%cで1文字を読み取ります。

  • 改行文字
  • 半角スペース
  • タブ文字

入力バッファに改行文字が残っていても、その改行文字を読み飛ばしてから、次に入力される空白ではない1文字を読み取ってくれます。

空白類を読み飛ばして区分記号を入力するプログラム

ファイル名:5_10_3.c

#include <stdio.h>

int main(void)
{
    int quantity;
    int discount;
    char category;

    printf("商品数を入力してください > ");
    scanf("%d", &quantity);

    printf("区分記号を1文字入力してください > ");
    scanf(" %c", &category);  /* 空白類を読み飛ばして1文字読む */

    printf("割引率を入力してください > ");
    scanf("%d", &discount);

    printf("\n入力内容:商品数=%d、区分=%c、割引率=%d%%\n",
           quantity, category, discount);

    return 0;
}
実行結果の例
商品数を入力してください > 12
区分記号を1文字入力してください > B
割引率を入力してください > 8

入力内容:商品数=12、区分=B、割引率=8%

最初の数値を読み取ったあと、入力バッファには改行文字が残っています。

入力バッファ:
\n

ここで、次のscanfが実行されます。

scanf(" %c", &category);

書式文字列の先頭にある半角スペースが、入力バッファ内の改行文字を読み飛ばします。その後、scanfは空白ではない文字が入力されるまで待ちます。

ユーザーがBを入力すると、%cがその文字を読み取り、変数categoryへ格納します。

この書き方には、次の特徴があります。

  • 改行、半角スペース、タブなどの空白類を読み飛ばせる
  • getcharを別に記述する必要がない
  • 数値入力の直後でも文字入力が安定しやすい
  • %cを使う部分だけで対策できる

入門段階では、数値のあとに空白ではない1文字を入力したい場合、scanf(" %c", &category); と書く方法が分かりやすく、使いやすいでしょう。

getchar方式とスペース付き%c方式の違い

2つの方法は、どちらも入力バッファに残った改行文字への対策として利用できます。ただし、読み飛ばす範囲が異なります。

方法書き方読み飛ばす内容特徴
getchar方式(void)getchar();入力バッファの先頭から1文字読み捨てる処理が分かりやすい
スペース付き%c方式scanf(" %c", &category);連続する空白類短く書けて、改行以外の空白にも対応しやすい

getchar方式では、入力バッファの先頭にある1文字を読み捨てます。数値の直後に改行文字だけが残っている場合は、その改行文字を取り除けます。

スペース付き%c方式では、入力バッファにある空白類をまとめて読み飛ばします。改行文字だけでなく、半角スペースやタブが残っている場合にも、それらを越えて次の文字を読み取れます。

入門の範囲では、数値のあとに空白ではない1文字を読み取る場合、次の書き方を基本として覚えておくとよいでしょう。

scanf(" %c", &category);

入力バッファの変化を順番に確認しよう

数値のあとに文字を入力するとき、入力バッファの内容は次のように変化します。

最初に12と入力してEnterを押したときの状態です。

入力バッファ:
1  2  \n

scanf("%d", &quantity); を実行すると、整数として読み取れる12が取り出されます。

読み取られる内容:
1  2

残っている内容:
\n

続けてscanf("%c", &category); を実行すると、入力バッファの先頭にある \n が読み取られます。

categoryに格納される文字:
\n

一方、scanf(" %c", &category); を実行した場合は、最初の半角スペースの指示によって \n が読み飛ばされます。

読み飛ばされる文字:
\n

そのあとにBと入力すると、%cがBを読み取ります。

categoryに格納される文字:
B

図3:スペース付き%cが改行を読み飛ばす流れ

この図から分かること

scanf("%d", &quantity); は数字を読み取りますが、Enterによって入力された改行文字までは取り除かないことがあります。

scanf(" %c", &category); の先頭にある半角スペースは、その改行文字を読み飛ばす役割を持っています。改行を読み飛ばしたあとに %c が実行されるため、ユーザーが新しく入力したBを正しく変数へ格納できます。

scanfが文字入力を無視しているわけではない

文字入力が飛ばされたように見えると、scanfが何も読み取らなかったように感じるかもしれません。

しかし、scanf("%c", &category); は正しく1文字を読み取っています。その1文字が、画面では見えにくい改行文字だったということです。

入力バッファに残っていた文字:\n
%cが読み取った文字:\n

scanfは指示されたとおりに、入力バッファの先頭にある1文字を変数へ格納しています。

問題は、scanfが文字を読み取っていないことではありません。ユーザーが入力しようとしていた文字よりも先に、入力バッファ内の改行文字を読み取っていることです。

Enterも入力データの一部になる

Enterは入力を確定するためのキーですが、同時に改行文字 \n を入力します。

改行文字は通常の画面では見えません。そのため、数字だけが入力バッファに入っているように感じやすいのですが、実際にはEnterによる改行文字も含まれています。

画面上で入力した内容:
12

入力バッファ内の内容:
1  2  \n

この見えない改行文字を意識すると、数値入力の直後に %c が実行されたときの動きを理解しやすくなります。

数値のあとに1文字を入力するときの基本ルール

数値を読み取ったあとに空白ではない1文字を入力したい場合は、%cの前に半角スペースを入れます。

scanf(" %c", &category);

まずは、次の3つのポイントを覚えておきましょう。

  • Enterによる改行文字は入力バッファに入る
  • %cは改行文字も1文字として読み取る
  • scanf(" %c", &category); は改行などの空白類を読み飛ばしてから文字を読み取る