13b3ebbe14
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。
Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
(common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
(改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。
Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
循环改为 QCoreApplication 等。
任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。
文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。
隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
1 line
17 KiB
JSON
1 line
17 KiB
JSON
{"id":"58954552","type":"page","status":"current","title":"11 文件系统","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:25.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954552/version/1"},"_expandable":{"content":"/rest/api/content/58954552"}},"body":{"storage":{"value":"<p>文件操作是应用程序必不可少的部分。Qt 作为一个通用开发库,提供了跨平台的文件操作能力。Qt 通过QIODevice提供了对 I/O 设备的抽象,这些设备具有读写字节块的能力。下面是 I/O 设备的类图(Qt5):<br /><ac:image ac:height=\"379\" ac:width=\"554\"><ri:attachment ri:filename=\"worddav2438ea2e5914f8fb72adf3a2857085bd.png\" /></ac:image></p><ul><li>QIODevice:所有 I/O 设备类的父类,提供了字节块读写的通用操作以及基本接口;</li><li>QFileDevice:Qt5新增加的类,提供了有关文件操作的通用实现。</li><li>QFlie:访问本地文件或者嵌入资源;</li><li>QTemporaryFile:创建和访问本地文件系统的临时文件;</li><li>QBuffer:读写QbyteArray, 内存文件;</li><li>QProcess:运行外部程序,处理进程间通讯;</li><li>QAbstractSocket:所有套接字类的父类;</li><li>QTcpSocket:TCP协议网络数据传输;</li><li>QUdpSocket:传输 UDP 报文;</li><li>QSslSocket:使用 SSL/TLS 传输数据;</li></ul><p><strong>文件系统分类:</strong></p><ul><li>顺序访问设备:</li></ul><p>是指它们的数据只能访问一遍:从头走到尾,从第一个字节开始访问,直到最后一个字节,中途不能返回去读取上一个字节,这其中,QProcess、QTcpSocket、QUdpSoctet和QSslSocket是顺序访问设备。</p><ul><li>随机访问设备:</li></ul><p>可以访问任意位置任意次数,还可以使用QIODevice::seek()函数来重新定位文件访问位置指针,QFile、Q<ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"0b2db2e1-b1e7-46fc-a304-cb69efb93007\"><ac:parameter ac:name=\"\">OLE_LINK9</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"d57a9af2-1f23-407e-8cbd-a6b7deb02974\"><ac:parameter ac:name=\"\">OLE_LINK10</ac:parameter></ac:structured-macro>TemporaryFile和QBuffer是随机访问设备,</p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"807693a9-9e14-4503-aac9-5150b359058e\"><ac:parameter ac:name=\"\">_Toc466556806</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"e7aba0b8-254f-4841-8c3a-d5a88a297f35\"><ac:parameter ac:name=\"\">_Toc471842039</ac:parameter></ac:structured-macro>11.1 基本文件操作</h1><p>文件操作是应用程序必不可少的部分。Qt <span>作为一个通用开发库,提供了跨平台的文件操作能力。在所有的</span> I/O <span>设备中,文件</span> I/O <span>是最重要的部分之一。因为我们大多数的程序依旧需要首先访问本地文件(当然,在云计算大行其道的将来,这一观点可能改变)。</span><strong>QFile<span>提供了从文件中读取和写入数据的能力。</span></strong></p><p><strong>我们通常会将文件路径作为参数传给</strong><strong>QFile<span>的构造函数。不过也可以在创建好对象最后,使用</span>setFileName()<span>来修改</span></strong>。QFile<span>需要使用</span> / <span>作为文件分隔符,不过,它会自动将其转换成操作系统所需要的形式。例如</span> C:/windows <span>这样的路径在</span> Windows <span>平台下同样是可以的。</span></p><p>QFile<span>主要提供了有关文件的各种操作,比如打开文件、关闭文件、刷新文件等。<strong>我们可以使用</strong></span><strong>QDataStream<span>或</span>QTextStream<span>类来读写文件,也可以使用</span>QIODevice<span>类提供的</span>read()<span>、</span>readLine()<span>、</span>readAll()<span>以及</span>write()<span>这样的函数。</span></strong><span>值得注意的是,<strong>有关文件本身的信息,比如文件名、文件所在目录的名字等,则是通过</strong></span><strong>QFileInfo<span>获取</span></strong><span>,而不是自己分析文件路径字符串。</span></p><p>下面我们使用一段代码来看看QFile<span>的有关操作:</span></p><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"ae4b0dbf-9a45-43cb-a9e4-ab9623cf5901\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[int main(int argc, char *argv[])\n{\n QApplication app(argc, argv);\n \n QFile file(\"in.txt\");\n if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {\n qDebug() << \"Open file failed.\";\n return -1;\n } else {\n while (!file.atEnd()) {\n qDebug() << file.readLine();\n }\n }\n \n QFileInfo info(file);\n qDebug() << info.isDir();\n qDebug() << info.isExecutable();\n qDebug() << info.baseName();\n qDebug() << info.completeBaseName();\n qDebug() << info.suffix();\n qDebug() << info.completeSuffix();\n \n return app.exec();\n}\n\n]]></ac:plain-text-body></ac:structured-macro><p><br /></p><ul><li>我们首先使用QFile创建了一个文件对象。</li></ul><p>这个文件名字是 in.txt。如果你不知道应该把它放在哪里,可以使用QDir::currentPath()来获得应用程序执行时的当前路径。只要将这个文件放在与当前路径一致的目录下即可。</p><ul><li>使用open()函数打开这个文件,打开形式是只读方式,文本格式。</li></ul><p>这个类似于fopen()的 r 这样的参数。open()函数返回一个 bool 类型,如果打开失败,我们在控制台输出一段提示然后程序退出。否则,我们利用 while 循环,将每一行读到的内容输出。</p><ul><li>可以使用QFileInfo获取有关该文件的信息。</li></ul><p>QFileInfo有很多类型的函数,我们只举出一些例子。比如:</p><ul><li><ul><li>isDir()检查该文件是否是目录;</li><li>isExecutable() 检查该文件是否是可执行文件等。</li><li>baseName() 可以直接获得文件名;</li><li>completeBaseName() 获取完整的文件名</li><li>suffix() 则直接获取文件后缀名。</li><li>completeSuffix() 获取完整的文件后缀</li></ul></li></ul><p>我们可以由下面的示例看到,baseName()和completeBaseName(),以及suffix()和completeSuffix()的区别:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"dcb398f5-534e-4fde-be15-594f58dd70ff\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[QFileInfo fi(\"/tmp/archive.tar.gz\");\nQString base = fi.baseName(); // base = \"archive\"\nQString base = fi.completeBaseName(); // base = \"archive.tar\"\nQString ext = fi.suffix(); // ext = \"gz\"\nQString ext = fi.completeSuffix(); // ext = \"tar.gz\"]]></ac:plain-text-body></ac:structured-macro><p><br /></p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"7724380c-1655-46e5-a8be-b37e83ab3a8d\"><ac:parameter ac:name=\"\">_Toc466556807</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"70c6b29e-24b9-4436-b0a5-e43f67642213\"><ac:parameter ac:name=\"\">_Toc471842040</ac:parameter></ac:structured-macro>11.2 二进制文件读写</h1><p><span style=\"color: rgb(255,0,0);\"><strong>QDataStream</strong></span>提供了基于QIODevice的二进制数据的序列化。数据流是一种二进制流,这种流<span style=\"color: rgb(255,0,0);\"><strong>完全不依赖</strong></span>于底层操作系统、CPU 或者字节顺序(大端或小端)。例如,在安装了 Windows 平台的 PC 上面写入的一个数据流,可以不经过任何处理,直接拿到运行了 Solaris 的 SPARC 机器上读取。由于数据流就是二进制流,因此我们也可以<strong>直接读写没有编码的二进制数据,例如图像、视频、音频</strong>等。<br /><strong>QDataStream既能够存取 C++ 基本类型,如 int、char、short 等,也可以存取复杂的数据类型,例如自定义的类。实际上,QDataStream对于类的存储,是将复杂的类分割为很多基本单元实现的。</strong><br />结合QIODevice,QDataStream可以很方便地对文件、网络套接字等进行读写操作。我们从代码开始看起:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"b4c584b6-84c9-4cbe-9518-4852a99c1ca8\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[QFile file(\"file.dat\");\nfile.open(QIODevice::WriteOnly);\nQDataStream out(&file);\nout << QString(\"the answer is\");\nout << (qint32)42;]]></ac:plain-text-body></ac:structured-macro><p><br /></p><ul><li>在这段代码中,我们首先打开一个名为 file.dat 的文件(注意,我们为简单起见,并没有检查文件打开是否成功,这在正式程序中是不允许的)。然后,我们将刚刚创建的file对象的指针传递给一个QDataStream实例out。类似于std::cout标准输出流,QDataStream也重载了输出重定向<<运算符。后面的代码就很简单了:将"the answer is"和数字 42 输出到数据流。由于我们的 out 对象建立在file之上,因此相当于将问题和答案写入file。</li><li>需要指出一点:最好使用 Qt 整型来进行读写,比如程序中的qint32。这保证了在任意平台和任意编译器都能够有相同的行为。</li></ul><p>如果你直接运行这段代码,你会得到一个空白的 file.dat,并没有写入任何数据。这是因为我们的file没有正常关闭。<span style=\"color: rgb(255,0,0);\"><strong>为性能起见,数据只有在文件关闭时才会真正写入</strong></span>。因此,我们必须在最后添加一行代码:<br />file.close(); // 如果不想关闭文件,可以使用 file.flush();<br />接下来我们将存储到文件中的答案取出来</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"311f44a2-4009-47fe-8b5c-c5283491a92c\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[QFile file(\"file.dat\");\nfile.open(QIODevice::ReadOnly);\nQDataStream in(&file);\nQString str;\nqint32 a;\nin >> str >> a;]]></ac:plain-text-body></ac:structured-macro><p><br /><span style=\"color: rgb(255,0,0);\"><strong>唯一需要注意的是,你必须按照写入的顺序,将数据读取出来。顺序颠倒的话,程序行为是不确定的,严重时会直接造成程序崩溃。</strong></span><br />那么,既然QIODevice提供了read()、readLine()之类的函数,为什么还要有QDataStream呢?QDataStream同QIODevice有什么区别?区别在于,<span style=\"color: rgb(255,0,0);\"><strong>QDataStream提供流的形式,性能上一般比直接调用原始 API 更好一些。</strong></span>我们通过下面一段代码看看什么是流的形式:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"477deacf-8c37-453f-a26b-926538e3f087\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[QFile file(\"file.dat\");\nfile.open(QIODevice::ReadWrite);\nQDataStream stream(&file);\nQString str = \"the answer is 42\";\nstream << str;]]></ac:plain-text-body></ac:structured-macro><p><br /></p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"8bb2fe43-2dae-45fe-9c45-83b6e1d10110\"><ac:parameter ac:name=\"\">_Toc466556808</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"96b0581d-67dc-4e1b-9eb4-c813863f15cc\"><ac:parameter ac:name=\"\">_Toc471842041</ac:parameter></ac:structured-macro>11.3 文本文件读写</h1><p>上一节我们介绍了有关二进制文件的读写。二进制文件比较小巧,却不是人可读的格式。而文本文件是一种人可读的文件。为了操作这种文件,我们需要使用QTextStream类。QTextStream和QDataStream的使用类似,只不过它是操作纯文本文件的。<br />QTextStream会自动将 Unicode 编码同操作系统的编码进行转换,这一操作对开发人员是透明的。它也会将换行符进行转换,同样不需要自己处理。<span style=\"color: rgb(255,0,0);\"><strong>QTextStream使用 16 位的QChar作为基础的数据存储单位,同样,它也支持 C++ 标准类型,如 int 等。实际上,这是将这种标准类型与字符串进行了相互转换。</strong></span><br />QTextStream同QDataStream的使用基本一致,例如下面的代码将把"The answer is 42"写入到 file.txt 文件中:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"5b9497c0-5f7d-42e9-a56b-c39b28b78737\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[QFile data(\"file.txt\");\nif (data.open(QFile::WriteOnly | QIODevice::Truncate))\n{\nQTextStream out(&data);\nout << \"The answer is \" << 42;\n}]]></ac:plain-text-body></ac:structured-macro><p><br />这里,我们在open()函数中增加了QIODevice::Truncate打开方式。我们可以从下表中看到这些打开方式的区别:<br />枚举值 描述</p><ul><li>QIODevice::NotOpen 未打开</li><li>QIODevice::ReadOnly 以只读方式打开</li><li>QIODevice::WriteOnly 以只写方式打开</li><li>QIODevice::ReadWrite 以读写方式打开</li><li>QIODevice::Append 以追加的方式打开,</li></ul><p>新增加的内容将被追加到文件末尾</p><ul><li>QIODevice::Truncate 以重写的方式打开,在写入新的数据时会将原有</li></ul><p>数据全部清除,游标设置在文件开头。</p><ul><li>QIODevice::Text 在读取时,将行结束符转换成 \\n;在写入时,</li></ul><p>将行结束符转换成本地格式,例如 Win32 平台<br />上是 \\r\\n</p><ul><li>QIODevice::Unbuffered忽略缓存</li></ul><p>我们在这里使用了QFile::WriteOnly | QIODevice::Truncate,也就是以只写并且覆盖已有内容的形式操作文件。注意,QIODevice::Truncate会直接将文件内容清空。 <br class=\"atl-forced-newline\" />虽然QTextStream的写入内容与QDataStream一致,但是读取时却会有些困难:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"68fe1148-e689-4f26-a4bb-d0f23479f8fc\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[QFile data(\"file.txt\");\nif (data.open(QFile::ReadOnly))\n{\nQTextStream in(&data);\nQString str;\nint ans = 0;\nin >> str >> ans;\n}]]></ac:plain-text-body></ac:structured-macro><p><br />在使用QDataStream的时候,这样的代码很方便,但是使用了QTextStream时却有所不同:读出的时候,str 里面将是 The answer is 42,ans 是 0。这是因为<span style=\"color: rgb(255,0,0);\"><strong>当使用QDataStream写入的时候,实际上会在要写入的内容前面,额外添加一个这段内容的长度值。而以文本形式写入数据,是没有数据之间的分隔的。</strong></span>因此,使用文本文件时,很少会将其分割开来读取,而是使用诸如使用:</p><ul><li>QTextStream::readLine() 读取一行</li><li>QTextStream::readAll()读取所有文本</li></ul><p>这种函数之后再对获得的QString对象进行处理。<br />默认情况下,QTextStream的编码格式是 Unicode,如果我们需要使用另外的编码,可以使用:<br />stream.setCodec("UTF-8");<br />这样的函数进行设置。</p><div style=\"left: -12.0px;top: 3939.0px;\"><div class=\"gtx-trans-icon\"><br /></div></div>","representation":"storage","_expandable":{"content":"/rest/api/content/58954552"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":67},"_links":{"webui":"/pages/viewpage.action?pageId=58954552","edit":"/pages/resumedraft.action?draftId=58954552","tinyui":"/x/OJODAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954552"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954552/child","restrictions":"/rest/api/content/58954552/restriction/byOperation","history":"/rest/api/content/58954552/history","ancestors":"","descendants":"/rest/api/content/58954552/descendant","space":"/rest/api/space/2CT"}} |