第15回 テキストファイルのリード/ライト

このドキュメントは http://icrus.org/c_language_beginers_course/ 上にあります.

ここまでのプログラムは,数値,文字列などを入力して,それを処理し,結果を出力するものでした.しかしこれでは,せっかく入力したデータも,出力データも1回の処理に利用されるだけで,データを他のプログラムで利用したりすることは,不可能でした.またせっかく時間をかけて入力したデータも,1回限りでつぎに同じプログラムを実行してもまた入力し直さなけらばなりませんでした.その不便さを「ファイルの操作」が解決します.
 ファイルには①テキストファイルと②バイナリーファイルの2種類があります.その違いを以下に示します.この章ではテキストファイルについて説明します.
①テキストファイル 簡単に言って,MS-DOSのTYPEコマンドやテキストエディタ,ワープロで読むことができるファイルのことです.一般にアルファベットや記号,数字,日本語と復改,ファイルエンドなどのコードからできています.
②バイナリーファイル テキストファイルで機能が特定されているコード(文字に割り当てられていないコード)を含む1バイトて表される256通りのコードすべてを自由に使えるファイル形式です.普通のテキストエディタでは見ることができません.

[課題15-1]誕生日のリストをファイルにしよう.

[プログラム例15-1]


01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <process.h>
04: #define  FILENAME "a:\\datafile.dat"
05: void main(void){
06:     char name[50], str[50];
07:     FILE *fp;
08:     if( (fp=fopen( FILENAME, "at"))==NULL ){
09:         printf( "file open error:%s\n", FILENAME );
10:     exit(1);
11:     }
12:     while(1){
13:         printf("名前を入力して下さい(リターンのみで終了します.):\n");
14:         gets( name );
15:         if( *name=='\0' )
16:             break;
17:         printf("%sさんの生年月日を入力して下さい(例1995,9,30):\n", name );
18:         gets( str );
19:         printf( "%s,%s-->ファイル\n", name, str );
20:         fprintf( fp, "%s,%s\n", name, str );
21:     }
22:     if( fclose( fp )==EOF ){
23:         printf( "file close error:%s\n", FILENAME );
24:         exit(1);
25:     }
26: }

行説明
02~03行 → 関数exit(1)を使用するときに必要なインクルードファイルです.
04行目 → 処理の対象とするファイル名をdefineします.ファイル名が必要な文脈にはすべてdefineされたFILENAMEを使用します.
07行目 → ファイルポインタfpを宣言します.
08~11行 → ファイルをオープンするときの決まり文句です.必要に応じてファイル名("a:\datafile.dat")とオープンモード("at")を変更します.エラーが出るのは,「ファイルがオープンできないときです.」
20行目 → 関数fprintf()を使ってファイルに出力します.使い方は関数printf()とほとんど同じです.ポインタfpは関数fopen()から渡されたファイルポインタです.
22~25行目 → ファイルを閉じるときの決まり文句です.エラーが出るのは「ファイル容量が足りないとき」などです.

[結果15-1]

コンパイル・実行すると次のよう表示されます.
名前を入力して下さい(リターンのみで終了します.):
常盤 貴子[リターン印]
常盤 貴子さんの生年月日を入力して下さい(例1995,9,30):
1972,4,30[リターン印]
常盤 貴子,1972,4,30-->ファイル
名前を入力して下さい(リターンのみで終了します.):
葉月 里緒奈[リターン印]
葉月 里緒奈さんの生年月日を入力して下さい(例1995,9,30):
1975,7,11[リターン印]
葉月 里緒奈,1975,7,11-->ファイル
名前を入力して下さい(リターンのみで終了します.):
森田 和義[リターン印]
森田 和義さんの生年月日を入力して下さい(例1995,9,30):
1945,8,22[リターン印]
森田 和義,1945,8,22-->ファイル
名前を入力して下さい(リターンのみで終了します.):
[リターン印]

MS-DOSのTYPEコマンドを使うと作成したファイルの様子が見れます.
A:\>TYPE DATAFILE.DAT
常盤 貴子,1972,4,30
葉月 里緒奈,1975,7,11
森田 和義,1945,8,22

 次に,同じこのプログラムを実行すると,データをつけ足していくことができます.
ファイルの処理には①ファイルをオープンする.→本の表紙を開くことに相当します.②ファイルを処理する.→本の内容を読んだり,ノートに書き込んだり,読み込んだデータを処理したりします.③ファイルをクローズする.→本の表紙を閉じることに相当します.という3つの作業が必要です.
①では,標準関数fopen()を用いています.この関数では,オープンしたいファイル名とオープンモードを引数で渡すと,ファイルポインタを返してきます.ファイルポインタを用いて,ファイルの読み書きをします.ファイルポインタはファイルの書いたり読んだりする場所を指していると考えて下さい.
②では,標準関数fprintf()を使って,画面出力する標準関数printf()と同様にファイルへ,出力します.出力するファイルはファイルポインタで指示します.③では,標準関数fclose()を用いてファイルを閉じます.通常プログラムを終了するときは自動的にファイルをクローズしてくれますが,エラーのためクローズできないことがたまにありますので,エラー処理を含めて記述しておく必要があります.
 プログラム自体は簡単で,名前と生年月日を入力して,ファイルに出力(書いていく)するものです.名前入力のとき文字を入力しないでリータンキーを押すとプログラムはファイルをクローズした後,終了します.

[課題15-2] 誕生日のリストからその人が何座か調べよう.

[プログラム例15-2]


01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <process.h>
04: #define  FILENAME "a:\\datafile.dat"
05: char *seiza[]={ "月のデータがまちがい",
06:     "山羊座", "水瓶座", "魚座",
07:     "牡羊座", "牡牛座", "双子座",
08:     "蟹座", "獅子座", "乙女座",
09:     "天秤座", "蠍  座", "射手座" };
10: void main(void){
11:     char name[50], str[50];
12:     FILE *fp;
13:     int  year, month, day;
14:     int get_seiza(int, int);
15:     if( (fp=fopen( FILENAME, "rt" ))==NULL ){
16:         printf( "file open error:%s\n", FILENAME );
17:         exit(1);
18:     }
19:     while( fgets( str, 50, fp )!=NULL ){
20:         sscanf( str, "%[^,],%d,%d,%d", name, &year, &month, &day);
21:         printf( "%s %d %d %d\n", name, year, month, day );
22:         printf("%sです.\n", seiza[ get_seiza( month, day ) ] );
23:     }
24:     getchar();
25:     if( fclose( fp )==EOF ){
26:         printf( "file close error:%s\n", FILENAME );
27:         exit(1);
28:     }
29: }
30:
31: int get_seiza( int month, int day ){
32:     switch( month ){
33:     case 1: if( day>=21 ) /* 1月21日~*/
34:         month++;
35:         break;
36:     case 2: if( day>=20 ) /* 2月20日~*/
37:         month++;
38:         break;
39:     case 3: if( day>=21 ) /* 3月21日~*/
40:         month++;
41:         break;
42:     case 4: if( day>=21 ) /* 4月21日~*/
43:         month++;
44:         break;
45:     case 5: if( day>=22 ) /* 5月222日~*/
46:         month++;
47:         break;
48:     case 6: if( day>=22 ) /* 6月22日~*/
49:         month++;
50:         break;
51:     case 7: if( day>=23 ) /* 7月23日~*/
52:         month++;
53:         break;
54:     case 8: if( day>=24 ) /* 7月24日~*/
55:         month++;
56:         break;
57:     case 9: if( day>=24 ) /* 8月24日~*/
58:         month++;
59:         break;
60:     case 10:if( day>=24 ) /* 10月24日~*/
61:         month++;
62:         break;
63:     case 11:if( day>=24 ) /* 11月24日~*/
64:         month++;
65:         break;
66:     case 12:if( day>=23 ) /* 12月23日~*/
67:         month = 1;
68:         break;
69:     default:month = 0;    /* monthが1~12月でない場合*/
70:         break;
71:     }
72:     return month;
73: }


05~08行→星座の文字列をポインタの配列seizaに設定します.
15~18行→ファイルをオープンするときの決まり文句です.今回はデータファイルを読み出しますのでオープンモード"rt"にします.
19~24行ファイルが終了するまで繰り返します.
19行目→関数fgets()を使ってファイルのデータを読み出します.ファイルの終わりに来ると関数fgets()はNULLを返します.そのときwhileループを終わります.
20行目→関数sscanf()を用いて文字列strから文字列name,変数year,month,dayを取り出します.
22行目→関数get_seiza()を呼び出し,その戻り値により配列seizaの文字列を関数printf()により出力します.
25~28行目→ファイルを閉じるときの決まり文句です.
31~73行目→関数get_saiza()の本体.月と日付から実際の星座の番号を返します.そのためにswitch文を使っています.

[結果15-2]

コンパイル・実行すると次のよう表示されます.
常盤 貴子 1972 4 30
牡牛座です.
葉月 里緒奈 1975 7 11
蟹 座です.
森田 和義 1945 8 22
獅子座です.
リターンキーを押すとプログラムは終了します.

 プログラムは,プログラム例15-3を使って入力したデータファイルを読み出して,その誕生日から星座名を出力するものです.ファイルが終了するまでファイルのデータと星座名を出力します.

[文法15]

1.ファイルのオープン


FILE *fp;
fp = fopen( "ファイル名", "モード" );

 関数fopen()は"ファイル名"のファイルを"モード"のオープンモードでオープンします.オープンに成功したときは,ファイルポインタを返します.失敗したときはNULLを返します.オープン以降,ファイルへのアクセスにはファイルポインタfpを使用します.

ファイルのオープンモード
モード 働き
----------------------------------------------------------------------
rt rb 読み出し専用としてオープンする.ファイルがないときはエラー (NULL)を返す
wt wb 書き込み用としてオープンする.ファイルが既にあるときは前の 内容は失われる.ファイルがないときは新規作成する.
at ab 追加用としてオープンする.ファイルの終わりから書き込む.フ ァイルが存在しない場合は新規作成する.
r+t r+b 既存ファイルをリード/ライト用としてオープンする.ファイル が見つからないときはエラー(NULL)を返す.
w+t w+b リード/ライト用としてオープンする.ファイルが既にあるとき は前の内容は失われる.
a+t a+b リード/ライト用としてオープンする.ファイルの終わりからリ ード/ライトする.ファイルがないときは新規作成する.
右文字がtのときテキストファイル
右文字がbのときバイナリーファイル

2.ファイルのクローズ


FILE *fp;
fclose( fp );

 関数fclose()はファイルポインタfpの指すファイルをクローズします.クローズに成功したときは0を返します.失敗したときはEOFを返します.

3.テキストファイルのリード


FILE *fp;
char *str;
int n;
fgets( str, n, fp );

 ファイルポインタfpが指すファイルから最大でn-1文字の文字列を入力し,文字列strに代入します.文字列strの最後にはヌル文字'\0'が代入されます.

4.テキストファイルのライト(1)


FILE *fp;
char *str;
int n;
fpus( str, fp );

 ファイルポインタfpが指すファイルに文字列strを出力します.ヌル文字'\0'は出力しません.

5.テキストファイルのライト(2)
FILE *fp;
fprinf( fp, "変換文字列", 引数並び, ・・・ );

 関数printf()と同様に変換文字列によって変換した引数並びの変数および文字列をファイルポインタfpが指すファイルに出力します.


5.switch文


switch( 式 ){

    case ラベル1:
        文;
        break;

    case ラベル2:
        文;
        break;

    default:
        文;
}

switch文はif文else文がいくつも連なったような制御構造と考えることができます.
①式の結果と同じ値のラベルがあるcase ラベル:に飛び,以降の文が実行されます.
②一般的には,文の後(次のcase ラベル:の前)にbreak文をおいて,switch文のブロックから抜けます.(break文がないとブロックを抜け出すことなく,以降のすべての文を実行してしまいますので注意が必要です.)
③式の結果に合うラベルがないときは,default:以降の文が実行されます.





このドキュメントは http://icrus.org/c_language_beginers_course/ 上にあります.

2017,1 ssatoh@ 足立工科大学 工学部 情報通信工学科