----------------
title : 씹어먹는 C 언어 - <5. 문자 입력 받기>
cat_title : 5. 문자 입력 받기
next_page : 10
publish_date : 2009-04-27
path : /C
chapter : 문자 입력 받기
--------------


이번 강좌에서는...

* 문자를 저장하는 변수

* `scanf` 의 사용

* 섭씨 → 화씨 환산 프로그램

등을 배우게 됩니다.

![씹어먹는 C 언어](/img/ChewingClogo.png)


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

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

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

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


![각 자료형의 데이터 범위를 나타낸 표 입니다.](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile22.uf.tistory.com%2Fimage%2F1126BC1149F5682ED85700)

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

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

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

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

```exec
a 의 값과 들어 있는 문자는? 값 : 97 , 문자 : a 
```

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

```cpp-formatted
char a;
```

이 부분은 `char` 형 변수를 선언하는 부분입니다. 기억이 안나시는 분들은 [3강](http://itguru.tistory.com/?page=2)을 참조하세요.

```cpp-formatted
a = 'a';
```


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

```cpp-formatted
a = a;
```

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

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

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

일단, `%d` 는 `a` 의 값을 숫자 (정수인 10 진수) 라고 출력하라는 뜻입니다. 그 옆의 `%c` 는 아마 예상했겠지만 `a` 의 값을 문자로 출력하라는 뜻이지요. 따라서, `%c` 에는 `a` 에 저장되어 있던 문자 'a' 가 출력되게 됩니다.

그렇다면 `%d` 에는 무엇이 출력되었을까요? 앞에서 말했지만 컴퓨터는 문자와 숫자를 일대일 대응 시켜서 생각한다고 했습니다. 따라서, `%d` 에 출력되는 숫자가 바로 `a` 에 대응되는 숫자를 가리킵니다.

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


![ASCII 표라고 부르며 128 개의 데이터에 어떤 문자가 대응되어 있는지 표로 나와 있습니다. ](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile21.uf.tistory.com%2Fimage%2F16506D0949F56DF8DED1B5)

위 표는 미국 표준 학회(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)** 라는 새로운 형식의 문자 체계를 도입하게 됩니다. 유니코드는 한 문자를 1 에서 4 바이트까지 다양한 길이로 처리합니다. 이는, 기존 아스키 코드의 체계를 유지하면서, 새로운 문자들을 추가하기 위함입니다. 여러분은 아직 유니코드를 직접 다룰 일은 없기 때문에 여기서는 무시하셔도 괜찮습니다.

###  scanf 의 도입

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

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

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

  return 0;
}
```

위 소스를 성공적으로 컴파일 했다면 아래와 같이 나옵니다. 참고로 `scanf_s` 를 사용하라며 컴파일 되지 않는다면 [여기](https://bymakers.tistory.com/6) 에 소개된 방법으로 해당 메세지를 끌 수 있습니다.\sidenote{scanf_s 를 사용하라는 의미는 scanf 가 입력받는 데이터의 크기를 확인하지 않기 때문에 버퍼 오버플로우 (입력받는 데이터의 크기가 준비된 공간보다 큰 문제) 가 발생할 수 있기 때문입니다. 이 문제에 대해서는 나중에 다루어보도록 하겠습니다.}

![](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile5.uf.tistory.com%2Fimage%2F20064E1D49F5AD9E3974E5)

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

![](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile24.uf.tistory.com%2Fimage%2F1950C20949F5AE667BA795)

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

```cpp-formatted
double celsius;  // 섭씨 온도
```

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

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

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

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

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

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

```cpp-formatted
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` 부분에는 위 계산된 화씨의 값이 들어가게 됩니다.

```cpp-formatted
/* 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 : %ld , float : %f, double : %f \n", lo, fl, du);
  return 0;
}
```

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

![](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile22.uf.tistory.com%2Fimage%2F1806421D49F5BFD8C959E0)


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

일단, 제일 먼저 문자를 입력 받는 부분을 봅시다. 예전에도 이야기 했지만 한글은 2 바이트 이상을 차지하기 때문에 최대 1 바이트를 차지하는 `char` 형 변수인 `ch` 에 한글을 치면 오류가 납니다. 이와 같이 허용된 메모리 이상에 데이터를 집어넣어 발생하는 오류를 **버퍼 오버플로우(Buffer Overflow)** 라고 하며 보안 상 매우 취약합니다.\sidenote{버퍼 오버플로우를 이용해서 공격자들이 프로그램의 정상적인 코드가 아니라, 자신들이 원하는 코드가 실행될 수 있도록 조종할 수 있습니다.} 뿐만 아니라 근처의 데이터가 손상됨에 따라 큰 문제가 발생하게 될 수 도 있습니다.  따라서, 여러분들은 버퍼 오버플로우가 일어나지 않게 허용된 데이터 이상을 집어넣는지 안집어 넣는지 검사해야 합니다.

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

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


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

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

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

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

### 생각 해보기

#### 문제 1

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

```lec-summary
* `char` 은 1 바이트 정수를 저장하는 타입으로, 주로 문자를 저장하는데 사용됩니다.
* 각 문자들은 아스키 테이블이란 표를 통해 특정 정수와 대응되어 있습니다. 예를 들어서 65 는 알파벳 A 와 대응됩니다.
* `scanf` 를 통해 사용자로 부터 데이터를 받을 수 있습니다.
* `%c` 는 문자, `%d` 는 정수, `%f` 는 `float`, `%lf` 는 `double` 을 받습니다.
```

##@ chewing-c-end