Programming Windows, Fifth Edtion学习笔记(2.2)

类别:编程语言 点击:0 评论:0 推荐:

Programming Windows, Fifth Edtion学习笔记
鼹鼠

介绍Unicode
In the first chapter, I promised to elaborate on any aspects of C that you might not have encountered in conventional character-mode programming but that play a part in Microsoft Windows. The subject of wide-character sets and Unicode almost certainly qualifies in that respect.

Unicode是一个16位的字符集,它可以移植到所有主要的计算机平台并且覆盖几乎整个世界。它也是单一地区的;它不包括代码页或者其它让软件很难读写和测试的复杂的东西。现在还没有一个合理的多平台的字符集可以和它竞争。

Wide Characters and C
Few programmers are aware that ANSI/ISO 9899-1990, the "American National Standard for Programming Languages—C" (also known as "ANSI C") supports character sets that require more than one byte per character through a concept called "wide characters." These wide characters coexist nicely with normal and familiar characters.

ANSI C也支持多字节字符,如支持中文,日文和韩文的windows版本。然而,这些多字节字符在后续字符的意思表达上使用呢单个字符的字符串来对待。对比一下,宽字符要比通常的字符要宽并且包含了一些编译器的问题。
宽字符不一定就是UNICODE。UNICODE是可能的宽字符编码。

Wide characters aren't necessarily Unicode. Unicode is one possible wide-character encoding. However, because the focus in this book is Windows rather than an abstract implementation of C, I will tend to speak of wide characters and Unicode synonymously.

The char Data Type
想一下,我们都应该熟悉在我们的C程序中使用char数据类型来定义和存储字符和字符串。但是为了简化对C如何处理宽字符的理解,让我们先复习一下在win32程序中的字符定义

下面的语句定义了一个初始化了的变量,包含了一个字符:
char c = `A' ;
变量c需要1字节的空间并且用十六进制值0x41来初始化,这是从ASCII编码中来的字符A的值。

你也可以定义一个指向字符串的指针,如:
char * p ;
因为Windows是32位的操作系统,指针变量p需要4字节来存储。你更可以给这个指针初始化一个字符串:
char * p = "Hello!" ;
p仍然需要4字节来存储。字符串会存储到一个静态内存中并使用7字节,其中6个字节为字符,并以0来结束。

You can also define an array of characters, like this:
char a[10] ;

In this case, the compiler reserves 10 bytes of storage for the array. The expression sizeof (a) will return 10. If the array is global (that is, defined outside any function), you can initialize an array of characters by using a statement like so:

char a[] = "Hello!" ;

If you define this array as a local variable to a function, it must be defined as a static variable, as follows:

static char a[] = "Hello!" ;

In either case, the string is stored in static program memory with a 0 appended at the end, thus requiring 7 bytes of storage.

宽字符
Nothing about Unicode or wide characters alters the meaning of the char data type in C. The char continues to indicate 1 byte of storage, and sizeof (char) continues to return 1. In theory, a byte in C can be greater than 8 bits, but for most of us, a byte (and hence a char) is 8 bits wide.

C中的宽字符基于wchar_t数据类型,wchar_t定义在几个头文件中,包括WCHAR.H:
typedef unsigned short wchar_t ;
因此,wchar_t数据类型等价于一个unsigned short integer,16位宽。

定义一个包含一个宽字符的变量,使用下面的语句:
wchar_t c = `A' ;
c是2字节的值0x0041,这是A在UNICODE中的表示。

同样,你也可以定义一个初始化了的宽字符串指针:
wchar_t * p = L"Hello!" ;
值中的首字母L是指示编译器该字符串被存储在宽字符中。这个指针需要4个字节来存储,这个字符串中每个字符占用了2位并以双位地0作为结尾,所以该变量占用了14位的空间。

类似地,可以定义一个宽字符数组:
static wchar_t a[] = L"Hello!" ;
这个字符串同样需要14位来存储,sizeof(a)将返回14。

wchar_t c = L'A' ;

But it's usually not necessary. The C compiler will zero-extend the character anyway.

宽字符类库函数
我们都知道如何得到字符串的长度。比如:
char * pc = "Hello!" ;
我们可以调用
iLength = strlen (pc) ;    //iLength会得到6,是字符串的长度

好!我们定义一个宽字符串的指针:
wchar_t * pw = L"Hello!" ;
iLength = strlen (pw) ;

问题出来了。首先,C编译器会给你一些警告信息,或许包含下面的信息:
`function' : incompatible types - from `unsigned short *' to `const char *'
它告诉你strlen函数被定义为接收字符指针类型,现在确得到一个unsigned short。你可以继续编译和运行程序,但是你会发现iLength被设置成了1,What happened?
长度为6的字符串"Hello!"有16位的值:
0x0048 0x0065 0x006C 0x006C 0x006F 0x0021
它们通过Intel处理器存储在内存中:
48 00 65 00 6C 00 6C 00 6F 00 21 00
strlen函数,假定它试图去寻找一个字符串的长度,计算首字符48,但是第二个字符是0,就意味着该字符串结束了。
这个小代码清楚地解释了C语言和运行时库函数。编译器把字符串L"Hello!"解释成16位short整型并存储到wchar_t数组。编译器也处理其他地一些操作,但是运行库函数如strlen在like时候被加入。这个函数期待包含单个字符的字符串。当他们面对宽字符串的时候,他们不能如我们所期待的那样执行。

Oh, great, you say. Now every C library function has to be rewritten to accept wide characters. Well, not every C library function. Only the ones that have string arguments. And you don't have to rewrite them. It's already been done.

The wide-character version of the strlen function is called wcslen ("wide-character string length"), and it's declared both in STRING.H (where the declaration for strlen resides) and WCHAR.H. The strlen function is declared like this:
size_t __cdecl strlen (const char *) ;

and the wcslen function looks like this:

size_t __cdecl wcslen (const wchar_t *) ;

So now we know that when we need to find out the length of a wide-character string we can call

iLength = wcslen (pw) ;

The function returns 6, the number of characters in the string. Keep in mind that the character length of a string does not change when you move to wide characters—only the byte length changes.

All your favorite C run-time library functions that take string arguments have wide-character versions. For example, wprintf is the wide-character version of printf. These functions are declared both in WCHAR.H and in the header file where the normal function is declared.

Maintaining a Single Source
使用Unicode当然有一些不利条件。首要的所有的你程序中的字符串将占用两倍的空间。另外,你会注意到宽字符运行时库比通常的函数要大。因为这些原因,你可能需要给你的程序用ASCII字符串和Unicode字符串实现两个版本。最好的方法是维持一个源码文件,你可以用ASCII或者Unicode来进行编译。

有一个小问题,因为运行时库函数拥有不同的名称,允许你直接定义字符,所以会出现讨厌的前缀字符L。

使用Visual C++的包含头文件TCHAR.H是一个答案。这个头文件不是ANSI C的标准。因此每个函数和宏定义中被加载上一个下划线。TCHAR.H为需要字符串参数的普通的运行时库函数提供了一组交替的名字(比如:_tprintf和_tcslen)。有时把某个函数叫做"generic"的名字,因为他们可以用Unicode和非Unicode的函数。

如果一个_UNICODE的标志符被定义,TCAHR.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问题。如果_UNICODE标识符被定义,宏调用_T被定义成如下:
#define __T(x) L##x
这是模糊的语法,但是它是C处理器的ANSI C标准。另一对标记叫做"token paster",它导致字符L追加到宏参数中。因此,如果宏的参数是"Hello!",那么L##x就是 L"Hello!"。

如果_UNICODE标识符没有被定义,_T的宏就是简单的定义如下:
#define __T(x) x
两个宏都被定义为了_T:
#define _T(x) __T(x)
#define _TEXT(x) __T(x)

哪一个用到Win32控制台程序是主要靠你如何选择。基本上,你一定要定义你的字符串在_T或_Text宏中,如下:
_TEXT ("Hello!")

这样会稳定地当使用了_UNICODE标志符则采用宽字符,而如果没有则使用8位地字符。

(上学的时候似乎对这些东西都没有太注意,书上介绍得也较少,现在看来还是够基础,这是第二章的第一部分,还得看第二部分)

鼹鼠于西安
2005-1-27

本文地址:http://com.8s8s.com/it/it23410.htm