220216, C++ Ch.08-1
Ch.08
앞서 조금 맛보기를 했던 "상속"과, "다형성"에 대해 공부한다.
특히 저자가 다형성(Polypormhism)은 어마어마하게 중요하다고 하니까, 집중 흐려지지 말고 제대로 익혀두자.
08-1
객체 포인터 참조 관계
앞서 예제들에서도 보았겠지만, 클래스를 기반으로도 포인터 변수를 선언할 수 있다.
Person *ptr = new Person();
이렇게. Person 객체의 주소 값 저장을 위해 (= Person 객체를 가리키기 위해서) 위와 같이 포인터 변수 선언이 가능하다.
그런데 여기서 Person형 포인터는 Person을 상속하는 "유도 클래스의 객체"도 가리킬 수 있다.
이게 머선말이냐고?
만약
class Student : public Person { 뭐시기뭐시기 }
이렇게 Person을 상속하는 Student라는 유도 클래스가 있다면,
Person *ptr = new Student();
이렇게 Student 객체도 가리킬 수 있게 된다.
여기서 또또 Student를 상속하는 유유도클래스(??)가 있다면,
class PartTimeStudent : public Student { 뭐시기 }
Person형 포인터는 또또또 PartTimeStudent 객체를 가리킬 수 있게된다.
Person *ptr = new PartTimeStudent()
위의 내용들을 말로써 표현한다면
C++에서, X형 포인터 변수는 X 객체, 혹은 X를 직간접적으로 상속하는 모든 객체를 가리킬 수 있다.
이를 조금 더 깊게 이해하도록 아래의 예시를 먼저 보도록 하자.
/* Example.cpp */
#include <iostream>
using namespace std;
class Person {
public :
void Sleep() {
cout<<"Sleep.. zzZ"<<endl;
}
};
class Student : public Person {
public :
void Study() {
cout<<"Study Now."<<endl;
}
};
class PartTimeStudent : public Student {
public :
void Work() {
cout<<"Work hard.."<<endl;
}
};
int main() {
Person *ptr1 = new Student();
// 포인터 변수 ptr1은 Student 객체의 주소값을 저장하고 있다.
// 학생(Student)은 사람(Person)이다 라는 IS-A 관계를 성립시킬 수 있다.
Student *ptr2 = new PartTimeStudent();
// 포인터 변수 ptr2는 PartTimeStudent 객체의 주소값을 저장하고 있다.
// 근로학생(PartTimeStudent)은 학생(Person)이다 라는 IS-A 관계를 성립시킬 수 있다.
ptr1->Sleep();
ptr2->Study();
delete ptr1;
delete ptr2;
return 0;
}
위의 Example.cpp를 보면, main함수에 뭐라뭐라 주석으로 단 내용이 있다. 이를 분석해 보겠다.
위에서 보다시피 객체 포인터와 함께 "상속"과 관련된 내용들을 설명해주고 있다.
논리적인 이해를 해본다면, Person *ptr1 = new Student(); 은 Student는 Person입니다. 라고 해석할 수 있게 된다. 즉, Student is a Person으로 IS-A 상속 관계가 설명이 된다고 할 수 있다.
그렇다면, Student *ptr2 = new PartTimeStudent(); 은?
그렇지. PartTimeStudent는 Person입니다.로 앞과 마찬가지의 상속 관계가 설명이 된다.
실제 객체지향에선 위 문장들이 성립함으로 인해 Student 객체, PartTimeStudent 객체를 Person 객체의 일종으로 간주한다고 한다.
그래서 위처럼 Person형 포인터 변수로 Student나 PartTimeStudent를 가리킬 수 있게 되는 것이다.
07-1에서 제시한 문제 해결
우린 상속에 대해 처음 접할 때, A Company의 급여 관리 시스템의 문제점 제시와 함께 Ch.07을 시작했다.
여기서 제시한 문제는, "고용의 형태가 늘어났을 때 프로그램이 유연성 / 확장성 측면에서 쉽게 대응 가능한 프로그램 짜기" 였다.
이를 해결하기 위한 방법은.
Control Class가 저장 및 관리하는 대상이 PermanentWorker로 한정되어있는 지금, 이 관리 대상이 "Employee"객체가 되게 하고, 이후 Employee 클래스를 상속하는 클래스가 추가된다면, Control Class에는 변화가 발생하지 않는다.
즉, 위의 그림과 같이 관리하겠단 거다. 여기서 오른쪽 그림과 이어진 정규 / 영업 / 임시는 각각 상속을 통해 Employee 객체 안에 포함이 될 것이다.
#include <iostream>
#include <cstring>
using namespace std;
class Employee { // 가장 기본 형태인 "피고용인"
private : // 모든 고용인의 공통적인 멤버는 "이름" 밖에 없다.
char name[100];
public :
Employee(char *empname) {
strcpy(name, empname);
}
void ShowYourName() const {
cout<<"Name : "<<name<<endl;
}
};
/* 고용 형태 */
class PermanentWorker : public Employee { // 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;
}
};
/* Control class */
class EmployeeHandler { // Control Class
private :
Employee* empList[50];
int empNum;
public :
EmployeeHandler() : empNum(0) {}
void AddEmployee(Employee* emp) { // 기능 1. 직원 추가
empList[empNum++] = emp;
}
void ShowAllSalryInfo() const { // 기능 2. 모든 직원들의 급여 출력
// 대기!
}
void ShowTotalSalary() const { // 모든 직원들 급여의 총액 출력
int sum = 0;
// 대기!
cout<<"Salary sum : "<<sum<<endl;
}
~EmployeeHandler() {
for (int i=0 ; i<empNum ; i++) {
delete empList[i];
}
}
};
일단, 기존 코드에 있던 정규직만 Employee 객체를 상속받아 코드로 표현해줫다.
여기서 이제
- 임시직 : 시급 x 일한 시간
- 영업직 : 기본급여 + 인센티브(상여금)
이라는 걸 고려하고, 임시직과 영업직의 class를 표현해보자.
class SalesWorker : public PermanentWorker {
Employee 중 PermantWokrer의 고용 형태 중 하나인 "영업직"
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 {
// 이의 경우, PermanentWorker 클래스와 같은 이름의 함수를 가지게 되는데,
// 이를 "함수 오버라이딩"이라고 한다.
return PermanentWorker::GetPay() + (int)(salesResult*bonusRatio);
}
// PermanentWorker::GetPay()는 오버라이딩 된
// 기초 클래스의 GetPay 함수를 호출하는 것.
// 만약 매개변수의 자료형 / 개수가 다르다면 함수 오버라이딩이 아닌 함수 오버로딩이 된다.
void ShowSalaryInfo() const {
ShowYourName();
cout<<"Salary : "<<GetPay()<<endl<<endl;
}
// PermanentWorker에 저장된 ShowSalaryInfo와 100% 똑같으나 오버라이딩 진행.
// because, 각 클래스에 있는 서로 이름만 같은 GetPay()함수 때문에.
};
class TemporaryWorker : public Employee {
// Employee의 고용 형태 중 하나인 "임시직"
private :
int workTime; // 근무시간
int payPerHour; // 시급
public :
TemporaryWorker(char *empname, int pay) : Employee(empname), 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;
}
};
여기서 보면, SalesWorker의 GetPay() 함수에 "함수 오버라이딩(function overriding)"이라는 것이 있다.
쉽게 생각해서는 함수 오버로딩의 하위호환이라 생각하면 된다. (그렇다고 함수 오버로딩이랑 혼동하진 말자.)
서로 다른 클래스에서 같은 이름의 함수를 가지고 있을 때, 위의 PermanentWorker::GetPay()처럼 사용해주면 함수 오버라이딩된 기초 클래스의 GetPay 함수를 사용한 것이 된다.
이 함수 오버라이딩에서 매개변수의 자료형 / 개수가 달라지면 함수 오버로딩이 되면서, 전달되는 인자에 따라 호출되는 함수가 결정된다! Aradooja.
자 그러면, 이 EmployeeManager(ver2)의 풀코드를 올리겠다. 중간 중간 빈 곳이 몇개 있을텐데, (// 대기! 와 같이 작성된 곳) 이는 금방 이후(?) 다루도록 하겠다.
그리고, 위에서 설명이 부족했던 부분과 새로 추가된 부분은 주석으로 들어갔으니, 이를 참고하자.
#include <iostream>
#include <cstring>
using namespace std;
class Employee { // 가장 기본 형태인 "피고용인"
private : // 모든 고용인의 공통적인 멤버는 "이름" 밖에 없다.
char name[100];
public :
Employee(char *empname) {
strcpy(name, empname);
}
void ShowYourName() const {
cout<<"Name : "<<name<<endl;
}
};
/* 고용 형태 */
class PermanentWorker : public Employee {
// 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 SalesWorker : public PermanentWorker {
// Employee 중 PermantWokrer의 고용 형태 중 하나인 "영업직"
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;
}
// PermanentWorker에 저장된 ShowSalaryInfo와 100% 똑같으나 오버라이딩 진행.
// because, 각 클래스에 있는 서로 이름만 같은 GetPay()함수 때문에.
};
class TemporaryWorker : public Employee {
// Employee의 고용 형태 중 하나인 "임시직"
private :
int workTime; // 근무시간
int payPerHour; // 시급
public :
TemporaryWorker(char *empname, int pay) : Employee(empname), 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;
}
};
/* Control class */
class EmployeeHandler { // Control Class
private :
Employee* empList[50];
int empNum;
public :
EmployeeHandler() : empNum(0) {}
void AddEmployee(Employee* emp) { // 기능 1. 직원 추가
empList[empNum++] = emp;
}
void ShowAllSalryInfo() const { // 기능 2. 모든 직원들의 급여 출력
// 대기!
}
void ShowTotalSalary() const { // 모든 직원들 급여의 총액 출력
int sum = 0;
// 대기!
cout<<"Salary sum : "<<sum<<endl;
}
~EmployeeHandler() {
for (int i=0 ; i<empNum ; i++) {
delete empList[i];
}
}
};
/* 메인함수 */
int main() {
EmployeeHandler handler;
// 정규직
handler.AddEmployee(new PermanentWorker("Go", 372));
// ★☆ handler가 여전히 PermanentWorker 객체를 저장하고 관리한다!
handler.AddEmployee(new PermanentWorker("Bae", 365));
// 임시직 (객체 포인터 사용)
TemporaryWorker *alba = new TemporaryWorker("Jung", 700);
alba->AddWorkTime(6);
handler.AddEmployee(alba);
// 영업직 (객체 포인터 사용)
SalesWorker *seller = new SalesWorker("Hong", 980, 0.15);
seller->AddSalesResult(7000);
handler.AddEmployee(seller);
handler.ShowAllSalryInfo();
handler.ShowTotalSalary();
return 0;
}
여기까지
Author And Source
이 문제에 관하여(220216, C++ Ch.08-1), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@gom1nh/220216-C-Ch.08-1저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)