[c++]클래스 상속

이승희·2023년 11월 16일

c++

목록 보기
6/6
post-thumbnail

클래스 상속

  • C++ 언어에서의 상속이란 기본 클래스의 속성과 기능을 파생 클래스에게 물려 주는 것이다.
    • 기본 클래스(base class): 상속해주는 부모 클래스
    • 파생 클래스(derived class): 상속을 받는 자식 클래스
  • 파생 클래스는 기본 클래스의 속성과 기능을 물려받고 자신만의 속성과 기능을 추가하여 작성할 수 있다.🤓
  • 클래스 사이에서만 상속 개념이 있으며 객체 사이에는 상속 개념이 없다.❗️❗️❗️
  • 상속을 사용함으로써 기본클래스에서 파생클래스로 갈수록 클래스의 개념이 구체화가 된다. 계속 속성과 기능들을 추가하다보니 아무래도 원래의 기본 클래스보다 더 구체적임😯
  • 다중 상속을 통한 클래스의 재활용성을 높일 수 있다.

상속의 목적과 장점 3가지❗️

  1. 간결한 클래스 작성
  • 기본 클래스의 기능을 물려받으니 중복된 기능의 재작성 필요성이 없으니 파생 클래스를 간결하게 작성할 수 있다.❗️
  1. 클래스 간의 계층적 분류 및 관리의 용이함
  • 상속으로 통해 클래스들의 구조적 관계를 파악하기 쉬워졌다.🤓
  1. 클래스 재사용과 확장을 통한 소프트웨어 생산성 향상
  • 빠른 하드웨어의 발전으로 소프트웨어도 생산성이 향상 되었어야 하는데 객체지향의 개념인 상속을 통해 빠른 소프트웨어 생산이 향상되었다.
  • 기존에 작성되었던 클래스를 재사용 할 수있다. 이것이 가장큰 장점 같다.❗️
  • 하지만, 앞으로 있을 상속에 대비한 클래스의 객체 지향성 설계가 필요하니 이러한 부분은 약간 까다로운것 같다.😓

상속 선언

class 파생클래스명 : 상속접근지정 기본클래스명{
 ...
};
class Student : public Person {
// Person을 상속받는 Student 선언
..... };
class StudentWorker : public Student {
// Student를 상속받는 StudentWorker 선언
..... };
  • Student 클래스는 Person 클래스의 멤버를 물려받는다.
  • StudentWorker 클래스는 Student의 멤버를 물려받는다.
    • Student가 물려받은 Person의 멤버도 함께 물려받는다.❗️

Point 클래스를 상속받는 ColorPoint 클래스 만들기

#include <iostream> 
#include <string> 
using namespace std;
// 2차원 평면에서 한 점을 표현하는 클래스 Point 선언 
class Point {
   int x, y; //한 점 (x,y) 좌표값 
public:
   void set(int x, int y) { 
      this->x = x;
      this->y = y; 
   } 
   void showPoint() {
      cout << "(" << x << "," << y << ")" << endl; 
   }
};

class ColorPoint : public Point { // 2차원 평면에서 컬러 점을 표현하는 클래스 ColorPoint. Point를 상속받음
   string color;// 점의 색 표현 public:
   void setColor(string color) {
      this->color = color; 
   }
   void showColorPoint(); 
};

void ColorPoint::showColorPoint() { 
   cout << color << ":";
   showPoint(); // Point의 showPoint() 호출 
}

int main() {
   Point p; // 기본 클래스의 객체 생성
   ColorPoint cp; // 파생 클래스의 객체 생성 
   cp.set(3,4); // 기본 클래스의 멤버 호출 
   cp.setColor("Red"); // 파생 클래스의 멤버 호출 
   cp.showColorPoint(); // 파생 클래스의 멤버 호출
}
✅ **출력결과 —————————————————————————————————————————————**

Red:(3,4)

—————————————————————————————————————————————

  • main()에서 살펴보면 파생 클래스 객체인 ColorPoint클래스로 생성한 cp객체에서도 기본 클래스 즉 부모 클래스의 멤버에 접근할 수 있는 갓을 볼 수있다.❗️
  • 이것은 파생 클래스의 객체는 기본 클래스의 멤버를 포함하기 때문이다.😮
  • ColorPoint 클래스 내부에서 기본 클래스인 Point 클래스의 showPoint() 멤버 함수를 호출할 수 있다.

ex2)

#include <iostream>
using namespace std;

class Parent {
public:
    int mValue;
    void func1() {
        mValue = 1;
    }
};

class Child : public Parent {
public:
    int mValue2;
    void func2() {
        mValue2 = 2;
    }

};

int main()
{
    Child c;
    c.func1(); //부모 클래스의 멤버 함수도 접근 가능
    c.func2();
    c.Parent::func1(); //부모 클래스의 멤버 함수도 접근 가능

    cout << c.Parent::mValue << endl;
    cout << c.mValue << endl;
    cout << c.mValue2 << endl;

    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

1

1

2

—————————————————————————————————————————————

ex3)

#include <iostream>
using namespace std;

class Car {
    string model;
    int speed;
public:
    Car() {
        speed = 0;
    }

    void setModel(string model) {
        this->model = model;
    }

    void setSpeed(int speed) {
        this->speed = speed;
    }

    string getModel() {
        return model;
    }

    int getSpeed() {
        return speed;
    }

};

class SportsCar : public Car {
    bool turbo;
public:
    SportsCar() {
        turbo = true;
    }

    bool isTurbo() {
        return turbo;
    }

};

