Глава 13 Интернационализация

13.0. Введение

В данной главе приводятся решения некоторых задач, которые обычно возникают при интернационализации программ С++. Обеспечение возможности работы программы в различных регионах (это обычно называется локализацией), как правило, требует решения двух задач: форматирования строк, воспринимаемых пользователем, в соответствии с местными соглашениями (например, даты, времени, денежных сумм и чисел) и обеспечения работы с различными символьными наборами. В настоящей главе рассматривается в основном первая проблема и только кратко вторая, потому что имеется немного возможностей по стандартизации поддержки различных символьных наборов из-за того, что такая поддержка во многом зависит от реализации.

Большая часть программного обеспечения будет работать в странах, отличных от той, где они были написаны. Для поддержки этой практики стандартная библиотека C++ имеет несколько средств, способствующих написанию программного кода, предназначенного для работы в различных странах. Однако они спроектированы не так. как многие другие средства стандартной библиотеки, например строки, файловый ввод-вывод, контейнеры, алгоритмы и т.п. Например, класс, представляющий локализацию, имеет имя

locale
и содержится в заголовочном файле
. Класс
locale
предоставляет средства для записи и чтения потоков с применением специфичного для данной местности форматирования и получения таких сведений о локализации, как, например, ее символ валюты или формат даты. Однако стандартом предусматривается обеспечение только одной локализации, и этой локализацией является «C»-локализация, или классическая локализация. Классическая локализация использует стандарт ANSI С: принятые в американском варианте английского языка соглашения по форматированию и 7-битовой код ASCII. И от реализации зависит, будут ли обеспечены экземпляры locale для других языков и регионов.

Заголовочный файл

имеет три основные части. Во-первых, это класс
locale
(локализация). Он инкапсулирует все поддерживаемые в C++ особенности локализованного поведения и обеспечивает точки входа для получения различной информации о локализации, необходимой для выполнения локализованного форматирования. Во-вторых, самыми маленькими элементами локализации и конкретными классами, с которыми вы будете работать, являются классы, называемые фасетами (facets). Примером фасета является, например, класс
time_put
, предназначенный для записи даты в поток. В-третьих, каждый фасет принадлежит к некоторой категории, которая объединяет связанные фасеты в одну группу. Например, имеются числовая, временная и денежная категории (только что упомянутый мною фасет
time_put
относится к временной категории). Я кратко описываю категории в данной главе, однако действительную пользу они приносят при осуществлении более изощренных действий, связанных с локализацией.

Каждая программа на C++ имеет, по крайней мере, одну локализацию, называемую глобальной локализацией (она часто реализуется как глобальный статический объект). По умолчанию это будет классическая локализация «С», пока вы не измените ее на что- нибудь другое. Один из конструкторов

locale
позволяет инстанцировать локализацию, предпочитаемую пользователем, хотя точное определение «предпочитаемой» пользователем локализации полностью зависит от реализации.

В большинстве случаев локализации используются при записи и чтении потоков. Это является основной темой настоящей главы.

13.1. Жесткое кодирование строк в коде Unicode

Проблема

Требуется в исходном файле жестко закодировать строки в коде Unicode, т.е. используя расширенный набор символов.

Решение

Начинайте строку с префикса

L
и затем вводите символы в своем редакторе исходных текстов, как вы это обычно делаете при написании строк, или используйте шестнадцатеричные значения, представляющие нужный вам символ в коде Unicode. Пример 13.1 демонстрирует оба способа кодирования таких строк.

Пример 13.1. Жесткое кодирование строк в коде Unicode

#include 

#include 

#include 


using namespace std;


int main() {

 // Создать несколько строк с символами кода Unicode

 wstring ws1 = L"Infinity: \u221E";

 wstring ws2 = L"Euro: €"

 wchar_t w[] = L"Infinity: \u221E";

 wofstream out("tmp\\unicode.txt");

 out << ws2 << endl;

 wcout << ws2 << endl;

}

Обсуждение

Основной вопрос, возникающий при жестком кодировании строк в коде Unicode, связан с выбором способа ввода строки в редакторе исходных текстов. В C++ предусмотрен тип расширенного набора символов

wchar_t
, который может хранить строки в коде Unicode. Точное представление
wchar_t
зависит от реализации, однако часто используется формат UTF-32. Класс
wstring
определяется в
как последовательность символов типа
wchar_t
, подобно тому как класс
string
представляет собой последовательность символов типа
char
. (Строго говоря, тип
wstring
определяется, конечно, с помощью
typedef
как
basic_string
.)

