如何将非 ASCII 字符导入控制台?

Kit*_*tso 4 c++ c++17

我一直在挠头一段时间,我需要一些帮助。基本上我想要代码做的是将一系列非 ASCII 符号读入一个空的预设数组,我正在打印它们以查看它们是否确实被读取,而它们目前没有被读取。记事本可以很好地显示它们,但由于某种原因,C++ 无法将它们识别为有效字符,强烈建议任何仅与代码有关且不更改计算机内部设置的建议。

char displayCharacters[5] = {};

try {

    instream.open("characters.txt");
    instream >> displayCharacters;
    cout << "Here is the first symbol: " << displayCharacters[4];

} 

catch (exception) {

    cout << "Something went wrong with the file handling.";

}
Run Code Online (Sandbox Code Playgroud)

是的,我已经正确设置了输入流,从 iostream 的导入和使用命名空间 std 中使用了 cout。以下是该文件的内容:

?
 
?
?
?
Run Code Online (Sandbox Code Playgroud)

编辑:如果您需要知道,该文件是 UTF-8。

MTC*_*ter 8

tl;博士;

您需要先解码UTF-8,然后才能对其进行索引。继续阅读以获取比我预期要写的更多的细节……


C++ 流不是编码感知的——它只是一个字节流。例如,这个转储整个 UTF-8 字符串的代码工作得很好:

#include <iostream>
#include <sstream>
#include <string>

int main() {
    // Simulate your `instream` using an `std::stringstream`
    std::stringstream instream;
    // Load the simulated `instream` using a UTF-8 string literal [1]
    instream << u8"?\n \n?\n?\n?\n";
    
    // Print entire `instream`
    std::cout << instream.rdbuf();
}
Run Code Online (Sandbox Code Playgroud)

[1]:https : //en.cppreference.com/w/cpp/language/string_literal

您的问题来自 UTF-8 编码本身。UTF-8 是一种多字节编码。某些字符(特别是 ASCII 字符)被编码为单个字节。例如,字母a被编码为值 97(0x61十六进制)。

让我们来看看您尝试打印的五个字符:

字符 Unicode 代码点 UTF-8 编码 Unicode 名称
? U+2588 0xe2 0x96 0x88 整块
U+20 0x20 空格(没有链接;这只是普通的 ASCII
? U+2580 0xe2 0x96 0x80 上半块
? U+2584 0xe2 0x96 0x84 下半块
? U+2593 0xe2 0x96 0x93 黑暗阴影

UTF-8 编码是这里有趣的部分——这就是这些字符中的每一个如何作为字节序列存储在 UTF-8 编码文件中的方式。对于四个块绘图字符(我们将忽略空格,因为那只是一个单字节字符),编码需要三个字节。

但是如果代码点只有两个字节长,为什么编码需要三个字节呢?

好问题。让我们分解第一个字符:

   0xe2     0x96     0x88
 11100010 10010110 10001000
 AAAA^^^^ BB^^^^^^ BB^^^^^^
Run Code Online (Sandbox Code Playgroud)

二进制文件下方的注释指示编码的工作方式。

由于字符的代码点太大而无法放入单个字节,UTF-8 将其分成多个字节。但是,必须有一种方法来确定字节序列代表单个字符,而不仅仅是更简单的字符序列。这就是字节前缀(A、B 和 C)发挥作用的地方。多字节序列中的第一个字节以1位序列开始,表示编码字符中的总字节数,后跟一个终止符0. 这里我们需要三个字节,所以我们有1110(A)。

其余两个字节的前缀表示它们是连续字节(即它们不应被视为字符的开头)。连续字节的前缀定义为10(B)。

删除这些前缀后,剩余的位(用插入符号 [ ^]标记)被打包和解析以检索编码的代码点。

单字节字符(即从 0 到 127 的基本 US-ASCII 字符平面)只需要 7 位进行编码,因此以0位为前缀表示该字符没有连续字节。

这一切与您的问题有什么关系?

我之前说过“你的问题来自UTF-8编码本身”。嗯,我撒谎了。对不起。您的问题来自尝试将 UTF-8 编码数据作为纯字节序列读取。

使用上面的编码表,让我们看看文件中的原始字节(假设\n每一行都有一个终止):

e2 96 88 0a 20 0a e2 96 80 0a e2 96 84 0a e2 96 93 0a
\--01--/    02    \--03--/    \--04--/    \--05--/
Run Code Online (Sandbox Code Playgroud)

我已经用它们的行号标记了这些字符。

从这个转储中,你可以很容易地看到有问题的代码的输出是什么:

char displayCharacters[5] = {};
std::cout << "Here is the first symbol: " << displayCharacters[4];
Run Code Online (Sandbox Code Playgroud)

这是一个空间!请记住,流不知道文件的编码,因此它只是吐出一个字节序列(charC/C++ 中的 a 只是一个 8 位变量)。您的数组 ( displayCharacters) 包含上面显示的字节序列,因此为其添加下标以获取第四个(零索引)元素返回 byte 0x20

你在这里真的很幸运。将 UTF-8 数据索引为原始字节通常会导致更难看的错误。还记得那些连续字节(开始10)吗?如果您提取并尝试自行打印其中之一,您的终端将不知道如何处理它。与多字节序列(前缀11)的开头类似。

正确索引 UTF-8 字符串很困难。您几乎肯定需要一个库来处理它。

根据相关文件的用途和/或来源,您可能需要考虑固定宽度的编码,例如UTF-32