| 目录 |

26 区域设置与国际化

本地化是指让你的应用能在不同区域设置(或国家/地区)中良好运行的过程。

你可能知道,并不是所有人都用同样的字符来表示小数点、千位分隔符……或者货币符号。

这些区域设置都有名字,你可以选择使用其中一个。例如,美国的区域设置可能会把数字写成这样:

100,000.00

而在巴西,同样的数字可能会把逗号和小数点对调过来写:

100.000,00

这样写代码,移植到其他国家或地区时就轻松多了!

呃,差不多吧。事实证明 C 只有一个内建的区域设置,而且功能有限。规范在这里确实留下了很多含糊之处,想做到完全可移植很难。

不过我们会尽力而为!

26.1 快速设置本地化

调用这些函数时,要包含 <locale.h>。

就声明一个特定的区域设置而言,可移植的做法基本上只有一种。如果你打算跟区域设置打任何交道,这大概就是你想做的:

setlocale(LC_ALL, "");  // 用本环境的区域设置来处理一切

你会希望调用它,好让程序以你当前的区域设置来初始化。

再深入一点,还有一件事可以做,而且仍然保持可移植:

setlocale(LC_ALL, "C");  // 使用默认的 C 区域设置

不过每次程序启动时都会默认调用它,所以没太大必要自己再调一遍。

在第二个字符串里,你可以指定系统所支持的任何区域设置。这完全取决于系统,所以会各不相同。在我的系统上,我可以指定这个:

setlocale(LC_ALL, "en_US.UTF-8");  // 不可移植!

这样能行。但它只能移植到那些对同一个区域设置使用完全相同名称的系统上,而你没法保证这一点。

给第二个实参传一个空字符串(""),就等于告诉 C:“嘿,你自己去弄清楚这台系统上当前的区域设置是什么吧,省得我来告诉你。”

26.2 获取货币区域设置

既然倒腾绿色纸片被说成是通往幸福的关键155,那我们就来聊聊货币区域设置吧。写可移植代码时,你总得知道表示钱该怎么写,对吧?不管是“$”、“€”、“¥”还是“£”。

怎么写这段代码才不至于把人逼疯?幸运的是,只要调用过 setlocale(LC_ALL, ""),你就可以用 localeconv() 查出这些信息:

struct lconv *x = localeconv();

这个函数返回指向一个静态分配的 struct lconv 的指针,里面装着你想要的那些丰富信息。

下面是 struct lconv 的各字段及其含义。

先约定几件事。_p_ 表示“正”,_n_ 表示“负”,而 int_ 表示“国际”。虽然其中很多是 char 或 char* 类型,但大多数(或者说它们指向的字符串)实际上都被当作整数来处理156。

在继续之前,请先知道:CHAR_MAX(来自 <limits.h>)是 char 所能容纳的最大值。下面很多 char 值用它来表示该值在给定的区域设置中不可用。

字段 说明
char *mon_decimal_point 货币的小数点字符,例如 "."。
char *mon_thousands_sep 货币的千位分隔符字符,例如 ","。
char *mon_grouping 货币的分组说明(见下文)。
char *positive_sign 货币的正号,例如 "+" 或 ""。
char *negative_sign 货币的负号,例如 "-"。
char *currency_symbol 货币符号,例如 "$"。
char frac_digits 打印货币金额时,小数点后要打印多少位,例如 2。
char p_cs_precedes 1 表示非负金额中 currency_symbol 位于数值之前,0 表示位于之后。
char n_cs_precedes 1 表示负金额中 currency_symbol 位于数值之前,0 表示位于之后。
char p_sep_by_space 决定非负金额中 currency symbol 与数值之间的分隔方式(见下文)。
char n_sep_by_space 决定负金额中 currency symbol 与数值之间的分隔方式(见下文)。
char p_sign_posn 决定非负数值中 positive_sign 的位置。
char n_sign_posn 决定负数值中 positive_sign 的位置。
char *int_curr_symbol 国际货币符号,例如 "USD "。
char int_frac_digits frac_digits 的国际值。
char int_p_cs_precedes p_cs_precedes 的国际值。
char int_n_cs_precedes n_cs_precedes 的国际值。
char int_p_sep_by_space p_sep_by_space 的国际值。
char int_n_sep_by_space n_sep_by_space 的国际值。
char int_p_sign_posn p_sign_posn 的国际值。
char int_n_sign_posn n_sign_posn 的国际值。

26.2.1 货币数字分组

好,这个有点绕。mon_grouping 是 char*,所以你可能会以为它是个字符串。但这回不是,它其实是一个 char 数组。它的结尾应当总是 0 或 CHAR_MAX。

这些值描述的是货币数字在小数点左侧(整数部分)如何分组。

例如,我们可能有:

  2   1   0
 --- --- ---
$100,000,000.00

这些都是三位一组。组 0(紧挨小数点左边)有 3 位数字。组 1(再往左一组)有 3 位,最后一组也是 3 位。

所以我们可以从右(小数点)往左,用一组表示各组大小的整数值来描述这些分组:

3 3 3

这样就能处理最大到 $100,000,000 的数值。

那如果要处理的数更大呢?我们可以一直往上加 3……

3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3

但那就太疯狂了。好在我们可以指定 0,表示沿用前一组的大小并重复:

3 0

意思就是每 3 位重复一次。这样就能应付 $100、$1,000、$10,000、$10,000,000、$100,000,000,000 等等。

你完全可以用这些值玩出各种花样,表示某些稀奇古怪的分组。

例如:

4 3 2 1 0

表示的是:

$1,0,0,0,0,00,000,0000.00

还可能出现的另一个值是 CHAR_MAX。它表示不再进行分组,并且可以出现在数组中的任何位置,包括第一个值。

3 2 CHAR_MAX

表示的是:

100000000,00,000.00

例如。

而只要数组第一个位置是 CHAR_MAX,就说明根本不做分组。

26.2.2 分隔符与符号位置

所有 sep_by_space 变体都处理货币符号周围的空格。合法取值有:

值 说明
0 货币符号与数值之间不加空格。
1 用空格把货币符号(以及正负号,如果有)与数值隔开。
2 若正负号符号与货币符号相邻,则用空格把两者隔开;否则用空格把正负号符号与数值隔开。

sign_posn 的各变体由下面这些值决定:

值 说明
0 在数值和货币符号外面加圆括号。
1 把正负号字符串放在货币符号和数值之前。
2 把正负号字符串放在货币符号和数值之后。
3 把正负号字符串直接放在货币符号之前。
4 把正负号字符串直接放在货币符号之后。

26.2.3 示例值

在我的系统上取这些值时,我看到的是这样(分组字符串按单个字节值显示):

mon_decimal_point  = "."
mon_thousands_sep  = ","
mon_grouping       = 3 3 0
positive_sign      = ""
negative_sign      = "-"
currency_symbol    = "$"
frac_digits        = 2
p_cs_precedes      = 1
n_cs_precedes      = 1
p_sep_by_space     = 0
n_sep_by_space     = 0
p_sign_posn        = 1
n_sign_posn        = 1
int_curr_symbol    = "USD "
int_frac_digits    = 2
int_p_cs_precedes  = 1
int_n_cs_precedes  = 1
int_p_sep_by_space = 1
int_n_sep_by_space = 1
int_p_sign_posn    = 1
int_n_sign_posn    = 1

26.3 本地化的细节

注意我们前面传给 setlocale() 的是宏 LC_ALL……这暗示可能还有别的变体,让你能更精确地指定要设置区域设置的哪些部分。

我们来看看这些可用的值:

宏 说明
LC_ALL 把下面所有项都设为给定的区域设置。
LC_COLLATE 控制 strcoll() 和 strxfrm() 函数的行为。
LC_CTYPE 控制字符处理函数的行为157。
LC_MONETARY 控制 localeconv() 返回的值。
LC_NUMERIC 控制 printf() 系列函数的小数点。
LC_TIME 控制 strftime() 和 wcsftime() 这些时间和日期打印函数的时间格式。

设置 LC_ALL 是很常见的做法,不过嘿,至少你还有得选。

另外我还得指出,LC_CTYPE 是其中的大头之一,因为它牵扯到宽字符——这可是个大麻烦,我们后面再聊。


| 目录 |