CFile 클래스를 사용하면 바이너리 또는 텍스트모드로 파일을 열어서 사용할수가 있습니다. 하지만 CFile 클래스는 파일을 입출력하는 함수가 텍스트기반이 아닌 바이너리 기반의 함수형태만 제공하기 때문에 텍스트 파일을 입출려할때 다소 불편함이 있습니다. 예를들어, 텍스트 파일을 열고 한줄씩 텍스트 데이터를 읽는다고 한다면 한자씩 읽어서 아스키코드를 비교해야하는 번거로운 작업을 프로그래머가 직접 구성해야 합니다.
이런불편함을 줄여주고자 예전에 런타임함수에서 사용하던 fgets, fputs 같은 함수를 제공하는 CStdioFile 클래스를 추가적으로 제공하는 것입니다.
The POSIX name forthis item is deprecated. Instead, use the ISO C and C++ conformant name: new-name.
To turn off deprecation warnings for these functions, define the preprocessor macro_CRT_NONSTDC_NO_WARNINGS. You can define this macro at the command line by including the option/D_CRT_NONSTDC_NO_WARNINGS.
역시 포인터 형만 갖고 대입가능성을 판단하여 역시 컴파일 에러가 난다. Base * bptr = new Derived();가 컴파일이 되는 이유 : Derived 클래스는 Base 클래스의 유도클래스니까 Base 클래스의 포인터 변수로 Derived 객체의 참조가 가능하니 컴파일에 문제가 없다.
pol 이름으로 메모리공간 할당 멤버로 cud 포인터 메모리공간 4바이트 할당 Police 생성자 호출 cud 객체 동적 생성 (Cudgel 객체를 동적할당) 명시적인 게 없으므로 void 생성자(디폴트 생성자) 호출하면서 Cudgel 객체를 생성 Police 객체는 Cudgel객체를 가리킨다
예제 2, 3 결과는 같으나 3이 낫다.
=> 논리적인 포함관계는 상속보다는 대안 방법 (포함, 3번 예제의 사용) 이 낫다. 느슨하면서 확실한 관계가 좋고, 커플링(결합도)이 낮아지는 게 좋다
8-2 상속된 객체와 포인터 관계
객체포인터, 객체포인터 권한에 대해 알아본다.
객체 포인터
- 객체의 주소 값을 저장할 수 있는 포인터 (포인터인데 객체를 가리킬 수 있는 포인터) - AAA(Person)클래스의 포인터는 AAA객체 뿐만 아니라, AAA클래스를 상속하는 Derived 클래스(Student) 객체의 주소 값도 저장 가능
Student는 Person이다. Person 클래스 포인터 p로 Student 객체를 가리킬 수 있다. Student 객체로는 Person을 가리킬 수 없다.
#include<iostream>usingnamespace std;
classA
{public:
voida(){
cout<<"a()"<<endl;
}
};
classB :public A
{
public:
voidb(){
cout<<"b()"<<endl;
}
};
classC :public B
{
public:
voidc(){
cout<<"c()"<<endl;
}
};
intmain(){
C* c = newC(); //void 생성자 호출
B* b = c; // b클래스의 포인터, c클래스의 포인터로 포인터의 타입이 달라도 대입 가능하다// C클래스의 포인터는 C클래스의 포인터이자 B, A클래스의 포인터도 되는 것이기 떄문이다// 그래서 타입이 좌 우가 일치한다.//A* a = c; //그래서 이것도 가능하다.
A* a = b; // 이것도 가능하다.//cout<<c<<endl;//cout<<b<<endl;//cout<<a<<endl;// 출력을 해보면 같은 위치를 가리키고 있다
b->a(); // b라는 포인터가 가리키는 객체 안에는 a, b, c 함수가 있는데
b->b(); // 호출할 수 있는 함수는 a, b로 제한되어진다// b클래스 내에 선언되어있는, 비클래스가 상속하고 있는 클래스의 멤버에만 접근이 가능하다.//b->c(); // 컴파일타임에 에러가 발생
a->a();
//a->b();//a->c();return0;
}
참조를 쓰게 되면 Call by reference 인지 value인지 의미가 불분명하기 때문에쓰기 꺼려하기도 한다. 따라서 둘 중 하나를 고르자면 포인터에 더 익숙해지는 게 낫다.
객체 포인터의 권한
- 포인터를 통해서 접근할 수 있는 객체 멤버의 영역 - AAA객체와, AAA를 상속하고 있는 클래스의 객체도 가리킬 수 있다 (IS-A 관계) (Person 클래스의 p포인터로..) - 그러나 AAA 클래스의 객체 포인터는 가리키는 대상에 상관없이 AAA클래스 내에 선언된 멤버에만 접근
Student *s가 Person 클래스의 객체를 가리키는 건 문제가 된다. 역방향은 성립하지 않기 때문이다 (IS-A가 아님)
유도클래스의 객체까지 가리킬 수 있다
C++에서 AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접/간접적으로 상속하는 모든 객체를 가리킬 수 있다. (객체의 주소값을 저장할 수 있다.) Student, PartTimeStudent 객체는 Person객체의 일종이기 때문이다.
전 단원의 급여관리 예제 확장 + 함수오버라이딩
정규직, 영업직, 임시직 모두 고용인이며 영업직은 정규직의 일종이다. EmployeeHandler 클래스가 저장,관리 하는 대상이 Employee 객체가 되면 이후 Employee 클래스를 직간접적으로 상속하는 클래스가 추가되어도 EmployeeHandler 클래스는 변화가 없다.
// EmployeeManager3.cpp#include<iostream>#include<cstring>usingnamespace std;
classEmployee // 고용인
{private:
char name[100]; // 고용인의 이름public:
Employee(char* name)
{
strcpy(this->name, name);
}
voidShowYourName()const{
cout<<"name : "<<name<<endl;
}
};
classPermanentWorker:public Employee
{
private:
int salary; //월급여public:
PermanentWorker(char* name, int money)
: Employee(name), salary(money)
{ }
intGetPay()const{
return salary;
}
voidShowSalaryInfo()const{
ShowYourName();
cout<<"salary: "<<GetPay()<<endl<<endl;
}
};
classEmployeeHandler
{private:
Employee* empList[50]; //Employee 객체의 주소값 저장// Employee 클래스를 상속하는 클래스의 객체도 이 배열에 함께 저장 가능int empNum;
public:
EmployeeHandler()
: empNum(0)
{ }
voidAddEmployee(Employee* emp)//Employee 객체의 주소값을 전달 // Employee 클래스를 상속하는 클래스 PermenentWorker 객체의 주소값도 전달 가능{
empList[empNum++]=emp;
}
voidShowAllSalaryInfo()const{
}
voidShowTotalSalary()const{
int sum=0;
cout<<"salary sum: "<<sum<<endl;
}
~EmployeeHandler()
{
for(int i=0; i<empNum; i++)
delete empList[i];
}
};
// 임시직. 실제 일을 한 시간으로 급여 계산classTemporaryWorker:public Employee
{
private:
int workTime; // 이달에 일한 총 시간int payPerHour; // 시간 당 급여public:
TemporaryWorker(char* name, int pay)
: Employee(name), workTime(0), payPerHour(pay)
{ }
voidAddWorkTime(int time)// 일한 시간 추가{
workTime+=time;
}
intGetPay()const// 이달의 급여{
return workTime*payPerHour;
}
voidShowSalaryInfo()const{
ShowYourName();
cout<<"salary: "<<GetPay()<<endl<<endl;
}
};
// 영업직 (정규직 일종). Employee가 아닌 PermanentWorker 상속// 기본급여과 관련된 부분을 멤버로 포함, 상여금 부분만 멤버로 추가classSalesWorker:public PermanentWorker
{
private:
int salesResult; //월 판매실적double bonusRatio; //상여금 비율public:
SalesWorker(char* name, int money, double ratio)
: PermanentWorker(name, money), salesResult(0), bonusRatio(ratio)
{ }
voidAddSalesResult(int value){
salesResult+=value;
}
intGetPay()const{
return PermanentWorker::GetPay() //PermanentWorker의 GetPay() 호출
+(int)(salesResult*bonusRatio);
}
voidShowSalaryInfo()const{
ShowYourName();
cout<<"salary: "<<GetPay()<<endl<<endl; //SalesWorker의 GetPay() 호출// PermanentWorker 클래스의 ShowSalaryInfo()랑 같은 내용의 함수인데도// 오버라이딩 한 이유는 상속이 되었어도 어느 GetPay를 부를 지 컨트롤 할 수 없어서
}
};
intmain(void){
// 직원관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
EmployeeHandler handler;
// 정규직 등록
handler.AddEmployee(newPermanentWorker("KIM", 1000));
handler.AddEmployee(newPermanentWorker("LEE", 1500));
// 임시직 등록
TemporaryWorker* alba=newTemporaryWorker("Jung", 700);
alba->AddWorkTime(5); //5시간 일한결과 등록
handler.AddEmployee(alba);
// 영업직 등록
SalesWorker* seller=newSalesWorker("Hong", 1000, 0.1) ;
seller->AddSalesResult(7000); //영업실적 7000
handler.AddEmployee(seller);
// 이달 지불할 급여 정보
handler.ShowAllSalaryInfo();
// 이달 지불할 급여 총합
handler.ShowTotalSalary();
return0;
}
메인 함수 내 handler.AddEmployee(new PermanentWorker("KIM", 1000)); 와 같은 코드 4줄에서 발생. 에러 내용 : 인수 1을(를) const char[4]에서 char* (으)로 변환할 수 없습니다. 이유 : "aa" 문자열은 상수인데 변수에 값을 넣으려 해서. C에서는 문자열 리터럴이 char의 배열이지만, C++에서는 const char의 배열이다.
에러 수정 : 1) 포인터가 아닌 배열로 선언 2) (char*) 형식으로 형변환 3) 자료형을 const char* 형태로 바꾸기 4) const_cast<char*> 사용하기
protected 멤버변수를 사용하면 자식 클래스에서도 사용 가능하지만, protected보다 멤버이니셜라이저를 사용하는 게 좋은 구조이다.
Person 클래스의 name을 char Person_name으로 변경한다면 Student 클래스 내의 name까지 바꿔줘야 하는, 한 클래스의 변경이 다른 클래스까지의 변경을 유도하기 때문이다.
#define _CRT_SECURE_NO_WARNINGS#include<iostream>usingnamespace std;
classPerson
{protected:
int age;
char name[20];
public:
intGetAge()const{
return age;
}
constchar* GetName()const{
return name;
}
Person(int _age=1, constchar* _name="noname")
{
age = _age;
strcpy(name, _name);
}
~Person() //소멸자
{
cout << "~Person()" << endl;
}
};
classStudent :public Person
{
char major[20]; //전공public:
Student(int a, constchar* _name, constchar* _major) { : Person(a, _name) {
// 직접 멤버 변수를 변경하면 Person 멤버변수가 변경되면 Student 클래스에서도 변경이 불가피하다.//Student(int a, char* _name, const char* _major) : num(a) { //const 멤버변수를 초기화 // 멤버 이니셜라이져 - 클래스 이름일 경우 a라는 이름의 인자값을 받을 수 있는 생성자를 호출// 생성자이니셜라이저를 이용해서 초기화하면// Person클래스의 멤버 변수를 변경하더라도 해당 클래스 내에서만 멤버변수를 변경하면 된다.// 객체 내의 변수를 외부에서 직접 변경하지 않도록 설계하면 유지보수에 유리하고 오류도 적을 것.
age=a;
strcpy(name, _name);
strcpy(major, _major);
}
constchar* GetMajor()const{
return major;
}
voidShowData()const{
cout << "이름:" << GetName() <<endl;
cout << "나이:" << GetAge() <<endl;
cout << "전공:" << GetMajor() << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
};
intmain(){
Student Kim(20, "kim", "computer");
Kim.ShowData();
return0;
}
7-5 세 가지 형태의 상속
- public 상속이 대부분이다.
접근 권한 변경
- Base 클래스의 멤버는 상속되는 과정에서 접근 권한 변경
protected 상속일 때, 자기(protected)보다 넓은 접근 권한이 있다면 좁힌다. (public멤버는 protected로...) 자기(protected)보다 좁은 접근 권한이 있다면 ... private 멤버의 경우 접근 불가가 된다. 표를 참고하면 된다.
7-6 상속을 하는 이유
문제의 도입
- 운송 수단에 관련된 클래스 정의 - 자동차, 열차, 선박, 비행기 등등... 중복되는 부분이 생긴다.
중복되는 것들은 상위 객체에 넣어 상위 객체를 가져와서 코드를 간소화 할 수 있다.
문제의 해결
- 클래스를 상속하여 구성하면 코드가 간결해진다. - 클래스를 확장하기 좋다.
// 탈 것classVehicle {int pass;
int bag;
public:
Vehicle(int person = 0, int weight = 0);
voidRide(int person);
voidLoad(int weight);
};
// 비행기classAirplane:public Vehicle {
int crew; //승무원 인원public:
Airplane(int p = 0, int b = 0, int c = 0):Vehicle(p,w);
voidTakeCrew(int crew);
};
// 기차classTrain:public Vehicle {
int length; //열차 칸 수public:
Train(int p = 0, int b = 0, int l = 0):Vehicle(p,w);
voidSetLength(int len);
};
UnivStudent 클래스의 생성자는 Person 클래스의 멤버까지 초기화해야 한다. 그래서 UnivStudent 클래스의 생성자는 Person 클래스의 생성자를 호출하는 형태로 Person클래스의 멤버를 초기화하는게 좋다. 상속받는 클래스는 이니셜라이저를 이용해서 상속하는 클래스의 생성자 호출을 명시할 수 있다.
* 멤버는 클래스가 정의될 때 멤버의 초기화를 목적으로 정의된 생성자를 통해서 초기화하는게 가장 안정적이다.
상속받은 멤버변수의 접근 제한의 기준은 클래스이다. 클래스 외부에서는 private 멤버에 접근 불가하고, 상속받아도 멤버변수에 직접접근이 불가하다. 클래스 내 정의된 public 함수를 통해 간접 접근해야 한다.
Person ↔ UnivStudent
상위클래스 ↔ 하위 클래스 기초 클래스(base) ↔ 유도(derived) 클래스 *derive : 유래, 파생 슈퍼 클래스 ↔ 서브 클래스 부모 클래스 ↔ 자식 클래스
유도 클래스의 객체생성 과정에서 기초 클래스의 생성자는 100% 호출된다. 유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면 기초 클래스의 void 생성자가 호출된다.
유도 클래스의 객체생성 과정에서 생성자가 두 번 호출된다. 1) 기초클래스의 생성자 2)유도 클래스의 생성자
SoDerived dr3(23, 24); ---- 메모리 공간 할당 SoDerived(int n1, int n2): SoBase(n1), deriveNum(n2) { ... } ---- 객체생성문에 의해 23, 24가 전달되면서 유도클래스 생성자 호출 SoBase(n1) ---- 기초클래스 생성자 호출 : 상속 관계에 의해 기초 클래스의 생성자 호출을 위해 이니셜라이저 확인, 매개변수 n1으로 전달된 값을 인자로 받을 수 있는 SoBase클래스의 생성자 호출 SoBase(int n): baseNum(n) { } ---- 기초클래스 멤버변수 초기화 ---- 유도 클래스의 생성자 실행 완료 ---- 유도클래스의 멤버변수 초기화 완료
SoDerived dr1; ---- 메모리 공간 할당 ---- 유도 클래스 생성자 호출 SoDerived(): deriveNum(30) { ... } ---- 생성자 관련 초기화 내용이 없으면 기초 클래스의 void 생성자를 대신 호출 deriveNum = 30; ---- 기초클래스의 멤버변수 초기화 완료 ---- 유도클래스 생성자의 나머지 실행 ---- 초기화된 SoDerived 객체 생성
유도 클래스의 객체 생성과정에서도 적용되는 원칙 : 클래스의 멤버는 해당 클래스의 생성자를 통해서 초기화해야 한다.
유도 클래스 객체의 소멸과정
유도 클래스의 객체 생성 과정에서는 생성자가 2번 호출, 유도 클래스의 객체 소멸 과정에서는 소멸자가 2번 호출된다.
접근제어 지시자의 접근 범위는 private < protected < public 과 같다. private, protected 는 외부에서 접근 불가, 클래스 내부에서는 접근 가능하나 상속이 되면 조금 달라진다.
protected, public 선언된 멤버변수는 상속하는 유도클래스에서 접근할 수 있고 private 멤버는 컴파일 에러가 발생한다. protected는 유도클래스에게만 제한적으로 접근을 허용한다. (private과 유일한 차이점)
세 가지 형태의 상속
classDerived:public Base { ... }
classDerived:protected Base { ... }
classDerived:private Base { ... }
protected 상속
protected보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다는 뜻
#include<iostream>usingnamespace std;
classBase
{private:
int num1;
protected:
int num2;
public:
int num3;
Base(): num1(1), num2(2), num3(3)
{ }
};
classDerived:protected Base{ };
intmain(){
Derived drv;
cout<<drv.num3<<endl; //error
}
public변수도 외부 접근 불가능 멤버가 되었다.
private 상속
private보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속
#include<iostream>usingnamespace std;
classBase
{private:
int num1;
protected:
int num2;
public:
int num3;
Base(): num1(1), num2(2), num3(3)
{ }
};
classDerived:private Base{ };
intmain(){
Derived drv;
cout<<drv.num3<<endl; //error
}
위 코드는 아래의 형태가 되고, num2, num3는 Derived 클래스 내에서만 접근이 가능한 멤버가 된다. 다른 클래스가 Derived클래스를 다시 상속하면 Derived 클래스 모든 멤버가 private이거나 접근불가여서 public으로 받아도 모두 접근불가가 되어 의미 없는 상속이 된다.
classBase :
{
접근불가:
int num1;
private:
int num2;
private:
int num3;
};
private 상속이 이뤄진 클래스를 재상속할 경우, 멤버함수를 포함하여 모든 멤버가 접근불가가 되어서 의미없는 상속
public 상속
private을 제외한 나머지는 그냥 그대로 상속한다.
상속의 대부분은 public 상속이며 다중상속과 같은 특별한 때가 아니면 나머지는 잘 안 사용한다.
04 상속을 위한 조건
상속으로 클래스의 관계를 구성하려면 조건이 필요하다. 상속을 위한 최소한의 조건을 정리해본다.
상속을 위한 기본 조건인 IS-A 관계의 성립
유도클래스 = 기초클래스가 지니는 모든 것 + 유도클래스만의 추가적 특징
기초 클래스 - 유도 클래스 ------------------------------ 전화기 - 무선전화기 컴퓨터 - 노트북컴퓨터
통화, 계산 + 이동성 ------------------------------ 무선전화기 is a 전화기 놋북 is a 컴퓨터 (~이다)
상속관계가 성립하려면 기초클래스와 유도클래스 간에 IS-A 관계가 성립해야 한다. 성립하지 않는다면 적절한 상속관계가 아닐 확률이 높다.
#include<iostream>#include<cstring>usingnamespace std;
classComputer //소유자 정보 저장, 계산 함수
{private:
char owner[50];
public:
Computer(char* name){
strcpy(owner, name);
}
voidCalculate(){
cout<<"요청 내용을 계산합니다."<<endl;
}
};
classNotebookComp:public Computer // 배터리 관련 변수, 함수 추가
{
private:
int Battery;
public:
NotebookComp(char* name, int initChag)
:Computer(name), Battery(initChag)
{ }
voidCharging(){ Battery+=5; }
voidUseBattery(){ Battery-=1; }
voidMovingCal(){
if(GetBatteryInfo()<1){
cout<<"충전이 필요합니다"<<endl;
return;
}
cout<<"이동하면서 ";
Calculate();
UseBattery();
}
intGetBatteryInfo(){ return Battery; }
};
classTabletNoteBook:public NotebookComp // 펜을 등록, 등록이 된 펜을 사용해야 필기가 가능한 상황 표현
{
private:
char regstPenModel[50];
public:
TabletNoteBook(char* name, int initChag, char* pen)
: NotebookComp(name, initChag)
{
strcpy(regstPenModel, pen);
}
voidWrite(char* penInfo){
if(GetBatteryInfo()<1)
{
cout<<"충전이 필요합니다"<<endl;
return;
}
if(strcmp(regstPenModel, penInfo)!=0)
{
cout<<"등록된 펜이 아닙니다";
return;
}
cout<<"필기 내용을 처리합니다"<<endl;
UseBattery();
}
};
intmain(){
NotebookComp nc("이수종", 5);
TabletNoteBook tn("정수영", 5, "ISE-241-242");
nc.MovingCal();
tn.Write("ISE-241-242");
return0;
}
TabletNotebook 클래스 객체 생성과정에서 TabletNotebook 클래스가 상속하는 NotebookComp 클래스의 생성자와 NotebookComp 클래스가 상속하는 Computer 클래스의 생성자가 모두 호출된다
'타블렛컴퓨터는 컴퓨터이다'의 관계도 성립된다.
HAS-A 관계(소유의 관계)도 상속의 조건은 되지만 복합 관계로 이를 대신하는 게 일반적이다. 유도클래스는 기초클래스가 지닌 모든걸 소유하기에 소유의 관계도 상속으로 표현할 수 있다.