lesson6(update:2019/11/14) [例題 6-1課題6]


6. ファイル処理


[ prev | next | index ]

目次

[目次]


6.1 ファイル

6.1.1 ファイルとストリーム

 画面への文字列の出力やキーボードからのデータ入力は、一列に並んだデータの書き出しや読み込みです。 この様な順番に並んだデータの流れ をストリーム(stream)と言います。一方で、データを一列に並べたもの をファイル(file)とも言います。このファイルとストリームは同じモノの 動的な側面と静的な側面を表わしていると見なすことが可能です。
 キーボードからの入力は順番に押されたキーのデータの流れでストリームの一種です。しかし、押された順番に並んだデータと見なしてC言語ではファイルの 一種として扱います。

ファイルはHDD等の二次記憶に置かれたデータのまとまりを表わす言葉としてもよく使われます。二次記憶装置と主記憶装置とのデータの入出力は まとまったデータを順番に読み込んだり、書き込んだりするストリームで実現されるため、二次記憶装置は一列にデータが並んだファイルを記録する装置と言っ ていいでしょう。

二次記憶装置

 計算機の記憶装置はRAM(ランダムアクセスメモリー)を用いた主記憶装置と、HDD等の二次記憶装置に分類できます。主記憶装置では0 番地から一列に振られたアドレス によってメモリを区別して読み書きを行います。最近のパソコンでも容量は2〜4GByte程度で、アクセス速度は1秒間に10億回(1GHz)以上の場合も多くかなり高速で す。この様な主記憶の特徴はアドレスを指定することで並び順とは関係なく、どのデータも同じように高速に参照できることです。

 二次記憶装置はフロッピーディスク(FD)の様な遅くて容量の小さい物から、数100GByte程度の容量を持つハードディスク (HDD) まで様々な物があります。電源無しでデータを維持できるのが普通で、フロッピーディスク、MOディスク、CD-ROM、DVD-RAM、フラッシュメモリーなど記憶媒体を装置 から取り出して持 ち運べる物も存在します。二次記憶装置はその物理的構造からデータの並び順に従ってなら高速にデータを参照できます。しかし、とびとびのデータを参照する のは不得手で、この場合データの参照は主記憶装置に比べて桁違いに低速になります。データの読み取り時間は主記憶なら常に数十ns以下ですがHDDの場合は遅 いと数十ms必要になります。この差は6桁もあります。
 日常的な例では、ビデオテープは充分に映像を再生できる速度でデータを読めるが、一方で頭出しの作業は非常に時間がかかるのと同じ理屈です。 (DVDやHDDを使ったビデオコーダの頭出しはかなり改善されていますが、それでも計算機の速度から見ると非常に遅い)

 二次記憶装置ではファイル名(file name)を使ってファイルを区別します。この様なファイルやその入出力の管理は計算機のオペレーティングシステム(OS)の役目 で、様々な手法が存在します。また計算機の処理速度の点からは二次記憶装置の読み書き速度が一様でなく、場合によっては極端に遅くなることへの対応が重要 です。データを送る側と受る側の速度が違うために同期が取れないことへの対処 が必要になるわけで、この問題は二次記憶装置だけでなくキー入力や画面への出力、通信といった場面で一般に発生し、ストリームを処理するする上での重要な問題です。

6.1.2 バッファ

 この問題を解決するために多くの場合、バッファ(buffer)が 使われます。バッファは送られてくるデータを一時的に溜める場所です。バッファがあれば、受取側は自分のペースでバッファからデータを取り出せばいい事に なります。HDDへの書き込みの場合、プログラムは主記憶に置かれたバッファにデータを書き込み、バッファのデータがある程度溜まったところで、二次記憶 装置のHDDへまとまった連続データとして書き込めば効率的です。HDDからの読み出しも連続データとしてバッファに読み込んでおいて、プログラムからは バッファのデータを利用することにすれば処理は高速に行えます。この様なバッファはオペレーティングシステム側で用意し管理するのが普通です。

[目次]


6.2 テキストファイルの処理

 Cプログラムではファイルはstdio.h内に定義されたFILE型を 用いて処理します。FILEはバッファ等のファイル処理に必要なデータがまとめられたデータ領域で、通常の利用ではその中身の構造を知る必要はありません。 stdio.h内にはファイルを処理する為の関数が宣言されていますが、これらの関数はFILE型データをそのポインタで受け取る形に書かれています。 FILE型を指すポインタをファイルポインタと呼ぶ。以下にファイルを使うプ ログラムの手順に従って、stdio.hに宣言された主なテキストファイル処理関数を紹介します。

