| 特性 | UTF-8 | UTF-16 | UTF-32 |
| 碼位 → 字節 | 1-4 字節 (變長) | 2 或 4 字節 (代理對) | 固定 4 字節 |
| ASCII 兼容 | ✅ 完全兼容 (0-127 單字節) | ❌ 不兼容 | ❌ 不兼容 |
| CJK 常用字 | 3 字節 | 2 字節 (BMP 內) | 4 字節 |
| 內存佔用 (中文為主) | 較大 | **最優** | 最大 |
| 隨機訪問 O(1) | ❌ 需掃描 | ⚠️ 代理對需檢查 | ✅ 完全支持 |
| 主流用途 | **網絡傳輸、文件存儲、Linux/Web 標準** | **Windows 內核、Java/JS/C# 內部字符串、Qt** | 內部處理、字符層面算法 |
UTF-8 編碼規則 (RFC 3629)
U+0000 ~ U+007F: 0xxxxxxx (1 字節)
U+0080 ~ U+07FF: 110xxxxx 10xxxxxx (2 字節)
U+0800 ~ U+FFFF: 1110xxxx 10xxxxxx 10xxxxxx (3 字節) ← 中日韓基本區
U+10000~U+10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (4 字節) ← Emoji、罕用字
UTF-16 代理對
BMP 外字符 (`U+10000+`) 用**高代理** `U+D800~U+DBFF` + **低代理** `U+DC00~U+DFFF` 兩個 16-bit 碼元表示。`wchar_t` 在 Windows 為 16-bit (UTF-16 碼元),Linux 為 32-bit (UTF-32 碼位) —— **跨平台 `wchar_t` 寬度不一,不可直接序列化**。
三、Windows 字符集機制與 API 字符串轉換
代碼頁與「內碼」概念
- **代碼頁** = 字符集到字節映射表 (如 CP936=GBK, CP950=Big5, CP65001=UTF-8, CP1252=西歐)。
- **系統默認 ANSI 代碼頁** (GetACP) 決定「無前綴」字符串字面量編譯編碼、舊版 API 行為。
- **現代 Windows 內核原生 Unicode (UTF-16 LE)**,ANSI API 實為「代碼頁轉換 → 內核 Unicode API」的薄封裝。
關鍵轉換 API (定義於 ``)
// 多字節 (ANSI/代碼頁) → 寬字符 (UTF-16)
int MultiByteToWideChar(
UINT CodePage, // CP_ACP(系統ANSI), CP_UTF8(65001), 936(GBK), 950(Big5)
DWORD dwFlags, // 0 或 MB_ERR_INVALID_CHARS
LPCCH lpMultiByteStr, // 源字符串
int cbMultiByte, // 源字節數 (-1 表以 \0 結尾)
LPWSTR lpWideCharStr, // 目標緩衝區
int cchWideChar // 目標字符數 (含 \0)
);
// 寬字符 (UTF-16) → 多字節 (指定代碼頁)
int WideCharToMultiByte(
UINT CodePage,
DWORD dwFlags,
LPCWCH lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCCH lpDefaultChar, // 無法轉換時的替代字符
LPBOOL lpUsedDefaultChar
);
實戰:GBK ↔ UTF-16 ↔ Big5 全鏈路轉換
char gbk[] = "中文"; // 源 GBK 字節流
wchar_t *utf16;
char *big5;
// 1. GBK → UTF-16
int len = MultiByteToWideChar(936, 0, gbk, -1, NULL, 0);
utf16 = malloc(len * sizeof(wchar_t));
MultiByteToWideChar(936, 0, gbk, -1, utf16, len);
// 2. UTF-16 → Big5
len = WideCharToMultiByte(950, 0, utf16, -1, NULL, 0, NULL, NULL);
big5 = malloc(len);
WideCharToMultiByte(950, 0, utf16, -1, big5, len, NULL, NULL);
// big5 現為 Big5 編碼 "中文"
// 記得 free(utf16), free(big5)
四、C/C++ 字符串類完整對比與選型指南
| 類型 | 編碼/寬度 | 內存管理 | 適用場景 | 關鍵特性/坑點 |
| `char*` / `char[]` | 多字節 (依賴代碼頁) | 手動 | C 風格、舊 API、UTF-8 存儲 | 長度不含 `\0`,`strlen` 計字節非字符 |
| `wchar_t*` / `wchar_t[]` | 寬字符 (Windows 16-bit UTF-16, Linux 32-bit UTF-32) | 手動 | Windows API 參數、跨平台內部處理 | **跨平台寬度不同**,不可直接序列化 |
| `std::string` | 字節容器 (通常存 UTF-8) | RAII 自動 | **現代 C++ 首選**、跨平台存儲/傳輸 | 不關心編碼,視為字節袋;`size()` 為字節數 |
| `std::wstring` | 寬字符容器 | RAII 自動 | Windows 內部處理、調用 `wchar_t` API | 同 `wchar_t` 跨平台寬度問題 |
| `CString` (MFC/ATL) | TCHAR (隨 `_UNICODE` 切換 `char`/`wchar_t`) | **引用計數 COW** (複製極快) | **MFC/ATL 項目首選**、Windows 開發 | `LPCTSTR` 隱式轉換;`GetBuffer`/`ReleaseBuffer` 修改需成對 |
| `_bstr_t` (COM) | BSTR (長度前綴 + UTF-16 + 雙空終止) | 智能指針 (引用計數) | COM 自動化、VBA/VBScript 互操作 | `copy()` 返回需 `SysFreeString` 的 BSTR;不可直接修改內部緩衝區 |
| `CComBSTR` (ATL) | BSTR | RAII 封裝 `SysAllocString`/`SysFreeString` | ATL COM 開發,比 `_bstr_t` 輕量 | 提供 `m_str` 直接訪問 BSTR 指針,可作 `[out]` 參數 |
| `_variant_t` | VARIANT (可含 BSTR、整數、雙精度、IDispatch* 等) | RAII 封裝 `VariantInit`/`VariantClear` | COM 參數傳遞、Script 互操作 | 字符串提取需轉 `_bstr_t`;異常安全需 `try-catch(_com_error)` |
CString 核心機制:引用計數 + 寫時複製 (COW)
CString s1("hello"); // 分配塊:Ref=1, Len=5, Data="hello"
CString s2 = s1; // 僅複製指針,Ref=2
CString s3(s1); // Ref=3
s3 = "world"; // 觸發 COW:分配新塊 Ref=1,舊塊 Ref=2
// 修改前必須 GetBuffer() 確保獨佔,修改後 ReleaseBuffer() 更新長度
關鍵規則:
- `LPCTSTR` 轉換僅供**讀取**/傳入參數,**絕不修改**返回指針指向的內存 (多實例共享)。
- 需修改內容 → `LPTSTR p = s.GetBuffer(len)` → 修改 `p` → `s.ReleaseBuffer()` (觸發 COW 分離)。
- `FreeExtra()` 釋放 `GetBuffer` 預分配的多餘容量。
- 對象銷毀 → Ref-- → Ref=0 → 釋放塊。
BSTR 內存佈局
// BSTR = 長度前綴 (4字節) + UTF-16 數據 + 雙空終止 (2字節)
BSTR b = SysAllocString(L"Bob");
// 內存: 06 00 00 00 | 42 00 6F 00 62 00 | 00 00
// 長度=6字節 B o b 結束符 雙空終止
// SysFreeString(b) 必須配對調用
五、最佳實踐總結
- 內部統一用 UTF-8 (`std::string`):存儲、網絡傳輸、日志、配置文件。跨平台零麻煩。
- Windows API 邊界轉 UTF-16 (`CString`/`std::wstring`/`wchar_t*`):調用 `CreateFileW`、`MessageBoxW` 等 `W` 後綴 API。
- 編譯選項 `/utf-8` (MSVC) / `-finput-charset=UTF-8` (GCC):確保源文件 UTF-8 編碼,字符串字面量正確編碼。
- 使用 `TCHAR`/`_T()`/`TEXT()` 宏適配 ANSI/Unicode 雙構建** (維護舊項目);**新項目直接用顯式 `W` 版 API + `wchar_t`/`CString`**。
- COM 互操作用 `CComBSTR`/`_variant_t`**,自動管理 `SysAllocString`/`VariantClear`,避免泄漏。
- 禁用 `CP_ACP` 依賴**:顯式指定 `CP_UTF8` (65001) 或具體代碼頁 (936/950),消除「中文機器正常、英文機器亂碼」Heisenbug。
字符集問題的本質是**「編碼約定不匹配」**。建立「內部 UTF-8、邊界轉換、顯式指定代碼頁」的鐵律,99% 的亂碼、截斷、崩潰將不復存在。
- 上一篇文章: C語言文件讀寫全解析:文本模式 vs 二進制模式、fread/fwrite 實戰指南
- 下一篇文章: 字符集工程落地:代碼書寫規範、編譯配置、底層原理與正則重構指南
Copyright ©
程式碼之家 All Rights Reserved