我有一个我想要标记的字符串。但是C strtok()
函数需要我的字符串是char*
。我怎么能这么做呢?
我试过了:
token = strtok(str.c_str(), " ");
失败,因为它变成了const char*
,而不是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可以获得更大的灵活性。
它失败是因为str.c_str()
返回常量字符串,但char * strtok (char * str, const char * delimiters )
需要volatile字符串。所以你需要使用* const_cast <char>才能使它成为voletile。我给你一个完整但很小的程序来使用C strtok()函数来标记字符串。
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
int main() {
string s="20#6 5, 3";
// strtok requires volatile string as it modifies the supplied string in order to tokenize it
char *str=const_cast< char *>(s.c_str());
char *tok;
tok=strtok(str, "#, " );
int arr[4], i=0;
while(tok!=NULL){
arr[i++]=stoi(tok);
tok=strtok(NULL, "#, " );
}
for(int i=0; i<4; i++) cout<<arr[i]<<endl;
return 0;
}
注意:strtok可能并不适用于所有情况,因为传递给函数的字符串会被分解为较小的字符串而被修改。请使用ref来更好地理解strtok功能。
strtok如何运作
添加了一些print语句,以便更好地理解每次调用strtok时字符串发生的变化以及它如何返回令牌。
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
int main() {
string s="20#6 5, 3";
char *str=const_cast< char *>(s.c_str());
char *tok;
cout<<"string: "<<s<<endl;
tok=strtok(str, "#, " );
cout<<"String: "<<s<<"\tToken: "<<tok<<endl;
while(tok!=NULL){
tok=strtok(NULL, "#, " );
cout<<"String: "<<s<<"\t\tToken: "<<tok<<endl;
}
return 0;
}
输出:
string: 20#6 5, 3
String: 206 5, 3 Token: 20
String: 2065, 3 Token: 6
String: 2065 3 Token: 5
String: 2065 3 Token: 3
String: 2065 3 Token:
strtok迭代字符串第一次调用找到非delemetor字符(在这种情况下为2)并将其标记为令牌开始然后继续扫描一个分隔符并将其替换为null charater(#gets在实际字符串中替换)并返回start指向令牌开始字符(即,它返回由null终止的令牌20)。在后续调用中,它从下一个字符开始扫描,如果找到则返回令牌null。随后它返回令牌6,5,3。
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));
}
}
}
复制字符串,将其标记化,然后释放它。
char *dup = strdup(str.c_str());
token = strtok(dup, " ");
free(dup);
有一个更优雅的解决方案。
With std::string you can use resize() to allocate a suitably large buffer, and &s[0] to get a pointer to the internal buffer.
在这一点上,许多优秀的人都会在屏幕上大喊大叫。但这是事实。大约2年前
the library working group decided (meeting at Lillehammer) that just like for std::vector, std::string should also formally, not just in practice, have a guaranteed contiguous buffer.
另一个问题是strtok()增加了字符串的大小。 MSDN文档说:
Each call to strtok modifies strToken by inserting a null character after the token returned by that call.
但这不正确。实际上,该函数用\ 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 );
}
阅读有关http://www.archivum.info/comp.lang.c++/2008-05/02889/does_std::string_have_something_like_CString::GetBuffer的讨论
我想这个语言是C或C ++ ......
strtok,IIRC,用\ 0替换分隔符。这就是它不能使用const字符串。要解决“快速”,如果字符串不是很大,你可以strdup()它。如果你需要保持字符串不变,这是明智的(const建议......)。
另一方面,你可能想要使用另一个tokenizer,也许是手动滚动,对给定的参数不那么暴力。
编辑: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
因此,您必须使用本机机制,复制字符串或第三方库,如前所述。
首先,我会说使用boost tokenizer。 或者,如果您的数据是空格分隔的,那么字符串流库非常有用。
但上述两点都已被涵盖。 因此,作为第三个C-Like替代方案,我建议将std :: string复制到缓冲区进行修改。
std::string data("The data I want to tokenize");
// Create a buffer of the correct length:
std::vector<char> buffer(data.size()+1);
// copy the string into the buffer
strcpy(&buffer[0],data.c_str());
// Tokenize
strtok(&buffer[0]," ");
如果你不介意开源,可以使用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(' ');