Làm cách nào để cắt khoảng trắng hàng đầu / dấu theo cách chuẩn?


177

Có một phương pháp sạch, tốt nhất là tiêu chuẩn để cắt khoảng trắng hàng đầu và dấu kiểm từ một chuỗi trong C không? Tôi tự lăn, nhưng tôi nghĩ đây là một vấn đề phổ biến với một giải pháp phổ biến không kém.

Câu trả lời:


164

Nếu bạn có thể sửa đổi chuỗi:

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

Nếu bạn không thể sửa đổi chuỗi, thì về cơ bản bạn có thể sử dụng cùng một phương thức:

// 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
Xin lỗi, câu trả lời đầu tiên không tốt chút nào trừ khi bạn không quan tâm đến rò rỉ bộ nhớ. Bây giờ bạn có hai chuỗi chồng chéo (ban đầu, có các dấu cách được cắt bớt và một chuỗi mới). Chỉ có thể giải phóng chuỗi gốc, nhưng nếu bạn thực hiện, chuỗi thứ hai sẽ chỉ vào bộ nhớ được giải phóng.
David Nehme

7
@nvl: Không có bộ nhớ được phân bổ, vì vậy không có bộ nhớ để giải phóng.
Adam Rosenfield

15
@nvl: Số strlà một biến cục bộ và việc thay đổi nó không thay đổi con trỏ ban đầu được truyền vào. Các lệnh gọi hàm trong C luôn luôn là giá trị truyền, không bao giờ chuyển qua tham chiếu.
Adam Rosenfield 17/03/2016

11
@Raj: Không có gì sai khi trả về một địa chỉ khác với địa chỉ được truyền vào. Không có yêu cầu nào ở đây là giá trị được trả về là đối số hợp lệ của free()hàm. Hoàn toàn ngược lại - Tôi đã thiết kế điều này để tránh sự cần thiết phải phân bổ bộ nhớ cho hiệu quả. Nếu địa chỉ được truyền được phân bổ động, thì người gọi vẫn có trách nhiệm giải phóng bộ nhớ đó và người gọi cần chắc chắn không ghi đè giá trị đó bằng giá trị được trả về ở đây.
Adam Rosenfield

3
Bạn phải bỏ lập luận cho isspacetới unsigned char, nếu không bạn gọi hành vi không xác định.
Roland Illig

37

Đây là một trong đó chuyển chuỗi vào vị trí đầu tiên của bộ đệm của bạn. Bạn có thể muốn hành vi này để nếu bạn tự động phân bổ chuỗi, bạn vẫn có thể giải phóng nó trên cùng một con trỏ mà trim () trả về:

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;
}

Kiểm tra tính chính xác:

#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;
}

Tập tin nguồn đã được trim.c. Được biên dịch với 'cc -Wall trim.c -o trim'.


2
Bạn phải bỏ lập luận cho isspacetới unsigned char, nếu không bạn gọi hành vi không xác định.
Roland Illig

@RolandIllig: Cảm ơn, tôi chưa bao giờ nhận ra điều đó là cần thiết. Đã sửa nó.
indiv

@Simas: Tại sao bạn nói vậy? Hàm gọi isspace()như vậy tại sao sẽ có sự khác biệt giữa " ""\n"? Tôi đã thêm các unit test cho dòng mới và có vẻ OK để tôi ... ideone.com/bbVmqo
C.nhân

1
@indiv nó sẽ truy cập khối bộ nhớ không hợp lệ khi được phân bổ thủ công. Cụ thể là dòng này : *(endp + 1) = '\0';. Bài kiểm tra ví dụ về câu trả lời sử dụng bộ đệm 64 để tránh vấn đề này.
Simas

1
@nolandda: Cảm ơn chi tiết. Tôi đã sửa nó và cập nhật kiểm tra để phát hiện lỗi tràn bộ đệm vì hiện tại tôi không có quyền truy cập vào valgrind.
indiv

23

Giải pháp của tôi. Chuỗi phải được thay đổi. Ưu điểm trên một số giải pháp khác là nó di chuyển phần không phải không gian về đầu để bạn có thể tiếp tục sử dụng con trỏ cũ, trong trường hợp bạn phải giải phóng () nó sau.

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);
}   

Phiên bản này tạo một bản sao của chuỗi với strndup () thay vì chỉnh sửa nó tại chỗ. strndup () yêu cầu _GNU_SOURCE, vì vậy có lẽ bạn cần tạo strndup () của riêng bạn với malloc () và strncpy ().

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()gọi UB nếu slà cuộc gọi ""đầu tiên và không nhất thiết phải tham chiếu một địa điểm hợp pháp. isspace()isspace(p[-1])p[-1]
chux - Phục hồi Monica

1
Bạn phải bỏ lập luận cho isspacetới unsigned char, nếu không bạn gọi hành vi không xác định.
Roland Illig

1
nên thêm if(l==0)return;để tránh str có độ dài bằng không
ch271828n

11

Đây là thư viện C mini của tôi để cắt tỉa trái, phải, cả hai, tất cả, tại chỗ và tách biệt và cắt xén một tập hợp các ký tự được chỉ định (hoặc khoảng trắng theo mặc định).

nội dung của 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

nội dung của 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); }

Một thói quen chính làm tất cả. Nó cắt đúng vị trí nếu src == dst , nếu không, nó hoạt động như các strcpythói quen. Nó cắt một tập hợp các ký tự được chỉ định trong chuỗi delimhoặc khoảng trắng nếu null. Nó cắt trái, phải, cả hai và tất cả (như tr). Không có gì nhiều cho nó, và nó lặp lại qua chuỗi chỉ một lần. Một số người có thể phàn nàn rằng việc cắt phải bắt đầu ở bên trái, tuy nhiên, không cần strlen nào bắt đầu ở bên trái. . . Vì giải pháp hoạt động từ trái sang phải và chỉ lặp lại một lần, nên nó có thể được mở rộng để hoạt động trên các luồng. Hạn chế: nó không hoạt động trên chuỗi unicode .


2
Tôi ủng hộ điều này và tôi biết nó cũ nhưng tôi nghĩ rằng có một lỗi. dtab[*d]không cast *dđể unsigned inttrước khi sử dụng nó như là một chỉ số mảng. Trên một hệ thống có ký hiệu char, nó sẽ đọc lên dtab[-127]và sẽ gây ra lỗi và có thể bị sập.
Zan Lynx

2
Hành vi không xác định tiềm năng trên dtab[*delim++]charcác giá trị chỉ mục phải được truyền tới unsigned char. Mã giả định 8 bit char. delimnên được tuyên bố là const char *. dtab[0xFF & (unsigned int)*d]sẽ rõ ràng hơn như dtab[(unsigned char)*d]. Mã này hoạt động trên các chuỗi được mã hóa UTF-8, nhưng sẽ không loại bỏ các chuỗi khoảng cách không phải ASCII.
chqrlie

@ michael-plainer, điều này có vẻ thú vị. Tại sao bạn không kiểm tra nó và đưa nó lên GitHub?
Daisuke Aramaki

9

