Использование Qt-2.2.x в русскоязычных программах
Автор : Сукиязов Сергей Александрович
Эта статья содержит информацию об одной из популярных библиотек для организации графического пользовательского интерфейса (GUI) - Qt Toolkit, разработаной норвежской фирмой Troll Tech.
Библиотека Qt представляет собой законченное и глубоко проработанное многоплатформенное объектноориентированное окружение для разработки GUI-приложений с использованием языка C++. Qt также хорошо интегрируется с библиотеками OpenGL/Mesa 3D.
				Qt является бесплатной (free)
				библиотекой для разработки бесплатного программного обеспечения
				(freeware) в X Window System. Она
				включает в себя полный исходный код X-версии библиотеки
				и make-файлы для операционных систем Linux,
				Solaris, SunOS, 
				FreeBSD и др. Эта редакция (free)
				Qt может модифицироваться и 
				распространяться с соблюдением условий перечисленных в файле 
				LICENSE.QPL.
			
В настоящее время Qt используется в сотнях проектов по разработке программного обеспечения по всему миру, включая популярную оболочку K Desktop Environment. Для более полной информации смотрите ссылку http://www.trolltech.com/qtprogs.html.
Qt можно загрузить по адресу http://www.trolltech.com/dl/ или через анонимный FTP с сервера ftp.trolltech.com. На этом сервере также доступны нестабильные версии, находящиеся на стадии разработки, в виде ежедневных "снапшотов".
Qt содержит замечательную документацию: более 750 страниц в формате Postscript и HTML. Документация также доступна через WEB: http://doc.trolltech.com/.
Qt является полностью объектноориентированной библиотекой. Все "виджеты" представляют собой C++ объекты, и, используя наследование, создание новых "виджетов" получается простым и естественным.
В рамках этой статьи мы не будем подробно разбирать все детали программирования с использованием библиотеки Qt. Как говорилось выше Qt содержит подробную документацию и множество примеров. В этой статье мы остановимся на проблемах корректной локализации (интернационализации) программ, разработанных с использованием Qt, и попытаемся выработать некоторые советы по использованию классов библиотеки Qt, которые помогут избежать проблем с интернационализацией программ.
Т.к. библиотека Qt для представления текстовых данных использует UNICODE, то при некорректном преобразовании текстовых данных из однобайтовых кодировок в UNICODE и проявляются проблемы с отображением национальных символов.
Внешне эти проблемы выглядят следующим образом: вместо национальных символов (коды более 127) выводится символ '?' или вместо строки, содержащей национальные UNICODE-символы (коды более U+00FF), в результате преобразований получаются пустые однобайтовые строки.
Может сложиться впечатление, что в Qt вопрос преобразования из однобайтовых строк к UNICODE-строкам недостаточно продуман. На самом деле это не так. В Qt преобразование строк достачно хорошо продуманно и описано в документации. К единственному недостатку докуметации можно отнести тот факт, что документацию писали в большинстве своем англоязычные программисты и, соответственно, вопросам использования национальных UNICODE-символов уделено незначительное внимание.
Дело в том, что англоязычные программисты используют кодировку ISO-8859-1 (или US-ASCII), коды символов которой совпадают с UNICODE-кодами этих символов. В этом случае преобразование из однобайтовой строки в UNICODE-строку сводится к простому расширению однобайтового значения до двухбайтового (просто в сташий байт заносится значение 0). Для национальных символов преобразование не столь тривиально: русская буква 'А' имеет код в кодировке ISO-8859-5 равный 0xB0, в кодировке UNICODE - код равный U+0410. В результате простого расширения однобайтового значения русская буква 'А' получит UNICODE код U+00B0 вместо U+0410, что далеко не одно и тоже.
Далее мы проанализируем причины приводящие к возникновению этих ошибок и разберем несколько советов по их устранению.
В библиотеке Qt, как говорилось выше, внутренний формат строк - UNICODE. Поэтому почти все методы и функции библиотеки Qt в качестве своих фактических параметров желают иметь именно UNICODE строки.
				Для хранения кажого символа в кодировке UNICODE в Qt
				отводится два байта. Для представления UNICODE символа в
				Qt используется класс
				QChar. Полное и подробное описание конструкторов, методов
				и операторов этого класса можно посмотреть в документации по библиотеке
				Qt. Преобразование по умолчанию
				из одобайтового символа (C тип char) в UNICODE символ
				(Qt тип QChar)
				выполняется простым расширением значения. Т.е. русская буква 'А' (код в
				ISO-8859-5 - 0xB0)	получает UNICODE код U+00B0.
			
				Для представления UNICODE строк в	Qt 
				используется класс QString, который в общем представляет
				собой массив символов типа QChar. Полное и подробное описание конструкторов, методов
				и операторов этого класса также можно посмотреть в документации по библиотеке
				Qt. Преобразование по умолчанию
				из одобайтовой строки (C тип char *) в UNICODE строку
				(Qt тип QString) также
				выполняется простым расширением значения. Т.е. русская буква 'А' (код в
				ISO-8859-5 - 0xB0 )	получает UNICODE код U+00B0.
			
				Для хранения однобайтовых строк, в библиотеке Qt,
				используется класс QCString, наследуемый от класса 
				QByteArray. В этом классе строки представлены как 
				массивы однобайтовых символов.
			
				Для корректного преобразования из однобайтовых строк в UNICODE-строки и
				обратно с	учетом особенностей национальных кодировок, в 
				Qt используется класс QTextCodec.
				Полное и подробное описание конструкторов, методов 	и операторов этого 
				класса также можно посмотреть в документации по библиотеке Qt.
				Мы только остановимся на некоторых наиболее важных методах этого класса:
				
				static QTextCodec* QTextCodec::codecForName(const char* hint, int accuracy=0);
				
				QTextCodec, и 
				возвращает указатель на объект QTextCodec, имя которого
				наиболее совпадает с именем, переданным через параметр hint.
				Если кодек (Codec) не найден возвращает NULL. Параметр accuracy
				определяет точность совпадения имени кодека, значение 0 определяет точное 
				совпадение.
				
				static QTextCodec* QTextCodec::codecForLocale();
				
				
				virtual QString QTextCodec::toUnicode(const char* chars, int len) const;
				
				
				virtual QCString QTextCodec::fromUnicode(const QString& uc, int& lenInOut) const;
				
				QChar)	начиная с первого символа строки uc.
				Возвращает объект типа QCString, и также возвращает длинну
				результата в lenInOut.
				
				QCString QTextCodec::fromUnicode(const QString& uc) const;
				QString QTextCodec::toUnicode(const char* chars) const;
				
			
			
				В качестве примера использования класса QTextCodec для
				преобразования строк, можно привести следующий пример (предполагается 
				что используется локаль "ru" и она настроена):
				
				
					
			
  1: char       *str = "Привет мир!!!"
  2: QTextCodec *c;
  3: QString    wstr;
  4: 
  5: wstr = str;
  6: qWarning("%s", (const char *)wstr.local8Bit() );
  7: //^^ Будет напечатано : ?????? ???!!!
  8: qWarning("%s", wstr.latin1() );
  9: //^^ Будет напечатано : Привет мир!!!
 10: 
 11: c = QTextCodec::codecForLocale(); 
 12: // или c = QTextCodec::codecForName("ISO-8859-5");
 13: if ( c )
 14:   {
 15:     wstr = c->toUnicode(str);
 16:     qWarning("%s", (const char *)c->fromUnicode(wstr) );
 17:     //^^ Будет напечатано : Привет мир!!!
 18:     //   c->fromUnicode(wstr) эквивалентно wstr.local8Bit()
 19:     qWarning("%s", (const char *)wstr.latin1() );
 20:     //^^ Будет напечатана пустая строка
 21:   }
 22: else
 23:   {
 24:     qWarning("Кодек не найден");
 25:   }
					
				
