getchar関数

コンソールからの入力の読み取り

getchar関数は標準入力(コンソール画面)から文字を受け取ります。

int getchar()
標準入力から文字を受け取る。

標準入力というのは通常はコンソール画面上のキーボード入力です。
以下の説明はすべてキーボード入力を前提とします。

getchar関数を実行すると、プログラムはユーザーからのキー入力待ちとなり一時停止状態となります。
ユーザーからの入力はバッファという一時保存領域に格納されます。
Enterキーを入力するとキー入力の終わりとなり、プログラムは再開しバッファから先頭の一文字を受け取ります。
読み取られなかった残りの文字はバッファに残ったままとなります。
なお、プログラムの開始直後はバッファは空の状態です。

サンプルコード1(ダメな例)

getchar関数は「キーボード入力をさせて文字を受け取る」のではなく「バッファから文字を受け取る」ものです。
入力待ちになるのはバッファが空の場合だけで、すでにバッファにデータがある場合はキー入力待ちになることなくバッファから文字を読み取り、すぐに制御を返します。


#include <stdio.h>

int main()
{
    int c = getchar();

    //受け取った文字を表示
    printf("%c", c);

    //プログラムを一時停止して
    //画面を確認できるようにするつもり
    getchar();
}

このコードはユーザーにキー入力をさせ、先頭の一文字を表示します。
しかし何か文字を入力してEnterキーを押下した瞬間にプログラムは終了してしまいます。

最初のgetchar関数でキー入力をバッファに格納するのですが、プログラムに制御が返った後に変数cに受け取るのは先頭の文字だけです。
残りの入力はバッファに残ったままですから、二回目のgetchar関数はキー入力待ちになることはなく、バッファから次の文字を受け取ります。
そのままプログラムは続行されますので、main関数の終端に達し、画面を確認する暇なくプログラムは終了してしまいます。

サンプルコード2

ユーザーが何文字の入力を行うかはプログラマは知ることはできないので、期待通りの動作をさせるには少し工夫する必要があります。
何文字の入力があっても良いように、通常はループ文と組み合わせて使用されます。


#include <stdio.h>

int main()
{
    int c = 0;

    printf("何か文字を入力してください。\n");
    do {
        c = getchar();
        printf("%c", c);
    } while (c != '\n');

	//プログラムを一時停止する
    getchar();
}
何か文字を入力してください。
abc
abc

実行結果中の赤字はユーザーからの入力を表わしています。

ループ文を使用して、改行文字(\n)を受け取るまでgetchar関数を繰り返し実行しています。
キー入力の最後はEnterキーの入力ですから、改行文字を読み取ったらバッファにデータは残っていないということになります。
最後のgetchar関数の実行時はバッファは空なので、期待通りプログラムはキー入力待ちになり、もう一度Enterキーを押下すると終了します。

サンプルコード3

先頭の一文字だけを読み取り、残りのバッファを空にするサンプルです。


#include <stdio.h>

int main()
{
    int c = 0;
    
    do {
        printf("プログラムを終了しますか?。\n");
        printf("y=終了, n=続行: ");

        //1.getchar関数を実行する
        //2.戻り値を変数cに格納する
        //3.変数cと'\n'を比較する
        //4.改行のみの入力だった場合はループ先頭に戻る
        if ((c = getchar()) == '\n')
            continue;

        //改行文字までバッファを読み捨てる
        while (getchar() != '\n');

		//この時点でバッファは空になっている

    //変数cの中身が'y'ならループを抜ける
    } while (c != 'y');

    printf("プログラムを終了します。");
    getchar();
}
プログラムを終了しますか?。
y=終了, n=続行: a
プログラムを終了しますか?。
y=終了, n=続行: 
プログラムを終了しますか?。
y=終了, n=続行: yes
プログラムを終了します。

入力文字の先頭が「y」の場合にループを抜けプログラムを終了するコードです。
(「n」で続行としていますが、「y」以外なら続行されます。

コード中にもコメントをしていますが、少しややこしいのは以下の箇所です。


if ((c = getchar()) == '\n')
	continue;

代入式を評価すると、代入された値が返ります。
これを利用して、変数への代入と、代入した値との比較を一行で記述しています。
この処理は、何も文字を入力せずにEnterキーが押下された場合にループをやり直すためのものです。

代入式自体を括弧で囲わないと演算子の優先順位が変わってしまい、意味が変わるので注意してください。
以下のように書くと「比較してから代入」という順序になり、比較した結果(真偽値)が変数cに代入されてしまいます。


if (c = getchar() == '\n')
	continue;

演算子の優先順位については演算子と予約語の一覧を参考にしてください。

バッファのクリア

「Enterキーのみが押下された場合」は先ほどの処理で対処できました。
次に「二文字以上が入力された場合」への対処が必要です。


//改行文字までバッファを読み捨てる
while (getchar() != '\n');

これは単純に、改行文字が読み取られるまでgetchar関数を繰り返し実行しています。
while文にはループする処理となる「文」が必要ですが、セミコロン(;)がその文に該当します。
要するにこれは何もしないwhile文ですが、条件式内の処理は式が成立している間は繰り返されるためこのような書き方ができます。

このループを抜けた後はバッファは空になっているので、再度getchar関数を実行するとキー入力待ちになります。

getchar関数の戻り値の型

「文字」はC言語ではchar型ですが、getchar関数はint型の値を返します。
C言語のchar型というのは文字型であり、文字を格納する目的のデータ型です。
逆に言えば文字以外の情報を格納する目的ではありません。

getchar関数は必ず文字を返すわけではなく、エラーを示す値を返すこともあります。
この時、char型でエラーを返してしまうと、それは(関数が成功して返された)正常な文字なのか、エラー値なのかを判別できません。
(char型の範囲の値すべてが文字として割り当てられていることもあり得る)
そこでchar型よりも範囲の広いint型を戻り値の型とし、エラー値をchar型の範囲外の値に設定することでこの問題を回避しています。

EOF

先ほどはgetchar関数で入力された文字を読み取るとき、改行文字(\n)をバッファの終端として使用していました。
しかし、バッファの終端には常に改行文字が存在するとは限りません。
標準入力はキーボード以外を指定することもできます。
例えばファイルを入力先として指定することもできるのですが、この場合改行文字はファイルの終端を示しているとは限りません。

入力の終端に達すると、getchar関数はEOFという定数を返す仕様になっています。
ただし通常のキー入力では、改行文字の次の文字を読み取ろうとするとキー入力待ちとなり、EOFを取得することはありません。
ではキーボード入力のみを想定したプログラムならばEOFは無視して良いかというと、そういうわけにも行きません。
キー入力時に、Windowsなら「Ctrl + Z」キー、Linuxなら「Ctrl + D」キーを押下するとEOFを入力することができます。

先ほど示したプログラムはこれを考慮していないため、EOFが入力されると意図しない動作となります。
今回のコードではもう一度キー入力が発生するだけで大した問題にはなりませんが、意図しない動作は可能な限り修正すべきでしょう。

EOFは「End Of File」の略で、「ファイル終端」という意味です。
標準入力は、キーボード入力もファイルからの入力も「ファイル」として扱います。

ファイルの終端を検出したとき、getchar関数がEOFを返すのは正常な動作であり、エラーが発生したわけではありません。

EOFを正しく処理するには以下の三つの関数を使用します。

feof関数

getchar関数は、正常にファイル終端に達した場合のほか、何らかの読み取りエラーが発生した時もEOFを返します。
feof関数はファイルが終端に達しているか否かを判定します。

int feof(
 FILE *stream
);
ファイルストリームstreamが終端に達している場合は0以外を返す。
それ以外の場合は0を返す。

ストリームというのは「流れ」という意味で、プログラムと実データとの間にバッファを作成し、データをスムースにやり取りできるようにしたものです。
ファイルやキーボードなど、データをやり取りする対象が変わってもプログラム上は同じように扱うことができます。

feof関数の引数にstdinという定数を指定することで、標準入力ストリームを指定することができます。
これでキーボードからEOFが送信されたことを検知することができます。

ferror関数

ferror関数は、ファイルストリームのエラーを判定します。

int ferror(
 FILE *stream
);
ファイルストリームstreamにエラーが発生している場合は0以外を返す。
それ以外の場合は0を返す。

clearerr関数

clearerr関数は、ファイルストリームのエラー状態をリセットします。

void clearerr(
 FILE *stream
);
ファイルストリームstreamにエラー状態をリセットする。

ferror関数でエラーを検出した場合、自動で元の(エラーではない)状態には戻らないので、clearerr関数でエラーの状態を元に戻しておく必要があります。

サンプルコード4

先ほどのサンプルコード3を、EOFの入力に対応するように修正したサンプルコードです。


#include <stdio.h>

//「Ctrl + Z」入力時のキーコード
#define SUB 26

int main()
{
    int c1 = 0;
    int c2 = 0;

    do {
        printf("プログラムを終了しますか?。\n");
        printf("y=終了, n=続行: ");

        c1 = getchar();

		//EOFを検出した場合
        if (c1 == EOF) {
            if (feof(stdin)) {
				//正常終了
                printf("EOFを検出しました。\n");
            }
            if (ferror(stdin)) {
				//異常終了
                printf("読み取りエラーが発生しました。\n");
                clearerr(stdin);
            }
            break;
        }

		//改行のみの入力だった場合
        if (c1 == '\n') {
            continue;
        }

		//バッファのクリア
		//Windows環境で2文字目以降にEOFが入力された場合、
		//getchar関数はEOFを返さずに26(SUB、置換文字)を返すので
		//改行が入力された時と同じ扱いにする
        do {
            c2 = getchar();
        } while (c2 != '\n' && c2 != SUB);
    } while (c1 != 'y');

    printf("プログラムを終了します。");
    getchar();
}
プログラムを終了しますか?。
y=終了, n=続行: a
プログラムを終了しますか?。
y=終了, n=続行: ^Z
EOFを検出しました。
プログラムを終了します。

「^Z」は「Ctrl + Z」の入力を意味します。

Windows環境とLinux環境とでは微妙に動作は異なりますが、EOFが入力された時にプログラムを終了するようにしています。

Windowsでは「Ctrl + Z」のみが入力された場合はgetchar関数はEOFを返しますが、二文字目以降に入力した場合は「26」という数値を返します。
これは無効な文字入力を示す置換文字(substitute character)というもので、これが入力されると入力の終端となり、バッファの残りの文字は切り捨てられるようです。
Enterキーの入力による改行文字も切り捨てられるため、\nと比較では入力の終端を検出できないので、26と比較して改行と同じ扱いにしています。

Linux環境では先頭文字に「Ctrl + D」を入力することでEOFを入力できますが、二文字目以降には入力できないようです。
なお、「Ctrl + Z」はEOFではなくプログラムの一時停止となります。