[문과 코린이의 IT 기록장] C,C++ - 상속 2 (protected로 선언된 맴버가 허용하는 접근의 범위, 3가지 형태의 상속, 상속을 위한 조건)
[문과 코린이의 IT 기록장] C,C++ - 상속 2
(protected로 선언된 맴버가 허용하는 접근의 범위, 3가지 형태의 상속, 상속을 위한 조건)
1. protected로 선언된 맴버가 허용하는 접근의 범위
- C++의 접근 제어 지시자
(허용범위 좁음) private < protected < public (허용범위 넓음) |
ex )
class A{
private : // 클래스 내부에서 접근 가능
int num1;
protected : // 클래스 내부에서 접근 가능
int num2;
public :
int num3;
void ShowData(){
cout<<num1<<", "<<num2<<", "<<num3;
}
};
class B : public A{
public:
void ShowAMember(){
cout<<num1; // 컴파일 에러 ( private 맴버는 유도 클래스에서 접근이 불가능하다. )
cout<<num2; // 컴파일 가능 ( portected 맴버는 유도 클래스에서 접근이 가능하다. )
cout<<num3; // 컴파일 가능
}
};
- 즉 protected 맴버는, 유도 클래스에서만 제한적으로 접근을 허용한다.
2. 3가지 형태의 상속
1) public 상속
class B : public A { ... }
- A클래스는, public으로 상속되었다.
- public 상속은, 'public보다 접근의 범위가 넓은 맴버는 public으로 변경시켜서 상속하겠다'라는 뜻이다.
* 즉, private를 제외한 나머지는 그냥 그대로 상속한다는 뜻이다.
2) protected 상속
class B : protected A { ... }
- A클래스는, protected로 상속되었다.
class A{ class B : protected A{ }; |
- protected 상속은, 'protected보다 접근의 범위가 넓은 맴버는 protected로 변경시켜서 상속하겠다.'는 뜻이다.
- 따라서, 위의 내용에서 portected보다 접근 범위가 넓은 맴버는 public 맴버이기 때문에, protected 상속을 한 A클래스는 아래의 형태가 된다.
class B : protected A{ private : int num1; protected : int num2; protected : int num3; }; |
- 그러나, 위의 코드는 잘못 표현된 부분이 있다. 위 코드처럼, num1이 그대로 private 선언이 된다면, 이 A의 맴버는 B 클래스 내에서 접근이 가능할 수 있다.
- 즉, num1은, 선언이 된 A 클래스 이외의 영역에서 접근이 불가능하므로 아래의 형태로 표현되어야 옳다.
class B : protected A{ 접근불가 : int num1; protected : int num2; protected : int num3; }; |
ex )
#include <iostream>
using namespace std;
class Base {
private:
int num1;
protected:
int num2;
public:
int num3;
Base():num1(1),num2(2),num3(3){ } // 생성자
};
class Derived : protected Base { }; //empty!
int main() {
Derived drv; // drv 객체 생성
cout << drv.num3 << endl; // 컴파일 에러 발생!
// Base 클래스를 protected로 상속했기 때문에, public 맴버변수인 num3는 Derived에서 protected맴버가 된 것이다. 따라서 외부에서 접근이 불가능하다.
return 0;
}
3) private 상속
class B : private A { ... }
- A클래스는, private로 상속되었다.
class A{ class B : private A{ }; |
- private 상속은, 'private보다 접근범위가 넓은 맴버는 private로 변경시켜서 상속하겠다.'는 뜻이다.
- 따라서, num2, num3 모두 Derived 클래스 내에서만 접근이 가능한 맴버로 변경된다.
class B : private A{ 접근불가 : int num1; 접근불가 : int num2; 접근불가 : int num3; }; |
3. 상속을 위한 조건
1) IS-A 관계의 성립
- 상속의 기본 문법에서 보이듯이, 유도 클래스는 기초 클래스가 지니는 모든 것을 지니고, 거기다 유도클래스만의 추가적인 특성이 더해진다.
- IS-A관계는, 'A(유도 클래스)는 B(기초 클래스)이다'라는 것을 이야기한다.
: 상속관계가 성립하려면, 기초클래스와 유도클래스간에 IS-A관계가 성립해야 한다.
: 만약 그렇지 않을 경우에는 적절한 상속의 관계가 아닐 경우가 높다.
ex )
#include <iostream>
#include <cstring>
using namespace std;
class Computer { // 컴퓨터 클래스 : 모든 컴퓨터의 공통적인 속성을 computer 클래스 하나에 표현
private:
char owner[50]; // 소유주 변수
public:
Computer(const char* name) { // 컴퓨터 생성자
strcpy(owner, name);
}
void Calculate() { // 계산 기능 출력 함수
cout << "요청 내용을 계산합니다." << endl;
}
};
class NotebookComp : public Computer { // 노트북 컴퓨터 클래스
private:
int Battery; // 베터리 기능 변수 추가
public:
NotebookComp(const char * name, int initChag) : Computer(name),Battery(initChag){ } // 노트북 생성자
void Charging() { Battery += 5; } // 베터리 교환 기능 함수
void UseBattery() { Battery -= 1; } // 베터리 사용 기능 함수
void MovingCal() { // 노트북을 사용할 때마다, 베터리가 소모되는 상황에 대한 함수
if (GetBatteryInfo() < 1) {
cout << "충전이 필요합니다." << endl;
return;
}
cout << "이동하면서 ";
Calculate();
UseBattery();
}
int GetBatteryInfo() { return Battery; } // 객체에 대한, 베터리 반환 함수
};
class TableNotebook : public NotebookComp { // 테이블노트북 컴퓨터 클래스
private:
char regstPenModel[50]; // 펜 등록 변수 추가
public:
TableNotebook(const char* name, int initChag, const char* pen) : NotebookComp(name, initChag) { // 테이블노트북 컴퓨터 생성자
strcpy(regstPenModel, pen);
}
void Write(const char* penInfo) { // 펜 사용(필기) 기능 함수
if (GetBatteryInfo() < 1) {
cout << "충전이 필요합니다." << endl;
return;
}
if (strcmp(regstPenModel, penInfo) != 0) {
cout << "등록된 펜이 아닙니다.";
return;
}
cout << "필기 내용을 처리합니다." << endl;
UseBattery();
}
};
int main() {
NotebookComp nc("이수종", 5);
TableNotebook tn("정수영", 5, "ISE-241-242");
nc.MovingCal();
tn.Write("ISE-241-242");
return 0;
}
2) HAS-A관계
- HAS-A관계 : '소유'의 관계 (~는 ~를 소유하다)
: HAS-A관계도 상속의 조건은 되지만, 복잡한 관계로, 이를 대신하는 것이 일반적이다.
ex 1 )
#include <iostream>
#include <cstring>
using namespace std;
class Gun { // 총 클래스
private:
int bullet; // 장전된 총알의 수
public:
Gun(int bnum) : bullet(bnum){ } // 총 생성자
void Shot() { // 총 발사 기능 함수
cout << "BBANG!" << endl;
bullet--;
}
};
class Police : public Gun { // 경찰 클래스 (권총을 소유하는 경찰 표현)
private:
int handcuffs; // 소유한 수갑의 수
public:
Police(int bnum,int bcuff): Gun(bnum),handcuffs(bcuff){ } // 경찰 생성자
void PutHandCuff() { // 수갑 기능 함수
cout << "SNAP!" << endl;
handcuffs--;
}
};
int main() {
Police pman(5, 3); // pman 객체 생성 (총알 5, 수갑 3)
pman.Shot();
pman.PutHandCuff();
return 0;
}
- 소유의 관계는 상속이 아닌 다른 방식으로도 가능하다.
ex 2 )
#include <iostream>
#include <cstring>
using namespace std;
class Gun { // 총 클래스
private:
int bullet; // 장전된 총알의 수
public:
Gun(int bnum) : bullet(bnum){ } // 총 생성자
void Shot() { // 총 발사 기능 함수
cout << "BBANG!" << endl;
bullet--;
}
};
class Police { // 경찰 클래스
private:
int handcuffs; // 소유한 수갑의 수
Gun* pistol; // 소유하고 있는 권총
public:
Police(int bnum, int bcuff) : handcuffs(bcuff) { // 경찰 생성자
if (bnum > 0)
pistol = new Gun(bnum); // Gun 클래스를 상속하는 것이 아닌, 생성자에서 객체를 생성하여 이를 참조함.
else
pistol = NULL;
}
void PutHandCuff() { // 수갑 기능 함수
cout << "SNAP!" << endl;
handcuffs--;
}
void Shot() { // 총 발사 기능 함수
// Gun 객체를 맴버변수 pistol을 통해 참조하는 구조이기 때문에, 이렇게 별도의 함수 정의 필요
if (pistol == NULL)
cout << "Hut BBANG!" << endl;
else
pistol->Shot();
}
~Police() { // 경찰 소멸자
if (pistol != NULL)
delete pistol;
}
};
int main() {
Police pman1(5, 3); // pman1 객체 생성
pman1.Shot();
pman1.PutHandCuff();
Police pman2(0, 3); // pman2 객체 생성 (권총을 소유하지 않은 경찰)
pman2.Shot();
pman2.PutHandCuff();
return 0;
}
[ 코드의 양이 늘었음에도, ex 1)보다 ex 2)의 예제가 더 좋은 모델이다. 그 이유는? ] a. 권총을 소지하지 않은 경찰을 표현해야 할 경우. b. 경찰이 수갑뿐만 아니라, 다른 것들도 소유하기 시작했을 경우 - 이와 같이 복잡하고 다양한 코드를 구현해야 할 경우, 상황을 대처하는데 이 방법이 더 좋음. 따라서, IS-A방법에서 상속 관계의 표현에 있어 사용하는데 적절하며, HAS-A 관계에서는 프로그램의 변경에 많은 제약을 줄 수 있다. |
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |