----------------
title : 씹어먹는 C 언어 - <15 - 3. 일로와봐, 문자열(string) - 문자열 지지고 볶기 & 리터럴>
cat_title : 15 - 3. 일로와봐, 문자열(string) - 문자열 지지고 볶기 & 리터럴
next_page : 43
publish_date : 2010-02-01
tex_title : 문자열 리터럴에 대한 이해
path : /C
--------------


이번 강좌에서는

* 문자열 리터럴(literal)에 대한 이해

* 문자열 다루기 (복사, 합치기, 비교하기)


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

안녕하세요~ 여러분. 문자열의 3 번째 강의 입니다. 아마도 지난번 강의에서 좌절을 느끼신 분들은 아마 이번 강의는 아주 아주 수월하게 이해해 나갈 수 있으리라 믿고 있습니다. 끝이 보이지 않는 C 언어 강좌 이지만 이제 거의 70 ~ 80% 정도를 지나 왔다고 해도 무방 합니다. 문자열이 끝나게 되면 구조체에 대해 다루게 되는데, 구조체가 끝나면 잡다한 것들만 남아서 슝슝슝 지나갈 수 있습니다. 또한 조금만 더 지나면 C 언어에 대한 강좌 보다는 여러가지 프로그램을 만들어 보며 C 언어를 익혀 보는 강좌를 중심으로 진행할 것입니다.

인터넷 강좌 치고 체계적인 것 같죠? 아무튼. 15 - 3 강, 전체 강좌 수로 치면 25 번째 강좌를 시작하겠습니다.

일단, 아래 코드를 실행해봅시다.

```cpp-formatted
/* 문자열 */
#include <stdio.h>
int main() {
  char str[] = "sentence";
  char *pstr = "sentence";

  printf("str : %s \n", str);
  printf("pstr : %s \n", pstr);

  return 0;
}
```

성공적으로 컴파일 하였다면

```exec
str : sentence 
pstr : sentence
```

와 같이 나옵니다.

```cpp-formatted
char str[] = "sentence";
char *pstr = "sentence";
```

일단, 여러분들은 당연하게도 위 두 개의 문장을 보고 이상하다고 생각하셨을 것입니다. 일단 첫번째 문장은 평범한 문장 입니다. `sentence` 라는 문자열을 `str` 이라는 배열에 집어 넣고 있지요. 그런데 두 번째 문장은 말이죠. 상당히 이상합니다. 왜냐하면 일단 *"sentence"* 는 문자열이고, 어떤 변수의 주소값이 아닙니다. `pstr` 는 `char` 형을 가리키는 포인터 이므로 `char` 형 변수의 주소값이 들어가야되기 때문이죠.

그런데 우리는 마치 *"sentence"* 를 특정한 주소값 마냥 사용하고 있습니다. 그런데, 말이죠. *"sentence"* 는 주소값 맞습니다. 그렇다면 무엇의 주소값이죠? 바로, *"sentence"* 라는 문자열이 저장된 주소값 (시작 주소값) 을 말합니다. 정말로 놀랍지 않습니까? 사실 저도 잘 믿기지 않습니다. 만일 믿기지 않는다면 아래 문장을 넣어 실행해 보세요.

```cpp-formatted
printf("%d \n", "sentence");
```

정말로, 특정한 수가 출력됨을 알 수 있습니다. 그렇다면 이 *"sentence"* 는 도대체 뭘까요?

일단, *"sentence"* 의 정체를 먼저 파악하기 전에 다음의 소스 코드를 실행해보시기 바랍니다.

```cpp-formatted
/* 문자열 */
#include <stdio.h>
int main() {
  char str[] = "hello";
  char *pstr = "goodbye";

  str[1] = 'a';
  pstr[1] = 'a';

  return 0;
}
```

성공적으로 컴파일 했어도 실행해 보면 오류가 날 것입니다.

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

Windows 7 의 경우 위와 같은 화면이 나오며 다른 운영체제의 경우 다른 화면이 나올 수 있습니다.

헐.. 왜 오류가 난 것일까요? 일단, `pstr[1] = 'a';` 를 주석 처리한 후 다시 실행해 보면 제대로 실행됨을 알 수 있습니다. 다시말해,

```cpp-formatted
pstr[1] = 'a';
```

