Bagaimana cara memangkas spasi memimpin / mengikuti di jalan standar?

177

Apakah ada metode bersih, lebih disukai standar pemangkasan spasi putih depan dan akhir dari string dalam C? Saya akan roll sendiri, tetapi saya akan berpikir ini adalah masalah umum dengan solusi yang sama-sama umum.

coledot
sumber

Jawaban:

164

Jika Anda dapat memodifikasi string:

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

Jika Anda tidak dapat mengubah string, pada dasarnya Anda dapat menggunakan metode yang sama:

// 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;
}
Adam Rosenfield
sumber
6
Maaf, jawaban pertama tidak baik sama sekali kecuali Anda tidak peduli dengan kebocoran memori. Anda sekarang memiliki dua string yang tumpang tindih (yang asli, yang memiliki spasi tambahan, dan yang baru). Hanya string asli yang dapat dibebaskan, tetapi jika Anda melakukannya, yang kedua menunjuk ke memori yang dibebaskan.
David Nehme
7
@ nvl: Tidak ada memori yang dialokasikan, jadi tidak ada memori untuk dikosongkan.
Adam Rosenfield
15
@ nvl: No. stradalah variabel lokal, dan mengubahnya tidak mengubah pointer asli yang dilewatkan. Panggilan fungsi dalam C selalu pass-by-value, tidak pernah pass-by-reference.
Adam Rosenfield
11
@ Raj: Tidak ada yang salah dengan mengembalikan alamat yang berbeda dari yang dilewatkan. Tidak ada persyaratan di sini bahwa nilai yang dikembalikan menjadi argumen free()fungsi yang valid . Justru sebaliknya - saya merancang ini untuk menghindari kebutuhan alokasi memori untuk efisiensi. Jika alamat yang diteruskan dialokasikan secara dinamis, maka penelepon masih bertanggung jawab untuk membebaskan memori itu, dan penelepon perlu memastikan untuk tidak menimpa nilai itu dengan nilai yang dikembalikan di sini.
Adam Rosenfield
3
Anda harus melemparkan argumen untuk isspaceuntuk unsigned char, jika Anda menjalankan perilaku undefined.
Roland Illig
37

Inilah salah satu yang menggeser string ke posisi pertama buffer Anda. Anda mungkin menginginkan perilaku ini sehingga jika Anda mengalokasikan string secara dinamis, Anda masih bisa membebaskannya pada pointer yang sama yang memangkas () mengembalikan:

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

Uji kebenaran:

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

File sumber tadinya trim.c. Dikompilasi dengan 'cc -Wall trim.c -o trim'.

indiv
sumber
2
Anda harus melemparkan argumen untuk isspaceuntuk unsigned char, jika Anda menjalankan perilaku undefined.
Roland Illig
@RollandIllig: Terima kasih, saya tidak pernah menyadari bahwa itu perlu. Memperbaikinya.
indiv
@Simas: Mengapa Anda mengatakan itu? Fungsi panggilan isspace()jadi mengapa ada perbedaan antara " "dan "\n"? Saya menambahkan unit test untuk baris baru dan terlihat OK dengan saya ... ideone.com/bbVmqo
indiv
1
@indiv akan mengakses blok memori yang tidak valid ketika dialokasikan secara manual. Yaitu baris ini: *(endp + 1) = '\0';. Contoh pengujian pada jawaban menggunakan buffer 64 yang menghindari masalah ini.
Simas
1
@nolandda: Terima kasih atas detailnya. Saya memperbaikinya dan memperbarui tes untuk mendeteksi buffer overrun karena saya tidak memiliki akses ke valgrind saat ini.
indiv
23

Solusi saya. String harus dapat diubah. Keuntungan di atas beberapa solusi lain yang memindahkan bagian non-ruang ke awal sehingga Anda dapat tetap menggunakan pointer lama, jika Anda harus membebaskan () nanti.

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

Versi ini membuat salinan string dengan strndup () alih-alih mengeditnya di tempat. strndup () membutuhkan _GNU_SOURCE, jadi mungkin Anda perlu membuat strndup sendiri () dengan malloc () dan 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);
}
Jkramer
sumber
4
trim()memanggil UB jika sadalah ""yang pertama isspace()panggilan akan isspace(p[-1])dan p[-1]tidak selalu referensi lokasi hukum.
chux - Reinstate Monica
1
Anda harus melemparkan argumen untuk isspaceuntuk unsigned char, jika Anda menjalankan perilaku undefined.
Roland Illig
1
harus ditambahkan if(l==0)return;untuk menghindari str nol panjang
ch271828n
11

