C++은 할건데, C랑 다른 것만 합니다. 8편 상속(Inheritance), 접근 제한자

0

C++

목록 보기
8/10

상속(Inheritance), 접근 제한자

https://modoocode.com/209
본 포스팅은 위 링크의 글을 참조하여 작성하였습니다.

참고로 해당 글은 상속에 대해서 자세히 설명하기 보다는 C++문법에 집중할 것이다.

1. 상속


출처 : 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 클래스의 맴버 변수인 nameDrived 클래스에서 호출할 수 있다.

물론 생성자 초기화 리스트와 같이 쓰일 수 있다.

참고로 자식 클래스에서 부모 클래스의 생성자를 호출하지 않으면 기본적으로 부모 클래스의 디폴트 생성자가 호출된다.

2. 오버라이드(override)

오버라이드는 Base 클래스의 맴버 함수를 Drived 클래스에서 다시 재정립 시키는 것이다. 때문에 override라는 이름이 붙은 것이다.

오버라이드와 오버로드를 헷갈리지 말자, 면접 단골 질문인데 은근 헷갈려 하는 사람들이 있어 정리한다.

  1. 오버로드 : 오버로드는 매개변수의 타입이나, 개수에 따라 다른 함수처럼 취급되는 것이다. 오버로드는 상속과는 관련이 없다.
  2. 오버라이딩 : 오버라이딩은 부모 클래스의 함수를 자식 클래스에서 재정립하는 것을 말한다. 이는 함수의 구조는 모두 같아야 하며, 상속과 관련이 있다.

때문에 오버라이드는 오버로딩과는 달리 함수의 모양이 똑같아야 한다. 다만 안에 들어가는 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()을 호출했을 때 derivedprint_name()이 호출되었다.

이것이 오버라이딩이다.

3. 접근 제한자 (private, public , protected)

이제 그동안 아무렇게나 쓴 접근 제한자인 private, public, protected의 제한 범위와 의미를 알아보자

굉장히 간단한데 정리하면 다음과 같다.

  1. private : 해당 영역에 선언된 맴버 변수, 함수들을 외부에서 접근할 수 없도록 한다. 오직 클래스 내부에서만 사용가능하며 상속을 받아도 접근 불가능하다.
  2. protected : 해당 영역에 선언된 맴버 변수, 함수들을 외부에서 접근할 수 없도록 한다. 오직 클래스 내부와 상속받은 클래스에서만 접근할 수 있다.
  3. public : 해당 영역에 선언된 맴버 변수, 함수들은 외부, 내부, 상속받은 클래스에서 접근 가능하다.

즉, 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로 상속을 받냐에 따라, 상속 받는 클래스들이 실제로 어떻게 작동하는지 영향을 준다.

  1. public 형태로 상속 시 기반 클래스의 접근 지시자들에 영향이 없다. Base 클래스의 publicpublic이고, privateprivate이고, protectedprotected이다.
  2. protected로 상속 시에 파생 클래스 입장에서 기반 클래스의publicprotected로 바뀌고 나머지는 그대로 유지된다.
  3. 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로 상속받았고, protectedpublic 지시자가 바뀌어 name맴버 변수는 밖으로 호출되지 못한다.

따라서 public으로 바꿔주면 해결될 것이다.

class Drived :public Base{
    public: 
        Drived(int _id, string _name) : Base(_id, _name){}
};

0개의 댓글