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

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


今回はキーボードから入力した文字列をプログラム内で使用する方法についてです。

キーボードからの入力はCUIアプリでは「標準入力」という項目から読み出すことが出来ます。
「標準入力」は「標準入力から読み取る関数」と「ファイル読み取り関数で標準入力を指定する」ことで読み出せます。

そして、CUIアプリで画面への出力は「標準出力」という項目に書き出すと画面に出るようになっています。
今までの「 printf 」や「 puts 」などの関数は標準出力に文字列を書き出す関数です。

さて、標準出力を扱う関数と標準入力を扱う関数は基本的に対になっていて、
入力と出力が逆転したような仕様の関数が多く存在します。

「 printf 」関数は「 scanf 」という関数が、
「 puts 」関数は「 gets 」関数が対になっているのですが・・・
gets 関数は仕様に致命的な欠陥があることが知られており、
現在は使用してはいけない関数とされています。


さて、 scanf 関数は第1引数に「フォーマット指定文字列」、第2引数以降に受け取る変数を指定します。
「フォーマット指定文字列」は基本的には printf 関数のものと同じですが、
scanf 関数の方が指定がより細かいので注意が必要です。

また、フォーマット指定を間違えた場合の動作は保証されておらず、
「配列の範囲外アクセス」と同じような状況になるので(しかも書き込むのでタチが悪い)
printf 関数よりも注意が必要です。

フォーマット指定は printf 関数同様複雑なので、今回も抜粋したものを書きます。
変換指定要求型解説
%cchar*1バイトのデータを読み取ります。
%hdshort*文字列を10進の数字列とみなし、short型として読み取ります。
%dint*文字列を10進の数字列とみなし、int型として読み取ります。
%ldlong*文字列を10進の数字列とみなし、long型として読み取ります。
%huunsigned short*文字列を10進の数字列とみなし、unsigned short型として読み取ります。
%uunsigned int*文字列を10進の数字列とみなし、unsigned int型として読み取ります。
%luunsigned long*文字列を10進の数字列とみなし、unsigned long型として読み取ります。
%hxshort*文字列を16進の数字列とみなし、short型として読み取ります。
%xint*文字列を16進の数字列とみなし、int型として読み取ります。
%lxlong*文字列を16進の数字列とみなし、long型として読み取ります。
%ffloat*文字列を10進実数の文字列とみなし、float型として読み取ります。
%lfdouble*文字列を10進実数の文字列とみなし、double型として読み取ります。
%schar*文字列をそのまま読み取ります。指定する配列は読み取りできる十分なサイズが必要です。

上のように、scanf 関数は要求型に全て「 * 」がついています。
これはこれまでもしばしば出てきているのにちゃんと解説していないポインタ型ですが、
現在は型のひとつとして捉え、中身は気にしない方がいいと思います。
ポインタ型についてはそのうちやりますので・・・

普通の変数からポインタ型に変換するには「 & 」(アドレス取得)演算子を使います。

& 演算子は「アドレス取得」(単項、その他、優先15、結合なし)、変数をポインタ型に変換する演算子です。
この演算子は右辺に変数をとります。
また、基本的にどんな型の変数でも取ることが出来ます。

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

