WinAPI textout函数如何使用阿
我用VB编写时Textout函数输出英文正常,中文乱码,有知道原因的吗?声明是TextoutA,但是用字数*2,英文输出又不正常了...
我用VB编写时 Textout函数输出英文正常,中文乱码,有知道原因的吗?
声明是TextoutA,但是用字数*2,英文输出又不正常了 展开
声明是TextoutA,但是用字数*2,英文输出又不正常了 展开
4个回答
展开全部
这是VC书的摘抄:
Unicode简介
在第一章中,我已经预告,C语言中在Microsoft Windows程式设计中扮演著重要角色的任何部分都会讲述到,您也许在传统文字模式程式设计中还尚未遇到过这些问题。宽字元集和Unicode差不多就是这样的问题。
简单地说,Unicode扩展自ASCII字元集。在严格的ASCII中,每个字元用7位元表示,或者电脑上普遍使用的每字元有8位元宽;而Unicode使用全16位元字元集。这使得Unicode能够表示世界上所有的书写语言中可能用於电脑通讯的字元、象形文字和其他符号。Unicode最初打算作为ASCII的补充,可能的话,最终将代替它。考虑到ASCII是电脑中最具支配地位的标准,所以这的确是一个很高的目标。
Unicode影响到了电脑工业的每个部分,但也许会对作业系统和程式设计语言的影响最大。从这方面来看,我们已经上路了。Windows NT从底层支援Unicode(不幸的是,Windows 98只是小部分支援Unicode)。先天即被ANSI束缚的C程式设计语言通过对宽字元集的支援来支援Unicode。下面将详细讨论这些内容。
自然,作为程式写作者,我们通常会面对许多繁重的工作。我已试图透过使本书中的所有程式「Unicode化」来减轻负担。其含义会随著本章对Unicode的讨论而清晰起来。
字元集简史
虽然不能确定人类开始讲话的时间,但书写已有大约6000年的历史了。实际上,早期书写的内容是象形文字。每个字元都对应於发声的字母表则出现於大约3000年前。虽然人们过去使用的多种书写语言都用得好好的,但19世纪的几个发明者还是看到了更多的需求。Samuel F. B. Morse在1838年到1854年间发明了电报,当时他还发明了一种电报上使用的代码。字母表中的每个字元对应於一系列短的和长的脉冲(点和破折号)。虽然其中大小写字母之间没有区别,但数字和标点符号都有了自己的代码。
Morse代码并不是以其他图画的或印刷的象形文字来代表书写语言的第一个例子。1821年到1824年之间,年轻的Louis Braille受到在夜间读写资讯的军用系统的启发,发明了一种代码,它用纸上突起的点作为代码来帮助盲人阅读。Braille代码实际上是一种6位元代码,它把字元、常用字母组合、常用单字和标点进行编码。一个特殊的escape代码表示后续的字元代码应解释为大写。一个特殊的shift代码允许后续代码被解释为数字。
Telex代码,包括Baudot (以一个法国工程师命名,该工程师死于1903年)以及一种被称为CCITT #2的代码(1931年被标准化),都是包括字元和数字的5位元代码。
美国标准
早期电脑的字元码是从Hollerith卡片(号称不能被折叠、卷曲或毁伤)发展而来的,该卡片由Herman Hollerith发明并首次在1890年的美国人口普查中使用。6位元字元码系统BCDIC(Binary-Coded Decimal Interchange Code:二进位编码十进位交换编码)源自Hollerith代码,在60年代逐步扩展为8位元EBCDIC,并一直是IBM大型主机的标准,但没使用在其他地方。
美国资讯交换标准码(ASCII:American Standard Code for Information Interchange)起始於50年代后期,最后完成於1967年。开发ASCII的过程中,在字元长度是6位元、7位元还是8位元的问题上产生了很大的争议。从可靠性的观点来看不应使用替换字元,因此ASCII不能是6位元编码,但由於费用的原因也排除了8位元版本的方案(当时每位元的储存空间成本仍很昂贵)。这样,最终的字元码就有26个小写字母、26个大写字母、10个数字、32个符号、33个代号和一个空格,总共128个字元码。ASCII现在记录在ANSI X3.4-1986字元集-用於资讯交换的7位元美国国家标准码(7-Bit ASCII:7-Bit American National Standard Code for Information Interchange),由美国国家标准协会(American National Standards Institute)发布。图2-1中所示的ASCII字元码与ANSI文件中的格式相似。
ASCII有许多优点。例如,26个字母代码是连续的(在EBCDIC代码中就不是这样的);大写字母和小写字母可通过改变一位元资料而相互转化;10个数位的代码可从数值本身方便地得到(在BCDIC代码中,字元「0」的编码在字元「9」的后面!)
最棒的是,ASCII是一个非常可靠的标准。在键盘、视讯显示卡、系统硬体、印表机、字体档案、作业系统和Internet上,其他标准都不如ASCII码流行而且根深蒂固。
0- 1- 2- 3- 4- 5- 6- 7-
-0 NUL DLE SP 0 @ P ` p
-1 SOH DC1 ! 1 A Q a q
-2 STX DC2 " 2 B R b r
-3 ETX DC3 # 3 C S c s
-4 EOT DC4 $ 4 D T d t
-5 ENQ NAK % 5 E U e u
-6 ACK SYN & 6 F V f v
-7 BEL ETB ' 7 G W g w
-8 BS CAN ( 8 H X h x
-9 HT EM ) 9 I Y I y
-A LF SUB * : J Z j z
-B VT ESC + ; K [ k {
-C FF FS , < L \ l |
-D CR GS - = M ] m }
-E SO RS . > N ^ n ~
-F SI US / ? O _ o DEL
图2-1 ASCII字元集
国际方面
ASCII的最大问题就是该缩写的第一个字母。ASCII是一个真正的美国标准,所以它不能良好满足其他讲英语国家的需要。例如英国的英镑符号(£)在哪里?
英语使用拉丁(或罗马)字母表。在使用拉丁语字母表的书写语言中,英语中的单词通常很少需要重音符号(或读音符号)。即使那些传统惯例加上读音符号也无不当的英语单字,例如cöoperate或者résumé,拼写中没有读音符号也会被完全接受。
但在美国以南、以北,以及大西洋地区的许多国家,在语言中使用读音符号很普遍。这些重音符号最初是为使拉丁字母表适合这些语言读音不同的需要。在远东或西欧的南部旅游,您会遇到根本不使用拉丁字母的语言,例如希腊语、希伯来语、阿拉伯语和俄语(使用斯拉夫字母表)。如果您向东走得更远,就会发现中国象形汉字,日本和朝鲜也采用汉字系统。
ASCII的历史开始於1967年,此后它主要致力於克服其自身限制以更适合於非美国英语的其他语言。例如,1967年,国际标准化组织(ISO:International Standards Organization)推荐一个ASCII的变种,代码0x40、0x5B、0x5C、0x5D、0x7B、0x7C和0x7D「为国家使用保留」,而代码0x5E、0x60和0x7E标为「当国内要求的特殊字元需要8、9或10个空间位置时,可用於其他图形符号」。这显然不是一个最佳的国际解决方案,因为这并不能保证一致性。但这却显示了人们如何想尽办法为不同的语言来编码的。
扩展ASCII
在小型电脑开发的初期,就已经严格地建立了8位元位元组。因此,如果使用一个位元组来保存字元,则需要128个附加的字元来补充ASCII。1981年,当最初的IBM PC推出时,视讯卡的ROM中烧有一个提供256个字元的字元集,这也成为IBM标准的一个重要组成部分。
最初的IBM扩展字元集包括某些带重音的字元和一个小写希腊字母表(在数学符号中非常有用),还包括一些块型和线状图形字元。附加的字元也被添加到ASCII控制字元的编码位置,这是因为大多数控制字元都不是拿来显示用的。
该IBM扩展字元集被烧进无数显示卡和印表机的ROM中,并被许多应用程式用於修饰其文字模式的显示方式。不过,该字元集并没有为所有使用拉丁字母表的西欧语言提供足够多的带重音字元,而且也不适用於Windows。Windows不需要图形字元,因为它有一个完全图形化的系统。
在Windows 1.0(1985年11月发行)中,Microsoft没有完全放弃IBM扩展字元集,但它已退居第二重要位置。因为遵循了ANSI草案和ISO标准,纯Windows字元集被称作「ANSI字元集」。 ANSI草案和ISO标准最终成为ANSI/ISO 8859-1-1987,即「American National Standard for Information Processing-8-Bit Single-Byte Coded Graphic Character Sets-Part 1: Latin Alphabet No 1」,通常也简写为「Latin 1」。
在Windows 1.0的《Programmer's Reference》中印出了ANSI字元集的最初版本,如图2-2所示。
0- 1- 2- 3- 4- 5- 6- 7- 8- 9- A- B- C- D- E- F-
-0 * * 0 @ P ` p * * ° À Ð à ð
-1 * * ! 1 A Q a q * * ¡ ± Á Ñ á ñ
-2 * * " 2 B R b r * * ¢ ² Â ò â ò
-3 * * # 3 C S c s * * £ ³ Ã ó ã ó
-4 * * $ 4 D T d t * * ¤ ´ Ä ô ä ô
-5 * * % 5 E U e u * * ¥ µ Å õ å õ
-6 * * & 6 F V f v * * ¦ ¶ Æ ö æ ö
-7 * * ' 7 G W g w * * § · Ç * ç *
-8 * * ( 8 H * h * * * ¨ ¸ È ø è ø
-9 * * ) 9 I Y I y * * © ¹ É Ù é ù
-A * * * : J Z j z * * ª º Ê Ú ê ú
-B * * + ; K [ k { * * « » Ë Û ë û
-C * * , < L \ l | * * ¬ ¼ Ì Ü ì ü
-D * * - = M ] m } * * ½ Í Ý í ý
-E * * . > N ^ n ~ * * ® ¾ Î Þ î þ
-F * * / ? * _ o DEL * * ¯ ¿ Ï ß ï ÿ
* - not applicable
图2-2 Windows ANSI字元集(基於ANSI/ISO 8859-1)
空方框表示该位置未定义字元。这与ANSI/ISO 8859-1的最终定义一致。ANSI/ISO 8859-1仅显示了图形字元,而没有控制字元,因此没有定义DEL。此外,代码0xA0定义为一个非断开的空格(这意味著在编排格式时,该字元不用於断开一行),代码0xAD是一个软连字元(表示除非在行尾断开单词时使用,否则不显示)。此外,ANSI/ISO 8859-1将代码0xD7定义为乘号(*),0xF7为除号(/)。Windows中的某些字体也定义了从0x80到0x9F的某些字元,但这些不是ANSI/ISO 8859-1标准的一部分。
MS-DOS 3.3(1987年4月发行)向IBM PC用户引进了内码表(code page)的概念,Windows也使用此概念。内码表定义了字元的映射代码。最初的IBM字元集被称作内码表437,或者「MS-DOS Latin US)。内码表850就是「MS-DOS Latin 1」,它用附加的带重音字母(但 不是 图2-2所示的Latin 1 ISO/ANSI标准)代替了一些线形字元。其他内码表被其他语言定义。最低的128个代码总是相同的;较高的128个代码取决於定义内码表的语言。
在MS-DOS中,如果用户为PC的键盘、显示卡和印表机指定了一个内码表,然后在PC上创建、编辑和列印文件,一切都很正常,每件事都会保持一致。然而,如果用户试图与使用不同内码表的用户交换档案,或者在机器上改变内码表,就会产生问题。字元码与错误的字元相关联。应用程式能够将内码表资讯与文件一起保存来试图减少问题的产生,但该策略包括了某些在内码表间转换的工作。
虽然内码表最初仅提供了不包括带重音符号字母的附加拉丁字元集,但最终内码表的较高的128个字元还是包括了完整的非拉丁字母,例如希伯来语、希腊语和斯拉夫语。自然,如此多样会导致内码表变得混乱;如果少数带重音的字母未正确显示,那么整个文字便会混乱不堪而不可阅读。
内码表的扩展正是基於所有这些原因,但是还不够。斯拉夫语的MS-DOS内码表855与斯拉夫语的Windows内码表1251以及斯拉夫语的Macintosh内码表10007不同。每个环境下的内码表都是对该环境所作的标准字元集修正。IBM OS/2也支援多种EBCDIC内码表。
但等一下,你会发现事情变得更糟糕。
双位元组字元集
迄今为止,我们已经看到了256个字元的字元集。但中国、日本和韩国的象形文字符号有大约21,000个。如何容纳这些语言而仍保持和ASCII的某种相容性呢?
解决方案(如果这个说法正确的话)是双位元组字元集(DBCS:double-byte character set)。DBCS从256代码开始,就像ASCII一样。与任何行为良好的内码表一样,最初的128个代码是ASCII。然而,较高的128个代码中的某些总是跟随著第二个位元组。这两个位元组一起(称作首位元组和跟随位元组)定义一个字元,通常是一个复杂的象形文字。
虽然中文、日文和韩文共用一些相同的象形文字,但显然这三种语言是不同的,而且经常是同一个象形文字在三种不同的语言中代表三件不同的事。Windows支援四个不同的双位元组字元集:内码表932(日文)、936(简体中文)、949(韩语)和950(繁体汉字)。只有为这些国家(地区)生产的Windows版本才支援DBCS。
双字元集问题并不是说字元由两个位元组代表。问题在於一些字元(特别是ASCII字元)由1个位元组表示。这会引起附加的程式设计问题。例如,字串中的字元数不能由字串的位元组数决定。必须剖析字串来决定其长度,而且必须检查每个位元组以确定它是否为双位元组字元的首位元组。如果有一个指向DBCS字串中间的指标,那么该字串前一个字元的位址是什么呢?惯用的解决方案是从开始的指标分析该字串!
Unicode解决方案
我们面临的基本问题是世界上的书写语言不能简单地用256个8位元代码表示。以前的解决方案包括内码表和DBCS已被证明是不能满足需要的,而且也是笨拙的。那什么才是真正的解决方案呢?
身为程式写作者,我们经历过这类问题。如果事情太多,用8位元数值已经不能表示,那么我们就试更宽的值,例如16位元值。而且这很有趣的,正是Unicode被制定的原因。与混乱的256个字元代码映射,以及含有一些1位元组代码和一些2位元组代码的双位元组字元集不同,Unicode是统一的16位元系统,这样就允许表示65,536个字元。这对表示所有字元及世界上使用象形文字的语言,包括一系列的数学、符号和货币单位符号的集合来说是充裕的。
明白Unicode和DBCS之间的区别很重要。Unicode使用(特别在C程式设计语言环境里)「宽字元集」。「Unicode中的每个字元都是16位元宽而不是8位元宽。」在Unicode中,没有单单使用8位元数值的意义存在。相比之下,在双位元组字元集中我们仍然处理8位元数值。有些位元组自身定义字元,而某些位元组则显示需要和另一个位元组共同定义一个字元。
处理DBCS字串非常杂乱,但是处理Unicode文字则像处理有秩序的文字。您也许会高兴地知道前128个Unicode字元(16位元代码从0x0000到0x007F)就是ASCII字元,而接下来的128个Unicode字元(代码从0x0080到0x00FF)是ISO 8859-1对ASCII的扩展。Unicode中不同部分的字元都同样基於现有的标准。这是为了便於转换。希腊字母表使用从0x0370到0x03FF的代码,斯拉夫语使用从0x0400到0x04FF的代码,美国使用从0x0530到0x058F的代码,希伯来语使用从0x0590到0x05FF的代码。中国、日本和韩国的象形文字(总称为CJK)占用了从0x3000到0x9FFF的代码。
Unicode的最大好处是这里只有一个字元集,没有一点含糊。Unicode实际上是个人电脑行业中几乎每个重要公司共同合作的结果,并且它与ISO 10646-1标准中的代码是一一对应的。Unicode的重要参考文献是《The Unicode Standard,Version 2.0》(Addison-Wesley出版社,1996年)。这是一本特别的书,它以其他文件少有的方式显示了世界上书写语言的丰富性和多样性。此外,该书还提供了开发Unicode的基本原理和细节。
Unicode有缺点吗?当然有。Unicode字串占用的记忆体是ASCII字串的两倍。(然而压缩档案有助於极大地减少档案所占的磁碟空间。)但也许最糟的缺点是:人们相对来说还不习惯使用Unicode。身为程式写作者,这就是我们的工作。
宽字元和C
对C程式写作者来说,16位元字元的想法的确让人扫兴。一个char和一个位元组同宽是最不能确定的事情之一。没几个程式写作者清楚ANSI/ISO 9899-1990,这是「美国国家标准程式设计语言-C」(也称作「ANSI C」)通过一个称作「宽字元」的概念来支援用多个位元组代表一字元的字元集。这些宽字元与常用的字元完美地共存。
ANSI C也支援多位元组字元集,例如中文、日文和韩文版本Windows支援的字元集。然而,这些多位元组字元集被当成单位元组构成的字串看待,只不过其中一些字元改变了后续字元的含义而已。多位元组字元集主要影响C语言程式执行时期程式库函式。相比之下,宽字元比正常字元宽,而且会引起一些编译问题。
宽字元不需要是Unicode。Unicode是一种可能的宽字元集。然而,因为本书的焦点是Windows而不是C执行的理论,所以我将把宽字元和Unicode作为同义语。
char资料型态
假定我们都非常熟悉在C程式中使用char资料型态来定义和储存字元跟字串。但为了便於理解C如何处理宽字元,让我们先回顾一下可能在Win32程式中出现的标准字元定义。
下面的语句定义并初始化了一个只包含一个字元的变数:
char c = 'A' ;
变数c需要1个位元组来保存,并将用十六进位数0x41初始化,这是字母A的ASCII代码。
您可以像这样定义一个指向字串的指标:
char * p ;
因为Windows是一个32位元作业系统,所以指标变数p需要用4个位元组保存。您还可初始化一个指向字串的指标:
char * p = "Hello!" ;
像前面一样,变数p也需要用4个位元组保存。该字串保存在静态记忆体中并占用7个位元组-6个位元组保存字串,另1个位元组保存终止符号0。
您还可以像这样定义字元阵列:
char a[10] ;
在这种情况下,编译器为该阵列保留了10个位元组的储存空间。运算式sizeof(a) 将返回10。如果阵列是整体变数(即在所有函式外定义),您可使用像下面的语句来初始化一个字元阵列:
char a[] = "Hello!" ;
如果您将该阵列定义为一个函式的区域变数,则必须将它定义为一个static变数,如下:
static char a[] = "Hello!" ;
无论哪种情况,字串都储存在静态程式记忆体中,并在末尾添加0,这样就需要7个位元组的储存空间。
宽字元
Unicode或者宽字元都没有改变char资料型态在C中的含义。char继续表示1个位元组的储存空间, sizeof (char) 继续返回1。理论上,C中1个位元组可比8位元长,但对我们大多数人来说,1个位元组(也就是1个char)是8位元宽。
C中的宽字元基於wchar_t资料型态,它在几个表头档案包括WCHAR.H中都有定义,像这样:
typedef unsigned short wchar_t ;
因此,wchar_t资料型态与无符号短整数型态相同,都是16位元宽。
要定义包含一个宽字元的变数,可使用下面的语句:
wchar_t c = 'A' ;
变数c是一个双位元组值0x0041,是Unicode表示的字母A。(然而,因为Intel微处理器从最小的位元组开始储存多位元组数值,该位元组实际上是以0x41、0x00的顺序保存在记忆体中。如果检查Unicode文字的电脑储存应注意这一点。)
您还可定义指向宽字串的指标:
wchar_t * p = L"Hello!" ;
注意紧接在第一个引号前面的大写字母L(代表「long」)。这将告诉编译器该字串按宽字元保存-即每个字元占用2个位元组。通常,指标变数p要占用4个位元组,而字串变数需要14个位元组-每个字元需要2个位元组,末尾的0还需要2个位元组。
同样,您还可以用下面的语句定义宽字元阵列:
static wchar_t a[] = L"Hello!" ;
该字串也需要14个位元组的储存空间,sizeof (a) 将返回14。索引阵列a可得到单独的字元。a[1] 的值是宽字元「e」,或者0x0065。
虽然看上去更像一个印刷符号,但第一个引号前面的L非常重要,并且在两个符号之间必须没有空格。只有带有L,编译器才知道您需要将字串存为每个字元2位元组。稍后,当我们看到使用宽字串而不是变数定义时,您还会遇到第一个引号前面的L。幸运的是,如果忘记了包含L,C编译器通常会给提出警告或错误资讯。
您还可在单个字元文字前面使用L字首,来表示它们应解释为宽字元。如下所示:
wchar_t c = L'A' ;
但通常这是不必要的,C编译器会对该字元进行扩充,使它成为宽字元。
宽字元程式库函式
我们都知道如何获得字串的长度。例如,如果我们已经像下面这样定义了一个字串指标:
char * pc = "Hello!" ;
我们可以呼叫
iLength = strlen (pc) ;
这时变数iLength将等於6,也就是字串中的字元数。
太好了!现在让我们试著定义一个指向宽字元的指标:
wchar_t * pw = L"Hello!" ;
再次呼叫strlen :
iLength = strlen (pw) ;
现在麻烦来了。首先,C编译器会显示一条警告消息,可能是这样的内容:
'function' : incompatible types - from 'unsigned short *' to 'const char *'
这条消息的意思是:宣告strlen函式时,该函式应接收char类型的指标,但它现在却接收了一个unsigned short类型的指标。您仍然可编译并执行该程式,但您会发现iLength等於1。为什么?
字串「Hello!」中的6个字元占用16位元:
0x0048 0x0065 0x006C 0x006C 0x006F 0x0021
Intel处理器在记忆体中将其存为:
48 00 65 00 6C 00 6C 00 6F 00 21 00
假定strlen函式正试图得到一个字串的长度,并把第1个位元组作为字元开始计数,但接著假定如果下一个位元组是0,则表示字串结束。
这个小练习清楚地说明了C语言本身和执行时期程式库函式之间的区别。编译器将字串L"Hello!" 解释为一组16位元短整数型态资料,并将其保存在wchar_t阵列中。编译器还处理阵列索引和sizeof操作符,因此这些都能正常工作,但在连结时才添加执行时期程式库函式,例如strlen。这些函式认为字串由单位元组字元组成。遇到宽字串时,函式就不像我们所希望那样执行了。
您可能要说:「噢,太麻烦了!」现在每个C语言程式库函式都必须重写以接受宽字元。但事实上并不是每个C语言程式库函式都需要重写,只是那些有字串参数的函式才需要重写,而且也不用由您来完成。它们已经重写完了。
strlen函式的宽字元版是wcslen(wide-character string length:宽字串长度),并且在STRING.H(其中也说明了strlen)和WCHAR.H中均有说明。strlen函式说明如下:
size_t __cdecl strlen (const char *) ;
而wcslen函式则说明如下:
size_t __cdecl wcslen (const wchar_t *) ;
这时我们知道,要得到宽字串的长度可以呼叫
iLength = wcslen (pw) ;
函式将返回字串中的字元数6。请记住,改成宽位元组后,字串的字元长度不改变,只是位元组长度改变了。
您熟悉的所有带有字串参数的C执行时期程式库函式都有宽字元版。例如,wprintf是printf的宽字元版。这些函式在WCHAR.H和含有标准函式说明的表头档案中说明。
维护单一原始码
当然,使用Unicode也有缺点。第一点也是最主要的一点是,程式中的每个字串都将占用两倍的储存空间。此外,您将发现宽字元执行时期程式库中的函式比常规的函式大。出於这个原因,您也许想建立两个版本的程式-一个处理ASCII字串,另一个处理Unicode字串。最好的解决办法是维护既能按ASCII编译又能按Unicode编译的单一原始码档案。
虽然只是一小段程式,但由於执行时期程式库函式有不同的名称,您也要定义不同的字元,这将在处理前面有L的字串文字时遇到麻烦。
一个办法是使用Microsoft Visual C++包含的TCHAR.H表头档案。该表头档案不是ANSI C标准的一部分,因此那里定义的每个函式和巨集定义的前面都有一条底线。TCHAR.H为需要字串参数的标准执行时期程式库函式提供了一系列的替代名称(例如,_tprintf和_tcslen)。有时这些名称也称为「通用」函式名称,因为它们既可以指向函式的Unicode版也可以指向非Unicode版。
如果定义了名为_UNICODE的识别字,并且程式中包含了TCHAR.H表头档案,那么_tcslen就定义为wcslen:
#define _tcslen wcslen
如果没有定义UNICODE,则_tcslen定义为strlen:
#define _tcslen strlen
等等。TCHAR.H还用一个新的资料型态TCHAR来解决两种字元资料型态的问题。如果定义了 _UNICODE识别字,那么TCHAR就是wchar_t:
typedef wchar_t TCHAR ;
否则,TCHAR就是char:
typedef char TCHAR ;
现在开始讨论字串文字中的L问题。如果定义了_UNI
Unicode简介
在第一章中,我已经预告,C语言中在Microsoft Windows程式设计中扮演著重要角色的任何部分都会讲述到,您也许在传统文字模式程式设计中还尚未遇到过这些问题。宽字元集和Unicode差不多就是这样的问题。
简单地说,Unicode扩展自ASCII字元集。在严格的ASCII中,每个字元用7位元表示,或者电脑上普遍使用的每字元有8位元宽;而Unicode使用全16位元字元集。这使得Unicode能够表示世界上所有的书写语言中可能用於电脑通讯的字元、象形文字和其他符号。Unicode最初打算作为ASCII的补充,可能的话,最终将代替它。考虑到ASCII是电脑中最具支配地位的标准,所以这的确是一个很高的目标。
Unicode影响到了电脑工业的每个部分,但也许会对作业系统和程式设计语言的影响最大。从这方面来看,我们已经上路了。Windows NT从底层支援Unicode(不幸的是,Windows 98只是小部分支援Unicode)。先天即被ANSI束缚的C程式设计语言通过对宽字元集的支援来支援Unicode。下面将详细讨论这些内容。
自然,作为程式写作者,我们通常会面对许多繁重的工作。我已试图透过使本书中的所有程式「Unicode化」来减轻负担。其含义会随著本章对Unicode的讨论而清晰起来。
字元集简史
虽然不能确定人类开始讲话的时间,但书写已有大约6000年的历史了。实际上,早期书写的内容是象形文字。每个字元都对应於发声的字母表则出现於大约3000年前。虽然人们过去使用的多种书写语言都用得好好的,但19世纪的几个发明者还是看到了更多的需求。Samuel F. B. Morse在1838年到1854年间发明了电报,当时他还发明了一种电报上使用的代码。字母表中的每个字元对应於一系列短的和长的脉冲(点和破折号)。虽然其中大小写字母之间没有区别,但数字和标点符号都有了自己的代码。
Morse代码并不是以其他图画的或印刷的象形文字来代表书写语言的第一个例子。1821年到1824年之间,年轻的Louis Braille受到在夜间读写资讯的军用系统的启发,发明了一种代码,它用纸上突起的点作为代码来帮助盲人阅读。Braille代码实际上是一种6位元代码,它把字元、常用字母组合、常用单字和标点进行编码。一个特殊的escape代码表示后续的字元代码应解释为大写。一个特殊的shift代码允许后续代码被解释为数字。
Telex代码,包括Baudot (以一个法国工程师命名,该工程师死于1903年)以及一种被称为CCITT #2的代码(1931年被标准化),都是包括字元和数字的5位元代码。
美国标准
早期电脑的字元码是从Hollerith卡片(号称不能被折叠、卷曲或毁伤)发展而来的,该卡片由Herman Hollerith发明并首次在1890年的美国人口普查中使用。6位元字元码系统BCDIC(Binary-Coded Decimal Interchange Code:二进位编码十进位交换编码)源自Hollerith代码,在60年代逐步扩展为8位元EBCDIC,并一直是IBM大型主机的标准,但没使用在其他地方。
美国资讯交换标准码(ASCII:American Standard Code for Information Interchange)起始於50年代后期,最后完成於1967年。开发ASCII的过程中,在字元长度是6位元、7位元还是8位元的问题上产生了很大的争议。从可靠性的观点来看不应使用替换字元,因此ASCII不能是6位元编码,但由於费用的原因也排除了8位元版本的方案(当时每位元的储存空间成本仍很昂贵)。这样,最终的字元码就有26个小写字母、26个大写字母、10个数字、32个符号、33个代号和一个空格,总共128个字元码。ASCII现在记录在ANSI X3.4-1986字元集-用於资讯交换的7位元美国国家标准码(7-Bit ASCII:7-Bit American National Standard Code for Information Interchange),由美国国家标准协会(American National Standards Institute)发布。图2-1中所示的ASCII字元码与ANSI文件中的格式相似。
ASCII有许多优点。例如,26个字母代码是连续的(在EBCDIC代码中就不是这样的);大写字母和小写字母可通过改变一位元资料而相互转化;10个数位的代码可从数值本身方便地得到(在BCDIC代码中,字元「0」的编码在字元「9」的后面!)
最棒的是,ASCII是一个非常可靠的标准。在键盘、视讯显示卡、系统硬体、印表机、字体档案、作业系统和Internet上,其他标准都不如ASCII码流行而且根深蒂固。
0- 1- 2- 3- 4- 5- 6- 7-
-0 NUL DLE SP 0 @ P ` p
-1 SOH DC1 ! 1 A Q a q
-2 STX DC2 " 2 B R b r
-3 ETX DC3 # 3 C S c s
-4 EOT DC4 $ 4 D T d t
-5 ENQ NAK % 5 E U e u
-6 ACK SYN & 6 F V f v
-7 BEL ETB ' 7 G W g w
-8 BS CAN ( 8 H X h x
-9 HT EM ) 9 I Y I y
-A LF SUB * : J Z j z
-B VT ESC + ; K [ k {
-C FF FS , < L \ l |
-D CR GS - = M ] m }
-E SO RS . > N ^ n ~
-F SI US / ? O _ o DEL
图2-1 ASCII字元集
国际方面
ASCII的最大问题就是该缩写的第一个字母。ASCII是一个真正的美国标准,所以它不能良好满足其他讲英语国家的需要。例如英国的英镑符号(£)在哪里?
英语使用拉丁(或罗马)字母表。在使用拉丁语字母表的书写语言中,英语中的单词通常很少需要重音符号(或读音符号)。即使那些传统惯例加上读音符号也无不当的英语单字,例如cöoperate或者résumé,拼写中没有读音符号也会被完全接受。
但在美国以南、以北,以及大西洋地区的许多国家,在语言中使用读音符号很普遍。这些重音符号最初是为使拉丁字母表适合这些语言读音不同的需要。在远东或西欧的南部旅游,您会遇到根本不使用拉丁字母的语言,例如希腊语、希伯来语、阿拉伯语和俄语(使用斯拉夫字母表)。如果您向东走得更远,就会发现中国象形汉字,日本和朝鲜也采用汉字系统。
ASCII的历史开始於1967年,此后它主要致力於克服其自身限制以更适合於非美国英语的其他语言。例如,1967年,国际标准化组织(ISO:International Standards Organization)推荐一个ASCII的变种,代码0x40、0x5B、0x5C、0x5D、0x7B、0x7C和0x7D「为国家使用保留」,而代码0x5E、0x60和0x7E标为「当国内要求的特殊字元需要8、9或10个空间位置时,可用於其他图形符号」。这显然不是一个最佳的国际解决方案,因为这并不能保证一致性。但这却显示了人们如何想尽办法为不同的语言来编码的。
扩展ASCII
在小型电脑开发的初期,就已经严格地建立了8位元位元组。因此,如果使用一个位元组来保存字元,则需要128个附加的字元来补充ASCII。1981年,当最初的IBM PC推出时,视讯卡的ROM中烧有一个提供256个字元的字元集,这也成为IBM标准的一个重要组成部分。
最初的IBM扩展字元集包括某些带重音的字元和一个小写希腊字母表(在数学符号中非常有用),还包括一些块型和线状图形字元。附加的字元也被添加到ASCII控制字元的编码位置,这是因为大多数控制字元都不是拿来显示用的。
该IBM扩展字元集被烧进无数显示卡和印表机的ROM中,并被许多应用程式用於修饰其文字模式的显示方式。不过,该字元集并没有为所有使用拉丁字母表的西欧语言提供足够多的带重音字元,而且也不适用於Windows。Windows不需要图形字元,因为它有一个完全图形化的系统。
在Windows 1.0(1985年11月发行)中,Microsoft没有完全放弃IBM扩展字元集,但它已退居第二重要位置。因为遵循了ANSI草案和ISO标准,纯Windows字元集被称作「ANSI字元集」。 ANSI草案和ISO标准最终成为ANSI/ISO 8859-1-1987,即「American National Standard for Information Processing-8-Bit Single-Byte Coded Graphic Character Sets-Part 1: Latin Alphabet No 1」,通常也简写为「Latin 1」。
在Windows 1.0的《Programmer's Reference》中印出了ANSI字元集的最初版本,如图2-2所示。
0- 1- 2- 3- 4- 5- 6- 7- 8- 9- A- B- C- D- E- F-
-0 * * 0 @ P ` p * * ° À Ð à ð
-1 * * ! 1 A Q a q * * ¡ ± Á Ñ á ñ
-2 * * " 2 B R b r * * ¢ ² Â ò â ò
-3 * * # 3 C S c s * * £ ³ Ã ó ã ó
-4 * * $ 4 D T d t * * ¤ ´ Ä ô ä ô
-5 * * % 5 E U e u * * ¥ µ Å õ å õ
-6 * * & 6 F V f v * * ¦ ¶ Æ ö æ ö
-7 * * ' 7 G W g w * * § · Ç * ç *
-8 * * ( 8 H * h * * * ¨ ¸ È ø è ø
-9 * * ) 9 I Y I y * * © ¹ É Ù é ù
-A * * * : J Z j z * * ª º Ê Ú ê ú
-B * * + ; K [ k { * * « » Ë Û ë û
-C * * , < L \ l | * * ¬ ¼ Ì Ü ì ü
-D * * - = M ] m } * * ½ Í Ý í ý
-E * * . > N ^ n ~ * * ® ¾ Î Þ î þ
-F * * / ? * _ o DEL * * ¯ ¿ Ï ß ï ÿ
* - not applicable
图2-2 Windows ANSI字元集(基於ANSI/ISO 8859-1)
空方框表示该位置未定义字元。这与ANSI/ISO 8859-1的最终定义一致。ANSI/ISO 8859-1仅显示了图形字元,而没有控制字元,因此没有定义DEL。此外,代码0xA0定义为一个非断开的空格(这意味著在编排格式时,该字元不用於断开一行),代码0xAD是一个软连字元(表示除非在行尾断开单词时使用,否则不显示)。此外,ANSI/ISO 8859-1将代码0xD7定义为乘号(*),0xF7为除号(/)。Windows中的某些字体也定义了从0x80到0x9F的某些字元,但这些不是ANSI/ISO 8859-1标准的一部分。
MS-DOS 3.3(1987年4月发行)向IBM PC用户引进了内码表(code page)的概念,Windows也使用此概念。内码表定义了字元的映射代码。最初的IBM字元集被称作内码表437,或者「MS-DOS Latin US)。内码表850就是「MS-DOS Latin 1」,它用附加的带重音字母(但 不是 图2-2所示的Latin 1 ISO/ANSI标准)代替了一些线形字元。其他内码表被其他语言定义。最低的128个代码总是相同的;较高的128个代码取决於定义内码表的语言。
在MS-DOS中,如果用户为PC的键盘、显示卡和印表机指定了一个内码表,然后在PC上创建、编辑和列印文件,一切都很正常,每件事都会保持一致。然而,如果用户试图与使用不同内码表的用户交换档案,或者在机器上改变内码表,就会产生问题。字元码与错误的字元相关联。应用程式能够将内码表资讯与文件一起保存来试图减少问题的产生,但该策略包括了某些在内码表间转换的工作。
虽然内码表最初仅提供了不包括带重音符号字母的附加拉丁字元集,但最终内码表的较高的128个字元还是包括了完整的非拉丁字母,例如希伯来语、希腊语和斯拉夫语。自然,如此多样会导致内码表变得混乱;如果少数带重音的字母未正确显示,那么整个文字便会混乱不堪而不可阅读。
内码表的扩展正是基於所有这些原因,但是还不够。斯拉夫语的MS-DOS内码表855与斯拉夫语的Windows内码表1251以及斯拉夫语的Macintosh内码表10007不同。每个环境下的内码表都是对该环境所作的标准字元集修正。IBM OS/2也支援多种EBCDIC内码表。
但等一下,你会发现事情变得更糟糕。
双位元组字元集
迄今为止,我们已经看到了256个字元的字元集。但中国、日本和韩国的象形文字符号有大约21,000个。如何容纳这些语言而仍保持和ASCII的某种相容性呢?
解决方案(如果这个说法正确的话)是双位元组字元集(DBCS:double-byte character set)。DBCS从256代码开始,就像ASCII一样。与任何行为良好的内码表一样,最初的128个代码是ASCII。然而,较高的128个代码中的某些总是跟随著第二个位元组。这两个位元组一起(称作首位元组和跟随位元组)定义一个字元,通常是一个复杂的象形文字。
虽然中文、日文和韩文共用一些相同的象形文字,但显然这三种语言是不同的,而且经常是同一个象形文字在三种不同的语言中代表三件不同的事。Windows支援四个不同的双位元组字元集:内码表932(日文)、936(简体中文)、949(韩语)和950(繁体汉字)。只有为这些国家(地区)生产的Windows版本才支援DBCS。
双字元集问题并不是说字元由两个位元组代表。问题在於一些字元(特别是ASCII字元)由1个位元组表示。这会引起附加的程式设计问题。例如,字串中的字元数不能由字串的位元组数决定。必须剖析字串来决定其长度,而且必须检查每个位元组以确定它是否为双位元组字元的首位元组。如果有一个指向DBCS字串中间的指标,那么该字串前一个字元的位址是什么呢?惯用的解决方案是从开始的指标分析该字串!
Unicode解决方案
我们面临的基本问题是世界上的书写语言不能简单地用256个8位元代码表示。以前的解决方案包括内码表和DBCS已被证明是不能满足需要的,而且也是笨拙的。那什么才是真正的解决方案呢?
身为程式写作者,我们经历过这类问题。如果事情太多,用8位元数值已经不能表示,那么我们就试更宽的值,例如16位元值。而且这很有趣的,正是Unicode被制定的原因。与混乱的256个字元代码映射,以及含有一些1位元组代码和一些2位元组代码的双位元组字元集不同,Unicode是统一的16位元系统,这样就允许表示65,536个字元。这对表示所有字元及世界上使用象形文字的语言,包括一系列的数学、符号和货币单位符号的集合来说是充裕的。
明白Unicode和DBCS之间的区别很重要。Unicode使用(特别在C程式设计语言环境里)「宽字元集」。「Unicode中的每个字元都是16位元宽而不是8位元宽。」在Unicode中,没有单单使用8位元数值的意义存在。相比之下,在双位元组字元集中我们仍然处理8位元数值。有些位元组自身定义字元,而某些位元组则显示需要和另一个位元组共同定义一个字元。
处理DBCS字串非常杂乱,但是处理Unicode文字则像处理有秩序的文字。您也许会高兴地知道前128个Unicode字元(16位元代码从0x0000到0x007F)就是ASCII字元,而接下来的128个Unicode字元(代码从0x0080到0x00FF)是ISO 8859-1对ASCII的扩展。Unicode中不同部分的字元都同样基於现有的标准。这是为了便於转换。希腊字母表使用从0x0370到0x03FF的代码,斯拉夫语使用从0x0400到0x04FF的代码,美国使用从0x0530到0x058F的代码,希伯来语使用从0x0590到0x05FF的代码。中国、日本和韩国的象形文字(总称为CJK)占用了从0x3000到0x9FFF的代码。
Unicode的最大好处是这里只有一个字元集,没有一点含糊。Unicode实际上是个人电脑行业中几乎每个重要公司共同合作的结果,并且它与ISO 10646-1标准中的代码是一一对应的。Unicode的重要参考文献是《The Unicode Standard,Version 2.0》(Addison-Wesley出版社,1996年)。这是一本特别的书,它以其他文件少有的方式显示了世界上书写语言的丰富性和多样性。此外,该书还提供了开发Unicode的基本原理和细节。
Unicode有缺点吗?当然有。Unicode字串占用的记忆体是ASCII字串的两倍。(然而压缩档案有助於极大地减少档案所占的磁碟空间。)但也许最糟的缺点是:人们相对来说还不习惯使用Unicode。身为程式写作者,这就是我们的工作。
宽字元和C
对C程式写作者来说,16位元字元的想法的确让人扫兴。一个char和一个位元组同宽是最不能确定的事情之一。没几个程式写作者清楚ANSI/ISO 9899-1990,这是「美国国家标准程式设计语言-C」(也称作「ANSI C」)通过一个称作「宽字元」的概念来支援用多个位元组代表一字元的字元集。这些宽字元与常用的字元完美地共存。
ANSI C也支援多位元组字元集,例如中文、日文和韩文版本Windows支援的字元集。然而,这些多位元组字元集被当成单位元组构成的字串看待,只不过其中一些字元改变了后续字元的含义而已。多位元组字元集主要影响C语言程式执行时期程式库函式。相比之下,宽字元比正常字元宽,而且会引起一些编译问题。
宽字元不需要是Unicode。Unicode是一种可能的宽字元集。然而,因为本书的焦点是Windows而不是C执行的理论,所以我将把宽字元和Unicode作为同义语。
char资料型态
假定我们都非常熟悉在C程式中使用char资料型态来定义和储存字元跟字串。但为了便於理解C如何处理宽字元,让我们先回顾一下可能在Win32程式中出现的标准字元定义。
下面的语句定义并初始化了一个只包含一个字元的变数:
char c = 'A' ;
变数c需要1个位元组来保存,并将用十六进位数0x41初始化,这是字母A的ASCII代码。
您可以像这样定义一个指向字串的指标:
char * p ;
因为Windows是一个32位元作业系统,所以指标变数p需要用4个位元组保存。您还可初始化一个指向字串的指标:
char * p = "Hello!" ;
像前面一样,变数p也需要用4个位元组保存。该字串保存在静态记忆体中并占用7个位元组-6个位元组保存字串,另1个位元组保存终止符号0。
您还可以像这样定义字元阵列:
char a[10] ;
在这种情况下,编译器为该阵列保留了10个位元组的储存空间。运算式sizeof(a) 将返回10。如果阵列是整体变数(即在所有函式外定义),您可使用像下面的语句来初始化一个字元阵列:
char a[] = "Hello!" ;
如果您将该阵列定义为一个函式的区域变数,则必须将它定义为一个static变数,如下:
static char a[] = "Hello!" ;
无论哪种情况,字串都储存在静态程式记忆体中,并在末尾添加0,这样就需要7个位元组的储存空间。
宽字元
Unicode或者宽字元都没有改变char资料型态在C中的含义。char继续表示1个位元组的储存空间, sizeof (char) 继续返回1。理论上,C中1个位元组可比8位元长,但对我们大多数人来说,1个位元组(也就是1个char)是8位元宽。
C中的宽字元基於wchar_t资料型态,它在几个表头档案包括WCHAR.H中都有定义,像这样:
typedef unsigned short wchar_t ;
因此,wchar_t资料型态与无符号短整数型态相同,都是16位元宽。
要定义包含一个宽字元的变数,可使用下面的语句:
wchar_t c = 'A' ;
变数c是一个双位元组值0x0041,是Unicode表示的字母A。(然而,因为Intel微处理器从最小的位元组开始储存多位元组数值,该位元组实际上是以0x41、0x00的顺序保存在记忆体中。如果检查Unicode文字的电脑储存应注意这一点。)
您还可定义指向宽字串的指标:
wchar_t * p = L"Hello!" ;
注意紧接在第一个引号前面的大写字母L(代表「long」)。这将告诉编译器该字串按宽字元保存-即每个字元占用2个位元组。通常,指标变数p要占用4个位元组,而字串变数需要14个位元组-每个字元需要2个位元组,末尾的0还需要2个位元组。
同样,您还可以用下面的语句定义宽字元阵列:
static wchar_t a[] = L"Hello!" ;
该字串也需要14个位元组的储存空间,sizeof (a) 将返回14。索引阵列a可得到单独的字元。a[1] 的值是宽字元「e」,或者0x0065。
虽然看上去更像一个印刷符号,但第一个引号前面的L非常重要,并且在两个符号之间必须没有空格。只有带有L,编译器才知道您需要将字串存为每个字元2位元组。稍后,当我们看到使用宽字串而不是变数定义时,您还会遇到第一个引号前面的L。幸运的是,如果忘记了包含L,C编译器通常会给提出警告或错误资讯。
您还可在单个字元文字前面使用L字首,来表示它们应解释为宽字元。如下所示:
wchar_t c = L'A' ;
但通常这是不必要的,C编译器会对该字元进行扩充,使它成为宽字元。
宽字元程式库函式
我们都知道如何获得字串的长度。例如,如果我们已经像下面这样定义了一个字串指标:
char * pc = "Hello!" ;
我们可以呼叫
iLength = strlen (pc) ;
这时变数iLength将等於6,也就是字串中的字元数。
太好了!现在让我们试著定义一个指向宽字元的指标:
wchar_t * pw = L"Hello!" ;
再次呼叫strlen :
iLength = strlen (pw) ;
现在麻烦来了。首先,C编译器会显示一条警告消息,可能是这样的内容:
'function' : incompatible types - from 'unsigned short *' to 'const char *'
这条消息的意思是:宣告strlen函式时,该函式应接收char类型的指标,但它现在却接收了一个unsigned short类型的指标。您仍然可编译并执行该程式,但您会发现iLength等於1。为什么?
字串「Hello!」中的6个字元占用16位元:
0x0048 0x0065 0x006C 0x006C 0x006F 0x0021
Intel处理器在记忆体中将其存为:
48 00 65 00 6C 00 6C 00 6F 00 21 00
假定strlen函式正试图得到一个字串的长度,并把第1个位元组作为字元开始计数,但接著假定如果下一个位元组是0,则表示字串结束。
这个小练习清楚地说明了C语言本身和执行时期程式库函式之间的区别。编译器将字串L"Hello!" 解释为一组16位元短整数型态资料,并将其保存在wchar_t阵列中。编译器还处理阵列索引和sizeof操作符,因此这些都能正常工作,但在连结时才添加执行时期程式库函式,例如strlen。这些函式认为字串由单位元组字元组成。遇到宽字串时,函式就不像我们所希望那样执行了。
您可能要说:「噢,太麻烦了!」现在每个C语言程式库函式都必须重写以接受宽字元。但事实上并不是每个C语言程式库函式都需要重写,只是那些有字串参数的函式才需要重写,而且也不用由您来完成。它们已经重写完了。
strlen函式的宽字元版是wcslen(wide-character string length:宽字串长度),并且在STRING.H(其中也说明了strlen)和WCHAR.H中均有说明。strlen函式说明如下:
size_t __cdecl strlen (const char *) ;
而wcslen函式则说明如下:
size_t __cdecl wcslen (const wchar_t *) ;
这时我们知道,要得到宽字串的长度可以呼叫
iLength = wcslen (pw) ;
函式将返回字串中的字元数6。请记住,改成宽位元组后,字串的字元长度不改变,只是位元组长度改变了。
您熟悉的所有带有字串参数的C执行时期程式库函式都有宽字元版。例如,wprintf是printf的宽字元版。这些函式在WCHAR.H和含有标准函式说明的表头档案中说明。
维护单一原始码
当然,使用Unicode也有缺点。第一点也是最主要的一点是,程式中的每个字串都将占用两倍的储存空间。此外,您将发现宽字元执行时期程式库中的函式比常规的函式大。出於这个原因,您也许想建立两个版本的程式-一个处理ASCII字串,另一个处理Unicode字串。最好的解决办法是维护既能按ASCII编译又能按Unicode编译的单一原始码档案。
虽然只是一小段程式,但由於执行时期程式库函式有不同的名称,您也要定义不同的字元,这将在处理前面有L的字串文字时遇到麻烦。
一个办法是使用Microsoft Visual C++包含的TCHAR.H表头档案。该表头档案不是ANSI C标准的一部分,因此那里定义的每个函式和巨集定义的前面都有一条底线。TCHAR.H为需要字串参数的标准执行时期程式库函式提供了一系列的替代名称(例如,_tprintf和_tcslen)。有时这些名称也称为「通用」函式名称,因为它们既可以指向函式的Unicode版也可以指向非Unicode版。
如果定义了名为_UNICODE的识别字,并且程式中包含了TCHAR.H表头档案,那么_tcslen就定义为wcslen:
#define _tcslen wcslen
如果没有定义UNICODE,则_tcslen定义为strlen:
#define _tcslen strlen
等等。TCHAR.H还用一个新的资料型态TCHAR来解决两种字元资料型态的问题。如果定义了 _UNICODE识别字,那么TCHAR就是wchar_t:
typedef wchar_t TCHAR ;
否则,TCHAR就是char:
typedef char TCHAR ;
现在开始讨论字串文字中的L问题。如果定义了_UNI
展开全部
'用API函数lstrlen 来判断字串长度就不会有这种问题了.
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As String) As Long
X=lstrlen("啊a1")
X=4
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As String) As Long
X=lstrlen("啊a1")
X=4
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
你的声明是用的是textoutA吗?ncount你用字数*2了吗?
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
可以加我QQ616898255,我这里有示例程序
本回答被提问者采纳
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询