Turbo-C
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
터보-C 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
Lua 게시판
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C/C++ 강좌/문서
[33] (04) Inheritance in C++
남병철.레조 [lezo] 8426 읽음    2009-06-21 12:00


[목차]

(1) 상속성의 소개
(2) 재활용성
(3) 생성자와 상속성
(4) 접근제어
(5) 구성
(6) 다중상속










(1) 상속성의 소개

    - 구성 ('소유'관계)
    Class에 인스턴스 데이타를 넣는 것은 곧 '소유 관계'를 만드는 것이다.
    Ex) employee 객체가 있고 이 클래스의 데이타 항목중 하나가 사원의 이름
        이라면, employee 객체가 '이름을 소유한다'고 말할 수 있다.
        employee 객체가 다른 변수로 구성되므로 '구성'이라고 부른다.
    OOP의 구성은 객체가 다른 객체로 구성되는 실생활의 상황을 모델링한다.



    - 상속성 ('동족'관계)
    OOP의 상속성은 실생활에서 일반화라고 부르는 개념을 반영한다.
    Ex) 경주용 자전거, 산악용 자전거 및 어린이용 자전거가 있다면, 이들은
        모두 자전거라는 더 일반적인 개념의 특정 인스턴스(사례)라고 말할
        수 있다.



    - 상속성의 간단한 예
//---------------------------------------------------------------------------]
// 작성일 : 2001.08.28
// 제  목 : 상속성의 간단한 예
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
#include 
#pragma hdrstop

//---------------------------------------------------------------------------

class Parent
{
private:
    float flov;

public:
    void get() {
        cout << "\n    Enter float value : ";
        cin >> flov;
    }

    void display() {
        cout << "flov = " << flov;
    }
};

//---------------------------------------------------------------------------

class Child : public Parent
{
private:
    int intv;

public:
    void get() {
        Parent::get();
        cout << "    Enter integer value : ";
        cin >> intv;
    }

    void display() {
        Parent::display();
        cout << ", intv = " << intv;
    }
};

//---------------------------------------------------------------------------

#pragma argsused
void main()
{

    Child ch;

    cout << "Requesting data for child object";
    ch.get();

    cout << "Data in child object is ";
    ch.display();

    getch();

}

//---------------------------------------------------------------------------

(결과)
Requesting data for child object
    Enter float value : 5.5
    Enter integer value : 5
Data in child object in flov = 5.5, intv = 5

//---------------------------------------------------------------------------
- 기초 Class와 유도 Class의 함수에 접근하기 ===================================================================================== 함수의 모체 겹지정 상태 기초 클래스에서 함수에 접근 유도 클래스에서 함수에 접금 ===================================================================================== 기초 클래스 서로다른이름 basefunc() basefunc() 기초 클래스 겹지정됨 func() Base::func() 유도 클래스 서로다른이름 함수를 알 수 없음 dervfunc() 유도 클래스 겹지정됨 함수를 알 수 없음 func() ===================================================================================== Ex)첫번째 라인 설명 - 호출 하고자 하는 함수는 기초 Class에 있고 기초 Class와 유도 Class는 서로 다른 이름으로 지정된 함수이다. 즉 기초 클래스 인스턴스에서는 함수에 접근할 때 basefunc()를 호출하고 함수가 기초 Class에 있다하더라도 유도 클래스 인스턴스 에서도 basefunc()를 호출하면 겹지정 되어있지 않으므로 기초 Class의 함수를 호출 할 수 있다. Ex)네번째 라인 설명 - 유도 클래스에 호출하고자하는 함수가 있다고 가정하자. 함수는 겹지정 되어있으므로 기초 Class와 유도 Class가 같은 형태이다. 기초 Class 인스턴스에서는 유도 Class의 내용을 볼 수 없으므로 함수 호출도 불가능하다. 유도 Class 인스턴스에서는 함수의 이름만으로 호출이 가능하다. - 추상 Class 다른 Class의 기초 Class로만 사용하는 Class를 추상 Class라고 한다. (가상함수 및 프렌트 함수를 다룰때 다시 자세히 언급하겠다.) (2) 재활용성 - 상속에의한 스택 Class 보완
//---------------------------------------------------------------------------
// 작성일 : 2001.08.30
// 제  목 : 상속으로 클래스 강화하기 예 (Stack)
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
#include 
#include 
#pragma hdrstop

//---------------------------------------------------------------------------

class Stack
{
protected:
    enum { SIZE = 20 };
    int st[ SIZE ];
    int top;

public:
    Stack() {
        top = -1;
    }

    void push( int var ) {
        st[ ++top ] = var;
    }

    int pop() {
        return st[ top-- ];
    }
};

/////////////////////////////////////////////////////////////////////////////

class Stack2 : public Stack
{
public:
    void push( int var ) {
        if( top >= SIZE - 1 ) {
            cout << "Error : stack overflow";
            exit( -1 );
        }
        Stack::push( var );
    }

    int pop() {
        if( top < 0 ) {
            cout << "Error : stack underflow";
            exit( -1 );
        }
        return Stack::pop();
    }
};

//---------------------------------------------------------------------------

#pragma argsused
void main()
{

    Stack2 s;
    s.push( 11 );
    s.push( 12 );
    s.push( 13 );
    cout << s.pop() << endl;
    cout << s.pop() << endl;
    cout << s.pop() << endl;
//    cout << s.pop() << endl;
            
    getch();

}

//---------------------------------------------------------------------------

(결과)
13
12
11

//---------------------------------------------------------------------------
- 상속되지 않는 함수 * 생성자는 자동으로 상속할 수 없다. 기초 Class와 유도 Class 생성자는 서로 다른 데이터를 만들어(생성, 초기화) 내므로, 한 생성자 대신 또 다른(상위 Class) 생성자를 사용할 수 없다. * '=' 연산자는 자동으로 상속할 수 없다. '=' 연산자는 유도 Class 데이타에 값을 대입해야 하며, 기초 Class의 '='는 기초 Class에 값을 대입한다. * 파괴자는 서로다른 일을 하므로 상속할 수 없다. 위와 같은 맥락으로 각각의 경우에 각각의 작업을 한다. Ex) '=' 연산자를 기초 Class에는 명시적으로 정의하는데 유도 Class에는 명시적 정의가 없을경우에 컴파일러는 기본 버전의 '='을 생성하게 된다. 이 경우에 Class는 구성원별 일대일 복제가될 것이다. (3) 생성자와 상속성 - 생성자 연쇄 기초 Class와 유도 Class가 있을경우 기초 Class 생성자가 먼저 실행된 후에 유도 Class 생성자가 실행된다. 유도 Class를 기준으로 보았을 경우 기초 Class는 유도 Class의 하위 객체여서 각 부분을 먼저 구성해야만 하는 당연한 이치이다. 아래의 예처럼 명시적인 생성자와 파괴자를 정의하지 않더라도, 컴파일러는 실제로 현재의 객체 및 그 하위(기초Class)객체를 내부적으로 만들고 파괴하는 생성자와 파괴자를 호출한다. 객체의 내부적 자동생성자는 언제나 프로그래머가 정의하는 명시적인 것보다 먼저 호출된다. 명시적인 생성자가 실행하기 시작할 때, 객체와 그것의 모든 하위 객체는 이미 존재하며 초기화되어 있다.
//---------------------------------------------------------------------------]
// 작성일 : 2001.08.30
// 제  목 : Base Class와 Inherite Class의 생성과 소멸관계
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
#include 
#pragma hdrstop

//---------------------------------------------------------------------------

class Parent
{
public:
    Parent() {
        cout << "\n    Parent constructor";
    }

    ~Parent() {
        cout << "\n    Parent destructor";
    }
};
/////////////////////////////////////////////////////////////////////////////
class Child : public Parent
{
public:
    Child() {
        cout << "\n    Child constructor";
    }

    ~Child() {
        cout << "\n    Child destructor";
    }
};

//---------------------------------------------------------------------------

#pragma argsused
void main()
{

    cout << "\nStarting";
    Child ch;
    cout << "\nTerminating";

    getch();

}

//---------------------------------------------------------------------------

(결과)
Starting
    Parent constructor
    Child constructor
Terminating
    Child destructor
    Parent destructor

//---------------------------------------------------------------------------
- 생성자의 상속성? 기초 Class에 n인수 생성자가 있더라도 유도 Class에서 기초 Class와 똑같은 n인수 생성자를 사용해야할 때 기초 Class의 n인수 생성자를 상속의 방법으로 재사용할 수 없다. 결국 유도 Class에서 기초클래스와 일치하는 n인수 생성자를 만들고 기초 Class 의 생성자를 호출한다. 이처럼 초기화 목록에서 기초 Class에 데이타를 전달해주는 알맹이(?)없는 생성자는 유도 Class에서 많이보게 될것이다.
//---------------------------------------------------------------------------]
// 작성일 : 2001.08.30
// 제  목 : 유도 Class 생성자에서 기초 Class 생성자 호출하기
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
#include 
#pragma hdrstop

//---------------------------------------------------------------------------

class Mu
{
private:
    int mudata;

public:
    Mu() : mudata( 0 ) {}

    Mu( int m ) : mudata( m ) {
        cout << "Data = " << mudata;
    }
};
/////////////////////////////////////////////////////////////////////////////
class Nu : public Mu
{
public:
    Nu() {}

    Nu( int n ) : Mu( n ) {}
};

//---------------------------------------------------------------------------

#pragma argsused
void main()
{

    Nu n1;
    Nu n2( 77 );

    getch();

}

//---------------------------------------------------------------------------

(결과)
Data = 77

//---------------------------------------------------------------------------
(4) 접근제어 - 데이터를 private으로 유지하려면 어떻게? 데이타는 private으로 설정하고 데이타를 액세스하는 함수는 protected으로 설정한다. 마지막으로 일반 기능 한수들을 public으로 선언한다. 상속시에 private으로 상속하면 유도 Class에 기초 Class가 포함되어 있지만 유도 Class는 기초 Class에 대해 알지 못한다.
//---------------------------------------------------------------------------
// 작성일 : 2001.09.01
// 제  목 : Private 상속
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
#include 
#pragma hdrstop

//---------------------------------------------------------------------------

class alpha
{
public:
    void memfunc() {}
};
/////////////////////////////////////////////////////////////////////////////
class beta : public alpha
{};
/////////////////////////////////////////////////////////////////////////////
class gamma : private alpha
{};

//---------------------------------------------------------------------------

#pragma argsused
void main()
{

    void anyfun( alpha );           // 함수는 alpha 인수를 가진다.
    alpha aa;
    beta bb;
    gamma gg;

    bb.memfunc();                   // 좋음
    gg.memfunc();                   // 오류 : 'memfunc()에 접근할 수 없음'

    aa = bb;                        // 좋음
    aa = gg;                        // 오류 : 'gamma를 alpha로 변환할 수 없음'

    getch();

}

//---------------------------------------------------------------------------
- 접근 요약 유도 Class 구성원 함수에 허용된 기초 Class 구성원에 대한 접근 ==================================================================== Class 상속성 : private protected public ==================================================================== private Base Class Data No Access No Access No Access protected Base Class Data No Access Access Access public Base Class Data No Access Access Access ==================================================================== 외부에 정의된 데이타에서 유도 Class 객체에 허용된 기초 Class접근 ==================================================================== Class 상속성 : private protected public ==================================================================== private Base Class Data No Access No Access No Access protected Base Class Data No Access No Access No Access public Base Class Data No Access No Access Access ==================================================================== - 2세대 이상의 상속 여러 세대를 상속할때는 초기화 목록에서 선조 생성자를 호출할 때 세대를 무시할 수 없다. Ex) 아래의 Parent를 선조인 Gparent로 바로 초기화 할 수 없다. public: Child( int i1, float f1, int i2, float f2, int i3, float f3 ) : Parent( i1, f1, i2, f2 ), --> (X) Gparent( i1, f1 ) intv( i3 ), flov( f3 ) {}
//---------------------------------------------------------------------------
// 작성일 : 2001.09.01
// 제  목 : 2이상의 상속성에서의 생성자 초기화
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
#include 
#pragma hdrstop

//---------------------------------------------------------------------------

class Gparent
{
private:
    int intv;
    float flov;
public:
    Gparent( int i, float f ) : intv( i ), flov( f ) {}

    void display() {
        cout << "조부모 : " << intv << ", " << flov << "; " << endl;
    }
};
/////////////////////////////////////////////////////////////////////////////
class Parent : public Gparent
{
private:
    int intv;
    float flov;

public:
    // Gparent와 Parent를 초기화
    Parent( int i1, float f1, int i2, float f2 ) : Gparent( i1, f1 ),
                                                   intv( i2 ), flov( f2 ) {}

    void display() {
        Gparent::display();
        cout << "부  모 : " << intv << ", " << flov << "; " << endl;
    }
};
/////////////////////////////////////////////////////////////////////////////
class Child : public Parent
{
private:
    int intv;
    float flov;

public:
    Child( int i1, float f1,
           int i2, float f2,
           int i3, float f3 ) :
           Parent( i1, f1, i2, f2 ),
           intv( i3 ), flov( f3 )
           {}

    void display()
    {
        Parent::display();
        cout << "자  식 : " << intv << ", " << flov << endl;
    }
};

//---------------------------------------------------------------------------

#pragma argsused
void main()
{

    Child ch( 1, 1.1, 2, 2.2, 3, 3.3 );
    cout << "\nData in ch = " << endl;
    ch.display();

    getch();

}

//---------------------------------------------------------------------------

(결과)
Data in ch =
조부모 : 1, 1.1;
부  모 : 2, 2.2;
자  식 : 3, 3.3

//---------------------------------------------------------------------------
(5) 구성 - 구성이란? 한 객체를 또 다른 객체에 넣는 것. 다른 Class 사양에 어떤 클래스의 객체를 정의하는 것. Ex) class alpha {}; class beta { private: alpha aObj; }; 물론 객체에 int나 char와 같은 기본형의 인스턴스 데이터를 넣을 때마다 초보적인 종류의 구성을 사용했다. 그러나 구성이라 하면 대개 다른 Class의 사양에 클래스 객체를 삽입하는 것을 의미한다. - 구성은 어떤때 사용하는게 적절한가? 구성으로 사용할 Class를 A라고 하고 A를 사용하는 Class를 B라고 하자. A는 B Class의 내부 작동에 사용되기 때문에 Class 사용자는 A를 상속(동족)관계로 간주할 필요가 없다. 즉, 이럴때 구성을 사용한다. - 구성보다 상속성을 사용할 때는? Class들 사이의 '동족' 관계가 중요한 경우가 바로 그런 경우이다. Ex) employee의 객체 배열이 있다고 하자. 다음과 같이 관리자, 과학자, 현장 주임 등 모든 종류의 사원을 이 배열에 저장할 수 있으면 좋을 것이다. employee emparray[SIZE]; employee[0] = laborer1; employee[1] = scientist1; employee[2] = laborer2; ... 이 일을 할 수 있는 유일한 방법은 employee를 여러 종류의 사원에 상속하는 것이다. (구성은 Class들 사이에 필요한 관계를 만들지 않는다.) 이미 이전에 서로다른 유도 Class의 객체를 기초 Class의 객체에 대입할 수 있음을 배웠다.
//---------------------------------------------------------------------------]
// 작성일 : 2001.09.02
// 제  목 : 구성을 사용한 예
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
#include 
#pragma hdrstop

//---------------------------------------------------------------------------

class safearray
{
private:
    enum {SIZE = 100};
    int arr[SIZE];

public:
    int& operator [] ( int n );
};

int& safearray::operator [] ( int n )
{
    if( n < 0 || n >= SIZE ) {
        cout << "\nIndex out of bounds";
//        exit(1);
    }
    return arr[n];
}

//---------------------------------------------------------------------------

class Stack
{
private:
    safearray st;
    int top;

public:
    Stack() {
        top = -1;
    }

    void push( int var ) {
        st[ ++top ] = var;
    }

    int pop() {
        return st[ top-- ];
    }
};

//---------------------------------------------------------------------------

#pragma argsused
void main()
{

    Stack s;

    s.push( 11 );
    s.push( 12 );
    s.push( 13 );

    cout << s.pop() << endl;
    cout << s.pop() << endl;
    cout << s.pop() << endl;
    cout << s.pop() << endl;

    getch();

}

//---------------------------------------------------------------------------

(결과)
13
12
11

Index out of bounds4199160

//---------------------------------------------------------------------------
- 구성과 상속성의 특성을 정리해보자. ======================================================================================== 구성 개별 상속성 보호 상속성 공용상속성 ======================================================================================== 유도 클래스에 '소유' '소유' 유도 클래스의 동족 대한 기초 '동족' 클래스 클래스의 관계 사용자에 '소유' 유도 클래스 공용 공용 공용 공용 구성원이 접근할 보호 보호 보호 수 있는 기초 클래스 구성원 유도 클래스가 Base b; Base::func(); Base::func(); Base::func(); 기초 클래스 b.func(); 함수에 접근하기 위한 문법 유도 클래스 아니오 아니오 아니오 예 개체가 공용 기초 클래스 인터페이스에 접근할 수 있는가? 유도 클래스 아니오 아니오 아니오 예 개체를 기초 클래스 개체로 취급할 수 있는가? 권장 사항 유도 클래스 권장하지 않음. 권장하지 않음. 유도 클래스 개체가 기초 기초 클래스 기초 클래스 보호 개체가 기초 클래스 보호 데이터가 데이터가 노출됨. 클래스의 인터페이스를 노출됨. 유도 클래스 동족으로 볼 필요가 구성원이 기초 취급되고 기초 없고 기초 클래스 구성원에 클래스 클래스 접근해야 할 때 인터페이스를 개체의 사용. 보아야 할 때 동족으로 사용. 취급될 필요가 없을 때 사용. ======================================================================================== (6) 다중상속 클래스가 둘 이상의 기초 클래스에서 상속할 때 이루어진다. Ex) class Base1 {}; class Base2 {}; class Derv : public Base1, public Base2 {}; - 논쟁의 여지 다중 상속이 다이아몬드 형으로 되었을 경우. Ex) class Gparent {}; class Mother : public Gparent {}; class Father : public Gparent {}; class Child : public Mother, public Father {}; 이경우 Chile 객체에서 Gparent의 객체는 모호하게 된다. 만약 Gparent의 구성원 함수를 호출할 경우는 컴파일러는 데이타 참조가 모호하다는 경고 메시지를 발생시킨다. - 해결책(?) Gparent를 상속할때 virtual 키워드를 사용한다. virtual 키워드는 컴파일러가 Class에서 차후의 유도 Class로 한 개의 하위 객체만 상속하게 된다. class Gparent {}; class Mother : virtual public Gparent {}; class Father : virtual public Gparent {}; class Child : public Mother, public Father {}; 또다른 해결책으로 구성을 사용하기도 한다.

+ -

관련 글 리스트
33 (04) Inheritance in C++ 남병철.레조 8426 2009/06/21
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.