<가상함수(virtual function)의 필요성>
- 클래스를 상속 받아서 쓰는데, 상속 받은 클래스의 함수 중 고치고 싶은 함수가 있을 때
- 기존 클래스의 모든 내용을 모두 현재 프로그램에 사용할 수 있는데, 특정 함수가 마음에 들지 않음
- 기존의 클래스를 고치느니 상속을 받고, 가상함수를 이용하여 기존의 함수를 고쳐서 사용
- 상속 받은 함수(부모)를 실행하지 않고 자신(자식)의 함수를 실행
- 가상함수의 구현을 중복(overriding)이라고 함
<오버라이딩: 가상함수 구현>
- 가상함수는 기본(부모) 클래스 내에서 정의된 멤버함수를 파생(자식) 클래스에서 재정의하고자 할 때 사용한다.
- 기본 클래스의 멤버함수와 같은 이름의 함수를 파생 클래스에서 재정의하여 사용한다.
- 파생 클래스 내에서 가상함수의 재정의는 함수 중첩 기능과 비슷하지만 파생 클래스에서 재정의되는 가상함수는 기본 클래스와 함수의 리턴값, 매개변수
개수, 형이 완전히 같아야 한다.- 가상함수의 구현을 중복(overriding)이라 한다.
overloading vs. overriding
//overloading, 함수중첩 class A { private: int age; public: A() { age = 1; } A(int a) { age = a; } };//overriding, 가상함수 class A { public: virtual int SS(int i) { return(i * 2); } }; class B : public A { public: int SS(int i) { return(i * 3); } };
<바인딩(binding)>
- 변수와 함수에 대한 메모리 할당이나 함수 호출이 발생했을 때 실제로 처리해야 하는 명령어들이 결정되는 시점
- 정적(static) 바인딩, early
컴파일시 변수의 위치와 함수가 실행할 명령이 결정되는 경우 static 변수, overloading
- 동적(dynamic) 바인딩, late
실제 실행할 때(run time) 결정되는 경우 지역 변수, overriding
<동적바인딩(지역 변수)과 정적바인딩(static변수)>
#include <iostream> using std::cout; void sub(); int main() { cout << "start\n"; sub(); sub(); sub(); return 0; } void sub() { int x = 10; //동적 바인딩, run-time시 static int y = 10; //정적 바인딩, y의 초기값은 컴파일시 10으로 //정해지며 실행시에 이 선언문은 실행하지 않음 cout << x << y << '\n'; x++; y++; } //start //1010 //1011 //1012
<정적(static) 멤버변수>
class Point { int x; int y; static int count; public: Point(int i, int j) { x = i; y = j; } int getX() { return x; } int getY() { return y; } };
- 클래스의 멤버 변수 선언문에 static이라는 키워드 붙임
- C언어의 static변수와 의미가 다름
- 객체들이 정보를 공유할 목적으로 사용하는 변수
<정적 멤버변수의 특징>
- 모든 객체가 공유하는 멤버변수
- 한 클래스로부터 객체가 여러 개 만들어지더라도 이 멤버변수는 하나만 생성됨
- 여러 객체들에서 공유해야 하는 정보는 정적 멤버변수로 선언
class Dog { private: static int age; public: int getAge() { return age; } void setAge(int a) { age = a; } }; int main() { Dog merry, happy; : }
<정적(static) 멤버변수>
#include <iostream> using std::cout; class Point { int x; int y; static int count; //선언 public: Point() { cout << ++count; } ~Point() { cout << --count; } }; int Point::count = 0; //정의 int main() { Point p1, p2, p3; return 0; } //123210
<가상함수(virtual function)>
- 오버라이딩을 구현하는 방법
- 동적(실행시) 바인딩, late binding
- 가상함수를 정의하기 위해서는 기본 클래스의 멤버함수 앞에 'virtual'이라는 키워드를 씀
- 가상함수는 하나의 이름(인터페이스)으로 여러 개의 수단과 방법을 제공하는 객체지향 프로그래밍의 다형성을 구현하는 한 방법
- 기본 클래스에서 상속 받았는데 파생 클래스에서 멤버함수를 재정의하는 이유는 어떠한 경우라도 기본클래스의 멤버함수를 버리고 파생 클래스의 멤버함수를 사용하려는 것
- 기존 클래스의 모든 내용을 모두 현재 프로그램에 사용할 수 있는데 특정 함수가 걸맞지 않을 경우, 기존의 클래스를 고치느니 상속을 받고 가상함수를 이용하여 마음에 들지 않는 함수만 고쳐서 사용함
<가상함수 형식>
class 클래스명 { virtual 리턴형 함수명(매개변수) { } : };class A { public: virtual int SS(int i) { return(i * 2); } //자식아! 이 함수가 마음에 들지 않으면 재정의해라. 그럼 이 함수는 무시될 거야. //그런데 함수명, 리턴형, 매개변수의 개수와 자료형은 같아야 하고 기능만 다르게 만들어야 한다. }; class B : public A { public: int SS(int i) { return(i * 3); } //부모클래스 A의 SS함수가 마음에 들지 않아 자식클래스에서 재정의 };
static binding(overloading)
#include <iostream> using std::cout; using std::endl; class A { public: int ss(int i) { return(i * 2); } int dd(int i) { return(ss(i) * 2); } }; class B : public A { public: //부모클래스 A의 ss함수가 마음에 들지 않아 자식클래스에서 재정의 int ss(int i) { return(i * 3); } // 전혀 사용되지 않음 }; int main() { B bb; A* pA = new A; A* pB = new B; //부모 클래스의 포인터는 자식 클래스를 가리킬 수 있음 cout << bb.dd(2) << endl; cout << pA->dd(2) << endl; cout << pB->dd(2) << endl; delete pA; delete pB; return 0; } //8 //8 //8
dynamic binding(overriding)
#include <iostream> using std::cout; using std::endl; class A { public: virtual int ss(int i) { return(i * 2); } //클래스 A의 멤버함수 ss는 자식 클래스에서 재정의 할 경우 무시하세요. int dd(int i) { return(ss(i) * 2); } }; class B : public A { public: //부모클래스 A의 ss함수가 마음에 들지 않아 자식클래스에서 재정의 int ss(int i) { return(i * 3); } }; int main() { B bb; A* pA = new A; A* pB = new B; //부모 클래스의 포인터는 자식 클래스를 가리킬 수 있음 cout << bb.dd(2) << endl; cout << pA->dd(2) << endl; cout << pB->dd(2) << endl; delete pA; delete pB; return 0; } //12 //8 //12
<순수가상함수(pure virtual function)>
#include <iostream> using std::cout; using std::endl; class Shape {//추상클래스(abstract class) protected: double x, y; public: virtual double area() { return 0; } //주석처리 // 일반 가상 함수 // virtual double area()=0; //주석 해제 }; // 순수 가상 함수 class Rectangle : public Shape { private: double height, width; public: Rectangle(double h, double w) { height = h; width = w; } double area() { return(width * height); } }; class Triangle : public Shape { private: double height, width; public: Triangle(double h, double w) { height = h; width = w; } double area() { return(width * height / 2.0); } }; int main() { Shape ss; // virtual double area()=0; // 윗줄 주석과 같이 순수 가상함수가 있으면 추상클래스이고 // 추상클래스는 객체(인스턴스)를 만들 수 없음 Shape* p; //포인터 객체는 가능 Rectangle nemo(10.0, 20.0); Triangle semo(10.0, 20.0); p = &nemo; //자식의 주소를 부모 포인터에 대입 cout << "네모면적:" << p->area() << endl; //Rectangle::area() p = &semo; cout << "세모면적:" << p->area() << endl; //Triangle::area() return 0; }
<virtual 있을 때와 없을 때의 차이>
#include <iostream> using std::cout; class Dot { public: virtual void draw() {cout << "Dot::draw()\n";} void print() { cout << "Dot 클래스\n"; draw(); } }; class Line :public Dot { public: void draw() {cout << "Line::draw()\n";} }; int main() { Line line; line.print(); return 0; } //Dot 클래스 //Line::draw()#include <iostream> using std::cout; class Dot { public: void draw() {cout << "Dot::draw()\n";} void print() { cout << "Dot 클래스\n"; draw(); } }; class Line :public Dot { public: void draw() {cout << "Line::draw()\n";} }; int main() { Line line; line.print(); return 0; } //Dot 클래스 //Dot::draw()