Files
scuc-qt-course/docs/teaching/examples/file_io_demo/README.md
T
张宗平 01b264fe97 Day4 教学示例补三个:QDialog 综合 / QLineEdit 小眼睛 / 文件 IO(+顶层构建注册)
- qdialog_showcase:wiki「6 对话框 QDialog」6.2–6.5 综合演示。含两套实现:
  · Designer 表单版(dialog.{h,cpp,ui} + resources.qrc + source.gif,QMovie 播 GIF)
  · 纯代码版(qt_dialog_showcase.cpp,单文件,QWidget 布满按钮逐个演示 QDialog 行为)
- lineedit_eye_toggle:子类化 QLineEdit,paintEvent 自绘小眼睛、mouseEvent 命中切换
  echoMode,并与内置 clear button 动态避让(读 objectName=="clearButton" 子 widget 几何)。
  含独立子目录 CMakeLists.txt 供单独打开(顶层 add_subdirectory 不递归)。
- file_io_demo:QFile/QTextStream/QDataStream/QFileInfo/QDir/QTextCodec/QJson 五主题
  综合示例,每个按钮产出一个文件落到 demo_output/ 便于课堂手动查看。

构建注册:examples/CMakeLists.txt 用 add_teaching_example 注册前两个纯代码示例,
mandelbrot 多源文件单独 add_executable;calculator_mainwindow_qss 加 themes.qrc 与
AUTORCC;all_teaching_examples 依赖列表同步补齐。

附 lineedit_eye_toggle 设计文档(docs/superpowers/specs)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 16:51:09 +08:00

