首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Unicode 與字元集完全指南:從 DBCS 到寬字元的最佳實踐

     編輯:關於C++

為何要關心字元集?

全球化軟體的基石是**正確處理文字**。從早期的 ANSI (單位元組) 到 DBCS (雙位元組字元集,如 Big5, GBK, Shift_JIS),再到 Unicode (UTF-16/UTF-8),每一代演進都為了解決前一代的字碼衝突、資料交換困擾與平台相容性。本文系統整理 Windows 平台下的字元集操作精要。

一、 DBCS 字串操作陷阱與對策

問題錯誤做法正確做法
取得字元數`strlen(str)` → 回傳位元組數`_mbslen(str)` (MBCS) 或 `lstrlen(str)` (通用)
指標移動`ptr++` → 可能切斷雙位元組字元`CharNext(ptr)` / `CharPrev(start, ptr)`
判斷前導位元組手動比對範圍`IsDBCSLeadByte(byte)`

二、 為什麼選擇 Unicode (UTF-16/Wide Char)?

  1. 單一二進位支援所有語言:同一份 `.exe`/`.dll` 可同時顯示中文、日文、阿拉伯文、Emoji,無需切換代碼頁。
  2. 避免 ANSI↔Unicode 轉換開銷:Windows 2000+ 核心原生 Unicode。呼叫 ANSI 版 API (`CreateWindowExA`) 會強制系統內部 `MultiByteToWideChar` → 核心 → `WideCharToMultiByte` 回傳,浪費 CPU/記憶體。
  3. COM/ActiveX 強制 Unicode:所有涉及字串的 COM 介面方法僅接受 `BSTR` (UTF-16)。
  4. Windows CE / 現代平台原生 Unicode:CE 完全不支援 ANSI API;.NET/CLR 字串即 UTF-16。

三、 關鍵資料類型對照表

語意ANSIUnicode通用 (TCHAR)
字元型別`CHAR``WCHAR``TCHAR`
字串指標`LPSTR` / `PSTR``LPWSTR` / `PWSTR``LPTSTR` / `PTSTR`
常數字串指標`LPCSTR` / `PCSTR``LPCWSTR` / `PCWSTR``LPCTSTR` / `PCTSTR`

四、 通用原始碼撰寫:`UNICODE` / `_UNICODE` 雙巨集控制

// 專案設定:定義 UNICODE 和 _UNICODE (或專案屬性 → Character Set → Use Unicode Character Set)

// 1. 字串常值
TCHAR* szMsg = _TEXT("中文訊息");  // 或 _T("...")、TEXT("...")
TCHAR ch = _TEXT('中');

// 2. 通用執行期函數 (C Runtime)
_tcscpy(dest, src);   // 對應 strcpy / wcscpy / _mbscpy
_tcslen(str);         // 對應 strlen / wcslen / _mbslen
_tcscmp(s1, s2);      // 對應 strcmp / wcscmp / _mbscmp
_tprintf(_T("Value: %s\n"), str);

// 3. Windows API 函數
CreateWindowEx(..., _T("MyClass"), _T("標題"), ...); // 宏展開為 CreateWindowExW

// 4. 緩衝區大小計算 —— 易犯錯誤點!
// 錯誤:sizeof(szBuffer) → 位元組數
// 正確:sizeof(szBuffer) / sizeof(TCHAR) → 字元數
// 分配記憶體:
TCHAR* p = (TCHAR*)malloc(nChars * sizeof(TCHAR)); // 不要 malloc(nChars)

五、 字串比較進階:`CompareString` / `CompareStringEx`

支援區域感知、忽略大小寫/假名/符號/寬度等語意比較:

int result = CompareString(
    LOCALE_USER_DEFAULT,           // 或具體 LCID
    NORM_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH,
    str1, -1, str2, -1
);
// result: 2=相等, 1=str1str2, 0=錯誤
旗標效果
`NORM_IGNORECASE`忽略大小寫
`NORM_IGNOREKANATYPE`不區分平/片假名
`NORM_IGNORENONSPACE`忽略非間距字元 (重音符等)
`NORM_IGNORESYMBOLS`忽略符號
`NORM_IGNOREWIDTH`全形/半形視為相同
`SORT_STRINGSORT`標點視為一般字元排序

