Jak tokenizować ciąg w C ++?

414

Java ma wygodną metodę podziału:

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

Czy istnieje prosty sposób na zrobienie tego w C ++?

Bill jaszczurka
źródło
172
Nie mogę uwierzyć, że to rutynowe zadanie powoduje taki ból głowy w c ++
wfbarksdale,
6
To nie jest ból głowy w c ++ - istnieją różne sposoby na osiągnięcie tego. programiści są mniej świadome niż C ++ C # - jej o marketing i inwestycje ... zobacz to na różne opcje C ++, aby osiągnąć ten sam: cplusplus.com/faq/sequences/strings/split
hB0
9
@ hB0 przechodzenie przez wiele pytań odpowiedzi i wciąż brak decyzji oznacza ból głowy. jedna potrzebuje tej biblioteki, druga jest tylko dla spacji, druga nie obsługuje spacji ..
Paschalis
1
Możliwy duplikat podziału łańcucha w C ++?
KOB
2
Dlaczego wszystko w C ++ musi być walką?
Wael Assaf,

Odpowiedzi:

145

Standardowe algorytmy biblioteki C ++ są dość powszechnie oparte na iteratorach, a nie na konkretnych kontenerach. Niestety utrudnia to udostępnienie splitfunkcji podobnej do języka Java w standardowej bibliotece C ++, nawet jeśli nikt nie twierdzi, że byłoby to wygodne. Ale jaki byłby typ zwrotu? std::vector<std::basic_string<…>>? Być może, ale wtedy jesteśmy zmuszeni wykonać (potencjalnie zbędne i kosztowne) alokacje.

Zamiast tego C ++ oferuje mnóstwo sposobów podziału ciągów w oparciu o dowolnie złożone ograniczniki, ale żaden z nich nie jest tak dobrze zamknięty jak w innych językach. Liczne sposoby wypełniania całych postów na blogu .

W najprostszym przypadku możesz iterować za pomocą std::string::findaż do naciśnięcia std::string::nposi wyodrębnić zawartość za pomocąstd::string::substr .

Bardziej płynna (i idiomatyczna, ale podstawowa) wersja do podziału na białe znaki użyłaby std::istringstream:

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

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

Korzystanie z std::istream_iterators zawartość strumienia łańcucha można również skopiować do wektora za pomocą konstruktora zakresu iteratora.

Wiele bibliotek (takich jak Boost.Tokenizer ) oferuje określone tokenyzery.

Bardziej zaawansowane dzielenie wymaga wyrażeń regularnych. C ++ zapewnia w std::regex_token_iteratortym celu w szczególności:

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
źródło
53
Niestety przyspieszenie nie zawsze jest dostępne dla wszystkich projektów. Będę musiał znaleźć odpowiedź bez wzmocnienia.
FuzzyBunnySlippers,
36
Nie każdy projekt jest otwarty na „open source”. Pracuję w ściśle regulowanych branżach. To naprawdę nie jest problem. To tylko fakt z życia. Zwiększenie nie jest dostępne wszędzie.
FuzzyBunnySlippers,
5
@NonlinearIdeas Drugie pytanie / odpowiedź wcale nie dotyczyło projektów Open Source. To samo dotyczy każdego projektu. To powiedziawszy, oczywiście rozumiem ograniczone standardy, takie jak MISRA C, ale zrozumiałem, że i tak budujesz wszystko od zera (chyba że znajdziesz zgodną bibliotekę - rzadkość). W każdym razie nie chodzi o to, że „Boost nie jest dostępny” - chodzi o to, że masz specjalne wymagania, dla których prawie żadna odpowiedź ogólnego przeznaczenia byłaby nieodpowiednia.
Konrad Rudolph,
1
@NonlinearIdeas W tym przypadku inne, nieobsługujące odpowiedzi odpowiedzi również nie są zgodne z MISRA.
Konrad Rudolph,
3
@Dmitry Co to jest „STL barf” ?! I cała społeczność opowiada się za zastąpieniem preprocesora C. W rzeczywistości istnieją propozycje, aby to zrobić. Ale twoja sugestia użycia PHP lub innego języka byłaby ogromnym krokiem wstecz.
Konrad Rudolph,
188

