프로그래밍/C++

객체 지향 프로그래밍(OOP)의 핵심 개념: 클래스, 객체, 생성자, 소멸자, 상속, 다형성

shimdh 2025. 2. 2. 13:11
728x90

1. 클래스와 객체

1.1 클래스(Class)

클래스는 객체를 생성하기 위한 설계도입니다. 클래스는 속성(Attributes)과 행동(Methods)을 정의하며, 이를 통해 객체의 상태와 동작을 결정합니다. 클래스를 잘 설계하면 코드의 재사용성과 유지보수성이 크게 향상됩니다.

class Car {
public:
    // 속성
    std::string brand;
    int year;

    // 메서드
    void displayInfo() {
        std::cout << "Brand: " << brand << ", Year: " << year << std::endl;
    }
};

위 예제에서 Car 클래스는 brandyear라는 속성을 가지며, displayInfo() 메서드를 통해 자동차 정보를 출력합니다. 이 클래스를 기반으로 여러 객체를 생성할 수 있습니다.

1.2 객체(Object)

객체는 클래스의 인스턴스로, 클래스에서 정의한 속성과 메서드를 실제로 사용할 수 있는 실체입니다. 객체는 독립적인 상태를 가지며, 각 객체는 서로 다른 데이터를 가질 수 있습니다.

int main() {
    Car myCar; // 객체 생성
    myCar.brand = "Toyota"; // 속성 초기화
    myCar.year = 2020;
    myCar.displayInfo(); // 메서드 호출

    Car yourCar;
    yourCar.brand = "Hyundai";
    yourCar.year = 2018;
    yourCar.displayInfo();

    return 0;
}

위 코드에서는 myCaryourCar라는 두 개의 객체를 생성하고, 각각 다른 브랜드와 연도를 설정했습니다. 이처럼 클래스를 통해 다양한 객체를 생성하고 관리할 수 있습니다.


2. 생성자와 소멸자

2.1 생성자(Constructor)

생성자는 객체가 생성될 때 호출되는 특별한 멤버 함수로, 주로 객체의 초기화를 담당합니다. 생성자는 클래스 이름과 동일하며, 반환값이 없습니다. 생성자를 통해 객체의 초기 상태를 설정할 수 있습니다.

class Rectangle {
public:
    int width, height;

    // 기본 생성자
    Rectangle() : width(0), height(0) {}

    // 매개변수가 있는 생성자
    Rectangle(int w, int h) : width(w), height(h) {}

    int area() {
        return width * height;
    }
};

int main() {
    Rectangle rect1; // 기본 생성자 호출
    Rectangle rect2(10, 5); // 매개변수가 있는 생성자 호출
    std::cout << "rect2 면적: " << rect2.area() << std::endl; // 출력: 50
    return 0;
}

위 예제에서는 Rectangle 클래스에 기본 생성자와 매개변수가 있는 생성자를 정의했습니다. 이를 통해 객체를 생성할 때 초기값을 설정할 수 있습니다.

2.2 소멸자(Destructor)

소멸자는 객체가 소멸될 때 호출되는 함수로, 주로 동적 메모리 해제나 자원 정리를 담당합니다. 소멸자는 클래스 이름 앞에 ~를 붙여 정의하며, 반환값과 매개변수를 가질 수 없습니다.

class Sample {
public:
    Sample() { std::cout << "객체 생성" << std::endl; }
    ~Sample() { std::cout << "객체 소멸" << std::endl; }
};

int main() {
    Sample obj; // 객체 생성 시 생성자 호출
    return 0; // 프로그램 종료 시 소멸자 호출
}

위 코드에서는 Sample 클래스의 생성자와 소멸자를 정의했습니다. 객체가 생성될 때와 소멸될 때 각각 메시지를 출력하도록 했습니다.


3. 상속(Inheritance)

3.1 상속의 기본 개념

상속은 기존 클래스의 속성과 메서드를 새로운 클래스에서 재사용하고 확장하는 기능입니다. 이를 통해 코드 중복을 줄이고 유지보수를 용이하게 합니다. 상속은 부모 클래스(Base Class)와 자식 클래스(Derived Class)로 구성됩니다.

