출처 :
- 명품 C++ Programming (저자 황기태)
- 객체지향프로그래밍
파생 클래스에서 함수를 재정의
#include <iostream>
using namespace std;
class Base {
public:
void f() { cout << "Base:f() called" << end; }
};
class Derived : public Base {
public:
// 함수 재정의 redefine
void f() { cout << "Derived:f() called" << endl; }
};
void main() {
Derived d *pDer; // Derived 포인터 - 파생 클래스 객체를 가리킴
pDer = &d;
pDer->f(); // Derived::f() 호출
Base* pBase;
pBase = pDer; // 업캐스팅, pDer은 d를 가리킴, d의 부모 클래스 Base를 가리킴
pBase->f(); // pBase로 Derived 고유멤버에 접근 불가능
// Base::f() 호출
가상 함수와 오버라이딩
가상 함수 virtual function
- virtual 키워드로 선언된 멤버 함수
- virtual 키워드의 의미
- 동적 바인딩 지시어
- 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시
class Base { public: virtual void f(); };
함수 오버라이딩 function overriding
- 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언
- 기본 클래스의 가상 함수의 존재감 상실시킴
- 파생 클래스에서 오버라이딩한 함수가 호출되도록 동적 바인딩
- 함수 재정의라고도 부름
- 다형성의 한 종류
오버라이딩과 가상 함수 호출
class Base {
public:
virtual void f() { cout << "Base::f() called" << endl; }
};
class Derived : public Base {
public:
virtual void f() { cout << "Derived::f() called" << endl; }
};
int main() {
Derived d, *pDer;
pDer = &d;
pDer->f(); // Derived::f() 호출
Base * pBase;
pBase = pDer; // 업 캐스팅 pDer을 Base로 본다.
pBase->f(); // 동적 바인딩 발생!! Derived::f() 실행
}
오버라이딩의 목적
- 파생 클래스에서 구현할 함수 인터페이스 제공
- 파생 클래스의 다형성
- 다형성 실현
- draw() 가상 함수를 가진 기본 클래스 Shape
- 오버 라이딩을 통해 Circle, Rect, Line 클래스에서 자신만의 draw() 구현
// 포인터형은 Shape으로 되어있지만 Circle, Rect, Line 각자 다르게 그려짐 void paint(Shape* p) { // 파생클래스의 draw()가 실행됨 p->draw(); } paint(new Circle()); // Circle을 그린다. paint(new Rect()); // Rect을 그린다. paint(new Line()); // Line을 그린다.
동적 바인딩
- 파생 클래스에 대해 기본 클래스에 대한 포인터로 가상 함수를 호출하는 경우
- 객체 내에 오버라이딩한 파생 클래스의 함수를 찾아 실행
- 실행 중에 이루어짐 → 실행시간 바인딩, 런타임 바인딩, 늦은 바인딩
오버라이딩된 함수를 호출하는 동적 바인딩
class Shape {
public:
void paint() { draw(); }
virtual void draw() { cout << "Shape::draw() called" << endl; }
};
class Circle : public Shape {
public:
virtual void draw() { cout << "Circle::draw() called" << endl; }
};
int main() {
Shape *pShape = new Circle(); // 업캐스팅
pShape->paint(); // 동적 바인딩되어 Circle의 paint()가 불림
delete pShape;
}
C++ 오버라이딩의 특징
- 오버라이딩의 성공 조건
- 가상 함수 이름, 매개 변수 타입과 개수, 리턴 타입이 모~~두 일치
- 오버라이딩 시 파생 클래스에서 virtual 지시어 생략 가능
- 가상 함수의 접근 지정 private, protected, public 중 자유롭게 지정
상속이 반복되는 경우 가상 함수 호출
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;
bp = dp = gp = &g; // 업캐스팅
// 동적 바인디에 의해 모두 GrandDerived의 f() 호출
bp->f();
dp->f();
gp->f();
}
오버라이딩과 범위 지정 연산자 ::
- 범위 지정 연산자
- 정적 바인딩 지시
- 기본클래스::가상함수() 형태로 기본 클래스의 가상 함수를 정적으로 호출
class Shape {
public:
virtual void draw() { cout << "--Shape--"; }
};
class Circle : public Shape {
public:
virtual void draw() {
// 기본 클래스의 draw() 호출
Shape::draw();
cout << "Circle" << endl;
}
};
int main() {
Circle circle;
Shape * pShape = &circle;
// 동적바인딩 Circle의 draw() 호출
pShape->draw();
// 정적바인딩 Shape의 draw() 호출
pShape->Shape::draw();
}
가상 소멸자
- 소멸자를 virtual 키워드로 선언
- 소멸자 호출 시 동적 바인딩 발생
class Base {
public:
virtual ~Base();
};
class Derived: public Base {
public:
virtual ~Derived();
};
int main() {
Base *p = new Derived();
delete p;
// ~Base() 호출
// ~Derived() 실행 - 동적 바인딩에 의해 실행, 소멸은 파생클래스부터
// ~Base() 실행
}
// virtual 선언이 안되어있을 경우 ~Base()만 실행되고, 파생클래스는 소멸할 수 없음
소멸자를 가상 함수로 선언
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의 포인터로 소멸
}
// 파생클래스를 소멸하면 파생 - 기본 순으로 소멸 ~Derived() ~Base() 순서로 소멸
// 업캐스팅된 bp를 소멸하면 동적바인딩이 일어나 ~Derived() ~Base() 순서로 소멸
가상 함수와 오버라이딩 활용 사례
가상 함수를 가진 기본 클래스의 목적 (기출)
- 링크드 리스트
#include <iostream>
#include "Shape.h"
#include "Circle.h"
#include "Rect.h"
#include "Line.h"
using namespace std;
int main() {
Shape *pStart=NULL;
Shape *pLast;
pStart = new Circle(); // 처음에 원 도형을 생성한다.
pLast = pStart;
pLast = pLast->add(new Rect());
pLast = pLast->add(new Circle());
pLast = pLast->add(new Line());
pLast = pLast->add(new Rect());
// 현재 연결된 모든 도형을 화면에 그린다.
Shape* p = pStart;
while(p != NULL) {
// 동적 바인딩으로 Circle, Rect, Line의 draw() 호출
p->paint();
p = p->getNext();
}
// 현재 연결된 모든 도형을 삭제한다.
p = pStart;
while(p != NULL) {
// 다음 도형 주소 기억
Shape* q = p->getNext();
// 기본 클래스의 가상 소멸자 호출 - 현재 도형 객체 소멸
delete p;
// 다음 도형 주소를 p에 저장
p = q;
}
}
Circle
Rectangle
Circle
Line
Rectangle
순수 가상 함수
- 기본 클래스의 가상 함수 목적
- 파생 클래스에서 재정의할 함수를 알려주는 역할
- 실행할 코드를 작성할 목적이 아님
- 굳이.. 기본 클래스의 가상 함수를 구현할 필요가 있을까??
- 순수 가상 함수
- 함수의 코드가 없고 선언만 있는 가상 멤버 함수
class Shape {
public:
virtural void draw = 0;
};
추상 클래스
- 하나 이상의 순수 가상 함수를 가진 클래스
- 온전한 클래스가 아니므로 객체 생성 불가능
- 순수 가상 “함수”는 호출은 가능하다.
- 추상 클래스 포인터는 선언 가능 - 동적 바인딩하기 위해
추상 클래스의 목적
- 추상 클래스의 인스턴스를 생성할 목적 아님
- 상속에서 기본 클래스의 역할을 하기 위함
- 순수 가상 함수를 통해 파생 클래스의 구현할 함수의 원형을 보여주는 인터페이스 역할
- 추상 클래스의 모든 멤버 함수를 순수 가상함수로 선언할 필요는 없음
추상 클래스의 상속과 구현
- 추상 클래스를 단순 상속하면 자동 추상 클래스
class Shape { // 추상 클래스
public:
virtual void draw() = 0;
};
class Circle : public Shape { // 단순 상속받은 Circle도 추상 클래스
public:
string toString() { return "Circle 객체"; }
};
- 추상 클래스를 상속받은 파생 클래스를 추상 클래스로 만들지 않으려면 순수 가상 함수를 오버라이딩해야함
class Shape {
public:
virtual void draw() = 0;
};
class Circle : public Shape {
public:
// Circle을 추상클래스로 만들지 않으려면
// 순수 가상 함수 draw()를 overriding 해야함
virtual void draw() {
cout << "Circle";
}
string toString() { return "Circle 객체"; }
};
추상 클래스 구현 연습
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는 배열의 크기
};
class GoodCalc : public Calculator {
public:
int add(int a, int b) { return a + b; }
int subtract(int a, int 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;
}
};
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;
}
#include <iostream>
using namespace std;
class Calculator {
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;
}
};
class Adder : public Calculator {
protected:
// 순수 가상 함수 구현
int calc(int a, int b) { return a + b; }
};
class Subtractor : public Calculator {
protected:
// 순수 가상 함수 구현
int calc(int a, int b) { return a - b; }
};
int main() {
Adder adder;
Subtractor subtractor;
adder.run();
subtractor.run();
}
'[전공]' 카테고리의 다른 글
[명품 C++] 11 C++ 입출력 시스템 (2) | 2022.06.06 |
---|---|
[명품 C++] 10 템플릿과 표준 템플릿 라이브러리 STL (0) | 2022.06.06 |
[명품 C++] 08 상속 (0) | 2022.06.06 |
[명품 C++] 07 프렌드와 연산자 중복 (0) | 2022.06.05 |
[명품 C++] 6장 함수 중복과 static 멤버 (3) | 2022.06.05 |