8 장상속 상속의기본개념상속관련문제제기 base 클래스의접근제어와 protected 멤버상속관계에서의생성자와소멸자함수재정의 (function overriding) 디폴트액세스지정자와구조체 derived 클래스로부터의상속다중상속 virtual base 클래스 derived 클래스의디폴트복사생성자와디폴트대입연산자 private 생성자의사용 C++ 프로그래밍입문
1. 상속의기본개념 다음과같은문제를위한클래스설계 자동차 속성 : 색상, 배기량, 현재속도 메서드 : 가속하라, 멈춰라, 시동을켜라 트럭 속성 : 색상, 배기량, 현재속도, 최대중량 메서드 : 가속하라, 멈춰라, 시동을켜라 택시 속성 : 색상, 배기량, 현재속도, 요금, 주행거리 메서드 : 가속하라, 멈춰라, 시동을켜라 방법 1 : 자동차클래스 (CCar) 로트럭과택시를모두표현 class CCar { 속성 : 색상, 배기량, 현재속도, 최대중량, 요금, 주행거리 ; 메서드 : 가속하라, 멈춰라, 시동을켜라 ; 문제점 : 실제객체가트럭이라면요금과주행거리는불필요. 실제객체가택시라면최대중량불필요 8 장상속 1
1. 상속의기본개념 방법 2 : 자동차 (CCar), 트럭 (CTruck), 택시 (CTaxi) 클래스만들기 class CCar { 속성 : 색상, 배기량, 현재속도 ; 메서드 : 가속하라, 멈춰라, 시동을켜라 ; class CTruck { 속성 : 색상, 배기량, 현재속도, 최대중량 ; 메서드 : 가속하라, 멈춰라, 시동을켜라 ; 문제점 : 코드의중복성 CTruck 의대부분이 CCar 와동일 CTaxi 역시대부분이 CCar 와동일 à 작업의비효율성 코드의중복, 비효율성을제거하는방법 à 상속 class CTaxi { 속성 : 색상, 배기량, 현재속도, 요금, 주행거리 ; 메서드 : 가속하라, 멈춰라, 시동을켜라 ; 8 장상속 2
1. 상속의기본개념 상속의개념 CTruck과 CTaxi 클래스작성시 CCar로부터상속을받으면중복된내용은다시언급할필요가없음 상속기본문법 class CTruck : public CCar { 속성 : 최대중량 ; 메서드 : 8 장상속 3
1. 상속의기본개념 base 클래스와 derived 클래스 base 클래스 : CCar 클래스 derived 클래스 : CTruck 클래스, CTaxi 클래스 base와 derived 클래스의관계는상대적인개념 CCar 클래스를 CVehicle 클래스로부터상속받아만든다면 상속관계가자연스럽게성립하는경우 is-a 관계 : 트럭은자동차이다. 택시는자동차이다. 사과는과일이다. 그외에기존클래스의모든특성을상속받고자할때상속 : 코드재활용을위한강력한수단 8 장상속 4
2. 상속관련문제제기 예 : 원을나타내는 CCircle 클래스작성 #define PI 3.14 class CCircle { int x, y; // 중심 double Radius; // 반지름 편의상모든멤버는 public 에선언 double GetArea() { return (PI * Radius * Radius); // 면적 int main(void) { CCircle Cir; Cir.x = 1; Cir.y = 1; Cir.Radius = 5; cout << Cir.GetArea() << endl; return 0; 8 장상속 5
2. 상속관련문제제기 예 : 구를나타내는 CSphere 클래스작성 중심 (x, y, z), 반지름 (Radius), 표면적함수, 부피함수 à CCircle 상속! #define PI 3.14 class CCircle { int x, y; // 중심 double Radius; // 반지름 double GetArea() { return (PI * Radius * Radius); // 면적 class CSphere : public CCircle { // CCircle 로부터상속 int z; double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); int main(void) 이프로그램의문제점은? { CSphere Sph; Sph.x = 1; Sph.y = 1; Sph.z = 1; Sph.Radius = 5; // Sph:x, y, Radius 상속 cout << " 구의표면적 : " << Sph.GetArea() << endl; // Sph:GetArea 상속 cout << " 구의부피 : " << Sph.GetVolume() << endl; return 0; 8 장상속 6
2. 상속관련문제제기 상속관련문제제기 1. 액세스지정자 CCircle의멤버변수들을 private 영역에포함시킨다면 à 에러발생 액세스지정자의종류 : public, private, protected 2. 생성자와소멸자 base 클래스와 derived 클래스에생성자와소멸자가필요하다면 작성방법, 호출방법, 호출순서 3. CCircle과 CSphere 클래스의 GetArea 함수 함수이름및기능은동일하되구현방법은차이가남 ü CCircle : pi * r * r; ü CSphere : 4 * pi * r * r; 함수오버라이딩! 8 장상속 7
3. base 클래스의접근제어와 protected 멤버 액세스지정자의위치및종류 액세스지정자 public 과 private 에대한상속권한접근 액세스지정자 base 멤버 public private 포함영역내부접근포함영역내부접근 public public O private X private private O private X 8 장상속 8
3. base 클래스의접근제어와 protected 멤버 다음프로그램의문제점은? #define PI 3.14 class CCircle { int x, y; double Radius; // 멤버변수를 private으로선언 // 중심 // 반지름 double GetArea() { return (PI * Radius * Radius); class CSphere : public CCircle { int z; public 상속 double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); int main(void) { return 0; base 클래스의 private 멤버로의직접접근허용하지않음 8 장상속 9
3. base 클래스의접근제어와 protected 멤버 액세스지정자 protected 멤버 외부접근허용하지않음 à private 과동일 derived 클래스에서의접근허용 à public 과동일 액세스지정자에대한상속권한접근 protected 포함 base 멤버 주로 public 상속사용! public protected private 포함영역내부접근포함영역내부접근포함영역내부접근 public public O protected O private X protected protected O protected O private X private private O private O private X 8 장상속 10
3. base 클래스의접근제어와 protected 멤버 예 : CCircle 의멤버변수들을 protected 멤버로포함 #define PI 3.14 class CCircle { protected : int x, y; double Radius; // 멤버변수를 protected로선언 // 중심 // 반지름 double GetArea() { return (PI * Radius * Radius); class CSphere : public CCircle { int z; double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); int main(void) { CCircle Cir; Cir.x = 5; 에러 : x 는 protected 멤버 à 외부접근은허용되지않음 return 0; 8 장상속 11
4. 상속관계에서의생성자와소멸자 예 : CSphere 의생성자추가 #define PI 3.14 class CCircle { protected : int x, y; double Radius; // 중심 // 반지름 double GetArea() { return (PI * Radius * Radius); class CSphere : public CCircle { int z; CSphere(int a, int b, int c, double r) // 생성자 { x = a; y = b; z = c; Radius = r; double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); int main(void) { CSphere Sph(1, 2, 3, 4); cout << Sph.GetVolume() << endl; return 0; 생성자 : base 클래스의 protected 에대한접근가능 à 문제없이수행됨 à CCircle 클래스에도생성자를추가한다면??? 8 장상속 12
4. 상속관계에서의생성자와소멸자 derived 클래스객체생성시메모리생성순서 base 클래스부분생성 à derived 클래스부분생성 derived 클래스객체생성시생성자의수행순서 CCircle 클래스생성자수행 à CSphere 클래스생성자수행 앞의예에서 CCircle 클래스의생성자는? 디폴트생성자! CCircle 클래스의생성자를명시적으로호출하는방법??? à 멤버초기화구문 8 장상속 13
4. 상속관계에서의생성자와소멸자 예 : base 클래스생성자의호출 #define PI 3.14 class CCircle { protected : int x, y; double Radius; // 중심 // 반지름 CCircle(int a, int b, double r) : x(a), y(b), Radius(r) { double GetArea() { return (PI * Radius * Radius); class CSphere : public CCircle { int z; base 클래스생성자호출 CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); int main(void) { CSphere Sph(1, 2, 3, 4); cout << Sph.GetVolume() << endl; return 0; 8 장상속 14
4. 상속관계에서의생성자와소멸자 고려사항 다음과같은초기화구문사용불가 CSphere(int a, int b, int c, double r) : x(a), y(b), z(c), Radius(r) { 아직 x, y, Radius 변수가생성되기전이므로초기화불가능 ü base 클래스의멤버변수는반드시해당 (base) 클래스의생성자로초기화 앞의예에서다음과같은초기화는가능할까? CSphere(int a, int b, int c, double r) { x = a; y = b; z = c; Radius = r; 이생성자가수행되기전에 CCircle의생성자가수행되어야만됨 ü 이경우디폴트생성자 ( 매개변수가없는생성자 ) 가수행됨 à CCircle 클래스에디폴트생성자가존재하지않으므로수행불가 8 장상속 15
4. 상속관계에서의생성자와소멸자 상속관계에서소멸자의호출순서 : 생성자의역순! #define PI 3.14 class CCircle { protected : int x, y; double Radius; CSphere 객체하나에대한소멸자호출주목 // 중심 // 반지름 return 0; CCircle(int a, int b, double r) : x(a), y(b), Radius(r) { cout << "CCircle 생성자 " << endl; ~CCircle() { cout << "CCircle 소멸자 " << endl; double GetArea() { return (PI * Radius * Radius); class CSphere : public CCircle { int z; int main(void) { CSphere Sph(1, 1, 1, 1); cout << Sph.GetArea() << endl; cout << Sph.GetVolume() << endl; CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { cout << "CSphere 생성자 " << endl; ~CSphere() { cout << "CSphere 소멸자 " << endl; double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); 8 장상속 16
5. 함수재정의 앞의예에서 CSphere 객체의표면적 (GetArea) 에주목 구의표면적 = 4 * PI * r * r = 12.56 예에서는원의표면적계산함수 GetArea를상속받아사용했기때문에 à PI * r * r = 3.14가나옴 int main(void) { CSphere Sph(1, 1, 1, 1); cout << Sph.GetArea() << endl; cout << Sph.GetVolume() << endl; return 0; 원의면적, 구의표면적을구하는함수모두 GetArea로하되 GetArea 함수를호출하는객체에따라자신의함수가수행되도록하는방법 à 함수재정의 (function overriding) 8 장상속 17
5. 함수재정의 // class CCircle 생략 class CSphere : public CCircle { int z; CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { cout << "CSphere 생성자 " << endl; ~CSphere() { cout << "CSphere 소멸자 " << endl; double GetArea() { return (4 * PI * Radius * Radius); // 함수재정의 double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); int main(void) { CSphere Sph(1, 1, 1, 1); 함수재정의를통해 CSphere 의 GetArea 함수구현 cout << Sph.GetArea() << endl; cout << Sph.GetVolume() << endl; return 0; // CSphere 의 GetArea 함수호출 CCircle 의 GetArea 함수는상속되는가? CCircle 객체를통해서만호출가능한가? 8 장상속 18
5. 함수재정의 base 클래스 (CCircle) 의 GetArea 함수호출 CCircle 클래스의 GetArea 멤버함수역시상속됨 CSphere의멤버함수에서 CCircle의 GetArea를호출하는예 다음코드 (CSphere 의멤버함수 GetArea 에서 ) 의의미는? ü double GetArea() { return (4 * GetArea()); ü CSphere 멤버함수 GetArea의재귀호출 à 무한반복호출 CCircle 의 GetArea 를호출하는방법 ü double GetArea() { return (4 * CCircle::GetArea()); ü 범위지정연산자 :: 사용 CSphere 클래스외부에서 CSphere 객체를통한함수호출 CSphere sph; Sph.CCircle::GetArea(); // CCircle의 GetArea 함수호출, :: 사용 만약 CShpere 클래스에 int x, y가선언된다면 CCricle의 x, y 역시존재함 CCircle::x, CCircle::y 와같이접근가능 8 장상속 19
6. 디폴트액세스지정자와구조체 클래스와구조체의차이 public, protected, private 영역구분이없을경우의디폴트영역 클래스 : private 구조체 : public 상속클래스작성시 derived 클래스의디폴트액세스지정자 derived 클래스가클래스인경우 : private derived 클래스가구조체인경우 : public base 클래스가클래스이냐구조체이냐는무관 struct CSphere : CCircle { // derived가 struct이므로디폴트로 public 상속 int z; 다음과동일 struct CSphere : public CCircle CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); 8 장상속 20
6. 디폴트액세스지정자와구조체 다음프로그램의문제점은? #define PI 3.14 class CCircle { protected : int x, y; double Radius; // 중심 // 반지름 CCircle(int a, int b, double r) : x(a), y(b), Radius(r) { double GetArea() { return (PI * Radius * Radius); class CSphere : CCircle {// derived 가 class 이므로디폴트로 private 상속 int z; CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); int main(void) { CSphere Sph(1, 1, 1, 1); cout << Sph.GetArea() << endl; cout << Sph.GetVolume() << endl; return 0; 8 장상속 21
7. derived 클래스로부터의상속 derived 클래스로부터의상속예 생성자와소멸자의호출순서에주의 class CPoint2 : public CPoint1 { int y; protected : int v; class CPoint1 { int x; protected : int u; CPoint2(int a, int b) : CPoint1(a), y(b) { cout << "CPoint2 생성자 " << endl; ~CPoint2() { cout << "CPoint2 소멸자 " << endl; void Print() { cout << "CPoint2" << endl; CPoint1(int a) : x(a) { cout << "CPoint1 생성자 " << endl; ~CPoint1() { cout << "CPoint1 소멸자 " << endl; void Print() { cout << "CPoint1" << endl; 8 장상속 22
7. derived 클래스로부터의상속 코드계속 class CPoint3 : public CPoint2 { int z; protected : int w; CPoint3(int a, int b, int c) : CPoint2(a, b), z(c) { cout << "CPoint3 생성자 " << endl; ~CPoint3() { cout << "CPoint3 소멸자 " << endl; void Print() { CPoint1::Print(); // CPoint1의 Print 함수호출 CPoint2::Print(); // CPoint2의 Print 함수호출 cout << "CPoint3" << endl; int main(void) { CPoint3 P3(1, 2, 3); P3.Print(); return 0; 8 장상속 23 Print 함수재정의 CPoint1, CPoint2 의 Print 함수모두상속됨
8. 다중상속 다중상속 : 2 개이상의클래스로부터동시에상속받는경우 class CPointX { protected : int x; int a; CPointX(int a) : x(a) { cout << "CPointX 생성자 " << endl; ~CPointX() { cout << "CPointX 소멸자 " << endl; void Print() { cout << "CPointX" << endl; class CPointY { protected : int y; int a; CPointY(int b) : y(b) { cout << "CPointY 생성자 " << endl; ~CPointY() { cout << "CPointY 소멸자 " << endl; void Print() { cout << "CPointY" << endl; 8 장상속 24
8. 다중상속 코드계속 class CPointXYZ : public CPointX, public CPointY { int z; // 다중상속 CPointXYZ(int a, int b, int c) : CPointX(a), CPointY(b), z(c) { cout << "CPointXYZ 생성자 " << endl; ~CPointXYZ() { cout << "CPointXYZ 소멸자 " << endl; void Print() { // cout << "a : " << a << endl; // 에러발생, 어떤 a? CPointX::Print(); // CPointX의 Print 함수호출 CPointY::Print(); // CPointY의 Print 함수호출 cout << "CPointXYZ" << endl; 모호성문제발생 CPointX::a, CPointY::a 와같이사용 int main(void) { CPointXYZ Pxyz(1, 2, 3); Pxyz.Print(); return 0; 생성자호출순서 : 어느쪽을따를까? 위쪽 ( 클래스선언시작시명시된순서 ) 8 장상속 25
9. virtual base 클래스 다중상속의예및고려사항 class CPointX { protected : int x; CPointX(int a) : x(a) { cout << "CPointX 생성자 " << endl; ~CPointX() { cout << "CPointX 소멸자 " << endl; void Print() { cout << "CPointX" << endl; class CPointXY : public CPointX { protected : int y; CPointXY(int a, int b) : CPointX(a), y(b) { cout << "CPointXY 생성자 " << endl; ~CPointXY() { cout << "CPointXY 소멸자 " << endl; void Print() { cout << "CPointXY" << endl; class CPointXZ : public CPointX { protected : int z; CPointXZ(int a, int c) : CPointX(a), z(c) { cout << "CPointXZ 생성자 " << endl; ~CPointXZ() { cout << "CPointXZ 소멸자 " << endl; void Print() { cout << "CPointXZ" << endl; 8 장상속 26
9. virtual base 클래스 코드계속 class CPointXYZ : public CPointXY, public CPointXZ { int xyz; CPointXYZ(int a, int b, int c) : CPointXY(a, b), CPointXZ(a, c), xyz(0) { cout << "CPointXYZ 생성자 " << endl; ~CPointXYZ() { cout << "CPointXYZ 소멸자 " << endl; void Print() { 모호성문제발생 // cout << "x : " << x << endl; // 에러발생, 어떤x? CPointX::Print(); // VC++ 6.0에서는에러발생 CPointXY::Print(); CPointXZ::Print(); cout << "CPointXYZ" << endl; int main(void) { CPointXYZ Pxyz(1, 2, 3); Pxyz.Print(); return 0; x 2 개, y 1 개, z 1 개존재 CPointX 로부터단한번만상속받는방법은 à virtual base 클래스지정 class CPointXY : virtual public CPointX {... class CPointXZ : virtual public CPointX {... 수정후출력결과를비교해보라. ( 디폴트생성자필수 ) 8 장상속 27
10. derived 클래스의디폴트복사생성자와디폴트대입연산자 디폴트복사생성자와디폴트대입연산자의기본동작방식 멤버단위복사 class CPoint { int x, y; CPoint(const CPoint &Po) { x = Po.x; y = Po.y; CPoint &operator=(const CPoint &Po) { x = Po.x; y = Po.y; return (*this); derived 클래스의디폴트복사생성자와디폴트대입연산자 멤버단위복사? // 복사생성자 // 대입연산자 다음과같이멤버단위복사를수행하는복사생성자의문제점은? class CPoint3 : public CPoint { CPoint3(const CPoint3 &Po) { x = Po.x; y = Po.y; z = Po.z; x 접근불가 : CPoint의 private 멤버임! 8 장상속 28
10. derived 클래스의디폴트복사생성자와디폴트대입연산자 derived 클래스의디폴트복사생성자와디폴트대입연산자의동작 멤버단위복사 base 클래스의복사생성자및대입연산자호출 class CPoint3 :public CPoint { int z; CPoint3(const CPoint3 &Po) : CPoint(Po) { z = Po.z; CPoint3 &operator=(const CPoint3 &Po) { CPoint::operator=(Po); z = Po.z; return (*this); 만약, base 클래스의복사생성자와대입연산자를 private 멤버로명시 적으로작성하고, derived 클래스에서는명시적으로작성하지않는다면? base 객체와 derived 객체에대한복사생성및대입연산불가 8 장상속 29
11. private 생성자의사용 다음프로그램의문제점은? class CPoint { int x, y; CPoint(int a, int b) : x(a), y(b) { // 생성자, private 멤버임 void Print() { cout << "(" << x << ", " << y << ")" << endl; int main(void) { CPoint P1(3, 4); P1.Print(); private 영역에있는생성자호출불가 à 에러발생 return 0; 생성자를 private 영역에위치시키는경우도있을까? 8 장상속 30
11. private 생성자의사용 private 생성자의사용예 프로그램전체적으로유일한객체생성및사용 class CPoint { int x, y; CPoint(int a, int b) : x(a), y(b) { ~CPoint() { if (OnlyPoint!= NULL) delete OnlyPoint; static CPoint *OnlyPoint; // 유일한 CPoint 객체를가리킬포인터 static CPoint *GetPoint() { if (OnlyPoint == NULL) OnlyPoint = new CPoint(3, 4); return OnlyPoint; // OnlyPoint를반환하는함수 // 최초수행시객체생성 void Print() { cout << "(" << x << ", " << y << ")" << endl; CPoint *CPoint::OnlyPoint = NULL; // 초기화, 아직객체생성전 int main(void) { CPoint::GetPoint()->Print(); return 0; 디자인패턴 : 반복적으로발생하는문제에대한해법연구예 : Singleton 패턴 객체가오직하나만존재 à 표준 C++ 과는별개의주제 8 장상속 31