6.2.1 ファイルを開く

FILE型のデータ領域を確保し初期化する関数。指定されたファイルを検索し、読み書き用のバッファなどを準備する。

FILE *fopen(const cahr *fname, const char *mode);

ファイル名:fnameのファイルを処理するFILE型領域(バッファなどを含む)の確保と初期化を行い、そのポインタを戻す。旨く行かない場合は NULLを戻す。テキストファイルに関連したmodeの文字列の例を以下に示す。バイナリファイルでは"rb"など「b」が後ろに追加される。

fopen関数は、ファイルの読み書きに必要な情報を入れるFILE構造体を作成して、そのアドレスをFILE型のポインタとして返す。

例1: // ファイルのオープン
FILE *fin = fopen("abcde.txt","r");

例2: // エラー処理を追加した場合
FILE *fin = fopen("abcde.txt","r");
if((fin == NULL) {
printf("ファイル%sを開くことができません","abcde.txt");
}


if((fin=fopen(fname,"r"))==NULL)printf("ファイル%sは開くことができません",fname);
は良く使われる書き方ですが、分り易く分けて書けば次の様になります。
fin=fopen(fname,"r");
if(fin==NULL)printf("ファイル%sは開くことができません",fname);

 

6.2.2 テキストファイルの読み書き

1)1バイト(1文字)の読み書き

getcharと同様な関数。streamから一文字(1バイト)読み出しunsigned int型の値として戻す、ファイルの終端やエラーの発生ではEOFを戻す。正常に読込めれば0-255の範囲の値が戻される。この範囲外の値は何らかの異常を示すことになる。

int fgetc(FILE *stream);
int getc(FILE *stream);
getchar()はgetc(stdin)と同じ

putcharと同様な関数。streamに一文字書き出し、cをunsigned int型の値に変換して書き出し、書き出した値を戻す、エラーの発生ではEOFを戻す。常に0-255の範囲の値を文字コードとして書き出す。範囲外の値 を引数に与えても上位のバイトは捨てられ、最下位の1バイトのみを出力する。異常は戻り値で判定可能。

int fputc(int c,FILE *stream);
int putc(int c,FILE *stream);
putchar(c)はputc(c,stdout)と同じ

2)文字列の読み書き

getsと同様な関数。streamから改行か終端までの最大でn-1文字を文字配列sに読み込む。
※改行の文字も文字列に読み込まれる。
文字列終端の'\0'を追加する。sを そのまま戻すが、エラーの発生ではNULLを戻す。

char* fgets(char*s, int n, FILE *stream);

putsと同様な関数。streamに文字列sのデータを書きだす。書き出した文字数を戻すが、エラーの発生ではEOFを戻す。

int fputs(char*s, FILE *stream);
puts(s)はfputs(s,stdout)と同じ

3)書式付き文字列の読み書き

printf や sacnfと同様な関数。テキストファイルにデータを読み書きするのに便利

int fprintf(FILE *stream, const char* format,...);
int fscanf(FILE *stream, const char* format,...);
 
例: fprintf(fp,"%s さんは %d才\n","安藤",25);
良く似た関数として文字列を対象にした以下のものがある int sprintf(char *s, const char* format,...);文字配列sに書き出す int sscanf(const char *s, const char* format,...);文字列sから読み込む

4)フラッシュ

出力ストリームのバッファからデータを全て書き出し、バッファを空にする処理

int fflush(FILE *stream);

正常に処理できれば0を戻す。異常が生じた場合はEOFを戻す。streamがNULLの場合は全出力ストリームをフラッシュする。 streamが入力ストリームの場合は動作は不定。

例: fflush(fout);
例: 標準出力のバッファをフラッシュする場合は fflush(stdout);

(注)バイナリファイルについては教科書など適当な参考書を見てください。

6.2.3 ファイルを閉じる

fclose関数は、必要でなくなったFILE構造体のメモリーを開放する。

int fclose(FILE *stream);

streamが入力の場合はまだ読まれていないデータを捨て、出力の場合はバッファのデータをフラッシュする。

streamが指すFILE型領域を解放しファイルを閉じる。うまくいけば0を戻し、異常があればEOFを戻す。

例1:fclose(fin);
例2:
if(fclose(fin) == EOF)
printf("ファイルを閉じる際にエラーが発生しました\n");

6.2.4 標準入出力ファイル

実は、プログラムが開始された時点で既に開かれているストリーム(テキストファイル)が3個ある。これらをプ ログラム中でfopenやfcloseしてはいけない。

stdin
標準入力:デフォルトではキーボードからの入力ストリーム。OS側(Unix、Windowsなど)でHDD上のファイルや通信のストリー ムに切り替えられる場合もある。これをリダイレクションと言う。
stdout
標準出力:デフォルトでは画面へのテキスト出力ストリーム。stdinと同様にリダイレクションが可能。
stderr
標準エラー出力:画面へのテキスト出力ストリーム。実行時エラーなどのテキストをstdoutとは区別して書き出せる様に別のストリーム として用意されている。stdoutをリダイレクションで他のファイルに書き出している場合でもstderrは画面に出力するといった区別ができる。

6.2.5 行番号付き画面出力(例題)

HDD上のテキストファイルを行番号付きで画面出力するプログラム。
※下記のプログラムをレポートツールで実行するさい、現在はファイルを 捜すフォルダはレポートツールが置かれたフォルダです。
今後、レポートツールの更新で実行ファイルのフォルダになるように変更 を予定しています。

テスト実行では正常に動作したのにレポート送信時の実行ではファイルが見つからずエラーになることが在ります。レポートツールと同じ場所に読み込むファイルを置いて実行してください

例題6-1

/*テキストファイルを読んで行番号付きで表示するプログラム*/
#include<stdio.h>

char fileName[]="p6.c";/*元のcプログラムファイル*/

int main(void)
{   
	FILE *input;
	int num,c;
		
	/*------------ファイルを開く------------*/
	input=fopen(fileName,"r");/*名前がfileNameで読み込み専用ファイル*/
	if(input==NULL){
		fprintf(stderr,"ファイル%sを開けません",fileName);
	}

	/*------------ファイルを1字づつ読む------------*/
	num=1;
	printf("%4d: ",num);/*行番号を書いて*/
	while(1){
		c=fgetc(input);
		if(c==EOF)break;
		putchar(c);
		if(c=='\n'){  /*改行*/
			num++;
			printf("%4d: ",num);
		}
	}

	/*------------ファイルを閉じる------------*/
	fclose(input);
	
	return 0;
}

[実行結果]

   1: /*テキストファイルを読んで行番号付きで表示するプログラム*/
   2: #include<stdio.h>
   3: 
   4: char fileName[]="p6.c";/*元のcプログラムファイル*/
   5: 
   6: int main(void)
   7: {   
   8: 	FILE *input;
   9: 	int num,c;
  10: 	
  11: 	/*------------ファイルを開く------------*/
  12: 	input=fopen(fileName,"r");/*名前がfileNameで読み込み専用ファイル*/
  13: 	if(input==NULL){
  14: 		fprintf(stderr,"ファイル%sを開けません",fileName);
  15: 	}
  16: 
  17: 	/*------------ファイルを1字づつ読む------------*/
  18: 	num=1;
  19: 	printf("%4d: ",num);/*行番号を書いて*/
  20: 	while(1){
  21:		c=fgetc(input);
  22: 		if(c==EOF)break;
  23: 		putchar(c);
  24: 		if(c=='\n'){  /*改行*/
  25: 			num++;
  26: 			printf("%4d: ",num);
  27: 		}
  28: 	}
  29: 
  30: 	/*------------ファイルを閉じる------------*/
  31: 	fclose(input);
  32: 	
  33: 	return 0;
  34: }

[目次]


6.3 演習課題

課題6 HTMLファイルに変換

このファイル(p06.c)を使ってプログラムを作成すること。 (右クリック「対象をファイルに保存」でダウンロードする)

CのソースプログラムをHTMLファイルに変換するプログラムを作りなさい。

変換元のプログラムファイルと書き出すHTMLファイルの名前をキーボードから入力すると、プログラムファイルを読み込んで変換を行い、 HTMLファイルに書き出す。下記にヒントを示す。太字の注釈に注意して課題のプログラムを作成すること。

注 変換元のプログラムファイルはレポートツールと同じフォルダに置いてください。Runボタンでは実行できてReportボタンではうまくいかない場合があります。

「ファイル名の文字列の読み込み」

1)scanfを使う場合(非推奨)

char fname[100];/*読み込む文字列よりも大きな配列を用意する*/

printf("入力ファイル名?\n");
fflush(stdout);
scanf("%s",fname);/*読み込む文字列が99文字以上だとfnameに収まらないので注意*/

※上記の様にscanfで書式%sを使って読み取る場合、文字列の長さの上限がチェックされません。したがってこのようなプログラムは一行が100文字を超える入力によって暴走しやすく、セキュリティーホールを持つことにもなります。少し面倒かもしれませんがfgets関数を使って入力する文字 数を制限すると安全です。

2) fgetsを使う場合(推奨)
 fgetsは入力文字列の取り込みで改行コードも文字列の一部として取り込みます。この結果、入力でエンターキーを押したときにファイル名の文字列の最後に 改行コード('\n')が付いてしまいます。この文字列のままでは、fopenでファイルが作れません。最後の改行コード('\n')を除く以下の赤字の部分 が必要です。

char fname[100];/*読み込む文字列よりも大きな配列を用意すること*/

printf("入力ファイル名?\n");
fflush(stdout);
fgets(fname,100,stdin);/*100文字を超える入力に対しても99文字までしか読み込みません*/
for(c=0;c<100;c++) if(fname[c]=='\n')fname[c]=0;

文字列を格納する配列を1個余計に用意すればsscanfを使って次のようなことも可能です。

char buffer[100];/*文字列の1次的な置き場所*/
char fname[100];/*ファイル名を入れる配列*/
......

fgets(buffer,100,stdin);
sscanf(buffer,"%s",fname);/*標準入力の代わりに文字列から読み取るscanf関数*/

 

「ファイルを読んでそのまま別ファイルに書き出すプログラム例」

/*HTMLファイルへ変換するプログラム p6.c*/

#include<stdio.h>
 
/*ここではファイル名をプログラム内に文字列定数で与えましたが*/
/*提出する課題では、ファイル名を実行時にキー入力する形に書き直してください*/ 
char inputFileName[100]="p06.c";/*元のcプログラムファイル*/
char outputFileName[100]="p06.html";/*作るHTMLファイルの名前*/

int main(void)
{
	/*FILEはバッファを含むストリーム管理領域。*/
	/*ファイル処理関数はこの領域を示すポインタを必要とする*/
	FILE *input,*output;
	int c;

	/*------------ファイルを開く------------*/
	input=............ ; /*読み込み専用でファイルinputFileNameをオープンする*/
	if(input==NULL){
		printf("入力ファイルが開けませんでした");
		return;
	}
	output=............ ; /*書き込み専用でファイルoutputFileNameをオープンする*/
	if(output==NULL){
		printf("出力ファイルが開けませんでした");
		fclose(input);
		return;
	}

	/*------------ファイルの読み書き------------*/
	fprintf(output ,"<HTML><HEAD></HEAD><BODY><PRE>\n");/*HTMLファイルの最初の1行*/

	/*一字づつ読んでは書き出し*/
	while(EOF!=(c=getc(input))){
		putc(c,output);
		/*ここはこのままでは不十分。(注)の様に、タグに使われる文字を変換する必要がある*/
	} 
	fprintf(output ,"\n</PRE></BODY></HTML>\n");/*HTMLファイルの最後の1行*/

	/*------------ファイルを閉じる------------*/
	fclose(input);
	fclose(output);



	/*チェックのために書き出したファイルを画面表示する*/
	input=fopen(outputFileName,"r");
	while(EOF!=(c=getc(input))){
		putchar(c);
	}
	fclose(input);

	return 0;
}
	

(注)

HTMLでは 「<」 と 「>」 は特別な意味を持つ文字です。Webブラウザにこの文字 を表示させる為には、以下のような変換が必要になります。

元の文字
変換後の文字列
<
&lt;
>
&gt;
&
&amp;
 


6.4 ホームワーク(ファイル処理)

ソースコードと出力ファイルの中身をWordに貼り付けてピアサポ室のポストに提出すること。 【提出期限 11月20日(水)まで】
レポート上部に「 プログラミング序論演習U 第6回(ファイル処理) 学籍番号 氏名 」を記入すること。

5名の学生の5教科(国語、数学、理科、社会、英語)の試験の点数が入ったファイルがある。 この入力ファイルを読み込み、名前と平均点を出力ファイルに出力するプログラムを作成せよ。 ただし、入力、出力ファイル名はつぎのようにする。(入力ファイル名:exam_data.dat, 出力ファイル名:average.dat)

入力ファイル:
安藤 80 72 65 69 76
内田 74 88 71 67 89
小島 62 69 90 78 71
浜田 85 75 78 83 88
山岡 67 74 79 81 63

出力ファイル:
安藤 72.4
内田 xx.x
小島 xx.x
浜田 xx.x
山岡 xx.x

(xx.xには数値が入る)

ヒント


[ prev | next | index ]