【问题标题】:Tokenize stringstream based on type根据类型标记字符串流
【发布时间】:2012-10-06 18:59:17
【问题描述】:

我有一个包含整数和特殊含义字符“#”的输入流。它看起来如下: ... 12 18 16 # 22 24 26 15 # 17 # 32 35 33 ... 标记由空格分隔。 '#' 的位置没有模式。

我试图像这样标记输入流:

int value;
std::ifstream input("data");
if (input.good()) {
  string line;
  while(getline(data, line) != EOF) {
    if (!line.empty()) {
      sstream ss(line);
      while (ss >> value) {
        //process value ...

      }
    }
  }
}

此代码的问题是遇到第一个“#”时处理停止。

我能想到的唯一解决方案是将每个单独的标记提取成一个字符串(不是'#')并使用 atoi() 函数将字符串转换为整数。但是,由于大多数令牌是整数,因此效率非常低。在令牌上调用 atoi() 会带来很大的开销。

有没有办法可以按类型解析单个令牌?即,对于整数,将其解析为整数,而对于'#',跳过它。谢谢!

【问题讨论】:

  • 使用 getline 两次可以接受吗?如果是这样,请使用getline( data, line, '#'); fisrt。
  • @ahenderson 我没明白你的意思。 getline 的函数签名之一是: istream& getline ( istream& is, string& str, char delim );通过将 '#' 作为第三个参数传递给 getline,getline() 将使用 '#' 作为分隔符。

标签: c++ iostream sstream


【解决方案1】:

一种可能性是显式跳过空格 (ss >> std::ws),然后使用 ss.peek() 来确定是否跟随 #。如果是,则使用ss.get() 读取并继续,否则使用ss >> value 读取值。

如果# 的位置无关紧要,您也可以在用它初始化stringstream 之前从该行中删除所有'#'

【讨论】:

  • 谢谢。您的解决方案总是向前看,对于“#”,使用 ss.get() 来使用它并继续前进。如果 '#' 可以是其他非数字字符,我们能有更通用的解决方案吗?
  • @itnovice:如果您的数字都是正整数(即仅由数字09 组成),您可以将其传递给isdigit。如果它是一个数字,您可以继续读取该值,否则您知道它是一个非数字字符。或者,您可以使用测试next < '0' || next > '9'(其中next 包含peek 的结果)来识别非数字。
【解决方案2】:

通常不值得对 good() 进行测试

if (input.good()) {

除非您的下一个操作正在生成错误消息或异常。如果它不好,那么所有进一步的操作都会失败。

不要针对EOF进行测试。

while(getline(data, line) != EOF) {

std::getline() 的结果不是整数。它是对输入流的引用。输入流可转换为可在 bool 上下文中使用的类似 bool 的对象(如 while if 等)。所以你想做什么:

while(getline(data, line)) {

我不确定我会读一行。您可以只阅读一个单词(因为输入是空格分隔的)。在字符串上使用 >> 运算符

std::string word;
while(data >> word) {  // reads one space separated word

现在你可以测试一下这个词是否是你的特殊字符:

if (word[0] == "#")

如果不把单词转换成数字。

这就是我会做的:

// define a class that will read either value from a stream
class MyValue
{
  public:
    bool isSpec() const {return isSpecial;}
    int  value()  const {return intValue;}

    friend std::istream& operator>>(std::istream& stream, MyValue& data)
    {
        std::string item;
        stream >> item;
        if (item[0] == '#') {
            data.isSpecial = true;
        } else
        {   data.isSpecial = false;
            data.intValue  = atoi(&item[0]);
        }
        return stream;
    }
  private:
    bool isSpecial;
    int  intValue;
};

// Now your loop becomes:
MyValue  val;
while(file >> val)
{
    if (val.isSpec())  { /* Special processing */ }
    else               { /* We have an integer */ }
}

【讨论】:

  • 您的解决方案非常好。谢谢!
【解决方案3】:

也许您可以将所有值读取为 std::string 然后检查它是否为“#”(如果不是 - 转换为 int)

【讨论】:

    【解决方案4】:
    int value;
    std::ifstream input("data");
    if (input.good()) {
        string line;
        std::sstream ss(std::stringstream::in | std::stringstream::out);
        std::sstream ss2(std::stringstream::in | std::stringstream::out);
        while(getline(data, line, '#') {
            ss << line;
            while(getline(ss, line, ' ') {
                ss2 << line;
                ss2 >> value
                //process values ...
                ss2.str("");  
            }
            ss.str("");
        }
    }
    

    在这里,我们首先在第一个while循环中用标记'#'分割行,然后在第二个while循环中,我们用''分割行。

    【讨论】:

    • 我在这台机器上没有 c++ 编译器,代码没有经过测试。因此,请将此作为它应该如何工作的总体大纲。
    • @itnovice 小错误我有 ss &gt;&gt; value 应该是 ss2 >> 值
    【解决方案5】:

    就个人而言,如果您的分隔符是 always 无论接下来发生什么,我都建议您将输入作为字符串并从那里解析。这样,您可以获取字符串,查看它是数字还是 # 等等。

    【讨论】:

      【解决方案6】:

      我认为你应该重新审视你的前提,即“在令牌上调用 atoi() 会带来很大的开销——”

      std::cin &gt;&gt; val 没有魔法。在引擎盖下,它最终会调用(类似于)atoi。

      如果您的令牌很大,创建std::string 可能会有一些开销,但正如您所说,绝大多数是数字(其余是#),所以它们应该很短。

      【讨论】:

      • 我不知道 std::cin >> val 最终调用了一些类似于 atoi() 的函数。所以我的假设是错误的。感谢您指出这一点。
      猜你喜欢
      • 2013-09-22
      • 2017-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-09
      • 1970-01-01
      相关资源
      最近更新 更多