250x250
Notice
Recent Posts
Recent Comments
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 07-2 : 상속의 문법적인 이해 본문

Programming Lang/C++

C++ Chapter 07-2 : 상속의 문법적인 이해

시데브 2023. 7. 18. 18:00
728x90

상속이란?

 "UnivdStudent 클래스가 Person 클래스를 상속한다." 라고 한다면 UnivStudent 클래스는 Person 클래스가 지니고 있는 모든 멤버를 물려받는다. 즉, UnivStudent 객체에는 UnivStudent 클래스에 선언된 멤버 뿐만 아니라 Person 클래스에 선언된 멤버도 존재하게 된다.

 

상속의 방법과 그 결과

 예제를 통해서 상속의 방법과 결과를 확인해보자.

 

 우슨 다음과 같이 Person 클래스를 정의하겠다.

class Person
{
private:
    int age;        //나이
    char name[50];  //이름
public:
    Person(int myage, char * myname) : age(myage)
    {
        strcpy(name, myname);
    }
    void WhatYourName() const
    {
        cout<<"My name is "<<name<<endl;
    }
    void HowOldAreYou() const
    {
        cout<<"I'm "<<age<<" years old"<<endl;
    }
};

 이어서 Person 클래스를 상속하는 UnivStudent 클래스는 다음과 같다.

class UnivStudent : public Person   // Person 클래스의 상속을 의미함
{
private:
    char major[50]; // 전공 과목
public:
    UnivStudent(char * myname, int myage, char * mymajor)
        : Person(myage, myname)
    {
        strcpy(major, mymajor);
    }
    void WhoAreYou() const
    {
        WhatYourName();
        HowOldAreYou();
        cout<<"My major is "<<major<<endl<<endl;
    }
};

 위의 상속 선언문에서 class UnivStudent : public Person이 의미하는 바는 'public 상속'이다. 하지만, 우리는 상속이 선언되었다는 데에 집중하고 public의 의미에 대해서는 다음에 살펴보자.

 

 첫 번째로 WhoAreyou()함수를 살펴보면, 클래스 UnivStudent에 해당 함수들이 따로 정의되어있지 않음에도 불구하고 클래스 Person의 멤버함수인 WhatYourName()과 HowOldAerYou()를 호출하고 있다. 여기서 우리는 클래스를 상속하면 해당 클래스의 public 멤버에 접근할 수 있다는 것을 알 수 있다. 

 

더보기

+char * 는 문자열을 나타내는 포인터 변수이나, 해당 변수를 그대로 출력하면 문자열이 출력된다. 그러나, 포인터 변수이기 때문에 직접 접근이나 수정할 수 는 없고 읽기만 할 수 있다. 

 

 두 번째로 클래스 UnivStudent의 생성자 형태를 살펴보자.

UnivStudent(char * myname, int myage, char * mymajor)
    : Person(myage, myname)
{
    strcpy(major, mymajor);
}

 UnivStudent는 Person 클래스를 상속하기 때문에 UnivStudent의 객체를 생성했을 때, 멤버 변수로 major 뿐만 아니라 Person의 멤버 변수인 age와 name을 포함하고 있다. 따라서, 해당 클래스의 생성자는 이들 모두에 대한 초기화의 책임을 가지고 있다. 또한, 클래스 Person의 멤버변수를 초기화하는 방식은 해당 클래스를 정의할 때 그 클래스의 생성자에서 정해지기 때문에 이미 만들어진 생성자를 활용하는 것이 안정적이다. 그래서 위 예제의 UnivStudent의 생성자는 클래스 Person의 생성자를 호출해서 해당 멤버변수들을 초기화한다. 

 

 그렇다면 이제 위 예제를 완성해보자.

#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
    int age;        //나이
    char name[50];  //이름
public:
    Person(int myage, char * myname) : age(myage)
    {
        strcpy(name, myname);
    }
    void WhatYourName() const
    {
        cout<<"My name is "<<name<<endl;
    }
    void HowOldAreYou() const
    {
        cout<<"I'm "<<age<<" years old"<<endl;
    }
};

class UnivStudent : public Person   // Person 클래스의 상속을 의미함
{
private:
    char major[50]; // 전공 과목
public:
    UnivStudent(char * myname, int myage, char * mymajor)
        : Person(myage, myname)
    {
        strcpy(major, mymajor);
    }
    void WhoAreYou() const
    {
        WhatYourName();
        HowOldAreYou();
        cout<<"My major is "<<major<<endl<<endl;
    }
};

int main(void)
{
    UnivStudent ustd1("Lee", 22, "Computer eng.");
    ustd1.WhoAreYou();

    UnivStudent ustd2("Yoon", 21, "Electronic eng.");
    ustd2.WhoAreYou();
    
    return 0;
}
My name is Lee
I'm 22 years old
My major is Computer eng.

My name is Yoon
I'm 21 years old
My major is Electronic eng.

 추가적으로 접근제한의 기준은 클래스이다. 이는 UnivStudent 클래스의 멤버함수(또는 생성자) 내에서 Person 클래스에 private으로 선언된 멤버변수에 직접 접근이 불가능하다는 뜻이다. 직접 접근이 불가능하기 때문에 Person 클래스의 Public 영역에서 선언된 함수를 통해서 접근해야 한다. 

 

용어의 정리

 클래스와 관련된 용어를 다음과 같이 정리하겠다.

Person UnivStudent
상위 클래스 하위 클래스
기초(base) 클래스 유도(derived) 클래스
슈퍼(super) 클래스 서브(sub) 클래스
부모 클래스 자식 클래스

 

유도 클래스의 객체 생성과정

 유도 클래스의 객체 생성과정을 다음 예제를 통해 정리해보자.

#include <iostream>
using namespace std;

class SoBase
{
private:
    int baseNum;
public:
    SoBase() : baseNum(20)
    {
        cout<<"SoBase()"<<endl;
    }
    SoBase(int n) : baseNum(n)
    {
        cout<<"SoBase(n)"<<endl;
    }
    void ShowBaseData()
    {
        cout<<baseNum<<endl;
    }
};

class SoDerived : public SoBase
{
private:
    int derivNum;
public:
    SoDerived() : derivNum(30) 					// 기초 클래스의 생성자 호출 언급 x
    {
        cout<<"SoDerived()"<<endl;
    }
    SoDerived(int n) : derivNum(n) 				// 기초 클래스의 생성자 호출 언급 x
    {
        cout<<"SoDerived(int n)"<<endl;
    }
    SoDerived(int n1, int n2) : SoBase(n1), derivNum(n2) 	// 기초 클래스의 생성자 호출 직접 명시
    {
        cout<<"SoDerived(int n1, int n2)"<<endl;
    }
    void ShowDerivData()
    {
        ShowBaseData();
        cout<<derivNum<<endl;
    }
};

int main(void)
{
    cout<<"case1....."<<endl;
    SoDerived dr1;
    dr1.ShowDerivData();
    cout<<"----------------"<<endl;
    cout<<"case2....."<<endl;
    SoDerived dr2(12);
    dr2.ShowDerivData();
    cout<<"----------------"<<endl;
    cout<<"case3....."<<endl;
    SoDerived dr3(23, 24);
    dr3.ShowDerivData();

    return 0;
}
case1.....
SoBase()
SoDerived()		// 유도 클래스와 기초 클래스의 void 생성자 호출
20
30
----------------
case2.....
SoBase()
SoDerived(int n)	// 기초 클래스의 void 생성자 호출
20
12
----------------
case3.....
SoBase(n)
SoDerived(int n1, int n2)
23
24

 위 예제에서 낼 수 있는 결론은 두 가지가 있다.

  • 유도 클래스의 객체 생성 과정에서 기초 클래스의 생성자는 100% 호출된다.
  • 유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출된다.

 

유도 클래스 객체의 소멸과정

 유도 클래스의 객체 생성 과정에서 생성자가 두 번 호출됨을 통해서, 유도 클래스 객체의 소멸 과정에서 소멸자가 두 번 호출될 것임을 쉽게 예측할 수 있다. 

 

그렇다면 다음 예제를 통해서 해당 내용을 자세히 알아보자.

#include <iostream>
using namespace std;

class SoBase
{
private:
    int baseNum;
public:
    SoBase(int n) : baseNum(n)
    {
        cout<<"SoBase() : "<<baseNum<<endl;
    }
    ~SoBase()
    {
        cout<<"~SoBase() : "<<baseNum<<endl;
    }
};

class SoDerived : public SoBase
{
private:
    int derivNum;
public:
    SoDerived(int n) : SoBase(n), derivNum(n)
    {
        cout<<"SoDerived(int n) : "<<derivNum<<endl;
    }
    ~SoDerived()
    {
        cout<<"~SoDerived(int n) : "<<derivNum<<endl;
    }
};

int main(void)
{
    SoDerived drv1(15);
    SoDerived drv2(27);

    return 0;
}
SoBase() : 15
SoDerived(int n) : 15
SoBase() : 27
SoDerived(int n) : 27
~SoDerived(int n) : 27
~SoBase() : 27
~SoDerived(int n) : 15
~SoBase() : 15

 위 실행결과를 통해 우리는 다음 사일을 알 수 있다.

  • 유도 클래스 객체가 소멸될 때, 유도 클래스 소멸자가 먼저 실행되고 나서 기초 클래스 소멸자가 실행된다.
  • 스택에 생성된 객체의 소멸 순서는 생성 순서와 반대이다.

 그리고 이러한 특징 때문에 상속과 연관된 소멸자를 정의할 때, 생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제해야 한다.

 

 이번 글은 이와 관련된 예제를 하나 제시하고 마무리하겠다.

#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
    char * name;
public:
    Person(char * myname)
    {
        name = new char[strlen(myname)+1];      // strlen은 문자열의 실제 길이가 아닌 문자열의 개수만 return -> 문자열 뒤 \0 포함하지 않음.
        strcpy(name, myname);
    }
    ~Person()
    {
        delete []name;	// 기초 클래스 생성자에서 할당된 메모리 공간은 기초 클래스 소멸자에서 해제
    }
    void WhatYourName() const
    {
        cout<<"My name is "<<name<<endl;
    }
};

class UnivStudent : public Person
{
private:
    char * major;
public:
    UnivStudent(char * myname, char * mymajor)
        : Person(myname)
    {
        major = new char[strlen(mymajor)+1];
        strcpy(major, mymajor);
    }
    ~UnivStudent()
    {
        delete []major;	// 유도 클래스 생성자에서 할당한 메모리공간은 유도 클래스 소멸자에서 해제
    }
    void WhoAreYou() const
    {
        WhatYourName();
        cout<<"My name is "<<major<<endl<<endl;
    }
};

int main(void)
{
    UnivStudent st1("Kim", "Mathematics");
    st1.WhoAreYou();
    UnivStudent st2("Hong", "Physics");
    st2.WhoAreYou();
    
    return 0;
}
My name is Kim
My name is Mathematics

My name is Hong
My name is Physics

 


참고자료

  • 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12
  • https://erjuer.tistory.com/57
  • https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=youngsunkr&logNo=80024020959

 

728x90