[Chap 08] 상속과 다형성
객체 포인터의 참조 관계
객체 포인터 변수
객체의 주소 값을 저장하는 포인터 변수이다.
Person * ptr;
ptr = new Student();
뭐,,, 이런걸 얘기하는 것이다. 여튼 슬슬 다형성 얘기가 나올듯,,,,
#include <iostream>
class Person
{
public:
void Sleep()
{
std::cout << "Sleep" << std::endl;
}
};
class Student : public Person
{
public:
void Study()
{
std::cout << "Study" << std::endl;
}
};
class PartTimeStudent : public Student
{
public:
void Work()
{
std::cout << "Work" << std::endl;
}
};
int main()
{
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;
}
>>>
Sleep
Sleep
Study
오렌지미디어 급여 관리 확장성 문제의 1차적 해결과 함수 오버라이딩
chap 07에서 정규직, 영업직, 입시직을 모두 각각에 해당하는 클래스를 만들려고 한다.
- 고용인: Employee
- 정규직: PermanentWorker
- 영업직: SaleWorker
- 임시직: TemporaryWorker
#include <iostream>
#include <cstring>
class Employee
{
private:
char name[100];
public:
Employee(char *name)
{
strcpy(this->name, name);
}
void ShowYourName() const
{
std::cout << "Name: " << name << std::endl;
}
virtual int GetPay() const
{
return 0;
}
virtual void ShowSalaryInfo() const
{
}
};
class PermanentWorker : public Employee
{
private:
int salary;
public:
PermanentWorker(char *name, int money) : Employee(name), salary(money)
{
}
int GetPay() const
{
return salary;
}
void ShowSalaryInfo() const
{
ShowYourName();
std::cout << "salary: " << GetPay() << std::endl
<< std::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();
std::cout << "salary: " << GetPay() << std::endl
<< std::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();
std::cout << "salary: " << GetPay() << std::endl
<< std::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();
}
std::cout << "salary sum: " << sum << std::endl;
}
~EmployeeHandler()
{
for (int i = 0; i < empNum; i++)
{
delete empList[i];
}
}
};
int main()
{
// 직원관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
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;
}
>>>
Name: KIM
salary: 1000
Name: LEE
salary: 1500
Name: Jung
salary: 3500
Name: Hong
salary: 1700
salary sum: 7700
Employee Class를 만들고 그 클래스에 있는 멤버 함수를 기준으로 오버로딩을 통해서 급여 관리를 확장한 모습을 볼 수 있다.
상속의 이유
- 상속을 통해 연관된 일련의 클래스에 대해 공통적인 규약을 정의할 수 있다.
순수 가상함수와 추상 클래스
-
순수 가상함수: 함수의 몸체가 정의되지 않은 함수로, 키워드는 virtual을 사용한다.
- 이를 사용함으로써 객체 생성 목적이 아닌 클래스임을 알려줌과 동시에 완전하지 않은 클래스이므로 컴파일 오류가 나서 객체 생성 목적이 아닌 클래스임을 알 수 있다.
-
추상 클래스
하나 이상의 멤버함수를 순수 가상 함수로 선언한 클래스를 가리켜 추상 클래스라고 한다.
다형성
문장은 같은데 결과는 다르다
First Class <- Second Class 인 경우 First 객체를 참조하는 ptr이 Second 객체를 참조하게 되면 같은 ptr이더라도 다른 결과를 출력하게 된다.
가상 소멸자와 참조자의 참조 가능성
가상 소멸자
virtual로 선언된 소멸자를 가리켜 가상 소멸자라고 한다. 소멸자에 virtual을 붙히지 않은 경우 사용한 자료형의 클래스에서의 소멸자만 소멸하게 된다. 하지만 virtual을 사용하게 되면 다음과 같은 결과를 얻을 수 있다.
#include <iostream>
#include <cstring>
using namespace std;
class First
{
private:
char *strOne;
public:
First(char *str)
{
strOne = new char[strlen(str) + 1];
}
virtual ~First()
{
cout << "~First()" << endl;
delete[] strOne;
}
};
class Second : public First
{
private:
char *strTwo;
public:
Second(char *str1, char *str2) : First(str1)
{
strTwo = new char[strlen(str2) + 1];
}
virtual ~Second()
{
cout << "~Second()" << endl;
delete[] strTwo;
}
};
int main()
{
First *ptr = new Second("simple", "complex");
delete ptr;
return 0;
}
>>>
~Second()
~First()
실행 과정
- 객체 소멸 과정에서 First 호출 -> 소멸자가 가상 소멸자 이므로 계층 구조상 맨 아래쪽에 해당하는 유도 클래스의 소멸자 호출
- 제일 아래쪽의 유도 클래스의 소멸자 실행
- 기초 클래스까지 올라가면서 소멸자 실행
참조자의 참조 가능성
First Class <- Second Class <- Third Class 인 경우
- 일반 멤버 함수인 경우 : 해당 클래스 또는 상속받은 클래스에 있는 멤버 함수만 사용 가능
- 순수 가상함수인 경우 : 상속의 계층 구조상 제일 아래있는 클래스의 가상 함수를 사용 (즉, Third Class의 가상 함수가 출력이 된다는 것이다.)
댓글남기기