【问题标题】:reading formatted input using C++ streams使用 C++ 流读取格式化输入
【发布时间】:2013-06-19 02:42:11
【问题描述】:

使用stdio.h 时,我可以轻松读取某些类型的格式化输入,如下所示:

FILE* fin = fopen(...);
fscanf(fin, "x = %d, y = %d", &x, &y);

这样做的好处是我不必担心字符“x”和后面的“=”之间有多少空格,以及其他小细节。

在我看来,在 C++ 中,

ifstream fin(...);
string s;
fin >> s;

可能导致s 成为"x""x=",甚至"x=12",具体取决于输入的间距。

有没有一种便捷的方法可以使用iostream/fstream 获得类似于scanf/fscanf 的行为?

【问题讨论】:

  • 有趣!我也想知道:)
  • 您可以使用带有忽略空格作为分隔符的构面的语言环境。
  • @0x499602D2:正常语言环境将空格视为分隔符。不将空格视为分隔符的语言环境在这里似乎没有帮助(至少对我而言)。 (显然)需要的是字母、逗号和等号被视为空白(除了正常的空白字符之外)。
  • @JerryCoffin 我相信你可以制作一个。为什么不创建它作为答案? :)

标签: c++ iostream


【解决方案1】:

考虑到先决条件,这实际上非常简单。我有这三个函数,我把它们放在某个地方的标题中。这些允许您输入字符文字和字符串文字。我一直不太明白为什么这些不是标准的。

#include <iostream>

//These are handy bits that go in a header somewhere
template<class e, class t, int N>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e(&sliteral)[N]) {
        e buffer[N-1] = {}; //get buffer
        in >> buffer[0]; //skips whitespace
        if (N>2)
                in.read(buffer+1, N-2); //read the rest
        if (strncmp(buffer, sliteral, N-1)) //if it failed
                in.setstate(std::ios::failbit); //set the state
        return in;
}
template<class e, class t>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e& cliteral) {
        e buffer(0);  //get buffer
        in >> buffer; //read data
        if (buffer != cliteral) //if it failed
                in.setstate(std::ios::failbit); //set the state
        return in;
}
//redirect mutable char arrays to their normal function
template<class e, class t, int N>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, e(&carray)[N]) {
        return std::operator>>(in, carray);
}

有了这些,剩下的就很简单了:

in>>'x'>>'='>>data.first>>','>>'y'>>'='>>data.second;

Proof here

对于更复杂的情况,您可能想要使用std::regexboost::regex,或者可能是真正的词法分析器/解析器。

【讨论】:

  • 我看到接受字符串文字的函数不能处理像fscanf() 这样的嵌入空格,但这仍然很好。但是,最后一个函数的目的是什么?如果要调用第一个函数,我看不出它会如何正常工作。看起来它会递归调用自己,直到堆栈崩溃。
  • @Adrian:它看起来像它,但事实并非如此。第一个捕获const e(&amp;)[N]。第二个捕获const e&amp;std::basic_istream::operator&gt;&gt; 捕获 e&amp;。不幸的是,第一个会捕获e(&amp;)[N](非常量),所以我有一个覆盖,它明确地将它转发给std::operator&gt;&gt;(...,e(&amp;)[N])。 (注意std::operator&gt;&gt;std命名空间中,这些在全局命名空间中,因此没有递归)
  • @Adrian:是的,字符串字面量用于in&gt;&gt;"name"&gt;&gt;'='&gt;&gt;value; 之类的东西。我想让它允许/禁止像 scanf 这样的空间可能是有意义的。我和我的朋友们争论了一段时间,关于字符串文字的输入是否应该忽略前面的空格以及它应该如何处理内部空格。表现得像fscanf 是有道理的,我可能会改变它来做到这一点。这会大大增加复杂性,但它会很方便。
  • 根据您的需要,使用 regex 可能有点臃肿。
  • @MooingDuck,嗨,你能指出我正确的方向,以便了解你在这里做了什么吗?我对 C++ 和 OOP 有基本的了解,但对流和模板知之甚少,我想了解更多。最让我困惑的是const e(&amp;sliteral)[N] 以及它是如何工作的,据我了解,它实例化了一个以字符串文字的地址作为参数构造的对象数组,但同时我看不出这在对重载运算符的签名。底线:我很困惑。
【解决方案2】:

就像您在 fscanf/scanf 中使用 %xx 指定格式一样,您可以使用本教程中详细介绍的流操纵器指定格式 -

http://www.tenouk.com/Module18.html

非常全面。流操纵器位于页面底部附近。

【讨论】:

    【解决方案3】:

    简短的回答是“不”。

    一个稍微长一点的答案是“您可能可以构建一些可以做到这一点的东西”。例如,您可以读取文本行,然后使用合适的“用空字符串替换空格”类型的函数。或者可能是这样的:

    int x, y;
    string s;
    getline(cin, s, '=');
    cin.get();  // Get rid of =
    cin >> x;
    getline(cin, s, '=');
    cin >> y;
    

    或者,使用cin.ignore 跳过某些内容(因为字符串读取并不是很有用,除非您想知道 'x' 和 'y' 实际上是 'x' 和 'y'=:

    int x, y;
    cin.ignore(1000000, '=');  // Skip up to a '=' 
    cin >> x;
    cin.ignore(1000000, '=');  // Skip up to a '='
    cin >> y;
    

    如果有人在没有 = 符号的情况下输入超过 100k 个字符,这将“中断”,并且需要进行错误检查以查看“垃圾”没有进入 - 就像 fscanf 一样。 if (cin &gt;&gt; x) 会处理“检测到出现问题,但您需要根据出现问题的事实做一些明智的事情,我现在不确定......

    当然,由于 C++ 支持(几乎)所有 C,您当然也可以始终使用您想使用的 &lt;cstdio&gt; 函数的任何成员。 [至少在某些情况下,它们实际上要好一些]。

    【讨论】:

    • 您没有补偿 ,,因为 istream 是用空格分隔的。
    • @Adrian 他的代码完全忽略了逗号,直接读入了两个数字。逗号可能存在​​也可能缺失,无论哪种方式都可以正常工作。
    • @MooingDuck 是的,但这可能会被解析为错误,具体取决于要求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-02
    • 2021-06-30
    • 2021-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多