10節 ファイル操作


概要
 いままで、処理データや結果データを単に変数に格納していましたが、それだけでは、処理を終了したり、パソコンの電源を切ったり、するとデータ内容は失われてしまいます(*1)。処理データ数が少ない場合は処理毎に入力していけばいいかもしれません。しかし、処理データが100件、1000件となると毎回入力するだけでもかなりの苦労です。そうするより、処理を終了しても、電源をきってもデータ内容が失われない(*2)”ファイル”というものに保存しておけば、見たいとき即座に処理データを見ることができます。
 この節では、ファイルのデータを読み書きする方法を説明します。
 ここで、いきなり”ファイル”という言葉がでてきましたが、ファイルとは、みなさんが講義で黒板等の内容を写しているノートのようなものだと考えてください。

*1:処理を終了したり、パソコンの電源をきったりすることでデータの内容が失われることを揮発といい、そのデータを格納しているメモリのことを揮発性メモリと言います。主記憶などがそれにあたります。
*2:処理を終了したり、パソコンの電源をきったりしてもデータの内容が保持されることを不揮発といい、そのデータを格納しているメモリのことを不揮発性メモリと言います。ハードディスクやフロッピーディスク等がそれにあたります。よって、ファイルは、ハードディスクやフロッピーディスク等に作成・保存されます。


ファイルの種類
 ファイルは大きくわけて、テキストファイルとバイナリファイルの2種類があります。
 テキストファイルは、テキストエディタ(Windowsのメモ帳など)で容易に中を見ることが出来ますが、バイナリファイルを見るには専用ソフトが必要となります。

 テキストファイルには、
ファイルの種類
拡張子
説明
C言語ソースファイル .c C言語のプログラムを記述したファイル
C言語ヘッダーファイル .h C言語のヘッダーを記述したファイル
C++ソースファイル .cpp C++のプログラムを記述したファイル
HTML文章 .html インターネットで使われているマークアップ文章
データファイル .dat 様々なデータを保存するファイル
などがあります。

また、バイナリファイルには、
ファイルの種類
拡張子
説明
JPEG画像ファイル .jpg JPEG圧縮された画像ファイル
GIF画像ファイル .gif GIF圧縮された画像ファイル
WAV音声ファイル .wav WINDOWS標準音声ファイル
MPEGファイル .mpg MPEG圧縮された動画ファイル
などがあります。
この他にもたくさんのファイルがあります。自分のパソコンの中にはどういうファイルがあるのか、見てみても面白いと思います。

今回は、主にテキストファイルのデータファイルに関して、作成・書きこみ・読みこみなどの方法を説明していきます。

注)拡張子:"*.xxx"の".xxx"の部分で、ファイルの種類を決定する役割をします。


ファイルの4つの基本手順
 ファイルの作成や読み書きをする、次の4つの基本手順があります。

  1:FILE宣言子を使って、ファイルポインターを宣言する。
  2:ファイルを開く。
  3:ファイルにデータを書いたり、ファイルからデータを読んだりする。
  4:ファイルを閉じる。

という作業です。
 みなさんが勉強するときに、ノートを用意し、そのノートを開け、読み書きをした後、ノートを閉じて勉強を終了する作業と同じようなものです。

ファイルに書きこむ
 では、ファイルの4つの基本手順を含めて、ファイルに数値や文字を書きこんでみます。以下のプログラムをみてください
 
10-1 ファイルに書きこむプログラム例
プログラム説明

#include <stdio.h>



void main(void)

{

        char myName[] = {"YamadaKouji"};

        int myNumber = 96131028;

        FILE *fp;    //ファイルポインター



        fp = fopen("test.dat","w"); //ファイルを開く



        //ファイルに書きこむ

        fprintf(fp,"%s %d\n",myName,myNumber); //区切り文字:スペース

        fprintf(fp,"%d,%s\n"myNumber,myName,); //区切り文字:カンマ



        //画面に出力

        printf("%s %d\n",myName,myNumber);

        printf("%d,%s\n",myNumber,myName);



        fclose(fp); //ファイルを閉じる

}
 まず、FILE宣言子を使ってファイルポインターを宣言しています。その後、fopen関数を使ってファイル名"test.dat"というデータファイルを新規作成し開いています。fopen関数の引数は、1番目がファイル名(正しくは、ファイル名の入った文字配列の先頭アドレス)、2番目がファイルのオープンモードです。オープンモードは数種類あり、今回は書きこみなので、書きこみ(WRITE)を示す"w"としています。他のオープンモードについては、この後紹介していきます。fopenでファイルポインターの取得ができれば、ファイル操作をする際、ファイル名を使わずファイルポインターを使います。
 ファイルに書きこむ時は、fprintf関数を使います。fprintf関数の引数はprintf関数と似ていますが、違うところは、引数の1番目に書きこみたいファイルへのファイルポインターを指定することです。その他の書式等は、printf関数と同じです。全てのファイル操作が終了したら、fclose関数でファイルを閉じます。

注1)プログラム10-1で作成したファイルは、実行ファイルと同じ場所(フォルダ)に作成されます。
注2)作成したのはテキストファイルなので、テキストエディタでファイルの中身を確認することができます。

関数説明:fopen,fprintf

実行結果
画面出力
YamadaKouji 96131028
96131028,Yamada Kouji

ファイルに書きこまれた内容
YamadaKouji 96131028
96131028,YamadaKouji

 実行結果を見てもわかるように、今までprintfで出力していたのと同じようにファイルに書きこまれます。ファイルに書きこんだ数値や文字の順序や区切り文字(スペースかカンマ)は、書きこみのときにはあまり関係ありませんが、読みこみ時に大変重要になるのでここでは、どのようにファイルに書きこんだのかをしっかり覚えておいてください。

 それでは、今つくったファイルからデータを読んでみます。

ファイルからデータを読みこむ
 さて次は、ファイルからデータを読む方法を紹介します。読みこみは、書きこみよりも注意する点が多いので注意してください。読みこむファイルは、先ほど作成した"test.dat"を使います。
 以下のプログラムをみてください。
 
10-2 ファイルからデータを読みこむプログラム例
プログラム説明

#include <stdio.h>

#include <string.h>



void main(void)

{

        char myName[20];

        int myNumber;

        FILE *fp;     //ファイルポインター



        fp = fopen("test.dat","r"); //ファイルを開く



        fscanf(fp,"%s%d",myName,&myNumber); //区切り文字スペース

        printf("%s %d\n",myName,myNumber); 



        //変数の内容をクリアー

        myNumber = 0;

        strcpy(myName,"");



        fscanf(fp,"%d,%s",&myNumber,myName); //区切り文字:カンマ

        printf("%d,%s\n",myNumber,myName);

        fclose(fp); //ファイルを閉じる

}
 読みこみの時は、fopen関数の2番目の引数に、読みこみ(READ)を示す"r"を指定します。この時ファイルは既に存在している必要があります。もし、存在しないファイルを"r"で開こうとすると、fopen関数はオープン失敗を示すNULLを返します。成功すれば書き込み同様、指定ファイルのファイルポインターを取得でき、そのファイルポインターを使ってファイル操作することになります。
 読みこむ時は、fscanf関数を使います。これもscanf関数と似ていますが、一番目の引数に読みこむ対象となるファイルへのポインターを渡します。あとの書式等は、scanf関数と同じです。
 読みこみで大切なのは、データの型の並びです。ファイル"test.dat"の1行目は、文字列・数値、区切り文字はスペースですので、fscanf(fp,"%s%d",myName,&myNumber);となります。また、2行目は、数値・文字列、区切り文字はカンマですので、fscanf(fp,"%d,%s",&myNumber,myName);となります。ファイルに書かれているデータの型の並びと、読み取ろうとするデータの型が一致しないと正しくデータを読み取ることができないので、注意してください。
 処理が終了すれば、ファイルを閉じることを忘れないようにしましょう。

関数説明:fscanf

実行結果
YamadaKouji 96131028
96131028,YamadaKouji

ファイルに追加書きこみする
 オープンモード"w"でファイルを開いた場合、既にファイルが存在しているときはファイルの中身を空にし、ファイルが存在しないときはファイルが作成されます。
 よって、既に存在しているファイルをオープンモード"w"で開け処理しても追加書きこみしたことにはなりません。既存のファイルに追加書きこみしたい場合は、追加(append)を示す"a"を使用します。このモードでファイルを開き書きこみをすると、ファイルの一番最後から追加書きこみされていきます。もし、ファイルが存在しない場合は新しくファイルが作成されます。
 
10-3 ファイルに追加書きこみするプログラム例
プログラム説明

#include <stdio.h>



void main(void)

{

        char appName[]="ShinkichiTamaki";

        FILE *fp;    //ファイルポインター



        fp = fopen("test.dat","a"); //ファイルを開く



        fprintf(fp,"%s\n",appName);



        fclose(fp); //ファイルを閉じる

}
 プログラム10-1で作成したファイル"test.dat"をオープンモード"a"で開き、文字列をファイルに追加書きこみしています。実行結果の太字の部分が追加書きこみされた部分です。
 オープンモード"a"は追加書きこみだけではなく、データを読みこむことも可能ですが、ファイル位置はエンド・オブ・ファイル(EOF)に置かれている為、ファイル位置の変更なしにファイルの内容をリードすることはできません。ファイル位置に関しては、後で説明します。
実行結果
test.datファイルの内容

YamadaKouji 96131028
96131028,YamadaKouji
ShinkichiTamaki

 ファイルの読み書きをする関数は、ここで紹介した関数以外にも数種類あります。全ての使い方を説明することはしませんが、最後にリファレンスとして一部を紹介してありますので、自分で勉強してみてください。


複数のファイルを開く
 ファイルは同時に複数開くことが出来ます。処理によっては、2つ3つと開く必要が出てくることもあるでしょう。ここでは、複数のファイルを開く方法を紹介します。
 
10-4 複数のファイルを開くプログラム例
プログラム説明

#include <stdio.h>



void main(void)

{

        FILE *ip,*op;

        char ic;



        ip = fopen("test.dat","r");  //コピー元ファイル

        op = fopen("copy.dat","w");  //コピー先ファイル

        

        printf("以下の内容をコピーします。\n\n");

        while((ic = fgetc(ip)) != EOF) //EOFまで読みこむ

        {

                putchar(ic); //画面に読みこんだ文字を表示する



                fputc(ic,op); //ファイルに1文字書きこむ

        }



        fclose(ip);

        fclose(op);



        printf("\nコピーが終了しました。\n");



}
 このプログラムでは、10-3で追加書きこみしたファイルの内容を新規ファイル"copy.dat"にコピーするプログラムです。
 ファイルポインターを開けるファイルの数だけ用意し、fopen関数で開けます。
 読み取りは、fgetc関数を使っています。fgetc関数はファイルより1文字読みこむ関数で、引数は読みたいファイルのファイルポインターです。また、書きこみは、ファイルに1文字書きこむfputc関数を使い、引数は書きこみたい文字と書きこむファイルのファイルポインターです。
 プログラム中のEOFとは、エンド・オブ・ファイルのことで、ファイルの終りを表します。よって書きこみは、"test.dat"ファイルの最初からEOFに到達するまで1文字ずつ読み取り、画面と"copy.dat"ファイルに1文字ずつ入力するという仕組みになっています。
 最後に、全てのファイルを閉じることを忘れないようにしましょう。
実行結果
以下の内容をコピーします。

YamadaKouji 96131028
96131028,YamadaKouji
ShinkichiTamaki
 

コピーが終了しました。

copy.datファイルの内容
YamadaKouji 96131028
96131028,YamadaKouji
ShinkichiTamaki


ファイル位置って何?
 ここまでで、ファイルのデータの読み書きがある程度できるようになったと思います。 初級では、この程度ができれば十分だと思いますが、ここでもう1つ、ファイル位置というものを勉強しておきましょう。
 ファイル位置とは、現在ファイルのどの位置からデータを読もうとしているのか、またデータを書きこもうとしているのかを示すものです。
 今回の講座の内容だと、ファイル位置を意識する必要はあまりありませんでしたが、これから複雑なファイル操作を学ぶに従って、意識していかなければならないものだと思います。ですから、今回は「こういうものがあるのかぁ」くらいの感じで頭の片隅にでも入れておいて下さい。
 
ファイルをオープンモード"w"で開いたときのファイル位置

 ファイルのオープンモードを書きこみの"w"でオープンした場合、すでにファイルが存在していれば内容を全て削除し、ファイルが存在していなければ新規作成されます。よってどちらにしても、ファイルには何も書かれていない状態で、ファイル位置はファイルの先頭でありEOF(エンド・オブ・ファイル:ファイルの終端)を指しています。
 

EOF

この状態からfprintf関数を使って"YamadaKouji"と書きこむと、ファイル位置(EOF)から文字列がファイルに書きこまれます。
 

'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' EOF

次に、区切り文字としてスペースをいれ、数値の96131028と改行文字'\n'を入れてみます。
 

'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' EOF

と、いうようにファイル位置(EOF)からデータが書きこまれていきます。

注)背景色の違っているところが現在のファイル位置、表中の文字がファイルの内容を示す。また、EOFはファイルの終端を表す。
 

ファイルをオープンモード"r"で開いたときのファイル位置

 オープンモード"r"でファイルを開く時は、既にファイルが存在していることが条件になります。存在しないファイルをこのモードで開こうとすると、エラーを示すNULLが帰ってきます。
 このモードでファイルを開いた時のファイル位置は、ファイルの先頭です。10-1で作成したファイル"test.dat"を開いたと過程して説明していきます。
 

'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF

テキストエディタなどで中身をみると、改行文字('\n')で改行されていますが、実際のファイル中のデータは改行されずに一連の状態になっています。
 このファイルよりfscanf関数で最初の文字列を読みこむと、1文字ずつファイル位置が移動し、文字配列によみとったデータが格納されていきます。そして、区切り文字(スペース等)が出てきた時点で移動が止まり、読み取り終了となります。
 この時、ファイル位置があるデータは文字なので、文字以外(数値など)で読みこむと正しくデータを取得できません。
 

'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF
'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF
'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF
'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF
'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF


'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF

 文字列が読み終わると、次の型は数値なので数値型でデータとして読み込みます。また、区切りは改行文字('\n')になります。
 また、先頭のホワイト・スペース(空白、タブ、改行文字)は無視されます。
 

'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF
'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF

 同様に、データを読んでいくとEOFに到達します。
 

'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF

 EOF以降は何も書きこまれてないため、これ以上の読みこみは出来ません。また、再びデータを読み込みたいときは、ファイル位置を先頭に移動させる必要があります。ファイル位置を先頭に戻す関数はありますが、一番簡単な方法は、一度ファイルを閉じ再度オープンモード"r"で開く方法です。

注)背景色の違っているところがファイル位置、表中の文字がファイルの内容を示す。また、EOFはファイルの終端を表す。

ファイルをオープンモード"a"で開いたときのファイル位置

 オープンモード"a"は、ファイルが存在していれば追加書きこみを、存在していなければ新規作成を行います。"a"で開けられたファイルは読み書きの両方を行うことができます。しかし、ファイル位置がEOFに置かれるため、ファイル位置の変更なしにデータの読みこみを行うことは不可能です。追加書きこみは、オープンモード"w"と同じ方法で、通常通り行えます。
 プログラム10-1で作成したファイル"test.dat"を"a"で開いたとして図にしました。
 

'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i'
 
96131028 '\n' 96131028 , 'Y' 'a'  'm' 'a' 'd' 'a' 'K' 'o' 'u' 'j' 'i' '\n' EOF

注)背景色の違っているところがファイル位置、表中の文字がファイルの内容を示す。また、EOFはファイルの終端を表す。

 簡単にファイル位置について説明しました。しかし、これだけの説明ではまだまだ不充分ですので、これより先は自分で勉強し知識を深めていってください。


ファイル構造のお話
 ある試験の結果をファイルに書きこむ時、次のような順序でデータを書きこんだとします。

 1,YamadaKouji,85,72,95
 2,FujiwaraNaoto,95,85,96
 3,OzakiMotoharu,82,95,86

 ファイルの1行{ 出席番号 , 名前 , 国語の結果 , 数学の結果 , 英語の結果 }のデータのまとまりをレコードと呼びます。そして、レコードを構成している各構成要素をフィールドと呼びます。
 今、ある計算処理をする過程で3番目のレコードが必要になったとします。しかし、今までのファイルの構造だと、3番目だけを読みこむことは出来ず、1番目より順に読みこんでいかなければいけません。このように、順番にデータを読みこんでいくファイルをシーケンシャルファイルといいます。その逆に、順番を問わず自由にレコードにアクセス出きるファイルをランダムファイルといいます。
 今回の講座で作成したのはシーケンシャルファイルです。ランダムファイルに関しては、自分で勉強してください。


ファイル関するライブラリ関数一覧
 ここまでで、数個のファイルに関する関数を使ってきました。しかし、この他にもファイルに関する標準ライブラリ関数が用意されていますので、ここで良く使われる関数だけを一覧としてまとめたいと思います。全て掲載することはしませんので、興味がある方は、C言語リファレンスガイドブックなどを参考にしてください。
 
必要なヘッダーファイル

戻り値 関数名(引数1[,引数2])
戻り値
引数
関数説明
入力(読みこみ)関数
#include <stdio.h>

int getc(fp);
正常:入力された文字,

エラー:EOF(-1)
FILE *fp:ファイルpointer
 ファイルfpから1文字入力します。
 getcはマクロ,fgetcは関数であることを除けば両者は同じです。
#include <stdio.h>

int fgetc(fp);
#include <stdio.h>

int getw(fp);
入力データ,エラー:EOF(-1)
FILE *fp:ファイルpointer
ファイルfpからint型のデータを入力します。
#include <stdio.h>

char *fgets(str,n,fp);
正常:strへのpointer,

エラー:NULL
char *str:文字列を格納するバッファ

int n:入力最大文字数

FILE *fp:ファイルpointer
 ファイルfpから文字列を読み取り、バッファstrに格納します。読み取りは行末文字('\n')に出会うかn-1個の故事を読みこむまで行われます。文字列の最後には'\0'が付加されます。
 テキストモードの場合、'\n'も入力されます。
#include <stdio.h>

int fscanf(fp,format[]);
リードした項目数

EOFまたはエラー:-1
FILE *fp:ファイルpointer

char *format:書式制御文字列
 ファイルfpから書式formatに従ってデータを入力します。書式制御の扱いはscanf関数と同じです。
出力(書きこみ)関数
#include <stdio.h>

int putc(c,fp);
正常:書き出した文字

ファイル・エンド、エラー:EOF(-1)
int c:書き出す文字

FILE *fp:ファイルpointer
 文字cをファイルfpに書き出します。テキストモードでオープンされていれば、'\n'はCR・LFに変換されます。
 putcはマクロ,fputcは関数であることを除けば両者は同じです。
#include <stdio.h>

int fputc(c,fp);
#include <stdio.h>

int fprintf(fp,format[]);
出力した文字数
FILE *fp:ファイルpointer

char *format:書式制御文字列
 ファイルfpに書式formatに従ってデータを出力します。書式制御はprintf関数と同じです。
#include <stdio.h>

int fputs(str,fp);
正常:0

エラー:非0
char *str:文字列が格納されているバッファ

FILE *fp:ファイルpointer
バッファstrの文字列をファイルfpに書き出します。'\0'は書き出されません。テキストモードであれば、'\n'はCR・LFの2文字に変換されます。


あとがき
 プログラムは、アイデアや工夫が一番だと思っています。例えば、プログラムによっては1元配列ばかりでなく、2元配列を使ったり、構造体を使ったりを使ったりした方がすっきりとしたプログラムになる場合がたくさんあります。
 ただ動けば良いというプログラム作成から早く抜け出し、動くことは当たり前で、人に分かりやすく読みやすいプログラムを書こうという努力をしていってください。(とはいえ、僕自身まだまだ未熟で勉強中なので、偉そうな事は言えませんが・・・)

 今回取り扱ったのは、ファイルに関するほんとうに極わずかな部分にすぎず、もっと説明したいことがいっぱいあります。しかし、時間的な理由から今回はこれだけになってしまいました。もちろん、良いページにしていきたいという気持ちはあります。ですから、「ここがわかりにくい!」とか「こういうことをしたいんだけど、どうすればいいのか?」などの疑問・質問・苦情をメールしてください。

 最後に、説明不足や分かりにくい表現等があるかとは思いますが、この節を最後まで読んでいただきありがとうございました。(経済性工学研究室:山田 宏治)