Wie kann ich einen String in C ++ tokenisieren?

414

Java hat eine bequeme Split-Methode:

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

Gibt es eine einfache Möglichkeit, dies in C ++ zu tun?

Bill die Eidechse
quelle
171
Ich kann nicht glauben, dass diese Routineaufgabe in c ++
wfbarksdale
6
In C ++ gibt es keine Kopfschmerzen - es gibt verschiedene Möglichkeiten, dies zu erreichen. Programmierer sind sich c ++ weniger bewusst als c # - es geht um Marketing und Investitionen ... siehe dies für verschiedene c ++ - Optionen, um dasselbe zu erreichen: cplusplus.com/faq/sequences/strings/split
hB0
9
@ hB0 viele Fragen durch Antworten und immer noch nicht entscheiden Mittel ist Kopfschmerzen. Der eine braucht diese Bibliothek, der andere ist nur für Leerzeichen, der andere behandelt keine Leerzeichen.
Paschalis
1
Mögliches Duplikat von Split a string in C ++?
KOB
2
Warum muss alles in C ++ ein Kampf sein?
Wael Assaf

Antworten:

144

C ++ - Standardbibliotheksalgorithmen basieren ziemlich universell auf Iteratoren und nicht auf konkreten Containern. Leider ist es schwierig, eine Java-ähnliche splitFunktion in der C ++ - Standardbibliothek bereitzustellen , obwohl niemand argumentiert, dass dies praktisch wäre. Aber wie würde die Rückgabe aussehen? std::vector<std::basic_string<…>>? Vielleicht, aber dann sind wir gezwungen, (möglicherweise redundante und kostspielige) Zuweisungen durchzuführen.

Stattdessen bietet C ++ eine Vielzahl von Möglichkeiten, Zeichenfolgen basierend auf beliebig komplexen Trennzeichen zu teilen, aber keine davon ist so gut gekapselt wie in anderen Sprachen. Die zahlreichen Möglichkeiten füllen ganze Blog-Beiträge .

Im einfachsten Fall können Sie mit iterieren, std::string::findbis Sie drücken std::string::npos, und den Inhalt mit extrahieren std::string::substr.

Eine flüssigere (und idiomatischere, aber einfachere) Version zum Aufteilen auf Leerzeichen würde Folgendes verwenden std::istringstream:

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

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

Mit std::istream_iterators kann der Inhalt des String-Streams auch mit seinem Iterator-Range-Konstruktor in einen Vektor kopiert werden.

Mehrere Bibliotheken (wie Boost.Tokenizer ) bieten bestimmte Tokenisierer.

Für eine erweiterte Aufteilung sind reguläre Ausdrücke erforderlich. C ++ bietet std::regex_token_iteratorzu diesem Zweck insbesondere Folgendes:

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{}
);
Konrad Rudolph
quelle
53
Leider ist Boost nicht immer für alle Projekte verfügbar. Ich muss nach einer Non-Boost-Antwort suchen.
FuzzyBunnySlippers
36
Nicht jedes Projekt ist offen für "Open Source". Ich arbeite in stark regulierten Branchen. Das ist wirklich kein Problem. Es ist nur eine Tatsache des Lebens. Boost ist nicht überall verfügbar.
FuzzyBunnySlippers
5
@NonlinearIdeas Bei der anderen Frage / Antwort ging es überhaupt nicht um Open Source-Projekte. Gleiches gilt für jedes Projekt. Das heißt, ich verstehe natürlich eingeschränkte Standards wie MISRA C, aber dann versteht es sich, dass Sie sowieso alles von Grund auf neu erstellen (es sei denn, Sie finden zufällig eine kompatible Bibliothek - eine Seltenheit). Auf jeden Fall geht es kaum darum, dass „Boost nicht verfügbar ist“ - Sie haben spezielle Anforderungen, für die fast jede allgemeine Antwort ungeeignet wäre.
Konrad Rudolph
1
@NonlinearIdeas In diesem Fall sind auch die anderen Nicht-Boost-Antworten nicht MISRA-konform.
Konrad Rudolph
3
@Dmitry Was ist "STL barf"?! Und die ganze Community ist sehr dafür, den C-Präprozessor zu ersetzen - tatsächlich gibt es Vorschläge, dies zu tun. Aber Ihr Vorschlag, stattdessen PHP oder eine andere Sprache zu verwenden, wäre ein großer Rückschritt.
Konrad Rudolph
188

