Почему китайские иероглифы превращаются в тарабарщину после прохождения через компилятор? - PullRequest
0 голосов
/ 14 ноября 2018

Итак, я пишу программу, которая превратит китайско-английский файл определения .txt в инструктор vocab, который проходит через CLI. Тем не менее, в Windows, когда я пытаюсь скомпилировать это в VS2017, это превращается в бред, и я не уверен, почему. Я думаю, что в Linux все работало нормально, но Windows, кажется, немного испортила его. Это как-то связано с таблицей кодирования в Windows? Я что-то пропустил? Я написал код в Linux, а также входной файл, но я попытался написать символы с помощью Windows IME и все еще имеет тот же результат. Я думаю, что картина говорит лучше всего для себя. Спасибо

Примечание: добавлен пример ввода / вывода, как он отображается в Windows, как и было запрошено. Также ввод UTF-8.

Образец ввода

人(rén),person
刀(dāo),knife
力(lì),power
又(yòu),right hand; again
口(kǒu),mouth

Пример вывода

人(rén),person
刀(dāo),knife
力(lì),power
又(yòu),right hand; again
口(kǒu),mouth
土(tǔ),earth

Изображение входного файла и вывода

1 Ответ

0 голосов
/ 14 ноября 2018

TL; DR: терминал Windows ненавидит Unicode. Вы можете обойти это, но это не красиво.

Ваши проблемы здесь не связаны с "char против wchar_t". На самом деле, нет ничего плохого в вашей программе! Проблемы возникают только тогда, когда текст проходит через cout и достигает терминала.


Вы, вероятно, привыкли думать о char как о «персонаже»; это распространенное (но понятное) заблуждение. В C / C ++ тип char обычно синонимичен с 8-битным целым числом и, таким образом, более точно описывается как байт .

Ваш текстовый файл chineseVocab.txt кодируется как UTF-8. Когда вы читаете этот файл через fstream, вы получаете строку байтов в кодировке UTF-8 .

В I / O нет такой вещи, как "символ" ; вы всегда передаете байтов в определенной кодировке . В вашем примере вы читаете байты в кодировке UTF-8 из дескриптора файла (fin).

Попробуйте запустить это, и вы должны увидеть одинаковые результаты на обеих платформах (Windows и Linux):

int main()
{
    fstream fin("chineseVocab.txt");
    string line;
    while (getline(fin, line))
    {
        cout << "Number of bytes in the line: " << dec << line.length() << endl;
        cout << "    ";
        for (char c : line)
        {
            // Here we need to trick the compiler into displaying this "char" as an integer:
            unsigned int byte = (unsigned char)c;
            cout << hex << byte << "  ";
        }
        cout << endl;
        cout << endl;
    }
    return 0;
}

Вот что я вижу в моей (Windows):

Number of bytes in the line: 16
    e4  ba  ba  28  72  c3  a9  6e  29  2c  70  65  72  73  6f  6e

Number of bytes in the line: 15
    e5  88  80  28  64  c4  81  6f  29  2c  6b  6e  69  66  65

Number of bytes in the line: 14
    e5  8a  9b  28  6c  c3  ac  29  2c  70  6f  77  65  72

Number of bytes in the line: 27
    e5  8f  88  28  79  c3  b2  75  29  2c  72  69  67  68  74  20  68  61  6e  64  3b  20  61  67  61  69  6e

Number of bytes in the line: 15
    e5  8f  a3  28  6b  c7  92  75  29  2c  6d  6f  75  74  68

Пока все хорошо.


Проблема начинается сейчас: вы хотите записать те же байты в кодировке UTF-8 в другой дескриптор файла (cout).

Дескриптор файла cout подключен к вашему CLI («терминал», «консоль», «оболочка», как вы хотите это называть). CLI читает байты из cout, а декодирует их в символы, чтобы их можно было отображать.

  • Терминалы Linux обычно настроены на использование декодера UTF-8 . Хорошие новости! Ваши байты имеют кодировку UTF-8 , поэтому декодер вашего терминала Linux соответствует кодировке текстового файла. Поэтому в терминале все выглядит хорошо.

  • Терминалы Windows, с другой стороны, обычно настроены на использование системно-зависимого декодера (у вас кодовая страница DOS 437 ). Плохие новости! Ваши байты имеют кодировку UTF-8 , поэтому декодер вашего терминала Windows не соответствует кодировке текстового файла. Вот почему все выглядит искаженным в терминале.


Ладно, как вы решаете эту проблему? К сожалению, я не смог найти какой-либо переносимый способ сделать это ... Вам нужно будет преобразовать вашу программу в версию для Linux и версию для Windows. В версии для Windows:

  1. Преобразование байтов UTF-8 в кодовые единицы UTF-16.
  2. Установить стандартный вывод в режим UTF-16.
  3. Запись в wcout вместо cout
  4. Скажите пользователям, чтобы они изменили свои терминалы на шрифт, поддерживающий китайские символы.

Вот код:

#include <fstream>
#include <iostream>
#include <string>

#include <windows.h>

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

using namespace std;

// Based on this article:
// https://msdn.microsoft.com/magazine/mt763237?f=255&MSPPError=-2147217396
wstring utf16FromUtf8(const string & utf8)
{
    std::wstring utf16;

    // Empty input --> empty output
    if (utf8.length() == 0)
        return utf16;

    // Reject the string if its bytes do not constitute valid UTF-8
    constexpr DWORD kFlags = MB_ERR_INVALID_CHARS;

    // Compute how many 16-bit code units are needed to store this string:
    const int nCodeUnits = ::MultiByteToWideChar(
        CP_UTF8,       // Source string is in UTF-8
        kFlags,        // Conversion flags
        utf8.data(),   // Source UTF-8 string pointer
        utf8.length(), // Length of the source UTF-8 string, in bytes
        nullptr,       // Unused - no conversion done in this step
        0              // Request size of destination buffer, in wchar_ts
    );

    // Invalid UTF-8 detected? Return empty string:
    if (!nCodeUnits)
        return utf16;

    // Allocate space for the UTF-16 code units:
    utf16.resize(nCodeUnits);

    // Convert from UTF-8 to UTF-16
    int result = ::MultiByteToWideChar(
        CP_UTF8,       // Source string is in UTF-8
        kFlags,        // Conversion flags
        utf8.data(),   // Source UTF-8 string pointer
        utf8.length(), // Length of source UTF-8 string, in bytes
        &utf16[0],     // Pointer to destination buffer
        nCodeUnits     // Size of destination buffer, in code units          
    );

    return utf16;
}

int main()
{
    // Based on this article:
    // https://blogs.msmvps.com/gdicanio/2017/08/22/printing-utf-8-text-to-the-windows-console/
    _setmode(_fileno(stdout), _O_U16TEXT);

    fstream fin("chineseVocab.txt");
    string line;
    while (getline(fin, line))
        wcout << utf16FromUtf8(line) << endl;
    return 0;
}

В моем терминале это в основном выглядит нормально после того, как я изменил шрифт на MS Gothic :

Most Chinese characters look OK

Некоторые символы все еще запутались, но это из-за того, что шрифт их не поддерживает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...