가 문제인 것이군요. 그런데 말이죠. 왜 문제가 발생한 것일까요? 맨 위의 예제에서 `pstr` 의 값을 읽기만 하였을 때(`printf` 함수는 값을 읽기만 하지 변경하지 않는다) 정상적으로 실행되었지만 아래에서 `pstr[1] = 'a';` 를 통해 `pstr` 의 값을 변경하였을 때 오류가 출력된 것을 보아 마치 상수 처럼 컴퓨터에서 값을 변경하도록 허락 하지 않는 것 같습니다.


###  리터럴(literal)

프로그래밍 언어에서 **리터럴(literal)**이란, 소스 코드 상에서 고정된 값을 가지는 것을 일컫습니다. 특히, C 언어의 경우 큰 따옴표(`"`) 로 묶인 것들을 **문자열 리터럴(string literal)** 이라 부릅니다.

```cpp-formatted
char *pstr = "goodbye";
printf("why so serious?");
scanf("%c", str[0]);
```

그렇다면 위 3 개의 문장에서 문자열 리터럴은 무엇일까요? 물론, 짐작하였던 대로 `goodbye, why so serious, %c` 모두 리터럴이 됩니다.

컴퓨터는 이러한 리터럴들을 따로 모아서 보관합니다. 즉, 프로그램을 실행하면 메모리 상의 특별한 곳에 `goodbye, why so serious, %c` 와 같은 리터럴들이 쭈르륵 보관되어 있는 공간이 생긴다는 것입니다.\sidenote{좀 더 궁금하신 분들을 위해 추가적으로 설명하자면, 프로그램이 실행되서 메모리에 로드되면, 5 가지 종류의 영역(text segment, data segment, bss segment, heap, stack) 이 존재합니다. 이 때, 텍스트 세그먼트(text segment) 에 프로그램 코드와 상수, 리터럴 등이 여기서 정의됩니다. 왜냐하면 텍스트 세그먼트에 있는 내용들은 읽기만 가능하기 때문이지요. 물론 이 사실은 컴파일러 구현에 따라, 사용하는 운영체제 환경에 따라서 다를 수 있습니다.} 

따라서 `char *pstr = "goodbye";` 을 실행하게 되면 컴퓨터는 *"goodbye 의 시작 주소값 좀 가져와서 `pstr` 에 대입좀 해라"* 라는 의미의 작업을 실행합니다. 따라서, `pstr` 은 *"goodbye"* 라는 리터럴을 가리키고 `printf("%s", pstr)` 을 했을 때 `goodbye` 를 성공적으로 출력할 수 있게 되었던 것이죠.

그런데 말이죠. 아까 위에서 이야기 하였던 리터럴의 조건 기억 하시나요? 아까 분명히 리터럴은 소스 코드 상에서 고정된 값을 가지는 것들 이라고 이야기 했습니다. 다시 말해서 실제 프로그램 실행 중에서도 리터럴의 값은 절대로 변경 되서는 안된다는 것입니다. 

아까도 말했듯이 만일 `hello` 라는 리터럴의 값을 실수로 (물론 내가 했을 수도 있고 컴퓨터의 버그로 그랬을 수 도 있고) `hi` 로 변경하였다면 사용자는 분명히 `str` 에 `hello` 라는 값을 넣으라고 명령했지만 `hi` 가 들어가게 되어 큰 문제를 야기할 수 있게 됩니다.

따라서 리터럴이 보관되는 곳은 **오직 읽기만 가능한 곳** 이 됩니다. 만일 이곳을 함부로 변경하려고 하는 시도가 있다면 바로 프로그램이 강제로 종료되게 됩니다.\sidenote{이 사실 역시 사용하는 운영체제, 환경에 따라 다릅니다.}

그렇기 때문에 우리는 `char str[] = "hello";` 를 했다면 `str` 에 `hello` 가 들어가고 `printf("why so serious?");` 를 했다면 화면에 `why so serious` 가 출력될 것이라고 보장할 수 있다는 것이죠. 왜냐하면 이 모든 문자열들이 "문자열 리터럴" 이라는 이름 하에 메모리 상의 특별한 공간에서 보호 받고 있기 때문입니다.

```cpp-formatted
char *pstr = "goodbye";
pstr[1] = 'a';
```

그럼 위 코드를 다시 살펴봅시다. 우리는 앞서 `goodbye` 역시 문자열 리터럴 이기 때문에 "리터럴 들의 세상 (정확히 말하면 이 곳에는 리터럴들만 있는 것이 아니라 우리가 프로그램 상에서 정의한 상수들도 이곳에 저장됩니다) " 에 저장된다고 했습니다. 그런데 이 곳은 오직 읽기만 가능한 곳이므로 쓰기를 시도 하려고 할 시에 오류를 뿜게 된다고 했습니다. 그런데 말이죠. 제가 무례하게도 `pstr[1] = 'a';` 을 통해 *"리터럴 세상에 저장된 리터럴 goodbye"* 의 값을 변경하려고 했습니다. 따라서 컴퓨터에서 오류를 삑 뿜게 되는 것이죠.

반면에

```cpp-formatted
printf("pstr : %s \n", pstr);
```

와 같이 오직 읽기 작업만을 수행하는 `printf` 의 경우 잘 실행되지요. 왜냐하면 리터럴 세상에 저장된 리터럴들에 쓰기는 불가능 하지만 적어도 읽기는 허용되기 때문입니다.

그렇다면 아래 코드는 어떨까요?

```cpp
  char str[] = "hello";
```

사실은 위 *"hello"* 는 리터럴이라고 부르기 애매합니다. 왜냐하면 위 배열의 정의는 사실 컴파일러에서 아래와 같이 해석되기 때문이지요.

```cpp
  char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};
```

이는 그냥 `str` 이라는 배열에 hello 라는 문자열을 복사하게 될 뿐입니다. 그리고 위 배열은 텍스트 세그먼트가 아니라 스택(stack)이라는 메모리 수정이 가능한 영역에 정의가 됩니다. 따라서 `str` 안의 문자열은 수정이 가능합니다.

```lec-warning
참고적으로 VS 2017 이상에서는 리터럴을 `char*` 가 가리킬 수 없습니다. 반드시 `const char*` 가 가리켜야 하며, 덕분에 리터럴을 수정하는 괴랄한 짓을 컴파일 단에서 막을 수 있습니다.
```

###  문자열 다시 가지고 놀기

C 언어에서 문자열을 다루는 일은 생각보다 불편한 편입니다. 예를 들어서 `int` 형 변수의 경우

```cpp-formatted
int i, j = 0;
i = j + 3;
```

  과 같이 값을 더하는 것이 가능하지만 문자열의 경우

```cpp-formatted
char str1[] = {"abc"};
char str2[] = {"def"};
str1 = str1 + str2;
```

를 한다고 해서 `str1` 이 "abcdef" 가 되는 것이 절대로 아니지요. `str1 + str2` 는 각 배열의 주소값을 더하는 것인데, 이전에도 말했듯이 배열의 이름은 포인터 상수 이기 때문에 대입 연산을 수행시 오류가 나게 됩니다.

뿐만 아니라 다음과 같이 문자열을 비교하는 것도 불가능합니다.

```cpp-formatted
if (str1 == str2) ```

왜냐하면 위 문장의 의미는 "`str1` 의 문자열이 들어있는 메모리 상의 (시작)주소와 `str2` 의 문자열이 들어있는 메모리 상의 (시작) 주소값을 비교해라" 라는 의미의 문장이기 때문입니다. 따라서 역시 우리가 원하던 기능이 실행 될 수가 없습니다. 물론 다음과 같은 문장도 원하는 대로 실행이 되지 않습니다.

```cpp-formatted
if (str1 == "abc") ```

잘 알겠지만 "abc" 은 리터럴 입니다. 즉, `str1` 과 "abc" 를 비교한다는 뜻은 "str1 이 저장된 메모리 상의 주소값과 `abc` 라는 문자열 리터럴이 보관된 메모리 상의 주소값을 비교" 하는 문장이기 때문에 절대로 우리가 원하는 "str1 의 문자열과 `abc` 를 비교한다" 라는 뜻을 가질 수 없습니다.

가장 짜증나는 문제는 문자열을 원하는 대로도 복사를 못한다는 것입니다. 다시말해 `int` 형 변수처럼 원하는 값을 "대입" 할 수 없다는 말입니다. 만일 우리가

```cpp-formatted
str1 = str2;
```

라는 문장을 쓴다라면 "`str1` 에 `str2` 의 값을 대입해라" 라는 문장이 되는데 역시 `str1` 의 값은 바뀔 수 없는 포인터 상수 이기 때문에 오류가 발생하게 됩니다. 여하튼 문자열을 다루는데에는 제약이 너무나 많습니다. 하지만 다행스럽게 함수를 이용해서 그나마 편리하게 다룰 수 있습니다.

일단, 위에서 지적한 내용을 바탕으로 문자열을 자유롭게 다루려면 다음과 같은 함수들이 필요할 것입니다.


* 문자열 내의 총 문자의 수를 세는 함수
* 문자열을 복사하는 함수
* 문자열을 합치는 함수 (즉 더하는)
* 문자열을 비교하는 함수

  제 강좌에서는 위 4 개의 함수들을 모두 구현해 보도록 할 것입니다. (1 번의 경우 [15 -1](http://itguru.tistory.com/29)강에서 한 내용이므로 생략하도록 하겠습니다) 제가 이를 모두 구현하기 전에 여러분들이 한 번 어떻게 하면 만들 수 있는지 생각해 보도록 했으면 합니다.

#### 문자열을 복사하는 함수

문자열을 복사하는 함수는 어떻게 하면 만들 수 있을까요? 우리가 무언가를 작업하는 함수를 만들기 전에 반드시 고려해야 하는 사항들은 다음과 같습니다. (이 사실을 이전 함수 단원에서 이야기 했으면 더 좋았을 것을..)


1. 이 함수는 무슨 작업을 하는가? (자세할 수록 좋다)

2. 함수의 리턴형이 무엇이면 좋을까?

3. 함수의 인자으로는 무엇을 받아야 하는가?


특히 ① 번의 경우 상당히 중요합니다. "무슨 무슨 함수를 만들어야 겠다" 라고 정하지도 않고 무턱대고 함수를 만들다 보면 소스 코드가 상당히 난잡해지고 이해하기 힘들게 됩니다. 이 경우 우리는 말그대로 문자열을 복사하는 함수, 즉 `a` 라는 문자열이 있다면 `a` 문자열의 모든 내용을 `b` 로 복사하는 함수 입니다.

두번째로 함수의 리턴형을 생각해봅시다. 문자열을 복사하는 함수에서 무슨 리턴형이 필요하냐고 물을 수도 있는데 저의 경우 복사가 성공적으로 되었다면 1 을 리턴하도록 만들어보고 싶습니다. 즉 `int` 형의 함수를 만들 것 입니다.

세번째로 함수의 인자로 무엇을 받아야 할 지 생각해 봅시다. 당연하게도 두 개의 문자열을 받아야 하므로 포인터를 사용해야겠죠? 이 때 문자열들은 `char` 형 배열 이기에 `char*` 을 인자로 2 개 가지는 함수를 만들 것 입니다.

```cpp-formatted
/*

int copy_str(char *dest, char *src);

src 의 문자열을 dest 로 복사한다. 단, dest 의 크기가 반드시 src 보다 커야 한다.

*/
int copy_str(char *dest, char *src) {
  while (*src) {
    *dest = *src;
    src++;  // 그 다음 문자를 가리킨다.
    dest++;
  }
  *dest = '\0';

  return 1;
}
```

예를 들어 위 함수를 써먹어 봅시다.

```cpp-formatted
/* copy_str 사용 예제 */
#include <stdio.h>
int copy_str(char *src, char *dest);
int main() {
  char str1[] = "hello";
  char str2[] = "hi";

  printf("복사 이전 : %s \n", str1);

  copy_str(str1, str2);

  printf("복사 이후 : %s \n ", str1);

  return 0;
}
int copy_str(char *dest, char *src) {
  while (*src) {
    *dest = *src;
    src++;
    dest++;
  }

  *dest = '\0';

  return 1;
}
```

성공적으로 컴파일 했다면

```exec
복사 이전 : hello 
복사 이후 : hi 
```

현재 여러분 정도의 수준이 되었다면 위 `copy_str` 함수 정도는 손쉽게 분석할 수 있으리라 믿지만 그래도 만약을 위해서 한 번 설명 해보도록 하겠습니다.

```cpp-formatted
while (*src) {
  *dest = *src;
  src++;
  dest++;
}
```

먼저 `while` 문 부분을 살펴봅시다. `while` 문의 조건이 `*src` 입니다. 뭔 뜻인지 알겠죠? 문자열을 다룰 때 많이 쓰는 방법인데, `NULL` 문자의 값이 0 이므로 `*src` 가 `NULL` 문자에 도달하기 전 까지 `while` 문이 계속 돌아가게 됩니다.

그리고 `*dest = *src` 를 통해서 `src` 의 문자를 `dest` 에 대입하였습니다. 그리고 `src` 와 `dest` 를 각각 1 씩 증가시켰는데.. 포인터의 연산 기억 하시죠? 포인터에 1 을 더하면 단순히 주소값이 1 이 들어가는 것이 아니라 포인터가 가리키는 타입의 크기를 곱한 만큼 증가한다는 사실. 다시말해 배열의 그 다음 원소를 가리킬 수 있다는 것입니다.

```cpp-formatted
*dest = '\0';
```

마지막으로 `dest` 에 '\0' ,  즉 `NULL` 문자를 집어 넣었습니다. 아까 위의 `while` 문에서 `src` 가 `NULL` 이 된다면 `while` 문을 종료해 버렸기 때문에 `src` 에 넣을 틈이 없었는데 마지막에 위와 같이 처리해줌으로써 `dest` 에 `NULL` 문자를 끝부분에 삽입할 수 있게되었습니다.

참고적으로 이야기 하지만 이 함수는 상당히 위험한 편인데 왜냐하면 `dest` 의 크기가 `src` 의 크기보다 큰지 작은지 검사하지 않기 때문입니다. 만일 `dest` 의 크기가 `src` 보다 작다면 메모리의 허락 되지 않는 공간까지 침범하므로 큰 문제를 야기할 수 있습니다.

잠깐만요! 아마도 이 문자열을 복사하는 함수를 만들면서 "굳이 이 함수를 만들어야 되나?" 라고 생각하시는 분들이 있나요? 아마 있겠지요. 저도 그랬으니까요. 보통 이런 생각을 하시는 분들은 다음과 같은 코드를 제안합니다.

```cpp-formatted
char str[100];
str = "abcdefg"; /* str 에 abcdefg 가 복사되지 않을까? */
```

그러나 이 방법으로 컴파일을 하게 되면 아래와 같은 오류를 만나게 됩니다.

```compiler-warning
error C2106: '=' : 왼쪽 피연산자는 l-value이어야 합니다.

```

도대체 왜 그런 것일까요? 아마 리터럴과 배열을 제대로 이해한 사람이라면 쉽게 답을 알 수 있을 것입니다. 일단, `str = "abcdefg"` 라는 문장은 '`str` 에 문자열 리터럴 `abcdefg` 가 위치한 곳의 주소값을 넣어라' 입니다. 그런데 말이죠. 우리가 이전에 배열에 대해 공부한 바로는 배열 이름은 상수 입니다. 즉, 배열의 주소값을 바꿀 수 없다는 것입니다!

따라서, 위와 같은 코드는 상수에 값을 대입하는 의미이기 때문에 오류가 발생하게 됩니다.

그런데 말이죠. 왜 다음 문장은 말이 되는 것일까요?

```cpp-formatted
char str[100] = "abcdefg";
```

이는 단순히 C 언어에서 사용자의 편의를 위해 제공하는 방법이라 생각하면 됩니다. 오직 배열을 정의할 때 사용할 수 있는 방법이죠. 기억하세요!오직 배열을 정의할 때 에만 위 방법을 사용할 수 있습니다. 위처럼 사용하면 우리가 예상하던 대로 `str` 의 각각의 원소에 `a` 부터 `g` 까지 들어가게 됩니다.

#### 문자열을 합치는 함수

  문자열을 합치는 함수라 하면 다음과 같은 작업을 하는 함수를 말합니다.

```cpp
    char str1[100]="hello my name is ";
    char str2[]="Psi";

    stradd(str1, str2);

    // str1 은 "hello my name is Psi" 가 된다.
```

 한 번 만들어보세요.

완성된 소스는 아래와 같습니다.

```cpp-formatted
/*

stradd 함수

dest 에 src 문자열을 끝에 붙인다.
이 때 dest 문자열의 크기를 검사하지 않으므로 src 가 들어갈 수 있는 충분한 크기가
있어야 한다.

*/
int stradd(char *dest, char *src) {
  /* dest 의 끝 부분을 찾는다.*/
  while (*dest) {
    dest++;
  }

  /*
  while 문을 지나고 나면 dest 는 dest 문자열의 NULL 문자를 가리키고 있게 된다.
  이제 src 의 문자열들을 dest 의 NULL 문자 있는 곳 부터 복사해넣는다.
  */
  while (*src) {
    *dest = *src;
    src++;
    dest++;
  }

  /* 마지막으로 dest 에 NULL 추가 (왜냐하면 src 에서 NULL 이 추가 되지
   * 않았으므로) */
  *dest = '\0';

  return 1;
}
```


이제 위 함수를 써먹어 봅시다.

```cpp-formatted
#include <stdio.h>
int stradd(char *dest, char *src);
int main() {
  char str1[100] = "hello my name is ";
  char str2[] = "Psi";

  printf("합치기 이전 : %s \n", str1);

  stradd(str1, str2);

  printf("합친 이후 : %s \n", str1);

  return 0;
}
int stradd(char *dest, char *src) {
  /* dest 의 끝 부분을 찾는다.*/
  while (*dest) {
    dest++;
  }

  /*
  while 문을 지나고 나면 dest 는 dest 문자열의 NULL 문자를 가리키고 있게 된다.
  이제 src 의 문자열들을 dest 의 NULL 문자 있는 곳 부터 복사해넣는다.
  */
  while (*src) {
    *dest = *src;
    src++;
    dest++;
  }

  /* 마지막으로 dest 에 NULL 추가 (왜냐하면 src 에서 NULL 이 추가 되지
   * 않았으므로) */
  *dest = '\0';

  return 1;
}
```

성공적으로 컴파일 했다면

```exec
합치기 이전 : hello my name is  
합친 이후 : hello my name is Psi 
```

역시. 제대로 출력이 됩니다. 일단 `stradd` 의 구조는 단순합니다. `dest` 의 끝에 문자열을 덧붙이기 위해서는 먼저 `dest` 문자열의 끝 부분을 찾아야겠죠? 따라서

```cpp-formatted
while (*dest) {
  dest++;
}
```

를 통해서 `dest` 의 널문자의 위치를 찾습니다. (물론 그 위치는 `dest` 가 가리키고 있겠지요) 이제, 그 널문자가 들어갔던 곳을 포함하여 `dest` 의 끝에 `src` 문자열을 덧쓰면 됩니다. 아래와 같이죠.

```cpp-formatted
while (*src) {
  *dest = *src;
  src++;
  dest++;
}
```

물론 이때도 주의해야 할 점은 `*src` 가 `NULL` 이 되면 `while` 문이 종료되므로 `src` 의 널문자를 복사할 수 없게 됩니다. 따라서 아래와 같이 `dest` 의 끝부분에 `NULL` 문자를 집어 넣어주어야 합니다.

```cpp-formatted
*dest = '\0';
```

자. 그럼 이해가 되셨는지요?

#### 문자열을 비교하는 함수

  문자열을 비교하는 함수라 하면 다음과 같은 작업을 하는 함수를 의미합니다.

```cpp
if(compare(str1, str2))
{
/*
 만일 str1 과 str2 가 같다면 이 부분이 실행되고 아니면 지나갑니다.
참고로 if 문에서 0 이 아닌 값만 들어가면 무조건 참으로 처리되는 사실은 알고 계시죠?
*/
}

```

한 번 만들어보세요.

완성된 소스는 아래와 같습니다.

```cpp-formatted
int compare(char *str1, char *str2) {
  while (*str1) {
    if (*str1 != *str2) {
      return 0;
    }

    str1++;
    str2++;
  }

  if (*str2 == '\0') return 1;

  return 0;
}
```


이제 위 함수를 써먹어 봅시다.

```cpp-formatted
#include <stdio.h>
int compare(char *str1, char *str2);
int main() {
  char str[20] = "hello every1";
  char str2[20] = "hello everyone";
  char str3[20] = "hello every1 hi";
  char str4[20] = "hello every1";

  if (compare(str, str2)) {
    printf("%s 와 %s 는 같다 \n", str, str2);
  } else {
    printf("%s 와 %s 는 다르다 \n", str, str2);
  }

  if (compare(str, str3)) {
    printf("%s 와 %s 는 같다 \n", str, str3);
  } else {
    printf("%s 와 %s 는 다르다 \n", str, str3);
  }

  if (compare(str, str4)) {
    printf("%s 와 %s 는 같다 \n", str, str4);
  } else {
    printf("%s 와 %s 는 다르다 \n", str, str4);
  }

  return 0;
}
int compare(char *str1, char *str2) {
  while (*str1) {
    if (*str1 != *str2) {
      return 0;
    }

    str1++;
    str2++;
  }

  if (*str2 == '\0') return 1;

  return 0;
}
```

성공적으로 컴파일 했다면

```exec
hello every1 와 hello everyone 는 다르다 
hello every1 와 hello every1 hi 는 다르다 
hello every1 와 hello every1 는 같다 
```

`compare` 함수가 어떻게 작동하는지 알아보도록 합시다,.

```cpp-formatted
while (*str1) {
  if (*str1 != *str2) {
    return 0;
  }

  str1++;
  str2++;
}
```


일단 `while` 문에서 `str1` 의 끝에 도달할 때 까지 각 문자들을 비교합니다. 만일 한 문자라도 다르다면 `if` 문에 의해 0 이 리턴되고 함수는 종료됩니다. 그렇지 않다면 `while` 문을 끝가지 통과하게 되죠.

그런데 여기서 끝난 것이 아닙니다. 만일 `str1` 과 `str2` 가 `str1` 부분만 일치하였다면 어떨까요? 다시말해 `str1` 은 "abc" 이지만 `str2` 는 "abcd" 라면? 그렇다면 `while` 문에서 검사할 때 `str1` 이 끝날 때 까지만 검사하므로 `while` 문을 잘 통과하게 됩니다. 따라서 여러분은 `str1` 이 끝났을 때 `str2` 도 끝났는지 확인해볼 필요성이 있습니다.

```cpp-formatted
if (*str2 == '\0') return 1;
```

따라서 위 문장을 추가해줌으로써 우리는 `str2` 가 끝이 났는지 확인할 수 있게 됩니다. 만일 `*str2` 가 \0 이 아니라면, 즉 `str2` 가 끝난 것이 아니라면 `str1` 과 `str2` 는 다른 것이 되므로 함수는 0 을 리턴하게 됩니다. 어때요? 간단하죠? 사실 위에서 설명한 4 개의 함수들만 이용하면 문자열을 이용한 왠만한 작업들은 수행이 가능합니다. 이번 강좌는 이쯤게 끝내도록 하겠습니다. 문자열 함수를 이용해서 문자열들을 적절히 가지고 노는 것은 여러분의 몫입니다. 부디 문자열을 가지고 여러가지 재미있는 프로그램을 만들어보았으면 합니다.

### 생각해보기

#### 문제 1

길이가 최대 100 인 문자열을 하나 입력 받아서 문자열을 역순으로 출력하는 함수를 만들어보세요. (난이도 : 下)
예를 들어서 "abcde" 입력 --> "edcba" 출력

#### 문제 2

길이가 최대 100 인 문자열을 입력 받아서 소문자는 대문자로, 대문자는 소문자로 출력하는 함수를 만들어보세요. (난이도 : 下)
예를 들어서 "aBcDE" 입력 --> "AbCde" 출력

#### 문제 3

두 개의 문자열을 입력 받아서 같다면 "같다", 다르면 "다르다" 라고 출력하는 함수를 만들어보세요. (난이도 : 下)

#### 문제 4

문자열을 두 개 입력 받아서 먼저 입력받은 문자열에서 나중에 입력받은 문자열의 위치를 검색하는 함수를 만들어보세요. 만일 없다면 -1 을 리턴하고 있다면 그 위치를 리턴합니다. (난이도 : 中)

예를 들어먼저 처음 입력한 것이 `I_am_a_boy` 이고, 나중에 입력한 것이 `am` 이였다면 컴퓨터는 `I_am_a_boy` 에서 `am` 의 위치를 찾는다. 이 경우에는 `am` 의 위치는 2 (처음에서 세번째) 이므로 2 를 리턴한다. 만일 `am` 이라는 문자열이 없다면 -1 을 리턴한다.

#### 문제 5

도서 관리 프로그램을 만들어봅시다. 프로그램에는 다음과 같은 기능들이 구현되어 있어야 합니다. (난이도 : 上)

* 책을 새로 추가하는 기능 (책의 총 개수는 100 권이라 하자. 이 때, 각 책의 정보는 제목, 저자의 이름, 출판사로 한다)

* 책의 제목을 검색하면 그 책의 정보가 나와야 한다.

* 위와 마찬가지로 저자, 출판사 검색 기능이 있어야 한다.

* 책을 빌리는 기능.

* 책을 반납하는 기능


##@ chewing-c-end