Die Boost-Tokenizer- Klasse kann so etwas ganz einfach machen:

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

Aktualisiert für 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;
    }
}
Ferruccio
quelle
1
Gutes Zeug, das habe ich kürzlich genutzt. Mein Visual Studio-Compiler hat einen merkwürdigen Fehler, bis ich die beiden Zeichen ">" vor dem Token-Bit (Text, Sep) durch ein Leerzeichen trenne: (Fehler C2947: Erwartet, dass '>' die Vorlagenargumentliste beendet, gefunden '> > ')
AndyUK
@AndyUK Ja, ohne den Speicherplatz analysiert der Compiler ihn als Extraktionsoperator und nicht als zwei schließende Vorlagen.
EnabrenTane
Theoretisch wurde das in C ++ 0x behoben
David Souther
3
Achten Sie auf die dritten Parameter des char_separatorKonstruktors ( drop_empty_tokensist die Standardeinstellung, Alternative ist keep_empty_tokens).
Benoit
5
@puk - Es ist ein häufig verwendetes Suffix für C ++ - Headerdateien. (wie .hfür C-Header)
Ferruccio
167

Hier ist eine ganz einfache:

#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;
}
Adam Pierce
quelle
Muss ich einen Prototyp für diese Methode in die .h-Datei einfügen?
Suhrob Samiev
5
Dies ist nicht gerade die "beste" Antwort, da immer noch ein Zeichenfolgenliteral verwendet wird, bei dem es sich um das einfache C-Konstantenzeichenarray handelt. Ich glaube, der Fragesteller hat gefragt, ob er eine C ++ - Zeichenfolge vom Typ "Zeichenfolge", die von dieser eingeführt wurde, tokenisieren könnte.
Vijay Kumar Kanta
Dies erfordert eine neue Antwort, da ich stark vermute, dass die Aufnahme regulärer Ausdrücke in C ++ 11 die beste Antwort geändert hat.
Omnifarious
113

Verwenden Sie strtok. Meiner Meinung nach ist es nicht erforderlich, eine Klasse für das Tokenisieren zu erstellen, es sei denn, strtok bietet Ihnen nicht das, was Sie benötigen. Möglicherweise nicht, aber in mehr als 15 Jahren, in denen ich verschiedene Parsing-Codes in C und C ++ geschrieben habe, habe ich immer strtok verwendet. Hier ist ein Beispiel

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

Ein paar Vorsichtsmaßnahmen (die möglicherweise nicht Ihren Anforderungen entsprechen). Die Zeichenfolge wird dabei "zerstört", was bedeutet, dass EOS-Zeichen in den Begrenzungspunkten inline platziert werden. Für eine korrekte Verwendung müssen Sie möglicherweise eine nicht konstante Version der Zeichenfolge erstellen. Sie können auch die Liste der Trennzeichen während der Analyse ändern.

Meiner Meinung nach ist der obige Code viel einfacher und benutzerfreundlicher als das Schreiben einer separaten Klasse dafür. Für mich ist dies eine der Funktionen, die die Sprache bietet und die sie gut und sauber macht. Es ist einfach eine "C-basierte" Lösung. Es ist angemessen, es ist einfach und Sie müssen nicht viel zusätzlichen Code schreiben :-)

Kennzeichen
quelle
42
Nicht, dass ich C nicht mag, aber strtok ist nicht threadsicher, und Sie müssen sicher sein, dass die Zeichenfolge, die Sie senden, ein Nullzeichen enthält, um einen möglichen Pufferüberlauf zu vermeiden.
Tloach
11
Es gibt strtok_r, aber dies war eine C ++ - Frage.
Prof. Falken Vertrag verletzt
3
@tloach: In MS C ++ Compiler ist strtok threadsicher, da die interne statische Variable auf dem TLS (Thread Local Storage) erstellt wird (tatsächlich ist es vom Compiler abhängig)
Ahmed Said
3
@ahmed: thread safe bedeutet mehr als nur die Funktion zweimal in verschiedenen Threads ausführen zu können. In diesem Fall ist es möglich, dass der String während des gesamten Strtok-Laufs gültig ist, wenn der Thread während der Ausführung von strtok geändert wird. Strtok wird jedoch weiterhin durcheinander gebracht, da sich der String geändert hat. Er hat jetzt bereits das Nullzeichen überschritten und wird dies auch tun Lesen Sie den Speicher so lange weiter, bis entweder eine Sicherheitsverletzung auftritt oder ein Nullzeichen gefunden wird. Dies ist ein Problem mit den ursprünglichen C-String-Funktionen, wenn Sie an keiner Stelle eine Länge angeben, auf die Sie stoßen.
Tloach
4
Für strtok ist ein Zeiger auf ein nicht-const-nullterminiertes char-Array erforderlich, was in C ++ - Code nicht häufig vorkommt. Wie konvertieren Sie am liebsten aus einem std :: string in dieses Array?
FuzzyTew
105

