✍️ 오늘의 TIL (2025년 8월 22일) - 객체 지향 프로그래밍 (전직 시스템)
💡 과제를 통해 배운 객체 지향 프로그래밍 (OOP) 개념
이번 과제를 통해 C++의 핵심인 객체 지향 프로그래밍의 원리를 직접 적용해 보았다.
특히, 'Player'와 여러 직업 클래스를 만들면서 상속과 다형성의 유용성을 느낄 수 있었다.
1. 상속: 코드 재사용성의 경험
`Player`라는 기본 클래스를 만들고, `Warrior`, `Magician` 같은 직업 클래스가 이를 상속받는 구조를 만들었다.
-모든 직업이 공통으로 가지는 속성(`HP`, `MP` 등)과 기능(`printPlayerStatus()`)을 부모 클래스에 정의함으로써 중복 코드를 줄일 수 있었다.
자식 클래스는 부모의 기능을 물려받으면서, `attack()`처럼 자신만의 고유한 기능을 재정의(오버라이딩)하여 확장했다.
2. 다형성: 하나의 코드로 다양한 행동 실행
`Player` 타입의 포인터(`Player* player`) 하나로 `Warrior`든 `Magician`이든 어떤 객체든 가리킬 수 있었다.
`player->attack()`이라는 하나의 코드를 호출했을 때, 포인터가 가리키는 실제 객체에 따라 다른 공격 함수가 실행되는 것을 확인했다.
이것은 `attack()` 함수가 가상 함수(`virtual`)로 선언되었기 때문에 가능했다는 점을 배웠다.
3. 파일 분리 (.h vs .cpp)
처음에는 모든 코드를 한 파일에 넣고 싶었지만, `.h`와 `.cpp` 파일을 분리하는 것이 중요하다는 것을 깨달았다.
`.h` 파일에는 클래스 구조(설계도)만 선언하고, `.cpp` 파일에는 실제 함수의 구현을 작성함으로써 코드를 깔끔하게 관리할 수 있었다.
(*.h 클래스의 선언(설계도)를 담고 .cpp는 함수의 구현(실제 작동 코드)를 담는다.)
이는 복잡한 프로젝트를 관리하고 컴파일 속도를 높이는 데 필수적인 규칙이라고 한다.
[제작 과정과 배운 점]
1단계: 프로젝트 폴더와 파일 만들기
먼저, 과제를 위한 프로젝트를 만들고 그 안에 필요한 .h와 .cpp 파일을 준비.
- 헤더 파일(.h): 클래스들의 설계도 역할.
- Player.h
- Warrior.h
- Magician.h
- Thief.h
- Archer.h
- 소스 파일(.cpp): 클래스들의 실제 기능을 구현.
- Player.cpp
- Warrior.cpp
- Magician.cpp
- Thief.cpp
- Archer.cpp
- main.cpp (프로그램의 시작점)
2단계: 코드 작성하기 (파일별 역할 이해하기)
핵심은 .h에는 선언, .cpp에는 구현.
1. Player.h (설계도: "Player는 이렇게 생겼어!")
이 파일에는 Player 클래스가 어떤 변수와 함수를 가질지만 알려준다.
실제 함수가 어떻게 작동하는지에 대한 코드는 넣지 않는다.
#pragma once
#include <string>
#include <iostream>
using namespace std;
class Player {
public:
Player(string nickname);
// attack()은 '순수 가상 함수'로, 자식 클래스들이 반드시 구현해야 함을 강제합니다.
virtual void attack() = 0;
void printPlayerStatus();
// getter/setter 함수 선언
string getJobName();
//... (나머지 함수들도 선언만 추가)
protected:
string job_name;
string nickname;
int level;
//... (나머지 멤버 변수들도 선언만 추가)
};
- #pragma once: 이 코드는 "이 파일을 딱 한 번만 불러와!"라는 의미. 파일 중복 포함 문제를 막아준다.
- class Player { ... };: 클래스를 정의하는 코드다. 이 안에 변수와 함수 목록이 들어간다.
2. Player.cpp (구현: "Player의 기능은 이렇다!")
이 파일에는 Player.h에 선언했던 함수들이 실제로 어떻게 작동할지 코드를 작성.
#include "Player.h" // Player.h에 있는 설계도를 가져옵니다.
#include <iostream>
// 생성자 구현: 객체가 만들어질 때 초기 값을 설정합니다.
Player::Player(string nickname) : nickname(nickname) {
level = 1;
HP = 100;
// ... (나머지 속성 초기화)
}
// printPlayerStatus() 함수 구현
void Player::printPlayerStatus() {
cout << "------------------------------------" << endl;
cout << "* 현재 능력치" << endl;
cout << "닉네임: " << nickname << endl;
// ... (나머지 출력 코드)
}
// getter/setter 함수 구현
string Player::getJobName() {
return job_name;
}
//... (나머지 함수 구현)
- #include "Player.h": 쌍따옴표(" ")는 내 프로젝트 폴더에 있는 파일을 포함한다는 뜻.
- 이 파일이 Player.h의 설계도를 바탕으로 구현된다는 것을 알려준다.
3. Warrior.h / Warrior.cpp (상속: "Warrior는 Player의 특별판!")
Warrior 클래스는 Player의 모든 기능을 물려받고, 자신만의 특별한 기능을 추가하거나 바꾼다.
- Warrior.h:
#pragma once
#include "player.h" // 부모 클래스의 설계도를 가져옵니다.
class Warrior : public Player { // Player를 상속받습니다.
public:
Warrior(string nickname);
void attack() override; // 부모의 함수를 재정의합니다.
};
- Warrior.cpp:
#include "Warrior.h"
#include <iostream>
Warrior::Warrior(string nickname) : Player(nickname) { // 부모의 생성자를 먼저 호출!
job_name = "전사";
cout << "* 전사로 전직하였습니다." << endl;
HP = 80;
// ... (전사만의 속성 설정)
}
void Warrior::attack() { // 전사만의 공격 기능을 구현합니다.
cout << "* 검을 휘두른다!🗡️" << endl;
}
4. main.cpp (지휘관: "자, 이제 다들 움직여봐!")
main.cpp는 프로그램의 시작점.
여기서 사용자 입력을 받고, 원하는 직업의 객체를 만들고, 그 객체의 기능을 호출.
main.cpp에는 클래스 구현 코드가 들어가지 않는다.
#include <iostream>
#include "player.h"
#include "warrior.h"
// ... (나머지 직업의 헤더 파일 포함)
using namespace std;
int main() {
// Player 포인터 변수를 하나 선언합니다.
Player* player = nullptr;
// 사용자 입력에 따라 다른 직업 객체를 만듭니다.
switch (job_choice) {
case 1:
player = new Warrior(nickname);
break;
// ... (나머지 직업도 똑같이 만듭니다)
}
// ⭐️ 이 부분이 다형성의 핵심!
// 포인터가 어떤 객체를 가리키든, attack()을 호출하면
// 그 객체에 맞는 attack() 함수가 실행됩니다.
player->attack();
player->printPlayerStatus();
// 메모리 해제
delete player;
return 0;
}
- Player* player = new Warrior(nickname);: 이 코드는 다형성을 보여줌. Player 타입의 포인터로 Warrior 객체를 가리키는 것.
- player->attack(): 이 한 줄의 코드가 전사면 "검을 휘두른다"를, 마법사면 "화염 마법을 시전한다"를 출력. 코드는 하나지만, 상황에 따라 다른 동작을 하는 것이다.
[만들면서 느낀 나에게 필요한 것]
1단계: 문제를 한국어로 나누고 생각하기
코드를 쓰기 전에 먼저 '한국어'로 프로그램을 어떻게 만들지 구체적으로 계획. 즉, 알고리즘을 짜는 과정.
예를 들어, '전직 시스템' 과제를 시작하기 전에 아래와 같이 스스로에게 질문을 던지고 답을 써보는 거다.
- "플레이어는 어떤 정보를 가지고 있어야 하지?"
- 닉네임, 레벨, HP, 공격력...
- "플레이어는 어떤 행동을 할 수 있어야 하지?"
- 공격하기, 상태창 보여주기...
- "직업을 선택하는 건 어떻게 구현하지?"
- 1번은 전사, 2번은 마법사... 숫자를 입력받아야겠네.
- "각 직업은 공격 방식이 다른데, 이걸 어떻게 표현하지?"
- 전사는 검을 휘두르고, 마법사는 마법을 쓰고...
- "프로그램은 어떤 순서로 진행되어야 하지?"
-
- 닉네임 입력 -> 2. 직업 선택 -> 3. 선택한 직업으로 공격 -> 4. 상태창 보여주기.
-
이렇게 머릿속에 떠다니는 생각들을 구체적인 한국어 문장으로 정리하는 게 중요할 것 같다.
2단계: 한국어 문장을 코드로 번역하기
1단계에서 정리한 한국어 문장들을 이제 C++ 문법으로 하나씩 '번역'해보기.
- 한국어: "닉네임을 입력받아야겠네."
- C++ 코드: string nickname; (닉네임을 저장할 변수 선언) cin >> nickname; (키보드로 입력받기)
- 한국어: "플레이어는 닉네임, HP, MP 같은 걸 가지고 있어야 해."
- C++ 코드: class Player { string nickname; int HP; ... }; (클래스 안에 멤버 변수들을 선언)
- 한국어: "각 직업은 공격 방식이 달라."
- C++ 코드: virtual void attack() = 0; (다형성을 위한 가상 함수 선언)
처음엔 이 번역 과정이 어렵게 느껴지겠지만, 계속 연습하다 보면 한국어 문장이 자연스럽게 C++ 코드로 떠오르게 될 것이라고 믿는다. 이게 핵심인 것 같다.
3단계: 구조화와 디버깅
위 과정이 익숙해지면, 이제 코드를 효율적으로 구조화하는 법을 배울 것이다. 이것이 바로 우리가 파일을 .h와 .cpp로 나누는 이유일 것이다.
- Player.h: "플레이어의 설계도만 여기 모아두자."
- Player.cpp: "플레이어의 실제 기능은 여기다 구현해놓자."
- main.cpp: "전체 프로그램의 진행 순서는 여기다 정리해놓자."
이렇게 역할을 나눠 코딩하면, 복잡한 프로그램도 한눈에 파악하고 오류를 찾기(디버깅)가 쉬워질 것이다.
결론: 생각의 전환
코딩을 처음 배울 때는 언어의 규칙을 외우는 시간이 필요하다. 하지만 어느 정도 외운 후에는, 규칙을 이용해 나만의 논리를 코드로 표현하는 훈련으로 나아가야 할 것이다.
코드를 짜는 것뿐만 아니라, 코드를 어떻게 구조화해야 하는지, 그리고 객체 지향의 원리들이 실제 코드에서 어떻게 동작하는지 조금 이해할 수 있었던 과제였다.
좀 더 채워질 학습량으로 다음 과제에서는 더 나은 구조를 설계해봐야겠다.
- 깃허브 리포지토리 링크: https://github.com/zeh0va/2nd-Assignment-NBC-
'C++' 카테고리의 다른 글
| [TIL_C++] 자원관리(스마트포인터)/리플렉션 (1) | 2025.08.26 |
|---|---|
| [TIL_C++]주어진 단어를 회전시켜 단어를 만들기 (2) | 2025.08.25 |
| [TIL_C] 클래스 (0) | 2025.08.21 |
| [TIL_C++] 상태창 구현해보기 (0) | 2025.08.20 |
| [TIL_C++]매개변수, 지역변수 / Call by Value, Ref / 메모리 할당 (3) | 2025.08.19 |