SturdyCobble's Study Note

[프로그래밍] 5.4 클래스(4) - 클래스의 상속 (1) 본문

휴지통/['19.06-'20.07]프로그래밍101

[프로그래밍] 5.4 클래스(4) - 클래스의 상속 (1)

StudyingCobble 2019. 11. 12. 01:27

NOTICE : 독학인 만큼, 잘못된 내용이 있을 수 있습니다. 내용 지적은 언제나 환영합니다.

더 이상 이 블로그에는 글을 올리지는 않을 예정입니다. 그렇지만 댓글을 달아주시면 최대한 답변드리고자 노력하겠습니다.


※이 글은 프로그래밍 언어에 대한 기초적인 이해를 가정하고 있습니다. 최소 프로그래밍 언어 하나 정도를 약간이라도 접해보시는 것을 추천합니다. 또한, 이 글은 심화 내용은 되도록 피하여 서술했습니다.


클래스의 상속은 객체 지향 언어를 통해 프로그래밍에 걸리는 시간과 관리의 편의성을 늘리기 위해 필수적입니다. 기존의 클래스의 기능을 물려받으면서 새로운 클래스를 만들어야 할 때, 상속의 개념을 이용할 수 있습니다.

'상속'의 의미를 강조하기 위해(기업이나 유산의 상속 등의 느낌을 강조하기 위해) 화살표 방향을 위와 같이 사용하였지만, 실제 주로 사용되는 다이어그램은 위와 같은 형태로 그려지지 않습니다.

 

이러한 관계는 한 클래스가 한 클래스 만을 상속받는 왼쪽 그림과 같은 형태일수도 있고, 한 클래스가 여러 클래스를 상속받는 형태일수도 있습니다. 다만, 오른쪽과 같은 여러 클래스를 상속하는 경우는 C++, Python에서 가능하지만, Java에서는 불가능합니다. (오른쪽과 같은 경우 에러가 발생하는 경우 찾기도 힘들고, 그런 경우가 많기에 Java에서는 자체적으로 지원하지 않는 것 같습니다.)

 

 다음 코드들은 여러 언어에서 상속이 어떻게 이루어지는지 보여줍니다.

# include <iostream>

using namespace std;

class Parents{
public:
  Parents(){
    cout << "Parents init" << endl;
  }
protected:
  void justPrinter(){
    cout << "Into the unknown" << endl;
  }
};

class Child : public Parents{
public:
  Child(){
    cout << "Child init " << endl;
    justPrinter();
  }
};

int main(){
  Child ch;
}


------------------RESULT-------------------
Parents init
Child init
Into the unknown

class parents:
    def __init__(self):
        print("Parents init")
    def __justPrint__(self):
        print("Into the unknown")

class child(parents):
    def __init__(self):
        print("Child init")
        self.__justPrint__()

ch = child()

------------------RESULT----------------------
Child init
Into the unknown

public class HelloWorld {
    public static void main(String[] args) {
      Child ch = new Child();
    }
}

class Parents {
  public Parents(){
    System.out.println("Parents init");
  }
  protected void wannaPrint(){
    System.out.println("Into the unknown");
  }
}

class Child extends Parents {
  public Child() {
    System.out.println("Child init");
    this.wannaPrint();
  }
}

----------------RESULT-------------------------
Parents init
Child init
Into the unknown

 위 코드에서 알 수 있는 점은 다음과 같습니다.

(1) 클래스 상속을 위해 사용되는 코드는 언어마다 다르다.

C++

class Child : public Parents 
(뒷 부분의 public은 나중에 설명하겠습니다.)

Python

class child(parents)
Java

class Child extends Parents

(2) 상속한 클래스의 생성자가 실행될 때  C++, Java는 생성자는 부모 클래스의 생성자를 먼저 실행하고, 자녀 클래스의 생성자를 실행한다.

(3) 상속한 메소드는 마음대로 (protected된 것까지도. private은 불가능) 자식 클래스에서 사용가능하다. (물론 property도!)

(4) Python은 부모 클래스의 생성자를 가져오지만, 자녀 클래스에도 생성자가 있으면 덮어쓰기되어, 자녀 클래스 생성자만 실행된다.

 

 예시 코드로 부터 상속은 메소드와 멤버 변수와 같은 클래스의 모든 멤버를 (별도로 지정하지 않는 한) 상속함을 확인하였습니다. 그리나 Python을 제외하곤, 생성자가 일반적인 메소드의 개념이 아니기 때문에 각 클래스의 고유한 영역으로 남아있는 점을 확인할 수 있었습니다. 

 

만약, 여기서 생성자가 인수를 받는다면 어떻게 될까요? 이 부분은 언어마다 내용이 상이한 관계로 언어별로 다루겠습니다.

 

 먼저, Python입니다.

class parents:
    def __init__(self,num1):
        print(1+num1)

class child(parents):
    def __init__(self,num1):
        print(num1)

ch = child(10)
---------RESULT-------------
10

Python은 생성자 역시 하나의 메소드처럼 취급되는 만큼, 겹치는 부분에 대해서는 자식의 생성자가 우선이 되어 위와 같은 결과를 내놓습니다. 

 

만약, 둘 다 표시하고 싶다면, 다음과 같이 메소드 오버라이딩을 위한 키워드인 super를 이용해줍니다. (super키워드의 자세한 용법은 다음 글에서 다룹니다.)

class parents:
    def __init__(self,num1):
        print(1+num1)

class child(parents):
    def __init__(self,num1):
        super().__init__(num1+10)
        print(num1)

ch = child(10)
-----------RESULT-----------------
21
10

 

 C++의 경우에는 생성자가 상속되지 않기 때문에, 다음의 코드는 에러가 납니다.

# include <iostream>

using namespace std;

class Parents{
public:
  Parents(int m_num1){
    int num1 = m_num1;
    cout << "Parents init" << num1 << endl;
  }
};

class Child : public Parents{
public:
  Child(int m_num2){
    int num2 = m_num2;
    cout << "Child init " << num2 << endl;
  }
};

int main(){
  Child ch(10);
}

어쨌든 Parents 생성자에도 인수를 전달해야 하는데, 인수가 따로 전달되지 않아 에러가 발생합니다.  이 경우, 생성자만 따로 상속을 해주어야 합니다.

Child클래스만 다음과 같이 바꿔주면, 그 아래와 같은 결과가 얻어집니다.

class Child : public Parents{
public:
  Child(int m_num2) : Parents(m_num2+10){
    int num2 = m_num2;
    cout << "Child init " << num2 << endl;
  }
};
Parents init 20
Child init 10

계속하려면 아무 키나 누르십시오 . . .

이때 클래스 상속과 다르게, 뒤에 생성자가 오며, 생성자에 값을 집어 넣어주면서 전달해줌을 볼 수 있습니다.

 

Java에서도 비슷하게 다음과 같은 코드는 에러를 발생시킵니다.

class Parents {
  public Parents(int num2){
    System.out.println("Parents init"+num2);
  }

}

class Child extends Parents {
  public Child(int num1) {
    System.out.println("Child init"+num1);
  }
}

이를 위해 super를 이용해 값을 위로 전달해주게 됩니다.

public class HelloWorld {
    public static void main(String[] args) {
      Child ch = new Child(20);
    }
}

class Parents {
  public Parents(int num2){
    System.out.println("Parents init"+num2);
  }

}

class Child extends Parents {
  public Child(int num1) {
    super(num1-10);
    System.out.println("Child init"+num1);
  }
}

--------------RESULT---------------
Parents init10
Child init20

(super 키워드의 좀 더 다양한 사용법은 다음 글에서 다룹니다.)

 

 

하지만, 때로는 메소드를 단순히 가져오는 것이 아닌 바꿔서 사용하거나 기능을 더해야할 경우도 생길 것입니다. 이 경우 오버라이딩(Overriding)을 이용하게 됩니다. 관련 내용은 다음 글에 이어서 작성하겠습니다.

Comments