当前位置: 首页 > news >正文

不同地区(语言)windows系统的字符串乱码问题

如以下场景:
在简体中文的win系统,记事本创建一个txt,输入

我是谁?
Who am I?

保存txt后,拷贝到日文系统中打开是这样的

ホメハヌヒュ」ソ
Who am I?

原因是系统的字符编码不同,不同编码的解析方法是不同的,按照错误的编码来解析就产生错误的结果了,比如常见的乱码

像上面的txt文件,二进制显示如下:

ce d2 ca c7 cb ad a3 bf 0d 0a 57 68 6f 20 61 6d 20 49 3f

这是GB2312编码的字符串,在日文系统中会按照Shift_JIS编码解析,导致错误

在记事本右下角可以看到编码方式是"ANSI",这个ANSI又是什么意思?见下文对代码页的详细说明

解决办法:
采用UTF-8编码,记事本中可以通过另存为选择编码

我是谁?
私は誰ですか?
Who am I?

二进制显示如下:

e6 88 91 e6 98 af e8 b0 81 ef bc 9f 0d 0a e7 a7 81 e3 81 af e8 aa b0 e3 81 a7 e3 81 99 e3 81 8b ef bc 9f 0d 0a 57 68 6f 20 61 6d 20 49 3f

不管在什么语言系统下显示都是按照UTF-8解析,可以正常显示字符串


代码页

通过命令行chcp可以查询系统当前的代码页,不同代码页对应不同的字符编码,比如日文系统是932,简体中文系统是936
但这里还要注意一点,命令行里查到的是OEM代码页,还有一个叫做ANSI代码页,区别如下

ANSI代码页用于Windows图形界面程序
OEM代码页用于命令行和控制台程序

有的语言这两会不一样,如俄文,OEM代码页是866,ANSI代码页是1251

常用字符编码和代码页的对应关系如下:

编码名称代码页别名说明
UTF-865001-8位Unicode转换格式,现代标准,跨平台通用
UTF-16LE1200-16位Unicode小端序,Windows内部使用
UTF-16BE1201-16位Unicode大端序,某些旧系统
UTF-32LE12000-32位Unicode小端序,特殊用途
UTF-32BE12001-32位Unicode大端序,特殊用途
Windows-12521252ANSI Latin-1英语、法语、德语、西班牙语等西欧语言
ISO-8859-128591Latin-1西欧语言(ISO标准)
IBM437437OEM US原始IBM PC编码,早期美国 DOS 系统
IBM850850Multilingual Latin-1多语言 DOS
GB2312936GBK, CP936简体中文(中国大陆)
GB1803054936-简体中文国家标准
Big5950-繁体中文(台湾、香港、澳门)
EUC-CN51936-中文扩展Unix编码
Shift_JIS932CP932, SJIS日语(Microsoft扩展)
ISO-2022-JP50220JIS日语(邮件标准)
EUC-JP51932-日语Unix编码
EUC-KR949CP949, UHC韩语扩展编码
ISO-2022-KR50225-韩语邮件标准
Johab1361-韩语字符集
Windows-12511251ANSI Cyrillic俄语、保加利亚语等
IBM866866OEM RussianDOS俄语编码
KOI8-R20866-俄语(Unix传统)
KOI8-U21866-乌克兰语
Windows-12561256-阿拉伯语
Windows-12551255-希伯来语
ISO-8859-628596-阿拉伯语
ISO-8859-828598-希伯来语
Windows-12501250-波兰语、捷克语、匈牙利语等
ISO-8859-228592-中欧语言
Windows-12541254-土耳其语
ISO-8859-928599-土耳其语
Windows-12571257-爱沙尼亚语、拉脱维亚语、立陶宛语
ISO-8859-428594-波罗的海语言

系统区域设置”决定代码页。windows系统在安装时会根据不同国家/地区修改系统区域设置,我们可以自行修改为其它语言(代码页)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


编程

对于编程,这个问题会更复杂一些。我们可以发现有一些程序只支持英文路径,其原因就是程序对字符串的处理没有考虑字符编码的问题,不同语言的字符需要特定字符编码才能表示,而英文字符在ASCII中规定,单字节的字符编码(如GBK、UTF-8)都兼容ASCII,而多字节编码(如UTF-16、UTF-32)则不兼容 ASCII

下面讨论一些编码造成的典型问题

1、字符串字面量
源代码(.h、.cpp)文件的字符编码会决定字面量的编码,如

    const char* str = "我是谁";printf("%s", str);printf("\n");for (int i = 0; i < strlen(str); i++) {printf("%02X ", (unsigned char)str[i]);}printf("\n");

用记事本或其它工具打开源码文件,右下角查看字符编码,正常应该是ANSI(此处为简体中文系统,代码页936,对应GB2312)。打印结果如下

我是谁
CE D2 CA C7 CB AD

字符串显示正常

通过记事本把源码文件的编码改为UTF-8,VS-“配置属性”-“C/C++”>“命令行”属性页>“其它选项”填/utf-8 (必须VS 2019以上才支持该命令),重新编译,打印结果如下

鎴戞槸璋
E6 88 91 E6 98 AF E8 B0 81

字符串显示乱码,此时通过勾选"系统区域设置"-"Beta版:使用Unicode UTF-8 提供全球语言支持"可以使printf打印正常的字符

VS默认是按照ANSI保存源代码文件及编译,所以字面量支持本地代码页的字符,如代码页936就可以直接表示中文字符。但如果程序运行在其它代码页的系统时,str的内存打印结果如下

CE D2 CA C7 CB AD

可见,程序依然按照编译时的代码页936来解析字符串

如果希望用字面量表示字符能够在不同的代码页系统中保持一致,有两种思路:

一是使用宽字符,对于windows就是UTF-16 LE编码
以下代码在不同代码页下都能够获得正确的输出

    //(这段代码可以忽略)初始化控制台,为了在不同代码页环境下正确显示中文HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);DWORD charsWritten;CONSOLE_FONT_INFOEX fontInfo;fontInfo.cbSize = sizeof(fontInfo);GetCurrentConsoleFontEx(hConsole, FALSE, &fontInfo);wcscpy_s(fontInfo.FaceName, L"NSimSun");     //设置控制台字体为新宋体,确保正确显示中文SetCurrentConsoleFontEx(hConsole, FALSE, &fontInfo);//核心代码const wchar_t* str_w = L"我是谁";WriteConsoleW(hConsole, str_w, wcslen(str_w), &charsWritten, NULL);  //通过WriteConsoleW来正确输出宽字符,避免使用wprintfWriteConsoleW(hConsole, L"\n", 1, &charsWritten, NULL);for (int i = 0; i < wcslen(str_w); i++) {for (int j = 0; j < sizeof(wchar_t); j++) {printf("%02X ", ((unsigned char*)str_w)[i * sizeof(wchar_t) + j]);}}printf("\n");

二是先把字面量(编译时的字符编码)转换为UTF-8编码后再处理,避免使用ANSI编码。这样就可以仍然用窄字符表示字符串
以下代码在不同代码页下都能够获得一致的输出

		//(这段代码可以忽略)初始化控制台,让其支持打印UTF-8字符SetConsoleOutputCP(65001);SetConsoleCP(65001);HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);DWORD charsWritten;CONSOLE_FONT_INFOEX fontInfo;fontInfo.cbSize = sizeof(fontInfo);GetCurrentConsoleFontEx(hConsole, FALSE, &fontInfo);wcscpy_s(fontInfo.FaceName, L"NSimSun");     //设置控制台字体为新宋体,确保正确显示中文SetCurrentConsoleFontEx(hConsole, FALSE, &fontInfo);//核心代码const char* str = "我是谁";  //编译时的字符编码为GB2312auto GbkToUtf16 = [=](const std::string& str) {std::wstring ret;if (str.empty()) return ret;int wide_len = MultiByteToWideChar(936, 0, str.c_str(), -1, nullptr, 0);if (wide_len == 0) return ret;std::vector<wchar_t> buffer(wide_len);MultiByteToWideChar(936, 0, str.c_str(), -1, buffer.data(), wide_len);return std::wstring(buffer.data());};auto Utf16ToUtf8 = [](const std::wstring& str) {std::string ret;if (str.empty()) return ret;int utf8_len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, nullptr, 0, nullptr, nullptr);if (utf8_len == 0) return ret;std::vector<char> buffer(utf8_len);WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, buffer.data(), utf8_len, nullptr, nullptr);return std::string(buffer.data());};std::string str_utf8 = Utf16ToUtf8(GbkToUtf16(str));  //GB2312转换为UTF-8printf("%s", str_utf8.c_str());printf("\n");for (int i = 0; i < str_utf8.size(); i++) {printf("%02X ", (unsigned char)str_utf8[i]);}printf("\n");

2、windows api的字符编码
常用的windows api一般分为两个版本,FunctionAFunctionW,用于区分不同的字符串处理方式,如果写Function会根据宏UNICODE决定使用哪个版本。FunctionA对于输入参数中的字符串会按照ANSI(依赖代码页)编码来解析,后者则按照UTF-16(Unicode)编码。这里以INI文件的解析接口GetPrivateProfileInt 为例进行分析

