이 장에서는 전문적인 C프로그래머가 되기 위해서 필요한 기본적인 사항들을 배우기로 하겠다. 

오늘은 다음과 같은 내용을 배울 것이다. 

여러 가지 프로그래밍 언어 중에서 C언어가 훌륭한 이유

프로그램 개발 과정의 4 단계

첫 번째 C프로그램을 작성하고 컴파일해서 실행하는 방법

컴파일러와 링커에서 출력되는 에러 메시지


C 언어의 역사

여러분은 아마도 C언어의 시초가 무엇이고, 어떻게 해서 'C'라는 이름을 가지게 되었 는지에 대해서 많은 의문을 가지고 있을 것이다. C는 1972년 벨 연구소에서 데니스 리치(Dennis Ritchie)에 의해서 개발되었다. 이 언어는 우연히 개발된 것이 아니라 많은 컴퓨터에서 사용되는 UNIX 운영 체계를 제작하는데 사용한다는 특별한 목적을 가지고 개발되었다. 즉, C 언어는 처음부터 프로그래머들이 작업을 완료하는데 유용하게 사용할 수 있도록 고안되었다. 이처럼 C 언어는 뛰어난 기능과 융통성을 제공해 주었으므로 오래지 않아 벨 연구소 뿐만 아니라 다른 여러 곳으로 빠르게 보급되었다. 많은 프로그래머들은 모든 프로그램을 작성하기 위해서 C 언어를 사용하기 시작했다. 그러나 서로 다른 곳에서 C 언어를 사용하는 프로그래머들은 C 언어를 약간씩 수정하여 각자 자신만의 독특한 환경을 구성 하기 시작했고, 결과적으로 C 언어로 작성된 프로그램들 간에는 미묘한 차이가 생기게 되었으며, 그로 인해 많은 프로그래머들은 다른 곳에서 작성된 프로그램을 정상적으로 실행하기 위해 다시 수정해야 하는 상황이 발생했다. 이러한 문제점을 해결하기 위해서 미국의 국가 표준 협회(ANSI : American National Standard Institute)에서는 C에 대한 표준을 만들기 위해서 1983년 위원회를 결성했고, ANSI 표준 C(ANSI Standard C)라고 알려진 표준안을 발표했다. 이제, C 언어의 명칭에 대해서 알아보도록 하자. C 언어는 이전에 사용되던 B언어를 계승한다는 점에서 'C'라는 이름을 가지게 되었다. B 언어도 벨 연구소의 켄 톰슨 (Ken Thompson)에 의해서 개발된 것이다. B 언어의 명칭이 어떻게 정해졌는지는 (B = Bell Labs) 쉽게 추축할 수 있을 것이다

오늘날 컴퓨터 프로그램을 작성할 때에는 C, 파스칼(Pascal), 베이직(BASIC), 자바(Java) 와 같이 많은 고급 언어 중에서 필요한 것을 선택하여 사용할 수 있다. 이런 대부분의 프로그래밍 언어는 특정 작업을 처리하는 데 있어서 최상의 환경을 제공해 준다. 그러나 많은 컴퓨터 전문가들은 다음과 같은 몇 가지 이유를 예로 들어서 여러 가지 프로그래밍 언어 중에서도 C 언어가 가장 뛰어나다고 주장한다


- C 언어는 강력한 기능을 제공하며 융통성을 발휘하는 언어이다. C 언어를 사용하여 수행할 수 있는 작업의 종류에는 아무런 제한이 없다. 프로그래밍 언어 자체에는 전혀 제한 사항이 없다. C 언어는 운영체제(operating system), 문서 작성기(word processor),  스프레드시트(spreadsheet)와 같은 응용 프로그램을 제작하는 데 사용될 수 있으며, 심지어는 다른 언어의 컴파일러를 개발하기 위해서도 사용될 수 있다

- C 언어는 전문적인 프로그래머들이 가장 선호하는 프로그래밍 언어이다. 그래서 시중  에는 매우 다양한 C 컴파일러와 유용한 유틸리티 프로그램이 존재한다


- C 언어는 이식성이 뛰어나다. 이식성(portable)이 뛰어나다는 것은, 예를 들어 IBM PC  와 같은 컴퓨터 시스템에서 작성된 C 프로그램이 DEC VAX 시스템과 같은 시스템에서 도 거의 수정 없이 컴파일되고 실행될 수 있다는 것을 뜻한다. 이식성은 앞에서 설명했던 C 컴파일러에 대한 여러 가지 규칙을 제안하는 C 언어의 ANSI 표준에 의해서 더욱 높아 지게 된다. 


- C 언어는 키워드(keyword)라는 몇 개의 단어만을 사용하여 프로그램을 구성하는 간편 함을 제공한다. 키워드는 C 언어의 기보적인 동작을 수행하는 데 사용되는 단어이며,  예약어라고도 한다. 어떤 사람들은 더 많은 키워드를 가지는 언어가 더욱 강력한 기능을  제공할 것이라고 생각할 것이다. 그러나 실제로는 그렇지 않다. C 언어를 사용하여 많은 프로그래밍 작업을 수행함에 따라, 기본적인 키워드만으로도 대부분의 작업을 수행하는 프로그램을 작성할 수 있다는 것을 알게 될 것이다


- C 언어는 모듈을 기본으로 한다. C 언어로 작성되는 프로그램은 함수(function)라는 각각의 루틴별로 작성될 수 있으며, 효율성 면에서도 하뭇 단위로 작성되는 것이 좋다. 각각의 함수는 다른 응용 프로그램을 작성할 때에도 사용될 수 있다. 프로그래머는 필요에 따라 함수에 자료를 전달하여 유용하고 재사용 가능한 프로그램을 작성할 수 있다. 이런 몇 가지 특징으로도 알 수 있듯이, C 언어는 가장 뛰어난 프로그래밍 언어라고 할 수있다. 참고로 C++라는 새로운 언어에 대해서 알아보도록 하자. 여러분은 이미 C++와 객체 지향 프로그래밍(OCP : Object-Oriented Programming) 방식에 대해서 들은 적이 있을 것이다. 또한, C와 C++ 언어의 차이점은 무엇이고, C 언어를 배운 다음에 다시 C++ 언어를 배워야 하지 않느냐는 의문을 가질 수도 있을 것이다. 그러나 이 점에 대해서는 전혀 걱정할 필요가 없다. C++ 언어는 C 언어의 기본적인 기능을 그대로 가지고 있으며, 추가로 객체 지향 프로그래밍에 필요한 사항을 포함하고 있는 언어이다. 그래서 나중에 C++ 언어를 배우더라도 C 언어에 대해서 배우는 거의 대부분의 내용이 C++ 언어에서도 적용되므로 많은 어려움이 없을 것이다. 결과적으로, C 언어를 배우는 것은 현재의 가장 강력하고 유용한 프로그래밍 언어를 배우는 것일 뿐 아니라, 미래에 대한 준비 로 객체 지향 프로그래밍의 기초를 배우는 것이기도 하다. 최근에 많은 관심을 모으고 있는 또다른 언어로 자바(Java)가 있다. 자바는 C++와 마찬가지 로 C를 기반으로 하는 언어이다. 만약 나중에 자바를 배우게 된다면 대부분의 내용이 C 언어에도 적용되는 것임을 알 수 있을 것이다.


일반적으로 어떤 문제를 해결하기 위해서는 단계별로 하나씩 해결해 나가야 한다. 우선, 문제점을 분명히 파악해야 한다. 문제점이 무엇인지 정확히 모른다면 해결 방법을 찾을 수 없다. 일단 문제점을 파악하고 나면 문제를 해결하기 위한 계획을 세울 수 있다. 또한, 계획을 작성한 후에는 실제로 필요한 작업을 수행할 수 있다. 마지막으로, 계획에 따라 필요한 작업을 수행하고 나면 문제가 바르게 해결되었는지 확인해볼 필요가 있을 것이다. 이런 개념은 프로그래밍에 있어서도 동일하게 적용되며 다른 많은 경우에도 적용될 것이다. C 언어를 사용하여 프로그램을 개발하고나 또는 동일한 문제를 해결하기 위해 다른 어떤 프로그래밍 언어를 사용하여 프로그램을 개발할 때에는 다음과 같은 일련의 과정을 따라야 한다. 


프로그램의 목적을 결정한다.

프로그램을 작성할 때 사용하기 원하는 방법을 결정한다. 문제를 해결하기 위해서 프로그램을 작성한다.

결과를 확인하기 위해서 프로그램을 실행한다. 

첫 번째 단계인 프로그램의 목적으로는 문서 작성기나 데이터베이스 프로그램을 작성하는 것을 예로 들 수 있다. 또는 화면 상에 사용자의 이름을 출력하는 것과 같은 간단한 목적도 될 수 있다. 만약 어떤 목적을 설정하지 않았다면 프로그램을 작성하는 것이 필요하지 않을 것이고, 전체적인 과정은 첫번째 단계에서 종료될 것이다. 두 번째 단계는 프로그램을 작성할 때 사용하기 원하는 방법을 결정하는 것이다. 이에 대한 예로는 주어진 문제를 해결하기 위해서 컴퓨터 프로그램을 작성할 필요가 있는가, 어떤 자료를 조사해야 하는가, 또는 어떤 공식을 사용하여 문제를 해결할 것인가를 결정하는 것이다. 두 번째 단계에서 프로그래머는 알아둘 필요가 있는 사항과 문제를 해결하기 윟나 순서를 결정해야 한다. 예를 들어, 원의 면적을 구하기 윟나 프로그램을 작성해야 한다고 가정하자. 첫 번째 단계는 윈의 면적을 구해야 한다는 분명한 목적이 있으므로 완료된 것이다. 두 번째 단계에서 원의 면적을 구하기 위해서 필요한 것을 결정해야 한다. 예제에서 원의 반지름을 알고 있다고 하자. 프로그래머는 원의 면적을 구하기 위해서 pr2이라는 공식을 사용할 수 있을 것이다. 이제 두 번째 단계까지의 작업이 완료되었으므로 실제 프로그램 개발 과정인 세 번째와 네 번째 단계로 진행할 수 있을 것이다


프로그램 개발 과정은 다시 단계별로 나눌 수 있다. 우선, 첫 번째 단계에서는 소스 코드 (Source code)를 작성하여 디스크 파일로 저장하기 위해서 에디터를 사용한다. 두 번째 단계에서는 오브젝트 코드(object code)를 생성하기 위해서 소스 코드를 컴파일 (compile)한다. 세 번째 단계에서는 실행 파일(executable file)을 생성하기 위해서 컴파일된 코드를 링크(link)한다. 마지막으로 네 번째 단계에서는 작성된 프로그램이 정상적으로 동작하는지 확인하기 위해서 프로그램을 실행한다. 


4.1 소스 코드의 입력

소스 코드는 프로그래머가 원하는 작업을 컴퓨터가 수행하도록 지시하는 데 사용되는 일련의 명령문(statements)이나 명령(commands)이다. 앞에서도 설명했듯이, 프로그램 개발 과정의 첫 번째 단계는 에디터를 사용하여 소스 코드를 입력하는 것이다. 예를 들어, 다음과 같은 한 줄의 C 소스 코드가 있다. 


printf("Hello, Kim!"); 


이 것은 컴퓨터가 'Hello, Kim!'이라는 내용을 화면 상에 출력하도록 지시하는 문장이다. 일단, 이 명령문의 상세한 내용에 대해서는 잠시 미루어 두자. 


▶ 에디터의 사용

대부분의 컴파일러는 소스 코드를 입력하는 데 사용할 수 있는 에디터(editor)를 내장하고 있지만, 일부는 에디터를 내장하고 있지 않다. 여러분의 컴파일러가 에디터를 내장하고 있는지 알아보기 위해서 컴파일러와 함께 제공되는 설명서를 참조하기 바란다. 컴파일러에 에디터가 내장되어 있지 않더라도 많은 에디터 프로그램을 사용할 수 있다. 대부분의 컴퓨터 시스템에는 에디터로 사용할 수 있는 프로그램이 포함되어 있다. UNIX를 사용 중이라면 ed, ex, edit, emacs, vi와 같은 에디터를 사용할 수 있을 것이다. 

마이크로 소프트(Ms)의 윈도우를 사용 중이라면 노트패드(Notepad)가 포함되어 있다. DOS 5.0이상의 버전을 사용 중이라면 Edit를 사용할 수 있다. 또한, 5.0 이전의 DOS버전을 사용 중이라면 Edlin을 사용할 수 있다. PC DOS 6.0 이상의 버전을 사용 중이라면 E를 사용 할 수 있다. OS/2를 사용 중이라면 E와 EPM 에디터를 사용할 수 있다. 대부분의 문서 작성기는 작성된 문서를 형식화(format)하기 위해서 특수한 코드를 사용하고 있다. 이런 코드는 다른 프로그램에서 정상적으로 사용될 수 없다. 그래서 C 언어를 포함하여 거의 모든 프로그램에서는 텍스트의 표준 형식으로 ASCII(American Standard Code for Information Interchange)를 사용한다. 워드퍼펙트(WordPerfect), 아미프로(AmiPro), 워드(Word), 워드패드, 워드스타(WordStar)와 같은 여러 가지 문서 작성기에서는 소스 파일을 독특한 문서 파일 형식뿐 아니라 ASCII 형식의 텍스트 파일로 저장할 수 있을 것이다. 

문서 작성기를 사용하여 작성된 파일을 ASCII 파일 형식으로 저장하기 위해서는 문서 작성기 내에서 ASCII나 텍스트 파일 저장 기능을 선택하면 된다. 소스 코드를 입력하여 파일로 저장할 때에는 적절한 이름을 지정해야 한다. 파일의 이름은 프로그램의 동작을 설명하는 것이어야 한다. 또한, C 프로그램의 소스 파일을 저장할 때에는 확장자로 .C를 사용해야 한다. 소스 파일에 어떤 이름과 확장자를 사용하든지 관계는 없지만 확장자로 .C를 사용하는 것이 가장 좋다. 


4.2 소스 코드의 컴파일

여러분은 C 언어로 작성된 소스 코드를 이해할 수 있겠지만 컴퓨터는 결코 소스 코드를 이해할 수 없을 것이다. 대신, 컴퓨터는 기계어(machine language)로 작성된 명령문을 요구한다. 그래서 C 언어로 작성된 프로그램이 컴퓨터 상에서 실행될 수 있으려면, 우선 소스 코드에서 기계어로 변환될 필요가 있는 것이다. 프로그램 개발 과정의 두 번째 단계인 변환 작업은 컴파일러라는 프로그램에 의해서 수행된다. 컴파일러는 소스 파일을 입력받아서 대응하는 기계어 명령문을 작성하여 디스크 파일로 저장한다. 컴파일러에 의해서 생성되는 기계어 명령문을 오브젝트 코드(object code)라고 하며, 오브젝트 코드가 포함되어 있는 디스크 상의 파일을 오브젝트 파일(object file)이라고 한다. 각각의 컴파일러에서 오브젝트 코드를 생성하기 위해서는 정해진 명령을 사용해야 한다. 대개 소스 코드를 컴파일하기 위해서는 컴파일러의 이름과 함께 소스 파일의 이름을 입력하게 된다. 다음은 다양한 DOS/윈도우용 컴파일러에서 RADIUS.C라는 소스 파일을 컴파일하기 위해서 필요한 명령의 예이다. 


  컴파일러

  명   령

Microsoft C

cl radius.c

Borland's Turbo C

tcc radius.c

Borland C

bcc radius.c

Zortec C

ztc radius.c


RADIUS.C 파일을 UNIX에서 컴파일하기 위해서는 다음과 같은 명령을 사용하기 바란다.

cc radius.c 

실제로 여러분의 컴파일러에서 사용되는 정확한 명령을 알아보기 위해서는 제공되는 컴파일러 설명서를 참조하기 바란다. 만약 그래픽 개발 환경을 사용 중이라면 컴파일 작업은 훨씬 더 쉬어진다. 대부분의 그래픽 환경에서는 컴파일 아이콘을 선택하거나 또는 메뉴에서 특정 항목을 선택하여 프로그램 리스트를 컴파일할 수 있다. 일단 코드가 컴파일되면 실행 아이콘을 선택하거나 메뉴에서 특정 항목을 선택하는 것만으로 프로그램을 실행할 수 있다. 프로그램을 컴파일하고 실행하는 방법에 대해서는 여러분이 사용 중인 컴파일러의 사용 설명서 참조하기 바란다. 소스 파일을 컴파일하게 되면 오브젝트 파일이 생성된다. 그래서 컴파일을 수행한 디렉토리의 파일 목록을 살펴보면, 소스 파일과 동일한 이름을 가지고 있지만 .C가 아니라 .OBJ라는 확장자를 가지는 파일을 볼 수 있을 것이다. .OBJ의 확장자를 가지는 파이은 오브젝트 파일이며 링커에 의해서 사용된다. UNIX 시스템의 경우 컴파일러는 .OBJ의 확장자가 아니라 .O의 확장자를 가지는 오브젝트 파일을 생성할 것이다. 


4.3 실행 파일을 생성하기 위한 링크 작업


프로그램을 실행하기 위해서는 다른 한 가지 단계가 더 필요하다. 

C 컴파일러에서는 오브젝트 코드를 가지고 있는 함수 라이브러리(function library)가 함께 제공된다. 오브젝트 코드는 컴파일된 상태의 코드를 말한다. 내장 함수는 컴파일러를 제작한 회사에서 따로 작성한 C 코드를 통해서 생성된 것이며, 컴파일러를 구입할 때 사용할 수 있는 상태로 함께 제공되는 것이다. 앞에서 예제를 설명할 때 사용된 printf() 함수는 라이브러리 함수의 하나이다. 이런 라이브러리 함수는 화면에 자료를 출력하거나 디스크 파일에서 데이터를 읽어들이는 것과 같이 프로그램에서 흔히 요구되는 동작을 수행하기 위해서 사용된다. 프로그래머가 작성한 프로그램에서 이런 내장 라이브러리 함수를 전혀 사용하지 않는 경우는 거의 없으며, 이런 함수를 사용하는 프로그램을 작성했다면 소스 코드를 컴파일하여 생성되는 오브젝트 파일은 마지막 단계인 실행 가능한 프로그램을 생성하기 위해서 함수 라이브러리 내의 오브젝트 코드와 결합되어야 한다. '실행이 가능하다(executable)'는 것은 프로그램을 컴퓨터에서 실행하거나 사용할 수 있다는 것을 뜻한다. 이것을 링크(linking) 과정이라고 하며, 짐작할 수 있듯이 링커(linker) 라는 프로그램에 의해서 수행된다.


4.4 프로그램 개발 과정의 완료

일단 프로그램이 컴파일되고 실행 가능한 파일을 생성하기 위해 링크 과정을 거치게 되면, 사용자는 시스템 프롬프트에서 파일 이름을 입력하거나 또는 다른 어떤 프로그램을 사용하는 것과 같은 방법으로 실행할 수 있다. 만약 프로그램을 실행할 때 예상했던 것과 다른 결과가 나타난다면 다시 첫 번째 단계로 돌아갈 수 있을 것이다. 프로그래머는 문제가 발생한 부분을 확인하고, 소스 코드에서 문제를 해결해야 한다. 소스 코드의 내용이 변경될 때에는 실행 가능한 파일의 내용도 정정하기 위해서 프로그램을 다시 컴파일하고 링크할 필요가 있다. 이런 과정은 예상했던 결과가 나타날 때까지 반복되어야 한다. 마지막으로, 컴파일과 링크 과정에 대해 알아둘 사항이 있다. 여기에서는 컴파일과 링크 과정을 두 개의 독립된 단계로 설명했지만 이런 두 가지 과정을 한번에 수행한다. 그러나 컴파일과 링크 과정이 비록 하나의 명령을 통해서 동시에 수행되더라도 수행 방법에 관계없이 이런 과정은 두 개의 독립된 과정이라는 것을 기억하기 바란다


5. 간단한 C 예제 프로그램

이제 여러분은 C 언어를 사용하여 프로그램을 작성해 보기 원할 것이다. 여기에서는 컴파일러의 사용법을 익힐 수 있도록 도와주기 위해서 간단한 프로그램의 개발 과정을 설명할 것이다. 여기서 설명하는 모든 기술적인 내용을 이해할 수는 없겠지만, 예제를 통해서 C 프로그램을 작성하고 컴파일하여 실행하는 과정에 대해서 충분히 이해할 수 있을 것이다. 예제에서는 문장 'Nice guy!'를 화면 상에 출력하는 NICE.C라는 간단한 프로그램을 사용한다. 리스트 1.1 에는 NICE.C의 소스 코드가 나타나 있다. 리스트를 입력할 때 문장 번호나 콜론을 입력할 필요는 없다. 

리스트 1.1> NICE.C

    #include <stdio.h>


    main()

    {

        printf("Nice guy!");

        return 0;

    }



우선, 설명서를 참조하여 컴파일러를 정확히 설치했는지 확인하기 바란다. UNIX, DOS 또는 어떤 운영체제를 사용하든지, 여러분의 사용 환경에서 컴파일러와 에디터를 사용하는 정확한 방법을 알아두기 바란다. 컴파일러와 에디터를 사용할 준비가 되었다면, NICE.C를 입력하고 컴파일하여 실행하기 위해서 다음과 같은 단계를 따르도록 한다 .


5.1 NICE.C의 입력과 컴파일

NICE.C 프로그램을 입력하고 컴파일하기 위해 다음과 같이 한다. 

a. C프로그램이 저장되어 있는 디렉토리로 이동하거나 또는 C 프로그램의 경로를 검색 경로에 추가하고 나서 에디터를 실행한다. 앞에서도 설명했듯이, 원한다면 어떤 텍스트 에디터든지 사용할 수 있지만, 볼랜드의 Turbo C++나 마이크로소프트의 Visual C/C++와 같은 최신의 C 컴파일러에서는 하나의 환경에서 프로그램을 입력하고, 컴파일하고, 링크할 수 있도록 해주는 편리한 통합 개발환경(IDE : Integrated Development Environment) 을 제공해준다. 여러분의 컴파일러에서 IDE가 제공되는지 알아보기 위해서 설명서를 확인하기 바란다. 

b. 키보드를 사용하여 앞의 <리스트 1.1>에 나타난 것과 동일하게 NICE.C의 소스 코드를 입력한다. 각 문장의 마지막 부분에서는 Enter를 누른다. 

c. 소스 코드를 저장한다. 파일의 이름은 NICE.C라고 지정한다. 

d. 디렉토리나 폴더의 파일을 나열하여 NICE.C가 디스크에 저장되어 있는지 확인한다. 

e. NICE.C를 컴파일하고 링크한다. 여러분의 컴파일러 설명서에 나타나 있는 명령을 사용하면 된다. 아무런 에러나 경고 사항이 없다고 알려주는 메시지가 출력되어야 한다. 

f. 컴파일러의 메시지를 확인한다. 만약 아무런 에러나 경고 메시지도 나타나지 않는다면 모든 것이 정상적이라고 할 수 있다. 만약 프로그램을 입력할 때 실수를 했다면, 컴파일러는 문제를 찾아서 화면 상에 적절한 에러 메시지를 출력할 것이다. 예를 들어, 단어 printf를 잘못하여 prntf로 입력했다면 다음과 비슷한 메시지가 출력될 것이다


Error : undefined symbols:_prntf in nice.c (nice.OBJ) 


g. 이런 에러 메시지나 다른 어떤 메시지가 출력된다면 2단계로 돌아가야 한다. 그리고 에디터를 실행하고 NICE.C를 읽어들여서 파일의 내용을 리스트 1.1과 주의 깊게 비교하여 필요한 내용을 정정하고 3단계부터 다시 진행한다

h. 이제 첫 번째 C 프로그램이 컴파일되고 실행할 수 있는 상태가 되었을 것이다. 만약 디렉토리에서 확장자에 관계없이 NICE라는 이름을 가지는 모든 파일의 목록을 확인한다면, 다음과 같은 내용을 볼 수 있을 것이다. 

- NICE.C - 에디터를 사용하여 입력한 소스 코드 파일

- NICE.OBJ 또는 NICE.O - NICE.C의 오브젝트 코드가 저장된 파일

- NICE.EXE - NICE.C를 컴파일하고 나서 링크하여 생성된 실행 가능한 프로그램


i. NICE.EXE를 실행하거나 사용하기 위해서 간단히 nice를 입력한다. 'Nice guy!'라는 문장이 화면에 출력될 것이다. 정상적으로 프로그램이 실행되었다면 축하할 일이다. 여러분은 첫 번째 C 프로그램을 입력하고 컴파일하여 실행해보았다. NICE.C는 특별히 유용한 어떤 동작을 수행하지 않는 간단한 프로그램이지만 프로그래밍을 시작하는 아주 좋은 예이다 실제로, 오늘날의 대부분의 전문적인 C 프로그래머는 이런 과정을 거쳐서 NICE.C를 컴파일하여 C를 배우기 시작했으므로, 여러분은 앞으로 뛰어난 프로그래머가 될 수 있다는 희망을 가질 필요가 있다.

▶ 컴파일에러

컴파일에러(compilation error)는 컴파일러가 컴파일할 수 없는 어떤 내용을 소스 코드 내에서 발견할 때 발생하는 에러이다. 컴파일에러는 소스를 잘못 입력하거나 구두점을 빠뜨리고, 또는 다른 어떤 실수로 인해서 발생한다. 다행히, 현재 사용되는 컴파일러는 잘못된 부분에서 작업을 포기하지 않고 에러의 내용과 위치를 알려준다. 이런 기능은 소스 코드의 에러를 찾아서 정정하기 쉽게 도와준다. NICE.C에 잘못된 내용을 고의로 포함시켜 컴파일에러를 살펴보도록 하겠다. 앞에서 NICE.C를 입력하여 실행해 보았으므로 디스크에는 NICE.C라는 파일이 저장되어 있을 것이다. 에디터를 실행하고 printf() 함수가 사용된 곳으로 커서를 이동시키고 문장의 마지막에서 세미콜론(;)을 제거한다. 이제 NICE.C는 <리스트 1.2>와 같은 내용이 될 것이다. 

리스트 1.2 에러가 포함된 NICE.C

     1: #include <stdio.h>

     2:

     3: main()

     4: {

     5:    printf("Nice guy!")

     6:    return 0;

     7: }


다시 파일을 저장한다. 이제 파일을 컴파일할 수 있을 것이다. 컴파일러 명령을 입력하여 파일을 컴파일하자. 여기에서는 고의로 에러를 만들었기 때문에 컴파일은 정상적으로 수행되지 않을 것이다. 대신에 화면에는 다음과 같은 메시지가 출력될 것이다.

nice.c(6) : Error: ';' expected 

이것은 다음과 같이 세 부분으로 나누어 볼 수 있다.

>nice.c              - 에러가 발생한 파일의 이름

(6)                - 에러가 발생한 문장 번호

Error: ';' expected - 에러의 내용


앞에서 발생한 에러는 NICE.C의 6번째 줄에서 세미콜론이 발견되어야 하지만 컴파일러가 세미콜론을 발견할 수 없었다는 것을 상세하게 알려주고 있다. 그러나 실제로는 소스 코드의 5번째 줄에서 세미콜론이 생략되었으므로 발견된 에러와 사실에는 차이가 있다는 것을 알 수 있다. 여기에서, 컴파일러가 5번째 줄에서 생략된 세미콜론을 6번째 줄에서 생략된 것으로 받아들이는 이유는 무엇일까? 해답은 c 언어가 각 줄을 정확히 '구분하지 못한다.'는 데 있다. 세미콜론을 함수와 분리하여 그 다음 줄에 입력하는 것은 좋지 못한 습관이지만, printf() 함수의 다음 줄에 세미콜론이 사용될 수도 있을 것이다. 컴파일러는 6번째 줄에서 다음 명령인 return을 발견하고 나서야 앞에서 세미콜론이 생략되었다는 것을 알게 된다. 그래서 컴파일러는 에러가 6번째 줄에서 발생했다고 알려주는 것이다. 여기서 C 컴파일러와 에러 메시지에 대한 중요한 사실을 알 수 있다. 컴파일러는 에러를 발견하고 찾아내는 일에 대해서 상당히 영리하게 동작하지만 그 결과가 항상 정확하지는 않다. 프로그래머는 가끔 컴파일러가 출력하는 에러 메시지를 읽고 에러의 정확한 위치를 찾기 위해서 C에 대해서 알고 있는 지식을 응용해야 할 것이다. 실제로, 컴파일러가 발견하는 에러의 위치가 정확하지 않다면 대부분의 경우에는 바로 앞 문자에서 발생한 것이다. 처음에는 이런 문제로 인해 에러를 발견하는 것이 어려울 수도 있겠지만 곧 에러의 위치를 쉽게 찾을 수 있게 될 것이다.

'C 언어' 카테고리의 다른 글

2장 C 프로그램의 구성 요소  (0) 2019.06.02
3장 데이터 저장하기 : 변수와 상수  (0) 2019.06.02
4장 문장, 수식, 연산자  (0) 2019.06.02
5장 함수의 기본  (0) 2019.06.02
6장 기본적인 프로그램 제어문  (0) 2019.06.02

main() 함수

  : 모든 C 프로그램에서 반드시 필요한 한 가지 구성 요소는 main() 함수이다. 가장 간단한 형식으로 사용될 경우 main() 함수는 main이라는 함수의 이름과 한 쌍의 괄호인 {, } 만으로 구성된다. 괄호 내에는 프로그램의 몸체를 구성하는 문장들이 포함된다. 일반적인 경우에, 프로그램은 main() 내의 첫번째 문장에서 시작하여 마지막 문장까지 실행된 후에 종료된다. 


#include 지시어

  : #include 지시어는 컴파일 과정에서 프로그램에 필요한 include 파일의 내용을 추가하도록 C 컴파일러에게 지시한다. Include 파일은 컴파일러가 실행될 때 필요한 자료를 저장하고 있는 디스크 상의 독립 된 파일이다. 이것은 헤더 파일(Header file)이라고도 하며, 컴파일러 와 함께 여러 개의 파일이 제공된다. Include 파일의 내용은 수정할 필요가 없기 때문에 소스코드와 별도로 관리된다. 또한 include 파일은 .H의 확장자를 사용해야 한다. 예를 들어, stdio.h와 같은 것이 있다. 



변수 정의문

  : 변수(variable)는 데이터가 저장되는 영역에 주어지는 이름이다. 프로그램이 실행되는 동안, 여러 가지 종류의 데이터를 저장하기 위해서 변수를 사용하게 된다. C에서는 변수를 사용하기 전에 정의해야 한다. 변수 정의문(variable definition)은 변수의 이름과 데이터 형을 컴파일러에게 알려주기 위해서 사용된다. 예를 들어, int a, b, c;등이 있다. 



함수 원형

  : 함수 원형(function prototype)은 프로그램내에 포함되어 있는 함수의 이름과 인수를 C 컴파일러에게 알려주는 것으로, 함수가 사용되기 전에 선언되어야 한다.

프로그램 문장

  : C프로그램은 실제로 문장(statements)에 의해 필요한 동작을 수행 한다. C의 프로그램 문장은 화면으로 결과를 출력하고, 키보드에서 입력을 받아들이고, 연산 동작을 수행하고, 함수를 호출하고, 디스크 파일을 읽어들이고, 그밖게 프로그램에서 요구되는 다른 여러 가지 동작을 수행한다. 



printf()

  : printf()문은 화면 상에 메시지나 결과를 출력하는 라이브러리 함수다. 



scanf()

  : scanf()문은 또 하나의 라이브러리 함수이다. 이 함수는 키보드에서 데이터를 읽어들이고, 하나 이상의 프로그램 변수에 저장한다. 



return

  : return문은 결과값을 돌려준다.

함수 정의 : 함수(function)는 특별한 동작을 수행하기 위해서 사용되는 독립적인 프로그램 문장의 집합으로, 내부에 독립된 프로그램 문장을 포함하고 있다. 모든 함수는 독특한 이름을 가지고 있으며, 각 함수 내에 포함 되어 있는 코드는 프로그램에서 해당 함수의 이름을 사용함으로써 실행할 수 있다. 이런 동작을 ‘함수 호출(calling)’이라고 한다. 



프로그램의 주석문

  : 프로그램 내에서 /*로 시작하고 */로 끝나는 문장을 주석문(comment)이라고 한다. 컴파일러는 주석문의 내용을 완전히 무시하므로 주석문은 프로그램의 동작 상태에 아무런 영향을 주지 않는다. 그러나 여러 개의 주석 문을 종속된 상태로 사용해서는 안된다. 즉, 하나의 주석 문을 다른 주석문 내에 포함시키면 안된다. 



괄호

  : main() 함수를 포함하여 C의 함수를 구성하는 모든 프로그램 문장을 구분하기 위해서는 괄호( {, } )를 사용한다. 괄호 내에 집단적으로 포함된 한 줄 이상의 문장을 블록(block)이라고 한다. 



참고 

-프로그램의 소스 코드에 많은 주석 문을 포함시키자. 특히, 나중에 소스 코드를 변경할 필요가 있는 사람이 함수나 프로그램 문장의 내용을 잘 이해할 수 없을 것이라고 생각되는 부분을 중심으로 해서 자세한 주석 문을 입력하자. 내용을 분명하게 알 수 있는 부분에 불필요한 주석 문을 추가하지 말자. 예를 들어, 다음과 같은 주석문은

/*다음 문장은 화면에 ‘Hello World!’를 출력한다. */ printf(“Hello World!); 

프로그래머가 printf() 함수의 사용에 익숙해지게 되면 불필요한 내용이 된다. 


-주석 문을 적절히 사용하기 위한 연습을 하자. 주석문이 없거나 또는 애매하게 포함된 프로그램은 이해하기 힘들고, 너무 많은 주석 문을 포함시키는 것도 프로그래밍보다 주석문의 추가에 더 많은 시간을 소모하게 한다.

'C 언어' 카테고리의 다른 글

1장 C 프로그래밍의 기초  (0) 2019.06.02
3장 데이터 저장하기 : 변수와 상수  (0) 2019.06.02
4장 문장, 수식, 연산자  (0) 2019.06.02
5장 함수의 기본  (0) 2019.06.02
6장 기본적인 프로그램 제어문  (0) 2019.06.02

데이터 저장하기 : 변수와 상수

 : 컴퓨터 프로그램은 일반적으로 여러 가지 종류의 데이터를 다루는데, 동시에 데이터를 저장하기 위한 수단을 필요로 한다. 이런 데이터나 값은 숫자나 문자가 될 수 있을 것이다. C에서는 숫자값을 저장하기 위한 두 가지 방법으로 변수와 상수를 제공하고, 변수와 상수는 다시 여러 가지 종류로 세분된다. 변수는 프로그램이 실행되는 동안에도 내용을 변경할 수 있는 데이터 저장 영역인 반면에, 상수는 변경할 수 없는 고정된 값을 가지게 된다. 여기서 배울 내용은

  

   ▶ C에서 변수의 이름을 정의하는 방법

   ▶ 여러 가지 종류의 숫자 변수

   ▶ 문자 변수와 숫자 변수의 차이점과 유사점

   ▶ 숫자 변수의 선언과 초기화

   ▶ C에서 사용되는 두 가지 형태의 숫자 상수


이 내용을 이해하기 위해선 컴퓨터의 메모리가 동작하는 방법에 대해서 알아둘 필요가 있다.



1. 컴퓨터의 메모리

 : 컴퓨터가 실행되는 동안 사용되는 자료나 데이터는 읽고 쓰기가 가능한 RAM(Random Access Memory)에 저장된다. RAM은 컴퓨터 내에서 집적 회로나 칩(chips)의 형태로 존재한다. RAM은 휘발성(volatile)이 있으므로, 필요할 때마다 저장된 내용을 지우고 새로운 자료로 대치할 수 있다. 또한 RAM은 컴퓨터가 사용되는 동안에만 자료를 ‘기억하며’ 컴퓨터의 전원이 차단되면 모든 내용을 잃게 된다. 각각의 컴퓨터에는 서로 다른 일정량의 RAM이 설치되어 있다. 시스템에 설치되는 RAM은 대개의 경우 521KB, 640KB, 2MB, 4MB, 8MB와 같이 킬로 바이트(KB)나 메가 바이트(MB) 단위로 사용된다. 1킬로 바이트의 메모리는 1,024바이트로 구성된다. 그래서 640KB의 메모리를 가지고 있는 시스템은 실제로 640*1,024인 655,360바이트의 RAM을 가지는 것이다. 1메가 바이트는 1,024킬로 바이트이다. 4MB의 메모리를 가지는 시스템은 4,096, 즉 4,194,304바이트의 RAM을 가지는 셈이다. 바이트(byte)는 컴퓨터의 기억 장소에 대한 기본적인 단위이다. 나중에 ‘메모리 다루기’에서 상세히 설명하고, 여기에서는 특정 형태의 데이터를 저장하는 데 필요한 바이트의 양을 알아보겠다.


    ■ 데이터를 저장하는 데 필요한 메모리의 양

   데이터의 형태

필요한 바이트 수

   문자 x

 1

   숫자 500

 2

   숫자 241.105

 4

   구절 Yeach Yourself C

 17

   한 페이지의 내용

 대략적으로 3000



컴퓨터의 내의 RAM은 순서대로 다루어진다. 즉, 하나의 바이트는 다른 것 다음에 위치되고, 그 다음에는 또 다른 바이트가 위치되는 것이다. 메모리의 각 바이트는 독특한 주소(address)를 가지고 있으며, 주소는 메모리 내에서 특정 바이트를 다른 것과 구분하는 데 사용된다. 메모리의 주소는 0부터 시적하여 시스템에 설치된 메모리의 마지막까지 순서대로 주어진다. 프로그래머는 메모리에 주소를 할당하는 것에 대해서 염려할 필요가 없다. 주소를 할당하는 일은 모두 C컴파일러에 의해서 자동으로 처리된다. 컴퓨터의 RAM은 어디에 사용되는 것일까? RAM은 여러 가지 용도를 가질 수 있지만, 여기에서는 데이터를 저장하는 데 사용된다는 것만을 알아둘 필요가 있다. 데이터(data)는 C프로그램이 사용되는 특정 형태의 자료를 말한다. 사용자가 주소록을 관리하거나, 가계부를 정리하거나 또는 정육점에서 육류의 시세를 관리하는 등 어떤 목적을 가지고 있든지, 프로그램에서 사용되는 사람의 이름, 전화번호, 수입, 지출, 잔액, 육류의 가격 등과 같은 자료는 프로그램이 실행되는 동안 컴퓨터 내의 RAM에 저장된다. 지금까지 메모리에 대해 간단히 알아봤다. 이제는 C프로그래밍에 대한 내용으로 돌아가서, C가 자료를 저장하기 위해서 메모리를 사용하는 방법에 대해 알아보도록 하겠다


2. 변 수

 : 변수(varibles)는 컴퓨터의 메모리 내에서 독특한 이름을 가지고 있는 데이터 저장영역을 말한다. 변수의 이름을 프로그램 내에서 사용하면, 실제로 해당 메모리 영역에 저장된 데이터를 사용할 수 있게 된다.


2.1 변수의 이름

 : C프로그램 내에서 변수를 사용하려면, 우선 변수의 이름을 명명하는 방법을 알 필요가 있다. C에서 사용되는 변수의 이름은 다음과 같은 규칙이 따른다.

      - 변수의 이름은 문자, 숫자, 밑줄을 포함할 수 있다.

      - 변수의 이름에서 첫 번째 문자는 영문자(letter)가 되어야 한다. 또한, 밑줄을 

      - 첫 번째 문자로 사용할 수 있지만 가능하다면 사용하지 않는 것이 좋다.

      - 대문자나 소문자의 구분에는 중요한 차이가 있다. 그래서 count와 Count라는 두 개의 이름은 서로 다른 변수이다.

      - C의 키워드를 변수의 이름으로 사용할 수 없다. 키워드(keyword)는 C 언어의 일부분으로 사용되는 독특한 명령어를 말한다.


다음은 사용할 수 있거나 사용할 수 없는 변수 이름의 예이다.

변수 이름

                              허용 여부

Percent

허용됨.

y2x5_fg7h

허용됨.

annual_profit

허용됨.

_1990_tax

허용되지만 사용하지 않는 것이 좋다.

savings#account

허용 안됨 - 사용할 수 없는 문자(#)가 포함되어 있다.

double

허용 안됨 - C의 키워드이다.

9winter

허용 안됨 - 첫 번째 문자가 숫자이다.


C는 대문자와 소문자를 구별하므로, 세 개의 변수 이름 percent, PERCENT, Percent는 서로 다른 세 개의 변수를 뜻하는 것으로 간주된다. 반드시 필요하지는 않지만, C프로그래머들은 대개의 경우 소문자만을 사용하여 변수를 정의한다. C의 변수 이름은 대부분의 컴파일러에서 31까지의 길이를 가진다. 변수의 이름이 더욱 길어질 수 있지만, 컴파일러는 사용된 이름에서 처음 31자만을 받아들인다. 이런 특징을 이용하여 변수에 저장되는 데이터의 내용을 알 수 있도록 변수의 이름을 지정할 수 있다.
2.2 숫자 변수의 종류
 : C는 여러 가지 형태의 숫자 변수를 제공해준다. 서로 다른 숫자 변수는 서로 다른 양의 메모리 영역을 필요로 하고 특정 연산 동작을 수행하는 경우에도 변수에 따라 결과가 달라지므로 다양한 형태의 변수가 필요하다. 예를 들어, 1, 199, -8과 같은 작은 정수값을 저장하기 위해서는 적은 양의 메모리가 필요하고, 덧셈이나 곱셈과 같은 연산 동작도 더욱 빠르게 수행된다. 프로그래머는 프로그램을 작성할 때 적절한 변수의 형태를 선택하여 프로그램이 효과적 으로 실행되게 할 수 있다. C의 숫자 변수는 크게 다음과 같은 두 종류로 구분할 수 있다.

 - 정수형 변수(Integer variables)는 소수 부분이 전혀 없는 값을 저장한다. 즉, 음수와 양수만으로 구성되는 값을 가진다. 정수형 변수는 다시 두 가지로 나누어진다. 부호 있는 정수형 변수는 양수나 음수 값을 모두 저장할 수 있지만 부호 없는 변수에는 0과 양수값만을 저장할 수 있다.

 - 부동 소수형 변수(Floating-point variables)는 소수 부분을 가지는 숫자값을 저장하는 변수이다. 즉, 실수 값이 저장된다.

이러한 각각의 형태에는 여러 가지 특정 변수의 형태가 포함된다. 상세한 구분 내용은 <표 3.2>에 요약되어있다. 또한, 여기서 설명되는 내용은 16비트 데이터 구조를 가지고 있는 컴퓨터를 기본으로 하는 것이다. 표에 나타나 있는 메모리의 양은 바이트 단위이다.

<표 3.2> C의 숫자 변수 형태

변수의 이름

  키워드

 메모리의 양

 값의 범위

문자형

 char

  1

 -128 ~ 127

정수형

 int

  2

 -32,768 ~ 32,767

short형 정수

 short

  2

 -32,768 ~ 32,767

long형 정수

 long

  4

 -2,147,483,648 ~ 2,147,483,647

부호 없는 문자형

 unsigned char

  1

 0 ~ 255

부호 없는 정수형

 unsigned int

  2

 0 ~ 65,535

부호 없는 short형 정수

 unsigned short

  2

 0 ~ 65,535

부호 없는 long형 정수

 unsigned long

  4

 0 ~ 4,294,967,295

단정도 부동 소수형

 float

  4

 1.2E-38 ~ 3.4E38 ①

배정도 부동 소수형

 double

  8

 2.2E-308 ~ 1.8E308 ②

① 대략적인 범위이며 정밀도는 7자리까지이다.

② 대략적인 범위이며 정밀도는 19자리까지이다.


<표 3.2>에 나타나 있는 대략적인 범위(approximate range)는 주어진 변수가 가질 수 있는 최대값과 최소값을 뜻한다. 이런 변수의 정확한 범위는 여기서 다루기에 복잡하므로 생략하겠다. 정밀도(precision)는 변수의 정확성을 말하는 것이다. 예를 들어, 1/3을 계산할 경우의 결과는 3이 무한 대로 계속되는 0.3333....이된다. 7의 정밀도를 가지는 변수에서 이 결과는 7자리까지의 값(0.3333333)을 저장하는 것이다.

<표 3.2>를 보면 int형과 short형이 동일하다는 것을 알 수 있을 것이다. 그렇다면 두 가지 다른 형태가 존재하는 이유는 무엇일까? 16비트의 IBM PC호환 기종에서는 int와 short가 동일한 것으로 처리되지만, 다른 하드웨어 기종에서는 달라질 수 있다. 예를 들어, VAX 시스템에서는 short와 int가 동일하지 않은 것으로 처리된다. short는 2바이트이지만 int는 4바이트를 사용하게 된다. C는 융통성과 이식성이 뛰어난 언어이므로, 두 가지 형태에 대해 다른 키워드를 제공한다는 것을 기억할 필요가 있다. 만약 PC 호환 기종에서 C언어를 사용한다면, int와 short를 상호 교환하며 사용할 수 있을 것이다. 정수형 변수를 부호 있는 형태로 지정하기 위해서는 아무런 키워드도 필요하지 않다. 정수형 변수는 이미 부호가 있는 형태로 설정되어 있다. 그러나 필요하다면 분명하게 지정하기 위해서 signed 키워드를 포함시킬 수 있다. <표 3.2>에 나타나 있는 키워드는 잠시 후에 변수 선언을 설명할 때 사용할 것이다. <리스트 3.1>의 프로그램은 특정 컴퓨터 상에서 변수의 크기를 확인할 수 있도록 도와줄 것이다. 프로그램의 결과가 리스트 다음에 있는 결과와 다르더라도 놀랄 필요는 없다.


<리스트 3.1> 여러 가지 형태의 변수 크기를 출력하는 프로그램

       #include <stdio.h>
       

       main()
       {
         printf(“\nA char            is %d bytes”, sizeof(char));
         printf(“\nA int              is %d bytes”, sizeof(int));
         printf(“\nA short           is %d bytes”, sizeof(short));

         printf(“\nA long            is %d bytes”, sizeof(long));
         printf(“\nAn unsigned char  is %d bytes”, sizeof(unsigned char));
         printf(“\nAn unsigned int    is %d bytes”, sizeof(unsigned int));
         printf(“\nAn unsigned short is %d bytes”, sizeof(unsigned short));
         printf(“\nAn unsigned long  is %d bytes”, sizeof(unsigned long));
         printf(“\nAn float           is %d bytes”, sizeof(float));
         printf(“\nAn double         is %d bytes”, sizeof(double));
         return 0;
       }


           -> 출력
           A char            is 1 bytes
           A int              is 2 bytes
           A short           is 2 bytes
           A long            is 4 bytes
           An unsigned char  is 1 bytes
           An unsigned int    is 2 bytes
           An unsigned short is 2 bytes
           An unsigned long  is 4 bytes
           A float           is 4 bytes
           A double          is 8 bytes
  

2.3 변수의 선언
 : C 프로그램에서 변수를 사용하기 위해서는 변수를 선언해야 한다. 변수의 선언 (variable declaration)은 컴파일러에게 변수의 이름과 형태를 알려주며, 선택적으로 변수에 특정 값을 저장하여 초기화하는 것도 가능하다. 만약 프로그램에서 이전에 선언되지 않은 변수를 사용하려고 한다면, 컴파일러는 에러 메시지를 출력할 것이다. 변수의 선언은 다음과 같은 형식으로 수행된다.
             typename      varname;

typename은 변수의 형태로, <표 3.2>에 나타나 있는 키워드의 하나가 사용된다. 변수의 이름인 varname은 앞에서 설명했던 변수 이름의 규칙을 따르는 것이어야 한다. 쉼표로 각 변수의 이름을 구분하여 한 줄에 동일한 형태의 여러 변수를 선언할 수도 있다.

             int count, number, start;
             float percent, total;


2.4 typedef 키워드
 : typedef 키워드는 이미 존재하는 데이터 형에 새로운 이름을 지정하는데 사용된다. 즉, typedef는 특정변수형의 동의어를 정의한다. 예를 들어, 다음 문장은

             typedef int   integer;

int형에 대한 동의어로 integer라는 변수형을 정의한다. 이제 int형 변수를 정의하기 위해서 다음 예제와 같이 integer를 사용할 수 있을 것이다.

             Integer count;

typedef는 새로운 데이터형을 생성하는 것이 아니라 이미 존재하는 데이터형에 대한 다른 이름을 정의한다는 것에 주의하기 바란다.
  

2.5 숫자 변수의 초기화 
 : 변수를 선언하는 것은 컴파일러에게 변수를 위한 저장 영역을 보존하도록 지시하는 것이다. 그러나 보존된 공간에 저장되는 변수의 실제 값은 정의되지 않는다. 실제 값은 0이 될 수도 있고 다른 어떤 ‘임의의’ 값이 될 수도 있다. 그래서 변수를 사용하기 전에는 항상 원하는 값으로 초기화해야 한다. 변수의 초기화는 다음과 같이 할당문을 사용하여 변수 선언문과 별도로 수행할 수 있다.

             Int count;
             Count = 0;    ‘ count를 0으로 초기화

또한, 변수를 선언할 때 직접 초기화할 수도 있다. 이렇게 하려면 변수 선언문에서 변수의 이름을 입력한 다음에 등호(=)와 함께 원하는 초기값을 지정하면 된다.

             Int count = 0;
             Double percent = 0.01, taxrate = 28.5;

      변수를 초기화 할 때에는 허용되는 범위를 벗어난 값을 사용하지 않도록 주의하기 바란다. 다음은 범위를 벗어난 값이 사용되는 초기화의 예이다.

             Int weight = 100000;
             Unsigned int value = -2500;

      C 컴파일러는 이런 에러를 찾아주지 않는다. 프로그램은 이상 없이 컴파일되고 링크 되지만, 실제로 실행하면 예상하지 못한 결과를 얻는다.
 
3. 상 수
 : 상수(constant)는 변수와 마찬가지로 프로그램에서 사용되는 데이터를 저장하는 메모리 영역이다. 그러나 변수와는 달리, 상수로 지정되는 값은 프로그램이 실행되는 동안 바뀌지 않는다. C는 각각 독특한 용도를 가지고 있는 두 가지 형태의 상수를  제공한다.
 

3.1 실제 상수
 : 실제 상수(literal constant)는 필요할 때마다 소스 코드에 직접 포함되는 실제 값이다. 다음은 실제 상수의 사용 예이다.

            Int count = 20;
            Float tax_rate = 0.28;

      20과 0.28은 실제 상수이다. 예제에서는 이런 갓을 변수 count와 tax_rate에 저장하고 있다. 두 변째 예제에서는 소수점이 사용되는 반면에, 처음 예제에서는 소수점이 사용되지 않고 있다. 소수점은 부동소수형 상수와 정수형 상수를 구분하는 기준이 된다. 소수점이 포함되어 있는 상수는 부동 소수형 상수(floating-point constant)이며, C 컴파일러에서 배정도형 숫자로 취급된다. 부동 소수형 상수는 다음의 예제에서처럼 일반적인 십 진 표기법으로 사용될 수 있다.

            123.456
            0.019
            100.

      세 번째 예제의 상수 100.은 소수 부분을 가지고 있지 않은 정수이지만 소수점을 포함하고 있다. 소수점은 C컴파일러가 상수를 배정도형으로 취급하게 해준다. 소수점이 없는 경우에는 일반적으로 정수형 상수로 취급한다. 또한, 부동 소수형 상수는 공학 표기법(scientific notation)으로 사용될 수 있다. 공학 표기법에서는 숫자를 10의 제곱 형태로 표시한다. 공학 표기법은 아주 크거나 작은 값을 나타내는 데 유용하다. C에서는 공학 표기법을 십진수, 문자 E나e, 제곱수의 형태로 사용한다. 다음은 몇 가지 예이다.

       - 1.23E2  : 1.23의 기본 값에 10을 2번 곱한 123
       - 4.08e6  : 4.08의 기본 값에 10을 6번 곱한 4,080,000
       - 0.85e-4 : 0.85의 기본 값에 1/10을 4번 곱한 0.000085
         
소수점이 없는 상수는 컴파일러에서 정수형 숫자로 처리된다. 정수형 상수는 다음과 같이 여러 가지 방법으로 표현할 수 있다.

- 0이 아닌 다른 어떤 숫자로 시작하는 상수는 십 진(decimal) 정수로 처리된다. 십 진 정수는 10을 기본으로 하는 십진수 체계를 말한다. 십 진 상수는 숫자 0에서부터 9까지를 사용하며, 숫자 앞에 +나 ?와 같은 부호를 가질 수 있다. 부호가 없을 때 상수는 양수 값으로 간주된다.

- 숫자 0으로 시작하는 상수는 8진(octal) 정수로 처리된다. 8진 정수는 8을 기본으로 하는 8진수 체계를 말한다. 8진 상수는 숫자 0에서부터 7까지를 사용하며, 숫자 앞에 +나 ?와 같은 부호를 가질 수 있다.

- 0x나 0X로 시작하는 상수는 16진(hexadecimal) 정수로 처리된다. 16진 정수는 16을 기본으로 하는 16진수 체계를 말한다. 16진수 상수는 숫자 0에서부터 9까지와 문자 A~F까지를 사용하며, 숫자 앞에 +나 ?와 같은 부호를 가질 수 있다.

3.2 기호 상수
 : 기호 상수(symbolic constant)는 프로그램 내에서 독특한 이름이나 기호로 표현되는 상수를 말한다. 실제 상수와 마찬가지로 기호 상수의 내용은 변경될 수 없다. 프로그램 내에서 상수의 값을 사용할 필요가 있을 때마다 기호 상수의 이름을 변수의 이름처럼 사용할 수 있다. 기호 상수의 실제 값은 상수를 처음 정의할 때에만 입력해주면 된다. 다음의 예제에서도 알 수 있듯이, 기호 상수는 실제 상수에 비해서 두 가지 중요한 장점을 제공한다. 여러 가지 기하학적인 계산을 수행하는 프로그램을 작성한다고 가정하자. 프로그램은 연산을 수행하기 위해서 π(3.14159)를 자주 사용하게 된다. 참고로, π는 원의 지름에 대한 원주의 비율이다. 예를 들어, radius라는 반지름을 가지는 원의 원주와 면적을 계산하기 위해서는 다음과 같은 프로그램을 작성할 수 있을 것이다.

             Circumference = 3.14159 * (2 * radius);
             Area = 3.14159 * (radius) * (radius)

여기서 사용된 *는 C에서 곱셈 연산자로 사용된다. 첫 번째 문장은 ‘변수 radius에 저장된 값에 2를 곱하고 나서 그 결과에 3.14159를 곱한다. 그리고 계산된 최종 결과를 circumference라는 이름의 변수에 저장한다. 그러나, PI라는 이름을 가지고 3.14라는 값을 저장하는 기호 상수를 정의하면 앞의 예제를 다음과 같이 작성할 수 있다.

             Circumference = PI * (2 * radius); 
             Area = PI * (radius) * (radius);

      이것은 더욱 이해하기가 쉽다. 3.14라는 값이 왜 사용되었는지 의문을 가졌던 사람도 상수 PI를 보고 프로그램의 내용을 더욱 명확히 알 수 있을 것이다. 기호 상수의 다른 한 가지 장점은 상수의 값을 변경할 필요가 있을 때 더욱 중요한 것이다. 앞에서 사용된 예제를 다시 살펴보자. 계산 결과를 더욱 정확하게 하게 하기 위해서 3.14라는 값 대신에 더욱 정확하게 3.14159의 값을 사용하기 원한다고 하자. 만약 π의 값을 실제 상수의 형태로 입력했다면, 프로그래머는 전체 프로그램에서 3.14를 3.14159로 변경해야 할 것이다. 그러나 기호 상수를 사용했다면 단지 상수가 정의된 부분에서만 값을 변경해주면 된다. C는 기호 상수를 정의하기 위한 두 가지 방법으로 #define 지시어와 const 키워드를 제공한다.
#define 지시어는 다음과 같이 사용된다.

             #define CONSTNAME literal

이 문장은 literal이라는 값을 가지는 CONSTNAME라는 이름의 기호 상수를 정의한다. literal은 앞에서 설명했듯이 실제 상수를 나타낸다. CONSTNAME은 변수의 이름에 대해서 설명했던 것과 같은 규칙을 따른다. 일반적으로, 기호 상수의 이름에는 대문자가 사용된다. 이런 특징은 소문자를 주로 사용하는 변수의 이름과 분명하게 구분할 수 있도록 해준다. 앞에서 사용된 예제에서 필요한 #define 지시어는 다음과 같은 내용이 될 것이다.

             #define PI 3.14159

#define문은 세미콜론(;)으로 끝나지 않는다는 것을 기억하기 바람. #define은 소스 코드 내의 어느 위치에서든 사용할 수 있지만, 실제로는 #define 지시어 다음에 있는 코드에만 영향을 준다. 대부분의 프로그래머는 모든 #define문을 파일의 시작 부분과 main() 함수의 시작 사이에 위치시킨다.

▶ #define의 적용 범위
 : #define 지시어가 실제로 수행하는 것은 컴퓨터에게 ‘소스 코드 내의 CONSTNAME을 literal로 대치하여 사용하라’는 것을 지시하는 것이다. 결과는 에디터에서 소스 코드를 입력하고 수동으로 직접 변경하는 것과 동일하다. #define은 큰 따옴표 내의 문장에서 일부분으로 나타나는 것이나 또는 프로그램 주석문의 내부에서 사용되는 내용을 대치하지 않는다는 것을 기억하기 바란다. 예를 들어, 다음 코드에서 두 번째 줄과 세 번째 줄의 PI는 바뀌지 않을 것이다.

             #define PI 3.14159
             #define PIPETTE 100

▶ const 키워드로 상수 정의하기
 : 기호 상수를 정의하는 다른 한 가지 방법은 const 키워드를 사용하는 것이다. const는 어떤 변수 선언에도 사용할 수 있는 키워드이다. const를 사용하여 선언한 변수는 프로그램이 실행되는 동안 변경할 수 없고, 단지 변수를 초기화하는 것만 가능하다. 다음은 몇 가지 예이다.

             Const int   count = 100;
             Const long debt = 12000000, float tax_rate = 0.21;

const는 선언문에 포함되어 있는 모든 변수에 영향을 준다. 두 번째 중에 있는 debt와 tax_rate는 기호 상수이다. 만약 프로그램에서 const로 선언된 변수의 값을 변경하려고 한다면, 컴파일러는 에러 메시지를 나타낼 것다. 예를 들어, 다음과 같은 결과가 나타난다.

             Const int count = 100;
             Count = 200;   ‘ 이것은 컴파일되지 않는다. 
          상수형으로 정의된 변수의 값을 변경하지 않도록 주의해야 한다.

#define 지시어로 생성되는 기호 상수와 const키워드를 통해서 정의되는 기호 상수의 차이점은 무엇일까? 차이점은 포인터와 변수의 범위에 관련된 것이다. 포인터와 변수의 범위는 C 프로그래밍에서 중요한 두 가지 사항으로 "포인터에 대해서"와 "변수의 범위"에서 상세히 설명할 것이다.  이제 변수를 선언하고 실제 상수와 기호 상수를 사용하는 프로그램의 예제를 살펴보도록 하겠다. <리스트 3.2>에있는 프로그램은 사용자에게 파운드 단위의 몸무게와 생일을 입력하도록 요구할 것이다. 그리고 나서 몸무게를 그램(g)단위로 환산하고, 2002년에는 나이가 어떻게 되는지 계산하여 결과를 출력한다.

<리스트 3.2> 변수화 상수의 사용 예를 보여주는 프로그램

      #include <stdio.h>
      #define GRAMS_PER_POUND 454
      const int NEXT_CENTURY = 2002;
      long weight_in_grams, weight_in_pounds;
      int year_of_birth, age_int_2002;
      main()
      {
          printf(“Enter your weight in pounds: “);
          scanf(“%d”, &weight_in_pounds);
          printf(“Enter your year of birth: “);

          scanf(“%d”, &year_of_birth);

          weight_in_grams = weight_in_pounds * GRAMS_PER_POUND;
          age_in_2002 = NEXT_CENTURY - year_of_birth;

          printf(“\nYour weight in grams = %ld.”, weight_in_grams);
          printf(“\nIn 2002 you will be %d years old.\n”, age_in_2002);

          return 0;
      }


      -> 입력 / 출력

          Enter your weight in pounds: 175

          Enter your year of birth: 1975

 

          Your weight in grams = 79450

          In 2002 you will be 27 years old

'C 언어' 카테고리의 다른 글

1장 C 프로그래밍의 기초  (0) 2019.06.02
2장 C 프로그램의 구성 요소  (0) 2019.06.02
4장 문장, 수식, 연산자  (0) 2019.06.02
5장 함수의 기본  (0) 2019.06.02
6장 기본적인 프로그램 제어문  (0) 2019.06.02

문장, 수식, 연산자


 C 프로그램은 여러 가지 문장으로 구성되고, 대부분의 문장은 수식과 연산자로 이루어져 있다. C 프로그램을 작성하기 위해서는 이런 세 가지 내용(문장, 수식, 연산자)을 이해할 필요가 있다. 오늘은 다음과 같은 내용을 배울 것이다.


- 문장은 무엇인가?

- 수식은 무엇인가?

- C에서 사용되는 산술, 관계, 논리 연산자

- 연산자의 우선 순위

- if문


1. 문 장

 : 문장(statement)은 컴퓨터에게 어떤 동작을 수행하도록 지시하는 명령문이다. C의 문장은 가끔 여러 줄에 걸쳐 나타나기도 하지만, 일반적으로 한 줄에 하나씩 입력된다. C의 문장은 #define이나 #include와 같은 선행 처리기 지시어를 제외하고는 항상 세미콜론 으로 끝난다. 앞에서는 이미 C의 여러 가지 문장을 보았었다. 예를 들어, 다음은


 x = 2 + 3;


할당문(assignment statement)이다. 이것은 컴퓨터에게 2와 3을 더하여 그 결과를 변수 x에 저장하도록 지시한다. 다른 종류의 문장은 필요할 때마다 설명할 것이다.


1.1 문장과 공백

 : 공백(whitespace)이라는 용어는 소스 코드에 포함되어 있는 빈칸(space), 탭(tab), 빈줄(blank line)을 일컫는 말이다. C 컴파일러는 모든 공백을 무시한다. 컴파일러는 소스 코드의 내용을 읽어들일 때 문장에 포함된 문자와 종료를 뜻하는 세미콜론을 찾지만 모든 공백을 무시한다. 그래서 다음과 같은 문장은


 x=2+3;


다음과 같이 입력할 수 있다.


x = 2 + 3;


또한, 다음과 같이 사용할 수도 있다.


x      =

2

+

3;


이런 특징은 소스 코드를 정리하는 데 있어서 여러 가지 융통성을 제공해준다. 그러나 앞의 예제처럼 소스 코드를 입력해서는 안된다. 하나의 문장은 변수와 연산자 주변에 몇 개의 공백을 포함시킨 상태로 한 줄에 하나씩 입력하는 것이 일반적이다. C 프로그램에 더욱 익숙해지게 되면 자신만의 기호에 맞는 문장 입력 방식을 사용하게 되는데, 중요한 것은 소스 코드를 항상 이해하기 쉽게 만들어야 한다는 것이다. 그러나 C가 공백을 무시하지 않는 예외적인 경우가 있다. 문자열 상수 내에서 사용되는 탭과 빈칸은 무시되지 않고 문자열의 일부분으로 받아들여진다. 문자열(string)은 일련의 문자들을 말한다. 실제문자열 상수(literal string constants)는 따옴표에 포함되고 컴파일러가 공백을 그대로 받아들이는 '문자열'을 말한다. 다음 예제는 비록 좋지 못한 입력 방식이지만 잘못된 것은 아니다.


          printf(

          "Hello, world!"

          );


그러나 다음과 같은 내용은 잘못된 것이다.


          printf("Hello,

          world!");


실제 문자열 상수의 내용을 여러 줄로 구분하여 사용하기 위해서는 내용이 끊어지는 곳의 바로 앞에 백슬래시(\)를 포함시켜야 한다. 그래서 다음과 같은 문장은 사용할 수 있다.


          printf("Hello,\

          world!");


1.2 널 문장

 : 만약 한 줄에서 아무런 내용도 입력하지 않고 세미콜론을 사용하면, 아무런 동작도 수행하지 않는 문장을 뜻하는 널 문장(null statement)이 된다. 이런 문장은 C에서 허용된다. 나중에 널 문장이 유용하게 사용되는 경우를 설명할 것이다.


1.3 복합문

 : 블록(block)이라고도 하는 복합문(compound statement)은 괄호 내에 포함되어 있는 두 줄 이상의 C 문장들을 말한다. 다음은 블록의 예이다.


           {

               printf("Hello, ");

               printf("world!");

           }


C에서 블록은 일반적인 문장을 사용할 수 있는 곳이면 어디에서든지 사용할 수 있다. 괄호는 여러가지 방법으로 사용할 수 있다. 다음의 예는 앞에서와 같은 내용이다.


           {printf("Hello, ");

           printf("world!");}


블록의 시작과 끝을 분명하게 하기 위해서 괄호를 대응하는 열(column)에 위치시키는 것은 좋은 생각이다. 또한 괄호를 정렬하여 입력하게 되면 빠뜨리지 않고 입력하는데 도움이 된다.


2. 수 식

 : 수식(expression)은 C에서 숫자값을 계산하거나 어떤 결과로 평가되는 것을 말한다. C의 수식은 다양한 형태로 구성될 수 있다.


2.1 단순 수식

 : C의 가장 간단한 수식은 하나의 항목으로 구성된다. 이런 수식에는 간단한 변수, 실제 상수, 기호 상수등이 있다. 다음은 단순 수식의 예이다.

 수         식

            설          명

              PI

프로그램 내에서 정의되는 기호 상수

              20

실제 상수

              rate

변수

              -1.25

실제 상수


실제 상수는 그 자체로 값이 평가된다. 기호 상수는 #define 지시어를 사용하여 정의할 때 지정된 값으로 평가된다. 변수는 프로그램에 의해서 할당된 값으로 평가된다.
2.2 복합 수식
 : 더욱 복잡한 복합 수식(complex expression)은 단순 수식이 연산자에 의해서 결합된 것이다. 예를 들어, 다음은

2 + 8

단순 수식인 2와 8, 덧셈 연산자인 +로 구성되는 수식이다. 수식 2 + 8이 10으로 계산된다는 것은 쉽게 알 수 있다. 또한 다음과 같이 아주 복잡한 C의 수식을 사용할 수도 있을 것이다.

1.25 / 8 + 5 * rate + rate * rate / cost

수식 내에 여러 개의 연산자가 사용될 때에는 연산자의 우선 순위에 의해서 결과가 계산된다. 자세한 내용은 C의 연산자에 대해 모든 내용을 상세히 설명할 이 장의 후반부에서 다룰 것이다. C 의 수식을 더욱 복잡하게 사용할 수도 있다. 다음의 할당문을 살펴보자.

x = a + 10;

이것은 수식 a + 10을 계산하여 그 결과를 x에 저장하는 문장이다. 또한, x = a + 10이라는 문장은 그 자체로 등호의 왼쪽 부분에 있는 변수의 값을 계산하는 하나의 수식이 된다. 다음과 같은 문장을 작성할 수 있을 것이다. 이 문장은 수식 a + 10의 값을 두 변수인 x와 y에 할당한다.

y = x = a + 10;

다음과 같은 문장을 작성할 수도 있다.

x = 6 + (y = 4 + 5);

이 문장은 y에 4와 5의 합인 9를 저장하고, 다시 6을 더한 결과를 마지막으로 x에 저장하여 x의 값이 15가 되는 할당문이다. 이 문장이 정상적으로 수행되기 위해서는 괄호의 사용에 주의해야 한다. 괄호를 사용하는 이유는 나중에 설명할 것이다.

3. 연산자
 : 연산자(operator)는 C에서 하나 이상의 연산항에 대해 어떤 동작이나 계산을 수행하도록 지시하는 기호를 말한다. 연산항(operand)은 연산자의 동작 대상이다. C에서 모든 연산항은 수식이다. C의 연산자에는 여러 가지 종료가 있다.

    - 할당 연산자(assignment operator)
    - 산술 연산자(mathematical operator)
    - 관계 연산자(relational operator)
    - 논리 연산자(logical operator)

3.1 할당 연산자
 : 할당 연산자는 등호(=)이다. 프로그래밍에서 사용되는 등호의 용도는 일반적인 수학에서 사용되는 것과 약간 다르다. 다음과 같은 내용을 프로그램에 입력하면,

x = y;

'x는 y와 같다'는 것을 뜻하지 않고 'y의 값을 x에 할당하라'는 것을 뜻한다. C의 할당문에서 오른쪽부분은 어떤 수식이든지 될 수 있고, 왼쪽 부분은 변수의 이름이 되어야 한다. 그래서 할당문은 다음과 같은 형식을 사용한다.

변수 = 수식;

이 문장을 실행하면 수식이 평가되거나 계산되어 그 결가가 변수에 저장된다.

3.2 산술 연산자
 : C의 산술 연산자는 덧셈이나 뺄셈과 같은 수학적인 연산을 수행하는 것이다. C에는 두 개의 단항 산술 연산자와 다섯 개의 이항 산술 연산자가 존재한다.

▶ 단항 산술 연산자
 : 단항(unary) 산술 연산자는 하나의 연산항만을 사용하기 때문에 '단항' 연산자라고 불린다. C에서는 <표 4.1>에 나열되어 있는 두 개의 단항 산술 연산자가 제공된다.

<표 4.1> C의 단항 산술 연산자

연산자의 이름

    부    호

    동작   내용

      예   제

 증가 연산자

     ++

연산항을 1 증가

  ++x, x++

 감소 연산자

     --

연산항을 1 감소

  --x, x--



증가 연산자와 감소 연산자는 상수가 아니라 변수에서만 사용할 수 있다. 수행되는 동작은 주어진 연산항에서 1의 값을 빼거나 또는 연산항에 1의 값을 더해주는 것이다. 즉, 다음 문장은

++x;
--y;

각각 다음과 동일한 내용이다.

x = x + 1;
y = y - 1;

<표 4.1>에서 하나의 단항 연산자가 연산항 앞에서 사용되는 선행 모드(prefix mode)와 연산항 뒤에서 사용되는 후행 모드(postfix mode)가 있다는 것을 주목할 필요가 있다. 이런 두 가지 모드는 동일한 결과를 나타내지 않는다. 각각의 모드에서는 증가나 감소 동작이 수행되는 시기가 다르다.
- 선행 모드로 사용될 때 증가 연산자와 감소 연산자는 연산항이 사용되기 전에 연산을 수행한다.
- 후행 모드로 사용될 때 증가 연산자와 감소 연산자는 연산항이 사용된 후에 연산을 수행한다.

더욱 이해하기 쉽도록 하기 위해서 예제를 살펴보자. 다음과 같은 두 줄의 문장이 있다.

x = 10;
y = x++;

이런 문장을 실행하면 x는 11의 값을 가지고, y는 10의 값을 가지게 된다. x의 값은 y에 할당되고 증가된다. 반면에 다음의 문장을 살펴보자. 두 개의 변수 y와 x는 동일한 11의 값을 가지게 된다. 즉, x가 증가된 후에 결과 값이 y에 할당된다.

x = 10;
y = ++x;

등호는 동일하다는 것을 뜻하는 기호가 아니라 할당 연산자라는 사실을 기억하기 바란다. 비유를 하자면 등호는 '복사기'와 같은 기능을 하는 연산자이다. 즉, y = x라는 문장은 y에 x의 내용을 복사하는 것이다. 복사가 수행된 후에는 x의 값이 변경되어도 y에는 아무런 영향을 주지 않는다. <리스트 4.1>에 있는 프로그램은 증가 연산자와 감소 연산자가 선행 모드와 후행 모드에서 사용될 때의 차이점을 보여준다.

<리스트 4.1> UNARY.C : 선행과 후행 모드의 사용 예

#include <stdio.h>


int a, b;


main()

{

   a = b =5;


   printf("\n%d   %d", a--, --b);

   printf("\n%d   %d", a--, --b);

   printf("\n%d   %d", a--, --b);

   printf("\n%d   %d", a--, --b);

   printf("\n%d   %d", a--, --b);

   printf("\n%d   %d\n", a--, --b);


   return 0;

}


-> 출 력

   5            4
   4            3
   3            2
   2            1
   1            0

▶ 이항 산술 연산자
 : C의 이항 연산자(binary operators)는 두 개의 연산항을 사용한다. <표 4.2>에는 계산기에서도 흔히 볼 수 있는 여러 가지 산술 연산자를 포함하여 이항 연산자가 나열되어 있다.

<표 4.2> C의 이항 산술 연산자

연산자의 이름

 기호

                    동작 내용

  예 제

덧셈 연산자

   +

두 개의 연산항을 더한다.

  x + y

뺄셈 연산자

   -

첫 번째 연산항에서 두 번째 연산항을 뺀다.

  x - y

곱셈 연산자

   *

두 개의 연산항을 곱한다.

  x * y

나눗셈 연산자

   /

첫 번째 연산항을 두 번째 연산항으로 나눈다.

  x / y

나머지 연산자

   %

첫 번째 연산항을 두 번째 연산항으로 나눌 때

생성되는 나머지 값을 구한다.

  x % y


<표 4.2)에서 처음에 있는 네 개의 연산자는 자주 볼 수 있는 것이므로 동작내용을 쉽게 이해할 수 있을 것이다. 그러나 5번째 연산자인 나머지 연산자는 처음 보는 것이다. 나머지 연산자(modulus)는 첫 번째 연산항을 두 번째 연산항으로 나눌 때의 나머지 값을 돌려준다. 예를 들어, 11을 4로 나누면 결과는 3이된다. 즉, 11에는 4가 두 번 포함되며, 3이 나머지가 된다. 다음에는 몇 가지 예제가 나타나 있다.

100 % 9  -> 1
10 % 5    -> 0
40 % 6    -> 4

<리스트 4.2>에 있는 프로그램은 초 단위로 표현되는 값을 시간, 분, 초로 변환하기 위해서 나머지 연산자를 사용하는 방법을 보여주고 있다.

<리스트 4.2> SECONDS.C : 나머지 연산자의 사용 예

      #include <stdio.h>


      #define SECS_PER_NIM 60

      #define SECS_PER_HOUR 3600


      unsigned seconds, minutes, hours, secs_left, mins_left;


      main()

      {

          printf("Enter number of seconds (< 65000): ");

          scanf("%d", &seconds);


          hours = seconds / SECS_PER_HOUR;

          minutes = seconds / SECS_PER_MIN;

          mins_left = minutes % SECS_PER_MIN;

          secs_left = seconds % SECS_PER_MIN;


          printf("%u seconds is equal to ", seconds);

          printf("%u h, %u m, and %u s\n", hours, mins_left, secs_left);


          return 0;

      }


  -> 입력 / 출력
           Enter number of seconds (<65000): 60
           60 seconds is dequsl to 0 h, 1 m, and 0 s

3.3 연산자의 우선 순위와 괄호
 : 하나 이상의 연산자가 사용되는 수식에서는 어떤 순서에 의해서 연산자가 사용되는 것일까? 이것은 다음과 같은 할당문을 예로 들어 설명할 수 있다.

x = 4 + 5 * 3;

덧셈이 먼저 수행되면 결과는 다음과 같고, x에는 27의 값이 저장된다.

x = 9 * 3;

반면에, 곱셈이 먼저 수행된다면 결과는 다음과 같이 될 것이고, x에는 19의 값이 저장된다.

x = 4 + 15;

분명히, 연산자가 사용되는 순서에는 어떤 규칙이 필요하다는 것을 알 수 있다. C에서는 연산자 우선순위(operator precedence) 라고 하는 규칙이 엄격히 적용된다. 각각의 연산자는 일정한 우선 순위를 가지고 있다. 수식이 평가될 때에는 가장 높은 우선 순위를 가지는 연산자가 제일 먼저 사용된다. C의 산술 연산자의 우선 순위가 <표 4.3>에 나타나 있다. 상대적인 우선 순위에서 1이 가장 빠른 순서를 뜻하므로 가장 먼저 계산된다.

<표 4.3> C의 산술 연산자의 우선 순위

      연산자

상대적인 우선 순위

           ++  --

 1

           *  /  %

 2

           +  -

 3


<표 4.3>을 통해서 어떤 수식에서든지 연산자는 다음과 같은 순서로 사용된다는 것을 알 수 있을 것이다.

- 단항 증가 연산자와 감소 연산자

- 곱셈, 나눗셈, 나머지 연산자

- 덧셈, 뺄셈 연산자


만약 수식 내에 동일한 우선 순위를 가지는 하나 이상의 연산자가 포함되어 있다면, 수식 내의 왼쪽에서부터 오른쪽으로 진행하며 사용된다. 예를 들어, 다음과 같은 수식에서 %와 *는 동일한 우선 순위를 가지고 있지만, %는 가장 왼쪽의 연산자이므로 먼저 사용된다.


12 % 5 * 2


이 수식은 12 % 5가 2이고 2에 2를 곱한 것이 4이므로 4로 계산된다. 앞에서 사용된 예제로 돌아가서 이제 x = 4 + 5 * 3;에서는 곱셈이 덧셈보다 먼저 수행되므로, x에는 19의값이 저장된다는 것을 알 수 있을 것이다. 그렇다면, 우선 순위에 의해서 원하는 결과를 구할 수 없다면 어떤 문제가 발생할까? 앞에서 사용된 예제의 경우, 만약 4아 5를 더하고 나서 다시 3을 곱하기 원했다면 어떻게 해야 할까? C에서는 연산자의 우선 순위를 변경하기 위해서 괄호를 사용한다. 괄호 내에 포함된 수식은 연산자의 우선 순위에 관계없이 가장 먼저 계산된다. 그래서 앞의 예제는 다음과 같이 변경할 수 있을 것이다.


x = (4 + 5) * 3;


괄호 내의 수식 4 + 5가 먼저 계산되므로 x에는 27의 값이 저장될 것이다. 또한, 수식에서는 여러 개의 종속된 괄호를 사용할 수 있다. 종속된 괄호를 사용할 때에는 가장 안쪽에 있는 수식부터 바깥쪽으로 진행하며 연산이 수행된다. 다음의 복합 수식을 살펴보자.


x = 25 - (2 * (10 + 4))


이것은 다음과 같은 순서로 진행된다.

① 가장 안쪽의 수식인 8 / 2가 4의 값으로 계산되며 가장 먼저 수행된다.

② 바깥쪽으로 한 단계 이동하여 10 + 4의 수식이 14의 값으로 계산된다.

③ 마지막으로, 가장 바깥쪽의 수식인 2 * 14는 28의 값으로 계산된다.

④ 이제 남은 수식이 계산되어 -3의 값이 변수 x에 저장된다.


수식의 내용을 분명하게 하기 위해서는 원하는 곳에서 괄호를 사용할 수 있다. 이것은 우선 순위를 변경할 필요가 없는 곳에서도 가능하다. 괄호는 항상 쌍을 이루어 사용해야 하고, 그렇지 않으면 컴파일러에서 에러가 발생할 것이다.


3.4 수식의 계산 순서

 : 앞에서도 설명했듯이, C의 수식에서 동일한 우선 순위를 가지는 하나 이상의 연산자를 사용할 때에는 수식의 왼쪽에서 오른쪽으로 진행하며 계산하게 된다. 예를 들어, 다음 수식에서는


w * x / y * z


w와 x를 곱한 후에 다시 y로 나누고, 마지막으로 나눗셈의 결과와 z를 곱한다. 그러나 우선 순위가 다른 연산자의 겨우에는 왼쪽에서 오른쪽으로 진행하지 않는다. 다음과 같은 수식을 살펴보자.


w * x / y + z / y


여기에서는 우선 순위에 위해서 곱셈과 나눗셈이 덧셈보다 먼저 수행된다. 그러나 w * x / z가 z / y보다 먼저 수행되는지 또는 나중에 수행되는지를 지정하지는 않으므로, 이것을 정확히 알 수는 없다. 다른 하나의 예제를 살펴보자.


w * x / ++y + z / y


만약 앞의 수식(w * x / ++y)이 먼저 계산된다면, y의 값은 뒤의 수식(z / y)이 계산될 때 증가될 것이다. 뒤의 수식이 먼저 평가된다면 y는 증가되지 않을 것이며 결가는 달라지게 된다. 그래서 프로그램을 작성할 때에는 이런 애매한 수식을 사용하지 않도록 주의해야 한다.


* 이장의 마지막 부분에서 C의 모든 연산자의 우선 순위를 참조하기 바란다.


3.5 관계 연산자

 : C의 관계 연산자(relational operators)는 'x가 100보다 큰가' 또는 'y가 0과 동일한가'와 같은 내용을 질문하는 수식에서 값을 '비교하는' 경우에 사용된다. 관계 연산자가 사용되는 수식은 참(1)이나 거짓(0)으로 평가된다. <표 4.4>에는 C의 6가지 관계 연산자가 나타나 있다.


<표 4.4> C의 관계 연산자

연산자의 이름

기호

                 의        미

 예 제

같다.

  ==

연산항 x가 연산항 y와 같다.

 x == y

크다.

  >

연산항 x가 연산항 y보다 크다.

 x > y

작다.

  <

연산항 x가 연산항 y보다 작다.

 x < y

크거나 같다.

  >=

연산항 x가 연산항 y와 같거나 크다.

 x >= y

작거나 같다.

  <=

연산항 x가 연산항 y와 같거나 작다.

 x <= y

같지 않다.

  !=

연산항 x가 연산항 y와 다르다.

 x != y



<표 4.5>에서 관계 연산자가 사용되는 여러 가지 예를 살펴보자. 예제에서는 실제 상수를 사용하고 있지만 변수에서도 똑같은 규칙이 적용된다.
<표 4.5> 관계 연산자의 사용 예

수 식

                   의       미

 결 과

5 == 1

5가 1과 같다.

 0(거 짓)

5 > 1

5가 1보다 크다.

 1(참)

5 != 1

5가 1과 같지 않다.

 1(참)

(5 + 10) == (3 * 5)

(5 + 10)이 (3 * 5)와 같다.

 1(참)



    {

        printf("\nInput an integer value for x: ");

        scanf("%d", &x);

        printf("\nInput an integer value for y: ");

        scanf("%d", &y);

 

        if ( x == y )

           printf("x is equal to y\n");

 

        if ( x > y )

           printf("x is greater than y\n");

 

        if ( x < y )

           printf9"x is smaller than y\n");

 

        return 0;

    }

-> 입력 / 출력

    Input an integer value for x: 100

    Input an integer value for y: 10

    x is greater than y


4.1 else절

 : if문은 선택적으로 else절을 포함할 수 있다. else절은 다음과 같은 형식을 가진다.


            if (expression)

                statement1;

            else

                statement2;


만약 expressin이 참이라면 statement1이 실행된다. expression이 거짓이라면 statement2가 실행된다. statement1과 statement2는 복합문이나 블록이 될 수 있다. <리스트 4.4>는 if문에서 else절을 사용하여 <리스트 4.3>에 있는 프로그램을 다시 작성한 것이다.


<리스트 4.4> else절을 사용하는 if문의 예

      #include <stdio.h>


      int x, y;


      main()

      {

          printf("\nInput an integer value for x: ");

          scanf("%d", &x);

          printf("\nInput an integer value for y: ");

          scanf("%d", &y);


          if ( x == y )

              printf("x is equal to y\n");

          else

              if ( x > y )

                  printf("x is greater than y\n");

              else

                  printf("x is smaller than y\n");

          return 0;

      }


-> 입력 / 출력

     Input an integer value for x: 99

     Input an integer value for y: 8

     x is greater than y


5. 관계 수식의 계산

 : 관계 연산자를 사용하는 수식은 제한된 값을 가지게 되는 C의 수식이다. 관계 연산자는 수식을 거짓(0)이나 참(1)의 값으로 평가한다. 물론 관계 연산자는 if문이나 다른 어떤 조건문 내에서 가장 많이 사용되지만, 순수한 숫자값에 대해서 사용될 수도 있다. <리스트 4.5>에 있는 프로그램은 이런 사실을 설명해준다.


<리스트 4.5> 관계 수식의 사용 예를 보여주는 프로그램

    #include <stdio.h>


    int a;


    main()

    {

        a = (5 == 5);

        printf("\na = (5 == 5)\na = %d", a);


        a = (5 != 5);

        printf("\na = ( 5 != 5)\na = %d", a);


        a = (12 == 12) + (5 != 1);

        printf("\na = (12 == 12) + (5 != 1)\na = %d\n", a);

        return 0;

    }


-> 출 력

    a = (5 == 5)

    a = 1

    a = (5 != 5)

    a = 0

    a = (12 == 12) + (5 != 1)

    a = 2


5.1 관계 연산자의 우선 순위

 : 앞에서 설명되었던 산술 연산자와 마찬가지로 관계 연산자는 여러 개의 연산자를 사용하는 수식에서 수행 순서를 결정하는 우선 순위를 가지고 있다. 또한 관계 연산자를 사용하는 수식에서는 우선 순위를 변경하기 위해서 괄호를 사용할 수 있다. 우선, 모든 관계 연산자는 산술 연산자보다 낮은 우선 순위를 가진다. 그래서 다음과 같은 문장을 작성한다면 x에 2를 더하고 나서 그 결과를 y와 비교하게 된다.


    if (x + 2 > y)


이 문장은 우선 순위를 분명하게 해주기 위해서 괄호를 사용하는 다음 문장과 동일하다.


    if ((x + 2) > y)


C 컴파일러에 의해서 요구되는 것은 아니지만, (x + 2)에서 사용되는 괄호는 전체적인 결과가 y와 비교되어야 한다는 것을 분명히 알려줄 것이다. 관계 연산자에는 두 가지 단계의 우선 순위가 있다. <표 4.6>에는 우선 순위가 나타나 있다.


<표 4.6> C의 관계 연산자의 우선 순위

연산자

 상대적인 우선순위

 <  <=  >  >=

  1

 !=  ==

  2



만약 다음과 같은 내용을 사용한다면


   x == y > z


C는 0이니 1의 값으로 평가되는 y > z라는 수식을 먼저 사용하므로 다음과 같은 뜻이 될 것이다.


   x == (y > z)


그리고 나서 C는 x가 처음에 계산된 1이나 0과 동일한지 비교해본다. 이러한 형태의 비교는 드물게 사용되지만 알아둘 필요가 있을 것이다.


6. 논리 연산자

 : 프로그램을 작성하다 보면 가끔 하나 이상의 질문을 동시에 요구해야 하는 경우가 있다. 예를 들어, '평일이고, 오존 7시이며, 휴일이 아니라면 알람을 울린다.'와 같은 내용이 필요할 수 있다. C의 논리연산자(logical operators)는 두 개 이상의 관계 수식을 참이나 거짓으로 평가하기 위해서 하나의 수식으로 결합하는 데 사용된다. C의 세 가지 논리 연산자는 <표 4.7>에 나타나 있다.


<표 4.7> C의 논리 연산자

연산자

 기호

 예제

 AND

 &&

exp 1 && exp2

 OR

 ||

exp 1 || exp2

 NOT

 !

!exp1


이런 논리 연산자의 내용은 <표 4.8>에 나타나 있다.

<표 4.8> C의 논리 연산자의 동작 내용 

수  식

                               결    과

(exp1 && exp2)

exp1과 exp2가 모두 참이라면 참(1)이 되고, 그렇지 않다면

거짓(0)이 된다.

(exp1 || exp2)

exp1이나 exp2의 하나가 참이라면 참(1)이 되고, 두 가지 모두

거짓인 경우에만 거짓(0)이 된다.

(!exp1)

exp1이 참이라면 거짓(0)이 되고, exp1이 거짓이라면 참(1)이 된다.


연산항의 참이나 거짓 값에 따라서 결과가 참이나 거짓으로 평가되는 수식에서 논리 연산자가 사용되는 것을 쉽게 볼 수 있을 것이다. <표 4.9>에는 이런 수식의 예가 나타나 있다.


<표 4.9> C의 논리 연산자의 사용 예

수 식

 결 과

(5 == 5) && (6 != 2)

두 연산항이 참이므로 참(1)이 된다.

(5 > 1) || (6 < 1)

하나의 연산항이 참이므로 참(1)이 된다.

(2 == 1) && (5 == 5)

하나의 연산항이 거짓이므로 거짓(0)이 된다.

!(5 == 4)

연산항이 거짓이므로 참(1)이 된다.



또한, 여러 개의 논리 연산자를 사용하는 수식을 구성할 수도 있을 것이다. 예를 들어, 'x가 2, 3, 4 중의 하나와 동일한가?'라는 질문을 수식으로 나타내기 위해서는 다음과 같은 내용을 작성할 수 있을 것이다.


(x == 2) || (x == 3) || (x == 4)


논리 연산자는 이러한 질문에 대해서 가끔 여러 가지 해결 방법을 제공해주기도 한다. 그래서 x가 정수형 변수라면 앞의 질문을 다음과 같이 작성할 수도 있을 것이다.


      (x > 1) && (x < 5)

      (x >= 2) && (x <= 4)


6.1 참과 거짓을 뜻하는 값

 : C의 관계 수식에서는 거짓을 나타내기 위해서 0이 사용되고, 참을 나타내기 위해서 1이 사용된다는 것을 설명했다. 그러나 참이나 거짓의 논리 값으로 평가되는 C의 수식이나 문장에서 어떤 숫자값이 사용될 때에는 숫자값 역시 참이나 거짓으로 간주된다는 사실을 기억할 필요가 있다. 이것은 다음과 같은 규칙을 가진다.


- 0의 값은 거짓을 표현한다.

- 0이 아닌 다른 모든 값은 참을 표현한다.


x의 값을 출력하는 다음 예제를 살펴보자.


    x = 125;

    if (x)

    printf("%d", x);


x가 0이 아닌 값을 가지므로 if문에서 (x)라는 수식은 참으로 평가되는 것이다. 또한, 다음과 같은 수식은 어떤 수식에서든지


     (expression)


다음과 같은 뜻을 가진다.


     (expression != 0)


이러한 두 개의 수식은 expression이 0이 아니라면 참으로 평가되고, expression이 0이라면 거짓으로 평가된다. 부정 연산자(!)를 사용하면 다음과 같은 수식을 사용할 수 있다.


     (!expression)


이것은 다음과 같은 뜻이다.


     (expression == 0)


6.2 연산자의 우선 순위

 : 예상할 수 있듯이, C의 논리 연산자에도 우선 순위가 정해져 있다. 이것은 논리 연사자간의 우선 순위뿐 아니라 다른 연산자와도 관계된 것이다. ! 연산자는 단항 연산자인 ++나 --와 같은 우선 순위를 가지고 있다. 그래서 ! 는 모든 관계 연산자와 이항 산술 연산자보다도 높은 우선 순위를 가지고 있다. 반면에, &&는 || 연산자보다 높은 우선 순위를 가지고 있지만, 이런 두 가지 연산자는 모든 산술 연산자와 관계 연산자보다 낮은 우선 순위를 가진다. C의 다른 모든 연산자에서와 마찬가지로, 논리 연산자를 사용할 때에는 우선 순위를 변경하기 위해서 괄호를 사용할 수 있다. 예제를 살펴보자.

다음과 같은 세 가지 비교를 수행하는 논리 수식을 작성하기 원한다고 하자.


     ① a가 b보다 작은가?

     ② a가 c보다 작은가?

     ③ c가 d보다 작은가?


조건 1이나 조건 2의 하나가 참이고, 조건 3이 참인 경우에만 전체적으로 참이 되는 수식을 작성하기 원한다면 다음과 같은 내용을 작성할 수 있을 것이다.


     a < b || a < c && c < d


그러나 이것은 예상했던 대로 동작하지 않을 것이다. && 연산자는 ||보다 높은 우선 순위를 가지므로 앞의 수식은 다음과 동일하다.


     a < b || (a < c && c < d)


그래서 (a < b)가 참이라면 (a < c)와 (c < d)가 참인지의 여부에 관계없이 전체적인 결과는 참이 될 것이다. 그러므로 정상적인 결과를 얻기 위해서는 다음과 같이 작성해야 할 것이다.


     (a < b || a < c) && c < d


이것은 ||연산자가 &&연산자보다 먼저 수행되게 한다. <리스트 4.6>에는 이러한 두 가지 형태의 수식에 의한 결과를 비교할 수 있는 프로그램이 나타나 있다. 변수는 정상적인 경우에 결과가 거짓이 되도록 설정되어 있다.


<리스트 4.6> 논리 연산자의 우선 순위

      #include <stdio.h>


      int a = 5, b = 6, c = 5, d = 1;

      int x;


      main()

      {

           x = a < b || a < c && c < d;

           printf("\nWithout parentheses the expression evaluates as %d.", x);


           x = (a < b || a < c) && c < d;

           printf("\nWith parentheses the expression evaluates as %d.\n", x);

           return 0;

      }


->출 력


    Without parentheses the expression evaluates as 1

    With parentheses the expression evaluates as 0


6.3 복합 할당 연산자

 : C의 복합 할당 연산자(compound assignment operators)는 할당문과 이항 산술 연산자의 기능을 동시에 사용하게 해주는 간편한 방법을 제공해준다. 예를 들어, x의 갓을 5 증가 시키거나 x에 5를 더하여 x에 저장하기 원한다고 하자. 다음과 같은 수식을 작성할 수 있을 것이다.


     x = x + 5;


이것은 더욱 간편한 방법인 복합 할당 연산자를 사용하여 다음과 같이 작성할 수 있다.

     x += 5;


일반적으로 복합 할당 연산자는 다음과 같은 형식을 사용한다. op는 이항 연산자를 뜻하는 것이다.


     exp1 op = exp2;


이것은 다음과 같은 뜻을 가지고 있다.


     exp1 = exp1 op exp2;


복합 할당 연산자에서는 이 장의 앞 부분에서 설명했던 5가지의 이항 연산자를 사용할 수 있다. <표 4.10>에는 몇 가지 예제가 설명된다.


<표 4.10> 복합 할당 연산자의 사용 예

다음과 같이 작성할 때

 다음과 같은 의미를 갖는다.

 x *= y

 x = x * y

 y -= z + 1

 y = y - z + 1

 a /= b

 a = a / b

 x += y / 8

 x = x + y / 8

 y %= 3

 y = y % 3


복합 연산자는 특히 할당 연산자의 왼쪽 부분에 있는 변수가 복잡할 때 간편하고 유용하게 사용된다. 다른 모든 할당문에서와 마찬가지로 복합 할당문은 수식이므로 그 결과는 왼쪽의 변수에 저장된다. 그래서 다음의 수식을 실행하면 x와 z는 모두 14의 값을 가지게 된다.


      x = 12;

      z = x += 2;

 

6.4 조건 연산자

 : 조건 연산자(conditional operator)는 세 개의 연산항을 사용하므로 3항 연산자 (ternary operator)라고 하며, C에서 제공되는 유일한 3항 연산자이다. 조건 연산자는 다음과 같은 형식으로 사용된다.


      exp1 ? exp2 : exp3


exp1이 참, 즉 0이 아닌 어떤 값을 가진다면 전체적인 수식의 결과는 exp2의 값이 된다. 그러나 exp1이 0의값을 가지는 거짓으로 평가되면 전체적인 수식은 exp3의 값이 된다. 예를 들어, 다음 문장에서 y가 참이면 1의 값을 x에 할당하고, y가 거짓이면 x에 100을 할당한다.


      x = y ? 1 : 100;


비슷하게, z에 x와 y 중에서 더 큰 값을 저장하기 위해서는 다음과 같은 문장을 작성할 수 있을 것이다.


      z = (x > y) ? x : y;


아마도 조건 연산자가 if문과 약간 비슷한 기능을 제공한다는 사실을 알 수 있을 것이다. 그래서 앞의 예제는 다음과 같은 형태로 변경할 수 있다.


      if (x > y)

           z = x;

      else

           z = y;


조건 연산자가 모든 경우에 if....else 구조를 대신해서 사용될 수 있는 것은 아니지만 if문보다 편한 것은 사실이다. 또한, 조건 연산자는 printf()함수의 내부와 같이 if문을 사용할 수 없는 곳에서도 사용할 수 있다.


      printf("The larger value is %d", ((x > y) ? x : y));


6.5 쉼표 연산자

 : 쉼표(comma)는 C에서 변수의 선언, 함수의 인수 등을 독립적으로 구분하기 위한 간단한 구두점으로 자주 사용된다. 그러나 어떤 경우에는 쉼표가 내용을 구분하는 구두점이 아니라 연산자로 사용되기도 한다. 쉼표는 수식을 두 개의 부분으로 구분하기 위해서 사용될 수 있다. 이것은 다음과 같은 결과를 나타낸다.


- 수식 내에서 구분되는 두 개의 부분은 왼쪽에서 오른쪽으로 진행하며 사용된다.

- 전체적인 수식의 결과는 오른쪽에 있는 내용에 의해서 결정된다.


예를 들어, 다음 문장은 x에 b의 값을 할당하고 나서 a를 증가시키고 b를 증가시킨다.


      x = (a++, b++);


++연산자가 후행 모드로 사용되기 때문에 b의 값은 증가되기 전에 x에 저장된다. 괄호는 쉼표 연산자가 할당 연산자보다도 낮은 우선 순위를 가지고 있으므로 사용된 것이다. 다음에 설명하겠지만, 쉼표 연산자가 가장 흔히 사용되는 경우는 for문에서이다.


7. 연산자 우선 순위

 : <표 4.11>은 우선 순위가 감소하는 순서대로 C의 모든 연산자를 나열하고 있다. 같은 줄에 있는 연산자는 같은 우선 순위를 가진다. 

단 계

        연 산 자

  1

 ()  []  ->  .

  2

 !  ~  ++  --  * (간접연산자)  & (주소 연산자)

sizeof(형 변환)  + (단항연산자)  - (단항연산자)

  3

 *(곱셈 연산자)  /  %

  4

 +  -

  5

 <<  >>

  6

 <  <=  >  >=

  7

 ==  !=

  8

 & (비트 AND 연산자)

  9

 ^

  10

 |

  11

 &&

  12

 ||

  13

 ? :

  14

 =  +=  -=  *=  /=  %=  &=  ^=  |=  <<=  >>=

  15

,

()는 함수 연산자이다.  []는 배열 연산자이다.



'C 언어' 카테고리의 다른 글

2장 C 프로그램의 구성 요소  (0) 2019.06.02
3장 데이터 저장하기 : 변수와 상수  (0) 2019.06.02
5장 함수의 기본  (0) 2019.06.02
6장 기본적인 프로그램 제어문  (0) 2019.06.02
7장 입출력의 기초  (0) 2019.06.02

함수는 C에서 프로그램을 작성할 때 중심이 되는 부분이다. 앞에서는 이미 컴파일러의 일부분으로 제공되는 C의 라이브러리 함수의 몇 가지를 살펴보았다. 이 장에서는 프로그래머가 작성하는 사용자 정의 함수에 대해서 알아보도록 하겠다.


오늘은 다음과 같은 내용을 배울 것이다.

- 함수는 무엇이고, 어떤 내용으로 구성되는가?

- 함수와 구조화 프로그래밍의 장점

- 함수를 작성하는 방법

- 함수 내에서의 지역 변수 선언

- 함수에서 프로그램으로 결과 값을 돌려주는 방법

- 함수에 인수를 전달하는 방법


1. 함수는 무엇인가?

 : 이 장에서는 '함수란 무엇인가?'에 대한 해답을 두 가지 나누어 설명할 것이다. 우선, 함수가 무엇인지 설명하고 나서 함수의 사용 방법을 알아보도록 하자.


1.1 함수의 정의

 : 우선 함수가 무엇인지 알아보자. 함수(function)는 일정한 동작을 수행하고, 필요에 따라 함수를 호출했던 프로그램으로 결과 값을 돌려주는 C의 독립적인 코드이며, 독특한 이름을 가진다. 이것을 좀더 구체적으로 살펴보자.


 - 함수는 이름을 가진다.

: 모든 함수는 독특한 이름을 가지고 있다. 프로그램에서는 이런 이름을 사용하여 함수에 포함된 내용(코드)을 사용할 수 있게 된다. 이런 동작을 함수 호출이라고 한다. 함수는 또한 다른 함수 내에서 호출될 수 있다.


 - 함수는 독립적이다.

: 함수는 프로그램 내의 다른 부분에 의해서 영향을 받거나 또는 다른 부분에 영향을 주지 않고 주어진 동작을 수행한다.


 - 함수는 특정 동작을 수행한다.

: 이것은 아주 간단한 사실이다. 함수가 수행하는 동작은 프로그램에서 프린터로 텍스트 문장을 인쇄하거나 숫자 순서대로 배열을 정렬하고, 세제곱근을 구하는 것과 같이 프로그램의 전체적인 동작을 구성하는 일부분이자 독립된 작업 내용이다.


 - 함수는 호출한 프로그램으로 결과 값을 돌려줄 수 있다.

: 프로그램이 함수를 호출할 때에는 함수에 포함된 문장이 실행된다. 이런 함수 내의 문장은 필요할 때마다 호출한 프로그램으로 결과를 전달할 수 있다. 지금까지 설명한 내용이 함수의 '정의'이다. 함수에 대한 내용을 설명할 때 이런 내용을 기억하기 바람.


1.2 함수의 사용 예

<리스트 5.1>에 있는 프로그램에는 사용자 정의 함수가 포함되어 있다.


<리스트 5.1> 숫자의 세제곱을 계산하기 위해 함수를 사용하는 프로그램

#include <stdio.h>


long cube(long x);


long input, answer;


main(0

{

   printf("Enter an integer value: ");

   scanf("%d", &input);

   answer = cube(input);


   printf("\nThe cube of %ld is %ld.\n", input, answer);


   return 0;

}


long cube(long x)

{

   long x_cubed;


   x_cubed = x * x * x;

   return x_cubed;

}



-> 입력 / 출력
 Enter an integer value: 9
 The cube of 9 is 729.
=> cube()함수와 main()함수의 구조를 비교해본다면, 두 함수의 구조가 같다는 것을 알 수 있다. main()도 하나의 함수이다. 함수의 다른 예로는 앞에서 사용했던 printf()와 scanf()가 있다. printf()와 scanf()는 사용자 정의 함수와 달리 라이브러리 함수이지만, 사용자 정의 함수와 마찬가지로 인수를 사용하고 결과를 돌려주는 함수이다.

2. 함수의 사용
 : C 프로그램에서는 프로그램 내에서 함수를 호출할 때까지 함수 내의 문장을 실행하지 않는다. 프로그램은 함수를 호출할 때 하나 이상의 자료를 인수의 형식으로 함수에 전달할 수 있다. 인수(argument)는 함수에서 특정 동작을 수행하기 위해서 필요한 프로그램의 데이터이다. 인수를 전달받은 함수는 주어진 동작을 수행하기 위해 함수 내에 포함된 문장을 실행하게 된다. 함수 내의 모든 문장이 실행되고 나면, 프로그램에서 함수를 호출했던 부분이 다시 실행될 것이다. 함수는 필요한 경우에 복귀값의 형식으로 프로그램에 결과값을 전달할 수 있다. <그림 5.1>은 한 번씩 호출되는 세 개의 함수가 사용되는 프로그램을 보여주고 있다. 함수가 호출될 때마다 프로그램의 제어는 함수로 전달된다. 함수의 실행이 완료될 때 제어는 프로그램 내에서 함수를 호출한 부분으로 다시 전달된다. 함수는 필요한 만큼 여러 번 호출될 수 있으며, 어떤 순서로도 호출될 수 있다.

<그림 5.1> 프로그램에서 함수를 호출할 때 제어는 함수로 전달되며, 함수의 실행이 완료될 때 다시 프로그램으로 전달된다. 이제 함수에 대한 개념과 함수의 중요성을 이해할 수 있을 것이다. 잠시 후에 자신만의 함수를 작성하고 사용하는 방법을 살펴보도록 하자.

** 문법 - 함수의 작성과 사용


함수 원형

    return_type function_name(arg-type name-1, ..., arg-type name-n);


함수 정의

    return_type function_name(arg-type name-1, ..., arg-type name-n)

    {

         /* 함수의 여러 가지 문장들 */

    }


함수 원형(function prototype)은 프로그램에서 나중에 정의되는 함수에 대한 내용을 컴파일러에게 알려주고 함수가 돌려주는 복귀값의 형태를 포함한다. 또한 함수의 동작 내용을 설명하는 함수의 이름을 포함하고 있다. 함수 원형에는 함수에 전달되는 인수의 형태(arg-type)가 포함되고, 필요에 따라 함수에 전달되는 변수의 이름을 포함한다. 함수 원형은 항상 세미콜론으로 끝난다. 함수 정의(function definition)는 실제적인 함수의 내용이다. 함수 정의에는 실행될 문장이 포함된다. 함수 정의에서 첫 번째 부분은 함수 헤더 (function header)라고 하는데, 함수 원형에서 세미콜론을 제거한 것과 같은 내용을 가진다. 함수 헤더는 세미콜론으로 끝나지 않는다. 또한 함수 원형에서는 변수의 이름이 선택적으로 사용되지만, 함수 헤더에서는 반드시 포함되어야 한다. 헤더의 다음에는 함수가 수행하는 동작을 위해서 필요한 프로그램 문장이 포함된다. 함수의 주요 내용이 되는 이 부분은 중괄호 내에 포함되어야 한다. 또한, void가 아닌 다른 어떤 형태의 복귀값이 필요하다면, 지정된 형태에 일치하는 return문이 포함되어야 한다.


   함수 원형의 예


       double squared(double number);

       void print_report(int report_number);

       int get_menu_choice(void);


   함수 정의의 예


       double squared(double number)

       {

         return (number * number);

       }

 

       void print_report(int report_number)

       {

          if(report_number == 1)

            puts("Printing Report 1");

          else

            puts("Not printing Report 1");

       }

 


3. 함수와 구조화 프로그래밍

 : C 프로그램을 작성할 때에는 개별적인 프로그램 동작을 함수를 통해서 독립적으로 수행하는 구조화 프로그래밍(structured programming)을 구현할 수 있다. 프로그램의 독립된 부분에서 작업을 처리한다는 것은 앞에서도 설명한 함수의 특징과 동일하다. 함수와 구조화 프로그래밍은 아주 밀접한 관련이 있다.


3.1 구조화 프로그래밍의 장점

 : 구조화 프로그래밍이 왜 중요한가? 두 가지 중요한 이유가 있다.


- 복잡한 프로그래밍 문제를 여러 개의 단순하고 짧은 내용으로 분할하므로 구조화 프로그래밍을 통해 프로그램을 작성하는 것이 더 쉽다. 각각의 작업은 프로그램과 독립적으로 존재하는 함수에 의해서 수행된다. 이런 과정을 통해서 한 번에 하나씩 문제를 처리하며 상대적으로 쉽게 프로그램을 작성할 수 있다.


- 구조화 프로그래밍을 통해 작성한 프로그램은 디버깅이 쉽다. 만약 프로그램에 비정상적인 동작을 나타내는 버그(bug)가 포함되어 있다면, 구조화 프로그래밍에서는 특정 함수를 분리해서 문제를 쉽게 발견할 수 있다.


 구조화 프로그래밍과 관련된 다른 한 가지 장점은 시간을 절약할 수 있다는 것이다. 만약 어떤 프로그램에서 특정 동작을 수행하는 함수를 작성한다면, 이런 동작을 수행할 필요가 있는 다른 프로그램에서 그 함수를 쉽게 사용할 수 있을 것이다. 심지어 새로운 프로그램에서 약간 다른 내용의 동작을 수행할 필요가 있다면, 처음부터 새로운 함수를 작성하는 것보다 이전에 작성했던 함수를 변경하여 사용하는 것이 더 쉽다는 것을 알 수 있을 것이다. 실제로 이런 함수를 직접 작성해보지 않더라도, 지금까지 두 함수 printf()와 scanf()를 아주 많이 사용했다는 것을 생각해보기 바란다. 만약 함수가 한 가지 작업을 수행하도록 작성되어 있다면, 다른 프로그램에서 사용하기는 더 쉬어진다.


3.2 구조화 프로그래밍의 방법

 : 구조화 프로그래밍을 구현하기 원한다면, 우선 몇 가지 계획을 세울 필요가 있다. 이런 계획은 프로그램을 직접 작성하기 전에 세워야 하는 것으로, 종이와 연필만으로도 간단히 할 수 있는 일이다. 프로그래밍 계획에는 처리할 작업을 목록으로 나열해야 한다. 일단 전체적이고 포괄적인 프로그램의 동작을 생각해보자. 만약 이름과 주소록을 관리하기 위한 프로그램을 작성하려고 한다면, 프로그램이 어떤 동작을 수행해야 할까? 몇 가지 예를 들어보자.


- 새로운 이름과 주소를 입력한다.

- 현재의 내용을 변경한다.

- 성과 이름에 의한 순서대로 내용을 분류한다.

- 우편용 레이블(label)을 인쇄한다.


여기에서는 프로그램의 동작 내용을 네 가지 주요 작업으로 분할했다. 각각의 내용은 함수로 구성할 수 있다. 이제, 이것을 더욱 구체적으로 나누어보자. 예를 들어, '새로운 이름과 주소를 입력한다'는 동작은 다음과 같이 더욱 구체적으로 나타낼 수 있을 것이다.


- 디스크에서 현재의 주소록을 읽어들인다.

- 하나 이상의 새로운 내용을 입력하도록 해준다.

- 새로운 데이터를 목록에 추가한다.

- 갱신된 목록을 디스크에 저장한다.


이와 비슷하게 '현재의 내용을 변경한다.'는 동작은 다음과 같이 나누어볼 수 있다.


- 디스크에서 현재의 주소록을 읽어들인다.

- 하나 이상의 내용을 변경한다.

- 갱신된 목록을 디스크에 저장한다.


이런 두 가지 항목에서 공통적인 내용이 있다는 것을 알 수 있다. 디스크에서 데이터를 읽어들이고 갱신된 내용을 다시 디스크에 저장하는 동작은 두 가지 경우에 모두 사용된다. 그래서 '디스크에서 현재의 주소록을 읽어들이는' 하나의 함수를 작성하고, 이 함수를 '새로운 이름과 주소를 입력한다'는 함수와 '현재의 내용을 변경한다.'는 함수에서 호출할 수 있다. 또한, '갱신된 목록을 디스크에 저장하는' 함수도 똑같은 방법으로 사용할 수 있을 것이다.


여기서 구조화 프로그램의 한 가지 장점을 알 수 있을 것이다. 프로그램을 작업별로 세분화하면 프로그램 내에서 공통적으로 수행되는 작업이 무엇인지 알 수 있다. 그래서 프로그램을 작성하는데 소모되는 시간을 절약하고, 프로그램을 더욱 작고 효율적으로 만들 수 있으며, '공통적인' 디스크 작업 함수를 작성할 수 있다.이런 프로그래밍 방식은 계층적(hierarchical)으로 짜여진 프로그램의 구조를 형성한다. <그림 5.2>에서는 주소 목록 프로그램의 계층적인 프로그래밍 방식을 보여주고 있다.

<그림 5.2> 구조화 프로그래밍은 계층적으로 구성된다. 이런 과정에 의해서, 프로그램에서 수행할 필요가 있는 작업의 세분화도니 목록을 작성할 수 있다. 그리고 나서 모든 동작을 한번에 수행해야 하는 복잡한 프로그램을 작성하는 것보다는 상대적으로 더욱 간단한 각각의 함수를 한 번에 하나씩 작성할 수 있게 된다. 하나의 함수를 작성해서 정상적으로 동작하는지 확인하고 나면 다른 함수를 작성할 수 있다. 프로그램은 서서히 틀을 잡게 될 것이다.

3.3 하향식 접근

 : C 프로그래머들은 구조화 프로그래밍을 통해서 하향식 접근 방법(top-down approach)를 사용하게 된다. 이것은 프로그램의 구성 방법이 마치 나무를 뒤집어 놓은 것처럼 보이는 <그림 5.2>에서 설명한 내용이다. 프로그램의 실제 동작은 대부분 '가지의 끝 부분'에 해당하는 함수에서 수행된다. '상위'의 함수는 이런 여러 가지 함수들 중에서 필요한 것을 실행하도록 지시하는 역할을 한다. 결과적으로, 많은 C 프로그램은 프로그램의 주요 부분인 main() 함수에 적은 양의 코드만을 포함하게 되고, 프로그램의 골격을 이루는 내용은 여러 함수에 포함된다. main()에는 프로그램의 실행을 지시하는 몇 줄의 함수 호출문만 포함된다. 메뉴를 사용하는 프로그램에서는 종종 사용자가 선택한 사항에 따라서 프로그램의 실행을 분기하는 방법이 사용된다. 메뉴의 각 분기는 서로 다른 함수를 사용한다.


4. 함수의 작성

 : 함수를 작성하는 데 있어서의 첫 번째 단계는 함수의 용도를 결정하는 것이다. 일단 함수가 수행해야 하는 동작을 결정하고 나면 함수를 작성하는 실제 과정은 그리 어렵지 않을 것이다.


4.1 함수의 헤더

 : 모든 함수의 첫 번째 부분은 함수에 대한 특정 사항을 설명하는 세 개의 요소로 이루어지는 함수 헤더이다. 헤더는 <그림 5.3>에 나타나 있다. 각각의 요소에 대해서 하나씩 살펴보도록 하자.


<그림 5.3> 함수 헤더의 세 가지 요소

▶ 함수의 복귀형

 : 함수의 복귀형은 함수의 동작이 완료된 후에 함수를 호출했던 프로그램으로 돌려주는 데이터의 형태를 가리킨다. 복귀형은 C의 모든 데이터형이 될 수 있다. 즉, char, int, long. float, double이 사용될 수 있다. 또한, 아무런 값도 전달하지 않는 void형의 함수를 정의할 수 있다. 다음은 몇 가지 예이다.


        int   func1(...)    /* int형을 돌려준다. */

        float func2(...)    /* float형을 돌려준다. */

        void  func3(...)    /* 아무 것도 돌려주지 않는다. */


▶ 함수의 이름

 : 함수의 이름을 지정할 때에는 C의 변수 이름에 대한 규칙을 따라야 한다. 함수의 이름은 독특한 것이어야 하고, 다른 어떤 함수나 변수에 할당된 이름을 사용해서는 안된다. 함수가 수행하는 동작 내용을 알 수 있도록 함수의 이름을 지정하는 것이 좋다.


▶ 매개 변수 목록

 : 대부분의 함수에서는 프로그램에서 전달받는 인수(arguments)를 사용한다. 함수는 어떤 형태의 인수를 사용하는지 나타내어야 한다. 즉, 인수의 데이터가 지정되어야 한다. 함수에는 모든 C의 데이터형이 인수로 사용될 수 있다. 인수의 형태에 대한 내용은 함수 헤더에 매개 변수 목록의 형식으로 포함된다. 함수에 전달되는 각각의 인수에 대해서 매개 변수 목록에는 대응하는 내용이 포함되어야 한다. 이런 내용은 데이터형과 매개 변수의 이름이다. 예를 들어, 다음은 <리스트 5.1>의 함수에서 사용된 헤더이다.


        long cube(long x)


매개 변수 목록은 이 함수가 x라는 매개 변수로 표현되는 하나의 long형 인수를 사용한다는 것을 나타내기 위해서 long x라고 되어 있다. 하나 이상의 매개 변수가 존재한다면 각각의 변수를 쉼표로 구분해야 한다. 그래서 다음과 같은 헤더는


        void func1(int x, float y, char z)


x라는 이름의 int형 변수, y라는 이름의 float형 변수, z라는 이름의 char형 변수 등 세 개의 인수를 사용하는 함수를 정의한다. 어떤 함수에서는 아무런 인수도 사용하지 않으며, 이 때에는 매개 변수의 목록에 void를 사용해야 한다.


        void func2(void)


가끔 매개 변수와 인수의 차이점에 대해서 혼란스러울 것이다. 매개 변수(parameter)는 함수의 헤더에 포함되는 내용으로, 인수에 대응하여 '영역을 확보하는' 역할을 한다. 함수의 매개 변수는 고정적인 것이므로 프로그램이 실행되는 동안에 변경되지 않고 사용된다. 반면에, 인수는 함수를 호출하는 프로그램에 의해서 함수로 전달되는 실제값이다. 함수가 호출될 때마다 다른 인수값이 전달될 수 있다. 함수가 호출될 때마다 같은 수와 같은 형태의 인수를 전달해야 하지만, 인수의 실제 값은 달라질 수 있다. 함수는 인수에 대응하는 매개 변수의 이름을 통해서 값을 받아들인다. 예제를 통해서 이런 사실을 분명히 이해하도록 하자. <리스트 5.2>는 두 변 호출되는 하나의 함수가 포함되어 있는 아주 간단한 프로그램이다


<리스트 5.2> 인수와 매개 변수의 차이점


    #include <stdio.h>


    float x = (float) 3.5, y = (float) 65.11;


    float half_of(float k);


    main()

    {

       z = half_of(x);

       printf("The value of z = %f\n", z);


       z = half_of(y);

       printf("The value of z = %f\n", z);


       return 0;

    }


    float half_of(float k)

    {

       return (k / 2);

    }



-> 출력

   The value of z = 1.750000

   The value of z = 32.555000


<그림 5.4>에는 인수와 매개 변수의 관계가 나타나 있다.


 <그림 5.4> 함수가 호출될 때마다 인수가 함수 내의 매개 변수에 전달된다.

4.2 함수의 몸체

 : 함수의 몸체(function body)는 중괄호 내에 포함되어 있으며 함수의 헤더 다음에 나타난다. 함수의 실제 동작은 여기서 수행된다. 함수가 호출될 때, 프로그램의 제어는 함수의 몸체에서 부터 시작해서 return문을 만나거나 닫는 중괄호가 나타날 때 다시 함수를 호출한 프로그램으로 돌아간다.


▶ 지역변수

 : 함수의 몸체에서는 변수를 선언할 수 있다. 함수 내에서 선언되는 변수를 지역 변수(local variables)라고 한다. 지역적(local)이라는 것은 변수가 함수 내에서만 사용되고 프로그램의 다른 부분에서 동일한 이름으로 선언된 변수와 구분된다는 것을 뜻한다. 이것이 지역 변수의 개념이다. 이제부터 지역 변수를 선언하는 방법을 살펴보자. 지역 변수는 다른 변수에서와 동일한 규칙을 사용하여 선언할 수 있다. 또한, 지역 변수는 선언하는 동시에 초기화할 수 있다. 함수 내에서는 C의 모든 변수형을 선언할 수 있다. 다음은 함수 내에서 선언되는 네 개의 지역 변수를 사용하는 예제이다.


        int func1(int y)

        {

            int a, b = 10;

            float rate;

            double cost = 12.55;

        }


여기에서는 함수 내부에서 사용되는 지역 변수 a, b, rate, cost를 선언한다. 함수의 매개 변수도 변수의 선언에 해당되는 것이므로, 매개 변수의 목록에 있는 변수도 함수 내에서 사용될 수 있다. 함수 내에서 변수를 선언하고 사용할 때, 이 지역 변수는 프로그램의 다른 부분에서 선언된 모든 변수에 대해서 독립적이다. 심지어 다른 변수가 같은 이름을 사용하고 있더라도 서로 다른 변수이다. <리스트 5.3>에 있는 프로그램은 이런 독립성을 보여주고 있다.


<리스트 5.3> 지역 변수의 사용 

  #include <stdio.h>


    int x = 1, y = 2;


    void demo(void);


    main()

    {

       printf("\nBefore calling demo(), x = %d and y = %d.", x, y);

       demo();

       printf("\nAfter calling demo(), x = %d and y = %d.\n", x, y);


       return 0;

    }


    void demo(void)

    {

       int x = 88, y = 99;

       printf("\nWithin demo(), x = %d and y = %d.", x, y);

    }


-> 출력


   Before calling demi(), x = 1 and y = 2.

   Within demo(), x = 88 and y = 99.

   After calling demo(), x = 1 and y = 2.


여기에서 알 수 있듯이 함수의 지역 변수 x와 y는 함수의 바깥에서 선언된 전역 변수 x, y와 완전히 다르다. 함수 내에서 변수를 사용하는 경우에는 세가지 규칙이 적용된다.


- 함수에서 변수를 사용하기 위해서는 함수 헤더나 함수 내에서 선언해야 한다.

- 함수를 호출하는 프로그램에서 사용되는 값을 함수 내에서 사용하기 위해서는 인수의 형태로 전달해야 한다.

- 함수를 호출한 프로그램으로 함수의 결과값을 전달하기 위해서는 함수에서 결과값을 돌려주어야 한다.


▶ 함수의 내용

 : 함수에서 사용할 수 있는 문장의 종류나 내용에는 기본적으로 아무런 제한이 없다. 함수에서 수행할 수 없는 유일한 한가지 동작은 다른 함수를 정의하는 것이다. 그러나 순환문, if문, 할당문을 포함하여 함수에서는 C의 모든 문장을 사용할 수 있다. 또한 함수에서는 라이브러리 함수와 사용자 정의 함수를 호출할 수 있다. 함수의 길이에는 제한이 있을까? C는 함수의 길이에 대해서 아무런 제한을 두고 있지 않지만, 실용적인면에서 볼 때 함수는 상대적으로 짧은 것이 좋다. 구조화 프로그래밍에서 각각의 함수는 상대적으로 간단한 하나의 작업을 수행하는 것이 좋다는 사실을 기억하기 바란다. 함수의 길이가 상당히 길어진다면 하나의 함수로 여러 가지 동작을 수행하려고 했기 때문이다. 이런 함수는 아마도 두 개 이상의 더욱 세분화된 함수로 나눌 수 있을 것이다. 그렇다면, 어느 정도의 길이가 긴 것일까? 여기에 대한 정확한 해답은 없지만, 실제 경험에 의하면 25줄이나 30줄 이상의 함수를 작성하는 경우는 거의 없고 찾아보기도 힘들다. 함수의 길이에 대해서는 스스로 판단해야 한다. 어떤 프로그램에서는 긴 함수가 필요할 것이고, 대부분의 함수에서는 몇 줄의 문장만이 필요할 것이다. 프로그래밍 경험을 쌓아감에 따라, 더욱 세분화되니 함수를 사용해야 하는 경우와 어느 정도의 길이를 가지는 함수를 작성하여 사용해야 하는 경우를 구분할 수 있게 될 것이다.


▶ 값의 전달

 : 함수의 결과 값을 프로그램으로 돌렺주기 위해서는 return문과 함께 C의 수식을 사용해야 한다. 제어가 return문에 도달할 때, 수식은 평가되고 제어는 다시 원래의 프로그램으로 전달된다. 함수의 복귀값은 수식의 결과이다. 다음 함수를 살펴보자.


        int func1(int var)

        {

           int x;

           return x;

        }


이 함수가 호출될 때 return문까지의 함수의 내용이 실행된다. return은 함수를 마치고 처음에 함수를 호출했던 프로그램으로 x라는 값을 돌려준다. return에는 모든 C의 수식을 사용할 수 있다. 함수는 여러 개의 return문을 가질 수 있다. 그러나 실제로 효과를 나타내는 것은 가장 먼저 실행되는 return문이다. <리스트 5.4>에서 볼 수 있듯이 여러 개의 return문을 사용하는 것은 함수에서 다양한 값을 돌려주는 호과적인 방법이다.


<리스트 5.4> 함수에서 여러 개의 return문을 사용하는 예

    #include <stdio.h>


    int x, y, z;


    int larger_of(int, int);


    main()

    {

       puts("Enter two different integer values: ");

       scanf("%d%d", &x, &y);


       z = larger_of(x, y);


       printf("\nThe larger value is %d.\n", z);


       return 0;

    }


    int larger_of(int a, int b)

    {

       if(a > b)

         return a;

       else

         return b;

    }


-> 입력 / 출력

   Enter two different integer values:
   300
   200
   The larger value is 300.

4.3 함수의 원형
 : 프로그램에는 사용되는 각각의 함수에 대한 함수 원형이 포함되어야 한다. <리스트 5.1>의 4변째중에는 함수 원형이 나타나 있고 앞에서 다른 여러 프로그램의 함수 원형을 보았다. 함수 원형은 무엇이고 왜 사용해야 하는 것일까? 앞의 예제에서, 함수의 원형은 함수의 헤더에 세미콜론을 추가한 것과 같다는 것을 알 수 있다. 함수 헤더와 마찬가지로 함수의 원형에는 함수의 복귀형, 이름, 매개 변수에 대한 내용이 포함된다. 함수 원형이 하는 일은 컴파일러에게 함수의 복귀형, 이름, 매개 변수에 대해서 알려주는 것이다. 컴파일러는 이런 자료를 이용하여 소스 코드에서 함수를 호출할 때마다 함수를 찾아서 함수에 전달되는 인수의 수와 형태를 확인하고 함수가 돌려주는 복귀값이 정확한지 검사할 수 있다.
 만약 이런 과정에서 문제가 발생하면 컴파일러는 에러 메시지를 출력한다. 실제로 함수 원형이 함수의 헤더와 정확히 일치할 필요는 없다. 동일한 형태와 순서의 매개 변수를 사용하고 동일한 개수의 매개 변수를 포함시킨다면 변수의 이름은 달라질 수 있다. 헤더와 원형의 내용이 반드시 일치할 필요는 없다. 그러나 헤더와 원형을 똑같이 작성하면 프로그램을 더욱 이해하기 쉽다. 함수 정의를 마칠 때 함수의 원형을 작성하기 위해서 헤더를 복사해서 에디터에서 제공되는 잘라 붙이기 기능을 사용하면 된다. 마지막에는 반드시 세미콜론을 추가하기 바란다. 함수 원형은 소스 코드의 어디에 위치되어야 하는 것일까? 함수 원형은 main()이 시작되기 전이나 또는 첫 번째 함수가 정의되기 전에 포함되어야 한다. 프로그램을 이해하기 쉽게 하려면 모든 함수원형을 한군데 모아두는 것이 가장 좋다.

5. 함수에 인수 전달하기
 : 함수에 인수를 전달하기 위해서는 함수의 이름 다음에 나타나는 괄호 내에 인수를 포함시 켜야 한다. 인수의 수와 형태는 함수 헤더나 원형에 나타나 있는 것과 일치해야 한다. 예를 들어, 함수가 두 개의 int형 인수를 사용하도록 정의되어 있다면, 더 많거나 적지 않고 데이터형이 다르지 않은 두 개의 int형 인수를 전달해야 한다. 만약 함수에 부적절한 형태나 개수의 인수를 전달하려고 한다면, 컴파일러는 함수 원형에 나타나 있는 내용을 근거로 하여 문제를 찾을 것이다.
 함수가 여러 개의 인수를 사용할 경우 함수 호출에 포함되는 인수는 순서대로 함수의 매개 변수에 할당된다. <그림 5.5>에 나타나듯이 첫 번째 인수는 첫 번째 매개 변수에 대응하고, 두 번째 인수는 두번째 매개 변수에 대응하는 순서로 전달된다.

<그림 5.5> 여러 개의 인수는 순서대로 함수의 매개 변수에 할당된다.

각각의 인수는 C에서 사용할 수 있는 어떤 수식이든지 될 수 있다. 상수, 변수, 산술이나 논리 수식, 또는 심지어 하나의 return값을 가지는 다른 함수를 사용할 수도 있다. 예를 들어, half(), square(), third()가 모두 복귀값을 가지는 함수라면 다음과 같은 문장을 작성할 수 있다.


      x = half ( third ( square ( half ( y ) ) ) );


프로그램은 우선 y라는 인수를 전달하며 함수 half()를 호출한다. half()의 실행이 완료되면 half()의 복귀값을 인수로 사용하여 square()가 호출된다. 다음으로 square()의 복귀값을 인수로 사용하여 third()가 호출된다. 그리고 나서 다시 half()가 호출되며, 이번에는 third()의 복귀값을 인수로 사용하게 된다. 마지막으로 half()의 복귀값은 변수 x에 할당된다. 다음은 이와 같은 내용의 문장을 여러 개로 나눈 것이다.


       a = half(y);

       b = square(a);

       c = third(b);

       x = half(c);


6. 함수의 호출

 : 함수는 두 가지 방법으로 호출할 수 있다. 어떤 함수는 단순히 함수 이름과 인수만을 사용하여 한 문장으로 호출할 수 있다. 만약 함수가 복귀값을 가진다면 그 값은 버려진다.


       wait(12);


두 번째 방법은 복귀값을 생성하는 함수에서만 사용할 수 있다. 이런 함수는 결과적인 값, 즉 복귀값을 전달하므로 C의 수식으로 간주된다. 그래서 C의 수식을 사용할 수 있는 곳이라면 어디에서든지 사용할 수 있다. 앞에서는 이미 복귀값을 생성하며 할당문의 오른쪽에서 사용되는 수식의 예를 보았다. 몇 가지 다른 예를 살펴보도록 하자. 다음 예제에서 half_of()는 함수의 매개 변수이다.


       printf("Half of %d is %d." x, half_of(x));


우선, x를 인수로 하여 함수 half_of()가 호출되고, printf()는 x와 half_of(x)의 결과를 인수로 사용하여 호출된다. 두 번째 예에서는 하나의 수식 네에서 여러 함수를 사용하고 있다.

  

       y = half_of + half_of(z);


여기에서는 half_of()를 두 번 사용하고 있지만, 두 번째 함수는 다른 함수가 될 수도 있을 것이다. 동일한 내용을 여러 문장으로 나누면 다음과 같다.


       a = half_of(x);

       b = half_of(z);

       y = a + b;


마지막 두 예제는 함수의 복귀값을 사용하는 효과적인 방법을 보여준다. 다음은 if문 내에서 사용된 함수이다.


       if (half_of(x) > 10)

       {

          /* 어떤 문장이 될 수 있다 */

       }


함수의 복귀값이 기준에 일치한다면, 즉 half_of()가 10보다 큰 값을 돌려주면 if문은 참이 되고 그 아래에 있는 문장이 실행된다. 그러나 함수의 복귀값이 조건을 만족시키지 않는다면 if문의 내용은 실행되지 않는다. 다음은 더 좋은 예이다.


       if (do_a_process() != OKAY)

       {

          /* 에러 처리 루틴 */

       }


여기서 사용된 예제도 실제 프로그래밍 예제는 아니다. 그러나 이 문장은 함수가 정상적으로 실행되었는지를 확인하기 위해서 복귀값을 사용하는 중요한 예제이다. 만약 정상적이지 않은 결과가 나타나면 if문에서는 에러 처리 루틴이 실행된다. 이것은 파일에서 자료를 읽어들이고, 값을 비교하고, 메모리를 할당하는 경우에 흔히 사용되는 에러 처리 방법이다. 또한, void의 복귀형을 가지는 함수를 수식에서 사용하게 되면 컴파일러는 에러 메시지를 출력할 것이다.


6.1 재귀 용법

 : 재귀 용법(reacquisition)은 함수가 직접적으로나 간접적으로 그 자신을 호출하는 것을 말한다. 간접적인 재귀(indirect reacquisition)는 하나의 함수를 호출하는 다른 어떤 함수가 현재의 함수 내에서 호출되는 경우를 말한다. C에서는 재귀적인 함수의 사용을 허용하는데, 재귀 용법은 가끔 매우 유용하게 사용된다. 예를 들어, 재귀 용법은 숫자의 계승(factorial)을 계산하는 데 유용하다. 숫자 x의 계승은 x!로 표기하는데 다음과 같이 계산할 수 있다.


      x! = x * (x - 1) * (x - 2) * (x - 3) * ... * (2) * 1


x!는 다음과 같이 계산할 수 있다.


      x! = x * (x - 1)!


계산을 한 번 진행하면 (x-1)!은 동일한 방법으로 계산할 수 있을 것이다.


      (x - 1)! = (x - 1) * (x - 2)!


이런 공식을 사용하여 x가 1이 될 때까지 재귀적인 방법으로 계산을 진행하게 된다. <리스트 5.5>에 있는 프로그램은 계승을 계산하기 위해서 재귀적 함수를 사용한다. 프로그램이 unsigned int형을 사용하므로 계승을 구할 숫자값은 8까지만 허용된다. 9이상의 계승의 결과는 정수형 변수의 범위를 벗어난다.


<리스트 5.5> 계승을 계산하기 위한 재귀적 함수의 사용

  #include <stdio.h>


    unsigned int f, x;

    unsigned int factorial(unsigned int a);


    main()

    {

       puts("Enter an integer value between 1 and 8: ");

       scanf("%d", &x);


       if(x > 8 || x < 1)

       {

          printf("Only values from 1 to 8 are acceptable!\n");

       }

       else

       {

          f = factorial(x);

          printf("%u factorial equals %u\n", x, f);

       }


       return 0;

    }


    unsigned int factorial(unsigned int a)

    {

       if(a == 1)

         return 1;

       else

       {

         a *= factorial(a - 1);

         return a;

       }


-> 입력 / 출력


   Enter an integer value between 1 and 8:

   6

   6 factorial equals 720}


7. 함수의 위치

 : 프로그램에서 함수를 어디에 위치시키는 것이 좋을까? 여기에서는 함수를 main()과 같은 파일 내에서 main() 함수의 뒤에 위치시키는 것이 좋다고 알아 두기 바란다. <그림 5.6>에는 함수를 사용하는 프로그램의 기본적인 구조가 나타나 있다. 실제로는 사용자 정의 함수를 main()이 아닌 독립된 소스 코드 파일에 포함시킬 수 있다. 이런 방법은 큰 프로그램을 작성하거나 하나 이상의 프로그램에서 동일한 기능의 함수를 사용하기 원할 때 유용하다.


<그림 5.6> 함수의 원형을 main()의 앞 부분에 위치시키고, 함수 정의 내용을 main()의 뒷 부분에 위치시킨다.



'C 언어' 카테고리의 다른 글

3장 데이터 저장하기 : 변수와 상수  (0) 2019.06.02
4장 문장, 수식, 연산자  (0) 2019.06.02
6장 기본적인 프로그램 제어문  (0) 2019.06.02
7장 입출력의 기초  (0) 2019.06.02
8장 숫자 배열 사용하기  (0) 2019.06.02

+ Recent posts