中文亂碼:從編碼原理到實(shí)戰(zhàn)方案)
1. 項(xiàng)目概述一個(gè)困擾無(wú)數(shù)開(kāi)發(fā)者的“小”問(wèn)題如果你用Visual Studio后面簡(jiǎn)稱(chēng)VS寫(xiě)C或者C#程序在控制臺(tái)里打印個(gè)“你好世界”十有八九會(huì)看到一堆像“浣犲ソ錛屼笘鐣?”這樣的亂碼。這問(wèn)題不大但極其煩人就像鞋里進(jìn)了顆小石子不解決它每一步都硌得慌。我從業(yè)十幾年帶過(guò)的新手幾乎沒(méi)人能繞過(guò)這個(gè)坑網(wǎng)上搜到的解決方案又五花八門(mén)有的管用一陣子換個(gè)項(xiàng)目或系統(tǒng)又失效了。今天我就把這個(gè)問(wèn)題的來(lái)龍去脈、各種場(chǎng)景下的根治方案以及背后的編碼原理給你徹底講透。無(wú)論你是剛?cè)腴T(mén)的新手還是被這個(gè)問(wèn)題反復(fù)折磨的老鳥(niǎo)這篇文章都能讓你一勞永逸。簡(jiǎn)單說(shuō)這個(gè)問(wèn)題就是Windows控制臺(tái)那個(gè)黑框框、Visual Studio的源代碼文件、以及你程序運(yùn)行時(shí)使用的編碼這三者沒(méi)有對(duì)齊導(dǎo)致的。它不只會(huì)影響printf或cout輸出的中文還會(huì)影響從控制臺(tái)讀取的中文輸入、日志文件里的中文甚至是調(diào)試時(shí)監(jiān)視窗口里變量的中文值。接下來(lái)我會(huì)從問(wèn)題根源拆解到各種具體場(chǎng)景的解決方案最后分享一些只有踩過(guò)坑才知道的排查技巧和注意事項(xiàng)。2. 亂碼根源深度解析編碼戰(zhàn)爭(zhēng)的“三國(guó)演義”要解決問(wèn)題必須先理解問(wèn)題。Visual Studio控制臺(tái)中文亂碼本質(zhì)上是三套編碼體系在“打架”。2.1 核心角色一Windows控制臺(tái)的“代碼頁(yè)”Windows控制臺(tái)cmd.exe, PowerShell終端有一個(gè)歷史遺留概念叫“活動(dòng)代碼頁(yè)”。你可以把它理解為控制臺(tái)這個(gè)“顯示器”自己能識(shí)別和顯示的文字字典。在中文Windows系統(tǒng)上默認(rèn)的活動(dòng)代碼頁(yè)通常是936它對(duì)應(yīng)GBK編碼。這意味著控制臺(tái)默認(rèn)只“認(rèn)識(shí)”并“顯示”用GBK編碼的字符。注意這里有個(gè)關(guān)鍵點(diǎn)??刂婆_(tái)的代碼頁(yè)設(shè)置影響的是它如何“解讀”和“渲染”接收到的字節(jié)流。它不改變你程序輸出的字節(jié)只是用自己的“字典”去翻譯這些字節(jié)。2.2 核心角色二Visual Studio的源代碼文件編碼你用VS寫(xiě)的.cpp或.cs文件本身是以某種編碼保存在磁盤(pán)上的。VS 2015及以后版本默認(rèn)創(chuàng)建的新文件通常是帶BOM的UTF-8。BOMByte Order Mark是文件開(kāi)頭的一個(gè)特殊標(biāo)記EF BB BF用來(lái)聲明“我這個(gè)文件是UTF-8編碼的”。但是更早的VS版本或者你從別處拷貝來(lái)的代碼可能是無(wú)BOM的UTF-8甚至是GBK編碼。問(wèn)題來(lái)了如果你的源代碼文件是UTF-8編碼無(wú)論有無(wú)BOM里面包含了中文字符串比如你好那么這些中文字符在編譯后會(huì)以UTF-8的字節(jié)序列例如E4 BD A0 E5 A5 BD被硬編碼到程序的可執(zhí)行文件中。2.3 核心角色三C/C運(yùn)行時(shí)庫(kù)的“執(zhí)行字符集”對(duì)于C/C程序還有一個(gè)編譯期的概念叫“執(zhí)行字符集”。編譯器需要決定源代碼中的字符串字面量比如你好在生成的可執(zhí)行文件里應(yīng)該被轉(zhuǎn)換成什么編碼。在Visual Studio中這個(gè)轉(zhuǎn)換受兩個(gè)設(shè)置影響源代碼文件的物理編碼如上所述。編譯器的/execution-charset選項(xiàng)或舊版本的/source-charset。默認(rèn)情況下如果源代碼文件是帶BOM的UTF-8MSVC編譯器會(huì)識(shí)別BOM并將字符串轉(zhuǎn)換為UTF-8存入執(zhí)行文件。如果源代碼是無(wú)BOM的編譯器可能會(huì)把它當(dāng)作系統(tǒng)本地編碼即GBK來(lái)處理這就為亂碼埋下了伏筆。2.4 亂碼是如何發(fā)生的現(xiàn)在讓我們把三條線(xiàn)串起來(lái)看一個(gè)最常見(jiàn)的亂碼場(chǎng)景你的操作在VS默認(rèn)UTF-8 with BOM中寫(xiě)printf(你好世界);然后按F5調(diào)試運(yùn)行。編譯過(guò)程VS識(shí)別到BOM將“你好世界”以UTF-8編碼比如字節(jié)序列A編譯進(jìn)程序。運(yùn)行過(guò)程程序啟動(dòng)向控制臺(tái)輸出字節(jié)序列A。顯示過(guò)程控制臺(tái)活動(dòng)代碼頁(yè)936接收到字節(jié)序列A。它用自己的GBK“字典”去解碼這些UTF-8字節(jié)。GBK解碼UTF-8字節(jié)大概率會(huì)產(chǎn)生無(wú)效字符于是顯示為亂碼如“浣犲ソ”。核心矛盾程序輸出的是UTF-8字節(jié)流但控制臺(tái)期待的是GBK字節(jié)流。編碼和解碼的“字典”沒(méi)對(duì)上信息就失真了。3. 解決方案全景圖從臨時(shí)修改到一勞永逸理解了根源解決方案就清晰了讓輸出流的編碼和控制臺(tái)期待的編碼一致。主要有三大類(lèi)思路我會(huì)按推薦程度和適用場(chǎng)景詳細(xì)說(shuō)明。3.1 方案一修改程序強(qiáng)制輸出GBK兼容性方案這是最直接、兼容性最好的方法尤其適合需要分發(fā)、在未知環(huán)境終端下運(yùn)行的程序。思路是在程序內(nèi)部將字符串從源代碼編碼如UTF-8轉(zhuǎn)換為控制臺(tái)默認(rèn)的GBK編碼后再輸出。對(duì)于C程序#include stdio.h #include windows.h int main() { // 源代碼文件保存為UTF-8 with BOM const char* utf8_str 你好世界; // 獲取控制臺(tái)輸出句柄 HANDLE hConsole GetStdHandle(STD_OUTPUT_HANDLE); // 第一步計(jì)算所需緩沖區(qū)大小UTF-8 - GBK int gbk_size WideCharToMultiByte(CP_ACP, 0, (LPCWCH)utf8_str, -1, NULL, 0, NULL, NULL); // 第二步分配緩沖區(qū)并執(zhí)行轉(zhuǎn)換 char* gbk_str (char*)malloc(gbk_size); WideCharToMultiByte(CP_ACP, 0, (LPCWCH)utf8_str, -1, gbk_str, gbk_size, NULL, NULL); // 第三步輸出轉(zhuǎn)換后的GBK字符串 printf(%s\n, gbk_str); free(gbk_str); return 0; }實(shí)操心得上面代碼示例為了清晰分了三步實(shí)際項(xiàng)目中你可以封裝一個(gè)工具函數(shù)char* UTF8ToGBK(const char* utf8)來(lái)復(fù)用。注意這里有一個(gè)常見(jiàn)的“坑”我們假設(shè)源代碼是UTF-8所以直接硬編碼了字符串。更嚴(yán)謹(jǐn)?shù)淖龇ㄊ谴_保編譯器以UTF-8處理源代碼通過(guò)/utf-8編譯選項(xiàng)這樣代碼中的字符串字面量在內(nèi)存里才是確定的UTF-8。對(duì)于C程序使用標(biāo)準(zhǔn)庫(kù)#include iostream #include string #include windows.h #include locale #include codecvt int main() { // 設(shè)置全局locale為中文這會(huì)影響某些C標(biāo)準(zhǔn)庫(kù)函數(shù)如cout string std::locale::global(std::locale(zh-CN.UTF-8)); // 注意這個(gè)名稱(chēng)可能因系統(tǒng)而異 // 方法1使用Windows API轉(zhuǎn)換同C程序 // 方法2使用C11的codecvt已棄用但可用 std::wstring_convertstd::codecvt_utf8wchar_t converter; std::wstring wide_str converter.from_bytes(你好世界); // 獲取控制臺(tái)句柄并輸出寬字符 HANDLE hConsole GetStdHandle(STD_OUTPUT_HANDLE); WriteConsoleW(hConsole, wide_str.c_str(), (DWORD)wide_str.length(), NULL, NULL); return 0; }注意事項(xiàng)C的std::cout直接輸出中文寬字符或UTF-8字符串到控制臺(tái)依然會(huì)亂碼因?yàn)閏out最終調(diào)用的是C標(biāo)準(zhǔn)庫(kù)函數(shù)走的還是字節(jié)流。最可靠的方式仍然是轉(zhuǎn)換為寬字符UTF-16后使用WriteConsoleW或者轉(zhuǎn)換為GBK后使用cout。方案一總結(jié)優(yōu)點(diǎn)兼容性最強(qiáng)在任何默認(rèn)中文代碼頁(yè)的Windows控制臺(tái)下都能正確顯示。缺點(diǎn)代碼繁瑣需要手動(dòng)轉(zhuǎn)換如果程序還需要處理文件、網(wǎng)絡(luò)等UTF-8數(shù)據(jù)內(nèi)部需要維護(hù)多套編碼邏輯。適用場(chǎng)景傳統(tǒng)的Windows桌面控制臺(tái)應(yīng)用對(duì)兼容性要求極高且不涉及復(fù)雜國(guó)際化i18n。3.2 方案二修改控制臺(tái)使其支持UTF-8現(xiàn)代方案既然程序輸出UTF-8更方便現(xiàn)代庫(kù)、網(wǎng)絡(luò)數(shù)據(jù)多是UTF-8那不如讓控制臺(tái)能正確顯示UTF-8。這就是修改控制臺(tái)活動(dòng)代碼頁(yè)為65001UTF-8的代碼頁(yè)。方法A在程序中修改推薦在程序啟動(dòng)時(shí)調(diào)用系統(tǒng)API修改控制臺(tái)代碼頁(yè)。這樣修改只影響你的程序啟動(dòng)的這個(gè)控制臺(tái)窗口。#include windows.h #include stdio.h int main() { // 設(shè)置控制臺(tái)輸出代碼頁(yè)為UTF-8 SetConsoleOutputCP(65001); // 設(shè)置控制臺(tái)輸入代碼頁(yè)為UTF-8如果需要從控制臺(tái)讀取中文輸入 SetConsoleCP(65001); // 現(xiàn)在可以直接輸出UTF-8字符串了 // 前提編譯器以UTF-8方式編譯字符串字面量 printf(你好世界\n); // 源代碼文件需為UTF-8 with BOM或使用/utf-8編譯選項(xiàng) return 0; }方法B手動(dòng)修改控制臺(tái)屬性在運(yùn)行程序前手動(dòng)修改cmd或PowerShell的代碼頁(yè)。CMD命令執(zhí)行chcp 65001PowerShell命令執(zhí)行[Console]::OutputEncoding [System.Text.Encoding]::UTF8重要警告方法B是臨時(shí)性的只對(duì)當(dāng)前控制臺(tái)窗口生效關(guān)閉后重置。而且有些舊版控制臺(tái)字體對(duì)UTF-8支持不好即使設(shè)置了代碼頁(yè)也可能顯示為方框或問(wèn)號(hào)需要同時(shí)修改控制臺(tái)字體為“NSimSun”或“Consolas”等支持Unicode的字體。方案二總結(jié)優(yōu)點(diǎn)程序代碼干凈無(wú)需轉(zhuǎn)換直接處理UTF-8。符合現(xiàn)代軟件開(kāi)發(fā)趨勢(shì)。缺點(diǎn)依賴(lài)運(yùn)行環(huán)境。如果你的程序給別人用別人的控制臺(tái)默認(rèn)不是UTF-8就會(huì)亂碼。部分老舊系統(tǒng)或終端模擬器對(duì)65001代碼頁(yè)支持有瑕疵。適用場(chǎng)景自己開(kāi)發(fā)、自己使用的工具團(tuán)隊(duì)內(nèi)部統(tǒng)一了開(kāi)發(fā)環(huán)境明確要求運(yùn)行環(huán)境需配置為UTF-8的項(xiàng)目。3.3 方案三源頭治理統(tǒng)一編譯與文件編碼根治方案這是最徹底的一勞永逸的方法旨在從源頭編輯和編譯階段就統(tǒng)一使用UTF-8再配合方案二程序內(nèi)設(shè)置控制臺(tái)代碼頁(yè)實(shí)現(xiàn)完美閉環(huán)。步驟1確保所有源代碼文件是UTF-8 with BOM在VS中你可以查看和修改文件編碼用VS打開(kāi)文件。點(diǎn)擊菜單欄“文件” - “另存為”。在保存按鈕旁邊點(diǎn)擊“編碼保存”按鈕在舊版VS中是“保存”對(duì)話(huà)框右下角的“編碼...”按鈕。選擇“Unicode (UTF-8 帶簽名) - 代碼頁(yè) 65001”然后保存。踩坑實(shí)錄VS對(duì)于“無(wú)BOM的UTF-8”文件有時(shí)會(huì)誤判為系統(tǒng)本地編碼GBK導(dǎo)致編譯時(shí)字符串錯(cuò)亂。因此強(qiáng)烈建議始終使用“帶BOM的UTF-8”。BOM雖然在某些場(chǎng)景如Shell腳本不推薦但在Windows的C/C開(kāi)發(fā)中它是幫助編譯器正確識(shí)別編碼的可靠標(biāo)記。步驟2配置項(xiàng)目屬性強(qiáng)制使用UTF-8對(duì)于VS 2015及更新版本特別是VS 2019 (16.2) 和 VS 2022可以設(shè)置項(xiàng)目屬性讓編譯器將所有源代碼都當(dāng)作UTF-8處理并生成UTF-8編碼的字符串。右鍵點(diǎn)擊項(xiàng)目 - “屬性”。選擇“配置屬性” - “C/C” - “命令行”。在“其他選項(xiàng)”框中添加/utf-8這個(gè)選項(xiàng)同時(shí)設(shè)置了/source-charset:utf-8和/execution-charset:utf-8確保源代碼和執(zhí)行文件都使用UTF-8。步驟3在程序入口設(shè)置控制臺(tái)UTF-8代碼頁(yè)如方案二所述在main函數(shù)開(kāi)頭調(diào)用SetConsoleOutputCP(65001)。方案三總結(jié)優(yōu)點(diǎn)徹底、干凈。源代碼、編譯、運(yùn)行環(huán)境全線(xiàn)UTF-8與現(xiàn)代工具鏈Git、CI/CD、跨平臺(tái)庫(kù)完美兼容再無(wú)編碼煩惱。缺點(diǎn)需要配置項(xiàng)目和開(kāi)發(fā)環(huán)境對(duì)遺留項(xiàng)目或必須保持GBK兼容的項(xiàng)目不適用。適用場(chǎng)景新項(xiàng)目、個(gè)人項(xiàng)目、追求現(xiàn)代編碼規(guī)范的項(xiàng)目、有跨平臺(tái)需求的組件。4. 不同場(chǎng)景下的實(shí)戰(zhàn)配置與避坑指南理論說(shuō)完了我們來(lái)點(diǎn)實(shí)際的。下面我針對(duì)幾種常見(jiàn)的開(kāi)發(fā)場(chǎng)景給出具體的配置步驟和避坑提醒。4.1 場(chǎng)景一全新C控制臺(tái)項(xiàng)目VS 2022目標(biāo)創(chuàng)建項(xiàng)目后無(wú)需修改代碼直接讓cout “中文”正常工作。創(chuàng)建項(xiàng)目新建一個(gè)“控制臺(tái)應(yīng)用”項(xiàng)目。設(shè)置項(xiàng)目編碼項(xiàng)目屬性 - C/C - 命令行 - 其他選項(xiàng)添加/utf-8。項(xiàng)目屬性 - C/C - 所有選項(xiàng) - 掃描源確保是“是 (/scanSource)”默認(rèn)即可它幫助編譯器更好地處理編碼。設(shè)置文件編碼打開(kāi)main.cpp點(diǎn)擊“文件”-“高級(jí)保存選項(xiàng)”編碼選擇“Unicode (UTF-8 帶簽名) - 代碼頁(yè) 65001”。如果沒(méi)有“高級(jí)保存選項(xiàng)”需在工具-自定義-命令中添加到菜單。修改代碼在main函數(shù)開(kāi)頭添加#include windows.h int main() { SetConsoleOutputCP(65001); // ... 你的代碼 std::cout 你好UTF-8世界 std::endl; return 0; }運(yùn)行測(cè)試按F5調(diào)試運(yùn)行控制臺(tái)應(yīng)正確顯示中文。4.2 場(chǎng)景二舊有C項(xiàng)目改造舊項(xiàng)目文件編碼混亂有GBK的有無(wú)BOM UTF-8的。統(tǒng)一文件編碼批量使用高級(jí)文本編輯器如VS Code、Notepad的“批量轉(zhuǎn)換文件編碼”功能將所有.cpp,.h文件轉(zhuǎn)換為“UTF-8 with BOM”。務(wù)必先備份轉(zhuǎn)換后用VS打開(kāi)檢查是否有亂碼。如果原文件是GBK且被誤當(dāng)作無(wú)BOM UTF-8打開(kāi)轉(zhuǎn)換會(huì)損壞文件。設(shè)置項(xiàng)目屬性同上添加/utf-8編譯選項(xiàng)。處理已有字符串如果舊代碼中使用了SetConsoleOutputCP(936)或類(lèi)似的硬編碼GBK轉(zhuǎn)換邏輯需要將其改為SetConsoleOutputCP(65001)并移除不必要的字符串轉(zhuǎn)換代碼。測(cè)試重點(diǎn)測(cè)試所有涉及中文輸入輸出的功能包括文件讀寫(xiě)、日志打印、用戶(hù)交互等。4.3 場(chǎng)景三C# (.NET) 控制臺(tái)項(xiàng)目C#的情況比C簡(jiǎn)單很多因?yàn)?NET運(yùn)行時(shí)內(nèi)部字符串統(tǒng)一使用UnicodeUTF-16。控制臺(tái)亂碼問(wèn)題主要出在輸入輸出時(shí)與系統(tǒng)編碼的轉(zhuǎn)換上。最簡(jiǎn)方案在Main方法開(kāi)頭設(shè)置控制臺(tái)輸出編碼。using System; using System.Text; class Program { static void Main(string[] args) { Console.OutputEncoding Encoding.UTF8; // 可選設(shè)置輸入編碼 // Console.InputEncoding Encoding.UTF8; Console.WriteLine(你好C#世界); } }確保源代碼文件編碼VS創(chuàng)建的C#文件默認(rèn)就是UTF-8 with BOM一般無(wú)需修改。但如果從別處拷貝代碼最好用VS的“高級(jí)保存選項(xiàng)”確認(rèn)一下。.NET Core / .NET 5 注意事項(xiàng)在新版.NET中控制臺(tái)默認(rèn)編碼可能已經(jīng)是UTF-8取決于操作系統(tǒng)和終端。但為了最大兼容性顯式設(shè)置Console.OutputEncoding仍是好習(xí)慣。實(shí)操心得對(duì)于C#還有一個(gè)常見(jiàn)坑是向控制臺(tái)寫(xiě)入大量數(shù)據(jù)時(shí)如果包含非ASCII字符可能會(huì)因?yàn)榫彌_區(qū)問(wèn)題導(dǎo)致輸出不完整或亂碼。這時(shí)可以嘗試在寫(xiě)入前調(diào)用Console.OpenStandardOutput()獲取流并直接操作流對(duì)象。5. 高級(jí)議題與疑難雜癥排查解決了基本輸出還有一些進(jìn)階問(wèn)題和疑難雜癥。5.1 調(diào)試器監(jiān)視窗口與即時(shí)窗口中的亂碼有時(shí)程序輸出正常了但在VS調(diào)試時(shí)監(jiān)視窗口查看字符串變量或者即時(shí)窗口里打印變量中文還是顯示亂碼。這是因?yàn)檎{(diào)試器顯示變量值時(shí)使用的是它自己的編碼解讀方式。對(duì)于本地變量窄字符調(diào)試器可能會(huì)用系統(tǒng)本地編碼GBK去解釋內(nèi)存中的UTF-8字節(jié)導(dǎo)致顯示亂碼。這通常只影響顯示不影響程序邏輯??梢試L試在監(jiān)視窗口中對(duì)char*變量添加,s8格式化符號(hào)來(lái)告訴調(diào)試器按UTF-8解釋如myStr, s8。對(duì)于寬字符串wchar_t*顯示通常正常因?yàn)檎{(diào)試器對(duì)寬字符支持更好。根本解決較新版本的VS對(duì)UTF-8的調(diào)試支持在改善但尚未完美。如果監(jiān)視窗口亂碼讓你困擾可以臨時(shí)將字符串復(fù)制到剪貼板粘貼到記事本保存為UTF-8中查看正確內(nèi)容。5.2 第三方庫(kù)或系統(tǒng)調(diào)用返回的中文亂碼你的程序調(diào)用了某個(gè)DLL的函數(shù)或者使用了system()調(diào)用命令行工具返回的中文信息是亂碼。原因這些外部接口返回的字符串編碼可能是系統(tǒng)本地編碼GBK而你的程序內(nèi)部期望的是UTF-8或者反之。解決方案查閱該庫(kù)或函數(shù)的文檔明確其返回字符串的編碼。如果文檔沒(méi)寫(xiě)一個(gè)實(shí)用的方法是用你的程序接收返回的字符串然后分別嘗試用MultiByteToWideChar配合CP_ACPGBK和CP_UTF8去轉(zhuǎn)換看哪個(gè)能轉(zhuǎn)出正確的中文。確定編碼后在程序內(nèi)部進(jìn)行相應(yīng)的轉(zhuǎn)換。5.3 跨平臺(tái)項(xiàng)目Windows/Linux/macOS的編碼處理如果你的代碼需要在Linux/macOS上編譯運(yùn)行那里的終端通常默認(rèn)就是UTF-8環(huán)境。策略在代碼中通過(guò)預(yù)編譯宏進(jìn)行條件編譯。#ifdef _WIN32 #include windows.h #endif void init_console_encoding() { #ifdef _WIN32 SetConsoleOutputCP(65001); #else // Linux/macOS 通常無(wú)需特別設(shè)置locale設(shè)為UTF-8即可 // setlocale(LC_ALL, en_US.UTF-8); #endif }核心原則在程序內(nèi)部統(tǒng)一使用UTF-8。在Windows邊界輸入輸出、調(diào)用Win32 API、讀取系統(tǒng)文件路徑時(shí)進(jìn)行UTF-8與寬字符UTF-16的轉(zhuǎn)換。在Linux/macOS邊界通常可以直接使用UTF-8。5.4 文件讀寫(xiě)中的中文亂碼文件亂碼和控制臺(tái)亂碼本質(zhì)相同都是編碼不一致。寫(xiě)文件明確指定文件的編碼。使用C標(biāo)準(zhǔn)庫(kù)時(shí)如果寫(xiě)中文最好先將其轉(zhuǎn)換為目標(biāo)編碼如UTF-8或GBK再寫(xiě)入字節(jié)。使用C流或C#的StreamWriter時(shí)可以在創(chuàng)建寫(xiě)入器時(shí)指定編碼如new StreamWriter(file.txt, false, Encoding.UTF8)。讀文件同樣需要知道文件的編碼。如果是你自己程序?qū)懙哪阒谰幋a。如果是讀取用戶(hù)提供的文件可以嘗試通過(guò)BOM判斷UTF-8, UTF-16LE/BE沒(méi)有BOM的話(huà)可以嘗試用編碼檢測(cè)庫(kù)或者提供選項(xiàng)讓用戶(hù)指定。6. 終極檢查清單與總結(jié)建議當(dāng)你被中文亂碼問(wèn)題搞得焦頭爛額時(shí)可以按以下清單逐一排查源代碼編碼你的.cpp/.h/.cs文件是不是“UTF-8 with BOM”用VS的“高級(jí)保存選項(xiàng)”檢查并轉(zhuǎn)換。編譯器設(shè)置C項(xiàng)目屬性里是否添加了/utf-8編譯選項(xiàng)程序初始化在main函數(shù)入口是否調(diào)用了SetConsoleOutputCP(65001)C或設(shè)置了Console.OutputEncoding Encoding.UTF8C#控制臺(tái)本身如果你不是通過(guò)F5調(diào)試而是直接雙擊exe運(yùn)行檢查cmd的屬性-字體是否選擇了支持中文的字體如宋體、Consolas代碼頁(yè)是否為936GBK如果是你的程序需要輸出GBK或者修改代碼頁(yè)。字符串轉(zhuǎn)換如果程序需要與外部GBK系統(tǒng)交互是否在邊界處正確使用了WideCharToMultiByte/MultiByteToWideChar進(jìn)行轉(zhuǎn)換調(diào)試器顯示監(jiān)視窗口亂碼是否影響了調(diào)試如果不影響邏輯可以暫時(shí)忽略或使用,s8格式化符。我的個(gè)人經(jīng)驗(yàn)總結(jié)對(duì)于全新的個(gè)人或團(tuán)隊(duì)項(xiàng)目我強(qiáng)烈推薦采用“方案三源頭治理”的組合拳源代碼全部使用“UTF-8 with BOM”。編譯器添加/utf-8選項(xiàng)。程序入口設(shè)置控制臺(tái)代碼頁(yè)為65001。內(nèi)部邏輯統(tǒng)一使用UTF-8僅在必須調(diào)用Windows ANSI API時(shí)進(jìn)行臨時(shí)的、局部的編碼轉(zhuǎn)換。這套方法雖然前期需要一些配置但它將編碼問(wèn)題隔離在最小的范圍內(nèi)讓代碼主體保持干凈并為你未來(lái)的跨平臺(tái)開(kāi)發(fā)和與現(xiàn)代工具鏈集成鋪平了道路。編碼問(wèn)題本質(zhì)是“數(shù)據(jù)表示一致性問(wèn)題”從一開(kāi)始就確立并堅(jiān)守統(tǒng)一的“協(xié)議”UTF-8是避免后續(xù)無(wú)數(shù)麻煩的最高效手段。希望這篇長(zhǎng)文能幫你徹底理清Visual Studio下的中文亂碼問(wèn)題從此告別那些令人頭疼的“錕斤拷”和“燙燙燙”。