Inheritance : is-a relationship
Studnet is a Person
Teacher is a Person
-> Person
으로 일반화 시킬 수 있으므로, Person Class 를 새로 만들어주자.
Child Class 에서 Mother Class 를 include 하기 때문에, Mother Class 에서 선언을 해주면 굳이 Child Class 에서 선언할 필요 없음. 당연한 이야기
클래스는 별도의 헤더 파일을 만들어서 빼는 것이 좋다.
#include <bits/stdc++.h> //비추천. 편의상 작성
using namespace std; //비추천. 편의상 작성
//Person Class
class Person {
private:
std::string m_name;
public:
Person(const std::string& name_in = "No name") : m_name(name_in) {}
void setName(const string& name_in) {
//의도적인 막음.
m_name = name_in; //Person 에서 public 변수가 아니니까 당연함.
}
// const 선언 이유. Child 클래스에서 param 으로 받아오는데 param에서 const로 불러오고 있음.
string getName() const {
return m_name;
}
};
//Student Class
class Student : public Person {
private:
int m_intel;
public:
// 생성자에서 initialize 할 떄 에러가 남. m_name 은 Person 의 member variable.
// Student에서 Person 의 constructor를 초기화하는식으로 해야함
Student(const string& name_in = "no name", const int& intel_in = 0)
// : m_name(name_in), m_intel(intel_in) {}
: Person(name_in), m_intel(intel_in) {} //Studnet 생성자를 통해서 Person 생성자를 호출
// void setName(const string& name_in) {
// //의도적인 막음. Person의 m_name 을 자식에서 접근하지 않도록
// m_name = name_in; //Person 에서 public 변수가 아니니까 당연함.
// }
void setIntel(const int& intel_in) {
m_intel = intel_in;
}
int getIntel() const {
return m_intel;
}
friend ostream& operator << (ostream& out, const Student& student) {
// getName이 mother. Stduent 을 const로 params로 받아오므로, getName() 뒤에 const 선언
out << student.getName() << " " << student.getIntel();
// out << student.m_name << " " << student.m_intel;
return out;
}
void study() {
cout << getName() << " is studying" << endl;
}
};
//Teacher Class
class Teacher : public Person {
private:
public:
Teacher(const string& name_in = "no name")
: Person(name_in) {
}
friend ostream& operator << (ostream& out, const Teacher& teacher) {
out << teacher.getName(); //직접 접근 불가능
return out;
}
void teach() {
cout << getName() << " is teaching" << endl;
}
};
Child 클래스인 Teacher, Student에서 name을 초기화하고자 할 때, parent 클래스인 Person 클래스의 생성자를 직접 호출하는 식으로 초기화를 진행해준다. 이것에 대한 설명은 다음 Chapter에서 더 자세하게 다룰 것. (생성자 순서 때문 )
Student(const string& name_in = "no name", const int& intel_in = 0)
: Person(name_in), m_intel(intel_in) {}
항상 Mother class 생성 후, Child class가 생성된다.
11.2 에서 살펴봤듯이, initializer 에서 mother class에 있는 멤버를 초기화 하는 것은 불가능 했다. 그 이유는, Child class의 initializer가 진행될 시점에서 Mother 클래스의 생성자가 호출되기 때문이다. 즉, 우리 눈에
Child() : m_d(1.0) {}
라고 보이는 것이 사실은
Child() : Mother(), m_d(1.0) {}
인 것이다. 생성자의 {}
내에서 Mother class의 멤버 변수를 호출하는 것은 됐지만, initializer 에서 호출하는 것은 안되는 이유이다.
class A {
public:
A() { cout << " A Constructor " << endl;}
};
class B : public A {
public:
B() { cout << " B Constructor " << endl;}
};
class C : public B {
public:
C() { cout << " C Constructor " << endl;}
};
A Constructor
B Constructor
C Constructor
이 경우, Constructor
는 당연히 A
먼저 생성되고, 차례대로 B
, C
생성된다. Destructor
는 Constructor
의 역순이다.
Child class 가 생성될 때, Child 변수 + Mother 에 선언된 것 만큼 메모리 사이즈를 할당받는다.
Base
Class 가 다음과 같이 있다고 예시를 들어보자.
class Base {
public:
int m_public;
// protected 는 child 만 접근 가능
protected:
int m_protected;
private:
int m_private;
};
class Derived : private Base
인 경우Derived class 내에서 Base class의 public, private 변수에 접근하는 것은 가능하지만, GrandChild 클래스, 즉 Derived Class 를 상속받는 클래스에서는 Base의 모든 변수 접근이 불가능하다.
class Derived : private Base {
public:
int d_int;
Derived() {
m_public = 123;
m_protected = 321;
}
};
class GrandChild : public Derived {
public:
GrandChild() {
d_int = 123;
// Derived::m_public = 123; 불가
// Derived::m_protected = 123 ; 불가
}
};
class Derived : protected Base
인 경우클래스 내부에서는 Base 클래스의 public, protected 변수까지 접근이 가능하지만, 외부 (main) 의 경우, 접근이 모두 불가능하다.
Skip
class Base {
private:
int m_val;
public:
Base(int val_) : m_val(val_) {}
void print() {
cout << "I'm base" << endl;
}
// output operator overrloading
friend ostream& operator << (ostream& out, const Base& b) {
cout << "this is base output" << endl;
return out;
}
};
class Derived : public Base {
private:
double m_d;
public:
Derived(int val_) : Base(val_) {}
// override 하고 싶을 때.
void print() {
cout << "I'm Derived" << endl;
}
// output operator overrloading
friend ostream& operator << (ostream& out, const Derived& b) {
//base operator 를 불러오고 싶을 때
//이게 왜 되냐? 메모리가 사이즈가 있으면,Base + Derived니까
//Base에 대한 내용을 Derived가 갖고 있기 때문에 가능함!
cout << static_cast<Base>(b);
cout << "this is derived output" << endl;
return out;
}
};
int main() {
Base base(5);
// base.print();
cout << base;
Derived derived(7);
// derived.print();
cout << derived;
}
상속 받은 변수를 클래스 내부에서 public
으로 사용하는 것이 가능하다
// m_i 가 Derived 안에서 public이 되고, 외부에서 변경 가능함.
using Base::m_i;
반대로, 상속받은 함수를 사용하지 못하게 막을 수도 있다.
//방법 1. private 선언 후, 작성
private:
using Base::print;
//방법 2. delete
private:
void print() = delete;
2개 이상의 클래스를 상속받는 것을 말한다.
class A 가 class B, C 를 상속받은 상황인데, B와 C에 모두 getID()
함수가 있고, getID 함수를 호출해야 할 경우,
A a("test)";
a.B::getID();
a.C::getID();
이런 식으로 호출하면 된다.
다음과 같은 상황일 경우를 다이아몬드 상속이라고 부른다. 다중 상속을 잘못 사용하면 다음과 같은 문제가 생길 수 있다.
문제점에 대한 해결 방법은 다형성 통해서 살펴보자.