C言語編 第7章 キーボードから入力するA

この章の概要

この章の概要です。

scanf関数

前回に引き続き、標準入力を扱います。 前章では fgets関数(⇒リファレンス)を使いましたが、 今回は scanf関数(⇒リファレンス)を取り上げます。

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

本当のところ、scanf関数なんて使わないのが一番の選択肢ですが、fgets関数だけでは文字列しか受け取れないため、 例えば整数を受け取ろうとすると困ります(他の関数と組み合わせれば fgets関数でも可能です。これは後で取り上げます)。 scanf関数は、printf関数と同じように %d や %f などのフォーマット指定を行えるため、どんなデータでも受け取れます。

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

#include <stdio.h>

int main(void)
{
	int num;

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

	return 0;
}

実行結果:

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

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

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

scanf関数に指定する1つ目の情報は、printf関数と同じような形式になっています。 今回であれば "%d" なので、整数を受け取るという意味になります。 scanf関数の場合は入力なので、改行を意味する "\n" は付きません

もう1つ重要なのは、scanf関数の2つ目の情報です。今回は「&num」と書かれています。 何となく解るかもしれませんが、この情報は、入力された値を受け取る変数の指定です。 問題なのは、変数名である「num」の頭に「&」が付いている点です。 この「&」は必須なのですが、困ったことに付け忘れてもコンパイラはエラーとはしてくれません。 この記号の意味の解説は、当分先までお預けとします。 とりあえず忘れず付けましょう、ということにしたいのですが、逆に「&」を付けてはならない場面もあるのです。 それは、fgets関数のように、文字列を受け取る場合です。次の例で確認してみましょう。

#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章以降でポインタ(と配列)について学ぶ必要があります。

さて、前章のfgets関数のときと同じようなサンプルにしたのですが、「おや?」と思いませんでしたか? どこにも、受け取る文字数を指定している箇所がないので、前回説明したバッファオーバーフローが起きそうな感じがしますよね。 実際起きます。scanf関数の場合でも、文字数の制限をかけてやる必要があります。 これについては後で触れることにします。

scanf関数が抱える問題として大きいのは、途中で空白文字を受け取ると、そこで打ち切られてしまう点があります。 この問題を解決する一番簡単な方法は、scanf関数を諦めて、fgets関数を使うことです。 fgets関数は、必ず1行分受け取ってくれるので、途中に空白文字があっても関係ありません。

バッファオーバーフロー

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

#include <stdio.h>

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

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

	return 0;
}

実行結果:

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

「abcdefg」という7文字の入力に対して、出力されたのは「abcd」の4文字だけです。 fgets関数のときと同じ結果になりましたが、制限のかけ方は違ってきます。

scanf関数の場合は、1つ目の情報のところを "%4s" のように、数値を含めて記述します。 受け取る側の配列が5文字分なら "%4s" でなければならず、"%5s" ではダメです。 これは前回説明したように、文字列の末尾には '\0' という見えない文字(ヌル文字)が隠れているためです。

なお、前章でも見たように、残された文字が何文字かあります。 scanf関数の場合でも、この残された文字の対処は非常に難しい問題です。 また、scanf関数の場合は、"%d" を指定したのに文字が入力されてしまったとか、大き過ぎる値が入力されてしまったとか、色々と問題が多いのです。 やはりこの辺りの問題は、ここでは触れないことにします。

fgets関数 + sscanf関数

問題の多い scanf関数に代わり、よく使われる方法が fgets関数と 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関数の使い方はこれまで通りです。 変数str に 10桁を限度として数字の列を受け取り、これを sscanf関数を使って、int型の変数num に取り出しています。

sscanf関数には、1つ目の情報として「どこから取り出すか」、2つ目に「どういうデータを取り出すか」、3つ目に「どこへ取り出すか」を指定します。 上のサンプルを落ち着いて観察すれば、これまでに何度か見たような指定の仕方だと分かるでしょう。 "%d" という指定で整数を表すことも、結果を受け取る int型の変数の名前に「&」を付けることも、scanf関数のときと同じですね。 そう、結局のところ、scanf関数に1つ情報が追加(「どこから取り出すか」の部分)されただけです。

さて、fgets+sscanf の組み合わせですが、これで常に問題無くいくかといえば、そうとも限りません。 fgets関数が受け取りきれないような大量の入力の問題、 sscanf関数が間違ったフォーマット(整数を受け取りたいのに、入力されたのは文字であるとか)で取り出してしまう問題が代表的でしょう。
こういった問題への対策は非常に難しいので、ここでは取り上げません。


練習問題

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

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

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


解答ページはこちら

参考リンク

更新履歴

'2015/3/25 VisualC++ 2017 に対応。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/2/8 VisualC++ で起こる警告やエラーに関するコラムに、開発ツールの情報ページへのリンクを追加。

'2014/1/14 VisualC++ 2008 の対応終了。

'2013/12/15 文章をわずかに修正。コラムに、他の章へのリンクを追加。

'2010/2/27 main() を main(void) に修正。

'2009/3/17 「この章の概要」を追加。

'2009/1/24 新規作成。



前の章へ

次の章へ

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

Programming Place Plus のトップページへ