C++ 标准库的 locale 类用法

原来一篇总结了下 C 标准库的 setlocale() 用法,这篇讲解的是 C++ 标准库中 locale 类的用法。

参考

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

GNU libstdc++ 中的 locale

参考

 

  1. 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 all LC_ALL collate LC_COLLATE ctype LC_CTYPE messages LC_MESSAGES monetary LC_MONETARY none 表示所有 category 的空集(LC_ALL 的补集) numeric LC_NUMERIC time LC_TIME

      category 类型被用作掩码类型,所以上面 locale 类别可以用 | 进行组合,比如:monetary | time

  2. locale 类静态方法

    • const locale& classic()

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

    • locale global(const locale& _Loc)

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

      相关内容:C++中的locale设置 (即系统区域设置),C++中的locale设置,区域设置和代码页,C++之国际化(2) --- locale,C++ 标准库的 locale 类用法

      [C++中的locale设置参考:区域设置和代码页C/C++程序中,locale(即系统区域设置,即国家或地区设置)将决定程序所使用的当前语言编码、日期格式、数

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

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

      1 // 相当于 setlocale(LC_ALL, ""); 2 locale::global(locale(""));
  3. 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 的 << 操作。

  4. 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

参考

我在 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 实现的问题。

相关问题

评论

写下你的理解