ฉันจะตัดช่องว่างนำหน้า / ต่อท้ายด้วยวิธีมาตรฐานได้อย่างไร


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: ไม่ strเป็นตัวแปรในตัวเครื่องและการเปลี่ยนแปลงจะไม่เปลี่ยนตัวชี้ดั้งเดิมที่ถูกส่งผ่านการเรียกใช้ฟังก์ชันใน C มักจะผ่านค่าต่อตัวเสมอไม่เคยผ่านการอ้างอิง
Adam Rosenfield

11
@Raj: ไม่มีอะไรผิดปกติโดยการส่งคืนที่อยู่ที่แตกต่างจากที่ส่งผ่านมาไม่มีความต้องการที่นี่ว่าค่าที่ส่งคืนเป็นอาร์กิวเมนต์ที่ถูกต้องของfree()ฟังก์ชั่น ค่อนข้างตรงกันข้าม - ฉันออกแบบสิ่งนี้เพื่อหลีกเลี่ยงความจำเป็นในการจัดสรรหน่วยความจำเพื่อประสิทธิภาพ หากที่อยู่ที่ส่งผ่านถูกจัดสรรแบบไดนามิกผู้เรียกยังคงรับผิดชอบในการเพิ่มหน่วยความจำนั้นและผู้โทรต้องแน่ใจว่าจะไม่เขียนทับค่านั้นด้วยค่าที่ส่งคืนที่นี่
Adam Rosenfield

3
คุณต้องโต้แย้งisspaceเพื่อunsigned charมิฉะนั้นคุณจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
Roland Illig

37

นี่คือสตริงที่เลื่อนไปยังตำแหน่งแรกของบัฟเฟอร์ของคุณ คุณอาจต้องการพฤติกรรมนี้เพื่อให้หากคุณจัดสรรสตริงแบบไดนามิกคุณยังสามารถทำให้ว่างบนตัวชี้เดียวกันกับที่ตัด () ส่งคืน:

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

ไฟล์ต้นฉบับตัดแต่ง รวบรวมด้วย 'cc -Wall trim.c -o trim'


2
คุณต้องโต้แย้งisspaceเพื่อunsigned charมิฉะนั้นคุณจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
Roland Illig

@RolandIllig: ขอบคุณฉันไม่เคยรู้ว่ามีความจำเป็น ซ่อมมัน.
บุคคล

@Simas: ทำไมคุณถึงพูดอย่างนั้น? ฟังก์ชั่นเรียกisspace()ดังนั้นทำไมจะมีความแตกต่างระหว่าง" "และ"\n"? ฉันได้เพิ่มการทดสอบหน่วยสำหรับการขึ้นบรรทัดใหม่และมันก็โอเคกับฉัน ... ideone.com/bbVmqo
indiv

1
@indiv มันจะเข้าถึงบล็อกหน่วยความจำที่ไม่ถูกต้องเมื่อจัดสรรด้วยตนเอง คือบรรทัดนี้: *(endp + 1) = '\0';. ตัวอย่างการทดสอบคำตอบใช้บัฟเฟอร์ 64 ซึ่งหลีกเลี่ยงปัญหานี้
Simas

1
@nolandda: ขอบคุณสำหรับรายละเอียด ฉันแก้ไขแล้วและอัปเดตการทดสอบเพื่อตรวจสอบบัฟเฟอร์โอเวอร์รันเนื่องจากฉันไม่สามารถเข้าถึง valgrind ในขณะนี้
indiv

23

ทางออกของฉัน สตริงต้องมีการเปลี่ยนแปลง ข้อได้เปรียบเหนือโซลูชันอื่น ๆ ที่จะย้ายส่วนที่ไม่ใช่ช่องว่างไปยังจุดเริ่มต้นเพื่อให้คุณสามารถใช้ตัวชี้เก่าได้ในกรณีที่คุณต้องว่าง () ในภายหลัง

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 ดังนั้นบางทีคุณอาจต้องสร้าง strndup () ของคุณเองด้วย malloc () และ 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()จะเรียก UB ถ้าsเป็น""เป็นครั้งแรกที่isspace()โทรจะเป็นisspace(p[-1])และp[-1]ไม่จำเป็นต้องอ้างอิงทางกฎหมายที่ตั้ง
chux - Reinstate Monica

1
คุณต้องโต้แย้งisspaceเพื่อunsigned charมิฉะนั้นคุณจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
Roland Illig

1
ควรเพิ่มif(l==0)return;เพื่อหลีกเลี่ยงความยาวเป็นศูนย์
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) มีไม่มากไปและมันวนซ้ำสตริงเพียงครั้งเดียว บางคนอาจบ่นว่าการเล็มขวาเริ่มทางด้านซ้ายอย่างไรก็ตามไม่จำเป็นต้องใช้สเตนซึ่งเริ่มทางด้านซ้าย (ไม่ทางใดก็ทางหนึ่งคุณต้องไปที่จุดสิ้นสุดของสายอักขระภายนอกเพื่อให้ถูกต้องดังนั้นคุณอาจจะทำงานตามที่คุณไปด้วย) อาจมีข้อโต้แย้งเกี่ยวกับการวางท่อและขนาดแคชและใครรู้ . เนื่องจากโซลูชันทำงานจากซ้ายไปขวาและวนซ้ำเพียงครั้งเดียวจึงสามารถขยายเพื่อทำงานบนสตรีมได้เช่นกัน ข้อ จำกัด : มันไม่ทำงานบนสตริงUnicode


2
ฉัน upvoting นี้และฉันรู้ว่ามันเก่า แต่ฉันคิดว่ามีข้อผิดพลาด dtab[*d]ไม่ส่ง*dไปunsigned intก่อนใช้เป็นดัชนีอาร์เรย์ บนระบบที่มีถ่านที่ลงชื่อแล้วสิ่งนี้จะอ่านข้อมูลdtab[-127]ซึ่งจะก่อให้เกิดข้อบกพร่องและอาจผิดพลาดได้
Zan Lynx

2
ไม่ได้กำหนดพฤติกรรมที่อาจเกิดขึ้นdtab[*delim++]เพราะค่าดัชนีจะต้องโยนไปchar unsigned charรหัสอนุมาน char8 ควรได้รับการประกาศให้เป็นdelim จะเป็นที่ชัดเจน รหัสทำงานบนสตริงที่เข้ารหัส UTF-8 แต่จะไม่ตัดลำดับระยะห่างที่ไม่ใช่ ASCII const 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
แนะนำให้เปลี่ยนเป็นwhile ((end >= begin) && isspace(str[end]))ป้องกัน UB เมื่อstr is "" . Prevents str [-1] `
chux - Reinstate Monica

Btw ฉันต้องเปลี่ยนสิ่งนี้เป็น str [i
start

1
คุณต้องโต้แย้งisspaceเพื่อunsigned charมิฉะนั้นคุณจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
Roland Illig

@RolandIllig ทำไมมันจะไม่ได้กำหนดพฤติกรรม? ฟังก์ชั่นมีวัตถุประสงค์เพื่อทำงานกับตัวอักษร
wovano

@ wovano ไม่มันไม่ใช่ ฟังก์ชั่นจาก<ctype.h>มีความตั้งใจที่จะทำงานร่วมกับ ints ซึ่งเป็นตัวแทนของทั้งสองหรือมูลค่าพิเศษunsigned char EOFดูstackoverflow.com/q/7131026/225757
Roland Illig

8

สายไปยังบุคคลที่ตัดแต่ง

คุณสมบัติ:
1. ตัดจุดเริ่มต้นอย่างรวดเร็วเช่นเดียวกับในจำนวนคำตอบอื่น ๆ
2. หลังจากจบการตัดแต่งด้านขวาด้วยการทดสอบ 1 ครั้งต่อการวนซ้ำเท่านั้น ชอบ @ jfm3 แต่งานสำหรับสตริงทุกพื้นที่สีขาว)
3. เพื่อหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดเมื่อcharมีการลงนามcharโยนไป *sunsigned 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
Yay ในที่สุดคนที่รู้เกี่ยวกับพฤติกรรมที่ไม่ได้กำหนด ctype
Roland Illig

2
@chux ฉันคิดว่ามันควรจะ len = (size_t) (ps) +1; มิฉะนั้นตัวอักษรตัวสุดท้ายจะทับซ้อนกัน
บริการ

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; ถ่าน 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' ของคุณจะทำ) ในโลกแห่งความเป็นจริงคุณอาจจะโอเค แต่คุณสามารถเปลี่ยน '> =' เป็น '>' และย้ายการลดลงของ p เป็น 'isspace (* - p)'
Villemoes

ฉันคิดว่าทนายความจะโอเคเพราะมันเป็นเพียงการเปรียบเทียบที่อยู่โดยไม่ต้องสัมผัส แต่ฉันชอบข้อเสนอแนะของคุณเกี่ยวกับการลดลงเช่นกัน ฉันได้ทำการปรับปรุงแล้ว ขอบคุณ
ริส Ulerich

1
ที่นี่, เป็นกังวลของคุณที่บัฟเฟอร์ทั้งหมดหลังจาก foobar ไม่เต็มไปด้วยศูนย์? ถ้าเป็นเช่นนั้นมันจะค่อนข้างมีประโยชน์มากกว่านี้ถ้าคุณพูดอย่างชัดเจนแทนที่จะขว้างก้อนหินที่คลุมเครือ
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 ต้องการ (เช่นการตัดแต่ง "abc" น่าจะส่งผลให้ "ab c" ในขณะที่ scanf เดียวของคุณส่งผลให้ "a") ดังนั้นเราต้องการลูปและตัวนับสำหรับตัวอักษรที่ถูกข้ามพร้อมตัว%nระบุการแปลงและในที่สุดมันก็ง่ายกว่าที่จะทำได้ด้วยมือฉันกลัว
Peter - Reinstate Monica

มีประโยชน์มากเมื่อคุณต้องการคำแรกของสตริงโดยไม่สนใจช่องว่างเริ่มต้นใด ๆ
เจ ...

3

ฉันไม่ชอบคำตอบส่วนใหญ่เพราะพวกเขาทำอย่างหนึ่งอย่างใดต่อไปนี้ ...

  1. ส่งคืนพอยน์เตอร์ที่แตกต่างกันภายในสตริงของพอยน์เตอร์ดั้งเดิม (ชนิดของความเจ็บปวดที่จะเล่นปาหี่สองพอยน์เตอร์ที่แตกต่างกันในสิ่งเดียวกัน)
  2. ใช้ประโยชน์จากสิ่งต่าง ๆ เช่นstrlen ()ซึ่งทำซ้ำสตริงทั้งหมดล่วงหน้า
  3. ใช้งานฟังก์ชั่น lib เฉพาะที่ไม่ใช่พกพาได้
  4. Backscanned
  5. ใช้การเปรียบเทียบกับ''แทนisspace ()เพื่อให้ TAB / CR / LF ถูกเก็บรักษาไว้
  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

เนื่องจากคำตอบนี้มีความกังวลเกี่ยวกับ "รอบการสูญเปล่า" โปรดทราบว่ารหัสจะคัดลอกทั้งต่อยเมื่อไม่มีที่ว่าง ผู้นำwhile (isspace((unsigned char) *szWrite)) szWrite++;จะป้องกันไม่ให้ รหัสยังคัดลอกพื้นที่สีขาวต่อท้ายทั้งหมด
chux - Reinstate Monica

@ chux การใช้งานนี้กลายเป็นแบบแทนที่ด้วยพอยน์เตอร์การอ่านและเขียนที่แยกจากกัน (ตรงข้ามกับการส่งคืนพอยน์เตอร์ใหม่ในตำแหน่งอื่น) ดังนั้นคำแนะนำสำหรับการกระโดด szWrite ไปยังพื้นที่ว่างในบรรทัดแรก สตริงเดิม
Jason Stewart

@ chux คุณถูกต้องแล้วว่าจะคัดลอก white-space (ก่อนเพิ่ม null หลังจากอักขระที่ไม่ใช่ช่องว่างสุดท้าย) แต่นั่นคือราคาที่ฉันเลือกจ่ายเพื่อหลีกเลี่ยงการสแกนสตริงก่อน สำหรับจำนวนเล็กน้อยต่อท้าย WS มันถูกกว่าเพียงแค่คัดลอกไบต์แทนที่จะสแกนสตริงทั้งหมดล่วงหน้าสำหรับอักขระที่ไม่ใช่ WS ล่าสุด สำหรับ WS ที่ตามมาจำนวนมากการสแกนล่วงหน้าอาจจะคุ้มค่ากับการลดการเขียน
Jason Stewart

@chux สำหรับสถานการณ์ "คัดลอกเมื่อไม่มีที่ว่าง" การดำเนินการ*szWrite = *szReadเมื่อตัวชี้ไม่เท่ากันจะข้ามการเขียนในกรณีนั้น แต่จากนั้นเราได้เพิ่มการเปรียบเทียบ / สาขาอื่น ด้วย CPU / MMU / BP ที่ทันสมัยฉันไม่รู้เลยว่าการตรวจสอบนั้นจะเป็นการสูญเสียหรือได้รับ ด้วยตัวประมวลผลและสถาปัตยกรรมหน่วยความจำที่ง่ายขึ้นมันถูกกว่าที่จะทำสำเนาและข้ามการเปรียบเทียบ
Jason Stewart

2

ดึกมากไปงานปาร์ตี้ ...

โซลูชันการสแกนไปข้างหน้าแบบ Single-pass ที่ไม่มีการย้อนรอย อักขระทุกตัวในสตริงที่มาจะถูกทดสอบสองครั้งครั้งเดียว (ดังนั้นควรเร็วกว่าโซลูชันอื่น ๆ ส่วนใหญ่ที่นี่โดยเฉพาะอย่างยิ่งหากสตริงต้นฉบับมีช่องว่างต่อท้ายจำนวนมาก)

ซึ่งรวมถึงโซลูชันที่สองวิธีหนึ่งที่จะคัดลอกและตัดแต่งสายอักขระของแหล่งที่มาลงในสายปลายทางอื่น ฟังก์ชั่นทั้งสองใช้รหัสเดียวกัน

สตริง (แก้ไขได้) จะถูกย้ายในสถานที่ดังนั้นตัวชี้เดิมไปยังคงไม่เปลี่ยนแปลง

#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,s s < d
chux - Reinstate Monica

@chux - ในกรณีมุมนั้นควรtrim()ทำอย่างไร? คุณกำลังขอให้ตัดและคัดลอกสตริงลงในหน่วยความจำที่ครอบครองโดยสตริงตัวเอง ซึ่งแตกต่างจากmemmove()นี้ต้องกำหนดความยาวของสตริงต้นฉบับก่อนที่จะทำการตัดแต่งเองซึ่งต้องสแกนทั้งสายอีกครั้ง ดีกว่าที่จะเขียนrtrim2()ฟังก์ชั่นต่าง ๆที่รู้ว่าจะคัดลอกแหล่งที่มาไปยังปลายทางไปข้างหลังและอาจใช้เวลาอาร์กิวเมนต์อาร์กิวเมนต์ความยาวของสตริงเพิ่มเติม
David R Tribble

1

ฉันไม่แน่ใจว่าสิ่งที่คุณพิจารณาว่า "เจ็บปวด"

สาย C ค่อนข้างเจ็บปวด เราสามารถค้นหาตำแหน่งของอักขระที่ไม่ใช่ช่องว่างแรกได้โดยง่าย:

ในขณะที่ (isspace (* p)) p ++;

เราสามารถหาตำแหน่งตัวละครที่ไม่ใช่ช่องว่างสุดท้ายด้วยการเคลื่อนไหวสองอย่างที่คล้ายกัน:

ในขณะที่ (* q) q ++;
ทำ {q--; } while (isspace (* q));

(ฉันได้ให้คุณเจ็บปวดกับการใช้*และ++โอเปอร์เรเตอร์ในเวลาเดียวกัน)

คำถามตอนนี้คุณทำอะไรกับสิ่งนี้ ประเภทข้อมูลที่อยู่ในมือไม่ได้เป็นนามธรรมที่แข็งแกร่งขนาดใหญ่Stringที่ง่ายต่อการคิด แต่จริงๆแล้วแทบจะไม่ได้มากกว่าไบต์ของพื้นที่จัดเก็บ การขาดประเภทข้อมูลที่แข็งแกร่งมันเป็นไปไม่ได้ที่จะเขียนฟังก์ชั่นที่จะทำเช่นเดียวกับchompฟังก์ชั่นของ PHperytonby ฟังก์ชันอะไรใน C จะส่งคืน


วิธีนี้ใช้งานได้ดียกเว้นว่าสตริงนั้นประกอบด้วยช่องว่างสีขาวทั้งหมด จำเป็นต้องมีการตรวจสอบครั้งหนึ่งก่อนที่จะรู้ว่าdo { q--; } ... *q != 0
chux - Reinstate 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
ไม่สำคัญว่าอาร์เรย์ต้นทางและปลายทางทับซ้อนกันอย่างไรมันเป็นพฤติกรรมที่ไม่ได้กำหนด อย่าพึ่งพาสมมติฐานที่ว่าการคัดลอกอาจเกิดขึ้นทีละหนึ่งไบต์พร้อมที่อยู่ที่เพิ่มขึ้น นอกจากนี้ฉันลืมพูดถึงที่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ไม่ได้ชี้ไปที่ไบต์ว่างท้ายและคุณ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 *str < 0เมื่อ
chux - Reinstate Monica

1
การใช้งานsize_t nเป็นสิ่งที่ดี แต่อินเทอร์เฟซไม่แจ้งผู้โทรในลักษณะใด ๆ เมื่อnมีขนาดเล็กเกินไปสำหรับสตริงที่ถูกตัดแต่ง พิจารณาtrim(out, 12, "delete data not")
chux - Reinstate 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 - Reinstate 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 ช่วยในการตัดแต่งช่องว่างสีขาวทั้งหมด

  • เรียกใช้ลูปแรกเพื่อตรวจสอบจากไบต์สุดท้ายสำหรับอักขระเว้นวรรคและลดความยาวของตัวแปร
  • เรียกใช้ลูปที่สองเพื่อตรวจสอบจากไบต์แรกสำหรับอักขระช่องว่างและลดความยาวของตัวแปรและตัวชี้อักขระถ่านเพิ่มขึ้น
  • สุดท้ายถ้าตัวแปรความยาวมากกว่า 0 ให้ใช้strndupเพื่อสร้างบัฟเฟอร์สตริงใหม่โดยไม่รวมช่องว่าง

เพียง nit Nit นิดหน่อยstrndup()ไม่ได้เป็นส่วนหนึ่งของมาตรฐาน C แต่มีเพียง Posix แต่เนื่องจากใช้งานได้ง่ายจึงไม่ใช่เรื่องใหญ่
Patrick Schlüter

trim_space("")NULLผลตอบแทน ""ผมคาดว่าตัวชี้ไปยัง ควรจะเป็น int len; UB เมื่อ size_t len;isspace(in[len - 1])in[len - 1] < 0
chux - Reinstate Monica

การเริ่มต้นwhile (isspace((unsigned char) *in) in++;ก่อนlen = strlen(in);จะมีประสิทธิภาพมากกว่าในภายหลังwhile(len && *in && isspace(*in)) ++in, --len;
chux - Reinstate 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 ครั้งใหญ่" Nope ผู้เขียนต้นฉบับนั้นมีความซื่อสัตย์
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. Backtrack ที่ส่วนท้ายของสตริง (เช่นค้นหาจุดสิ้นสุดของสตริงแล้วค้นหาไปข้างหลังจนกระทั่งพบอักขระที่ไม่ใช่ช่องว่าง) หรือ
  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()ไม่ควรผ่านการโต้แย้งเพราะมันจะเรียกพฤติกรรมที่ไม่ได้กำหนดค่าเชิงลบที่แตกต่างกว่าchar EOF
chqrlie

0

คุณคิดอย่างไรเกี่ยวกับการใช้ฟังก์ชั่น StrTrim ที่กำหนดไว้ในส่วนหัว Shlwapi.h. มันตรงไปตรงมาค่อนข้างจะกำหนดด้วยตัวคุณเอง
รายละเอียดสามารถพบได้ที่:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx

หากคุณมี
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
นี้จะให้ausCaptainเป็นไม่ได้"GeorgeBailey""GeorgeBailey "


0

เพื่อตัดสายของฉันจากทั้งสองด้านฉันใช้ oldie แต่ gooody;) มันสามารถตัดแต่งอะไรที่มี ascii น้อยกว่าช่องว่างหมายความว่า chars ควบคุมจะถูกตัดแต่งด้วย!

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)ถูกกำหนดให้เป็นเท็จคุณสามารถลดความซับซ้อนของฟังก์ชั่นทั้งสอง นอกจากนี้ย้ายmemmove()ภายในifบล็อก
chqrlie

0

ที่นี่ฉันใช้การจัดสรรหน่วยความจำแบบไดนามิกเพื่อตัดทอนสตริงอินพุตให้กับฟังก์ชั่น trimStr ก่อนอื่นเราจะพบว่ามีอักขระที่ไม่ว่างเปล่าจำนวนมากในสตริงอินพุต จากนั้นเราจัดสรรอาร์เรย์อักขระที่มีขนาดนั้นและดูแลอักขระที่สิ้นสุดด้วยค่า null เมื่อเราใช้ฟังก์ชั่นนี้เราจำเป็นต้องเพิ่มหน่วยความจำภายในฟังก์ชั่นหลัก

#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.