zoukankan      html  css  js  c++  java
  • windows程序设计第二章Unicode简介

     

    字符集历史

       莫斯码,Braille盲文

    American标准

    1980年,the coding used on Hollerith cards

    1960年,BCDIC(Binary-Coded Decimal Interchange Code)从6位扩展到8位的EBCDIC。

    1950s后期,American Standard Code for Information Interchange(ASCII), 1967年完成。7位。

    The World Beyond

    7位ASCII的问题是,还有很多常用的符号没有包含进来,比如拉丁文,还有汉字,日文韩文等。

    Extending ASCII

       一个字节8位,还有128个额外的字符可以附加进来。1981年,IBM PC引入了一些声调符,小写希腊字母等。这套扩展的字符集并不是并不适合Windows。

       在Windows1.0中,微软搞了套ANSI字符集,最终定为“American National Standard for Information Processing---8-Bits Single-Byte Coded Graphic Character Sets-Part 1:Latin Alphabet No 1”,简称”Latin 1”

       MS-DOS 3.3 引入了code pages的概念。所谓code pages,即为字符码集到字符集的映射。最初的IBM字符集被称为code page 437,或”MS-DOS Latin US”. 而code page 850被称为”MS-DOS Latin 1”.

      在MS-DOS下,当用户设置PC的键盘,视频显示器或打印机到某一个特定的code page后,字符将由该code page定义给出。但code page的不同,会给应用程序带来很大麻烦。

    双字节字符集

    double-byte character set(DBCS).Windows支持四种DBCS,code page 932(日文),936(简体中文),949(韩文),950(繁体中文)。

    DBCS的一个问题是,ASCII字符是单字节,而其他是双字节的字符。双字节的字符由一个lead byte和trail byte组成,通常可以通过判断一个字符的第一个字节是否是lead byte,来判断它是否是一个双字节的字符。在编程中,这样会有麻烦,比如给定一个字节流,某个字符处的指针,那么该字符的前一个字符的地址是什么呢?由于无法判断前一个字符是单字节的,还是双字节的,必须我们得从字节流的开头处重新parse。

    救星Unicode

        16位字符。取代混乱的多个256字符的code映射,或者DBCS,Unicode是一套统一的字符系统,每个字符为16位,支持65536个字符。

        Unicode的组成是这样的,前128个位ASCII(0x0000 to 0x007F),0x0080~0x00FF为ISO 8859-1的ASCII扩展,000370~0x03FF为希腊字母,0x0400~0x04FF为西里尔字母,0x0530~0x058F为亚美尼亚语 0x0590~0x05FF为希伯来语,0x3000~0x9FFF(CJK)为中日韩的字符总集。

       Unicode最好的地方在于,它只有一个字符集。

       Unicode的缺点是,占内存多。另外,unicode推广不力,还没有被广泛使用,也是其弱点之一。

    宽字符和C语言

         ANSI C(American National Standard for Programming Languages--C)支持宽字符。

        ANSI C也支持多字节字符集。

        宽字符不是unicode,unicode只是宽字符的一种编码(encoding)。但在本书中,意义差不多。

     

    char 类型

       char数组的定义

    char a[]="Hello!";//defined globally
    static char a[] = "Hello!"//defined as a local variable

    它们都存放在程序的静态区。都需要7个字节(还有一个0终结符)。

    宽字符

    typedef unsigned short wchar_t;

    为16位的。定义宽字符及宽字符字符串的例子如下:

            wchar_t c = 'A';//此时c为双字节值0x0041,为unicode中的字母A
    	                //如果机器是小端(least-significant bytes first)
    	                //在内存中的表示为0x41 0x00
    	unsigned char * cp = (unsigned char *)(&c);
    
    	wchar_t b = '高';//在内存中的表示为0xDF 0xB8
    	cp = (unsigned char*)(&b);
    
    	wchar_t * p = L"Hello!";//L让编译器知晓,字符串中的字符是宽字节字符,
    	//一共占14个字节(最后的0终结符也占2个字节)
    
    	//类似的,我们可以如下定义一个宽字符数组
    	static wchar_t a[] = L"Hello!";

    宽字符库函数

    	wchar_t * pw = L"Hello!";
    	//如不加(const char *)强制转换,则会有编译错误
    	//cannot convert parameter 1 from 'wchar_t *' to 'const char *'
    	//加上强制转换后,由于宽字节字符'H'在内存中的内容为0x48,0x00(小端),所以
    	//使用strlen时,结果为1,碰到了0x00,而实际上这个0x00是宽字符的一部分。
    	int iLength = strlen((const char *)pw);
    	
    	//上述例子中,运行库函数strlen是在运行时加载的,它期望的是单字节字符。
    	//为了支持宽字符,运行库加入了相应的函数版本。
    	//strlen的宽字符版本为wcslen(wide-character string length)
    	iLength = wcslen(pw);//结果为6
    

    printf的宽字符版本为wprintf。

    维护一份源代码

         只要修改一个宏,同一份源代码就可以编译成unicode版本或多字节版本。在windows中,一个解决方法是使用TCHAR,它不属于标准C,所以其中的每个函数和定义都有下划线前缀。在TCHAR.H头文件中,如果定义了_UNICODE,

    则有

    #define _tcslen wcslen

    如果_UNICODE没有定义,则有

    #define _tcslen strlen

    同样的,TCHAR在unicode下位wchar_t,在非unicode下为char

    //if _UNICODE defined
    typedef wchar_t TCHAR
    #define __T(x) L##x
    //else
    typedef char TCHAR
    #define __T(x) x

    所以,建议使用_TEXT或_T宏将字符串常量包起来。其中,##是token paste

    宽字符和Windows

    Windows NT内部使用的是16位字符的字符串。Win98只有一小部分函数支持unicode。最好只维护一份源代码,这样可以根据实际情况编译成ascii版本或unicode版本。

    Windows头文件类型

    WINDOWS.H            包括一系列windows头文件

    WINDEF.H               定义了windows中的很多基本类型,包含WINNT.H

    WINNT.H                 对unicode的支持

    在WINNT.H中,首先包含了C头文件CTYPE.H,其中定义了wchar_t。然后定义了CHAR和WCHAR

    typedef char CHAR;
    typedef wchar_t WCHAR;

    WCHAR的匈牙利前缀为wc。

    随后WINNT.H定义了6个数据类型,为指向8位字符的字符串的指针,以及四种数据类型,为指向8位字符常量字符串的指针。如下所示

    typedef CHAR * PCHAR,*LPCH,*PCH,*NPSTR,*LPSTR,*PSTR;
    typedef CONST CHAR * LPCCH,*PCCH,*LPCSTR,*PCSTR;

    N前缀表示”near”,L表示”long”,在Win16中表示指针大小的不同。在Win32中无区别。这里需注意,LPCH等都是类型char *。

    类似的,WINNT.H也定义了宽字符字符串的指针和常量字符串的指针,如下

    typedef WCHAR *PWCHAR,*LPWCH,*PWCH,*NWPSTR,*LPWSTR,*PWSTR;
    typedef CONST WCHAR * LPCWCH,*PCWCH,*LPCWSTR,*PCWSTR;

    使用TCHAR以及相关的指针类型可以把UNICODE和非UNICODE的代码统一起来,如下

    #ifndef UNICODE
    typedef WCHAR TCHAR,*PTCHAR;
    typedef LPWSTR LPTCH,PTCH,PTSTR,LPTSTR
    typedef LPCWSTR LPCTSTR;
    #else
    typedef char TCHAR,*PTCHAR;
    typedef LPSTR LPTCH,PTCH,PTSTR,LPTSTR
    typedef LPCSTR LPCTSTR;
    #endif

    谨记,由于windows.h包含了很多基本的类型,在包含其他头文件时,最好现在最开始包含windows.h.

    综上,有以下三点经验

    1.如明确使用8位字符,请使用CHAR,PCHAR

    2.如明确使用16位字符,请使用WCHAR,PWCHAR,以及加L的字符串常量

    3.若依赖于UNICODE标识符定义如否,请使用TCHAR,PTCHAR和TEXT宏。

    Windows函数调用

         32位的Windows API其实没有MessageBox这个函数,只有MessageBoxA(ASCII版本)和MessageBoxW(宽字节版本)。但是程序员可以放心地使用MessageBox,原因见如下代码:

    int WINAPI MessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT UType);
    int WINAPI MessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT UType)
    #ifndef UNICODE
    #define MessageBox MessageBoxW
    #else
    #define MessageBox MessageBoxA
    #endif

    Windows的字符串函数

        除了C运行库提供的字符串函数外,微软自己也提供了一些变种函数。比如

    	int iLength = lstrlen(pw);
            pString = lstrcpy(pString1,pString2);
    	pString = lstrcpyn(pString1,pString2);
    	pString = lstrcat(pString1,pString2);
    	iComp = lstrcmp(pString1,pString2);
    	iComp = lstrcmpi(pString1,pString2);
    	
    
    

    使用 printf

        坏消息是,在windows程序中,无法使用printf函数,好消息是,可以使用sprintf,该函数可将格式化文本写入buffer。sprintf的用例如下:

    char szBuffer[100];
    sprintf(szBuffer,"The sum of %i and %i is %i",5,3,5+3);
    puts(szBuffer);

       使用sprintf的一个麻烦之处在于,需要考虑buffer的大小。另一个win32平台的函数_snprintf解决了这个问题,它引入了表示buffer大小的参数。sprintf还有一个变形vsprintf,它只有三个参数,前两个参数与sprintf一致,第三个为指向参数数组的指针。而该指针其实是存在栈上的变量,访问这些栈上变量时,需借助于va_list,va_start,va_end等宏。

       sprintf函数即可如下实现:

    int MySprintf(char * szBuffer,const char * szFormat,...)
    {
    	int iReturn;
    	va_list pArgs;
    	va_start(pArgs,szFormat);
    	iReturn = vsprintf(szBuffer,szFormat,pArgs);
    	va_end(pArgs);
    	return iReturn;
    }
    测试之
    	int   inumber = 30;    
    	float   fnumber = 90.0;    
    	char   str[4] = "abc";  
    	char szBuffer[100];
    	MySprintf(szBuffer,"%d %f %s",inumber,fnumber,str);
    	puts(szBuffer);
    	return 0;

    上面的va_start,实际上即将变量szFormat后的变量地址赋予了pArgs。具体的宏va_list,va_end,va_start如下

    typedef char *  va_list;
    typedef va_start _crt_va_start
    #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
    typedef va_end _crt_va_end
    #define _crt_va_end(ap)      ( ap = (va_list)0 )

    后来,微软又加了wsprintf和wvsprintf,可惜的是,它们不支持浮点数格式化。随着宽字符的引入,sprintf人丁兴盛,让人有点糊涂。先总结于下

      ASCII 宽字符 Generic
    参数个数可变      
    标准版本 sprintf swprintf _stprintf
    最大长度版本 _snprintf _snwprintf _sntprintf
    win版本 wsprintfA wsprintfW wsprintf
    数组参数指针      
    标准版本 vsprintf vswprintf _vstprintf
    最大长度版本 _vsnprintf _vsnwprintf _vsntprintf
    win版本 wvsprintfA wvsprintfW wvsprintf

    Formatting Message Box

    下面的程序SCRNSIZE展示了如何实现一个MessageBoxPrintf函数,以接受可变数量的参数,并像printf那样格式化。

    #include <Windows.h>
    #include <tchar.h>
    #include <stdio.h>
    int CDECL MessageBoxPrintf(TCHAR * szCaption,TCHAR * szFormat,...)
    {
    	TCHAR szBuffer[1024];
    	va_list pArglist;
    
    	va_start(pArglist,szFormat);
    
    	_vsntprintf(szBuffer,sizeof(szBuffer) / sizeof(TCHAR),szFormat,pArglist);
    	va_end(pArglist);
    
    	return MessageBox(NULL,szBuffer,szCaption,0);
    
    }
    
    int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
    {
    	int cxScreen,cyScreen;
    	cxScreen = GetSystemMetrics(SM_CXSCREEN);
    	cyScreen = GetSystemMetrics(SM_CYSCREEN);
    
    	MessageBoxPrintf(TEXT("ScrnSize"),
    					 TEXT("The screen is %i pixels wide by %i pixels high"),
    					 cxScreen,cyScreen);
    	return 0;
    }
     
     
     

    该程序展示了屏幕的分辨率。这里有一个CDECL需要解释一下,和__stdcall(WINAPI)一样,都是函数的调用方式。介绍如下
    1.__stdcall声明的函数被调用时,主调方负责对参数压栈,而参数出栈的任务由被调函数完成,这样,被调函数必须知道压栈参数
    的个数,所以,带可变数量参数的函数不能用__stdcall声明。
    2.cdecl声明的函数被调用时,主调方负责对参数的压栈,并在调用返回后,再负责参数出栈,由于主调方知道压入的参数个数,
    所以被调函数可带可变数量参数。这样生成的汇编码会更多,执行程序会更大(调用函数的次数总会比较比较多的嘛)。

    国际化

    本书不涉及,参考Developing International Software for Windows 95 and Windows NT。
    本书的程序将在UNICODE设置与否的情况下成功编译,普遍使用TCHAR和TEXT.并努力不要将byte和字符混淆。

  • 相关阅读:
    C++中类模板的概念和意义
    C++中模板类声明和实现能否分离?
    C/C++ 关于大小端模式,大小端字节序转换程序
    C++中的赋值操作符重载和拷贝构造函数
    C++中的友元
    C/C++内存对齐详解
    C++ 虚函数表、函数地址、内存布局解析
    虚析构函数的必要性(C++)
    C++中的抽象类和接口
    C++中的单例类模板
  • 原文地址:https://www.cnblogs.com/speedmancs/p/1721715.html
Copyright © 2011-2022 走看看