선행 / 후행 공백을 표준 방식으로 자르려면 어떻게해야합니까?


178

C의 문자열에서 선행 및 후행 공백을 트리밍하는 깨끗하고 선호되는 표준 방법이 있습니까? 나는 내 자신을 굴릴 것이지만 이것이 똑같이 일반적인 해결책으로 일반적인 문제라고 생각할 것입니다.

답변:


164

문자열을 수정할 수있는 경우 :

// Note: This function returns a pointer to a substring of the original string.
// If the given string was allocated dynamically, the caller must not overwrite
// that pointer with the returned value, since the original pointer must be
// deallocated using the same allocator with which it was allocated.  The return
// value must NOT be deallocated using free() etc.
char *trimwhitespace(char *str)
{
  char *end;

  // Trim leading space
  while(isspace((unsigned char)*str)) str++;

  if(*str == 0)  // All spaces?
    return str;

  // Trim trailing space
  end = str + strlen(str) - 1;
  while(end > str && isspace((unsigned char)*end)) end--;

  // Write new null terminator character
  end[1] = '\0';

  return str;
}

문자열을 수정할 수 없으면 기본적으로 동일한 방법을 사용할 수 있습니다.

// Stores the trimmed input string into the given output buffer, which must be
// large enough to store the result.  If it is too small, the output is
// truncated.
size_t trimwhitespace(char *out, size_t len, const char *str)
{
  if(len == 0)
    return 0;

  const char *end;
  size_t out_size;

  // Trim leading space
  while(isspace((unsigned char)*str)) str++;

  if(*str == 0)  // All spaces?
  {
    *out = 0;
    return 1;
  }

  // Trim trailing space
  end = str + strlen(str) - 1;
  while(end > str && isspace((unsigned char)*end)) end--;
  end++;

  // Set output size to minimum of trimmed string length and buffer size minus 1
  out_size = (end - str) < len-1 ? (end - str) : len-1;

  // Copy trimmed string and add null terminator
  memcpy(out, str, out_size);
  out[out_size] = 0;

  return out_size;
}

6
메모리 누수에 신경 쓰지 않는 한 첫 번째 대답은 전혀 좋지 않습니다. 이제 두 개의 겹치는 줄이 있습니다 (원래 줄 끝이 잘리고 새 줄이 있습니다). 원래 문자열 만 해제 할 수 있지만 가능하면 두 번째 문자열은 해제 된 메모리를 가리 킵니다.
David Nehme

7
@nvl : 할당 된 메모리가 없으므로 해제 할 메모리가 없습니다.
Adam Rosenfield

15
@nvl : No. str는 지역 변수이며,이를 변경해도 전달되는 원래 포인터는 변경되지 않습니다. C의 함수 호출은 항상 값으로 전달되며 참조로 전달되지 않습니다.
Adam Rosenfield

11
@Raj : 전달 된 주소와 다른 주소를 반환하는 데 본질적으로 잘못된 것은 없습니다. 반환 된 값이 free()함수 의 유효한 인수 일 필요는 없습니다 . 반대의 경우-효율성을 위해 메모리 할당이 필요하지 않도록 설계했습니다. 전달 된 주소가 동적으로 할당 된 경우 호출자는 여전히 해당 메모리를 해제해야하므로 호출자는 해당 값을 여기에 리턴 된 값으로 겹쳐 쓰지 않아야합니다.
Adam Rosenfield 2016 년

3
당신의 인수를 캐스팅해야 isspace하는 unsigned char, 그렇지 않으면 당신은 정의되지 않은 동작을 호출.
Roland Illig

37

다음은 문자열을 버퍼의 첫 번째 위치로 이동시키는 것입니다. 문자열을 동적으로 할당 한 경우 trim ()이 반환하는 동일한 포인터에서 문자열을 해제 할 수 있도록이 동작을 원할 수 있습니다.

char *trim(char *str)
{
    size_t len = 0;
    char *frontp = str;
    char *endp = NULL;

    if( str == NULL ) { return NULL; }
    if( str[0] == '\0' ) { return str; }

    len = strlen(str);
    endp = str + len;

    /* Move the front and back pointers to address the first non-whitespace
     * characters from each end.
     */
    while( isspace((unsigned char) *frontp) ) { ++frontp; }
    if( endp != frontp )
    {
        while( isspace((unsigned char) *(--endp)) && endp != frontp ) {}
    }

    if( frontp != str && endp == frontp )
            *str = '\0';
    else if( str + len - 1 != endp )
            *(endp + 1) = '\0';

    /* Shift the string so that it starts at str so that if it's dynamically
     * allocated, we can still free it on the returned pointer.  Note the reuse
     * of endp to mean the front of the string buffer now.
     */
    endp = str;
    if( frontp != str )
    {
            while( *frontp ) { *endp++ = *frontp++; }
            *endp = '\0';
    }

    return str;
}

정확성을 테스트하십시오.

#include <stdio.h>
#include <string.h>
#include <ctype.h>

/* Paste function from above here. */

int main()
{
    /* The test prints the following:
    [nothing to trim] -> [nothing to trim]
    [    trim the front] -> [trim the front]
    [trim the back     ] -> [trim the back]
    [    trim front and back     ] -> [trim front and back]
    [ trim one char front and back ] -> [trim one char front and back]
    [ trim one char front] -> [trim one char front]
    [trim one char back ] -> [trim one char back]
    [                   ] -> []
    [ ] -> []
    [a] -> [a]
    [] -> []
    */

    char *sample_strings[] =
    {
            "nothing to trim",
            "    trim the front",
            "trim the back     ",
            "    trim front and back     ",
            " trim one char front and back ",
            " trim one char front",
            "trim one char back ",
            "                   ",
            " ",
            "a",
            "",
            NULL
    };
    char test_buffer[64];
    char comparison_buffer[64];
    size_t index, compare_pos;

    for( index = 0; sample_strings[index] != NULL; ++index )
    {
        // Fill buffer with known value to verify we do not write past the end of the string.
        memset( test_buffer, 0xCC, sizeof(test_buffer) );
        strcpy( test_buffer, sample_strings[index] );
        memcpy( comparison_buffer, test_buffer, sizeof(comparison_buffer));

        printf("[%s] -> [%s]\n", sample_strings[index],
                                 trim(test_buffer));

        for( compare_pos = strlen(comparison_buffer);
             compare_pos < sizeof(comparison_buffer);
             ++compare_pos )
        {
            if( test_buffer[compare_pos] != comparison_buffer[compare_pos] )
            {
                printf("Unexpected change to buffer @ index %u: %02x (expected %02x)\n",
                    compare_pos, (unsigned char) test_buffer[compare_pos], (unsigned char) comparison_buffer[compare_pos]);
            }
        }
    }

    return 0;
}

