티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.

 

C++은 객체지향 언어입니다. 객체지향은 많은 프로그래머 사이에서 사랑받고 있습니다. 객체지향이 왜 이렇게 사랑받고, 자주 언급되는 것인지, 객체지향이 과연 어떠한 매력을 가지고 있는 것인지, 이번 시간부터 '상속'이라는 것을 배우면서 이해하게 됩니다.

 

상속에 대해 배울 때는 단순한 문법적인 요소만을 배우는 것이 아니라, 상속이 가져다주는 이점을 명확히 이해하는 것이 중요합니다. 

 

상속을 이해하기 앞서

아래의 예제는 OrangeMedia라는 회사가 운영하는 '급여관리 시스템'입니다. 이 회사가 처음 이 시스템을 도입할 당시만 해도 직원의 근무형태는 '정규직' 하나였습니다. 따라서 이 시스템은 정규직 직원을 관리하기 위한 형태로 설계되었습니다. 그럼 우선 정규직 직원의 관리를 목적으로 정의된 클래스를 하나 소개하겠습니다.

class PermanentWorker
{
private:
	char name[40];
	int salary;    //매달 지급하는 급여액

public:
	PermanentWorker(char* name, int money)
		:salary(money)
	{
		strcpy(this->name, name);
	}

	int GetPay(void) const
	{
		return salary;
	}

	void ShowSalaryInfo(void) const
	{
		cout << "name : " << name << endl;
		cout << "salary : " << salary << endl << endl;
	}
};

 

이 회사의 정규직 급여는 입사 당시 정해집니다(급여 인상 부분은 고려하지 않습니다). 따라서 이 클래스는 정규직의 이름과 급여정보를 저장할 수 있는 멤버 변수를 가지고 있습니다.

 

다음은 위에서 정의한 클래스의 객체를 저장 및 관리하기 위한 클래스입니다. PermanentWorker 객체의 저장을 목적으로 배열을 멤버로 지니고 있으며, 저장된 객체의 급여 정보를 출력하기 위한 함수를 멤버로 지니고 있습니다.

class EmployeeHandler
{
private:
	PermanentWorker* empList[50];
	int empNum;

public:
	EmployeeHandler(void) : empNum(0) {}

	void AddEmployee(PermanentWorker* emp)
	{
		empList[empNum++] = emp;
	}

	void ShowAllSalaryInfo(void) const
	{
		for (int i = 0; i < empNum; i++)
		{
			empList[i]->ShowSalaryInfo();
		}
	}

	void ShowTotalSalary(void) const
	{
		int sum = 0;

		for (int i = 0; i < empNum; i++)
		{
			sum += empList[i]->GetPay();
		}
		cout << "salary sum : " << sum << endl;
	}

	~EmployeeHandler()
	{
		for (int i = 0; i < empNum; i++)
		{
			delete empList[i];
		}
	}
};

 

위의 클래스는 앞서 정의한 PermanentWorker 클래스와는 성격이 다릅니다. PermanentWorker 클래스는 정규직의 데이터를 저장하는 성격이 강한 반면, EmployeeHandler 클래스는 다음의 기능들을 처리하는 기능적인 성격이 강합니다.

  • 새로운 직원 정보의 등록
  • 모든 직원의 이번 달 급여정보 출력
  • 이번 달 급여의 총액 출력

 

그리고 이렇게 기능의 처리를 실제로 담당하는 클래스를 가리켜 '컨트롤 클래스' 또는 '핸들러 클래스'라고 합니다. 컨트롤 클래스는 기능 제공의 핵심이 되기 때문에 모든 객체지향 프로그램에서 반드시 존재하는 클래스입니다.

 

마지막으로 위의 두 클래스를 기반으로 작성된 main 함수를 제시하겠습니다.

int main(void)
{
	//직원 관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
	EmployeeHandler handler;

	//직원의 등록
	handler.AddEmployee(new PermanentWorker("KIM", 1000));
	handler.AddEmployee(new PermanentWorker("JO", 1500));
	handler.AddEmployee(new PermanentWorker("KOEY", 4000));

	//이번 달에 지불해야 할 급여의 정보
	handler.ShowAllSalaryInfo();

	//이번 달에 지불해야 할 급여의 총합
	handler.ShowTotalSalary();

	return 0;
}

/*
실행결과

name : KIM
salary : 1000

name : JO
salary : 1500

name : KOEY
salary : 4000

salary sum : 6500

*/

 

