Wie iteriere ich über die Wörter einer Zeichenfolge?

2986

Ich versuche, über die Wörter einer Zeichenfolge zu iterieren.

Es kann angenommen werden, dass die Zeichenfolge aus durch Leerzeichen getrennten Wörtern besteht.

Beachten Sie, dass ich nicht an C-String-Funktionen oder dieser Art von Zeichenmanipulation / -zugriff interessiert bin. Bitte geben Sie in Ihrer Antwort auch der Eleganz Vorrang vor der Effizienz.

Die beste Lösung, die ich derzeit habe, ist:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

Gibt es eine elegantere Möglichkeit, dies zu tun?

Ashwin Nanjappa
quelle
617
Alter ... Eleganz ist nur eine ausgefallene Art, in meinem Buch "Effizienz, die hübsch aussieht" zu sagen.
14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
Pyon
21
@Eduardo: Das ist auch falsch ... Sie müssen iss zwischen dem Versuch, einen anderen Wert zu streamen, und der Verwendung dieses Wertes testen, dhstring sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy
9
Verschiedene Optionen in C ++, um dies standardmäßig zu tun: cplusplus.com/faq/sequences/strings/split
hB0
14
Eleganz ist mehr als nur Effizienz. Zu den eleganten Attributen gehören eine geringe Zeilenanzahl und eine hohe Lesbarkeit. IMHO Elegance ist kein Proxy für Effizienz, sondern für Wartbarkeit.
Matt

Antworten:

1369

Für das, was es wert ist, gibt es eine andere Möglichkeit, Token aus einer Eingabezeichenfolge zu extrahieren, wobei nur Standardbibliotheksfunktionen verwendet werden. Es ist ein Beispiel für die Kraft und Eleganz hinter dem Design der STL.

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

Anstatt die extrahierten Token in einen Ausgabestream zu kopieren, könnte man sie mit demselben generischen copyAlgorithmus in einen Container einfügen .

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

... oder erstellen Sie vectordirekt:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};
Zunino
quelle
164
Ist es möglich, hierfür ein Trennzeichen anzugeben? Wie zum Beispiel durch Kommas aufteilen?
l3dx
15
@ Jonathan: \ n ist in diesem Fall nicht das Trennzeichen, sondern das Trennzeichen für die Ausgabe an cout.
Huy
772
Dies ist eine schlechte Lösung, da kein anderes Trennzeichen erforderlich ist, daher nicht skalierbar und nicht wartbar.
HelloWorld
37
Tatsächlich kann dies mit anderen Trennzeichen gut funktionieren (obwohl einige etwas hässlich sind). Sie erstellen eine ctype-Facette, die die gewünschten Trennzeichen als Leerzeichen klassifiziert, erstellen ein Gebietsschema, das diese Facette enthält, und fügen dem Stringstream dieses Gebietsschema hinzu, bevor Sie Zeichenfolgen extrahieren.
Jerry Coffin
53
@Kinderchocolate "Es kann angenommen werden, dass die Zeichenfolge aus durch Leerzeichen getrennten Wörtern besteht" - Hmm, das klingt nicht nach einer schlechten Lösung für das Problem der Frage. "nicht skalierbar und nicht wartbar" - Hah, nett.
Christian Rau
2426

Ich benutze dies, um die Zeichenfolge durch ein Trennzeichen zu teilen. Der erste setzt die Ergebnisse in einen vorkonstruierten Vektor, der zweite gibt einen neuen Vektor zurück.

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Beachten Sie, dass bei dieser Lösung keine leeren Token übersprungen werden. Im Folgenden werden 4 Elemente gefunden, von denen eines leer ist:

