[ C++ 스터디 ]

자료출처 : 열혈강의 C++ 프로그래밍
자료저자 : 윤성우


3. 클래스의 기본


★ 구조체와 클래스 ★


< 1 구조체의 등장 배경은 무엇인가 ? >

구조체를 활요해 본 경험은 있을 것이다. 그렇다면 구조체가 프로그래머에게 주는 이점은
무엇인가 ? 물론 현직 프로그래들에게 이러한 질문을 던진다면 일하고 있는 특성에 따라
서 답은 다양해질 수밖에 없다. 그러나 그 이면에는 다음과 같은 특징을 공통적으로 내포
하고 있다.

" 관련 있는 데이터를 하나로 묶으면 관리하기에도 , 프로그래밍 하기에도 편리하다. "

실제로 프로그래밍을 하다 보면 데이터들은 부류를 형성하게 된다. 부류를 형성하는 데이
터들은 일반적으로 함께 저장하고 , 함께 소멸된다는 특징을 지닌다. 비디오 대여점에서
사용하는 " 비디오 item( tape ) 및 고객 관리 프로그램 " 을 예로 들어 보자 이 프로그램
을 구현한다면 어떠한 데이터들을 관리하도록 구현해야 하겠는가 ? 그냥 생각나는 대로
적어 보자

" 고객의 이름 , 비다오 item ( tape ) 제목 , 등록된 고객의 전화번호 , 고객의 집주소 ,
비디오 item의 대여 상태 , 고객의 대여 목록 등등 ...... "

대략 몇 가지만 언급을 하였다. 그런데 자세히 보면 , 데이터의 종류는 ' 비디오 item에
대한 부류 ' 와 ' 고객 정보 ' 에 대한 부류로 크게 나눠짐을 알 수 있다.


그림 3-1 : 부류를 형성하는 데이터

위 그림의 왼편에서는 고객 정보에 대한 부류를 보여 준다 . 새로운 고객이 등록될 경
우 함께 저장 되어야 하는 데이터들이다. 오른편에는 비디오 item 정보에 대한 부류
를 보여 준다. 비디오 대여점에 새로운 비디오 item ( tape )이 들어 올 때마다 등록
되어야 할 데이터들이다. 어디 그뿐인가 ? 고객이 회원 탈퇴를 하거나, 오래된 비
디오 item을 폐기 할 때에는 함께 소멸되어야 할 것이다.

비단, 비디오 관리 프로그램뿐만 아니라 대부분의 프로그램은 필요로 하는 데이터들이
부류를 형성하게 된다.

따라서 부류를 형성하는 데이터들을 하나의 자료형으로 정의해서 , 관리 및 프로그램
구현에 도움을 주겠다는 의도로 구조체가 등장한 것이다. 만약에 이 내용이 이해되지
않는다면 약 300 라인 정도 되는 비디오 관리 프로그램이나 이와 비슷한 예제를 하나
구해서 그 안에 정의되어 있는 구조체를 사용하지 않는 형태로 예제를 변경해 보기
바란다. 구조체가 왜 필요한 것인지 아주 절절히 느끼게 될 것이다.


< 2 구조체에 대한 불만과 개선 >

C 언어의 사용자 정의 자료형으로서 대표적인 것이 구조체이다. 그 이외에도 공용체
와 열거형이 있긴 하지만 구조체에 비해서 사용 빈도가 낮은 편이다. 필자는 과거
C 언어에서 제공하는 구조체 문법에 불만이 한가지 있었다. 다음 예제를 보자 그리
고 확장자를 .c ( .cpp가 아니다 )로 해서 컴파일해 보자 . 어디서 오류가 발생하는
가 ?

# include < stdio.h >

struct Person  // Person이라는 이름의 사용자 정의 자료형
{

int age ;
char name[ 10 ] ;

} ;

int main ( void )
{

int a=10 ;
Person p ;  // struct Person p ;  // 1번

return 0 ;

}


1번에서 오류가 발생함을 확인하였는가? 문제를 해결하기 위한 가장 쉬운 방법은 오류
가 나는 1번 앞에 키워드 struct를 붙여 주는 것이다. ( 물론 여러분은 typedef 선언을
생각했으리라 믿는다 . )

이제 필자의 불만사항이 무엇이었는지를 이야기할 차례이다. 위의 예제에서 우리는 Per
son이라는 구조체를 정의하였다. 그렇다면 Person이라는 이름은 하나의 자료형이 된다.
물론 사용자 정의 자료형이다. 그러나 사용자 정의 자료형도 기본 자료형처럼 인정을
해 줬으면 좋겠다. 이것이 바로 필자의 요구 사항이다. 왜? 앞에다가 struct라는 키워드
를 붙이라고 요구하는가 ! int형 변수를 선언할 때는 앞에다가 아무것도 붙여주지 않아
도 되지 않는가 !

이제 위 예제의 확장자를 .cpp로 변경해서 컴파일해 보기 바란다. 어떤가? 아직도 오류가
발생하는가? 발생하지 않음을 확인할 수 있을 것이다. 즉 C 언어와 달리 C++의 메커니즘
에는 사용자 정의 자료형과 기본 자료형을 동일시하자는 기본 철학이 담겨 있다. 이러한
철학에 힘입어 한참 뒤에서는 " 연산자 오버로딩 " 이라는 개념도 공부하게 된다.

결론은 아주 간단하다! C++에서는 사용자 정의 자료형 변수도 기본 자료형 변수처럼 동
일한 방법으로 선언이 가능하다는 것이다. 따라서 앞으로는 사용자 정의 자료형 변수도
기본 자료형 변수처럼 선언하고 활용할 것이다.


< 3 함수를 넣으면 좋은 구조체 >

여러분은 C 언어를 공부하면서 몇몇 프로그램을 작성해 보았을 것이다. 프로그램이란 무
엇인지에 대해서 정의를 내려보라고 한다면 여러분은 어떻게 정의를 내리겠는가 ? 프로
그램에 대한 정의는 아주 다양하다 ( 정의를 논하지만 지금 이 시점에서는 원론적인 이야
기로 치부되어서는 안 된다. ) 그 중에서도 가장 간결하면서 정확한 정의는 다음과 같다.

프로그램 = 데이터 + 데이터를 조작하는 루틴 ( 함수 )

외울 필요 없이 가만히 생각해 보자 과거 여러분이 작성했던 프로그램이나 교재에 있는
예제들을 생각해 보자 제일 먼저 모니터에 " Hello World " 를 출력해 주는 프로그램이
떠오를 것이다.

여기서 문자열 " Hello World " 는 데이터에 해당된다. 그리고 이 문자열을 입력받는
printf 함수는 데이터를 조작하는 루틴에 해당이 된다.

실제로 모든 프로그램은 데이터와 데이터를 조작하는 루틴으로 이뤄진다. 앞에서 데이터
들이 부류를 형성한다고 했던 것을 기억하는가 ? 잘 구성된 프로그램들은 데이터들만을
가지고 부류를 형성하는 것이 아니라 , 데이터를 조작하는 루틴 , 즉 함수와 더불어 부류
를 형성하기 마련이다.

아주 쉬운 예를 하나 들어 보겠다. 은행에서 고객의 계좌 관리 프로그램을 작성한다고
가정해 보자 물론 프로그램의 부피가 너무 크기 때문에 우리는 그 중 일부 ( 아주 작은
일부 )에 대해서만 생각할 것이다.

데이터들의 부류를 형성하는 가운데 , 다음과 같이 고객의 계좌에 관련된 데이터들의
부류가 형성 되었다. ( 새로운 고객이 등록될 때마다 함께 등록되어야 할 데이터들이다. )



그림 3-2 : 계좌 관련 데이터 부류

데이터가 부류를 형성하였으나 이를 구조체로 묶어야 하겠다.

/*

oo1.cpp

*/

# include < iostream >

using std : : cout ;
using std : : endl ;

struct Account
{

char accID[ 20 ] ;  // 계좌 번호
char secID[ 20 ] ;  // 비밀 번호
char name[ 20 ] ; // 이 름
int balance ; // 잔 액

} ;

int main ( void )
{

Account yoon={ " 1234 " , " 2321 " , " yoon " , 1000 } ;
cout << " 계좌번호 : " << yoon.accID << endl ;
cout << " 비밀번호 : " << yoon.secID << endl ;
cout << " 이 름 : " << yoon.name << endl ;
cout << " 잔 액 : " << yoon.balance << endl ;

return 0 ;

}

▶ 실행 결과

계좌번호 : 1234
비밀번호 : 2321
이 름 : yoon
잔 액 : 1000


