" /> C++ 11주차 상속 | BlackWerf's Blog
포스트

C++ 11주차 상속

상속

상속한 객체 접근

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Shape {
    int x, y; //한 점 (x,y)의 좌표값
    int type; //0:미지정, 1:원 2:사각형
public:
    Shape() { type = 0; }
    void set(int x, int y) {
        this->x = x;
        this->y = y;
    };
    void showShape() {
        cout << "(" << x << "," << y << ")" << endl;
    }
};
    class Circle : public Shape {
        string color;
    public:
        void setColor(string color) { 
            this->color = color; 
        }
        void showCircle();
    };
    void Circle::showCircle() {
        cout << color << ":";
        showShape(); //Shape의 showShape() 호출
    }
    
int main()
{
    Circle a;
    a.set(1, 2); //기본 클래스 Shape의 set(int x, int y) 호출
    a.showShape(); //기본 클래스 Shape의 showShape() 호출
    a.setColor("Red"); //파생 클래스 Circle의 setColor(string color) 호출
    a.showCircle(); //파생 클래스의 showCircle();
}
    

Output

1
2
(1,2)
Red:(1,2)


  • Circle 클래스는 Shape 클래스를 상속받는다.

    • 파생클래스의 객체인 a에서 set()과 showShape()는 부모 클래스로부터 상속받은 함수를 호출하고,

    • setColor(string color)과 showCircle()은 파생 클래스에서 구현되었으므로, 파생 클래스 내의 함수를 호출한다.


상속한 객체의 포인터 접근

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class parentClass{
	...
};
class childClass : parentClass{
    ...
};


int main(){
    //파생 클래스로부터 객체 생성
	childClass child;
    //생성된 객체를 이용한 접근
    childClass *cp = &child;
}
  • 생성된 객체를 이용한 접근

    • *cp


  • 파생 클래스의 포인터로 기본/파생 클래스의 멤버를 접근 가능함


업캐스팅

  • 개념

    • 파생 클래스의 객체를 기본 클래스의 포인터로 가르키는 것


      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
      class classP{
          ...
      };
      class classC : public class P{
          ...
      };
          
          
      int main(){
      	//파생 클래스로부터 객체 생성 - 
          classC child;
          //기본 클래스 포인터를 이용한 업캐스팅
          classP *cp = &child;
      }
      


      Image Alt 텍스트


다운캐스팅

  • 개념

    • 파생 클래스의 포인터로 기본 클래스의 객체를 가르키는 것


      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      class classP{
      	...
      };
      class classC : public classP{
          ...
      };
          
      int main(){
          //파생 클래스로부터 객체 생성
          classP base;
          //기본 클래스 포인터를 이용한 업캐스팅
          classC *cp = (classC*)base;
      }
      


      Image Alt 텍스트


상속관계의 접근 지정자

  • private

    • 선언된 클래스 내부에서만 접근 가능함

    • 파생 클래스에서 직접 접근이 불가능함

  • public

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

    • 선언된 클래스에서 접근 가능
    • 파생된 클래스에서만 접근 허용

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using namespace std;
#include <string>
#include <iostream>

class Shape {
    protected:
        int x, y; //한점 (x,y) 좌표값
    public:
        void set(int x, int y);
        void showShape();
};
void Shape::set(int x, int y) { this->x = x; this->y = y; }
void Shape::showShape() { cout << "(" << x << "," << y << ")" << endl; }

class Circle : public Shape {
    string color;
public:
    void setColor(string color);
    void showCircle();
    bool equals(Circle p);
};
void Circle::setColor(string color) { this->color = color; }
void Circle::showCircle() { 
    cout << color << ':'; 
    showShape(); //Shape 클래스의 showShape() 호출
}
bool Circle::equals(Circle p) {
    if (x == p.x && p.y && color == p.color)
        return true; //정상
    else
        return false;
}

int main()
{
    Shape p;
    p.set(2, 3); //정상
    p.x - 5; //오류
    p.y = 5; //오류
    p.showShape();
    
    Circle cp;
    cp.set(3, 4);
    cp.x = 10; //오류
    cp.y - 10; //오류
    cp.setColor("Red");
    cp.showCircle();

    Circle cp2;
    cp2.set(3, 4);
    cp2.setColor("Red");
    cout << ((cp.equals(cp2)) ? "true" : "false");
}

1
2
3
4
5
Output:

(2,3)
Red:(3,4)
true