Разберем подробнее приведенный пример.
				В строке 5 используется преобразование по умолчанию (вызывается конструктор
				QString( const char * ) ) из одобайтовой строки (C тип 
				char *) в UNICODE строку QString - т.е. 
				простое расширением значения. В этом случае русская буква 'А' (код в 
				ISO-8859-5 - 0xB0) получает UNICODE код U+00B0.
			
				В строке 6 производится преобразование из строки QString в
				однобайтовую строку с использованикм метода QCString QString::local8Bit().
				Этот метод выполняет преобразование с учетом установок локали, и 
				подробнее это метод будет рассмотрен ниже. Т.к. для кириллицы в 
				UNICODE не определены символы с кодами от U+0080 до U+00FF, то в 
				результате преобразования вместо русских букв получаются символы '?'.
				То же самое происходит и с другими языками, для которых UNICODE коды
				символов больше U+00FF.
			
				В строке 8 производится преобразование из строки QString в
				однобайтовую строку с использованикм метода const char *QString::latin1().
				Этот метод выполняет преобразование простым сжатием двухбайтового значения 
				до однобайтового. При сжатии для значений, старший октет которых равен 0,
				берется значение младшего октета, а для значений, старший октет которых 
				отличен от нуля, берется значение 0 (Октет - последовательность из 8 бит
				или байт. На различных платформах размер байта может отличаться.). 
				Подробнее это метод будет рассмотрен ниже. Т.к. при отбросе старшего
				нулевого октета для U+00B0 получается 0xB0 (код русской буквы 'А' в 
				ISO-8859-5) то в строке 7 русские символы выводятся правильно.
			
				В строке 11 мы находим кодек, который будет использоваться по умолчанию 
				для текущих установок локали. Если метод QTextCodec::codecForLocale()
				вернет ненулевое значение то кодек найден и можно выполнять преобразования.
			