六、 檔案/資料流編碼偵測

  • BOM 偵測:檔案開頭 `0xFF 0xFE` (UTF-16LE) / `0xFE 0xFF` (UTF-16BE) / `0xEF 0xBB 0xBF` (UTF-8) → 確認為 Unicode。
  • 啟發式偵測:`IsTextUnicode(buffer, size, NULL)` — 統計/啟發法猜測,**非 100% 可靠**,僅作輔助。
  • 最佳實踐:協定/格式**明確指定編碼** (如 XML ``, HTTP `Content-Type: text/html; charset=utf-8`),拒絕猜測。

七、 ANSI ↔ Unicode 轉換 API


// 多位元組 (ANSI/DBCS/UTF-8) → 寬字元 (UTF-16)
int nWide = MultiByteToWideChar(CP_UTF8, 0, pszMulti, -1, NULL, 0);
PWSTR pszWide = new WCHAR[nWide];
MultiByteToWideChar(CP_UTF8, 0, pszMulti, -1, pszWide, nWide);

// 寬字元 → 多位元組
int nMulti = WideCharToMultiByte(CP_UTF8, 0, pszWide, -1, NULL, 0, NULL, NULL);
PSTR pszMulti = new CHAR[nMulti];
WideCharToMultiByte(CP_UTF8, 0, pszWide, -1, pszMulti, nMulti, NULL, NULL);

// 記得 delete[] 釋放

代碼頁常數:`CP_ACP` (系統預設 ANSI), `CP_OEMCP` (DOS 代碼頁), `CP_UTF8` (65001), `CP_UTF7` (65000), `1200` (UTF-16LE), `1201` (UTF-16BE)。

八、 現代 C++ 最佳實踐 (C++17/20)

  • 內部統一使用 `std::u8string` (UTF-8) 或 `std::wstring` (UTF-16),邊界處理才轉換。
  • 使用 ``, `` (C++17 棄用, C++26 重新設計) 或第三方庫 `iconv`, `ICU`, `utf8cpp` 進行轉換。
  • Windows API 呼叫統一用 `W` 版本 (`CreateFileW`, `SetWindowTextW`),避免 `TCHAR` 宏汙染。
  • 原始碼檔案**儲存為 UTF-8 (含 BOM 或無 BOM)**,編譯器 `/source-charset:utf-8 /execution-charset:utf-8` (MSVC) 或 `-finput-charset=UTF-8 -fexec-charset=UTF-8` (GCC/Clang)。

九、 常見錯誤檢核清單

  1. ❌ `sizeof(szBuf)` 當作字元數傳給 API → ✅ `sizeof(szBuf)/sizeof(TCHAR)`
  2. ❌ `strcpy` 複製可能含中文的字串 → ✅ `_tcscpy` / `wcscpy_s` / `strcpy_s`
  3. ❌ 硬編碼 `CP_ACP` 轉換 → ✅ 明確指定 `CP_UTF8` 或目標代碼頁
  4. ❌ 假設 `wchar_t` 永遠 2 位元組 → ✅ Windows 是 2 (UTF-16),Linux 通常 4 (UTF-32),用 `char16_t`/`char32_t` 跨平台
  5. ❌ 直接操作 `BSTR` 當 `wchar_t*` → ✅ 用 `SysAllocString`/`SysFreeString`/`SysStringLen` 管理

總結

字元集處理的核心原則是:**內部統一 Unicode (UTF-16/UTF-8),邊界明確轉換,拒絕隱含假設**。Windows 開發者應全面遷移至 `UNICODE` 建置、直接呼叫 `W` 版 API、使用 `std::wstring`/`std::u8string`,並建立編碼轉換工具庫集中管理代碼頁邏輯。這不僅能消除亂碼 Bug,更是打造世界級軟體的基本功。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved