ファイル入出力

目次

概要

計算プログラムでは,処理結果を単に画面に表示するだけでなく,そのデータ永続的に(=PCの電源を切っても)保存しておきたい場合がある.
また,処理に先立って計算に必要な数値・条件や,入力データなどをディスク上から変数などに読み込んでから処理を開始する場合も多い.
実用的なデータ処理プログラムでは,このような手順が一般的である.

コンピュータにおいてはこのようなデータの永続的な保管機能を実現するハードウエアを 補助記憶装置と呼ぶ.
これらはディスクやドライブ,ストレージとも呼ばれる.

補助記憶装置の具体的な例として,ハードディスク(HDD)や,ソリッドステートディスク(SSD), SDメモリ, USBメモリ, Blue-ray disk, DVD, CD-ROM(古くは MO, PD, FDD, 磁気テープ, パンチカード・・・)などが挙げられる.
この中には,名称に「メモリ」とつくものがあるが,PC内蔵メモリ(主記憶装置)ではなく補助記憶装置である.

HDD drive
補助記憶装置.容量は 61GB(古い!)
ハードディスクドライブ,HDDと略す.

Windows, Mac OS, UNIX など,ほとんどのオペレーティングシステム (OS) では,ディスク上にデータを保存する際に,「ファイル」という管理単位を用いており,1つのファイルに1つの名前(ファイル名)をつけて識別している.

ファイルの種類は非常に多岐にわたり,例えば Windows OS ではファイル名の後に続く「拡張子」により区別している. (UNIXなどの他のOSでは,拡張子が重要ではない場合もある.)

以下,WindowsOSで使用されている主な拡張子の例:

今回の実習では,Cで書いたプログラムから

  1. ディスク上にファイルを作成して,データを保存(書き出し)
  2. ディスク上のファイルから,データを変数や配列に読みこむ

方法を学ぶ.

テキストファイル と バイナリファイル

まず,ファイルには大きく分けて「テキストファイル」と「バイナリファイル」の2種類がある.

テキストファイル
人間が読める文字の集合(=ASCIIコードやUNICODEなどの文字コードの集まり)である.
C言語ソースファイル(.cpp),テキストファイル(.txt) ファイル,htmlファイル(.html) ファイル,などがこれにあたる.
テキストエディタと呼ばれるソフトウエアで読み書きすることができる.
必ずしも拡張子が.txt であるとは限らない.
バイナリファイル
人間が直接読むことを前提としていない形式で,人間が直接判読することは困難である.
具体例は,実行ファイル(exeファイル)や jpeg, bmp, mp4 などの画像・動画ファイル等がこれにあたる.
バイナリファイルは,変数や配列など「メモリー上で表現されている0と1の並び」を,そのままディスク上に複製・保存したものである.
バイナリエディタと呼ばれるソフトで閲覧・編集することができる.

ただし,いずれも,ディスク上に0または1の並びが書かれているだけなので,コンピュータにとって本質的な差異は無い.
テキストエディタで開いて人が読めるものはテキストファイルであると考えてよい.

ファイル入出力の基本操作

C言語でのファイル入出力の操作は,以下の3つの手順が必ずセットとなる.
このパターンは毎回,決まっているので,覚えよう.
(stdio.h 内の f から始まるファイル関連のライブラリ関数を使う.)

  1. ファイルを開く fopen() 関数

  2. データの読み書き(下記のいずれか,または組合せて使う)

  3. ファイルを閉じる fclose()

この3つの手順(開く,読み書き,閉じる)が必ずこの順序で用いられる.

ファイル操作では,この基本操作に加えて,

など,多くの関数を組み合わせて用いられる.

ファイルを開く・閉じる

ファイルは「ファイル名」によって識別された複数のデータ集合であるから,まず,どのファイルに対して読み書きするかを識別するために,ファイルポインタと呼ばれるポインタ変数を定義する.
ファイルポインタは,ファイル 1 つに付き 1 個必要である. ファイルポインタ変数を複数用いれば,複数のファイルを同時に扱うことができる.

ファイルポインタとは? ファイルポインタは,「どのファイルの,どの箇所を読み書きしているか」などの情報が書かれている FILE 構造体を指すポインタである.
その中身の詳細については,さしあたり知る必要はなく,ここでは,さまざまなファイル入出力関数の引数として渡すために必要,と考えれば良い.

プログラムの中でファイルを扱う際には,最初に「ファイルを開く」,最後に「ファイルを閉じる」必要がある.

プログラム例
#include <stdio.h>

int main(void)
{
    FILE *fp;   //  ファイルポインタ.

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

    // ... ここで,読み書き処理 ...

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

    return 0;
}

fopen() 関数の引数の説明

ファイル名

fopen 関数の第 1 引数「ファイル名」は,ディスク上でのファイルの名称を表す文字列である.
ファイル名の箇所には,"datafile.txt" のように文字列定数を書いてもよいが,文字列変数char fname[] = "datafile.txt";のように,文字列変数としておく方が良い.

モード

fopen 関数の第 2 引数「モード」は,同じく文字列であるが,以下に示すいずれかのモードを指定する.
読み込み or 書き込み or 追記モードと,テキストファイル or バイナリファイルの組み合わせを指定する.

モード一覧
モード 意味 備考
ファイルが存在する場合 ファイルが存在しない場合
"r" 読み込み専用でファイルを開く.read 正常終了 エラー
"w" 書き込み専用で新規ファイルを開く.write 元ファイルが消去され,新たに作成 ファイルが新規作成される
"a" 追加書き込み専用でファイルを開く.append 元のファイルの末尾に追記 ファイルが新規作成される
"r+" 読み書き用でファイルを開く. 正常終了 エラー
"w+" 読み書き用で新規ファイルを開く. 元ファイルが消去され,新たに作成 ファイルが新規作成される
"a+" 読み込み+追加書き込み用でファイルを開く. 元のファイルの末尾に追記 ファイルが新規作成される

テキストモードとバイナリモード

fopen() でバイナリファイルを開く場合は,"rb","wb" のように,モードの文字列に b をつける. (b を省略した場合はテキストモードとみなされる.)

テキストモードとバイナリモードでは,改行コードの扱いが異なる. 開きたいファイルの性質を考慮して,正しいモードで開く必要がある.
テキストファイルをバイナリモードで開くことも可能であり,その逆も(無意味だが)可能であるが,正しい動作をしない可能性がある.
OSによっては,モード設定が意味をなさない場合もある.

ファイルのクローズ

最後に,ファイルの読み書きが終わったあとは,必ず fclose() 関数を用いてファイルを閉じなければならない.
ファイルを閉じないと,ファイルにデータが書かれなかったり,ファイルそのものが壊れてしまう場合がある.

fclose()閉じたファイルは,プログラム中のそれ以降の処理では読み書き処理ができない.
再度,読み書きする場合は,また fopen() 関数を用いてファイルを開きなおす必要がある.

fopen() の戻り値チェック

fopen() は,ファイルのオープンに成功すると,そのファイルを操作するためのファイルポインタを戻り値として返す.
逆に,「指定したファイルが無い」とか,「ディスクが満杯」,「別のプログラムで開いてロックされている」など,なんらかの原因によりオープンに失敗すると,NULL ポインタを返す.
このため,以下のような fopen()戻り値のチェック処理を必ず入れる必要がある.

ファイルが開けなかった場合で,処理の続行が不可能の場合は,exit() 関数でプログラムを終了させると良い. main()関数内であれば,return でも良い.

#include <stdio.h>
#include <stdlib.h>       // exit() 関数用

int main(void)
{
    FILE *fp;
    const char fname[] = "datafile.txt";    // ファイル名を表す文字列.

    fp = fopen(fname, "w");

    if( fp ==  NULL ) {
        printf("Cannot open %s !\n", fname);
        exit(1);                       // ファイル処理が続行出来ないので,ここで強制終了
    }


    // ここで読み書き処理をする


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

fopen()ifを同じ行にまとめて

FILE *fp;
if( (fp = fopen(fname, "w") ) == NULL ) {

と書いたり,ファイルポインタの定義とfopen()を同じ行にまとめて

FILE *fp = fopen(fname, "w");
if( fp ==  NULL ) ...

と書いても良い.

注釈 C言語では,fp = fopen(fname, "w")の式全体の値が,代入された値と等値になるため,この式全体を条件式内で if( (fp = fopen(fname, "w") ) == NULL ) と比較することができる.

また,NULLチェックの箇所は省略してif( !fp ) と書く例もあるが,NULL==0 を前提とするため,あまりお勧めできない.

練習問題

  1. 上記プログラム例を実行して,指示した名前でファイルが生成されているか,確認してみよう.
    (dir コマンド,または,エクスプローラ(Windowsの場合),ls コマンドまたはフォルダ(Mac OSの場合)で,実行ファイルのあるフォルダ内を確認.)
    何も書き込んでいないので,ファイルサイズは0バイトのはずである.
  2. 次に,フォルダ内に存在しないファイル名(例えば,"aaazzz.txt" など)を指定し,読込モード("r")で開いてみよ.
    fopenNULL を返し,エラーメッセージが正しく表示されることを確かめよう.)

データの読み書き

ファイルを無事に開くことができたら,ファイルポインタを使ってデータの読み書きを行う. まず,データを「読む」,「書く」の意味(データの流れる方向)については,

ファイルに書く
・・・プログラム中の変数や配列データを,ファイルに保存すること. プログラム中の変数・配列→ディスク上のファイル
ファイルから読む
・・・ファイル内に書かれている文字や数値を,プログラム中の変数や配列に取り込むこと. ディスク上のファイル→プログラム中の変数・配列

と覚えておこう.「読む」「書く」の主語は,自分の書いたプログラム,と考えればよい.

以下,順にファイル入出力関数の使い分けおよび実例を学ぼう.

FILEポインタと読み書き

FILEポインタには,現在操作しているファイルの「どこを読み書きしているか」の情報が含まれる.
読み込み・書き込みモードでファイルを開いた直後は「ファイルの先頭」を指し示している.
追加モードでファイルを開いた場合は「ファイルの最後」を指し示しており,ここから書き込みが行われる.
ファイル関連の関数でファイル読み書きをすると,ファイルを読み書きしている位置は自動的にインクリメントされる.

1 バイト入力,出力

ファイル読み書きの基本単位は 1 バイト ( byte ) であり,1 バイトづつ読み書きする関数が用意されている.

int fgetc(FILE* fp);
int fputc(int 書き出す値, FILE* fp);
fgetc()
ファイルの現在の位置から 1byte 読み込み,int型の値として返す.
戻り値:読み込んだ値. エラー,またはファイルの終端に達した場合は EOF (End Of File, -1と定義されている) を返す.
fputc()
ファイル内の 現在の位置に,1byte分の値を書き込む.
戻り値:書き込みが成功すると書き込んだ値を返す.失敗すると EOF を返す.

fgetc(), fputc() 関数は,ファイルに書かれている内容を 1byte = 8bit の数値として取り出す/書き出すだけである.
テキストファイル・バイナリファイルを問わずいかなるファイルでも読み書きできる.
その反面,1バイトごとの処理であるので,大量のデータ読み書きにおいては処理速度が遅く,効率が悪い.

ファイルから1バイトごとに読み込む例.
exeファイルと同じフォルダーに,sample.txt という名前のファイルが必要.
テキストファイル,バイナリファイルどちらも実行可能.
	
#include <stdio.h>
int main(void)
{
    // ファイルを開く
    FILE *fp = fopen("sample.txt", "r");

    if( ! fp ) {    // fp == NULL と等値.
        printf("File open error\n");
        return 1;    // exit()でもOK
    }

    char c;
    while((c = fgetc(fp)) != EOF){   // ファイルから 1 byte読み込んで c に代入するとともに,EOFを検出
        printf("%d\n", c);
    }

    fclose(fp);
    return 0;
}

1行 入出力関数

ファイルから 「行」単位で文字列の入出力を行うには,fputs()関数や,fgets()関数を使用する.

注:「行」という考え方がそもそもテキストファイルであるので,この関数はテキストファイル用である.

fgets()は,ファイルに書かれている内容が文字・数値にかかわらず文字(文字コード)として読み込んでくる.
例:ファイルに100と書かれていた場合,数値の100でなく,文字列の「1」「0」「0」として,3文字読み込む.

int fputs(char* 文字列, FILE* fp);
char* fgets(char* 文字列バッファ, int 文字列バッファのサイズ, FILE* fp);
fputs()
引数の文字列すべてをファイルに書き出す.
戻り値:書き込みが成功すると負でない値を返し.エラーの時は EOF を返す.
fgets()
読み込みに成功すると,引数の「文字列バッファ」にその文字列を代入する.
戻り値:読み込んだ文字列(の先頭アドレスを指すポインタ)を返す.
エラー(ファイルの終端に達した等)の時は, NULL ポインタを返す.
指定した「文字列バッファのサイズ」よりも,ファイル内の1行が長い場合は,読み込みがうまくできない.

書式指定したテキストファイルの入出力

テキストファイル入出力で一番柔軟性が高いのは,fprintf(), fscanf() 関数である.

書き出し(出力)
int fprintf(FILE* fp, char* 書式指定文字列, 変数...);

読み込み(入力)
int fscanf(FILE* fp, char* 書式指定文字列, 変数...);

fprintf()fscanf() は,画面・キーボード入出力で使う printf()scanf() と,ほぼ同じ引数である.
違いは,入出力先がファイルとなるため,第1引数にファイルポインタが追加されている点である.

注:テキストファイルに書かれている内容は基本的に文字・文字列である. 例えばファイル内に「100」と書かれていても,整数の百ではなく,文字の「いち,ぜろ,ぜろ」に過ぎない.
この場合,fscanfを使用して読み込むと,書式指定文字(%f%d)により,scanfと同様,文字を数値に変換してくれる.

練習問題

以下のプログラムを順次実行してみよう.
fprintf_sample.txt という名前のファイルが生成されているはずである. エディタなどで開いて中身を確かめよう.
fprintf()の出力は画面ではなく,ファイルに対して行われるため,画面には何も表示されない.

書き出しの例
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "fprintf_sample.txt";

    /***********************************************/
    /* 書き込みモードでテキストファイルを開く     */
    /***********************************************/
    fp = fopen(fname, "w");
    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    /***********************************************/
    /*  fprintfの例                                */
    /***********************************************/
    int k = 10;

    fprintf(fp, "%d ,%d ,%d \n", k, k+10, k+20);  /*  カンマ区切りで数値を出力する例. */
    fprintf(fp, "%X\n", k*10);           /*  16進数で数値を出力する例. */
    fprintf(fp, "My name is %s\n", fname);  /*  文字列を出力する例. */
    fprintf(fp, "This is a pen.\n");        /*  文字列を出力する例. */

    fprintf(fp, "\n");

    fclose(fp);     /* これを忘れてはいけない. */
    return 0;
}

次に,読み込みの例を示す.
この実行には,上記の出力ファイル fprintf_sample.txt が必要.

読み込みの例
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "fprintf_sample.txt";

    /***********************************************/
    /* 読み込みモードでテキストファイルを開く     */
    /***********************************************/
    fp = fopen(fname, "r");
    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    /***********************************************/
    /*  fscanfの例                                */
    /***********************************************/

    char buf[100];                             /* 読み込み用バッファ */
    while( fscanf(fp, "%s", buf) != EOF) {     /* EOF = end of file を表す記号 */
        printf("%s\n", buf);
    }

    fclose(fp);     /* これを忘れてはいけない. */
    return 0;
}

バイナリ入出力

bmp, jpeg画像ファイル,mp3, wav音声ファイル,mp4, mov動画ファイルなどは人が直接読むための文字列ではないため,テキスト入出力関数では扱えない.
このようなデータを扱う場合,以下のバイナリ入出力関数を用いる必要がある.

バイナリファイルは,メモリ上に置かれたデータ(0,1 の並び)を,そのままディスク上に保存するファイル形式であり,配列などの保存にとても便利である.
また,テキストファイル処理に必要な行末の探索処理や,数値⇔文字列の変換などを行わないため,読み書きが非常に高速である. (テキストファイルの読み書きより,数十倍~数百倍速いことが多い.)

バイトオーダー,エンディアンについて int や float, double など,複数バイトにわたる変数をメモリ上に表現する方式には,「リトルエンディアン」と「ビッグエンディアン」の2種類がある.
この説明で使用しているWindowsPCや,Intelプロセッサを使用したMacでは「リトルエンディアン」を採用している. 一方で,PowerPCや,ネットーワーク上を行き来するパケットではビッグエンディアンが用いられている.
たとえば,int型の整数 5 のメモリ上での表現は,ビッグエンディアンでは 00 00 00 05 だが,リトルエンディアンでは 05 00 00 00 となる. エンディアン方式が異なるPC間でバイナリファイルをやり取りする場合はバイトの並び(バイトオーダー)が逆順になっているので,その変換が必要となる.
バイナリファイルの読み書き
size_t  fread(void* buffer,  size_t  データ1個のサイズ,  size_t  個数,  FILE* fp);
size_t fwrite(void* buffer,  size_t  データ1個のサイズ,  size_t  個数,  FILE* fp);


(1)
void* は,voidポインタであり,「どんな型でも指すことができる汎用ポインタ」である.
従って,第1引数には,char,int, float や doubleなどの単純型や,配列,構造体など,ありとあらゆる変数のアドレスを渡すことができる.

(2)
size_t 型は,「処理系がもつ最大の値を格納できる整数」と定義され,一般に符号なし整数である.

これらの関数は,buffer を先頭アドレスとする配列データなどを,指定されたバイト数だけファイルに読み書きする関数である.
読み書きするデータの総バイト数は,「データ1個のサイズ」×「個数」バイトとなる.
「データ1個のサイズ」については,機種により変化することがあるので,定数で 2 や 4 と書くより,sizeof()演算子を用いて,sizeof(int)sizeof(float)と書いておくほうが良い.

バイナリファイルの出力例

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    const int N = 5;                                    // 配列の要素数
    float a[5] = {0.0,  1.2,  -5.3,  99.99, 10000};     // float 配列

    FILE* fp;
    const char fname[] = "data.bin";    // ファイル名

    fp = fopen(fname, "wb");            // バイナリ書き込みモードで開く

    if( fp ==  NULL ) {
        printf("Cannot open %s \n", fname);
        exit(1);
    }

    // バイナリ出力
    fwrite(a, sizeof(float), N, fp);

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

    printf("%s was written.\n", fname);

    return 0;
}

は,float 型配列 5 個分 = 20 byte のデータをファイルに書き出す.
sizeof は,「変数1個分のサイズを返す」演算子である. ここでは sizeof(float) == 4 であるが,定数で「4」と書かずに,このように記述しておくことにより,プログラムの意図をはっきり示すだけでなく,将来の移植性も担保できる.)

練習問題

  1. 上記のソースを用いて float 型配列を書き出し,書き出されたファイル名 data.bin のファイルサイズを確認しよう.予想したサイズになっているだろうか.
  2. data.bin ファイルをエディタで開いてみよう.(意味不明なはずである.)
    VSCodeの拡張機能「Hex Editor」をインストールすると,Bzなどのバイナリエディタを用いると,中身を見ることができる.
  3. 次に,別のプログラム(新しいソースファイル)を作成し,書き出した data.bin を逆に fread() 関数で 4バイトずつfloat 型変数に5回読み込んで,画面に表示してみよう.
    ヒント:
    float r;
    for(...) {  // 5回繰り返す
        fread(&r, sizeof(float), 1, fp);   // float 1 個分を,バイナリ形式で読み込む.
        printf("r = %f\n", r);
    }
    
    または,
    const int N = 5;
    float r[N];
    fread(&r[0], sizeof(float), N, fp);   // float 5 個分を,バイナリ形式で一括読み込み.
    

ファイルの状態判定

一つのファイル内に,何文字,何行のデータが含まれているかは,ファイルを実際に読み込んでみなければわからない場合がある.
そのような場合は,何らかの方法でファイルの末尾を検出する必要がある

feof() 関数

int feof(FILE* fp);

feof() は,現在の位置がファイルの終わりであれば 0以外の値 を,そうでなければ 0 を返す.
ファイルの中身にどれだけデータが書かれているか不明の場合に,while文と組み合わせて大いに役立つ関数である.

ファイルの終端まで 1byte ずつ読む例
sometext.txt をダウンロードして,実行ファイルと同じフォルダに保存すること.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;

    fp = fopen("sometext.txt", "r");
    if( fp ==  NULL ) {
        printf("File open error\n");
        exit(1);
    }

    char c;
    while ( ! feof(fp) )  {
        fscanf(fp, "%c", &c);	// fgetc() でもOK
        printf("%d , %c\n", c, c);      // 1文字読み込んで,10進数と文字で表示.
    }

    fclose(fp);
    return 0;
}

練習問題

上記のプログラムを作成し,実行してみよ.
ダウンロードした sometext.txt と,出力結果を見比べよう.

ファイル入出力の実例

ここでは,実際のファイル入出力のサンプルプログラムを例示する.
パターンは決まっているので,サンプルを各自で実行してみて,定石を覚えてしまうと良い.

テキストファイル,「読み込み」の例

テキストファイル myfile.txt を行単位で読み込む例.
プログラム実行時に,このファイルが実行ファイルと同じフォルダ内に無いとエラーになる.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "myfile.txt";   // ファイル名

    //  読み込みモードでテキストファイルを開く
    fp = fopen(fname, "r");

    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    ///////////////////////////////////////////////////
    //  fscanfの例
    //  先頭から最後まで1行づつ読み込んで画面に表示
    ///////////////////////////////////////////////////

    const int N = 100;    //  読み込みバッファのサイズ.1行の文字数より大きい値にしておく
    char buffer[N];       //  読み込みバッファ. ここに 1 行分のデータを格納する

    while( ! feof(fp) ) {
        fscanf(fp, "%[^\n]%*c", buffer);     //  1 行読み込み.fgetsでもOK
        printf("%s\n", buffer);  //  読み込んだ1行を文字列で表示,確認
    }

    fclose(fp);
    return 0;
}

ファイルの中身と,画面に表示される文字とを比べてみよう.

カンマ区切りテキスト(.csv)の読み込み例

データファイル data.csv 右クリックでダウンロード.
まずこのファイルをエディタで開いて,中身の構造を確認しよう.テキストファイルである.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "data.csv";   // ファイル名

    //  読み込みモードでテキストファイルを開く
    fp = fopen(fname, "r");

    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    int num;
    while( fscanf(fp, "%d,", &num) != EOF ) {   // camma 区切りの数値を1個読み込む
        printf("%d\n", num);  //  読み込んだ値を表示
    }

    fclose(fp);
    return 0;
}

配列データの読み+書きの例,バイナリファイル

以下のプログラムを作成し,実行してみよ. 出力される myfile.bin は,どのようなファイルとなるか.

#include <stdio.h>
#include <stdlib.h>

// 配列を表示する関数
void disp_array(int data[], int n)
{
    int i;
    for(i=0; i<n; i++) {
        printf("data[%d] = %d\n", i, data[i]);
    }
}

// 配列をゼロクリアする関数
void clear(int data[], int n)
{
    int i;
    for(i=0; i<n; i++) {
        data[i] = 0;
    }
}

int main(void)
{
    FILE* fp;
    const char fname[] = "myfile.bin";   // ファイル名

    //  書き込みモードでバイナリファイルを開く
    if( (fp = fopen(fname, "wb")) == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    const int N = 5;     //  データ個数
    int data[N] = {10, 20, 30, 40, 50};         // データ本体

    if( fwrite(data, sizeof(int), N, fp) != N) {
        printf("write error\n");
        exit(1);
    }
    fclose(fp);


    // ファイルに保存したので,配列の中身をいったんゼロクリア
    clear(data, N);


    /*************************** ここでいったん区切り *****************************************/

    printf("Before reading:\n");
    disp_array(data, N);


    //  読み込みモードでバイナリファイルを開く
    if( (fp = fopen(fname, "rb")) == NULL) {
        printf("Open Error\n");
        exit(1);
    }

    if( fread(data, sizeof(int), N, fp) != N) {
        printf("read error\n");
        exit(1);
    }
    fclose(fp);

    printf("After reading:\n");
    disp_array(data, N);

    return 0;
}

練習問題

以下の問それぞれに対応するプログラムを作成しなさい.
ファイルのダウンロードは,リンクを右クリック,「名前を付けてリンクを保存」などをクリックして,実行ファイルと同じフォルダに保存する.

  1. まず,テキストファイルhello.txtをダウンロードする.
    プログラムからこのファイルを開き,fscanf()関数で「1文字」ずつ読み込み, ファイル終端まですべての文字について, 「文字:そのアスキーコード16進数」を画面に表示せよ.

    表示できない文字は,文字コードのみ表示すること. (ヒント:ライブラリ関数isprint() を調べてみよ.)

    実行例.以下の値になるとは限らない
    H : 0x48
    e : 0x65
    l : 0x6C
    l : 0x6C
    o : 0x6F
      : 0x12
    W : 0x55
    ...
    

  2. フォルダー内の任意のテキストファイル(例えばcppソースファイル)の名前をキーボードから入力すると, そのファイルを1行ごとに読み込み,各行の先頭に4桁の10進数で行番号を付加し,画面に出力するプログラムを作成せよ.

    ヒント1:行番号は,printf()内の書式指定子を "%04d" とする.
    ヒント2:fscanf(), fgets(), fgetc() いろいろなバージョンで作成してみよう.

    【実行例】
    
    Input a filename to read:test.cpp    <-キーボードから入力
    
    0001 #include <stdio.h>
    0002
    0003 int main(void)
    0004 {
         …
    0022     return 0;
    0023 }