모두의 코드
씹어먹는 C 언어 - <5. 문자 입력 받기>

이번 강좌에서는...

등을 배우게 됩니다.

씹어먹는 C 언어

지난번 강좌는 잘 이해 되셨는지요? 이번 강좌에서는 제목에서도 볼 수 있듯이 두 가지 내용을 한꺼번에 배우게 됩니다. 바로, 문자를 키보드로 부터 입력을 받는 것이지요. 문자를 입력 받을 수 있다면, 숫자도 당연히 입력 받을 수 있게 됩니다. 즉, 이번 강좌에서는 문자 형식의 변수와 키보드로 부터 입력을 받는 입력에 대해 알아 보도록 하겠습니다.

일단, 컴퓨터에서 문자를 처리하는 방식에 대해 생각해 봅시다. 제가 누누히 말하지만 우리의 컴퓨터는 그다지 똑똑하지 못합니다. 아무리 최신 Intel CPU 를 장착해도 컴퓨터는 단지 0 과 1 만을 처리할 뿐이죠.

따라서, 2 와 3 같은 숫자도 처리하지 못하는데 어떻게 a, b 가, 나, 韓 과 같은 수 많은 문자를 처리할 수 있겠습니까? 하지만, 방법이 있습니다. 이러한 문자들을 숫자에 대응시키는 것입니다. 그런데, 숫자에 대응시킨다면 컴퓨터가 이 것이 숫자인지, 아니면 문자인지 어떻게 알까요? 물론 알 방법은 없습니다. 단지 이 숫자를 '문자' 형태로 사용하거나 '숫자' 형태로 사용하는 것이지요.

문자를 저장하는 변수는 앞에서 살짝 본 적이 있습니다. 바로 char 이지요. intinteger 의 약자였다면 charcharacter 의 약자 입니다. 변수가 등장하면 어김없이 등장하는 아래의 표를 살펴 봅시다.

각 자료형의 데이터 범위를 나타낸 표 입니다.

보시는 것과 같이 char 은 맨 위에 위치해 있으며 크기는 1 바이트 입니다. 또한, 이를 통해 나타낼 수 있는 숫자의 범위를 알려주고 있는데, 이는 -128 부터 127 까지, 256 가지 입니다.

/* 문자를 저장하는 변수  */
#include <stdio.h>
int main() {
  char a;
  a = 'a';

  printf("a 의 값과 들어 있는 문자는? 값 : %d , 문자 : %c \n", a, a);
  return 0;
}

위 소스를 성공적으로 컴파일 했다면

  위와 같이 나옵니다. 일단, 소스를 분석해 보겠습니다.

char a;

  이 부분은 char 형 변수를 선언하는 부분입니다. 기억이 안나시는 분들은 3강을 참조하세요.

a = 'a';

이 부분은 a 라는 변수에 문자 a 를 대입하고 있습니다. 이 때, 모든 문자들은 모두 작은 따옴표로 묶어 주어야 합니다. 만약 작은 따옴표로 묶지 않고 그냥 썼다면

a = a;

C 컴파일러는 이 a 가 변수 a 라고 착각하여 a 라는 변수의 값을 a 라는 변수에 대입하는 문장으로 인식하게 되죠. 따라서 a 에는 아무런 값이 들어있지 않은 쓰레기 값(NULL) 이 되어 나중에 a 라는 문자를 출력해 보았을 때, 이상한 값이 나오게 됩니다.  문자를 대입하는 것도 숫자를 대입하는 것과 동일합니다. 대입 연산자를 이용하면 되죠.

printf("a 의 값과 들어 있는 문자는? 값 : %d , 문자 : %c \n", a, a);

마지막으로, 위 printf 문에 대해 보도록 하겠습니다. 앞에서 말했듯이 컴퓨터는 a 가 문자라는 것 자체를 모른다고 했습니다. 단지 우리가 a 를 문자로 보느냐 아니면 숫자를 보느냐에 따라 달라진다고 했는데, 이 말 뜻을 위 printf 문을 보면 알 수 있습니다.

일단, %da 의 값을 숫자 (정수인 10 진수) 라고 출력하라는 뜻입니다. 그 옆의 %c 는 아마 예상했겠지만 a 의 값을 문자로 출력하라는 뜻이지요. 따라서, %c 에는 a 에 저장되어 있던 문자 'a ' 가 출력되게 됩니다. 그렇다면 %d 에는 무엇이 출력되었을까요? 앞에서 말했지만 컴퓨터는 문자와 숫자를 일대일 대응 시켜서 생각한다고 했습니다. 따라서, %d 에 출력되는 숫자가 바로 a 에 대응되는 숫자를 가리킵니다.

이 때, 각 문자 마다 대응되는 숫자를 아무렇게나 하는 것이 아니라 일정하게 정해져 있는데 현재 우리가 쓰고 있는 컴퓨터에서는 다음과 같이 정의되어 있습니다.

ASCII 표라고 부르며 128 개의 데이터에 어떤 문자가 대응되어 있는지 표로 나와 있습니다.

위 표는 미국 표준 학회(ASA) 에서 정한 아스키(ASCII, American Standard Code for Information Interchange) 코드로 8 비트 데이타를 이용하여 여러 문자에 번호를 붙인 것 입니다. 아까, a 의 숫자 값을 출력하였을 때 97 이 나왔는데 위 표에서 찾아 보면 a 의 값이 97 임을 볼 수 있습니다. 이 때, 위 표의 내용이 0 부터 127 까지 밖에 없는 이유는 위 표준을 정할 당시 그 당시 7 비트 만으로 충분하다고 생각했기 때문이죠. 하지만 IBM 에서 좀 더 많은 종류의 문자가 필요하게 되자 1 비트를 더 추가 시켜서 확장된 아스키 코드(Extended ASCII Code) 를 만들었습니다.

하지만 위 256 개 가지고는 충분하지 못하죠. 왜냐하면 우리 글만 해도 자모음 24 개로 구성되어 있는데, 한 글자당 최대 초성/중성/종성 을 모두 표현해야 합니다. 또한 더욱 심각한 것은 한자와 같은 표의문자의 경우 수만 개가 넘는 한자 데이터들을 가지고 있어야 하는데 이를 256 개 안에 다 표현한다는 것은 불가능하기 때문이죠. 따라서, 컴퓨터가 전세계에 보급되자 좀 더 많은 종류의 문자를 표현해야 한다는 필요성이 대두되었습니다.

결국에는 유니 코드(Unicode) 라는 새로운 형식의 문자 체계를 도입하게 됩니다. 유니코드는 한 문자를 2 바이트로 처리하였습니다. 이는, 기존 아스키의 1 바이트 체계 보다 대응할 수 있는 숫자의 양이 2 배가 늘어난 것이 아니라 256 배가 늘어나, 총 65536 가지의 문자를 처리할 수 있게 되었습니다. 유니코드는 이렇게 막강하여 현재까지 대부분의 언어의 문자 체계를 모두 표시하고도 2만 개 가량 더 대응시킬 수 있는 숫자가 남았다고 합니다. 따라서, 한글, 한자 등 영어를 제외한 대부분의 문자는 2 바이트를 차지합니다.

 scanf 의 도입

/* 섭씨온도를 화씨로 바꾸기  */
#include <stdio.h>
int main() {
  double celsius;  // 섭씨 온도

  printf("섭씨 온도를 화씨 온도로 바꿔주는 프로그램 입니다. \n");
  printf("섭씨 온도를 입력해 주세요 : ");
  scanf("%lf", &celsius);  // 섭씨 온도를 입력 받는다.

  printf("섭씨 %f 도는 화씨로 %f 도 입니다 \n", celsius, 9 * celsius / 5 + 32);

  return 0;
}

위 소스를 성공적으로 컴파일 했다면 아래와 같이 나옵니다. 참고로 경고가 1 개 정도 나올 수 있는데, 이는 scanf 의 보안 문제 때문입니다. scanfscanf_s 로 바꾸면 되지만 지금 이 수준에서 보안문제에 크게 신경 쓸 것은 없으므로 그냥 scanf 로 하셔도 됩니다.

이 때, 원하는 숫자를 쓴 후 엔터를 누른다면 (예 : 100)

 위와 같이 섭씨가 화씨 온도로 변경된 값이 출력됩니다. 와우! 드디어 쓸만한 프로그램을 처음으로 만들어 보게 된 것 같군요. 소스 코드를 찬찬히 살펴 보도록 합시다.

double celsius;  // 섭씨 온도

일단, celsius 라는 double 형 변수를 선언하였습니다. 변수의 이름을 종전의 a , b 에서 celsius 라고 한 이유는 좀 더 이해하기 편하기 때문이죠. 좋은 소스 코드의 조건은 다른 사람이 이해하기 쉬운 소스 코드 이고, 다른 사람이 이해하기 쉬운 소스코드는 기본적으로 변수 이름을 보고도 변수를 한 눈에 파악하기 쉽게 만드는 것입니다.

scanf("%lf", &celsius);  // 섭씨 온도를 입력 받는다.

이제, 새로운 것이 등장하였군요. printf 에 이어 등장한 scanf 군. printf 가 화면에 결과를 출력해 주는 함수였다면, scanf 는 화면(키보드) 로 부터 결과를 받아들이는 입력 함수 입니다. 이렇게 흔히 printfscanf 를 가리켜 입출력함수라 하죠. 이 때, scanf 함수는 우리가 어떠한 입력을 하기 전까지 계속 기다립니다. 또한, 입력을 할 때 엔터를 눌러야지만 입력으로 처리됩니다.

scanf 와  printf 는 이름도 비수무리 할 뿐더러, 사용하는 방법도 비슷합니다. printf 에서 각 변수를 출력할 포맷(%d, %f, %c 등) 을 변수마다 다르게 하는 것처럼 scanf 도 각 변수의 타입마다 입력받는 포맷을 달리 해야 합니다.

위 경우 처럼 double 형의 변수를 입력 받으려면 %lf (소문자 LF 이다, if 가 아니다) 로 해야 합니다. 그런데, printf 보다 조금 까다로운 점은 printfdouble 이나 float 모두 %f 로 출력하지만 이에 경우 float%f 로 무조건 입력 받아야 한다는 점입니다.

마찬가지로 double 형 변수도 무조건 %lf  로만 입력 받아야 합니다. 그 외에도, printf 는 정수형 변수는 모두 %d 로 출력 가능했던 반면에 scanf 는 각 자료형 마다 포맷이 다 정해져 있습니다. 아래 예제에서 잠시 scanf 의 포맷 들에 대해 정리해 보도록 하겠습니다

printf("섭씨 %f 도는 화씨로 %f 도 입니다 \n", celsius, 9 * celsius / 5 + 32);

  마지막으로 위 프로그램의 중요한 부분을 살펴보자. 바로 이 부분에서 섭씨와 화씨의 환산 작업이 이루어 진다. 참고로, 화씨와 섭씨의 변환 공식은 아래와 같습니다.

$$C  \cdot \frac{9}{5} + 32 = F$$

따라서, 이 공식을 그대로 C 언어 수식을 바꾼 것이 9 * celsius / 5 + 32 인 것입니다. 곱셈과 나눗셈의 우선순위가 높으므로 9 * celsius / 5 가 먼저 계산 된 후 32 가 더해지므로 위의 식과  일치합니다. 따라서, 결국 printf 의 두번째 %f 부분에는 위 계산된 화씨의 값이 들어가게 됩니다.

/* scanf 총 정리  */
#include <stdio.h>
int main() {
  char ch;  // 문자

  short sh;  // 정수
  int i;
  long lo;

  float fl;  // 실수
  double du;

  printf("char 형 변수 입력 : ");
  scanf("%c", &ch);

  printf("short 형 변수 입력 : ");
  scanf("%hd", &sh);
  printf("int 형 변수 입력 : ");
  scanf("%d", &i);
  printf("long 형 변수 입력 : ");
  scanf("%ld", &lo);

  printf("float 형 변수 입력 : ");
  scanf("%f", &fl);
  printf("double 형 변수 입력 : ");
  scanf("%lf", &du);

  printf("char : %c , short : %d , int : %d ", ch, sh, i);
  printf("long : %d , float : %f, double : %f \n", lo, fl, du);
  return 0;
}

성공적으로 컴파일 후 (경고가 6 개 정도 나올 수 있는데 무시하세요^^)

printf("char 형 변수 입력 : ");
scanf("%c", &ch);

일단, 제일 먼저 문자를 입력 받는 부분을 봅시다. 예전에도 이야기 했지만 한글은 2 바이트를 차지하기 때문에 최대 1 바이트를 차지하는 char 형 변수인 ch 에 한글을 치면 오류가 납니다. 이와 같이 허용된 메모리 이상에 데이터를 집어넣어 발생하는 오류를 버퍼 오버플로우(Buffer Overflow) 라고 하며 보안 상 매우 취약합니다.

뿐만 아니라 근처의 데이터가 손상됨에 따라 큰 문제가 발생하게 될 수 도 있습니다.  따라서, 여러분들은 버퍼 오버플로우가 일어나지 않게 허용된 데이타 이상을 집어넣는지 안집어 넣는지 검사할 필요성이 있습니다.

또한 앞으로 우리가 char 형 변수를 선언할 때 에는 '이 사람이 문자를 보관하는 변수를 선언하는 구나' 라고 생각하도록 합시다. 왜냐하면 보통 수 데이타를 보관하는 변수로는 int 를 쓰지 char 을 잘 쓰지 않을 뿐더러 char 이름도 character 에서 따왔을 만큼 문자와 무언가 관련이 있기 때문이죠.

printf("short 형 변수 입력 : ");
scanf("%hd", &sh);
printf("int 형 변수 입력 : ");
scanf("%d", &i);
printf("long 형 변수 입력 : ");
scanf("%ld", &lo);

이 부분은 여러분들이 무난하게 이해하실 수 있으리라 봅니다. 단지 포맷에 %hd, %d, %ld 로 다른 것 뿐이지요. 참고로 short 형이나 long 형은 아직 다루지는 않았지만 int 와 똑같은 계열의 정수형 변수라고 생각하시면 됩니다.

printf("float 형 변수 입력 : ");
scanf("%f", &fl);
printf("double 형 변수 입력 : ");
scanf("%lf", &du);

마찬가지로 float 형에서는 %f 로, double 형에서는 %lf 로 사용한다는 것을 기억하시기 바랍니다.

이번 강좌는 지난번 강좌보다는 조금 짧습니다. 하지만 이번 강좌를 통해 응용할 수 있는 것들이 무궁 무진해졌습니다. 일단, 기본적으로 연습하실 것은 단위 환산 프로그램을 만들어 보세요! 아니면, 금리와 원금을 입력 받아서 일정 개월 후의 상환할 돈이라 든지 등등... 수 많은 프로그램을 만들 수 있습니다. 지금, 이러한 것들을 만들 수 있는 모든 도구들은 준비되어 있습니다. 이제 여러분이 스스로 창작할 세계가 남아 있을 뿐입니다.

생각 해보기

문제 1

앞서 섭씨를 화씨로 바꿀 때 9 * celsius / 5 + 32 라고 하였습니다. 만약에 이를 9 / 5 * celcius + 32 로 바꾸면 결과가 달라질까요?

강좌를 보다가 조금이라도 궁금한 것이나 이상한 점이 있다면꼭 댓글을 남겨주시기 바랍니다. 그 외에도 강좌에 관련된 것이라면 어떠한 것도 질문해 주셔도 상관 없습니다. 생각해 볼 문제도 정 모르겠다면 댓글을 달아주세요.

현재 여러분이 보신 강좌는 <<씹어먹는 C 언어 - <5. 문자 입력 받기>>> 입니다. 이번 강좌의 모든 예제들의 코드를 보지 않고 짤 수준까지 강좌를 읽어 보시기 전까지 다음 강좌로 넘어가지 말아주세요


 다음 강좌 보러가기
프로필 사진 없음
댓글에 글쓴이에게 큰 힘이 됩니다