文字显示2——C++字符串与字符编码

在看这篇文章之前,最好先看一下上一篇文章:文字显示1——字符与编码

 

C++字符串与字符编码

目前大部分编程语言字符串默认使用的是Unicode,比如Java,C#,JavaScript等等,大部分情况不需要考虑字符编码问题。在C/C++中char为一个字节,最初是用于存储ASCII字符,当然也可以存储中文字符,对于中文字符需要两个char才能存储一个字符。C++中的char也可以用来存储其他编码的字符,因为char可存储多种编码的字符,所以我们必须明确知道字符char的编码类型,否则显示时容易出现乱码。

C++中除了char以外,还有wchar_t,char16_t,char32_t,各种字符类型对于的字符如下:

char可以表示ASCII,GBK,ISO-8859,UTF-8等等。

wchar_t用于表示Unicode,基本包括所有的常用字符,也就是Unicode第0平面中的字符。

char16_t用于表示UTF-16,大部分情况下与wchar_t相同。

char32_t 用于表示UTF-32

有些地方会提到UCS编码,UCS和Unicode编码完全一致,可以认为两者为同一个编码。UCS-2和UFT-16类似,UCS-4和UTF-32等价。

 

C++中可以通过在字符串前面添加一定的标识(字面量)来规定字符的编码,如L”abc”,代表wchar_t类型的字符串。以下是所有字符类型的字符串:

char cs[] = "abc中文";
char u8cs[] = u8"abc中文";
wchar_t wcs[] = L"abc中文";
char16_t u16cs[] = u"abc中文";
char32_t u32cs[] = U"abc中文";

 

 

我们可以将上面代码中的字符串字节值打印出来,得到以下结果:

这个是在VS2015上运行的结果,完整程序代码地址

 

其中‘中’字的GBK编码是D6D0,Unicode编码是4E2D,UTF-8编码是E4B8AD

其中‘文’字的GBK编码是CEC4,Unicode编码是6587,UTF-8编码是E69687

 

从结果可以看到wchar_t,char16_t,char32_t中Unicode编码子节顺序是反的。这是因为VS2015中采用小端存储,即底位字节在前面。

 

当然也有大端存储的方式,对于采用UTF-16/UCS-2编码的文本文件,文件开头会有两个BOM字节如果是FFFE则文件是采用小端存储,如果是FEFF文件则是采用大段存储。如下图,显示了一个只有‘中’字的文本文件的字节。

BOM即byte order mark,有的UTF-8文件也会有BOM,但标准的UTF-8文件是不需要BOM的,UTF-8加BOM只是Windows平台的习惯。UTF-8的字节顺序是固定的,UTF-8的BOM本身并不是来标记大段小端,而是用来标记当前文本是否是UTF-8编码存储。UTF-8的BOM为3个字节EF BB BF,如果读取文件时,文件以EF BB BF3个字节开头,则代表当前文件采用的时UTF-8编码。如下图:

以上文件的字节截图是通过Nodepad++的HexEdit插件显示的,插件下载地址

 

控制台显示文字

这里讲的是windows控制台。

在命令行窗口中输入chcp命令回车,可以查看当前命令行窗口的使用的编码表示符号,如下图:

这里的936指的就是GBK编码,中文操作系统中一般默认都是这个编码,其他编码的表示符号可以在这个网站查看。

从以上网站中可以看到UTF-8的编码是65001,我们也可以通过命令修改当前控制台窗口的编码,如下图:

网站中列举的所有代码,windows并不是全部都支持,例如       utf-16的1200就不支持。

 

控制台默认使用的编码可通过控制面板设置,设置方法

 

C++控制显示的编码

C++中的四种字符char、wchar_t、char16_t、char32_t,只有char、wchar_t可以直接输出到控制台,char使用cout输出,wchart_t通过wcout输出。

当char *s= “abc中文”时,s字符串的编码为系统默认的编码,也就是系统设置的默认编码;当char *s= u8”abc中文“时,s字符串的编码为UTF-8。wchar_t的字符串只能表示UTF-16/UCS-2编码。

 

C++中可以通过函数locale::global设置当前程序要输出到控制台使用的编码,这个函数只接受一个locale对象作为参数。以下是三种locale对象

locale locC("C"); //默认编码 
locale locutf8 = locale("zh_cn.utf8");//UTF-8编码
locale locZH("zh"); //中文编码GBK

当设置了Local对象后系统在显示前会将字符串转换为控制台使用的编码。如下代码完整程序代码地址

	char *str = "abc中文";
	char *u8str = u8"abc中文";
	wchar_t *wstr = L"abc中文";

	locale locC("C"); //默认编码
	locale locutf8 = locale("zh_cn.utf8");
	locale locZH("zh"); 

	locale::global(locC);
	cout << "LocC locale:\n";
	cout << "str: " << str << endl;
	cout << "u8str: " << u8str << endl;
	wcout << L"wstr: " << wstr << endl;
	wcout.clear();
	cout.clear();
	cout << "\n" << endl;

	locale::global(locutf8); 
	cout << "UTF-8 locale:\n";
	cout << "str: " << str << endl;
	cout << "u8str: " << u8str << endl;
	wcout << L"wstr: " << wstr << endl;
	wcout.clear();
	cout.clear();
	cout << endl;

	locale::global(locZH);
	cout << "locZH locale:\n";
	cout << "str: " << str << endl;
	cout << "u8str: " << u8str << endl;
	wcout << L"wstr: " << wstr << endl;
	wcout.clear();
	cout.clear();
	cout << endl;

输出结果

从结果中可以看出,默认编码locC只能输char默认编码的字符,UTF-8的字符和wchar_t的字符显示不正常。UTF-8编码的情况下UTF-8和wchar_t的字符显示正常。

 

控制台乱码

控制台乱码一般会有两种情况,1是当前程序输出的字符编码与控制台不符合。2是控制台当前编码中不包含程序的字符,这种情况下字符会以“?”显示。如下:

程序代码
 

system("chcp 1252");//将编码设为iso 8895-1 西欧字符
cout << "abc中文" << endl;
cout << "abcÖÐÎÄ" << endl; //西欧字符,编译会有warning C4566

输出结果

以上程序是在中文操作系统上编译的,程序默认编码是GBK,以上代码文件在保存时需要选择UTF-8编码,否则无法保存。

虽然代码文件以UTF-8保存,但是程序默认编码还是GBK,因为程序中包含了西欧字符,字符无法转换到程序用的GBK,所以此时会出现warning C4566,说字符串无法转换到GBK编码。

 

因为程序输出的GBK编码的字符,以上结果中当前控制台编码时1252西欧字符,编码不匹配,所以第二行出现乱码,第三行的乱码是因为当前显示的编码页中不包含相应的字符,这个不包含是因为编译时字符无法转换到GBK,程序直接转换成了“?“。

 

字符串排序

如果仔细查看GBK和Unicode的编码,可以发现GB2312(GBK)中汉族编码顺序是根据拼音来的,而Unicode编码顺序是按照偏旁部首来的。如下图:

 

在C++中字符串比较是通过比较字符的编码大小,因为GB2312和Unicode编码顺序不一致,这会导致两者的字符串排序结果也不一致。例如:对于以下字符进行排序排序:
 

char *gbkstr[] = {"一", "二", "三"};
char *utf8str[] = { u8"一", u8"二", u8"三" };
wchar_t *char16str[] = { L"一", L"二", L"三" };

分别使用strcmp/wcscmp和转成string类用“<”比较,结果如下,完整代码地址

一、二、三的GBK/GB2312编码分别为D2BB、B6FE、C8FD;
一、二、三的Unicode编码分别为4E00、4E8C、4E09;

 

编码转换

UTF-8与UCS-2之间的转换,因为两者转换有一定规律,所以转换也比较简单,C++11原生支持两者之间的转换,但C++17中弃用了。

UTF-8、UTF-16、UTF32之间的转换可以使用ConvertUTF库(地址),这个库也很小,只有两个文件几百行代码。

 

Unicode与其他编码之间的转换

在win32平台中这种转换叫做宽字节与多字节之间的转换,可以通过系统函数WideCharToMultiByte/ MultiByteToWideChar完成。WideCharToMultiByte是Unicode的UTF-16与其他编码转换,MultiByteToWideChar则是相反的转换。

 

对于其他平台可以通过libiconv库完成,地址,当然这个库也可以支持windows上使用,官网上没有提供VS平台的编译,github上有对于的编译版本,地址

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值