지금 소개한 이 시스템은 언뜻 문제가 없어 보입니다. 그러나 모든 소프트웨어 설계에 있어서 중요시하는 것 중 하나는 다음과 같습니다.

  • 요구사항의 변경에 대응하는 프로그램의 유연성
  • 기능의 추가에 따른 프로그램의 확장성

 

예를 들어서, 프로그램 사용자의 업무 형태가 바뀌어서 프로그램 변경을 요구할 수도 있는 일이고, 회사의 업무가 확장되어서 프로그램의 기능 추가를 요구할 수도 있습니다. 그런데 좋은 프로그램은 이러한 변경의 요구에 대처가 가능해야 합니다. 즉, 프로그램 사용자의 요구에 다음과 같이 이야기하는 일은 없어야 합니다.

 

"그 기능을 변경하려면 프로그램을 거의 처음부터 다시 만들다시피 해야 하는데요."

 

오렌지 미디어 급여 관리 확장성 문제

위에서 소개한 '급여관리 시스템'을 사용하던 OrangeMedia에서 다음과 같이 요구를 해왔습니다.

 

"덕분에 회사가 많이 번창했습니다. 이제 부서도 세분화되었고 직원도 늘어나게 되었죠. 그러다 보니 예전에 쓰던 프로그램이 이제는 조금 맞지가 않네요. 왜냐하면 직원의 고용형태가 조금 다양해졌거든요."

 

이전에는 직원의 고용형태가 '정규직' 하나였는데, 이제는 영업직과 임시직도 고용하게 되었습니다. 그리고 급여의 계산 방식도 달라졌습니다. 정규직은 매달 급여가 정해져 있었지만, 영업직은 '기본급여 + 인센티브' 현태의 급여를 받고, 임시직은 '시간당 급여 * 일한 시간'의 형태로 급여를 받습니다.

 

따라서 OrangeMedia의 요구사항을 만족시키기 위해서는 영업직과 임시직의 특성을 프로그램 내에 반영해야 합니다. 그러면 이 문제를 다음과 같이 매우 쉽게 생각할 수도 있습니다.

 

"영업직을 의미하는 SalesMan 클래스와 임시직을 의미하는 Temporary 클래스를 추가하면 되겠군"

 

과연 그렇게만 하면 문제가 해결되는 걸까요? 한 번 차근차근 생각해보겠습니다.

  • 우선 위에서 생각한 것처럼 영업직과 임시직의 정보를 저장할 클래스를 두 개 추가해야 할 것 같습니다.
  • 그리고 핸들러 클래스 내에도 영업직과 임시직 객체를 저장할 배열을 선언해야 하고, 또 영업직과 임시직 객체의 개수를 저장할 int형 변수도 두 개 선언해야 할 것 같습니다.
  • 그리고 해당 int형 변수를 0으로 초기화하도록 생성자를 수정해야겠습니다.
  • 그리고 영업직과 임시직 객체를 핸들러 클래스의 배열에 추가할 수 있도록 AddEmployee 함수를 오버 로딩하여 2개 더 추가해야 할 것 같습니다.
  • ShowAllSalesInfo 함수에 두 개의 for문을 추가하여 영업직과 임시직에 대해서도 정보를 출력할 수 있도록 수정해야겠습니다.
  • ShowTotalSalary함수도 역시 두 개의 for문을 추가하여 영업직과 임시직의 급여까지도 총합에 추가할 수 있도록 수정해야겠습니다.
  • 핸들러 클래스의 소멸자에 영업직과 임시직 객체가 동적 할당된 메모리 공간을 지울 수 있는 delete연산을 추가해야 할 것 같습니다.

 

사실상 수정하지 않아도 되는 구석이 단 하나도 없습니다. 즉, 프로그램을 처음부터 다시 만들다시피 하는 대대적인 공사가 이뤄져야 하는 것입니다. 따라서 위 시스템은 확장성 측면에서 결코 좋은 점수를 줄 수가 없습니다. 좋은 점수를 받으려면 영업직과 임시직 클래스의 추가로 인한 변경을 최소화할 수 있어야 합니다. 이왕이면 핸들러 클래스를 조금도 변경하지 않아도 된다면, 매우 좋은 점수를 줄 수 있을 것입니다.

 

이제부터 공부할 '상속'을 적용하면 이러한 일이 가능합니다. 상속을 이용해 위 문제를 해결하기 전에 우선 상속의 문법적 이해부터 시작하겠습니다. 이 문제의 해결을 위해 알아야 할 것이 많기 때문입니다.

 

그리고 위의 시나리오에서 소개된 문제를 '오렌지 미디어 급여관리 확장성 문제'라고 칭하겠습니다.

 

상속의 기본 문법

상속이라 하면, 아버지의 재산을 상속받았다고 쉽게 생각해볼 수 있습니다. 그런데 아버지의 재산뿐만 아니라 아들은 아버지로부터 좋은 목소리와 큰 키도 물려받았다고 할 수 있습니다. 클래스의 상속은 바로 이렇게 진행됩니다.

 

예를 들어서, UnivStudent 클래스가 Person 클래스를 상속받게 되면, UnivStudent 클래스는 Person 클래스가 가지고 있는 모든 멤버 변수와 멤버 함수를 물려받습니다. 따라서 UnivStudent 클래스에는 UnivStudent 클래스의 멤버뿐만 아니라 Person 클래스의 멤버까지 존재하게 됩니다.

 

예제를 통해 확인해보겠습니다. 우선 Person 클래스를 다음과 같이 정의했습니다.

class Person
{
private:
	int age;
	char name[50];

public:
	Person(int age, char* name)
		:age(age)
	{
		strcpy(this->name, name);
	}

	void WhatsYourName() const
	{
		cout << "My nama is " << name << endl;
	}

	void HowOldAreYou() const
	{
		cout << "I'm " << age << endl;
	}
};

 

위 클래스를 이해하기는 어렵지 않을 것입니다. 이어서 다음 UnivStudent 클래스를 정의하겠습니다.

class UnivStudent : public Person
{
private:
	char major[50];

public:
	UnivStudent(const char* name,const int age,const char* major)
		:Person(name, age)
	{
		strcpy(this->major, major);
	}

	void WhoAreYou() const
	{
		WhatsYourName();
		HowOldAreYou();
		cout << "My major is " << major << endl << endl;
	}
};

 

위 클래스는 Person 클래스를 상속받고 있습니다. 아래의 코드가 그 의미를 갖습니다.

class UnivStudent : public Person

 

UnivStudent 클래스는 Person 클래스를 'public 상속' 받고 있습니다. 여기서 public의 의미는 나중에 살펴보고, 우선은 Person 클래스를 상속했다는 것에 주목하면 됩니다.

 

그리고 UnivStudent 클래스의 생성자를 보겠습니다. 해당 클래스는 오로지 major만을 멤버 변수로 가지고 있습니다. 그런데 생성자에서는 name과 age까지 입력받고 있습니다. 왜냐하면 해당 클래스가 Person 클래스를 상속받고 있고, Person 클래스가 name과 age를 멤버 변수로 가지고 있기 때문입니다.

 

Person 클래스를 상속받고 있는 UnivStudent 클래스는 Person 클래스의 멤버 변수까지 초기화해야 할 의무가 있습니다. 그래서 UnivStudent의 생성자가 name과 age값까지 입력받고 있습니다. 그리고 Person 클래스의 멤버 변수들을 초기화하기 위해 멤버 이니셜 라이저를 이용하여 Person 클래스의 생성자를 호출하였습니다. 아래의 코드가 바로 그 의미를 가지고 있습니다.

UnivStudent(const char* name,const int age,const char* major)
		:Person(name, age)    //Person 클래스의 생성자 호출
	{
		strcpy(this->major, major);
	}

 

다음으로 위 클래스의 WhoAreYou 함수를 보겠습니다. 해당 함수를 보면 WhatsYourName 함수와 HowOldAreYou 함수를 호출하고 있습니다. 이 두 함수는 UnivStudent 클래스가 가지고 있지는 않지만 Person 클래스가 멤버 함수로 가지고 있기 때문에 이를 상속한 UnivStudent 클래스에서 호출할 수 있게 되는 것입니다.

 

다음은 위 두 클래스를 이용하여 실행결과를 확인해보는 예제입니다.

#include <iostream>
#include <cstring>

using namespace std;

class Person
{
private:
	char name[50];
	int age;

public:
	Person(const char* name, const int age)
		:age(age)
	{
		strcpy(this->name, name);
	}

	void WhatsYourName() const
	{
		cout << "My nama is " << name << endl;
	}

	void HowOldAreYou() const
	{
		cout << "I'm " << age << endl;
	}
};

class UnivStudent : public Person
{
private:
	char major[50];

public:
	UnivStudent(const char* name,const int age,const char* major)
		:Person(name, age)
	{
		strcpy(this->major, major);
	}

	void WhoAreYou() const
	{
		WhatsYourName();
		HowOldAreYou();
		cout << "My major is " << major << endl << endl;
	}
};

int main(void)
{
	UnivStudent ustd1("Lee", 22, "Computer eng.");
	ustd1.WhoAreYou();

	UnivStudent ustd2("Yoon", 21, "Electronic eng.");
	ustd2.WhoAreYou();

	return 0;
}

/*
실행결과

My nama is Lee
I'm 22
My major is Computer eng.

My nama is Yoon
I'm 21
My major is Electronic eng.

*/

 

그런데 이쯤 되면 궁금할 수도 있습니다. UnivStudent 클래스가 Person 클래스를 상속받았다면, UnivStudent 클래스의 멤버 함수를 통해서 Person 클래스의 private 멤버 변수에 접근할 수 있을까요?

 

UnivStdent 객체는 Person 클래스의 멤버까지 상속받아 가지고 있기 때문에, 얼핏 보면 가능해 보입니다. 하지만 이는 불가능합니다. 왜냐하면 한 클래스의 private 변수는 그 클래스 외부에서는 절대 접근할 수가 없기 때문입니다. 분명히 UnivStudent 클래스의 객체는 Person 클래스의 멤버들도 가지고 있지만, 분명히 각 멤버들의 소속 클래스는 다릅니다. 따라서 Person 클래스를 상속받은 UnivStudent의 멤버 함수에서도 Person 클래스의 private 멤버 변수에는 접근할 수가 없게 되는 것입니다.

 

용어 정리

이쯤에서 용어 정리를 하고 가겠습니다. 앞선 예에서 UnivStudent 클래스는 Person 클래스를 상속받고 있습니다. 이때 Person 클래스를 '상위 클래스'라고 하며, 이를 상속받은 UnivStudent 클래스를 '하위 클래스'라고 합니다.

 

혹은 '기초 클래스''유도 클래스',

혹은 '슈퍼 클래스''서브 클래스',

혹은 '부모 클래스''자식 클래스'라고 부르기도 합니다.

이 중에서는 '기초 크래스''유도 클래스'라고 표현하는 것을 자주 사용한다고 합니다.

 

기초 클래스와 유도 클래스의 객체 생성 과정

유도 클래스의 객체 생성과정은 매우 중요하기 때문에 조금 더 정확히 이해하고 넘어가겠습니다. 먼저 다음 에제의 실행결과를 통해서 유도 클래스의 객체 생성과정을 나름대로 정리해봅니다. 참고로 여기서 중요한 것은 기초 클래스의 생성자 호출입니다.

#include <iostream>

using namespace std;

class Base
{
private:
	int baseNum;
public:
	Base() : baseNum(20)
	{
		cout << "Base()" << endl;
	}

	Base(int n) : baseNum(n)
	{
		cout << "Base(int n)" << endl;
	}

	void ShowBaseData()
	{
		cout << baseNum << endl;
	}
};

class Derived : public Base
{
private:
	int derivNum;
public:
	Derived() : derivNum(30)
	{
		cout << "Derived()" << endl;
	}

	Derived(int n) : derivNum(n)
	{
		cout << "Derived(int n)" << endl;
	}

	Derived(int n1, int n2) : Base(n1),  derivNum(30)
	{
		cout << "Derived(int n1, int n2)" << endl;
	}

	void ShowDeriveData()
	{
		ShowBaseData();
		cout << derivNum << endl;
	}
};

int main(void)
{
	cout << "case1... " << endl;
	Derived dr1;
	dr1.ShowDeriveData();
	cout << "-------------------------" << endl;
	
	cout << "case2... " << endl;
	Derived dr2(12);
	dr2.ShowDeriveData();
	cout << "-------------------------" << endl;
	
	cout << "case3... " << endl;
	Derived dr3(23, 24);
	dr3.ShowDeriveData();

	return 0;
}

/*
실행결과

case1...
Base()
Derived()
20
30
-------------------------
case2...
Base()
Derived(int n)
20
12
-------------------------
case3...
Base(int n)
Derived(int n1, int n2)
23
30

*/

 

예제를 살펴보면, 유도 클래스의 객체를 생성하게 되면 유도 클래스 객체의 생성자뿐만 아니라, 기초 클래스의 생성자까지 100% 호출됩니다. 그리고 유도 클래스의 객체를 생성하면서 직접 기초 클래스의 생성자를 호출하지 않으면, 기초 클래스의 void 생성자가 자동으로 호출됨을 확인할 수 있습니다.

 

