brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Jul 29. 2016

문자열 이야기

프로그램과 우리 사이, 대화가 필요해 II



문자와 문자열 처리 함수들 1


앞의 글에서 언급된 세가지 문자열의 속성을 마음 깊이 꼭 간직하길 바랍니다.

이제 우리는 문자열을 처리하는 여러 함수들과 방법들을 함께 알아보도록 하겠습니다. 하지만 모든 표준 문자열 함수를 이곳에서 다 다루지 않고 가급적 중요하고 문제가 있는 것들을 중심으로 설명할 것입니다.

그리고 다시 한 번 기억할 것은 표준 함수들이 모든 문제를 완벽하게 해결할 뿐만 아니라 성능도 가장 뛰어날 것이라 믿으면 안 된다는 사실입니다.

대부분 표준 함수들은 최소한의 기능만을 포함하고 있습니다. 이 세상에는 그것보다 더 좋은 방법들이 많이 있습니다. 그러므로 평소에 알고리즘 공부하는 것과 다양한 사고력 키우는 것을 게을리하면 아니 되옵니다~


앞으로 문자열 처리 함수들을 사용하려면 <string.h>를 문자 처리 함수들을 사용하려면 <ctype.h>를 include 해야 합니다. 그리고 표준 함수들에 대한 설명과 더불어 같은 기능을 하는 함수를 함께 작성해 보도록 하겠습니다.



문자열도 길고 짧은 건 대봐야 안다? sizeof 연산자말구요~                              


size_t strlen(const char * s);


문자열에서 널 문자인 '\0'를 제외한 실제 문자열의 길이를 알려주는 함수 입니다. return 타입 size_t는 unsigned int로 typedef 되어 있습니다. 문자열의 길이는 최초의 널 문자가 나오기 이전까지의 문자의 개수를 말합니다. 아래와 같이 직접 코드를 작성할 수도 있습니다.   


/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/

// prototype

int strlen2(register const char *) ;  

int strlen3(register const char *) ;  


int strlen2(register const char * s) {

    register int count = 0 ;

    while(*s++) { count++ ; } // 널 문자는 카운트하지 않고 빠져 나온다

    return count ;

} // end strlen2()


int strlen3(register const char * s) {

    register char * o = s ;

    while(*s++) ; // *s가 널문자가 될 때 끝난다.

    // return --s - o ; // 아래와 같은 코드

    return s - o - 1 ;

} // end strlen3()



   

문자열이 대입연산자 안 먹는다 게 진짠가요?                              


char * strcpy(char * s1, const char * s2);

char * strncpy(char * s1, const char * s2, size_t n);


문자열에 대입 연산자를 사용하게 되면 포인터 문자열은 주소 값만 바뀌게 되고, 배열 문자열은 아예 대입 연산자를 사용할 수 없습니다. 그래서 문자열의 경우 대입 연산자를 사용한 것과 동일한 결과를 갖도록 각 문자 값들 그대로 복사해야 한다면 strcpy() 함수를 사용합니다.

일반수식에서 s1 = s2 ; 라고 사용하면 s2의 값이 s1으로 그대로 복사 되는 것처럼 여기에서도 s1이 사본이고 s2는 원본입니다.

s1의 영역에 적어도 복사해야 할 s2의 사이즈만큼은 반드시 메모리 할당이 되어 있어야 합니다.

특히 s1에 상수 문자가 들어 있을 경우에는 실행 중에 상수 문자를 바꾸라고 했으니 실행 에러가 발생합니다.


strncpy()는 마지막 인자로 지정 길이를 넣어 주면 s2의 지정 길이만큼 s1으로 복사됩니다.

물론 s1의 할당된 메모리 사이즈가 지정 길이와 같거나 커야 합니다. 안 그래도 에러 없이 잘만 돌아가던데요? 그 프로그램이 내일도 아무 문제없이 동작할 거라고 아무도 장담 못합니다. 언제 터질지도 모르는 시한폭탄을 껴안고 주무시려고요?

그리고 strncpy()는 마지막에 '\0' 문자를 자동으로 넣어 주지 않습니다. 하지만 지정 길이보다 원본의 사이즈가 작을 때는 나머지는 널 문자로 채웁니다.


    char * name1st = "Sinclair" ;

    const char name2nd[10] = "Joshua" ;

    char name3rd[10] = { 0 } ;

    strcpy(name3rd , "바보") ;

    /*

    // 모두 실행 시 Segmentation fault 발생

    strcpy(name2nd , "바보") ; // 여기에서만 컴파일 워닝(경고) 발생

    strcpy(name1st , "바보") ;

    strcpy("Sinclair" , "바보") ;

    */

    strcpy(name3rd , name1st) ;

    puts(name3rd) ; // Sinclair 출력

    strncpy(name3rd , "Dream" , 5) ;

    puts(name3rd) ; // Dreamair 출력

    strcpy(name3rd , name1st) ;

    strncpy(name3rd , "Dream" , 6) ;

    puts(name3rd) ; // Dream 출력

    strcpy(name3rd , name1st) ;

    strncpy(name3rd , "Dream" , 7) ;

    puts(name3rd) ; // Dream 출력

    // strcpy(s1 , s2) == strncpy(s1 , s2 , strlen(s2) + 1)  




/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/


// prototypes

// 따로 문자열을 다시 리턴 하지 않았습니다.

int strcpy2(register char * , register const char *) ;

int strncpy2(register char * , register const char * , int) ;  


int strcpy2(register char * t , register const char * s) {

    while(*t++ = *s++) ; // s의 널 문자가 t로 복사되어야 끝난다.

    return 0 ;

} // end strcpy2()  


int strncpy2(register char * t , register const char * s , int n) {

    register int i ;

    for(i = 0 ; i < n ; ++i) {

        if( !( *t++ = *s++ ) ) {

            for( ; i < n ; ++i ) { *t++ = 0 ; }

            // 위의 코드 대신에 아래 코드를 사용해도 좋을 듯

            // memset( t , 0 , n-i) ; break ;

        } // end if

    } // end for

    return 0 ;

} // end strncpy2()   




문자열 비교? 각 문자들끼리 서로 빼고 막 요래~ 요래~                              


int strcmp(const char * s1, const char * s2);

int strncmp(const char * s1, const char * s2, size_t n);

int strcoll(const char * s1, const char * s2);


strcpy() 함수가 대입 연산자인 =를 대신하는 것이라면 strcmp() 함수는 비교 연산자인 ==를 대신합니다. 상수 문자열이건 포인터나 배열 문자열이건 모두 다 비교 연산자를 만나면 주소 값들입니다. 그러므로 단순히 같은 주소 값인지 비교할 목적이 아니라 실제 같은 문자들을 포함하고 있는 같은 문자열인지 알고 싶다면 반드시 strcmp() 함수를 사용해야 합니다. strcmp() 함수는 문자열을 각 문자 별로 비교하여 같으면 0, s1이 크면 양수, s2가 크면 음수가 리턴 됩니다. 주의할 것은 사전적 순서로 비교하는 것이 아니라 문자열에 포함되어 있는 각 문자들의 코드 값을 서로 빼기 때문에 사전식 정렬의 경우에는 적합하지 않을 수 있습니다. 그리고 현재 정해진 locale(지역정보 값, <locale.h> 파일 참조)에 따라 문자열을 비교하는 함수가 strcoll() 함수입니다.   


/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/

// prototypes

int strlen2(register const char *) ;

int strcmp2(const char * , const char *) ;

int strncmp2(register const char * , register const char * , int) ;  


int strcmp2(const char * s1 , const char * s2) {

    int l = strlen2(s1) ;

    int m = strlen2(s2) ;

    return strncmp2(s1 , s2 , l < m ? m : l) ;

} // end strcmp2()  


int strncmp2(    register const char * s1 ,

                register const char * s2 , int length) {

    register int i , result ;

    for(i = length ; i > 0 ; --i )

         if( result = *s1++ - *s2++ ) return result ;

    return 0 ;

} // end strncmp2()                                 




size_t strxfrm(char * s1, const char * s2, size_t n);


일반적으로 strcmp() 함수 대신 strcoll() 함수를 사용하게 되면 문자열을 비교할 때 locale을 사용할 수 있는데, 이때 strxfrm() 함수를 사용하여 비교할 문자열을 locale에 맞는 문자열로 미리 바꿔야 합니다. strcoll() 함수와 strxfrm() 함수는 현재 설정되어 있는 locale category 인자 값들 중 LC_COLLATE로부터 참조하게 되고 표준 라이브러리에 setlocale() 함수를 사용하면 이러한 locale category 인자 값들을 바꾸거나 가져올 수도 있습니다. 지금 단계에서는 거의 사용할 필요가 없으며 오히려 잘못 설정하게 된다면 프로그램이 잘못 동작하는 원인이 되기도 합니다. 그래서 현재는 특별한 이유가 없다면 이 함수는 조작하지 않는 것이 좋습니다.


  

문자결합: 문자열 더하기 문자열은 문자스물?                              


char * strcat(char * s1, const char * s2);

char * strncat(char * s1, const char * s2, size_t n);


문자열을 결합하는 함수들입니다. 포인터 문자열이든 배열문자열이든 일반 연산자를 만나는 경우 모두 주소 값이고 같은 타입의 주소 값끼리 할 수 있는 유일한 사칙연산은 바로 뺄셈뿐입니다. 그렇기 때문에 문자열을 서로 결합하려면 덧셈이 아닌 strcat() 함수를 사용해야 합니다. s2의 문자열을 s1의 문자열 뒤에 결합하고 결합된 문자열의 시작주소인 s1을 리턴 합니다. 당연히 s1의 메모리에는 결합에 충분한 여유공간이 있어야 합니다. 그리고 strncpy() 함수와는 다르게 strncat() 함수는 널 문자를 포함하지 않더라도 결합 후에 널 문자를 자동으로 넣어줍니다.   


/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/

// prototypes

// 따로 문자열을 다시 리턴 하지 않았습니다.

int strlen2(register const char *) ;

int strcpy2(register char * , register const char *) ;

int strncpy2(register char * , register const char * , int) ;  

int strcat2(register char * , register const char *) ;  

int strncat2(register char * , register const char * , int) ;


int strcat2(register char * t , register const char * s) {

    return strcpy2(t + strlen2(t) , s) ;

} // end strcat2()  


int strncat2(register char * t , register const char * s, int n) {

    *( (t += strlen2(t)) + n ) = 0 ; // 널 문자를 먼저 넣고 시작

    return strncpy2(t , s , n) ;

} // end strncat2()










#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language

매거진의 이전글 문자열 이야기
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari