25.05.30 (3) - 추가학습

김영하·2025년 5월 30일

C++

목록 보기
15/32

다형성을 활용한 게임 스킬 사용 프로그램

다형성을 이용해 다양한 직업을 가진 모험가들이 각기 다른 스킬을 사용하는 프로그램을 구현합니다.

기본 클래스

  • Adventure라는 기본 클래스를 정의하세요.
  • useSkill()이라는 순수가상함수를 선언하세요.

파생 클래스

  • Warror, Mage, Archer라는 세 가지 파생 클래스를 만드세요.
  • 각 클래스의 useSkill 함수를 재정의 해서 아래와 같이 출력하세요.
    • Warror : Warror uses Slash!
    • Mage : Mage casts Fireball!
    • Archer : Archer shoots an Arrow!

다형성 구현

  • Adventure*타입의 포인터를 사용하여 여러 모험가 객체를 가리키고,
    반복문을 통해 각 모험가의 스킬을 호출하는 동작을 구현하세요.


1단계 코드

#include <iostream>
#include <string>

using namespace std;

class Adventure {
public:
	virtual void useSkill() = 0;
};

class Warrior : public Adventure {
public:
	Warrior() {};

	void useSkill(Adventure* adv) {
		cout << "Warrior uses Slash!" << endl;
	}
};

class Mage : public Adventure {
public:
	Mage() {};

	void useSkill(Adventure* adv) {
		cout << "Mage casts Fireball!" << endl;
	}
};

class Archer : public Adventure {
public:
	Archer() {};

	void useSkill(Adventure* adv) {
		cout << "Archer shoots an Arrow!" << endl;
	}
};

int main() {
	Warrior w;
	Mage m;
	Archer a;

	for {

	} // 반복문 채워야할 부분

	return 0;
}

마지막 '반복문' 만 구현을 하면 되는데..
포인터와 참조변수를 통해서 스킬을 호출해야 하는 상황에서
그걸 어떻게 반복문으로 만들어 주느냐

일단 기본적인 형태로 만들어서
함수호출을 시험해 보려 했으나 Adventure는 추상클래스 이기 때문에 실패


2단계 코드

일단 잘못 생각하고 있었던 부분이,
void useSkill(Adventure* adv) 이 부분:
useSkill 은 파라미터가 필요 없는 함수이고,

애초에 추상 클래스에서 파라미터 없는 형태이다

배웠던 걸 다시 보고 알았는데
Adventure adv 포인터는 이렇게 넣으라고 둔 게 아니라
메인 함수에서 써먹으라고 있는 거다.
`Adventure
adv;로 **"객체 포인터"** 를 만들어 두고 그걸 이용해서 다른 파생 객체들의 주소를 주고->` 까지 써서 스킬을 호출해야 한다

cf)


이제 다시 반복문 작성을 해줘야 하는데..
일단 "포인터 배열" 을 염두에 두고 작성

"반복문을 통해 각 모험가의 스킬을 호출" 이라는 요구사항이 있었기 때문에
배열이 필요하다고 생각했고 => 추상 클래스 때문에 포인터들의 배열이 필요했다

Warrior w;
Mage m;
Archer a;

Adventure* ptrArr[3] = { &w, &m, &a };

우선 배운대로 각 파생 클래스 인스턴스 선언해주고
객체들 주소값 모아서 "포인터 배열" 선언

이제 이걸 돌면서 반복문

Warrior w;
Mage m;
Archer a;

Adventure* ptrArr[3] = { &w, &m, &a };
for (Adventure* adv : ptrArr) {
	adv->useSkill();
}

처음에는 잠깐 배웠던 걸 돌아보고 왔을 때처럼
Adventure* adv; 로 객체 포인터를 미리 만들어뒀지만,
반복문 단계에서 추상 클래스의 포인터를 만들어주기 때문에
메인함수 첫줄에 있던 Adventure* adv 는 필요가 없다고 보고 삭제.

adv가 ptrArr 을 돌면서 => 가상함수 호출에 쓰이는 -> 로 useSkill 호출


결과도 제대로 나왔다
이제 아래 정답 코드 예시를 비교)

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

// 기본 클래스
class Adventurer {
public:
    virtual void useSkill() = 0; // 순수 가상 함수
    virtual ~Adventurer() {} // 아직 안배운 소멸자
};

// 파생 클래스
class Warrior : public Adventurer {
public:
    void useSkill() {
        cout << "Warrior uses Slash!" << endl;
    }
};

class Mage : public Adventurer {
public:
    void useSkill() {
        cout << "Mage casts Fireball!" << endl;
    }
};

class Archer : public Adventurer {
public:
    void useSkill() {
        cout << "Archer shoots an Arrow!" << endl;
    }
};

int main() {
    // 다형성 사용
    vector<Adventurer*> adventurers; // 포인터 배열(백터) 선언
    adventurers.push_back(new Warrior()); 
    adventurers.push_back(new Mage());
    adventurers.push_back(new Archer());
    // push_back 으로 추가

    // 각 모험가의 스킬 사용
    for (size_t i = 0; i < adventurers.size(); ++i) {
        adventurers[i]->useSkill();
    }

    // 메모리 해제 // 아직 안배움
    for (size_t i = 0; i < adventurers.size(); ++i) {
        delete adventurers[i];
    }

    return 0;
}

소멸자와 메모리 해제.. 부분은 아직 배우지 않은 파트라 잠깐 접어두고
adventurers.push_back(new Warrior()); 부분인데:
이 new Warrior() 부분이 Warrior 객체의 주소값이 되는 경위를
알아봐야 할 것 같다

확인 결과:
new 연산자는 = "동적 메모리 할당" 에 사용되는 연산자로,
일종의 '함수' 라고 생각하면 되는데

new 를 쓰면 operator new 함수 를 호출한다

new 에 뒤이어오는 데이터타입의 크기에 따라 메모리를 할당받고,
"주소값" 을 반환한다
=> 즉 각 파생 클래스 객체의 주소값을 배열에 넣어주면서
정확하게 포인터 배열이 만들어진다는 것

애초에 포인터와 new delete 는 밀접한 관련이 있다고 한다
delete 의 경우 문자대로 순수한 "삭제" 가 아니라
"메모리를 비워준다"

new 와 반복문에서 나온 size_t 타입도 세트메뉴라고 한다.
size_t 로 메모리 크기를 지정한다고 함


그래서 결론은,
포인터 배열을 선언해서 원소로 각 주소값들을 채워주고,
그 배열을 반복문 돌려주면 되는 과정.
코드는 다르지만 접근법은 같았다고 할 수 있다.

profile
내일배움캠프 Unreal 3기

0개의 댓글