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 ..
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:
autoconst str ="The quick brown fox"s;autoconst re = std::regex{R"(\s+)"};autoconst vec = std::vector<std::string>(
std::sregex_token_iterator{begin(str), end(str), re,-1},
std::sregex_token_iterator{});
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.
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>usingnamespace std;vector<string> split(constchar*str,char c =' '){vector<string> result;do{constchar*begin = str;while(*str != c &&*str)
str++;
result.push_back(string(begin, str));}while(0!=*str++);return result;}
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 :-)
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:
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);}
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) << "\"" << endlint
main(){vector<string> v;
split( v,"A:PEP:909:Inventory Item",":");for(unsignedint 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!)
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>usingnamespace 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;}}
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.
#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(constauto& field : fields)
std::cout <<"\""<< field <<"\"\n";return0;}
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);}}
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,"-");
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>constchar*whitespace =" \t\r\n\f";constchar*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;}}return0;}
#include<iostream>#include<sstream>usingnamespace std;int main (){
string tmps;
istringstream is ("the dellimiter is the space");while(is.good ()){
is >> tmps;
cout << tmps <<"\n";}return0;}
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.
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.
+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:
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:
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 )
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
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ć:
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ć:
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:
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 versionreturn 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(constauto&s:v){
std::cout << s << std::endl;}
std::cout <<"crazy version:"<< std::endl;
v = split(str, std::regex{"[^e]+"});//using e as delim shows flexibilityfor(constauto&s:v){
std::cout << s << std::endl;}return0;}
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...>structBoolSequence{};//just here to hold boolstemplate<char...>structCharSequence{};//just here to hold charstemplate<typename T,char C>structContains;//generictemplate<charFirst,char...Cs,charMatch>//not first specializationstructContains<CharSequence<First,Cs...>,Match>:Contains<CharSequence<Cs...>,Match>{};//strip first and increase indextemplate<charFirst,char...Cs>//is first specializationstructContains<CharSequence<First,Cs...>,First>: std::true_type {};template<charMatch>//not found specializationstructContains<CharSequence<>,Match>: std::false_type{};template<int I,typename T,typename U>structMakeSequence;//generictemplate<int I,bool...Bs,typename U>structMakeSequence<I,BoolSequence<Bs...>, U>://not lastMakeSequence<I-1,BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};template<bool...Bs,typename U>structMakeSequence<0,BoolSequence<Bs...>,U>{//last usingType=BoolSequence<Bs...>;};template<typename T>structBoolASCIITable;template<bool...Bs>structBoolASCIITable<BoolSequence<Bs...>>{/* could be made constexpr but not yet supported by MSVC */staticbool isDelim(constchar c){staticconstbool table[256]={Bs...};return table[static_cast<int>(c)];}};usingDelims=CharSequence<'.',',',' ',':','\n'>;//list your custom delimiters hereusingTable=BoolASCIITable<typenameMakeSequence<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 endauto second = std::find_if(begin,end,Table{});//find first delim or endreturn 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;}return0;}
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>inlinevector< basic_string<CH>> tokenize(const basic_string<CH>&Input,const basic_string<CH>&Delimiter,bool remove_empty_token
){typedeftypename basic_string<CH>::const_iteratorstring_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()));}elseResult.push_back(basic_string<CH>(it,i->begin()));
it = i->end();}if(it != it_end)Result.push_back(basic_string<CH>(it,it_end));returnResult;}
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.
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:
classTextLineSplitter{public:TextLineSplitter(constsize_t max_line_len );~TextLineSplitter();voidSplitLine(constchar*line,constchar sep_char =',',);inlinesize_tNumTokens(void)const{return mNumTokens;}constchar*GetToken(constsize_t token_idx )const{
assert( token_idx < mNumTokens );return mTokens[ token_idx ];}private:constsize_t mStorageSize;char*mBuff;char**mTokens;size_t mNumTokens;inlinevoidResetContent(void){
memset( mBuff,0, mStorageSize );// mark all items as empty:
memset( mTokens,0, mStorageSize *sizeof(char*));// reset counter for found items:
mNumTokens =0L;}};
// 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 ));}
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>usingnamespace std;usingnamespace 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 charactersTok 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";}return0;}
„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 tokenif(!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;}
Odpowiedzi:
Standardowe algorytmy biblioteki C ++ są dość powszechnie oparte na iteratorach, a nie na konkretnych kontenerach. Niestety utrudnia to udostępnienie
split
funkcji 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::find
aż do naciśnięciastd::string::npos
i 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
:Korzystanie z
std::istream_iterator
s 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_iterator
tym celu w szczególności:źródło
The Doładowania tokenizer klasa może zrobić tego rodzaju rzeczy dość prosty:
Zaktualizowano dla C ++ 11:
źródło
char_separator
konstruktora (drop_empty_tokens
domyślnie, alternatywą jestkeep_empty_tokens
)..h
dla nagłówków C)Oto naprawdę prosty:
źródło
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
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 :-)
źródło
Innym szybkim sposobem jest użycie
getline
. Coś jak:Jeśli chcesz, możesz zrobić prostą
split()
metodę zwracającą avector<string>
, co jest naprawdę przydatne.źródło
Możesz użyć strumieni, iteratorów i algorytmu kopiowania, aby zrobić to dość bezpośrednio.
źródło
std
ten sposób wiedziałem, skąd pochodzi mój przedmiot, to tylko kwestia stylu.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 #.
Na przykład (w przypadku Douga)
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.)
źródło
std::string
klasa nie zawiera funkcji split ()?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.Rozwiązanie wykorzystujące
regex_token_iterator
s:źródło
Zwiększenie ma silną funkcję podziału: boost :: algorytm :: split .
Przykładowy program:
Wynik:
źródło
Wiem, że poprosiłeś o rozwiązanie C ++, ale możesz uznać to za pomocne:
Qt
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
źródło
Oto przykładowa klasa tokenizera, która może robić, co chcesz
Przykład:
źródło
Jest to proste rozwiązanie tylko STL (~ 5 linii!) Używając
std::find
istd::find_first_not_of
który obsługuje powtórzenia ogranicznika (jak spacjami lub okresów, na przykład), a także natarcia i spływu ograniczników:Wypróbuj na żywo !
źródło
pystring to mała biblioteka, która implementuje kilka funkcji łańcuchowych Pythona, w tym metodę split:
źródło
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.
źródło
Sprawdź ten przykład. To może ci pomóc ...
źródło
while ( is >> tmps ) { std::cout << tmps << "\n"; }
MFC / ATL ma bardzo ładny tokenizer. Z MSDN:
źródło
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ą.
źródło
strtok_s
który jest zasadniczostrtok
z jawnym przekazywaniem stanu.Do prostych rzeczy używam tylko:
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.
źródło
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).
źródło
Wiele tutaj zbyt skomplikowanych sugestii. Wypróbuj to proste rozwiązanie std :: string:
źródło
Myślałem, że po to jest
>>
operator strumieni strumieniowych:źródło
Odpowiedź Adama Pierce'a zapewnia ręcznie obracany tokenizer przyjmujący
const char*
. Jest to nieco bardziej problematyczne w przypadku iteratorów, ponieważ zwiększeniestring
iteratora 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ąć:Live Example
Jeśli szukasz abstrakcyjnej złożoności za pomocą standardowej funkcjonalności, jak sugeruje On Freund,
strtok
jest to prosta opcja:Jeśli nie masz dostępu do C ++ 17, musisz go zastąpić,
data(str)
jak w tym przykładzie: http://ideone.com/8kAGoaChociaż nie pokazano tego w przykładzie,
strtok
nie trzeba używać tego samego separatora dla każdego tokena. Oprócz tej przewagi istnieje jednak kilka wad:strtok
nie można używać jednocześnie na wielustrings
: albonullptr
należy przekazać, aby kontynuować tokenizowanie bieżącej,string
albo nową,char*
aby tokenizować, należy przekazać (istnieją jednak niestandardowe implementacje, które to obsługują, takie jak:strtok_s
)strtok
nie 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 )strtok
modyfikujestring
to, na którym działa, więc nie można go używać na ciągachconst string
s,const char*
s lub dosłownych, aby tokenizować dowolne z nichstrtok
lub operować na tym,string
komu należy zachować treść,str
trzeba by było skopiować, a następnie kopia być obsługiwanymc ++ 20zapewnia nam
split_view
tokenizację ciągów w sposób nieniszczący: https://topanswers.xyz/cplusplus?q=749#a874Poprzednie metody nie mogą wygenerować tokenizowanego
vector
w 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ą komendyistream_iterator
. Na przykład:const string str{ "The quick \tbrown \nfox" }
możemy to zrobić:Live Example
Wymagana konstrukcja
istringstream
tej opcji ma znacznie większy koszt niż poprzednie 2 opcje, jednak koszt ten zwykle jest ukryty w kosztachstring
alokacji.Jeśli żadna z powyższych opcji nie jest wystarczająco elastyczna dla twoich potrzeb tokenizacji, najbardziej elastyczną opcją jest
regex_token_iterator
oczywiście użycie tej elastyczności wiąże się z większymi wydatkami, ale znowu jest to prawdopodobnie ukryte wstring
koszcie 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ć:Live Example
źródło
strtok_s
nawiasem mówiąc, jest standardem C11.strtok_r
jest standardem POSIX2001. Pomiędzy nimi znajduje się standardowa wersjastrtok
dla większości platform.#include <cstring>
zawiera tylko wersję c99strtok
. Więc zakładam, że przekazujesz ten komentarz jako materiał pomocniczy, demonstrując dostępnośćstrtok
rozszerzeń dla konkretnej implementacji ?strtok_s
jest dostarczany zarówno przez C11, jak i jako samodzielne rozszerzenie w środowisku wykonawczym C. Microsoft. Jest tu ciekawa historia, w której_s
funkcje Microsoftu stały się standardem C.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:
Proszę o komentarz, jeśli w moim kodzie jest lepsze podejście do czegoś lub coś jest nie tak.
AKTUALIZACJA: dodano ogólny separator
źródło
Oto podejście, które pozwala kontrolować, czy puste tokeny są uwzględniane (jak strsep), czy wykluczane (jak strtok).
źródło
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:
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:
Dzięki temu tworzenie
getNextToken
funkcji jest łatwe:Korzystanie z niego jest również łatwe:
Oto przykład na żywo: http://ideone.com/GKtkLQ
źródło
możesz skorzystać z boost :: make_find_iterator. Coś podobnego do tego:
źródło
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.
źródło
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:
Plik implementacji:
Scenariusz użytkowania wyglądałby następująco:
wynik:
źródło
boost::tokenizer
jest twoim przyjacielem, ale rozważ uczynienie swojego kodu przenośnym w odniesieniu do problemów związanych z internacjonalizacją (i18n) za pomocąwstring
/wchar_t
zamiast starszego typustring
/char
typów.źródło
wchar_t
jest okropnym typem zależnym od implementacji, którego nikt nie powinien używać, chyba że jest to absolutnie konieczne.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.
źródło