Ein weiterer schneller Weg ist die Verwendung getline. Etwas wie:

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

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

Wenn Sie möchten, können Sie eine einfache split()Methode erstellen vector<string>, die a zurückgibt, was sehr nützlich ist.

user35978
quelle
2
Ich hatte Probleme bei der Verwendung dieser Technik mit 0x0A-Zeichen in der Zeichenfolge, wodurch die while-Schleife vorzeitig beendet wurde. Ansonsten ist es eine schöne einfache und schnelle Lösung.
Ryan H.
4
Dies ist gut, aber Sie müssen nur berücksichtigen, dass das Standardtrennzeichen '\ n' dabei nicht berücksichtigt wird. Dieses Beispiel wird funktionieren, aber wenn Sie etwas wie: while (getline (inFile, word, '')) verwenden, wobei inFile ein ifstream-Objekt ist, das mehrere Zeilen enthält, erhalten Sie lustige Ergebnisse.
hackrock
Es ist schade, dass getline den Stream und nicht den String zurückgibt, was ihn in Initialisierungslisten ohne temporären Speicher unbrauchbar macht
fuzzyTew
1
Cool! Kein Boost und C ++ 11, eine gute Lösung für die alten Projekte da draußen!
Deqing
1
DAS ist die Antwort, der Name der Funktion ist nur ein bisschen umständlich.
Nils
82

Sie können Streams, Iteratoren und den Kopieralgorithmus verwenden, um dies ziemlich direkt zu tun.

#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);
}
KeithB
quelle
17
Ich finde diese std :: irritierend zu lesen .. warum nicht "using" verwenden?
user35978
80
@Vadi: weil das Bearbeiten des Posts eines anderen ziemlich aufdringlich ist. @pheze: Ich ziehe es vor, auf stddiese Weise zu wissen, woher mein Objekt kommt, das ist nur eine Frage des Stils.
Matthieu M.
7
Ich verstehe Ihren Grund und ich denke, es ist tatsächlich eine gute Wahl, wenn es für Sie funktioniert, aber aus pädagogischer Sicht stimme ich Pheze tatsächlich zu. Es ist einfacher, ein völlig fremdes Beispiel wie dieses mit einem "using namespace std" oben zu lesen und zu verstehen, da die Interpretation der folgenden Zeilen weniger Aufwand erfordert ... insbesondere in diesem Fall, weil alles aus der Standardbibliothek stammt. Sie können das Lesen und Erkennen der Herkunft der Objekte durch eine Reihe von "using std :: string;" etc. Zumal die Funktion so kurz ist.
Cheshirekow
61
Obwohl die Präfixe "std ::" irritierend oder hässlich sind, ist es am besten, sie in Beispielcode aufzunehmen, damit völlig klar ist, woher diese Funktionen kommen. Wenn sie Sie stören, ist es trivial, sie durch ein "using" zu ersetzen, nachdem Sie das Beispiel gestohlen und als Ihr eigenes beansprucht haben.
Dlchambers
20
ja! was er sagte! Best Practices ist die Verwendung des Standardpräfixes. Jede große Codebasis wird zweifellos über eigene Bibliotheken und Namespaces verfügen. Die Verwendung von "using namespace std" bereitet Ihnen Kopfschmerzen, wenn Sie anfangen, Namespace-Konflikte zu verursachen.
Miek
48

Keine Beleidigung, aber für ein so einfaches Problem machen Sie die Dinge viel zu kompliziert. Es gibt viele Gründe, Boost zu verwenden . Aber für etwas so Einfaches ist es, als würde man eine Fliege mit einem 20 # Schlitten schlagen.

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

Zum Beispiel (für Dougs Fall),

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

Und ja, wir hätten split () einen neuen Vektor zurückgeben können, anstatt einen zu übergeben. Es ist trivial, zu wickeln und zu überladen. Aber je nachdem, was ich mache, finde ich es oft besser, bereits vorhandene Objekte wiederzuverwenden, als immer neue zu erstellen. (Nur solange ich nicht vergesse, den Vektor dazwischen zu leeren!)

Referenz: http://www.cplusplus.com/reference/string/string/ .

(Ich habe ursprünglich eine Antwort auf Dougs Frage geschrieben: Ändern und Extrahieren von C ++ - Zeichenfolgen basierend auf Trennzeichen (geschlossen) . Aber da Martin York diese Frage hier mit einem Zeiger geschlossen hat ... werde ich nur meinen Code verallgemeinern.)

Mr.Ree
quelle
12
Warum ein Makro definieren, das Sie nur an einer Stelle verwenden? Und wie ist Ihr UASSERT besser als der Standard? Wenn Sie den Vergleich in 3 Token aufteilen, benötigen Sie lediglich mehr Kommas, als Sie sonst benötigen würden.
Crelbor
1
Vielleicht zeigt das UASSERT-Makro (in der Fehlermeldung) die tatsächliche Beziehung zwischen (und den Werten) der beiden verglichenen Werte? Das ist eigentlich eine ziemlich gute Idee, IMHO.
GhassanPL
10
Ugh, warum enthält die std::stringKlasse keine split () -Funktion?
Herr Shickadance
Ich denke, die letzte Zeile in der while-Schleife sollte sein start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());und die while-Schleife sollte sein while (start != string::npos). Außerdem überprüfe ich den Teilstring, um sicherzustellen, dass er nicht leer ist, bevor ich ihn in den Vektor einfüge.
John K
@JohnK Wenn die Eingabe zwei aufeinanderfolgende Trennzeichen hat, ist die Zeichenfolge zwischen ihnen eindeutig leer und sollte in den Vektor eingefügt werden. Wenn leere Werte für einen bestimmten Zweck nicht akzeptabel sind, ist dies eine andere Sache, aber meiner Meinung nach sollten solche Einschränkungen außerhalb dieser Art von sehr allgemeinen Funktionen durchgesetzt werden.
Lauri Nurmi
45

Eine Lösung mit 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;
    }
}
wb
quelle
5
Dies sollte die am besten bewertete Antwort sein. Dies ist der richtige Weg, um dies in C ++> = 11 zu tun.
Omnifarious
1
Ich bin froh, dass ich bis zu dieser Antwort gescrollt habe (derzeit nur 9 positive Stimmen). Genau so sollte ein C ++ 11-Code für diese Aufgabe aussehen!
YePhIcK
Ausgezeichnete Antwort, die nicht auf externen Bibliotheken beruht und bereits verfügbare Bibliotheken verwendet
Andrew
1
Tolle Antwort, die die größte Flexibilität bei Trennzeichen bietet. Einige Einschränkungen: Durch die Verwendung von \ s + Regex werden leere Token in der Mitte des Textes vermieden, es wird jedoch ein leeres erstes Token ausgegeben, wenn der Text mit Leerzeichen beginnt. Außerdem scheint Regex langsam zu sein: Auf meinem Laptop dauert es für 20 MB zufälligen Text 0,6 Sekunden, verglichen mit 0,014 Sekunden für strtok, strsep oder Parhams Antwort mit str.find_first_of oder 0,027 Sekunden für Perl oder 0,021 Sekunden für Python . Bei kurzen Texten spielt die Geschwindigkeit möglicherweise keine Rolle.
Mark Gates
2
Ok, vielleicht sieht es cool aus, aber dies ist eindeutig eine Überbeanspruchung regulärer Ausdrücke. Nur dann sinnvoll, wenn Sie sich nicht für die Leistung interessieren.
Marek R
35

Boost hat eine starke Split-Funktion: boost :: algorithm :: split .

Beispielprogramm:

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

Ausgabe:

"a"
"b"
" c "
""
"e"
"f"
""
Raz
quelle
26

Ich weiß, dass Sie nach einer C ++ - Lösung gefragt haben, aber Sie könnten dies als hilfreich erachten:

Qt

#include <QString>

...

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

Der Vorteil gegenüber Boost in diesem Beispiel besteht darin, dass es sich um eine direkte Eins-zu-Eins-Zuordnung zum Code Ihres Posts handelt.

Weitere Informationen finden Sie in der Qt-Dokumentation

Sivabudh
quelle
22

Hier ist eine Beispiel-Tokenizer-Klasse, die möglicherweise das tut, was Sie wollen

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

Beispiel:

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

