0. C++을 시작해봅시다.
뭐든지 기초가 튼튼해야 합니다. 건물을 세울 때 기초를 다지고 뼈대를 세우는게 일의 첫 순서인 것과 같은 이치입니다. 기초와 뼈대가 튼튼하지 않으면 나중에 창문이나 문틀과 같은 세부적인 구조물을 설치하는 데 어려움을 겪기 때문입니다. 컴퓨터 언어를 배우는 것도 이와 같습니다. 프로그램의 기본적인 구조부터 확실하게 알아야 하며, 그러고 나서 루프(Loop)나 객체(Object)와 같은 세부 구조를 기본 구조 위에 세워야 합니다. 이 게시글에서는 C++의 가장 기본이 되는 구조를 살펴볼 것입니다.
1. C++의 시작
하나의 메시지를 출력하는 간단한 C++ 프로그램을 통해 시작을 해봅시다. 아래의 코드는 C++의 cout 기능을 사용하여 문자를 출력합니다. 이 코드에는 몇 개의 주석문이 달려있습니다. C++의 주석은 //로 시작합니다. 컴파일러는 주석을 무시합니다. C++는 대소문자를 구별합니다. 따라서 cout이 아닌 Cout이나 COUT이라고 표기하면 알 수 없는 식별자(Unknown Identifier)로 처리하여 컴파일 작업을 거부합니다(당연하지만, 철자도 검사합니다).
#include <iostream>
int main()
{
using namespace std;
cout << "Hello, World!";
cout << endl; // 새로운 행의 시작
cout << "Welcome to C++!"; << endl; // 동시에 할 수도 있음
return 0;
}
컴파일된 위 코드를 실행하면, 다음과 같은 메시지가 출력될 것입니다.
//Hello, World!
//Welcome to C++!
저는 출력을 주석으로 처리합니다. C 프로그래밍에 익숙한 사람은 printf() 함수 대신에 cout을 사용하는 것에 대해 조금 의아하게 생각했을 것입니다. C++에서 소스 코드 파일의 처음에 #include <stdio.h>를 입력하면 C언어의 printf(), scanf() 및 기타 표준 입출력 함수들을 사용할 수 있습니다. 그러나 저는 C++에 대해 글을 작성할 것이기 때문에 C언어의 함수를 여러 방면으로 개선한 C++의 새로운 입출력 기능을 사용합니다.
조립식 건물을 지을 때 조립용 부품들이 하는 역할들을 프로그램에선느 함수(Function)가 담당합니다. 일반적으로 프로그래머는 프로그램을 작성할 때 수행할 작업들을 나누고, 이렇게 분리된 작업들을 전담하여 처리하는 함수를 설계합니다. 위의 코드는 main()이라는 하나의 함수로 이뤄진 프로그램입니다. 위의 코드는 다음과 같은 구성 요소들을 갖고 있습니다.
- //로 시작하는 주석문
- #include 전처리 지시자
- 함수 머리: int main()
- using namespace 지시자
- {와 }로 범위가 정해지는 함수 몸체
- C++의 cout 기능을 사용하여 메시지를 출력하는 구문들
- main() 함수를 종료하는 return 구문
C++ 프로그램의 구성 요소들에 대해 자세히 알아봅시다. main() 함수의 역할을 알면 main() 앞에 오는 전처리 지시자와 같은 것들을 이해하기가 더 쉽기 때문에, main() 함수 부터 알아봅시다.
1.1. main() 함수
위 코드를 상당히 요약하면 다음과 같이 됩니다.
int main(){
구문들
return 0;
}
이것을 살펴보면 main()이라는 함수가 있다는 사실과, 그 함수가 어떠한 동작을 처리하는 것인지 알 수 있습니다. 이것은 두 부분으로 나뉘는데, 이들이 함수 정의(Function Definition)를 구성합니다. 첫 번째 행에 있는 int main() 이라는 부분이 함수 머리(Function Header)이고, 중괄호로 묶인 부분이 함수 몸체(Function Body)입니다. 이 함수 머리는 이 함수를 프로그램의 다른 부분과 연결하는 고리 역할을 하고, 함수 몸체는 그 함수가 처리하는 동작들을 컴퓨터에게 지시하는 역할을 합니다. 컴퓨터에게 내리는 지시(Introduction)를 구문(Statement)이라고 하는데, C++에서 모든 구문은 세미콜론(;)으로 끝나야 합니다. 그러므로 코딩할 때 세미콜론을 빠뜨리지 않도록 주의해야 합니다. main() 함수의 끝에 있는 구문은 return 구문입니다. 이것은 함수를 종료하는 역할을 합니다. return 구문에 대해서는 나중에 다시 자세하게 설명하겠습니다.
참고로, 구문은 컴퓨터에게 내리는 하나의 완전한 지시를 말합니다. 사용자가 작성한 소스 코드 파일을 컴파일러가 컴파일하려면, 어떤 구문이 언제 끝나고 다른 구문이 언제 시작하는지를 알 필요가 있습니다. 이를 위해 일부 언어에서는 구문 분리자(Statement Separator)라는 것을 사용합니다. 예를 들어, FORTRAN에선느 한 구문과 다음 구문을 분리하기 위해 그 행의 끝을 사용합니다. Pascal에서는 이를 위해 세미콜론을 사용합니다. 그러나 Pascal에선느 약간의 예외를 인정합니다. END 바로 앞에 있는 구문처럼 구문을 굳이 분리할 필요가 없는 경우에는 세미콜론을 생략할 수 있습니다. 그러나 C와 C++에서는 세미콜론을 분리자가 아니라 종결자(Terminator)로 사용합니다. 종결자 역할을 하는 세미콜론은 구문과 구문을 분리하는 마커가 아니라 구문의 일부라는 것이 다릅니다. 그러므로 구문의 끝에 있는 세미콜론을 절대로 빠뜨리면 안됩니다.
인터페이스 역할을 하는 함수 머리
지금 우리가 기억해야 하는 것은 main() 함수의 정의가 함수 머리 int main()으로부터 시작한다는 사실입니다. 함수 머리의 문법에 대해서는 나중에 설명하겠지만, 미리 조금만 설명하겠습니다.
일반적으로 함수는 다른 함수에 의해 호출됩니다. 이때 함수 머리는 호출 함수와 피호출 함수의 인터페이스를 나타냅니다. 함수 이름의 앞부분을 함수 리턴형(Function Return Type)이라고 부릅니다. 이것은 피호출 함수가 호출 함수로 다시 넘겨주는 정보의 흐름을 나타냅니다. 함수 이름 뒤에 있는 괄호 안의 부분을 인자 리스트(Argument List) 또는 매개변수 리스트(Parameter List)라고 합니다. 이것은 호출 함수가 피호출 함수로 넘겨주는 정보의 흐름을 나타냅니다. 이러한 일반 규칙이 main() 함수에서는 다소 혼동됩니다. 그 이유는 프로그램의 어디에도 main() 함수를 호출하는 부분이 없기 때문입니다. 일반적으로 main()은, 프로그램과 운영체제를 중개하기 위해 컴파일러가 프로그램에 추가하는 시동 코드에 의해 호출됩니다. 따라서 이 함수 머리는 main()과 운영체제 사이의 인터페이스를 나타냅니다.
main()을 위한 인터페이스를 살펴봅시다. 먼저 int부터 봅시다. 다른 함수에 의해 호출된 함수는 자신을 호출한 함수에게 값을 리턴할 수 있습니다. 이 값을 리턴값(Return Value)이라 합니다. 앞의 코드에서 main() 함수는 정수값을 리턴할 수 있습니다. 키워드 int를 보고 이 사실을 알 수 있습니다. 다음은 빈 괄호에 대해 살펴봅시다. 일반적으로 어던 함수가 다른 함수를 호출할 때 정보도 함께 전달할 수 있습니다. 함수 머리의 괄호 안에 있는 부분이 이 정보를 나타냅니다. 이 코드이 경우에 괄호 안이 비어 있는 것은 main() 함수가 어떠한 정보도 전달받지 않는다는 것을 뜻합니다. 이것을 프로그래밍 전문 용어로 말하면, main()이 어떠한 매개변수도 요구하지 않는다는 것을 의미합니다.
따라서 이 함수 머리는 main() 함수가 자신을 호출한 함수로부터 어더한 정보도 전달받지 않지만, 그 함수에게 정수값을 리턴한다는 것을 나타냅니다.
많은 기존의 프로그램들이 클래식 C의 함수 머리를 사용하고 있습니다.
main()
클래식 C에서, 리턴형을 생략하는 것은그 함수가 int 형이라고 말하는 것과 같습니다. 그러나 C++은 이와 같은 용법을 폐기했습니다.
또한 다음과 같이 사용할 수도 있습니다.
int main(void)
괄호 안에 void를 사용한는 것은 그 함수가 다른 함수로부터 어떠한 정보도 전달받지 않는다는 것을 명시적으로 밝히는 것입니다. C++에서 (C아 아닙니다) 괄호 안을 비워 두는 것은 괄호 안에 void가 있는 것과 같습니다(C에서 괄호 안을 비워 두는 것은 매개변수가 없다는 것을 암묵적으로 말하는 것입니다).
어떤 프로그래머들은 다음과 같은 스타일의 함수 머리를 사용하고, 리턴 구문을 생략합니다.
void main()
void 리턴형이, 함수가 값을 리턴하지 않는다는 것을 뜻하기 때문에, 이것은 논리적으로 타당합니다. 그러나 이러한 스타일이 일부 시스템에서는 잘 동작하더라도, C++ 표준은 아니므로 어떤 시스템에서는 동작하지 않을 수 있습니다. 따라서 이러한 스타일을 사용하는건 피하고, C++ 표준을 사용해야 합니다. 그렇게 하는 데 딱히 힘이 많이 드는 것도 아니고요.
마지막으로, main()의 끝에 리턴 구문을 두어야 한다는 귀찮은 강요에 대해 불평하는 사람들에게 ISO C++ 표준은 양보해줍니다. 컴파일러가 리턴 구문을 만나지 못한 채 main()의 끝에 도달한다면, 다음과 같은 구문으로 main()을 끝내는 것과 동일한 효과를 냅니다.
return 0;
이 암시적 리턴은 다른 함수들에는 해당되지 않고 오로지 main() 함수에만 특별히 허용됩니다.
이름이 반드시 main()이어야 하는 이유
이번 절을 시작하면서 작성한 코드에서 함수 이름을 main()으로 한 이유가 있습니다. 그것은 모든 C++ 프로그램에는 main() 함수가 반드시 하나가 있어야 하기 때문입니다. (Main(), MAIN() 등의 이름은 안됩니다. 대소문자를 구분하거든요.) 모든 C++프로그램은 main() 함수로부터 실행을 개시합니다. 이번 절을 시작하며 작성한 코드엔느 함수가 하나만 존재하므로 그 함수가 main() 함수의 역할도 해야합니다. 프로그램에는 main() 함수가 없으면 완전한 프로그램이 아닙니다. 이러한 경우에 컴파일러는 main() 함수를 정의하지 않았다고 지적할 것입니다.
물론 예외도 존재합니다. 예를 들어, 윈도우 프로그래밍에서 동적 링크 라이브러리(DLL) 모듈을 작성할 때입니다. DLL 모듈은 다른 윈도우 프로그램이 사용할 수 있는 코드로, 독립된 프로그램이 아니므로 main()이 필요 없습니다. 로봇의 컨트롤러 칩과 같은 특수한 환경에서는 main()이 필요하지 않을 수도 있습니다. 그러나 일반적인 독립형 프로그램에는 반드시 main()이 필요합니다. 방금 전에 언급한 예외적인 프로글매에 대해서는 나중에 자세히 설명하겠습니다.
1.2. C++ 주석문
더블슬래시(//) 뒤에는 항상 C++의 주석문이 옵니다. 주석문은 프로그래머가 프로그램 안에 기록해 두는 일종의 메모로, 프로그램의 구역을 구분하거나 코드의 어떤 부분이 무슨 역할을 하는 것인지 표시하는 데 사용합니다. 컴파일러는 이 주석문을 아예 무시하고, 주석문이 아닌 것만을 컴파일하기 때문에 주석문의 내용을 결코 볼 수 없습니다.
C++의 주석문은 더블슬래시에서 그 행의 끝까지입니다. 주석문은 한 행을 혼자 차지할 수도 있고, 프로그램 코드와 같은 행에 나올 수도 있습니다.
팁을 주자면, 주석문을 사용하여 프로그램을 최대한 문서화하는 것이 좋습니다. 프로그램이 복잡해질수록 주석문은 더 제 역할을 합니다. 주석문이 붙어 있으면 작성한 프로그램을 다른 사람이 쉽게 이해할 수 있습니다. 그리고 프로그램을 작성하고 오랜 시간이 지난 후에 그 프로그램을 재검토해야 할 때 주석문은 매우 유용합니다.
C++은 /*와 */로 둘러싸인 C스타일의 주석문도 인식할 수 있습니다. C 스타일의 주석문은 행의 끝이 아니라 */에 의해 끝납니다. 따라서 주석문이 여러 행을 차지할 수도 있습니다. 어떤 스타일을 선택할지는 본인의 선택이지만, C++스타일은 시작과 끝의 짝을 맞추기 위해 신경쓰지 않아도 되므로 C스타일보다 비교적 덜 문제를 일으킵니다. C언어도 C99에서 // 주석문을 지원하기 때문에 전 C++ 스타일을 권장합니다.
1.3. C++ 전처리기와 iostream 파일
여기서 알아야할 점이 있습니다. C++의 일반적인 입출력 기능을 사용하려면 다음 두 행을 프로그램에 꼭 넣어야 합니다.
#include <iostream>
using namespace std;
위 두번째 라인은 몇 가지 대체 방법이 있는데, 지금은 단순한 방법만을 다룹니다. (만약 컴파일러가 위 구문에 대해 에러를 발생하면, 그 컴파일러는 C++98 호환 버전이 아닙니다. 많이 낡았네요.) 위와 같이 하면 프로그램이 정상적으로 동작하겠지만, 이제부터 좀 더 깊이 있게 살펴보도록 하겠습니다.
C와 마찬가지로 C++도 전처리기(Preprocessor)를 사용합니다. 전처리기는 컴파일을 하기 전에 소스 파일에 대해 미리 어떤 처리를 수행하는 프로그램입니다. #으로 시작하는 것은 전처리 지시자(Directive)입니다. 전처리기는 특별히 따로 호출하는 것이 아니라, 소스 파일을 컴파일할 때 자동으로 실행됩니다. 이 절에서 처음 작성한 코드는 #include 전처리 지시자를 사용하고 있습니다.
#include <iostream>
이 지시자는 전처리기에게 iostream 파일의 내용을 프로그램에 추가하라고 지시합니다. 이와 같이 컴파일되기 전에 소스 코드에 텍스트를 추가하거나 텍스트를 대체하는 것이 전처리기가 수행하는 기본적이 역할입니다.
그런데 iostream 파일의 내용을 프로그램에 왜 포함시켜야 하는 것일까요? 그 이유는 프로그램과 바깥 세상이 정보를 주고 받을 수 있도록 하기 위해서입니다. iostream의 i는 입력(Input), o는 출력(Output)을 나타냅니다. 입력은 프로그램 안으로 들어오는 정보를 말하고, 출력은 프로그램이 바깥 세상으로 내보내는 정보를 말합니다. iostream 파일에는 C++의 몇 가지 입출력 기능이 정의되어 잇습니다. 프로그램에서 cout 기능을 사용하려면 이러한 정의가 필요합니다. #include 지시자는 iostream파일의 내용을 프로그램과 함께 컴파일러에게 보냅니다. 결과적으로는 프로그램 안에 있는 #include <iostream>이라는 행이 iostream 파일의 내용으로 대체됩니다. 사용자가 작성한 소스 파일은 변경되지 않은 채 그대로 유지되고, 소스 파일과 iostream의 결합 파일이 컴파일의 다음 단계로 넘어갑니다.
따라서, 프로그램에서 입력과 출력을 위해 cin과 cout을 사용하려면 iostream 파일을 포함시켜야 합니다.
1.4. 헤더 파일 이름
iostream과 같은 파일을 포함 파일(다른 파일에 포함된다는 의미에서), 또는 헤더 파일(파일의 앞부분에 들어간다는 의미에서)이라고 부릅니다. C++컴파일러는 많은 헤더 파일을 제공합니다. 각각의 헤더 파일은 특정 부류의 기능을 지원합니다. C에서는 헤더 파일의 이름만 보고도 파일 유형을 알 수 있도록 헤더 파일에 h 확장자를 사용했습니다. 예를 들어, C의 math.h 헤더 파일은 여러 가지 수학 함수를 지원합니다. 초기의 C++도 이 규칙을 따랐습니다. 그러나 최근의 C++에서는 규칙이 바뀌었습니다. C의 헤더 파일에만 확장자 h를 사용하고(C++프로그램에서 이들을 사용할 수는 있습니다), C++ 헤더 파일에는 확장자를 사용하지 않기로 했습니다. 즉, C++ 헤더 파일의 이름은 C 헤더 파일의 이름에서 .h를 뺀 것입니다. C 헤더 파일이 C++ 헤더 파일로 진화한 것들도 있는데, 그러한 헤더 파일들은 .h 확장자를 빼고(C++ 스타일의 헤더 파일로 만들기 위해), 이름의 앞부분에 c를 넣습니다(C로부터 유래되었다는 의미에서). 예를 들어, C의 math.h 헤더 파일은 C++에서는 cmath로 이름이 바뀌었습니다. C 버전과 C++ 버전이 같은 내용을 가진 경우도 있지만 C++ 버전에서 내용이 약간 바뀐 것도 있습니다. iostream과 같이 순수한 C++ 헤더 파일은 겉으로만 .h를 뺀 것이 아니라, 이름 공간(Namespace) 기능을 포함하고 있습니다. 헤더 파일의 이름을 짓는 규칙을 다음의 표로 요약했습니다.
헤더 파일의 종류 | 규칙 | 보기 | 설명 |
C++ 구식 스타일 | .h로 끝남 | iostream.h | C++ 프로그램에 사용할 수 있다. |
C 구식 스타일 | .h로 끝남 | math.h | C와 C++ 프로그램에 모두 사용할 수 있다. |
C++ 최신 스타일 | 확장자 없음 | iostream | C++ 프로그램에 사용할 수 있고, namespace std를 사용한다. |
C 변환 스타일 | c 접두어, 확장자 없음 | cmath | C++ 프로그램에 사용할 수 있고, namespace std와 같이 C에 없는 기능은 사용할 수도 있고 못할 수도 있다. |
파일의 유형을 구분하기 위해 서로 다른 파일 이름 확장자를 사용한다는 C의 전통을 그대로 따른다면, C++ 헤더 파일을 나타내기 위해 .hx 또는 .hxx와 같은 특별한 확장자를 사용하는 것이 합리적인 것처럼 보입니다. ANSI/ISO 위원회도 그것이 옳다고 생각했지만 어떤 확장자를 사용할 것인가 합의를 보질 못했기 때문에 무산되었습니다.
1.5. 이름 공간
iostream.h 대신에 iostream을 사용할 때, 프로그램이 iostream의 정의를 사용할 수 있게 하려면 다음과 같은 이름 공간 지시자를 사용해야 합니다.
using namespace std;
이것을 using 지시자(Directive)라고 합니다. 지금 당장은 이런 것도 있구나 하면서 개념만 잡고 넘어가고, 나중에 자세히 설명하겠습니다. 물론 간단히 설명은 하겠습니다.
이름 공간은 C++의 가장 새로운 기능입니다. 이름 공간은 프로그램을 작성할 때 여러 소프트웨어 개발업체들이 제공하는 코드들을 사용할 수 있도록 도와줍니다. 예를 들어, 두 업체의 제품을 사용해야 하는데 두 제품에 모두 wanda()라는 함수가 들어있다면, 컴파일러는 어느 제품의 wanda()를 사용해야 할지 판단할 수 없게 됩니다. 이와 같은 경우에 한 업체의 패키지를 이름 공간(Namespace)이라는 하나의 단위로 포장하여 나타낼 수 있습니다. 예를들면, Samsung이라는 회사가 만든 제품에 대해서는 Samsung이라는 이름 공간으로 포장하여, 그 회사의 wanda() 함수에 Samsung::wanda()라는 이름을 사용합니다. 다른 예로 LG라는 회사가 만든 wanda() 함수에 LG::wanda()라는 이름을 사용합니다. 그러면 컴파일러는 두 가지 버전을 확실하게 구분할 수 있습니다.
이러한 방식에 의해 C++ 컴파일러의 표준 구성 요소인 클래스, 함수, 변수는 std라는 이름 공간 안에 담겨집니다. 이와 같은 일은 .h 확장자가 없는 헤더 파일들 안에서 일어납니다. 예를 들어, 이것은 출력하는 데 상요되고 iostream 헤더 파일 안에 정의되어 있는 cout 변수가 실제로는 std::cout으로 호출되고, endl이 실제로는 std::endl이라는 것을 의미합니다. 그래서 사용자는 using 지시자를 생략하고 다음과 같은 스타일로 코드를 작성할 수 있습니다.
std::cout << "Hello, World!";
std::cout << std::endl;
그러나 iostream.h와 cout을 사용하는 이름 공간 이전의 소스 코드를 iostream과 std::cout을 사용하는 이름 공간 소스 코드로 변환하는 일이 번거롭다면 대부분의 사용자들이 이러한 변환을 달가워하지 않을 것입니다. 이때 사용하는 것이 using 지시자입니다. 다음과 같은 행을 소스 코드에 넣으면 std:: 접두어를 붙이지 않고도 std 이름 공간에 정의되어 있는 이름들을 사용할 수 있습니다.
using namespace std;
using 지시자는 std 이름 공간에 들어 있는 모든 이름을 사용할 수 있게 해줍니다. 요즘에는 이 방식을 게으른 것으로 간주하고, using 선언이라는 것을 사용하여 자신에게 필요한 이름들만 선택해서 사용할 수 있게 하는 다음과 같은 최신 방식을 선호합니다.
using std::cout;
using std::endl;
using std::cin;
std::를 cin과 cout에 직접 붙이지 않고, 아래와 같이 사용할 수도 있습니다.
using namespace std;
그러나 이 최신 방식에서 iostream에 들어 있는 다른 이름들을 사용하려면, 그 다른 이름들을 using 리스트에 개별적으로 추가해야 합니다. 저는 당분간은 두 가지 이유로 게으른 방식을 사용할 것입니다. 첫째로, 간단한 프로그램의 경우에 이름 공간 관리 기술로 어떤 것을 사용할 것인지는 큰 문제가 아닙니다. 둘째로, 아직은 좀 더 기본적인 측면을 배워야 할 때 입니다. 나중에는 최신 방식의 이름 공간 기술을 사용하겠습니다.
1.6. cout을 이용한 C++의 출력
이제 메시지를 어떻게 출력하는지 알아봅시다. 이 장의 초반부에서 작성한 프로그램은 메시지를 출력하는 다음과 같은 구문을 사용합니다.
cout << "Hello, World!";
큰따옴표 안에 들어 있는 부분이 출력할 메시지입니다. C++에서는 큰따옴표 안에 들어 있는 연속된 문자들을 문자열(String)이라고 부릅니다. 이것은 문자들이 끈처럼 하나로 이어져 보다 큰 단위를 이루고 있기 때문입니다. << 표시는 구문이 그 문자열을 cout에 전달한다는 것을 뜻합니다. << 표시가 나타내는 방향이 정보의 흐름을 상징합니다. 그렇다면 cout은 무엇을 의미할까요? cout은 문자열(String), 수(Number), 문자(Character) 들을 포함한 여러 가지 다양한 정보들을 출력하는 방법을 알고 있는, 미리 정의된 객체입니다. 객체란, 데이터가 저장되고 사용되는 방법을 서술한 추상적인 클래스 정의를 구체화한 것입니다.
객체를 배우기 위해서는 아직 남은게 많이 있기 때문에 이렇게 빨리 객체를 사용하는 것이 약간 당혹스러울 것입니다. 그래도 사용해야 합니다. 인터페이스만 알고 넘어가면 됩니다. cout 객체의 인터페이스는 간단합니다. string이 어떤 문자열을 나타낸다면, 다음과 같은 구문으로 그 문자열을 출력할 수 있습니다.
cout << string;
문자열을 출력하는 데에는 이것으로 충분합니다. C++의 입장에서 이 과정이 어떻게 처리되는지 개념적으로 살펴봅시다. 출력은 프로그램으로부터 흘러나오는 일련의 문자들, 즉 스트림입니다. cout 객체가 이 스트림을 나타냅니다. cout 객체의 속성은 iostream 파일에 정의되어 있습니다. cout 객체의 속성은 삽입 연산자(<<)를 포함합니다. 삽입 연산자는 자신의 오른쪽에 있는 정보를 스트림에 삽입합니다. 따라서 다음과 같은 구문은 "Hello, World!"라는 문자열을 출력 스트림에 삽입합니다.
만약 이 글을 읽으시는 당신이 C에서 C++로 넘어오신 분이라면, 삽입 연산자(<<)가 왼쪽 비트 시프트 연산자와 모양이 같다는 것을 눈치챘을 것입니다. 이것은 연산자 오버로딩(Operator Overloading)입니다. 연산자 오버로딩에 의해 동일한 연산자 기호가 여러 가지 의미를 가질 수 있습니다. 컴파일러가 전후 관계를 파악하여 그 연산자가 어떤 의미로 사용된 것인지 결정합니다. C언어도 약간의 연산자 오버로딩 기능을 갖고 있습니다. 예를 들어, &는 주소 연산자와 AND 비트 연산자로 모두 사용됩니다. 또한 * 기호는 곱셈 연산자와 포인터 간접 참조 연산자로 모두 사용됩니다. 여기서는 이 연산자들이 무슨 기능을 하는지가 중요한 것이 아니라, 동일한 연산자가 여러 의미로 사용될 수 있다는 사실과, 컴파일러가 전후 관계를 파악하여 연산자의 의미를 결정한다는 사실이 중요합니다(일상에서 동음이의어의 개념이라고 보면 편합니다). C++은 이와 같은 연산자 오버로딩 기능을 더 확장하여 사용자가 정의하는 데이터형인 클래스에 대해서도 의미를 오버로딩합니다.
조정자 endl
이번에는 게시글의 초반부에서 작성한 코드의 두 번째 출력 구문에 나타나는 다음 표기를 살펴봅시다.
cout << endl;
endl은 새로운 행이 시작된다는 중요한 개념을 나타내는 특별한 C++ 표기입니다. endl을 출력 스트림에 삽입하면 화면 커서가 다음 행의 시작 위치로 갑니다. cout에게 특수한 의미를 가지는 endl과 같은 특별한 표기들을 조정자(Manipulator)라고 부릅니다. cout과 마찬가지로, endl도 iostream 헤더 파일에 정의되어 있고, std 이름 공간에 속합니다.
cout은 문자열을 출력하고 나서 다음 행의 시작 위치로 커서를 자동으로 넘겨주지 않습니다. 그래서 게시글의 초반부에서 작성한 코드의 첫 번째 cout 구문은 출력 문자열의 끝에 있는 마침표 바로 뒤에 커서를 그대로 남겨둡니다. 각각의 cout 구문에 의한 출력은 마지막 출력이 끝난 곳엣 시작됩니다. 그래서 만일 endl을 생략하면 한 줄로 쭉 출력이 됩니다.
다른 예시를 살펴봅시다. 코드를 다음과 같이 작성했다고 가정합시다.
cout << "Hello,";
cout << "World!";
cout << "Welcome to C++!";
cout << endl;
이것은 다음과 같은 출력을 만듭니다.
//Hello,World!Welcome to C++!
앞 문자열에 이어 다음 문자열이 출력된 것에 주목하십시오. 두 개의 문자열이 결합되는 곳에 빈칸을 하나 넣고 싶으면, 두 문자열 중의 어느 하나에 빈칸을 미리 넣어야 합니다.
개행 문자(\n)
C++는 출력에 새로운 행을 나타내는 다소 고전적인 또 하나의 방법인 C표기 \b을 제공합니다.
cout << "What's next?\n";
\n은 개행문자(Newline Character)라고 부르는 하나의 문자를 나타내는 것으로 간주됩니다. 문자열을 출력하고자 할 때, 꼬리표처럼 끝에 endl을 달아놓는 것보다 문자열의 일부로 개행 문자를 포함시키는 것이 타이핑 수고가 적습니다.
cout << "Hello, World!\n";
cout << "Hello, World!" << endl;
다른 한편, 개행 그 자체를 발생시키고 싶을 때에는, 두 방법이 같은 양의 타이핑 수고를 유발합니다. 그렇지만 사람들을 보통 endl을 타이핑하는 것을 더 편하게 여깁니다.
cout << "\n";
cout << endl;
대체로 저는 따옴표로 묶인 문자열을 출력할 때에는 개행 문자 \n을 문자열에 넣어 사용하고, 그 외의 경우에는 endl을 사용합니다. 개행 문자는 이스케이프 시퀀스(Escape Sequence)라고 하는 특별한 키 스트로크 조합의 한 예시입니다. 이스케이프 시퀀스에 대해서는 나중에 다른 게시글로 소개하겠습니다.
1.7. C++ 소스 코드의 모양
FORTRAN과 같은 프로그래밍 언어에서는 하나의 구문이 하나의 행을 차지합니다. 이러한 프로그래밍 언어에서는 캐리지 리턴(Carriage Return)이 구문들을 분리하는 역할을 합니다. 그러나 C++에서는 세미콜론이 구문의 끝을 표시합니다. 이것은 C++에서 캐리지 리턴을 빈칸이나 탭과 같은 방식으로 자유롭게 사용할 수 있다는 것을 의미합니다. 즉, C++에서는 캐리지 리턴을 사용할 수 있는 곳에 빈칸을 대신 사용할 수 있고, 그와 반대로 빈칸을 사용할 수 있는 곳에 캐리지 리턴을 대신 사용할 수 있다는 뜻입니다. 다시 말해서, 이것은 하나의 구문을 여러 행에 걸쳐서 서술할 수도 있고, 여러 개의 구문을 한 행에 붙여 놓을 수도 있다는 의미입니다.
물론 여기에도 반드시 지켜야 할 규칙이 하나 있습니다. 바로, C나 C++에서 이름과 같은 요소의 중간에 빈칸이나 탭, 캐리지 리턴을 넣을 수 없으며, 또한 문자열의 중간에 캐리지 리턴을 넣을 수 없다는 것입니다...고 하지만, C++11 버전에서 추가된 내용에 따르면 문자열 안에 캐리지 리턴을 허용하고 있습니다.
토큰과 화이트스페이스
한 행의 코드에서 더 이상 분리할 수 없는 기본 요소를 토큰(Token)이라고 합니다. 일반적으로 하나의 토큰은 빈칸, 탭, 캐리지 리턴에 의해 다음 토큰과 분리됩니다. 빈칸, 탭, 캐리지 리턴을 집합적으로 화이트스페이스(White space)라고 부릅니다. 괄호나 콤마와 같은 일부 단일 문자들은 화이트스페이스로 분리하지 않아도 되는 토큰입니다. 다음은 화이트스페이스를 언제 사용할 수 있는지 보여줍니다.
return 0; // 안됩니다. return 0;이어야 합니다.
return(0); // 됩니다. 화이트스페이스가 생략되었습니다.
return (0); // 됩니다. 화이트스페이스가 사용되었습니다.
intmain(); // 안됩니다. 화이트스페이스가 생략되었습니다.
int main() // 됩니다. () 안에 화이트스페이스가 생략되었습니다.
int main ( ) // 됩니다. ( ) 안에 화이트스페이스가 사용되었습니다.
C++ 소스 코드 스타일
C++ 프로그램은 매우 자유로운 스타일로 작성할 수 있습니다. 그러나 명쾌하고 정형화된 스타일로 프로그램을 작성하면 이해하기가 쉽습니다. 유효하기는 하지만 모양이 보기 흉한 프로그램은 무슨 내용인지 파악하기가 어렵습니다. 대부분의 C++프로그래머들은 다음의 규칙을 따른 스타일을 사용합니다.
- 한 행에 하나의 구문을 사용한다.
- 함수를 여는 중괄호와 닫는 중괄호에 각각 한 행을 할애한다.
- 함수 안에 들어갈 구문들은 중괄호에서 약간 오른쪽으로 들어간 위치에서 시작한다.
- 함수 이름과 괄호 사이에는 어떠한 화이트스페이스도 넣지 않는다.
앞의 세 규칙은 프로그램을 이해하기 쉽게 만들기 위한 것이고, 마지막 규칙은 루프와같이 괄호를 사용하는 구성요소와 함수를 구별하기 위한 것입니다. 이 책에서 그 외의 규칙들을 설명할 필요가 있을 때에는 그때 가서 설명하겠습니다.
2. C++ 구문
C++ 프로그램은 함수들의 집합입니다. 또한 함수는 구문들의 집합입니다. C++는 여러 종류의 구문들을 사용합니다. 여기서 일부 구문들에 대해 알아봅시다.
#include <iostream>
int main()
{
using namespace std;
int carrots;
carrots = 25;
cout << "나는 당근을 ";
cout << carrots;
cout << "개 갖고 있다.";
cout << endl;
carrots = carrots - 1;
cout << "나는 하나를 먹었고, 이제 당근은 " << carrots << "개이다." << endl;
return 0;
}
위 코드(저는 이 코드를 carrots.cpp라고 이름 지었습니다.)에는 두 종류의 새로운 구문이 들어있습니다. 하나는 변수를 선언하는 선언 구문(Declaration Statement)이고, 다른 하나는 변수에 값을 대입한느 대입 구문(Assignment Statement)입니다. 또한 이 프로그램에서는 cout을 이 게시글 초반에 작성했던 코드와는 다르게 사용하고 있습니다.
빈 행은 변수 선언부를 프로그램의 다른 부분과 구분하는 역할을 합니다. 이것은 C 언어에서 일반적으로 통용되던 관례입니다. 그러나 C++에서는 잘 쓰이지 않습니다. 다음은 이 코드의 실행 결과입니다.
//나는 당근을 25개 갖고 있다.
//나는 하나를 먹었고, 이제 당근은 24개이다.
이 프로그램에 대해 더 자세히 살펴봅시다.
2.1. 선언 구문과 변수
컴퓨터는 매우 정확하고 논리적인 기계입니다. 컴퓨터에 어떤 정보를 저장하려면그 정보가 얼마나 많은 기억 공간(Memory Storage)을 차지하는지 알아야 하며, 또한 그 정보가 저장될 위치도 알아야 합니다. C++에서 이것을 하는 가장 쉬운 방법은 선언 구문을 사용하는 것입니다. 선언 구문은 정보를 저장할 기억 공간의 형태를 지정하고, 그 위치가 어디인지 말해주는 꼬리표(Label)를 제공합니다. 예를 들면, carrots.cpp에서는 다음과 같은 선언 구문을 사용합니다.
int carrots;
이 구문은 하나의 정수를 저장할 수 있는 기억 공간을 프로그램이 사용할 예정이라고 선언합니다. 그러면 컴파일러는 이를 위한 기억 공간을 대입하고, 그 위치가 어디인지 말해 주는 꼬리표를 붙이는 작업을 수행합니다. C++에서는 몇 가지 종류의 데이터형을 사용할 수 있는데, int형이 가장 기본적인 데이터형입니다. int형은 소수부가 없는 정수(Integer)를 말하며, 양의 정수와 음의 정수가 있습니다. int형의 크기는 사용하는 시스템에 따라 다릅니다. int형을 포함한 기본적인 데이터형에 대해서는 나중에 다른 게시글에서 설명하겠습니다.
그 다음으로 해야 할 일이 저장된 데이터의 이름을 선언하는 것입니다. 선언 구문은 데이터형을 선언하는 것일 뿐만 아니라, 앞으로 프로그램에서 그 기억 공간에 저장되어 있는 값을 carrots라는 이름으로 사용하겠다고 선언하는 것입니다. 이와 같은 carrots를 변수(Variable)라고 부릅니다. 변수라는 이름은 값이 변경될 수 있기 때문에 붙여진 이름입니다. C++에서는 모든 변수를 사용하기 전에 미리 선언해야 합니다. carrots.cpp에서 그 선언을 생략하면, 프로그램이 carrots를 사용하려고 시도하는 곳마다 컴파일러가 에러를 내보냅니다. 한 번 해 보는 것도 나쁘진 않습니다.
참고로, BASIC과 같은 언어에서는 변수를 따로 선언할 필요 없이 새로운 이름을 사용할 때마다 변수들이 자동으로 생성됩니다. 이것은 프로그래머에게 친절을 베푸는 것일 수도 있지만, 변수 이름을 잘못 입력하면 자기도 모르는 사이에 엉뚱한 변수가 생성될 수 있습니다. 이런 면에서 보면 C++은 잠재적인 버그의 소지를 없애는 것이죠.
일반적으로 선언 구문은 저장될 정보의 데이터형과, 거기에 저장될 데이터를 프로그램에서 무슨 이름으로 사용할 것인지를 함께 선언합니다. carrots.cpp에서는 int형 정수를 저장할 수 있는 carrots라는 변수를 생성하고 있습니다.
carrots.cpp에서 사용된 선언 구문을 정의 선언 구문, 또는 간단히 줄여서 정의(Definition)라고 부릅니다. 프로그램에 정의 선언 구문이 있으면 컴파일러는 그 변수를 위해 메모리 공간을 대입합니다. 그리고 약간 복잡한 것으로, 참조(Reference) 선언 구문이라는 것도 있습니다. 참조 선언 구문은 어딘가 다른 곳에서 이미 정의된 변수를 사용할 수 있도록 허용합니다. 일반적으로 선언이 꼭 정의일 필요는 없지만, 이 코드에서는 정의에 해당됩니다.
만약 당신이 C나 Pascal을 이미 알고 있다면 변수 선언에도 익숙할 것입니다. 그러나 여기에도 약간의 차이가 있습니다. C나 Pascal에서는 모든 변수를 함수나 프로시저의 시작 위치에 선언해야 합니다. 그러나 C++에는 이러한 제약이 없습니다. 일반적으로 C++ 프로그램에서는 변수를 처음 사용하는 곳 바로 앞에 선언하면 됩니다. 이렇게 하면 변수 선언이 바로 앞에 있으므로 그 변수의 데이터형이 무엇인지 알기 위해 프로그램을 샅샅이 뒤질 필요가 없습니다. 그러나 이러한 변수 선언 스타일은 모든 변수의 이름을 한 자리에 모아 놓을 수 없다는 단점이 있기 때문에, 어떤 함수가 어떤 변수들을 사용하는지 한눈에 파악하기가 어렵습니다. 요약하자면, C++의 변수 선언 스타일은 가능하면 그 변수를 처음 사용하는 곳에서 가장 가까운 위치에 그 변수를 선언하는 것입니다.
2.2. 대입 구문
대입 구문은 기억 위치에 어떤 값을 대입합니다. 예를 들어, 다음과 같은 대입 구문은 변수 carrots로 나타내는 기억 위치에 정수 25를 대입합니다.
carrots = 25;
여기서 등호는 대입 연산자(Assignment Operator)라고 부릅니다. C++의 특징은 대입 연산자를 연이어 사용할 수 있다는 것입니다(물론 C도 가능합니다). 예를 들면, 다음과 같은 코드는 유효합니다.
int steinway;
int baldwin;
int yamaha;
yamaha = baldwin = steinway = 88;
앞에 나온 대입 구문은 오른쪽에서 왼쪽으로 차례대로 처리됩니다 즉, 88이 steinway에 대입되고, 88인 steinway의 값이 baldwin에 대입됩니다. 마지막으로 88인 baldwin의 값이 yamaha에 대입됩니다. carrots.cpp의 두 번째 대입 구문은 변수의 값을 바꿀 수 있다는 것을 보여줍니다.
carrots = carrots - 1;
대입 연산자의 오른쪽에 있는 수식은 산술식의 한 예시입니다. 컴퓨터는 25에서 1을 빼서 carrots의 값으로 24를 얻습니다. 그러고 나서 대입 연산자는 새로운 값을 carrots 위치에 저장합니다.
2.3. cout의 새로운 사용법
이 게시글에서 나온 지금까지의 코드들은 cout에 출력할 문자열을 넘겨주었습니다. 거기에 더해 carrots.cpp는 정수를 값으로 가지고 있는 변수를 cout에 넘겨줍니다.
cout << carrots;
이 코드는 carrots라는 단어 대신 carrots에 현재 저장되어있는 값인 정수 25를 출력합니다. 내부적으로 이것은 두 가지 작업을하는 것입니다. 먼저 cout은 carrots를 정수 25로 대체합니다. 그러고 나서 정수 25를 적다안 출력 문자인 '2'와 '5'로 변환합니다.
이처럼 cout은 문자열 뿐만 아니라 정수도 출력할 수 있습니다 이것이 별것 아닌 것처럼 보이지만 실은 아주 특별한 것입니다. 정수 25와 문자열 "25"는 완전히 다른 것입니다. 문자열 "25"는 문자 '2'와 문자 '5'를 단순히 붙여 놓은 것입니다. 프로그램은 내부적으로 문자 '2'와 '5'에 대항하는 코드를 각각 따로 저장합니다. 문자열 "25"를 출력할 때 cout은 그 문자열 안에 있는 문자 '2'와 문자 '5'를 하나씩 꺼내 이어 붙여 출력합니다. 그러나 정수 25는 온전한 하나의 수이기 때문에, 25라는 수를 하나의 2진수로 저장합니다. 여기서 중요한 것은 cout이 int형의 수를 출력하기 전에 문자형으로 변환한다는 사실입니다. 다행스럽게도 cout은 carrots가 변환할 필요가 있는 정수라는 사실을 알고 있습니다.
이것은 구식 C와 비교하면 cout이 얼마나 편리한 것인지 알 수 있습니다. 구식 C에서 문자열 "25"와 정수 25를 출력하려면 printf()를 다음과 같이 사용해야 합니다.
printf("문자열: %s\n", "25");
printf("정수형: %d\n", 25);
printf()는 생김새가 복잡한 것은 둘째치고, 문자열 또는 정수를 출력한다는 것을 나타내기 위해 특별한 코드 %s와 %d를 구별해서 사용해야 합니다. 또한 printf()에게 문자열을 출력하라고지시하면서 잘못하여 정수를 넘겨주게되면 printf()는 그것이 잘못된 것인지 모르고 쓰레기를 출력합니다(물론 이 쓰레기를 이용해야하는 상황도 있긴 합니다).
출력할 것이 문자열인지 정수인지 알아차리는 cout의 능력은 C++가 객체 지향 기능을 갖고 있기 때문입니다. C++의 삽입 연산자는 자신의 뒤에 오는 데이터형이 무엇인지에 따라 자신이 취할 동작을 결정합니다. 이것은 연산자 오버로딩의 한 예시입니다. 나중에 함수 오버로딩과 연산자 오버로딩에 대해 설명할 때, 이게 어떻게 가능한지 자세히 설명하겠습니다.
당신이 printf()에 익숙하다면, cout이 다소 생소하게 느껴질 것입니다. 그래서 이미 손에 익은 printf()에 자꾸 애착이 갈지도 모르지만, cout은 %d나 %s와 같은 형식 지정자들을 반드시 사용해야 하는 printf()보다 훨씬 간단하며 뛰어난 장점을 갖고 있습니다. 게다가 cout은 여러 가지 데이터형을 스스로 판단할 수 있고, 생김새가 단순하며, 또한 사용자 정의 데이터형을 인식할 수 있도록 기능을 확장할 수 있습니다. 당신이 printf()를 좋아하는 이유가 정교한 출력 제어가 가능하기 때문이라면, cout도 그와 동일한 효과를 낼 수 있으니 안심해도 좋습니다(이에 대해서는 추후에 작성될 다른 게시글에서 다루겠습니다).
3. C++의 기타 구문
구문의 예시를 두 개만 더 살펴봅시다. 다음의 코드(getinfo.cpp로 이름 붙이겠습니다)는 실행되는 동안에 사용자가 값을 입력시킬 수 있도록 carrots.cpp를 확장합니다. 그렇게 하기 위해 이 코드는 cin을 사용합니다. cout은 출력을 수행하는 객체인데, cin은 입력을 수행하는 객체입니다. 또한 이 코드는 cout 객체의 또 다른 용도를 설명합니다.
#include <iostream>
int main()
{
using namespace std;
int carrots;
cout << "당근 얼마나 갖고 계세요?" << endl;
cin >> carrots;
cout << "여기 두개 더 드릴게요. ";
carrots = carrots + 2;
cout << "이제 당근은 모두 " << carrots << "개네요." << endl;
return 0;
}
위 코드의 실행 결과는 다음과 같습니다.
//당근 얼마나 갖고 계세요?
//12
//여기 두개 더 드릴게요. 이제 당근은 모두 14개네요.
위 코드는 두 가지 새로운 기능을 보여줍니다. 하나는 cin을 사용하여 키보드로부터 정수를 입력받는 것이고, 다른 하나는 네 개의 출력을 하나로 이어 붙여 출력하는 것입니다. 이에 대해 잠시 살펴봅시다.
3.1. cin 사용법
위 코드의 실행 결과가 보여 주듯이, 키보드로 타이핑한 값 12가 변수 carrots에 대입됩니다. 다음과 같은 구문이 이를 수행합니다.
cin >> carrots;
이 구문의 생김새를 보면 정보다 cin에서 carrots쪽으로 흐른다는 것을 볼 수 있습니다. 이 과정을 좀 더 깊이 알아봅시다. C++에서 출력은 프로그램에서 바깥 세상으로 흘러나오는 문자들의 스트림이고, 입력은 바깥 세상에서 프로그램 안으로 흘러 들어가는 문자들의 스트림입니다. cin은 iostream 파일에 입력 스트림을 나타내는 객체로 정의되어 있습니다. 출력할 때에는 cout이 << 연산자를 사용하여 문자들을 출력 스트림에 삽입하고, 입력할 때에는 cin이 >> 연산자를 사용하여 입력 스트림에서 문자들을 가져옵니다. 가져온 정보를 저장할 변수는 >> 연산자의 오른쪽에 적습니다. 입력 연산자와 출력 연산자로 >>와 << 기호를 선택한 것은 정보가 흐르는 방향을 연상시키기 위함입니다.
cin도 cout과 마찬가지로 똑똑한 객체입니다. cin은 키보드로 입력한 일련의 문자들인 입력을, 그것을 저장할 변수가 받아들이는 형태로 바꿉니다. 위 코드의 경우에는 carrots를 int형 변수로 선언했으므로 입력은 컴퓨터가 정수를 저장할 때 사용하는 수치 형태로 바뀝니다.
3.2. cout에 의한 출력의 결합
getinfo.cpp의 두 번재 새로운 기능은 네 개의 출력 구문을 하나로 결합하는 것입니다. iostream 파일에는 << 연산자가 여러 개의 출력을 하나로 연결할 수 있도록 정의되어 있습니다.
cout << "이제 당근은 모두 " << carrots << "개네요." << endl;
이것은 문자열 출력과 정수 출력을 하나의 구문으로 결합합니다. 이것의 실행 결과는 다음과 같은 세 행의 코드가 만들어 내는 결과와 동일합니다.
cout << "이제 당근은 모두 ";
cout << carrots;
cout << "개네요.";
cout << endl;
또한 독자가 cout을 사용하라는 권고를 받아들이기로 했다면, 하나로 결합된 출력 구문을 다음과 같이 네 행에 걸쳐 작성할 수도 있습니다.
cout << "이제 당근은 모두 "
<< carrots
<< "개네요."
<< endl;
이렇게 작성할 수 있는 이유는 C++의 프로그램 작성 스타일이 토큰들 사이에서 캐리지 리턴과 빈칸을 바꾸어 사용하는 것을 허용하기 때문입니다. 따라서 폭이 모자라 한 행에 작성할 수 없을 때 이 방법을 사용하면 편리합니다. 여기서 한 가지 주목할 것은, 다음과 같은 출력이
//이제 당근은 모두 14개네요.
다음과 같은 출력과 동일한 행에 나타난다는 점입니다.
//여기 두개 더 드릴게요.
이것은 앞에서도 설명했듯이, 한 cout 구문의 출력이 앞선 cout 구문의 출력에 바로 이어지기 때문입니다. 이것은 둘 사이에 또 다른 출력 구문들이 들어 있는 경우에도 적용됩니다.
3.3. cin과 cout: 클래스 맛보기
지금까지 cin과 cout을 통해 객체에 대해 약간이나마 알아보았습니다. 특별히 이 절에서 클래스의 개념에 대해 잠시 알아봅시다. 클래스는 C++로 객체 지향 프로그래밍(OOP)을 하는 데 필요한 핵심 개념 중 하나입니다.
클래스는 사용자가 정의하는 데이터형입니다. 클래스를 정의하려면, 클래스로 표현할 수 있는 정보의 종류는 무엇이고, 그것으로 수행할 수 있는 동작은 무엇인가 서술해야 합니다. 클래스와 객체의 관계는 데이터형과 변수의 관계와 같습니다. 클래스 정의는 데이터 형식과 그것이 사용되는 방법을 서술하는 것입니다. 반면에 객체는 클래스에 서술된 데이터 형식에 따라 실제로 생성되는 구체물입니다. 이를 비유적으로 표현한다면, 클래스는 필기구와 같이 어떤 범주를 나타내는 추상적 개념이고, 그 속의 연필과 같은 것은 그 클래스가 구체화된 객체라고 할 수 있습니다.이러한 비유를 더욱 확장시킨다면, 필기구라는 클래스에는 그 필기구의 쓰임새까지도 포함시킬 수 있습니다. 예를 들면 펜돌리기, 글씨 쓰기 등이 될 수 있습니다. 다른 객체 지향 프로그래밍 언어에서는 클래스를 객체형(Object Type)이라 부르고, 객체를 객체 인스턴스(Object Instance) 또는 인스턴스 변수(Instance Variable)라고 부르기도 합니다.
다음의 변수 선언으로 좀 더 자세히 살펴봅시다.
int carrots;
이 선언은 int 형의 속성을 가진 변수(carrots)를 생성합니다. 즉 carrots는 정수를 저장할 수 있고, 더하기나 빼기와 같은 연산에 사용될 수 있습니다. 이제 cout을 살펴봅시다. cout은 ostream 클래스의 속성을 가지고 생성된 객체입니다. ostream 클래스는 iostream 파일에 정의되어 있습니다. ostream 클래스 정의에는 ostream 객체가 표현할 수 있는 데이터 형식과, 그 데이터 형식을 가지고 수행할 수 있는 동작, 예를 들면 문자열을 출력 스트림에 삽입하는 동작 등을 정의하고 있습니다. 마찬가지로 cin은 istream 클래스의 속성을 가지고 생성된 객체이며, 이것 역시 iostream 파일에 정의되어 있습니다.
그러니까, 클래스는 데이터 형식의 모든 속성을 서술한 것이고, 객체는 그 서술에 따라 실제로 생성된 구체물입니다.
지금까지 클래스를 사용자 정의 데이터형이라고 말해왔지만, 당신은 ostream이나 istream 클래스를 만든 적이 없습니다. 이는 함수 라이브러리에서 원하는 함수를 불러다 쓰듯이, 클래스 라이브러리에서 원하는 클래스를 불러다 쓸 수 있기 때문입니다. ostream과 istream 클래스가 바로 그런 클래스입니다. 기술적으로, 이 두 클랫느느 C++ 언어에 내장된 클래스가 아닙니다. 이 둘은 C++ 컴파일러와 함께 제공되는 클래스의 예제들입니다. 이 클래스 정의들은 컴파일러에 내장되어 있지 않고, iostream 파일에 들어 있습니다. 이 클래스 정의들을 사용자가 수정할 수 있지만, 하지 않는 것을 권장합니다. 초창기의 C++는 iostream 계열의 클래스와 파일 입출력을 담당하는 fstream 계열의 클래스만을 제공했습니다. 그러나 ANSI/ISO C++ 위원회가 몇 가지 클래스들을 표준에 더 추가시켰습니다. 오늘날 대부분의 C++는 부가적인 클래스 정의들을 패키지의 일부로 제공합니다.
클래스 정의는 그 클래스의 객체를 대상으로 수행할 수 있는 모든 동작들을 서술합니다. 어떤 특정 객체를 대상으로 허용된 어떤 동작을 수행하려면 해당 객체에 메시지를 전달하면 됩니다. 예를 들어, cout 객체를 사용하여 문자열을 출력하고 싶다면, cout 객체에 어떠한 문자열을 출력하라는 메시지를 전달합니다. 메시지를 전달하는 방법은 두 가지 입니다. 하나는 클래스 메소드를 사용하는 함수 호출이고, 다른 하나는 cin과 cout의 경우처럼 연산자를 오버로딩하는 것입니다. 그래서 cout을 이용한 출력 구문은 오버로딩된 << 연산자를 사용하여 출력할 메시지를 cout에 전달합니다. 이 경우에는 출력할 메시지가 매개변수입니다.
4. 함수
함수는 C++ 프로그램을 구성하는 모듈일 뿐만 아니라, C++의 OOP 정의에 필수적인 사항이므로 잘 익혀야 합니다. 함수의 일부 측면은 좀 고급진 주제입니다. 따라서 함수에 대해서는 좀 더 시간이 지난 후에 다른 게시글에서 다룰 생각입니다. 그러나 함수의 기초적인 특징을 알면, 나중에 함수를 이해하기 쉽기에 기초적인 사항을 설명하겠습니다.
C++ 함수는 두 가지 유형으로 나뉩니다. 리턴값의 유무로 말입니다. 표준 함수 라이브러리에 있는 함수를 사용할 수도 있고, 사용자가 직접 만들 수도 있습니다. 먼저 리턴값이 있는 라이브러리 함수를 살펴보고, 사용자가 직접 만드는 함수에 대해 알아보겠습니다.
4.1. 리턴값이 있는 함수
리턴값이 있는 함수는 변수에 대입할 수 있는 하나의 값을 만들어 냅니다. 예를 들어, C/C++ 표준 라이브러리에는 어떤 수의 제곱근을 리턴하는 sqrt()라는 함수가 있습니다. 만약, 6.25의 제곱근(2.5)을 구하여 변수 x에 대입해야 한다고 가정해 봅시다. 그렇다면 코드를 다음과 같이 작성하면 됩니다.
x = sqrt(6.25);
여기서 sqrt(6.25)라는 표현이 sqrt() 함수를 호출(Call)합니다. 이 sqrt(6.25)를 함수 호출(Function Call)이라 하고, 이것에 의해 호출되는 sqrt() 함수를 피호출 함수(Called Function)라고 합니다. 또한, 함수 호출이 들어 있는 함수를 호출 함수(Calling Function)라고 합니다.
괄호 안에 있는 값(위의 코드에서 6.25에 해당하는 것)이 함수에 전달되는 정보입니다. 이러한 방식으로 함수에 전달되는 값을 전달인자(Argument)라고 합니다.
sqrt() 함수는 6.25의 제곱근을 계산하여 그 결과인 2.5를 호출 함수에게 다시 리턴합니다. 다시 돌려받는 이 값을 함수의 리턴값(Return Value)라고 합니다. 결과적으로 함수가 작업을 끝내면 구문에 있는 함수 호출이 그 리턴값으로 대체됩니다. 그렇기 대문에 위의 코드에서는 리턴값 2.5가 변수 x에 대입됩니다. 간단히 말해서, 매개변수는 함수에 전달되는 정보이고, 리턴값은 함수가 돌려주는 값입니다.
함수 호출에 관련된 문법은 이것이 전부입니다. 다만, 함수를 사용하기 전에 C++ 컴파일러는 그 함수가 어떤 종류의 매개변수를 사용할 것이며, 어떤 종류의 리턴값을 리턴하는지 미리 알고 있어야 합니다. 즉, 리턴값이 정수인지, 문자인지, 부동소수점수인지 등등을 미리 알고 있어야 합니다. 이런 정보가 없다면 컴파일러는 그 리턴값을 어떻게 처리할지 판단할 수 없게 됩니다. C++는 함수 원형(Function Prototype) 구문을 사용하여 이런 정보를 컴파일러에게 전달합니다.
변수 선언이 변수에 대해서 하는 일을 함수 원형이 함수에 대해서 합니다. C++ 라이브러리에는 sqrt() 함수가 소수부가 있는 수를 매개변수로 전달받으며, 전달받은 것과 동일한 데이터형의 값을 리턴한다고 정의되어 있습니다. sqrt()의 함수 원형은 다음과 같습니다.
double sqrt(double);
앞에 있는 double은 sqrt() 함수가 double형의 값을 리턴한다는 뜻입니다. 괄호 안에 있는 double은 sqrt() 함수가 double형의 매개변수를 전달받는다는 뜻입니다. 따라서 이것은 다음과 같은 코드에서 사용하는 sqrt() 함수를 정확히 서술하고 있는 함수 원형입니다.
double x;
x = sqrt(6.25);
함수 원형의 끝에 있는 세미콜론은 이것도 분명히 하나의 구문이라는 것을 나타냅니다. 바로 이 세미콜론이 이것을 함수 머리가 아니라 함수 원형으로 인식하게 만듭니다. 이 세미콜론이 없으면 컴파일러는 이것을 함수 머리로 인식하게 되고, 당연히 그 뒤에는 그 함수를 정의하는 함수 몸체가 올 것이라고 기대합니다. 프로그램에서 sqrt() 함수를 사용하려면 함수 원형을 제공해야 합니다. 함수 원형을 제공하는 방법은 두 가지가 있습니다.
- 함수 원형을 소스 코드 파일에 직접 입력한다.
- 함수 원형이 들어 있는 cmath 헤더 파일을 포함시킨다.
당연히 두 번재 방법이 실수를 저지를 위험이 적습니다. C++ 라이브러리의 모든 함수들은 함수 원형이 하나 또는 그 이상의 헤더 파일에 들어 있습니다. 그러므로 라이브러리 사용 설명서나 온라인 도움말을 잘 읽고 적당한 헤더 파일을 포함시키면 됩니다. 예를 들어, sqrt() 함수에 대한 설명에는 cmath 헤더 파일을 포함시켜야 한다고 쓰여 있습니다.
함수 원형과 함수 정의를 혼동하면 안 됩니다. 함수 원형은 함수의 인터페이스만 알려주는 것입니다. 즉, 그 함수에 전달하는 정보와 그 함수가 리턴하는 정보만을 서술합니다. 반면에 함수 정의는 그 함수가 수행할 작업을 위한 실제 코드(예를 들면, 어떤 수의 제곱근을 구하는 코드)를 포함하고 있습니다. C와 C++는 라이브러리 함수들에 대해서 이 두 가지 기능을 따로 분리해 놓았습니다. 라이브러리 함수들의 컴파일된 코드는 라이브러리 파일에 들어 있고, 함수 원형들은 헤더 파일에 들어 있습니다.
함수 원형은 그 함수가 처음 사용되는 곳 앞에 두어야 합니다. 일반적인 관행은 main() 함수 정의 앞에다 함수 원형을 두는 것입니다. 다음 코드는 라이브러리 함수 sqrt() 를 사용하는 방법을 설명합니다. 여기서는 cmath 헤더 파일을 포함시켜 함수 원형을 제공하고 있습니다.
#include <iostream>
#include <cmath>
int main(){
using namespace std;
double area, side;
cout << "마루 면적을 입력해주세요: ";
cin >> area;
side = sqrt(area);
cout << "사각형 마루라면 한 변이 " << side
<< "입니다!" << endl;
<< "어-메이징" << endl;
return 0;
}
이 코드를 sqrt.cpp라고 부르겠습니다. sqrt() 함수는 double형 데이터를 다루므로, 이 코드에선느 area와 side를 모두 double 형 변수로 만들었습니다. dobule형 변수를 선언하는 형식은 int형 변수를 선언하는 형식과 같습니다. double형은 변수 are와 side에 소수부가 있는 수들을 저장할 수 있게 합니다. 정수가 double형 변수에 저장될 때에는, .0 소수부를 가진 실수값으로 저장됩니다. 추후에 작성될 다른 게시글에서 설명하겠지만, double형 변수에는 int형 변수보다 더 큰 범위의 수를 저장할 수 있습니다.
C++에서는 새로운 변수를 어디에서나 선언할 수 있습니다. 그래서 sqrt.cpp에서는 사용하기 직전까지도 side를 선언하지 않았습니다. 도한 C++에서는 변수를 선언할 때 값도 함께 대입할 수 있기 때문에, 다음과 같이 할 수 있습니다.
double side = sqrt(area);
이러한 과정을 초기화(Initialization)라고 부릅니다. 이것도 추후에 별도의 게시글로 작성하겠습니다.
cin은 입력 스트림에서 가져온 정보를 dobule형으로 변환할 줄 압니다. 또한 cout은 double형 정보를 출력 스트림에 삽입하는 방법을 알고 있습니다. 앞에서 이미 설명했듯이, 이것은 cin과 cout이 똑똑한 객체이기 때문입니다.
4.2. 변이 함수들
어떤 함수는 하나 이상의 정보를 요구합니다. 그러한 함수는 여러 개의 매개변수를 콤마로 분리합니다. 예를 들어, 수학 함수인 pow()는 매개변수를 2개 요구합니다. 이 함수는 첫 번째 매개변수를 두 번째 매개변수의 횟수만큼 거듭제곱하여 그 결과값을 반환합니다. 이 함수의 원형은 다음과 같습니다.
double pow(double, double);
예를 들어, 5의 8제곱을 구한다면 이 함수를 다음과 같이 사용합니다.
answer = pow(5.0, 8.0);
매개변수를 사용하지 않는 함수도 있습니다. 예를 들어, C 라이브러리(cstdlib 나 stdlib.h 헤더 파일과 관련된 것들)에는 매개변수는 사용하지 않지만, 임의의 정수를 리턴하는 rand()라는 함수가 있습니다. 이 함수의 원형은 다음과 같습니다.
int rand(void);
키워드 void는 매개변수를 사용하지 않는다는 것을 명시적으로 밝히는 것입니다. void를 생략하고 괄호 안을 그냥 비워 두면 매개변수를 사용하지 않는다는 것을 묵시적으로 밝히는 것입니다. 즉, 다음과 같은 코딩이 가능합니다.
numRand = rand();
다른 컴퓨터 언어와는 달리, C++에서는 함수에 매개변수가 없더라도 함수 호출에 반드시 괄호를 사용해야 합니다.
값을 리턴하지 않는 함수도 있습니다. 예를 들어, 숫자를 전달받아 그 숫자를 출력하는 함수를 작성했다고 가정해봅시다. 이 함수의 역할은 값을 화면에 출력하는 것이기 때문에 리턴이 필요하지 않습니다. 이러한 경우에는 함수 원형이 다음과 같습니다.
void printing(double);
printing()은 값을 리턴하지 않으므로, 이 함수를 대입 구문이나 기타 다른 표현의 일부분으로는 사용할 수 없고, 순수한 함수 호출 구문으로만 사용할 수 있습니다.
printing(3.14);
어떤 프로그래밍 언어에서는 값을 리턴하는 것을 함수(Function)라 부르고, 값을 리턴하지 않는 것을 프로시저(Procedure) 또는 서브루틴(Subroutine)이라고 부릅니다. 그러나 C++(C도 마찬가지)에서는 이 두 가지를 모두 함수(Function)라고 부릅니다.
4.3. 사용자 정의 함수
표준 C 라이브러리는 140개 이상의 미리 정의된 함수를 제공합니다. 이들 중에 어느 것이 자신의 프로그래밍 요구에 적합하다면 그것을 사용하면 됩니다. 그러나 경우에 따라서는 사용자가 직접 함수를 직접 함수를 작성해야 합니다. 특히 클래스를 설계할 때에 그렇습니다. 지금부터 사용자 정의 함수를 작성하는 작성하는 방법에 대해 알아봅시다. 우리는 사용자 정의 함수를 이미 몇 번 사용해 보았습니다. 그것은 바로 main() 함수입니다. 무슨 말이냐면, 모든 C++ 프로그램에는 반드시 main() 함수가 들어 있습니다. 이 main() 함수가 사용자 정의 함수입니다. 이제 새로운 사용자 정의 함수를 프로그램에 추가하는 방법을 알아봅시다. 라이브러리 함수와 마찬가지로, 사용자 정의 함수도 그 이름을 불러서 호출합니다. 또한 함수를 사용하기 전에 main() 함수의 앞에 반드시 그 함수의 원형을 두어야 합니다. 그러고 나서 새로운 함수를 위한 코드를 작성해야 합니다.
가장 간단한 방법은 하나의 소스 코드 파일에서 그 함수를 위해 작성하는 새로운 코드를 main() 함수의 코드 뒤에 두는 것입니다. 다음의 코드처럼 말입니다.
#include <iostream>
void printer(int);
int main(){
using namespace std;
printer(3);
cout << "Cnt??: ";
int cnt;
cin >> cnt;
printer(cnt);
cout << "Hou!!!" << endl;
return 0;
}
void printer(int n){
using namespace std;
cout << "Ah! " << n << "X" << endl;
}
위의 코드를 cstFunc.cpp 라고 부르겠습니다. main() 함수는 printer() 함수를 두 번 호출합니다. 한 번은 3을 매개변수로 사용하여 호출하고, 또 한 번은 변수 cnt를 매개변수로 사용하여 호출합니다. 첫 번째 호출과 두 번째 호출 사이에서 사용자는 cnt의 값으로 사용할 정수를 입력합니다. 이 입력을 안내하는 cout 프롬프트 메시지의 끝에 개행 문자를 넣지 않은 이유는 프롬프트 메시지와 같은 행에서 정수를 입력할 수 있도록 하기 위한 것입니다. 다음은 cstFunc.cpp의 실행 예시입니다.
//Ah! 3X
//Cnt??: 30
//Ah! 30X
//Hou!!!
함수 모양
cstFunc.cpp에 있는 printer() 함수를 정의하는 방법은 main() 함수를 정의하는 방법과 동일합니다. 먼저 함수 머리가 있어야 하고, 여는 중괄호와 닫는 중괄호로 함수 몸체를 둘러싸야 합니다. 그러므로 함수 정의의 모양을 다음과 같이 일반화할 수 있습니다.
type functionname(arguments){
statements;
}
printer() 함수를 정의하는 소스 코드는 main() 함수의 닫는 괄호 뒤에 나옵니다. C와 마찬가지로 C++에서는(예외로 Pascal에서는 안그렇습니다) 어떤 함수 안에 다른 함수를 넣을 수 없습니다. 즉, 각각의 함수는 서로 독립되어 있습니다. 모든 함수는 동등한 자격으로 만들어집니다.
함수 머리
cstFunc.cpp에 있는 printer() 함수는 다음과 같은 함수 머리를 갖고 있습니다.
void printer(int n)
앞에 나오는 void는 printer() 함수가 값을 반환하지 않는다는 것을 뜻합니다. 따라서 printer() 함수는 main() 함수에 있는 어떤 변수에 대입할 수 있는 값을 만들지 않습니다. 그러므로 다음과 같이 사용된 첫 번째 printer() 함수는 올바른 것입니다. 함수 머리에서 괄호 안에 있는 int n은 printer() 함수가 하나의 int형 매개변수를 전달받는다는 것을 뜻합니다. 여기서 n은 새로운 변수로서 함수가 호출될 때 매개변수로 전달되는 값이 여기에 대입됩니다. 따라서 다음과 같은 함수 호출은 함수 머리에 정의된 변수 n에 값 3을 대입합니다.
printer(3);
printer() 함수의 몸체에 있는 cout 구문은 n을 사용할 일이 있을 때 전달받은 이 값을 사용합니다. 이것이 printer(3) 이 3을 출력하는 이유입니다. 실행 예시에서 printer(cnt)라는 함수 호출은 30을 호출합니다. 이유는 간단합니다. cnt에 30이 들어있기 때문입니다. 간단히 말해서, printer() 함수의 머리는 이 함수가 하나의 int형 매개변수를 요구하며, 값을 리턴하지 않는다는 것을 알려줍니다.
main() 함수의 머리를 다시 살펴보겠습니다.
int main()
앞에 있는 int는 main() 함수가 int형 값을 리턴한다는 것을 나타냅니다. 괄호 안이 비어 있는 것은(넣고 싶다면, void를 넣어도 됩니다) 매개변수가 없다는 것을 나타냅니다. 값을 리턴하는 함수는 값을 리턴하고 그 함수를 끝내기 위해 반드시 return 이라는 키워드를 사용해야 합니다. 그렇기 때문에 지금까지 main() 함수의 끝에 다음과 같은 구문을 사용한 것입니다.
return 0;
main() 함수가 int형 값을 리턴하기로 되어 있기 때문에, 정수 0을 리턴하는 것은 논리적으로 합당합니다. 그럼 이 값을 도대체 어디로 리턴되는 것일까요? 프로그램 안에 main() 함수를 호출하는 부분이 있진 않습니다.
운영체제 (Unix 또는 DOS)가 이 프로그램을 호출한다는 것이 이 의문에 대한 해답입니다. 많은 운영체제가 프로그램이 리턴하는 값을 처리할 수 있습니다. 예를 들어, 프로그램을 실행하고, 프로그램이 리턴하는 종료값(Exit Value)을 검사하는 Unix의 셸 스크립트(Shell Script)나 DOS의 배치(Batch) 파일을 작성할 수 있습니다. 일반적으로 종료값이 0이면 프로그램이 성공적으로 실행되었다는 것을 의미하고, 종료값이 0이 아닌 다른 값일 경우에는 문제가 발생했다는 것을 의미합니다. 그러므로 만약 파일을 열다 실패햇을 경우에는 0이 아닌 다른 값을 리턴하는 프로그램을 작성할 수 있습니다. 그러고 나서 그 프로그램을 실행시켜 만약 실행에 실패한 경우에는 다른 작업을 처리하는 셸 스크립트나 배치 파일을 설계할 수 있습니다.
4.4. 리턴값이 있는 사용자 정의 함수
이번 절에서는 return 구문을 사용하는 함수를 작성할 것입니다. 값을 리턴하는 함수가 어떤 모양이어야 하는지는 main() 함수를 참고하여 살펴보았습니다. main() 함수와 마찬가지로, 함수 머리에 리턴형을 지정하고, 함수 몸체의 끝에 return을 사용하면 됩니다.
분(Minute)을 초(Second)로 변환시켜주는 프로그램을 작성해봅시다. 여러분들도 잘 알다시피, 1분은 60초에 해당합니다.
#include <iostream>
int m2s(int); // 함수 원형
int main(){
using namespace std;
int m, s;
cout << "초로 환산하고 싶은 분을 입력하세요(정수)";
cin >> m;
int s = m2s(m);
cout << m << " 분은 ";
cout << s << " 초입니다." << endl;
return 0;
}
int m2s(int m){
return 60 * m;
}
위 프로그램을 convert.cpp라고 하겠습니다. 위 프로그램을 실행시키면, 다음과 같이 진행됩니다.
//초로 환산하고 싶은 분을 입력하세요(정수) 1
//1 분은 60 초 입니다.
main()은 cin을 사용하여 변수 m에 int형 값을 입력받습니다. 그 값은 매개변수로 사용되어 m2s() 함수로 전달되고, m2s() 함수에 있는 변수 s에 대입됩니다. m2s() 함수는 60 * m이라는 수식의 값을 계산하여 그 결과를 return 구문을 통해 main()으로 반환합니다. 이는 return 뒤에 간단한 수만 사용할 수 있을 뿐만이 아니라, 복잡한 수식도 사용할 수 있다는 것을 의미합니다. 이는 나중에 재귀의 개념을 배울 때 사용하니, 꼭 기억해두시길 바랍니다.
이렇게 하면 새로운 변수를 만들고, 수식을 계산한 결과를 그 변수에 대입하고, 마지막으로 그 값으 리턴하는 여러 단계의 번거로운 작업을 피할 수 있습니다. 그러나 이 방식이 마음에 들지 않는다면, 다음처럼 더 긴 방식을 선택할 수도 있습니다.
int m2s(int m){
int s = 60 * m;
return s;
}
두 함수는 모두 동일한 결과를 만들지만, 아래의 것이 약간 시간이 더 걸립니다.
일반적으로, 상수를 사용할 수 있는 곳이라면 어디든지 그 상수와 같은 데이터형의 값을 리턴하는 함수를 사용할 수 있습니다. 예를 들어, m2s() 함수는 int형 값을 리턴하기 때문에 다음과 같이 사용할 수 있습니다.
int study = m2s(50);
int class = study + m2s(10);
cout << "1 시간은 " << m2s(60) << " 초 입니다." << endl;
위의 코드의 모든 경우에, 프로그램은 int형 리턴값을 계산하여 그 구문에서 그 값을 사용합니다.
함수 원형은 함수의 인터페이스를 나타냅니다. 즉, 함수 원형은 그 함수가 프로그램의 다른 부분들과 정보를 주고받는 형식을 나타냅니다. 매개변수 리스트는 그 함수에 전달할 정보들의 종류를 나타내고, 함수형은 그 함수가 리턴하는 값의 데이터형을 나타냅니다. 흔히 프로그래머들은 정보가 안으로 들어가 가공된 후에 다시 밖으로 나온다는 의미에서 함수를 블랙박스에 비유합니다. 함수 원형이 이러한 정보의 흐름을 표현합니다.
앞의 예시 코드에서 m2s() 함수는 짧고 간단하지만 함수로서 갖추어야 할 다음과 같은 특징을 모두 지니고 있습니다.
- 함수 머리와 함수 몸체를 가진다.
- 매개변수를 갖는다.
- 값을 리턴한다.
- 함수 원형을 요구한다.
앞으로 함수를 작성할 때에는 앞서 작성한 m2s() 함수의 형태를 표준으로 사용하면 좋습니다. 추후에 작성될 게시글에서 함수에 대해 보다 더 자세하게 설명하겠지만, 이 절에서 설명한 내용 만으로도 C++에서 함수를 어떻게 작성하고, 어떻게 사용하는지 대략적으로 이해할 수 있을 것입니다.
4.5. 복수 함수 프로그램에 using 지시자 사용하기
cstFunc.cpp에서 두 개의 함수는 다음과 같은 using 지시자를 각각 따로 갖고 있었습니다.
using namespace std;
이것은 두 함수가 모두 cout을 사용하고, std 이름 공간(Namespace)에 있는 cout의 정의에 접근할 필요가 있기 때문입니다.
cstFunc.cpp에 있는 두 함수가 std 이름 공간을 사용할 수 있게 만드느 ㄴ또 다른 방법은 두 함수의 외부에 그리고 위에 using 지시자를 넣는 것입니다.
#include <iostream>
using namespace std;
void printer(int);
int main(){
printer(3);
cout << "Cnt??: ";
int cnt;
cin >> cnt;
printer(cnt);
cout << "Hou!!!" << endl;
return 0;
}
void printer(int n){
cout << "Ah! " << n << "X" << endl;
}
접근이 필요한 함수들만 차별적으로 std 이름 공간에 접근할 수 있도록 하는 것이 일반적입니다. 예를 들면, convert.cpp에서 main() 만이 cout을 사용하니, m2s() 함수까지 std 이름 공간을 사용할 수 있게 만들 필요는 없다고 보는 것입니다. 그래서 using 지시자가 main() 함수 안에만 존재합니다. 이것은 main() 함수만 std 이름 공간에 접근할 수 있게 제한합니다.
대략적으로 요약해보자면, 어떤 프로그램이 std 이름 공간의 요소들을 사용할 수 있게 만드는 방법은 다음과 같은 여러 방법이 있습니다.
- 파일에 있는 모든 정의 위에 넣기: 이는 파일에 있는 모든 함수가 std 이름 공간의 모든 내용을 사용할 수 있게 만듭니다.
- 특정 함수 정의 안에 넣기: 이는 그 특정 함수가 std 이름 공간의 모든 내용을 사용할 수 있게 만듭니다.
- 다음과 같은 지시자를 특정 함수 정의 안에 넣기: 이는 그 특정 함수가 cout과 같은 특정 요소를 사용할 수 있게 만듭니다.
using std::cout;
- std 이름 공간에 있는 요소를 사용할 때는 언제든지, using 지시자를 완전히 생략하고 std:: 접두어를 사용할 수 있습니다.
std::cout << "이렇게 사용하는 것을 말하는 것입니다" << std::endl;
이번 게시글을 대략적으로 요약하자면 다음과 같습니다.
C++ 프로그램은 함수라고 부르는 하나 또는 그 이상의 모듈들로 구성됩니다. 프로그램은 main() 이라는 함수부터 실행됩니다. 따라서, 어떤 프로그램이든지 간에 main() 함수가 반드시 하나 있어야 합니다. 함수는 함수 머리와 함수 몸체로 이뤄집니다. 함수 머리는 그 함수가 (리턴값이 존재한다면) 어떤 데이터형의 값을 리턴하고, 어떤 종류의 정보를 매개변수로 받는지를 나타냅니다. 함수 몸체는 중괄호로 둘러싸인 C++ 구문들로 이뤄집니다.
C++ 구문들의 유형은 다음과 같습니다.
- 선언 구문(Declaration Statement): 함수에서 사용되는 변수의 이름과 데이터형을 선언합니다.
- 대입 구문(Assignment Statement): 대입 연산자(=)를 사용하여 변수에 값을 대입합니다.
- 메시지 구문(Message Statement): 객체에 메시지를 전달하여 활동을 시작하게 합니다.
- 함수 호출(Function Call): 함수를 동작시킵니다. 호출된 함수가 종료디면, 프로그램은 해당 함수를 호출한 함수의 함수 호출 바로 뒤에 있는 구문으로 복귀합니다.
- 함수 원형(Function Prototype): 함수가 기대하는 매개변수의 개수, 매개변수의 데이터형, 함수의 리턴형을 선언합니다.
- return 구문(Return Statement): 호출된 함수가 리턴하는 값을 호출한 함수에 전달합니다.
클래스는 사용자가 정의하는 데이터 형식입니다. 클래스에는 정보를 어떻게 표현할 것이며, 그 정보를 대상으로 수행할 수 있는 동작은 무엇인지가 함께 정의됩니다. 데이터형을 사용하여 간단한 변수를 생성하는 것과 마찬가지로, 객체는 클래스의 속성을 사용하여 실제로 생성하는 구체물입니다.
C++에서는 입력과 출력을 위해 cin과 cout이라는 두 개의 미리 정의된 객체를 사용합니다. 이들은 각각 istream과 osteam 클래스의 속성으로 생성된 객체입니다. istream과 ostream 클래스는 iostream 헤더파일에 정의되어 있습니다. 이 클래스들은 입력과 출력을 연속된 문자들의 스트림(Stream)이라고 간주합니다. 삽입(Insertion) 연산자(<<)는 ostream 클래스에 정의되어 있으며, 입력 스트림으로부터 정보를 추출합니다. cin과 cout은 생각보다 똑똑한 객체이기 때문에, 프로그램의 문맥에 따라 한 형식을 다른 형식으로 자동으로 변환할 수 있습니다.
C++은 방대한 양의 C 라이브러리 함수를 사용할 수 있습니다. 라이브러리 함수를 사용하려면 함수 원형을 제공하는 헤더 파일을 프로그램에 포함시켜야 합니다.
지금까지는 간단한 코드를 통해 C++ 프로그램의 전체적인 개요를 그려보았습니다. 다음 게시글에서는 조금 더 깊게 들어가 보도록 하겠습니다.