ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Categories:

С++: ввод/вывод для файлов, UTF-8

В учебнике Лафоре на стр.551-553 рассказано про форматированный вывод данных в файл, получение данных из файла.

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

#include <fstream>                 // для файлового ввода/вывода
#include <iostream>
#include <string>
using namespace std;

int main()
{
    char ch = 'x';
    int j = 77;
    double d = 6.02;
    string str1 = "Kafka";         // строки без
    string str2 = "Proust";        // пробелов

    ofstream outfile("fdata.txt"); // создать объект класса ofstream

    outfile << ch                  // вставить (записать) данные
            << j
            << ' '                 // пробел между числами
            << d
            << str1
            << ' '                 // пробел между строками
            << str2;

    cout << "File written\n";

    return 0;
}

#include <fstream>                          // для файлового ввода/вывода
#include <iostream>
#include <string>
using namespace std;

int main()
{
    char ch;
    int j;
    double d;
    string str1;
    string str2;

    ifstream infile("fdata.txt");           // создать объект класса ifstream

    infile >> ch >> j >> d >> str1 >> str2; // извлечь (прочесть) из него данные

    cout << ch << endl                      // вывести данные на экран
         << j << endl
         << d << endl
         << str1 << endl
         << str2 << endl;

    return 0;
}

Привычно (подробности — тут) переделываю эти программы вместо работы с обычными символами (char) для работы с широкими символами (wchar_t), чтобы можно было работать в программе с Юникодом (в том числе с русскими буквами). То есть тип char меняем на wchar_t, string на wstring, ofstream на wofstream, ifstream на wifstream, cout на wcout, символьные и строковые литералы предваряем префиксом L.

Кстати, чтобы смена cout на wcout сработала, в Windows требуется переключить стандартный поток вывода в формат Юникода (в Линуксе это делается по-другому) с помощью функции _setmode, которая требует подключения заголовочных файлов <io.h> и <fcntl.h>.

После этого (в тестовых целях) меняю выводимый латинский символ на русский, одну из двух выводимых строк с латиницы на русские буквы. Название файла меняю с "fdata.txt" на "файл с данными.txt", а сообщение в консоль меняю с "File written" на "Файл записан".

#include <io.h>                      // для функции _setmode
#include <fcntl.h>                   // для константы _O_U16TEXT
#include <fstream>                   // для файлового ввода/вывода
#include <iostream>
#include <string>
using namespace std;

int main()
{
    // переключение стандартного потока вывода в формат Юникода
    _setmode(_fileno(stdout), _O_U16TEXT);
    
    wchar_t ch = L'ы';               // русская буква 'ы'
    int j = 77;
    double d = 6.02;
    wstring str1 = L"Kafka";         // строки без    // строка на латинице
    wstring str2 = L"Пруст";         // пробелов      // строка русскими буквами

    wofstream outfile(L"файл с данными.txt"); // создать объект класса wofstream

    outfile << ch                    // вставить (записать) данные
            << j
            << L' '                  // пробел между числами
            << d
            << str1
            << L' '                  // пробел между строками
            << str2;

    wcout << L"Файл записан\n";

    return 0;
}

#include <io.h>                             // для функции _setmode
#include <fcntl.h>                          // для константы _O_U16TEXT
#include <fstream>                          // для файлового ввода/вывода
#include <iostream>
#include <string>
using namespace std;

int main()
{
    // переключение стандартного потока вывода в формат Юникода
    _setmode(_fileno(stdout), _O_U16TEXT);

    wchar_t ch;
    int j;
    double d;
    wstring str1;
    wstring str2;

    wifstream infile(L"файл с данными.txt"); // создать объект класса wifstream

    infile >> ch >> j >> d >> str1 >> str2; // извлечь (прочесть) из него данные

    wcout << ch << endl                     // вывести данные на экран
          << j << endl
          << d << endl
          << str1 << endl
          << str2 << endl;

    return 0;
}

В результате сообщение на русском "Файл записан" в консоль вывелось успешно (это уже было отработано мной раньше), файл с названием русскими буквами "файл с данными.txt" создался успешно. Но, как только в выходной поток начинают поступать русские буквы, запись в файл не ведется, при этом никаких сообщений об ошибке не выдается.

Как оказалось, для вывода в файл (чтения из файла) широких символов недостаточно использования классов, работающих с широкими символами — wofstream и wifstream (я ожидал, что запись в файл будет произведена в кодировке UTF-16, исходя из того, что символы в Windows хранятся в кодировке UCS-2 (подмножество UTF-16), но этого не случилось).

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

Раз уж итоговую кодировку текстового файла, в который будем записывать данные, всё равно нужно будет указывать, то выбираю кодировку UTF-8, как самую популярную на сегодня для текстовых файлов. Для этой кодировки имеется специальный фасет класса codecvt_utf8.

Для использования указанных классов в программу должны быть включены соответствующие заголовочные файлы <locale> и <codecvt>.

Итак, в начале функции main обеих программ создадим константу, содержащую локаль с нужным фасетом для преобразования символов при сохранении в файл в кодировке UTF-8:
const locale utf8_locale = locale(locale(), new codecvt_utf8());

А после создания объекта-потока нужного класса свяжем наш поток с определенной ранее локалью, воспользовавшись методом ios::imbue:
outfile.imbue(utf8_locale);
для второй программы:
infile.imbue(utf8_locale);

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

Итоговые тексты программ: formato.cpp, formati.cpp.

Обсуждения по теме на портале «Stack Overflow»:
https://stackoverflow.com/questions/9859020/windows-unicode-c-stream-output-failure
https://stackoverflow.com/questions/4775437/read-unicode-utf-8-file-into-wstring
Tags: Английский язык, Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments