2008-11-14 13 views
40

Tengo una cadena que me gustaría convertir en tokens. Pero la función C strtok() requiere que mi cadena sea char*. ¿Cómo puedo hacer esto simplemente?Uso de strtok con std :: string

me trataron:

token = strtok(str.c_str(), " "); 

que falle debido a que la convierte en una const char*, no un char*

+0

Ver esta pregunta: ¿Es http://stackoverflow.com/questions/53849/how-do-i-tokenize-a-string-in-c/55680 – Ferruccio

Respuesta

15

duplicar la cadena, tokenize, luego liberarlo.

char *dup = strdup(str.c_str()); 
token = strtok(dup, " "); 
free(dup); 
+2

no la mejor pregunta, ¿por qué el uso strtok cuando el idioma en cuestión tiene mejores opciones nativas? –

+1

No necesariamente. Si el contexto de la pregunta se refiere al mantenimiento de una base de código frágil, alejarse del enfoque existente (en teoría, strtok en mi ejemplo) es más riesgoso que cambiar el enfoque. Sin más contexto en la pregunta, prefiero responder a lo que se me pide. – DocMax

+0

Si el asker es un novato, debe evitar hacer free() antes de usar token ... :-) – PhiLho

1

supongo que el lenguaje es C o C++ ...

strtok, IIRC, sustituir los separadores con \ 0. Eso es lo que no puede usar una cadena const. Para solucionar ese problema "rápidamente", si la cadena no es grande, puede simplemente strdup(). Lo cual es sabio si necesitas mantener la cuerda sin alterar (lo que sugiere la const ...).

Por otro lado, es posible que desee utilizar otro tokenizador, tal vez hecho a mano, menos violento en el argumento dado.

20
  1. Si boost está disponible en su sistema (creo que es estándar en la mayoría de distribuciones de Linux en estos días), que tiene una clase Tokenizer que puede utilizar.

  2. Si no, un Google rápido aparece un hand-rolled tokenizer para std :: cadena que probablemente solo pueda copiar y pegar. Es muy corto.

  3. Y, si no te gusta ninguno de estos, entonces aquí hay una función de división() que escribí para hacer mi vida más fácil. Rompe una cuerda en pedazos usando cualquiera de los caracteres en "delim" como separadores. Las piezas se anexan al vector "partes":

    void split(const string& str, const string& delim, vector<string>& parts) { 
        size_t start, end = 0; 
        while (end < str.size()) { 
        start = end; 
        while (start < str.size() && (delim.find(str[start]) != string::npos)) { 
         start++; // skip initial whitespace 
        } 
        end = start; 
        while (end < str.size() && (delim.find(str[end]) == string::npos)) { 
         end++; // skip to end of word 
        } 
        if (end-start != 0) { // just ignore zero-length strings. 
         parts.push_back(string(str, start, end-start)); 
        } 
        } 
    } 
    
+0

+1 para el tokenizador enrollado a mano. –

1

Suponiendo que por "cadena" que está hablando std :: string en C++, es posible que tenga una mirada en el paquete Tokenizer en Boost.

57
#include <iostream> 
#include <string> 
#include <sstream> 
int main(){ 
    std::string myText("some-text-to-tokenize"); 
    std::istringstream iss(myText); 
    std::string token; 
    while (std::getline(iss, token, '-')) 
    { 
     std::cout << token << std::endl; 
    } 
    return 0; 
} 

O, como ya se mencionó, use boost para una mayor flexibilidad.

2

EDIT: uso del reparto const sólo esutilizado para demostrar el efecto de strtok() cuando se aplica a un puntero devuelto por string :: c_str().

Usted no debe usar strtok() ya que modifica la cadena de tokens que puede llevar a no deseado, si no es indefinido, el comportamiento como la cadena C "pertenece" a la instancia de cadena.

#include <string> 
#include <iostream> 

int main(int ac, char **av) 
{ 
    std::string theString("hello world"); 
    std::cout << theString << " - " << theString.size() << std::endl; 

    //--- this cast *only* to illustrate the effect of strtok() on std::string 
    char *token = strtok(const_cast<char *>(theString.c_str()), " "); 

    std::cout << theString << " - " << theString.size() << std::endl; 

    return 0; 
} 

Después de la llamada a strtok(), el espacio fue "eliminado" de la cadena, o rechazaron a un carácter no imprimible, pero la duración se mantiene sin cambios.

>./a.out 
hello world - 11 
helloworld - 11 

Por lo tanto, tiene que recurrir al mecanismo nativo, la duplicación de la cadena o una biblioteca de terceros como se mencionó anteriormente.

+0

descartando la const no ayuda. Es const por una razón. –

+0

@Martin York: De acuerdo. Es const por una razón: abajo votado. –

+1

@Martin York, @Sherm Pendley: ¿leyó la conclusión o solo el fragmento de código? Edité mi respuesta para aclarar lo que quería mostrar aquí. Rgds. – philant

0

En primer lugar, yo diría usar tokenizer de boost.
Alternativamente, si sus datos están separados por espacios, la biblioteca de secuencias de cadenas es muy útil.

Pero las dos anteriores ya han sido cubiertas.
Así que como tercera alternativa C-Like propongo copiar std :: string en un buffer para la modificación.

std::string data("The data I want to tokenize"); 

// Create a buffer of the correct length: 
std::vector<char> buffer(data.size()+1); 

// copy the string into the buffer 
strcpy(&buffer[0],data.c_str()); 

// Tokenize 
strtok(&buffer[0]," "); 
5

Hay una solución más elegante.

Con std :: string puede usar resize() para asignar un búfer adecuadamente grande, y & s [0] para obtener un puntero al búfer interno.

En este punto, mucha gente buena saltará y gritará en la pantalla. Pero este es el hecho. Hace aproximadamente 2 años

el grupo de trabajo de la biblioteca decidió (reunión en Lillehammer) que al igual que para std :: vector, std :: string también debería tener formalmente, no solo en la práctica, un buffer contiguo garantizado.

La otra preocupación es strtok() aumenta el tamaño de la cadena. La documentación de MSDN dice:

Cada llamada a strtok modifica strToken insertando un carácter nulo después del token devuelto por esa llamada.

Pero esto no es correcto. En realidad, la función reemplaza la primera aparición de un carácter separador con \ 0. Sin cambios en el tamaño de la cuerda. Si tenemos esta cadena:

uno-dos --- tres - cuatro

vamos a terminar con

uno \ 0two \ 0 - tres \ 0 y cuatro

Así que mi solución es muy sencilla:


std::string str("some-text-to-split"); 
char seps[] = "-"; 
char *token; 

token = strtok(&str[0], seps); 
while(token != NULL) 
{ 
    /* Do your thing */ 
    token = strtok(NULL, seps); 
} 

Leer la discusión sobre http://www.archivum.info/comp.lang.c++/2008-05/02889/does_std::string_have_something_like_CString::GetBuffer

+0

-1. 'strtok()' funciona en una cadena terminada en nulo, mientras que la memoria intermedia de 'std :: string' no es obligatoria para ser terminada en nulo. No hay forma de evitar 'c_str()'. – SnakE

+0

@SnakE 'std :: string''s * buffer * is * requiere terminación en nulo. 'data' y' c_str' deben ser idénticos y ['data() + i == & operator [] (i)' para cada 'i' en' [0, size()] '] (http: // en.cppreference.com/w/cpp/string/basic_string/c_str). – Leushenko

+0

@Leushenko tienes razón en parte. La terminación nula solo está garantizada desde C++ 11. He agregado una nota a la respuesta. Levantaré mi -1 tan pronto como mi edición sea aceptada. – SnakE

0

Si no le molesta el código abierto, puede utilizar las clases de subfamilia y subpanel de https://github.com/EdgeCast/json_parser. La cadena original se deja intacta, no hay asignación ni copia de datos. No he compilado lo siguiente, por lo que puede haber errores.

std::string input_string("hello world"); 
subbuffer input(input_string); 
subparser flds(input, ' ', subparser::SKIP_EMPTY); 
while (!flds.empty()) 
{ 
    subbuffer fld = flds.next(); 
    // do something with fld 
} 

// or if you know it is only two fields 
subbuffer fld1 = input.before(' '); 
subbuffer fld2 = input.sub(fld1.length() + 1).ltrim(' '); 
1

falla porque str.c_str() vuelve constante de cadena pero requiere char * strtok (char * str, const char * delimiters) cadena volátil. Por lo tanto, debe usar * const_cast < char > para hacer que sea voletile. Te estoy dando un programa completo pero pequeño para tokenizar la cadena usando la función C strtok().

#include <iostream> 
#include <string> 
#include <string.h> 
using namespace std; 
int main() { 
    string s="20#6 5, 3"; 
    char *str=const_cast< char *>(s.c_str());  
    char *tok; 
    tok=strtok(str, "#, ");  
    int arr[4], i=0;  
    while(tok!=NULL){ 
     arr[i++]=stoi(tok); 
     tok=strtok(NULL, "#, "); 
    }  
    for(int i=0; i<4; i++) cout<<arr[i]<<endl; 
    return 0; 
} 
+1

FYI: strtok cambiará el valor de s. No deberías usar const_cast, ya que esto simplemente oculta un problema. – orbitcowboy

Cuestiones relacionadas