수리 공작소

[명품 C++ Programming] 제 3장 클래스와 객체 본문

카테고리 없음

[명품 C++ Programming] 제 3장 클래스와 객체

suleee 2024. 10. 13. 22:01

객체란?

속성과 행동을 가진 프로그래밍의 독립적인 실체

실세계는 객체와 객체 사이의 상호작용으로 이루어질 수 있기 때문에 이러한 객체 지향 프로그래밍이 절차 지향에 비해 실세계를 더 잘 반영하고 있다.

 

객체의 특징

  • 캡슐화된다. 즉, 캡슐 내부의 것을 외부는 알 수 없다.
  • 외부와 소통에 필요한 일부 요소만을 공개한다.
  • 멤버 변수와 멤버 함수로 이루어져있다. 보통 변수는 객체의 상태를 저장하고 함수는 객체의 행동을 정의한다.

객체(object) 와 클래스(class) 의 차이

클래스를 실체화시킨 것이 객체이다.

예를 들어 강아지라는 동물의 부류가 있으면 우리집 강아지 또삐, 옆집 강아지 왈왈이는 실제 강아지들인 것과 같은 맥락이다.

강아지가 클래스 역할이고 또삐 왈왈이가 객체 역할인 셈이다!

 

클래스 선언부/구현부/접근 지정자

선언부는 클래스가 어떤 멤버 함수, 멤버 변수들로 이루어져 있는지를 나타내는, 즉, 클래스의 생김새를 선언하는 곳이다.

구현부는 선언부에 있는 멤버 함수에 대한 실제 구현을 하는 곳이다.

클래스 선언부 예시

class Cricle {
private: //접근 지정자
	int radius = 5; // 멤버 변수 (이렇게 선언과 함께 초기화를 하는 것은 C++11부터 가능)
public:  //접근 지정자 
	double getArea(); //멤버 함수
}; 
// 선언부는 세미콜론으로 끝이 나야 함.

 

위와 같이 클래스를 선언할 수 있다.

 

이 클래스의 double getArea() 함수에 대한 구현부는  다음과 같이 구현될 수 있다.

double Circle :: getArea() { //::은 '범위 지정 연산자' 
	return 3.14*radius*radius;
}

 

주석으로 달아놓은 '접근 지정자'와 '범위 지정 연산자'를 알아보자.

 

  1. 접근 지정자 private / public / protected
    • private 외부에서 전혀 접근할 수가 없다.
    • public 접근 지정자로 선언된 멤버는 외부에서 자유롭게 접근할 수 있다.
    • protected  상속관계에서만 접근 가능하다.
  2. 범위 지정 연산자 ::
    • 언제 사용?
      • 네임 스페이스를 명시해줄 때
      • 이 변수 혹은 함수가 어떤 클래스의 멤버인지 명시해줄 때
      • 상속 관계에서 자식 클래스에서 부모 클래스의 멤버를 참조할 때
    •  용도
      • 컴파일러에게 특정한 범위에서 이름을 찾도록 지시하는 역함. 즉, 네임스페이스나 클래스의 범위를 명확히 하여, 이름 충돌을 방지하고 코드의 구조를 보다 명확하게 만들어 준다.

생성자

우리는 객체를 사용할 때 변수를 선언하는 것처럼 선언문을 작성해야한다.

class Circle {
};

int main() {
	Circle pizza;
	Circle ricepaper;
}

 

위와 같이 타입에 객체를 생성하고자 하는 클래스와 객체의 이름을 적어서 선언을 해주면 이는 객체는 생성하는 것이 된다!

 

객체를 생성자면 클래스에 구현되어있는 생성자라는 것이 호출되게 된다.

다음은 생성자의 특징이다.

 

  1. 클래스는 여러개의 생성자를 가질 수 있다. (파라미터가 다르게, 오버로딩)
    • 이러한 경우에 생성자 안에 코드가 겹치게 되는 경우가 있을 수 있다. 예를 들어 같은 멤버 변수를 어쨋든 같은 값으로 초기화를 해줘야 할 때라던지..
    • 이러한 코드의 중복을 줄이기 위해 C++11부터는 위임 생성자를 이용하여 코드를 간결화 할 수 있다.
  2. 생성자가 있는 이유는 객체가 생성될 때 필요한 초기 작업을 객체 지향의 원칙 중 캡슐화에 어긋나지 않게 작업을 해주기 위함이다.
    • 여기서 초기작업이란 멤버 변수 초기화가 될 수도, 동적 할당 작업이 될 수도, 네트워크 연결 작업이 될 수도...
  3. 생성자 함수는 클래스의 이름과 동일해야한다.
  4. 생성자 함수는 리턴타입을 선언하지 않는다 (void도 아니고 아예 적으며 안됨)
  5. 생성자는 객체가 생성될 때 오직 한번만 실행된다.

 

멤버 변수 초기화 방식 정리

  1. 생성자 구현 코드에서 멤버 변수 초기화
  2. 생성자 서두에 초깃값으로 초기화
  3. C++11 부터는 선언부에서 초기화 가능
Circle() : radius(0) {
} //2번 방식

 

 

기본 생성자

모든 클래스는 생성자가 꼭 하나는 있어야한다. 왜냐하면 객체가 선언될 때 무조건 호출이 하나는 되어야하기 때문이다.

그런데 클래스 내부 코드에 생성자가 있지 않은 경우가 있다. 그건 컴파일러가 모든 클래스에 기본 생성자를 삽입해주기 때문이다.

기본 생성자는 말 그대로 어떤 것도 추가 구현이 없는 기본 생성자이다.

class Cricle {
	Circle(); //기본 생성자
}

 

주의 사항 : 클래스에 추가한 생성자가 하나라도 있으면 컴파일러는 기본 생성자를 추가해주지 않는다. 따라서 이 때는 기본 생성자를 사용하고 싶으면 명시해주어야 한다.

 

소멸자

생성된 객체가 소멸되는 시점에 호출되는 함수이다.

~Circle() 이런식으로 ~를 붙이면 된다.

  1. 소멸자는 객체가 소멸될 때 마무리 작업을 위해 존재한다.
  2. 생성자와 마찬가지로 어떠한 리턴타입을 가져서도 안된다.
  3. 생성자와 다르게 소멸자는 오직 한개만 가질 수 있으며, 매개변수를 가지지 않는다.
  4. 선언되어있지 않으면 기본 소멸자가 생성된다.

소멸자는 객체의 생성된 순서 반대 순서도 호출된다.

예를 들어

int main() {
	Circle donut;
	Circle pizza;
	return 0;
}

 

return 0으로 메인함가 종료하면 main 함수 스택에 생성된 pizza donut 객체가 소멸된다.

위와 같이 선언이 되어있으면 

pizza 소멸

donut 소멸

 

접근 지정자

C++의 디폴트 접근 지정자는 private이다.

그 이유는 캡슐화의 기본 원칙이 비공개이기 때문이다.

 

기본적으로 멤버 변수는 private 으로 지정하는 것이 좋다.  

그리고 간혹, 생성자를 private 이나 protected로 선언하는 경우가 있는데, 이 경우는 의도적으로 외부에서는 객체를 생성하지 못하도록 막는 경우이고 대부분 public으로 지정한다 생성자는.

 

인라인 함수

우리가 함수를 호출하면 다음과 같은 작업으로 시간 소모가 된다.

함수 호출 -> 돌아올 리턴 주소 저장 -> CPU 레지스터 값 저장 -> 함수의 매개변수를 스택에 저장 -> 함수 실행

함수 종료 -> 함수의 리턴 값을 임시 저장소에 저장 -> 저장한 레지스터 값 CPU에 복귀 -> 돌아갈 주소를 알아내어 리턴

 

이러한 '함수 호출 오버헤드' 시간이 무시할 수  없을 정도로 커질 수도 있다.

(짧은 코드를 모두 함수로 만들어버렸을 때의 단점이라고 볼 수 있다.)

 

이러한 함수 호출로 인한 실행 속도 저하를 막기 위해 인라인 함수가 도입되어있다.

다음과 같이 inline 키워드를 이용하여 다음과 같이 선언한다.

inline int odd(int x) {
	return (x%2);
}

 

컴파일러는 이러한 인라인으로 선언된 함수들은 위의 함수 호출 과정을 거치치 않도록 그냥 함수의 코드를 그대로 삽입해버린다.

인라인 함수는 비교적 작은 함수들을 대상으로 사용하기 유용하다.

 

장단점 

  • 프로그램의 실행 속도 향상 가능
  • 프로그램 크기가 늘어나는 단점 존재

작은 함수에 사용하기 적합하다. (단순한 getter/setter같은)

또, 인라인 함수는 강제 명령이 아니기 때문에 컴파일러는 불필요한 경우 inline 선언을 무시할 수도 있다. 컴파일러에 따라 static 변수, 반복문, switch문 등을 가진 함수는 인라인 함수를 허용하지 않는 경우도 있다.