int main(void)
{
   
int n=0;//int型の変数 n を定義。0で初期化。
   
int *p;//int型のポインタ型変数 p を定義。
   
p=&n;//pにnへのポインタを代入。
   
return 0;
7行目で & 演算子が使われています。
では以前と同様に通し記号を付けます。

A B C D
p = & n


通し記号種別
Aint*不定(未初期化)変数(p)
B演算子(=)(2項、算術、優先2、結合←) 
C演算子(&)(単項、その他、優先15、結合なし)
Dint0変数(n)

優先順位から、演算子C( & )から処理します。
& 演算子は単項なので右辺値のみを取ります。右辺値は変数D( n )です。
& 演算子は右辺値のポインタ値を返しますが、返される値は「右辺値を指すポインタ値」としかされておらず、
同じ変数だからといって常に同じ値が返ってくる保証はありません。
実行するたびに異なる値が返される可能性もあるので、定数を用いることは基本的にできません。

上記理由により、ポインタ型の「値」の欄には一例を示すのみとなります。
実際には滅多に同じ値が出ることはありません。

演算後は以下のようになります。

A B C
p = vtemp1


通し記号種別
Aint*不定(未初期化)変数(p)
B演算子(=)(2項、算術、優先2、結合←) 
Cint*0x0013ff7c一時変数(参照先:n)
次に演算子B( = )が処理されます。
同じ型どうしなのでそのまま代入されます。


というわけで、 & 演算子を使えば scanf 関数が要求するポインタ型の値を生成することができます。
なお、最初からポインタ型になる文字列用配列を渡す場合は当然 & 演算子は必要ありません。

scanf 関数の使用例
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
#include <stdio.h>

int main(void)
{
   
int n=0;//int型の変数 n を定義。0で初期化。
   
puts("数字を入力してください。");//文章表示
   
scanf("%d",&n);//変数nに入力を読み取る
   
printf("入力された値:%d",n);//変数nを表示
   
return 0;
実行例:
数字を入力してください。
10
入力された値:10
これを実行すると「数字を入力してください。」と表示された後、いったんプログラムが停止します。
この状態は「入力待ち」と呼び、キーボードから文字を入力することができます。
文字入力後、Enterキーを押すと入力が確定され、プログラムが続行されます。

この例は正しく入力されているため 特に問題はありませんが、
ここで数字ではなくアルファベットなどを入力すると、
scanf 関数は失敗し、変数nには値が読み込まれません。
しかも、 scanf 関数は読み取れなかった文字を「残す」ので、
次回読み取り時にその残った文字を読み取ろうとします。

scanf 関数を2回呼び出すようにした例です。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
#include <stdio.h>

int main(void)
{
   
int n=0;//int型の変数 n を定義。0で初期化。
   
puts("数字を入力してください。");//文章表示
   
scanf("%d",&n);//変数nに入力を読み取る
   
printf("入力された値:%d\n",n);//変数nを表示
   
puts("2つめの数字を入力してください。");//文章表示
   
scanf("%d",&n);//変数nに入力を読み取る
   
printf("入力された値:%d\n",n);//変数nを表示
   
return 0;
実行例1(正しく入力した例)
数字を入力してください。
10
入力された値:10
2つめの数字を入力してください。

20
入力された値:20

正しく入力した例では、2回の「入力してください。」表示後、入力待ちになっています。

実行例2(変なものを入力した例)
数字を入力してください。
j
入力された値:0
2つめの数字を入力してください。
入力された値:0


これは最初の入力時にアルファベットを入力した場合です。
この場合、2回目の「入力してください。」表示後に入力待ちにならず、すり抜けてしまっています。
これは、1回目の scanf 関数の失敗時に残された文字「j」が2回目の入力として扱われ、再び失敗させてしまったためです。

このように、 scanf 関数は正しく入力される分には使いやすいのですが、
変なものを入力されると連鎖的に失敗しやすい特性があり、
復帰処理をはさまずにループしたりすると簡単に無限ループにハマる可能性があります。

そういうわけで、 scanf 関数は結構うっとおしいです。
まぁ、「正しい」入力が保証される限り、便利な関数ではありますが・・・

また、実行例2では「入力された値」として0が出ていますが、
これはVisual C++6 の scanf 関数が失敗した場合、
それ以降の変数の中身を変更しない(最初に0を代入したのが残っている)ためなのですが、
言語として保証された動作かどうかは分かりませんので注意してください。
最も、実装を考えると変更しないというのが自然っぽいのでそうなる可能性は高いと思いますけど(笑)


一方、 gets 関数はというと、
第1引数に「格納する配列」を指定するのみです。

例としては・・・
<  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行どうぞ");
   gets(str);
//1行読み込む
   
printf("入力:%s\n",str);//読み込んだ文字列を表示
   
printf("完了");
   
return 0;
実行例:
1行どうぞ
abcde
入力:abcde
完了

ってな感じですが・・・
問題がお分かりでしょうか?

動作としては「1行どうぞ」と表示された後、 scanf 関数同様入力待ちになります。
そこで文字列を入力してEnterキーを押すと続行されるまでは同じです。
gets 関数は全て文字列として読み込みますが・・・
許容文字数指定してませんよね?

前回も書いたように文字列は他の関数に渡すと文字数もわかりません。
今回は入力なのでナル文字をアテにできず、文字数を数えることも出来ません。
読み込む先は配列ですから入れられる限界ってものがあります。

・・・じゃあどうなってんの?というと・・・
何も気にしてません(笑)
つまり gets 関数は読込先の配列のサイズなんて気にせずに入ってきただけ読み込みます。
用意した配列のサイズ以上の文字列を入力されると簡単に有効範囲外に書き込みを行ってしまいます。
というわけで gets 関数は使い物にならないんです(笑)


と、まぁこれらの関数が実質使えないため、何故かファイル読み込み関数の fgets 関数が使われることになります(笑)
後編は fgets 関数と sscanf 関数による gets 関数と scanf 関数の代替についてです。


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

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

最終更新 2008/10/17