#include <windows.h>
#include <iostream>void exampleUsage() {// 方法1:明确使用A版本(ANSI)UINT valueA = GetPrivateProfileIntA("Section",          // ANSI字符串"Key",              // ANSI字符串0,                  // 默认值"config.ini"        // ANSI文件名);// 方法2:明确使用W版本(Unicode)UINT valueW = GetPrivateProfileIntW(L"Section",         // Unicode字符串(L前缀)L"Key",             // Unicode字符串0,                  // 默认值L"config.ini"       // Unicode文件名);// 方法3:使用宏版本(推荐)UINT value = GetPrivateProfileInt(TEXT("Section"),    // 使用TEXT宏TEXT("Key"),        // 自动适配编码0,                  // 默认值TEXT("config.ini")  // 自动适配编码);std::cout << "Value: " << value << std::endl;
}

头文件

// 在winnt.h中的定义
#ifdef UNICODE
typedef wchar_t TCHAR;
#define TEXT(quote) L##quote
#else
typedef char TCHAR;
#define TEXT(quote) quote
#endif// 函数声明
#ifdef UNICODE
#define GetPrivateProfileInt  GetPrivateProfileIntW
#else
#define GetPrivateProfileInt  GetPrivateProfileIntA
#endif

假设简体中文系统(代码页936),字符编码为ANSI的config.ini,内容如下

[我是谁]
我=1

调用GetPrivateProfileIntA可以读取key"我"的值

		char section[] = {0xce, 0xd2,0xca, 0xc7,0xcb, 0xad,0x00};  //GB2312编码的“我是谁”char key[] = { 0xce, 0xd2, 0x00 };  //GB2312编码的“我”int value = GetPrivateProfileIntA(section, key, -1, "config.ini");

通过记事本将上面的config.ini保存为UTF-16 LE编码,config_utf16.ini,需要调用GetPrivateProfileIntW才可以读取key"我"的值

		wchar_t section_w[] = {0x6211,0x662F, 0x8C01, 0x0000};  //UTF-16 LE编码的“我是谁”wchar_t key_w[] = { 0x6211, 0x0000 };  //UTF-16 LE编码的“我”int value = GetPrivateProfileIntW(section_w, key_w, -1, "config_utf16.ini");   		

对于字符编码为UTF-8的ini文件,不能直接用GetPrivateProfileInt来读取,只能自己写代码实现ini解析,或者勾选"系统区域设置"-"Beta版:使用Unicode UTF-8 提供全球语言支持"将代码页强制设置为65001即UTF-8,则可以调用GetPrivateProfileIntA读取成功

3、标准库的字符编码
一般会有窄字符和宽字符两个版本的函数或类,如ifstreamwifstream
windows平台窄字符对应ANSI编码,宽字符对应UTF-16 LE编码;Linux/macOS的窄字符对应UTF-8编码

对于windows,为了适配多语言(不同代码页)环境,需要使用宽字符。系统对于诸如文件名、目录名都是采用UTF-16编码保存的

http://www.dtcms.com/a/412160.html

相关文章:

  • 英文外贸商城网站设计wordpress网站怎么加速
  • 软考-系统架构设计师 关系数据库详细讲解
  • 国外网站购物西安到北京火车票查询
  • 视频广告网站h5 技术做健康类网站
  • Java基础-面向对象复习知识3
  • [Windows] VarCalc v0.0.1
  • 网站开发培训北京有用建站宝盒做网站的吗
  • 亚马逊网站青岛鑫隆建设集团网站
  • 南宁最高端网站建设局域网内部网站建设app下载
  • 电机东莞网站建设建立网站时首先考虑的问题
  • 有没有什么网站做泰国的东西免费国内linux服务器
  • 销售网站制作电话甘肃交通建设监理公司网站
  • sqlite是什么
  • React学习第二天——表单控件
  • wordpress多站点详细设置(图解)杭州网络科技公司排名
  • 网站建设公司ejiew网站建设的公司哪家好
  • 免费前端模板网站公司加盟
  • ResNeXt-50--分组卷积--J6
  • 【开题答辩全过程】以 “勤工有道”微信小程序为例,包含答辩的问题和答案
  • 淄博网站建设费用摄影网站开发综述
  • 个人模板网站wordpress主题免费吗
  • 基于英飞凌PSOC Control C3的高速吹风机变频控制方案
  • 服装企业网站建设的目的网站宣传搭建
  • 有没有做维修的网站一个服务器可以做两个网站
  • display version 概念、故障排错及题目
  • 沈阳网站开发培训多少钱网站主页布局
  • 快速排名网站系统如何做不同域名跳转同一个网站
  • 网赌网站怎么做网站备案是域名备案还是空间备案
  • 第八章 惊喜12 信息传达
  • 贵池区城乡与住房建设网站做网站 图片格式