상황에 따라 다양한 형태로 해석가능
캐릭터의 충돌을 표현해보자
class Warrior;
class Archer;
class Wizard;
class Paladin;
bool CheckCollision(Warrior A, Archer B);
bool CheckCollision(Paladin A, Archer B);
...
캐릭터가 다양할 수록 만들어야하는 함수가 기하급수적으로 늘어남
같은 부모를 가지고 있다면
class Warrior: public Character;
class Archer :public Character;
class Wizard :public Character;
class Paladin:public Character;
자식은 부모다가 가능함
(child is a parent)
bool CheckCollision(Character,Character);
위저드 아처 팔라딘 등을 캐릭터로 할 수 있다면 쉬워짐
상속과 포인터(필수), virtual(가상 멤버 함수)으로 다형성을 표현할 수 있음
A* p = new C;
C는 상황에따라 C의 부모로 취급 가능함
자식에서 재정의(override)할 것을 기대하는 가상의 멤버함수
가상임으로 실제로 존재하는 것이 아니라 누군가가 실체를 제공해줌
자식으로 해석하고싶은 것
class character
{
public:
virtual void attack()
{
std::cout << "공격" << std::endl;
}
};
class warrior : public character
{
public:
void attack() override
//오버라이드를 명시하게 되어있음
//modern c++이후의 기능
//안써도되지만 오버라이딩과 관련된 필수기능이기 때문
//가상함수를 오버라이딩한다는 뜻으로 쓸 수 있음
{
std::cout << "칼로 공격" << std::endl;
}
};
class wizard : public character
{
public:
void attack()
{
std::cout << "마법 공격" << std::endl;
}
};
class archer : public character
{
public:
void attack()
{
std::cout << "활로 공격" << std::endl;
}
};
class thief : public character
{
public:
void attack()
{
std::cout << "돈을 훔침" << std::endl;
}
};
class prist : public character
{
public:
void attack()
{
std::cout << "회복 주문" << std::endl;
}
};
int main()
{
character *c = new warrior;
c->attack();
//전사가 되어야 하지만 아님
//워리어로 만들었지만 포인터 자체가 캐릭터이기 때문에
//하지만 워리어의 함수로 만들고 싶음
character *party[5] {
new warrior,
new wizard,
new archer,
new prist,
new thief
};
//공격을 시켜보면
for ( int i = 0; i < 5; i++ )
{
party[i]->attack();
//편한데 전부다 부모의 공격을 사용할 수 밖에없음
}
//캐릭터포인터로 가리키더라도 만든 클래스의 기능을 쓰고 싶음
//이 때 부모의 attack함수를 virtual로 만들면
//실체가 없으므로 실제 객체들에서 찾아서 하려고 할 것
//그래서 객체별로 다른 공격을 하게됨
//기능은 있지만 실제는 자식 객체를 참조하세요 라는 뜻
//상황에따라 다르게 불림
//이것이 다형성
//상속의 관계가 있어야만 다형성이 가능해짐
//명시적으로 인스턴스를 만들면 못씀(명시적임으로)
//상황에 따라 가리킬수있으므로 포인터만 가능함
//거기에 virtual 기능이 상황에 따라서 가능하도록 만들어주는 것이 이 키워드임
//만약에 warrior를 재상속 받는 클래스가 있다면
//워리어와 캐릭터 모두 virtual attack이어야함
character *pchar = new warrior;
pchar->attack();
warrior *w = (warrior *) p;
for ( int i = 0; i < 5; i++ )
{
warrior *pw = (warrior*)(party[i]);
pw->attack();
}
}
early(static,compiletime) binding vs late(dynamic, runtime) binding
가상함수의 경우는 late binding(다형성에 의해서 실체를 다양하게 해석해야하기 때문)
프로그램이 실행될 때 누구에게 바인딩 할 것인지 결정하게됨
실제로 이런 기능을 코드로 구현하기도 함
부모의 소멸자는 반드시 virtual(가상) 소멸자로 만들어야함
가상 소멸자로 만들지 않으면 memory leak 가능성이 있음
암시적 형변환 : 데이터 손실이 적은 쪽으로 일어날 때
float x= (float)4/2.0f;
상향 변환(Up Cast)
자식에서 부모로 가는 것이 가능함
명시적 형변환 : 데이터가 손실될 수 있어 조심해야함
부모에서 자식
하향 변환(down cast)
프로그래머가 결과를 가져감(컴파일러는 컴파일해주지만 문제 가능성)
다형성 -> runtime에 발생
형변환은 런타임에 하는 경우가 생김
형변환에서 고려해야할 사항
1.데이터 손실
2.상향이냐 하향이냐
3.런타임이냐 컴파일타임이냐
static_cast(early binding, compile time)
c언어 스타일의 형변환 + 추가 기능
dynamiccast_(late binding, run-time)
동적으로 객체를 파악함
실행한다음 다형성하듯 메모리에서 p의 정체를 찾아냄
reinterpret_cast
const_cast
c스타일의 형변환은 좋지 않음 위의 2가지 형변환을 사용하기(객체 개념 반영 못함)
virtual : 자식이 재정의하길 기대하는 함수 (강제성이 없음)
강제성이 있도록 하는 기능이 필요
pure virtual : 자식이 반드시 재정의해야 하는 것
Abstract Class(추상 클래스)
추상적인 개념 - 규격이나 법칙과 같은 개념으로 강제성을 부여함
인스턴스화가 불가능함
순수 가상 함수를 하나라도 가지고 있는 클래스
자식들은 반드시 순수가상함수를 오버라이딩해야만 함
모든 멤버가 순수가상함수인 클래스
정보가 없고 함수의 규칙만 있는 함수class HDMI { public: virtual void Pins() =0; virtual int Width() =0; }; HDMI display; //순수가상함수라서 인스턴스 불가능 class LgMoniter : public HDMI { //상속을 받아서 밖에 사용 불가능함 //LGMONITOR 고유의 기능을 만들어서 사용해야함 //하지만 HDMI의 필수 규격은 지켜야함 void Pins() { } int Width() { } };