모두의코드: 씹어먹는 C++ - <6 - 1. C++ 표준 문자열 & 부모의 것을 물려쓰자 - 상속>

YP J·2022년 6월 17일
0

모두의코드 C++

목록 보기
6/11
  • C++ 표준 문자열 (std::string)
  • 상속 (inheritance)
  • 오버라이딩(overriding)
  • protected 키워드

표준 'string' 클래스

  • 짧은 문자열에 대해서는 동적으로 메모리를 할당하지 않고 그냥 지역변수로 보관을 하고,

  • 문자열을 복사 할때 그 복사된 문자열의 내용이 바뀌지 않는한 실제로 데이터를 복사하는것이 아니라 원래 문자열을 가리키기만 한다,
    (어떤 라이브러리에 사용함에 따라 아닐수도 있음).

#include <iostream>
#include <string>


int main()
{
    std::string s = "abc";

    std::cout << s << std::endl;
    
    return (0);
}
  • "abc"는 컴파일러 상에서 C형식 문자열로 인식됩니다.

  • 즉, 위 문장은 string 클래스의 인자를 const char *로 받는 생성자를 호출한것.


#include <iostream>
#include <string>

int main()
{
    std::string s = "abc";
    std::string t = "def";
    std::string s2 = s; 

    std::cout << s << " 의  길길이  : " << s.length() << std::endl;
    std::cout << s << " 뒤에 " << t << " 를 붙이면 " << s + t << std::endl;

    if (s == s2)
    {
        std::cout << s << " 와 " << s2 << " 는 같다 " << std::endl;
    }
    if (s != t)
    {
        std::cout << s <<" 와 " << t << "는 다르다" << std::endl;
    }
    return (0);
}
  • length() ,
  • 연산자 오버로딩 사용한 + 연산자,
  • 연산자 오버로딩 사용해서 문자열 비교할때 C였으면 strcmp 함수를 사용했어야 할것을
    • if (s == s2) std::cout << s << " 와 " << s2 << " 는 같다 " << std::endl;
      와 같이 != 나 == 로 비교 가능 (C에선 문자열의 주소값을 비교하는것이였으니 불가능!)

string 제공하는 함수들 참고 (+ insert , erase, replace...)
https://modoocode.com/233


사원 관리 프로그램

  • 회사의 사원들의 월급을 계산해서 한달에 총 얼마나 되는 돈을 월급으로 지출해야 하는지 알려주는 단순한 프로그램.

  • 먼저 각 사원들의 정보를 클래스로 만들어서 데이터를 보관하도록해보자.

  • 사원들의 필요한 데이터는 이름, 나이, 직책, 직책 넘버

#include <iostream>
#include <string>

class Employee
{
    private:
        std::string _name;
        int _age;

        std::string _pos; // 직책(이름)
        int _rank;       // 직책 순위(클수록 높음)

    public:
        Employee(std::string name, int age, std::string pos, int rank)
        : _name(name), _age(age), _pos(pos), _rank(rank) {}

        // 복사 생성자
        Employee(const Employee& employee)
        {
            _name = employee._name;
            _age  = employee._age;
            _pos  = employee._pos;
            _rank = employee._rank;
        }

        // 디폴트 생성자
        Employee() {}

        void print_info()
        {
            std::cout << _name << " (" << _pos << " , " << _age << ") ==> "
            << calcul_pay() << "만원" <<std::endl;
        }
        int calcul_pay()
        {
            return (200+_rank*50);
        }
};

int main()
{
    Employee s1("junyopar", 123, "high", 123);
    Employee s2("elonmusk", 122, "mid", 122);

    s1.print_info();
    s2.print_info();
}
  • 이제 각가의 Employee 클래스를 만들었으니, 이 Emplpyee 객체들을 관리할 수 있는 EmployeeList 클래스를 만들어서 간단하게 처리해보자.

아래 멤버 변수들을 이용해 사원 데이터 처리.

int alloc_emp;	//할당한 총 직원수 - 할당된 크기를 알려주는 배열
int curr_emp;	// 현재 직원수  - 현재 emp_list에 등록된 사원수
Employee **emp_list;// 직원 데이터
  • 언제나 동적으로 데이터를 할당하는것을 처리 하기 위해서는 두개의 변수가 필요하다.
  • 하나는 현재 할당된 총 크기이고
  • 다른 하나는 그중 실제로 사용하고 있는 양이다.
  • 이렇게 해야지만 할당된 크기보다 더 많은 양을 실수로 사용하는것을 막을 수 있다.
  • emp_listEmploy** 타입으로 되어있는 이유
    • 우리가 이를 Employee* 객체를 담는 배열로 사용할것이기 때문.
    EmployeeList(int alloc_emp): alloc_emp(alloc_emp)
    {
    	emp_list = new Employee*[alloc_emp];
      curr_emp = 0;
    }
  • 사원 추가 함수
void add_emp(Emplyee* emp)
{
  // 사실 current_employee 보다 alloc_employee 가 더
  // 많아지는 경우 반드시 재할당을 해야 하지만, 여기서는
  // 최대한 단순하게 생각해서 alloc_employee 는
  // 언제나 current_employee 보다 크다고 생각한다.
  // (즉 할당된 크기는 현재 총 직원수 보다 많음)
  emp_list[curr_emp] = emp;
  curr_emp++;
}

code

#include <iostream>
#include <string>

class Employee
{
    private:
        std::string _name;
        int _age;

        std::string _pos; // 직책(이름)
        int _rank;       // 직책 순위(클수록 높음)

    public:
        Employee(std::string name, int age, std::string pos, int rank)
        : _name(name), _age(age), _pos(pos), _rank(rank) {}

        // 복사 생성자
        Employee(const Employee& employee)
        {
            _name = employee._name;
            _age  = employee._age;
            _pos  = employee._pos;
            _rank = employee._rank;
        }

        // 디폴트 생성자
        Employee() {}

        void print_info()
        {
            std::cout << _name << " (" << _pos << " , " << _age << ") ==> "
            << calcul_pay() << "만원" <<std::endl;
        }
        int calcul_pay()
        {
            return (200+_rank*50);
        }
};

class EmployeeList
{
    private:
        int alloc_emp;
        int curr_emp;
        Employee** emp_list;
    public:
        EmployeeList(int alloc_emp) : alloc_emp(alloc_emp)
        {
            emp_list = new Employee*[alloc_emp];
            curr_emp = 0;
        }

        void add_emp(Employee* emp)
        {
             
            emp_list[curr_emp] = emp;
            curr_emp++;
        }

        int curr_emp_num() { return curr_emp; }

        void print_emp_info()
        {
            int total_pay = 0;
            for (int i= 0; i <  curr_emp; i++)
            {
                emp_list[i]->print_info();
                total_pay += emp_list[i]->calcul_pay();
            }
            std::cout << "total pay : " << total_pay << "만원 " << std::endl;
        }

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

int main()
{
    Employee s1("junyopar", 123, "high", 123);
    Employee s2("elonmusk", 122, "mid", 122);

    s1.print_info();
    s2.print_info();

    EmployeeList emp_list(10);
    emp_list.add_emp(new Employee("노홍철", 34, "평사원", 1));
    emp_list.add_emp(new Employee("하하", 34, "평사원", 1));
    emp_list.add_emp(new Employee("유재석", 41, "부장", 7));
    emp_list.add_emp(new Employee("정준하", 43, "과장", 4));
    emp_list.add_emp(new Employee("박명수", 43, "차장", 5));
    emp_list.add_emp(new Employee("정형돈", 36, "대리", 2));
    emp_list.add_emp(new Employee("길", 36, "인턴", -2));
    emp_list.print_emp_info();
    while (1)
    ;
}

근속 년수를 포함시켜라 특정 직급에는

  • manager 클래스 추가
  • 사실 Employee 클래스랑 거의 같지만
  • EmplyeeList 클래스 에서도 Employee 와 Manager 를 따로 처리해야된다.

Manager 클래스 코드

class Manager {
  std::string name;
  int age;

  std::string position;  // 직책 (이름)
  int rank;              // 순위 (값이 클 수록 높은 순위)
  int year_of_service;

 public:
  Manager(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 200 + rank * 50 + 5 * year_of_service; }
  void print_info() {
    std::cout << name << " (" << position << " , " << age << ", "
              << year_of_service << "년차) ==> " << calculate_pay() << "만원"
              << std::endl;
  }
}

~~ 그래서 상속!!

상속 (Ingeritance)

  • 위의 Manager 클래스의 코드 자체가 Employee의 대부분을 포함하고 있기때문에.

  • C++에서는 이와 같은 경우, 다른 클래스의 내용을 그대로 포함할수있는 작업을 가능하도록 해준다

  • 그게 바로 상속이다.

  • C++ 에서 상속을 통해 다른 클래스의 정보를 물려받아서 사용할수 있다.

  • 간단한 예시를 보자.

#include <iostream>
#include <string>

class Base
{
    std::string s;

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

기반 클래스, 아래는 기반클래스(Base)를 물려받을 파생 (Derived)클래스의 모습

  • 보톡 부모-자식 클래스라고도 한다.
  • 근데 여러명의 부모를 가진다는 어감 때문에 기반, 파생 클래스 라 부르는것을 선호.
class Derived : public Base
{
    std::string s;
    
    public:
        Derived() : Base(), s("파생")
        {
             std::cout << "파생 클래스" << std::endl;

             // Base에서 what()물려 받았으므로
             // Derived 에서 호출 가능하다.
             what();  
        }
};
  • 가장 먼저 class의 정의부분
class Derived : public Base
  • Derived가 Base를 public형식으로 상속 받겠다는 의미.
  • public형식으로 상속받는게 무엇인가? (는 좀있다)
  • 아무튼 위처럼 상속받은후 Derived 모습
    • 마치 Derived 클래스 안에 Base 클래스의 코드가 그대로 있는것처럼.
    • Derived 클래스에서 Base 클래스의 what함수 호출 가능한이유
Derived() : Base(), s("파생") {
   std::cout << "파생 클래스" <<  std::endl;

  // Base 에서 what() 을 물려 받았으므로
  // Derived 에서 당연히 호출 가능하다
  what();
}
  • Derived 의 생성자 호출 부분

  • 위처럼 초기화 리스트기반의 생성자를 호출해서 기반의 생성을 먼저 처리한 다음에 ,

  • Derived의 생성자가 실행 되어야 한다.

  • 따라서 아래처럼

Derived () : Base() ,s("파생")
  • 초기화 리스트에서 Base를 통해 기반의 생성자를 먼조 호출하게 된다.

  • 참고로 기반클래스의 생성자를 명시적으로 호출하지 않을 경우
    디폴트 생성자 호출 된다.

  • 위에서 Derived 의 s에 "파생" 을 넣게 되고,

  • Dervied 생성자 내부를 실행하기 전에

  • Base의 생성자를 먼저 호출한다.

  • 따라서 파생 클래스 생성 바로 아래에 파생 클래스가 출력하기 이전에 Base 의 생성자가 호출되어서 기반 클래스가 먼저 출력하게 되는것

what()

what() 호출하는 부분을 보자

  • what() 호출 했을때 파생 이 아니라 기반 으로 출력되는데 그이유는?
  • what 함수는 Base에 정의가 되어 있기 때문에
  • Dervied의 s가 아니라 Base의 s가 출력 되어 "기반" 이라고 나옴.
#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;

             // Base에서 what()물려 받았으므로
             // Derived 에서 호출 가능하다.
             what();  
        }
        void what()
        {
            std::cout << s <<std::endl;
        }
};

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

    std::cout << " === 파생 클래스 생성 === " << std::endl;
    Derived c;

    return 0;
}
>>  
=== 기반 클래스 생성 === 
기반 클래스
 === 파생 클래스 생성 === 
기반 클래스
파생 클래스
기반
  • 그렇다면 Dervied 에도 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;

             // Base에서 what()물려 받았으므로
             // Derived 에서 호출 가능하다.
             what();  
        }
        void what()
        {
            std::cout << s <<std::endl;
        }
};

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

    std::cout << " === 파생 클래스 생성 === " << std::endl;
    Derived c;

    return 0;
}
>>
 === 기반 클래스 생성 === 
