我如何在c++中标记一个字符串?

Java有一个方便的分割方法:

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

在c++中有简单的方法来做到这一点吗?

633679 次浏览

下面是一个示例标记器类,它可以实现您想要的功能

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

例子:

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

c++标准库算法普遍基于迭代器,而不是具体的容器。不幸的是,这使得在c++标准库中很难提供类似java的split函数,尽管没有人认为这会很方便。但是它的返回类型是什么呢?std::vector<std::basic_string<…>>吗?也许吧,但这样我们就被迫执行(可能是冗余的和昂贵的)分配。

相反,c++提供了大量基于任意复杂的分隔符分割字符串的方法,但它们都没有像其他语言中那样封装得很好。许多方法填满整个博客文章

最简单的方法是,你可以使用std::string::find进行迭代,直到你击中std::string::npos,并使用std::string::substr提取内容。

更流畅的(和惯用的,但基本的)对空格进行分割的版本将使用std::istringstream:

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


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

使用std::istream_iterators,字符串流的内容也可以使用其迭代器范围构造函数复制到vector中。

多个库(如提振。分词器)提供特定的标记器。

更高级的分裂需要正则表达式。c++特别为此目的提供了std::regex_token_iterator:

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

如果你愿意使用C语言,你可以使用strtok函数。在使用它时,您应该注意多线程问题。

这是一个非常简单的问题:

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

我认为这就是字符串流上的>>操作符的用途:

string word; sin >> word;

您可以使用流、迭代器和复制算法来相当直接地做到这一点。

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

使用strtok。在我看来,没有必要围绕标记化构建类,除非strtok不能提供您所需要的东西。可能不会,但在用C和c++编写各种解析代码的15年多时间里,我一直在使用strtok。这里有一个例子

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

一些注意事项(可能不适合您的需要)。该字符串在该过程中被“销毁”,这意味着EOS字符内联放置在分隔符点中。正确的用法可能需要创建字符串的非const版本。还可以在解析过程中更改分隔符列表。

在我看来,上面的代码比为它单独编写一个类要简单得多,也更容易使用。对我来说,这是语言提供的功能之一,而且它做得很好,很干净。这只是一个“基于C”的解决方案。它很合适,很简单,而且你不需要写很多额外的代码:-)

提高记号赋予器类可以使这类事情变得非常简单:

#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更新:

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

提高具有很强的分割函数:

示例程序:

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

输出:

"a"
"b"
" c "
""
"e"
"f"
""

对于简单的事情,我只使用以下方法:

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

懦弱的免责声明:我编写实时数据处理软件,其中数据通过二进制文件、套接字或一些API调用(I/O卡、摄像头)传入。除了在启动时读取外部配置文件以外,我从未将这个函数用于更复杂或时间要求更严格的事情。

无意冒犯,但对于这样一个简单的问题,你把事情< em > < / em >方式太复杂了。使用提高有很多原因。但对于这么简单的事情,就像用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());
}
}

例如(以Doug为例),

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

是的,我们可以split()返回一个新的向量,而不是传入一个。包装和重载是很简单的。但根据我所做的事情,我经常发现重用已有的对象比总是创建新对象更好。(只要我不忘记清空中间的向量!)

参考:http://www.cplusplus.com/reference/string/string/

(我最初是写一个回答道格的问题:基于分隔符的c++字符串修改和提取(关闭)。但由于马丁·约克用这里的指针结束了这个问题……我将泛化我的代码。)

另一种快速方法是使用getline。喜欢的东西:

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


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

如果你愿意,你可以创建一个简单的split()方法返回一个vector<string>,它是 真的有用。< / p >

MFC/ATL有一个非常好的标记器。从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

我知道你想要一个c++的解决方案,但你可能会认为这是有帮助的:

Qt

#include <QString>


...


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

在这个例子中,与Boost相比的优势在于,它直接一对一地映射到你的文章代码。

详见Qt文档

请看这个例子。它可能对你有帮助。

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

你可以简单地使用正则表达式库并使用正则表达式来解决它。

使用表达式(\w+)和\1中的变量(或$1,取决于正则表达式的库实现)。

如果已知要标记化的输入字符串的最大长度,就可以利用这一点并实现一个非常快的版本。下面是我的基本思想草图,它的灵感来自strtok()和Jon Bentley的“Programming Perls”第二版第15章中描述的“后缀数组”数据结构。在这种情况下,c++类只提供了一些组织和使用方便。可以轻松扩展所示的实现,以删除令牌中的前导和尾随空白字符。

基本上,可以将分隔符替换为以字符串结束的'\0'字符,并设置指向修改后字符串中的标记的指针。在极端情况下,当字符串仅由分隔符组成时,将得到字符串长度加1个空标记。复制要修改的字符串是可行的。

头文件:

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

Implementattion文件:

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

使用的场景是:

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

输出:

Item1


Item2
Item3

你可以利用boost::make_find_iterator。类似于这个:

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

pystring是一个小的库,它实现了一些Python的字符串函数,包括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, "-");

boost::tokenizer是你的朋友,但是考虑使用wstring/wchar_t而不是遗留的string/char类型,使你的代码在国际化(i18n)问题上可移植。

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