소스 파일은 trim.c입니다. 'cc -Wall trim.c -o trim'으로 컴파일되었습니다.


2
당신의 인수를 캐스팅해야 isspace하는 unsigned char, 그렇지 않으면 당신은 정의되지 않은 동작을 호출.
Roland Illig

@RolandIllig : 고마워, 나는 그것이 필요하다는 것을 결코 깨닫지 못했습니다. 고쳤다.
indiv

@Simas : 왜 그렇게 말합니까? 이 함수는 isspace()왜 호출 합니까 " ""\n"? 나는 줄 바꿈에 대한 단위 테스트를 추가하며 ... 나에게 확인을 보이는 ideone.com/bbVmqo
개별 선택

1
@indiv 수동으로 할당되면 유효하지 않은 메모리 블록에 액세스합니다. 즉이 줄 : *(endp + 1) = '\0';. 답에 대한 예제 테스트는이 문제를 피하는 64 버퍼를 사용합니다.
Simas

1
@nolandda : 자세한 내용 감사합니다. 나는 현재 valgrind에 액세스 할 수 없기 때문에 버퍼 오버런을 감지하도록 수정하고 테스트를 업데이트했습니다.
indiv

23

내 솔루션. 문자열은 변경 가능해야합니다. 다른 솔루션 중 일부는 공간이 아닌 부분을 처음으로 이동시켜 나중에 포인터를 free () 해야하는 경우 이전 포인터를 계속 사용할 수 있다는 이점이 있습니다.

void trim(char * s) {
    char * p = s;
    int l = strlen(p);

    while(isspace(p[l - 1])) p[--l] = 0;
    while(* p && isspace(* p)) ++p, --l;

    memmove(s, p, l + 1);
}   

이 버전에서는 문자열을 편집하는 대신 strndup ()을 사용하여 문자열 복사본을 만듭니다. strndup ()에는 _GNU_SOURCE가 필요하므로 malloc () 및 strncpy ()를 사용하여 자체 strndup ()을 만들어야 할 수도 있습니다.

char * trim(char * s) {
    int l = strlen(s);

    while(isspace(s[l - 1])) --l;
    while(* s && isspace(* s)) ++s, --l;

    return strndup(s, l);
}

4
trim()발동 할 UB는 경우 s입니다 ""처음으로 isspace()호출 될 것 isspace(p[-1])p[-1]반드시 법적 위치를 참조하지 않습니다.
chux-복원 Monica Monica

1
당신의 인수를 캐스팅해야 isspace하는 unsigned char, 그렇지 않으면 당신은 정의되지 않은 동작을 호출.
Roland Illig

1
if(l==0)return;길이가 0 인 str을 피하기 위해 추가해야합니다
ch271828n

11

다음은 왼쪽, 오른쪽, 모두, 전체 및 장소에서 트리밍하고 지정된 문자 세트 (또는 기본적으로 공백)를 트리밍하는 C 미니 라이브러리입니다.

strlib.h의 내용 :

#ifndef STRLIB_H_
#define STRLIB_H_ 1
enum strtrim_mode_t {
    STRLIB_MODE_ALL       = 0, 
    STRLIB_MODE_RIGHT     = 0x01, 
    STRLIB_MODE_LEFT      = 0x02, 
    STRLIB_MODE_BOTH      = 0x03
};

char *strcpytrim(char *d, // destination
                 char *s, // source
                 int mode,
                 char *delim
                 );

char *strtriml(char *d, char *s);
char *strtrimr(char *d, char *s);
char *strtrim(char *d, char *s); 
char *strkill(char *d, char *s);

char *triml(char *s);
char *trimr(char *s);
char *trim(char *s);
char *kill(char *s);
#endif

strlib.c의 내용 :

#include <strlib.h>

char *strcpytrim(char *d, // destination
                 char *s, // source
                 int mode,
                 char *delim
                 ) {
    char *o = d; // save orig
    char *e = 0; // end space ptr.
    char dtab[256] = {0};
    if (!s || !d) return 0;

    if (!delim) delim = " \t\n\f";
    while (*delim) 
        dtab[*delim++] = 1;

    while ( (*d = *s++) != 0 ) { 
        if (!dtab[0xFF & (unsigned int)*d]) { // Not a match char
            e = 0;       // Reset end pointer
        } else {
            if (!e) e = d;  // Found first match.

            if ( mode == STRLIB_MODE_ALL || ((mode != STRLIB_MODE_RIGHT) && (d == o)) ) 
                continue;
        }
        d++;
    }
    if (mode != STRLIB_MODE_LEFT && e) { // for everything but trim_left, delete trailing matches.
        *e = 0;
    }
    return o;
}

// perhaps these could be inlined in strlib.h
char *strtriml(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_LEFT, 0); }
char *strtrimr(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_RIGHT, 0); }
char *strtrim(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_BOTH, 0); }
char *strkill(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_ALL, 0); }

char *triml(char *s) { return strcpytrim(s, s, STRLIB_MODE_LEFT, 0); }
char *trimr(char *s) { return strcpytrim(s, s, STRLIB_MODE_RIGHT, 0); }
char *trim(char *s) { return strcpytrim(s, s, STRLIB_MODE_BOTH, 0); }
char *kill(char *s) { return strcpytrim(s, s, STRLIB_MODE_ALL, 0); }

하나의 주요 루틴이 모든 것을 수행합니다. src == dst 인 경우 제자리 에서 잘립니다. 그렇지 않으면 strcpy루틴 처럼 작동 합니다. 문자열 delim에 지정된 문자 세트를 자릅니다.이거나 공백 인 경우 공백입니다. 왼쪽, 오른쪽, 둘 다 및 모두 (tr과 같이) 다듬습니다. 그다지 많지 않고 문자열을 한 번만 반복합니다. 일부 사람들은 오른쪽에서 트림 시작이 왼쪽에서 시작된다고 불평 할 수 있지만 어쨌든 왼쪽에서 시작되는 strlen이 필요하지 않습니다. (오른쪽 손질을 위해 줄 끝까지 가야하는 한 가지 방법이 있습니다. 따라서 작업을 수행 할 수도 있습니다.) 파이프 라이닝 및 캐시 크기 등에 대해서는 논란이있을 수 있습니다. . 솔루션은 왼쪽에서 오른쪽으로 작동하고 한 번만 반복되므로 스트림에서도 작동하도록 확장 할 수 있습니다. 제한 사항 : 유니 코드 문자열 에서는 작동 하지 않습니다 .


2
나는 이것을 높이고 오래된 것을 알고 있지만 버그가 있다고 생각합니다. 배열 인덱스로 사용 하기 전에 dtab[*d]캐스트하지 않습니다 . 서명 된 char가있는 시스템에서이 내용을 읽은 다음 버그가 발생하여 충돌이 발생할 수 있습니다. *dunsigned intdtab[-127]
Zan Lynx

2
인덱스 값을로 캐스팅해야 dtab[*delim++]하므로 정의되지 않은 잠재적 동작입니다 . 코드는 8 비트를 가정합니다 . 로 선언해야합니다 . 로 더 명확합니다 . 이 코드는 UTF-8 인코딩 문자열에서 작동하지만 비 ASCII 간격 시퀀스는 제거하지 않습니다. charunsigned charchardelimconst char *dtab[0xFF & (unsigned int)*d]dtab[(unsigned char)*d]
chqrlie

@ michael-plainer, 이것은 흥미로워 보입니다. 테스트하고 GitHub에 올려 놓지 않겠습니까?
다이스케 아라 마키

9

다음은 간단하지만 올바른 인플레 이스 트림 기능을 시도한 것입니다.

void trim(char *str)
{
    int i;
    int begin = 0;
    int end = strlen(str) - 1;

    while (isspace((unsigned char) str[begin]))
        begin++;

    while ((end >= begin) && isspace((unsigned char) str[end]))
        end--;

    // Shift all characters back to the start of the string array.
    for (i = begin; i <= end; i++)
        str[i - begin] = str[i];

    str[i - begin] = '\0'; // Null terminate string.
}

