繁体   English   中英

为什么扩展的ASCII(特殊)字符需要2个字节才能存储?

[英]Why extended ASCII (special) characters take 2 bytes to get stored?

ASCII范围从32到126是可打印的。 127是DEL ,之后被认为是扩展字符

为了检查,它们是如何存储在std::string ,我写了一个测试程序:

int main ()
{
  string s; // ASCII
  s += "!"; // 33
  s += "A"; // 65
  s += "a"; // 97
  s += "â"; // 131
  s += "ä"; // 132
  s += "à"; // 133

  cout << s << endl;  // Print directly
  for(auto i : s)     // Print after iteration
    cout << i;

  cout << "\ns.size() = " << s.size() << endl; // outputs 9!
}

上面代码中可见的特殊字符实际上看起来不同,可以在这个在线示例中看到(在vi中也可见)。

在字符串s ,前3个普通字符按预期获得每个字节1个字节。 接下来的3个扩展字符每个令人惊讶地占用2个字节。

问题

  1. 尽管是ASCII(在0到256的范围内),为什么这3个扩展字符占用2个字节的空间?
  2. 当我们使用基于范围的循环迭代s ,如何计算出对于普通字符,它必须增加1次并且对于扩展字符增加2次!?

[注意:这也可能适用于C和其他语言。]

  1. 尽管是ASCII(在0到256的范围内),为什么这3个扩展字符占用2个字节的空间?

如果将'as ASCII'定义为仅包含[0,256]范围内的字节,那么所有数据都是ASCII:[0,256]与字节能够表示的范围相同,因此所有数据都用字节表示在您的定义下是ASCII。

问题是您的定义不正确,并且您正在错误地查看数据类型的确定方式; 由字节序列表示的数据类型不由这些字节确定。 相反,数据类型是字节序列外部的元数据 (这并不是说不可能检查一个字节序列并在统计上确定它可能是什么类型的数据。)

让我们检查一下你的代码,记住上面的内容。 我从两个版本的源代码中获取了相关的片段:

s += "â"; // 131
s += "ä"; // 132

s += "â"; // 131
s += "ä"; // 132

您将这些源代码片段视为在浏览器中呈现的文本,而不是原始二进制数据。 你把这两件事作为“相同的”数据提出来了,但事实上它们并不相同。 上图是两个不同的字符序列。

然而,这两个文本元素序列有一些有趣的东西:当使用某种编码方案编码成字节时,其中一个字节用与文本元素的另一个序列相同的字节序列表示,当该序列被编码为字节时使用不同的编码方案。 也就是说, 根据编码方案 ,磁盘上相同的字节序列可能代表两个不同的文本元素序列! 换句话说,为了弄清楚字节序列的含义 ,我们必须知道它是什么类型的数据,因此需要使用什么解码方案。

所以这就是可能发生的事情。 在vi你写道:

s += "â"; // 131
s += "ä"; // 132

您的印象是vi将使用扩展ASCII表示这些字符,因此使用字节131和132.但这是不正确的。 vi没有使用扩展的ASCII,而是使用不同的方案(UTF-8)表示这些字符,它恰好使用两个字节来表示每个字符。

稍后,当您在另一个编辑器中打开源代码时,该编辑器错误地认为该文件是扩展的ASCII并显示它。 由于扩展的ASCII为每个字符使用一个字节,因此用两个字节vi代表每个字符,并为每个字节显示一个字符。

最重要的是,您错误的是源代码使用的是扩展ASCII,因此您假设这些字符将由值为131和132的单个字节表示,这是不正确的。

  1. 当我们使用基于范围的循环迭代s时,如何计算出对于普通字符,它必须增加1次并且对于扩展字符增加2次!?

你的程序没有这样做。 您的ideone.com示例中的字符打印正常,因为独立打印出代表这些字符的两个字节可以显示该字符。 这是一个明确的例子: 实例

std::cout << "Printed together: '";
std::cout << (char)0xC3;
std::cout << (char)0xA2;
std::cout << "'\n";

std::cout << "Printed separated: '";
std::cout << (char)0xC3;
std::cout << '/';
std::cout << (char)0xA2;
std::cout << "'\n";

Printed together: 'â'
Printed separated: '�/�'

“ ”字符是遇到无效编码时出现的字符。

如果您正在询问如何编写执行此操作的程序,则答案是使用能够理解所使用编码细节的代码。 要么获得了解UTF-8的库,要么自己阅读UTF-8规范。

您还应该记住,在这里使用UTF-8只是因为这个编辑器和编译器默认使用UTF-8。 如果您使用不同的编辑器编写相同的代码并使用不同的编译器进行编译,则编码可能完全不同; 假设代码是UTF-8可能与先前假设代码是扩展ASCII一样错误。

您的终端可能使用UTF-8编码。 它对ASCII字符使用一个字节,对其他所有字符使用2-4个字节。

C ++源代码的基本源字符集不包括扩展的ASCII字符(ISO / IEC 14882:2011中的参考§2.3):

基本源字符集由96个字符组成:空格字符,表示水平制表符的控制字符,垂直制表符,换页符和换行符,以及以下91个图形字符:

abcdefghijklmnopqrstu vwxyz

ABCDEFGHIJKLMNOPQRSTU VWXYZ

0 1 2 3 4 5 6 7 8 9

_ {} []#()<>%:; * + - / ^&| 〜! =,\\“'

因此,实现必须将源文件中的这些字符映射到基本源字符集中的字符,然后再将它们传递给编译器。 根据ISO / IEC 10646(UCS),它们可能会映射到通用字符名称:

通用字符名称构造提供了一种命名其他字符的方法。

universal-character-name \\ UNNNNNNNN指定的字符是ISO / IEC 10646中的字符短名称为NNNNNNNN的字符; 通用字符名称\\ uNNNN指定的字符是ISO / IEC 10646中字符短名称为0000NNNN的字符。

窄字符串文字中的通用字符名称(如您的情况)可以使用多字节编码映射到多个字符(ISO / IEC 14882:2011中的参考§2.14.5):

在窄字符串文字中,由于多字节编码,通用字符名称可能映射到多个char元素。

这就是你最后三个角色所看到的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM