[前へ]   [目次へ]   [次へ]

キーボードからの入力(後編)(C/C++)


今回は、 fgets 関数と sscanf 関数による gets 関数と scanf 関数の代替についてです。

「 fgets 」関数は、ファイルから1行を読み込む関数です。
ファイルからの読み込み関数ですが、キーボードからの入力も受け取ることが出来ます。

fgets 関数は、
第1引数に「文字列を格納する配列」(型は char* )、
第2引数に「配列のサイズ」(型は int )、
第3引数に「操作するファイル」(型は FILE* )、
を渡します。

gets 関数と異なり、第2引数に配列のサイズを指定することが出来ます。
fgets 関数は改行を見つけるか、第2引数に指定されたサイズ-1(ナル文字の分)文字を読み込みます。
配列のサイズを正しく渡せば、 gets 関数のように配列の領域違反を起こすことはありません。

第3引数には初登場の FILE* 型が現れます。

この型は、ファイル操作系の関数によく出てくる型で、
名前の通り、ファイルに関する情報を持っています。
この型の値は、最初から定義されているものと、「 fopen 」関数で得ることができます。

今回は「標準入力」を表す「 stdin 」を渡せばOKです。( stdin は最初から定義されています)

実際に前項の gets 関数の置換として fgets 関数を使うと以下のようになります。

<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
#include <stdio.h>

int main(void)
{
   
char str[32];//char型の要素数32の文字列用配列、strを定義
   
puts("1行どうぞ");
   fgets(str,31,stdin);
//1行読み込む
   
printf("入力:%s\n",str);//読み込んだ文字列を表示
   
printf("完了");
   
return 0;
実行例:
1行どうぞ
abcde
入力:abcde

完了


ソース中で変更したのは7行目だけです。

これらの例で変化がわかるのは str の要素数である32バイトを超える書き込みをした時です。
char 型は1バイトなので、要素数=バイト数になります)

例えば入力に40文字一気に入れたとします。
gets を使った場合は str に40文字(40バイト)とナル文字(1バイト)の41バイトを書き込みます。
これは、 str の0番〜40番に対して書き込んだことになります。
str の有効範囲は0〜31なので、有効範囲を外れます。
(このように有効範囲外へのアクセスは「配列の領域の外にアクセスした」ということで「領域違反」と呼びます)
結果、その先のデータを上書きし、後はどうなっても知らないと・・・
まさに あとは野となれ山となれ・・・なわけです(笑)

対して fgets の場合は、限界として31を渡してあるので、
str に30文字(30バイト)とナル文字(1バイト)の31バイトを書き込み、後は次のために残します。
これは、 str の0番〜30番に対して書き込んだことになり、有効範囲内に収まります。
なんか残ってますけど(笑)、これならとりあえず大丈夫なわけです。


fgets 関数の第2引数に str の要素数より少ない31を指定しているのは、
以前も書いた、「配列の最後の要素は使わないようにする」をやっているのですが、
文字列の限界長を指定する関数は、仕様上指定値がナル文字の分を含むか含まないかが分かれるため、
余裕をみておいたほうが確実という話でもあります。

ところで・・・ fgets を使った場合、出力に変な改行が紛れたのが分かると思います。
実はコレ、 str の文字列の中に改行が入っちゃってるんです。

テキストエディタなどで改行を入力する時、Enterキーを使うのが普通ですが、
この入力の時にもそれは同じで、確定としてEnterキーを押すのと同時に改行文字も入力されてしまいます。
gets は改行文字を捨てるという動作があるので、前回は見えませんでした。

最も、コレを捨てるのは1行でできるので、それほどのことはありませんが、覚えておいてください。
(この辺は「文字列操作」の解説の時に書きます)


続いて、sscanf 関数についてです。

sscanf 関数は scanf 関数の読込先が文字列になったものです。
第1引数に「解析する文字列」(型は const char* )、
第2引数に「フォーマット指定文字列」(型は const char* )、
第3引数以降に受け取る変数を指定します。

scanf 関数と比べると、キーボードから入力を待っていた部分が
新しく挿入された第1引数を使うようになっただけで、それ以外は同じです。

解析する文字列に文字列定数を渡してもしょうがないので、
fgets 関数で読み込んだ文字列を渡してみます。

<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
#include <stdio.h>

int main(void)
{
   
char str[32];//char型の要素数32の文字列用配列、str を定義
   
int n=0;//int型の変数 n を定義、0で初期化
   
puts("数値を入力してください");
   fgets(str,31,stdin);
//1行読み込む
   
sscanf(str,"%d",&n);//strから数値をnに読み込む
   
printf("入力:%d\n",n);//nの数値を表示
   
return 0;
実行例:
数値を入力してください
10
入力:10

最初に fgets 関数(8行目)で文字列として str に読み込んだ後、
sscanf 関数(9行目)で str の文字列から n に読み込み、
それを最後に表示しています。

この例では scanf 関数の時と同じ結果が得られています。

では、 scanf 関数の時に問題だった2回連続呼び出しの例をあててみます。

<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
#include <stdio.h>

int main(void)
{
   
char str[32];//char型の要素数32の文字列用配列、str を定義
   
int n=0;//int型の変数 n を定義、0で初期化
   
puts("数値を入力してください");
   fgets(str,31,stdin);
//1行読み込む
   
sscanf(str,"%d",&n);//strから数値をnに読み込む
   
printf("入力:%d\n",n);//nの数値を表示
   
puts("2つめの数値を入力してください");
   fgets(str,31,stdin);
//1行読み込む
   
sscanf(str,"%d",&n);//strから数値をnに読み込む
   
printf("入力:%d\n",n);//nの数値を表示
   
return 0;
前回同様、2回連続呼び出しにしただけです。

以下は正しく入力した場合の実行例です。

数値を入力してください
10
入力:10
2つめの数値を入力してください

20
入力:20
こちらは前回同様の動作です。

以下は前回問題になった入力例です。

数値を入力してください
j
入力:0
2つめの数値を入力してください

10
入力:10
今回は1回目の失敗を引きずらず、2回目の入力を受け付けることができています。

キーボードからの入力は読み出さないと残留して次回の入力に使われますが、
今回は8行目の fgets 関数により全て読み出されているため、キーボードからの入力には何も残っていません。
続く9行目の sscanf 関数は失敗し、10行目では0が出力されます。
12行目での再入力時には8行目で読みきっているので入力待ちになります。
また、この時前回の入力の j は2回目の入力で上書きされ、消去されます。


と、このように fgets 関数と sscanf 関数を組み合わせることで、
scanf 関数の代替とすることができ、厄介なクセを解消することができます。

さて、これで「加算だけ計算機」を作成するのに必要な要素が揃いました。
プログラムは結局のところ、自分で組み立てなければなかなか作れるようにならないらしいので、
ここまでの内容を使って「加算だけ計算機」を組み立ててみてください。

次回は「加算だけ計算機」を実際に組み立てる予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2008/10/17