본문 바로가기
Programming/C++

상속과 다형성

by OKOK 2017. 7. 31.

객체지향에서 가장 중요하다고 할 수 있는 다형성을 공부할 때 입니다. 

객체 포인터의 참조 관계

Person * ptr;

ptr=new Person();

위의 두 문장이 실행되면, 포인터 ptr은 Person 객체를 가리키게 됩니다. 그런데 Person형 포인터는 Person 객체뿐만 아니라, Person을 상속하는 유도 클래스의 객체도 가리킬 수 있습니다. C++ 에서, AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접 혹은 간접적으로 상속하는 모든 객체를 가리킬 수 있다. 객체의 주소 값을 저장할 수 있다. 


#pragma warning(disable:4996)


#include <iostream>

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; delete ptr2; delete ptr3;

return 0;


C++ 에서, AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접 혹은 간접적으로 상속하는 모든 객체를 가리킬 수 있습니다. (객체의 주소 값을 저장할 수 있습니다.)


EmployeeHandler 클래스가 저장 및 관리하는 대상이 Employee 객체가 되게 하면, 이후에 Employee 클래스를 직접 혹은 간접적으로 상속하는 클래스가 추가되었을 떄, EmloyeeHandler 클래스에는 변화가 발생하지 않습니다. PermanentWorker 클래스에도 GetPay 함수와 ShowSalaryInfo 함수가 있는데, 유도 크래스인 SalesWorker 클래스에서도 동일한 이름과 형태로 두 함수를 정의하였습니다. 이를 가리켜 함수 오버라이딩이라고 합니다. 함수의 오버로딩과 혼동하지 않도록 합니다. 오버라이딩 된 기초클래스의 함수는 오버라이딩을 한 유도 클래스의 함수에 가려집니다.  오버라이딩 된 기초 클래스의 GetPay 함수를 호출하는 구문입니다. 기초크래스의 오버라이딩 된 함수를 호출 할 수 있습니다. seller 객체의 PermanentWorker 클래스에 정의된 ShowSalaryInfo 함수를 호출하세요. 


기초 클래스와 동일한 이름의 함수를 유도 클래스에서 정의한다고 해서 무조건 함수 오버라이딩이 되는 것은 아닙니다. 매개변수의 자료형 및 개수가 다르면, 이는 함수 오버로딩 되어, 전달되는 인자에 따라서 호출되는 함수가 결정됩니다. 즉, 함수 오버로딩은 상속의 관계에서도 구성이 될 수 있습니다. 


#pragma warning(disable:4996)

#include <iostream>

#include <cstring>

using namespace std;


class Employee

{

private:

char name[100];

public:

Employee(char * name)

{

strcpy(this->name, name);

}

void ShowYourName() const

{

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

}

};


class PermanentWorker : public Employee

{

private:

int salary;

public:

PermanentWorker(char* name, int moeny)

: Employee(name), salary(moeny)

{ }

int GetPay() const

{

return salary;

}

void ShowSalaryInfo() const

{

ShowYourName();

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

}

};


class TemporaryWorker : public Employee

{

private:

int workTime;

int payPerHour;

public:

TemporaryWorker(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(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;

}

};


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 sum: " << 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("LEE", 1500));


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;


SalesWorker 클래스에서 ShowSalaryInfo 함수를 오버라이딩 한 이유는?

PernamentWorker 클래스의 ShowSalaryInfo 함수는 상속에 의해서 SalesWorker 객체에도 존재하게 됩니다. 그러나 비록 상속이 되었다고는 하나 PermanentWorker 클래스의 ShowSalaryInfo 함수 내에서 호출되는 GetPay 함수는 PermanentWorker 클래스의 ShowSalaryInfo 함수 내에서 호출되는 GetPay 함수는 PermanentWorker 클래스에 정의된 GetPay 함수의 호출로 이어지고 만다. 따라서 SalesWorker 클래스에 정의된 GetPay 함수가 호출되도록 SalesWorker 클래스에 별도의 ShowSalaryInfo 함수를 정의해야만 합니다. 비록 함수의 몸체부분이 동일하더라도 말입니다. 


가상 함수(Virtual Function)

기초 클래스의 포인터로 객체를 참조하면, sim1, sim2가 가리키는 객체는 Simple 클래스, 또는 Simple 클ㄹ스르 상속하는 클래스의 객체라는 사실입니다. C++ 컴파일러는 포인터 연산의 가능성 여부를 판단할 때, 포인터의 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형을 기준으로 판단하지 않습니다. dptr은 Derived 클래스의 포인터 변수이니, 이 포인터가 가리키는 객체는 분명 Base 클래스를 직접 혹은 간접적으로 상속하는 객체입니다. 그러니 Base 형 포인터 변수로도 참조가 가능합니다. 


포인터 형에 해당하는 클래스에 정의된 멤버에만 접근이 가능한 것입니다. C++ 컴팡일러는 포인터를 이용한 연산의 가능성 여부를 판단할 때, 포인터의 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형을 기준으로 판단하지 않습니다. 


#include <iostream>

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;


총 3개의 클래스가 상속관계로 연결되어 있으며, 이들은 모두 MyFunc 함수를 통해서 오버라이딩 관계를 형성하고 있습니다. Third 객체를 생성한 다음, Third형, Second형, 그리고 First 형 포인터 변수로 이를 참조하고 있습니다. 각 포인터 형 변수를 이용해서 MyFunc 함수를 호출하고 있습니다.


fptr이 First형 포인터이니, 이 포인터가 가리키는 객체를 대상으로 First 클래스에 정의된 MyFunc 함수는 무조건 호출 할 수 있겠구나.