Qual è il modo migliore per tagliare std :: string?


812

Attualmente sto usando il seguente codice per tagliare tutto a destra std::stringsnei miei programmi:

std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

Funziona bene, ma mi chiedo se ci sono alcuni casi finali in cui potrebbe fallire?

Naturalmente, le risposte con alternative eleganti e anche la soluzione di assetto sinistro sono benvenute.


549
Le risposte a questa domanda sono una testimonianza di quanto manca la libreria standard C ++.
Idan K,

83
@IdanK E non ha ancora questa funzione in C ++ 11.
quantico

44
@IdanK: Fantastico, vero? Guardate tutte le opzioni concorrenti che ora abbiamo a nostra disposizione, non gravate da un'idea di una singola persona di " il modo in cui dobbiamo farlo"!
Corse di leggerezza in orbita

59
La funzionalità @LightnessRacesinOrbit all'interno di un tipo, beh, questa è una decisione di progettazione e l'aggiunta di una funzione trim a una stringa potrebbe (almeno in c ++) non essere comunque la soluzione migliore - ma non fornire alcun modo standard per farlo, lasciando invece che tutti si preoccupino le stesse questioni così piccole ancora e ancora, sicuramente non aiutano nessuno
codifica il

27
Puoi chiederti perché le funzioni di taglio non sono integrate nella std::stringclasse, quando sono funzioni come queste che rendono altri linguaggi così piacevoli da usare (ad esempio Python).
Ciao Arrivederci

Risposte:


648

EDIT Da c ++ 17, alcune parti della libreria standard sono state rimosse. Fortunatamente, a partire da c ++ 11, abbiamo lambda che sono una soluzione superiore.

#include <algorithm> 
#include <cctype>
#include <locale>

// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
        return !std::isspace(ch);
    }));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
        return !std::isspace(ch);
    }).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}

// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}

// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}

// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

Grazie a https://stackoverflow.com/a/44973498/524503 per aver proposto la soluzione moderna.

Risposta originale:

Tendo a utilizzare uno di questi 3 per le mie esigenze di taglio:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start
static inline std::string &ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
    return s;
}

// trim from end
static inline std::string &rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
    return s;
}

// trim from both ends
static inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}

Sono abbastanza autoesplicativi e funzionano molto bene.

EDIT : A proposito, ho std::ptr_funlì per aiutare a chiarire std::isspaceperché in realtà esiste una seconda definizione che supporta le versioni locali. Questo avrebbe potuto essere un cast lo stesso, ma tendo a piacermi meglio.

EDIT : per rispondere ad alcuni commenti sull'accettazione di un parametro come riferimento, modificandolo e restituendolo. Sono d'accordo. Un'implementazione che preferirei probabilmente sarebbe due serie di funzioni, una per il posto e una che ne fa una copia. Una migliore serie di esempi sarebbe:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}

// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}

// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}

// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

Sto mantenendo la risposta originale sopra per il contesto e nell'interesse di mantenere ancora disponibile la risposta votata.


28
Questo codice non funzionava su alcune stringhe internazionali (shift-jis nel mio caso, memorizzato in uno std :: string); Ho finito per usare boost::trimper risolvere il problema.
Tom,

5
Userei i puntatori invece dei riferimenti, in modo che dal callpoint sia molto più facile capire che queste funzioni modificano la stringa in atto, invece di creare una copia.
Marco Leogrande,

3
Nota che con isspace puoi facilmente ottenere un comportamento indefinito con caratteri non ASCII impilati-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes

10
Perché la statica? È qui che sarebbe preferito uno spazio dei nomi anonimo?
Trevor Hickey,

3
@TrevorHickey, certo, potresti usare uno spazio dei nomi anonimo, se preferisci.
Evan Teran,

417

L'uso degli algoritmi di stringa di Boost sarebbe più semplice:

#include <boost/algorithm/string.hpp>

std::string str("hello world! ");
boost::trim_right(str);

strè adesso "hello world!". C'è anche trim_lefte trim, che taglia entrambi i lati.


