Qual é a melhor maneira de aparar std :: string?

812

Atualmente, estou usando o seguinte código para aparar à direita todos os std::stringsmeus programas:

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

Funciona bem, mas gostaria de saber se há alguns casos finais em que pode falhar.

Obviamente, respostas com alternativas elegantes e também solução à esquerda são bem-vindas.

Milan Babuškov
fonte
549
As respostas a esta pergunta são uma prova de como está faltando a biblioteca padrão C ++.
Idan K
83
@IdanK E ainda não possui essa função no C ++ 11.
quantum
44
@IdanK: Ótimo, não é! Veja todas as opções concorrentes que agora temos à nossa disposição, livres da idéia de uma única pessoa de " a maneira que devemos fazer"!
Lightness Races em órbita
59
A funcionalidade @LightnessRacesinOrbit dentro de um tipo, bem, isso é uma decisão de design, e adicionar uma função trim a uma string pode (pelo menos em c ++) não ser a melhor solução de qualquer maneira - mas não fornecer uma maneira padrão de fazê-lo, deixando todos preocupados os mesmos problemas tão pequenos repetidamente, certamente também não está ajudando ninguém
codeling
27
Você pode questionar por que as funções de recorte não são incorporadas à std::stringclasse, quando são funções como essas que tornam outras linguagens tão agradáveis ​​de usar (Python, por exemplo).
HelloGoodbye

Respostas:

648

EDIT Desde c ++ 17, algumas partes da biblioteca padrão foram removidas. Felizmente, começando com o c ++ 11, temos lambdas que são uma solução superior.

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

Agradecemos a https://stackoverflow.com/a/44973498/524503 por apresentar a solução moderna.

Resposta original:

Costumo usar um destes três para minhas necessidades de corte:

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

Eles são bastante auto-explicativos e funcionam muito bem.

EDIT : BTW, eu tenho std::ptr_funlá para ajudar a desambiguar, std::isspaceporque na verdade existe uma segunda definição que suporta localidades. Este poderia ter sido um elenco da mesma forma, mas eu costumo gostar mais disso.

EDIT : Para endereçar alguns comentários sobre aceitar um parâmetro por referência, modificá-lo e retorná-lo. Concordo. Uma implementação que eu provavelmente preferiria seria dois conjuntos de funções, um para o local e outro que faz uma cópia. Um melhor conjunto de exemplos seria:

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

Mantenho a resposta original acima, porém, por contexto e no interesse de manter a resposta mais votada ainda disponível.

Evan Teran
fonte
28
Este código estava falhando em algumas strings internacionais (shift-jis no meu caso, armazenadas em um std :: string); Acabei usando boost::trimpara resolver o problema.
Tom
5
Eu usaria ponteiros em vez de referências, para que, a partir do ponto de chamada, seja muito mais fácil entender que essas funções editam a string no lugar, em vez de criar uma cópia.
Marco Leogrande
3
Note-se que com isspace você pode facilmente obter um comportamento indefinido com caracteres não-ASCII stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes
10
Por que a estática? É aqui que um espaço para nome anônimo seria preferido?
Trevor Hickey
3
@TrevorHickey, com certeza, você pode usar um espaço para nome anônimo, se preferir.
Evan Teran
417

Usar os algoritmos de string do Boost seria mais fácil:

#include <boost/algorithm/string.hpp>

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

stré agora "hello world!". Há também trim_lefte trim, que apara ambos os lados.


Se você adicionar _copysufixo a qualquer um dos nomes de funções acima trim_copy, por exemplo , a função retornará uma cópia cortada da string em vez de modificá-la através de uma referência.

Se você adicionar _ifsufixo a qualquer um dos nomes de funções acima trim_copy_if, por exemplo , poderá cortar todos os caracteres que satisfazem seu predicado personalizado, em vez de apenas espaços em branco.

Leon Timmermans
fonte
7
Depende da localidade. Meu código do idioma padrão (VS2005, pt) significa que guias, espaços, retornos de carro, novas linhas, guias verticais e feeds de formulário são cortados.
MattyT
117
O impulso é um martelo tão grande para um problema tão pequeno.
Casey Rodarmor 27/03
143
@rodarmor: O Boost resolve muitos pequenos problemas. É um martelo enorme que resolve muito.
Nicol Bolas
123
Boost é um conjunto de martelos de diversos tamanhos, resolvendo muitos problemas diferentes.
Ibrahim
11
@rodarmor Você diz isso como se o Boost fosse um monólito do tipo tudo ou nada, onde a inclusão de um de seus cabeçalhos de alguma forma inflige a coisa toda no programa de alguém. O que claramente não é o caso. Btw, eu nunca usei Boost, fwiw.
Underscore_d
61

Use o código a seguir para aparar à direita (à direita) espaços e caracteres de tabulação de 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 só para equilibrar as coisas, também incluirei o código de compensação esquerdo ( ideona ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}
Bill the Lizard
fonte
4
Isso não detectará outras formas de espaço em branco ... nova linha, avanço de linha, retorno de carro em particular.
7242 Tom
1
Direita. Você precisa personalizá-lo para o espaço em branco que deseja aparar. Meu aplicativo em particular esperava apenas espaços e guias, mas você pode adicionar \ n \ r para capturar os outros.
Bill Bill Lizard
5
str.substr(...).swap(str)é melhor. Salve uma atribuição.
updogliu
4
@updogliu Não usará a atribuição de movimentação basic_string& operator= (basic_string&& str) noexcept;?
Nurettin
8
Esta resposta não altera seqüências de caracteres que são todos os espaços. O que é um fracasso.
Tom Andersen
56

O que você está fazendo é bom e robusto. Eu uso o mesmo método há muito tempo e ainda não encontrei um método mais rápido:

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

Ao fornecer os caracteres a serem aparados, você tem a flexibilidade de aparar caracteres que não sejam espaços em branco e a eficiência de aparar apenas os caracteres que deseja aparar.

Galik
fonte
se você alterar a ordem em trim, ou seja, torná-lo rtrim(ltrim(s, t), t)ele vai ser um pouco mais eficiente
CITBL
1
@CITBL A função interna é executada primeiro, para que você corte da esquerda antes de cortar da direita. Eu acho que seria menos eficiente, não?
Galik
Exatamente. Meu erro
CITBL 7/01/19
se você usar basic_string e template no CharT, poderá fazer isso para todas as strings, basta usar uma variável de template para o espaço em branco, para usá-lo como ws <CharT>. tecnicamente nesse ponto você poderia torná-lo pronto para c ++ 20 e marcá-lo constexpr também como isso implica em linha
encalhada
@Beached De fato. Um pouco complicado para responder aqui. Eu escrevi funções de modelo para isso e certamente está bastante envolvido. Eu tentei várias abordagens diferentes e ainda não tenho certeza qual é a melhor.
Galik
55

Um pouco tarde para a festa, mas não importa. Agora que o C ++ 11 está aqui, temos variáveis ​​lambdas e auto. Portanto, minha versão, que também lida com espaços em branco e strings vazios, é:

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

Poderíamos criar um iterador reverso wsfronte usá-lo como condição de finalização no segundo, find_if_notmas isso só é útil no caso de uma cadeia de espaços em branco, e o gcc 4.8 pelo menos não é inteligente o suficiente para inferir o tipo do iterador reverso ( std::string::const_reverse_iterator) com auto. Eu não sei o quão caro é a construção de um iterador reverso, então YMMV aqui. Com essa alteração, o código fica assim:

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());
}
David G
fonte
9
Agradável. +1 de mim. Pena que o C ++ 11 não introduziu trim () no std :: string e tornou a vida mais fácil para todos.
Milan Babuškov 31/07
3
Eu sempre quero uma chamada de função para aparar string, em vez de implementá-la
linquize
22
Para o que vale, não há necessidade de usar esse lambda. Você pode simplesmente passar std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob
4
+1 para provavelmente a única resposta com a implementação que faz apenas uma cópia de seqüência de caracteres O (N).
Alexei Averchenko
4
Os compiladores @vmrob não são necessariamente tão inteligentes. fazendo o que você diz é ambígua:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers
42

Tente isso, funciona para mim.

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;
}
user818330
fonte
12
Se a sua string não contiver espaços com sufixo, isso será apagado a partir de npos + 1 == 0 e você excluirá a string inteira.
mhsmith
3
@rgove Por favor, explique. str.find_last_not_of(x)retorna a posição do primeiro caractere diferente de x. Ele retorna npos apenas se nenhum caractere não corresponder a x. No exemplo, se não houver espaços com sufixo, ele retornará o equivalente a str.length() - 1, produzindo essencialmente str.erase((str.length() - 1) + 1).Isto é, a menos que eu esteja terrivelmente enganado.
Travis
5
Isso deve retornar std :: string e para evitar chamar desnecessariamente o construtor de cópia.
heksesang
7
Estou confuso por que isso retorna uma cópia depois de modificar o parâmetro de retorno?
Galik
3
@MiloDC Minha confusão é por que retornar uma cópia em vez de uma referência. Faz mais sentido para mim voltar std::string&.
Galik
25

