C99标准第7.11节描述了<locale.h>
标题及其内容。特别是,它定义了struct lconv
并说:
[...]在“C”语言环境中,成员应具有 注释中指定的值。
char *decimal_point; // "." char *thousands_sep; // "" char *grouping; // "" char *mon_decimal_point; // "" char *mon_thousands_sep; // "" char *mon_grouping; // "" char *positive_sign; // "" char *negative_sign; // "" char *currency_symbol; // "" char frac_digits; // CHAR_MAX char p_cs_precedes; // CHAR_MAX char n_cs_precedes; // CHAR_MAX char p_sep_by_space; // CHAR_MAX char n_sep_by_space; // CHAR_MAX char p_sign_posn; // CHAR_MAX char n_sign_posn; // CHAR_MAX char *int_curr_symbol; // "" char int_frac_digits; // CHAR_MAX char int_p_cs_precedes; // CHAR_MAX char int_n_cs_precedes; // CHAR_MAX char int_p_sep_by_space; // CHAR_MAX char int_n_sep_by_space; // CHAR_MAX char int_p_sign_posn; // CHAR_MAX char int_n_sign_posn; // CHAR_MAX
第7.11.2.1节“localeconv()函数”继续说:
类型为
char *
的结构成员是指向字符串的指针,其中任何一个都是 (decimal_point
除外)可以指向""
,表示该值不可用 当前区域设置或长度为零。 [...] char类型的成员是 非负数,其中任何一个都可以CHAR_MAX
表示该值不是 在当前区域设置中可用。
继续讨论每个成员。您可以看到4组3个成员,一个代表组为p_cs_precedes
,p_sep_by_space
和p_sign_posn
。
char p_cs_precedes
如果currency_symbol分别位于或之前,则设置为1或0 继承非负的本地格式货币数量的值。
char p_sep_by_space
设置为一个值,表示currency_symbol的分隔 符号字符串,以及非负局部格式货币的值 量。
char p_sign_posn
设置为表示a的positive_sign定位的值 非负的本地格式货币数量。
给出了p_sign_posn
的解释细节;它们对这个问题不重要。
该标准还提供了一些如何解释这些类型的例子。
如果您发现原始C99标准(ISO / IEC 9899:1999),请注意TC1(国际标准ISO / IEC 9899:1999技术勘误1,2001-09-01发布)和TC2(国际标准ISO / IEC 9899:1999技术勘误2,发布于2004-11-15)对§7.11.2.1进行了修改(但TC3没有)。但是,这些变化既不会解决也不会影响我要问的问题的答案。
我的前两个问题是关于四个三元组(cs_precedes,sep_by_space和sign_posn),以及关于什么构成有效语言环境的其他更一般的问题:
如果是明智的,应该如何解释这些组合?
定义了两个组合(所有值设置为CHAR_MAX
,如"C"
区域设置,以及所有有效设置的值);这是我很好奇的其他6种混合设置。
如果定义了三元组但是没有相关的货币符号,是否正确形成了语言环境?
我倾向于回答:
frac_digits
大于零。然后,实现可能会强制执行这些规则,但可以想象另一个实现会以不同的方式解释规则并得出不同的结论。
你说什么?
答案 0 :(得分:0)
据我所知,标准C和POSIX都没有规定struct lconv
中有效和无效的规则。一个可能的原因是标准C或POSIX中的任何函数都不需要struct lconv
作为参数;只有localeconv()
函数返回结构:
struct lconv *localeconv(void);
因此,由于实现名义上是struct lconv
值的唯一来源,因此无论实现方式如何,在实现方面都必须正常。总而言之,它有点像一个尚未出生的特征;它提供了没有直接使用的功能。但是,在幕后,支持部分信息(对于初学者来说,请考虑printf()
和scanf()
等)。任何标准C功能都不使用货币信息。他们(<locale.h>
标题和localeconv()
和setlocale()
函数)被委员会添加到C89,部分是为了确保可以有一个单独的ISO标准C与C的ANSI标准相同。
普劳格的书&#39; The Standard C Library&#39; (它实现了C89标准库)提供了一个名为_Fmtval()
的函数,可以使用当前语言环境的约定来格式化国际货币,国家(本地)货币和数字,但再一次,使用的结构是由实现定义,不由用户提供。
POSIX确实提供了一对函数strfmon()
and strfmon_l()
,后者将locale_t
作为参数之一。
ssize_t strfmon(char *restrict s, size_t maxsize, const char *restrict format, ...);
ssize_t strfmon_l(char *restrict s, size_t maxsize, locale_t locale,
const char *restrict format, ...);
然而,POSIX对类型locale_t
的内容一无所知,尽管它确实提供了以下函数来以有限的方式操作它们:
locale_t duplocale(locale_t)
void freelocale(locale_t)
locale_t newlocale(int, const char *, locale_t)
locale_t uselocale (locale_t)
但是,这些方法提供了一种操作区域设置的最小和不干涉方法,并且绝对不会详细说明struct lconv
中可能接受或不接受的内容。还有nl_langinfo()
函数:
#include <langinfo.h>
char *nl_langinfo(nl_item item);
char *nl_langinfo_l(nl_item item, locale_t locale);
这些允许您使用诸如ABDAY_1
之类的名称一次找出一个项目的部分值,以找出第1天的缩写名称,即&#39;太阳&#39;在讲英语的地方。 <langinfo.h>
中有大约55个这样的名字。有趣的是,这个集合并不完整;你无法通过这种方式找到国际货币符号。
鉴于两个主要相关标准对struct lconv
内容的限制没有任何说明,我们只能尝试确定实际约束。
(除了:鉴于C99标准中国家和国际格式信息的对称性,在某些方面遗憾的是,结构未被用于编码信息;它使得繁琐的代码选择正确的部分和部分进入泛型函数。一些字段(cs_precedes
,sep_by_space
)也可能是布尔值,但<stdbool.h>
不是C89。)
重述问题:
我的前两个问题是关于四个三元组(
cs_precedes
,sep_by_space
和sign_posn
),以及关于什么构成有效区域设置的其他更一般的问题:
- 拥有一个或两个具有CHAR_MAX名称的三元组成员是否可行或明智,而其他成员的值在正常范围内(0-1,0-1,0-4)?
- 如果它是明智的,那么组合应该如何解释?
- 如果定义了三元组但是相关的货币符号不是,则是否正确形成了区域设置?
- 如果未定义货币小数点但定义了货币符号,是否正确形成了区域设置。
- 如果符号位置不为0(表示值是括号括起来的话),如果设置了货币符号但是正号和负号都是空的,是否正确形成了一个语言环境?
- 当负三联不是时,是否有必要定义正三联?
醇>
原始的,大纲答案是:
- 否;要么将三元组的全部成员都设置为CHAR_MAX。
- 根据(1)的答案不适用。
- 没有
- 否(但是旧的意大利货币(里拉)存在边界情况,其中没有分数,因此不需要小数点;这可以通过仅在{{{{ 1}}或
frac_digits
大于零。- 没有
- 没有
醇>
花了一些时间实现代码来处理这样的格式化,在我看来,我的原始答案基本上是正确的。
我最终实现的用于验证语言环境的代码是:
int_frac_digits
该标准规定/* Locale validation */
#define VALUE_IN_RANGE(v, mn, mx) ((v) >= (mn) && (v) <= (mx))
#define ASSERT(condition) do { assert(condition); \
if (!(condition)) \
return false; \
} while (0)
#define ASSERT_RANGE(v, mn, mx) ASSERT(VALUE_IN_RANGE(v, mn, mx))
static bool check_decpt_thous_group(bool decpt_is_opt, const char *decpt,
const char *thous, const char *group)
{
/* Decimal point must be defined; monetary decimal point might not be */
ASSERT(decpt != 0);
ASSERT(decpt_is_opt || *decpt != '\0');
/* Thousands separator and grouping must be valid (non-null) pointers */
ASSERT(thous != 0 && group != 0);
/* Thousands separator should be set iff grouping is set and vice versa */
ASSERT((*thous != '\0' && *group != '\0') ||
(*thous == '\0' && *group == '\0'));
/* Thousands separator, if set, should be different from decimal point */
ASSERT(*thous == '\0' || decpt_is_opt ||
(*decpt != '\0' && strcmp(thous, decpt) != 0));
return true;
}
static bool currency_valid(const char *currency_symbol, char frac_digits,
char p_cs_precedes, char p_sep_by_space, char p_sign_posn,
char n_cs_precedes, char n_sep_by_space, char n_sign_posn)
{
ASSERT(currency_symbol != 0);
if (*currency_symbol == '\0')
{
ASSERT(frac_digits == CHAR_MAX);
ASSERT(p_cs_precedes == CHAR_MAX);
ASSERT(p_sep_by_space == CHAR_MAX);
ASSERT(p_sign_posn == CHAR_MAX);
ASSERT(n_cs_precedes == CHAR_MAX);
ASSERT(n_sep_by_space == CHAR_MAX);
ASSERT(n_sign_posn == CHAR_MAX);
}
else
{
ASSERT_RANGE(frac_digits, 0, 9); // 9 dp of currency is a lot!
ASSERT_RANGE(p_cs_precedes, 0, 1);
ASSERT_RANGE(p_sep_by_space, 0, 2);
ASSERT_RANGE(p_sign_posn, 0, 4);
ASSERT_RANGE(n_cs_precedes, 0, 1);
ASSERT_RANGE(n_sep_by_space, 0, 2);
ASSERT_RANGE(n_sign_posn, 0, 4);
}
return true;
}
static bool locale_is_consistent(const struct lconv *loc)
{
if (!check_decpt_thous_group(false, loc->decimal_point, loc->thousands_sep, loc->grouping))
return false;
if (!check_decpt_thous_group((loc->frac_digits == 0 || loc->frac_digits == CHAR_MAX),
loc->mon_decimal_point, loc->mon_thousands_sep, loc->mon_grouping))
return false;
/* Signs must be valid (non-null) strings */
ASSERT(loc->positive_sign != 0 && loc->negative_sign != 0);
/* Signs must be different or both must be empty string (and probably n_sign_posn == 0) */
ASSERT(strcmp(loc->positive_sign, loc->negative_sign) != 0 || *loc->negative_sign == '\0');
if (!currency_valid(loc->currency_symbol, loc->frac_digits,
loc->p_cs_precedes, loc->p_sep_by_space, loc->p_sign_posn,
loc->n_cs_precedes, loc->n_sep_by_space, loc->n_sign_posn))
return false;
if (!currency_valid(loc->int_curr_symbol, loc->int_frac_digits,
loc->int_p_cs_precedes, loc->int_p_sep_by_space, loc->int_p_sign_posn,
loc->int_n_cs_precedes, loc->int_n_sep_by_space, loc->int_n_sign_posn))
return false;
/*
** If set, international currency symbol must be 3 (upper-case)
** alphabetic characters plus non-alphanum separator
*/
if (*loc->int_curr_symbol != '\0')
{
ASSERT(strlen(loc->int_curr_symbol) == 4);
ASSERT(isupper(loc->int_curr_symbol[0]));
ASSERT(isupper(loc->int_curr_symbol[1]));
ASSERT(isupper(loc->int_curr_symbol[2]));
ASSERT(!isalnum(loc->int_curr_symbol[3]));
}
return true;
}
用作“空间”&#39;格式化国际货币时的字符,允许字母字符以及ISO 4217国际货币代码(基本字母表中的三个大写字母)几乎没有意义。如果标志也是分开的话,允许数字可能会导致混淆,所以我认为loc->int_curr_symbol[3]
断言是明智的。严格检查将验证国际货币符号是ISO 4217中列出的符号之一;但是,编码有点棘手!