std::vector<std::string> x = split("one:two::three", ':');
Evan Teran
quelle
86
Um zu vermeiden, dass leere Token übersprungen werden, empty()überprüfen Sie if (!item.empty()) elems.push_back(item)
Folgendes
11
Wie wäre es mit dem Delim, das zwei Zeichen enthält ->?
Heldhuyongtao
7
@herohuyongtao, diese Lösung funktioniert nur für einzelne Zeichenbegrenzer.
Evan Teran
4
@JeshwanthKumarNK, es ist nicht notwendig, aber Sie können damit beispielsweise das Ergebnis direkt an eine Funktion wie diese übergeben: f(split(s, d, v))und trotzdem den Vorteil einer vorab zugewiesenen Funktion haben, vectorwenn Sie möchten.
Evan Teran
8
Vorsichtsmaßnahme: split ("eins: zwei :: drei", ':') und split ("eins: zwei :: drei:", ':') geben denselben Wert zurück.
Dshin
834

Eine mögliche Lösung mit Boost könnte sein:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Dieser Ansatz ist möglicherweise sogar schneller als der stringstreamAnsatz. Und da dies eine generische Vorlagenfunktion ist, kann sie verwendet werden, um andere Arten von Zeichenfolgen (wchar usw. oder UTF-8) unter Verwendung aller Arten von Trennzeichen zu teilen.

Einzelheiten finden Sie in der Dokumentation .

ididak
quelle
35
Die Geschwindigkeit spielt hier keine Rolle, da beide Fälle viel langsamer sind als eine strtok-ähnliche Funktion.
Tom
45
Und für diejenigen, die noch keinen Boost haben ... bcp kopiert dafür über 1.000 Dateien :)
Roman Starkov
12
Warnung: Wenn eine leere Zeichenfolge ("") angegeben wird, gibt diese Methode einen Vektor zurück, der die Zeichenfolge "" enthält. Fügen Sie also vor dem Teilen ein "if (! String_to_split.empty ())" hinzu.
Offirmo
29
@ Ian Embedded-Entwickler verwenden nicht alle Boost.
ACK_stoverflow
31
als Nachtrag: Ich benutze Boost nur, wenn ich muss. Normalerweise ziehe ich es vor, meine eigene Codebibliothek zu erweitern, die eigenständig und portabel ist, damit ich einen kleinen präzisen spezifischen Code erzielen kann, der ein bestimmtes Ziel erreicht. Auf diese Weise ist der Code nicht öffentlich, performant, trivial und portabel. Boost hat seinen Platz, aber ich würde vorschlagen, dass es ein bisschen übertrieben ist, um Saiten zu kennzeichnen: Sie würden nicht Ihr ganzes Haus zu einem Ingenieurbüro transportieren lassen, um einen neuen Nagel in die Wand hämmern zu lassen, um ein Bild aufzuhängen ... sie könnten es tun Sehr gut, aber die Vorteile werden bei weitem durch die Nachteile aufgewogen.
GMasucci
362
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}
kev
quelle
12
Sie können auch auf andere Trennzeichen aufteilen, wenn Sie getlinein der whileBedingung verwenden, z. B. durch Kommas teilen, verwenden while(getline(ss, buff, ',')).
Ali
181

Für diejenigen, bei denen es nicht gut ist, die gesamte Effizienz für die Codegröße zu opfern und "effizient" als eine Art Eleganz zu betrachten, sollte Folgendes einen Sweet Spot treffen (und ich denke, die Template-Container-Klasse ist eine unglaublich elegante Ergänzung.):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Normalerweise verwende ich std::vector<std::string>Typen als zweiten Parameter ( ContainerT) ... aber es list<>ist viel schneller als vector<>wenn kein direkter Zugriff erforderlich ist. Sie können sogar Ihre eigene Zeichenfolgenklasse erstellen und so etwas wie " std::list<subString>wo subStringkeine Kopien für unglaubliche Geschwindigkeit erstellt" verwenden erhöht sich.

Es ist mehr als doppelt so schnell wie das schnellste Token auf dieser Seite und fast fünfmal schneller als einige andere. Mit den perfekten Parametertypen können Sie auch alle Zeichenfolgen- und Listenkopien entfernen, um die Geschwindigkeit zu erhöhen.

Darüber hinaus führt es nicht die (äußerst ineffiziente) Rückgabe des Ergebnisses durch, sondern übergibt die Token als Referenz, sodass Sie auf Wunsch auch Token mit mehreren Aufrufen erstellen können.

