Question

Java a une pratique d'une méthode split:

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

Est-il un moyen facile de faire cela en C++?

Était-ce utile?

La solution

Votre simple peut facilement être construit à l'aide de la std::string::find la méthode.Cependant, prendre un coup d'oeil à Coup de pouce.Générateur de jetons.C'est génial.Boost généralement très cool les instruments à cordes.

Autres conseils

L' Boost tokenizer la classe peut faire ce genre de chose très 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 le 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;
    }
}

Voici un vrai simple:

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

Utiliser strtok.À mon avis, il n'y a pas besoin de construire une classe autour de la segmentation, à moins que strtok n'est pas vous fournir ce dont vous avez besoin.Il ne pourrait pas, mais dans+ de 15 ans d'écriture différents d'analyse de code en C et C++, j'ai toujours utilisé des 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 peut ne pas convenir à vos besoins).La chaîne est "détruit" dans le processus, ce qui signifie que EOS personnages sont placés en ligne dans le delimter spots.L'utilisation correcte peut vous obliger à prendre une non-const version de la chaîne.Vous pouvez également modifier la liste des délimiteurs milieu de l'analyser.

Dans ma propre opinion, le code ci-dessus est beaucoup plus simple et plus facile à utiliser que l'écriture d'une catégorie distincte pour elle.Pour moi, c'est l'une de ces fonctions que le langage possède et il le fait bien et proprement.C'est tout simplement un "C" basée sur la solution.C'est approprié, c'est facile, et vous n'avez pas à écrire beaucoup de code en plus :-)

Un autre moyen rapide est l'utilisation de getline.Quelque chose comme:

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

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

Si vous le souhaitez, vous pouvez faire un simple split() méthode retournant une vector<string>, qui est vraiment utile.

Vous pouvez utiliser les flux, les itérateurs, et l'algorithme de copie pour ce faire 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);
}

N'en déplaise aux gens, mais pour un problème simple, vous faites des choses façon trop compliqué.Il y a beaucoup de raisons d'utiliser Boost.Mais pour quelque chose de simple, c'est comme le fait de frapper une mouche avec un 20# traîneau.

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

Par exemple (pour Doug cas),

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

Et oui, on aurait pu split() renvoient un nouveau vecteur plutôt que de passer une dans.Il est trivial de les envelopper et les surcharges.Mais selon ce que j'ai fais, je trouve souvent qu'il est préférable de re-utiliser les objets existants plutôt que d'en créer de nouveaux.(Pourvu que je n'oublie pas de vider le vecteur entre les deux!)

Référence: http://www.cplusplus.com/reference/string/string/.

(J'avais écrit une réponse à Doug question: Les Chaînes C++ de la Modification et de l'Extraction basée sur des Séparateurs (fermé).Mais depuis Martin York fermé à cette question avec un pointeur sur ici...Je vais généraliser mon code.)

Boost a une forte fonction de répartition: boost::algorithme::split.

Exemple de programme:

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

Sortie:

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

Une solution à l'aide de 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;
    }
}

Je sais que vous avez demandé pour un C++ solution, mais vous pouvez considérer ceci utiles:

Qt

#include <QString>

...

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

L'avantage sur coup de pouce dans cet exemple, c'est que c'est un direct à une cartographie de votre poste de code.

Pour en savoir plus Documentation de Qt

Voici un exemple de générateur de jetons de classe qui pourrait faire ce que vous voulez

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

Exemple:

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

C'est un simple STL seule solution (~5 lignes!) à l'aide de std::find et std::find_first_not_of qui gère les répétitions de la délimiteur (tels que les espaces ou les périodes, par exemple), ainsi attaque et de fuite des délimiteurs:

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

L'essayer live!

pystring une petite bibliothèque qui met en oeuvre un ensemble de Python dans la chaîne de fonctions, y compris la méthode de fractionnement:

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

J'ai posté cette réponse pour une question similaire.
Ne pas réinventer la roue.J'ai utilisé un certain nombre de bibliothèques et de la manière la plus rapide et la plus flexible que j'ai rencontré est: Chaîne C++ Bibliothèque D'Outils.

Voici un exemple de comment l'utiliser que j'ai posté ailleurs sur le 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;
}

Cochez cette exemple.Il peut vous aider..

#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 a un très bon générateur de jetons.À partir de 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

Vous pouvez tout simplement utiliser un bibliothèque d'expression régulière et le résoudre à l'aide d'expressions régulières.

L'utilisation de l'expression (\w+) et la variable \1 (ou 1 $en fonction de la mise en œuvre de bibliothèque d'expressions régulières).

Si vous êtes prêt à utiliser C, vous pouvez utiliser le strtok fonction.Vous devez faire attention à multi-threading problèmes lors de son utilisation.

Pour des choses simples je viens d'utiliser le texte suivant:

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

Lâche avertissement:J'écris en temps réel logiciel de traitement de données où les données grâce à des fichiers binaires, les sockets, ou de certains d'appel d'API (cartes I/O, appareil photo).Je n'ai jamais utiliser cette fonction pour quelque chose de plus compliqué ou à temps critique que la lecture externe des fichiers de configuration au démarrage.

Beaucoup trop compliqué suggestions ici.Essayez ce simple std::string solution:

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

Je pensais que c'était ce que l' >> opérateur sur chaîne de flux a été de:

string word; sin >> word;

Adam Pierce répondre fournit une filée main générateur de jetons de prendre un const char*.C'est un peu plus compliqué à faire avec les itérateurs parce que incrémentation d'une stringla fin de l'itérateur est pas défini.Cela dit, étant donné string str{ "The quick brown fox" } nous pouvons certainement le faire:

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

Live Exemple


Si vous êtes à la recherche d'abstraire la complexité en utilisant les fonctionnalités standards, comme Sur Freund suggère strtok est une option simple:

vector<string> tokens;

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

Si vous n'avez pas accès à C++17, vous devrez remplacer data(str) comme dans cet exemple: http://ideone.com/8kAGoa

Bien que non démontré dans l'exemple, strtok n'a pas besoin d'utiliser le même séparateur pour chaque jeton.Avec cet avantage mais, il y a plusieurs inconvénients:

  1. strtok ne peut pas être utilisé sur de multiples strings dans le même temps:Soit un nullptr doivent être transmises pour continuer à la segmentation de l'actuel string ou une nouvelle char* pour marquer doit être passé (il y a des implémentations qui ne l'appui de cette cependant, comme: strtok_s)
  2. Pour la même raison strtok ne peut pas être utilisé sur plusieurs threads simultanément (ce qui peut toutefois être mise en œuvre définis, par exemple: Visual Studio de la mise en œuvre est thread-safe)
  3. L'appel strtok modifie le string c'est de l'exploitation, il ne peut pas être utilisé sur const strings, const char*s, ou des chaînes de caractères littérales, pour marquer l'une de ces avec strtok ou pour fonctionner sur un string qui est contenu doit être préservée, str devrait être copié, puis la copie pourrait être exploité sur

Les deux méthodes précédentes ne peuvent pas générer un sous vector sur place, c'est à dire sans en faire abstraction dans une fonction d'assistance, ils ne peut pas initialiser const vector<string> tokens.Cette fonctionnalité et la capacité à accepter tout white-space délimiteur peut être exploitée à l'aide d'un istream_iterator.Par exemple: const string str{ "The quick \tbrown \nfox" } nous pouvons faire ceci:

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

Live Exemple

La nécessaire construction d'un istringstream pour que cette option est beaucoup plus économique que les 2 précédentes options, cependant, ce coût est généralement cachés dans les frais de string l'allocation.


Si aucune des options ci-dessus sont flexable assez pour votre segmentation des besoins, de la plus souple, permet à l'aide d'un regex_token_iterator bien sûr, avec cette flexibilité s'accompagne d'une plus grande dépense, mais encore une fois c'est probablement caché dans le string l'allocation des coûts.Disons, par exemple, nous voulons marquer basée sur la non-virgules échappées, également manger des blancs de l'espace, compte tenu de la manière suivante: const string str{ "The ,qu\\,ick ,\tbrown, fox" } nous pouvons faire ceci:

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

Live Exemple

Voici une approche qui vous permet de contrôler si vide jetons sont inclus (comme strsep) ou exclu (comme 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 semble bizarre qu'avec tout ce que nous vitesse conscient nerds ici DONC, on n'a présenté une version qui utilise un temps de compilation généré look up table pour le délimiteur (exemple de mise en œuvre plus bas).À l'aide d'une table et itérateurs devrait battre std::regex en efficacité, si vous n'avez pas besoin de battre les regex, juste l'utiliser, son standard de C++11 et super flexible.

Certains ont suggéré que les regex déjà, mais pour les noobs ici est un emballés exemple qui devrait faire exactement ce que l'OP attend:

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 nous avons besoin d'être plus rapide et d'accepter la contrainte que tous les caractères de 8 bits, nous pouvons faire une table au moment de la compilation à l'aide de la métaprogrammation:

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

Avec qui au lieu de faire un getNextToken la fonction est facile:

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

Son utilisation est aussi facile:

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

Ici est un exemple vivant: http://ideone.com/GKtkLQ

Je sais que cette question est déjà répondu, mais j'ai envie de contribuer.Peut-être que ma solution est un peu simple mais c'est ce que je suis venu avec:

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

S'il vous plaît commentaire si il y a une meilleure approche de quelque chose dans mon code ou si quelque chose est faux.

Il n'y a pas de moyen direct pour ce faire.Reportez-vous ce projet de code de code source pour savoir comment créer une classe pour cette.

vous pouvez profiter de boost::make_find_iterator.Quelque chose de semblable à ceci:

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 la longueur maximale de la chaîne d'entrée pour être segmentées en est connue, on peut l'exploiter et de mettre en œuvre très rapide version.Je suis esquisse l'idée de base ci-dessous, qui a été inspiré par les deux strtok() et le "suffixe tableau"structure de données décrite Jon Bentley "Programmation Perl" 2e édition, chapitre 15.La classe C++ dans ce cas seulement donne une certaine organisation et de la commodité d'utilisation.La mise en œuvre montré peut être facilement étendu pour la suppression d'attaque et de fuite des espaces dans les jetons.

Fondamentalement, on peut remplacer le séparateur de caractères chaîne de terminaison de '\0'-caractères et de définir des pointeurs vers les jetons dans la chaîne modifiée.Dans le cas extrême, lorsque la chaîne se compose uniquement de séparateurs, on obtient chaîne-longueur de plus de 1 entraînant vide jetons.C'est pratique pour dupliquer la chaîne à modifier.

Fichier d'en-tête:

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

Implementattion fichier:

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 scénario d'utilisation serait:

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

sortie:

Item1

Item2
Item3

boost::tokenizer c'est votre ami, mais envisager de faire votre code portable avec référence à l'internationalisation (i18n) des problèmes en utilisant wstring/wchar_t au lieu de l'héritage string/char les types.

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

Simple code C++ (C++standard 98), accepte plusieurs séparateurs (spécifié dans un std::string), n'utilise que des vecteurs, des cordes et des itérateurs.

#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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top