클래스
클래스 기본
구조체는 어떤한 것의 정보(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);
- 위와같이 하나의 생성자에 값이 안들어오면 초기값을 기본 인수로 활용하는 방법도 있다.
- 인수를 안받으면 0으로, 받으면 그걸로
- 생성자를 생략하면 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 |