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:
- Преобразование байтов UTF-8 в кодовые единицы UTF-16.
- Установить стандартный вывод в режим UTF-16.
- Запись в
wcout
вместо cout
- Скажите пользователям, чтобы они изменили свои терминалы на шрифт, поддерживающий китайские символы.
Вот код:
#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 :
Некоторые символы все еще запутались, но это из-за того, что шрифт их не поддерживает.