55

我有一个要标记的字符串。但是 Cstrtok()函数要求我的字符串是char*. 我怎么能简单地做到这一点?

我试过了:

token = strtok(str.c_str(), " "); 

失败是因为它将它变成 a const char*,而不是 achar*

4

13 回答 13

76
#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 以获得更大的灵活性。

于 2008-11-14T06:29:38.493 回答
20

复制字符串,标记它,然后释放它。

char *dup = strdup(str.c_str());
token = strtok(dup, " ");
free(dup);
于 2008-11-14T06:22:42.063 回答
20
  1. 如果你的系统上可以使用boost(我认为它是当今大多数 Linux 发行版的标准),它有一个你可以使用的Tokenizer类。

  2. 如果没有,那么快速的 Google 会为 std::string 提供手动标记器,您可能只需复制和粘贴即可。它很短。

  3. 而且,如果您不喜欢其中任何一个,那么这是我编写的一个 split() 函数,以使我的生活更轻松。它将使用“delim”中的任何字符作为分隔符将字符串分成几部分。碎片被附加到“零件”向量中:

    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));
        }
      }
    }
    
于 2008-11-14T06:28:00.400 回答
9

有一个更优雅的解决方案。

使用 std::string 您可以使用 resize() 分配一个适当大的缓冲区,并使用 &s[0] 获取指向内部缓冲区的指针。

此时,许多优秀的人会跳起来对着屏幕大喊大叫。但这就是事实。大约 2 年前

图书馆工作组决定(在 Lillehammer 开会)就像 std::vector 一样,std::string 也应该正式地,而不仅仅是在实践中,有一个保证的连续缓冲区。

另一个问题是 strtok() 是否会增加字符串的大小。MSDN 文档说:

对 strtok 的每次调用都会通过在该调用返回的标记后插入一个空字符来修改 strToken。

但这是不正确的。实际上,该函数将第一次出现的分隔符替换为 \0。字符串的大小没有变化。如果我们有这个字符串:

一二三四

我们最终会得到

一\0二\0--三\0-四

所以我的解决方案很简单:


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

于 2009-10-20T06:49:46.340 回答
3

使用 C++17str::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;
}

输出

流行 音乐
_

于 2019-08-08T09:48:17.540 回答
2

编辑: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

因此,您必须求助于本机机制、字符串复制或前面提到的第三方库。

于 2008-11-14T07:59:27.297 回答
1

我想语言是C或C++ ...

strtok,IIRC,用 \0 替换分隔符。这就是它不能使用 const 字符串的原因。为了“快速”解决这个问题,如果字符串不是很大,你可以 strdup() 它。如果您需要保持字符串不变(const 建议的内容......),这是明智的。

另一方面,您可能想要使用另一个标记器,也许是手卷的,在给定的参数上不那么暴力。

于 2008-11-14T06:23:30.347 回答
1

假设您所说的“字符串”是指 C++ 中的 std::string,您可能会查看Boost中的Tokenizer包。

于 2008-11-14T06:29:09.010 回答
1

使用 std::string 时,Chris的答案可能很好;但是,如果您想使用 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)) {
    ...
}
于 2021-11-17T10:52:13.437 回答
0

首先,我会说使用 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]," ");
于 2008-11-14T10:05:48.853 回答
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(' ');
于 2015-06-11T13:30:52.613 回答
0

类型转换为 (char*) 让它为我工作!

token = strtok((char *)str.c_str(), " "); 
于 2020-11-25T15:05:04.967 回答
-1

它失败是因为str.c_str()返回常量字符串但char * strtok (char * str, const char * delimiters )需要易失性字符串。因此,您需要使用*const_cast< char >才能使其具有可变性。我给你一个完整但很小的程序来使用 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 可能并不适合所有情况,因为传递给函数的字符串会被分解为更小的字符串而被修改。请参考以更好地理解 strtok 功能。

strtok 的工作原理

添加了一些打印语句,以更好地理解每次调用 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 遍历字符串第一次调用找到非删除字符(在本例中为 2)并将其标记为标记开始,然后继续扫描分隔符并将其替换为空字符(# 被替换为实际字符串)并返回指向令牌开始字符(即,它返回以 null 终止的令牌 20)。在随后的调用中,它从下一个字符开始扫描,如果发现则返回令牌,否则返回 null。随后它返回令牌 6、5、3。

于 2015-10-16T20:38:54.900 回答