【图书推荐】《Linux C与C++一线开发实践(第2版)》_linux c与c++一线开发实践pdf-CSDN博客
《Linux C与C++一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要 书评 试读】- 京东图书
LinuxC\C++编程技术_夏天又到了的博客-CSDN博客
3.12.1 计算机上的3种字符集
在计算机中,每个字符都要使用一个编码来表示,而每个字符究竟使用哪个编码来表示,取决于使用哪个字符集(charset)。
计算机字符集可归类为3种,单字节字符集(Single-Byte Character System,SBCS)、多字节字符集(Multi-Byte Character System,MBCS)和Unicode字符集(Unicode Character Set,UCS)。
1)单字节字符集
单字节字符集的所有字符都只有一个字节的长度,它是一个理论规范。具体实现时有两种字符集:ASCII字符集和扩展ASCII字符集。
ASCII字符集主要用于美国,它由美国国家标准局(ANSI)颁布,全称是美国国家标准信息交换码(American National Standard Code For Information Interchange),使用7位来表示一个字符,总共可以表示128个字符(0~127),一个字节有8位,有一位不需要用到,因此人们把最高的一位永远设为0,用剩下的7位来表示这128个字符。ASCII字符集包括英文字母、数字、标点符号等常用字符,如字符A的ASCII码是65,字符a的ASCII码是97,字符0的ASCII码是48,字符1的ASCII码是49,具体可以查看ASCII码表。
在计算机刚刚在美国兴起的时候,ASCII字符集中的128个字符够用了,一切应用都是妥妥的。但后来计算机发展到欧洲,欧洲各个国家的字符就多了,128个不够用了,怎么办?人们对ASCII码进行了扩展,因此就有了扩展ASCII字符集,它使用8位表示一个字符,这样表示256个字符,在前面0~127的范围内定义与ASCII字符集相同的字符,后面多出来的128个字符用来表示欧洲国家的一些字符,如拉丁字母、希腊字母等。有了扩展ASCII 字符集,计算机在欧洲的发展也是妥妥的。
2)多字节字符集
随着计算机普及到更多国家和地区(比如东亚和中东),由于这些国家的字符更多,8位的单字节字符集已经不能满足信息交流的需要了。因此,为了能够表示其他国家的文字(比如中文),人们继续扩展ASCII码,即英文字母和欧洲字符为了和扩展ASCII兼容,依然用一个字节表示,而对于其他各国自己的字符(如中文字符),则用两个字节表示。这就是多字节字符集,它也是一个理论规范,具体实现时,各个国家根据自己的语言字符分别各自实现不同的字符集,比如中国实现了GB-2312字符集(后来又扩展出GBK和GB18030),日本实现了JIS字符集,等等。这些具体的字符集虽然不同,但实现依据都是MBCS,即256后面的字符用2个字节表示。
MBCS解决了欧美以外地区的字符表示,但缺点也是明显的。MBCS在保留原有扩展ASCII码(前面256个)的同时,用两个字节来表示各国的语言字符,这就导致占用一个字节和两个字节的ASCII码混在一起,使用起来不方便。例如字符串“你好abc”,字符数是5,而字节数是8(最后还有一个\0)。对于用++或--运算符来遍历字符串的程序员来说,这简直就是噩梦。另外,各个国家、地区各自定义的字符集难免会有交集,因此使用简体中文的软件就不能在日文环境下运行(显示乱码)。
3)Unicode编码
Unicode编码是纯理论的概念,和具体计算机没关系。为了把全世界所有的文字符号都统一进行编码,国际标准化组织(International Standard Organization,ISO)提出了Unicode编码方案,它是可以容纳世界上所有文字和符号的字符编码方案,这个方案规定任何语言中的任一字符都只对应唯一的数字,这个数字被称为代码点(Code Point),或称码点、码位,它用十六进制书写,并加上“U+”前缀,比如,“田”的代码点是U+7530;“A”的代码点是U+0041。再强调一下,代码点是一个理论的概念,和具体的计算机无关。
所有字符及其Unicode编码构成的集合就叫Unicode字符集。早期的版本有UCS-2,它用两个字节编码,最多能表示65535个字符。在这个版本中,每个码点的长度有16位,这样可以用数字0~65535(2的16次方)来表示世界上的字符(当初以为够用了),其中0~127这128个数字表示的字符依旧跟ASCII码完全一样,比如Unicode和ASCII中的数字65,都表示字母“A”;数字97都表示字母“a”。但反过来却是不同的,字符“A”在Unicode中的编码是0x0041,在ASCII中的编码是0x41,虽然它们的值都是97,但编码的长度是不一样的,Unicode码是16位长度,ASCII码是8位长度。
但UCS-2后来不够用了,因此有了UCS-4这个版本。UCS-4用4个字节编码(实际上只用了31位,最高位必须为0),它根据最高字节分成2^7=128个组(最高字节的最高位恒为0,所以有128个)。每个组再根据次高字节分为256个平面(Plane)。每个平面根据第3个字节分为256行(Row),每行有256个码位(Cell)。组0的平面0被称作基本多语言平面(Basic Multilingual Plane,BMP),即范围在U+00000000~U+0000FFFF的码点,如果将UCS-4的BMP去掉前面的两个0字节,就得到了UCS-2(U+0000 ~ U+FFFF)。每个平面有2^16=65536个码位。Unicode计划使用17个平面,一共有17*65536=1114112个码位。在Unicode 5.0.0版本中,已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中,平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000~0xFFFFD和0x100000~0x10FFFD。所谓专用区,就是保留给大家放自定义字符的区域,可以简写为PUA。平面0也有一个专用区,是0xE000~0xF8FF,有6400个码位。平面0的0xD800~0xDFFF共有2048个码位,是一个被称作代理区(Surrogate)的特殊区域。代理区的目的是用两个UTF-16字符表示BMP以外的字符。在介绍UTF-16编码时会介绍。
在Unicode 5.0.0版本中,238605-65534×2-6400-2408=99089,余下的99089个已定义码位分布在平面0、平面1、平面2和平面14上,它们对应着Unicode目前定义的99089个字符,其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080、3419、43253和337个字符。平面2的43253个字符都是汉字。平面0上定义了27973个汉字。
最后归纳总结一下:
(1)在Unicode字符集中的某个字符对应的代码值称作代码点,简称码点,用十六进制书写,并加上“U+”前缀。比如,“田”的代码点是U+7530;“A”的代码点是U+0041。
(2)随着字符越来越多,最初定义的16位(UC2版本)已经不够用了,于是用32位(UC4版本)表示某个字符的代码点,并把所有代码点分成17个代码平面。其中,U+0000 ~ U+FFFF划入基本多语言平面;其余划入16个辅助平面(Supplementary Plane),代码点范围为U+10000 ~ U+10FFFF。
(3)并不是每个平面中的代码点都有对应的字符,有些是保留的,还有些是有特殊用途的。
3.12.2 查看Linux系统的字符集
字符集在Linux系统中的体现形式是一个环境变量,以Ubuntu为例,它查看当前终端使用字符集的方式有以下几种:
第1种查看方式:
# echo $LANG
zh_CN.UTF-8
第2种查看方式:
# env |grep LANG
LANGUAGE=zh_CN:zh
LANG=zh_CN.UTF-8
第3种查看方式:
# export |grep LANG
declare -x LANG="zh_CN.UTF-8"
declare -x LANGUAGE="zh_CN:zh"
第4种查看方式:
# locale
LANG=zh_CN.UTF-8
LANGUAGE=zh_CN:zh
LC_CTYPE="zh_CN.UTF-8"
LC_NUMERIC="zh_CN.UTF-8"
LC_TIME="zh_CN.UTF-8"
LC_COLLATE="zh_CN.UTF-8"
LC_MONETARY="zh_CN.UTF-8"
LC_MESSAGES="zh_CN.UTF-8"
LC_PAPER="zh_CN.UTF-8"
LC_NAME="zh_CN.UTF-8"
LC_ADDRESS="zh_CN.UTF-8"
LC_TELEPHONE="zh_CN.UTF-8"
LC_MEASUREMENT="zh_CN.UTF-8"
LC_IDENTIFICATION="zh_CN.UTF-8"
LC_ALL=
3.12.3 修改Linux系统的字符集
值得注意的是,如果默认语言是en_US.UTF-8,那么在Linux的字符和图形界面下都是无法显示和输入中文的。如果默认语言是中文,比如zh_CN.GB18030或者zh_CN.gb2312,那么字符界面无法显示和输入中文,图形界面可以。
修改Linux系统的字符串的方式有如下两种:
(1)通过直接设置变量的方式修改:
# export LANG=zh_CN.UTF-8
(2)通过修改/etc/default/locale文件的方式控制:
# vim /etc/default/locale
LANG="zh_CN.UTF-8"
LANGUAGE="zh_CN:zh"
3.12.5 C运行时库对Unicode的支持
首先要记住宽字节,即wchar_t类型采用Unicode编码方式,在 Windows 中为UTF-16,在 Ubuntu中默认情况下为UTF-8。
C95标准化了两种表示大型字符集的方法:宽字符(Wide Character,该字符集内每个字符使用相同的位长)以及多字节字符(Multibyte Character,每个字符可以是一到多个字节不等,而某个字节序列的字符值由字符串或流所在的环境背景决定)。自从1994年增补之后,C语言不止提供char类型,还提供wchar_t类型(宽字符),此类型定义在stddef.h头文件中。wchar_t指定的宽字节类型足以表示某个实现版本扩展字符集的任何元素。
在多字节字符集中,每个字符的编码宽度都是不等的,可以是一个字节,也可以是多个字节。源码字符集和运行字符集都可能包含多字节字符。多字节字符可以用于字符的常量、字符串字面值、标识符、注释以及头文件。
C语言本身并没有定义或指定任何编码集合或任何字符集(基本源码字符集和基本运行字符集除外),而是由其实现指定如何编码宽字符,以及要支持什么类型的多字节字符编码机制。
虽然C标准没有支持Unicode字符集,但是许多实现版本使用Unicode转换格式UTF-16和UTF-32来处理宽字符。如果遵循Unicode标准,wchar_t类型至少是16位或32位长,而wchar_t类型的一个值就代表一个Unicode字符。
UTF-8是一个由Unicode Consortium(万国码联盟)定义的实现,可以表示Unicode字符集的所有字符。UTF-8字符所使用的空间大小从1个字节到4个字节都有可能。
多字节字符和宽字符的主要差异在于宽字符占用的字节数目都一样,而多字节字符的字节数目不等,这样的表示方式使得多字节字符串比宽字符串更难处理。比如,即使字符A可以用一个字节来表示,但是要在多字节的字符串中找到此字符,就不能使用简单的字节比对,因为即使在某个位置找到相符合的字节,此字节也不见得是一个字符,它可能是另一个不同字符的一部分。然而,多字节字符相当适合用来将文字存储成文件。
在下面的代码中,我们可以看到wchar_t所占用的字节数。
#include <stdio.h>
#include <wchar.h>
int main()
{
wchar_t ch = 'A'; // ch占用4个字节
printf("sizeof(ch)=%d\n", sizeof(ch));
return 0;
}
在Ubuntu下编译并运行:
# g++ test.cpp -o test
# ./test
sizeof(ch)=4
3.12.6 C++标准库对Unicode的支持
C++标准库中的string也有对应的宽字符版本wstring,但没有提供统一的函数形式,不过可以自己定义一个,例如:
#ifdef _UNICODE
#define tstr wstring
#else
#define tstr string
#endif
然后在程序中使用tstr即可。类似的还有fstream/wfstream、ofstream/wofstream等,都有两个版本。
3.12.7 字符集相关实例
【例3.82】找出char类型的数组里的汉字
(1)打开Visual Studio Code,新建文本文件,输入代码如下:
#include "string.h"
#include "iostream"
using namespace std;
int main(int argc, char* argv[])
{
char sz1[] = "a世界a1a都去asdfad哪啦";
string str;
int i, len = strlen(sz1); // 得到字符数组长度
for (int i = 0; i < len;)
{
if (sz1[i] < 0) // 若为负数,则前后两个字节存的是汉字
{
str.push_back(sz1[i]);
i++;
str.push_back(sz1[i]);
}
i++;
}
cout << str << endl; // 输出找到的汉字
return 0;
}
(2)保存代码为test.cpp,并上传到Linux,在命令行下编译并运行:
# g++ test.cpp -o test
# ./test
世界都去哪啦
注意,若用SecureCRT终端工具,则要把字符集设为“简体中文GB2312”,否则无法正确显示出中文。