一、字符集
常用的字符集 utf-8 是 unicode 标准的一种实现方案。utf-8 支持带 BOM 的版本。其为 byte order mark 的缩写,用以标识它所使用的是 big-endian 或 little-endian。
二、Locale 概念
解决国际化问题,通常是通过所谓的 locale(地域),它被用来封装国家以及文化之间的转换行为。C社区正是采用了这种做法。在国际化情境下,locale 就是一个 ”参数和函数的集合“,用以进行国家和文化之间的转换。根据 X/Open(Unix标准) 规约,环境变量 LANG 用来确定当前使用的 locale。根据这个 locale 于是定出不同的浮点数、日期、货币格式。 对于 windows 系统,可以借助 PowerShell 中的 Get-WinSystemLocale 命令来获取本地化信息。
1、locale 的定义
想要定义一个 locale,需要采用以下 string 格式: language[_area[.code]][@modifier] 其中:
- language 表示语言,例如英语或德语。它通常是包含两个小写字符如 en 或 de 的 string。
- area 表示使用该语言的地域、国家或文化;它通常是“包含两个大写字符如US或DE”的string。
- code 代表字符编码方案,其重要性在亚洲特别明显。因为在这里相同的字符集常常有不同的字符编码方案,如utf8、ISO-8859-1、eucJP。
- modifier 允许某些平台指出额外修饰,例如@euro用于欧元符号,@phone用于对电话号码排序。
2、使用 locale
对真正的国际化而言,仅仅翻译“文字所带的信息”往往不够。各种不同的数值、货币,日期等规格也都必须遵守。另外,用来操作字母的函数应该根据字符进行编码,以确保正确处理特定语言中所有身为字母的字符。
根据 POSIX 和 X/Open 标准,C程序可使用 setlocale() 设定一个locale。改变 locale 会对 issupper() 和 tosupper () 之类的字符分类操作函数及 printf() 之类的 I/O 函数产生影响。然而 C 解法毕竟有诸多限制。由于 locale 是全局属性,所以同时使用一个以上的 locale (例如按英文规则读取浮点数,按德文规则写出),虽非不可能,却很麻烦。此外 C 中的 locale 无法扩展,只能提供由实现选取的设施;如果某种国家规定或范式未被实现,那么我们就无法使用这种解决方案。
在C++标准库中,利用面向对象做法解决了上述所有问题。首先,与 locale 相关的细节被封装在类型为 locale 的对象中。仅仅如此,同一时间使用多个 locale 便成为可能。依赖于 locale 的各种操作都将通过具体对象得以展开。如:
#include <locale>
#include <iostream>
using namespace std;
int main()
{
cin.imbue(locale::classic());
cout.imbue(locale("deu_deu.1252"));
cout << "input floating-point values" << endl;
double value;
while (cin >> value)
{
cout << value << endl;
}
}
我们将英语中的浮点数转化为了德语的浮点数(, 和 . 是相反的)。
(1)locale 的默认构造
classic 函数用于获取典型的C风格的locale。我们可以使用 std::locale(“C”) 获得相同的 locale 对象。“C” 是个特殊名称,是所有C++实现唯一必须支持的名称。
我们可以选择使用 locale 的默认构造函数初始化一个 locale:
cin.imbue(locale());
cin.imbue(locale(""));
第一种方式将使用程序当前的全局 locale,第二种方式使用程序环境的本地locale。
(2)global 和 imbue
global 函数用于设置一个全局 locale 对象。需要注意的是其设置的全局对象所具备的属性只对使用默认构造函数所产生的 locale 对象生效,对于存在于既有流对象中的 locale 对象并不生效。
#include <locale>
#include <iostream>
using namespace std;
int main()
{
double value = 2.3456;
cout << "origin :" << value << endl;
cout.imbue(locale());
cout << "default :" << value << endl;
cout.imbue(locale(""));
cout << "default with empty name :" << value << endl;
locale::global(std::locale("deu_deu.1252"));
cout << "default after global :" << value << endl;
cout.imbue(locale());
cout << "default after global and imbue :" << value << endl;
cout << "default with empty name after global :" << value << endl;
cout.imbue(locale(""));
cout << "default with empty name after global and imbue :" << value << endl;
}
需要注意使用 locale("") 并不同于 locale(),前者使用的是本地环境的默认语言,而非程序的全局 locale。
3、Facet
国家约定俗成的具体项目被划分为数个不同的方面,分别由相应的对象处理。负责处理 “国际化议题中的某一特定方面” 的对象,我们称之为 facet。locale 对象就是扮演 facet 的容器。要访问 locale 的某个方面,可拿相应的 facet 类型作为索引。将facet 当做 template 实参,明确传给 template 函数 use_facet(),便可取用特定的 facet。
三、细究locale
1、locale 的构造
复制对于 locale 而言是廉价操作,因为其底层是通过引用计数方式进行拷贝和内存管理,然而,建立一个有所改变的 locale 成本就比较高,因为需要调整每一个 facet 的引用计数值。
2、facet 的访问
C++ locale 是一个 不可变的 facet容器。
locale 中对象的访问方式很有趣。locale 内含的 facet,是以 facet 类型为索引来访问。然后,不同的 facet 支持的接口是不一样的,所以我们希望获取 facet 的函数(use_facet)返回的是引用类型的。
3、其余操作符
值得注意的是,locale 中提供了字符串比较操作符,可以用于判断在当前国际化库中两个字符串的大小关系:
#include <locale>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
cout << wcout.getloc().name() << endl;
wcout.imbue(locale(""));
vector<wstring> vec = { L"中文", L"英文", L"俄文" };
sort(vec.begin(), vec.end(), locale(""));
copy(vec.begin(), vec.end(), ostream_iterator<wstring, wchar_t>(wcout, L" "));
wcout << endl;
}
需要注意,wcout 默认使用的 locale 是标准C 中的,而我们需要打印中文则应使用本地环境的国际化设置。
四、细究 Facet
1、建立 facet 的原则和条件
任何一个 class F 只要满足以下两个条件,就可以成为 facet:
- F 以 public 形式继承 std::locale::facet class。后者主要定义了一些机制供 locale 对象的内部引用计数器使用,此外还去除了 copy 构造函数和 assignment 操作符,禁止从外部对 facet 赋值和复制。
- F必须拥有一个名为 id 的 public static 成员,其类型为 locale::id。此成员用来“以某个facet类型为索引”查找 locale 内的 facet。
标准 facet 不仅遵循上述要求,还遵循若干特殊实现方针(非必要)。这些原则包括:
- 所有成员均声明为 const。因为 use_facet() 会返回一个 const 引用。
- 所有 public 函数都不应该是 virtual 函数,而是将任务委派给某个 protected virtual 函数执行。后者的名称在 public 函数前加上了 do_。这是为了防止名称遮掩的问题。
2、Facet分类及作用
其中,后面为 punct 的表示该类别所使用的符号,put 和 get 分别为格式化输入和输出类别。
3、数值格式化
#include <locale>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
locale locC;
locale locG("deu_deu.1252");
cout << "decimal_point in C : " << use_facet<numpunct<char>>(locC).decimal_point() << endl;
cout << "thousands_sep in C : " << use_facet<numpunct<char>>(locC).thousands_sep() << endl;
cout << "grouping in C : " << use_facet<numpunct<char>>(locC).grouping() << endl;
cout << "truename in C : " << use_facet<numpunct<char>>(locC).truename() << endl;
cout << "falsename in C : " << use_facet<numpunct<char>>(locC).falsename() << endl << endl;
cout << "decimal_point in German : " << use_facet<numpunct<char>>(locG).decimal_point() << endl;
cout << "thousands_sep in German : " << use_facet<numpunct<char>>(locG).thousands_sep() << endl;
cout << "grouping in German : " << use_facet<numpunct<char>>(locG).grouping() << endl;
cout << "truename in German : " << use_facet<numpunct<char>>(locG).truename() << endl;
cout << "falsename in German : " << use_facet<numpunct<char>>(locG).falsename() << endl << endl;
cout.precision(10);
cout.imbue(locC);
use_facet<num_put<char>>(locC).put(cout, cout, ' ', 1234.5678);
cout << endl << endl;
cout.imbue(locG);
use_facet<num_put<char>>(locG).put(cout, cout, ' ', 1234.5678);
cout << endl << endl;
}
需要注意的是,这里的输出结果仍受 cout 中的格式化标志影响。
4、货币和时间格式化
货币、时间格式化的使用方式同上。在C++11中,提供了一些货币和时间操作符。我们可以使用这些操作符直接与流交互,实现对货币值得读取或写入。
5、字符的分类和转换
ctype 中提供了一些方便的分类和转换函数: 我们可以通过自定义 do_is 函数实现对指定格式代码的特化处理(代码来自cpp参考手册):
#include <iostream>
#include <locale>
#include <sstream>
struct csv_whitespace : std::ctype<wchar_t>
{
bool do_is(mask m, char_type c) const
{
if ((m & space) && c == L' ') {
return false;
}
if ((m & space) && c == L',') {
return true;
}
return ctype::do_is(m, c);
}
};
int main()
{
std::wstring in = L"Column 1,Column 2,Column 3\n123,456,789";
std::wstring token;
std::wcout << "default locale:\n";
std::wistringstream s1(in);
while (s1 >> token) {
std::wcout << " " << token << '\n';
}
std::wcout << "locale with modified ctype:\n";
std::wistringstream s2(in);
csv_whitespace* my_ws = new csv_whitespace;
s2.imbue(std::locale(s2.getloc(), my_ws));
while (s2 >> token) {
std::wcout << " " << token << '\n';
}
}
6、字符编码转换
这部分功能在C++17中大部分被弃用
|