C++ で作る強化された連絡先管理システム

概要

この記事では、基本的な C++ による連絡先管理アプリケーションを拡張・改善するプロセスについて説明します。元の実装にはいくつかの課題がありました。それらを解決することで、より堅牢でユーザーフレンドリーなシステムへと進化させます。

主な改善点

以下の問題点を修正しました:
  • 終了処理の不備:「0」を選択してもループが継続し、プログラムが終了しなかった。
  • データ永続性の欠如:プログラム終了時にすべてのデータが失われていた。
  • 入力検証の不足:年齢や電話番号などの無効な値も受け入れていた。
  • 誤操作防止の不足:削除や全消去操作に確認ダイアログがなかった。
  • 柔軟性の欠如:連絡先の編集時、すべての項目を再入力する必要があった。

構造体の定義

連絡先情報は構造体としてモデル化されています。
struct Contact {
    std::string name;           // 名前
    int gender;                 // 性別(1: 男性, 2: 女性)
    int age;                    // 年齢
    std::string phone;          // 電話番号
    std::string address;        // 住所
};

struct AddressBook {
    Contact entries[1000];      // 最大1000件まで保存可能
    int count;                  // 現在の登録数
};

ファイルからの読み込みと保存

アプリ起動時に自動でデータを復元し、終了時に変更内容を保存します。
void loadFromStorage(AddressBook* book) {
    std::ifstream file("contacts.dat");
    if (!file.is_open()) return;

    file >> book->count;
    file.ignore();

    for (int i = 0; i < book->count; ++i) {
        std::getline(file, book->entries[i].name);
        file >> book->entries[i].gender;
        file >> book->entries[i].age;
        file.ignore();
        std::getline(file, book->entries[i].phone);
        std::getline(file, book->entries[i].address);
    }
    file.close();
}

void saveToStorage(const AddressBook* book) {
    std::ofstream file("contacts.dat");
    file << book->count << "\n";
    for (int i = 0; i < book->count; ++i) {
        file << book->entries[i].name << "\n"
             << book->entries[i].gender << "\n"
             << book->entries[i].age << "\n"
             << book->entries[i].phone << "\n"
             << book->entries[i].address << "\n";
    }
    file.close();
}

入力バリデーション関数

安全な入力を保証するために、文字列形式のチェックを行います。
bool isValidInteger(const std::string& input) {
    if (input.empty()) return false;
    for (char c : input) {
        if (c < '0' || c > '9') return false;
    }
    return true;
}

bool isValidPhoneNumber(const std::string& number) {
    if (number.length() < 7 || number.length() > 11) return false;
    for (char c : number) {
        if (c < '0' || c > '9') return false;
    }
    return true;
}

int convertToInt(const std::string& str) {
    int result = 0;
    for (char c : str) {
        result = result * 10 + (c - '0');
    }
    return result;
}

部分更新に対応した編集機能

