객체 포인터 변수 : 객체의 주소 값을 저장하는 포인터 변수
C++에서 AAA형 포인터 변수는 AAA 객체뿐만 아니라, AAA를 직접 혹은 간접적으로 상속하는 모든 객체를 가리킬 수 있다.(객체의 주소 값을 저장할 수 있다.)
#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() {
Person* ptr1 = new Student();
//Student는 Person을 상속하므로, Person형 포인터 변수는 Student 객체를 가리킬 수 있다.
Person* ptr2 = new PartTimeStudent();
//PartTimeStudet는 Person을 간접 상속하므로, Person형 포인터 변수는 PartTimeStudent 객체를 가리킬 수 있다.
Student* ptr3 = new PartTimeStudent();
//PartTimeStudent는 Student를 상속하므로, Student형 포인터 변수는 PartTimeStudent 객체를 가리킬 수 있다.
ptr1->Sleep();
ptr2->Sleep();
ptr3->Study();
delete ptr1; delete ptr2; delete ptr3;
return 0;
}
위 그림과 같은 경우, 기본적으로 IS-A 관계에 따라 다음이 성립한다.
- BBB는 AAA이다.
- CCC는 BBB이다.
그리고, IS-A 관계는 간접 상속의 관계 속에서도 유지되기 때문에 다음 문장도 성립한다.
- CCC는 AAA이다.
따라서, AAA형 포인터 변수는 AAA객체뿐만 아니라, BBB 객체와 CCC 객체도 가리킬 수 있으며,
BBB형 포인터 변수로는 BBB객체뿐만 아니라 CCC 객체도 가리킬 수 있다.
급여관리 확장성 문제의 1차적 해결과 함수 오버라이딩
이전 7-1에서 제시한 문제를 해결해보자.
- 고용인 Employee
- 정규직 PermanentWorker
- 영업직 SalesWorker
- 임시직 TemporaryWorker
위 네가지의 클래스를 다음과 같은 관계로 정의하고자 한다.
- 정규직, 영업직, 임시직은 고용인이다.
- 영업직은 정규직이다.
여기서 상속은 단순히 재활용을 위해 사용된 것이 아니라,
모든 클래스의 객체를 Employee 클래스의 객체로 바라볼 수 있게 하기위해서 상속을 이용한 것이다.
#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 money)
:Employee(name), salary(money)
{ }
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()//PermanentWorker의 GetPay함수 호출
+ (int)(salesResult * bonusRatio);
}
void ShowSalaryInfo() const {
ShowYourName();
cout << "salary: " << GetPay() << endl << endl;//SalesWorker의 GetPay함수가 호출됨
}
};
class EmployeeHandler {
private:
//Employee 객체의 주소 값을 저장하는 방식으로 객체를 저장한다.
//따라서 Employee 클래스를 상속하는 클래스의 객체도 이 배열에 함께 저장이 가능하다.
Employee* empList[50];
int empNum;
public:
EmployeeHandler() : empNum(0)
{ }
void AddEmployee(Employee* emp) {
//인자로 Employee 객체의 주소값을 전달한다.
//따라서 Employee 클래스를 상속하는 PermanentWorker 객체의 주소 값도 전달이 가능하다.
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));
handler.AddEmployee(new PermanentWorker("JUN", 2000));
//이번 달에 지불해야 할 금액의 정보
handler.ShowAllSalaryInfo();
//이번 달에 지불해야 할 급여의 총합
handler.ShowTotalSalary();
return 0;
}
(주석처리된 부분의 해결은 8-2에서 이어질 예정이다.)
위 코드에서 SalesWorker 클래스에서는 PermanentWorker를 상속함으로써 기본 급여와 관련된 부분을 멤버로 포함시켰으며,
상여금에 대한 부분만 별도로 추가해주었다.
기초 클래스인 PermanentWorker클래스에도 GetPay 함수와 ShowSalaryInfo 함수가 있는데,
유도 클래스에도 동일한 이름과 형태로 두 함수가 정의되어있다는 사실을 알 수 있다.
이를 가리켜 '함수 오버라이딩(function overriding)'이라 한다.
함수가 오버라이딩 되면, 기초 클래스의 함수는, 오버라이딩 한 유도 클래스의 함수에 가려진다.
즉, SalesWorker 클래스 내에서 GetPay함수를 호출하면 SalesWorker클래스에 정의된 함수가 호출된다.
SalesWorker 클래스의 ShowSalaryInfo함수는 PermanentWorker 클래스의 ShowSalaryInfo함수와 동일한데,
굳이 다시 정의되어있는 이유는 무엇일까?
이는 만약 SalesWorker 클래스 내의 ShowSalaryInfo함수를 없애고, PermanentWorker클래스의 ShowSalaryInfo함수를 호출할 경우, SalesWorker클래스의 GetPay함수가 아닌, PermanentWorker클래스의 GetPay함수가 호출되기 때문이다.
오버라이딩 된 기초 클래스의 GetPay함수를 호출하려면 다음과 같이 하면 된다.
PermanentWorker::GetPay()
int main(void){
SalesWorker seller("Hong",1000,0.1);
cout<<seller.GetPay()<<endl;//SalesWorker에 정의된 함수 호출
seller.ShowSalaryInfo();//SalesWorker에 정의된 함수 호출
cout<<seller.PermanentWorker::GetPay()<<endl;//기초클래스에 정의된 함수 호출
seller.PermanentWorker::ShowSalaryInfo();//기초클래스에 정의된 함수 호출
}
오버라이딩을 하는 이유
앞의 예제에서 SalesWorker클래스의 ShowSalaryInfo 함수와 PermanentWorker클래스의 ShowSalary 함수는 내용이 완전히 동일한데 오버라이딩 한 이유가 무엇일까?
PermanentWorker 클래스의 ShowSalaryInfo 함수는 상속에 의해서 SalesWorker 객체에도 존재하게 된다.
그러나 PermanentWorker 클래스의 ShowSalaryInfo 함수 내에서 호출되는 GetPay함수는 PermanentWorker 클래스에 정의된 GetPay함수의 호출로 이어지고 만다.
따라서 SalesWorker 클래스의 GetPay함수가 호출되도록 SalesWorker 클래스에 별도의 ShowSalaryInfo함수를 정의해야만 한다.
'공부 > C++' 카테고리의 다른 글
[C++] 8-3.가상 소멸자와 참조자의 참조 가능성 (0) | 2024.11.15 |
---|---|
[C++] 8-2. 가상함수(Virtual Function) (1) | 2024.11.12 |
[C++] 7-5. OOP 단계별 프로젝트 05단계 (1) | 2024.11.10 |
[C++] 7-4. 상속을 위한 조건 (0) | 2024.11.08 |
[C++]7-3. protected 선언과 세가지 형태의 상속 (0) | 2024.11.07 |