Zuletzt können Sie festlegen, ob leere Token über einen letzten optionalen Parameter aus den Ergebnissen entfernt werden sollen.

Alles was es braucht ist std::string... der Rest ist optional. Es verwendet keine Streams oder die Boost-Bibliothek, ist jedoch flexibel genug, um einige dieser fremden Typen auf natürliche Weise akzeptieren zu können.

Marius
quelle
5
Ich bin ein ziemlicher Fan davon, aber für g ++ (und wahrscheinlich eine gute Praxis) möchte jeder, der dies verwendet, typedefs und typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; Dann ersetzen Sie die value_type und size_types entsprechend.
aws
11
Für diejenigen von uns, für die das Vorlagenmaterial und der erste Kommentar völlig fremd sind, wäre ein Verwendungsbeispiel mit den erforderlichen Includes sehr schön.
Wes Miller
3
Ahh gut, ich habe es herausgefunden. Ich habe die C ++ - Zeilen aus dem Kommentar von aws in den Funktionskörper von tokenize () eingefügt und dann die Zeilen tokens.push_back () bearbeitet, um den ContainerT :: value_type in nur ValueType zu ändern, und (ContainerT :: value_type :: size_type) in ( SizeType). Behoben, dass die Bits, über die g ++ gejammert hatte, behoben waren. Rufen Sie es einfach als Tokenize auf (some_string, some_vector).
Wes Miller
2
Abgesehen von einigen Leistungstests für Beispieldaten habe ich diese in erster Linie auf so wenige Anweisungen wie möglich und auch auf so wenig Speicherkopien wie möglich reduziert, die durch die Verwendung einer Teilzeichenfolgenklasse ermöglicht werden, die nur auf Offsets / Längen in anderen Zeichenfolgen verweist. (Ich habe meine eigenen gerollt, aber es gibt einige andere Implementierungen). Leider kann man nicht zu viel anderes tun, um dies zu verbessern, aber schrittweise Erhöhungen waren möglich.
Marius
3
Das ist die richtige Ausgabe für wann trimEmpty = true. Beachten Sie, dass "abo"dies in dieser Antwort kein Trennzeichen ist, sondern die Liste der Trennzeichen. Es wäre einfach, es so zu ändern, dass es eine einzelne Zeichenfolge mit Trennzeichen enthält (ich denke, es str.find_first_ofsollte sich ändern str.find_first, aber ich könnte mich irren ... kann nicht testen)
Marius
158

Hier ist eine andere Lösung. Es ist kompakt und ziemlich effizient:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Es kann leicht als Vorlage für String-Trennzeichen, breite Strings usw. verwendet werden.

Beachten Sie, dass das Teilen ""zu einer einzelnen leeren Zeichenfolge führt und das Teilen ","(dh Sep.) zu zwei leeren Zeichenfolgen führt.

Es kann auch einfach erweitert werden, um leere Token zu überspringen:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Wenn das Teilen einer Zeichenfolge an mehreren Trennzeichen beim Überspringen leerer Token gewünscht wird, kann diese Version verwendet werden:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}
Alec Thomas
quelle
10
Die erste Version ist einfach und erledigt die Arbeit perfekt. Die einzige Änderung, die ich vornehmen würde, wäre, das Ergebnis direkt zurückzugeben, anstatt es als Parameter zu übergeben.
Gregschlom
2
Die Ausgabe wird als Parameter für die Effizienz übergeben. Wenn das Ergebnis zurückgegeben würde, wäre entweder eine Kopie des Vektors oder eine Heap-Zuordnung erforderlich, die dann freigegeben werden müsste.
Alec Thomas
2
Ein kleiner Nachtrag zu meinem obigen Kommentar: Diese Funktion könnte den Vektor ohne Strafe zurückgeben, wenn die C ++ 11-Verschiebungssemantik verwendet wird.
Alec Thomas
7
@AlecThomas: Würden die meisten Compiler die Rückgabekopie nicht schon vor C ++ 11 über NRVO optimieren? (+1 sowieso; sehr prägnant)
Marcelo Cantos
11
Von allen Antworten scheint dies eine der attraktivsten und flexibelsten zu sein. Zusammen mit der getline mit einem Trennzeichen, obwohl es eine weniger offensichtliche Lösung ist. Hat der c ++ 11 Standard nichts dafür? Unterstützt c ++ 11 heutzutage Lochkarten?
Spacen Jasset
123

Dies ist meine Lieblingsmethode, um eine Zeichenfolge zu durchlaufen. Sie können pro Wort tun, was Sie wollen.

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}
gnomed
quelle
Ist es möglich, wordals zu deklarieren char?
Abatishchev
Sorry abatishchev, C ++ ist nicht meine Stärke. Aber ich stelle mir vor, es wäre nicht schwierig, eine innere Schleife hinzuzufügen, um jedes Zeichen in jedem Wort zu durchlaufen. Aber im Moment glaube ich, dass die aktuelle Schleife von Leerzeichen für die Worttrennung abhängt. Es sei denn, Sie wissen, dass sich zwischen jedem Leerzeichen nur ein einziges Zeichen befindet. In diesem Fall können Sie einfach "Wort" in ein Zeichen umwandeln. Tut mir leid, ich kann Ihnen nicht weiterhelfen. Ich wollte mein C ++
gnomed
11
Wenn Sie ein Wort als Zeichen deklarieren, wird es über jedes Nicht-Leerzeichen durchlaufen. Es ist einfach genug zu versuchen:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
Wayne Werner
79

Dies ähnelt der Frage zum Stapelüberlauf. Wie kann ich eine Zeichenfolge in C ++ tokenisieren? .

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}
Ferruccio
quelle
Wird dadurch eine Kopie aller Token erstellt oder wird nur die Start- und Endposition des aktuellen Tokens beibehalten?
Einpoklum
66

Ich mag das Folgende, weil es die Ergebnisse in einen Vektor einfügt, eine Zeichenfolge als Trennzeichen unterstützt und die Kontrolle über das Beibehalten leerer Werte gibt. Aber dann sieht es nicht so gut aus.

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

Natürlich hat Boost eine split(), die teilweise so funktioniert. Und wenn mit "Leerraum" wirklich jede Art von Leerraum gemeint ist, is_any_of()funktioniert die Aufteilung von Boost mit funktioniert hervorragend.

Shadow2531
quelle
Endlich eine Lösung, die leere Token auf beiden Seiten des Strings korrekt behandelt
fmuecke
53

Die STL verfügt noch nicht über eine solche Methode.

