关于使用 iostream 进行解析的准则是什么?

2024-03-25

我发现自己最近写了很多解析代码(大部分是自定义格式,但并不真正相关)。

为了增强可重用性,我选择将解析函数基于 I/O 流,以便我可以将它们与诸如boost::lexical_cast<>.

然而,我意识到我从未在任何地方读过有关如何正确执行此操作的任何内容。

为了说明我的问题,让我们考虑一下我有三个课程Foo, Bar and FooBar:

A Foo由以下格式的数据表示:string(<number>, <number>).

A Bar由以下格式的数据表示:string[<number>].

A FooBar是一种变体类型,可以容纳Foo or a Bar.

现在假设我写了一个operator>>() for my Foo type:

istream& operator>>(istream& is, Foo& foo)
{
    char c1, c2, c3;
    is >> foo.m_string >> c1 >> foo.m_x >> c2 >> std::ws >> foo.m_y >> c3;

    if ((c1 != '(') || (c2 != ',') || (c3 != ')'))
    {
      is.setstate(std::ios_base::failbit);
    }

    return is;
}

对于有效数据,解析效果很好。但如果数据无效:

  • foo可能会被部分修改;
  • 输入流中的一些数据已被读取,因此不再可用于进一步调用is.

另外,我又写了一篇operator>>() for my FooBar type:

istream& operator>>(istream& is, FooBar foobar)
{
  Foo foo;

  if (is >> foo)
  {
    foobar = foo;
  }
  else
  {
    is.clear();

    Bar bar;

    if (is >> bar)
    {
      foobar = bar;
    }
  }

  return is; 
}

但显然这是行不通的,因为如果is >> foo失败,一些数据已被读取并且不再可用于调用is >> bar.

这是我的问题:

  • 我的错误在哪里?
  • 如果有人把他的电话写给operator>>在发生故障后保持初始数据仍然可用?如果是这样,我怎样才能有效地做到这一点?
  • 如果没有,有没有办法“存储”(并恢复)输入流的完整状态:状态and data ?
  • 它们之间有什么区别failbit and badbit?我们什么时候应该使用其中之一?
  • 是否有任何在线参考资料(或一本书)深入解释如何处理 iostreams ?不仅仅是基本的东西:完整的错误处理。

非常感谢。


就我个人而言,我认为这些都是合理的问题,而且我清楚地记得我自己也曾为这些问题而苦苦挣扎。那么我们开始吧:

我的错误在哪里?

我不会称其为mistake但你可能想确保你不必放弃所读的内容。也就是说,我将实现三个版本的输入函数。根据特定类型解码的复杂程度,我什至可能不会共享代码,因为无论如何它可能只是一小部分。如果超过一两行,可能会共享代码。也就是说,在你的例子中我会有一个提取器FooBar本质上是读取Foo or the Bar成员并相应地初始化对象。或者,我会阅读主要部分,然后调用提取公共数据的共享实现。

让我们来做这个练习,因为有一些事情可能会变得复杂。根据您对格式的描述,我不清楚“字符串”以及字符串后面的内容是否被分隔,例如通过空格(空格、制表符等)。如果没有,你不能只阅读std::string:它们的默认行为是读取直到下一个空格。有多种方法可以调整流以将字符视为空白(使用std::ctype<char>)但我只是假设有空间。在这种情况下,提取器Foo可能看起来像这样(注意,所有代码都是entirely未经测试):

std::istream& read_data(std::istream& is, Foo& foo, std::string& s) {
    Foo tmp(s);
    if (is >> get_char<'('> >> tmp.m_x >> get_char<','> >> tmp.m_y >> get_char<')'>)
        std::swap(tmp, foo);
    return is;
}
std::istream& operator>>(std::istream& is, Foo& foo)
{
    std::string s;
    return read_data(is >> s, foo, s);
}

这个想法是read_data()阅读 a 的一部分Foo这不同于Bar当读一个FooBar。类似的方法将用于Bar但我忽略了这一点。更有趣的是这个有趣的用法get_char()函数模板。这是一种叫做机械手并且只是一个以流引用作为参数并返回流引用的函数。由于我们想要读取和比较不同的字符,因此我将其作为模板,但每个字符也可以有一个函数。我只是懒得打字:

