简介

经常在处理字符串的时候出现乱码,主要是字符串编码未正确处理。在这种情况下我们首先要分析清楚输入字符串的编码,经过处理的编码和最终输出的编码是否是正确处理的。

本章以内存分析方法跟踪字符集变化的过程,以了解字符存储与转换的编码问题。

宽字符和多字节

  • 宽字符和多字节是存储一个字符的字节不一样,和字符编码无关。多字节就是用一个字节表示一个字符,宽字符就是用≥2个字节表示一个字符。

    #include <string>
    #include <iostream>void printMem(char* pMem, size_t size)
    {for (std::size_t index = 0; index < size; index++){if (index != 0){std::cout << " ";}std::cout << std::hex << (int)pMem[index];}std::cout << std::endl;
    }int main()
    {std::string str1("AB");printMem(str1.data(), str1.length() + 1);std::wstring str2(L"AB");printMem((char*)str2.data(), (str2.length() + 1) * 2);return 0;
    }// 结果输出:
    // 41 42 0
    // 41 0  42 0 0 0
    

查看系统编码

  • 查看字符集编码

    windows在cmd命令下输入chcp命令可查看。如下:

    代码页是字符集编码的别名,也有人称"内码表。下表列出了所有支持的代码页及其国家(地区)或者语言:

    代码页       国家(地区)或语言
    437          美国
    708          阿拉伯文(ASMO 708)
    720          阿拉伯文(DOS)
    850          多语言(拉丁文 I)
    852          中欧(DOS) - 斯拉夫语(拉丁文 II)
    855          西里尔文(俄语)
    857          土耳其语
    860          葡萄牙语
    861          冰岛语
    862          希伯来文(DOS)
    863          加拿大 - 法语
    865          日耳曼语
    866          俄语 - 西里尔文(DOS)
    869          现代希腊语
    874          泰文(Windows)
    932          日文(Shift-JIS)
    936          中国 - 简体中文(GB2312)现在是GBK了,GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准。
    949          韩文
    950          繁体中文(Big5)
    1200         Unicode
    1201         Unicode (Big-Endian)
    1250         中欧(Windows)
    1251         西里尔文(Windows)
    1252         西欧(Windows)
    1253         希腊文(Windows)
    1254         土耳其文(Windows)
    1255         希伯来文(Windows)
    1256         阿拉伯文(Windows)
    1257         波罗的海文(Windows)
    1258         越南文(Windows)
    20866        西里尔文(KOI8-R)
    21866        西里尔文(KOI8-U)
    28592        中欧(ISO)
    28593        拉丁文 3 (ISO)
    28594        波罗的海文(ISO)
    28595        西里尔文(ISO)
    28596        阿拉伯文(ISO)
    28597        希腊文(ISO)
    28598        希伯来文(ISO-Visual)
    38598        希伯来文(ISO-Logical)
    50000        用户定义的
    50001        自动选择
    50220        日文(JIS)
    50221        日文(JIS-允许一个字节的片假名)
    50222        日文(JIS-允许一个字节的片假名 - SO/SI)
    50225        韩文(ISO)
    50932        日文(自动选择)
    50949        韩文(自动选择)
    51932        日文(EUC)
    51949        韩文(EUC)
    52936        简体中文(HZ)
    65000        Unicode (UTF-7)
    65001        Unicode (UTF-8)
    

    linux下通过locale命令查询。如下:

乱码之字符集

首先我们需要了解代码开发中,从源代码到执行文件到最后展示出来的字符集:

  • 源码字符集:即源代码保存的字符集

  • 执行字符集:二进制执行文件中字符串的字符集

  • 显示字符集:这个概念比较笼统,比如写入文件时用户打开展示的字符集,显示到控制台控制的字符集,显示到界面展示的字符集。

乱码之文件内容字符串转换

先以简单的读取文件内容,并在控制台显示举例。代码展示了从utf8和gb2312的文件编码中读取数据,并都按utf8和gb2312的方式呈现在控制台。

  • 测试代码如下:

    #include <string>
    #include <iostream>
    #include <locale>
    #include <windows.h>
    #include <fstream>std::string translateStrCodePage(unsigned int codepageFrom, const std::string &strFrom, unsigned int codepageTo)
    {// 计算字符串所需空间大小int len = MultiByteToWideChar(codepageFrom, 0, strFrom.c_str(), -1, NULL, 0);wchar_t* wStr = new wchar_t[len + 1];memset(wStr, 0, len + 1);// 转换为宽字符MultiByteToWideChar(codepageFrom, 0, strFrom.c_str(), -1, wStr, len);// 计算从codepageFrom到codepageTo的所需空间大小len = WideCharToMultiByte(codepageTo, 0, wStr, -1, NULL, 0, NULL, NULL);char* str = new char[len + 1];memset(str, 0, len + 1);// 转换WideCharToMultiByte(codepageTo, 0, wStr, -1, str, len, NULL, NULL);std::string strTo(str);delete[] wStr;delete[] str;return strTo;
    }std::wstring translateStrToWStr(unsigned int codepage, const std::string &strFrom)
    {// 计算字符串所需空间大小int len = MultiByteToWideChar(codepage, 0, strFrom.c_str(), -1, NULL, 0);wchar_t* wStr = new wchar_t[len + 1];memset(wStr, 0, len + 1);// 转换为宽字符MultiByteToWideChar(codepage, 0, strFrom.c_str(), -1, wStr, len);std::wstring strTo(wStr);delete[] wStr;return strTo;
    }void showFileToDispaly(const std::string &filePath, unsigned int codepageFile, unsigned int codepageDisplay)
    {std::cout << "showFileToDispaly file " << filePath << " from codepage " << codepageFile << " to " << codepageDisplay << std::endl;std::ifstream fileIn;fileIn.open(filePath, std::ios_base::in | std::ios_base::binary);if (!fileIn.is_open()){std::cout << "file not open." << std::endl;return;}// 设置控制台显示编码格式SetConsoleOutputCP(codepageDisplay);std::string str1;fileIn >> str1;// 设置cout识别的字符编码格式为最终要显示的编码auto strCodepageDisplay = "." + std::to_string(codepageDisplay);std::cout.imbue(std::locale(strCodepageDisplay));// 判断文件和要显示的字符编码是否一致,否则转换编码if (codepageFile != codepageDisplay){str1 = translateStrCodePage(codepageFile, str1, codepageDisplay);}std::cout << "string:" << str1 << ",len:" << str1.length() << std::endl;// 设置wcout识别的字符编码格式为最终要显示的编码std::wcout.imbue(std::locale(strCodepageDisplay));// 根据显示的字符编码转换成宽字符auto str2 = translateStrToWStr(codepageDisplay, str1);std::wcout << "wstring:" << str2 << ",len:" << str2.length() << std::endl;fileIn.close();
    }int main()
    {// utf-8的codpageunsigned int codepageUtf8 = 65001;unsigned int codepageGb2312 = 936;// 文件是utf8的,显示也使用utf8showFileToDispaly("d:/1.txt", codepageUtf8, codepageUtf8);std::cout << std::endl;// 文件是utf8的,显示也使用GBK2312showFileToDispaly("d:/1.txt", codepageUtf8, codepageGb2312);std::cout << std::endl;// 文件是GB2312的,显示也使用GB2312showFileToDispaly("d:/2.txt", codepageGb2312, codepageGb2312);std::cout << std::endl;// 文件是GB2312的,显示也使用utf8showFileToDispaly("d:/2.txt", codepageGb2312, codepageUtf8);return 0;
    }
    
  • 输出结果:

  • 分析:

    • 在多字节的情况下,UTF8和GB2312的长度不一致。UTF8中一个中文字符占用3个字节,在GB2312中一个中文字符占用2个字节。
    • 在宽字符的情况下,UTF8和GB2312的长度一致的。

乱码之源码文件包含中文字符的字符集

这种方式要稍微复杂点,要确定输入的字符格式。首先要确定源码的文件格式和编译要将源码转换成的执行字符集,以确定最终执行字符集。

  • 不同字符集的源文件的中文字符保存

    源文件的内容是以UTF8编码保存,那字符串在源码文件中是以utf8编码保存。不管源码文件时以utf8的bom方式和非bom方式都是这样的。

    • utf8不带bom格式的源码文件中字符的保存

    • utf8带bom格式的源码文件中字符的保存

      utf8带bom格式的,前面3个字节是BOM(Byte order mark),后面的utf8字符串的字节和不带bom的是一致的。

      BOM是Unicode的字节顺序标记,有2个作用:

      1. 说明字符流属于Unicode编码,且表明了编码方式
      2. 说明了字节序:big endian 和 little endian
      编码方式 BOM(十六进制)
      UTF-8 EF BB BF
      UTF-16(BE) FE FF
      UTF-16(LE) FF FE
      UTF-32(BE) 00 00 FE FF
      UTF-32(LE) FF EE 00 00

    • gb2312字符集的源码中文字符

  • utf8 无bom编码的源码文件的执行字符集

    定义的字符常量就是utf8的,并且不能使用u8指定字符串。

    • 定义的常量字符串就是utf8

      源码的文件是utf8,那字符串在源码文件中就是按utf8编码保存;visual studio编译器编译解析源码时,源码格式是utf-8无bom编码的,编译器不知道codepage,会以当前语言作为源码字符的编码方式,也就是他认为这个字符串是gbk的编码,因编译器未指定目标程序的字符串,会当前环境gbk来处理,gbk转gbk无必要转换,所以visual studio编译器编译解析源码时,会把引号的内容当成二进制数据直接保存到std::string中,也就是可执行程序中的字符串就是按utf8保存的。

      #include <string>int main()
      {std::string str1 = "中文";auto str1Data = str1.data();return 0;
      }
      

    • 不能使用u8指定字符串或者增加execution_character_set(“utf-8”)编译指令

      源码的文件是utf8,那字符串在源码文件中就是按utf8编码保存;visual studio编译器编译解析源码时,源码格式是utf-8无bom编码的,编译器不知道codepage,会以当前语言作为源码字符的编码方式,也就是他认为这个字符串是gbk的编码,因指定了u8,会转会为utf8。也就是编译器会错误的将本是utf8的字符串当成GBK转换utf8编码。

      以下测试代码验证该结论,首先如下代码以utf-8无bom格式保存,然后运行后,从内存可以看出,str2Data手动将字符串从GBK转为UTF8后的字符串内存和vs 将u8字符串自动转换的内存一致的。

      #include <string>
      #include <windows.h>std::string translateStrCodePage(unsigned int codepageFrom, const std::string& strFrom, unsigned int codepageTo)
      {// 计算字符串所需空间大小int len = MultiByteToWideChar(codepageFrom, 0, strFrom.c_str(), -1, NULL, 0);wchar_t* wStr = new wchar_t[len + 1];memset(wStr, 0, len + 1);// 转换为宽字符MultiByteToWideChar(codepageFrom, 0, strFrom.c_str(), -1, wStr, len);// 计算从codepageFrom到codepageTo的所需空间大小len = WideCharToMultiByte(codepageTo, 0, wStr, -1, NULL, 0, NULL, NULL);char* str = new char[len + 1];memset(str, 0, len + 1);// 转换WideCharToMultiByte(codepageTo, 0, wStr, -1, str, len, NULL, NULL);std::string strTo(str);delete[] wStr;delete[] str;return strTo;
      }int main()
      {std::string str1 = "中文";auto str2 = translateStrCodePage(936, str1, 65001);auto str2Data = str2.data();std::string str3 = u8"中文";auto str3Data = str3.data();return 0;
      }
      

  • utf-8 BOM编码的源码文件的执行字符集

    执行程序的中文字符串常量是gb2312编码;使用了u8指定或者增加execution_character_set(“utf-8”)编译指令后,字符常量是utf-8编码。

    • 定义的中文字符串常量是gb2312编码

      这个就比较清晰了,源码的文件是utf8,那字符串在源码文件中就是按utf8编码保存;visual studio编译器编译解析源码时,源码格式是utf-8有bom编码的,编译器识别到codepage,正确的对源码以utf8方式解析,因编译器未指定目标程序的字符串,会当前环境gbk来处理,字符串正确的以utf8转gbk,最后当前的二进制的字符串以gbk保存。

      #include <string>int main()
      {std::string str1 = "中文";auto str1Data = str1.data();return 0;
      }
      

    • 使用了u8指定或者增加execution_character_set(“utf-8”)编译指令后,字符常量是utf-8编码:

      这个也比较清晰,源码的文件是utf8,那字符串在源码文件中就是按utf8编码保存;visual studio编译器编译解析源码时,源码格式是utf-8有bom编码的,编译器识别到codepage,正确的对源码以utf8方式解析,编译器指定以uf8作为执行字符集,最终是utf8到utf8的一个转换,能正确识别。

  • gbk2312编码的源码文件的执行字符集

    这个能识别到codepage能正确识别。

C++:乱码之字符串编码相关推荐

  1. python字符串编码及乱码解决方案

    http://blog.csdn.net/pipisorry/article/details/44136297 字符编码详解 [字符编码ASCII,Unicode和UTF-8] 主要非英文字符集的编码 ...

  2. String字符串编码解码格式

    https://blog.csdn.net/qq_35241080/article/details/83001149 //2 如何识别字符串编码 public static String getEnc ...

  3. python中文字符串编码_浅谈python下含中文字符串正则表达式的编码问题

    前言 Python文件默认的编码格式是ascii ,无法识别汉字,因为ascii码中没有中文. 所以py文件中要写中文字符时,一般在开头加 # -*- coding: utf-8 -*- 或者 #co ...

  4. python3中字符串编码常见种类_Python基础篇—标准数据类型—String字符串编码问题...

    我要开始写String编码问题了...脑壳疼.. 在String字符串的第一篇末尾有留一个坑,就是关于中文字符串编码.整个编码的故事说起来都是很费劲的,我也只能把我所知道的梳理整理一下,在日常敲码过程 ...

  5. python中字符串编码转换

    字符串编码转换程序员最苦逼的地方,什么乱码之类的几乎都是由汉字引起的. 其实编码问题很好搞定,只要记住一点: 任何平台的任何编码,都能和Unicode互相转换. UTF-8与GBK互相转换,那就先把U ...

  6. QT乱码总结7.编码测试和总结二

    QT乱码总结0.Qt乱码产生因素 https://blog.csdn.net/liujiayu2/article/details/103167953 QT乱码总结1.Unicode 和 UTF-8 h ...

  7. QT乱码总结6.编码测试和总结一

    QT乱码总结0.Qt乱码产生因素 https://blog.csdn.net/liujiayu2/article/details/103167953 QT乱码总结1.Unicode 和 UTF-8 h ...

  8. Java笔记-字符串编码与解码以及编码表原理

    编码表 编码表:是一张由字符及其对应编码的表 计算机只能识别二进制数据,早期由电信号演化而来. 为了方便使用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张编码 ...

  9. python2字符串编码方式_一、基础部分-2.字符串编码

    一.字符编码历史 1. ASCII 美国人搞了个ASCII码表,把123abcABC%$#(数字.字母.特殊符号) ,全部用10进制的数字表示.例如数字65,代表着"A" ,ASC ...

最新文章

  1. SSH下的组合批量增加
  2. autumn 0.5.1 : Python Package Index
  3. CodeForces - 1207F Remainder Problem(分块)
  4. GCC 中文手册 - 摘自纯C论坛
  5. 【数学基础】最小二乘法
  6. Hadoop集群安装(真分布式)
  7. Spring Boot + Spring Cloud 实现权限管理系统 后端篇(八):MyBatis分页功能实现
  8. el表达式判断不为空_Java学习72天---EL和JSTL表达式学完.
  9. openfeign seata事务不回滚_Spring,你为何中止我的事务?
  10. 第二章、Linux操作系统及常用命令
  11. 有的字体,设置了粗体,也不能用粗体方式来绘制
  12. Day296.原子类 -Juc
  13. 难译 | windbg 乐趣之道(下)
  14. mysql_assoc函数_关于PHP的函数mysql_fetch_assoc的问题
  15. 《前端》eval函数
  16. 雅西高速交警列16处危险路段 司机需小心行驶
  17. 安卓 网络工具_小米公布MIUI适配计划,支持10台机型升级安卓Q,9款今年内测
  18. hive的窗口函数详解
  19. 2022年登高架设免费试题及登高架设复审考试
  20. 安徽省计算机水平考试试卷,第一次安徽省计算机水平考试试卷.doc

热门文章

  1. 开发者工具的暖心提示语
  2. vue 用key拿对象value_基于vue--key值的特殊用处详解
  3. 手机间高速传输---微传
  4. Dubbo学习笔记:No provider available for the service ...异常问题的解决
  5. 英语笔记(计算机词汇,翻译/写作)
  6. 计算机u盘设备无法启动不了,系统提示“该设备无法启动(代码:10)”,USB设备不能开始工作怎么办?...
  7. c语言程序设计学籍信息,c语言学籍信息管理系统设计
  8. 骗子网站--正规网赚系统--www.j9m2.com--诈骗网站
  9. 19 01 18 dango 模型
  10. (数据结构)1.实现顺序栈的各种基本运算 2.实现环形队列的各种基本运算