The Doładowania tokenizer klasa może zrobić tego rodzaju rzeczy dość prosty:

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

Zaktualizowano dla 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
źródło
1
Dobre rzeczy, ostatnio to wykorzystałem. Mój kompilator Visual Studio ma dziwny wątek, dopóki nie użyję spacji, aby oddzielić dwa znaki „>” przed bitem tokenów (tekst, sep): (błąd C2947: oczekiwanie, że>> ”zakończy szablon-lista argumentów, znaleziono„> > ')
AndyUK
@AndyUK tak, bez spacji kompilator analizuje go jako operator ekstrakcji zamiast dwóch zamykających szablonów.
EnabrenTane
Teoretycznie zostało to naprawione w C ++ 0x
David Souther
3
uważaj na trzecie parametry char_separatorkonstruktora ( drop_empty_tokensdomyślnie, alternatywą jest keep_empty_tokens).
Benoit,
5
@puk - Jest to powszechnie używany przyrostek w plikach nagłówkowych C ++. (jak .hdla nagłówków C)
Ferruccio,
167

Oto naprawdę prosty:

#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
źródło
czy muszę dodać prototyp dla tej metody w pliku .h?
Suhrob Samiev,
5
To nie jest dokładnie „najlepsza” odpowiedź, ponieważ nadal używa literału łańcuchowego, który jest zwykłą tablicą znaków stałej C. Uważam, że pytający pytał, czy mógłby tokenizować łańcuch C ++, który jest typu „ciąg” wprowadzony przez ten ostatni.
Vijay Kumar Kanta
To wymaga nowej odpowiedzi, ponieważ mocno podejrzewam, że włączenie wyrażeń regularnych do C ++ 11 zmieniło najlepszą odpowiedź.
Wszechobecny
113

Użyj strtok. Moim zdaniem nie ma potrzeby budowania klasy wokół tokenizacji, chyba że strtok nie zapewni ci tego, czego potrzebujesz. Może nie, ale przez ponad 15 lat pisania różnych parsów w C i C ++ zawsze używałem strtok. Oto przykład

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

Kilka ostrzeżeń (które mogą nie odpowiadać Twoim potrzebom). Ciąg jest „niszczony” w procesie, co oznacza, że ​​znaki EOS są umieszczane w liniach w miejscach separatora. Prawidłowe użycie może wymagać wykonania niestałej wersji ciągu. Możesz także zmienić listę ograniczników w trakcie analizy.

Moim zdaniem powyższy kod jest znacznie prostszy i łatwiejszy w użyciu niż pisanie dla niego osobnej klasy. Dla mnie jest to jedna z tych funkcji, które zapewnia język i robi to dobrze i czysto. To po prostu rozwiązanie oparte na „C”. Jest odpowiedni, łatwy i nie musisz pisać dużo dodatkowego kodu :-)

znak
źródło
42
Nie, że nie lubię C, jednak strtok nie jest bezpieczny dla wątków i musisz mieć pewność, że wysłany przez niego ciąg zawiera znak null, aby uniknąć możliwego przepełnienia bufora.
tloach
11
Jest strtok_r, ale to było pytanie C ++.
Umowa prof. Falkena została naruszona
3
@tloach: w kompilatorze MS C ++ strtok jest bezpieczny dla wątków, ponieważ wewnętrzna zmienna statyczna jest tworzona w TLS (lokalna pamięć wątków) (tak naprawdę jest zależna od kompilatora)
Ahmed Powiedział
3
@ahmed: wątek bezpieczny oznacza więcej niż możliwość dwukrotnego uruchomienia tej funkcji w różnych wątkach. W takim przypadku, jeśli wątek zostanie zmodyfikowany podczas działania strtok, możliwe jest, aby ciąg był poprawny podczas całego uruchomienia strtok, ale strtok nadal będzie się bałaganował, ponieważ łańcuch się zmienił, teraz już przekroczył znak zerowy i będzie kontynuuj czytanie pamięci, dopóki nie zostanie naruszone bezpieczeństwo lub nie znajdzie znaku o wartości zerowej. Jest to problem z oryginalnymi funkcjami łańcucha C, jeśli nie określisz długości w miejscu, w którym występują problemy.
tloach
4
strtok wymaga wskaźnika do nietrwałej tablicy znaków zakończonej znakiem null, co nie jest powszechnym stworzeniem do znalezienia w kodzie c ++ ... jaki jest twój ulubiony sposób konwersji na to ze std :: string?
fuzzyTew
105

Innym szybkim sposobem jest użycie getline. Coś jak:

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

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

Jeśli chcesz, możesz zrobić prostą split()metodę zwracającą a vector<string>, co jest naprawdę przydatne.

użytkownik35978
źródło
2
Miałem problemy z użyciem tej techniki ze znakami 0x0A w ciągu, który spowodował, że pętla while przedwcześnie zakończyła działanie. W przeciwnym razie jest to ładne proste i szybkie rozwiązanie.
Ryan H.
4
Jest to dobre, ale należy pamiętać, że w ten sposób domyślny separator „\ n” nie jest brany pod uwagę. Ten przykład będzie działał, ale jeśli używasz czegoś takiego: while (getline (inFile, słowo, '')), gdzie inFile jest obiektem ifstream zawierającym wiele linii, otrzymasz wyniki Funnny ..
hackrock
szkoda, że ​​getline zwraca strumień, a nie ciąg znaków, co czyni go bezużytecznym na listach inicjujących bez tymczasowego przechowywania
fuzzyTew
1
Fajne! Bez ulepszeń i C ++ 11, dobre rozwiązanie dla tych starszych projektów tam!
Deqing
1
TO jest odpowiedź, nazwa funkcji jest nieco niezręczna.
Nils,
82

Możesz użyć strumieni, iteratorów i algorytmu kopiowania, aby zrobić to dość bezpośrednio.

#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
źródło
17
Uważam, że te std :: irytujące do przeczytania. Dlaczego nie użyć „używania”?
user35978,
80
@Vadi: ponieważ edytowanie czyichś postów jest dość nachalne. @pheze: Wolę, aby w stdten sposób wiedziałem, skąd pochodzi mój przedmiot, to tylko kwestia stylu.
Matthieu M.
7
Rozumiem twój powód i myślę, że to właściwie dobry wybór, jeśli to działa dla ciebie, ale z pedagogicznego punktu widzenia zgadzam się z pheze. Łatwiej jest odczytać i zrozumieć zupełnie obcy przykład, taki jak ten z „używaniem przestrzeni nazw std” u góry, ponieważ wymaga mniej wysiłku, aby zinterpretować następujące wiersze ... szczególnie w tym przypadku, ponieważ wszystko pochodzi ze standardowej biblioteki. Możesz ułatwić czytanie i oczywiste, skąd pochodzą obiekty, za pomocą serii „using std :: string;” itp. Zwłaszcza, że ​​funkcja jest tak krótka.
cheshirekow
61
Pomimo, że przedrostki „std ::” są irytujące lub brzydkie, najlepiej umieścić je w przykładowym kodzie, aby było całkowicie jasne, skąd pochodzą te funkcje. Jeśli ci przeszkadzają, banalne jest zastąpienie ich „używaniem” po wykradzeniu przykładu i uznaniu go za swój własny.
dlchambers 11.04. O
20
tak! co on powiedział! najlepszą praktyką jest używanie przedrostka std. Każda duża baza kodu bez wątpienia będzie mieć własne biblioteki i przestrzenie nazw, a użycie „using namespace std” spowoduje ból głowy, gdy zaczniesz powodować konflikty przestrzeni nazw.
Miek,
48

No ludzie urazy, ale dla takiego prostego problemu, robisz rzeczy, sposób zbyt skomplikowane. Istnieje wiele powodów, dla których warto korzystać ze wzmocnienia . Ale dla czegoś tak prostego jest to jak uderzenie w muchę saniami 20 #.

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

Na przykład (w przypadku Douga)

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

I tak, moglibyśmy mieć funkcję split () zwracającą nowy wektor, zamiast go przekazywać. Tajemnicze jest pakowanie i przeciążanie. Ale w zależności od tego, co robię, często lepiej jest używać wcześniej istniejących obiektów niż zawsze tworzyć nowe. (Tak długo, jak nie zapomnę opróżnić wektora pomiędzy!)

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

(Pierwotnie pisałem odpowiedź na pytanie Douga: ciągi C ++ modyfikujące i wyodrębniające na podstawie separatorów (zamknięte) . Ale ponieważ Martin York zamknął to pytanie wskaźnikiem tutaj ... Po prostu uogólnię mój kod.)

Mr.Ree
źródło
12
Po co definiować makro, którego używasz tylko w jednym miejscu. A w jaki sposób twój UASSERT jest lepszy od standardowego potwierdzenia. Podział porównania na 3 tokeny w ten sposób nie wymaga nic innego, jak więcej przecinków, niż byłoby to konieczne.
crelbor
1
Może makro UASSERT pokazuje (w komunikacie o błędzie) rzeczywisty związek między (i wartościami) dwóch porównywanych wartości? To naprawdę dobry pomysł, IMHO.
GhassanPL,
10
Ugh, dlaczego std::stringklasa nie zawiera funkcji split ()?
Pan Shickadance,
Myślę, że ostatnia linia w pętli while powinna być, start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());a pętla while powinna być while (start != string::npos). Sprawdzam również podciąg, aby upewnić się, że nie jest pusty przed wstawieniem go do wektora.
John K
@JohnK Jeśli dane wejściowe mają dwa kolejne separatory, to wyraźnie łańcuch między nimi jest pusty i powinien zostać wstawiony do wektora. Jeśli puste wartości nie są dopuszczalne do określonego celu, to inna sprawa, ale ograniczenia IMHO powinny być egzekwowane poza tego rodzaju funkcjami o bardzo ogólnym przeznaczeniu.
Lauri Nurmi
46

Rozwiązanie wykorzystujące 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
źródło
5
To powinna być najwyższa odpowiedź. Jest to właściwy sposób na zrobienie tego w C ++> = 11.
Omnifarious
1
Cieszę się, że przewinąłem całą drogę do tej odpowiedzi (obecnie miałem tylko 9 głosów pozytywnych). Właśnie tak powinien wyglądać kod C ++ 11 dla tego zadania!
YePhIcK,
Doskonała odpowiedź, która nie opiera się na bibliotekach zewnętrznych i wykorzystuje biblioteki już dostępne
Andrew
1
Świetna odpowiedź, dająca największą elastyczność w ogranicznikach. Kilka zastrzeżeń: użycie \ s + regex pozwala uniknąć pustych tokenów w środku tekstu, ale daje pusty pierwszy token, jeśli tekst zaczyna się od białych znaków. Również wyrażenie regularne wydaje się być wolne: na moim laptopie, dla 20 MB losowego tekstu, zajmuje to 0,6 sekundy, w porównaniu do 0,014 sekundy dla strtok, strsep lub odpowiedzi Parhama przy użyciu str.find_first_of lub 0,027 s dla Perla lub 0,021 s dla Pythona . W przypadku krótkiego tekstu szybkość może nie stanowić problemu.
Mark Gates,
2
Ok, może to wygląda fajnie, ale to wyraźnie nadużywanie wyrażeń regularnych. Rozsądne, tylko jeśli nie zależy Ci na wydajności.
Marek R
35

Zwiększenie ma silną funkcję podziału: boost :: algorytm :: split .

Przykładowy program:

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

Wynik:

"a"
"b"
" c "
""
"e"
"f"
""
Raz
źródło
26

Wiem, że poprosiłeś o rozwiązanie C ++, ale możesz uznać to za pomocne:

Qt

#include <QString>

...

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

Przewagą nad Boostem w tym przykładzie jest to, że jest to bezpośrednie mapowanie jeden do jednego na kod Twojego postu.

Zobacz więcej w dokumentacji Qt

sivabudh
źródło
22

Oto przykładowa klasa tokenizera, która może robić, co chcesz

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

Przykład:

std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
    v.push_back(s.GetToken());
}
vzczc
źródło
19

Jest to proste rozwiązanie tylko STL (~ 5 linii!) Używając std::findi std::find_first_not_ofktóry obsługuje powtórzenia ogranicznika (jak spacjami lub okresów, na przykład), a także natarcia i spływu ograniczników:

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

Wypróbuj na żywo !

Parham
źródło
3
To jest dobre, ale myślę, że musisz użyć find_first_of () zamiast find (), aby działało to poprawnie z wieloma ogranicznikami.
2
@ user755921 wiele ograniczników jest pomijanych podczas znajdowania pozycji początkowej przez find_first_not_of.
Początkujący
16

pystring to mała biblioteka, która implementuje kilka funkcji łańcuchowych Pythona, w tym metodę split:

#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
źródło
3
Wow, odpowiedziałeś na moje bezpośrednie pytanie i wiele przyszłych pytań. Rozumiem, że c ++ jest potężny. Ale gdy podział łańcucha powoduje kod źródłowy taki jak powyższe odpowiedzi, jest to po prostu zniechęcające. Chciałbym wiedzieć o innych tego typu bibliotekach, które obniżają wygodę langauges wyższego poziomu.
Ross
wow, naprawdę poważnie sprawiłeś, że mój dzień !! nie wiedziałem o pystringu. pozwoli mi to zaoszczędzić dużo czasu!
accraze
11

Odpowiedziałem na podobne pytanie.
Nie wymyślaj koła ponownie. Użyłem wielu bibliotek, a najszybszą i najbardziej elastyczną, z jaką się zetknąłem jest: C ++ String Toolkit Library .

Oto przykład tego, jak go użyć, który zamieściłem gdzie indziej w przepełnieniu stosu.

#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
źródło
8

Sprawdź ten przykład. To może ci pomóc ...

#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
źródło
1
Zrobiłbymwhile ( is >> tmps ) { std::cout << tmps << "\n"; }
Jordan
6

MFC / ATL ma bardzo ładny tokenizer. Z 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
źródło
1
Ta funkcja Tokenize () pomija puste tokeny, na przykład jeśli w głównym ciągu znajduje się podciąg „%%”, nie jest zwracany pusty token. Jest pomijany.
Sheen,
4

Jeśli chcesz użyć C, możesz użyć funkcji strtok . Podczas korzystania z niego należy zwrócić uwagę na problemy związane z wielowątkowością.

Na Freund
źródło
3
Zauważ, że strtok modyfikuje sprawdzany ciąg, więc nie możesz używać go na ciągach const char * bez robienia kopii.
Graeme Perrow,
9
Problemem wielowątkowości jest to, że strtok używa zmiennej globalnej do śledzenia jej położenia, więc jeśli masz dwa wątki, z których każdy używa strtok, otrzymasz niezdefiniowane zachowanie.
JohnMcG
@JohnMcG Lub po prostu użyj, strtok_sktóry jest zasadniczo strtokz jawnym przekazywaniem stanu.
Matthias
4

Do prostych rzeczy używam tylko:

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

Tchórzliwe zrzeczenie się odpowiedzialności: piszę oprogramowanie do przetwarzania danych w czasie rzeczywistym, w którym dane przychodzą przez pliki binarne, gniazda lub niektóre wywołania API (karty we / wy, kamery). Nigdy nie używam tej funkcji do czegoś bardziej skomplikowanego lub krytycznego czasowo niż czytanie zewnętrznych plików konfiguracyjnych podczas uruchamiania.

Jilles de Wit
źródło
4

Możesz po prostu użyć biblioteki wyrażeń regularnych i rozwiązać ją za pomocą wyrażeń regularnych.

Użyj wyrażenia (\ w +) i zmiennej w \ 1 (lub 1 $ w zależności od implementacji bibliotek wyrażeń regularnych).

Fawix
źródło
+1 za sugerowanie wyrażenia regularnego, jeśli nie potrzebujesz prędkości wypaczania, jest to najbardziej elastyczne rozwiązanie, nie wszędzie obsługiwane, ale z upływem czasu to będzie mniej ważne.
odinthenerd
+1 ode mnie, właśnie wypróbowałem <regex> w c ++ 11. Tak prosty i elegancki
StahlRat
4

Wiele tutaj zbyt skomplikowanych sugestii. Wypróbuj to proste rozwiązanie std :: string:

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
źródło
4

Myślałem, że po to jest >>operator strumieni strumieniowych:

string word; sin >> word;
Daren Thomas
źródło
1
Moja wina za podanie złego (zbyt prostego) przykładu. O ile mi wiadomo, działa to tylko wtedy, gdy separatorem jest spacja.
Bill the Lizard,
4

Odpowiedź Adama Pierce'a zapewnia ręcznie obracany tokenizer przyjmujący const char*. Jest to nieco bardziej problematyczne w przypadku iteratorów, ponieważ zwiększenie stringiteratora końcowego jest niezdefiniowane . To powiedziawszy, biorąc pod uwagę, string str{ "The quick brown fox" }że z pewnością możemy to osiągnąć:

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


Jeśli szukasz abstrakcyjnej złożoności za pomocą standardowej funkcjonalności, jak sugeruje On Freund, strtok jest to prosta opcja:

vector<string> tokens;

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

Jeśli nie masz dostępu do C ++ 17, musisz go zastąpić, data(str)jak w tym przykładzie: http://ideone.com/8kAGoa

Chociaż nie pokazano tego w przykładzie, strtoknie trzeba używać tego samego separatora dla każdego tokena. Oprócz tej przewagi istnieje jednak kilka wad:

  1. strtoknie można używać jednocześnie na wielu strings: albo nullptrnależy przekazać, aby kontynuować tokenizowanie bieżącej, stringalbo nową, char*aby tokenizować, należy przekazać (istnieją jednak niestandardowe implementacje, które to obsługują, takie jak:strtok_s )
  2. Z tego samego powodu strtoknie można używać jednocześnie w wielu wątkach (może to być jednak określona implementacja, na przykład: Implementacja programu Visual Studio jest bezpieczna dla wątków )
  3. Wywołanie strtokmodyfikuje stringto, na którym działa, więc nie można go używać na ciągach const strings, const char*s lub dosłownych, aby tokenizować dowolne z nich strtoklub operować na tym, stringkomu należy zachować treść, strtrzeba by było skopiować, a następnie kopia być obsługiwanym

zapewnia nam split_viewtokenizację ciągów w sposób nieniszczący: https://topanswers.xyz/cplusplus?q=749#a874


Poprzednie metody nie mogą wygenerować tokenizowanego vectorw miejscu, co oznacza, że ​​bez abstrakcyjnego przekształcenia ich w funkcję pomocnika, której nie mogą zainicjować const vector<string> tokens. Funkcjonalność i możliwość akceptowania dowolnego separatora białych znaków można wykorzystać za pomocą komendy istream_iterator. Na przykład: const string str{ "The quick \tbrown \nfox" }możemy to zrobić:

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

Live Example

Wymagana konstrukcja istringstreamtej opcji ma znacznie większy koszt niż poprzednie 2 opcje, jednak koszt ten zwykle jest ukryty w kosztach stringalokacji.


Jeśli żadna z powyższych opcji nie jest wystarczająco elastyczna dla twoich potrzeb tokenizacji, najbardziej elastyczną opcją jest regex_token_iteratoroczywiście użycie tej elastyczności wiąże się z większymi wydatkami, ale znowu jest to prawdopodobnie ukryte w stringkoszcie alokacji. Powiedzmy na przykład, że chcemy tokenizować na podstawie przecinków bez znaku ucieczki, również jedząc białe znaki, biorąc pod uwagę następujące dane wejściowe: const string str{ "The ,qu\\,ick ,\tbrown, fox" }możemy to zrobić:

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
źródło
strtok_snawiasem mówiąc, jest standardem C11. strtok_rjest standardem POSIX2001. Pomiędzy nimi znajduje się standardowa wersja strtokdla większości platform.
Andon M. Coleman,
@ AndonM.Coleman Ale to jest pytanie c ++ , aw C ++ #include <cstring>zawiera tylko wersję c99strtok . Więc zakładam, że przekazujesz ten komentarz jako materiał pomocniczy, demonstrując dostępność strtokrozszerzeń dla konkretnej implementacji ?
Jonathan Mee
1
Po prostu nie jest to tak niestandardowe, jak ludzie mogą w to uwierzyć. strtok_sjest dostarczany zarówno przez C11, jak i jako samodzielne rozszerzenie w środowisku wykonawczym C. Microsoft. Jest tu ciekawa historia, w której _sfunkcje Microsoftu stały się standardem C.
Andon M. Coleman,
@ AndonM.Coleman Racja, jestem z tobą. Oczywiście, jeśli jest to standard C11, interfejs i implementacja mają nałożone ograniczenia, które wymagają identycznego zachowania niezależnego od platformy. Teraz jedynym problemem jest zapewnienie, że funkcja C11 jest dla nas dostępna na różnych platformach. Mamy nadzieję, że standard C11 będzie czymś, co C ++ 17 lub C ++ 20 zdecyduje się na odbiór.
Jonathan Mee,
3

Wiem, że na to pytanie już udzielono odpowiedzi, ale chcę się przyłączyć. Być może moje rozwiązanie jest nieco proste, ale oto co wymyśliłem:

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

Proszę o komentarz, jeśli w moim kodzie jest lepsze podejście do czegoś lub coś jest nie tak.

AKTUALIZACJA: dodano ogólny separator

Orzechówka
źródło
Użyłem Twojego rozwiązania z tłumu :) Czy mogę zmodyfikować kod, aby dodać separator?
Zac
1
@Zac cieszę się, że ci się podobało i możesz go zmodyfikować ... po prostu dodaj pogrubioną sekcję aktualizacji do mojej odpowiedzi ...
NutCracker
2

Oto podejście, które pozwala kontrolować, czy puste tokeny są uwzględniane (jak strsep), czy wykluczane (jak strtok).

#include <string.h> // for strchr and strlen

/*
 * want_empty_tokens==true  : include empty tokens, like strsep()
 * want_empty_tokens==false : exclude empty tokens, like strtok()
 */
std::vector<std::string> tokenize(const char* src,
                                  char delim,
                                  bool want_empty_tokens)
{
  std::vector<std::string> tokens;

  if (src and *src != '\0') // defensive
    while( true )  {
      const char* d = strchr(src, delim);
      size_t len = (d)? d-src : strlen(src);

      if (len or want_empty_tokens)
        tokens.push_back( std::string(src, len) ); // capture token

      if (d) src += len+1; else break;
    }

  return tokens;
}
Darren Smith
źródło
2

Wydaje mi się dziwne, że przy wszystkich naszych nerdach świadomych prędkości tutaj, na SO, nikt nie przedstawił wersji, która korzysta z tabeli wyszukiwania wygenerowanej w czasie kompilacji dla ogranicznika (przykładowa implementacja poniżej). Używając tabeli przeglądowej i iteratorów powinno pokonać efektywność std :: regex, jeśli nie potrzebujesz pokonać wyrażenia regularnego, po prostu użyj go, jest to standard C ++ 11 i super elastyczny.

Niektórzy sugerowali już wyrażenia regularne, ale dla noobów tutaj jest spakowany przykład, który powinien zrobić dokładnie to, czego oczekuje OP:

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

Jeśli musimy być szybsi i zaakceptować ograniczenie, że wszystkie znaki muszą mieć 8 bitów, możemy utworzyć tabelę wyszukiwania w czasie kompilacji, używając metaprogramowania:

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

Dzięki temu tworzenie getNextTokenfunkcji jest łatwe:

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

Korzystanie z niego jest również łatwe:

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

Oto przykład na żywo: http://ideone.com/GKtkLQ

odinthenerd
źródło
1
Czy można tokennizować za pomocą ogranicznika ciągu?
Galigator,
ta wersja jest zoptymalizowana tylko dla ograniczników jednoznakowych, użycie tabeli przeglądowej nie jest odpowiednie dla ograniczników wieloznakowych (łańcuchowych), więc trudniej jest pokonać wydajność wyrażenia regularnego.
odinthenerd
1

możesz skorzystać z boost :: make_find_iterator. Coś podobnego do tego:

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;
}
Wysypka
źródło
1

Oto mój szwajcarski scyzoryk z tokenizerem ciągów do dzielenia ciągów znaków za pomocą białych znaków, rozliczania pojedynczych i podwójnie cytowanych ciągów znaków, a także usuwania tych znaków z wyników. Użyłem RegexBuddy 4.x do wygenerowania większości fragmentu kodu, ale dodałem niestandardową obsługę usuwania wycen i kilka innych rzeczy.

#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
źródło
1
Głosy (w dół) mogą być tak samo konstruktywne jak głosowanie w górę, ale nie wtedy, gdy nie zostawisz komentarzy na temat tego, dlaczego ...
kayleeFrye_onDeck
1
Wyrównałem cię, ale może to być spowodowane tym, że kod wygląda dość zniechęcająco dla programisty, który googluje „jak podzielić ciąg znaków”, zwłaszcza bez dokumentacji
mattshu,
Dzięki @mattshu! Czy to segmenty regularne sprawiają, że jest on zniechęcający czy coś innego?
kayleeFrye_onDeck
0

Jeśli znana jest maksymalna długość ciągu wejściowego, który ma być tokenizowany, można to wykorzystać i zaimplementować bardzo szybką wersję. Naszkicowałem poniżej podstawową ideę, która została zainspirowana zarówno przez strtok (), jak i strukturę „tablicy przyrostków” opisaną w drugim wydaniu Jona Bentleya „Programming Perls”, rozdział 15. Klasa C ++ w tym przypadku zapewnia jedynie pewną organizację i wygodę użytkowania. Przedstawioną implementację można łatwo rozszerzyć w celu usunięcia wiodących i końcowych białych znaków w tokenach.

Zasadniczo można zastąpić znaki separatora znakami kończącymi ciąg „\ 0” i ustawić zmienne na tokeny za pomocą zmodyfikowanego ciągu. W skrajnym przypadku, gdy łańcuch składa się tylko z separatorów, jeden otrzymuje długość łańcucha plus 1 wynikowy pusty token. Praktyczne jest zduplikowanie ciągu, który ma zostać zmodyfikowany.

Plik nagłówka:

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

Plik implementacji:

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

Scenariusz użytkowania wyglądałby następująco:

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

wynik:

Item1

Item2
Item3
Angel Sinigersky
źródło
0

boost::tokenizerjest twoim przyjacielem, ale rozważ uczynienie swojego kodu przenośnym w odniesieniu do problemów związanych z internacjonalizacją (i18n) za pomocą wstring/ wchar_tzamiast starszego typu string/ chartypów.

#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
źródło
„dziedzictwo” jest zdecydowanie niepoprawne i wchar_tjest okropnym typem zależnym od implementacji, którego nikt nie powinien używać, chyba że jest to absolutnie konieczne.
CoffeeandCode
Użycie wchar_t nie rozwiązuje automatycznie problemów z i18n. Do rozwiązania tego problemu używasz kodowania. Jeśli dzielisz ciąg znaków przez ogranicznik, sugeruje się, że ogranicznik nie koliduje z zakodowaną zawartością dowolnego tokena wewnątrz łańcucha. Ucieczka może być potrzebna itp. Wchar_t nie jest magicznym rozwiązaniem tego problemu.
yonil
0

Prosty kod C ++ (standard C ++ 98), akceptuje wiele separatorów (określonych w std :: string), używa tylko wektorów, ciągów i iteratorów.

#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
źródło