跳转到内容

C++/Locale

维基教科书,自由的教学读本
< C++

<locale>C++標準程式庫中的一個头文件,定义了C++标准中的区域设置(本地化策略集)的类及相关的模板类实例。[參 1]

C++ locale对象与C语言locale机制只有一点链接:如果一个具名的C++ locale被设为global locale,则C语言的global locale也被改动。

相关环境变量

[编辑]
  • LC_COLLATE 字符排序规则
  • LC_CTYPE 字符分类,字符编码,字符是单字节还是多字节
  • LC_MONETARY 货币格式
  • LC_NUMERIC 数字格式
  • LC_TIME 日期时间格式
  • LC_MESSAGES 提示信息的语言
  • LC_ALL 以上所有
  • LANG 除以上其它任何用途

C/C++程序的默认区域设置是C或POSIX,二者基本是一码事。

locale类

[编辑]

C++标准定义的locale类,作为本地化的策略集:

namespace std {
class locale {
public:
  // types:
    class facet;
    class id;
    typedef int category; //provides bitmask values to denote standard facet families.
    static const category // values assigned here are for exposition only
        none = 0,
        collate = LC_COLLATE, 
        ctype = LC_CTYPE,
        monetary = LC_MONETARY, 
        numeric = LC_NUMERIC,
        time = LC_TIME, 
        messages = LC_MESSAGES,
        all = collate | ctype | monetary | numeric | time | messages; // =LC_ALL
// construct/copy/destroy:
    locale() throw();                                                 //11 复制当前的全局C locale
    locale(const locale& other) throw();                              //22 Copy constructor
    explicit locale(const char* std_name);                            //33  Constructs a copy of the named C library locale。如Linux下中文系统的locale名字为"zh_CN.UTF-8",Windows下中文系统为"chs"
    locale(const locale& other, const char* _Locname, category _cat);      //44 复制other,并使用以_Locname命名的locale中由_cat指定的facet
    template <class Facet> locale(const locale& other, Facet* _f);     //55 从当前全局locale复制构造一个locale对象,并使用由_f指定的facet。编程者一般派生定义一个Facet,然后通过这个ctor使用它
    locale(const locale& _loc, const locale& _other, category _cat);         //66 复制other,并使用_other所指的locale中由_cat指定的facet
    ~locale() throw(); // non-virtual
    const locale& operator=(const locale& other) throw();             //77 Assignment operator
    template <class Facet> locale combine(const locale& other) const; 
          // 返回一个新的locale对象,复制了当前locale对象,使用other对应的locale中的Facet替换

// locale operations:
    basic_string<char> name() const; //获得当前locale的完整名称
    bool operator==(const locale& other) const;
    bool operator!=(const locale& other) const;
    template <class charT, class Traits, class Allocator>
        bool operator()(const basic_string<charT,Traits,Allocator>& s1, //使用当前locale的collate facet来比较两个字符串;用于std::sort等算法
                                   const basic_string<charT,Traits,Allocator>& s2) const;
// global locale objects:
    static locale global(const locale&); //把参数locale设置为全局locale
    static const locale& classic();//获得缺省的“C”locale
};
}

流在初始化时使用全局的locale。此后,可以通过流的imbue()成员函数改用别的locale。如果流已经初始化完毕,这时再修改全局locale,对这个流没有影响。

中文locale的名字:

  • linux下: zh_CN.utf8,zh_CN.GBK zh_CN.GB2312 zh_CN.GB18030
  • windows下:
    • 标准格式的locale:"Chinese (Simplified)_People's Republic of China.936"、Chinese_China.936、zh-CN.936 、 Chinese (Simplified)_China.936、zh-CN.utf8
    • 非标准格式的locale:chs、 Chinese-simplified 、 Chinese、 ZHI 、cp936、gbk、zh-Hans、zh、zh-CN、"chinese"、"chinese-simplified"、"chinese_CHN"
    • 不能使用的locale: Chinese.936,chs.936,Chinese.GB2312,chs.GB18030

facet

[编辑]

facet翻译为策略或“刻面”,原意指钻石切削加工后的一个小面。locale 是多种 facet对象的容器(实际上在locale类对象中用一个std::vector存储该locale类支持的所有facet的指针)。一个程序可以有多个locale对象,从而避免了C语言程序只能使用一个global locale的问题。多个locale对象可以共用同一个facet对象实例。因此facet对象内部使用w:引用计数。而locale::facet作为基类,主要定义了一些机制,以支持内部的引用计数器。如w:Visual C++的类库中locale::facet定义了_Incref、_Decref成员函数。此外locale::facet的拷贝构造和拷贝赋值函数声明为private或使用=delete,禁止外界拷贝facet,也禁止外界对facet赋值。从locale::facet派生出一大批模板类,各自管理某一种功能;按照类别(category)列出如下:

  • locale::ctype 类别
    • ctype // 字符分类和转换
    • codecvt // 字符编码转换
  • locale::collate 类别
    • collate // 字符比较
  • locale::message 类别
    • messages // 从信息目录中获得本地化信息
  • locale::numeric 类别
    • numpunct // 有关数字和布尔运算表达式中标点符号及格式信息
    • num_get // 代表数字或布尔值的字符串的解析
    • num_put // 代表数字或布尔值的格式化字符串的生成
  • locale::monetary 类别
    • moneypunct // 货币表达式中的标点符号及格式
    • money_get // 代表货币值的字符串的解析
    • money_put // 代表货币值的格式化字符串的生成
  • locale::time 类别
    • time_get // 代表日期和时间的字符串的解析
    • time_put // 代表日期和时间的格式化字符串的生成

因为C++的locale所有的成员函数均声明为const,所以一旦定义locale实例就不能修改。可以用自定义的facet去改造一个已有的locale产生新locale,然后植入(imbue)IO流中:

std::locale new_loc(old_loc, new NewFacet);
locale new1_loc("zh-CN", new_loc, locale::ctype); //使用new_loc的ctype

基类facet有一个public static member,型别为locale::id,名字为id。从facet类派生的各个模板类,使用这个基类facet的数据成员id,作为各个派生模板类实例化后的类标识。id初值为0,在运行时第一次访问类标识id时确定其非0值随后即保持不再改变。id用来“以某个facet型别为索引,搜索locale之内的一个facet”。例如:

locale loc_ger( "German_Germany" ); 
locale loc_chs("chs");
typedef  ctype<wchar_t> ctype_wchar; // 定义一个类型
const ctype_wchar &cw_ger =use_facet<ctype_wchar>(loc_ger); //一个对象
const ctype_wchar &cw_chs =use_facet<ctype_wchar>(loc_chs); //另一个对象
(cw_ger.id == cw_chs.id) // 为真
(use_facet<codecvt<wchar_t,char, mbstate_t>>(loc_ger).id == use_facet<codecvt<wchar_t,char, mbstate_t>>(loc_chs).id) // 为真

每个基于facet派生的模板类的实例,可以有多个不同的对象,如上例中的ctype<wchar_t>类型有表示德文字符集的对象cw_ger、表示简体中文字符集的对象cw_chs。 每个locale对象,都有一个内部存储facet指针的数组,数组索引值为facet的id,数组的元素为facet对象的地址,该facet对象的类标识id等于数组索引值。数组元素也可为空指针。在构造一个locale对象时,可以替换默认使用的facet对象或者增加一个facet对象,这是通过查找locale对象的内部的facet指针的数组,把facet对象的类标识id作为数组索引值,在相应数组元素位置上写入facet对象的地址。

每个基于facet派生的模板类,所有公开的成员函数均声明为const;所有的public成员函数均不是虚函数,而是将调用操作委托给一个protectedw:虚函数,后者的命名类似于对应的public函数,只不过在前面加上do_,如numpunct::truename会调用numpunct::do_truename()。因此,如想改变某个facet,应该在该facet为基类写一个派生类,并覆盖重写其对应的protected函数。

ctype

[编辑]

ctype是一个预定义的facet类模板,用于分类字符、大写小写转换,在内部字符与locale使用字符之间转换。例如,所有基于std::basic_istream<charT>的流输入使用当前流所使用(imbue)的locale的std::ctype<charT>过滤出输入字符中的空白符。标准输出流使用std::ctype<charT>::widen()来把窄字符(narrow-character)扩展为w:宽字符

标准库提供两个单独的(locale独立的)模板特化:

  • std::ctype<char>提供最小的"C" locale窄字符分类的等价实现。该特化版本使用查表方法实现字符分类。
  • std::ctype<wchar_t>提供本地字符集的w:宽字符分类。

此外,C++程序中创建的每个locale实现自己的(locale-specific)ctype特化版本。

模板类的成员类型char_type,其内容是模板参数 CharT。

类成员函数is用于给一个字符或者字符串中每个字符做分类,如字母数字alnum, 字母alpha, 控制字符cntrl, 数字digit, 字母数字或标点字符graph, 小写字母lower, 可打印字符print, 标点punct, 空白符space, 大写字符upper, 16进制数字xdigit。

类成员函数narrow与类成员widen用于宽字符、窄字符的转换,不适用于多字节编码的东亚字符。

类成员函数tolower与类成员toupper用于字符或字符串的大小写的转换。

类成员函数scan_is与类成员scan_not用于扫描字符串,返回第一个属于(或不属于)某类别的字符。

collate

[编辑]

collate是一个预定义的facet类模板,用于w:字符串的词典序比较、多字节表示单个字符的成组(grouping)。类方法compare比较一个locale下的两个字符串。类方法transform用于转换locale下的字符串到一个string对象,这个string对象也是用于词典序比较,这适用于一个字符串与很多个字符串比较的情形。类方法hash用于根据一个字符串计算出hash值。

codecvt

[编辑]

codecvt是一个预定义的facet类模板,用于字符串的编码转换:

templat <class InnerCoding, class ExternCoding, class State> class std::codecvt: public locale, public codecvt_base{};

codecvt类模板的第一个模板参数是内部字符类型;第二个模板参数是外部字符类型,如简体中文Windows操作系统默认使用的gbk(代码页936)|gbk(代码页936)Linux使用的utf-8;第三个模板参数是字符转换所需的中间状态的类型,如std::mbstate_t。C++标准允许两种codecvt类模板的实例化:

  1. 内部字符类型与外部字符类型都是char。这种情况实际上没有做字符转码。
  2. 内部字符类型与外部字符类型分别是宽字符(wchar_t或char16_t或char32_t)与窄字符(char或char8_t)。窄字符既包括单字节字符集,也包括中日韩等的双字节字符集、多字节字符集。

从一个locale通过use_facet()函数获得codecvt facet,则该codecvt的外部字符类型就是该locale的ctype所指定的char的字符集类型。从而获得了在wchar_t与该locale使用的char的字符集之间来回转码的能力。编程时,如果需要在两种窄字符(如w:gbkw:big5)之间转码,可以分别获得w:gbkw:big5的locale,然后使用两个locale各自的codecvt facet,以w:宽字符(wchar_t)为中介实现转码。

codecvt的类方法in()是从外部编码转换到内部编码;类方法out()是从内部编码转换到外部编码。 把宽字符串转换为东亚的几种多字节字符集编码的字符串,需要特别注意预留出足够的目标字符串的长度;另外,目标字符串的所有字节在转换前应用数值0做初始化,转换后目标字符串自然成了以0结尾,就无需用其他方法去判断目标字符串转换后的长度。codecvt的类方法length可用于判断在给定外部字符串、给定内部字符串的字符长度上限,返回多少个外部字符可以被转换。

basic_filebuf模板类内部使用了通过imbue()函数指定的locale的codecvt做字符编码转换,因此文件流fstream可以用于字符转码。而basic_stringbuf模板类不使用codecvt,因此不能用于字符转码。默认情况,文件流输出与输入的硬盘文件总是用窄字符(char)编码的。即Wide file I/O的两个对象wofstream与wifstream,分别默认把宽字符转码为窄字符后写入硬盘文件、从硬盘文件读取窄字符后转码为宽字符。

如果需要把宽字符通过wofstream写入硬盘文件,一种解决办法是写一个codecvt<wchar,char,mbstate_t>的派生类,内部实际上不做任何转码。把此codecvt派生类加入新的locale中,然后imbue()到wofstream中。

类模板codecvt_byname是codecvt的派生类:

template< class InternT, class ExternT, class State >
class codecvt_byname : public std::codecvt<InternT, ExternT, State>;

其中InternT应是宽字符类,ExternT是窄字符类。构造函数的参数指明所用locale的名称。如GBK在linux下的locale名是"zh_CN.GBK",而windows下是".936"或"gbk"或"chs",因此做跨平台的话仍然要给不同的系统做适配。

numpunct

[编辑]

处理数值I/O中相关的标点符号。成员方法包括:

  • decimal_point(); //返回一个字符用来表示小数点
  • thousands_sep(); //返回一个字符用来表示千分位符号
  • grouping(); //返回一个string表示数值的分组规则
  • truename(); //true的文本表示
  • falsename();//false的文本表示

C++标准规定,numpunct<char>numpunct<wchar_t>必须作为模板特化实现。

num_put

[编辑]

处理数值的格式化输出。put成员函数把值val写入指定的w:输出流迭代器

num_get

[编辑]

处理数值输入的解析。get成员函数解析w:输入流迭代器first和last之间的字符序列,得出一个对应类型的数值。确切格式由ios_base型的格式化标志确定。

time_get

[编辑]

处理时间日期输入的解析。成员方法包括:

  • dateorder date_order();//返回facet中年月日的次序
  • iter_type get_time();//解析输入流的字符串表示的时间,和strftime的%X格式一致
  • iter_type get_date();//解析输入流的字符串表示的日期,和strftime的%x格式一致
  • iter_type get_weekday() ;//解析输入流的字符串表示的星期的日期
  • iter_type get_month();//解析输入流的字符串表示的月份
  • iter_type get_year();//解析输入流的字符串表示的年份

time_put

[编辑]

处理时间日期的格式化输出。

字符串与流转换的类模板

[编辑]

wstring_convert

[编辑]

std::wstring_convert 是 C++11 提供的用于字符编码转换的工具类,能够方便地在宽字符类型(std::wstring)和多字节字符串类型(std::string)之间进行转换,尤其适用于 UTF-8 和宽字符之间的转换。然而,随着 C++17 的推出,std::wstring_convert 已被标记为废弃。C++17 推荐使用更现代的字符编码转换库,如 std::format、std::filesystem 等,或者通过第三方库(例如 iconv、Boost.Locale 或 UTF8-CPP)进行编码转换。

类模板std::wstring_convert在std::string与宽字符串std::basic_string<Elem>(其中Elem是宽字符类型)之间转换。使用的编码转换facet——Codecvt,必须由 std::wstring_convert所有,不能属于某个locale。构造函数需要传入一个codecvt对象的指针,如果没有传入,则默认使用new codecvt来创建。std::wstring_convert自行维护codecvt对象的生命周期,它的析构函数会调用delete操作符来删除该对象。这就限制了只能使用通过new操作符来创建的codecvt,而不能使用从std::locale中获取的codecvt。 标准预定义的codecvt有3个:

  • std::codecvt_utf8 注:在utf8和UCS2/UCS4之间转换
  • std::codecvt_utf16 注:在 UTF-16 和宽字符类型之间转换
  • std::codecvt_utf8_utf16 注:在utf8和utf16之间转换

获取其它字符编码的codecvt,需要使用std::codecvt_byname,这个类可以通过字符编码的名称来创建一个codecvt。由于历史原因,std::codecvt_byname的析构函数是protected的,std::wstring_convert不能对它调用delete,所以首先要自行定义一个类来继承std::codecvt_byname。

通常的定义类型:

template< class Codecvt,            //字符转换facet
          class Elem = wchar_t>     //宽字符类型
class wstring_convert;

主要成员函数:

  • from_bytes 从字节字符串到宽字符串
  • to_bytes 从宽字符串到字节字符串

示例:

#include "stdafx.h"
#include <iostream>
#include <string>
#include <codecvt>
 
using namespace std;
 
using WCHAR_GBK		= codecvt_byname<wchar_t, char, mbstate_t>;
using WCHAR_UTF8	= codecvt_utf8<wchar_t>;
 
// linux下为"zh_CN.GBK"或“zh_CN.gb2312”或"zh_CN.gb18030" Windows下为 .936或chs或gbk
#define GBK_NAME ".936"
 
int main()
{
	// 定义一个utf8字符串
	string result = u8"国人"; 
	// gbk与unicode之间的转换器
	wstring_convert<WCHAR_GBK>  cvtGBK(new WCHAR_GBK(GBK_NAME));
	// utf8与unicode之间的转换器
	wstring_convert<WCHAR_UTF8> cvtUTF8;
	// 从utf8转换为unicode
	wstring ustr = cvtUTF8.from_bytes(result);
	// 从unicode转换为gbk
	string str = cvtGBK.to_bytes(ustr);
 
	cout << str << endl;
	getchar();
    return 0;
}

wbuffer_convert

[编辑]

C++11引入,从C++17过时。在宽字符流缓冲区字符与字节流缓冲区之间转换

#include <iostream>
#include <sstream>
#include <locale>
#include <codecvt>
int main()
{
    // wrap a UTF-8 string stream in a UCS4 wbuffer_convert
    std::stringbuf utf8buf(u8"z\u00df\u6c34\U0001f34c");  // or u8"zß水🍌"
                       // or "\x7a\xc3\x9f\xe6\xb0\xb4\xf0\x9f\x8d\x8c";
    std::wbuffer_convert<std::codecvt_utf8<wchar_t>> conv_in(&utf8buf);//构造一个wbuffer_convert,指定codecvt,指定字节流utf8buf
    std::wistream ucsbuf(&conv_in);
    std::cout << "Reading from a UTF-8 stringbuf via wbuffer_convert:\n";
    for(wchar_t c; ucsbuf.get(c); )
        std::cout << std::hex << std::showbase << c << '\n';
}

模板函数has_facet

[编辑]

测试一个locale是否实现了指定facet

模板函数use_facet

[编辑]

访问一个locale对象中的某个facet:

namespace std {
    template <class facet>
       facet const&  use_facet(locale const& loc);
};

这个模板函数从一个locale类中取得指定的facet类的只读引用,然后就可以调用该facet类提供的工作函数。该模板的类型参数可以区分属于一个locale类的不同的facet类。

字符分类与处理的函数

[编辑]
  • isalnum
  • isalpha
  • iscntrl
  • isdigit
  • isgraph 字母数字或标点字符
  • islower
  • isprint
  • ispunct
  • isspace
  • isupper
  • isxdigit
  • tolower
  • toupper

参考文献

[编辑]

页面Template:ReflistH/styles.css没有内容。

  1. Langer, Angelika; Klaus Kreft. Standard C++ IOStreams and Locales. Addison Wesley Longman, Inc. [2000]. ISBN 9780321585585.