为什么 'new line' 将 .txt 文件中的所有字符字节位置偏移 +1?

Why does 'new line' offset the all the characters byte position in a .txt file +1?

当我使用 fstream::tellg 时,在用 fstream::get (char) 读取第一个字符后,结果是:1

然后我在第一个字符

之后插入一个'new line'

fstream::seekg开头:0

当我使用fstream::tellg时,这次读取第一个字符后 结果是:2

如果我插入:"abc",到 .txt 文件中:

但是如果我插入:"abc\n""abc" << endl;:

这是什么原因?

我知道'newline'也是字符。我不明白的是 tellg 读取字符后结果的偏移。每次使用 'newline' 此偏移量都会增加一个。

更新

我猜你是在 Microsoft OS.

上编写代码

在文本文件中,Microsoft OSes(和相关软件)希望行尾用 \r\n 序列标记,因此当您将换行符写入 ( text) 文件,它将从 \n 翻译成 \r\n。因此,即使您只将一个字符插入到流中,也会导致将两个字符写入外部文件。

如果您关心确保外部文件的内容与您插入到流中的内容完全匹配,这可能表明您想要 C++ 标准库认为的二进制文件,您会得到通过在打开文件时指定 std::ios::binary

现在,当您处理文本文件时,tellg 确实不会产生非常有意义的数字。我们拥有的是这样的:

上面是你看到的数据。下方是存储在文件中的数据。当您调用 tellg 时,它会告诉您下方的位置,即相对于文件开头的位置。但是,根据文件中之前有多少 \r\n 对,这可能会导致上行中的字符数不同,这就是您从文件中读取数据时会看到的内容。

这意味着 tellg 的结果只能以几种相当具体的方式使用——大多数情况下,当你从 tellg 得到一个数字时,你可以将该数字返回给 seekg,然后从同一个地方。

就您的代码而言,我想我不明白您的问题在说什么。我稍微重写了代码以一起显示结果:

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>

using namespace std;

std::string show(char x) {
    if (x > 32)
        return std::string(1, x);
    else switch (x) {
    case '\r': return "<\r>";
    case '\n': return "<\n>";
    case '\t': return "<\t>";
    default: return "<BAD>";
    }
}

void display_txt_file(fstream& file)
{
    file.seekg(0, ios_base::beg);
    char x;
    cout << "tellg: " << file.tellg() << "| ";
    while (file.get(x))
    {
        cout << "'" << show(x) << "' tellg: " << file.tellg() << "| ";
    }
    file.clear();
    file.seekg(0, ios_base::end);
    std::cout << "\n";
//    cout << "\n> " << file.tellg() << "\n" << endl;
}

int main(int argc, char* argv[])
{
    ofstream new_file;
    new_file.open("test.txt");
    new_file.close();

    fstream file("test.txt", ios::in | ios::out);
    if (!file.is_open())
    {
        cout << "error file not opened" << endl;
        return 0;
    }

    file << "ABCD";
    display_txt_file(file);

    file.seekp(0);

    file << "ABCD\nE";
    display_txt_file(file);

    return 0;
}

当我在 Windows 上 运行 时,我得到以下输出:

tellg: 0| 'A' tellg: 1| 'B' tellg: 2| 'C' tellg: 3| 'D' tellg: 4|
tellg: 0| 'A' tellg: 1| 'B' tellg: 2| 'C' tellg: 3| 'D' tellg: 4| '<\n>' tellg: 6| 'E' tellg: 7|

因此,换行符之前的所有内容都符合我们的预期。然后换行符扩展为两个字符,然后是 E。但是,在我们阅读 'A' 之后,tellg 返回了 1,而不是问题中声称的 2

如果没有对您的期望的解释和完整的代码清单,很难说出您要解决什么问题或为什么要解决任何问题。

但是,在读取和写入文件时了解字符编码很重要。

换行符占一个字节。如果我们使用 ASCII 字符集,它的值为 0x0A。除了 ASCII,还有其他字符编码。例如,还有 UTF-8 或 UTF-16 编码。对于可读文本字符以及不可读文本字符(例如换行符),每种字符编码可能具有不同的字节或多字节表示形式。

在 Windows 上,约定使用回车符 return 后跟换行符,而不仅仅是换行符。这两个字节在 ASCII 中看起来像 0x0D、0x0A。在 *nix 系统上没有这样的约定。

因此,当您计算 fstream 中的字节数时,您将需要考虑换行符占用一个字节,如果您期望 '\r\n',则需要占用两个字节,也就是说,如果您正在使用 ASCII 编码。

据我所知,fstream 假设它的内容是 ASCII。 C++17 可能改变了这一点。我认为有计划在流中支持各种字符编码。那些最前沿的人也许可以发表评论。

您的操作系统在其配置的某处设置了默认字符编码。我知道旧的 Windows 机器使用 Windows-1252。我不确定 Windows 10 的用途。我认为大多数 *nix 系统都使用 UTF-8。无论如何,您需要查阅操作系统的配置。

当您读取和写入文件时,C++ 流将希望从一种流转换为另一种流。将文本转换为字节表示是流试图为您做的事情的重要组成部分。

如果您不想要流将提供的字节表示形式,那么您可以随意以二进制模式自行编写字节。但是,请注意这会如何影响文件的其他读者以及他们期望的编码。

因此,请记住文件的创建者、文本形式、文件和内存中的二进制表示形式,以及相应的代码。

幸运的是,一些编码还包含整个 ASCII 字符集,只需对其进行扩展即可。 UTF-8 就是这样一种编码方式。

您可以参考 What's the difference between \n and \r\n? 以了解有关该主题的讨论。

也可以参考Difference between files written in binary and text mode

"标准 C++ IOStreams 和语言环境:高级程序员指南和参考 Angelika Langer 和 Klaus Kreft 合着的书如果你想真正了解你的流的内在和外在是一本好书。

更新

  • 结论:我的IDE设置有一个问题!我一直在使用Code::Blocks。我尝试在 Microsoft Visual 中构建程序 Studio IDE 和 运行 没有问题的痕迹。这确实 并不意味着 Code::Blocks 坏了。这可能是一个问题 我的 Code::Blocks 设置。我不记得换过 任何事物。即使是这样;我,以我的拙见,不 认为你可以偶然改变这种事情是对的。 我对Code::Blocks感到失望。
  • 我的 解决方法:改IDE