https://modoocode.com/209
본 포스팅은 위 링크의 글을 참조하여 작성하였습니다.
참고로 해당 글은 상속에 대해서 자세히 설명하기 보다는 C++문법에 집중할 것이다.
출처 : https://www.programiz.com/cpp-programming/inheritance
상속에 대한 대표적인 예제는 동물
에 대한 예제가 있다.
가령 동물
에 개
, 고양이
군이 있고, 개
군에는 포메
, 진돗개
등이 있다.
이렇게 세부적으로 나눠진 포메
, 진돗개
는 개
의 특성을 가지고 있으며, 동물
의 특성 또한 가지고 있다.
그래서 이들은 동물
과 개
의 특성을 상속받는다.
가령, 개
의 생김새, 짖는 방식
등이 포메
와 진돗개
같은 세부 군에 상속되는 것이다. 또한, 동물
의 먹는 능력
, 잠자는 능력
은 고양이, 강아지가 모두 상속받는다.
상속받은 특성은 그저 하나의 특성처럼 여겨진다.
위에서는 계층을 표시하기위해서 상하의 상속 관계를 표시했는데, 실제 구현은 동물
과 개
의 특성이 진돗개
, 포메
에 딱 넣어져 있는 것이다.
그럼 상속을 이용하여 이를 구현해보자
상속을 하는 방법은 다음과 같다. Base
가 부모 클래스(상속해주는), Drived
가 자식 클래스(상속 받는)이다.
class Base{
};
class Drived : public Base{
}; // 상속
이를 이용하여 위의 예제 중 동물
과 개
의 관계를 그려보겠다.
#include <iostream>
using namespace std;
class Animal{
public:
void bark(){
cout << "으르으르" << endl;
}
void eat(){
cout << "냠냠쩝쩝" << endl;
}
};
class Dog :public Animal{
public:
};
int main(){
Dog dog;
dog.bark();
dog.eat();
}
으르으르
냠냠쩝쩝
이렇게 된다.
dog
에는 어떠한 함수도 없었는데 Animal
이 가진 맴버 함수인 bark()
와 eat()
을 호출할 수 있다.
이는 상속을 받았기 때문이다. 상속받은 Dog
클래스는 맴버 함수로 bark()
와 eat()
을 갖게 되기 때문이다.
참고로, 상속받은 Drived
클래스의 생성자에서 Base
클래스의 생성자를 호출할 수 있다.
Base
클래스의 생성자를 호출하는 방법은 마치 생성자 초기 리스트처럼 써주면 된다.
Drived : Base(){
}
이렇게 말이다. Base
생성자가 먼저 호출되고, Drived
생성자가 호출된다.
그럼 Base
클래스의 생성자와 함께 Base
클래스의 맴버 변수도 같이 초기화가 가능할까??
가능하다. Base
클래스의 생성자를 호출하여 초기화시켜주면 되기 때문이다.
단, private로 선언하면 안되므로 조심하자, 접근 지시자 private, public, protected에 관해서는 뒤에 더 자세히 배우도록 하자
#include <iostream>
using namespace std;
class Base{
public:
string name;
Base(){
cout << "hello my name is base" << endl;
}
Base(int _id, string _name) : name(_name){
cout << "hello my name is base, id : " << _id << endl;
}
};
class Drived :public Base{
public:
Drived(int _id, string _name) : Base(_id, _name){
cout << "Drived class id : " << _id+1 << " name is : " <<name<< endl;
}
}; // 상속
int main(){
Drived derived(5, "gyu");
}
hello my name is base, id : 5
Drived class id : 6 name is : gyu
다음과 같이 Base
클래스의 생성자를 호출하고, Base
클래스의 맴버 변수인 name
을 Drived
클래스에서 호출할 수 있다.
물론 생성자 초기화 리스트와 같이 쓰일 수 있다.
참고로 자식 클래스에서 부모 클래스의 생성자를 호출하지 않으면 기본적으로 부모 클래스의 디폴트 생성자가 호출된다.
오버라이드는 Base
클래스의 맴버 함수를 Drived
클래스에서 다시 재정립 시키는 것이다. 때문에 override라는 이름이 붙은 것이다.
오버라이드와 오버로드를 헷갈리지 말자, 면접 단골 질문인데 은근 헷갈려 하는 사람들이 있어 정리한다.
때문에 오버라이드는 오버로딩과는 달리 함수의 모양이 똑같아야 한다. 다만 안에 들어가는 body의 모습이 달라도 되는 것이다.
구조는 다음과 같다.
#include <iostream>
using namespace std;
class Base{
public:
void print_name(){}
};
class Drived :public Base{
public:
void print_name(){}
}; // 상속
부모 클래스나 자식 클래스나 맴버 함수의 이름과 반환값, 매개변수 수, 타입 모두 같다.
이렇게해야 오버라이딩이 된다. 그럼 좀 더 완성된 예제를 통해 결과를 확인해보자
#include <iostream>
using namespace std;
class Base{
public:
string name;
Base(){
cout << "hello my name is base" << endl;
}
Base(int _id, string _name) : name(_name){
cout << "hello my name is base, id : " << _id << endl;
}
void print_name(){
cout << "Base print name : " << name << endl;
}
};
class Drived :public Base{
public:
Drived(int _id, string _name) : Base(_id, _name){
cout << "Drived class id : " << _id+1 << " name is : " <<name<< endl;
}
void print_name(){
cout << "Drived print name : " << name << endl;
}
}; // 상속
int main(){
Drived derived(5, "gyu");
derived.print_name();
}
hello my name is base, id : 5
Drived class id : 6 name is : gyu
Drived print name : gyu
print_name()
을 호출했을 때 derived
의 print_name()
이 호출되었다.
이것이 오버라이딩이다.
이제 그동안 아무렇게나 쓴 접근 제한자인 private, public, protected의 제한 범위와 의미를 알아보자
굉장히 간단한데 정리하면 다음과 같다.
즉, private는 오직 해당 클래스 내부에서만, protected는 private의 특징을 갖지만, 상속받은 클래스에 대해서는 열려있다. public은 내부든 외부든, 상속받은 클래스든 열려있다.
기본적으로 아무것도 안써주면 private이다.
위의 예제에서
class Base{
string name;
public:
Base(){
cout << "hello my name is base" << endl;
}
Base(int _id, string _name) : name(_name){
cout << "hello my name is base, id : " << _id << endl;
}
void print_name(){
cout << "Base print name : " << name << endl;
}
};
다음처럼 Base
클래스의 맴버 변수인 string name
은 default로 접근 제한자의 영역에 있기 때문에, private
에 있는 것과 마찬가지이다.
class Drived :public Base{
public:
Drived(int _id, string _name) : Base(_id, _name){
cout << "Derived class id : " << _id+1 << " name is : " <<name<< endl;
}
void print_name(){
cout << "Derived print name : " << name << endl;
}
};
Drived
클래스에서 접근이 불가능하여 컴파일 에러가 발생할 것이다. 따라서 이를 해결하기 위해서 protected
로 바꿔줄 수 있다. 물론 public
도 가능하지만 더 안전한 protected
가 좋다.
class Base{
protected:
string name;
이렇게 접근 제한자를 바꿔주면 컴파일 에러가 멈추게 된다.
상속하는 것을 잘보면 접근제한자가 있는 것을 알 수 있다.
class Dervied : public Base
이건 어떤 역할을 할까?? 사실 public
, private
, protected
로 상속을 받냐에 따라, 상속 받는 클래스들이 실제로 어떻게 작동하는지 영향을 준다.
public
형태로 상속 시 기반 클래스의 접근 지시자들에 영향이 없다. Base
클래스의 public
은 public
이고, private
는 private
이고, protected
는 protected
이다.protected
로 상속 시에 파생 클래스 입장에서 기반 클래스의public
은 protected
로 바뀌고 나머지는 그대로 유지된다.private
로 상속한다면, 파생 클래스에서 기반 클래스의 모든 접근 지시자들이 private
가 된다.#include <iostream>
using namespace std;
class Base{
public:
string name;
Base(int _id, string _name) : name(_name){}
};
class Drived :protected Base{
public:
Drived(int _id, string _name) : Base(_id, _name){}
};
int main(){
Drived derived(5, "gyu");
cout << derived.name; // error
}
다음의 코드는 에러가 발생한다. 왜냐하면 Base
클래스를 Derived
에서 protected
로 상속받았고, protected
로 public
지시자가 바뀌어 name
맴버 변수는 밖으로 호출되지 못한다.
따라서 public
으로 바꿔주면 해결될 것이다.
class Drived :public Base{
public:
Drived(int _id, string _name) : Base(_id, _name){}
};