int main()
{
    Car myCar;
    myCar.setModel("audi");
    myCar.setSpeed(50);

    cout << "myCar : " << myCar.getModel() << ", speed = " << myCar.getSpeed() << endl;

    SportsCar yourCar;
    yourCar.setModel("bmw");
    yourCar.setSpeed(100);
    cout << "yourCar : " << yourCar.getModel() << ", speed = " << yourCar.getSpeed() << endl;
    if (yourCar.isTurbo()) {
        cout << "yourCar = turbo engine" << endl;
    }
    else
    {
        cout << "youtCar = not turbo engine" << endl;
    }

    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

myCar : audi, speed = 50

yourCar : bmw, speed = 100

yourCar = turbo engine

—————————————————————————————————————————————

상속과 객체 포인터

int main() {
   ColorPoint cp; 
   ColorPoint *pDer = &cp;

   pDer->set(3,4); 
   pDer->setColor("Red"); 
   pDer->showColorPoint();
}
  • pDer 포인터로 객체 cp의 모든 public 멤버를 접근할 수 있다.😯

객체 포인터 이용

#include <iostream>
using namespace std;

//2차원 평면에서 한 점을 표현하는 클래스
class Point {
    int x, y;

public:
    void set(int x, int y) {
        this->x = x;
        this->y = y;
    }

    void showPoint() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

};

//컬러값이 추가된 파생 클래스
class ColorPoint : public Point {
    string color;
public:
    void setColor(string color) {
        
        
        this->color = color;
    }
    
    void showColorPoint() {
        cout << color << "." ;
        showPoint();
    }
};

int main()
{
    Point p;
    p.set(1, 2);
    p.showPoint();

    //Point 객체의 주소를 저장하는 포인터변수
    //부모 클래스 객체
    Point* pBase = &p;
    p.set(100, 200);
    p.showPoint();

    
    ColorPoint cp;
    cp.set(10, 20);
    cp.setColor("Blue");
    cp.showColorPoint();

    //ColorPoint 객체의 주소를 저장하는 포인터 변수
    //자식 클래스 객체
    ColorPoint* pDer;
    pDer = &cp;
    pDer->set(3, 4);
    pDer->setColor("Yellow");
    pDer->showColorPoint();
		
	
		reteurn 0;
}
✅ **출력결과 —————————————————————————————————————————————**

(1, 2)

(100, 200)

Blue.(10, 20)

Yellow.(3, 4)

—————————————————————————————————————————————

업 캐스팅과 다운 캐스팅

업 캐스팅

  • 파생 클래스 포인터가 기본 클래스 포인터에 치환되는 것이다.
  • 기본 클래스의 포인터(부모 클래스 포인터)에 파생 클래스이 포인터(자식 클래스 포인터)를 대입하는 것이다.
  • 업 캐스팅을 하면 기본 클래스에 포함된 멤버만 접근 가능하다.❗️

다운 캐스팅

  • 기본 클래스의 포인터가 파생 클래스의 포인터에 치환되는 것이다.
  • 강제 타입 변환이 반드시 필요하다.
#include <iostream>
using namespace std;

//2차원 평면에서 한 점을 표현하는 클래스
class Point {
    int x, y;

public:
    void set(int x, int y) {
        this->x = x;
        this->y = y;
    }

    void showPoint() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

};

//컬러값이 추가된 파생 클래스
class ColorPoint : public Point {
    string color;
public:
    void setColor(string color) {
        
        
        this->color = color;
    }
    
    void showColorPoint() {
        cout << color << "." ;
        showPoint();
    }
};

int main()
{
    Point p;
    p.set(1, 2);
    p.showPoint();

    //Point 객체의 주소를 저장하는 포인터변수
    //부모 클래스 객체
    Point* pBase = &p;
    p.set(100, 200);
    p.showPoint();

    
    ColorPoint cp;
    cp.set(10, 20);
    cp.setColor("Blue");
    cp.showColorPoint();

    //ColorPoint 객체의 주소를 저장하는 포인터 변수
    //자식 클래스 객체
    ColorPoint* pDer;
    pDer = &cp;
    pDer->set(3, 4);
    pDer->setColor("Yellow");
    pDer->showColorPoint();
    
    //부모 객체 포인터 <= 자식객체 포인터를 대입
    //업캐스팅
    pBase = pDer;
    pBase->set(1, 2);
    pBase->showPoint();
    //pBase->setColor("Red"); // 안됨
    //pBase->showColorPoint(); // 안됨

    //자식 개체 포인터 <= 부모 객체 포인터 대입
    //다운 캐스팅
    //업캐스팅이 되어야 다운캐스팅이 됨
    pDer = (ColorPoint*)pBase;
    pDer->setColor("green");
    pDer->showColorPoint();
    pDer->set(8, 8);
    pDer->showPoint();

    cout << endl;

    Point g;
    Point* pp = &g;

    //업캐스팅
    pp = pDer;

    //다운 캐스팅
    pDer = (ColorPoint*)pp;
    pDer->set(7, 8);
    pDer->setColor("Black");
    pDer->showPoint();
    pDer->showColorPoint();

    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

(1, 2)

(100, 200)

Blue.(10, 20)

Yellow.(3, 4)

(1, 2)

green.(1, 2)

(8, 8)

(7, 8)

Black.(7, 8)

—————————————————————————————————————————————

  • 부모 객체 포인터에 자식 객체 포인터를 대입하여 업 캐스팅을 하였지만 실질적으로 사용 가능한 속성은 부모 클래스의 속성만 접근 가능하다.
  • 자식 객체 포인터에 부모 객체 포인터를 대입하여 다운 캐스팅을 할때에는 강제 타입 변환이 반드시 필요하다❗️❗️❗️
  • 다운 캐스팅을 할 때에는 이미 전에 업 캐스팅이 진행 된 객체를 통해 사용할 수 있다.

업캐스팅 상세히 살펴보기

#include <iostream>
using namespace std;

//2차원 평면에서 한 점을 표현하는 클래스
class Point {
    int x, y;

public:
    void set(int x, int y) {
        this->x = x;
        this->y = y;
    }

    void showPoint() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

};

//컬러값이 추가된 파생 클래스
class ColorPoint : public Point {
    string color;
public:
    void setColor(string color) {
        
        
        this->color = color;
    }
    
    void showColorPoint() {
        cout << color << "." ;
        showPoint();
    }
};

int main()
{
    ColorPoint cp;
    ColorPoint* pDer = &cp;
    
    Point *pBase = pDer;//업캐스팅
    
    pDer->set(3,4);
    
    pBase->showPoint();
    
    pDer->setColor("Red");
    
    pDer->showColorPoint();
    //pBase->showColorPoint();//컴파일오류

    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

(3, 4)

Red.(3, 4)

—————————————————————————————————————————————

  • 업캐스팅을 하여 자식 클래스 객체를 대입 하였어도 부모 클래스 객체의 속성만 접근 가능한 것을 살펴 볼 수 있다.
  • 하지만 pDer(자식 클래스)를 통하여 x, y 값을 변경하면 pBase의 x, y 값도 변경되는 것을 확인할 수 있었다.
  • pDer 포인터로 객체 cp의 모든 public 멤버 접근 가능하다.
  • pBase 포인터로 기본 클래스의 public 멤버만 접근 가능하다.
  • 정리, 자식 클래스의 객체를 부모 클래스의 포인터로 참조하게 하는 것이다. 이 경우, 업캐스팅된 부모 클래스 포인터를 통해 접근할 수 있는 것은 부모 클래스가 가진 속성과 메소드만 가능하다.

다운캐스팅 상세히 살펴보기

#include <iostream>
using namespace std;

//2차원 평면에서 한 점을 표현하는 클래스
class Point {
    int x, y;

public:
    void set(int x, int y) {
        this->x = x;
        this->y = y;
    }

    void showPoint() {
        cout << "(" << x << ", " << y << ")" << endl;
    }

};

//컬러값이 추가된 파생 클래스
class ColorPoint : public Point {
    string color;
public:
    void setColor(string color) {
        
        
        this->color = color;
    }
    
    void showColorPoint() {
        cout << color << "." ;
        showPoint();
    }
};

int main()
{
    ColorPoint cp;
    ColorPoint *pDer;
    
    Point *pBase = &cp;//업캐스팅
    
    pBase->set(3,4);
    pBase->showPoint();
    
    pDer = (ColorPoint*)pBase;//다운캐스팅
    pDer->setColor("Red");//정상컴파일
    pDer->showColorPoint();//정상컴파일

    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

(3, 4)

Red.(3, 4)

—————————————————————————————————————————————

  • 업캐스팅을 진행한 클래스 객체를 강제 타입 변환으로 인하여 다운캐스팅을 진행할 수 있다.
  • 반드시 강제 타입 벼환을 작성해야 하며 또한 이미 업캐스팅이 진행되어 있어야 한다.
  • 부모 클래스의 포인터가 파생 클래스의 포인터에 치환되는 것이므로 부모 클래스의 값이 변경되면 자식 클래스의 값도 같이 변경되는 것을 알 수 있다.
  • pDer 포인터로 객체 cp의 모든 public 멤버 접근 가능
  • pBase 포인터로 기본 클래스의 public 멤버만 접근 가능하다.

접근 지정자

  • 클래스는 외부 함수에 자신의 멤버에 대한 공개 여부를 결정할 수 있어야 하는데 이러한 역할을 하는 것이 접근 지정자이다.
  • 자기 자신 클래스에서 정의된 멤버에 사용되는 것을 ‘멤버 접근 지정자’ 라고 하며, 부모 클래스로부터 상속받은 멤버에 사용되는 것을 ‘상속 접근 지정자’라고 한다.

private 멤버

  • 선언된 클래스 내에서만 접근 가능👌
  • 파생 클래스에서도 기본 클래스의 private 멤버 직접 접근 불가❌

public 멤버

  • 선언된 클래스나 외부 어떤 클래스, 모든 외부 함수에 접근 허용👌
  • 파생 클래스에서도 기본 클래스이 public 멤버 접근 가능👌

protected 멤버

  • 선언된 클래스에서 접근 가능👌
  • 파생 클래스에서만 접근 허용❗️
    • 파생 클래스가 아닌 다른 클래스나 외부 함수(ex) main() 함수)에서는 protected 멤버를 접근할 수 없다.❌❗️❗️❗️❗️❗️

상속 관계의 생성자와 소멸자 실행

Q. 파생 클래스의 객체가 생성될 때 파생 클래스의 생성자와 기본 클래스의 생성자가 모두 실행되는가? 아니면 파생 클래스의 생성자만 실행되는가?

A. 둘 다 실행됨

Q. 파생 클래스의 생성자와 기본 클래스의 생성자 중 어떤 생성자가 먼저 실행되는가?

A. 기본 클래스의 생성자가 먼저 실행된 후 파생 클래스 생성자가 실행된다.

#include <iostream>
using namespace std;

class A{
public:
    A()
    {
        cout << "생성자 A" << endl;
    }
    ~A()
    {
        cout << "소멸자 A" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "생성자 B" << endl;
    }
    ~B()
    {
        cout << "소멸자 B" << endl;
    }
};

class C : public B
{
public:
    C()
    {
        cout << "생성자 C" << endl;
    }
    ~C()
    {
        cout << "소멸자 C" << endl;
    }
};

int main(){
    C c;
    
    
    
    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

생성자 A

생성자 B

생성자 C

소멸자 C

소멸자 B

소멸자 A

—————————————————————————————————————————————

  • 호출 : C → B → A
  • 생성 : A → B → C
  • 소멸 : C → B → A
    • 파생 클래스의 소멸자가 먼저 실행되고 기본 클래스의 소멸자가 나중에 실행

컴팡일러에 의해 묵시적으로 기본 클래스이 생성자를 선택하는 경우

#include <iostream>
using namespace std;

class A{
public:
    A()
    {
        cout << "생성자 A" << endl;
    }
    
    A(int x)
    {
        cout << "매개변수 생성자 A" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "생성자 B" << endl;
    }
    
};

int main(){
    B b;
    
    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

생성자 A

생성자 B

—————————————————————————————————————————————

  • 파생 클래스이 생성자에서 기본 클래스이 기본 생성자 호출❗️
  • 컴파일러는 묵시적으로 기본 클래스의 기본 생성자를 호출 하도록 컴파일한다❗️

기본 클래스에 기본 생성자가 없는 경우

#include <iostream>
using namespace std;

class A{
public:
  
    A(int x)
    {
        cout << "매개변수 생성자 A" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "생성자 B" << endl;
    }
    
};

int main(){
    B b;
    
    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

Constructor for 'B' must explicitly initialize the base class 'A' which does not have a default constructor

—————————————————————————————————————————————

  • 컴파일러가 B()에 대한 짝으로 A()를 찾을 수 없음❌
  • 에러::사용할 수 있는 적절한 기본 생성자가 없다!!!

매개 변수를 가진 파생 클래스의 생성자는 묵시적으로 기본 클래스의 기본 생성자 선택

#include <iostream>
using namespace std;

class A{
public:
    A()
    {
        cout << "생성자 A" << endl;
    }
    
    A(int x)
    {
        cout << "매개변수 생성자 A" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "생성자 B" << endl;
    }
    B(int x)
    {
        cout << "매개변수 생성자 B(" << x << ")" << endl;
    }
    
};

int main(){
    B b(7);
    
    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

생성자 A

매개변수 생성자 B(7)

—————————————————————————————————————————————

  • 파생 클래스의 매개 변수를 가진 생성자가 기본 클래스의 기본 생성자 호출❗️
  • 컴파일러는 묵시적으로 기본 클래스의 기본 생성자를 호출하도록 컴파일 한다.

파생 클래스의 생성자에서 명시적으로 기본 클래스의 생성자 선택

#include <iostream>
using namespace std;

class A{
public:
    A()
    {
        cout << "생성자 A" << endl;
    }
    
    A(int x)
    {
        cout << "매개변수 생성자 A(" << x << ")" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "생성자 B" << endl;
    }
    B(int x) : A(x+6)
    {
        cout << "매개변수 생성자 B(" << x << ")" << endl;
    }
    
};

int main(){
    B b(7);
    
    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

매개변수 생성자 A(13)

매개변수 생성자 B(7)

—————————————————————————————————————————————

  • 파생 클래스의 생성자가 명시적으로 기본 클래스의 생성자를 선택 호출한다.

명시적 기본 클래스의 생성자 선택 활용 예시

#include <iostream>
using namespace std;

class TV {
    int size;
public:
    TV()
    {
        size = 20;
    }

    TV(int size)
    {
        this->size = size;
    }

    int getSize()
    {
        return size;
    }
};

class WideTV : public TV {
    bool videoIn;
public:
    WideTV(int size, bool videoIn) : TV(size) {
        this->videoIn = videoIn;

        //int h = getSize(); // 가능한거 같음 확인 다시 한번
        //cout << h << endl;
    }

    bool getVideoIn() {
        return videoIn;
    }
};

class SmartTV : public WideTV {
    string ipAddr;
public:
    SmartTV(string ipAddr, int size) : WideTV(size, true) {
        this->ipAddr = ipAddr;
    }

    string getIpAddr() {
        return ipAddr;
    }
};

int main()
{
    SmartTV htv("192.0.0.1", 75);
    cout << "size = " << htv.getSize() << endl;
    cout << "VideoIn = " << htv.getVideoIn() << endl;
    cout << "IP = " << htv.getIpAddr() << endl;

    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

size = 75

VideoIn = 1

IP = 192.0.0.1

—————————————————————————————————————————————

  • TV의 size를 기본 클래스까지 전달할 수 있다.

상속 지정

  • 상속 선언 시 public, private, protected의 3가지 중 하나를 지정한다.
  • 기본 클래스의 멤버의 접근 속성을 어떻게 계승할지 결정
    • public : 기본 클래스의 protected, public 멤버 속성을 그대로 계승
    • private : 기본 클래스의 protected, public 멤버를 private로 계승
    • protected : 기본 클랫의 protected, public 멤버를 protected로 계승

private 상속 사례

#include <iostream>
using namespace std;

class Base
{
    int a;
protected:
    void setA(int a)
    {
        this->a = a;
    }
public:
    void showA()
    {
        cout << a;
    }
};

class Derived : private Base
{
    int b;
protected:
    void setB(int b)
    {
        this->b = b;
    }
public:
    void showB()
    {
        cout << b;
    }
};

int main()
{
    Derived x;
    x.a = 5; //에러
    x.setA(10); //에러
    x.showA(); //에러
    x.b = 10; //에러
    x.setB(10); //에러
    x.showB();
    
    
    return 0;
}
  • 상속 후 Derived는 Base의 멤버들이 private의 접근 지정으로 물려받았으니 setA와 showA는 Base와 Derived까지만 사용가능한 것을 확인할 수 있다. 아래의 코드로 확인을 해보았다.
#include <iostream>
using namespace std;

class Base
{
    int a;
protected:
    void setA(int a)
    {
        this->a = a;
    }
public:
    void showA()
    {
        cout << "멋진 ";
    }
};

class Derived : private Base
{
    int b;
protected:
    void setB(int b)
    {
        this->b = b;
        
    }
public:
    void showB()
    {
        showA();
        cout << "개발자되기" << endl;
    }
};

int main()
{
    Derived x;
    x.showB();
    
    
    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

멋진 개발자되기

—————————————————————————————————————————————

protected 상속 사례

#include <iostream>
using namespace std;

class Base {
    int a;
protected:
    void setA(int a)
    {
        this->a = a;
    }
public:
    void showA()
    {
        cout << a;
    }
};

class Derived : protected Base
{
    int b;
protected:
    void setB(int b)
    {
        this->b = b;
    }
public:
    void showB()
    {
        cout << b;
    }
};

int main()
{
    Derived x;
    x.a = 5; //에러
    x.setA(10); //에러
    x.showA(); //에러
    x.b = 10; //에러
    x.setB(10); //에러
    x.showB();
      
    
    return 0;
}
  • main() 함수에서 Derived의 클래스 객체를 접근 하였을 때 showB()를 제외하고는 멤버들이 private와 public로 접근 지정이 되어있기 때문에 에러가 발생한다. 하지만 Derived를 상속받는 클래스를 지정했을 때는 showA(), setA(), setB(), showB()를 사용가능하다.
#include <iostream>
using namespace std;

class Base {
    int a;
protected:
    void setA(int a)
    {
        this->a = a;
    }
public:
    void showA()
    {
        cout << a << endl;
    }
};

class Derived : protected Base
{
    int b;
protected:
    void setB(int b)
    {
        this->b = b;
    }
public:
    void showB()
    {
        cout << b << endl;
    }
};

class test : public Derived
{
    int c;
public:
    void showTest()
    {
        setA(999);
        showA();
        setB(888);
        showB();
        
    }
};

int main()
{
    test t;
    t.showB();
    t.showTest();
      
    
    return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

1

999

888

—————————————————————————————————————————————

상속되지 않는 함수

  • 생성자와 소멸자는 절대 상속되지 않는다❗️❗️❗️
  • 대입 연산자 함수도 상속되지 않는다❗️❗️❗️

다중 상속

  • 하나의 파생 클래스가 여러 클래스를 동시에 상속 받는것이다.
#include <iostream>
using namespace std;

class Adder {

protected:
	int add(int a, int b) {
		return a + b;
	}
};

class Substractor {
protected:
	int minus(int a, int b) {
		return a - b;
	}
};

class Calculator : public Adder, public Substractor {
public:
	int calc(char op, int a, int b) {
		int res;

		if (op == '+')
		{
			res = add(a, b);
		}
		else if (op == '-')
		{
			res = minus(a, b);
		}

		return res;
	}
};

int main()
{
	Calculator hCal;
	cout << "2 + 4 = " << hCal.calc('+', 2, 4) << endl;
	cout << "2 - 4 = " << hCal.calc('+', 2, 4) << endl;

	return 0;
}
✅ **출력결과 —————————————————————————————————————————————**

2 + 4 = 6

2 - 4 = 6

—————————————————————————————————————————————

다중 상속의 문제점(기본 클래스 멤버의 중복 상속)

  • 다중 상속을 사용하면 '다이아몬드 문제'가 발생할 수 있다.
  • 이 문제는 두 개 이상의 클래스가 동일한 기본 클래스를 상속 받아 여러 경로를 통해 같은 기본 클래스의 인스턴스가 생성되는 문제이다.
class Animal {
public:
    void eat() { /* ... */ }
};

class Dog : public Animal {
public:
    void bark() { /* ... */ }
};

class Cat : public Animal {
public:
    void meow() { /* ... */ }
};

class CatDog : public Dog, public Cat {
public:
    void beAwesome() { /* ... */ }
};
  • 이런 경우 CatDog 객체를 만들면 Dog와 Cat이 각각 Animal을 상속 받고 있으므로 Animal 클래스의 메서드에 접근할 때 모호함이 발생 즉, Animal의 인스턴스가 Dog와 Cat 경로를 통해 두 번 생성되어 CatDog 클래스에서 Animal 클래스의 함수를 호출하면 어느 경로를 통해 호출되는지 모호해진다.

가상 상속

  • 다중 상속으로 인한 기본 클래스 멤버의 중복 상속 해결
  • 파샐 클래스의 선언문에서 기본 클래스 앞에 virtual로 선언한다.
  • 파생 클래스의 객체가 생성될 때 기본 클랫의 멤버는 오직 한번한 생성
    • 기본 클래스의 멤버가 중복하여 생성되는것을 방지한다.
  • 가상 상속을 사용하면 다중 상속에서 기본 클래스의 인스턴스가 여러 번 생성되는 것을 방지할 수 있습니다.
class Animal {
public:
    void eat() { /* ... */ }
};

class Dog : virtual public Animal {
public:
    void bark() { /* ... */ }
};

class Cat : virtual public Animal {
public:
    void meow() { /* ... */ }
};

class CatDog : public Dog, public Cat {
public:
    void beAwesome() { /* ... */ }
};
  • Dog와 Cat이 Animal을 가상으로 상속하므로 CatDog 객체 내에는 Animal의 단일 인스턴스만 존재하게 된다.
  • 따라서 CatDog 클래스에서 Animal 클래스의 함수를 호출할 때 모호함이 발생하지 않는다.

0개의 댓글