В данной главе приводятся решения некоторых задач, которые обычно возникают при интернационализации программ С++. Обеспечение возможности работы программы в различных регионах (это обычно называется локализацией), как правило, требует решения двух задач: форматирования строк, воспринимаемых пользователем, в соответствии с местными соглашениями (например, даты, времени, денежных сумм и чисел) и обеспечения работы с различными символьными наборами. В настоящей главе рассматривается в основном первая проблема и только кратко вторая, потому что имеется немного возможностей по стандартизации поддержки различных символьных наборов из-за того, что такая поддержка во многом зависит от реализации.
Большая
часть программного обеспечения будет работать в странах, отличных от той, где они были написаны. Для поддержки этой практики стандартная библиотека C++ имеет несколько средств, способствующих написанию программного кода, предназначенного для работы в различных странах. Однако они спроектированы не так. как многие другие средства стандартной библиотеки, например строки, файловый ввод-вывод, контейнеры, алгоритмы и т.п. Например, класс, представляющий локализацию, имеет имя
locale
и содержится в заголовочном файле
<lосаlе>
. Класс
locale
предоставляет средства для записи и чтения потоков с применением специфичного для данной местности форматирования и получения таких сведений о локализации, как, например, ее символ валюты или формат даты. Однако стандартом предусматривается обеспечение только одной локализации, и этой локализацией является «C»-локализация, или классическая локализация. Классическая локализация использует стандарт ANSI С: принятые в американском варианте английского языка соглашения по форматированию и 7-битовой код ASCII. И от реализации зависит, будут ли обеспечены экземпляры locale для других языков и регионов.
Заголовочный файл
<locale>
имеет три основные части. Во-первых, это класс
locale
(локализация). Он инкапсулирует все поддерживаемые в C++ особенности локализованного поведения и обеспечивает точки входа для получения различной информации о локализации, необходимой для выполнения локализованного форматирования. Во-вторых, самыми маленькими элементами локализации и конкретными классами, с которыми вы будете работать, являются классы, называемые фасетами (facets). Примером фасета является, например, класс
time_put
, предназначенный для записи даты в поток. В-третьих, каждый фасет принадлежит к некоторой категории, которая объединяет связанные фасеты в одну группу. Например, имеются числовая, временная и денежная категории (только что упомянутый мною фасет
time_put
относится к временной категории). Я кратко описываю категории в данной главе, однако действительную пользу они приносят при осуществлении более изощренных действий, связанных с локализацией.
Каждая программа на C++ имеет, по крайней мере, одну локализацию, называемую глобальной локализацией (она часто реализуется как глобальный статический объект). По умолчанию это будет классическая локализация «С», пока вы не измените ее на что- нибудь другое. Один из конструкторов
locale
позволяет инстанцировать локализацию, предпочитаемую пользователем, хотя точное определение «предпочитаемой» пользователем локализации полностью зависит от реализации.
В большинстве случаев локализации используются при записи и чтении потоков. Это является основной темой настоящей главы.
13.1. Жесткое кодирование строк в коде Unicode
Проблема
Требуется в исходном файле жестко закодировать строки в коде Unicode, т.е. используя расширенный набор символов.
Решение
Начинайте строку с префикса
L
и затем вводите символы в своем редакторе исходных текстов, как вы это обычно делаете при написании строк, или используйте шестнадцатеричные значения, представляющие нужный вам символ в коде Unicode. Пример 13.1 демонстрирует оба способа кодирования таких строк.
Пример 13.1. Жесткое кодирование строк в коде Unicode
#include <iostream>
#include <fstream>
#include <string>
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
определяется в
<string>
как последовательность символов типа
wchar_t
, подобно тому как класс
string
представляет собой последовательность символов типа
char
. (Строго говоря, тип
wstring
определяется, конечно, с помощью
typedef
как
basic_string<wchar_t>
.)
Самый простой способ ввода символов в коде 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. Запись чисел с использованием локализованного форматирования