여니의 프로그래밍 study/C, C++, C#

[열혈C++ Programming] 8장 예제문제 / 상속과 다형성

여니's 2019. 9. 23. 20:23

안녕하세요 공대생 블로거 여니입니다

오늘은 윤성우 저 열혈 C++ Programming 8장 상속과 다형성 예제 문제를 같이 다뤄보려고 합니다

(저도 소스코드를 직접 쳐보면서 공부하고 있어요)

 

<8-1 객체 포인터의 참조 관계>

일단 소스코드를 직접 쳐보시고 글을 읽어주세요~

 

ObjectPointer.cpp



#include 

using namespace std;



class Person

{

public:

	void Sleep() { cout << "Sleep" << endl;}



};

class Student :public Person

{

public:

	void Study() { cout << "Study" << endl; }

};

class PartTimeStudent : public Student

{

public:

	void Work() { cout << "Work" << endl; }

};

int main(void)

{

	Person* ptr1 = new Student();

	Person* ptr2 = new PartTimeStudent();

	Student* ptr3 = new PartTimeStudent();

	ptr1->Sleep();

	ptr2->Sleep();

	ptr3->Study();

	delete ptr1, ptr2, ptr3;

	return 0;

}



C++에서 AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접 혹은 간접적으로 상속하는 모든 객체를 가리킬 수 있다

이 말이 무슨 뜻일까요?

 

코드를 보면서 설명해드리도록 하겠습니다.

코드에서는 Person, Student, PartTimeStudent 이렇게 총 3개의 클래스를 정의했습니다

그런데 여기서 Student는 Person을 상속하고, PartTimeStudent는 Student를 상속하고 있습니다

 

여기서 질문!

Person과 PartTimeStudent는 어떤 관계일까요?

 

PartTimeStudent가 Person을 간접적으로 상속하고 있습니다

 

그래서 위 코드를 통해 다시 설명해드리자면

C++에서 Person형 포인터변수는 Person객체 또는 Person을 직접(Student) 혹은 간접적(PartTimeStudent)으로 상속하는 모든 객체를 가리킬 수 있다는 뜻이 됩니다

 

 

메인 함수를 살펴보게 되면

ptr1,ptr2는 Person형 포인터변수, ptr3는 Student형 포인터변수

Person형 포인터변수는 Student와 PartTimeStudent 객체를 가리 킬 수 있고,

Student형 포인터변수는 PartTimeStudent 객체를 가리 킬 수 있다는 결론이 나오게 되는것이죠~

 

그래서 위 코드가 오류 나지 않고 올바른 결과값이 출력되는걸 확인 할 수 있습니다!

 

이번엔 7장에서 다뤘던 "오렌지 미디어 급여 관리 확장성 문제"의 1차적 해결과 함수 오버라이딩"에 대한 소스코드를 다뤄 보려고 합니다!

7장에서 문제점을 제시 했었는데 기억이 나지 않는다면 다시 7장을 복습 하고 오는게 좋을 것 같다는 필자의 말이 더더욱 와닿았습니다.

(왜냐하면 저 조차도 기억이 안났기 때문에.. 복습하기 귀찮다고 패스해버리면 실력이 절대 늘지 않습니다.. )

 

 

다시 본론으로 돌아와서 확장성 문제라 함은

이전에는 직원의 고용형태가 정규직 하나였는데, 회사가 커져서 영업직과 임시직이라는 새로운 고용형태가 생겼고 급여 계산 방식 등 프로그램을 고쳐야 할 부분이 많다라는 점인데.. 포인터의 특성을 이용하면 대공사를 거치지 않아도 됩니다!

 

 



 

#define _CRT_SECURE_NO_WARNINGS

#include 

#include 

using namespace std;



class Employee {

private:

	char name[100];

public:

	Employee(const char* name)

	{

		strcpy(this->name, name);

	}

	void ShowYourName() const

	{

		cout <<"name : " << name << endl;

	}

};

class PermanentWorker :public Employee

{

private:

	int salary;

public:

	PermanentWorker(const char* name, int money) :Employee(name), salary(money)

	{}

	int GetPay() const

	{

		return salary;

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout << "salary : " << GetPay() << endl << endl;

	}

};

class EmployeeHandler

{

private:

	Employee* empList[50];

	int empNum;

public:

	EmployeeHandler() :empNum(0) {}

	void AddEmployee(Employee* emp)

	{

		empList[empNum++] = emp;

	}

	void ShowAllSalaryInfo() const

	{}

	void ShowTotalSalary() const

{

		int sum = 0;

		cout << "salary num :" << sum << endl;

	}

	~EmployeeHandler()

	{

		for (int i = 0; i < empNum; i++)

		{

			delete empList[i];

		}

	}

};

int main(void)

{

	EmployeeHandler handler;

	handler.AddEmployee(new PermanentWorker("kim", 1000));

	handler.AddEmployee(new PermanentWorker("yeon", 2000));

	handler.AddEmployee(new PermanentWorker("hong", 3000));



	handler.ShowAllSalaryInfo();

	handler.ShowTotalSalary();

	return 0;



}



 

책에 나와있는 소스를 똑같이 입력해서 프로그램을 돌렸더니 오류들이 뜨더라구요..

E0289인수 목록이 일치하는 생성자의 인스턴스가 없습니다.

C2664 const char [5]에서 "char *"(으)로 변환 할 수 없습니다.

C4996 "strcpy":...

 

일단 C2664 에러와 E0289 에러는 char * 앞에 const를 붙여주면 문제가 해결됩니다

하지만 C4996 에러는 해결이 되지 않았습니다

 

C4996에러는 #define _CRT_SECURE_NO_WARNINGS 코드를 소스코드 맨 첫줄에 넣어주셔야 에러를 방지 할 수 있습니다~!

 

 

#define _CRT_SECURE_NO_WARNINGS

#include 

#include 

using namespace std;



class Employee {

private:

	char name[100];

public:

	Employee(const char* name)

	{

		strcpy(this->name, name);

	}

	void ShowYourName() const

	{

		cout <<"name : " << name << endl;

	}

};

class PermanentWorker :public Employee

{

private:

	int salary;

public:

	PermanentWorker(const char* name, int money) :Employee(name), salary(money)

	{}

	int GetPay() const

	{

		return salary;

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout << "salary : " << GetPay() << endl << endl;

	}

};

class EmployeeHandler

{

private:

	Employee* empList[50];

	int empNum;

public:

	EmployeeHandler() :empNum(0) {}

	void AddEmployee(Employee* emp)

	{

		empList[empNum++] = emp;

	}

	void ShowAllSalaryInfo() const

	{}

	void ShowTotalSalary() const

	{

		int sum = 0;

		cout << "salary num :" << sum << endl;

	}

	~EmployeeHandler()

	{

		for (int i = 0; i < empNum; i++)

		{

			delete empList[i];

		}

	}

};

int main(void)

{

	EmployeeHandler handler;

	handler.AddEmployee(new PermanentWorker("kim", 1000));

	handler.AddEmployee(new PermanentWorker("yeon", 2000));

	handler.AddEmployee(new PermanentWorker("hong", 3000));



	handler.ShowAllSalaryInfo();

	handler.ShowTotalSalary();

	return 0;



}

위 코드를 입력하시면 오류 나지 않고 코드가 잘 돌아갑니다!

 

영업직과 임시직까지 포함한 코드를 한번 돌려보도록 할게요

Employ.cpp

#define _CRT_SECURE_NO_WARNINGS

#include 

#include 

using namespace std;



class Employee {

private:

	char name[100];

public:

	Employee(const char* name)

	{

		strcpy(this->name, name);

	}

	void ShowYourName() const

	{

		cout <<"name : " << name << endl;

	}

};

class PermanentWorker :public Employee

{

private:

	int salary;

public:

	PermanentWorker(const char* name, int money) :Employee(name), salary(money)

	{}

	int GetPay() const

	{

		return salary;

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout << "salary : " << GetPay() << endl << endl;

	}

};

class EmployeeHandler

{

private:

	Employee* empList[50];

	int empNum;

public:

	EmployeeHandler() :empNum(0) {}

	void AddEmployee(Employee* emp)

	{

		empList[empNum++] = emp;

	}

	void ShowAllSalaryInfo() const

	{

		/*for (int i = 0; i < empNum; i++)

		{

			empList[i]->ShowSalaryInfo();

		}*/

	}

	void ShowTotalSalary() const

	{

		int sum = 0;

		/*for (int i = 0; i < empNum; i++)

		{

			sum += empList[i]->GetPay();

		}*/

		cout << "salary num :" << sum << endl;

	}

	~EmployeeHandler()

	{

		for (int i = 0; i < empNum; i++)

		{

			delete empList[i];

		}

	}

};

class TemporaryWorker :public Employee

{

private:

	int workTime;

	int payPerHour;

public:

	TemporaryWorker(const char* name, int pay) :Employee(name), workTime(0),payPerHour(pay) {}

	void AddWorkTime(int time)

	{

		workTime += time;

	}

	int GetPay() const

	{

		return workTime * payPerHour;

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout << "salary : " << GetPay() << endl << endl;

	}

};

class SalesWorker :public PermanentWorker

{

private:

	int salesResult; // 월 판매실적

	double bonusRatio; // 상여금 비율

public:

	SalesWorker(const char* name, int money, double ratio) :PermanentWorker(name, money), salesResult(0), bonusRatio(ratio) {}

	void AddSalesResult(int value)

	{

		salesResult += value;

	}

	int GetPay() const

	{

		return PermanentWorker::GetPay() + (int)(salesResult + bonusRatio);

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout << "salary : " << GetPay() << endl << endl;

	}

};

int main(void)

{

	EmployeeHandler handler;

	handler.AddEmployee(new PermanentWorker("kim", 1000));

	handler.AddEmployee(new PermanentWorker("yeon", 2000));

	

	TemporaryWorker* alba = new TemporaryWorker("Jung", 700);

	alba->AddWorkTime(5);

	handler.AddEmployee(alba);



	SalesWorker* seller = new SalesWorker("Hong", 1000, 0.1);

	seller->AddSalesResult(7000);

	handler.AddEmployee(seller);





	handler.ShowAllSalaryInfo();

	handler.ShowTotalSalary();

	return 0;

}

함수 오버로딩  vs 함수 오버라이딩

(제가 요즘 가장 헷갈리고 있는 용어들이기도 합니다 ㅠㅠ)

 

간단히 말씀드리자면

오버로딩은 함수의 이름은 똑같으나 매개변수 자리에 들어가는 인자의 갯수나 타입으로 구분해서 사용하는 것! 즉 이름만 같지 새로운 함수를 만든다는 개념입니다

 

오버라이딩은 부모 클래스에 있는 메소드를 자식클래스에서 재정의하는 개념인데요!

즉 부모클래스를 무시한채 오버라이딩 한 함수를 사용한다는 뜻입니다!

 

 

<8-2 가상함수(Virtual Function) >

이번엔 Chanper 8-2 가상함수 개념에 대해 살펴보고자 합니다~!

(TMI) 이 책은 굉장히 자세하게 내용이 나와있고, 설명도 너무 친철해서 좋은데 앞챕터에서 사용하던 소스 코드를 모르면 다시 앞으로 돌아가서 그부분을 공부하고 와야 한다는 단점이 있어요 ㅠㅠ 그리고 책장을 왔다갔다하면서 공부해야하니까 약간 정신사나운 느낌.. 왜냐면 이미 한번 앞에서 거론했던 소스코드라서 생략이 됬더라구요..

 

 

제가 예제문제들을 모아서 올리는 이유중에 하나이기도 합니다!

잡담 그만하고 이제 소스코드를 다시 들여다볼까요~!?

 


#define _CRT_SECURE_NO_WARNINGS

#include 

#include 

using namespace std;

class First {

public:

	void MyFunc() { cout << "FirstFunc" << endl; }

};

class Second :public First

{

public:

	void MyFunc() { cout << "SecondFunc" << endl; }

};

class Third :public Second

{

public:

	void MyFunc() { cout << "ThirdFunc" << endl; }

};



int main(void)

{

	Third* tptr = new Third();

	Second* sptr = tptr;

	First* fptr = sptr;



	fptr->MyFunc();

	sptr->MyFunc();

	tptr->MyFunc();

	delete tptr;

	return 0;

}

MyFunc함수를 오버라이딩 해서 사용하고 있네요!

First,Second,Third는 상속의 관계에 놓여있죠?

메인 함수를 살펴 보면,

Third형 포인터 변수를 선언하는데 tptr는 Third객체를 생성한 다음 Third형, Second형, First형 포인터 변수를 이를 참조하고 있어요!!

 

 

오버라이딩을 하면 부모객체에 있던 기존 함수는 사용하지 않는 다고 했죠?

포인터의 자료형을 기반으로 호출대상이 결정되기 때문에

결과값은 이렇게 나옵니다~!

 

 

이제 가상함수에 대해 살펴보도록 하겠습니다!


#define _CRT_SECURE_NO_WARNINGS

#include 

#include 

using namespace std;

class First {

public:

	virtual void MyFunc() { cout << "FirstFunc" << endl; }

};

class Second :public First

{

public:

	virtual void MyFunc() { cout << "SecondFunc" << endl; }

};

class Third :public Second

{

public:

	virtual void MyFunc() { cout << "ThirdFunc" << endl; }

};



int main(void)

{

	Third* tptr = new Third();

	Second* sptr = tptr;

	First* fptr = sptr;



	fptr->MyFunc();

	sptr->MyFunc();

	tptr->MyFunc();

	delete tptr;

	return 0;

가상함수의 선언은 virtual 키워드의 선언을 통해서 이루어집니다.

상위 클래스의 함수가 virtual로 선언된다면, 하위클래스는 줄줄이 다 virtual함수로 됩니다.

(물론 하위클래스에는 virtual 선언을 하지 않아도 가상함수가 자동으로 되지만 써주는게 좋습니다 가독성을 위해서요)

 

결과값은 아래 캡처본처럼 나오게 됩니다.

포인트 변수가 실제로 가리키는 객체를 참조해서 호출의 대상을 결정하기 때문에 ThirdFunc이 3번 출력되게 됩니다.


#define _CRT_SECURE_NO_WARNINGS

#include 

#include 

using namespace std;

class Employee {

private:

	char name[100];

public:

	Employee(const char* name)

	{

		strcpy(this->name, name);

	}

	void ShowYourName() const

	{

		cout  << "name : " << name  << endl;

	}

	virtual int GetPay() const

	{

		return 0;

	}

	virtual void ShowSalaryInfo() const

	{}

 };

class PermanentWorker :public Employee

{

private:

	int salary;

public:

	PermanentWorker(const char* name, int money) :Employee(name), salary(money)

 {}

	int GetPay() const

	{

		return salary;

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout  << "salary : " << GetPay() << endl  << endl;

	}

 };

class EmployeeHandler

{

private:

	Employee* empList[50];

	int empNum;

public:

	EmployeeHandler() :empNum(0) {}

	void AddEmployee(Employee* emp)

	{

		empList[empNum++] = emp;

	}

	void ShowAllSalaryInfo() const

	{

		for (int i = 0; i < empNum; i++)

		{

			empList[i]->ShowSalaryInfo();

		}

	}

	void ShowTotalSalary() const

	{

		int sum  = 0;

		for (int i = 0; i < empNum; i++)

		{

			sum += empList[i]->GetPay();

		}

		cout  << "salary num :" << sum  << endl;

	}

	~EmployeeHandler()

 	{

		for (int i  = 0; i  < empNum; i++)

 		{

			delete empList[i];

		}

	}

};

class TemporaryWorker :public Employee

{

private:

	int workTime;

	int payPerHour;

public:

	TemporaryWorker(const char* name, int pay) :Employee(name), workTime(0),payPerHour(pay) {}

	void AddWorkTime(int time)

 {

		workTime  += time;

	}

	int GetPay() const

	{

		return workTime * payPerHour;

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout  << "salary : " << GetPay() << endl  << endl;

	}

 };

class SalesWorker :public PermanentWorker

{

private:

	int salesResult; // 월 판매실적

	double bonusRatio; // 상여금 비율

public:

	SalesWorker(const char* name, int money, double ratio) :PermanentWorker(name, money), salesResult(0), bonusRatio(ratio) {}

	void AddSalesResult(int value)

 {

		salesResult  += value;

	}

	int GetPay() const

	{

		return PermanentWorker::GetPay() + (int)(salesResult  + bonusRatio);

	}

	void ShowSalaryInfo() const

	{

		ShowYourName();

		cout  << "salary : " << GetPay() << endl  << endl;

	}

 };

int main(void)

 {

	EmployeeHandler handler;

	handler.AddEmployee(new PermanentWorker("kim", 1000));

	handler.AddEmployee(new PermanentWorker("yeon", 2000));



	TemporaryWorker* alba  = new TemporaryWorker("Jung", 700);

	alba->AddWorkTime(5);

	handler.AddEmployee(alba);

	SalesWorker* seller  = new SalesWorker("Hong", 1000, 0.1);

	seller->AddSalesResult(7000);

	handler.AddEmployee(seller);

	handler.ShowAllSalaryInfo();

	handler.ShowTotalSalary();

	return 0;

}





아까 Employ.cpp에서 주석처리한 부분을 사용하고자 한다면 가상함수를 이용하면 됩니다

Employee (상위클래스) 에 GetPay() 함수와 ShowSalaryInfo()함수를 virtual로 선언해주면 되는데요

가상함수를 선언하게 되면 가장 마지막에 오버라이딩을 진행한 함수가 호출됩니다~

 

 

이제 순수가상함수와 추상클래스에 대해 알아보도록 하겠습니다~

클래스들 중에서는 객채 생성을 목적으로 정의되지 않는 클래스들이 존재하는데요!

객채 생성을 목적으로 정의되지 않는 클래스임에도 불구하고 객체 생성을 시도할때 오류가 나지 않습니다

이러한 문제를 해결하기 위해 가상함수를 순수가상함수로 선언해서 객체의 생성을 문법적으로 막는것입니다

 

순수가상함수는 "함수의 몸체가 정의되지 않은 함수"를 의미합니다.

ex) virtual int GetPay() const = 0;

virtual void ShowSalaryInfo() const=0;

// 0의 대입을 의미하는게 아니라 명시적으로 몸체를 정의하지 않았음을 컴파일러에게 알리는 것!

 

 

<8-3 가상 소멸자와 참조자의 참조 가능성>

virtual로 선언된 소멸자 : 가상소멸자

 

 

 

8장 마무리 하면서 포스팅 마치겠습니다~

지금까지 이니였습니다

구독 좋아요♬