¿Cómo iterar sobre las palabras de una cadena?

Estoy tratando de iterar sobre las palabras de una cadena.

Se puede suponer que la cadena está compuesta por palabras separadas por espacios en blanco.

Tenga en cuenta que no estoy interesado en las funciones de cadena C o ese tipo de manipulación / acceso de caracteres. Además, dé prioridad a la elegancia sobre la eficiencia en su respuesta.

La mejor solución que tengo ahora es:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

¿Existe una forma más elegante de hacer esto?

preguntado Oct 25 '08, 09:10

Amigo ... La elegancia es solo una forma elegante de decir "eficiencia-que-se-ve-bonita" en mi libro. No evite usar funciones C y métodos rápidos para lograr cualquier cosa solo porque no está contenida dentro de una plantilla;) -

while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; } -

@Eduardo: eso también está mal ... necesitas probar iss entre intentar transmitir otro valor y usar ese valor, es decir string sub; while (iss >> sub) cout << "Substring: " << sub << '\n'; -

Varias opciones en C ++ para hacer esto por defecto: cplusplus.com/faq/sequences/strings/split -

La elegancia es más que una simple eficiencia. Los atributos elegantes incluyen un bajo número de líneas y una alta legibilidad. En mi humilde opinión, la elegancia no es un sustituto de la eficiencia, sino de la capacidad de mantenimiento. -

30 Respuestas

Por lo que vale, aquí hay otra forma de extraer tokens de una cadena de entrada, confiando solo en las instalaciones de la biblioteca estándar. Es un ejemplo del poder y la elegancia detrás del diseño del STL.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

En lugar de copiar los tokens extraídos a un flujo de salida, se podrían insertar en un contenedor, usando el mismo genérico copy algoritmo.

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... o crea el vector directamente:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

Respondido 02 ago 19, 14:08

¿Es posible especificar un delimitador para esto? ¿Como, por ejemplo, dividir entre comas? - l3dx

@Jonathan: \ n no es el delimitador en este caso, es el delimitador para la salida a cout. - comandante

Esta es una mala solución ya que no necesita ningún otro delimitador, por lo tanto, no es escalable ni mantenible. - Ajedrez Pequeño

En realidad, esto puede funciona bien con otros delimitadores (aunque hacer algunos es algo feo). Usted crea una faceta ctype que clasifica los delimitadores deseados como espacios en blanco, crea una configuración regional que contiene esa faceta y luego imbuye el flujo de cadenas con esa configuración regional antes de extraer las cadenas. - Jerry Coffin

@Chocolatechocolate "Se puede suponer que la cadena está compuesta por palabras separadas por espacios en blanco" - Hmm, no suena como una mala solución al problema de la pregunta. "no escalable y no mantenible" - Hah, bonita. - Christian Rau

Utilizo esto para dividir la cadena por un delimitador. El primero pone los resultados en un vector preconstruido, el segundo devuelve un nuevo vector.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Tenga en cuenta que esta solución no omite los tokens vacíos, por lo que lo siguiente encontrará 4 elementos, uno de los cuales está vacío:

std::vector<std::string> x = split("one:two::three", ':');

Respondido 14 Oct 19, 20:10

Para evitar que se salte las fichas vacías, haga una empty() comprobar: if (!item.empty()) elems.push_back(item) - 0x499602D2

¿Qué tal si el delim contiene dos caracteres como ->? - herohuyongtao

@herohuyongtao, esta solución solo funciona para delimitadores de un solo carácter. - Evan Terán

@JeshwanthKumarNK, no es necesario, pero te permite hacer cosas como pasar el resultado directamente a una función como esta: f(split(s, d, v)) sin dejar de tener el beneficio de un preasignado vector Si te gusta. - Evan Terán

Advertencia: split ("uno: dos :: tres", ':') y split ("uno: dos :: tres:", ':') devuelven el mismo valor. - dshin

Una posible solución con Boost podría ser:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Este enfoque podría ser incluso más rápido que el stringstream Acercarse. Y como se trata de una función de plantilla genérica, se puede utilizar para dividir otros tipos de cadenas (wchar, etc. o UTF-8) utilizando todo tipo de delimitadores.

Consulta la sección de documentación para más detalles.

Respondido 04 ago 15, 02:08

La velocidad es irrelevante aquí, ya que ambos casos son mucho más lentos que una función similar a strtok. - tom

Y para aquellos que aún no tienen boost ... bcp copia más de 1,000 archivos para esto :) - Roman Starkov

Advertencia, cuando se le da una cadena vacía (""), este método devuelve un vector que contiene la cadena "". Así que agregue un "if (! String_to_split.empty ())" antes de la división. - Offirmo

No todos los desarrolladores de @Ian Embedded están usando boost. - ACK_stoverflow

como un apéndice: uso boost solo cuando debo, normalmente prefiero agregar a mi propia biblioteca de código que es independiente y portátil para que pueda lograr un código específico pequeño y preciso, que logre un objetivo dado. De esa forma, el código es no público, eficaz, trivial y portátil. Boost tiene su lugar, pero sugeriría que es un poco exagerado para tokenizar cuerdas: no haría que transporten toda su casa a una empresa de ingeniería para que le claven un clavo nuevo en la pared para colgar un cuadro ... extremadamente bien, pero los pros son superados con creces por los contras. - GMasucci

#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}

contestado el 19 de mayo de 18 a las 13:05

También puede dividir en otros delimitadores si usa getline en el objeto while condición, por ejemplo, para dividir por comas, use while(getline(ss, buff, ',')). - Ali

Para aquellos a los que no les sienta bien sacrificar toda la eficiencia por el tamaño del código y ver "eficiente" como un tipo de elegancia, lo siguiente debería llegar a un punto óptimo (y creo que la clase de contenedor de plantilla es una adición increíblemente elegante):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Normalmente elijo usar std::vector<std::string> tipos como mi segundo parámetro (ContainerT)... pero list<> es mucho más rápido que vector<> para cuando no se necesita acceso directo, e incluso puede crear su propia clase de cadena y usar algo como std::list<subString> dónde subString no hace ninguna copia para incrementos de velocidad increíbles.

Es más del doble de rápido que el tokenizado más rápido de esta página y casi 5 veces más rápido que otros. Además, con los tipos de parámetros perfectos, puede eliminar todas las copias de cadenas y listas para aumentar la velocidad.

Además, no devuelve el resultado (extremadamente ineficiente), sino que pasa los tokens como referencia, lo que también le permite acumular tokens utilizando múltiples llamadas si así lo desea.

Por último, le permite especificar si recortar los tokens vacíos de los resultados a través de un último parámetro opcional.

Todo lo que necesita es std::string... el resto son opcionales. No utiliza transmisiones ni la biblioteca boost, pero es lo suficientemente flexible como para poder aceptar algunos de estos tipos foráneos de forma natural.

Respondido el 19 de Septiembre de 16 a las 16:09

Soy bastante fan de esto, pero para g ++ (y probablemente una buena práctica) cualquiera que use esto querrá typedefs y typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Luego, para sustituir value_type y size_types en consecuencia. - AWS

Para aquellos de nosotros para quienes el material de la plantilla y el primer comentario son completamente ajenos, un ejemplo de uso completo con las inclusiones requeridas sería encantador. - Wes Miller

Ahh, bueno, lo descubrí. Puse las líneas de C ++ del comentario de aws dentro del cuerpo de la función de tokenize (), luego edité las líneas de tokens.push_back () para cambiar el ContainerT :: value_type a solo ValueType y cambié (ContainerT :: value_type :: size_type) a ( Tipo de letra). Se corrigieron los bits por los que g ++ había estado lloriqueando. Simplemente invocalo como tokenize (some_string, some_vector); - Wes Miller

Además de ejecutar algunas pruebas de rendimiento en datos de muestra, principalmente lo reduje a la menor cantidad de instrucciones posibles y también a la menor cantidad de copias de memoria posibles habilitadas por el uso de una clase de subcadena que solo hace referencia a compensaciones / longitudes en otras cadenas. (Hice la mía propia, pero hay algunas otras implementaciones). Desafortunadamente, no hay mucho más que se pueda hacer para mejorar esto, pero fueron posibles incrementos graduales. - Marius

Esa es la salida correcta para cuando trimEmpty = true. Manten eso en mente "abo" no es un delimitador en esta respuesta, sino la lista de caracteres delimitadores. Sería simple modificarlo para tomar una sola cadena de caracteres delimitadores (creo str.find_first_of debería cambiar a str.find_first, pero podría estar equivocado ... no puedo probar) - Marius

Aquí tienes otra solución. Es compacto y razonablemente eficiente:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Se puede configurar fácilmente para manejar separadores de cuerdas, cuerdas anchas, etc.

Tenga en cuenta que dividir "" da como resultado una sola cadena vacía y la división "," (es decir, sep) da como resultado dos cadenas vacías.

También se puede expandir fácilmente para omitir tokens vacíos:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Si se desea dividir una cadena en varios delimitadores mientras se omiten los tokens vacíos, se puede usar esta versión:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}

Respondido 05 Oct 16, 01:10

La primera versión es simple y hace el trabajo a la perfección. El único cambio que haría sería devolver el resultado directamente, en lugar de pasarlo como parámetro. - gregschlom

La salida se pasa como parámetro de eficiencia. Si se devolviera el resultado, se requeriría una copia del vector o una asignación de montón que luego tendría que liberarse. - Alec Thomas

Un ligero apéndice a mi comentario anterior: esta función podría devolver el vector sin penalización si se usa la semántica de movimiento de C ++ 11. - Alec Thomas

@AlecThomas: Incluso antes de C ++ 11, ¿no optimizarían la mayoría de los compiladores la copia de retorno a través de NRVO? (+1 de todos modos; muy conciso) - Marcelo Cantos

De todas las respuestas, esta parece ser una de las más atractivas y flexibles. Junto con la línea de obtención con un delimitador, aunque es una solución menos obvia. ¿El estándar c ++ 11 no tiene nada para esto? ¿C ++ 11 admite tarjetas perforadas en estos días? - Spacen Jasset

Esta es mi forma favorita de iterar a través de una cadena. Puedes hacer lo que quieras por palabra.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

Respondido 12 Abr '18, 14:04

¿Es posible declarar word como una char? - abatishchev

Lo siento abatishchev, C ++ no es mi punto fuerte. Pero imagino que no sería difícil agregar un bucle interno para recorrer cada carácter de cada palabra. Pero ahora mismo creo que el ciclo actual depende de los espacios para la separación de palabras. A menos que sepa que solo hay un carácter entre cada espacio, en cuyo caso puede simplemente lanzar "palabra" a un carácter ... lo siento, no puedo ser de más ayuda, he tenido la intención de repasar mi C ++ - gnomed

si declara palabra como un carácter, se repetirá sobre cada carácter que no sea un espacio en blanco. Es bastante simple de probar: stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c; - Wayne Werner

Esto es similar a la pregunta de Stack Overflow ¿Cómo tokenizo una cadena en C ++?.

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

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

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

contestado el 23 de mayo de 17 a las 15:05

¿Esto materializa una copia de todos los tokens, o solo mantiene la posición inicial y final del token actual? - einpoklum

Me gusta lo siguiente porque coloca los resultados en un vector, admite una cadena como delimitación y da control sobre el mantenimiento de valores vacíos. Pero entonces no se ve tan bien.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Por supuesto, Boost tiene un split() que funciona parcialmente así. Y, si por 'espacio en blanco', realmente te refieres a cualquier tipo de espacio en blanco, usando la división de Boost con is_any_of() funciona muy bien.

Respondido el 08 de enero de 17 a las 07:01

Finalmente, una solución que está manejando tokens vacíos correctamente en ambos lados de la cadena: fmuecke

El STL aún no dispone de un método de este tipo.

Sin embargo, puede usar C strtok() función mediante el uso de la std::string::c_str() miembro, o puede escribir el suyo propio. Aquí hay una muestra de código que encontré después de una búsqueda rápida en Google ("División de cadena STL"):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Tomado de: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Si tiene preguntas sobre el código de muestra, deje un comentario y se lo explicaré.

Y solo porque no implementa un typedef llamado iterador o sobrecargar el << operador no significa que sea un código incorrecto. Utilizo funciones C con bastante frecuencia. Por ejemplo, printf y los scanf ambos son más rápidos que std::cin y los std::cout (significativamente), el fopen La sintaxis es mucho más amigable para los tipos binarios, y también tienden a producir EXE más pequeños.

No te dejes convencer por esto "Elegancia sobre rendimiento" acuerdo.

Respondido 12 Abr '18, 14:04

Conozco las funciones de la cadena C y también estoy al tanto de los problemas de rendimiento (los cuales he notado en mi pregunta). Sin embargo, para esta pregunta específica, estoy buscando una solución elegante de C ++. - Ashwin Nanjappa

@Nelson LaQuet: Déjame adivinar: ¿Porque strtok no es reentrante? - paercebal

@Nelson no vez pase string.c_str () a strtok! strtok tira la cadena de entrada a la papelera (inserta caracteres '\ 0' para reemplazar cada delimitador de foudn) y c_str () devuelve una cadena no modificable. - Evan Terán

@Nelson: Esa matriz debe tener el tamaño str.size () + 1 en su último comentario. Pero estoy de acuerdo con tu tesis de que es una tontería evitar las funciones C por razones "estéticas". - j_random_hacker

@paulm: No, la lentitud de las transmisiones de C ++ se debe a varias facetas. Siguen siendo más lentas que las funciones stdio.h incluso cuando la sincronización está desactivada (y en cadenas de cadenas, que no se pueden sincronizar). - Ben Voigt

Aquí hay una función dividida que:

  • es genérico
  • usa C ++ estándar (sin refuerzo)
  • acepta varios delimitadores
  • ignora los tokens vacíos (se puede cambiar fácilmente)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }
    

Ejemplo de uso:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

contestado el 24 de mayo de 17 a las 01:05

Olvidó agregar a la lista de uso: "extremadamente ineficiente" - Tulipán Xander

@XanderTulip, ¿puedes ser más constructivo y explicar cómo o por qué? - Marco M.

@XanderTulip: Supongo que te refieres a que devuelve el vector por valor. La optimización del valor de retorno (RVO, google it) debería encargarse de esto. También en C ++ 11 puede regresar por referencia de movimiento. - Joseph Garvin

En realidad, esto se puede optimizar aún más: en lugar de .push_back (str.substr (...)) se puede usar .emplace_back (str, start, pos - start). De esta manera, el objeto de cadena se construye en el contenedor y, por lo tanto, evitamos una operación de movimiento + otras travesuras realizadas por la función .substr. - Mihai Bişog

@zoopp sí. Buena idea. VS10 no tenía soporte emplace_back cuando escribí esto. Actualizaré mi respuesta. Gracias - Marco M.

Tengo una solución de 2 líneas para este problema:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Luego, en lugar de imprimir, puedes ponerlo en un vector.

Respondido el 15 de enero de 13 a las 04:01

Otra forma más flexible y rápida

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Para usarlo con un vector de cadenas (Editar: ya que alguien señaló no heredar las clases STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

¡Eso es! Y esa es solo una forma de usar el tokenizador, como cómo contar palabras:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Limitado por la imaginación;)

Respondido el 11 de Septiembre de 13 a las 12:09

Aquí hay una solución simple que usa solo la biblioteca de expresiones regulares estándar

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

El argumento regex permite verificar múltiples argumentos (espacios, comas, etc.)

Por lo general, solo verifico dividir en espacios y comas, por lo que también tengo esta función predeterminada:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

El "[\\s,]+" comprueba los espacios\\s) y comas (,).

Tenga en cuenta que si desea dividir wstring en lugar de string,

  • cambia todo std::regex a std::wregex
  • cambia todo sregex_token_iterator a wsregex_token_iterator

Tenga en cuenta que es posible que también desee tomar el argumento de cadena como referencia, según su compilador.

Respondido el 24 de junio de 15 a las 12:06

Esta habría sido mi respuesta favorita, pero std :: regex está roto en GCC 4.8. Dijeron que lo implementaron correctamente en GCC 4.9. Todavía te estoy dando mi +1 - Mchiasson

Este es mi favorito con cambios menores: vector devuelto como referencia como dijiste, y los argumentos "str" ​​y "regex" también pasados ​​por referencias. gracias. - QuantumKarl

Las cadenas sin formato son bastante útiles al tratar con patrones de expresiones regulares. De esa manera, no tienes que usar las secuencias de escape ... Solo puedes usar R"([\s,]+)". - Diana

Usar std::stringstream ya que tiene funciona perfectamente bien, y hace exactamente lo que quería. Sin embargo, si solo está buscando una forma diferente de hacer las cosas, puede usar std::find()/std::find_first_of() y los std::string::substr().

He aquí un ejemplo:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}

Respondido 12 Abr '18, 14:04

Esto solo funciona para delimitadores de un solo carácter. Un simple cambio le permite trabajar con varios caracteres: prev_pos = pos += delimiter.length(); - David Doria

Si desea usar boost, pero desea usar una cadena completa como delimitador (en lugar de caracteres individuales como en la mayoría de las soluciones propuestas anteriormente), puede usar el boost_split_iterator.

Código de ejemplo que incluye una plantilla conveniente:

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

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}

Respondido 09 Feb 12, 13:02

Aquí hay una solución de expresiones regulares que solo usa la biblioteca de expresiones regulares estándar. (Estoy un poco oxidado, por lo que puede haber algunos errores de sintaxis, pero esta es al menos la idea general)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}

Respondido 29 Oct 12, 20:10

Respuestas similares con quizás un mejor enfoque de expresiones regulares: aquíe invirtiendo aquí. - Brent Bradburn

Hay una función llamada strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}

contestado el 02 de mayo de 14 a las 18:05

strtok es de la biblioteca estándar de C, no de C ++. No es seguro usarlo en programas multiproceso. Modifica la cadena de entrada. - Kevin Panko

Debido a que almacena el puntero char de la primera llamada en una variable estática, de modo que en las siguientes llamadas, cuando se pasa NULL, recuerda qué puntero debe usarse. Si un segundo hilo llama strtok cuando todavía se está procesando otro hilo, este puntero char se sobrescribirá y ambos hilos tendrán resultados incorrectos. mkssoftware.com/docs/man3/strtok.3.asp - Kevin Panko

como se mencionó antes, strtok no es seguro e incluso en C se recomienda usar strtok_r - fallas del sistema

strtok_r se puede utilizar si se encuentra en una sección de código a la que se puede acceder. este es el , de manera solución de todo lo anterior que no es "ruido de línea", y es un testimonio de lo que, exactamente, está mal con c ++ - Erik Aronesty

Actualizado para que no pueda haber objeciones por motivos de seguridad de subprocesos de los expertos de C ++. - Erik Aronesty

El corriente de hilo puede ser conveniente si necesita analizar la cadena con símbolos sin espacios:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')

Respondido el 22 de junio de 15 a las 20:06

Hasta ahora usé el de Boost, pero necesitaba algo que no depende de eso, así que llegué a esto:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Un buen punto es que en separators puede pasar más de un carácter.

contestado el 23 de mayo de 11 a las 03:05

Corto y elegante

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

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

puede usar cualquier cadena como delimitador, también se puede usar con datos binarios (std :: string admite datos binarios, incluidos nulos)

mediante:

auto a = split("this!!is!!!example!string", "!!");

salida:

this
is
!example!string

Respondido 14 Jul 16, 23:07

Me gusta esta solución porque permite que el separador sea una cadena y no un carácter, sin embargo, está modificando la cadena en su lugar, por lo que está forzando la creación de una copia de la cadena original. - Alessandro Teruzzi

Hice rodar el mío usando strtok y usé boost para dividir una cuerda. El mejor método que he encontrado es el Biblioteca C ++ String Toolkit. Es increíblemente flexible y rápido.

#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 s("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 s("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;
}

El conjunto de herramientas tiene mucha más flexibilidad de lo que muestra este simple ejemplo, pero su utilidad para analizar una cadena en elementos útiles es increíble.

Respondido el 08 de enero de 14 a las 00:01

Hice esto porque necesitaba una forma fácil de dividir cadenas y cadenas basadas en c ... Espero que alguien más pueda encontrarlo útil también. Además, no depende de tokens y puede usar campos como delimitadores, que es otra clave que necesitaba.

Estoy seguro de que se pueden realizar mejoras para mejorar aún más su elegancia y, por favor, hágalo por todos los medios.

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Ejemplos:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Saldrá:

Este patrón de
is
an
ejemplo
cuerda C

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Para mantener las entradas vacías (por defecto, se excluirán los vacíos):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

El objetivo era hacerlo similar al método Split () de C #, donde dividir una cadena es tan fácil como:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Espero que alguien más pueda encontrar esto tan útil como yo.

Respondido 19 Feb 17, 20:02

Usar std::string_view y Eric Niebler's range-v3 biblioteca:

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Usando un rango for bucle en lugar de ranges::for_each algoritmo:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}

contestado el 19 de mayo de 19 a las 23:05

Yepp, la gama de bases se ve mejor - estoy de acuerdo - Porsche9II

Esta respuesta toma la cadena y la pone en un vector de cadenas. Utiliza la biblioteca boost.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Respondido el 10 de diciembre de 17 a las 00:12

C ++ 20 finalmente nos bendice con un split función. O mejor dicho, un adaptador de alcance. Enlace Godbolt.

#include <iostream>
#include <ranges>
#include <string_view>

namespace ranges = std::ranges;
namespace views = std::views;

using str = std::string_view;

constexpr auto view =
    "Multiple words"
    | views::split(' ')
    | views::transform([](auto &&r) -> str {
        return {
            &*r.begin(),
            static_cast<str::size_type>(ranges::distance(r))
        };
    });

auto main() -> int {
    for (str &&sv : view) {
        std::cout << sv << '\n';
    }
}

contestado el 05 de mayo de 20 a las 22:05

Esto parece MUCHO más complejo que la solución propuesta original. ¡No deberías tener que hacer tanto trabajo solo para dividir una cuerda! - UsuarioX

¿Qué pasa con esto?

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}

Respondido el 20 de diciembre de 12 a las 02:12

Esta es la mejor respuesta aquí, si solo desea dividir en un solo carácter delimitador. Sin embargo, la pregunta original quería dividirse en espacios en blanco, es decir, cualquier combinación de uno o más espacios o tabulaciones consecutivas. Realmente has respondido stackoverflow.com/questions/53849 - Oktalista

Aquí hay otra forma de hacerlo ...

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

Respondido el 08 de enero de 10 a las 06:01

Me gusta usar los métodos boost / regex para esta tarea, ya que brindan la máxima flexibilidad para especificar los criterios de división.

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

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}

Respondido el 12 de junio de 11 a las 13:06

Recientemente tuve que dividir una palabra en mayúsculas y minúsculas en subpalabras. No hay delimitadores, solo caracteres superiores.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Por ejemplo, esto divide "AQueryTrades" en "A", "Consulta" y "Operaciones". La función funciona con cadenas anchas y estrechas. Debido a que respeta la configuración regional actual, divide "RaumfahrtÜberwachungsVerordnung" en "Raumfahrt", "Überwachungs" y "Verordnung".

Nota: std::upper realmente debería pasarse como argumento de plantilla de función. Entonces, la forma más generalizada de esta función se puede dividir en delimitadores como ",", ";" or " "

Respondido el 14 de Septiembre de 11 a las 13:09

Ha habido 2 revoluciones. Qué lindo. Parece como si mi inglés tuviera mucho de "alemán". Sin embargo, el revisionista no solucionó dos errores menores tal vez porque eran obvios de todos modos: std::isupper podría pasarse como argumento, no std::upper. Segundo poner un typename antes de String::const_iterator. - Andreas Spindler

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.