Pergunta

Java possui um método de divisão conveniente:

String str = "The quick brown fox";
String[] results = str.split(" ");

Existe uma maneira fácil de fazer isso em C++?

Foi útil?

Solução

Seu caso simples pode ser facilmente construído usando o std::string::find método.No entanto, dê uma olhada Boost.Tokenizer.É ótimo.O Boost geralmente tem algumas ferramentas de cordas muito legais.

Outras dicas

O Aumentar o tokenizador class pode tornar esse tipo de coisa bastante simples:

#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer< char_separator<char> > tokens(text, sep);
    BOOST_FOREACH (const string& t, tokens) {
        cout << t << "." << endl;
    }
}

Atualizado para C++ 11:

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int, char**)
{
    string text = "token, test   string";

    char_separator<char> sep(", ");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const auto& t : tokens) {
        cout << t << "." << endl;
    }
}

Aqui está um muito simples:

#include <vector>
#include <string>
using namespace std;

vector<string> split(const char *str, char c = ' ')
{
    vector<string> result;

    do
    {
        const char *begin = str;

        while(*str != c && *str)
            str++;

        result.push_back(string(begin, str));
    } while (0 != *str++);

    return result;
}

Use strtok.Na minha opinião, não há necessidade de construir uma classe em torno da tokenização, a menos que o strtok não forneça o que você precisa.Pode não ser, mas em mais de 15 anos escrevendo vários códigos de análise em C e C++, sempre usei strtok.Aqui está um exemplo

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");
}

Algumas advertências (que podem não atender às suas necessidades).A string é "destruída" no processo, o que significa que os caracteres EOS são colocados em linha nos pontos delimitadores.O uso correto pode exigir que você crie uma versão não const da string.Você também pode alterar a lista de delimitadores no meio da análise.

Na minha opinião, o código acima é muito mais simples e fácil de usar do que escrever uma classe separada para ele.Para mim, essa é uma daquelas funções que a linguagem oferece e faz isso bem e de forma limpa.É simplesmente uma solução "baseada em C".É apropriado, é fácil e você não precisa escrever muito código extra :-)

Outra maneira rápida é usar getline.Algo como:

stringstream ss("bla bla");
string s;

while (getline(ss, s, ' ')) {
 cout << s << endl;
}

Se quiser, você pode fazer um simples split() método retornando um vector<string>, o que é realmente útil.

Você pode usar fluxos, iteradores e o algoritmo de cópia para fazer isso de maneira bastante direta.

#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>

int main()
{
  std::string str = "The quick brown fox";

  // construct a stream from the string
  std::stringstream strstr(str);

  // use stream iterators to copy the stream to the vector as whitespace separated strings
  std::istream_iterator<std::string> it(strstr);
  std::istream_iterator<std::string> end;
  std::vector<std::string> results(it, end);

  // send the vector to stdout.
  std::ostream_iterator<std::string> oit(std::cout);
  std::copy(results.begin(), results.end(), oit);
}

Sem ofensa, pessoal, mas para um problema tão simples, vocês estão fazendo coisas caminho muito complicado.Existem muitos motivos para usar Impulsionar.Mas para algo tão simples, é como acertar uma mosca com um trenó 20#.

void
split( vector<string> & theStringVector,  /* Altered/returned value */
       const  string  & theString,
       const  string  & theDelimiter)
{
    UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.

    size_t  start = 0, end = 0;

    while ( end != string::npos)
    {
        end = theString.find( theDelimiter, start);

        // If at end, use length=maxLength.  Else use length=end-start.
        theStringVector.push_back( theString.substr( start,
                       (end == string::npos) ? string::npos : end - start));

        // If at end, use start=maxSize.  Else use start=end+delimiter.
        start = (   ( end > (string::npos - theDelimiter.size()) )
                  ?  string::npos  :  end + theDelimiter.size());
    }
}

Por exemplo (para o caso de Doug),

#define SHOW(I,X)   cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl

int
main()
{
    vector<string> v;

    split( v, "A:PEP:909:Inventory Item", ":" );

    for (unsigned int i = 0;  i < v.size();   i++)
        SHOW( i, v[i] );
}

E sim, poderíamos fazer com que split() retornasse um novo vetor em vez de passar um.É trivial embrulhar e sobrecarregar.Mas dependendo do que estou fazendo, muitas vezes acho melhor reutilizar objetos pré-existentes em vez de sempre criar novos.(Contanto que eu não esqueça de esvaziar o vetor no meio!)

Referência: http://www.cplusplus.com/reference/string/string/.

(Originalmente, eu estava escrevendo uma resposta à pergunta de Doug: Modificação e extração de strings C++ com base em separadores (fechado).Mas já que Martin York encerrou essa questão com uma indicação aqui...Vou apenas generalizar meu código.)

Impulsionar tem uma forte função de divisão: impulsionar::algoritmo::dividir.

Programa de amostra:

#include <vector>
#include <boost/algorithm/string.hpp>

int main() {
    auto s = "a,b, c ,,e,f,";
    std::vector<std::string> fields;
    boost::split(fields, s, boost::is_any_of(","));
    for (const auto& field : fields)
        std::cout << "\"" << field << "\"\n";
    return 0;
}

Saída:

"a"
"b"
" c "
""
"e"
"f"
""

Uma solução usando regex_token_iteratoré:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    string str("The quick brown fox");

    regex reg("\\s+");

    sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
    sregex_token_iterator end;

    vector<string> vec(iter, end);

    for (auto a : vec)
    {
        cout << a << endl;
    }
}

Eu sei que você pediu uma solução C++, mas você pode considerar isso útil:

Qt

#include <QString>

...

QString str = "The quick brown fox"; 
QStringList results = str.split(" "); 

A vantagem sobre o Boost neste exemplo é que ele é um mapeamento direto para o código da sua postagem.

Veja mais em Documentação Qt

Aqui está um exemplo de classe tokenizer que pode fazer o que você deseja

//Header file
class Tokenizer 
{
    public:
        static const std::string DELIMITERS;
        Tokenizer(const std::string& str);
        Tokenizer(const std::string& str, const std::string& delimiters);
        bool NextToken();
        bool NextToken(const std::string& delimiters);
        const std::string GetToken() const;
        void Reset();
    protected:
        size_t m_offset;
        const std::string m_string;
        std::string m_token;
        std::string m_delimiters;
};

//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");

Tokenizer::Tokenizer(const std::string& s) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(DELIMITERS) {}

Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
    m_string(s), 
    m_offset(0), 
    m_delimiters(delimiters) {}

bool Tokenizer::NextToken() 
{
    return NextToken(m_delimiters);
}

bool Tokenizer::NextToken(const std::string& delimiters) 
{
    size_t i = m_string.find_first_not_of(delimiters, m_offset);
    if (std::string::npos == i) 
    {
        m_offset = m_string.length();
        return false;
    }

    size_t j = m_string.find_first_of(delimiters, i);
    if (std::string::npos == j) 
    {
        m_token = m_string.substr(i);
        m_offset = m_string.length();
        return true;
    }

    m_token = m_string.substr(i, j - i);
    m_offset = j;
    return true;
}

Exemplo:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}

Esta é uma solução simples somente STL (~5 linhas!) Usando std::find e std::find_first_not_of que lida com repetições do delimitador (como espaços ou pontos, por exemplo), bem como delimitadores iniciais e finais:

#include <string>
#include <vector>

void tokenize(std::string str, std::vector<string> &token_v){
    size_t start = str.find_first_not_of(DELIMITER), end=start;

    while (start != std::string::npos){
        // Find next occurence of delimiter
        end = str.find(DELIMITER, start);
        // Push back the token found into vector
        token_v.push_back(str.substr(start, end-start));
        // Skip all occurences of the delimiter to find new start
        start = str.find_first_not_of(DELIMITER, end);
    }
}

Experimente ao vivo!

corda é uma pequena biblioteca que implementa várias funções de string do Python, incluindo o método split:

#include <string>
#include <vector>
#include "pystring.h"

std::vector<std::string> chunks;
pystring::split("this string", chunks);

