背景:
阅读新闻

字符与汉字的小百科

[日期:2011-10-11] 来源:  作者: [字体: ]

那么我就给你介绍一下我总结出来的GB字符识别方法(是的,就是你想要的那种把半角字符、全角符号和汉字区别开来的方法)吧。在此之前希望你有心里准备,篇幅可能会稍微长一些,因为涉及到的内容比较多。不论你之前看过什么样的资料,并对此有多大程度的了解,如果你希望能非常专业地解决这个问题(嗯哼,就像Win32 API或VCL做的那样专业),那就要有一定的耐心从最基础的地方看起。对了,在继续看下去之前,我想你至少应该明白ANSI和双字节字符集的概念,如果不明白,可以先看看这篇文章

http://dev.csdn.net/develop/article/70/70372.shtm

原文虽没有明确解释ANSI的概念,但在介绍双字节字符集时已多次涉及,这里我总结一下:所谓ANSI就是在Unicode和ISO10646出现以前,各个非英语国家和地区的计算机科技工作者为了将本地语言文字编码进计算机,而对基本ASCII编码所做的各种扩展编码方案的统称,它们多用两个以上的字节编码一个字符(称为多字节字符集MBCS),其中比较典型的就是在东亚广泛被使用的双字节字符集(DBCS)。这个东西可没你想象的那么简单呢--



第一步 认识Delphi的ByteType函数(我用的是Delphi7,6用得少,里面应该有这个函数吧)

这个函数的作用是检测ANSI字符串中某个字节是属于一个单字节字符(即ASCII字符)还是双字节字符的第一个字节,又或是双字节字符的第二个字节。

声明单元
SysUtils

VCL例程分类
MBCS处理

Delphi语法:
function ByteType(const S: string; Index: Integer): TMbcsByteType;
C++语法:
extern PACKAGE TMbcsByteType __fastcall ByteType(const AnsiString S, int Index);

说明
用ByteType函数可以检测字符串指定字节的类型:单字节字符、多字节字符的引导字节或多字节字符的尾字节中的一个。
参数S是一个可能包含多字节字符的ANSI字符串。
参数Index表示你想要检测其类型的字节在字符串中的索引位置,这个索引位置是从1开始的(也就是说字符串中第一个字节的索引位置为1)。
如果当前系统不使用多字节字符集(MBCS),ByteType函数将始终返回mbSingleByte;否则,函数将在指定的字节在字符串中单独地表示一个字符的时候返回mbSingleByte,在指定的字节在字符串中表示一个双字节字符的首字节的时候返回mbLeadByte,在指定的字节在字符串中表示一个多字节字符的尾字节中的一个的时候返回mbTrailByte。
注意:这个函数不会检查Index值是否小于S的长度,程序员自己应该意识到这一点:在调用ByteType函数前,必须确保所要检测的字节的索引位置不会超出字符串的长度。
在双字节字符集系统里面(95以上的简体中文Windows操作系统所用的GB字符集就是典型的ANSI双字节字符集),字符只有基本的两类:单字节字符(映射ASCII字符集)和双字节字符。ByteType是Delphi提供给我们的一个非常有用的函数,它让我们可以在不必涉及底层操作的情况下轻易地得知一个字符串里面的某个字节(嗯!是的,你没有看错,就是字节,ANSI字符串的基本访问单位是字节,不是字符)是属于什么字节类型。我们可以通过字节类型的信息,进而推论它所代表的字符的类型。所以,字符类型识别的第一步就是熟练地运用ByteType函数进行字节类型的识别。以下是一个例子:
case ByteType(Edit1.Text,strtoint(Edit2.Text)) of
mbSingleByte:
Label1.Caption:='单字节(ASCII)字符';
mbLeadByte:
Label1.Caption:='双字节字符首字节';
mbTrailByte:
Label1.Caption:='双字节字符尾字节'
End;

如果你还不是很明白ByteType函数到底能干些什么,那我再举一些例子:
ByteType('你',1)=mbLeadByte
ByteType('你',2)=mbTrailByte
ByteType('a',1)=mbSingleByte
ByteType('ab',2)=mbSingleByte
ByteType('你好,我是SparkV',4)=mbTrailByte
ByteType('你好,我是SparkV',5)=mbLeadByte
...

