본문 바로가기

문과 코린이의, [C. C++] 기록/C++ 이론

[문과 코린이의 IT 기록장] C,C++ - 상속 1 (상속 공부 접근 방법, 문제의 제시, 상속의 문법적인 이해, 유도 클래스 객체 생성과정, 유도 클래스 객체의 소멸과정)

반응형

[문과 코린이의 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;
}

 

 


 

[ 객체지향, 소프트웨어 설계에 있어서 중요시하는 것 ]
a. 요구사항의 변경에 대응하는 프로그램의 유연성
b. 기능의 추가에 따른 프로그램의 확장성

 


 

 

 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;

 }

};

 

UnivStudent객체 요약 

 


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;
}

 

[ 이 코드를 보고 알 수 있는 부분 ]
1. 유도 클래스의 객체 생성과정에서, 기초 클래스의 생성자는 무조건 호출된다.
2. 유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출된다.

 

 


 

 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;
}

 

[ 위 코드를 통해 알 수 있는 사실 ]
a. 유도 클래스의 객체가 소멸될 때는, 유도 클래스의 소멸자가 실행되고 난 다음에 기초 클래스의 소멸자가 실행된다.
 * 즉, 유도 클래스와 기초 클래스의 소멸자 모두 호출된다.
b. 스택에 생성된 객체의 소멸순서는, 생성순서와 반대이다.

 


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 객체 소멸
}

 

 


* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)

 

반응형