Re: Как вывести в файл строку wstring с кириллицей???
От: LaptevVV Россия  
Дата: 18.04.06 14:29
Оценка: 18 (5) +1
Здравствуйте, SvatSergio, Вы писали:

SS>Не могу никак разобраться, как корректно вывести строку wstring в файл. Строка содержит кириллицу.

SS>Не знаю в чем проблема: выводятся только латинские символы, кириллические совсем не выводятся.

Широкие потоки
Широкие (wide) потоки работают с широкими символами wchar_t [1-C.2.2.1]. Если sizeof(char) по стандарту равно 1, то размер wchar_t зависит от реализации [1 5.3.3/1]. Обычно размер sizeof(wchar_t) равен 2 (например, в системе Visual C++.NET 2003). Все стандартные классы объектно-ориентированной библиотеки реализованы и в «узком», и в широком виде. В стандартном пространстве имен std прописаны следующие операторы [1-27.2]:

typedef basic_streambuf<wchar_t>         wstreambuf;
typedef basic_istream<wchar_t>             wistream;
typedef basic_ostream<wchar_t>             wostream;
typedef basic_iostream<wchar_t>         wiostream;
typedef basic_stringbuf<wchar_t>         wstringbuf;
typedef basic_istringstream<wchar_t>    wistringstream;
typedef basic_ostringstream<wchar_t>     wostringstream;
typedef basic_stringstream<wchar_t>     wstringstream;
typedef basic_filebuf<wchar_t>             wfilebuf;
typedef basic_ifstream<wchar_t>         wifstream;
typedef basic_ofstream<wchar_t>         wofstream;
typedef basic_fstream<wchar_t>             wfstream;

При этом заголовки нужно подключать те же самые, что и для обычных узких потоков. Каждый «узкий» поток имеет соответствующую пару — широкий поток. Например, стандартные широкие потоки обозначаются так:
wcin — объект класса wistream, соответствующий стандартному вводу (stdin); по умолчанию связан с клавиатурой;
wcout — объект класса wostream, соответствующий стандартному выводу (stdout); по умолчанию связан с экраном;
wclog — объект класса wostream, соответствующий стандартному выводу для ошибок (stderr); по умолчанию связан с экраном;
wcerr — объект класса wostream, соответствующий стандартному выводу для ошибок (stderr); по умолчанию связан с экраном.
Для широких потоков перегружены операции ввода operator>> и вывода operator<<. В стандартных широких потоках для встроенных типов все средства форматирования, все методы работают точно так же, как и для узких потоков. Например, ввод целой переменной и вывод ее в шестнадцатиричном виде выполняется как обычно

int i;
wcin >> i;
wcout << hex << i << endl;


Ввод/вывод булевских выражений в виде true/false тоже не отличается от обычного

wcout << boolalpha << true << endl;


Широкие символьные константы объявляются следующим образом:
wchar_t ch = L'a';
wchar_t s[] = L”Hello world!”;
wstring ws = L”Hello, my students!”;

Вывод таких англоязычных констант сложностей не представляет:
wcout << ch << endl;
wcout << s << endl;
wcout << ws << endl;


ПРИМЕЧАНИЕ
В системе Visual C++.NET 2003 в широкие потоки прекрасно можно выводить и «узкие» англоязычные константы. Никаких ошибок не диагностируется, и строка выводится именно так, как написана. Однако «узкие» переменные типа string и char выводить нельзя — возикают ошибки трансляции.
Удивительно, но мне не удалось заставить работать стандартные широкие потоки в системе C++ Builder 6.
На экран просто ничего не выводится, хотя никаких ошибок ни при трансляции, ни при сборке не диагностируется.

Отдельный символ можно преобразовать в широкий, используя метод widen(). Обычно это метод используют для преобразования специальных символов, например

wcout << wcout.widen('\n') << endl;


Код широкого символа можно вывести с помощью явного преобразования типа, например

wcout << int(ch) << endl;


В данном случае ch не обязательно должен быть широким символом — выводится-то целое число.
Для вывода указателя на широкую строку его тоже необходимо приводить к void *

wchar_t *p = L"Hello, world!";
wcout << p << endl;                    // вывод строки
wcout << (void *)p << endl;         // вывод указателя


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

wcin.get(ch); 
сh = wcin.get(); 
wcin.read(&ch, 1);


В последнем случае нужно задавать именно единицу, так как вводится один символ.
Ввод строк из wcin тоже выполняется до пробела. Если мы хотим вводить с пробелами, то нужно использовать теже функции getline(), описанные выше:

wcin.getline(s,99);        // ввод в широкий символьный массива с пробелами
getline(wcin, ws);         // ввод в широкую строку с пробелами


Для ввода/вывода русских символов и строк необходимо установить русскоязычный локальный констекст (локаль — locale). Локаль [1-22.1.1] — это объект, инкапсулирующий национальные особенности внешнего представления данных вроде символа-разделителя целой и дробной части в числах, формата даты и тому подобные «мелочи» [1-22.1.1.1.1]. Заодно локаль устанавливает и нужную кодировку. Вообще-то локали устроены довольно сложно — в стандарте [1] этой теме посвящен раздел 22, занимающий почти 50 страниц, но мы рассмотрим только простейшие применения для работы с потоками.
Для работы с локалями нужно прописать в программе оператор

#include <locale>


После этого можно объявлять в программе объекты-локали., например

locale american;        // установки по умолчанию


Установки по умолчанию, естественно, американские. Но ни объявление локалей, ни стандартные американские установки нас не интересуют. Локальный контекст устанавливается для потока. Чтобы выводить русские символы на консоль, мы должны установить русскую локаль для wcout, а чтобы вводить русские символы нужно установить русскую локаль для wcin. Это делается с помощью метода потока imbue()[1-27.4.2.3], который имеет прототип

locale imbue(const locale& L);


Параметром служит объект-локаль, но его не обязательно объявлять в программе — можно использовать временный анонимный объект, например

wcout.imbue(locale("rus_rus.866"));


В данном случае русский контекст установлен для стандартного широкого потока wcout. Аргумент локали — строка «rus_rus.866». Эта строка называется именем локального контекста, и ее вид зависит от реализации . Это имя можно получить в виде строки типа string с помощью метода name() класса locale.

locale loc ("rus_rus.866");
cout << loc.name( ) << endl;


На экран выведется

Russian_Russian.866


Это полное имя локали, мы же писали сокращенное. Первое слово Russian означает язык — русский, второе Russian — страна, 866 — кодовая страница.
После установки русской локали на экран в консольное окно нормально выводятся русские сообщения, например

wstring s(L"Привет!");
wcout << L"Снова привет!" << endl;
wcout << s << endl;


Для ввода русских букв нужно точно таким же способом задать русский контекст для входного широкого потока wcin

wcin.imbue(locale("rus_rus.866"));


После этого можно будет вводить русские строки и символы обычными способами, которые мы рассматривали выше, например

wstring s;
getline(wcin, s);         // ввод широкой строки с пробелами
wchar_t g[100]; 
wcin.getline(g,99);        // ввод символьного массива с пробелами
wcin.get(ch);             // ввод широкого символа
ch = wcin.get();        // ввод широкого символа

Все введенные символы нормально выводятся на экран.
Текущий локалный контекст можно сохранить как объект-локаль с помощью метода getloc(), который имеет прототип

locale getloc() const;


Можно установить «классический» локальный контекст С с помощью метода

static const locale& classic( );


Можно установить некоторую локаль по умолчанию методом

static locale global(const locale& L);


Применять эти методы можно так:

locale current = wcout.getloc();    // сохранение текущей локали
wcout.imbue(locale::classic());        // установка классической локали
wcout.imbue(locale(“C”));            // тоже установка классической локали
wcout.imbue(current);                // восстановление прежней локали
locale::global(current);            // установка контекста по умолчанию


Не представляет особого труда перейти от обычных строковых потоков к широким. Перепишем пример из листинга 10.32, используя широкие потоки и русские символы (листинг 10.40).
Листинг 10.40. Широкие строковые потоки
#include <iostream>
#include <sstream>
#include <locale>
using namespace std;
int main()
{  char s[] = "computer", c = 'L';
   int   i = 35;
   float fp = 1.7320534f;
   wostringstream os;                    
// установка русской локали для строкового потока и для вывода
   os.imbue(locale("rus_rus.866"));
   wcout.imbue(locale("rus_rus.866"));
   /* Форматирование и вывод данных */
   os << L"Строка: "    << s << L"; " 
      << L"Символ: "    << c << L"; "
      << L"Целое: "     << i << L"; "
      << L"Дробное: "   << fp<< L"; " 
      << endl;
   wcout << L"Вывод:" << endl << os.str() << endl;
   wcout << L"Количество символов=" << os.str().length() << endl;
   int ch = getchar();                    // чтобы видеть результат
   return EXIT_SUCCESS;
}

Для того, чтобы осуществить вывод по-русски, мы использовали широкие строковые константы и установили русский контекст для строкового потока и для стандартного выводного потока. Программа выдает на экран
Вывод:
Строка: computer; Символ: L; Целое: 35; Дробное: 1,73205;
Количество символов=59
Обратите внимание, что ни строку «computer», ни символ ‘L’ мы не стали писать в широком виде — все прекрасно сработало и так.


Использовать широкие текстовые потоки для работы с текстовыми файлами не представляет труда.

