출처 => C++ How to Program (Detitel / Prenticehall) / 현재 그리고 미래지향적인 C++ 프로그래밍(장석우, 임정목 / 비앤씨에듀케이션)
https://private.tistory.com/25
오버라이딩
▶ 상속관계에 있는 클래스를 작성하다 보면 다음과 같이 함수가 중복되는 경우를 볼 수 있다.
#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;
//f라는 함수 이름 중복
}
};
void main() {
Derived d,* pDer;
pDer = &d;
pDer->f();//Derived클래스의 f() 호출
Base* pBase;
pBase = pDer;
pBase->f();//Base 클래스의 f() 호출
}
▶ 이렇게 함수의 이름이 중복되는 것을 함수 오버라이딩(function overriding)이라고 부른다.
▶ 오버라이딩이 있고, 또 오버로딩도 존재를 한다. 이 둘의 차이를 비교해보자
1. 오버로딩 (Overloading) : 같은 이름의 메서드 여러 개를 가지면서 매개 변수의 유형과 개수가 다르도록 하는 기술
#include<iostream>
using namespace std;
class Overloading {
public:
void overload() {
cout << "Hello" << endl;
}
void overload(int a) {
cout << a << endl;
}
void overload(string b) {
cout << b << endl;
}
};
void main() {
Overloading ol;
ol.overload();
ol.overload(2);
ol.overload("This is overloading");
}
▶ Overloading 클래스의 3개의 overload 메소드 다 이름이 같으나 매개변수나 모습이 다르다. 이를 오버로딩(Overloading)이라고 부른다.
2. 오버라이딩(Overridding) : 상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의해서 사용하는 기술
#include<iostream>
using namespace std;
class Parent {
public:
void overriding(int a) {
cout <<"Parent 클래스의 함수"<< a << endl;
}
};
class Child : public Parent {
public:
void overriding(int a) {
cout <<"Child 클래스의 함수"<< a * a << endl;
}
};
void main() {
Parent p;
Child c;
p.overriding(1);
c.overriding(2);
}
▶상위 클래스의 함수와 하위 클래스의 함수는 이름, 매개변수의 유형, 개수, 그리고 함수의 모습까지 다 같다. 상위 클래스 메소드를 같은 형태로 재정의하는 것이 바로 오버라이딩(Overriding)이다.
가상 함수의 호출
#include<iostream>
using namespace std;
class Base {
public:
virtual void f() {//가상함수 선언
cout << "Base::f() called" << endl;
}
};
class Derived :public Base {
public:
virtual void f() {
cout << "Derived::f() called" << endl;
}
};
void main() {
Derived d, * pDer;
pDer = &d;
pDer->f();
Base* pBase;
pBase = pDer;
pBase->f();
}
▶ 가상함수로 선언함으로서 Base클래스의 virtual void f( ) 메서드가 존재감을 상실하였다. 그러 인해 동적 바인딩이 일어나 하위 클래스인 Derived 클래스의 f( ) 메서드가 실행되었다.
▶ 가상의 사전적인 의미는 "실제로 존재하지 않는"이다. 이 의미는 C++에서 함수의 호출과 클래스의 참조가 컴파일 시간에 정확히 알려지지 않는다는 의미이다.
▶ 바인딩이란 함수나 변수의 주소가 결정되는 것을 말한다. 컴파일 시간에 바인딩이 되면 이른 바인딩(early binding), 실행 시간에 바인딩이 되면 늦은 바인딩(late binding)이라고 한다. virtual이 붙으면 모두 늦은 바인딩이 된다.
동적 바인딩
▶ 동적 바인딩은 파생 클래스에 대해 기본 클래스에 대한 포인터로 가상 함수를 호출하는 경우, 객체 내에 오버라이딩한 파생클래스의 함수를 찾아 실행하는 것을 말한다.
▶ 동적 바인딩이 일어나는 예제
#include<iostream>
using namespace std;
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;
}
//기본 클래스에서 파생클래스의 함수를 호출
};
void main() {
Shape* pShape = new Circle();//업캐스팅
pShape->paint();
delete pShape;
}
▶ main()메소드를 보자
void main() {
Shape* pShape = new Circle();//업캐스팅
pShape->paint();
delete pShape;
}
▶ 업 캐스팅을 하여 Shape의 객체를 Circle에서도 사용할 수 있게 하였다.
pShape->paint()라고 하였으니 Shape 클래스의 paint()를 실행하러 간다.
▶ 하지만 draw()함수의 동적 바인딩이 발생하였다. 따라서 기본 클래스인 Shape에서 파생 클래스인 Circle 클래스의 함수인 draw()를 실행할 수 있게 된 것이다.
상속의 반복으로 인한 가상 함수 호출
#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;
}
//기본 클래스 쪽에서 가상 함수를 선언하며
//그것으로부터 파생된 클래스 내의 멤버 함수도 자동으로 가상함수가 되기 때문에
//재선언할 필요는 없다.
};
void main() {
GrandDerived g;
Base* bp;
Derived* dp;
GrandDerived* gp;
bp = dp = gp = &g;
bp->f();
dp->f();
gp->f();
}
▶ 이렇게 나온 이유는 동적 바인딩에 의해 모두 GrandDerived의 함수 f( )로 호출이 되었기 때문이다.
▶ 만약 기본 클래스의 가상 함수를 호출하고 싶다면 범위 지정 연산자(::)를 이용하여 호출할 수 있다.
#include<iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "--Shape--";
}
};
class Circle :public Shape {
public:
virtual void draw() {
Shape::draw();
cout << "Circle" << endl;
}
};
void main() {
Circle c;
Shape* pShape = &c;
pShape->draw();
pShape->Shape::draw();
}
▶ 직접적으로 Shape클래스의 draw( )메소드를 출력해달라고 명령하였으므로 (정적 바인딩) 위의 결과와 같이 나오게 된다.
가상 소멸자
▶ 소멸자를 virtual 키워드로 선언한다. 소멸자를 호출 시 동적 바인딩이 발생한다.
#include<iostream>
using namespace std;
class Base {
public:
virtual ~Base() {
cout << "~Base()" << endl;
}
};
class Derived :public Base {
public:
virtual~Derived() {
cout << "~Derived" << endl;
}
};
void main() {
Derived* dp = new Derived();
Base* bp = new Derived();
delete dp;
delete bp;
}
추상클래스란 무엇인가?
▶ 추상 클래스는 다른 형식의 기반 클래스로만 사용할 수 있고 개체를 생성할 수 없는 클래스를 말한다. 이와 반대되는 개념이 개체를 생성할 수 있는 클래스인 구상 클래스이다. C++에서는 멤버 메서드 중 하나라도 순수 가상 메서드를 가지고 있는 클래스를 추상클래스라고 한다. 순수 가상 메서드는 기능을 구현하지 않고 약속만 하는 메서드이다. 순수 가상 메서드는 virtual 키워드로 메서드를 선언하고 메서드 내부를 정의하지 않겠다는 의미로 '=0;을 표시'한다.
#include<iostream>
using namespace std;
class Virtual {
public:
virtual void f() = 0;
};
void main() {
Virtual v//오류
v.f();
}
▶ 오류 발생! 추상 클래스의 객체는 생성이 불가능하다.
문제
▶ 다음 코드와 실행결과를 참고하여 추상 클래스 Calculator를 상속받는 Adder와 Subractor 클래스를 구현하여라
#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;
}
};
void main() {
Adder adder;
Subtractor subtractor;
adder.run();
subtractor.run();
}
▶ 정답
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;
}
};
'💻 개인공부 💻 > C | C++' 카테고리의 다른 글
[C++] 조작자를 이용한 구조체 객체들의 정보 출력 프로그램(feat. struct, setw(), switch) (0) | 2020.06.21 |
---|---|
[C++] 제네릭 함수 (feat. template) (0) | 2020.06.21 |
[C++] 상속(Inheritance) - 1 (feat. 개념, 상속 접근 지정자) (0) | 2020.06.19 |
[C++] 연산자 중복 (feat. 프랜드 함수) (0) | 2020.06.19 |
[C++] 프랜드 함수 (0) | 2020.06.19 |