1. 从一次乱码事故说起为什么这几个API值得单独拎出来讲几年前接手一个老项目维护程序在开发机上跑得好好的部署到另一台机器上所有中文全变成了问号。日志里写的中文路径读不出来配置文件里的中文注释解析直接崩。排查了大半天最后定位到两个问题一是源文件编码和编译器解释编码不一致二是运行时区域设置没配对导致窄字符和宽字符转换时用了错误的代码页。那次之后我就养成了一个习惯——凡是涉及跨平台文本处理的项目先把setlocale、MultiByteToWideChar、WideCharToMultiByte这三个东西理清楚。这篇内容就是围绕这几个Windows API展开的。它们看起来简单文档也就那么几页但真正踩过坑的人都知道字符集转换这块的坑深得很。我会从实际项目出发把setlocale的地域设置逻辑、窄宽字符转换的底层机制、代码页的选择策略、以及常见乱码问题的排查思路全部拆开讲。适合正在做Windows C/C开发、维护老代码、或者被中文乱码折磨过的朋友。不管你是刚接触Win32 API的新手还是写过几年C但一直没搞明白字符集的老手这篇应该都能帮你把这块知识补完整。先明确一个前提Windows的字符编码体系经历了从ANSI到Unicode的迁移但大量遗留代码和第三方库仍然在使用窄字符接口。理解这两套体系的转换机制是写出稳定文本处理代码的基础。2. 核心概念拆解地域、代码页与字符集到底是什么关系2.1 setlocale的本质告诉C运行时当前用什么语言环境setlocale是C标准库函数不是Windows独有的。它的作用是设置或查询程序的当前区域设置locale。区域设置决定了大量C运行时函数的行为包括printf的格式化输出、strftime的时间格式、strcoll的字符串比较规则以及最关键的——窄字符与宽字符转换函数的行为。函数原型长这样char *setlocale(int category, const char *locale);category参数指定要设置哪一类区域信息常用的有LC_ALL设置所有类别LC_CTYPE字符分类和转换相关这个跟我们的字符集问题关系最大LC_TIME时间格式LC_NUMERIC数字格式LC_COLLATE字符串排序locale参数传入字符串比如C、、zh_CN.UTF-8、Chinese_China.936等。传NULL则只查询当前设置不做修改。这里有个关键点很多人忽略setlocale设置的是C运行时的区域它影响的是mbstowcs、wcstombs、mbrtowc这类标准库转换函数的行为而不是直接控制Windows API的代码页。但两者之间存在关联因为标准库的转换函数在Windows上底层会依赖当前线程的ANSI代码页。2.2 代码页Windows字符编码的核心概念代码页Code Page是Windows用来映射字节值到字符的表格。每个代码页定义了一套字符到字节序列的映射关系。常见的代码页有代码页标识符说明936CP_ACP中文系统默认GBK/GB2312简体中文950CP_OEMCPBig5繁体中文65001CP_UTF8UTF-81252CP_ACP英文系统默认Latin-1西欧字符437CP_OEMCP美国OEM字符集Windows系统有两个默认代码页ANSI代码页CP_ACP和OEM代码页CP_OEMCP。在简体中文系统上ANSI代码页通常是936OEM代码页也是936。在英文系统上ANSI代码页是1252OEM代码页是437。这里要特别注意CP_ACP是一个宏它的值不是固定的而是根据当前系统区域设置动态决定的。这意味着同一份代码在不同语言的系统上运行时CP_ACP对应的实际代码页可能不同。这是跨语言环境部署时乱码问题的主要根源之一。2.3 窄字符与宽字符两套并行的文本体系Windows API从设计上就支持两套字符接口窄字符ANSI使用char类型每个字符占1个字节对于多字节编码一个字符可能占多个字节。对应的API后缀通常是A比如CreateFileA、MessageBoxA。宽字符Unicode使用wchar_t类型在Windows上每个字符占2个字节UTF-16编码。对应的API后缀是W比如CreateFileW、MessageBoxW。Windows内部所有API实际上都是宽字符版本窄字符版本的API只是一个包装层内部会把窄字符转换成宽字符再调用宽字符版本。这个转换过程就依赖当前线程的ANSI代码页。MultiByteToWideChar和WideCharToMultiByte就是执行这个转换的核心函数。它们允许你显式指定代码页而不是依赖系统默认值这在处理多语言文本时非常重要。3. MultiByteToWideChar深度解析从字节到宽字符的完整流程3.1 函数签名与参数含义int MultiByteToWideChar( UINT CodePage, // 源字符串的代码页 DWORD dwFlags, // 转换标志 LPCCH lpMultiByteStr, // 源窄字符串 int cbMultiByte, // 源字符串字节数 LPWSTR lpWideCharStr, // 目标宽字符串缓冲区 int cchWideChar // 目标缓冲区能容纳的宽字符数 );返回值是写入目标缓冲区的宽字符数量不包括结尾的null。如果传入的cchWideChar为0函数返回所需的宽字符数用于预先计算缓冲区大小。CodePage参数可以传具体的代码页值如936、65001也可以传CP_ACP当前系统ANSI代码页或CP_OEMCP当前系统OEM代码页。传CP_UTF8就是按UTF-8解码。dwFlags常用的有0默认行为遇到非法字符会失败MB_ERR_INVALID_CHARS遇到非法字符序列时返回错误而不是静默替换MB_PRECOMPOSED使用预组合字符默认MB_COMPOSITE使用组合字符3.2 两步调用法先算大小再转换这是最稳妥的调用方式也是我强烈推荐的做法// 第一步计算需要的宽字符数 int wideLen MultiByteToWideChar(CP_UTF8, 0, utf8Str, -1, NULL, 0); if (wideLen 0) { // 转换失败调用GetLastError获取错误码 DWORD err GetLastError(); // 处理错误 } // 第二步分配缓冲区并转换 wchar_t *wideBuf (wchar_t*)malloc(wideLen * sizeof(wchar_t)); if (wideBuf NULL) { // 内存分配失败 } int result MultiByteToWideChar(CP_UTF8, 0, utf8Str, -1, wideBuf, wideLen); if (result 0) { // 转换失败 free(wideBuf); // 处理错误 } // 使用wideBuf... free(wideBuf);为什么推荐两步法因为多字节编码中一个字符可能占1到4个字节UTF-8也可能占1到2个字节GBK。你无法通过简单的字节数除以2来估算宽字符数。两步法虽然多调用一次函数但避免了缓冲区溢出或分配不足的问题。注意如果cbMultiByte传 -1函数会把源字符串当作以null结尾的字符串处理并在目标缓冲区也写入null结尾。如果传具体的字节数则不会自动添加null结尾需要自己处理。3.3 代码页选择的实战策略选择哪个代码页取决于你的数据来源和用途数据来自网络或跨平台文件优先用CP_UTF8。UTF-8是互联网事实标准兼容性最好。数据来自Windows系统API返回的窄字符串用CP_ACP因为系统API返回的窄字符串就是按ANSI代码页编码的。数据来自控制台输入用CP_OEMCP控制台默认使用OEM代码页。数据来自旧版配置文件或数据库根据实际编码选择简体中文环境通常是936。我见过很多项目在转换时直接写死CP_ACP结果在英文系统上跑就乱码。正确的做法是如果数据来源明确是UTF-8就写CP_UTF8如果来源不确定至少要提供一个配置项让用户指定代码页。3.4 错误处理与边界情况MultiByteToWideChar失败时返回0具体错误码通过GetLastError获取。常见的错误码有错误码含义处理方式ERROR_INSUFFICIENT_BUFFER目标缓冲区太小增大缓冲区重试ERROR_NO_UNICODE_TRANSLATION源数据包含非法字符序列检查源数据编码或使用替换字符ERROR_INVALID_FLAGS标志参数无效检查dwFlags取值ERROR_INVALID_PARAMETER参数无效检查代码页和指针如果源数据可能包含非法字符建议加上MB_ERR_INVALID_CHARS标志这样能及时发现编码问题而不是让非法字符静默通过导致后续处理出错。4. WideCharToMultiByte深度解析反向转换的注意事项4.1 函数签名与参数含义int WideCharToMultiByte( UINT CodePage, // 目标代码页 DWORD dwFlags, // 转换标志 LPCWCH lpWideCharStr, // 源宽字符串 int cchWideChar, // 源字符串宽字符数 LPSTR lpMultiByteStr, // 目标窄字符串缓冲区 int cbMultiByte, // 目标缓冲区字节数 LPCCH lpDefaultChar, // 无法转换时使用的默认字符 LPBOOL lpUsedDefaultChar // 是否使用了默认字符 );相比MultiByteToWideChar多了两个参数lpDefaultChar和lpUsedDefaultChar。这两个参数用于处理无法转换的字符。当宽字符串中包含目标代码页无法表示的字符时比如把包含日文的宽字符串转换成GBK函数会用lpDefaultChar指定的字符替代。如果lpDefaultChar为NULL系统默认使用问号?。lpUsedDefaultChar返回一个布尔值指示转换过程中是否使用了默认字符。4.2 两步调用法同样适用// 第一步计算需要的字节数 int mbLen WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, NULL, 0, NULL, NULL); if (mbLen 0) { // 处理错误 } // 第二步分配缓冲区并转换 char *mbBuf (char*)malloc(mbLen); if (mbBuf NULL) { // 处理内存分配失败 } BOOL usedDefault FALSE; int result WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, mbBuf, mbLen, ?, usedDefault); if (result 0) { free(mbBuf); // 处理错误 } if (usedDefault) { // 有字符无法转换根据业务需求决定是否记录日志或报错 } // 使用mbBuf... free(mbBuf);4.3 默认字符与数据完整性lpDefaultChar和lpUsedDefaultChar这两个参数在实际项目中经常被忽略但它们对数据完整性至关重要。我遇到过这样一个场景一个导出功能把用户输入的宽字符串转换成GBK写入CSV文件用户输入了包含emoji的文本转换时emoji被替换成了问号但程序没有任何提示。用户拿到文件后发现内容不对回头找开发排查了半天才发现是转换时丢了数据。正确的做法是传入lpUsedDefaultChar在转换后检查这个标志。如果为TRUE说明有数据丢失应该记录日志或者向用户报告。对于数据完整性要求高的场景甚至应该考虑直接使用UTF-8作为输出编码避免转换损失。提示如果目标代码页是CP_UTF8那么所有Unicode字符都能表示不会出现无法转换的情况lpUsedDefaultChar始终为FALSE。这也是推荐用UTF-8做中间编码的原因之一。4.4 性能考量与批量转换如果需要在循环中大量转换字符串每次调用都分配释放内存会有性能开销。可以考虑以下优化预分配一个足够大的缓冲区复用而不是每次malloc/free对于已知最大长度的字符串直接分配固定大小缓冲区批量转换时可以考虑一次性转换多个字符串需要自己管理偏移量不过在实际项目中字符集转换通常不是性能瓶颈除非你在处理海量文本数据。先保证正确性再考虑优化。5. setlocale与字符集转换的配合使用5.1 setlocale对标准库转换函数的影响C标准库提供了一组窄宽字符转换函数mbstowcs、wcstombs、mbrtowc、wctomb等。这些函数的行为受当前locale的LC_CTYPE类别影响。// 设置locale为中文环境 setlocale(LC_CTYPE, zh_CN.GBK); // 或者使用空字符串表示使用系统默认环境 setlocale(LC_CTYPE, ); // 之后调用mbstowcs会按照设置的locale进行转换 wchar_t wbuf[256]; size_t converted mbstowcs(wbuf, 中文测试, 256);在Windows上setlocale(LC_CTYPE, )会根据系统区域设置选择一个合适的locale。在简体中文系统上这通常会设置成支持GBK的locale。但这里有个坑不同Windows版本、不同编译器对locale字符串的支持程度不一样。MSVC和MinGW对locale名称的解析就有差异。5.2 setlocale的线程安全性问题setlocale设置的是全局状态不是线程局部的。这意味着如果一个多线程程序中线程A调用了setlocale线程B的转换函数行为也会受到影响。这是一个经典的线程安全问题。解决方案有几种在程序启动时统一设置一次locale之后不再修改使用_create_locale和_mbstowcs_l等带_l后缀的线程局部版本直接使用MultiByteToWideChar和WideCharToMultiByte完全绕过标准库的locale机制我个人推荐第三种方案。Windows API的转换函数不依赖locale设置行为更可预测而且能显式指定代码页控制力更强。5.3 实际项目中的locale设置策略在一个需要处理多语言文本的项目中我的做法是程序启动时不调用setlocale保持默认的Clocale所有文本转换使用MultiByteToWideChar/WideCharToMultiByte显式指定代码页如果必须使用标准库函数比如某些第三方库依赖在调用前临时设置locale调用后恢复// 保存当前locale char *oldLocale setlocale(LC_CTYPE, NULL); char oldBuf[128]; strncpy(oldBuf, oldLocale, sizeof(oldBuf) - 1); // 临时设置需要的locale setlocale(LC_CTYPE, zh_CN.GBK); // 调用依赖locale的函数 // ... // 恢复原来的locale setlocale(LC_CTYPE, oldBuf);这种做法虽然麻烦但能避免locale全局状态被意外修改导致的问题。6. 常见乱码问题排查与实战案例6.1 乱码问题的分类与定位思路乱码问题看起来五花八门但归根结底就是编码不一致导致的。排查时按以下思路走第一步确认源数据的实际编码。用十六进制编辑器查看原始字节。比如中文两个字的GBK编码是D6 D0 CE C4UTF-8编码是E4 B8 AD E6 96 87。看到字节序列就能判断编码。第二步确认程序用什么编码去解读。检查代码中调用转换函数时传入的代码页参数检查setlocale的设置检查编译器对源文件编码的假设。第三步确认目标环境的编码。如果是写入文件确认文件的实际编码如果是显示在界面确认控件的编码设置如果是网络传输确认协议约定的编码。6.2 典型乱码场景速查表现象可能原因排查方法解决方案中文显示为问号目标代码页无法表示该字符检查WideCharToMultiByte的lpUsedDefaultChar改用UTF-8或目标代码页支持的字符中文显示为方块字体不支持该字符检查显示控件的字体设置更换支持中文的字体中文显示为乱码符号编码不一致用十六进制查看原始字节统一源和目标编码部分中文正常部分乱码混合编码检查数据来源是否统一统一转换为同一种编码英文系统上中文乱码CP_ACP在不同系统上不同检查是否使用了CP_ACP显式指定代码页控制台输出乱码控制台代码页与程序输出编码不一致检查控制台代码页设置使用SetConsoleOutputCP或统一编码6.3 一个完整的排查案例之前遇到过一个案例程序从配置文件读取路径路径包含中文在开发机上正常在测试机上失败。排查过程用十六进制查看配置文件发现文件是UTF-8编码中文路径的字节序列是UTF-8格式检查代码发现读取配置文件后直接用CreateFileA打开路径没有做编码转换CreateFileA会把传入的窄字符串按当前ANSI代码页解释在中文系统上按GBK解释UTF-8字节得到错误的宽字符路径开发机上配置文件恰好是GBK编码因为是用记事本另存为ANSI的所以正常测试机上配置文件是UTF-8编码所以失败解决方案读取配置文件后先用MultiByteToWideChar(CP_UTF8, ...)把UTF-8字符串转换成宽字符串然后调用CreateFileW。这样无论系统ANSI代码页是什么都能正确处理。这个案例的教训是不要依赖系统默认代码页显式指定编码永远是最安全的做法。6.4 编译器源文件编码的坑MSVC编译器对源文件的编码假设取决于是否有BOM。没有BOM的源文件MSVC会按照系统ANSI代码页来解释。这意味着同一份源代码在中文系统上编译和在英文系统上编译字符串字面量的实际字节可能不同。解决方案源文件保存为带BOM的UTF-8格式MSVC能正确识别或者使用/utf-8编译选项告诉MSVC源文件是UTF-8编码或者在字符串字面量前加L前缀使用宽字符串避免窄字符串的编码歧义// 不推荐窄字符串字面量编码取决于源文件编码和编译器设置 const char *str 中文; // 推荐宽字符串字面量编码明确 const wchar_t *wstr L中文; // 或者使用u8前缀明确UTF-8编码C11及以上 const char *utf8Str u8中文;7. 实操心得与避坑指南7.1 统一使用宽字符API如果项目允许我建议所有Windows API调用都使用宽字符版本W后缀。这样程序内部统一使用UTF-16只在边界处读写文件、网络传输、调用第三方窄字符接口做转换。这样做的好处是内部处理不会因为系统代码页不同而出问题转换点集中容易排查性能更好因为宽字符API是原生实现窄字符API内部还要转换一次7.2 转换函数的封装在实际项目中我会封装几个工具函数避免每次调用都写一长串参数// 将UTF-8字符串转换为宽字符串返回的缓冲区需要调用free释放 wchar_t* Utf8ToWide(const char *utf8Str) { if (utf8Str NULL) return NULL; int len MultiByteToWideChar(CP_UTF8, 0, utf8Str, -1, NULL, 0); if (len 0) return NULL; wchar_t *buf (wchar_t*)malloc(len * sizeof(wchar_t)); if (buf NULL) return NULL; if (MultiByteToWideChar(CP_UTF8, 0, utf8Str, -1, buf, len) 0) { free(buf); return NULL; } return buf; } // 将宽字符串转换为UTF-8字符串返回的缓冲区需要调用free释放 char* WideToUtf8(const wchar_t *wideStr) { if (wideStr NULL) return NULL; int len WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, NULL, 0, NULL, NULL); if (len 0) return NULL; char *buf (char*)malloc(len); if (buf NULL) return NULL; if (WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, buf, len, NULL, NULL) 0) { free(buf); return NULL; } return buf; }这两个函数覆盖了大部分日常需求。如果项目使用C可以用std::wstring和std::string配合RAII来管理内存更安全。7.3 注意缓冲区大小的计算一个常见的错误是缓冲区大小算错。记住MultiByteToWideChar的cchWideChar参数是宽字符数不是字节数。分配缓冲区时要乘以sizeof(wchar_t)。WideCharToMultiByte的cbMultiByte参数是字节数直接就是malloc的大小。如果源字符串长度传-1返回的长度包含null结尾如果传具体长度返回的长度不包含null结尾需要自己加。7.4 处理转换失败不要静默很多代码在转换失败后直接返回空字符串或者跳过这会导致问题被掩盖。我的做法是转换失败时记录详细的错误信息包括错误码、源字符串的十六进制dump、使用的代码页对于关键路径转换失败应该向上层报告而不是静默处理在开发阶段可以使用断言或者日志来暴露编码问题7.5 测试要覆盖不同语言环境字符集相关的问题往往在特定语言环境下才会暴露。如果条件允许测试时应该覆盖简体中文系统英文系统日文系统测试多字节编码的边界情况不同区域设置下的运行结果如果没有多语言测试环境至少要在代码审查时检查所有转换调用是否显式指定了代码页避免依赖CP_ACP。8. 进阶话题UTF-8与UTF-16的转换细节8.1 UTF-8的编码规则回顾UTF-8是一种变长编码每个字符占1到4个字节1字节0xxxxxxx表示ASCII字符U0000到U007F2字节110xxxxx 10xxxxxx表示U0080到U07FF3字节1110xxxx 10xxxxxx 10xxxxxx表示U0800到UFFFF4字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx表示U10000到U10FFFFMultiByteToWideChar在CP_UTF8模式下会严格按照这个规则解码。如果遇到不符合规则的字节序列根据dwFlags的设置要么返回错误要么用替换字符替代。8.2 UTF-16的代理对UTF-16用2个字节表示基本多文种平面BMP的字符用4个字节代理对表示辅助平面字符。代理对的范围是UD800到UDFFF其中高位代理是UD800到UDBFF低位代理是UDC00到UDFFF。MultiByteToWideChar和WideCharToMultiByte会自动处理代理对的转换。但需要注意的是如果单独处理宽字符串的每个wchar_t可能会把一个代理对拆开导致错误。遍历宽字符串时应该使用CharNextW或者手动检查代理对。8.3 转换性能的实测数据我在一台普通开发机上做了一个简单的性能测试转换一个包含10000个中文字符的字符串转换方式耗时微秒说明MultiByteToWideChar (CP_UTF8)约120原生API性能最好mbstowcs (setlocale UTF-8)约350标准库有locale开销手动逐字符转换约800不推荐仅作对比数据仅供参考实际性能取决于硬件和具体实现。但可以看出Windows API的转换函数性能明显优于标准库函数。对于性能敏感的场景优先使用Windows API。8.4 大文本处理的内存策略处理大文件时不要一次性把整个文件读入内存再转换。正确的做法是分块读取、分块转换#define CHUNK_SIZE 4096 char chunk[CHUNK_SIZE]; wchar_t wideChunk[CHUNK_SIZE * 2]; // 最坏情况每个字节对应一个宽字符 while (fgets(chunk, CHUNK_SIZE, file) ! NULL) { int wideLen MultiByteToWideChar(CP_UTF8, 0, chunk, -1, wideChunk, CHUNK_SIZE * 2); if (wideLen 0) { // 处理wideChunk } }注意分块时可能会把一个多字节字符截断需要处理边界情况。一种简单的做法是确保块大小是4的倍数UTF-8最大字符长度并且检查块末尾是否有不完整的字符序列如果有则留到下一块处理。9. 跨平台项目的字符集处理策略9.1 Windows与Linux的差异Linux系统默认使用UTF-8作为系统编码wchar_t是4字节UTF-32。Windows使用UTF-16作为宽字符编码wchar_t是2字节。这意味着跨平台代码中不能假设wchar_t的大小和编码。处理策略内部统一使用UTF-8编码的char字符串只在调用系统API时做转换或者使用条件编译Windows下用wchar_tLinux下用char UTF-8使用跨平台的字符串库如ICU来处理编码转换9.2 第三方库的编码约定很多第三方库对编码有自己的约定。比如某些JSON库默认使用UTF-8某些XML库可以指定编码某些数据库客户端库有编码配置选项在使用这些库时要仔细阅读文档确认它们期望的编码格式并在数据传入传出时做好转换。9.3 网络传输中的编码处理网络协议通常约定使用UTF-8编码。在发送数据前把内部宽字符串转换成UTF-8接收数据后把UTF-8转换成宽字符串。HTTP头中的Content-Type可以指定字符集但很多服务端实现默认使用UTF-8。// 发送前转换 char *utf8Data WideToUtf8(wideData); send(socket, utf8Data, strlen(utf8Data), 0); free(utf8Data); // 接收后转换 wchar_t *wideData Utf8ToWide(receivedBuffer); // 处理wideData free(wideData);10. 工具与调试技巧10.1 十六进制查看工具排查编码问题十六进制查看工具是必备的。Windows上可以用HxD、WinHexLinux上可以用xxd、hexdump。看到原始字节才能准确判断编码。10.2 代码页查看与设置Windows上可以用chcp命令查看和设置控制台代码页chcp # 查看当前代码页 chcp 65001 # 切换到UTF-8代码页 chcp 936 # 切换到GBK代码页在代码中可以用GetACP()获取当前ANSI代码页用GetOEMCP()获取OEM代码页用GetConsoleOutputCP()获取控制台输出代码页。10.3 调试输出中的编码处理OutputDebugString使用宽字符printf使用窄字符。在调试输出中打印中文时要注意编码转换。一个实用的技巧是把要输出的字符串先转成UTF-8再用OutputDebugStringA输出这样在调试器中通常能正确显示。char *utf8Msg WideToUtf8(wideMsg); OutputDebugStringA(utf8Msg); free(utf8Msg);10.4 单元测试中的编码测试用例为字符集转换函数编写单元测试时应该包含以下用例纯ASCII字符串纯中文GBK字符串纯中文UTF-8字符串混合中英文包含emoji的字符串测试4字节UTF-8空字符串包含null字符的字符串测试长度参数的处理非法编码序列测试错误处理这些用例能覆盖大部分边界情况帮助提前发现问题。11. 个人经验总结与实用建议写了这么多最后分享几条我在实际项目中总结出来的经验。第一条永远不要假设系统默认编码。CP_ACP在不同系统上不一样setlocale(LC_CTYPE, )的行为也不完全可预测。显式指定代码页代码在哪里跑都一样。第二条转换点越少越好。程序内部统一用一种编码推荐UTF-16宽字符只在边界处转换。转换点多了出问题的概率就大。第三条转换失败要处理。不要忽略MultiByteToWideChar和WideCharToMultiByte的返回值不要忽略lpUsedDefaultChar标志。数据丢失了不报告后面排查起来更痛苦。第四条测试要覆盖多语言环境。如果只在一个语言环境下测试很多编码问题发现不了。至少要在中文和英文环境下各跑一遍。第五条善用工具。十六进制查看器、代码页查看命令、调试输出这些工具能帮你快速定位编码问题。不要靠猜要看实际字节。字符集处理这块知识看文档觉得简单实际用起来坑很多。希望这篇内容能帮你少走一些弯路。如果遇到奇怪的问题欢迎在评论区交流我尽量回复。
阅读完成 · 觉得有帮助?