IS-A 관계에 대해 계속 설명한다.
객체 포인터 변수
객체의 주소 값을 저장하는 포인터 변수
클래스를 기반으로도 포인터 변수를 선언할 수 있다.
Person* ptr; //포인터변수 선언
ptr = new Person(); //포인터 변수의 객체 참조
Person형 포인터는 Person 객체, Person을 상속하는 유도 클래스의 객체도 가리킬 수 있다.
class Student: public Person
{ .... } ;
Person* ptr = new Student();
Student 클래스를 상속하는 유도 클래스 PartTimeStudent가 다음의 형태로 정의되었다고 가정하면
Person형 포인터변수는 PartTimeStudent 객체도 가리킬 수 있다.
class PartTimeStudent: public Student
{ ..... };
Person* ptr = new PartTimeStudent();
즉, C++에서 AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접/간접적으로 상속하는 모든 객체를 가리킬 수 있다. (객체의 주소값을 저장할 수 있다.)
예
// ObjectPointer.cpp
#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();
Person* ptr2 = new PartTimeStudent();
Student* ptr3 = new PartTimeStudent();
ptr1->Sleep();
ptr2->Sleep();
ptr3->Study();
delete ptr1; delete ptr2; delete ptr3;
return 0;
}
Student *s가 Person 클래스의 객체를 가리키는 건 문제가 된다. 역방향은 성립하지 않기 때문이다 (IS-A가 아님)
유도클래스의 객체까지 가리킬 수 있다
C++에서 AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접/간접적으로 상속하는 모든 객체를 가리킬 수 있다. (객체의 주소값을 저장할 수 있다.) Student, PartTimeStudent 객체는 Person객체의 일종이기 때문이다.
전 단원의 급여관리 예제 확장 + 함수오버라이딩
정규직, 영업직, 임시직 모두 고용인이며 영업직은 정규직의 일종이다.
EmployeeHandler 클래스가 저장,관리 하는 대상이 Employee 객체가 되면 이후 Employee 클래스를 직간접적으로 상속하는 클래스가 추가되어도 EmployeeHandler 클래스는 변화가 없다.
// EmployeeManager2.cpp
#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 EmployeeHandler
{
private:
Employee* empList[50]; //Employee 객체의 주소값 저장
// Employee 클래스를 상속하는 클래스의 객체도 이 배열에 함께 저장 가능
int empNum;
public:
EmployeeHandler()
: empNum(0)
{ }
void AddEmployee(Employee* emp) //Employee 객체의 주소값을 전달
// Employee 클래스를 상속하는 클래스 PermenentWorker 객체의 주소값도 전달 가능
{
empList[empNum++]=emp;
}
void ShowAllSalaryInfo() const{
}
void ShowTotalSalary() const{
int sum=0;
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;
}
EmployeeHandler 객체가 여전히 PermanentWorker 객체를 저장/관리하고 있다.
그래서
영업직 급여 = 월 기본급여 + 인센티브
임시직 급여 = 시간당급여 * 일한시간
형태로 정의를 추가한다.
임시직 클래스
// 임시직. 실제 일을 한 시간으로 급여 계산
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;
}
};
영업직 클래스
// 영업직 (정규직 일종). Employee가 아닌 PermanentWorker 상속
// 기본급여과 관련된 부분을 멤버로 포함, 상여금 부분만 멤버로 추가
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() 호출
}
};
정규직과 영업직에는 동일 함수 GetPay()와 ShowSalaryInfo()가 있다. (기본클래스 유도클래스에 같은 함수가 있다)
* 함수 오버라이딩 : 함수오버로딩과 혼동 x
기초 클래스와 동일한 이름의 함수를 유도클래스에서 정의되어도 매개변수의 자료형 및 개수가 다르면 함수오버로딩이 되어 전달되는 인자에 따라 호출함수가 결정된다. 함수오버로딩은 상속관계에서도 구성 가능.
오버라이딩 된 기초클래스 함수는 오버라이딩 한 유도클래스 함수에 가려진다.
SalesWorker 클래스 내에서 GetPay를 호출하면 SalesWorker 클래스에 정의된 GetPay함수가 호출된다.
단 SalesWorker 클래스 내에서도 PermanentWorker::GetPay() 이렇게 호출하면 PermanentWorker 클래스 내부의 GetPay가 호출된다.
아래처럼도 호출 가능하다.
seller 객체의 PermanentWorker 클래스에 정의된 ShowSallryInfo()를 호출..이런 뜻
int main(void)
{
SalesWorker seller("H", 1000, 0.1);
cout<<seller.PermanentWorker::GetPay()<<endl;
seller.PermanentWorker::ShowSalaryInfo();
....
}
완성본
// EmployeeManager3.cpp
#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 EmployeeHandler
{
private:
Employee* empList[50]; //Employee 객체의 주소값 저장
// Employee 클래스를 상속하는 클래스의 객체도 이 배열에 함께 저장 가능
int empNum;
public:
EmployeeHandler()
: empNum(0)
{ }
void AddEmployee(Employee* emp) //Employee 객체의 주소값을 전달
// Employee 클래스를 상속하는 클래스 PermenentWorker 객체의 주소값도 전달 가능
{
empList[empNum++]=emp;
}
void ShowAllSalaryInfo() const{
}
void ShowTotalSalary() const{
int sum=0;
cout<<"salary sum: "<<sum<<endl;
}
~EmployeeHandler()
{
for(int i=0; i<empNum; i++)
delete empList[i];
}
};
// 임시직. 실제 일을 한 시간으로 급여 계산
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;
}
};
// 영업직 (정규직 일종). Employee가 아닌 PermanentWorker 상속
// 기본급여과 관련된 부분을 멤버로 포함, 상여금 부분만 멤버로 추가
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() 호출
// PermanentWorker 클래스의 ShowSalaryInfo()랑 같은 내용의 함수인데도
// 오버라이딩 한 이유는 상속이 되었어도 어느 GetPay를 부를 지 컨트롤 할 수 없어서
}
};
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); //5시간 일한결과 등록
handler.AddEmployee(alba);
// 영업직 등록
SalesWorker* seller=new SalesWorker("Hong", 1000, 0.1) ;
seller->AddSalesResult(7000); //영업실적 7000
handler.AddEmployee(seller);
// 이달 지불할 급여 정보
handler.ShowAllSalaryInfo();
// 이달 지불할 급여 총합
handler.ShowTotalSalary();
return 0;
}
메인 함수 내 handler.AddEmployee(new PermanentWorker("KIM", 1000)); 와 같은 코드 4줄에서 발생.
에러 내용 : 인수 1을(를) const char[4]에서 char* (으)로 변환할 수 없습니다.
이유 : "aa" 문자열은 상수인데 변수에 값을 넣으려 해서.
C에서는 문자열 리터럴이 char의 배열이지만, C++에서는 const char의 배열이다.
에러 수정 :
1) 포인터가 아닌 배열로 선언
2) (char*) 형식으로 형변환
3) 자료형을 const char* 형태로 바꾸기
4) const_cast<char*> 사용하기
참고 : http://egloos.zum.com/kim0522ms/v/6438724
'C, C++ > 열혈 C++ 프로그래밍' 카테고리의 다른 글
08.상속과 다형성 - 2. 가상함수 (0) | 2021.10.14 |
---|---|
[윤성우 열혈 C++] 08.상속과 다형성 영상 (0) | 2021.10.12 |
[윤성우 열혈 c++] 07.상속의 이해 영상 (0) | 2021.10.07 |
윤성우 열혈 C++ 07.상속의 이해 연습문제 (0) | 2021.10.07 |
07.상속의 이해 책 (0) | 2021.10.06 |