苦しんで覚えるC言語

1行の文字列として入力する


第1項:gets関数によるキーボード入力

以前、scanf関数でキーボードから入力する場合に、
入力ミスがあってもプログラムでそれを知ることができないことを説明しました。
対策として、文字列として入力し、後から数値に読み替える方法を説明します。

C言語には、キーボードから1行の文字列を入力するgets関数が用意されています。
なお、gets関数を使うには <stdio.h> を #include する必要があります。
gets関数の使い方は次の通りです。

gets(文字配列);
gets関数を実行すると、scanf関数と同様に入力待ち状態になります。
ユーザーがキーボードから入力した文字列は指定した文字配列内に格納されます。

また、ついでに、画面に1行の文字列を表示するputs関数も説明してしまいます。
なお、puts関数を使うには <stdio.h> を #include する必要があります。
puts関数の使い方は次の通りです。

puts(文字配列);
指定した文字列が画面に表示されます。また、文字列の最後で必ず改行されます。
printf関数よりも低機能ですが、文字列の表示だけならばputs関数が簡単です。

次のプログラムは、ユーザーが入力した文字列をそのまま表示する例です。

#include <stdio.h>

int main(void)
{
	char str[32];
	
	gets(str);
	puts(str);
	
	return 0;
}
このプログラムの実行結果は次の通りになります。

DRAGON QUEST 3 入力した文字列
DRAGON QUEST 3
入力した文字列がそのまま表示されています。
scanf関数で %s を使用したときと異なり、空白が含まれていても問題ありません。

目次に戻る

第2項:バッファオーバーラン対策

どうやら、筆者のひねくれた性格は依然として直っていないようです。
何故ならば、説明したばかりのgets関数の使用禁止令を出すつもりだからです。

以前、関数に配列を渡すとどうなるかについて説明したときに、
関数に渡されるのは配列の先頭アドレスであり、要素数は無視されると説明しました。

先ほど、gets関数には文字配列しか渡していませんでした。
ということは、gets関数には、文字配列の要素数がわからないと言うことです。
つまり、要素数を超える入力でも受け付けてしまい、バグにつながるのです。

先ほどのプログラムの要素数は32だったので、31文字以上入力するとバグが発生します。
次の実行結果は、実際に入力して見た例です。

0123456789012345678901234567890123456789 入力した文字列

実行してみると、おなじみのエラー画面が表示され、強制終了されてしまいました。

このような問題がある以上、gets関数は使用できません。
そのかわりに、fgets関数を使用することができます。
なお、fgets関数を使うには <stdio.h> を #include する必要があります。
fgets関数の使い方は次の通りです。

fgets(文字配列,配列の要素数,ファイルポインタ);
最後でファイルポインタを指定していることからもわかるように、
この関数は、ファイルから文字列を読み込むための関数です。

しかし、実は、C言語では、全ての周辺機器はファイルとして扱うことができます。
キーボードには stdin という名前のファイルポインタが割り当てられています。
この stdin を指定すれば、ファイルから読み込む関数がキーボード用に早変わりします。
周辺機器はファイル扱い
周辺機器のファイル扱いは UNIX というOSで取り入れられた機能で、
現在のコンピュータはほとんど全てが同様の仕組みを備えています。
配列の要素数を知るには、sizeof関数を使用するのが簡単で確実です。
つまり、次のようにすれば、安全なgets関数のかわりが実現します。

fgets(文字配列,sizeof(文字配列),stdin);
改行文字
gets関数は '\n' を格納しませんが、fgets関数は格納します。
入力の終わりを調べるには '\n' を検索して下さい。
次のプログラムは、安全な入力方法に書き換えた例です。

#include <stdio.h>

int main(void)
{
	char str[32];
	
	fgets(str,sizeof(str),stdin);
	puts(str);
	
	return 0;
}
このプログラムで長い文字列を入力して安全度をテストした結果は次の通りです。

0123456789012345678901234567890123456789 入力した文字列
0123456789012345678901234567890
要素数の限界となる部分で入力を打ち切り、バグの発生を防いでいることがわかります。

目次に戻る

第3項:文字列から数値などを取り出す

これで文字列の入力は万全ですが、まだ問題が残されています。
それは、scanf関数と異なり、数値の入力ができないという点です。
入力した文字列を数値に読み替える必要があります。

文字列を数値に読み替える一番簡単な方法は、atoi関数を使うことです。
すでに説明済みなので、詳しい説明は省きます。
次のプログラムは、入力された数値の2乗を表示する例です。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	char str[32];
	int val;
	
	fgets(str,sizeof(str),stdin);
	val = atoi(str);
	printf("%d\n",val * val);
	
	return 0;
}
このプログラムの実行結果は次の通りになります。

5
25
入力した文字列に数字以外が含まれていてもatoi関数は無視するので、
scanf関数の時のような、わけのわからない数値になることはありません。
また、元の文字列の文字数を数えれば入力された数字の桁数がわかるので、
大きすぎる数値が入力されたとしてもチェックすることができます。
なお、実数の場合にはatof関数を使用します。使い方は同じです。

単純に数値を入力するだけであればこれで十分ですが、
scanf関数のように、複数の数値を記号で区切って入力したい場合には、
自前で記号を検索し、それぞれ数値に変換する必要があります。
sscanf関数もあるが
文字列から数値を切り出すsscanf関数もあるのですが、
scanf関数と似た問題があるので、ここでは扱いません。

文字列の中から単語を取り出すにはstrtok関数を使用します。
なお、strtok関数を使うには <string.h> を #include する必要があります。
strtok関数の使い方は次の通りです。


/* 初めの単語を取り出す */
単語のアドレス値 = strtok(文字配列,区切り文字);

/* 次の単語を取り出す */
単語のアドレス値 = strtok(NULL,区切り文字);

単語が見つからない場合は NULL を返します。

strtok関数は区切り記号を検索し、その位置を EOS に置き換えます。
文字列を区切り記号の前と後ろに分割することができます。
その後、その単語をatoi関数で変換すれば数値を得ることができます。

次のプログラムは、入力された数値の一覧を表示する例です。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
	int i,j,val[10];
	char str[32],*ch;
	
	fgets(str,sizeof(str),stdin);
	
	ch = strtok(str,",\n");
	for (i = 0;i < 10;i++) {
		if (ch == NULL) {
			break;
		} else {
			val[i] = atoi(ch);
		}
		ch = strtok(NULL,",\n");
	}
	
	for (j = 0;j < i;j++) printf("%d\n",val[j]);
	
	return;
}
このプログラムの実行結果は次の通りになります。

85,41,26,956,12  入力した文字列
85
41
26
956
12
数字の桁数を大きくしたり、文字列を入力したりしても、
致命的なバグになってしまうことは無いはずです。

目次に戻る