跳至內容

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.