IS - A 관계


상속으로 클래스의 관계를 구성하기 위해서는 조건이 필요하다. 그리고 조건과 그에 따른 필요가 충족되지 않으면, 상속은 하지 않는 것만 못하다.


상속을 위한 기본 조건인 IS-A 관계의 성립.


무선 전화기는 일종의 전화기 입니다.

무선 전화기 is a 전화기


노트북 컴퓨터는 일종의 컴퓨터입니다.

노트북컴퓨터 is a 컴퓨터


즉, 상속관계가 성립되려면 기초 클래스와 유도 클래스간에 IS-A 관계가 성립해야 한다. 만약에 이 상속관계로 묶고자 하는 두 클래스가 IS-A 관계로 표현되지 않는다면, 이는 적절한 상속의 관계가 아닐 확률이 매우 높은 것이니, 신중한 판단이 필요하다.


#include <iostream>

#include <cstring>

using namespace std;


class Computer

{

    private:

        char owner[50];

    public:

        Computer(char* name)

        {

            strcpy(owner, name);

        }

        void Calculate(void) const

        {

            cout<<"요청 내용을 계산합니다."<<endl;

        }

};


class NotebookComp :public Computer

{

    private:

        int battery;

    public:

        NotebookComp(char* name, int chag)

            : Computer(name), battery(chag)

        {}

        void Charging() {battery+=5;}

        void UseBattery() {battery-=1;}

        void MovingCal()

        {

            if(GetBatteryInfo() < 1)

            {

                cout<<"충전이 필요합니다."<<endl;

                return;

            }

            cout<<"이동하면서 ";

            Calculate();

            UseBattery();

        }

        int GetBatteryInfo(void) {return battery;}

};


class TabletNotebook :public NotebookComp

{

    private:

        char penModel[50];

    public:

        TabletNotebook(char* name, int chag, char* pen)

            :NotebookComp(name, chag)

        {

            strcpy(penModel, pen);

        }

        void Write(char* penInfo)

        {

            if(GetBatteryInfo() < 1)

            {

                cout<<"충전필요"<<endl;

                return;

            }

            if(strcmp(penModel, penInfo) != 0)

            {

                cout<<"등록된 펜이 아니야"<<endl;

                return;

            }

            cout<<"필기내용 처리한다"<<endl;

            UseBattery();

        }

};


int main(void)

{

    NotebookComp nc("qwersd", 5);

    TabletNotebook tn("sdfdfdd", 5, "123");


    nc.MovingCal();

    tn.Write("123");


    return 0;

}


NotebookComp(노트북 컴퓨터)는 Computer(컴퓨터)이다.
TabletNotebook(타블렛 컴퓨터)는 NotebookComp(노트북 컴퓨터)이다.

다음의 관계도 성립이 된다.
TabletNotebook(타블렛 컴퓨터)는 Computer(컴퓨터)이다.

때문에 IS-A 관계의 관점에서만 보면 위 클래스들의 상속관계는 적절하다고 볼 수 있다.

UML(Unified Modeling Language) 의 상속 표현.

Computer
                                                       ↑   class NotebookComp :public Computer
NotebookComp
                                                              ↑   class TabletNotebook :public NotebookComp
TabletNotebook





HAS - A 관계

※ has-a 관계도 상속의 조건은 되지만 복합 관계로 이를 대신하는 것이 일반적.

is-a 관계 외에도 상속이 형성될만한 관계가 "소유의 관계(has-a)" 이다.
경찰이 총을 소유한다. 
경찰 has a 총.

#include <iostream>
#include <cstring>
using namespace std;

class Gun
{
private:
int bullet;
public:
Gun(int bnum) :bullet(bnum)
{ }
void Shut(void)
{
cout<<"BBANG!"<<endl;
bullet--;
}
};

class Police
{
private:
int handcuffs;
Gun* pistol;
public:
Police(int bnum, int bcuff)
: handcuffs(bcuff)
{
if(bnum > 0)
pistol = new Gun(bnum);
else
pistol = NULL;
}
void PutHandcuff(void)
{
cout<<"SNAP!"<<endl;
handcuffs--;
}
void Shut(void)
{
if(pistol==NULL)
cout<<"Hut BBAND!"<<endl;
else
pistol->Shut();
}
~Police(void)
{
if(pistol != NULL)
delete pistol;
}
};

int main(void)
{
Police pman(4, 3);
pman.Shut();
pman.PutHandcuff();

return 0;
}

상속으로 묶인 두 개의 클래스는 강한 연관성을 띤다. 즉, Gun 클래스를 상속하는 Police 클래스로는 총을 소유하는 경찰만 표현 가능하다. 하지만 바로 위의 예제에서는 멤버변수 pistol을 NULL로 초기화함으로써 권총을 소유하지 않은 경찰을 매우 간단히 표현하였다. 
그리고 만약 전기봉을 소유하는 경찰의 표현을 위해서 Police 클래스를 확장하는 것도 어렵지 않다. 전기봉을 표현하는 객체를 참조하는 멤버변수만 하나 추가하면 되기 때문이다.

※ 상속은 IS-A 관계의 표현에 매우 적절하다. 그리고 경우에 따라서는 HAS-A 관계의 표현에도(소유 관계의 표현에도) 사용될 수 있으나, 이는 프로그램의 변경에 많은 제약을 가져다 줄 수 있다.

Posted by scii
:

protected 선언


C++ 의 접근 제어 지시자에는 private, protected, public 이렇게 세가지가 존재한다. 그리고 이들이 허용하는 접근의 범위에는 다음의 관계가 있다.


private < protected < public


즉, public이 허용하는 접근의 범위가 가장 넓고, private이 허용하는 접근의 범위가 가장 좁다. 반면, protected는 그 중간 범위의 접근을 허용한다. 하지만 protected는 private과 매우 유사하다.


class A

{

private:

int num1;


protected:

int num2;


public:

int num3;

A(int n1, int n2, int n3)

:num1(n1), num2(n2), num3(n3)

{ }

void Show() const

{

cout<<num1<<num2<<num3<<endl;

}

};


이 상태에서는 private과 protected가 차이를 보이지 않는다. 둘 다 클래스의 외부에서는 접근이 불가능한 반면, 클래스 내부에서는 접근이 가능하기 때문이다. 

하지만, 이 클래스가 상속이 되면, 이야기는 달라진다.


class B :public A

{

public:

B(int n1, int n2, int n3)

:A(n1, n2, n3)

{ }

void ShowData() const

{

cout<<num1; // 컴파일 에러

cout<<num2; // 컴파일 OK

cout<<num3; // 컴파일 Ok

}

};


위의 클래스는 A 클래스를 상속하고 있다. 따라서 public으로 선언된 멤버변수 num3에 접근이 가능하지만 private으로 선언된 멤버변수 num1에는 접근이 불가능하다. 

여기서 protected는 "protected로 선언된 멤버변수는 이를 상속하는 유도 클래스에서 접근이 가능하다."


protected 멤버는 유도 클래스에서 접근이 가능하다. 그리고 이것이 private과 protected의 유일한 차이점이다!!


protected선언은 private과 public에 비해 그리 많이 사용되지 않는다. 물론 유도 클래스에게만 제한적으로 접근을 허용한다는 측면에서 유용하게 사용될 수 있는 키워드이다. 하지만 기본적으로는 기초 클래스와 이를 상속하는 유도 클래스 사이에서도 '정보 은닉'은 지켜지는 게 좋다.







세가지 형태의 상속


class B :public A

{

...

}

상속을 명시하는데 있어서 public이 사용되었음을 알 수 있다. 따라서 이러한 경우 A클래스는 "public으로 상속되었다." 라고 한다. 


class B :protected A        //A클래스가 protected로 상속되었다.

{

...

}


class B :private A            //A클래스가 private으로 상속되었다.

{

...

}


이렇듯 상속에는 세가지 형태의 상속이 존재한다. 그리고 키워드 public, protected, private은 멤버의 접근권한을 명시하는 용도로도 사용되지만, 상속의 형태를 명시하는 용도로도 사용이 된다.




proteced 상속


class B :protected A

{

...

}


'protected 상속'이 의미하는 바는 다음과 같다.


"protected보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다."


protected보다 접근범위가 넓은 멤버는 public 멤버뿐이니, protected 상속을 한 클래스는 public이 protected로 변경되어서 상속이 이루어진다.


class B :protected A

{

접근불가:

int num1;

protected:

int num2;

protected:

int num3;

};

그리고 중요한 것이 있는데 private은 접근불가이다. 




private 상속


class B :private A

{

...

}


"private보다 접근의 범위가 넓은 멤버는 private으로 변경시켜서 상속하겠다"


private보다 접근범위가 넓은 멤버는 protected와 public이니 모두 private으로 변경되어 상속이 이루어진다.


class B :private A

{

접근불가:

int num1;

private:

int num2;

private:

int num3;

};


그리고 만약, 다른 클래스가 이 클래스를 다음과 같이 다시 상속한다면...

class C :public B

{

...

}


C 클래스의 모든 멤버가 private(접근불가) 이기 대문에 다음과 같은 형태가 되어버린다.

class C :public B

{

접근불가:

int num1;

접근불가:

int num2;

접근불가:

int num3;

};


이렇듯, private 상속이 이뤄진 클래스를 다시 상속할 경우, 멤버함수를 포함하여 모든 멤버가 '접근불가'가 되기 때문에 사실상 의미 없는 상속이 되고 만다.




public 상속


"public보다 접근의 범위가 넓은 멤버는 public으로 변경시켜서 상속하겠다."


그런데 public보다 넓은 멤버는 public밖에 없다. 고로 "private을 제외한 나머지는 그냥 그대로 상속한다."


즉, private 멤버는 '접근불가'의 형태로 상속을 하지만 protected 멤버는 protected로, public 멤버는 public으로 상속이 진행된다.


※ 참고로 상속의 대부분은 public 상속이다.

'Programming > C++' 카테고리의 다른 글

상속을 이용한 프로그램  (0) 2012.06.21
상속을 위한 조건  (0) 2012.06.19
상속(inheritance)  (0) 2012.06.06
C++에서의 static  (0) 2012.05.30
friend  (0) 2012.05.26
Posted by scii
:

상속(inheritance)

Programming/C++ 2012. 6. 6. 20:19 |

상속은 적용이 중요한 문법이다. 적절할 때 선별적으로 적용할 수 있어야 한다.


#include <iostream>

#include <cstring>

using namespace std;


class PermenentWorker //데이터적 성격이 강한 클래스

{

    private:

        char name[100];

        int salary;


    public:

        PermenentWorker(char* name, int money)

            :salary(money)

        {

            strcpy(this->name, name);

        }

        int GetPay(void) const

        {

            return salary;

        }

        void ShowSalaryInfo(void) const

        {

            cout<<"name: "<<name<<endl;

            cout<<"salary: "<<GetPay()<<endl<<endl;

        }

};


class EmployeeHandler //기능적 성격이 강한 클래스(컨트롤 클래스)

{

    private:

        PermenentWorker* empList[50];

        int empNum;


    public:

        EmployeeHandler() :empNum(0)

{}

        void AddEmployee(PermenentWorker* emp) //새로운 직원정보 등록

        {

            empList[empNum++] = emp;

        }

        void ShowAllSalaryInfo(void) const         //모든 직원의 급여정보 출력

        {

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

                empList[i]->ShowSalaryInfo();

        }

        void ShowTotalSalary(void) 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 PermenentWorker("asdf", 1000));

    handler.AddEmployee(new PermenentWorker("xcv", 2332));


    handler.ShowAllSalaryInfo();


    handler.ShowTotalSalary();

    return 0;

}



기능의 처리를 실제로 담당하는 클래스를 가리켜 '컨트롤(control) 클래스' 또는 '핸들러(handler) 클래스' 라 한다.


※ 컨트롤 클래스는 기능 제공의 핵심이 되기 때문에 모든 객체지향 프로그램에서 반드시 존재하는 클래스이다.




※ 멤버는 클래스가 정의될 때, 멤버의 초기화를 목적으로 정의된 생성자를 통해서 초기화하는 것이 가장 안정적이다. 그것이 비록 상속의 관계로 묶여있다 할지라도.


◆ 용어 정리 ◆


상위 클래스                                하위 클래스

기초(base) 클래스                      유도(derived) 클래스

슈퍼(super) 클래스                    서브(sub) 클래스

부모 클래스                               자식 클래스




#include <iostream>

using namespace std;


class Base

{

    private:

        int baseNum;

    public:

        Base(void) :baseNum(20)

        {

        cout<<"Base(void)"<<endl;

        }

        Base(int n) :baseNum(n)

        {

        cout<<"Base(int n)"<<endl;

        }

        void ShowBaseData()

        {

            cout<<baseNum<<endl;

        }

};


class Derived :public Base

{

    private:

        int derivedNum;

    public:

        Derived() :derivedNum(30)

        {

        cout<<"Derived()"<<endl;

        }

        Derived(int n) : derivedNum(n)

        {

        cout<<"Derived(int n)"<<endl;

        }

        Derived(int n1, int n2)

            : Base(n1), derivedNum(n2)

        {

            cout<<"Derived(int n1, int n2)"<<endl;

        }

        void ShowDerivedData()

        {

            ShowBaseData();

            cout<<derivedNum<<endl;

        }

};


int main(void)

{

    cout<<"case1......"<<endl;

    Derived dr1;

    dr1.ShowDerivedData();


    cout<<"case2........."<<endl;

    Derived dr2(12);

    dr2.ShowDerivedData();


    cout<<"case3..........."<<endl;

    Derived dr3(14, 15);

    dr3.ShowDerivedData();


    return 0;

}



위의 소스코드로 알 수 있는 점 두가지.

1. 유도 클래스의 객체 생성 과정에서 기초 클래스의 생성자는 100% 호출이 된다.

2. 유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출이 된다.


★ "클래스의 멤버는 해당 클래스의 생성자를 통해서 초기화해야 한다."




유도 클래스 객체의 소멸과정



위의 결과로 알 수 있는 사실.

1. 유도 클래스의 객체가 소멸될 때에는, 유도 클래스의 소멸자가 실행되고 난 다음에 기초 클래스의 소멸자가 실행된다.

2. 스택에 생성된 객체의 소멸순서는 생성순서와 반대이다.


※ 이러한 객체소멸의 특성 때문에 상속과 연관된 클래스의 소멸자는 "생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제한다" 는 원칙을 지켜서 정의해야 한다.




#include <iostream>

#include <cstring>

using namespace std;


class Person

{

    private:

        char* name;

        

    public:

        Person(char* name)

        {

            this->name = new char[strlen(name)+1];

            strcpy(this->name, name);

        }

        ~Person()

        {

            delete []name;

        }

        void WhatYourName() const

        {

            cout<<"My name is "<<name<<endl;

        }

};


class UnivStudent : public Person

{

    private:

        char* major;


    public:

        UnivStudent(char* name, char* major)

            :Person(name)

        {

            this->major = new char[strlen(major)+1];

            strcpy(this->major, major);

        }

        ~UnivStudent()

        {

            delete []major;

        }

        void WhoAreYou() const

        {

            WhatYourName();

            cout<<"My major is "<<major<<endl<<endl;

        }

};


int main(void)

{

    UnivStudent st1("Jeon", "Houdini");

    st1.WhoAreYou();


    UnivStudent st2("Seo", "Maya");

    st2.WhoAreYou();


    return 0;

}



Posted by scii
: