본문 바로가기
Basic/C/C++

[C++ 언어] 제 8 강 : 상속과 다형성

by boxbop 2012. 7. 2.
반응형

 

 

 저번 강의에 이어서 상속에 관하여 조금 더 공부해보도록 하겠습니다.

 

[상속된 객체와 포인터]

 

*객체 포인터 : 객체의 주소 값을 저장할 수 있는 포인터*

 

 AAA 라는 클래스가 있다면 포인터 AAA* 는 AAA 객체의 주소 값과 AAA 클래스를 상속하는 Derived 클래스 객체의 주소 값도 저장이 가능합니다. 여기서 AAA 클래스의 포인터를 가리켜 객체 포인터라고 정의하게되죠. 아래 예제를 살펴보도록 하죠.

 

#include <iostream>

 using namespace std;

 

class Person

{

 public:

   void Sleep()

   {

      cout << "Sleep" <<endl;

    }

 };

 

class Student : public Person

{

  public:

    void Study()

    {

       cout << "Study" <<endl;

     }

  };

 

class PartTimeStd : Public Student

{

  public:

     void Work()

     {

        cout<<"Work"<<endl;

     }

};

 

int main(void)

{

  Person* p1 = new Person;

  Person* p2 = new Student;

  Person* p3 = new PartTimeStd;

 

  p1->Sleep();

  p2->Sleep();

  p3->Sleep();

 

 return 0;

}

 

 볼드처리된 부분을 살펴보면 p1에서는 딱히 궁금한점은 보이지 않을꺼구요~ p2는 Student 클래스가 Person 클래스를 상속하고 있으므로 문제가 되지 않으며 Person 포인터는 Student 객체의 주소값을 표현할 수 있으므로 역시나 문제가 되지 않습니다. p3도 마찬가지로 Person클래스를 상속하게 되므로 문제가 없겠죠? Student 클래스를 상속하긴 하지만 다시 Student 클래스는 Person 클래스를 상속하므로 결국엔 PartTimeStd 클래스도 Person 클래스를 상속하게 되는겁니다.

 

 이러한 예제를 조금 논리적으로 해석해도록 하겠습니다. Student 객체는 Student 객체이자 동시에 Person 객체가 되는 것 입니다. 왜냐하면 "Student는 Person이다" 라고 얘기할 수 있습니다. 그래서 다음과 같이 "Student 클래스의 객체는 Person 클래스 객체이다" 라고 말 할 수 있겠죠? 따라서 Person 타입의 포인터로 Student객체의 주소값을 저장할 수 있는겁니다. 다른말로 하자면 Person 타입의 포인터로 Student 객체를 가리킬 수 있다는 의미죠.

 

 마찬가지로 PartTimeStd 클래스의 객체는 PartTimeStd 객체이자, Student 객체이면서 동시에 Person 객체도 되는 것 입니다. 따라서 PartTimeStd 타입의 포인터 뿐만 아니라 Person 타입의 포인터, Student 타입의 포인터로도 가리키는 것이 가능합니다.때문에 다음과 같은 문장은 모두 올바르게 실행됩니다.

 

int main(void)

{

  Person* p1 = new PartTimeStd;

  Student* p1 = new PartTimeStd;

  PartTimeStd* p1 = new PartTimeStd;

 

  p1->Sleep();

  p2->Sleep();

  p3->Sleep();

  return 0;

 }

 

 자 여기서 하나 살펴보고 넘어갈 문제가 있습니다. 저번장에서 설명드렸습니다만 Employee Problem 생각 나시죠? 그 문제를 지금까지 배운 내용을 토대로 수정해보도록 하겠습니다. 물론 완성본은 아닙니다^^

 

class Employee

{

  protected:

   char name[20];

  public:

   Employee(char * _name);

   const char* GetName();

};

Employee::Employee(char* _name)

{

  strcpy(name,_name);

}

char* Employee::GetName()

{

   return name;

}

 

class Permanent : public Employee

{

  private:

    int salary;

  public:

    Permanent(char* _name, int sal);

    int GetPay();

};

Permanent::Permanent(char* _name, int sal) : Employee(char* _name)

{

  salary = sal;

}

int Permanent::GetPay()

{

  return salary;

}

 

class Department

{

  private:

    Employee* empList[10];

    int index;

  public:

    Department(): index(0) {};

    void AddEmployee(Employee* emp);

    void ShowList();

 };

void Department::AddEmployee(Employee* emp)

{

   empList[intdex++]=emp;

}

void Department::ShowList()

{

  for(int i = 0 ; i < index ; i++)

  {

    cout<<"name:"<<empList[i]->GetName()<<endl;

    cout<<"salary:"<<empList[i]->GetPay()<<endl; // 컴파일 오류!

   }

 }

 

class Temporary : public Employee

{

 private:

   int time;

   int pay;

 public:

   Temporary(char* _name, int _time, int _pay);

   int GetPay();

};

Temporary::Temporary(char* _name, int _time, int _pay) : Employee(_name)

{

   time = _time;

   pay = _pay;

}

int Temporary::GetPay()

{

   return time*pay; //곱셈연산

}

 

 일단은 볼드 처리된 부분을 살펴보시길 바랍니다. 상속하는 부분은 익히 아실거고 (Employee*) 부분은 전혀 문제가 되지 않음도 알겠지요? Permanent 클래스 Employee 클래스를 상속하고 있으므로, Employee 타입의 포인터는 Permanent 객체를 가리킬 수 있겟죠.

 

 볼드처리된 부분중에 컴파일 오류가 생기는 부분 보이시죠? 이 문제의 원인은 객체 포인터의 권한에 있습니다. 이 부분을 조금 더 자세하게 다루기 위해서 아까 사용했던 예제들을 조금 변경해서 살펴보도록 하겠습니다.

 

class Person

{

 public:

   void Sleep()

   {

      cout << "Sleep" <<endl;

    }

 };

 

class Student : public Person

{

  public:

    void Study()

    {

       cout << "Study" <<endl;

     }

  };

 

class PartTimeStd : Public Student

{

  public:

     void Work()

     {

        cout<<"Work"<<endl;

     }

};

 

int main(void)

{

  Person* p3 = new PartTimeStd;

 

  p3->Sleep();

  p3->Study(); //컴파일 오류

  p3->Work(); //컴파일 오류

 

 return 0;

}

 

 포인터 p3가 가리키는 객체의 Sleep 함수를 호출하고 있습니다. p3가 가리키는 객체는 partTimeStd 객체이므로 Sleep 함수가 존재합니다. 따라서 호출이 가능하겠죠? 그러면 컴파일 오류가 발생하는 2,3 번째 문장은 각각 p3가 가리키는 객체의 Study 함수와 Work 함수를 호출하고 있습니다. 분명 p3는 PartTimeStd 객체이므로 문제될 것이 없어보이죠? 그러나 컴파일 에러가 발생합니다.

 

 객체 포인터의 특성때문에 위와 같은 문제가 발생했습니다. 객체 포인터의 특성이란? AAA 클래스의 객체 포인터는 가리키는 대상이 어떠한 객체이건, AAA 클래스 타입 내에 선언된 맴버와 AAA클래스가 상속한 클래스의 맴버에만 접근이 가능하다! 라는 성질을 가지고 있습니다. 조금 더 쉽게 얘기하자면, 포인터 p3가 무엇을 가리키건,p3는 Person 타입의 포인터이므로 Person 클래스 내에 선언된 맴버에만 접근이 가능하다는 것입니다.

 

 때문에 아까

 cout<<"salary:"<<empList[i]->GetPay()<<endl;

 이러한 부분에서도 객체 포인터의 특성때문에 컴파일 에러가 발생하는 것입니다. empList[i]는 Employee 타입의 포인터이지만 GetPay 함수는 Employee 클래스의 맴버 함수가 아니고 Permanent 클래스의 함수이기 때문입니다. 따라서 접근할 수 있는 권한이 없는겁니다.

 

[상속된 객체와 참조]

 

이번에는 상속된 객체와 참조의 관계를 살펴보겠습니다.

 

*객체 레퍼런스 : 객체를 참조할 수 있는 레퍼런스*

 

마찬가지로 AAA 클래스의 레퍼런스(AAA&)는 AAA객체 뿐만 아니라, AAA클래스를 상속하는 Derived 클래스의 객체도 참조 가능합니다. 다음 예제를 통해서 설명하도록 하겠습니다.

 

class Person

{

 public:

   void Sleep()

   {

      cout << "Sleep" <<endl;

    }

 };

 

class Student : public Person

{

  public:

    void Study()

    {

       cout << "Study" <<endl;

     }

  };

 

class PartTimeStd : Public Student

{

  public:

     void Work()

     {

        cout<<"Work"<<endl;

     }

};

 

int main(void)

{

   PartTimeStd p;

   Student& ref1 = p;

   Person& ref2 = p;

  

   p.Sleep();

   ref1.Sleep();

   ref2.Sleep();

   return 0;

 }

 

 ref1, ref2 라는 이름을 p 객체에 부여하고 있습니다. 레퍼런스란 이름을 하나 더 부여하는 행위와 같다고 설명했었죠? 따라서 p, ref1, ref2 모두 같은 객체입니다. 객체 p 는 Student 클래스와 Person 클래스를 상속하고 있기 때문에 볼드처리된 부분이 문제될건 없습니다.

 

*레퍼런스의 권한*

 : AAA 클래스의 레퍼런스는 참조하는 대상이 어떠한 객체이건, AAA클래스 타입 내에 선언된 맴버와 AAA클래스가 상속한 클래스의 맴버에만 접근이 가능하다.

 

자 그렇다면 다음과 같은 예제는 어떻게 될까요?

 

itn main(void)

{

  PartTimeStd p;

  p.Sleep();

  p.Study();

  p.Work();

 

  Person& ref = p;

  ref.Sleep();

  ref.Study(); //컴파일 에러

  ref.Work(); //컴파일 에러

  return 0;

}

 

 Person 클래스는 맴버로 Study와 Work를 가지고 있지않죠? 충분히 이해가능하신 내용이라 추가 설명없이 넘어가도록 하겠습니다!

 

 이번강의는 여기서 마치도록 하구요 다음 강의는 다소 이해하기 어려운 내용일 수 있어서 이번 강의을 충분히 이해하시고 9강을 공부하시길 바랍니다!

 

 

 

 

 

반응형