web-dev-qa-db-fra.com

Utiliser strtok avec un std :: string

J'ai une chaîne que je voudrais tokenize . Mais la fonction C strtok() nécessite que ma chaîne soit un char*. Comment puis-je le faire simplement?

J'ai essayé:

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

qui échoue car il le transforme en un const char*, pas un char*

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

Ou, comme mentionné, utilisez boost pour plus de flexibilité.

60
Chris Blackwell
  1. Si boost est disponible sur votre système (je pense que c'est standard sur la plupart des distributions Linux de nos jours), il dispose d'une classe Tokenizer que vous pouvez utiliser.

  2. Si ce n'est pas le cas, un rapide Google met en place un tokenizer roulé à la main pour std :: string que vous pouvez probablement simplement copier et coller. C'est très court.

  3. Et, si vous n'aimez ni l'un ni l'autre, voici la fonction split () que j'ai écrite pour faciliter ma vie. Il va briser une chaîne en utilisant n'importe lequel des caractères de "delim" comme séparateurs. Les pièces sont ajoutées au vecteur "parties":

    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));
        }
      }
    }
    
20
Todd Gamblin

Dupliquer la chaîne, la marquer, puis la libérer.

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

Il existe une solution plus élégante.

Avec std :: string, vous pouvez utiliser resize () pour allouer un tampon suffisamment grand et & s [0] pour obtenir un pointeur sur le tampon interne .

À ce stade, beaucoup de braves gens vont sauter et crier à l'écran. Mais c'est le fait. Il y a environ 2 ans

le groupe de travail des bibliothèques a décidé (lors d'une réunion à Lillehammer) que, tout comme pour std :: vector, std :: string devrait également disposer, de manière formelle, et non seulement dans la pratique, d'un tampon contigu garanti .

L'autre préoccupation est que strtok () augmente la taille de la chaîne. La documentation MSDN indique:

Chaque appel à strtok modifie strToken en insérant un caractère nul après le jeton renvoyé par cet appel .

Mais ce n'est pas correct. En réalité, la fonction remplace l'occurrence first d'un caractère séparateur par\0. Aucun changement dans la taille de la chaîne. Si nous avons cette chaîne:

un deux trois quatre

nous finirons avec

un\0deux\0 - trois\0-quatre

Donc ma solution est très simple:


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

Lire la discussion sur  http://www.archivum.info/comp.lang.c++/2008-05/02889/does_std::string_have_something_like_CString::GetBuffer

5
Martin Dimitrov

EDIT: l'utilisation de const cast est seulement est utilisée pour démontrer l'effet de strtok() lorsqu'il est appliqué à un pointeur renvoyé par string :: c_str ().

Vous ne devez pas utiliser strtok()car il modifie la chaîne sous forme de jetons qui peut entraîner un comportement indésirable, voire indéfini, car la chaîne C "appartient" à l'instance de chaîne. 

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

Après l'appel à strtok(), l'espace a été "supprimé" de la chaîne ou réduit à un caractère non imprimable, mais la longueur reste inchangée. 

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

Par conséquent, vous devez recourir au mécanisme natif, à la duplication de la chaîne ou à une bibliothèque tierce, comme indiqué précédemment. 

2
philant

Il échoue carstr.c_str()renvoie une chaîne constante, mais char * strtok (char * str, const char * delimiters ) nécessite une chaîne volatile. Donc vous devez utiliser * const_cast <char >pour le rendre voletile . Je vous donne un programme complet mais petit pour tokeniser la chaîne en utilisant la fonction 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
Satendra

Je suppose que le langage est C, ou C++ ...

strtok, IIRC, remplacez les séparateurs par\0. C’est ce qu’il ne peut pas utiliser une chaîne de const. Ce qui est judicieux si vous devez garder la chaîne inchangée (ce que le const suggère ...).

D'un autre côté, vous pouvez utiliser un autre tokenizer, peut-être roulé à la main, moins violent sur l'argument donné.

1
PhiLho

En supposant que par "chaîne" vous parlez de std :: string en C++, vous pouvez jeter un coup d'œil au Tokenizer package dans Boost .

1
Sherm Pendley

Si vous n’aimez pas l’open source, vous pouvez utiliser les classes de sous-tampon et de sous-fournisseur de https://github.com/EdgeCast/json_parser . La chaîne d'origine est laissée intacte, il n'y a pas d'allocation ni de copie des données. Je n'ai pas compilé ce qui suit, il peut donc y avoir des erreurs.

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(' ');
0
Scott Yeager

Tout d'abord, je dirais utiliser boost tokenizer.
Sinon, si vos données sont séparées par des espaces, la bibliothèque de flux de chaînes est très utile.

Mais les deux ci-dessus ont déjà été couverts.
Donc, comme troisième alternative C-Like, je propose de copier std :: string dans un tampon pour modification.

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]," ");
0
Martin York