Windows API --- Unicode简介 2.1
文章目录
- Unicode简介
- 字符集简史
- char数据类型
- 更宽的字符
- 宽字符库函数
- 维护一个源代码文件(一个很巧妙的设计)
Unicode简介
字符集简史
对于Unicode的发展不再缀叙
- 简而言之: 随着计算机的发展与不断普及,ASCII码已经不足以应对计算机全球化,因此Unicode编码应运而生。Unicode编码是16位,而ASCII码是8位,因此Unicode可以存储更多信息,以应对应对全球化并提供一个标准。 另外一提 Unicode 被认为是“宽字符”,且Unicode只有一个字符集(因此避免了"二义性")。
- 注意: 在发展史上,并不是ASCII码无法适应全球化后就直接出现了Unicode,实际上这期间还有例如 “扩展 ASCII” ,”ANSI字符集” , “DBCS”等等。
- 宽字符 并不一定是 Unicode。 Unicode只是宽字符编码的一种实现,但我们后续的重点是为了研究和学习了解 Windows API , 因此后续我们会将 宽字符和Unicode划等号
char数据类型
为了后续对宽字符的研究,这里对char数据类型进行一个介绍,相信学过c/c++的各位对char数据类型都有自己的认识,但是还请看下去,或许会有一些收获
- 下面声明定义和初始化一个包含单一字符的变量
char c = 'A';
变量c会申请一个字节大小的存储空间而且会用十六进制0X41来初始化这个存储空间。也就是ASCII字母A的符号。
- 讲解 字符串的指针 和 字符数组
-
字符串指针:
char* p = "Hello!";
在32位机器上,指针的大小为4个字节,这里的 指针p指向了 字符串 “Hello!”- 注意: 这里是 指针p指向了 字符串”Hello!” ,这个字符串”Hello!”是存储在静态内存中并使用7个字节的存储空间----其中6个字节存储字符串而另一个字节存储表示字符串结尾的’\0’
-
字符数组: 我们定义一个
char a[10];
这种情况下编译器为数组a保留了10个字节的存储空间。sizeof(a);
表达式返回10。- 如果这个数组是全局的,我们可以属于静态字符串对其进行初始化:
char a[] = "Hello!";
- 如果这个数组是一个局部变量,那么它必须定义为静态变量:
static char a[] = "Hello!";
- 当其是局部变量时,只有把它设为静态的(static),编译器才在正式进入程序前会为其申请内存。
如下图,静态局部和局部的区别:
- 当其是局部变量时,只有把它设为静态的(static),编译器才在正式进入程序前会为其申请内存。
- 如果这个数组是全局的,我们可以属于静态字符串对其进行初始化:
-
总结:无论是以上那种情况,字符串始终被存储在静态内存中,并有一个’\0’在最后,一共需要7个字节的存储空间。
-
更宽的字符
使用Unicode 或者是 宽字符 并不会改变 C语言中的字符数据类型,char类型仍然为1个字节的存储空间,而且
sizeof(char);
继续返回1.
-
C语言的宽字符是基于
wchar_t
数据类型的。 这个数据类型被定义中多个头文件中,包括WCHAR.H
。wchar_t
的本质是 对unsigned short
的封装,即:typedef unsigned short wchar_t;
因此我们也可以看到,wchar_t
实质上是一个无符号的16位数据类型。 -
定义一个包含单个宽字符的变量
wchar_t c = 'A';
- 变量 c 是一个两个字节的值0X0041,这是Unicode中字母A的代表。
- 然而,因为Intel微处理器存储多字节数组是总是最低有效数字有限,说要这些字节在内存是以这样的形式顺序存储的:0X41,0X00。 说人话就是 这里是“小端存储”。 如果要检查Unicode文本的内存存储,务必要记住这点
- 下图是验证c的内存大小是16位,而不是8位。
-
定义并初始化一个指向宽字符串的指针 以及 数组
-
指向宽字符串的指针:
wchar_t* p = L"Hello!";
- 注意: 大写字母L(表示长整型)紧接左引号。这是向编译器表面这个字符串将用宽字符存储-----也就是说,每个字符占2个字节。指针变量p还是运用4个字节的存储空间,但是这个字符串占14个字节的存储空间----每个字符需要2个字节,再加上最后的0需要两个字节。
-
定义数组:
static wchar_t a[] = "Hello!";
- 尽管前面有个L很怪,但是这个L是非常重要的,且L和引号之间不能有空格。只要有了这个L编译器才能知道你想要创建的是宽字符数组。
-
验证 宽字符的存储为 两个字节 , 且在小端存储下,是先存有效数字。
-
如下图
这里第一个字节存储了0X41 , 第二个字节存储0X00。
-
-
单个字符的定义其实也可以加L,即 wchar_t c = L’A’; 也是可以的,这是这个就没有那么必要了。
-
宽字符库函数
- 如果用
strlen
来计算 宽字符串 的长度, 那么结果会是怎么样的呢?- 首先我们知道对于
char* p = “Hello!”
而言,strlen(p);
的结果是 6 , 可是对于wchar_t wp = L”Hello!”;
而言 ,strlen(wp);
会先报个error
,这个error
说明了strlen
接受的参数一个是一个指向char的指针,而不是一个指向unsigned short
的指针。但是我们仍然可以强制编译,但是结果会显示1。
为什么这里的结果会是1呢?- 这是因为对于 宽字符串“Hello!” 而言,它的16位值如下
0X0048 0X0065 0X006C 0X006F 0X0021
- 而由于小端存储的原因,逐个字节来看其存储如下
48 00 65 00 6C 00 6F 00 21 00
- 而对于
strlen
函数,它是以一个字节为单位进行检索的,在检索到0时停止,因此它在计算第一个字符后就停止了,返回1。
- 这是因为对于 宽字符串“Hello!” 而言,它的16位值如下
- 首先我们知道对于
- 看到这里,我们可以知道我们必须对C语言库中那些有字符串参数的函数进行重新,不过幸运的是 这些函数的重写已经被完成了,而不需要我们对其进行重写。
- 宽字符版本的
strlen
函数被称为wcslen
(宽字符的长度)函数,被定义在STRING.H
(也就是定义strlen
被定义的地方)和WCHAR.H
中。- strlen函数的声明如下:
size_t _cdecl strlen(const char*);
- wcslen函数的声明如下:
size_t _cdecl wcslen(const wchar_t*);
- 所以我们可以通过
wcslen
函数来计算wp
的长度,wcslen(wp);
,返回值为6。也就是字符个数为6. - 注意:在使用宽字符时,宽字符串的长度并没有改变,改变的只是字节长度
- strlen函数的声明如下:
维护一个源代码文件(一个很巧妙的设计)
上文我们可以知道,对于宽字符的大小是大于普通字符的,宽字符运行库函数比正常的函数要大,为此可能会想要写两个版本的程序,一个用ASCII字符串,而另一个用Unicode字符串。那么最好的方法是什么呢? 最好的办法是 维护一个单一的源代码文件,但是可以编译成ASCII或者Unicode!!! 【这个设计十分的巧妙,笔者感觉已经是有 泛型 的雏形了,但是对于C语言这个面向过程的语言,如何实现这个设计?,下面我们就要看大神的设计实现和演示了,相信各位会受益,至少笔者是感觉受益良多的】
-
一个问题
因为运行库函数具有不同名称,字符变量的定义也不同,而且宽字符串字面之前必须要加L,如何解决呢? -
解决方案
一个答案被包含在TCHAR.H
头文件中,这个头文件是ANSI C标准的一部分,因此其中定义的每个函数和宏都有一个下划线前缀。TCHAR.H
为那些需要字符串参数的普通运行库函数(例如:_tprintf
和_tcslen
)提供了一系列的替代名称。 这些函数有时被称为“通用”的函数名字。因为它们可以指Unicode或非Unicode版本的函数。
-
具体实现方式
-
如果一个命名为
_UNICODE
的标识符被定义了(即存在宏_UNICODE
)了,并且TCHAR.H
头文件被包含到程序中,_tcslen
就被定义为wcslen
- 定义如下:
#define _tcslen wcslen
- 定义如下:
-
如果
_UNICODE
并没有被定义,那么_tcslen
就被定义为strlen
- 定义如下:
#define _tcslen strlen
- 定义如下:
-
下图对其进行验证。
#undef _UNICODE
是取消宏定义_UNICODE
-
以此类推。
TCHAR.H
也用一个命名为TCHAR
的新的数据类型解决了两个字符数据类型的问题。- 如果
_UNICODE
标识符被定义了,TCHAR
就是wchar_t
定义如下:
typedef wchar_t TCHAR
- 否则的话,
TCAHR
就是一个简单的char
定义如下:
typedef char TCHAR
- 如果
-
好了,现在该解决 宽字符串字面量前L的问题了。这个问题通过一个宏
__T
来解决的。-
或许读者对于宏方面的不是太过了解,对于符号
##
的解释如下:
A##B
是将A和B 合成一个符号,即L##X
在预处理后,表示的是LX
。 -
如果_UNICODE表示被定义,一个叫__T的宏是如下定义的:
#define __T(X) L##X
-
如果_UNICODE标识符没有被定义,宏__T的定义如下:
#define __T(X) X
-
而宏__T的写法还是不太简介,因此有了下面两个宏定义:
#define _T(X) __T(X) #define _TEXT(X) __T(X)
-
Win32控制台程序下使用哪一种取决于你的需求。基本上必须用下列方式将字符串字面定义为_T或_TEXT宏内:
_TEXT("Hello!");
- 这样做的结果就是: 当_UNICODE被定义时,字符串就被解释为由宽字符组成。否则,被解释为 由普通字符组成)
演示如下图:
- 这样做的结果就是: 当_UNICODE被定义时,字符串就被解释为由宽字符组成。否则,被解释为 由普通字符组成)
-
-