가상함수는 부모, 자식 클래스 간의 동형 동명 함수를 오버라이딩(재정의)을 하기 위해 사용된다.
#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);
}
결과
용사님이 로그인했습니다.
고블린이(가) 생성되었습니다.
용사이(가) 고블린을(를) 공격했습니다.
꾸엑!..
고블린이(가) 용사을(를) 공격했습니다.
크윽!
용사이(가) 고블린을(를) 공격했습니다.
꾸엑!..
고블린이(가) 사망했습니다.
몬스터 객체 삭제!
플레이어 객체 삭제!