2
"" str [-1]`인 while ((end >= begin) && isspace(str[end]))경우 UB를 방지 하도록 변경하십시오 . str is . Prevents
chux-복원 Monica Monica

Btw, 작동하려면 str [i-begin + 1]로 변경해야합니다.
truongnm

1
당신의 인수를 캐스팅해야 isspace하는 unsigned char, 그렇지 않으면 당신은 정의되지 않은 동작을 호출.
Roland Illig

@RolandIllig, 왜 정의되지 않은 동작입니까? 이 기능은 문자와 함께 작동하도록 고안되었습니다.
wovano

@wovano 아니요, 그렇지 않습니다. 의 함수는 <ctype.h>int와 함께 작동하도록 고안되었으며, 이는 int unsigned char또는 special value 를 나타냅니다 EOF. stackoverflow.com/q/7131026/225757을 참조하십시오 .
Roland Illig

8

트림 파티에 늦게

특징 :
1. 많은 다른 답변에서와 같이 시작을 빨리 다듬습니다.
2. 끝까지 가고 나면 루프 당 하나의 테스트만으로 오른쪽을 트리밍합니다. @ jfm3과 유사하지만 모든 공백 문자열에 대해 작동합니다.)
3. char부호있는 char일 때 정의되지 않은 동작을 피하려면로 캐스팅 *s하십시오 unsigned char.

문자 처리 "모든 경우에 인수는 int의 값이며, 그 값은 unsigned char매크로의 값 으로 나타내 거나 매크로의 값과 같아야합니다 EOF. 인수에 다른 값이 있으면 동작이 정의되지 않습니다." C11 §7.4 1

#include <ctype.h>

// Return a pointer to the trimmed string
char *string_trim_inplace(char *s) {
  while (isspace((unsigned char) *s)) s++;
  if (*s) {
    char *p = s;
    while (*p) p++;
    while (isspace((unsigned char) *(--p)));
    p[1] = '\0';
  }

  // If desired, shift the trimmed string

  return s;
}

@chqrlie 는 위에서 잘린 문자열을 이동시키지 않는다고 언급했습니다. 그렇게하려면 ....

// Return a pointer to the (shifted) trimmed string
char *string_trim_inplace(char *s) {
  char *original = s;
  size_t len = 0;

  while (isspace((unsigned char) *s)) {
    s++;
  } 
  if (*s) {
    char *p = s;
    while (*p) p++;
    while (isspace((unsigned char) *(--p)));
    p[1] = '\0';
    // len = (size_t) (p - s);   // older errant code
    len = (size_t) (p - s + 1);  // Thanks to @theriver
  }

  return (s == original) ? s : memmove(original, s, len + 1);
}

3
예, 마지막으로 ctype 정의되지 않은 동작에 대해 알고있는 사람입니다.
Roland Illig

2
@ chux 나는 len = (size_t) (ps) +1이어야한다고 생각합니다. 그렇지 않으면 마지막 글자가 겹칩니다.
theriver

4

다음은 @ adam-rosenfields 내부 수정 루틴과 유사하지만 strlen ()에 불필요하게 의존하지 않는 솔루션입니다. @jkramer와 마찬가지로 문자열은 버퍼 내에서 왼쪽으로 조정되므로 동일한 포인터를 해제 할 수 있습니다. memmove를 사용하지 않으므로 큰 문자열에는 적합하지 않습니다. @ jfm3이 언급 한 ++ /-연산자를 포함합니다. FCTX 기반 단위 테스트가 포함되었습니다.

#include <ctype.h>

void trim(char * const a)
{
    char *p = a, *q = a;
    while (isspace(*q))            ++q;
    while (*q)                     *p++ = *q++;
    *p = '\0';
    while (p > a && isspace(*--p)) *p = '\0';
}

/* See http://fctx.wildbearsoftware.com/ */
#include "fct.h"

FCT_BGN()
{
    FCT_QTEST_BGN(trim)
    {
        { char s[] = "";      trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "   ";   trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "\t";    trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "a";     trim(s); fct_chk_eq_str("a",   s); } // NOP
        { char s[] = "abc";   trim(s); fct_chk_eq_str("abc", s); } // NOP
        { char s[] = "  a";   trim(s); fct_chk_eq_str("a",   s); } // Leading
        { char s[] = "  a c"; trim(s); fct_chk_eq_str("a c", s); } // Leading
        { char s[] = "a  ";   trim(s); fct_chk_eq_str("a",   s); } // Trailing
        { char s[] = "a c  "; trim(s); fct_chk_eq_str("a c", s); } // Trailing
        { char s[] = " a ";   trim(s); fct_chk_eq_str("a",   s); } // Both
        { char s[] = " a c "; trim(s); fct_chk_eq_str("a c", s); } // Both

        // Villemoes pointed out an edge case that corrupted memory.  Thank you.
        // http://stackoverflow.com/questions/122616/#comment23332594_4505533
        {
          char s[] = "a     ";       // Buffer with whitespace before s + 2
          trim(s + 2);               // Trim "    " containing only whitespace
          fct_chk_eq_str("", s + 2); // Ensure correct result from the trim
          fct_chk_eq_str("a ", s);   // Ensure preceding buffer not mutated
        }

        // doukremt suggested I investigate this test case but
        // did not indicate the specific behavior that was objectionable.
        // http://stackoverflow.com/posts/comments/33571430
        {
          char s[] = "         foobar";  // Shifted across whitespace
          trim(s);                       // Trim
          fct_chk_eq_str("foobar", s);   // Leading string is correct

          // Here is what the algorithm produces:
          char r[16] = { 'f', 'o', 'o', 'b', 'a', 'r', '\0', ' ',                     
                         ' ', 'f', 'o', 'o', 'b', 'a', 'r', '\0'};
          fct_chk_eq_int(0, memcmp(s, r, sizeof(s)));
        }
    }
    FCT_QTEST_END();
}
FCT_END();

이 솔루션은 매우 위험합니다! 원래 문자열에 공백이 아닌 문자가 포함되어 있지 않으면 트림의 마지막 줄은 '바이트'바이트를 포함하는 경우 앞에 오는 모든 문자를 행복하게 덮어 씁니다. 최적화없이 이것을 컴파일하고 y에 어떤 일이 일어나는지보십시오 : unsigned x = 0x20202020; char s [4] = ""; 부호없는 y = 0x20202020; printf ( "& x, & s, & y = % p, % p, % p \ n", & x, & s, & y); printf ( "x, [s], y = % 08x, [% s], % 08x \ n", x, s, y); trim_whitespace (s); printf ( "x, [s], y = % 08x, [% s], % 08x \ n", x, s, y);
Villemoes

@Villemoes, 버그 보고서에 감사드립니다. 문자열에 공백이있을 때 버퍼의 왼쪽을 걷지 않도록 논리를 업데이트했습니다. 이 새로운 버전이 귀하의 우려를 해결합니까?
Rhys Ulerich

언어 변호사는 아마도 'a'가 가리키는 문자 앞에 포인터를 만드는 것에 대해 생각하는 단순한 생각에 대해 아마 당신에게 소리 지을 것입니다. 현실에서는 아마 괜찮을 것입니다. 그러나 '> ='를 '>'로 변경하고 p의 감소를 'isspace (*-p)'로 이동할 수도 있습니다.
Villemoes

변호사는 주소를 건드리지 않고 주소를 비교하기 때문에 변호사가 괜찮을 것이라고 생각하지만 감소에 대한 귀하의 제안도 좋아합니다. 그에 따라 업데이트했습니다. 감사.
Rhys Ulerich

1
doukremt, foobar 이후의 전체 버퍼가 0으로 채워지지 않는다는 걱정이 있습니까? 그렇다면 모호한 돌을 던지기보다는 명시 적으로 말하면 도움이 될 것입니다.
Rhys Ulerich

3

하나는 실제 작업을 수행하는 다른 하나입니다.

#include <stdio.h>

int main()
{
   const char *target = "   haha   ";
   char buf[256];
   sscanf(target, "%s", buf); // Trimming on both sides occurs here
   printf("<%s>\n", buf);
}

1
scanf를 사용하는 것이 좋습니다. 그러나 그의 OP는 OP가 원했던 것이 아닐 수도있는 단일 단어로만 작동합니다 (예 : "abc"를 트리밍하면 "ab c"가 발생하고 단일 scanf는 "a"가 됨). 따라서 %n변환 지정자 를 사용하여 건너 뛴 문자에 대한 루프와 카운터가 필요하며 결국 수동으로 수행하는 것이 더 간단합니다.
Peter-복직 자 Monica Monica

문자열의 첫 단어가 초기 공백을 무시하고 싶을 때 매우 유용합니다.
J ... S

3

나는 다음 중 하나 이상을 수행했기 때문에 이러한 답변을 좋아하지 않았습니다 ...

  1. 원래 포인터의 문자열 내부에서 다른 포인터를 반환했습니다 (두 가지 다른 포인터를 같은 것으로 가리킬 때 고통 스럽습니다).
  2. 전체 문자열을 미리 인용하는 strlen () 과 같은 것을 무용지물 로 사용했습니다.
  3. 이식 불가능한 OS 특정 lib 함수를 사용했습니다.
  4. 백 스캔.
  5. TAB / CR / LF가 유지되도록 isspace () 대신 '' 와 비교를 사용했습니다 .
  6. 정적 버퍼가 큰 낭비 메모리
  7. 같은 고가의 기능을 낭비 사이클 sscanf를 / sprintf를 .

내 버전은 다음과 같습니다.

void fnStrTrimInPlace(char *szWrite) {

    const char *szWriteOrig = szWrite;
    char       *szLastSpace = szWrite, *szRead = szWrite;
    int        bNotSpace;

    // SHIFT STRING, STARTING AT FIRST NON-SPACE CHAR, LEFTMOST
    while( *szRead != '\0' ) {

        bNotSpace = !isspace((unsigned char)(*szRead));

        if( (szWrite != szWriteOrig) || bNotSpace ) {

            *szWrite = *szRead;
            szWrite++;

            // TRACK POINTER TO LAST NON-SPACE
            if( bNotSpace )
                szLastSpace = szWrite;
        }

        szRead++;
    }

    // TERMINATE AFTER LAST NON-SPACE (OR BEGINNING IF THERE WAS NO NON-SPACE)
    *szLastSpace = '\0';
}

2
당신의 인수를 캐스팅해야 isspace하는 unsigned char, 그렇지 않으면 당신은 정의되지 않은 동작을 호출.
Roland Illig

이 답변은 "Wasted cycles"에 관한 것이므로 공간이 없을 때 코드는 불필요하게 전체 찌르기를 복사합니다. 선행 while (isspace((unsigned char) *szWrite)) szWrite++;은 그것을 막을 것입니다. 코드는 또한 후행 공백을 모두 복사합니다.
chux-복원 Monica Monica

@chux이 구현은 별도의 읽기 및 쓰기 포인터로 다른 위치에서 변경됩니다 (다른 위치에서 새 포인터를 반환하는 것과는 달리). 원래 문자열
Jason Stewart

@ chux, 마지막 공백 문자 (마지막 비 공백 문자 뒤에 null을 추가하기 전에)를 복사하는 것이 맞지만 문자열 사전 스캔을 피하기 위해 지불하기로 선택한 가격입니다. 적은 양의 후행 WS의 경우 마지막 비 WS 문자에 대한 전체 문자열을 사전 스캔하는 것보다 바이트를 복사하는 것이 더 저렴합니다. 많은 양의 후행 WS의 경우 사전 스캔은 쓰기 감소의 가치가 있습니다.
Jason Stewart

@chux는 "공간이 없을 때의 복사"상황에 *szWrite = *szRead대해 포인터가 같지 않을 때만 수행 하면 쓰기를 건너 뛰지 만 다른 비교 / 분기를 추가했습니다. 최신 CPU / MMU / BP를 사용하면 해당 검사가 손실 또는 이득인지 알 수 없습니다. 더 간단한 프로세서와 메모리 아키텍처를 사용하면 복사를 수행하고 비교를 건너 뛰는 것이 더 저렴합니다.
Jason Stewart

2

파티에 늦었 어 ...

역 추적이없는 단일 패스 정방향 스캔 솔루션. 소스 문자열의 모든 문자는 정확히 한 번만 테스트됩니다 두 번. (따라서 소스 문자열에 후행 공백이 많은 경우 특히 다른 솔루션보다 빠릅니다.)

여기에는 소스 문자열을 다른 대상 문자열로 복사 및 트리밍하는 것과 다른 방법으로 소스 문자열을 트리밍하는 두 가지 솔루션이 포함됩니다. 두 함수 모두 동일한 코드를 사용합니다.

(수정 가능) 문자열은 제자리로 이동하므로 원래 포인터는 변경되지 않습니다.

#include <stddef.h>
#include <ctype.h>

char * trim2(char *d, const char *s)
{
    // Sanity checks
    if (s == NULL  ||  d == NULL)
        return NULL;

    // Skip leading spaces        
    const unsigned char * p = (const unsigned char *)s;
    while (isspace(*p))
        p++;

    // Copy the string
    unsigned char * dst = (unsigned char *)d;   // d and s can be the same
    unsigned char * end = dst;
    while (*p != '\0')
    {
        if (!isspace(*dst++ = *p++))
            end = dst;
    }

    // Truncate trailing spaces
    *end = '\0';
    return d;
}

char * trim(char *s)
{
    return trim2(s, s);
}

1
소스 문자열의 모든 문자는 정확히 한 번만 테스트됩니다 . 실제로 소스 문자열의 대부분의 문자는 두 번 '\0'테스트됩니다 isspace(). 로 모든 문자를 테스트하는 것은 낭비적인 것 같습니다 isspace(). 병리가 아닌 경우에는 문자열 끝에서 역 추적하는 것이 더 효율적입니다.
chqrlie

@chqrlie-예, 각 문자는 두 번 테스트됩니다. 이 코드가 실제로 테스트되었으며, 특히 다른 알고리즘과 비교할 때 후행 공백이 많은 문자열이 제공되는 것을보고 싶습니다.
David R Tribble

trim()확인. 코너 케이스 : 와 겹치면 trim2(char *d, const char *s)문제가 있습니다. d,ss < d
chux-복원 Monica Monica

@chux-이 경우 어떻게 trim()행동 해야 합니까? 문자열 자체가 차지하는 메모리로 문자열을 자르고 복사하도록 요청하고 있습니다. 와 달리 memmove()트림 자체를 수행하기 전에 소스 문자열의 길이를 결정해야하므로 전체 문자열을 추가로 스캔해야합니다. rtrim2()소스를 대상에 뒤로 복사하는 것을 알고 다른 소스 문자열 길이 인수를 취하는 다른 함수 를 작성하는 것이 좋습니다.
David R Tribble

1

"무통"이라고 생각하는 것이 확실하지 않습니다.

C 문자열은 꽤 고통 스럽습니다. 공백이 아닌 첫 번째 문자 위치를 간단하게 찾을 수 있습니다.

while (isspace (* p)) p ++;

우리는 두 개의 유사한 간단한 움직임으로 공백이 아닌 마지막 문자 위치를 찾을 수 있습니다.

while (* q) q ++;
{q--; } while (isspace (* q));

( *++연산자를 동시에 사용하는 데 따르는 고통을 아끼지 않았습니다 .)

문제는 지금 당신이 이것으로 무엇을합니까? 현재 데이터 유형은 String생각하기 쉬운 큰 강력한 추상 이 아니라 실제로 스토리지 바이트 배열 이상입니다. 강력한 데이터 유형이 없기 때문에 PHperytonby의 chomp기능 과 동일한 기능을 작성하는 것은 불가능 합니다. C에서 그러한 함수는 무엇을 반환합니까?


문자열이 모든 공백으로 구성되어 있지 않으면 잘 작동합니다. do { q--; } ...알기 전에 한 번 확인해야 합니다 *q != 0.
chux-복원 Monica Monica

1

예를 들어 문자열 라이브러리를 사용하십시오 .

Ustr *s1 = USTR1(\7, " 12345 ");

ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));

... 이것이 "일반적인"문제라고 말했듯이, 네는 #include 등을 포함해야하며 libc에는 포함되지 않지만 임의의 포인터를 저장하는 자체 해킹 작업과 size_t를 발명하지는 않습니다. 버퍼 오버 플로우.



1

이 성장을 유지하기 위해 수정 가능한 문자열이있는 옵션이 하나 더 있습니다.

void trimString(char *string)
{
    size_t i = 0, j = strlen(string);
    while (j > 0 && isspace((unsigned char)string[j - 1])) string[--j] = '\0';
    while (isspace((unsigned char)string[i])) i++;
    if (i > 0) memmove(string, string + i, j - i + 1);
}

1
strlen()size_t범위를 초과 할 수있는를 반환합니다 int. 공백은 공백 문자로 제한되지 않습니다. 마지막으로 가장 중요합니다. strcpy(string, string + i * sizeof(char));소스 및 대상 배열이 겹치므로 정의되지 않은 동작입니다 . memmove()대신에 사용하십시오 strcpy().
chqrlie

@chqrlie 당신이 맞아요, 당신의 제안을 포함 시켰습니다. 소스와 대상이 겹칠 때 복사하면 정의되지 않은 동작이 발생할 수 있지만이 특별한 경우에는 나중에 메모리의 위치에서 처음으로 복사하기 때문에 문제가 발생하지 않는다고 지적하고 싶습니다. 피드백 감사드립니다.
wallek876

1
소스와 대상 배열이 어떻게 겹치는지는 중요하지 않으며 정의되지 않은 동작입니다. 주소가 증가함에 따라 복사가 한 번에 1 바이트 씩 발생할 수 있다는 가정에 의존하지 마십시오. 또한 while (isspace((int)string[i])) string[i--] = '\0';문자열의 시작 부분을 넘어서 반복 될 수 있음 을 언급하지 않았습니다 . 이 루프를 이전 및 다음 행과 결합하고 다음과 같이 작성해야합니다.while (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
chqrlie

@chqrlie 좋은 점은 모든 공백이있는 문자열이 처음부터 반복되도록 만들었을 것이라고 생각하지 않았습니다.
wallek876

실제로, 내 제안은 end후행 null 바이트를 가리 키지 않았으므로 end = ++i;모든 공백 문자를 포함하는 문자열에 여전히 문제가있었습니다. 방금 코드를 수정했습니다.
chqrlie

1

나는 많은 답변이 있다는 것을 알고 있지만 내 해결책이 충분한 지 확인하기 위해 여기에 답변을 게시합니다.

// Trims leading whitespace chars in left `str`, then copy at almost `n - 1` chars
// into the `out` buffer in which copying might stop when the first '\0' occurs, 
// and finally append '\0' to the position of the last non-trailing whitespace char.
// Reture the length the trimed string which '\0' is not count in like strlen().
size_t trim(char *out, size_t n, const char *str)
{
    // do nothing
    if(n == 0) return 0;    

    // ptr stop at the first non-leading space char
    while(isspace(*str)) str++;    

    if(*str == '\0') {
        out[0] = '\0';
        return 0;
    }    

    size_t i = 0;    

    // copy char to out until '\0' or i == n - 1
    for(i = 0; i < n - 1 && *str != '\0'; i++){
        out[i] = *str++;
    }    

    // deal with the trailing space
    while(isspace(out[--i]));    

    out[++i] = '\0';
    return i;
}

2
참고 : isspace(*str)UB when *str < 0.
chux-복원 Monica Monica

1
사용하는 size_t n것이 좋지만 n완전한 트리밍 된 문자열에 비해 너무 작을 때 인터페이스는 어떤 식 으로든 호출자에게 알리지 않습니다 . 고려trim(out, 12, "delete data not")
chux-복원 Monica Monica

1

문자열에서 선행 공백을 건너 뛰는 가장 쉬운 방법은 imho,

#include <stdio.h>

int main()
{
char *foo="     teststring      ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
    return 0;
}

1
중간에 공백이있는 문자열에는 작동하지 않습니다 (예 :) " foo bar ".
David R Tribble

1

좋아, 이것이 나의 질문이다. 나는 그것이 제자리에서 문자열을 수정하고 ( free작동 할) UB를 피하는 가장 간결한 솔루션이라고 생각합니다 . 작은 줄의 경우 아마도 memmove와 관련된 솔루션보다 빠릅니다.

void stripWS_LT(char *str)
{
    char *a = str, *b = str;
    while (isspace((unsigned char)*a)) a++;
    while (*b = *a++)  b++;
    while (b > str && isspace((unsigned char)*--b)) *b = 0;
}

b > str시험은 한 번만 필요합니다. *b = 0;한 번만 필요했습니다.
chux-복원 Monica Monica

1
#include <ctype.h>
#include <string.h>

char *trim_space(char *in)
{
    char *out = NULL;
    int len;
    if (in) {
        len = strlen(in);
        while(len && isspace(in[len - 1])) --len;
        while(len && *in && isspace(*in)) ++in, --len;
        if (len) {
            out = strndup(in, len);
        }
    }
    return out;
}

isspace 모든 공백을 제거하는 데 도움이됩니다.

  • 첫 번째 루프를 실행하여 마지막 바이트에서 공백 문자를 확인하고 길이 변수를 줄입니다.
  • 두 번째 루프를 실행하여 첫 번째 바이트에서 공백 문자를 확인하고 길이 변수를 줄이고 문자 포인터를 증가시킵니다.
  • 마지막으로 length 변수가 0보다 큰 경우 strndup공백을 제외하여 새 문자열 버퍼를 만드는 데 사용 하십시오.

작은 nitpick strndup()은 C 표준의 일부가 아니라 Posix입니다. 그러나 구현하기가 쉽기 때문에 큰 문제는 아닙니다.
Patrick Schlüter

trim_space("")을 반환합니다 NULL. 에 대한 포인터가 필요합니다 "". int len;이어야합니다 size_t len;. isspace(in[len - 1])UB in[len - 1] < 0.
chux-복원 Monica Monica

while (isspace((unsigned char) *in) in++;이전 len = strlen(in);보다 초기 보다 더 효율적일 것입니다.while(len && *in && isspace(*in)) ++in, --len;
chux-Monica Monica

0

개인적으로, 나는 내 자신의 롤. strtok을 사용할 수 있지만 어떤 메모리가 무엇인지 알기 위해 (특히 선행 문자를 제거하는 경우)주의해야합니다.

후행 공백을 제거하는 것은 쉽고 안전합니다. 마지막 공간의 맨 위에 0을 넣고 끝에서 세어 볼 수 있습니다. 선행 공간을 없애는 것은 물건을 옮기는 것을 의미합니다. 적절한 장소에 배치하고 싶다면 (현실적으로) 선행 공백이 없을 때까지 모든 문자를 한 문자 뒤로 이동하면됩니다. 또는보다 효율적으로하기 위해 공백이 아닌 첫 번째 문자의 색인을 찾아 모든 숫자를 해당 숫자만큼 되돌릴 수 있습니다. 또는 공백이 아닌 첫 번째 문자에 대한 포인터를 사용할 수도 있습니다 (그러나 strtok에서와 같은 방식으로주의해야합니다).


4
strtok은 일반적으로 사용하기에 아주 좋은 도구는 아닙니다. 최소한 재진입이 없기 때문입니다. 단일 함수 안에 있으면 안전하게 사용할 수 있지만 strtok을 사용할 수있는 스레드 또는 다른 함수를 호출 할 가능성이 있으면 문제가 있습니다.
Jonathan Leffler

0
#include "stdafx.h"
#include "malloc.h"
#include "string.h"

int main(int argc, char* argv[])
{

  char *ptr = (char*)malloc(sizeof(char)*30);
  strcpy(ptr,"            Hel  lo    wo           rl   d G    eo rocks!!!    by shahil    sucks b i          g       tim           e");

  int i = 0, j = 0;

  while(ptr[j]!='\0')
  {

      if(ptr[j] == ' ' )
      {
          j++;
          ptr[i] = ptr[j];
      }
      else
      {
          i++;
          j++;
          ptr[i] = ptr[j];
      }
  }


  printf("\noutput-%s\n",ptr);
        return 0;
}

3
dreamlax가 테스트 문자열을 편집하여 "sucks big time"을 포함한다고 생각했기 때문에 이것은 나를 웃게 만들었다. 아니. 원저자는 정직합니다.
James Morris

1
이 코드를 사용하지 마십시오. 버퍼 오버 플로우가 발생합니다.
Roland Illig

0

게임에 조금 늦었지만, 나는 내 일상을 싸움에 빠뜨릴 것이다. 아마도 가장 효율적이지 않을 수도 있지만 정확하고 간단하다고 생각합니다 ( rtrim()복잡성 범위 를 밀고 있음).

#include <ctype.h>
#include <string.h>

/*
    Public domain implementations of in-place string trim functions

    Michael Burr
    michael.burr@nth-element.com
    2010
*/

char* ltrim(char* s) 
{
    char* newstart = s;

    while (isspace( *newstart)) {
        ++newstart;
    }

    // newstart points to first non-whitespace char (which might be '\0')
    memmove( s, newstart, strlen( newstart) + 1); // don't forget to move the '\0' terminator

    return s;
}


char* rtrim( char* s)
{
    char* end = s + strlen( s);

    // find the last non-whitespace character
    while ((end != s) && isspace( *(end-1))) {
            --end;
    }

    // at this point either (end == s) and s is either empty or all whitespace
    //      so it needs to be made empty, or
    //      end points just past the last non-whitespace character (it might point
    //      at the '\0' terminator, in which case there's no problem writing
    //      another there).    
    *end = '\0';

    return s;
}

char*  trim( char* s)
{
    return rtrim( ltrim( s));
}

1
당신은 캐스팅해야 char하는 인수를 isspace()(unsigned char)가능성이 음의 값에 정의되지 않은 동작을 방지 할 수 있습니다. ltrim()필요하지 않은 경우 줄을 움직이지 마십시오 .
chqrlie

0

지금까지 대부분의 답변은 다음 중 하나를 수행합니다.

  1. 문자열 끝에서 역 추적 (즉, 문자열 끝을 찾은 다음 공백이 아닌 문자가 발견 될 때까지 뒤로 탐색) 또는
  2. strlen()먼저 전화 를 걸어 전체 문자열을 두 번째 통과시킵니다.

이 버전은 한 번만 패스하고 역 추적하지는 않습니다. 따라서 수백 개의 후행 공백 (일반적으로 SQL 쿼리의 출력을 처리 할 때 드문 일이 아님)이있는 경우에만 다른 것보다 성능이 향상 될 수 있습니다.

static char const WHITESPACE[] = " \t\n\r";

static void get_trim_bounds(char  const *s,
                            char const **firstWord,
                            char const **trailingSpace)
{
    char const *lastWord;
    *firstWord = lastWord = s + strspn(s, WHITESPACE);
    do
    {
        *trailingSpace = lastWord + strcspn(lastWord, WHITESPACE);
        lastWord = *trailingSpace + strspn(*trailingSpace, WHITESPACE);
    }
    while (*lastWord != '\0');
}

char *copy_trim(char const *s)
{
    char const *firstWord, *trailingSpace;
    char *result;
    size_t newLength;

    get_trim_bounds(s, &firstWord, &trailingSpace);
    newLength = trailingSpace - firstWord;

    result = malloc(newLength + 1);
    memcpy(result, firstWord, newLength);
    result[newLength] = '\0';
    return result;
}

void inplace_trim(char *s)
{
    char const *firstWord, *trailingSpace;
    size_t newLength;

    get_trim_bounds(s, &firstWord, &trailingSpace);
    newLength = trailingSpace - firstWord;

    memmove(s, firstWord, newLength);
    s[newLength] = '\0';
}

1
당신이 성능에 관심을하는 경우, 사용하지 않는 strspn()strcspn()꽉 루프있다. 이것은 매우 비효율적이며 오버 헤드는 단일 전달 패스의 입증되지 않은 이점을 능가합니다. strlen()일반적으로 실제 문제가 아닌 매우 효율적인 코드로 인라인으로 확장됩니다. 흰색이 아닌 문자가 거의 없거나 전혀없는 특수한 문자열 인 경우에도 문자열의 시작과 끝을 트리밍하는 것이 문자열의 모든 문자를 테스트하여 백색도보다 훨씬 빠릅니다.
chqrlie

0

이것은 내가 생각할 수있는 가장 짧은 구현입니다.

static const char *WhiteSpace=" \n\r\t";
char* trim(char *t)
{
    char *e=t+(t!=NULL?strlen(t):0);               // *e initially points to end of string
    if (t==NULL) return;
    do --e; while (strchr(WhiteSpace, *e) && e>=t);  // Find last char that is not \r\n\t
    *(++e)=0;                                      // Null-terminate
    e=t+strspn (t,WhiteSpace);                           // Find first char that is not \t
    return e>t?memmove(t,e,strlen(e)+1):t;                  // memmove string contents and terminator
}

1
이것에 대해 :char *trim(char *s) { char *p = s, *e = s + strlen(s); while (e > s && isspace((unsigned char)e[-1])) { *--e = '\0'; } while (isspace((unsigned char)*p)) { p++; } if (p > s) { memmove(s, p, e + 1 - p); } return s; }
chqrlie

0

이 함수는 원래 버퍼를 수정하므로 동적으로 할당 된 경우 원래 포인터를 해제 할 수 있습니다.

#include <string.h>

void rstrip(char *string)
{
  int l;
  if (!string)
    return;
  l = strlen(string) - 1;
  while (isspace(string[l]) && l >= 0)
    string[l--] = 0;
}

void lstrip(char *string)
{
  int i, l;
  if (!string)
    return;
  l = strlen(string);
  while (isspace(string[(i = 0)]))
    while(i++ < l)
      string[i-1] = string[i];
}

void strip(char *string)
{
  lstrip(string);
  rstrip(string);
}

rstrip()빈 문자열에서 정의되지 않은 동작을 호출합니다. lstrip()공백 문자의 초기 부분이 길면 문자열에서 불필요하게 느립니다. 와 다른 음수 값에 대해 정의되지 않은 동작을 호출하기 때문에 인수에 isspace()전달해서는 안됩니다 . charEOF
chqrlie


0

양쪽에서 문자열을 자르려면 oldie를 사용하지만 끈적 거리는 곳을 사용하십시오.) 공백보다 작은 ASCII로 자르는 것이 가능합니다. 즉 제어 문자도 자릅니다!

char *trimAll(char *strData)
{
  unsigned int L = strlen(strData);
  if(L > 0){ L--; }else{ return strData; }
  size_t S = 0, E = L;
  while((!(strData[S] > ' ') || !(strData[E] > ' ')) && (S >= 0) && (S <= L) && (E >= 0) && (E <= L))
  {
    if(strData[S] <= ' '){ S++; }
    if(strData[E] <= ' '){ E--; }
  }
  if(S == 0 && E == L){ return strData; } // Nothing to be done
  if((S >= 0) && (S <= L) && (E >= 0) && (E <= L)){
    L = E - S + 1;
    memmove(strData,&strData[S],L); strData[L] = '\0';
  }else{ strData[0] = '\0'; }
  return strData;
}

size_t대신 사용해야 합니다 unsigned int. 이 코드에는 많은 중복 테스트가 있으며 strncpy(strData,&strData[S],L)소스 및 대상 배열이 겹치기 때문에 정의되지 않은 동작을 호출합니다 . memmove()대신에 사용하십시오 strncpy().
chqrlie

이 경우 대상 주소가 항상 소스보다 작은 인덱스이므로 괜찮습니다. 그러나 memmove가 실제로 더 좋습니다.
Деян Добромиров

아니 괜찮습니다. 소스 및 대상 배열이 어떻게 겹치는지는 중요하지 않으며 표준 사양 이외의 라이브러리 함수 구현을 안전하게 가정 할 수 없기 때문에 정의되지 않은 동작을 호출합니다. 현대의 컴파일러는 잠재적으로 정의되지 않은 동작이있는 상황을 부당하게 이용하고, 안전하게 플레이하고 UB에서 멀리 떨어져 있고, 초보자가 안전하지 않은 가정을하지 않도록하는 경향이 있습니다.
chqrlie

0

지금까지 게시 된 코드가 차선책 인 것처럼 보이기 때문에 코드를 포함하고 있습니다 (아직 언급 할 담당자가 없습니다).

void inplace_trim(char* s)
{
    int start, end = strlen(s);
    for (start = 0; isspace(s[start]); ++start) {}
    if (s[start]) {
        while (end > 0 && isspace(s[end-1]))
            --end;
        memmove(s, &s[start], end - start);
    }
    s[end - start] = '\0';
}

char* copy_trim(const char* s)
{
    int start, end;
    for (start = 0; isspace(s[start]); ++start) {}
    for (end = strlen(s); end > 0 && isspace(s[end-1]); --end) {}
    return strndup(s + start, end - start);
}

strndup()GNU 확장입니다. 당신이 그것 또는 동등한 것을 가지고 있지 않으면, 당신의 자신의 롤. 예를 들면 다음과 같습니다.

r = strdup(s + start);
r[end-start] = '\0';

1
isspace(0)false로 정의 된 경우 두 기능을 모두 단순화 할 수 있습니다. 또한 블록 memmove()내부를 움직 if입니다.
chqrlie

0

여기에서는 동적 메모리 할당을 사용하여 입력 문자열을 trimStr 함수로 자릅니다. 먼저, 입력 문자열에 비어 있지 않은 문자가 몇 개 있는지 확인합니다. 그런 다음 해당 크기의 문자 배열을 할당하고 널 종료 문자를 처리합니다. 이 함수를 사용할 때는 메인 함수 내부의 메모리를 비워야합니다.

#include<stdio.h>
#include<stdlib.h>

char *trimStr(char *str){
char *tmp = str;
printf("input string %s\n",str);
int nc = 0;

while(*tmp!='\0'){
  if (*tmp != ' '){
  nc++;
 }
 tmp++;
}
printf("total nonempty characters are %d\n",nc);
char *trim = NULL;

trim = malloc(sizeof(char)*(nc+1));
if (trim == NULL) return NULL;
tmp = str;
int ne = 0;

while(*tmp!='\0'){
  if (*tmp != ' '){
     trim[ne] = *tmp;
   ne++;
 }
 tmp++;
}
trim[nc] = '\0';

printf("trimmed string is %s\n",trim);

return trim; 
 }


int main(void){

char str[] = " s ta ck ove r fl o w  ";

char *trim = trimStr(str);

if (trim != NULL )free(trim);

return 0;
}

0

내가하는 방법은 다음과 같습니다. 문자열을 제자리에 트림하므로 반환 된 문자열을 할당 해제하거나 할당 된 문자열에 대한 포인터를 잃을 염려가 없습니다. 가능한 가장 짧은 답변은 아니지만 대부분의 독자에게 명확해야합니다.

#include <ctype.h>
#include <string.h>
void trim_str(char *s)
{
    const size_t s_len = strlen(s);

    int i;
    for (i = 0; i < s_len; i++)
    {
        if (!isspace( (unsigned char) s[i] )) break;
    }

    if (i == s_len)
    {
        // s is an empty string or contains only space characters

        s[0] = '\0';
    }
    else
    {
        // s contains non-space characters

        const char *non_space_beginning = s + i;

        char *non_space_ending = s + s_len - 1;
        while ( isspace( (unsigned char) *non_space_ending ) ) non_space_ending--;

        size_t trimmed_s_len = non_space_ending - non_space_beginning + 1;

        if (s != non_space_beginning)
        {
            // Non-space characters exist in the beginning of s

            memmove(s, non_space_beginning, trimmed_s_len);
        }

        s[trimmed_s_len] = '\0';
    }
}

독자에게는 분명하지만 strlen은 또 다른 루프를 수행합니다. :)
ingconti

0
char* strtrim(char* const str)
{
    if (str != nullptr)
    {
        char const* begin{ str };
        while (std::isspace(*begin))
        {
            ++begin;
        }

        auto end{ begin };
        auto scout{ begin };
        while (*scout != '\0')
        {
            if (!std::isspace(*scout++))
            {
                end = scout;
            }
        }

        auto /* std::ptrdiff_t */ const length{ end - begin };
        if (begin != str)
        {
            std::memmove(str, begin, length);
        }

        str[length] = '\0';
    }

    return str;
}

2
이 코드는 질문에 대답 할 수 있지만 문제를 해결하는 방법 및 / 또는 이유에 대한 추가 컨텍스트를 제공하면 답변의 장기적인 가치가 향상됩니다.
Nic3500
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.