본문 바로가기

C++/윤성우의 열혈 프로그래밍 C++

윤성우의 열형 C++ chpt.10

상속

 

상속의 내용을 다루기 전 다음과 같은 코드를 살펴보자

 

Theme : 급여관리

 

<정규직을 표현해놓은 클래스> - 데이터 중심적 클래스

class PermanentWorker
{
private:
    char nam[100];
    int salary;	매달 지불해야 하는 급여액
pubilc:
    permanentVorker(char *name, int money)
        :salary(money)
    {
        strcpy(thie->name, name);
    }
    int GetPay() const
    {
        return salary;
    }
    void ShowSalaryInfo() cosnt;
    {
        cout << "name: " << name << endl;
        cout << "salary: " << salay << endl;
    }
};

 

< 급여 관련 정보를 다루는 클래스> - 행위 중심적 클래스

class EmployeeHandler
{		// 프로그램 전체 기능의 처리를, 프로그램의 흐름을 담당하는 클래스를
private:	// '컨트롤 클래스' 라 부르고, 이 클래스가 해당된다
    PermanentWorker* empLsit[50];
    int empNUm;
public:
    EmployeeHandeler() : 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];
    }
};

 

 이 코드를 통해서 다음과 같은 문제를 해결해보고자 한다

 

이 문제는 영업직과 임시직에 해당하는 클래스의 추가로 끝나지 않는다.

컨트롤 틀래스인 EmplyeeHandler 클래스의 대대적인 변경으로 이어진다.

 

좋은 코드요구사항의 변경 및 기능의 추가에 따른 변경이 최소화되어야 한다.

 

이를 위한 해결책으로 '상속'이 사용된다

 

[ 상속의 문법적 이해 ]

상속의 방법과 그 결과

class Person
{
private:
    int age;		// 나이
    chat name[50];	//이름
pubilc:
    Person(int myage, char* myname) : age(myage)
    {
        strcpy(name, myname);
    }
    void WhatYourName() const
    {
        cout << "My name is " << neme << endl;
    }
    void HowOldAreYou() const
    {
        cout << "I'm " << age << " years old" << endl;
    }
};
class UnivStudent : public Person	// Person 클래스를 pulic 상속함
{
private:
    char major[50];	// 전공과목
public:
    UnivStudent(char* name, int myage, char* mymajor)
        : Person(myage, myname)
    {
        strcpy(major, mymajor);
    }
    void WhoAreYou() const
    {
        WhatYourName();	// Person 클래스의 멤버
        HowOldAreYou();	// Person 클래스의 멤버
        cout << "My major is " << major << endl << endl;
    }
};

 

상속을 받게 되면, 상속된 객체의 멤버 변수나 함수도 같이 상속된 객체에 전달된다,

 

상속자와 피상속자의 용어는 다음과 같이 통용된다.

 

 

상속의 의미에서 헷갈리지 말아야 할 포인트는 상속을 받는 것은 객체가 아닌 클래스라는 점이다.

 

즉, 이미 민들어진 객체를 상속받는 게 아닌 코드적인 클래스를 상속 받기 때문에

 

모양상 자식이 부모를 무등 태우고 있다고 접근해야 된다.

 

부모를 무등 태우고 있는 상황에서 초기화를 진행할 때,

 

부모에게서 초콜릿을 뻇어 오고 싶어도, 우선 부모에게 초콜릿이 쥐어져 있어야 뺏어 오든 말든 할 수 있듯이

 

public:
    UnivStudent(char* name, int myage, char* mymajor)
        : Person(myage, myname)

이처럼 상속 받은 클래스를 먼저 멤버 변수들을 초기화 시켜준 후,

해당 초기화된 데이터를 바탕으로 자식의 멤버 변수를 초기화 시킬 수 있다.

 

상속받은 클래스로 객체 생성시
UnivStudent(char* name, int myage, char* mymajor) 생성자를 호출하면서 전달되는 앞의 인자 두 개

부모의 생성자에 값을 전달 해주기 위해 마련된 파라미터이다

 

즉, 부모에게 초콜릿을 전달해 줄 의무가 있기 때문에 자식이 먼저 초콜릿을 받아서 부모의 입에 물린 후,

 

다시 뺏어올 때, 불효 자식이 쾌감을 얻을 수 있다는 메커니즘이다.

 

또한, 자식 클래스부모 클래스 입장에서 외부로 인식되기 때문에 

자식 클래스부모 클래스의 private 영역에 직접 접근할 수가 없다.

이러한 이유 때문에서도 부모 클래스의 생성자를 호출해줘야 되는 부분도 존재한다.

 

이는 부모의 입에 물릿 초콜릿을 강제로 가져올 수 없으니

불효 자식이 부모를 살살 구슬려서 초콜릿을 받아내는 행위라고 볼 수 있다.

 

 

[ 자식 클래스의 객체 생성 과정]

class SoBase
{
private:
    int baseNum;
public:
    SoBase() : BaseNum(20);
    {
        cout << "SoBase()" << endl;
    }
    SoBase(int n) : baseNum(n);
    {
        cout << "SoBase(int n)" << endl;
    }
    void ShowBaseData()
    {
        cout << baseNum << endl;
    }
};
class SoDerived : public SoBase
{
private:
    int derivNum;
public:
    SoDerived() : derivNum(30)		// 상속 생성자의 명시 없으므로 인해 void 생성자 자동 호출
    {
        cout << "SoDerived()" << endl;
    }
    SoDerived(int n) : derivNum(n);	// 상속 생성자의 명시 없으므로 인해 void 생성자 자동 호출
    {
        cout << "SoDerived(int n)" << endl;
    }
    SoDerived(int n1, int n2) : SoBase(n1), derivNum(n2)
    {
        cout << "SoDerived(int n1, int n2)" << endl;
    }
    void ShowDerivedData()
    {
        ShowBaseData();
        cout << derivNUm << endl;
    }
};
int main()
{
    cout << "case1..... " << endl;
    SoDerived dr1;
    dr1.ShowDeriveData();
    cout << "-------------------------" << endl;

    cout << "case2..... " << endl;
    SoDerived dr2(12);
    dr2.showDeriveData();
    cout << "-------------------------" << endl;

    cout << "case3..... " << endl;
    SoDerived dr3(23, 24);
    dr3.ShowDeriveData();

    return 0;
}
-----------------------------------------------------------------------

// 결과
case1.....
SoBase()
SoDerived()
20
30
-------------------------
case2.....
SoBase()
SoDerived(int n)
20
12
-------------------------
case3.....
SoBase(int n)
SoDerived(int n1, int n2)
23
24

 

class SoDerived : public SoBase

의 첫 번째와 두 번째 생성자를 보면, 상속 받은 SoBase 의 생성자를 명시하지 않은 것을 볼 수 있다.

 

이 경우, SoBase 의 void 생성자가 자동으로 호출된다.

 

이는 우리가 클래스 제작시 생성자를 넣지 않으면 컴파일러가 기본 생성자를 넣어 주고,

복사 생성자 명시할 때, 기본 생성자를 만들어 주지 않는 것과 유사한 메커니즘이다.

 

단, 상속 받는 클래스에 void 생성자가 존재했을 때만 유효하며,

상속 받는 클래스에서 void 생성자가 없다면 이는 컴파일 에러를 발생시킨다

 

여기서 한 가지 궁금한 점이 생겼다

더보기

부모 클래스에 생성자를 명시하지 않으면 기본 생성자가 생성이 되자않나?

 

자식 클래스에서 부모 클래스의 생성자를 기입하지 않으면 부모의 기본 생성자가 자동으로 호출된다면,

 

이 둘이 이어질 수도 있어 보이는데?

실제로 부모 클래스에 아무런 생성자를 명시하지 않았을 때,

 

자식 클래스에서 부모 클래스의 명시를 생략한다면, 

 

컴파일러가 만들어준 부모 클래스의 default 생성자를 자식에서 호출할 수 있게 된다.

 

[ 자식 클래스의 객체 소멸 과정]

class SoBase
{
private:
    int baseNum;
public:
    SoBase(int n) : baseNum(n)
    {
        cout << "SoBase() : " << baseNum << endl;
    }
    ~SoBase()
    {
        cout << "~SoBase() : " << baseNum << endl;
    }
};
class SoDerived : public SoBase
{
private:
    int derivNum;
public:
    SoDerived(int n) : SoBase(n), derivNum(n)
    {
        cout << "SoDerived() : " << deriveNum << endl;
    }
    ~SoDerived90
    {
        cout << "~SoDerived() : " << derivenum << endl;
    }
};
int main(void)
{
    SoDerived drv1(15);
    SoDerived drv2(27);
    
    return 0;
}
--------------------------------------------------------
SoBase() : 15
SoDerived() : 15
SoBase() : 27
SoDerived() : 27
~SoDerived() : 27
~SoBase() : 27
~SoDerived() : 15
~SoBase() : 15

 

소멸은 생성된 객체의 역순으로 소멸된다.