Files
张宗平 13b3ebbe14 课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证
把川大 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 运行通过。
2026-06-30 14:16:55 +08:00

1 line
17 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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>QFileDeviceQt5新增加的类,提供了有关文件操作的通用实现。</li><li>QFlie:访问本地文件或者嵌入资源;</li><li>QTemporaryFile:创建和访问本地文件系统的临时文件;</li><li>QBuffer:读写QbyteArray, 内存文件;</li><li>QProcess:运行外部程序,处理进程间通讯;</li><li>QAbstractSocket:所有套接字类的父类;</li><li>QTcpSocketTCP协议网络数据传输;</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 />结合QIODeviceQDataStream可以很方便地对文件、网络套接字等进行读写操作。我们从代码开始看起:</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也重载了输出重定向&lt;&lt;运算符。后面的代码就很简单了:将&quot;the answer is&quot;和数字 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的使用基本一致,例如下面的代码将把&quot;The answer is 42&quot;写入到 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 42ans 是 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(&quot;UTF-8&quot;);<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"}}