第6回ファイル操作(5月26日)

今日のポイント

■ オープンとクローズ

主なファイルのオープンの方法("w+"などのオプションもある。man fopen を参照せよ。)

返り値は、FILE * 型。fgets や fprintf に使用可能。返り値が NULL ならば、オープンに失敗。

ファイルをオープンすることは、計算機のファイル管理のリソースを消費 するので、使わなくなったファイルは、クローズする必要がある。

fclose の引数には、fopen の返り値を代入する。

例題1

ファイルを書き込みオープンし、文字列 "Hello" をファイルに書き込もう。
/* list0601.c */
#include <stdio.h>
#include <stdlib.h>
int main(){
  FILE *fout;

  fout = fopen("test0601.dat","w");  // 書き込みオープン
  if( fout == NULL ) {
    fprintf(stderr,"File open error\n");
    exit(1);
  }
  fprintf(fout,"Hello");  // fout を通じて書き込む
  fclose(fout); // fout をクローズ
  return 0;
}

less コマンドで、test0601.dat の中身を見てみよう。

■ fread と fwrite

ファイルをオープンして、文字列の読み書きをするプログラムは、fgets や fprintf を使用して実現できる。しかし、NameCard のような構造体のファ イルの読み書きには、fgets や fprintf よりも fread や fwrite のほうが効率 的である。

書式は以下の通りである。

fread( データの格納先(ポインタ), データの1つぶんの大きさ, データの個数, 読み込み元 );

fwrite( データのありか(ポインタ), データの1つぶんの大きさ, データの個数, 書き込み先 );

例題2

使用例を以下に示す。
/* list0602.c */
#include <stdio.h>
#include <stdlib.h>

#define MAX 10
#define SIZ 100

typedef struct {
  int  id;     // 学生の ID 番号
  int  score;  // 点数
  char grade;  // 評価の文字: A B C 
} Score;

int main() {
  Score *score_list;
  int i;
  char tmp[SIZ];
  FILE *fout;

  // MAX 人分の Score を用意する。
  if( ( score_list = (Score *)malloc(sizeof(Score) * MAX) ) == NULL ){
    fprintf(stderr,"malloc failed\n");
    exit(1);
  }

  // MAX 人分の ID と点数を標準入力より入力する
  i = 0;
  while( fgets(tmp, SIZ, stdin) != NULL ){  // MAX 人に満たない人数のとき NULL
    if( i >= MAX ){
      fprintf(stderr,"overflow\n");
      exit(1);
    }
    // 2つの数字が正しく入力されなかったとき、2 以外が sscanf から返される
    if( sscanf(tmp,"%d %d", &(score_list[i].id), &(score_list[i].score)) != 2 ){
      fprintf(stderr,"syntax error\n");
      exit(1);
    }

    // 点数が、90 点以上ならば A, 80点以上ならば B, それ以下ならば C とする。
    if( score_list[i].score >= 90 )
      score_list[i].grade = 'A';
    else if( score_list[i].score >= 80 )
      score_list[i].grade = 'B';
    else
      score_list[i].grade = 'C';
    
    i += 1;
  }

  // ファイルの書き込み
  if( (fout = fopen("test0602.dat","w")) == NULL ){
    fprintf(stderr,"file oepn failed\n");
    exit(1);
  }
  // ヘッダの出力
  if( fprintf(fout,"%d\n", MAX) < 0 ){  // fprintf の返り値が負ならばエラー
    fprintf(stderr,"failed to save\n");
    exit(1);
  }
  // データの出力
  if( fwrite( score_list, sizeof(Score), MAX, fout ) < 0 ){ // fwrite の返り値が負ならばエラー
    fprintf(stderr,"failed to save\n");
    exit(1);
  }
  fclose(fout);

  return 0;
}

構造体の使用方法についても理解してもらいたいが、この例題では、「ファイルの書き込み」以降に重点を置いて理解してもらいたい。

  1. fout = fopen により、書き込みでオープンする。
  2. ヘッダの出力では、何個分のデータを出力するのかを表す。今回は、MAX という定数にしているが、実際に使用したぶんだけを出力してもよい。具体的には、変数 i がデータ数を表しているので、i としてもよい。
  3. fwrite によるデータの出力では、各引数の意味を理解してもらいたい。データの先頭のポインタ、1つぶんの大きさ、個数、出力ファイルである。個数は、ヘッダと同じ値とする。

課題

test0602.dat というファイルが、例題2のプログラムより生成される。そこで、test0602.dat を読み込み、その内容を表示するプログラム(prac0601.c)を作成せよ。
/* prac0601.c */
#include ???
#include ???

? 構造体 Score を定義する

int main() {
  ??? fin;        // 読み込み用ファイルデスクリプタ
  int size, i;
  ??? score_list, *tmp; // 格納先と一時的な変数

  fin = ?? ファイルの読み込みオープン
  ?? fin が NULL ならばエラー終了

  if( fscanf(fin, "%d\n", ???? ) <= 0 ){ // データ数を読み込む。size に代入
    ?? エラー終了
  }

  score_list = ?? 格納先のメモリ確保
  ?? エラーチェック

  ?? fread を使って読み込み
  ?? クローズ

  for( i = 0; i < size; i += 1 ){
    tmp = &score_list[i];
    printf("%d %d %c\n", tmp->id, tmp->score, tmp->grade);
  }

  return 0;
}

□ 宿題(小レポート)

課題を完成させよ。下記のとおり入力に対する出力が得られることを確認せよ。また、「0 0」が余分に表示されないようにするためには、どのようにプログラムを変更すればよいか?

小レポートには、(1)prac0601.c のソースコード、(2)手書きによるコメント、および、(3)「0 0」の表示されないようにする工夫の説明文を記載せよ。
(入力例)
1 90
2 80
3 70
4 60
5 78
6 98
(ここで、Control-Dを押す)

(出力例)
1 90 A
2 80 B
3 70 C
4 60 C
5 78 C
6 98 A
0 0 
0 0 
0 0 
0 0 


(c) Masato TOKUHISA (2006, May 23)