为了可以使 windows 桌面应用可以支持西班牙语,并且在无法使用 QT 等现代具有多语言翻译模块的背景下,尝试设计软件显示多种语言。
背景
为了可以使 windows 桌面应用可以支持西班牙语,并且在无法使用 QT 等现代具有多语言翻译模块的背景下,尝试设计软件显示多种语言。
西班牙语中的字符
西班牙语字母表共有27个字母,包括:
- 26个基础拉丁字母(与英语相同),
- 再加上一个特殊字母 “ñ”。
除此之外,西班牙语中还有以下常见的带附加符号的字符:
- 带重音符号(á, é, í, ó, ú):用于表示重读音节的位置。
- 分音符(ü):用于特殊情况下标示发音,例如 pingüino(企鹅),表示字母 “u” 应发音。
虽然这些带附加符号的字母并未单独计入字母表,但在西班牙语书写和阅读中仍然非常重要。
特殊标点符号的使用:
句首和句尾分别使用 ¿? 和 ¡! 表示疑问句和感叹句:
- 如:“¿Quieres salir del cliente de administración de red?” —— 是否退出网管客户端?
- 如:“¡Contraseña incorrecta!” —— 密码错误!
ANSI 编码
-
“ANSI 编码”不是正式标准名:
- 虽然名字中有“ANSI”,但“ANSI 编码”不是指 ANSI 官方制定的某个明确的字符编码标准。
- 在微软文档中,“ANSI 编码”常指 Windows 的默认“本地代码页”(ACP,Active Code Page)。
-
“ANSI 编码”会随系统语言/区域设置变化:
- 简体中文环境:ANSI 编码 = Code Page 936 = GBK。
- 英文(美国)/西欧环境:ANSI 编码 = Code Page 1252 = Windows-1252。
- 其他国家/地区的 Windows 系统可能采用不同的代码页(如 1251、932 等)。
-
ANSI ≠ ASCII:
- 有时初学者会将“ANSI 编码”与“ASCII”混淆,实际 ANSI 编码一般是基于 ASCII 的扩展,但字符集远大于 ASCII。
-
“ANSI 编码”≠ Unicode:
- Unicode(如 UTF-8、UTF-16)是一种跨平台、跨语言的统一编码方式。
- ANSI 编码不具备这种兼容性,容易出现乱码。
ASCII 编码
ASCII(American Standard Code for Information Interchange),中文通常译作“美国信息交换标准代码”,是一种基于拉丁字母的 7 位字符编码方案。它主要包括:
-
可显示字符:
- 英文字母(大小写)
- 阿拉伯数字(0–9)
- 标点符号及其他常用符号
-
控制字符:
- 用于控制设备(如打印机、终端等)行为的一些特殊字符(如换行、回车、制表符等)
主要特点
- 范围: ASCII 编码使用 7 位二进制数表示字符,编码值从 0 到 127。(Extended ASCII 使用 8 位,但不是标准 ASCII 的一部分。)
- 用途广泛: 几乎所有现代字符编码方案都兼容 ASCII,或将其作为子集。例如:UTF-8、ISO-8859-1、GBK 等编码在 ASCII 范围内的字符通常与 ASCII 保持一致。
- 历史地位: ASCII 在 20 世纪 60 年代就已标准化,是最早的一批计算机字符编码标准之一,对后续的字符编码体系产生了深远影响。
容易与 ANSI 的混淆
“ANSI”是 美国国家标准学会(American National Standards Institute) 的缩写,也常被用来泛指 Windows 系统默认使用的某些本地编码(如 Windows-1252)。然而它:
- 并不是 ASCII,
- 也与 ASCII 在设计理念和字符范围上不同,
- 两者不应混为一谈。
GB2312、GBK 和 GB18030 编码
GB2312
- 中国最早的简体中文字符集标准,由国家标准局于 1980 年发布。
- 收录字符: 共 6763 个汉字,覆盖简体中文常用字符。
- 编码方式: 使用双字节编码表示汉字,ASCII 字符使用单字节编码。
- 局限性: 仅支持简体中文和部分符号,不支持繁体字、生僻字及其他语言字符。
GBK
- 是 GB2312 的扩展版本,由中国国家标准化管理委员会于 1995 年发布。
- 收录字符: 约 21,000 个汉字,大幅增加了繁体字和生僻字。
- 编码方式: 双字节编码,向下兼容 GB2312。
- 特点: 支持部分繁体字,但不包含藏文、蒙古文等少数民族文字。
GB18030
- 是 GBK 的进一步扩展,最新版本为 GB18030-2022(于 2022 年发布)。
- 收录字符: 完整支持 Unicode 的全部字符,包括所有汉字(简体、繁体)、生僻字、少数民族文字,甚至覆盖日文、韩文、部分符号等。
- 编码方式: 变长编码(可使用单字节、双字节或四字节),向下兼容 GBK 和 GB2312。
- 特点: 是中国官方的 强制性编码标准,所有在中国境内销售的软件产品必须支持 GB18030 编码。
编码对比表:
GB2312 | GBK | GB18030 | |
---|---|---|---|
制定时间 | 1980 年 | 1995 年 | 2000 年(最新版本为 2022 年) |
编码长度 | 单字节(ASCII)+ 双字节(汉字) | 单字节(ASCII)+ 双字节(汉字) | 变长编码(单字节、双字节、四字节) |
收录字符数 | 约 6,763 个汉字 | 约 21,000 个汉字 | 支持全部 Unicode 字符(超过百万字符) |
支持繁体字 | 否 | 是 | 是 |
兼容性 | 不支持繁体、生僻字和其他语言字符 | 支持繁体字,不支持部分民族文字 | 向下兼容 GBK、GB2312,支持所有 Unicode 字符 |
法律地位 | – | – | 中国强制性国家标准(GB18030) |
Unicode 编码
Unicode 是一个全球统一的“字符大字典”,目标是把全世界所有字符(英文、汉字、阿拉伯文、日文、韩文、特殊符号、甚至表情符号等)都纳入到一个统一的标准体系里。
每个字符在 Unicode 中都有一个唯一编号(码点,code point) 通过这样一种方式,Unicode 确保了字符编码的统一性,避免了过去各种字符集相互冲突的问题。
下面介绍几种 Unicode 的实现方式:
UTF-8
UTF-8 是一种编码方式(Encoding),用于将 Unicode 中的码点转换为计算机可处理的二进制字节序列。
UTF-8 的特点:
- 是 Unicode 最流行的实现方式之一。
- 变长编码(1–4 个字节):
- 英文字符(ASCII 范围)仅使用 1 个字节。
- 常用汉字、日文等非 ASCII 字符通常使用 3 个字节。
- 生僻字符、表情符号等可能使用 4 个字节。
- 与 ASCII 编码完全兼容(ASCII 字符的 UTF-8 编码与 ASCII 完全一致)。
UTF-8 与 UTF-8 with BOM
UTF-8 with BOM(Byte Order Mark)是 UTF-8 的一种带有标记的形式,用来标识文件使用 UTF-8 编码。
-
什么是 BOM:
BOM(字节顺序标记)是一个特殊的 Unicode 字符(U+FEFF),在 UTF-8 中对应的字节序列为EF BB BF
。它可以出现在文件开头,用于指示编码格式。 -
作用:
添加 BOM 有助于某些编辑器或软件自动识别文件编码为 UTF-8。 -
注意事项:
- UTF-8 不涉及字节序,因此 BOM 并非必须。
- 跨平台开发或服务器环境中,通常推荐使用不带 BOM 的 UTF-8文件。
- Windows 上的一些编辑器(如记事本)保存 UTF-8 文件时默认带 BOM。例如,配置文件经记事本保存后添加了 BOM,可能导致程序读取失败。
UTF-16 编码
UTF-16 是另一种 Unicode 编码方式,使用 2 个或 4 个字节表示一个字符。
UTF-16 的特点:
- 变长编码(2 或 4 字节):
- 对于 BMP(基本多语言平面,码点范围 U+0000 ~ U+FFFF)中的字符,使用一个 UTF-16 单元(2 字节)。
- 对于超出 BMP 的字符(如 emoji、大量生僻字),使用一对代理项(surrogate pair),总共占用 4 字节。
UTF-32 编码
UTF-32 是一种固定长度的 Unicode 编码方式,每个字符使用固定的 4 个字节。
UTF-32 的特点:
- **固定长度:**每个字符占用 4 个字节(实现简单,但空间占用大)。
- 与 ASCII 编码在字符值层面兼容(ASCII 范围字符的 UTF-32 编码与其 Unicode 编码值一致)。
C/C++ 语言中的字符类型
char
- 最基本的字符类型,通常占用 1 字节
- 默认是否为
signed
取决于平台(如 x86 是 signed,但非所有架构一致) - 主要用于存储 ASCII 字符或 UTF-8 编码的文本(但标准本身不绑定具体编码)。
- 衍生类型:
unsigned char
:无符号版本signed char
:有符号版本
wchar_t
- 宽字符类型,大小依赖于平台:
- Windows 上通常为 16 位
- Unix/Linux 上通常为 32 位
- 用于表示宽字符,在国际化应用中曾广泛使用,但并不等价于 Unicode。
- 某些库函数(如
wcout
、wprintf
)在不同平台上处理宽字符的行为可能不同 - 字符串字面量
L"..."
对应wchar_t
类型。
char8_t
(C++20 起)
- 与
char
一样占用 1 字节(8 位),但含义更明确:专用于存储 UTF-8 编码单元。 - 是一种独立类型,不能与
char
互相隐式转换,以避免混淆本地编码与 UTF-8。 - 不同类型字符串(如
string
vsu8string
)不可直接拼接,需要显式转换 - 字符串字面量
u8"..."
对应char8_t
类型。 - 类型间转换需显式进行,例如使用
reinterpret_cast
。
char16_t
(C++11 起)
- 占用 16 位(2 字节),用于存储 UTF-16 编码单元。
- 同样是独立类型,不与
char
、wchar_t
互相隐式转换。 - 字符串字面量
u"..."
对应char16_t
类型。
char32_t
(C++11 起)
- 占用 32 位(4 字节),用于存储 UTF-32 编码单元,可以表示所有 Unicode 码点(范围 U+0000 ~ U+10FFFF)。
- 是一种独立类型,不与
char
、wchar_t
做隐式转换。 - 编码简单,但占用空间大
- 字符串字面量
U"..."
对应char32_t
类型。
总结对比表
类型名称 | 字节/位大小 | 主要用途/编码类型 | 字符串字面量前缀 | 引入版本 / 说明 |
---|---|---|---|---|
char |
通常 1 字节 | 存储 ASCII 或 UTF-8(不强制) | "" |
最基础类型,有符号/无符号依平台而定 |
wchar_t |
Windows: 16 位 / Linux: 32 位 | 早期宽字符类型,支持国际化,但非专属 Unicode | L"" |
宽字符类型,平台相关 |
char8_t |
1 字节(8 位) | 专门存储 UTF-8 编码 | u8"" |
C++20 引入,独立类型 |
char16_t |
2 字节(16 位) | 存储 UTF-16 编码单元 | u"" |
C++11 引入,独立类型 |
char32_t |
4 字节(32 位) | 存储 UTF-32 编码单元,表示所有 Unicode 码点 | U"" |
C++11 引入,独立类型 |
C++ STL 中的字符串类型
C++ 提供了 std::basic_string<T>
模板,可以为不同字符类型构建对应的字符串类型:
STL 类型 | 模板参数 | 用途说明 |
---|---|---|
std::string |
std::basic_string<char> |
常规字符串,默认用于 ASCII / UTF-8 |
std::wstring |
std::basic_string<wchar_t> |
宽字符字符串,国际化场景中使用 |
std::u8string |
std::basic_string<char8_t> |
UTF-8 字符串(C++20 起支持) |
std::u16string |
std::basic_string<char16_t> |
UTF-16 字符串 |
std::u32string |
std::basic_string<char32_t> |
UTF-32 字符串 |
Windows 平台字符串处理
Windows 对宽字符的支持
在 Windows 平台上,系统内部全面采用 Unicode(严格来说是 UTF-16 LE 编码)表示文本内容。为兼容早期系统,Windows API 分为 ANSI 版本和 Unicode 版本,分别以 A
和 W
为后缀:
CreateFileA
:参数为char*
,根据当前系统的本地代码页(ACP)进行编码解释。CreateFileW
:参数为wchar_t*
,使用 UTF-16 编码,原生支持 Unicode。
Windows 宏自动切换机制:
Windows 头文件使用宏来自动映射 Unicode 或 ANSI 版本的 API。例如:
1 |
这意味着只要定义了 UNICODE
(或 _UNICODE
),就能透明地调用 W
版本的 API 函数。
常见字符类型
TCHAR
:可移植字符类型,在启用UNICODE
宏时表示wchar_t
,否则为char
。WCHAR
:Windows 上的宽字符类型,等价于wchar_t
,占用 2 字节(16 位),用于表示 UTF-16 编码的字符。LPWSTR
/LPCWSTR
:分别表示“指向宽字符串的指针”和“指向常量宽字符串的指针”,用于 API 参数传递。
Windows API 中的 Unicode 处理方式
使用 UTF-16 编码字符串
Windows 中的宽字符字符串通过 UTF-16 编码表示。示例如下:
1 | wchar_t* ws = L"示例字符串"; |
字面量 L"..."
会在编译时转换为 UTF-16 字节序列(LE)。
多字节(如 UTF-8)与宽字符互转
若需支持 UTF-8 或其他多字节编码的输入,可以使用 Windows 提供的编码转换函数:
1 | int MultiByteToWideChar( |
对应地,使用 WideCharToMultiByte
可将宽字符转为指定编码(如 UTF-8、GBK、系统默认 ACP 等)。
控制台下的编码设置
在 Windows 控制台中在中文系统中默认编码为 GBK 编码,输出 UTF8 文本需要设置控制台代码页为 UTF-8 编码:
- 执行命令:
chcp 65001
切换到 UTF-8 代码页 - 或者使用
WriteConsoleW
直接输出wchar_t*
字符串(UTF-16) - 注意
std::wcout
输出非 ASCII 字符时,控制台必须能正确支持字体和编码,否则仍可能出现乱码
Linux 平台字符串处理
编码机制与字符处理方式
Linux 系统中,对字符串的处理本质上是对“字节序列”的处理:
- 系统调用层(如
open
、read
、write
)不关心字符串的编码,只看是否是合法的字节流。 - 编码的解释主要发生在用户空间(应用层)或库函数中。
现代 Linux 发行版几乎默认使用 UTF-8 编码:
- 通常环境变量中会有:
LANG=zh_CN.UTF-8
、LC_CTYPE=en_US.UTF-8
等 - Shell、终端、文本编辑器、Glibc 函数(如
wprintf
)等都默认使用 UTF-8
因此,在 Linux 上编写 UTF-8 兼容代码通常比 Windows 更简单和自然。
C++ Builder 中的字符串类型
在 C++ Builder 中,UnicodeString
是最常用的字符串类型,也是 VCL 和 FMX 框架中默认的字符串类型。
早期版本(如 C++ Builder 2007 及之前)默认使用的是 AnsiString
。从 C++ Builder 2009 开始,默认字符串类型转为 UnicodeString
,以更好地支持 Unicode 和多语言应用。
在现代 C++ Builder 项目中,与控件之间的字符串交互(如获取或设置控件文本)默认都使用 UnicodeString
。
AnsiString
-
编码形式:
使用 单字节字符,或者依赖指定代码页的多字节字符集(MBCS),常被称为“ANSI 编码”或“本地编码”。 -
支持自定义代码页(新版本):
在较新的 C++ Builder 版本(如 10 Seattle、10.1 Berlin、10.2 Tokyo、10.3 Rio、10.4 Sydney、11 Alexandria 等)中,可使用模板AnsiStringT<codepage>
显式指定字符编码,例如:1
AnsiStringT<936> str_gbk; // 指定 GBK 编码
-
局限性:
- 所能表示的字符范围依赖于指定的代码页。
- 如果某些字符不在目标编码内,将会出现乱码或数据丢失。
- 不推荐在多语言应用中继续使用
AnsiString
,应使用UnicodeString
。
UnicodeString
-
编码形式:
使用 UTF-16 编码,每个字符占用 2 字节。 -
兼容 Windows 宽字符 API:
可通过UnicodeString::c_str()
方法将字符串转换为const wchar_t*
,用于与 Windows 原生的W
系列 API 互通,例如:1
2UnicodeString str = L"标题";
MessageBoxW(NULL, str.c_str(), L"提示", MB_OK); -
默认字符串类型(C++ Builder 2009 起):
所有与界面控件交互的字符串默认类型均为UnicodeString
,无需手动转换。 -
注意:
- 第一个字符从 1 开始而不是 0
1 | UnicodeString str = L"0123546"; |
总结对比表
类型名称 | 编码方式 | 字节大小 | 可否指定代码页 | 是否支持 Unicode | 与 Windows API 的兼容性 |
---|---|---|---|---|---|
AnsiString |
单字节/MBCS | 1 字节/字符 | ✅(AnsiStringT<codepage> ) |
❌ 依赖代码页,无法覆盖全部 Unicode | 部分支持(需使用 ANSI API) |
UnicodeString |
UTF-16 | 2 字节/字符 | ❌ | ✅ 全面支持 Unicode | ✅(支持 Windows W 系列 API) |
C++ Builder 中的多语言字符串处理
使用 L""
常量初始化 UnicodeString
1 | UnicodeString uniStr = L"Hello, 你好!"; |
使用宽字符字面量 L""
可直接初始化 UnicodeString
,该类型以 UTF-16 编码存储字符。
AnsiString
转换为 std::string
1 | AnsiString ansiStr = "Hello, 你好!"; |
std::string
转换为 AnsiString
1 | std::string str = "Hello, 你好!"; |
AnsiString(UTF-8)
转换为 UnicodeString
使用函数 MultiByteToWideChar
将 UTF-8 编码的 char*
字符串转换为 UnicodeString
。
1 | UnicodeString UTF8ToUnicode(const char* str) |
UnicodeString
转换为 AnsiString(UTF-8)
1 | AnsiString UnicodeToUTF8(UnicodeString str) |
UTF-8 与 GB2312 编码转换说明
文件编码与显示编码不一致, UTF-8 文件用 GB2312 打开,常见乱码,需在编辑器中指定正确编码。
-
GBK 是对 GB2312 的超集,因此:
- GB2312 → UTF-8 可无损转换
- UTF-8 → GB2312,无法反向完整转换,非交集字符会变乱码或问号
-
示例说明:
1
GB2312 不支持字符串 "ñáéíóúü"
字符串常量初始化
UnicodeString
支持使用宽字符常量 L""
初始化。
1 | UnicodeString uniStr = L"Hello, 你好!"; |
- 建议源码文件保存为 UTF-8 编码,否则如使用 GBK 编码时,部分非 ASCII 字符(如中文、ñáéíóúü)可能出现乱码。
- 编译器将
L""
文字编译为 UTF-16 编码,存储于UnicodeString
中。
日志打印(编码转换示例)
界面读取内容为 UnicodeString
类型,打印日志时应转换为 UTF-8 编码再传入格式化函数。
1 | UnicodeString str = L"Hello, 你好!"; |