기반 클래스
 === 파생 클래스 생성 === 
기반 클래스
파생 클래스
파생
  • Dervied와 Base 에 둘다 what() 함수가 정의 되어 있다.

  • 이경우 Dervied 에서 what을 호출하게되면 무엇을 호출할까?

  • 사실 두 함수는 같은 이름이지만(인자도 같지만)

  • 다른 클래스에 정의되어 있기 때문에 다른 함수로 취급된다.

  • 위의 경우 Dervied 에 what함수가 정의 되어 있기 때문에 Dervied 생성자에서 what을 호출할때 굳이 멀리 Base으 함수 까지 뒤지지 않고

  • 바로 앞에 있는 Dervied 의 what을 호출하게 된다.

  • 이런것을 가리켜 오버라이딩 이라고 한다.

  • 즉 Derived 의 what 함수가 Base의 what함수를 오버라이딩 한것.

  • 간혹 함수 오버로딩 과 혼동하는데 ,
  • 오버로딩은 같은 이름의 함수를 인자를 달리 하여 정의하는것을 의미.
  • 상속에서 오버라이딩 과는 전혀 다른 이야기.

새로운 친구 protected

#include <iostream>
#include <string>

class Base 
{
 private:
  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() : child_string("파생"), Base()
  {
        std::cout << "파생 클래스" <<  std::endl;

        // 그렇다면 현재 private 인 Base 의
        // parent_string 에 접근할 수 있을까?
        parent_string = "바꾸기";
  }

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

이코드는 컴파일 에러.

  • private 멤버 변수들은 그 어떠한 경우에서도 자기 클래스 말고는 접근 할 수없다.

  • 하지만 상속 받은 클래스에서 직접 기반 클래스의 private 데이터에 접근할 필요가 있다.

  • C++에서 다행히도 private , protected ,public 즉 중간 단계인 protected 키워드를 둬서

  • 상속 받는 클래스에서는 접근 가능하고 그외의 기타 정보는 접근 불가능

    • private: 자신만의 비밀번호
    • protected: 집 현관문 비밀번호(가족들은 알지만 그외 접근불가)
    • public: 집주소(가족뿐만아니라 그외도 접근가능)
#include <iostream>
#include <string>

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() : child_string("파생"), Base()
  {
        std::cout << "파생 클래스" <<  std::endl;

        // 그렇다면 현재 private 인 Base 의
        // parent_string 에 접근할 수 있을까?
        parent_string = "바꾸기";
  }

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

int main() {}

얘는 컴파일 됨!


class Dervied : public Base

  • 위 코드 에서 public 키워드의 의미.

  • 만일 위처럼 public 형태로 상속하였다면

    • 기반 클래스의 접근 지시자들에 영향 없이 그대로 작동한다.
    • 즉 파생 클래스 입장에서 public은 그대로 public이고
      protected 는 그대로 protected ~ 이다.
  • 만일 proted 로 상속 했다면

    • 파생 클래스 입장에서 public은 protected로 바뀌고
      나머지는 그대로 유지
  • 만일 private로 상속 했다면

    • 파생 클래스 입장에서 모든 접근 지시자들이 private가 된다.
#include <iostream>
#include <string>

class Base
{

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

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

             // Base에서 what()물려 받았으므로
             // Derived 에서 호출 가능하다.
             what();  
        }
        void what()
        {
            std::cout << s <<std::endl;
        }
};

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

    std::cout << " === 파생 클래스 생성 === " << std::endl;
    Derived c;

    return 0;
}
  • Base 객체에서 s 에 접근한다면 public이므로 main함수에서도 접근 가능하지만
  • Dervied 에서 s 에 접근하려고 한다면
  • private로 상속 받았기 때문에
  • Base에서 public이더라도
  • Derived 에서는 private로 처리되어서 접근 불가능.

//
사원 프로그램에 적용시켜보기 ~
pass

profile
be pro

0개의 댓글