C言語ファイル操作:追加・読み込み・更新・削除(CRUD)機能を持つ連絡先管理プログラム

目次

はじめに

C言語によるファイル操作の学習は、単にデータを書き込んだり読み込んだりするだけでは終わりません。実用的なアプリケーションを開発するには、保存したデータの更新削除といった、より高度な操作が不可欠です。

この記事では、基本的な連絡先の「追加」「読み込み」「検索」機能に加え、「更新」「削除」機能を実装した、本格的な連絡先管理プログラムの作成方法を解説します。いわゆるCRUD(Create, Read, Update, Delete)と呼ばれる、データ操作の基本要素をすべて網羅します。

入力処理にはfgetsを使用し、スペースを含む氏名や住所も扱えるように改良しています。ファイル操作の応用的なテクニックの学習に適した内容です。


1. プログラムの全体設計と構造体

プログラムは、以下の5つの主要機能を持ちます。

  1. Create(追加): 新しい連絡先をファイルに保存する。
  2. Read(読み込み・検索): 番号や名前で連絡先を検索し、表示する。
  3. Update(更新): 既存の連絡先情報を新しい内容で上書きする。
  4. Delete(削除): 既存の連絡先をファイルから削除する。
  5. List(一覧表示): 保存されているすべての連絡先を表示する。

データを管理するための構造体は、前回と同様のものを使用します。

// 連絡先一件分のデータを格納する構造体
typedef struct {
    char name[60];
    char address[120];
} Contact;

2. 機能の実装

各機能を個別の関数として実装します。特に、今回の中心となる「更新」と「削除」のロジックに注目してください。

2-1. ユーティリティ関数

まず、コード全体で利用する補助的な関数を準備します。

  • 入力バッファのクリア: fgetsを使った安全な入力の後、バッファに残る不要な改行文字などをクリアします。
  • 安全な文字列入力: fgetsを使い、改行文字を取り除く処理をまとめた関数です。
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // for exit, remove, rename

#define FILENAME "contacts_db.dat"

// 入力バッファをクリアする関数
void clearInputBuffer() {
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

// 安全に文字列入力を受け取る関数
void getStringInput(char* buffer, int size) {
    if (fgets(buffer, size, stdin) != NULL) {
        // fgetsで読み込んだ末尾の改行文字をNULL文字に置き換える
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len - 1] == '\n') {
            buffer[len - 1] = '\0';
        } else {
            // バッファがいっぱいになるまで入力された場合、残りの入力をクリア
            clearInputBuffer();
        }
    }
}

2-2. 連絡先の一覧表示 (listContacts)

ファイルに保存されている全ての連絡先を、先頭から順に読み込んで表示します。

// 連絡先を一覧表示する関数
void listContacts() {
    FILE* fp;
    Contact person;
    int index = 1;

    if (fopen_s(&fp, FILENAME, "rb") != 0) {
        fprintf(stderr, "エラー: データベースファイルを開けません。\n");
        return;
    }

    printf("\n--- 連絡先一覧 ---\n");
    while (fread(&person, sizeof(Contact), 1, fp) == 1) {
        printf("[%d]\n", index++);
        printf("  名前: %s\n", person.name);
        printf("  住所: %s\n", person.address);
        printf("--------------------\n");
    }

    if (index == 1) {
        printf("連絡先は登録されていません。\n");
    }

    fclose(fp);
}

2-3. 連絡先の追加 (addContact)

新しい連絡先情報をユーザーから受け取り、ファイルの末尾に追記します。

// 連絡先を追加する関数
void addContact() {
    FILE* fp;
    Contact person;

    if (fopen_s(&fp, FILENAME, "ab") != 0) {
        fprintf(stderr, "エラー: データベースファイルを開けません。\n");
        return;
    }

    printf("\n--- 新しい連絡先の追加 ---\n");
    printf("名前を入力してください: ");
    getStringInput(person.name, sizeof(person.name));

    printf("住所を入力してください: ");
    getStringInput(person.address, sizeof(person.address));

    fwrite(&person, sizeof(Contact), 1, fp);
    fclose(fp);

    printf("連絡先を正常に追加しました。\n");
}

2-4. 連絡先の更新 (updateContact)

ここからが新しい機能です。更新は、以下の手順で行います。

  1. 更新対象の連絡先を一覧表示し、ユーザーに番号で選択させる。
  2. 指定された番号のデータを読み込む。
  3. ファイルポインタをfseekでそのデータの開始位置に戻す。
  4. 新しいデータを入力させ、fwriteで元のデータに上書きする。
// 連絡先を更新する関数
void updateContact() {
    FILE* fp;
    Contact person;
    int targetIndex, currentIndex = 0;
    long filePosition;

    listContacts();
    printf("\n更新したい連絡先の番号を入力してください (0でキャンセル): ");
    scanf_s("%d", &targetIndex);
    clearInputBuffer();

    if (targetIndex <= 0) return;

    if (fopen_s(&fp, "r+b") != 0) { // 読み書き両用でバイナリオープン
        fprintf(stderr, "エラー: データベースファイルを開けません。\n");
        return;
    }

    while (fread(&person, sizeof(Contact), 1, fp) == 1) {
        currentIndex++;
        if (currentIndex == targetIndex) {
            // 更新対象が見つかった
            printf("\n--- 連絡先の更新 ---\n");
            printf("現在の名前: %s\n", person.name);
            printf("新しい名前を入力してください: ");
            getStringInput(person.name, sizeof(person.name));

            printf("現在の住所: %s\n", person.address);
            printf("新しい住所を入力してください: ");
            getStringInput(person.address, sizeof(person.address));

            // ファイルポインタをこのレコードの開始位置に戻す
            filePosition = (long)(currentIndex - 1) * sizeof(Contact);
            fseek(fp, filePosition, SEEK_SET);

            // 更新されたデータを書き込む
            fwrite(&person, sizeof(Contact), 1, fp);
            printf("連絡先を更新しました。\n");
            fclose(fp);
            return;
        }
    }
    
    printf("指定された番号の連絡先は見つかりませんでした。\n");
    fclose(fp);
}

2-5. 連絡先の削除 (deleteContact)

ファイルの途中からデータを物理的に削除するのは複雑なため、以下の安全な手順を踏みます。

  1. 削除対象以外のデータを、一時的な新しいファイル(temp.dat)に書き出す。
  2. 元のファイルを削除する (remove)。
  3. 一時ファイルを元のファイル名に変更する (rename)。

これにより、結果的に対象データが削除されたのと同じ状態になります。

// 連絡先を削除する関数
void deleteContact() {
    FILE* fp, * tempFp;
    Contact person;
    int targetIndex, currentIndex = 1;
    int found = 0;
    const char* tempFilename = "temp.dat";

    listContacts();
    printf("\n削除したい連絡先の番号を入力してください (0でキャンセル): ");
    scanf_s("%d", &targetIndex);
    clearInputBuffer();

    if (targetIndex <= 0) return;

    if (fopen_s(&fp, FILENAME, "rb") != 0) {
        fprintf(stderr, "エラー: 元ファイルを開けません。\n");
        return;
    }
    if (fopen_s(&tempFp, tempFilename, "wb") != 0) {
        fprintf(stderr, "エラー: 一時ファイルを作成できません。\n");
        fclose(fp);
        return;
    }

    // 元のファイルから1件ずつ読み込み、削除対象でなければ一時ファイルに書き込む
    while (fread(&person, sizeof(Contact), 1, fp) == 1) {
        if (currentIndex == targetIndex) {
            found = 1; // 削除対象なので何もしない
        } else {
            fwrite(&person, sizeof(Contact), 1, tempFp);
        }
        currentIndex++;
    }

    fclose(fp);
    fclose(tempFp);

    if (found) {
        // 元のファイルを削除し、一時ファイルをリネームする
        if (remove(FILENAME) != 0) {
            fprintf(stderr, "エラー: 元ファイルの削除に失敗しました。\n");
        } else if (rename(tempFilename, FILENAME) != 0) {
            fprintf(stderr, "エラー: ファイルのリネームに失敗しました。\n");
        } else {
            printf("連絡先を正常に削除しました。\n");
        }
    } else {
        printf("指定された番号の連絡先は見つかりませんでした。\n");
        remove(tempFilename); // 不要な一時ファイルを削除
    }
}

3. すべてを統合する main 関数

最後に、これらの機能をユーザーが選択できるメニューを持つmain関数を作成します。

int main(void) {
    int choice;

    while (1) {
        printf("\n===== 連絡先管理システム =====\n");
        printf("1. 連絡先を一覧表示\n");
        printf("2. 連絡先を追加\n");
        printf("3. 連絡先を更新\n");
        printf("4. 連絡先を削除\n");
        printf("0. 終了\n");
        printf("============================\n");
        printf("操作を選択してください: ");

        if (scanf_s("%d", &choice) != 1) {
            fprintf(stderr, "不正な入力です。数値を入力してください。\n");
            clearInputBuffer();
            continue;
        }
        clearInputBuffer(); // scanfの後の改行文字をクリア

        switch (choice) {
            case 1: listContacts(); break;
            case 2: addContact(); break;
            case 3: updateContact(); break;
            case 4: deleteContact(); break;
            case 0:
                printf("プログラムを終了します。\n");
                return 0;
            default:
                printf("無効な選択です。もう一度入力してください。\n");
                break;
        }
    }
    return 0;
}

4. 全体のサンプルコード

以下に、全ての機能を統合した完全なソースコードを掲載します。

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

#define FILENAME "contacts_db.dat"

// 構造体定義
typedef struct {
    char name[60];
    char address[120];
} Contact;

// プロトタイプ宣言
void clearInputBuffer();
void getStringInput(char* buffer, int size);
void listContacts();
void addContact();
void updateContact();
void deleteContact();

int main(void) {
    int choice;

    while (1) {
        printf("\n===== 連絡先管理システム =====\n");
        printf("1. 連絡先を一覧表示\n");
        printf("2. 連絡先を追加\n");
        printf("3. 連絡先を更新\n");
        printf("4. 連絡先を削除\n");
        printf("0. 終了\n");
        printf("============================\n");
        printf("操作を選択してください: ");

        if (scanf_s("%d", &choice) != 1) {
            fprintf(stderr, "不正な入力です。数値を入力してください。\n");
            clearInputBuffer();
            continue;
        }
        clearInputBuffer(); // scanfの後の改行文字をクリア

        switch (choice) {
            case 1: listContacts(); break;
            case 2: addContact(); break;
            case 3: updateContact(); break;
            case 4: deleteContact(); break;
            case 0:
                printf("プログラムを終了します。\n");
                return 0;
            default:
                printf("無効な選択です。もう一度入力してください。\n");
                break;
        }
    }
    return 0;
}

void clearInputBuffer() {
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

void getStringInput(char* buffer, int size) {
    if (fgets(buffer, size, stdin) != NULL) {
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len - 1] == '\n') {
            buffer[len - 1] = '\0';
        } else {
            clearInputBuffer();
        }
    }
}

void listContacts() {
    FILE* fp;
    Contact person;
    int index = 1;

    if (fopen_s(&fp, FILENAME, "rb") != 0) {
        // ファイルが存在しない場合はエラーではなく、まだ登録がないことを示す
        printf("\n連絡先はまだ登録されていません。\n");
        return;
    }

    printf("\n--- 連絡先一覧 ---\n");
    while (fread(&person, sizeof(Contact), 1, fp) == 1) {
        printf("[%d]\n", index++);
        printf("  名前: %s\n", person.name);
        printf("  住所: %s\n", person.address);
        printf("--------------------\n");
    }

    if (index == 1) {
        printf("連絡先は登録されていません。\n");
    }

    fclose(fp);
}

void addContact() {
    FILE* fp;
    Contact person;

    if (fopen_s(&fp, FILENAME, "ab") != 0) {
        fprintf(stderr, "エラー: データベースファイルを開けません。\n");
        return;
    }

    printf("\n--- 新しい連絡先の追加 ---\n");
    printf("名前を入力してください: ");
    getStringInput(person.name, sizeof(person.name));

    printf("住所を入力してください: ");
    getStringInput(person.address, sizeof(person.address));

    fwrite(&person, sizeof(Contact), 1, fp);
    fclose(fp);

    printf("連絡先を正常に追加しました。\n");
}

