c++/알고리즘

C++ 알고리즘) 함수, STL의 기초 및 디자인 패턴

eclipse2 2026. 3. 6. 20:57

함수들의 정리

  1. 수학 <math> 사용

함수 기능 예시 (3.4 기준)

ceil(x) 올림 (천장) ceil(3.4) → 4.0
floor(x) 내림 (바닥) floor(3.4) → 3.0
round(x) 반올림 round(3.4) → 3.0
abs(x) 절댓값 abs(-5) → 5
pow(base, exp) 거듭제곱 ($base^{exp}$) pow(2, 3) → 8.0
sqrt(x) 제곱근 ($\sqrt{x}$) sqrt(16) → 4.0
  1. 알고리즘 함수

함수 기능 사용 예시

sort(start, end) 정렬 (오름차순) sort(v.begin(), v.end())
reverse(start, end) 거꾸로 뒤집기 reverse(v.begin(), v.end())
max(a, b) / min(a, b) 둘 중 큰 값 / 작은 값 max(10, 20) → 20
max_element(s, e) 범위 내 최대값의 위치 *max_element(v.begin(), v.end())
min_element(s, e) 범위 내 최소값의 위치 *min_element(v.begin(), v.end())
find(s, e, val) 특정 값의 위치 찾기 find(v.begin(), v.end(), 5)
  1. 문자열 함수

함수 기능 사용법 및 예시

length() / size() 문자열 길이 반환 s.length()
substr(pos, len) 부분 문자열 추출 s.substr(0, 3) (0번부터 3글자)
find(str) 특정 문자열 위치 찾기 s.find("abc") (못 찾으면 string::npos)
stoi(str) 문자열 → 정수 변환 stoi("123") → 123
to_string(val) 숫자 → 문자열 변환 to_string(10) → "10"
push_back(char) 맨 뒤에 문자 하나 추가 s.push_back('A')
  1. Vector

함수 설명

push_back(val) 벡터의 맨 뒤에 요소 추가
pop_back() 벡터의 맨 뒤 요소 삭제
insert(pos, val) 특정 위치(pos)에 요소 삽입
erase(pos) 특정 위치(pos)의 요소 삭제
clear() 모든 요소 삭제 (크기를 0으로 만듦)
emplace_back(args) 요소를 생성하여 맨 뒤에 삽입 (성능상 push_back보다 유리할 때가 있음)

함수 설명

at(index) 해당 인덱스의 요소 접근 (범위 검사를 수행하여 안전함)
operator[] 해당 인덱스의 요소 접근 (범위 검사 없음, 빠름)
front()/begin() 첫 번째 요소 참조
back()/end() 마지막 요소 참조

함수 설명

size() 현재 담긴 요소의 개수 반환
empty() 벡터가 비어있는지 확인 (true/false)
capacity() 할당된 메모리 공간의 크기 반환
reserve(n) $n$개의 요소를 담을 메모리를 미리 예약 (성능 최적화)
resize(n) 벡터의 크기를 $n$으로 변경
   

STL 기초

  • Standard Template Library는 c++ 표준 라이브러리의 일부로, 컨테이너, 알고리즘, 반복자 등의 템플릿 기반 구성요소를 포함함
  1. 컨테이너

데이터를 담는 자료구조

모든 컨테이너는 템플릿으로 구현 다양한 데이터를 저장할 수 있음

모든 컨테이너는 메모리 관리를 내부적으로 하고 사용시 메모리 해제를 직접 고려하지 않아도 된다.

대부분 컨테이너는 반복자를 제공한다. 따라서 내부 구현을 몰라도 동일한 방식으로 컨테이너를 순회할 수 있다.

  • 벡터 - 배열과 매우 유사한 컨테이너
    • 템플릿 클래스로 구현되어 특정 타입에 종속되지 않음
    • 삽입되는 원소 개수에 따라 내부 배열의 크기가 자동으로 조정
    • 임의 접근이 가능 (인덱스를 통해)
    • 삽입 / 삭제는 맨 뒤에 하는 게 좋다
#include <vector>
using namespace std;

// 1. 기본 생성 및 초기화 없이 선언
vector<int> vec1;

// 2. 특정 크기와 초기값으로 벡터 선언
vector<int> vec2(5, 10); // 크기 5, 모든 원소가 10으로 초기화

// 3. 리스트 초기화로 벡터 선어
vector<int> vec3 = {1, 2, 3, 4, 5};

//4. 기존 벡터 복사
vector<int> vec4(vec3);

//5. 2차원 배열 벡터
vector<int> vec2D(3, vector<int>(4,7);

vec1.push_back(10);
vec1.push_back(20);
vec1.push_back(30); 
// 10 20 30
cout << vec1.size(); //3
vec1.pop_back(); //10 20
cout << vec1.size(); //2
vec1.pop_back(); // 10
cout << vec1.size(); //1
vec1.pop_back(); // 


//메인 함수 생략
  • 맵 - 특정 키를 활용해서 값을 찾는 기능
    • 키-값 쌍은 map<const Key, Value>map1; 형태로 저장
    • 키값을 기준으로 내부 데이터가 자동으로 정렬
    • 중복된 키값을 허용하지 않음
#include <iostream>
#include <map>

using namespace std;

// 정수 키와 문자열 값을 저장하는 map 예제
int main() {
    map<int, string> studentMap;

    // 요소 추가
    studentMap[101] = "Alice";
    studentMap[102] = "Bob";
    studentMap[103] = "Charlie";
    //요소 추가 방식2
    studentMap.insert({104, "Mark"});
    //요소 추가 방식 3
    studentMap.insert(make_pair(100, "Park"));
    //요소 추가 방식 4
    studentMap[106] = "Kim";

    // 요소 출력
    for (const auto& pair : studentMap) {
        cout << "ID: " << pair.first << ", Name: " << pair.second << endl;
    }
    int key=100;
    auto it = studentMap.find(key);
    if(it != studentMap.end()) {
	    cout << "Key : " << it -> first << "Value: " << it->second;
	    
    } else {
    cout << "Key not found";
    

    return 0;
}

객체지향적 설계는 단순 문법이 아니라 클래스를 어떻게 효과적으로 설계하는지가 핵심이다.

이에 대한 기준은 응집도와 결합도에 있다.

  1. 응집도
  • 응집도는 클래스 또는 모듈 내부의 구성 요소들이 얼마나 밀접하게 관련되어 있는지를 나타낸다.
  1. 결합도
  • 결합도는 각 모듈들의 서로 얼마나 의존하는 지 나타낸다.
  • 즉 결합도는 낮은 게 좋은 코드다. 이는 인터페이스를 사용에 결합도를 감소시킬 수 있다.

SOLID 원칙

  1. SRP 단일 책임 원칙 - 역할과 책임을 분리해 수정을 최소화 한다.
  2. OCP 개방 폐쇄 원칙 - 확장에는 열려 있고 수정에는 닫혀 있어야 함, 따라서 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있도록 하는 것.
  3. LSP 리스 코프 치환 원칙 - 자식 클래스는 부모 클래스에서 기대되는 행동을 보장해야 함
  4. ISP 인터페이스 분리 원칙 - 각 클래스에는 불필요한 메서드를 구현하지 말아야 함
  5. DIP 의존 역전 원칙 - 인터페이스나 추상 클래스를 사용해 결합도를 낮추는 것

실습 과제 2:

 

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>

using namespace std;

struct Movie {
    string title;
    double rating;
};

// TODO: MovieProcessor 추상 클래스 정의
// 순수 가상 함수 process를 선언해야 합니다.
// process는 vector<Movie>&를 인자로 받아야 합니다.
class MovieProcessor
{
public:
	virtual void process(vector<Movie>& movies) = 0; // 순수 가상 함수
	virtual ~MovieProcessor() {} // 가상 소멸자
};

// 기본 영화 관리자
class MovieManager {
private:
    vector<Movie> movies;
    map<string, double> movieMap;

public:
    MovieManager() {
        // 초기 데이터 설정
        movies = {
            {"Inception", 9.0},
            {"Interstellar", 8.6},
            {"The Dark Knight", 9.1},
            {"Memento", 8.4}
        };

        for (const auto& movie : movies) {
            movieMap[movie.title] = movie.rating;
        }
    }

    void printMovies() {
        cout << "영화 목록:\n";
        for (const auto& movie : movies) {
            cout << "제목: " << movie.title << ", 평점: " << movie.rating << "\n";
        }
    }

    void findMovie(const string& title) {
        auto it = movieMap.find(title);
        if (it != movieMap.end()) {
            cout << "영화 제목: " << it->first << ", 평점: " << it->second << "\n";
        }
        else {
            cout << "해당 영화는 목록에 없습니다.\n";
        }
    }

    // MovieProcessor를 사용하여 기능 확장
    void processMovies(MovieProcessor& processor) {
        processor.process(movies);
    }
};


// TODO: compareMovies 함수 정의
// Movies 객체의 대소를 비교하는 함수 입니다.
// STL에서 제공하는 sort 함수를 활용해서 vector<Movie>를 멤버변수 rating 기준 내림차순으로 정렬 할 수 있도록 해야 합니다. 
void compareMovies(vector<Movie>& movies) {
    sort(movies.begin(), movies.end(), [](const Movie& a, const Movie& b) {
        return a.rating > b.rating; // 내림차순 정렬
    });
}

// TODO: RatingSorter 클래스 정의
// MovieProcessor를 상속받아 구현합니다.
// process 는 vector<Movie>&를 인자로 받으며 영화목록이 저장되어 있습니다.
// process는 인자로 받은 벡터는 내림차순으로 정렬하고, 정렬된 영화목록을 출력합니다.
class RatingSorter : public MovieProcessor {
public:
    void process(vector<Movie>& movies) override {
        compareMovies(movies); // 영화 목록을 평점 기준으로 내림차순 정렬
        cout << "평점 기준 내림차순으로 정렬된 영화 목록:\n";
        for (const auto& movie : movies) {
            cout << "제목: " << movie.title << ", 평점: " << movie.rating << "\n";
        }
    }
};

// 구체 클래스: 특정 평점 이상의 영화 필터링
class RatingFilter : public MovieProcessor {
private:
    double minRating;

public:
    explicit RatingFilter(double minRating) : minRating(minRating) {}

    void process(vector<Movie>& movies) {
        cout << "평점 " << minRating << " 이상인 영화 목록:\n";
        for (const auto& movie : movies) {
            if (movie.rating >= minRating) {
                cout << "제목: " << movie.title << ", 평점: " << movie.rating << "\n";
            }
        }
    }
};

int main() {
    MovieManager manager;

    cout << "1. 영화 목록 출력\n";
    manager.printMovies();

    cout << "\n2. 영화 검색 (예: Interstellar)\n";
    manager.findMovie("Interstellar");

    cout << "\n3. 평점 기준 정렬 및 출력\n";
    RatingSorter sorter;
    manager.processMovies(sorter);

    cout << "\n4. 평점 8.5 이상인 영화 필터링 및 출력\n";
    RatingFilter filter(8.5);
    manager.processMovies(filter);

    return 0;
}

디자인 패턴

  1. 생성 패턴
    1. 새로운 것을 만들어내는 방법과 관련된 패턴
  2. 구조 패턴
    1. 여러 부품을 어떻게 조립하고 연결하는 방법에 대한 패턴
  3. 행동 패턴
    1. 부품이 서로 어떻게 상호작용 할지에 대한 패턴

생성 패턴: 싱글톤

클래스에 인스턴스가 하나만 있도록 하면서 해당 인스턴스에 전역 접근 지점을 제공한다. 따라서 새로운 객체를 생성했다고 해도 이미 만든 객체를 받게 된다.

 

싱글톤 패턴 예시 코드:

#include <iostream>
using namespace std;

class Airplane {
private:
    static Airplane* instance; // 유일한 비행기 객체를 가리킬 정적 포인터
    int positionX;             // 비행기의 X 위치
    int positionY;             // 비행기의 Y 위치

    // private 생성자: 외부에서 객체 생성 금지
    Airplane() : positionX(0), positionY(0) {
        cout << "Airplane Created at (" << positionX << ", " << positionY << ")" << endl;
    }

public:
    // 복사 생성자와 대입 연산자를 삭제하여 복사 방지
    Airplane(const Airplane&) = delete;
    Airplane& operator=(const Airplane&) = delete;

    // 정적 메서드: 유일한 비행기 인스턴스를 반환
    static Airplane* getInstance() {
        if (instance == nullptr) {
            instance = new Airplane();
        }
        return instance;
    }

    // 비행기 위치 이동
    void move(int deltaX, int deltaY) {
        positionX += deltaX;
        positionY += deltaY;
        cout << "Airplane moved to (" << positionX << ", " << positionY << ")" << endl;
    }

    // 현재 위치 출력
    void getPosition() const {
        cout << "Airplane Position: (" << positionX << ", " << positionY << ")" << endl;
    }
};

// 정적 멤버 초기화
Airplane* Airplane::instance = nullptr;

// 메인 함수 (사용 예시)
int main() {
    // 유일한 비행기 인스턴스를 가져옴
    Airplane* airplane = Airplane::getInstance();
    airplane->move(10, 20);  // 비행기 이동
    airplane->getPosition();

    // 또 다른 요청도 같은 인스턴스를 반환
    Airplane* sameAirplane = Airplane::getInstance();
    sameAirplane->move(-5, 10); // 비행기 이동
    sameAirplane->getPosition();

    return 0;
}

 

구조 패턴: 데코레이터 패턴

데코레이터는 객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴이다.

 

데코레이터 패턴 예시 코드:

#include <iostream>
#include <string>

using namespace std;

// **추상 컴포넌트 (Component): Pizza**
// - 피자 객체의 기본 구조를 정의하는 인터페이스입니다.
// - 모든 피자는 이름(`getName`)과 가격(`getPrice`)을 가져야 합니다.
class Pizza {
public:
    virtual ~Pizza() {}
    virtual string getName() const = 0;  // 피자의 이름 반환
    virtual double getPrice() const = 0; // 피자의 가격 반환
};

// **구체 컴포넌트 (Concrete Component): BasicPizza**
// - 기본 피자 클래스입니다.
// - 피자의 기본 베이스(이름과 가격)를 구현합니다.
class BasicPizza : public Pizza {
public:
    string getName() const {
        return "Basic Pizza"; // 기본 피자의 이름
    }
    double getPrice() const {
        return 5.0; // 기본 피자의 가격
    }
};

// **데코레이터 추상 클래스 (Decorator): PizzaDecorator**
// - 기존 피자의 기능을 확장하기 위한 데코레이터의 기본 구조를 정의합니다.
// - 내부적으로 `Pizza` 객체를 감싸며, 이름과 가격에 추가적인 기능을 제공합니다.
class PizzaDecorator : public Pizza {
protected:
    Pizza* pizza; // 기존의 피자 객체를 참조합니다.
public:
    // 데코레이터는 피자 객체를 받아서 감쌉니다.
    PizzaDecorator(Pizza* p) : pizza(p) {}
    
    // 소멸자에서 내부 피자 객체를 삭제합니다.
    virtual ~PizzaDecorator() {
        delete pizza;
    }
};

// **구체 데코레이터 (Concrete Decorators): Cheese, Pepperoni, Olive**
// - 각각의 토핑 데코레이터는 `PizzaDecorator`를 상속받아 이름과 가격을 확장합니다.

// 치즈 토핑 데코레이터
class CheeseDecorator : public PizzaDecorator {
public:
    CheeseDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const {
        // 기존 피자의 이름에 " + Cheese"를 추가
        return pizza->getName() + " + Cheese";
    }
    double getPrice() const {
        // 기존 피자의 가격에 치즈 추가 비용 1.5를 더함
        return pizza->getPrice() + 1.5;
    }
};

// 페퍼로니 토핑 데코레이터
class PepperoniDecorator : public PizzaDecorator {
public:
    PepperoniDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const {
        // 기존 피자의 이름에 " + Pepperoni"를 추가
        return pizza->getName() + " + Pepperoni";
    }
    double getPrice() const {
        // 기존 피자의 가격에 페퍼로니 추가 비용 2.0을 더함
        return pizza->getPrice() + 2.0;
    }
};

// 올리브 토핑 데코레이터
class OliveDecorator : public PizzaDecorator {
public:
    OliveDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const {
        // 기존 피자의 이름에 " + Olive"를 추가
        return pizza->getName() + " + Olive";
    }
    double getPrice() const {
        // 기존 피자의 가격에 올리브 추가 비용 0.7을 더함
        return pizza->getPrice() + 0.7;
    }
};

// **클라이언트 코드**
// - 피자와 데코레이터를 조합하여 최종 피자를 생성하고, 정보를 출력합니다.
int main() {
    // 1. 기본 피자를 생성합니다.
    Pizza* pizza = new BasicPizza();

    // 2. 치즈 토핑을 추가합니다.
    pizza = new CheeseDecorator(pizza);

    // 3. 페퍼로니 토핑을 추가합니다.
    pizza = new PepperoniDecorator(pizza);

    // 4. 올리브 토핑을 추가합니다.
    pizza = new OliveDecorator(pizza);

    // 5. 최종 피자 정보 출력
    cout << "Pizza: " << pizza->getName() << endl; // 피자의 이름 출력
    cout << "Price: $" << pizza->getPrice() << endl; // 피자의 가격 출력

    // 6. 메모리 해제
    delete pizza;

    return 0;
}

 

행동 패턴: 옵서버 패턴

옵저버 패턴은 당신이 여러 객체에 자신이 관찰 중인 객체에 발생하는 모든 이벤트에 대하여 알리는 구독 메커니즘을 정의할 수 있도록 하는 행동 디자인 패턴이다.

 

옵저버 패턴 예시 코드:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// Observer 인터페이스
// - Observer 패턴에서 상태 변화를 알림받는 객체들의 공통 인터페이스
// - Observer들은 이 인터페이스를 구현하여 `update` 메서드를 통해 데이터를 전달받음
class Observer {
public:
    virtual ~Observer() = default;               // 가상 소멸자
    virtual void update(int data) = 0;           // 데이터 업데이트 메서드 (순수 가상 함수)
};

// Subject 클래스 (엑셀 시트 역할)
// - 데이터의 상태 변화를 관리하며, 모든 등록된 Observer들에게 변경 사항을 알림
class ExcelSheet {
private:
    vector<Observer*> observers;                 // Observer들을 저장하는 리스트
    int data;                                    // 현재 데이터 상태

public:
    ExcelSheet() : data(0) {}                    // 생성자: 초기 데이터 값은 0

    // Observer 등록 메서드
    // - 새로운 Observer를 등록하여 변경 사항 알림을 받을 수 있도록 추가
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

    // 데이터 변경 알림 메서드
    // - 등록된 모든 Observer들의 `update` 메서드를 호출하여 데이터 변경 사항을 알림
    void notify() {
        for (Observer* observer : observers) {
            observer->update(data);              // 각 Observer에게 데이터를 전달
        }
    }

    // 데이터 설정 메서드
    // - 데이터를 변경하고 변경 사항을 모든 Observer에게 알림
    void setData(int newData) {
        data = newData;                          // 새로운 데이터로 갱신
        cout << "ExcelSheet: Data updated to " << data << endl;
        notify();                                // Observer들에게 알림
    }
};

// 구체적인 Observer 클래스: BarChart (막대 차트)
// - 데이터를 막대 그래프로 표현
class BarChart : public Observer {
public:
    void update(int data) {                      // 데이터 업데이트 시 호출됨
        cout << "BarChart: Displaying data as vertical bars: ";
        for (int i = 0; i < data; ++i) {
            cout << "|";                         // 데이터 값만큼 막대 출력
        }
        cout << " (" << data << ")" << endl;
    }
};

// 구체적인 Observer 클래스: LineChart (라인 차트)
// - 데이터를 선형 그래프로 표현
class LineChart : public Observer {
public:
    void update(int data) {                      // 데이터 업데이트 시 호출됨
        cout << "LineChart: Plotting data as a line: ";
        for (int i = 0; i < data; ++i) {
            cout << "-";                         // 데이터 값만큼 선 출력
        }
        cout << " (" << data << ")" << endl;
    }
};

// 구체적인 Observer 클래스: PieChart (파이 차트)
// - 데이터를 파이 그래프로 표현
class PieChart : public Observer {
public:
    void update(int data) {                      // 데이터 업데이트 시 호출됨
        cout << "PieChart: Displaying data as a pie chart slice: ";
        cout << "Pie [" << data << "%]" << endl; // 데이터 값 출력 (가정: % 비율로 표현)
    }
};

// 메인 함수
int main() {
    // Subject 생성
    ExcelSheet excelSheet;                       // 데이터를 관리하는 엑셀 시트 객체 생성

    // Observer 객체 생성 (각 차트 객체)
    BarChart* barChart = new BarChart();         // 막대 차트 생성
    LineChart* lineChart = new LineChart();      // 라인 차트 생성
    PieChart* pieChart = new PieChart();         // 파이 차트 생성

    // Observer 등록
    // - 각 차트(Observer)를 엑셀 시트(Subject)에 등록
    excelSheet.attach(barChart);
    excelSheet.attach(lineChart);
    excelSheet.attach(pieChart);

    // 데이터 변경 테스트
    // - 데이터를 변경하면 등록된 모든 Observer들이 알림을 받고 화면에 갱신
    excelSheet.setData(5);                       // 데이터 변경: 5
    excelSheet.setData(10);                      // 데이터 변경: 10

    // 메모리 해제
    // - 동적 할당된 Observer(차트) 객체 삭제
    delete barChart;
    delete lineChart;
    delete pieChart;

    return 0;
}

디자인 패턴 실습 과제 코드:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// 고객 인터페이스 (Observer 역할)
class Customer {
public:
    virtual void update(const string& status) = 0; // 순수 가상 함수
};

// 일반 고객 클래스
class RegularCustomer : public Customer {
private:
    string name;
public:
    RegularCustomer(const string& name) : name(name) {}

    void update(const string& status) {
        cout << "Regular customer " << name << " received update: " << status << endl;
    }
};

// TODO: VIP 고객 클래스 (Customer를 상속받아 구현)
// 요구 사항:
// - 고객 이름을 저장하는 멤버 변수 `name`을 추가하세요.
// - 생성자에서 이름을 초기화하세요.
// - `update` 메서드를 구현하여 "VIP customer [이름] received VIP update: [배송 상태]" 형식으로 출력되도록 하세요.
class VIPCustomer : public Customer {
private:
    string name;
public:
    VIPCustomer(const string& name) : name(name) {}
    void update(const string& status) {
        cout << "VIP customer " << name << " received VIP update: " << status << endl;
	}
};
class BusinessCustomer : public Customer {
private:
	string name;
public:
    BusinessCustomer(const string& name) : name(name) {}
    void update(const string& status) {
        cout << "Business customer " << name << " received business update: " << status << endl;
    }
};

// TODO: 배송 회사 클래스 (DeliveryService)
// 요구 사항:
// - `customers`라는 고객 리스트를 저장하는 멤버 변수를 추가하세요.
// - `currentStatus`라는 현재 배송 상태를 저장하는 멤버 변수를 추가하세요.
// - 고객을 추가하는 `addCustomer` 메서드를 구현하세요.
// - 고객을 제거하는 `removeCustomer` 메서드를 구현하세요.
// - 배송 상태를 업데이트하고 모든 고객에게 알리는 `updateStatus` 메서드를 구현하세요.
// - 등록된 모든 고객에게 상태를 전달하는 `notifyCustomers` 메서드를 구현하세요.
class DeliveryService {
private:
    vector<Customer*> customers; // 고객 리스트
	string currentStatus; // 현재 배송 상태
public:
    void addCustomer(Customer* customer) {
        customers.push_back(customer);
    }
    void removeCustomer(Customer* customer) {
        customers.erase(remove(customers.begin(), customers.end(), customer), customers.end());
    }
    void updateStatus(const string& status) {
        currentStatus = status;
        notifyCustomers();
    }
    void notifyCustomers() {
        for (Customer* customer : customers) {
            customer->update(currentStatus);
        }
	}
};

// Main 함수
int main() {
    DeliveryService service;

    // 고객 객체 생성
    RegularCustomer* customer1 = new RegularCustomer("Alice");
    VIPCustomer* customer2 = new VIPCustomer("Bob");
    BusinessCustomer* customer3 = new BusinessCustomer("CompanyX");

    // 고객 등록
    service.addCustomer(customer1);
    service.addCustomer(customer2);
    service.addCustomer(customer3);

    // 배송 상태 업데이트 및 알림
    cout << "Updating status: 배송 준비 중" << endl;
    service.updateStatus("배송 준비 중");

    cout << "\nUpdating status: 배송 완료" << endl;
    service.updateStatus("배송 완료");

    // 메모리 해제
    delete customer1;
    delete customer2;
    delete customer3;

    return 0;
}