[Day11] JobAndCombat_Required

베리투스·2025년 8월 19일

TIL: Today I Learned

목록 보기
19/93
post-thumbnail

절차지향 프로그래밍에서 벗어나 처음으로 C++ 객체 지향 프로그래밍(OOP)의 세계에 발을 들였다. 🐾 Player라는 부모 클래스를 만들고, 이를 상속받는 전사, 마법사 등 다양한 직업 클래스를 구현했다. 특히 부모 클래스의 포인터 하나로 모든 자식 객체를 다루는 다형성(Polymorphism)의 강력함을 체험할 수 있었다. 순수 가상 함수를 통해 자식 클래스가 반드시 특정 기능을 구현하도록 강제하는 멋진 설계도 경험했다.


📌 목표

  • C++ 클래스와 객체 개념 이해하기
  • 상속과 다형성 구현하기
  • 가상 함수와 오버라이딩 실습하기
  • 추상 클래스 및 순수 가상 함수의 역할 파악하기
  • new/delete를 이용한 동적 메모리 관리 익히기

💻 코드

main.cpp

// main.cpp: 프로그램의 전체 흐름을 제어합니다.
#include <iostream>
#include "player.h"
#include "warrior.h"
#include "magician.h"
#include "thief.h"
#include "archer.h"

using namespace std;

int main() {
    // Player* 타입의 포인터 변수.
    // 이 포인터 하나로 Warrior, Magician 등 모든 자식 객체를 가리킬 수 있습니다. (다형성)
    Player* player = nullptr; 
    
    string nickname;
    int job_choice;

    cout << "* 닉네임을 입력해주세요: ";
    cin >> nickname;
    // ... (직업 선택 UI) ...
    cin >> job_choice;

    // 사용자의 선택에 따라 해당하는 자식 클래스의 객체를 동적으로 생성합니다.
    switch (job_choice) {
        case 1: player = new Warrior(nickname); break;
        case 2: player = new Magician(nickname); break;
        case 3: player = new Thief(nickname); break;
        case 4: player = new Archer(nickname); break;
        default:
            cout << "잘못된 입력입니다." << endl;
            return 1;
    }

    // player 포인터는 Player 타입이지만, 실제로는 선택된 직업 객체를 가리키고 있습니다.
    // 따라서 실제 객체에서 재정의(오버라이딩)된 attack() 함수가 호출됩니다.
    player->attack();
    player->printPlayerStatus();

    // 'new'로 동적 할당한 메모리는 반드시 'delete'로 해제해야 메모리 누수를 막을 수 있습니다.
    delete player;
    return 0;
}

player.h

// player.h: 모든 직업의 기반이 되는 부모 클래스의 설계도입니다.
#pragma once // 헤더 파일의 중복 포함을 방지하는 지시어입니다.
#include <string>
using namespace std;

class Player {
public:
    Player(string nickname); // 생성자: 객체가 생성될 때 호출됩니다.

    // 순수 가상 함수: "= 0"은 이 함수가 본체가 없음을 의미합니다.
    // 이 함수를 가진 Player 클래스는 '추상 클래스'가 되어 직접 객체를 만들 수 없습니다.
    // 자식 클래스는 반드시 attack() 함수를 자신만의 방식으로 구현(오버라이딩)해야 합니다.
    virtual void attack() = 0; 

    void printPlayerStatus();

protected: // protected 멤버는 자식 클래스에서만 접근이 가능합니다.
    string job_name;
    string nickname;
    int HP, MP, power, defence;
};

warrior.cpp

// warrior.cpp: Warrior 클래스의 실제 동작을 구현합니다.
#include "warrior.h"
#include <iostream>

// Warrior 생성자
// ': Player(nickname)' 문법은 '초기화 리스트'라고 합니다.
// 자식 객체가 생성될 때, 먼저 부모 클래스의 생성자를 호출하여 기본 정보를 초기화합니다.
Warrior::Warrior(string nickname) : Player(nickname) {
    // 부모로부터 물려받은 멤버 변수들을 전사 직업에 맞게 재설정합니다.
    this->job_name = "전사";
    this->HP = 150;
    this->power = 20;
    this->defence = 15;
}

// 부모 클래스의 순수 가상 함수 attack()을 재정의(오버라이딩)합니다.
void Warrior::attack() {
    cout << "\n" << job_name << " '" << nickname << "' (이)가 검으로 강하게 내려칩니다! 휙!" << endl;
}

⚠️ 실수

  • 분명히 Warrior 클래스를 만들었는데 왜 객체를 생성할 수 없다는 건지 한참을 헤맸다. 알고 보니 부모 클래스(Player)에 남아있던 다른 순수 가상 함수(attack(Monster*))를 구현하지 않아서, Warrior도 여전히 "미완성"인 추상 클래스 취급을 받았던 것이었다. 😅
  • 헤더 파일(.h)에 함수를 선언해놓고 .cpp 파일에 구현하는 걸 깜빡해서 LNK2001이라는 무시무시한 링커 오류를 만났다. 컴파일은 되는데 왜 실행 파일이 안 만들어지는지, '선언'과 '구현'의 연결고리를 뼈저리게 느낄 수 있었다. 🔗
  • new로 신나게 객체를 만들고 나서 delete를 빼먹을 뻔했다. 프로그램이 그냥 꺼지니 티는 안 나지만, 메모리 누수가 일어날 수 있다는 걸 배우고 꼭 짝을 맞춰줘야겠다고 다짐했다.

✅ 핵심 요약

개념설명비고
클래스객체를 만들기 위한 '설계도'. 데이터와 기능을 하나로 묶어 관리한다.Player, Warrior
상속부모 클래스의 특징을 자식 클래스가 물려받아 코드를 재사용하는 기술.class Warrior : public Player
다형성하나의 포인터가 여러 다른 타입의 객체를 가리키고, 상황에 맞게 동작하는 성질.Player* p = new Warrior();
가상 함수virtual 키워드가 붙은 함수. 포인터 타입이 아닌 실제 객체 타입에 따라 호출될 함수가 결정된다.virtual void attack()
추상 클래스순수 가상 함수를 하나 이상 가진 클래스. 객체 생성이 불가능하며 오직 상속을 위한 '미완성 설계도'로 쓰인다.Player 클래스가 해당된다.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글