void updateContact() {
    FILE* fp;
    Contact person;
    int targetIndex, currentIndex = 0;
    long filePosition;

    listContacts();
    printf("\n更新したい連絡先の番号を入力してください (0でキャンセル): ");
    if (scanf_s("%d", &targetIndex) != 1) {
        fprintf(stderr, "不正な入力です。\n");
        clearInputBuffer();
        return;
    }
    clearInputBuffer();

    if (targetIndex <= 0) return;

    if (fopen_s(&fp, "r+b") != 0) {
        fprintf(stderr, "エラー: データベースファイルを開けません。\n");
        return;
    }

    while (fread(&person, sizeof(Contact), 1, fp) == 1) {
        currentIndex++;
        if (currentIndex == targetIndex) {
            printf("\n--- [%d]の連絡先を更新 ---\n", targetIndex);
            printf("現在の名前: %s\n", person.name);
            printf("新しい名前を入力してください: ");
            getStringInput(person.name, sizeof(person.name));

            printf("現在の住所: %s\n", person.address);
            printf("新しい住所を入力してください: ");
            getStringInput(person.address, sizeof(person.address));

            filePosition = (long)(currentIndex - 1) * sizeof(Contact);
            fseek(fp, filePosition, SEEK_SET);
            fwrite(&person, sizeof(Contact), 1, fp);
            
            printf("連絡先を更新しました。\n");
            fclose(fp);
            return;
        }
    }
    
    printf("指定された番号の連絡先は見つかりませんでした。\n");
    fclose(fp);
}

void deleteContact() {
    FILE* fp, * tempFp;
    Contact person;
    int targetIndex, currentIndex = 1;
    int found = 0;
    const char* tempFilename = "temp_db.dat";

    listContacts();
    printf("\n削除したい連絡先の番号を入力してください (0でキャンセル): ");
     if (scanf_s("%d", &targetIndex) != 1) {
        fprintf(stderr, "不正な入力です。\n");
        clearInputBuffer();
        return;
    }
    clearInputBuffer();

    if (targetIndex <= 0) return;

    if (fopen_s(&fp, FILENAME, "rb") != 0) {
        fprintf(stderr, "エラー: 元ファイルを開けません。\n");
        return;
    }
    if (fopen_s(&tempFp, tempFilename, "wb") != 0) {
        fprintf(stderr, "エラー: 一時ファイルを作成できません。\n");
        fclose(fp);
        return;
    }

    while (fread(&person, sizeof(Contact), 1, fp) == 1) {
        if (currentIndex != targetIndex) {
            fwrite(&person, sizeof(Contact), 1, tempFp);
        } else {
            found = 1;
        }
        currentIndex++;
    }

    fclose(fp);
    fclose(tempFp);

    if (found) {
        if (remove(FILENAME) != 0) {
            fprintf(stderr, "エラー: 元ファイルの削除に失敗しました。\n");
            remove(tempFilename); // 一時ファイルも削除
        } else if (rename(tempFilename, FILENAME) != 0) {
            fprintf(stderr, "エラー: ファイルのリネームに失敗しました。\n");
        } else {
            printf("連絡先を正常に削除しました。\n");
        }
    } else {
        printf("指定された番号の連絡先は見つかりませんでした。\n");
        remove(tempFilename);
    }
}

まとめ

本記事では、C言語のファイル操作を用いて、データの**CRUD(作成、読み取り、更新、削除)**を全て実装した連絡先管理プログラムを作成しました。

  • 入力処理の改善: scanf_sの代わりにfgetsを用いることで、スペースを含む文字列も安全に扱えるようになりました。
  • 更新処理: r+bモードでファイルを開き、fseekで目的の場所へ移動してfwriteで上書きする、というランダムアクセスの実践的な応用を学びました。
  • 削除処理: 一時ファイルを利用して、安全かつ確実に特定のレコードを削除する手法を実装しました。これはファイル操作における定石の一つです。

このプログラムは、C言語による実践的なアプリケーション開発の第一歩となります。ここから更に、名前による検索機能の追加データの並べ替え(ソート)機能より詳細なエラーハンドリングなどを実装することで、さらに高機能なプログラムへと発展させることが可能です。

この記事が、C言語学習の助けとなれば幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次