[문과 코린이의 IT 기록장] C,C++ - 상속 1
(상속 공부 접근 방법, 문제의 제시, 상속의 문법적인 이해, 유도 클래스 객체 생성과정, 유도 클래스 객체의 소멸과정)
[ 상속 공부 접근 방법 ]
a. 문제의 제시 : 상속과 더불어 다형성의 개념을 적용해야만 해결 가능한 문제를 먼저 제시한다,.
b. 기본개념 소개 : 상속의 문법적 요소를 하나씩 소개해 나간다.
c. 문제의 해결 : 처음 제시한 문제를, 상속을 적용해 해결해 나간다.
ex ) OrangeMedia라는 회사가 운영하는 '급여관리 시스템'
- 이 회사에서는 기존에는 정규직(ermanent) 하나의 근무형태만 존재했음.
#include<iostream>
using namespace std;
class PermanentWorker { // 정규직 클래스 (데이터적 성격 O)
private:
char name[100];
int salary; // 매달 지불해야 하는 급여액
public:
/* 이 회사의 정규직 급여는 입사 당시에 정해진다는 것을 알 수 있음 */
PermanentWorker(const char* name, int money) : salary(money) // 생성자
{
strcpy(this->name, name);
}
int GetPay() const { // 객체에 맞는 salary값 반환 함수
return salary;
}
void ShowSalaryInfo() const { // name, salary값 출력 함수
cout << "name: " << name << endl;
cout << "salary: " << GetPay() << endl << endl;
}
};
(기능적 성격 O = 컨트롤 클래스 = 핸들러 클래스)
class EmployeeHandler { // 직원 관리 클래스 : 이름과 급여정보를 저장할 수 있도록 클래스를 정의
private:
PermanentWorker* empList[50]; // 직원 정보리스트
int empNum; // 현존하는 직원 정보리스트 총 숫자
public:
EmployeeHandler():empNum(0){ } // 생성자 (직원 정보리스트 생성)
void AddEmployee(PermanentWorker* emp) { // 신규 직원정보 등록 함수
empList[empNum++] = emp;
}
void ShowAllSalaryInfo() const { // 직원 정보리스트 총 출력
for (int i = 0; i < empNum; i++)
empList[i]->ShowSalaryInfo();
}
void ShowTotalSalary() const { // 직원 정보리스트 총 급여 출력
int sum = 0;
for (int i = 0; i < empNum; i++)
{ sum += empList[i]->GetPay(); }
cout << "salary sum: " << sum << endl;
}
~EmployeeHandler() { // 소멸자
for (int i = 0; i < empNum; i++)
delete empList[i];
}
};
int main() {
// 직원관리를 목적으로 설계된 컨트롤 클래스의 객체생성
EmployeeHandler handler;
// 직원 등록
handler.AddEmployee(new PermanentWorker("KIM", 1000));
handler.AddEmployee(new PermanentWorker("LEE", 1500));
handler.AddEmployee(new PermanentWorker("JUN", 2000));
// 이번 달에 지불해야 할 급여의 정보
handler.ShowAllSalaryInfo();
// 이번 달에지불해야 할 급여의 총합
handler.ShowTotalSalary();
return 0;
}
[ 객체지향, 소프트웨어 설계에 있어서 중요시하는 것 ] |
1. 문제의 제시
- 위의 OrangeMedia의 예시를 보면, 직원의 고용형태가 '정규직(Permanent)' 하나이다.
- 그러나, 이후 회사의 운영방침이 변경되어, 새로운 고용형태가 등장했다고 가정해보자.
a. 영업직(Sales) : 조금 특화된 형태의 고용직으로, 인센티브 개념이 도입된다.
* 영업직 급여 : 기본급여 + 인센티브
b. 임시직(Temporary) : 학생들을 대상으로 하는 임시고용 형태, 흔히 아르바이트라고 한다.
* 임시직 급여 : 시간당 급여 x 일한 시간
- 이렇게 변화하게 되면, SaleMan 객체와 Temporary 객체의 저장을 위한 배열을 두 개 추가하고, 각각의 배열에 저장된 객체의 수를 별도로 세어야 하니, 정수형 변수도맴버로 두 개 추가해야 한다.
- 또한, AddEmployee함수는 SalesMan객체용과 Temporary 객체용으로 각각 추가되어야 하고, 급여정보를 출력하는 나머지 두 맴버함수에 대한 반복문이 추가로 두개씩 더 삽입되어야 한다.
- 이는 위에서 말한, 객체지향에서 중요시 여겨지는 프로그램의 확장성에 있어 좋은 방안이 아니다.
- 이러한 문제는 상속이라는 개념을 통해 해결할 수 있다.
2. 상속의 문법적인 이해
1) 상속이란?
ex ) UnivStudent클래스가 Person클래스를 상속한다.
= Person클래스는 UnivStudent 클래스에 의해 상속당한다. (?)
= UnivStudent클래스는, Person 클래스가 지니고 있는 모든 맴버를 물려받는다.
2) 상속의 방법과 그 결과
class Person{
private:
int age; // 나이
char name[50]; // 이름
public:
Person(int myage, char *myname) : age(myage){ // 생성자
strcpy(name, mynmae);
}
void WhatYourName() const{ // 객체 이름 출력
cout<<"My name is "<<name<<endl;
}
void HowOldAreYou() onst{ // 객체 나이 출력
cout<<"I'm " <<age<<" years old"<<endl;
}
};
class UnivStudent : public Person{
// Person 클래스의 상속을 의미함 (UnivStudent 클래스가 Person 클래스를 상속한다.)
private:
char major[50]; // 전공과목
public:
UnivStudent(char *myname, int myage, char *mymajor) : Person(myage, myname){ // 생성자
// UnivStudent 객체는 Person의 맴버변수와 UnivStudent의 맴버변수가 존재하므로, 이 둘 모두에 대한 초기화에 책임을 져야 한다. 따라서, 이니셜라이저를 통해, Person 클래스의 생성자를 호출하는 형태로, 맴버를 초기화하는 것이 좋다.
strcpy(major, mymajor);
}
void WhoAreYou() const{ // 객체 이름, 나이, 전공 출력 함수
WhatYourName(); // Person 클래스를 상속했기 때문에 호출 가능 (Person 클래스의 맴버함수)
HowOldAreYou(); // Person 클래스를 상속했기 때문에 호출 가능 (Person 클래스의 맴버함수)
cout<<"My major is "<<major<<endl<<endl;
}
};
3) 상속과 관련한 예제
#include<iostream>
#include<cstring>
using namespace std;
class Person { // Person 클래스
private:
int age; // 나이
char name[50]; // 이름
public:
Person(int myage, const char* myname) :age(myage) { // 생성자
strcpy(name, myname);
}
void WhatYourName() const { // 객체 name 출력 함수
cout << "My name is " << name << endl;
}
void HowOldAreYou() const { // 객체 age 출력 함수
cout << "I'm " << age << " years old" << endl;
}
};
class UnivStudent : public Person { // UnivStudent 클래스 ( UnivStudent 클래스는 Person 클래스를 상속한다. )
private:
char major[50]; //전공과목
public:
UnivStudent(const char* myname, int myage, const char* mymajor) : Person(myage, myname) { // 생성자
strcpy(major, mymajor);
}
void WhoAreYou() const { // 객체 (name, age), major 출력 함수
WhatYourName(); // Person 클래스를 상속했기 때문에 호출 가능 (Person 클래스의 맴버함수)
HowOldAreYou(); // Person 클래스를 상속했기 때문에 호출 가능 (Person 클래스의 맴버함수)
cout << "My major is " << major << endl << endl;
}
};
int main() {
UnivStudent ustd1("Lee", 22, "Computer eng."); // ustd1 객체 생성
ustd1.WhoAreYou();
UnivStudent ustd2("Yoon", 21, "Electronic eng."); // ustd2 객체 생성
ustd2.WhoAreYou();
return 0;
}
4) 용어의 정리
Person | UnivStudent |
상위 클래스 | 하위 클래스 |
기초(base) 클래스 | 유도(derived) 클래스 |
슈퍼(super) 클래스 | 서브(sub) 클래스 |
부모 클래스 | 자식 클래스 |
3. 유도 클래스의 객체 생성과정
ex )
#include <iostream>
using namespace std;
class SoBase { // SoBase 클래스
private:
int baseNum;
public:
SoBase() : baseNum(20) { // 생성자
cout << "SoBase()" << endl;
}
SoBase(int n) : baseNum(n) { // 생성자
cout << "SoBase(int n)" << endl;
}
void ShowBaseData() { // 객체 baseNum 출력
cout << baseNum << endl;
}
};
class SoDerived : public SoBase { // SoDerived 클래스 (SoDerived클래스는 SoBase클래스를 상속한다.)
private:
int derivNum;
public:
(a) SoDerived() : derivNum(30) { // 생성자 (기초 클래스의 생성자 호출에 대한 언급이 없다.)
cout << "SoDerived()" << endl;
}
(b) SoDerived(int n) :derivNum(n) { // 생성자 (기초 클래스의 생성자 호출에 대한 언급이 없다.)
cout << "SoDerived(int n)" << endl;
}
(c) SoDerived(int n1, int n2) : SoBase(n1), derivNum(n2) { // 생성자 (n1을 인자로 받는, 기초 클래스의 생성자 호출을 직접 명시하고 있다.)
cout << "SoDerived(int n1, int n2)" << endl;
}
void ShowDerivData() {
ShowBaseData();
cout << derivNum << endl;
}
};
int main() {
cout << "case1....." << endl;
SoDerived dr1; // (a) 실행 : dr1 객체 생성
dr1.ShowDerivData();
cout << "---------------------" << endl;
cout << "case2....." << endl;
SoDerived dr2(12); // (b) 실행 : dr2 객체 생성
dr2.ShowDerivData();
cout << "---------------------" << endl;
cout << "case3....." << endl;
SoDerived dr3(23, 24); // (c) 실행 : dr3 객체 생성
dr3.ShowDerivData();
return 0;
}
[ 이 코드를 보고 알 수 있는 부분 ] |
4. 유도 클래스 객체의 소멸과정
- 유도 클래스의 객체 생성과정에서는, 생성자가 두 번 호출됨을 알 수 있다.
- 그렇다면 유도 클래스의 객체 소멸과정에서도 소멸자가 두 번 호출될 것인가?
ex 1 )
#include <iostream>
using namespace std;
class SoBase { // SoBase 클래스
private:
int baseNum;
public:
SoBase(int n) :baseNum(n) { // SoBase 객체생성자
cout << "SoBase() : " << baseNum << endl;
}
~SoBase() { // SoBase 객체 소멸자
cout << "~SoBase() : " << baseNum << endl;
}
};
class SoDerived : public SoBase { // SoDerived 클래스 (SoDerived 클래스는, SoBase 클래스를 상속한다.)
private:
int derivNum;
public:
SoDerived(int n) :SoBase(n), derivNum(n) { // SoDerived 객체 생성자
cout << "SoDerived() : " << derivNum << endl;
}
~SoDerived() { // SoDerived 객체 소멸자
cout << "~SoDerived() : " << derivNum << endl;
}
};
int main() {
SoDerived drv1(15); // drv1 객체 생성
SoDerived drv2(27); // drv2 객체 생성
return 0;
}
[ 위 코드를 통해 알 수 있는 사실 ] |
ex 2 )
#include <iostream>
#include <cstring>
using namespace std;
class Person { // Person 클래스
private:
char* name;
public:
Person(const char* myname) { // Person 객체 생성자
name = new char[strlen(myname) + 1];
strcpy(name, myname);
}
~Person() { // Person 객체 소멸자
delete[] name;
}
void WhatYourName()const { // 객체의 name 출력
cout << "My name is " << name << endl;
}
};
class UnivStudent : public Person { // UnivStudent 클래스 (UnivStudent 클래스는, Person 클래스를 상속한다.)
private:
char* major;
public:
UnivStudent(const char* myname, const char* mymajor) : Person(myname) { // UnivStudent 객체 생성자
major = new char[strlen(mymajor) + 1];
strcpy(major, mymajor);
}
~UnivStudent() { // UnivStudent 객체 소멸자
delete[] major;
}
void WhoAreYou() const { // 객체의 (name), major 출력
WhatYourName();
cout << "My major is" << ma0jor << endl << endl;
}
};
int main() {
UnivStudent st1("Kim", "Mathematics"); // st1 객체 생성
st1.WhoAreYou();
UnivStudent st2("Hong", "Physics"); // st2 객체 생성
st2.WhoAreYou();
return 0; // st2 객체 소멸, st1 객체 소멸
}
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |