Type(const Type& a);
다른 Type 객체 a를 상수 레퍼런스로 받는다. 여기서 a가 const 이기 때문에 우리는 복사 생성자 내부에서 a의 데이터를 변경할 수 없고, 초직 새롭게 초기화 되는 인스턴트 변수들에게 '복사'만 할 수 있게 된다.
Type::Type(const Type& a) {
std:;cout << "복사 생성자 호출" << std::endl;
hp = a.hp;
shiled = a.shield;
coord_x = a.coord_x;
coord_y = a.coord_y;
damage = a.damage;
}
위와 같이 Type 클래스 내에 정의된 복사생성자의 내부에서 새로 생겨날 a의 인스턴수 변수들에 접근해서 객체의 변수들을 초기화할 수는 있지만,
a.shiled = 3;
이런 식으로 값 자체를 변경할 수는 없다. 왜냐하면 '상수 레퍼런스'로 인자를 받았기 때문이다. 이처럼 인자로 받는 변수의 내용을 함수 내부에서 바꾸지 않는다면, 앞에 const 를 붙여 함수 원형을 선언하는 편이 바람직하다.
주의할 점은, '생성 시에 대입하는 연산', 즉 예를 들어
Type a3 = a;
를 한다면, 복사 생성자가 호출된다. 복사 생성자는 오직, 새로운 인스턴스가 생성될 때에만 호출된다. C++ 컴파일러는 디폴트 복사 생성자를 지원한다.
그러나 디폴트 복사 생성자의 경우에는, 이를테면 이미 원래의 클래스에 동적 할당 메모리 (문자열 같은)가 담긴 멤버 변수가 있었을 경우, 복사를 하려고 했던 원본 인스턴스가 파괴되면서 소멸자를 호출하게 된다면, 복사후 생성된 메모리가 가리키던 멤버 변수의 메모리가 해제되어 버린다.
이를 피하기 위해, 메모리를 '새로 할당'해서 내용을 복사하는 것을 깊은 복사라고 한다. 이와 다르게, 단순히 대입만 해주는 것을 '얕은 복사'라고 한다. 컴파일러가 생성하는 디폴트 복사 생성자의 경우에는 얕은 복사만 할 수 있으므로, 깊은 복사가 필요한 경우 사용자가 직접 복사 생성자를 정의해야 한다.
int Type::total_number = 0;
보통 어떤 클래스에서 자식 클래스들(인스턴스들)을 만들면서 총 개수를 세어줘야 할 때 자주 쓰는데, 생성자가 호출될때 total_number++, 소멸자가 호출될 때 total_number-- 이런 식으로 처리해 주면 쉽다.
기존 함수의 정의 const;
형태로 선언
많은 경우 클래스를 설계할 때, 멤버 변수들은 모두 private
에 넣고, 이 변수들의 값에 접근하는 방법으로 사용하는 함수들을 public
에 넣어 이 함수들을 사용해 값을 리턴하는 방식을 많이 사용한다.
이런 식으로 외부에서 멤버 변수에 접근하는 것을 막고, 그 값은 자유롭게 구할 수 있도록 할 때 const 함수를 많이 사용한다.
(Return type) operator(operator) (parameter of operator)
이를테면 비교 연산자 ==
를 오버로딩 하고자 한다면,
bool operator==(MyString& str);
라는 식으로 쓴다.
연산자를 오버로딩해서, 특정한 타입의 인자들이 들어왔을 때 어떤 걸 해줄건지를 함수 블럭 내부에 정의하면 됨.
operator<<
함수를 정의하면 된다.// cout으로 Sorcerer 객체를 출력하기위해 <<연산자 오버로딩
std::ostream &operator<<(std::ostream &os, const Sorcerer &ref)
{
os << "I am " << ref.getName() << ", " << ref.getTitle() << ", and I like ponies!\n";
return (os);
}
위 함수를 해석하자면, std 네임스페이스 내에 정의된 ostream 클래스 (std::ostream) 에 대한 참조자 타입
을 리턴하는, 연산자 <<에 대한 연산자 오버로딩 함수
는, std::ostream 클래스에 대한 참조자 타입의 os 변수
와, 우리가 정의한 Sorcerer클래스에 대한 상수 타입의 참조자 변수 ref
를 인자로 받는다.
이 함수를 실행하면, os 객체에 문자열 "I am"을 출력하고, ref 인스턴스의 getName() 함수로 이름을 가져온 후 출력하고, 다시 문자열 ", "을 출력하고, ref 인스턴스의 getTitle() 함수로 타이틀을 가져온 후 출력하고, 나머지 문자열 ", and I like ponies!\n"을 출력한다. 그리고 os를 리턴한다.
Sorcerer class는
1. name, title을 갖습니다.
2. 파라미터로 이름, 타이틀을 받는 생성자를 포함합니다.
3. 파라미터 없이는 instanciate 될 수 없습니다. (인스턴스화)
4. 그러나 여전히 Coplien 형태로 작성되어야 합니다.
Sorcerer이 태어날 때: NAME, TITLE, is born! 이 출력되어야 하고요.
Sorcerer이 죽을 때: NAME, TITLE, is dead. Consequnces will never be the same! 이 출력되어야 합니다.
Sorcerer이 자신을 소개할 때: I am NAME, TITLE, and I like ponies! 가 출력 되어야 합니다. <<
연산자 오버로딩을 통해서, Sorcerer은 자신을 소개할 수 있게 될 것입니다.
우리의 Sorcerer은 이제 피해자들을 필요로 합니다. Victim 클래스를 만듭시다. Sorcerer이랑 비슷하게, 이름을 갖고, 생성자는 이름을 인자로 받습니다.
Victim이 태어날 때: Some random victim called NAME just appeard!
Victim이 죽을 때: Victim NAME just died for no apparent reason!
Victim 또한 자신을 소개할 수 있어야 합니다: I'm NAME and I like otters!
우리의 Victim은 Sorcerer에 의해 "polymorphed" 될 수 있습니다. void getPolymorphed() const
메서드를 Victim에 추가하세요.: NAME has been turned into a cute little sheep!
그리고, void polymorph(Victim const &) const
멤버 함수도 Sorcerer에 추가하세요. 사람들을 polymorph할 수 있도록요.
이제, 우리의 Sorcerer이 Victim 말고 다른 것들도 polymorph할 수 있게 변주를 해봅시다. Peon 클래스를 만드세요. Peon 클래스가 태어날 때, "Zog Zog"하고 운답니다. 죽을 때는 "Bleuark..."라고 말하고 죽어요. Peon 클래스는 다음과 같이 polymorphed 됩니다: NAME has been turned into a pink pony!"
주어진 main 함수를 컴파일 하세요. 그리고 주어진 아웃풋이 출력되도록 해 보세요.
파생 클래스도 추가해 보시고요...
무기를 만듭시다. (코플리엔 형식을 따라서...)
무기는 이름을 갖고, 공격에 의한 데미지 포인트를 갖고, AP라는 슈팅 비용을 가집니다. 무기는 특정 소리와 빛 효과를 발생시켜요. attack() 함수를 사용할 때 말이죠. 상속받는 클래스에 따라 달라지게 될 거에요.
이후에, PlasmaRifle 과 PowerFist라는 클래스들을 만들어요.
섭젝에 나와 있는 형태로요. 가지고 놀 많은 무기들이 생겼으니 적과 싸워 봅시다. Enemy 클래스를 만들어요. 마찬가지로 섭젝에서 주어진 대로요.
몇 가지 제한이 있습니다...
1. 적은 hit points, type을 가져요.
2. 데미지를 입으면 HP가 줄어요. 만약 데미지가 0보다 작을 경우 아무 일도 일어나지 않아요. 0 HP 이하로 가면 안됩니다.
몇개의 적을 만들어 보셔요:
SuperMutant, RadScorpion... (주어진 대로요.)
우리는 이제 무기도 만들었고, 무기로 싸울 적들도 만들었고... 이제 싸울 준비만 되면 되겠죠. Character이라는 클래스를 만듭시다. 주어진 대로요.
이름, AP (Action Points), 그리고 현재 사용하는 무기를 가리키는 Aweapon 포인터를 가져요. 태어날 때 40 AP를 갖고, 각각의 상황에서 사용하는 무기에 따라 AP를 잃게 되고, recoverAP() 함수를 호출할 떄마다 10 AP씩 회복하게 될거에요.
문제에서 요구하는 대로 출력하세요: NAME attacks ENEMY_TYPE with a WEAPON_NAME 을요. attack() 함수를 호출할 때요. 이 함수는 지금 갖고 있는 무기의 attack() 메서드에 후행되어야 하겠죠. 만약 장착한 무기가 없다면 attack() 은 아무것도 하면 안돼요. 공격당한 적의 HP를 무기의 데미지 값만큼 빼고, 타겟의 HP가 0이 되면 타겟을 제거하세요.
equip() 함수 또한 무기에 대한 포인터를 저장합니다. 여기에 복사는 개입되지 않아요.
그리고 <<
연산자 오버로딩을 해서, Character 클래스의 속성들을 출력하세요. 모든 필요한 getter 함수들을 넣으셔요: NAME has AP_NUMBER AP and wields a WEAPON_NAME을 출력할 것이고, 만약 무기가 장착되었다면: NAME has AP_NUMBER AP and is unarmed을 출력하게 될 겁니다.
주어진 main문을 컴파일 하고 실행시켜서, 주어진 결과가 나오는지 확인하세요.
이제, Squad 와 TacticalMarine 이라는 미래의 부대를 만듭시다.
ISquad 클래스는...
부대 안에 있는 유닛들이 몇개인지 반환하는 getCount() 함수와,
N번째 유닛에 대한 포인터를 반환하는 getUnit(N)함수,
XXX 유닛을 부대의 끝에 추가하는 push(XXX)함수를 가질 거고, 이 함수는 부대 안에 몇 개의 유닛들이 있는지 반환해요. null 유닛이나, 이미 스쿼드 안에 있는 유닛을 추가하는 건 말이 안되겠죠.
Squad는 Space Marine의 간단한 컨테이너에요. 당신의 군대를 조직하기 위해 필요한 거죠. Squad에 대한 복사 생성이나 대입에 있어서, 이 복사는 필히 깊은 복사여야 해요. 대입에 있어서는, 부대 안에 유닛이 존재한다면, 그 유닛들은 필수로 파괴되어야 해요. (대체되기 전에요.) 모든 유닛들은 new 키워드와 함께 동적 할당 되어야 한다는 것을 알겠죠?
Squad가 파괴되면, 안에 있는 유닛들도 순서대로 파괴되어야 해요.
TacticalMarine 클래스는...
거의 비슷하게, AssaultTerminator을 만들고, 아래의 내용을 반영하세요
주어진 main을 컴파일 및 실행해 보고 주어진 출력 결과가 나오는지 확인하세요.
주어진 AMateria 클래스를 완성해 봅시다...
AMateria의 XP 시스템은 아래와 같이 동작해요:
주의점은, Materia를 다른 것에 대입할 때, 복사되는 것은 말이 안된다는 거죠.
이제 Character 인스턴스는 Materia를 장착할 수 있습니다.
Materia를 직접 만드는 건 귀찮으니까, Materia의 오리지널 소스를 만들어 보세요. MateriaSource 클래스는 다음과 같습니다.