В строке 15 используется преобразование с помощью найденного кодека. В этом случае русская буква 'А' (код в ISO-8859-5 - 0xB0) получает UNICODE код U+0410. Поэтому в строке 16 будут корректно напечатаны русские буквы. А строке 19 при отбросе старшего не нулевого октета для U+0410 (старший октет 0x04) получается значение 0x00 (признак конца строки в языке C) и, соответственно, выводится пустая строка.
				Для корректного преобразования из однобайтовых строк в UNICODE-строки и
				обратно с	учетом особенностей национальных кодировок кроме класса
				QTextCodec, в классе QString помимо конструкторов
				введены специалные методы, которые позволяют конструировать объекты 
				QString с учетом особенностей определенных однбайтовых
				кодировок. Это следующие методы:
				
				const char* latin1() const;
				
				QString представлена в кодировке ISO-8859-1.
				
				static QString fromLatin1(const char*, int len=-1);
				
				char *) в UNICODE строку QString кодек
				для кодировки ISO-8859-1. Т.о. преобразование выполняется простым 
				расширением значения. В этом случае русская буква 'А' (код в ISO-8859-5
				- 0xB0) получает UNICODE код U+00B0. Если задан параметр len
				то преобразуются только len символов исходной строки.
				
				QCString utf8() const;
				
				
				static QString fromUtf8(const char*, int len=-1);
				
				char *) в UNICODE строку QString кодек
				для кодировки UTF-8. Если задан параметр len то преобразуются
				только len символов исходной строки.
				
				QCString local8Bit() const;
				
				QString представлена в "чистом" UNICODE. 
				Если для текущей локали не найден подходящий кодек то, используется 
				кодек для кодировки ISO-8859-1.
				
				static QString fromLocal8Bit(const char*, int len=-1);
				
				char *) в UNICODE строку QString кодек
				для текущих установок локали. Т.о. преобразование выполняется с учетом
				особенностей кодировки, определяемой установками текущей локали. Например,
				если в текущей локали используется кодировка ISO-8859-5, то будет
				использован кодек для этой кодировки. В этом случае русская буква 'А' 
				(код в ISO-8859-5 - 0xB0) получает UNICODE код U+0410. Если задан 
				параметр len, то преобразуются только len символов исходной строки. Если для
				текущей локали не найден подходящий кодек, то используется кодек для
				кодировки ISO-8859-1.
				
				C использованием методов класса QString приведенный выше 
				пример можно изменить следующим образом:
				
				
				
			
  1: char       *str = "Привет мир!!!"
  2: QTextCodec *c;
  3: QString    wstr;
  4: 
  5: wstr = str;
  6: qWarning("%s", (const char *)wstr.local8Bit() );
  7: //^^ Будет напечатано : ?????? ???!!!
  8: qWarning("%s", wstr.latin1() );
  9: //^^ Будет напечатано : Привет мир!!!
 10: 
 11: wstr = QString::fromLocal8Bit(str);
 12: qWarning("%s", (const char *)c->fromUnicode(wstr) );
 13: //^^ Будет напечатано : Привет мир!!!
 14: //   c->fromUnicode(wstr) еквивалентно wstr.local8Bit()
 15: qWarning("%s", (const char *)wstr.latin1() );
 16: //^^ Будет напечатана пустая строка
					
				
				Разработчики Qt реомендуют
				использовать перечисленные выше методы для конструирования UNICODE
				строк QString и пребразования из QString
				в однобайтовые строки, вместо преобразований по умолчанию. 
			
				Явный вызов вышеперечисленных методов, для конструирования строк типа 
				QString, позволит быть уверенным в том, что в программе
				используется "чистый" UNICODE.
			
Т.к. большинство программ написаны англоязычными программистами, которые используют в качестве основной кодировку ISO-8859-1, в таких программах мало внимания уделяется преобразованиям символов. Как уже говорилось выше, для кодировок ISO-8859-1 (LATIN1) и US-ASCII, преобразования из однобайтовой строки в UNICODE и обратно не имеют особого значения, т.к. коды символов (значения кодов) в этих кодировках совпадают. Например, латинская буква 'A' имеет код в кодировке ISO-8859-1 (US-ASCII) 0x65 и в UNICODE код латинской буквы 'A' будет равным U+0065. Т.е. <Код символа ISO-8859-1>==<Код символа UNICODE>. Соответственно, для кодировок ISO-8859-1 (LATIN1) и US-ASCII, преобразование простым расширением/сжатием значения выполняется корректно и программа работает с "чистым" UNICODE.
Более того, англоязычные программисты не могут заметить и, соответственно, исправить такие ошибки, т.к. в большинстве случаев для отладки программ они используют тексты, содержащие латинские символы, и локаль с кодировкой ISO-8859-1. Нереально представить себе ситуацию, когда каждый программист, например, из Великобритании для отладки программы устанавливает у себя русские шрифты и настраивает русскую локаль, а ведь кроме русского языка существует еще множество других языков.
Поэтому, чтобы избавить себя и пользователей программы от проблем с локализацией, нужно более четко определить ситуации когда возникают эти ошибки и сформулировать некоторые принципы написания кода программы. Все рекомендации, которые будут сформулированны ниже, в общем виде, применимы не только к программам использующим библиотеку Qt, но и ко всем другим программам которые хранят и обрабатывают строки в формате UNICODE или WideChar.
				Чаще всего ошибки с преобразованием национальных символов в библиотеке 
				Qt возникают из-за использования
				неявного преобразования типов к типу QString. Неприятность
				этих ошибок заключается в том, что на этапе компиляции не выдается никаких
				сообщений и предупреждений, т.к. с точки зрения синтаксиса и правил языка
				программирования ошибки действительно нет. Подобная ошибка проявляется только
				на этапе выполнения программы, причем к исключительной ситуации не приводит.
			
