출처 :
- 명품 C++ Programming (저자 황기태)
- 객체지향프로그래밍
- 블뎁블뎁
C++ 에서 상속 inheritance
- 클래스 사이의 상속, 객체 사이에는 상속 관계 없음
- 기본 클래스의 속성과 기능을 파생 클래스에 물려줌
- base class - derived class
- 기본 클래스의 속성과 기능 + 자신만의 속성과 기능
- 파생 클래스로 갈수록 클래스의 개념이 쿠체화
- 다중 상속으로 클래스의 재활용성을 높임
- 자바는 다중 상속이 없고 인터페이스 개념이 있음
상속의 목적 및 장점
- 간결한 클래스 작성
- 클래스 간의 계층적 분류 및 관리의 용이함
- 상속은 클래스들의 구조적 관계 파악이 용이 하다
- 클래스 재사용과 확장을 통한 소프트웨어 생산성 향상
- 앞으로 있을 상속에 대비해 클래스를 객체 지향적으로 설계 해야함
- 다형성 - function overriding
상속 선언
// 파생클래스명:상속접근지정 기본클래스명
class Student: public Person {
...
}
point 클래스를 상속받는 ColorPoint 클래스 만들기
#include <iostream>
#include <string>
using namespace std;
class Point {
int x, y;
public:
void set(int x, int y) { this->x = x; this->y = y; }
void showPoint() {
cout << "(" << x << "," << y <<")" << endl;
}
};
class ColorPoint: public Point {
string color;
public:
void setColor(string color) { this->color = color; }
void showColorPoint();
};
void ColorPoint::showColorPoint() {
cout << color << ";";
// 파생클래스에서 기본 클래스 멤버 호출
showPoint();
// x, y에 접근가능하다? No! private 멤버 접근 불가능
}
int main() {
Point p;
ColorPoint cp;
// 파생클래스에서 기본클래스 멤버함수 호출
cp.set(3,4);
cp.setColor("Red");
cp.showColorPoint();
업 캐스팅 up-casting
- 파생 클래스 포인터가 기본 클래스 포인터에 치환되는것
int main() {
ColorPoint cp;
ColorPoint * pDer = &cp;
// ColorPoint의 주소를 담고 있지만 Point로만 봄
// ColorPoint 멤버에 접근 불가능
Point * pBase = pDer;
pDer->set(3,4);
pBase->showPoint();
pDer->setColor("Red");
pDer->showColorPoint();
// 컴파일 오류 - 업캐스팅한 pBase로 파생클래스 ColorPoint에 접근불가능
pBase->showColorPoint();
다운 캐스팅 down-casting
- 기본 클래스의 포인터가 파생 클래스의 포인터에 치환되는것
int main() {
ColorPoint cp;
ColorPoint * pDer;
Point * pBase = &cp;
// 다운캐스팅 - ColorPoint 포인터에 넣기 위해 강제 타입 변환
pDer = (ColorPoint *) pBase;
PDer->setColor("Red"); // 정상 컴파일
pDer->shokwColorPoint(); // 정상 컴파일
// pDer로 private멤버인 color에는 접근 불가능
접근 지정자
- private 멤버
- 선언된 클래스 내에서만 접근가능
- 파생 클래스에서도 기본 클래스의 private 멤버 직접 접근 불가
- public 멤버
- 선언된 클래스나 외부 어떤 클래스, 모든 외부 함수에 접근 허용
- 파생 클래스에서 기본 클래스의 public 멤버 접근 가능
- protected 멤버
- 선언된 클래스에서 접근 가능
- “파생 클래스에서만” 접근 허용
protected 멤버에 대한 접근
#include <iostream>
#include <string>
using namespace std;
class Point {
protected:
int x, y; //한 점 (x,y) 좌표값
public:
void set(int x, int y);
void showPoint();
};
void Point::set(int x, int y) {
this->x = x;
this->y = y;
}
void Point::showPoint() {
cout << "(" << x << "," << y << ")" << endl;
}
class ColorPoint : public Point {
string color;
public:
void setColor(string color);
void showColorPoint();
bool equals(ColorPoint p);
};
void ColorPoint::setColor(string color) {
this->color = color;
}
void ColorPoint::showColorPoint() {
cout << color << ":";
showPoint(); // Point 클래스의 showPoint() 호출
}
bool ColorPoint::equals(ColorPoint p) {
if(x == p.x && y == p.y && color == p.color) // ①
return true;
else
return false;
}
int main() {
Point p;
p.set(2,3);
p.x = 5; // protected 접근불가능
p.y = 5; // protected 접근불가능
p.showPoint();
ColorPoint cp; // 파생 클래스의 객체 생성
cp.x = 10; // main은 클래스의 외부 protected 접근불가능
cp.y = 10; // protected 접근불가능
cp.set(3,4);
cp.setColor("Red");
cp.showColorPoint();
}
상속 관계의 생성자와 소멸자 실행
- 파생 클래스의 객체가 생성될 때 파생 클래스의 생성자와 기본클래스의 생성자가 모두 실행된다.
- 기본 클래스의 생성자가 먼저 실행된 후 파생 클래스의 생성자가 실행된다.
- 파생 클래스의 소멸자가 먼저 실행된 후 기본 클래스의 소멸자가 실행된다.
class A {
public:
A() { cout << "생성자 A" << endl; }
~A() { cout << "소멸자 A" << endl; }
};
class B : public A {
public:
B() { cout << "생성자 B" << endl; }
~B() { cout << "소멸자 B" << endl; }
};
class C : public B {
public:
// 묵시적으로 기본 클래스의 기본 생성자를 호출하도록 컴파일됨.
C() { cout << "생성자 C" << endl; }
~C() { cout << "소멸자 C"<< endl; }
};
int main() {
C c;
return 0;
}
// 생성자A 생성자B 생성자C 소멸자C 소멸자B 소멸자A
// 생성은 기본 클래스부터! 소멸은 생성의 역순
기본 클래스에 기본 생성자가 없는경우
- 파생 클래스에 “기본 생성자”가 있으면 기본 클래스에도 “기본 생성자”가 있어야 한다.
class A {
public:
// 파생 클래스에서 기본 생성자를 호출했는데
// 기본 생성자가 없음 - 컴파일 오류!
A(int x) {
cout << "매개변수생성자 A" << x << endl;
}
};
class B : public A {
public:
B() {
// 기본 생성자 A() 호출하도록 컴파일됨
cout << "생성자 B" << endl;
}
};
매개 변수를 가진 파생 클래스의 생성자는 묵시적으로 기본 클래스의 기본 생성자 선택
- 파생 클래스의 매개 변수를 가진 생성자가 묵시적으로 기본 클래스의 기본 생성자 호출
- 매개변수가 있는 생성자를 호출해도 별다른 지시가 없으면 A의 기본 생성자 호출
- 컴파일러가 묵시적으로 B() : A() 로 기본 생성자를 위임생성자로 삽입함
class A {
public:
A() { cout << "생성자 A" << endl; }
A(int x) { cout << "매개변수생성자 A" << x << endl; }
};
class B : public A {
public:
// A() 호출하도록 컴파일됨
B() { cout << "생성자 B" << endl }
// A() 호출하도록 컴파일됨
// 매개변수가 있는 생성자를 호출해도 별다른 지시가 없으면 A의 기본 생성자 호출
B(int x) { cout << "매개변수생성자 B" << x << endl; }
};
파생 클래스의 생성자에서 “명시적으로” 기본 클래스의 생성자 선택
- 위임 생성자 사용 B(int x): A(x + 3)
class A {
public:
A() { cout << "생성자 A" << endl; }
A(int x) { cout << "매개변수생성자 A" << x << endl; }
};
class B : public A {
public:
// A() 호출하도록 컴파일됨
B() { cout << "생성자 B" << endl }
// 파생 클래스의 생성자가 명시적으로 기본 클래스의 생성자를 선택하여 호출함
B(int x): A(x + 3) {
cout << "매개변수생성자 B" << x << endl;
}
};
TV, WideTV, SmartTV 생성자 매개 변수 전달
#include <iostream>
#include <string>
using namespace std;
class TV {
int size;
public:
TV() { size = 20; }
TV(int size) { this->size = size; }
int getSize() { return size; }
};
class WideTV : public TV {
bool videoIn;
public:
WideTV(int size, bool videoIn) : TV(size) {
this->videoIn = videoIn;
}
bool getVideoIn() { return videoIn; }
};
class SmartTV : public WideTV {
string ipAddr;
public:
// SmartTV 생성자가 호출되면 TV - WideTV - SmartTV 순으로 생성자 실행
SmartTV(string ipAddr, int size) : WideTV(size, true) {
this->ipAddr = ipAddr;
}
string getIpAddr() { return ipAddr; }
};
int main() {
// 32 인치 크기에 "192.0.0.1"의 인터넷 주소를 가지는 스마트 TV 객체 생성
SmartTV htv("192.0.0.1", 32);
cout << "size=" << htv.getSize() << endl;
cout << "videoIn=" << boolalpha << htv.getVideoIn() << endl;
cout << "IP="htv.getIpAddr() << endl;
}
상속 지정 (중요!!!)
- 상속 선언 시 public, private, protected 중 하나 지정
- 기본 클래스의 멤버의 접근 속성을 어떻게 계승할 지 지정
- public - 기본 클래스의 protected, public 멤버 속성을 그대로 계승
- private - 기본 클래스의 protected, public 멤버 속성을 그대로 계승
- A가 B를 private 상속 - B내부에서는 A의 protected, public 그대로 사용가능
- 단, B를 상속받은 C에서는 A에 접근 불가능
- 왜? A의 protected, public이 B에서는 private이 되어버려서
- protected - 기본 클래스의 protected, public 멤버를 protected로 계승
private 상속 사례
class Base {
int a;
protected:
void setA(int a) { this->a = a; }
public:
void showA() { cout << a; }
};
class Derived : private Base {
int b;
protected:
void setB(int b) { this->b = b; }
public:
void showB() { cout << b; }
};
int main() {
Derived x;
x.a = 5; // 기본 클래스 private 접근불가능
x.setA(10); // private으로 계승하여 외부에서 접근불가능
x.showA(); // private으로 계승하여 외부에서 접근불가능
x.b = 10; // private은 외부 접근불가능
x.setB(10); // protected는 외부 접근 불가능
x.showB(); // 가능
}
protected 상속 사례
class Base {
int a;
protected:
void setA(int a) { this->a = a; }
public:
void showA() { cout << a; }
};
class Derived : protected Base {
int b;
protected:
void setB(int b) { this->b = b; }
public:
void showB() { cout << b; }
};
int main() {
Derived x;
x.a = 5; // 기본 클래스 private 접근불가능
x.setA(10); // protected로 계승하여 외부에서 접근불가능
x.showA(); // protected로 계승하여 외부에서 접근불가능
x.b = 10; // private은 외부 접근불가능
x.setB(10); // protected는 외부 접근 불가능
x.showB(); // 가능
}
상속이 중첩될 때 접근 지정 사례 (시험 문제!)
class Base {
int a;
protected:
void setA(int a) { this->a = a; }
public:
void showA() { cout << a; }
};
class Derived : private Base {
int b;
protected:
void setB(int b) { this->b = b; }
public:
void showB() {
setA(5); // protectd이므로 파생 클래스내에서는 접근가능
showA(); // public이므로 접근간으
cout << b;
}
};
class GrandDerived : private Derived {
int c;
protected:
void setAB(int x) {
setA(x); // Derived에서 private이므로 접근불가능
showA(); // Derived에서 private이므로 접근불가능
setB(x); // 아직은 public이므로 클래스 내에서는 접근가능
}
};
다중 상속 선언 및 멤버 호출 (시험문제)
class Adder {
protected:
int add(int a, int b) { return a+b; }
};
class Subtractor {
protected:
int minus(int a, int b) { return a-b; }
};
// 다중 상속 - 상속받고자 하는 기본 클래스를 나열한다.
class Calculator : public Adder, public Subtractor {
public:
int calc(char op, int a, int b);
};
//캡슐화
int Calculator::calc(char op, int a, int b) {
int res=0;
switch(op) {
// protected이므로 파생 클래스에서 접근가능
case '+' : res = add(a, b); break;
case '-' : res = minus(a, b); break;
}
return res;
}
int main() {
Calculator handCalculator;
// 객체 외부에서는 add(), minus()에 직접 접근 불가능
// Calculator를 통해서 add(), minus()에 접근 = 캡슐화
cout << "2 + 4 = "
<< handCalculator.calc('+', 2, 4) << endl;
cout << "100 - 8 = "
<< handCalculator.calc('-', 100, 8) << endl;
}
다중 상속의 문제점 - 기본 클래스 멤버의 중복 상속
- Base의 멤버가 이중으로 객체에 삽입되는 문제
- 동일한 x를 접근하는 프로그램이 서로 다른 x에 접근하는 결과를 낳게되어 실행 오류
가상 상속
- 다중 상속으로 인한 기본 클래스 멤버의 중복 상속 해결
- 파생 클래스의 선언문에서 기본 클래스 앞에 virtual 선언
- 파생 클래스의 객체가 생성될 때 기본 클래스의 멤버는 오직 한번만 생성
- 기본 클래스이 멤버가 중복하여 생성되는 것을 방지
class In : virtual public BaseIO
'[전공]' 카테고리의 다른 글
[명품 C++] 10 템플릿과 표준 템플릿 라이브러리 STL (0) | 2022.06.06 |
---|---|
[명품 C++] 9장 가상 함수와 추상 클래스 (1) | 2022.06.06 |
[명품 C++] 07 프렌드와 연산자 중복 (0) | 2022.06.05 |
[명품 C++] 6장 함수 중복과 static 멤버 (3) | 2022.06.05 |
[명품 C++] 05 함수와 참조, 복사 생성자 (6) | 2022.06.05 |