C++基础(十八)区域设置、locale、中文乱码、中文不输出

在C++程序中,尤其是新手,在涉及到打印/输出字符串的时候,经常莫名其妙遇到乱码或者不输出的问题。

如果是在windows系统上编程,更是如此。不管是CFile还是CStdioFile,莫名其妙就输出不了或者输出的是乱码。

如果只是想要解决输出问题,很简单,很多文章直接让setlocale(LC_ALL, "")、setlocale(LC_CTYPE, "")、setlocale(LC_ALL, "chs")、setlocale(LC_ALL, "zh_CN")等等,反正就是setlocale,最后发现真的可以。可是真的了解具体含义吗?如果软件是跨区域/国家的,一个setlocale可能确实解决了你的这个乱码问题,但是也许会给别人带来灾难。

首先,setlocale属于C语言的函数,在C++中尽量少用,C++针对国际化有专门的解决方案,且更加灵活。

C++的国际化是通过std名称空间下的locale类实现的,其对外接口比较简单,如下:

所有输入输出流,都有其对应的locale,可以通过getloc获取,再调用locale的成员函数name,就可以获取locale的名字。请看代码(vs2019):

#include <iostream>

int main()
{
	using namespace std;

	cout << "cout:" << cout.getloc().name() << "\n";
	cout << "wcout:" << wcout.getloc().name() << "\n";

	system("pause");
	return 0;
}

执行结果如下:

可以看到,不管是cout,还是wcout,在vs2019编译器里面,其默认的locale都是"C",即与C语言的设置保持一致。但是,上一篇文章中有介绍wcout输出中文的时候默认是无法输出的,但是cout可以。

不知道是wcout的支持的问题,还是C语言在处理wchar_t的中文时的问题,对C语言了解得不多,不敢乱下结论。

1、locale

看locale的构造函数介绍:

形式有很多种,其中的默认构造函数,取得是当前的全局locale,我们可以通过两行代码获取当前的locale设定:

#include <iostream>

int main()
{
	using namespace std;

	locale lc_defalut;
	cout << lc_defalut.name() << "\n";

	system("pause");
	return 0;
}

执行结果:

C会根据当前的系统区域设定来判断,如果操作系统是中文的,那么确实可以正确显式中文;但如果不是,直接设置成默认的,肯定会有问题;另外,用系统默认的locale设置到wcout上,也无法正确输出中文。

其他构造函数,还有一个接受"char*"/"string"参数的构造函数,这两个是用得最多的。参数的形式如下:language[_area[.code]]

_area和.code都是可选的,简单列几个(包含世界所有的国家和地区的来源没找到,此处参考的是另一篇文章:https://blog.csdn.net/templar1000/article/details/20369271):

c           Default: ANSI-C conventions (English, 7 bit)
en_US       English in the United States
en_AU       English in Australia
en_CA       English in Canada
fr_FR       French in France
fr_CH       French in Switzerland
fr_CA       French in Canada
ja_JP.jis   Japanese in Japan with Japanese Industrial Standard (JIT) encoding
ja_JP.sjis  Japanese in Japan with Shift JIS encoding
ja_JP.ujis  Japanese in Japan with UNIXized JIS encoding
ja_JP.EUC   Japanese in Japan with Extended UNIX Code encoding
zh_CN       Chinese in China
zh_TW       Chinese in Taiwan
lt_LN.bit7  ISO Latin, 7 bit
lt_LN.bit8  ISO Latin, 8 bit

 

可以看出来,对于中文,有两种,我们用的一般是"zh_CN",所以想要定一个支持中文的locale,别用系统默认的,而应该直接指定。

如果传递的字符串为空,则返回的也是环境变量中默认的locale;如果传的字符串非法,即不是约定的,则会抛出异常:

总结一下,对于locale来说,有两种方式获取当前默认的区域环境:

(1)用默认的构造函数构造一个lcoale对象;

(2)传入空字符串构造一个locale("")对象。

2、设置locale

C++的locale设置很灵活,可以设置全局的,也可以设置到具体的流对象上。这里建议设置到具体的流对象上,除非你有必须设置到全局的充分理由。

(1)设置全局的

如果有充分的理由需要设置全局的locale,在不使用C语言的setlocle函数的前提下,如何设置呢?

locale类有一个静态的成员函数:globale,如下:

可以看到,调用global,传入一个locale,就可以设置想要的区域环境;但是需要注意,如果参数loc有name,还会设置C的global locale

因此,在调用global前,一定要记住当前的全局locale是什么,操作结束后再重新设置回来。

看global的返回值,返回的是之前的全局locale,这就简单了,示例代码如下:

#include <iostream>

void PrintWchar()
{
	std::wcout << L"output English\n";
	std::wcout << L"输出一句中文\n";
	std::cout << "now-wcout: " << std::wcout.getloc().name() << std::endl;

	std::locale lc_global;
	std::cout << "now-global: " << lc_global.name() << std::endl;
}

int main()
{
	using namespace std;
	
	std::cout << "before: "<< std::wcout.getloc().name() << std::endl;
	locale lc_old = locale::global(locale("zh_cn"));  //设置全局locale为中文,并记录返回值,即原来的全局locale
	PrintWchar();
	locale::global(lc_old);  //设回原来的全局locale
	std::cout << "after: " << std::wcout.getloc().name() << std::endl;

	system("pause");
	return 0;
}

执行结果非常有意思,如下:

程序其实非常简单:

先打印wcout里的locale,发现是"C";

调用locale::global函数,传入一个参数为"zh_cn"的locale的临时变量,并定义lc_old接收返回值;

调用PrintWchar函数,函数内部打印一句英文,一句中文,另外,还打印了此刻wcout流中locale值以及此时全局的locale值;结果非常有意思,wcout依然是C,而全局的"zh_cn";

再次调用locale::global,传入参数为刚才接收的返回值lc_old;

打印此时的wcout的locale值,依然是C。

程序从始至终,都没有改变wcout的locale值,但是在设置了全局的locale为"zh_cn"后,wcout就能正常打印中文。

具体的原理,我也不太清楚,似乎很有意思,这里不强行解释。

再次强调一下,设置全局的locale后,一定要记得重新设置回来!

(2)设置到具体的流对象上

标准输出

设置到具体的流对象上,都是调用流的"imbue"函数,接受一个locale的const引用。如下:

#include <iostream>

int main()
{
	using namespace std;
	
	locale lc_zh("zh_CN");
	wcout.imbue(lc_zh);
	wcout << L"中文" << endl;
	cout << "wcout:" << wcout.getloc().name() << endl;
	cout << "cout:" << cout.getloc().name() << endl;

	system("pause");
	return 0;
}

执行结果如下:

可以看到,imbue的设置,仅仅影响到了wcout,对cout没有影响。

试了一下,这里locale的参数,可以传:"zh_cn"、"chs"、"chinese"中的任何一个,且不区分大小写

文件流

如果想要操作文件,用imbue设置到具体的文件流对象上更合适。如下:

#include <iostream>
#include <fstream>

void WriteFile(std::wfstream& fOut)
{
	if (!fOut.is_open())
		return;
	fOut << L"A line English\n";
	fOut << L"窗前明月光\n";
	fOut << L"疑似地上霜\n";
	fOut << L"举头望明月\n";
	fOut << L"低头思故乡";
	fOut.close();
}

int main()
{
	using namespace std;

	wfstream wf_not;
	wf_not.open(R"(L:\locale_not.txt)", ios_base::out);
	WriteFile(wf_not);
	
	wfstream wf_yes;
	wf_yes.imbue(locale("zh_cn"));
	wf_yes.open(R"(L:\locale_yes.txt)", ios_base::out);
	WriteFile(wf_yes);

	system("pause");
	return 0;
}

执行完程序后,会在L盘下生成连个文件:

打开文件,会发现locale_not里的中文没有输出:

原因很简单,wf_not没有设置locale,wf_yes设置了。

可以看到,这里不需要记录全局的lcoale再重新设置回去。因为这里locale的设置是针对流对象而言的,仅影响这一个对象。

(3)涉及中文输出(包括文件),尽量设置locale

尽管我们开发的软件大部分情况都用于中国,但不能百分百确定一定会是中文操作系统,换句话说,不能百分百确定用户操作系统中的默认环境是中文的;或者,其他人在某个地方把全局的locale设置成了英文等其他环境,忘了设回系统默认的。

如果不是,会出现什么情况呢?请看下面代码:

#include <iostream>

void OtherOp()
{
	std::locale::global(std::locale("en_US"));
}

int main()
{
	using namespace std;

	OtherOp();
	cout << "C++ nice! \n";
	cout << "C++ 很棒!\n";

	system("pause");
	return 0;
}

运行结果如下:

如此结果的原因很简单,OtherOp里面把全局的locale设置成了英文环境,但没有设回原来的,导致cout无法识别中文。如果是和别人一起合作项目,而且该项目涉及多个国家和区域,这种情况极有可能发生。

所以,在涉及文件的读写时,尽量设置一下流的locale,以防其他人在别的地方修改了全局locale忘记设回之前的状态。

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值