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

时间:2015-03-10 14:18:17

标签: c++ c++11 utf-8 byte ascii

ASCII范围从32到126是可打印的。 127为DEL,之后被视为extended characters

要检查它们是如何存储在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!
}

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

在字符串s中,前3个普通字符按预期获得1个字节。接下来的3个扩展字符各占2个字节。

问题

  1. 尽管是一个ASCII(在0到256范围内),为什么这3个扩展字符占用2个字节的空间?
  2. 当我们使用基于范围的循环遍历s时,如何确定对于普通字符,它必须增加1次,对于扩展字符增加2次!?
  3. [注意:这也可能适用于C语言和其他语言。]

3 个答案:

答案 0 :(得分:4)

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

如果您定义为ASCII&#39;由于只包含[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次!?
  2.   

您的计划不是这样做的。您的ideone.com示例中的字符打印正常,因为独立打印出代表这些字符的两个字节可以显示该字符。以下是一个明确的示例:live example

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: '�/�'

&#39;�&#39;遇到无效编码时会出现字符。

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

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

答案 1 :(得分:2)

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

答案 2 :(得分:2)

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

  

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

     

a b c d e f g h i j k l m n o p q r s t u v w x y z

     

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

     

0 1 2 3 4 5 6 7 8 9

     

_ {} []#()&lt; &GT; %:; 。 ? * + - / ^&amp; | 〜! =,\“'

因此,实现必须将源文件中的这些字符映射到基本源字符集中的字符,然后再将它们传递给编译器。根据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元素。

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