12-1-라.문자열 검색

문자열 검색 함수는 문자열 중 특정 문자나 부분 문자열의 위치를 찾아 주는데 다음과 같은 것들이 있다. 원형이 대체로 비슷한데 첫 번째 인수로 검색 대상 문자열을 주고 두 번째 인수로 검색할 문자(열)을 주며 리턴값은 모두 문자형 포인터이다.

 

char *strchr(const char *string, int c);

char *strrchr(const char *string, int c);

char *strstr(const char *string, const char *strSearch);

char *strpbrk(const char *string, const char *strCharSet );

char *strtok(char *strToken, const char *strDelimit);

 

strchr 함수는 문자열중에 c라는 문자가 있는지를 찾아 그 포인터를 리턴한다. str이 "notebook"일 때 strchr(str,'b') 함수는 다음과 같이 동작한다.

문자형 포인터를 리턴하므로 이 번지를 대입한 후 검색된 위치에 대해 어떤 작업을 할 수 있다. 다음 코드는 "notebook" 문자열에서 b 문자를 찾아 c로 바꾼다. 결국 str은 "notecook"이 될 것이다.

 

char str[]="notebook";

char *ptr=strchr(str,'b');

*ptr='c';

 

만약 지정된 문자가 발견되지 않으면 strchr 함수는 NULL을 리턴하는데 이때 NULL은 찾는 대상이 없다는 뜻이다. strchr 함수뿐만 아니라 모든 문자열 검색함수들은 찾는 대상이 없으면 항상 NULL을 리턴하도록 되어 있다. 문자열중 특정 문자의 위치는 관심없고 단지 문자의 포함 여부만 알고 싶을 때는 리턴값이 NULL인지 아닌지만 비교하면 된다.

 

if (strchr(Name,'a')!=NULL) {

     puts("이름에 a문자가 포함되어 있군요.");

}

 

strchr 함수는 항상 주어진 문자열의 선두에서부터 문자를 찾는다. 이에 비해 strrchr 함수는 문자열의 뒤에서부터 문자를 검색하는데 함수명에 포함된 r은 rear, 즉 뒤쪽이라는 뜻이다. str이 "notebook"일 때 strchr 함수로 'o'를 찾는 것과 strrchr 함수로 "o"를 찾은 결과가 달라진다.

strchr 함수는 문자열 처음부터 'o'를 찾으므로 str[1]에서 'o'를 찾아내지만 strrchr 함수는 끝에서부터 검색하므로 str[6]에서 'o'를 찾을 것이다. 문자열내에 같은 문자가 둘 이상 있을 때는 검색 방향에 따라 찾는 문자가 달라진다. 다음 예제는 임의의 문자열에 a문자가 몇개 있는지 세어 보고 그 결과를 출력한다. strchr 함수는 문자가 발견된 포인터를 리턴하는데 이 번지 이후부터 반복적으로 strchr 함수를 호출하면 특정 문자의 개수를 구할 수 있다.

 

: strchr

#include <Turboc.h>

#include <string.h>

 

void main(void)

{

     char str[256];

     char *ptr;

     int count=0;

 

     printf("아무 문자나 입력하세요(공백없이 최대 255문자) ");

     scanf("%s",str);

     for (ptr=str;;) {

          ptr=strchr(ptr,'a');

          if (ptr == NULL) {

              break;

          }

          count++;

          ptr++;

     }

 

     printf("입력한 문자열에는 a가 %d개 있습니다.\n",count);

}

 

strstr 함수는 문자열에서 부분 문자열을 찾는다. str이 "Korea/Japan WorldCup"일 때 strstr(str, "World") 함수는 다음과 같이 문자열을 검색한다. 부분 문자열을 구성하는 일련의 문자들이 연속적으로 발견될 때 그 시작 번지를 리턴한다.

strcmp 함수는 두 문자열이 정확히 같은지 비교하는데 비해 strstr 함수는 부분 문자열의 포함 여부를 조사할 때 흔히 많이 사용된다. 가령 language라는 문자열이 회화 가능 언어에 대한 정보를 가지고 있다고 하자. 이 사람이 영어를 할 수 있는지 조사하려면 다음과 같이 이 문자열을 비교할 수 있다.

 

