
---
#领域/未知

#复盘/0 #Bug/关闭

## 一句话描述

[__开启软件总是提示上次文件______]

---
 
## 基础元信息

影响范围：[__软件启动_____]

## 缺陷记录

### 前置条件

[___编程软件N46_____]

### 复现步骤

1. [___打开主程序-1，关闭软件，期望下次开启无弹窗_____]
2. [___关闭主程序-1，关闭软件，期望下次开启无弹窗_____]
3. [___任务管理器关闭软件，期望下次开启有弹窗_____]

### 实际发生结果

[___总是出现弹窗_____]

### 期望结果

[________]

### 排查建议

void MainWindow::closeEvent(QCloseEvent *event)

### 根因分析

[________]

### 验证依据


## 第1部分：变量声明与未保存检测（第462~469行）

```cpp
bool has_unsaved = false;
bool user_discard_unsaved = false;  // 用户主动选择不保存
for (int i = 0; i < ui->tabWidget->count(); i++) {
    if (ui->tabWidget->tabText(i).contains("*")) {
        has_unsaved = true;
        break;
    }
}
```

**知识点：**

|代码|含义|
|---|---|
|`ui->tabWidget->count()`|获取标签页总数量|
|`ui->tabWidget->tabText(i)`|获取第 i 个标签页的显示文字|
|`.contains("*")`|检查字符串是否包含 `*`（未保存标记）|
|`break`|找到第一个就够了，不用继续遍历|

**为什么用两个变量？**

- `has_unsaved` = "有没有带星号的标签"（客观事实）
- `user_discard_unsaved` = "用户是不是主动说不保存了"（用户意图）
- 两者组合才能判断最终是否要记录 crash

---

## 第2部分：弹窗与用户选择处理（第471~496行）

```cpp
if (has_unsaved) {
    int result = QMessageBox::information(
        NULL,
        tr("提示"),
        tr("正在关闭软件，是否保存当前文件？"),
        QObject::tr("保存"),   // 返回值 0
        QObject::tr("不保存"), // 返回值 1
        QObject::tr("取消")    // 返回值 2
    );
    // ... 三种分支 ...
}
```

**知识点 — `QMessageBox::information` 的返回值：**
这个函数有3个按钮，返回值就是用户点击的第几个（从0开始数）：

```
┌─────────────────────────────┐
│          提示                │
│  正在关闭软件，是否保存当前文件？ │
│                             │
│   [保存]  [不保存]  [取消]    │
└─────────────────────────────┘
   ↑         ↑         ↑
 result=0  result=1  result=2
```

**三种分支的含义：**

```cpp
if (result == 0) { // 保存 → 调用保存函数，文件写磁盘
    for (...) { on_actionsave_triggered(); }
}
else if (result == 1) { // 不保存 → 标记一下，但不是异常
    user_discard_unsaved = true;
}
else if (result == 2) { // 取消 → 阻止窗口关闭！
    event->ignore();
    return;             // 直接退出函数，后面都不执行了
}
```

**核心知识点：`event->ignore()` vs `event->accept()`**

|方法|效果|类比|
|---|---|---|
|`event->accept()`|允许关门|说"好的，可以关了"|
|`event->ignore()`|拒绝关门|说"等等，先别关！"|
|不调用任何|默认行为取决于控件|窗口默认是 accept|

---

## 第3部分：写入 crash 标志（第498~523行）

```cpp
QString path_crash = QString("%1/system/data/crash_flag/crash_flag.txt")
                         .arg(QCoreApplication::applicationDirPath());
QFile file_crash(path_crash);
// ... 写入逻辑 ...
```

**知识点 — QString 的 `%1` 占位符拼接：**

```cpp
QString("%1/system/data/...").arg(QCoreApplication::applicationDirPath())
//                        ↑
//                    %1 会被替换成 arg() 的内容

// 假设 applicationDirPath() = "D:/Program/App"
// 结果: "D:/Program/App/system/data/crash_flag/crash_flag.txt"
```

这比 C 风格的字符串拼接更安全、更可读：

```cpp
// ❌ C风格（容易出错）:
QString path = QCoreApplication::applicationDirPath() + "/system/data/...";

// ✅ Qt风格（推荐）:
QString path = QString("%1/system/data/...").arg(QCoreApplication::applicationDirPath());
```

**知识点 — QTextStream 写文件：**

```cpp
QTextStream crashStream(&file_crash);  // 把文件绑定到文本流
crashStream.setCodec("gbk");           // 设置编码（中文需要）
crashStream << crash_list;             // 用 << 操作符写入（类似 cout）
file_crash.close();                    // 关闭文件（flush缓冲区到磁盘）
```

**crash 标志的两种内容：**

|条件|写入内容|含义|示例|
|---|---|---|---|
|`has_unsaved && !user_discard_unsaved`|`"1\n/path/a.nyt\n/path/b.nyt"`|异常退出，记录路径供恢复|下次启动会弹出"是否重新打开"|
|其他情况|`"0"`|正常退出|下次启动不会提示|

**知识点 — `chop(1)`：**

```cpp
crash_list.chop(1);  // 从末尾删掉1个字符
```

用来去掉循环中最后一次多余的 `\n` 换行符。等价于原来的 `.remove(size-1, 1)` 但更语义化。

---

## 第4部分：关闭所有标签页（第525~527行）

```cpp
while (ui->tabWidget->count() > 0) {
    removeSubTab(0);  // 始终删除索引0
}
```

**为什么每次都删索引0？**

因为 `removeSubTab` 删除一个 tab 后，后面的所有 tab 索引都会 **前移一位**：

```
初始状态:  [TabA] [TabB] [TabC]  count=3
remove(0)→  [TabB] [TabC]        count=2, 原来的TabB变成索引0
remove(0)→  [TabC]              count=1, TabC变成索引0
remove(0)→  (空)                 count=0, 结束循环
```

如果从后往前删也可以，但从前往删最简洁，不需要计算动态变化的索引。

---

## 第5部分：写入历史记录（第529~544行）

```cpp
QString history_str = "";
for (int i = 0; i < str_history_list.size(); i++) {
    history_str.append(str_history_list.at(i));
    if (i != str_history_list.size() - 1) {
        history_str.append("\n");  // 最后一条不加换行
    }
}
```

**这段做了什么？** 把内存中的历史列表（`str_history_list`）拼成一个字符串写入文件，这样下次打开软件还能看到最近打开过的文件。

---

## 第6部分：接受关闭事件（第546行）

```cpp
event->accept();
```

**这是整个函数唯一的出口**（除了 `return` 的取消分支）。执行到这里意味着：

- 用户没有点"取消"
- crash 标志已写好
- 标签页全部关掉了
- 历史记录已保存
- **正式允许窗口关闭**

---
## 总结：你学到的 Qt/C++ 模式

|模式|在哪用的|记忆口诀|
|---|---|---|
|**遍历检测**|第1部分|遍历 + break（找到就停）|
|**三按钮弹窗**|第2部分|返回值 0/1/2 对应按钮顺序|
|**事件拦截**|第2部分|ignore = 别关 / accept = 可以关|
|**%1 占位符**|第3部分|`"%1/...".arg(变量)` 比 `+` 更清晰|
|**QTextStream 写文件**|第3部分|绑定 → setCodec → `<<` 写入 → close|
|**前向删除法**|第4部分|while 删索引0，自动补位|
|**单出口原则**|第6部分|函数只有一个 accept 出口（除了错误return）|