ユーザーは特定のフィールドだけを選んで更新できます。
void editContact(AddressBook* book) {
    std::cout << "編集する名前を入力してください: ";
    std::string target;
    std::cin >> target;

    int index = findContactIndex(book, target);
    if (index == -1) {
        std::cout << "該当する連絡先が見つかりません。\n";
        system("pause");
        system("cls");
        return;
    }

    displayContact(book->entries[index]);

    std::cout << "\nどの情報を変更しますか?\n";
    std::cout << "1. 名前 2. 性別 3. 年齢 4. 電話 5. 住所 6. 全て 0. キャンセル\n";

    std::string choiceStr;
    int choice;
    while (true) {
        std::cin >> choiceStr;
        if (isValidInteger(choiceStr)) {
            choice = convertToInt(choiceStr);
            if (choice >= 0 && choice <= 6) break;
        }
        std::cout << "無効な入力です。再入力してください: ";
    }

    Contact& current = book->entries[index];
    std::string temp;

    switch (choice) {
        case 1:
            std::cout << "新しい名前: "; std::cin >> current.name; break;
        case 2:
            while (true) {
                std::cout << "性別 (1=男, 2=女): "; std::cin >> temp;
                if (isValidInteger(temp) && (temp == "1" || temp == "2")) {
                    current.gender = convertToInt(temp); break;
                }
            }
            break;
        case 3:
            while (true) {
                std::cout << "年齢: "; std::cin >> temp;
                if (isValidInteger(temp)) {
                    int ageVal = convertToInt(temp);
                    if (ageVal > 0 && ageVal < 150) {
                        current.age = ageVal; break;
                    }
                }
                std::cout << "有効な年齢を入力してください。\n";
            }
            break;
        case 4:
            while (true) {
                std::cout << "電話番号(7-11桁): "; std::cin >> temp;
                if (isValidPhoneNumber(temp)) {
                    current.phone = temp; break;
                }
                std::cout << "無効な形式です。\n";
            }
            break;
        case 5:
            std::cout << "住所: "; std::cin >> current.address; break;
        case 6:
            performFullUpdate(book, index); break;
        case 0:
            std::cout << "編集をキャンセルしました。\n"; break;
    }

    if (choice != 0) std::cout << "更新が完了しました。\n";
    system("pause");
    system("cls");
}

安全な削除とクリア操作

重要な操作に対しては、ユーザーによる明示的な確認を求めます。
void removeContact(AddressBook* book) {
    std::cout << "削除する名前を入力: ";
    std::string name; std::cin >> name;
    int pos = findContactIndex(book, name);

    if (pos == -1) {
        std::cout << "該当なし。\n";
    } else {
        std::cout << "本当に削除しますか?(y/n): ";
        char confirm; std::cin >> confirm;
        if (confirm == 'y' || confirm == 'Y') {
            for (int i = pos; i < book->count - 1; ++i) {
                book->entries[i] = book->entries[i + 1];
            }
            --book->count;
            std::cout << "削除しました。\n";
        } else {
            std::cout << "操作を中止しました。\n";
        }
    }
    system("pause");
    system("cls");
}

void clearAllContacts(AddressBook* book) {
    if (book->count == 0) {
        std::cout << "既に空です。\n";
    } else {
        std::cout << "本当に全件削除しますか?(y/n): ";
        char response; std::cin >> response;
        if (response == 'y' || response == 'Y') {
            book->count = 0;
            std::cout << "すべて削除しました。\n";
        } else {
            std::cout << "操作を中止しました。\n";
        }
    }
    system("pause");
    system("cls");
}

メインループの改善

文字列入力を受け取り、数値変換を行い、範囲外の選択を無効にするようにしました。
int main() {
    AddressBook book{ {}, 0 };
    loadFromStorage(&book);

    std::string input;
    int selection;

    while (true) {
        showMainMenu();
        std::cin >> input;

        if (!isValidInteger(input)) {
            selection = -1;
        } else {
            selection = convertToInt(input);
        }

        switch (selection) {
            case 1: addNewContact(&book); break;
            case 2: displayAllContacts(&book); break;
            case 3: removeContact(&book); break;
            case 4: searchContact(&book); break;
            case 5: editContact(&book); break;
            case 6: clearAllContacts(&book); break;
            case 0:
                saveToStorage(&book);
                std::cout << "ご利用ありがとうございました!\n";
                system("pause");
                return 0;
            default:
                std::cout << "無効な選択です。\n";
                system("pause");
                system("cls");
                break;
        }
    }
}

開発戦略の考察

他人のコードを改良する際、最初から詳細を読むのではなく、まず動作させて挙動を観察することが効果的です。異常入力に対する反応やUIの流れを確認することで、根本的な問題点を素早く把握できます。特に入力検証と編集ロジックの再設計は時間と注意力を要しますが、エラーハンドリングを適切に行うことで信頼性が大幅に向上します。

タグ: C++ データ永続化 入力検証 構造体 ファイルI/O

8月10日 15:05 投稿