Dies ist eine einfache STL-only - Lösung (~ 5 Zeilen!) Mit std::findund std::find_first_not_ofdass Griffe Wiederholungen des Trennzeichens (wie Leerzeichen oder Punkte zum Beispiel) sowie vorderen und hinteren Begrenzungszeichen:

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

Probieren Sie es live aus !

Parham
quelle
3
Dies ist gut, aber ich denke, Sie müssen find_first_of () anstelle von find () verwenden, damit dies ordnungsgemäß mit mehreren Trennzeichen funktioniert.
2
@ user755921 Mehrere Trennzeichen werden übersprungen, wenn die Startposition mit find_first_not_of gefunden wird.
Anfänger
16

pystring ist eine kleine Bibliothek, die eine Reihe von Python-Zeichenfolgenfunktionen implementiert, einschließlich der Split-Methode:

#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, "-");
dbr
quelle
3
Wow, du hast meine unmittelbare Frage und viele zukünftige Fragen beantwortet. Ich verstehe, dass C ++ mächtig ist. Wenn das Teilen eines Strings jedoch zu Quellcode wie den obigen Antworten führt, ist dies eindeutig entmutigend. Ich würde gerne andere Bibliotheken wie diese kennenlernen, die die Bequemlichkeiten höherer Sprachen nach unten ziehen.
Ross
wow, du hast meinen Tag ernsthaft gemacht !! wusste nichts über Pystring. Das spart mir viel Zeit!
Accraze
11

Ich habe diese Antwort für eine ähnliche Frage gepostet.
Das Rad nicht neu erfinden. Ich habe eine Reihe von Bibliotheken verwendet und die schnellste und flexibelste, die mir begegnet ist, ist: C ++ String Toolkit Library .

Hier ist ein Beispiel für die Verwendung, das ich an anderer Stelle im Stackoverflow gepostet habe.

#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;
}
DannyK
quelle
8

Überprüfen Sie dieses Beispiel. Es könnte Ihnen helfen ..

#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;
}
Sohesado
quelle
1
Ich würde tunwhile ( is >> tmps ) { std::cout << tmps << "\n"; }
Jordan
6

MFC / ATL hat einen sehr schönen Tokenizer. Von 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
Jim in Texas
quelle
1
Diese Tokenize () -Funktion überspringt leere Token. Wenn beispielsweise die Teilzeichenfolge "%%" in der Hauptzeichenfolge enthalten ist, wird kein leeres Token zurückgegeben. Es wird übersprungen.
Sheen
4

Wenn Sie bereit sind, C zu verwenden, können Sie die strtok- Funktion verwenden. Sie sollten bei der Verwendung auf Multithreading-Probleme achten.

Auf Freund
quelle
3
Beachten Sie, dass strtok die Zeichenfolge, die Sie überprüfen, ändert, sodass Sie sie nicht für const char * -Strings verwenden können, ohne eine Kopie zu erstellen.
Graeme Perrow
9
Das Multithreading-Problem besteht darin, dass strtok eine globale Variable verwendet, um zu verfolgen, wo es sich befindet. Wenn Sie also zwei Threads haben, die jeweils strtok verwenden, erhalten Sie ein undefiniertes Verhalten.
JohnMcG
@JohnMcG Oder verwenden Sie einfach, strtok_swas im Grunde strtokmit expliziter Statusübergabe ist .
Matthias
4

Für einfache Sachen benutze ich einfach folgendes:

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

Feiger Haftungsausschluss: Ich schreibe eine Echtzeit-Datenverarbeitungssoftware, bei der die Daten über Binärdateien, Sockets oder einen API-Aufruf (E / A-Karten, Kameras) eingehen. Ich verwende diese Funktion niemals für etwas Komplizierteres oder Zeitkritischeres als das Lesen externer Konfigurationsdateien beim Start.

jilles de wit
quelle
4

Sie können einfach eine Bibliothek für reguläre Ausdrücke verwenden und diese mit regulären Ausdrücken lösen.

Verwenden Sie den Ausdruck (\ w +) und die Variable in \ 1 (oder $ 1, abhängig von der Bibliotheksimplementierung regulärer Ausdrücke).

Fawix
quelle
+1 für den Vorschlag von Regex. Wenn Sie keine Warp-Geschwindigkeit benötigen, ist dies die flexibelste Lösung, die noch nicht überall unterstützt wird. Mit der Zeit wird dies jedoch weniger wichtig.
Odinthenerd
+1 von mir, habe gerade <regex> in c ++ 11 ausprobiert. So einfach und elegant
StahlRat
4