Самый простой способ ввода символов в коде Unicode — это использование префикса

L
перед строковым литералом, как показано в примере 13.1.

wstring ws1 = L"Infinity, \u2210"; // Использовать сам код

wstring ws2 = L"Euro: €"; // или просто ввести символ

Теперь можно записать эти строки с расширенным набором символов в поток с расширенным набором символов.

wcout << ws1 << endl; // wcout - версия cout для расширенного набора символов

Их можно записывать также в файлы:

wofstream out("tmp\\unicode.txt");

out << ws2 << endl;

При работе с различными кодировками наибольшую ловкость приходится проявлять не для ввода правильных символов в ваши исходные файлы, а при определении типа символьных данных, получаемых из базы данных, по запросу HTTP, из пользовательского ввода и т.д., что выходит за рамки стандарта C++. Стандарт C++ не устанавливает никаких специальных требований, кроме того, что операционная система может использовать для исходных файлов любую кодировку, если она поддерживает, по крайней мере, 96 символов, используемых в языке С++. Для символов, не попадающих в этот набор, называемый основным исходным набором символов, стандартом предусматривается возможность их получения с помощью escape-последовательностей

\uXXXX
или
\UXXXXXXXX
, где
X
— шестнадцатеричная цифра.

13.2. Запись и чтение чисел

Проблема

Требуется записать число в поток в форматированном виде в соответствии с местными соглашениями.

Решение

Закрепите (imbue) текущую локализацию за потоком, в который вы собираетесь писать данные, и запишите в него числа, как это сделано в примере 13.2, или можете установить глобальную локализацию и затем создать поток. Последний подход рассматривается в обсуждении.

Пример 13.2. Запись чисел с использованием локализованного форматирования

#include 

#include 

#include 


using namespace std;


// На заднем плане существует глобальная локализация, установленная средой

// этапа выполнения. По умолчанию это локализация "С". Вы можете ее

// заменить локализацией locale::global(const locale&).

int main() {

 locale loc(""); // Создать копию пользовательской локализации

 cout << "Locale name = " << loc.name() << endl;

 cout.imbue(loc); // Уведомить cout о необходимости применения

         // пользовательской локализации при форматировании

 cout << "pi in locale " << cout.getloc().name() << " is << 3.14 << endl;

}

Обсуждение

Пример 13.2 показывает, как можно использовать пользовательскую локализацию для форматирования числа с плавающей точкой. Это делается в два этапа: сначала создается экземпляр класса

locale
, который затем закрепляется за потоком с помощью функции
imbue
.

Сначала в примере 13.2 создается

loc
, который является копией пользовательской локализации. Это необходимо делать, используя конструктор
locale
, принимающий пустую строку (а не конструктор по умолчанию).

locale loc("");

Отличие небольшое, но важное, и я вскоре вернусь к нему. При создании здесь объекта

locale
создается копия «пользовательской локализации», которая зависит от реализации. Это значит, что, если машина сконфигурирована на применение американского варианта английского языка, функция
locale::name()
может возвращать такие строковые имена локализации, как «
en_US
», «
English_United States.1252
», «
english-american
» и т.д. Реальная строка определяется реализацией, а по стандарту C++ достаточно иметь только одну локализацию — «C»-локализацию.

Для сравнения отметим, что конструктор по умолчанию класса

locale
возвращает копию текущей глобальной локализации. Всякая выполняемая программа, написанная на С++, имеет один глобальный объект
locale
(возможно, реализованный как статическая переменная где-то в библиотеке этапа выполнения; детали его реализации зависят от используемой платформы). По умолчанию это будет локализация С, и вы можете заменить ее локализацией
locale::global(locale& loc)
. Когда потоки создаются, они используют глобальную локализацию, существующую на момент их создания; это означает, что
cin
,
cout
,
cerr
,
wcin
,
wcout
и
wcerr
используют локализацию С, поэтому приходится явным образом ее менять, если требуется, чтобы форматирование подчинялось соглашениям, принятым в определенной местности.

Имена локализаций не стандартизованы. Однако обычно они имеют следующий формат.

<язык>_<страна>.<кодовая_страница>

Язык задается полным названием, например «

Spanish
», или двухбуквенным кодом, например «
sp
»; страна задается своим названием, например «
Colombia
», или двухбуквенным кодом страны, например «
СО
», а кодовая страница задается своим обозначением, например
1252
. Обязательно должен быть указан только язык. Поэкспериментируйте, явно задавая локализации в различных системах, чтобы почувствовать характер отличий имен при применении разных компиляторов. Если вы используете неверное имя локализации, будет выброшено исключение
runtime_error
. Пример 13.3 показывает, как можно явно задавать имена локализаций.

Пример 13.3. Явное именование локализаций

#include 

#include 

#include 

#include 


using namespace std;


int main() {

 try {

  locale loc("");

  cout << "Locale name = " << loc.name() << endl;

  locale locFr("french");

  locale locEn("english-american");

  locale locBr("portuguese-brazilian");

  cout.imbue(locFr); // Уведомить cout о необходимости применения

           // французского форматирования

 cout << "3.14 (French) = " << 3.14 << endl;

 cout << "Name = " << locFr.name() << endl;

 cout.imbue(locEn); // Теперь перейти на английский (американский

            // вариант)

  cout << "3.14 (English) = " << 3.14 << endl;

  cout << "Name = " << locEn.name() << endl;

  cout.imbue(locBr); // Уведомить cout о необходимости применения

           // бразильского форматирования

  cout << "3.14 (Brazil) = " << 3.14 << endl;

  cout << "Name = " << locBr.name() << endl;

 } catch (runtime_error& e) {

  // Если используется неверное имя локализации, выбрасывается исключение

  // runtime_error.

  cerr << "Error: " << e.what() << endl;

 }

}

Результат выполнения этой программы в системе Windows при использовании Visual C++ 7.1 выглядит следующим образом.

Locale name = English_United States.1252

3.14 (French) = 3,14

Name = French_France.1252

3.14 (English) = 3.14

Name = English_United States.1252

3.14 (Brazil) = 3,14

Name = Portuguese_Brazil.1252

Отсюда видно, что моя машина локализована на американский вариант английского языка с использованием кодовой страницы 1252. Этот пример также показывает, как выводится число «пи» при использовании двух других локализаций. Обратите внимание, что во французском и бразильском вариантах применяется запятая вместо десятичной точки. Разделитель тысяч тоже другой: во французском и португальском вариантах используется пробел вместо запятой, поэтому число 1,000,000.25, представленное в американском формате, имело бы вид 1 000 000,25 в формате французской и португальской локализации.

В большинстве случаев все же не стоит создавать локализации, явно задавая их имена. Чтобы использовать локализации для печати чисел, дат, денежных и каких-либо других значений, просто достаточно инстанцировать локализацию, используя пустую строку и закрепляя ее за потоком.

Правила применения локализаций могут показаться немного путанными, поэтому я кратко изложу основные моменты.

• Используемая по умолчанию глобальная локализация является локализацией «С», потому что стандартом гарантируется существование в любой реализации только этой локализации.

• Все стандартные потоки создаются с применением глобальной локализации при запуске программы, и этой локализацией является локализация «С».

• Копию пользовательской текущей локализации можно создать, передавая пустую строку конструктору

locale
, например
locale("")
.

• Объект

locale
для именованной локализации можно создать, передавая строку, идентифицирующую локализацию, например
locale("portuguese-brazilian")
. Однако эти строки не стандартизованы.

• После получения объекта

locale
, представляющего стандартную пользовательскую локализацию или именованную локализацию, можно установить глобальную локализацию с помощью функции
locale::global
. Все создаваемые после этого потоки будут использовать глобальную локализацию.

• Для потока локализацию можно задать явно при помощи функции-члена

imbue
.

При написании программного обеспечения, учитывающего местные особенности, применяйте локализованное форматирование только к данным, которые пользователь видит. То есть если вам требуется отобразить число в формате, привычном для пользователя, инстанцируйте локализацию и закрепите ее за потоком, чтобы число отображалось правильно. Однако, если вы записываете данные в файл или в какую-то другую промежуточную сериализованную память, используйте локализацию С, обеспечивая переносимость. Если ваша программа явным образом изменяет глобальную локализацию, вам необходимо явно закрепить ее за потоками, использующими локализацию С. Вы это можете сделать двумя способами: создавая локализацию с именем «С» или вызывая функцию

locale::classic()
.

ofstream out("data.dat");

out.imbue(locale::classic());

out << pi << endl; // Записать, используя локализацию С

Считываются числа аналогично. Например, для чтения числа с использованием французской локализации и записи его с использованием локализации С выполните следующее.

double d;

cin.imbue(locale("french"));

cin >> d;

cout << "In English: " << d;

Если вы выполните эту программу и введете

300,00
, она распечатает
300
.

Чтобы поток подчинялся местным соглашениям по выводу чисел, явно закрепите за потоком объект

locale
целевой локализации с помощью функции
imbue
. Если требуется во всех созданных потоках использовать конкретную локализацию, сделайте ее глобальной. Значения денежных значений обрабатываются немного по-другому; см. рецепт 13.4, где показано, как можно записывать и считывать денежные значения.

Смотри также

Рецепт 13.4.

13.3. Запись и чтение дат и времен

Проблема

Требуется отобразить или прочитать значения дат и времен, используя местные соглашения по форматированию.

Решение

Используйте тип

time_t
и
tm struct
из
, а также фасеты даты и времени, предусмотренные в
, для записи и чтения дат и времен (фасеты вскоре будут рассмотрены при обсуждении примера). См. пример 13.4.

Пример 13.4. Запись и чтение дат

#include 

#include 

#include 

#include 

#include 


using namespace std;


void translateDate(istream& in, ostream& out) {

 // Создать считывающий дату объект

 const time get& dateReader =

 use_facet >(in.getloc());


 // Создать объект состояния который будет использован фасетом для

 // уведомления нас о возникновении проблемы

 ios_base::iostate state = 0;

 // Маркер конца

 istreambuf_iterator end;

 tm t; // Структура для представления времени (из )


 // Теперь, когда все подготовлено, считать дату из входного потока

 // и поместить ее в структуру времени.

 dateReader.get_date(in, end, in, state, &t);


 // В данный момент дата находится в структуре tm. Вывести ее в поток,

 // используя соответствующую локализацию. Убедитесь, что выводятся только

 // достоверные данные из t.

 if (state == 0 || state == ios_base::eofbit) { // Чтение выполнено успешно.

  const Time_put& dateWriter =

   use_facet >(out.getloc());

  char fmt[] = "%x";

  if (dateWriter.put{out, out, out.fill(),

  &t, &fmt[0], &fmt[2]).failed())

  cerr << "Unable to write to output stream.\n";

 } else {

  cerr << "Unable to read cin!\n";

 }

}


int main() {

 cin.imbue(locale("english"));

 cout.imbue(locale("german"));

 translateDate(cin, cout);

}

Эта программа выдает следующий результат

3/28/2005

28.03.2005

Обсуждение

Для правильной записи и чтения значений даты и времени необходимо знать некоторые детали проекта класса

locale
. Прочтите введение в эту главу, если вы еще не знакомы с концепциями локализаций и фасетов.

В C++ нет стандартного класса для представления даты и времени, а наиболее подходящими для этого типами являются

time_t
и структура
tm
из
. Если требуется записывать и считывать даты с использованием средств стандартной библиотеки, вам придется любое нестандартное представление даты преобразовывать в структуру
tm
. Это имеет смысл, поскольку используемые вами реализации, вероятно, уже имеют встроенную поддержку форматирования дат с учетом местных особенностей.

Ранее я говорил, что фасет определяет некоторый аспект локализации, отражающий ее особенности. Более конкретно, фасет — это константная инстанциация шаблона класса символьного типа, поведение которого зависит от класса локализации, используемого при конструировании. В примере 13.4 я следующим образом создаю экземпляр фасета

time_get
.

const time_get& dateReader =

 use_facet >(in.getloc());

Шаблон функции

use_facet
находит заданный фасет для заданной локализации. Все стандартные фасеты являются шаблонами классов, которые принимают параметр символьного типа, и, поскольку мною считываются и записываются символы типа
char
, я инстанцирую мой класс
time_get
для
char
. Стандарт требует, чтобы реализация обеспечивала специализацию шаблона для
char
и
wchar_t
, поэтому они гарантированно существуют (хотя не гарантируется поддержка заданной локализации, кроме локализации С). Созданный мною объект
time_get
имеет спецификатор
const
, потому что предусмотренная реализацией функциональность локализации это набор правил форматирования различного вида данных в разных локализациях, и эти правила не могут редактироваться пользователем, поэтому состояние заданного фасета не должно изменяться в программном коде, где он используется.

Локализация, передаваемая мною в функцию

use_facet
, связана с потоком, в который я собираюсь записывать данные. Функция
getloc()
объявляется в
ios_base
; она возвращает локализацию, связанную с потоком ввода или вывода. Наилучший подход — применение локализации, уже связанной с потоком, который вы собираетесь использовать для ввода или вывода данных; передача в качестве параметра или каким-либо другим способом имени локализации легко приводит к ошибкам.

