Cara paling mudah untuk memuat dan menyimpan data biner dengan Qt adalah untuk instantiate QFile, untuk membuka file tersebut, dan untuk mengaksesnya melalui objek QDataStream. QDataStream menyediakan format penyimpanan platform-independen yang mendukung dasar C++ seperti int dan double, dan jenis berbagai data Qt, termasuk QByteArray, QFont, QImage, QPixmap, QString, dan QVariant, serta kelas wadah Qt seperti QList<T> dan QMap<K, T>.
Berikut ini bagaimana kita akan menyimpan integer, sebuah QImage, dan sebuah QMap<QString, QColor> dalam sebuah file bernama facts.dat:
Jika kita tidak bisa membuka file, kami menginformasikan kepada user dan kembali. qPrintable() macro kembali dan const char * untuk QString. (Pendekatan lain akan menggunakan QString::toStdString(), yang mengembalikan std::string, yang <iostream.h> memiliki << kelebihan).
Jika file berhasil dibuka, kita membuat QDataStream dan menetapkan nomor versinya. Nomor versi adalah sebuah integer yang mempengaruhi cara tipe data Qt direpresentasikan (dasar C++ Tipe data selalu direpresentasikan dengan cara yang sama).
Untuk memastikan bahwa jumlah 0x12345678 ditulis sebagai sebuah integer unsigned 32-bit pada semua platform, kami dilemparkan ke quint32, jenis data yang dijamin akan tepat 32 bit. Untuk memastikan interoperabilitas QDataStream secara default standarisasi pada big-endian, hal ini dapat diubah dengan memanggil setByteOrder().
QImage image("philip.png"); QMap<QString, QColor> map; map.insert("red", Qt::red); map.insert("green", Qt::green); map.insert("blue", Qt::blue); QFile file("facts.dat");
if (!file.open(QIODevice::WriteOnly)) {
std::cerr << "Cannot open file for writing: "
<< qPrintable(file.errorString()) << std::endl; return;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_3);
80 Kita tidak perlu secara eksplisit menutup file tersebut, karena ini dilakukan secara otomatis jika variabel QFile keluar dari ruang lingkup. Jika kita ingin memastikan bahwa data tersebut telah benar-benar telah ditulis, kita bisa memanggil flush() dan memeriksa nilai pengembaliannya (true pada sukses). Kode untuk membaca kembali cermin data kode yang kita digunakan untuk menulis itu:
Versi QDataStream kita gunakan untuk membaca adalah sama dengan yang kita digunakan untuk menulis. Ini harus selalu terjadi. Dengan hard-coding nomor versi, kita menjamin bahwa aplikasi selalu dapat membaca dan menulis data (dengan asumsi dikompilasi dengan Qt versi yang lebih baru).
QDataStream menyimpan data sedemikian rupa sehingga kita dapat membaca kembali. Sebagai contoh, sebuah QByteArray direpresentasikan sebagai jumlah byte 32-bit diikuti oleh byte sendiri. QDataStream juga dapat digunakan untuk membaca dan menulis byte, tanpa menghitung byte header, menggunakan readRawBytes() dan writeRawBytes().
Penanganan kesalahan ketika membaca dari QDataStream cukup mudah. Arus memiliki status() nilai yang dapat QDataStream::Ok, QDataStream::ReadPastEnd, atau QDataStream::ReadCorruptData. Setelah kesalahan telah terjadi, operator >> selalu membaca nol atau nilai kosong. Ini berarti bahwa kita dapat membaca seluruh berkas tanpa khawatir tentang potensi kesalahan dan memeriksa status() Nilai akhir untuk melihat apakah apa yang kita baca. QDataStream menangani berbagai C++ dan tipe data Qt; daftar lengkap tersedia di http://doc.qt.nokia.com/4.7/datastreamformat.html. Kita juga dapat menambahkan dukungan untuk jenis kita sendiri kustom oleh overloading operator <<and>>. Berikut definisi dari tipe data kustom yang dapat digunakan dengan QDataStream:
quint32 n; QImage image;
QMap<QString, QColor> map; QFile file("facts.dat");
if (!file.open(QIODevice::ReadOnly)) {
std::cerr << "Cannot open file for reading: "
<< qPrintable(file.errorString()) << std::endl; return; } QDataStream in(&file); in.setVersion(QDataStream::Qt_4_3); in >> n >> image >> map;
81 Berikut ini bagaimana kita akan melaksanakan operator <<:
Untuk output oainting, kita hanya ada dua output QStrings dan quint32. Pada akhir fungsi, kita kembali stream. Ini adalah C++ idiom umum yang memungkinkan kita untuk menggunakan rantai operator << dengan output stream. Sebagai contoh:
out <<painting1 <<painting2 <<painting3; Pelaksanaan operator>>() adalah sama dengan operator<<():
class Painting {
public:
Painting() { myYear = 0; }
Painting(const QString &title, const QString &artist, int year) {
myTitle = title; myArtist = artist; myYear = year; }
void setTitle(const QString &title) { myTitle = title; } QString title() const { return myTitle; }
... private: QString myTitle; QString myArtist; int myYear; };
QDataStream &operator<<(QDataStream &out, const Painting &painting);
QDataStream &operator>>(QDataStream &in, Painting &painting);
QDataStream &operator<<(QDataStream &out, const Painting &painting)
{
out << painting.title() << painting.artist() << quint32(painting.year());
return out; }
QDataStream &operator>>(QDataStream &in, Painting &painting) {
QString title; QString artist; quint32 year;
in >> title >> artist >> year;
painting = Painting(title, artist, year); return in;
82 Ada beberapa keuntungan untuk operator streaming menyediakan untuk tipe data kustom. Salah satunya adalah bahwa hal itu memungkinkan kita untuk wadah aliran yang menggunakan jenis kustom. Sebagai contoh:
QList<Painting> paintings = ...;
out
<<
paintings;
Kita dapat membaca dalam kontainer dengan mudah: QList<Painting> paintings;
in
>>
paintings;
Ini akan menghasilkan kesalahan kompiler jika painting tidak mendukung <<or>>. Manfaat lain dari operator menyediakan streaming untuk jenis data adalah bahwa kita dapat menyimpan nilai dari tipe sebagai QVariants, yang membuat mereka lebih luas digunakan-misalnya, dengan QSettings. Ini bekerja dengan ketentuan bahwa kita mendaftar tipe menggunakan qRegisterMetaTypeStreamOperators<T>().
Ketika kita menggunakan QDataStream, Qt membaca dan menulis masing-masing tipe, termasuk wadah dengan jumlah item. Hal ini membebaskan kita dari kebutuhan untuk struktur apa yang kita tulis dan dari melakukan setiap jenis parsing pada apa yang kita baca. Hanya kewajiban kita adalah untuk memastikan bahwa kita membaca semua jenis dalam urutan yang sama persis seperti yang kita tulis. QDataStream berguna baik bagi kita format file aplikasi khusus dan untuk format biner standar. Kita dapat membaca dan menulis format biner standar menggunakan operator streaming pada tipe dasar (seperti quint16 atau float) atau menggunakan readRawBytes() dan writeRawBytes(). Jika QDataStream sedang digunakan murni untuk membaca dan menulis dasar tipe data C++, kita bahkan tidak perlu memanggil setVersion().
Sejauh ini, kita memuat dan menyimpan data dengan versi aliran hard-code sebagai QDataStream::Qt_4_3. Pendekatan ini sederhana dan aman, tetapi memiliki satu kekurangan kecil: Kita tidak dapat mengambil keuntungan dari format baru atau diperbarui. Misalnya, jika versi yang lebih baru Qt menambahkan atribut baru untuk QFont (selain ukuran point, family, dll) dan atribut tidak akan disimpan atau diambil. Ada dua solusi. Pendekatan pertama adalah untuk melekatkan nomor versi QDataStream di file:
QDataStream out(&file);
out
<<
quint32(MagicNumber)
<<
quint16(out.version());
(MagicNumber adalah konstanta yang secara unik mengidentifikasi jenis file.) Pendekatan ini memastikan bahwa kita selalu menulis data menggunakan versi terbaru QDataStream, apa pun yang kebetulan. kita membaca versi stream:
83 Kita dapat membaca data selama versi aliran kurang dari atau sama dengan versi yang digunakan oleh aplikasi, jika tidak, akan melaporkan kesalahan.
Jika format file berisi nomor versi sendiri, kita dapat menggunakannya untuk menyimpulkan nomor versi aliran bukannya menyimpannya secara eksplisit. Sebagai contoh, mari kita anggap bahwa format file untuk versi 1.3 dari aplikasi kita. Kemudian kita bisa menulis data sebagai berikut:
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_3);
out
<<
quint32(MagicNumber)
<<
quint16(0x0103);
Ketika kita membaca kembali, kita menentukan versi QDataStream digunakan berdasarkan nomor versi aplikasi: quint32 magic; quint16 streamVersion; QDataStream in(&file); in >> magic >> streamVersion; if (magic != MagicNumber) {
std::cerr << "File is not recognized by this application" << std::endl;
} else if (streamVersion > in.version()) {
std::cerr << "File is from a more recent version of the " << "application" << std::endl; return false; } in.setVersion(streamVersion); QDataStream in(&file); in >> magic >> appVersion; if (magic != MagicNumber) {
std::cerr << "File is not recognized by this application" << std::endl;
return false;
} else if (appVersion > 0x0103) {
std::cerr << "File is from a more recent version of the " << "application" << std::endl; return false; } if (appVersion < 0x0103) { in.setVersion(QDataStream::Qt_3_0); } else { in.setVersion(QDataStream::Qt_4_3); }
84 Dalam contoh ini, kita menetapkan bahwa setiap file yang disimpan dengan versi sebelum 1.3 aplikasi tersebut menggunakan aliran data versi 4 (Qt_3_0), dan file yang disimpan dengan versi 1.3 versi aplikasi data menggunakan aliran 9 (Qt_4_3).
Singkatnya, ada tiga kebijakan untuk menangani versi QDataStream: hard-coding, secara eksplisit menulis dan membaca nomor versi, dan menggunakan nomor versi yang berbeda tergantung pada versi aplikasi. Setiap kebijakan ini dapat digunakan untuk memastikan bahwa data yang ditulis oleh versi lama dari suatu aplikasi dapat dibaca dengan versi yang baru, bahkan jika link versi baru terhadap versi yang lebih baru dari Qt. Setelah kita memilih kebijakan untuk menangani versi QDataStream, membaca dan menulis data biner menggunakan Qt ini sederhana dan dapat diandalkan.
Jika kita ingin membaca atau menulis file dalam satu waktu, kita dapat menghindari menggunakan QDataStream dan sebagai gantinya menggunakan QIODevice write() dan fungsi readAll(). Sebagai contoh:
Pada baris dimana readAll() dipanggil, seluruh isi dari file input yang dibaca ke dalam QByteArray, yang kemudian diteruskan ke fungsi write() yang akan ditulis ke file output. Setelah semua data dalam QByteArray memerlukan memori lebih dari membaca item dengan item, tapi ini menawarkan beberapa keuntungan. Sebagai contoh, kita kemudian dapat menggunakan qCompress() dan qUncompress() untuk kompres dan uncompress data. Sebuah alternatif memori kurang untuk menggunakan qCompress() dan qUncompress() adalah QtIOCompressor dari Qt Solutions. Sebuah QtIOCompressor kompres stream itu menulis dan dekompresi tanpa menyimpan seluruh file dalam memori.
Ada skenario lain di mana QIODevice mengakses secara langsung adalah lebih tepat daripada menggunakan QDataStream. QIODevice memberikan fungsi peek() yang mengembalikan byte data berikutnya tanpa memindahkan posisi perangkat serta fungsi ungetChar() yang "unreads" byte. Ini bekerja baik untuk perangkat random-access (seperti file) dan untuk perangkat sekuensial (seperti soket jaringan). Ada juga fungsi seek() yang menentukan posisi perangkat, untuk perangkat yang mendukung akses random.
bool copyFile(const QString &source, const QString &dest) { QFile sourceFile(source); if (!sourceFile.open(QIODevice::ReadOnly)) return false; QFile destFile(dest); if (!destFile.open(QIODevice::WriteOnly)) return false; destFile.write(sourceFile.readAll());
return sourceFile.error() == QFile::NoError && destFile.error() == QFile::NoError; }
85
Qt Timer
Kelas QTimer menyediakan repetitif dan single-shot timer. Kelas QTimer menyediakan antar muka tingkat tinggi pemrograman untuk manajemen waktu. Untuk menggunakannya, membuat QTimer terhubung sinyal timeout() ke slot yang sesuai, dan panggil fungsi start().