객체지향 언어의 특징
- 캡슐화
- 목적으로 하는 기능에 필요한 멤버 변수, 기능을 하나의 클래스로 묶어서 구현하는 것.
- 은닉성 : 외부에서 접근이 불필요한 중요 멤버에 대해서는 접근시키지 못하게 하는 것.
- 상속
- 다형성
- 추상화
상속 Inherit
class CParent
{
private:
int m_i;
public:
CParent()
: m_i(0)
{}
~CParent();
};
class CChild : public CParent
{
private:
float m_f;
public:
CChild()
: m_f(0f)
{}
};
- 어떤 클래스가 상속을 받는다. => 해당 클래스가 다른 클래스의 기능을 가져온다.
- 상위 클래스의 기능을 토대로 새로운 기능을 확장시킨다.
상속의 이점
int main()
{
CParent parent;
CChild child;
return 0;
}
상속 시, 메모리 구조
CParent : 4 byte
CChild : 8 byte
- CParent 부분 (4 byte) + 새로운 CChild 부분 (4 byte) = 8 byte
- 상속을 이용해 구현한 클래스의 메모리 구조는 상위 클래스가 메모리상으로 먼저 배치됨.
- 순차적으로 부모 -> 자식 순으로
- 메모리가 생기는 순서는 매우 중요함.
CChild child;
선언 시, 메모리 할당
[1. CParent (4 byte) | 2. CChild (4 byte)]
protected 접근 제한자
- 기존에 외부 클래스에서 다른 클래스의 멤버에 접근하기 위해선
public 을 사용했음.
- 캡슐화를 위해서 멤버 변수는
private로 선언하고
Setter 함수를 public으로 선언해왔음.
- 상위 클래스의
private 멤버는 자식 클래스에서도 접근하지 못함.
- 하위 클래스의 구성에 포함하고 있으나 직접 접근할 순 없는 것.
public으로 선언해야하나? => 접근은 할 순 있으나, 외부에도 공개될 것.
- 은닉성의 문제가 발생할 여지가 있음.
- 이런 상황을 위해서 하위 클래스에선 접근할 수 있으나 외부에선 접근하지 못하도록
protected 접근 제한자를 사용.
생성자 작동 방식
생성자의 호출 순서
- CChild 선언시 주체가 되는 것은 CChild임.
- 그러면 클래스의 생성자는? => 부모 생성자가 먼저 호출됨.
- 메모리 배치 구조를 보면 부모가 먼저 배치된다고 했음.
class CChild : public CParent
{
private:
float m_f;
public:
CChild()
: m_f(0.f)
, m_i(1)
{}
CChild()
: CParent()
, m_i(1)
{}
}
- 부모의 생성자가 먼저 마무리된 이후에 자식 생성자가 진행됨.
- 호출 순서상 자식이 먼저되는 것은 맞으나, 실행은 부모가 먼저됨
상속 시, 생성자 호출 및 동작 순서 정리
- 자식 or 부모 클래스는 상속 관계에서는 다른 클래스의 멤버를 초기화할 수 없음
- 자기 할 일은 자기가 하는 것. : 초기화 한정
- 생성자 호출 순서
- 생성자 실행 순서, 초기화 동작 순서
소멸자의 경우
- 실행, 호출 모두 자식 파트가 실행되고, 부모 파트가 실행됨.
오버라이딩 Overriding
- 오버로드와 오버라이딩이 이름이 유사한 것을 보고 개념을 아는지 묻는 면접 질문이 자주 있음
- 오버로드 Overload
- 어떤 함수를 매개변수를 달리하여 다양한 상황에서 쓸 수 있도록 작성한 것
- 오버라이드 Override
- 상속 관계에서 상위 클래스의 추상, 가상 함수를 하위 클래스에서 재정의 또는 확장하는 것
class CParent
{
public:
void Output()
{
cout << "output: parent" << endl;
}
};
class CChild : public CParent
{
~~
};
int main()
{
CParent parent;
parent.Output();
CChild child;
child.Output();
return 0;
}
- 자식 클래스는 당연히 부모 클래스의 함수를 호출할 수 있음.
class CParent
{
public:
void Output()
{
cout << "output: parent" << endl;
}
};
class CChild : public CParent
{
public:
void Output()
{
cout << "output: child" << endl;
}
};
- 상속받은 부모클래스의 함수를 재정의 override
int main()
{
CParent parent;
parent.Output();
CChild child;
child.Output();
child.CParent::Output();
return 0;
}
- 둘 다 있을 땐, 자식 쪽에 구현된 것이 호출됨.
없다면 부모의 함수가 호출될 것.
child.CParent::Output();
굳이 명시적으로 부모 클래스의 함수를 호출할 수 있음.
=> 이렇게 할거면 애초에 오버라이딩을 안할 것
오버라이드는 왜 쓸까?
- 상속을 사용하는 상황 예시.
- 동물 => 모든 동물들이 공통적으로 사용할 기능을 정리해둘 것임.
- 조류 : 동물이 하는 동작 + 조류만의 기능
- 포유류 : 동물이 하는 동작 + 포유류만의 기능
- 파충류 : 동물이 하는 동작 + 파충류만의 기능
- 모든 설계가 다 맞아 떨어지면 좋은데 항상 예외가 생김.
- 분명 파생되는 모든 것들이 대다수에는 해당되더라도 일부는 조금 다르게 동작하는 경우가 생김.
- 이를 위해서 오버라이딩이 필요한 것.