9 장상속과다형성 기본형의형변환복습서로다른클래스객체들사이의대입상속관계인객체와포인터의관계가상함수가상함수의동작원리추상클래스와순수가상함수 virtual 소멸자클래스멤버변수로서의클래스객체다중파일프로그래밍 C++ 프로그래밍입문
1. 기본형의형변환복습 C/C++ 형변환의종류 자동형변환 ( 묵시적형변환 ) 강제형변환 ( 명시적형변환 ) 기본형의자동형변환의예 1. 배열 to 포인터변환 배열명은해당배열의첫번째원소의 주소로변환됨 int ary[10]; int *pary = ary; 2. 함수 to 포인터변환 함수명은해당함수에대한주소로변환됨 3. 정수승격 정수계열타입의값은수식에서사용될경우 int값으로변환되어처리됨 int Sum(int a, int b); int (*psum)(int, int); psum = Sum; char c1 = 1, c2 = 2; char c3 = c1 + c2; 9 장상속과다형성 1
1. 기본형의형변환복습 기본형의자동형변환의예 ( 계속 ) 4. 실수승격 실수계산시 float 형의값은 double 형의 값으로변환되어처리됨 5. 정수변환 정수계열타입의값은또다른정수계열 타입의값으로자동변환됨 6. 실수변환 실수계열타입값사이의변환 7. 부동소수점수와정수사이의변환 실수, 정수값사이의변환 변환후의표현범위에주의 8. 포인터변환 널포인터상수 (NULL) 는평가값이 0 인 int 형포인터임. 널포인터는다른타입의포 인터로자동형변환됨 float pi = 3.14f; float area = pi * 5.0 * 5.0; int a = 5; char c1 = a; float pi = 3.14f; double dpi = pi; int a = 123456; float b = a; double *ptr = NULL; C++ : 이외의포인터자동형변환은불가 9장상속과다형성 비교 : C는 void * 와다른포인터사이의형변환가능 2
1. 기본형의형변환복습 강제형변환이필요한상황 int a = 1, b = 2; double c = a / b; // 실행결과는? int a = 1, b = 2; double c = (double) a / (double) b; C++ : 강제형변환문법 다음둘다사용가능 double c = (double) a; double c = double (a); // 기존스타일 // 함수호출스타일 9 장상속과다형성 3
2. 서로다른클래스객체들사이의대입 서로다른클래스객체들사이의자동형변환? 1 서로다른클래스 A 와 B 가있으며각각에대한객체 ObjA 와 ObjB 가있다. ObjA = ObjB 와같은대입문이자동으로수행될수있는경우는언제인가? 2 1 과같이 ObjA = ObjB 가자동으로수행가능한경우이외에 ObjA = ObjB 가가능하도록만들려면어떻게해야되는가? 3 ObjA 의값을클래스 B 값으로만들수있을까? 즉, ObjA 객체를기반으로클래스 B 객체를만들수있을까? 위의세가지경우중에서 1 à 자동형변환처럼보이지만동작원리상자동형변환과무관 2 à 한가지해결방안 : 대입연산자오버로딩 (7.10 절 ) 3 à 실질적인형변환 à 2 도해결됨 (15.2 절 ) 본절의주제 : 1 디폴트로대입문 (ObjA = ObjB) 이가능한경우 원리 : derived 클래스객체는 base 클래스의참조객체로대입될수있다. 그외의관계에있는클래스객체와참조객체사이의대입은허용되지않는다. 9 장상속과다형성 4
2. 서로다른클래스객체들사이의대입 #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; CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { 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); CCircle &Cir = Sph; // ok! derived 객체를 base 참조로대입 cout << " 표면적 : " << Cir.GetArea() << endl; // 어떤 GetArea 함수? //cout << Cir.GetVolume() << endl; // CCircle은 GetVolume을볼수없음 return 0; 9 장상속과다형성 5
2. 서로다른클래스객체들사이의대입 다음과같은클래스들이있을때가능한대입은? 1 Po = Cir; // 서로무관한클래스객체사이의대입 2 Cir = Sph; // derived 클래스객체로부터 base 클래스객체로의대입 3 Sph = Cir; // base 클래스객체로부터 derived 클래스객체로의대입힌트 : 각클래스에대한대입연산자멤버함수를생각해보라. CPoint &operator=(const CPoint &P) {... CCircle &operator=(const CCircle &C) {... CSphere &operator=(const CSphere &S) {... derived 객체는 base 객체로대입가능. 그외는불가능! 1 Po = Cir; Po.operator=(Cir); // 불가능 2 Cir = Sph; Cir.operator=(Sph); // 가능 3 Sph = Cir; Sph.operator=(Cir); // 불가능 9 장상속과다형성 6
2. 서로다른클래스객체들사이의대입 예 : CCircle 클래스에명시적대입연산자오버로딩구현, 호출확인 #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); CCircle &operator=(const CCircle &Cir) { // 대입연산자오버로딩 cout << "CCircle 대입연산자 " << endl; x = Cir.x; y = Cir.y; Radius = Cir.Radius; return (*this); class CSphere : public CCircle { int z; CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { double GetArea() { return (4 * PI * Radius * Radius); double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius); 9 장상속과다형성 7
2. 서로다른클래스객체들사이의대입 코드계속 int main(void) { CSphere Sph(1, 1, 1, 1); CCircle Cir(2, 2, 100); Cir = Sph; // ok! derived 객체를 base 객체로대입 cout << Cir.GetArea() << endl; return 0; 응용 : 다음중수행가능한것은? CCircle Cir(1, 1, 1); CSphere Sph = Cir; // 복사생성 CSphere Sph(1, 1, 1, 1); CCircle Cir = Sph; // 복사생성 CSphere(const CSphere &Sph) { à 불가능 CCircle(const CCircle &Cir) { à 가능 9 장상속과다형성 8
3. 상속관계인객체와포인터의관계 서로다른타입의포인터들사이의자동형변환 널포인터와다른타입사이의형변환외에는자동형변환불가 예외 : derived 클래스타입의포인터는묵시적으로 base 클래스타입의포인터로변환될수있다! 다음중가능한것은 1 CCircle *pcir = &Sph; 2 CSphere *psph = &Cir; // 가능 // 불가능 비유적표현 과일 : base 클래스 사과 : derived 클래스 9 장상속과다형성 9
3. 상속관계인객체와포인터의관계 #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; CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { 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); CCircle *pcir = &Sph; // ok! derive 객체주소를 base 포인터로대입 예 : CCircle 클래스와 CSphere 클래스사이의포인터와객체의관계 cout << " 표면적 : " << pcir->getarea() << endl; // 어떤 GetArea 함수? // cout << pcir->getvolume() << endl; // CCircle 포인터는 GetVolume 안보임 return 0; 9 장상속과다형성 10
3. 상속관계인객체와포인터의관계 CCircle과 CSphere의예에서 CSphere Sph(1, 1, 1, 1); CCircle *pcir = &Sph; pcir->getarea(); // 어떤함수가수행되는가? 디폴트 : CCircle 클래스의 GetArea 함수 à 겉으로보이는대로! CSphere 클래스의 GetArea 함수가수행되도록하려면 à 가상함수 ( 다음절 ) 다음은수행가능한가? pcir->getvolume(); // 불가능, CCircle 클래스에서는보이지않음 수행되도록하려면 à 강제형변환 cout << ((CSphere *) pcir)->getvolume() << endl; 참조 : 동작방식은포인터일때와동일함 int main(void) { CSphere Sph(1, 1, 1, 1); CCircle &Cir = Sph; // derived 객체를 base 참조로대입 cout << Cir.GetArea() << endl; // 어떤 GetArea 함수? 디폴트 CCircle return 0; CSphere의 GetArea 함수가수행되도록하려면? à 가상함수 9 장상속과다형성 11
4. 가상함수 프로그래밍언어론에서의정적바인딩과동적바인딩 바인딩 : 프로그램을구성하는요소들의속성을결정하는것 정적바인딩 : 어떤속성이컴파일시에결정되는것 C/C++ : 변수의타입 à 선언 ( 예, int Var) 후사용가능 동적바인딩 : 어떤속성이실행중에결정되는것 javascript : 변수 Var 의타입 à 실행중에변경가능 pcir->getarea() 에서 GetArea 함수의결정시기 디폴트 : 정적바인딩 à 컴파일시결정 à 호출하는객체또는포인터의 타입 CCircle Cir(1, 1, 1); CSphere Sph(1, 1, 1,1); CCircle *pcir; CSphere *psph; 1 Cir.GetArea(); // CCircle의 GetArea 함수 2 Sph.GetArea(); // CSphere의 GetArea 함수 3 pcir = &Cir; pcir->getarea(); // CCircle의 GetArea 함수 4 pcir = &Sph; pcir->getarea(); // CCircle의 GetArea 함수 5 CCircle &rcir = Cir; rcir.getarea(); // CCircle의 GetArea 함수 6 CCircle &rcir = Sph; rcir.getarea(); // CCircle의 GetArea 함수 실객체 CSphere 의 GetArea 함수가수행되도록하려면? à 동적바인딩 à 가상함수 9 장상속과다형성 12
4. 가상함수 실객체의타입이실행시점에결정되는예 int main(void) { int input; CCircle *pcir; cout << " 입력 (1-CCircle, 2-CSphere) : "; cin >> input; if (input == 1) pcir = new CCircle(1, 1, 1); else pcir = new CSphere(1, 1, 1, 1); 디폴트 CCircle 의 GetArea 함수호출 cout << " 면적 : " << pcir->getarea() << endl; return 0; 실객체 (CSphere) 의 GetArea 함수가호출되도록하려면 CCircle 의 GetArea 함수를가상함수로선언 à CCircle 을다형적클래스라고함 9 장상속과다형성 13
4. 가상함수 가상함수선언 : 함수선언시 virtual 키워드추가 #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) { virtual double GetArea() { return (PI * Radius * Radius); // 가상함수 class CSphere : public CCircle { int z; base 클래스의함수를가상함수로만드는경우 derived 클래스의함수는자동으로가상함수가됨. CSphere 에는 virtual 이없어도가상함수임. CSphere(int a, int b, int c, double r) : CCircle(a, b, r), z(c) { virtual double GetArea() { return (4 * PI * Radius * Radius); double GetVolume() { return ((4.0/3.0) * PI * Radius * Radius * Radius * z); 9 장상속과다형성 14
4. 가상함수 가상함수의활용예 함수의형식매개변수로 base 클래스의참조로받음 참조역시포인터와동일하게동작함 void PrintArea(CCircle &Cir) { cout << " 면적 : " << Cir.GetArea() << endl; int main(void) { CCircle Cir(1, 1, 1); CSphere Sph(1, 1, 1, 1); 상황에따라실객체의 GetArea 함수가호출됨 PrintArea(Cir); PrintArea(Sph); return 0; 9 장상속과다형성 15
5. 가상함수의동작원리 가상함수의동작원리 일반적으로가상함수테이블 (virtual function table) 사용 가상함수를 1 개이상포함하는클래스는모든가상함수에대한주소를저장 à 가상함수테이블 ß 해당클래스객체는자신의클래스에대한가상함수테 이블을가리킴 실제로는컴파일러의구현에따라달라질수있음 동작원리이해를위한예 class base { int x; class derived : public base { int y; void func1() { cout << "derived::func1" << endl; void func2() { cout << "derived::func2" << endl; void func4() { cout << "derived::func4" << endl; void func1() { cout << "base::func1" << endl; virtual void func2() { cout << "base::func2" << endl; virtual void func3() { cout << "base::func3" << endl; 9 장상속과다형성 16
5. 가상함수의동작원리 코드계속 int main(void) { base *pbase1 = new base(); base *pbase2 = new derived(); pbase2->func1(); pbase2->func2(); pbase2->func3(); //pbase2->func4(); // 호출불가 * 가상함수테이블구조및동작방식 1. base, derived 클래스의가상함수테이블작성 2. base 객체생성 (vptr: base 테이블지시 ) 3. derived 객체생성 (vptr: derived 테이블지시 ) 4. 해당객체를가리키는포인터에관계없이항상그객체의클래스함수가수행됨 delete pbase1; delete pbase2; return 0; 9 장상속과다형성 17
6. 추상클래스와순수가상함수 예 : 원 (CCircle), 직사각형 (CRect) 클래스작성 둘다좌표 (x, y), Move 함수, GetArea 함수포함 class CCircle { int x, y; double Radius; CCircle(int a, int b, double r) : x(a), y(b), Radius(r) { void Move(int a, int b) { x += a; y += b; double GetArea() { return (3.14 * Radius * Radius); class CRect { int x, y; int Garo, Sero; CRect(int a, int b, int g, int s) : x(a), y(b), Garo(g), Sero(s) { void Move(int a, int b) { x += a; y += b; double GetArea() { return (Garo * Sero); 9 장상속과다형성 18
6. 추상클래스와순수가상함수 코드계속 int main(void) { CCircle Cir(1, 1, 1); CRect Rect(2, 2, 2, 2); Cir.Move(1, 1); Rect.Move(2, 2); cout << " 원의면적 : " << Cir.GetArea() << endl; cout << " 사각형의면적 : " << Rect.GetArea() << endl; return 0; * CCircle 과 CRect 의공통부분 - x, y 변수 - Move 함수 : 내용동일 - GetArea 함수 : 내용다름 à 공통부분을포함하는 base 클래스를하나만들고 CCircle 과 CRect 는이클래스로부터상속받아만듦 9 장상속과다형성 19
6. 추상클래스와순수가상함수 예 : base 클래스인 CShape 클래스추가 (main 함수는동일 ) class CShape { protected : int x, y; 공통부분인 x, y 변수와 Move 함수를 CShape 클래스에서구현 CShape(int a, int b) : x(a), y(b) { void Move(int a, int b) { x += a; y += b; class CCircle : public CShape { double Radius; // CShape 로부터상속 CCircle(int a, int b, double r) : CShape(a, b), Radius(r) { double GetArea() { return (3.14 * Radius * Radius); class CRect : public CShape { int Garo, Sero; // CShape 로부터상속 CRect(int a, int b, int g, int s) : CShape(a, b), Garo(g), Sero(s) { double GetArea() { return (Garo * Sero); 9 장상속과다형성 20
6. 추상클래스와순수가상함수 본예제에대한제약사항 CShape 클래스객체는존재할수없음 단지다른클래스의 base 클래스역할만담당 그러나 CShape 클래스객체가존재하지않는다는강제적장치가 없음 à 추상클래스로만들면됨 추상클래스 객체가존재할수없는클래스 추상클래스작성방법 순수가상함수추가 * 순수가상함수 - 몸체가없음 - derived 클래스에서반드시재정의 à 재정의하지않으면그클래스역시추상클래스가됨 class CShape { protected : int x, y; 주의 : 포인터는존재할수있음 CShape *Spe = new CCircle(1, 1, 1, 1); cout << Spe->GetArea() << endl; CShape(int a, int b) : x(a), y(b) { void Move(int a, int b) { x += a; y += b; virtual double GetArea() = 0; 9 장상속과다형성 21
7. virtual 소멸자 다음프로그램의문제점및해결방안은? class CString { char *pstr; int len; CString(char *str) { len = strlen(str); pstr = new char[len + 1]; strcpy(pstr, str); cout << "CString 생성자 " << endl; ~CString() { delete [] pstr; cout << "CString 소멸자 " << endl; class CMyString : public CString { char *pmystr; int MyLen; CMyString(char *str1, char *str2) : CString(str1) { MyLen = strlen(str2); pmystr = new char[mylen + 1]; strcpy(pmystr, str2); cout << "CMyString 생성자 " << endl; ~CMyString() { delete [] pmystr; cout << "CMyString 소멸자 " << endl; 9 장상속과다형성 22
7. virtual 소멸자 코드계속 int main(void) { CString *pstr = new CMyString("CString", "CMyString"); delete pstr; return 0; 생성자호출순서 : CString à CMyString 소멸자호출순서 : ~CString? - 올바른동작 : ~CMyString à ~CString - 그러나 pstr 이 CString 포인터이므로 ~CString 만수행됨 실객체의타입에따라소멸자가호출되도록하려면? à 동적바인딩 à CString 클래스의소멸자를가상함수로 virtual ~CString() { 9 장상속과다형성 23
8. 클래스멤버변수로서의클래스객체 어떤클래스의멤버변수로서다른클래스의객체가오는예 class CPoint { int x, y; CPoint(int a, int b) : x(a), y(b) { void Print() { cout << "(" << x << ", " << y << ")"; class CCircle { CPoint Center; double Radius; // CPoint 객체를멤버변수로선언 CCircle(int a, int b, int r) : Center(a, b), Radius(r) { // 생성자 void Print() { Center.Print(); cout << " : " << Radius << endl; * 주의사항 : 멤버객체의초기화 à 멤버초기화구문사용 int main(void) { CCircle Cir(1, 2, 3); Cir.Print(); return 0; 9 장상속과다형성 24
8. 클래스멤버변수로서의클래스객체 상속 Vs. 멤버객체 is-a 관계 : 상속으로표현 has-a 관계 : 멤버객체로표현 ( 예 : 원은점을가지고있다.) 절대적인것은아님 has-a 관계 ( 원은점을가지고있다 ) 를상속으로표현한다면? class CCircle : public CPoint { double Radius; CCircle(int a, int b, int r) : CPoint(a, b), Radius(r) { void Print() { CPoint::Print(); cout << " : " << Radius << endl; CPoint 클래스의멤버변수로 CPoint 객체가올수있을까? No CPoint 클래스에대한포인터는올수있음 struct Node { int data; Node *next; 9 장상속과다형성 25
9. 다중파일프로그래밍 C++ 프로그램 : 주로클래스별로소스파일작성클래스및클래스관련구성요소 단순클래스선언 : class CPoint; 하나의프로그램내에여러번등장가능 하나의소스파일내에여러번등장가능 클래스선언, 멤버선언, 멤버함수의내부정의 : class CPoint {... 지금까지사용한방법 하나의프로그램내에여러번등장가능 하나의소스파일내에단한번만등장가능 클래스멤버함수의외부정의 : void CPoint::GetArea() {... 하나의프로그램내에단한번만등장가능 9 장상속과다형성 26
9. 다중파일프로그래밍 클래스별다중파일프로그래밍 #ifndef MY_CPOINT #define MY_CPOINT class CPoint { int x, y; CPoint(int a = 0, int b = 0) : x(a), y(b) { void Print(); #endif Point.h : 클래스선언 하나의소스파일내에서단한번만포함토록보장 #include <iostream> #include "Point.h" using namespace std; Point.cpp : 외부함수정의 void CPoint::Print() { cout << "(" << x << ", " << y << ")" << endl; #include <iostream> #include "Point.h" using namespace std; int main(void) { CPoint Po(1, 2); Po.Print(); return 0; 9 장상속과다형성 27 main.cpp