class Animal {
public:
    void eat() {
        std::cout << "Animal is eating." << std::endl;
    }
};

class Dog : public Animal {
public:
    void bark() {
        std::cout << "Dog is barking." << std::endl;
    }
};

int main() {
    Dog myDog;
    myDog.eat(); // 부모 클래스의 메서드 호출
    myDog.bark(); // 자식 클래스의 메서드 호출
    return 0;
}

위 예제에서는 Animal 클래스를 상속받은 Dog 클래스를 정의했습니다. Dog 클래스는 Animal 클래스의 eat() 메서드를 사용할 수 있으며, 자신만의 bark() 메서드를 추가로 정의했습니다.

3.2 다중 상속(Multiple Inheritance)

C++에서는 다중 상속을 지원합니다. 다중 상속은 두 개 이상의 클래스를 상속받는 것을 의미합니다. 하지만 다중 상속은 복잡성을 증가시킬 수 있으므로 주의가 필요합니다.

class A {
public:
    void funcA() { std::cout << "Function A" << std::endl; }
};

class B {
public:
    void funcB() { std::cout << "Function B" << std::endl; }
};

class C : public A, public B {
public:
    void funcC() { std::cout << "Function C" << std::endl; }
};

int main() {
    C obj;
    obj.funcA(); // Function A 출력
    obj.funcB(); // Function B 출력
    obj.funcC(); // Function C 출력
    return 0;
}

4. 다형성(Polymorphism)

4.1 다형성의 기본 개념

다형성은 동일한 인터페이스를 통해 서로 다른 객체를 처리할 수 있는 능력입니다. C++에서는 가상 함수를 통해 런타임 다형성을 구현합니다. 다형성을 통해 코드의 유연성과 확장성을 높일 수 있습니다.

4.2 가상 함수(Virtual Function)

가상 함수는 기본 클래스에서 정의되고 파생 클래스에서 재정의될 수 있는 멤버 함수입니다. 가상 함수를 사용하면 런타임에 어떤 함수가 호출될지 결정됩니다.

class Animal {
public:
    virtual void sound() { // 가상 함수
        std::cout << "Animal makes sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void sound() override { // 오버라이딩
        std::cout << "Dog barks" << std::endl;
    }
};

class Cat : public Animal {
public:
    void sound() override { // 오버라이딩
        std::cout << "Cat meows" << std::endl;
    }
};

void makeSound(Animal* animal) {
    animal->sound();
}

int main() {
    Dog dog;
    Cat cat;
    makeSound(&dog); // Dog barks 출력
    makeSound(&cat); // Cat meows 출력
    return 0;
}

위 예제에서는 Animal 클래스의 sound() 메서드를 가상 함수로 정의하고, DogCat 클래스에서 이를 재정의했습니다. makeSound() 함수는 Animal 포인터를 매개변수로 받지만, 실제로는 DogCat 객체의 sound() 메서드가 호출됩니다.


5. 실제 프로젝트에서의 활용

5.1 클래스와 객체의 활용

클래스와 객체는 실제 프로젝트에서 데이터를 구조화하고 관리하는 데 매우 유용합니다. 예를 들어, 은행 시스템에서는 Account 클래스를 정의하고, 각 계좌를 객체로 관리할 수 있습니다.

class Account {
private:
    std::string owner;
    double balance;

public:
    Account(std::string name, double initialBalance) : owner(name), balance(initialBalance) {}

    void deposit(double amount) {
        balance += amount;
    }

    void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
        } else {
            std::cout << "Insufficient funds!" << std::endl;
        }
    }

    void display() const {
        std::cout << "Owner: " << owner << ", Balance: " << balance << std::endl;
    }
};

int main() {
    Account account("John Doe", 1000);
    account.deposit(500);
    account.withdraw(200);
    account.display(); // Owner: John Doe, Balance: 1300 출력
    return 0;
}

5.2 상속과 다형성의 활용

상속과 다형성은 게임 개발에서 캐릭터 시스템을 설계할 때 유용합니다. 예를 들어, Character 클래스를 상속받은 Warrior, Mage, Archer 클래스를 정의하고, 각 캐릭터의 공격 방식을 다형성을 통해 구현할 수 있습니다.

class Character {
public:
    virtual void attack() = 0; // 순수 가상 함수
};

