第6回:ファイル (5/16)

第5回演習解答例

今週は,数値や文字データをプログラムに読み込んだり出力したりする際に必要となる「ファイルの入出力」を行う。

キーワード:「ファイル」「テキストファイル」「バイナリファイル」「ファイルポインタ」「EOF」


ファイルとは

計算結果や画像データ,文章など,さまざまな情報は外部記憶装置(ハードディスクやフロッピーディスク,USBメモリ,CD,DVD等のこと)に「ファイル」として記録しておけば,後で読み出したり,別のコンピュータに持っていくことができる。たとえば,これまで作ってきたプログラムファイルは「C言語ソースファイル」であり,コンパイルされた.exe形式のファイルは「実行ファイル」と呼ばれる。 その他,ディジタルカメラで撮影された「画像ファイル」,「動画ファイル」,今閲覧しているこの演習用ホームページも「ファイル」としてWWWサーバー内に保存されている。コンピュータのディスク上においては,ファイルはメモリと同様,「0」と「1」で表されているが,ファイルには,その性質により大きく分けて二種類あり,それぞれ

テキストファイル

C言語のソースファイルや,ホームページのファイルなど,人間が中身を「文字」として読むことのできるファイル

バイナリファイル

プログラムの実行ファイルや,画像ファイル音楽のファイルなど,人間が「文字」として読めないファイル

と呼ばれている。

テキストファイル バイナリファイル
文字として読むことのできるファイル 文字として読むことができないファイル

Cプログラムソースファイル

HTMLファイル

Cプログラム実行ファイル

画像ファイル,動画ファイル

ワード,エクセルの文書ファイル

 

プログラム内の変数や配列など,何らかの情報を「ファイル」として保存しておく事を「ファイル出力」または「ファイルに書き出す」と言い,逆にファイルに蓄えられている情報をプログラム内の変数や配列に読み込むことを「ファイル入力」または「ファイルから読み込む」と言う。ファイル入力とファイル出力をあわせて,ファイル入出力と呼び,これら一連の処理をファイル操作と呼ぶ。


ファイル操作の手順

プログラム中でファイルを扱うときの基本的な流れは

  1. ファイルを開く(オープンと言う)
  2. ファイルから情報を変数に読み込む,またはファイルに変数の内容を書き出す
  3. ファイルを閉じる(クローズと言う)

である。

ファイル入出力

 

ファイル操作プログラム例


#include <stdio.h>

void main()
{
    char filename[100];    /*  ファイル名のための文字列  */
    FILE *fp;  /*  0:ファイルポインタを宣言  */

    printf("作成するファイル名を入力してください : ");  /*  これは画面への出力  */
    scanf("%s", filename);                       /*  これはキーボードからの入力  */

    fp = fopen(filename, "w");       /*  1:ファイルを開く(オープン)  */

    fprintf(fp, "test ¥n");          /*  2:ファイルに文字列を書き出す  */
    fprintf(fp, "%d ¥n", 10);        /*  2:ファイルに整数を書き出す  */
    fprintf(fp, "%f ¥n", 3.1415);    /*  2:ファイルに実数を書き出す  */

    fclose(fp);                      /*  3:ファイルを閉じる(クローズ)  */
}

ファイル操作を順に見ていこう。

0:ファイルポインタの宣言

ファイルを扱う場合には,必ずファイルポインタを宣言する。これは,ファイルはディスク上に複数あるため,どのファイルを扱うのか,そのファイルのどの位置を読み書きしているのかを識別・記憶するためである。

書式:FILE *ファイルポインタ変数名;

たとえば,fp と言う名前のファイルポインタを使いたい場合は,

FILE *fp;

と書く。(FILEは構造体であり,ここではfpFILE 構造体変数を指すポインタという意味になる)

1:ファイルを開く

あるファイルに読み・書きするためには,まず fopen 関数を使って,ファイルをオープンする必要がある。

書式:ファイルポインタ変数名 = fopen ( "ファイル名","モード");

第1引数はファイル名(文字列型),第2引数は「読み取り」と「書き出し」のモード指定(後述)である。fopen 関数が成功しファイルが開かれると,開いたファイルの先頭位置(ファイルポインタ)が戻り値として fp に代入される。何らかの理由でファイルのオープンに失敗すると (ファイルが存在しない,別のプログラムがファイルを開いている,ファイル名間違い等),fopen 関数は NULL を返す。
このため,実際のプログラムにおいては,通常は以下のように if 文を用いたエラーチェックを行う。


char filename[] = "data.txt";      /*  ファイル名  */
fp = fopen(filename, "r");  /*  読み取りモードの場合  */
if (fp == NULL) {
    exit(1);                /* exit関数は,ここでプログラムを終了させる関数.要 #include<stdlib.h> */
}   
/* ここ以降にファイルの読み書きを行う */

ファイルが開けなかった場合は,その続きの読み書き処理ができないので,上の例のようにプログラムの実行を終了させるための exit 関数を使うか,または return 文で関数を終わらせる。

モードについて

fopen 関数の第2引数のモードについては以下のとおり。文字列型なので,必ず" "で括る必要がある。

モード 説明
"r" 読み込み専用で開く (read)
"w" 書き込み専用で開く (write)
"rw" 読み取りと書き込み両方で開く (read and write)
"a" 追加書き込みモードで開く (append)
すでにあるファイルの末尾に追加 もしファイルがなければ新規作成する

2:ファイルに情報を書き出す,ファイルから情報を読み込む

ファイル入出力では画面からデータを読み書きするのに用いたscanf, printf 関数によく似た,fscanf, fprintf関数をそれぞれ用いる。

書式(ファイル読み込み):fscanf(ファイルポインタ, "書式指定子", 引数, ...);
書式(ファイル書き出し):fprintf(ファイルポインタ, "書式指定子", 引数, ...);

使い方は,scanf, printf 関数と同じである。
違いは第1引数にファイルポインタを渡す点である。(どのファイルに読み書きするかを識別するため)

fprinf 関数の使用法は,画面出力のprintfとほぼ同じである。

例1:ファイルに実数を書き出す例。(注:以下の4例では,FILE* fp; や,fopen など必要な処理が省略されている)


fprintf(fp, "%f ¥n", 3.14159265);

例2:ファイルに文字列を書き出す例


char str[] = "Meiji University";

fprintf(fp, "%s ¥n", str);

fscanf 関数は,ファイル中の「スペース記号」または「改行記号」を区切りとしてファイルの内容を読み取り,変数に値を代入する。

例3:ファイルに書かれた数値 (例えば「1000」など) を,整数型変数に読み込む例


int i;
fscanf(fp, "%d", &i);   /*  &を忘れないこと.アドレスを取り出す演算子  */

例4:ファイルに書かれた文字列 (例えば「Ikuta」など) を,文字列型変数に読み込む例


char mojiretsu[100];
fscanf(fp, "%s", mojiretsu);   /*  & は不要!  */

3:ファイルを閉じる

ファイルの利用が終わったら,プログラムを終了する前に必ずファイルをクローズする。

書式:fclose(ファイルポインタ);

例:fclose(fp);

これを忘れると,ファイルの書き出しが完了しない場合がある。


ここまでのまとめ

以下に,ファイル入出力と,キーボード・画面入出力の比較を示す。

画面出力 ファイル出力

#include <stdio.h>

void main()
{










    printf("test");

}

#include <stdio.h>

void main()
{
    char filename[] = "test.txt";   /*  ファイル名は任意  */
    FILE *fp;

    fp = fopen(filename, "w");
   
    if(fp == NULL){
        printf("ファイルを作れませんでした");
        return;  /*  ここでプログラム終了  */
    }

    fprintf(fp, "test");
    fclose(fp);
}
キーボード入力 ファイル入力

#include <stdio.h>

void main()
{
    char s[100];











    scanf("%s", s);
    printf("%s", s);

}

#include <stdio.h>

void main()
{
    char s[100];
    char filename[] = "test.txt";   /*  ファイル名は任意  */
   
    FILE *fp;
   
    fp = fopen(filename, "r");
   
    if(fp == NULL){
        printf("ファイルがありませんでした");
        return;  /*  ここでプログラム終了  */
    }

    fscanf(fp, "%s", s);
    printf("%s", s);
    fclose(fp);
}

実際にやってみて確認してみよう。


演習課題(前半)

(1)キーボードから入力した数値をファイルに保存するプログラムを作成せよ。

(2)上問で作ったファイルに保存された数値を読み込み,画面に表示するプログラムを作成せよ。

(3)角度を0度から360度まで1度ずつ変えたときのsinの値を出力するプログラムを以前作成した。今回は,これを応用して1行に1度づつ結果をファイルに保存するようにせよ。ファイル名は,"sin.csv"とする。ファイル名の末尾に .csv (拡張子と呼ぶ)をつけると,Excelで簡単に開くことができる。


配列とファイル

前半のプログラムでは,ファイルからデータを一個取り出したり書き込んだり,連続データの書き出しを行った。後半では,ファイルから連続したデータを配列にすべて読み込もう。

配列にキーボードからデータを10個読み込むには,


#include <stdio.h>
 
void main()
{
    int i, n;
    int data[10];
 
    for(i = 0; i < 10; i++) {
        printf("%d番目のデータ:", i+1);
        scanf("%d", &(data[i]) );
    }
 
    /*  画面に表示  */
    for(i=0; i<10; i++) {
        printf("%d¥n", data[i]);
    }
}

のような手順である。これをもとに,ファイルから数値を10個,配列に読み込むように改良してみよう。ファイルには,値が一行に一つだけあるとすると,


#include <stdio.h>
 
void main()
{
    int i, n;
    float sin[10];
    FILE *fp;
 
    fp = fopen("sin.csv", "r");     /*  読み込みモードでファイルをオープン  */
    if(fp == NULL) {
        printf("ファイルを開くことが出来ませんでした.¥n");
        return;
    }
 
    for(i=0; i<10; i++){
        fscanf(fp, "%f", &(sin[i]) );     /*  1行読む,ファイルのデータの並びにあわせる  */
    }
 
    fclose(fp);
 
    for(i=0; i<10; i++) {
        printf("%f¥n", sin[i]);
    }
}

ここで,事前にファイル中のデータ個数がわからない場合はどうしたらよいだろう?

ファイルの終わりには,文字列と同様,ファイル終端を表す特殊なマークEOFがついている。(EOFは,End Of File の略)。ファイルを先頭から順に読み込んでゆき,終わりに達したかどうかを判定する(=EOFを探す)には,feof()関数を使う。この関数はファイルの終わりに達すると「真」(正確にはゼロでない値)になる。これを使ってデータの並んだファイルから配列にデータを読み込んでみよう。

配列のサイズは,あらかじめ読み込むデータより大きめに用意しておく。


#include <stdio.h>
 
void main()
{
    int i, n;
    float siny[512];    /* 大きめのサイズの配列を用意しておく. */
 
    FILE *fp;
 
    fp = fopen("sin.csv", "r");
    if(fp == NULL) {
        printf("ファイルを開くことが出来ませんでした.¥n");
        return;
    }
 
    n = 0;
 
    /*  ファイルが終わりでない 「かつ」 配列を飛び出さないうちは,読み込みを続ける  */
    while ( ! feof(fp) && n < 512) {
        fscanf(fp, "%f", &(siny[n]));
        n++;
    }
 
    fclose(fp);
 
    n = n-1; /* 上のwhileループでは,EOFの行を余分に読み込んでいるので,実際のデータ数は一つ少ない. */
 
    /*  画面に表示  */
    for(i=0; i<n; i++) {
        printf("%f¥n", siny[i]);
    }
}

演習課題(後半)

(4)演習(3)で作成したファイル,sin.csv から各データを配列に読み込んで,データ数,データの合計,および1度毎のデータを画面に出力するプログラムを作成せよ。