A클래스 : fct함수
↑
B클래스 : fct함수, fct2함수 [함수 오버라이딩: 매개변수형이 동일한 함수를 생성]
int main(void)
{
B obj;
obj.fct();
//B의 fct 함수 호출, 오버라이딩에 의해 A의 fct함수가 가려짐
//만약에 B에 fct함수가 없다면, A의 fct함수 호출
}
포인터 관점 접근
int main(void)
{
B* b=new B();
b->fct(); //b의 fct함수 호출
A* a=b; //A의 클래스 포인터에 b를 저장
//b가 가리키는 모든 대상은 a를 상속하는 객체이며
//a의 포인터로 그 객체를 가리킬 수 있다.
B* b=a; //컴파일 에러 발생
//a가 가리키는 대상은 a객체이거나 a가 상속하는 다른 클래스의 객체인데
//그 대상은 b객체일수도 있지만 아닐수도 있기때문에 컴파일 에러 발생
a -> fct(); //a의 fct 함수 호출
a -> fct2(); //컴파일 에러 발생
//즉, a의 포인터로 가리키면 컴파일러는 a클래스의 함수를 호출
}
A : A*은 A클래스의 범위로 제한
↑
B : B*은 B클래스의 범위로 제한
↑
C : C*은 C클래스의 범위로 제한
기초 클래스의 포인터로 객체를 참조
: C++ 컴파일러는 포인터 연산의 가능성 여부를 판단할 때,
포인터의 자료형을 기준으로 판단하므로, 실제 가리키는 객체의 자료형과는 관계가 없다.
#include <iostream>
using namespace std;
class Base
{
public:
void BaseFunc() { cout << "Base Function" << endl; }
};
class Derived : public Base
{
public:
void DerivedFunc() { cout << "Derived Function" << endl; }
};
int main(void)
{
Base* bptr = new Derived(); //컴파일 성공
bptr->DerivedFunc(); //컴파일 에러
}
Derived클래스는 Base클래스를 상속하기 때문에 bptr이 Derived클래스를 가리킬 수 있다.
하지만 bptr은 Base클래스로 접근이 제한되기 때문에 DerivedFunc함수는 가리킬 수 없다
int main(void)
{
Base* bptr = new Derived(); //컴파일 성공
Derived* dptr = bptr; //컴파일 에러
}
bptr이 가리키는 클래스는 Derived클래스 객체일 수도 있지만, 아닐수도 있기 때문에 컴파일 에러 발생
[컴파일러는 포인터형 정보로 판단하기 때문에 실제 가리키는 객체와 상관없이 컴파일 에러]
int main(void)
{
Derived* dptr = new Derived(); //컴파일 성공
Base* bptr = dptr; //컴파일 성공
}
Derived클래스가 Base클래스를 상속하기 때문에, bptr이 가리키는 객체도 상속을 하기 때문에 컴파일 성공
#include <iostream>
using namespace std;
class First
{
public:
void FirstFunc()
{
cout << "FirstFunc" << endl;
}
};
class Second :public First
{
public:
void SecondFunc()
{
cout << "SecondFunc" << endl;
}
};
class Third :public Second
{
public:
void ThirdFunc()
{
cout << "ThirdFunc" << endl;
}
};
int main(void)
{
Third* tptr = new Third();
Second* sptr = tptr;
First* fptr = sptr;
tptr->FirstFunc(); //컴파일 성공
tptr->SecondFunc(); //컴파일 성공
tptr->ThirdFunc(); //컴파일 성공
sptr->FirstFunc(); //컴파일 성공
sptr->SecondFunc(); //컴파일 성공
sptr->ThirdFunc(); //컴파일 에러
fptr->FirstFunc(); //컴파일 성공
fptr->SecondFunc(); //컴파일 에러
fptr->ThirdFunc(); //컴파일 에러
delete tptr; tptr = NULL;
return 0;
}
"C++ 컴파일러는 포인터를 이용한 연산의 가능성 여부를 판단할 때, 포인터의 자료형을 기준으로 판단하므로,
포인터 형에 해당하는 클래스의 멤버에만 접근이 가능하다.
함수 오버라이딩과 포인터 형
#include <iostream>
using namespace std;
class First
{
public:
void MyFunc()
{
cout << "FirstFunc" << endl;
}
};
class Second :public First
{
public:
void MyFunc()
{
cout << "SecondFunc" << endl;
}
};
class Third :public Second
{
public:
void MyFunc()
{
cout << "ThirdFunc" << endl;
}
};
int main(void)
{
Third* tptr = new Third();
Second* sptr = tptr;
First* fptr = sptr;
fptr->MyFunc();
sptr->MyFunc();
tptr->MyFunc();
delete tptr; tptr = NULL;
return 0;
}
함수를 호출할 때 사용된 포인터의 형에 따라서 호출되는 함수가 결정된다 -> 포인터의 형에 정의된 함수가 호출된다.
EmployeeHandler 클래스의 forloop 문제의 해결
Employee* -> P.W, T.W, S.W의 함수를 호출불가 [Employee*은 Employee클래스 멤버로 접근이 제한]
->해결방법 :가상함수 이용 [가리키는 대상의 함수가 호출되도록]
가상함수
#include <iostream>
using namespace std;
class First
{
public:
virtual void MyFunc()
{
cout << "FirstFunc" << endl;
}
};
class Second :public First
//오버라이딩 된 함수가 virtual이면 오버라이딩 한 함수도 자동 virtual
{
public:
virtual void MyFunc()
{
cout << "SecondFunc" << endl;
}
};
class Third :public Second
{
public:
virtual void MyFunc()
{
cout << "ThirdFunc" << endl;
}
};
int main(void)
{
Third* tptr = new Third();
Second* sptr = tptr;
First* fptr = sptr;
fptr->MyFunc();
sptr->MyFunc();
tptr->MyFunc();
delete tptr; tptr = NULL;
return 0;
}
virtual function을 이용하면 포인터의 형에 상관 없이 포인터가 가리키는 객체의 마지막 오버라이딩 함수를 호출한다.
급여관리시스템 -3단계
//Employee.h
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
class Employee
{
private:
char name[100];
public:
Employee(const char* name);
void ShowYourName() const;
virtual int Getpay() const;
virtual void ShowSalaryInfo() const;
//EmployeeHandler가 호출하는 함수가 Employee클래스의 멤버함수지만
//실제 호출되는 함수는 각 포인터가 가리키는 객체의 마지막 오버라이딩 함수
};
#endif // !EMPLOYEE_H
//Employee.cpp
#include <iostream>
#include "Employee.h"
using namespace std;
Employee::Employee(const char* name)
{
strcpy_s(this->name, strlen(name) + 1, name);
}
void Employee::ShowYourName() const
{
cout << "name: " << name << endl;
}
int Employee::Getpay() const
{
return 0;
}
void Employee::ShowSalaryInfo() const
{}
//오버라이딩의 관계를 목적으로 정의된 함수이므로, 몸체 부분의 정의는 의미가 없다. -> 순수가상함수로 대체
순수 가상함수와 추상 클래스
순수 가상함수 : 몸체가 정의되지 않은 함수
추상 클래스 : 하나 이상의 순수 가상함수를 멤버로 두어서 객체생성이 불가능한 클래스
virtual int GetPay() const=0;
virtual void ShowSalaryInfo() const = 0;
몸체 부분을 생략한 함수 =순수 가상 함수 [호출이 불가능한 함수]
순수 가상 함수로 정의를 하는 경우 객체 생성이 불가능한 클래스가 된다. =추상 클래스
다형성
- 가상함수와 관련된 내용을 가리키는 말
- 동질 이상의 의미를 갖는데
- 모습은 같은데 형태는 다르다
- 문장은 같은데 결과는 다르다
#include <iostream>
using namespace std;
class First
{
public:
virtual void SimpleFunc()
{
cout << "First" << endl;
}
};
class Second:public First
{
public:
virtual void SimpleFunc()
{
cout << "Second" << endl;
}
};
int main(void)
{
First* ptr = new First();
ptr->SimpleFunc(); //아래에 동일한 문장이 존재
delete ptr; ptr = NULL;
ptr = new Second();
ptr->SimpleFunc(); //위에 동일한 문장이 존재
delete ptr; ptr = NULL;
return 0;
}
가리키는 객체에 따라서, 가리키는 객체에 선언된 가상함수에 의해
같은 이름의 함수이지만, 호출이 되는 함수가 결정된다.
'Programming > C++ 3' 카테고리의 다른 글
[C++] 09-01. 멤버함수와 가상함수의 동작 원리 (0) | 2021.05.30 |
---|---|
[C++] 08-03. 가상 소멸자 (0) | 2021.05.30 |
[C++] 08-01. 객체 포인터의 참조관계 [급여관리 시스템 2단계] (0) | 2021.05.29 |
[C++] 07-04. 상속을 위한 최소한의 조건 (0) | 2021.05.29 |
[C++] 07-03. protected 선언과 세 가지 형태의 상속 (0) | 2021.05.29 |