★ファイル操作(シーク)★


今回はバイナリファイルの特徴を生かした使い方を説明します。

まずバイナリファイルの特性とは何かから説明します。

テキストファイルはデータを文字として保存します。

例えば数値の123は文字の"123"としてファイルに保存します。

バイナリファイルはデータを2進数でバイト単位に保存します。

同じく数値の123は 00000000 00000000 00000000 01111011 というビットデータで保存されます。

テキストファイルで12345を保存すると"12345"として保存します。

123と比べて2桁増えています。1文字は8ビット(1バイト)ですので、桁が増えると保存するデータ量が増えていきます。

バイナリファイルで12345を保存すると 00000000 00000000 00110000 00111001 です。

長く見えますが123の時とバイト数は変わっていません。

テキストファイルは1個のデータのサイズが変わりますが、バイナリファイルは変わらないというところがポイントです。


下のプログラムを動かし、バイナリファイルを作成します。

5人分の名前と年齢のデータが構造体配列に入っています。

これを「People.ppl」という独自に作った拡張子のファイルに保存します。

※「txt」では無くなるため、ダブルクリックでメモ帳は開かなくなります。

<sample program 125-01>

#include <stdio.h>

#define PERSON_MAX 5

#define NAME_MAX 21

typedef struct{
    char name[NAME_MAX];
    int age;
} Person;

int main(void)
{
    Person people[PERSON_MAX] = {
        { "Alex", 21 },
        { "Billy", 25 },
        { "Charlie", 30},
        { "Dennis", 42 },
        { "Eddy", 48 }
    };

    FILE* fp;

    fp = fopen("People.ppl", "wb");

    if (fp == NULL) {
        printf("OPEN ERROR\n");
        return 1;
    }

    fwrite(people, sizeof(Person), PERSON_MAX, fp);

    fclose(fp);

    return 0;
}

実行して、「People.ppl」を作成します。

※中身が見たい方は、先にメモ帳を開いておき、「People.ppl」をドラッグドロップしてください。


次に読み込みのプログラムを作るのですが、今回やりたいことを書きます。

全てのデータを読み込むのではなく、こちらで指定したデータを1つだけ読み込みたいです。

まずは枠組みを作りましょう。

<sample program 125-02>

#include <stdio.h>

#define PERSON_MAX 5

#define NAME_MAX 21

typedef struct{
    char name[NAME_MAX];
    int age;
} Person;

int main(void)
{
    Person person;

    FILE* fp;

    fp = fopen("People.ppl", "rb");

    if (fp == NULL) {
        printf("OPEN ERROR\n");
        return 1;
    }


    fclose(fp);

    return 0;
}

まだ読み込み部分は入れていません。


これまでのプログラムで、ファイルは先頭から読み込まれることを見てきたと思います。

テキストファイルでは区切り文字があり、読み込み用の関数が呼ばれる度にファイルに入っているデータが順次読み込まれました。

バイナリファイルでは区切り文字はありませんが、バイト数を区切って順番に読み込むことも出来ます。

ファイルの中には、次に読み書きするデータの位置を保持している場所があり、データを読み込む度に自動的に次のデータを指していきます。

その「読み込むデータの位置」を変える関数が「fseek関数」です。

<fseek関数>

fseek(@, A, B);

@ファイルポインタ
A基準位置からのバイト数
B基準位置

 基準位置
  SEEK_SET ファイルの先頭
  SEEK_CUR 現在の位置
  SEEK_END ファイルの終わり

この関数を使えば、読み込む前に位置を変更することが出来ます。

取りあえず、2人目のデータを読み込むプログラムを追加してみます。

<sample program 125-03>

#include <stdio.h>

#define PERSON_MAX 5

#define NAME_MAX 21

typedef struct{
    char name[NAME_MAX];
    int age;
} Person;

int main(void)
{
    Person person;

    FILE* fp;

    fp = fopen("People.ppl", "rb");

    if (fp == NULL) {
        printf("OPEN ERROR\n");
        return 1;
    }

    fseek(fp, sizeof(Person), SEEK_SET);

    fread(&person, sizeof(Person), 1, fp);

    fclose(fp);

    printf("Name = %s\n", person.name);
    printf("Age = %d\n", person.age);

    return 0;
}

<実行結果>

Name = Billy
Age = 25
続行するには何かキーを押してください・・・

1人目のAlexは読まれず、2人目のBillyのデータが読み込まれました。

SEEK_SETがファイルの先頭を表しますから、先頭から1人分(sizeof(Person))後へ読み込み位置を変更しました。

冒頭で書きましたが、格納したデータによって1人分のデータサイズが変わることはありませんので、それぞれのデータの先頭番地に移動させることが出来るのです。

それでは、指定した番号(0番から4番)のデータを読み込めるよう変更してみましょう。

<sample program 125-04>

#include <stdio.h>

#define PERSON_MAX 5

#define NAME_MAX 21

typedef struct{
    char name[NAME_MAX];
    int age;
} Person;

int main(void)
{
    Person person;

    int input;

    FILE* fp;

    fp = fopen("People.ppl", "rb");

    if (fp == NULL) {
        printf("OPEN ERROR\n");
        return 1;
    }

    do {
        scanf("%d", &input);
    } while (input < 0 || input >= PERSON_MAX);

    fseek(fp, sizeof(Person) * input, SEEK_SET);

    fread(&person, sizeof(Person), 1, fp);

    fclose(fp);

    printf("Name = %s\n", person.name);
    printf("Age = %d\n", person.age);

    return 0;
}

<実行結果>

3
Name = Dennis
Age = 42
続行するには何かキーを押してください・・・

ファイルを開いたまま、必要な時に必要なデータを読み込むことも可能です。

工夫すれば色々な場面で使えそうですね。


次へ

戻る

目次へ