Wiki

Внимание! Двоичные и Символьные Данные

Jasmin Blanchette

Классы QString, QCString и QByteArray - это альтернативы для старомодных и ограниченных символьныхмассивов. Их надлежащее использование может сделать ваши приложения быстрее, меньше и более надежными. QString также приносит выгоды при интернационализации, использовании Юникода, а классы потоков типа QDataStream и QTextStream делают ввод/вывод более простым. Эта статья объясняет, как реализовать выгоды, предлагаемые этими классами.

QByteArray - Массив Символов

Qt обеспечивает шаблонQMemArray класса, чтобы хранить массивы основных типов и структурные типы. Класс предлагает много удобных особенностей, типа способности изменять размеры массива в любое время. Как только Вы начинаете использовать его, Вы откажетесь от устарелых функций memcpy() и memset(). И если ваше приложение попробует обращаться к элементу вне границ массива, Вы получите предупреждение во время выполнения.

Массив QMemArray может содержать любые двоичные данные, включая '\0 ', и не должен кончаться на '\0 '. Это очень похоже на массив символов C-стиля, только лучше.

QByteArray - явно общедоступный класс. Это делает класс более эффективным в большинстве случаев, но может привести к тонким ошибкам. Стоит прочитать Общедоступные Классы, если Вы планируете использоватьQByteArray интенсивно.

Знайте, что стандарт C++ позволяет компилятору выбирать, является ли символ (char) со знаком (signed char) или без знака (unsigned char). Программы, которые имеют дело с символом, вероятно, предполагают, что символ со знаком. Многие компиляторы принимают параметры с командной строки для того, чтобы установить тип символа; например /j опция MSVC и С ++ 's - определяют символ как символ без знака. Вы можете использовать эти варианты, чтобы проверить ваши программы.

QByteArray - совершенный класс, чтобы хранить нетекстовые данные в памяти. Вы можете легко прочитать целый файл в QByteArray и выполнять итерации по массиву впоследствии:

QFile file( "Friday's jam session.mp3" );
if ( file.open(IO_ReadOnly) ) {
  QByteArray array = file.readAll();
  file.close();
  for ( int i = 0; i < array.size(); i++ ) {
     // etc.
  }
}

QCString - Строка Символов

Словарь С состоял из научно-технических терминов, которые не использовал никто, кроме ученых и техников.
-- George Orwell

QCString класс сохраняет классический C-стиль '\0 '-законченной строки . QCString может использоваться, чтобы хранить любые '\0 '-законченные строки данных на 8 битов, типа Latin-1, EUC-KR, и расширенного двоично-десятичного кода.

Благодаря наследованию, QCString - это также и QByteArray. Строка с 5 знаками "HELLO" - также 6-символьный массив ""Hello\0"":

QCString s( "Hello" );
s.length(); // returns 5
s.size();   // returns 6

Если Вы конвертируете QCString в QByteArray:

QCString s( "Hello" );
QByteArray a = s.copy();
a.size();   // returns 6

(Мы вызываем copy (), чтобы получить копию массива.) Вы можете усечь массив, чтобы избавиться от '\0':

a.truncate( a.size() - 1 );

Если Вы преобразовываете QByteArray в QCString, требуется осторожность: Вы должны убедиться, чтоQByteArray - '\0 '- законченный. Если это не так, делайте следующее:

int n = a.size();
a.resize( n + 1 );
a[n] = '&#92;&#48;';
QCString s = a.data();

Исторически, QCString то же, что и 8 битов QString в Qt 1.x, и был сформирован главным образом, чтобы облегчить перенесение на Qt 2.0. Есть очень немного контекстов, где QCString предпочтителен для QByteArray или QString. В QCString главная выгода - то, что требуется только приблизительно половина памяти в отличие от QString.

QString - Строка Unicode

Но почему Вы должны смешивать одно - и двухбайтовые значения так или иначе?
-- Ken Lunde

QString класс хранит16-битовые Unicode строки. В отличие отQCString, они не '\0 '-законченные, и могут включить '\0'.

Преобразование из QString в QByteArray, QCString, или наоборот, может быть хитрым. Qt обеспечивает операторы и конструкторы, чтобы удобно было конвертировать из одного в другое (но не всегда).

1. Преобразование из QString в QCString.

QString состоящий полностью из Latin-1 знаков, (включая ASCII), может легко быть преобразован в QCString:

QString qstr( "Anders Еngstrцm <anders@telia.se>" );
QCString cstr = (const char *)qstr;

Но если QString не содержит Latin-1 знаков, результат неопределен.

Чтобы отключить это автоматическое (и возможно нежелательное) преобразование, Вы можете определить символ предпроцессора QT_NO_ASCII_CAST перед включением любого Qt заголовка. Это будет гарантировать вызов latin1 () явно:

QCString cstr = qstr.latin1();

Если Вы используете qmake, Вы можете отключать преобразование для вашего проекта, добавляя эту строку в .pro файл:

DEFINES += QT_NO_ASCII_CAST

Есть другие способы преобразования QString к QCString (см.документацию к классам QString и QTextCodec); функция utf8 () имеет преимущество сохранения всей информации:

QCString cstr = qstr.utf8();

2. Преобразование из QCString в QString.

QCString не передает специфические коды. Если Вы храните Latin - 1 вQCString, преобразование в Юникод тривиально:

QCString cstr( "Carl Friedrich GauЯ <gauss@gmx.de>" );
QString qstr = cstr;

Если Вы используете другие кодировки, кроме Latin-1, это автоматическое преобразование опасно. Чтобы отключить это, определитесимвол QT_NO_CAST_ASCII препроцессора(не перепутайте с QT_NO_ASCII_CAST). Вы тогда должны вызвать fromLatin1 () явно, чтобы конвертировать Latin-1QCString кQString:

QString qstr = QString::fromLatin1( cstr );

Подобная функция существует для строк в формате UTF-8.

3. Преобразование из QString в QByteArray.

Самый простой способ поребразовывать QString в QByteArray - подход редукции: конвертируйтеQString вQCString, затем конвертируйте QCString в QByteArray:

QString qstr( "Anders Еngstrцm <anders@telia.se>" );
QByteArray array = QCString( qstr );

Окончание QByteArray будет иметь '\0' признак конца.

4. Преобразование из QByteArray в QString.

Qt обращает QByteArray в QString автоматически:

QByteArray array;
array.assign( "Hello&#92;&#48;World", 11 );
QString str = array;

Конструктор останавливается в первом '\0' символе. В вышеупомянутом фрагменте кода, str - строка из 5 символов "HELLO",а не строка из 11 символов ""HELLO \\ 0World"".

 

*

*

*

       

Преобразование от одного типа к другому может быть опасно, потому что компьютер не может знать мнение программиста, чтобы установить соответствующую кодировку. Следующие правила могут помочь Вам:

  1. Выбирайте ваши типы данных тщательно.
  2. Избегайте ненужных преобразований.
  3. Проверяйте правильность преобразования.

Теперь давайте увидим, как эти вещи касаются ввода - вывода.

QDataStream- Потоки двоичных данных

Мы имеем два выбора, или чтобы напасть на ввод - вывод сразу и закончить его, или откладывать ввод - вывод до конца. Никакая из перспектив не очень привлекательна.
-- Donald E. Knuth

QDataStream класс может использоваться, чтобы читать и записывать двоичные данные в файл или в некоторое другое "устройство" ввода - вывода. Пример, как записать 32-разрядное целое число без знака 0x92025428 в небольшой endian файл с названием "nitty":

QFile file( "nitty" );
if ( file.open(IO_WriteOnly) ) {
  QDataStream out( &file );
  out.setByteOrder( QDataStream::LittleEndian );
  out << (Q_UINT32)0x92025428;
}

Пример, как читать 16-разрядное целое число со знаком в большом endian (значение по умолчанию) из файла, названного "gritty":

QFile file( "gritty" );
if ( file.open(IO_ReadOnly) ) {
     QDataStream in( &file );
     Q_INT16 n;
     in >> n;
}

QDataStream поддерживаетмного типов: Q_INT8, Q_INT16, Q_INT32, Q_UINT8, Q_UINT16, Q_UINT32, float, double, char *, QBitArray .QByteArray, QCString, QString, QVariant, и еще многие. Полный список доступен вФормате QDataStream Операторов.

Наш первый пример будет демонстрировать, как сохранить все свойстваQObject в устройстве ввода - вывода (обычно файл) и как загрузить их впоследствии. Вот код:

const Q_UINT32 MagicNumber = 0x1A7D3EF6;
 
void writeProperties( QObject *obj, QIODevice *device )
{
  QDataStream out( device );
  out << (Q_UINT32)MagicNumber
      << (Q_UINT8)out.version();
 
  int n = obj->metaObject()->numProperties( true );
  for ( int i = 0; i < n; i++ ) {
     const QMetaProperty *prop = obj->metaObject()->property( i, true );
     if ( prop->writable() ) {
        out << prop->name()
        << obj->property( prop->name() );
     }
  }
}

При вводе или выводе сложных типов типаQVariant очень важно удостовериться, что та же самая версия потока используется для чтения и записи.:

QDataStream out( device );
out.setVersion( 5 );
...
QDataStream in( device );
in.setVersion( 5 );

Недостаток непосредственного задания данных в том, что приложение не будет изменяться в усовершенствованной Qt 3.2 или более поздних версиях - например, при добавлении нового цвета в QColorGroup.

Пример, как считать свойства, записанные writeProperties ():

void readProperties( QObject *obj, QIODevice *device )
{
  QDataStream in( device );
  Q_UINT32 magic;
  Q_UINT8 version;
 
  in >> magic;
  if ( magic != MagicNumber ) {
     qWarning( "Not property data" );
	 return;
  }
 
  in >> version;
  if ( in.version() < version ) {
     qWarning( "Data is from a newer version" );
     return;
  }
  in.setVersion( version );
 
  QCString name;
  QVariant value;
  while ( !in.atEnd() ) {
     in >> name;
     if ( name.isNull() )
        break;
     in >> value;
     obj->setProperty( name, value );
  }
}

В следующем фрагменте кода, мы используем writeProperties () и readProperties (), чтобы сохранить свойства окна между сеансами:

QFrame *frame = new QFrame( parent );
QFile file( "properties" );
if ( file.open(IO_ReadOnly) )
  readProperties( frame, &file );
...
QFile file( "properties" );
if ( file.open(IO_WriteOnly) )
  writeProperties( frame, &file );
delete frame;

PNG file format

Ширина и высота изображения - первые два поля заголовка изображения ("IHDR"). Вот законченный код программы Qt, которая распечатывает ширину и высоту изображений PNG, указанных в командной строке:

#include <qbuffer.h>
#include <qcstring.h>
#include <qdatastream.h>
#include <qfile.h>
#include <qsize.h>
 
QSize getPngImageSize( QIODevice *device )
{
   Q_UINT32 width = 0;
   Q_UINT32 height = 0;
   char signature[8];
   char chunkType[4];
 
   QDataStream in( device );
   in.readRawBytes( signature, 8 );
 
   if ( memcmp(signature, "\211PNG\r\n\32\n", 8) == 0 ) {
       while ( !in.atEnd() ) {
           Q_UINT32 length;
           in >> length;
           in.readRawBytes( chunkType, 4 );
           if ( memcmp(chunkType, "IHDR", 4) == 0 ) {
               in >> width >> height;
               break;
           }
 
           // jump to the next chunk
           device->at( device->at() + length + 4 );
       }
   }
   return QSize( width, height );
}
 
int main( int argc, char **argv )
{
   if ( argc < 2 ) {
       qWarning( "Usage: pngsize file1.png etc." );
       return 1;
   }
 
 for ( int i = 1; i < argc; i++ ) {
       QFile file( QFile::decodeName(argv[i]) );
       if ( file.open(IO_ReadOnly) ) {
           QSize size = getPngImageSize( &file );
           qWarning( "%s: %d x %d", argv[i],
                     size.width(), size.height() );
       } else
           qWarning( "Cannot open file '%s'", argv[i] );
   }
   return 0;
}

(Если скорость не важна, и Qt конфигурирован с поддержкой PNG, Вы можете позволитьQImage делать трудную работу:

QSize size = QImage( argv[i] ).size();

Но это не демонстрирует QDataStream.)

Теперь давайте предположим, что нам внедрили изображение PNG в наше приложение, используя qembed или подобный инструмент:

static const char png_data[] = {
  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
  0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
  ...
  0x44, 0x75, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6e,
  0x44, 0xae, 0x42, 0x60, 0x82
};

Как мы можем определить размер этого изображения, не создаваяQImage? Ответ следует из этих четырех фактов:

  • массив символов может быть преобразован в QByteArray.
  • QByteArray может читаться вQBuffer.
  • QBuffer класс наследован отQIODevice.
  • наш getPngImageSize () читает изQIODevice.

Пример, как преобразовать эти идеи в выполняющийся код:

QByteArray array;
array.setRawData( png_data, sizeof(png_data) );
QBuffer buffer( array );
buffer.open( IO_ReadOnly );
QSize size = getPngImageSize( &buffer );
array.resetRawData( png_data, sizeof(png_data) );

QTextStream Текст Потоков

QTextStream читает и пишет данные в текстовый файл или в любое другое устройство ввода - вывода. Например, как записать "Hello {w|0c o} rld! " \\ "n" в файл "greetings":

QFile file( "greetings" );
if ( file.open(IO_WriteOnly) ) {
    QTextStream out( &file );
out << "Hello world!\n"; }

Главная особенность QTextStream - то, что можно использовать QTextCodec, чтобы конвертировать данные относительно устройства ввода - вывода на основе байта и 16-разрядного Юникода в QString класс. По умолчанию используется кодирование в 8 битов, предописанное языком (Latin-1 в большинстве Европы).

К сожалению, поддержка QTextCodec делает QTextStream намного медленнее, чем двоичный ввод - вывод. Trolltech планирует сделать QTextStream быстрее в Qt 4.0.

stream.setEncoding( QTextStream::Latin1 );

Если Вы записываете XML, Вы захотите также применять

stream.setEncoding( QTextStream::UnicodeUTF8 );

или

stream.setEncoding( QTextStream::Unicode );

QTextStream может даже оперироватьс QString. Два удобных подкласса - QTextIStream и QTextOStream - делают это очень простым:

QString str;
QTextOStream( &str ) << 22 << " " << "trees";
// str == "22 trees"
int n;
QString thing;
QTextIStream( &str ) >> n >> thing;
// n == 22, thing == "trees"

Большая часть того, что Вы уже знаете про , будет все еще работать. Пример, как смоделировать cout сQTextOStream:

QTextOStream qout( stdout );
qout << oct << 31 << " = " << dec << 25 << endl;

Статья, генерирующая XML, представляет приложениеQTextStream .


Copyright © 2003 Trolltech.

Trademarks