前面写的8篇文章,基本上将VC环境下命令行模式程序的入口,以及链接库的知识进行了介绍,基本可以告一段落了。至于kernel32.lib包括后面的user32.lib等,之后介绍win32应用程序(Win32 Application)的时候会介绍到,这里就不展开了。另外,忽然想到,这里写的程序都是win32程序,至于64位的程序,具体还没有开发过,也就不涉及了,以后如果业务上使用到的时候,再具体研究之后加上。
这里新的话题就是字符编码以及国际化的问题。在写完这个话题之后,我们就将开始"Win32 Application"的旅程。
既然你在看这篇文章,那我就假定你是一个开发者,并且对于ANSI,ASCII,Unicode,UTF8这些名词都有所了解,至少是听说过这些概念。因为这个例子中,我们就会使用到这些概念。如果你对于这些概念不熟悉,那请先从我的“参考”链接中找到对应的条目进行研究,然后再开始做这个例子。
OK,那我们就可以开始了。首先,打开你的Notepad,中文系统中叫做“记事本”。下面是一个空的打开的记事本:
我们来输入一些文字。
我们现在输入了一行中文,一行英文,并且带了一些标点符号,同时有一个换行符。
将其保存起来,命名为"hello.txt",位置放在"d:\test"目录下。
到文件管理器下,我们可以看到hello.txt这个文件,大小为27个byte。
现在我们需要用一些其他的辅助工具,我这里用的是Notepad++以及它的一个叫HexEditor的插件,不喜欢使用的同学们可以用其他类似的工具代替,如WinHex或HexEdit等十六进制编辑器。用工具将这个txt文件打开,来看看这27个byte是怎么组成的。
打开文件后,点击工具栏上的“H”图标,我们来看看十六进制。
其实这里的对应是有些小问题的,对于中文的部分,dump出来的映射关系在移动光标的时候有些不准确,但是这个并不是影响很大,我们可以忽略。我们可以挑选其中几个来看看,这里的“你”对应的十六进制为“c4 e3”,也就是0xC4E3;"l"对应的十六进制为"6c",即0x6C。我们在这里看到的映射关系,就叫做字符编码(character encoding),而所有字符编码的集合,我们叫做字符集(character set--charset)。
我们在这里看到的是ANSI编码的,可以回过来看一下“保存”的那个截图,最下方的"encoding"选择的是"ANSI"。我们在保存文件的时候,还可以有其他选择。用saveas来另存一下。
可以看到,除了ANSI选项外,我们还有3个选项。
这里我们选择使用"unicode",其他两个也是unicode的概念,只不过是不同形式的表示,我们之后会进行解释,将其保存为"hello_unicode.txt",从文件管理器中查看,其大小为44个byte。同样的内容,换了不同的encoding,大小也变得不同了。来看看到底是为什么吧。
我们也将其用Notepad++打开,查看其十六进制。
这里Dump出来怎么都是“乱码”呀?不要慌张,这是因为dump使用的是ANSI编码显示,而现在我们用的是unicode编码。这里文件最前面的"ff fe"是用来判断"大小端"的,如果你学过TCP/IP或者计算机基础课程,那应该对于这个概念比较熟悉,如果不熟悉,那就搜索一下"littlen endian, big endian"来看看吧,这个对于文件是需要的,但是对于字符串等在内存中的字符表示,就不需要了,所以这里只需要了解就可以了。字符“你”的对应十六进制编码为"60 4f",而这里是小端模式,即0x4F60。要验证的话,也很简单,我博客中有篇文章中已经介绍过了,可以参考巧用WORD进行字符与Unicode字符的转换中的内容,用word来验证一下。
当你有耐心看到这里,并且明白了我上面所写的内容的话,背景介绍完毕。下面就开始真正介绍“字符编码”了。
首先,上面我们所做的都是针对简体中文环境下的字符编码,至于其他编码,我们还没有涉及到,之后会谈到。对于“你”这个字符,在ANSI中,为0xC4E3;在Unicode中,为0x4F60。这是由不同的标准所决定的,决定前面的标准为中国国家标准,包括一系列一脉相承的标准,从GB2312到GBK到GB18030。决定后面的为The Unicode Standard。为什么会出现这么多标准,这是由历史原因以及当时的机器性能决定的。
历史
刚刚前面一直提到ANSI,其实并不是那么准确的,其中我们要涉及到codepage的概念,这里我们的codepage为936,也就是简体中文,在这个环境下,我们的ANSI编码表示才是中国国家标准的那套。ANSI真正的含义是“美国国家标准协会”(American National Standards Institute)。就是这个协会,定义了字符编码的“鼻祖”--ASCII码。现在的字符编码,大多是基于ASCII码扩展而来的。
ASCII(American Standard Code for Information Interchange, 美国信息交换标准代码)是一个7位(7-bits)码,包含了128个字符的定义(128=2^7),其中有33个非打印控制字符,以及95个可打印字符(包括空格),主要是用来显示英文字母的,其对应的ISO标准为ISO646。为了避免概念混淆,IANA(Internet Assigned Numbers Authority,互联网号码分配局)使用US-ASCII来称呼该字符集。至于为什么定义7位而不是一下子8位,当时是个“锱铢必较”的时代,内存太紧俏太贵了,能省一点是一点哈,够用就行。
尽管能表示英文字母,但是还是有很多字符还是无法表示,比如德文中的öäüß等,于是不够用了,然后就出现了扩展ASCII码(Extended ASCII),也就是将ASCII码从7位扩展成了8位,这样一共就有了256个字符。EASCII码比ASCII码扩充出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号。这里,就开始有了区分,对应的标准并不是一个,而是一系列的8位字符集标准,在ISO标准中叫做ISO 8859,全称为ISO/IEC 8859,现在定义了15个字符集(这里有16个,应该是希伯来语一个是视觉顺序,一个是逻辑顺序,为1个,猜想,未验证)。
上面的这些字符问题,基本解决了,下面就是比较麻烦的了。上面的字符,用1个byte都能够表示完整了,叫做SBCS(Single Byte Character Set)。但是咱们是象形文字啊,几千个字符,这小小256个地方怎么够用呢?于是没办法,扩1个byte吧,于是叫DBCS(Double Byte Character Set)。对于CJK语言(以中文、日文、韩文为主的东亚语言),基本上使用2个byte表示一个字符的编码方式来进行表示。具体了解的有日文、简体中文、韩文、繁体中文(至于泰文并不是很清楚,据有些同学说是3字节编码?资料没有找到很确切的,希望了解的同学指导一下)。
我们最熟悉的当然是简体中文了,所以就看这个,其余除了以前玩大宇游戏的时候,知道“大五码”(Big 5),用过南极星,就了解不深了。这里就不做介绍了。
对于简体中文编码,最开始的就是GB2312编码(GB 2312-80),是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,又稱GB0,由中国国家标准总局发布,1981年5月1日实施。GB2312编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。
GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。
尽管覆盖了99.75%,但是还是有没覆盖的,所以之后又出现了GBK和GB-18030。这里对于编程人员的建议就是,如果需要更全的字符覆盖,就使用GB-18030,如果是嵌入式系统,需要在更小的内存中实现较多的功能,使用GB-2312即可,甚至在某些场合,可以使用其子集(一般嵌入式系统需要显示的字符是可以统计出来的,有些字符并不需要)。
这样,对于非Unicode部分的字符集,基本上要了解的都介绍了。不过,如何使用呢?我Latin-1的和GB 2312的,其同一个十六进制,对应的字符不一样、同样用“你”的编码来说,对应为0xC4E3,对应到Latin-1中,就是两个字符,Äã,两个是不能共存的。
在这种情况下,有一个新的概念出现了:
为了使用不同的字符集,我们使用不同的codepage。不同的codepage对应不同的字符集。对于简体中文,其codepage为936;日文的codepage为932;韩文的codepage为949;繁体中文的codepage为950。我们可以参考Windows下面的Region and Language Option设置,对于“非Unicode”语言设置,选择的就是特定的codepage。
字体文件的显示,也是经过长时间的演变过来的。一开始的字体为“点阵字体”,后来出现了PostScript字体,TrueType字体(True Type Font, TTF),我们这里就看一下具体点阵字体的显示过程。一般嵌入式方案或者有些游戏中还是使用该种显示。这里我们使用GB 2312的一个点阵字体方案,叫做“hzk16.fnt”(最后部分有下载,也可google到),是提供16*16的点阵字体给用户使用。这里我写了一个比较简单的程序来进行显示:
1 #include2 #include 3 #include <string.h> 4 5 int printHZK16(char gbcode[3]); 6 7 int main() 8 { 9 char input[10]={0}; 10 while(1) 11 { 12 printf("输入中文,一次一个字符:"); 13 printf("\n"); 14 scanf("%s",input); 15 printHZK16(input); 16 printf("\n"); 17 } 18 19 return 0; 20 } 21 22 int printHZK16(char gbcode[3]) 23 { 24 //要读出的汉字,GB2312编码 25 unsigned char incode[3]; 26 unsigned char qh = 0, wh = 0; 27 unsigned long offset = 0; 28 char mat[16][2] = {0}; 29 FILE *HZK = 0; 30 int i,j,k; 31 32 memcpy(incode, gbcode, 3); 33 34 //每个汉字,占2个字节,取其区位码 35 qh = incode[0] - 0xa0;//获得区码 36 wh = incode[1] - 0xa0;//获得位码 37 offset = (94*(qh-1)+(wh-1))*32; //得到偏移位置 38 //打开hzk16.fnt文件 39 if((HZK=fopen("hzk16.fnt", "rb")) == NULL) 40 { 41 printf("Can't Open hzk16\n"); 42 getchar(); 43 return 0; 44 } 45 //根据 offset进行定位 46 fseek(HZK, offset, SEEK_SET); 47 //读取32字节字模 48 fread(mat, 32, 1, HZK); 49 fclose(HZK); 50 51 //显示 52 for(i=0; i<16; i++) 53 { 54 for(j=0; j<2; j++) 55 { 56 for(k=0; k<8; k++) 57 { 58 if(mat[i][j] & (0x80>>k)) 59 { 60 //测试为1的位则进行显示 61 printf("%c",'#'); 62 } 63 else 64 { 65 printf("%c",' '); 66 } 67 } 68 } 69 printf("\n"); 70 } 71 getchar(); 72 return 1; 73 }
效果如下:
写得有点累了,放假还是休息休息吧,这篇就先介绍非Unicode部分了;Unicode部分以及其他相关的,在下一篇再说吧。有什么疑问问题,欢迎提出来。
hzk16.fnt文件下载