728x90
728x90

1. 전장 내용확인

int main(void)
{
	Simple * sim1 = new ....;
	Simple * sim2 = new ....;
}

두 포인터 변수는 new 연산자에 의해 생성된 객체가 가리킨다.

Q. 두 포인터 변수가 가리키는 객체의 자료형은 무엇일까 생각해보자.

sim1, sim2가 가리키는 객체는 Simple 클래스이거나 Simple 클래스를 상속하는 클래스의 객체일 것이다.

 

 

2. 전장 내용확인

class Base
{
public:
	void BaseFunc() { cout<<"Base"<<endl; }
};

class Derived : public Base
{
public:
	void DerivedFunc() { cout<<"Derived"<<endl; }
};
int main(void)
{
Base * bptr = new Derived(); //컴파일 ㅇㅋ
bptr->DerivedFunc(); //에러
....
}

 

컴파일 에러 이유 : bptr이 Base형 포인터이기 때문이다.

가리키는 대상은 Derived 객체지만 컴파일되지 않는다. C++ 컴파일러는 포인터연산의 가능성 여부를 판단할 때,
포인터의 자료형을 기준으로 판단하지 실제 가리키는 객체의 자료형을 기준으로 판단하지 않는다.

 

 

int main(void)
{
Base * bptr = new Derived(); //컴파일 ㅇㅋ
Derived * dptr = bptr; //에러 - 포인터 bptr의 포인터 형만 갖고 대입가능성을 판단한다.
....
}

오류메세지 : base*에서 derived*로의 잘못된 변환

 

 

역시 포인터 형만 갖고 대입가능성을 판단하여 역시 컴파일 에러가 난다. 
Base * bptr = new Derived();가 컴파일이 되는 이유 : Derived 클래스는 Base 클래스의 유도클래스니까 Base 클래스의 포인터 변수로 Derived 객체의 참조가 가능하니 컴파일에 문제가 없다. 

 

int main(void)
{
Derived * dptr = new Derived(); //컴파일 ㅇㅋ
Base * bptr = dptr; //컴파일 ㅇㅋ
....
}

 

 

다른 예

#include <iostream>
using namespace std;

class First
{
public:
    void FirstFunc() { cout << "firstFunc" << endl; }
};

class Second: public First
{
public:
    void SecondFunc() { cout << "SecondFunc" << endl; }
};

class Third: public Second
{
public:
    void ThirdFunc() { cout << "ThirdFunc" << endl; }
};


int main()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;
}

Third형 포인터 변수 tptr이 가리키는 객체는 Second형 포인터 변수 sptr도 가리킬 수 있으므로 컴파일 오류가 없다.
그러나 객체를 참조하는 포인터의 형에 따라서 호출할 수 있는 함수의 종류에는 제한이 따른다.

int main()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;

    tptr->FirstFunc(); // (o)
    tptr->SecondFunc(); // (o)
    tptr->ThirdFunc(); // (o)

    sptr->FirstFunc(); // (o)
    sptr->SecondFunc(); // (o)
    //sptr->ThirdFunc(); // (x) class Second에 ThirdFunc 멤버가 없습니다

    fptr->FirstFunc(); // (o)
    //fptr->SecondFunc(); // (x)
    //fptr->ThirdFunc(); // (x) class First에 ThirdFunc 멤버가 없습니다

}

 

 결론 : 포인터 형에 해당하는 클래스에 정의된 멤버에만 접근이 가능하다. 

 

 

함수의 오버라이딩과 포인터 형
#include <iostream>
using namespace std;

class First
{
public:
    void MyFunc() { cout << "firstFunc" << endl; }
};

class Second: public First
{
public:
    void MyFunc() { cout << "SecondFunc" << endl; } //오버라이딩
};

class Third: public Second
{
public:
    void MyFunc() { cout << "ThirdFunc" << endl; }
};


int main()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;

    fptr->MyFunc();
    sptr->MyFunc();
    tptr->MyFunc();
    
    delete tptr;
    return 0;
}

 

third형 포인터 변수 tptr 이 참조하는 객체에는 총 3개의 MyFunc()이 있고, 오버라이딩 관계라서 
가장 마지막에 오버라이딩한 Third 클래스의 MyFunc()이 호출된다.

 

 

가상함수

포인터 변수의 자료형에 따라 호출되는 함수의 종류가 달라지지 않게 하기 위한 함수
virtual 키워드의 선언을 통해 이뤄진다.

class First
{
public:
    /*void MyFunc() { cout << "firstFunc" << endl; }*/
    virtual void MyFunc(){ cout << "firstFunc" << endl; }
};

 

가상함수가 선언되면, 가상함수를 오버라이딩 하는 함수도 가상함수가 된다.
가상함수가 되면, 함수 호출 시 포인터의 자료형을 기반으로 호출대상을 결정하지 않고, 
포인터 변수가 실제로 가리키는 객체를 참조하여 호출을 대상을 결정한다. ***

#include <iostream>
using namespace std;

class First
{
public:
    /*void MyFunc() { cout << "firstFunc" << endl; }*/
    virtual void MyFunc(){ cout << "firstFunc" << endl; }
};

class Second: public First
{
public:
    virtual void MyFunc() { cout << "SecondFunc" << endl; }
    //void MyFunc() { cout << "SecondFunc" << endl; } //오버라이딩
};

class Third: public Second
{
public:
    virtual void MyFunc() { cout << "ThirdFunc" << endl; }
    //void MyFunc() { cout << "ThirdFunc" << endl; }
};


int main()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;

    fptr->MyFunc();
    sptr->MyFunc();
    tptr->MyFunc();
    
    delete tptr;
    return 0;
}

 

 

이전 단원의 문제 해결

// EmployeeManager4.cpp

#define _CRT_SECURE_NO_WARNINGS
#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;
    }

    // 추가
    virtual int GetPay() const
    {
        return 0;
    }

    virtual void ShowSalaryInfo() const
    {

    }
};

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 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() 호출 
    }
};


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 {
        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((char*)"KIM", 1000));
    handler.AddEmployee(new PermanentWorker((char*)"LEE", 1500));

    // 임시직 등록
    TemporaryWorker* alba = new TemporaryWorker((char*)"Jung", 700);
    alba->AddWorkTime(5); //5시간 일한결과 등록
    handler.AddEmployee(alba);

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

    // 이달 지불할 급여 정보
    handler.ShowAllSalaryInfo();

    // 이달 지불할 급여 총합
    handler.ShowTotalSalary();
    return 0;
}

 

 

순수 가상함수와 추상 클래스

Employee클래스는 객체 생성을 목적으로 정의된 것이 아니다
다음과 같은 실수가 있어도 문법적으로는 문제가 없어 컴파일러에서 잡지 못한다.

Employee * emp = new Employee("Lee Dong Sook");


가상함수 말고 순수 가상함수로 바꿔서 객체 생성을 문법적으로 막아준다.
순수 가상함수 : 함수의 몸체가 정의되지 않은 함수

class Employee  // 고용인
{
private:
    char name[100];  // 고용인의 이름
public:
    Employee(char* name)
    {
        strcpy(this->name, name); 
    }
    void ShowYourName() const
    {
        cout << "name : " << name << endl;
    }

    //가상함수
    /*virtual int GetPay() const
    {
        return 0;
    }*/

    //순수가상함수 
    virtual int GetPay() const = 0;

    //가상함수
    /*virtual void ShowSalaryInfo() const
    {

    }*/

    //순수가상함수 
    virtual void ShowSalaryInfo() const = 0;
};

 

= 0은 명시적으로 몸체를 정의하지 않았음을 컴파일러에 알리는 것이다. 
이제 Employee 객체를 생성하려고 하면 컴파일에러가 난다.

하나 이상의 멤버함수를 순수 가상함수로 선언한 클래스추상클래스라고 한다.
완전하지 않아 객체생성이 불가능한 클래스라는 뜻이다.

 

 

다형성

방금처럼 문장은 같은데 결과는 다름을 의미한다.

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v