std::fstream 中的输入位置问题

Problem with input position in std::fstream

提问人:Leon 提问时间:5/23/2023 最后编辑:Remy LebeauLeon 更新时间:5/23/2023 访问量:77

问:

使用 ,在调用函数后,我调用它的工作是将输入位置前进 1,就像在所有其他情况下一样,但这次它什么都不做,也没有前进输入位置。如下代码所示:std::fstreamtestFile.getline()testFile.seekg(1, std::ios_base::cur);

std::fstream testFile;
testFile.open("test.txt", std::ios_base::in | std::ios_base::out | std::ios_base::trunc);
if (testFile.is_open())
{
    testFile.flush();
    testFile.seekp(0);
    char msg[]{ "GETTING IT TO WORK;\nNEXT LINE;\n" };
    testFile.write(msg, 32);
    testFile.put('\0');
    testFile.flush();
         
    std::string header;
    header.reserve(100);
    testFile.seekg(0);
    testFile.getline(&header[0], 100, ';');
    header.clear();

    //testFile.seekg(1, std::ios_base::cur); 
    //uncommenting this code will do nothing only if getline has not been called 
    //immediately before it, will do the advancing
    testFile.getline(&header[0], 100, ';');
}

在此方案中,呼叫只会将输入位置前进 1,但在之前没有呼叫时,呼叫会将输入位置前进 2。testFile.seekg(2, std::ios_base::cur)getline()

我已经阅读了文档并亲自测试了每个场景,但文档中没有解释这种行为,也不是程序员的预期行为。

使用 MSVC v143 和 ISO C++20。

C++ STL 标准 FSTREAM

评论


答:

2赞 paxdiablo 5/23/2023 #1

突出的事实似乎是:

  • 将它提高一个似乎没有区别;
  • 将它前进 2 看起来像是将它前进 1;和
  • 这紧跟在 A 之后,否则不会导致问题。getline()

这似乎表明您可能有一个实际行结尾的文件,而不是 .我会使用十六进制转储实用程序检查文件的内容,看看是否是这种情况。CR/LF\r\n\n

Seek 实际上可能处理的是二进制内容而不是文本内容,因此跳过一个字符只会跳过 .如果是这种情况,那么您可以通过将字符读入垃圾缓冲区来修复它,而不是尝试向前寻找一个字节。前者应该有望正确处理行尾。换言之,改变:\r

testFile.seekg(1, std::ios_base::cur);

到:

int junk = testFile.get();

事实上,这似乎就是正在发生的事情。考虑以下程序,它的功能与您的代码大致相同,但依次查找每个位置并读取该行,打印它读取的第一个字符:

#include <iostream>
#include <fstream>

int main()
{
    std::fstream testFile;
    testFile.open("test.txt",
        std::ios_base::in | std::ios_base::out | std::ios_base::trunc);
    if (testFile.is_open())
    {
        char msg[] = "a\nb\nc;";
        testFile.write(msg, sizeof(msg));
        testFile.flush();

        char buff[10];
        for (int i = 0; i < 7; ++i) {
            testFile.seekg(i);
            testFile.getline(buff, 100, ';');
            std::cout << "<" << int(buff[0]) << " "
                      << ((buff[0] >= ' ') ? buff[0] : '.')
                      << "> <"
                      << int(buff[1]) << " "
                      << ((buff[1] >= ' ') ? buff[1] : '.')
                      << ">\n";
        }
    }
}

其输出为:

<97 a> <10 .>
<10 .> <98 b>   *1
<10 .> <98 b>   *2
<98 b> <10 .>
<10 .> <99 c>   *1
<10 .> <99 c>   *2
<99 c> <0 .>

您可以看到,当您寻求 或 时,字符读取的结果是 (decimal;10 是换行符)。我已经用 和 标记了发生这种情况的行。\r\n\n*1*2

因此,文本文件的基础 I/O 例程似乎将执行如下操作:

  • 如果接下来的两个字符是 ,你会得到一个 ()。\r\n\n*1
  • 如果下一个字符是 ,你会得到一个 ()。\n\n*2

这就是为什么当下一个字符是 .它正在寻求,但行为如上所述(寻求然后按照)。\r\n*2


顺便说一句,一个好的调试选项是在每个文件操作后输出,只是为了看看它认为它在哪里。testFile.tellg()

有趣的是,当我这样做时(在 Linux 下),缓冲区似乎移动正常,但字符串打印为空。如果我替换(并进行其他调整以匹配),似乎没问题。请注意,一个人的 seek ahead 在这里有效,可能是因为它是一个换行结束平台。string headerchar header[200]

#include <iostream>
#include <fstream>

using namespace std;

int main() {
    std::fstream testFile;
    testFile.open("test.txt", std::ios_base::in | std::ios_base::out | std::ios_base::trunc);
    if (testFile.is_open()) {
        cout << testFile.tellg() << " a\n";

        testFile.flush();
        cout << testFile.tellg() << " b\n";

        testFile.seekp(0);
        cout << testFile.tellg() << " c\n";

        char msg[]{ "GETTING IT TO WORK;\nNEXT LINE;\n" };

        testFile.write(msg, 32);
        cout << testFile.tellg() << " d\n";

        testFile.put('\0');
        cout << testFile.tellg() << " e\n";

        testFile.flush();
        cout << testFile.tellg() << " f\n";

        char header[200];
        testFile.seekg(0);
        cout << testFile.tellg() << " g\n";

        testFile.getline(&header[0], 100, ';');
        cout << testFile.tellg() << " h\n";

        cout << '<' << header << ">\n";

        testFile.seekg(1, std::ios_base::cur);
        testFile.getline(&header[0], 100, ';');
        cout << testFile.tellg() << " i\n";

        cout << '<' << header << ">\n";
    }
}

这将输出:

0 a
0 b
0 c
32 d
33 e
33 f
0 g
19 h
<GETTING IT TO WORK>
30 i
<NEXT LINE>

我建议在您的平台上尝试该代码以查看它输出的内容。


直接写入字符串不起作用的原因似乎是,这并不一定能为此做好准备。我必须实际填充字符串,使其至少足够大(大小,而不仅仅是保留容量),以便直接写入有效。当然,之后我不得不调整长度,因为 C++ 字符串保存字符非常有效。reserve()\0

这可能是因为直接写入只是将数据放入字符串区域,但不会影响字符串对象的任何其他重要属性(例如长度)。

将行读取器重构为:

void getUpTo(fstream &strm, string &str, size_t sz, char delim) {
    if (str.length() < sz + 1)
        str.resize(sz + 1);
    strm.getline(&str[0], sz, delim);
    str.resize(str.find('\0'));
}

并更改呼叫:

// from: testFile.getline(&header[0], 100, ';');
// to:
getUpTo(testFile, header, 100, ';');

让它起作用了。

这并不能立即适用于您的行尾问题,但即使您解决了这个问题,如果您似乎得到空/坏字符串,您也应该记住这一点。

评论

0赞 Leon 5/23/2023
它是在记事本++设置中编码的文本格式ansi,与CR+LF一样,你是对的,记事本++说CR+LF,但我该怎么办?
0赞 paxdiablo 5/23/2023
@Leon:“您可以通过将字符读入垃圾缓冲区来修复它,而不是尝试向前寻找一个字节”:-)话虽如此,我不确定这是问题所在,因为您使用的是分隔符,而不是默认换行符。;
1赞 Remy Lebeau 5/23/2023
@Leon您正在将一个字符串写入文本模式下操作,那么在 Windows 上它将写入实际文件。而在阅读和寻找时,它会被视为一个字符。如果您不希望出现此行为,请改为进入二进制模式,然后它将单独处理每个字节。\nfstream\r\n\r\n\nfstream
1赞 paxdiablo 5/23/2023
@Remy,根据描述,当您在 不将其视为单个角色时,它似乎被一个人寻找。鉴于文件处于文本模式,我实际上希望它。\r\n
1赞 Remy Lebeau 5/23/2023
@paxdiablo是有道理的,因为搜索不会读取字节(如果这样做,效率会很低)