모두의 코드
씹어먹는 C 언어 - <15 - 3. 일로와봐, 문자열(string) - 문자열 지지고 볶기 & 리터럴>

이번 강좌에서는

씹어먹는 C 언어

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

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

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

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

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

  return 0;
}

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

char str[] = "sentence";
char *pstr = "sentence";

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

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

printf("%d \n", "sentence");

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

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

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

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

  return 0;
}

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

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

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

pstr[1] = 'a';

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

 리터럴(literal)

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

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

그렇다면 위 4 개의 문장에서 문자열 리터럴은 무엇일까요? 물론, 짐작하였던 대로 hello, goodbye, why so serious, %c 모두 리터럴이 됩니다. 왜냐하면 모두 큰따옴표로 묶여져 있잖아요? 그런데 왜 hello, goodbye, why so serious, %c 처럼 큰따옴표로 둘러쌓인 것들을 왜 리터럴이라 부르는 것일까요?

답은 간단합니다. 이들은 소스 코드 상에서 고정된 값을 가지기 때문이죠. 예를 들어서 hello 가 소스 코드 상에서 고정된 값을 가지지 않는 다고 해봅시다. 만일 내가 아래와 같은 문장을 이용했다고 하면

char str[] = "hello";

실제 프로그램에서는 strhello 가 아닌 다른 값 - 예를 들어 hi 가 들어갈 수 도 있다는 것입니다.

이는 마치....

int i, j;
// 잡다한 작업들...
i = j;

위와 같은 코드에서 맨 마지막 문장이 i 에 3 을 대입되기를 기대하는 것과 같은 것입니다.

하지만 다행스럽게도 hello 는 리터럴이기 때문에 우리가 char str[] = "hello"; 라고 쓴 이상 컴퓨터는 str 이라는 배열에 hello 라는 문자열을 대입할 것입니다. 마찬가지로 why so serious 의 경우에도 리터럴 이기 때문에 prinf 가 이상하지 않은 이상 컴퓨터는 why so serious 를 화면에 출력할 것입니다.

컴퓨터는 이러한 리터럴들을 따로 모아서 보관합니다. 즉, 프로그램을 실행하면 메모리 상의 특별한 곳에 hello, goodbye, why so serious, %c 와 같은 리터럴들이 쭈르륵 보관되어 있는 공간이 생긴다는 것입니다.

그러다가 컴퓨터가 char str[] = "hello"; 라는 문장을 실행하게 되면 컴퓨터는 "야, 리터럴 있는 곳에서 첫번째 리터럴 좀 가져와서 str 에 대입좀 해라" 와 같은 명령을 실행하게 되는 것이지요.

마찬가지로 char *pstr = "goodbye"; 을 실행하게 되면 컴퓨터는 "야, 리터럴 있는 곳에서 두번째 리터럴 (물론 두번째가 아닐 수도 있지만 여기선 예시의 단순화를 위해서..) 의 주소값 좀 가져와서 pstr 에 대입좀 해라" 라는 의미의 작업을 실행합니다. 따라서, pstr 은 "goodbye" 라는 리터럴을 가리키고 printf("%s", pstr) 을 했을 때 goodbye 를 성공적으로 출력할 수 있게 되었던 것이죠.

그런데 말이죠. 아까 위에서 이야기 하였던 리터럴의 조건 기억 하시나요? 아까 분명히 리터럴은 소스 코드 상에서 고정된 값을 가지는 것들 이라고 이야기 했습니다. 다시 말해서 실제 프로그램 실행 중에서도 리터럴의 값은 절대로 변경 되서는 안된다는 것입니다. 아까도 말했듯이 만일 hello 라는 리터럴의 값을 실수로 (물론 내가 했을 수도 있고 컴퓨터의 버그로 그랬을 수 도 있고) hi 로 변경하였다면 사용자는 분명히 strhello 라는 값을 넣으라고 명령했지만 hi 가 들어가게 되어 큰 문제를 야기할 수 있게 됩니다.

따라서 리터럴이 보관되는 곳은 "오직 읽기만 가능한 곳" 이 됩니다. 다시 말해 이곳에 한 번 저장된 값은 죽었다 깨어나도 영원이 동일하게 유지가 되는 것입니다. 만일 이곳을 함부로 변경하려고 하는 시도가 있다면 바로 오류를 삑 출력하게 되죠. 그렇기 때문에 우리는 char str[] = "hello"; 를 했다면 strhello 가 들어가고 printf("why so serious?"); 를 했다면 화면에 why so serious 가 출력될 것이라고 보장할 수 있다는 것이죠. 왜냐하면 이 모든 문자열들이 "문자열 리터럴" 이라는 이름 하에 메모리 상의 특별한 공간에서 철저히 보호 받고 있기 때문입니다.

char *pstr = "goodbye";
pstr[1] = 'a';

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

  반면에

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

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

사실 위 리터럴들을 굳이 비유하자면 다음과 같습니다.

옛날에 부자가 있었습니다. 이 부자는 돈이 너무나 많아서 금고에 보관하려고 하는데, 돈을 넣다 보니 금고가 200 개나 필요하게 되었습니다. 그런데 이 금고의 비밀번호를 모두 똑같게 하면 안되니까 이 금고에 비밀번호를 모두 다르게 하려고 합니다. 하지만 200 개의 금고의 비밀번호를 막상 정하고 나니 이를 어디에 기록할 곳이 없는 것입니다. 종이에 써놓기도 조금 위험하고, 그렇다고 컴퓨터에 저장하기에는 해킹에 위협이 있고... 그래서 부자는 알바생 200 명(== 리터럴) 을 데리고 와서 한 명씩 자기 금고의 비밀번호를 알려주었습니다.

그리고는 이 알바생들을 수용소(== 리터럴이 저장되는 영역) 에 가두어놓았지요. 물론 수용소에는 어떤 사람도 출입할 수 없습니다. 왜냐하면 어떤 이상한 사람이 들어와 알바생과 이야기를 나누다가 알바생이 비밀번호를 까먹으면, 아니 비밀번호를 조금 이라도 잊어 버리면 금고의 문을 절대로 열 수 없게 되기 때문이죠. 따라서 이 수용소는 철통 보안 24 시간 내내 감시되어야 합니다 (== 리터럴이 저장되는 영역에는 쓰기가 불가능 하다). 하지만 부자가 금고문을 열어야 할 때 에는 반드시 알바생을 불러서 비밀번호를 알아야 문을 열어야 겠죠? (== 리터럴은 오직 읽기만 가능하다)

 문자열 다시 가지고 놀기

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

int i, j = 0;
i = j + 3;

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

char str1[] = {"abc"};
char str2[] = {"def"};
str1 = str1 + str2;

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

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

if (str1 == str2) 

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

if (str1 == "abc")

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

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

str1 = str2;

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

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

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

문자열을 복사하는 함수

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

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

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

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

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

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

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

/*

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

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

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

  return 1;
}

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

/* copy_str 사용 예제 */
#include <stdio.h>
char 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;
}
char copy_str(char *dest, char *src) {
  while (*src) {
    *dest = *src;
    src++;
    dest++;
  }

  *dest = '\0';

  return 1;
}

  성공적으로 컴파일 했다면

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

while (*src) {
  *dest = *src;
  src++;
  dest++;
}

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

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

*dest = '\0';

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

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

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

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

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

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

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

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

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

char str[100] = "abcdefg";

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

문자열을 합치는 함수

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

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

    stradd(str1, str2);

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

 한 번 만들어보세요.

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

/*

stradd 함수

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

*/
char 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;
}

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

#include <stdio.h>
char 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;
}
char 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;
}

  성공적으로 컴파일 했다면

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

while (*dest) {
  dest++;
}

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

while (*src) {
  *dest = *src;
  src++;
  dest++;
}

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

*dest = '\0';

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

문자열을 비교하는 함수

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

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

한 번 만들어보세요.

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

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

    str1++;
    str2++;
  }

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

  return 0;
}

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

#include <stdio.h>
char 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;
}
char compare(char *str1, char *str2) {
  while (*str1) {
    if (*str1 != *str2) {
      return 0;
    }

    str1++;
    str2++;
  }

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

  return 0;
}

성공적으로 컴파일 했다면

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

while (*str1) {
  if (*str1 != *str2) {
    return 0;
  }

  str1++;
  str2++;
}

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

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

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

따라서 위 문장을 추가해줌으로써 우리는 str2 가 끝이 났는지 확인할 수 있게 됩니다. 만일 *str2 가 \0 이 아니라면, 즉 str2 가 끝난 것이 아니라면 str1str2 는 다른 것이 되므로 함수는 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

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

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

현재 여러분이 보신 강좌는 <씹어먹는 C 언어 - <15 - 3. 일로와봐, 문자열(string) - 문자열 지지고 볶기 & 리터럴>>> 입니다. 이번 강좌의모든 예제들의 코드를 보지 않고 짤 수준까지 강좌를 읽어 보시기 전까지 다음 강좌로 넘어가지 말아주세요


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