C++/Locale
<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类模板的实例化:
- 内部字符类型与外部字符类型都是char。这种情况实际上没有做字符转码。
- 内部字符类型与外部字符类型分别是宽字符(wchar_t或char16_t或char32_t)与窄字符(char或char8_t)。窄字符既包括单字节字符集,也包括中日韩等的双字节字符集、多字节字符集。
从一个locale通过use_facet()函数获得codecvt facet,则该codecvt的外部字符类型就是该locale的ctype所指定的char的字符集类型。从而获得了在wchar_t与该locale使用的char的字符集之间来回转码的能力。编程时,如果需要在两种窄字符(如w:gbk与w:big5)之间转码,可以分别获得w:gbk与w: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没有内容。
- ↑ Langer, Angelika; Klaus Kreft. Standard C++ IOStreams and Locales. Addison Wesley Longman, Inc. [2000]. ISBN 9780321585585.