Листинг 10.41. Текстовые файлы с широкими текстовыми потоками
#include <fstream>
#include <iostream>                    // требуется в Visual C++.NET 2003
#include <ctime>
using namespace std;
// функция копирования файлов
void filecopy (wifstream &in, wofstream &out)
{ wchar_t ch;
  while(in.get(ch))        // читать все символы, в том числе пробельные
    out.put(ch); 
}
int main()
{    srand((unsigned)time(NULL));                // датчик случайных чисел
    wofstream strm;                            // выходной поток-объект
    strm.open("c:/textfiles/woonumber.txt");    // открываем
    if (strm.is_open())                        // проверка открытия
    { for(int i = 0; i < 10; i++)             
        strm << rand()%10 << endl;            // выводим 10 чисел
      strm.close();                        // закрываем выходной поток-файл
      // суммирование чисел записанных в файле
      // открываем тот же текстовый файл для чтения 
      wifstream strm("c:/textfiles/woonumber.txt");
      if (strm)                             // проверка открытия    
      { int number, summa = 0, count = 0;
        while(strm >> number)            // ввод числа
        { ++count;                        // подсчет количества
          summa+=number;                // суммирование
        }
        wcout << summa << L"; " << count;    // вывод результатов
        strm.close();                    // закрываем поток-файл
      }
    }
    wifstream instrm ("c:/textfiles/woonumber.txt");
     wofstream outstrm("c:/textfiles/woonumber.new");
     if (instrm) filecopy(instrm, outstrm);        // копирование файлов
    instrm.close();                                // закрываем файлы
    outstrm.close();
    return EXIT_SUCCESS;
}

Сначала программа объявляет широкий поток strm, который при открытии связывается с текстовым файлом woonumber.txt. Затем в фал выводятся 10 случайных чисел, и поток закрывается. На этой стадии в нашем каталоге TextFiles можно обнаружить текстовый файл размером в 30 байт. Затем тот же файл открывается как входной, числа читаются и суммируются Затем существующий файл копируется с помощью функции копирования filecopy(), параметрами которой являются широкие потоки. На этой стадии программы в каталоге TextFiles можно увидеть 2 файла: woonumber.txt и woonumber.new.

Размеры файлов показывают, что символы-цифры выводятся в «широкий» файл в однобайтовом режиме . Проверим, как выводятся широкие русские и английские символы. Для этого создадим два широких выходных файловых потока. Для «чистоты» эксперимента установим для одного из них русскую локаль и выведем в поток русскую и английскую константу. Для второго потока локаль устанавливать не будем. Добавим в конец предыдущего примера следующие строки:

wofstream rstrm("c:/textfiles/woorussian.txt");
rstrm.imbue(locale("rus_rus.866"));
rstrm << L"Проверка вывода русских букв в файл" << endl;
rstrm << L"aaaaaaaa vvvvvv rrrrrrr cccc i ffff" << endl;
rstrm.close();
wofstream estrm("c:/textfiles/wooenglish.txt");
estrm << L"aaaaaaaa vvvvvv rrrrrrr cccc i ffff" << endl;
estrm << L"Проверка вывода русских букв в файл" << endl; // не выводятся
estrm.close();

И в «русский», и в «английский» поток символы выводятся в однобайтной кодировке. Если русская локаль не установлена, то русские буквы просто не выводятся в файл — и все, что после русских символов, в файл тоже не попадает!
Ничего не меняется, если мы используем вместо широкой константы-строки широкую переменную:

wstring ws = L"Проверка вывода русских букв в файл";
rstrm << ws << endl;


Размер русской строки в файле остается тем же самым.
Широкие потоки в функции filecopy() использовать совершенно необязательно — вполне корректно работают и обычные, открытые в двоичном режиме.

ifstream instrm ("c:/textfiles/woonumber.txt", ios::binary);
ofstream outstrm("c:/textfiles/woonumber.new", ios::binary);

При этом не происходит форматного преобразования символа «новая строка» ‘\n’. Тогда считывание и запись можно выполнить побайтно — независимо от того, сколько и каких байтов в файл записано. Поэтому функция копирования может быть такой:

void filecopy (ifstream &in, ofstream &out)
{ char ch;
  while(in.read(&ch, 1))    // читать все символы, в том числе пробельные
    out.write(&ch,1); 
}

или такой:

void filecopy (ifstream &in, ofstream &out)
{ char ch;
  ch = in.get();        
  while(!in.eof())        
  { out.put(ch);
    ch = in.get();            // читать все символы, в том числе пробельные
  }
}


ПРИМЕЧАНИЕ
Если широкие потоки, связанные с текстовыми файлами, работают правильно, то при связывании их с двоичными файлами возникают малопонятные проблемы. Во всяком случае, и в системе Visual C++.NET 2003, и в C++ Builder 6 методы read() и write() для широких потоков работают неправильно.

Ну, копирование можно делать в одну строку при помощи копирования буфера с помощью rdbuf() — эт чтоб не пинали, что учу не тому...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.