본문 바로가기

Programming/C++ 3

[C++] 08-02. 가상함수 [급여관리 시스템 3단계]

반응형

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;
}

 

 

가리키는 객체에 따라서, 가리키는 객체에 선언된 가상함수에 의해

같은 이름의 함수이지만, 호출이 되는 함수가 결정된다.

 

반응형