为什么在这种情况下 seekp() 会失败?

Why does seekp() fail in this case?

我正在使用此程序修改 text.txt 中的每个元音以成为主题标签 ('#')

#include <fstream>
#include <iostream>

int main()
{
    std::fstream iofile{ "text.txt", std::ios::in | std::ios::out };

    char chChar;

    while (iofile.get(chChar))
    {
        switch (chChar)
        {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
        case 'A':
        case 'E':
        case 'I':
        case 'O':
        case 'U':

            iofile.seekp(-1, std::ios::cur);

            iofile << '#';

            iofile.seekg(iofile.tellg(), std::ios::beg); 
        }
    }
}

所以如果text.txt的初始内容是

something is funny

会变成

s#m#th#ng #s f#nny

问题出在这一行

iofile.seekg(iofile.tellg(), std::ios::beg); 

这一行将文件指针保持在同一位置,所以我想我可以将这一行更改为这一行

iofile.seekg(0, std::ios::cur);

我以为这会做同样的事情,但事实并非如此。当我 运行 程序时,控制台只是停在那里并且不会停止,并且 text.txt 开始打印奇怪的东西。但是当我用这一行替换它时,它又起作用了

iofile.seekg(1, std::ios::cur);

我认为这应该将文件指针移得太远(我们应该将其保留在该位置,但在这里我们将其移动了 1 个字母)。但是,这有效。发生了什么事?

iofile << '#';
iofile.flush();  // flush buffer when switching from output to input
iofile.seekg(0, std::ios::cur);

我最近在读一本关于 iostream 的书,“Standard C++ IOStreams and Lcocales”。该书在第 1.4.3 节“双向文件流:从输出切换到输入”中进行了描述,

When output has been written to the bidirectional file streams, a read attempt immediately after writing to the file stream will lead to "undefined result." .... .... The read operation might fail without indicating this failure in anyway;

作者提到写完后读前必须flush流,也写了调用seekg(0, ios_base::beg)有清空内部缓冲区的作用,但是我不确定 seekg() 在第二个参数上使用不同的值调用 ios_base::cur 是否也有效。

这是个好问题!我能够使用 Microsoft Visual Studio Community Edition 2019 重现您的问题。但是,使用 Visual Studio 代码和 g++ 9.3.0,我没有遇到同样的问题。这似乎是一个编译器问题,特别是对于 Microsoft Visual C++。

C++ standard参考C标准库关于文件流的限制:

The restrictions on reading and writing a sequence controlled by an object of class basic_­filebuf<charT, traits> are the same as for reading and writing with the C standard library FILEs.

C 标准库(7.19.5.3 第 6 段)说:

When a file is opened with update mode ('+' as the second or third character in the above list of mode argument values), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters endof-file.

换句话说:在文件流的输入和输出之间切换时,您必须执行刷新或查找。现在,这一行:

iofile.seekg(0, std::ios::cur);

应该执行查找(到相对于当前流位置的相同位置),但是使用 MSVC++ 似乎没有发生,随后出现未定义的行为。但是,当您通过指定 std::ios::beg 从文件开头查找时,实际上执行了查找并且您的程序按预期工作。