下面我将分别介绍在 Windows 和 Linux/macOS 两种主流操作系统上的实现方法。

(图片来源网络,侵删)
核心概念
- Unicode: 这是一个字符集,为世界上几乎所有的每个字符都分配了一个唯一的数字(码点,Code Point),
U+4E2D代表“中”。 - UTF-16: Windows 内部使用 UTF-16 来表示字符串,一个
wchar_t类型通常就是一个 UTF-16 编码的字符,宽字符串字面量L"你好"UTF-16 编码的。 - GBK: 这是一个字符编码,是 GB2312 的扩展,包含了更多的中文字符,它使用 1-2 个字节来表示一个字符。
- 转换流程:
- Windows:
UTF-16 (wchar_t)->GBK(使用 Windows API) - Linux/macOS:
UTF-8 (char*)->GBK(使用iconv库)
- Windows:
在 Windows 系统下 (使用 Windows API)
Windows 提供了 WideCharToMultiByte 这个 API 函数,专门用于将宽字符(通常是 UTF-16)转换为多字节字符(如 GBK、UTF-8 等)。
示例代码
#include <stdio.h>
#include <windows.h> // 包含 Windows API 头文件
#include <stdlib.h> // 用于 malloc 和 free
// 函数:将 UTF-16 字符串转换为 GBK 字符串
char* utf16_to_gbk(const wchar_t* utf16_str) {
// 1. 获取转换后的字符串所需缓冲区大小
// CP_ACP 表示使用当前系统的 ANSI 代码页,中文 Windows 下就是 GBK
// 0 表示不进行字符替换,如果遇到无法转换的字符,函数会失败
int gbk_size = WideCharToMultiByte(
CP_ACP, // 代码页: CP_ACP = ANSI 代码页 (GBK)
0, // 转换标志: 0 表示默认行为
utf16_str, // 源字符串 (UTF-16)
-1, // 源字符串长度: -1 表示以空字符结尾
NULL, // 目标缓冲区: 设为 NULL 来查询所需大小
0, // 目标缓冲区大小: 0 表示查询大小
NULL, // 默认字符 (不常用)
NULL // 设置标志 (不常用)
);
if (gbk_size <= 0) {
// 获取大小失败
wprintf(L"WideCharToMultiByte failed to get size. Error: %d\n", GetLastError());
return NULL;
}
// 2. 分配内存
char* gbk_str = (char*)malloc(gbk_size * sizeof(char));
if (gbk_str == NULL) {
wprintf(L"Memory allocation failed.\n");
return NULL;
}
// 3. 执行实际转换
int result = WideCharToMultiByte(
CP_ACP,
0,
utf16_str,
-1,
gbk_str,
gbk_size,
NULL,
NULL
);
if (result == 0) {
// 转换失败
wprintf(L"WideCharToMultiByte failed to convert. Error: %d\n", GetLastError());
free(gbk_str);
return NULL;
}
return gbk_str;
}
int main() {
// 使用宽字符串字面量,这是 UTF-16 编码
const wchar_t* unicode_str = L"你好,世界!Hello, World! 你好,Unicode。";
printf("Original UTF-16 string: %ls\n", unicode_str);
// 调用转换函数
char* gbk_str = utf16_to_gbk(unicode_str);
if (gbk_str != NULL) {
// GBK 字符串是 char* 类型,直接用 printf 打印
// 注意:如果终端是 UTF-8 编码(如现代的 VS Code 终端或 Git Bash),直接打印可能会乱码
// 因为终端期望接收 UTF-8,而我们给了它 GBK。
// 在真正的 GBK 编码的终端(如某些旧版 CMD)中才能正确显示。
printf("Converted GBK string: %s\n", gbk_str);
// 使用完毕后,务必释放内存!
free(gbk_str);
}
return 0;
}
如何编译和运行
-
将代码保存为
unicode_to_gbk.c。 -
使用 Visual Studio 的命令行工具或 MinGW 进行编译:
# 使用 MinGW (gcc) gcc -o unicode_to_gbk.exe unicode_to_gbk.c # 使用 MSVC (cl) cl unicode_to_gbk.c
-
运行生成的
.exe文件。
(图片来源网络,侵删)- 注意:如果你的终端(如 VS Code 的集成终端)是 UTF-8 编码的,
printf打印 GBK 字符串时会显示乱码,这是正常的,因为printf不知道它接收的是 GBK 编码,要在终端正确显示,你需要一个能设置代码页为GBK(936) 的环境,或者在程序中调用SetConsoleOutputCP(CP_GBK)。
- 注意:如果你的终端(如 VS Code 的集成终端)是 UTF-8 编码的,
在 Linux/macOS 系统下 (使用 iconv 库)
Linux 和 macOS 系统默认不提供直接将 UTF-16 转为 GBK 的函数,但它们都提供了标准的 iconv 库,这是一个功能强大的字符编码转换工具。
iconv 通常工作在 char* 指针上,所以最常见的情况是先将 UTF-16 转换为中间编码(通常是 UTF-8),然后再从 UTF-8 转换为 GBK,很多现代系统也支持直接从 UTF-16 转换。
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iconv.h> // 包含 iconv 头文件
#include <errno.h> // 用于错误处理
// 函数:使用 iconv 进行编码转换
// 参数说明:
// - from_encoding: 源编码,如 "UTF-16LE", "UTF-8"
// - to_encoding: 目标编码,如 "GBK"
// - inbuf: 输入缓冲区
// - inbytesleft: 输入缓冲区剩余字节数
// - outbuf: 输出缓冲区 (需要预先分配)
// - outbytesleft: 输出缓冲区剩余字节数
int convert_encoding(const char* from_encoding, const char* to_encoding,
char* inbuf, size_t inbytesleft,
char* outbuf, size_t outbytesleft) {
iconv_t cd = iconv_open(to_encoding, from_encoding);
if (cd == (iconv_t)-1) {
perror("iconv_open failed");
return -1;
}
char *inbuf_ptr = inbuf;
char *outbuf_ptr = outbuf;
size_t result = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft);
iconv_close(cd);
if (result == (size_t)-1) {
perror("iconv failed");
return -1;
}
return 0;
}
int main() {
// --- 场景1: 从 UTF-8 转到 GBK ---
// 在 Linux/macOS 上,字符串字面量通常是 UTF-8 编码
const char* utf8_str = "你好,世界!Hello, World! 你好,Unicode。";
printf("Original UTF-8 string: %s\n", utf8_str);
// 计算输出缓冲区大小 (简单起见,分配一个较大的空间)
// GBK 编码最多一个字符占3字节,UTF-8 最多4字节,这里简单预估。
size_t outbuf_size = strlen(utf8_str) * 4 + 1;
char* gbk_str_from_utf8 = (char*)malloc(outbuf_size);
if (!gbk_str_from_utf8) {
perror("malloc failed");
return 1;
}
memset(gbk_str_from_utf8, 0, outbuf_size);
if (convert_encoding("UTF-8", "GBK", (char*)utf8_str, strlen(utf8_str), gbk_str_from_utf8, outbuf_size) == 0) {
printf("Converted GBK string from UTF-8: %s\n", gbk_str_from_utf8);
} else {
printf("Conversion from UTF-8 to GBK failed.\n");
}
free(gbk_str_from_utf8);
// --- 场景2: 从 UTF-16 转到 GBK ---
// 注意:UTF-16 有字节序问题 (BE/LE)。
// 在 x86/x64 系统上,通常是 UTF-16LE (Little Endian)。
// "中" 的 UTF-16LE 编码是 \xD6 \xD0
unsigned char utf16_le_data[] = {0xD6, 0xD0, 0xC8, 0xFD, 0xFF, 0x0C, 0xCA, 0xA1, 0x48, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x2C, 0x00, 0x20, 0x00, 0x57, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x6C, 0x00, 0x64, 0x00, 0x21, 0x00, 0xD6, 0xD0, 0xCA, 0xA1, 0x2C, 0x20, 0x55, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x63, 0x00, 0x6F, 0x00, 0x64, 0x00, 0x65, 0x00, 0x2E, 0x00, 0x00, 0x00}; // 以 \x00\x00
printf("\nOriginal UTF-16LE data (as hex):\n");
for (int i = 0; i < sizeof(utf16_le_data); i++) {
printf("%02x ", utf16_le_data[i]);
}
printf("\n");
gbk_str_from_utf8 = (char*)malloc(outbuf_size);
if (!gbk_str_from_utf8) {
perror("malloc failed");
return 1;
}
memset(gbk_str_from_utf8, 0, outbuf_size);
if (convert_encoding("UTF-16LE", "GBK", (char*)utf16_le_data, sizeof(utf16_le_data), gbk_str_from_utf8, outbuf_size) == 0) {
printf("Converted GBK string from UTF-16LE: %s\n", gbk_str_from_utf8);
} else {
printf("Conversion from UTF-16LE to GBK failed.\n");
}
free(gbk_str_from_utf8);
return 0;
}
如何编译和运行
- 将代码保存为
unicode_to_gbk_unix.c。 - 使用
gcc编译,并链接iconv库:# -liconv 告诉链接器链接 iconv 库 gcc -o unicode_to_gbk_unix unicode_to_gbk_unix.c -liconv
- 运行生成的可执行文件:
./unicode_to_gbk_unix
- 注意:和 Windows 的情况类似,如果终端默认是 UTF-8,直接打印 GBK 字符串会乱码,在 Linux/macOS 中,你可以使用
export LANG=zh_CN.GBK来临时设置终端的语言环境为 GBK,这样打印就能正常显示了。
- 注意:和 Windows 的情况类似,如果终端默认是 UTF-8,直接打印 GBK 字符串会乱码,在 Linux/macOS 中,你可以使用
总结与最佳实践
| 特性 | Windows (API) | Linux/macOS (iconv) |
|---|---|---|
| 核心函数 | WideCharToMultiByte |
iconv |
| 源编码 | wchar_t (UTF-16) |
char* (通常是 UTF-8) |
| 优点 | 原生支持,与系统深度集成,无需额外库 | 跨平台 (POSIX 标准),支持海量编码 |
| 缺点 | 仅限 Windows,代码不具备可移植性 | 需要链接 libiconv,API 相对复杂 |
| 推荐场景 | 开发 Windows 桌面应用 | 开发跨平台应用、服务器端应用 |
关键点:
- 内存管理:动态分配的内存(
malloc)一定要记得free,否则会造成内存泄漏。 - 错误处理:调用系统 API 或库函数后,务必检查返回值,以判断操作是否成功。
- 字节序:处理 UTF-16 时,一定要注意字节序(大端 BE 或小端 LE),x86/x64 架构的系统默认是小端。
- 终端编码:程序输出的编码和终端期望的编码必须一致,才能正确显示文本,现代开发环境推荐所有内部处理和存储都使用 UTF-8,只在需要与特定旧系统交互时才进行编码转换。

(图片来源网络,侵删)
