Проблемы с Unicode в С++, но не C

Я пытаюсь написать строки unicode на экране в С++ в Windows. Я изменил свой консольный шрифт на Lucida Console, и я установил вывод на CP_UTF8 aka 65001.

Я запускаю следующий код:

#include <stdio.h>  //notice this header file..
#include <windows.h>
#include <iostream>

int main()
{
    SetConsoleOutputCP(CP_UTF8);
    const char text[] = "Россия";
    printf("%s\n", text);
}

Он распечатывается просто отлично!

Однако, если я это сделаю:

#include <cstdio>  //the C++ version of the header..
#include <windows.h>
#include <iostream>

int main()
{
    SetConsoleOutputCP(CP_UTF8);
    const char text[] = "Россия";
    printf("%s\n", text);
}

он печатает: ������������

У меня нет понятия, почему..

Другое дело, когда я делаю:

#include <windows.h>
#include <iostream>

int main()
{
    std::uint32_t oldcodepage = GetConsoleOutputCP();
    SetConsoleOutputCP(CP_UTF8);

    std::string text = u8"Россия";
    std::cout<<text<<"\n";

    SetConsoleOutputCP(oldcodepage);
}

Я получаю тот же вывод, что и выше (нерабочий выход).

Используя printf на std::string, он отлично работает, хотя:

#include <stdio.h>
#include <windows.h>
#include <iostream>

int main()
{
    std::uint32_t oldcodepage = GetConsoleOutputCP();
    SetConsoleOutputCP(CP_UTF8);

    std::string text = u8"Россия";
    printf("%s\n", text.c_str());

    SetConsoleOutputCP(oldcodepage);
}

но только если я использую stdio.h и NOT cstdio.

Любые идеи, как я могу использовать std::cout? Как я могу использовать cstdio? Почему это происходит? Не cstdio просто версия С++ stdio.h?

EDIT: Я только что попробовал:

#include <iostream>
#include <io.h>
#include <fcntl.h>

int main()
{
    _setmode(_fileno(stdout), _O_U8TEXT);
    std::wcout << L"Россия" << std::endl;
}

и да, но это работает, но только если я использую std::wcout и wide strings. Мне бы очень хотелось избежать wide-strings, и единственным решением, которое я вижу до сих пор, является C-printf: l

Итак, вопрос все еще стоит.

Ответ 1

Хотя вы настроили консоль на вывод UTF-8, я подозреваю, что ваш компилятор обрабатывает строковые литералы как находящиеся в каком-то другом наборе символов. Я не знаю, почему компилятор C действует по-разному.

Хорошей новостью является то, что С++ 11 включает некоторую поддержку UTF-8 и что Microsoft реализовала соответствующие части стандарта. Код немного волосатый, но вы захотите заглянуть в std::wstring_convert (конвертирует в и из UTF-8) и <cuchar>.

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

Лично, когда мне нужно отлаживать что-то подобное, я часто направляю вывод в текстовый файл. Текстовые редакторы, похоже, работают с Unicode лучше, чем консоль Windows. В моем случае, я часто выводят коды правильно, но консоль настроена неправильно, так что я все еще заканчиваю печать мусора.


Я могу сказать, что это сработало для меня как в Linux (используя Clang), так и в Windows (с использованием GCC 4.7.3 и Clang 3.5, вам нужно добавить "std = С++ 11" в командную строку для компиляции с помощью GCC или Clang):

#include <cstdio>

int main()
{
    const char text[] = u8"Россия";
    std::printf("%s\n", text);
}

Использование Visual С++ (2012, но я считаю, что он также будет работать с 2010), мне пришлось использовать:

#include <codecvt>
#include <cstdio>
#include <locale>
#include <string>

int main()
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    auto text = converter.to_bytes(L"Россия");
    std::printf("%s\n", text.c_str());
}

Ответ 2

Если ваш файл закодирован как UTF-8, вы найдете длину строки 12. Запустите strlen от <string.h> (<cstring>) на нем, чтобы понять, что я имею в виду. Установка выходной кодовой страницы будет печатать байты точно так, как вы их видите.

То, что видит компилятор, эквивалентно следующему:

const char text[] = "\xd0\xa0\xd0\xbe\xd1\x81\xd1\x81\xd0\xb8\xd1\x8f";

Оберните его в широкую строку (в частности, wchar_t), и все будет не так хорошо.

Почему С++ обрабатывает его по-другому? Я не имею ни малейшего понятия, кроме, может быть, механизм, используемый кодом, лежащим в основе версии С++, несколько неосведомлен (например, std::cout счастливо выводит все, что вы хотите вслепую). Какая бы ни была причина, по-видимому, придерживаться C, является самым безопасным... что на самом деле неожиданно для меня, учитывая тот факт, что собственный C-компилятор C не может даже скомпилировать код C99.

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

Ответ 3

Более удивительно, что реализация C работает здесь, а не С++. char может содержать только один байт (числовые значения 0-255), и, таким образом, консоль должна отображать только символы ASCII.

C должен делать для вас магию - на самом деле он полагает, что эти байты вне диапазона ASCII (который 0-127) вы предоставляете из многобайтового символа Unicode (возможно, UTF-8). С++ просто отображает каждый байт вашего массива const char[], и поскольку байты UTF, обработанные отдельно, не имеют отдельных символов в вашем шрифте, он помещает эти. Обратите внимание, что вы назначаете 6 букв и получаете 12 вопросительных знаков.

Вы можете прочитать UTF-8 и ASCII, если вы хотите, но дело в том, что std::wstring и std::wcout - действительно лучшее решение, предназначенное для обработки символов большего размера.

(Если вы вообще не используете латинские символы, вы даже не сохраняете память, когда используете char -решенные решения, такие как const char[] и std::string вместо std::wstring. Все эти кириллические коды в любом случае, должны занимать некоторое пространство).