본문 바로가기

프로그램언어/C++

클래스5

상속

상속(Inheritance)의 사전적 의미는 자식이 부모가 가진 모든 것을 물려 받는 것을 의미하는데 OOP의 상속도 기본적인 의미는 동일하다. 이미 정의되어 있는 클래스의 모든 특성을 물려 받아 새로운 클래스를 작성하는 기법을 상속이라고 한다.

상속에 의해서 새로운 클래스를 만들 때 원래 있던 클래스를 기반클래스라고 하고, 새로 만들어지는 클래스를 파생 클래스라고 한다.

  • 상위 클래스, 기초(base) 클래스, 슈퍼(super) 클래스, 부모(parents) 클래스
  • 하위 클래스, 유도(derived) 클래스, 서브(sub) 클래스, 자식(child) 클래스

- 기존의 클래스를 재활용한다.

- 공통되는 부분을 상위 클래스에 통합하여 반복을 제거하고 유지, 보수를 편리하게 한다.

- 공동의 조상을 가지는 계층을 만듬으로써 객체의 집합에 다형성을 부여한다.

 

선언방법

class <기반 클래스> {

  // 클래스 내용

};

class <파생 클래스> : access <기반 클래스> {

  // 클래스 내용

};

 

접근지정자

class <파생 클래스> : [접근 제어자] <기반 클래스> {

  // 클래스 내용

};

※ []는 생략가능 기본값 : 클래스에서는 private

 

public 상속

#include <iostream>

using namespace std;

class AA {

private:

    int a1;

protected:

    int a2;

public:

    int a3;

    void AA1() {

        a1 = a2 = a3 = 1;

    }

};

class BB: public AA {

private:

    int b1;

protected:

    int b2;

public:

    int b3;

    void BB1() {

        b1 = b2 = b3 = 2;

        a2 = a3 = 22;

        //a1 = 11;  //접근불가

    }

};

class CC: public BB {

private:

    int c1;

protected:

    int c2;

public:

    int c3;

    void CC1() {

        c1 = c2 = c3 = 3;

        a2 = a3 = b2 = b3 = 33;

        //a1= 11;     //접근불가

        //b1 = 22;    //접근불가

    }

};

int main() {

    CC C;

    C.a3 = 100;

    C.b3 = 200;

    C.c3 = 300;

    //C.a1 = 110;    //접근불가

    //C.a2 = 120;    //접근불가

    //C.b1 = 210;    //접근불가

    //C.b2 = 220;    //접근불가

    //C.c1 = 310;    //접근불가

    //C.c2 = 320;    //접근불가

  return 0;

}

Public의 상속방식으로 상속을 받으면

부모클래스의 public은 파생클래스에서도 public으로.

부모클래스의 private은 파생클래스에서도 private,

부모 클래스의 protected는 파생 클래스에서도 protected로 상속된다.

 

#include <iostream>

using namespace std;

class Base

{

private:

    int num1;

protected:

    int num2;

public:

    int num3;

    Base() {

  num1 = 1;

  num2 = 2;

  num3 = 3;

    }

    void ShowData() {

  cout << num1 << ", " <<num2 << ", " << num3;

    }

};

class Derived: public Base

{

public:

    void ShowBaseMember() {

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

  cout << num2 << endl;

  cout << num3 << endl;

  }

};

int main() {

    Derived test;

    cout<< "main : " << test.num3 << endl;

    test.ShowBaseMember();

    return 0;

}

 

 

protected 상속

#include <iostream>

using namespace std;

class AA {

private:

    int a1;

protected:

    int a2;

public:

    int a3;

    void AA1() {

        a1 = a2 = a3 = 1;

    }

};

class BB: protected AA {

private:

    int b1;

protected:

    int b2;

public:

    int b3;

    void BB1() {

        b1 = b2 = b3 = 2;

        a2 = a3 = 22;

        //a1 = 11;  //접근불가

    }

};

class CC: public BB {

private:

    int c1;

protected:

    int c2;

public:

    int c3;

    void CC1() {

        c1 = c2 = c3 = 3;

        b2 = b3 = 33;

        //a1= 11;     //접근불가

        //b1 = 22;    //접근불가

    }

};

int main() {

    CC C;

    //C.a3 = 100; //접근불가

    C.b3 = 200;

    C.c3 = 300;

    //C.a1 = 110;    //접근불가

    //C.a2 = 120;    //접근불가

    //C.b1 = 210;    //접근불가

    //C.b2 = 220;    //접근불가

    //C.c1 = 310;    //접근불가

    //C.c2 = 320;    //접근불가

  return 0;

}

protected의 상속방식으로 상속을 받으면

부모클래스의 private은 파생클래스에서도 private로 상속되지만

부모클래스의 protectedpublic은 모두 파생클래스에서도 protected 상속된다.

(publicprotected로 상속되고 나머지는 있는 그대로 상속)

 

#include <iostream>

using namespace std;

class Base

{

private:

    int num1;

protected:

    int num2;

public:

    int num3;

    Base() {

        num1 = 1;

        num2 = 2;

        num3 = 3;

    }

    void ShowData() {

        cout << num1 << ", " <<num2 << ", " << num3;

    }

};

class Derived: protected Base

{

public:

    void ShowBaseMember() {

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

        cout << num2 << endl;

        cout << num3 << endl;

    }

};

int main() {

    Derived test;

    //cout << test.num3 << endl;  //컴파일에러

    test.ShowBaseMember();

}

 

 

private 상속

 

#include <iostream>

using namespace std;

class AA {

private:

    int a1;

protected:

    int a2;

public:

    int a3;

    void AA1() {

        a1 = a2 = a3 = 1;

    }

};

class BB: private AA {

private:

    int b1;

protected:

    int b2;

public:

    int b3;

    void BB1() {

        b1 = b2 = b3 = 2;

        a2 = a3 = 22;

        //a1 = 11;  //접근불가

    }

};

class CC: public BB {

private:

    int c1;

protected:

    int c2;

public:

    int c3;

    void CC1() {

        c1 = c2 = c3 = 3;

        b2 = b3 = 33;

        //a1= 11;     //접근불가

        //a2 = 12;    //접근불가

        //a3= 13;     //접근불가

        //b1 = 21;    //접근불가

    }

};

int main() {

    CC C;

    //C.a3 = 100; //접근불가

    C.b3 = 200;

    C.c3 = 300;

    //C.a1 = 110;    //접근불가

    //C.a2 = 120;    //접근불가

    //C.b1 = 210;    //접근불가

    //C.b2 = 220;    //접근불가

    //C.c1 = 310;    //접근불가

    //C.c2 = 320;    //접근불가

}

 

private의 상속방식으로 상속을 받으면

부모클래스의 public, private, protected는 모두 파생클래스에서도 private로 상속된다.

(BB클래스에서 private로 상속받으면 CC클래스에서는 AA클래스에 있는 멤버들을 모두 사용할 수 없게되므로 사용하려면 BB클래스를 거쳐서 사용하여야 한다. 이러한 상속방식을 파생종료이라고도 함)

(모두 private접근 지정자로 상속)

 

#include <iostream>

using namespace std;

class Base

{

private:

    int num1;

protected:

    int num2;

public:

    int num3;

    Base() {

        num1 = 1;

        num2 = 2;

        num3 = 3;

    }

    void ShowData() {

        cout << num1 << ", " <<num2 << ", " << num3;

    }

};

class Derived: private Base

{

public:

    void ShowBaseMember() {

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

        cout << num2 << endl;

        cout << num3 << endl;

    }

};

int main() {

    Derived test;

    //cout<< "main : " << test.num3 << endl;  //컴파일에러

    test.ShowBaseMember();

}

 


#include <iostream> 

#include <cstring> 
using namespace std; 

class Human 
{ 
protected: 
    char Name[16]; 
public: 
    Human(const char* aName)  
    { 
        strcpy(Name, aName);  
    } 
    void Intro()  
    {  
        cout << "이름 : " << Name;  
    } 
    void Think()  
    { 
        cout << "오늘은 어디갈까?";  
    } 
}; 


class Student : public Human 
{ 
private: 
    int StNum; 
public: 
    Student(const char* aName, int aStNum) : Human(aName)  
    { 
        StNum = aStNum;  
    } 
    void Intro()  
    {  
        Human::Intro();  
        cout << ", 학번 : " << StNum << endl;  
    } 
    void Think()  
    {  
        cout << "어떤 책을 볼까?" << endl;  
    } 
    void Study()  
    { 
        cout << "열공 열공!" << endl; 
    } 
}; 

void main() 
{ 
    Student K("학생11", 20201001); 
    K.Intro(); 
    K.Think(); 
    K.Study(); 
}

 

가상함수

포인터를 이용하여 함수를 호출하면 클래스에 있는 함수가 호출되는데, 이때 기반 클래스의 함수 대신 파생 클래스의 멤버 함수를 호출하고자 할 때 가상 함수를 쓴다.

가상 함수는 기반 클래스의 멤버 함수에 virtual이라는 예약어를사용하여 선언한다.

기반 클래스에서 가상 함수로 정의된 함수가 파생 클래스에 동일 이름, 동일 인수, 동일 반환값으로 재정의되면, 파생 클래스에 있는 함수도 자동적으로 virtual이라는 예약어가 있건 없건 가상 함수가 된다.

 

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

class A
{
public:
    virtual void Print() 
    { 
        cout << "A 클래스의 Print() 함수" << endl; 
    }
};

class B : public A
{
    virtual void Print()
    { 
        cout << "B 클래스의 Print() 함수" << endl;
    }
};

int main(void)
{
    A* ptr;
    A obj_a;
    B obj_b;

    ptr = &obj_a;
    ptr->Print();
    ptr = &obj_b;
    ptr->Print();

    return 0;
}
실행 결과
A 클래스의 Print() 함수
B 클래스의 Print() 함수

가상 함수 호출 유형(복사 전달에 의한 가상함수)

#include <iostream>

using namespace std;

class Base_Class {

public:

    virtual void f() {

        cout << "기반클래스입니다." << endl;

    }

};

class Derived_Class : public Base_Class {

public:

    void f() {

        cout << "파생된클래스입니다." << endl;

    }

};

void f(Base_Class b) {

    b.f();

}

int main() {

    Base_Class b;

    f(b);

    Derived_Class d;

    f(d);

}

실행 결과
기반클래스입니다.
기반클래스입니다.

 

참조 전달에 의한 가상함수

#include <iostream>

using namespace std;

class Base_Class {

public:

    virtual void f() {

        cout << "기반클래스입니다." << endl;

    }

};

class Derived_Class : public Base_Class {

public:

    void f() {

        cout << "파생된클래스입니다." << endl;

    }

};

void f(Base_Class &b) {

    b.f();

}

int main() {

    Base_Class b;

    f(b);

    Derived_Class d;

    f(d);

}

실행 결과
기반클래스입니다.
파생된클래스입니다.

 

포인터 전달에 의한 가상 함수

#include <iostream>

using namespace std;

class Base_Class {

public:

    virtual void f() {

        cout << "기반클래스입니다." << endl;

    }

};

class Derived_Class : public Base_Class {

public:

    void f() {

        cout << "파생된클래스입니다." << endl;

    }

};

void f(Base_Class *pb) {

    pb->f();

}

int main() {

    Base_Class b;

    f(&b);

    Derived_Class d;

    f(&d);

}

실행 결과
기반클래스입니다.
파생된클래스입니다.

 

 

순수 가상 함수

기반 클래스 쪽에서 가상 함수로 사용될 멤버 함수를 정의 부분 없이 선언만 해놓고 파생 클래스 쪽에서 재정의 하는 멤버 함수를 순수 가상 함수라고 한다. 또 순수 가상 함수와 같이 정의되지 않은 함수를 1개 이상 가지는 클래스는 자신의 객체를 생성할 수 없고 오직 다른 파생클래스의 기반 클래스로만 사용되며 이런 목적으로 사용되는 클래스를 추상 클래스라고 한다.