Viele zu komplizierte Vorschläge hier. Probieren Sie diese einfache std :: string-Lösung aus:

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;
}
David919
quelle
4

Ich dachte, dafür war der >>Operator für String-Streams gedacht :

string word; sin >> word;
Daren Thomas
quelle
1
Mein Fehler, ein schlechtes (zu einfaches) Beispiel zu geben. Soweit ich weiß, funktioniert das nur, wenn Ihr Trennzeichen ein Leerzeichen ist.
Bill the Lizard
4

Die Antwort von Adam Pierce liefert einen handgesponnenen Tokenizer, der a aufnimmt const char*. Es ist etwas problematischer mit Iteratoren zu tun, da das Inkrementieren des Enditerators eines stringundefiniert ist . Das heißt, vorausgesetzt, string str{ "The quick brown fox" }wir können dies sicherlich erreichen:

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 Example


Wenn Sie die Komplexität mithilfe der Standardfunktionalität abstrahieren möchten, wie On Freund vorschlägt, strtok ist dies eine einfache Option:

vector<string> tokens;

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

Wenn Sie keinen Zugriff auf C ++ 17 haben, müssen Sie data(str)wie in diesem Beispiel ersetzen : http://ideone.com/8kAGoa

Obwohl im Beispiel nicht gezeigt, strtokmuss nicht für jedes Token das gleiche Trennzeichen verwendet werden. Zusammen mit diesem Vorteil gibt es jedoch mehrere Nachteile:

  1. strtokkann nicht für mehrere stringsgleichzeitig verwendet werden: Entweder nullptrmuss a übergeben werden, um die aktuelle Tokenisierung fortzusetzenstring oder es muss eine neuechar* übergeben werden (es gibt jedoch einige nicht standardmäßige Implementierungen, die dies unterstützen, wie z.strtok_s )
  2. Aus dem gleichen Grunde strtok kann nicht für mehrere Threads gleichzeitig verwendet werden (dies kann jedoch eine definierte Implementierung sein, zum Beispiel: Die Implementierung von Visual Studio ist threadsicher ).
  3. Das Aufrufen strtokändert die Funktion, mit der stringes ausgeführt wird, sodass es nicht für const strings-, const char*s- oder Literal-Zeichenfolgen verwendet werden kann, um eine dieser Zeichenfolgen mit einem Token zu versehen strtokoder zu bearbeiten string, dessen Inhalt beibehalten werden muss. Es strmüsste kopiert werden, dann könnte die Kopie kopiert werden operiert werden

bietet uns die Möglichkeit, split_viewZeichenfolgen zerstörungsfrei zu tokenisieren: https://topanswers.xyz/cplusplus?q=749#a874


Die vorherigen Methoden können kein direktes Token generieren vector, dh ohne sie in eine Hilfsfunktion zu abstrahieren, die sie nicht initialisieren können const vector<string> tokens. Diese Funktionalität und die Fähigkeit, jeden Leerraumbegrenzer zu akzeptieren , können mithilfe von a genutzt werden istream_iterator. Zum Beispiel gegeben: const string str{ "The quick \tbrown \nfox" }Wir können dies tun:

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

Live Example

Die erforderliche Konstruktion einer istringstreamfür diese Option hat weitaus höhere Kosten als die beiden vorherigen Optionen, diese Kosten sind jedoch in der Regel in den Kosten für die stringZuweisung verborgen .


Wenn keine der oben genannten Optionen für Ihre Tokenisierungsanforderungen flexibel genug ist, ist die flexibelste Option die Verwendung einer regex_token_iteratornatürlich mit dieser Flexibilität verbundenen höheren Kosten, aber auch dies ist wahrscheinlich in den stringZuweisungskosten verborgen . Nehmen wir zum Beispiel an, wir möchten ein Token basierend auf nicht maskierten Kommas erstellen und dabei auch Leerzeichen essen, wenn const string str{ "The ,qu\\,ick ,\tbrown, fox" }wir folgende Eingaben machen: Wir können dies tun:

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

Live Example

Jonathan Mee
quelle
strtok_sist übrigens C11-Standard. strtok_rist ein POSIX2001-Standard. Zwischen diesen beiden gibt es strtokfür die meisten Plattformen eine Standard-Wiedereintrittsversion von .
Andon M. Coleman
@ AndonM.Coleman Dies ist jedoch eine C ++ - Frage, die in C ++ #include <cstring>nur die c99- Version von enthält strtok. Ich gehe also davon aus, dass Sie diesen Kommentar nur als unterstützendes Material bereitstellen, um die implementierungsspezifische Verfügbarkeit von strtokErweiterungen zu demonstrieren .
Jonathan Mee
1
Nur, dass es nicht so ungewöhnlich ist, wie die Leute sonst glauben könnten. strtok_swird sowohl von C11 als auch als eigenständige Erweiterung in der C-Laufzeit von Microsoft bereitgestellt. Es gibt hier ein merkwürdiges Stück Geschichte, in dem die _sFunktionen von Microsoft zum C-Standard wurden.
Andon M. Coleman
@ AndonM.Coleman Richtig, ich bin bei dir. Wenn es sich um den C11-Standard handelt, gelten für die Schnittstelle und die Implementierung Einschränkungen, die unabhängig von der Plattform ein identisches Verhalten erfordern. Jetzt besteht das einzige Problem darin, sicherzustellen, dass die C11-Funktion plattformübergreifend für uns verfügbar ist. Hoffentlich wird der C11-Standard von C ++ 17 oder C ++ 20 ausgewählt.
Jonathan Mee
3

Ich weiß, dass diese Frage bereits beantwortet ist, aber ich möchte einen Beitrag leisten. Vielleicht ist meine Lösung ein bisschen einfach, aber das habe ich mir ausgedacht:

vector<string> get_words(string const& text, string const& separator)
{
    vector<string> result;
    string tmp = text;

    size_t first_pos = 0;
    size_t second_pos = tmp.find(separator);

    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 + separator.length());
        second_pos = tmp.find(separator);
    }

    result.push_back(tmp);

    return result;
}

Bitte kommentieren Sie, ob es einen besseren Ansatz für etwas in meinem Code gibt oder ob etwas nicht stimmt.

UPDATE: generisches Trennzeichen hinzugefügt

Nussknacker
quelle
Ich habe Ihre Lösung aus der Menge verwendet :) Kann ich Ihren Code ändern, um ein Trennzeichen hinzuzufügen?
Zac
1
@ Zac froh, dass es Ihnen gefallen hat und Sie können es ändern ... fügen Sie einfach einen fett gedruckten Update-Abschnitt zu meiner Antwort hinzu ...
NutCracker
2

Hier ist ein Ansatz, mit dem Sie steuern können, ob leere Token eingeschlossen (wie strsep) oder ausgeschlossen (wie strtok) sind.

#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;
}
Darren Smith
quelle
2

Mir kommt es seltsam vor, dass bei uns allen geschwindigkeitsbewussten Nerds hier auf SO niemand eine Version vorgestellt hat, die eine nach der Kompilierungszeit generierte Nachschlagetabelle für das Trennzeichen verwendet (Beispielimplementierung weiter unten). Die Verwendung einer Nachschlagetabelle und von Iteratoren sollte std :: regex in ihrer Effizienz übertreffen. Wenn Sie Regex nicht schlagen müssen, verwenden Sie es einfach, seinen Standard ab C ++ 11 und super flexibel.

Einige haben bereits Regex vorgeschlagen, aber für die Noobs ist hier ein Beispiel, das genau das tun sollte, was das OP erwartet:

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

Wenn wir schneller sein und die Einschränkung akzeptieren müssen, dass alle Zeichen 8 Bit groß sein müssen, können wir zur Kompilierungszeit mithilfe der Metaprogrammierung eine Nachschlagetabelle erstellen:

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

Damit getNextTokenist es einfach , eine Funktion zu erstellen:

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

Die Verwendung ist auch einfach:

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

Hier ist ein Live-Beispiel: http://ideone.com/GKtkLQ

odinthenerd
quelle
1
Ist es möglich, mit einem String-Trennzeichen zu token;
Galigator
Diese Version ist nur für Einzelzeichen-Trennzeichen optimiert. Die Verwendung einer Nachschlagetabelle ist nicht für Trennzeichen mit mehreren Zeichen (Zeichenfolgen) geeignet, sodass die Effizienz von Regex schwerer zu übertreffen ist.
Odinthenerd
1

Sie können boost :: make_find_iterator nutzen. Ähnliches:

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;
}
Arash
quelle
1

Hier ist mein Swiss® Army Knife von String-Tokenizern zum Aufteilen von Strings nach Leerzeichen, zum Berücksichtigen von Strings mit einfachen und doppelten Anführungszeichen sowie zum Entfernen dieser Zeichen aus den Ergebnissen. Ich habe RegexBuddy 4.x verwendet, um den größten Teil des Code-Snippets zu generieren , aber ich habe eine benutzerdefinierte Behandlung zum Entfernen von Anführungszeichen und ein paar anderen Dingen hinzugefügt.

#include <string>
#include <locale>
#include <regex>

std::vector<std::wstring> tokenize_string(std::wstring string_to_tokenize) {
    std::vector<std::wstring> tokens;

    std::wregex re(LR"(("[^"]*"|'[^']*'|[^"' ]+))", std::regex_constants::collate);

    std::wsregex_iterator next( string_to_tokenize.begin(),
                                string_to_tokenize.end(),
                                re,
                                std::regex_constants::match_not_null );

    std::wsregex_iterator end;
    const wchar_t single_quote = L'\'';
    const wchar_t double_quote = L'\"';
    while ( next != end ) {
        std::wsmatch match = *next;
        const std::wstring token = match.str( 0 );
        next++;

        if (token.length() > 2 && (token.front() == double_quote || token.front() == single_quote))
            tokens.emplace_back( std::wstring(token.begin()+1, token.begin()+token.length()-1) );
        else
            tokens.emplace_back(token);
    }
    return tokens;
}
kayleeFrye_onDeck
quelle
1
(Abwärts-) Abstimmungen können genauso konstruktiv sein wie Aufwärtsstimmen, aber nicht, wenn Sie keine Kommentare dazu hinterlassen, warum ...
kayleeFrye_onDeck
1
Ich habe Sie ausgeglichen, aber es könnte sein, dass der Code für den Programmierer, der googelt, wie man einen String teilt, ziemlich entmutigend aussieht, insbesondere ohne Dokumentation
mattshu
Danke @mattshu! Sind es die Regex-Segmente, die es entmutigend machen oder etwas anderes?
kayleeFrye_onDeck
0

Wenn die maximale Länge der zu tokenisierenden Eingabezeichenfolge bekannt ist, kann dies ausgenutzt und eine sehr schnelle Version implementiert werden. Ich skizziere die Grundidee unten, die sowohl von strtok () als auch von der Datenstruktur "Suffix Array" inspiriert wurde, die Jon Bentleys 2. Ausgabe von "Programming Perls", Kapitel 15, beschreibt. Die C ++ - Klasse bietet in diesem Fall nur eine gewisse Organisation und Bequemlichkeit von Nutzen. Die gezeigte Implementierung kann leicht erweitert werden, um führende und nachfolgende Leerzeichen in den Token zu entfernen.

Grundsätzlich kann man die Trennzeichen durch Zeichenfolgen-terminierende '\ 0'-Zeichen ersetzen und Zeiger auf die Token mit der geänderten Zeichenfolge setzen. Im Extremfall, wenn die Zeichenfolge nur aus Trennzeichen besteht, erhält man die Zeichenfolgenlänge plus 1 resultierende leere Token. Es ist praktisch, die zu ändernde Zeichenfolge zu duplizieren.

Header-Datei:

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

Implementierungsdatei:

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

Ein Nutzungsszenario wäre:

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

Ausgabe:

Item1

Item2
Item3
Engel Sinigersky
quelle
0

boost::tokenizerist Ihr Freund, aber erwägen Sie, Ihren Code in Bezug auf Internationalisierungsprobleme (i18n) portabel zu machen, indem Sie wstring/ wchar_tanstelle der Legacy string/ char-Typen verwenden.

#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;
}
jochenleidner
quelle
"Legacy" ist definitiv nicht korrekt und wchar_tein schrecklicher implementierungsabhängiger Typ, den niemand verwenden sollte, es sei denn, dies ist absolut notwendig.
Kaffee und Code
Die Verwendung von wchar_t löst keine i18n-Probleme automatisch. Sie verwenden Codierungen, um dieses Problem zu lösen. Wenn Sie eine Zeichenfolge durch ein Trennzeichen teilen, bedeutet dies, dass das Trennzeichen nicht mit dem codierten Inhalt eines Tokens in der Zeichenfolge kollidiert. Möglicherweise ist ein Entkommen erforderlich usw. wchar_t ist keine magische Lösung dafür.
Yonil
0

Einfacher C ++ - Code (Standard C ++ 98) akzeptiert mehrere Trennzeichen (angegeben in einem std :: string) und verwendet nur Vektoren, Strings und Iteratoren.

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