가상함수

조한별·2022년 4월 11일
0

가상함수의 기본 개념

가상함수는 부모, 자식 클래스 간의 동형 동명 함수를 오버라이딩(재정의)을 하기 위해 사용된다.

#include <iostream>

using namespace std;

class alpha
{
public:
	virtual void func()
	{
		cout << "alpha" << endl;
	}
};

class beta :public alpha
{
public:
	//override는 가상함수로 만드는 키워드이다.
	//virtual이랑 같은 기능을 하고 있지만 가상함수의 시작엔 사용할 수 없다.
	//또한, 키워드 사용 위치가 다르다.
	//오타를 검사할 때에도 유용하게 사용할 수 있다.
	//ex) void fuc() override		//에러 발생!!
	virtual void func() override
	{
		cout << "beta" << endl;
	}
};

int main()
{
	alpha a;
	beta b;

	a.func();
	b.func();

	alpha& a1 = b;
	alpha* a2 = &b;

	a1.func();
	a2->func();
}

결과

alpha
beta
beta
beta

위의 예제에서 부모 클래스인 alpha 클래스의 func() 앞에 virtual이 붙어 있다. 이는 func()를 가상함수로서 사용한다는 명령어다. 이렇게 사용하면 자식 클래스인 beta 클래스 내의 func()를 사용할 때 가려지지 않게 된다.
만약에 alpha::func()를 다음과 같이 가상함수를 사용하지 않게 된다면 결과는 달라진다.

#include<iostream>
using namespace std;

class alpha
{
public:
	void func()
	{
		cout << "alpha" << endl;
	}
};

class beta :public alpha
{
public:
	void func()
	{
		cout << "beta" << endl;
	}
};

int main()
{
	alpha a;
	beta b;

	a.func();
	b.func();

	alpha& a1 = b;
	alpha* a2 = &b;

	a1.func();
	a2->func();
}

결과

alpha
beta
alpha
alpha

이처럼 가상함수를 사용하지 않게 되면 시작인 alpha::func()를 출력하게 된다.

가상함수를 사용할 때에는 몇가지 조건이 있다.
1. public 섹션에 선언해야 한다.
2. static과 friend를 사용할 수 없다.
3. 포인터 혹은 레퍼런스로 접근해야 한다.
4. 생성자엔 사용할 수 없다.
5. 반환 데이터형과 매개변수 그리고 함수 이름이 같아야 한다.
단, 반환형이 부모 자식 관계에 있는 포인터나 래퍼런스일 경우엔 달라도 허용된다.

// 4번 예외사항 예제

class alpha
{
public:
	virtual alpha* func()
    {
    	return nullptr;
    }
};

class beta :public alpha
{
public:
	virtual beta* func() override
    {
    	return nullptr;
    }
 };

추가적으로 처음의 함수엔 virtual을 붙혀서 가상함수임을 나타내야 하지만 그 다음인 자식 클래스의 가상함수에는 virtual을 붙히지 않아도 암묵적으로 붙혀지기 때문에 굳이 virtual을 붙히지 않아도 된다.

다음은 가상함수를 사용한 간단한 예제이다.

#include <iostream>

using namespace std;

class Character
{
protected:
	int _hp;
	int _dmg;
	const string _name;

public:
	Character(int hp, int dmg, string name)
		:_hp(hp), _dmg(dmg), _name(name)
	{

	}

	virtual void damage(int dmg)
	{
		_hp -= dmg;
	}

	void hit(Character& target) const
	{
		cout << this->_name << "이(가) " << target._name << "을(를) 공격했습니다." << endl;
		target.damage(target._dmg);

		if (target._hp <= 0)
		{
			cout << target._name << "이(가) 사망했습니다." << endl;
		}
			

	}

	virtual ~Character()
	{

	}
};

class Player :public Character
{
public:
	Player(int hp, int dmg, string name) :Character(hp, dmg, name)
	{
		cout << name << "님이 로그인했습니다." << endl;
	}

	void damage(int dmg) override
	{
		Character::damage(dmg);
		cout << "크윽!" << endl;
	}

	~Player() override
	{
		cout << "플레이어 객체 삭제!" << endl;
	}
};

class Monster :public Character
{
public:
	Monster(int hp, int dmg, string name) :Character(hp, dmg, name)
	{
		cout << name << "이(가) 생성되었습니다." << endl;
	}

	void damage(int dmg) override
	{
		Character::damage(dmg);
		cout << "꾸엑!.." << endl;
	}

	~Monster() override
	{
		cout << "몬스터 객체 삭제!" << endl;
	}
};

int main()
{
	Player player(200, 100, "용사");
	Monster monster(100, 50, "고블린");

	player.hit(monster);
	monster.hit(player);
	player.hit(monster);
}

결과

용사님이 로그인했습니다.
고블린이(가) 생성되었습니다.
용사이(가) 고블린을(를) 공격했습니다.
꾸엑!..
고블린이(가) 용사을(를) 공격했습니다.
크윽!
용사이(가) 고블린을(를) 공격했습니다.
꾸엑!..
고블린이(가) 사망했습니다.
몬스터 객체 삭제!
플레이어 객체 삭제!
profile
게임프로그래머 지망생

0개의 댓글