순서는 기초 클래스의 생성자가 먼저 호출되고 유도 클래스의 생성자가 호출됩니다.

 

기초 클래스와 유도 클래스의 객체 소멸 과정

유도 클래스의 객체 소멸 과정도 생성 과정과 비슷합니다. 유도 클래스의 소멸 과정에서 기초 클래스의 소멸자도 함께 호출이 됩니다. 순서는 유도 클래스의 소멸자가 먼저 호출이 되고, 다음 기초 클래스의 소멸자가 호출됩니다.

 

예제를 통해 확인해 보겠습니다.

#include <iostream>

using namespace std;

class Base
{
private:
	int baseNum;
public:
	Base(int n) : baseNum(n)
	{
		cout << "Base(int n) : " << baseNum << endl;
	}

	~Base()
	{
		cout << "~Base() : " << baseNum << endl;
	}
};

class Derived : public Base
{
private:
	int derivNum;
public:
	Derived(int n) : Base(n), derivNum(n)
	{
		cout << "Derived(int n) : " << derivNum << endl;
	}

	~Derived()
	{
		cout << "~Derived() : " << derivNum << endl;
	}
};

int main(void)
{
	Derived drv1(15);
	Derived drv2(27);

	return 0;
}

/*
실행결과

Base(int n) : 15
Derived(int n) : 15
Base(int n) : 27
Derived(int n) : 27
~Derived() : 27
~Base() : 27
~Derived() : 15
~Base() : 15

*/

 

 

protected 선언

C++의 접근 제어 지시자는 private, protected, public 세 가지가 존재합니다. 앞서 이 중 private와 public에 대해서는 배웠지만, protected는 상속과 관련이 있기 때문에 배우는 것을 미뤘습니다. 따라서 상속을 배운 지금 protected에 대해 배우도록 합니다.

 

protected는 허용하는 접근의 범위가 private와 public의 사이에 있으면서도 private과 유사합니다. 아래에 정의된 클래스를 살펴보겠습니다.

class Base
{
private:
	int num1;
protected:
	int num2;
public:
	int num3;

	void ShowData()
	{
		cout << num1 << ", " << num2 << ", " << num3 << endl;
	}
};

class Derived : public Base
{
public:
	void ShowBaseMember()
	{
		cout << num1;    //컴파일 에러 발생
		cout << num2;
		cout << num3;
	}
};

 

protected는 기본적으로 private과 같습니다. public과 달리 클래스의 외부에서는 이 변수에 접근할 수 없습니다. 그런데 private과 유일한 차이점은 클래스를 상속할 때 발생합니다. 위 예제에서 Derived 클래스는 Base클래스를 상속받고 있습니다. 앞서 배운 내용에 따르면 유도 클래스에서는 기초 클래스의 private 멤버 변수에 접근하는 것이 불가능했습니다. 그런데 기초 클래스의 protected 변수에 접근하는 것은 가능합니다. 이것이 private과 protected의 유일한 차이점입니다.

 

protected는 private이나 public에 비해서는 그리 많이 사용되지는 않습니다. 물론 유도 클래스에게만 제한적으로 접근을 허용한다는 측면에서 유용하게 사용될 수 있는 키워드입니다. 그러나 기본적으로 기초 클래스와 이를 상속하는 유도 클래스 사이에서도 '정보은닉'은 지켜지는 게 좋습니다.

 

세 가지 형태의 클래스 상속

클래스를 상속하는 데에는 세 가지 형태가 있습니다. 지금까지 사용해 왔던 public상속 외에도 private상속, protected상속이 더 있습니다. 

 

protected상속은 다음과 같은 의미를 가집니다.

  • protected보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다.

 

다음 예제를 보겠습니다.

#include <iostream>
using namespace std;

class Base
{
private:
	int num1;
protected:
	int num2;
public:
	int num3;

	Base() : num1(1), num2(2), num3(3) {}
};

class Derived : protected Base {};

int main(void)
{
	Derived drv;
	cout << drv.num3 << endl;    //컴파일 에러 발생

	return 0;
}

 

위 예제에서 기초 클래스인 Base는 유도 클래스인 Derived에 멤버들을 상속합니다. 이때 protected상속이기 때문에 protected 보다 범위가 넓은 public은 protected로 변경시켜서 상속합니다. 그리고 이를 확인하기 위해 main함수에서 num3에 직접 접근을 시도하지만, 컴파일 에러가 발생합니다.

 