생성자와 소멸자의 호출

  • 생성자의 실행

    • 기본 클래스의 생성자 먼저 실행
  • 소멸자의 실행

    • 파생 클래스의 소멸자 먼저 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Parent_Constructor {
public:
    Parent_Constructor() {
        cout << "부모 클래스에서 생성자가 호출 되었습니다." << endl;
    }
    ~Parent_Constructor() {
        cout << "부모 클래스에서 소멸자가 호출 되었습니다." << endl;
    }
};
class Child_Constructor : public Parent_Constructor {
public:
    Child_Constructor() {
        cout << "자식 클래스에서 생성자가 호출 되었습니다." << endl;
    }
    ~Child_Constructor() {
        cout << "자식 클래스에서 소멸자가 호출 되었습니다." << endl;
    }
};


int main(){
    Child_Constructor cc;
}

Output

1
2
3
4
부모 클래스에서 생성자가 호출 되었습니다.
자식 클래스에서 생성자가 호출 되었습니다.
자식 클래스에서 소멸자가 호출 되었습니다.
부모 클래스에서 소멸자가 호출 되었습니다.
  • 위에서 설명한 것과 같이, 부모 클래스와 자식 클래스 모두 생성자를 가지고 있을때 부모 클래스 - 자식 클래스 순으로 생성자가 실행됨
  • 부모 클래스와 자식 클래스가 모두 소멸자를 가지고 있을때는, 반대로 자식 클래스 - 부모 클래스 순으로 소멸자가 실행됨

생성자 매개 변수 전달

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class TV {
    int size; //스크린 크기
public:
    TV() { size = 20; }
    TV(int size) { this->size = size; }
    int getSize() { return size; }
};
class WideTV : public TV { //TV 상속받는 WideTV
    bool videoIn;
public:
    WideTV(int size, bool videoIn) : TV(size) { this->videoIn = videoIn; }
    bool getVideoIn() { return videoIn; }
};

class SmartTV : public WideTV { //WideTV 상속받는 SmartTV
    string ipAddr; //인터넷 주소
public:
    SmartTV(string ipAddr, int size) : WideTV(size, true) { this->ipAddr = ipAddr; }
    string getIpAddr() { return ipAddr; }
};

int main()
{
    //32인치 크기에 192.0.0.1 인터넷 주소 가지는 스마트 Tv 객체
    SmartTV htv("192.0.0.1", 32);
    cout << "size = " << htv.getSize() << endl;
    cout << "videoIn = " << boolalpha << htv.getVideoIn() << endl;
    cout << "IP = " << htv.getIpAddr() << endl;
}
1
2
3
4
5
Output:

size = 32
videoIn = true
IP = 192.0.0.1


  • htv

    1
    2
    3
    4
    
    - TV 영역 : int size = 32;
      	
    - WideTV 영역: bool videoIn
    - SmartTV 영역: string ipAddr = "192.0.0.1"
    


상속 지정

  • 상속지정자

    • public
      • 기본 클래스의 protected, public 멤버 속성을 동일하게 계승
    • private
      • 기본 클래스의 protected, public 멤버를 private로 계승
    • protected
      • 기본 클래스의 protected, public 멤버를 protected로 계승

다중 상속

  • 여러 클래스를 동시에 상속 받는 것
  • 예시)

    1
    
      class multimediaPlayer : public mp3Player, public videoPlayer
    


예시 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//다중 상속
class Adder { protected: int add(int a, int b) { return a + b; } };
class Subtractor { protected: int minus(int a, int b) { return a - b; } };

class Calculator : public Adder, public Subtractor { public: int calc(char op, int a, int b); };
int Calculator::calc(char op, int a, int b) {
    int res = 0;
    switch (op){
        case '+': res = add(a,b); break;
        case '-': res = minus(a, b); break;
        }
    return res;
}


int main()
{
    //다중 상속
    Calculator handCalculator;
    cout << " 2 + 4 = " << handCalculator.calc('+', 2, 4) << endl;
    cout << "100 - 8 = " << handCalculator.calc('-', 100, 8) << endl;
}
1
2
3
4
Output:

 2 + 4 = 6
100 - 8 = 92

다중 상속의 문제

  • 예시)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    class Animal{
    public:
        	int lifetime;
    };
      
      
    class eagle : public Animal{
    public:
        int wing;
    };
      
    class lion : public Animal{
    public:
        int tooth;
    };
      
      
    class kimera : public eagle, public lion{
    public:
        int leg;
    };
      
      
    int main(){
        kimera wild;
        wild.wing = 10;
        wild.tooth = 20;
        wild.lifetime = 30;
    }
    
    1
    2
    
    심각도	코드	설명	프로젝트	파일	줄	비표시 오류(Suppression) 상태
    오류(활성)	E0266	"kimera::lifetime"이(가) 모호합니다.	11th	F:\School\23-2\Cpp\Cpp_Study\11th\11th.cpp	137	
    
    • 위와 같이 예시의 코드를 작성하였을 경우, kimera 클래스의 lifetime가 모호하다는 오류를 띄운다.

      • 이를 다이아몬드 상속 문제라고한다

      Image Alt 텍스트

      • 위와 같은 코드에서 다중 상속을 받을 경우, 위의 사진의 오른쪽 모습과 같이 작동을 한다.
        • 왼쪽의 모습처럼 다이아몬드 모습으로 생성되는것이 아닌, 부모 객체가 2개가 생기는 방식이다
        • 이는 다음에 설명할 가상상속을 이용하여 해결이 가능하다


Virtual 이용한 다중 상속의 문제 해결

  • 가상 상속을 통하여, 기본 크랠스의 멤버를 한번만 생성하도록 설정이 가능함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal {
public:
    int lifetime;
};
class eagle : virtual public Animal {
public:
    int wing;
};
class lion : virtual public  Animal{
public:
    int tooth;
};
class kimera : public eagle, public lion {
public:
    int leg;
};


int main(){
    kimera wild;
    wild.wing = 10;
    wild.tooth = 20;
    wild.lifetime = 30;
}

파생 클래스의 함수 재정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Base {
public:
    void f() { cout << "Base::f() called" << endl; }
};

class Derived : public Base {
public:
    void f() { cout << "Derived::f() called" << endl; }
};


int main(){
	Derived d, * pDer;
    pDer = &d;
    pDer->f(); //Derived::f() 호출

    Base* pBase;
    pBase = pDer; //업캐스팅
    pBase->f(); //Base::f() 호출
}

Output

1
2
Derived::f() called
Base::f() called

가상함수와 오버라이딩

가상 함수

  • Virtual로 선언
    • 함수에 대한 호출 바인딩을 실행 시간에 결정(동적 바인딩)
  • 오버라이딩
    • 파생 클래스에서 기본 클래스의 함수와 동일한 이름의 함수 선언
      • 기본 클래스의 함수 내용을 재정의
      • 다형성을 위한 기능
      • 가상함수에 대한 오버라이딩을 할 경우 동적 바인딩이 가능함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base2 { 
    public: 
        virtual void f() {  //가상 함수
            cout << "Base::f() Called" << endl; 
        } 
};
class Derived2 : public Base2 { 
public: 
    virtual void f() {  //오버라이딩
        cout << "Derived::f() called" << endl; 
    } 
};


int main(){
    Derived2 d, * pDer2;
    pDer2 = &d;
    pDer2->f(); //Derived::f() 호출

    Base2* pBase2;
    pBase2 = pDer2; //업캐스팅
    pBase2->f(); //Base::f() 호출
}

Output

1
2
Derived::f() called
Derived::f() called


  • 정적 바인딩

    • 함수에 대한 호출을 컴파일 시점에 결정하는 것
  • 동적 바인딩

    • 함수에 대한 호출을 실행 시점에 결정하는 것

    Image Alt 텍스트

예시

class Base2 { 
    public: 
        virtual void f() { 
            cout << "Base::f() Called" << endl; 
        } 
};
class Derived2 : public Base2 { 
public: 
    virtual void f() { 
        cout << "Derived::f() called" << endl; 
    } 
};


int main(){
    Derived2 d, * pder; //Base 멤버의 void f()는 호출되지 않음
    pder = &d;
    pder->f(); //derived::f() 호출

    Base2* pbase;
    pbase = pder; //업 캐스팅
    pbase->f(); // 동적 바인딩 발생 derived::f() 실행
}

Output

1
2
Derived::f() called
Derived::f() called


오버라이딩의 목적

  • 기본 클래스에서는 인터페이스 생성
  • 파생클래스에서는 실제 동작 정의(다형성 구현)


  • 예시 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Shape {
public:
    //가상 함수 선언(파생클래스에서 구현할 인터페이스 역활)
    virtual void draw() {} 
};
class Circle : public Shape {
public:
    virtual void draw() //하위 클래스에서 다형성 구현
    { 
        cout << "Circle을 그리기" << endl;
    }
};
class Rect : public Shape {
public:
    virtual void draw()
    {
        cout << "Rect를 그리기" << endl;
    }
};
class Line : public Shape {
public:
    virtual void draw()
    {
        cout << "Line을 그리기" << endl;
    }
};


void paint(Shape3* p) {
    p->draw(); //p가 가르키는 객체에 오버라이딩된 draw() 호출
}


int main(){
    paint(new Circle3());
    paint(new Rect3());
    paint(new Line3());
}

Output

1
2
3
Circle을 그리기
Rect를 그리기
Line을 그리기
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.