c++ 中字符串编码 转码
转自:https://my.oschina.net/shelllife/blog/1827897
C/C++语言中的字符类型
存在两种表示字符的基本类型:
- char:一个字节8bit表示,最多表示256个字符,表示和用来处理ASCII字符集,国际通用
- wchar_t:多字节字符表示,典型2个字节或者4个字节,如GNU libc中为4B,可以表示更多的字符,满足国际化应用开发的需求,实现标准
在开发中ASCII编码字符都是用char来表示,可以转换成wchar_t表示;wchar_t类型与Unicode编码是完全独立的概念,不过在实现上Unicode编码一般用wchar_t来表示实现而已,但wchar_t字符并不一定就是Unicode编码字符。
对应两种字符类型存在两种字符串类型(C++):
- string: char字符列表或者是字节列表(bytes)
- wstring: wchar_t字符列表或者是宽子节列表
对应两种字符类型的输出函数流对象有:
- sprintf/wsprintf: 分别对应char与wchar_t
- cout/wcout:分别对应string与wstring
- stringstream/wstringstream: 分别对应string与wstring
字符串常量
C++11标准中增加了一些表示字符串常量的标识,如下有:
- L"您好!": wstring字符串常量,使用文件保存编码方式字符集
- R"(您 好 \n)": 原始字符串常量(字节数组),保留所有的字符
- u8"您好!": string字符串常量(字节数组),使用UTF8进行编码保存
字符集及编码
已知有很多的字符集,比如:ASCII,UTF8,GBK,GB2312,UTF16,UTF32,UNICODE,Latin等等。常用中文字符集编码有:
- UTF8:又分为带签名和不带签名两种,Windows代码页为65001,VS中应该选择【UTF8-带签名】的格式
- GBK/GB2312:Windows代码页为936
- GB18030: Windows代码页为54936
小技巧:修改Windows系统中cmd命令行窗口的显示字符集,默认字符集为OS字符集,如GBK-936。如果希望显示UTF8字符,则可以修改:chcp 65001.
在VS项目的调试命令窗口中无法手动修改,可以通过system函数来修改:
system("C:\\Windows\\system32\\chcp 65001");// 修改终端字符集为UTF8
源文件的编码保存选项
可以将源文件保存成不同的编码方式,如果文件中有中文,则必须选择可以对中文进行编码的字符集,如UTF8,GBK,GB2312等,否认可能会出现莫名其妙的编译错误,因为文件中存在无法识别的字符内容。
在Visual Studio中,选中文件后该设置在:【文件 - 高级保存选项】中。
C++11中GBK/UTF/wchar_t之间的编码处理转换
在处理中文时,不同的应用场景下总是无法避免进行GBK和UTF8之间的相互转换,C++11标识提供了<locale>和<codecvt>机制和工具来简化处理。
借助其中的std::wstring_convert和std::codecvt_utf8模板,通过wchar_t类型为中介,可以快速地实现转换,基本代码如下:
/*
转换GBK编码的中文到UTF8编码:GBK - WChar - UTF8两次转换
*/
//GBK在linux下的locale名可能是"zh_CN.GBK"
const char* GBK_LOCALE_NAME = ".936"; //GBK在windows下的locale name
std::wstring_convert<std::codecvt_byname<wchar_t, char, mbstate_t>> Conver_GBK(new codecvt_byname<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME)); //GBK - wchar_t
std::wstring _wname = Conver_GBK.from_bytes(data.Name); // 输入为char*的字符串表示或者数组,输出为wstring
std::wstring _waddr = Conver_GBK.from_bytes(data.Address);// 输入为char*的字符串表示或者数组,输出为wstring
std::wstring _wdept = Conver_GBK.from_bytes(data.GrantDept);// 输入为char*的字符串表示或者数组,输出为wstring
wcout << "Name: " << _wname << ",addr:" << _waddr << ",dept:" << _wdept << endl;
// 将wstring转化为UTF8编码的字节数组string表示
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
string _name = conv.to_bytes(_wname);// 输入为wstring字符串
string _addr = conv.to_bytes(_waddr);
string _dept = conv.to_bytes(_wdept);
// 将UTF8字符串转换为GBK字符编码表示string字节数组
std::string _name = Conver_GBK.to_bytes(conv.from_bytes(_name)); // 先转换成wstring,然后再转换成GBK的string
std::string _addr = Conver_GBK.to_bytes(conv.from_bytes(_addr)); // 先转换成wstring,然后再转换成GBK的string
https://blog.csdn.net/xuyouqiang1987/article/details/102869440
字符集
简单的说,字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。常见的字符集如ASCII、GB2312、GBK等。
下面就是屌
这个字在各种编码下的十六进制和二进制编码结果
字符编码
字符集只是一个规则集合的名字,对应到真实生活中,字符集就是对某种语言的称呼。例如:英语,汉语,日语。
对于一个字符集来说要正确编码转码一个字符需要三个关键元素:字库表、编码字符集、字符编码。
字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。
编码字符集,即用一个编码值来表示一个字符在字库中的位置。
字符编码定义编码字符集和实际存储数值之间的转换关系。一般来说,都会直接将字符的编码值直接进行存储。例如在ASCII中A
在表中排第65位,而编码后A
的数值是0100 0001
也即十进制的65的二进制转换结果。
字符集与字符编码的关系
一般一个字符集等同于一个编码方式,ANSI体系的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我们说一种编码都是针对某一特定的字符集。
一个字符集上也可以有多种编码方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。
多种字符编码存在的意义
既然字库表中的每一个字符都有一个自己的序号,直接把序号作为存储内容就好了。为什么还要多此一举通过字符编码
把序号转换成另外一种存储格式呢?
统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。例如中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。于是就出现了UTF-8这样的变长编码。在UTF-8编码中原本只需要一个字节的ASCII字符,仍然只占一个字节。而像中文及日语这样的复杂字符就需要2个到3个字节来存储。
字符编码的发展历史
第一个阶段:ASCII字符集和ASCII编码
计算机刚开始只支持英语(即拉丁字符),其它语言不能够在计算机上存储和显示。ASCII用一个字节(Byte)的7位(bit)表示一个字符,第一位置0。后来为了表示更多的欧洲常用字符又对ASCII进行了扩展,又有了EASCII,EASCII用8位表示一个字符,使它能多表示128个字符,支持了部分西欧字符。
第二个阶段:ANSI编码(本地化)
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。
不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。
第三个阶段:UNICODE(国际化)
为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。UNICODE 常见的有三种编码方式:UTF-8(1-4个字节表示)、UTF-16((2个字节表示))、UTF-32(4个字节表示)。
活动代码页
代码页是字符集编码的别名,也有人称"内码表"。
早期,代码页是IBM称呼电脑BIOS本身支持的字符集编码的名称。当时通用的操作系统都是命令行界面系统,这些操作系统直接使用BIOS供应的VGA功能来显示字符,操作系统的编码支持也就依靠BIOS的编码。现在这BIOS代码页被称为OEM代码页。图形操作系统解决了此问题,图形操作系统使用自己字符呈现引擎可以支持很多不同的字符集编码。
早期IBM和微软内部使用特别数字来标记这些编码,其实大多的这些编码已经有自己的名称了。虽然图形操作系统可以支持很多编码,很多微软程序还使用这些数字来点名某编码。
下表列出了部分代码页及其国家(地区)或者语言:
代码页 国家(地区)或语言
437 美国
932 日文(Shift-JIS)
936 中国 - 简体中文(GB2312)
950 繁体中文(Big5)
1200 Unicode
1201 Unicode (Big-Endian)
50220 日文(JIS)
50225 韩文(ISO)
50932 日文(自动选择)
50949 韩文(自动选择)
52936 简体中文(HZ)
65000 Unicode (UTF-7)
65001 Unicode (UTF-8)
c++的多字节字符与宽字节字符
C++基本数据类型中表示字符的有两种:char、wchar_t。
char叫多字节字符,一个char占一个字节,之所以叫多字节字符是因为它表示一个字时可能是一个字节也可能是多个字节。一个英文字符(如’s’)用一个char(一个字节)表示,一个中文汉字(如’中’)用2个char(二个字节)表示,看下面的例子。
-
void TestChar()
-
{
-
char ch1 = 's'; // 正确
-
cout << "ch1:" << ch1 << endl;
-
char ch2 = '中'; // 错误,一个char不能完整存放一个汉字信息
-
cout << "ch2:" << ch2 << endl;
-
-
char str[3] = "中"; //前二个字节存放汉字'中',最后一个字节存放字符串结束符\0
-
cout << "str:" << str << endl;
-
}
c++的多字节字符串与宽字节字符串
字符数组可以表示一个字符串,但它是一个定长的字符串,我们在使用之前必须知道这个数组的长度。为方便字符串的操作,STL为我们定义好了字符串的类string和wstring。大家对string肯定不陌生,但wstring可能就用的少了。
string是普通的多字节版本,是基于char的,对char数组进行的一种封装。
wstring是Unicode版本,是基于wchar_t的,对wchar_t数组进行的一种封装。
###string 与 wstring的相关转换:
以下的两个方法是跨平台的,可在Windows下使用,也可在Linux下使用。
C++程序输出字符串的编码
C++程序输出的字符串编码规则如下:
- 若程序中指定了字符串的编码,则输出字符串的编码与指定编码相同;
- 若程序中没有指定字符串的编码,则输出字符串的编码与程序在编译时Windows代码页编码相同(Linux情况尚未实验);
- 程序输出字符串的编码与程序源文件编码无关;
对规则1的证实可以使用如下代码:
-
-
-
int main()
-
{
-
using namespace std;
-
-
ofstream OutFile("OutInUTF8.txt");
-
OutFile << "你" << endl;
-
OutFile.close();
-
-
return 0;
-
}
以上代码在活动代码页为GBK的Window中生成的txt文件的编码是UTF8。
对规则2的证实可以使用如下代码:
-
-
-
int main()
-
{
-
using namespace std;
-
-
ofstream OutFile("Output.txt");
-
OutFile << "你" << endl;
-
OutFile.close();
-
-
return 0;
-
}
上面的程序如果编译时Window的代码页是936(GBK),结果生成的txt文件编码也是GBK。即使将该程序在Window的代码页为65001(UTF8)的Windows系统中运行,生成的txt文件编码依然是GBK。
对规则3的证实:
仍然使用上面的程序。但是其源文件编码设成UTF8,然后在代码页是936(GBK)的Window系统上编译,结果生成的txt文件编码是GBK。
字符串常量
C++98标准中一些表示字符串常量的标识有:
- "您好": string字符串常量(字节数组),使用当前系统代码页进行编码
C++11标准中增加了一些表示字符串常量的标识,如下有:
- L"您好!": wstring字符串常量,使用文件保存编码方式字符集
- R"(您好 \n)": 原始字符串常量(字节数组),保留所有的字符
- u8"您好!": string字符串常量(字节数组),使用UTF8进行编码保存
可用以下程序进行验证
-
-
-
-
int main()
-
{
-
using namespace std;
-
-
string str;
-
wstring wstr;
-
-
str = "你好";
-
wstr = "你好"; //编译报错——“初始化”: 无法从“const char [3]”转换为“std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t>>”
-
-
str = L"你好"; //编译报错——无法从“const wchar_t [2]”转换为“std::basic_string<char,std::char_traits<char>,std::allocator<char>>”
-
wstr = L"你好";
-
-
str = R"(你好)";
-
wstr = R"(你好)";//编译报错——二进制“=”: 没有找到接受“const char [5]”类型的右操作数的运算符(或没有可接受的转换)
-
-
return 0;
-
}
-
C++11字符串换初始化方式
-
char16_t* p1 = u"中国";//把字符串初始化为UTF16字符串存储
-
char32_t* p2 = U"中国";//把字符串初始化为UTF32字符串存储
-
wchar_t* p3 = L"中国";//win是UCS2码下等同UTF16字符串,Linxu是UCS4码下等同utf32字符串
-
char* p4 = u8"中国";//把字符串初始化为UTF8字符串存储
-
char* p5 = "中国";//p5字符的编码格式见我的上篇博文《c++字符串乱码研究》
-
-
std::string str1 = u8"中国";
-
std::wstring str2 = L"中国";
-
std::u16String str3 = u"中国";
-
std::u32String str4 = U"中国";
可以看到以下字符串转换都是先转 std::wstring,即先转成字符的UCS码
std::string 转为 std::wstring( utf-8 --> wchar )
-
//src必须是UTF8否则抛异常
-
std::wstring utf8_to_wstr(const std::string& src)
-
{
-
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
-
return converter.from_bytes(src);
-
}
std::wstring转为std::string(wchar --> utf-8)
-
std::string wstr_to_utf8(const std::wstring& src)
-
{
-
std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
-
return convert.to_bytes(src);
-
}
在构造std::wstring_convert对象的时候需要传入一个codecvt对象的指针,如果没有传入,则默认使用new codecvt
来创建。std::wstring_convert自行维护codecvt对象的生命周期,它的析构函数会调用delete操作符来删除该对象。这就限制了只能使用通过new操作符来创建的codecvt,而不能使用从std::locale中获取的codecvt。
在C++标准提供的codecvt中,能够直接用于std::wstring_convert的只有三个:std::codecvt_utf8,std::codecvt_utf16以及std::codecvt_utf8_utf16。可见,标准只支持UTF族的字符编码。为了获取其它字符编码的codecvt,需要使std::codecvt_byname,这个类可以通过字符编码的名称来创建一个codecvt。这看起来挺不错,但遗憾的是,字符编码的名称并没有统一的标准,各个平台的支持情况都不一样。例如,在Windows下可以使用“chs”或“.936”来创建简体中文编码的codecvt,linux下可是"zh_CN.GBK"在Mac OS X下则要使用“zh_cn.gb2312”;甚至在Mac OS X下,即使成功创建了这个codecvt,它也不能正常地转换。
由于历史原因,std::codecvt_byname的析构函数是protected的(g++是,VC好像不是),std::wstring_convert不能对它调用delete,所以首先要自行定义一个类来继承std::codecvt_byname:
-
class chs_codecvt : public std::codecvt_byname<wchar_t, char, std::mbstate_t> {
-
public:
-
chs_codecvt() : codecvt_byname("chs") { }//zh_CN.GBK or .936
-
};
std::wstring 转GBK(wchar --> ansi)
-
std::string wstr_to_gbk(const std::wstring& str)
-
{
-
std::wstring_convert<chs_codecvt> converter;
-
return converter.to_bytes(str);
-
}
GBK 转为 std::wstring( ansi --> wchar )
-
std::wstring gbk_to_wstr(const std::string& str)
-
{
-
std::wstring_convert<chs_codecvt> converter;
-
return converter.from_bytes(str);
-
}
#include<string> #include<windows.h> #include<vector> using namespace std; //utf8 转 Unicode std::wstring Utf82Unicode(const std::string& utf8string) { int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0); if (widesize == ERROR_NO_UNICODE_TRANSLATION) { throw std::exception("Invalid UTF-8 sequence."); } if (widesize == 0) { throw std::exception("Error in conversion."); } std::vector<wchar_t> resultstring(widesize); int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize); if (convresult != widesize) { throw std::exception("La falla!"); } return std::wstring(&resultstring[0]); } //unicode 转为 ascii std::string WideByte2Acsi(std::wstring& wstrcode) { int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL); if (asciisize == ERROR_NO_UNICODE_TRANSLATION) { throw std::exception("Invalid UTF-8 sequence."); } if (asciisize == 0) { throw std::exception("Error in conversion."); } std::vector<char> resultstring(asciisize); int convresult =::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL); if (convresult != asciisize) { throw std::exception("La falla!"); } return std::string(&resultstring[0]); } //utf-8 转 ascii std::string UTF_82ASCII(std::string& strUtf8Code) { std::string strRet(""); //先把 utf8 转为 unicode std::wstring wstr = Utf82Unicode(strUtf8Code); //最后把 unicode 转为 ascii strRet = WideByte2Acsi(wstr); return strRet; } /// //ascii 转 Unicode std::wstring Acsi2WideByte(std::string& strascii) { int widesize = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0); if (widesize == ERROR_NO_UNICODE_TRANSLATION) { throw std::exception("Invalid UTF-8 sequence."); } if (widesize == 0) { throw std::exception("Error in conversion."); } std::vector<wchar_t> resultstring(widesize); int convresult = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize); if (convresult != widesize) { throw std::exception("La falla!"); } return std::wstring(&resultstring[0]); } //Unicode 转 Utf8 std::string Unicode2Utf8(const std::wstring& widestring) { int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL); if (utf8size == 0) { throw std::exception("Error in conversion."); } std::vector<char> resultstring(utf8size); int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL); if (convresult != utf8size) { throw std::exception("La falla!"); } return std::string(&resultstring[0]); } //ascii 转 Utf8 std::string ASCII2UTF_8(std::string& strAsciiCode) { std::string strRet(""); //先把 ascii 转为 unicode std::wstring wstr = Acsi2WideByte(strAsciiCode); //最后把 unicode 转为 utf8 strRet = Unicode2Utf8(wstr); return strRet; }