class Warrior : public Character {
public:
    void attack() override {
        std::cout << "Warrior attacks with a sword!" << std::endl;
    }
};

class Mage : public Character {
public:
    void attack() override {
        std::cout << "Mage casts a fireball!" << std::endl;
    }
};

class Archer : public Character {
public:
    void attack() override {
        std::cout << "Archer shoots an arrow!" << std::endl;
    }
};

int main() {
    Character* characters[] = { new Warrior(), new Mage(), new Archer() };
    for (Character* character : characters) {
        character->attack();
    }
    return 0;
}

6. 고급 OOP 개념

6.1 추상 클래스(Abstract Class)

추상 클래스는 하나 이상의 순수 가상 함수를 포함하는 클래스로, 직접 객체를 생성할 수 없습니다. 추상 클래스는 인터페이스를 정의하고, 파생 클래스에서 이를 구현하도록 강제합니다.

class Shape {
public:
    virtual void draw() = 0; // 순수 가상 함수
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle" << std::endl;
    }
};

class Square : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a square" << std::endl;
    }
};

int main() {
    Shape* shapes[] = { new Circle(), new Square() };
    for (Shape* shape : shapes) {
        shape->draw();
    }
    return 0;
}

6.2 인터페이스(Interface)

인터페이스는 모든 메서드가 순수 가상 함수인 추상 클래스입니다. 인터페이스를 통해 클래스 간의 계약을 정의하고, 이를 통해 다양한 클래스가 동일한 인터페이스를 구현할 수 있습니다.

class Printable {
public:
    virtual void print() = 0;
};

class Document : public Printable {
public:
    void print() override {
        std::cout << "Printing document" << std::endl;
    }
};

class Image : public Printable {
public:
    void print() override {
        std::cout << "Printing image" << std::endl;
    }
};

int main() {
    Printable* printables[] = { new Document(), new Image() };
    for (Printable* printable : printables) {
        printable->print();
    }
    return 0;
}

7. 디자인 패턴과 OOP

7.1 싱글톤 패턴(Singleton Pattern)

싱글톤 패턴은 클래스의 인스턴스가 하나만 생성되도록 보장하는 디자인 패턴입니다. 이 패턴은 전역 변수를 사용하지 않고도 전역적인 접근을 제공합니다.

class Singleton {
private:
    static Singleton* instance;
    Singleton() {} // private 생성자

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

    void doSomething() {
        std::cout << "Doing something" << std::endl;
    }
};

Singleton* Singleton::instance = nullptr;

int main() {
    Singleton::getInstance()->doSomething();
    return 0;
}

7.2 팩토리 패턴(Factory Pattern)

팩토리 패턴은 객체 생성 로직을 캡슐화하여 클라이언트 코드에서 객체 생성에 대한 의존성을 줄이는 디자인 패턴입니다.

class Product {
public:
    virtual void use() = 0;
};

class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "Using Product A" << std::endl;
    }
};

class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "Using Product B" << std::endl;
    }
};

class Factory {
public:
    static Product* createProduct(const std::string& type) {
        if (type == "A") {
            return new ConcreteProductA();
        } else if (type == "B") {
            return new ConcreteProductB();
        }
        return nullptr;
    }
};

int main() {
    Product* productA = Factory::createProduct("A");
    Product* productB = Factory::createProduct("B");

    productA->use(); // Using Product A 출력
    productB->use(); // Using Product B 출력

    delete productA;
    delete productB;

    return 0;
}

결론

객체 지향 프로그래밍은 클래스와 객체를 중심으로 코드를 구조화하여 재사용성과 유지보수성을 높입니다. 생성자와 소멸자를 통해 객체의 생명주기를 관리하고, 상속을 통해 코드를 확장하며, 다형성을 통해 유연한 코드를 작성할 수 있습니다. 이러한 개념들을 잘 이해하고 활용하면 더 나은 소프트웨어를 설계하고 개발할 수 있습니다.

C++는 이러한 OOP 개념을 강력하게 지원하므로, 다양한 예제를 통해 연습해보는 것을 추천합니다. 다음 단계로는 더 복잡한 디자인 패턴이나 고급 OOP 개념을 학습해보세요!

728x90