Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 41 (всего у книги 55 страниц)
localeconv()
Корректное форматирование числовых и денежных значений требует значительной низкоуровневой информации. Указанная информация доступна в struct lconv
, которую получают с помощью функции localeconv()
:
#include
struct lconv *localeconv(void);
Подобно функции ctime()
, эта функция возвращает указатель на внутренние статические данные. Следует сделать копию возвращенных данных, поскольку последующие вызовы могут возвратить другие значения, если локаль изменилась. Вот struct lconv
(слегка сжатая), непосредственно из GLIBC
:
struct lconv {
/* Числовая (не денежная) информация. */
char *decimal_point; /* Разделитель десятичной дроби. */
char *thousands_sep; /* Разделитель тысяч. */
/* Каждый элемент является числом цифр в каждой группе;
элементы с большими индексами оставлены дальше. Элемент со
значением CHAR_MAX означает, что дальнейшая группировка не
производится. Элемент со значением 0 означает, что предыдущий
элемент используется для всех оставшихся групп. */
char *grouping;
/* Денежная информация. */
/* Первые три символа являются символами валют из ISO 4217.
Четвертый символ является разделителем. Пятый символ ' '. */
char *int_curr_symbol;
char *currency_symbol; /* Символ местной валюты. */
char *mon_decimal_point; /* Символ десятичной точки. */
char *mon_thousands_sep; /* Разделитель тысяч. */
char *mon_grouping; /* Аналогично элементу 'группировки' (выше). */
char *positive_sign; /* Знак для положительных значений. */
char *negative_sign; /* Знак для отрицательных значений. */
char int_frac_digits; /* Международные цифры дробей. */
char frac_digits; /* Местные цифры дробей. */
/* 1, если символ валюты перед положит, значением, 0, если после. */
char p_cs_precedes;
/* 1, если символ валюты отделяется от положит, значения пробелом. */
char p_sep_by_space;
/* 1, если символ валюты перед отриц. значением, 0, если после. */
char n_cs_precedes;
/* 1, если символ валюты отделяется от отриц. значения пробелом. */
char n_sep_by_space;
/* Размещение положительного и отрицательного знака:
0 Количество и символ валюты окружены скобками.
1 Строка знака перед количеством и символом валюты.
2 Строка знака за количеством и символом валюты.
3 Строка знака непосредственно перед символом валюты.
4 Строка знака непосредственно после символа валюты. */
char p_sign_posn;
char n_sign_posn;
/* 1, если int_curr_symbol до положит. значения, 0, если после. */
char int_p_cs_precedes;
/* 1, если int_curr_symbol отделен от положит, знач. пробелом. */
char int_p_sep_by_space;
/* 1, если int_curr_symbol перед отриц. значением, 0, если после. */
char int_n_cs_precedes;
/* 1, если int_curr_symbol отделен от отриц. знач. пробелом. */
char int_n_sep_by_space;
/* Размещение положительного и отрицательного знака:
0 Количество и int_curr_symbol окружены скобками.
1 Строка знака до количества и int_curr_symbol.
2 Строка знака после количества и int_curr_symbol.
3 Строка знака непосредственно до int_curr_symbol.
4 Строка знака непосредственно после int_curr_symbol. */
char int_p_sign_posn;
char int_n_sign_posn;
};
Комментарии показывают довольно ясно, что происходит. Давайте посмотрим на несколько первых полей struct lconv
:
decimal_point
Используемый символ разделителя десятичной дроби. В Соединенных Штатах и других англоязычных странах это точка, но во многих странах используется запятая.
thousands_sep
Символ, используемый для разделения каждых 3 цифр значения.
grouping
Массив однобайтных целых значений. Каждый элемент указывает, сколько цифр в группе. Как сказано в комментарии, CHAR_MAX
означает, что дальше группировка не используется, а 0 означает повторное использование последнего элемента (Далее в главе мы покажем пример кода.)
int_curr_symbol
Это международный символ для местной валюты. Например, 'USD' для доллара США.
currency_symbol
Локальный символ для местной валюты. Например, $ для доллара США.
mon_decimal_point
, mon_thousands_sep
, mon_grouping
Соответствуют предыдущим полям, предоставляя те же сведения, но для денежных сумм.
Большая часть оставшихся значений не имеет значения для повседневного программирования. Следующая программа, ch13-lconv.c
, выводит некоторые из этих значений, чтобы дать вам представление, для какого рода сведений они используются:
/* ch13-lconv.c – показывает некоторые компоненты struct lconv */
#include
#include
#include
int main(void) {
struct lconv l;
int i;
setlocale(LC_ALL, "");
l = *localeconv();
printf("decimal_point = [%s]n", l.decimal_point);
printf("thousands_sep = [%s]n", l.thousands_sep);
for (i = 0; l.grouping[i] != 0 && l.grouping[i] != CHAR_MAX; i++)
printf("grouping[%d] = [%djn", i, l.grouping[i]);
printf("int_curr_symbol = [%s]n", l.int_curr_symbol);
printf("currency_symbol = f%s]n", l.currency_symbol);
printf("mon_decimal_point = f%s]n", l.mon_decimal_point);
printf("mon_thousands_sep = [%s]n", l.mon_thousands_sep);
printf("positive_sign = [%s]n", l.positive_sign);
printf("negative_sign = [%s]n", l.negative_sign);
}
Неудивительно, при запуске в различных локалях мы получаем различные результаты.
$ LC_ALL=en_US ch13-lconv /* Результаты для Соединенных Штатов */
decimal_point = [.]
thousands_sep = [,]
grouping[0] = [3]
grouping[1] = [3]
int_curr_symbol = [USD ]
currency_symbol = [$]
mon_decimal_point = [.]
mon_thousands_sep = [,]
positive_sign = []
negative_sign = [-]
$ LC_ALL=it_IT ch13-lconv /* Результаты для Италии */
decimal_point = [.]
thousands_sep = []
int_curr_symbol = []
currency_symbol = []
mon_decimal_point = []
mon_thousands_sep = []
positive_sign = []
negative_sign = []
Обратите внимание, что значение int_curr_symbol
в локали "en_US
" включает завершающий символ пробела, который служит для отделения символа от последующего денежного значения.
strfmon()
и printf()
После рассмотрения всех полей struct lconv
вы можете поинтересоваться: «Нужно ли мне на самом деле выяснять, как использовать все эти сведения, просто для форматирования денежного значения?» К счастью, ответом является «нет».[140]140
Мы так же счастливы, как и вы, поскольку нам не нужно представлять код, использующий эту полнофункциональную структуру – Примеч. автора.
[Закрыть] Функция strfmon()
делает за вас всю работу:
#include
ssize_t strfmon(char *s, size_t max, const char *format, ...);
Эта функция во многом подобна strftime()
(см. раздел 6.1.3.2 «Сложное форматирование времени: strftime()
»), используя format
для копирования символов букв и форматированных числовых значений в s
, помещая в нее не более max символов. Следующая простая программа, ch13-strfmon.c
, демонстрирует работу strfmon()
:
/* ch13-strfmon.c – демонстрация strfmon() */
#include
#include
#include
int main(void) {
char buf[BUFSIZ];
double val = 1234.567;
setlocale(LC_ALL, "");
strfmon(buf, sizeof buf, "You owe me %n (%i)n", val, val);
fputs(buf, stdout);
return 0;
}
При запуске в двух различных локалях она выдает такой результат:
$ LC_ALL=en_US ch13-strfmon /* В Соединенных Штатах */
You owe me $1,234.57 (USD 1,234.57)
$ LC_ALL=it_IT ch13-strfmon /* В Италии */
You owe me EUR 1.235 (EUR 1.235)
Как вы можете видеть, strfmon()
подобна strftime()
, копируя обычные символы в буфер назначения без изменений и форматируя аргументы в соответствии со своими собственными спецификациями форматирования. Их всего три.
%n
Вывести национальное (т.е. местное) представление значения валюты.
%i
Вывести международное представление значения валюты.
%%
Вывести символ '%
'.
Форматируемые значения должны иметь тип double
. Разницу между %n
и %i
мы видим в локали "en_US
": %n
использует символ $
, тогда как %i
использует USD, которая означает «доллары США».
Гибкость – и соответственно определенная сложность – сопровождают многие функции API, разработанные для POSIX, и strfmon()
не является исключением. Как и с printf()
, несколько необязательных элементов, которые могут быть между %
и i
или n
, обеспечивают повышенный контроль. Полные формы следующие:
%[флаги][ширина поля][#точность_слева][.точность_справа]i
%[флаги][ширина поля][#точность_слева][.точность_справа]n
%% /* Не допускаются поля флагов, ширины и т.д. */
Флаги перечислены в табл. 13.2.
Таблица 13.2. Флаги для strfmon()
=с | Использовать символ с в качестве символа числового заполнения слева. Символом по умолчанию является пробел. Обычной альтернативой является 0 |
^ | Запретить использование символа группировки (например, запятой в Соединенных Штатах) |
( | Отрицательные значения заключать в скобки. Несовместим с флагом + |
+ | Обрабатывать положительные/отрицательные значения обычным образом. Использовать положительные и отрицательные знаки локали. Несовместим с флагом ( |
! | Не включать символ валюты. Этот флаг полезен, если вы хотите использовать strfmon() для более гибкого форматирования обычных чисел, чем это предусматривает sprintf() |
- | Выровнять результат слева. По умолчанию используется выравнивание справа. Этот флаг не действует без указания ширины поля |
Ширина поля является строкой десятичных цифр, представляющих минимальную ширину. По умолчанию использует столько символов, сколько необходимо, основываясь на оставшейся части спецификации. Значения, меньшие ширины поля, дополняются пробелами слева (или справа, если указан флаг '-
').
Точность слева состоит из символа #
и строки десятичных цифр. Она указывает минимальное число цифр, которые должны быть слева от десятичного символа-разделителя дробной части[141]141
В стандарте используется технический термин radix point (позиционный разделитель), поскольку числа с другим основанием счисления также могут иметь дробные части. Однако, для денежных значений можно довольно безопасно использовать термин 'decimal point' (десятичный разделитель) – Примеч. автора.
[Закрыть]; если преобразованное значение меньше этого, результат выравнивается символом числового заполнения. По умолчанию используется пробел, однако для его изменения можно использовать флаг =
. Символы группировки не включаются в общий счет.
Наконец, точность справа состоит из символа '.
' и строки десятичных цифр. Она указывает, с каким числом значащих цифр округлить значение до форматирования. По умолчанию используются поля frac_digits
и int_frac_digits
в struct lconv
. Если это значение равно 0, десятичная точка не выводится.
strfmon()
возвращает число символов, помещенных в буфер, не включая завершающий нулевой байт. Если недостаточно места, функция возвращает -1 и устанавливает errno
в E2BIG
.
Помимо strfmon()
, POSIX (но не ISO С) предусматривает специальный флаг – символ одинарной кавычки, '
– для форматов printf()
%i
, %d
, %u
, %f
, %F
, %g
и %G
. В локалях, имеющих разделитель тысяч, этот флаг добавляет и его. Следующая простая программа, ch13-quoteflag.c
, демонстрирует вывод:
/* ch13-quoteflag.c – демонстрация флага кавычки printf */
#include
#include
int main(void) {
setlocale(LC_ALL, ""); /* Это нужно, иначе не будет работать */
printf("%'dn", 1234567);
return 0;
}
Вот что происходит для двух различных локалей: в одной есть разделитель тысяч, в другой нет:
$ LC_ALL=C ch13-quoteflag /* Обычное окружение без разделителя */
1234567
$ LC_ALL=en_US ch13-quoteflag /* Локаль с разделителем (англ.) */
1,234,567
На время написания лишь GNU/Linux и Solaris поддерживают флаг '
. Дважды проверьте справочную страницу printf(3) на своей системе.
gawk
gawk
реализует свои собственные версии функций printf()
и sprintf()
. Для полного использования локали gawk
должен поддерживать флаг '
, как в С. Следующий фрагмент из файла builtin.c
в gawk
3.1.4 показывает, как gawk
использует struct lconv
для числового форматирования:
1 case 'd':
2 case 'i':
3 ...
4 tmpval = force_number(arg);
5
6 ...
7 uval = (uintmax_t)tmpval;
8 ...
9 ii = jj = 0;
10 do {
11 *–cp = (char)('0' + uval % 10);
12 #ifdef HAVE_LOCALE_H
13 if (quote_flag && loc.grouping[ii] && ++jj == loc.grouping[ii]) {
14 *–cp = loc.thousands_sep[0]; /* XXX – предположение, что это один символ */
15 if (loc.grouping[ii+1] == 0)
16 jj = 0; /* продолжить использовать текущий val в loc.grouping [ii] */
17 else if (loc.grouping[ii+1] == CHAR_MAX)
18 quote_flag = FALSE;
19 else {
20 ii++;
21 jj = 0;
22 }
23 }
24 #endif
25 uval /= 10;
26 } while (uval > 0);
(Номера строк даны относительно начала фрагмента.) Некоторые части кода, не имеющие отношения к обсуждению, были опущены, чтобы облегчить фокусировку на важных частях.
Переменная loc
, используемая в строках 13–17, представляет struct lconv
. Она инициализируется в main()
. Здесь для нас интерес представляет loc.thousands_sep
, который является символом разделителя тысяч, и loc.grouping
, который является массивом, описывающим число цифр между разделителями. Нулевой элемент означает «использовать для всех последующих цифр значение предыдущего элемента», а значение CHAR_MAX
означает «прекратить вставку разделителей тысяч».
С таким введением, давайте посмотрим на код. Строка 7 устанавливает uval
, которая является беззнаковой версией форматируемого значения. ii
и jj
отслеживают положение в loc.grouping
и число цифр в текущей группе, которые были преобразованы, соответственно[142]142
Нам, вероятно, следовало выбрать более осмысленные имена вместо простых ii
и jj
, поскольку использующий их код короткий, отсутствие у нас воображения не представляет значительной проблемы – Примеч. автора.
[Закрыть]. quote_flag
равен true, когда в спецификации преобразования был отмечен символ '
.
Цикл do-while
генерирует символы цифр в обратном порядке, заполняя буфер с конца к началу. Каждая цифра создается в строке 11. Затем строка 25 делится на 10 путем смещения значения вправо на одну десятичную цифру.
Нас интересуют строки 12–24. Эта работа осуществляется только на системе, поддерживающей локали, на что указывает наличие заголовочного файла
. Именованная константа HAVE_LOCALE
в такой системе будет равна true[143]143
Это устанавливается механизмом Autoconf и Automake. Autoconf и Automake являются мощными программными наборами, дающими возможность поддержки широкого круга Unix-систем систематическим образом – Примеч. автора.
[Закрыть].
Когда условие в строке 13 истинно, настало время добавить символ разделителя тысяч. Это условие можно прочесть как «если требуется группировка и текущее положение в loc.grouping
указывает нужное для группировки количество и текущее число цифр равно группируемому количеству». Если это условие истинно, строка 14 добавляет символ разделителя тысяч. Комментарий обращает внимание на предположение, которое, вероятно, истинно, но которое может вновь появиться позже. ('XXX' является традиционным способом выделения опасного или сомнительного кода. Его легко отыскать, и он весьма заметен для читателя кода.)
После использования текущего положения в loc.grouping
строки 15–22 заглядывают в значение в следующем положении. Если это 0, продолжает использоваться значение текущего положения. Мы указываем на это, восстанавливая 0 в jj
(строка 16). С другой стороны, если в следующем положении CHAR_MAX
, группировка должна быть прекращена, и строка 18 убирает ее, устанавливая quote_flag
в false. В противном случае, следующее значение является значением группировки, поэтому строка 20 восстанавливает 0 в jj
, а строка 21 увеличивает значение ii
.
Это низкоуровневый, подробный код. Однако, поняв один раз, как представляется информация в struct lconv
, код читать просто (и его было просто писать).
ctime()
и strftime()
В разделе 6.1 «Времена и даты» описаны функции для получения и форматирования значений времени и даты. Функция strftime()
также может использовать локаль, если setlocale()
была вызвана должным образом. Это демонстрирует следующая простая программа, ch13-times.с
:
/* ch13-times.c – демонстрация времени на основе локали */
#include
#include
#include
int main(void) {
char buf[100];
time_t now;
struct tm *curtime;
setlocale(LC_ALL, "");
time(&now);
curtime = localtime(&now);
(void)strftime(buf, sizeof buf,
"It is now %A, %B %d, %Y, %I:%M %p", curtime);
printf("%sn", buf);
printf("ctime() says: %s", ctime(&now));
exit(0);
}
При запуске программы мы видим, что результаты strftime()
в самом деле варьируют, тогда как результаты ctime()
– нет:
$ LC_ALL=en_US ch13-times /* Время в Соединенных Штатах */
It is now Friday, July 11, 2003, 10:35 AM
ctime() says: Fri Jul 11 10:35:55 2003
$ LC_ALL=it_IT ch13-times /* Время в Италии */
It is now venerdi, luglio 11, 2003, 10:36
ctime() says: Fri Jul 11 10:36:00 2003
$ LC_ALL=fr_FR ch13-times /* Время во Франции */
It is now vendredi, juillet 11, 2003, 10:36
ctime() says: Fri Jul 11 10:36:05 2003
Причина отсутствия изменений в том, что ctime()
(и asctime()
, на которой основана ctime()
) является традиционным интерфейсом; он существует для поддержки старого кода, strftime()
, будучи более новым интерфейсом (первоначально разработанным для C89), свободен использовать локали.
nl_langinfo()
Хотя ранее мы сказали, что API catgets()
трудно использовать, одна часть этого API обычно полезна: nl_langinfo()
. Она предоставляет дополнительные связанные с локалью сведения, помимо тех, которые доступны из struct lconv
:
#include
#include
char *nl_langinfo(nl_item item);
Заголовочный файл
определяет тип nl_item
. (Это скорее всего int
или enum
.) Параметр item
является одной из именованных констант, определенных в
. Возвращаемое значение является строкой, которую можно при необходимости использовать либо непосредственно, либо в качестве форматирующей строки для strftime()
.
Доступная информация поступает из нескольких категорий локали. В табл. 13.3 перечислены константы элементов, соответствующие категории локали и их значения.
Таблица 13.3. Значения элементов для nl_langinfo()
ABDAY_1 , …, ABDAY_7 | LC_TIME | Сокращенные названия дней недели. Воскресенье является днем 1 |
ABMON_1 , …, ABMON_12 | LC_TIME | Сокращенные названия месяцев |
ALT_DIGITS | LC_TIME | Альтернативные символы для цифр; см. текст |
AM_STR , PM_STR | LC_TIME | Обозначения a.m/p.m. для локали. |
CODESET | LC_TYPE | Имя кодовой страницы для локали, т.е. использующиеся набор символов и кодировка |
CRNCYSTR | LC_MONETARY | Символ местной валюты, описанный ниже |
DAY_1 , …, DAY_7 | LC_TIME | Названия дней недели. Воскресенье является днем 1 |
D_FMT | LC_TIME | Формат даты |
D_T_FMT | LC_TIME | Формат даты и времени |
ERA_D_FMT | LC_TIME | Формат даты эры. |
ERA_D_T_FMT | LC_TIME | Формат даты и времени эры. |
ERA_T_FMT | LC_TIME | Формат времени эры. |
ERA | LC_TIME | Сегменты описания эры, см. текст. |
MON_1 , …, MON_12 | LC_TIME | Названия месяцев. |
RADIXCHAR | LC_NUMERIC | Символ системы счисления. Для базы 10 это символ точки в десятичной дроби. |
THOUSEP | LC_NUMERIC | Символ-разделитель тысяч |
T_FMT_AMPM | LC_TIME | Формат времени в записи a.m/p.m. |
T_FMT | LC_TIME | Формат времени. |
YESEXPR , NOEXPR | LC_MESSAGES | Строка, представляющая положительный и отрицательный ответы. |
Эра является определенным временем в истории. Поскольку она имеет отношение к датам и временам, она имеет наибольший смысл в странах, управляемых императорами и династиями.[144]144
Хотя американцы часто ссылаются на эры определенных президентов, они не являются частью национального календаря в том же смысле, как в Японии до Второй мировой войны или в докоммунистическом Китае – Примеч. автора.
[Закрыть]
Спецификации эр POSIX могут определять эры ранее 1 г. н.э. В таких случаях у начальной даты большее абсолютное числовое значение, чем у конечной даты. Например, Александр Великий правил с 336 г. до н.э. по 323 г до н.э.
Значение, возвращенное 'nl_langinfo(ERA)
', если оно не равно NULL
, состоит из одной или более спецификаций эр. Каждая спецификация отделена от следующей символом ';
'. Компоненты спецификации каждой эры отделяются друг от друга символом ':
'. Компоненты описаны в табл. 13.4.
Таблица 13.4. Компоненты спецификации эры
Направление | Символы '+ ' или '- '. '+ ' означает, что эра отсчитывается от численно меньшего года к численно большему году, а '- ' означает обратный порядок |
Смешение | Ближайший к дате начала эры год |
Дата начала | Дата начала эры в виде 'гггг/мм/дд'. Это соответственно год, месяц и день. Годы до н.э используют для гггг отрицательные значения |
Дата конца | Дата завершения эры в том же самом виде. Допустимы два дополнительных вида: -* означает «начало времени», а +* означает «конец времени» |
Название эры | Название эры, соответствующее спецификации преобразования %EC функции strftime() |
Формат эры | Формат года в пределах эры, соответствующий спецификации преобразования %EY функции strftime() |
Значение ALT_DIGITS
также нуждается в некотором объяснении. Некоторые локали предоставляют «альтернативные цифры». (Рассмотрите арабский язык, в котором используется десятичная система счисления, но изображения для цифр 0–9 другие. Или рассмотрите гипотетическую локаль «Древнего Рима», использующую римские цифры.) Они появляются, например, в различных спецификациях преобразования %OC
в функции strftime()
. Возвращаемое значение для 'nl_langinfo(ALT_DIGITS)
' является разделяемым точками с запятой списком строк символов для альтернативных цифр. Первая должна использоваться для 0, следующая для 1 и т.д. POSIX утверждает, что могут быть предоставлены до 100 альтернативных символов. Сущность в том, чтобы избежать ограничения локалей использованием символов цифр ASCII, когда у локали есть собственная система счисления.
Наконец, 'nl_langinfo(CRNCYSTR)
' возвращает символ местной валюты. Первый символ возвращаемого значения, если это '-
', '+
' или '.
', указывает, как должен использоваться символ:
–
Символ должен быть перед значением.
+
Символ должен быть после значения.
.
Символ должен заменить символ основания (разделитель десятичной дроби).