// also can specify a separator
pystring::split("this-string", chunks, "-");

Postei esta resposta para uma pergunta semelhante.
Não reinvente a roda.Usei várias bibliotecas e a mais rápida e flexível que encontrei é: Biblioteca do kit de ferramentas de string C++.

Aqui está um exemplo de como usá-lo que postei em outro lugar no stackoverflow.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
       std::string s("Somewhere down the road");
       std::vector<std::string> result;
       if( strtk::parse( s, whitespace, result ) )
       {
           for(size_t i = 0; i < result.size(); ++i )
            std::cout << result[i] << std::endl;
       }
    }

    {  // parsing a string into a vector of floats with other separators
       // besides spaces

       std::string t("3.0, 3.14; 4.0");
       std::vector<float> values;
       if( strtk::parse( s, whitespace_and_punctuation, values ) )
       {
           for(size_t i = 0; i < values.size(); ++i )
            std::cout << values[i] << std::endl;
       }
    }

    {  // parsing a string into specific variables

       std::string u("angle = 45; radius = 9.9");
       std::string w1, w2;
       float v1, v2;
       if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
       {
           std::cout << "word " << w1 << ", value " << v1 << std::endl;
           std::cout << "word " << w2 << ", value " << v2 << std::endl;
       }
    }

    return 0;
}

Verifique este exemplo.Pode te ajudar..

#include <iostream>
#include <sstream>

using namespace std;

int main ()
{
    string tmps;
    istringstream is ("the dellimiter is the space");
    while (is.good ()) {
        is >> tmps;
        cout << tmps << "\n";
    }
    return 0;
}

MFC/ATL tem um tokenizer muito bom.Do MSDN:

CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;

resToken= str.Tokenize("% #",curPos);
while (resToken != "")
{
   printf("Resulting token: %s\n", resToken);
   resToken= str.Tokenize("% #",curPos);
};

Output

Resulting Token: First
Resulting Token: Second
Resulting Token: Third

Você pode simplesmente usar um biblioteca de expressões regulares e resolva isso usando expressões regulares.

Use a expressão (\w+) e a variável em \1 (ou $1 dependendo da implementação da biblioteca de expressões regulares).

Se você estiver disposto a usar C, você pode usar o strtok função.Você deve prestar atenção aos problemas de multithreading ao usá-lo.

Para coisas simples eu apenas uso o seguinte:

unsigned TokenizeString(const std::string& i_source,
                        const std::string& i_seperators,
                        bool i_discard_empty_tokens,
                        std::vector<std::string>& o_tokens)
{
    unsigned prev_pos = 0;
    unsigned pos = 0;
    unsigned number_of_tokens = 0;
    o_tokens.clear();
    pos = i_source.find_first_of(i_seperators, pos);
    while (pos != std::string::npos)
    {
        std::string token = i_source.substr(prev_pos, pos - prev_pos);
        if (!i_discard_empty_tokens || token != "")
        {
            o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
            number_of_tokens++;
        }

        pos++;
        prev_pos = pos;
        pos = i_source.find_first_of(i_seperators, pos);
    }

    if (prev_pos < i_source.length())
    {
        o_tokens.push_back(i_source.substr(prev_pos));
        number_of_tokens++;
    }

    return number_of_tokens;
}

Isenção de responsabilidade covarde:Eu escrevo software de processamento de dados em tempo real onde os dados chegam através de arquivos binários, soquetes ou alguma chamada de API (placas de E/S, câmeras).Nunca uso essa função para algo mais complicado ou urgente do que ler arquivos de configuração externos na inicialização.

Muitas sugestões excessivamente complicadas aqui.Experimente esta solução std::string simples:

using namespace std;

string someText = ...

string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
    sepOff = someText.find(' ', sepOff);
    string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
    string token = someText.substr(tokenOff, tokenLen);
    if (!token.empty())
        /* do something with token */;
    tokenOff = sepOff;
}

Eu pensei que era isso que >> operador em fluxos de string era para:

string word; sin >> word;

A resposta de Adam Pierce fornece um tokenizador girado à mão que absorve um const char*.É um pouco mais problemático fazer isso com iteradores porque incrementando um stringo iterador final de é indefinido.Dito isto, dado string str{ "The quick brown fox" } certamente podemos conseguir isso:

auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };

while (start != cend(str)) {
    const auto finish = find(++start, cend(str), ' ');

    tokens.push_back(string(start, finish));
    start = finish;
}

Exemplo ao vivo


Se você deseja abstrair a complexidade usando funcionalidade padrão, como Em Freund sugere strtok é uma opção simples:

vector<string> tokens;

for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);

Se você não tiver acesso ao C++ 17, precisará substituir data(str) como neste exemplo: http://ideone.com/8kAGoa

Embora não demonstrado no exemplo, strtok não precisa usar o mesmo delimitador para cada token.Junto com essa vantagem, porém, existem várias desvantagens:

  1. strtok não pode ser usado em vários strings ao mesmo tempo:Ou um nullptr deve ser passado para continuar a tokenizar o atual string ou um novo char* para tokenizar deve ser passado (no entanto, existem algumas implementações não padrão que suportam isso, como: strtok_s)
  2. Pela mesma razão strtok não pode ser usado em vários threads simultaneamente (no entanto, isso pode ser definido pela implementação, por exemplo: A implementação do Visual Studio é thread-safe)
  3. Chamando strtok modifica o string está operando, então não pode ser usado em const stringé, const char*s, ou strings literais, para tokenizar qualquer um deles com strtok ou para operar em um string cujo conteúdo precisa ser preservado, str teria que ser copiado, então a cópia poderia ser operada

Ambos os métodos anteriores não podem gerar um tokenizado vector no local, ou seja, sem abstraí-los em uma função auxiliar, eles não podem inicializar const vector<string> tokens.Essa funcionalidade e a capacidade de aceitar qualquer delimitador de espaço em branco pode ser aproveitado usando um istream_iterator.Por exemplo dado: const string str{ "The quick \tbrown \nfox" } nós podemos fazer isso:

istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };

Exemplo ao vivo

A necessária construção de um istringstream pois esta opção tem um custo muito maior do que as 2 opções anteriores, no entanto, esse custo normalmente fica oculto nas despesas de string alocação.


Se nenhuma das opções acima for flexível o suficiente para suas necessidades de tokenização, a opção mais flexível é usar um regex_token_iterator é claro que com essa flexibilidade vêm maiores despesas, mas, novamente, isso provavelmente está oculto no string custo de alocação.Digamos, por exemplo, que queiramos tokenizar com base em vírgulas sem escape, também ocupando espaços em branco, dada a seguinte entrada: const string str{ "The ,qu\\,ick ,\tbrown, fox" } nós podemos fazer isso:

const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };

Exemplo ao vivo

Aqui está uma abordagem que permite controlar se tokens vazios são incluídos (como strsep) ou excluídos (como strtok).

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)
{
  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  {
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    }

  return tokens;
}

Parece estranho para mim que, com todos nós, nerds preocupados com a velocidade aqui no SO, ninguém tenha apresentado uma versão que use uma tabela de consulta gerada em tempo de compilação para o delimitador (exemplo de implementação mais abaixo).Usar uma tabela de consulta e iteradores deve superar o std::regex em eficiência, se você não precisa vencer o regex, basta usá-lo, seu padrão a partir do C++ 11 e super flexível.

Alguns já sugeriram regex, mas para os novatos aqui está um exemplo empacotado que deve fazer exatamente o que o OP espera:

std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
    std::smatch m{};
    std::vector<std::string> ret{};
    while (std::regex_search (it,end,m,e)) {
        ret.emplace_back(m.str());              
        std::advance(it, m.position() + m.length()); //next start position = match position + match length
    }
    return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){  //comfort version calls flexible version
    return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
    std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
    auto v = split(str);
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    std::cout << "crazy version:" << std::endl;
    v = split(str, std::regex{"[^e]+"});  //using e as delim shows flexibility
    for(const auto&s:v){
        std::cout << s << std::endl;
    }
    return 0;
}