После создания объекта, который будет выполнять реальное чтение, мне необходимо обеспечить контроль состояния потока.

ios_base::iostate state = 0;

Сами фасеты не модифицируют состояние потока (например, устанавливая

stream::failbit = 1
); вместо этого они установят соответствующее значение в вашем объекте состояния, показывая, что дату нельзя считывать. Это объясняется тем, что чтение форматированного значения терпит неудачу не обязательно из-за потока; поток ввода символов может быть в полном порядке, однако его чтение с использованием нужного вам формата может оказаться невозможным.

Реальное значение даты хранится в структуре

tm
. Вам требуется только создать локальную переменную типа tm и передать ее адрес фасету
time_get
или
time_put
.

Считав дату, я могу проверить значение переменной, которую я использую для контроля состояния потока. Если это значение равно нулю или

ios_base::eofbit
, то это говорит о том, что поток находится в нормальном состоянии и что моя дата была считана без проблем. Поскольку в примере 13.4 мне нужно было записать дату в другой поток, пришлось создать объект, используемый именно для этой цели. Я делаю это следующим образом.

const time_put& dateWriter =

use_facet >(out.getloc());

Это работает так же, как и предыдущая инстанциация класса

time_get
, но в другом направлении. После этого я создал строку форматирования (используя синтаксис, подобный применяемому в функции
printf
), которая будет печатать дату. «
%x
» выводит дату, а «
%X
» выводит время. Однако следует быть осторожным: в этом примере считывается только дата, поэтому члены структуры
tm
, относящиеся ко времени, в этот момент имеют неопределенные значения.

Теперь можно писать данные в поток вывода. Это делается следующим образом.

if (dateWriter.put(out, // Итератор потока вывода

 out,          // Лоток вывода

 out.fill(),       // Использовать символ заполнителя

 &t,           // Адрес структуры tm

 &fmt[0],        // Начало и конец строки форматирования

 &fmt[2]

).failed())       // iter_type.failed() показывает, была или

             // нет ошибка при записи

Функция

time_put::put
записывает дату в переданный ей поток вывода, используя локализацию, с которой был создан объект
time_put
.
time_put::put
возвращает итератор
ostreambuf_iterator
, который имеет функцию-член
failed
, позволяющую зафиксировать ситуацию, когда итератор оказывается испорченным.

get_date
не единственная функция-член, которую можно использовать для получения компонент даты из потока. Ниже перечислены некоторые из них.

get_date

Получает дату из потока, используя местные правила форматирования.

get_time

Получает время из потока, используя местные правила форматирования.

get_weekday

Получает название дня недели, например Monday, lundi, понедельник.

get_year

Получает год, используя местные правила форматирования.

Может быть полезной также функция-член

date_order
. Она возвращает перечисление (
time_base::dateorder
из
), которое определяет порядок месяца, дня и года в дате. Эта функция может помочь в тех случаях, когда вам приходится анализировать вывод даты, полученной функцией
time_get::put
. Пример 13.5 показывает, как можно проверять порядок элементов, составляющих дату.

Пример 13.5. Определение последовательности элементов в дате

#include 

#include 

#include 


using namespace std;


int main() {

 cin.imbue(locale("german"));

 const time_get& dateReader =

  use_facet >(cin.getloc());

 time_base::dateorder d = dateReader.date_order();

 string s;

 switch (d) {

 case time_base::no_order:

  s = "No order";

  break;

 case time_base::dmy:

  s = "day/month/year";

  break;

 case time_base::mdy:

  s = "month/day/year";

  break;

 case time_base::ymd:

  s = "year/month/day";

  break;

 case time_base::ydm:

  s = "year/day/month";

  break;

 }

 cout << "Date order for locale " << cin.getloc().name()

  << " is " << s << endl;

}

Имеется еще одно средство, которое может оказаться полезным при инстанцировании фасетов:

has_facet
. Шаблон этой функции возвращает значение типа
bool
, показывающее, определен или нет нужный вам фасет в заданной локализации. Поэтому для надежности используйте
has_facet
во всех случаях, когда вы инстанцируете фасет. Если она возвращает значение «ложь», вы всегда можете перейти к используемой по умолчанию классической локализации С, поскольку ее наличие гарантировано в реализации, отвечающей требованиям стандарта.
has_facet
применяется следующим образом.

if (has_facet >(loc)) {

 const time_put& dateWriter =

 use_facet >(loc);

Разобравшись однажды в синтаксисе классов

time_get
и
time_put
, вы поймете, что использовать их достаточно просто. Как всегда, можно воспользоваться
typedef
, чтобы свести к минимуму количество неприятных угловых скобок.

typedef time_put TimePutNarrow;

typedef time_get TimeGetNarrow;

// ...

const TimeGetNarrow& dateReader = use_facet(loc);

Процедура записи и чтения дат в локализованных форматах немного утомительна, однако, после того как вы один раз разберетесь в требованиях класса локализации

locale
, вы сможете это делать эффективно и быстро. Глава 5 полностью посвящена датам и временам, поэтому более детальные сведения по форматированию вывода дат и времен вы найдете в рецепте 5.2.

Смотри также

Глава 5 и рецепт 5.2.

13.4. Запись и чтение денежных значений

Проблема

Требуется записать в поток или прочитать из него денежное значение.

Решение

Используйте фасеты

money_put
и
money_get
для записи или чтения денежных значений, как показано в примере 13.6.

Пример 13.6. Запись и чтение денежных значений

#include 

#include 

#include 

#include 


using namespace std;


long double readMoney(istream& in, bool intl = false) {

 long double val;

 // Создать фасет для чтения

 const money_get& moneyReader =

  use_facet >(in.getloc());

 // Маркер конца

 istreambuf iterator end;

 // Переменная состояния для обнаружения ошибок

 ios_base::iostate state = 0;

 moneyReader.get(in, end, intl, in, state, val);

 // если что-то не получилось, будет установлен бит неудачного завершения

 if (state != 0 && !(state & ios_base::eofbit))

  throw "Couldn't read money!\n";

 return(val);

}


void writeMoney(ostream& out, long double val, bool intl = false) {

 // Создать фасет для записи

 const money_put& moneyWriter =

  use_facet >(out.getloc());

 // Записать данные в поток. Вызвать failed() (возвращает итератор

 // ostreambuf_iterator), чтобы можно было обнаружить ошибку.

 if (moneyWriter.put(out, intl, out, out.fill(), val).failed())

  throw "Couldn't write money!\n";

}


int main() {

 long double val = 0;

 float exchangeRate = 0.775434f; // Курс доллара по отношению к евро

 locale locEn("english");

 locale locFr("french");

 cout << "Dollars: ";

 cin.imbue(locEn);

 val = readMoney(cin, false);

 cout.imbue(locFr);

 // Установить флаг showbase, чтобы выводить символ валюты

 cout.setf(ios_base::showbase);

 cout << "Euros: ";

 writeMoney(cout, val = exchangeRate, true);

}

Если выполнить программу примера 13.6, можно получить следующий результат.

Dollars: $100

Euros: EUR77,54

Обсуждение

Фасеты

money_put
и
money_get
записывают форматированные денежные значения в поток и считывают их из потока. Они работают почти так же, как фасеты даты/времени и числовые фасеты, описанные в предыдущих рецептах. Стандарт требует, чтобы были их реализации для стандартных символов и расширенного набора символов, например
money_put
и
money_put
. Как и для других фасетов, функции записи и чтения многословны, но, применив их несколько раз, легко запоминаешь параметры.
money_get
и
money_put
используют класс
moneypunct
, содержащий информацию о форматировании.

Сначала рассмотрим запись денежных значений в поток. Отображение денежной суммы состоит из нескольких частей: знака валюты, знака плюс или минус, разделителя тысяч и десятичной точки. Все они, кроме десятичной точки, могут отсутствовать.

Вы создаете объект

money_put
с типом символа и локализацией следующим образом.

const money_put& moneyWriter =

 use_facet >(out.getloc());

Стандарт требует наличия версий как для

char
, так и для
wchar_t
. Разумно использовать локализацию потока, в который осуществляется запись, чтобы избежать несогласованности, возникающей при попытке синхронизации потока и объекта
money_put
. На следующем шаге вызовите метод put для записи денежного значения в поток вывода.

if (moneyWriter.put(out, // Итератор вывода

 intl,          // bool: использовать формат intl?

 out,           // ostream&

 out.fill(),       // использовать символ заполнителя

 val)           // денежное значение, тип long double

.failed()) throw "Couldn't write money!\n";

Функция

money_put::put
записывает денежное значение в переданный ей поток вывода, используя локализацию, с которой был создан объект
money_put
.
money_put::put
возвращает итератор
ostreambuf_iterator
, который ссылается на позицию за последним выведенным символом; этот итератор имеет функцию-член
failed
, позволяющую зафиксировать ситуацию, когда итератор оказывается испорченным.

Все параметры

money_put::put
не требуют дополнительных пояснений, кроме, возможно, второго (аргумент
intl
в примере). Он имеет тип
bool
и показывает, будет использоваться символ валюты (например, $, €) или трехбуквенное международное обозначение валюты (например, USD, EUR). Для использования символа валюты установите его в значение
false
, а для использования международного обозначения валюты — в значение
true
.

При записи денежных значений в поток вывода можно задавать некоторые параметры потока, которые управляют форматированием. Ниже описывается каждый параметр и объясняется его воздействие на вывод денежного значения.

ios_base::internal

Если при форматировании денежного значения задается пробел или пустое значение, будет использован символ заполнителя (а не пробел). Ниже при обсуждении

moneypunct
приводятся дополнительные сведения по шаблонам форматирования.

ios_base::left 
и
ios_base::right

Выравнивает денежное значение влево или вправо; при этом остальные позиции в пределах заданной ширины заполняются символом заполнителя (см. описание следующего параметра,

width
). Это удобно, потому что облегчает табуляцию денежного значения.

ios_base::width

Значения, выдаваемые функцией

money_put
, подчиняются стандартным правилам управления шириной поля потока. По умолчанию эти значения выравниваются влево. Если поле больше, чем размер значения, используется символ заполнителя, указанный при вызове функции
money_put
.

ios_base::showbase

Если этот флаг имеет значение «истина», символ валюты выводится, в противном случае он не выводится.

Как я говорил ранее, функции

money_get
и
money_put
используют класс
moneypunct
, в котором фактически хранится информация о форматировании. Вам не стоит беспокоиться о классе
moneypunct
, если вы не заняты реализацией стандартной библиотеки, но вы можете использовать его для исследования параметров форматирования, применяемых в конкретной локализации,
moneypunct
содержит такие сведения, как используемый символ валюты, символ, используемый в качестве десятичной точки, формат положительных и отрицательных значений и т.д. В примере 13.7 представлена короткая программа, печатающая информацию о формате денежных значений, который используется в заданной локализации.

Пример 13.7. Вывод информации о форматировании денежных значений

#include 

#include 

#include 


using namespace std;


string printPattern(moneypunct::pattern& pat) {

 string s(pat.field); // pat.field имеет тип char[4]

 string r;

 for (int i = 0; i < 4; ++i) {

 switch (s[i]) {

  case moneypunct::sign:

  r += "sign ";

  break;

  case moneypunct::none:

  r += "none ";

  break;

  case moneypunct::space:

  r += "space ";

  break;

  case moneypunct::value:

  r += "value ";

  break:

  case moneypunct::symbol:

  r += "symbol ";

  break;

  }

 }

 return(r);

}


int main() {

 locale loc("danish");

 const moneypunct& punct =

  use_facet >(loc),

 cout << "Decimal point: " << punct.decimal_point() << '\n'

  << "Thousands separator. " << punct.thousands_sep() << '\n'

  << "Currency symbol: " << punct.curr_symbol() << '\n'

  << "Positive sign: " << punct.positive_sign() << '\n'

  << "Negative sign: " << punct.negative_sign() << '\n'

  << "Fractional digits: " << punct.frac_digits() << '\n'

  << "Positive format: "

  << printPattern(punct pos_format()) << '\n'

  << "Negative format: "

  << printPattern(punct.neg_format()) << '\n';

 // Группировки описываются символьной строкой, но осмысленными

 // являются числовые значения символов, а не собственно символы

 string s = punct.grouping();

 for (string::iterator p = s.begin(); p != s.end(); ++p)

  cout << "Groups of: " << (int)*p << '\n';

}

Назначение большинства этих методов самоочевидно, но некоторые методы требуют дополнительных пояснений. Во-первых, метод

grouping
возвращает строку символов, которая интерпретируется как строка целочисленных значений. Каждый символ описывает свою группу цифр в числе, начиная с правой стороны числа. И если в какой-то позиции строки нет значения, то используется значение в предыдущей позиции. Другими словами, для стандартного американского формата в позиции 0 этой строки будет значение 3, что означает три цифры для группы с индексом 0. Поскольку других значений нет, все группы с индексом, большим нуля, должны также состоять из трех цифр.

pos_format
и
neg_format
возвращают объект типа
moneypunct::pattern
, который имеет член
field
типа
T[4]
, где
T
— символьный тип. Каждый элемент поля
field
содержит один из элементов перечисления
moneypunct::part
, который имеет пять возможных значений:
none
,
space
,
symbol
,
sign
и
value
. Строковое представление денежного значения состоит из четырех частей (т.е. массив с четырьмя элементами) Обычно части денежного значения образуют последовательность
symbol space sign value
(символ валюты пробел знак значение), что означало бы вывод, например, значения $ -32.00. Часто знак плюс заменяется пустой строкой, поскольку значение без знака обычно рассматривается как положительное значение. Признак отрицательного числа может содержать несколько символов, как, например, «
()
», и в этом случае первый символ выдается в части
symbol
формата отрицательного числа (
neg_format
), а другой символ выдается в конце, поэтому отрицательные числа могут иметь, например, такой вид: $(32.00).

Большую часть времени вам не придется беспокоиться по поводу получения информации о форматировании, содержащейся в

moneypunct
. Однако если вам необходимо выполнить большой объем формирования денежных значений в различных локализациях, то имеет смысл поэкспериментировать и познакомиться с особенностями форматирования в различных локализациях.

Смотри также

Рецепты 13.2 и 13.3.

13.5. Сортировка локализованных строк

Проблема

Имеется последовательность строк, содержащая символы не в коде ASCII, и требуется ее отсортировать с учетом местных особенностей.

Решение

В класс локализации встроена поддержка операций сравнения символов в заданной локализации путем перегрузки оператора

operator<
. При вызове любой стандартной функции, принимающей функтор сравнения, можно использовать в качестве такого функтора экземпляр класса локализации. (См. пример 13.8.)

Пример 13.8. Сортировка с учетом местных особенностей

#include 

#include 

#include 

#include 

#include 


using namespace std;


bool localelessThan(const string& s1, const string& s2) {

 const collate& col =

  use_facet >(locale()); // Использовать глобальную

                    // локализацию

 const char* pb1 = s1.data();

 const char* pb2 = s2.data();

 return (col.compare(pb1, pb1 + s1.size(),

  pb2, pb2 + s2.size()) < 0);

}


int main() (

 // Создать две строки, одна с немецким символом

 string s1 = "diät";

 string s2 = "dich";

 vector v;

 v.push_back(s1);

 v.push_back(s2);

 // Сортировать, не используя заданную локализацию, т.е. Применяя

 // правила текущей глобальной локализации

 sort(v.begin(), v.end());

 for (vector::const_iterator p = v.begin();

  p != v.end(); ++p)

  cout << *p << endl;

 // Установить в качестве глобальной немецкую локализацию и затем

 // сортировать

 locale::global(locale("german"));

 sort(v.begin(), v.end(), localelessThan);

 for (vector::const_iterator p = v.begin();

  p != v.end(); ++p)

  cout << *p << endl;

}

Первый вариант обеспечивает сортировку по коду ASCII, и поэтому результат будет выглядеть следующим образом.

dich

diät

Вторая сортировка использует правильный порядок букв немецкого алфавита, и поэтому результат будет противоположным.

diät

dich

Обсуждение

Сортировка усложняется, когда вы работаете с различными локализациями, но стандартная библиотека решает эту проблему. Фасет

collate
обеспечивает функцию-член
compare
, которая работает как
strcmp
: она возвращает значение -1, если первая строка меньше второй, значение 0, если они равны, и значение 1, если первая строка больше второй. В отличие от
strcmp
, функция
collate::compare
использует определенную в целевой локализации упорядоченность символов.

В примере 13.8 приводится функция

localeLessThan
, которая возвращает
True
, если согласно глобальной локализации первый аргумент меньше второго. Самым важным здесь моментом является вызов функции сравнения.

col.compare(pb1,  // Указатель на первый символ

 pb1 + s1.size(), // Указатель на позицию за последним символом

 pb2,

 pb2 + s2.size());

Выполнение примера 13.8 на вашем компьютере может дать результат как совпадающий, так и не совпадающий с приведенным мною, — это зависит от используемого в вашей реализации набора символов. Однако, если требуется обеспечить сравнение строк с учетом местных особенностей, вам следует использовать

collate::compare
. Конечно, стандарт не требует, чтобы реализация поддерживала какую-либо локализацию, кроме локализации «C», поэтому не забудьте протестировать все используемые вами локализации.

Загрузка...