您的位置:首页 > 编程语言 > C语言/C++

C++ 标准库的 locale 类用法

2016-05-23 20:45 281 查看
原来一篇总结了下 C 标准库的 setlocale() 用法,这篇讲解的是 C++ 标准库中 locale 类的用法。

参考

cplusplus.com 上关于标准 C++ 中国际化支持的参考:

Localization library

Locale class

The C++ Standard Library(Nicolai M. Josuttis,侯捷译)第 14 章 Internationalization(国际化)

locale 类在头文件 <locale> 中声明,另外可能会用到 <stdexcept> 中的标准异常类,和<iostream> 中的流对象类。

GNU libstdc++ 中的 locale

参考

GNU libstdc++ 在线文档:The GNU C++ Library Documentation

上面的在线文档可以在 libstdc++ 的源码包中 doc/html 目录下找到。

Doxygen 生成的 libstdc++ API 参考:libstdc++ Source Documentation

locale 类参考:std::locale Class Reference

libstdc++ 在线手册国际化章节:Part VI. Localization

 

locale 对象的构造

构造函数有以下几个常用的重载形式:

locale()

构造一个 locale 对象,它和程序当前的全局 locale 属性相同。

explicit locale(const char* _Locname)

根据 locale 名构造一个 locale 对象,影响该对象的所有的 locale 类别(category),locale 名就是 C 标准库 setlocale() 中使用的名字,所以 libstdc++ 用的是 POSIX 标准的 locale 名,而 Windows CRT 实现的 locale 类,用的是 Windows 的 setlocale() 中特有的 locale 名字。

和 setlocale() 类似,如果要使用环境设置的 locale 时,可以将 locale 名字写为空字符串""

注意这个构造函数被声明为显式的(explicit),说明隐式的参数转换会被认为是语法错误,例如:locale loc = "zh_CN.UTF-8" 会在编译时报错。

当指定的 locale 名为 NULL,或是一个无效的名字,此构造函数会抛出 runtime_error 异常。

locale(const locale& _Loc, const locale& _Other, category _Cat)

locale(const locale& _Loc, const char* _Locname, category _Cat)


首先用 _Loc 拷贝构造一个 locale 对象作为基础 locale,然后从 _Other 或 _Locname 指定的 locale 中,抽取里面由 _Cat 指定的 category,替换掉基础 locale 中相应的 category。

category 是 int 的 typedef,在 locale 类内有一些预定义的 category 常量(类静态常量成员),分别对应于 C 标准库中预定义的 locale 类别常量:

locale::category 常量与 setlocale() 中的 category 参数对应关系
locale::category 常量setlocale() 中的 category
allLC_ALL
collateLC_COLLATE
ctypeLC_CTYPE
messagesLC_MESSAGES
monetaryLC_MONETARY
none表示所有 category 的空集(LC_ALL 的补集)
numericLC_NUMERIC
timeLC_TIME
category 类型被用作掩码类型,所以上面 locale 类别可以用 | 进行组合,比如:monetary | time

locale 类静态方法

const locale& classic()

得到一个表示 C locale 的 locale 对象。

locale global(const locale& _Loc)

设置程序全局 locale 为 _Loc,返回以前的全局 locale。

所谓全局 locale,就是流对象不用 imbue() 方法采用指定 locale 时的缺省 locale,相当于 C 标准库中 setlocale() 设定的 locale。程序初始化时,全局 locale 为 C locale。

例如,设定全局 locale 为环境设置的 locale:

1
// 相当于 setlocale(LC_ALL, "");
2
locale::global(locale(
""
));
locale 对象方法

string name() const

返回 locale 的名字,例如,打印当前全局 locale 名字可以为:

1
locale lc;
2
cout << 
"Current global locale is: "
 
<< lc.name() << endl;
注意:name() 返回的是 string 对象,而 wostream 的 << 操作在 string 上是无定义的,所以这个会出错:wcout << lc.name(),除非自己重载 wostream 的 << 操作。

locale 使用示例与问题

locale 类的作用和 setlocale() 相同:一是输出 wchar_t 字符时,根据活动 locale 将字符从 UCS 编码转换为 Native ANSI 编码,最后传递给终端、控制台设备;另一个作用是使用特定 locale 的某些 category 做操作(相应的函数、方法称为 locale 相关方法),比如:时间、货币文本的格式化,排序等等。

使用 wcout 流对象和 locale 对象,输出 wchar_t 型字符限制很大,以下是正确向终端输出中文的例子:

01
const
 
wchar_t
* strzh = L
"中文abc"
;
02
 
03
try
04
{
05
    
locale lc(
"zh_CN.UTF-8"
);
06
    
locale::global(lc);
07
    
// wcout.imbue(lc); // libstdc++ 的实现 imbue() 不起作用
08
    
wcout << L
"Zhong text is: "
 
<< strzh << endl;
09
}
10
catch
 
(runtime_error& e)
11
{
12
    
cerr << 
"Error: "
 
<< e.what() << endl;
13
    
cerr << 
"Type:"
 
<< 
typeid
(e).name() << endl;
14
}
上面程序的运行环境为:终端和 shell 都使用 zh_CN.UTF-8 编码,最后用重定向输出到文件的方法测试出:输出的文本为 UTF-8 编码。

限制来自于:无法混用 char 和 wchar_t 版的流对象,而很多对象只有返回 string 的方法(比如:locale::name()),导致必须使用 char 版的 cout、cerr 等,或者可以用 string::c_str() 方法。

GNU libstdc++ 中实现的 locale 类,在使用时注意以下问题(VC8 中没有下面前两个问题):

流对象的 imbue() 方法不起作用

使用 locale::global() 设定全局 locale 后,wostream 可以输出正确转换的 Native ANSI 编码字符。而使用 wostream::imbue() 后,流对象无法向终端传递正确转换的编码字符。在测试 wcout 时,使用重定向输出到文件的方法发现:wcout 在输出含有 ASCII 外的 wchar_t 时,无法正确转换到 Native ANSI 编码字符,而转换成字节 0x3F(对应 ASCII 字符 ?)。

char 和 wchar_t 版的流对象不能混用

和 glibc 实现的 C 标准库类似,在 libstdc++ 中混用 char 和 wchar_t 版的流对象,也会出现意外的错误,比如下面代码:

01
const
 
wchar_t
* strzh = L
"中文abc"
;
02
locale lc(
"zh_CN.UTF-8"
);
03
locale::global(lc);
04
 
05
// 这句会引发后面的 wcout 输出错误
06
cout << 
"abc"
 
<< endl;
07
 
08
// 即使刷缓冲区和清空流对象状态也无法解决问题
09
cout.flush();
10
cout.clear();
11
wcout.flush();
12
wcout.clear();
13
 
14
wcout << L
"Zhong text is: "
 
<< strzh << endl;
程序的输出如下:

# ./test_03.exe | hd
00000000  61 62 63 0a 5a 68 6f 6e  67 20 74 65 78 74 20 69  |abc.Zhong text i|
00000010  73 3a 20 2d 87 61 62 63  0a                       |s: -.abc.|
00000019

上面程序错误之处为:没有将 "中文" 的 UCS-4 LE 编码 2D 4E 00 00 87 65 00 00 转换为正确的 UTF-8 编码 E4 B8 AD E6 96 87,而是将每个 wchar_t 字符的最低字节(L'中' 为 2D,L'文' 为 87)传给了终端。

如果把 cout << "abc" << endl; 这句放到 wcout 之后,那么 wcout 是可以正常输出中文的,但 cout 就工作不正常了,根本就不向终端传递字符。

Cygwin 的 libstdc++ 中的 locale 实现

测试版本:g++ 4.3.4,bash 3.2.49,cygwin1.dll 1.7.1,libstdc++6 4.3.4-3

构造 locale 对象时,C.UTF-8,zh_CN.UTF-8,en_US.UTF-8 这些 locale 名字都无效,都会抛出runtime_error 异常,就连使用环境 locale 的空字符串参数 "" 都无效。只有一个 locale 名是有效的,就是最小的 C locale,即程序初始化时的默认locale。另外,Cygwin bash 启动后默认的 locale 为 C.UTF-8,可用查看 LANG 环境变量得知。

cout、wcout 与 printf()、wprintf() 的区别

我的感觉是 cout、wcout 没有 printf()、wprintf() 好用,至少 printf()、wprintf() 都可以输出 wchar_t 型字符,而 cout 默认无法输出 wchar_t 型字符(当然可以自己重载 <<),而把 wchar_t 当整数输出,把 wchar_t[] 和 wchar_t* 输出个首地址。

wostream 是可以以 char 型字符、字符串为 << 的参数的,但如果传入的 char 型字符在 ASCII 范围之外(在 0x00 ~ 0x7F 之外),wostream 是不会将正确的 Native ANSI 编码序列传递给终端、控制台设备的,导致终端中不会显示超出 ASCII 之外的字符。

string 没有在 wostream 的 << 上定义,但可以用 string::c_str() 的方法让其输出 char 型字符,前提是事先知道 string 对象中不含有超出 ASCII 之外的字符。

Windows CRT 中的 locale

参考

MSDN VC8 参考:

<locale>

locale Class

我在 VC8 下测试,和 GNU libstdc++ 的差异挺大,表现结果是:wcout.imbue(lc) 起作用,可以在控制台中输出含中文的 wchar_t 型字符,并且控制台得到的是根据 locale 转换的 Native ANSI 编码。Windows 的 locale 类实现不支持 UTF-8(代码页 65001),所以要用 GBK 的 locale,对应的 locale 名为 chs,长名字:Chinese_People's Republic of China.936。

用 VC8 的 locale::global(lc) 时有个奇怪的现象,它导致后面的 wcout 流输出到控制台完全没有显示,我将输出重定向到文件,发现结果正常,和用 imbue() 输出的相同,为 GBK 编码的中文。所以我认为,无法显示中文字符是 Windows 的控制台设备实现,以及 CRT 和控制台设备交互的问题,CRT 根据 locale 转换编码的工作是正常的。

另外,VC8 的 cout 和 wcout 的混用,以及 printf() 和 wprintf() 混用,都没有 GNU 实现的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: