標準入力② | Programming Place Plus C言語編 第7章

C言語編 第7章 標準入力②

先頭へ戻る

この章の概要

この章の概要です。


scanf関数

前章に引き続き、標準入力に関する話題です。今回は、scanf関数を取り上げます。

scanf関数は、多くの入門書や入門記事で、最初に登場する入力関数です。 しかし、正しく使うことが非常に難しい関数でもあります。 多くの入門書でそうしているように、ここでの解説も完全ではないことを断っておきます。

scanf関数は、printf関数と同じように "%d" や "%f" などの変換指定子を指定できます。 本当のところ、scanf関数は使わないのが一番良いのですが、fgets関数だけでは文字列しか受け取れないため、 例えば整数を受け取ろうとすると困ります(他の関数と組み合わせれば fgets関数でも可能です。 これは、後で取り上げます)。

全てではありませんが、scanf関数の変換指定子をいくつか挙げておきます。

変換指定子 意味
%d 整数
%f 浮動小数点数(float型)
%c 文字
%s 文字列

printf関数とほぼ同じですが、"%f" については違いがあります。 printf関数の "%f" は、float型でも double型でも使えますが、scanf関数は float型の変数にしか使えません。 scanf関数で double型の変数を使う場合は、"%lf" を指定します。

それでは、まずは整数を受け取る簡単な例を見てみましょう。

#include <stdio.h>

int main(void)
{
    int num;

    /* 数値を受け取って、2倍した値を出力する */
    puts( "数値を入力してください。" );
    scanf( "%d", &num );
    printf( "%d\n", num * 2 );

    return 0;
}

実行結果:

数値を入力してください。
10
20

このプログラムは、VisualStudio 2015/2017 では設定次第ではエラーになるかも知れません。警告の場合は、ここでは無視しておいて下さい。この警告は、C言語そのものとは無関係で、VisualStudio に特有のものです。エラーの場合は無視できないので、設定を変更しておく必要があります。この件に関する情報は、こちらを参照して下さい

実行してみると分かりますが、fgets関数のときと同様、scanf関数を呼び出したところで一旦停止します。 その状態で、適当な数値を入力して Enterキーを押すと、その数値を2倍した値が出力されます。

scanf関数に指定する1つ目の情報は、printf関数と同じような形になっています。 printf関数の "%d" が整数を表すのと同様、scanf関数の "%d" も整数を表します。 つまり、入力されたデータを整数として受け取るという意味になります。

なお、scanf関数の場合は入力なので、改行させることを意味する "\n" は付けません

もう1つ重要なのは、scanf関数の2つ目の情報です。 今回は「&num」と書かれています。 何となく解るかもしれませんが、この情報は、入力された値を受け取る変数の指定です。

変数名である「num」の頭に「&」が付いています。 これを付けなくても、コンパイルエラーとはなりませんが、正しく動作しません。 やや難しい話題になるので、ここでは詳細には触れません。第31章で改めて取り上げます。
とりあえずは、忘れずに「&」を付けましょう、ということにしたいのですが、 逆に「&」を付けてはならない場面もあります。 それは、文字列を受け取る場合です。 次の例で確認してみましょう。

#include <stdio.h>

int main(void)
{
    char str[80];

    /* 文字列を受け取って、そのまま出力する */
    puts( "何か文字列を入力してください。" );
    scanf( "%s", str );
    puts( str );

    return 0;
}

実行結果:

何か文字列を入力してください。
abcde
abcde

scanf関数で文字列を受け取る場合は、1つ目の情報に "%s" を指定し、2つ目の情報には char型配列の名前を指定します。このとき「&」は付きません。「&」が付かない理由を説明するのも、やはり当分先のことになります。文字列のときは付けないものだと覚えておいて下さい。

理由を理解するには、第31章以降でポインタ(と配列)について学ぶ必要があります。

ところで、scanf関数は、入力の途中に空白文字があると、そこで打ち切られてしまうという問題があります。 例えば、先程のサンプルプログラムを実行して、「abc def」のように空白込みの文字列を入力してみると、次のような実行結果になります。

実行結果:

何か文字列を入力してください。
abc def
abc

前章で、fgets関数が受け取りきれなかった入力情報は、どこかに取り残されていて、次回の fgets関数で使われることを取り上げました。 事情はこれと同じで、空白文字より後ろの部分は、どこかに取り残されています。 ですから、次回の scanf関数で、続きの "def" が得られます。

#include <stdio.h>

int main(void)
{
    char str[80];

    /* 文字列を受け取って、そのまま出力する */
    puts( "何か文字列を入力してください。" );
    scanf( "%s", str );
    puts( str );

    scanf( "%s", str );
    puts( str );

    return 0;
}

実行結果:

何か文字列を入力してください。
abc def
abc
def

空白文字で入力が中断されてしまうのは、結構厄介です。 この問題を解決する一番簡単な方法は、scanf関数を諦めて、fgets関数を使うことです。 fgets関数は、必ず1行分受け取ってくれるので、途中に空白文字があっても関係ありません。

バッファオーバーフロー

ここまでに取り上げた scanf関数のサンプルプログラムには、 fgets関数のときと違って、受け取る文字数を指定している箇所がありません。 これでは、バッファオーバーフローが起きそうな感じがします。 実際、このままでは、バッファオーバーフローを起こす可能性があります。

scanf関数でバッファオーバーフローを防ぐには、次のように書きます。 今回も実験しやすいように、文字数を減らします。

#include <stdio.h>

int main(void)
{
    char str[5];

    /* 文字列を受け取って、そのまま出力する */
    puts( "何か文字列を入力してください。" );
    scanf( "%4s", str );
    puts( str );

    return 0;
}

実行結果:

何か文字列を入力してください。
abcdefg
abcd

1つ目の情報のところを "%4s" のように、数値を含めて記述します。 この数値は、最大フィールド幅という指定です。 これは、入力を受け取って、変換指定子に対応した表現に変換された結果が、最大で何文字分になるかを指定するものです。 例えば、受け取る側の配列が5文字分なら "%4s" と指定します。 前章で説明したように、文字列の末尾には '\0' という見えない文字(ヌル文字)が隠れているため、"%5s" では駄目です。

最大フィールド幅は、"%80s" のように、2桁以上の指定になっても構いません。

fgets関数 + sscanf関数

扱いづらい問題が多い scanf関数に代わり、よく使われる方法が fgets関数と sscanf関数を組み合わせるというものです。scanf関数ではなく、sscanf関数です。

sscanf関数は、していることは基本的に scanf関数と同じですが、標準入力ではなく、char型配列に入っているデータを使うという点が異なります。 先程までの例では、標準入力から「10」のような情報がやってくることを期待して、scanf関数を使っていました。 今回は、中身が "10" となっている char型配列が既に存在していて、ここから 10 という整数を受け取るという形になります。
といっても、あくまでも、プログラムを実行した人に「10」のような情報を入力してもらいたいので、 まず fgets関数を使って、標準入力から情報を受け取り、char型配列に入れてもらいます。 これで、"10" が入った char型配列ができますから、これを sscanf関数を使って、整数として受け取れば良いということです。 冒頭のプログラムを、この方法で書き変えると次のようになります。

#include <stdio.h>

int main(void)
{
    char str[10];
    int num;

    /* 数値を受け取って、2倍した値を出力する */
    puts( "数値を入力してください。" );
    fgets( str, sizeof(str), stdin );
    sscanf( str, "%d", &num );
    printf( "%d\n", num * 2 );

    return 0;
}

実行結果:

数値を入力してください。
10
20

fgets関数の使い方はこれまで通りです。 最大で 9桁までの整数を受け取り、変数str に入れています。 バッファオーバーフローは、この時点で防がれています。
あとは、sscanf関数を使って、整数に変換したものを変数num に入れれば良いということになります。

sscanf関数に渡す情報は、まず1つ目に、「どこから取り出すか」です。 つまりは、入力情報が入っている char型配列を指定すれば良いです。
2つ目の情報には、「どういう種類の値を取り出すか」を変換指定子を含んだ文字列で指定します。 今回は、整数が欲しいので "%d" です。
3つ目以降には、入力を受け取る変数を指定します。 今回は、文字列ではなく整数を受け取るので、& を忘れずに付ける必要があります。

fgets+sscanf の組み合わせは、比較的トラブルが少ない方法ではありますが、この方法なら、常に意図通りに動くかといえば、そうとも限りません。
例えば、fgets関数が受け取りきれないような大量の入力の問題や、 sscanf関数が間違ったフォーマット(整数を受け取りたいのに、入力されたのは文字であるとか)で取り出してしまう問題が代表的でしょう。
正しい入力処理は、上級者であっても相当に難しいので、ここでは取り上げません。 現段階では、想定したとおりに入力が行われるということを前提にして、学習を先へ進めた方が良いです。


練習問題

問題① scanf関数を2回使って、2つの整数を受け取り、両方を足し合わせた結果を出力するプログラムを作って下さい。

問題② 問題①のプログラムを、fgets関数と sscanf関数を組み合わせる方法で書き直して下さい。

問題③ fgets関数と sscanf関数を使い、入力された整数を3乗した結果を出力するプログラムを作って下さい。

問題④ 標準入力から秒数を受け取り、それを時・分・秒に変換して出力するプログラムを書いて下さい。

問題⑤ 標準入力から半径の長さを受け取り、円の面積と、円周の長さを出力するプログラムを書いて下さい。円周率にどの程度の精度を与えるかは各自の自由としますが、最低でも 3.14 として下さい。


解答ページはこちら

参考リンク



更新履歴

'2018/6/15 第8章から練習問題⑥⑪を移動してきて、練習問題④⑤とした。

'2018/4/5 VisualStudio 2013 の対応終了。

'2018/4/2 「VisualC++」という表現を「VisualStudio」に統一。

'2018/2/2 scanf関数の変換指定子の表を追加した。

'2018/2/1 全面的に文章を見直し、修正を行った。

'2018/1/31 タイトルを変更(キーボードから入力する -> 標準入力)

≪更に古い更新履歴を展開する≫



前の章へ(第6章 標準入力①)

次の章へ(第8章 文字)

C言語編のトップページへ

Programming Place Plus のトップページへ


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS