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

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

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

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

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

Функции SetConsoleOutputCP и SetConsoleCP

Для установления нужной кодировки можно применить функции SetConsoleOutputCP и SetConsoleCP из набора Windows API. Первая применяется к выводимым символам, вторая — к вводимым.

#include "windows.h" // подключаем для функций Windows API
#include <iostream>
using namespace std;
int main()
{
    char str[80]; // для вводимой строки

    // с помощью функций Windows API устанавливаем
    // нужную кодировку для вывода и ввода в командной строке
    SetConsoleOutputCP(1251);
    SetConsoleCP(1251);

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

    return 0;
}

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

В консоли, в которой запускается командная строка моей «Windows 7 Профессиональная», есть по умолчанию выбор из трех шрифтов: двух векторных (формата TrueType) Consolas и Lucida Console; и одного точечного (или растрового) Terminal. Юникодными являются только два первых, а точечный шрифт Terminal таковым не является.

По умолчанию у меня выбран точечный шрифт Terminal, а значит, наша программа выдаст кракозябры:

Шрифт по умолчанию в консоли можно поменять через настройки консоли, запустив командную строку (cmd.exe). Откроем меню консоли, щелкнув правой кнопкой мыши по заголовку окна консоли. В этом меню следует выбрать пункт «Свойства», в открывшемся окне свойств — выбрать вкладку «Шрифт». На этой вкладке можно выбрать шрифт из списка, его начертание (жирный или нормальный), размер символов шрифта из списка возможных размеров, а также посмотреть, как будет выглядеть выбранный шрифт с выбранными настройками. После выбора нужного шрифта и его настроек следует нажать кнопку «OK» внизу вкладки.

Выберем в консоли шрифт Lucida Console с нормальным начертанием и размером 12 (что означает размер символов в 7 пикселей в ширину и 12 в высоту). Снова запустим нашу программу. Теперь всё в порядке:

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

Кроме настройки шрифта вручную его можно настроить программно с помощью вызова соответствующих функций из набора Windows API. Добавим в нашу программу такую настройку (она сохраняется только на время работы программы):

#include "windows.h" // подключаем для функций Windows API
#include <iostream>
using namespace std;
int main()
{
    char str[80]; // для вводимой строки

    // заполняем структуру, представляющую шрифт,
    // нужными настройками
    CONSOLE_FONT_INFOEX Font = { sizeof(Font) };
    Font.dwFontSize.Y = 12; // достаточно определить высоту шрифта
    Font.FontWeight = FW_NORMAL; // начертание
    wcscpy_s(Font.FaceName, L"Lucida Console");

    // с помощью функций Windows API включаем в консоли
    // настроенный выше шрифт
    SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE,
                            &Font);

    // с помощью функций Windows API устанавливаем
    // нужную кодировку для вывода и ввода в командной строке
    SetConsoleOutputCP(1251);
    SetConsoleCP(1251);

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

    return 0;
}

Использование описанного здесь способа адекватного отображения русских букв в консольной программе имеет плюсом то, что при переключении языка системы (system locale) программа продолжает работать правильно, без кракозябр.

Если в программе потребуется использовать функции, результат работы которых зависит от локали, то, возможно, потребуется использование функции смены локали setlocale. Важно понимать, что совместное использование функции setlocale, включенной в стандарт C++, и пары описанных выше функций SetConsoleOutputCP и SetConsoleCP, входящих в набор Windows API, требует аккуратности, так как все они могут переключать кодировку консоли, а значит, могут влиять на работу друг друга. Но в принципе их совместное использование возможно, что продемонстрируем с помощью дополнения в нашу программу (шрифт Lucida Console в консоли выбран вручную):

#include "windows.h" // подключаем для функций Windows API
#include <iostream>
using namespace std;
int main()
{
    char str[80]; // для вводимой строки
    struct lconv * lc; // структура с настройками локали

    // с помощью функций Windows API устанавливаем
    // нужную кодировку для вывода и ввода в командной строке
    SetConsoleOutputCP(1251);
    SetConsoleCP(1251);

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

    // вывод обозначения валюты при локали "Английский (США)"
    cout << "Валюта: ";
    setlocale(LC_ALL, "en-US"); lc = localeconv();
    cout << lc->int_curr_symbol << "\n";

    // вывод обозначения валюты при локали "Русский (Россия)"
    setlocale(LC_ALL, "ru-RU"); lc = localeconv();
    cout << "Валюта: ";
    cout << lc->int_curr_symbol << "\n";

    return 0;
}

Результат работы этой программы:

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