[C++ 기초] 상속 심화(생성자 순서, 업캐스팅, 다중 상속)

라멘커비·2023년 12월 28일
0

CPP 입문

목록 보기
12/25
post-thumbnail

상속 심화

💀부모 자식의 생성자

  1. 생성자는 무조건 호출된다.
  2. 부모가 먼저 만들어 진다.
  3. 부모가 먼저 만들어져야 자식의 생성자나 함수도 만들어질 수 있기 때문이다.

어제 과제 코드 자식 생성자 사용해서 초기화 → 부모의 생성자 사용하는 코드로 수정 가능

ConsoleObject.h
#pragma once
#include "Math.h"

// 자식들이 공통적으로 가져야 할 기능을 만들어야 한다.

class ConsoleObject
{
public:
	ConsoleObject();
	ConsoleObject(const int2& _StartPos, char _RenderChar);
	int2 GetPos();
	void SetPos(const int2& _Pos);
	char GetRenderChar();
protected:
	int2 Pos = { 0, 0 };
	char RenderChar = '@';
};
ConsoleObject.cpp
#include "ConsoleObject.h"
ConsoleObject::ConsoleObject() {

}
ConsoleObject::ConsoleObject(const int2& _StartPos, char _RenderChar)
	: Pos(_StartPos), RenderChar(_RenderChar)
{

}
int2 ConsoleObject::GetPos()
{
	return Pos;
}
char ConsoleObject::GetRenderChar()
{
	return RenderChar;
}
void ConsoleObject::SetPos(const int2& _Pos) {
	Pos = _Pos;
}
Player.cpp
#include "Player.h"
#include "ConsoleScreen.h"
#include <conio.h>

Player::Player()
{
}

Player::Player(const int2& _StartPos, char _RenderChar)
	: ConsoleObject(_StartPos, _RenderChar)
{
	//Pos = _StartPos;
	//RenderChar = _RenderChar;
}
...

💀Has A, Is A 관계

Is A : ~이다. 상속.
Has A : 가졌다.

Player 클래스가 FightUnit 클래스를 상속 받았으므로 Player Is A FightUnit,
멤버변수로 다른 클래스를 가지면 Player Has A Sword 라고 표현한다.

class Item {
public:
	int cost;
};
class Sword : public Item {

};
class FightUnit {

};
class Player : public FightUnit {
private:
	Sword Weapon;
};

💀업캐스팅

자식 클래스의 객체는 언제든 부모 클래스의 참조형으로 형변환이 될 수 있다.
참조형으로 주고받는 것이 굉장히 건강하고 권장되는 암시적 형변환이다.

class FightUnit {
public:

};

class Player : public FightUnit {
private:
	Sword Weapon;
};
int main() {

	{
		// 업캐스팅
		// 굉장히 건강하고 권장되는 암시적 형변환
		Player NewPlayer;
		FightUnit* Unit = &NewPlayer;
	}

	{
		// 이렇게도 되긴 함
		Player NewPlayer;
		FightUnit Unit = NewPlayer;
	}
	return 0;
}

💀다운캐스팅

부모클래스의 참조형에서 자식클래스의 참조형으로 변환하는 것이다.

  • 건강하지 못한 캐스팅이다. 이게 Player였는지 Monster였는지 구분할 수가 없다. 구분하기 위해 변수를 두고 구분할 수도 있지만 실수할 여지가 있다.
  • 부모클래스에는 없지만 자식클래스에는 있는 변수가 있는 경우 수치 오류가 발생한다.
class FightUnit {

};
class Player :	public FightUnit {

};
class Monster : public FightUnit { 

};
int main() {
	Player NewPlayer;
	Monster NewMonster;
	FightUnit* NewUnit = &NewMonster;

	// 다운캐스팅
	Player* CurPlayer = reinterpret_cast<Player*>(NewUnit);
	return 0;
}

💀다중 상속

여러 클래스를 상속받을 수 있다.

#include <iostream>
class TalkUnit {
public:
	void Talk(const TalkUnit& _Unit) {

	}
};
class MoveUnit {
public:
	void Move() {

	}
};
class FightUnit {
public:
	void Fight(const FightUnit& _Unit) {
		if (this == &_Unit) {
			return;
		}
	}

};
class Player : public TalkUnit, public MoveUnit, public FightUnit { // 다중상속

};
class Monster : public MoveUnit, public FightUnit { 

};
int main() {
	Player NewPlayer;
	Monster NewMonster;
	return 0;
}

부모클래스가 자식클래스의 자료형을 사용하면 안 된다.

ex) FightUnit를 상속받는 Player가 있다. " FightUnit에서 Player를 알아야 하는 코딩을 해야겠다. " 라는 생각이 들었다면 잘못된 생각이다.
문법적으로는 가능하다.

class FightUnit {
public:
	Player* Test; // 구조를 망치는 행위
};
class Player :	public FightUnit {

};

멤버 함수 const

함수 뒤에 const를 붙이는 것은 this를 const Player* const this로 변환하겠다는 의미이다. this의 멤버변수값을 바꾸지 않도록 한다.


Tip) 클래스는 밑단 개념이 아닌 근본적인 개념을 제공하기 위한 클래스를 만드는 경우가 많다.
부모의 함수를 사용하고 싶다면 FullName을 사용하는 것이 정석이지만 보통 그렇게 사용하지 않는다. 클래스 멤버변수, 멤버함수 모두 FullName이 있다.

F8 : 에러난 곳으로 바로 이동.


과제

💀0. SetName이 되게 해라.

  • StatusUnit 클래스의 멤버함수 SetName에 예전에 배운 문자열 관련 함수를 활용해서 이름을 변경할 수 있도록 만들었다. 근데 저렇게 바꾸면 이름의 초기값이 "None"이므로 4글자 이상의 이름으로 Set해야한다.
// in 'StatusUnit.cpp'
void StatusUnit::SetName(const char* _Name)
{
	int Count = 0;
	while (*(_Name + Count)) {
		Count++;
	}
	
	for (int i = 0; i < Count; i++) {
		Name[i] = _Name[i];
	}
}

💀1. 데미지가 Random Damage가 되게 해라.

  • FightUnit 클래스의 DamaheLogic에서 원래 Hp -= _Unit.Att 라고 사용하던 것을 랜덤 데미지rDamage를 바로 계산해서 때리도록 변경하였다.
  • 그리고 이전 턴의 랜덤 공격력이 더 높은 쪽에 Speed를 추가해주기 위해(2번 과제를 위해 Speed를 추가해주는 로직을 만듦.) Status에 PrevAtt라는 멤버변수를 만들고 랜덤 공격력을 매번 갱신해주었다.
  • 나중에보니 선생님은 GetRandomDamage라는 함수를 한 개 더 만들어서 분리하셨다. 그게 더 멋진듯.
// in 'FightUnit.cpp'
void FightUnit::DamageLogic(FightUnit& _Unit) // _Unit이 때리는 것
{
	// Min ~ Max 사이의 랜덤한 공격력으로 공격.
	rDamage = rand() % (_Unit.MaxAtt - _Unit.MinAtt + 1) + _Unit.MinAtt; 
	
    Hp -= rDamage;
	_Unit.PrevAtt = rDamage;
}
  • 시드값 설정은 main함수에서 했다.
int main()
{
	// 실핼할 때마다 달라지는 변수의 메모리 주소를 이용해서 srand()
    int Value = 0;
    __int64 Seed = reinterpret_cast<__int64>(&Value);
    srand(static_cast<unsigned int>(Seed));

    Player NewPlayer = Player();
    NewPlayer.SetName("Player");

    FightZone NewZone;
    NewZone.Fight(NewPlayer);

}

💀2. 선공 후공이 랜덤이 되게 만들어라.

  • 과제 1번에서 이전 턴의 랜덤 공격력을 PrevAtt에 저장해주었다. PrevAtt가 더 큰 곳에 Speed를 1 추가해준다.
  • 그리고 StatucUnit에서 Speed와 Hp의 Get()함수를 만들어 속도 - 체력이 큰 곳이 선공을 할 수 있도록 한다. (속도가 빠르고 체력이 적을수록 선공 확률이 높도록 만들고 싶었다.)
// in 'FightZone.cpp'
void FightZone::Fight(FightUnit& _Player)
{
	NewMonster.SetName("Monster");
	//NewMonster.AddSpeed(5); // 테스트용
	while (true)
	{
		_Player.StatusRender();
		NewMonster.StatusRender();

		bool IsEnd = false;

		// 속도가 빠르고 체력이 적으면 선공
		int PlayerStat = _Player.GetSpeed() - _Player.GetHp();
		int MonsterStat = NewMonster.GetSpeed() - NewMonster.GetHp();

		if (PlayerStat > MonsterStat) {
			// Player 선공
			IsEnd = FightLogic(_Player, NewMonster, _Player, NewMonster);
		}
		else {
			IsEnd = FightLogic(NewMonster, _Player, _Player, NewMonster);
		}

		if (true == IsEnd) {	// 죽었으면 끝
				return;
		}
		else {					// 안 끝났으면 한 쪽에 속도 지급
			// Player의 이전 공격력이 더 쎘으면 Player 속도 [1]만큼 추가
			int gap = _Player.GetPrevAtt() - NewMonster.GetPrevAtt();
			if (gap > 0) {
				_Player.AddSpeed(1);
			}
			else if (gap < 0) {
				NewMonster.AddSpeed(1);
			}
		}
	}

}

💀실행 화면

(키를 누를 때마다 공격이 이어지는 모습)
공격력이 랜덤이고 선공, 후공도 계산에따라 달라진다.

profile
일단 시작해보자

0개의 댓글

관련 채용 정보