if (strcmp(language,"영어") != NULL) { 영어 가능함 }

 

이 비교문은 language라는 문자열이 정확하게 "영어"라고 되어 있는지를 조사한다. 만약 어떤 사람은 영어도 가능하고 일본어도 가능해서 "영어, 일본어"라고 썼다면 strcmp로 비교해서는 정확한 결과를 구할 수 없다. "영어"와 "영어, 일본어"는 완전히 다른 문자열이다. 두 문자열이 정확하게 같은지 비교해서는 안되며 부분 문자열이 포함되어 있는지를 조사해야 하는데 이럴 때 바로 strstr 함수가 사용된다.

 

if (strstr(language,"영어") != NULL) { 영어 가능함 }

 

strstr 함수는 부분 문자열을 검색하므로 "영어, 일본어"는 물론이고 "일본어, 영어"와 같이 순서를 바꿔 써도 이 문자열이 포함되어 있는지 검색할 수 있다. 요컨데 strcmp는 두 문자열이 정확하게 같은지를 비교하는 것이고 strstr은 문자열중에 특정 내용이 포함되어 있는지를 조사하는 것이다. 물론 이 함수를 쓰더라도 "영어 절대 못함", "난 영어가 미워"라는 문자열도 영어를 잘 하는 것으로 오판하는 맹점이 있는데 그래서 코드화가 필요한 것이다.

strpbrk 함수는 문자열 검색 함수 중에 가장 복잡하며 사용 빈도도 낮지만 잘 알아 두면 여러 번 검색해야 하는 작업을 간단하게 끝낼 수 있다. 이 함수는 첫 번째 인수로 주어진 문자열에서 두 번째 인수로 주어진 문자열에 속해 있는 문자 중 가장 먼저 발견된 문자를 찾아 그 번지를 리턴한다. 이게 도대체 무슨 뜻인지 얼른 감이 안 올텐데 예를 보면 쉽게 이해할 수 있다.

 

char str[]="Four score and seven years ago";

char *ptr=strpbrk(str,"def");

 

str에 긴 문자열이 들어 있는데 이 문자열 중에서 d나 e나 f중 가장 먼저 발견되는 문자를 찾아 ptr에 대입한다. 이 경우 str[9]에 있는 'e'가 검색될 것이다. 문자열에서 여러 개의 문자 중 하나를 찾고자 할 때 이 함수를 사용하는데 strchr을 여러 번 호출할 필요없이 찾고자 하는 문자의 집합을 문자열로 만든 후 strpbrk 함수만 호출하면 된다.

몇 가지 예를 더 들어 보자. strpbrk(str,"aeiouAEIOU")는 str 문자열에서 가장 먼저 발견되는 모음을 찾을 것이며 strpbrk(str,"0123456789")는 str 문자열에서 가장 먼저 발견되는 숫자를 찾는다. 만약 원하는 문자가 하나도 발견되지 않으면 NULL을 리턴한다. 이 함수를 사용하면 문자열에 특정 문자군이 포함되어 있는지를 쉽게 조사할 수 있으며 반복적으로 호출하면 문자들의 출현 회수를 조사할 수도 있다.

strtok 함수는 문자열을 토큰으로 잘라낸다. 예를 들어 "서울/대전/대구/부산" 문자열을 "/" 구분자로 자르면 서울, 대전, 대구, 부산 4개의 문자열로 분할할 수 있다. 첫 번째 인수로 잘라낼 문자열을 주며 두 번째 인수로 구분자를 구성하는 문자열을 준다. 구분자는 한 문자열에 여러 개를 지정할 수 있는데 /와 :그리고 ,가 토큰 구분자라면 "/:,"를 준다. 일단 예제를 보자.

 

: strtok

#include <Turboc.h>

#include <string.h>

 

void main()

{

     char str[]="I am a boy,you are a girl";

     char *p;

 

     p=strtok(str," ,");

     while (p!= NULL) {

          puts(p);

          p=strtok(NULL," ,");

     }

}

 

이 예제는 str 배열의 문자열에서 공백과 콤마를 구분자로 하는 단어를 추출하여 출력한다. 실행 결과는 다음과 같다.

 

I

am

a

boy

you

are

a

girl

 

strtok 함수는 최초 호출될 때 문자열의 첫 번째 토큰을 찾고 두 번째 토큰 위치를 NULL문자로 만든 후 토큰의 포인터를 리턴한다. 검색한 토큰을 널 종료 문자열로 만들어 주므로 strtok가 리턴하는 포인터를 바로 출력하거나 별도의 버퍼에 복사하면 분리된 토큰을 얻을 수 있다. 이때 strtok 함수는 중간 검색 결과를 자신의 정적변수에 저장해 놓는데 검색을 계속 하려면 첫 번째 인수를 NULL로 전달하면 된다. 더 이상 토큰이 발견되지 않으면 NULL을 리턴하므로 strtok(NULL, 구분자)를 반복적으로 호출하면 문자열을 구성하는 모든 토큰을 찾을 수 있다. 매 검색시마다 구분자를 바꿔 가며 검색하는 것도 가능하다.

strtok 함수는 무척 독특한 특징이 몇 가지 있어 주의해서 사용해야 한다. 우선 토큰이 발견될 때마다 널 문자열로 만들기 위해 검색 대상 문자열을 변경한다는 점이 다른 검색 함수와 다르다. 그래서 검색 대상 문자열이 변경되지 말아야 할 경우는 반드시 사본을 복사한 후 사본에서 토큰을 추출해야 한다. 만약 Win32 환경에서 strtok("Test String"," ");이런 식으로 문자열 상수를 바로 검색하면 읽기 전용 상수를 건드리게 되므로 즉시 다운된다.

또한 이 함수는 검색 중간 결과, 즉 어디까지 검색했는지를 스스로의 정적변수에 저장한다는 점이 특이한데 정적변수를 사용하기 때문에 검색 중에 새로운 검색을 할 경우 이전 검색에 대한 정보는 잃어 버린다. 과거 환경에서는 이런 특성이 편리했지만 요즘같은 멀티 스레드 환경에서는 이런 동작 방식은 권장되지 않으며 여러 가지 문제점을 야기할 수 있다. 두 개의 스레드가 동시에 이 함수를 호출할 경우 결과는 예측할 수 없다.

문자열 검색 함수들 몇 가지를 알아 봤는데 처음 배우는 입장에서는 함수들이 너무 다양해서 혼란스러울지도 모르겠다. C 표준 라이브러리는 자주 사용될만한 대부분의 함수들을 제공하므로 이들 중 상황에 꼭 맞는 함수를 찾아 쓰는 것도 쉽지 않은 일이다. 그러나 실제 코딩을 하다 보면 문자열을 자유롭게 다루기에는 이 함수들만으로 부족하다는 것을 느낄 것이다.

문자열에서 문자를 찾아 주는 strchr 함수는 있지만 대소문자 구분없이 문자를 찾아주는 strichr이라는 함수는 없다. 만약 이런 기능이 꼭 필요하면 응용을 하는 수밖에 없다. 예를 들어 str 문자열에서 대소문자 구분없이 'a'가 포함되어 있는지 알고 싶다면 다음과 같이 strchr을 두 번 호출한다.

 

if (strchr(str,'a') != NULL || strchr(str,'A') != NULL) { ... }

 

문자 검색의 경우는 소문자, 대문자에 대해 각각 검색해 보면 되지만 부분 문자열을 대소구분없이 비교하는 것은 조금 어렵다. stristr이라는 함수가 있다면 쉽겠지만 이런 함수는 제공되지 않는다. 이럴 때는 검색할 문자열의 사본을 만든 후 모두 소문자로 바꾼 상태에서 검색을 하는 방법이 흔히 사용된다. 부분 문자열을 끝에서부터 찾는 함수(이름을 붙인다면 strrstr)도 없으므로 이런 함수도 직접 만들어서 사용해야 한다.