C++ 기반 동적 주소록 시스템 개선 및 재구성

기존 시스템 분석

기존 주소록 관리 프로그램은 C++로 구현되었으나, 고정 크기 배열(MAX = 1000)을 사용하여 확장성이 부족했으며, 입력 검증이 미흡해 비정상 종료 위험이 존재했습니다. 또한 system("cls") 및 system("pause")를 사용해 Windows에 종속적이었고, 데이터가 메모리에만 저장되어 프로그램 종료 시 모든 정보가 소실되었습니다. 특히 이름 기반 검색/삭제 방식은 동명이인 처리에 한계가 있었습니다.

핵심 개선 사항

  • 동적 메모리 관리: std::vector<Person>로 전환하여 용량 제한 해소
  • 입력 검증 강화: 나이(0~150), 성별(1/2), 공백 포함 문자열 처리를 위한 getline() 적용
  • 데이터 영속성: 텍스트 파일(contacts.txt)을 통한 자동 저장/복원 기능 추가
  • 크로스 플랫폼 호환성: system() 대신 표준 라이브러리 기반의 화면 정리 및 일시정지 구현
  • 동명이인 처리: 이름으로 검색 시 인덱스 목록 제공 및 사용자 선택 메커니즘 도입
  • 부분 수정 기능: 필드별 선택적 업데이트 지원 (빈 입력 시 기존 값 유지)

재구성된 코드 구조

헤더 파일 (addressbook.h)

#pragma once
#include <string>
#include <vector>

enum class Sex { Male = 1, Female = 2 };

struct Contact {
    std::string name;
    Sex gender;
    int age;
    std::string phoneNumber;
    std::string homeAddress;
};

class ContactManager {
private:
    std::vector<Contact> entries;
    const std::string dataFile = "contacts.txt";

    Sex promptGender() const;
    std::vector<size_t> searchByName(const std::string& target) const;

public:
    ContactManager();
    ~ContactManager();

    void createEntry();
    void listAll() const;
    void removeEntry();
    void searchEntry() const;
    void updateEntry();
    void eraseAll();

    void loadEntries();
    void persistEntries() const;

    static void waitForInput();
    static void refreshDisplay();
};

구현 파일 (addressbook.cpp)

#include "addressbook.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include <limits>
using namespace std;

ContactManager::ContactManager() { loadEntries(); }
ContactManager::~ContactManager() { persistEntries(); }

Sex ContactManager::promptGender() const {
    int input;
    while (true) {
        cout << "성별 (1=남, 2=여): ";
        cin >> input;
        if (!cin.fail() && (input == 1 || input == 2)) {
            return static_cast<Sex>(input);
        }
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        cout << "유효하지 않은 입력입니다." << endl;
    }
}

vector<size_t> ContactManager::searchByName(const string& target) const {
    vector<size_t> matches;
    for (size_t i = 0; i < entries.size(); ++i) {
        if (entries[i].name == target) matches.push_back(i);
    }
    return matches;
}

void ContactManager::createEntry() {
    Contact newContact;
    cout << "이름: ";
    cin.ignore();
    getline(cin, newContact.name);
    
    newContact.gender = promptGender();
    
    cout << "나이 (0-150): ";
    while (!(cin >> newContact.age) || newContact.age < 0 || newContact.age > 150) {
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        cout << "유효한 나이를 입력하세요: ";
    }
    
    cout << "전화번호: ";
    cin.ignore();
    getline(cin, newContact.phoneNumber);
    
    cout << "주소: ";
    getline(cin, newContact.homeAddress);
    
    entries.push_back(newContact);
    cout << "등록 완료!" << endl;
    waitForInput();
    refreshDisplay();
}

void ContactManager::listAll() const {
    if (entries.empty()) {
        cout << "등록된 연락처가 없습니다." << endl;
    } else {
        for (size_t i = 0; i < entries.size(); ++i) {
            const auto& c = entries[i];
            cout << "[" << i << "] " 
                 << c.name << " | "
                 << (c.gender == Sex::Male ? "남" : "여") << " | "
                 << c.age << "세 | "
                 << c.phoneNumber << " | "
                 << c.homeAddress << endl;
        }
    }
    waitForInput();
    refreshDisplay();
}

// 삭제/검색/수정 함수는 동명이인 처리 로직 포함
// 파일 입출력은 각 필드를 별도 줄로 저장

void ContactManager::persistEntries() const {
    ofstream output(dataFile);
    for (const auto& contact : entries) {
        output << contact.name << '\n'
                << static_cast<int>(contact.gender) << '\n'
                << contact.age << '\n'
                << contact.phoneNumber << '\n'
                << contact.homeAddress << '\n';
    }
}

void ContactManager::waitForInput() {
    cout << "\n계속하려면 Enter 키를 누르세요...";
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
    cin.get();
}

void ContactManager::refreshDisplay() {
    cout << string(50, '\n'); // 간이 화면 정리
}

메인 함수 (main.cpp)

#include "addressbook.h"
#include <iostream>
using namespace std;

void displayMenu() {
    cout << "\n========== 연락처 관리 시스템 ==========\n"
         << "1. 새 연락처 등록\n"
         << "2. 전체 목록 보기\n"
         << "3. 연락처 삭제\n"
         << "4. 연락처 검색\n"
         << "5. 정보 수정\n"
         << "6. 전체 삭제\n"
         << "0. 종료\n"
         << "====================================\n";
}

int main() {
    ContactManager manager;
    int option;

    while (true) {
        displayMenu();
        cout << "선택: ";
        cin >> option;

        if (cin.fail()) {
            cin.clear();
            cin.ignore(numeric_limits<streamsize>::max(), '\n');
            cout << "숫자만 입력 가능합니다.\n";
            continue;
        }

        switch (option) {
            case 1: manager.createEntry(); break;
            case 2: manager.listAll(); break;
            case 3: manager.removeEntry(); break;
            case 4: manager.searchEntry(); break;
            case 5: manager.updateEntry(); break;
            case 6: manager.eraseAll(); break;
            case 0: 
                cout << "프로그램을 종료합니다.\n";
                return 0;
            default:
                cout << "0~6 사이의 숫자를 입력하세요.\n";
        }
    }
}

주요 기술적 개선점

코드는 클래스 기반 설계로 재구성되어 캡슐화를 달성했으며, const 올바른 사용과 명확한 함수 네이밍(isExit → searchByName)으로 가독성을 향상시켰습니다. 삭제 로직에서 벡터의 erase() 메서드를 사용해 메모리 안전성을 확보했고, 파일 입출력 시 각 필드를 별도 줄로 저장함으로써 파싱 오류를 최소화했습니다. 특히 동명이인 처리를 위해 검색 결과 인덱스 목록을 반환하고 사용자에게 선택지를 제공하는 방식으로 실제 사용성 문제를 해결했습니다.

태그: C++ STL vector file I/O input validation

10월 3일 08:33에 게시됨