Come tagliare gli spazi iniziali / finali in modo standard?


178

Esiste un metodo pulito, preferibilmente standard, per tagliare gli spazi bianchi iniziali e finali da una stringa in C? Mi piacerebbe il mio, ma penso che questo sia un problema comune con una soluzione altrettanto comune.

Risposte:


164

Se puoi modificare la stringa:

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

Se non è possibile modificare la stringa, è possibile utilizzare sostanzialmente lo stesso metodo:

// 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
Siamo spiacenti, la prima risposta non è affatto buona a meno che non ti interessi delle perdite di memoria. Ora hai due stringhe sovrapposte (l'originale, che ha gli spazi finali tagliati, e quello nuovo). Solo la stringa originale può essere liberata, ma se lo fai, la seconda punta alla memoria liberata.
David Nehme,

7
@nvl: non è stata allocata memoria, quindi non c'è memoria da liberare.
Adam Rosenfield,

15
@nvl: No. strè una variabile locale e la sua modifica non cambia il puntatore originale che viene passato. Le chiamate di funzione in C sono sempre pass-by-value, mai pass-by-reference.
Adam Rosenfield,

11
@Raj: non c'è nulla di intrinsecamente sbagliato nel restituire un indirizzo diverso da quello che è stato passato. Non è necessario qui che il valore restituito sia un argomento valido della free()funzione. Al contrario, ho progettato questo per evitare la necessità di allocazione della memoria per l'efficienza. Se l'indirizzo passato è stato allocato in modo dinamico, il chiamante è comunque responsabile della liberazione di tale memoria e il chiamante deve essere sicuro di non sovrascrivere quel valore con il valore restituito qui.
Adam Rosenfield,

3
Bisogna lanciare l'argomento per isspacea unsigned char, altrimenti si richiama un comportamento indefinito.
Roland Illig,

37

Eccone uno che sposta la stringa nella prima posizione del buffer. Potresti desiderare questo comportamento in modo che se hai allocato dinamicamente la stringa, puoi comunque liberarla sullo stesso puntatore che restituisce ():

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

Verifica della correttezza:

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

Il file di origine era trim.c. Compilato con 'cc -Wall trim.c -o trim'.


2
Bisogna lanciare l'argomento per isspacea unsigned char, altrimenti si richiama un comportamento indefinito.
Roland Illig,

@RolandIllig: Grazie, non ho mai capito che fosse necessario. Aggiustato.
indiv

@Simas: Perché dici così? La funzione chiama isspace()quindi perché dovrebbe esserci una differenza tra " "e "\n"? Ho aggiunto unit test per nuove linee e sembra OK per me ... ideone.com/bbVmqo
indiv

1
@indiv accederà al blocco di memoria non valido se allocato manualmente. Vale a dire questa linea: *(endp + 1) = '\0';. Il test di esempio sulla risposta utilizza un buffer di 64 che evita questo problema.
Simas,

1
@nolandda: grazie per il dettaglio. L'ho corretto e aggiornato il test per rilevare il sovraccarico del buffer poiché al momento non ho accesso a valgrind.
indiv

23

La mia soluzione. La stringa deve essere modificabile. Il vantaggio rispetto ad alcune delle altre soluzioni è che sposta la parte non spaziale all'inizio in modo da poter continuare a utilizzare il vecchio puntatore, nel caso in cui sia necessario liberarlo () in un secondo momento.

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

Questa versione crea una copia della stringa con strndup () invece di modificarla in posizione. strndup () richiede _GNU_SOURCE, quindi forse devi creare il tuo strndup () con malloc () e 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()invoca UB se sè ""come la prima isspace()chiamata sarebbe isspace(p[-1])e p[-1]non fa necessariamente riferimento a una posizione legale.
chux - Ripristina Monica il

1
Bisogna lanciare l'argomento per isspacea unsigned char, altrimenti si richiama un comportamento indefinito.
Roland Illig,

1
dovrebbe aggiungere if(l==0)return;per evitare str di lunghezza zero
ch271828n

11

Ecco la mia mini libreria C per tagliare a sinistra, a destra, entrambi, tutti, in posizione e separati, e tagliare un set di caratteri specificati (o uno spazio bianco per impostazione predefinita).

contenuto di 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

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

L'unica routine principale fa tutto. Si regola se src == dst , altrimenti funziona come le strcpyroutine. Taglia un set di caratteri specificato nel delim di stringao spazio bianco se nullo. Rifila a sinistra, a destra, entrambi e tutti (come tr). Non c'è molto da fare e scorre la stringa una sola volta. Alcune persone potrebbero lamentarsi del fatto che tagliare a destra inizia a sinistra, tuttavia, non è necessario alcun rinforzo che inizia comunque a sinistra. (In un modo o nell'altro devi arrivare alla fine della stringa per i giusti trim, quindi potresti anche fare il lavoro mentre procedi.) Potrebbero essere fatti degli argomenti su pipeline e dimensioni della cache e simili - chissà . Poiché la soluzione funziona da sinistra a destra e viene ripetuta una sola volta, può essere espansa per funzionare anche su stream. Limitazioni: non funziona su stringhe unicode .


2
Ho votato questo e so che è vecchio ma penso che ci sia un bug. dtab[*d]non espressi *da unsigned intprima di utilizzarla come un indice di matrice. Su un sistema con caratteri firmati, questo si leggerà fino a dtab[-127]che causerà bug e probabilmente arresti anomali.
Zan Lynx,

2
Potenziale comportamento indefinito attivo dtab[*delim++]perché charè necessario eseguire il cast dei valori dell'indice unsigned char. Il codice assume 8 bit char. delimdovrebbe essere dichiarato come const char *. dtab[0xFF & (unsigned int)*d]sarebbe più chiaro come dtab[(unsigned char)*d]. Il codice funziona su stringhe codificate UTF-8, ma non rimuove le sequenze di spaziatura non ASCII.
Chqrlie,

@ michael-plainer, sembra interessante. Perché non lo provi e lo metti su GitHub?
Daisuke Aramaki,

9

Ecco il mio tentativo di una funzione di trim sul posto semplice ma corretta.

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
Suggerisci di cambiare in while ((end >= begin) && isspace(str[end]))per impedire UB quando str is "" . Prevents str [-1] `.
chux - Ripristina Monica il

A proposito, devo cambiarlo in str [i - inizio + 1] per funzionare
truongnm,

1
Bisogna lanciare l'argomento per isspacea unsigned char, altrimenti si richiama un comportamento indefinito.
Roland Illig,

@RolandIllig, perché dovrebbe essere un comportamento indefinito? La funzione è destinata a funzionare con i caratteri.
wovano,

@wovano No, non lo è. Le funzioni di <ctype.h>hanno lo scopo di lavorare con ints, che rappresentano uno unsigned charo il valore speciale EOF. Vedi stackoverflow.com/q/7131026/225757 .
Roland Illig,

8

In ritardo per la festa

Caratteristiche:
1. Taglia rapidamente l'inizio, come in una serie di altre risposte.
2. Dopo essere arrivato alla fine, tagliare la destra con solo 1 test per loop. Come @ jfm3, ma funziona per una stringa tutta vuota.
3. Per evitare comportamenti indefiniti quando charè un segno, eseguire il charcast *ssu unsigned char.

Gestione del carattere "In tutti i casi l'argomento è un int, il cui valore deve essere rappresentabile come unsigned charo deve essere uguale al valore della macro EOF. Se l'argomento ha un altro valore, il comportamento non è definito." 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 ha commentato quanto sopra non sposta la stringa tagliata. Fare così....

// 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
Sì, finalmente qualcuno che conosce il comportamento indefinito del tipo.
Roland Illig,

2
@chux Penso che dovrebbe essere len = (size_t) (ps) +1; altrimenti l'ultima lettera si sovrappone.
theriver

4

Ecco una soluzione simile alla routine di modifica sul posto di @ adam-rosenfields ma senza ricorrere inutilmente a strlen (). Come @jkramer, la stringa viene regolata a sinistra all'interno del buffer in modo da poter liberare lo stesso puntatore. Non ottimale per stringhe di grandi dimensioni poiché non utilizza memmove. Include gli operatori ++ / - citati da @ jfm3. Test unitari basati su FCTX inclusi.

#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();

Questa soluzione è decisamente pericolosa! Se la stringa originale non contiene caratteri non bianchi, l'ultima riga di trim sovrascrive felicemente tutto ciò che precede a, se quei byte contengono byte "bianchi". Compila questo senza ottimizzazioni e guarda cosa succede a 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, grazie per la segnalazione di bug. Ho aggiornato la logica per evitare di uscire dal lato sinistro del buffer quando la stringa contiene solo spazi bianchi. Questa nuova versione risponde alle tue preoccupazioni?
Rhys Ulerich,

Gli avvocati di lingua probabilmente ti urlerebbero per il semplice pensiero di speculare sulla creazione di un puntatore al carattere che precede quello a cui "a" indica (che è ciò che farà il tuo '--p'). Nel mondo reale, probabilmente stai bene. Ma puoi anche cambiare '> =' in '>' e spostare il decremento di p in 'isspace (* - p)'.
Villemoes,

Penso che gli avvocati starebbero bene dato che sta solo confrontando un indirizzo senza toccarlo, ma mi piace anche il tuo suggerimento sul decremento. L'ho aggiornato di conseguenza. Grazie.
Rhys Ulerich,

1
doukremt, sei preoccupato che l'intero buffer dopo il foobar non sia pieno di zeri? Se è così, sarebbe un po 'più utile se lo dicessi esplicitamente piuttosto che lanciare pietre vaghe.
Rhys Ulerich,

3

Un altro, con una riga che fa il vero lavoro:

#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
Buona idea usare scanf; ma il suo funzionerà solo con una sola parola che potrebbe non essere ciò che l'OP voleva (cioè tagliare "abc" dovrebbe probabilmente dare come risultato "ab c", mentre la tua singola scansionef si limita a "a"). Quindi abbiamo bisogno di un ciclo e di un contatore per i caratteri saltati con l'identificatore di %nconversione, e alla fine è più semplice farlo a mano, temo.
Peter - Ripristina Monica il

Molto utile quando si desidera che la prima parola della stringa ignori qualsiasi spazio iniziale.
J ... S,

3

La maggior parte di queste risposte non mi è piaciuta perché hanno fatto una o più delle seguenti ...

  1. Restituito un puntatore diverso all'interno della stringa del puntatore originale (una specie di dolore per destreggiarsi tra due puntatori diversi alla stessa cosa).
  2. Ha fatto un uso gratuito di cose come strlen () che pre-itera l'intera stringa.
  3. Funzioni lib libere specifiche del sistema operativo non portatili.
  4. Backscanned.
  5. Usato il confronto con '' invece di isspace () in modo da preservare TAB / CR / LF.
  6. Memoria sprecata con grandi buffer statici.
  7. Cicli sprecati con funzioni ad alto costo come sscanf / sprintf .

Ecco la mia versione:

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
Bisogna lanciare l'argomento per isspacea unsigned char, altrimenti si richiama un comportamento indefinito.
Roland Illig,

Poiché questa risposta riguarda "Cicli sprecati", notare che il codice copia inutilmente l'intera puntura quando non c'è spazio. Un leader while (isspace((unsigned char) *szWrite)) szWrite++;lo impedirebbe. Il codice copia anche tutto lo spazio bianco finale.
chux - Ripristina Monica il

@chux questa implementazione si trasforma sul posto con puntatori di lettura e scrittura separati (invece di restituire un nuovo puntatore in una posizione diversa), quindi il suggerimento per saltare szWrite al primo non-spazio sulla linea-uno lascerebbe lo spazio iniziale in la stringa originale.
Jason Stewart,

@chux, hai ragione a dire che copia spazio vuoto finale (prima di aggiungere un valore nullo dopo l'ultimo carattere non spaziale), ma questo è il prezzo che ho scelto di pagare per evitare la pre-scansione della stringa. Per quantità modeste di WS finali, è più economico semplicemente copiare i byte piuttosto che eseguire una pre-scansione dell'intera stringa per l'ultimo carattere non WS. Per grandi quantità di WS finali, la pre-scansione varrebbe probabilmente la riduzione delle scritture.
Jason Stewart,

@chux, per la situazione "copie quando non c'è spazio", eseguire solo *szWrite = *szReadquando i puntatori non sono uguali salterebbe le scritture in quel caso, ma poi abbiamo aggiunto un altro confronto / ramo. Con la moderna CPU / MMU / BP, non ho idea se quel controllo sarebbe una perdita o un guadagno. Con processori e architetture di memoria più semplici, è più economico fare semplicemente la copia e saltare il confronto.
Jason Stewart,

2

Molto tardi alla festa ...

Soluzione di scansione in avanti a passaggio singolo senza backtracking. Ogni carattere nella stringa di origine viene testato esattamente una volta due volte. (Quindi dovrebbe essere più veloce della maggior parte delle altre soluzioni qui, specialmente se la stringa di origine ha molti spazi finali.)

Ciò include due soluzioni, una per copiare e tagliare una stringa sorgente in un'altra stringa di destinazione e l'altra per tagliare la stringa sorgente in posizione. Entrambe le funzioni usano lo stesso codice.

La stringa (modificabile) viene spostata sul posto, quindi il puntatore originale rimane invariato.

#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
Ogni carattere nella stringa di origine viene testato esattamente una volta : non proprio, la maggior parte dei caratteri nella stringa di origine vengono testati due volte: confrontati '\0'e quindi testati con isspace(). Sembra inutile testare tutti i personaggi con isspace(). Il backtracking dall'estremità della stringa dovrebbe essere più efficiente per i casi non patologici.
Chqrlie,

@chqrlie - Sì, ogni personaggio viene testato due volte. Mi piacerebbe vedere questo codice effettivamente testato, specialmente dato stringhe con molti spazi finali, rispetto ad altri algoritmi qui.
David R Tribble,

trim()OK. Caso d'angolo: trim2(char *d, const char *s)ha problemi quando si d,ssovrappongono e s < d.
chux - Ripristina Monica il

@chux - In quel caso d'angolo, come dovrebbe trim()comportarsi? Stai chiedendo di tagliare e copiare una stringa nella memoria occupata dalla stringa stessa. Diversamente memmove(), ciò richiede la determinazione della lunghezza della stringa sorgente prima di eseguire il trim stesso, il che richiede la scansione dell'intera stringa una volta in più. Meglio scrivere una rtrim2()funzione diversa che sappia copiare l'origine nella destinazione all'indietro e probabilmente accetta un argomento aggiuntivo della lunghezza della stringa di origine.
David R Tribble,

1

Non sono sicuro di cosa consideri "indolore".

Le stringhe C sono piuttosto dolorose. Possiamo trovare banalmente la prima posizione di carattere non spaziale:

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

Possiamo trovare l'ultima posizione del personaggio non di spazio bianco con due mosse banali simili:

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

(Ti ho risparmiato il dolore di usare gli operatori *e ++allo stesso tempo.)

La domanda ora è cosa fai con questo? Il tipo di dati a portata di mano non è in realtà un grande abstract robusto a Stringcui è facile pensare, ma invece a malapena più di un array di byte di archiviazione. Mancando di un robusto tipo di dati, è impossibile scrivere una funzione che farà lo stesso della chompfunzione di PHperytonby . Cosa sarebbe una tale funzione in C?


Funziona bene a meno che la stringa non sia composta da tutti gli spazi bianchi. Hai bisogno di un controllo una volta prima do { q--; } ...di sapere *q != 0.
chux - Ripristina Monica il

1

Utilizzare una libreria di stringhe , ad esempio:

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

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

... come dici tu questo è un problema "comune", sì, devi includere un #include o giù di lì e non è incluso in libc ma non andare a inventare il tuo lavoro di hacking memorizzando puntatori casuali e size_t in quel modo porta solo a buffer overflow.



1

Solo per continuare a crescere, un'altra opzione con una stringa modificabile:

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()restituisce un valore size_tche può superare l'intervallo di int. lo spazio bianco non è limitato al carattere spazio. Infine, ma soprattutto: comportamento indefinito strcpy(string, string + i * sizeof(char));perché le matrici di origine e destinazione si sovrappongono. Usa memmove()invece di strcpy().
Chqrlie,

@chqrlie hai ragione, hai appena incluso i tuoi suggerimenti. Capisco che la copia quando l'origine e la destinazione si sovrappongono può causare comportamenti indefiniti, ma voglio solo sottolineare che in questo caso particolare ciò non dovrebbe causare alcun problema poiché copieremo sempre da una posizione successiva della memoria all'inizio, Grazie per il feedback.
wallek876,

1
non importa come si sovrappongono gli array di origine e destinazione, è un comportamento indefinito. Non fare affidamento sul presupposto che la copia può avvenire un byte alla volta lungo indirizzi crescenti. Ho anche dimenticato di menzionare che while (isspace((int)string[i])) string[i--] = '\0';potrebbe andare oltre l'inizio della stringa. Dovresti combinare questo ciclo con le righe precedenti e seguenti e scriverewhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
chqrlie,

@chqrlie buon punto, una stringa con tutti gli spazi bianchi avrebbe fatto ricadere all'inizio, non ci avevo pensato.
wallek876,

In realtà, il mio suggerimento era errato in quanto endnon puntava al byte null finale e il end = ++i;problema persiste per le stringhe contenenti tutti i caratteri degli spazi bianchi. Ho appena corretto il codice.
Chqrlie,

1

So che ci sono molte risposte, ma inserisco la mia risposta qui per vedere se la mia soluzione è abbastanza buona.

// 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
Nota: isspace(*str)UB quando *str < 0.
chux - Ripristina Monica il

1
L'uso di size_t nè buono, tuttavia l'interfaccia non informa il chiamante in alcun modo quando nè troppo piccolo per una stringa tagliata completa. Consideratrim(out, 12, "delete data not")
chux - Ripristina Monica il

1

Il modo più semplice per saltare gli spazi iniziali in una stringa è, imho,

#include <stdio.h>

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

1
Questo non funzionerà per le stringhe con spazi nel mezzo, come " foo bar ".
David R Tribble,

1

Ok, questa è la mia opinione sulla domanda. Credo che sia la soluzione più concisa che modifica la stringa in atto ( freefunzionerà) ed evita qualsiasi UB. Per stringhe di piccole dimensioni, è probabilmente più veloce di una soluzione che coinvolge 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;
}

Il b > strtest è necessario solo una volta. *b = 0;necessario solo una volta.
chux - Ripristina Monica il

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 aiuta a tagliare tutti gli spazi bianchi.

  • Esegui un primo ciclo per controllare l'ultimo carattere dello spazio per il carattere e ridurre la variabile di lunghezza
  • Esegui un secondo ciclo per controllare dal primo byte il carattere dello spazio e ridurre la variabile lunghezza e aumentare il puntatore carattere.
  • Infine, se la variabile length è maggiore di 0, utilizzare strndupper creare un nuovo buffer di stringhe escludendo gli spazi.

Solo un po 'di nitpick, strndup()non fa parte dello standard C ma solo Posix. Ma poiché è abbastanza facile da implementare non è un grosso problema.
Patrick Schlüter,

trim_space("")ritorna NULL. Mi aspetto un puntatore a "". int len;dovrebbe essere size_t len;. isspace(in[len - 1])UB quando in[len - 1] < 0.
chux - Ripristina Monica il

Un'iniziale while (isspace((unsigned char) *in) in++;prima len = strlen(in);sarebbe più efficiente della successivawhile(len && *in && isspace(*in)) ++in, --len;
chux - Reinstate Monica il

0

Personalmente, mi piacerebbe il mio. Puoi usare strtok, ma devi prenderti cura di farlo (soprattutto se stai rimuovendo i personaggi principali) per sapere quale memoria è cosa.

Sbarazzarsi degli spazi finali è facile e abbastanza sicuro, poiché puoi semplicemente mettere uno 0 sopra la parte superiore dell'ultimo spazio, contando alla fine. Liberarsi degli spazi guida significa spostare le cose. Se vuoi farlo sul posto (probabilmente ragionevole) puoi semplicemente spostare tutto indietro di un personaggio fino a quando non c'è spazio iniziale. Oppure, per essere più efficiente, potresti trovare l'indice del primo carattere non spaziale e spostare tutto indietro di quel numero. Oppure, puoi semplicemente usare un puntatore al primo carattere non spaziale (ma poi devi stare attento allo stesso modo che fai con strtok).


4
strtok non è generalmente un ottimo strumento da utilizzare, anche perché non rientra. Se rimani all'interno di una singola funzione, può essere utilizzata in modo sicuro, ma se esiste la possibilità di thread o di chiamare altre funzioni che potrebbero utilizzare strtok, sei nei guai.
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
Questo mi ha fatto ridere perché pensavo che dreamlax avesse modificato la stringa di test per includere "fa schifo alla grande". No. L'autore originale è solo onesto.
James Morris,

1
Non usare questo codice. Produce un overflow del buffer.
Roland Illig,

0

Un po 'tardi al gioco, ma getterò le mie routine nella mischia. Probabilmente non sono i più efficienti in assoluto, ma credo che siano corretti e semplici ( rtrim()spingendo l'inviluppo della complessità):

#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
si dovrebbe lanciare l' charargomento per isspace()per (unsigned char)evitare un comportamento indefinito su valori potenzialmente negativi. Evitare anche di spostare la stringa se dentro ltrim()se non necessario.
Chqrlie,

0

La maggior parte delle risposte finora effettua una delle seguenti operazioni:

  1. Torna indietro alla fine della stringa (ad esempio trova la fine della stringa e cerca indietro fino a quando non viene trovato un carattere non spaziale) oppure
  2. Chiama strlen()prima, facendo un secondo passaggio attraverso l'intera stringa.

Questa versione effettua un solo passaggio e non esegue il backtrack. Quindi potrebbe funzionare meglio degli altri, anche se solo se è comune avere centinaia di spazi finali (il che non è insolito quando si tratta dell'output di una 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';
}

1
Se si è interessati alle prestazioni, non utilizzare strspn()e strcspn()in loop. Questo è molto inefficiente e il sovraccarico ridurrà il vantaggio non dimostrato del singolo passaggio in avanti. strlen()di solito viene espanso in linea con un codice molto efficiente, non una vera preoccupazione. Tagliare l'inizio e la fine della stringa sarà molto più veloce che testare ogni carattere nella stringa per il bianco anche nel caso speciale di stringhe con pochissimi caratteri o non bianchi.
Chqrlie,

0

Questa è l'implementazione più breve che mi viene in mente:

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
Che ne dici di questo: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

Queste funzioni modificheranno il buffer originale, quindi se allocato dinamicamente, il puntatore originale può essere liberato.

#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()invoca un comportamento indefinito sulla stringa vuota. lstrip()è inutilmente lento sulla stringa con una lunga porzione iniziale di caratteri bianchi. isspace()non dovrebbe essere passato un charargomento perché invoca un comportamento indefinito su valori negativi diversi da EOF.
Chqrlie,


0

Per tagliare le corde da entrambi i lati uso il vecchio ma il cattivo;) Può tagliare qualsiasi cosa con ASCII meno di uno spazio, il che significa che anche i caratteri di controllo saranno tagliati!

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

Dovresti usare size_tinvece di unsigned int. Il codice ha molti test ridondanti e richiama comportamenti indefiniti strncpy(strData,&strData[S],L)perché gli array di origine e destinazione si sovrappongono. Usa memmove()invece di strncpy().
Chqrlie,

In questo caso è ok poiché l'indirizzo di destinazione ha sempre un indice più piccolo rispetto alla sorgente, ma sì memmove sarà effettivamente migliore.
Деян Добромиров,

no non va bene. non importa come gli array di origine e destinazione si sovrappongano, invoca comportamenti indefiniti perché non è possibile fare ipotesi in modo sicuro sull'implementazione delle funzioni della libreria oltre le loro specifiche standard. I compilatori moderni tendono a trarre vantaggio ingiusto da situazioni con un potenziale comportamento indefinito, giocare al sicuro e stare lontano da UB, e non lasciare che i principianti facciano ipotesi non sicure.
Chqrlie,

0

Includo solo il codice perché il codice pubblicato finora sembra non ottimale (e non ho ancora il rappresentante per commentare).

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()è un'estensione GNU. Se non lo hai o qualcosa di equivalente, fai il tuo. Per esempio:

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

1
isspace(0)è definito come falso, è possibile semplificare entrambe le funzioni. Sposta anche l' memmove()interno del ifblocco.
Chqrlie,

0

Qui utilizzo l'allocazione dinamica della memoria per tagliare la stringa di input alla funzione trimStr. Innanzitutto, troviamo quanti caratteri non vuoti esistono nella stringa di input. Quindi, assegniamo un array di caratteri con quella dimensione e ci occupiamo del carattere nullo. Quando utilizziamo questa funzione, dobbiamo liberare la memoria all'interno della funzione principale.

#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

Ecco come lo faccio. Taglia la stringa in posizione, quindi non preoccuparti di deallocare una stringa restituita o perdere il puntatore su una stringa allocata. Potrebbe non essere la risposta più breve possibile, ma dovrebbe essere chiaro alla maggior parte dei lettori.

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

assolutamente chiaro per i lettori, ma strlen esegue un altro ciclo .. :)
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
Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo riguardo a come e / o perché risolve il problema migliorerebbe il valore a lungo termine della risposta.
Nic3500,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.