461 votes

Comment tokeniser une chaîne de caractères en C++ ?

Java dispose d'une méthode de fractionnement pratique :

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

Existe-t-il un moyen simple de faire cela en C++ ?

230 votes

Je ne peux pas croire que cette tâche de routine soit un tel casse-tête en c++.

6 votes

Ce n'est pas un casse-tête en c++ - il y a plusieurs façons d'y parvenir. Les programmeurs sont moins au courant de c++ que de c# - c'est une question de marketing et d'investissements... voir ceci pour les différentes options c++ pour réaliser la même chose : cplusplus.com/faq/sequences/strings/split

11 votes

@hB0 passer par beaucoup de questions réponses et toujours pas décider des moyens est un mal de tête. l'un a besoin de cette bibliothèque, l'autre est juste pour les espaces, l'autre ne gère pas les espaces .

193voto

Ferruccio Points 51508

El Boost tokenizer peut rendre ce genre de chose assez simple :

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

Mise à jour pour 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;
    }
}

1 votes

C'est bien, je l'ai utilisé récemment. Mon compilateur Visual Studio se plaint étrangement jusqu'à ce que j'utilise un espace pour séparer les deux caractères ">" avant le bit tokens(text, sep) : (error C2947 : expecting '>' to terminate template-argument-list, found '>>')

0 votes

@AndyUK oui, sans l'espace le compilateur l'analyse comme un opérateur d'extraction plutôt que deux gabarits de fermeture.

0 votes

Théoriquement, cela a été corrigé dans C++0x.

168voto

Konrad Rudolph Points 231505

Les algorithmes de la bibliothèque standard C++ sont presque universellement basés sur des itérateurs plutôt que sur des conteneurs concrets. Malheureusement, cela rend difficile la mise à disposition d'une fonction de type Java split dans la bibliothèque standard C++, même si personne ne soutient que cela serait pratique. Mais quel serait son type de retour ? std::vector<std::basic_string<…>> ? Peut-être, mais alors nous sommes obligés d'effectuer des allocations (potentiellement redondantes et coûteuses).

Au lieu de cela, le C++ offre une pléthore de moyens de diviser les chaînes de caractères en fonction de délimiteurs arbitrairement complexes, mais aucun d'entre eux n'est encapsulé aussi bien que dans d'autres langages. Les nombreux moyens remplir des articles de blog entiers .

Dans sa forme la plus simple, vous pourriez itérer en utilisant std::string::find jusqu'à ce que tu atteignes std::string::npos et extraire le contenu en utilisant std::string::substr .

Une version plus fluide (et idiomatique, mais basique) pour le fractionnement sur les espaces blancs utiliserait une balise std::istringstream :

auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};

while (iss >> str) {
    process(str);
}

Utilisation de std::istream_iterator s En outre, le contenu du flux de chaînes de caractères peut également être copié dans un vecteur à l'aide de son constructeur de plage d'itérateurs.

Plusieurs bibliothèques (telles que Boost.Tokenizer ) proposent des tokénisateurs spécifiques.

Un fractionnement plus avancé nécessite des expressions régulières. Le C++ fournit les std::regex_token_iterator à cette fin en particulier :

auto const str = "The quick brown fox"s;
auto const re = std::regex{R"(\s+)"};
auto const vec = std::vector<std::string>(
    std::sregex_token_iterator{begin(str), end(str), re, -1},
    std::sregex_token_iterator{}
);

61 votes

Malheureusement, le coup de pouce n'est pas toujours disponible pour tous les projets. Je vais devoir chercher une réponse sans boost.

41 votes

Tous les projets ne sont pas ouverts à l'"open source". Je travaille dans des industries fortement réglementées. Ce n'est pas un problème, vraiment. C'est juste un fait de la vie. Boost n'est pas disponible partout.

5 votes

@NonlinearIdeas L'autre question/réponse ne concernait pas du tout les projets Open Source. Il en va de même pour cualquier projet. Cela dit, je comprends bien sûr les normes restreintes telles que MISRA C, mais il est entendu que vous construisez tout à partir de zéro de toute façon (à moins que vous ne trouviez une bibliothèque conforme - une rareté). Quoi qu'il en soit, le problème n'est pas que "Boost n'est pas disponible", mais que vous avez des exigences particulières pour lesquelles presque tout le monde est d'accord. cualquier Une réponse générale serait inadaptée.

138voto

user35978 Points 1187

Un autre moyen rapide est d'utiliser getline . Quelque chose comme :

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

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

Si vous voulez, vous pouvez faire un simple split() retournant un vector<string> qui est vraiment utile.

2 votes

J'ai eu des problèmes en utilisant cette technique avec des caractères 0x0A dans la chaîne, ce qui faisait sortir la boucle while prématurément. Sinon, c'est une solution simple et rapide.

4 votes

C'est bien, mais il faut garder à l'esprit qu'en faisant cela, le délimiteur par défaut ' \n n'est pas pris en compte. Cet exemple fonctionnera, mais si vous utilisez quelque chose comme : while(getline(inFile,word,' ')) où inFile est un objet ifstream contenant plusieurs lignes, vous obtiendrez des résultats bizarres

0 votes

C'est dommage que getline retourne le flux plutôt que la chaîne, ce qui le rend inutilisable dans les listes d'initialisation sans stockage temporaire

117voto

Mark Points 6505

Utilisez strtok. À mon avis, il n'y a pas besoin de construire une classe autour de la tokenisation, à moins que strtok ne vous fournisse pas ce dont vous avez besoin. Ce n'est peut-être pas le cas, mais en plus de 15 ans d'écriture de divers codes d'analyse syntaxique en C et C++, j'ai toujours utilisé strtok. Voici un exemple

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

Quelques mises en garde (qui pourraient ne pas répondre à vos besoins). La chaîne est "détruite" dans le processus, ce qui signifie que les caractères EOS sont placés en ligne dans les emplacements de délimitation. Pour une utilisation correcte, vous devrez peut-être créer une version non-const de la chaîne. Vous pouvez également modifier la liste des délimiteurs à mi-parcours.

À mon avis, le code ci-dessus est beaucoup plus simple et facile à utiliser que l'écriture d'une classe séparée pour cela. Pour moi, c'est une de ces fonctions que le langage fournit et il le fait bien et proprement. Il s'agit simplement d'une solution "basée sur le C". C'est approprié, c'est facile, et vous n'avez pas à écrire beaucoup de code supplémentaire :-)

47 votes

Ce n'est pas que je n'aime pas le C, mais strtok n'est pas thread-safe, et vous devez être certain que la chaîne que vous lui envoyez contient un caractère nul pour éviter un éventuel dépassement de tampon.

11 votes

Il existe strtok_r, mais c'était une question sur le C++.

3 votes

@tloach : dans le compilateur MS C++, strtok est thread safe car la variable statique interne est créée sur le TLS (thread local storage) (en fait, cela dépend du compilateur).

85voto

KeithB Points 9459

Vous pouvez utiliser les flux, les itérateurs et l'algorithme de copie pour faire cela assez directement.

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

18 votes

Je trouve ces std: : irritants à lire pourquoi ne pas utiliser "using" ?

86 votes

@Vadi : parce que modifier le message de quelqu'un d'autre est assez intrusif. @pheze : Je préfère laisser le std De cette façon, je sais d'où vient mon objet, c'est une simple question de style.

7 votes

Je comprends ta raison et je pense que c'est un bon choix si ça marche pour toi, mais d'un point de vue pédagogique, je suis d'accord avec pheze. Il est plus facile de lire et de comprendre un exemple complètement étranger comme celui-ci avec un "using namespace std" en haut parce que cela demande moins d'effort pour interpréter les lignes suivantes... surtout dans ce cas parce que tout provient de la bibliothèque standard. Vous pouvez le rendre facile à lire et évident d'où viennent les objets par une série de "using std::string ;" etc. D'autant plus que la fonction est très courte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X