C++ 프로그래밍 11 2007년 2학기 전자정보공학대학 컴퓨터공학부
Outline Review: 가상함수와 오버라이딩 연산자 오버로딩
Review: 포함 포함을 사용한 재사용의 예 [22-1]
상속의 연습 – 도형 class 만들기 Shape Class 정의하기 Rectangle Class 정의하기 맴버변수: float _x,_y 생성자: Shape(float x, float y) _x와 _y의 값을 설정 맴버함수: void Draw() const 아래와 같은 내용 출력 [SHAPE] position = ( ‘_x값’, ‘_y값’ ) Rectangle Class 정의하기 Shape Class로부터 상속 맴버변수: float _width, _height 생성자: Rectangle(float x, float y, float w, float h) 아래와 같은 내용 출력 [RECTANGLE] position = ( ‘_x값’, ‘_y값’), size = (‘_width’, ‘_height’)
상속의 연습 – 도형 class 만들기 Circle Class 정의하기 Shape Class로부터 상속 맴버변수: float _radius 생성자: Circle(float x, float y, float radius) 맴버함수: void Draw() const 아래와 같은 내용 출력 [CIRCLE] position = ( ‘_x값’, ‘_y값’), radius = ‘_radius’
상속의 연습 – 도형 class 만들기 도형 Class 테스트 int main() { Shape a(100,40); Rectangle b(120,40,50,20); Circle c(200,100,50); a.Draw(); b.Draw(); c.Draw(); return 0; }
Rectangle Class : Shape Class Circle Class : Shape Class Shape(float x, float y) void Draw() const float _x float _y Rectangle Class : Shape Class Rectangle(float x, float y, float w, float h) void Draw() const float _width float _height Circle Class : Shape Class Circle(float x, float y, float r) void Draw() const float _radius
포인터의 형변환 이용 1. 부모(base) 클래스 포인터의 배열을 설정 2. 필요 시 마다 new를 이용 자식class 생성 4. 다 사용 했으면 delete를 이용 메모리 해제
다양한 클래스의 객체를 배열에 담기(1) 도형 클래스의 객체들을 배열에 담아서 사용하는 예 Shape* shapes[5] = {NULL}; shapes[0] = new Circle( 100, 100, 50); shapes[1] = new Rectangle( 300, 300, 100, 100); shapes[2] = new Rectangle( 200, 100, 50, 150); shapes[3] = new Circle(100, 300, 150); shapes[4] = new Rectangle( 200, 200, 200, 200); for (int i = 0; i < 5; ++i) shapes[i]->Draw(); for (i = 0; i < 5; ++i) { delete shapes[i]; shapes[i] = NULL; }
다양한 클래스의 객체를 배열에 담기(2) 배열과 객체들의 메모리 구조 [23-5]
Draw의 실행 결과가 이상하다?? 부모(base) 클래스의 함수가 호출됐다?!
가상함수 가상 함수 부모 클래스의 포인터로 자식 클래스를 가르킬 경우: 객체의 실제 내용에 따라 불릴 순 없을까? 같은 이름의 함수의 실행은 포인터 타입이 우선 객체의 실제 내용에 따라 불릴 순 없을까? 컴파일 시에 결정되지 않고 함수가 런타임 시 결정 맴버 함수의 동적인 선택(동적바인딩)! 다형성 가상함수
가상함수의 선언 부모 클래스에서 함수 선언부분에 virtual 이라고 추가 구현부분은 그대로 나둔다. class parent 구현부분은 그대로 나둔다. class parent { public: virtual void DoIt(); }; class child : class parent void DoIt();
Draw 문제의 해결 shape 클래스의 Draw() 선언부분 수정 virtual void Draw() const
다형성(Polymorphism)과 가상함수(1) 다른 분야에서의 다형성의 의미 객체지향 프로그래밍에서의 다형성: 타입에 관계 없이 동일한 방법으로 다룰 수 있는 능력 예) Circle이나 Rectangle 객체들을 타입에 상관 없이 Shape 객체처럼 다룬다. [표 23-1]
다형성(Polymorphism)과 가상함수(2) 다형성은 객체간의 결합(Coupling)을 약하게 만들어서, 객체 간의 연결을 유연하게 해준다. // 도형을 원점으로 이동하는 함수 void Controller::MoveToOrigin( Shape* p ) { p->Move( 0, 0 ); p->Draw(); } [23-7]
순수 가상 함수(Pure Virtual Function) 부모 클래스의 가상함수의 구현을 생략 (구현 대신 0 을 대입) 순수 가상 함수 class parent { public: virtual void DoIt()= 0; };
순수 가상 함수(Pure Virtual Function) Shape::Draw() 함수를 순수 가상 함수로 만들어보자. class Shape { public: virtual void Draw() const = 0; … }; /* 함수의 정의 부분도 삭제 void Shape::Draw() const cout << "[Shape] Position = ( " << _x << ", " << _y << ")\n"; } */ [23-11]
추상 클래스 추상 클래스(Abstract Class) 하나 이상의 순수 가상 함수를 포함하고 있는 클래스 추상 클래스 타입의 객체 생성은 불가능 오로지 부모 클래스로서만 사용
추상 클래스 Shape::Draw() 함수를 순수 가상 함수로 만드는 것의 효과 Shape 클래스의 객체를 못만듬 오버라이딩(Overriding)을 통해서만 구현 컴파일러나 다른 개발자들에게 Shape::Draw() 함수가 자식 클래스에 의해서 오버라이딩 될 것이며, 다형성을 통해서만 호출할 것임을 알려줌
다양한 종류의 멤버 함수 상속과 관련된 멤버 함수의 종류 어떤 종류의 멤버 함수를 만들까? 일반적인 멤버 함수 가상 함수 순수 가상 함수 어떤 종류의 멤버 함수를 만들까? 기본형태 멤버 함수 다형성 가상 함수 다형성을 위한 함수의 원형만 필요 순수 가상 함수
오버로딩과 오버라이딩 오버로딩(Overloading) 오버라이딩(Overriding) 호출 인자가 다를 경우에 알맞은 것을 선택 ex) void DoIt( ); void DoIt(int i); 오버라이딩(Overriding) 부모 객체의 함수를 자식 객체에서 재정의 ex) parent::DoIt( ); child::DoIt( );
오버로딩과 오버라이딩 부모 클래스에서 오버로드 된 함수 중에서 어느 것 하나라도 오버라이드 하면 나머지 다른 함수들도 모두 사용할 수 없다. class Pet { public: void Eat(); void Eat(const string& it); string name; }; class Dog : public Pet void Eat(); // 하나만 재정의 [23-11]
오버로딩과 오버라이딩 부모 클래스에서 오버로드 된 함수 중에서 어느 것 하나라도 오버라이드 하면 나머지 다른 함수들도 모두 사용할 수 없다. int main() { // 강아지 생성 Dog dog1; dog1.name = "Patrasche"; // 두 가지 Eat() 함수를 호출한다. dog1.Eat(); dog1.Eat( "milk" ); // Error!! return 0; } [23-11]
연산자 오버로딩
연습 복소수(Complex) 클래스 만들기 private: public: 실수형인 맴버 변수 2개 생성자를 통한 값 초기화 float real, imaginary; public: 생성자를 통한 값 초기화 Complex a(10,20); 접근자를 통한 값 설정 및 변경 a.GetReal(); a.GetImaginary(); a.SetReal(30); a.SetImaginary(40);
complex 클래스 연산자 오버로딩을 위한 예제 클래스 // 복소수 클래스 class Complex { public: // 생성자 Complex(int realPart, int imaginaryPart); // 접근자들 int SetReal(int realPart); int SetImaginary(int ImaginaryPart); int GetReal() const {return real;} int GetImaginary() const {return imaginary;} private: int real; // 실수부 int imaginary; // 허수부 };
복소수간 더하기? 복소수 간 더하기는 실수부 따로, 허수부 따로! a = 1+3i b = 4+5i 더하기의 의미가 다르다! 자신이 만든 객체 간의 연산을 정의할 수 있을까? a = 1+3i b = 4+5i a+b = (1+4) + (3+5)i 연산자 오버로딩
연산자 오버로딩 객체 안에 연산자에 따른 함수를 정의하는 것 기본형: 용법: 반환값 = 자신(this) + right class Test { Test operator+ (Test& right); }; Test operator+ (Test& right) … }
피연산자가 두 개인 연산자(1) 피연산자가 두 개인 + 연산자를 오버로딩 하는 예 class Complex { // 중간 생략 Complex operator+(const Complex& right) // 실수부와 허수부를 각각 더한다. int real = this->real + right.real; int imag = this->imaginary + right.imaginary; // 결과를 보관한 복소수 객체를 반환한다. return Complex(real, imag); }
피연산자가 두 개인 연산자(2) 피연산자가 두 개인 + 연산자를 오버로딩 하는 예 Complex c1(1, 1); // + 연산자를 사용한 덧셈 c3 = c1 + c2; // c3 = (3, 3) c3 = c1.operator +(c2);
피연산자가 두 개인 연산자(3) 피연산자와 인자의 매칭 [27-1]
연습 복소수의 빼기 연산을 오버로딩 해보자 복소수의 곱하기 연산을 오버로딩 해보자
피연산자가 한 개인 연산자(1) 전치형과 후치형의 ++ 연산자를 오버로딩 하는 예 class Complex { // 중간 생략 Complex operator++() // 실수부의 값을 먼저 더한다. this->real++; // 현재 값을 반환하다. return *this; }
피연산자가 한 개인 연산자(2) 전치형과 후치형의 ++ 연산자를 오버로딩 하는 예 class Complex { Complex operator++(int) // 현재값을 먼저 보관한다. Complex prev( this->real, this->imaginary); // 실수부의 값을 더한다. this->real++; // 보관한 값을 반환한다. return prev; }
피연산자가 한 개인 연산자(3) 전치형과 후치형의 ++ 연산자를 오버로딩 하는 예 Complex c1(1, 1); Complex prefix(0, 0); Complex postfix(0, 0); // 전치연산 prefix = ++c1; // prefix = c1 = (2,1) // 후치 연산 postfix = c1++; // postfix = (2,1), c1 = (3,1)
일반함수를 사용한 오버로딩 연산자는 꼭 맴버 함수여야 하는가? 일반 함수를 사용할 수 있다.
일반 함수를 사용한 연산자 오버로딩(1) 멤버 함수가 아닌 일반 함수를 사용해서 + 연산자를 오버로딩 하는 예 Complex operator+(const Complex& left, const Complex& right) { // 실수부와 허수부를 각각 더한다. int real = left.real + right.real; int imag = left.imaginary + right.imaginary; // 결과를 보관한 복소수 객체를 반환한다. return Complex(real, imag); }
일반 함수를 사용한 연산자 오버로딩(2) 피연산자와 인자의 매칭 [27-2]
일반 함수를 사용한 연산자 오버로딩(3) 멤버 함수가 아닌 일반 함수를 사용해서 + 연산자를 오버로딩 하는 예 !!! .real과 .imaginary는 private!!! Complex operator+(const Complex& left, const Complex& right) { // 실수부와 허수부를 각각 더한다. int real = left.real + right.real; int imag = left.imaginary + right.imaginary; // 결과를 보관한 복소수 객체를 반환한다. return Complex(real, imag); }
friend 함수의 등록 친구 함수: 친구 함수 선언법: friend 사용 하여 함수등록 일반 함수 이지만 객체의 private 맴버 함수, 변수를 접근할 수 있는 함수 친구 함수 선언법: friend 사용 하여 함수등록 class Complex { // 중간 생략 friend Complex operator+(const Complex& left, const Complex& right); };
일반 함수를 사용한 연산자 오버로딩(4) 멤버 함수가 아닌 일반 함수를 사용해서 + 연산자를 오버로딩 하는 예 int main() { Complex c1(1, 1); Complex c2(2, 2); Complex c3(0, 0); // + 연산자를 사용한 덧셈 c3 = c1 + c2; // c3 = (3, 3) c3 = operator +(c1, c2); return 0; }
멤버 함수로 만들 수 없는 경우(1) 오른쪽 피연산자만 클래스 타입이라면 일반 함수를 사용해서 오버로딩 할 수 밖에 없다. ostream& operator<<(ostream& o, const Complex& right) { o << right.Real() << showpos << right.Imaginary() << "i" << noshowpos; return o; } int main() Complex c1(10, 5); cout << c1 << "\n"; return 0; [27-3]
멤버 함수로 만들 수 없는 경우(2) 연산 과정 [27-4]
연산자 오버로딩의 규칙 오버로딩이 가능한 연산자가 제한되어 있다. 기본 타입의 연산 방법은 바꿀 수 없다. 피연산자가 모두 기본 타입인 연산자 함수는 만들 수 없다. 기존 연산자의 의미를 헤치지 않는 것이 좋다. 예) + 연산자를 뺄셈의 용도로 오버로딩 하는 것은 좋지 않다.
오버로딩이 가능한 연산자(1) 피연산자가 하나인 연산자 (Unary Operators) ! 논리 NOT 연산 & 주소를 얻는 연산 ~ 비트 NOT 연산 * 포인터가 가르키는 곳의 변수 + +3 처럼 부호 ++ 1증가 연산 - -3 처럼 부호 -- 1감소 피연산자가 하나인 연산자 (Unary Operators)
오버로딩이 가능한 연산자(2) , != % %= & && &= * *= + += - -= -> ->* / /= < << <<= <= = == > >= >> >>= ^ ^= | |= || 피연산자가 두개인 연산자 (Binary Operators)
오버로딩이 가능한 연산자(3) 기타연산자 ( ) 함수 호출 연산 [ ] 배열의 원소를 가져오는 연산 new 객체 생성 연산 객체의 배열을 생성하는 연산 delete 객체 해제 연산 delete [] 객체의 배열을 해제하는 연산 기타연산자
오버로딩이 불가능한 연산자 . 맴버 선택 연산 ex) student.math .* 맴버에 대한 포인터 선택 연산 :: : ? a>b ? a: b 와 같은 삼항 연산자