字符编码

更新时间:2023-12-26 23:35:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

字符编码 ASCII码

ASCII码的取值范围是0~127,可以用7个bit表示。C语言规定 char 型占一个字节,如果存放ASCII码则只用到低7位,高位为0。以下是ASCII码表: ASCII码表 Dec Hx OChar ct c x 20 0 0 NUL (null) 32 0 SOH (start 1 1 1 of heading) STX (start 2 2 2 of text) ETX (end 3 3 3 of text) EOT (end of 4 4 4 transmission) ENQ 5 5 5 (enquiry) 6 6 6 ACK 37 5 38 246 & 245 % 69 5 70 45 10F 410E 1 105 65 14f 10614e 36 4 244 $ 68 4 4 410D 0 4 4 10614d 35 3 34 2 243 # 67 3 3 33 1 242 “ 66 2 42 10C 99 3 3 241 ! 65 1 41 10B 98 2 62 14c 40 k) ct (blan64 0 40 10A 97 1 61 14b DeHOChar c x 4t 10@ 96 0 60 14a ar c x 6t 14` ar DeHOcChDeHOcChASCII码表 Dec Hx OChar ct (acknowledge) 27 7 7 BEL (bell) 39 7 BS 28 8 10 (backspace40 8 ) TAB 29 9 11 (horizontal tab) LF (NL line 210 a 12 feed, new 42 a line) VT (vertical 11 b 13 tab) FF 12 c (NP 214 form feed, 44 c new page) 54 , 76 c 4 411L 8 c 4 10615l 43 b 253 + 75 b 3 411K 7 b 3 10615k 52 * 74 a 2 411J 6 a 2 10615j 41 9 51 ) 73 9 1 411I 5 9 1 10615i 50 ( 72 8 0 411H 4 8 0 10615h 47 ‘ 71 7 7 410G 3 7 7 10614g c x 6 ct DeHOChar c x 6 t 6 ar c 2 x 6 t 6 ar DeHOcChDeHOcChASCII码表 Dec Hx OChar ct CR 213 d 15 (carriage return) SO 14 e 16 out) (shift 46 e 256 . 78 e 6 1115 f 17 SI (shift in) 47 2f 57 / 79 4f 7 DLE (data 116 0 escape) DC1 117 1 control 1) DC2 118 2 control 2) 119 3 23 (device DC3 51 3 363 3 83 3 3 512S 5 3 3 11716s 22 (device 50 2 362 2 82 2 2 512R 4 2 2 11716r 21 (device 49 1 361 1 81 1 1 512Q 3 1 1 11716q 20 link 48 0 360 0 80 0 0 512P 2 0 0 11716p O 1 411N 0 116f 7 e 6 15o 11615n 45 d 55 - 77 d 5 411M 9 d 5 10615m c x ct DeHOChar c x t ar c x t ar DeHOcChDeHOcChASCII码表 Dec Hx OChar ct control 3) DC4 120 4 control 4) NAK 121 5 25 acknowledge) SYN 122 6 us idle) ETB (end 123 7 block) 124 8 125 9 31 medium) 30 (cancel) EM (end of 57 9 CAN 56 8 371 9 89 9 1 370 8 88 8 50 13Y 1 9 1 513X 0 128 70 17y 12717x 27 of trans. 55 7 367 7 87 7 7 512W 9 7 7 11716w 26 (synchrono54 6 366 6 86 6 6 512V 8 6 6 11716v (negative 53 5 365 5 85 5 5 512U 7 5 5 11716u 24 (device 52 4 364 4 84 4 4 512T 6 4 4 11716t c x ct DeHOChar c x t ar c x t ar DeHOcChDeHOcChASCII码表 Dec Hx 126 a 127 b 128 c 129 d 35 separator) RS 130 e separator) US 31 1f 37 separator) (unit 63 3f 77 ? 95 5f 7 13_ 7 127f 7 L 17DE36 (recored 62 e 376 > 94 e 6 513^ 6 e 6 12717~ 34 separator) GS (group 61 d 33 (escape) FS (file 60 c 375 = 93 d 5 32 (substitute) ESC 59 b 374 < 92 c 54 13] 5 d 5 OChar ct SUB 58 a 373 ; 91 b 53 13\\ 4 12c 74 17} c x 372 : 90 a 52 13[ 3 12b 73 17| ct DeHOChar c x 5t 13Z 2 12a 72 17{ ar c 12x 7t 17z ar DeHOcChDeHOcCh绝大多数计算机的一个字节是8位,取值范围是0~255,而ASCII码并没有规定编号为128~255的字符,为了能用一个字节表示更多的字符,各厂商制定了很多种ASCII码的扩展规范。注意,虽然通常把这些规范称为扩展ASCII码(Extended ASCII),但其实它们并不属于ASCII码标准。例如下面这种扩展ASCII码由IBM制定,在字符终端下被广泛采用,其中包含了很多表格边线字符用来画界面。

IBM的扩展ASCII码表

在图形界面下最广泛使用的扩展ASCII码是ISO-8859-1,也称为Latin-1,其中包含欧洲各国语言中最常用的非英文字母,但毕竟只扩展了128个字符,一些不常用的字母就没有包含进来。如下表所示。 ISO-8859-1 160 176 ° 192 à 208 D 224 à 240 è 161 ? 177 ± 193 á 209 ? 225 á 241 é 162 ¢ 178 2 163 £ 179 3 164 ¤ 180 ′ 194 ? 210 ò 226 ? 242 ò 195 ? 211 ó 227 à 243 ó 196 ? 212 ? 228 á 244 ê 165 ¤ 181 μ 197 ? 213 ? 229 a 245 ? 166 ¥ 182 ? 198 ? 214 ? 230 ? 246 ì 167 § 183 · 168 ¨ 184 ? 169 | 185 1 170 a 186 o 199 ? 215 × 231 ? 247 ÷ 200 è 216 × 232 è 248 í 201 é 217 ? 233 é 249 ù 202 ê 218 ù 234 ê 250 ú 171 ? 187 ? 203 ? 219 ú 235 ? 251 ? 172 ? 188 ? 204 ì 220 ? 236 ì 252 ü 173 174 ? 190 ? 206 ? 222 Y 238 ? 254 e 175 ˉ 191 ? 207 ? 223 T 239 ? 255 ? 编号128~159的是一些控制字符,在上表中没有列出。

189 ? 205 í 221 ü 237 í 253 ? Unicode和UTF-8

为了在一套编码中涵盖全世界各国语言文字和专业领域符号(例如数学符号、乐谱符号),ISO制定了ISO 10646标准,也称为UCS(Universal Character Set)。UCS编码的长度是31位,可以表示231 个字符。如果两个字符编码的高位相同,只有低16位不同,则它们属于同一个平面(Plane),所以一个平面由216 个字符组成。目前绝大多数常用字符都位于第一个平面(编码范围是0x0000~0xFFFF),称为BMP(Basic Multilingual Plane)或Plane 0,为了向后兼容,其中编号为0~256的字符和ASCII码以及Latin-1相同。UCS编码通常用U-xxxxxxxx这种形式表示,而BMP的编码通常用U+xxxx这种形式表示,其中x是十六进制数字。在ISO制定UCS的同时,另一个厂商联合组织也在着手制定这样的编码,称为Unicode,后来两家联手制定统一的编码,但各自发布各自的标准文档,所以UCS编码和Unicode码是相同的。

有了字符编码,另一个问题就是这样的编码在计算机中怎么表示。现在已经不可能用一个字节表示一个字符了,最直接的想法就是用四个字节表示一个字符,这种表示方法称为UCS-4或UTF-32,UTF是Unicode Transformation Format的缩写。这样表示显然比较浪费存储空间,如果表示BMP字符,4个字节中的两个高位字节都是0,如果表示ASCII或Latin-1字符,4个字节中的3个高位字节都是0,而我们常用的绝大多数字符都在BMP、ASCII或Latin-1字符集中。另一种比较节省存储空间的办法是用两个字节表示一个字符,称为UCS-2或UTF-16,这样只能表示BMP中的字符,但BMP中有一些控制字符用于扩展,可以用两个这样的控制字符表示其他平面的字符,称为Surrogate Pair。

无论是UTF-32还是UTF-16都有一个更严重的问题就是和C语言不兼容,在C语言中字节0表示字符串结尾,库函数 strlen 、 strcpy 等等都依赖于这一点,如果字符串用UTF-32或UTF-16存储,其中有很多字节0并不表示字符串结尾,那就乱套了。

UNIX之父Ken Thompson提出的UTF-8编码很好地解决了这个问题,因此得到广泛应用。

和UTF-16、UTF-32不同的是,UTF-8编码的长度不固定,每个字符用1~6个字节表示。UTF-8编码具有以下性质:

? ?

编码为U+0000~U+007F的字符只占一个字节,就是0x00~0x7F,和ASCII码兼容。 编码大于U+007F的字符用2~6个字节表示,每个字节的最高位都是1,而所有ASCII码的最高位都是0,因此非ASCII码字符的UTF-8编码中不会出现ASCII码的字节(也不会出现字节0)。

? 在非ASCII码字符的多字节编码中,第一个字节的取值范围是0xC0~0xFD,根据第一个字节可以判断后面还有几个字节也属于当前字符的编码,后面每个字节的取值范围都是0x80~0xBF,详见下面的编码格式。

? ? ?

所有Unicode字符(共231个)都可以用UTF-8编码表示出来。 UTF-8编码最长6个字节,BMP字符的UTF-8编码最长三个字节。 0xFE和0xFF这两个字节在UTF-8编码中不会出现。

具体来说,UTF-8编码有以下几种格式: U-00000000 – U-0000007F: 0xxxxxxx

U-00000080 – U-000007FF: 110xxxxx 10xxxxxx

U-00000800 – U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx

U-00010000 – U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

U-00200000 – U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

U-04000000 – U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

? ?

ASCII码字符的UTF-8编码只有一个字节(就是ASCII码本身),最高位是0。 非ASCII码字符的第一个字节最高位是1,并且后面至少还要跟一个1,最高位后面跟几个1就表示后面还有几个字节也属于当前字符的编码,例如111110xx,最

高位后面跟4个1,表示后面还有4个字节也属于当前字符的编码。

?

后面每个字节的最高两位都是10,而第一个字节的最高两位要么是0x,要么是11,因此可以和后面的字节区分开。这样的设计有利于误码同步,例如在网络传输过程中丢失了几个字节,很容易判断当前字符是不完整的,也很容易找到下一个字符应该从哪开始,顶多丢掉一两个字符就可以同步了,而不会导致后面的解码过程全部错乱。

? 上面的格式中标为x的位就是字符的Unicode码,最后一种6字节的格式中x位有31个,可以表示31位的Unicode码。UTF-8编码就像一列火车,第一个字节是车头,后面每个字节是车厢,其中承载的货物是Unicode码。UTF-8规定承载的Unicode码以大端表示,就是说第一个字节中的x位是Unicode码的高位,后面字节中的x位是Unicode码的低位。

? UTF-8规定每个字符必须用尽可能少的字节来编码,换句话说,在符合编码格式的前提下,Unicode码从最高位开始的0位要尽可能少。

举例来说,U+00A9(|字符)的二进制是10101001,编码成UTF-8是11000010 10101001(0xC2 0xA9),但不能编码成11100000 10000010 10101001。 在Linux C编程中使用Unicode和UTF-8

目前各种Linux发行版都支持UTF-8编码,在磁盘上保存一个含有非ASCII字符的文本文件,默认是以UTF-8编码的。当前系统的字符编码设置可以用 locale 命令查看: $ locale

LANG=en_US.UTF-8 LANGUAGE=

LC_CTYPE=\LC_NUMERIC=\LC_TIME=\

LC_COLLATE=\LC_MONETARY=\LC_MESSAGES=\LC_PAPER=\LC_NAME=\LC_ADDRESS=\LC_TELEPHONE=\LC_MEASUREMENT=\LC_IDENTIFICATION=\LC_ALL=

Locale定义了语言、字符编码、日期时间格式、数字格式、货币格式等参数,详见 locale(1) 、 locale(5) 、 locale(7) 。 locale 命令列出的这些参数每一个都可以用环境变量单独设置,但通常这些参数的取值是一致的。在我的系统中只设置了环境变量 LANG ,其他参数没有设置,用 locale 命令可以看到其他Locale参数也继承了环境变量 LANG 的值。

常用汉字都位于BMP中,所以一个汉字的UTF-8编码通常是3个字节。例如编辑一个C程序:

1 #include 2

3 int main(void) 4 {

5 printf(\你好\\n\6 return 0;

7 }

源文件是以UTF-8编码存储的: $ hexdump -C nihao.c

00000000 23 69 6e 63 6c 75 64 65 20 3c 73 74 64 69 6f 2e |#include ..int main(voi| 00000020 64 29 0a 7b 0a 09 70 72 69 6e 74 66 28 22 e4 bd |d).{..printf(\00000030 a0 e5 a5 bd 5c 6e 22 29 3b 0a 09 72 65 74 75 72 |....\\n\00000040 6e 20 30 3b 0a 7d 0a |n 0;.}.| 00000047

其中 e4 bd a0 这三个字节就是“你”的UTF-8编码, e5 a5 bd 这三个字节就是“好”的UTF-8编码。把它编译成目标文件,”你好n”这个字符串就成了这样一串字节: e4 bd a0 e5 a5 bd 0a 00 ,转义序列由两个字节变成一个字节,字符串末尾添了一个字节0,而汉字仍然占3个字节,在C标准中多字节编码的字符称为Multibyte Character。运行这个程序会把这一串字节输出到当前终端设备,如果当前终端能够识别UTF-8编码(比如图形界面的终端窗口)就能打印出汉字,如果不能识别UTF-8编码(比如一般的字符终端)就打印不出汉字。也就是说,在这个程序中识别汉字的工作既不是由C编译器做的也不是由 printf 函数做的,C编译器原封不动地把源文件中的UTF-8编码复制到目标文件中,printf 函数再把这一串字节当作以Null结尾的字符串原封不动地输出到终端设备,识别汉字的工作是由终端设备做的。

仅有这种程度的汉字支持是不够的,有时候我们需要在C程序中操作字符串里的字符,比如求字符串”你好n”中有几个汉字或字符,用 strlen 就不灵了,因为 strlen 求的是字节数而不是字符数。为了在程序中操作Unicode字符,C标准定义了宽字符(Wide Character)

类型 wchar_t (使用这个类型名需包含头文件 wchar.h )。在字符常量或字符串字面值前面加一个 L 就表示宽字符常量或宽字符串,例如定义 wchar_t c = L'你'; ,变量 c 的值就是汉字“你”的31位Unicode码,而 L\你好\\n\ 就相当于数组 wchar_t str[] = { L'你', L'好', L'\\n', 0 }; 。C标准还定义了一些操作宽字符串的库函数,例如 wcslen 函数可以取宽字符串中的字符个数。

注意Wide Character和Multibyte Character这两个概念的区别:

1. C标准没有规定Wide Character和Multibyte Character应该采用什么编码,但目前

各种Linux发行版的Wide Character都采用Unicode码,Multibyte Character都采用UTF-8编码。

2. 每个Wide Character有固定的长度,用 wchar_t 类型来表示。而每个Multibyte

Character的长度不固定,没有规定一种类型来表示Multibyte Character。 3. Wide Character中可能包含字节0,所以不能保存在普通的以Null结尾的字符串中,

而必须保存在宽字符串中。而Multibyte Character中除了Null字符外不允许出现字节0,因此可以保存在以Null结尾的字符串中。

4. Wide Character适合做字符运算,比如统计字符数,而Multibyte Character适合做存储和传输,存储时比较节省空间,传输时有较好的容错性。 看下面的例子: 1 2 3 4 5 6

#include #include

int main(void) {

if (!setlocale(LC_CTYPE, \

7 8 9 10 11 12 13

fprintf(stderr, \ \\\n\return 1; }

printf(\你好\\n\return 0; }

宽字符串 L\你好\\n\ 在源代码中当然还是UTF-8编码,但编译器会把它转换成4个Unicode码 0x00004f60 0x0000597d 0x0000000a 0x00000000 保存到目标文件中,按小端存储就是 60 4f 00 00 7d 59 00 00 0a 00 00 00 00 00 00 00 ,用 hexdump 命令查看目标文件可以找到这些字节。

printf 的转换说明 %ls 表示把后面的参数按宽字符串解释,不是见到字节0就结束,而是见到Null字符的Unicode码(4个字节0)才结束,但输出到终端仍然要以Multibyte Character编码输出,这样终端设备才能识别,所以 printf 函数先把宽字符串转换成UTF-8编码的Multibyte Character字符串再输出到终端。我们把输出重定向到文件会看得更清楚: $ gcc main.c $ ./a.out > foo $ hexdump -C foo

00000000 e4 bd a0 e5 a5 bd 0a |.......| 00000007

最后解释一下 setlocale(3) 函数:

1. 虽然当前系统的Locale设置是 \ ,但C程序在启动时各种Locale

参数都设置成默认值 \ ,并不继承当前系统的Locale设置。这样规定是为了代码的可移植性,如果一个C程序不调用 setlocale 函数,那么它不管在什么系统上运行,其各种Locale参数都是 \ ,其中 LC_CTYPE 参数是 \ 表示采用ASCII字符集。

2. 我们调用 setlocale 传的第一个参数是 LC_CTYPE ,它在C语言中被定义成一个

整数常量,第二个参数是设置给它的值。如果第二个参数是空字符串 \ ,则表示采用当前系统的Locale设置, setlocale 函数依次查找环境变量 LC_ALL、 LC_CTYPE 和 LANG ,找到第一个有定义的环境变量就用它的值来设置 LC_CTYPE 。因此,调用 setlocale 函数后 LC_CTYPE 参数的值变成了 \ 。

3. LC_CTYPE 参数影响C语言对宽字符和宽字符串的处理,正因为我们通过这个参

数设置了UTF-8编码, printf 函数才会把宽字符串转换成UTF-8编码的Multibyte Character字符串再输出到终端。上面的程序如果去掉 setlocale 调用,LC_CTYPE 参数的值默认是 \ (采用ASCII字符集),则无法打印出“你好”,因为这两个字符不属于ASCII字符集。

关于Unicode和UTF-8本节只介绍了最基本的概念,部分内容出自 [UnicodeFAQ] ,读者可进一步参考这篇文章。

本文来源:https://www.bwwdw.com/article/6l0x.html

Top