461 | | free(text); /* 動的に確保したメモリー領域を開放 */ |
462 | | return 0; |
463 | | } |
464 | | }}} |
465 | | |
466 | | {{{ |
467 | | #!div class="important" |
468 | | '''注意''': MinGW をお使いの方は、上記のサンプルをコンパイルする際、事前に !GnuWin プロジェクトの LibGW32C ライブラリをインストールし、コンパイルコマンドの末尾に -lgw32c オプションを付けてコンパイルしてください。 |
469 | | |
470 | | {{{ |
471 | | #!sh |
472 | | $ gcc -o test test.c -lgw32c |
473 | | }}} |
474 | | |
475 | | LibGW32C のインストールについては、[#install-compiler コンパイラを用意する]の節を参照してください。 |
476 | | }}} |
477 | | |
478 | | そこで、使い物にならない gets() 関数の代用品として、 GNU は標準ライブラリに拡張仕様として '''getline() 関数'''を追加しました。この関数は、ファイルから 1行を入力し、その長さに応じて必要なだけのメモリー領域を動的に確保し、その領域を char 型の配列として取得する、というものです。 |
479 | | |
480 | | {{{ |
481 | | #!c |
482 | | char *text = NULL; |
483 | | }}} |
484 | | |
485 | | 変数 text は char 型の'''ポインタ'''です。ポインタ変数とは、要するにメモリーアドレスを格納しておく変数のことですが、ここでは char 型の配列にアクセスする為の変数である、ぐらいに思っておいてください。 '''NULL''' は入出力ライブラリおよび一般ユーティリティライブラリ (stdlib.h) にて定義されている値で、メモリーアドレスとしては確実に無効である、という意味の定数です。絶対に無効であることが分かっていれば避けられる事故も多いので、多くの場面において利用される「意味のある」値です。なお、 getline() 関数で使用する場合、変数 text には必ず事前に NULL を入れておいてください。そうしないと、多分事故ります[[FootNote(NULL 以外のものを入れて使う方法もあるのですが、それはそれで微妙に特別な使い方だからです。ていうか、そんな細かいことまでここで書いてたら、多分キリがないです。orz)]]。 |
486 | | |
487 | | {{{ |
488 | | #!c |
489 | | letter_num = getline(&text, &length, stdin); |
490 | | }}} |
491 | | |
492 | | で、 getline() 関数を使うことによって、 text 変数から文字列が入った配列にアクセスできるようになります。ここで、変数 text の手前にアンパサント記号 "&" がついていますね。これは'''アドレス参照演算子'''といって、その変数に代入している値を実際に格納しているメモリーアドレスを取得しろ、というものです。こうすることによって、 getline() 関数は変数 text に、取得した文字列が格納された char 型の配列 (がある場所のアドレス値) を入れてあげることができる、というわけです。 |
493 | | |
494 | | 変数 length には、 getline() が動的に確保したメモリー領域のサイズが入ってきます。これまた、 & 記号が手前に付いているのは、 getline() 関数がこの変数に値を入れる為です。 |
495 | | |
496 | | '''stdin''' は入出力ライブラリにおいてあらかじめ定義されている名前で、この名前を getline() 関数の 3つ目の引数に指定してあげると、 getline() 関数は文字列をファイルではなく、ユーザーによるキーボードでの入力から取得するようになります。 |
497 | | |
498 | | getline() 関数の戻り値を letter_num 変数に代入していますね。ここには、 getline() 関数が取得した文字列の文字数が入ってきます。 length 変数と同じ値になるんじゃないの? と思いがちですが、 length 変数には文字列を格納する配列の末尾に追加するヌル文字 '\0' も含めた長さが入るので、 通常は letter_num == length - 1 になるはずです[[FootNote(getline() 関数が必要 _以上_ にメモリー領域を確保してはいけないという決まりはないので、 length にはさらに大きな値が入ってくる可能性もある。)]]。 |
499 | | |
500 | | getline() 関数は gets() 関数とは違って、ユーザーが最後に押した Enter キーによる改行文字も捨てずに取得してきてしまいます。そこで、 getline() 関数の戻り値を代入した変数 letter_num の値を利用して改行文字の位置を特定し、それをヌル文字 '\0' に置き換えることで、次の printf() 関数が表示を行う際に、余計に改行しすぎないようにしています。それが以下の行ですね。 |
501 | | |
502 | | {{{ |
503 | | #!c |
504 | | text[letter_num - 1] = '\0'; /* 改行文字を潰す */ |
505 | | }}} |
506 | | |
507 | | ところで、 C 言語はメモリーの使い方をプログラマー自身の責任で管理する必要のある言語です。 getline() 関数を用いる場合、 getline() 関数が動的に取得したメモリー領域を、後で開放してあげる必要があります。それをやってくれるのが、 '''free() 関数''' です。 |
508 | | |
509 | | {{{ |
510 | | #!c |
511 | | free(text); /* 動的に確保したメモリー領域を開放 */ |
512 | | }}} |
513 | | |
514 | | この辺、 gets() 関数を使う場合に比べると若干面倒で知識も必要ですが、安全なプログラムを書く為には必要なコストだと割り切るほかないでしょう。 |
515 | | |
| 465 | return 0; |
| 466 | } |
| 467 | }}} |
| 468 | |
| 469 | gets() 関数の代用として最も一般的に使われているのは、おそらく '''fgets() 関数''' でしょう。この関数は、ファイルからテキストを 1行読み込む関数ですが、ユーザーからの入力を読み込むのにも使うことができます[[FootNote(というより、ファイルから何かを読み込む関数はすべて、ユーザーからの入力を読み込むこともできます。同様に、ファイルに何かを書き込む関数はすべて、コンソールに何かを表示することもできます。)]]。ただし、いくつかの点で gets() 関数とは動きが異なるので、注意が必要です。 |
| 470 | |
| 471 | {{{ |
| 472 | #!c |
| 473 | fgets(text, BUFSIZ, stdin); |
| 474 | }}} |
| 475 | |
| 476 | まず、 fgets() 関数は、 2つ目の引数に、文字列を受け取る配列の大きさを指定する必要があります。裏を返せば、ここでサイズを指定するからこそ、この関数は gets() 関数よりも安全であると言えます。 |
| 477 | |
| 478 | 説明を分かりやすくするために、 BUFSIZ を使うのをやめて、配列のサイズを普通の数字に置き換えてみましょう。 |
| 479 | |
| 480 | {{{ |
| 481 | #!c |
| 482 | char text[100]; |
| 483 | fgets(text, 100, stdin); |
| 484 | }}} |
| 485 | |
| 486 | このとき、 fgets() 関数は、ユーザーから入力された文字列が (改行を含めて) 配列の要素 '''99個分'''に収まらない場合には、配列の要素 99個分までを配列 text にコピーし、残りは読み込まなかったことにします[[FootNote(その場合、もう一度 fgets() 関数を呼ぶと、残りの文字列を読み込むことになります。)]]。いずれにせよ、配列に読み込まれた文字列の末尾には、文字列の終端を表すヌル文字 '\0' が書き込まれ、そのまま文字列として扱えるようになります。 |
| 487 | |
| 488 | それから、 3つ目の引数に指定している謎のキーワード '''stdin''' は、'''ファイルポインタ'''と呼ばれるものです。ここには本来、 fopen() という関数を用いてファイルを開いた際に得られる値を指定しますが、ユーザーからの入力を読み込む場合には、この stdin というキーワードを代わりに指定します。この stdin というのも、入出力ライブラリ stdio.h の中で定義されている値です。 |
| 489 | |
| 490 | {{{ |
| 491 | #!c |
| 492 | /* 改行文字を潰す */ |
| 493 | for (i = 0; i < BUFSIZ && text[i] != '\0'; i++) { |
| 494 | if (text[i] == '\n') { |
| 495 | text[i] = '\0'; |
| 496 | break; |
| 497 | } |
| 498 | } |
| 499 | }}} |
| 500 | |
| 501 | もう一つ大事な性質として知っておかなければならないのは、 fgets() 関数は受け取った文字列に含まれる改行文字をそのまま残すということです。このプログラムでは改行文字を残しておきたくはなかったので、上記のように、ループを回して配列の中を先頭から順に辿り、改行文字 '\n' を見つけたら、それをヌル文字 '\0' に置き換える、ということをやっています。ヌル文字に置き換えてしまえば、そこが文字列の終端になるので、改行文字はなかったことにされる、という寸法です。 |
| 502 | |
| 503 | なお、ユーザーからの入力を 1行読み込むために使用できる関数は、他にもいくつかありますので、各自で調べて、しっくり来るものを探してみるのも良いかも知れません。例えば GNU C ライブラリや POSIX.1-2008 に準拠したライブラリを用いた環境であれば [http://www.linux.or.jp/JM/html/LDP_man-pages/man3/getline.3.html getline() 関数]が使えます[[FootNote(GCC を用いているのであれば普通は使える…と言いたいところなのですが、残念ながら MinGW は GNU C ライブラリを用いていないためか、 getline() 関数は使えないようです…。)]]し、 Microsoft Visual Studio をお使いであれば [http://msdn.microsoft.com/ja-jp/library/5b5x9wc7%28v=VS.100%29.aspx gets_s() 関数]を使うという手もあります。 |
| 504 | |
| 505 | {{{ |
| 506 | #!c |
| 507 | #include <stdio.h> |
| 508 | |
| 509 | int main() |
| 510 | { |
| 511 | int a, b; |
| 512 | puts("a + b を求めるプログラム"); |
| 513 | printf("a = ? >> "); |
| 514 | scanf("%d", &a); |
| 515 | printf("b = ? >> "); |
| 516 | scanf("%d", &b); |
| 517 | |
| 518 | printf("a + b = %d\n", a + b); |
| 519 | |
| 520 | return 0; |
| 521 | } |
| 522 | }}} |
| 523 | |
| 524 | さて次に、ユーザーに文章ではなく数字を入力させ、それを数値として扱いたい場合にはどうしようか、という問題です。いくつかのアプローチが考えられますが、恐らく最も手軽なのは、上記のサンプルで使用している '''scanf() 関数''' を用いることでしょう。 printf() 関数が書式を指定して表示を行うのとは逆に、 scanf() 関数は指定した書式でのみ入力を受け付けて、そこから値を拾い上げるというものです。 |