【问题标题】:Using strtok/strtok_r in a while loop in C++在 C++ 的 while 循环中使用 strtok/strtok_r
【发布时间】:2015-11-02 19:58:35
【问题描述】:

我从 strtok 和 strtrok_r 函数中得到了意外的行为:

queue<string> tks;
char line[1024];
char *savePtr = 0;

while(true)
{
    //get input from user store in line

    tks.push(strtok_r(line, " \n", &savePtr));  //initial push only works right during first loop

    char *p = nullptr;
    for (...)
    {
        p = strtok_r(NULL, " \n", &savePtr);
        if (p == NULL)
        {
            break;
        }
        tks.push(p);
    }
    delete p;
    savePtr = NULL;

    //do stuff, clear out tks before looping again

}

我尝试使用strtok 并意识到在第二个循环期间,初始推送没有发生。我尝试使用可重入版本strtok_r 来控制保存的指针在第二次循环期间指向的内容,方法是在再次循环之前确保它为空。

tks 仅在第一次通过循环时正确填充 - 后续循环根据 line 的长度给出不同的结果

我在这里错过了什么?

【问题讨论】:

  • minimal reproducible example 或者它没有发生。
  • 重要有趣的事实:strtok(和 strtok_r)不 new 指针被分配给 p。它获取您输入的字符串line 并将其切碎,插入一个空字符来代替下一个找到的分隔符,然后返回一个指向现在以空结尾的字符串的第一个字符的指针。所以delete p; 实际上是在尝试delete 的一部分line。卡繁荣。好的。也许不是,因为由于循环退出条件,您真正删除的是 NULL 。不过,可能无论如何都不应该这样做。
  • Gotcha...虽然这不会影响我的问题:(
  • 建议:既然您已经知道您有来自用户的一行,那么\n 分隔符似乎有点多余。您可以将line 放入std::stringstream linestream(line);,然后std::getline(linestream, word, ' '); 其中wordstd::string,然后是tks.push(word);

标签: c++ parsing tokenize strtok


【解决方案1】:

只关注内部循环并删除所有我认为不必要的东西。

#include <iostream>
#include <queue>
#include <string>
#include <cstring>

using namespace std;

int main()
{
    std::queue<std::string> tks;

    while(true)
    {
        char line[1024];
        char *savePtr;
        char *p;
        cin.getline(line, sizeof(line));
        p = strtok_r(line, " \n", &savePtr); // initial read. contents of savePtr ignored
        while (p != NULL) // exit when no more data, which includes an emtpy line
        {
            tks.push(p); // got data, store it
            p = strtok_r(NULL, " \n", &savePtr); // get next token
        }
        // consume tks  
    }
}

比起 Toby Speight 在他的回答中使用的 for 循环,我更喜欢 while 循环,因为我认为它更透明且更易于阅读。你的旅费可能会改变。当编译器完成它时,它们将是相同的。

无需删除任何内存。它都是静态分配的。除了tks,下一轮之前不需要清除任何东西。 savePtr 将被第一个 strtok_r 重置。

如果用户在一行中输入超过 1024 个字符,则会出现故障情况,但这不会崩溃。如果这仍然不起作用,请查看您是如何使用 tks 的。它没有发布,因此我们无法解决该部分的问题。

如果可能,强烈建议更改为基于字符串的解决方案。这是一个非常简单、易于编写,但速度很慢的一个:

#include <iostream>
#include <queue>
#include <string>
#include <sstream>

int main()
{
    std::queue<std::string> tks;

    while(true)
    {
        std::string line;
        std::getline(std::cin, line);
        std::stringstream linestream(line);
        std::string word;
        // parse only on  ' ', not on the usual all whitespace of >>
        while (std::getline(linestream, word, ' ')) 
        {
            tks.push(word);
        }
        // consume tks  
    }
}

【讨论】:

    【解决方案2】:

    您的代码无法为我编译,所以我修复了它:

    #include <iostream>
    #include <queue>
    #include <string>
    #include <cstring>
    
    std::queue<std::string> tks;
    
    int main() {
        char line[1024] = "one \ntwo \nthree\n";
        char *savePtr = 0;
    
        for (char *p = strtok_r(line, " \n", &savePtr);  p;
                              p = strtok_r(nullptr, " \n", &savePtr))
            tks.push(p);
    
        // Did we read it correctly?
        for (;  tks.size() > 0;  tks.pop())
            std::cout << ">" << tks.front() << "<" << std::endl;
    }
    

    这会产生预期的输出:

    >one<
    >two<
    >three<
    

    所以你的问题不在于你发布的代码。

    【讨论】:

      【解决方案3】:

      如果你可以选择使用 boost,试试这个来标记一个字符串。当然是提供你自己的字符串和分隔符。

      #include <vector>
      #include <boost/algorithm/string.hpp>
      
      int main()
      {
          std::string str = "Any\nString\nYou want";
      
          std::vector< std::string > results;
          boost::split( results, str, boost::is_any_of( "\n" ) );
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-03
        • 2014-04-08
        • 1970-01-01
        • 2013-06-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多