我有一个字符串需要分割成标记。
但是 C strtok()
函数要求我的字符串是char*
类型的。
如何简单地实现这个需求?
我已经尝试过:
token = strtok(str.c_str(), " ");
它失败的原因是它将其转换为 const char*
而不是 char*
The reason for its failure is that it converts it into a
const char*
instead of a char*
.我有一个字符串需要分割成标记。
但是 C strtok()
函数要求我的字符串是char*
类型的。
如何简单地实现这个需求?
我已经尝试过:
token = strtok(str.c_str(), " ");
它失败的原因是它将其转换为 const char*
而不是 char*
const char*
instead of a char*
.#include <iostream>
#include <string>
#include <sstream>
int main(){
std::string myText("some-text-to-tokenize");
std::istringstream iss(myText);
std::string token;
while (std::getline(iss, token, '-'))
{
std::cout << token << std::endl;
}
return 0;
}
或者,如之前提到的,使用boost以获得更多的灵活性。
复制字符串,将其分词,然后释放它。
char *dup = strdup(str.c_str());
token = strtok(dup, " ");
free(dup);
strtok()
不是线程安全或可重入的。在具有多个任务的程序中,应避免使用它。 - Colin D Bennettstrdup()
来自POSIX,这就是为什么最好不要使用它的原因。 - FanaticDIf boost is available on your system (I think it's standard on most Linux distros these days), it has a Tokenizer class you can use.
If not, then a quick Google turns up a hand-rolled tokenizer for std::string that you can probably just copy and paste. It's very short.
And, if you don't like either of those, then here's a split() function I wrote to make my life easier. It'll break a string into pieces using any of the chars in "delim" as separators. Pieces are appended to the "parts" vector:
void split(const string& str, const string& delim, vector<string>& parts) {
size_t start, end = 0;
while (end < str.size()) {
start = end;
while (start < str.size() && (delim.find(str[start]) != string::npos)) {
start++; // skip initial whitespace
}
end = start;
while (end < str.size() && (delim.find(str[end]) == string::npos)) {
end++; // skip to end of word
}
if (end-start != 0) { // just ignore zero-length strings.
parts.push_back(string(str, start, end-start));
}
}
}
有一种更优雅的解决方案。
使用std::string,您可以使用resize()来分配一个适当大小的缓冲区,并使用&s[0]来获取指向内部缓冲区的指针。
此时,许多优秀的人会跳起来大喊。但这是事实。大约两年前,
图书馆工作组决定(在Lillehammer会议上)像std::vector一样,std::string也应该正式地、而不仅仅是在实践中拥有保证连续的缓冲区。
另一个问题是strtok()是否会增加字符串的大小。MSDN文档说:
每次调用strtok都会通过在该调用返回的标记后插入空字符来修改strToken。
但这是不正确的。实际上,该函数将第一个分隔符字符替换为\0。字符串的大小不会改变。如果我们有这个字符串:
one-two---three--four
我们最终会得到
one\0two\0--three\0-four
所以我的解决方案非常简单:
std::string str("some-text-to-split");
char seps[] = "-";
char *token;
token = strtok( &str[0], seps );
while( token != NULL )
{
/* Do your thing */
token = strtok( NULL, seps );
}
strtok()
在以空字符结尾的字符串上工作,而 std::string
的缓冲区并不要求以空字符结尾。没有绕过 c_str()
的方法。 - SnakEstd::string
的缓冲区必须以空字符结尾。要求data
和c_str
是相同的,并且data() + i == &operator[](i)
对于每个i
在[0,size()]
中都成立。 - Alex Celestestd::cout << str << " " << str.size(); std::cout << str.c_str()<< " " << strlen(str.c_str());
之前:some-text-to-split 18 some-text-to-split 18
之后:sometexttosplit 18 some 4
。 - dmitri在C++17中,str::string
得到了一个重载的data()
函数,它返回一个指向可修改缓冲区的指针,因此字符串可以直接在strtok
中使用,而无需进行任何黑客攻击:
#include <string>
#include <iostream>
#include <cstring>
#include <cstdlib>
int main()
{
::std::string text{"pop dop rop"};
char const * const psz_delimiter{" "};
char * psz_token{::std::strtok(text.data(), psz_delimiter)};
while(nullptr != psz_token)
{
::std::cout << psz_token << ::std::endl;
psz_token = std::strtok(nullptr, psz_delimiter);
}
return EXIT_SUCCESS;
}
输出
弹出
删除
读取
std::string
将不再持有相同的值,因为 strtok 会将它找到的分隔符替换为原地的空终止符,而不是返回字符串的副本。如果你想保留原始字符串,请创建字符串的副本并将其传递给 strtok。 - user233009strtok
仅处理单个分隔符,则可以通过在每次迭代中放回分隔符并替换空终止符来保留字符串的原始值。 - user7860670编辑:仅使用const cast是为了演示将strtok()
应用于string::c_str()返回的指针时的效果。
不应该使用strtok()
,因为它会修改已分词的字符串,这可能导致不希望的、甚至未定义的行为,因为C字符串“属于”字符串实例。
#include <string>
#include <iostream>
int main(int ac, char **av)
{
std::string theString("hello world");
std::cout << theString << " - " << theString.size() << std::endl;
//--- this cast *only* to illustrate the effect of strtok() on std::string
char *token = strtok(const_cast<char *>(theString.c_str()), " ");
std::cout << theString << " - " << theString.size() << std::endl;
return 0;
}
调用 strtok()
后,空格被“移除”或转换为不可打印字符,但字符串的长度保持不变。
>./a.out
hello world - 11
helloworld - 11
我猜测这是C或C++语言...
据我所知,strtok会用\0替换分隔符。这就是为什么它不能使用const字符串的原因。 如果字符串不是很大,为了“快速”解决这个问题,你可以使用strdup()。如果你需要保持字符串不变(正如const所建议的那样...),这是明智的选择。
另一方面,你可能想使用另一个分词器,也许是手写的,对给定的参数更加温和。
如果您不介意使用开源代码,您可以使用https://github.com/EdgeCast/json_parser中的subbuffer和subparser类。原始字符串保持不变,没有分配和复制数据。我没有编译以下内容,因此可能会出现错误。
std::string input_string("hello world");
subbuffer input(input_string);
subparser flds(input, ' ', subparser::SKIP_EMPTY);
while (!flds.empty())
{
subbuffer fld = flds.next();
// do something with fld
}
// or if you know it is only two fields
subbuffer fld1 = input.before(' ');
subbuffer fld2 = input.sub(fld1.length() + 1).ltrim(' ');
Chris的答案在使用std::string时可能是可以的;但是如果您想使用std::basic_string<char16_t>,则无法使用std::getline。这里是另一种可能的实现方式:
template <class CharT> bool tokenizestring(const std::basic_string<CharT> &input, CharT separator, typename std::basic_string<CharT>::size_type &pos, std::basic_string<CharT> &token) {
if (pos >= input.length()) {
// if input is empty, or ends with a separator, return an empty token when the end has been reached (and return an out-of-bound position so subsequent call won't do it again)
if ((pos == 0) || ((pos > 0) && (pos == input.length()) && (input[pos-1] == separator))) {
token.clear();
pos=input.length()+1;
return true;
}
return false;
}
typename std::basic_string<CharT>::size_type separatorPos=input.find(separator, pos);
if (separatorPos == std::basic_string<CharT>::npos) {
token=input.substr(pos, input.length()-pos);
pos=input.length();
} else {
token=input.substr(pos, separatorPos-pos);
pos=separatorPos+1;
}
return true;
}
然后像这样使用它:
std::basic_string<char16_t> s;
std::basic_string<char16_t> token;
std::basic_string<char16_t>::size_type tokenPos=0;
while (tokenizestring(s, (char16_t)' ', tokenPos, token)) {
...
}