跳转到内容

Windows 编程/Unicode

来自维基教科书,自由的教科书

有关 Unicode 标准的参考,请参见 Unicode

Unicode 简介

[编辑 | 编辑源代码]

Unicode 是一个行业标准,其目标是提供一种方法,使各种形式和语言的文本能够被计算机编码使用。最初,文本字符在计算机中使用字节宽数据表示:每个可打印字符(以及许多不可打印字符或“控制”字符)都使用单个字节实现,这允许总共 256 个字符。然而,全球化带来了对计算机能够适应世界各地许多不同字母的需求。

旧代码称为 ASCII 或 EBCDIC,但很明显,这些代码都无法处理来自世界各地的所有不同字符和字母。Unicode 是解决这个问题的方案。Windows NT 使用“宽”16 位字符集实现其许多核心功能,接近 Unicode 标准,尽管它也提供了一系列与标准 ASCII 字符兼容的功能。

UNICODE 字符通常称为“宽字符”、“通用字符”或“T 字符”。本书可能会互换使用这些术语。

变长字符

[编辑 | 编辑源代码]

在 Unicode 之前,有一种国际化尝试引入了具有变长字符的字符串。一些字符,例如标准 ASCII 字符,将是 1 字节长。其他字符,例如扩展字符集,是 2 字节长。随着 UNICODE 的出现,这类字符格式不再受欢迎,因为它们更难编写,也更难阅读。Windows 仍然维护一些功能来处理变长字符串,但我们不会在这里讨论这些功能。

不幸的是,由于所需的字符数量很快超过了 65,536 个可能的 16 位值,使用宽字符的所有优势都消失了。Windows 实际上使用称为 UTF-16 的方法来存储字符,其中大量字符实际上占用 //两个// 字,这些称为“代理对”。这种发展是在大部分 Windows API 文档编写之后发生的,现在很多都已过时。您永远不应该将字符串数据视为“字符数组”,而应该始终将其视为以 null 结尾的块。例如,始终将整个字符串发送到函数以在屏幕上绘制它,不要尝试绘制每个字符。任何在 LPSTR 后面加上方括号的代码都是错误的。

同时,变长字符字符串在称为 UTF-8 的跨平台标准中强势回归,UTF-8 与 UTF-16 相同,但使用 8 位单元。它的主要优势在于无需两个 API。如果使用 UTF-8,'A' 和 'W' API 将相同,并且由于两者都是可变大小的,因此没有缺点。尽管大多数 Windows 程序员不熟悉 UTF-8,但您可能会看到更多使用非 UNICODE API 的引用。

Windows 实现

[编辑 | 编辑源代码]

Win32 API 将其所有需要文本输入的功能分为两类。一些函数带有“A”后缀(表示 ASCII),另一些则带有“W”后缀(表示宽字符或 Unicode)。这些函数使用宏“UNICODE”进行区分。

#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif

由于这种区分,当您收到编译器错误时,您将在“MessageBoxW”上收到错误,而不是简单地收到“MessageBox”。在这种情况下,编译器没有错误。它只是试图遵循一组复杂的宏。

Unicode 环境

[编辑 | 编辑源代码]

所有需要字符字符串的 Windows 函数都以这种方式定义。如果要在程序中使用 unicode,则需要在包含 windows.h 文件之前显式定义 UNICODE 宏。

#define UNICODE
#include <windows.h>

此外,其他库中的某些函数要求您定义宏 _UNICODE。标准库函数可以通过包含 <tchar.h> 文件来提供 unicode。因此,要在项目中使用 unicode,您需要在项目中进行以下声明。

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>

一些头文件包含如下机制,以便当两个 UNICODE 宏之一被定义时,另一个也会自动被定义。

#ifdef UNICODE
  #ifndef _UNICODE
    #define _UNICODE
  #endif
#endif
#ifdef _UNICODE
  #ifndef UNICODE
    #define UNICODE
  #endif
#endif

如果要编写一个使用 UNICODE 的库,那么在您的头文件中包含此机制可能会有所帮助,这样其他程序员就不必担心包含这两个宏。通常情况下,只定义两个宏中的一个是一个陷阱,所以要注意!

在 C 中,要创建一个宽字符字符串,您需要在字符串前面加上字母“L”。以下是一个例子

char *asciimessage = "This is an ASCII string.";
wchar_t *unicodemessage = L"This is a Wide Unicode string.";

数据类型“TCHAR”定义为,如果未定义 unicode,则为 char 类型,如果定义了 UNICODE(在 tchar.h 中),则为宽类型。为了使字符串在 unicode 和非 unicode 之间可移植,我们可以使用 TEXT() 宏自动将字符串定义为 unicode 或非 unicode

TCHAR *automessage = TEXT("This message can be either ASCII or UNICODE!");

使用 TCHAR 数据类型和 TEXT 宏是使代码在不同环境之间可移植的重要步骤。

此外,TEXT 宏可以写成

TEXT("This is a generic string");
_T("This is also a generic string");
T("This is also a generic string");

所有这三个语句都是等效的。

TEXT 宏通常这样定义

#ifdef UNICODE
#define TEXT(t) L##t
#define _T(t) L##t
#define T(t) L##t
#else
#define TEXT(t) t
#define _T(t) t
#define T(t) t
#endif

Unicode 参考

[编辑 | 编辑源代码]

控制字符

[编辑 | 编辑源代码]

Unicode 字符 0 到 31(U+0000 到 U+001F)属于 C0 控制和基本拉丁块。它们都是控制字符。这些字符对应于 ASCII 集的前 32 个字符。

代码点 十进制等效值 名称 C 转义
U+0000 0 空字符
U+0001 1 报头开始
U+0002 2 文本开始
U+0003 3 文本结束
U+0004 4 传输结束
U+0005 5 询问
U+0006 6 确认
U+0007 7 响铃 '\a'
U+0008 8 退格 '\b'
U+0009 9 水平制表符 '\t'
U+000A 10 换行符 '\n'
U+000B 11 垂直制表符 '\v'
U+000C 12 换页符 '\f'
U+000D 13 回车符 '\r'
U+000E 14 换出
U+000F 15 换入
U+0010 16 数据链路转义
U+0011 17 设备控制 1
U+0012 18 设备控制 2
U+0013 19 设备控制 3
U+0014 20 设备控制 4
U+0015 21 否定确认
U+0016 22 同步空闲
U+0017 23 传输块结束
U+0018 24 取消
U+0019 25 介质结束
U+001A 26 替换
U+001B 27 转义
U+001C 28 文件分隔符
U+001D 29 组分隔符
U+001E 30 记录分隔符
U+001F 31 单元分隔符

注意:在 Windows API 中,控制字符的含义与命令行程序中的含义不同!通常,控制字符根本不会被处理,而是显示为不可打印字符,例如 ExtTextOut() 和所有 GDI 函数。对于 USER 函数,例如 DrawText()、MessageBox() 和多行按钮,'\n' 将被处理以将文本分成行。对于菜单,'\a' 和 '\t' 具有特殊含义并被预处理。要响铃,'\a' 永远不会起作用。为此,请使用 Beep() 或 sndPlaySound()。

下一章

[编辑 | 编辑源代码]
华夏公益教科书