Changes between Version 27 and Version 28 of HowTo/CTutorial


Ignore:
Timestamp:
Sep 16, 2010, 9:42:05 AM (14 years ago)
Author:
村山 俊之
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • HowTo/CTutorial

    v27 v28  
    237237MinGW を導入する場合は、 [http://sourceforge.net/projects/mingw/files/ SourceForge.net の MinGW ダウンロードサイト]にて、「Download Now!」ボタンをクリック (または「Automated MinGW Installer」→「mingw-get-inst」→「mingw-get-inst-''yyyymmdd''」→「mingw-get-inst-''yyyymmdd''.exe」の順に選択) してインストーラをダウンロードし、実行して下さい。デフォルトの構成では C コンパイラのみの構成の GCC のみがインストールされます。 MinGW の GCC は Windows のコマンドプロンプトからでも扱うことはできますが、他の言語のコンパイラや UNIX ライクなターミナルが欲しい場合は、構成ツリーが表示されたところで該当するコンポーネントにチェックを入れて下さい。
    238238
    239 MinGW を使用する場合は、さらに、 [http://gnuwin32.sourceforge.net/ GnuWin] プロジェクトが配布するパッケージ [http://gnuwin32.sourceforge.net/packages/libgw32c.htm LibGW32C] を入れておくことをお勧めします。これにより、 GNU 拡張仕様由来の一部の関数 (getline() など) が使えるようになります[[FootNote(使用する際には、コンパイル時にオプション -lgw32c を (末尾に!) つける必要があります (ex: gcc -o test test.c -lgw32c)。)]]。 LibGW32C の配布ページにて「Developer files」と書かれた方の Zip ファイルをダウンロードし、展開したファイルを、 MinGW のインストールディレクトリ下 (通常は C:\MinGW) に上書きコピーしてください。
    240 
    241239 * Linux や *BSD、 Solaris、 Mac OS X などの UNIX ライクな OS を利用する場合
    242240
     
    448446#!c
    449447#include <stdio.h>
    450 #include <stdlib.h>
    451 
    452 int main()
    453 {
    454     char *text = NULL;
    455     size_t length;
    456     ssize_t letter_num;
     448
     449int main()
     450{
     451    char text[BUFSIZ];
     452    int i;
    457453    printf("何か入力してください。 >> ");
    458     letter_num = getline(&text, &length, stdin);
    459     text[letter_num - 1] = '\0';    /* 改行文字を潰す */
     454    fgets(text, BUFSIZ, stdin);
     455   
     456    /* 改行文字を潰す */
     457    for (i = 0; i < BUFSIZ && text[i] != '\0'; i++) {
     458        if (text[i] == '\n') {
     459            text[i] = '\0';
     460            break;
     461        }
     462    }
     463   
    460464    printf("あなたは今、「%s」と入力しましたね。\n", text);
    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
     469gets() 関数の代用として最も一般的に使われているのは、おそらく '''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
     509int 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() 関数は指定した書式でのみ入力を受け付けて、そこから値を拾い上げるというものです。
    516525
    517526[[FootNote]]