본문 바로가기

Programming Languages/C++

[클래스와 객체]-클래스 선언과 객체 정의

클래스는 C++ 언어에서 객체지향 개념을 구현하기 위한 도구로서, 프로그램에서 사용하고자 하는 객체들에 대한 형판을 정의한 것으로 볼 수 있다. 즉, 클래스와 객체의 관계는 자료형과 변수의 관계와 유사하다. 그러나 자료형이 변수가 저장할 데이터의 형태만 정의한 반면, 클래스는 각각의 객체를 표현하는 속성과 함께 그 객체들에 대한 메시지를 처리하는 메소드를 정의해 놓은 것 이다. 그리고 객체는 이러한 형판에 따라 만들어진 사례가 되는 것이다. C++ 언어에서는 클래스 안에 정의한 속성들을 데이터 멤버(data member)라고 부르고, 메소들르 멤버함수(member function)라고 부른다.

클래스의 선언

클래스 선언문의 형식은 다음과 같다.

class ClassName {
 가시성_지시어_1:
  데이터 멤버 또는 멤버 함수 리스트;
 가시성_지시어_2:
    데이터 멤버 또는 멤버 함수 리스트;
   ......
 };

ClassName은 사용자가 정의하는 클래스이 명칭으로, 그 클래스의 의미를 쉽게 이해할 수 있는 이름을 쓰는 것이 좋다. 가시성 지시어[visibility specifier, 또는 사용권한 지시어(access specifier)]는 그 다음에 나열되는 데이터 멤버나 멤버함수들이 외부에 공개되는 범위를 나타낸다. 지정된 가시성은 다음 지시어가 나올 때까지 유효하다. 가시성 지시어 뒤에는 클래스의 데이터 멤버 및 멤버함수들이 나열된다.

가시성 지시어는 private, protected 및 public의 세 가지 종류가 있다.

가시성 지시어 공개되는 범위
private * 소속 클래스의 멤버함수
* 친구 클래스의 멤버함수 및 친구함수
public * 전 범위

private는 해당 클래스의 멤버함수에만 공개되고, 외부에 대해서는 감추어짐을 나타낸다. 그러므로 private를 이용하여 정보은닉을 실현할 수 있다. 반면 public은 어느 곳에서든 액세스할 수 있도록 허용된 것이므로, 외부를 위한 인터페이스 용으로 활용된다. 가시성이 지정되지 않은 경우는 private로 처리된다. (#구조체는 지정하지 않으면 public으로 처리)

데이터 멤버의 선언

데이터 멤버는 일반적으로 private 멤버로 선언한다. 물론 데이터 멤버를 공개하는 것이 불가능한 것은 아니다. 그러나 객체의 모델링은 객체의 행위를 중심으로 이루어지며, 데이터 멤버는 그러한 행위와 관련된 객체 내부의 상태로 보는 것이 일반적이어서 주로 private로 선언하는 것이다.

데이터 멤버 선언은 구조체에서 데이터 항목들을 선언하는 것과 동일한 형태를 취한다.

멤버함수의 선언

멤버함수는 클래스 선언문 내에 선언하거나, 클래스 선언문에는 멤버함수의 원형만 선언해 놓고 외부에 별도로 멤버함수를 정의한다. 멤버함수가 매우 작은 경우 클래스 내부에 선언하며, 이 경우 inline 함수로 선언한 효과가 있다. 그렇지 않은 경우 클래스 선언문 외부에 별도로 멤버함수를 정의한다. 객체를 사용하기 위한 인터페이스로 클래스 외부에 제공하기 위한 멤버함수는 public으로 가시성을 지정하여 어느 위치에서든 자유롭게 사용할 수 있도록 한다.

객체의 정의 및 사용

클래스 선언문은 클래스에 해당되는 객체가 갖게 되는 멤버들에 대하여 선언한 것이며, 실제 객체는 일반 자료형의 변수를 정의하듯 별도로 정의하여야 한다. 여기에서 객체를 정의한다는 것은 실제 객체를 만드는 것을 의미한다. 다음은 객체를 정의하는 기본적인 형식이다. 변수를 정의하는 것과 같은 형식을 사용하는 것을 볼 수 있다.

      ClassName objName;
      ClassName objName1, objName2, ...;

이와 같이 정의된 객체들은 구조체와 유사한 형식으로 사용할 수 있다. 객체의 멤버들은 멤버선택 연산자( . )로 선택하여 사용한다.

예) 카운드 기능이 있는 계수기

Counter 클래스의 메소드

메소드 비고
void reset() 계수기의 값을 0으로 지운다
void count() 계수기의 값을 +1 증가시킨다
int getValue() 계수기의 현재 값을 읽는다

Counter 클래스의 속성

속성 비고
int value 계수기의 현재 값을 저장한다

다음의 Counter.h는 위의 사항에 맞게 클래스 Counter를 선언한 것이다.