Eu gosto da solução do tzaman, o único problema é que ele não apara uma string contendo apenas espaços.

Para corrigir essa falha, adicione str.clear () entre as duas linhas do aparador

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
Michaël Schoonbrood
fonte
Bom :) o problema com ambas as nossas soluções, porém, é que elas cortam as duas extremidades; não pode fazer um ltrimou rtrimassim.
Tsaman 07/07/10
44
Bom, mas não pode lidar com string com espaço em branco interno. por exemplo, trim (abc def ") -> abc, apenas abc restante.
liheyuan
Uma boa solução se você souber que não haverá espaço em branco interno!
Elliot Gorokhovsky
Isso é agradável e fácil, mas também é bastante lento, pois a cadeia é copiada para dentro e para fora da 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());
}
Pushkoff
fonte
1
Solução elegante para a guarnição espaço básico, finalmente ... :)
jave.web
Como isso funciona: Esta é uma solução semelhante a cópia - encontra a posição do primeiro caractere que não é space ( it) e reverse: posição do caractere após o qual existem apenas espaços ( rit) - depois disso, ele retorna uma string recém-criada == uma cópia da parte da string original - uma parte com base nessas iterators ...
jave.web
Obrigado, trabalhou para mim: std: string s = "Oh noez: espaço \ r \ n"; std :: string clean = trim (s);
Alexx Roche #
15

No caso de uma cadeia vazia, seu código pressupõe que adicionar 1 a string::npos0. string::nposfor do tipo string::size_typenão assinado. Assim, você está confiando no comportamento de adição de transbordamento.

Greg Hewgill
fonte
23
Você está dizendo isso como se fosse ruim. O comportamento de estouro de número inteiro assinado é ruim.
MSalters 20/10/08
2
Adicionando 1a std::string::npos deve dar de 0acordo com o C++ Standard. Portanto, é uma boa suposição que pode ser absolutamente invocada.
Galik
13

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

Isso funciona também para o caso nulo. :-)

Paul Nathan
fonte
4
Isto é apenas rtrim, não #ltrim
ub3rst4r 14/03
1
^ Você se importa em usar find_first_not_of? É relativamente fácil modificá-lo.
Abhinav Gauniyal
13

Com o C ++ 17, você pode usar 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;
}

Uma boa 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));
}
Phidelux
fonte
Não tenho certeza do que você está testando, mas no seu exemplo, std :: find_first_not_of retornará std :: string :: npos e std :: string_view :: size retornará 4. O mínimo é obviamente quatro, o número de elementos a serem removido por std :: string_view :: remove_prefix . O gcc 9.2 e o clang 9.0 lidam com isso corretamente: godbolt.org/z/DcZbFH
Phidelux
1
Obrigado! Parece bom para mim.
Contango 16/03
11

Minha solução com base na resposta de @Bill the Lizard .

Observe que essas funções retornarão a sequência vazia se a sequência de entrada contiver nada além de espaço em branco.

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);
}
DavidRR
fonte
9

Minha resposta é uma melhoria na resposta superior desta publicação que apara caracteres de controle e espaços (0-32 e 127 na tabela ASCII ).

std::isgraphdetermina se um caractere tem uma representação gráfica; portanto, você pode usá-lo para alterar a resposta de Evan para remover qualquer caractere que não tenha uma representação gráfica de ambos os lados de uma sequência. O resultado é uma solução muito mais 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: Como alternativa, você poderá usá- std::iswgraphlo se precisar de suporte para caracteres largos, mas também precisará editar esse código para ativar a std::wstringmanipulação, algo que não testei (consulte a página de referência para std::basic_stringexplorar esta opção) .

Clay Freeman
fonte
3
std :: ptr_fun está obsoleto
johnbakers 08/12
8

Com o C ++ 11, também veio um módulo de expressão regular , que obviamente pode ser usado para aparar espaços à esquerda ou à direita.

Talvez algo parecido com isto:

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));
}
Algum cara programador
fonte
8

É isso que eu uso. Continue removendo o espaço da frente e, se sobrar alguma coisa, faça o mesmo por trás.

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
}
synaptik
fonte
8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
freeboy1015
fonte
2
Seria um pouco mais eficiente se você fizer as que estão na ordem oposta e aparar da direita primeiro antes de chamar uma mudança aparando a esquerda.
Galik
7

Para o que vale a pena, aqui está uma implementação elegante com um olho no desempenho. É muito mais rápido do que muitas outras rotinas de corte que eu já vi por aí. Em vez de usar iteradores e std :: found, ele usa strings e índices c brutos. Otimiza os seguintes casos especiais: tamanho 0 string (não faça nada), string sem espaço em branco para aparar (não faça nada), string com apenas espaço em branco à esquerda para aparar (apenas redimensione a string), string que seja totalmente em branco (limpe a string) . E, finalmente, na pior das hipóteses (string com espaço em branco à esquerda), ele faz o possível para executar uma construção de cópia eficiente, executando apenas 1 cópia e movendo essa cópia no lugar da string original.

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));
    }
}
mbgda
fonte
@bmgda talvez, teoricamente, a versão mais rápida possa ter esta assinatura: extern "C" void string_trim (char ** begin_, char ** end_) ... Entendeu?
6

Uma maneira elegante de fazer isso pode ser como

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

E as funções de suporte são implementadas como:

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 uma vez que você tenha todas essas opções, você também pode escrever:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}
jha-G
fonte
6

Apare a implementação do 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());
}
GutiMac
fonte
5

Eu acho que se você começar a pedir a "melhor maneira" de cortar uma string, eu diria que uma boa implementação seria aquela que:

  1. Não aloca seqüências temporárias
  2. Possui sobrecargas para aparar no local e aparar cópias
  3. Pode ser facilmente personalizado para aceitar diferentes sequências / lógicas de validação

Obviamente, existem muitas maneiras diferentes de abordar isso e isso definitivamente depende do que você realmente precisa. No entanto, a biblioteca padrão C ainda possui algumas funções muito úteis em <string.h>, como o memchr. Há uma razão pela qual C ainda é considerado o melhor idioma para IO - seu stdlib é pura eficiência.

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;
}
Jorma Rebane
fonte
3

Não tenho certeza se o seu ambiente é o mesmo, mas no meu, o caso de cadeia vazia fará com que o programa seja interrompido. Gostaria de quebrar essa chamada de apagar com um if (! S.empty ()) ou usar o Boost, como já mencionado.

Steve
fonte
3

Aqui está o que eu vim com:

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

A extração de fluxo elimina os espaços em branco automaticamente, portanto, isso funciona como um encanto.
Muito limpo e elegante também, se é que digo. ;)

tzaman
fonte
15
Hmm; isso pressupõe que a cadeia não tenha espaços em branco internos (por exemplo, espaços). O OP disse apenas que queria aparar os espaços em branco à esquerda ou à direita.
SuperElectric
3

Contribuindo com minha solução para o barulho. trimO padrão é criar uma nova string e retornar a modificada enquanto trim_in_placemodifica a string passada para ela. A trimfunção suporta semântica de movimento 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");  
}
vmrob
fonte
3

Isso pode ser feito de maneira mais simples no C ++ 11 devido à adição de back()e pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();
nobar
fonte
A abordagem sugerida pelo OP também não é ruim - apenas um pouco mais difícil de seguir.
Nobar
3

Aqui está a minha versão:

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);
nulleight
fonte
Está faltando o último caractere. Um +1 no comprimento resolve isso
galinette
2

Os métodos acima são ótimos, mas às vezes você deseja usar uma combinação de funções para o que sua rotina considera como espaço em branco. Nesse caso, o uso de functores para combinar operações pode ficar confuso, por isso prefiro um loop simples que posso modificar para o ajuste. Aqui está uma função de recorte ligeiramente modificada, copiada da versão C aqui no SO. Neste exemplo, estou aparando caracteres não alfanuméricos.

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);
}
Corwin Joy
fonte
2

Aqui está uma implementação direta. Para uma operação tão simples, você provavelmente não deve usar nenhuma construção especial. A função isspace () incorporada cuida de várias formas de caracteres em branco, portanto devemos tirar proveito dela. Você também deve considerar casos especiais em que a string está vazia ou simplesmente vários espaços. O corte à esquerda ou à direita pode ser derivado do código a seguir.

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);
}
Kemin Zhou
fonte
2

Aqui está uma solução fácil de entender para iniciantes que não costumam escrever em std::todos os lugares e ainda não estão familiarizados com const-correctness, iterators, STL algorithms, etc ...

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

Espero que ajude...

cute_ptr
fonte
1

Esta versão apara espaços em branco internos e não alfanuméricos:

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;
}
Brian
fonte
1

Ainda outra opção - remove um ou mais caracteres de ambas as extremidades.

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);
}
Brian W.
fonte