객체의 주소 값 저장을 위해 포인터 변수를 선언할수 있으며 객체를 상속하는 유도 클래스의 객체도 가리킬 수 있다.
#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(void)
{
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;
}
Person형 포인터 변수에서 Perosn은 Student class의 기초 클래스이기 때문에 Person형 포인터 변수가 Studnet객체를 가리킬 수 있음을 나타낸다. Student와 PartimeStudent class 관계도 마찬가지.
전 챕터부터 급여관리 시스템과 관련한 이 책의 문제가 있었는데 여기서 이 문제를 위한 1차적인 해결로 상속을 소개한다.
이러한 관계들을 상속으로 구현함
EmployeeManger2.cpp
#include <iostream>
#include <cstring>
using namespace std;
class Employee
{
private:
char name[100];
public:
Employee(const char* name)
{
strcpy(this->name, name);
}
void ShowYourName() const
{
cout << "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;
}
};
class EmployeeHandler
{
private:
Employee* empList[50];
int empNum;
public:
EmployeeHandler() : empNum(0)
{}
void AddEmployee(PermanentWorker* 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();
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에 Employee클래스 객체 배열이 있는 데 여기에 이 클래스를 상속하는 클래스의 객체도 저장이 가능하다는 점이다.
EmployeeManger3.cpp
#include <iostream>
#include <cstring>
using namespace std;
class Employee
{
private:
char name[100];
public:
Employee(const char* name)
{
strcpy(this->name, name);
}
void ShowYourName() const
{
cout << "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;
}
};
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;
}
};
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
{
return PermanentWorker::GetPay() + (int)(salesResult * bonusRatio);
}
void ShowSalrayInfo() const
{
ShowYourName();
cout << "salary: " << GetPay() << endl << 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(); // 오류가 나는 이유는 Employee 객체에 ShowSalaryInfo함수가 없기 때문에!
*/
}
void ShowTotalSalary() const
{
int sum = 0;
/*
for (int i = 0; i < empNum; i++)
sum += empList[i]->GetPay(); // 마찬가지로 empList의 자료형이 Employee로 선언되었는데 이 클래스내에 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));
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;
}
위의 코드에서는 함수 오버라이딩을 통해 getpay() 함수와 ShowSalaryInfo 함수를 새로운 클래스마다 각각 멤버 함수로 정의를 해주었음. 눈 여겨 보아야 할점은 SalesWorker 클래스와 PermanentWorker 클래스의 ShowSalaryInfo 함수가 동일하지만 굳이 오버라이딩 한 이유가 그 안에서 실행되는 GetPay()함수가 다르기 때문에 별도로 정의를 한 것!
C++ 컴파일러는 포인터 연산의 가능성 여부를 판단할 때, 포인터 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형을 기준으로 판단하지 않는다.
예시 코드
class First ...
class Second ... // First 클래스 상속
class Third ... // Second 클래스 상속
int main(void)
{
Third * tptr = new Third();
Second * sptr = tptr;
First * fptr = sptr;
// 각각 first, second, third 멤버 함수로 가지고 있다고 가정
tptr->FirstFunc(); (o)
tptr->SecondFunc(); (o)
tptr->ThirdFunc(); (o)
sptr->FirstFunc(); (o)
sptr->SecondFunc(); (o)
sptr->ThirdFunc(); (x)
fptr->FirstFunc(); (o)
fptr->SecondFunc(); (x)
fptr->ThirdFunc(); (x)
이를 통해 포인터 형에 해당하는 클래스에 정의된 멤버에만 접근이 가능함을 확인할 수 있다.
--> 정리: C++ 컴파일러는 포인터를 이용한 연산의 가능성 여부를 판단할 때, 포인터의 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형을 기준으로 판단하지 않는다.
FunctionVirtualOverrid.cpp
#include <iostream>
using namespace std;
class First
{
public:
virtual void MyFunc() { cout << "FirstFunc" << endl; }
};
class Second : public First
{
public:
virtual void MyFunc() { cout << "SecondFunc" << endl; }
};
class Third : public Second
{
public:
virtual void MyFunc() { cout << "ThirdFunc" << endl; }
};
int main(void)
{
Third* tptr = new Third();
Second* sptr = tptr;
First* fptr = sptr;
fptr->MyFunc();
sptr->MyFunc();
tptr->MyFunc();
delete tptr;
return 0;
}
함수가 가상함수로 선언되면 해당 함수 호출 시, 포인터의 자료형을 기반으로 호출대상 결정을 하지 않고 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정함!
가상함수와 함수 오버라이딩 개념을 사용!
EmployeeManager4.cpp
#include <iostream>
#include <cstring>
using namespace std;
class Employee
{
private:
char name[100];
public:
Employee(const char* name)
{
strcpy(this->name, name);
}
void ShowYourName() const
{
cout << "name: " << name << endl;
}
virtual int GetPay() const
{
return 0;
}
virtual void ShowSalaryInfo() const
{
}
};
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;
}
};
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
{
return PermanentWorker::GetPay() + (int)(salesResult * bonusRatio);
}
void ShowSalrayInfo() const
{
ShowYourName();
cout << "salary: " << GetPay() << endl << 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();
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));
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;
}
위의 코드에서 함수 오버라이딩과 가상함수 설정을 위해 Employee 클래스에 의미만 지닌 함수가 2가지가 있다. GetPay()함수와 ShowSalaryInfo() 함수인데 이를 순수 가상함수로 바꾸어 잘못된 객체의 생성을 막을 수 있다.
예시
virtual int GetPay() const = 0;
virtual void ShowSalaryInfo() const = 0;
이렇게 하나 이상의 멤버함수를 순수 가상함수로 선언한 클래스를 가리켜 추상 클래스라고 한다.
의미 : 모습은 같은데 형태는 다르다. --> C++에서 같은 자료형으로 선언했어도 다른 객체를 참조하는 경우 실행하는 함수가 같아도 결과가 달라질 수 있음.
VirtualDestructor.cpp
#include <iostream>
using namespace std;
class First
{
private:
char* strOne;
public:
First(const char* str)
{
strOne = new char[strlen(str) + 1];
}
virtual ~First()
{
cout << "~First()" << endl;
delete[]strOne;
}
};
class Second : public First
{
private:
char* strTwo;
public:
Second(const char* str1, const char* str2) : First(str1)
{
strTwo = new char[strlen(str2) + 1];
}
~Second()
{
cout << "~Second()" << endl;
delete[]strTwo;
}
};
int main(void)
{
First* ptr = new Second("simple", "complex");
delete ptr;
return 0;
}
다음의 코드는 가상 소멸자가 필요한 경우의 코드이며 실행시 가상 소멸자가 정의되지 않았기 때문에 ~First 소멸자만 실행이 됨! 이러한 경우 메모리의 누수가 발생하므로 객체의 소멸과정에서는 모든 소멸자가 호출되어야하므로 virtual 선언을 추가해서 사용함
앞서 언급한 포인터와 참조 특성이 같음 --> 자료형에 따른 특성