5日目 7日目

6日目

目標


課題 5

標準入出力

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

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

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

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

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

 ~/kadai04/mycal7 2005 12 > 2005-12.cal

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

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

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

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

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

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

問題 5

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 | }

課題 6

ファイルからの入出力

課題5の mycat1 では、ファイルからの入力を処理するには シェルの redirect 機能である 「<」 を利用して

mycat1 < filename
とする必要があった。これを課題4で扱ったコマンドライン引数を用いて リダイレクト記号(<)を用いることなく実現する方法について考える。

標準入出力のリダイレクションではなく、ファイルからの入出力を直接 C プログラムから行うためにはプログラムの中でファイル操作を行わなければならない。 その操作を行うように mycat1.c を変更したプログラムを次に示す。

/* mycat2.c */
/*
 * 起動時に指定するファイル(1つ)の内容を
 * 標準出力に表示するプログラム
 */
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	int ch;
	FILE *fp;

	if(argc != 2) {
		printf("表示するファイル名を1つ指定してください\n");
		exit(1);
	}

	if (NULL == (fp = fopen(argv[1], "r"))) {
		printf("ファイル %s を開けません\n", argv[1]);
		exit(1);
	}

	while (EOF != (ch = fgetc(fp))) {
		fputc(ch, stdout);
	}
	fclose(fp);
	exit(0);
}

このプログラムの流れは次のようになる。

  1. 起動時に与えられたパラメタの数を確認し、ファイル名が指定されているかどうかを確認する。 ファイル名が指定されていなかったり2つ以上指定されていた場合はエラーとして、 プログラムを終了する。

  2. 指定されたファイルの内容を読み込めるように、準備を行う。 この準備の事を「ファイルをオープンする」と呼ぶ。 オープンできなかった場合には、エラーメッセージを表示してプログラムを終了する。

  3. オープンしたファイルから1文字ずつ読み込み、標準出力へ書き出す。 ここでは、for ではなく、新しく while文を使ってみた。

  4. 読み込むものがなくなれば、「ファイルをクローズ」してプログラムを終了する。

最初にファイルからの入出力に関する部分として増えた部分を見ていく。


fopen

fopen関数は、引数を2つ取る関数で、第1引数としてファイルの位置を指定し、 第2引数でどのようなモードでオープンするかを指定する。 ファイルがオープンできれば、ストリーム(stream)と結び付け、結果を FILE型の ポインタとして返す。 オープンできなかった場合は NULLという値を返すので、 返り値が NULL かどうかを確認することでファイルオープンが成功したかどうかを判定することが可能である。

英語の stream には、「流れ」と言う意味があるが、ここで流れるものはデータである。 ストリームにデータを書き込んだり、ストリームからデータを読み込むことによって、 実際のファイルやデバイスからの入出力を実現しているのである。

課題5で取り上げた、標準入出力も実はストリームであり、プログラムの開始時に 自動的に準備されているものである。 標準入力はstdin, 標準出力はstdout、 そして標準エラー出力はstderrという名前でアクセスすることができる。

第2引数として指定できるモードとしては次のようなものが用意されている。

これ以外にも、r+, w+, a+ というモードが あり、読み書き両用にファイルをオープンする際に用いる。

さらに、bという文字を(先頭部分以外に)追加することによって、 ストリームがバイナリモードであることを指示することもできる。 POSIX 準拠システム(本講義で使用している Linux を含む)では、 通常のテキストモードとバイナリモードの違いはないので、 この bを指定しても無視されるだけであるが、 システムによっては、区別しているものもあるため、 移植を行う事が予想されるようなプログラムの場合は適切に指定することが必要である。


fclose

ファイルとストリームを接続する為にオープンするのに、fopen を用いたが 不要になったストリームとファイルの接続を解除するのにはfclose 関数を用いる。引数には fopenで与えられた FILE 型のポインタを 指定する。


fgetc

fgetcは 引数のFILE 型のポインタで示されるストリームから1文字読み込む。 読み込めなかった場合は EOF を返す。 getcharfgetc(stdin)に相当すると考えれば良い。


fputc

fputcは、引数を2つ取り、最初の引数で指定された内容を、 2番目の引数である FILE 型のポインタで示されるストリームに出力する。 putchar(ch)fputc(ch, stdout)に相当すると考えれば良い。


while

課題5では、ファイルの内容を読み出すのに forを使ったが、 課題6では、while文を用いた。

while 文は次のような構成になっている。

 while (繰り返しを行うかどうかの判定文){
     <繰り返し処理>
 }

英語の while には、「…するうち」という意味があるので、 「()で与えられた条件が成立する間、繰り返し処理を行う」というふうに 読み下だすと覚えやすいかも知れない。 このようにある条件文が成立する間の繰り返し処理には、for文ではなく この while文が使われている場合が多い。


exit

今までのプログラムでは、プログラムは必ず main()の最後まで実行しないと 終了しなかった。 プログラムを書いている場合、ある特定の条件の場合には、プログラムを途中で終了したいような場合も 多く存在する。(たとえば、カレンダ表示プログラムで「14月」がしていされた場合など) このような場合には、exit関数を用いる。

exit関数は、呼び出されるとプログラムを正常に終了させる。 終了する前に、プログラム中でオープンされ、まだクローズしていないファイルを クローズするなどの手続きを行う。詳しくは man 3 exitを参照のこと。

一般に、プログラムの正常終了時には引数として 0 を、間違ったパラメタが 与えられるなどエラーのために異常終了する場合には 0以外を引数に指定する。

問題 6

上で作成した mycat3.cを元に、与えられたファイルの 先頭5行だけを表示するようなプログラムを作成し myhead.cという名前で 保存せよ。

繰り返し処理を途中で終了するには、break文を利用することができる。


5日目 表紙 7日目

tacha@tack.fukui-med.ac.jp
$Id: 06.html,v 1.1 2005/12/02 04:34:21 tacha Exp $