第二步 基本字符类型识别

有了ByteType函数,要把单字节字符和双字节字符区分出来就好办得多了,先说说算法:
对于字符串S,有
1.如果检测S[I]的字节类型为单字节字符,那么毫无疑问,S[I]就是一个单独的单字节字符了。
2.如果检测S[I]的字节类型为双字节字符首字节,那么就必须检测它的下一个字节S[I+1]的类型,如果检测S[I+1]的结果是双字节字符尾字节,那么就可以判定S[I]+S[I+1]是一个双字节字符;如果检测S[I+1]的类型还是双字节字符首字节或S[I+1]不存在(即I+1>Length(S)),那么就应该判定S[I]在字符串中是一个无效字符。
3.如果检测S[I]的字节类型为双字节字符尾字节,那么就必须检测它的上一个字节S[I-1]的类型,如果检测S[I-1]的结果是双字节字符首字节,那么就可以判定S[I-1]+S[I]组成一个双字节字符;如果检测S[I-1]的类型还是双字节字符尾字节或S[I-1]不存在(即I-1<1),那么就应该判定S[I]在字符串中是一个无效字符。
下面给出一个根据上述算法编写的基本字符类型识别函数--
Function AnsiCharType(const S: string; Index: Integer):integer;
Var
Len,CharType:integer;
Begin
Len:=Length(S);
if (Index>0) and (Index<=Len) then
begin
case ByteType(S,Index) of
mbSingleByte:
CharType:=0; // 返回值为1,表示被检测字节所在的字符是单字节字符
mbLeadByte:
if (Index+1<=Len) and (ByteType(S,Index+1)=mbTrailByte) then
CharType:=1 // 返回这为1,表示被检测字节所在的字符是双字节字符
else
CharType:=-1; // 返回值为-1,表示被检测字节所在的字符是无效字符
mbTrailByte:
if (Index-1>0) and (ByteType(S,Index-1)=mbLeadByte) then
CharType:=1
else
CharType:=-1
End;
Result:=CharType
End
Else
Result:=-1
End;

这个函数怎么用呢?比如,字符串S="你好,我是SparkV",那么用上面这个函数对它进行测试,会有如下的结果:
AnsiCharType(S,1)=1
AnsiCharType(S,2)=1
AnsiCharType(S,5)=1
AnsiCharType(S,6)=1
AnsiCharType(S,11)=0
AnsiCharType(S,12)=0
...
由于S[1]+S[2]="你",S[5]+S[6]=",",而"你"和","都是双字节字符,所以对这4个字节的检测结果都是1;又因为S[11]='S',S[12]='p',而'S'和'p'都是单字节字符,所以对这2个字节的检测结果都是0。

可能你在看其他资料的时候听到过这样的说法:“判断一个字符是全角字符(即双字节字符)还是半角字符(即单字节字符)很简单,只要看它的字节数据是否大于127就可以了”。这种说法曾经一度很流行(其实现在也很流行,不过有所进步--盲目迷信的人不多了),其实它的产生是有一定的历史根源的,且在当时的软件环境下也确实是正确的。不过现在时代不同了,继续采用这个方法有很大的片面性,它只适用于现在的GB编码体系的其中一部分字符,若用它来检测所有的GB字符就不能保证得到正确的结果。所以这个方法现在已经不适用了。除了纪念,它已经没有任何的意义。

第三部分 获取字符的内码

