[Modern C++] 6.1. 표준 문자열, 상속, 오버라이딩, protected

윤정민·2023년 7월 4일
0

C++

목록 보기
18/46

1. 표준 string 클래스

C언어 스타일의 문자열에서 불편한 요소를 개선한 클래스로 C++에서는 string클래스 사용을 지향한다.

  • 특징(빠른속도를 위한)
    • 짧은 문잘열에 대해서는 동적으로 메모리를 할당하지 않고 그냥 지역 변수로 보관
    • 문자열을 복사할 때 그 복사된 문자열의 내용이 바뀌지 않는 한 실제로 데이터를 복사하는 것이 아니라 원래 문자열을 가리키기만 함

2. 상속

부모 클래스의 멤버 변수와 메소드를 자식 클래스가 재사용하는 것이다. 이 때 멤버 변수나 메소드에 접근 제한자를 더하여 아무나 접근할 수 없도록 할 수 있다.

2.1. Employee Class

직원을 관리하는 Employee class를 만들고 List로 관리하기 위해 Employee List class를 만들어보자.

#include <iostream>

class Employee {

public:
	Employee(std::string name, int age, std::string position, int rank)
		: name(name), age(age), position(position), rank(rank){}

	Employee(const Employee& e) {//복사생성자
		name = e.name;
		age = e.age;
		position = e.position;
		rank = e.rank;
	}
	Employee() {}

	int calculate_pay() { return 200 + rank * 50; }//기본급 200 + 직급수당 50*rank

private:
	std::string name;
	int age;

	std::string position;
	int rank;
};

class EmployeeList {
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++;
	}

	~EmployeeList() {
		for (int i = 0; i < current_employee; ++i) {
			delete employee_list[i];
		}
		delete[] employee_list;
	}

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

이때 연차가 높은 사원은 따로 Manager class의 인스턴스로 관리해야 한다면 Manager class를 만들어 Emplyee class에서 연차를 나타내는 변수 하나만 추가해 만들면 된다. 하지만 Manager 말고 새로운 직급이 생긴다면 클래스 하나를 추가하기 위해 코드 전체를 바꿔야 한다. 이러한 문제를 상속을 통해 해결할 수 있다.

2.2. 상속 사용법

Base Class를 만들고 이를 상속받는 Derived Class를 만들어보자.

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

class Derived : public Base	//멤버 변수와 함수를 해당 접근지정자로 받는다는 뜻
{
public:
    Derived() : Base(), child_string("파생") {
        std::cout << "파생 클래스" << std::endl;

        //Base의 parent_string은 protected, public일때 접근 가능
        parent_string = "바꾸기";
    }

    void what() { std::cout << child_string << std::endl; }
private:
    std::string child_string;
};

int main() {
    std::cout << " === 기반 클래스 생성 ===" << std::endl;
    Base p;

    std::cout << " === 파생 클래스 생성 ===" << std::endl;
    Derived c;
    return 0;
}
  • 실행결과
    • 파생 클래스를 생성하니 기반클래스가 생성된 후 파생 클래스가 생성됨

2.3. Employee에 적용

Employee class를 상속받은 Manage class를 만들면 불필요한 코드를 작성할 필요가 없다.

#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) {
    // 사실 current_employee 보다 alloc_employee 가 더
    // 많아지는 경우 반드시 재할당을 해야 하지만, 여기서는
    // 최대한 단순하게 생각해서 alloc_employee 는
    // 언제나 current_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;
}

느낀점

공부를 하다보니 내가 당연하다고 썼던 것들을 쓰는 이유가 궁금해진다. 그 중 하나가 객체를 포인터로 만드는 이유다. 그냥 객체로 만드는 것과 객체 포인터로 만드는 것은 어떤 차이점이 있을까?

1. 객체와 객체 포인터 차이

아래의 코드를 보면서 이해해보자.

  • 객체로 선언된 a1
    • Stack에 올라감
    • sizeof(A) = 8byte
  • 객체 포인터로 선언된 a2
    • a2자체는 stack에 올라감
    • a2가 담고있는 것은 주소값으로 실제 a2의 객체를 가리키고 있음
    • a2의 실제 객체는 Heap 메모리에 올라가 있음
    • sizeof(a2) = 4byte = 주소값의 크기
    • sizeof(*a2) = 8byte = 실제 a2의 크기
class A
{
public:
	int _hp = 100;
    int _level = 4;
};

int main()
{
	A a1;
	A* a2 = new A();

	return 0;
}

2. 포인터 객체를 사용하는 이유

  1. 상속 관계에 있는 클래스들을 바꿔가며 사용가능하기 때문
  2. 생명 주기의 관리를 각각 할 수 있기 때문
  3. 클래스 안에 클래스를 가질 경우, 크기가 비대해지는 것을 방지하기 위해
profile
그냥 하자

0개의 댓글