根据类型标记字符串流

Tokenize stringstream based on type

本文关键字:字符串 类型      更新时间:2023-10-16

我有一个包含整数和特殊含义字符'#'的输入流。它看起来如下: ... 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() 会带来很大的开销。

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

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

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

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

if (input.good()) {

除非下一个操作生成错误消息或异常。如果不好,所有进一步的操作都将失败。

不要针对EOF进行测试。

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

std::getline() 的结果不是整数。它是对输入流的引用。输入流可转换为类似布尔值的对象,该对象可以在布尔上下文(如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 */ }
}

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

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 循环中,我们将行拆分为"。

就个人而言,如果您的分隔符无论以下内容如何都始终是空格,我建议您仅将输入作为字符串并从那里解析。这样,你可以获取字符串,看看它是一个数字还是一个#等等。

我认为

你应该重新检查你的前提,即"在令牌上调用atoi()会带来很大的开销-"

没有魔法可以std::cin >> val.在引擎盖下,它最终调用(非常类似于)atoi。

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