ファイルの概念
これまでデータの入力はキーボードから行ってきた。しかし、入力データが大量になると、その作業は大変なものとなる。そこで、Cではあらかじめファイルに入っているデータの入出力ができるように、fopen,fscanf,fprintf,fcloseなどの関数が用意されている。
関数名 |
機能 |
一般形 |
fopen |
x.c やy.datなどの外部名を受け取り、ファイルを読み書きするのに必要なポインタを返す。 |
FILE *fp; fp = fopen(name,mode); |
fscanf |
fopenで開いたファイルのデータを1文字列ずつ読み込む |
int s; fscanf(fp,”%d”,&s); |
fprintf |
fopenで開いたファイルのデータへ書き込み |
FILE *fp; int s; fprintf(fp, “%d”,s); |
fclose |
fopenで開いたファイルを閉じる。 |
FILE *fp; fclose(fp); |
feof |
データが空かどうかの判定を行う。空ならば真を返す。 |
FILE *fp; feof(fp); |
fgets |
1行取り込む fgets(fp,80,s)で配列sに1行取り込まれる。 fgetsを1回実行すると、fpidが次の行の先頭に移る。
|
char s[80]; FILE *fp; fgets(fp,80,s) |
fgetc |
1文字取り込む |
FILE *fp; fgtec(fp); |
fseek |
ファイル位置の移動 offsetには先頭からの文字数が入る。 originには0,1,2が入る。0で始め、1で現在地、2で最後 |
FILE *fp; fseek(fp,offset,origin); |
fflush |
ファイルを閉じずに書き込み |
Fflush(fp); |
ftell |
ファイルポジションインディケータ(fpid)の位置を調べる fgetsを1回実行すると、2行目の先頭にfpidが移動。 |
|
rewind |
ファイルの先頭に移る。 |
|
fopen(name,mode)のnameはファイル名を表し、modeにはr,w,a,r+,w+,a+があり、rはread(読み込み)、wはwrite(書き込み)、aはappend(ファイルの終わりに追加書き込み)を表す。また、r+はファイルの読み書き、w+は今までのファイルを破棄して読み書き、a+はファイルの追加・変更を表す。
fopen,fscanf,fprintf,fcloseの使い方
ファイルからの入力
これらの関数を使ってdata.datという名前のファイルの操作をしてみよう。
例題10.1 入力ファイルからデータを読み込む
次のようなデータを持ったファイルdata.datがC:\tempにあるとき、data.datからデータを読み込み、平均と和を求めるプログラムを作成しよう。
データ
150
164
153
174
189
185
168
156
152
174
173
169
実行結果
考え方
fopenを使ってdata.datファイルを開き、feofでデータが空かチェックし、fscanfを用いて1つずつデータを最後まで読み込む。data.datはC:\tempにあるのでダブルクォートの中では、”C:\\temp\\data.dat”と書く必要がある。いったんファイルを開いてしまえば、fscanfはscanfと同じ使い方になる。平均と合計を計算したら、最後にfcloseでファイルを閉じる。
解答
#include <stdio.h> #include <stdlib.h> void main(void) { int value=1; int sum=0; int count=0; FILE *fpin; if( (fpin=fopen("C:\\temp\\data.dat","r")) == NULL) { printf("ファイルが見つかりません。---data.dat\n"); exit(1); } while( !feof(fpin) ) // 最後まで { fscanf(fpin,"%d",&value); sum = sum + value; count++; } if (count > 0) { printf("合計=%d 平均値=%f\n",sum,(double)sum/count); } fclose(fpin); } |
読み込みのときは、読み込みであることが後からすぐに分かるように、fpinとしておくと便利である。
例題10.2 ファイル名を指定した入力
プログラムを工夫することによって、データファイルの名前をキーボードから直接入力するプログラムを作成せよ。
実行結果
解答 fopenで指定したファイル名の要素は、文字配列でも指定できる。例えば、
char FileName[16]:
printf(“ファイル名を入力してください”);
scanf(“%s”,FileName);
とすればよい。ではプログラムを書いてみよう。
#include <stdio.h> #include <stdlib.h> void main(void) { int value=1; int sum=0; int count=0; char FileName[16]; FILE *fpin; printf("ファイル名を入力してください"); scanf("%s",FileName); if( (fpin=fopen("data.dat","r")) == NULL) { printf("ファイルが見つかりません。---data.dat\n"); exit(1); } while( !feof(fpin) ) { fscanf(fpin,"%d",&value); sum = sum + value; count++; } if (count > 0) { printf("合計=%d 平均値=%f\n",sum,(double)sum/count); } fclose(fpin); } |
ここでは、1つのデータファイルdata.datからデータを読み込むことを行ったが、複数個のデータファイルを読み込むには
FILE *fpin1, *fpin2, *fpin3;
と宣言し、それぞれのファイルをfopenで開けばよい。
ファイルへの書き込み
ファイルへデータを出力するには、
1.まずout.datという名前のファイルを作成する。書きこみのときはfpoutとしておく。
FILE *fpout;
fpout = fopen(“out.dat”,”w”);
このとき、fopenの第2引数modeは書き込み可能とするため”w”になる。ただし、”w”を指定してfopenを行った場合、out.datが存在するとout.datのデータ内容は失われるので注意すること。つまり、”w”は上書きを意味する。
2.fprintf関数を用いてファイルにデータを出力する。
fprintf(fpout,”%d\n”,value);
3.最後にファイルを閉じる。
fclose(fpout);
例題10.3 次のデータをout.datというファイルを作成し、書き込むプログラムを作成しよう。
データ
150
164
153
174
189
185
168
156
152
174
173
169
考え方
1.まずout.datというファイルを作成。
2.キーボードからのデータをscanfで読み込み、fprintfでout.datに書き込む
解答
#include <stdio.h> #include <stdlib.h> void main(void) { int value=1; FILE *fpout; if( (fpout = fopen("out.dat","w")) == NULL) { fclose(fpout); printf("ファイルが作成できません。---out.dat\n"); exit(1); }
while( value != 0 ) { printf("データを入力してください。終了するには0を入力\n"); scanf("%d",&value); fprintf(fpout,"%d\n",value); } fclose(fpout); } |
ファイルへの追加書き込み
ここでは、すでに作成してあるファイルに新しいデータを追加する方法を考える。fopenのmodeをaにすると追加書き込みができるようになる。このとき新しく追加したデータは元のファイルの下に追加されます。
例題10.4 例題10.3で作成したout.datに次のデータを追加してみよう。
データ
160
170
180
175
解答
#include <stdio.h> #include <stdlib.h> void main(void) { int value=1; FILE *fpout; if( (fpout = fopen("out.dat","a")) == NULL) { fclose(fpout); printf("ファイルが作成できません。---out.dat\n"); exit(1); }
while(value != 0) { printf("データを入力してください。終了するには0を入力\n"); scanf("%d",&value); fprintf(fpout,"%d\n",value); } fclose(fpout); } |
ファイルの文字変更(fseekの使いかた)
fseek(fp,offset,origin)でoriginに対してファイルのoffsetの位置に移動。originは0,1,2が入る、0は先頭、1は現在地、3はファイルの最後。offsetには0L,1L,2L,…が入る。0Lで1文字目、2Lで2文字目に移動する。
fseek(fp,0L,0)で開始行の1文字目に移動する。
次のような文字列があるデータファイルをtest.datと名前を付けて保存しておく。
D is defficalt.
1.文字DをCに変える。
#include <stdio.h> #include <stdlib.h> void main(void) { char c; char s[80]; FILE *fpout; if( (fpout = fopen("test.dat","r+")) == NULL) { fclose(fpout); printf("ファイルが作成できません。---test.dat\n"); exit(1); } fgets(s,80,fpout); fseek(fpout,0L,0); c = 'C'; fprintf(fpout,"%c",c); fclose(fpout); } |
で文字DはCに置き換えられる。
2.C is defficaltのeをiに変える。
#include <stdio.h> #include <stdlib.h> void main(void) { int i; int j; char c; char s[80]; FILE *fpout; if( (fpout = fopen("test.dat","r+")) == NULL) { fclose(fpout); printf("ファイルが作成できません。---test.dat\n"); exit(1); } fgets(s,80,fpout); for(i=0;i<80;i++) { if(s[i] == 'e') { j = (long)i; fseek(fpout,j,0); c = 'i'; fprintf(fpout,"%c",c); } } fclose(fpout); } |
3.C is difficaltのスペルミスを見つけて修正する。
fseekとfprintfはペアで用いる。つまり、fseekとfprintfの間には他の操作を行ってはいけない。他の操作を行うとfseekのfpが変わってしまう。
#include <stdio.h> #include <stdlib.h> void main(void) { int i; int j; char ch,ad; char s[80]; FILE *fpout; if( (fpout = fopen("test.dat","r+")) == NULL) { fclose(fpout); printf("ファイルが作成できません。---test.dat\n"); exit(1); } fgets(s,80,fpout); printf("%s",&s[0]); printf("修正したい文字と修正用の文字を入力\n"); scanf("%c %c",&ch,&ad); for(i=0;i<80;i++) { if(s[i] == ch) { j = (long)i; fseek(fpout,j,0); fprintf(fpout,"%c",ad); } } fclose(fpout); } |
ファイルの追加変更(文字列)
文字列を読み込むにはfgets(s,80,fpout);を用いる。
次の文字列を含んだデータファイルtest.datを用意する。このファイルをプロンプトから開き、文字列を表示し修正したい文字列を入力することにより、ファイルが書き直されるプログラムを作成しよう。
They is stupid.
文字列の長さはstrlen(s)で計る。
#include <stdio.h> #include <stdlib.h> #include <string.h> void main(void) { int i; int j; int len,len1,len2; char ch[5],ad[5]; char s[80]; FILE *fpout; if( (fpout = fopen("test.dat","r+")) == NULL) { fclose(fpout); printf("ファイルが作成できません。---test.dat\n"); exit(1); } fgets(s,80,fpout); printf("%s",&s[0]); len=strlen(s); printf("修正したい文字列と修正用の文字列を入力\n"); scanf("%s %s",ch,ad); len1=strlen(ch); len2=strlen(ad); for(i=0;i<80;i++) { if(s[i] == ch[0]) { j = (long)i; fseek(fpout,j,0); fgets(s,40,fpout); fseek(fpout,j+len2-len1,0); fprintf(fpout,"%s",s); fseek(fpout,j,0); fprintf(fpout,"%s",ad); break; } } fclose(fpout); } |
リダイレクション
Cにはリダイレクションと呼ばれ、入出力の切り替えを行う機能がある。つまり、キーボードやディスプレイもファイルと考えると、ファイルから読み込むことも、ファイルへの出力もキーボードからの入力やディスプレイへの出力と同じと考えることができる。では、実際にリダイレクションを行ってみる。
1. 次のプログラムをread_data.cというファイル名でコンパイルしよう。
#include <stdio.h> void main(void) { char s[80];
while(gets(s) !=NULL)) { puts(s); } } |
2. 例題10.1で用いた入力ファイルdata.datをリダイレクションを用いて読み込んでみよう。
read_data < data.dat
とプロンプトで実行する。これは、data.datとread_data.exeは同じフォルダにあることを前提にしている。もしdata.datが別の場所にあるときは、パス(path)が必要になる。パスとは目的地までの道順のことである。
例えば、read.datがCドライブのフォルダMy Documents の下のフォルダCEXEにあり、read_dataがCドライブのフォルダDATにあるとする。この場合は次のように入力すればよい。
cd c:\mydocu~1\CEXE
read_data < ..\data.dat
これは相対パスと呼ばれるものである。この他絶対パスを用いると次のようになる。
read_data < C:\DAT\data.dat
3. 例題10.3で行ったファイルへの書き込みをリダイレクションを用いて行ってみよう。書き込みするファイル名はout.txtとする。
read_data > out.txt
この後、type out.txtとプロンプトで入力するとout.txtの内容が見れる。UNIXの場合はcat out.txtと入力。
4. 例題10.4で行ったファイルへの追加書き込みをリダイレクションを用いて行ってみよう。追加書き込みするファイル名はout.txtとする。
read_data >> out.txt
この後、type out.txtとプロンプトで入力するとout.txtの内容が見れる。UNIXの場合はcat out.txtと入力。
5. 読み込みと書き込みをリダイレクションで行ってみよう。
read_data < data.dat > data.out
この操作を行うと、まずdata.datの内容がよき込まれ、次にdata.outに書き込まれる。
練習問題10.1
次のプログラムの説明およびプログラムを読んで、設問に答えよ。
「プログラムの説明」
ソフトウェア開発のテスト工程におけるバグ収束状況を評価するための信頼度成長曲線を印刷するプログラムである。信頼度成長曲線は、テスト項目の消化状況とバグの発生状況との関係をグラフにしたものである。
(1) テスト担当者が日々記録しているテスト項目の消化項目数(その日に終了したテストの項目数)と摘出バグ数(終了した項目において摘出したバグ数)の一覧が、下のバグ記録表である。
バグ記録表
日付 |
担当 |
消化項目数 |
摘出バグ数 |
19960802 |
A |
2 |
40 |
19960803 |
B |
1 |
20 |
19960804 |
A |
3 |
20 |
19960805 |
B |
4 |
10 |
|
|
|
|
19960823 |
A |
10 |
5 |
19960823 |
C |
5 |
0 |
19960825 |
A |
8 |
1 |
19960827 |
C |
12 |
1 |
(2) バグ記録表は、下の図のレコード様式でbugrec.txtというファイルに格納されている。
8けた |
1けた |
1けた |
1けた |
2けた |
1けた |
2けた |
1けた |
日付 |
△ |
担当 |
△ |
消化項目数 |
△ |
摘出バグ数 |
\n |
△ :空白文字
1. 日付は、年月日を8桁の10進数字(YYYYMMDD)で表現しており、先頭の4桁が西暦年を、次の2桁が月を、最後の2桁が日を表している。
2. 担当は、テスト担当を1桁の英字にコード化したものである。
3. 消化項目数および摘出バグ数は、2桁の10進数字である。
4. 各項目は、1個の空白文字で区切られており、レコードの終端は改行文字”\n”である。
5. レコードはテスト担当者ごとに1日のテスト結果を記録したものであり、同一日付でテスト担当者が異なるレコードが2件以上存在することもある。
6. レコードは日付の昇順に整列されている。
7.
ファイルbugrec.txtに記録されているテスト作業日の数は60日以内である。
(3) このプログラムでは、ファイルbugrec.txtを入力し、1日あたりの消化項目数および摘出バぐ数を集計する。さらに、その日までの累積数を計算して、累積消化項目数と累積摘出バグ数の関係をもとめ、折れ線グラフ出力関数prtgraphを用いて信頼度成長曲線を印刷する。
(4) 関数prtgraphの仕様は、次のとおりである。
「機能」 第1引数で指定された個数の点(座標は第2引数の配列内に格納)を折れ線でつないで印刷する。
「書式」
typedef struct{int x;int y;} ZAHYO;
void prtgraph(int dcnt,ZAHYO +tbl, char *title);
「引数」 構造体ZAHYOのメンバx:印刷する点のX座標
構造体ZAHYOのメンバy:印刷する点のY座標
dcnt: 印刷する点の個数
tbl: 点の座標を格納した配列
title: グラフの表題
<プログラム>
#include <stdio.h>
#define STBMAX 61
typedef struct{int x;int y;} ZAHYO;
void prtgraph(int,ZAHYO,char*);
void main(void)
{
ZAHYO s_tbl[STBMAX];
FILE *fp;
long yymmdd, t_yymmdd;
int t_bug, t_chk,I;
int idx=0;
char tanto;
fp = fopen(“bugrec.txt”,”r”);
yymmdd=0;
s_tbl[0].x = s_tbl{0},y = 0;
while(fscanf(fp,”%81d %c %2d %2d\n”,
&t_yymmdd, &tanto, &t_chk, &t_bug) != EOF)
{
if (yymmdd != t_yymmdd)
{
idx++;
yymmdd = t_yymmdd;
s_tbl[idx].x = s_tbl[idx].y = 0;
}
s_