这里有许多过于复杂的建议。试试这个简单的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;
}

下面是一种方法,允许您控制是否包含空标记(如strsep)或排除空标记(如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;
}

这是一个简单的循环,只对标准库文件进行标记

#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <conio.h>
class word
{
public:
char w[20];
word()
{
for(int j=0;j<=20;j++)
{w[j]='\0';
}
}






};


void main()
{
int i=1,n=0,j=0,k=0,m=1;
char input[100];
word ww[100];
gets(input);


n=strlen(input);




for(i=0;i<=m;i++)
{
if(context[i]!=' ')
{
ww[k].w[j]=context[i];
j++;


}
else
{
k++;
j=0;
m++;
}


}
}

简单的c++代码(标准c++ 98),接受多个分隔符(在std::string中指定),只使用向量、字符串和迭代器。

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

我贴出了类似问题的答案 不要白费力气。我使用过许多库,我遇到的最快和最灵活的库是:c++字符串工具箱库。< / p >

这里有一个如何使用它的例子,我已经张贴在stackoverflow的其他地方。

#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;
}
/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
///
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end(), i;
iter sep_b = sep.begin(), sep_e = sep.end();


// search through s
while (b != e){
i = search(b, e, sep_b, sep_e);


// no more separator found
if (i == e){
// it's not an empty string
if (b != e)
v.push_back(string(b, e));
break;
}
else if (i == b){
// the separator is found and right at the beginning
// in this case, we need to move on and search for the
// next separator
b = i + sep.length();
}
else{
// found the separator
v.push_back(string(b, i));
b = i;
}
}
}

boost库很好,但并不总是可用的。手工做这些事情也是很好的脑力锻炼。这里我们只使用STL中的std::search()算法,参见上面的代码。

我一直在寻找一种用任意长度的分隔符分割字符串的方法,所以我从头开始编写它,因为现有的解决方案不适合我。

这是我的小算法,只使用STL:

//use like this
//std::vector<std::wstring> vec = Split<std::wstring> (L"Hello##world##!", L"##");


template <typename valueType>
static std::vector <valueType> Split (valueType text, const valueType& delimiter)
{
std::vector <valueType> tokens;
size_t pos = 0;
valueType token;


while ((pos = text.find(delimiter)) != valueType::npos)
{
token = text.substr(0, pos);
tokens.push_back (token);
text.erase(0, pos + delimiter.length());
}
tokens.push_back (text);


return tokens;
}

据我测试,它可以与任何长度和形式的分离器一起使用。用string或wstring类型实例化。

该算法所做的就是搜索分隔符,获取到分隔符的字符串部分,删除分隔符并再次搜索,直到再也找不到它为止。

希望能有所帮助。

在我看来很奇怪的是,SO网站上有这么多注重速度的书呆子,却没有人给出一个使用编译时生成的分隔符查找表的版本(下面是示例实现)。使用查找表和迭代器应该在效率上击败std::regex,如果你不需要击败regex,就使用它,它是c++ 11的标准,超级灵活。

有些人已经建议使用正则表达式,但对于新手来说,这里有一个打包的示例,应该完全符合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;
}

如果我们需要更快并接受所有字符必须为8位的约束,我们可以在编译时使用元编程创建一个查找表:

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

有了这些,创建getNextToken函数就很容易了:

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

使用它也很简单:

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

下面是一个生动的例子:http://ideone.com/GKtkLQ

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

我以前只用标准库做了一个lexer/tokenizer。代码如下:

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


using namespace std;


string seps(string& s) {
if (!s.size()) return "";
stringstream ss;
ss << s[0];
for (int i = 1; i < s.size(); i++) {
ss << '|' << s[i];
}
return ss.str();
}


void Tokenize(string& str, vector<string>& tokens, const string& delimiters = " ")
{
seps(str);


// Skip delimiters at beginning.
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first "non-delimiter".
string::size_type pos = str.find_first_of(delimiters, lastPos);


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


int main(int argc, char *argv[])
{
vector<string> t;
string s = "Tokens for everyone!";


Tokenize(s, t, "|");


for (auto c : t)
cout << c << endl;


system("pause");


return 0;
}

这是一个简单的stl解决方案(~5行!),使用std::findstd::find_first_not_of来处理重复的分隔符(例如空格或句号),以及开头和结尾的分隔符:

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

试试< em > < / em >生活!

亚当·皮尔斯的回答提供了一个接受const char*的手工编织标记器。使用迭代器会有一些问题,因为递增string的结束迭代器是未定义的。也就是说,给定string str{ "The quick brown fox" },我们当然可以做到这一点:

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

现场示例 . .


如果你想通过使用标准功能来抽象复杂性,如弗洛伊德认为 strtok是一个简单的选项:

vector<string> tokens;


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

如果你不能访问c++ 17,你需要替换data(str),就像这个例子:http://ideone.com/8kAGoa

虽然没有在示例中演示,但strtok不需要为每个令牌使用相同的分隔符。除了这个优势,还有几个缺点:

  1. strtok不能同时用于多个strings:要么必须传递一个nullptr来继续标记当前的string,要么必须传递一个新的char*来标记(然而,有一些非标准实现支持这一点,例如:strtok_s)
  2. 出于同样的原因,strtok不能同时在多个线程上使用(但这可能是由实现定义的,例如:Visual Studio的实现是线程安全的)
  3. 调用strtok会修改它所操作的string,因此它不能用于__abc2、__abc3或字面值字符串,不能使用strtok对其中任何一个进行标记化,也不能对内容需要保留的string进行操作,必须复制str,然后才能对该副本进行操作

为我们提供了split_view来标记字符串,以一种非破坏性的方式


前面的方法不能原地生成标记化的vector,这意味着如果不将它们抽象为帮助函数,它们就不能初始化const vector<string> tokens。该功能而且接受任何空白分隔符的能力可以使用istream_iterator。例如给定:const string str{ "The quick \tbrown \nfox" },我们可以这样做:

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

现场示例 . .

这个选项所需构造的istringstream的代价比前面两个选项要大得多,然而这个代价通常隐藏在string分配的代价中。


如果上面的选项都不够灵活,不能满足你的标记化需求,最灵活的选项是使用regex_token_iterator。当然,这种灵活性会带来更大的开销,但同样,这可能隐藏在string的分配成本中。例如,我们想要基于非转义的逗号进行标记化,同时也占用空白,给定以下输入:const string str{ "The ,qu\\,ick ,\tbrown, fox" }我们可以这样做:

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

现场示例 . .

我知道这个问题已经有了答案,但我想有所贡献。也许我的解决方案有点简单,但这就是我想到的:

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

如果在我的代码中有更好的方法,或者有什么错误,请评论。

更新:添加了通用分隔符

下面是我的Swiss®军刀字符串标记器,用于用空格分隔字符串,处理单引号和双引号包装的字符串,以及从结果中剥离这些字符。我使用RegexBuddy 4。x生成代码片段的大多数,但我添加了对剥离引号和其他一些东西的自定义处理。

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

如果你正在使用c++范围——完整的ranges-v3库,而不是c++ 20所接受的有限功能——你可以这样做:

auto results = str | ranges::views::tokenize(" ",1);

... 这是惰性求值。你也可以在这个范围内设置一个向量:

auto results = str | ranges::views::tokenize(" ",1) | ranges::to<std::vector>();

如果str有n个字符组成m个单词,这将占用O(m)个空间和O(n)个时间。

参见标准库自己的标记化示例在这里

我写了一个简化版本的https://stackoverflow.com/a/50247503/3976739供我自己使用(可能有点高效)。我希望这能有所帮助。

void StrTokenizer(string& source, const char* delimiter, vector<string>& Tokens)
{
size_t new_index = 0;
size_t old_index = 0;


while (new_index != std::string::npos)
{
new_index = source.find(delimiter, old_index);
Tokens.emplace_back(source.substr(old_index, new_index-old_index));


if (new_index != std::string::npos)
old_index = ++new_index;
}
}

我只是看了所有的答案,找不到下一个前提条件的解决方案:

  1. 没有动态内存分配
  2. 不使用boost
  3. 不使用正则表达式
  4. c++17标准

这就是我的解

#include <iomanip>
#include <iostream>
#include <iterator>
#include <string_view>
#include <utility>


struct split_by_spaces
{
std::string_view      text;
static constexpr char delim = ' ';


struct iterator
{
const std::string_view& text;
std::size_t             cur_pos;
std::size_t             end_pos;


std::string_view operator*() const
{
return { &text[cur_pos], end_pos - cur_pos };
}
bool operator==(const iterator& other) const
{
return cur_pos == other.cur_pos && end_pos == other.end_pos;
}
bool operator!=(const iterator& other) const
{
return !(*this == other);
}
iterator& operator++()
{
cur_pos = text.find_first_not_of(delim, end_pos);


if (cur_pos == std::string_view::npos)
{
cur_pos = text.size();
end_pos = cur_pos;
return *this;
}


end_pos = text.find(delim, cur_pos);


if (cur_pos == std::string_view::npos)
{
end_pos = text.size();
}


return *this;
}
};


[[nodiscard]] iterator begin() const
{
auto start = text.find_first_not_of(delim);
if (start == std::string_view::npos)
{
return iterator{ text, text.size(), text.size() };
}
auto end_word = text.find(delim, start);
if (end_word == std::string_view::npos)
{
end_word = text.size();
}
return iterator{ text, start, end_word };
}
[[nodiscard]] iterator end() const
{
return iterator{ text, text.size(), text.size() };
}
};


int main(int argc, char** argv)
{
using namespace std::literals;
auto str = " there should be no memory allocation during parsing"
"  into words this line and you   should'n create any"
"  contaner                  for intermediate words  "sv;


auto comma = "";
for (std::string_view word : split_by_spaces{ str })
{
std::cout << std::exchange(comma, ",") << std::quoted(word);
}


auto only_spaces = "                   "sv;
for (std::string_view word : split_by_spaces{ only_spaces })
{
std::cout << "you will not see this line in output" << std::endl;
}
}