Search
Write a publication
Pull to refresh

Аспект числовой пунктуации в C++

Предположим, наше приложение работает с текстовым представлением чисел, и в этом представлении «десятичная точка» должна быть на самом деле запятой. В C++ достаточно обширные возможности манипуляции с локальными языковыми особенностями (т.н. locales), грех ими не воспользоваться.

Не вдаваясь в подробности, скажу, что за представление чисел отвечает «аспект» (facet) std::numpunct. Для модификации представления десятичной точки достаточно переопределить виртуальный метод do_decimal_point()
#include <locale><br/>
 <br/>
struct num_comma : std::numpunct<char><br/>
{<br/>
    char do_decimal_point () const { return ','; }<br/>
};

Дальнейшее использование этого аспекта зависит от характера работы приложения. Скажем, при однократном выводе достаточно «внедрить» (imbue) этот аспект в локаль потока:
void print_data (std::ostream& out, double x)<br/>
{<br/>
    std::locale old_loc = out.imbue (std::locale (out.getloc()new num_comma));<br/>
    out << x;<br/>
    // замечу, что такой подход не является exception-safe -- любое<br/>
    // исключение в теле функции оставит поток с внедрённой изменённой<br/>
    // локалью.  следует создать класс-обёртку, в конструкторе и<br/>
    // деструкторе которого производить внедрение и его отмену.<br/>
    out.imbue (old_loc);<br/>
}

Беспокоиться об удалении (delete) не следует, классы аспектов ведут подсчет ссылок (reference counting) и удаляются когда не остаётся локалей, ссылающихся на них, в данном случае по выходу из функции. Если эта функция вызывается многократно, имеет смысл создать новую локаль отдельно и «внедрять» её в поток перед началом вывода.
// создаём копию глобальной локали со своим "аспектом"<br/>
std::locale comma_locale (std::locale()new num_comma);<br/>
out.imbue (comma_locale);<br/>
// ...<br/>
out << some_number;

Для повсеместной смены интерпретации чисел, следует заменить «глобальную» локаль:
// где-то в районе инициализации приложения, скажем, в начале main()<br/>
std::locale comma_loc (std::locale()new num_comma);<br/>
std::locale::global (comma_loc);<br/>
 <br/>
// стандартные потоки уже инициализированы локалью по умолчанию,<br/>
// поэтому если предполагается работа с ними, их надо модифицировать:<br/>
std::cout.imbue (comma_loc);<br/>
std::cin.imbue (comma_loc);<br/>
 <br/>
// все вновь создаваемые потоки (std::fstream, std::stringstream) будут<br/>
// сами "подхватывать" нашу модифицированную глобальную локаль.<br/>
 

Замечу, что при этом представление чисел меняется не только при выводе, но также и при вводе, т.е. operator>> при вводе чисел будет рассматривать символ запятой как «десятичную точку». По этой причине следует быть осторожным, манипулируя глобальной локалью.

В заключение стоит упомянуть об ещё одном методе аспекта numpunct, позволяющем группировать в числах разряды, do_thousand_grouping:
struct num_sep : std::numpunct<char><br/>
{<br/>
    // разделитель разрядов<br/>
    char do_thousands_sep () const { return '\''; }<br/>
    // группировать разряды по 3<br/>
    std::string do_grouping () const { return "\003"; }<br/>
};<br/>
 <br/>
std::cout.imbue (std::locale (std::cout.getloc()new num_sep));<br/>
std::cout << 12345678 << std::endl;<br/>
// выведет 12'345'678<br/>
 

По материалам драфта ISO/IEC FDIS 14882 N3290 от 11 апреля 2011 года,
разделы [22.3] и [22.4.3]. См. также о языковых и культурных особенностях.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.