0、引言
? ? ? ? 我們知道 C 和 C++ 都提供了文件讀寫的類庫,不過 Qt 也有一套自己的文件讀寫操作;本文主要介紹 Qt 中進行文件讀寫操作的類 —— QFile。
1、QFileDialog 文件對話框
? ? ? ? 一般的桌面應用程序,當我們想要打開一個文件時,通常會彈出一個文件對話框。在 Qt 中,文件對話框使用 QFileDialog 類實現。
????????QFileDialog 類允許用戶遍歷文件系統,以便選擇一個或多個文件或目錄。
? ? ? ? 最簡單的創建 QFileDialog 的方法是使用靜態函數:
fileName = QFileDialog::getOpenFileName(this,tr("Open Image"), "/home", tr("Image Files (*.png *.jpg *.bmp)"));
? ? ? ? tr() 是 Qt 中的一個函數,用于將字符串標記為可翻譯的字符串。這個函數的作用是在運行時,根據配置在 Qt Linguist 工具中的翻譯,將輸入字符串翻譯成適當的語言。
????????這意味著,如果您的應用程序支持多種語言,并且在 Qt Linguist 工具中配置了相應的翻譯,那么這些字符串將在運行時被翻譯成用戶選擇的語言。
? ? ? ? 在上面的例子中,我們使用靜態方法創建了一個模態 QFileDialog(什么是 模態對話框?)。該對話框初始顯示 "/home" 目錄中的內容,并顯示與字符串 "Image files (*.png *.jpg *.bmp)" 中給出的模式匹配的文件。文件對話框的父窗口設置為 this,窗口標題設置為 "Open Image"。
? ? ? ? 如果你想要使用多個過濾器,請用 兩個分號 分隔每一個過濾器。例如:
"Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)"
? ? ? ? 如果您想要不使用任何文件過濾器,請直接傳遞空字符串。或者傳遞
"All Files (*)"
? ? ? ? (了解更多...)
????????在上面的例子中我們可以嘗試使用 QFileDialog 選擇一個文件并將 fileName 打印到單行文本框,最終實現效果類似于下圖:
2、QFile
? ? ? ? QFileDialog 只是幫助我們完成了獲取文件的絕對路徑(什么是 絕對路徑(以 Windows 系統為例)?)的工作;接下來才是要進行文件讀寫的操作。在 Qt 中,文件讀寫通常與 QFile 類相關。
? ? ? ? 💬 有關 QFile 的用法,還請讀者參考 QFile 的官方文檔,這里筆者不再贅述。下面主要介紹 QFileDialog 類配合 QFile 類的使用案例:
2.1、QFile 讀操作
? ? ? ? 以下代碼展示了 QFileDialog 類配合 QFile 類打開并讀取一張圖片文件(我們在與 QPushButton::clicked 信號關聯的槽函數中讀取圖片文件):
//點擊“打開”按鈕,彈出文件選擇對話框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){QString fileName = QFileDialog::getOpenFileName(this,tr("Open Image"), "/home" , tr("Image Files (*.png *.jpg *.bmp *.svg)"));//將路徑放到 LineEdit 中ui->lineEdit->setText(fileName);//使用文件的絕對路徑實例化 QFileQFile file(fileName);//只讀模式打開文件file.open(QIODevice::ReadOnly);//全部讀取QByteArray bytes = file.readAll();//生成 QImage 實例QImage image = QImage::fromData(bytes);//生成 QPixmap 實例并顯示在 QLabel 控件上ui->label->setPixmap(QPixmap::fromImage(image));//關閉文件file.close();
});
????????使用 QFileDialog 配合 QFile,我們可以做到在運行時選擇并讀取圖片文件:
????????此外,我們還可以嘗試使用 QFileDialog 配合 QFile 打開文本文件,只需略微修改上面示例代碼中的槽函數即可:
- ?在 Linux(Ubuntu) 下,你可以使用如下命令創建文本文件:
touch filename.txt
- 創建完成后,在其中編輯一些字符,保存后就可以使用如下命令查看該文本文件的字符編碼格式:
file filename.txt# 如果文本文件中只有英文字符,則編碼格式為 ASCII; # 如果帶有中文字符,則編碼格式為 UTF-8 Unicode。
- 使用如下命令轉換文本文件的字符編碼格式(參考鏈接):
sudo iconv -f 'utf-8' -t 'gbk' filename.txt > GbkText.txt
//點擊“打開”按鈕,彈出文件選擇對話框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){QString fileName = QFileDialog::getOpenFileName(this,tr("Open Text File"), "/home" , tr("Text Files (*.txt)"));//將路徑放到 LineEdit 中ui->lineEdit->setText(fileName);//使用文件的絕對路徑實例化 QFileQFile file(fileName);//只讀模式打開文件file.open(QIODevice::ReadOnly);//全部讀取QByteArray bytes = file.readAll();//在 QLabel 文件上顯示文本ui->label->setText(bytes);//關閉文件file.close();
});
????????需要注意的是,QLabel::SetText()?方法接受一個 QString 類型的參數,而在上面的示例代碼中我們傳遞了一個字節數組作為參數。
? ? ? ? 巧合的是,QString 類提供了一個接受 QByteArray 類型的構造函數,這意味著這里會發生一次隱式轉換將 QByteArray 類型轉換為 QString 類型。不過,這個構造函數的轉換將會使用 UTF-8 格式解碼字符串。
? ? ? ? 這樣的隱式轉換可能會導致一些問題,特別是當你的文本文件不是 UTF-8 格式編碼時:
![]()
兩個文本文件內容一致,但 'GBK' 字符編碼格式文件會顯示亂碼 ? ? ? ? 為了避免這樣的問題,我們可以嘗試使用一些 Qt 中專門用于編碼和解碼文本的類,比如 QTextCodec(在 Qt 5.15 過后已經部分棄用)和 QStringConverter 類;或者使用流來讀取文件(使用流的時候,默認情況下,QTextStream 假設文件以 UTF-8 編碼;但這可以使用QTextStream::setEncoding() 來改變)。
2.2、QFile 寫操作
????????除了讀操作,我們還可以使用 QFileDialog 類配合 QFile 類打開并向文件中寫入數據。下面是一個打開文本文件并向其中追加新的文本的示例,同樣略微修改上面示例代碼中的槽函數即可:
//點擊“打開”按鈕,彈出文件選擇對話框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){QString fileName = QFileDialog::getOpenFileName(this,tr("Open Text File"), "/home" , tr("Text Files (*.txt)"));//將路徑放到 LineEdit 中ui->lineEdit->setText(fileName);//使用文件的絕對路徑實例化 QFileQFile file(fileName);//以追加的模式打開文件file.open(QIODevice::Append);//寫數據file.write("這是我寫入的文本!\n");//關閉文件file.close();//重新以只讀方式打開文件file.open(QIODevice::ReadOnly);QByteArray data;//當沒有到達文件末尾時while(!file.atEnd()){//按行讀取data += file.readLine();}ui->label->setText(data);//關閉文件file.close();
});
下面是運行效果:
3、QFileInfo 文件信息
? ? ? ? QFileInfo?提供了文件在文件系統中的名稱和位置(路徑)、訪問權限以及是目錄還是符號鏈接等信息,還有文件的大小、最后修改/讀取時間等信息。此外,QFileInfo 也可以用來獲取關于 Qt 資源的信息。
????????QFileInfo 可以指向具有相對或絕對文件路徑的文件。絕對文件路徑以目錄分隔符 "/" 開頭(或在 Windows 上以驅動器規格開頭)。相對文件名以目錄名或文件名開頭,并指定相對于當前工作目錄的路徑。絕對路徑的一個例子是字符串 "/tmp/quartz"。相對路徑可能看起來像 "src/fatlib"。可以使用函數 isRelative() 檢查 QFileInfo 使用的是相對文件路徑還是絕對文件路徑。您可以調用 makeAbsolute() 函數將 QFileInfo 的相對路徑轉換為絕對路徑。
?? 注意:以冒號(:)開頭的路徑總是被認為是絕對路徑,因為它們表示一個 QResource。
? ? ? ? QFileInfo 處理的文件是在構造函數中設置的(或者后續使用 setFile() 設置)。使用 exists() 查看文件是否存在并使用 size() 獲取其大小。
? ? ? ? 文件的類型通過 isFile()、isDir() 和 isSymLink() 獲得。symLinkTarget() 函數提供了符號鏈接指向的文件的名稱。
????????在 Unix(包括 macOS 和 iOS)上,該類中的屬性獲取器函數返回目標文件的時間和大小等屬性,而不是符號鏈接的時間和大小等屬性,這是因為 Unix 透明地處理符號鏈接。使用 QFile 打開符號鏈接可以有效地打開鏈接的目標。例如:
#ifdef Q_OS_UNIXQFileInfo info1("/home/bob/bin/untabify");
info1.isSymLink(); // returns true
info1.absoluteFilePath(); // returns "/home/bob/bin/untabify"
info1.size(); // returns 56201
info1.symLinkTarget(); // returns "/opt/pretty++/bin/untabify"QFileInfo info2(info1.symLinkTarget());
info2.isSymLink(); // returns false
info2.absoluteFilePath(); // returns "/opt/pretty++/bin/untabify"
info2.size(); // returns 56201#endif
????????在 Windows 上,快捷方式(.lnk 文件)目前被視為符號鏈接。與 Unix 系統一樣,屬性獲取器返回目標文件的大小,而不是 .lnk 文件本身。此行為已被棄用,可能會在 Qt 的未來版本中刪除,之后 .lnk 文件將被視為常規文件。
#ifdef Q_OS_WINQFileInfo info1("C:\\Users\\Bob\\untabify.lnk");
info1.isSymLink(); // returns true
info1.absoluteFilePath(); // returns "C:/Users/Bob/untabify.lnk"
info1.size(); // returns 63942
info1.symLinkTarget(); // returns "C:/Pretty++/untabify"QFileInfo info2(info1.symLinkTarget());
info2.isSymLink(); // returns false
info2.absoluteFilePath(); // returns "C:/Pretty++/untabify"
info2.size(); // returns 63942#endif
? ? ? ? 文件名稱的元素可以使用 path() 和 fileName() 提取。fileName() 的部分可以用 baseName()、suffix()?或 completeSuffix() 提取。QFileInfo 對象到由 Qt 類創建的目錄將不會有尾隨文件分隔符。如果您希望在自己的文件信息對象中使用尾隨分隔符,只需在構造函數或 setFile() 給出的文件名后附加一個。
? ? ? ? 文件的日期由 birthTime()、lastModified()、lastRead() 和 fileTime() 返回。通過 isReadable()、isWritable() 和 isExecutable() 獲取文件的訪問權限信息。文件的所有權可以從 owner()、ownerId()、group() 和 groupId() 中獲得。您可以使用 permission() 函數在單個語句中檢查文件的權限和所有權。
?? 注意:在 NTFS 文件系統上,出于性能原因,默認情況下禁用所有權和權限檢查。要啟用它,包括以下行:
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
然后通過對 qt_ntfs_permission_lookup 加 1 和減 1 來打開和關閉權限檢查。
qt_ntfs_permission_lookup++; //啟用檢查
qt_ntfs_permission_lookup--; //再次關閉檢查
?? 注意:由于這是一個非原子全局變量,所以只有在除主線程以外的任何線程啟動之前或除主線程以外的每個線程結束之后,才可以對 qt_ntfs_permission_lookup 進行遞增或遞減操作。
? ? ? ? 下面是一個 QFileDialog 類配合 QFileInfo 類獲取文件信息的代碼示例,同樣略微修改 2.1 小節示例代碼中的槽函數即可:
//點擊“打開”按鈕,彈出文件選擇對話框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), "/home", tr("All Files (*)"));//將路徑放到 LineEdit 中ui->lineEdit->setText(fileName);QFileInfo info(fileName);QString str = "path() = " + info.path() + "\n"+ "filePath() = " + info.filePath() + "\n"+ "fileName() = " + info.fileName() + "\n"+ "baseName() = " + info.baseName() + "\n"+ "suffix() = " + info.suffix() + "\n"+ "completeSuffix() = " + info.completeSuffix() + "\n"+ "birthTime() = " + info.birthTime().toString(); //toString()方法可指定日期輸出格式ui->label->setText(str);
});
💬 在打印文件的日期信息時,可以指定輸出的格式,比如:
info.birthTime().toString("yyyy/MM/dd hh:mm:ss"); // 2023/08/16 21:12:34
(參考 QDateTime 的 toString() 方法了解更多)
運行效果: