0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語でデータを保存する|ファイル操作・ヘッダーファイル・複数ファイルへの分割

0
Last updated at Posted at 2026-06-26

C言語を基礎から順に学びたい場合は、C言語 学習ロードマップ を参照してください。

構造体と動的メモリの基礎を確認してから読むと、複数のデータを保存する設計につなげやすくなります。

C言語でデータを保存する|ファイル操作・ヘッダーファイル・複数ファイルへの分割

プログラムを終了すると、変数や配列に入っていた値は基本的に消えます。

計算結果、設定値、商品一覧、ログのように、後で使いたいデータはファイルへ保存します。

また、プログラムが大きくなると、すべての処理を1つの .c ファイルへ書くのは読みづらくなります。

C言語では、役割ごとに .c ファイルを分け、共通して使う型や関数の宣言を .h ファイルへ置きます。

この記事では、商品データを題材に、次の内容を学びます。

  • fopen でファイルを開く
  • fprintf でファイルへ書き込む
  • fgets で1行ずつ読み込む
  • fclose で必ず閉じる
  • "r""w""a" の使い分け
  • .h ファイルと .c ファイルの役割
  • ヘッダーファイルのインクルードガード
  • 複数のソースファイルをまとめてコンパイルする

前提

この記事は、次の内容を学んだ人を対象にしています。

  • 構造体、typedefenum
  • 配列と sizeof
  • ポインタと const
  • 関数、戻り値、NULL

先に、次の記事を読んでください。

C言語の基本|初めてのプログラムを動かしながら、書き方と考え方を学ぶ

C言語のポインタ入門|「値」と「場所」を分けて考える

構造体の記事を読んでから進むと、最後の複数ファイルの例を理解しやすくなります。

C言語でデータをまとめて扱う|構造体・typedef・enum・構造体配列

動的メモリを使う場合は、メモリ確保と解放のルールも確認しておきます。

C言語の動的メモリ管理入門|malloc・calloc・realloc・free・memset


1. ファイルへ保存する理由

変数に入っている値は、プログラムが終了すると消えます。

プログラムを実行する
↓
変数へ値を入れる
↓
プログラムを終了する
↓
変数の値は消える

ファイルへ保存すると、次回の実行後もデータを残せます。

プログラムでデータを作る
↓
ファイルへ書き込む
↓
プログラムを終了する
↓
後でファイルを読み込める

ファイル操作では、次の流れを守ります。

fopen でファイルを開く
↓
開けたか確認する
↓
読み込みまたは書き込みを行う
↓
fclose でファイルを閉じる

ファイルを開いたら、最後に必ず fclose で閉じます。


2. ファイルへ文字を書き込む

最初に、ファイルへ文字を書き込む最小のプログラムを作ります。

write_file.c というファイルを作成してください。

#include <stdio.h>

int main(void)
{
    // 開いたファイルを扱うためのポインタです。
    FILE *file_pointer = NULL;

    // sample.txt を書き込み用で開きます。
    // "w" は書き込み用で開く指定です。
    file_pointer = fopen("sample.txt", "w");

    // ファイルを開けなかった場合は、以降の処理を行いません。
    if (file_pointer == NULL)
    {
        printf("sample.txt を開けませんでした。\n");

        return 1;
    }

    // fprintf は、画面ではなくファイルへ文字を書き込みます。
    if (fprintf(file_pointer, "C言語でファイルへ書き込みました。\n") < 0)
    {
        printf("ファイルへ書き込めませんでした。\n");
        fclose(file_pointer);

        return 1;
    }

    if (fprintf(file_pointer, "2行目の文字です。\n") < 0)
    {
        printf("ファイルへ書き込めませんでした。\n");
        fclose(file_pointer);

        return 1;
    }

    // ファイルを閉じます。
    // fclose の失敗も確認します。
    if (fclose(file_pointer) == EOF)
    {
        printf("ファイルを閉じるときに失敗しました。\n");

        return 1;
    }

    printf("sample.txt を作成しました。\n");

    return 0;
}

コンパイルして実行します。

gcc -std=c17 -Wall -Wextra -Wpedantic write_file.c -o write_file
./write_file

実行後、同じフォルダーに sample.txt が作成されます。

中身は次のとおりです。

C言語でファイルへ書き込みました。
2行目の文字です。

fprintf の役割

fprintf は、printf と似ています。

関数 出力先
printf 画面
fprintf 指定したファイル
printf("画面へ表示します。\n");
fprintf(file_pointer, "ファイルへ書き込みます。\n");

fprintf の最初の引数には、書き込み先のファイルを渡します。


3. ファイルから1行ずつ読み込む

次に、作成した sample.txt を読み込みます。

先に write_file.c を実行して、sample.txt を作成してください。

read_file.c というファイルを作成します。

#include <stdio.h>

int main(void)
{
    // 開いたファイルを扱うためのポインタです。
    FILE *file_pointer = NULL;

    // 1行分の文字を入れる配列です。
    // 最大255文字と、文字列の終わりを表す値1つを入れられます。
    char line[256];

    // sample.txt を読み込み用で開きます。
    // "r" は読み込み用で開く指定です。
    file_pointer = fopen("sample.txt", "r");

    if (file_pointer == NULL)
    {
        printf("sample.txt を開けませんでした。\n");

        return 1;
    }

    // ファイルの終わりまで、1行ずつ読み込みます。
    while (fgets(line, sizeof line, file_pointer) != NULL)
    {
        // line には改行も含まれるため、%s の後に改行は付けません。
        printf("%s", line);
    }

    // 読み込みエラーが起きていないか確認します。
    if (ferror(file_pointer))
    {
        printf("ファイルの読み込み中にエラーが起きました。\n");
        fclose(file_pointer);

        return 1;
    }

    if (fclose(file_pointer) == EOF)
    {
        printf("ファイルを閉じるときに失敗しました。\n");

        return 1;
    }

    return 0;
}

コンパイルして実行します。

gcc -std=c17 -Wall -Wextra -Wpedantic read_file.c -o read_file
./read_file

実行結果です。

C言語でファイルへ書き込みました。
2行目の文字です。

fgets の役割

fgets は、ファイルから1行を読み込み、char 型の配列へ入れる関数です。

fgets(line, sizeof line, file_pointer)
引数 意味
line 読み込んだ文字を入れる配列
sizeof line 読み込める最大サイズ
file_pointer 読み込むファイル

1行読めた場合は、line に文字列が入り、戻り値は NULL 以外になります。

ファイルの終わりまで読んだ場合や読み込みに失敗した場合は、NULL になります。

そのため、次の形で最後まで読み込めます。

while (fgets(line, sizeof line, file_pointer) != NULL)
{
    printf("%s", line);
}

4. ファイルを開く指定

最初に覚える指定は、次の3つです。

指定 意味 注意点
"r" 読み込み用で開く ファイルがない場合は失敗する
"w" 書き込み用で開く ファイルがあれば中身をすべて上書きする
"a" 追記用で開く 既存の末尾に追加する

たとえば、ログを末尾へ追加する場合は "a" を使います。

FILE *file_pointer = fopen("application.log", "a");

"w" は既存の内容を消して書き直すため、特に注意してください。

"w" で開く
↓
既存のファイルがある
↓
中身は上書きされる

5. ファイル操作で必ず確認すること

ファイル操作では、次の3点を必ず確認してください。

fopen が NULL ではないか
↓
読み込み・書き込みが失敗していないか
↓
fclose で閉じたか

特に、fopen の結果を確認せずに fprintffgets を使うのは危険です。

FILE *file_pointer = fopen("sample.txt", "r");

if (file_pointer == NULL)
{
    printf("ファイルを開けませんでした。\n");

    return 1;
}

ファイルがない、権限がない、保存先が使えないなど、開けない理由は複数あります。


6. 1つのCファイルが大きくなる問題

小さなプログラムでは、すべてを main.c に書いても問題ありません。

しかし、処理が増えると次のようになりがちです。

main.c
├ Product 構造体
├ 商品表示
├ 商品検索
├ ファイル保存
├ ファイル読込
├ 入力処理
└ main 関数

この状態では、どこに何が書かれているのか探しにくくなります。

そこで、役割ごとにファイルを分けます。

役割ごとに分けたときの #include 関係を確認します。

矢印は、矢印元のファイルが矢印先のヘッダーファイルを #include する関係です。

product_file.h の中では、Product 型を使うために #include "product.h" も書きます。

次に、コンパイルの流れを確認します。

main.cproduct.cproduct_file.c の3つをまとめて指定し、product_app を作成します。

main.c
  プログラム全体の流れ

product.h
  Product 型と商品表示関数の宣言

product.c
  商品表示関数の実装

product_file.h
  ファイル保存・読込関数の宣言

product_file.c
  ファイル保存・読込関数の実装

7. .h ファイルと .c ファイルの役割

ファイル 主な役割
.h 型、定数、関数の宣言を共有する
.c 実際の処理を書く

関数は、次の2つに分けて考えます。

// 関数宣言
void print_product(const Product *product);
// 関数定義
void print_product(const Product *product)
{
    // 実際の処理
}

product.h には宣言を書き、product.c には定義を書きます。

ほかの .c ファイルは、#include "product.h" と書くことで、Product 型や関数宣言を共有できます。


#include "..."#include <...>

書き方 主な用途
#include <stdio.h> C言語の標準ヘッダーファイル
#include "product.h" 自分で作ったヘッダーファイル

8. インクルードガードを付ける

ヘッダーファイルは、複数の場所から読み込まれることがあります。

同じ内容が何度も読み込まれるのを防ぐため、インクルードガードを付けます。

#ifndef PRODUCT_H
#define PRODUCT_H

// ヘッダーファイルの内容

#endif

意味は次のとおりです。

PRODUCT_H がまだ定義されていない場合だけ
↓
このヘッダーファイルの内容を読み込む

最初のうちは、ヘッダーファイルを作ったら次の形をそのまま使って問題ありません。

#ifndef ファイル名に対応する名前
#define ファイル名に対応する名前

// 宣言

#endif

9. 商品一覧を保存する複数ファイルのプログラム

ここからは、商品一覧を products.txt へ保存し、保存した内容を画面へ表示するプログラムを作ります。

フォルダー内に、次の5ファイルを作成してください。

product_app/
├ main.c
├ product.h
├ product.c
├ product_file.h
└ product_file.c

product.h

#ifndef PRODUCT_H
#define PRODUCT_H

#include <stddef.h>

// 商品名に使う文字数です。
#define PRODUCT_NAME_LENGTH 32

// 商品1件を表す型です。
typedef struct
{
    int id;
    char name[PRODUCT_NAME_LENGTH];
    int unit_price;
    int stock_count;
} Product;

// 商品1件を画面へ表示する関数です。
void print_product(const Product *product);

#endif

product.c

#include <stdio.h>

#include "product.h"

// 商品1件を画面へ表示する関数です。
void print_product(const Product *product)
{
    // NULL が渡された場合は何もしません。
    if (product == NULL)
    {
        return;
    }

    printf("商品ID: %d\n", product->id);
    printf("商品名: %s\n", product->name);
    printf("単価: %d円\n", product->unit_price);
    printf("在庫数: %d個\n", product->stock_count);
}

product_file.h

#ifndef PRODUCT_FILE_H
#define PRODUCT_FILE_H

#include <stddef.h>

#include "product.h"

// 商品一覧をテキストファイルへ保存します。
// 成功した場合は1、失敗した場合は0を返します。
int save_products_to_file(
    const char *file_path,
    const Product products[],
    size_t product_count);

// テキストファイルの内容を1行ずつ画面へ表示します。
// 成功した場合は1、失敗した場合は0を返します。
int print_file_contents(const char *file_path);

#endif

product_file.c

#include <stdio.h>

#include "product_file.h"

// 商品一覧をテキストファイルへ保存します。
int save_products_to_file(
    const char *file_path,
    const Product products[],
    size_t product_count)
{
    // 書き込み用でファイルを開きます。
    FILE *file_pointer = fopen(file_path, "w");

    if (file_pointer == NULL)
    {
        return 0;
    }

    // 1行目に見出しを書き込みます。
    if (fprintf(file_pointer, "ID,商品名,単価,在庫数\n") < 0)
    {
        fclose(file_pointer);

        return 0;
    }

    // 商品を1件ずつ、カンマ区切りのテキストとして保存します。
    for (size_t index = 0; index < product_count; index++)
    {
        if (fprintf(
                file_pointer,
                "%d,%s,%d,%d\n",
                products[index].id,
                products[index].name,
                products[index].unit_price,
                products[index].stock_count) < 0)
        {
            fclose(file_pointer);

            return 0;
        }
    }

    // ファイルを閉じ、失敗していないか確認します。
    if (fclose(file_pointer) == EOF)
    {
        return 0;
    }

    return 1;
}

// テキストファイルの内容を1行ずつ画面へ表示します。
int print_file_contents(const char *file_path)
{
    char line[256];
    FILE *file_pointer = fopen(file_path, "r");

    if (file_pointer == NULL)
    {
        return 0;
    }

    while (fgets(line, sizeof line, file_pointer) != NULL)
    {
        printf("%s", line);
    }

    // ファイル末尾ではなく、読み込みエラーで終わった場合を確認します。
    if (ferror(file_pointer))
    {
        fclose(file_pointer);

        return 0;
    }

    if (fclose(file_pointer) == EOF)
    {
        return 0;
    }

    return 1;
}

main.c

#include <stdio.h>

#include "product.h"
#include "product_file.h"

int main(void)
{
    // 商品一覧を作成します。
    Product products[] = {
        { 101, "りんご", 120, 10 },
        { 102, "みかん", 100, 8 },
        { 103, "牛乳", 210, 5 }
    };

    // 配列の要素数を自動で取得します。
    size_t product_count = sizeof products / sizeof products[0];

    printf("商品一覧\n");

    for (size_t index = 0; index < product_count; index++)
    {
        print_product(&products[index]);
        printf("\n");
    }

    // 商品一覧をファイルへ保存します。
    if (!save_products_to_file("products.txt", products, product_count))
    {
        printf("商品一覧を保存できませんでした。\n");

        return 1;
    }

    printf("products.txt を作成しました。\n\n");
    printf("保存したファイルの内容\n");

    // 保存したファイルの内容を表示します。
    if (!print_file_contents("products.txt"))
    {
        printf("products.txt を読み込めませんでした。\n");

        return 1;
    }

    return 0;
}

複数の .c ファイルをコンパイルする

複数の .c ファイルがある場合は、必要なファイルをすべて指定してコンパイルします。

gcc -std=c17 -Wall -Wextra -Wpedantic \
    main.c product.c product_file.c \
    -o product_app

実行します。

./product_app

実行後、products.txt が作成されます。

ID,商品名,単価,在庫数
101,りんご,120,10
102,みかん,100,8
103,牛乳,210,5

この例では、商品名にカンマが含まれないことを前提にしています。

実務でCSVを厳密に扱う場合は、カンマ、改行、ダブルクォートを含む値の扱いを別途考える必要があります。


10. 役割ごとにファイルを分ける考え方

今回のファイル構成を整理します。

ファイル 役割
main.c プログラム全体の流れを決める
product.h Product 型と商品表示関数の宣言を共有する
product.c 商品表示の処理を書く
product_file.h ファイル保存・読込関数の宣言を共有する
product_file.c ファイル保存・読込の処理を書く

ファイルを分ける目的は、ファイル数を増やすことではありません。

処理の役割を分ける
↓
読む場所を探しやすくする
↓
変更の影響を小さくする
↓
テストや修正をしやすくする

main.c が長くなってきたら、役割ごとに関数へ分け、その関数群を別ファイルへ移すことを検討してください。


11. 確認課題

課題1:追記モードでログを保存する

application.log を追記モードの "a" で開き、次の文字を末尾へ追加してください。

アプリケーションを起動しました。
答えを見る
#include <stdio.h>

int main(void)
{
    FILE *file_pointer = fopen("application.log", "a");

    if (file_pointer == NULL)
    {
        printf("application.log を開けませんでした。\n");

        return 1;
    }

    if (fprintf(file_pointer, "アプリケーションを起動しました。\n") < 0)
    {
        printf("ログを書き込めませんでした。\n");
        fclose(file_pointer);

        return 1;
    }

    if (fclose(file_pointer) == EOF)
    {
        printf("ファイルを閉じるときに失敗しました。\n");

        return 1;
    }

    return 0;
}

課題2:商品表示の見出しを変更する

product.cprint_product を変更し、商品名を次の形式で表示してください。

商品名: りんご(在庫: 10個)

課題3:保存するファイル名を変更する

main.cproducts.txtinventory.txt へ変更してください。

保存処理と読み込み処理の両方を変更する必要があります。


まとめ

ファイル操作では、次の順番を守ります。

fopen で開く
↓
NULL ではないか確認する
↓
読む・書く
↓
fclose で閉じる
関数・指定 役割
fopen ファイルを開く
fprintf ファイルへ書き込む
fgets ファイルから1行読み込む
fclose ファイルを閉じる
"r" 読み込み用
"w" 書き込み用。既存内容は上書き
"a" 追記用

複数ファイルへ分けるときは、次の役割分担を意識してください。

.h
= 型・定数・関数宣言を共有する

.c
= 実際の処理を書く

main.c
= プログラム全体の流れを書く

この構成を基礎にすると、構造体で表したデータを保存し、役割ごとに処理を分けたCプログラムを作れるようになります。


次に取り組むこと

ここまでで、C言語の基本文法、ポインタ、構造体、動的メモリ管理、ファイル操作、複数ファイルへの分割までを一通り学びました。

次は、練習問題や小さなプログラムを作り、値の集計、検索、入力チェック、データ保存を組み合わせて使う練習に進みます。

コードを書くときの基準は、次の記事も参照してください。

コーディング規約の基準(C言語)|命名/メモリ/エラー処理/未定義動作

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?