textpub.neocities.org . [ записки: обучаюсь C++ ]

Из моих записок про самообучение C++. Дата первой публикации: 28.01.2019.

Консольная программа выдает кракозябры 3.1

Второй способ решения проблемы

Начало можно прочитать в этой записи, вторая часть тут. Если во второй части мы подстраивали выводимый в командную строку Windows 7 (cmd.exe) текст под кодировку из группы кодировок OEM (866), то здесь будем настраивать из нашей программы кодировку командной строки под нужную нам кодировку из группы кодировок ANSI (1251). Это тоже можно сделать несколькими способами.

Локаль и кодировка

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

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

Перед запуском программ на языках Си и C++ система по умолчанию устанавливает минимальные региональные настройки, имеющие название «C». Вообще, разные локали имеют рекомендованные названия вроде «ru-RU» («Русский (Россия)») или «en-US» («Английский (США)»). Подробнее об этом можно прочесть в упоминавшейся ранее документации по Visual C++ по функции setlocale или тут.

Установка для нашей программы минимальной локали «C» подразумевает для командной строки кодировку из группы кодировок OEM (в случае языка системы «Русский (Россия)» это, как уже отмечалось, кодировка 866).

Переключением на другую локаль с помощью функции setlocale можно неявно поменять кодировку для командной строки. Например, установка русской локали с помощью вызова функции setlocale подразумевает для командной строки кодировку из группы кодировок ANSI (1251).

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

#include <iostream>
using namespace std;
int main()
{
    // при втором параметре NULL локаль не меняется,
    // функция лишь возвращает название текущей локали
    cout << setlocale(LC_ALL, NULL) << "\n";
    cout << "У каждой эпохи свой язык\n\n"; // ANSI 1251

    // при пустой строке во втором параметре локаль меняется
    // на локаль по умолчанию текущего пользователя Windows
    cout << setlocale(LC_ALL, "") << "\n";
    cout << "У каждой эпохи свой язык\n\n"; // ANSI 1251

    // возвращаем минимальную локаль
    cout << setlocale(LC_ALL, "C") << "\n";
    cout << "У каждой эпохи свой язык\n\n"; // ANSI 1251

    cout << setlocale(LC_ALL, "ru-RU") << "\n";
    cout << "У каждой эпохи свой язык\n"; // ANSI 1251

    return 0;
}

Результат ее работы:

Значения второго параметра функции setlocale «Russian_Russia.1251», «Russian», «ru-RU» приводят к одинаковому результату, но в документации рекомендуют вариант «ru-RU».

Результат вызова функции setlocale с пустой строкой во втором параметре setlocale(LC_ALL, "") зависит от настройки локали по умолчанию текущего пользователя Windows. Эта настройка может быть изменена вручную через «Панель управления — Часы, язык и регион — Язык и региональные стандарты — вкладка «Форматы» — выбрать значение из списка «Формат» (для «Windows 7 Профессиональная»). То есть если в этой настройке выбрать, к примеру, значение «Английский (США)», то вызов функции setlocale(LC_ALL, "") приведет к переключению на локаль «Английский (США)». Таким образом вызовы setlocale(LC_ALL, "") и setlocale(LC_ALL, "ru-RU") не являются аналогами.

Кстати, настройки «языка системы» (system locale) и «формата чисел, денежных единиц, даты и времени» (он же — «локаль пользователя» или «user locale») в случае необходимости можно проверить программно с помощью функций Windows API GetSystemDefaultLocaleName и GetUserDefaultLocaleName. Пример использования:

#include "windows.h" // подключаем для функций Windows API
#include <iostream>
using namespace std;
int main()
{
    WCHAR localName[LOCALE_NAME_MAX_LENGTH];
    GetSystemDefaultLocaleName(localName, LOCALE_NAME_MAX_LENGTH);
    wcout << localName << "\n";
    GetUserDefaultLocaleName(localName, LOCALE_NAME_MAX_LENGTH);
    wcout << localName << "\n";
    
    return 0;
}

У приведенного выше способа решения проблемы кракозябр с помощью переключения локали есть два недостатка. Во-первых, функция setlocale влияет на кодировку выводимого, но не вводимого текста. Пример программы:

#include <iostream>
using namespace std;
int main()
{
    char str[80]; // для входящей строки

    // Переключаем локаль на русскую
    setlocale(LC_ALL, "ru-RU");

    cout << "ANSI  : " << "У каждой эпохи свой язык\n"; // 1251
    cout << "In    : "; cin.getline(str, 80);
    cout << "Out   : " << str << "\n";

    return 0;
}

Результат ее работы:

Несмотря на то, что при вводе фразы «Я ввел эту фразу» буквы на экране отразились правильно, в переменную str эта фраза сохранилась в кодировке 866, но при выводе была интерпретирована в кодировке 1251 из-за переключения локали на русскую, при котором, как отмечалось выше, произошло переключение кодировки вывода на 1251.

Если в программе не предполагается работа с вводимыми значениями, то этот недостаток несущественен. Если же ввод значений предполагается, то для исправления этого недостатка можно воспользоваться упоминавшейся уже в предыдущей записи функцией OemToCharBuff. Например:

#include "windows.h" // для OemToCharBuff
#include <iostream>
using namespace std;
int main()
{
    char str[80]; // для входящей строки

    // Переключаем локаль на русскую
    setlocale(LC_ALL, "ru-RU");

    cout << "ANSI  : " << "У каждой эпохи свой язык\n"; // 1251
    cout << "In    : "; cin.getline(str, 80);
    OemToCharBuff(str, str, 80);
    cout << "Out   : " << str << "\n";

    return 0;
}

Второй недостаток способа с переключением локали заключается в том, что этот способ не работает при смене языка системы (system locale), к примеру, на «Английский (США)». Берем текст программы из предыдущего параграфа, собираем в исполняемый файл, меняем язык системы на «Английский (США)», запускаем исполняемый файл и получаем следующее:

Вместо символов получили одни знаки вопроса. Как с этим справиться, рассмотрим в следующих записях.

textpub.neocities.org . [ записки: обучаюсь C++ ]