일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- WinAPI
- Deque
- map
- C언어
- 컴퓨터 그래픽스
- Arduino
- 운영체제
- priority_queue
- Stack
- c++
- 아두이노 소스
- html
- 아두이노
- list
- 아두이노 컴파일러
- 라인트레이서
- queue
- stl
- directx
- 시스템프로그래밍
- arduino compiler
- 수광 소자
- vector
- LineTracer
- Algorithm
- set
- 통계학
- Visual Micro
- Array
- 자료구조
- Today
- Total
Kim's Programming
C++ - 클래스 상속(1/3) 본문
상속
클래스 확장
상속은 캡슐화, 추상화와 함께 객체 지향 프로그래밍의 중요한 특징중 하나입니다. 캡슐화와 추 상화는 객체가 온전한 부품이 될 수 있는 방법을 제공하는데 비해 상속은 클래스를 좀 더 쉽게 만들 수 있는 고수준의 재사용성을 확보하고 클래스간의 계층적인 관계를 구성함으로써 객체 지향의 또 다른 큰 특징인 다형성의 문법적 토대가 됩니다. 상속(Ingeritance의 사전적 의미는 자식이 부모가 가진 모든 것을 물려받는 것을 의미하는데 OOP의 상속도 기본적인 의미는 동일합니다. 이미 정의되어 있는 클래스의 모든 특성을 물려받아 새로운 클래스를 작성하는 기법을 상속이라고 합니다. 흔히 상속은 이미 만들어진 클래스를 재활용하기 위한 기법으로 소개되며 재활용이 상속의 가장 큰 장점이기는 하지만 상속에 의해 부차적으로 발생하는 효과도 있습니다. 상속을 하는 목적 또는 상송에 의한 효과는 다음 세 가지로 요약할 수 있습니다.
- 기존의 클래스를 재활용한다. 가장 기본적인 효과
- 공통되는 부분을 상위 클래스에 통합하여 본복을 제거하고 유지, 보수를 편리하게 한다.
- 공동의 조상을 가지는 계층을 만듦으로써 객체의 집합에 다형성을 부여한다.
1 2 3 4 5 | void putsdelay(const char *message) { puts(message); delay(1000); } | cs |
상속의 예
상속에 대한 문법적인 이론만 계속 나열해서는 이해하기 쉽지 않으므로 일단은 구체적인 실제 예를 보도록 하겠습니다. 다음 소스는 비록 극단적으로 간단하지만 상속을 통해 클래스를 재활용 하는 기본적인 방법을 보여 줍니다.
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 55 56 | #include<iostream> #include<Windows.h> #include<conio.h> void gotoxy(int x, int y) { COORD CursorPosition = { x, y }; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), CursorPosition); } class Coord { protected: int x, y; public: Coord(int ax, int ay) { x = ax; y = ay; } void GetXY(int &rx, int &ry) const { rx = x; ry = y; } void SetXY(int ax, int ay) { x = ax; y = ay; } }; class Point :public Coord { protected: char ch; public: Point(int ax, int ay, char ach) :Coord(ax, ay) { ch = ach; } void Show() { gotoxy(x, y); _putch(ch); } void Hide() { gotoxy(x, y); _putch(' '); } }; void main() { Point P(10, 10, '@'); P.Show(); } | cs |
두 개의 클래스를 정의하고 있는데 Coord 클래스는 화면상의 좌표 하나를 표현합니다. 좌표는 위치만을 가지며 보이는 실체가 아니므로 크기나 모양, 생상 따위의 개념이 없습니다. 그래서 Coor 클래스에는 순수하게 위치만 표현할 수 있는 x,y만 멤버 변수로 선언되어 있습니다. 그리고 x,y를 액세스 하는 Get(Set)XY 멤버 함수와 생성자가 정의되어 있습니다.
두번째 클래스인 Point는 점을 표현하는데 눈에 보이는 점을 그리기 위해서는 좌표 외에도 실제로 화면에 출력할 때 어떤 문자로 출력할 것인지에 대한 정보가 필요합니다. 그래픽 환경이라면 점의 색상이 필요하겠지만 콘솔이므로 특정 문자를 출력함으로써 점을 대신 표현하기로 합니다. 이 특정 문자를 ch 멤보로 지정합니다. 이 외에 점을 관리하는 Show, Hide 멤버 함수가 정의되어 있는데 점은 화면에 보일 수도 있고 숨을 수도 있으므로 이 두 동장을 처리하는 멤버 함수가 반드시 필요합니다. 만약 이번 특성을 가지는 Point 클래스를 단독으로 정의한다면 아마도 다음과 같은 모양이 될 것입니다. 앞에서 썼던 Position 클래스와도 비슷합니다
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 Point { protected: int x, y; char ch; public: Point(int ax, int ay, char ach) :Coord(ax, ay) { x = ax; y = ay; ch = ach; } void GetXY(int &rx, int &ry) const { rx = x; ry = y; } void SetXY(int ax, int ay) { x = ax; y = ay; } void Show() { gotoxy(x, y); _putch(ch); } void Hide() { gotoxy(x, y); _putch(' '); } }; | cs |
이 선언문에서 보다시피 x,y와 Get(Set)XY멤버 함수는 좌표를 표현하는 Coor 클래스에 이미 정의되어 있는 것들입니다. 따라서 멤버를 새로 정의할 필요없이 Coord 클래스로부터 상속받으면 됩니다. 위 소스의 Point 클래스 선언문 뒤에 :public Coord라는 선언이 바로 Coord로부터 상속을 받으라는 뜻이며 컴파일러는 이 선언에 의해 Point 클래스에 Coord가 가진 멤버를 물려줍니다. Point는 Coord가 가진 좌표와 관련된 멤버는 그대로 사용하면서 여기에 점을 표시할 문자 ch 멤버와 자신을 보이거나 숨길 수 있는 Show, Hide 멤버 그리고 생성자만 추가하면 됩니다.
Point 클래스가 Coord 클래스로부터 상속받은 것입니다. 클래스끼리 상속될 떄 상위의 클래스를 기반 클래스(Base Class)라고 하며 상속을 받는 클래스를 파생 클래스(Derive Class)라고 합니다. 이 경우 Coord 기반 클래스로부터 Point 클래스가 파생되었다고 표현합니다. 기반, 파생이라는 용어 대신 부모, 자식이라는 용어를 대신 사용하기도 하고 상위 클래스(Super Class), 하위 클래스(Sub Class)라는 용어를 쓰기도 하는데 언어에 따라 사용하는 용어가 조금씩 다릅니다. 생성자, 파괴자 등의 특수한 몇 가지를 제외하고 파생 클래스는 기반 클래스의 모든 멤버를 상속받습니다. Point는 좌표에 대한 정보인 x, y 멤버 변수와 이 멤버에 대한 액세스 함수인 Get(Set)XY 멤버 함수를 정의하고 있지 않지만 기반 클래스인 Coord로부터 상속받았으며 그래서 Point에는 x, y 멤버가 정의되어 있는 것과 마찬가지입니다. Point의 멤버 함수인 Show, Hide에서 x,y좌표를 참조하여 점을 찍거나 숨길 위치를 결정하는데 아무런 문제가 없는 것입니다.
main에서 Point형의 객체 P를 선언하되 (10,10) whkvydp answk '@'으로 점을 표현하도록 했습니다. P,Show 함수를 호출하면 (10,10)좌표에 @ 문자가 출력됩니다. P는 상속에 의해 좌표에 대한 정보를 가질 수 있으며 이 좌표에 지정된 문자를 출력함으로써 자신의 존재를 나타낼 수 있는 완전한 객체인 것입니다.
상속과 정보 은폐
클래스가 상속될 떄 기반 클래스의 멤버에 대한 엑세스 속성이 파생 클래스에게 어떻게 상속되는지 다음 소스를 통해서 보겠습니다.
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 | #include<iostream> using namespace std; class B { private: int B_private; void B_fprivate(){ puts("private function in base Class"); } protected: int B_protected; void B_fprotected(){ puts("protected function in base Class"); } public: int B_public; void B_fpublic(){ puts("public function in base Class"); } }; class D : public B { private: int D_private; void D_fprivate(){ puts("private function in derived Class"); } public: void D_fpublic() { D_private = 0; //자신의 모든 멤버 엑세스 가능 D_fprivate(); B_fprivate(); // 부모 private 멤버에 엑세스 불가 B_private = 0; B_fprotected(); //부모 protected 멤버에 엑세스 가능 B_protected = 0; B_fpublic(); //부모 public 멤버에 엑세스 가능 B_public = 0; } }; void main() { D d; d.D_fpublic(); //자신의 멤버 함수 호출 d.B_fpublic(); //부모의 멤버 함수 호출 } | cs |
기반 클래스인 B에는 private, protected, public 각각의 엑세스 속성으로 멤버 변수와 멤버 함수를 모두 정의해 두었습니다. 멤버 이름과 소속과 엑세스 지정을 포함하여 쉽게 구분할 수 있게 작성했습니다. B에서 D를 파생했을 때 파생 클래스인 D에서 기반 클래스의 각 멤버들을 엑세스 하면 어떻게 될까요? 기반 클래스의 public 멤버는 공개되어 있으므로 파생 클래스뿐만 아니라 이 클래스의 외부에서도 얼마든지 액세스할 수 있습니다. D의 멤버 함수 D_fpublic에서 B_public과 b_fpublic은 얼마든지 액세스할 수 있으며 main 함수에서도 이 멤버들을 참조가 가능합니다. 그러나 기반 클래스의 private 멤버는 숨겨져 있으므로 외부에서와 마찬가지로 파생 클래스에서 직접 엑세스할 수 없습니다. 아무리 자식이라 하더라도 부모의 숨겨진 멤버를 건드리는 것을 허용하지 않습니다. 그래서 D::D_fpublic에서 B::B_private를 참조한다거나 B::b_fprivate멤버 함수를 호출하는 것은 에러로 처리됩니다. 이 두줄을 주석처리해야 컴파일이 됩니다.
상속관계에 있어서도 파생 클래스는 기반 클래스의 외부로 간주되어 엄격한 엑세스 제한이 적용됩니다. 그런데 파생 클래스는 기반 클래스와 어느 정도 관련이 있기 떄문에 때로는 파생 클래스에게 숨겨진 멤버에 대한 엑세스를 허용해야 할 경우도 있습니다. 클래스 외부와는 달리 쌩판 남은 아닌겁니다. 이럴 때 사용하는 액세스 지정이 protected이며 public과 private의 중간 정도에 해당합니다. protected로 지정된 멤버는 클래스 외부에서는 참조할 수 없지만 파생 클래스에서는 참조할 수 있는 엑세스 속성입니다. 위 소스에서 D::D_fpublic에서 부모의 protected 멤버인 B_protected, B_fproected는 엑세스가 가능합니다. 그러나 main에서 이 값을 참조하면 에러입니다. main은 명백한 클래스 외부이며 파생 클래스도 아니므로 이 멤버를 엑세스 할 수 업습니다. 엑세스 지정자의 기능을 표로 정리하면 다음과 같습니다.
엑세스 지정자 |
클래스 외부 |
파생 클래스 |
비고 |
private |
엑세스 금지 |
엑세스 금지 |
무조건 금지 |
protected |
엑세스 금지 |
엑세스 허용 |
파생 클래스만 허용 |
public |
엑세스 허용 |
엑세스 허용 |
무조건 허용 |
protected 엑세스 속성은 상속 관계에 있지 않은 클래스나 외부에 대해 private와 같으며 파생 클래스에 대해서는 public과 같습니다. 외부에 대해서는 숨겨야 하지만 파생 클래스에서 액세스 할 필요가 있는 멤버는 proteced 엑세스 속성을 지정합니다. 파생 클래스는 기반 클래스와 아주 밀접한 관계에 있음에도 불구하고 기반 클래스의 private 멤버를 참조하지 못한다는 것은 선뜻 이해하기가 어려울 수 있지만 쓰지도 못할 멤버를 왜 상속받는지는 이해가 되지 않습니다. 하지만 부모 클래스가 스스로의 정보 은폐를 위해 자식에게조차 멤버를 숨겨야 할 필요는 분명히 있으며 이렇게 해야 파생 클래스가 영향을 받지 않습니다.
만약 부모의 private 멤버를 자식이 읽을 수 있다면 이는 정보 은폐를 완전히 포기하는 것과 마찬가지입니다. 왜냐하면 클래스가 아무리 정보를 감춰놓아도 외부에서 상속만 받으면 모든 멤버를 마음대로 건드릴 수 있기 때문입니다. private는 자식이 몰라도 되는 부분이며 마땅히 몰라야 하는 부분입니다. 파생 클래스는 기반 클래스의 private 멤버를 직접 읽지는 못하지만 기반 클래스의 public, protected 함수를 통해 이 멤버를 여전히 사용할 수는 있습니다. 다음 소스를 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class B { private: int b; public: //public 말고 protected도 됨 int Getb(){ return B; } void Setb(int ab){ b = ab; } }; class D :public B { public: void function() { cout << "'b'in the base class = " << Getb() << endl; } }; | cs |
D는 B의 private 멤버인 b를 직접 참조할 수는 없지만 상속받은 Get(Set)b 멤버 함수를 통해서 이 멤버값을 간접적으로 읽고 쓸 수 있습니다. 클래스 외부에서 적용되는 규칙이 파생 클래스에 대해서도 그대로 적용됨을 알 수 있습니다. 단 외부와는 달리 파생 클래스를 위해 protected라는 액세스 속성이 별도로 준비되어 있다는 점만 다릅니다. 일단 숨기되 차후에 상속될 가능성이 조금이라도 있다면 protected 엑세스 속성을 지정하는 것이 좋습니다.
상속 엑세스 지정
파생 클래스를 정의하는 일반적인 문법은 다음과 같습니다.
1 2 3 4 5 6 | public class 파생클래스 : protected 기반클래스 private { 추가 멤버 선언 }; | cs |
클래스 선언문 다음에 : 이 오고 상속받을 기반 클래스의 이름이 옵니다. 그리고 :과 기반 클래스 이름 사이에 상속 엑세스 지정자라는 것이 위치하는데 이 지정자는 기반 클래스의 멤버들이 파생클래스로 상속될 때 엑세스 속성이 어떻게 변할 것인가를 지정합니다. 멤버의 엑세스 속성을 지정하는 public, protected, private와 똑같은 키워드를 사용하지만 의미는 다릅니다. 이 지정자에 따라 파생 클래스가 상속받는 멤버의 엑세스 지정이 어떻게 변하는지 표로 보겠습니다.
상속 엑세스 지정자 |
기반 클래스의 액세스 속성 |
파생 클래스의 엑세스 속성 |
public |
public |
public |
private |
엑세스 불가능 |
|
protected |
protected |
|
private |
public |
private |
private |
엑세스 불가능 |
|
protected |
private |
|
protected |
public |
protected |
private |
엑세스 불가능 |
|
protected |
protected |
먼저 기반 클래스의 privated 멤버는 어떤 경우라도 파생 클래스에서 읽을 수 없습니다. 따라서 private 멤버는 상속은 되지만 파생 클래스에서는 직접 참조할 수 없으므로 엑세스 속성이 아예 없다고 할 수 있습니다. 자신도 못 읽는 멤버에 대해 외부에서 이 멤버를 읽도록 허가하거나 금지하는 속성을 지정할 수는 없느 노릇입니다. 기반 클래스의 public, protected 멤버는 상속 엑세스 지정자에 따라 엑세스 속성이 변경됩니다. 상속 엑세스 지정자가 public 이면 기반 클래스의 엑세스 속성이 그대로 유지됩니다. 즉 부모의 protected 멤버는 상속된 후의 사식 클래스에서도 여전히 protected이며 부모의 public 멤버는 자실 클래스에서도 외부로 공개됩니다. public 상속은 부모로부터 상속받은 멤버의 엑세스 속성에 아무런 변화도 없는 상속입니다. 상속 엑세스 지정자가 private, protected 인 경우는 부모의 모든 멤버가 상속되면서 private, protected로 변경됩니다. 상속 엑세스가 지정자가 생략되면 디폴트인 private가 적용됩니다. 즉 다음 두 구문은 동일한 문장입니다.
1 2 | class D : B class D : private B | cs |
클래스는 가급적이면 정보를 숨기려는 경향이 있기 때문인데 구조체의 경우 생략시 public이 적용됩니다. 통상 상속이라 하면 public 상속을 의미하며 나머지 두 가지 상속 액세스 지정자는 아주 특수한 목적에 사용됩니다. 이 두 가지 경우에 대해서는 나중에 하기로 하고 public 상속에 대해서만 고려하겠습니다.
'Programming > Cplusplus' 카테고리의 다른 글
네임스페이스(namespace)의 이용 (0) | 2016.03.07 |
---|---|
C++ - 클래스 상속(2/3) (0) | 2015.10.12 |
C++ - 연산자 오버로딩(3/3) (0) | 2015.09.21 |
C++ - 연산자 오버로딩(2/3) (0) | 2015.09.20 |
C++ - 연산자 오버로딩(1/3) (1) | 2015.09.20 |