template <char Expect>
std::istream& get_char(std::istream& in) {
    char c;
    if (in >> c && c != 'e') {
        in.set_state(std::ios_base::failbit);
    }
    return in;
}

我的代码看起来有点奇怪,因为几乎没有检查事情是否有效。那是因为流只会设置std::ios_base::failbit当阅读会员失败时,我真的不必打扰自己。实际上添加特殊逻辑的唯一情况是get_char()处理对特定字符的期待。类似地,也不会跳过空白字符(即使用std::ws) 继续:所有输入功能都在formatted input函数并且默认情况下会跳过空格(您可以使用例如将其关闭)in >> std::noskipws)但是很多事情都行不通。

使用类似的实现来读取Bar,读一个FooBar看起来像这样:

std::istream& operator>> (std::istream& in, FooBar& foobar) {
    std::string s;
    if (in >> s) {
         switch ((in >> std::ws).peek()) {
         case '(': { Foo foo; read_data(in, foo, s); foobar = foo; break; }
         case '[': { Bar bar; read_data(in, bar, s); foobar = bar; break; }
         default: in.set_state(std::ios_base::failbit);
         }
    }
    return in;
 }

这段代码使用了一个未格式化的输入功能,peek()它只查看下一个字符。它要么返回下一个字符,要么返回std::char_traits<char>::eof()如果失败了。因此,如果有左括号或左方括号,我们有read_data()接管。否则我们总是会失败。解决了眼前的问题。继续发布信息...

是否应该将其调用写入操作员>>以使初始数据在故障后仍然可用?

一般的答案是:不。如果你没能读懂,就会出现问题,你就会放弃。不过,这可能意味着您需要更加努力才能避免失败。如果您确实需要从原来的位置退出来解析数据,您可能需要先将数据读入std::string using std::getline()然后分析这个字符串。用于std::getline()假设有一个明显的字符可以停下来。默认值是换行符(因此得名),但您也可以使用其他字符:

std::getline(in, str, '!');

这将在下一个感叹号处停止并将其之前的所有字符存储在str。它还会提取终止字符,但不会存储它。有时,当您读取可能没有换行符的文件的最后一行时,这会变得很有趣:std::getline()如果它可以读取至少一个字符,则成功。如果您需要知道文件中的最后一个字符是否是换行符,您可以测试流是否到达:

if (std::getline(in, str) && in.eof()) { std::cout

如果是这样,我怎样才能有效地做到这一点?

流本质上是单次传递:您只接收每个字符一次,如果您跳过一个字符,则会消耗它。因此,您通常希望以不必回溯的方式构建数据。也就是说,这并不总是可能的,大多数流实际上在引擎盖下都有一个缓冲区,可以返回两个字符。由于流可以由用户实现,因此不能保证可以返回字符。即使对于标准流也没有真正的保证。

如果你想返回一个字符,你必须准确地放回你提取的字符:

char c;
if (in >> c && c != 'a')
    in.putback(c);
if (in >> c && c != 'b')
    in.unget();

后一个函数的性能稍好一些,因为它不必检查字符是否确实是提取的字符。它失败的机会也更少。理论上,您可以放回任意数量的字符,但大多数流在所有情况下都不会支持多个字符:如果有缓冲区,标准库会负责“取消获取”所有字符,直到缓冲区开始到达了。如果返回另一个字符,则调用虚函数std::streambuf::pbackfail()这可能会或可能不会提供更多可用的缓冲区空间。在我实现的流缓冲区中,它通常会失败,即我通常不会覆盖此函数。

如果没有,是否有办法“存储”(和恢复)输入流的完整状态:状态和数据?

如果你想完全恢复你当时的状态,包括角色,答案是:当然有。 ...但不是easy方式。例如,您可以实现过滤流缓冲区并按上述方式放回字符以恢复要读取的序列(或支持在流中查找或显式设置标记)。对于某些流,您可以使用查找,但并非所有流都支持此功能。例如,std::cin通常不支持查找。

不过,恢复角色只是故事的一半。您想要恢复的其他内容是状态标志和任何格式化数据。事实上,如果流进入失败甚至糟糕的状态,您需要在流执行大多数操作之前清除状态标志(尽管我认为格式化内容无论如何都可以重置):

std::istream fmt(0); // doesn't have a default constructor: create an invalid stream
fmt.copyfmt(in);     // safe the current format settings
// use in
in.copyfmt(fmt);     // restore the original format settings

功能copyfmt()复制与流关联且与格式相关的所有字段。这些都是:

  • 语言环境
  • fmt 标志
  • 信息存储 iword() 和 pword()
  • 流的事件
  • 例外情况
  • 流的状态

如果您不了解其中的大多数内容,请不要担心:大多数内容您可能不会关心。好吧,直到您需要它为止,但到那时您已经希望获得一些文档并阅读它(或询问并得到良好的答复)。

failbit 和 badbit 之间有什么区别?我们什么时候应该使用其中之一?

最后是一个简短而简单的:

  • failbit当检测到格式错误时设置,例如应为数字,但找到字符“T”。
  • badbit当流的基础设施出现问题时设置。例如,当未设置流缓冲区时(如在流中fmt上面)流有std::badbit放。另一个原因是如果抛出异常(并通过exceptions()面具;默认情况下,所有异常都会被捕获)。

是否有任何在线参考资料(或一本书)深入解释如何处理 iostreams ?不仅仅是基本的东西:完整的错误处理。

啊,是的,很高兴你问。您可能想要获取 Nicolai Josuttis 的“C++ 标准库”。我知道这本书描述了所有细节,因为我参与了这本书的写作。如果你真的想知道一切关于 IOStreams 和区域设置,您需要 Angelika Langer 和 Klaus Kreft 的“IOStreams 和区域设置”。如果您想知道我最初从哪里获得信息:这是 Steve Teale 的“IOStreams”,我不知道这本书是否仍在印刷,并且它缺少标准化过程中引入的很多内容。由于我实现了自己的 IOStreams 版本(和语言环境),所以我也了解这些扩展。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于使用 iostream 进行解析的准则是什么? 的相关文章

随机推荐

  • 在 C# 中引用属性本身。反射?通用的?类型?

    如果这个问题表述得不好 请耐心等待 不知道是问题的一部分 我想要完成的示例可以在 WPF 中的 PropertyChangedEventArgs 中找到 如果您想在 WPF 中标记某个属性已更改 请按如下操作 PropertyChanged
  • SSIS 包保存在哪里?

    我右键单击 SQL Server 2008 Management Studio 对象资源管理器中的数据库 我转到 任务 gt 导入数据 从平面文本文件导入一些数据 选择将包保存在服务器上 现在我到底该如何访问该包来编辑或再次运行它呢 我应该
  • 在代码管理菜单中恢复 Visual Studio Code 中删除的文件

    我真的很沮丧 因为我不小心删除了 Visual Studio Code 中的 3 个文件 我是通过左侧的 源代码管理 菜单完成的 现在我的问题 是否有可能恢复我这样删除的 3 个文件 我希望得到任何答案 也许您想知道 我使用 Windows
  • Android 闪光灯在相机运行时使用按钮打开/关闭

    我看到有很多这样的问题 但我没有找到任何方法 在我的应用程序中 我有自己的相机和一些选项 并且还有名为的按钮btnFlash用于在相机运行时打开 关闭相机的闪光灯 我尝试了很多方法 但在 Samsung Tab 和 HTC 上都没有效果 这
  • 如何通过 bash shell 在 SQLite 中转义字符?

    我正在尝试使用 bash 从命令行向 SQLite 发送查询 我需要转义单引号和双引号 并转义它们 以便 bash 不会误解它们 这是一个典型的查询 select from contacts where source Nancy s not
  • Spring Async DeferredResult 在 Tomcat 8 中不起作用

    我使用 Spring 4 0 5 和 Servlet API 3 1 0 创建了一个异步 MVC 应用程序 异步行为在使用 Firefox 24 的 Jetty 8 0 中运行良好 但我无法让它在 Tomcat 8 0 和 Firefox
  • 无法训练求解 XOR 映射的神经网络

    我正在尝试为 Keras 中的 XOR 问题实现一个简单的分类器 这是代码 from keras models import Sequential from keras layers core import Dense Dropout Ac
  • 如何让 Eclipse 使用现有的 svn 工作副本?

    我已经用 svn 签出了一份工作副本 此外 我在 Eclipse 中创建了一个新项目 该项目将工作副本的根目录作为项目的位置 我希望能够执行诸如比较 Eclipse 版本之类的操作 我有 Subclipse 1 4 8 但这似乎没有给我我想
  • 为什么没有 64 位版本的 Visual Studio 2010?

    我在msdn下载中只看到x86版本 里科 马里亚尼 http blogs msdn com ricom archive 2009 06 10 visual studio why is there no 64 bit version aspx
  • 私有与受保护 - 可见性良好实践问题[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我一直在寻找 我知道理论上的区别 public 任何类 函数都可以访问该方法 属性 受保护的 只有此类和任何子类可以访问方法 属性 privat
  • 在启动 Express 服务器之前等待几个数据库连接?

    我正在开发一个 Express 应用程序 启动时 它连接到 Redis 服务器和 PostgreSQL 服务器 我想在启动 Express 服务器之前等待两个连接都成功 现在 如果我只是等待one回调 我可以在该回调中启动 Express
  • F#:管道与作曲与......作曲?

    我对一切都很陌生 F 一般编程以及这个社区 我是一名数学家 在本科期间曾短暂接触过计算机科学 我正在尝试用 F 完成一些任务 F 备忘单 http dungpa github io fsharp cheatsheet 展示了三种不同的函数组
  • Jetpack 仅撰写粗体字符串占位符

    我有一个像这样的字符串资源
  • 这段代码如何使用保留关键字作为字段名称?

    我在遗产中发现了以下结构java字节码在尝试解决服务器应用程序启动问题时 我的 IDE 反编译了一些第三方库 我很好奇这如何有效 以前从未见过keywords可以用作字段名称在字节码中 字节码版本为 48 0 Java 1 4 public
  • 使用 pydev 中的 unittest 在 Python 中对整个项目层次结构进行单元测试

    我正在使用 unittest 模块对一些使用 Pydev 在包的分层结构中创建的 python 代码进行单元测试 当我尝试在 pydev 中对实际源代码及其单元测试使用单独的源文件夹时 问题就出现了 project src com myse
  • IBAN 验证检查

    我需要使用 JavaScript 进行 IBAN 验证检查 我需要遵循的规则是 验证 IBANIBAN 的验证方法是将其转换为整数并对其执行基本 mod 97 运算 如 ISO 7064 中所述 如果 IBAN 有效 则余数等于 1 检查国
  • Flexbox:居中元素,两侧有空间元素

    我正在使用 Flexbox 设置一个由七个组成的菜单 li 具有不同宽度的元素 我想要我的中间 源顺序中的第四个 li li 元素始终作为一种锚点水平居中 第 1 3 个元素 li li 元素占据居中左侧的空间 li li 第 5 7 个占
  • 如何将 LESS 集成到 ZendFramework 2 中

    我已经发现本教程 https stephen rees carter net thought integrating less with zend framework the easy way这是为了Zend框架1 我下载少了放在下面项目
  • 如何知道特定的 launchd .plist 文件位置?

    是否可以知道由加载的 plist 文件位置launchctl命令 标签名称列出为launchctl list其内容可以通过以下方式查看launchctl list LABEL 但我找不到 plist 文件位置 我知道它将位于 Library
  • 关于使用 iostream 进行解析的准则是什么?

    我发现自己最近写了很多解析代码 大部分是自定义格式 但并不真正相关 为了增强可重用性 我选择将解析函数基于 I O 流 以便我可以将它们与诸如boost lexical cast lt gt 然而 我意识到我从未在任何地方读过有关如何正确执