今天開始進入 Qt 的另一個部分:文件讀寫,也就是 IO。文件讀寫在很多應用程序中都是需要的。Qt 通過 QIODevice 提供了IO的抽象,這種設備(device)具有讀寫字節塊的能力。常用的IO讀寫的類包括以下幾個: QFlie 訪問本地文件系統或者嵌入資源 QTemporaryFile 創建和訪問本地文件系統的臨時文件 QBuffer 讀寫 QByteArray QProcess 運行外部程序,處理進程間通訊 QTcpSocket TCP 協議網絡數據傳輸 QUdpSocket 傳輸 UDP 報文 QSslSocket 使用 SSL/TLS 傳輸數據 QProcess、QTcpSocket、QUdpSoctet 和 QSslSocket 是順序訪問設備,它們的數據只能訪問一遍,也就是說,你只能從第一個字節開始訪問,直到最後一個字節。QFile、QTemporaryFile 和 QBuffer 是隨機訪問設備,你可以從任何位置訪問任意次數,還可以使用 QIODevice::seek() 函數來重新定位文件指針。 在訪問方式上,Qt 提供了兩個更高級別的抽象:使用 QDataStream 進行二進制方式的訪問和使用 QTextStream 進行文本方式的訪問。這些類可以幫助我們控制字節順序和文本編碼,使程序員從這種問題中解脫出來。 QFile 對於訪問獨立的文件是非常方便的,無論是在文件系統中還是在應用程序的資源文件中。Qt 同樣也提供了 QDir 和 QFileInfo 兩個類,用於處理文件夾相關事務以及查看文件信息等。 這次我們先從二進制文件的讀寫說起。 以二進制格式訪問數據的最簡單的方式是實例化一個 QFile 對象,打開文件,然後使用 QDataStream 進行訪問。QDataStream 提供了平台獨立的訪問數據格式的方法,這些數據格式包括標准的 C++ 類型,如 int、double等;多種 Qt 類型,如QByteArray、QFont、QImage、QPixmap、QString 和 QVariant,以及 Qt 的容器類,如 QList<T> 和 QMap<K, T>。先看如下的代碼:
這裡,我們首先創建了一個 QImage 對象,一個 QMap<QString, QColor>,然後使用 QFile 創建了一個名為 "facts.dat" 的文件,然後以只寫方式打開。如果打開失敗,直接 return;否則我們使用 QFile 的指針創建一個 QDataStream 對象,然後設置 version,這個我們以後再詳細說明,最後就像 std 的 cout 一樣,使用 << 運算符輸出結果。 0x12345678 成為“魔術數字”,這是二進制文件輸出中經常使用的一種技術。我們定義的二進制格式通常具有一個這樣的“魔術數字”,用於標志文件格式。例如,我們在文件最開始寫入 0x12345678,在讀取的時候首先檢查這個數字是不是 0x12345678,如果不是的話,這就不是可識別格式,因此根本不需要去讀取。一般二進制格式都會有這麼一個魔術數字,例如 Java 的 class 文件的魔術數字就是 0xCAFE BABE(很 Java 的名字),使用二進制查看器就可以查看。魔術數字是一個 32 位的無符號整數,因此我們使用 quint32 宏來得到一個平台無關的 32 位無符號整數。 在這段代碼中我們使用了一個 qPrintable() 宏,這個宏實際上是把 QString 對象轉換成 const char *。注意到我們使用的是 C++ 標准錯誤輸出 cerr,因此必須使用這個轉換。當然,QString::toStdString() 函數也能夠完成同樣的操作。 讀取的過程就很簡單了,需要注意的是讀取必須同寫入的過程一一對應,即第一個寫入 quint32 型的魔術數字,那麼第一個讀出的也必須是一個 quint32 格式的數據,如
- 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);
- out << quint32(0x12345678) << image << map;
好了,數據讀出了,拿著到處去用吧! 這個 version 是干什麼用的呢?對於二進制的讀寫,隨著 Qt 的版本升級,可能相同的內容有了不同的讀寫方式,比如可能由大端寫入變成了小端寫入等,這樣的話舊版本 Qt 寫入的內容就不能正確的讀出,因此需要設定一個版本號。比如這裡我們使用 QDataStream::Qt_4_3,意思是,我們使用 Qt 4.3 的方式寫入數據。實際上,現在的最高版本號已經是 QDataStream::Qt_4_6。如果這麼寫,就是說,4.3 版本之前的 Qt 是不能保證正確讀寫文件內容的。那麼,問題就來了:我們以硬編碼的方式寫入這個 version,豈不是不能使用最新版的 Qt 的讀寫了? 解決方法之一是,我們不僅僅寫入一個魔術數字,同時寫入這個文件的版本。例如:
- 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;
這個 file.xxx 文件的版本號是 123。我們認為,如果版本號是123的話,則可以使用 Qt_4_0 版本讀取。所以我們的讀取代碼就需要判斷一下:
- QFile file("file.xxx");
- file.open(QIODevice::WriteOnly);
- QDataStream out(&file);
- // Write a header with a "magic number" and a version
- out << (quint32)0xA0B0C0D0;
- out << (qint32)123;
- out.setVersion(QDataStream::Qt_4_0);
- // Write the data
- out << lots_of_interesting_data;
這樣,我們就可以比較完美的處理二進制格式的數據讀寫了。
- QFile file("file.xxx");
- file.open(QIODevice::ReadOnly);
- QDataStream in(&file);
- // Read and check the header
- quint32 magic;
- in >> magic;
- if (magic != 0xA0B0C0D0)
- return XXX_BAD_FILE_FORMAT;
- // Read the version
- qint32 version;
- in >> version;
- if (version < 100)
- return XXX_BAD_FILE_TOO_OLD;
- if (version > 123)
- return XXX_BAD_FILE_TOO_NEW;
- if (version <= 110)
- in.setVersion(QDataStream::Qt_3_2);
- else
- in.setVersion(QDataStream::Qt_4_0);
- // Read the data
- in >> lots_of_interesting_data;
- if (version >= 120)
- in >> data_new_in_XXX_version_1_2;
- in >> other_interesting_data;
本文出自 “豆子空間” 博客,請務必保留此出處http://devbean.blog.51cto.com/448512/293892