Java'nın uygun bir bölünme yöntemi vardır:
String str = "The quick brown fox";
String[] results = str.split(" ");
C ++ bunu yapmanın kolay bir yolu var mı?
Java'nın uygun bir bölünme yöntemi vardır:
String str = "The quick brown fox";
String[] results = str.split(" ");
C ++ bunu yapmanın kolay bir yolu var mı?
Yanıtlar:
C ++ standart kütüphane algoritmaları, evrensel olarak somut kaplar yerine yineleyiciler etrafında oluşturulmuştur. Ne yazık ki bu split
, C ++ standart kütüphanesinde Java benzeri bir işlev sağlamayı zorlaştırıyor , ancak kimse bunun uygun olacağını iddia etmese de. Ancak geri dönüş türü ne olurdu? std::vector<std::basic_string<…>>
? Belki, ama sonra (potansiyel olarak gereksiz ve maliyetli) tahsisler yapmaya mecburuz.
Bunun yerine, C ++, dizeleri keyfi olarak karmaşık sınırlayıcılara dayalı olarak bölmenin çok sayıda yolunu sunar, ancak bunların hiçbiri diğer dillerde olduğu gibi güzelce kapsüllenmez. Sayısız yol tüm blog yayınlarını doldurur .
En basit haliyle, siz vuruncaya std::string::find
kadar kullanarak yineleme yapabilir std::string::npos
ve içeriği kullanarak çıkarabilirsiniz std::string::substr
.
Boşlukta bölünmek için daha akıcı (ve deyimsel, ancak temel) bir sürüm aşağıdakileri kullanır std::istringstream
:
auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
Kullanılması std::istream_iterator
s , string akışının muhtevası da onun yineleyici aralığı yapıcısı kullanılarak bir vektör içine kopyalanan edilebilir.
Birden fazla kütüphane ( Boost.Tokenizer gibi ) özel belirteçler sunar.
Daha gelişmiş bölme için düzenli ifadeler gerekir. C ++ std::regex_token_iterator
bu amaçla özellikle şunları sağlar:
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{}
);
Boost tokenizer sınıfı oldukça basit bu tür bir şey yapabilir:
#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;
}
}
C ++ 11 için güncellendi:
#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;
}
}
char_separator
yapıcı üçüncü parametrelerine dikkat ( drop_empty_tokens
varsayılan, alternatif keep_empty_tokens
).
.h
C başlıkları gibi )
İşte gerçekten basit:
#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;
}
Strtok kullanın. Benim düşünceme göre, strtok size ihtiyacınız olanı sağlamadığı sürece tokenleştirmenin etrafında bir sınıf oluşturmaya gerek yoktur. Bu olmayabilir, ancak C ve C ++ 'da çeşitli ayrıştırma kodu yazmanın 15 yıldan fazla bir süresinde, her zaman strtok kullandım. İşte bir örnek
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Birkaç uyarı (ihtiyaçlarınızı karşılamayabilir). Dize süreçte "yok edilir", yani EOS karakterleri sınırlayıcı noktalara satır içine yerleştirilir. Doğru kullanım, dizenin sabit olmayan bir sürümünü oluşturmanızı gerektirebilir. Ayrıştırma öğelerinin orta ayrıştırma listesini de değiştirebilirsiniz.
Kendi düşünceme göre, yukarıdaki kod bunun için ayrı bir sınıf yazmaktan çok daha basit ve kullanımı daha kolaydır. Bana göre, bu dilin sağladığı işlevlerden biridir ve iyi ve temiz bir şekilde yapar. Bu sadece "C tabanlı" bir çözümdür. Bu uygun, kolay ve çok fazla kod yazmak zorunda değilsiniz :-)
Başka bir hızlı yol kullanmaktır getline
. Gibi bir şey:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
İsterseniz, split()
a'yı döndürmek için basit bir yöntem yapabilirsiniz vector<string>
, bu gerçekten yararlıdır.
Bunu doğrudan yapmak için akışları, yineleyicileri ve kopyalama algoritmasını kullanabilirsiniz.
#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);
}
std
nereden geldiğini bu şekilde tanımayı tercih ediyorum , bu sadece bir stil meselesi.
Darılma millet, ama böyle basit bir sorun için, olayları yapıyoruz yolu çok karmaşık. Boost'u kullanmanın birçok nedeni vardır . Ama bu kadar basit bir şey için, 20 # kızak ile bir sinek vurmak gibi.
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());
}
}
Örneğin (Doug'un davası için),
#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] );
}
Ve evet, split () yeni bir vektör döndürmek yerine yeni bir vektör döndürebiliriz. Sarmak ve aşırı yüklemek önemsizdir. Ancak yaptığım şeye bağlı olarak, önceden var olan nesneleri her zaman yenilerini oluşturmak yerine yeniden kullanmayı daha iyi buluyorum. (Vektörü arada boşaltmayı unutmadığım sürece!)
Referans: http://www.cplusplus.com/reference/string/string/ .
(Aslen Doug'un sorusuna bir cevap yazıyordum: C ++ Dizeleri Ayırıcılara Dayalı Değiştirme ve Çıkarma (kapalı) . Ancak Martin York bu soruyu burada bir işaretçi ile kapattığından ... Kodumu genelleştireceğim.)
std::string
sınıf neden bir split () işlevi içermiyor?
start = ((end > (theString.size() - theDelimiter.size())) ? string::npos : end + theDelimiter.size());
gerektiğini düşünüyorum while (start != string::npos)
. Ayrıca, alt dizeyi vektöre yerleştirmeden önce boş olmadığından emin olmak için kontrol ediyorum.
S kullanarak bir çözüm regex_token_iterator
:
#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;
}
}
Boost'un güçlü bir bölünme işlevi vardır: boost :: algorithm :: split .
Örnek 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;
}
Çıktı:
"a"
"b"
" c "
""
"e"
"f"
""
Bir C ++ çözümü istediğini biliyorum, ama bunu yararlı düşünebilirsin:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Bu örnekte Boost'a göre avantajı, postanızın koduyla doğrudan bire bir eşleşmesidir.
Qt dokümanlarında daha fazlasını görün
İşte istediğini yapabilecek bir örnek belirteç sınıfı
//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;
}
Misal:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Bu, basit bir STL tek çözeltisi (~ 5 satır!) Kullanıldığı std::find
ve std::find_first_not_of
ayrıca ön ve ayraçları arka, (boşluklar ya da örneğin dönemleri gibi) sınırlayıcı bu kolları tekrarı:
#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);
}
}
Canlı olarak deneyin !
pystring , split yöntemi de dahil olmak üzere bir grup Python'un dize işlevini uygulayan küçük bir kütüphanedir:
#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, "-");
Benzer bir soru için bu cevabı gönderdim.
Tekerleği yeniden icat etme. Bir dizi kütüphane kullandım ve karşılaştığım en hızlı ve en esnek: C ++ String Toolkit Library .
İşte stackoverflow üzerinde başka bir yerde yayınladığım nasıl kullanılacağı ile ilgili bir örnek.
#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;
}
Bu örneği kontrol edin. Size yardımcı olabilir ..
#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;
}
while ( is >> tmps ) { std::cout << tmps << "\n"; }
MFC / ATL'nin çok güzel bir tokenizörü var. MSDN'den:
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
C kullanmaya hazırsanız, strtok işlevini kullanabilirsiniz . Kullanırken çoklu iş parçacığı sorunlarına dikkat etmelisiniz.
Basit şeyler için sadece aşağıdakileri kullanıyorum:
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;
}
Korkakça feragatname: Verilerin ikili dosyalar, soketler veya bazı API çağrıları (G / Ç kartları, kameralar) aracılığıyla geldiği gerçek zamanlı veri işleme yazılımı yazıyorum. Bu işlevi hiçbir zaman başlangıçta harici yapılandırma dosyalarını okumaktan daha karmaşık veya zaman açısından kritik bir şey için kullanmam.
Sadece normal bir ifade kütüphanesi kullanabilirsiniz ve bunu normal ifadeler kullanarak çözebilirsiniz.
İfade (\ w +) ve değişkeni \ 1 (veya normal ifadelerin kitaplık uygulamasına bağlı olarak 1 $) kullanın.
Burada çok fazla karmaşık öneri var. Bu basit std :: string çözümünü deneyin:
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;
}
Ben >>
dize akışları üzerinde operatör ne olduğunu düşündüm :
string word; sin >> word;
Adam Pierce'ın cevabı , bir el bükülmüş tokenizatörünün a const char*
. Yineleyicilerle ilgili biraz daha sorunludur, çünkü bir string
son yineleyiciyi artırmak tanımsızdır . Bununla birlikte, string str{ "The quick brown fox" }
bunu kesinlikle başarabileceğimiz göz önüne alındığında :
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;
}
On Freund'un önerdiği gibi standart işlevselliği kullanarak soyut karmaşıklığı arıyorsanız, strtok
basit bir seçenektir:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
C ++ 17'ye erişiminiz yoksa data(str)
, bu örnekteki gibi değiştirmelisiniz : http://ideone.com/8kAGoa
Örnekte gösterilmemesine rağmen, strtok
her belirteç için aynı sınırlayıcıyı kullanmanız gerekmez. Bu avantajla birlikte, birkaç dezavantaj var:
strtok
çoklu ile kullanılamaz strings
, aynı zamanda: Ya bir nullptr
akım tokenizing devam etmek için geçmesi gereken string
ya da yeni bir char*
tokenize geçmiş olması gerekir (örneğin, ancak bu destek yapmak standart olmayan uygulama vardır: strtok_s
)strtok
birden çok iş parçacığında aynı anda kullanılamaz (bununla birlikte uygulama tanımlanabilir, örneğin: Visual Studio'nun uygulaması iş parçacığı için güvenlidir )strtok
değiştirir, string
bu nedenle const string
s, const char*
s veya değişmez dizgilerde kullanılamaz, bunlardan herhangi birini tokenize etmek strtok
veya string
içeriğinin korunması gereken, str
kopyalanması gerekecek bir kopya ile kopyalamak gerekir. ameliyat olmakc ++ 20split_view
dizeleri tahrip edici olmayan bir şekilde tokenize etmemizi sağlar : https://topanswers.xyz/cplusplus?q=749#a874
Önceki yöntemler bir tokenize vector
yerinde üretemezler , yani başlatamadıkları yardımcı bir işleve soyutlamadan const vector<string> tokens
. Bu işlevsellik ve herhangi bir beyaz boşluk sınırlayıcıyı kabul etme yeteneği bir istream_iterator
. Örneğin verilen: const string str{ "The quick \tbrown \nfox" }
bunu yapabiliriz:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
istringstream
Bu seçeneğin gerekli konstrüksiyonu önceki 2 seçenekten çok daha fazla maliyete sahiptir, ancak bu maliyet tipik olarak string
tahsis pahasına gizlenir .
Yukarıdaki seçeneklerden hiçbiri tokenizasyon ihtiyaçlarınız için yeterince esnek değilse, en esnek seçenek regex_token_iterator
elbette bu esneklikle kullanmaktır, daha büyük bir masraf gelir, ancak yine de bu muhtemelen string
tahsis maliyetinde gizlidir . Örneğin, aşağıdaki girdi göz önüne alındığında, kaçan virgüllere dayalı olarak da beyaz boşluk yiyerek belirtmek istiyoruz: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
bunu yapabiliriz:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
strtok_s
bu arada C11 standardıdır. strtok_r
bir POSIX2001 standardıdır. Her ikisi arasında, strtok
çoğu platform için standart bir yeniden giriş sürümü bulunmaktadır .
#include <cstring>
sadece c99 sürümünü içerir strtok
. Benim varsayımım, bu yorumu sadece destekleyici malzeme olarak sağladığınızı ve strtok
uzantıların uygulamaya özel kullanılabilirliğini gösterdiğinizi mi?
strtok_s
hem C11 tarafından hem de Microsoft'un C çalışma zamanında bağımsız bir uzantı olarak sağlanır. Burada Microsoft'un _s
işlevlerinin C standardı haline geldiği ilginç bir tarih var .
Bu sorunun zaten cevaplandığını biliyorum ama katkıda bulunmak istiyorum. Belki benim çözümüm biraz basit ama ben geldi budur:
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;
}
Kodumdaki bir şeye daha iyi bir yaklaşım varsa veya bir sorun varsa lütfen yorum yapın.
GÜNCELLEME: genel ayırıcı eklendi
Aşağıda, boş belirteçlerin (strsep gibi) dahil edilip edilmediğini (strtok gibi) kontrol etmenizi sağlayan bir yaklaşım var.
#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;
}
Burada hepimiz SO ile bilinçli nerds hız hiç kimse sınırlayıcı için derleme zamanı oluşturulan arama tablosu kullanan bir sürüm sunmamıştır garip görünüyor (örnek uygulama daha aşağı). Bir up tablosu kullanarak ve yineleyiciler, std :: regex'i verimlilikle geçmelidir, regex'i yenmeniz gerekmiyorsa, sadece kullanın, C ++ 11'den itibaren standardı ve süper esnek.
Bazıları zaten regex önerdi, ancak burada noobs için OP'nin tam olarak beklediği şeyi yapması gereken paketlenmiş bir örnek:
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;
}
Daha hızlı olmamız ve tüm karakterlerin 8 bit olması gerektiği kısıtlamasını kabul etmemiz gerekiyorsa, meta programlamayı kullanarak derleme zamanında bir tablo oluşturabiliriz:
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>;
Yerinde bir getNextToken
fonksiyon yapmak kolaydır:
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);
}
Bunu kullanmak da kolaydır:
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;
}
İşte canlı bir örnek: http://ideone.com/GKtkLQ
boost :: make_find_iterator avantajından yararlanabilirsiniz. Buna benzer bir şey:
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;
}
İşte, dizeleri boşlukla ayırmak, tek ve çift tırnaklı sarılmış dizeleri hesaba katmak ve bu karakterleri sonuçlardan çıkarmak için kullanılan Swiss® Army Bıçağı. Kod snippet'inin çoğunu oluşturmak için RegexBuddy 4.x'i kullandım , ancak tırnak ve diğer bazı şeyleri soymak için özel işlem ekledim.
#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;
}
Belirlenecek giriş dizesinin maksimum uzunluğu biliniyorsa, bundan faydalanılabilir ve çok hızlı bir sürüm uygulanabilir. Aşağıdaki temel fikri, hem strtok () hem de Jon Bentley'in "Programlama Perls" 2. baskısı, bölüm 15'te açıklanan "sonek dizisi" -data yapısından esinlenerek taslağı çiziyorum. Bu durumda C ++ sınıfı sadece bazı organizasyon ve kolaylık sağlar kullanım. Gösterilen uygulama, belirteçlerdeki önde gelen ve sondaki boşluk karakterlerini kaldırmak için kolayca genişletilebilir.
Temel olarak, ayırıcı karakterleri dize sonlandırıcı '\ 0' karakterleri ile değiştirebilir ve değiştirilmiş dize ile belirteçlere işaretçiler ayarlayabilirsiniz. Aşırı durumda dize yalnızca ayırıcılardan oluştuğunda, biri dize uzunluğu artı 1, boş belirteçlerle sonuçlanır. Değiştirilecek dizeyi çoğaltmak pratiktir.
Başlık dosyası:
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;
}
};
Uygulama dosyası:
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++ ] );
}
Bir kullanım senaryosu şöyle olacaktır:
// 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 ) );
}
çıktı:
Item1
Item2
Item3
boost::tokenizer
arkadaşınızdır, ancak eski / tür yerine wstring
/ kullanarak uluslararası kodlama (i18n) sorunlarıyla ilgili olarak kodunuzu taşınabilir hale getirmeyi düşünün .wchar_t
string
char
#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;
}
wchar_t
kesinlikle gerekli olmadıkça kimsenin kullanmaması gereken korkunç bir uygulamaya bağımlı türdür.
Basit C ++ kodu (standart C ++ 98), birden çok ayırıcıyı kabul eder (std :: string'de belirtilir), yalnızca vektörleri, dizeleri ve yineleyicileri kullanır.
#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;
}