有了前面两步的基础,现在就可以坦然地读取字符串中指定位置上的字符的内码值了(因为字符串中任意一个字符的类型都可以被掌握,所以才可以确信自己应该在字符串中的哪个位置获取字符,并进而读取它的内码)。为什么要读取字符的内码呢?因为如果能够确定一个字符是双字节字符,就可以通过它的内码值知道它是全角符号还是汉字了,这么说的依据是在GB编码中,全角符号和汉字分属不同的编码区间,这样只要检查一个字符的内码在哪种字符的编码区间内,就能知道那个字符是全角符号还是汉字了。
那么怎么取得双字节字符的内码呢?我们已经知道,双字节字符是用两个字节表示的,这也可以理解为要用两个字节来存储它,如果把双字节字符看作是一个独立的单位,那么存储它就需要一个字(Word,16位)。再进一步讲,双字节字符的内码值是一个16位的无符号整数。另外,GB编码系统是高位在前的。这是什么意思呢?比如,汉字“啊”的GB内码值为0xB0A1(注意啦,我在表示字符编码值的时候用的C/C++风格的十六进制数表示法,因为这是比较通用的表示法),那么计算机在存储这个字的时候就是按 B0、A1 这样的字节顺序来存储的。用Delphi语言的习惯来解释的话,如果S='啊',那么就会有Ord(S[1])=$B1,Ord(S[2])=$A1。这就是在GB编码标准里面,必须牢记的一点:“双字节字符的第一个字节就是其内码的高8位数据,双字节字符的第二个字节就是其内码的低8位数据”。
根据以上原理,不难得出计算内码的算法:
比如,在字符串S='你好,我是SparkV'里面,经检测得知S[5]+S[6]是一个双字节字符,那么可以编写如下程序计算它的内码值
var
GBCode:Word;
...
GBCode:=Ord(S[5]) shl 8 + Ord(S[6]);
那好,现在
showmessage('0x'+IntToHex(GBCode,4));
看看,结果是不是显示“0xA3AC”?(因为S[5]+S[6]=",",而","的GB内码值为0xA3AC。
OK,验证上述算法无误后,我们可以编写自己的ANSI字符内码求取函数了:
function GetAnsiChrCode(const AnsiString:string;CharIndex:integer):Word;
var
Code:Word;
begin
case AnsiCharType(AnsiString,CharIndex) of
0:
Code:=Ord(AnsiString[CharIndex]);
1:
Code:=Ord(AnsiString[CharIndex]) shl 8 + Ord(AnsiString[CharIndex+1]);
else
Code:=$FFFF // 无效内码值,表示无效字符
end;
Result:=Code
end;

第四部分 GB字符分类

上一贴已经说过了,可以通过检测内码值所在编码区间的方法来推断字符所属的类别。那么GB编码标准里面,字符又是怎么分布的呢?要回答这个问题,就应该去查阅GB编码标准文档,根据最新的GB18030-2000标准(GB标准是向下兼容的,不必担心我的方法是否适合GBK和GB2312)的描述:
双字节编码部分(GB18030-2000 包括双字节和四字节编码方案,一般我们只需关心双字节部分即可)码位安排情况如下:

双字节1区: A1A1—A9FE 图形符号
双字节5区: A840—A9A0 图形符号
------------------
双字节2区: B0A1—F7FE 汉字
双字节3区: 8140—A0FE 汉字
双字节4区: AA40—FEA0 汉字
------------------
双字节用户区1: AAA1—AFFE
双字节用户区2: F8A1—FEFE
双字节用户区3: A140—A7A0

好啦,掌握这些资料以后,我们的字符分类函数就呼之欲出了:
function GBChrType(const GBString:string;CharIndex:integer):integer;
var
Code:Word;
ChrType:integer;
begin
if SysLocale.DefaultLCID=$0804 then
begin
Code:=GetAnsiChrCode(GBString,CharIndex);
if Code<0 then
ChrType:=-1 // 返回值-1,表示这是个无效字符
else
if (Code>=0) and (Code<=$80) then
ChrType:=0 // 返回值0,表示这是个单字节字符
else
if (Code>=$A1A1) and (Code<=$A9FE) // 双字节1区
or
(Code>=$A840) and (Code<=$A9A0) // 双字节5区
then
ChrType:=1 // 返回值1,表示这是个双字节图形符号
else
if (Code>=$B0A1) and (Code<=$F7FE) // 双字节2区
or
(Code>=$8140) and (Code<=$A0FE) // 双字节3区
or
(Code>=$AA40) and (Code<=$FEA0) // 双字节4区
then
ChrType:=2 // 返回值2,表示这是一个汉字
else
if (Code>=$AAA1) and (Code<=$AFFE) // 双字节用户区1
or
(Code>=$F8A1) and (Code<=$FEFE) // 双字节用户区2
or
(Code>=$A140) and (Code<=$A7A0) // 双字节用户区3
then
ChrType:=3 // 返回值3,表示这是一个用户自定义字符
end;

收藏 推荐 打印 | 录入:admin | 阅读:
相关新闻      
本文评论   [发表评论]   全部评论 (0)
热门评论