Native/C

문자열 함수들

aucd29 2013. 10. 2. 18:47

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

 

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

#include

 

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

#include

 

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) 없으므로 이런 함수도 직접 만들어서 사용해야 한다.

.문자열 변환

문자열 변환이란 문자열의 내용을 일정한 규칙에 따라 바꾸는 것이다. 다음 다섯 가지의 함수가 있는데 아주 쉬우므로 간략하게 설명하도록 한다.

 

char *strset(char *string, int c);

char *strnset(char *string, int c, size_t count);

char *strlwr(char *string);

char *strupr(char *string);

char *strrev(char *string);

 

strset 함수는 문자열을 c문자로 가득 채운다. 문자 직전까지의 모든 문자들이 c 문자로 바뀐다. str "password" 문자열을 가질 strset(str,'*') 문자열을 "********" 바꿀 것이다. strnset 함수는 개수를 지정할 있다는 것만 다르다.

strlwr 함수는 모든 문자를 소문자로 바꾸며 strupr 함수는 모든 문자를 대문자로 바꾼다. str "String Test" strlwr(str) "string test", strupr(str) "STRING TEST" 바꿀 것이다. 함수 모두 영문자가 아닌 한글이나 숫자, 기호는 그대로 유지한다.

strrev 함수는 문자열을 거꾸로 뒤집는다. , 제일 처음에 있는 문자를 마지막 문자와 교환하고 번째 문자는 마지막 번째 문자와 교환하는 식이다. 종료 문자는 교환 대상에서 제외된다. str "String" strrev(str) 호출하면 "gnirtS" 것이다. 함수는 문자열을 구성하는 개별 문자를 기계적으로 교환할 뿐이므로 한글에 대해서는 제대로 동작하지 않는다.

str "쓰레기통" strrev 함수를 호출한다고 해서 문자열이 "통기레쓰" 되는 것은 아니다. 한글은 음절이 2바이트이기 때문에 2바이트씩 한꺼번에 교환해야 거꾸로된 문자열을 만들 있다. 다음 예제는 문자열 변환 함수들을 골고루 사용해 것이다.

 

: strrev

#include

#include

 

void main(void)

{

   char str[]="Made In Korea";

   char str2[16];

 

   strcpy(str2,str);

   strupr(str2);

   printf("strupr => %s\n",str2);

   strcpy(str2,str);

   strlwr(str2);

   printf("strlwr => %s\n",str2);

   strcpy(str2,str);

   strrev(str2);

   printf("strrev => %s\n",str2);

   strcpy(str2,str);

   strset(str2,'*');

   printf("strset => %s\n",str2);

}

 

str2 "Made In Korea"라는 문자열을 저장한 각각의 문자열 변환 함수로 문자열을 바꾸어 보았다. 실행 결과는 다음과 같다.

 

strupr => MADE IN KOREA

strlwr => made in korea

strrev => aeroK nI edaM

strset => *************

 

함수 이름을 외우기가 어려워서 그렇지 동작은 정말 간단한 편이다.

.문자 관리 함수

다음은 문자열을 구성하는 개별 문자들을 관리하는 함수에 대해 알아 보자. 함수들은 인수로 전달된 문자가 특정 그룹에 속하는지 조사하는데 그룹에 속하면 0 아닌 값을 리턴하고 그렇지 않다면 0 리턴한다. 모두 ctype.h 선언되어 있으며 C 표준 함수 중에 손에 꼽을만큼  쉬운 함수들이라 도표로 간단하게 정리하도록 한다.

 

함수

참이 되는 문자 범위

int isalpha(int c);

A-Z, a-z

int isupper(int c);

A-Z

int islower(int c);

a-z

int isdigit(int c);

0-9

int isxdigit(int c);

0-9, A-F, a-f

int isalnum(int c);

0-9, A-Z, a-z

int isprint(int c);

인쇄 가능한 문자

int isgraph(int c);

공백을 제외한 인쇄 가능한 문자

int ispunct(int c);

인쇄 가능한 문자중 공백과 isalnum 제외한 문자

int isspace(int c);

0x09-0x0d, 0x20

 

특정 문자가 알파벳인지, 숫자인지 등을 판단하고 싶을 함수들을 호출한다. 예를 들어 점수를 입력할 때는 입력받은 모든 문자가 아라비아 숫자인지 점검해야 하며 이때는 isdigit 함수를 사용한다. 다음 함수는 영문 대소문자를 변환한다.

 

int tolower(int c);

int toupper(int c);

 

만약 c 영문자가 아니라면 아무 동작도 하지 않으며 c 그대로 리턴한다. 입력값을 비교할 비교하는 것보다 한쪽으로 바꾼 비교하는 것이 쉽다. 다음 코드는 동일하다.

 

if (ch == 'A' || ch == 'a') { ... }

if (tolower(ch) == 'a') { ... }

 

조건을 || 논리 연산자로 비교하는 것보다 소문자로 바꾼 'a' 비교하는 것이 훨씬 쉽다. 물론 대문자로 바꾼 'A' 비교해도 결과는 같다. 특히 switch문의 경우 대소문자별로 일일이 case 쓰는 것보다 변환한 결과로 다중 분기를 하는 것이 훨씬 간단하고 깔끔하다.

 

switch (ch) {

   case 'A':

   case 'a':

      어쩌고 저쩌고;break;

   case 'B':

   case 'b':

      시끌 시끌;break;

   // 계속 이런 식으로 다중 분기

}