Inilah perpustakaan mini C saya untuk memotong kiri, kanan, keduanya, semua, di tempat dan terpisah, dan memotong seperangkat karakter yang ditentukan (atau spasi putih secara default).

isi dari 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

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

Satu rutinitas utama melakukan semuanya. Ini terpotong pada tempatnya jika src == dst , jika tidak, itu berfungsi seperti strcpyrutinitas. Ini memangkas satu set karakter yang ditentukan dalam batasan string , atau spasi putih jika nol. Ini memotong kiri, kanan, keduanya, dan semua (seperti tr). Tidak ada banyak untuk itu, dan itu berulang di atas string hanya sekali. Beberapa orang mungkin mengeluh bahwa trim kanan mulai di sebelah kiri, namun, tidak ada strlen yang diperlukan yang dimulai dari kiri. (Dengan satu atau lain cara, Anda harus sampai ke akhir string untuk trim yang tepat, jadi sebaiknya Anda melakukan pekerjaan sambil jalan.) Mungkin ada argumen yang dibuat tentang pipelining dan ukuran cache dan semacamnya - siapa tahu . Karena solusinya bekerja dari kiri ke kanan dan hanya mengulangi sekali saja, solusinya dapat diperluas untuk bekerja pada stream juga. Keterbatasan: ini tidak bekerja pada unicode string.

Tembak Bulan
sumber
2
Saya membatalkan ini dan saya tahu yang lama tapi saya pikir ada bug. dtab[*d]tidak dilempar *dke unsigned intsebelum menggunakannya sebagai indeks array. Pada sistem dengan char yang ditandatangani ini akan membaca dtab[-127]yang akan menyebabkan bug dan mungkin crash.
Zan Lynx
2
Potensi perilaku tidak terdefinisi pada dtab[*delim++]karena charnilai indeks harus dilemparkan ke unsigned char. Kode mengasumsikan 8-bit char. delimharus dinyatakan sebagai const char *. dtab[0xFF & (unsigned int)*d]akan lebih jelas sebagai dtab[(unsigned char)*d]. Kode ini bekerja pada string yang dikodekan UTF-8, tetapi tidak akan menghapus urutan spasi ASCII non.
chqrlie
@ Michael-plainer, ini terlihat menarik. Mengapa Anda tidak mengujinya dan meletakkannya di GitHub?
Daisuke Aramaki
9

Berikut ini adalah upaya saya pada fungsi trim in-place yang sederhana namun benar.

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.
}
Orang Swiss
sumber
2
Sarankan perubahan untuk while ((end >= begin) && isspace(str[end]))mencegah UB ketika str is "" . Prevents str [-1] `.
chux - Reinstate Monica
Btw, saya harus mengubah ini ke str [i - begin +1] untuk bekerja
truongnm
1
Anda harus melemparkan argumen untuk isspaceuntuk unsigned char, jika Anda menjalankan perilaku undefined.
Roland Illig
@RolandIllig, mengapa itu perilaku yang tidak terdefinisi? Fungsi ini dimaksudkan untuk bekerja dengan karakter.
wovano
@wano Tidak, tidak. Fungsi dari <ctype.h>dimaksudkan untuk bekerja dengan int, yang mewakili unsigned charnilai khusus atau EOF. Lihat stackoverflow.com/q/7131026/225757 .
Roland Illig
8

Terlambat ke pesta trim

Fitur:
1. Pangkas awal dengan cepat, seperti pada sejumlah jawaban lainnya.
2. Setelah pergi ke akhir, memotong kanan dengan hanya 1 tes per loop. Seperti @ jfm3, tetapi berfungsi untuk semua string spasi putih)
3. Untuk menghindari perilaku yang tidak terdefinisi saat charditandatangani char, masukkan *ske unsigned char.

Penanganan karakter "Dalam semua kasus, argumennya adalah int, nilainya harus dinyatakan sebagai unsigned charatau sama dengan nilai makro EOF. Jika argumen memiliki nilai lain, perilaku tidak terdefinisi." 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 berkomentar di atas tidak menggeser string yang dipangkas. Untuk melakukannya ....

// 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);
}
chux - Pasang kembali Monica
sumber
3
Yay, akhirnya seseorang yang tahu tentang ctype perilaku tidak terdefinisi.
Roland Illig
2
@ chux Saya pikir itu harus len = (size_t) (ps) +1; jika tidak, huruf terakhir tumpang tindih.
theRiver
4

Berikut adalah solusi yang mirip dengan rutin modifikasi in-place @ adam-rosenfields tetapi tanpa harus menggunakan strlen (). Seperti @jkramer, string disetel ke kiri di dalam buffer sehingga Anda dapat membebaskan pointer yang sama. Tidak optimal untuk string besar karena tidak menggunakan memmove. Termasuk operator ++ / - yang @jfm3 sebutkan. Termasuk unit test berbasis 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();
Rhys Ulerich
sumber
Solusi ini benar-benar berbahaya! Jika string asli tidak mengandung karakter non-spasi putih, baris terakhir trim dengan senang hati menimpa apa pun yang mendahului a, jika byte tersebut kebetulan mengandung byte 'spasi putih'. Kompilasi ini tanpa optimasi dan lihat apa yang terjadi pada y: unsigned x = 0x20202020; char s [4] = ""; unsigned 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, terima kasih atas laporan bugnya. Saya telah memperbarui logika untuk menghindari berjalan dari sisi kiri buffer ketika string hanya berisi spasi putih. Apakah versi baru ini menjawab kekhawatiran Anda?
Rhys Ulerich
Pengacara bahasa mungkin akan meneriaki Anda hanya karena berspekulasi tentang membuat pointer ke char sebelum huruf 'a' menunjuk (yang akan dilakukan '- p' Anda). Di dunia nyata, Anda mungkin baik-baik saja. Tetapi Anda juga bisa mengubah '> =' ke '>' dan memindahkan penurunan p ke 'isspace (* - p)'.
Villemoes
Saya pikir pengacara akan baik-baik saja karena hanya membandingkan alamat tanpa menyentuhnya, tapi saya suka saran Anda tentang pengurangan juga. Saya telah memperbaruinya sesuai. Terima kasih.
Rhys Ulerich
1
doukremt, apakah kekhawatiran Anda bahwa seluruh buffer setelah foobar tidak diisi dengan nol? Jika demikian, itu akan sedikit lebih membantu jika Anda mengatakannya secara eksplisit daripada melemparkan batu yang tidak jelas.
Rhys Ulerich
3

Satu lagi, dengan satu baris melakukan pekerjaan nyata:

#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);
}
Daniel
sumber
1
Ide bagus untuk menggunakan scanf; tetapi itu hanya akan bekerja dengan satu kata yang mungkin bukan yang diinginkan OP (yaitu memangkas "abc" mungkin akan menghasilkan "ab c", sedangkan scanf tunggal Anda hanya menghasilkan "a"). Jadi kita perlu pengulangan, dan penghitung untuk loncatan karakter dengan %nspecifier konversi, dan pada akhirnya itu hanya lebih mudah untuk melakukannya dengan tangan, saya khawatir.
Peter - Pasang kembali Monica
Sangat berguna ketika Anda ingin kata pertama dari string mengabaikan spasi awal.
J ... S
3

Saya tidak suka sebagian besar jawaban ini karena mereka melakukan satu atau lebih dari yang berikut ...

  1. Mengembalikan pointer yang berbeda di dalam string pointer asli (agak sakit untuk menyulap dua pointer berbeda ke hal yang sama).
  2. Membuat penggunaan serampangan dari hal-hal seperti strlen () yang pre-iterate seluruh string.
  3. Digunakan fungsi lib non-portabel OS.
  4. Di-scan ulang.
  5. Digunakan perbandingan dengan '' alih-alih isspace () sehingga TAB / CR / LF dipertahankan.
  6. Memori yang terbuang dengan buffer statis yang besar.
  7. Siklus yang terbuang dengan fungsi berbiaya tinggi seperti sscanf / sprintf .

Ini versi saya:

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';
}
Jason Stewart
sumber
2
Anda harus melemparkan argumen untuk isspaceuntuk unsigned char, jika Anda menjalankan perilaku undefined.
Roland Illig
Karena jawaban ini berkaitan dengan "Siklus Terbuang", perhatikan bahwa kode tidak perlu menyalin seluruh sengatan ketika tidak ada ruang. Seorang pemimpin while (isspace((unsigned char) *szWrite)) szWrite++;akan mencegah hal itu. Code juga menyalin semua spasi putih yang tertinggal.
chux - Reinstate Monica
@Chux implementasi ini bermutasi di tempat dengan petunjuk baca & tulis terpisah (sebagai lawan mengembalikan pointer baru di lokasi yang berbeda), sehingga saran untuk melompat szWrite ke non-spasi pertama pada baris-satu akan meninggalkan ruang terdepan di string asli.
Jason Stewart
@ chux, Anda benar bahwa itu menyalin spasi putih (sebelum menambahkan nol setelah karakter non-spasi terakhir), tapi itu harga yang saya pilih untuk membayar untuk menghindari pra-pemindaian string. Untuk jumlah trailing WS yang sederhana, lebih murah untuk hanya menyalin byte daripada melakukan pra-pemindaian seluruh string untuk karakter non-WS yang terakhir. Untuk trailing WS dalam jumlah besar, pra-pemindaian mungkin layak dilakukan pengurangan dalam penulisan.
Jason Stewart
@ chux, untuk situasi "salinan ketika tidak ada ruang", hanya melakukan *szWrite = *szReadketika pointer tidak sama akan melewatkan penulisan dalam kasus itu, tapi kemudian kami telah menambahkan perbandingan / cabang lain. Dengan CPU / MMU / BP modern, saya tidak tahu apakah cek itu akan menjadi kerugian atau keuntungan. Dengan prosesor dan arsitektur memori yang lebih sederhana, lebih murah untuk hanya menyalin dan melewati perbandingan.
Jason Stewart
2

Sangat terlambat ke pesta ...

Solusi pemindaian lintasan tunggal tanpa backtracking. Setiap karakter dalam string sumber diuji tepat dua kali sekali . (Jadi itu harus lebih cepat daripada sebagian besar solusi lain di sini, terutama jika string sumber memiliki banyak ruang tambahan.)

Ini termasuk dua solusi, satu untuk menyalin dan memotong string sumber ke string tujuan lain, dan yang lainnya untuk memotong string sumber di tempatnya. Kedua fungsi menggunakan kode yang sama.

String (yang dapat dimodifikasi) dipindahkan di tempat, jadi penunjuk asli tetap tidak berubah.

#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);
}
David R Tribble
sumber
1
Setiap karakter dalam string sumber diuji tepat sekali : tidak juga, sebagian besar karakter dalam string sumber diuji dua kali: dibandingkan dengan '\0'dan kemudian diuji dengan isspace(). Tampaknya boros untuk menguji semua karakter isspace(). Mundur dari ujung tali harus lebih efisien untuk kasus non patologis.
chqrlie
@ chqrlie - Ya, setiap karakter diuji dua kali. Saya ingin melihat kode ini benar-benar diuji, terutama diberikan string dengan banyak spasi tambahan, dibandingkan dengan algoritma lain di sini.
David R Tribble
trim()BAIK. Kasus sudut: trim2(char *d, const char *s)memiliki masalah saat d,stumpang tindih dan s < d.
chux - Reinstate Monica
@ chux - Dalam kasus sudut itu, bagaimana seharusnya trim()bersikap? Anda meminta untuk memotong dan menyalin string ke memori yang ditempati oleh string itu sendiri. Tidak seperti memmove()ini, ini membutuhkan penentuan panjang string sumber sebelum melakukan trim itu sendiri, yang membutuhkan pemindaian seluruh string tambahan waktu. Lebih baik menulis rtrim2()fungsi berbeda yang tahu untuk menyalin sumber ke tujuan mundur, dan mungkin membutuhkan argumen panjang string sumber tambahan.
David R Tribble
1

Saya tidak yakin apa yang Anda anggap "tidak menyakitkan."

Senar C sangat menyakitkan. Kita dapat menemukan posisi karakter non-spasi putih pertama secara sepele:

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

Kita dapat menemukan posisi karakter non-spasi putih terakhir dengan dua gerakan sepele yang serupa:

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

(Saya telah menyelamatkan Anda dari rasa sakit menggunakan *dan ++operator pada saat yang sama.)

Pertanyaannya sekarang adalah apa yang Anda lakukan dengan ini? Datatype yang ada saat ini sebenarnya bukan abstrak Stringyang kuat dan besar yang mudah dipikirkan, tetapi sebenarnya hampir tidak lebih dari sebuah array byte penyimpanan. Karena tidak memiliki tipe data yang kuat, tidak mungkin untuk menulis fungsi yang akan melakukan hal yang sama seperti chompfungsi PHperytonby . Apa fungsi seperti itu di C kembali?

jfm3
sumber
Ini bekerja dengan baik kecuali jika string terdiri dari semua spasi putih. Perlu pemeriksaan satu kali sebelum do { q--; } ...tahu *q != 0.
chux - Reinstate Monica
1

Gunakan pustaka string , misalnya:

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

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

... seperti yang Anda katakan ini adalah masalah "umum", ya Anda harus menyertakan #include atau lebih dan itu tidak termasuk dalam libc tapi jangan menciptakan pekerjaan hack Anda sendiri menyimpan pointer acak dan size_t dengan cara itu hanya mengarah ke buffer overflows.

James Antill
sumber
1

Jika Anda menggunakan glib, maka Anda dapat menggunakan g_strstrip

sleepycal
sumber
1

Untuk menjaga pertumbuhan ini, satu opsi lagi dengan string yang dapat dimodifikasi:

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);
}
wallek876
sumber
1
strlen()mengembalikan a size_tyang dapat melebihi kisaran int. ruang putih tidak terbatas pada karakter ruang. Akhirnya tetapi yang paling penting: Perilaku tidak terdefinisi pada strcpy(string, string + i * sizeof(char));karena array sumber dan tujuan tumpang tindih. Gunakan memmove()sebagai ganti strcpy().
chqrlie
@ chqrlie Anda benar, sertakan saja saran Anda. Saya mengerti bahwa penyalinan ketika sumber dan tujuan tumpang tindih dapat menyebabkan perilaku tidak terdefinisi, tetapi hanya ingin menunjukkan bahwa dalam kasus khusus ini seharusnya tidak menyebabkan masalah karena kita akan selalu menyalin dari posisi memori nanti ke awal, Terima kasih untuk umpan baliknya.
wallek876
1
tidak masalah bagaimana array sumber dan tujuan tumpang tindih, itu adalah perilaku yang tidak terdefinisi. Jangan mengandalkan asumsi bahwa penyalinan dapat dilakukan satu byte pada satu waktu bersamaan dengan meningkatnya alamat. Saya juga lupa menyebutkan bahwa while (isspace((int)string[i])) string[i--] = '\0';mungkin loop melampaui awal string. Anda harus menggabungkan loop ini dengan baris sebelumnya dan berikut dan tuliswhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
chqrlie
@chqrlie poin bagus, string dengan semua spasi putih akan menyebabkan loop melewati awal, tidak memikirkan itu.
wallek876
Sebenarnya, saran saya salah karena endtidak menunjuk ke null byte yang tertinggal dan Anda end = ++i;masih memiliki masalah untuk string yang berisi semua karakter spasi putih. Saya baru saja memperbaiki kodenya.
chqrlie
1

Saya tahu ada banyak jawaban, tetapi saya mengirim jawaban saya di sini untuk melihat apakah solusi saya cukup baik.

// 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;
}
Ekeyme Mo
sumber
2
Catatan: isspace(*str)UB kapan *str < 0.
chux
1
Penggunaannya size_t nbagus, namun antarmuka tidak memberi tahu pemanggil dengan cara apa pun ketika nterlalu kecil untuk string yang dipangkas sepenuhnya. Pertimbangkantrim(out, 12, "delete data not")
chux - Reinstate Monica
1

Cara termudah untuk melewati spasi dalam string adalah, imho,

#include <stdio.h>

int main()
{
char *foo="     teststring      ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
    return 0;
}
Zibri
sumber
1
Ini tidak akan berfungsi untuk string dengan spasi di tengah, seperti " foo bar ".
David R Tribble
1

Ok, inilah pendapat saya. Saya percaya ini adalah solusi paling ringkas yang memodifikasi string di tempat ( freeakan bekerja) dan menghindari UB. Untuk string kecil, itu mungkin lebih cepat daripada solusi yang melibatkan 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;
}
poby
sumber
The b > strtes hanya dibutuhkan sekali. *b = 0;hanya dibutuhkan sekali.
chux
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 membantu memangkas semua ruang putih.

  • Jalankan loop pertama untuk memeriksa dari byte terakhir untuk karakter spasi dan mengurangi variabel panjang
  • Jalankan loop kedua untuk memeriksa dari byte pertama untuk karakter spasi dan mengurangi variabel panjang dan penambahan pointer char.
  • Akhirnya jika variabel panjang lebih dari 0, maka gunakan strndupuntuk membuat buffer string baru dengan mengecualikan spasi.
rashok
sumber
Hanya sedikit nitpick, strndup()bukan bagian dari standar C tetapi hanya Posix. Tetapi karena cukup mudah untuk diimplementasikan, itu bukan masalah besar.
Patrick Schlüter
trim_space("")kembali NULL. Saya akan mengharapkan pointer ke "". int len;seharusnya size_t len;. isspace(in[len - 1])UB kapan in[len - 1] < 0.
chux - Reinstate Monica
Inisial while (isspace((unsigned char) *in) in++;sebelumnya len = strlen(in);akan lebih efisien daripada yang belakanganwhile(len && *in && isspace(*in)) ++in, --len;
chux - Reinstate Monica
0

Secara pribadi, saya akan roll sendiri. Anda dapat menggunakan strtok, tetapi Anda harus berhati-hati melakukannya (terutama jika Anda menghapus karakter utama) sehingga Anda tahu memori apa itu.

Menyingkirkan spasi tambahan itu mudah, dan cukup aman, karena Anda bisa menempatkan 0 di atas spasi terakhir, menghitung mundur dari akhir. Menyingkirkan ruang terdepan berarti memindahkan berbagai hal. Jika Anda ingin melakukannya di tempat (mungkin masuk akal) Anda bisa terus menggeser semuanya kembali satu karakter sampai tidak ada ruang terkemuka. Atau, agar lebih efisien, Anda bisa menemukan indeks karakter non-spasi pertama, dan menggeser semuanya ke belakang dengan angka itu. Atau, Anda bisa menggunakan pointer ke karakter non-spasi pertama (tapi kemudian Anda harus berhati-hati dengan cara yang sama seperti yang Anda lakukan dengan strtok).

Ben
sumber
4
strtok umumnya bukan alat yang sangat baik untuk digunakan - paling tidak karena tidak masuk kembali. Jika Anda tetap berada di dalam satu fungsi, itu dapat digunakan dengan aman, tetapi jika ada kemungkinan utas atau memanggil fungsi lain yang mungkin sendiri menggunakan strtok, Anda dalam masalah.
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;
}
Balkrishna Talele
sumber
3
Ini membuat saya tertawa karena saya pikir dreamlax telah mengedit string tes untuk memasukkan "sucks big time". Nggak. Penulis asli hanya jujur.
James Morris
1
Jangan gunakan kode ini. Ini menghasilkan buffer overflow.
Roland Illig
0

Agak terlambat ke pertandingan, tapi aku akan membuang rutinitasku ke medan. Mereka mungkin bukan yang paling efisien, tapi saya percaya itu benar dan sederhana (dengan rtrim()mendorong amplop kompleksitas):

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

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

    Michael Burr
    [email protected]
    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));
}
Michael Burr
sumber
1
Anda harus melemparkan charargumen isspace()ke (unsigned char)untuk menghindari perilaku tidak terdefinisi pada nilai negatif yang berpotensi. Juga hindari memindahkan string jika dalam ltrim()jika tidak perlu.
chqrlie
0

Sebagian besar jawaban sejauh ini melakukan salah satu dari yang berikut:

  1. Mundur kembali di akhir string (yaitu menemukan ujung string dan kemudian mencari mundur sampai karakter non-spasi ditemukan,) atau
  2. Panggil strlen()dulu, buat pass kedua melalui seluruh string.

Versi ini hanya membuat satu operan dan tidak mundur. Oleh karena itu mungkin berkinerja lebih baik daripada yang lain, meskipun hanya jika itu umum untuk memiliki ratusan ruang tambahan (yang tidak biasa ketika berhadapan dengan output dari query 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';
}
menemukan
sumber
1
Jika Anda khawatir dengan kinerja, jangan gunakan strspn()dan strcspn()dalam loop ketat. Ini sangat tidak efisien dan overhead akan mengerdilkan keuntungan yang tidak terbukti dari satu umpan maju. strlen()biasanya diperluas sesuai dengan kode yang sangat efisien, bukan masalah nyata. Memotong awal dan akhir string akan jauh lebih cepat daripada menguji setiap karakter dalam string untuk putih bahkan dalam kasus khusus string dengan sangat sedikit atau tidak ada karakter non-putih.
chqrlie
0

Ini adalah implementasi sesingkat mungkin yang dapat saya pikirkan:

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
}
Michał Gawlas
sumber
1
Bagaimana dengan ini: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

Fungsi-fungsi ini akan memodifikasi buffer asli, jadi jika dialokasikan secara dinamis, pointer asli dapat dibebaskan.

#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);
}
Telc
sumber
rstrip()memanggil perilaku yang tidak terdefinisi pada string kosong. lstrip()tidak perlu lambat pada string dengan bagian awal panjang karakter spasi. isspace()tidak boleh dilewatkan charargumen karena memunculkan perilaku tidak terdefinisi pada nilai negatif yang berbeda dari EOF.
chqrlie
0

Apa pendapat Anda tentang menggunakan fungsi StrTrim yang didefinisikan dalam header Shlwapi.h.? Ini lurus ke depan, bukan mendefinisikan Anda sendiri.
Detailnya dapat ditemukan di:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx

Jika Anda memiliki
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
ini akan memberi ausCaptainsebagai "GeorgeBailey"tidak "GeorgeBailey ".

Carthi
sumber
0

Untuk memangkas dawai saya dari kedua sisi saya menggunakan oldie tetapi gooody;) Ini dapat memangkas apa pun dengan ascii kurang dari satu ruang, yang berarti bahwa chars kontrol akan dipangkas juga!

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;
}
Деян Добромиров
sumber
Anda harus menggunakan size_tbukan unsigned int. Kode memiliki banyak tes berlebihan dan mengaktifkan perilaku tidak terdefinisi strncpy(strData,&strData[S],L)karena array sumber dan tujuan tumpang tindih. Gunakan memmove()sebagai ganti strncpy().
chqrlie
Dalam hal ini tidak apa-apa karena alamat tujuan selalu memiliki indeks lebih kecil dari sumbernya, tapi ya memmove memang lebih baik.
Деян Добромиров
tidak itu tidak baik. tidak masalah bagaimana array sumber dan tujuan tumpang tindih, itu memanggil perilaku yang tidak terdefinisi karena Anda tidak dapat dengan aman membuat asumsi tentang implementasi fungsi perpustakaan di luar spesifikasi standar mereka. Kompiler modern cenderung mengambil keuntungan dari situasi yang tidak adil dengan potensi perilaku yang tidak terdefinisi, bermain aman dan menjauh dari UB, dan jangan biarkan pemula membuat asumsi yang tidak aman.
chqrlie
0

Saya hanya memasukkan kode karena kode yang diposting sejauh ini tampaknya kurang optimal (dan saya belum memiliki perwakilan untuk berkomentar.)

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()adalah ekstensi GNU. Jika Anda tidak memilikinya atau setara, gulirkan sendiri. Sebagai contoh:

r = strdup(s + start);
r[end-start] = '\0';
sfink
sumber
1
isspace(0)didefinisikan sebagai salah, Anda dapat menyederhanakan kedua fungsi. Juga pindahkan bagian memmove()dalam ifblok.
chqrlie
0

Di sini saya menggunakan alokasi memori dinamis untuk memotong string input ke fungsi trimStr. Pertama, kami menemukan berapa banyak karakter tidak kosong yang ada di string input. Kemudian, kami mengalokasikan array karakter dengan ukuran itu dan merawat karakter terminasi nol. Ketika kita menggunakan fungsi ini, kita perlu membebaskan memori di dalam fungsi utama.

#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;
}
saeed_falahat
sumber
0

Inilah cara saya melakukannya. Ini memotong string di tempat, jadi tidak perlu khawatir tentang membatalkan alokasi string yang dikembalikan atau kehilangan pointer ke string yang dialokasikan. Ini mungkin bukan jawaban sesingkat mungkin, tetapi harus jelas bagi sebagian besar pembaca.

#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';
    }
}
Isaac To
sumber
benar-benar jelas untuk pembaca, tetapi strlen melakukan loop lain .. :)
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;
}
Mitch Laber
sumber
2
Sementara kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang bagaimana dan / atau mengapa memecahkan masalah akan meningkatkan nilai jangka panjang jawaban.
Nic3500