为什么 '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 文件中:
- 阅读后"a"
tellg
将给出:1
- 在 "b" 2
之后
- 和"c"之后 3.
但是如果我插入:"abc\n"
或 "abc" << endl;
:
- 阅读后"a"
tellg
会给出2
- 在 "b" 3
之后
- 在 "c" 4
之后
- 新行后的最后5个。
这是什么原因?
我知道'newline'也是字符。我不明白的是 tellg
读取字符后结果的偏移。每次使用 'newline' 此偏移量都会增加一个。
更新
- 结论:
我的 IDE 设置有问题! 我一直在使用Code::Blocks。我尝试 在 Microsoft Visual Studio IDE 中构建程序,并且 它 运行 没有发现任何问题 .这并不意味着 Code::Blocks 已损坏。这可能是我的 Code::Blocks 设置中的一个问题。我不记得改变了什么。即使是这样;以我的愚见,我认为你可以偶然改变这种事情是不对的。我对 Code::Blocks 感到失望。
- 我的解决方案:更改IDE
我猜你是在 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
当我使用 fstream::tellg
时,在用 fstream::get (char)
读取第一个字符后,结果是:1
然后我在第一个字符
之后插入一个'new line'我fstream::seekg
开头:0
当我使用fstream::tellg
时,这次读取第一个字符后
结果是:2
如果我插入:"abc"
,到 .txt 文件中:
- 阅读后"a"
tellg
将给出:1 - 在 "b" 2 之后
- 和"c"之后 3.
但是如果我插入:"abc\n"
或 "abc" << endl;
:
- 阅读后"a"
tellg
会给出2 - 在 "b" 3 之后
- 在 "c" 4 之后
- 新行后的最后5个。
这是什么原因?
我知道'newline'也是字符。我不明白的是 tellg
读取字符后结果的偏移。每次使用 'newline' 此偏移量都会增加一个。
更新
- 结论: 我的 IDE 设置有问题! 我一直在使用Code::Blocks。我尝试 在 Microsoft Visual Studio IDE 中构建程序,并且 它 运行 没有发现任何问题 .这并不意味着 Code::Blocks 已损坏。这可能是我的 Code::Blocks 设置中的一个问题。我不记得改变了什么。即使是这样;以我的愚见,我认为你可以偶然改变这种事情是不对的。我对 Code::Blocks 感到失望。
- 我的解决方案:更改IDE
我猜你是在 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