Sie können jedoch entweder die C- strtok()Funktion mithilfe des std::string::c_str()Elements verwenden oder Ihre eigene schreiben. Hier ist ein Codebeispiel, das ich nach einer schnellen Google-Suche gefunden habe ( "STL String Split" ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Entnommen aus: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Wenn Sie Fragen zum Codebeispiel haben, hinterlassen Sie einen Kommentar und ich werde es erklären.

Und nur weil es keinen typedefaufgerufenen Iterator oder <<keine Überladung implementiert, bedeutet der Operator nicht, dass es sich um schlechten Code handelt. Ich benutze ziemlich häufig C-Funktionen. Zum Beispiel printfund scanfbeide sind schneller als std::cinund std::cout(signifikant), diefopen Syntax für Binärtypen viel benutzerfreundlicher und sie neigen auch dazu, kleinere EXEs zu erzeugen.

Lassen Sie sich von diesem "Elegance over Performance" -Deal nicht verkaufen .

Benutzer19302
quelle
Ich kenne die C-String-Funktionen und auch die Leistungsprobleme (beides habe ich in meiner Frage notiert). Für diese spezielle Frage suche ich jedoch nach einer eleganten C ++ - Lösung.
Ashwin Nanjappa
11
@ Nelson LaQuet: Lassen Sie mich raten: Weil strtok nicht wiedereintrittsfähig ist?
Paercebal
40
@ Nelson übergibt niemals string.c_str () an strtok! strtok verwirft die Eingabezeichenfolge (fügt '\ 0' Zeichen ein, um jedes foudn-Trennzeichen zu ersetzen) und c_str () gibt eine nicht modifizierbare Zeichenfolge zurück.
Evan Teran
3
@ Nelson: Dieses Array muss in Ihrem letzten Kommentar die Größe str.size () + 1 haben. Aber ich stimme Ihrer These zu, dass es dumm ist, C-Funktionen aus "ästhetischen" Gründen zu vermeiden.
j_random_hacker
2
@paulm: Nein, die Langsamkeit von C ++ - Streams wird durch Facetten verursacht. Sie sind immer noch langsamer als die Funktionen von stdio.h, selbst wenn die Synchronisierung deaktiviert ist (und bei Stringstreams, die nicht synchronisiert werden können).
Ben Voigt
42

Hier ist eine Split-Funktion, die:

  • ist generisch
  • verwendet Standard C ++ (kein Boost)
  • akzeptiert mehrere Trennzeichen
  • ignoriert leere Token (kann leicht geändert werden)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

Anwendungsbeispiel:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");
Marco M.
quelle
Sie haben vergessen, zur Verwendungsliste hinzuzufügen: "extrem ineffizient"
Xander Tulip
1
@ XanderTulip, kannst du konstruktiver sein und erklären, wie oder warum?
Marco M.
3
@ XanderTulip: Ich nehme an, Sie beziehen sich darauf und geben den Vektor nach Wert zurück. Die Return-Value-Optimierung (RVO, google it) sollte sich darum kümmern. Auch in C ++ 11 können Sie per Verschiebungsreferenz zurückkehren.
Joseph Garvin
3
Dies kann tatsächlich weiter optimiert werden: Anstelle von .push_back (str.substr (...)) kann .emplace_back (str, start, pos - start) verwendet werden. Auf diese Weise wird das String-Objekt im Container erstellt und wir vermeiden eine Verschiebungsoperation + andere Spielereien, die von der .substr-Funktion ausgeführt werden.
Mihai Bişog
@zoopp ja. Gute Idee. VS10 hatte keine emplace_back-Unterstützung, als ich das schrieb. Ich werde meine Antwort aktualisieren. Vielen Dank
Marco M.
36

Ich habe eine 2-Zeilen-Lösung für dieses Problem:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Anstatt zu drucken, können Sie es dann in einen Vektor einfügen.

Rhomu
quelle
35

Noch ein flexibler und schneller Weg

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

So verwenden Sie es mit einem Vektor von Zeichenfolgen (Bearbeiten: Da jemand darauf hingewiesen hat, STL-Klassen nicht zu erben ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

Das ist es! Und das ist nur eine Möglichkeit, den Tokenizer zu verwenden, beispielsweise wie man nur Wörter zählt:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Begrenzt durch Vorstellungskraft;)

Robert
quelle
Nett. In Bezug auf AppenderAnmerkung : „Warum sollen wir nicht eine Klasse von STL - Klassen erben?“
Andreas Spindler
32

Hier ist eine einfache Lösung, die nur die Standard-Regex-Bibliothek verwendet

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

Das Regex-Argument ermöglicht die Überprüfung auf mehrere Argumente (Leerzeichen, Kommas usw.).

Normalerweise überprüfe ich nur, ob Leerzeichen und Kommas geteilt werden sollen, daher habe ich auch diese Standardfunktion:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

Die "[\\s,]+"Prüfung auf Leerzeichen ( \\s) und Kommas (, ).

Beachten Sie, wenn Sie wstringstatt teilen möchten string,

  • ändere alles std::regexaufstd::wregex
  • ändere alles sregex_token_iteratoraufwsregex_token_iterator

Beachten Sie, dass Sie abhängig von Ihrem Compiler möglicherweise auch das Zeichenfolgenargument als Referenz verwenden möchten.

dk123
quelle
Dies wäre meine Lieblingsantwort gewesen, aber std :: regex ist in GCC 4.8 fehlerhaft. Sie sagten, dass sie es in GCC 4.9 korrekt implementiert haben. Ich gebe Ihnen immer noch meine +1
mchiasson
1
Dies ist mein Favorit mit geringfügigen Änderungen: Der Vektor wurde wie gesagt als Referenz zurückgegeben, und die Argumente "str" ​​und "regex" wurden ebenfalls von Referenzen übergeben. Danke.
QuantumKarl
1
Rohe Zeichenfolgen sind beim Umgang mit Regex-Mustern sehr nützlich. Auf diese Weise müssen Sie die Escape-Sequenzen nicht verwenden ... Sie können sie einfach verwenden R"([\s,]+)".
Sam
26

Die Verwendung std::stringstreamwie Sie funktioniert einwandfrei und macht genau das, was Sie wollten. Wenn Sie jedoch nur nach einer anderen Vorgehensweise suchen, können Sie std::find()/ std::find_first_of()und verwenden std::string::substr().

Hier ist ein Beispiel:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}
KTC
quelle
Dies funktioniert nur für Einzelzeichenbegrenzer. Eine einfache Änderung lässt es mit Multicharakter arbeiten:prev_pos = pos += delimiter.length();
David Doria
25

Wenn Sie Boost verwenden möchten, aber eine ganze Zeichenfolge als Trennzeichen verwenden möchten (anstelle einzelner Zeichen wie in den meisten zuvor vorgeschlagenen Lösungen), können Sie das verwenden boost_split_iterator.

Beispielcode mit praktischer Vorlage:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}
zerm
quelle
20

Hier ist eine Regex-Lösung, die nur die Standard-Regex-Bibliothek verwendet. (Ich bin ein wenig verrostet, daher kann es zu einigen Syntaxfehlern kommen, aber dies ist zumindest die allgemeine Idee.)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}
AJMansfield
quelle
Ähnliche Antworten mit vielleicht besserem Regex-Ansatz: hier und hier .
Nobar
20

Es gibt eine Funktion namens strtok.

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}
Pratik Deoghare
quelle
3
strtokstammt aus der C-Standardbibliothek, nicht aus C ++. Die Verwendung in Multithread-Programmen ist nicht sicher. Es ändert die Eingabezeichenfolge.
Kevin Panko
13
Da der Zeichenzeiger vom ersten Aufruf an in einer statischen Variablen gespeichert wird, merkt er sich bei den nachfolgenden Aufrufen, wenn NULL übergeben wird, welcher Zeiger verwendet werden soll. Wenn ein zweiter Thread aufruft, strtokwährend ein anderer Thread noch verarbeitet wird, wird dieser Zeichenzeiger überschrieben, und beide Threads führen dann zu falschen Ergebnissen. mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko
1
Wie bereits erwähnt, ist strtok unsicher und sogar in C wird strtok_r zur Verwendung empfohlen
Systemfehler
4
strtok_r kann verwendet werden, wenn Sie sich in einem Codeabschnitt befinden, auf den zugegriffen werden kann. Dies ist die einzige Lösung von all dem, die kein "Leitungsrauschen" ist und ein Beweis dafür ist, was genau mit c ++ falsch ist
Erik Aronesty
Aktualisiert, damit keine Einwände aus Gründen der Thread-Sicherheit von C ++ - Wonks erhoben werden können.
Erik Aronesty
17

Der Stringstream kann praktisch sein, wenn Sie den String nach Nicht-Leerzeichen analysieren müssen:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')
lukmac
quelle
14

Bisher habe ich die in Boost verwendet , aber ich brauchte etwas, das nicht davon abhängt, also bin ich dazu gekommen:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Ein guter Punkt ist, dass separatorsSie mehr als ein Zeichen übergeben können.

Goran
quelle
13

Ich habe meine eigene mit strtok gewürfelt und Boost verwendet, um eine Saite zu teilen. Die beste Methode, die ich gefunden habe, ist die C ++ String Toolkit Library . Es ist unglaublich flexibel und schnell.

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

Das Toolkit ist viel flexibler als dieses einfache Beispiel zeigt, aber seine Nützlichkeit beim Parsen einer Zeichenfolge in nützliche Elemente ist unglaublich.

DannyK
quelle
13

Kurz und elegant

#include <vector>
#include <string>
using namespace std;

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

kann eine beliebige Zeichenfolge als Trennzeichen verwenden, kann auch mit Binärdaten verwendet werden (std :: string unterstützt Binärdaten, einschließlich Nullen)

mit:

auto a = split("this!!is!!!example!string", "!!");

Ausgabe:

this
is
!example!string
1438233
quelle
1
Ich mag diese Lösung, weil das Trennzeichen eine Zeichenfolge und kein Zeichen sein kann. Es ändert jedoch die Zeichenfolge an Ort und Stelle, sodass die Erstellung einer Kopie der ursprünglichen Zeichenfolge erzwungen wird.
Alessandro Teruzzi
11

Ich habe das gemacht, weil ich einen einfachen Weg brauchte, um Strings und C-basierte Strings zu teilen ... Hoffentlich kann es auch jemand anderes nützlich finden. Außerdem sind keine Token erforderlich, und Sie können Felder als Trennzeichen verwenden. Dies ist ein weiterer Schlüssel, den ich benötigte.

Ich bin sicher, es gibt Verbesserungen, die vorgenommen werden können, um die Eleganz noch weiter zu verbessern, und bitte auf jeden Fall

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

Beispiele:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

Wird ausgegeben:

Dies
ist
ein
Beispiel für eine
Zeichenfolge

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

So behalten Sie leere Einträge bei (standardmäßig werden Leergut ausgeschlossen):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

Das Ziel war es, es der Split () -Methode von C # ähnlich zu machen, bei der das Teilen eines Strings so einfach ist wie:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

Ich hoffe, jemand anderes kann dies genauso nützlich finden wie ich.

Steve Dell
quelle
10

Was ist damit:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}
Gibbz
quelle
Dies ist hier die beste Antwort, wenn Sie nur ein einzelnes Trennzeichen aufteilen möchten. Die ursprüngliche Frage wollte jedoch auf Leerzeichen aufgeteilt werden, dh eine beliebige Kombination aus einem oder mehreren aufeinanderfolgenden Leerzeichen oder Tabulatoren. Sie haben tatsächlich stackoverflow.com/questions/53849
Oktalist
10

Diese Antwort nimmt die Zeichenfolge und fügt sie in einen Vektor von Zeichenfolgen ein. Es verwendet die Boost-Bibliothek.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));
NL628
quelle
9

Hier ist eine andere Möglichkeit, es zu tun.

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}
user246110
quelle
9

Ich verwende für diese Aufgabe gerne die Boost / Regex-Methoden, da sie maximale Flexibilität für die Angabe der Aufteilungskriterien bieten.

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}
Marty B.
quelle
9

Vor kurzem musste ich ein Wort mit Kamelhülle in Unterwörter aufteilen. Es gibt keine Trennzeichen, nur obere Zeichen.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Dies teilt beispielsweise "AQueryTrades" in "A", "Query" und "Trades" auf. Die Funktion arbeitet mit schmalen und breiten Zeichenfolgen. Da es das aktuelle Gebietsschema respektiert, teilt es die "RaumfahrtÜberwachungsVerordnung" in "Raumfahrt", "Überwachung" und "Verordnung" auf.

Hinweis std::uppersollte wirklich als Funktionsvorlagenargument übergeben werden. Dann verallgemeinert die mehr von dieser Funktion aufspalten können Trennzeichen wie ",", ";"oder " "auch.

Andreas Spindler
quelle
2
Es gab 2 Umdrehungen. Das ist schön. Scheint, als hätte mein Englisch zu viel "Deutsch". Der Revisionist hat jedoch zwei kleinere Fehler nicht behoben, vielleicht weil sie sowieso offensichtlich waren: Sie std::isupperkonnten als Argument übergeben werden, nicht std::upper. Zweitens setzen Sie eine typenamevor die String::const_iterator.
Andreas Spindler
9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}
san45
quelle
9

Mit std::string_viewund Eric Nieblerrange-v3 Bibliothek:

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

Durch Verwendung einer Bereichsschleife foranstelle eines ranges::for_eachAlgorithmus:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}
Porsche9II
quelle
Yepp, die Reichweite für basiert sieht besser aus - ich stimme zu
Porsche9II