← 블로그 목록

[Study Note]C++ 상속

@jinlee0310
  • #Study
목차

TL;DR

이 글은 씹어먹는 C++ 6-1의 내용을 정리 및 요약한 글입니다.

사원 관리 프로그램

회사 사원들의 월급을 계산해서 한달에 총 얼마를 월급으로 지출하는 지 알려주는 프로그램을 만드려고 한다. 사원들의 필요한 데이터는 이름, 나이, 직책, 직책 순위로 구성된다.

class Employee{
	std::string name;
	int age;
	
	std::string position;
	int rank;
	
	public:
		Employee(std::string name, int age, std::string position, int rank) : name(name), age(age), position(position), rank(rank) {}
		
		// 복사 생성자
		Employee(const Employee& employee){
			name=employee.name;
			age=employee.age;
			position=employee.position;
			rank=employee.rank;
		}
		
		// 디폴트 생성자
		Employee(){}
		
		void print_info(){
			std::cout << name << "(" << position << "," << age << ") ==>" <<calculate_pay() << "만원" << std::endl;
		}
		
		int calculate_pay(){return 200+rank*50;}
}

Employee를 만들었으니 Employee 객체들을 관리할 수 있는 EmployeeList 클래스를 만들어보자.

int alloc_employee; // 할당한 총 직원 수
int current_employee; // 현재 직원 수
Employee **employee_list; // 직원 데이터

이 변수들을 통해 사원 데이터를 처리할 것이다. alloc_employee는 할당된 크기를 알려주는 배열이고 curren_employee는 현재 employee_list에 등록된 사원 수이다. employee_list가 Employee** 타입인 이유는 이를 Employee* 객체를 담는 배열로 사용할 것이기 때문이다.

EmployeeList(int alloc_employee) : alloc_employee(alloc_employee){
	employee_list=new Employee*[alloc_employee];
	current_employee=0;
}

cf). 이부분에 대해서 new Employee*[alloc_employee]라는 코드는 현대에는 잘 쓰이지 않는다. 현대에는 스마트 포인터와 vector를 사용하여 코드를 작성한다.

사원을 추가하는 함수는 아래처럼 구성한다.

void add_employee(Employee* employee){
	employee_list[current_employee]=employee;
	current_employee++;
}
class EmployeeList{
	int alloc_employee;
	int current_employee;
	Employee** employee_list;
	
	public:
		EmployeeList(int alloc_employee) : alloc_employee(alloc_employee){
			employee_list = new Employee*[alloc_employee];
			current_employee=0;
		}
		void add_employee(Employee* employee){
			employee_list[current_employee]=employee;
			current_employee++;
		}
		int current_employee_num(){return current_employee;}
		
		void print_employee_info(){
			int total_pay=0;
			for(int i=0;i<current_employee;i++){
				employee_list[i]->print_info();
				total_pay+=employee_list[i]->calculate_pay();
			}
			std::cout << "총 비용: " << total_pay << "만원" << std::endl;
		}
		~EmployeeList(){
			for(int i=0;i<current_employee;i++){
				delete employee_list[i];
			}
			delete[] employee_list;
		}
}

전체 코드

...
int main(){
	EmployeeList emp_list(10);
	emp_list.add_employee(new Employee("노홍철", 34, "평사원", 1));
	emp_list.add_employee(new Employee("하하", 34, "평사원", 1));
	
	emp_list.add_employee(new Employee("유재석", 41, "부장", 7));
	emp_list.add_employee(new Employee("정준하", 43, "과장", 4));
	emp_list.add_employee(new Employee("박명수", 43, "차장", 5));
	emp_list.add_employee(new Employee("정형돈", 36, "대리", 2));
	emp_list.add_employee(new Employee("길", 36, "인턴", -2));
	emp_list.print_employee_info();
	return 0;
}
노홍철(평사원,34) ==>250만원
하하(평사원,34) ==>250만원
유재석(부장,41) ==>550만원
정준하(과장,43) ==>400만원
박명수(차장,43) ==>450만원
정형돈(대리,36) ==>300만원
길(인턴,36) ==>100만원
 비용: 2300만원

여기에서 차장 이상 급은 관리 데이터에 근속 년수를 포함시켜서 월급에 추가해달라는 요청사항이 생긴다. 이를 위해 매니저 클래스를 만든다.

class Manager{
	std::string name;
	int age;
	
	std::string position;
	int rank;
	int year_of_service;
	
	public:
		Manage(std::string name, int age, std::string position, int rank, int year_of_service) : year_of_service(year_of_service), name(name), age(age), position(position), rank(rank) {}
		
		Manager(const Manager& manager){
			name=manager.name;
			age=manager.age;
			position=manager.position;
			rank=manager.rank;
			year_of_service=manager.year_of_service;
		}
		
		Manager(){}
		
		int calculate_pay(){return 2-+rank*50+5*year_of_service;}
		
		void print_info(){
			std::cout << name << "(" << position << ", " << age << ", " << year_of_service << "년차) ==>" << calculate_pay() << "만원" << std::endl;
		}
}

기존의 Employee 클래스와 같고 year_of_service만 추가 되었다. 그리고 이를 위해 employee_list와 manage_list를 따로 구분해야 한다.

상속(Inheritance)

C++에서는 상속을 통해 다른 클래스의 내용을 그대로 포함할 수 있는 작업을 가능하게 해준다. 상속을 통해 다른 클래스의 정보를 물려받아서 사용할 수 있다.

class Base{
	std::string s;
	public:
		Base() : S("기반") {std::cout << "기반 클래스" << std::endl; }
		
		void what(){std::cout << s << std::endl;}
}

class Derived : public Base{
	std::string s;
	
	public:
		Derived() : Base(), s("파생"){
			std::cout << "파생 클래스" << std::endl;
			what();
		}
}

class Derived : public Base는 Derived가 Base를 public 형식으로 상속받겠다는 의미가 된다. 따라서 Derived 클래스에서 what을 호출할 수 있다. Derived의 생성자는 초기화 시트르에서 기반 생성자를 호출해서 기반 생성을 먼저 한 후 Derived의 생성자가 실행되어야 한다. 기반 클래스의 생성자를 명시적으로 호출하지 않을 경우 기반 클래스의 디폴트 생성자가 호출된다.

#include <iostream>
#include <string>

class Base{
	std::string s;
	
	public:
		Base() : s("기반"){std::cout << "기반 클래스" << std::endl;}
		
		void what(){std::cout << s << std::endl;}
};

class Derived : public Base{
	std::string s;
	
	public:
		Derived() : Base(), s("파생"){
			std::cout << "파생 클래스" << std::endl;
			what();
		}
};

int main(){
	std::cout << "=== 기반 클래스 생성 ===" << std::endl;
	Base p;
	
	std::cout << "=== 파생 클래스 생성 ===" << std::endl;
	Derived c;
	
	return 0;
}
=== 기반 클래스 생성 ===
기반 클래스
=== 파생 클래스 생성 ===
기반 클래스
파생 클래스
기반

기반 클래스가 생성될 때는 정상적으로 기반 클래스라고 출력된다. 파생 클래스가 생성될 때는 Base가 먼저 초기화 되면서 기반 클래스가 출력되고 그 다음 파생 클래스가 출력되고 what을 호출하게 된다. what을 호출했을 때 기반이 출력된 이유는 what 함수는 Base에 정의되어 있기 때문에 Base의 s가 출력되어 기반이라고 나오는 것이다.

그렇다면 만약 Derived에서 what을 정의해준다면?

#include <iostream>
#include <string>

class Base{
	std::string s;
	
	public:
		Base() : s("기반"){std::cout << "기반 클래스" << std::endl;}
		void what(){std::cout << s << std::endl;}
};

class Derived : public Base{
	std::string s;
	
	public:
		Derived() : Base(), s("파생"){
			std::cout << "파생 클래스" << std::endl;
			what();
		}
		void what(){std::cout << s << std::endl;}
};

int main(){
	std::cout << "=== 기반 클래스 생성 ===" << std::endl;
	Base p;
	
	std::cout << "=== 파생 클래스 생성 ===" << std::endl;
	Derived c;
	
	return 0;
}
=== 기반 클래스 생성 ===
기반 클래스
=== 파생 클래스 생성 ===
기반 클래스
파생 클래스
파생

이번에는 Derived 클래스에 Base와 같은 기능을 하는 what을 정의하였다. 이 경우 Derived를 초기화 할 때 what을 호출하면 Derived의 what을 호출하게 되어 파생을 출력하게 된다. 이런 것을 가리켜 오버라이딩이라고 한다. Derived의 what 함수가 Base의 what 함수를 오버라이딩 한 것이다.

protected

기본적으로 private 멤버 변수 혹은 멤버 함수는 어떠한 경우에서도 자기 클래스 말고는 접근할 수 없다. 하지만 종종 파생 클래스에서 기반 클래스의 데이터에 직접 접근할 필요성이 있다. 예를 들면 위에서 Employee 클래스를 기반으로 하여 Manager 클래스를 만든다면 name이나 age에 접근할 필요성이 있다. C++에서는 private과 public의 중간인 protected를 이용하여 중간 위치에 있는 접근 지시자를 지원한다. 이 키워드는 상속 받는 클래스에서는 접근 가능하고 그 외의 곳에서는 접근 불가능하다.

class Base{
	protected:
		std::string parent_string;
	public:
		Base() : parent_string("기반"){std::cout << "기반 클래스" << std::endl;}
		void what(){std::cout << parent_string << std::endl;}
};

class Derived : public Base{
	std::string child_string;
	
	public:
		Derived() : Base(), child_string("파생"){
			std::cout << "파생 클래스" <<std::endl;
			parent_string="바꾸기";
		}
		void what(){std::cout << child_string << std::endl}
};

위 코드에서 protected로 선언된 parent_string은 파생 클래스인 Derived에서 접근이 가능하다.

또한 class Derived : public Base에서

  • public으로 상속받았다면 접근 지시자들에 영향 없이 그대로 작동한다.
  • protected로 상속하였다면 public은 protected로 바뀌고 나머지는 그대로 유지된다.
  • private으로 상속하였다면 모든 접근 지시자들이 private이 된다.

사원 관리 프로그램에 적용하기

#include <iostream>
#include <string>

class Employee{
	protected:
		std::string name;
		int age;
		
		std::string position;
		int rank;
		
	public:
		Employee(std::string name, int age, std::string position, int rank):name(name), age(age), position(position), rank(rank){}
		
		Employee(const Employee& employee){
			name=employee.name;
			age=employee.age;
			position=employee.position;
			rank=employee.rank;
		}
		Employee(){}
		void print_info(){
			std::cout << name << "(" << position << ", " << age << ")==>"<< calculate_pay() << "만원" << std::endl;
		}
		int calculate_pay(){return 200+rank*50;}
};

class Manager:public Employee{
	int year_of_service;
	public:
		Manager(std::string name, int age, std::string position, int rank, int year_of_service):Employee(name, age, position, rank), year_of_service(year_of_service){}
	
		Manager(const Manager& manager):Employee(manager.name, manager.age, manager.position, manager.rank){
		year_of_service=manager.year_of_service;
	}
	
		Manager():Employee(){}
		
		int calculate_pay(){return 200+rank*50+5*year_of_service;}
		void print_info(){
			std::cout << name << "(" << position << ", " << age << ", " << year_of_service << "년차)==>" << calculate_pay() << "만원" << std::endl;
		}
};

class EmployeeList{
	int alloc_employee;
	
	int current_employee;
	int current_manager;
	
	Employee** employee_list;
	Manager** manager_list;
	
	public:
		EmployeeList(int alloc_employee):alloc_employee(alloc_employee){
			employee_list=new Employee*[alloc_employee];
			manager_list=new Manager*[alloc_employee];
			
			current_employee=0;
			current_manager=0;
		}
		void add_employee(Employee* employee){
			employee_list[current_employee]=employee;
			current_employee++;
		}
		void add_manager(Manager* manager){
			manager_list[current_manager]=manager;
			current_manager++;
		}
		int current_employee_num(){return current_employee+current_manager;}
		void print_employee_info(){
			int total_pay=0;
			for(int i=0;i<current_employee;i++){
				employee_list[i]->print_info();
				total_pay+=employee_list[i]->calculate_pay();
			}
			for(int i=0;i<current_manager;i++){
				manager_list[i]->print_info();
				total_pay+=manager_list[i]->calculate_pay();
			}
			std::cout << "총 비용: " << total_pay << "만원" << std::endl;
		}
		~EmployeeList(){
			for(int i=0;i<current_employee;i++){
				delete employee_list[i];
			}
			for(int i=0;i<current_manager;i++){
				delete manager_list[i];
			}
			delete[] employee_list;
			delete[] manager_list;
		}
};

int main(){
	EmployeeList emp_list(10);
	emp_list.add_employee(new Employee("노홍철", 34, "평사원", 1));
	emp_list.add_employee(new Employee("하하", 34, "평사원", 1));
	emp_list.add_manager(new Manager("유재석", 41, "부장", 7, 12));
	emp_list.add_manager(new Manager("정준하", 43, "과장", 4, 15));
	emp_list.add_manager(new Manager("박명수", 43, "차장", 5, 13));
	emp_list.add_employee(new Employee("정형돈", 36, "대리", 2));
	emp_list.add_employee(new Employee("길", 36, "인턴", -2));
	emp_list.print_employee_info();
	return 0;
}
노홍철(평사원, 34)==>250만원
하하(평사원, 34)==>250만원
정형돈(대리, 36)==>300만원
길(인턴, 36)==>100만원
유재석(부장, 41, 12년차)==>610만원
정준하(과장, 43, 15년차)==>475만원
박명수(차장, 43, 13년차)==>515만원
 비용: 2500만원

댓글

Discussion 원문