[C++] Chapter 07 - 상속의 이해

Lee Jeong Min·2021년 1월 8일
0

Cpp

목록 보기
7/16
post-thumbnail

07-1 상속에 들어가기 앞서

상속과 관련한 문제 제시를 위한 시나리오

우선 처음에 회사에는 정규직 직원을 관리하기 위한 프로그램이 다음과 같이 만들었다고 가정한다

#include <iostream>
using namespace std;

class PermanentWorker
{
private:
	char name[100];
	int salary;
public:
	PermanentWorker(const char* name, int money) : salary(money)
	{
		strcpy(this->name, name);
	}
	int GetPay() const
	{
		return salary;
	}
	void ShowSalaryInfo() const
	{
		cout << "name: " << name << endl;
		cout << "salary: " << GetPay() << endl;
	}
};

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(void)
{
	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;
}

그러나 이러한 코드는 프로그램의 유연성과 확장성 부분에서 떨어진다. 왜냐하면 새로운 고용형태가 생겼을 경우 프로그램을 다시짜야하기 때문. 이러한 문제들을 '상속'을 통해서 해결할 수 있다.


07-2 상속의 문법적인 이해

상속관련 예제

#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
	int age;
	char name[50];
public:
	Person(int myage, const char* myname) : age(myage)
	{
		strcpy(this->name, myname);
	}
	void WhatYourName() const
	{
		cout << "My name is " << name << endl;
	}
	void HowOldAreYou() const
	{
		cout << "I'm " << age << " years old" << endl;
	}
};

class UnivStudent : Person
{
private:
	char major[50];
public:
	UnivStudent(const char* myname, int myage, const char* mymajor) : Person(myage, myname)
	{
		strcpy(this->major, mymajor);
	}
	void WhoAreYou() const
	{
		WhatYourName();
		HowOldAreYou();
		cout << "My major is " << major << endl << endl;
	}
};

int main(void)
{
	UnivStudent ustd1("Lee", 22, "Computer eng.");
	ustd1.WhoAreYou();

	UnivStudent ustd2("Yoon", 21, "Electronic eng.");
	ustd2.WhoAreYou();

	return 0;
}

이 예시를 보면 UnivStudent 클래스에서 Person 클래스의 private 멤버 변수로 함수를 통한 접근이 가능하며 Person클래스에서 정의한 public함수들을 모두 사용할 수 있음을 확인할 수 있다. C++에선 여기서 부모 클래스인 Person을 보통 기초 클래스라고 하며 UnivStudent와 같은 자식클래스를 유도 클래스라고 한다.

유도 클래스의 객체 생성과정

#include <iostream>
using namespace std;

class SoBase
{
private:
	int baseNum;
public:
	SoBase() : baseNum(20)
	{
		cout << "SoBase()" << endl;
	}
	SoBase(int n) : baseNum(n)
	{
		cout << "SoBase(int n)" << endl;
	}
	void ShowBaseData()
	{
		cout << baseNum << endl;
	}
};

class SoDerived : public SoBase
{
private:
	int derivNum;
public:
	SoDerived() : derivNum(30)
	{
		cout << "SoDerived()" << endl;
	}
	SoDerived(int n) : derivNum(n)
	{
		cout << "SoDerived(int n)" << endl;
	}
	SoDerived(int n1, int n2) : SoBase(n1), derivNum(n2)
	{
		cout << "SoDerived(int n1, int n2)" << endl;
	}
	void ShowDerivData()
	{
		ShowBaseData();
		cout << derivNum << endl;
	}
};

int main(void)
{
	cout << "case1.... " << endl;
	SoDerived dr1;
	dr1.ShowDerivData();
	cout << "---------------------" << endl;
	cout << "case2......." << endl;
	SoDerived dr2(12);
	dr2.ShowDerivData();
	cout << "--------------------" << endl;
	cout << "case3......." << endl;
	SoDerived dr3(23, 24);
	dr3.ShowDerivData();
	return 0;
}

이 예제를 통해 유도 클래스의 객체생성 과정에서 기초 클래스의 생성자가 호출되며 기초 클래스의 생성자 호출을 명시하지 않은경우엔 기초 클래스의 void 생성자가 호출 된다. 또한 클래스의 멤버는 해당 클래스의 생성자를 통해서 초기화해야 한다는 것을 알 수 있다.

유도 클래스의 객체의 소멸과정

#include <iostream>
using namespace std;

class SoBase
{
private:
	int baseNum;
public:
	SoBase(int n) : baseNum(n)
	{
		cout << "SoBase()" << baseNum << endl;
	}
	~SoBase()
	{
		cout << "~SoBase()" << baseNum << endl;
	}
};

class SoDerived : public SoBase
{
private:
	int derivNum;
public:
	SoDerived(int n) : SoBase(n), derivNum(n)
	{
		cout << "SoDerived() : " << derivNum << endl;
	}
	~SoDerived()
	{
		cout << "~SoDerived() : " << derivNum << endl;
	}
};

int main(void)
{
	SoDerived drv1(15);
	SoDerived drv2(27);
	return 0;
}

이 예제로는 유도 클래스의 객체가 소멸될 때는 유도 클래스의 소멸자가 실행되고 난 다음에 기초 클래스의 소멸자가 실행이되고 스택에 생성된 객체의 소멸 순서는 생성순서와 반대이다.
또한 이러한 특성은 생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제한다는 원칙을 지켜서 정의해야함을 보여준다.


07-3 protected 선언과 세 가지 형태의 상속

protected로 선언된 멤버가 허용하는 접근의 범위

protected의 경우 private과 마찬가지로 클래스의 외부에서는 접근이 불가능하지만 클래스가 상속이 된경우 상속 된 클래스에선 protected로 선언한 멤버변수에 접근이 가능하다.

class Base
{
private:
    int num1;
protected:
    int num2;
public:
    int num3;
}

class Derived : public Base
{
public:
    void ShowBaseMember()
    {
        cout << num1; // 컴파일 에러
        cout << num2; // 컴파일 OK
        cout << num3; // 컴파일 OK
    }
};

세가지 형태의 상속

public, protected, private이 있지만 public을 제외한 나머지의 경우는 특별한 경우가 아니면 잘 사용하지 않기때문에 public에 대해서만 알아두어도 충분하다고 함.


07-4 상속을 위한 조건

보통 상속의 경우 IS-A 관계 일 경우에만 상속을 한다고 한다.

ex)

  • 전화기 -> 무선 전화기
  • 컴퓨터 -> 노트북

HAS-A 관게도 상속의 조건은 되지만 복합 관계로 이를 대신하는 것이 일반적

별도의 Class를 생성하여 복합관계 표시!

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글