형식 : virtual 결과형 func()=0

 

#include <iostream>
using namespace std;

class MEMBER { //순수 가상함수로 정의하여 추상 클래스로 만듬
public:
    virtual void Order() = 0; //순수 가상 함수 선언
};

class mem :public MEMBER {
public:
    void Order() { //파생 클래스는 순수 가상 함수로 선어된 함수를  Overriding해야 한다.
        cout << "주문하다." << endl;
    }
};

int main()
{
    //MEMBER *p = new MEMBER; 추상클래스는객체화될수없다.
    MEMBER* pc = new mem;
    pc->Order();

    return 0;
}

실행 결과

주문하다.

 

#include <cstring>

using namespace std;

 

class Animal

{

public:

    virtual ~Animal() {} // 가상소멸자의선언

    virtual void Cry() = 0; // 순수가상함수의선언

}; 

class Dog : public Animal

{

public:

    virtual void Cry()

    {

        cout << "멍멍!" << endl;

    }

};

class Cat : public Animal

{

public:

    virtual void Cry()

    {

        cout << "야옹야옹!" << endl;

    }

};

int main(void)

{

    Dog my_dog;

    my_dog.Cry();

    Cat my_cat;

    my_cat.Cry();

}

실행 결과
멍멍!
야옹야옹!

 

소멸자와 가상함수

클래스 상속 관계에서 부모 클래스의 포인터 변수로 파생 클래스형의 객체에 접근하는 경우 부모 클래스형의 객체가 소멸될 때 부모 클래스의 소멸자가 호출되어 부모 클래스형의 객체로 할당된 메로리의 제거는 정상적으로 이루어지지만 파생 클래스로 할당된 메모리는 제거는 정상적으로 이루어지지 않는 경우가 있다.

이런 문제를 해결하기 위해서는 파생 클래스의 소멸자가 정확히 호출되도록 부모 클래스의 소멸자를 가상 함수로 정의해야 한다. 그러면 파생 클래스의 소멸자가 먼저 호출되고 다음에 부모 클래스의 소멸자가 호출된다.

클래스 안의 생성자가 가상 함수이면 소멸자도 가상 함수이어야 한다.

 

소멸자 필요성의 예제1

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

class First
{
private:
    char* strOne;
public:
    First(const char* str) {
        strOne = new char[strlen(str) + 1];
    }
    ~First() {
        cout << "~First()" << endl;
        delete[]strOne;
    }
};
class Second :public First
{
private:
    char* strTwo;
public:
    Second(const char* str1, const char* str2) :First(str1)
    {
        strTwo = new char[strlen(str2) + 1];
    }
    ~Second()
    {
        cout << "~Second()" << endl;
        delete[]strTwo;
    }
};

int main()
{
    First* ptr = new Second("simple", "complex");
    delete ptr;
}
실행 결과
~First()

 

소멸자 필요성의 예제2

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

class First 
{ 
private: 
    char* strOne; 
public: 
    First(const char* str) { 
        strOne = new char[strlen(str) + 1]; 
    } 
    virtual ~First() { 
        cout << "~First()" << endl; 
        delete[]strOne; 
    } 
}; 
class Second :public First 
{ 
private: 
    char* strTwo; 
public: 
    Second(const char* str1, const char* str2) :First(str1) 
    { 
        strTwo = new char[strlen(str2) + 1]; 
    } 
    virtual ~Second() 
    { 
        cout << "~Second()" << endl; 
        delete[]strTwo; 
    } 
}; 

int main() 
{ 
    First* ptr = new Second("simple", "complex"); 
    delete ptr; 
}
실행 결과
~Second()

~First()

 

'프로그램언어 > C++' 카테고리의 다른 글

연산자1  (0) 2020.08.09
클래스5 문제  (0) 2020.08.08
클래스4 문제  (0) 2020.08.08
클래스4  (0) 2020.08.08
클래스3 문제  (0) 2020.08.08