원래 num3는 public으로 선언이 되었기 때문에 num3에 직접 접근할 수 있어야 하지만 protected 상속으로 인해서 범위가 protected로 변경되었기 때문에 클래스 외부에서 직접 접근하는 것이 불가능합니다.

 

이러한 상속 개념은 private과 public 둘 다 동일합니다.

 

private상속은 다음과 같은 의미를 가집니다.

  • private보다 접근 허용 범위가 넓은 멤버는 private으로 변경해서 상속한다.
  • 따라서 모든 멤버를 private으로 변경하여 상속한다.

public상속은 다음과 같은 의미를 가집니다.

  • public보다 접근 허용 범위가 넓은 멤버는 public으로 변경해서 상속한다.
  • 따라서 모든 멤버의 접근 허용 범위를 그대로 유지하여 상속한다.

 

상속을 위한 조건

클래스를 상속할 때는 조건이 잘 맞아야 합니다. 조건이 맞지 않으면 상속을 안 하느니만 못하게 됩니다.

 

IS - A 관계의 성립

상속을 위한 기본 조건으로 'IS-A관계의 성립'이 있습니다. 이 조건은 어렵지 않습니다. 이 조건을 다르게 말하면 '유도 클래스는 기초 클래스의 세부항목이어야 한다'라고 말할 수 있습니다. 벤다이어그램 혹은 집합을 생각하면 쉽습니다.

  • 유도 클래스 ⊂ 기초 클래스 (유도 클래스는 기초 클래스에 속한다)

혹은 다음과 같이 표현할 수도 있습니다.

  • 유도 클래스 si a 기초 클래스(이를 IS-A 관계라고 합니다)

 

예를 들어보겠습니다. '대학생' 클래스와 '인간' 클래스가 정의되어 있습니다. 그럼 다음과 같은 관계가 성립됨을 알 수 있습니다.

  • 대학생 ⊂ 인간
  • 대학생 si a 인간

 

따라서 대학생 클래스는 인간 클래스를 상속받을 수 있습니다. 이런 관계는 '사자'와 '동물', '소나무'와 '나무', '슬라임'과 '몬스터' 등도 마찬가지입니다.

 

HAS - A 관계의 성립

상속의 조건으로 IS-A 관계뿐만 아니라 HAS-A 관계도 있습니다.

 

유도 클래스는 기초 클래스의 모든 멤버들을 상속받기 때문에 이를 HAS-A 관계를 표현할 때도 사용할 수가 있습니다. 예를 들어보겠습니다. '총' 클래스와 '총알' 클래스가 있습니다. 총은 총알을 소유하고 있기 때문에 총알 클래스를 총 클래스가 상속받아서 총알 데이터를 소유할 수 있습니다.

 

이는 '경찰관'과 '수갑', '대학생'과 '학생증'과 같은 관계에도 적용할 수 있습니다.

 

하지만 가능하면 HAS-A관계에는 상속을 하지 않는 것이 좋습니다. 왜냐하면 수갑을 들고 있지 않은 경찰관이 있을 수 있고, 수갑뿐만 아니라 총과 진압봉까지 가진 경찰관을 표현하려면 추가적인 상속을 해야 하는 등 활용성이 그다지 좋지 못합니다. 대신 HAS-A 관계는 다음 예제와 같이 표현합니다.

#include <iostream>
#include <cstring>
using namespace std;

class Gun
{
private:
	int bullet;
public:
	Gun(int bulletNum) : bullet(bulletNum) {}

	void Shot()
	{
		cout << "BANG!" << endl;
		if (bullet - 1 < 0) bullet = 0;
		else bullet--;
	}
};

class Police
{
private:
	int handcuffs;
	Gun* pistol;
public:
	Police(int handcuffNum, int bulletNum) : handcuffs(handcuffNum)
	{
		if (bulletNum > 0)
		{
			pistol = new Gun(bulletNum);
		}
		else pistol = NULL;
	}

	void PutHandcuff()
	{
		cout << "SNAP!" << endl;
		if (handcuffs - 1 < 0) handcuffs = 0;
		else handcuffs--;
	}

	void Shot()
	{
		if (pistol == NULL) cout << "Hut BANG!" << endl;
		else pistol->Shot();
	}

	~Police()
	{
		if (pistol != NULL) delete pistol;
	}
};

int main(void)
{
	Police pman1(2, 12);
	pman1.Shot();
	pman1.PutHandcuff();

	Police pman2(4, 0);
	pman2.Shot();
	pman2.PutHandcuff();

	return 0;

}

/*
실행결과

BANG!
SNAP!
Hut BANG!
SNAP!

*/

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함