// 소스코드 4-1 : Counter.h
#ifndef COUNTER_H_INCLUDED // Counter.h가 중복 include되지 않았는지 검사

#define COUNTER_H_INCLUDED  // Counter.h가 처음 include될 때 정의됨

class Counter { // 클래스 Counter의 선언 시작
	int value;  // private 데이터 멤버
public:         // public 멤버함수
	void reset() // 계수기를 0으로 지움
	{
		value = 0;
	}
	void count() { // 계수기의 값을 1 증가
		++value;
	}
	int getValue() const // 계수기의 값 리턴
	{
		return value;
	}
};
#endif // COUNTER_H_INCLUDED

일반적으로 클래스를 선언할 때는 2개의 파일을 만든다. 하나는 클래스 선언문이 포함된 헤더 파일이고, 다른 하나는 클래스의 멤버함수들의 정의를 담고 있는 소스 프로그램 파일이다.

파일의 명칭은 클래스의 이름을 사용하여 각각 Counter.h, Counter.cpp등으로 만드는 것이 일반적이다. 이 예에서는 클래스가 매우 간단하여 클래스의 멤버함수들을 클래스 선언문 안에 모두 포함하여 작성하였기 때문에 Counter.h만 있다.

#ifndef COUNTER_H_INCLUDED // Counter.h가 중복 include되지 않았는지 검사
#define COUNTER_H_INCLUDED  // Counter.h가 처음 include될 때 정의됨
#endif // COUNTER_H_INCLUDED

위의 선행처리기 지시어는 동일한 헤더 파일이 하나의 번역 단위에 여러 번 중복 삽입되는 것을 막기 위한 것이다. 프로그램을 작성할 때 의도적으로 같은 헤더 파일을 중복하여 삽입하려 하지는 않을 것이다. 그렇지만 동일한 헤더 파일을 삽입하게 하는 여러 개의 헤더 파일들을 함께 C++ 소스 프로그램에 삽입하는 경우 의도하지 않는 중복이 발생할 가능성이 있으며, 이러한 경우를 대비하는 것이다.

// 소스코드 4-2 : CntMain.cpp
#include <iostream>
#include "Counter.h"
using namespace std;

int main() {
	Counter cnt; // Counter 객체의 정의
	cnt.reset(); // 계수기를 0으로 지움
	cout << "현재 계수기 값: " << cnt.getValue() << endl;
	cnt.count(); // 계수기를 1 증가
	cnt.count();
	cout << "현재 계수기 값: " << cnt.getValue() << endl;
	return 0;
}

====== 결과 ======
현재 계수기 값: 0
현재 계수기 값: 2

CntMain.cpp는 Counter 클래스를 사용하는 예를 보여 주는 소스 프로그램이다. 소스 프로그램 파일에서 직접 Counter 클래스를 선언하는 대신 #include 지시어로 Counter.h를 삽입하도록 하고 있는 것을 볼수 있다. main() 함수는 클래스의 외부이므로 cnt의 private 멤버인 value를 직접 액세스할 수 없다. 즉,

cnt.value = 0; // 에러 - private 멤버의 액세스

와 같은 문장은 허용되지 않으며, 이와 같은 처리를 하려면 이를 public 멤버인 reset()을 통하여 호출해야 한다.

const 멤버함수

getValue() 멤버함수는 함수의 머리와 몸체 사이에 const라는 키워드가 붙어 있다. 이와 같은 함수들을 const 멤버함수라고 하는데, 멤버함수 내에서 데이터 멤버들의 값을 변경하지 않는다는 것을 의미한다. 만일 const 멤버함수 내에서 데이터 멤버의 값을 수정하면 컴파일러가 오류  메세지를 보낸다. Counter.h의 멤버함수 getValue()는 데이터 멤버의 값을 읽기만 하므로 const 멤버함수로 정의한 것을 볼 수 있다.

const Counter c; // c는 상수 객체임
int n = c.getValue(); // OK - const 멤버함수이므로 c를 수정하지 않음
c.count(); // 오류 - const 멤버함수가 아니므로 c를 수정함

위의 예에서 cconst 객체로 정의하였으므로 c의 내용을 변경할 수 있는 멤버함수는 호출할 수 없다. 여기서 내용의 변경 여부는 함수 내부에서 실제로 값을 변경하였는지에 대한 것이 아니라, const 멤버함수로 지정하였는지의 여부에 대한 것이다. getValue()const 멤버함수로 정의하였으므로 객체 c를 통해 호출할 수 있으나, count()const 멤버함수가 아니므로 호출할 수 없다. 만일 getValue()const 멤버함수로 지정하지 않았다면 설사 객체의 내용을 변경하는 문장을 함수 내에 포함하고 있지 않다 하더라도 const 객체인 c를 통해 호출할 수 없다.