C++에서는 파생 클래스에 기본 클래스의 멤버 함수와 동일한 이름과 원형으로 함수를 재정의(redefine)하여 사용할 수 있다. 예제 9-1은 파생 클래스에서 함수 f()를 재정의한 사례이다. 예제 9-1을 통해 함수 재정의의 의미를 알아보자.
[실행 결과]
Derived::f() called Base::f() called
#include <iostream>
using namespace std;
class Base {
public:
void f() {cout << "Base::f() called" << endl;}
};
class Derived : public Base {
public:
void f() {cout << "Derived::f() called" << endl;} // Base의 멤버 함수 f()를 재정의
};
void main() {
Derived d, *pDer;
pDer = &d; // 객체 d를 가리킨다.
pDer->f(); // Derived의 멤버 f() 호출
Base* pBase;
pBase = pDer; // 업캐스팅. 객체 d를 가리킨다.
pBase->f(); // Base의 멤버 f() 호출
}
pDer이 가리키는 객체에는 두 개의 f() 함수가 있지만, 컴파일러는 파생 클래스의 함수를 우선적으로 바인딩하기 때문이다. 다음 코드는 업 캐스팅을 통해 pBase가 객체 d를 가리킨다. pBase로 함수 f()를 호출하면 어떤 결과가 나타나게 될까?
pBase = pDer; // 업 캐스팅. pBase는 객체 d를 가리킨다.
pBase->f(); // Base의 멤버 f() 호출
pBase가 Base 클래스에 대한 포인터이므로 컴파일러는 Base의 멤버 f() 함수를 호출하도록 컴파일한다. 그 결과 그림과 같이 Base의 f() 함수를 호출한다.
9-3
#include <iostream>
using namespace std;
class Base {
public: virtual void f() {cout << "Base::f() called" << endl;}
};
class Derived : public Base {
public: void f() {cout << "Derived::f() called" << endl;}
};
class GrandDerived : public Derived {
public: void f() {cout << "GrandDerived::f() called" << endl;}
};
int main() {
GrandDerived g;
Base *bp;
Derived *dp;
GrandDerived *gp;
}
9-4
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "--Shape--";
}
};
class Circle : public Shape {
public:
int x;
virtual void draw() {
Shape::draw(); // 기본 클래스의 draw() 호출
cout << "Circle" << endl;
}
};
int main() {
Circle circle;
Shape * pShape = &circle;
pShape->draw(); // 동적 바인딩 발생. draw()가 virtual이므로
pShape->Shape::draw(); // 정적 바인딩 발생. 범위 지정 연산자로 인해
}
9-5
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() {cout << "~Base()" << endl;}
};
class Derived: public Base {
Public:
virtual ~Derived() {cout << "~Derived()" << endl;}
};
int main() {
Derived *dp = new Derived();
Base *bp = new Derived();
delete dp; // Derived의 포인터로 소멸
delete bp; // Base의 포인터로 소멸
}
다음과 같은 추상 클래스 Calculator가 있다고 할 때 이를 상속받은 GoodCalc 클래스를 구현하라.
class Calculator {
public:
virtual int add(int a, int b) = 0; // 두 정수의 합 리턴
virtual int subtract(int a, int b) = 0; // 두 정수의 차 리턴
virtual double average(int a [], int size) = 0; // a의 평균 리턴, size는 배열 크기
};
Calculator는 계산기가 제공해야 하는 기능을 정의하고 있다고 볼 수 있다. GoodCalc는 Calculator에 선언된 3개의 순수 가상 함수를 모두 구현하여야 한다. GoodCalc 클래스와 이를 활용하는 main() 함수는 다음과 같다.
#include <iostream>
using namespace std;
class Calculator {
public:
virtual int add(int a, int b) =0;
virtual int subtract(int a, int b) =0;
virtual double average(int a[], int size) =0;
};
class GoodCalc : public Calculator {
public:
int add(int a, b) {return a+b;}
int subtract(int a, b) {return a-b;}
double average(int a[], int size) {
double sum = 0;
for (int i=0; i<size; i++) {
sum +=a[i];
}
return sum / size;
}
}; // private 빼고 다 public으로 가져옴
int main() {
int a[] = {1,2,3,4,5};
Calculator *p = new GoodCalc(); // 동적 할당
cout << p->add(2,3) << endl;
cout << p->subtract(2,3) << endl;
cout << p->average(a,5) << endl;
delete p;
}
다음 코드와 실행 결과를 참고하여, 추상 클래스 Calculator를 상속받는 Adder와 Subtractor 클래스를 구현하라.
[실행 결과]
정수 2 개를 입력하세요>> 5 3 계산된 값은 8 정수 2 개를 입력하세요>> 5 3 계산된 값은 2
#include <iostream>
using namespace std;
class Calculator {
private:
void input() {
cout << "정수 2 개를 입력하세요>> ";
cin >> a >> b;
}
protected:
int a, b;
virtual int calc(int a, int b) = 0; // 두 정수의 합 리턴
public:
void run() {
input();
cout << "계산된 값은 " << calc(a,b) << endl;
}
}
int main() {
Adder adder;
Subtractor subtractor;
adder.run();
subtractor.run();
}