Java有一个方便的拆分方法:
String str = "The quick brown fox";
String[] results = str.split(" ");
有没有一种简单的方法可以在 C++ 中做到这一点?
Boost tokenizer类可以使这种事情变得非常简单:
#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 <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;
}
C++ 标准库算法非常普遍地基于迭代器而不是具体容器。不幸的是,这使得在 C++ 标准库中提供类似 Java 的split
函数变得很困难,尽管没有人认为这会很方便。但它的返回类型是什么?std::vector<std::basic_string<…>>
? 也许吧,但随后我们被迫执行(可能是冗余且成本高昂的)分配。
相反,C++ 提供了多种基于任意复杂分隔符来拆分字符串的方法,但它们都没有像其他语言那样被封装得那么好。无数种方式填满了整个博客文章。
在最简单的情况下,您可以迭代 usingstd::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_iterator
s,字符串流的内容也可以使用其迭代器范围构造函数复制到向量中。
多个库(例如Boost.Tokenizer)提供特定的标记器。
更高级的拆分需要正则表达式。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{}
);
另一种快速的方法是使用getline
. 就像是:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
如果需要,您可以创建一个split()
返回 a 的简单方法vector<string>
,这非常有用。
使用 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 字符被内嵌在分隔符点中。正确使用可能需要您制作字符串的非常量版本。您还可以在解析中更改分隔符列表。
在我自己看来,上面的代码比为它编写一个单独的类要简单得多,也更容易使用。对我来说,这是该语言提供的功能之一,它做得很好而且干净。它只是一个“基于 C”的解决方案。很合适,很简单,而且你不用写很多额外的代码:-)
您可以使用流、迭代器和复制算法相当直接地执行此操作。
#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);
}
使用regex_token_iterator
s 的解决方案:
#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的原因有很多。但是对于这么简单的事情,就像用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/ 。
(我最初是在写对 Doug 的问题的回复:C++ Strings Modifying and Extracting based on Separators (closed)。但由于 Martin York 在这里用指针结束了这个问题......我将概括我的代码。)
Boost具有强大的拆分功能:boost::algorithm::split。
示例程序:
#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"
""
我知道您要求提供 C++ 解决方案,但您可能会认为这很有帮助:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
在此示例中,与 Boost 相比的优势在于它直接一对一映射到您的帖子代码。
在Qt 文档中查看更多信息
这是一个简单的仅限 STL 的解决方案(约 5 行!),它使用std::find
和std::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);
}
}
现场试用!
这是一个示例标记器类,它可能会执行您想要的操作
//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());
}
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, "-");
我为类似的问题发布了这个答案。
不要重新发明轮子。我使用了许多库,我遇到的最快和最灵活的是: C++ String Toolkit Library。
这是我在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;
}
Adam Pierce 的回答提供了一个手工旋转的分词器,采用const char*
. 使用迭代器会有更多问题,因为递增 astring
的结束迭代器是 undefined。也就是说,鉴于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;
}
如果您希望通过使用标准功能来抽象复杂性,正如On Freund 建议 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
不必为每个标记使用相同的分隔符。除了这个优点,还有几个缺点:
strtok
不能同时用于多个strings
:要么nullptr
必须传递 a 以继续标记当前,要么必须传递string
newchar*
来标记化(但是有一些非标准实现确实支持这一点,例如strtok_s
:)strtok
,不能同时在多个线程上使用(但这可能是实现定义的,例如:Visual Studio 的实现是线程安全的)strtok
会修改string
它正在操作的对象,因此它不能用于const string
s、const char*
s 或文字字符串,以对其中的任何一个进行标记strtok
或对string
需要保留的内容进行操作,str
必须复制,然后复制可以被操作c++20为我们提供了split_view
以非破坏性方式标记字符串:https ://topanswers.xyz/cplusplus?q=749#a874
以前的方法不能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
分配费用中。
如果上述选项都不能灵活地满足您的代币化需求,那么最灵活的选项是使用 aregex_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() };
检查这个例子。它可能会帮助你..
#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;
}
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,你可以使用strtok函数。使用时要注意多线程问题。
对于简单的东西,我只使用以下内容:
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 卡、相机)进入。我从不将此功能用于比在启动时读取外部配置文件更复杂或时间紧迫的事情。
您可以简单地使用正则表达式库并使用正则表达式解决该问题。
使用表达式 (\w+) 和 \1 中的变量(或 $1,具体取决于正则表达式的库实现)。
这里有许多过于复杂的建议。试试这个简单的 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;
}
如果您使用的是 C++ 范围 - 完整的范围-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) 时间。
另请参阅此处的库自己的标记化示例。
我认为这就是>>
字符串流上的运算符的用途:
string word; sin >> word;
这是一种允许您控制是否包含空标记(如 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;
}
对我来说似乎很奇怪,对于我们所有有速度意识的书呆子来说,没有人提出使用编译时生成的查找表作为分隔符的版本(示例实现进一步向下)。使用查找表和迭代器应该在效率上击败 std::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
我知道这个问题已经得到解答,但我想做出贡献。也许我的解决方案有点简单,但这就是我想出的:
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;
}
请评论我的代码中是否有更好的方法或有问题。
更新:添加了通用分隔符
您可以利用 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;
}
这是我的字符串标记器的 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;
}
如果要标记的输入字符串的最大长度已知,则可以利用这一点并实现一个非常快速的版本。我正在勾画下面的基本思想,它的灵感来自于 strtok() 和“后缀数组”——Jon Bentley 的“Programming Perls”第 2 版第 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;
}
};
实现文件:
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::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;
}
简单的 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;
}
/// 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 类型实例化。
该算法所做的只是搜索定界符,获取到定界符的字符串部分,删除定界符并再次搜索,直到找不到为止。
希望能帮助到你。
我之前只使用标准库制作了词法分析器/标记器。这是代码:
#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;
}
这是一个仅使用标准库文件进行标记的简单循环
#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++;
}
}
}