5日目 7日目

6日目

目標

問題 4 の解説

mycal6.c正しく作成されていれば、 main()関数の中で、calendar(年, 月);と呼び出すことで 対応する月のカレンダが表示されるようになっているはずである。

したがって、問題4は、main() 関数での呼び出し部分の変更と、 呼び出しのための変数の準備を行う部分の作成を行えば良いことになる。 実行例

./mycal7 2006 11

を実現するためには、プログラム実行時の引数で年、月の順で指定しているので、 arg_test を使って、確認すると

の情報が「文字列」として格納されることになる。 文字列が表現している数値から数値への変換(例: "2005" から 2005 への変換)には atoiを用いれば良いのであったから、例えば main()関数を次のように書き直せば良い。

/* x.c */
#include <stdio.h>
#include <stdlib.h>  /* atoi() を使用したいため追加 */

int feburary(int);
int youbi_keisan(int, int);
int calendar(int, int);

int main(int argc, char *argv[])
{
	int y, m;  /* 引数で与えられた文字列から
		    * 変換した数値を保持する変数 */

	y = atoi(argv[1]);
	m = atoi(argv[2]);

	calendar(y, m);
	exit(0);
}

しかし、このままでは、引数をつけるのを忘れると

[tacha@proge1]~/kadai05% ./mycal7
zsh: segmentation fault  ./mycal7
[tacha@proge1]~/kadai05% ./mycal7 2006
zsh: segmentation fault  ./mycal7
[tacha@proge1]~/kadai05%

のように異常終了する。 これは、コマンド実行時の引数がたらないことで atoi()へ渡すパラメタが存在しないため、 不正なメモリアクセスが発生することで、OS により強制終了させられているためである。 したがって、このような「みっともない」状況を回避するためには、argv[1], argv[2]を使う前に、 存在するかどうかをチェックする必要がある。

        if (argc > 2) {
	    y = atoi(argv[1]);
        } else {
	    printf("No year\n");
	    exit(1);  /* ここで終了 */
        }

        if (argc > 3) {
	    m = atoi(argv[2]);
        } else {
	    printf("No month\n");
	    exit(1);  /* ここで終了 */
        }

とするか、年月の両方が必須なので、チェックは1度だけ行うことにして

        if (argc != 3) {
	    printf("missing parameters\n");
	    printf("Usage %s year month\n", argv[0]);
	    exit(1);  /* ここで終了 */
        }

	m = atoi(argv[1]);
	m = atoi(argv[2]);

などとする。 ここで argv[0] には起動したコマンド名が必ずはいるのでチェックは不要である。

確認

argv[0] を使ったエラー処理を行ったあとに、 コンパイル済みの mycal7 を別の名前にコピーして実行し、 エラー時のメッセージに含まれる名前が変化することを確かめてみよ。


課題 5

標準入出力

課題4までで出てきたプログラムでは、プログラムの実行が始まってしまうと、 対話的な場面はいっさいなく、いきなりプログラムが結果を出してしまっていた。 カレンダの場合には、それでも良かったのであるが、すべてのプログラムに当てはまるわけではない。
キーボードから打ち込めないワープロソフト等使い道があるだろうか?

一般に、プログラムには、入力(にゅうりょく, input)および 出力(しゅつりょく, output)がある。 すべてのプログラムは入出力を、少なくとも1つは持つはずである。 (入出力を持たないプログラムは CPU の浪費でしかない。)

C では、あらかじめ用意されている入出力が 3つ存在する。 これらは標準入出力(standard input/output)と呼ばれる。 その3つとは次の通りである。

標準出力 : 一般的には画面になっている
標準入力 : 一般的にはキーボードになっている。
標準エラー出力 : 一般的には画面になっている

しかし、この入力元/出力先は固定されているわけではない。 たとえば課題4で作成した mycal7を次のように実行してみると、 画面には何も表示されない。

 ~/kadai04/mycal7 2006 12 > 2006-12.txt

kadai05 のフォルダの中を確認すると、 lsで確認すると、新しく 2005-12.txt というファイルが 作成されていることが分かるはずである。 ダブルクリック等で開いてみると(正しく mycal7.c を作成していれば) 今月のカレンダが入っているはずである。 「> 2006-12.txt」 はシェルに対する標準出力先を2006-12.txtという ファイルに変更せよという指示である。

同様に「<」を用いると標準入力元も変更することができるが、 現段階ではまだ標準入力を扱うプログラムを作成していないので試せない。

課題5では、この標準入出力について学習する。 というわけで、いままでのカレンダ作成から気分を一新して、次のようなプログラムをつくってみよう。

/* mycat1.c */
#include <stdio.h>

int main()
{
        int ch;
        
        for (;(ch = getchar()) != EOF;) {
                putchar(ch);
        }
}

実行しても何も起きないはずである。 しかも、今までのプログラムと異なりプロンプトも戻ってこない....。 闇雲にキーボードを叩いた人は、Enter キーを押すと、入力した内容がそのまま画面に 出てくることに気付いたかも知れない。 このプログラムを終了するには Ctrl-D(実際には stty -a で表示される eof に割り当てられているコード)を 入力する。

では、このプログラムの内容を見ていくことにしよう。

まず、気付くことはforの使い方が少し変わっているという事である。 2日目のところを確認すると、「最初に1度だけ行う分」と 「毎回の最後に行う分」が何もない形であることが分かる。 このように、for文では不要な部分を省略することができる

getchar() は標準入力から1文字(1バイト)を読み込み、返す関数である。 もし、なにも読み込むものが無くなった場合は、EOF という特別な値を返す。 そのため、getchar() は、char で表される256通りではなく、257通りの値を取るため、 char ではなく、int の型を持つ。 結局、変数 ch には読み込めた場合にはその内容が、読み込むものがなくなった場合には EOF という特別なコードが代入されることになる。 したがって、for文の条件文は、「標準入力から何か読み込める限り」という 意味になる。

一方、putchar(ch)は標準出力へ 変数chの内容を出力する関数である。 変数 chには、標準入力から読み込んだ内容が入っているのであるから、 結局、このプログラムは、標準入力(キーボード)から入力された内容をそのまま 標準出力(画面)に出力するという動作を行う事になる。

それを確かめるために次の操作を行ってみよ。

  1. > を用いて標準出力をファイルに変更した上で、キーボードから何かを入力してみる。
  2. < の後ろに上で作られたファイルを指定して、その内容が画面に出ることを確認してみる。

問題 5-1

mycat1.cを元に、行番号を表示するようなプログラムを作成し、 mycatn.cという名前で保存せよ。 行番号を表示する幅は5桁とし、ファイルの内容の間には" | "を 表示せよ。

出力例
% ./mycatn < mycat1.c
    1 | #include <stdio.h>
    2 | 
    3 | int main()
    4 | {
    5 |         int ch;
    6 | 
    7 |         for (;(ch = getchar()) != EOF;) {
    8 |                 putchar(ch);
    9 |         }
   10 | }

while 文

なお、今回のような繰り返し処理の場合には、for ではなく、 C 言語に存在する別の繰り返し構文である whileを用いて

    while ((ch = getchar()) != EOF) {
         putchar(ch);
    }

と書かれていることが多い。

英語の while には、「…するうち」という意味があるので、 「()で与えられた条件が成立する間、繰り返し処理を行う」というふうに 読みくだすと覚えやすいかも知れない。 while の構成は次のようにまとめられる。

 while (続けるかどうかの判定文) {
     <繰り返し処理>;
 }

このようにある条件文が成立する間の繰り返し処理には、while文をつかい、 あらかじめ繰り返し回数がわかっているような場合は for を用いるというように、 使い分けると良い。

問題 5-2

次のプログラム(先週使った arg_test.c である)を while を使って書き直すとどうなるか?

/* arg_test.c */
/* コマンドライン引数について */

#include <stdio.h>

int main(int argc, char *argv[])
{
	int i;

	for(i = 0; i < argc; ++i) {
		printf("argv[%d] = <%s>\n", i, argv[i]);
	}
}

5日目 表紙 7日目

tacha@tack.fukui-med.ac.jp
$Id: 06.html,v 1.2 2006/12/06 00:28:29 tacha Exp $