c++ locale类

原来一篇总结了下 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

 

  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。

      所谓全局 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

参考

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

----------------------------------------------------------------------------------------------------------------------------------------------------------

用facet对象来设置输入输出流的格式

#include<iostream>
#include<boost\date_time\gregorian\gregorian.hpp>
#include<boost\date_time\posix_time\posix_time.hpp>
using namespace std;
using namespace boost::gregorian;
using namespace boost::posix_time;
/*格式化输出时间*/
/*date_time库提供了date_facet,time_facet对象来搭配IO流*/
int main() {
	/*把日期格式化为中文显示*/
	date d(2010, 3, 6);
	date_facet * df = new date_facet("%Y年%m月%d日");
	cout.imbue(locale(cout.getloc(), df));
	cout << d << endl;

	ptime p(d, time_duration(1, 10, 30, 1));
	time_facet * tf = new time_facet("%Y年%m月%d日%H点%M分%S%F秒");
	cout.imbue(locale(cout.getloc(), tf));
	cout << p << endl;
	return 0;
}


你可能感兴趣的:(c++ locale类)