본문 바로가기
프로그래밍/c++

c++배워보기(4)

by 나도한강뷰 2023. 3. 19.

클래스

클래스 기본

  • 구조체는 어떤한 것의 정보(type)들을 모아놓은게 구조체이고

  • 클래스는 정보(type)들 뿐만아니라 기능(func)까지 모아놓은 형태이다.

    • 클래스는 섞어서 모을 수 있고, 구조체는 정보만 or 함수만 모을 수 있다.
  • go는 클래스가 없고 특성 타입에 대한 method(전용 함수)만 있다.

  • 선언: class 클래스명 {
    접근지정자:
    변수 선언;
    함수 선언;
    }

  • 멤버는 데이터 멤버, 멤버 함수로 구성되어 있다.

  • 멤버 함수는 밖에서 선언되어 있음

    • 선언 리턴값_형 클래스명::멤버_함수명(인수 리스트){}
      • ::는 범위 결정 연산자로, 클래스가 범위다라는걸 나타낸다.
  • 멤버는 . 연산자로 접근한다.

  • 이러한 클래스를 통해서 객체를 생성하고 소멸시키는데, 아래와같이 메모리를 동적으로 할당했다 삭제할 수 있다.

      car* pcar;
      pcar = new car;
    
      pcar->num = 1234;
      pcar->gas = 20.5;
    
      delete pcar;
  • 동적으로 할당했기에, 포인터를 통해서 접근하는데, 그렇기에 ->연산자로 값에 접근한다.

  • private 멤버

    • private:로 시작한다.
    • 그러면 외부에서 private멤버에 접근할 수 없어진다.
    • 하지만 private멤버도 public 멤버함수로 접근할 수 있다.
    • 그렇기에 public함수에 필터를 넣어서 private에 값을 넣도록 구성할 수 있다.
    • 이런것을 캡슐화라고 한다.
    • 접근지정자를 생략하면 private이 된다.(구조체는 public이 된다.)
  • 멤버함수를 인라인 함수로 만들기

    • 내부에서 한줄로 본체를 선언해버리면 된다.
    • 속도향상에 도움이 된다.

클래스 기능

  • 초기화 방법

  • 생성자(consstructor)라는 기능을 통해서 초기화 과정을 위탁한다

    • 클래스로부터 객체가 생성될때 자동적으로 호출되는 특수기능 멤버 함수
    • 클래스명::클래스명(인수 목록)
    • 함수이름을 클래스명으로 구성한다.
    • 생성자는 리턴값이 없다
    • 생성자는 일반적으로 public으로 만든다. 다만 객체를 생성하지 않을 목적으로 만들어진 클래스는 생성자를 private에 만드는 경우도 있다.
    • private에 생성자를 만들면 객체를 생성할 수 없기때문이다.
    • 생성자 오버로드: 여러 인수를 가진 동일 생성자를 만드는것
      • 인수를 안받으면 0으로, 받으면 그걸로
        class car {
          public:
              car();
              car(int n, double g);
        }
        car car1;
        car car2(1234, 20.5);
      • 위와같이 클래스와 동일이름으로 생성자를 만들기에 객체 선언자체가 생성자를 호출하게 되는것이다.
        class car {
          public:
              car(int n=0, double g=0);
        }
        car car1;
        car car2(1234, 20.5);
      • 위와같이 하나의 생성자에 값이 안들어오면 초기값을 기본 인수로 활용하는 방법도 있다.
    • 생성자를 생략하면 default constructor가 생기게된다. 기본생성자라고 한다.
    • 컴파일러가 텅빈 생성자를 호출한다.
  • 객체 배열 생성

      car mycar[3]={
          car(),
          car(1234,25),
          car()
      };
      car cars[3];
    • 위와같이 생성할수 있다.
    • 다만 객체 배열은 인수없는 생성자로만 생성한다.
  • 정적 멤버

    • static 기능을 통해서 만든 멤버이다.
    • 이경우 클래스 전체에 연결되게 된다.
    • 하나의 값이 클래스를 통해서 생성된 모든 객체랑 상호작용하게 된다.
      class car{
        public:
            static int sum;
            car();
            void setcar(int n, double g);
            void show();
            static void showSum();
      };
      car::showSum();
    • 여기서 sum은 생성자로 초기화 시킬 수 없다.
    • 정적 멤버는 car:: 을 붙여서 호출해야된다.
    • 정적 멤버는 일반 멤버에 접근 할 수 없다.

      새로운 클래스(상속)

  • 상속

    • car라는 클래스를 만들고, 그 클래스 내용을 바탕으로 새로운 클래스를 만드는것을 상속이라고 한다.

    • 이때 car는 기본 클래스가 되고, 새로 만들어진 클래스는 파생 클래스(derived class)라고 한다.

      class car {}
      class Racingcar : public Car{
        추가할 멤버 선언
      }
    • 기본 클래스를 선언하는 것도 public인지 private인지 선택해야된다.

    • 파생된 클래스도 생성자를 갖게되는데,

    • 기본 클래스의 생성자가 먼저 실행되고, 그후 파생 클래스의 생성자가 실행된다.

    • 기본(슈퍼) 클래스의 생성자는 파생 클래스가 상속받지 않고, 기본 클래스의 인수없는 생성자가 자동으로 호출됩니다.

    • 파생 클래스는 객체화될때, 먼저 기본 클래스의 정보를 가져와야되고, 그에따라서 기본 클래스의 생성자가 실행되는것으로 보인다.

    • 이러한 슈퍼 클래서의 생성자도 선택할 수 있는데,

      class car{
        public:
            car();
            car(int n, double g);
      };
      class racingCar : public car{
        public:
            racingCar();
            racingCar(int n, double g, int c);
      };
      racingCar::racingCar(int n, double g, int c) : car(n,g)
      {
      
      }
    • 위와같이 함수를 선언하면, 파생 클래스의 생성자에 원하는 슈퍼 클래스의 생성자를 조합 할 수 있다.

  • 접근권한

    • 파생이 수퍼 클래스에 접근할때, 수퍼 클래스에서 public 맴버함수를 통해서 private으로 선언된건 접근 불가능하다.
    • 이런경우, protected라는 접근지정자를 사용한다.
    • 그럼 파생클래스도 접근이 가능해 진다.
    • 접근 지정자의 종류에 따른 상속시,
      • public, protected, private : public -> public, proteced, 접근불가
      • public, protected, private : protected -> protected, protected, 접근불가
      • public, protected, private : private -> private, private, 접근불가
    • 결국 상속시 접근지정자에 의해서 보안이 강한순으로 덮여쓰여진다고 생각하면 된다.
  • 가상 함수

    • 기존 멤버 함수 오버라이드하기

      • 기본 클래스와 완전히 동일한 함수를 선언하는 방법
      • 기존함수를 파생 클래스의 멤버함수로 덮어치기하게 된다.
      • 이걸 오버라이드라고 한다.(대신 동작하는 것)
    • 기본 클래스형 포인터를 사용하여서 파생 클래스의 객체도 가리킬수 있다.

      car* pcar;
      racingCar rccar1;
      pcar = &rccar1;
      
      pcar->show();
    • 위와같이 기본 클래스의 포인터를 이용해서 파생클래스의 객체를 다루면 어떻게 될까?

      • 그럼 오버라이드된 함수를 호출하는게 아닌, 기본 클래스의 함수를 호출 하게 된다.
      • 기본 클래스의 정보에 의해서 기본클래스의 멤버 함수를 가르키게 된다.
    • 이런경우를 해결하기위해서 나온게 virtual 선언이다.

    • 멤버함수 선언시 virtual을 앞에 붙인다.

      • 그럼 동일한 상황에서 기본클래스의 함수가 아닌, 파생클래스의 오버라이드된 함수를 호출하게 된다.
      • 오버로드와 오버라이드
        • overload: 같은 이름의 인수가 다른 함수를 여러개 정의하는것
        • override: 파생클래스에서 함수명과 인수가 100% 동일함 함수를 새로 정의하는 기능
    • pure virtual function

      • virtual 멤버함수 = 0;
      • 이런 함수를 하나라도 존재하는 클래스는 객체를 생성할 수 없게된다.
      • 이것을 추상 클래스 라고한다. (abstract class)
      • 이런 클래스를 이용해서 파생클래스를 만드는데 이용한다.
        • 그러기위해서는 virtual로 선언된 부분을 파생 클래스에서 오버라이드해야된다.
        • 이러한 추상 클래스는 파생된 클래스를 관리하는 형이 될 수 있다.
          typeid()를 통해서 객체의 클래스를 알아 낼수 있고,
          그를 통해서 객체의 클래스를 기반으로한 조건식들이 가능해진다.
          decltype(식)
  • 클래스의 계층

    • 클래스는 파생 클래스의 파생도 가능하다

    • 다중 상속받는것도 가능하다.

      class derivedClass : aaclass, bbclass {
      
      };
    • 이런경우 동일한 멤버를 가지면 어떻게 하지?

      • 무엇을 상속받을지 결정할 수 없으면, 컴파일 불가능해진다.
      • 이럴때 :: 연산자를 사용한다.
        drv.showbs(); error
        drv.Base1::showbs();
        drv.Base2::showbs();
      • 위와같이 선언해야된다.
      • 맴버함수 앞에 누구로 부터 상속받았는지를 명시적으로 쓰지않으면 컴파일 에러가 발생한다.
    • 다이아몬드 상속

      • 하나의 뿌리에서 나온 2개의 파생 클래스를 동시에 새로운 클래스로 상속하게될때, 뿌리 클래스의 무언가를 선언할때, 2개중 어느 클래스를 통해서 접근해야되는가?
      • virtual base class라는 기능을 사용해야된다.
      • class derived : 접근지정자 virtual origin_class{};
      • 위와같은 식으로 파생받게되면, orign_class를 가상 기본함수로 만들었기때문에 호출이 가능해진다.

클래스 추가 기능

  • 연산자 오버로드
  • 클래스의 형변환
  • 메모리 확보 및 해제
    • 클래스에서 객체가 소멸될때 자동적으로 호출되는 멤버함수: 소멸자(destructor)
    • 클래스명::~클래스명() 으로 선언한다.
  • 객체 초기화시, 다른 객체의 값을 대입으로 초기화를 시도하면, 객체의 값이 카피되는게 아니라, 주소값이 복사됨으로, 사실상 한개의 객체만 생성되게된다.
    • 복사 생성자를 정의해야된다.
      • 그러기위해서는 메모리를 먼저 확보
      • 클래스명::클래스명(const 래퍼런스인수) 와 같은 식으로 진행하면, 초기화시, 내가 원하는 객체의 정보를 가져다가 새로운 객체에 값을 대입할 수 있게된다.
    • 대입연산자 오버로드해서 해결하기
      • 클래스명& 클래스명::operator=(const 레퍼런스 인수)
      • 이런식으로 정의해놓으면 =를통해서 값을 복사할때 주소값이 복사되는게 아니라, 메모리값을 확보하면서 값자체를 복사해서 넣을 수 있다.
  • 템플릿 클래스
    • 템플릿을 이용하여서 여러가지 자료형에 알맞는 클래스를 생성할 수 도 있다.
  • 예외처리: try-catch 구문기본함수로 만들었기때문에 호출이 가능해진다.

'프로그래밍 > c++' 카테고리의 다른 글

c++배워보기(3)  (0) 2023.03.18
c++배워보기(2)  (0) 2023.03.13
c++ 배워보기(1)  (0) 2023.03.08