11. 상속
11.1 상속의 기본(1) Inheritance (is-a relationship)
#include <iostream>
using namespace std;
class Mother
{
private:
int m_i;
public:
Mother(const int& i_in)
: m_i(i_in)
{
cout << "Mother constructor" << endl;
}
Mother()
: m_i(0)
{
cout << "Mother constructor" << endl;
}
int getValue()
{
return m_i;
}
void setValue(int i_in)
{
m_i = i_in;
}
};
class Child : public Mother
{
};
int main()
{
Mother mother;
mother.setValue(1024);
cout << mother.getValue() << endl;
Child child;
child.setValue(123);
cout << child.getValue() << endl;
return 0;
}
Child가 Mother의 모든 멤버 및 기능을 다 재사용할 수 있다.
Chile class is derived class from Mother class.
Mother class is generalized class.
#include <iostream>
using namespace std;
class Mother
{
private:
int m_i;
public:
Mother(const int& i_in)
: m_i(i_in)
{
cout << "Mother constructor" << endl;
}
Mother()
: m_i(0)
{
cout << "Mother constructor" << endl;
}
int getValue()
{
return m_i;
}
void setValue(int i_in)
{
m_i = i_in;
}
};
class Child : public Mother
{
private:
double m_d;
public:
void setValue(const double& d_in)
{
m_d = d_in;
}
double getValue()
{
return m_d;
}
};
int main()
{
Mother mother;
mother.setValue(1024);
cout << mother.getValue() << endl;
Child child;
child.setValue(123.123);
cout << child.getValue() << endl;
return 0;
}#include <iostream>
using namespace std;
class Mother
{
private:
int m_i;
public:
Mother(const int& i_in)
: m_i(i_in)
{
cout << "Mother constructor" << endl;
}
Mother()
: m_i(0)
{
cout << "Mother constructor" << endl;
}
int getValue()
{
return m_i;
}
void setValue(int i_in)
{
m_i = i_in;
}
};
class Child : public Mother
{
private:
double m_d;
public:
void setValue(const double& d_in)
{
m_d = d_in;
}
double getValue()
{
return m_d;
}
};
int main()
{
Mother mother;
mother.setValue(1024);
cout << mother.getValue() << endl;
Child child;
child.setValue(123);
cout << child.getValue() << endl;
return 0;
}
메소드가 겹친다면 자식 class에 있는 것을 우선적으로 사용한다.
#include <iostream>
using namespace std;
class Mother
{
protected: //protected는 상속 관계에선 접근이 가능하도록 허용한다.
//private일 경우 상속받은 클래스에서도 접근 불가하다.
int m_i;
public:
Mother(const int& i_in)
: m_i(i_in)
{
cout << "Mother constructor" << endl;
}
Mother()
: m_i(0)
{
cout << "Mother constructor" << endl;
}
int getValue()
{
return m_i;
}
void setValue(int i_in)
{
m_i = i_in;
}
};
class Child : public Mother
{
private:
double m_d;
public:
Child(const int& i_in, const double& d_in)
// :m_i(i_in), m_d(d_in) //불가능.
// 생성자는 메모리를 초기에 할당받는 것이지,
//이미 존재하는 메모리에 값을 복사하기 위함이 아님.
//m_i(i_in)이 안 되는 이유는 자식 생성자가 호출될 때
//m_i를 위한 메모리는 할당이 되지 않기 때문.
{
Mother::setValue(i_in);
m_d = d_in;
}
void setValue(const int& i_in, const double& d_in)
{
//m_i = i_in; //Mother의 m_i 멤버가 private일 때는 접근 불가
//멤버가 protected일 경우엔 가능.
Mother::setValue(i_in); //Mother 클래스의 setValue를 확실히 지정해서 호출
m_d = d_in;
}
void setValue(const double& d_in)
{
m_d = d_in;
}
double getValue()
{
return m_d;
}
};
int main()
{
Mother mother;
mother.setValue(1024);
cout << mother.getValue() << endl;
Child child(1024, 128);
/* child.setValue(123.123); */
//child.Mother::setValue(1); //도 가능.
cout << child.Mother::getValue() << endl;
cout << child.getValue() << endl;
return 0;
}
Child 클래스는 생성되면서 Mother 클래스의 생성자를 같이 호출한다.
Child(const int& i_in, const double& d_in)
: Mother(i_in), m_d(d_in)
{
}
디폴트 생성자를 만들어 놓는 것이 편하다. (다른 생성자를 만들면 디폴트 생성자가 사라지기 때문)
Child에서도 Mother의 생성자를 이용해 초기화하는 것이 편하다.
11.2 상속의 기본(2)
source.cpp
#include "Student.h"
#include "Teacher.h"
using namespace std;
int main()
{
Student std("Jack Jack");
std.setName("Jack Jack 2");
cout << std << endl;
Teacher teacher1("Dr. H");
cout << teacher1 << endl;
std.doNothing();
teacher1.doNothing();
std.study();
teacher1.teach();
return 0;
}
Person.h
#pragma once
#include <iostream>
#include <string>
using namespace std;
class Person
{
private:
std::string m_name;
public:
Person(const std::string& name_in = "No Name")
: m_name(name_in)
{}
void setName(const std::string& name_in)
{
m_name = name_in;
}
std::string getName() const
{
return m_name;
}
void doNothing() const
{
cout << m_name<< " is doing nothing " << endl;
}
};
Teacher.h
#pragma once
#include "Person.h"
class Teacher : public Person
{
private:
public:
Teacher(const std::string& name_in = "No Name")
: Person(name_in)
{}
void teach() const
{
cout << getName() << " is teaching " << endl;
}
friend std::ostream& operator << (std::ostream& out, const Teacher& teacher)
{
out << teacher.getName();
return out;
}
};
Student.h
#pragma once
#include "Person.h"
class Student : public Person
{
private:
int m_intel; //intelligence;
public:
Student(const std::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)
{
}
void setIntel(const int& intel_in)
{
m_intel = intel_in;
}
int getIntel()
{
return m_intel;
}
void study() const
{
cout << getName() << " is studying " << endl;
}
friend std::ostream& operator << (std::ostream& out, const Student& student)
{
out << student.getName() << " " << student.m_intel;
return out;
}
};
11.3 유도된 클래스들의 생성 순서
#include <iostream>
using namespace std;
class Mother
{
public:
int m_i;
};
class Child : public Mother
{
public:
Child()
// : m_i(1024) //생성자로 초기화가 안 됨.
{
//this->m_i = 10;
//this-Mother::m_i = 1024;
}
};
int main()
{
return 0;
}
#include <iostream>
using namespace std;
class Mother
{
public:
int m_i;
public:
Mother()
: m_i(1)
{
cout << "Mother construction " << endl;
}
};
class Child : public Mother
{
public:
double m_d;
public:
Child()
: m_d(1.0)
{
cout << "Child construction" << endl;
}
};
int main()
{
Child c1;
return 0;
}
Mother costructor가 호출되고 그 다음 Child가 호출된다.
Child()
: Mother(), m_d(1.0)
처럼 Mother의 생성자가 Child 생성자에 숨어 있는 것.
#include <iostream>
using namespace std;
class Mother
{
public:
int m_i;
public:
Mother(const int & i_in = 0) //기본값 넣어주면 default constructor
//문제까지 한 방에 해결됨
: m_i(i_in)
{
cout << "Mother construction " << endl;
}
};
class Child : public Mother
{
public:
double m_d;
public:
Child()
: Mother(1024), m_d(1.0)
{
cout << "Child construction" << endl;
}
};
int main()
{
Child c1;
return 0;
}
#include <iostream>
using namespace std;
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;
}
};
int main()
{
C c;
return 0;
}
이렇게 상속을 여러 번 받을 수 있다.
C 생성에서 A->B->C 생성자 순으로 호출됨.
11.4 유도된 클래스들의 생성과 초기화
#include <iostream>
using namespace std;
class Mother
{
public:
int m_i;
public:
Mother(const int& i_in = 0)
: m_i(i_in)
{
cout << "Mother construction " << endl;
}
};
class Child : public Mother
{
private:
float m_d;
public:
Child()
: m_d(1.0f), Mother(1024)
{
cout << "Child construction " << endl;
}
};
int main()
{
Child c1;
cout << sizeof(Mother) << endl;
cout << sizeof(Child) << endl;
return 0;
}
Child에서 메모리를 할당할 때는 Mother 클래스의 것도 다 담을 수 있을만큼 크게 할당받는다.
#include <iostream>
using namespace std;
class A
{
public:
A(int a)
{
cout << "A: " << a << endl;
}
~A()
{
cout << "Destuctor A" << endl;
}
};
class B : public A
{
public:
B(int a, double b)
: A(a)
{
cout << "B: " << b << endl;
}
~B()
{
cout << "Destuctor B" << endl;
}
};
class C : public B
{
public:
C(int a, double b, char c)
: B(a, b)
{
cout << "C: " << c << endl;
}
~C()
{
cout << "Destuctor C" << endl;
}
};
int main()
{
C c(1024, 3.14, 'a');
return 0;
}
소멸자 호출 순서
11.5 상속과 접근 지정자
#include <iostream>
using namespace std;
class Base
{
public:
int m_public;
protected:
int m_protected;
private:
int m_private;
};
class Derived : public Base
{
public:
Derived()
{
m_public = 123;
m_protected = 1234;
}
};
int main()
{
Base base;
base.m_public = 123;
return 0;
}
#include <iostream>
using namespace std;
class Base
{
public:
int m_public;
protected:
int m_protected;
private:
int m_private;
};
class Derived : protected Base
{
public:
Derived()
{
Base::m_public;
Base::m_protected;
}
};
int main()
{
Derived d;
// d.m_public; //불가
return 0;
}
#include <iostream>
using namespace std;
class Base
{
public:
int m_public;
protected:
int m_protected;
private:
int m_private;
};
class Derived : private Base
{
public:
Derived()
{
Base::m_public;
Base::m_protected;
}
};
class GrandChild : public Derived
{
public:
GrandChild()
{
/*Derived::m_public;
Derived::m_protected;*/ //불가
}
};
int main()
{
Derived d;
// d.m_public; //불가
return 0;
}
상속 시 접근 지정자를 설정하면, 그 지정자보다 더 안전하지 않은 멤버가 그 접근 지정자처럼 지정된다.
ex. private 부모 클래스로 상속 받으면, public한 멤버라도 private처럼 작동한다.
11.6 유도된 클래스에 새로운 기능 추가하기
#include <iostream>
using namespace std;
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
void setValue(int value)
{
Base::m_value = value;
//do some work with the variables defined in Derived.
//protected로 하면 자식 클래스에서
//부모 클래스의 멤버에 접근 가능.
}
};
int main()
{
// d.m_public; //불가
return 0;
}
11.7 상속받은 함수Inherited Functions를 오버라이딩Overriding하기.
#include <iostream>
using namespace std;
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
void print()
{
cout << "I'm base" << endl;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
void print()
{
cout << "I'm derived" << endl;
}
};
int main()
{
Base base(5);
base.print();
Derived derived(7);
derived.print();
return 0;
}
#include <iostream>
using namespace std;
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
void print()
{
cout << "I'm base" << endl;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
void print()
{
Base::print();
cout << "I'm derived" << endl;
}
};
int main()
{
Base base(5);
base.print();
Derived derived(7);
derived.print();
return 0;
}
#include <iostream>
using namespace std;
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
void print()
{
cout << "I'm base" << endl;
}
friend std::ostream& operator << (std::ostream& out, const Base& b)
{
out << "This is base output" << endl;
return out;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
void print()
{
Base::print();
cout << "I'm derived" << endl;
}
friend std::ostream& operator << (std::ostream& out, const Derived &d)
{
out << "This is derived output" << endl;
return out;
}
};
int main()
{
Base base(5);
cout << base;
Derived derived(7);
cout << derived;
return 0;
}
#include <iostream>
using namespace std;
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
void print()
{
cout << "I'm base" << endl;
}
friend std::ostream& operator << (std::ostream& out, const Base& b)
{
out << "This is base output" << endl;
return out;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
void print()
{
Base::print();
cout << "I'm derived" << endl;
}
friend std::ostream& operator << (std::ostream& out, const Derived &d)
{
cout << static_cast<Base>(d);
out << "This is derived output" << endl;
return out;
}
};
int main()
{
Base base(5);
cout << base;
Derived derived(7);
cout << derived;
return 0;
}
Derived로 Base를 캐스팅하면, Base의 함수로 나온다.
11.8 상속 받은 함수를 감추기
#include <iostream>
using namespace std;
class Base
{
protected:
int m_i;
public:
Base(int value)
: m_i(value)
{
}
void print()
{
cout << "I'm base" << endl;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
using Base::m_i;
};
int main()
{
Derived derived(7);
derived.m_i = 1024;
return 0;
}
유도된 클래스에서 부모 클래스의 멤버를 public으로 바꿀 수 있다.
#include <iostream>
using namespace std;
class Base
{
protected:
int m_i;
public:
Base(int value)
: m_i(value)
{
}
void print()
{
cout << "I'm base" << endl;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
using Base::m_i;
private:
using Base::print; //do not add ()
};
int main()
{
Derived derived(7);
derived.m_i = 1024;
//derived.print() //불가
return 0;
}
함수도 접근 지정자를 바꿀 수 있다.
혹은 void print() = delete; 를 하면 부모의 기능을 삭제할 수 있다.
11.9 다중 상속 Multiple inheritance
#include <iostream>
using namespace std;
class USBDevice
{
private:
long m_id;
public:
USBDevice(long id) : m_id(id) {}
long getID() { return m_id; }
void plugAndPlay() {}
};
class NetworkDevice
{
private:
long m_id;
public:
NetworkDevice(long id) : m_id(id) {}
long getID() { return m_id; }
void networking() {}
};
class USBNetworkDevice : public USBDevice, public NetworkDevice
{
public:
USBNetworkDevice(long usb_id, long net_id)
: USBDevice(usb_id), NetworkDevice(net_id)
{}
};
int main()
{
USBNetworkDevice my_device(3.14, 6.022);
my_device.networking();
my_device.plugAndPlay();
my_device.USBDevice::getID();
my_device.NetworkDevice::getID();
return 0;
}
위처럼 여러 개를 상속받을 수 있지만, 메소드가 겹치는 경우 어떤 부모 클래스에 지정되어 있던 메소드인지
명기해줄 필요가 있다.
다이아몬드 상속이 문제가 될 수 있다.
'개발 공부 > C++' 카테고리의 다른 글
따라하며 배우는 C++ 13. 템플릿 (0) | 2020.07.02 |
---|---|
따라하며 배우는 C++ 12. 가상 함수들 (0) | 2020.07.02 |
따라하며 배우는 C++ 10. 객체 사이의 관계들에 대해 (0) | 2020.07.02 |
따라하며 배우는 C++ 9. 연산자 오버로딩 (0) | 2020.07.02 |
따라하며 배우는 C++ 8. 객체지향의 기초 (0) | 2020.07.01 |