문과 코린이의, [C. C++] 기록/C++ 이론

[문과 코린이의 IT 기록장] C,C++ - 상속과 다형성 1 : 객체 포인터의 참조관계 (객체 포인터 변수 : 객체의 주소 값을 저장하는 포인터 변수, OrangeMedia 급여관리 확장성 문제의 1차적 해결과 함수 ..

벼리네 2021. 3. 5. 21:18
반응형

[문과 코린이의 IT 기록장] C,C++ - 상속과 다형성 1 : 객체 포인터의 참조관계 

(객체 포인터 변수 : 객체의 주소 값을 저장하는 포인터 변수, OrangeMedia 급여관리 확장성 문제의 1차적 해결과 함수 오버라이딩)  


 

 

new & delete에 대해서 좀 더 알고 싶다면?

 

 


 

 

 1. 객체 포인터 변수 : 객체의 주소 값을 저장하는 포인터 변수

1) 객체 포인터 변수 이해

- 포인터 변수클래스를 기반으로도 작성될 수 있다.

ex) Person : 클래스 이름

 Person * ptr; // 포인터 변수 선언

 ptr = new Person(); // 포인터 변수의 객체 참조 ( 위 문장이 실행되면, 포인터 변수 ptrPerson 객체를 가리키게 된다. )

 

[ 여기서 꼭 알아야할 점 ]

- Person형 포인터 Person 객체뿐만 아니라, Person을 상속하는 유도 클래스의 객체가리킬 수 있다.

ex ) 
class Student : public Person{ ... }; // Student는 Person을 상속하는 유도클래스임.
class PartTimeStudent : public Student{ ... }; // PartTimeStudent는 Student를 상속하는 유도클래스임.

Person
* ptr= newStudent(); // 실행 가능
Person * ptr = new PartTimeStudent(); // 실행 가능
Student * ptr = new PartTimeStudent(); // 실행 가능

 

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


ex )

 

 

 

 

#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(); // PartTimeStudent는 Person을 간접 상속하므로, Person형 포인터 변수는 PartTimeStudent 객체를 가리킬 수 있음.

Student* ptr3 = new PartTimeStudent(); // PartTimeStudent는 Student를 상속하므로, Student형 포인터 변수는 PartTimeStudent 객체를 가리킬 수 있음.

ptr1->Sleep(); // ptr1은 Student 객체

ptr2->Sleep(); // ptr2는 PartTimeStudent 객체

ptr3->Study(); // ptr3는 PartTimeStudent 객체

delete ptr1; delete ptr2; delete ptr3// 포인터 변수 ptr1, ptr2, ptr3의 동적할당된 메모리 삭제

return 0;

}

 

 


2) 유도 클래스의 객체를 가리키는, 객체 포인터 변수

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

- 이 특징은, 상속의 IS-A 관계를 통해 이해할 수 있다.

 

 

 

- 위의 경우, 기본적으로 다음 두 문장이 성립된다.

 "BBB는 AAA이다." or "BBB는 AAA의 일종이다."

 "CCC는 BBB이다." or "CCC는 BBB의 일종이다."

 

- 그리고 IS-A관계는 간접 상속의 경우에서도 유지되어야 하므로, 다음 문장도 성립된다.

 "CCC는 AAA이다." or "CCC는 AAA의 일종이다."

 

- 이에 따라,

 : AAA형 포인터 변수AAA객체 뿐만 아니라, BBB객체와, CCC객체가리킬 수 있으며,

 : BBB형 포인터 변수로는 BBB 객체뿐만 아니라 CCC 객체가리킬 수 있는 것이다.

 

 


 

 2. OrangeMedia 급여관리 확장성 문제의 1차적 해결과 함수 오버라이딩.

- OrangeMedia 급여관리 확장성 문제 

상속 1 : OrangeMedia 이전 학습 내용 예제

- 고용인 : Employee

- 정규직 : PermanentWorker

- 영업직 : SalesWorker

- 임시직 : TemporaryWorker

 

- 각각의 관계 

 : 정규직, 영업직, 임시직 모두 고용의 한 형태이다. (고용인이다.)

 : 영업직정규직의 일종이다.

 

 

 

- 이는 EmployeeHandler 클래스에 대한, 확장성 문제를 해결할 수 있다

 : EmployeeHandler 클래스가 저장 및 관리하는 대상이, Employee 객체가 되게 하면, 이후에 Employee 클래스를 직/간접적으로 상속하는 클래스가 추가되었을 때, EmployeeHandler 클래스에는 변화가 발생하지 않는다.

 

 


ex 1 ) 위 링크의, OrangeMedia 예제와 비교하며 이해해보세요.

#include <iostream>

#include <cstring>

using namespace std;

 

class Employee { // 고용인 클래스

private:

 char name[100]; // 이름 변수

public:

 Employee(const char* name) { // 고용인 생성자

   strcpy_s(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 { // 고용인 handler 클래스

- 저장의 대상이 PermanentWorker 객체 -> Employee 객체로 변환되었다.
- 그러나, PermanentWorker 객체는 Employee 객체의 일종이므로(Employee 객체를 상속하므로) , 저장이 가능하다.

private:

 Employee* empList[50]; // 고용인 리스트 포인터 변수 (Employee 객체의 주소 값을 저장하는 방식으로 객체를 저장. PermanentWorker 객체 역시 Employee 객체의 일종이므로 저장 가능.)

 int empNum; // 고용인 총 개수 변수

public:

 EmployeeHandler() :empNum(0) { } // 고용인 handler 생성자

 void AddEmployee(Employee* emp) { // 고용인 추가 함수 (객체의 주소 값 전달 방식. 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() { // 고용인 handler 소멸자

   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));

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

 

// 이번달에 지불해야 할 급여의 정보

handler.ShowAllSalaryInfo();

 

// 이번달에 지불해야 할 급여의 총합

handler.ShowTotalSalary();

return 0;

 


- 영업직, 임시직 관련 클래스 추가 정의
 : 영업직 급여 = '기본급여(월 기본급여) + 인센티브'
 : 임시직 급여 = '시간당급여 x 일한 시간'

 

 

ex 2 ) 영업직, 임시직 클래스 추가 정의 예제

 

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

class Employee { // 고용인 클래스 
private:
 char name[100]; // 이름 변수
public:
 Employee(const char* name) { // 고용인 생성자
   strcpy_s(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 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; // TemporaryWorker 클래스GetPay() 함수가 출력됨.
 }
};

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 { // 정규직 급여 + (월 판매 실적 x 상여금 비율) 반환 함수
   return PermanentWorker::GetPay() + // PermanentWorker 클래스GetPay() 함수 호출
   (int)(salesResult * bonusRatio);
 }

 void ShowSalaryInfo() const { // 정보(이름, 급여) 출력 함수
   ShowYourName();
   cout << "salary: " << GetPay() << endl << endl; // SaleWorker 클래스GetPay() 함수가 출력됨
 }

[ 함수 오버라이딩 ]
- PermanentWorker 클래스에도 GetPay() 함수와, ShowSalayInfo() 함수가 존재하는데, 유도클래스인 SalesWorker 클래스에서도 동일한 이름과 형태로 두 함수를 정의한 것을 볼 수 있다.
- 이와 같은 것을 함수의 오버라이딩이라고 하며, 함수가 오버라이딩 되면, 오버라이딩 된 기초 클래스의 함수는, 오버라이딩을 한 유도클래스의 함수에 가려지게 된다. 
* 즉, SalesWorker 클래스 내에서 GetPay() 함수를 호출하면, SalesWorker 클래스에 정의된 GetPay() 함수가 호출되지, PermanentWorker 내부의 함수가 호출되는 것이 아니라는 의미이다.

ex )
int main(){
 SalesWorker seller("Hong",1000,0.1);
 cout<<seller.GetPay()<<endl; // SalesWorker 클래스의 GetPay() 호출
 seller.ShowSalaryInfo(); // SalesWorker 클래스의 ShowSalaryInfo() 호출
 [ 주의 ] seller.PermanentWorker::ShowSalaryInfo(); // PermanentWorker 클래스의 ShowSalaryInfo() 호출
};


};

class EmployeeHandler { // 고용인 handler 클래스
private:
 Employee* empList[50]; // 고용인 리스트 포인터 변수(Employee 객체의 주소 값을 저장하는 방식으로 객체를 저장. PermanentWorker 객체 역시 Employee 객체의 일종이므로 저장 가능.)
 int empNum; // 고용인 총 개수 변수
public:
 EmployeeHandler() :empNum(0) { }  // 고용인 handler 생성자
 void AddEmployee(Employee* emp) {   // 고용인 추가 함수 (객체의 주소 값 전달 방식. 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() { // 고용인 handler 소멸자
   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); // 5시간 일한 결과 등록
handler.AddEmployee(alba);

// 영업직 등록
SalesWorker* seller = new SalesWorker("Hong", 1000, 0.1);
seller->AddSalesResult(7000); 
handler.AddEmployee(seller);

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

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


 

- 위의 코드 중, PermanentWorker 클래스와 이를 상속하는 SalesWorker 클래스의, ShowSalaryInfo() 함수를 다시 한 번 살펴보자.

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 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 { // 정규직 급여 + (월 판매 실적 x 상여금 비율) 반환 함수
   return PermanentWorker::GetPay() + // PermanentWorker 클래스 GetPay() 함수 호출 
   (int)(salesResult * bonusRatio); 
 }
 void ShowSalaryInfo() const { // 정보(이름, 급여) 출력 함수
   ShowYourName(); 
   cout << "salary: " << GetPay() << endl << endl; // SaleWorker 클래스 GetPay() 함수가 출력됨
 }
}; 

 

- SalesWorker 클래스의 ShowSalaryInfo() 함수는, PermanentWorker ShowSalaryInfo() 함수와 완전히 코드가 똑같은데, 오버라이딩을 한 이유가 무엇일까?

* PermanentWorker 클래스의 ShowSalaryInfo() 함수 내에서 호출되는, GetPay() 함수는 PermanentWorker 클래스의 GetPay() 함수의 호출로 이어진다.

* SalesWorker 클래스의 ShowSalaryInfo() 함수 내에서 호출되는, GetPAy() 함수는, SalesWorker 클래스의 GetPay() 함수의 호출로 이어진다.

 


- 주석 부분을 해제할 때 발생하는 컴파일 에러에 관한 사항에 대한 해결책은, 다음 파트의 가상함수(Virtual Function) 부분에서 더욱 자세히 설명하고자 한다.


* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)

 

반응형