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 << "My age is " << age << endl;
}
}
Person을 상속받는 UnivStudent 클래스를 보자.
class UnivStudent: public 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;
}
}
위 코드블록에는 두 가지 특징이 있다.
class UnivStudent: public Person
whatYourName()
, howOldAreYou()
첫 번째는 UnivStudent가 Person클래스를 public 상속했다는 말이다. public은 잠시 뒤로 미뤄두고 상속했다는 것에 주목하자.
UnivStudent 클래스에서 whatYourName()
, howOldAreYou()
를 호출할 수 있었던 이유는 Person 클래스를 상속했기 때문이다. 이 두 함수는 Person 클래스의 멤버함수이다.
상속을 하게 되면, 상속 받은 클래스(UnivStudent)의 객체 내에는 상속의 대상이 되는 클래스(Person)의 멤버까지 포함이 된다.
UnivStudent 객체 내부
상속받은 Person 클래스의 멤버
age
,name
,whatYourName()
,howOldAreYou()
UnivStudent 클래스의 멤버
major
,whoAreYou()
UnivStudent 클래스의 생성자는 다음과 같이 정의되어 있다.
UnivStudent(char *myname, int myage, char *mymajor)
: Person(myage, myname)
{
strcpy(major, mymajor);
}
Q: UnivStudent 클래스의 생성자는 Person 클래스의 멤버까지 초기화해야 할 의무가 있을까?
UnivStudent 클래스 내에 Person의 멤버변수도 존재하므로, UnivStudent의 생성자에서 Person 클래스의 멤버까지 초기화해야 한다.
Q: UnivStudent 클래스의 생성자가 Person 클래스의 멤버를 어떻게 초기화하는 것이 좋을까?
멤버는 클래스가 정의될 때, 멤버의 초기화를 목적으로 정의된 생성자를 통해 초기화하는 것이 가장 안정적이다(당시에 초기화의 내용 및 방법이 결정되므로). 그것이 비록 상속의 관계로 묶여있다 할지라도 말이다.
그러므로 UnivStudent클래스의 생성자는 Person클래스의 생성자를 호출해서 Person클래스의 멤버를 초기화하는 것이 좋다.
따라서 UnivStudent 클래스의 생성자는 멤버 이니셜라이저를 통해 Person 클래스의 생성자를 호출해서 Person의 멤버들을 초기화했다.
UnivStudent 클래스의 멤버함수(또는 생성자) 내에서 Person 클래스 내에 private으로 선언된 멤버 변수 age와 name에 접근이 가능할까?
만약 private의 접근제한이 객체를 기준으로 결정된 거라면, 접근이 가능할 것이다. UnivStudent 내에 UnivStudent의 멤버변수와 Person의 멤버변수가 함께 존제하기 때문이다. 하지만 접근제한의 기준은 클래스이다. 클래스 외부에서는 private 멤버에 접근이 불가능하다. 그러므로 Person 클래스에 정의된 public 함수를 통해 간접적으로 접근해야 한다.
언제까지 상속한 클래스, 상속 받은 클래스라고 쓸 수 없으니 정리를 하고 가자.
Person | UnivStudent |
---|---|
상위클래스 | 하위클래스 |
기초(base)클래스 | 유도(derived)클래스 |
부모클래스 | 자식클래스 |
앞으로는 기초 클래스와 유도 클래스라는 표현을 쓸 것이다.
(내용 추가 예정)
유도 클래스의 객체 생성 과정에서는 생성자가 두 번 호출된다. 하나는 기초 클래스의 생성자이고, 다른 하나는 유도 클래스의 생성자이다.
생성자를 호출하고, 클래스가 상속을 받는다면 멤버 이니셜라이저를 살피게 된다. 멤버 이니셜라이저를 통해 기초 클래스 생성자가 호출되면 이어서 유도 클래스의 생성자 실행이 완료된다.
클래스의 멤버는 해당 클래스의 생성자를 통해 초기화해야 한다.
객체 생성 과정에서 생성자가 두 번 호출됨을 알았으니, 소멸자도 두 번 호출됨을 유추할 수 있을 것이다.
class Base
{
private:
int num1;
protected:
int num2;
public:
int num3;
void showData()
{
cout << num1 << ", " << num2 << ", " << num3 << endl;
}
}
위와 같이 정의된 클래스를 상속하는 클래스를 보자.
class Derived
{
public:
void showBaseMember()
{
cout << num1; // 컴파일 에러
cout << num2; // 컴파일 OK
cout << num3; // 컴파일 OK
}
}
위 클래스는 Base 클래스를 상속하고 있다. 따라서 public으로 선언된 멤버변수 num3에 접근이 가능하지만, private으로 선언된 num1 에는 접근이 불가능하다. protected로 선언된 num2는 어떨까?
protected로 선언된 멤버변수는 이를 상속하는 유도 클래스에서 접근이 가능하다!
class Derived: protected Base
protected 상속이 의미하는 바는 다음과 같다.
protected보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다.
즉, 기초 클래스에 public으로 선언된 멤버 변수가 있다면, 그 변수는 상속 시 접근 범위가 protected로 변경되는 것이다.
만약 Base 클래스에 멤버변수 public num1
, protected num2
, public num3
이 있다면, Derived 클래스의 멤버는 다음과 같이 표현할 수 있다
Class Derived: protected
{
접근불가:
int num1;
protected:
int num2;
protected:
int num3;
}
실제로 접근 불가라는 키워드는 존재하지 않지만, 존재는 하되 접근이 불가능함을 의미하기 위해 위와 같이 표현했다.
class Derived: public Base
private에서도 protected 상속과 마찬가지로 다음과 같은 의미를 지닌다.
private보다 접근의 범위가 넓은 멤버는 private으로 변경시켜서 상속하겠다.
즉, 다음과 같이 표현할 수 있을 것이다.
Class Derived: protected
{
접근불가:
int num1;
private:
int num2;
private:
int num3;
}
만약 private으로 상속된 Derived를 상속하는 클래스는 다음과 같은 형태가 될 것이다.
Class DerivedDerived
{
접근불가:
int num1;
접근불가:
int num2;
접근불가:
int num3;
}
class Derived: public Base
위와 같이 public으로 상속된 클래스에서는 기초클래스의 protected와 public 멤버 변수에 접근이 가능하다.
즉, private은 다음과 같이 정리할 수 있을 것이다.
public 상속은 private을 제외한 나머지는 그냥 그대로 상속한다!