상속은 ‘뒤를 잇다’라는 뜻을 가지고 있는 단어이다. 클래스에서도 상속을 통해 자신의 것을 물려줄 수 있다. 한 클래스를 잘 정의해놓고 이를 물려받게 하면 코드의 중복을 막을 수 있고, 이미 검증된 코드를 통해서 안정성을 확보할 수 있고, 이런 시간들을 아낄 수 있기에 시간 또한 확보할 수 있다. 항상 부모와 동일한 동작을 원하는 것은 아니기에 부모의 기능에 수정을 하거나 변경을 할 수도 있는 다양성 또한 가능하다.
접근 지정자 | 자신의 클래스 | 파생 클래스 | 클래스 외부 |
---|---|---|---|
private | O | X | X |
protected | O | O | X |
public | O | O | O |
class 자식클래스이름 : 접근지정자 부모클래스
{
멤버변수;
멤버함수;
}
class Parent
{
public:
Parent() { cout << "Parent" << endl; }
};
class Child : public Parent
{
public:
Child() { cout << "Child" << endl; }
};
int main()
{
Child a;
}
// 출력 결과
// Parent
// Child
class Parent
{
public:
~Parent() { cout << "~Parent" << endl; }
};
class Child : public Parent
{
public:
~Child() { cout << "~Child" << endl; }
};
int main()
{
Child a;
}
// 출력 결과
// ~Child
// ~Parent
class Parent
{
public:
// Parent() { cout << "Parent()" << endl; } // 이 생성자가 없고
Parent(int a) { cout << "Parent(int)" << endl; } // 매개변수 있는 생성자가 있다면, 기본 생성자가 만들어지지 않음
// 그럼 자식 클래스에서 있지도 않은 매개변수가 없는 생성자를 호출하려 하기 때문에 컴파일 에러
// 자식 클래스에서 명시적으로 매개변수 있는 생성자를 호출하거나, 부모 클래스에 매개변수 없는 생성자를 만들 것
~Parent() { cout << "~Parent()" << endl; }
};
class Child : public Parent
{
public:
Child() { cout << "Child()" << endl; }
Child(int a) { cout << "Child(int)" << endl; }
~Child() { cout << "~Child()" << endl; }
};
자식 클래스에서 명시적으로 매개변수 있는 생성자를 호출
Child() : Parent(0)
{
}
Child(int n) : Parent(n)
{
}
// Child(int n) : Parent(int n) // 잘못된 표현
부모 클래스에 매개변수 없는 생성자를 만들 것
부모 클래스에 매개변수 있는 생성자를 지울 것 → 컴파일러에 의해 기본 생성자가 만들어짐
이전에 오버로딩이라는 하나의 함수 이름을 갖고 시그니처를 통해 원하는 함수를 맞춰부르도록 하는 개념을 배웠었다. 같은 함수 이름을 사용하는 것에서 오버로딩이랑 동일하지만 개념이 다르다.
부모 클래스에 정의되어 있는 함수와 동일한 형태로 자식 클래스에서 다시 정의하는 것
정의는 다음과 같고 특징으로는 아래와 같다.
class Parent
{
public:
void Print() { cout << "Parent Print()" << endl; }
};
class Child : public Parent
{
public:
void Print() { cout << "Child Print()" << endl; }
};
int main()
{
Parent p;
Child c;
p.Print();
c.Print();
}
// 출력 결과
// Parent Print()
// Child Print()
클래스를 기본 자료형 혹은 다른 클래스와의 형변환을 할 때와, 부모와 자식 사이의 형변환은 차이가 있다. 거기에 대해서 알아보도록 하자.
부모 클래스의 포인터 변수가 자식 클래스의 객체를 갖게 될 때
Child c;
Parent* p = &c; // c 앞에 (Parent)를 붙이지 않아도 자동 형변환이 됨
자식 클래스의 포인터 변수가 부모 클래스의 객체를 갖게 될 때
Child c;
Parent* pp = &c; // c 앞에 (Parent)를 붙이지 않아도 자동 형변환이 됨
Child* pc = (Child*)pp; // 명시적으로 형변환을 해주어야함
자식 객체를 업캐스팅하여 오버라이딩된 함수를 호출하면 부모의 함수가 호출된다
int main()
{
Child c;
Parent* pp = &c; // 업캐스팅
Child* pc = (Child*)pp; // 다운캐스팅
c.Print();
pp->Print(); // Parent* 였기 때문에 Parent 함수가 호출됨
pc->Print(); // Child* 였기 때문에 Child에 오버라이딩된 함수가 호출됨
}
// 출력 결과
// Child Print()
// Parent Print()
// Child Print()