109 lines
6.2 KiB
Markdown
Raw 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.
# qt_file_io_demo —— Qt5 文件操作教学示例
一个 `QWidget` 布满按钮,分 5 个主题演示 Qt5 文件操作。每个按钮产生一个文件,落到
**可执行文件同目录的 `demo_output/`** 子目录下,界面顶部 `statusLabel` 始终显示绝对路径,
方便课堂逐个手动打开查看文件内容。
> 这是对仓库 `json_io`(控制台、仅内存序列化)的 GUI 补充:把 **QJson + 真实文件 I/O**
> 与其它文件操作主题一起,放进一个可点击演示的界面。
## 构建
仓库统一构建(走 `examples/CMakeLists.txt``add_teaching_example`):
```bash
cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON \
-DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64 -DCMAKE_BUILD_TYPE=Debug
cmake --build build --target qt_file_io_demo
tools/run_qt.sh qt_file_io_demo
```
单独打开(用本目录的独立 `CMakeLists.txt`):
```bash
cmake -S . -B build -DCMAKE_PREFIX_PATH=<Qt5.14.2> -DCMAKE_CXX_COMPILER=<g++>
cmake --build build
```
离屏自动化验证(仿 p03 风格,200ms 自动退出、退出码 0):
```bash
QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_file_io_demo
```
## 界面布局
```
┌─ statusLabel(显示绝对路径 + 每个演示的结果摘要)─────────────┐
├─ ① 基础文件操作 QFile 写/读/追加、QFileInfo、QDir │
├─ ② 字符集编码 UTF-8 / Latin-1 / GBK / UTF-16LE / 乱码对比 │
├─ ③ 文本vs二进制 QTextStream / QDataStream / QIODevice::Text │
├─ ④ 异常处理 Qt 返回值式 | C++ try/catch(对照) │
├─ ⑤ QJson 文件 写 / 读 / 改写 │
├─ [在文件管理器中打开 demo_output] [清空 demo_output] │
└──────────────────────────────────────────────────────────────┘
```
## 各主题要点与产出文件
### ① 基础文件操作
| 按钮 | 产出文件 | 要点 |
|---|---|---|
| QFile 写文本 | `01_qfile_write.txt` | `QTextStream` 写;**坑点:Qt5 默认 codec 是系统区域编码(中文 Windows 常为 GBK),写中文必须显式 `setCodec("UTF-8")`** |
| QFile 读文本 | (读上一个) | 读端同样要 `setCodec("UTF-8")`,否则跨平台乱码 |
| 追加 vs 覆盖 | `01_append.txt` | `WriteOnly` 每次清空;`Append` 在末尾追加 |
| QFileInfo 信息 | `01_fileinfo.txt` | `exists / size / absoluteFilePath / absolutePath` |
| QDir 列目录 | — | `entryList(QDir::Files)` 列出 `demo_output` 内文件 |
### ② 字符集编码
| 按钮 | 产出文件 | 要点 |
|---|---|---|
| 写 UTF-8 | `02_utf8.txt` | `QString::toUtf8()`:中文每字 3 字节、ASCII 每字 1 字节 |
| 写 UTF-8 + BOM | `02_utf8_bom.txt` | 头部 `EF BB BF`(十六进制可见) |
| 写 Latin-1 | `02_latin1.txt` | `toLatin1()` 只能表 `U+0000~U+00FF`,中文超出变 `?` |
| 写 GBK | `02_gbk.txt` | `QTextCodec::codecForName("GBK")`**未装 CJK codec 插件时返回 `nullptr`,界面列出当前可用编码并给出提示**。本仓库 MinGW Qt 未含 `plugins/codecs/`,故此按钮走降级提示(这本身就是教学点);如需真正写 GBK,用 Qt Maintenance Tool 勾选安装 codecs 插件,或把 `plugins/codecs/` 拷到 Qt 的 plugins 目录 |
| 写 UTF-16LE | `02_utf16le.txt` | 手动写 `FF FE` BOM + 每字符固定 2 字节(低字节在前) |
| 乱码:错码读回 | (读 `02_utf8.txt` | UTF-8 字节用 Latin-1 强解 → mojibake,说明「读写编码须一致」 |
> 用编辑器(VS Code / Notepad++)切换编码打开这些文件,可直观看到「同一段中文在不同编码下的字节差异」与乱码成因。
### ③ 文本方式 vs 二进制方式
| 按钮 | 产出文件 | 要点 |
|---|---|---|
| 文本写结构 | `03_struct_text.txt` | `QTextStream``name=张三 / age=20 / score=95.5`:可读但格式松散 |
| 二进制写结构 | `03_struct_bin.dat` | `QDataStream``qint32 + double + QString`:紧凑、带类型、可精确还原(程序内立即读回验证) |
| 对比大小 | — | `QFileInfo` 报告两文件字节数;用十六进制工具看 `.dat` 内部布局 |
| QIODevice::Text 换行 | `03_nl_text.txt` / `03_nl_bin.txt` | Windows 下 `Text` 标志把 `\n``\r\n`,不加则原样 `\n`Linux 下两者相同 |
> `QDataStream` **务必 `setVersion(Qt_5_14)`**,否则 Qt 升级后二进制格式可能不兼容。
### ④ 异常与错误处理(两范式对照)
| 按钮 | 要点 |
|---|---|
| Qt: 打开不存在文件 | **Qt 范式**`open()` 返回 `false` + `error()` / `errorString()`,不抛异常 |
| Qt: 只读模式写 | 只读 `QFile``write()` 返回 `-1` + `errorString()` |
| C++: ifstream 异常 | **C++ 范式**`in.exceptions(failbit\|badbit)` 后打开失败抛 `std::ios_base::failure``try/catch` 捕获 |
| C++: throw + catch | 自定义异常 + `catch` 顺序(派生类在前、基类在后) |
| JSON: 损坏解析 | `04_broken.json``QJsonDocument::fromJson` 不抛异常,用 `QJsonParseError` 拿出错偏移与描述 |
> 核心对照:**Qt 几乎不用 C++ 异常**,错误经返回值/API 暴露;标准库流默认也不抛,需显式开启。
> 教学 LLVM 式「异常 vs 返回值」两种错误处理哲学的绝佳切入点。
### ⑤ QJson 文件读写
| 按钮 | 产出文件 | 要点 |
|---|---|---|
| 写 JSON 文件 | `05_student.json` | `QJsonObject` + `QJsonArray``QJsonDocument::toJson(Indented)` → 写文件 |
| 读 JSON 文件 | (读上一个) | `fromJson``object()` → 按键取值,`toArray()` 遍历数组 |
| 修改后回写 | (覆盖上一个) | 改 `score`、增 `grade`、删 `enrolled`,覆盖回写 |
## demo_output 在哪?
`QCoreApplication::applicationDirPath() + "/demo_output"` —— 即**可执行文件旁边**。
`tools/run_qt.sh` 运行时通常在 `build/` 下,所以路径形如 `build/qt_file_io_demo` 同级。
界面底部「在文件管理器中打开 demo_output」按钮可一键定位。
## 相关课程点
对应「C++ 方向 Day3」文件与数据格式章节;与 `json_io`(控制台 QJson 内存演示)、
`p03/ch11`wiki 的 QFile/QTextStream/QDataStream)互为补充。