赞
踩
目录
三. 结合使用 QFile 和 QTextStream 读写文本文件
文件读写是很多应用程序具有的功能,有些软件就是围绕着某一种格式文件的处理而开发的。 Qt 提供了很多类,能进行文件系统操作和文件读写,例如获取文件信息、复制或重命名文件、读写文本文件和二进制文件、读写 XML 文件和 JSON 文件。熟悉这些文件操作和文件读写相关类的用法后,就可以根据某种文件的格式编写相应的文件读写程序,也可以自己设计特定格式的文件。
Qt 中进行文件读写的基本的类是 QFile。QFile 的父类是 QFileDevice,QFileDevice 提供了文件交互操作的底层功能。 QFileDevice 的父类是 QIODevice,它有两个父类:QObject 和 QIODeviceBase。
QIODevice 是所有输入输出设备(input/output device,后文简称 I/O 设备)的基础类,I/O 设 备就是能进行数据输入和输出的设备,例如文件是一种 I/O 设备,网络通信中的 socket 是 I/O 设备, 串口、蓝牙等通信接口也是 I/O 设备,所以它们也是从 QIODevice 继承来的。Qt 中主要的一些 I/O 设备类的继承关系如图所示:
• QFile 是用于文件操作和文件数据读写的类,使用 QFile 可以读写任意格式的文件。
• QSaveFile 是用于安全保存文件的类。使用 QSaveFile 保存文件时,它会先把数据写入一个临时文件,成功提交后才将数据写入最终的文件。如果保存过程中出现错误,临时文件里的数据不会被写入最终文件,这样就能确保最终文件中不会丢失数据或被写入部分数据。 在保存比较大的文件或复杂格式的文件时可以使用这个类,例如从网络上下载文件时。
• QTemporaryFile 是用于创建临时文件的类。使用函数 QTemporaryFile::open()就能创建一个文件名唯一的临时文件,在 QTemporaryFile 对象被删除时,临时文件被自动删除。
• QTcpSocket 和 QUdpSocket 是分别实现了 TCP 和 UDP 的类。
• QSerialPort 是实现了串口通信的类,通过这个类可以实现计算机与串口设备的通信。
• QBluetoothSocket 是用于蓝牙通信的类。手机和平板计算机等移动设备有蓝牙通信模块,笔记本电脑一般也有蓝牙通信模块。通过 Qt Bluetooth 模块的一些类,我们可以编写蓝牙通信程序,例如编程实现笔记本电脑与手机的蓝牙通信。
• QProcess 类用于启动外部程序,并且可以给程序传递参数。
• QBuffer 以一个 QByteArray 对象作为数据缓冲区,将 QByteArray 对象当作一个 I/O 设备来读写。
QFile 是进行文件读写时必须用到的一个类,它提供了一些接口函数进行文件的读写操作,文 件读写主要涉及以下一些操作:
• 打开文件:使用函数 open()以不同模式打开文件,如只读、只写、可读可写等模式。
• 读数据:QFile 有多个接口函数可读取文件内容,如 read()、readAll()等。
• 写数据:QFile 有多个接口函数可以向文件写入数据,如 write()、writeData()等。
• 关闭文件,文件使用结束后还必须用函数 close()关闭文件。
使用 QFile 就可以实现文本文件或二进制文件的读写,但是 QFile 只有一些基本的文件数据读写函数,使用起来不够方便。Qt 还提供了两个用于文件流操作的类,其中 QTextStream 能以流方式读写文本文件,QDataStream 能以流方式读写二进制文件,这两个类需要与 QFile 搭配使用。
QTextStream 和 QDataStream 使用流操作符“<<”和“>>”可以很方便地进行各种类型数据的读写,包括 Qt 的一些类的数据,如 QColor、QFont 等对象数据。QDataStream 也可以读写二进制原始数据。
QTextStream 和 QDataStream 的父类是 QIODeviceBase。QIODeviceBase 类还有一个子类 QDebug,QDebug 是将调试信息输出到某种设备的类,可以输出到文件、字符串或 console 窗口。 在使用函数 qDebug()输出调试信息时,实际上是创建了一个默认的 QDebug 对象,通过该对象将 调试信息输出到 Qt Creator 的 Application Output 窗口。
Qt 还提供了一些类用于读写特定格式的文件,如 XML 文件、JSON 文件、图片文件等。
XML 是用于标记结构化数据的一种标记语言,基于 XML 标记的文件与平台无关,是互联网上广泛使用的一种交换数据的文件。Qt 有一个 Qt XML 模块,提供了用于读写 XML 文件的相关类。Qt 提供了两种用于读写 XML 文件的方法,一种是基于文档对象模型(document object model, DOM)的,另一种是基于流的。
基于 DOM 的 API 将 XML 文档用树状结构表示,整个 XML 文档用一个 QDomDocument 对象表示,文档树状结构中的节点都用 QDomNode 及其子类表示。基于 DOM 的 API 在解析 XML 文档后,在内存中保留了文档的对象模型,因而便于操作文档内容。
基于流的方法是使用 QXmlStreamReader 和 QXmlStreamWriter 类进行 XML 文件的读写,这两个类易于使用,与 XML 标准兼容效果好。使用 QXmlStreamReader 类读取 XML 文件时,就是将 XML 文件解析为一系列的标记(token)。
JS 对象标记(JavaScript object notation,JSON)是一种轻量级的数据交换格式。JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻松地传递这个 字符串,并在需要的时候将它还原为各种编程语言所支持的数据格式。相比于 XML 文件,JSON 文件编解码难度低,文件更小。
Qt 提供了一些类可用于解析、修改和保存 JSON 文件。QJsonDocument 是用于读写 JSON 文件 的类,QJsonArray 是封装了 JSON 数组的类,QJsonObject 是封装了 JSON 对象的类,QJsonValue 是 封装了 JSON 值的类。JSON 的数据有 6 种基本数据类型:bool、double、string、array、object、null。
使用 QImage 和 QPixmap 可以直接读取图片文件,这两个类都是从 QPaintDevice 继承来的, 它们在读取图片文件时总是按图片原始大小读取整张图片。Qt 还提供了一个类 QImageReader 用于在读取图片文件时进行更多的控制,例如通过函数 setScaledSize()以指定大小读取图片,可以实 现缩略图显示。
使用 QImage 和 QPixmap 的函数 save()可以直接将图片保存为文件。Qt 还提供了一个 QImageWriter 类,可以实现在保存图片时提供更多的选项,例如设置压缩级别和图片品质。
QImageReader 和 QImageWriter 主要用于读取和保存图片时需要进行一些特殊处理的场合。如 果不需要进行特殊处理,使用 QImage 和 QPixmap 类自带的读取和保存图片文件的函数即可。
除了文件读写的类,Qt 还提供了一些类用于目录和文件操作,例如获取当前目录、新建目录、复 制文件、分离文件的路径和基本文件名、判断文件是否存在等。目录和文件操作主要涉及如下一些类:
• QCoreApplication:可以提取应用程序路径、程序名等信息。
• QFile:可进行文件的复制、删除、重命名等操作。
• QFileInfo:用于获取文件的各种信息,如文件的路径、基本文件名、文件名后缀、文件大小等。
• QDir:用于目录信息获取和目录操作,包括新建目录、删除目录、获取目录下的文件或子目录等。
• QTemporaryDir:用于创建临时目录,临时目录可以在使用后自动删除。
• QTemporaryFile:用于创建临时文件,临时文件可以在使用后自动删除。
• QFileSystemWatcher:用于监视设定的目录和文件,当所监视的目录或文件出现复制、重命名、删除等操作时会发射相应的信号。
这些类基本涵盖了文件操作所需的主要功能,有些功能还可由多个类各自实现,例如 QFile 和 QDir 都具有删除文件、判断文件是否存在的功能。
如图所示的是示 例运行时界面。窗口左侧是一个 QToolBox 组件,一个页面演示一个类的功能,每个页面里都有一些 QPushButton 按钮,一个按钮主要测试一个函数的功能,按钮的标题一般就是使用的函数名称。
窗口右侧是显示区,可以选择一个目录和一个文件,窗口左侧的功能按钮基本上都是对所选择的目录或文件进行操作。窗口右下方是一个 QPlainTextEdit 组件,用于显示信息。
使用向导创建本示例项目时,选择窗口基类为 QDialog。界面上的按钮较多,我们没有对每个按钮专门命名,而使用自动编号的对象名称。界面的可视化设计结果见 UI 文件 dialog.ui,不再赘述。
在 Dialog 类中增加了一些自定义变量和函数,Dialog 类的定义代码如下:
- class Dialog : public QDialog
- {
- Q_OBJECT
- private:
- QFileSystemWatcher fileWatcher; //用于监视文件和目录
- void showBtnInfo(QObject *btn); //显示按钮的标题和 toolTip 提示信息
- public:
- Dialog(QWidget *parent = 0);
- public slots:
- void do_directoryChanged(const QString &path);
- void do_fileChanged(const QString &path);
- private:
- Ui::Dialog *ui;
- }
QFileSystemWatcher 类型的变量 fileWatcher 用于监视设定的文件和目录,两个自定义槽函数用于与 fileWatcher 的信号关联。函数 showBtnInfo()用于显示每个按钮的标题和 toolTip 提示信息, 每个按钮的槽函数里都会调用这个函数。
Dialog 类的构造函数代码如下:
- Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
- {
- ui->setupUi(this);
- this->setWindowFlags(Qt::Window); //使窗口具有最小化和最大化按钮
- }
上述代码设置了窗口标志 Qt::Window,这样使得窗口标题栏上有最小化和最大化按钮,因为窗口基类是 QDialog,默认只有关闭按钮。
界面上每个按钮一般用函数名称作为标题,例如“QFileInfo 类”页面的标题为“baseName()” 的按钮用于测试 QFileInfo 的 baseName()函数。另外,我们将所测试函数的 Qt 帮助文档里的基本描述文字作为按钮的 toolTip 文字,例如标题为“baseName()”的按钮的 toolTip 属性是“Returns the base name of the file without the path”。
在按钮被点击时,程序会先显示按钮的标题和 toolTip 信息。例如,标题为“baseName()”的按钮的 clicked()信号的槽函数代码如下:
- void Dialog::on_pushButton_30_clicked()
- { //QFileInfo::basename()
- showBtnInfo(sender()); //显示按钮的标题和 toolTip 信息
- QFileInfo fileInfo(ui->editFile->text());
- QString str= fileInfo.baseName();
- ui->plainTextEdit->appendPlainText(str+"\n");
- }
这里调用了 Dialog 类中的一个自定义函数 showBtnInfo(),这个函数的实现代码如下:
- void Dialog::showBtnInfo(QObject *btn)
- {
- QPushButton *theBtn = static_cast<QPushButton*>(btn);
- ui->plainTextEdit->appendPlainText(theBtn->text());
- ui->plainTextEdit->appendPlainText(theBtn->toolTip()+"\n");
- }
函数 showBtnInfo()的输入参数 btn 是一个 QObject 类型指针,程序中用函数 static_cast()将 btn 转换为 QPushButton 类型指针,这样就可以显示按钮的 text()和 toolTip()函数返回的内容。
按钮的 clicked()信号的槽函数里调用函数 showBtnInfo()的代码是:showBtnInfo(sender());
这里用到了函数 QObject::sender(),该函数用于在槽函数里获取发射信号的对象。因为这个槽函数是 QPushButton 按钮的 clicked()信号的槽函数,所以,函数 sender()获取的信号发射对象就是 这个按钮。这样编写代码的优点是没有出现按钮的对象名称,在每个按钮的 clicked()信号的槽函 数里运行这一条语句均适用。
QCoreApplication 是为无 UI 的应用程序提供事件循环的类,是所有应用程序类的基类,其子类 QGuiApplication 是具有 GUI 的应用程序类,它具有主事件循环,能处理和派发来自操作系统的事件或其他来源的事件。QGuiApplication 的子类 QApplication 为基于 QWidget 的应用程序提供支 持,包括界面的初始化等。使用 Qt Creator 的向导创建的 Qt Widget Application 项目都是基于 QApplication 类的应用程序,在 main()函数里可以看到应用了 QApplication 类。
QCoreApplication 提供了一些有用的静态函数,可以获取应用程序的名称、启动路径等信息。 QCoreApplication 的与应用程序信息相关的几个静态函数如下表所示,表中省略了函数参数中的 const 关键字。
函数原型 | 功能 |
QString applicationDirPath() | 返回应用程序可执行文件所在的路径 |
QString applicationFilePath() | 返回应用程序的带有路径的完整文件名 |
QString applicationName() | 返回应用程序名称,默认是无后缀的可执行文件名 |
void setApplicationName(QString &application) | 设置应用程序名称,替代默认的应用程序名称 |
QStringList libraryPaths() | 返回一个字符串列表,其是应用程序动态加载库文件时搜索的 目录列表 |
void addLibraryPath(QString &path) | 将一个路径添加到应用程序的库搜索目录列表中 |
void setOrganizationName(QString &orgName) | 为应用程序设置一个组织名 |
QString organizationName() | 返回应用程序的组织名 |
void exit() | 退出应用程序 |
窗口上“QCoreApplication 类”页面的按钮会测试 QCoreApplication 类的这些函数,其中两个按钮的槽函数代码如下:
- void Dialog::on_pushButton_62_clicked()
- {//QCoreApplication::applicationName()
- showBtnInfo(sender());
- QCoreApplication::setApplicationName("MyApp"); //设置应用程序名称
- QString str= QCoreApplication::applicationName(); //返回应用程序名称
- ui->plainTextEdit->appendPlainText(str+"\n");
- }
- void Dialog::on_pushButton_61_clicked()
- {//QCoreApplication::organizationName()
- showBtnInfo(sender());
- QCoreApplication::setOrganizationName("UPC"); //设置组织名
- QString str= QCoreApplication::organizationName(); //返回组织名
- ui->plainTextEdit->appendPlainText(str+"\n");
- }
在创建 QSettings 对象时会默认使用 QCoreApplication 的 applicationName()和 organizationName() 函数的返回值,用于设置读写的注册表目录.
QFile 除了能用于进行文件内容的读写,还有一些静态函数和接口函数可用于文件操作,例如复制文件、删除文件、重命名文件等。下表是 QFile 用于文件操作的一些静态函数,表中省略了函数参数中的 const 关键字。
函数原型 | 功能 |
bool copy(QString &fileName, QString &newName) | 复制一个文件 |
bool rename(QString &oldName, QString &newName) | 重命名一个文件 |
bool remove(QString &fileName) | 删除一个文件 |
bool moveToTrash(QString &fileName, QString *pathInTrash = nullptr) | 将一个文件移除到回收站 |
bool exists(QString &fileName) | 判断一个文件是否存在 |
bool link(QString &fileName, QString &linkName) | 创建文件链接,在 Windows 上就是创建快捷方式 |
QString symLinkTarget(QString &fileName) | 返回一个链接指向的绝对文件名或路径 |
bool setPermissions(QString &fileName, QFileDevice::Permissions permissions) | 设置一个文件的权限,权限是枚举类型 QFileDevice::Permission 的枚举值组合 |
QFileDevice::Permissions permissions(QString &fileName) | 返回文件的权限 |
例如使用静态函数 moveToTrash()将一个文件移除到回收站的代码如下:
- void Dialog::on_pushButton_63_clicked()
- {//QFile 的静态函数 exists()、moveToTrash()
- showBtnInfo(sender());
- QString sous= ui->editFile->text(); //源文件
- if (QFile::exists(sous)) //判断文件是否存在
- {
- QFile::moveToTrash(sous); //将文件移除到回收站
- ui->plainTextEdit->appendPlainText("文件移除到回收站:"+sous+"\n");
- }
- else
- ui->plainTextEdit->appendPlainText("文件不存在\n");
- }
QFile 还提供了一些接口函数,可以对 QFile 的当前文件进行操作。这些函数如下表所示, 表中省略了函数参数中的 const 关键字.
函数原型 | 功能 |
Qvoid setFileName(QString &name) | 设置文件名,文件已打开后不能再调用此函数 |
QString fileName() | 返回当前所操作的文件名 |
bool copy(QString &newName) | 当前文件复制为 newName 表示的文件 |
bool rename(QString &newName) | 将当前文件重命名为 newName |
bool remove() | 删除当前文件 |
bool moveToTrash() | 将当前文件移除到回收站 |
bool exists() | 判断当前文件是否存在 |
bool link(QString &linkName) | 为当前文件创建一个链接,在 Windows 上就是创建快捷方式 |
QString symLinkTarget() | 返回链接指向的绝对文件名或路径 |
bool setPermissions(QFileDevice::Permissions permissions) | 为当前文件设置权限,权限是枚举类型 QFileDevice::Permission 的枚举值组合 |
QFileDevice::Permissions permissions() | 返回当前文件的权限 |
qint64 size() | 返回当前文件的大小,单位是字节 |
表中的函数都是针对QFile对象设定的当前文件进行操作的,需要用QFile的setFileName()函数指定当前文件,或者在创建 QFile 对象时指定文件。如果用 QFile 的函数 open()打开了一个文 件,就不能再用 copy()、rename()等函数对一个打开的文件进行操作。
窗口上演示 QFile 的成员函数 exists()和 remove()的两个按钮的槽函数代码如下:
- void Dialog::on_pushButton_54_clicked()
- {//QFile::exists()
- showBtnInfo(sender());
- QString sous= ui->editFile->text();
- QFile file;
- file.setFileName(sous);
- if(file.exists()) //判断文件是否存在
- ui->plainTextEdit->appendPlainText(+"true \n");
- else
- ui->plainTextEdit->appendPlainText(+"false \n");
- }
- void Dialog::on_pushButton_55_clicked()
- {//QFile::remove()
- showBtnInfo(sender());
- QString sous= ui->editFile->text(); //源文件
- QFile file(sous); //定义变量时就指定文件名
- file.remove(); //删除文件
- ui->plainTextEdit->appendPlainText("删除文件:"+sous+"\n");
- }
QFileInfo 类用于获取文件的各种信息。创建 QFileInfo 对象时可以指定一个文件名使该文件作为当前文件,也可以用函数 setFile()指定一个文件作为当前文件。常用的两种构造函数定义如下:
- QFileInfo(const QFileInfo &fileinfo) //指定文件名
- QFileInfo() //不指定文件名
QFileInfo 常用接口函数如下表所示,表中省略了函数参数中的 const 关键字。除了静态函数 exists(),其他都是公共接口函数,接口函数的操作都是针对 QFileInfo 对象的当前文件的。
函数原型 | 功能 |
void setFile(QString &file) | 设置一个文件名,使该文件作为 QFileInfo 对象操作的当前文件 |
QString absoluteFilePath() | 返回包含文件名的绝对路径 |
QString absolutePath() | 返回绝对路径,不包含文件名 |
QDir absoluteDir() | 返回绝对路径,返回值是 QDir 类型 |
QString fileName() | 返回去除路径的文件名 |
QString filePath() | 返回包含路径的文件名 |
QString path() | 返回不含文件名的路径 |
qint64 size() | 返回文件大小,单位是字节 |
QString baseName() | 返回文件基名,第一个“.”之前的文件名 |
QString completeBaseName() | 返回文件基名,最后一个“.”之前的文件名 |
QString suffix() | 返回最后一个“.”之后的后缀 |
QString completeSuffix() | 返回第一个“.”之后的后缀 |
bool isDir() | 判断当前对象是不是一个目录或目录的快捷方式 |
bool isFile() | 判断当前对象是不是一个文件或文件的快捷方式 |
bool isExecutable() | 判断当前文件是不是可执行文件 |
QDateTime fileTime(QFile::FileTime time) | 返回文件的时间,参数 time 是枚举类型 QFile::FileTime,用于指定需要返回的时间数据类型,如文件创建时间、最后一次读写时间等 |
QDateTime birthTime() | 返回文件创建的时间 |
QDateTime lastModified() | 返回文件最后被修改的时间 |
QDateTime lastRead() | 返回文件最后被读取的时间 |
QDateTime metadataChangeTime() | 返回文件的元数据最后被修改的时间 |
void refresh() | 刷新文件信息 |
bool exists() | 判断文件是否存在 |
bool exists(QString &file) | 静态函数,判断 file 表示的文件是否存在 |
函数 fileTime()可以返回文件的多种时间,参数 time 是枚举类型 QFile::FileTime,其各枚举常 量的含义如下:
• QFileDevice::FileAccessTime:最后一次读或写文件的时间。
• QFileDevice::FileBirthTime:文件创建的时间。
• QFileDevice::FileMetadataChangeTime:文件的元数据被修改的时间,如文件的权限被修改。
• QFileDevice::FileModificationTime:文件最后被修改的时间。
函数 fileTime()返回的时间实际上可以用专门的函数获取,例如 fileTime(QFileDevice::FileBirthTime) 返回的时间与函数 birthTime()返回的是等效的。
QFileInfo 的这些函数可用于提取文件的各种信息,包括目录名、不带后缀的文件名、文件后 缀等。利用这些函数可以实现灵活的文件操作,例如,下面的代码利用静态函数 QFile::rename() 和 QFileInfo 的一些函数实现文件重命名:
- void Dialog::on_pushButton_50_clicked()
- {//QFile::rename()
- showBtnInfo(sender());
- QString sous= ui->editFile->text(); //源文件
- QFileInfo fileInfo(sous); //源文件信息
- QString newFile= fileInfo.path()+"/"+fileInfo.baseName()+".XYZ"; //更改文件后缀
- QFile::rename(sous,newFile); //重命名文件
- ui->plainTextEdit->appendPlainText("源文件:" +sous);
- ui->plainTextEdit->appendPlainText("重命名为:" +newFile+"\n");
- }
QDir 是进行目录操作的类,可以在 QDir 的构造函数里传递一个目录字符串作为当前目录, 或者在创建 QDir 对象后使用函数 setPath()设置当前目录。QDir 常用的构造函数定义如下:
QDir(const QString &path = QString())
QDir 的目录字符串以“/”作为目录分隔符,可以表示相对路径或绝对路径。绝对路径以“/” 或盘符开始,例如下面表示的都是绝对路径:
- QDir("/home/user/Documents") //Linux 上的绝对路径
- QDir("C:/Users/wwb") //Windows 上的绝对路径
QDir 还可以操作 Qt 的资源文件,“:/”是资源文件的目录起始符,资源文件目录是绝对路径,如:
QDir(":/images/icons") //表示资源文件中的目录
QDir 表示的相对路径针对的是应用程序的当前路径,相对路径直接以目录名称开始,如:
QDir("sampleData/") //相对路径
QDir 有一些静态函数可以获取应用程序的当前目录、驱动器列表等信息。QDir 的主要静态函数如下表所示,表中省略了函数参数中的 const 关键字。
函数原型 | 功能 |
QString tempPath() | 返回系统的临时目录,在Windows系统上就是系统环境变量TEMP 所指向的目录 |
QDir temp() | 返回系统的临时目录,返回值是 QDir 类型 |
QString rootPath() | 返回系统根目录。在 Unix 系统上就是“/”,在 Windows 系统上通常是“C:/” |
QDir root() | 返回系统根目录,返回值是 QDir 类型 |
QString homePath() | 返回用户主目录,在 Windows 系统上就是用户主目录,如“C:/ Users/wwb” |
QDir home() | 返回用户主目录,返回值是 QDir 类型 |
QString currentPath() | 返回应用程序的当前目录 |
QDir current() | 返回应用程序的当前目录,返回值是 QDir 类型 |
bool setCurrent(QString &path) | 设置 path 表示的目录作为应用程序的当前目录 |
QFileInfoList drives() | 返回系统的根目录列表,在 Windows 系统上返回的是盘符列表 |
bool isAbsolutePath(QString &path) | 判断 path 表示的目录是不是绝对路径 |
bool isRelativePath(QString &path) | 判断 path 表示的目录是不是相对路径 |
在使用 QFileDialog 选择打开文件或目录时需要传递一个初始目录,可以用 QDir::currentPath() 获取应用程序的当前目录作为初始目录,前面一些示例的代码中已经用过这个功能。如果 QDir 对象的目录名称是相对路径,那么绝对路径就是基于 QDir::currentPath()的。
静态函数 QDir::drives()返回系统的根目录列表,在 Windows 系统上返回的就是盘符列表。该 函数的返回值是 QFileInfo 的列表,通过函数 QFileInfo::path()可以返回驱动器路径名称,如“C:/” “D:/”等。窗口上“QDir 类”页面的标题为“drives()”的按钮的槽函数代码如下:
- void Dialog::on_pushButton_7_clicked()
- {//QDir::drives()
- showBtnInfo(sender());
- QFileInfoList driverList= QDir::drives(); //返回驱动器列表
- for(int i=0; i<driverList.size(); i++)
- ui->plainTextEdit->appendPlainText(driverList.at(i).path());
- ui->plainTextEdit->appendPlainText("\n");
- }
下表所示的是 QDir 的一些公共接口函数,表中省略了函数参数中的 const 关键字。
函数原型 | 功能 |
void setPath(QString &path) | 设置 QDir 对象的当前目录 |
QString path() | 返回 QDir 对象的当前目录,即用 setPath()设置的目录 |
QString absoluteFilePath(QString &fileName) | 返回当前目录下的文件 fileName 的含有绝对路径的文件名 |
QString absolutePath() | 返回当前目录的绝对路径。如果 QDir 对象的当前目录是相对路径, 返回的是相对于应用程序当前路径的绝对路径。应用程序的当前路 径是静态函数 QDir::currentPath()返回的路径 |
QString canonicalPath() | 返回当前目录的标准路径 |
QString filePath(QString &fileName) | 如果 fileName 是不带有路径的文件名,函数返回值是带有操作目录 的完整文件名 |
QString dirName() | 返回当前目录的最后一级目录名称 |
bool cd(QString &dirName) | 切换当前目录到 dirName 表示的目录 |
bool cdUp() | 切换当前目录到上一级目录 |
bool exists() | 判断当前目录是否存在 |
bool exists(QString &name) | 如果 name 是不带有路径的文件名,判断当前目录下是否存在这个文件 |
void refresh() | 刷新目录信息 |
bool mkdir(QString &dirName) | 在当前目录下创建 dirName 表示的子目录 |
bool mkpath(QString &dirPath) | 创建绝对目录 dirPath,如有必要会创建所有上级目录 |
bool rmdir(QString &dirName) | 删除指定的目录 dirName |
bool rmpath(QString &dirPath) | 删除 dirPath 表示的目录,包括上级目录,需要上级目录是空的 |
bool remove(QString &fileName) | 删除当前目录下的文件 fileName |
bool removeRecursively() | 删除当前目录及其下所有目录和文件 |
bool rename(QString &oldName, QString &newName) | 将文件或目录 oldName 更名为 newName |
bool isEmpty(QDir::Filters filters = Filters(AllEntries | NoDotAndDotDot)) | 判断当前目录是否为空 |
void setFilter(QDir::Filters filters) | 设置使用函数 entryList()和 entryInfoList()时的过滤器 |
void setSorting(QDir::SortFlags sort) | 设置使用函数 entryList()和 entryInfoList()时的过滤器 |
QStringList entryList(QDir::Filters filters = NoFilter, QDir::SortFlags sort = NoSort) | 返回当前目录下的所有文件名、子目录名等 |
QFileInfoList entryInfoList(QDir::Filters filters = NoFilter, QDir::SortFlags sort = NoSort) | 返回当前目录下的所有文件、子目录等,返回值是 QFileInfo 对象列表 |
在创建 QDir 对象时,需要在构造函数里传递一个目录名称表示 QDir 对象的当前目录。如果 创建 QDir 对象时不传递目录名称,QDir 对象会自动以应用程序的当前路径作为自己的当前目录。 应用程序的当前路径就是静态函数 QDir::currentPath()返回的路径。
isEmpty()、setFilter()、entryList()等函数需要一个 QDir::Filters 类型的参数,参数是枚举类型 QDir::Filter 的枚举值的组合。枚举类型 QDir::Filter 表示目录下的过滤选项,其常用的枚举值含义如下:
• QDir::AllDirs:列出所有目录。
• QDir::Files:列出所有文件。
• QDir::Drives:列出所有驱动器(Unix 系统下无效)。
• QDir::NoDotAndDotDot:不列出特殊的项,如“.”和“..”。
• QDir::AllEntries:列出目录下的所有项。
窗口上标题为“entryList(dir)”的按钮的功能是使用函数 entryList()列出一个目录下的所有子 目录,其槽函数代码如下:
- void Dialog::on_pushButton_11_clicked()
- {//列出子目录
- showBtnInfo(sender());
- QDir dir(ui->editDir->text());
- QStringList strList= dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
- ui->plainTextEdit->appendPlainText("所选目录下的所有目录:");
- for(int i=0; i<strList.size(); i++)
- ui->plainTextEdit->appendPlainText(strList.at(i));
- ui->plainTextEdit->appendPlainText("\n");
- }
另外一个标题为“entryList(file)”的按钮的功能是使用函数 entryList()列出一个目录下的所有文件,其槽函数代码与上述这段代码相似,只是调用函数 entryList()时传递了不同的参数:
QStringList strList= dir.entryList(QDir::Files);
QTemporaryDir 用于创建临时目录,它有两种参数形式的构造函数,其函数原型定义如下:
- QTemporaryDir() //在系统的临时目录下创建临时目录
- QTemporaryDir(const QString &templatePath) //在指定目录下创建临时目录
如果在创建 QTemporaryDir 对象时不传递任何参数,就在静态函数 QDir::tempPath()表示的系 统临时目录下创建一个临时文件夹。临时文件夹自动以“applicationName-××××××”的形式命名, 其中的 applicationName 就是静态函数 QCoreApplication::applicationName()返回的应用程序名称, “××××××”表示 6 个随机字母。QTemporaryDir 创建的临时文件夹能确保是唯一的。
如果使用带有参数的构造函数,参数 templatePath 是临时目录模板。如果 templatePath 是相对 目录,就在 QDir::currentPath()表示的当前目录下创建临时目录,否则就在指定的绝对路径下创建 临时目录。templatePath 可以对临时文件夹使用命名模板,只需在最后用“××××××”表示 6 个随 机字母。例如下面的代码:
QTemporaryDir dir("SubDir_XXXXXX");
这样会在 QDir::currentPath()表示的当前目录下创建一个临时文件夹,文件夹的前缀是“SubDir_”.
QTemporaryDir 的一些接口函数如下表所示,表中省略了函数中的 const 关键字。
函数原型 | 功能 |
void setAutoRemove(bool b) | 若设置为 true,QTemporaryDir 对象被删除时,其创建的临时目录也被自动删除 |
QString path() | 返回创建的临时目录名称,如果临时目录未被成功创建,返回值是空字符串 |
bool isValid() | 如果临时目录被成功创建,该函数返回值为 true |
QString filePath(QString &fileName) | 返回临时目录下的一个文件的带有路径的文件名,函数不会检查文件是否存在 |
bool remove() | 删除此临时目录及其所有内容 |
窗口上“QTemporaryDir 类”页面上 3 个按钮的槽函数代码如下:
- void Dialog::on_pushButton_21_clicked()
- {//在系统临时目录下创建临时文件夹
- showBtnInfo(sender());
- ui->plainTextEdit->appendPlainText("QDir::tempPath()= " + QDir::tempPath());
- QTemporaryDir dir; //不传递任何参数,在系统临时目录下创建临时目录
- dir.setAutoRemove(true); //自动删除临时目录
- ui->plainTextEdit->appendPlainText(dir.path()+"\n");
- }
- void Dialog::on_pushButton_67_clicked()
- {//在指定目录下创建临时文件夹
- showBtnInfo(sender());
- QString specDir= ui->editDir->text(); //界面上设置的目录
- ui->plainTextEdit->appendPlainText("指定目录= "+specDir);
- QTemporaryDir dir(specDir+"/TempDir_XXXXXX"); //文件夹名称模板,绝对路径
- dir.setAutoRemove(false); //不自动删除
- ui->plainTextEdit->appendPlainText(dir.path()+"\n");
- }
- void Dialog::on_pushButton_68_clicked()
- {//在当前目录下创建临时文件夹
- showBtnInfo(sender());
- ui->plainTextEdit->appendPlainText("当前目录= " + QDir::currentPath()+'\n');
- QTemporaryDir dir("SubDir_XXXXXX"); //文件夹名称模板,相对路径
- dir.setAutoRemove(false); //不自动删除
- ui->plainTextEdit->appendPlainText(dir.path()+"\n");
- }
QTemporaryFile 用于创建临时文件,临时文件可以保存在系统临时目录、指定目录或应用程序当前目录下。QTemporaryFile 的父类是 QFile,QTemporaryFile 有多种参数形式的构造函数,定义如下:
- QTemporaryFile(const QString &templateName, QObject *parent)
- QTemporaryFile(QObject *parent)
- QTemporaryFile(const QString &templateName) //指定临时文件名模板
- QTemporaryFile() //在系统临时目录下创建临时文件
在这几个函数中,参数 parent 是父容器对象指针,一般指定为所在的窗口对象;参数 templateName 是文件名模板。
如果在创建 QTemporaryFile 对象时不设置文件名模板,就会在静态函数 QDir::tempPath()表示 的系统临时目录下创建一个临时文件,文件名自动以“applicationName.××××××”的形式命名。其 中的 applicationName 是静态函数 QCoreApplication::applicationName()返回的应用程序名称, “××××××”表示 6 个随机字母。
可以在创建 QTemporaryFile 对象时设置文件名模板 templateName。如果 templateName 是带有 相对路径的文件名,就会在 QDir::currentPath()表示的当前目录下创建临时文件,否则就在指定的 绝对路径下创建临时文件。templateName 可以设置临时文件的名称模板,用“××××××”表示 6 个 随机字母,且其可以放在文件名中的任何位置。
创建 QTemporaryFile 对象时只是设置了临时文件的文件名,使用函数 QTemporaryFile::open()打开 文件时才会实际创建文件,文件使用后需要用函数 QFile::close()关闭它。如果用函数 setAutoRemove() 设置为自动删除,则 QTemporaryFile 对象被删除时其创建的临时文件会被自动删除。
QTemporaryFile 的一些接口函数如下表所示,表中省略了函数中的 const 关键字。
函数原型 | 功能 |
void setAutoRemove(bool b) | 若设置为true,QTemporaryFile对象被删除时,其创建的临时文件也会被自动删除 |
void setFileTemplate(QString &name) | 设置临时文件的文件名模板 |
QString fileTemplate() | 返回临时文件的文件名模板 |
bool open() | 打开临时文件,且总是以 QIODevice::ReadWrite 模式打开 |
窗口上“QTemporaryFile 类”页面上 3 个按钮的槽函数代码如下:
- void Dialog::on_pushButton_25_clicked()
- {//在系统临时目录下创建临时文件
- showBtnInfo(sender());
- ui->plainTextEdit->appendPlainText("QDir::tempPath()= " + QDir::tempPath());
- QTemporaryFile aFile; //在系统临时目录下创建临时文件
- aFile.setAutoRemove(true); //自动删除
- aFile.open();
- ui->plainTextEdit->appendPlainText(aFile.fileName()+"\n");
- aFile.close();
- }
- void Dialog::on_pushButton_69_clicked()
- {//在指定目录下创建临时文件
- showBtnInfo(sender());
- QString specDir= ui->editDir->text(); //界面上设置的目录
- ui->plainTextEdit->appendPlainText("指定目录= " + specDir);
- QTemporaryFile aFile(specDir+"/我的文件_XXXXXX.tmp"); //文件名模板,带有绝对路径
- aFile.setAutoRemove(false); //不自动删除
- aFile.open();
- ui->plainTextEdit->appendPlainText(aFile.fileName()+"\n");
- aFile.close();
- }
- void Dialog::on_pushButton_70_clicked()
- {//在当前目录下创建临时文件
- showBtnInfo(sender());
- ui->plainTextEdit->appendPlainText("QDir::currentPath()= " + QDir::currentPath());
- QTemporaryFile aFile("图片 XXXXXX.tmp"); //文件名模板,当前目录下
- aFile.setAutoRemove(false); //不自动删除
- aFile.open();
- ui->plainTextEdit->appendPlainText(aFile.fileName()+"\n");
- aFile.close();
- }
QFileSystemWatcher 是对目录和文件进行监视的类,其父类是 QObject。把某些目录或文件添 加到 QFileSystemWatcher 对象的监视列表后,当目录下发生新建、删除文件等操作时, QFileSystemWatcher 会发射 directoryChanged()信号;当所监视的文件发生修改、重命名等操作时, QFileSystemWatcher 会发射 fileChanged()信号。
QFileSystemWatcher 的主要接口函数如下表所示,表中省略了函数参数中的 const 关键字。
函数原型 | 功能 |
bool addPath(QString &path) | 添加一个监视的目录或文件 |
QStringList addPaths(QStringList &paths) | 添加需要监视的目录或文件列表 |
QStringList directories() | 返回监视的目录列表 |
QStringList files() | 返回监视的文件列表 |
bool removePath(QString &path) | 移除监视的目录或文件 |
QStringList removePaths(QStringList &paths) | 移除监视的目录或文件列表 |
QFileSystemWatcher 有两个信号,这两个信号在目录变化或文件变化时分别被发射。
- void QFileSystemWatcher::directoryChanged(const QString &path) //目录发生了变化
- void QFileSystemWatcher::fileChanged(const QString &path) //文件发生了变化
下图所示的是示例中测试 QFileSystemWatcher 的界面。首先打开一个目录和一个文件,点击“addPath()并开始监视”按钮将目录和文件 都添加到监视列表中,并且将信号与槽函数关联起来。然后在目录下复制某个文件,这会触发 directoryChanged()信号,重命名所监 视的文件后会触发 fileChanged()信号,运行结果如图所示:
为了测试 QFileSystemWatcher 的功能, 我们在窗口类中定义了 QFileSystemWatcher 类型的变量 fileWatcher 和两个槽函数。两个自定义槽函数的代码如下:
- void Dialog::do_directoryChanged(const QString &path)
- { //directoryChanged()信号的槽函数
- ui->plainTextEdit->appendPlainText(path);
- ui->plainTextEdit->appendPlainText("目录发生了变化\n");
- }
- void Dialog::do_fileChanged(const QString &path)
- {//fileChanged()信号的槽函数
- ui->plainTextEdit->appendPlainText(path);
- ui->plainTextEdit->appendPlainText("文件发生了变化\n");
- }
图中 QFileSystemWatcher 分组里“addPath()并开始监视”和“removePath()并停止监视” 两个按钮的槽函数代码如下:
- void Dialog::on_pushButton_46_clicked()
- {//开始监视,addPath()
- showBtnInfo(sender());
- ui->plainTextEdit->appendPlainText("监视目录:" + ui->editDir->text()+"\n");
- fileWatcher.addPath(ui->editDir->text()); //添加监视目录
- fileWatcher.addPath(ui->editFile->text()); //添加监视文件
- connect(&fileWatcher,&QFileSystemWatcher::directoryChanged,
- this,&Dialog::do_directoryChanged); //directoryChanged()信号
- connect(&fileWatcher,&QFileSystemWatcher::fileChanged,
- this,&Dialog::do_fileChanged); //fileChanged()信号
- }
- void Dialog::on_pushButton_47_clicked()
- {//停止监视,removePath()
- showBtnInfo(sender());
- ui->plainTextEdit->appendPlainText("停止监视目录:" + ui->editDir->text()+"\n");
- fileWatcher.removePath(ui->editDir->text()); //移除监视的目录
- fileWatcher.removePath(ui->editFile->text()); //移除监视的文件
- disconnect(&fileWatcher); //解除 fileWatcher 所有信号的连接
- }
用函数 addPath()添加多个监视的目录和文件后,fileWatcher 自动开始监视。程序中将 fileWatcher 的两个信号与自定义槽函数关联,当监视的目录或文件发生变化时就会显示信息。
用函数 removePath()移除监视的目录和文件,就可以停止对这些目录或文件的监视。
文本文件是指以纯文本格式存储的文件, 例如 C++程序的头文件和源程序文件就是文本文件。XML 文件和 JSON 文件也是文本文件, 只是它们使用了特定的标记符号定义文本的含义,读取这种文本文件时需要先对内容进行解析再显示。
Qt 提供了两种读写文本文件的方法,一种 是用 QFile 类直接读写文本文件,另一种是将 QFile 和 QTextStream 结合起来,用流(stream)方法进行文本文件读写。
示例运行时界面如图表示如何使用这两种方法读写文本文件:
示例窗口类是基于 QMainWindow 的窗口类 MainWindow。在 UI 可视化设计时,我们删除了主窗口上的菜单栏,创建几个 Action 用于设计工具栏。主窗口工作区放置了一个 QTabWidget 组件,设计了两个页面,每个页面上放置一个 QPlainTextEdit 组件。 我们在 MainWindow 类中定义了几个私有函数用于文本文件读写,后面介绍具体内容时再介 绍这几个私有函数。
MainWindow 类的构造函数代码如下:
- MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- ui->tabWidget->setTabsClosable(false); //不允许关闭分页
- ui->tabWidget->setDocumentMode(true); //文档模式,无边框
- this->setCentralWidget(ui->tabWidget);
- }
界面组件 tabWidget 中只有两个页面,且不允许关闭,所以不显示分页的关闭按钮。组件 tabWidget 的每个页面上只有一个 QPlainTextEdit 组件填充满页面,所以将 tabWidget 设置为文档模式。
QFile 主要的功能是进行文件读写,它可以读写文本文件或二进制文件。QFile 读写文件相关 的接口函数如下表所示,这些函数中有的是 QFile 里定义的,有的是其上层父类里定义的。表中省略了函数参数中的 const 关键字。
函数原型 | 功能 |
void setFileName(QString &name) | 设置文件名,在调用 open()函数打开文件后不能再设置文件名 |
bool open(QIODeviceBase::OpenMode mode) | 打开文件,以只读、只写、可读可写等模式打开文件 |
qint64 read(char *data, qint64 maxSize) | 读取最多 maxSize 字节的数据,存入缓冲区 data,函数返回值是实际读取的字节数 |
QByteArray read(qint64 maxSize) | 读取最多 maxSize 字节的数据,返回为字节数组 |
QByteArray readLine(qint64 maxSize = 0) | 读取一行文本,以换行符“\n”判断一行的结束。读取的内容返回为字节数组,在数组末尾会自动添加一个结束符“\0” |
QByteArray readAll() | 读取文件的全部内容,返回为字节数组 |
bool getChar(char *c) | 从文件读取一个字符,存入 char*指针指向的变量里 |
qint64 write(char *data, qint64 maxSize) | 将缓冲区 data 里的数据写入文件,最多写入 maxSize 字节的数据,函数的返回值为实际写入的字节数。这个函数用于写入任意类型的数据 |
qint64 write(char *data) | 这个函数用于写入 char*类型的字符串数据。data 是以“\0”作为结束 符的字符串的首地址,函数返回值是实际写入文件的字节数 |
qint64 write(QByteArray &data) | 将一个字节数组的数据写入文件,函数返回值是实际写入的字节数 |
bool putChar(char c) | 向文件写入一个 char 类型字符。getChar()和 putChar()一般用于 I/O 设备 的数据读写,例如串口数据读写 |
bool flush() | 将任何缓存的数据保存到文件里,其作用相当于函数 close(),但是不关闭文件。调用此函数可以防止未正常调用 close()函数而导致的数据丢失 |
void close() | 文件读写操作完成后关闭文件 |
bool atEnd() | 判断是否到达文件末尾,返回 true 表示已经到达文件末尾 |
bool reset() | 返回到文件的起始位置,可以从头开始读写 |
QFile 有多种参数形式的构造函数,常用的几种构造函数定义如下:
- QFile(const QString &name, QObject *parent) //指定文件名和父容器对象
- QFile(QObject *parent) //指定父容器对象
- QFile(const QString &name) //指定文件名
- QFile() //不做任何初始设置
如果在创建 QFile 对象时没有指定文件名,可以用函数 setFileName()设置文件名。注意,在调用函数 open()打开文件后,就不能再调用 setFileName()设置文件名。
用 QFile 进行文件内容读写的基本操作步骤是:(1)调用函数 open()打开或创建文件;(2)用 读写函数读写文件内容;(3)调用函数 close()关闭文件。调用 close()会将缓存的数据写入文件,如果不能正常调用 close(),可能会导致文件数据丢失。
QFile 的函数 open()的原型定义如下:
bool QFile::open(QIODeviceBase::OpenMode mode)
参数 mode 决定了文件以什么模式打开,mode 是标志类型 QIODeviceBase::OpenMode,它是枚举类型 QIODeviceBase::OpenModeFlag 的枚举值的组合,其各主要枚举值的含义如下:
• QIODevice::ReadOnly:以只读模式打开文件,加载文件时使用此模式。
• QIODevice::WriteOnly:以只写模式打开文件,保存文件时使用此模式。
• QIODevice::ReadWrite:以读写模式打开文件。
• QIODevice::Append:以添加模式打开文件,新写入文件的数据添加到文件尾部。
• QIODevice::Truncate:以截取模式打开文件,文件原有的内容全部被删除。
• QIODevice::Text:以文本模式打开文件,读取时“\n”被自动翻译为一行的结束符,写入时字符串结束符会被自动翻译为系统平台的编码,如 Windows 平台上是“\r\n”。
注:QIODeviceBase 是 QIODevice 的父类,枚举值 QIODeviceBase::ReadOnly 和 QIODevice::ReadOnly 是完 全一样的。在 QFile 的函数中,一般使用前缀为“QIODevice::”的枚举常量。
在给函数 open()传递参数时,可以使用枚举值的组合,例如 QIODevice::ReadOnly | QIODevice::Text 表示以只读和文本模式打开文件。
从文本文件读取数据可以使用函数 readLine()或 readAll(),这两个函数的返回值都是 QByteArray 类型,而 QByteArray 类型数据可以转换为 QString 字符串。函数 readLine()读取一行文字,它以“\n” 判断一行的结束。
示例窗口工具栏上“QFile 打开”按钮的槽函数以及相关的两个自定义函数代码如下:
- void MainWindow::on_actOpen_IODevice_triggered()
- {//“QFile 打开”按钮
- QString curPath= QDir::currentPath(); //获取应用程序当前目录
- QString dlgTitle= "打开一个文件";
- QString filter= "程序文件(*.h *.cpp);;文本文件(*.txt);;所有文件(*.*)";
- QString aFileName= QFileDialog::getOpenFileName(this,dlgTitle,curPath,filter);
- if (aFileName.isEmpty())
- return;
- QFileInfo fileInfo(aFileName);
- QDir::setCurrent(fileInfo.absolutePath()); //设置应用程序当前目录
- openByIO_Whole(aFileName); //整体读取
- // openByIO_Lines(aFileName); //逐行读取
- }
- bool MainWindow::openByIO_Whole(const QString &aFileName)
- {//整体读取
- QFile aFile(aFileName);
- if (!aFile.exists()) //文件不存在
- return false;
- if (!aFile.open(QIODevice::ReadOnly | QIODevice::Text))
- return false;
- QByteArray all_Lines = aFile.readAll(); //读取全部内容
- QString text(all_Lines); //将字节数组转换为字符串
- ui->textEditDevice->setPlainText(text);
- aFile.close();
- ui->tabWidget->setCurrentIndex(0);
- return true;
- }
- bool MainWindow::openByIO_Lines(const QString &aFileName)
- {//逐行读取
- QFile aFile;
- aFile.setFileName(aFileName);
- if (!aFile.exists()) //文件不存在
- return false;
- if (!aFile.open(QIODevice::ReadOnly | QIODevice::Text))
- return false;
- ui->textEditDevice->clear();
- while (!aFile.atEnd())
- {
- QByteArray line = aFile.readLine(); //读取一行文字,自动添加“\0”
- QString str= QString::fromUtf8(line); //从字节数组转换为字符串,文件必须采用UTF-8 编码
- str.truncate(str.length()-1); //去除增加的空行
- ui->textEditDevice->appendPlainText(str);
- }
- aFile.close();
- ui->tabWidget->setCurrentIndex(0);
- return true;
- }
按钮的槽函数里调用MainWindow类里自定义的函数openByIO_Whole()或openByIO_Lines(), 必须注释掉其中一个函数对应的代码。
函数 openByIO_Whole()里使用函数 QFile::readAll()将文本文件的全部内容一次性读出,并将 其保存到一个 QByteArray 类型的变量 all_Lines 里,然后将其转换为 QString 字符串。
函数 openByIO_Lines()里用函数 QFile::readLine()一次读取一行文字,这种读取方式适用于需要对 每行文字进行解析的情况。注意,函数 readLine()读出的数据会自动在最后添加一个结束符“\0”,如果 不去除这个字符,显示到文本框里时会自动在每一行文字后增加一个空行。函数 QString::fromUtf8()用 于将 UTF-8 编码字节数组数据转换为 QString 字符串。所以,使用函数 openByIO_Lines()打开文本 文件时,文件必须采用 UTF-8 编码。如果文本文件采用其他编码,需要使用 QString 相应的转换 函数进行转换,例如对于采用 Latin1 编码的文件,就应该用函数 QString::fromLatin1()将其转换为 QString 字符串。
要将一个 QString 字符串写入文本文件,一般是先将 QString 字符串转换为 QByteArray 字节 数组,再用函数 QFile::write(QByteArray &data)将字节数组数据写入文件。
示例窗口工具栏上“QFile 另存”按钮的槽函数以及相关的一个自定义函数代码如下:
- void MainWindow::on_actSave_IODevice_triggered()
- {//“QFile 另存”按钮
- QString curPath= QDir::currentPath(); //获取应用程序当前目录
- QString dlgTitle= "另存为一个文件";
- QString filter= "h 文件(*.h);;C++文件(*.cpp);;文本文件(*.txt);;所有文件(*.*)";
- QString aFileName= QFileDialog::getSaveFileName(this,dlgTitle,curPath,filter);
- if (aFileName.isEmpty())
- return;
- QFileInfo fileInfo(aFileName);
- QDir::setCurrent(fileInfo.absolutePath()); //设置应用程序当前目录
- saveByIO_Whole(aFileName); //整体保存
- }
- bool MainWindow::saveByIO_Whole(const QString &aFileName)
- {
- QFile aFile(aFileName);
- if (!aFile.open(QIODevice::WriteOnly | QIODevice::Text))
- return false;
- QString str= ui->textEditDevice->toPlainText(); //整个内容作为字符串
- QByteArray strBytes= str.toUtf8(); //转换为字节数组,UTF-8 编码
- aFile.write(strBytes,strBytes.length()); //写入文件
- aFile.close();
- ui->tabWidget->setCurrentIndex(0);
- return true;
- }
Qt 中还有一个与 QFile 并列的类 QSaveFile。QSaveFile 专门用于保存文件,可以 保存文本文件或二进制文件。在保存文件时,QSaveFile 会在目标文件所在的目录下创建一个临时 文件,向文件写入数据是先写入临时文件,如果写入操作没有错误,调用 QSaveFile 的函数 commit() 提交修改时临时文件里的内容才被移入目标文件,然后临时文件会被删除。在调用函数 commit() 之前,如果写入操作出现异常导致程序异常结束,目标文件不会有任何损失,这样可以避免目标文件里只保存了部分数据而破坏文件结构的情况。
QSaveFile 类新定义的接口函数如下表所示,向文件写入数据的函数是在其父类中定义的。 在 QSaveFile 中,函数 close()变成了一个私有函数,不能再调用 close()。
函数原型 | 功能 |
void setFileName(QString &name) | 设置文件名,在调用 open()函数打开文件后不能再设置文件名 |
bool commit() | 提交写入文件的修改,返回值为 true 表示提交修改成功 |
void cancelWriting() | 取消写入操作,取消后就不能再调用 commit()函数 |
void setDirectWriteFallback(bool enabled) | 如果设置为 true,则不使用临时文件,而直接向目标文件写入 |
bool directWriteFallback() | 返回值表示是否直接向目标文件写入 |
函数 setDirectWriteFallback(bool)用于设置是否直接向目标文件写入数据,如果设置为 true, 就表示直接向目标文件写入数据,那么 QSaveFile 的功能就与 QFile 的是相同的。有时目录的操作权限不允许创建临时文件,这时就需要调用此函数设置为直接写入目标文件。
QSaveFile 一般用于保存数据结构比较复杂的文件的数据,可以避免写入部分数据时出错而导致文件出现错误的情况。例如从网络上下载文件或压缩文件时都使用了类似的技术。示例窗口工具栏上的“QSaveFile 另存”按钮的槽函数和一个自定义函数的代码如下:
- void MainWindow::on_actSave_TextSafe_triggered()
- {//“QSaveFile 另存”按钮
- QString curPath= QDir::currentPath();
- QString dlgTitle= "另存为一个文件";
- QString filter= "h 文件(*.h);;C++文件(*.cpp);;文本文件(*.txt);;所有文件(*.*)";
- QString aFileName= QFileDialog::getSaveFileName(this,dlgTitle,curPath,filter);
- if (aFileName.isEmpty())
- return;
- QFileInfo fileInfo(aFileName);
- QDir::setCurrent(fileInfo.absolutePath());
- saveByIO_Safe(aFileName); //使用 QSaveFile 保存文件
- }
- bool MainWindow::saveByIO_Safe(const QString &aFileName)
- {//使用 QSaveFile 保存文件
- QSaveFile aFile(aFileName);
- if (!aFile.open(QIODevice::WriteOnly | QIODevice::Text))
- return false;
- aFile.setDirectWriteFallback(false); //使用临时文件
- try
- {
- QString str= ui->textEditDevice->toPlainText(); //整个内容作为字符串
- QByteArray strBytes= str.toUtf8(); //转换为字节数组,UTF-8 编码
- aFile.write(strBytes,strBytes.length()); //写入文件
- aFile.commit(); //提交对文件的修改
- ui->tabWidget->setCurrentIndex(0);
- return true;
- }
- catch (QException &e)
- {
- qDebug("保存文件的过程发生了错误");
- aFile.cancelWriting(); //出现异常时取消写入
- return false;
- }
- }
自定义函数 saveByIO_Safe()中使用 QSaveFile 类保存文件,并且在程序中进行了异常处理。 如果向文件写入的过程正常,在运行 aFile.commit()后会正式将写入的内容移入目标文件;如果出 现异常,就会运行 aFile.cancelWriting()取消写入。
函数 saveByIO_Safe()中写入文件的过程非常简单,不可能出现异常。QSaveFile 适合在写入复杂格式的二进制文件时使用,可避免写入部分数据而异常退出时导致的文件格式错误。
QTextStream 是能与 I/O 设备类结合来为读写文本数据提供一些简便接口函数的类。QTextStream 可以和 QIODevice 的各种子类结合使用,如 QFile、QSaveFile、QTcpSocket、QUdpSocket 等 I/O 设备类。
QTextStream 有多种参数形式的构造函数,与 QIODevice 类型的设备结合使用时,可以使用 如下两种形式的构造函数:
- QTextStream(QIODevice *device) //指定关联的 QIODevice 对象
- QTextStream() //不指定任何关联对象
在构造 QTextStream 对象时,如果未指定任何关联对象,可以后面再调用 QTextStream 的 setDevice()函数指定关联的 QIODevice 对象。
QTextStream 的主要接口函数如下表所示,表中省略了函数参数中的 const 关键字。如果设置函数有对应的读取函数,我们一般只列出设置函数,不列出读取函数。
函数原型 | 功能 |
void setDevice(QIODevice *device) | 设置关联的 QIODevice 设备 |
void setAutoDetectUnicode(bool enabled) | 设置是否自动检测 Unicode 编码 |
void setEncoding(QStringConverter::Encoding encoding) | 设置读写文本文件时的编码方式,默认是 UTF-8 编码方式 |
void setGenerateByteOrderMark(bool generate) | 设置是否产生字节序标记(byte order mark,BOM)。如果设置为 true, 且使用了某种 UTF 编码,那么 QTextStream 在向设备写入数据之前会 插入 BOM,否则不会插入 BOM |
void setIntegerBase(int base) | 读写整数时使用的进制,默认是十进制。在文本文件中,整数是以字符串形式保存的 |
void setRealNumberPrecision(int precision) | 设置浮点数精度,即小数位数 |
QString read(qint64 maxlen) | 读取最多 maxlen 个字符,返回值为 QString 字符串 |
QString readAll() | 读取文件的全部内容,返回值为 QString 字符串 |
QString readLine(qint64 maxlen = 0) | 读取一行文字,遇到“\n”时将其作为一行的结束 |
QTextStream &operator>>(QString &str) | 流读取操作符,将一个单词读取到 QString 字符串里,遇空格结束 |
QTextStream &operator>>(int &i) | 流读取操作符,将一个整数字符串读取到 int 类型变量里,遇空格结束 |
QTextStream &operator>>(double &f) | 流读取操作符,将一个双精度浮点数字符串读取到 double 类型变量 里,遇空格结束 |
QTextStream &operator<<( QString &string) | 流写入操作符,将一个 QString 字符串写入流 |
QTextStream &operator<<(int i) | 流写入操作符,将一个 int 类型整数转换为字符串并将其写入流,字符串使用的整数进制由函数 integerBase()的值确定 |
QTextStream &operator<<(double f) | 流写入操作符,将一个 double 类型浮点数转换为字符串并将其写入流,字符串的小数位数由函数 realNumberPrecision()的值确定 |
bool atEnd() | 返回值为 true 时,表示流里没有任何数据可读写了 |
qint64 pos() | 读写操作在流里的当前位置 |
bool seek(qint64 pos) | 读写位置定位到流的某个位置,如 seek(0)就表示定位到流的开始位置 |
void flush() | 将任何缓存的数据保存到文件里 |
QTextStream 的流操作符“>>”和“<<”对应的参数形式有很多,表 8-12 中列出了其中的几种,全部的参数形式请查阅 Qt 帮助文档。QTextStream 的 read()、readAll()、readLine()等函数读取的内容的返回值就是 QString,便于直接操作。
QTextStream 读写的是文本文件。使用流操作符写入一个整数时,QTextStream 会自动将整数转换为字符串,然后将其写入文件。写入其他各种类型的数据时也是类似的,QTextStream 会自动 将它们转换为字符串,然后将其写入文件。
示例窗口工具栏上“QTextStream 打开”按钮的槽函数以及相关的两个自定义函数代码如下:
- void MainWindow::on_actOpen_TextStream_triggered()
- { //“QTextStream 打开”按钮
- QString curPath= QDir::currentPath();
- QString aFileName= QFileDialog::getOpenFileName(this,"打开一个文件",curPath,
- "程序文件(*.h *cpp);;文本文件(*.txt);;所有文件(*.*)");
- if (aFileName.isEmpty())
- return;
- QFileInfo fileInfo(aFileName);
- QDir::setCurrent(fileInfo.absolutePath());
- // openByStream_Whole(aFileName); //打开文件,整体读取
- openByStream_Lines(aFileName); //打开文件,逐行读取
- }
- bool MainWindow::openByStream_Whole(const QString &aFileName)
- {
- QFile aFile(aFileName);
- if (!aFile.exists())
- return false;
- if (!aFile.open(QIODevice::ReadOnly | QIODevice::Text))
- return false;
- QTextStream aStream(&aFile); //用文本流读取文件内容
- aStream.setAutoDetectUnicode(true); //自动检测 Unicode
- QString str= aStream.readAll(); //读取全部内容
- ui->textEditStream->setPlainText(str);
- aFile.close();
- ui->tabWidget->setCurrentIndex(1);
- return true;
- }
- bool MainWindow::openByStream_Lines(const QString &aFileName)
- {
- QFile aFile(aFileName);
- if (!aFile.exists())
- return false;
- if (!aFile.open(QIODevice::ReadOnly | QIODevice::Text))
- return false;
- QTextStream aStream(&aFile); //用文本流读取文件内容
- aStream.setAutoDetectUnicode(true); //自动检测 Unicode
- ui->textEditStream->clear();
- while (!aStream.atEnd())
- {
- QString str= aStream.readLine(); //读取一行文字
- ui->textEditStream->appendPlainText(str);
- }
- aFile.close();
- ui->tabWidget->setCurrentIndex(1);
- return true;
- }
函数 openByStream_Whole()里使用 QTextStream::readAll()将文本文件的内容一次性全部读取出 来,函数 openByStream_Lines()里则使用 QTextStream::readLine()逐行读取文件的内容。QTextStream 的这两个函数的返回值都是 QString 类型的,使用其相比于用 QFile 直接读取文本文件要简便一些, 因为无须进行 QByteArray 到 QString 的转换。
示例窗口工具栏上“QTextStream 另存”按钮的槽函数以及相关的两个自定义函数代码如下:
- void MainWindow::on_actSave_TextStream_triggered()
- {//“QTextStream 另存”按钮
- QString curPath= QDir::currentPath();
- QString dlgTitle= "另存为一个文件";
- QString filter= "h 文件(*.h);;C++文件(*.cpp);;文本文件(*.txt);;所有文件(*.*)";
- QString aFileName= QFileDialog::getSaveFileName(this,dlgTitle,curPath,filter);
- if (aFileName.isEmpty())
- return;
- QFileInfo fileInfo(aFileName);
- QDir::setCurrent(fileInfo.absolutePath());
- // saveByStream_Whole(aFileName); //整体保存
- saveByStream_Safe(aFileName); //逐段读取后保存
- }
- bool MainWindow::saveByStream_Whole(const QString &aFileName)
- {
- QFile aFile(aFileName);
- if (!aFile.open(QIODevice::WriteOnly | QIODevice::Text))
- return false;
- QTextStream aStream(&aFile); //用文本流读取文件内容
- aStream.setAutoDetectUnicode(true); //自动检测 Unicode
- QString str= ui->textEditStream->toPlainText();
- aStream<<str; //写入文本流
- aFile.close();
- return true;
- }
- bool MainWindow::saveByStream_Safe(const QString &aFileName)
- {
- QSaveFile aFile(aFileName);
- if (!aFile.open(QIODevice::WriteOnly | QIODevice::Text))
- return false;
- try
- { //逐段保存
- QTextStream aStream(&aFile); //用文本流读取文件
- aStream.setAutoDetectUnicode(true); //自动检测 Unicode
- QTextDocument *doc= ui->textEditStream->document(); //文本编辑器的全部内容
- int cnt= doc->blockCount(); //硬回车符对应一个块
- for (int i=0; i<cnt; i++) //扫描所有块
- {
- QTextBlock textLine= doc->findBlockByNumber(i); //获取一段
- QString str= textLine.text(); //提取文本,末尾无"\n"
- aStream<<str<<"\n"; //写入时增加一个换行符
- }
- aFile.commit(); //提交修改
- return true;
- }
- catch (QException &e)
- {
- qDebug("保存文件的过程发生了错误");
- aFile.cancelWriting(); //出现异常时取消写入
- return false;
- }
- }
函数 saveByStream_Safe()里使用了 QSaveFile 类,并且是逐段读取文本编辑器里的内容后将其写入文件。函数 QPlainTextEdit::document()返回一个 QTextDocument 对象,是文本编辑器里所 有内容对应的文档。纯文本文档由块(block)组成,每个块就表示由回车符确定的一个段。每个 块是 QTextBlock 对象,通过 QTextBlock::text()函数可以获得一个段的文字,但是没有换行符,所 以在写入文本流的时候需要添加一个换行符。
除了文本文件,其他文件都可以看作二进制文件。可以单独使用 QFile 读写二进制文件,但是一般结合使用QFile 和 QDataStream 读写二进制文件,因为QDataStream 提供了更丰富的接口函数。
除了文本文件,其他需要按照一定的格式定义读写操作的文件都可以称为二进制文件,例如JPG 图片文件、PDF 文件、SEGY 地震数据文件等。即使需要保存的数据全是文字,将其以二进 制形式保存到文件也比使用文本文件少占用存储空间。例如,把一个整数 54321 保存为文本文件, 需要将其转换为字符串“54321”再保存到文件,如果以 Latin1 编码字符串需要占用 5 字节,而如 果以 quint16 数据类型保存这个数只需占用 2 字节。
每种格式的二进制文件都有自己的格式定义,写入文件时按照一定的顺序写入数据,读出时 也按照相应的顺序从文件读出。例如对于地震勘探中常用的 SEGY 格式的地震数据文件,就必须 按照其标准格式要求写入数据才符合这种文件的格式规范,读取数据时也需要符合格式定义。
字节序是指一个多字节数据的各个字节码在内存或文件中的存储顺序,分为大端(big-endian) 字节序和小端(little-endian)字节序。大端字节序是高位字节在前(低地址),低位字节在后(高 地址);小端字节序是低位字节在前(低地址),高位字节在后(高地址)。
内存中的数据字节序与 CPU 类型和操作系统有关,Intel x86、AMD64、ARM 处理器全采用 小端字节序,而 MIPS 采用大端字节序。在将数据写入文件时可以根据需要设定字节序,一般使 用与操作系统一致的字节序,但也可以不一致。例如 Windows 系统采用的是小端字节序,而保存 数据到文件时也可以保存为大端字节序形式。
在编写关于文件读写操作的程序时,一个查看二进制文件内容的工具软件是必不可少的,这种软件能显示文件中每字节的十六进制内容。这样的软件比较多,其中一个完全免费且非常好用的软件是 HxD Hex Editor(后文统一用“HxD”),这是一个专门用于查看和编辑文件十六进制内容的软件。使用 HxD 查看文件的十六进制内容的界面如下图所示。本节后面的示例中通过编程 向这个文件写入了一个 QString 类型的字符串“Hello”,图显示了文件中的十六进制内容。
图示界面的文件内容区域的左侧部分显示了每个地址的十六进制字节码,右侧部分显示了这些字节码对应的文本,如果一个字节码正好对应一个 ASCII 字符就显示为该字符。窗口右侧的“数据检视”编辑器显示了文件内容区域中选中的数据对应的各种格式的数值,例如选中了连续的 2 字节,其对应的 Int16 和 UInt16 数值就会自动显示出来。
单独使用 QFile 可以读写二进制文件,但是使用起来不方便。要读写二进制文件,一般可将 QFile 和 QDataStream 类结 合使用,QDataStream 类为读写二进制文件提供了丰富的接口函数。
QDataStream 是对 I/O 设备进行二进制流数据读写操作的类,其流数据格式与 CPU 类型、操 作系统无关,是完全独立的。QDataStream 不仅可以用于二进制文件的读写操作,还可以用于网络 通信、串口通信等 I/O 设备的数据读写操作。如果用于二进制文件读写操作,创建 QDataStream 对 象时需要传递一个 QFile 对象作为参数,从而实现与物理文件关联,也就是使用如下的构造函数:
QDataStream(QIODevice *dev)
QDataStream 以数据流的方式读写文件,数据流编码有两种方式:一种是使用 Qt 的预定义编 码方式,另一种是使用原始二进制数据方式。
使用 Qt 的预定义编码方式就是将一些基本类型的数据和简单的 Qt 类序列化(serialization) 为二进制数据,这种预定义编码与操作系统、CPU 类型和字节序无关。QDataStream 主要使用流 写入操作符“>”分别进行数据的序列化写入和读取。例如,QDataStream 类中定义的流写入操作符“<<”有如下一些参数形式。
- QDataStream &operator<<(qint8 i) //将一个 qint8 类型的数序列化后写入流
- QDataStream &operator<<(quint8 i)
- QDataStream &operator<<(qint16 i)
- QDataStream &operator<<(quint16 i)
- QDataStream &operator<<(qint32 i)
- QDataStream &operator<<(quint32 i)
- QDataStream &operator<<(qint64 i)
- QDataStream &operator<<(quint64 i)
- QDataStream &operator<<(std::nullptr_t ptr)
- QDataStream &operator<<(bool i)
- QDataStream &operator<<(qfloat16 f) //将一个 qfloat16 类型的数序列化后写入流
- QDataStream &operator<<(float f) //将一个 float 类型的数序列化后写入流
- QDataStream &operator<<(double f) //将一个 double 类型的数序列化后写入流
- QDataStream &operator<<(const char *s) //将一个 char 字符串写入流,以"\0"判断字符串的末尾
- QDataStream &operator<<(char16_t c)
- QDataStream &operator<<(char32_t c)
QDataStream 类中的流读取操作符“>>”也有与流写入操作符类似的定义,所以,QDataStream 可以使用流操作符直接读写这些基本类型的数据。
此外,Qt 类库中一些简单的类的对象也可以使用 QDataStream 的流操作符进行读写,这些类 如 QString、QFont、QColor 等,序列化就是使用 Qt 的预定义编码将这些类的对象数据编码为二进 制数据流。支持序列化的类在其“related non-members”部分会定义流操作符,例如 QColor 类中 有如下的定义:
- QDataStream &operator<<(QDataStream &stream, const QColor &color) //写入
- QDataStream &operator>>(QDataStream &stream, QColor &color) //读取
所以,使用序列化方式读写文件时可以读写一些复杂类型的数据,而我们并不用关注这些数 据是如何具体编码和解析的。
另一种数据流编码方式是直接使用原始二进制数据,所用的主要函数是 readRawData()和 writeRawData()。用这种方式向文件写入数据时,需要用户将数据转换为二进制数据然后将其写入 文件,从文件读取的二进制数据需要解析为所需的类型的数据。这种方式适合完全自定义文件格 式并需要掌控文件的每字节的数据含义的应用场景。
除了数据流化读写操作符,QDataStream 还有如下一些主要的接口函数。
在使用 QDataStream 进行文件数据读写之前,需要设置数据 序列化格式版本,函数定义如下:
void setVersion(int v) //设置数据序列化格式版本
Qt 对各种类型数据的预定义编码会随着 Qt 的版本不同而变化,在使用 QDataStream 对象读 写文件时,需要先设置数据序列化格式版本,Qt 中定义了版本号常数,部分定义如下:
- QDataStream::Qt_5_12 18 //Version 18 (Qt 5.12)
- QDataStream::Qt_5_13 19 //Version 19 (Qt 5.13)
- QDataStream::Qt_5_14 Qt_5_13 //等同于 Qt_5_13
- QDataStream::Qt_5_15 Qt_5_14 //等同于 Qt_5_13
- QDataStream::Qt_6_0 20 //Version 20 (Qt 6.0)
- QDataStream::Qt_6_1 Qt_6_0 //等同于 Qt_6_0
- QDataStream::Qt_6_2 Qt_6_0 //等同于 Qt_6_0
每个 Qt 版本都有一个序列化格式版本,例如,对于 Qt 6.0,其序列化格式版本是 QDataStream::Qt_ 6_0,某些 Qt 版本的序列化格式版本等同于先前的版本,例如 Qt 6.2 的流化版本等同于 Qt 6.0 版本。
在使用预定义编码读写文件时,读取文件和写入文件时用的序列化格式版本应该兼容,例 如如果写入数据时用的序列化格式版本是 Qt_6_0,那么读取数据时要使用与其相同、等效的版 本或更高的版本,而不能使用比其低的版本,否则可能会导致某些类型的数据不能被正常读取 和解析。
在使用预定义编码读写数据时,可以设置所使用的字节序,函数定义如下:
void setByteOrder(QDataStream::ByteOrder bo) //设置字节序
字节序是枚举类型 QDataStream::ByteOrder,其有两种枚举值,其中 QDataStream::BigEndian 表示大端字节序,QDataStream::LittleEndian 表示小端字节序。
使用流操作符读写的浮点数有 3 种类型,即 qfloat16、float 和 double, 它们都是 IEEE 754 格式的浮点数类型。qfloat16 是 2 字节表示的浮点数类型,float 和 double 是 4 字节或 8 字节表示的浮点数类型,受设置的浮点数精度的影响。函数 setFloatingPointPrecision()用 于设置浮点数精度,定义如下:
void setFloatingPointPrecision(QDataStream::FloatingPointPrecision precision)
浮点数精度是枚举类型 QDataStream::FloatingPointPrecision,其两种枚举值的含义如下:
• QDataStream::SinglePrecision:单精度,数据流中的 float 和 double 都用 4 字节表示。
• QDataStream::DoublePrecision:双精度,数据流中的 float 和 double 都用 8 字节表示。
使用 QDataStream 读取数据流时,可以使用事务处理,相关的 4 个函数定义 如下:
- void startTransaction() //开始一个读取操作的事务
- bool commitTransaction() //提交当前的读取操作事务,返回值为 true 表示读取过程没有错误
- void rollbackTransaction() //回滚当前的读取操作事务,一般在检测到错误时回滚
- void abortTransaction() //取消当前的读取操作事务
在开始一个读取操作事务时,QDataStream 会记住当前的文件读取点,如果在读取过程中出 现错误,在使用回滚或取消事务操作后,可以回到开始读取操作事务时的文件读取点。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。