Pregunta

Java tiene un método de división conveniente:

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

¿Existe una manera fácil de hacer esto en C++?

¿Fue útil?

Solución

Su caso simple se puede construir fácilmente usando el std::string::find método.Sin embargo, eche un vistazo a Boost.Tokenizador.Es genial.Boost generalmente tiene algunas herramientas de cuerdas muy interesantes.

Otros consejos

El Impulsar tokenizador La clase puede hacer que este tipo de cosas sean 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;
    }
}

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

Aquí tienes uno muy sencillo:

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

Utilice strtok.En mi opinión, no es necesario crear una clase en torno a la tokenización a menos que strtok no le proporcione lo que necesita.Puede que no, pero en más de 15 años escribiendo varios códigos de análisis en C y C++, siempre he usado strtok.Aquí hay un ejemplo

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

Algunas advertencias (que pueden no satisfacer sus necesidades).La cadena se "destruye" en el proceso, lo que significa que los caracteres EOS se colocan en línea en los puntos delimitadores.El uso correcto puede requerir que cree una versión no constante de la cadena.También puede cambiar la lista de delimitadores a mitad del análisis.

En mi propia opinión, el código anterior es mucho más simple y fácil de usar que escribir una clase separada para él.Para mí, esta es una de esas funciones que proporciona el lenguaje y lo hace bien y de forma limpia.Es simplemente una solución "basada en C".Es apropiado, es fácil y no es necesario escribir mucho código adicional :-)

Otra forma rápida es utilizar getline.Algo como:

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

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

Si quieres puedes hacer una sencilla. split() método que devuelve un vector<string>, que es realmente útil.

Puede utilizar secuencias, iteradores y el algoritmo de copia para hacer esto de manera bastante directa.

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

Sin ofender amigos, pero para un problema tan simple, están haciendo cosas forma demasiado complicado.Hay muchas razones para usar Aumentar.Pero para algo así de simple, es como golpear una mosca con un trineo de 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 ejemplo (para el 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] );
}

Y sí, podríamos hacer que split() devuelva un nuevo vector en lugar de pasar uno.Es trivial envolver y sobrecargar.Pero dependiendo de lo que estoy haciendo, a menudo encuentro mejor reutilizar objetos preexistentes en lugar de crear siempre otros nuevos.(¡Siempre y cuando no me olvide de vaciar el vector en el medio!)

Referencia: http://www.cplusplus.com/reference/string/string/.

(Originalmente estaba escribiendo una respuesta a la pregunta de Doug: Modificación y extracción de cadenas de C++ según separadores (cerrado).Pero como Martin York cerró esa pregunta con un puntero aquí...Simplemente generalizaré mi código).

Aumentar tiene una fuerte función de división: impulso::algoritmo::división.

Programa de muestra:

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

Producción:

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

Una solución usando regex_token_iterators:

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

Sé que solicitaste una solución C++, pero esto podría resultarte útil:

cuarto

#include <QString>

...

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

La ventaja sobre Boost en este ejemplo es que es un mapeo directo uno a uno al código de tu publicación.

Ver más en documentación qt

Aquí hay una clase de tokenizador de muestra que podría hacer lo que quieras.

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

Ejemplo:

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

Esta es una solución simple solo STL (~5 líneas!) usando std::find y std::find_first_not_of que maneja repeticiones del delimitador (como espacios o puntos, por ejemplo), así como delimitadores iniciales y finales:

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

Pruébalo vivir!

cuerda de pitón es una pequeña biblioteca que implementa un montón de funciones de cadena de Python, incluido el método de división:

#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, "-");

Publiqué esta respuesta para una pregunta similar.
No reinventes la rueda.He usado varias bibliotecas y la más rápida y flexible que he encontrado es: Biblioteca del kit de herramientas de cadenas de C++.

Aquí hay un ejemplo de cómo usarlo que publiqué en otro lugar del 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;
}

Mira este ejemplo.Puede que te ayude..

#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 tiene un tokenizador muy bueno.Desde 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

Puedes simplemente usar un biblioteca de expresiones regulares y resuélvelo usando expresiones regulares.

Utilice la expresión (\w+) y la variable en \1 (o $1 dependiendo de la implementación de la biblioteca de expresiones regulares).

Si está dispuesto a utilizar C, puede utilizar el strtok función.Debes prestar atención a los problemas de subprocesos múltiples al usarlo.

Para cosas simples solo uso lo siguiente:

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

Descargo de responsabilidad cobarde:Escribo software de procesamiento de datos en tiempo real donde los datos ingresan a través de archivos binarios, sockets o alguna llamada API (tarjetas de E/S, cámaras).Nunca uso esta función para algo más complicado o en el que el tiempo es crítico que leer archivos de configuración externos al inicio.

Muchas sugerencias demasiado complicadas aquí.Pruebe esta sencilla solución std::string:

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

Pensé que eso era lo que >> El operador en secuencias de cadenas era para:

string word; sin >> word;

La respuesta de Adam Pierce proporciona un tokenizador hecho a mano que recoge un const char*.Es un poco más problemático hacerlo con iteradores porque incrementando un stringEl iterador final no está definido..Dicho esto, dado string str{ "The quick brown fox" } Ciertamente podemos lograr esto:

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

Ejemplo en vivo


Si busca abstraer la complejidad mediante el uso de funcionalidad estándar, como Sobre Freund sugiere strtok es una opción sencilla:

vector<string> tokens;

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

Si no tiene acceso a C++ 17, deberá sustituirlo data(str) como en este ejemplo: http://ideone.com/8kAGoa

Aunque no se demuestra en el ejemplo, strtok No es necesario utilizar el mismo delimitador para cada token.Sin embargo, junto a esta ventaja, existen varios inconvenientes:

  1. strtok no se puede utilizar en múltiples strings al mismo tiempo:Ya sea un nullptr debe pasarse para continuar tokenizando el actual string o un nuevo char* para tokenizar se debe aprobar (sin embargo, existen algunas implementaciones no estándar que sí lo admiten, como por ejemplo: strtok_s)
  2. Por la misma razón strtok no se puede utilizar en varios subprocesos simultáneamente (sin embargo, esto puede estar definido por la implementación, por ejemplo: La implementación de Visual Studio es segura para subprocesos)
  3. Vocación strtok modifica el string está funcionando, por lo que no se puede utilizar en const strings, const char*s, o cadenas literales, para tokenizar cualquiera de estos con strtok o para operar en un string cuyo contenido debe conservarse, str tendría que ser copiado, entonces la copia podría ser operada

Los dos métodos anteriores no pueden generar un tokenizado. vector en el lugar, es decir, sin abstraerlos en una función auxiliar que no pueden inicializar const vector<string> tokens.esa funcionalidad y la capacidad de aceptar cualquier El delimitador de espacios en blanco se puede aprovechar utilizando un istream_iterator.Por ejemplo dado: const string str{ "The quick \tbrown \nfox" } Podemos hacer esto:

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

Ejemplo en vivo

La construcción requerida de un istringstream Esta opción tiene un costo mucho mayor que las 2 opciones anteriores, sin embargo, este costo generalmente está oculto en el gasto de string asignación.


Si ninguna de las opciones anteriores es lo suficientemente flexible para sus necesidades de tokenización, la opción más flexible es usar un regex_token_iterator Por supuesto, esta flexibilidad conlleva un mayor gasto, pero nuevamente esto probablemente esté oculto en el string costo de asignación.Digamos, por ejemplo, que queremos tokenizar en función de comas sin escape, y también comiendo espacios en blanco, dada la siguiente entrada: const string str{ "The ,qu\\,ick ,\tbrown, fox" } Podemos hacer esto:

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

Ejemplo en vivo

Este es un enfoque que le permite controlar si los tokens vacíos se incluyen (como strsep) o se excluyen (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;
}

Me parece extraño que con todos nosotros, nerds preocupados por la velocidad aquí en SO, nadie haya presentado una versión que utilice una tabla de búsqueda generada en tiempo de compilación para el delimitador (ejemplo de implementación más abajo).El uso de una tabla de búsqueda e iteradores debería superar a std::regex en eficiencia, si no necesita superar a regex, simplemente úselo, es estándar a partir de C++ 11 y súper flexible.

Algunos ya han sugerido expresiones regulares, pero para los novatos aquí hay un ejemplo empaquetado que debería hacer exactamente lo que espera el OP:

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

Si necesitamos ser más rápidos y aceptar la restricción de que todos los caracteres deben tener 8 bits, podemos crear una tabla de consulta en tiempo de compilación usando metaprogramación:

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

Con eso en su lugar haciendo un getNextToken La función es 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);
}

Usarlo también es 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;
}

Aquí hay un ejemplo en vivo: http://ideone.com/GKtkLQ

Sé que esta pregunta ya está respondida pero quiero contribuir.Quizás mi solución sea un poco simple pero esto es lo que se me ocurrió:

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

Comente si hay un mejor enfoque para algo en mi código o si algo anda mal.

No existe una forma directa de hacer esto.Referirse este código código fuente del proyecto para descubrir cómo crear una clase para esto.

puedes aprovechar boost::make_find_iterator.Algo parecido a esto:

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

Si se conoce la longitud máxima de la cadena de entrada que se va a tokenizar, se puede aprovechar esto e implementar una versión muy rápida.Estoy esbozando la idea básica a continuación, que se inspiró tanto en strtok() como en la estructura de datos de "matriz de sufijos" descrita en la segunda edición de "Programación Perls" de Jon Bentley, capítulo 15.La clase C++ en este caso sólo ofrece cierta organización y comodidad de uso.La implementación que se muestra se puede ampliar fácilmente para eliminar los espacios en blanco iniciales y finales de los tokens.

Básicamente, se pueden reemplazar los caracteres separadores con caracteres '\0' que terminan en la cadena y establecer punteros a los tokens dentro de la cadena modificada.En el caso extremo, cuando la cadena consta solo de separadores, se obtiene la longitud de la cadena más 1 token vacío resultante.Es práctico duplicar la cadena a modificar.

Archivo de cabecera:

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

Archivo de implementación:

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

Un escenario de uso sería:

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

producción:

Item1

Item2
Item3

boost::tokenizer es tu amigo, pero considera hacer que tu código sea portátil con referencia a problemas de internacionalización (i18n) usando wstring/wchar_t en lugar del 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++ simple (C++98 estándar), acepta múltiples delimitadores (especificados en una cadena std::), usa solo vectores, cadenas 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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top