String str = "The quick brown fox";
String[] results = str.split(" ");
在C ++中有一种简便的方法吗?
#1 楼
C ++标准库算法非常普遍地基于迭代器而不是具体的容器。不幸的是,这使得很难在C ++标准库中提供类似于Java的split
函数,即使没有人认为这样做会很方便。但是它的返回类型是什么? std::vector<std::basic_string<…>>
?也许可以,但是后来我们被迫执行(可能是冗余的和昂贵的)分配。相反,C ++提供了多种基于任意复杂的分隔符来分割字符串的方法,但是它们都没有封装为和其他语言一样好。用多种方法填充整个博客帖子。惯用的但基本的)版本,用于在空白处进行拆分时,将使用
std::string::find
:它的迭代器范围构造函数。多个库(例如Boost.Tokenizer)提供特定的令牌生成器。 C ++为此特别提供了
std::string::npos
:auto iss = std::istringstream{"The quick brown fox"};
auto str = std::string{};
while (iss >> str) {
process(str);
}
评论
遗憾的是,boost并非总是适用于所有项目。我将不得不寻找一个非助推的答案。
–FuzzyBunny拖鞋
2013年12月20日21:00
并非每个项目都对“开源”开放。我在严格管制的行业中工作。真的,这不是问题。这只是生活中的事实。 Boost并非随处可用。
–FuzzyBunny拖鞋
2013年12月20日23:19
@NonlinearIdeas另一个问题/答案根本与开源项目无关。对于任何项目都是如此。就是说,我当然了解诸如MISRA C之类的受限制标准,但是后来了解到,无论如何,您都是从头开始构建所有东西的(除非您碰巧找到一个兼容的库-很少)。无论如何,要点几乎不是“无法获得升压”,而是您有特殊的要求,几乎所有通用答案都不适合。
–康拉德·鲁道夫(Konrad Rudolph)
2013年12月21日上午10:46
以@NonlinearIdeas为例,其他非Boost答案也不符合MISRA。
–康拉德·鲁道夫(Konrad Rudolph)
2013年12月21日上午10:47
@Dmitry什么是“ STL barf”?整个社区都非常赞成替换C预处理器-实际上,有建议这样做。但是您使用PHP或其他语言的建议反倒是一大步。
–康拉德·鲁道夫(Konrad Rudolph)
16年9月11日在17:43
#2 楼
Boost令牌生成器类可以使这种事情变得非常简单:#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;
}
}
评论
好东西,我最近利用了这个。我的Visual Studio编译器有点奇怪,直到我使用空格将标记(text,sep)之前的两个“>”字符分开:(错误C2947:期望'>'终止模板参数列表,找到'> >')
– AndyUK
10-10-1在15:57
@AndyUK是的,没有空格,编译器会将其解析为提取运算符,而不是两个封闭模板。
– EnabrenTane
2011年6月14日下午3:23
理论上,这已在C ++ 0x中修复
– David Souther
2011年9月1日下午2:09
当心char_separator构造函数的第三个参数(默认为drop_empty_tokens,替代为keep_empty_tokens)。
–天赋
2012-2-17在10:56
@puk-这是C ++头文件的常用后缀。 (如.h用于C标头)
–Ferruccio
2013年12月12日22:44
#3 楼
这是一个真正简单的示例:#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;
}
评论
我需要在.h文件中为此方法添加原型吗?
– Suhrob Samiev
2011-12-22 8:26
这并不是完全“最佳”的答案,因为它仍然使用字符串文字,即纯C常量字符数组。我相信发问者正在询问他是否可以标记由后者引入的“字符串”类型的C ++字符串。
– Vijay Kumar Kanta
17年4月19日在7:05
这需要一个新的答案,因为我强烈怀疑C ++ 11中包含正则表达式已经改变了最佳答案。
–万能的
17-10-25在15:45
对于这个答案,字符串的第一个/最后一个字符等于分隔符存在问题。例如字符串“ a”的结果是[“”,“ a”]。
– y30
11月13日11:26
#4 楼
使用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”的解决方案。这很合适,很容易,而且您不必编写很多额外的代码:-)
评论
并不是我不喜欢C,但是strtok并不是线程安全的,因此您需要确定发送给它的字符串包含空字符,以避免可能的缓冲区溢出。
–蟑螂
2010年5月10日13:18
有strtok_r,但这是一个C ++问题。
–法尔肯教授
2010-10-6 9:14
@tloach:在MS C ++编译器中,strtok是线程安全的,因为内部静态变量是在TLS(线程本地存储)上创建的(实际上取决于编译器)
–艾哈迈德·赛义德(Ahmed Said)
2010-11-28 15:03
@ahmed:线程安全不仅意味着能够在不同线程中两次运行该函数。在这种情况下,如果在strtok运行时修改了线程,则可以在整个strtok运行期间使字符串有效,但是strtok仍会混乱,因为字符串已更改,现在已经超过了null字符,并且它将继续读取内存,直到它遇到安全冲突或找到空字符为止。这是原始C字符串函数的问题,如果您未指定长度,则会遇到问题。
–蟑螂
2010-11-29 13:23
strtok需要一个指向非const以null终止的char数组的指针,这不是在c ++代码中找到的普通生物……从std :: string转换为此字符串的最喜欢的方式是什么?
–fuzzyTew
2013年8月3日14:43
#5 楼
另一种快速的方法是使用getline
。像这样的东西:stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
如果需要,您可以制作一个简单的
split()
方法,返回一个vector<string>
,这对真的很有用。
评论
我在使用0x0A字符的字符串中使用此技术时遇到问题,这使得while循环过早退出。否则,这是一个很好的简单快速的解决方案。
– Ryan H.
2011年1月24日23:00
这很好,但是只需记住,这样做不会考虑默认的定界符'\ n'。这个例子可以用,但是如果您使用类似:while(getline(inFile,word,''))之类的东西,其中inFile是包含多行的ifstream对象,您将得到有趣的结果。
– Hackrock
2012年6月19日在21:28
太糟糕了,getline返回流而不是字符串,从而使其无法在没有临时存储的初始化列表中使用
–fuzzyTew
13年8月3日在12:34
凉!没有boost和C ++ 11,这是对那些旧项目的良好解决方案!
–德清
2014年4月30日在7:09
这就是答案,该函数的名称有点尴尬。
–指甲
19年6月1日在21:31
#6 楼
您可以使用流,迭代器和复制算法来直接进行此操作。#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 ::令人讨厌阅读..为什么不使用“ using”?
–user35978
08年11月28日在4:19
@Vadi:因为编辑其他人的帖子是相当麻烦的。 @pheze:我更喜欢以这种方式让std知道我对象的来源,这只是样式问题。
– Matthieu M.
2010年4月2日在8:49
我了解您的原因,并且认为对您有用,这实际上是一个不错的选择,但从教学的角度来看,我实际上同意。更容易阅读和理解这样一个完全陌生的示例,它的顶部带有“ using namespace std”,因为它不需要花费很多精力来解释以下几行……尤其是在这种情况下,因为所有内容都来自标准库。您可以通过一系列“使用std :: string;”使它易于阅读并清楚地表明对象的来源。等等,特别是因为功能太短了。
–cheshirekow
10 Jul 16 '11:27
尽管前缀“ std ::”令人讨厌或难看,但最好将它们包括在示例代码中,以便完全清楚这些函数的来源。如果他们打扰了您,那么在您窃取该示例并将其声明为您自己的示例之后,将它们替换为“ using”是很简单的。
–dlchambers
2012年4月11日14:54
是的他说什么!最佳做法是使用std前缀。毫无疑问,任何大型代码库都将拥有自己的库和名称空间,当开始引起名称空间冲突时,使用“使用名称空间标准”将使您头疼。
– Miek
2012年7月18日在17:08
#7 楼
使用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;
}
}
评论
这应该是排名最高的答案。这是在C ++> = 11中执行此操作的正确方法。
–万能的
17-10-25在16:12
我很高兴我一直滚动到这个答案(目前只有9个投票)。这正是此任务的C ++ 11代码外观!
– YephICK
18年1月7日在17:17
出色的答案,它不依赖于外部库,而是使用已经可用的库
–安德鲁(Andrew)
18年2月6日在20:39
很好的答案,使定界符具有最大的灵活性。请注意以下几点:使用\ s + regex可以避免在文本中间出现空标记,但是如果文本以空格开头,则会给出一个空的第一个标记。同样,正则表达式似乎很慢:在我的笔记本电脑上,对于20 MB的随机文本,它需要0.6秒,而使用str.find_first_of的strtok,strsep或Parham的答案为0.014秒,对于Perl为0.027秒,对于Python为0.021秒。对于短文本,速度可能不是问题。
–马克·盖茨
18年4月21日在16:29
好的,也许看起来很酷,但这显然是对正则表达式的过度使用。仅当您不关心性能时才合理。
– Marek R
19年7月18日在12:06
#8 楼
没有冒犯性的人,但是对于这样一个简单的问题,您使事情变得太复杂了。有很多原因使用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());
}
}
例如(对于道格的情况),
#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 ++字符串修改和提取(关闭)。但是由于Martin York在这里用指针结束了这个问题,所以……我只归纳一下我的代码。)
评论
为什么要定义只在一个地方使用的宏。而且您的UASSERT与标准断言相比有何优势?将比较结果分成3个标记,除了需要比您需要的逗号更多的内容,别无其他。
– Crelbor
2011年5月13日13:10
也许UASSERT宏显示(在错误消息中)两个比较值(及其值)之间的实际关系?恕我直言,这实际上是一个不错的主意。
– GhassanPL
2012年3月17日在17:03
gh,为什么std :: string类不包含split()函数?
– Shickadance先生
2012年4月18日在20:34
我认为while循环的最后一行应该是start =((end>(theString.size()-theDelimiter.size()))?string :: npos:end + theDelimiter.size());而while循环应为while(start!= string :: npos)。另外,在将子字符串插入向量之前,我要检查它是否为空。
– John K
2012年7月31日20:11
@JohnK如果输入具有两个连续的定界符,则显然它们之间的字符串为空,应将其插入向量中。如果空值对于特定目的是不可接受的,那是另一回事,但是恕我直言,此类约束应在此类非常通用的函数之外实施。
– Lauri Nurmi
2013年6月26日14:02
#9 楼
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"
""
#10 楼
我知道您要求使用C ++解决方案,但您可能会认为这很有用:Qt
它是到您的帖子代码的直接一对一映射。
请参阅Qt文档中的更多内容
#11 楼
这是一个仅使用STL的简单解决方案(〜5行!),它使用std::find
和std::find_first_not_of
处理定界符(例如空格或句点)的重复以及前导和尾随定界符:br />
尝试一下!
评论
这是一个很好的选择,但我认为您需要使用find_first_of()而不是find()才能与多个定界符一起正常工作。
–user755921
15年11月27日在19:28
使用find_first_not_of查找起始位置时,会跳过@ user755921多个定界符。
–初学者
18-10-5在12:38
#12 楼
这是一个示例标记生成器类,可以满足您的要求//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());
}
#13 楼
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 ++功能强大。但是,当拆分字符串会导致源代码如上述答案时,这显然令人沮丧。我很想知道其他像这样的库,它们会拉低高级语言的便利性。
–罗斯
2012年6月14日22:55
哇,你真的让我过得愉快!!不知道pystring。这将节省很多时间!
–赞扬
2015年2月10日在17:14
#14 楼
我针对类似问题发布了此答案。不要重新发明轮子。我使用了许多库,遇到的最快,最灵活的是:C ++ String Toolkit库。
这里是我在堆栈溢出的其他地方发布了如何使用它的示例。
#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;
}
#15 楼
检查这个例子。它可能会帮助您。.#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;
}
评论
我会在(is >> tmps){std :: cout << tmps <<“ \ n”; }
–jordix
16年4月5日在14:46
#16 楼
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
评论
此Tokenize()函数将跳过空令牌,例如,如果主字符串中有子字符串“ %%”,则不返回空令牌。它被跳过。
–光泽
2011年1月20日在16:49
#17 楼
亚当·皮尔斯(Adam Pierce)的答案提供了一个手动旋转的分词器,其输入为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;
}
实时示例
如果您要抽象复杂性通过使用标准功能,如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)
,尽管未在示例中进行演示,但strtok
不必为每个令牌使用相同的定界符。尽管具有此优点,但也有一些缺点:无法同时在多个strtok
上使用strings
:必须传递nullptr
才能继续标记当前必须传递string
或新的char*
进行令牌化(但是,有些非标准实现确实支持此功能,例如:strtok_s
)出于相同的原因,不能同时在多个线程上使用
strtok
(但是可能定义的实现,例如:Visual Studio的实现是线程安全的)调用
strtok
会修改正在对其进行操作的string
,因此无法在const string
,const char*
或文字字符串上使用,以使用strtok
标记其中的任何一个或在需要保留其内容的string
上进行操作,则必须复制str
,然后可以在c ++ 20上操作副本,从而为q ++ 12079q提供了令牌化字符串的功能,以无损方式:https://topanswers.xyz/cplusplus?q=749#a874
先前的方法无法就地生成标记化的
split_view
,这意味着如果不将其抽象为帮助函数,就无法初始化vector
。可以使用const vector<string> tokens
来利用该功能和接受任何空格分隔符的功能。例如给定的istream_iterator
,我们可以执行以下操作:istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
实时示例
此选项所需的
const string str{ "The quick \tbrown \nfox" }
构造成本要高得多与前两个选项相比,此成本通常隐藏在istringstream
分配的开销中。如果以上选项都不足以满足令牌化需求,则最灵活的选项是当然,使用具有这种灵活性的
string
会带来更大的支出,但这又很可能隐藏在regex_token_iterator
的分配成本中。例如,假设输入以下内容,我们希望基于非转义逗号进行标记化,并且也占用空白:string
我们可以执行以下操作:const regex re{ "\s*((?:[^\\,]|\\.)*?)\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
实时示例
评论
顺便说一下,strtok_s是C11标准。 strtok_r是POSIX2001标准。在这两者之间,大多数平台都有标准的可重入版本的strtok。
–安东·科尔曼(Andon M. Coleman)
16年12月14日在9:43
@ AndonM.Coleman但这是一个c ++问题,在C ++中,#include
–乔纳森·梅(Jonathan Mee)
16 Dec 14'在12:10
只是它并不像人们原本认为的那样不合标准。 strtok_s由C11提供,并且作为Microsoft C运行时中的独立扩展提供。微软的_s函数成为C标准在这里有一段奇怪的历史。
–安东·科尔曼(Andon M. Coleman)
16 Dec 14'在13:48
@ AndonM.Coleman对,我和你在一起。显然,如果它在C11标准中,则接口和实现受到约束,它们要求独立于平台的相同行为。现在唯一的问题是确保C11功能可跨平台使用。希望C11标准将成为C ++ 17或C ++ 20选择的标准。
–乔纳森·梅(Jonathan Mee)
16年12月14日在14:13
#18 楼
如果您愿意使用C,则可以使用strtok函数。使用它时应注意多线程问题。评论
请注意,strtok修改了要检查的字符串,因此,如果不进行复制,则不能在const char *字符串上使用它。
– Graeme Perrow
08-09-10 13:53
多线程问题是strtok使用全局变量来跟踪其位置,因此,如果您有两个都使用strtok的线程,则会得到未定义的行为。
– JohnMcG
08-09-10 15:09
@JohnMcG或者只是使用strtok_s,它基本上是带有显式状态传递的strtok。
–马特西亚斯
18年6月2日在16:51
#19 楼
对于简单的东西,我只使用以下代码: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;
}
#20 楼
您可以简单地使用正则表达式库并使用正则表达式解决该问题。使用表达式(\ w +)和\ 1中的变量(或$ 1,取决于正则表达式的库实现)。 />
评论
+1表示正则表达式,如果您不需要经线速度,它是最灵活的解决方案,尚未在所有地方得到支持,但是随着时间的流逝,它将变得不那么重要。
–odinthenerd
2014年7月27日在19:57
从我+1,只是在c ++ 11中尝试过
– StahlRat
2014年11月6日16:30
#21 楼
这里有很多过于复杂的建议。试试这个简单的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;
}
#22 楼
我以为这是字符串流上的>>
运算符的用途:string word; sin >> word;
评论
我的错是举一个不好的例子(太简单了)。据我所知,这仅在分隔符为空格时有效。
–比尔蜥蜴
08年11月25日在18:24
#23 楼
这是一种允许您控制是否包含空令牌(如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 != 'q4312079q') // 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;
}
#24 楼
令我感到奇怪的是,在这里,我们所有人都对速度敏感,所以没有人提出过使用编译时生成的查找表作为分隔符的版本(示例实现进一步向下)。使用查找表和迭代器应该在效率上击败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
评论
是否可以使用String分隔符进行标记?
–加里加托
2014年7月26日13:51
该版本仅针对单字符定界符进行了优化,使用查找表不适用于多字符(字符串)定界符,因此在效率上很难击败正则表达式。
–odinthenerd
2014年7月27日13:50
#25 楼
我知道这个问题已经回答,但我想贡献自己的力量。也许我的解决方案有点简单,但这是我想出的: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;
}
如果在我的代码中有更好的方法,或者如果有什么方法,请发表评论错误。
更新:添加了通用分隔符
评论
从人群中使用您的解决方案:)我可以修改您的代码以添加任何分隔符吗?
–扎克
19年11月21日在17:32
@Zac很高兴您喜欢它并且可以修改它...只需在我的答案中添加一个粗体的更新部分...
– NutCracker
19年11月21日在20:52
#26 楼
您可以利用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;
}
#27 楼
这是我的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;
}
评论
(下)票可以像投票一样具有建设性,但当您不对原因发表评论时就不会这样。
–kayleeFrye_onDeck
19年8月9日在20:55
我使您感到惊讶,但这可能是因为该代码对于程序员在搜索“如何分割字符串”时显得相当艰巨,尤其是在没有文档的情况下
–mattshu
1月12日14:05
谢谢@mattshu!是使正则表达式令人生畏的细分还是其他东西?
–kayleeFrye_onDeck
1月16日20:50
#28 楼
如果知道要分词的输入字符串的最大长度,则可以利用它并实现非常快的版本。我正在草绘下面的基本思想,该思想受到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;
}
};
实现文件:
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 != '// 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 ) );
}
' );
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 == 'Item1
Item2
Item3
' )
{ // item or line finished
// overwrite separator with a 0-terminating character:
mBuff[ idx ] = 'q4312078q';
// count-up items:
mNumTokens ++;
} // if
} while( line[ idx++ ] );
}
使用场景是:
q4312078q
输出:
q4312078q
#29 楼
boost::tokenizer
是您的朋友,但是请考虑使用国际化(i18n)问题使您的代码可移植,方法是使用wstring
/ 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是一种依赖于实现的可怕类型,除非绝对必要,否则任何人都不应使用。
–CoffeeandCode
2014年5月21日,0:09
使用wchar_t不能以某种方式自动解决任何i18n问题。您可以使用编码来解决该问题。如果用定界符分割字符串,则暗示定界符不会与字符串内任何标记的编码内容发生冲突。可能需要转义,等等。wchar_t并非对此的神奇解决方案。
– yonil
2015年9月7日在17:50
#30 楼
简单的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 ++中的例行任务如此头痛在C ++中,它并不令人头痛-有多种方法可以实现它。与C#相比,程序员对C ++的了解较少-有关营销和投资的信息……有关实现相同目标的各种C ++选项,请参见以下内容:cplusplus.com/faq/sequences/strings/split
@ hB0经历了许多问题的答案,但仍未决定方法是令人头疼的事情。一个需要该库,另一个仅用于空格,另一个不处理空格。.
在C ++中拆分字符串可能重复吗?
为什么C ++中的所有内容都必须奋斗?