Se si aggiunge il _copysuffisso a uno dei nomi delle funzioni precedenti, ad es. trim_copy, La funzione restituirà una copia ritagliata della stringa anziché modificarla tramite un riferimento.

Se aggiungi il _ifsuffisso a uno dei nomi delle funzioni precedenti, ad esempio trim_copy_if, puoi tagliare tutti i caratteri che soddisfano il tuo predicato personalizzato, al contrario dei soli spazi bianchi.


7
Dipende dalle impostazioni locali. La mia locale predefinita (VS2005, en) significa che vengono tagliate le schede, gli spazi, i ritorni a capo, le nuove righe, le schede verticali e i feed dei moduli.
MattyT,

117
Boost è un martello così massiccio per un problema così piccolo.
Casey Rodarmor,

143
@rodarmor: Boost risolve molti piccoli problemi. È un martello enorme che risolve molto.
Nicol Bolas,

123
Boost è un insieme di martelli di dimensioni diverse che risolvono molti problemi diversi.
Ibrahim,

11
@rodarmor Dici che come se Boost fosse un monolito del tutto o niente, dove includere una delle sue intestazioni in qualche modo infligge l'intera cosa al proprio programma. Il che chiaramente non è il caso. A proposito, non ho mai usato Boost, prima.
underscore_d,

61

Utilizzare il codice seguente per tagliare a destra (finali) spazi e caratteri di tabulazione da std::strings( ideone ):

// trim trailing spaces
size_t endpos = str.find_last_not_of(" \t");
size_t startpos = str.find_first_not_of(" \t");
if( std::string::npos != endpos )
{
    str = str.substr( 0, endpos+1 );
    str = str.substr( startpos );
}
else {
    str.erase(std::remove(std::begin(str), std::end(str), ' '), std::end(str));
}

E solo per bilanciare le cose, includerò anche il codice trim sinistro ( ideone ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}

4
Questo non rileverà altre forme di spazi bianchi ... newline, avanzamento riga, ritorno carrello in particolare.
Tom,

1
Giusto. Devi personalizzarlo per lo spazio bianco che stai cercando di tagliare. La mia particolare applicazione prevedeva solo spazi e schede, ma è possibile aggiungere \ n \ r per catturare gli altri.
Bill the Lizard

5
str.substr(...).swap(str)è meglio. Salva un compito.
updogliu,

4
@updogliu Non utilizzerà l'assegnazione di traslochi basic_string& operator= (basic_string&& str) noexcept;?
Nurettin,

8
Questa risposta non modifica le stringhe che sono TUTTI gli spazi. Che è un fallimento.
Tom Andersen,

56

Quello che stai facendo è bello e robusto. Ho usato lo stesso metodo per molto tempo e devo ancora trovare un metodo più veloce:

const char* ws = " \t\n\r\f\v";

// trim from end of string (right)
inline std::string& rtrim(std::string& s, const char* t = ws)
{
    s.erase(s.find_last_not_of(t) + 1);
    return s;
}

// trim from beginning of string (left)
inline std::string& ltrim(std::string& s, const char* t = ws)
{
    s.erase(0, s.find_first_not_of(t));
    return s;
}

// trim from both ends of string (right then left)
inline std::string& trim(std::string& s, const char* t = ws)
{
    return ltrim(rtrim(s, t), t);
}

Fornendo i personaggi da tagliare, hai la flessibilità di tagliare i caratteri non bianchi e l'efficienza di tagliare solo i personaggi che vuoi tagliare.


se modifichi l'ordine trim, ovvero lo rtrim(ltrim(s, t), t)
renderai

1
@CITBL La funzione interna viene eseguita per prima, in modo che si taglierà da sinistra prima di tagliare da destra. Penso che sarebbe meno efficiente no?
Galik

Esattamente. Il mio errore
CITBL

se usi basic_string e template sul CharT puoi farlo per tutte le stringhe, usa semplicemente una variabile template per lo spazio bianco in modo da poterla usare come ws <CharT>. tecnicamente a quel punto potresti renderlo pronto per c ++ 20 e contrassegnarlo anche come costexpr poiché ciò implica in linea
Beached

@Beached Indeed. È un po 'complicato inserire una risposta qui. Ho scritto funzioni modello per questo ed è certamente abbastanza coinvolto. Ho provato diversi approcci e non sono ancora sicuro di quale sia il migliore.
Galik,

55

Un po 'tardi alla festa, ma non importa. Ora C ++ 11 è qui, abbiamo lambda e variabili automatiche. Quindi la mia versione, che gestisce anche tutti gli spazi bianchi e stringhe vuote, è:

#include <cctype>
#include <string>
#include <algorithm>

inline std::string trim(const std::string &s)
{
   auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
   return (wsback<=wsfront ? std::string() : std::string(wsfront,wsback));
}

Potremmo creare un iteratore inverso da wsfronte usarlo come condizione di terminazione nel secondo, find_if_notma questo è utile solo nel caso di una stringa per tutti gli spazi bianchi, e gcc 4.8 almeno non è abbastanza intelligente da inferire il tipo di iteratore inverso ( std::string::const_reverse_iterator) con auto. Non so quanto sia costoso costruire un iteratore inverso, quindi YMMV qui. Con questa modifica, il codice è simile al seguente:

inline std::string trim(const std::string &s)
{
   auto  wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}

9
Bello. +1 da me. Peccato che C ++ 11 non abbia introdotto trim () in std :: string e reso la vita più semplice per tutti.
Milan Babuškov,

3
Voglio sempre una chiamata di funzione per tagliare la stringa, invece di implementarla
linquize il

22
Per quello che vale, non c'è bisogno di usare quella lambda. Puoi semplicemente passare std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob

4
+1 per probabilmente l'unica risposta con l'implementazione che esegue solo una copia di stringa O (N).
Alexei Averchenko,

4
I compilatori di @vmrob non sono necessariamente così intelligenti. fare ciò che dici è ambiguo:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers,

42

Prova questo, funziona per me.

inline std::string trim(std::string& str)
{
    str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
    str.erase(str.find_last_not_of(' ')+1);         //surfixing spaces
    return str;
}

12
Se la tua stringa non contiene spazi suffissi, questo verrà cancellato a partire da npos + 1 == 0 e cancellerai l'intera stringa.
mhsmith,

3
@rgove Per favore, spiega. str.find_last_not_of(x)restituisce la posizione del primo carattere non uguale a x. Restituisce npos solo se nessun carattere non corrisponde a x. Nell'esempio, se non ci sono spazi suffissi, restituirà l'equivalente di str.length() - 1, producendo essenzialmente str.erase((str.length() - 1) + 1).Cioè, a meno che non mi sbagli terribilmente.
Travis,

5
Questo dovrebbe restituire std :: string e per evitare di richiamare inutilmente il costruttore di copie.
Heksesang,

7
Sono confuso perché questo restituisce una copia dopo aver modificato il parametro return?
Galik,

3
@MiloDC La mia confusione è il motivo per cui restituire una copia anziché un riferimento. Per me ha più senso tornare std::string&.
Galik,

25

Mi piace la soluzione di Tzaman, l'unico problema è che non taglia una stringa contenente solo spazi.

Per correggere quel 1 difetto, aggiungi uno str.clear () tra le 2 linee del rifinitore

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;

Bello :) il problema con entrambe le nostre soluzioni, tuttavia, è che taglieranno entrambe le estremità; non è possibile crearne una ltrimo rtrimcome questa
Tzaman,

44
Buono, ma non può gestire la stringa con spazi bianchi interni. es. trim (abc def ") -> abc, solo abc rimasto.
liheyuan

Una buona soluzione se sai che non ci saranno spazi bianchi interni!
Elliot Gorokhovsky,

Questo è facile e facile, ma è anche piuttosto lento poiché la stringa viene copiata dentro e fuori std::stringstream.
Galik,

23

http://ideone.com/nFVtEo

std::string trim(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it))
        it++;

    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit))
        rit++;

    return std::string(it, rit.base());
}

1
Finalmente una soluzione elegante per il trim spaziale di base ... :)
jave.web

Come funziona: questa è una soluzione simile a una copia: trova la posizione del primo carattere che non è spazio ( it) e viceversa: posizione del carattere dopo la quale ci sono solo spazi ( rit) - dopodiché restituisce una stringa appena creata == una copia della parte della stringa originale - una parte basata su quegli iteratori ...
jave.web

Grazie, ha funzionato per me: std: string s = "Oh noez: space \ r \ n"; std :: string clean = trim (s);
Alexx Roche,

15

Nel caso di una stringa vuota, il codice presuppone che l'aggiunta di 1 a string::npos0. string::npossia di tipo string::size_type, che è senza segno. Pertanto, fai affidamento sul comportamento di overflow dell'aggiunta.


23
Lo stai dicendo come se fosse un male. Il comportamento di overflow dell'intero con segno è errato.
MSalters,

2
Aggiungendo 1a std::string::npos deve dare 0secondo il C++ Standard. Quindi è una buona ipotesi su cui si può fare assolutamente affidamento.
Galik,

13

Violato da Cplusplus.com

std::string choppa(const std::string &t, const std::string &ws)
{
    std::string str = t;
    size_t found;
    found = str.find_last_not_of(ws);
    if (found != std::string::npos)
        str.erase(found+1);
    else
        str.clear();            // str is all whitespace

    return str;
}

Questo funziona anche per il caso null. :-)


4
Questo è solo rtrim, nonltrim
ub3rst4r

1
^ ti dispiace usare find_first_not_of? È relativamente facile modificarlo.
Abhinav Gauniyal,

13

Con C ++ 17 puoi usare basic_string_view :: remove_prefix e basic_string_view :: remove_suffix :

std::string_view trim(std::string_view s)
{
    s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
    s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));

    return s;
}

Una bella alternativa:

std::string_view ltrim(std::string_view s)
{
    s.remove_prefix(std::distance(s.cbegin(), std::find_if(s.cbegin(), s.cend(),
         [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view rtrim(std::string_view s)
{
    s.remove_suffix(std::distance(s.crbegin(), std::find_if(s.crbegin(), s.crend(),
        [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view trim(std::string_view s)
{
    return ltrim(rtrim(s));
}

Non sono sicuro di ciò che stai testando, ma nel tuo esempio std :: find_first_not_of restituirà std :: string :: npos e std :: string_view :: size restituirà 4. Il minimo è ovviamente quattro, il numero di elementi da rimosso da std :: string_view :: remove_prefix . Sia gcc 9.2 che clang 9.0 gestiscono questo correttamente: godbolt.org/z/DcZbFH
Phidelux

1
Grazie! Mi sembra buono.
Contango

11

La mia soluzione basata sulla risposta di @Bill the Lizard .

Si noti che queste funzioni restituiranno la stringa vuota se la stringa di input non contiene altro che spazi bianchi.

const std::string StringUtils::WHITESPACE = " \n\r\t";

std::string StringUtils::Trim(const std::string& s)
{
    return TrimRight(TrimLeft(s));
}

std::string StringUtils::TrimLeft(const std::string& s)
{
    size_t startpos = s.find_first_not_of(StringUtils::WHITESPACE);
    return (startpos == std::string::npos) ? "" : s.substr(startpos);
}

std::string StringUtils::TrimRight(const std::string& s)
{
    size_t endpos = s.find_last_not_of(StringUtils::WHITESPACE);
    return (endpos == std::string::npos) ? "" : s.substr(0, endpos+1);
}

9

La mia risposta è un miglioramento rispetto alla risposta migliore per questo post che taglia i caratteri di controllo e gli spazi (0-32 e 127 nella tabella ASCII ).

std::isgraphdetermina se un personaggio ha una rappresentazione grafica, quindi puoi usarlo per modificare la risposta di Evan per rimuovere qualsiasi personaggio che non ha una rappresentazione grafica da entrambi i lati di una stringa. Il risultato è una soluzione molto più elegante:

#include <algorithm>
#include <functional>
#include <string>

/**
 * @brief Left Trim
 *
 * Trims whitespace from the left end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& ltrim(std::string& s) {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
    std::ptr_fun<int, int>(std::isgraph)));
  return s;
}

/**
 * @brief Right Trim
 *
 * Trims whitespace from the right end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& rtrim(std::string& s) {
  s.erase(std::find_if(s.rbegin(), s.rend(),
    std::ptr_fun<int, int>(std::isgraph)).base(), s.end());
  return s;
}

/**
 * @brief Trim
 *
 * Trims whitespace from both ends of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& trim(std::string& s) {
  return ltrim(rtrim(s));
}

Nota: in alternativa dovresti essere in grado di utilizzare std::iswgraphse hai bisogno di supporto per caratteri ampi, ma dovrai anche modificare questo codice per abilitare la std::wstringmanipolazione, che è qualcosa che non ho testato (vedi la pagina di riferimento per std::basic_stringesplorare questa opzione) .


3
std :: ptr_fun è obsoleto
johnbakers l'

8

Con C ++ 11 è arrivato anche un modulo di espressione regolare , che ovviamente può essere usato per tagliare spazi iniziali o finali.

Forse qualcosa del genere:

std::string ltrim(const std::string& s)
{
    static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
    return std::regex_replace(s, lws, "");
}

std::string rtrim(const std::string& s)
{
    static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
    return std::regex_replace(s, tws, "");
}

std::string trim(const std::string& s)
{
    return ltrim(rtrim(s));
}

8

Questo è quello che uso. Continua a rimuovere lo spazio dalla parte anteriore e, se è rimasto qualcosa, fai lo stesso dalla parte posteriore.

void trim(string& s) {
    while(s.compare(0,1," ")==0)
        s.erase(s.begin()); // remove leading whitespaces
    while(s.size()>0 && s.compare(s.size()-1,1," ")==0)
        s.erase(s.end()-1); // remove trailing whitespaces
}

8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   

2
Sarebbe leggermente più efficiente se esegui quelli nell'ordine opposto e tagli prima da destra prima di invocare uno spostamento tagliando la sinistra.
Galik,

7

Per quello che vale, ecco un'implementazione dell'assetto con un occhio di riguardo per le prestazioni. È molto più veloce di molte altre routine di assetto che ho visto in giro. Invece di usare iteratori e std :: find, usa stringhe c e indici grezzi. Ottimizza i seguenti casi speciali: dimensione 0 stringa (non fare nulla), stringa senza spazi bianchi da tagliare (non fare nulla), stringa con solo spazi vuoti finali da tagliare (basta ridimensionare la stringa), stringa interamente bianca (basta cancellare la stringa) . E infine, nel peggiore dei casi (stringa con spazi bianchi iniziali), fa del suo meglio per eseguire una costruzione di copie efficiente, eseguendo solo 1 copia e spostando quella copia al posto della stringa originale.

void TrimString(std::string & str)
{ 
    if(str.empty())
        return;

    const auto pStr = str.c_str();

    size_t front = 0;
    while(front < str.length() && std::isspace(int(pStr[front]))) {++front;}

    size_t back = str.length();
    while(back > front && std::isspace(int(pStr[back-1]))) {--back;}

    if(0 == front)
    {
        if(back < str.length())
        {
            str.resize(back - front);
        }
    }
    else if(back <= front)
    {
        str.clear();
    }
    else
    {
        str = std::move(std::string(str.begin()+front, str.begin()+back));
    }
}

@bmgda forse in teoria la versione più veloce potrebbe avere questa firma: extern "C" void string_trim (char ** begin_, char ** end_) ... Catch my drift?

6

Un modo elegante di farlo può essere come

std::string & trim(std::string & str)
{
   return ltrim(rtrim(str));
}

E le funzioni di supporto sono implementate come:

std::string & ltrim(std::string & str)
{
  auto it =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( str.begin() , it);
  return str;   
}

std::string & rtrim(std::string & str)
{
  auto it =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( it.base() , str.end() );
  return str;   
}

E una volta che hai messo tutto questo a posto, puoi scrivere anche questo:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}

6

Implementazione Trim C ++ 11:

static void trim(std::string &s) {
     s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); }));
     s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end());
}

5

Immagino che se inizi a chiedere il "modo migliore" per tagliare una stringa, direi che una buona implementazione sarebbe quella che:

  1. Non alloca stringhe temporanee
  2. Presenta sovraccarichi per il taglio sul posto e il taglio della copia
  3. Può essere facilmente personalizzato per accettare diverse sequenze / logiche di validazione

Ovviamente ci sono troppi modi diversi per affrontarlo e dipende sicuramente da ciò di cui hai effettivamente bisogno. Tuttavia, la libreria standard C ha ancora alcune funzioni molto utili in <string.h>, come memchr. C'è un motivo per cui C è ancora considerato il miglior linguaggio per IO - il suo standard è pura efficienza.

inline const char* trim_start(const char* str)
{
    while (memchr(" \t\n\r", *str, 4))  ++str;
    return str;
}
inline const char* trim_end(const char* end)
{
    while (memchr(" \t\n\r", end[-1], 4)) --end;
    return end;
}
inline std::string trim(const char* buffer, int len) // trim a buffer (input?)
{
    return std::string(trim_start(buffer), trim_end(buffer + len));
}
inline void trim_inplace(std::string& str)
{
    str.assign(trim_start(str.c_str()),
        trim_end(str.c_str() + str.length()));
}

int main()
{
    char str [] = "\t \nhello\r \t \n";

    string trimmed = trim(str, strlen(str));
    cout << "'" << trimmed << "'" << endl;

    system("pause");
    return 0;
}

3

Non sono sicuro che il tuo ambiente sia lo stesso, ma nel mio caso la stringa vuota causerà l'interruzione del programma. Vorrei concludere la chiamata con un if (! S.empty ()) o utilizzare Boost come già accennato.


3

Ecco cosa mi è venuto in mente:

std::stringstream trimmer;
trimmer << str;
trimmer >> str;

L'estrazione del flusso elimina automaticamente gli spazi bianchi, quindi funziona come un fascino.
Abbastanza pulito ed elegante, se lo dico io stesso. ;)


15
Hmm; ciò presuppone che la stringa non abbia spazi bianchi interni (ad es. spazi). L'OP ha solo detto che voleva tagliare gli spazi bianchi a sinistra o a destra.
SuperElectric

3

Contribuendo alla mia soluzione al rumore. trimper impostazione predefinita, crea una nuova stringa e restituisce quella modificata mentre trim_in_placemodifica la stringa passata ad essa. La trimfunzione supporta la semantica di spostamento c ++ 11.

#include <string>

// modifies input string, returns input

std::string& trim_left_in_place(std::string& str) {
    size_t i = 0;
    while(i < str.size() && isspace(str[i])) { ++i; };
    return str.erase(0, i);
}

std::string& trim_right_in_place(std::string& str) {
    size_t i = str.size();
    while(i > 0 && isspace(str[i - 1])) { --i; };
    return str.erase(i, str.size());
}

std::string& trim_in_place(std::string& str) {
    return trim_left_in_place(trim_right_in_place(str));
}

// returns newly created strings

std::string trim_right(std::string str) {
    return trim_right_in_place(str);
}

std::string trim_left(std::string str) {
    return trim_left_in_place(str);
}

std::string trim(std::string str) {
    return trim_left_in_place(trim_right_in_place(str));
}

#include <cassert>

int main() {

    std::string s1(" \t\r\n  ");
    std::string s2("  \r\nc");
    std::string s3("c \t");
    std::string s4("  \rc ");

    assert(trim(s1) == "");
    assert(trim(s2) == "c");
    assert(trim(s3) == "c");
    assert(trim(s4) == "c");

    assert(s1 == " \t\r\n  ");
    assert(s2 == "  \r\nc");
    assert(s3 == "c \t");
    assert(s4 == "  \rc ");

    assert(trim_in_place(s1) == "");
    assert(trim_in_place(s2) == "c");
    assert(trim_in_place(s3) == "c");
    assert(trim_in_place(s4) == "c");

    assert(s1 == "");
    assert(s2 == "c");
    assert(s3 == "c");
    assert(s4 == "c");  
}

3

Questo può essere fatto più semplicemente in C ++ 11 grazie all'aggiunta di back()e pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();

Anche l'approccio suggerito dall'OP non è male - solo un po 'più difficile da seguire.
nobar

3

Ecco la mia versione:

size_t beg = s.find_first_not_of(" \r\n");
return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" \r\n") - beg);

Ti manca l'ultimo personaggio. Un +1 in lunghezza risolve questo problema
galinette,

2

I metodi di cui sopra sono fantastici, ma a volte vuoi usare una combinazione di funzioni per quello che la tua routine considera spazi bianchi. In questo caso, usare i funzione per combinare le operazioni può diventare confuso, quindi preferisco un semplice loop che posso modificare per il trim. Ecco una funzione di trim leggermente modificata copiata dalla versione C qui su SO. In questo esempio, sto tagliando caratteri non alfanumerici.

string trim(char const *str)
{
  // Trim leading non-letters
  while(!isalnum(*str)) str++;

  // Trim trailing non-letters
  end = str + strlen(str) - 1;
  while(end > str && !isalnum(*end)) end--;

  return string(str, end+1);
}

2

Ecco un'implementazione diretta. Per un'operazione così semplice, probabilmente non dovresti usare costrutti speciali. La funzione build-in isspace () si occupa di varie forme di caratteri bianchi, quindi dovremmo approfittarne. Devi anche considerare casi speciali in cui la stringa è vuota o semplicemente un mucchio di spazi. Il trim sinistro o destro potrebbe essere derivato dal seguente codice.

string trimSpace(const string &str) {
   if (str.empty()) return str;
   string::size_type i,j;
   i=0;
   while (i<str.size() && isspace(str[i])) ++i;
   if (i == str.size())
      return string(); // empty string
   j = str.size() - 1;
   //while (j>0 && isspace(str[j])) --j; // the j>0 check is not needed
   while (isspace(str[j])) --j
   return str.substr(i, j-i+1);
}

2

Ecco una soluzione facile da capire per i principianti che non sono abituati a scrivere std::ovunque e non hanno ancora familiarità con la constcorrettezza, la iterators, la STL algorithm, ecc ...

#include <string>
#include <cctype> // for isspace
using namespace std;


// Left trim the given string ("  hello!  " --> "hello!  ")
string left_trim(string str) {
    int numStartSpaces = 0;
    for (int i = 0; i < str.length(); i++) {
        if (!isspace(str[i])) break;
        numStartSpaces++;
    }
    return str.substr(numStartSpaces);
}

// Right trim the given string ("  hello!  " --> "  hello!")
string right_trim(string str) {
    int numEndSpaces = 0;
    for (int i = str.length() - 1; i >= 0; i--) {
        if (!isspace(str[i])) break;
        numEndSpaces++;
    }
    return str.substr(0, str.length() - numEndSpaces);
}

// Left and right trim the given string ("  hello!  " --> "hello!")
string trim(string str) {
    return right_trim(left_trim(str));
}

Spero che sia d'aiuto...


1

Questa versione consente di tagliare spazi bianchi interni e non alfanumerici:

static inline std::string &trimAll(std::string &s)
{   
    if(s.size() == 0)
    {
        return s;
    }

    int val = 0;
    for (int cur = 0; cur < s.size(); cur++)
    {
        if(s[cur] != ' ' && std::isalnum(s[cur]))
        {
            s[val] = s[cur];
            val++;
        }
    }
    s.resize(val);
    return s;
}

1

Ancora un'altra opzione: rimuove uno o più caratteri da entrambe le estremità.

string strip(const string& s, const string& chars=" ") {
    size_t begin = 0;
    size_t end = s.size()-1;
    for(; begin < s.size(); begin++)
        if(chars.find_first_of(s[begin]) == string::npos)
            break;
    for(; end > begin; end--)
        if(chars.find_first_of(s[end]) == string::npos)
            break;
    return s.substr(begin, end-begin+1);
}
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.