자료출처 : 열혈강의 C++ 프로그래밍
자료저자 : 윤성우
" Hello world " 메시지를 출력하는 프로그램을 기억하는가 ? C 언어를 처음 공부하는데
있어서 이보다 좋은 예는 없으리라 생각한다. 여러분도 그렇게 생각하지 않는가 ? 프로그
램이 아주 간단 하면서도 함수라는 기본 개념을 우리에게 심어 주었다. 그러나 그 프로그
램도 처음 접하는 사람에게는 그리 쉬운 내용이 아니다. 아무 생각 없이 한번 돌려보니 뭔가
가 출력되는 것은 알겠는데 , 어떠한 과정을 거쳐서 출력되는지를 처음부터 정확히 이해할
수는 없었다. 결국은 눈에 익히고 외워 버리게 된다.
우리는 C++이라는 언어를 공부하고 있다. 물론 시작은 " Hello world " 메시지를 출력하는
데서 부터 시작할 것이다. 그러나 그 무게감은 C 언어를 공부할 때 접했던 " Hello world "
프로그램과는 다소 차이가 있다. 아무튼 여러분은 눈에 익히고 외우는 수밖에 없을 것이다.
걱정할 것 없다. 이는 아주 긍정적인 현상이다.
다음 예제를 실행해 보자 기본적인 실행 방식은 C 프로그램을 실행하는 것과 동일하다.
단 ! 파일의 이름을 지정할 때 확장자는 반드시 .c 가 아니라 .cpp 로 지정해 줘야 한다
그래야 C++ 문법 규칙이 적용되기 때문이다.
|
cout << 1 << ' a ' << " String " << endl ; 라인을 보자 다음과 같은 형태로 출력
을 요구하고 있다.
cout << 1 << ' a ' << " String " << endl ;
이 문장은 가장 먼저 정수 1을 출력하고, 그 다음으로 문자 'a' 를 출력하고, 그 다음으
로 문자열 " String " 을 출력하고, 마지막으로 endl 을 출력하라는 의미이다. 그런데
여기서 endl 을 출력하라는 것은 어떤 의미일까 ?
( 넷째 )
endl 을 출력하라는 의미는 다음 줄로 넘어가라는 의미를 지닌다. ( 개행 문자를 출력
하라는 뜻 ) 이것을 가장 쉽게 이해할 수 있는 방법은 위의 프로그램에서 endl 을 지웠
을 경우에 보여지는 출력 결과를 확인하는 것이다.
위 예제를 통해서 기본적으로 모니터에 데이터를 출력하는 방식은 알게 되었다. 그러나
의문점은 많이 남아 있다. 우리는 아직 cout 와 << 연산자, 그리고 endl 의 정체를 모
른다. 다만 사용할 줄만 알고 있을 뿐이다. 진도를 나가면서 이들의 정체를 서서히 알게
될 것이다.
참고 => endl 을 출력할 경우 개행 문자를 출력하게 되고, 추가로 출력 버퍼를 비우는
효과도 얻게 된다. 따라서 C 언어에서처럼 버퍼를 비우는 작업을 추가로 할 필요가 없
다.
위에서 보여준 예제는 과거에 사용된 프로그래밍 스타일이다. 새롭게 정의된 C++ 표준
에서는 다른 형태의 프로그래밍 방식을 요구하고 있다. 다음 예제는 위의 예제와 같은
일을 하는 프로그램이다. 다만 새로운 표준에 부합하도록 프로그램을 변경했을 뿐이다.
|
실행 결과를 보면 HelloWorld1 . cpp와 같은 기능을 하는 프로그램이라는 것을 알 수 있다.
그렇다면 어떤 부분에서 차이를 보이는가 ? 차이가 나는 것은 두 가지이다.
첫째 |
# include < iostream > 라인을 보면 헤더 파일의 이름이 iostream.h 가 아니라
iostream 이다.
포함하고자 하는 헤더 파일의 이름에 확장자가 없다는 것에 대해 상당히 생소한 느낌을 받
을 것이다. 이는 구 버전의 헤더 파일과 새로운 버전의 헤더 파일을 구분하기 위한 것이다.
새로운 표준이 정의되는 과정에서 기존의 C++ 표준에 존재하던 헤더 파일에 일부 변경
이 가해졌다. 그래서 기존의 헤더 파일과 새로운 헤더 파일을 구분 짓기 위해서 이러한 방
법이 제안된 것이다. 물론 우리는 앞으로 새로운 표준 헤더 파일을 사용할 것이다.
둘째 | HelloWorld1 . cpp와 비교해서
cout << " Hello world !! " << endl ; 이 라인을
보면 cout을 대신해서 std : : cout을 사용하고 있고, endl을 대신해서 std : : endl 을 사
용하고 있음을 볼 수 있다. cout이 무엇인지 , 그리고 endl 이 무엇인지도 모르는 판국에
그 앞에 " std : : " 까지 붙여버리니 답답하기 그지 없다 ( 붙여주지 않으면 컴파일 에러가
발생한다. ) 잠시만 기다리자. 이번 장 마지막에서 이에 대한 궁금증을 풀어 볼 것이다.
이번에는 키보드로부터 데이터를 입력받는 방식에 대해서 이야기해 볼 차례이다. 다음 예
제는 사용자로부터 두 개의 숫자를 입력받아서 덧샘 결과를 출력하는 프로그램이다.
|
실행 결과2 첫 번째 숫자 입력 : 4.5 두 번째 숫자 입력 : 3.2 덧셈 결과 : 7.7 |
|
위의 예제를 통해서도 다음 두 가지 사실을 더 알 수 있다.
첫째 | 연산자를 이용해서 연속적인 데이터의 입력을 요구할 수 있다.
cin >> 입력 변수1 >> 입력 변수2
cin >> val1 >> val2 ; 이 쪽 라인을 보자 다음과 같은 형태로 데이터의 입력을 요구하
고 있다.
cin >> val1 >> val2 ;
이는 첫 번째로 입력받은 데이터를 변수 val1에 입력하고 , 두 번째로 입력받은 데이터
를 변수 val2에 입력하라는 의미이다. 이때 첫 번째 데이터와 두 번째 데이터의 경계는
( 데이터 간의 경계는 ) 공백 ( 탭 , 스페이스바 , 엔터키의 입력 )에 의해 나눠진다. 마치
C 언어에서 제공하는 scanf 함수 처럼 말이다.
둘째 | for문의 초기화 문장 내에서도 변수 선언이 가능하다.
for ( int i = val1+1 ; i< val2 ; i++ ) 이 라인과 result += i ; 이 라인을 보자 for문의
초기화 문장이 오는 곳에 변수의 선언이 함께 등장하고 있다.
즉 다음과 같은 선언은
|
다음과 같이 한 줄로 선언이 가능하다.
|
마지막으로 배열에 문자열을 입력 및 출력하는 예제를 소개하겠다. 앞의 예제들과 다를
것은 없다 . 다만 입 . 출력하는 데이터의 종류가 다를 뿐이다.
|
앞에서 데이터의 입력에 관련된 여러 가지 예제를 소개하였다. 그런데 문제는 과거의
표준을 따르고 있다는 것이다. 그렇다면 현재의 표준을 따르도록 하려면 프로그램을
어떻게 변경해야 하겠는가 ? 예제 HelloWorld2 . cpp를 참고하면 어렵지 않게 예제들을
변경할 수 있다. 여기서는 예제 SimpleAdder1 . cpp를 현재의 표준에 맞게 변경해
보기로 하자.
|
예제를 현재의 표준에 맞게 변경하는데 있어서 신경 쓸 부분은 헤더 파일을 포함
하는 5번째 줄과 여러 줄에 걸쳐서 여러 번 등장하는 cout , cin , endl을 대신해서
std : : cout , std : : cin , std : : endl 을 사용하는 것이다.
이제 기본적인 입력 및 출력 방식을 이해하게 되었다. 보다 구체적인 이해는 앞으로
내용이 전개 되면서 완성될 것이다. 지금까지 소개한 내용 이외의 궁금한 사항은 시간을 두
고 해결해 가기로하자.
확장자가 .c 로 끝나는 C 언어로 작성된 프로그램에서는 다음과 같은 함수의 정의가 허용
되지 않았다.
|
|
이 코드를 작성한 사람의 마음을 깊이 헤아려 보자 그리고 다시 한번 보자
function ( ) ; 이 라인을 통해서 호출하고 있는 function 함수는 어디에 선언된 함수이겠
는가 ? 과연 어떤 함수가 호출되기를 바라고 저라한 문장을 선언한 것이겠는가 ? 그렇다 !
int function ( void ) 이 쪽 라인에 정의되어 있는 함수를 호출하고자 하였을 것이다. (
전달되는 인자와 정의되어 있는 매개 변수를 참조해서 보면 알 수 있다 . )
그렇다면 12번째 줄을 통해서 호출하고자 하는 function 함수는 무엇이겠는가 ? 아마도
5번째 줄 에 정의된 함수를 호출하고자 하였을 것이다.
그렇다면 다음과 같은 궁금증을 가져볼 수도 있겠다. " 아니 ! 어떠한 함수를 호출하고자 하
는지는 나도 구분하겠는데 왜 ? C 컴파일러는 에러를 발생시키는 것일까 ? "
여러분은 위의 예제를 보면서 function ( ) ; 이 라인에 존해하는 함수의 호출이
int function ( void ) 이 라인에 정의되어 있는 함수를 호출해 달라는 요구이고 ,
function ( 12 , 13 ) ; 이 라인에 존해하는 함수의 호출은 int function ( int a , int b )
이 라인에 정의되어 있는 함수를 호출해 달라는 요구임을 쉽게 알 수 있다.
그림 1-1 : 함수 이름과 매개 변수 정보의 참조를 통한 호출
여러분은 어떻게 구분할 수 있었는가? 호출하고자 하는 함수를 찾을 때 이름뿐 아니라
매개 변수의 정보까지도 동시에 참조했기 때문에 가능한 것이다. 즉 여러분은 호출하고자
하는 함수를 다음 두 가지 정보를 가지고 찾아낸 것이다.
함수의 이름 + 매개 변수의 정보
반면에 C 컴파일러는 호출하고자 하는 함수를 찾을 때 오로지 함수의 이름 정보만을 가지
고 찾기 때문에 매개 변수의 형태가 달라도 동일한 이름의 함수 정의는 허용하지 않는 것
이다.
그렇다면 C++ 컴파일러는 호출하고자 하는 함수를 어떻게 찾을까? 여러분과 마찬가지로
함수의 이름뿐 아니라 매개 변수의 정보까지도 참조를 한다. 따라서 C++은 이름이 같고
매개 변수의 타입 혹은 개수가 다른 함수들의 정의를 허용한다. 그리고 이를 가리켜 함수
오버로딩 ( function overloading )이라 한다.
C++은 함수의 이름이 같아도 매개 변수의 타입 및 개수가 다르면 문제되지 않으며 , 이를
가리켜 " 함수가 오버로딩되었다 " 라고 표현한다고 하였다. 다음은 대표적인 함수 오버로
딩의 예이다.
|
|
위의 두 함수도 오버로딩되었다. 함수의 이름과 매개 변수의 자료형은 같지만 자료형의
개수가 다르기 때문이다.
즉 함수 오버로딩의 기본 조건은 다음과 같다.
" 함수의 이름은 같지만 매개 변수의 타입이나 개수가 달라야 한다 "
그 이외에도 함수가 오버로딩되기 위한 조건이 하나 더 있는데 이에 대해서는 다음 기회에
언급하도록 하겠다.
다음 예제는 오버로딩된 함수가 적절히 호출되고 있음을 보여주는 예제이다.
|
function ( ) ; 이 라인을 보면 인자를 받지 않는 function이라는 이름의 함수를 호출하고
있다 따라서 void function ( void ) 이 라인에 정의 되어 있는 함수를 호출하게 된다.
function ( 'a' ) ; 이 라인에서는 function이라는 이름의 함수를 호출하면서 char형 데이터
하나를 인자로 전달하고 있다. 따라서 void function ( char c ) 이 라인에 정의되어 있는
함수를 호출하게 된다.
함수 오버로딩을 처음 접하게 되면 두 개의 함수를 눈으로만 보고 잘못 판단하는 경우가 종
종있다. 예를 하나 들어 보겠다. 다음의 함수들은 오버로딩 조건을 충족하는가 , 아니면
충족 하지못하고 컴파일 시 오류를 발생시키겠는가? 눈 여겨서 볼 부분은 함수의 리턴
타입이다.
|
위에 정의되어 있는 두 개의 function 함수는 이름은 같지만 , 리턴 타입이 다르다. 그러나
리턴 타입만 가지고는 함수가 오버로딩되었다고 말할 수 없다. 위 예제의
function ( ) ;
이 라인을 보자 이름이 function 이면서 인자를 받지 않는 함수를 호출하고 있다. 문제는 이
조건을 만족시키는 함수가 void function ( void ) 이 라인에도 int function ( void ) 이 라
인에도 정의되어 있다는 것이다. 결국 컴파일러는 " 난 네가 뭘 원하는지 몰라 ( Ambiguous )
" 라는 메시지를 출력하게 된다. 결론이다 ! 리턴 타입만 달라서는 함수가 오버로딩되지 않
는다.
함수를 다음과 같은 형태로 정의하는 것이 가능하다.
|
여기서 매개 변수를 선언하는 부분을 보면 a는 0으로 설정되어 있다. 이것이 바로 디폴트
매개 변수이다.
int function ( int a=0 )
{
return a+1 ;
}
디폴트 매개 변수는 ( int a=0 ) 이 부분이다.
위에서 무엇을 가리켜 디폴트 매개 변수라고 하는지를 보여 준다. 현재 매개 변수 a는 0이
라는 디폴트 매개 변수 값이 설정되어 있다. 그렇다면 이것이 지니는 의미는 무엇일까? 의
외로 간단하다. " 만약에 function이라는 이름의 함수를 호출하면서 인자를 전달하지 않으
면 0이 들어온 것으로 간주를 하겠다 " 는 뜻이다 말 그대로 디폴트 ( 기본적으로 설정해
놓은 ) 매개 변수다. 다음 예제를 보자.
|
int function ( int a=0 ) 이 라인에는 function이라는 이름의 함수가 정의되어 있다. 전달
인자로 int형 데이터를 하나 전달받는다. 그리고 디폴트 매개 변수가 0으로 설정되어 있다.
이것은 이 함수를 호출하면서 아무런 인자도 전달하지 않으면 0이 들어온 것으로 간주하겠
다는 의미이다.
std : : cout << function ( 11 ) << std : : endl ; 이 라인에서는 함수 function을 호출하
면서11을 인자로 전달하고 있다. 따라서
int function ( int a=0 )
이 라인에 정의되어 있
는 함수를 호출하게 된다.
std : : cout << function ( ) << std : : endl ; 이 라인에서는 함수 function을 호출하면서
아무런 인자도 전달하지 않고 있다. 따라서 0을 인자로 전달한다고 가정하게 된다. 즉 다음
두 문장은 같은 의미를 지니고 있다.
" function ( ) " == " function ( 0 ) "
이번에는 디폴트 매개 변수의 다양한 형태를 예제를 통해서 살펴보기로 하자. 다음에 소개
하는 것은 직육면체의 넓이를 구하는 프로그램이다.
|
함수 BoxVolume이
int BoxVolume ( int length , int width , int height ) 이 라인에 정
의되어 있고,
int BoxVolume ( int length , int width=1 , int height=1 ) ; 이 라인에 선언
되어 있다. 이러한 형태의 구성은 이미 C 언어를 통해서 학습한 것이다. 여기서 눈 여겨
볼 부분은 함수의 선언이다. 총 세 개의 매개 변수를 인자로 전달받는데 두 번째 , 세 번째
인자에만 디폴트 매개 변수를 설정해 놓았다.
한가지 주의할 것은 이렇게 함수의 선언이 함수의 정의 이전에 존재하는 경우 디폴트
매개 변수는 선언 부분에 놓여져야 한다는 것이다. 보라!
int BoxVolume ( int length , int width , int height ) 이 라인에 있는 함수의 정의에는
디폴트 매개 변수에 대한 내용이 존재하지 않는다. 이미
int BoxVolume ( int length , int width=1 , int height=1 ) ; 이 라인에 선언되었기 때문이다.
std : : cout << " [ 3 , 3 , 3 ] : " << BoxVolume ( 3 , 3 , 3 ) << std : : endl ; 이 라인
을 보면 함수 BoxVolume을 호출하면서 3개의 인자를 전달하고 있다. 따라서 디폴트 매개
변수는 전혀 의미가 없다.
std : : cout << " [ 5 , 5 , def ] : " << BoxVolume ( 5 , 5 ) << std : : endl ; 이 라인
에서는 함수 BoxVolume을 호출하면서 2개의 인자를 전달하고 있다. 잠깐 생각해 보자
이 두 개의 전달 인자는 각각 어디로 들어가겠는가 ? 함수 호출 시 전달되는 인자들은
무조건 왼쪽에서부터 채워지기 때문에 첫 번째 전달 인자 5는 매개 변수 length로 두 번째
전달인자 5는 매개 변수 width로 전달이 될 것이다. 그리고 마지막 매개 변수 height는
디폴트 매개 변수 1이 전달된 것으로 간주하게 된다.
" BoxVolume ( 5 , 5 ) " == " BoxVolume ( 5 , 5 , 1 ) "
std : : cout << " [ 7 , def , def ] : " << BoxVolume ( 7 ) << std : : endl ; 이
라인에서는 함수 BoxVolume을 호출하면서 1개의 인자만을 전달하고 있다. 따라서 다음과
같은 의미를 지니게 된다.
" BoxVolume ( 7 ) " == " BoxVolume ( 7 , 1 , 1 )"
디폴트 매개 변수와 함수 오버로딩을 동시에 잘못 정의하는 경우가 있다. 다음 예제를
보자. 그리고 문제점을 지적해 보자
|
위의 예제를 컴파일해 보면 컴파일도 잘 되고 실행도 잘 된다. 왜냐하면
int function ( int a=10 ) 이 라인에 정의된 함수 function과
int function ( void )
이 라인에 정의된 함수 function과의 관계는 어버로딩 관계이고
std : : cout << function ( 10 ) << std : : endl ; 이 라인에서는 int형 데이터 하나
를 인자로 전달받는 함수 , 즉 이에 해당하는 함수를 호출하였으므로 문제될
것이 전혀 없다.
그러나 잠재적인 문제는 지니고 있다. 위의 예제에서 main 함수를 다음과 같이 변경한
다면 어떻게 될까 ?
|
자 ! 드디어 잠재적인 문제가 터지고 말았다. std : : cout << function ( ) << std : : endl ;
이 라인을 보면 function 함수를 호출하면서 인자를 전달하지 않고 있다. 그렇다면 어떤
함수를 호출 하겠는가 ? 문제는 위에서 정의한 두 함수 모두 호출 가능하다는데 있다.
위 그림처럼 컴파일러는 두 개의 함수 중에서 무엇을 호출해야 할 것인지를 결정짓지 못
한다. 따라서 에러를 발생시켜 버린다. 그러므로 이러한 형식의 함수 정의는 반드시 피해
야 한다.
in-line 함수의 의미부터 파악해 보자 in은 ' 내부 ' 를 의미하고 , line은 ' 프로그
램 라인 ' 을 의미하는 것이다. 즉 의역 해보면 " in-line 함수 " 란 프로그램 라인
안으로 들어가버린 함수라는 의미를 지닌다.
우리는 C 언어를 공부하면서 매크로에 대해서 알게 되었다. 여기서는 이에 관련해서
잠깐 복습을 하기로 하자. 매크로를 이용하면 다양한 일을 할 수 있는데 그 중에서도
가장 대표적인 일은 함수를 만드는 것이다. 다음은 매크로를 이용해서 함수를 정의하고 이
를 호출하는 형태를 보여준다. 일반적인 순서에 의해서 전처리, 컴파일 링크 과정을 거쳐
서 실행 파일이 생성될 것이다.
|
|
위의 두 코드를 통해서 알 수 있는 사실은 무엇인가 ? 그것은 함수를 매크로로 정의하면 전
처리기에 의해서 함수 호출 문장이 함수의 몸체 부분으로 완전히 대치돼 버린다는 것이다.
그렇다면 이를 통해서 얻게 되는 이점은 무엇인가? 함수 호출 과정이 사라졌기 때문에 성능
상의 이점을 맛볼 수 있다. ( 함수 호출은 스택 메모리 공간의 할당도 요구하며 시간도 많이
요구된다. )
위의 예제와 같이 함수 호출 문장이 함수의 몸체 부분으로 완전히 대치되 버리는 현상을
가리켜 " 함수가 inline 화되었다 " 라고 표현한다. 함수를 호출하는 위치에 함수의 몸체가
들어가 버렸으므로 아주 적절한 표현이다.
참고 => 매크로 함수에 대해서는 장점뿐만 아니라 단점도 알고 있어야 한다. 단점은
함수의 구현이 까다롭고 디버깅하기 어려우며 , 구현하고자 하는 함수의 크기가 크다면
프로그램의 크기가 된다는 것이다. 보다 자세한 사항은 C 언어 관련 서적을 참조하기
바란다.
앞에서 매크로를 이용해서 함수를 in-line화하였다. 이처럼 매크로를 이용해서 C++의 함수
도 in-line화할 수 있다. 그러나 보다 쉬운 방법이 있다. 다음 예제 코드는 앞에서 본 예제를
C++ 스타일로 in-line화 하고 있다.
|
inline int SQUARE ( int x ) 이 함수를 보자 함수를 정의하는 과정에서 키워드 inline을 붙
여준 것을 제외한다면 일반적인 함수와 다를 바가 없다. 그렇다면 여기서 붙여준 키워드
inline은 무엇을 의미하는 것일까? 이는 함수 SQUARE를 inline화 하라는 의미이다.
정리하자! C++에서는 성능 향상을 목적으로 함수를 inline화하기 위해서 매크로를 사용할
필요가 없다. 다만 inline이라는 키워드만 붙여주면 되는 것이다. 얼마나 간편하고 좋은가?
참고 => 매크로를 이용한 함수의 in-line화는 전처리기에 의해서 처리되지만 키워드 inline
을 이용한 함수의 in-line화는 컴파일러에 의해서 처리된다. 또한 컴파일러에 따라서는
inline 선언이 오히려 성능 향상에 해가 된다고 판단될 경우 , 그냥 무시해 버리기도 한다.
이름공간이란 여러분에게 다소 생소한 개념이다. C 언어에는 존재하지 않는 개념이기 때문
이다. 그러나 어려운 개념은 아니다. 이름공간이란 이름을 지니는 공간이라는 뜻이다. 말
그대로 특정 영역( 공간 )에 이름을 붙여주기 위한 문법적 요소이다.
한 집에 철수라는 이름을 지니는 사람이 두 명 살고 있다면 이는 문제가 된다. " 철수야 "
라고 부르면 , 어떤 철수를 말하는지 구분 지을 수 없기 때문이다. 그러나 서로 살고 있는
집이 다르다면 문제될 것이 없다. 201호에 사는 철수와 202호에 사는 철수는 구분 지을 수
있기 때문이다. 다음과 같이 부르면 되지 않는가 ?
" 202호에 사는 철수야 ! "
이것이 이름공간의 기본 원리이다.
다음 예제는 앞에서 이야기한 문제점을 간략화해서 코드로 옮겨 놓은 것이다.
A와 B라는 이름의 회사가 구현한 프로그램 모듈을 하나의 완성된 프로그램으로 옮기는
과정에서 이름 충돌이라는 문제가 발생하였다.
|
void function ( void )
{
std : : cout << " A . com에서 정의한 함수 " << std : : endl ;
}
B . com이라는 회사에서 정의한 함수가 다음 아래와 같이 정의되어 있다.
void function ( void )
{
std : : cout << " B . com에서 정의한 함수 " << std : : endl ;
}
문제는 함수의 이름이 같다는 것이다. 따라서 컴파일 시 문제를 일으킬 것이다.
둘 다 필요한 기능의 함수라서 어느것 하나를 뺄 수도 없는 상황이다. ( 물론
그렇게 가정을 하자는 뜻이다. )
다음 예제에서는 이름공간을 정의해서 문제를 해결하고 있다. 일단 위의 예제와 어느
부분이 달라 졌는지 비교해서 실행해 보기 바란다.
|
일단 위의 예제
namespace A_COM
이 부분의 함수를 보자 A라는 이름의 회사
에서 정의한 함수를 중괄호를 이용해서 감싸고 있다. 이것이 어떤 의미를 지니는지 다
음 그림을 통해서 설명하겠다.
위의 그림을 보면 namespace라는 키워드가 등장한다. 이는 이름공간을 구성하겠다는 의
미다. 즉 " namespace A_COM " 이라는 선언은 A_COM이라는 이름의 공간을 구성하겠
다는 의미가 되며, 이름공간의 범위는 이어서 등장하는 중괄호를 통해서 지정된다. 조금
더 쉽게 이야기를 하면 " 특정 영역( 공간 )의 범위를 지정하고 이름을 붙여준 것 " 이다.
따라서 위 그림에 선언되어 있는 함수 function은 A_COM이라는 이름공간 내에 선언이
된 것이다.
이제 다시 예제 namespace1 . cpp를 살펴보자 이 예제에서는 두 개의 이름공간이 등장
한다. 첫번째 이름공간은 namespace A_COM 이며, 두 번째 이름 공간은
namespace B_COM 이다. 이름공간의 내부에는 같은 이름의 함수가 정의되어 있다.
( 매개 변수의 타입까지도 같은 함수다 ). 그러나 문제되지 않는다. 왜냐하면 이름공간
이 다르면 같은 이름의 변수나 함수의 선언이 허용되기 때문이다. ( 201호에 사는
철수와 202호에 사는 철수의 예를 기억하자 ) .
한가지 주의할 것은 이름공간 내에 선언되어 있는 변수나 함수에 접근하는 방법이다. 위
의 예제 A_COM : : function ( ) ; 이 부분과 B_COM : : function ( ) ; 이 부분을
보자 다음과 같은 선언이 존재한다.
A_COM : : function ( ) ; |
위 그림에서 : : 연산자를 가리켜 " 범위 지정 연산자 ( scope resolution operator ) " 라
한다. 즉 이름공간의 범위를 지정할 때 사용하는 것이다. 따라서 위 그림과 같은 선언은
다음과 같은 의미를 지니게 된다.
" A_COM이라는 이름공간 안에 선언되어 있는 function 함수를 호출하라 "
이렇듯 이름공간 안에 선언되어 있는 변수나 함수에 접근하기 위해서는 범위 지정 연
산자를 사용해서 선언되어 있는 이름공간을 지정해 줘야 한다. 이는 201호에 사는 철수
를 그냥 " 철수야 ! " 라고 부르지 않고 , " 201호에 사는 철수야 ! "라고 부르는 것과
같은 이치이다. 이제 예제 namespace1 . cpp가 이해될 것이다.
이번에 소개하는 예제는 namespace1 . cpp와 같은 예제이다. 다만 함수의 선언과 정의
를 분리시키는 방법을 보여줄 뿐이다. ( C 언어에서 함수는 보통 이렇게 구현하지 않았던가 ! )
|
namespace A_COM
{
void function ( void ) ;
}
이 부분에서는 function이라는 이름의 함수를 A_COM이라는 이름공간 안에 선언만 하고
있다. 그리고
namespace A_COM
{
void function ( void )
{std : : cout << " A_COM에서 정의한 함수 " << std : : endl ;
}
}
이 부분에는 function이라는 이름의 함수를 A_COM이라는 이름공간 안에 정의하고 있다.
이처럼 함수의 선언과 정의를 분리하고자 하는 경우에는 같은 이름공간을 선언하고 그 내
부에 선언 및 정의해야 한다.
참고 => 위의 예제에서는 하나의 이름공간 안에 하나의 선언 및 정의만을 담고 있지만
실제로는 둘 이상의 선언 및 정의를 담을 수 있고 또 그것이 더 일반적이다.
지금까지 입력 및 출력을 하고자 하는 경우에는 std : : cout 과 std : : cin을 사용해 왔다.
익것의 정체는 아직 모르지만 사용법만은 알고 있다. 그런데 이제는 조금 이해가 될 것이다.
: : 연산자가 어떠한 의미를 지니는지 이해했기 때문이다.
std : : cout은 무슨 의미인가 ? std라는 이름공간 안에 존재하는 cout을 참조하겠다는 의미
이다. 물론 우리는 아직 cout의 정체를 모르고 있다. 마찬가지로 std : : cin은 std라는 이름
공간 안에 존재하는 cin을 참조하겠다는 의미로 사용된다. 아마도 다음과 같은 그림이 머리
속에 그려질 것이다.
|
즉 헤더 파일 iostream에 선언되어 있는 cout , cin 그리고 endl은 std라는 이름공간 안
에 선언되어 있다는 결론을 내릴 수 있다. 그래서 cout이나 cin을 참조할 때 " std : : "
라는 선언을 앞에다 붙여주는 것이다. 이름 충돌을 막기 위해서 C++ 표준에서 제공하
는 다양한 함수나 변수들을 이름공간 std 안에 선언 및 정의했기 때문이다.
이제 cout , cin 그리고 endl을 참조할 때마다 " std : : "을 앞에다 붙여줘야 하는 이
유에 대해서 알게 되었다. 그리고 이 일이 여간 귀찮은 것이 아니라는 생각도 들기 시
작한다. 오히려 접근하고하 하는 이름공간에 대한 선언이 불필요한 과거의 표준이 더
맘에 끌리기도 한다. 하지만 걱정하지 말자. 추가적인 선언 하나만으로도 여러분의
이러한 불만사항을 해소할 수 있으니 말이다.
다음 예를 보자 중요한 것은 A_COM이라는 이름공간 안에 존재하는 함수를 범위 지정 ,
연산 없이 그냥 호출하고 있다는 것이다.
|
namespace A_COM
{
void function ( void )
{std : : cout << " A_COM에서 정의한 함수 " << std : : endl ;
}
}
이 부분은 A_COM이라는 이름공간이 서언된 영역이며 그 안에 함수 function이 정의되어
있다.
using A_COM : : function ; 이 부분의 선언은 다음과 같은 의미를 지닌다 " 앞으로
function이라는 이름을 참조하는 코드가 나오면 A_COM이라는 이름공간 안에 선언된
function이라는 이름을 참조하라 ! " 그래서 function ( ) ; 이 부분에는 단순히
function이라는 이름의 함수를 호출하고 있다. 컴파일러는 이 함수를 A_COM이라는 이름
공간 안에서 찾게 된다.
이번에는 위의 예제에서 소개한 선언을 이용해서 이전에 구현했던 HelloWorld2 . cpp를
조금 변경해 보겠다.
|
|
이전 예제 using1 . cpp와 차이가 나는 부분은
using namespace A_COM ; 이 부분이다
이는 다음과 같은 의미를 지닌다. " A_COM 안에 선언되어 있는 이름을 참조하는 경우에
는 ( 그것이 변수이건 함수이건 ) 이름공간에 대한 선언 없이 function ( ) ; 이와 같이
참조 하겠다.
이제 위의 예제를 참조해서 HelloWorld3 . cpp를 조금 변경해 보자 훨씬 간결해 보이면서
도 여러분들 마음에 들 것이다.
|
이제 using 선언에 대한 내용도 마무리를 지어 보자 !
당장 눈으로 보기에는 HelloWorld3 . cpp에 존재하는 using 선언보다
HelloWorld4 . cpp에 존재하는 using 선언이 좋아 보인다. 그러나 가급적이면
HelloWorld3 . cpp에 존재하는 using 선언을 이용하는 것이 좋다.
왜냐하면 이름공간 std 안에는 다양한 종류의 선언이 존재하는데 HelloWorld4 . cpp의
using namespace std ; 이 부분과 같은 선언을 넣어버리면 프로그래밍 하기에는 조금
편리하겠지만 이름 충돌이 발생할 확률은 상대적으로 높아지기 때문이다. 때문에 앞
으로 소개하는 예제에서는 조금 코드의 양이 많아지더라도 HelloWorld3 . cpp에 존재
하는 using 선언을 사용할 것이다.
지역 변수는 전역 변수의 이름을 가린다는 특징을 기억하는가? 다음 코드를 보자
|
위의 예제 1번 에는 val이라는 이름의 전역 변수가 선언되어 있고 2번에도 val이라는
같은 이름의 지역 변수가 선언되어 있다. 그렇다면 3번에 선언되어 있는 연산에 의해서
값이 1 증가되는 변수는 지역 변수와 전역 변수 중 어느 것이 되었는가 ? 당연히 2번에
선언된 지역 변수의 값이 1 증가하게 된다.
그렇다면 main 함수 내에서 전역 변수의 값을 1 증가시키고자 할 때에는 어떻게 해야
하겠는가 ? 우리는 이러한 경우에도 범위 지정 연산자를 사용할 수 있다.
|
결국 1번에 선언되어 있는 전역 변수 val의 값이 1증가하게 된다. 이처럼 범위 지정
연산자는 전역 변수에 접근하기 위한 용도로도 사용 가능하다.
- 연습 문제 -
문제 1
|
|
|
|
|