读取文件中的结束字符问题

Endline character problem in reading files

提问人:Tomáš Nadrchal 提问时间:9/2/2023 更新时间:9/2/2023 访问量:40

问:

我有一个函数,可以将文件读取到缓冲区,然后通过可变参数模板逐行解析到容器。std::vector<char>

我发现带有字符的缓冲区有问题。\n

这是一个输入文件 (UTF-8): H,1, H,2,

这是十六进制的: 48 2C 31 2C 0D 0A 48 2C 32 2C 0D 0A

H , 1 , ..H , 2 , ..

现在我有了将其导入缓冲区的代码(故意省略了不同的检查):

std::ifstream in ("file.txt");
in.seekg(0, std::ios::end);
std::streampos fileLength = in.tellg();
in.seekg(0, std::ios::beg);
std::vector<char> buf (fileLength);
in.read(&buf[0], fileLength);
in.close ();

但是,如果我以这种方式打印缓冲区(使用相同的原理导入到容器中):

void printHex (std::vector<char>& buffer)
{
    std::size_t noOfChar {buffer.size()};
    std::cout << "Number of chars: " << noOfChar << "\n";
    for (std::size_t i {0}; i < noOfChar; ++i)
    {
        std::cout << buffer[i];
    }
    std::cout << "\n";
    for (std::size_t i {0}; i < noOfChar; ++i)
    {
        sum += (buffer[i]);
        std::cout << std::hex << std::setw(2) << std::setfill('0') 
                  << static_cast<int>(buffer[i]) << " ";
    }
    std::cout << std::endl;
}

我明白了:

字符数: 12 H,1, H,2, 48 2c 31 2c 0a 48 2c 32 2c 0a 00 00

我尝试了使用更多数据的不同输入,很明显,十六进制 0D 0A 仅作为 0A 读入缓冲区。 解决方案可能相对简单,在缓冲区末尾修剪所有多余的 00,以便以后不会被函数解析行所采用。

但我的问题是:

  1. 为什么这样做,“\n”字符将作为 0D 0A 保存到文件中,并作为“\n”字符导入回
  2. 有没有更好的方法通过导入到streamstring缓冲区来处理“\n”?
C++ 导入 缓冲区 IOSTREAM

评论

0赞 Some programmer dude 9/2/2023
读取以文本模式打开的流时,从流读取可能包括换行符转换。最值得注意的是,Windows 换行符序列被转换为单个 .写作时会进行相反的翻译。查找文件不会进行此转换,而是按字节完成。\r\n\n
0赞 Tomáš Nadrchal 9/2/2023
@Someprogrammerdude 我该怎么做?cpPreference 仅显示二进制模式。
1赞 Some programmer dude 9/2/2023
打开文件时的默认模式(如果未提及)是以文本模式打开。您需要文件的完整原始二进制内容吗?然后以二进制模式打开。否则,您可以使用一次性将文件读入向量(无需设置其大小),但请注意,它将使用普通的流提取运算符,因此默认情况下会跳过空格(可以更改)。你到底在做什么?您是否正在尝试读取 CSV(或类似)格式的文件?ios::binarystd::istream_iterator>>
0赞 Tomáš Nadrchal 9/2/2023
@Someprogrammerdude 谢谢,ios::binary 有效。是的,它的 csv 或任何其他类型具有指定的分隔符。首先,我尝试使用 std::getline 逐行读取文件,但之后我决定首先使用缓冲文件和流运算符,我将比较这两种方法。在我看来,使用运算符<< >>比将 std::string 用于非数值更好,因为它可以很容易地用于解析类,因为只需要重载运算符<< >>而不是来自 std::string 的构造函数,这可能是潜在的危险。
1赞 Tomáš Nadrchal 9/2/2023
@Someprogrammerdude 是的,它用于爱好编程,其中程序出于各种原因使用读写,保存或调试状态、数据等。这就是为什么我不想使用一些更大的库,但仍然有相对抽象的方式来存储数据、类等。作为业余程序员,这是我学习事物的方式。

答:

3赞 bumbread 9/2/2023 #1

对于第一个问题,这涉及到 ASCII 换行字符的历史。在 Linux 上,换行符通常由字符 () 表示。在 Windows 上,换行符由 () 字符表示(将光标返回到行的开头,后跟 () 字符(将光标放在同一列下方的行上。'\x0a'\n\x0d\r\x0a\n

在 C++ 中,为了提供抽象,对于文本流,它们会进行隐式转换。当您键入时,它会将其转换为不同的平台或在不同的平台上转换。如果您尝试读取二进制文件,这可能会导致问题。这就是为什么输入流会为要打开的流类型(文本或二进制)使用第二个标志。\n\n\r\n

为了回答第二个问题,只需将 slap 作为第二个参数,这将以二进制形式打开流,并且应该避免隐式行尾转换。std::ios::binary

std::ifstream in("file.txt", std::ios::binary);

如果您尝试将整个文件读入缓冲区并通过查找来获取其长度,我建议以二进制模式打开所有文件:以这种方式获取文本文件的长度是危险的,并且可能导致与读取未初始化内存相关的错误(您正在读取的字符比实际的要少)。

这也意味着您必须手动处理。\r