目录
问题
字符集
字符编码
字符集与字符编码的关系
多种字符编码存在的意义
字符编码的发展历史
活动代码页
c++的多字节字符与宽字节字符
c++的多字节字符串与宽字节字符串
C++程序输出字符串的编码
字符串常量
参考文章
字符集和编码往往是IT菜鸟甚至是各种大神的头痛问题。当遇到纷繁复杂的字符集,各种火星文和乱码时,问题的定位往往变得非常困难。本文就将会从原理方面对字符集和编码做个简单的科普介绍,同时也会介绍C++中设置字符串编码以及编码转换的一些方法。
简单的说,字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。常见的字符集如ASCII、GB2312、GBK等。
下面就是屌
这个字在各种编码下的十六进制和二进制编码结果
字符集只是一个规则集合的名字,对应到真实生活中,字符集就是对某种语言的称呼。例如:英语,汉语,日语。
对于一个字符集来说要正确编码转码一个字符需要三个关键元素:字库表、编码字符集、字符编码。
字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。
编码字符集,即用一个编码值来表示一个字符在字库中的位置。
字符编码定义编码字符集和实际存储数值之间的转换关系。一般来说,都会直接将字符的编码值直接进行存储。例如在ASCII中A
在表中排第65位,而编码后A
的数值是0100 0001
也即十进制的65的二进制转换结果。
一般一个字符集等同于一个编码方式,ANSI体系的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我们说一种编码都是针对某一特定的字符集。
一个字符集上也可以有多种编码方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。
既然字库表中的每一个字符都有一个自己的序号,直接把序号作为存储内容就好了。为什么还要多此一举通过字符编码
把序号转换成另外一种存储格式呢?
统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。例如中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。于是就出现了UTF-8这样的变长编码。在UTF-8编码中原本只需要一个字节的ASCII字符,仍然只占一个字节。而像中文及日语这样的复杂字符就需要2个到3个字节来存储。
第一个阶段:ASCII字符集和ASCII编码
计算机刚开始只支持英语(即拉丁字符),其它语言不能够在计算机上存储和显示。ASCII用一个字节(Byte)的7位(bit)表示一个字符,第一位置0。后来为了表示更多的欧洲常用字符又对ASCII进行了扩展,又有了EASCII,EASCII用8位表示一个字符,使它能多表示128个字符,支持了部分西欧字符。
第二个阶段:ANSI编码(本地化)
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。
不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
第三个阶段:UNICODE(国际化)
为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。UNICODE 常见的有三种编码方式:UTF-8(1-4个字节表示)、UTF-16((2个字节表示))、UTF-32(4个字节表示)。
代码页是字符集编码的别名,也有人称"内码表"。
早期,代码页是IBM称呼电脑BIOS本身支持的字符集编码的名称。当时通用的操作系统都是命令行界面系统,这些操作系统直接使用BIOS供应的VGA功能来显示字符,操作系统的编码支持也就依靠BIOS的编码。现在这BIOS代码页被称为OEM代码页。图形操作系统解决了此问题,图形操作系统使用自己字符呈现引擎可以支持很多不同的字符集编码。
早期IBM和微软内部使用特别数字来标记这些编码,其实大多的这些编码已经有自己的名称了。虽然图形操作系统可以支持很多编码,很多微软程序还使用这些数字来点名某编码。
下表列出了部分代码页及其国家(地区)或者语言:
代码页 国家(地区)或语言
437 美国
932 日文(Shift-JIS)
936 中国 - 简体中文(GB2312)
950 繁体中文(Big5)
1200 Unicode
1201 Unicode (Big-Endian)
50220 日文(JIS)
50225 韩文(ISO)
50932 日文(自动选择)
50949 韩文(自动选择)
52936 简体中文(HZ)
65000 Unicode (UTF-7)
65001 Unicode (UTF-8)
C++基本数据类型中表示字符的有两种:char、wchar_t。
char叫多字节字符,一个char占一个字节,之所以叫多字节字符是因为它表示一个字时可能是一个字节也可能是多个字节。一个英文字符(如’s’)用一个char(一个字节)表示,一个中文汉字(如’中’)用2个char(二个字节)表示,看下面的例子。
void TestChar()
{
char ch1 = 's'; // 正确
cout << "ch1:" << ch1 << endl;
char ch2 = '中'; // 错误,一个char不能完整存放一个汉字信息
cout << "ch2:" << ch2 << endl;
char str[3] = "中"; //前二个字节存放汉字'中',最后一个字节存放字符串结束符\0
cout << "str:" << str << endl;
}
字符数组可以表示一个字符串,但它是一个定长的字符串,我们在使用之前必须知道这个数组的长度。为方便字符串的操作,STL为我们定义好了字符串的类string和wstring。大家对string肯定不陌生,但wstring可能就用的少了。
string是普通的多字节版本,是基于char的,对char数组进行的一种封装。
wstring是Unicode版本,是基于wchar_t的,对wchar_t数组进行的一种封装。
###string 与 wstring的相关转换:
以下的两个方法是跨平台的,可在Windows下使用,也可在Linux下使用。
C++程序输出的字符串编码规则如下:
对规则1的证实可以使用如下代码:
#include
int main()
{
using namespace std;
ofstream OutFile("OutInUTF8.txt");
OutFile << "你" << endl;
OutFile.close();
return 0;
}
以上代码在活动代码页为GBK的Window中生成的txt文件的编码是UTF8。
对规则2的证实可以使用如下代码:
#include
int main()
{
using namespace std;
ofstream OutFile("Output.txt");
OutFile << "你" << endl;
OutFile.close();
return 0;
}
上面的程序如果编译时Window的代码页是936(GBK),结果生成的txt文件编码也是GBK。即使将该程序在Window的代码页为65001(UTF8)的Windows系统中运行,生成的txt文件编码依然是GBK。
对规则3的证实:
仍然使用上面的程序。但是其源文件编码设成UTF8,然后在代码页是936(GBK)的Window系统上编译,结果生成的txt文件编码是GBK。
C++98标准中一些表示字符串常量的标识有:
C++11标准中增加了一些表示字符串常量的标识,如下有:
可用以下程序进行验证
#include
int main()
{
using namespace std;
string str;
wstring wstr;
str = "你好";
wstr = "你好"; //编译报错——“初始化”: 无法从“const char [3]”转换为“std::basic_string,std::allocator>”
str = L"你好"; //编译报错——无法从“const wchar_t [2]”转换为“std::basic_string,std::allocator>”
wstr = L"你好";
str = R"(你好)";
wstr = R"(你好)";//编译报错——二进制“=”: 没有找到接受“const char [5]”类型的右操作数的运算符(或没有可接受的转换)
return 0;
}
带你玩转Visual Studio——带你理解多字节编码与Unicode码
十分钟搞清字符集和字符编码
活动代码页简介
Windows:如何设置Windows的默认代码页
C++中wchar_t与wstring理解及中文编码的处理