이제 데이터를 조작하는 루틴, 즉 함수를 정의할 차례이다. 다음과 같이 계좌 관련 데이
터와 긴밀한 관계를 갖는 함수들이 부류를 형성하게 된다.


그림 3-3 : 계좌 관련 함수 부류

위 그림은 입금과 출금에 관련된 함수들이 모여서 계좌 관련 함수의 부류를 구성하고 있
음을 보여준다 . 이것이 왜 하나의 부류가 되는지 가만히 생각해 보자 출금이나 입금에
관련된 기능이 조작하는 데이터는 무엇이겠는가 ? 위에서 정의한 구조체 Account의
멤버 balance이다 혹시라도 입금과 출금에 관련된 함수가 다른 부류에 있는 데이터를
조작할 일이 있겠는가 ? 전혀 없다. 뿐만 아니라 있어서도 안 된다. 구성이 잘 되어 있
는 프로그램이라면 말이다.

이렇듯 데이터가 부류를 형성하면 이 데이터들을 조작하는 함수들도 구성이 되는데 이것
도 하나의 부류를 인정을 하고 함께 관리를 해 주면 프로그래밍이 한결 수월해진다. 다음
예제는 지금까지 이야기한 내용을 C 프로그래밍 스타일로 옮겨 놓은 것이다.

/*

oo2.cpp

*/

# include < iostream >

using std : : cout ;
using std : : endl ;

struct Account
{

char accID[ 20 ] ;  // 계좌 번호
char secID[ 20 ] ; // 비밀 번호
char name[ 20 ] ; // 이름
int balance ;  // 잔액

}

void Deposit ( Account &acc , int money )  // 입 금
{

acc.balance += money ;

}
void Withdraw ( Account &acc , int money )  // 출 금
{

acc.balance -= money

}

int main ( void )
{

Account yoon={ " 1234 " , " 2321 " , " yoon " , 1000 } ;

Deposit ( yoon , 100 ) ;
cout << " 잔 액 : " << yoon.balance << endl ;
Withdraw ( yoon , 200 ) ;
cout << " 잔 액 : " << yoon.balance << endl ;

return 0 ;

}

▶ 실행 결과

잔 액 : 1100
잔 액 : 900


이제 위의 예제를 보고 최종 결론을 내릴 차례이다. 위의 예제에서 정의한 구조체
Account와 함수 Deposit 그리고 Withdraw는 계좌라는 하나의 부류를 형성하고 있
음을 이해하였을 것이다. 따라서 이번에는 이 모두를 하나로 묶어 볼 생각이다. C 언어
에서의 구조체는 관련된 데이터만을 하나로 묶을 수 있었다. 그러나 우리는 데이터뿐
만 아니라 함수까지도 하나로 묶기를 원한다. 동의하는가 ?


그림 3-4 : 계좌 과련 데이터와 함수의 부류

어떻게 묶을 것인가? 그냥 묶어버릴 테니 놀라지 말기 바란다.

/*

oo3.cpp

*/

# include < iostream >
using std : : cout ;
using std : : endl ;

struct Account  // 1번
{

char accID[ 20 ] ;  // 계좌 번호
char secID[ 20 ] ; // 비밀 번호
char name[ 20 ] ; // 이름
int balance ;  // 잔액   // 2번

void Deposit ( int money ){  // 3번

balance += money ;  // 4번

}
void Withdraw ( int money ) {

balance -= money ;  // 5번

}

} ;  // 6번

int main ( void )
{

Account yoon={ " 1234 " , " 2321 " , " yoon " , 1000 } ; //7번

yoon.Deposit ( 100 ) ;  // 8번
cout << " 잔 액 : " << yoon.balance << endl ;

yoon.Withdraw ( 200 ) ;  // 9번
cout << " 잔 액 : " << yoon.balance << endl ;

return 0 ;

}

▶ 실행 결과

잔 액 : 1100
잔 액 : 900


1번부터 6번까지 정의되어 있는 구조체 Account를 보자 앞의 예제에서 정의 한
구조체 Account와의 가장 큰 차이점은 Deposit과 Withdraw 함수가 멤버로 존재한다
는 것이다. 물론 C 언어에서의 구조체는 멤버로 함수를 가지지 못한다. 그러나 C++
에서는 가능하다. 즉 C++에서의 구조체는 C 언어에서의 구조체와는 다르다는 것을
알 수 있다.

4번 5번에서 참조하는 변수 balance는 2번에 선언되어 있는 멤버 변수 balance이
다.

7번에서는 yoon이라는 이름의 구조체 변수를 선언하고 있다. 여기서 생성되는 구조
체 변수는 다음과 같은 형태로 구성이 될 것이다. 구조체 멤버 초기화 방식과
100% 같다.


그림 3-5 함수가 존재하는 구조체의 변수

위의 그림에서 보면 Account라는 이름의 구조체를 정의할 때 함수도 멤버에 포함시
켰기 때문에 Account 구조체 변수 yoon에는 함수가 포함되어 있음을 볼 수 있다.

8번에서는 . 연산자 ( 멤버 접근 연산자 )를 이용해서 Deposit 함수를 호출하고 있다
변수 yoon 안에는 Deposit이라는 이름의 함수가 존재하지 않는가? 따라서 . 연산자를
이용한 Deposit 함수의 호출 요구는 정당하다. 그 결과 3번에 정의되어 있는 Deposit
함수가 호출되고, 그로 인해서 전달된 인자만큼 balance의 값은 증가하게 된다.

9번에서는 . 연산자 ( 멤버 접근 연산자 )를 이용해서 Withdraw 함수를 호출하고 있
다. 이 역시 정당하다.

이쯤에서 여러분의 솔직한 의견을 듣고 싶다. 구조체 안의 함수가 들어감으로 인해서
여러모로 프로그램의 구조가 좋아졌다고 생각하는가? 이것을 느끼는 것이 아직은 무
리일 수도 있다. 그럼에도 불구하고 여러분을 위해 예제를 발전시켜 온 이유는 클래
스의 등장 배경을 어렴풋하게나마 이해하기를 바래서다.


< 구조체가 아니라 클래스 ( Class ) >

이제 구조체가 Account는 더 이상 구조체가 아니라 클래스다. " struct라는 키워드를
사용했는데 어떻게 클래스가 되나요 ? " 라고 질문할 수도 있겠다. 그렇다면 필자는
거꾸로 묻겠다. " 구조체 인데 어떻게 함수를 정의해서 넣을 수 있나요 " 분명히
Account는 구조체가 아니라 클래스다.

C++에서의 구조체는 클래스라는 넓은 개념의 일부로 존재한다. 즉 다음과 같이 설명
할 수 있다.


그림 3-6 구조체와 클래스의 관계

C++에서의 구조체는 C 언어에서의 구조체와 특징에 있어서 거리가 조금 멀다. 구조체도
클래스라는 범주에 속해 있으면서, 다른 클래스들과는 조금 다른 특징을 지닐 뿐이다.
조금 다른 특징이 무엇인지는 다음 장에서 간단히 언급한다. 이제 클래스에 대한 정의
를 내릴 때가 된 것 같다. 클래스란 다음과 같이 정의 가능하다.

클래스 = Attribute ( 특성 ) + Method ( 방법 )

여기서 애트리뷰트 ( attribute) 라는 것은 변수를 의미하는 것이고, 메소드 ( method )
라는 것은 함수를 의미하는 것이다. 보통은 애트리뷰트를 가리켜 멤버 변수라 하고,
메소드를 가리켜 멤버 함수라 한다. 그렇다면 다음과 같이 조금 편하게 정의 내릴 수도
있겠다.

클래스 = 멤버 변수 + 멤버 함수

이제 왜? 구조체 Account를 가리켜 클래스라고 하였는지 이해가 가는가? 구조체
Account는 클래스의 정의에 부합이 된다. 마지막으로 위의 예제 ( oo3.cpp )에서
영 거슬리는 struct라는 키워드를 대신해서 class라는 키워드를 삽입해 보자.

그래야 비로서 누구나 인정하는 클래스가 될 테니까 말이다.

/*

oo4.cpp

*/

# include < iostream >
using std : : cout ;
using std : : endl ;

class Account  // 1번
{
public : // 2번

char accID[ 20 ] ;  // 계좌 번호
char secID[ 20 ] ; // 비밀 번호
char name[ 20 ] ; // 이름
int balance ;  // 잔액  

void Deposit ( int money ){ 

balance += money ;

}
void Withdraw ( int money ) {

balance -= money ;

}

} ;  // 6번

int main ( void )
{

Account yoon={ " 1234 " , " 2321 " , " yoon " , 1000 } ;

yoon.Deposit ( 100 ) ;
cout << " 잔 액 : " << yoon.balance << endl ;

yoon.Withdraw ( 200 ) ; 
cout << " 잔 액 : " << yoon.balance << endl ;

return 0 ;

}

▶ 실행 결과

잔 액 : 1100
잔 액 : 900

1번에서 키워드 struct를 대신해서 class라는 키워드를 삽입하였다. 그리고 2번에서
는 public 이라는 키워드를 등장시키고 있다. 이 키워드에 대해서는 잠시 후에 설명
할 것이다. 일단은 무조건 넣어두자


< 5 변수가 아니라 객체 ( Object ) >

클래스의 변수는 더 이상 변수라는 표현을 쓰지 않고 '객체' 라는 표현을 사용한다.
객체란, 현실 세계에 존재하는 주변의 모든 것들 ( 자동차 , 사람 , 강아지 , 건물
등등 )을 뜻하는 것으로서 완전한대상체라는 의미를 지닌다.

즉 프로그램의 세계에서는 데이터와 그 데이터를 적절히 처리하는 함수를 하나로
묶어서 이를 구체화시키면 이것을 두고 하나의 완전한 대상체로 바라보는 것이다. 말이
조금 어려웠다. 잠시 후에 조금 구체적인 이야기로 들어 가겠다.


★ 클래스와 객체 ★

필자는 여기서 여러분에게 세 가지 용어에 대한 이해를 전달해야 한다. 그 중에서도
가장 처음에 등장하는 데이터 추상화 ( Data Abstraction )라는 개념이 가장 설명하기
난해하다. 프로그램 코드는 등장하지 않고, 글만 존재한다고 해서 재미없게 생각하지
말자. 필자는 여러분이 이번에 소개하는 내용을 재미있게 읽을 수 있도록 많은 노력을
기울여야만 했다.


< 1 데이터 추상화 ( Data Abstraction ) >

코끼락 무엇인지를 모르는 인디언들이 있다. 그러나 그 가운데에는 세상 물정에 아주
밝은 그래서 코끼리를 본 적이 있는 인디언이 한 명 있다. 그 인디언이 나머지 인디언
들에게 코끼리가 무엇인지를 설명을 한다.

" 코끼리는 말이야 일단 발이 네 개야 , 그리고 코가 하나 있는데 그 코의 길이가 한
5미터 정도는 되니 그리고 그 코를 이용해서 목욕을 하기도 하고 , 또 물건을 집기도
해 야! 크기는 말이야 글쎄 무개로 따지면 1톤은 족히 넘을 거야 "

이 말을 들은 인디언들은 코끼리가 무엇인지를 정의하기 시작한다.

■ 특징 1. 발이 네 개
■ 특징 2. 코의 길이가 5미터 내외
■ 특징 3. 몸무게는 1톤 이상
■ 특징 4. 코를 이용해서 목욕을 함
■ 특징 5. 코를 이용해서 물건을 집기도 함

자 드디어 코끼리가 무엇인지에 대한 정의가 내려졌다. 특징 1, 2, 3은 코끼리의 데
이터적인 측면이고, 특징 4, 5는 코끼리의 기능적 측면이다. 비록 부족하기는 하지만
코끼리가 무엇인지에 대한 정의가 완전히 내려진 것이다. 이것이 바로 데이터 추상
화이다. 즉 현실세계의 사물을 데이터적인 측면과 기능적 측면을 통해서 정의하는
것 이것이 데이터 추상화이다. 코끼리에 대해서 정의를 내리긴 하였지만 지극히
관념적이고 추상적이지 않은가. 그래서 데이터 추상화라고 한다.


그림 3-7 데이터 추상화

< 2 클래스 ( Class ) >

추상화만 시켜 놓으면 무엇 하겠는가 ? 이것을 프로그램 코드로 옮겨 놓을 수 있어야
쓸모 있을 것 아닌가 ! 그래서 추상화된 데이터 ( 데디터적 측면 + 기능적 측면 )
를 가지고 자료형을 정의하게 된다. 이 때 사용되는 것이 클래스이다. 이렇게 추상화된
데이터를 가지고 사용자 정의 자료형을 정의하는 것 자체를 가리켜 보통은 " 클래스
화(化)한다 " 라고 표현한다.


그림 3-8 클래스화


< 3 객체 ( Object ) >

클래스를 정의한다는 것은 자료형을 하나 정의하는 것이다. 이것은 기본적으로 제공되
는 자료형 ( int, float, double 등등 )들과 별반 다를 것이 없다.

자료형을 기반으로 변수를 생성하지 않으면 프로그램 내에서 의미가 없다. 따라서 클래스
를 이용해서 자료형을 정의하였으면, 프로그램 내에서 변수를 생성해야 한다. 그러나
앞에서 말했듯이 클래스를 이용해서 정의된 자료형의 변수는 객체( object)라는 표현을
쓴다. 그리고 클래스를 기반으로 객체를 생성하는 것을 가리켜 " 인스턴스화 ( instantiation )
한다 " 라는 표현을 사용한다. 물론 " 객체화한다 " 라는 표현도 나쁘지 않다.


그림 3-9 인스턴스화


★ 클래스 멤버의 접근 제어 ★

앞에서 public 이라는 키워드 붙였던 것을 기억하는가 ? 무슨 의미인지도 모른면서
일단은 붙여줬다. 이는 클래스 안에 선언되어 있는 멤버의 접근 허용 범위를 이야기
하는 것이다. public , protected , private 이렇게 세 가지의 접근 제어 키워드가
존재한다.

< 1 클래스의 내부 접근과 외부 접근 >

세 가지 키워드에 대해서 공부하기 전에 클래스의 내부 접근과 외부 접근이 무엇을
의미하는지 다음 예제를 통해서 이야기해 보기로 하자

/*

Access . cpp

*/

# include < iostream >

using std : : cout ;
using std : : endl ;

class Counter
{

public :
int val ;

void Increment ( void )
{

val++ ;  // 1번

}

} ;

int main ( void )
{

Counter cnt ;  // 2번
cnt . val = 0;  // 3번
cnt . Increment ( ) ;  // 4번
cout << cnt . val << endl ; // 5번

}

실행결과
1

1번을 보면 같은 클래스 내에 선언되어 있는 변수 val의 값을 1 증가시키고 있다.
이렇게 같은 클래스 내에 존재하는 멤버에 의한 접근을 가리켜 클래스 내부
접근이라 한다. 이러한 형태가 아닌 그 이외의 모든 접근은 외부 접근에 해당이
된다.

3번 4번 5번 에서는 2번에 선언한 객체 cnt의 멤버에 . 연산자를 이용해서 접근하
고 있다. 이러한 형태의 접근을 외부 접근이라 한다. 연산자를 사용해서 접근하고
있는 위치가 클래스 내부인가? 외부인가? 당연히 main 함수 내에서 접근하는
것이므로 클래스 외부에 해당이 된다. 따라서 외부 접근이다.


< 2 public & private >

이번에는 세 가지의 접근 제어 키워드에 대해 살펴보기로 하자. 다음 그림은 public ,
protected 그리고 private의 관계를 그림으로 설명하고 있다.


그림 3-10 접근을 제어하는 영역의 범위

위 그림에서 원의 넓이는 접근을 허용하는 범위의 크기를 의미한다. 따라서 원의
가장 내부에 존해하는 private이 가장 최소한의 접근만을 허용하는 것이고, 가장
외부에 존해하는 public이 가장 넓은 접근을 허용한다는 뜻이 된다. 그 사이에
있는 protected는 private과 public의 중간 정도의 접근을 허용한다. 여기서는
public과 private에 대해서만 이야기하겠다. protected에 대해서는 상속으로
들어가서 소개하도록 하겠다.

일단 private이 의미하는 바부터 이야기하겠다. private으로 멤버가 선언이 되면
클래스의 내부 접그만 허용하겠다는 의미가 된다. 따라서 멤버가 private으로
선언이 되었음에도 불구하고 외부 접근을 시도한다면 컴파일러는 에러를
발생시킬 것이다.

반면에 멤버가 public으로 선언이 되면 클래스 외부 접근도 허용하겠다는 의미가
된다. 엄밀히 말하면 어디서든지 접근을 허용하겠다는 의미이다. 다음 예제를
보자 문 ( door )을 추상화해서 클래스로 정의하였다.

/*

AccessControl . cpp

*/

# include < iostream >

using std : : cout ;
using std : : endl ;

const int OPEN = 1 ;
const int CLOSE = 2 ;

class Door
{

private :  // 1번
int state ;

public :  // 2번
void Open ( ) {

state = OPEN ;  //3번

}
void Close ( ) {

state = CLOSE ; // 4번

}
void ShowState ( ) {

cout << " 현재 문의 상태 : " ;
cout << ( ( state == OPEN ) ? " OPEN " : " CLOSE " ) // 5번
<< endl ;

}

} ;

int main ( void )
{

Door d ;
// d . state = OPEN ; // 컴파일 오류 발생 // 6번

d . Open ( ) ; // 7번
d . ShowState ( ) ;  // 8번

return 0 ;

}

실행 결과

현재 문의 상태 : OPEN


Door라는 이름의 클래스를 정의하는데 있어서 1번에서 private 선언이 들어
가 있다. 이것은 다음과 같은 의미를 지닌다. " 지금부터 선언하는 모든 멤버는
private으로 선언을 하겠다 " 따라서 뒤를 이어 등장하는 멤버 변수 state는 private
멤버가 된다. 따라서 내부 접근만을 허용하게 된다.

2번에서는 public 선언이 들어가 있다. 이 또한 다음과 같은 의미를 지닌다. " 지금
부터 선언하는 모든 멤버는 public으로 선언을 하겠다. " 즉 선언이 private에서
public으로 바뀐 것이다. 따라서 그 뒤를 이어 등장하는 멤버 함수 Open , Close ,
ShowState는 public 멤버가 되어서 어디서든 접근이 가능하게 된다.

3번 4번 5번을 보면 private 멤버 state에 접근을 하고 있음을 할 수 있다. 문제가
있는가 ? 클래스 내부에서 접근을 하는 것이므로 문제될 것이 전혀 없다.!

이제 눈을 main 함수로 돌려보자 6번은 주석 처리되어 있다. 만약에 이 주석을
해제한다면 컴파일 오류가 발생할 것이다. 멤버 변수 state는 private으로 선언
되었기 때문이다. 반면에 7번 8번의 접근은 유효하다. 멤버 함수들이 public으로
선언되었기 때문이다.


★ 멤버 함수의 외부 정의 ★

앞에서 본 예제 AccessControl.cpp에서 Door라는 이름의 클래스를 정의하였다.
아주 간단한 클래스임에도 불구하고 길이가 조금 길어 보인다. 따라서 클래스
내에 멤버 함수가 몇 개이고 멤버 변수가 몇 개인지에 대한 내용들도 한눈에
들어오지 않는다.

앞으로는 더 복잡한 클래스들을 정의할 텐데, 이렇게 클래스 내에 모든 함수의
정의를 집어 넣는다면 클래스의 부피가 너무 커질 것이다. 경우에 따라서는 스무 줄
이상 되는 함수들도 존재하지 않는가! 이러한 함수들이 클래스의 멤버로 존재한다고
생각해 보자 무슨 대책이 있어야 할 듯하다.


1. 멤버 함수를 클래스 외부에 정의하기

C++에서는 멤버 함수를 클래스의 외부에 정의하는 방법을 제공한다. 앞에서 정의한
Door라는 클래스를 가지고 설명해 보겠다.

class Door
{

private :

int state ;

public :

void Open( );
void Close( );
void ShowState( );

} ;

void Door : : Open ( ) // 1번
{

state=OPEN ;

}

void Door : : Close ( ) // 2번
{

state=CLOSE;

}

void Door : : ShowState ( )  // 3번
{

cout << " 현재 문의 상태 : " ;
cout << ( ( state==OPEN ) ? " OPEN " : " CLOSE " )  << endl ;

}

 

Door라는 클래스를 보자 함수의 선언만 존재하는 것을 알 수 있다. 그리고 1번에 존
재하는 것은 분명히 함수의 정의이다. 그런데 그 형태가 다음과 같다.

void Door : : Open ( )
{

}

( 클래스 내에 선언되어 있는 Open ( ) 함수의 정의 )

" Door : : " 라는 선언이 존재하지 않는다면 이 함수의 정의는 전역 함수의 정의가 된다. 그러나
" Door : : " 라는 선언이 존재함으로써 Door 클래스 내에 선언되어 있는 Open 함수의 정의
가 되는 것이다. 이러한 방식으로 멤버 함수의 선언만 클래스 내부에 두고, 정의는 클래스
밖으로 빼낼 수 있다. 이제 3번 4번에 존재하는 함수의 정의도 어떤 의미를 지니는지
이해가 될 것이다.

이렇게 함수 정의를 클래스 외부로 빼낸다고 해서 달라지는 것은 없다. 단순히 클래스 내부의
선언을 간결하게 하기 위해서 외부로 빼낸다고 생각하기 바란다.


2. 클래스 내부 정의의 의미와 inline

클래스의 멤버 함수를 내부에 정의한다는 것은 외부에 정의하는 것과 달리 in-line으로
처리할 것을 요구한다는 의미를 지니고 있다. 혹시 함수의 in-line화가 의미하는 바를 잘 모르
겠다면 1 장을 다시 한번 참고하기 바란다.

그렇다면 모든 함수를 in-line화 하기 위해서는 함수가 길어도 클래스 내부에 선언하여만
하는 것일까 이러한 경우에는 다음과 같이 선언하면 된다.

class Door
{

private :

int state ;

public :

void Open ( ) ;
void Close ( ) ;
void ShowState ( ) ;

} ;

inline void Door : : Open ( ) // 1번
{

state=OPEN ;

}
inline void Door : : Close ( ) // 2번
{

state=CLOSE ;

}
inline void Door : : ShowState ( ) // 3번
{

cout << " 현재 문의 상태 : " ;
cout << ( ( state==OPEN ) ? " OPEN " : " CLOSE " ) << endl ;

}


1번 2번 3번을 보면 함수의 정의 앞에 inline이라는 키워드가 존재함을 알 수 있다. 이러한
선언은 함수를 in-line화하라는 의미이다. 비록 함수의 정의가 클래스 외부에 있다고 하더라도
말이다.

3. 헤더 파일을 이용한 파일의 분할

조금 더 세련되게 프로그래밍을 하는 방법에 대해 이야기해 보자 지금까지는 하나의 .cpp
파일 안에 클래스의 선언과 정의, 더불어 main 함수의 정의까지도 다 포함시켰다. 그러나
이는 프로그램을 관리하는데 어려움을 준다. 조금 현명한 프로그래머라면 같은 구조로
프로그램을 작성할 것이다.


그림 3-12 헤더 파일을 이용한 파일의 분할

즉 클래스의 선언은 헤더 파일로 구현을 하고, 클래스 멤버 함수의 정의는 .cpp파일로 구
현을 한다. 이렇게 되면 이 클래스를 필요로 하는 모든 파일에서는 헤더 파일만 포함하면
되는 것이다.

이렇게 구성을 하면 프로그램을 관리 및 확장하기가 좋아진다.

# include < iostream >
using std : : cout ;
using std : : endl ;

class Calculator
{

private :

int add ;
int min ;
int mul ;
int div ;

public :

void Init ( ) ;
double Add ( double a , double b ) ;
double Min ( double a , double b ) ;
double Mul ( double a , double b ) ;
double Div ( double a , double b ) ;
void ShowOpCount ( ) ;

};

void Calculator : : Init ( )
{

add=min=mul=div=0 ;

}
double Calculator : : Add ( )
{

add++ ;
return a+b ;

}

double Calculator : : Min ( )
{

min++ ;
return a-b ;

}

double Calculator : : Mul ( )
{

mul++ ;
return a*b ;

}

double Calculator : : Div ( )
{

div++ ;
return a/b ;

}
void Calculator : : ShowOpCount ( )
{

cout << " 덧셈 : " << add << '  ' ;
cout << " 뺄셈 : " << min << '   ' ;
cout << " 곱셈 : " << mul << '   ' ;
cout << " 나눗셈 : " << div << endl ;

}

int main ( void )
{

Calculator cal ;
cal . Init ( ) ;

cout << " 3+5= " << cal . Add ( 3 , 5 ) << endl ;
cout << " 3-5= " << cal . Min ( 3 , 5 ) << endl ;
cout << " 3*5= " << cal . Mul ( 3 , 5 ) << endl ;
cout << " 3/5= " << cal . Div ( 3 , 5 ) << endl ;

cal . ShowOpCount ( ) ;

}



# include < iostream >
using std : : cout ;
using std : : endl ;

class Printer
{

private :

char string [ 30 ] ;

public :

void SetString ( char * str ) ;
void ShowString ( ) ;

} ;

void Printer : : SetString ( char * str )
{

strcpy ( string , str ) ;

}
void Printer : : ShowString ( )
{

cout << string << endl ;

}

int main ( void )
{

Printer pnt ;
pnt . SetString ( " Hello World ! " ) ;
pnt . ShowString ( ) ;

pnt . SetString ( " I Love C++ " ) ;
pnt . ShowString ( ) ;

return 0 ;

}