Đây là nỗ lực của tôi tại một chức năng cắt tại chỗ đơn giản nhưng chính xác.

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
Đề nghị thay đổi để while ((end >= begin) && isspace(str[end]))ngăn UB khi str is "" . Prevents str [-1] `.
chux - Phục hồi Monica

Btw, tôi phải thay đổi điều này thành str [i
started

1
Bạn phải bỏ lập luận cho isspacetới unsigned char, nếu không bạn gọi hành vi không xác định.
Roland Illig

@RolandIllig, tại sao nó sẽ là hành vi không xác định? Các chức năng được dự định để làm việc với ký tự.
wovano

@wovano Không, không phải vậy. Các hàm từ <ctype.h>được dự định để làm việc với ints, đại diện cho một unsigned charhoặc giá trị đặc biệt EOF. Xem stackoverflow.com/q/7131026/225757 .
Roland Illig

8

Đi dự tiệc muộn

Các tính năng:
1. Cắt bắt đầu nhanh chóng, như trong một số câu trả lời khác.
2. Sau khi đi đến cuối, cắt tỉa bên phải chỉ với 1 thử nghiệm trên mỗi vòng lặp. Giống như @ jfm3, nhưng hoạt động cho một chuỗi toàn khoảng trắng)
3. Để tránh hành vi không xác định khi charđược ký char, hãy *schuyển sang unsigned char.

Xử lý ký tự "Trong mọi trường hợp đối số là một int, giá trị sẽ được biểu diễn dưới dạng unsigned charhoặc sẽ bằng giá trị của macro EOF. Nếu đối số có bất kỳ giá trị nào khác, hành vi không được xác định." 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 nhận xét ở trên không dịch chuyển chuỗi đã cắt. Làm như vậy....

// 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
Yay, cuối cùng cũng có người biết về hành vi không xác định của ctype.
Roland Illig

2
@chux Tôi nghĩ rằng nó nên là len = (size_t) (ps) +1; nếu không thì chữ cái cuối cùng trùng nhau.
theriver

4

Đây là một giải pháp tương tự như thói quen sửa đổi tại chỗ @ adam-rosenfields nhưng không cần dùng đến strlen (). Giống như @jkramer, chuỗi được điều chỉnh bên trái trong bộ đệm để bạn có thể giải phóng cùng một con trỏ. Không tối ưu cho các chuỗi lớn vì nó không sử dụng memmove. Bao gồm các toán tử ++ / - mà @ jfm3 đề cập. Bao gồm các bài kiểm tra đơn vị dựa trên 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();

Giải pháp này hết sức nguy hiểm! Nếu chuỗi gốc không chứa bất kỳ ký tự không phải khoảng trắng nào, dòng cắt cuối cùng sẽ vui vẻ ghi đè lên bất cứ thứ gì có trước a, nếu các byte đó xảy ra có chứa byte 'khoảng trắng'. Biên dịch cái này mà không tối ưu hóa và xem điều gì xảy ra với y: unsign x = 0x20202020; char s [4] = ""; không dấu 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, cảm ơn bạn đã báo cáo lỗi. Tôi đã cập nhật logic để tránh đi về phía bên trái của bộ đệm khi chuỗi chỉ chứa khoảng trắng. Có phiên bản mới này giải quyết mối quan tâm của bạn?
Rhys Ulerich

Các luật sư ngôn ngữ có thể sẽ hét vào mặt bạn vì suy nghĩ đơn thuần là suy đoán về việc tạo một con trỏ tới ký tự đứng trước điểm 'a' (đó là điều '--p' của bạn sẽ làm). Trong thế giới thực, có lẽ bạn ổn. Nhưng bạn cũng có thể thay đổi '> =' thành '>' và di chuyển phần giảm của p thành 'isspace (* - p)'.
Villemoes

Tôi nghĩ rằng các luật sư sẽ ổn vì nó chỉ so sánh một địa chỉ mà không chạm vào địa chỉ đó, nhưng tôi cũng thích đề xuất của bạn về việc giảm giá. Tôi đã cập nhật nó cho phù hợp. Cảm ơn.
Rhys Ulerich

1
doukremt, mối quan tâm của bạn là toàn bộ bộ đệm sau foobar không chứa đầy số không? Nếu vậy, sẽ hữu ích hơn một chút nếu bạn nói một cách dứt khoát thay vì ném những hòn đá mơ hồ.
Rhys Ulerich

3

Một số khác, với một dòng làm công việc thực sự:

#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
Ý tưởng tốt để sử dụng scanf; nhưng ý chí của anh ta chỉ hoạt động với một từ duy nhất có thể không phải là điều OP muốn (nghĩa là cắt "abc" có thể sẽ dẫn đến "ab c", trong khi lần quét duy nhất của bạn chỉ dẫn đến "a"). Vì vậy, chúng tôi cần một vòng lặp và một bộ đếm cho các ký tự bị bỏ qua với trình %nxác định chuyển đổi, và cuối cùng, nó chỉ đơn giản hơn để làm điều đó bằng tay, tôi sợ.
Peter - Tái lập Monica

Rất hữu ích khi bạn muốn từ đầu tiên của chuỗi bỏ qua mọi khoảng trắng ban đầu.
J ... S

3

Tôi không thích hầu hết những câu trả lời này vì họ đã làm một hoặc nhiều điều sau đây ...

  1. Trả về một con trỏ khác bên trong chuỗi của con trỏ ban đầu (loại đau đớn khi đưa hai con trỏ khác nhau vào cùng một thứ).
  2. Sử dụng vô cớ những thứ như strlen () lặp trước toàn bộ chuỗi.
  3. Được sử dụng các chức năng lib không dành riêng cho hệ điều hành.
  4. Quay lại.
  5. Được sử dụng so sánh với '' thay vì isspace () để TAB / CR / LF được bảo toàn.
  6. Bộ nhớ bị lãng phí với bộ đệm tĩnh lớn.
  7. Chu kỳ lãng phí với các chức năng chi phí cao như sscanf / sprintf .

Đây là phiên bản của tôi:

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
Bạn phải bỏ lập luận cho isspacetới unsigned char, nếu không bạn gọi hành vi không xác định.
Roland Illig

Vì câu trả lời này liên quan đến "Chu kỳ lãng phí", lưu ý rằng mã không cần thiết sao chép toàn bộ sting khi không có khoảng trống. Một dẫn đầu while (isspace((unsigned char) *szWrite)) szWrite++;sẽ ngăn chặn điều đó. Mã cũng sao chép tất cả các khoảng trắng ở cuối.
chux - Tái lập Monica

@chux việc triển khai này đột biến tại chỗ với các con trỏ đọc và ghi riêng biệt (trái ngược với việc trả về một con trỏ mới ở một vị trí khác), do đó, đề xuất nhảy szWrite sang không phải không gian đầu tiên trên dòng - một sẽ rời khỏi không gian hàng đầu trong chuỗi gốc.
Jason Stewart

@chux, bạn đúng là nó sao chép dấu trắng khoảng trắng (trước khi thêm null sau ký tự không phải dấu cách cuối cùng), nhưng đó là giá tôi chọn trả để tránh quét trước chuỗi. Đối với số lượng khiêm tốn của WS trailing, sẽ rẻ hơn nếu chỉ sao chép các byte thay vì quét trước toàn bộ chuỗi cho char không phải WS cuối cùng. Đối với số lượng lớn WS kéo dài, quét trước có thể đáng để giảm ghi.
Jason Stewart

@chux, đối với tình huống "bản sao khi không có khoảng trắng", chỉ thực hiện *szWrite = *szReadkhi con trỏ không bằng nhau sẽ bỏ qua phần ghi trong trường hợp đó, nhưng sau đó chúng tôi đã thêm một so sánh / nhánh khác. Với CPU / MMU / BP hiện đại, tôi không biết kiểm tra đó sẽ là lỗ hay lãi. Với các bộ xử lý và kiến ​​trúc bộ nhớ đơn giản hơn, việc sao chép và bỏ qua so sánh sẽ rẻ hơn.
Jason Stewart

2

Đến bữa tiệc rất muộn ...

Giải pháp quét đơn chuyển tiếp không có quay lại. Mỗi nhân vật trong chuỗi nguồn được kiểm tra chính xác một lần hai lần. (Vì vậy, nó phải nhanh hơn hầu hết các giải pháp khác ở đây, đặc biệt nếu chuỗi nguồn có nhiều khoảng trắng ở cuối.)

Điều này bao gồm hai giải pháp, một để sao chép và cắt một chuỗi nguồn thành một chuỗi đích khác và một để cắt chuỗi nguồn tại chỗ. Cả hai hàm đều sử dụng cùng một mã.

Chuỗi (có thể sửa đổi) được di chuyển tại chỗ, do đó con trỏ ban đầu đến nó vẫn không thay đổi.

#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
Mỗi ký tự trong chuỗi nguồn được kiểm tra chính xác một lần : không thực sự, hầu hết các ký tự trong chuỗi nguồn được kiểm tra hai lần: so với '\0'và sau đó được kiểm tra isspace(). Có vẻ lãng phí để kiểm tra tất cả các nhân vật với isspace(). Quay lui từ cuối chuỗi nên hiệu quả hơn cho các trường hợp không bệnh lý.
chqrlie

@chqrlie - Có, mỗi nhân vật được kiểm tra hai lần. Tôi muốn thấy mã này thực sự được thử nghiệm, đặc biệt là các chuỗi được cung cấp với nhiều khoảng trắng ở cuối, so với các thuật toán khác ở đây.
David R Tribble

trim()ĐỒNG Ý. Trường hợp góc: trim2(char *d, const char *s)có rắc rối khi d,schồng chéo và s < d.
chux - Phục hồi Monica

@chux - Trong trường hợp góc đó, nên trim()cư xử thế nào? Bạn đang yêu cầu cắt và sao chép một chuỗi vào bộ nhớ bị chiếm bởi chính chuỗi đó. Không giống như memmove(), điều này đòi hỏi phải xác định độ dài của chuỗi nguồn trước khi tự cắt, yêu cầu quét toàn bộ chuỗi thêm một thời gian. Tốt hơn là viết một rtrim2()hàm khác biết sao chép nguồn vào đích ngược lại và có thể cần một đối số độ dài chuỗi nguồn bổ sung.
David R Tribble

1

Tôi không chắc những gì bạn cho là "không đau."

Dây C khá đau. Chúng ta có thể tìm thấy vị trí ký tự không phải khoảng trắng đầu tiên một cách tầm thường:

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

Chúng ta có thể tìm thấy vị trí ký tự không phải khoảng trắng cuối cùng với hai bước di chuyển tầm thường tương tự:

trong khi (* q) q ++;
làm {q--; } while (isspace (* q));

(Tôi đã tránh cho bạn nỗi đau khi sử dụng *và các ++nhà khai thác cùng một lúc.)

Câu hỏi bây giờ là bạn làm gì với điều này? Kiểu dữ liệu trong tay không thực sự là một bản tóm tắt lớn mạnh mẽ Stringdễ nghĩ, mà thay vào đó thực sự hầu như không hơn một mảng byte lưu trữ. Thiếu một kiểu dữ liệu mạnh mẽ, không thể viết một hàm sẽ làm giống như hàm của PHperytonby chomp. Điều gì một chức năng như vậy trong C trở lại?


Điều này hoạt động tốt trừ khi chuỗi được tạo thành từ tất cả các khoảng trắng. Cần kiểm tra một lần trước khi do { q--; } ...biết *q != 0.
chux - Tái lập Monica

1

Sử dụng một thư viện chuỗi , ví dụ:

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

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

... như bạn nói đây là một vấn đề "phổ biến", vâng, bạn cần bao gồm một #incoide hoặc như vậy và nó không được bao gồm trong libc nhưng đừng phát minh ra công việc hack của riêng bạn lưu trữ con trỏ ngẫu nhiên và size_t theo cách đó chỉ dẫn đến tràn bộ đệm.



1

Để duy trì sự phát triển này, thêm một tùy chọn với chuỗi có thể sửa đổi:

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()trả về một size_tcó thể vượt quá phạm vi int. khoảng trắng không bị giới hạn trong ký tự khoảng trắng. Cuối cùng nhưng quan trọng nhất: Hành vi không xác định trên strcpy(string, string + i * sizeof(char));vì mảng nguồn và đích trùng nhau. Sử dụng memmove()thay vì strcpy().
chqrlie

@chqrlie bạn nói đúng, chỉ cần bao gồm các đề xuất của bạn. Tôi hiểu rằng sao chép khi trùng lặp nguồn và đích có thể gây ra hành vi không xác định, nhưng chỉ muốn chỉ ra rằng trong trường hợp cụ thể này, điều này không gây ra bất kỳ vấn đề nào vì chúng ta sẽ luôn sao chép từ vị trí sau của bộ nhớ vào đầu, Cảm ơn vì bạn đã phản hồi.
wallek876

1
nó không quan trọng như thế nào các mảng nguồn và đích trùng nhau, đó là hành vi không xác định. Đừng dựa vào giả định rằng việc sao chép có thể diễn ra một byte tại một thời điểm cùng với việc tăng địa chỉ. Ngoài ra tôi quên đề cập rằng while (isspace((int)string[i])) string[i--] = '\0';có thể lặp ngoài đầu chuỗi. Bạn nên kết hợp vòng lặp này với các dòng trước và sau và viếtwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
chqrlie

@chqrlie điểm tốt, một chuỗi với tất cả các khoảng trắng sẽ gây ra vòng lặp lại từ đầu, đã không nghĩ về điều đó.
wallek876

Trên thực tế, đề xuất của tôi không chính xác vì endđã không trỏ đến byte null và bạn end = ++i;vẫn gặp vấn đề với các chuỗi chứa tất cả các ký tự khoảng trắng. Tôi chỉ sửa mã.
chqrlie

1

Tôi biết có nhiều câu trả lời, nhưng tôi đăng câu trả lời của mình lên đây để xem giải pháp của tôi có đủ tốt không.

// 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
Lưu ý: isspace(*str)UB khi *str < 0.
chux - Tái lập Monica

1
Sử dụng size_t nlà tốt, nhưng giao diện không thông báo cho người gọi theo bất kỳ cách nào khi nquá nhỏ cho một chuỗi cắt hoàn chỉnh. Hãy xem xéttrim(out, 12, "delete data not")
chux - Tái lập Monica

1

Cách dễ nhất để bỏ qua các khoảng trắng hàng đầu trong một chuỗi là, imho,

#include <stdio.h>

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

1
Điều này sẽ không hoạt động cho các chuỗi có khoảng trắng ở giữa, chẳng hạn như " foo bar ".
David R Tribble

1

Ok đây là tôi đưa ra câu hỏi. Tôi tin rằng đó là giải pháp ngắn gọn nhất giúp sửa đổi chuỗi tại chỗ ( freesẽ hoạt động) và tránh mọi UB. Đối với các chuỗi nhỏ, nó có thể nhanh hơn một giải pháp liên quan đến 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ài b > strkiểm tra chỉ cần một lần. *b = 0;chỉ cần một lần.
chux - Phục hồi 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 giúp cắt tỉa tất cả các khoảng trắng.

  • Chạy một vòng lặp đầu tiên để kiểm tra từ byte cuối cùng cho ký tự khoảng trắng và giảm biến độ dài
  • Chạy một vòng lặp thứ hai để kiểm tra từ byte đầu tiên cho ký tự khoảng trắng và giảm biến độ dài và con trỏ char tăng.
  • Cuối cùng, nếu biến độ dài lớn hơn 0, thì hãy sử dụng strndupđể tạo bộ đệm chuỗi mới bằng cách loại trừ khoảng trắng.

Chỉ cần một chút nitpick, strndup()không phải là một phần của tiêu chuẩn C mà chỉ là Posix. Nhưng vì nó khá dễ thực hiện nên nó không phải là vấn đề lớn.
Patrick Schlüter

trim_space("")trả lại NULL. Tôi mong đợi một con trỏ đến "". int len;nên size_t len;. isspace(in[len - 1])UB khi nào in[len - 1] < 0.
chux - Tái lập Monica

Một khởi đầu while (isspace((unsigned char) *in) in++;trước len = strlen(in);sẽ hiệu quả hơn so với sauwhile(len && *in && isspace(*in)) ++in, --len;
chux - Tái lập Monica

0

Cá nhân, tôi sẽ tự lăn. Bạn có thể sử dụng strtok, nhưng bạn cần cẩn thận khi làm như vậy (đặc biệt nếu bạn xóa các ký tự đầu) để bạn biết bộ nhớ là gì.

Loại bỏ các dấu cách là dễ dàng và khá an toàn, vì bạn chỉ cần đặt 0 ở trên cùng của không gian cuối cùng, đếm ngược từ cuối. Loại bỏ không gian hàng đầu có nghĩa là di chuyển mọi thứ xung quanh. Nếu bạn muốn thực hiện tại chỗ (có thể hợp lý), bạn có thể tiếp tục chuyển mọi thứ trở lại một ký tự cho đến khi không còn khoảng trống. Hoặc, để hiệu quả hơn, bạn có thể tìm chỉ mục của ký tự không phải không gian đầu tiên và dịch chuyển mọi thứ trở lại theo số đó. Hoặc, bạn chỉ có thể sử dụng một con trỏ đến ký tự không phải không gian đầu tiên (nhưng sau đó bạn cần cẩn thận theo cách tương tự như bạn làm với strtok).


4
strtok nói chung không phải là một công cụ rất tốt để sử dụng - không chỉ bởi vì nó không được đăng ký lại. Nếu bạn ở trong một chức năng duy nhất, nó có thể được sử dụng một cách an toàn, nhưng nếu có bất kỳ khả năng nào của các luồng hoặc gọi các chức năng khác có thể tự sử dụng strtok, bạn sẽ gặp rắc rối.
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
Điều này làm tôi bật cười vì tôi nghĩ dreamlax đã chỉnh sửa chuỗi thử nghiệm để bao gồm cả "hút thời gian lớn". Không. Các tác giả ban đầu chỉ là trung thực.
James Morris

1
Đừng sử dụng mã này. Nó tạo ra một tràn bộ đệm.
Roland Illig

0

Một chút muộn để trò chơi, nhưng tôi sẽ ném thói quen của tôi vào cuộc cạnh tranh. Chúng có thể không phải là hiệu quả tuyệt đối nhất, nhưng tôi tin rằng chúng chính xác và chúng đơn giản (với rtrim()việc đẩy phong bì phức tạp):

#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
bạn nên bỏ các charlập luận để isspace()để (unsigned char)tránh hành vi undefined trên các giá trị tiêu cực tiềm tàng. Cũng tránh di chuyển chuỗi nếu trong ltrim()nếu không cần thiết.
chqrlie

0

Hầu hết các câu trả lời cho đến nay làm một trong những điều sau đây:

  1. Backtrack ở cuối chuỗi (nghĩa là tìm phần cuối của chuỗi và sau đó tìm ngược lại cho đến khi tìm thấy một ký tự không phải khoảng trắng,) hoặc
  2. Gọi strlen()đầu tiên, thực hiện một lần thứ hai thông qua toàn bộ chuỗi.

Phiên bản này chỉ thực hiện một lần và không quay lại. Do đó, nó có thể hoạt động tốt hơn các loại khác, mặc dù chỉ khi có hàng trăm khoảng trắng ở cuối (điều này không phải là bất thường khi xử lý đầu ra của truy vấn 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
Nếu bạn quan tâm đến hiệu suất, không sử dụng strspn()strcspn()trong một vòng lặp chặt chẽ. Điều này là rất không hiệu quả và chi phí sẽ làm giảm lợi thế chưa được chứng minh của đường chuyền đơn. strlen()thường được mở rộng nội tuyến với mã rất hiệu quả, không phải là một mối quan tâm thực sự. Việc cắt xén phần đầu và phần cuối của chuỗi sẽ nhanh hơn nhiều so với việc kiểm tra mọi ký tự trong chuỗi về độ trắng ngay cả trong trường hợp đặc biệt của chuỗi có rất ít hoặc không có ký tự không phải màu trắng.
chqrlie

0

Đây là cách thực hiện ngắn nhất có thể mà tôi có thể nghĩ ra:

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
Làm thế nào về điều này: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

Các hàm này sẽ sửa đổi bộ đệm ban đầu, vì vậy nếu được phân bổ động, con trỏ ban đầu có thể được giải phóng.

#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()gọi hành vi không xác định trên chuỗi trống. lstrip()là chậm một cách không cần thiết trên chuỗi với một phần dài các ký tự khoảng trắng. isspace()không nên thông qua một charđối số vì nó gọi hành vi không xác định trên các giá trị âm khác với EOF.
chqrlie

0

Bạn nghĩ gì về việc sử dụng hàm StrTrim được xác định trong tiêu đề Shlwapi.h.? Nó là thẳng về phía trước chứ không phải tự xác định.
Thông tin chi tiết có thể được tìm thấy trên:
http://msdn.microsoft.com/en-us/l Library / windows / desktop / bb773454 (v = vs85) .aspx

Nếu bạn có
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
Điều này sẽ cho ausCaptainnhư "GeorgeBailey"không "GeorgeBailey ".


0

Để cắt chuỗi của tôi từ cả hai phía, tôi sử dụng oldie nhưng gooody;) Nó có thể cắt bất cứ thứ gì với ascii nhỏ hơn một khoảng trắng, có nghĩa là các ký tự điều khiển cũng sẽ được cắt bớt!

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;
}

Bạn nên sử dụng size_tthay vì unsigned int. Mã này có rất nhiều kiểm tra dự phòng và gọi hành vi không xác định trên strncpy(strData,&strData[S],L)vì các mảng nguồn và đích trùng nhau. Sử dụng memmove()thay vì strncpy().
chqrlie

Trong trường hợp này là ổn vì địa chỉ đích luôn có chỉ số nhỏ hơn nguồn, nhưng có memmove sẽ thực sự tốt hơn.
Tôi là người sáng lập vào

không, nó không ổn nó không quan trọng như thế nào các mảng nguồn và đích trùng nhau, nó gọi hành vi không xác định bởi vì bạn không thể đưa ra các giả định một cách an toàn về việc thực hiện các chức năng của thư viện ngoài đặc tả tiêu chuẩn của chúng. Trình biên dịch hiện đại có xu hướng lợi dụng không công bằng các tình huống có hành vi không xác định tiềm năng, chơi an toàn và tránh xa UB và không để người mới đưa ra các giả định không an toàn.
chqrlie

0

Tôi chỉ bao gồm mã bởi vì mã được đăng cho đến nay dường như không tối ưu (và tôi chưa có đại diện để nhận xét.)

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()là một phần mở rộng GNU. Nếu bạn không có nó hoặc một cái gì đó tương đương, cuộn của riêng bạn. Ví dụ:

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

1
isspace(0)được định nghĩa là sai, bạn có thể đơn giản hóa cả hai chức năng. Cũng di chuyển memmove()bên trong ifkhối.
chqrlie

0

Ở đây tôi sử dụng cấp phát bộ nhớ động để cắt chuỗi đầu vào cho hàm trimStr. Đầu tiên, chúng tôi tìm thấy có bao nhiêu ký tự không trống tồn tại trong chuỗi đầu vào. Sau đó, chúng tôi phân bổ một mảng ký tự với kích thước đó và chăm sóc ký tự kết thúc null. Khi chúng ta sử dụng chức năng này, chúng ta cần giải phóng bộ nhớ bên trong chức năng chính.

#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

Đây là cách tôi làm điều đó. Nó cắt chuỗi tại chỗ, do đó, không phải lo lắng về việc sắp xếp lại chuỗi trả về hoặc mất con trỏ vào chuỗi được phân bổ. Nó có thể không phải là câu trả lời ngắn nhất có thể, nhưng nó phải rõ ràng với hầu hết độc giả.

#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';
    }
}

hoàn toàn rõ ràng cho độc giả, nhưng strlen thực hiện một vòng lặp khác .. :)
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
Mặc dù mã này có thể trả lời câu hỏi, việc cung cấp ngữ cảnh bổ sung về cách thức và / hoặc lý do giải quyết vấn đề sẽ cải thiện giá trị lâu dài của câu trả lời.
Nic3500
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.