Se precisarmos ser mais rápidos e aceitar a restrição de que todos os caracteres devem ter 8 bits, podemos fazer uma tabela de consulta em tempo de compilação usando metaprogramação:

template<bool...> struct BoolSequence{};        //just here to hold bools
template<char...> struct CharSequence{};        //just here to hold chars
template<typename T, char C> struct Contains;   //generic
template<char First, char... Cs, char Match>    //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
    Contains<CharSequence<Cs...>, Match>{};     //strip first and increase index
template<char First, char... Cs>                //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {}; 
template<char Match>                            //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};

template<int I, typename T, typename U> 
struct MakeSequence;                            //generic
template<int I, bool... Bs, typename U> 
struct MakeSequence<I,BoolSequence<Bs...>, U>:  //not last
    MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U> 
struct MakeSequence<0,BoolSequence<Bs...>,U>{   //last  
    using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
    /* could be made constexpr but not yet supported by MSVC */
    static bool isDelim(const char c){
        static const bool table[256] = {Bs...};
        return table[static_cast<int>(c)];
    }   
};
using Delims = CharSequence<'.',',',' ',':','\n'>;  //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;

Com isso no lugar fazendo um getNextToken a função é fácil:

template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
    begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
    auto second = std::find_if(begin,end,Table{});      //find first delim or end
    return std::make_pair(begin,second);
}

Usá-lo também é fácil:

int main() {
    std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
    auto it = std::begin(s);
    auto end = std::end(s);
    while(it != std::end(s)){
        auto token = getNextToken(it,end);
        std::cout << std::string(token.first,token.second) << std::endl;
        it = token.second;
    }
    return 0;
}

Aqui está um exemplo ao vivo: http://ideone.com/GKtkLQ

Sei que esta pergunta já está respondida mas quero contribuir.Talvez minha solução seja um pouco simples, mas foi isso que eu descobri:

vector<string> get_words(string const& text)
{
    vector<string> result;
    string tmp = text;

    size_t first_pos = 0;
    size_t second_pos = tmp.find(" ");;

    while (second_pos != string::npos)
    {
        if (first_pos != second_pos)
        {
            string word = tmp.substr(first_pos, second_pos - first_pos);
            result.push_back(word);
        }
        tmp = tmp.substr(second_pos + 1);
        second_pos = tmp.find(" ");
    }

    result.push_back(tmp);

    return result;
}

Por favor, comente se existe uma abordagem melhor para algo no meu código ou se algo está errado.

Não existe uma maneira direta de fazer isso.Referir este código código fonte do projeto para descobrir como construir uma classe para isso.

você pode aproveitar boost::make_find_iterator.Algo semelhante a isto:

template<typename CH>
inline vector< basic_string<CH> > tokenize(
    const basic_string<CH> &Input,
    const basic_string<CH> &Delimiter,
    bool remove_empty_token
    ) {

    typedef typename basic_string<CH>::const_iterator string_iterator_t;
    typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;

    vector< basic_string<CH> > Result;
    string_iterator_t it = Input.begin();
    string_iterator_t it_end = Input.end();
    for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
        i != string_find_iterator_t();
        ++i) {
        if(remove_empty_token){
            if(it != i->begin())
                Result.push_back(basic_string<CH>(it,i->begin()));
        }
        else
            Result.push_back(basic_string<CH>(it,i->begin()));
        it = i->end();
    }
    if(it != it_end)
        Result.push_back(basic_string<CH>(it,it_end));

    return Result;
}

Se o comprimento máximo da string de entrada a ser tokenizada for conhecido, pode-se explorar isso e implementar uma versão muito rápida.Estou esboçando a ideia básica abaixo, que foi inspirada em strtok() e na estrutura de dados "suffix array" descrita na 2ª edição de "Programming Perls" de Jon Bentley, capítulo 15.A classe C++, neste caso, oferece apenas alguma organização e conveniência de uso.A implementação mostrada pode ser facilmente estendida para remover caracteres de espaço em branco iniciais e finais nos tokens.

Basicamente, pode-se substituir os caracteres separadores por caracteres '\0' que terminam a string e definir ponteiros para os tokens dentro da string modificada.No caso extremo, quando a string consiste apenas em separadores, obtém-se o comprimento da string mais 1 token vazio resultante.É prático duplicar a string a ser modificada.

Arquivo de cabeçalho:

class TextLineSplitter
{
public:

    TextLineSplitter( const size_t max_line_len );

    ~TextLineSplitter();

    void            SplitLine( const char *line,
                               const char sep_char = ',',
                             );

    inline size_t   NumTokens( void ) const
    {
        return mNumTokens;
    }

    const char *    GetToken( const size_t token_idx ) const
    {
        assert( token_idx < mNumTokens );
        return mTokens[ token_idx ];
    }

private:
    const size_t    mStorageSize;

    char           *mBuff;
    char          **mTokens;
    size_t          mNumTokens;

    inline void     ResetContent( void )
    {
        memset( mBuff, 0, mStorageSize );
        // mark all items as empty:
        memset( mTokens, 0, mStorageSize * sizeof( char* ) );
        // reset counter for found items:
        mNumTokens = 0L;
    }
};

Arquivo de implementação:

TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
    mStorageSize ( max_line_len + 1L )
{
    // allocate memory
    mBuff   = new char  [ mStorageSize ];
    mTokens = new char* [ mStorageSize ];

    ResetContent();
}

TextLineSplitter::~TextLineSplitter()
{
    delete [] mBuff;
    delete [] mTokens;
}


void TextLineSplitter::SplitLine( const char *line,
                                  const char sep_char   /* = ',' */,
                                )
{
    assert( sep_char != '\0' );

    ResetContent();
    strncpy( mBuff, line, mMaxLineLen );

    size_t idx       = 0L; // running index for characters

    do
    {
        assert( idx < mStorageSize );

        const char chr = line[ idx ]; // retrieve current character

        if( mTokens[ mNumTokens ] == NULL )
        {
            mTokens[ mNumTokens ] = &mBuff[ idx ];
        } // if

        if( chr == sep_char || chr == '\0' )
        { // item or line finished
            // overwrite separator with a 0-terminating character:
            mBuff[ idx ] = '\0';
            // count-up items:
            mNumTokens ++;
        } // if

    } while( line[ idx++ ] );
}

Um cenário de uso seria:

// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )
{
    printf( "%s\n", spl.GetToken( i ) );
}

saída:

Item1

Item2
Item3

boost::tokenizer é seu amigo, mas considere tornar seu código portátil com referência a questões de internacionalização (i18n) usando wstring/wchar_t em vez do legado string/char tipos.

#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>

using namespace std;
using namespace boost;

typedef tokenizer<char_separator<wchar_t>,
                  wstring::const_iterator, wstring> Tok;

int main()
{
  wstring s;
  while (getline(wcin, s)) {
    char_separator<wchar_t> sep(L" "); // list of separator characters
    Tok tok(s, sep);
    for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) {
      wcout << *beg << L"\t"; // output (or store in vector)
    }
    wcout << L"\n";
  }
  return 0;
}

Código C++ simples (padrão C++98), aceita vários delimitadores (especificados em um std::string), usa apenas vetores, strings e iteradores.

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> 

std::vector<std::string> 
split(const std::string& str, const std::string& delim){
    std::vector<std::string> result;
    if (str.empty())
        throw std::runtime_error("Can not tokenize an empty string!");
    std::string::const_iterator begin, str_it;
    begin = str_it = str.begin(); 
    do {
        while (delim.find(*str_it) == std::string::npos && str_it != str.end())
            str_it++; // find the position of the first delimiter in str
        std::string token = std::string(begin, str_it); // grab the token
        if (!token.empty()) // empty token only when str starts with a delimiter
            result.push_back(token); // push the token into a vector<string>
        while (delim.find(*str_it) != std::string::npos && str_it != str.end())
            str_it++; // ignore the additional consecutive delimiters
        begin = str_it; // process the remaining tokens
        } while (str_it != str.end());
    return result;
}

int main() {
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top