В каких местах программы возникают такие ошибки ?
				Наиболее частое место возникновения этой ошибки - преобразование типа
				при передаче параметров в процедуру или функцию. Например, есть некоторая
				функция, которая в качестве параметра требует строку типа QString,
				а при вызове этой функции ей в качестве фактического параметра передается
				обчная C-like строка:
				
				
					
			
  1: #include <qstring.h>
  2:
  3: void foo( const QString & str )
  4:   {
  5:     qWarning("%s",(const char *)str.local8Bit()); 
  6:   }
  7: 
  8: int main( int argc, char *argv[] )  
  9:  {
 10:    foo( "Привет мир!!!" );
 11:  //^^ Будет напечатана строка : ?????? ???!!!
 12:  }
					
				
				В этом примере, в строке 10, выполняется неявное преобразование типов
				из const char * в QString с использованием
				конструктора QString::QString( const char * ). В результате
				такого преобразования, как говорилось выше, происходит простое расширение
				однобайтового значения до двухбайтового. Т.е. строка "Привет мир!!!"
				в формате UNICODE будет иметь следующее значение:
				
				
				
				"U+00BF,U+00E0,U+00D8,U+00D2,U+00D5,U+00E2,U+0020,U+00DC,
				U+00D8,U+00E0,U+0021,U+0021,U+0021"
				
				
				
				
				"U+041F,U+0440,U+0438,U+0432,U+0435,U+0442,U+0020,
				U+043C,U+0438,U+0440,U+0021,U+0021,U+0021"
				
				
				
				В качестве другого примера можно привести функцию, которая в качестве 
				параметра требует C-like строку, а при вызове этой функции ей в качестве
				фактического параметра передается строка типа QString:
				
				
					
			
  1: #include <qstring.h>
  2:
  3: void foo( const char * str )
  4:   {
  5:     qWarning("%s",str); 
  6:   }
  7: 
  8: int main( int argc, char *argv[] )  
  9:  {
 10:    QString qstr = QString::fromLocal8Bit( "Привет мир!!!" );	
 11:  //^^ Инициализируем qstr в "чистый" UNICODE
 12:
 13:    foo( qstr );
 14:  //^^ Будет напечатана пустая строка
 15:  }
					
				
				В этом примере в строке 13 выполняется неявное преобразование типов
				из QString в const char * с использованием
				оператора преобразования типа QString::operator const char *() const;,
				который определен в файле qstring.h как:
				
				
					
				
  1: class Q_EXPORT QString
  2: {
  3: ... 
  4:     operator const char *() const { return latin1(); }
  5: ...
  6: }; 
					
				
				Как видно из определения оператора QString::operator const char *() const,
				для преобразования типов будет вызван метод const char* QString::latin1() const.
				Этот метод для национальных символов с кодами большими U+00FF возвращает
				нулевое значение, т.о. функция void foo( const char * str ),
				из примера выше, в качестве параметра получит C-like строку, первый символ
				которой равен '\0', что, в свою очередь, является признаком конца строки
				для  C-like строк. Поэтому в результате выполнения строки 5 будет напечатана
				пустая строка.
			
Из-за того, что в этих примерах в той или иной степени используется "не чистый" UNICODE, и возникают проблемы с обработкой национальных символов. Впрочем, если мы установим локаль в "en_US" (export LC_ALL=en_US), то приведенные выше примеры будут корректно выводить русские символы.
				Этот тип ошибок очень часто встречается когда, например, из программы 
				использующей библиотеку Qt, 
				обращаются к системным вызовам ядра (например fopen(..), open(..), mkdir(..),
				access(..), unlink(..) и т.д.), которые требуют в качестве своих параметров
				C-like строку, а при вызове этой функции ей в качестве фактических 
				параметров передаеются строки типа QString. В случае 
				вызова функций fopen(..), open(..), mkdir(..), access(..), unlink(..),
				из-за этой ошибки невозможно работать с файлами и директориями, содержащими
				национальные символы в именах.
			
				Небольшое замечание: Применение метода QString::fromLatin1(...)
				для преобразования однобайтовой строки в UNICODE-строку, оправдано исключительно
				в случае, если однобайтовая строка содержит только символы в кодировке
				ISO-8859-1. В примерах строки 2 и 5, действительно строки "Hello world!!!"
				и " " содержат только ISO-8859-1 символы. В строке 1, строка
				"Привет мир!!!" содержит только русские символы, поэтому мы 
				используем метод QString::fromLocal8Bit(...). В строке 6 
				переменная buff может содержать не только латинские символы,
				поэтому наиболее безопасное решение - применять метод QString::fromLocal8Bit(...).
				Это даст гарантию того, что все символы из строки buff будут
				корректно преобразованы в UNICODE.
			
				В некоторых случаях бывает невозможно использовать ключи -DQT_NO_CAST_ASCII
				и -DQT_NO_ASCII_CAST при компиляции программы. Например,
				если вы используете другую билиотеку, базирующуюся на Qt,
				которая разрабатывалась без использования ключей -DQT_NO_CAST_ASCII
				и -DQT_NO_ASCII_CAST при компиляции. В этом случае,
				вам нужно придерживаться советов, рассмотренных ниже, они помогут избежать
				ошибок. Но если вы начинаете разрабатывать свою собственную программу с
				использованием библиотеки Qt, то
				обязательно используйте ключи -DQT_NO_CAST_ASCII
				и -DQT_NO_ASCII_CAST при компиляции программы.
			
				При проектировании программы нужно обязательно определить в каком формате
				будут храниться и обрабатываться строки: в однобайтовом или UNICODE формате.
				И в	дальнешем, при программировании, стараться внутри функций и классов не
				смешивать однобайтовые и UNICODE строки. Т.е. если некоторый класс содержит
				несколько членов данных с текстовой информацией, нужно стараться чтобы
				все эти члены данные были одного типа, а для доступа к этим членам данным
				использовать перегружаемые методы, которые, в свою очередь, гарантируют
				корректность преобразования, даже если при компиляции не заданы ключи
				-DQT_NO_CAST_ASCII и -DQT_NO_ASCII_CAST. 
				Например:
				
				
					
			
  1:  class Foo
  2:    {
  3:      public:
  4:        Foo( const Foo & foo )
  5:          {
  6:            m_d1 = foo.m_d1;
  7:            m_d2 = foo.m_d2;
  8:          };
  9:        Foo( const QString & d1, const QString & d2 = QString::null )
 10:          {
 11:            m_d1 = d1;
 12:            m_d2 = d2;
 13:          };
 14:        Foo( const char * d1, const char * d2 = NULL )
 15:          {
 16:            m_d1 = QString::fromLocal8Bit(d1);
 17:            m_d2 = d2 ? QString::fromLocal8Bit(d2) : QString::null;
 18:          };
 19:        vod setD1( const QString & d1 )
 20:          {
 21:            m_d1 = d1;
 22:          };
 23:        void setD1( const char * d1 )
 24:          {
 25:            m_d1 = QString::fromLocal8Bit(d1);
 26:          };
 27:        vod setD2( const QString & d2 )
 28:          {
 29:            m_d2 = d2;
 30:          };
 31:        void setD2( const char * d2 )
 32:          {
 33:            m_d2 = QString::fromLocal8Bit(d2);
 34:          };
 35:        QString do()
 36:          {
 37:            return m_d1 + QString::fromLatin1(" ") + m_d2;
 38:          };
 39:      private:
 40:        QString m_d1;
 41:        QString m_d2;
 42:    };
					
				
				Наличие перегруженных конструкторов и методов setD1/setD2
				в этом классе гарантирует, что внутри этого класса всегда будет использоваться
				"чистый" UNICODE, независимо от того, каким образом мы инициализируем
				объекты класса Foo:
				
				
					
				
  1:  Foo f1( "Привет","мир!!!" );
  2:  QString s1 = QString::fromLocal8Bit("Привет");
  3:  Foo f2;
  4:
  5:  f2.setD1( s1 );
  6:  f2.setD2( "мир!!!" );
  7:  qWarning("%s", (const char *)f1.do().local8Bit());
  8://^^ Напечатает строку : Привет мир!!!
  9:  qWarning("%s", (const char *)f2.do().local8Bit());
 10://^^ Напечатает строку : Привет мир!!!
					
				
				Еще более безопасное решение - добавление в класс Foo
				двух конструкторов:
				
				
					
			
  1:  ...
  2:        Foo( const QString & d1, const char * d2 )
  3:          {
  4:            m_d1 = d1;
  5:            m_d2 = QString::fromLocal8Bit(d2);
  6:          };
  7:        Foo( const char * d1, const QString & d2 )
  8:          {
  9:            m_d1 = QString::fromLocal8Bit(d1);
 10:            m_d2 = d2;
 11:          };
 12:  ...
 13:  QString s1 = QString::fromLocal8Bit("Привет");
 14:  Foo f(s1,"мир!!!");
 15:  ...
					
				
				Если мы не будем перегружать конструкторы и методы setD1/setD2,
				то мы получим все те ошибки, о которых говорилось выше:
				
				
					
			
  1:  class Foo
  2:    {
  3:      public:
  4:        Foo( const Foo & foo )
  5:          {
  6:            m_d1 = foo.m_d1;
  7:            m_d2 = foo.m_d2;
  8:          };
  9:        Foo( const QString & d1, const QString & d2 = QString::null )
 10:          {
 11:            m_d1 = d1;
 12:            m_d2 = d2;
 13:          };
 14:        vod setD1( const QString & d1 )
 15:          {
 16:            m_d1 = d1;
 17:          };
 18:        vod setD2( const QString & d2 )
 19:          {
 20:            m_d2 = d2;
 21:          };
 22:        QString do()
 23:          {
 24:            return m_d1 + QString::fromLatin1(" ") + m_d2;
 25:          };
 26:      private:
 27:        QString m_d1;
 28:        QString m_d2;
 29:    };
 30:
 31:  Foo f1( "Привет","мир!!!" ); // Неявное преобразование
 32:  QString s1 = QString::fromLocal8Bit("Привет");
 33:  Foo f2;
 34:
 35:  f2.setD1( s1 );
 36:  f2.setD2( "мир!!!" ); // Неявное преобразование
 37:  qWarning("%s", (const char *)f1.do().local8Bit());
 38://^^ Напечатает строку : ?????? ???!!!
 39:  qWarning("%s", (const char *)f2.do().local8Bit());
 40://^^ Напечатает строку : Привет ???!!!
					
				
				В реальных программах кроме отдельных строк очень часто приходится 
				использовать еще и списки строк. В библиотеке Qt
				для представления списков строк исоользуются два класса QStringList
				и QStrList. Первый представляет строки типа QString,
				второй строки типа QCString. В этой ситуации самой распространенной
				ошибкой, связанной с преобразованием строк, является попытка добавить или
				преобразовать строку типа QString к списку типа 
				QStrList, и наоборот извлечь строку из списка QStrList
				в переменную типа QString. Это справедливо относительно
				строк типа QCString и списков QStringList.
				Поэтому использованию списков строк тоже нужно уделять особое внимание.
			
				Конечно, как говорилось выше, использование ключей -DQT_NO_CAST_ASCII
				и -DQT_NO_ASCII_CAST при компиляции, защитит от таких 
				ошибок, но к сожалению не всегда возможно применение этих ключей.
			
				В заключении этой темы, нужно отметить: Т.к. библиотека 
				Qt внутри ориентированна на 
				использование UNICODE строк, то использование класса QString
				вместо char * и QCString для передставления 
				строк внутри программы, а также использование класса QStringList
				вместо класса QStrList для представления списков строк 
				будет самым безопасным решением с точки зрения появления ошибок.
			
				Если в своей программе вы используете системные вызовы для доступа
				к файлам или директориям (например fopen(..), open(..), mkdir(..),
				access(..), unlink(..) и т.д.) более безопасным решением для
				преобразования имени файла/директории из QString в 
				const char * использовать методы класса QFile:
				
				
							static QCString QFile::encodeName( const QString & fileName );
						
					QString,
						в однобайтовое представление типа QCString с использованием
						специфики файловой системы. По умолчанию используется преобразование
						QCString QString::local8Bit() const, но может быть переопределено
						с помощью метода static void QFile::setEncodingFunction( EncoderFn ).
					
							static QString decodeName( const QCString & localFileName );
						
					QCString,
						в представление UNICODE типа QString с использованием
						специфики файловой системы. По умолчанию используется преобразование
						QString QString::fromLocal8Bit(const char*, int), но может быть переопределено
						с помощью метода static void QFile::setDecodingFunction( DecoderFn ).
					
					
  1:  int renameFile( const QString & old, const QString & new )
  2:    {
  3:      ::rename( QFile::encodeName(old), QFile::encodename(new) );
  4:    }
  5:  ...
  6:  QStringList readDir( const QString & dName )
  7:    {
  8:      DIR           *dp = 0L;
  9:      struct dirent *ep;
 10:      QStringList   dList;
 11:
 12:      dp = opendir( QFile::encodeName(dName) );
 13:      if ( dp )
 14:        {
 15:          while ( ep=readdir( dp ) )
 16:            {
 17:              dList.append(QFile::decodeName(ep->d_name));
 18:            }
 19:          closedir( dp );
 20:        }
 21:      return dList;
 22:    }
 23:  ... 
					
				
			
			
				Если нужно проверить является строка, представленная типом QString
				или QCString, пустой или нулевой, нужно использовать методы:
				
				
bool QString::isEmpty() const,bool QCString::isEmpty() const
					str.isEmpty()==TRUE, то это не 
						означает, что будет выполняться и условие str.isNull()==TRUE.
					bool QString::isNull() const,bool QCString::isNull() const
					str.isNull()==TRUE, то обязательно будет 
						выполняться условие str.isEmpty()==TRUE.
					В программах написанных с ипользованием языка C++ очень часто используют потоки (streams) для ввода/вывода текстовых данных из/в строки, из/в файла или какого-либо другого устройства ввода/вывода. Действительно, с помощью потока доступ к буферу (строке), файлу или другому устройству организуется одинаково.
В обычных тестовых файлах подавляющего большинства операционных систем, информация представлена в виде однобайтовых строк в кодировке, определяемой установками локали. В некоторых случаях в тестовых файлах информация может быть представлена представлена в виде однобайтовых строк в кодировке UTF8, реже в виде двухбайтовых строк в кодировке UNICODE.
				В библиотеке Qt для 
				этих целей используется класс QTextStream. Но т.к. библиотека
				Qt внутри работает со строками
				в формате UNICODE, то этот класс имеет свои особенности. Эти особенности 
				мы рассмотрим ниже.
			
				В качестве примера использования класса QTextStream можно привести
				следующий фрагмент кода:
				
				
					
				
  1:  ...
  2:  QFile file(QFile::decodeName("TheFile"));
  3:  QString s;
  4:
  5:  if (file.exists()) 
  6:    {
  7:      if (file.open(IO_ReadOnly)) 
  8:        {
  9:          QTextStream t(&file);
 10:          
 11:          while ((s=t.readLine()) != QString::null)
 12:            {
 13:              qWarning((const char *)s.local8Bit());
 14:            }
 15:          file.close();
 16:        }
 17:    }
 18:  ... 
					
				
				Если файл TheFile содержит текст, представленный ниже, т.е. 
				текстовые однобайтовые строки в кодировке, определяемой установками
				локали:
				
				
					
				
  1:  Строка 1
  2:  Строка 2
  3:  Строка 3
					
				
				то соответственно в результате выполнения этого фрагмента кода содержимое 
				файла будет напечатано без искажений.
			
				Для демонстрации момента, в котором возникает искажение национальных 
				символов,	приведем другой пример (в этом примере я намеренно использую 
				класс QStrList):
				
				
					
				
  1:  ...
  2:  QFile file(QFile::decodeName("TheFile"));
  3:  QString s;
  4:  QStrList strList;
  5:  
  6:  strList.append("Строка 1");
  7:  strList.append("Строка 2");
  8:  strList.append("Строка 3");
  9:  
 10:  if (file.open(IO_WriteOnly)) 
 11:    {
 12:      QTextStream t( & file);
 13:      QStrListIterator it(strList);
 14:  
 15:      const char * tmp;
 16:      while ( (tmp=it.current()) ) 
 17:        {
 18:          ++it;
 19:          t << tmp << "\n";
 20:        }
 21:      file.close();
 22:    }
 23:  ...
					
				
				В этом примере в поток вставляются однобайтовые строки. В результате 
				выполнения этого фрагмента кода, файл TheFile, будет содержать
				строки:
				
				
					
				
  1:  ?????? 1
  2:  ?????? 2
  3:  ?????? 3
					
				
				Если вместо класса QStrList использовать класс QStringList
				или вместо файла связать поток с классом QCString (QByteArray),
				то строки будут выводиться правильно. Почему так происходит ?
			
				Разработчики библиотеки Qt реализовали
				класс QTextStream следующим образом: предполагается, что
				в файле, связанном с потоком, текст хранится в виде однобайтовых строк
				в кодировке, определенной для текущей локали. Поэтому при создании потока,
				связанного с файлом, устанавливается флаг Encoding == QTextStream::Locale
				с помощью метода QTextStream::setEncoding( QTextStream::Encoding e) c параметром
				e = QTextStream::Locale.
			
				При выводе однобайтового символа в поток применяется следующее преобразование:
				QChar <= unsigned short <= int <= char
				(Смотри реализацию методов QTextStream::ts_putc(...) и
				QTextStream::writeBlock( const char*, uint)). В результате
				такого преобразования русская буква 'А' (код в ISO-8859-5 - 0xB0)	
				получает UNICODE код U+00B0. 
			
				В методе QTextStream::ts_putc(QChar) при непосредственной
				записи в файл происходит преобразование к однобатовому символу с помощью
				метода QTextCodec::fromUnicode(...). Если мы вставляем в 
				поток, связанный с файлом, строки типа QString, то преобразование
				выполняется правильно и русские буквы выводятся без искажений. Если
				мы вставляем в поток однобайтовые строки, то преобразование выполняется
				некорректно (с точки зрения использования UNICODE) и русские символы 
				искажаются.
			
				В свою очередь, для текстового потока QTextStream, связанного
				с однобайтовой строкой (QByteArray или QCString),
				устанавливается флаг Encoding == QTextStream::Latin1, который
				выполняет преобразование из UNICODE в однобайтовую строку по следующему 
				алгоритму:
				
				
					
				
  1:  QChar c;
  2:
  3:  if( c.row() )  // Если старший октет отличен от нуля
  4:    {
  5:      dev->putch( '?' ); // Выводим символ '?'
  6:    }
  7:  else
  8:    {
 10:      dev->putch( c.cell() ); // Младший октет
 11:    }
					
				
					Если мы вставляем в поток, связанный с однобайтовой строкой, строку 
					типа QString то выполняется метод 
					QTextStream::operator<<( const QString & s ),
					который для Encoding == QTextStream::Latin1 производит преобразование 
					'Русская буква' ==> '?'. Для символов в кодировках ISO-8859-1 или 
					US-ASCII преобразования не имеют эффекта, коды этих символов не 
					меняются: в UNICODE старший байт равен 0, младший байт равен коду 
					символа. Для национальных символов в UNICODE старший байт всегда отличен
					от 0 и в общем случае младший байт не совпадает с однобайтовым кодом 
					символа для кодировки определенной в локали, и такие строки подвергаются 
					изменениям. Если тестировать программы, используя только строки в 
					кодировке ISO-8859-1 или US-ASCII, то ошибки преобразования "UNICODE 
					<==> однобайтовая строка"  не видны, т.к. фактически
					никаках преобразоаний не происходит.
			
				Для того чтобы избежать искажения национальных символов при использовании
				класса QTextStream, нужно придерживаться простого правила:
				после создания потока нужно явно установить для него тип кодирования 
				текста с помощь метода QTextStream::setEncoding( QTextStream::Encoding e).
			
				Наш пример будет выглядеть следубщим образом:
				
				
					
				
  1:  ...
  2:  QFile file(QFile::decodeName("TheFile"));
  3:  QString s;
  4:  QStrList strList;
  5:  
  6:  strList.append("Строка 1");
  7:  strList.append("Строка 2");
  8:  strList.append("Строка 3");
  9:  
 10:  if (file.open(IO_WriteOnly)) 
 11:    {
 12:      QTextStream t( & file);
 13:      t.setEncoding( QTextStream::Latin1 );
 14:    //^^^Сообщаем потоку, что однобайтовые строки преобразовывать
 15:    //   не нужно
 16:      QStrListIterator it(strList);
 17:  
 18:      const char * tmp;
 19:      while ( (tmp=it.current()) ) 
 20:        {
 21:          ++it;
 22:          t << tmp << "\n";
 23:        }
 24:      file.close();
 25:    }
 26:  ...
					
				
				В случае если мы используем поток, связанный с однобайтовой строкой, и в
				этот поток вставляем строки типа QString, то нужно указать
				Encoding == QTextStream::Locale:
				
				
					
				
  1:  ...
  2:  QCString buff;
  3:  QString s;
  4:  QStringList strList;
  5:  
  6:  strList.append(QString::fromLocal8Bit("Строка 1"));
  7:  strList.append(QString::fromLocal8Bit("Строка 2"));
  8:  strList.append(QString::fromLocal8Bit("Строка 3"));
  9:  
 10:  QTextStream t( & buff );
 11:  t.setEncoding( QTextStream::Locale );
 12:    //^^^Сообщаем потоку, что UNICODE строки нужно преобразовывать
 13:    //   в соответствии с установками локали
 14:  for ( QStringList::Iterator it = strList.begin(); it != strList.end(); ++it )
 15:    {
 16:       t << *it << QString::fromLatin1("+");
 17:    }
 18:  ...
					
				
					В результате выполнения этого фрагмента кода переменная buff
					получит значение "Строка 1+Строка 2+Строка 3". Если мы уберем
					из примера строку 11, то переменная buff	получит значение 
					"?????? 1+?????? 2+?????? 3".
			
Все выше сказанное можно сформулировать в виде следующих правил:
							Если в поток типа QTextStream с помощью оператора
							вставки в поток (QTextStream::operator<<(...))
							будут	вставляться однобайтовые строки в кодировке, определяемой 
							установками локали или UTF8, то перед вызовом оператора вставки
							в поток необходимо установить тип кодирования текстовой информации
							в значение QTextStream::Latin1 с помощью вызова метода 
							QTextStream::setEncoding( QTextStream::Encoding e).
						
							Если в поток типа QTextStream с помощью оператора
							вставки в поток (QTextStream::operator<<(...))
							будут	вставляться строки типа QString, то перед вызовом
							оператора вставки в поток	необходимо явно установить тип кодирования 
							текстовой информации c помощью вызова	метода 
							QTextStream::setEncoding( QTextStream::Encoding e).
						
Тип кодирования текстовой информации долженен принимать следующие значения:
							Если поток используется с одним типом строк, то тип кодирования 
							текстовой информации можно задавать только один раз - после создания 
							потока. Если поток используется с разными типами строк, то тип 
							кодирования текстовой информации можно задавать каждый раз перед измением
							типа строк. Например:
							
								
									
							
  1:  ...
  2:  QCString mbs = "Строка 1", buff;
  3:  QString wcs = QString::fromLocal8Bit("Строка 2");
  4:  
  5:  QTextStream t( & buff );
  6:
  7:  t.setEncoding( QTextStream::Latin1 );
  8:    //^^^Сообщаем потоку, что однобайтовые строки преобразовывать
  9:    //   не нужно
 10:  t << mbs;
 11:
 12:  t.setEncoding( QTextStream::Locale );
 13:    //^^^Сообщаем потоку, что UNICODE строки нужно преобразовывать
 14:    //   в соответствии с установками локали
 15:  t << wcs;
 16: 
 17:  ...
									
								
						
							Все вышесказанное справедливо и для оператора извлечения из потока 
							QTextStream::operator>>(...).
						
Патчи, исправляющие описанные проблемы в KDE 2.1.1, вы можете скачать здесь.