객체 포인터변수 : 객체의 주소값을 저장하는 포인터변수
class Person {...};
class Student : Person{...};
Person * ptr; // 포인터변수 선언
ptr = new Person; // 포인터 변수의 객체참조
ptr = new Student;
Person형 객체는 Person 객체뿐만아니라 Person을 직접 혹은 간접적으로 상속하는 모든 객체를 가리킬수있다. ( 객체의 주소값을 저장할 수 있다 )
이를통해 , 기능성 클래스의 인자를 기초함수로 설정하면 , 그 기초함수의 유도클래스를 전부 인자로 받을 수 있기때문에 , 기능성 클래스를 전부 수정하는등의 참사를 줄일 수 있다.
include<iostream>
include<cstring>
using namespace std;
class Employee // 순수가상함수가 포함되었으므로 추상클래스이다 ( 객체생성 불가 , 참조만가능 )
{
private :
char name[50];
public :
Employee(char * name )
{
strcpy(this->name,name);
}
void ShowYourName() const
{
cout << "name : "<<name<<endl;
}
virtual int GetPay() const : 0; // 나중에 오버라이딩될 함수 , 순수 가상함수
virtual void ShowSalaryInfo() const : 0;
};
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;
}
};
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 AddSaleResult (int value)
{
saleResult += value;
{
int GetPay() const // 함수 오버라이딩
{
return PermanentWorker::Getpay() + (int)(saleResult*bonusRatio);
}
void ShowSalaryInfo() const // 함수 오버라이딩
{
ShowYourName();
cout<<salary : :<<GetPay()<<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();// Employee 클래스에 GetPay() 가상함수를 선언했기때문에 호출가능
cout<<"salary sum : " << sum<<endl;
}
~Employee()
{
for(int i=0;i<empNum;i++)
delete empList[i];
}
};
int main (void)
{
//직원 관리를 목적으로 설계된 클래스의 객체생성
EmployeeHandler handler;
//정규직 등록
handler.AddEmployee(new PermanentWorker("Kim",1000);
//영업직 등록
SalesWorker * seller = new SalesWorker("Hong",1000,0.1)
seller->AddSalesResult(7000);
handler.AddEmployee(seller);
//이번달에 지불해야할 급여의 정보
handler.ShowAllSalaryInfo();
//이번달에 지불해야할 급여의 총합
handler.ShowTotalSalary();
return 0;
}
함수 오버라이딩
유도클래스에서 기초클래스와 같은 이름의 함수를 정의하면 기초클래스의 함수는 오버라이딩을 한 유도클래스의 함수에 가려지는데 , 이를 함수오버라이딩이라고 한다.
PermanentWorker::Getpay(); // 기초클래스에서 정의된 Getpay() 함수 호출
Getpay(); // 유도클래스에서 정의된 Getpay() 함수호출
SalesWorker클래스에서 ShowSalaryInfo 함수를 오버라이딩한 이유는 PermanentWorker 클래스의 ShowSalaryInfo 함수는 상속에의해 SaleWorker 객체에도 존재하게되는데 , 따라서 PermanentWorker 클래스에 선언된 ShowSalaryInfo 함수를 사용하면 ,PermanentWorker 클래스의 GetPay 함수가 호출되기때문에, SalesWorker클래스에서 오버라이딩할 필요가 있었다.
가상함수 ( Virtual Function )
기초클래스의 포인터로 객체를 참조할때,
Employee * em1 = new PermanentWorker; 처럼 객체를 참조하는것은 가능하지만
em1->ShowSalaryInfo(); 처럼 PermanentWorker의 함수를 호출하는것은 불가능하다.
이는 C++ 컴파일러가 포인터연산의 가능성여부를 판단할때 , 포인터의 자료형을 기준으로 판단하지 , 실제 가리키는 객체의 자료형을 기준으로 판단하지 않기때문이다. - > 포인터형에 해당하는 클래스에 정의된 멤버에만 접근이 가능하다. ( 유도클래스에서 기초클래스의 함수를 호출하는것은 가능 )
함수를 오버로딩한다는것은 해당객체에서 호출되어야하는 함수를 바꾼다는것인데 , 포인터 변수의 자료형에 따라서 호출되는 함수의 종류가 달라지는것은 문제가된다 . 이때 이를 해결하기위해 만들어진것이 가상함수이다.
가상함수의 선언은 virtual 키워드의 선언을 통해서 이루어진다 . 이렇게 가상함수가 선언되고나면 이 함수를 오버라이딩하는 하는 함수도 가상함수가된다.
순수 가상함수 ( Pure Virtual Function ) 와 추상클래스 ( Abstract Class )
Employee 클래스와 같이 , 기초클래스로의 의미를 가질뿐 객체생성을 목적으로하지않은 클래스가 존재하는데 , 이 클래스를 이용해 객체를 생성한다면 프로그래머의 실수임에 틀림없다. 이같은 실수를 문법적으로 막기위해 순수가상함수를 클래스내에 선언하면된다.
순수가상함수
순수가상함수란 함수의 몸체가 정의되지않은 함수를 말한다. 그리고 이를 표현하기위해서 0을 대입한다.
ex)
virtual int GetPay() const = 0;
추상클래스
하나이상의 멤버함수를 순수가상함수로 선언한 클래스를 추상클래스라고하며, 이는 객체를 참조할수는있지만 객체생성은 불가능한클래스이다.
다형성
다형성이란 모습은 같은데 형태는 다른것을말한다. 이를 C++에 적용하면 문장은 같은데 결과는 다르다는것이된다.
ex)
Employee * em = new PermanentWorker(...);
em.ShowSalaryInfo(); // 밑에 동일한 문장이 존재한다
delete em;
em = new SalesWorker( ... );
em.ShowSalaryInfo(); // 위에 동일한 문장이 존재한다
delete em;
위처럼 같은 문장이지만 참조하는 객체의 자료형이다르기때문에 다른 실행결과가 나오게되는데 , 이는 C++에서의 다형성의 예이다.
#include <iostream>
using namespace std;
class First
{
private :
char * strOne;
public :
First(char *str )
{
strOne = new char [strlen(str)+1]; // 메모리 동적할당
}
~First()
{
delete [] strOne; // 동적할당해제
}
};
class Second
{
private :
char * strTwo;
public :
Second(char * str1, char * str2):First(str1)
{
strTwo = new char [strlen(str2) +1]; //메모리 동적할당
}
~Second()
{
delete[] strTwo; // 동적할당해제
}
};
int main (void)
{
First * ptr = new Second("simple","Complex");
delete ptr;
return 0;
}
가상소멸자 ( Virtual Destructor )
위와같은 함수가 실행되었을때 , 포인터의 자료형이 First 이기때문에, 소멸자또한 First 의 소멸자만 호출되게되고 , strTwo 의 메모리가 해제되지않아 메모리 누수가 발생하게된다 . 따라서 객체의 소멸과정에서는 delete 연산자에 사용된 포인터변수의 자료형에 상관없이 모든 소멸자가 호출되어야하고 , 이를 위해서는 소멸자에 virtual 선언을 추가하면된다.
virtual ~First()
{ ... }
상속의 계층구조상 맨위에 존재하는 기초클래스의 소멸자만 virtual로 선언하면 , 이를 유도하는 클래스의 소멸자들도 모두 가상소멸자로 선언된다 . 가상소멸자가 호출되면 상속의 계층구조상 맨 아래에존재하는 유도클래스의 소멸자가 대신 호출되면서 , 기초클래스의 소멸자가 순차적으로 호출된다.
(Third - > Second - > First 순으로)
참조자의 참조가능성
참조자또한 AAA형 참조자는 AAA 객체 또는 AAA 를 직접 혹은 간접적으로 상속하는 모든 객체를 참조할수있다.
void GoodFunction(const First &ref) { ... }
따라서 위 같은 함수는
1.First 객체 또는 First를 직접 및 간접적으로 상속하는 클래스의 객체가 인자의 대상이된다
2.인자로 전달되는 객체의 실제 자료형에 상관없이 함수 내에서는 First 클래스에 정의된 함수만 호출 가능하다.
'C++언어 > 열혈C++' 카테고리의 다른 글
C++ : Chapter -10 연산자 오버로딩 PART1 (0) | 2018.06.13 |
---|---|
C++ : Chapter-9 가상의 원리와 다중상속 (0) | 2018.05.24 |
C++ : Chapter -7 상속의 이해 (0) | 2018.05.20 |
C++ : Chpater -6 friend , static 그리고 const (1) | 2018.05.16 |
C++ : Chapter -5 복사생성자 (0) | 2018.05.10 |