文字入力

文字入力


サイト全体の目次

稿


稿

scanf による文字列入力

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   char str1[32], str2[32];
/*06*/ 
/*07*/   scanf("%s%s", str1, str2);
/*08*/   printf("%s %s\n", str1, str2);
/*09*/ 
/*10*/   scanf("%s%s", str1, str2);
/*11*/   printf("%s %s\n", str1, str2);
/*12*/ 
/*13*/   return 0;
/*14*/ }

(untitled)

scanf の変換文字を「%s」とし、第2引数以降に文字列の先頭アドレスを書いています。これで文字列が「'\0'」付きで、指定された配列に代入されます。ここで第二引数について、厳密には指定されたアドレスを先頭とする領域に代入していくのだということは、前章から容易に察しがつくでしょう。

さて、このソースには「文字列を2つ入力するとそれを表示する」という処理が2つあることになります。そしてキーボードから入力する時は、2つの文字列をスペースで区切るか、Enter(改行)キーを押すかします。

Hello(第1文字列) World(第2文字列)

Hello(第1文字列)
World(第2文字列)

scanf では、この2つの入力方法はまったく同じ意味です。しかし、この方法だと問題があります。スペースを文字列の区切りとして判断してしまうため、「スペースも文字列として入力したい」場合はこの方法は使えません。例えば「Hello, World.」を1つの文字列として扱おうと思っても、「Hello,」と「World.」に勝手に分けられてしまいます。

さらに重大な問題があります。2つの文字列しか入力できないはずのところで、誤って3つの文字列を入力してしまったらどうなるでしょう?

Hello(第1文字列) World(第2文字列) abcde(第3文字列)

なんと、対応する配列が割り当てられなかった第3文字列は「次の入力に反映されてしまう」のです。つまり、この例では scanf が2つありますが、1度目の入力で誤って第3文字列まで入力してしまったとしましょう。するとその部分は次の入力時、すなわち2度目の scanf で第1文字列として勝手に読みこまれてしまうのです。
1度目の入力
Hello World abcde

2度目の入力
programming language

この場合、2度目の入力では、第1文字列が「abcde」に、第2文字列が「programming」になり、「language」はさらに次の入力まで持ち越されてしまいます(これは、数値を入力する時にも起こります)。あるいはこの例で、1度目の入力で4つの文字列を入力してしまったら、二度目の scanf が出てきても入力状態にすらならずに3つ目、4つ目の文字列が読み込まれてしまいます。こういったことは予期せぬエラーの原因になってしまうので注意が必要でしょう。

ですから scanf は

scanf("%d%s%d", コード, 商品名, 価格)

のような、決まった形式のものを while 文などで繰り返し入力する時以外はあまり使わないほうがいいかもしれません。ちなみに、これ以下にも出てくるこの手の問題の回避方法は本章の後半に載せておきます。

1行読み取り

このセクションのソース

scanf に対し、このセクションで使うgets関数は Enter までを1行として読みこむので、スペースもきちんと扱えますし、余分な入力が残ってしまう心配もありません。gets 関数は stdio.h に定義されています。

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   char str1[32], str2[32];
/*06*/ 
/*07*/   gets(str1);
/*08*/   gets(str2);
/*09*/   printf("%s\n", str1);
/*10*/   printf("%s\n", str2);
/*11*/ 
/*12*/   return 0;
/*13*/ }

(untitled)

gets 関数の使い方は見てのとおり、引数に代入したい配列の先頭アドレスを書くだけですが、引数は1つしか取れません(これも厳密には、指定されたアドレスを先頭とする領域に代入していくわけです)。

また、gets 関数に対応する出力関数としてputs関数があります。使い方は gets 関数のように、引数に文字列の先頭アドレスを書くだけです(これも厳密には、指定されたアドレスから '\0' までを表示するのです)。試しに printf のところを、puts(str1); などと書き換えて実行してみてください。なお、「puts」関数は「stdio.h」に定義されています。

数字列を数値に変換する

このセクションのソース

scanf を使った場合、数値を入力した場合でも文字列同様、誤って余分に入力してしまった分が次の入力時に持ち越されてしまうという問題が起こります。そこで、数字を1度文字列として読みこんでから数値に変換するという方法があります。「atoi」関数は「stdlib.h」に定義されています。

/*01*/ #include <stdio.h>
/*02*/ #include <stdlib.h>    /* atoi 関数を使うのに必要 */
/*03*/ 
/*04*/ main()
/*05*/ {
/*06*/   char str[32];
/*07*/   int x;
/*08*/ 
/*09*/   gets(str);
/*10*/   x = atoi(str);
/*11*/   printf(" -> %d\n", x);
/*12*/ 
/*13*/   return 0;
/*14*/ }

(untitled)

まず、数字を文字列として格納しておく char 型配列(str)と、整数にした時の値を格納する int 型の変数 (x) を用意しておきます。

次に、とりあえず gets 関数を使って数字を char 型配列(str)に文字列として格納します。そうしたら、例のようにその文字列を atoi 関数に引数として渡します。すると戻り値として整数が帰ってくるので、int 型変数(x)に格納します。

x = atoi(str);

また、atoi 関数とよく似たものに atof 関数があります。これは、atoi が int 型の数値に直すのに対し、atof は float 型の数値に直します。「atof」関数は「stdlib.h」に定義されています。

scanf による1文字入力

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   char ch1, ch2;
/*06*/ 
/*07*/   scanf("%c%c", &ch1, &ch2);
/*08*/   printf("%c%c\n",ch1, ch2);
/*09*/ 
/*10*/   scanf("%c%c", &ch1, &ch2);
/*11*/   printf("%c%c\n", ch1, ch2);
/*12*/ 
/*13*/   return 0;
/*14*/ }

(untitled)

変換文字を「%c」にし、第2引数以下に変数のアドレスを書きます。文字列の時とほとんど同じです。そして入力する文字はスペースで区切らずに続けて入れます。なお、スペースを入れるとそれも1文字として代入されます。

ab    /*「a」が「ch1」に、 「b」が「ch2」に代入される*/

しかし、今回も文字列の時と同様の問題が起こり、それはもっとたちの悪いものです。それは、スペースも1文字として代入されるだけならまだしも、入力のために押す Enter(エンター:リターン:改行)までもが「’\n(改行コード)’」として、1文字として扱われてしまうのです。そして、あまった分の文字はやはり次の入力に反映されてしまいます。
1度目のscanf
ab    /*ここで Enter*/

2度目のscanf
cd    /*ここで Enter*/

上の例ではやはり2つ scanf がありますが、1度目の scanf では ch1 ch2 にそれぞれ「a」「b」が入ります。しかし、2度目の scanf では、1度目の入力時に押した Enter の「'\n(改行コード)'」が ch1 に入り、「c」が ch2 に入ります。そして、「d」と2度目の Enter、つまり「'\n'」は次回の入力時まで持ち越されます。

getchar による1文字入力

このセクションのソース

もう1つの1文字入力関数は getchar です。「getchar」関数は「stdio.h」に定義されています。

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   char ch1, ch2;
/*06*/ 
/*07*/   ch1 = getchar();
/*08*/   ch2 = getchar();
/*09*/   printf("%c%c\n",ch1, ch2);
/*10*/ 
/*11*/   ch1 = getchar();
/*12*/   ch2 = getchar();
/*13*/   printf("%c%c\n",ch1, ch2);
/*14*/ 
/*15*/   return 0;
/*16*/ }

(untitled)

使い方は見てのとおり(引数はありません)で、入力された1文字が戻り値として返ってきます。scanf よりもすっきりしていますね。ですから、1文字入力をする時は scanf を使うよりも getchar を使うことのほうが多いようです。しかし、scanf にあった問題はこの関数でもまったく同様に現れます。つまり、Enter も1文字として扱われ、余った分は次の入力に持ち越されてしまうという事です。

また、getchar 関数に対応する出力関数として putchar 関数があります。使い方は putchar 関数の引数に表示したい1文字か、1文字変数を書くだけです(取れる引数は1つだけです)。試しに printf のところを、putchar(ch1); などと書き換えて実行してみてください。あるいは、改行だけして1行あけたい時などには putchar(’\n’); とすると便利かもしれません。「putchar」関数は「stdio.h」に定義されています。

余分な入力をクリアする

このセクションのソース

この章でここまでに出てきたキーボードから何かを入力する関数のいくつかは、余分に入力してしまった分が次の入力時まで持ち越されてしまいますね。特に getcharにおいて Enter(エンター:リターン:改行) による「’\n(改行コード)’」まで読みこんでしまうのは問題ですよね。このことに関しては入出力に関する「ストリーム」と呼ばれるものが影響してくるのですが、ここではあまり詳しく説明しません。しかし幸いなことに、この余分なものを破棄してしまう関数があります。それが「rewind」と呼ばれる関数で、本来はファイル操作に使う関数なのですが、このように余分な入力を破棄する働きもあるので活用しましょう。ここで使用するrewind関数は「stdio.h」に定義されています。この章の最初の例を次のように書き換えてみます。

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   char str1[32], str2[32];
/*06*/ 
/*07*/   scanf("%s%s", str1, str2);
/*08*/   printf("%s %s\n", str1, str2);
/*09*/   rewind(stdin);
/*10*/ 
/*11*/   scanf("%s%s", str1, str2);
/*12*/   printf("%s %s\n", str1, str2);
/*13*/   rewind(stdin);
/*14*/ 
/*15*/   return 0;
/*16*/ }

(untitled)

ここで rewind 関数の引数stdin入力ストリームといわれるもので、キーボードからの入力はまずはじめに入力ストリームの「バッファ(一時的な情報の蓄積場所)」にが蓄えられます。しかし riwind 関数はこの蓄えられた情報を破棄してくれます。では例えば、次のように入力したとします。

1度目の入力
Hello World abcde

2度目の入力
programming language

1回目の入力では scanf 関数で「Hello」と「World」が読み込まれ、「abcde」が持ち越されることになります。しかし、「rewind」関数でその持ち越される部分が破棄されるので、2回目の scanf 関数ではちゃんとに「programming」と「language」が読み込まれます。

ただし、この rewind 関数自体は ANSI で標準搭載されていますが、これを stdin に使ったときにバッファが全て破棄されるという動作は ANSI C で定義されているものではありません。したがって、コンパイラによってはバッファの内容を破棄するどころかバッファの先頭に移動してしまうので、これまでのバッファ内容がすべて復活してしまうこともあります(これは ANSI で規格された rewind の正常な動作です)。私が5つのコンパイラで試したところ、1つはバッファを復活させてしまいました。

そんなときは fseek 関数を試してみてください。この関数に以下の引数を与えて使うとバッファの最後に移動するので、これまでの入力はすでに取り込まれたものとして扱われます。従って見かけ上、先にあげた rewind と同じ効果が期待できます。fseek 関数は stdio.h に定義されています。

fseek(stdin, 0, SEEK_END);

最後に「getchar」関数に関する、よく使う手法を説明しておきます。関数が出てくると先にその関数を処理し、戻り値がそこにあるかのように処理してくれるのはすでに書きましたね。そして getchar 関数は「1文字」を返します。ですから、while 文の条件判断式で直接この関数を書いてしまうのです。

while(getchar() != '\n')

こうすることで、「'\n'(改行コード)」まで文字を読み込みつづけることができます。なお、このようなケースは特殊で、普通は getchar で変数に文字を代入するのですが、その場合は
while((c = getchar()) != '\n'){
    /*実行文*/
}

のように書きます。こうすることで、変数「c」に文字を読み込んだ上で「c」を評価します。この時「c = getchar()」を「()」でくくるのを忘れないで下さい。そうしないと「=」と「!=」の評価順位が同じになってしまい、正しく評価できなくなってしまいます。
while(c = getchar() != '\n')    /*間違い*/

この手の方法はよく使うので覚えておいてください。


読み込み中・・・
10秒待っても表示が変わらない場合、次の理由が考えられます。
・Javascript が無効になっています。
・検索エンジンのキャッシュを見ています。
サイトホームへ / 上位ページへ / ページトップへ / PAROFトレンドショッピングへ
Copyright (C) 2010 totobon all right reserved.