中文编码杂谈

编码问题的例子

在windows自带的notepad记事本程序中输入“联通”两个字保存后再次打开会发现“联通”不见了代之以“ͨ”的乱码。这是 windows平台上典型的中文编码问题。即文件保存的时候是按照ANSI编码其实就是GB2312后面会详细介绍保存打开的时候程序按照 UTF-8方式对内容解释于是就出现了乱码。避免乱码的方式很简单在“文件”菜单中选择“打开”命令选择保存的文件然后选择“ANSI”编码此 时就能看到久违的“联通”两个字了。

在Linux平台上如果使用cat等命令查看文件中的中文内容时可能出现乱码。这也是编码的问题。简单的说是文件时按照A编码保存但是cat命令按照当前Locale设定的B编码去查看在B和A不兼容的时候就出现了乱码。

为什么写这篇文章

中文编码由于历史原因牵扯到不少标准在不了解的时候感觉一头雾水但其实理解编码问题并不需要你深入了解各个编码标准只要你明白了来龙去脉了 解了关键的知识点就能分析和解决日常开发工作中碰到的大部分编码问题。有感于我看过的资料和文章要么不够全面要么略显枯燥所以通过这篇文章记录下笔 者在日常工作中碰到的中文编码原理相关问题目的主要是自我总结如果能给读者提供一些帮助那就算是意外之喜了。由于严谨的编码标准对我来说是无趣的枯 燥的难以记忆的本文尝试用浅显易懂的生活语言解释中文编码相关的也可能不相关的一些问题这也是为什么取名杂谈的原因。本文肯定存在不规范不全面 的地方我会在参考资料里给出官方文档的链接也欢迎读者在评论中提出更好的表达方式&指出错误不胜感激。

对编码问题的理解我认为分为三个层次第一个层次概念知道各个编码标准的应用场景了解之间的差异能分析和解决常见的一些编码问题。第二个层 次标准掌握编码的细节如编码范围编码转换规则知道这些就能自行开发编码转换工具。第三个层次使用了解中文的编码2进制存储在程序开发过程 中选择合理的编码并处理中文。为了避免让读者陷入编码标准的黑洞无法脱身不相信看看unicode的规范就明白我的意思了同时由于编码查 询&转换工具等都有现成工具可以使用本文只涉及第一个层次不涉及第二层次在第三层次上会做一些尝试。在本文的最后提供了相关链接供对标准细 节感兴趣的同学继续学习。最后本文不涉及具体软件的乱码问题解决如sshshellvimscreen等这些话题留给剑豪同学专文阐述。

一切都是因为电脑不识字

电脑很聪明可以帮我们做很多事情最开始主要是科学计算这也是为什么电脑别名计算机。电脑又很笨在她的脑子里只有数字即所有的数据在存储和 运算时都要使用二进制数表示。这在最初电脑主要用来处理大量复杂的科学计算时不是什么大问题但是当电脑逐步走入普通人的生活时情况开始变遭了。办公自动 化等领域最主要的需求就是文字处理电脑如何来表示文字呢这个问题当然难不倒聪明的计算机科学家们用数字来代表字符呗。这就是“编码”。

英文的终极解决方案ASCII

每个人都可以约定自己的一套编码只要使用方之间了解就ok了。比如说咱俩约定0×10表示a0×11表示b。在一开始也的确是这样的出现了各 式各样的编码。这样有两个问题1.各个编码的字符集不一样有的多有的少。2.相同字符的编码也不一样。你这里a是0×10.他那里a可能是 0×30。于是你保存的文件他就不能直接用必须要转换编码。随着沟通范围的扩大采用不同编码的人们互相通信就乱套了这就是我们常说的鸡同鸭讲。如 果要避免这种混乱那么大家就必须使用相同的编码规则于是美国有关的标准化组织就出台了ASCIIAmerican Standard Code for Information Interchange编码统一规定了英文常用符号用哪些二进制数来表示。ASCII是标准的单字节字符编码方案用于基于文本的数据。

ASCII最初是美国国家标准供不同计算机在相互通信时用作共同遵守的西文字符编码标准已被国际标准化组织International Organization for Standardization, ISO定为国际标准称为ISO 646标准。适用于所有拉丁文字字母。ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码使用7 位二进制数来表示所有的大写和小写字母数字0 到9、标点符号 以及在美式英语中使用的特殊控制字符。而最高位为1的另128个字符80H—FFH被称为“扩展ASCII”一般用来存放英文的制表符、部分音标字 符等等的一些其它符号。

其中031127(33)是控制字符或通信专用字符其余为可显示字符32126(共95个)是字符(32是空格其中4857为0到9十个阿拉伯数字6590为26个大写英文字母97122号为26个小写英文字母其余为一些标点符号、运算符号等。

ascii 编码表

现在所有使用英文的电脑终于可以用同一种编码来交流了。理解了ASCII编码其他字母型的语言编码方案就触类旁通了。

一波三折的中文编码

第一次尝试GB2312

ASCII这种字符编码规则显然用来处理英文没有什么问题它的出现极大的促进了信息在西方尤其是美国的传播和交流。但是对于中文常用汉字就有 6000以上ASCII 单字节编码显然是不够用。为了粉碎美帝国主义通过编码限制中国人民使用电脑的无耻阴谋中国国家标准总局发布了GB2312码即中华人民共和国国家汉字信 息交换用编码全称《信息交换用汉字编码字符集——基本集》1981年5月1日实施通行于大陆。GB2312字符集中除常用简体汉字字符外还包括希腊 字母、日文平假名及片假名字母、俄语西里尔字母等字符未收录繁体中文汉字和一些生僻字。 EUC-CN可以理解为GB2312的别名和GB2312完 全相同。

GB2312是基于区位码设计的在区位码的区号和位号上分别加上A0H就得到了GB2312编码。这里第一次提到了“区位码”我就连带把下面这几个让人摸不到头脑的XX码一锅端了吧

区位码国标码交换码内码外码

区位码就是把中文常用的符号数字汉字等分门别类进行编码。区位码把编码表分为94个区每个区对应94个 位每个位置就放一个字符汉字符号数字都属于字符。这样每个字符的区号和位号组合起来就成为该汉字的区位码。区位码一般用10进制数来表示如 4907就表示49区7位对应的字符是“学”。区位码中01-09区是符号、数字区16-87区是汉字区10-15和88-94是未定义的空白区。 它将收录的汉字分成两级第一级是常用汉字计3755个置于16-55区按汉语拼音字母/笔形顺序排列第二级汉字是次常用汉字计3008个置于 56-87区按部首/笔画顺序排列。在网上搜索“区位码查询系统”可以很方便的找到汉字和对应区位码转换的工具。为了避免广告嫌疑和死链这里就不举例 了。

国标码 区位码无法用于汉字通信因为它可能与通信使用的控制码00H~1FH即0~31还记得 ASCII码特殊字符的范围吗发生冲突。于是ISO2022规定每个汉字的区号和位号必须分别加上32即二进制数0010000016进制 20H得到对应的国标交换码简称国标码交换码因此“学”字的国标交换码计算为

00110001 00000111
+ 00100000 00100000
-------------------
  01010001 00100111

用十六进制数表示即为5127H。

交换码即国标交换码的简称等同上面说的国标码。

内码由于文本中通常混合使用汉字和西文字符汉字信息如果不予以特别标识就会与单字节的ASCII码混淆。 此问题的解决方法之一是将一个汉字看成是两个扩展ASCII码使表示GB2312汉字的两个字节的最高位都为1。即国标码加上128即二进制数 10000000,16进制80H这种高位为1的双字节汉字编码即为GB2312汉字的机内码简称为内码。20H+80H=A0H。这也就是常说的在 区位码的区号和位号上分别加上A0H就得到了GB2312编码的由来。

00110001 00000111
+ 10100000 10100000
   -------------------
  11010001 10100111

用十六进制数表示即为D1A7H。

外码机外码的简称,就是汉字输入码是为了通过键盘字符把汉字输入计算机而设计的一种编码。 英文输入时相输入什么字符便按什么键外码和内码一致。汉字输入时可能要按几个键才能输入一个汉字。 汉字输入方案有成百上千个但是这千差万别的外码输入进计算机后都会转换成统一的内码。

最后总结一下上面的概念。中国国家标准总局把中文常用字符编码为94个区每个区对应94个位每个字符的区号和位号组合起来就是该字符的区位码, 区位码用10进制数来表示如4907就表示49区7位对应的字符是“学”。 由于区位码的取值范围与通信使用的控制码00H~1FH即0~31发生冲突。每个汉字的区号和位号分别加上32即16进制20H得到国标码交换码。“学”的国标码为5127H。由于文本中通常混合使用汉字和西文字符为了让汉字信息不会与单字节的ASCII码混淆将一个汉字看成是两个扩展ASCII码即汉字的两个字节的最高位置为1得到的编码为GB2312汉字的内码。“学”的内码为D1A7H。无论你使用什么输入法通过什么样的按键组合把“学”输入计算机“学”在使用GB2312以及兼容GB2312编码的计算机里的内码都是D1A7H。

第二次尝试GBK

GB2312的出现基本满足了汉字的计算机处理需要但由于上面提到未收录繁体字和生僻字从而不能处理人名、古汉语等方面出现的罕用字这导致了 1995年《汉字编码扩展规范》GBK的出现。GBK编码是GB2312编码的超集向下完全兼容GB2312兼容的含义是不仅字符兼容而且相同 字符的编码也相同同时在字汇一级支持ISO/IEC10646—1和GB 13000—1的全部中、日、韩CJK汉字共计20902字。GBK还收录了GB2312不包含的汉字部首符号、竖排标点符号等字符。CP936和 GBK的有些许差别绝大多数情况下可以把CP936当作GBK的别名。

第三次尝试GB18030

GB18030编码向下兼容GBK和GB2312。GB18030收录了所有Unicode3.1中的字符包括中国少数民族字符GBK不支持的 韩文字符等等也可以说是世界大多民族的文字符号都被收录在内。GBK和GB2312都是双字节等宽编码如果算上和ASCII兼容所支持的单字节也可 以理解为是单字节和双字节混合的变长编码。GB18030编码是变长编码有单字节、双字节和四字节三种方式。

其实这三个标准并不需要死记硬背只需要了解是根据应用需求不断扩展编码范围即可。从GB2312到GBK再到GB18030收录的字符越来越多 即可。万幸的是一直是向下兼容的也就是说一个汉字在这三个编码标准里的编码是一模一样的。这些编码的共性是变长编码单字节ASCII兼容对其他字符 GB2312和GBK都使用双字节等宽编码只有GB18030还有四字节编码的方式。这些编码最大的问题是2个。1.由于低字节的编码范围和ASCII 有重合所以不能根据一个字节的内容判断是中文的一部分还是一个独立的英文字符。2.如果有两个汉字编码为A1A2B1B2存在A2B1也是一个有效汉 字编码的特殊情况。这样就不能直接使用标准的字符串匹配函数来判断一个字符串里是否包含某一个汉字而需要先判断字符边界然后才能进行字符匹配判断。

最后提一个小插曲上面讲的都是大陆推行的汉字编码标准使用繁体的中文社群中最常用的电脑汉字字符集标 准叫大五码Big5共收录13,060个中文字其中有二字为重覆编码(实在是不应该)。Big5虽普及于中国的台湾、香港与澳门等繁体中文通行 区但长期以来并非当地的国家标准而只是业界标准。倚天中文系统、Windows等主要系统的字符集都是以Big5为基准但厂商又各自增删衍生成多 种不同版本。2003年Big5被收录到台湾官方标准的附录当中取得了较正式的地位。这个最新版本被称为Big5-2003。

天下归一Unicode

看了上面的多个中文编码是不是有点头晕了呢如果把这个问题放到全世界n多个国家n多语种呢各国和各地区自己的文字编码规则互相冲突的情况全球信息交换带来了很大的麻烦。

要真正彻底解决这个问题上面介绍的那些通过扩展ASCII修修补补的方式已经走不通了而必须有一个全新的编码系统这个系统要可以将中文、日 文、法文、德文……等等所有的文字统一起来考虑为每一个文字都分配一个单独的编码。于是Unicode诞生了。Unicode统一码、万国码、单一 码为地球上以后会包括火星金星喵星等每种语言中的每个字符设定了统一并且唯一的二进制编码以满足跨语言、跨平台进行文本转换、处理的要求。在 Unicode里所有的字符被一视同仁汉字不再使用“两个扩展ASCII”而是使用“1个Unicode”来表示也就是说所有的文字都按一个字 符来处理它们都有一个唯一的Unicode码。Unicode用数字0-0x10FFFF来映射这些字符最多可以容纳1114112个字符或者说有 1114112个码位码位就是可以分配给字符的数字。

提到Unicode不能不提UCS通用字符集Universal Character Set。UCS是由ISO制定的ISO 10646或称ISO/IEC 10646标准所定义的标准字符集。UCS-2用两个字节编码UCS-4用4个字节编码。Unicode是由unicode.org制定的编码机 制ISO与unicode.org是两个不同的组织, 虽然最初制定了不同的标准; 但目标是一致的。所以自从unicode2.0开始, unicode采用了与ISO 10646-1相同的字库和字码, ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值, 使得两者保持一致。大家简单认为UCS等同于Unicode就可以了。

在Unicode中汉字“字”对应的数字是23383。在Unicode中我们有很多方式将数字23383表示成程序中的数据包括UTF- 8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写可以翻译成Unicode字符集转换格式即怎样将Unicode定义的数字转换成程序数据。例如“汉字”对应的数字是 0x6c49和0x5b57而编码的程序数据是

BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8编码
WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16编码
DWORD data_utf32[] = {0x6c49, 0x5b57}; // UTF-32编码

这里用BYTE、WORD、DWORD分别表示无符号8位整数无符号16位整数和无符号32位整数。UTF-8、UTF-16、UTF-32分别 以BYTE、WORD、DWORD作为编码单位。“汉字”的UTF-8编码需要6个字节。“汉字”的UTF-16编码需要两个WORD大小是4个字节。 “汉字”的UTF-32编码需要两个DWORD大小是8个字节。根据字节序的不同UTF-16可以被实现为UTF-16LE或UTF- 16BEUTF-32可以被实现为UTF-32LE或UTF-32BE。

下面介绍UTF-8、UTF-16、UTF-32、BOM。

UTF-8

UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下

Unicode编码(16进制)

UTF-8 字节流(二进制)

000000 – 00007F

0xxxxxxx

000080 – 0007FF

110xxxxx 10xxxxxx

000800 – 00FFFF

1110xxxx 10xxxxxx 10xxxxxx

010000 – 10FFFF

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0×00-0x7F之间的字符UTF-8编码与ASCII编码完全相同。UTF-8 编码的最大长度是4个字节。从上表可以看出4字节模板有21个x即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21 位。总结了一下规律UTF-8的第一个字节开始的1的个数代表了总的编码字节数后续字节都是以10开始。由上面的规则可以清晰的看出UTF-8编码克 服了中文编码的两个问题。

例1“汉”字的Unicode编码是0x6C49。0x6C49在0×0800-0xFFFF之间使用3字节模板了1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是0110 1100 0100 1001 用这个比特流依次代替模板中的x得到11100110 10110001 10001001即E6 B1 89。

例2Unicode编码0x20C30在0×010000-0x10FFFF之间使用用4字节模板了11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字不足21位就在前面补00 0010 0000 1100 0011 0000用这个比特流依次代替模板中的x得到11110000 10100000 10110000 10110000即F0 A0 B0 B0。

UTF-16

UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下如果U<0×10000U的UTF-16 编码就是U对应的16位无符号整数为书写简便下文将16位无符号整数记作WORD。中文范围 4E00-9FBF所以在UTF-16编码里中文2个字节编码。如果U≥0×10000我们先计算U’=U-0×10000然后将U’写成二进制形 式yyyy yyyy yyxx xxxx xxxxU的UTF-16编码二进制就是110110yyyyyyyyyy 110111xxxxxxxxxx。

UTF-32

UTF-32编码以32位无符号整数为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。

字节序

根据字节序(对字节序不太了解的同学请参考http://en.wikipedia.org/wiki/Endianness)的不同UTF-16可以被实现为UTF-16LELittle Endian或UTF-16BEBig EndianUTF-32可以被实现为UTF-32LE或UTF-32BE。例如

Unicode编码

UTF-16LE

UTF-16BE

UTF-32LE

UTF-32BE

0x006C49

49 6C

6C 49

49 6C 00 00

00 00 6C 49

0x020C30

43 D8 30 DC

D8 43 DC 30

30 0C 02 00

00 02 0C 30

那么怎么判断字节流的字节序呢Unicode标准建议用BOMByte Order Mark来区分字节序即在传输字节流前先传输被作为BOM的字符”零宽无中断空格”。这个字符的编码是FEFF而反过来的FFFEUTF- 16和FFFE0000UTF-32在Unicode中都是未定义的码位不应该出现在实际传输中。下表是各种UTF编码的BOM

UTF编码

Byte Order Mark

UTF-8

EF BB BF

UTF-16LE

FF FE

UTF-16BE

FE FF

UTF-32LE

FF FE 00 00

UTF-32BE

00 00 FE FF

总结一下ISO与unicode.org都敏锐的意识到只有为世界上每种语言中的每个字符设定统一并且唯一的二进制编码才能彻底解决计算机世界信 息交流中编码冲突的问题。由此诞生了UCS和unicode而这两个规范是一致的。在Unicode里所有的字符被一视同仁也就是说所有的文字都 按一个字符来处理它们都有一个唯一的Unicode码。UTF-8、UTF-16、UTF-32分别定义了怎样将Unicode定义的数字转换成程序数 据。UTF-8以字节为单位对Unicode进行编码一个英文字符占1个字节汉字占3个字节UTF-16以16位无符号整数为单位对Unicode 进行编码中文英文都占2个字节UTF-32以32位无符号整数为单位对Unicode进行编码中文英文都占4个字节。可以在http://www.unicode.org/charts/unihan.html 查看汉字的unicode码以及UTF-8、UTF-16、UTF-32编码。

中文二进制存储

介绍了这么多的编码知识真正的文件内容是什么样子的呢下面我们就通过实验看看在笔者Linux机器上 “中文”这两个字在不同的编码下保存的文件内容。下面是我的实验过程有兴趣的同学可以在自己的机器上重做一下。window平台上的情况类似这里就不赘述了。

实验需要需要使用2个工具

  1. od 查看文件内容http://www.gnu.org/software/coreutils/manual/html_node/od-invocation.html

  2. iconv 编码转换工具http://www.gnu.org/software/libiconv/

汉字

Unicodeucs-210进制表示

Utf-8

Utf-16

Utf32

区位码

GB2312/GBK/GB18030

20013

E4 B8 AD

4E2D

00004E2D

5448

D6D0

25991

E6 96 87

6587

00006587

4636

CEC4

机器环境
os: Red Hat Enterprise Linux AS release 4
Cpu: Intel(R) Xeon(R) CPU
localeLC_ALL=zh_CN.utf-8

//生成utf8编码下的文件
echo –n "中文" > foo.utf8

//检查foo的内容
od -t x1 foo.utf8
0000000 e4 b8 ad e6 96 87

//转换为utf16编码
iconv -f utf-8 -t utf-16 foo.utf8 > foo.utf16

//查看foo.utf16内容
od -t x1 foo.utf16
0000000 ff fe 2d 4e 87 65
Ff fe是BOM还记得吗通过BOM来字节流的字节序其余部分的确是UTF-16LE编码的内容

//转换为utf32编码
iconv -f utf-16 -t utf-32 foo.utf16 > foo.utf32

//查看foo.utf32内容
od -t x1 foo.utf32
0000000 ff fe 00 00 2d 4e 00 00 87 65 00 00
Ff fe是BOM的确是UTF-32LE编码的内容

//转换为gb2312编码
iconv -f utf-8 -t gb2312 foo.txt > foo.gb2312
od -t x1 foo.gb2312
0000000 d6 d0 ce c4

//转换为GBK编码
iconv -f utf-8 -t gbk foo.txt > foo.gbk
od -t x1 foo.gbk
0000000 d6 d0 ce c4

//转换为GB18030编码
iconv -f utf-8 -t gb18030 foo.txt > foo.gb18030
od -t x1 foo.gb18030
0000000 d6 d0 ce c4

C语言中文处理

先明确一个概念程序内部编码和程序外部编码。程序内部编码指的是中文字符在程序运行时在内存中的编码形式。程序外部编码则是中文字符在存储或者传输时的编码形式。程序外部编码的最直观的例子就是当把中文存储到硬盘文件中时选择的编码。

根据程序内部编码和程序外部编码是否一致C/C++的中文处理有两种常见的方式

  1. 内外编码相同。输入输出时不需要考虑编码转换程序内部处理时把中文字符当做普通的2进制数据流进行处理。

  2. 内外编码不同。输入输出的时候根据应用需要选择合适的编码格式进行编码转换程序内部统一编码处理。

方法1的优点不言而喻由于内外统一不需要进行转换。不足是如果不是C标准库支持的编码方式那么字符串处理函数需要自己实现。比如说标准 strlen函数不能计算中文编码&UTF-8等的字符串长度而需要根据编码标准自行实现。GBK等中文编码除了计算字符串长度的函数外字符 串匹配函数也要自己实现原因看上文中文编码总结。当需要支持的编码格式不断增多时处理函数的开发和维护就需要付出更大的代价。

方法2针对方法1的不足加以改进。在程序内部可以优先选择C标准库支持的编码方式或者根据需要自己实现对某一特定编码格式的完整支持这样任何编码都可以先转换为支持的编码代码通用性比较好。

那么C标准库对中文编码的支持如何呢目前Linux平台一般使用GNU C library内建了对单字节的char和宽字符wchar_t的支持。Char大家都很熟悉了处理中文需要的wchar_t要重点介绍一下。从实现 上来说在linux平台上可以认为wchar_t是4byte的int内部存储字符的UTF32编码。由于标准库已经内建了对wchar_t比较完备的 支持如使用wcslen 计算字符串长度使用wcscmp进行字符串比较等等。所以比较简单的方式是使用上面的方法2同时选择wchar_t作为内 部字符的表示。做到这一点还是比较容易的在输入输出的时候通过mbrtowc/wcrtomb 进行单个字符的内外编码转换以及通过 mbsrtowcs/wcsrtombs 进行字符串的内外编码转换即可。这里需要注意两点

  1. 代码中字符串常量的表示不同。举例说明Char c=’a’; Wchar_t wc=L’中’;

  2. 上面两组函数的转换是依赖locale设置的即locale决定了外部编码的类型。确切的说是LC_CTYPE决定了外部编码的类型。默认情况 下程序启动时使用标准“C”locale而不是LC系列的环境变量指定的。所以需要首先调用下面的函数setlocale (LC_ALL, “”);这样程序就使用了用户通过设置LC系列环境变量选择的Locale。

关于locale的话题比较大这里就不深入了留待下一篇文章吧.

上面的方法很完美是吗不是吗得到这么多的好处不是无代价的最明显的代价就是内存任何一个字符不管中文还是英文如果保持在wchar_t里就需要4个byte就这一个理由就足以限制了这个方案在关注内存使用的应用场景下的使用。

Python的中文处理

对Python来说由于内建unicde的支持所以采用输入输出的时候进行转换内部保持unicode的方式使用是个不错的方案。http://docs.python.org/tutorial/introduction.html#unicode-strings这里作为起点有兴趣的同学自学吧。

编码选择建议

  1. 只有英文毫不犹豫选择内外编码都选择ASCII通用且存储代价小。

  2. 主要存中文对存储大小比较敏感内外部编码根据文字使用范围选择GB2312或者GBK自行实现使用到的字符串处理函数。

  3. 通用性第一处理简单外部选择UTF-8内部可以使用UTF-8或者UTF-32即wchar_t

来源51CTO
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值