<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kiyose&apos;s Wiki</title><description>技术博客 &amp; 笔记 — 记录学习，整理思考</description><link>https://kiyose.wiki/</link><item><title>DeepSeek 搭配 Agent 客户端 — Reasonix / Cursor / Cline / Aider 配置与横评</title><link>https://kiyose.wiki/tech/deepseekclients-agent%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%A8%AA%E8%AF%84/</link><guid isPermaLink="true">https://kiyose.wiki/tech/deepseekclients-agent%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%A8%AA%E8%AF%84/</guid><description>2026年5月主流 Agent 客户端接入 DeepSeek V4 Pro 的配置方法、工具链能力对比与选型建议</description><pubDate>Sat, 16 May 2026 00:00:00 GMT</pubDate><content:encoded>## 为什么用第三方客户端而不是自己写

自己写的 Agent 循环（上篇）灵活但缺生态：

```
自己写            第三方客户端
  灵活性 ⭐⭐⭐⭐⭐    ⭐⭐⭐
  开箱工具          ⭐          ⭐⭐⭐⭐⭐ (LSP/终端/浏览器/Git)
  维护成本         高          低
  UI              ❌          ✅
  多模型切换       自己改代码   下拉菜单
```

结论：**原型/学习 → 自己写，日常工作 → 第三方客户端**。

---

## 一、Reasonix

&gt; Terminal-native AI 编程助手，支持多种模型，工具链内置

### 配置 DeepSeek

```bash
# 通过 CLI 配置
reasonix config set provider deepseek
reasonix config set model deepseek-v4-pro
reasonix config set api-key sk-your-key-here
```

或在 `~/.reasonix/config.yaml`：

```yaml
provider: deepseek
model: deepseek-v4-pro
api_key: sk-your-key-here
```

### 内置工具

| 工具 | 能力 |
|------|------|
| 文件读写 | 多文件编辑、diff 预览 |
| Shell | 沙箱执行、支持后台长任务 |
| LSP | 代码跳转、诊断、重构 |
| Git | commit、diff、branch 操作 |
| 子 Agent | explore / research / review 独立子任务 |
| Web | 网络搜索 + 网页抓取 |
| MCP | 扩展协议（数据库、文件系统等） |

### 优劣势

```
✅ 终端原生，无需离开命令行
✅ 工具链最完整（LSP + Git + 子 Agent + MCP）
✅ 多模型支持，可随时切换
✅ 任务追踪 + 计划审批流程
❌ 无图形 IDE 集成
❌ 学习曲线略高（纯 TUI 操作）
```

---

## 二、Claude Code (CC)

&gt; Anthropic 官方命令行 Agent，支持通过第三方 API 代理接入 DeepSeek

### 配置 DeepSeek

CC 原生只支持 Anthropic API，接入 DeepSeek 需要**代理转换层**：

```bash
# 方案1: one-api / litellm 代理
docker run -d -p 3000:3000 \
  -e OPENAI_API_KEY=sk-your-deepseek-key \
  -e OPENAI_API_BASE=https://api.deepseek.com/v1 \
  litellm/litellm

# 方案2: 直接用 openai 兼容接口
export ANTHROPIC_API_KEY=&quot;sk-your-key&quot;
export ANTHROPIC_BASE_URL=&quot;http://localhost:3000&quot;
# 配置映射: deepseek-v4-pro → 通过代理转发
```

### 内置工具

| 工具 | 能力 |
|------|------|
| 文件编辑 | 类似 Cursor 的 diff 编辑 |
| Shell | Bash 命令、Git 操作 |
| 浏览器 | Puppeteer 网页操作 |
| MCP | 扩展协议 |
| 子 Agent | Task 模式自动拆解任务 |

### 优劣势

```
✅ Anthropic 品质的 Agent 逻辑
✅ 子 Agent + 任务拆解能力强
✅ MCP 扩展生态
❌ 需要代理层，配置繁琐
❌ 代理层可能产生 token 损耗（格式转换）
❌ 非原生支持，稳定性风险
❌ Function Calling 格式差异可能丢失工具调用
```

---

## 三、Cursor

&gt; 最流行的 AI 编程 IDE，原生支持 OpenAI 兼容 API

### 配置 DeepSeek

Settings → Models → OpenAI API Key：

```
API Key: sk-your-deepseek-key
Base URL: https://api.deepseek.com/v1
Model: deepseek-v4-pro
```

### 内置工具

| 工具 | 能力 |
|------|------|
| Tab 补全 | 原生代码补全 |
| Inline Edit | 选中代码直接改 |
| Chat | 侧边栏对话 |
| Agent | 终端 + 文件 + LSP 一体化 |
| Composer | 多文件同时生成 |
| @符号引用 | @file @folder @web 上下文注入 |

### 优劣势

```
✅ IDE 深度集成，体验最流畅
✅ Tab 补全 + Agent 模式双引擎
✅ 配置最简单（原生支持 OpenAI 兼容）
✅ 图形化操作，零学习成本
❌ 付费产品（Pro $20/月）
❌ DeepSeek 不支持 Tab 补全（补全走 Cursor 自有模型）
❌ Agent 复杂度不如 Reasonix/CC
```

---

## 四、Cline (VS Code 插件)

&gt; VS Code 内 Agent，支持 OpenAI 兼容 API，开源免费

### 配置 DeepSeek

```
API Provider: OpenAI Compatible
Base URL: https://api.deepseek.com/v1
API Key: sk-your-key-here
Model ID: deepseek-v4-pro
```

### 内置工具

| 工具 | 能力 |
|------|------|
| 文件编辑 | diff 预览 + 确认 |
| Shell | VS Code 终端执行 |
| 浏览器 | Puppeteer |
| MCP | 扩展协议 |
| LSP | 诊断读取 |

### 优劣势

```
✅ VS Code 内使用，IDE + Agent 一体
✅ 开源免费
✅ MCP 支持
✅ 操作可审核（每次工具调用需用户批准）
❌ 工具调用需手动批准（效率低于 Reasonix 的自动执行）
❌ 大任务上下文消耗快
❌ 偶尔循环（同一问题反复修改）
```

---

## 五、Aider

&gt; 终端 Git-native 编程助手，专注多文件编辑

### 配置 DeepSeek

```bash
export DEEPSEEK_API_KEY=sk-your-key-here
aider --model deepseek/deepseek-v4-pro
```

或配置文件 `.aider.conf.yml`：

```yaml
model: deepseek/deepseek-v4-pro
api-key: sk-your-key-here
```

### 内置工具

| 工具 | 能力 |
|------|------|
| 文件编辑 | 语义查找替换 |
| Git | 自动 commit 变更 |
| Linter | 自动修复 lint 错误 |
| 测试 | 自动运行测试 |
| 多文件 | 架构理解 + 批量编辑 |

### 优劣势

```
✅ Git 集成最自然（自动 commit、diff 审查）
✅ 多文件架构编辑能力强
✅ 轻量、终端原生
✅ 地图式仓库理解（repo-map）
❌ 无 Shell 工具（不能执行命令）
❌ 无浏览器/网络工具
❌ 专注代码编辑，不是全功能 Agent
```

---

## 六、综合横评

| | Reasonix | Cursor | Cline | Aider | CC |
|------|:--:|:--:|:--:|:--:|:--:|
| **DeepSeek 接入** | ✅ 原生 | ✅ 原生 | ✅ 原生 | ✅ 原生 | ⚠️ 需要代理 |
| **IDE 集成** | ❌ TUI | ✅ VS Code 深度 | ✅ VS Code 插件 | ❌ TUI | ❌ TUI |
| **Git 操作** | ✅ | ✅ | ⚠️ 基础 | ✅ 深度 | ✅ |
| **Shell** | ✅ 沙箱 | ✅ | ✅ | ❌ | ✅ |
| **LSP** | ✅ | ✅ | ✅ | ❌ | ❌ |
| **子 Agent** | ✅ | ❌ | ❌ | ❌ | ✅ |
| **浏览器** | ✅ 搜索 | ❌ | ✅ | ❌ | ✅ |
| **MCP** | ✅ | ❌ | ✅ | ❌ | ✅ |
| **自动执行** | ✅ | ✅ | ❌ 需批准 | ❌ 需批准 | ✅ |
| **价格** | 开源 | $20/月 | 开源 | 开源 | 需 API |
| **学习曲线** | 中 | 低 | 低 | 低 | 中 |

## 七、场景选型

```
日常编码 + 补全          → Cursor (DeepSeek Agent 模式)
VS Code 内免费 Agent     → Cline
终端重度用户 + 多工具链   → Reasonix (子 Agent + MCP)
Git 仓库重构 + 批量编辑   → Aider
团队需要审核 Agent 操作   → Cline (手动批准)
预算充足 + 极致体验       → Cursor Pro
零成本 + 最强工具链       → Reasonix
```

## 八、我的推荐配置

```bash
# 主力: Reasonix (终端) + DeepSeek V4 Pro
# 原因: 子 Agent 并行处理大幅提效

# 辅助: Cursor (IDE) + DeepSeek V4 Pro
# 原因: Tab 补全 + 图形化方便浏览代码

# 搭配使用:
# 1. Cursor 打开项目 → 浏览代码结构
# 2. 终端切到项目目录 → reasonix 处理复杂任务
# 3. Cursor 查看 diff → 确认修改 → commit
```

## 九、常见问题

**Q: DeepSeek 的 Function Calling 格式和非 OpenAI 客户端兼容吗？**

DeepSeek API 完全兼容 OpenAI 的 Function Calling 格式（`tools` + `tool_calls`），和 Cursor / Cline / Aider 的 OpenAI 兼容模式都能直接对接。只有 CC 需要通过代理转换。

**Q: DeepSeek context window 够用吗？**

`deepseek-v4-pro` 支持 128K context，Agent 场景一般 10K-50K 足够。如果超出可以：

- 用子 Agent 拆解任务（Reasonix 的 explore/research）
- 手动清理对话历史
- 压缩之前的工具返回结果

**Q: 多个客户端共用一个 API Key 会被限流吗？**

DeepSeek 的并发限制按 Key 计算，建议不同客户端用不同 Key，或在客户端里设较低的 `max_tokens` 避免单个请求占用过长时间。</content:encoded></item><item><title>DeepSeek API 本地 Agent 搭建 — V4 Pro + 工具调用实战</title><link>https://kiyose.wiki/tech/deepseekagent-api%E6%9C%AC%E5%9C%B0agent%E6%90%AD%E5%BB%BA/</link><guid isPermaLink="true">https://kiyose.wiki/tech/deepseekagent-api%E6%9C%AC%E5%9C%B0agent%E6%90%AD%E5%BB%BA/</guid><description>从零配置 DeepSeek API，构建支持 Function Calling 的本地 Agent，实现文件读写、命令执行、网页搜索等工具链</description><pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate><content:encoded>## 为什么选择 DeepSeek

```
模型                   上下文    价格(输入/输出)       工具调用
deepseek-chat(V3)      128K     ¥1/¥4 每百万 token    ✅
deepseek-reasoner(R1)  128K     ¥4/¥16                ❌ 不支持 Function Calling
deepseek-v4-pro        128K     ¥2/¥8                 ✅ 支持 + 推理链
```

Agent 场景核心需求：**工具调用（Function Calling）** + 长上下文 + 推理能力。`deepseek-v4-pro` 三项都满足。

## 第一步：获取 API Key

1. 打开 [platform.deepseek.com](https://platform.deepseek.com)
2. 注册 → API Keys → 创建
3. 复制 `sk-xxxxxxxx` 保存好

## 第二步：最小调用验证

```bash
curl https://api.deepseek.com/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;Authorization: Bearer sk-your-key-here&quot; \
  -d &apos;{
    &quot;model&quot;: &quot;deepseek-v4-pro&quot;,
    &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Hello&quot;}],
    &quot;temperature&quot;: 0.7
  }&apos;
```

## 第三步：Function Calling — Agent 的灵魂

Agent 区别于普通聊天机器人的核心：**模型决定&quot;该调哪个工具&quot;，代码执行工具 + 把结果塞回对话**。

```python
import json
import requests

API_KEY = &quot;sk-your-key-here&quot;
API_URL = &quot;https://api.deepseek.com/v1/chat/completions&quot;

# ── 定义工具 ──
TOOLS = [
    {
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
            &quot;name&quot;: &quot;read_file&quot;,
            &quot;description&quot;: &quot;读取本地文件内容&quot;,
            &quot;parameters&quot;: {
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;path&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;description&quot;: &quot;文件路径&quot;}
                },
                &quot;required&quot;: [&quot;path&quot;]
            }
        }
    },
    {
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
            &quot;name&quot;: &quot;write_file&quot;,
            &quot;description&quot;: &quot;写入内容到本地文件&quot;,
            &quot;parameters&quot;: {
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;path&quot;: {&quot;type&quot;: &quot;string&quot;},
                    &quot;content&quot;: {&quot;type&quot;: &quot;string&quot;}
                },
                &quot;required&quot;: [&quot;path&quot;, &quot;content&quot;]
            }
        }
    },
    {
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
            &quot;name&quot;: &quot;run_command&quot;,
            &quot;description&quot;: &quot;执行 shell 命令并返回输出&quot;,
            &quot;parameters&quot;: {
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;command&quot;: {&quot;type&quot;: &quot;string&quot;}
                },
                &quot;required&quot;: [&quot;command&quot;]
            }
        }
    }
]

# ── 工具执行器 ──
def execute_tool(name: str, args: dict) -&gt; str:
    if name == &quot;read_file&quot;:
        with open(args[&quot;path&quot;], &quot;r&quot;) as f:
            return f.read()
    elif name == &quot;write_file&quot;:
        with open(args[&quot;path&quot;], &quot;w&quot;) as f:
            f.write(args[&quot;content&quot;])
        return f&quot;已写入 {args[&apos;path&apos;]}&quot;
    elif name == &quot;run_command&quot;:
        import subprocess
        result = subprocess.run(
            args[&quot;command&quot;], shell=True,
            capture_output=True, text=True, timeout=30
        )
        return result.stdout or result.stderr
    return f&quot;未知工具: {name}&quot;

# ── Agent 循环 ──
SYSTEM_PROMPT = &quot;&quot;&quot;你是一个本地编程助手，可以读写文件和执行命令。
需要操作文件或执行命令时，调用对应工具。完成用户任务后直接总结结果。&quot;&quot;&quot;

def agent_loop(user_input: str, max_turns: int = 10):
    messages = [
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: SYSTEM_PROMPT},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: user_input}
    ]

    for turn in range(max_turns):
        resp = requests.post(API_URL, headers={
            &quot;Authorization&quot;: f&quot;Bearer {API_KEY}&quot;,
            &quot;Content-Type&quot;: &quot;application/json&quot;
        }, json={
            &quot;model&quot;: &quot;deepseek-v4-pro&quot;,
            &quot;messages&quot;: messages,
            &quot;tools&quot;: TOOLS,
            &quot;temperature&quot;: 0.3
        }).json()

        msg = resp[&quot;choices&quot;][0][&quot;message&quot;]

        # 模型决定回复文本 → 结束
        if msg.get(&quot;content&quot;) and not msg.get(&quot;tool_calls&quot;):
            return msg[&quot;content&quot;]

        # 模型决定调工具 → 执行 + 塞回结果
        if msg.get(&quot;tool_calls&quot;):
            messages.append(msg)  # 助手消息（含工具调用）

            for tc in msg[&quot;tool_calls&quot;]:
                fn_name = tc[&quot;function&quot;][&quot;name&quot;]
                fn_args = json.loads(tc[&quot;function&quot;][&quot;arguments&quot;])
                print(f&quot;  🔧 调用工具: {fn_name}({fn_args})&quot;)

                result = execute_tool(fn_name, fn_args)

                messages.append({
                    &quot;role&quot;: &quot;tool&quot;,
                    &quot;tool_call_id&quot;: tc[&quot;id&quot;],
                    &quot;content&quot;: result[:2000]  # 截断过长结果
                })

    return &quot;达到最大轮次&quot;
```

## 第四步：实战 — 让 Agent 分析项目

```python
result = agent_loop(&quot;&quot;&quot;
请分析 /home/user/myproject 目录：
1. 列出所有 .cpp 和 .h 文件
2. 统计代码总行数
3. 把结果写入 project_stats.txt
&quot;&quot;&quot;)
print(result)
```

执行过程：

```
  🔧 调用工具: run_command({&quot;command&quot;: &quot;find /home/user/myproject -name &apos;*.cpp&apos; -o -name &apos;*.h&apos;&quot;})
  🔧 调用工具: run_command({&quot;command&quot;: &quot;find /home/user/myproject ... | xargs wc -l&quot;})
  🔧 调用工具: write_file({&quot;path&quot;: &quot;project_stats.txt&quot;, &quot;content&quot;: &quot;...&quot;})

Agent 回复：
  项目分析完成：
  - C++ 源文件: 15 个
  - 头文件: 8 个
  - 总行数: 4,230 行
  - 结果已写入 project_stats.txt
```

## 第五步：扩展更多工具

```python
# 网页搜索
{
    &quot;name&quot;: &quot;web_search&quot;,
    &quot;description&quot;: &quot;搜索网页&quot;,
    &quot;parameters&quot;: {
        &quot;properties&quot;: {
            &quot;query&quot;: {&quot;type&quot;: &quot;string&quot;}
        }
    }
}

# 执行 Python 代码
{
    &quot;name&quot;: &quot;run_python&quot;,
    &quot;description&quot;: &quot;执行 Python 代码并返回结果&quot;,
    &quot;parameters&quot;: {
        &quot;properties&quot;: {
            &quot;code&quot;: {&quot;type&quot;: &quot;string&quot;}
        }
    }
}

# Git 操作
{
    &quot;name&quot;: &quot;git_commit&quot;,
    &quot;description&quot;: &quot;提交当前变更&quot;,
    &quot;parameters&quot;: {
        &quot;properties&quot;: {
            &quot;message&quot;: {&quot;type&quot;: &quot;string&quot;}
        }
    }
}
```

## 第六步：安全边界 — 白名单 + 确认机制

```python
DANGEROUS_COMMANDS = [&quot;rm -rf&quot;, &quot;sudo&quot;, &quot;chmod 777&quot;, &quot;&gt; /dev/sda&quot;]

def execute_tool_safe(name: str, args: dict) -&gt; str:
    if name == &quot;run_command&quot;:
        cmd = args[&quot;command&quot;]
        for dangerous in DANGEROUS_COMMANDS:
            if dangerous in cmd:
                return f&quot;❌ 拒绝执行危险命令: &apos;{dangerous}&apos;&quot;
    return execute_tool(name, args)

# 高风险操作要求确认
HIGH_RISK_TOOLS = {&quot;write_file&quot;, &quot;run_command&quot;}

# 在 agent_loop 中：
if fn_name in HIGH_RISK_TOOLS:
    print(f&quot;  ⚠️ 高风险操作: {fn_name}({fn_args})&quot;)
    confirm = input(&quot;  执行? (y/n): &quot;)
    if confirm.lower() != &apos;y&apos;:
        messages.append({
            &quot;role&quot;: &quot;tool&quot;,
            &quot;tool_call_id&quot;: tc[&quot;id&quot;],
            &quot;content&quot;: &quot;用户拒绝了此操作&quot;
        })
        continue
```

## 关键参数调优

| 参数 | 推荐值 | 原因 |
|------|:--:|------|
| `temperature` | 0.3 | Agent 场景需要确定性，不要创意 |
| `top_p` | 0.9 | 配合低 temperature |
| `max_tokens` | 4096 | 足够返回工具参数 + 总结 |
| System Prompt | 精简 | 省 token，把空间留给工具结果 |

## 常见问题

**&quot;为何不用 deepseek-reasoner？&quot;**
→ R1 不支持 Function Calling，无法调工具。

**&quot;工具结果太长怎么办？&quot;**
→ `content[:2000]` 截断。或让工具返回摘要而非全文。

**&quot;如何让 Agent 记住之前的操作？&quot;**
→ messages 数组天然是对话历史，工具结果塞回去模型就能引用。

**&quot;如何对接到 Webhook / Discord Bot？&quot;**
→ 把 `agent_loop(input)` 包装成 HTTP 端点即可：

```python
from flask import Flask, request
app = Flask(__name__)

@app.post(&quot;/chat&quot;)
def chat():
    user_input = request.json[&quot;message&quot;]
    reply = agent_loop(user_input)
    return {&quot;reply&quot;: reply}
```

## 完整文件结构

```
my-agent/
├── agent.py          # 核心循环 + 工具定义
├── config.py         # API_KEY + 模型参数
├── tools/
│   ├── files.py      # 文件读写工具
│   ├── shell.py      # 命令执行工具
│   └── web.py        # 搜索工具
└── requirements.txt  # requests, flask
```</content:encoded></item><item><title>Qt 性能瓶颈与优化</title><link>https://kiyose.wiki/tech/qtperformance-qt%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/</link><guid isPermaLink="true">https://kiyose.wiki/tech/qtperformance-qt%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/</guid><description>CPU 热点定位、绘制优化、内存/IO 瓶颈、QObject 信号槽性能、Profiling 工具链</description><pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate><content:encoded>## 常见瓶颈分类

| 类型 | 表现 | 定位工具 |
|------|------|---------|
| CPU | 界面卡顿、数据处理慢 | perf / VTune / QElapsedTimer |
| 绘制 | 滚动不流畅、缩放掉帧 | Qt 帧率监控 |
| 内存 | OOM、swap、堆碎片 | valgrind / heaptrack |
| IO | 启动慢、读写卡顿 | strace / QIODevice 耗时 |
| 信号槽 | 大量信号场景响应慢 | QElapsedTimer 埋点 |

## CPU 瓶颈 — 绘制是头号杀手

### 禁用不必要的抗锯齿

```cpp
// 绘制 10000 条线段（如实时曲线）
// 开启抗锯齿:  ~200ms（掉帧到 5FPS）
// 关闭抗锯齿:  ~5ms（60FPS 无压力）
p.setRenderHint(QPainter::Antialiasing, false);
p.drawPolyline(polyline);
```

### QPolygonF &gt; QPainterPath（折线场景）

```cpp
// ❌ QPainterPath — 即使全直线也走贝塞尔管线，慢 3-5 倍
QPainterPath path;
for (auto &amp;pt : points) { path.lineTo(pt); }
p.drawPath(path);

// ✅ QPolygonF — 纯折线，硬件加速友好的数据结构
QPolygonF poly;
for (auto &amp;pt : points) { poly &lt;&lt; pt; }
p.drawPolyline(poly);
```

### 每帧开销检查清单

```cpp
void paintEvent(QPaintEvent *e) override {
    // ☐ QPixmap 是否复用？（成员变量，只在 resize 时重建）
    // ☐ Pen/Brush/Font 是否复用？（成员变量，不在 paintEvent 内创建）
    // ☐ 是否裁剪到 damage rect？（p.setClipRect(e-&gt;rect())）
    // ☐ 是否做了抽稀？（1px 内的点合并 → 减少 drawPolyline 数据量）
    // ☐ 文字绘制是否缓存？（staticText 替代 drawText）
}
```

## 内存瓶颈

### QPixmap 缓存策略

```cpp
// ❌ 每次 paintEvent 重建 QPixmap → 每帧 malloc + free 3.8MB
void paintEvent(QPaintEvent *) {
    QPixmap offscreen(size());  // 16ms 后析构 → 碎片累积
}

// ✅ 成员变量复用：只在 resizeEvent 重建
void resizeEvent(QResizeEvent *e) override {
    m_offscreen = QPixmap(e-&gt;size());
}
```

### QObject 父子关系与内存

```cpp
// ✅ 给 parent — Qt 自动管理生命周期
auto *btn = new QPushButton(&quot;OK&quot;, this);  // this 析构时自动 delete btn

// ❌ 漏设 parent + 忘记 delete → 内存泄漏
auto *btn = new QPushButton(&quot;OK&quot;);

// ⚠️ moveToThread 的 worker 不能有 parent
Worker *w = new Worker;  // 无 parent
w-&gt;moveToThread(thread);
// Qt 不允许 parent 和自身不在同一线程的 QObject
```

## IO 瓶颈

### SQLite 写入优化

```cpp
// ❌ 每条 INSERT 单独提交 — 1000 次 fsync
for (auto &amp;dp : dataPoints)
    query.exec(&quot;INSERT INTO ...&quot;);  // 每次都是隐式事务

// ✅ 批量事务 — 1 次 fsync
db.transaction();
for (auto &amp;dp : dataPoints) {
    query.addBindValue(dp.value);
    query.exec();
}
db.commit();
// 性能提升 10-50 倍

// ✅ WAL 模式 — 读写并发
db.exec(&quot;PRAGMA journal_mode=WAL&quot;);
```

### 文件 IO 异步化

```cpp
// ❌ 主线程同步写文件 → UI 冻结
void saveReport() {
    QFile f(&quot;report.csv&quot;);
    f.open(QIODevice::WriteOnly);
    f.write(largeData);  // 可能 500ms → UI 卡死
}

// ✅ 异步写
void saveReport() {
    auto *worker = new FileWriter(&quot;report.csv&quot;, largeData);
    QThread *thread = new QThread;
    worker-&gt;moveToThread(thread);
    connect(thread, &amp;QThread::started, worker, &amp;FileWriter::write);
    connect(worker, &amp;FileWriter::finished, thread, &amp;QThread::quit);
    connect(worker, &amp;FileWriter::finished, this, &amp;MainWindow::onSaved);
    thread-&gt;start();
}
```

## 信号槽性能

```cpp
// 信号槽调用开销 ≈ 函数指针的 5-10 倍
// 100Hz 数据流每秒 100 次 signal → 开销可忽略
// 100kHz 传感器每秒 100,000 次 signal → 考虑直接函数调用

// DirectConnection 比 QueuedConnection 快 3-5 倍
// 但跨线程必须用 QueuedConnection
connect(sender, &amp;Sender::dataReady,
        receiver, &amp;Receiver::onData,
        Qt::DirectConnection);  // 同线程 → 快
```

### 避免信号风暴

```cpp
// ❌ 大批量更新触发 N 次信号
for (auto &amp;row : hugeData)
    model-&gt;appendRow(row);  // 每次 emit layoutChanged → 整个 View 重绘

// ✅ 批量操作后发一次信号
model-&gt;beginResetModel();
for (auto &amp;row : hugeData)
    model-&gt;appendRowSilently(row);  // 内部追加，不发射信号
model-&gt;endResetModel();  // 一次性通知 View 刷新
```

## Profiling 工具链

| 工具 | 用途 | 命令 |
|------|------|------|
| `QElapsedTimer` | 函数耗时埋点 | 代码内嵌 |
| `valgrind --tool=callgrind` | CPU 调用图 | `valgrind --tool=callgrind ./app` |
| `heaptrack` | 内存分配热点 | `heaptrack ./app` |
| `perf` | Linux 采样 Profiling | `perf record -g ./app` |
| Qt 内置日志 | 绘制帧率 | `QT_LOGGING_RULES=qt.qpa.*=true` |

### QElapsedTimer 快速埋点

```cpp
#include &lt;QElapsedTimer&gt;

QElapsedTimer t; t.start();
doExpensiveWork();
qDebug() &lt;&lt; &quot;doExpensiveWork:&quot; &lt;&lt; t.elapsed() &lt;&lt; &quot;ms&quot;;

// 热点定位三步：
// 1. 先找最慢的大函数 → QElapsedTimer
// 2. 再进函数内找慢的循环/算法 → 代码审查
// 3. 最后用 perf/callgrind 找指令级热点
```

## 优化优先级

```
1. 绘制优化          ← 收益最大（卡顿的 80% 原因）
   抗锯齿/数据结构/QPixmap 复用/抽稀

2. IO 异步化         ← 阻塞主线程的直接原因
   文件读写踢到 Worker 线程 / SQLite WAL + 批量事务

3. 信号槽批量         ← 高频场景才需要
   beginResetModel / 直接函数调用 / 信号合并

4. 内存复用          ← 避免碎片
   QPixmap/Pen/Brush 成员变量复用

5. 算法优化          ← 最后才做
   只有在 Profiling 确认算法是瓶颈时才优化
```</content:encoded></item><item><title>Qt 界面绘制 — 顺序逻辑与设计方法</title><link>https://kiyose.wiki/tech/qtrendering-qt%E7%95%8C%E9%9D%A2%E7%BB%98%E5%88%B6/</link><guid isPermaLink="true">https://kiyose.wiki/tech/qtrendering-qt%E7%95%8C%E9%9D%A2%E7%BB%98%E5%88%B6/</guid><description>Qt 渲染管线、paintEvent 调用时机、update/repaint 区别、控件层级与布局策略</description><pubDate>Wed, 08 Oct 2025 00:00:00 GMT</pubDate><content:encoded>## Qt 渲染管线

```
事件循环
  │
  ├─→ update() 调用（标记脏区域）
  │     │
  │     └─→ QWidgetBackingStore::sync()
  │           │
  │           ├─→ paintEvent(QPaintEvent *) ← 你写绘制代码的地方
  │           │     └─ QPainter 绘制到 QPixmap（离屏）
  │           │
  │           └─→ 贴到屏幕（平台相关: D3D / OpenGL / X11）
  │
  └─→ 下一帧（16ms 后，如果 60FPS）
```

## paintEvent 调用时机

```cpp
// 以下操作触发 paintEvent:
widget-&gt;update();        // 异步，合并多次调用（推荐）
widget-&gt;repaint();       // 同步，立即绘制（慎用）
widget-&gt;show();          // 首次显示
widget-&gt;resize(w, h);    // 尺寸变化
widget-&gt;setVisible(true);// 从隐藏到可见
// 被遮挡部分重新露出     // 系统触发
```

### update() vs repaint()

```cpp
// update() — 标记需要重绘，等事件循环统一处理
void onDataReceived() {
    m_buffer.push(data);
    update();  // 不阻塞，数据继续进来
    update();  // 连续两次 → 合并为一次 paintEvent
}

// repaint() — 当场画，不等待
void onScreenshot() {
    repaint();  // 立即画，保证截图是当前状态
    grab().save(&quot;screenshot.png&quot;);
}
// ⚠️ repaint 在 60FPS 下每帧只能用一次，频繁调用卡主事件循环
```

## 控件层级与绘制顺序

```
父控件::paintEvent 先执行
  ├─ 绘制自身背景
  ├─ 绘制自身内容
  ├─ 子控件 1::paintEvent
  ├─ 子控件 2::paintEvent
  └─ 子控件 N::paintEvent
后绘制的控件覆盖在前面（Z 轴叠加）
```

```cpp
// 控件层级控制绘制顺序
void CustomWidget::paintEvent(QPaintEvent *) {
    QPainter p(this);

    // 1. 背景层 — 先画
    p.fillRect(rect(), QColor(&quot;#f5f5f5&quot;));

    // 2. 网格层
    drawGrid(p);

    // 3. 数据层 — 后画（覆盖在上面）
    drawCurves(p);

    // 4. 标注层 — 最上面
    drawLegend(p);
    drawCrosshair(p, m_mousePos);

    // 5. 子控件自动绘制（Qt 在 paintEvent 返回后处理）
}
```

## 布局设计方法

### QWidget 布局三要素

```cpp
// ① sizeHint — 告诉布局管理器&quot;我想要多大&quot;
QSize MyWidget::sizeHint() const override {
    return QSize(200, 150);  // 理想大小
}

// ② minimumSizeHint — &quot;不能再小了&quot;
QSize MyWidget::minimumSizeHint() const override {
    return QSize(100, 80);
}

// ③ sizePolicy — &quot;空间多了/少了怎么分配&quot;
setSizePolicy(QSizePolicy::Expanding,  // 水平：尽量占满
              QSizePolicy::Fixed);     // 垂直：固定
```

### 布局策略选择

| 场景 | 布局策略 | 原因 |
|------|---------|------|
| 固定尺寸控件 | `Fixed` | 按钮、标签 |
| 表单输入框 | `Expanding` 水平 + `Fixed` 垂直 | 横向随窗口伸缩 |
| 日志/终端输出 | `Expanding` x 2 | 占满所有剩余空间 |
| 侧边栏 | `Preferred` + 限制 `maximumWidth` | 可伸缩但有上限 |
| 占位块 | `Ignored` | 布局 spacer 替代 |

### 自定义控件在布局中的行为

```cpp
// 自绘控件必须重写 sizeHint，否则默认 640x480
class RealTimeChart : public QWidget {
public:
    QSize sizeHint() const override {
        return QSize(400, 300);  // 不给布局一个期望值 = 布局不工作
    }

    // ⚠️ 绝对不能用 setGeometry 替代布局
    // RealTimeChart(0,0, 400,300) ← 窗口放大控件不变
};
```

## 双缓冲的正确实现

```cpp
class SmoothWidget : public QWidget {
    QPixmap m_offscreen;

    void paintEvent(QPaintEvent *) override {
        // ① 尺寸变了 → 重建
        if (m_offscreen.size() != size())
            m_offscreen = QPixmap(size());

        // ② 清空
        m_offscreen.fill(palette().window().color());

        // ③ 在内存中画完
        {
            QPainter p(&amp;m_offscreen);
            p.setRenderHint(QPainter::Antialiasing, false);
            drawAll(p);
        }  // p 析构 → flush 到 QPixmap

        // ④ 一次性贴到屏幕
        QPainter screen(this);
        screen.drawPixmap(0, 0, m_offscreen);
    }
};
```

## 抗锯齿按区域控制

```cpp
void drawContent(QPainter &amp;p) {
    // 文字和轴 → 开抗锯齿（没几笔画，开销可忽略）
    p.setRenderHint(QPainter::Antialiasing, true);
    drawAxis(p);
    drawTextLabels(p);

    // 数据曲线 → 关抗锯齿（数千条线段，开抗锯齿慢 20 倍）
    p.setRenderHint(QPainter::Antialiasing, false);
    drawDataCurves(p);
}
```

## 常见错误

```
❌ 在 paintEvent 外创建 QPainter(this)  → 未定义行为
   QPainter 生命周期 = paintEvent 内

❌ 在 paintEvent 里 new 对象           → 每帧分配 → 堆碎片
   ✅ 成员变量复用（QPixmap、QPen、QBrush、QFont）

❌ 忽略 update() 的脏区域优化
   QPaintEvent::rect() 告诉你要画多大，可以只画这条
   ✅ 裁剪到 damage rect: p.setClipRect(event-&gt;rect())

❌ 在子控件 paintEvent 前忘了调父类
   ✅ 第一行: QWidget::paintEvent(event) (画背景)
```</content:encoded></item><item><title>SIMD 入门 — 单指令多数据</title><link>https://kiyose.wiki/notes/simd/</link><guid isPermaLink="true">https://kiyose.wiki/notes/simd/</guid><description>SSE/AVX 基础、_mm_add_ps 等 intrinsic、编译器自动向量化与实战场景</description><pubDate>Thu, 28 Aug 2025 00:00:00 GMT</pubDate><content:encoded>## 核心思想

```cpp
// 标量: 一次算 1 个
for (int i = 0; i &lt; 1024; ++i)
    c[i] = a[i] + b[i];  // 1024 次加法

// SIMD: 一次算 4 个 (SSE) 或 8 个 (AVX)
// [a0,a1,a2,a3] + [b0,b1,b2,b3] = [c0,c1,c2,c3] ← 一条指令
// → 4 倍吞吐
```

## SSE Intrinsics

```cpp
#include &lt;xmmintrin.h&gt;  // SSE
#include &lt;emmintrin.h&gt;  // SSE2

// 加载 4 个 float → 128 位寄存器
__m128 va = _mm_loadu_ps(&amp;a[i]);
__m128 vb = _mm_loadu_ps(&amp;b[i]);

// 并行加法
__m128 vc = _mm_add_ps(va, vb);

// 存回
_mm_storeu_ps(&amp;c[i], vc);
```

## AVX2 — 256 位

```cpp
#include &lt;immintrin.h&gt;

// 一次处理 8 个 float
__m256 va = _mm256_loadu_ps(&amp;a[i]);
__m256 vb = _mm256_loadu_ps(&amp;b[i]);
__m256 vc = _mm256_add_ps(va, vb);
_mm256_storeu_ps(&amp;c[i], vc);
```

## 常用 Intrinsics 速查

| 操作 | SSE (4×float) | AVX (8×float) |
|------|------|------|
| 加 | `_mm_add_ps` | `_mm256_add_ps` |
| 减 | `_mm_sub_ps` | `_mm256_sub_ps` |
| 乘 | `_mm_mul_ps` | `_mm256_mul_ps` |
| 除 | `_mm_div_ps` | `_mm256_div_ps` |
| 平方根 | `_mm_sqrt_ps` | `_mm256_sqrt_ps` |
| 最大 | `_mm_max_ps` | `_mm256_max_ps` |
| 比较 | `_mm_cmplt_ps` | `_mm256_cmp_ps` |

## 编译器自动向量化

```cpp
// 编译器在 -O2/-O3 自动尝试向量化
// 以下写法更容易被编译器向量化：

// ✅ 简单的计数循环
for (int i = 0; i &lt; N; ++i)  // 清晰的迭代次数
    c[i] = a[i] + b[i];

// ❌ 复杂的控制流 — 阻止向量化
for (int i = 0; i &lt; N; ++i) {
    if (a[i] &gt; 0) c[i] = a[i] + b[i];
    else c[i] = a[i] - b[i];
}

// 查看编译器是否向量化了
// gcc -fopt-info-vec-optimized
// clang -Rpass=vectorize
```

## 何时值得手动 SIMD

```
自动向量化 80% 场景够用。
手动 SIMD 值得的 3 个条件:
1. 热点循环（profiling 确认）
2. 数据布局对 SIMD 友好（连续、对齐）
3. 编译器没向量化（查看机器码确认）
```</content:encoded></item><item><title>测试设计 — 路径、用例选择与分层策略</title><link>https://kiyose.wiki/tech/testdesign-%E6%B5%8B%E8%AF%95%E8%AE%BE%E8%AE%A1/</link><guid isPermaLink="true">https://kiyose.wiki/tech/testdesign-%E6%B5%8B%E8%AF%95%E8%AE%BE%E8%AE%A1/</guid><description>测试金字塔实践、单元/集成/E2E 用例设计、Mock 与 Fakes、覆盖率衡量与 CI 集成</description><pubDate>Tue, 12 Aug 2025 00:00:00 GMT</pubDate><content:encoded>## 测试金字塔（重新审视）

```
        ┌──────────┐
        │  E2E     │  少(5%)   : 关键用户路径
       ┌┴──────────┴┐
       │  集成测试   │  中(15%)  : 模块间契约
      ┌┴────────────┴┐
      │   单元测试    │  多(80%)  : 每个函数的逻辑分支
     └───────────────┘
```

**反模式 — 冰淇淋甜筒**：E2E 多、单元少 → 跑得慢、定位难、维护贵。

## 单元测试：用例选择

### 等价类划分

```typescript
// 被测函数
function calculateDiscount(age: number, isMember: boolean): number

// 等价类划分
// age:  [0,12] 儿童  [13,59] 成人  [60,∞) 老人  [-∞,-1] 非法
// isMember: true / false

// 只需 4+1 个用例覆盖所有等价类:
test(&apos;儿童会员 50% 折扣&apos;, () =&gt; expect(calculateDiscount(10, true)).toBe(0.5))
test(&apos;成人非会员 0%&apos;,       () =&gt; expect(calculateDiscount(30, false)).toBe(0))
test(&apos;老人会员 30% 折扣&apos;,   () =&gt; expect(calculateDiscount(65, true)).toBe(0.3))
test(&apos;老人非会员 10% 折扣&apos;, () =&gt; expect(calculateDiscount(70, false)).toBe(0.1))
test(&apos;负数抛异常&apos;,           () =&gt; expect(() =&gt; calculateDiscount(-1, true)).toThrow())
```

### 边界值分析

```typescript
// 年龄边界: -1, 0, 12, 13, 59, 60
test(&apos;刚好 0 岁&apos;,   () =&gt; calculateDiscount(0, false))
test(&apos;刚好 12 岁&apos;,  () =&gt; calculateDiscount(12, false))
test(&apos;刚好 13 岁&apos;,  () =&gt; calculateDiscount(13, false))
test(&apos;刚好 59 岁&apos;,  () =&gt; calculateDiscount(59, false))
test(&apos;刚好 60 岁&apos;,  () =&gt; calculateDiscount(60, false))
```

## Mock vs Fake vs Stub

```typescript
// 被测代码
class OrderService {
    constructor(private repo: IOrderRepo, private notifier: INotifier) {}
    async place(order: Order) {
        await this.repo.save(order);
        await this.notifier.send(order.userId, &quot;订单已创建&quot;);
    }
}

// Fake — 内存实现，用于测试
class FakeOrderRepo implements IOrderRepo {
    orders: Order[] = [];
    async save(o: Order) { this.orders.push(o); }
    async findById(id: number) { return this.orders.find(o =&gt; o.id === id); }
}

// Mock — 验证调用行为
test(&apos;下单应保存并通知&apos;, async () =&gt; {
    const mockNotifier = { send: jest.fn() };
    const fakeRepo = new FakeOrderRepo();
    const svc = new OrderService(fakeRepo, mockNotifier);

    await svc.place(new Order(1, &quot;item&quot;));

    expect(fakeRepo.orders).toHaveLength(1);         // 状态验证
    expect(mockNotifier.send).toHaveBeenCalledTimes(1); // 行为验证
});
```

### 选择指南

| 类型 | 何时用 |
|------|--------|
| **Fake** | 有状态依赖（DB、缓存）→ 内存实现 |
| **Stub** | 返回固定值的查询 → 直接替代 |
| **Mock** | 需要验证&quot;是否被调用/调用几次&quot; |
| **Spy** | 真实对象 + 记录调用（部分 mock） |

## 集成测试：契约验证

```typescript
// 测 UserRepo 和真实 MySQL 的交互
describe(&apos;UserRepository → MySQL&apos;, () =&gt; {
    let repo: UserRepository;

    beforeAll(async () =&gt; {
        // 连接测试数据库（非生产）
        const db = await mysql.createConnection(testDbConfig);
        repo = new UserRepository(db);
    });

    beforeEach(async () =&gt; {
        await repo.deleteAll();  // 每个用例前清空
    });

    test(&apos;保存后能查到&apos;, async () =&gt; {
        await repo.save(new User(1, &quot;Alice&quot;));
        const user = await repo.findById(1);
        expect(user.name).toBe(&quot;Alice&quot;);
    });

    test(&apos;查不存在的返回 null&apos;, async () =&gt; {
        const user = await repo.findById(999);
        expect(user).toBeNull();
    });
});
```

## E2E：只测核心路径

```typescript
// 不要测&quot;每个按钮&quot;！只测关键用户旅程
test(&apos;用户注册 → 登录 → 创建订单&apos;, async () =&gt; {
    // 注册
    await page.goto(&apos;/register&apos;);
    await page.fill(&apos;#email&apos;, &apos;test@example.com&apos;);
    await page.click(&apos;#submit&apos;);
    await expect(page.locator(&apos;.success&apos;)).toBeVisible();

    // 登录
    await page.goto(&apos;/login&apos;);
    await page.fill(&apos;#email&apos;, &apos;test@example.com&apos;);
    await page.click(&apos;#login&apos;);

    // 下单
    await page.click(&apos;#new-order&apos;);
    await page.fill(&apos;#item&apos;, &apos;Test Item&apos;);
    await page.click(&apos;#place-order&apos;);
    await expect(page.locator(&apos;.order-id&apos;)).toBeVisible();
});
```

## 覆盖率指南

| 指标 | 健康值 | 说明 |
|------|:--:|------|
| 行覆盖率 | 70-80% | 100% 成本太高且边际收益递减 |
| 分支覆盖率 | 60-70% | 每个 if/else 都要走到 |
| 函数覆盖率 | 80%+ | 核心逻辑 100% |

**不追求 100%**：getter/setter、简单转发代码不测；复杂算法 100% 测。

## CI 集成

```yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test -- --coverage
      - name: Upload coverage
        uses: codecov/codecov-action@v4
```

## 测试反模式

```
❌ 测试实现细节: &quot;调了 save() 然后调了 send()&quot; — 重构就断
✅ 测试行为:        &quot;下单后订单已保存 + 用户收到通知&quot;

❌ 每个测试依赖前一个状态: test2 依赖 test1 的数据 → 顺序敏感
✅ 每个测试独立:           beforeEach 重置状态

❌ 过度 mock: Mock 了 5 个依赖 → 测试的是 mock 不是代码
✅ 轻 mock:     只用 fake 替代外部 IO，核心逻辑走真的
```</content:encoded></item><item><title>C++ Allocator — 自定义内存分配</title><link>https://kiyose.wiki/notes/allocator/</link><guid isPermaLink="true">https://kiyose.wiki/notes/allocator/</guid><description>std::allocator 接口、pmr 多态分配器、内存池、monotonic_buffer 与 STL 容器定制</description><pubDate>Tue, 05 Aug 2025 00:00:00 GMT</pubDate><content:encoded>## 为什么自定义 Allocator

```cpp
// std::vector 默认用 new/delete 分配 → 频繁小块分配 → 堆碎片
std::vector&lt;int&gt; v;
for (int i = 0; i &lt; 1000000; ++i) v.push_back(i);  // 多次 realloc

// 自定义 allocator → 预分配一大块 → 零碎片
```

## pmr (Polymorphic Memory Resource) — C++17

```cpp
#include &lt;memory_resource&gt;

// monotonic_buffer — 只增长不释放（最快，适合短期大量分配）
char buffer[1024 * 1024];  // 1MB 栈上缓冲区
std::pmr::monotonic_buffer_resource pool{buffer, sizeof(buffer)};

std::pmr::vector&lt;int&gt; v{&amp;pool};  // 用自定义分配器的 vector
for (int i = 0; i &lt; 100000; ++i) v.push_back(i);
// 所有分配走 buffer，无堆碎片，构造完成后一次性释放
```

## 三种 pmr 分配器

| 类型 | 特点 | 场景 |
|------|------|------|
| `monotonic_buffer_resource` | 只分配不释放，析构时全部回收 | 请求生命周期内的临时数据 |
| `unsynchronized_pool_resource` | 内存池，线程不安全但快 | 单线程高频分配 |
| `synchronized_pool_resource` | 线程安全内存池 | 多线程 |

```cpp
// 内存池 + 后备分配器
std::pmr::unsynchronized_pool_resource pool;

std::pmr::vector&lt;int&gt; hotPath{&amp;pool};   // 高频路径走池
std::pmr::vector&lt;int&gt; coldPath;          // 低频路径走默认 new

hotPath.reserve(1000);                   // 池预分配
```

## 自定义 Allocator（兼容旧 STL）

```cpp
template &lt;typename T&gt;
class Mallocator {
public:
    using value_type = T;
    Mallocator() = default;
    template &lt;typename U&gt; Mallocator(const Mallocator&lt;U&gt; &amp;) {}

    T *allocate(size_t n) {
        if (n &gt; max_size()) throw std::bad_alloc();
        void *p = malloc(n * sizeof(T));  // 自己控制分配方式
        if (!p) throw std::bad_alloc();
        return static_cast&lt;T *&gt;(p);
    }
    void deallocate(T *p, size_t) { free(p); }
    size_t max_size() const { return SIZE_MAX / sizeof(T); }
};

// 使用
std::vector&lt;int, Mallocator&lt;int&gt;&gt; v;
```

## 性能对比

```
场景: 100 万次 int push_back

默认 std::allocator:      8 次 realloc, ~20ms
monotonic_buffer(栈上):    0 次 realloc, ~8ms  ← 无碎片
unsynchronized_pool:       0 次 realloc, ~10ms

省下的不是 CPU 而是堆碎片 —— 长时间运行的服务差异巨大。
```</content:encoded></item><item><title>Python 常见 Bug 合集 — 高频陷阱与修复</title><link>https://kiyose.wiki/tech/pythonbugs-python%E5%B8%B8%E8%A7%81bug%E5%90%88%E9%9B%86/</link><guid isPermaLink="true">https://kiyose.wiki/tech/pythonbugs-python%E5%B8%B8%E8%A7%81bug%E5%90%88%E9%9B%86/</guid><description>按频率和难度排序：可变默认参数、浅拷贝、GIL 阻塞、编码问题、import 循环、asyncio 常见错误等</description><pubDate>Sun, 20 Jul 2025 00:00:00 GMT</pubDate><content:encoded>## 🔴 高频 + 高难度

### 1. 可变默认参数

**频率**: ⭐⭐⭐⭐⭐ **难度**: ⭐⭐

```python
# ❌ 默认参数在函数定义时求值，所有调用共享同一对象
def append_to(item, lst=[]):
    lst.append(item)
    return lst

append_to(1)  # [1]
append_to(2)  # [1, 2] ← 不是 [2]！
append_to(3)  # [1, 2, 3]

# ✅
def append_to(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst
```

### 2. 浅拷贝陷阱

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐⭐

```python
# 列表的 * 运算复制的是引用
grid = [[0] * 3] * 3   # ❌ 三行指向同一个列表！
grid[0][0] = 1
print(grid)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

# ✅ 列表推导式
grid = [[0] * 3 for _ in range(3)]

# 字典嵌套同理
import copy
deep = copy.deepcopy(original)          # 深拷贝
shallow = original.copy()               # 浅拷贝（嵌套对象还是引用）
```

### 3. asyncio 中混用同步阻塞调用

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐⭐⭐

```python
# ❌ 在 async 函数中调用阻塞函数 → 整个事件循环冻结
async def handler():
    data = requests.get(&quot;http://api&quot;)  # requests 是同步库！
    time.sleep(1)                       # time.sleep 也是阻塞的
    return data

# ✅
import aiohttp
async def handler():
    async with aiohttp.ClientSession() as s:
        async with s.get(&quot;http://api&quot;) as resp:
            return await resp.json()   # 异步等待，不阻塞

# 必须调同步代码时 → 线程池
result = await asyncio.to_thread(sync_function, arg)
```

### 4. GIL 导致多线程无效

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐⭐

```python
# ❌ 多线程计算 → GIL 导致串行，反而更慢
import threading
def compute():
    for _ in range(10**7): pass

# ✅ CPU 密集型用 multiprocessing
from multiprocessing import Pool
with Pool(4) as p:
    results = p.map(compute, range(4))

# ✅ IO 密集型用线程（IO 时会释放 GIL）
# 或者用 asyncio 协程
```

---

## 🟡 中频 + 中难度

### 5. import 循环依赖

```python
# a.py
from b import func_b

def func_a(): return func_b()

# b.py
from a import func_a  # ❌ 循环 import → ImportError

def func_b(): return func_a()

# ✅ 延迟导入
def func_b():
    from a import func_a  # 在函数内导入
    return func_a()

# 或重构：把共同依赖提取到 c.py
```

### 6. 闭包变量绑定

```python
# ❌ 所有 lambda 捕获的是同一个变量 i
funcs = []
for i in range(5):
    funcs.append(lambda: i)  # 延迟绑定，i 最终都是 4

print([f() for f in funcs])  # [4, 4, 4, 4, 4]

# ✅ 默认参数在定义时求值
funcs = [lambda i=i: i for i in range(5)]
print([f() for f in funcs])  # [0, 1, 2, 3, 4]
```

### 7. Unicode / 编码问题

```python
# ❌ Windows 默认编码可能不是 UTF-8
with open(&quot;file.txt&quot;, &quot;r&quot;) as f:  # 默认用系统编码
    text = f.read()

# ✅ 始终显式指定编码
with open(&quot;file.txt&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    text = f.read()

# ❌ 打印中文在某些终端乱码
print(&quot;中文&quot;)  # 可能输出乱码
# 检查: sys.stdout.encoding
# 设置: PYTHONIOENCODING=utf-8

# ❌ JSON 中文被转义成 \uXXXX
json.dumps({&quot;msg&quot;: &quot;你好&quot;})  # &apos;{&quot;msg&quot;: &quot;\\u4f60\\u597d&quot;}&apos;
json.dumps({&quot;msg&quot;: &quot;你好&quot;}, ensure_ascii=False)  # &apos;{&quot;msg&quot;: &quot;你好&quot;}&apos;
```

### 8. is vs ==

```python
a = [1, 2, 3]
b = [1, 2, 3]
a == b  # True  — 值相等
a is b  # False — 不是同一对象

# ⚠️ 小整数和短字符串被 Python 缓存
x = 256; y = 256; x is y  # True（缓存）
x = 257; y = 257; x is y  # False（不缓存）

# ✅ 永远用 == 比较值，is 只用于 None/True/False
if x is None: ...
if x is not None: ...
```

---

## 🟢 低频 + 低难度

### 9. 修改遍历中的列表

```python
# ❌ 遍历中删除元素 → 跳过元素
items = [1, 2, 3, 4, 5]
for item in items:
    if item % 2 == 0:
        items.remove(item)
print(items)  # [1, 3, 5] — 看起来对但逻辑有问题（跳过元素）

# ✅ 列表推导式
items = [item for item in items if item % 2 != 0]
# 或遍历副本
for item in items[:]:  # 切片创建副本
    if item % 2 == 0:
        items.remove(item)
```

### 10. 文件未关闭

```python
# ❌ 异常时 f 可能没关闭
f = open(&quot;file.txt&quot;)
data = f.read()
f.close()

# ✅ with 语句保证关闭
with open(&quot;file.txt&quot;) as f:
    data = f.read()
# 不用手动 close，异常也能正确关闭
```

### 11. pip 依赖冲突

```bash
# ❌ 全局安装 → 项目间冲突
pip install requests
pip install flask

# ✅ 虚拟环境
python -m venv .venv
source .venv/bin/activate  # Linux/Mac
# .venv\Scripts\activate   # Windows
pip install requests flask

# 锁版本
pip freeze &gt; requirements.txt
# 团队用 requirements.txt 安装
pip install -r requirements.txt
```

### 12. Timestamp / 时区混淆

```python
# ❌ naive datetime vs aware datetime 混用 → 5 小时偏移
from datetime import datetime

naive = datetime.now()                     # 无时区
aware = datetime.now(timezone.utc)         # UTC
# naive == aware → TypeError

# ✅ 统一使用时区
from datetime import datetime, timezone, timedelta
utc = datetime.now(timezone.utc)
cst = utc.astimezone(timezone(timedelta(hours=8)))  # 北京时间
```</content:encoded></item><item><title>数据库设计 — 基础原理、常见踩坑与设计原则</title><link>https://kiyose.wiki/tech/databasedesign-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1/</link><guid isPermaLink="true">https://kiyose.wiki/tech/databasedesign-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1/</guid><description>范式与反范式、索引原理、事务隔离级别、SQLite/MySQL 踩坑记录与数据库设计清单</description><pubDate>Fri, 20 Jun 2025 00:00:00 GMT</pubDate><content:encoded>## 第一原则：范式 vs 反范式

```sql
-- 第三范式 (3NF)：消除传递依赖
-- ❌ 违反 3NF
CREATE TABLE orders (
    id INT, user_name VARCHAR(50),  -- user_name 依赖 user_id，不依赖 id
    user_city VARCHAR(50)           -- user_city 依赖 user_id，不依赖 id
);
-- ✅ 拆分
CREATE TABLE orders (id INT, user_id INT, ...);
CREATE TABLE users  (id INT, name VARCHAR(50), city VARCHAR(50));
```

| 范式 | 规则 | 效果 |
|------|------|------|
| 1NF | 列原子性 | 每列不可再分 |
| 2NF | 消除部分依赖 | 非主键列完全依赖主键 |
| 3NF | 消除传递依赖 | 非主键列不依赖其他非主键列 |

**什么时候反范式**：报表查询 JOIN 太多 → 冗余一个 `user_name` 到订单表，空间换时间。

## 第二原则：索引要懂原理

### B+ 树结构

```
         [30]
        /    \
    [10,20]  [40,50]
    /  |  \   /  |  \
  叶子: &lt;--双向链表--&gt;

查找 key=25: 根→[30]左→[10,20]右→叶子(20,30)之间→不存在
范围查询 key∈[15,35]: 定位 15→顺链表读到 35
```

### 索引失效的 6 种场景

```sql
-- 假设索引: idx(a, b, c)

-- ① 最左前缀缺失
SELECT * FROM t WHERE b = 1;          -- ❌ 没有 a，不走索引

-- ② 中间断档
SELECT * FROM t WHERE a = 1 AND c = 2; -- ⚠️ 只用 a，b 缺失后 c 失效

-- ③ 范围查询后的列失效
SELECT * FROM t WHERE a = 1 AND b &gt; 5 AND c = 2;
-- ✅ a 精确 + b 范围 → 只用 a+b，c 不走索引

-- ④ 函数/运算
SELECT * FROM t WHERE YEAR(create_time) = 2025;  -- ❌
SELECT * FROM t WHERE create_time &gt;= &apos;2025-01-01&apos;; -- ✅

-- ⑤ 隐式类型转换
SELECT * FROM t WHERE phone = 13800138000;  -- ❌ phone 是 VARCHAR

-- ⑥ LIKE 前导模糊
SELECT * FROM t WHERE name LIKE &apos;%kiyose&apos;;  -- ❌
SELECT * FROM t WHERE name LIKE &apos;kiyose%&apos;;  -- ✅
```

### EXPLAIN 速读

```sql
EXPLAIN SELECT * FROM orders WHERE user_id = 5;
-- type:   const &gt; eq_ref &gt; ref &gt; range &gt; index &gt; ALL
--             好 ←─────────────────────→ 差
-- key:    NULL = 没走索引
-- rows:   扫描行数，越小越好
-- Extra:  Using filesort = 需要额外排序（加索引）
--         Using temporary = 需要临时表（改 SQL 结构）
```

## 第三原则：事务隔离级别

| 级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|------|:--:|:--:|:--:|:--:|
| READ UNCOMMITTED | ✅ | ✅ | ✅ | 最快 |
| READ COMMITTED | ❌ | ✅ | ✅ | 快 |
| **REPEATABLE READ** (MySQL 默认) | ❌ | ❌ | ⚠️ 部分 | 中 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 最慢 |

```sql
-- MySQL 查看当前级别
SELECT @@transaction_isolation;

-- 设置
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
```

## 踩坑记录

### 坑 1：SQLite 并发写入

```
错误: SQLITE_BUSY: database is locked

原因: SQLite 同时只允许一个写者
解决:
  ① PRAGMA journal_mode=WAL      -- 读写可并发
  ② PRAGMA busy_timeout=5000      -- 等 5 秒不立即报错
  ③ 批量写入放在同一事务中
```

### 坑 2：MySQL 隐式锁升级

```sql
-- ❌ 大范围 UPDATE 导致锁表
UPDATE orders SET status = &apos;expired&apos; WHERE create_time &lt; &apos;2024-01-01&apos;;
-- 可能锁住百万行 → 其他事务阻塞 → 连接池打满

-- ✅ 分批更新
UPDATE orders SET status = &apos;expired&apos;
WHERE create_time &lt; &apos;2024-01-01&apos; LIMIT 1000;
-- 循环执行直到 affected_rows = 0
```

### 坑 3：自增 ID 耗尽

```sql
-- INT 自增上限 ≈ 21 亿，高频插入 2-3 年用完
-- ✅ 用 BIGINT
CREATE TABLE events (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    ...
);
```

### 坑 4：NULL 的比较陷阱

```sql
-- NULL 不是值，不能用 =
SELECT * FROM t WHERE col = NULL;      -- ❌ 永远返回空
SELECT * FROM t WHERE col IS NULL;     -- ✅

-- NULL 在唯一索引中
CREATE UNIQUE INDEX idx ON t(col);     -- 允许多个 NULL！
-- 因为 NULL != NULL，唯一约束对 NULL 失效
```

### 坑 5：连接池配置不对

```python
# ❌ 连接池太小 → 请求排队
pool_size = 5   # 默认值

# ❌ 连接池太大 → DB 连接数超限
pool_size = 1000

# ✅ 估算公式
pool_size = (核心数 * 2) + 有效磁盘数
# 或: 并发请求数 * 平均查询时间(秒)
# 例: 100 req/s * 0.02s = 2 → 连接池 10 就够
```

## 设计清单

```
☐ 主键选型：UUID？自增 BIGINT？雪花 ID？
☐ 索引设计：覆盖高频查询 WHERE + ORDER BY + JOIN
☐ 字符集：始终 utf8mb4（不是 utf8）
☐ 时间戳：BIGINT 毫秒 或 DATETIME(3)
☐ 软删除 vs 硬删除：is_deleted 字段 or 归档表
☐ 分页：LIMIT+OFFSET vs 游标分页 (&gt;100 万行必须游标)
☐ 备份策略：全量频率 + binlog 保留天数
☐ 监控指标：慢查询阈值、连接数、Buffer Pool 命中率
```</content:encoded></item><item><title>C++20 Ranges — 管道式数据处理</title><link>https://kiyose.wiki/notes/ranges/</link><guid isPermaLink="true">https://kiyose.wiki/notes/ranges/</guid><description>views 管道操作符、range adaptors、projection、自定义 range 与标准库算法升级</description><pubDate>Tue, 10 Jun 2025 00:00:00 GMT</pubDate><content:encoded>## 从迭代器到 Range

```cpp
// C++17 — 迭代器对，啰嗦
std::vector&lt;int&gt; v = {1, 2, 3, 4, 5, 6, 7, 8};
std::vector&lt;int&gt; even;
std::copy_if(v.begin(), v.end(),
             std::back_inserter(even),
             [](int x) { return x % 2 == 0; });
std::transform(even.begin(), even.end(), even.begin(),
               [](int x) { return x * x; });

// C++20 — 管道风格
auto result = v
    | std::views::filter([](int x) { return x % 2 == 0; })
    | std::views::transform([](int x) { return x * x; })
    | std::ranges::to&lt;std::vector&lt;int&gt;&gt;();
// {4, 16, 36, 64} — 懒求值，只在 to&lt;&gt; 时执行
```

## 核心 View 速查

| View | 效果 | 示例 |
|------|------|------|
| `filter` | 保留满足条件的 | `v \| filter([](int x){return x&gt;0;})` |
| `transform` | 映射转换 | `v \| transform([](int x){return x*2;})` |
| `take` / `drop` | 取前N / 跳过前N | `v \| take(3)` |
| `take_while` / `drop_while` | 条件取/跳 | `v \| take_while([](int x){return x&lt;10;})` |
| `reverse` | 逆序 | `v \| views::reverse` |
| `enumerate` | 带索引 | `v \| enumerate` |
| `join` | 展平嵌套 | `vv \| join` |
| `split` | 按分隔符拆分字符串 | `str \| split(&apos;\n&apos;)` |
| `iota` | 无限序列 | `views::iota(0)` |
| `common` | 统一 iterator/sentinel 类型 | `v \| common` |

## 懒求值

```cpp
// Ranges 是懒求值：管道定义操作，不立即执行
auto pipeline = views::iota(0)              // 0, 1, 2, ...
              | views::filter(isEven)       // 过滤偶数
              | views::transform(square)    // 平方
              | views::take(5);             // 取 5 个

// 只有遍历时才真正计算
for (int x : pipeline)  // 0, 4, 16, 36, 64
    std::cout &lt;&lt; x &lt;&lt; &apos; &apos;;
```

## Projection — 投影

```cpp
struct Person { std::string name; int age; };
std::vector&lt;Person&gt; people = {{&quot;Bob&quot;, 30}, {&quot;Alice&quot;, 25}};

// C++17: 需要 lambda
std::sort(people.begin(), people.end(),
    [](auto &amp;a, auto &amp;b) { return a.name &lt; b.name; });

// C++20: projection 直接指定比较成员
std::ranges::sort(people, {}, &amp;Person::name);  // 按 name 排序
std::ranges::sort(people, {}, &amp;Person::age);   // 按 age 排序

// 第二个参数 {} = 默认比较器（less）
// 第三个参数 = projection：提取要比较的字段
```

## 算法升级

```cpp
// C++20 std::ranges 版算法——直接用容器，不再需要 begin/end
std::ranges::sort(v);
std::ranges::find(v, 42);
std::ranges::copy_if(v, std::back_inserter(out), pred);

// 返回值更丰富
auto result = std::ranges::minmax(v);
// result.min, result.max — 结构化返回
```

## 自定义 Range

```cpp
// 实现一个只读的循环缓冲区 View
template &lt;typename T&gt;
class RingView : public std::ranges::view_interface&lt;RingView&lt;T&gt;&gt; {
    const T *data_;
    size_t size_, head_;
public:
    RingView(const T *d, size_t n, size_t h)
        : data_(d), size_(n), head_(h) {}

    auto begin() const { return data_ + head_; }
    auto end() const {
        return std::default_sentinel;  // C++20 sentinel
    }
    // 需要定义迭代器类型时...
};

// 更简单：用已有的 range adaptor 组合
auto top5 = v | views::reverse | views::take(5);
```

## 常见陷阱

```cpp
// 陷阱 1: View 是借用语义 — 原数据不能提前析构
auto bad() {
    std::vector&lt;int&gt; v = {1, 2, 3};
    return v | views::reverse;  // ❌ v 析构 → view 悬空
}

// 陷阱 2: filter 后的元素不再是随机访问
auto evens = v | views::filter(isEven);
// evens[0]  // ❌ filter 后的 range 不是 random_access

// 陷阱 3: view 本身不拥有数据
auto v = views::iota(0);  // 这是 OK 的 — iota 自己生成数据
auto w = someVec | views::drop(5);  // w 只引用 someVec，不拷贝
```</content:encoded></item><item><title>mmap — 内存映射与共享内存</title><link>https://kiyose.wiki/notes/mmap/</link><guid isPermaLink="true">https://kiyose.wiki/notes/mmap/</guid><description>mmap 文件映射、匿名映射、共享内存 IPC、零拷贝读写与性能场景</description><pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate><content:encoded>## 文件映射 — 零拷贝读取

```cpp
#include &lt;sys/mman.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;sys/stat.h&gt;

int fd = open(&quot;large.bin&quot;, O_RDONLY);
struct stat sb;
fstat(fd, &amp;sb);

// 文件映射到内存 → 像访问数组一样访问文件
char *data = (char *)mmap(nullptr, sb.st_size,
                          PROT_READ, MAP_PRIVATE, fd, 0);
// 访问 data[0..size-1] 就像读内存，内核负责页缺失加载
munmap(data, sb.st_size);
close(fd);
```

## mmap vs read 的性能

```
场景: 读取 1GB 文件，顺序访问前半部分

read + 用户缓冲区:  内核 → 用户空间拷贝 1 次
mmap:               内核页缓存直接映射，0 次拷贝

随机访问时 mmap 优势更大: read 每次都进内核，mmap 命中页缓存 = 纯内存访问
```

## 匿名映射 — 大块内存分配

```cpp
// 比 malloc 更适合分配 1GB+ 大块内存
size_t size = 1ULL &lt;&lt; 30;  // 1GB
void *mem = mmap(nullptr, size,
                 PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// munmap 释放 → 立即归还内核，不像 free 可能留着不还
```

## 共享内存 IPC

```cpp
// 进程 A — 创建共享内存
int fd = shm_open(&quot;/myshm&quot;, O_CREAT | O_RDWR, 0666);
ftruncate(fd, 4096);
int *data = (int *)mmap(nullptr, 4096,
                        PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
data[0] = 42;  // 写入
munmap(data, 4096);

// 进程 B — 读取
int fd = shm_open(&quot;/myshm&quot;, O_RDONLY, 0666);
int *data = (int *)mmap(nullptr, 4096,
                        PROT_READ, MAP_SHARED, fd, 0);
std::cout &lt;&lt; data[0];  // 42
munmap(data, 4096);
```

## 注意事项

```cpp
// ⚠️ mmap 不是免费的
// 页故障 (page fault) 有开销 — 第一次访问触发内核分配物理页
// SIGBUS — 文件被截断后继续访问映射区 → 信号杀死进程
// 文件大小必须固定 — mmap 后 ftruncate 扩展文件是 UB
```</content:encoded></item><item><title>C++ 常见 Bug 合集 — 高频陷阱与修复</title><link>https://kiyose.wiki/tech/cppbugs-cpp%E5%B8%B8%E8%A7%81bug%E5%90%88%E9%9B%86/</link><guid isPermaLink="true">https://kiyose.wiki/tech/cppbugs-cpp%E5%B8%B8%E8%A7%81bug%E5%90%88%E9%9B%86/</guid><description>按频率和难度排序：悬空引用、未定义行为、迭代器失效、内存泄漏、多线程数据竞争、ODR 违反等</description><pubDate>Sat, 10 May 2025 00:00:00 GMT</pubDate><content:encoded>## 🔴 高频 + 高难度

### 1. 悬空引用 / 悬空指针

**频率**: ⭐⭐⭐⭐⭐ **难度**: ⭐⭐⭐⭐

```cpp
// ① 返回局部变量的引用
const string &amp;getMessage() {
    string msg = &quot;hello&quot;;
    return msg;  // ❌ msg 已析构 → 悬空引用
}
// ✅ 返回值而非引用
string getMessage() { return &quot;hello&quot;; }

// ② 容器元素引用在 realloc 后失效
vector&lt;int&gt; v = {1, 2, 3};
int &amp;ref = v[0];
v.push_back(4);  // realloc! ref 悬空
cout &lt;&lt; ref;     // ❌ 未定义行为

// ③ Lambda 按引用捕获 + 延迟执行
function&lt;void()&gt; createCallback() {
    int x = 42;
    return [&amp;x] { cout &lt;&lt; x; };  // ❌ x 已析构
}
```

### 2. 迭代器失效

**频率**: ⭐⭐⭐⭐⭐ **难度**: ⭐⭐⭐

```cpp
// ① 插入/删除导致 rehash
vector&lt;int&gt; v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 0) v.erase(it);  // ❌ it 失效
}
// ✅
for (auto it = v.begin(); it != v.end(); ) {
    if (*it % 2 == 0) it = v.erase(it);  // erase 返回下一个有效迭代器
    else ++it;
}

// ② map/unordered_map 遍历中删除
map&lt;int, string&gt; m;
for (auto it = m.begin(); it != m.end(); ) {
    if (it-&gt;second.empty()) it = m.erase(it);
    else ++it;
}
// C++20: std::erase_if(m, [](auto &amp;p) { return p.second.empty(); });
```

### 3. 多线程数据竞争

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐⭐⭐⭐

```cpp
// ❌ 最简单的竞争
int counter = 0;
void thread1() { for (int i = 0; i &lt; 1000000; ++i) ++counter; }
void thread2() { for (int i = 0; i &lt; 1000000; ++i) ++counter; }
// counter 最终 ≠ 2000000，且每次结果不同

// ✅
atomic&lt;int&gt; counter{0};
// 或: mutex + lock_guard

// ❌ 看似安全但实际危险的 Singleton
Singleton *Singleton::get() {
    if (!instance) {          // ① 线程 A 读到 nullptr
        lock();               // ② A 加锁
        if (!instance)        // ③ DCLP — 在 C++11 前有重排风险
            instance = new Singleton;  // ④ B 可能看到半构造对象
        unlock();
    }
    return instance;
}
// ✅ C++11+: 函数内 static 天然线程安全
Singleton &amp;get() { static Singleton s; return s; }
```

---

## 🟡 中频 + 中难度

### 4. 未定义行为 (UB) 合集

```cpp
// ① 有符号整数溢出
int x = INT_MAX;
x += 1;              // ❌ UB！编译器可以假设永远不会溢出

// ② 空指针解引用
int *p = nullptr;
*p = 42;             // ❌ 程序可能不崩溃（取决于平台）

// ③ 越界访问
int arr[10];
arr[10] = 0;         // ❌ UB，可能破坏相邻变量

// ④ 使用已释放的内存 (use-after-free)
auto *p = new int(42);
delete p;
*p = 100;            // ❌ UB

// ⑤ 违反 strict aliasing
float f = 3.14f;
int i = *(int *)&amp;f;  // ❌ UB
// ✅ memcpy 或 std::bit_cast (C++20)
int i; memcpy(&amp;i, &amp;f, sizeof(i));
```

### 5. 内存泄漏

```cpp
// ① 异常导致 delete 被跳过
void unsafe() {
    auto *p = new Resource;
    mightThrow();  // 抛异常 → delete 永远不执行
    delete p;
}
// ✅ unique_ptr
void safe() {
    auto p = make_unique&lt;Resource&gt;();
    mightThrow();  // p 自动销毁
}

// ② 数组用错 delete
int *arr = new int[100];
delete arr;       // ❌ UB — 应该 delete[]

// ③ 循环引用（shared_ptr）
class A { shared_ptr&lt;B&gt; b; };
class B { shared_ptr&lt;A&gt; a; };  // 互相持有 → 永不释放
// ✅ 一方用 weak_ptr
```

### 6. ODR 违反（One Definition Rule）

```cpp
// a.cpp
int GLOBAL = 42;

// b.cpp
double GLOBAL = 3.14;  // ❌ 同名不同类型 → ODR 违反

// header.h
int counter;  // ❌ 头文件里的非 const 变量 → 每个 .cpp 一份 → 链接错误
// ✅ inline (C++17) 或 extern + .cpp 中定义
```

### 7. 虚函数默认参数陷阱

```cpp
class Base {
public:
    virtual void f(int n = 10) { cout &lt;&lt; &quot;Base: &quot; &lt;&lt; n; }
};
class Derived : public Base {
public:
    void f(int n = 20) override { cout &lt;&lt; &quot;Derived: &quot; &lt;&lt; n; }
};

Base *p = new Derived();
p-&gt;f();  // &quot;Derived: 10&quot; ← 默认参数来自 Base（静态绑定），函数体来自 Derived
```

---

## 🟢 低频 + 低难度

### 8. 模板编译不过：缺少 typename

```cpp
template &lt;typename T&gt;
void foo() {
    T::value_type x;        // ❌ 编译器不知道 T::value_type 是类型还是变量
    typename T::value_type x; // ✅
}
```

### 9. 头文件循环依赖

```cpp
// a.h 包含 b.h，b.h 包含 a.h → 无限递归
// ✅ 头文件用 #pragma once + 前置声明 + cpp 中包含
```

### 10. CMake 中忘记编译新文件

```cmake
# 新增了 parser.cpp 但没加到 CMake
add_executable(myapp main.cpp)  # ❌ 少了 parser.cpp
# → 链接时 undefined reference to Parser::parse()
```

### 11. 类型转换精度丢失

```cpp
double d = 1.999;
int i = d;               // 1 — 截断，不是四舍五入
int i = static_cast&lt;int&gt;(d + 0.5);  // 2 — 手动四舍五入

size_t n = 0;
for (int i = 0; i &lt; n - 1; ++i)  // ❌ n-1 = SIZE_MAX → 无限循环！
// ✅ for (size_t i = 0; i + 1 &lt; n; ++i)
```</content:encoded></item><item><title>pytest — Python 测试框架</title><link>https://kiyose.wiki/notes/pytest/</link><guid isPermaLink="true">https://kiyose.wiki/notes/pytest/</guid><description>pytest 基础/夹具/参数化/mock、覆盖率、插件生态与 CI 配置</description><pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate><content:encoded>## 基础用法

```python
# test_math.py
def test_addition():
    assert 1 + 1 == 2

def test_division():
    assert 10 / 2 == 5

def test_raises():
    import pytest
    with pytest.raises(ZeroDivisionError):
        1 / 0

def test_approx():
    assert 3.14 == pytest.approx(3.14159, rel=1e-2)  # 近似比较
```

```bash
pytest                          # 运行所有测试
pytest test_math.py             # 指定文件
pytest -k &quot;addition&quot;            # 按名称过滤
pytest -v                       # 详细输出
pytest -x                       # 首次失败即停止
pytest --lf                     # 只运行上次失败的
pytest --maxfail=3               # 3 个失败后停止
```

## fixture — 测试夹具

```python
import pytest

@pytest.fixture
def db():
    &quot;&quot;&quot;创建测试数据库&quot;&quot;&quot;
    conn = create_test_db()
    yield conn          # 测试用例拿到 conn
    conn.close()        # 测试后清理

def test_insert(db):
    db.execute(&quot;INSERT INTO t VALUES (1)&quot;)
    assert db.fetchone() == (1,)

# scope 控制生命周期
@pytest.fixture(scope=&quot;module&quot;)   # 整个模块共享
def expensive_resource(): ...

@pytest.fixture(scope=&quot;session&quot;)  # 整个测试会话共享
def global_config(): ...

# 自动使用（不需传参）
@pytest.fixture(autouse=True)
def reset_counter():
    counter.reset()
```

## 参数化

```python
@pytest.mark.parametrize(&quot;hex_str,expected&quot;, [
    (&quot;00&quot;, 0),
    (&quot;0A&quot;, 10),
    (&quot;FF&quot;, 255),
    (&quot;A5&quot;, 165),
])
def test_hex_to_int(hex_str, expected):
    assert hex_to_int(hex_str) == expected

# 组合参数化
@pytest.mark.parametrize(&quot;a&quot;, [1, 2])
@pytest.mark.parametrize(&quot;b&quot;, [10, 20])
def test_combine(a, b):
    ...  # 生成 4 个用例: (1,10)(1,20)(2,10)(2,20)

# 从 JSON 加载测试数据
import json
def load_cases():
    with open(&quot;testdata/cases.json&quot;) as f:
        return json.load(f)

@pytest.mark.parametrize(&quot;case&quot;, load_cases())
def test_from_json(case):
    assert process(case[&quot;input&quot;]) == case[&quot;expected&quot;]
```

## mock

```python
from unittest.mock import Mock, patch, MagicMock

# 替换函数
@patch(&quot;mymodule.requests.get&quot;)
def test_fetch(mock_get):
    mock_get.return_value.status_code = 200
    mock_get.return_value.json.return_value = {&quot;key&quot;: &quot;val&quot;}

    result = fetch_data(&quot;http://api&quot;)

    assert result == {&quot;key&quot;: &quot;val&quot;}
    mock_get.assert_called_once_with(&quot;http://api&quot;)

# 替换对象方法
def test_service():
    mock_db = Mock()
    mock_db.query.return_value = [1, 2, 3]
    svc = Service(mock_db)
    assert svc.get_ids() == [1, 2, 3]

# 验证调用
mock.assert_called_with(arg1, arg2)
mock.assert_called_once()
mock.assert_not_called()
mock.assert_any_call(arg)
```

## conftest.py — 共享配置

```python
# tests/conftest.py — 自动被同级及子目录测试加载
import pytest

@pytest.fixture(scope=&quot;session&quot;)
def app():
    &quot;&quot;&quot;创建应用实例，整个测试会话共享&quot;&quot;&quot;
    app = create_app(test_mode=True)
    yield app
    app.shutdown()

def pytest_configure(config):
    config.addinivalue_line(&quot;markers&quot;, &quot;slow: marks tests as slow&quot;)

# 命令行选项
def pytest_addoption(parser):
    parser.addoption(&quot;--api-url&quot;, default=&quot;http://localhost&quot;)
```

## 常用插件

```bash
pip install pytest-cov        # 覆盖率
pip install pytest-xdist      # 并行运行（-n auto）
pip install pytest-timeout    # 超时控制
pip install pytest-mock       # 更简洁的 mock

pytest --cov=src --cov-report=html    # 覆盖率 HTML 报告
pytest -n auto                        # 并行（CPU 核数个 worker）
pytest --timeout=10                   # 单个用例 10 秒超时
```

## pytest vs unittest

```python
# unittest 风格（旧）
import unittest
class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 1, 2)

# pytest 风格（推荐）
def test_add():
    assert 1 + 1 == 2
```

## CI 集成

```yaml
# .github/workflows/test.yml
- name: Run tests
  run: |
    pip install pytest pytest-cov
    pytest --cov=src --cov-report=xml --junitxml=report.xml

- name: Upload coverage
  uses: codecov/codecov-action@v4
```</content:encoded></item><item><title>C++20 协程 — Coroutines</title><link>https://kiyose.wiki/notes/coroutines/</link><guid isPermaLink="true">https://kiyose.wiki/notes/coroutines/</guid><description>co_await/co_yield/co_return 语法、generator 实现、task 异步模型与协程句柄</description><pubDate>Sun, 20 Apr 2025 00:00:00 GMT</pubDate><content:encoded>## 三大关键字

```cpp
co_await expr   // 暂停，等待异步操作完成
co_yield expr   // 暂停，产出一个值（generator）
co_return expr  // 结束协程，返回最终值
```

任何含这三个关键字之一的函数就是协程——返回值必须是满足协程约束的类型。

## 最简单的 Generator

```cpp
#include &lt;coroutine&gt;
#include &lt;iostream&gt;

template &lt;typename T&gt;
struct Generator {
    struct promise_type {
        T current;
        Generator get_return_object() {
            return Generator{Handle::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(T v) { current = v; return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
    using Handle = std::coroutine_handle&lt;promise_type&gt;;
    Handle h;
    explicit Generator(Handle h) : h(h) {}
    ~Generator() { if (h) h.destroy(); }
    Generator(const Generator &amp;) = delete;
    Generator(Generator &amp;&amp;o) : h(o.h) { o.h = nullptr; }

    bool next() {
        if (!h || h.done()) return false;
        h.resume();
        return !h.done();
    }
    T value() { return h.promise().current; }
};

// 使用
Generator&lt;int&gt; range(int n) {
    for (int i = 0; i &lt; n; ++i)
        co_yield i;  // 暂停，产出 i，等 next() 再恢复
}

int main() {
    auto gen = range(5);
    while (gen.next())
        std::cout &lt;&lt; gen.value() &lt;&lt; &apos; &apos;;  // 0 1 2 3 4
}
```

## 协程的生命周期

```
1. 分配协程帧（堆上，编译器可能优化到栈）
2. 调用 promise_type::get_return_object() → 返回给调用者
3. initial_suspend → 暂停还是立即执行
4. 协程体执行（co_await / co_yield / co_return）
5. final_suspend → 暂停等待销毁或自动销毁
6. promise 析构 + 协程帧释放
```

## 简单 Task（异步替代 future）

```cpp
struct Task {
    struct promise_type {
        Task get_return_object() { return Task{Handle::from_promise(*this)}; }
        std::suspend_never initial_suspend() { return {}; } // 立即开始
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
    using Handle = std::coroutine_handle&lt;promise_type&gt;;
    Handle h;
    ~Task() { if (h) h.destroy(); }

    bool done() { return h.done(); }
    void resume() { h.resume(); }
};

Task asyncWork() {
    std::cout &lt;&lt; &quot;Step 1\n&quot;;
    co_await std::suspend_always{};  // 暂停
    std::cout &lt;&lt; &quot;Step 2\n&quot;;
}

int main() {
    auto task = asyncWork();  // Step 1（initial_suspend_never 立即执行）
    std::cout &lt;&lt; &quot;Main says hi\n&quot;;
    task.resume();            // Step 2
}
// 输出: Step 1 → Main says hi → Step 2
```

## 协程的实际价值

```cpp
// ❌ 传统异步 — 回调地狱
fetchData([this](Data d) {
    processData(d, [this](Result r) {
        saveResult(r, [this]() {
            updateUI();
        });
    });
});

// ✅ 协程 — 像同步代码一样写异步逻辑
Task loadAndProcess() {
    Data d = co_await fetchData();
    Result r = co_await processData(d);
    co_await saveResult(r);
    updateUI();
}
```

## 关键概念

| 概念 | 说明 |
|------|------|
| `promise_type` | 协程内部状态，控制协程行为 |
| `coroutine_handle` | 协程的外部句柄，可以 resume/destroy |
| `initial_suspend` | 刚进协程时的行为（立即开始 vs 暂停等待） |
| `final_suspend` | 结束时的行为（自动销毁 vs 让外部手动销毁） |
| 协程帧 | 存放局部变量和 promise 的堆内存 |

## 常见陷阱

```cpp
// 陷阱 1: 协程帧在堆上分配 → 可能引发 malloc
// 编译器有时能优化（HALO — Heap Allocation eLision Optimization）
// 但不保证 → 高频调用要测试

// 陷阱 2: 引用参数可能悬空
Task bad(const std::string &amp;s) {
    co_await something();
    std::cout &lt;&lt; s;  // s 可能已析构！
}
// ✅ 按值传参或确保生命周期

// 陷阱 3: co_await 只能在协程内使用
// co_await expr 只能在 async 函数中
```</content:encoded></item><item><title>分层架构设计 — 模式、逻辑与选型</title><link>https://kiyose.wiki/tech/layeredarchitecture-%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/</link><guid isPermaLink="true">https://kiyose.wiki/tech/layeredarchitecture-%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/</guid><description>经典三层/DDD/六边形/CQRS 架构对比，分层职责边界、依赖规则与场景选型决策树</description><pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate><content:encoded>## 经典三层（使用最广）

```
┌──────────────────────────┐
│      表示层 (UI)          │  ← Controller / View
├──────────────────────────┤
│      业务逻辑层           │  ← Service / UseCase
├──────────────────────────┤
│      数据访问层           │  ← Repository / DAO
└──────────────────────────┘
依赖方向：表示层 → 业务层 → 数据层
业务层不依赖具体 UI 或数据库实现
```

```cpp
// 三层示例 — C++
class UserRepository {  // 数据层
public:
    virtual User findById(int id) = 0;
};
class SqlUserRepo : public UserRepository {
    User findById(int id) override { /* SELECT */ }
};

class UserService {     // 业务层（依赖接口，不依赖实现）
    UserRepository &amp;repo;
public:
    UserService(UserRepository &amp;r) : repo(r) {}
    User getProfile(int id) { return repo.findById(id); }
};

class UserController {  // 表示层
    UserService &amp;svc;
public:
    Json handleRequest(int id) {
        auto user = svc.getProfile(id);
        return user.toJson();
    }
};
```

### 三层选择考量

| 考量 | 三层 |
|------|:--:|
| 简单项目（&lt;10 个接口） | ✅ 刚好 |
| 业务逻辑复杂、多变 | ⚠️ Service 容易膨胀 |
| 多端 UI（Web/桌面/移动） | ✅ 复用业务层 |
| 需要替换数据库 | ✅ Repository 接口隔离 |

## DDD（领域驱动设计）

```
┌──────────────────────────────┐
│        接口层 (API/UI)       │
├──────────────────────────────┤
│        应用层 (UseCase)      │  ← 编排领域对象
├──────────────────────────────┤
│    领域层 (Entity/ValueObj)  │  ← 核心业务规则
│    ┌──────────┬──────────┐   │
│    │ 聚合根   │ 领域服务  │   │
│    │ 值对象   │ 领域事件  │   │
│    └──────────┴──────────┘   │
├──────────────────────────────┤
│    基础设施层 (DB/MQ/Cache)   │
└──────────────────────────────┘
依赖方向：全部指向领域层（领域层不依赖任何外部）
```

```cpp
// DDD 示例 — 订单领域
class Money {  // 值对象（无 ID，不可变）
    double amount;
    string currency;
public:
    Money add(const Money &amp;other) const {
        if (currency != other.currency) throw ...;
        return {amount + other.amount, currency};
    }
};

class Order {  // 实体 / 聚合根
    int id;           // 实体的标识
    Money total;
    OrderStatus status;
public:
    void addItem(const Item &amp;item) {
        total = total.add(item.price);  // 业务规则在领域内
    }
    void submit() {
        if (status != OrderStatus::Draft) throw ...;
        status = OrderStatus::Submitted;
    }
};
```

### DDD 选择考量

| 考量 | DDD |
|------|:--:|
| 业务规则复杂多变 | ✅ 业务规则归位到领域对象 |
| 团队懂业务语言 | ✅ 统一语言（Ubiquitous Language） |
| 简单 CRUD 项目 | ❌ 过度设计 |
| 团队大、需要分模块 | ✅ 限界上下文（Bounded Context） |

## 六边形架构（端口-适配器）

```
          ┌──────────────────┐
   Web ──→│                  │──→ DB
          │   业务核心        │
  REST ──→│  (无外部依赖)     │──→ MQ
          │                  │
  CLI  ──→│                  │──→ Cache
          └──────────────────┘
   ← 主端口 (输入)   次端口 (输出) →
```

```cpp
// 端口 = 接口
class OrderRepository {  // 次端口（输出端口）
public:
    virtual void save(const Order &amp;order) = 0;
};

class OrderUseCase {     // 业务核心
    OrderRepository &amp;repo;
public:
    void submitOrder(Order &amp;order) {
        order.submit();
        repo.save(order);
    }
};

// 适配器 = 接口的具体实现
class PostgresOrderRepo : public OrderRepository { /* ... */ };
class MongoOrderRepo : public OrderRepository { /* ... */ };
```

### 六边形选择考量

| 考量 | 六边形 |
|------|:--:|
| 需要替换基础设施 | ✅ 换数据库只需新适配器 |
| 多输入渠道 | ✅ Web / CLI / 消息队列统一 |
| 测试驱动 | ✅ 所有依赖可 mock |
| 小项目 | ❌ 接口数量爆炸 |

## CQRS（读写分离）

```
         ┌─────────────┐
  写请求 →   Command    → 写 Model → DB (主)
         └─────────────┘

         ┌─────────────┐
  读请求 →   Query      → 读 Model → DB (从) / ES
         └─────────────┘
```

```cpp
// 命令侧 — 只管写
class CreateOrderCommand {
    int userId;
    vector&lt;Item&gt; items;
};

class CreateOrderHandler {
    void handle(const CreateOrderCommand &amp;cmd) {
        Order order = Order::create(cmd.userId, cmd.items);
        orderRepo.save(order);
        eventBus.publish(OrderCreatedEvent{order.id()});
    }
};

// 查询侧 — 只管读（可走缓存/只读副本/ES）
class OrderQueryService {
    OrderDTO findById(int id) {
        // 可能直接查 ES / Redis / 只读从库
        return readDb.query(&quot;SELECT ...&quot;).mapTo&lt;OrderDTO&gt;();
    }
};
```

### CQRS 选择考量

| 考量 | CQRS |
|------|:--:|
| 读 &gt;&gt; 写（报表/仪表盘） | ✅ 读模型专门优化 |
| 读写模型差异大 | ✅ 互不干扰 |
| 简单 CRUD | ❌ 双倍模型，过度设计 |
| 团队小 | ❌ 维护成本高 |

## 分层决策树

```
业务逻辑复杂 + 领域专家参与？
  ├─ 是 → DDD（四层 + 限界上下文）
  └─ 否 → CRUD 为主？
            ├─ 是 → 经典三层
            └─ 否 → 需要频繁替换基础设施？
                      ├─ 是 → 六边形架构
                      └─ 否 → 读写压力差异大？
                                ├─ 是 → CQRS
                                └─ 否 → 三层（够了）
```

## 层间依赖规则（所有架构适用）

```
1. 上层依赖下层，下层不依赖上层（依赖倒置）
2. 层间通信尽量通过接口（类型擦除）
3. 同一层的组件可以互相引用
4. 禁止跨层调用（表示层绝对不能直接调 DAO）
5. 数据对象不跨层「裸奔」— DTO/Entity 转换在层边界
```</content:encoded></item><item><title>Qt 常见 Bug 合集 — 高频陷阱与修复</title><link>https://kiyose.wiki/tech/qtbugs-qt%E5%B8%B8%E8%A7%81bug%E5%90%88%E9%9B%86/</link><guid isPermaLink="true">https://kiyose.wiki/tech/qtbugs-qt%E5%B8%B8%E8%A7%81bug%E5%90%88%E9%9B%86/</guid><description>按出现频率和解决难度排序：moveToThread 陷阱、信号槽失效、内存泄漏、QML 渲染、windeployqt 缺 DLL 等</description><pubDate>Tue, 25 Mar 2025 00:00:00 GMT</pubDate><content:encoded>## 🔴 高频 + 高难度

### 1. moveToThread 后的对象归属混乱

**频率**: ⭐⭐⭐⭐⭐ **难度**: ⭐⭐⭐⭐

```cpp
// ❌ 构造时在主线程创建子对象 → 线程亲和性错误
class Worker : public QObject {
    QSerialPort *m_serial;  // 在主线程构造
public:
    Worker() { m_serial = new QSerialPort; }  // 归属主线程！
};
worker-&gt;moveToThread(thread);
// m_serial 还在主线程，通信线程里 send → &quot;Cannot send events to objects owned by a different thread&quot;

// ✅ 延迟到槽中创建
class Worker : public QObject {
    QSerialPort *m_serial = nullptr;
public slots:
    void open() { m_serial = new QSerialPort; }  // 在 worker 线程创建
};
QMetaObject::invokeMethod(worker, &quot;open&quot;, Qt::QueuedConnection);
```

### 2. 信号槽连接后不触发

**频率**: ⭐⭐⭐⭐⭐ **难度**: ⭐⭐⭐

```cpp
// 原因① 参数签名不匹配（编译期不报错）
connect(sender, SIGNAL(valueChanged(int)),    // Qt4 风格
        receiver, SLOT(onValueChanged(float))); // ❌ int ≠ float

// 原因② 忘记 Q_OBJECT 宏
class MyWidget : public QWidget {
    // Q_OBJECT  ← 少了这行！moc 不生成信号槽代码
signals: void ready();
};

// 原因③ 线程事件循环没跑
QThread thread;
Worker *w = new Worker;
w-&gt;moveToThread(&amp;thread);
thread.start();  // ← 如果 Worker 在 start() 前就调了槽，不执行

// 排查方法
QMetaObject::Connection c = connect(...);
qDebug() &lt;&lt; c;  // 验证是否连接成功
// 或: connect(..., Qt::QueuedConnection) 强制跨线程
```

### 3. QPixmap 导致的内存泄漏

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐

```cpp
// ❌ paintEvent 中每帧新建 QPixmap → 16ms 一次 malloc+free → 堆碎片
void paintEvent(QPaintEvent *) {
    QPixmap offscreen(size());  // 3.8MB * 60fps = 228MB/s 分配
}

// ✅ 成员变量复用
QPixmap m_offscreen;
void resizeEvent(QResizeEvent *e) override {
    m_offscreen = QPixmap(e-&gt;size());  // 只在尺寸变化时重建
}
void paintEvent(QPaintEvent *) override {
    QPainter p(&amp;m_offscreen);
    // ...
}
```

---

## 🟡 中频 + 中难度

### 4. SQLite &quot;database is locked&quot;

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐

```sql
-- 原因：SQLite 同时只允许一个写者
-- 场景：采集线程在写入，UI 线程同时查询 → UI 线程报 locked

-- ✅ 修复
PRAGMA journal_mode=WAL;       -- 读写可并发
PRAGMA busy_timeout=5000;      -- 获取锁前等待 5 秒

-- ✅ 代码侧：写操作放进同一事务
db.transaction();
for (auto &amp;dp : dataPoints) insert(dp);
db.commit();
```

### 5. windeployqt 后程序闪退

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐⭐

```batch
:: 原因① 缺少 MSVC 运行时
:: 解决: 静态链接 /MT 或附带 VC_redist.x64.exe

:: 原因② 缺少插件 DLL（无任何提示直接退）
set QT_DEBUG_PLUGINS=1
myapp.exe 2&gt; debug.log
:: 查看日志找出缺哪个插件

:: 原因③ windeployqt 不处理非 Qt 依赖
:: 手动检查: depends.exe 或 Dependencies
:: 常见遗漏: OpenSSL DLL, libpq.dll, mysql.dll
```

### 6. QTableView 数据不刷新

**频率**: ⭐⭐⭐ **难度**: ⭐⭐

```cpp
// ❌ 修改了底层数据但没通知 View
model-&gt;m_data.append(row);  // QTableView 不知道数据变了

// ✅ 标准流程
model-&gt;beginInsertRows(QModelIndex(), model-&gt;rowCount(), model-&gt;rowCount());
model-&gt;m_data.append(row);
model-&gt;endInsertRows();

// 或全量刷新（数据量少时）
model-&gt;beginResetModel();
model-&gt;m_data = newData;
model-&gt;endResetModel();
```

### 7. 跨线程直接操作 UI 控件

**频率**: ⭐⭐⭐⭐ **难度**: ⭐⭐

```cpp
// ❌ Worker 线程直接更新 UI → 随机崩溃
void Worker::onDataReady(const Data &amp;d) {
    m_label-&gt;setText(d.text);  // ❌ QLabel 在主线程
}

// ✅ 信号槽自动跨线程
connect(worker, &amp;Worker::dataReady,
        label, [label](const Data &amp;d) {
            label-&gt;setText(d.text);  // 自动在主线程执行
        }, Qt::QueuedConnection);
```

---

## 🟢 低频 + 低难度（但反复踩）

### 8. 界面布局错乱

```cpp
// ❌ setGeometry 硬编码坐标 → 不同 DPI/窗口大小下错乱
button-&gt;setGeometry(10, 10, 100, 30);

// ✅ 使用布局管理器
auto *layout = new QVBoxLayout(this);
layout-&gt;addWidget(button);
setLayout(layout);

// 自绘控件不写 sizeHint → 布局给 0x0 空间
QSize MyWidget::sizeHint() const override {
    return QSize(400, 300);  // 不写这个，布局不知道怎么分配空间
}
```

### 9. 中文/UTF-8 乱码

```cpp
// ❌ 字符串字面量编码依赖源文件编码
QString s = &quot;中文&quot;;  // 源文件是 GBK → 乱码

// ✅ 显式编码
QString s = QString::fromUtf8(&quot;中文&quot;);
QString s = QStringLiteral(&quot;中文&quot;);  // 编译期处理
// C++20: QString s = u8&quot;中文&quot;;

// QSS 文件也要注意编码，保存为 UTF-8
```

### 10. 项目包含路径配置错误

```cmake
# ❌ 全局 include 而不是 target
include_directories(include/)  # 所有 target 都污染

# ✅ target 级别
target_include_directories(myapp PRIVATE include/)
target_include_directories(mylib PUBLIC include/)
```

### 11. QTimer 不触发

```cpp
// ❌ 在非主线程创建 QTimer 但没事件循环
auto *timer = new QTimer;
timer-&gt;start(1000);
connect(timer, &amp;QTimer::timeout, ...);  // 没有事件循环 → 不触发

// ✅ 用 QThread + exec() 启动事件循环
// 或者在主线程创建 QTimer
```

### 12. 资源文件 .qrc 路径大小写

```xml
&lt;!-- ❌ Windows 开发 → Linux 报错 --&gt;
&lt;file&gt;icons/Save.png&lt;/file&gt;
&lt;!-- ✅ 文件名严格匹配（Linux 大小写敏感） --&gt;
&lt;file&gt;icons/save.png&lt;/file&gt;
```</content:encoded></item><item><title>Linux epoll — IO 多路复用</title><link>https://kiyose.wiki/notes/epoll/</link><guid isPermaLink="true">https://kiyose.wiki/notes/epoll/</guid><description>epoll_create/epoll_ctl/epoll_wait、边缘触发 vs 水平触发、reactor 模式、与 select/poll 对比</description><pubDate>Sat, 15 Mar 2025 00:00:00 GMT</pubDate><content:encoded>## 为什么不用 select/poll

```cpp
// select 的历史包袱
fd_set readfds;              // FD_SETSIZE=1024 硬限制
FD_ZERO(&amp;readfds);           // 每次调用都要重新设置
FD_SET(sock, &amp;readfds);      // 设置关心哪个 fd
select(maxfd+1, &amp;readfds, NULL, NULL, NULL);
// 返回后遍历所有 fd 检查 → O(n) 扫描

// epoll → O(1) 只返回就绪的 fd
```

## epoll 三步

```cpp
#include &lt;sys/epoll.h&gt;

// ① 创建
int epfd = epoll_create1(0);

// ② 注册
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;  // 读事件 + 边缘触发
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &amp;ev);

// ③ 等待
const int MAX_EVENTS = 64;
struct epoll_event events[MAX_EVENTS];
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);  // -1 = 无限等待
for (int i = 0; i &lt; n; ++i) {
    if (events[i].events &amp; EPOLLIN)
        handle_read(events[i].data.fd);
}
```

## 边缘触发 (ET) vs 水平触发 (LT)

| | LT (默认) | ET |
|------|------|------|
| 通知时机 | 只要缓冲区有数据 | 仅当新数据到达 |
| 处理要求 | 可以分次读 | 必须读到 EAGAIN |
| 适用 | 简单、可靠 | 高性能 |

```cpp
// ET 必须循环读到 EAGAIN
void handle_read(int fd) {
    char buf[4096];
    while (true) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n &gt; 0) process(buf, n);
        else if (n == 0) { close(fd); break; }  // 对端关闭
        else if (errno == EAGAIN || errno == EWOULDBLOCK) break;
        else { perror(&quot;read&quot;); break; }
    }
}
```

## epoll_data 的妙用

```cpp
// 不只存 fd，还能存指针
struct Connection {
    int fd;
    std::string read_buf;
    // ...
};

Connection *conn = new Connection{client_fd};
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = conn;  // 存指针！
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &amp;ev);

// 事件回来时直接拿到上下文
for (int i = 0; i &lt; n; ++i) {
    auto *conn = static_cast&lt;Connection *&gt;(events[i].data.ptr);
    handle(conn);  // 不需要查找 fd → conn 映射
}
```

## 对比

| | select | poll | epoll |
|------|:--:|:--:|:--:|
| 最大 fd 数 | 1024 | 无限制 | 无限制 |
| 检测方式 | 全部遍历 O(n) | 全部遍历 O(n) | 只返回就绪 O(1) |
| 注册方式 | 每次重新设置 | 每次重新设置 | 注册一次 |
| 内核实现 | 轮询 | 轮询 | 回调 + 就绪队列 |</content:encoded></item><item><title>Python 类型标注 — Type Hints</title><link>https://kiyose.wiki/notes/pythontypehints/</link><guid isPermaLink="true">https://kiyose.wiki/notes/pythontypehints/</guid><description>基础类型、泛型、Optional/Union、TypedDict、Protocol、dataclass 与 mypy 检查</description><pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate><content:encoded>## 基础标注

```python
# 变量标注
name: str = &quot;Alice&quot;
age: int = 25
scores: list[float] = [95.5, 87.0]
config: dict[str, str] = {&quot;host&quot;: &quot;localhost&quot;}

# 函数标注
def greet(name: str, times: int = 1) -&gt; str:
    return f&quot;Hello {name}&quot; * times

# 无返回值
def log(msg: str) -&gt; None: ...

# 多类型
from typing import Union
def parse(data: Union[str, bytes]) -&gt; dict: ...
# Python 3.10+ 简写: str | bytes
```

## 容器类型

```python
# list / dict / set / tuple
names: list[str] = []
scores: dict[str, float] = {}
ids: set[int] = {1, 2, 3}
point: tuple[float, float] = (1.0, 2.0)

# 可变长度 tuple
records: tuple[int, ...] = (1, 2, 3, 4)

# 嵌套
matrix: list[list[int]] = [[1, 2], [3, 4]]

# 迭代器/生成器
from typing import Iterator, Generator
def fib() -&gt; Iterator[int]: ...
def gen() -&gt; Generator[int, None, str]: ...  # yield int, 无 send, return str
```

## Optional / Union

```python
from typing import Optional

# Optional[X] = Union[X, None]
def find(id: int) -&gt; Optional[str]:  # 可能返回 None
    ...

# Python 3.10+
def find(id: int) -&gt; str | None: ...

# 多种类型
def process(val: int | str | bytes) -&gt; str: ...
```

## Any / Callable

```python
from typing import Any, Callable

# Any — 关闭类型检查
def deserialize(data: str) -&gt; Any: ...

# Callable
def apply(fn: Callable[[int, int], int], a: int, b: int) -&gt; int:
    return fn(a, b)

# 参数名标注
Callback = Callable[..., None]      # 任意参数，无返回
Handler = Callable[[str], bool]     # (str) -&gt; bool
```

## TypedDict — 字典结构

```python
from typing import TypedDict

class User(TypedDict):
    name: str
    age: int
    email: str | None  # 可选

def get_user(id: int) -&gt; User:
    return {&quot;name&quot;: &quot;Alice&quot;, &quot;age&quot;: 25, &quot;email&quot;: None}

# 部分可选
class Config(TypedDict, total=False):
    host: str
    port: int
# 所有字段都是可选的
```

## Protocol — 结构化子类型

```python
from typing import Protocol

class Drawable(Protocol):
    def draw(self) -&gt; None: ...

def render(obj: Drawable) -&gt; None:
    obj.draw()  # 不需要继承，只要有 draw 方法即可

# 鸭子类型 + 类型安全
class Circle:
    def draw(self) -&gt; None: print(&quot;○&quot;)
class Square:
    def draw(self) -&gt; None: print(&quot;□&quot;)

render(Circle())  # ✅
render(Square())  # ✅
```

## 类型别名与 NewType

```python
# 类型别名
UserId = int
JsonDict = dict[str, Any]
Vector = list[float]

# NewType — 编译期区分的类型（运行时仍是 int）
from typing import NewType
UserId = NewType(&quot;UserId&quot;, int)
OrderId = NewType(&quot;OrderId&quot;, int)

def get_user(id: UserId) -&gt; User: ...
def get_order(id: OrderId) -&gt; Order: ...

uid = UserId(1)
oid = OrderId(1)
get_user(uid)  # ✅
get_user(oid)  # ❌ mypy 报错（OrderId ≠ UserId）
```

## dataclass — 数据类

```python
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float
    label: str = &quot;&quot;          # 默认值

@dataclass
class Config:
    host: str = &quot;localhost&quot;
    port: int = 8080
    tags: list[str] = field(default_factory=list)  # 可变默认值

p1 = Point(1.0, 2.0, &quot;A&quot;)
p2 = Point(1.0, 2.0, &quot;A&quot;)
assert p1 == p2  # ✅ 自动生成 __eq__
```

## 运行时类型检查

```bash
# mypy 静态检查
pip install mypy
mypy src/

# 常用配置（pyproject.toml）
[tool.mypy]
strict = true
disallow_untyped_defs = true
ignore_missing_imports = true  # 第三方库没标注时

# pydantic — 运行时验证
from pydantic import BaseModel
class User(BaseModel):
    name: str
    age: int
User(name=&quot;Alice&quot;, age=&quot;25&quot;)  # ❌ ValidationError: age must be int
```

## 标注风格建议

```python
# ✅ 所有公共函数加类型标注
def public_api(data: list[dict]) -&gt; Result: ...

# ✅ 内部函数可省略（上下文清晰时）
def _internal_helper(x): ...

# ⚠️ 避免过度标注（类型能从上下文推断时）
name = &quot;Alice&quot;         # 不需要: name: str = &quot;Alice&quot;
scores = [1, 2, 3]     # 不需要: scores: list[int] = [1, 2, 3]

# ✅ 标注返回值（推断不出的地方）
def parse_config() -&gt; dict[str, int]: ...
```</content:encoded></item><item><title>CPU 缓存友好编程</title><link>https://kiyose.wiki/notes/cacheoptimization/</link><guid isPermaLink="true">https://kiyose.wiki/notes/cacheoptimization/</guid><description>缓存行、伪共享、数据布局优化、prefetch 与分支预测对性能的影响</description><pubDate>Sat, 15 Feb 2025 00:00:00 GMT</pubDate><content:encoded>## 缓存层级

```
L1: 32KB, ~1ns, 每核独享
L2: 256KB, ~4ns, 每核独享
L3: 8-32MB, ~12ns, 核共享
内存: 16GB+, ~100ns

缓存行 (cache line): 64 字节，原子单位
读取 1 字节 → 实际加载整行 64 字节
```

## 伪共享 (False Sharing)

```cpp
// ❌ 两个线程各自修改相邻变量 → 同一缓存行 → 互相失效
struct BadLayout {
    std::atomic&lt;int&gt; counter1;  // 偏移 0
    std::atomic&lt;int&gt; counter2;  // 偏移 4 ← 同一缓存行！
};
// counter1++ 和 counter2++ 会互相使对方的缓存行无效
// → 性能下降 10-100 倍

// ✅ 对齐到不同缓存行
struct alignas(64) GoodLayout {
    std::atomic&lt;int&gt; counter1;
    char pad1[60];              // 填充到 64 字节
};
struct alignas(64) GoodLayout2 {
    std::atomic&lt;int&gt; counter2;
    char pad2[60];
};
```

## 数据布局

```cpp
// ❌ AoS (Array of Structs) — 遍历一个字段时拖入整行无关数据
struct Particle { float x, y, z, vx, vy, vz, mass; };
Particle particles[1000000];

// 遍历所有 x — 每次读 64 字节 = 2.3 个 Particle
// → 大量带宽浪费在不需要的 y, z, vx, vy 上

// ✅ SoA (Struct of Arrays) — 每个字段连续存储
struct Particles {
    std::vector&lt;float&gt; x, y, z, vx, vy, vz, mass;
};
// 遍历所有 x — 每次读 64 字节 = 16 个 float
// → 带宽利用率高 7 倍
```

## 分支预测与 Spectre

```cpp
// 对性能敏感的循环，排序数据可以加速
std::sort(data.begin(), data.end());  // 排序后分支可预测
for (auto x : data) {
    if (x &gt; threshold)  // 连续的真/假 → 分支预测 100% 命中
        doA();
    else
        doB();
}
// 不排序: 分支预测 ~50% 命中 → 流水线刷新 → 慢 3-5 倍
```

## prefetch

```cpp
#include &lt;xmmintrin.h&gt;

// 提前告知 CPU 将要访问的地址 → 后台加载
for (int i = 0; i &lt; N; ++i) {
    _mm_prefetch((char *)&amp;data[i + 16], _MM_HINT_T0);  // 提前 16 步
    process(data[i]);
}
// 适用于链表遍历等随机访问模式
```</content:encoded></item><item><title>后端语言选型 — C++/Go/Rust/Python/Java 场景对比与模板代码</title><link>https://kiyose.wiki/tech/backendlanguages-%E5%90%8E%E7%AB%AF%E8%AF%AD%E8%A8%80%E9%80%89%E5%9E%8B/</link><guid isPermaLink="true">https://kiyose.wiki/tech/backendlanguages-%E5%90%8E%E7%AB%AF%E8%AF%AD%E8%A8%80%E9%80%89%E5%9E%8B/</guid><description>五种主流后端语言的生态、性能、并发模型对比，附 REST API / gRPC / WebSocket 模板代码</description><pubDate>Mon, 10 Feb 2025 00:00:00 GMT</pubDate><content:encoded>## 选型矩阵

| | C++ | Go | Rust | Python | Java |
|------|:--:|:--:|:--:|:--:|:--:|
| **吞吐** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| **开发效率** | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| **内存管理** | 手动/RAII | GC | 所有权 | GC | GC |
| **并发模型** | 线程+锁 | goroutine | async/tokio | asyncio/多进程 | 线程池 |
| **生态** | Qt/Boost/grpc | 标准库全 | 年轻 | Django/FastAPI | Spring |
| **适合场景** | 工业上位机/游戏后端 | 微服务/API网关 | 安全/嵌入式 | 快速原型/AI | 企业系统 |

## C++ — REST API with Drogon

```cpp
// 适合：需要极致性能 + Qt 生态融合的场景
#include &lt;drogon/drogon.h&gt;
using namespace drogon;

int main() {
    app().registerHandler(&quot;/api/data?userId={}&quot;,
        [](const HttpRequestPtr &amp;req,
           std::function&lt;void(const HttpResponsePtr &amp;)&gt; &amp;&amp;callback,
           int userId) {
            Json::Value ret;
            ret[&quot;userId&quot;] = userId;
            ret[&quot;status&quot;] = &quot;ok&quot;;
            auto resp = HttpResponse::newHttpJsonResponse(ret);
            callback(resp);
        });

    app().addListener(&quot;0.0.0.0&quot;, 8080).run();
}
// 编译: g++ -std=c++17 $(pkg-config --cflags drogon) main.cpp
```

## Go — goroutine 并发 + 标准库

```go
// 适合：高并发微服务、API 网关
package main

import (
    &quot;encoding/json&quot;
    &quot;net/http&quot;
    &quot;sync&quot;
)

type Server struct {
    mu   sync.RWMutex
    data map[int]string
}

func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    json.NewEncoder(w).Encode(s.data)
}

func main() {
    s := &amp;Server{data: map[int]string{1: &quot;active&quot;}}
    http.HandleFunc(&quot;/api/data&quot;, s.handleGet)
    http.ListenAndServe(&quot;:8080&quot;, nil) // 每个请求自动 goroutine
}
// 编译: go build -o server main.go
// 千万级并发连接开销 ~3KB/goroutine
```

## Rust — axum 异步框架

```rust
// 适合：安全关键系统、WebAssembly 后端
use axum::{Router, routing::get, extract::Path, Json};
use serde::Serialize;
use std::collections::HashMap;
use tokio::sync::RwLock;
use std::sync::Arc;

type Db = Arc&lt;RwLock&lt;HashMap&lt;i32, String&gt;&gt;&gt;;

#[derive(Serialize)]
struct Response { user_id: i32, status: String }

async fn get_user(Path(id): Path&lt;i32&gt;, db: axum::extract::State&lt;Db&gt;) -&gt; Json&lt;Response&gt; {
    let data = db.read().await;
    Json(Response {
        user_id: id,
        status: data.get(&amp;id).cloned().unwrap_or_default(),
    })
}

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(RwLock::new(HashMap::from([(1, &quot;active&quot;.into())])));
    let app = Router::new()
        .route(&quot;/api/data/{id}&quot;, get(get_user))
        .with_state(db);
    axum::Server::bind(&amp;&quot;0.0.0.0:8080&quot;.parse().unwrap())
        .serve(app.into_make_service()).await.unwrap();
}
```

## Python — FastAPI

```python
# 适合：快速原型 + AI/ML 集成
from fastapi import FastAPI
from pydantic import BaseModel
import asyncio

app = FastAPI()

class DataOut(BaseModel):
    user_id: int
    status: str

@app.get(&quot;/api/data/{user_id}&quot;, response_model=DataOut)
async def get_data(user_id: int):
    await asyncio.sleep(0.01)  # 模拟 DB 查询
    return DataOut(user_id=user_id, status=&quot;active&quot;)

# 启动: uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
# 多 worker + async 组合异步 IO
```

## Java — Spring Boot

```java
// 适合：企业级、事务密集型
@RestController
@RequestMapping(&quot;/api&quot;)
public class DataController {

    record DataOut(int userId, String status) {}

    @GetMapping(&quot;/data/{userId}&quot;)
    public DataOut getData(@PathVariable int userId) {
        return new DataOut(userId, &quot;active&quot;);
    }
}
// 启动: mvn spring-boot:run
// 嵌入式 Tomcat, 连接池自动管理
```

## 并发模型对比

```python
# Python asyncio — 单线程协作式
async def handle(request):
    data = await db.query(&quot;...&quot;)  # await 处让出 CPU
    return data

# Go goroutine — 抢占式 + channel
func handle(w http.ResponseWriter, r *http.Request) {
    ch := make(chan Result)
    go func() { ch &lt;- db.Query(&quot;...&quot;) }() // 自动调度
    result := &lt;-ch
}

# Rust tokio — 零成本异步
async fn handle(Path(id): Path&lt;i32&gt;, db: State&lt;Db&gt;) -&gt; Json&lt;...&gt; {
    let data = db.read().await;  // .await 编译为状态机
    Json(data)
}
```

## 选型决策树

```
需要和 Qt/C++ 生态无缝集成？
  ├─ 是 → C++ (Drogon / Qt HttpServer)
  └─ 否 → 需要极致安全性（内存+类型）？
            ├─ 是 → Rust
            └─ 否 → 高并发微服务 + 运维简单？
                      ├─ 是 → Go
                      └─ 否 → 快速迭代 / AI 集成？
                                ├─ 是 → Python (FastAPI)
                                └─ 否 → 企业级 + 事务 → Java (Spring)
```

## 性能边界（经验值）

| 指标 | C++ | Go | Rust | Python | Java |
|------|:--:|:--:|:--:|:--:|:--:|
| 单机 QPS（简单 JSON） | 50k+ | 30k+ | 50k+ | 5k | 25k |
| 内存占用（1k 并发） | 15MB | 25MB | 12MB | 80MB | 200MB |
| 冷启动 | &lt;1s | &lt;1s | &lt;1s | 1s | 5s |
| Docker 镜像 | 20MB | 5MB | 5MB | 100MB | 200MB |</content:encoded></item><item><title>CRTP — 静态多态与编译期继承</title><link>https://kiyose.wiki/notes/crtp/</link><guid isPermaLink="true">https://kiyose.wiki/notes/crtp/</guid><description>奇异递归模板模式：编译期多态替代虚函数、混入(Mixin)、operator== 自动生成、性能对比</description><pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate><content:encoded>## 基本形式

```cpp
template &lt;typename Derived&gt;
class Base {
public:
    void interface() {
        static_cast&lt;Derived *&gt;(this)-&gt;impl();  // 编译期绑定
    }
};

class MyClass : public Base&lt;MyClass&gt; {
public:
    void impl() { std::cout &lt;&lt; &quot;MyClass\n&quot;; }
};

MyClass obj;
obj.interface();  // 无虚函数调用，编译期确定地址
```

**核心**：基类把自己&quot;向下转型&quot;为派生类，在编译期直接调用派生类方法。零运行时开销。

## vs 虚函数的性能差异

```cpp
// 虚函数：运行时查 vtable
class Animal {
public: virtual void speak() = 0;
};
void call(Animal &amp;a) { a.speak(); }  // 间接调用

// CRTP：编译期绑定 → 可内联
template &lt;typename T&gt;
void call(Base&lt;T&gt; &amp;b) { b.interface(); }  // 编译期确定调用地址

// 基准测试（100 万次调用）
// 虚函数: ~5ms（一次间接跳转）
// CRTP:   ~0.5ms（编译器内联后可能直接消除函数调用）
```

## 混入 (Mixin) — 组合功能

```cpp
// 给任意类加 operator==
template &lt;typename Derived&gt;
class Comparable {
    friend bool operator==(const Derived &amp;a, const Derived &amp;b) {
        return a.tie() == b.tie();  // 依赖派生类的 tie()
    }
};

class Point : public Comparable&lt;Point&gt; {
    int x, y;
public:
    Point(int x, int y) : x(x), y(y) {}
    auto tie() const { return std::tie(x, y); }
};

Point p1(1, 2), p2(1, 2);
p1 == p2;  // ✅ true — Comparable 自动生成
```

## 常见应用

```cpp
// ① enable_shared_from_this 就是 CRTP
class MyClass : public std::enable_shared_from_this&lt;MyClass&gt; {};

// ② std::ranges::view_interface 也是 CRTP
template &lt;typename Derived&gt;
class view_interface { /* 提供 front/back/empty/operator[] */ };

// ③ 计数器混入
template &lt;typename Derived&gt;
class Counter {
    static inline int count = 0;
public:
    Counter() { ++count; }
    ~Counter() { --count; }
    static int instances() { return count; }
};
class MyWidget : public Counter&lt;MyWidget&gt; {};
```

## 编译期多态选择

| | 虚函数 | CRTP | Concepts |
|------|:--:|:--:|:--:|
| 运行时多态 | ✅ | ❌ | ❌ |
| 编译期内联 | ❌ | ✅ | ✅ |
| 基类指针统一存储 | ✅ | ❌ | ❌ |
| 可读性 | 高 | 中 | 高 |
| 二进制体积 | +vtable | 模板膨胀 | 模板膨胀 |

**决策**：需要运行时存 `vector&lt;Base *&gt;` → 虚函数。不需要运行时多态但想复用代码 → CRTP 或 Concepts。</content:encoded></item><item><title>软件设计全流程 — 从需求到部署</title><link>https://kiyose.wiki/tech/softwaredesign-%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1%E5%85%A8%E6%B5%81%E7%A8%8B/</link><guid isPermaLink="true">https://kiyose.wiki/tech/softwaredesign-%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1%E5%85%A8%E6%B5%81%E7%A8%8B/</guid><description>完整软件设计方法论：需求分析、系统架构、协议与接口、数据库、测试、部署运维，含架构模式选型考量</description><pubDate>Sat, 25 Jan 2025 00:00:00 GMT</pubDate><content:encoded>## 一、需求分析

### 核心产出

需求分析不是&quot;列出功能&quot;，而是**厘清约束 + 量化指标**。

```
用户故事 ──→ 功能需求 ──→ 非功能需求（性能/安全/可用性）
                    │
                    └──→ 约束条件（平台/硬件/法规/工期）
```

### 文档模板

| 章节        | 内容                           |
| --------- | ---------------------------- |
| **业务背景**  | 谁用？解决什么问题？现有方案痛点？            |
| **功能需求**  | 用例图 + 优先级（MUST/SHOULD/COULD） |
| **非功能需求** | 性能指标（吞吐/延迟）、可靠性（MTBF）、安全等级   |
| **约束**    | 操作系统、硬件接口、网络环境、合规要求          |
| **验收标准**  | 每个需求的可验证条件                   |

### PulseQt 案例

| 要素  | 内容                                    |
| --- | ------------------------------------- |
| 业务  | 工业传感器数据采集，替代现有 LabVIEW 方案             |
| 功能  | TCP/串口双通道采集 → 实时曲线 → SQLite 存储 → 历史回放 |
| 非功能 | 100Hz 数据流不丢帧、24h 连续运行、&lt;5ms 绘制延迟       |
| 约束  | Windows 10+、Qt 6.5、RS232 串口、Modbus 协议 |

### 常见陷阱

- 需求写的是方案而不是问题：`&quot;用 Redis 缓存&quot;` → 应写 `&quot;查询延迟 &lt;50ms&quot;`
- 非功能需求缺失：&quot;能跑就行&quot; → 上线后 1000 条/秒就崩
- 验收标准模糊：&quot;响应快&quot; → 改为 `&quot;P99 延迟 &lt;100ms&quot;`

---

## 二、系统架构 + 模块设计

需求分析和架构设计高度耦合，合并为**系统设计**阶段。

### 架构模式选型

| 模式            | 适用场景      | 不足           |
| ------------- | --------- | ------------ |
| **分层架构**      | 企业应用、上位机  | 层间耦合可能导致级联修改 |
| **管道-过滤器**    | 数据流处理、编译器 | 不适合交互式 UI    |
| **微内核**       | IDE、插件系统  | 性能开销         |
| **微服务**       | 多团队、独立部署  | 网络延迟、运维复杂度   |
| **CQRS/事件溯源** | 审计系统、高读写比 | 实现复杂度高       |

### PulseQt 选型：分层架构

```
┌──────────────────────────────────┐
│            表示层 (UI)            │
│  MainWindow / RealTimeChart /    │
│  QTableView / SettingsDialog     │
├──────────────────────────────────┤
│           业务逻辑层              │
│  ProtocolDecoder / DataBuffer /  │
│  DatabaseManager                 │
├──────────────────────────────────┤
│           通信适配层              │
│  TcpWorker / SerialWorker /      │
│  ChannelManager                  │
├──────────────────────────────────┤
│           硬件抽象层              │
│  QTcpSocket / QSerialPort /      │
│  QSqlDatabase                    │
└──────────────────────────────────┘

层间通信：信号槽 (QueuedConnection)
依赖方向：上层依赖下层，下层不知上层
```

### 选型考量点

**为什么选分层而不是管道-过滤器？**

| 考量             | 分层        | 管道-过滤器      |
| -------------- |:---------:|:-----------:|
| 有 UI 交互（缩放、拖拽） | ✅         | ❌ 数据单向流动    |
| 需要历史数据回放       | ✅ 任意层可查询  | ❌ 需要重新流过管道  |
| 多通道（TCP/串口）切换  | ✅ 适配层抽象   | ❌ 需要不同的管道链  |
| 调试友好           | ✅ 层间信号可拦截 | ❌ 中间过滤节点难定位 |

**为什么不是微服务？** 单机桌面应用，不需要网络分布，微服务的服务发现/负载均衡/熔断全是过度设计。

### 模块划分原则

```
高内聚 + 低耦合 + 单一职责

✅ 好的拆分：
  ProtocolDecoder  — 只管帧解析，不管数据存哪、UI 怎么画
  DatabaseManager  — 只管读写 DB，不管数据从哪来

❌ 坏的拆分：
  DataProcessor    — 既解析协议、又写数据库、又更新 UI
```

---

## 三、通信协议 + 接口设计

协议和接口本质相同——都是**组件间的契约**，合并设计。

### 协议设计三要素

| 要素       | 说明             | PulseQt 实现              |
| -------- | -------------- | ----------------------- |
| **帧格式**  | 如何定界、校验        | `A5 5A` 帧头 + 长度 + CRC16 |
| **交互模式** | 请求-响应/发布-订阅/双向 | 设备主动上报 + 上位机心跳请求        |
| **异常处理** | 超时、CRC 失败、断线重连 | 30s 心跳超时 → 自动重连         |

### 帧格式设计对比

| 方案           | 定界方式                   | 优点       | 缺点             |
| ------------ | ---------------------- | -------- | -------------- |
| **固定帧头+长度**  | `Header(2B) + Len(2B)` | 高效、易实现   | 帧头可能出现在数据中     |
| **转义字符**     | `0x7E` 帧界 + `0x7D` 转义  | 数据透明     | 带宽膨胀、实现复杂      |
| **Modbus**   | 3.5 字符间隔               | 行业标准     | 仅 ASCII/RTU 模式 |
| **JSON/文本行** | `\n` 分隔                | 人类可读、易调试 | 二进制数据需 Base64  |

**PulseQt 选固定帧头+长度**：工业场景二进制数据为主，效率优先。加 CRC16 校验，帧头冲突通过 `A5 5A` 双字节降低概率。

### API 接口设计

上位机内部同样需要接口契约：

```cpp
// 通信层对外接口（信号）
signals:
    void rawDataReceived(const QByteArray &amp;data);  // 原始字节
    void connectionStateChanged(bool connected);

// 数据层对外接口
class DataBuffer {
public:
    void push(const DataPoint &amp;dp);              // 线程安全写入
    QVector&lt;DataPoint&gt; snapshot() const;         // 线程安全快照
signals:
    void bufferUpdated(int count);
};

// 存储层对外接口
class DatabaseManager {
public:
    void insert(const DataPoint &amp;dp);            // 缓冲写入
    void flush();                                // 强制落盘
    QVector&lt;DataPoint&gt; queryRange(uint64_t from, uint64_t to);
};
```

### 接口设计原则

```
1. 契约优先：先定义接口签名，再实现
2. 最小接口：只暴露需要的，不暴露实现细节
3. 线程安全明确化：哪个线程调用？谁负责锁？
4. 错误返回明确：异常/optional/error_code，不用 -1 魔法值
5. 向后兼容：新增字段用默认值，不删旧字段
```

---

## 四、数据库设计

### 建模流程

```
实体识别 ──→ 属性定义 ──→ 关系建模 ──→ 索引优化 ──→ 读写分离策略
```

### PulseQt 数据库设计

```sql
-- 采集数据表（写入密集型）
CREATE TABLE data_points (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp  INTEGER NOT NULL,          -- 毫秒时间戳
    channel    INTEGER NOT NULL DEFAULT 0,
    value      REAL    NOT NULL,
    unit       TEXT,
    alarm      INTEGER NOT NULL DEFAULT 0 -- 0=正常 1=警告 2=异常
);

-- 写入优化索引
CREATE INDEX idx_timestamp ON data_points(timestamp);
CREATE INDEX idx_channel_ts ON data_points(channel, timestamp);

-- 配置表（读多写少，键值对模式）
CREATE TABLE config (
    key   TEXT PRIMARY KEY,
    value TEXT NOT NULL
);

-- 会话表（设备连接记录）
CREATE TABLE sessions (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    start_time INTEGER NOT NULL,
    end_time   INTEGER,
    device_id  TEXT
);
```

### 存储引擎选型

| 考量    | SQLite (PulseQt 选择) | PostgreSQL    |
| ----- |:-------------------:|:-------------:|
| 部署复杂度 | 零 — 单文件             | 需要 service    |
| 并发写入  | 单写者（WAL 缓解）         | 多写者           |
| 数据量   | &lt;1TB 胜任             | 无限            |
| 数据类型  | 弱类型                 | 强类型 + JSON/数组 |
| 适用场景  | 嵌入式、桌面单机            | 服务端、多用户       |

### 写入优化策略

```
采集场景：100Hz × 3 通道 = 每秒 300 条 INSERT

❌ 每条 INSERT 单独提交 → 300 次 fsync/s → 极慢
✅ 批量提交：缓冲区满 100 条 → 事务提交 → 3 次 fsync/s

❌ 默认 journal 模式 → 读写互斥
✅ PRAGMA journal_mode=WAL → 读写并发

❌ 永远不清理 → DB 文件无限增长
✅ 定时 DELETE + VACUUM（或按时间分区）
```

---

## 五、测试设计

### 测试金字塔

```
          ┌──────┐
          │ E2E  │  少：完整流程（串口→UI 显示）
         ┌┴──────┴┐
         │  集成   │  中：模块间信号槽通路
        ┌┴────────┴┐
        │   单元    │  多：ProtocolDecoder 状态机
       └───────────┘
```

### 各层测试策略

| 层级      | 测什么              | 工具          | PulseQt 示例              |
| ------- | ---------------- | ----------- | ----------------------- |
| **单元**  | 协议解码、CRC 计算、坐标变换 | GTest/QTest | `timeToPixelX(ts)` 边界值  |
| **集成**  | 信号槽链路、DB 读写线程安全  | QTest       | Worker→Parser→Buffer 链路 |
| **E2E** | 模拟器发帧 → 曲线显示     | 自研 mock     | Python 模拟器 100Hz 24h 挂机 |
| **性能**  | 100Hz 丢帧率、绘制帧率   | 自研          | 1h 连续采集帧计数 vs 期望        |

### 测试用例模板

```
用例 ID: TC-PROTO-001
标题: CRC 校验错误丢弃帧
前置: ProtocolDecoder 就绪
步骤:
  1. 构造合法帧 → feed(validFrame)
  2. 构造 CRC 错误帧 → feed(corruptFrame)
  3. 构造合法帧 → feed(validFrame2)
预期:
  frameDecoded 信号发射 2 次（错误帧被丢弃）
  第 1 次数据 == validFrame 载荷
  第 2 次数据 == validFrame2 载荷
```

### 测试数据管理

```cpp
// 协议测试数据：用十六进制字面量定义
const QByteArray VALID_FRAME = QByteArray::fromHex(
    &quot;A55A000C01000203E8D4C2&quot;  // 帧头+长度+类型+4字节整数+CRC
);

// 大量数据点：用循环生成
auto testData = generateDataPoints(10000,    // 数量
    DataPoint{0, 0.0},                       // 起始
    DataPoint{10000, 100.0});                 // 终止（线性插值）
```

---

## 六、部署与运维设计

部署不是最后才想的——**在设计阶段就要决策**。

### 决策清单

| 决策   | 选项                    | PulseQt         |
| ---- | --------------------- | --------------- |
| 发行方式 | MSI/Portable/AppImage | Inno Setup      |
| 自动更新 | Sparkle/自研            | 暂不需要            |
| 配置管理 | 注册表/ini/环境变量          | SQLite config 表 |
| 日志   | 文件/syslog/DB          | 文件 + 分级         |
| 监控   | 健康检查端点/看门狗            | 心跳自检            |
| 备份   | 全量/增量/实时              | 定时备份 .db        |

### 版本策略

```
v1.0.0
│  ├─ 主版本：不兼容的 API 变更
│  ├─ 次版本：向后兼容的新功能
│  └─ 修订版：向后兼容的 Bug 修复

Git 分支策略（单人项目）：
  master   — 稳定版本，可随时发布
  develop  — 开发分支，功能完成后合入 master
  feature/*— 大功能分支
```

### 日志设计

```cpp
// 分级日志
enum LogLevel { DEBUG, INFO, WARN, ERROR, FATAL };

// 格式：时间 [级别] 模块: 消息
// 2025-01-25 14:30:00 [INFO] TcpWorker: Connected to 192.168.1.100:502
// 2025-01-25 14:30:05 [WARN] Heartbeat: Missed 2, retrying...

// 文件策略：按日期滚动，保留 30 天
log_2025-01-25.log
log_2025-01-24.log
...
```

### 部署检查清单

```
☐ Qt 运行时 DLL 已打包（windeployqt）
☐ SQLite/OpenSSL 等第三方库已包含
☐ Visual C++ 运行时已附带（或静态链接 /MT）
☐ 配置文件有默认值，首次运行自动生成
☐ 安装包有管理员权限声明（如需要）
☐ 安装路径无中文/空格（防止工具链问题）
☐ 崩溃时生成 dump 文件（Win: SetUnhandledExceptionFilter）
```

---

## 设计阶段总览

```
需求分析 ──→ 架构选型 ──→ 协议/接口定义 ──→ 数据库建模
    │                                    │
    │              ┌─────────────────────┘
    │              ▼
    │         模块详细设计
    │              │
    │    ┌─────────┴─────────┐
    │    ▼                   ▼
    │  测试设计            部署设计
    │    │                   │
    └────┴───────────────────┴──→ 验收交付
```

每个阶段的选择都在**约束条件**下做权衡：性能 vs 可维护性、简单 vs 灵活、开发速度 vs 代码质量。没有标准答案，只有适合当前项目的最优解。</content:encoded></item><item><title>Qt + SQLite 数据库操作 — 实战参考</title><link>https://kiyose.wiki/tech/qtsqlite-sqlite%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C/</link><guid isPermaLink="true">https://kiyose.wiki/tech/qtsqlite-sqlite%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C/</guid><description>基于 PulseQt 实战：Qt SQL 模块核心 API、事务与批量写入、WAL 模式、BLOB 序列化与常见业务模式</description><pubDate>Fri, 17 Jan 2025 00:00:00 GMT</pubDate><content:encoded># Qt + SQLite 开发参考

&gt; 基于 PulseQt 项目 T008（DatabaseManager）实战总结。涵盖 Qt SQL 模块的核心 API、常见业务模式和踩坑记录。

---

## 一、工程配置

### CMakeLists.txt

```cmake
find_package(Qt6 REQUIRED COMPONENTS Sql)    # 必须加 Sql 模块
target_link_libraries(YourApp PRIVATE Qt6::Sql)
```

### 头文件

```cpp
#include &lt;QSqlDatabase&gt;    // 数据库连接
#include &lt;QSqlQuery&gt;       // SQL 执行
#include &lt;QSqlError&gt;       // 错误信息
#include &lt;QDataStream&gt;     // 复杂类型序列化到 BLOB
```

---

## 二、连接与初始化

### 最小启动流程

```cpp
// 1. 创建连接（命名连接，防止多实例冲突）
QSqlDatabase db = QSqlDatabase::addDatabase(&quot;QSQLITE&quot;, &quot;my_connection&quot;);
db.setDatabaseName(&quot;data.db&quot;);

// 2. 打开
if (!db.open()) {
    qCritical() &lt;&lt; db.lastError().text();
    return false;
}

// 3. 业务操作...

// 4. 关闭 + 清理
db.close();
QSqlDatabase::removeDatabase(&quot;my_connection&quot;);
```

### 命名连接 vs 默认连接

| 方式 | 代码 | 场景 |
|------|------|------|
| 命名连接 | `addDatabase(&quot;QSQLITE&quot;, &quot;name&quot;)` | **推荐**，多实例、多次 open/close 不冲突 |
| 默认连接 | `addDatabase(&quot;QSQLITE&quot;)` | 单例、一次性使用 |

记不住的话：**永远用命名连接**。

---

## 三、SQL 执行 — QSqlQuery

### 执行模式对比

```cpp
QSqlQuery query(db);

// ── 模式 1: exec() — 直接执行字符串 ──
// 适用：一次性 DDL（CREATE TABLE）、无用户输入的语句
query.exec(&quot;CREATE TABLE IF NOT EXISTS test (id INTEGER, name TEXT)&quot;);

// ── 模式 2: prepare() + addBindValue() — 预编译 ──
// 适用：有用户输入、循环执行的 DML（INSERT/UPDATE/DELETE）
query.prepare(&quot;INSERT INTO test (id, name) VALUES (?, ?)&quot;);
query.addBindValue(1);
query.addBindValue(&quot;hello&quot;);
query.exec();    // 每次改绑定值，重复 exec()

// ── SQLite 占位符语法 ──
// SQLite 用 ?  不能用 :name
// MySQL/PostgreSQL 用 ? 或 :name
// ODBC 风格用 ?  —— Qt 统一用 ?
```

### 为什么预编译更快

```
exec(&quot;INSERT ... VALUES (1, &apos;a&apos;)&quot;)  每次重新解析 SQL
exec(&quot;INSERT ... VALUES (2, &apos;b&apos;)&quot;)  每次重新解析 SQL
    ↓ 换成预编译
prepare(&quot;INSERT ... VALUES (?, ?)&quot;)
addBindValue(1); addBindValue(&quot;a&quot;); exec()  解析 1 次
addBindValue(2); addBindValue(&quot;b&quot;); exec()  复用执行计划
                     ↑ 循环 1000 次，性能差距 ~30%
```

---

## 四、遍历查询结果

```cpp
QSqlQuery query(db);
query.prepare(&quot;SELECT id, name FROM test WHERE id &gt; ?&quot;);
query.addBindValue(10);
query.exec();

while (query.next()) {                        // 逐行推进
    int    id   = query.value(0).toInt();     // 第 0 列
    QString name = query.value(1).toString(); // 第 1 列
}

// ── value() 的类型转换速查 ──
query.value(0).toInt()            // INTEGER → int
query.value(0).toLongLong()       // INTEGER → qint64
query.value(0).toULongLong()      // INTEGER → quint64（时间戳用这个）
query.value(0).toString()         // TEXT → QString
query.value(0).toByteArray()      // BLOB → QByteArray
query.value(0).toDouble()         // REAL → double
query.value(0).toBool()           // INTEGER(0/1) → bool
```

---

## 五、事务

### 为什么需要显式事务

```
SQLite 默认：每行 INSERT 都是一个隐式事务
  → INSERT 1 行 → 写磁盘 → fsync → 下一行
  → 1000 行 = 1000 次 fsync ≈ 很慢

显式事务：
  → BEGIN TRANSACTION
  → INSERT 1000 行（都在内存）
  → COMMIT（1 次 fsync）
  → 性能提升 10-50 倍
```

### 标准写法

```cpp
// 开启
if (!db.transaction()) {
    qWarning() &lt;&lt; &quot;transaction failed&quot;;
    return;
}

// 批量操作
for (auto &amp;row : rows) {
    query.addBindValue(row.value);
    if (!query.exec()) {
        db.rollback();    // 任一条失败 → 回滚全部
        return;
    }
}

// 提交
if (!db.commit()) {
    qWarning() &lt;&lt; &quot;commit failed&quot;;
    db.rollback();
}
```

---

## 六、WAL 模式

### 默认模式 vs WAL

| | 默认（DELETE） | WAL |
|------|:--:|:--:|
| 写操作 | 直接写主文件 | 追加到 .db-wal 文件 |
| 读操作 | 被写操作阻塞 | **不阻塞**（读写并发） |
| 适合场景 | 低频操作 | 高频写入 + 同时查询 |
| 文件 | `data.db` | `data.db` + `data.db-wal` + `data.db-shm` |

### 开启

```cpp
QSqlQuery query(db);
query.exec(&quot;PRAGMA journal_mode=WAL&quot;);

// 验证
query.exec(&quot;PRAGMA journal_mode&quot;);
if (query.next())
    qInfo() &lt;&lt; query.value(0).toString();   // 输出 &quot;wal&quot;
```

### 注意事项

- WAL 模式下 `.db-wal` 文件会持续增长，需定期 `PRAGMA wal_checkpoint` 合并回主文件
- 数据库正常关闭时自动 checkpoint
- 网络文件系统（NFS）可能不支持 WAL，会静默降级

---

## 七、BLOB — 复杂类型的存储

QDataStream 可将任意 Qt 基础类型 + 容器序列化到 `QByteArray`，存入 BLOB 列。

```cpp
// ── 序列化：QVector&lt;double&gt; → QByteArray ──
QByteArray serialize(const QVector&lt;double&gt; &amp;v) {
    QByteArray data;
    QDataStream stream(&amp;data, QIODevice::WriteOnly);
    stream.setVersion(QDataStream::Qt_6_0);   // 锁定版本
    stream &lt;&lt; v;
    return data;
}

// ── 反序列化：QByteArray → QVector&lt;double&gt; ──
QVector&lt;double&gt; deserialize(const QByteArray &amp;data) {
    QVector&lt;double&gt; v;
    QDataStream stream(data);
    stream.setVersion(QDataStream::Qt_6_0);
    stream &gt;&gt; v;
    return v;
}

// ── 写入数据库 ──
query.prepare(&quot;INSERT INTO t (data) VALUES (?)&quot;);
query.addBindValue(serialize(myVector));
query.exec();

// ── 从数据库读出 ──
QVector&lt;double&gt; v = deserialize(query.value(0).toByteArray());
```

### 为什么 `setVersion()` 很重要

不设版本 = Qt 升级可能改变序列化格式 → 旧数据读不出来。`Qt_6_0` 锁死后兼容 Qt 6.x 全系列。

---

## 八、常见业务模式

### 模式 1：采集存储（PulseQt 的做法）

```cpp
class DatabaseManager {
    QVector&lt;DataPoint&gt; m_pending;  // 缓冲区

    void insert(const DataPoint &amp;dp) {
        m_pending.append(dp);
        if (m_pending.size() &gt;= 100)   // 满 100 条 → 批量提交
            commitBatch();
    }

    void flush() {
        if (!m_pending.isEmpty())      // 析构/手动调用 → 提交剩余
            commitBatch();
    }

    void commitBatch() {
        db.transaction();
        for (auto &amp;dp : m_pending) { /* INSERT */ }
        db.commit();
        m_pending.clear();
    }
};
```

### 模式 2：配置存储（键值对）

```sql
CREATE TABLE config (key TEXT PRIMARY KEY, value TEXT);
```

```cpp
QString readConfig(const QString &amp;key, const QString &amp;defaultVal) {
    QSqlQuery q(db);
    q.prepare(&quot;SELECT value FROM config WHERE key = ?&quot;);
    q.addBindValue(key);
    q.exec();
    return q.next() ? q.value(0).toString() : defaultVal;
}

void writeConfig(const QString &amp;key, const QString &amp;value) {
    QSqlQuery q(db);
    q.prepare(&quot;INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)&quot;);
    q.addBindValue(key); q.addBindValue(value);
    q.exec();
}
```

### 模式 3：分页查询

```cpp
// 第 page 页，每页 pageSize 条（page 从 1 开始）
QVector&lt;DataPoint&gt; queryPage(int page, int pageSize = 100) {
    QSqlQuery q(db);
    q.prepare(&quot;SELECT * FROM data_points ORDER BY timestamp DESC LIMIT ? OFFSET ?&quot;);
    q.addBindValue(pageSize);
    q.addBindValue((page - 1) * pageSize);
    q.exec();
    // ...遍历...
}
```

### 模式 4：自动清理旧数据

```cpp
int cleanup(int retentionDays = 7) {
    QSqlQuery q(db);
    uint64_t cutoff = QDateTime::currentMSecsSinceEpoch()
                      - retentionDays * 86400000ULL;
    q.prepare(&quot;DELETE FROM data_points WHERE timestamp &lt; ?&quot;);
    q.addBindValue(cutoff);
    q.exec();
    return q.numRowsAffected();   // 返回删除行数
}
```

---

## 九、踩坑记录

### 🐛 坑 1：忘记 `removeDatabase()` 导致警告

```
QSqlDatabasePrivate::removeDatabase: connection &apos;xxx&apos; is still in use
```

**原因**：析构时没调 `QSqlDatabase::removeDatabase()`，或调的时候还有活跃查询。

**修复**：

```cpp
~DatabaseManager() {
    flush();              // 先提交残留数据
    m_db.close();         // 再关闭连接
    QSqlDatabase::removeDatabase(m_connectionName);  // 最后移除
}
```

### 🐛 坑 2：多实例变量 `static` 计数器

```cpp
// ❌ 错误 — 所有实例共用同一个连接名
m_connectionName = &quot;pulseqt_db&quot;;

// ✅ 正确 — 每次构造生成唯一名
static int counter = 0;
m_connectionName = QString(&quot;pulseqt_db_%1&quot;).arg(++counter);
```

### 🐛 坑 3：`QDataStream` 版本不一致

```cpp
// 写入时 Qt 6.2，读出时 Qt 6.5 → 可能不兼容
// 解决：两端都 setVersion(QDataStream::Qt_6_0)
```

### 🐛 坑 4：BLOB 字段为 NULL

```cpp
// SELECT 结果中 BLOB 列可能是 NULL（从未写入过）
QByteArray blob = query.value(0).toByteArray();
if (blob.isEmpty()) return {};  // 安全处理
```

### 🐛 坑 5：事务中忘了 rollback

```cpp
// ❌ — INSERT 失败后不清空缓冲，下次 commitBatch 继续失败
if (!query.exec()) {
    m_db.rollback();       // 必须 rollback
    m_pending.clear();     // 必须清空缓冲
    return;
}
```

---

## 十、API 速查表

| 操作 | 代码 |
|------|------|
| 创建连接 | `QSqlDatabase::addDatabase(&quot;QSQLITE&quot;, &quot;name&quot;)` |
| 打开 | `db.setDatabaseName(path); db.open()` |
| 关闭 | `db.close()` |
| 移除连接 | `QSqlDatabase::removeDatabase(&quot;name&quot;)` |
| 执行 SQL | `QSqlQuery q(db); q.exec(&quot;SQL&quot;)` |
| 预编译 | `q.prepare(&quot;INSERT ... VALUES (?, ?)&quot;)` |
| 绑定值 | `q.addBindValue(val)` |
| 遍历结果 | `while (q.next()) { q.value(0).toXxx() }` |
| 事务开始 | `db.transaction()` |
| 提交 | `db.commit()` |
| 回滚 | `db.rollback()` |
| 错误信息 | `db.lastError().text()` |
| 影响行数 | `q.numRowsAffected()` |
| WAL | `q.exec(&quot;PRAGMA journal_mode=WAL&quot;)` |
| 序列化 | `QDataStream(&amp;byteArray, WriteOnly) &lt;&lt; data` |
| 反序列化 | `QDataStream(byteArray) &gt;&gt; data` |
| 锁版本 | `stream.setVersion(QDataStream::Qt_6_0)` |
| 毫秒时间戳 | `QDateTime::currentMSecsSinceEpoch()` |</content:encoded></item><item><title>信号与数据流设计 — PulseQt 架构解析</title><link>https://kiyose.wiki/tech/signaldataflow-%E4%BF%A1%E5%8F%B7%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%B5%81%E8%AE%BE%E8%AE%A1/</link><guid isPermaLink="true">https://kiyose.wiki/tech/signaldataflow-%E4%BF%A1%E5%8F%B7%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%B5%81%E8%AE%BE%E8%AE%A1/</guid><description>从传感器到屏幕：Qt 信号槽数据全链路、QueuedConnection 死锁预防、DataBuffer 线程安全设计与数据副本模式</description><pubDate>Thu, 16 Jan 2025 00:00:00 GMT</pubDate><content:encoded># PulseQt 数据流与信号传递 — 设计原理笔记

&gt; T009（DataTableModel + QTableView）完成后复盘。解释从传感器到屏幕的数据如何一步步流转。

---

## 一、数据全链路（端到端）

```
┌──────────┐   TCP    ┌─────────────┐  readyRead ┌─────────────┐  feed(bytes) ┌────────────────┐
│ Python    │ ──────→ │ TcpChannel  │ ─────────→ │ ChannelMgr  │ ────────────→ │ ProtocolDecoder │
│ 模拟器    │  数据帧  │ (T003)      │  信号转发   │ (T004)      │  信号转发      │ (T005)          │
└──────────┘         └─────────────┘            └─────────────┘               └───────┬────────┘
                                                                                       │
                                                                              frameDecoded(Frame)
                                                                                       │
                                                                                       ▼
                                                                              ┌────────────────┐
                                                                              │  main.cpp      │
                                                                              │  lambda 解析    │
                                                                              │  Frame→DataPoint│
                                                                              └───────┬────────┘
                                                                                      │ push()
                                                                                      ▼
                                                                              ┌────────────────┐
                                                                              │  DataBuffer    │
                                                                              │  (T007)        │
                                                                              │  环形缓冲       │
                                                                              └───┬───────┬────┘
                                                                      bufferUpdated │      │ snapshot()
                                                                                    │      │
                                                                                    ▼      ▼
                                                                              ┌────────────────┐
                                                                              │ DataTableModel │
                                                                              │ (T009)         │
                                                                              └───────┬────────┘
                                                                              dataRefreshed
                                                                                      │
                                                                                      ▼
                                                                              ┌────────────────┐
                                                                              │  MainWindow    │
                                                                              │  scrollToBottom│
                                                                              └────────────────┘
```

---

## 二、三种信号角色的区别

| 信号                      | 来源              | 目的地             | 携带数据 | 设计意图                       |
| ----------------------- | --------------- | --------------- | ---- | -------------------------- |
| `readyRead(QByteArray)` | ChannelManager  | ProtocolDecoder | 原始字节 | **传输层**：字节流，不知道含义          |
| `frameDecoded(Frame)`   | ProtocolDecoder | 外部 lambda       | 完整帧  | **协议层**：结构化数据，type+payload |
| `bufferUpdated(int)`    | DataBuffer      | DataTableModel  | 数量通知 | **数据层**：告诉观察者&quot;有新数据&quot;，不传数据体  |
| `dataRefreshed()`       | DataTableModel  | MainWindow      | 无数据  | **UI 层**：告诉视图&quot;刷新完了，该滚动&quot;    |

**核心设计决策**：`bufferUpdated` 只传 `count`（新增条数），不传数据体。

```
❌ 如果传数据：
  signal bufferUpdated(QVector&lt;DataPoint&gt; data);
  → 信号连接时参数被拷贝一次
  → 100Hz × 10000 条 × 每次一个 QVector = 每秒 100 万次对象拷贝
  → UI 线程卡死

✅ 只传通知：
  signal bufferUpdated(int count);
  → 不拷贝数据
  → UI 线程自己调 snapshot() 拿一次
  → 控制在自己手里
```

---

## 三、Qt::QueuedConnection 为什么在这里是必须的

```cpp
void DataBuffer::push(const DataPoint &amp;point)
{
    QMutexLocker locker(&amp;m_mutex);   // 🔒
    m_ring[m_head] = point;
    // ...
    emit bufferUpdated(1);           // 信号在此发射
}                                    // 🔓 锁释放

void DataTableModel::onBufferUpdated(int count)
{
    auto snap = m_buffer-&gt;snapshot();  // 内部 QMutexLocker → 🔒
}
```

同线程下 `AutoConnection` = `DirectConnection`，`onBufferUpdated` 在 `emit` 点**当场执行**。此时 `push()` 还没释放锁 → `snapshot()` 再锁 → 同一个线程同一把 `QMutex` 锁两次 → **死锁**。

```cpp
// 修复：强制走事件队列
connect(buffer, &amp;DataBuffer::bufferUpdated,
        model, &amp;DataTableModel::onBufferUpdated,
        Qt::QueuedConnection);
//               ↑ push() 返回后，事件循环再调 onBufferUpdated
```

---

## 四、数据副本模式 — 为什么 snapshot() 不返回引用

```cpp
// ❌ 返回引用
const QVector&lt;DataPoint&gt;&amp; snapshot() {
    QMutexLocker lock(&amp;m_mutex);
    return m_data;   // 返回引用 → 锁释放 → 引用悬空！
}

// ✅ 返回副本
QVector&lt;DataPoint&gt; snapshot() {
    QMutexLocker lock(&amp;m_mutex);
    QVector&lt;DataPoint&gt; copy = m_data;   // 锁内拷贝
    return copy;                         // 锁释放，返回独立副本
}
```

核心原则：**持有锁的时间内只做最少的事**——拷贝一份然后释放锁。调用方拿到返回后不再依赖原始数据，不需要考虑锁。

---

## 五、DataTableModel 的设计模式：视图/模型分离

```
┌──────────────┐      ┌─────────────────┐      ┌──────────┐
│  DataBuffer  │ ───→ │ DataTableModel  │ ───→ │QTableView│
│  (真实数据)   │      │ (数据的抽象视图)  │      │ (渲染)    │
└──────────────┘      └─────────────────┘      └──────────┘
     数据源               问答协议                    画像素

QTableView 不直接碰 DataBuffer，只通过 4 个约定好的&quot;问题&quot;和模型对话：
  - &quot;你有几行？&quot;         → rowCount()
  - &quot;你有几列？&quot;         → columnCount()
  - &quot;第 (r,c) 格显示啥？&quot; → data(r, c, DisplayRole)
  - &quot;第 c 列标题是啥？&quot;   → headerData(c)
```

带来的好处：

| 场景       | 好处                                |
| -------- | --------------------------------- |
| 数据源换成数据库 | 只改 DataTableModel，QTableView 不动   |
| 换 UI 框架  | 只改 QTableView，DataTableModel 可以复用 |
| 单元测试     | 模型可以用 mock 数据源测，不需要真实 QTableView  |

---

## 六、T009 踩到的坑

| #   | 坑                        | 原因                                           | 教训                        |
|:---:| ------------------------ | -------------------------------------------- | ------------------------- |
| 1   | `rowConut` vs `rowCount` | 拼写 → 不是 override → 编译器报&quot;Only virtual member&quot; | 纯虚函数签名一个字都不能错             |
| 2   | 构造函数 LNK2019             | 头文件声明了但 cpp 没写实现                             | 声明和定义要一起检查                |
| 3   | 表格无滚动                    | 忘了 `scrollToBottom()`                        | 数据流通了 ≠ UI 交互通了           |
| 4   | `QueuedConnection` 差点忘   | 不用会死锁                                        | DataBuffer 有锁 → 信号槽必须队列连接 |

---

## 七、一条信号走过的完整路径（时间线）

```
T=0ms    模拟器发送二进制帧
T=0ms    QTcpSocket::readyRead
T=0ms    TcpChannel 读取字节 → emit readyRead(data)
T=0ms    ChannelManager::onReadyRead → emit readyRead(data)
T=0ms    ProtocolDecoder::feed(data)
T=0ms    状态机逐字节解析 → CRC 通过 → emit frameDecoded(frame)
T=0ms    lambda: Frame → DataPoint → DataBuffer::push()
T=0ms      push 持锁 → 写入环形数组 → emit bufferUpdated(1) → push 返回
T=0ms      锁释放
T=1ms    事件循环: onBufferUpdated → snapshot() → beginResetModel/endResetModel
T=2ms    QTableView 调用 rowCount/columnCount/data/headerData 重绘
T=2ms    emit dataRefreshed → scrollToBottom()
T=10ms   下一帧到达，循环
```

全程单线程（目前），每帧处理时间 &lt; 2ms，100Hz 游刃有余。</content:encoded></item><item><title>Python 标准库速查 — os / sys / pathlib / subprocess / json / csv</title><link>https://kiyose.wiki/notes/pythonstdlib/</link><guid isPermaLink="true">https://kiyose.wiki/notes/pythonstdlib/</guid><description>日常开发高频 Python 标准库模块速查：路径处理、子进程、JSON/CSV、文件与系统操作</description><pubDate>Wed, 15 Jan 2025 00:00:00 GMT</pubDate><content:encoded>## pathlib — 现代路径处理

```python
from pathlib import Path

# 路径构建
p = Path.home() / &quot;projects&quot; / &quot;myapp&quot; / &quot;config.json&quot;
# / 运算符自动处理分隔符

# 属性
p.name          # &quot;config.json&quot;
p.stem          # &quot;config&quot;（无后缀）
p.suffix        # &quot;.json&quot;
p.parent        # Path(&quot;/home/user/projects/myapp&quot;)
p.parts         # (&apos;/&apos;, &apos;home&apos;, &apos;user&apos;, &apos;projects&apos;, &apos;myapp&apos;, &apos;config.json&apos;)

# 操作
p.exists()                     # 是否存在
p.is_file() / p.is_dir()       # 类型判断
p.mkdir(parents=True)          # 递归创建目录
text = p.read_text()           # 读取文本
p.write_text(&quot;content&quot;)        # 写入
data = p.read_bytes()          # 读取二进制

# 遍历
for f in Path(&quot;src&quot;).rglob(&quot;*.cpp&quot;):  # 递归 glob
    print(f)

# 相对路径
Path(&quot;a/b/c&quot;).relative_to(&quot;a&quot;)  # Path(&quot;b/c&quot;)
```

## os / sys / shutil

```python
import os, sys, shutil

# os
os.getcwd()                    # 当前目录
os.chdir(&quot;/tmp&quot;)               # 切换目录
os.environ[&quot;HOME&quot;]             # 环境变量
os.path.join(&quot;a&quot;, &quot;b&quot;)         # 路径拼接（pathlib 更好）
os.path.exists(&quot;file.txt&quot;)     # 是否存在

# sys
sys.argv                       # 命令行参数
sys.exit(0)                    # 退出
sys.platform                   # &quot;linux&quot; / &quot;darwin&quot; / &quot;win32&quot;

# shutil
shutil.copy(&quot;a.txt&quot;, &quot;b.txt&quot;)  # 复制
shutil.move(&quot;a.txt&quot;, &quot;/tmp/&quot;)  # 移动
shutil.rmtree(&quot;/tmp/build&quot;)    # 递归删除
shutil.make_archive(&quot;dist&quot;, &quot;zip&quot;, &quot;build/&quot;)  # 打包
```

## subprocess — 执行命令

```python
import subprocess

# 运行 + 捕获输出（推荐）
result = subprocess.run(
    [&quot;git&quot;, &quot;status&quot;, &quot;--short&quot;],
    capture_output=True,
    text=True,          # 返回 str 而非 bytes
    timeout=10,
    check=True          # 非零退出码抛异常
)
print(result.stdout)

# 管道
p1 = subprocess.Popen([&quot;ls&quot;], stdout=subprocess.PIPE)
p2 = subprocess.Popen([&quot;grep&quot;, &quot;.cpp&quot;], stdin=p1.stdout,
                       stdout=subprocess.PIPE, text=True)
output, _ = p2.communicate()

# 快捷版（仅获取输出不检查退出码）
out = subprocess.check_output([&quot;echo&quot;, &quot;hello&quot;], text=True)
```

## json

```python
import json

# 读取
with open(&quot;config.json&quot;) as f:
    config = json.load(f)

# 写入
with open(&quot;output.json&quot;, &quot;w&quot;) as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

# 字符串 ↔ 对象
s = json.dumps({&quot;key&quot;: &quot;值&quot;}, indent=2)   # 对象 → 字符串
obj = json.loads(s)                         # 字符串 → 对象

# 自定义序列化
class Point:
    def __init__(self, x, y): self.x, self.y = x, y

def point_encoder(obj):
    if isinstance(obj, Point):
        return {&quot;x&quot;: obj.x, &quot;y&quot;: obj.y}
    raise TypeError

json.dumps(Point(1, 2), default=point_encoder)
```

## csv

```python
import csv

# 读取
with open(&quot;data.csv&quot;) as f:
    reader = csv.reader(f)
    header = next(reader)        # 跳过表头
    for row in reader:
        print(row[0], row[1])

# 字典读取
with open(&quot;data.csv&quot;) as f:
    for row in csv.DictReader(f):
        print(row[&quot;Name&quot;], row[&quot;Age&quot;])

# 写入
with open(&quot;out.csv&quot;, &quot;w&quot;, newline=&quot;&quot;) as f:
    writer = csv.writer(f)
    writer.writerow([&quot;Name&quot;, &quot;Age&quot;])       # 表头
    writer.writerows([(&quot;Alice&quot;, 25), (&quot;Bob&quot;, 30)])

# 字典写入
with open(&quot;out.csv&quot;, &quot;w&quot;, newline=&quot;&quot;) as f:
    writer = csv.DictWriter(f, fieldnames=[&quot;Name&quot;, &quot;Age&quot;])
    writer.writeheader()
    writer.writerow({&quot;Name&quot;: &quot;Alice&quot;, &quot;Age&quot;: 25})
```

## datetime

```python
from datetime import datetime, timedelta

now = datetime.now()
ts = now.timestamp()                         # Unix 时间戳

# 格式化
now.strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)            # &quot;2025-01-15 14:30:00&quot;
datetime.strptime(&quot;2025-01-15&quot;, &quot;%Y-%m-%d&quot;)  # 字符串 → datetime

# 运算
tomorrow = now + timedelta(days=1)
diff = tomorrow - now  # timedelta
```

## collections

```python
from collections import defaultdict, Counter, deque

# defaultdict — 自动默认值
d = defaultdict(list)
d[&quot;key&quot;].append(1)       # 不需要先 d[&quot;key&quot;] = []

# Counter — 计数
c = Counter(&quot;abracadabra&quot;)
c.most_common(3)          # [(&apos;a&apos;, 5), (&apos;b&apos;, 2), (&apos;r&apos;, 2)]

# deque — 双端队列
q = deque(maxlen=100)     # 自动丢弃旧元素
q.append(1); q.appendleft(0)
```

## itertools

```python
from itertools import chain, groupby, product, combinations

list(chain([1,2], [3,4]))                     # [1,2,3,4]
list(combinations(&quot;ABC&quot;, 2))                  # [(&apos;A&apos;,&apos;B&apos;),(&apos;A&apos;,&apos;C&apos;),(&apos;B&apos;,&apos;C&apos;)]
list(product(&quot;AB&quot;, &quot;12&quot;))                     # [(&apos;A&apos;,&apos;1&apos;),(&apos;A&apos;,&apos;2&apos;),(&apos;B&apos;,&apos;1&apos;),(&apos;B&apos;,&apos;2&apos;)]
```</content:encoded></item><item><title>心跳机制 — 原理、实现与面试考点</title><link>https://kiyose.wiki/tech/heartbeat-%E5%BF%83%E8%B7%B3%E6%9C%BA%E5%88%B6%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E7%8E%B0/</link><guid isPermaLink="true">https://kiyose.wiki/tech/heartbeat-%E5%BF%83%E8%B7%B3%E6%9C%BA%E5%88%B6%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E7%8E%B0/</guid><description>基于 PulseQt 实战：TCP 长连接心跳协议设计、Ping-Pong 模式实现、参数调优与面试常考问题</description><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><content:encoded># 心跳机制 — 原理、实现与面试考点

&gt; 基于 PulseQt T018 实战，覆盖协议设计、代码实现、跨场景应用、面试八股。

---

## 一、为什么需要心跳

TCP 连接的&quot;假活&quot;问题：

```
客户端 ──── TCP 连接 ──── 服务端
            ↑
    双方都认为连接还在，但中间网络已经断了

原因：TCP 没有应用层数据时，keepalive 默认 2 小时才探测一次
结果：断线后 2 小时内双方都感知不到 → 数据丢失、资源泄漏
```

心跳 = 应用层定期发空包，&quot;我还活着，你还活着吗？&quot;

---

## 二、PulseQt 心跳实现

### 协议帧定义

```
心跳请求帧（上位机→下位机）:
  A5 5A 00 02 [CRC16]
  头   长=0 类型

心跳应答帧（下位机→上位机）:
  A5 5A 00 03 [CRC16]
  头   长=0 类型
```

### 核心代码

**定时检查（ParseWorker::onHeartbeatCheck）**：

```cpp
void ParseWorker::onHeartbeatCheck()
{
    qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - m_lastDataTime;

    // 5s 无数据 → 发心跳
    if (elapsed &gt;= 5000) {
        emit writeData(buildFrame(Frame::TYPE_HEARTBEAT));
        m_heartbeatMissed++;
    }

    // 连续 6 次无应答 (30s) → 超时
    if (m_heartbeatMissed &gt;= 6) {
        m_heartbeatTimer-&gt;stop();
        qWarning() &lt;&lt; &quot;Heartbeat timeout (30s)&quot;;
    }
}
```

**收到数据时重置**：

```cpp
void ParseWorker::onRawDataReceived(const QByteArray &amp;data)
{
    m_lastDataTime = QDateTime::currentMSecsSinceEpoch();  // 任何数据都算&quot;活动&quot;
    m_heartbeatMissed = 0;
    m_decoder.feed(data);
}
```

**收到心跳应答时重置计数器**：

```cpp
// frameDecoded lambda 中
if (frame.type == Frame::TYPE_ACK) {
    m_heartbeatMissed = 0;
    return;
}
```

**收到对方心跳时回应**：

```cpp
if (frame.type == Frame::TYPE_HEARTBEAT) {
    emit writeData(buildFrame(Frame::TYPE_ACK));
    return;
}
```

### 状态机

```
        收到任意数据
  ┌─────────────────────────────┐
  │                             │
  ▼                             │
 [正常] ──5s无数据──→ [发心跳] ──→ missed++
  │                    │
  │        收到ACK      │ missed &lt; 6
  └──────────────────────┘
  │
  │ missed &gt;= 6 (30s)
  ▼
 [超时] → 停止心跳 → 上层断线重连
```

---

## 三、三种常见心跳模式

### 模式 1：Ping-Pong（PulseQt 采用）

```
A ── Ping ──→ B
A ←─ Pong ── B
```

| 优点                     | 缺点          |
| ---------------------- | ----------- |
| 双向确认，A 知道自己发出的探测 B 收到了 | 需要 B 配合实现应答 |

**适用**：自定义协议、物联网设备、长连接服务器。

### 模式 2：单向心跳

```
A ── Ping ──→ B  （B 不回应）
A 只管发，B 只管收。B 端有超时检测。
```

| 优点          | 缺点            |
| ----------- | ------------- |
| B 不需要实现应答逻辑 | A 不知道 B 是否还活着 |

**适用**：单向数据上报（传感器→网关）、卫星遥测。

### 模式 3：TCP Keepalive（操作系统级）

```cpp
int keepalive = 1;
int idle = 60;      // 60s 无数据开始探测
int interval = 10;  // 每 10s 探测一次
int count = 3;      // 3 次失败判定断开
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &amp;keepalive, sizeof(keepalive));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &amp;idle, sizeof(idle));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &amp;interval, sizeof(interval));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &amp;count, sizeof(count));
```

| 优点     | 缺点                |
| ------ | ----------------- |
| 零应用层代码 | 全局生效，不能按连接定制      |
| 操作系统处理 | 默认 2 小时，改配置影响所有连接 |

---

## 四、设计心跳的三个关键参数

| 参数   | PulseQt 值     | 选择理由                   |
| ---- |:-------------:| ---------------------- |
| 心跳间隔 | 5s            | 100Hz 数据流下 5s 已经是&quot;空闲&quot;了 |
| 超时阈值 | 30s（6×5s）     | 网络抖动 2-3 个包正常，6 个确认断线  |
| 重置条件 | **收到任何数据**都重置 | 心跳目的是检测&quot;对方还活着&quot;，有数据就是活着 |

### 常见参数经验值

| 场景        | 心跳间隔    | 超时    | 说明              |
| --------- |:-------:|:-----:| --------------- |
| 物联网设备     | 30s-60s | 3-5 次 | 省电，不频繁唤醒        |
| WebSocket | 30s     | 3 次   | Nginx 默认 60s 超时 |
| 游戏长连接     | 5s-10s  | 2-3 次 | 实时性要求高          |
| 金融行情      | 1s      | 2 次   | 毫秒级感知断线         |
| 工业采集      | 5s      | 5-6 次 | 平衡可靠性和带宽        |

---

## 五、面试常考问题

### Q1：TCP 有 keepalive，为什么还要应用层心跳？

```
TCP keepalive:
  - 默认 2 小时才探测（Linux）
  - 改配置影响全局所有连接
  - 只能检测网络层通断，不检测应用层假死

应用层心跳:
  - 间隔可控（5s vs 2h）
  - 可按连接定制策略
  - 能检测&quot;服务进程卡死但 TCP 还连着&quot;的情况
```

### Q2：心跳间隔设多大合适？

**没有固定值，看业务**：带宽敏感的物联网设备（NB-IoT）可能 30 分钟一次；金融行情 1 秒一次。核心公式：

```
心跳间隔 = 业务可容忍的最大断线感知时间 / 超时次数
```

PulseQt：业务要求 30s 内感知断线 → 30s / 6 次 = 5s 间隔。

### Q3：心跳包应该带数据吗？

```cpp
// 方案 A：纯心跳（PulseQt 采用）
// 帧 = Header + Length=0 + Type=0x02 + CRC

// 方案 B：带时间戳的心跳
// 可以计算网络延迟：收到应答的时间 - 请求中携带的发送时间 = RTT
```

一般用纯心跳。带宽充裕时可以带时间戳，方便监控链路质量。

### Q4：心跳处理和业务逻辑应该在同一线程吗？

```
PulseQt 的做法（✅ 正确）:
  解析线程：处理心跳（在 frameDecoded lambda 中判断 type=0x02/0x03）
  → 心跳是协议层的事，和数据帧同层处理

反例（❌ 不好）:
  单独线程处理心跳 → 和业务数据线程抢锁 → 复杂度翻倍
```

### Q5：如何防止心跳风暴？

```
场景：1000 台设备同时连一个服务器
如果所有设备同一时刻发心跳 → 服务器瞬间 1000 个包

解法：心跳间隔加随机抖动
  interval = 5000 + rand() % 1000;  // 5s ± 0.5s 随机
```

### Q6：收到心跳应答后要不要重置数据超时？

**要**。心跳应答也是&quot;对方还活着的证据&quot;。PulseQt 的 `m_lastDataTime` 在 `onRawDataReceived` 中更新——但心跳应答走的是 `frameDecoded` 而不是 `rawDataReceived`。

PulseQt 的处理：心跳应答在 frameDecoded 中只重置 `m_heartbeatMissed`，不重置 `m_lastDataTime`。这意味着心跳应答只能证明对方活着，但不能阻止继续发心跳——因为&quot;没有业务数据&quot;这个事实没变。

这是一个**合理的设计选择**，面试时可以讲出这个权衡。

---

## 六、PulseQt 实现中的反向通道

心跳需要上位机主动发数据，但 T013 的数据流是单向的（设备→上位机）。解决方案：加一条反向信号线。

```
ParseWorker                  TcpWorker                 TCP
    │                           │                      │
    │ emit writeData(frame)     │                      │
    │──────────────────────────→│ write(frame)         │
    │   (QueuedConnection)      │──────────────────────→│ 设备
```

`ParseWorker::writeData` 信号 → `TcpWorker::write` 槽 → `m_socket-&gt;write()`。不需要修改线程模型，信号槽自动跨线程。</content:encoded></item><item><title>Qt 自定义绘制 — QPainter 核心语法与实战</title><link>https://kiyose.wiki/tech/qtcustompaint-qt%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%98%E5%88%B6/</link><guid isPermaLink="true">https://kiyose.wiki/tech/qtcustompaint-qt%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%98%E5%88%B6/</guid><description>基于 PulseQt 实战总结：QPainter 基础、双缓冲、坐标变换、抽稀优化、交互事件与性能陷阱</description><pubDate>Mon, 13 Jan 2025 00:00:00 GMT</pubDate><content:encoded># Qt 自定义绘制 — 核心语法与实战思路

&gt; 基于 PulseQt T010（RealTimeChart）实战总结。涵盖 QPainter、双缓冲、坐标变换、交互事件、性能优化。

---

## 一、QPainter 基础

### 最小绘制骨架

```cpp
void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);                 // ① 绑定到当前 widget
    painter.setRenderHint(QPainter::Antialiasing, true);  // ② 抗锯齿

    painter.setPen(Qt::black);              // ③ 设置画笔
    painter.setBrush(Qt::white);            // ④ 设置画刷（填充）
    painter.drawLine(0, 0, 100, 100);       // ⑤ 画
}
```

| 步骤  | API                           | 说明                     |
|:---:| ----------------------------- | ---------------------- |
| ①   | `QPainter(widget)`            | 绑到 widget，画的内容直接显示在控件上 |
| ②   | `setRenderHint(Antialiasing)` | 抗锯齿（⚠️ 软件渲染下极慢，见第七节）   |
| ③   | `setPen(QPen)`                | 线条颜色、粗细、样式             |
| ④   | `setBrush(QBrush)`            | 填充颜色、图案                |
| ⑤   | `drawLine/drawRect/...`       | 具体绘制                   |

### 坐标系

```
(0,0) ────→ x 增（右）
  │
  ↓
  y 增（下）

和数学坐标系 Y 轴相反！绘图时时刻记住：Y 越大越靠下。
```

---

## 二、常用绘制 API

### 线条与形状

```cpp
painter.drawLine(x1, y1, x2, y2);           // 直线
painter.drawRect(x, y, w, h);               // 矩形
painter.drawEllipse(cx, cy, rx, ry);        // 椭圆
painter.drawText(x, y, &quot;text&quot;);             // 文字

// 网格线：浅灰 + 细线
painter.setPen(QPen(QColor(0xE0, 0xE0, 0xE0), 0.5));
for (int i = 0; i &lt;= 4; ++i) {
    double y = top + (bottom - top) * i / 4.0;
    painter.drawLine(left, y, right, y);
}
```

### 折线（推荐用于实时数据）

```cpp
QPolygonF polyline;
polyline &lt;&lt; QPointF(10, 20) &lt;&lt; QPointF(30, 50) &lt;&lt; QPointF(80, 30);
painter.setPen(QPen(Qt::red, 1.5));
painter.drawPolyline(polyline);   // 不闭合（折线）
// painter.drawPolygon(polyline); // 闭合（多边形）
```

### QPainterPath（贝塞尔路径，不适合大量折线）

```cpp
QPainterPath path;
path.moveTo(startX, startY);     // 移到起点
path.lineTo(x1, y1);             // 画直线
path.cubicTo(...);                // 贝塞尔曲线
painter.drawPath(path);

// ⚠️ drawPath 比 drawPolyline 慢 3-5 倍，适合曲线，不适合大量折线
```

---

## 三、坐标变换（数据 → 像素）

这是自绘曲线最核心的数学。数据有自己的坐标系（时间 + 数值），需要映射到屏幕像素。

```cpp
// 时间戳（毫秒）→ 像素 X
double timeToPixelX(uint64_t ts, uint64_t latestTs, double windowMs) const
{
    // ts 在 [latestTs-windowMs, latestTs] 之间
    double ratio = (latestTs - ts) / windowMs;   // 0.0（最新）→ 1.0（最旧）
    double rightEdge = width() - 20.0;           // 右侧留 20px
    double leftEdge  = 50.0;                     // 左侧留 50px（Y 轴宽度）
    return rightEdge - ratio * (rightEdge - leftEdge);
}

// 数值 → 像素 Y
double valueToPixelY(double val, double yMin, double yMax) const
{
    // val 在 [yMin, yMax] 之间
    double ratio = (val - yMin) / (yMax - yMin); // 0.0（最小）→ 1.0（最大）
    double bottom = height() - 40.0;             // 底部留 40px
    double top    = 20.0;                        // 顶部留 20px
    return bottom - ratio * (bottom - top);       // Y 轴翻转！
}
```

**关键**：Y 像素 = 底部 - ratio × 高度。因为屏幕 Y 往下增长，而数据通常 Y 往上增长。

---

## 四、双缓冲

### 为什么需要

```cpp
// ❌ 直接绘制 — 用户看到绘制过程（空白→网格→曲线→图例），闪烁
void paintEvent(QPaintEvent *) {
    QPainter p(this);
    drawBackground(p);  // 屏幕先白后黑
    drawCurves(p);      // 屏幕再画曲线
}

// ✅ 双缓冲 — 在内存中画完整张图，一次性贴到屏幕
void paintEvent(QPaintEvent *) {
    QPixmap offscreen(size());         // 离屏画布
    offscreen.fill(Qt::white);

    QPainter p(&amp;offscreen);            // 在内存中画
    drawBackground(p);
    drawCurves(p);
    p.end();

    QPainter screen(this);             // 一次性贴到屏幕
    screen.drawPixmap(0, 0, offscreen);
}
```

### 性能优化：复用 QPixmap

```cpp
// 成员变量
QPixmap m_offscreen;

void paintEvent(QPaintEvent *) {
    if (m_offscreen.size() != size())      // 只在尺寸变化时重建
        m_offscreen = QPixmap(size());
    m_offscreen.fill(Qt::white);           // 填充（memset 3.8MB，很快）

    QPainter p(&amp;m_offscreen);
    // ... 绘制 ...
    p.end();

    QPainter screen(this);
    screen.drawPixmap(0, 0, m_offscreen);  // RAM→VRAM 拷贝
}
```

**收益**：避免每帧 `new QPixmap` 导致 3.8MB 堆碎片累积。

---

## 五、大量数据点的处理

### 裁剪（只画可见区域）

```cpp
auto snap = m_buffer-&gt;snapshot();   // 可能 10000 条

// 二分查找跳到第一个可见点，跳过屏外 7000 条
uint64_t minTs = latestTs - windowMs;
auto it = std::lower_bound(snap.begin(), snap.end(), minTs,
    [](const DataPoint &amp;dp, uint64_t ts) { return dp.timestamp &lt; ts; });

for (; it != snap.end(); ++it) { /* 只画可见的 3000 条 */ }
```

### 抽稀（相邻点太近就跳过）

```cpp
double threshold = 1.5;  // 像素间距阈值
double lastPx = -9999;

for (auto &amp;dp : snap) {
    double px = timeToPixelX(dp.timestamp, ...);
    if (qAbs(px - lastPx) &lt; threshold) continue;  // 太近，跳过
    lastPx = px;
    polyline.append(QPointF(px, py));
}
```

**效果**：3000 个数据点 → 900px 画面 → 约 600 个有效像素（阈值 1.5）。画面流畅，CPU 省 5 倍。

---

## 六、交互事件

### 滚轮缩放

```cpp
void wheelEvent(QWheelEvent *event) override {
    if (event-&gt;angleDelta().y() &gt; 0)
        m_timeWindow /= 1.2;      // 放大
    else
        m_timeWindow *= 1.2;      // 缩小
    m_timeWindow = qBound(5.0, m_timeWindow, 120.0);  // 限制范围
    update();                      // 触发重绘
}
```

### 鼠标拖拽

```cpp
void mousePressEvent(QMouseEvent *e) override { m_dragging = true; m_lastPos = e-&gt;pos(); }
void mouseMoveEvent(QMouseEvent *e) override {
    if (!m_dragging) return;
    double dx = e-&gt;pos().x() - m_lastPos.x();
    double msPerPixel = (m_timeWindow * 1000.0) / (width() - 70.0);
    m_xOffset -= dx * msPerPixel;  // 像素位移 → 时间偏移
    m_lastPos = e-&gt;pos();
    update();
}
void mouseReleaseEvent(QMouseEvent *) override { m_dragging = false; }
```

### 右键重置

```cpp
void contextMenuEvent(QContextMenuEvent *) override {
    m_timeWindow = 30.0; m_xOffset = 0.0; update();
}
```

---

## 七、性能陷阱与解决方案

### 🔴 陷阱 1：抗锯齿（最致命）

|        | 开启          | 关闭    |
| ------ |:-----------:|:-----:|
| 软件渲染   | **400ms/帧** | 5ms/帧 |
| GPU 加速 | 10ms/帧      | 3ms/帧 |

**规则**：大量折线（100+ 线段）→ 关抗锯齿。文字、坐标轴（少量线条）→ 可保留。

```cpp
// 分区域控制
painter.setRenderHint(QPainter::Antialiasing, true);
drawBackground(p);   // 坐标轴平滑
painter.setRenderHint(QPainter::Antialiasing, false);
drawCurves(p);       // 曲线用 QPolygonF，本身就直，不需要 AA
```

### 🔴 陷阱 2：QPainterPath vs QPolygonF

```cpp
// ❌ 慢 — 即使全直线，drawPath 也走贝塞尔管线
path.moveTo(x, y); path.lineTo(x2, y2); painter.drawPath(path);

// ✅ 快 3-5 倍 — 纯折线，不涉及曲线计算
QPolygonF poly; poly.append(QPointF(x, y)); painter.drawPolyline(poly);
```

### 🟡 陷阱 3：每帧 new QPixmap

```cpp
// ❌ 每帧分配 3.8MB → 堆碎片 → 越来越慢
QPixmap offscreen(size());

// ✅ 成员变量复用
if (m_offscreen.size() != size()) m_offscreen = QPixmap(size());
```

### 🟡 陷阱 4：repaint() vs update()

|        | `repaint()` | `update()`    |
| ------ |:-----------:|:-------------:|
| 执行方式   | 同步，立即画      | 异步，事件队列       |
| 阻塞事件循环 | ✅ 是         | ❌ 否           |
| 适用场景   | 罕见（截图）      | **99% 情况用这个** |

```cpp
void onTimer() { update(); }  // ✅ 不阻塞，数据照常进来
```

### 🟡 陷阱 5：snapshot 每点调一次

```cpp
// ❌ 循环内取 snapshot → 锁竞争
for (auto &amp;dp : snap) { auto ts = m_buffer-&gt;snapshot().last().timestamp; }

// ✅ 循环外取一次，参数传入
auto snap = m_buffer-&gt;snapshot();
uint64_t latestTs = snap.last().timestamp;
for (auto &amp;dp : snap) { double px = timeToPixelX(dp.ts, latestTs, windowMs); }
```

---

## 八、完整绘制流程（以 PulseQt RealTimeChart 为例）

```
定时器 40ms →
  update() → Qt 事件队列 →
    paintEvent():
      ① QPixmap 复用（尺寸不变不重建）
      ② fill(Qt::white)（memset 3.8MB）
      ③ QPainter 绑到 QPixmap
      ④ setRenderHint(Antialiasing, true) → drawBackground（网格+轴）
      ⑤ setRenderHint(Antialiasing, false) → drawCurves():
           snapshot() ×1
           lower_bound 跳到可见区域
           3 通道 × for loop:
             timeToPixelX + valueToPixelY × N 点
             间距 &lt; 1.5px → 跳过（抽稀）
             QPolygonF::append
           drawPolyline ×3
      ⑥ drawLegend（小色块）
      ⑦ p.end()
      ⑧ screenPainter.drawPixmap（贴到屏幕）
```

**每帧耗时**：~5ms（无抗锯齿 + 抽稀 + 裁剪），25FPS 下 5/40ms = 12.5% CPU。</content:encoded></item><item><title>Qt 多线程开发 — QThread + moveToThread</title><link>https://kiyose.wiki/tech/qtmultithreading-qt%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%BC%80%E5%8F%91/</link><guid isPermaLink="true">https://kiyose.wiki/tech/qtmultithreading-qt%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%BC%80%E5%8F%91/</guid><description>基于 PulseQt 实战总结：QThread、moveToThread、跨线程信号槽、QMutex 线程安全、常见坑与面试考点</description><pubDate>Sun, 12 Jan 2025 00:00:00 GMT</pubDate><content:encoded># Qt 多线程开发 — 基于 QThread + moveToThread

&gt; 基于 PulseQt T013（Worker+QThread 三线程改造）实战总结。

---

## 一、为什么需要多线程

```
单线程（T012 之前）:
  主线程: UI绘制 → TCP收数据 → 协议解码 → 写DB → UI刷新
           ↑                          ↑
      用户操作流畅                  DB写入(1-5ms)卡UI

多线程（T013）:
  通信线程: TCP收数据                 ← 只做IO
     ↓ QueuedConnection
  解析线程: 解码 → 写DB               ← 只做计算+磁盘
     ↓ QueuedConnection
  主线程:   UI绘制 + 用户交互          ← 永不阻塞
```

核心原则：**哪个活会阻塞，就把它踢出主线程**。

---

## 二、三种线程方案对比

| 方案 | 复杂度 | 适用场景 |
|------|:--:|------|
| `QThread::run()` 重写 | 低 | 一次性后台任务（下载文件） |
| **`moveToThread`**（PulseQt 采用） | 中 | 长期存在的 Worker，通过信号槽通信 |
| `QtConcurrent::run` | 低 | 纯函数、无状态的计算 |

---

## 三、moveToThread 核心模式

### 固定套路（四步）

```cpp
// ① 创建 QThread（主线程）
QThread *thread = new QThread;

// ② 创建 Worker（主线程）
//    ⚠️ 构造函数不 new QObject 子对象（QSerialPort 等）
Worker *worker = new Worker;   // 无 parent

// ③ 移入线程
worker-&gt;moveToThread(thread);

// ④ 启动
thread-&gt;start();
```

### 为什么 Worker 构造时不能 new QSerialPort

```cpp
// ❌ 错误 — 构造在主线程，m_serial 归属主线程
Worker::Worker() {
    m_serial = new QSerialPort;   // 线程亲和性 = 主线程
}
worker-&gt;moveToThread(thread);
// m_serial 还在主线程！后面在通信线程里操作它 → 报错

// ✅ 正确 — open 在通信线程里执行，m_serial 归属通信线程
void Worker::open() {            // 这个槽在通信线程里执行
    m_serial = new QSerialPort;  // 线程亲和性 = 通信线程
}
```

**规则**：QObject 的子对象必须在其工作线程中创建。方法：把创建延迟到第一个槽中执行。

---

## 四、跨线程通信 — QueuedConnection

### 为什么必须用

```cpp
// AutoConnection（默认）: 同线程=直接调，跨线程=排队调
connect(worker, &amp;Worker::rawData, parser, &amp;Parser::onData);  // 可以

// 显式指定 QueuedConnection（推荐，意图清晰）
connect(worker, &amp;Worker::rawData, parser, &amp;Parser::onData,
        Qt::QueuedConnection);
```

### 信号参数怎么传递

```cpp
// 跨线程 signal → slot 时，参数自动深拷贝到接收线程
// 支持的类型：基础类型、QString、QByteArray、QVector 等 Qt 类型
// ⚠️ 不支持：裸指针、非 Qt 类型的引用
```

### invokeMethod — 手动触发跨线程调用

```cpp
// 让 Worker 在它自己的线程里执行 open()
QMetaObject::invokeMethod(worker, &quot;open&quot;, Qt::QueuedConnection);

// 带参数
QMetaObject::invokeMethod(worker, &quot;setName&quot;, Qt::QueuedConnection,
                          Q_ARG(QString, &quot;hello&quot;));
```

| `invokeMethod` vs `connect+emit` | 场景 |
|------|------|
| `invokeMethod` | 一次性调用（&quot;去开一下串口&quot;） |
| `connect+emit` | 持续事件流（&quot;每次收到数据就通知我&quot;） |

---

## 五、线程生命周期

### 安全退出四步（顺序不能错）

```cpp
void cleanup()
{
    // ① 关闭硬件（在 Worker 自己的线程里关）
    QMetaObject::invokeMethod(worker, &quot;close&quot;, Qt::QueuedConnection);

    // ② Worker 在自己的线程里自杀
    worker-&gt;deleteLater();

    // ③ 退出事件循环
    thread-&gt;quit();

    // ④ 等线程结束
    thread-&gt;wait();   // 阻塞，等到线程真正退出

    worker = nullptr;
    delete thread;
}
```

### 为什么不用 terminate()

```cpp
thread-&gt;terminate();  // ❌ 暴力杀线程 — 锁没释放、内存没回收、DB 没 flush
thread-&gt;quit();       // ✅ 优雅退出 — 等事件循环自然结束
```

### 窗口关闭时的处理

```cpp
void MainWindow::closeEvent(QCloseEvent *event)
{
    onDisconnect();    // 先清理线程
    event-&gt;accept();   // 再关窗口
}
```

不写 `closeEvent` = 窗口先析构 → QThread 还在跑 → 报 &quot;Destroyed while still running&quot;。

---

## 六、QMutex — 数据共享

parseWorker 和 UI 线程都访问同一个 DataBuffer：

```cpp
// ParseWorker（解析线程）
void onFrame(const Frame &amp;f) {
    m_buffer.push(dp);   // 加锁写入
}

// RealTimeChart（UI 线程）
void paintEvent() {
    auto snap = m_buffer-&gt;snapshot();  // 加锁拷贝
}
```

### DataBuffer 内部的线程安全保证

```cpp
// push() — 可能被解析线程调用
void DataBuffer::push(const DataPoint &amp;dp) {
    QMutexLocker lock(&amp;m_mutex);   // ① 加锁
    m_ring[m_head] = dp;
    m_head = (m_head + 1) % m_maxSize;
    emit bufferUpdated(1);          // ② 发信号
}                                    // ③ 锁释放

// snapshot() — 可能被 UI 线程调用
QVector&lt;DataPoint&gt; snapshot() const {
    QMutexLocker lock(&amp;m_mutex);   // 加锁 → 拷贝 → 解锁 → 返回副本
}
```

### 死锁预防

```cpp
// ⚠️ 同线程 + signal/slot = 可能死锁
connect(&amp;m_buffer, &amp;DataBuffer::bufferUpdated,
        this, &amp;DataTableModel::onUpdate,
        Qt::QueuedConnection);   // ← 必须 QueuedConnection！
```

同线程下 `DirectConnection` = 当场调用槽 → 槽里又调 `snapshot()` → 同一把锁再加 → 死锁。用 `QueuedConnection` → 锁释放后事件循环再调槽。

---

## 七、moveToThread 常见坑

| 坑 | 现象 | 原因 | 规则 |
|------|------|------|------|
| ① | `Cannot move to target thread` | Worker 有 parent | moveToThread 前不能设 parent |
| ② | `SendEvent` 断言失败 | 主线程直接调了 `worker-&gt;close()` | 必须用 `invokeMethod` |
| ③ | `Destroyed while running` | 窗口关了线程还在跑 | 加 `closeEvent` 清理 |
| ④ | Worker 槽不执行 | 线程没 `start()` 或 Worker 没 `moveToThread` | 检查顺序 |
| ⑤ | 信号收不到 | 跨线程但没用 `QueuedConnection` | 显式加第五个参数 |
| ⑥ | 数据竞争 | 两个线程同时写同一变量 | 加 QMutex 或用信号槽传值 |

---

## 八、PulseQt 线程架构

```
┌─────────────────────────────────────────┐
│                 主线程                    │
│  MainWindow                              │
│  ├── QTableView ← DataTableModel         │
│  ├── RealTimeChart ← 25FPS 定时器        │
│  └── snapshot() → DataBuffer (QMutex)    │
│        ↑ QueuedConnection                │
├─────────────────────────────────────────┤
│               解析线程                    │
│  ParseWorker                             │
│  ├── ProtocolDecoder (状态机)            │
│  ├── DataBuffer (环形缓冲, QMutex)      │
│  └── DatabaseManager (SQLite WAL)       │
│        ↑ QueuedConnection                │
├─────────────────────────────────────────┤
│               通信线程                    │
│  TcpWorker / SerialWorker               │
│  ├── QTcpSocket / QSerialPort           │
│  └── readyRead → rawDataReceived        │
└─────────────────────────────────────────┘
```

| 线程 | 做什么 | 不做什么 |
|------|------|------|
| 主线程 | UI 绘制、用户交互 | 不碰 IO、不做解码 |
| 解析线程 | 解码+DB写入+缓冲 | 不碰 UI |
| 通信线程 | 收发字节 | 不关心数据含义 |

---

## 九、什么时候用多线程

| 场景 | 是否需要 | 方案 |
|------|:--:|------|
| 数据库写入 | ✅ | 踢到解析线程 |
| TCP/串口收发 | ✅ | 通信线程 |
| 文件读写 | ✅ | worker 线程 |
| 复杂计算(&gt;50ms) | ✅ | worker 线程 |
| UI 绘制 | ❌ | 只能在主线程 |
| 简单赋值/标记 | ❌ | 主线程就行，加线程反而复杂 |

**判断标准**：这个操作会阻塞超过一帧(16ms)吗？会 → 开线程。不会 → 主线程。</content:encoded></item><item><title>Qt 单元测试 — QTest 框架</title><link>https://kiyose.wiki/notes/qtest/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qtest/</guid><description>QTest 基础用法、数据驱动测试、GUI 测试、Mock 与测试组织</description><pubDate>Sat, 30 Nov 2024 00:00:00 GMT</pubDate><content:encoded>## 最小测试用例

```cpp
#include &lt;QTest&gt;

class TestMath : public QObject {
    Q_OBJECT
private slots:
    void testAddition() {
        QCOMPARE(1 + 1, 2);
    }
    void testDivision() {
        QVERIFY(10 / 2 == 5);
        QVERIFY2(10 / 3 == 3, &quot;整数除法应截断&quot;);  // 带消息
    }
};

QTEST_MAIN(TestMath)  // 自动生成 main
```

## 常用断言

```cpp
QVERIFY(condition);                    // 真
QVERIFY2(condition, &quot;message&quot;);       // 真 + 消息

QCOMPARE(actual, expected);           // 相等（自动类型安全）
QCOMPARE(str, &quot;hello&quot;);               // QString 直接比较
QCOMPARE(list.size(), 3);

QTRY_COMPARE(obj.property(), value);  // 重试直到相等（异步测试）
QTRY_VERIFY(condition);               // 重试直到 true

QVERIFY_EXCEPTION_THROWN(expr, Exc);  // 确认抛异常
QBENCHMARK { doWork(); }              // 性能测试
```

## 初始化和清理

```cpp
class TestDatabase : public QObject {
    Q_OBJECT
private:
    QSqlDatabase *db;
private slots:
    // 每个 test 用例前
    void init() { db = new QSqlDatabase(...); }
    // 每个 test 用例后
    void cleanup() { delete db; db = nullptr; }

    // 所有用例前（private slot！）
    void initTestCase() { /* 建表、加载测试数据 */ }
    // 所有用例后
    void cleanupTestCase() { /* 清空数据库 */ }

    void testInsert() { /* ... */ }
    void testQuery() { /* ... */ }
};
```

## 数据驱动测试

```cpp
class TestParser : public QObject {
    Q_OBJECT
private slots:
    void testHexToInt_data() {
        QTest::addColumn&lt;QString&gt;(&quot;hex&quot;);
        QTest::addColumn&lt;int&gt;(&quot;expected&quot;);

        QTest::newRow(&quot;zero&quot;)  &lt;&lt; &quot;00&quot; &lt;&lt; 0;
        QTest::newRow(&quot;ten&quot;)   &lt;&lt; &quot;0A&quot; &lt;&lt; 10;
        QTest::newRow(&quot;max&quot;)   &lt;&lt; &quot;FF&quot; &lt;&lt; 255;
        QTest::newRow(&quot;mixed&quot;) &lt;&lt; &quot;A5&quot; &lt;&lt; 165;
    }

    void testHexToInt() {
        QFETCH(QString, hex);
        QFETCH(int, expected);
        QCOMPARE(hexToInt(hex), expected);
    }
};
// 4 行测试数据 → 自动生成 4 个独立测试用例
```

## GUI 测试

```cpp
class TestMainWindow : public QObject {
    Q_OBJECT
private slots:
    void testButtonClick() {
        MainWindow w;
        w.show();

        // 查找控件
        auto *btn = w.findChild&lt;QPushButton *&gt;(&quot;submitBtn&quot;);
        QVERIFY(btn != nullptr);

        // 模拟点击
        QTest::mouseClick(btn, Qt::LeftButton);

        // 验证结果
        auto *label = w.findChild&lt;QLabel *&gt;(&quot;resultLabel&quot;);
        QCOMPARE(label-&gt;text(), QString(&quot;提交成功&quot;));
    }

    void testKeyInput() {
        QLineEdit edit;
        QTest::keyClicks(&amp;edit, &quot;hello&quot;);
        QCOMPARE(edit.text(), QString(&quot;hello&quot;));

        QTest::keyClick(&amp;edit, Qt::Key_Return);
        // 测试回车事件
    }
};
```

## 信号测试

```cpp
void testSignalEmission() {
    Worker worker;

    QSignalSpy spy(&amp;worker, &amp;Worker::finished);
    QVERIFY(spy.isValid());

    worker.start();
    QVERIFY(spy.wait(1000));  // 等 1 秒
    QCOMPARE(spy.count(), 1);  // 信号发射 1 次

    // 检查信号参数
    QList&lt;QVariant&gt; args = spy.takeFirst();
    QCOMPARE(args.at(0).toInt(), 42);  // 第一个参数
}
```

## 项目组织

```
tests/
├── CMakeLists.txt
├── tst_math.cpp         # 纯逻辑测试
├── tst_protocol.cpp     # 协议解析测试
├── tst_database.cpp     # DB 集成测试
├── tst_mainwindow.cpp   # GUI 测试
└── testdata/
    ├── frames.bin       # 测试用二进制数据
    └── fixtures.sql     # 数据库初始数据
```

```cmake
# tests/CMakeLists.txt
find_package(Qt6 REQUIRED COMPONENTS Test)

function(add_qt_test name)
    add_executable(${name} ${name}.cpp)
    target_link_libraries(${name} PRIVATE Qt6::Test myapp_lib)
    add_test(NAME ${name} COMMAND ${name})
endfunction()

add_qt_test(tst_math)
add_qt_test(tst_protocol)
add_qt_test(tst_database)

# 运行: ctest --output-on-failure
```

## 常见模式

```cpp
// 测试私有成员（加 friend）
class MyClass {
    friend class TestMyClass;  // 仅测试可以访问
    int privateMethod();
};

// 测试异步操作
void testAsync() {
    Worker w;
    QSignalSpy spy(&amp;w, &amp;Worker::done);
    QMetaObject::invokeMethod(&amp;w, &quot;start&quot;, Qt::QueuedConnection);
    QVERIFY(spy.wait(3000));
}
```</content:encoded></item><item><title>Python 异步编程</title><link>https://kiyose.wiki/notes/asyncio/</link><guid isPermaLink="true">https://kiyose.wiki/notes/asyncio/</guid><description>asyncio 核心概念：协程、事件循环、Task 并发与 aiohttp 实战</description><pubDate>Wed, 20 Nov 2024 00:00:00 GMT</pubDate><content:encoded>## 为什么需要异步

```python
# 同步：逐个等待
import time

def download(url):
    time.sleep(1)   # 模拟 IO 等待
    return f&quot;{url} done&quot;

start = time.time()
for url in [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]:
    download(url)
print(f&quot;{time.time() - start:.1f}s&quot;)  # 3.0s
```

```python
# 异步：并发等待
import asyncio

async def download(url):
    await asyncio.sleep(1)  # 不阻塞事件循环
    return f&quot;{url} done&quot;

async def main():
    tasks = [download(url) for url in [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())
# 耗时 ~1.0s，三个任务并发执行
```

## async/await 语法

```python
# async def 定义协程函数 — 调用它不执行，返回协程对象
async def foo():
    return 42

coro = foo()           # 不执行，只是协程对象
result = await coro    # 真正执行

# await 只能出现在 async 函数内部
# await 后面必须是 Awaitable（协程、Task、Future）

# 常见的可 await 对象：
await asyncio.sleep(1)        # 等待
await some_coroutine()        # 协程
await asyncio.gather(...)     # 并发等
await task                    # Task 对象
```

## 事件循环

```python
# 获取当前事件循环
loop = asyncio.get_event_loop()

# 主入口（Python 3.7+）
asyncio.run(main())

# 等价于：
loop = asyncio.new_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.close()
```

---

## Task 并发

```python
# create_task：立即调度，不阻塞
async def main():
    task1 = asyncio.create_task(download(&quot;a&quot;))
    task2 = asyncio.create_task(download(&quot;b&quot;))
    # 两个任务已经在后台跑了

    result1 = await task1    # 等 task1 完成
    result2 = await task2    # 等 task2 完成

# gather：批量等待
results = await asyncio.gather(
    download(&quot;a&quot;),
    download(&quot;b&quot;),
    download(&quot;c&quot;),
    return_exceptions=True   # 单个失败不影响其他
)

# as_completed：哪个先完成先处理
for coro in asyncio.as_completed(tasks):
    result = await coro
    print(f&quot;完成: {result}&quot;)
```

| 方法 | 返回时机 | 适用场景 |
|------|----------|----------|
| `gather` | 全部完成 | 批量请求 |
| `as_completed` | 逐个完成 | 哪个快处理哪个 |
| `wait` | 可配置 | FIRST_COMPLETED / ALL_COMPLETED |

---

## 实战：aiohttp 网络请求

```python
import aiohttp
import asyncio

async def fetch(url, session):
    async with session.get(url) as resp:
        return await resp.text()

async def main():
    urls = [
        &quot;https://httpbin.org/delay/1&quot;,
        &quot;https://httpbin.org/delay/1&quot;,
        &quot;https://httpbin.org/delay/1&quot;,
    ]
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(url, session) for url in urls]
        results = await asyncio.gather(*tasks)
        print(f&quot;Got {len(results)} responses&quot;)

asyncio.run(main())  # ~1s, not 3s
```

## 常见陷阱

### 陷阱 1：async 函数里调同步阻塞代码

```python
async def bad():
    time.sleep(2)          # ❌ 阻塞整个事件循环
    result = requests.get(url)  # ❌ requests 是同步库

async def good():
    await asyncio.sleep(2) # ✅
    # 用 aiohttp 替代 requests
```

### 陷阱 2：忘记 await

```python
asyncio.create_task(download(&quot;a&quot;))  # ⚠️ 没有 await，可能还没跑完 main 就退出了

# 解决：存引用，最后 await
task = asyncio.create_task(download(&quot;a&quot;))
await task
```

### 陷阱 3：协程对象重复 await

```python
coro = download(&quot;a&quot;)
await coro    # ✅ 第一次
await coro    # ❌ RuntimeError: cannot reuse already awaited coroutine

# 正确：想多次执行就每次重新调用
```

## 异步 vs 多线程

| | asyncio | threading |
|------|---------|-----------|
| 适合 | IO 密集型 | IO / CPU 密集型 |
| 切换成本 | 极低（协程切换） | 较高（线程切换） |
| 并发数 | 数千个 | 几十个 |
| GIL | 不受影响 | 受 GIL 限制 |
| 学习曲线 | 较高 | 中等 |
| 调试 | 较难 | 相对容易 |</content:encoded></item><item><title>Qt 资源系统与国际化</title><link>https://kiyose.wiki/notes/qtresources/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qtresources/</guid><description>.qrc 资源文件、多语言翻译 .ts/.qm、动态语言切换</description><pubDate>Fri, 18 Oct 2024 00:00:00 GMT</pubDate><content:encoded>## Qt 资源系统 (.qrc)

把图片、图标、QSS、配置文件编译进可执行文件。

```xml
&lt;!-- resources.qrc --&gt;
&lt;RCC&gt;
    &lt;qresource prefix=&quot;/icons&quot;&gt;
        &lt;file&gt;icons/save.png&lt;/file&gt;
        &lt;file&gt;icons/open.png&lt;/file&gt;
    &lt;/qresource&gt;
    &lt;qresource prefix=&quot;/styles&quot;&gt;
        &lt;file&gt;styles/dark.qss&lt;/file&gt;
    &lt;/qresource&gt;
    &lt;qresource prefix=&quot;/&quot;&gt;
        &lt;file&gt;config.json&lt;/file&gt;
    &lt;/qresource&gt;
&lt;/RCC&gt;
```

```cpp
// 代码中使用
QPixmap(&quot;:/icons/save.png&quot;);           // prefix + 文件名
QIcon(&quot;:/icons/open.png&quot;);
setStyleSheet(loadFile(&quot;:/styles/dark.qss&quot;));

// 路径规则：:/前缀/文件名
// ⚠️ 路径大小写敏感
```

### CMake 配置

```cmake
qt_add_resources(MyApp &quot;app_resources&quot;
    PREFIX &quot;/&quot;
    FILES
        resources.qrc
)
```

## 国际化 (i18n) 三步走

### 第一步：代码中用 tr()

```cpp
// 所有用户可见字符串用 tr() 包裹
label-&gt;setText(tr(&quot;Hello World&quot;));
button-&gt;setText(tr(&quot;Save&quot;));

// tr() 支持参数
statusBar()-&gt;showMessage(tr(&quot;%1 files processed&quot;).arg(count));

// 处理复数
tr(&quot;%n file(s) saved&quot;, &quot;&quot;, n);  // n=1 时显示 &quot;1 file saved&quot;

// QObject 子类自动有 tr()；非 QObject 用 QCoreApplication::translate()
QCoreApplication::translate(&quot;MyClass&quot;, &quot;Hello&quot;);
```

### 第二步：生成 .ts 翻译文件

```bash
# 在 .pro 文件中声明
TRANSLATIONS = myapp_zh_CN.ts myapp_ja.ts

# 或用 lupdate 命令行
lupdate main.cpp widget.cpp -ts myapp_zh_CN.ts
```

生成的 .ts 文件结构：

```xml
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;!DOCTYPE TS&gt;
&lt;TS version=&quot;2.1&quot; language=&quot;zh_CN&quot;&gt;
&lt;context&gt;
    &lt;name&gt;MainWindow&lt;/name&gt;
    &lt;message&gt;
        &lt;source&gt;Hello World&lt;/source&gt;
        &lt;translation&gt;你好，世界&lt;/translation&gt;
    &lt;/message&gt;
&lt;/context&gt;
&lt;/TS&gt;
```

### 第三步：编译 .qm 并加载

```bash
# 用 Qt Linguist 编辑 .ts → 或用 lrelease 编译
lrelease myapp_zh_CN.ts   # 生成 myapp_zh_CN.qm
```

```cpp
#include &lt;QTranslator&gt;

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QTranslator translator;
    if (translator.load(&quot;:/myapp_zh_CN.qm&quot;)) {
        app.installTranslator(&amp;translator);
    }

    // ...
    return app.exec();
}
```

## 运行时语言切换

```cpp
class LanguageManager {
    QMap&lt;QString, QTranslator *&gt; m_translators;
public:
    void install(const QString &amp;lang, const QString &amp;qmPath) {
        auto *t = new QTranslator(this);
        if (t-&gt;load(qmPath)) {
            qApp-&gt;installTranslator(t);
            m_translators.insert(lang, t);
        }
    }

    void switchTo(const QString &amp;lang) {
        // 移除所有旧翻译
        for (auto *t : m_translators)
            qApp-&gt;removeTranslator(t);

        // 安装目标语言
        auto *t = m_translators.value(lang);
        if (t) qApp-&gt;installTranslator(t);

        // 通知所有控件刷新
        emit languageChanged();
    }
signals:
    void languageChanged();
};
```

### 控件刷新

```cpp
// QWidget 重写 changeEvent
void MainWindow::changeEvent(QEvent *event) override {
    if (event-&gt;type() == QEvent::LanguageChange) {
        // Qt 自动刷新通过 UI 文件创建的控件
        // 手动设置的文字需要在这里重新设置
        ui-&gt;retranslateUi(this);  // 重新翻译 UI 文件
        updateDynamicTexts();      // 手动更新的文字
    }
    QMainWindow::changeEvent(event);
}

void MainWindow::updateDynamicTexts() {
    m_statusLabel-&gt;setText(tr(&quot;Ready&quot;));
    setWindowTitle(tr(&quot;My Application&quot;));
}
```

## 常见问题

```cpp
// ❌ 不要用 #define 或 const char * 存可翻译字符串
const char *msg = &quot;Hello&quot;;  // lupdate 扫描不到宏定义

// ✅ 直接用 tr()
label-&gt;setText(tr(&quot;Hello&quot;));

// ❌ 字符串拼接会破坏翻译查找
label-&gt;setText(tr(&quot;Hello &quot; + name));  // 不好

// ✅ 用 %1 占位符
label-&gt;setText(tr(&quot;Hello %1&quot;).arg(name));
```

## 文件组织建议

```
project/
├── resources.qrc
├── icons/              # 图标 → 编译进 .qrc
│   ├── save.png
│   └── open.png
├── styles/             # QSS → 编译进 .qrc
│   ├── light.qss
│   └── dark.qss
└── translations/       # .ts/.qm — 不编译进 .qrc（太大）
    ├── myapp_zh_CN.ts
    ├── myapp_zh_CN.qm
    ├── myapp_ja.ts
    └── myapp_ja.qm
```</content:encoded></item><item><title>Qt 应用部署 — 跨平台打包</title><link>https://kiyose.wiki/notes/qtdeployment/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qtdeployment/</guid><description>windeployqt/linuxdeployqt/macdeployqt 使用方法、依赖分析、安装包制作与常见问题</description><pubDate>Tue, 15 Oct 2024 00:00:00 GMT</pubDate><content:encoded>## Windows — windeployqt

```batch
:: 1. Release 构建
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release

:: 2. 复制 exe 到独立目录
mkdir dist
copy build\myapp.exe dist\

:: 3. 自动部署 Qt 依赖
cd dist
windeployqt myapp.exe

:: 完整命令（带参数）
windeployqt --release --qmldir ..\src\qml myapp.exe
```

### windeployqt 做了什么

```
myapp.exe
  ├── Qt6Core.dll
  ├── Qt6Widgets.dll
  ├── Qt6Sql.dll
  ├── platforms/
  │   └── qwindows.dll
  ├── styles/
  │   └── qwindowsvistastyle.dll
  ├── sqldrivers/
  │   └── qsqlite.dll
  └── imageformats/
      ├── qjpeg.dll
      └── qpng.dll
```

### 常见问题

```batch
:: 问题 1: 缺少 MSVC 运行时
:: 解决：静态链接 /MT（CMake: set(CMAKE_MSVC_RUNTIME_LIBRARY &quot;MultiThreaded&quot;)）
:: 或：把 vcredist 一起分发

:: 问题 2: 缺少 OpenSSL DLL
:: windeployqt 不检测 OpenSSL → 手动复制 libcrypto-3-x64.dll libssl-3-x64.dll

:: 问题 3: 程序不报错但闪退
:: 设置环境变量查看详细输出
set QT_DEBUG_PLUGINS=1
myapp.exe 2&gt; debug.log

:: 制作安装包（Inno Setup 示例）
:: 下载 Inno Setup → 向导生成 .iss → 编译 → 输出 .exe 安装包
```

## Linux — linuxdeployqt

```bash
# 1. 下载 linuxdeployqt
wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage
chmod +x linuxdeployqt-continuous-x86_64.AppImage

# 2. 部署
mkdir dist &amp;&amp; cp build/myapp dist/
./linuxdeployqt-continuous-x86_64.AppImage dist/myapp -appimage

# 3. 输出: MyApp-x86_64.AppImage（可直接运行）
```

### 手动部署（更精确）

```bash
# 查看依赖
ldd dist/myapp | grep Qt

# 复制依赖
export QT_DIR=/usr/lib/x86_64-linux-gnu/qt6
cp $QT_DIR/lib/libQt6Core.so.6 dist/lib/
cp $QT_DIR/lib/libQt6Widgets.so.6 dist/lib/
cp $QT_DIR/plugins/platforms/libqxcb.so dist/plugins/platforms/

# 设置 rpath（查找路径）
patchelf --set-rpath &apos;$ORIGIN/lib&apos; dist/myapp

# 启动脚本
cat &gt; dist/run.sh &lt;&lt; &apos;EOF&apos;
#!/bin/bash
DIR=&quot;$(cd &quot;$(dirname &quot;$0&quot;)&quot; &amp;&amp; pwd)&quot;
export QT_PLUGIN_PATH=&quot;$DIR/plugins&quot;
&quot;$DIR/myapp&quot;
EOF
chmod +x dist/run.sh
```

## macOS — macdeployqt

```bash
# 部署 + 签名
macdeployqt MyApp.app -dmg

# 或手动
macdeployqt MyApp.app
# 生成 MyApp.dmg（分发包）
```

## CMake 自动部署

```cmake
# CMakeLists.txt 末尾
install(TARGETS myapp DESTINATION .)

# CPack 打包
set(CPACK_GENERATOR &quot;ZIP&quot;)
set(CPACK_PACKAGE_NAME &quot;MyApp&quot;)
set(CPACK_PACKAGE_VERSION &quot;1.0.0&quot;)
include(CPack)
```

```bash
cmake --build build
cd build &amp;&amp; cpack  # 生成 MyApp-1.0.0.zip
```

## 发布前检查清单

```
☐ Release 模式编译（不开 Debug）
☐ windeployqt / linuxdeployqt 执行过
☐ 程序在干净环境/虚拟机测试过（没有装 Qt 的机器）
☐ OpenSSL DLL 已包含（如果用了 HTTPS）
☐ 数据库驱动已包含（如 qsqlite.dll）
☐ 图片/翻译/配置文件路径正确（相对路径）
☐ 图标 + 版本信息已设置（Windows .rc 文件）
☐ 没有硬编码的绝对路径
☐ 没有依赖 Visual Studio 调试运行时（/MD 而非 /MDd）

# Windows 额外检查
☐ MSVC 运行时已静态链接或附带安装包
☐ 管理员权限是否必要（如不需要，关闭 requestedExecutionLevel）

# Linux 额外检查
☐ AppImage / deb / flatpak 测试通过
☐ libc 版本兼容（在旧系统上测试）
```

## 最小化体积

```bash
# ① 用 UPX 压缩（慎用，可能被杀软误报）
upx --best myapp.exe  # 5MB → 2MB

# ② 删除不用的 Qt 模块
windeployqt --no-plugins-then --no-translations myapp.exe

# ③ 删除不需要的插件
# 只留 platforms/ + 实际用到的 imageformats/styles
```</content:encoded></item><item><title>VS Code 开发环境配置</title><link>https://kiyose.wiki/notes/vscode/</link><guid isPermaLink="true">https://kiyose.wiki/notes/vscode/</guid><description>C++/Qt 开发必备插件、settings.json 模板、tasks/launch 配置、快捷键与多平台设置同步</description><pubDate>Sun, 08 Sep 2024 00:00:00 GMT</pubDate><content:encoded>## 必装插件

| 插件 | 用途 | ID |
|------|------|-----|
| C/C++ | 语言支持 + IntelliSense | `ms-vscode.cpptools` |
| CMake Tools | CMake 集成 | `ms-vscode.cmake-tools` |
| CMake | 语法高亮 | `twxs.cmake` |
| clangd | 替代 C++ 插件（更快） | `llvm-vs-code-extensions.vscode-clangd` |
| Qt tools | Qt ui/qrc 支持 | `tonka3000.qtvsctools` |
| GitLens | Git 超级增强 | `eamodio.gitlens` |
| Error Lens | 行内显示错误 | `usernamehw.errorlens` |
| Even Better TOML | .toml 支持 | `tamasfe.even-better-toml` |

## settings.json 模板

```json
{
  // ── 编辑器 ──
  &quot;editor.fontSize&quot;: 14,
  &quot;editor.fontFamily&quot;: &quot;&apos;Cascadia Code&apos;, &apos;JetBrains Mono&apos;, Consolas, monospace&quot;,
  &quot;editor.fontLigatures&quot;: true,
  &quot;editor.tabSize&quot;: 4,
  &quot;editor.insertSpaces&quot;: true,
  &quot;editor.rulers&quot;: [100],
  &quot;editor.renderWhitespace&quot;: &quot;boundary&quot;,
  &quot;editor.minimap.enabled&quot;: false,
  &quot;editor.bracketPairColorization.enabled&quot;: true,

  // ── 文件 ──
  &quot;files.autoSave&quot;: &quot;onFocusChange&quot;,
  &quot;files.exclude&quot;: {
    &quot;**/build&quot;: true,
    &quot;**/.git&quot;: true
  },

  // ── C++ ──
  &quot;[cpp]&quot;: {
    &quot;editor.defaultFormatter&quot;: &quot;llvm-vs-code-extensions.vscode-clangd&quot;
  },
  &quot;C_Cpp.intelliSenseEngine&quot;: &quot;disabled&quot;,  // 用 clangd 时关掉
  &quot;clangd.arguments&quot;: [
    &quot;--background-index&quot;,
    &quot;--compile-commands-dir=build&quot;,
    &quot;-j=4&quot;
  ],

  // ── CMake ──
  &quot;cmake.configureOnOpen&quot;: false,
  &quot;cmake.buildDirectory&quot;: &quot;${workspaceFolder}/build&quot;,
  &quot;cmake.generator&quot;: &quot;Ninja&quot;,

  // ── 终端 ──
  &quot;terminal.integrated.fontSize&quot;: 13,
  &quot;terminal.integrated.defaultProfile.windows&quot;: &quot;PowerShell&quot;
}
```

## tasks.json — 构建任务

```json
{
  &quot;version&quot;: &quot;2.0.0&quot;,
  &quot;tasks&quot;: [
    {
      &quot;label&quot;: &quot;cmake configure&quot;,
      &quot;type&quot;: &quot;shell&quot;,
      &quot;command&quot;: &quot;cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug&quot;,
      &quot;problemMatcher&quot;: []
    },
    {
      &quot;label&quot;: &quot;cmake build&quot;,
      &quot;type&quot;: &quot;shell&quot;,
      &quot;command&quot;: &quot;cmake --build build -j8&quot;,
      &quot;group&quot;: { &quot;kind&quot;: &quot;build&quot;, &quot;isDefault&quot;: true },
      &quot;dependsOn&quot;: [&quot;cmake configure&quot;]
    },
    {
      &quot;label&quot;: &quot;run&quot;,
      &quot;type&quot;: &quot;shell&quot;,
      &quot;command&quot;: &quot;${workspaceFolder}/build/myapp&quot;,
      &quot;dependsOn&quot;: [&quot;cmake build&quot;]
    }
  ]
}
```

## launch.json — 调试配置

```json
{
  &quot;version&quot;: &quot;0.2.0&quot;,
  &quot;configurations&quot;: [
    {
      &quot;name&quot;: &quot;Debug MyApp&quot;,
      &quot;type&quot;: &quot;cppdbg&quot;,
      &quot;request&quot;: &quot;launch&quot;,
      &quot;program&quot;: &quot;${workspaceFolder}/build/myapp&quot;,
      &quot;args&quot;: [],
      &quot;cwd&quot;: &quot;${workspaceFolder}&quot;,
      &quot;environment&quot;: [],
      &quot;externalConsole&quot;: false,
      &quot;MIMode&quot;: &quot;gdb&quot;,
      &quot;miDebuggerPath&quot;: &quot;gdb&quot;,
      &quot;setupCommands&quot;: [
        {
          &quot;description&quot;: &quot;Enable pretty-printing&quot;,
          &quot;text&quot;: &quot;-enable-pretty-printing&quot;,
          &quot;ignoreFailures&quot;: true
        }
      ],
      &quot;preLaunchTask&quot;: &quot;cmake build&quot;
    }
  ]
}
```

## 快捷键

| 操作 | 快捷键 |
|------|--------|
| 命令面板 | `Ctrl+Shift+P` |
| 快速打开文件 | `Ctrl+P` |
| 全局搜索 | `Ctrl+Shift+F` |
| 跳转到定义 | `F12` |
| 查看引用 | `Shift+F12` |
| 重命名符号 | `F2` |
| 格式化文档 | `Shift+Alt+F` |
| 切换终端 | `Ctrl+`` |
| 多光标 | `Alt+Click` |
| 列选择 | `Shift+Alt+拖拽` |
| 折叠/展开代码块 | `Ctrl+Shift+[` / `]` |

## clangd 替代 C++ 插件（推荐）

```bash
# 安装 clangd
sudo apt install clangd-17

# VS Code 设置:
# 1. 禁用 C_Cpp IntelliSense
# 2. 安装 clangd 插件
# 3. 确认 build/ 下有 compile_commands.json
#    (CMake: set(CMAKE_EXPORT_COMPILE_COMMANDS ON))

# clangd 优势:
# - 索引速度比 C++ 插件快 3-5 倍
# - InlayHints（自动显示参数名/类型推导结果）
# - 更准确的补全和诊断
```

## 设置同步

```bash
# 方式 1: VS Code 内置（需要 GitHub/MS 账号）
# Settings Sync → 登录 → 自动同步

# 方式 2: 手动备份
cp ~/.config/Code/User/settings.json ~/dotfiles/
cp ~/.config/Code/User/keybindings.json ~/dotfiles/

# 方式 3: 用 Git 管理 dotfiles
git init ~/dotfiles
# 把 settings.json 软链到 ~/dotfiles/vscode/
```

## Qt 项目配置

```json
// .vscode/c_cpp_properties.json（用 C++ 插件时）
{
  &quot;configurations&quot;: [{
    &quot;name&quot;: &quot;Qt&quot;,
    &quot;includePath&quot;: [
      &quot;${workspaceFolder}/**&quot;,
      &quot;/usr/include/x86_64-linux-gnu/qt6&quot;,
      &quot;/usr/include/x86_64-linux-gnu/qt6/QtCore&quot;,
      &quot;/usr/include/x86_64-linux-gnu/qt6/QtWidgets&quot;
    ],
    &quot;defines&quot;: [],
    &quot;compilerPath&quot;: &quot;/usr/bin/g++&quot;,
    &quot;cStandard&quot;: &quot;c17&quot;,
    &quot;cppStandard&quot;: &quot;c++17&quot;,
    &quot;compileCommands&quot;: &quot;${workspaceFolder}/build/compile_commands.json&quot;
  }]
}
// ⚠️ 用 clangd 时不需此文件 — clangd 直接读 compile_commands.json
```</content:encoded></item><item><title>Python 装饰器</title><link>https://kiyose.wiki/notes/decorator/</link><guid isPermaLink="true">https://kiyose.wiki/notes/decorator/</guid><description>闭包、装饰器原理、带参数装饰器、functools.wraps 与实战场景</description><pubDate>Thu, 05 Sep 2024 00:00:00 GMT</pubDate><content:encoded>## 函数是一等公民

```python
def hello():
    return &quot;Hello!&quot;

# 函数可以赋值给变量
greet = hello
print(greet())          # Hello!

# 函数可以作为参数
def call_twice(fn):
    fn()
    fn()

# 函数可以定义在函数内部
def outer():
    def inner():
        return &quot;inner&quot;
    return inner()      # 返回 inner 的调用结果
```

## 闭包 → 装饰器

```python
# 闭包 = 函数 + 捕获的外部变量
def make_multiplier(factor):
    def multiply(x):
        return x * factor   # factor 被&quot;捕获&quot;
    return multiply          # 返回内部函数

double = make_multiplier(2)
print(double(5))             # 10
```

装饰器就是语法糖：

```python
# 下面两段等价：
@decorator
def foo(): ...

def foo(): ...
foo = decorator(foo)
```

## 基础装饰器

```python
import time
from functools import wraps

def timer(func):
    @wraps(func)             # 保留原函数的 __name__ 和 __doc__
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f&quot;{func.__name__} took {elapsed:.4f}s&quot;)
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(0.5)
    return &quot;done&quot;

slow_function()
# 输出: slow_function took 0.5001s
```

**`@wraps` 不可省略** — 不加的话 `slow_function.__name__` 会变成 `&quot;wrapper&quot;`。

## 带参数的装饰器

```python
def repeat(n):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print(&quot;Hello!&quot;)

say_hello()  # 打印 3 次
# 等价于: say_hello = repeat(3)(say_hello)
```

## 实战场景

### 场景 1：缓存（memoize）

```python
def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fib(n):
    if n &lt; 2: return n
    return fib(n-1) + fib(n-2)

print(fib(100))  # 毫秒级
```

### 场景 2：权限检查

```python
def require_auth(role):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get(&quot;role&quot;) != role:
                raise PermissionError(f&quot;Need {role}&quot;)
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_auth(&quot;admin&quot;)
def delete_user(admin, target_id):
    print(f&quot;Deleted {target_id}&quot;)
```

### 场景 3：重试

```python
def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f&quot;Retry {attempt+1}/{max_attempts}: {e}&quot;)
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_api_call():
    ...
```

### 场景 4：日志

```python
def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f&quot;CALL {func.__name__}({args}, {kwargs})&quot;)
        result = func(*args, **kwargs)
        print(f&quot;RETURN {func.__name__} → {result!r}&quot;)
        return result
    return wrapper
```

## 类装饰器

```python
class Singleton:
    def __init__(self, cls):
        self.cls = cls
        self.instance = None

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = self.cls(*args, **kwargs)
        return self.instance

@Singleton
class Database:
    def __init__(self):
        print(&quot;Init DB&quot;)

db1 = Database()   # Init DB (只打印一次)
db2 = Database()   # 返回同一个实例
print(db1 is db2)  # True
```</content:encoded></item><item><title>Qt QML 入门</title><link>https://kiyose.wiki/notes/qml/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qml/</guid><description>QML 基础语法、属性绑定、信号处理器、与 C++ 交互、常用元素速查</description><pubDate>Tue, 20 Aug 2024 00:00:00 GMT</pubDate><content:encoded>## 最小 QML 应用

```cpp
// main.cpp — C++ 启动 QML
#include &lt;QGuiApplication&gt;
#include &lt;QQmlApplicationEngine&gt;

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    engine.load(QUrl(&quot;qrc:/main.qml&quot;));
    return app.exec();
}
```

```qml
// main.qml
import QtQuick

Window {
    width: 400; height: 300
    visible: true
    title: &quot;Hello QML&quot;

    Text {
        text: &quot;Hello World&quot;
        anchors.centerIn: parent
        font.pixelSize: 24
    }
}
```

## 基础元素

```qml
// 布局容器
Rectangle { width: 100; height: 100; color: &quot;lightblue&quot;; radius: 8 }
Item { }                          // 不可见容器
Row { spacing: 10; /* 子元素水平排列 */ }
Column { spacing: 5; /* 垂直排列 */ }
Grid { columns: 3; spacing: 10 }

// 文本与输入
Text { text: &quot;Label&quot;; font.pixelSize: 16 }
TextInput { id: input; width: 200; text: &quot;editable&quot; }
TextEdit { width: 300; height: 200; text: &quot;multiline&quot; }

// 按钮
Button { text: &quot;Click&quot;; onClicked: console.log(&quot;clicked&quot;) }

// 图片
Image { source: &quot;qrc:/icon.png&quot;; width: 48; height: 48 }
```

## 属性绑定（核心概念）

```qml
Rectangle {
    width: 200; height: 100
    // 属性绑定 — 自动更新
    color: mouseArea.containsMouse ? &quot;red&quot; : &quot;blue&quot;

    MouseArea {
        id: mouseArea
        anchors.fill: parent
        hoverEnabled: true
    }

    Text {
        // 绑定到表达式
        text: &quot;Width: &quot; + parent.width
        anchors.centerIn: parent
    }
}
// 绑定是声明式的：color 自动跟随 mouseArea.containsMouse
```

## 信号与槽

```qml
Button {
    id: myButton
    text: &quot;Click&quot;
    onClicked: {
        console.log(&quot;clicked&quot;)
        label.text = &quot;Button was clicked&quot;
    }
}

// 属性变化信号
Item {
    property int count: 0
    onCountChanged: console.log(&quot;count:&quot;, count)
}

// 连接外部信号
Connections {
    target: cppObject        // C++ 暴露的对象
    function onDataReady(data) {
        chart.update(data)
    }
}
```

## C++ ↔ QML 交互

```cpp
// ① 注册 C++ 类型到 QML
class DataProvider : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString status READ status NOTIFY statusChanged)
public:
    QString status() const { return m_status; }
signals:
    void statusChanged();
    void dataReady(const QVariantList &amp;data);
private:
    QString m_status;
};

// main.cpp 中注册
qmlRegisterType&lt;DataProvider&gt;(&quot;myapp&quot;, 1, 0, &quot;DataProvider&quot;);
```

```qml
// QML 中使用
import myapp 1.0

DataProvider {
    id: provider
    onDataReady: function(data) { /* ... */ }
}
```

```cpp
// ② 从 C++ 访问 QML 对象
QObject *root = engine.rootObjects().first();
QObject *btn = root-&gt;findChild&lt;QObject *&gt;(&quot;myButton&quot;);
QMetaObject::invokeMethod(btn, &quot;clicked&quot;);
```

## 布局锚定

```qml
Rectangle {
    id: container
    width: 400; height: 300

    Rectangle {
        anchors {                // 用 anchors 而不是绝对坐标
            left: parent.left;   leftMargin: 10
            right: parent.right; rightMargin: 10
            top: parent.top;     topMargin: 20
            bottom: parent.bottom; bottomMargin: 20
        }
    }

    // 居中
    Text { anchors.centerIn: parent }

    // 填充父容器
    Item { anchors.fill: parent }
}
```

## 常用模式

```qml
// 列表（Repeater + model）
Column {
    Repeater {
        model: [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Carol&quot;]
        delegate: Text { text: modelData; font.pixelSize: 18 }
    }
}

// 状态切换
Rectangle {
    property bool active: false
    color: active ? &quot;green&quot; : &quot;gray&quot;

    states: [
        State {
            name: &quot;active&quot;; when: active
            PropertyChanges { target: indicator; scale: 1.2 }
        }
    ]
    transitions: Transition {
        NumberAnimation { property: &quot;scale&quot;; duration: 200 }
    }
}

// 动画
Rectangle {
    id: box
    Behavior on x { NumberAnimation { duration: 300 } }
    Behavior on opacity { NumberAnimation { duration: 200 } }
}
```

## QML vs QWidget 选择

| 场景 | QML | QWidget |
|------|:--:|:--:|
| 移动/嵌入式 | ✅ | ❌ |
| 传统桌面（表格/树/菜单） | ⚠️ | ✅ |
| 动画/特效 | ✅ | 需自绘 |
| 学习曲线 | 中 | 低 |
| 性能（大量控件） | 中 | 高 |</content:encoded></item><item><title>Qt 样式表 QSS</title><link>https://kiyose.wiki/notes/qss/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qss/</guid><description>Qt 层叠样式表语法、选择器、伪状态与主题切换实战</description><pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate><content:encoded>## 基本语法

QSS 语法和 CSS 几乎一样：

```css
选择器 {
    属性: 值;
    属性: 值;
}

/* 支持的选择器 */
QPushButton              /* 类选择器 */
QPushButton#okBtn        /* ID 选择器 */
QPushButton[flat=&quot;true&quot;] /* 属性选择器 */
QWidget &gt; QPushButton    /* 子控件 */
QWidget QPushButton      /* 后代 */
```

## 设置方式

```cpp
// 方式 1：全局样式（作用于整个应用）
qApp-&gt;setStyleSheet(
    &quot;QPushButton { background: #4CAF50; color: white; }&quot;
);

// 方式 2：单个控件
button-&gt;setStyleSheet(
    &quot;QPushButton { background: red; }&quot;
);

// 方式 3：从文件加载
QFile f(&quot;:/style.qss&quot;);
f.open(QFile::ReadOnly);
qApp-&gt;setStyleSheet(f.readAll());
```

## 常用控件样式

### QPushButton

```css
QPushButton {
    background: #4CAF50;
    color: white;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    font-size: 14px;
}

QPushButton:hover {
    background: #45a049;
}

QPushButton:pressed {
    background: #3d8b40;
}

QPushButton:disabled {
    background: #cccccc;
    color: #888888;
}
```

### QLineEdit

```css
QLineEdit {
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 6px 10px;
    font-size: 14px;
}

QLineEdit:focus {
    border-color: #4CAF50;
    /* 去掉默认的蓝色焦点框 */
    outline: none;
}
```

### QTableView / QTableWidget

```css
QTableView {
    border: 1px solid #e0e0e0;
    gridline-color: #f0f0f0;
    selection-background-color: #e8f5e9;
    alternate-background-color: #fafafa;
}

QTableView::item {
    padding: 4px 8px;
}

QHeaderView::section {
    background: #f5f5f5;
    border: none;
    border-bottom: 2px solid #e0e0e0;
    padding: 6px 8px;
    font-weight: bold;
}
```

### QScrollBar

```css
QScrollBar:vertical {
    width: 8px;
    background: transparent;
}
QScrollBar::handle:vertical {
    background: #c0c0c0;
    border-radius: 4px;
    min-height: 30px;
}
QScrollBar::handle:vertical:hover {
    background: #a0a0a0;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
    height: 0;
}
```

## 伪状态

```css
:hover          /* 鼠标悬停 */
:pressed        /* 按下 */
:checked        /* 选中（CheckBox/RadioButton） */
:disabled       /* 禁用 */
:enabled        /* 启用 */
:focus          /* 获得焦点 */
:selected       /* 被选中（列表/表格项） */
:read-only      /* 只读 */
:editable       /* 可编辑 */
:indeterminate  /* 半选状态 */
```

## 子控件

```css
/* QComboBox 下拉箭头 */
QComboBox::drop-down {
    subcontrol-origin: padding;
    subcontrol-position: top right;
    width: 20px;
}
QComboBox::down-arrow {
    image: url(:/arrow-down.png);
}

/* QSpinBox 上下按钮 */
QSpinBox::up-button { subcontrol-position: top right; }
QSpinBox::down-button { subcontrol-position: bottom right; }

/* QTabWidget 标签 */
QTabBar::tab { padding: 8px 16px; }
QTabBar::tab:selected { border-bottom: 2px solid #4CAF50; }
```

## 暗色主题实战

```cpp
void setDarkTheme() {
    qApp-&gt;setStyleSheet(R&quot;(
        * {
            color: #c9d1d9;
            background: #0d1117;
        }
        QMainWindow, QDialog {
            background: #0d1117;
        }
        QPushButton {
            background: #21262d;
            border: 1px solid #30363d;
            padding: 6px 12px;
            border-radius: 6px;
        }
        QPushButton:hover {
            background: #30363d;
            border-color: #58a6ff;
        }
        QLineEdit, QTextEdit, QPlainTextEdit {
            background: #161b22;
            border: 1px solid #30363d;
            border-radius: 6px;
            padding: 6px 10px;
            color: #c9d1d9;
        }
        QLineEdit:focus {
            border-color: #58a6ff;
        }
        QTableView {
            background: #161b22;
            border: 1px solid #30363d;
            gridline-color: #21262d;
            selection-background-color: #1f3a5f;
        }
        QHeaderView::section {
            background: #21262d;
            border-bottom: 1px solid #30363d;
            color: #c9d1d9;
        }
        QScrollBar:vertical {
            background: #0d1117;
            width: 8px;
        }
        QScrollBar::handle:vertical {
            background: #30363d;
            border-radius: 4px;
        }
        QMenuBar {
            background: #161b22;
            border-bottom: 1px solid #30363d;
        }
        QStatusBar {
            background: #161b22;
            border-top: 1px solid #30363d;
        }
    )&quot;);
}

// 在 QApplication 构造后调用
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    setDarkTheme();
    // ...
}
```

## QSS vs QPalette

| | QSS | QPalette |
|------|-----|----------|
| 灵活度 | 高（CSS 语法） | 低（固定角色） |
| 学习曲线 | 低（熟悉 CSS 即可） | 中 |
| 性能 | 略低（解析样式表） | 高 |
| 覆盖范围 | 大部分控件 | 所有控件 |
| 推荐场景 | **日常开发首选** | 全局颜色调色 |

## 调试技巧

```cpp
// 1. 查看控件实际样式
qDebug() &lt;&lt; button-&gt;styleSheet();

// 2. 查看应用了哪些样式规则
// 运行：QT_DEBUG_PLUGINS=1 ./myapp

// 3. 常见问题
// - 样式不生效 → 检查选择器优先级
// - 子控件不继承 → 检查父子关系
// - 某些属性被忽略 → 该控件不支持此属性
//   (例如 QPushButton 不支持 color 继承，需直接设置)
```</content:encoded></item><item><title>Docker 常用命令与 Dockerfile</title><link>https://kiyose.wiki/notes/docker/</link><guid isPermaLink="true">https://kiyose.wiki/notes/docker/</guid><description>Docker 镜像/容器/卷/网络速查、多阶段构建、docker-compose 模板与常见问题</description><pubDate>Mon, 15 Jul 2024 00:00:00 GMT</pubDate><content:encoded>## 镜像管理

```bash
docker pull ubuntu:22.04          # 拉取镜像
docker images                     # 列出本地镜像
docker rmi &lt;image&gt;                # 删除镜像
docker tag src:latest dst:v1.0    # 打标签
docker save -o img.tar myapp:v1   # 导出
docker load -i img.tar            # 导入
```

## 容器操作

```bash
# 运行
docker run -d --name myapp -p 8080:80 myapp:v1
# -d 后台  --name 命名  -p 宿主机:容器

# 常用选项
docker run -it ubuntu bash        # 交互式终端
docker run -v /host/data:/data    # 挂载卷
docker run -e ENV=production      # 环境变量
docker run --restart=always       # 自动重启
docker run --cpus=2 -m 512m       # 限制资源

# 生命周期
docker ps                         # 运行中的容器
docker ps -a                      # 全部容器
docker stop myapp                 # 停止
docker start myapp                # 启动
docker restart myapp              # 重启
docker rm myapp                   # 删除（先 stop）
docker rm -f myapp                # 强制删除

# 调试
docker logs myapp                 # 查看日志
docker logs -f myapp              # 跟随日志
docker exec -it myapp bash        # 进入容器
docker inspect myapp              # 查看配置详情
docker stats                      # 资源占用
```

## Dockerfile 模板

```dockerfile
# ── 构建阶段（多阶段构建）──
FROM gcc:13 AS builder
WORKDIR /app
COPY . .
RUN cmake -B build -DCMAKE_BUILD_TYPE=Release \
    &amp;&amp; cmake --build build -j$(nproc)

# ── 运行阶段 ──
FROM ubuntu:22.04
RUN apt update &amp;&amp; apt install -y \
    libqt6widgets6 \
    libqt6sql6-sqlite \
    &amp;&amp; rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/build/myapp /usr/local/bin/
CMD [&quot;myapp&quot;]
```

### Dockerfile 指令速查

| 指令 | 作用 |
|------|------|
| `FROM` | 基础镜像 |
| `WORKDIR` | 工作目录 |
| `COPY` / `ADD` | 复制文件（ADD 可解压 tar） |
| `RUN` | 构建时执行命令 |
| `ENV` | 环境变量 |
| `EXPOSE` | 声明端口 |
| `CMD` | 容器启动时默认命令 |
| `ENTRYPOINT` | 容器入口（不被 docker run 覆盖） |

## 卷与数据持久化

```bash
# 命名卷（推荐）
docker volume create app-data
docker run -v app-data:/data myapp

# 绑定挂载（开发时用）
docker run -v $(pwd):/app myapp

# tmpfs（内存，不持久化）
docker run --tmpfs /tmp myapp
```

## 网络

```bash
# 创建自定义网络（容器间通过名称互访）
docker network create mynet
docker run --network mynet --name db mysql
docker run --network mynet --name app myapp  # app 可以通过 db:3306 连接

# 查看网络
docker network ls
docker network inspect mynet
```

## docker-compose 模板

```yaml
version: &apos;3.8&apos;
services:
  app:
    build: .
    ports:
      - &quot;8080:80&quot;
    environment:
      - DB_HOST=db
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: myapp
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: [&quot;CMD&quot;, &quot;mysqladmin&quot;, &quot;ping&quot;, &quot;-h&quot;, &quot;localhost&quot;]
      interval: 5s
      timeout: 3s
      retries: 3

volumes:
  db-data:
```

```bash
docker-compose up -d         # 启动
docker-compose down          # 停止 + 删除
docker-compose logs -f app   # 查看日志
docker-compose exec app bash # 进入服务容器
docker-compose build         # 重新构建
```

## 多阶段构建优势

| | 单阶段 | 多阶段 |
|------|:--:|:--:|
| 最终镜像 | ~1.2GB (含编译工具) | ~50MB |
| 安全 | 源码 + 编译工具残留 | 只含运行时 |
| 层缓存 | 改一行代码全部重来 | COPY 之前层全部缓存 |

## 常见问题

```bash
# 容器内时区不对
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime

# 磁盘空间不足
docker system prune -a     # 清理未使用的镜像/容器/卷/网络
docker builder prune       # 清理构建缓存

# 镜像太大
# ① 多阶段构建 ② FROM alpine ③ 合并 RUN 命令减少层
# ④ .dockerignore 排除 node_modules/.git
```</content:encoded></item><item><title>Qt 网络编程</title><link>https://kiyose.wiki/notes/qtnetwork/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qtnetwork/</guid><description>QTcpSocket/QUdpSocket/QNetworkAccessManager 实战与协议处理</description><pubDate>Mon, 08 Jul 2024 00:00:00 GMT</pubDate><content:encoded>## 三种网络 API 选择

| 类 | 协议 | 层级 | 场景 |
|---|------|------|------|
| `QTcpSocket` | TCP | 传输层 | 自定义协议、长连接 |
| `QUdpSocket` | UDP | 传输层 | 实时音视频、广播 |
| `QNetworkAccessManager` | HTTP/HTTPS | 应用层 | REST API、下载 |

## QTcpSocket — TCP 客户端

```cpp
#include &lt;QTcpSocket&gt;

class TcpClient : public QObject {
    QTcpSocket *m_socket;
public:
    TcpClient() {
        m_socket = new QTcpSocket(this);

        connect(m_socket, &amp;QTcpSocket::connected, []() {
            qDebug() &lt;&lt; &quot;Connected&quot;;
        });
        connect(m_socket, &amp;QTcpSocket::readyRead, [this]() {
            QByteArray data = m_socket-&gt;readAll();
            // 处理数据
        });
        connect(m_socket, &amp;QTcpSocket::disconnected, []() {
            qDebug() &lt;&lt; &quot;Disconnected&quot;;
        });
        // 错误处理（QOverload 解决重载歧义）
        connect(m_socket,
            QOverload&lt;QAbstractSocket::SocketError&gt;::of(&amp;QAbstractSocket::error),
            [](QAbstractSocket::SocketError err) {
                qWarning() &lt;&lt; &quot;Error:&quot; &lt;&lt; err;
            });
    }

    void connectToHost(const QString &amp;host, quint16 port) {
        m_socket-&gt;connectToHost(host, port);
    }

    void send(const QByteArray &amp;data) {
        m_socket-&gt;write(data);
    }
};
```

## QTcpServer — TCP 服务端

```cpp
class TcpServer : public QObject {
    QTcpServer *m_server;
    QList&lt;QTcpSocket *&gt; m_clients;
public:
    TcpServer() {
        m_server = new QTcpServer(this);
        connect(m_server, &amp;QTcpServer::newConnection, [this]() {
            while (QTcpSocket *client = m_server-&gt;nextPendingConnection()) {
                m_clients.append(client);
                connect(client, &amp;QTcpSocket::readyRead, [this, client]() {
                    QByteArray data = client-&gt;readAll();
                    // 处理 + 回复
                    client-&gt;write(&quot;ACK&quot;);
                });
                connect(client, &amp;QTcpSocket::disconnected, [this, client]() {
                    m_clients.removeOne(client);
                    client-&gt;deleteLater();
                });
            }
        });
    }

    bool listen(quint16 port) {
        return m_server-&gt;listen(QHostAddress::Any, port);
    }
};
```

### TCP 粘包/拆包处理

```cpp
// TCP 是流式协议，需要自己定界
// 方案 1: 固定长度
// 方案 2: 分隔符（如 \r\n）
// 方案 3: 帧头 + 长度（PulseQt 的做法）

class FrameParser {
    QByteArray m_buffer;
public:
    void feed(const QByteArray &amp;data) {
        m_buffer.append(data);
        while (m_buffer.size() &gt;= 4) {   // 帧头 4 字节
            quint32 len = *reinterpret_cast&lt;const quint32 *&gt;(m_buffer.constData());
            if (m_buffer.size() &lt; 4 + len) break;  // 不够一帧
            QByteArray frame = m_buffer.mid(4, len);
            m_buffer.remove(0, 4 + len);
            emit frameReady(frame);
        }
    }
};
```

## QUdpSocket — UDP

```cpp
class UdpPeer : public QObject {
    QUdpSocket *m_socket;
public:
    UdpPeer() {
        m_socket = new QUdpSocket(this);
        connect(m_socket, &amp;QUdpSocket::readyRead, [this]() {
            while (m_socket-&gt;hasPendingDatagrams()) {
                QByteArray datagram;
                datagram.resize(m_socket-&gt;pendingDatagramSize());
                QHostAddress sender;
                quint16 senderPort;
                m_socket-&gt;readDatagram(datagram.data(), datagram.size(),
                                       &amp;sender, &amp;senderPort);
                // 处理...
            }
        });
    }

    // 绑定端口接收
    bool bind(quint16 port) {
        return m_socket-&gt;bind(QHostAddress::Any, port);
    }

    // 发送
    void send(const QByteArray &amp;data, const QString &amp;host, quint16 port) {
        m_socket-&gt;writeDatagram(data, QHostAddress(host), port);
    }
};
```

## QNetworkAccessManager — HTTP

```cpp
#include &lt;QNetworkAccessManager&gt;
#include &lt;QNetworkReply&gt;

class HttpClient : public QObject {
    QNetworkAccessManager *m_mgr;
public:
    HttpClient() { m_mgr = new QNetworkAccessManager(this); }

    void get(const QString &amp;url) {
        QNetworkRequest request(QUrl(url));
        QNetworkReply *reply = m_mgr-&gt;get(request);
        connect(reply, &amp;QNetworkReply::finished, [reply]() {
            if (reply-&gt;error() == QNetworkReply::NoError) {
                QByteArray data = reply-&gt;readAll();
                // 处理 data
            } else {
                qWarning() &lt;&lt; reply-&gt;errorString();
            }
            reply-&gt;deleteLater();  // ⚠️ 必须释放
        });
    }

    void post(const QString &amp;url, const QByteArray &amp;body) {
        QNetworkRequest request(QUrl(url));
        request.setHeader(QNetworkRequest::ContentTypeHeader,
                          &quot;application/json&quot;);
        QNetworkReply *reply = m_mgr-&gt;post(request, body);
        // ...
    }
};
```

## 注意事项

```cpp
// ⚠️ QTcpSocket 是异步的 — write() 不是立即发送
socket-&gt;write(data);  // 数据进入发送缓冲区，不阻塞

// ⚠️ connected 信号不代表数据可以立即发送
//    连接建立成功就发射，TCP 握手完成后就能写

// ⚠️ QNetworkReply 必须 deleteLater()
//    在 finished 信号里调 reply-&gt;deleteLater()

// ⚠️ 不要在 GUI 线程做同步网络操作
//    用异步 API，或把同步操作放到 Worker 线程

// ⚠️ 跨线程使用 socket 时注意线程亲和性
//    socket 必须在它使用的线程中创建
```</content:encoded></item><item><title>Python 基础语法</title><link>https://kiyose.wiki/notes/pythonbasics/</link><guid isPermaLink="true">https://kiyose.wiki/notes/pythonbasics/</guid><description>Python 核心语法速览：数据类型、控制流、函数、类与模块</description><pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate><content:encoded>## 变量与数据类型

```python
# 动态类型，不需要声明
name = &quot;Alice&quot;
age = 25
height = 1.68
is_student = True

# 类型检查
print(type(name))    # &lt;class &apos;str&apos;&gt;
print(isinstance(age, int))  # True

# 基本类型
int_val = 42
float_val = 3.14
str_val = &quot;hello&quot;
bool_val = False
none_val = None          # 空值

# 多变量赋值
a, b = 1, 2
x = y = z = 0            # 三个都是 0

# f-string（Python 3.6+）
print(f&quot;{name} is {age} years old&quot;)
```

## 容器类型

```python
# 列表 list — 可变有序
fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;]
fruits.append(&quot;orange&quot;)
fruits.insert(1, &quot;grape&quot;)
fruits.remove(&quot;banana&quot;)
print(fruits[0])          # apple
print(fruits[-1])         # 最后一个
print(fruits[1:3])        # 切片 [1, 3)

# 列表推导式
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]

# 元组 tuple — 不可变
point = (3, 4)
x, y = point              # 解包
single = (1,)             # 单元素元组，逗号不能丢

# 字典 dict — 键值对
user = {&quot;name&quot;: &quot;Bob&quot;, &quot;age&quot;: 30}
user[&quot;email&quot;] = &quot;bob@example.com&quot;
print(user.get(&quot;phone&quot;, &quot;N/A&quot;))  # 安全取值

# 字典推导式
squares_map = {x: x**2 for x in range(5)}

# 集合 set — 不重复无序
tags = {&quot;python&quot;, &quot;coding&quot;, &quot;python&quot;}  # {&quot;python&quot;, &quot;coding&quot;}
tags.add(&quot;tutorial&quot;)
print(&quot;python&quot; in tags)   # True
```

## 控制流

```python
# if-elif-else
score = 85
if score &gt;= 90:
    grade = &quot;A&quot;
elif score &gt;= 80:
    grade = &quot;B&quot;
else:
    grade = &quot;C&quot;

# for 循环
for i in range(5):           # 0..4
    print(i)

for fruit in fruits:         # 遍历可迭代对象
    print(fruit)

for i, fruit in enumerate(fruits):  # 带索引
    print(f&quot;{i}: {fruit}&quot;)

for k, v in user.items():    # 字典遍历
    print(f&quot;{k} = {v}&quot;)

# while
count = 0
while count &lt; 5:
    print(count)
    count += 1

# break / continue
for x in range(10):
    if x == 3:
        continue    # 跳过 3
    if x == 7:
        break       # 到 7 停止
    print(x)        # 输出 0 1 2 4 5 6
```

## 函数

```python
def greet(name, greeting=&quot;Hello&quot;):
    &quot;&quot;&quot;返回问候语&quot;&quot;&quot;          # docstring
    return f&quot;{greeting}, {name}!&quot;

print(greet(&quot;Alice&quot;))               # Hello, Alice!
print(greet(&quot;Bob&quot;, &quot;Hi&quot;))           # Hi, Bob!

# 可变参数
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))          # 10

# 关键字参数
def create_user(**kwargs):
    for k, v in kwargs.items():
        print(f&quot;{k}: {v}&quot;)

create_user(name=&quot;Alice&quot;, age=25)

# Lambda
square = lambda x: x ** 2
sorted(fruits, key=lambda f: len(f))  # 按长度排序
```

## 类

```python
class Animal:
    species = &quot;Unknown&quot;        # 类变量

    def __init__(self, name):  # 构造方法
        self.name = name       # 实例变量

    def speak(self):
        return f&quot;{self.name} makes a sound&quot;

class Dog(Animal):
    species = &quot;Canine&quot;         # 覆盖类变量

    def __init__(self, name, breed):
        super().__init__(name) # 调用父类构造
        self.breed = breed

    def speak(self):
        return f&quot;{self.name} barks!&quot;

dog = Dog(&quot;Rex&quot;, &quot;Labrador&quot;)
print(dog.speak())             # Rex barks!
print(isinstance(dog, Animal)) # True
```

## 模块与导入

```python
# 导入
import math
from datetime import datetime, timedelta
from pathlib import Path
import numpy as np             # 别名

print(math.sqrt(16))           # 4.0

# 文件操作
with open(&quot;file.txt&quot;, &quot;r&quot;) as f:
    content = f.read()

with open(&quot;output.txt&quot;, &quot;w&quot;) as f:
    f.write(&quot;Hello&quot;)

# Pathlib（推荐替代 os.path）
path = Path(&quot;data/config.json&quot;)
print(path.suffix)             # .json
print(path.parent)             # data
text = path.read_text()
```</content:encoded></item><item><title>C++ IO 流 — 文件与字符串流</title><link>https://kiyose.wiki/notes/cppio/</link><guid isPermaLink="true">https://kiyose.wiki/notes/cppio/</guid><description>fstream/stringstream 操作、格式化输出、错误状态处理、二进制读写与 locale</description><pubDate>Wed, 05 Jun 2024 00:00:00 GMT</pubDate><content:encoded>## 三个标准流

```cpp
#include &lt;iostream&gt;   // cin cout cerr clog
#include &lt;fstream&gt;    // ifstream ofstream fstream
#include &lt;sstream&gt;    // istringstream ostringstream stringstream
```

## 文件读写

```cpp
#include &lt;fstream&gt;

// 写文件
std::ofstream out(&quot;data.txt&quot;);
if (!out) { cerr &lt;&lt; &quot;无法打开文件\n&quot;; return; }
out &lt;&lt; &quot;Hello, &quot; &lt;&lt; 42 &lt;&lt; &apos;\n&apos;;
out.close();  // 显式关闭（析构也会关）

// 读文件 — 逐行
std::ifstream in(&quot;data.txt&quot;);
std::string line;
while (std::getline(in, line)) {
    cout &lt;&lt; line &lt;&lt; &apos;\n&apos;;
}

// 读文件 — 逐词
std::string word;
while (in &gt;&gt; word) { /* ... */ }

// 读整个文件到 string
std::ostringstream buf;
buf &lt;&lt; in.rdbuf();
std::string content = buf.str();
```

## 打开模式

```cpp
// ios::in      读
// ios::out     写（覆盖）
// ios::app     追加
// ios::ate     打开后定位到末尾
// ios::binary  二进制模式
// ios::trunc   清空已有内容（out 默认）

// 追加写
std::ofstream log(&quot;app.log&quot;, std::ios::app);
log &lt;&lt; &quot;new entry\n&quot;;

// 二进制写
std::ofstream bin(&quot;data.bin&quot;, std::ios::binary);
int arr[] = {1, 2, 3, 4};
bin.write(reinterpret_cast&lt;const char *&gt;(arr), sizeof(arr));

// 二进制读
std::ifstream binIn(&quot;data.bin&quot;, std::ios::binary);
int buf[4];
binIn.read(reinterpret_cast&lt;char *&gt;(buf), sizeof(buf));
auto bytesRead = binIn.gcount();  // 实际读取字节数
```

## 字符串流

```cpp
// ostringstream — 拼接字符串（效率优于 +）
std::ostringstream oss;
oss &lt;&lt; &quot;ID: &quot; &lt;&lt; id &lt;&lt; &quot;, Name: &quot; &lt;&lt; name;
std::string result = oss.str();

// istringstream — 解析字符串
std::istringstream iss(&quot;123 45.6 hello&quot;);
int a; double b; std::string c;
iss &gt;&gt; a &gt;&gt; b &gt;&gt; c;  // a=123, b=45.6, c=&quot;hello&quot;

// stringstream — 读写
std::stringstream ss;
ss &lt;&lt; 3.14;
double d;
ss &gt;&gt; d;  // 3.14
```

## 格式化控制

```cpp
#include &lt;iomanip&gt;

// 精度
cout &lt;&lt; std::fixed &lt;&lt; std::setprecision(2) &lt;&lt; 3.14159;  // 3.14

// 宽度 + 填充
cout &lt;&lt; std::setw(10) &lt;&lt; std::setfill(&apos;-&apos;) &lt;&lt; 42;       // &quot;--------42&quot;

// 进制
cout &lt;&lt; std::hex &lt;&lt; 255;      // ff
cout &lt;&lt; std::oct &lt;&lt; 8;        // 10
cout &lt;&lt; std::dec &lt;&lt; 0xff;     // 255

// bool
cout &lt;&lt; std::boolalpha &lt;&lt; true;  // &quot;true&quot;（不是 1）

// 保存/恢复状态
auto oldFlags = cout.flags();
cout &lt;&lt; std::hex &lt;&lt; 255;
cout.flags(oldFlags);  // 恢复
```

## 错误状态

```cpp
std::ifstream in(&quot;file.txt&quot;);

if (in.good())    { /* 没有错误 */ }
if (in.eof())     { /* 到达文件尾 */ }
if (in.fail())    { /* 逻辑错误（如格式不匹配） */ }
if (in.bad())     { /* 严重错误（如磁盘损坏） */ }

// 清除错误状态（才能继续操作）
in.clear();

// 异常模式（失败时抛异常）
in.exceptions(std::ios::failbit | std::ios::badbit);
```

## 二进制文件的序列化

```cpp
// 写入结构体
struct Record {
    int32_t id;
    double value;
    char name[32];
};

Record r{1, 3.14, &quot;test&quot;};
std::ofstream out(&quot;record.bin&quot;, std::ios::binary);
out.write(reinterpret_cast&lt;const char *&gt;(&amp;r), sizeof(r));

// ⚠️ 直接写结构体的问题：
// - 字节序（大小端）
// - 对齐填充（不同编译器可能不同）
// - 跨平台不可移植

// ✅ 推荐：逐字段写入或使用序列化库
void writeInt32(std::ostream &amp;os, int32_t val) {
    // 网络字节序（大端）
    val = htonl(val);
    os.write(reinterpret_cast&lt;const char *&gt;(&amp;val), 4);
}
```

## 性能

```cpp
// ❌ endl 每次都 flush → 慢
for (int i = 0; i &lt; 100000; ++i)
    cout &lt;&lt; i &lt;&lt; endl;

// ✅ &apos;\n&apos; 不 flush
for (int i = 0; i &lt; 100000; ++i)
    cout &lt;&lt; i &lt;&lt; &apos;\n&apos;;

// ✅ 解除与 C stdio 的同步 → 加速 3-5x
ios::sync_with_stdio(false);
cin.tie(nullptr);  // 解绑 cin 和 cout
```</content:encoded></item><item><title>C++20 Concepts</title><link>https://kiyose.wiki/notes/concepts/</link><guid isPermaLink="true">https://kiyose.wiki/notes/concepts/</guid><description>概念约束模板参数、requires 子句、标准库 concept 与 C++20 新特性实践</description><pubDate>Mon, 20 May 2024 00:00:00 GMT</pubDate><content:encoded>## 为什么需要 Concepts

```cpp
// ❌ C++17 之前：模板错误信息是灾难
template &lt;typename T&gt;
T add(T a, T b) { return a + b; }

vector&lt;int&gt; v;
add(v, v);  // 编译报错 50 行，核心信息是 &quot;vector 不能 +&quot;

// ✅ C++20：编译期检查 + 清晰的报错
template &lt;typename T&gt;
    requires std::is_arithmetic_v&lt;T&gt;
T add(T a, T b) { return a + b; }

add(v, v);  // 报错 1 行：constraint not satisfied: is_arithmetic_v&lt;vector&lt;int&gt;&gt;
```

## 定义 Concept

```cpp
#include &lt;concepts&gt;

// 方式 1: requires 表达式
template &lt;typename T&gt;
concept Addable = requires(T a, T b) {
    { a + b } -&gt; std::convertible_to&lt;T&gt;;  // a+b 合法且返回值可转为 T
};

// 方式 2: 组合已有 concept
template &lt;typename T&gt;
concept Numeric = std::integral&lt;T&gt; || std::floating_point&lt;T&gt;;

// 方式 3: 类型萃取
template &lt;typename T&gt;
concept Printable = requires(T t, std::ostream &amp;os) {
    { os &lt;&lt; t } -&gt; std::same_as&lt;std::ostream &amp;&gt;;
};

// 使用
template &lt;Addable T&gt;
T sum(const std::vector&lt;T&gt; &amp;v) {
    T total{};
    for (const auto &amp;x : v) total += x;
    return total;
}
```

## requires 子句的四种写法

```cpp
// ① requires 跟在 template 后面（推荐）
template &lt;typename T&gt;
    requires std::integral&lt;T&gt;
T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }

// ② concept 代替 typename
template &lt;std::integral T&gt;
T abs(T x) { return x &lt; 0 ? -x : x; }

// ③ auto 缩写（C++20 简写模板）
auto mul(std::integral auto a, std::integral auto b) { return a * b; }

// ④ 尾置 requires
template &lt;typename T&gt;
T inc(T x) requires std::integral&lt;T&gt; { return x + 1; }
```

## 标准库 Concepts

```cpp
#include &lt;concepts&gt;

// 类型属性
std::integral&lt;T&gt;        // char, int, long ...
std::floating_point&lt;T&gt;  // float, double
std::signed_integral&lt;T&gt;
std::unsigned_integral&lt;T&gt;

// 比较
std::equality_comparable&lt;T&gt;     // 可以 ==
std::totally_ordered&lt;T&gt;         // 可以 &lt; &gt; &lt;= &gt;=

// 可调用
std::invocable&lt;F, Args...&gt;      // F 可以用 Args... 调用
std::predicate&lt;F, Args...&gt;      // F(Args...) 返回 bool

// 复制/移动
std::copyable&lt;T&gt;
std::movable&lt;T&gt;
std::copy_constructible&lt;T&gt;

// 转换
std::convertible_to&lt;From, To&gt;
std::same_as&lt;T, U&gt;              // T 就是 U
std::derived_from&lt;Derived, Base&gt;
```

## 实战示例

### 约束排序函数

```cpp
template &lt;std::ranges::range R&gt;
    requires std::sortable&lt;std::ranges::iterator_t&lt;R&gt;&gt;
void mySort(R &amp;range) {
    std::ranges::sort(range);
}

vector&lt;int&gt; v{3, 1, 4};
mySort(v);   // ✅
// mySort(cin); // ❌ 漂亮报错
```

### 约束容器打印

```cpp
template &lt;typename T&gt;
concept Container = requires(T c) {
    c.begin();
    c.end();
    typename T::value_type;
};

template &lt;Container C&gt;
    requires Printable&lt;typename C::value_type&gt;
std::ostream &amp;operator&lt;&lt;(std::ostream &amp;os, const C &amp;c) {
    os &lt;&lt; &quot;[&quot;;
    for (auto it = c.begin(); it != c.end(); ++it) {
        if (it != c.begin()) os &lt;&lt; &quot;, &quot;;
        os &lt;&lt; *it;
    }
    return os &lt;&lt; &quot;]&quot;;
}
```

## C++20 其他推荐特性

```cpp
// Ranges（管道操作）
auto evens = views::iota(1)
           | views::filter([](int n) { return n % 2 == 0; })
           | views::take(5)
           | ranges::to&lt;vector&lt;int&gt;&gt;();
// {2, 4, 6, 8, 10}

// 三路比较 &lt;=&gt;
struct Point {
    int x, y;
    auto operator&lt;=&gt;(const Point &amp;) const = default;
};
Point{1,2} &lt; Point{3,4};  // ✅ 自动生成全部比较运算符

// 字符串 starts_with / ends_with
string s = &quot;hello world&quot;;
s.starts_with(&quot;he&quot;);  // true
s.ends_with(&quot;ld&quot;);    // true

// contains（容器）
vector&lt;int&gt; v{1,2,3};
v.contains(2);  // true (C++23 才进标准，但很多编译器已支持)
```</content:encoded></item><item><title>Qt Model/View 架构</title><link>https://kiyose.wiki/notes/modelview/</link><guid isPermaLink="true">https://kiyose.wiki/notes/modelview/</guid><description>QAbstractItemModel、QTableView、自定义数据模型与代理</description><pubDate>Fri, 10 May 2024 00:00:00 GMT</pubDate><content:encoded>## 为什么需要 Model/View

传统做法（QTableWidget）：数据直接存在控件里 → 数据一变就要手动更新控件 → 数据和 UI 耦合。

Model/View 把数据管理和显示分离：

```
QAbstractItemModel           QAbstractItemView
   (数据)       ← 问答协议 →     (显示)

View 不碰数据，只通过标准接口问 Model：
  - rowCount() — 几行？
  - columnCount() — 几列？
  - data(index, role) — 第(r,c)格显示什么？
  - headerData(section, orientation, role) — 表头是什么？
```

## 内置 Model

```cpp
// QStringListModel — 简单字符串列表
QStringListModel model;
model.setStringList({&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;});

QListView view;
view.setModel(&amp;model);  // View 绑定 Model

// QStandardItemModel — 通用表格/树
QStandardItemModel model(3, 2);  // 3 行 2 列
model.setHeaderData(0, Qt::Horizontal, &quot;Name&quot;);
model.setHeaderData(1, Qt::Horizontal, &quot;Age&quot;);
model.setItem(0, 0, new QStandardItem(&quot;Alice&quot;));
model.setItem(0, 1, new QStandardItem(&quot;25&quot;));

QTableView view;
view.setModel(&amp;model);

// QFileSystemModel — 文件系统
QFileSystemModel model;
model.setRootPath(QDir::homePath());

QTreeView view;
view.setModel(&amp;model);
view.setRootIndex(model.index(QDir::homePath()));
```

## 自定义 Model

```cpp
class MyTableModel : public QAbstractTableModel {
    QVector&lt;QVector&lt;QVariant&gt;&gt; m_data;
public:
    // 必须实现的 3 个虚函数
    int rowCount(const QModelIndex &amp;parent = QModelIndex()) const override {
        return parent.isValid() ? 0 : m_data.size();
    }
    int columnCount(const QModelIndex &amp;parent = QModelIndex()) const override {
        return parent.isValid() ? 0 : 3;  // 3 列
    }
    QVariant data(const QModelIndex &amp;index, int role) const override {
        if (!index.isValid()) return {};
        if (role == Qt::DisplayRole)
            return m_data[index.row()][index.column()];
        if (role == Qt::TextAlignmentRole)
            return int(Qt::AlignCenter);
        return {};
    }

    // 可选：表头
    QVariant headerData(int section, Qt::Orientation o, int role) const override {
        if (role != Qt::DisplayRole) return {};
        if (o == Qt::Horizontal)
            return QStringList{&quot;Name&quot;, &quot;Age&quot;, &quot;Email&quot;}[section];
        return section + 1;  // 行号
    }

    // 通知 View 数据变了
    void appendRow(const QVector&lt;QVariant&gt; &amp;row) {
        beginInsertRows(QModelIndex(), m_data.size(), m_data.size());
        m_data.append(row);
        endInsertRows();
    }
};
```

### 关键规则

```cpp
// ⚠️ 修改数据前后必须调用 begin/end 方法
beginInsertRows(parent, first, last);
// 修改 m_data ...
endInsertRows();

beginRemoveRows(parent, first, last);
// 修改 m_data ...
endRemoveRows();

beginResetModel();
// 全量替换数据 ...
endResetModel();

// 单个单元格变化
dataChanged(topLeft, bottomRight);
```

## Data Role — data() 的核心参数

```cpp
QVariant data(const QModelIndex &amp;index, int role) const override {
    switch (role) {
    case Qt::DisplayRole:    return m_data[index.row()].name;
    case Qt::ToolTipRole:    return m_data[index.row()].description;
    case Qt::DecorationRole: return QIcon(&quot;:/icon.png&quot;);
    case Qt::ForegroundRole: return QColor(Qt::red);     // 文字颜色
    case Qt::BackgroundRole: return QColor(Qt::lightGray);
    case Qt::FontRole:       return QFont(&quot;Consolas&quot;, 12);
    case Qt::TextAlignmentRole: return int(Qt::AlignCenter);
    case Qt::UserRole:       return m_data[index.row()].id;  // 自定义数据
    default: return {};
    }
}
```

| Role | 作用 |
|------|------|
| `DisplayRole` | 显示文本 |
| `DecorationRole` | 图标 |
| `ToolTipRole` | 鼠标悬停提示 |
| `ForegroundRole` | 文字颜色 |
| `BackgroundRole` | 背景色 |
| `FontRole` | 字体 |
| `UserRole` | 自定义（隐藏数据，如 ID） |

## Delegate — 自定义渲染和编辑

```cpp
class ColorDelegate : public QStyledItemDelegate {
    void paint(QPainter *painter, const QStyleOptionViewItem &amp;option,
               const QModelIndex &amp;index) const override {
        // 自定义绘制
        painter-&gt;save();
        if (index.data(Qt::UserRole).toBool()) {
            painter-&gt;fillRect(option.rect, QColor(&quot;#e8f5e9&quot;));
        }
        // 调用父类画文字
        QStyledItemDelegate::paint(painter, option, index);
        painter-&gt;restore();
    }
};

view-&gt;setItemDelegate(new ColorDelegate);
```

## View 常用设置

```cpp
QTableView *view = new QTableView;
view-&gt;setModel(&amp;model);

// 外观
view-&gt;setSelectionBehavior(QAbstractItemView::SelectRows);  // 整行选中
view-&gt;setSelectionMode(QAbstractItemView::SingleSelection);
view-&gt;setAlternatingRowColors(true);    // 交替行颜色
view-&gt;verticalHeader()-&gt;setVisible(false);
view-&gt;setSortingEnabled(true);          // 点击表头排序

// 列宽
view-&gt;horizontalHeader()-&gt;setStretchLastSection(true);
view-&gt;setColumnWidth(0, 150);

// 隐藏列
view-&gt;setColumnHidden(2, true);

// 滚动到底部（数据追加后）
view-&gt;scrollToBottom();
```</content:encoded></item><item><title>Git 进阶 — rebase / cherry-pick / bisect / reflog</title><link>https://kiyose.wiki/notes/gitadvanced/</link><guid isPermaLink="true">https://kiyose.wiki/notes/gitadvanced/</guid><description>Git 中级操作速查：交互式 rebase 合并 commit、cherry-pick 跨分支摘取、bisect 二分定位 Bug、reflog 恢复误删</description><pubDate>Wed, 08 May 2024 00:00:00 GMT</pubDate><content:encoded>## 交互式 rebase — 整理提交历史

```bash
# 合并最近 3 个 commit
git rebase -i HEAD~3

# 编辑器打开：
pick abc1234 feat: add login
pick def5678 fix: typo in login
pick ghi9012 fix: another typo

# 改为：
pick abc1234 feat: add login
squash def5678 fix: typo in login
squash ghi9012 fix: another typo
# → 3 个 commit 合并为 1 个整洁的 &quot;feat: add login&quot;
```

### rebase 命令速查

| 命令 | 效果 |
|------|------|
| `pick` | 保留此 commit |
| `reword` | 保留但改 message |
| `squash` | 合并到上一个 commit，保留 message |
| `fixup` | 合并到上一个，丢弃 message |
| `drop` | 删除此 commit |
| `edit` | 暂停，允许修改 commit 内容 |

```bash
# 编辑某个旧 commit
git rebase -i HEAD~5
# 把 pick 改为 edit → git 暂停在那个 commit
# 修改文件 → git add → git commit --amend
# git rebase --continue

# 把一个 commit 拆成两个
git rebase -i HEAD~3
# 标记 edit → reset HEAD^ → 分批 add + commit
```

## 变基 vs 合并

```bash
# merge: 保留真实历史，产生合并 commit
git merge feature
# 历史：A─B─C─D─E─F (merge commit)

# rebase: 重写历史，线性干净
git checkout feature
git rebase main
# 历史：A─B─C─D&apos;─E&apos; (无额外 merge commit)
```

**原则**：公共分支用 merge，个人分支用 rebase。永远不要 rebase 已经 push 的分支。

## cherry-pick — 跨分支摘 commit

```bash
# 把某个 commit 摘到当前分支
git cherry-pick abc1234

# 摘一串
git cherry-pick abc1234..def5678  # (abc, def]
git cherry-pick abc1234^..def5678 # [abc, def]

# 冲突时：
# 解决冲突 → git add → git cherry-pick --continue
# 放弃：git cherry-pick --abort

# 不自动 commit（方便修改）
git cherry-pick -n abc1234
# 修改... → git add → git commit
```

## bisect — 二分定位 Bug

```bash
# 启动
git bisect start
git bisect bad HEAD        # 当前版本有 bug
git bisect good v1.0        # v1.0 没有 bug

# Git 自动 checkout 中间版本 → 你测试
# 如果有 bug: git bisect bad
# 如果没有:   git bisect good
# 重复 ~log2(N) 次

# Git 定位到引入 bug 的第一个 commit
# 结束: git bisect reset

# 自动化（有测试脚本时）
git bisect start HEAD v1.0
git bisect run npm test  # Git 自动二分跑测试
```

## reflog — 后悔药

```bash
# 查看所有 HEAD 移动记录（默认保留 90 天）
git reflog
# abc1234 HEAD@{0}: commit: feat: add login
# def5678 HEAD@{1}: rebase (finish): ...
# ghi9012 HEAD@{2}: reset: moving to HEAD~3

# 恢复误删的分支
git branch -D feature    # 手抖删了！
git reflog               # 找到 feature 最后的 commit
git checkout -b feature HEAD@{3}  # 恢复

# 恢复 rebase 前状态
git rebase -i HEAD~5  # rebase 搞砸了！
git reflog             # 找到 rebase 前的 HEAD
git reset --hard HEAD@{1}

# 撤销 git reset --hard
git reset --hard HEAD~3  # 啊啊啊不该 reset！
git reflog
git reset --hard HEAD@{1} # 回到 reset 前
```

## stash — 暂存工作区

```bash
git stash                    # 暂存所有改动
git stash pop                # 恢复最近一次 stash
git stash list               # 查看所有 stash
git stash drop stash@{2}     # 删除某个 stash

# 部分 stash
git stash -p                 # 交互式选择 stash 哪些

# 带消息
git stash save &quot;WIP: refactor parser&quot;
```

## 常用场景速查

| 场景 | 命令 |
|------|------|
| 修改最近一次 commit | `commit --amend` |
| 撤销工作区改动 | `checkout -- &lt;file&gt;` |
| 撤销暂存区 | `reset HEAD &lt;file&gt;` |
| 回退到某版本（保留改动） | `reset --soft HEAD~3` |
| 回退到某版本（丢弃改动） | `reset --hard &lt;commit&gt;` |
| 创建新分支并切换 | `checkout -b feature/x` |
| 删除远程分支 | `push origin --delete feature/x` |
| 查看某个文件的改动历史 | `log -p -- &lt;file&gt;` |
| 看谁改了某行 | `blame &lt;file&gt; -L 10,20` |</content:encoded></item><item><title>Qt 事件系统</title><link>https://kiyose.wiki/notes/qtevents/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qtevents/</guid><description>QEvent 事件循环、event()/eventFilter()、自定义事件与事件传播机制</description><pubDate>Mon, 15 Apr 2024 00:00:00 GMT</pubDate><content:encoded>## 事件 vs 信号槽

| | 事件 (Event) | 信号槽 (Signal/Slot) |
|------|-------------|---------------------|
| 层级 | 底层（操作系统 → Qt） | 上层（QObject 之间） |
| 方向 | 自顶向下传播 | 任意对象间通信 |
| 处理 | `event()` / `eventFilter()` | `connect()` |
| 典型 | 鼠标点击、键盘按键、重绘 | 按钮点击、数据到达 |

信号槽底层由事件系统驱动，但日常开发中大多数场景用信号槽就够了。

## 事件循环

```cpp
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    // ...
    return app.exec();  // 进入事件循环
}

// 事件循环伪代码：
while (!shouldQuit) {
    QEvent event = getNextEvent();   // 从操作系统取事件
    sendEvent(widget, &amp;event);        // 发给目标控件
}
```

## QWidget 事件处理五层

```cpp
// 层 1: 专用事件处理器（最常用）
void MyWidget::mousePressEvent(QMouseEvent *event) override {
    if (event-&gt;button() == Qt::LeftButton) {
        // 处理左键点击
    }
    QWidget::mousePressEvent(event);  // ⚠️ 调用父类，否则无法继续传播
}

// 层 2: event() — 事件总入口（少用）
bool MyWidget::event(QEvent *event) override {
    if (event-&gt;type() == QEvent::KeyPress) {
        auto *ke = static_cast&lt;QKeyEvent *&gt;(event);
        // 拦截所有按键
    }
    return QWidget::event(event);  // 分发到专用处理器
}

// 层 3: 事件过滤器 — 拦截子控件事件
class Monitor : public QObject {
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event-&gt;type() == QEvent::MouseButtonPress) {
            qDebug() &lt;&lt; &quot;clicked on&quot; &lt;&lt; obj;
        }
        return QObject::eventFilter(obj, event);  // false = 继续传播
    }
};
// 安装：child-&gt;installEventFilter(monitor);

// 层 4: 全局事件过滤器（应用程序级）
// qApp-&gt;installEventFilter(globalMonitor);

// 层 5: notify() — QApplication 级（极少用）
// 重写 QApplication::notify()
```

## 常用事件类型

```cpp
// 鼠标事件
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;   // 需 setMouseTracking(true)
void wheelEvent(QWheelEvent *e) override;

// 键盘事件
void keyPressEvent(QKeyEvent *e) override;
void keyReleaseEvent(QKeyEvent *e) override;

// 窗口事件
void resizeEvent(QResizeEvent *e) override;
void closeEvent(QCloseEvent *e) override;       // 拦截关闭
void showEvent(QShowEvent *e) override;         // 首次显示前

// 重绘
void paintEvent(QPaintEvent *e) override;

// 拖放
void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent *e) override;
```

## 自定义事件

```cpp
// 1. 定义事件类型
const QEvent::Type MyCustomEvent = static_cast&lt;QEvent::Type&gt;(
    QEvent::registerEventType()
);

// 2. 自定义事件类（可携带数据）
class DataEvent : public QEvent {
public:
    static const QEvent::Type Type;
    QString message;

    DataEvent(const QString &amp;msg)
        : QEvent(Type), message(msg) {}
};
const QEvent::Type DataEvent::Type =
    static_cast&lt;QEvent::Type&gt;(QEvent::registerEventType());

// 3. 发送事件
// sendEvent — 同步（等处理完才返回）
QApplication::sendEvent(receiver, new DataEvent(&quot;hello&quot;));

// postEvent — 异步（加入事件队列，立即返回）
QApplication::postEvent(receiver, new DataEvent(&quot;hello&quot;));

// 4. 接收事件
bool Receiver::event(QEvent *event) override {
    if (event-&gt;type() == DataEvent::Type) {
        auto *de = static_cast&lt;DataEvent *&gt;(event);
        qDebug() &lt;&lt; de-&gt;message;
        return true;  // 已处理
    }
    return QWidget::event(event);
}
```

## 事件过滤器实战

```cpp
// 场景：让 LineEdit 响应回车键
class EnterFilter : public QObject {
    bool eventFilter(QObject *obj, QEvent *event) override {
        if (event-&gt;type() == QEvent::KeyPress) {
            auto *ke = static_cast&lt;QKeyEvent *&gt;(event);
            if (ke-&gt;key() == Qt::Key_Return) {
                emit enterPressed();  // 自定义信号
                return true;           // 拦截事件
            }
        }
        return false;  // 继续传播
    }
signals:
    void enterPressed();
};

auto *filter = new EnterFilter(edit);
edit-&gt;installEventFilter(filter);
connect(filter, &amp;EnterFilter::enterPressed, [=]() {
    // 处理回车
});
```

## 常见陷阱

```cpp
// 陷阱 1: 忘记调用父类事件处理器
void MyWidget::mousePressEvent(QMouseEvent *e) override {
    // 处理...但没调 QWidget::mousePressEvent(e)
    // → setFocus、右键菜单等默认行为丢失
}

// 陷阱 2: eventFilter 返回 true 会吞掉事件
bool eventFilter(QObject *obj, QEvent *event) override {
    if (event-&gt;type() == QEvent::KeyPress)
        return true;  // ❌ 按键被吃了，子控件永远收不到
    return false;
}

// 陷阱 3: postEvent 的内存管理
QApplication::postEvent(obj, new MyEvent);  // ✅ Qt 自动 delete
// sendEvent 也一样 — 不要手动 delete 事件对象
```</content:encoded></item><item><title>C++ chrono — 时间处理</title><link>https://kiyose.wiki/notes/chrono/</link><guid isPermaLink="true">https://kiyose.wiki/notes/chrono/</guid><description>duration/time_point/clock 三类核心模板、计时器、休眠、日期格式化与性能陷阱</description><pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate><content:encoded>## 三类核心

```cpp
#include &lt;chrono&gt;

// ① duration — 时间间隔
chrono::seconds sec(10);
chrono::milliseconds ms(500);
chrono::microseconds us = chrono::duration_cast&lt;chrono::microseconds&gt;(ms);

// ② time_point — 时间点
auto now = chrono::system_clock::now();
auto later = now + chrono::hours(2);

// ③ clock — 时钟源
// system_clock: 系统时间（可调，适合显示时间）
// steady_clock: 单调递增（用于计时，不受系统时间调整影响）
// high_resolution_clock: 最高精度（通常是 steady_clock 别名）
```

## 计时器

```cpp
auto t1 = chrono::steady_clock::now();
doExpensiveWork();
auto t2 = chrono::steady_clock::now();

auto elapsed = chrono::duration_cast&lt;chrono::milliseconds&gt;(t2 - t1);
cout &lt;&lt; &quot;耗时: &quot; &lt;&lt; elapsed.count() &lt;&lt; &quot;ms\n&quot;;

// 通用计时器类
class Timer {
    chrono::steady_clock::time_point start;
public:
    Timer() : start(chrono::steady_clock::now()) {}
    double elapsedMs() const {
        return chrono::duration&lt;double, milli&gt;(
            chrono::steady_clock::now() - start).count();
    }
};
```

## 字面量（C++14）

```cpp
using namespace chrono_literals;

auto halfSec = 500ms;
auto twoSec  = 2s;
auto tenMin  = 10min;
auto oneHour = 1h;

// 算术运算
auto total = 1h + 30min + 15s;  // 自动转换

// 与线程配合
this_thread::sleep_for(100ms);
this_thread::sleep_until(chrono::steady_clock::now() + 500ms);
```

## duration_cast 规则

```cpp
chrono::milliseconds ms(1500);

// 向下取整（截断）
chrono::seconds s = chrono::duration_cast&lt;chrono::seconds&gt;(ms);  // 1s

// 向上取整（C++17）
chrono::seconds s2 = chrono::ceil&lt;chrono::seconds&gt;(ms);        // 2s

// 四舍五入（C++17）
chrono::seconds s3 = chrono::round&lt;chrono::seconds&gt;(ms);       // 2s (1.5→2)

// 浮点 duration（不丢失精度）
chrono::duration&lt;double&gt; d = ms;  // 不需要 cast
```

## 时间与字符串转换

```cpp
// C++20 之前 — 用 C API（繁琐）
auto now = chrono::system_clock::now();
time_t t = chrono::system_clock::to_time_t(now);
cout &lt;&lt; ctime(&amp;t);  // 或 strftime

// C++20 — 现代格式化（推荐）
#include &lt;format&gt;   // C++20
cout &lt;&lt; format(&quot;{:%Y-%m-%d %H:%M:%S}&quot;, now);  // &quot;2024-04-10 14:30:00&quot;

// 解析字符串为时间点（C++20）
chrono::sys_days date;
istringstream(&quot;2024-04-10&quot;) &gt;&gt; chrono::parse(&quot;%F&quot;, date);
```

## 日期运算（C++20）

```cpp
using namespace chrono;

// 日期类型
year_month_day ymd = 2024y / April / 10d;   // 2024-04-10
auto lastDay = 2024y / February / last;      // 2024-02-29（闰年自动）

// 日期运算
auto tomorrow = sys_days{ymd} + days{1};
auto nextMonth = ymd + months{1};

// 星期几
auto wd = weekday{sys_days{ymd}};
cout &lt;&lt; wd;  // Wednesday

// 检查有效性
if (auto d = 2024y / February / 30d; d.ok())
    cout &lt;&lt; &quot;valid date&quot;;
else
    cout &lt;&lt; &quot;invalid date&quot;;  // 2 月没有 30 日
```

## 常见陷阱

```cpp
// 陷阱 1: system_clock 计时 → 可能不准
auto t1 = chrono::system_clock::now();   // ❌ NTP 时间调整会影响
// 计时用 steady_clock ✅

// 陷阱 2: duration_cast 精度丢失
chrono::milliseconds ms(1499);
auto s = chrono::duration_cast&lt;chrono::seconds&gt;(ms);
cout &lt;&lt; s.count();  // 1（不是 1.499）

// 陷阱 3: 纳秒溢出
chrono::nanoseconds ns = chrono::duration_cast&lt;chrono::nanoseconds&gt;(
    chrono::hours(1000));  // ⚠️ 1000h 转为 ns 可能溢出 64 位

// 陷阱 4: high_resolution_clock 不一定是 steady
// 跨平台时用 steady_clock 或显式检查 is_steady
static_assert(chrono::high_resolution_clock::is_steady);
```</content:encoded></item><item><title>Qt 窗口与布局</title><link>https://kiyose.wiki/notes/qtwidgets/</link><guid isPermaLink="true">https://kiyose.wiki/notes/qtwidgets/</guid><description>QWidget/QMainWindow/QDialog 窗口体系、布局管理器与常见 UI 模式</description><pubDate>Wed, 20 Mar 2024 00:00:00 GMT</pubDate><content:encoded>## 三大窗口基类

| 类 | 用途 | 特点 |
|---|------|------|
| `QWidget` | 基础窗口 | 所有控件的基类 |
| `QMainWindow` | 主窗口 | 自带菜单栏、工具栏、状态栏、Dock |
| `QDialog` | 对话框 | 模态/非模态，`exec()`/`open()`/`show()` |

```cpp
// 最小窗口
#include &lt;QApplication&gt;
#include &lt;QWidget&gt;

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QWidget w;
    w.resize(400, 300);
    w.setWindowTitle(&quot;Hello Qt&quot;);
    w.show();
    return app.exec();
}
```

## 布局管理器

```cpp
// 水平布局
auto *hLayout = new QHBoxLayout;
hLayout-&gt;addWidget(new QPushButton(&quot;Left&quot;));
hLayout-&gt;addWidget(new QPushButton(&quot;Right&quot;));

// 垂直布局
auto *vLayout = new QVBoxLayout;
vLayout-&gt;addWidget(new QLabel(&quot;Top&quot;));
vLayout-&gt;addLayout(hLayout);               // 嵌套布局
vLayout-&gt;addWidget(new QLabel(&quot;Bottom&quot;));

// 网格布局
auto *grid = new QGridLayout;
grid-&gt;addWidget(new QLabel(&quot;Name:&quot;), 0, 0);
grid-&gt;addWidget(new QLineEdit,      0, 1);
grid-&gt;addWidget(new QPushButton(&quot;OK&quot;), 1, 0, 1, 2); // 跨 2 列

// 表单布局（两列标签-值）
auto *form = new QFormLayout;
form-&gt;addRow(&quot;Name:&quot;, new QLineEdit);
form-&gt;addRow(&quot;Email:&quot;, new QLineEdit);

// 设置到窗口
setLayout(vLayout);
```

### 常用属性

```cpp
layout-&gt;setSpacing(10);              // 控件间距
layout-&gt;setContentsMargins(10,5,10,5); // 上下左右边距
widget-&gt;setSizePolicy(QSizePolicy::Expanding,  // 水平：尽量占满
                      QSizePolicy::Fixed);     // 垂直：固定
```

## 常用控件

```cpp
// 按钮
auto *btn = new QPushButton(&quot;Click me&quot;);
connect(btn, &amp;QPushButton::clicked, []() { qDebug() &lt;&lt; &quot;clicked&quot;; });

// 标签
auto *label = new QLabel(&quot;Hello&quot;);
label-&gt;setAlignment(Qt::AlignCenter);
label-&gt;setPixmap(QPixmap(&quot;:/icon.png&quot;));

// 输入框
auto *edit = new QLineEdit;
edit-&gt;setPlaceholderText(&quot;请输入...&quot;);
connect(edit, &amp;QLineEdit::returnPressed, []() { /* 回车 */ });

// 文本框
auto *text = new QTextEdit;
text-&gt;setPlainText(&quot;多行文本&quot;);

// 复选框 / 单选框
auto *cb = new QCheckBox(&quot;Enable feature&quot;);
auto *rb1 = new QRadioButton(&quot;Option A&quot;);

// 下拉框
auto *combo = new QComboBox;
combo-&gt;addItems({&quot;Item 1&quot;, &quot;Item 2&quot;, &quot;Item 3&quot;});
connect(combo, QOverload&lt;int&gt;::of(&amp;QComboBox::currentIndexChanged),
        [](int i) { qDebug() &lt;&lt; i; });

// 列表
auto *list = new QListWidget;
list-&gt;addItem(&quot;Item A&quot;);
```

## 信号槽在 UI 中的组合模式

```cpp
// 模式 1：按钮点击 → 读取输入 → 更新标签
connect(btn, &amp;QPushButton::clicked, [=]() {
    QString name = edit-&gt;text();
    label-&gt;setText(&quot;Hello, &quot; + name);
});

// 模式 2：输入框变化 → 实时更新
connect(edit, &amp;QLineEdit::textChanged, label, &amp;QLabel::setText);

// 模式 3：复选框 → 控制控件启用
connect(cb, &amp;QCheckBox::toggled, edit, &amp;QLineEdit::setEnabled);
```

## 布局黄金法则

1. **永远用布局，不要手动 setGeometry** — 布局自动处理 DPI、窗口缩放
2. **嵌套布局实现复杂界面** — VBox 套 HBox 套 Form
3. **用 QSpacerItem 撑开空间** — `layout-&gt;addStretch()` 推到一边
4. **最小尺寸由内容决定** — 不需要手动设 `setMinimumSize`</content:encoded></item><item><title>Linux Shell 速查 — grep/sed/awk/find/系统管理</title><link>https://kiyose.wiki/notes/linuxshell/</link><guid isPermaLink="true">https://kiyose.wiki/notes/linuxshell/</guid><description>日常开发高频 Linux 命令：文本处理三剑客、进程磁盘网络排查、systemd 服务管理</description><pubDate>Tue, 12 Mar 2024 00:00:00 GMT</pubDate><content:encoded>## grep — 文本搜索

```bash
grep &quot;error&quot; app.log                  # 搜索包含 error 的行
grep -i &quot;error&quot; app.log               # 忽略大小写
grep -v &quot;debug&quot; app.log               # 排除匹配行
grep -r &quot;TODO&quot; src/                   # 递归搜索目录
grep -n &quot;error&quot; app.log               # 显示行号
grep -c &quot;error&quot; app.log               # 计数
grep -A 3 &quot;error&quot; app.log             # 匹配行 + 后 3 行
grep -B 2 &quot;error&quot; app.log             # 匹配行 + 前 2 行
grep -E &quot;error|fail&quot; app.log          # 正则（扩展）

# 实用组合
grep -rn &quot;FIXME&quot; --include=&quot;*.cpp&quot; .  # 只搜 .cpp 文件的 FIXME
ps aux | grep nginx | grep -v grep    # 查进程排除 grep 自身
history | grep &quot;docker&quot;               # 搜索历史命令
```

## find — 文件查找

```bash
find . -name &quot;*.cpp&quot;                  # 按名称
find . -name &quot;*.cpp&quot; -o -name &quot;*.h&quot;   # 或逻辑
find . -type f -mtime -7              # 最近 7 天修改的文件
find . -type f -size +10M             # 大于 10MB 的文件
find . -name &quot;*.o&quot; -delete            # 查找并删除
find . -name &quot;*.log&quot; -exec rm {} \;   # 对每个结果执行命令
find . -name &quot;*.cpp&quot; | xargs wc -l    # 管道传递给 wc

# 排除目录
find . -name &quot;*.cpp&quot; -not -path &quot;./build/*&quot;
```

## sed — 流编辑器

```bash
# 替换（最常用）
sed &apos;s/old/new/&apos; file                 # 每行替换第一个
sed &apos;s/old/new/g&apos; file                # 全局替换
sed -i &apos;s/old/new/g&apos; file             # 原地修改（-i）

# 按行操作
sed -n &apos;10,20p&apos; file                  # 打印第 10-20 行
sed &apos;5d&apos; file                         # 删除第 5 行
sed &apos;/pattern/d&apos; file                 # 删除匹配行

# 实用场景
sed -i &apos;s/\r$//&apos; file                 # 去除 Windows 换行符
sed -i &apos;s/^#//&apos; config.ini            # 取消注释
sed -n &apos;s/.*PORT=\([0-9]*\).*/\1/p&apos;   # 提取 PORT 值
```

## awk — 文本分析

```bash
# 按列处理
awk &apos;{print $1, $3}&apos; file             # 打印第 1、3 列
awk -F: &apos;{print $1}&apos; /etc/passwd      # 指定分隔符 :
awk -F&apos;,&apos; &apos;{print $NF}&apos; file          # $NF = 最后一列

# 条件过滤
awk &apos;$3 &gt; 100&apos; data.txt               # 第 3 列 &gt; 100 的行
awk &apos;/error/ {print $0}&apos; app.log      # 含 error 的行
awk &apos;NR&gt;=10 &amp;&amp; NR&lt;=20&apos; file           # 第 10-20 行

# 统计
awk &apos;{sum += $1} END {print sum}&apos;   # 第 1 列求和
awk &apos;{cnt[$2]++} END {for(k in cnt) print k, cnt[k]}&apos; # 分组统计
```

## 进程管理

```bash
ps aux | grep myapp                   # 查看进程
top -p $(pgrep myapp)                 # 只看指定进程
htop                                  # 更友好的 top

kill -9 &lt;PID&gt;                         # 强杀进程
killall myapp                         # 杀所有同名进程
pkill -f &quot;python server.py&quot;           # 按命令行匹配杀

# 后台运行
nohup ./myapp &gt; output.log 2&gt;&amp;1 &amp;     # 忽略挂断信号
screen -S myapp                       # 创建会话
screen -r myapp                       # 恢复会话
tmux new -s myapp                     # tmux 版
```

## 磁盘与文件

```bash
df -h                                 # 磁盘使用
du -sh * | sort -h                    # 当前目录各文件/夹大小排序
ncdu                                  # 交互式磁盘分析（需安装）
ls -lhS                               # 按文件大小列出
lsof -p &lt;PID&gt;                         # 进程打开的文件
lsof -i :8080                         # 哪个进程在占用 8080 端口
```

## 网络

```bash
# 端口/连接
ss -tlnp                              # 监听中的 TCP 端口
ss -tlnp | grep 8080                  # 查 8080 端口
netstat -tlnp                         # 同上（旧工具）

# 连通性
ping -c 4 google.com                  # 4 次 ping
curl -I https://example.com           # 只看 HTTP 头
curl -X POST -d &apos;{&quot;key&quot;:&quot;val&quot;}&apos; url   # POST JSON
wget -c https://example.com/file.zip  # 断点续传

# DNS
nslookup example.com                  # 查 DNS
dig example.com +short                # 简洁输出
```

## systemd 服务管理

```bash
# 服务状态
systemctl status nginx
systemctl start/stop/restart nginx
systemctl enable nginx               # 开机自启
systemctl disable nginx

# 查看日志
journalctl -u nginx -f               # 跟随日志
journalctl -u nginx --since &quot;1h ago&quot; # 最近 1 小时
journalctl -u nginx -n 50            # 最后 50 行

# 查看启动时间
systemd-analyze                       # 总启动时间
systemd-analyze blame                 # 各服务耗时排序
```

## 组合技

```bash
# 统计代码行数（排除空行和注释）
find src -name &quot;*.cpp&quot; | xargs cat | grep -v &apos;^\s*$&apos; | grep -v &apos;^\s*//&apos; | wc -l

# 查找最大的 10 个文件
find . -type f -exec du -h {} + | sort -rh | head -10

# 实时监控日志中的错误
tail -f app.log | grep --color &quot;ERROR\|FATAL&quot;

# 批量重命名
for f in *.txt; do mv &quot;$f&quot; &quot;${f%.txt}.md&quot;; done

# 杀掉占用某端口的进程
kill $(lsof -t -i:8080)
```</content:encoded></item><item><title>C++ 运算符重载</title><link>https://kiyose.wiki/notes/operatoroverload/</link><guid isPermaLink="true">https://kiyose.wiki/notes/operatoroverload/</guid><description>算术/比较/赋值/下标/函数调用运算符重载规范、友元、类型转换运算符与最佳实践</description><pubDate>Sun, 25 Feb 2024 00:00:00 GMT</pubDate><content:encoded>## 可重载 vs 不可重载

```
可重载:
  算术: + - * / % += -= *= /= %=
  比较: &lt; &gt; &lt;= &gt;= == !=
  位:   &amp; | ^ ~ &lt;&lt; &gt;&gt; &amp;= |= ^= &lt;&lt;= &gt;&gt;=
  逻辑: ! &amp;&amp; ||
  赋值: =
  下标: []
  调用: ()
  解引用: * -&gt;
  自增: ++ --
  new / delete

不可重载:
  ::  .  .*  ?:  sizeof  typeid
```

## 基本原则

```cpp
// ① 成员 vs 非成员
class Vec2 {
    double x, y;
public:
    // 成员函数：第一个操作数必须是本类
    Vec2 operator+(const Vec2 &amp;other) const {
        return {x + other.x, y + other.y};
    }
    // 好处：可访问私有成员

    // 非成员（推荐对称运算符）：
    friend Vec2 operator*(double s, const Vec2 &amp;v) {
        return {s * v.x, s * v.y};
    }
    // 好处：3.0 * v 可以（左操作数隐式转换）
};

// ② 返回 const 引用的规则
// = += -=  → 返回 *this 的引用（支持链式 a=b=c）
// + - * /  → 返回新对象（值语义）
// &lt; &gt; ==   → 返回 bool

// ③ 不要重载逻辑运算符的短路行为
// operator&amp;&amp; — 两个操作数都会求值！已失去短路语义
// 基本不重载 &amp;&amp;
```

## 算术运算符

```cpp
class Complex {
    double re, im;
public:
    // 复合赋值（成员，返回引用）
    Complex &amp;operator+=(const Complex &amp;rhs) {
        re += rhs.re; im += rhs.im;
        return *this;
    }

    // 二元 +（非成员，用 += 实现）
    friend Complex operator+(Complex lhs, const Complex &amp;rhs) {
        lhs += rhs;          // 拷贝 lhs，然后 +=
        return lhs;          // 利用 RVO
    }
    // 技巧：operator+ 不访问私有成员 → 只需 operator+=
};
```

## 比较运算符（C++20 简化）

```cpp
// C++17 之前 — 写 4-6 个
class Person {
    string name; int age;
public:
    bool operator==(const Person &amp;o) const { return name == o.name &amp;&amp; age == o.age; }
    bool operator!=(const Person &amp;o) const { return !(*this == o); }
    bool operator&lt; (const Person &amp;o) const { return tie(name, age) &lt; tie(o.name, o.age); }
};

// C++20 — 只需 &lt;=&gt;
class Person20 {
    string name; int age;
public:
    auto operator&lt;=&gt;(const Person20 &amp;) const = default;  // 一行生成全部 6 个！
};
```

## 下标与函数调用

```cpp
// 下标 operator[]
class Array {
    int *data; size_t n;
public:
    int &amp;operator[](size_t i)       { return data[i]; }        // 可写
    int  operator[](size_t i) const { return data[i]; }        // 只读版
};

// 函数调用 operator()  → 仿函数
class Multiplier {
    int factor;
public:
    Multiplier(int f) : factor(f) {}
    int operator()(int x) const { return x * factor; }
};
Multiplier times3(3);
int nine = times3(3);  // 像函数一样调用

// 结合 std::function / 算法
vector&lt;int&gt; v = {1, 2, 3};
transform(v.begin(), v.end(), v.begin(), Multiplier(10));
```

## 类型转换运算符

```cpp
class Wrapper {
    int value;
public:
    // 隐式转换（方便但危险）
    operator int() const { return value; }       // Wrapper → int

    // C++11 explicit — 必须显式转换
    explicit operator bool() const { return value != 0; }
};

Wrapper w{42};
int i = w;              // ✅ 隐式
// bool b = w;           // ❌ explicit
bool b = bool(w);        // ✅ 显式
if (w) { /* ... */ }     // ✅ 条件上下文允许 explicit bool
```

## 增量运算符

```cpp
class Iterator {
    int pos;
public:
    Iterator &amp;operator++()    { ++pos; return *this; }  // 前置 ++it
    Iterator  operator++(int) { auto tmp = *this; ++pos; return tmp; } // 后置 it++
    //                          ↑ 哑元 int 区分前后置
};
```

## 输出运算符

```cpp
class Point {
    double x, y;
    friend ostream &amp;operator&lt;&lt;(ostream &amp;os, const Point &amp;p) {
        return os &lt;&lt; &quot;(&quot; &lt;&lt; p.x &lt;&lt; &quot;, &quot; &lt;&lt; p.y &lt;&lt; &quot;)&quot;;
    }
};
// 必须是非成员：第一个参数是 ostream，不能是本类
```

## 经验法则

```
1. = [] () -&gt;    → 成员函数（别无选择）
2. += -= *= /=   → 成员函数
3. + - * /       → 非成员（用 += 实现）
4. == &lt; &gt;        → 非成员（对称性）
5. &lt;&lt; &gt;&gt;          → 非成员（第一个参数是 stream）
6. 不要重载 &amp;&amp; || ,（破坏短路/求值顺序）
```</content:encoded></item><item><title>CMake 入门</title><link>https://kiyose.wiki/notes/cmake/</link><guid isPermaLink="true">https://kiyose.wiki/notes/cmake/</guid><description>CMakeLists.txt 核心语法、target 管理、find_package 与 Qt/库集成</description><pubDate>Sat, 10 Feb 2024 00:00:00 GMT</pubDate><content:encoded>## 最小 CMakeLists.txt

```cmake
cmake_minimum_required(VERSION 3.16)
project(MyApp VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(myapp main.cpp)
```

构建三步：

```bash
mkdir build &amp;&amp; cd build
cmake ..         # 生成构建系统
cmake --build .  # 编译
```

## target 管理（Modern CMake 核心）

```cmake
# 可执行文件
add_executable(myapp main.cpp)

# 静态库
add_library(mylib STATIC src/lib.cpp)

# 动态库
add_library(mylib SHARED src/lib.cpp)

# 头文件库（header-only）
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include/)

# 关键：全部用 target_* 命令，不用全局 set
target_include_directories(myapp PRIVATE include/)
target_link_libraries(myapp PRIVATE mylib)
target_compile_definitions(myapp PRIVATE VERSION=&quot;1.0&quot;)
target_compile_options(myapp PRIVATE -Wall -Wextra)
```

### PRIVATE / PUBLIC / INTERFACE

| 关键字 | 自己用 | 依赖者也用 | 场景 |
|--------|:--:|:--:|------|
| `PRIVATE` | ✅ | ❌ | 内部实现细节 |
| `PUBLIC` | ✅ | ✅ | 头文件也需要的 |
| `INTERFACE` | ❌ | ✅ | header-only 库 |

```cmake
# mylib 的头文件里用了 Qt::Core → 依赖者也必须能 include Qt
target_link_libraries(mylib PUBLIC Qt6::Core)

# mylib.cpp 里用了 boost → 只有 mylib 需要链接
target_link_libraries(mylib PRIVATE Boost::filesystem)
```

## 多目录项目

```
myapp/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   └── main.cpp
└── lib/
    ├── CMakeLists.txt
    └── mylib.cpp
```

```cmake
# 根 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyApp)
add_subdirectory(lib)
add_subdirectory(src)

# lib/CMakeLists.txt
add_library(mylib mylib.cpp)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# src/CMakeLists.txt
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
```

## find_package

```cmake
# Qt
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Sql)
target_link_libraries(myapp PRIVATE Qt6::Core Qt6::Widgets Qt6::Sql)

# OpenCV
find_package(OpenCV REQUIRED)
target_link_libraries(myapp PRIVATE ${OpenCV_LIBS})

# 自定义库（非标准路径）
find_package(mylib REQUIRED PATHS /opt/custom/lib/cmake)
```

## 条件编译

```cmake
option(ENABLE_TESTS &quot;Build tests&quot; ON)

if(ENABLE_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

# 平台判断
if(WIN32)
    target_sources(myapp PRIVATE win_specific.cpp)
elseif(APPLE)
    target_sources(myapp PRIVATE mac_specific.cpp)
else()
    target_sources(myapp PRIVATE linux_specific.cpp)
endif()
```

## Qt 集成

```cmake
cmake_minimum_required(VERSION 3.16)
project(PulseQt VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)       # 自动处理 Q_OBJECT moc
set(CMAKE_AUTORCC ON)       # 自动处理 .qrc
set(CMAKE_AUTOUIC ON)       # 自动处理 .ui

find_package(Qt6 REQUIRED COMPONENTS Core Widgets Sql SerialPort)

add_executable(pulseqt
    main.cpp
    mainwindow.h mainwindow.cpp
)

target_link_libraries(pulseqt PRIVATE
    Qt6::Core
    Qt6::Widgets
    Qt6::Sql
    Qt6::SerialPort
)
```

## 常见变量

```cmake
${CMAKE_SOURCE_DIR}         # 顶层 CMakeLists.txt 所在
${CMAKE_BINARY_DIR}         # build 目录
${CMAKE_CURRENT_SOURCE_DIR} # 当前 CMakeLists.txt 所在
${PROJECT_NAME}             # 项目名
${PROJECT_VERSION}          # 版本号

# 安装
install(TARGETS myapp DESTINATION bin)
install(FILES config.json DESTINATION etc/myapp)
```

## 调试 CMake

```cmake
message(STATUS &quot;Qt version: ${Qt6_VERSION}&quot;)  # 信息
message(WARNING &quot;Something might be wrong&quot;)     # 警告
message(FATAL_ERROR &quot;Cannot continue&quot;)          # 致命错误

# 打印所有变量
get_cmake_property(_vars VARIABLES)
foreach(_var ${_vars})
    message(STATUS &quot;${_var}=${${_var}}&quot;)
endforeach()
```</content:encoded></item><item><title>C++ 类型转换 — static_cast / dynamic_cast / const_cast / reinterpret_cast</title><link>https://kiyose.wiki/notes/cppcasting/</link><guid isPermaLink="true">https://kiyose.wiki/notes/cppcasting/</guid><description>四种 C++ 强制类型转换的用途、安全性对比、底层实现与常见错误</description><pubDate>Sat, 20 Jan 2024 00:00:00 GMT</pubDate><content:encoded>## 四种 cast 总览

| cast | 用途 | 运行时检查 | 安全性 |
|------|------|:--:|:--:|
| `static_cast` | 相关类型间转换 | ❌ 编译期 | 中 |
| `dynamic_cast` | 多态类型安全向下转型 | ✅ 运行时 | 高 |
| `const_cast` | 添加/移除 const | ❌ | 低（慎用） |
| `reinterpret_cast` | 任意指针/整数互转 | ❌ | 极低（尽量不用） |

## static_cast — 最常用

```cpp
// ① 基本类型转换
double d = 3.14;
int i = static_cast&lt;int&gt;(d);     // 3 — 明确表示&quot;我知道精度损失&quot;

// ② 基类 → 派生类（不检查安全性！）
Base *b = new Derived();
Derived *d = static_cast&lt;Derived *&gt;(b);  // ⚠️ 如果 b 不是 Derived → UB

// ③ void* → T*（合法且安全）
void *ptr = malloc(100);
int *arr = static_cast&lt;int *&gt;(ptr);

// ④ 枚举 ↔ 整数
enum Color { RED = 0, GREEN = 1 };
int val = static_cast&lt;int&gt;(GREEN);
```

## dynamic_cast — 安全的向下转型

```cpp
class Base { virtual ~Base() = default; };  // ⚠️ 必须有虚函数
class Derived : public Base {};

Base *b = new Derived();
if (auto *d = dynamic_cast&lt;Derived *&gt;(b)) {
    d-&gt;derivedMethod();  // 安全
}
// 失败: 返回 nullptr（指针）或抛 std::bad_cast（引用）

// 引用版
Derived &amp;dr = dynamic_cast&lt;Derived &amp;&gt;(*b);  // 失败抛异常

// 交叉转型（多重继承中）
class A { virtual ~A() = default; };
class B { virtual ~B() = default; };
class C : public A, public B {};
B *bp = new C();
A *ap = dynamic_cast&lt;A *&gt;(bp);  // ✅ 可以：C 同时是 A 和 B
```

### 运行时开销

```
dynamic_cast 需要 RTTI（Run-Time Type Information）
- 编译器生成 type_info 表
- 每次 dynamic_cast 查表比较类型
- 开销约为虚函数调用的 5-10 倍

关闭 RTTI: gcc -fno-rtti
→ dynamic_cast 不可用（编译不过）
→ typeid 不可用
```

## const_cast — 去 const

```cpp
// 唯一合法用途：兼容旧 API
void legacyApi(char *str);      // 旧 API 没写 const
const char *msg = &quot;hello&quot;;
legacyApi(const_cast&lt;char *&gt;(msg));  // ✅ 旧 API 实际不修改

// ❌ 危险：试图修改真正的常量
const int x = 42;
int *p = const_cast&lt;int *&gt;(&amp;x);
*p = 100;  // 未定义行为！x 可能被编译器优化为立即数
```

## reinterpret_cast — 最危险

```cpp
// 经典用途：网络编程字节转换
int val = 0x12345678;
char *bytes = reinterpret_cast&lt;char *&gt;(&amp;val);
// 大端: bytes[0]=0x12, 小端: bytes[0]=0x78

// 对象字节级复制
struct Packet { int id; double val; };
Packet pkt{1, 3.14};
char buffer[sizeof(Packet)];
memcpy(buffer, reinterpret_cast&lt;char *&gt;(&amp;pkt), sizeof(pkt));

// ❌ 函数指针互转 → 几乎总是 UB
typedef void (*FuncA)();
typedef int (*FuncB)(int);
FuncA fa = []{};
// FuncB fb = reinterpret_cast&lt;FuncB&gt;(fa);  // UB！
```

## 选择决策树

```
基类 → 派生类（多态）？
  ├─ 是 → dynamic_cast（安全）
  └─ 否 → 相关类型？
            ├─ 是 → static_cast
            └─ 否 → 去/加 const？
                      ├─ 是 → const_cast（确认旧 API 不修改）
                      └─ 否 → 必须这样？
                                ├─ 是 → reinterpret_cast（加注释说明原因）
                                └─ 否 → 重构设计
```

## C 风格转换 vs C++ cast

```cpp
// C 风格 — 什么都干，看不出意图
Derived *d = (Derived *)base;  // 是 static? dynamic? reinterpret?

// C++ — 意图明确
Derived *d = dynamic_cast&lt;Derived *&gt;(base);  // 读代码的人立刻知道：多态转型
int i = static_cast&lt;int&gt;(3.14);             // 数值转换，精度损失
```

**永远用 C++ cast**：grep 搜索时 `static_cast` 可以找到所有转换点，`(type)` 淹没在括号里。</content:encoded></item><item><title>Qt 信号槽入门</title><link>https://kiyose.wiki/notes/signalslot/</link><guid isPermaLink="true">https://kiyose.wiki/notes/signalslot/</guid><description>Qt 核心机制：connect 语法、五种连接方式、Lambda 信号槽与自定义信号</description><pubDate>Mon, 15 Jan 2024 00:00:00 GMT</pubDate><content:encoded>## 核心概念

信号槽是 Qt 最核心的通信机制，替代传统回调函数。

```
对象 A                     对象 B
  │                          │
  │  emit signal(data)       │
  │─────────────────────────→│  slot(data)
  │                          │
    信号发出者              槽接收者
    不需要知道谁在听          不需要知道谁发的
```

## connect 的五种写法

```cpp
// ① Qt4 风格（字符串匹配，编译期不检查 → 不推荐）
connect(sender, SIGNAL(valueChanged(int)),
        receiver, SLOT(onValueChanged(int)));

// ② 函数指针（编译期类型检查 → 推荐）
connect(sender, &amp;Sender::valueChanged,
        receiver, &amp;Receiver::onValueChanged);

// ③ Lambda（最灵活）
connect(sender, &amp;Sender::valueChanged,
        [](int val) { qDebug() &lt;&lt; val; });

// ④ Lambda + this 捕获
connect(button, &amp;QPushButton::clicked, this, [this]() {
    m_label-&gt;setText(&quot;clicked&quot;);
});

// ⑤ 信号连接信号
connect(obj1, &amp;Obj1::signalA, obj2, &amp;Obj2::signalB);
```

## 连接类型

```cpp
connect(a, &amp;A::sig, b, &amp;B::slot,
        Qt::AutoConnection);     // 默认：同线程直调，跨线程排队

connect(a, &amp;A::sig, b, &amp;B::slot,
        Qt::DirectConnection);   // 当场调用（发射线程中执行槽）

connect(a, &amp;A::sig, b, &amp;B::slot,
        Qt::QueuedConnection);   // 排队到接收者线程执行

connect(a, &amp;A::sig, b, &amp;B::slot,
        Qt::BlockingQueuedConnection); // 跨线程同步调用（发射者等槽执行完）
```

| 类型 | 执行线程 | 使用场景 |
|------|----------|----------|
| Auto | 自动判断 | 默认，大多数情况 |
| Direct | 发射者线程 | 同线程，实时性要求高 |
| Queued | 接收者线程 | 跨线程（必须用这个） |
| BlockingQueued | 接收者线程 | 跨线程同步调用 |

## 自定义信号槽

```cpp
class Worker : public QObject {
    Q_OBJECT    // ⚠️ 必须加这个宏
public:
    void doWork() {
        emit progressChanged(50);    // 发射信号
        emit finished(&quot;done&quot;);
    }

signals:       // 信号：只声明，不实现
    void progressChanged(int percent);
    void finished(const QString &amp;result);

public slots:  // 槽：需要实现
    void start() {
        doWork();
    }
};
```

## 注意事项

```cpp
// ❌ 信号和槽的参数类型必须兼容（槽参数可以比信号少）
connect(s, &amp;S::sig(int, string), r, &amp;R::slot(string)); // ❌ 类型不匹配

// ✅ 槽可以忽略尾部参数
connect(s, &amp;S::sig(int, string), r, &amp;R::slot(int));  // ✅ 只用第一个

// ❌ 同一个 connect 重复调用 → 槽被多次触发
// ✅ 用 Qt::UniqueConnection 防止重复
connect(s, &amp;S::sig, r, &amp;R::slot, Qt::UniqueConnection);

// disconnect 断开连接
disconnect(s, &amp;S::sig, r, &amp;R::slot);
```</content:encoded></item><item><title>C++ string_view 与 span</title><link>https://kiyose.wiki/notes/stringviewspan/</link><guid isPermaLink="true">https://kiyose.wiki/notes/stringviewspan/</guid><description>零拷贝字符串视图、缓冲区视图 span、避免悬空与所有权语义</description><pubDate>Tue, 05 Dec 2023 00:00:00 GMT</pubDate><content:encoded>## string_view — 零拷贝的字符串&quot;窗口&quot;

```cpp
#include &lt;string_view&gt;

// 不拥有数据，只是&quot;看&quot;一段字符
string_view sv = &quot;hello&quot;;               // C 字符串
string s = &quot;world&quot;;
string_view sv2 = s;                    // std::string
string_view sv3 = s.substr(0, 3);      // &quot;wor&quot; — 不分配新内存！

// 对比：string::substr 会产生新 string + 堆分配
string sub = s.substr(0, 3);            // 分配 + 拷贝
```

### 核心操作

```cpp
string_view sv = &quot;hello world&quot;;

sv.size();       // 11
sv.empty();      // false
sv[0];           // &apos;h&apos;
sv.front();      // &apos;h&apos;
sv.back();       // &apos;d&apos;

// 子视图（O(1)，不分配）
sv.substr(0, 5);       // &quot;hello&quot;
sv.remove_prefix(6);   // sv 变为 &quot;world&quot;
sv.remove_suffix(3);   // sv 变为 &quot;wo&quot;

// 查找（O(n)）
sv.find(&quot;wo&quot;);         // 返回位置
sv.starts_with(&quot;he&quot;);  // C++20
sv.ends_with(&quot;ld&quot;);    // C++20

// 转换回 string（这时才分配）
string s(sv);
```

### 函数参数 — 首选 string_view

```cpp
// ❌ 老写法 — 只能接受 std::string
void process(const string &amp;s);

// ❌ 或者重载两个
void process(const string &amp;s);
void process(const char *s);

// ✅ 一个解决所有
void process(string_view sv) {
    // 接受：string, const char*, char[], string_view
}

process(&quot;literal&quot;);      // ✅ 不构造临时 string
process(s);              // ✅
process(sv);             // ✅
```

## 悬空陷阱

```cpp
// ⚠️ string_view 不拥有数据 → 必须保证数据比 view 活得久

string_view danger() {
    string s = &quot;temp&quot;;
    return string_view(s);      // ❌ s 析构后 view 悬空
}

string_view safe() {
    return &quot;literal&quot;;           // ✅ 字符串字面量静态存储，永远有效
}

// ⚠️ 临时 string 的陷阱
string_view sv = string(&quot;temp&quot;) + &quot;file&quot;;  // ❌ 临时 string 已析构
string s = string(&quot;temp&quot;) + &quot;file&quot;;
string_view sv2 = s;                        // ✅ s 还在
```

## span — 缓冲区的 string_view

```cpp
#include &lt;span&gt;  // C++20

// 不拥有数据，只是&quot;看&quot;一段连续内存
void process(span&lt;int&gt; data) {
    for (int &amp;x : data) x *= 2;
}

int arr[] = {1, 2, 3, 4, 5};
vector&lt;int&gt; v = {6, 7, 8};

process(arr);   // ✅ 接受 C 数组
process(v);     // ✅ 接受 vector
// 一个接口覆盖所有连续容器
```

### 子区间

```cpp
vector&lt;int&gt; v = {1, 2, 3, 4, 5, 6, 7, 8};
span&lt;int&gt; sp = v;

auto first4  = sp.first(4);      // {1, 2, 3, 4}
auto last3   = sp.last(3);       // {6, 7, 8}
auto middle  = sp.subspan(2, 3); // {3, 4, 5}
```

### 字节视图

```cpp
void writeFile(span&lt;const byte&gt; data) {
    // 接受任何连续内存 → 写入文件
}

struct Header { int id; double timestamp; };
Header hdr{1, 3.14};

// 结构体 → 字节视图
writeFile(as_bytes(span{&amp;hdr, 1}));

// 修改底层数据（不能是 const span）
void zeroInit(span&lt;byte&gt; data) {
    fill(data.begin(), data.end(), byte{0});
}
```

## 对比总结

| | `string` | `string_view` | `vector&lt;T&gt;` | `span&lt;T&gt;` |
|------|:--:|:--:|:--:|:--:|
| 拥有数据 | ✅ | ❌ | ✅ | ❌ |
| 堆分配 | ✅ | ❌ | ✅ | ❌ |
| 可修改 | ✅ | ❌ | ✅ | ✅ (非 const) |
| 适用场景 | 需存储/修改 | 传参/读取 | 需动态扩容 | 传参/子区间 |
| 版本 | C++98 | C++17 | C++98 | C++20 |

## 最佳实践

```cpp
// 函数参数选择决策树：
//  只读字符串参数     → string_view
//  只读连续内存       → span&lt;const T&gt;
//  需要修改的连续内存  → span&lt;T&gt;
//  需要持有的字符串    → string
//  需要动态增长的容器  → vector&lt;T&gt;

// ❌ 不必要的拷贝
void print(const string &amp;s);
void print(const vector&lt;int&gt; &amp;v);

// ✅ 统一的零拷贝接口
void print(string_view s);
void print(span&lt;const int&gt; v);
```</content:encoded></item><item><title>C++ 虚函数与多态</title><link>https://kiyose.wiki/notes/polymorphism/</link><guid isPermaLink="true">https://kiyose.wiki/notes/polymorphism/</guid><description>虚函数表、override/final、纯虚函数、抽象类与运行时多态</description><pubDate>Wed, 15 Nov 2023 00:00:00 GMT</pubDate><content:encoded>## 为什么需要虚函数

```cpp
class Animal {
public:
    void speak() { cout &lt;&lt; &quot;Animal\n&quot;; }  // 普通函数
};

class Dog : public Animal {
public:
    void speak() { cout &lt;&lt; &quot;Woof!\n&quot;; }
};

Animal *a = new Dog();
a-&gt;speak();  // &quot;Animal&quot; ❌ 不是 &quot;Woof!&quot;
// 编译器根据指针类型(Animal)决定调用哪个函数
```

加上 `virtual` 后：

```cpp
class Animal {
public:
    virtual void speak() { cout &lt;&lt; &quot;Animal\n&quot;; }
};

class Dog : public Animal {
public:
    void speak() override { cout &lt;&lt; &quot;Woof!\n&quot;; }
};

Animal *a = new Dog();
a-&gt;speak();  // &quot;Woof!&quot; ✅ 运行时查虚函数表找到 Dog::speak
```

## 虚函数表（vtable）

```
Animal 对象:                  Dog 对象:
┌──────────┐                  ┌──────────┐
│ vptr ────┼──→ vtable       │ vptr ────┼──→ vtable
│  data... │    ┌──────────┐  │  data... │    ┌──────────┐
└──────────┘    │ speak()  │  └──────────┘    │ speak()  │
                └──────────┘                  └──────────┘
                   → Animal::speak               → Dog::speak

每个含虚函数的类有一张 vtable，对象里藏一个 vptr 指向它。
调用 obj-&gt;speak() → 查 vptr → 查 vtable → 调对应函数。
```

## override / final

```cpp
class Base {
public:
    virtual void foo() const {}
    virtual void bar() {}
};

class Derived : public Base {
public:
    void foo() const override {}  // ✅ 明确覆盖 Base::foo
    // void foo() override {}        // ❌ 编译错误：少了 const
    void bar() final {}             // 禁止子类再覆盖
};

class More : public Derived {
public:
    // void bar() override {}        // ❌ 编译错误：bar 是 final
};
```

**规则**：覆盖虚函数永远加 `override`。

## 纯虚函数与抽象类

```cpp
class Shape {
public:
    virtual double area() const = 0;  // 纯虚函数
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double r;
public:
    double area() const override { return 3.14159 * r * r; }
};

// Shape s;           // ❌ 不能实例化抽象类
Shape *p = new Circle(); // ✅ 指针/引用可以
```

## 虚析构函数

```cpp
class Base {
public:
    ~Base() { cout &lt;&lt; &quot;~Base\n&quot;; }
};

class Derived : public Base {
    string data;
public:
    ~Derived() { cout &lt;&lt; &quot;~Derived\n&quot;; }
};

Base *p = new Derived();
delete p;  // 只输出 &quot;~Base&quot; ❌ Derived 的 data 没析构 = 内存泄漏！
```

```cpp
class Base {
public:
    virtual ~Base() { cout &lt;&lt; &quot;~Base\n&quot;; }  // ✅ 虚析构
};

delete p;  // &quot;~Derived&quot; → &quot;~Base&quot; ✅ 正确析构
```

**规则**：有虚函数的基类，析构函数必须 `virtual`（或 `protected` 非虚）。

## 运行期类型识别

```cpp
// dynamic_cast — 安全的向下转型
Base *b = new Derived();
if (auto *d = dynamic_cast&lt;Derived *&gt;(b)) {
    d-&gt;derivedMethod();  // 安全调用派生类方法
}
// 失败返回 nullptr（指针）或抛 bad_cast（引用）

// typeid
if (typeid(*b) == typeid(Derived)) {
    cout &lt;&lt; &quot;是 Derived\n&quot;;
}
```

## 常见陷阱

```cpp
// 陷阱 1: 构造/析构函数中调虚函数
class Base {
public:
    Base() { init(); }         // ❌ 构造函数中调虚函数
    virtual void init() {}
};
class Derived : public Base {
    void init() override { /* 期望执行这个但不会 */ }
};
// 构造时 vptr 还指向 Base → 调的是 Base::init

// 陷阱 2: 默认参数是静态绑定的
class Base {
public:
    virtual void f(int n = 10) { cout &lt;&lt; n; }
};
class Derived : public Base {
public:
    void f(int n = 20) override { cout &lt;&lt; n; }
};
Derived d;
Base *p = &amp;d;
p-&gt;f();  // 输出 10 ← 默认参数来自 Base（静态），函数体来自 Derived（动态）
```

## 何时用虚函数

| 场景 | 用不用 |
|------|:--:|
| 基类指针/引用调用派生类方法 | ✅ 必须 virtual |
| 析构函数（基类有虚函数） | ✅ 必须 virtual |
| 非多态类的小对象 | ❌ 避免 vptr 开销 |
| 模板 — 编译期多态 | ❌ 用 CRTP 代替 runtime 虚函数 |</content:encoded></item><item><title>位运算技巧</title><link>https://kiyose.wiki/notes/bitmanipulation/</link><guid isPermaLink="true">https://kiyose.wiki/notes/bitmanipulation/</guid><description>与或非异或、移位、位掩码、Brian Kernighan、状态压缩 DP 与常见面试题</description><pubDate>Wed, 25 Oct 2023 00:00:00 GMT</pubDate><content:encoded>## 基础操作

```cpp
int a = 0b1010;  // 10

// 位运算
a &amp; 1;           // 取最低位 → 0
a | (1 &lt;&lt; 2);    // 第 2 位置 1 → 1110 (14)
a &amp; ~(1 &lt;&lt; 1);   // 第 1 位清 0 → 1000 (8)
a ^ (1 &lt;&lt; 3);    // 第 3 位翻转 → 0010 (2)

// 移位
a &gt;&gt; 1;          // 右移 = 除以 2 → 5
a &lt;&lt; 1;          // 左移 = 乘以 2 → 20
```

## 实用技巧

```cpp
// 判断奇偶
bool isOdd  = n &amp; 1;        // true = 奇
bool isEven = !(n &amp; 1);

// 乘除 2 的幂（编译器会优化，但显式写更清晰）
int half = n &gt;&gt; 1;          // n / 2
int double= n &lt;&lt; 1;         // n * 2

// 交换两数（不用临时变量）
a ^= b; b ^= a; a ^= b;    // a ↔ b

// 取绝对值（不用分支）
int mask = n &gt;&gt; 31;         // 算术右移：负数为 -1，正数为 0
int abs  = (n + mask) ^ mask;

// 判断是否为 2 的幂
bool isPowerOf2 = n &gt; 0 &amp;&amp; (n &amp; (n - 1)) == 0;
// 8  = 1000, 7 = 0111, 1000 &amp; 0111 = 0000 ✅
// 10 = 1010, 9 = 1001, 1010 &amp; 1001 = 1000 ❌
```

## Brian Kernighan 算法

```cpp
// 统计二进制中 1 的个数（每次消掉最低位的 1）
int popcount(int n) {
    int cnt = 0;
    while (n) { n &amp;= n - 1; ++cnt; }
    return cnt;
}
// 时间 O(k)，k 为 1 的个数（比逐位检查 O(32) 快）
// 内建替代：std::popcount(n) (C++20)
//            __builtin_popcount(n) (GCC/Clang)

// 取出最低位的 1
int lowbit = n &amp; -n;  // 12 &amp; -12 = 4 (1100 → 0100)

// 消掉最低位的 1
n &amp;= n - 1;
```

## 位掩码枚举

```cpp
// 枚举所有子集（状态压缩 DP 基础）
int mask = 0b1101;  // 全集
for (int sub = mask; sub; sub = (sub - 1) &amp; mask) {
    // sub 遍历 mask 的所有非空子集
    // 1101, 1100, 1001, 1000, 0101, 0100, 0001
}
```

## 常见面试题

### 只出现一次的数字

```cpp
// 数组中其他元素出现两次，一个出现一次，找它
int singleNumber(const vector&lt;int&gt; &amp;nums) {
    int ans = 0;
    for (int x : nums) ans ^= x;  // x ^ x = 0, x ^ 0 = x
    return ans;
}
```

### 丢失的数字

```cpp
// [0, n] 中缺了一个数
int missingNumber(const vector&lt;int&gt; &amp;nums) {
    int ans = nums.size();
    for (int i = 0; i &lt; nums.size(); ++i)
        ans ^= i ^ nums[i];       // 把索引和值全异或
    return ans;
}
```

### 两数之和（不用 + 号）

```cpp
int add(int a, int b) {
    while (b) {
        int carry = (unsigned)(a &amp; b) &lt;&lt; 1;  // 进位
        a ^= b;                               // 无进位和
        b = carry;
    }
    return a;
}
```

### 颠倒二进制位

```cpp
uint32_t reverseBits(uint32_t n) {
    n = (n &gt;&gt; 16) | (n &lt;&lt; 16);
    n = ((n &amp; 0xff00ff00) &gt;&gt; 8) | ((n &amp; 0x00ff00ff) &lt;&lt; 8);
    n = ((n &amp; 0xf0f0f0f0) &gt;&gt; 4) | ((n &amp; 0x0f0f0f0f) &lt;&lt; 4);
    n = ((n &amp; 0xcccccccc) &gt;&gt; 2) | ((n &amp; 0x33333333) &lt;&lt; 2);
    n = ((n &amp; 0xaaaaaaaa) &gt;&gt; 1) | ((n &amp; 0x55555555) &lt;&lt; 1);
    return n;
}
// 分治法，O(1) 时间
```

## 使用场景

| 场景 | 技巧 |
|------|------|
| 状态标记 | `bitset&lt;N&gt;` / `int mask` |
| 去重配对 | 异或 `^`（相同归零） |
| 状态压缩 DP | 用 int 的低 N 位表示集合 |
| 权限系统 | `READ=1, WRITE=2, EXEC=4` 组合 |
| O(1) 空间 | 用位替代 bool 数组 |

## 复杂度注意

```cpp
// 位运算都是 O(1)，但左移超过类型宽度是 UB
int x = 1 &lt;&lt; 31;   // ✅ 在 32 位 int 中
int y = 1 &lt;&lt; 32;   // ❌ UB（int 只有 32 位）
int z = 1LL &lt;&lt; 32; // ✅ 用 long long
```</content:encoded></item><item><title>C++ constexpr 与编译期计算</title><link>https://kiyose.wiki/notes/constexpr/</link><guid isPermaLink="true">https://kiyose.wiki/notes/constexpr/</guid><description>constexpr 变量、函数、if constexpr、consteval 与编译期容器</description><pubDate>Fri, 20 Oct 2023 00:00:00 GMT</pubDate><content:encoded>## const vs constexpr

```cpp
const int x = 42;          // 运行时常量（不可修改）
constexpr int y = 42;      // 编译期常量（一定在编译期已知）

int n;
const int a = n;           // ✅ 运行时确定的 const
constexpr int b = n;       // ❌ 编译期不可知

// 泛型编程中 constexpr 的价值：
constexpr int size = 10;
int arr[size];             // ✅ 编译期大小 → 栈上数组
array&lt;int, size&gt; stdArr;   // ✅ 模板参数必须是编译期常量
```

## constexpr 函数

```cpp
// C++11 限制：只能有一条 return 语句
constexpr int square11(int x) { return x * x; }

// C++14 放宽：可以有循环、分支、局部变量
constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i &lt;= n; ++i)
        result *= i;
    return result;
}

constexpr int f5 = factorial(5);  // 编译期计算 = 120

// constexpr 函数也可在运行时调用
int n = 6;
int f6 = factorial(n);            // 运行时计算
```

## if constexpr — C++17 编译期分支

```cpp
// 传统做法：SFINAE 或标签分发，很繁琐
// C++17: 一行搞定

template &lt;typename T&gt;
auto getValue(T t) {
    if constexpr (is_pointer_v&lt;T&gt;)
        return *t;           // 编译期选这路，不需要对非指针类型合法
    else
        return t;
}

int x = 42;
getValue(x);     // int → return x
getValue(&amp;x);    // int* → return *x

// 模板递归的终止条件
template &lt;typename T, typename... Args&gt;
void print(T first, Args... rest) {
    cout &lt;&lt; first;
    if constexpr (sizeof...(rest) &gt; 0) {
        cout &lt;&lt; &quot;, &quot;;
        print(rest...);           // 只在有剩余参数时实例化
    }
}
```

## consteval — C++20 即时函数

```cpp
// constexpr: 可能编译期也可能运行期
// consteval: 必须在编译期执行

consteval int compileOnly(int n) {
    return n * n;
}

constexpr int x = compileOnly(5);  // ✅ 编译期

int n;
// int y = compileOnly(n);          // ❌ 编译错误：不能在运行期调用

// 使用场景：确保某个计算一定在编译期完成
consteval size_t strlen_ct(const char *s) {
    size_t n = 0;
    while (*s++) ++n;
    return n;
}
```

## constexpr 容器（C++20）

```cpp
// vector 和 string 可以在 constexpr 中使用！
constexpr int sumFirstN(int n) {
    vector&lt;int&gt; v;
    for (int i = 1; i &lt;= n; ++i)
        v.push_back(i);
    int total = 0;
    for (int x : v) total += x;
    return total;
}
// ⚠️ constexpr 中 new 的内存必须在编译期内 delete

constexpr int s100 = sumFirstN(100);  // 5050，编译期计算
```

## 实战场景

### 场景 1：编译期字符串哈希

```cpp
constexpr uint64_t hash(const char *s) {
    uint64_t h = 14695981039346656037ULL;
    while (*s) {
        h ^= static_cast&lt;uint64_t&gt;(*s++);
        h *= 1099511628211ULL;
    }
    return h;
}

switch (hash(str)) {  // 编译期哈希 → switch 直接用于字符串匹配
    case hash(&quot;start&quot;): /*...*/ break;
    case hash(&quot;stop&quot;):  /*...*/ break;
}
```

### 场景 2：编译期查表

```cpp
// 编译期生成 sin 值表
template &lt;size_t N&gt;
constexpr array&lt;double, N&gt; sinTable() {
    array&lt;double, N&gt; table{};
    for (size_t i = 0; i &lt; N; ++i)
        table[i] = sin(2 * M_PI * i / N);
    return table;
}

constexpr auto sinLUT = sinTable&lt;360&gt;();  // 360 个值在编译期算完
double fastSin(int deg) { return sinLUT[deg % 360]; }
```

### 场景 3：类型安全的单元量纲

```cpp
template &lt;int M, int K, int S&gt;
struct Unit {
    double value;

    template &lt;int M2, int K2, int S2&gt;
    constexpr auto operator*(Unit&lt;M2, K2, S2&gt; other) const {
        return Unit&lt;M + M2, K + K2, S + S2&gt;{value * other.value};
    }
};

using Meter = Unit&lt;1, 0, 0&gt;;
using Second = Unit&lt;0, 0, 1&gt;;
using Speed = Unit&lt;1, 0, -1&gt;;   // m/s

constexpr Meter m{10};
constexpr Second s{2};
constexpr Speed v = m / s;  // 编译期计算：Unit&lt;1,0,-1&gt; 类型安全
```

## 速查：各版本能力

| 版本 | constexpr 能力 |
|------|---------------|
| C++11 | 变量、单 return 函数 |
| C++14 | 循环、分支、局部变量 |
| C++17 | `if constexpr`、Lambda 可为 constexpr |
| C++20 | `consteval`、`constinit`、vector/string 在 constexpr 中 |
| C++23 | `if consteval`、constexpr 虚函数 |</content:encoded></item><item><title>双指针与滑动窗口</title><link>https://kiyose.wiki/notes/twopointers/</link><guid isPermaLink="true">https://kiyose.wiki/notes/twopointers/</guid><description>快慢指针、左右指针、定长/变长滑动窗口模板与经典题解</description><pubDate>Fri, 22 Sep 2023 00:00:00 GMT</pubDate><content:encoded>## 三种双指针模式

| 模式 | 特点 | 典型场景 |
|------|------|---------|
| 快慢指针 | 同向，速度不同 | 链表环检测、去重 |
| 左右指针 | 从两端向中间 | 有序数组两数之和、反转 |
| 滑动窗口 | 窗口边界单向移动 | 子串/子数组问题 |

## 快慢指针

```cpp
// 链表是否有环（Floyd 判圈）
bool hasCycle(ListNode *head) {
    auto *slow = head, *fast = head;
    while (fast &amp;&amp; fast-&gt;next) {
        slow = slow-&gt;next;
        fast = fast-&gt;next-&gt;next;
        if (slow == fast) return true;
    }
    return false;
}
// 如果有环，慢指针入环前走 a 步，环长 b
// 相遇时慢指针走了 a + x，快指针 a + x + nb
// → a + x = nb → 慢指针再走 a 步到环入口

// 有序数组去重 in-place
int removeDuplicates(vector&lt;int&gt; &amp;nums) {
    if (nums.empty()) return 0;
    int slow = 0;
    for (int fast = 1; fast &lt; nums.size(); ++fast)
        if (nums[fast] != nums[slow])
            nums[++slow] = nums[fast];
    return slow + 1;
}
```

## 左右指针

```cpp
// 有序数组两数之和
vector&lt;int&gt; twoSum(const vector&lt;int&gt; &amp;nums, int target) {
    int l = 0, r = nums.size() - 1;
    while (l &lt; r) {
        int sum = nums[l] + nums[r];
        if (sum == target) return {l, r};
        else if (sum &lt; target) ++l;
        else --r;
    }
    return {};
}
// 时间 O(n)

// 反转数组 [l, r] 区间
void reverseRange(vector&lt;int&gt; &amp;nums, int l, int r) {
    while (l &lt; r) swap(nums[l++], nums[r--]);
}

// 盛最多水的容器
int maxArea(const vector&lt;int&gt; &amp;height) {
    int l = 0, r = height.size() - 1, ans = 0;
    while (l &lt; r) {
        ans = max(ans, min(height[l], height[r]) * (r - l));
        height[l] &lt; height[r] ? ++l : --r;  // 矮的移动
    }
    return ans;
}
```

## 滑动窗口模板

```cpp
// 变长窗口 — 找满足条件的最短/最长子数组
int slidingWindow(const vector&lt;int&gt; &amp;nums, int target) {
    int l = 0, sum = 0, ans = INT_MAX;
    for (int r = 0; r &lt; nums.size(); ++r) {
        sum += nums[r];                        // 扩展右边界
        while (sum &gt;= target) {                // 收缩左边界
            ans = min(ans, r - l + 1);
            sum -= nums[l++];
        }
    }
    return ans == INT_MAX ? 0 : ans;
}
// 时间 O(n)：每个元素最多被加入和移除各一次
```

### 定长窗口

```cpp
// 长度为 k 的最大平均值子数组
double maxAvgSubarray(const vector&lt;int&gt; &amp;nums, int k) {
    double sum = accumulate(nums.begin(), nums.begin() + k, 0.0);
    double ans = sum;
    for (int i = k; i &lt; nums.size(); ++i) {
        sum += nums[i] - nums[i - k];    // 滑入 + 滑出
        ans = max(ans, sum);
    }
    return ans / k;
}
```

## 经典题——无重复字符最长子串

```cpp
int lengthOfLongestSubstring(const string &amp;s) {
    vector&lt;int&gt; last(256, -1);  // 字符最后出现位置
    int l = 0, ans = 0;
    for (int r = 0; r &lt; s.size(); ++r) {
        l = max(l, last[s[r]] + 1);    // 有重复 → 跳转左边界
        ans = max(ans, r - l + 1);
        last[s[r]] = r;
    }
    return ans;
}
```

## 经典题——最小覆盖子串

```cpp
string minWindow(const string &amp;s, const string &amp;t) {
    vector&lt;int&gt; need(128, 0), have(128, 0);
    for (char c : t) need[c]++;
    int missing = t.size(), l = 0, start = 0, len = INT_MAX;

    for (int r = 0; r &lt; s.size(); ++r) {
        if (have[s[r]]++ &lt; need[s[r]]) missing--;
        while (missing == 0) {                     // 全覆盖了
            if (r - l + 1 &lt; len) { start = l; len = r - l + 1; }
            if (--have[s[l]] &lt; need[s[l]]) missing++;
            l++;
        }
    }
    return len == INT_MAX ? &quot;&quot; : s.substr(start, len);
}
```

## 复杂度分析

所有滑动窗口问题：**时间 O(n)**，每个元素进窗口一次出窗口一次。**空间 O(k)**，k 为字符集大小。</content:encoded></item><item><title>C++ 类型推导 — auto 与 decltype</title><link>https://kiyose.wiki/notes/autodecltype/</link><guid isPermaLink="true">https://kiyose.wiki/notes/autodecltype/</guid><description>auto、decltype、decltype(auto)、尾置返回类型与模板类型推导规则</description><pubDate>Sun, 10 Sep 2023 00:00:00 GMT</pubDate><content:encoded>## auto — 自动推导

```cpp
auto i = 42;              // int
auto d = 3.14;            // double
auto s = &quot;hello&quot;s;        // std::string
auto v = vector{1,2,3};   // vector&lt;int&gt;

// ⚠️ auto 会丢掉引用和顶层 const
const int &amp;cr = i;
auto x = cr;       // int（丢掉了 const 和 &amp;）
auto &amp;y = cr;      // const int &amp;（保留引用，const 也随之保留）

// 保持 const
const auto z = cr; // const int

// C++14: auto 函数返回
auto sum(int a, int b) { return a + b; }

// C++20: auto 函数参数（简写模板）
// void foo(auto x) { }  等价于 template&lt;typename T&gt; void foo(T x) { }
```

### auto 推导规则 = 模板推导规则

```cpp
template &lt;typename T&gt;
void foo(T param);   // 传值 → 丢掉 const、引用、volatile

template &lt;typename T&gt;
void bar(T &amp;param);  // 传引用 → 保留 const，T 推导为 const 类型
```

## decltype — 获取声明类型

```cpp
int x = 42;
decltype(x) y = 10;           // int

const int &amp;cr = x;
decltype(cr) z = x;           // const int &amp;（原封不动保留）

// decltype(表达式) — 表达式的值类别影响结果
decltype(x)    a = x;         // int（x 是变量名）
decltype((x))  b = x;         // int &amp;（(x) 是左值表达式！）

// 函数返回值
int foo();
decltype(foo()) result = foo(); // int

// 推导成员类型
vector&lt;int&gt; v;
decltype(v)::value_type val;    // int
decltype(v.begin()) it;         // vector&lt;int&gt;::iterator
```

## decltype(auto) — C++14 的精确转发

```cpp
// 场景：想完美转发函数的返回值类型
int &amp;getRef();
int getVal();

// ❌ auto 丢掉引用
auto r1 = getRef();          // int

// ❌ decltype 需要重复表达式
decltype(getRef()) r2 = getRef();  // int &amp;，但写了两遍

// ✅ decltype(auto) — 精确推导，不丢引用
decltype(auto) r3 = getRef();  // int &amp;
decltype(auto) r4 = getVal();  // int
```

### 实用：完美转发函数返回值

```cpp
template &lt;typename F, typename... Args&gt;
decltype(auto) invoke(F &amp;&amp;f, Args &amp;&amp;...args) {
    return forward&lt;F&gt;(f)(forward&lt;Args&gt;(args)...);
}
```

## 尾置返回类型

```cpp
// 需要 decltype 参数 → 参数在返回类型后面
template &lt;typename T, typename U&gt;
auto multiply(T &amp;&amp;a, U &amp;&amp;b) -&gt; decltype(a * b) {
    return a * b;
}

// C++14 简化
template &lt;typename T, typename U&gt;
decltype(auto) multiply(T &amp;&amp;a, U &amp;&amp;b) {
    return a * b;
}

// 实际场景：lambda 中
auto cmp = [](const auto &amp;a, const auto &amp;b) -&gt; decltype(a &lt; b) {
    return a &lt; b;
};
```

## 实际应用

```cpp
// 1. 范围 for
for (const auto &amp;item : container) { }    // 避免拷贝
for (auto &amp;&amp;item : container) { }         // 万能引用

// 2. 结构化绑定（C++17）
map&lt;string, int&gt; m;
for (auto &amp;&amp;[key, value] : m) { }  // key=const string&amp;, value=int&amp;

auto [it, inserted] = m.insert({&quot;x&quot;, 1});
// it = map::iterator, inserted = bool

// 3. 简化迭代器
auto it = v.begin();  // 替代 vector&lt;int&gt;::iterator

// 4. 通用 Lambda（C++14）
auto genericLambda = [](const auto &amp;x, const auto &amp;y) {
    return x + y;
};
```

## 速查表

| 写法 | 行为 | 保留引用 | 保留 const |
|------|------|:--:|:--:|
| `auto` | 值语义推导 | ❌ | ❌ |
| `auto &amp;` | 左值引用 | ✅ | ✅ |
| `const auto &amp;` | const 左值引用 | ✅ | ✅ |
| `auto &amp;&amp;` | 万能引用 | 取决于初始化值 | 取决于初始化值 |
| `decltype(expr)` | 精确推导表达式类型 | ✅ | ✅ |
| `decltype(var)` | 精确推导变量声明类型 | ✅ | ✅ |
| `decltype((var))` | 总是左值引用 | ✅ | ✅ |
| `decltype(auto)` | 同 decltype，但免写表达式 | ✅ | ✅ |

## 不要过度使用

```cpp
// ❌ — 类型信息丢失，可读性下降
auto result = someFunction();
// result 是什么？得跳转到 someFunction 才知道

// ✅ — 类型明确或无关紧要时用 auto
auto it = map.find(key);
for (const auto &amp;x : vec) ...
```</content:encoded></item><item><title>C++ 异常处理</title><link>https://kiyose.wiki/notes/exception/</link><guid isPermaLink="true">https://kiyose.wiki/notes/exception/</guid><description>try/catch/throw、noexcept、RAII 异常安全保证与最佳实践</description><pubDate>Fri, 25 Aug 2023 00:00:00 GMT</pubDate><content:encoded>## 基础语法

```cpp
void mightFail() {
    throw std::runtime_error(&quot;something went wrong&quot;);
}

try {
    mightFail();
} catch (const std::runtime_error &amp;e) {
    std::cerr &lt;&lt; e.what() &lt;&lt; &apos;\n&apos;;
} catch (const std::exception &amp;e) {
    std::cerr &lt;&lt; &quot;std exception: &quot; &lt;&lt; e.what() &lt;&lt; &apos;\n&apos;;
} catch (...) {
    std::cerr &lt;&lt; &quot;unknown exception\n&quot;;
}
```

## 标准异常层次

```
std::exception
├── std::logic_error         # 编程逻辑错误
│   ├── std::invalid_argument
│   ├── std::out_of_range
│   └── std::length_error
└── std::runtime_error       # 运行时错误
    ├── std::range_error
    ├── std::overflow_error
    └── std::system_error
```

```cpp
// 抛合适的异常
if (index &gt;= vec.size())
    throw std::out_of_range(&quot;index out of range&quot;);
if (ptr == nullptr)
    throw std::invalid_argument(&quot;null pointer&quot;);

// 自定义异常
class DatabaseError : public std::runtime_error {
public:
    explicit DatabaseError(const std::string &amp;msg)
        : std::runtime_error(&quot;DB: &quot; + msg) {}
};
```

## noexcept — 不抛异常标记

```cpp
void noFail() noexcept { /* 保证不抛异常 */ }

// 移动构造/赋值必须 noexcept（vector 扩容检测此标记）
class MyClass {
public:
    MyClass(MyClass &amp;&amp;other) noexcept : data(other.data) {
        other.data = nullptr;
    }
};

// 条件 noexcept
template &lt;typename T&gt;
void swap(T &amp;a, T &amp;b) noexcept(noexcept(a = std::move(b))) {
    T tmp = std::move(a);
    a = std::move(b);
    b = std::move(tmp);
}

// 编译期检查
static_assert(noexcept(noFail()));
```

## RAII 与异常安全

```cpp
// ❌ 不安全 — 异常导致资源泄漏
void unsafe() {
    auto *f = fopen(&quot;data.txt&quot;, &quot;r&quot;);   // 打开文件
    processFile(f);                      // 可能抛异常
    fclose(f);                           // 永远执行不到 → 泄漏
}

// ✅ RAII — 异常安全
void safe() {
    std::ifstream f(&quot;data.txt&quot;);        // RAII 包装
    processFile(f);
}  // f 析构 → 自动调用 close，异常也保证执行

// ✅ 智能指针同理
void safePtr() {
    auto p = std::make_unique&lt;Resource&gt;();
    mightFail();  // 抛异常 → p 自动 delete
}
```

### 异常安全三等级

| 等级 | 语义 | 例子 |
|------|------|------|
| **基本** | 不泄漏，对象状态有效（可能改过） | 大多数 STL 操作 |
| **强** | 失败回滚到调用前状态 | `vector::push_back` 失败 → 不变 |
| **不抛** | 绝不抛异常 | `swap`、析构函数 |

```cpp
// 强异常安全模式：copy-and-swap
class MyVec {
    void push_back(const T &amp;val) {
        auto tmp = m_data;          // 先拷贝
        tmp.push_back(val);         // 在新副本上操作
        m_data.swap(tmp);           // 交换（不抛异常）
    }  // 如果 push_back 抛异常，m_data 未变
};
```

## 最佳实践

```cpp
// ✅ 按 const 引用捕获
catch (const std::exception &amp;e)

// ✅ 析构函数绝不抛异常（默认 noexcept）
~MyClass() /* noexcept */ { /* ... */ }

// ✅ 构造函数抛异常前释放已分配资源
// 用 RAII 成员变量 → 自动处理

// ❌ 不要在析构函数中抛异常
~BadClass() {
    throw std::runtime_error(&quot;...&quot;);  // 💥 std::terminate
}

// ✅ 如果析构中可能失败，吞掉或记录
~FileWriter() {
    try { flush(); }
    catch (...) { /* 记录日志，不重抛 */ }
}
```

## 何时用异常

| 用异常 | 不用异常 |
|--------|----------|
| 构造函数失败 | 性能热点（noexcept 保证） |
| 深层调用栈错误传播 | 预期中的失败（std::optional） |
| 罕见的、不可恢复的错误 | 嵌入式 / -fno-exceptions 环境 |</content:encoded></item><item><title>贪心算法</title><link>https://kiyose.wiki/notes/greedy/</link><guid isPermaLink="true">https://kiyose.wiki/notes/greedy/</guid><description>贪心选择性质、最优子结构、活动选择/Huffman/区间调度与正确性证明</description><pubDate>Tue, 22 Aug 2023 00:00:00 GMT</pubDate><content:encoded>## 核心思想

每一步做**当前看起来最好**的选择，不回溯，不 DP。

```
能用贪心解决的问题必须满足：
1. 贪心选择性质：局部最优 → 全局最优
2. 最优子结构：子问题的最优解包含在全局最优中

⚠️ 贪心不总是正确 — 错误场景：
  硬币面额 [1, 3, 4]，找零 6
  贪心: 4+1+1 = 3 枚
  最优: 3+3 = 2 枚 ❌
```

## 区间调度（活动选择）

```cpp
// 给定 n 个区间 [start, end]，选最多的不重叠区间
int maxNonOverlap(vector&lt;pair&lt;int,int&gt;&gt; &amp;intervals) {
    // 按结束时间排序（贪心：最早结束的先选）
    sort(intervals.begin(), intervals.end(),
         [](auto &amp;a, auto &amp;b) { return a.second &lt; b.second; });

    int count = 0, lastEnd = INT_MIN;
    for (auto [s, e] : intervals) {
        if (s &gt;= lastEnd) {
            ++count;
            lastEnd = e;
        }
    }
    return count;
}
// 时间 O(n log n)，空间 O(1)

// 变体：最少箭射爆气球 = 合并重叠区间
int minArrows(vector&lt;pair&lt;int,int&gt;&gt; &amp;points) {
    sort(points.begin(), points.end(),
         [](auto &amp;a, auto &amp;b) { return a.second &lt; b.second; });
    int arrows = 0;
    long long last = LLONG_MIN;
    for (auto [s, e] : points)
        if (s &gt; last) { ++arrows; last = e; }
    return arrows;
}
```

## Huffman 编码

```cpp
// 构建最优前缀码，最小化加权路径长度
struct Node { int freq; Node *left, *right; };

Node *buildHuffman(const vector&lt;int&gt; &amp;freqs) {
    // 小顶堆，按频率排序
    auto cmp = [](Node *a, Node *b) { return a-&gt;freq &gt; b-&gt;freq; };
    priority_queue&lt;Node *, vector&lt;Node *&gt;, decltype(cmp)&gt; pq(cmp);

    for (int f : freqs) pq.push(new Node{f, nullptr, nullptr});

    while (pq.size() &gt; 1) {
        auto *left = pq.top(); pq.pop();
        auto *right = pq.top(); pq.pop();
        pq.push(new Node{left-&gt;freq + right-&gt;freq, left, right});
    }
    return pq.top();
}
// 时间 O(n log n)
```

## 跳跃游戏

```cpp
// [2,3,1,1,4] → true（从索引 0 可以跳到末尾）
bool canJump(const vector&lt;int&gt; &amp;nums) {
    int farthest = 0;
    for (int i = 0; i &lt; nums.size(); ++i) {
        if (i &gt; farthest) return false;   // 当前位置不可达
        farthest = max(farthest, i + nums[i]);
    }
    return true;
}
// 时间 O(n)，空间 O(1)
// 贪心核心：每一步维护&quot;最远可达位置&quot;
```

## 加油站问题

```cpp
// gas=[1,2,3,4,5], cost=[3,4,5,1,2] → 从索引 3 出发可绕一圈
int canCompleteCircuit(const vector&lt;int&gt; &amp;gas, const vector&lt;int&gt; &amp;cost) {
    int total = 0, cur = 0, start = 0;
    for (int i = 0; i &lt; gas.size(); ++i) {
        int diff = gas[i] - cost[i];
        total += diff;
        cur += diff;
        if (cur &lt; 0) {        // 从 start 到 i 无法走完
            start = i + 1;    // 尝试从下一个开始
            cur = 0;
        }
    }
    return total &gt;= 0 ? start : -1;
}
```

## 贪心 vs DP

| | 贪心 | DP |
|------|------|-----|
| 决策 | 只看当前最优 | 考虑所有子问题 |
| 回溯 | 不回溯 | 记录所有状态 |
| 时间复杂度 | 通常 O(n log n) | 通常 O(n²) 或更高 |
| 正确性 | 需要证明 | 自然正确 |

**判断能否贪心**：尝试举反例。如果能找到贪心失效的例子，就必须用 DP。</content:encoded></item><item><title>C++ 模板基础</title><link>https://kiyose.wiki/notes/template/</link><guid isPermaLink="true">https://kiyose.wiki/notes/template/</guid><description>函数模板、类模板、非类型参数、模板特化与 SFINAE 入门</description><pubDate>Thu, 10 Aug 2023 00:00:00 GMT</pubDate><content:encoded>## 函数模板

```cpp
// 不用模板 → 写 N 个重载
int max(int a, int b) { return a &gt; b ? a : b; }
double max(double a, double b) { return a &gt; b ? a : b; }

// 用模板 → 一个搞定所有类型
template &lt;typename T&gt;
T max(T a, T b) {
    return a &gt; b ? a : b;
}

auto r1 = max(3, 5);        // T = int
auto r2 = max(3.14, 2.71);  // T = double
auto r3 = max&lt;float&gt;(3, 5.0); // 显式指定 T = float

// 多个模板参数
template &lt;typename T, typename U&gt;
auto add(T a, U b) -&gt; decltype(a + b) {   // C++11 后置返回类型
    return a + b;
}

// C++14 简化：auto 自动推导返回类型
template &lt;typename T, typename U&gt;
auto add(T a, U b) {
    return a + b;
}
```

## 类模板

```cpp
template &lt;typename T&gt;
class Stack {
    vector&lt;T&gt; data;
public:
    void push(const T &amp;val) { data.push_back(val); }
    T pop() {
        T val = data.back();
        data.pop_back();
        return val;
    }
    bool empty() const { return data.empty(); }
};

Stack&lt;int&gt; intStack;
Stack&lt;string&gt; strStack;
```

### 非类型模板参数

```cpp
// 编译期固定大小的数组
template &lt;typename T, size_t N&gt;
class Array {
    T data[N];
public:
    T &amp;operator[](size_t i) { return data[i]; }
    size_t size() const { return N; }
};

Array&lt;int, 10&gt; arr;    // 10 个 int，栈上分配
Array&lt;double, 5&gt; arr2; // 5 个 double
```

## 模板特化

```cpp
// 通用模板
template &lt;typename T&gt;
class Traits {
public:
    static const char *name() { return &quot;unknown&quot;; }
};

// 全特化
template &lt;&gt;
class Traits&lt;int&gt; {
public:
    static const char *name() { return &quot;int&quot;; }
};

template &lt;&gt;
class Traits&lt;double&gt; {
public:
    static const char *name() { return &quot;double&quot;; }
};

cout &lt;&lt; Traits&lt;int&gt;::name();    // &quot;int&quot;
cout &lt;&lt; Traits&lt;float&gt;::name();  // &quot;unknown&quot; (走通用模板)
```

## SFINAE 入门

**S**ubstitution **F**ailure **I**s **N**ot **A**n **E**rror — 替换失败不是错误。

```cpp
// 仅在 T 有 .size() 方法时启用
template &lt;typename T&gt;
auto getSize(const T &amp;t) -&gt; decltype(t.size()) {
    return t.size();
}

// C++20 Concept 替代方案（更可读）
// template &lt;typename T&gt;
//     requires requires(T t) { t.size(); }
// auto getSize(const T &amp;t) { return t.size(); }
```

## 常见陷阱

```cpp
// ❌ 模板定义放在 .cpp → 链接错误
// ✅ 模板定义必须放在头文件（或显式实例化）

// ❌ 忘记 typename
template &lt;typename T&gt;
void foo() {
    typename T::value_type x;  // 告诉编译器 T::value_type 是类型
}

// ❌ 模板递归太深 → 编译超时
// ✅ 考虑用折叠表达式（C++17）替代递归
```</content:encoded></item><item><title>回溯算法</title><link>https://kiyose.wiki/notes/backtracking/</link><guid isPermaLink="true">https://kiyose.wiki/notes/backtracking/</guid><description>递归枚举、剪枝策略、排列/组合/子集/N 皇后模板与复杂度计算</description><pubDate>Thu, 20 Jul 2023 00:00:00 GMT</pubDate><content:encoded>## 核心思想

回溯 = 试错 + 撤销。在搜索空间树上 DFS，每一步有多个选择，不满足约束就回退。

```
典型解题模式：
  void backtrack(路径, 选择列表)
      if 满足结束条件: 记录结果; return
      for 选项 in 选择列表
          if 选项非法: continue      ← 剪枝
          做选择
          backtrack(路径, 新选择列表)
          撤销选择
```

## 子集 (Subsets)

```cpp
// [1,2,3] → [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
vector&lt;vector&lt;int&gt;&gt; subsets(vector&lt;int&gt; &amp;nums) {
    vector&lt;vector&lt;int&gt;&gt; res;
    vector&lt;int&gt; path;
    function&lt;void(int)&gt; dfs = [&amp;](int start) {
        res.push_back(path);           // 每个节点都记录
        for (int i = start; i &lt; nums.size(); ++i) {
            path.push_back(nums[i]);
            dfs(i + 1);                // i+1：不重复选
            path.pop_back();
        }
    };
    dfs(0);
    return res;
}
// 复杂度：O(n × 2ⁿ) — 2ⁿ 个子集，每个需要 O(n) 拷贝
```

## 排列 (Permutations)

```cpp
// [1,2,3] → 3! = 6 种排列
vector&lt;vector&lt;int&gt;&gt; permute(vector&lt;int&gt; &amp;nums) {
    vector&lt;vector&lt;int&gt;&gt; res;
    vector&lt;int&gt; path;
    vector&lt;bool&gt; used(nums.size(), false);

    function&lt;void()&gt; dfs = [&amp;]() {
        if (path.size() == nums.size()) {
            res.push_back(path);
            return;
        }
        for (int i = 0; i &lt; nums.size(); ++i) {
            if (used[i]) continue;     // 已选过，跳过
            used[i] = true;
            path.push_back(nums[i]);
            dfs();
            path.pop_back();
            used[i] = false;
        }
    };
    dfs();
    return res;
}
// 复杂度：O(n × n!) — n! 个排列
```

### 去重排列

```cpp
// [1,1,2] → [[1,1,2],[1,2,1],[2,1,1]]
// 关键：同层去重
sort(nums.begin(), nums.end());
// 在 for 循环中加:
if (i &gt; 0 &amp;&amp; nums[i] == nums[i-1] &amp;&amp; !used[i-1]) continue;
// 解释：相同元素，前一个没用过说明是同层重复
```

## 组合 (Combinations)

```cpp
// C(n, k) — 从 n 个中选 k 个
vector&lt;vector&lt;int&gt;&gt; combine(int n, int k) {
    vector&lt;vector&lt;int&gt;&gt; res;
    vector&lt;int&gt; path;
    function&lt;void(int)&gt; dfs = [&amp;](int start) {
        if (path.size() == k) { res.push_back(path); return; }
        // 剪枝：剩余不够 k 个
        for (int i = start; i &lt;= n - (k - path.size()) + 1; ++i) {
            path.push_back(i);
            dfs(i + 1);
            path.pop_back();
        }
    };
    dfs(1);
    return res;
}
```

## N 皇后

```cpp
vector&lt;vector&lt;string&gt;&gt; solveNQueens(int n) {
    vector&lt;vector&lt;string&gt;&gt; res;
    vector&lt;string&gt; board(n, string(n, &apos;.&apos;));
    vector&lt;bool&gt; col(n), diag1(2*n), diag2(2*n);  // 列 + 两个对角线

    function&lt;void(int)&gt; dfs = [&amp;](int row) {
        if (row == n) { res.push_back(board); return; }
        for (int c = 0; c &lt; n; ++c) {
            if (col[c] || diag1[row+c] || diag2[row-c+n]) continue;
            col[c] = diag1[row+c] = diag2[row-c+n] = true;
            board[row][c] = &apos;Q&apos;;
            dfs(row + 1);
            board[row][c] = &apos;.&apos;;
            col[c] = diag1[row+c] = diag2[row-c+n] = false;
        }
    };
    dfs(0);
    return res;
}
// 对角线索引：主对角 row+col 相同，反对角 row-col 相同
```

## 剪枝策略总结

| 策略 | 示例 |
|------|------|
| 跳过已用元素 | `used[i]` |
| 同层去重 | `nums[i]==nums[i-1] &amp;&amp; !used[i-1]` |
| 剩余不够 | `n - start + 1 &lt; k - path.size()` |
| 提前判断非法 | N 皇后的对角线检查 |
| 排序后剪枝 | 组合总和问题 sort + break |

## 复杂度快速估算

```
排列：O(n!)
组合：O(C(n,k))
子集：O(2ⁿ)
N 皇后：O(n!) 但剪枝后远小于
```</content:encoded></item><item><title>C++ 并发编程</title><link>https://kiyose.wiki/notes/concurrency/</link><guid isPermaLink="true">https://kiyose.wiki/notes/concurrency/</guid><description>std::thread、mutex、condition_variable、async/future 与原子操作</description><pubDate>Mon, 10 Jul 2023 00:00:00 GMT</pubDate><content:encoded>## 创建线程

```cpp
#include &lt;thread&gt;
#include &lt;iostream&gt;

void worker(int n) { std::cout &lt;&lt; &quot;Thread: &quot; &lt;&lt; n &lt;&lt; &apos;\n&apos;; }

// 传函数指针
std::thread t1(worker, 42);

// Lambda
std::thread t2([](int a, int b) { std::cout &lt;&lt; a + b; }, 3, 5);

// 成员函数
struct Task { void run() { /*...*/ } };
Task task;
std::thread t3(&amp;Task::run, &amp;task);

// 必须 join 或 detach
t1.join();   // 等待线程结束
t2.detach(); // 分离（后台运行，不 join —— 谨慎使用）

// ⚠️ 析构时未 join/detach 的 thread → std::terminate
```

## std::mutex — 互斥锁

```cpp
#include &lt;mutex&gt;

std::mutex mtx;
int counter = 0;

void increment() {
    std::lock_guard&lt;std::mutex&gt; lock(mtx);  // RAII 自动解锁
    ++counter;
}  // lock 析构 → 自动 mtx.unlock()

// lock_guard vs unique_lock
std::lock_guard&lt;std::mutex&gt; lg(mtx);       // 构造锁定，不可手动解锁
std::unique_lock&lt;std::mutex&gt; ul(mtx);      // 可手动 ul.unlock() / ul.lock()
std::unique_lock&lt;std::mutex&gt; ul2(mtx, std::defer_lock); // 构造不锁
ul2.lock();    // 手动锁
ul2.unlock();  // 手动解锁

// 同时锁多个 mutex（避免死锁）
std::scoped_lock lock(mtx1, mtx2);  // C++17
```

## condition_variable — 条件变量

```cpp
std::mutex mtx;
std::condition_variable cv;
bool ready = false;

// 等待方
void consumer() {
    std::unique_lock&lt;std::mutex&gt; lock(mtx);
    cv.wait(lock, [] { return ready; });  // 等待 ready == true
    // do work...
}

// 通知方
void producer() {
    {
        std::lock_guard&lt;std::mutex&gt; lock(mtx);
        ready = true;
    }
    cv.notify_one();  // 唤醒一个等待者
    // cv.notify_all(); // 唤醒所有
}
```

## std::atomic — 无锁原子操作

```cpp
#include &lt;atomic&gt;

std::atomic&lt;int&gt; ai{0};      // 原子整型
std::atomic&lt;bool&gt; flag{false};

ai.fetch_add(1);             // 原子 +1
ai.store(42);                // 原子写入
int val = ai.load();         // 原子读取

// CAS（Compare-And-Swap）
int expected = 0;
if (ai.compare_exchange_strong(expected, 1)) {
    // 成功：ai 从 0 变为 1
} else {
    // 失败：ai 不是 0，expected 被更新为 ai 当前值
}

// memory order（默认 sequential consistency，安全但略慢）
ai.store(42, std::memory_order_relaxed);  // 无同步要求
ai.store(42, std::memory_order_release);  // 释放语义
ai.load(std::memory_order_acquire);       // 获取语义
```

## std::async 与 std::future

```cpp
#include &lt;future&gt;

// 异步执行，返回 future
std::future&lt;int&gt; result = std::async(std::launch::async, []() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
});

// 获取结果（阻塞直到完成）
int val = result.get();  // 42
// ⚠️ get() 只能调一次，第二次抛异常

// 共享 future
std::shared_future&lt;int&gt; sf = result.share();
// sf.get() 可多次调用

// promise → future
std::promise&lt;int&gt; promise;
std::future&lt;int&gt; future = promise.get_future();

std::thread([&amp;promise]() {
    promise.set_value(100);  // 设置结果
}).detach();

int res = future.get();  // 100
```

## 线程安全模式

### 单例 double-check

```cpp
class Singleton {
    static std::atomic&lt;Singleton *&gt; instance;
    static std::mutex mtx;
public:
    static Singleton *get() {
        Singleton *p = instance.load(std::memory_order_acquire);
        if (!p) {
            std::lock_guard lock(mtx);
            p = instance.load(std::memory_order_relaxed);
            if (!p) {
                p = new Singleton();
                instance.store(p, std::memory_order_release);
            }
        }
        return p;
    }
};
// C++11+: 函数内 static 本身就是线程安全的
Singleton &amp;get() { static Singleton s; return s; }
```

### 读写锁

```cpp
#include &lt;shared_mutex&gt;

std::shared_mutex rwMtx;

// 读（共享锁，多线程可同时读）
void read() {
    std::shared_lock lock(rwMtx);
    // 读取共享数据...
}

// 写（独占锁）
void write() {
    std::unique_lock lock(rwMtx);
    // 修改共享数据...
}
```

## 速查

| 工具 | 用途 |
|------|------|
| `std::thread` | 创建线程 |
| `std::mutex` + `lock_guard` | 互斥保护 |
| `std::scoped_lock` | 多锁同时锁定 |
| `std::condition_variable` | 等待/通知 |
| `std::atomic` | 无锁原子变量 |
| `std::async` / `future` | 异步任务返回值 |
| `std::shared_mutex` | 读写锁 |
| `thread_local` | 线程局部存储 |</content:encoded></item><item><title>动态规划 — 核心思想与经典问题</title><link>https://kiyose.wiki/notes/dynamicprogramming/</link><guid isPermaLink="true">https://kiyose.wiki/notes/dynamicprogramming/</guid><description>最优子结构、状态转移、记忆化搜索、背包/LCS/LIS 经典问题与复杂度推导</description><pubDate>Thu, 22 Jun 2023 00:00:00 GMT</pubDate><content:encoded>## 核心思想

DP = 记忆化搜索（自顶向下）或 递推填表（自底向上），本质是**用空间换时间**。

```
斐波那契：
  f(0)=0, f(1)=1, f(n)=f(n-1)+f(n-2)

  递归树 — 大量重复计算
          f(5)
        /      \
     f(4)      f(3)
     /  \      /  \
  f(3) f(2) f(2) f(1)
   ↓ DP 解决
  数组 dp[n] 每个只算一次

时间：O(2ⁿ) → O(n)
空间：O(1)（只需两个变量）
```

### DP 适用条件

| 条件 | 含义 |
|------|------|
| **最优子结构** | 大问题的最优解包含子问题最优解 |
| **重叠子问题** | 递归中反复遇到相同子问题 |
| **无后效性** | 当前状态确定后不受后续决策影响 |

## 0/1 背包

```cpp
// 物品 i：重量 w[i]，价值 v[i]，背包容量 C
// dp[i][c] = 前 i 个物品，容量 c 的最大价值
int knapsack01(const vector&lt;int&gt; &amp;w, const vector&lt;int&gt; &amp;v, int C) {
    vector&lt;int&gt; dp(C + 1, 0);
    for (int i = 0; i &lt; w.size(); ++i)
        for (int c = C; c &gt;= w[i]; --c)  // ⚠️ 逆序：防止同物品多次使用
            dp[c] = max(dp[c], dp[c - w[i]] + v[i]);
    return dp[C];
}
// 时间 O(nC)，空间 O(C)
```

**逆序原因**：`dp[c]` 依赖 `dp[c - w[i]]`（上一行的值），正序会用到本行刚更新的值 = 物品被用了多次（变成完全背包）。

## 完全背包

```cpp
// 每种物品无限件
int knapsackComplete(const vector&lt;int&gt; &amp;w, const vector&lt;int&gt; &amp;v, int C) {
    vector&lt;int&gt; dp(C + 1, 0);
    for (int i = 0; i &lt; w.size(); ++i)
        for (int c = w[i]; c &lt;= C; ++c)   // 正序：允许重用
            dp[c] = max(dp[c], dp[c - w[i]] + v[i]);
    return dp[C];
}
```

## 最长公共子序列 (LCS)

```cpp
string lcs(const string &amp;a, const string &amp;b) {
    int n = a.size(), m = b.size();
    vector&lt;vector&lt;int&gt;&gt; dp(n + 1, vector&lt;int&gt;(m + 1, 0));

    for (int i = 1; i &lt;= n; ++i)
        for (int j = 1; j &lt;= m; ++j)
            dp[i][j] = (a[i-1] == b[j-1])
                ? dp[i-1][j-1] + 1
                : max(dp[i-1][j], dp[i][j-1]);

    // 回溯构建 LCS
    string res;
    int i = n, j = m;
    while (i &gt; 0 &amp;&amp; j &gt; 0) {
        if (a[i-1] == b[j-1]) { res += a[i-1]; --i; --j; }
        else if (dp[i-1][j] &gt; dp[i][j-1]) --i;
        else --j;
    }
    reverse(res.begin(), res.end());
    return res;
}
// 时间 O(nm)，空间 O(nm) → 可优化到 O(min(n,m))
```

## 最长递增子序列 (LIS)

```cpp
// 方法 1：DP O(n²)
int lisDP(const vector&lt;int&gt; &amp;nums) {
    vector&lt;int&gt; dp(nums.size(), 1);
    int ans = 1;
    for (int i = 1; i &lt; nums.size(); ++i)
        for (int j = 0; j &lt; i; ++j)
            if (nums[j] &lt; nums[i])
                ans = max(ans, dp[i] = max(dp[i], dp[j] + 1));
    return ans;
}

// 方法 2：贪心 + 二分 O(n log n)
int lisGreedy(const vector&lt;int&gt; &amp;nums) {
    vector&lt;int&gt; tails;  // tails[i] = 长度为 i+1 的 LIS 的最小末尾
    for (int x : nums) {
        auto it = lower_bound(tails.begin(), tails.end(), x);
        if (it == tails.end()) tails.push_back(x);
        else *it = x;
    }
    return tails.size();
}
```

## 路径计数类 DP

```cpp
// m×n 网格从左上到右下的路径数（只能右/下）
// dp[i][j] = dp[i-1][j] + dp[i][j-1]
int uniquePaths(int m, int n) {
    vector&lt;int&gt; dp(n, 1);
    for (int i = 1; i &lt; m; ++i)
        for (int j = 1; j &lt; n; ++j)
            dp[j] += dp[j-1];
    return dp[n-1];
}
// 数学解：C(m+n-2, m-1)
```

## DP 解题模板

```
1. 定义 dp[i] 或 dp[i][j] 的含义
2. 写出状态转移方程
3. 确定初始条件（base case）
4. 确定遍历顺序（正序/逆序，外层/内层）
5. 空间优化（滚动数组）

特征识别：
  - &quot;最值&quot; → 考虑 DP
  - &quot;方案数&quot; → 考虑 DP
  - &quot;能不能达到&quot; → 考虑 DP（布尔型）
```</content:encoded></item><item><title>C++ Lambda 与函数对象</title><link>https://kiyose.wiki/notes/lambda/</link><guid isPermaLink="true">https://kiyose.wiki/notes/lambda/</guid><description>Lambda 表达式语法、捕获列表、闭包原理、std::function 与性能考量</description><pubDate>Thu, 15 Jun 2023 00:00:00 GMT</pubDate><content:encoded>## 基础语法

```cpp
// [捕获](参数) -&gt; 返回类型 { 函数体 }
auto add = [](int a, int b) -&gt; int { return a + b; };
auto add2 = [](int a, int b) { return a + b; };  // 返回类型可省略

cout &lt;&lt; add(3, 5);  // 8
```

## 捕获列表

```cpp
int x = 10, y = 20;

// 按值捕获（默认不可修改）
auto f1 = [x, y] { return x + y; };  // 捕获副本

// 按引用捕获
auto f2 = [&amp;x, &amp;y] { x++; y++; };    // 修改外部变量

// mutable — 允许修改值捕获的副本
auto f3 = [x]() mutable { x++; return x; };

// 默认捕获方式
auto f4 = [=] { return x + y; };     // 全部按值
auto f5 = [&amp;] { x++; y++; };         // 全部按引用

// 混合捕获
auto f6 = [=, &amp;y] { return x + y; }; // y 引用，其余值
auto f7 = [&amp;, x] { return x + y; };  // x 值，其余引用

// C++14 初始化捕获（移动）
auto p = make_unique&lt;int&gt;(42);
auto f8 = [p = move(p)] { return *p; };

// C++14 捕获表达式
auto f9 = [y = x + 5] { return y; };  // 捕获时计算一次
```

## 捕获的本质

```cpp
int x = 42;
auto lambda = [x] { return x; };

// 编译器大致生成：
class __lambda {
    int x;  // 按值捕获 = 成员变量
public:
    __lambda(int arg) : x(arg) {}
    auto operator()() const { return x; }  // 调用运算符
};
// 按引用捕获 = 成员变量是引用类型
```

## 实战场景

### STL 算法

```cpp
vector&lt;int&gt; v = {1, 2, 3, 4, 5, 6};

// 条件删除
v.erase(remove_if(v.begin(), v.end(),
    [](int x) { return x % 2 == 0; }), v.end());

// 自定义排序
sort(v.begin(), v.end(),
    [](int a, int b) { return abs(a) &lt; abs(b); });

// 查找
int threshold = 3;
auto it = find_if(v.begin(), v.end(),
    [threshold](int x) { return x &gt; threshold; });

// 变换
transform(v.begin(), v.end(), v.begin(),
    [](int x) { return x * x; });
```

### 回调与事件

```cpp
// 按钮点击
button-&gt;onClick([this]() {
    m_label-&gt;setText(&quot;clicked&quot;);
});

// 定时器
QTimer::singleShot(1000, [this]() {
    m_status = Status::Timeout;
});

// 线程
thread t([](int n) {
    cout &lt;&lt; &quot;Thread: &quot; &lt;&lt; n;
}, 42);
t.join();
```

### 即调 Lambda（IILE）

```cpp
// 复杂的 const 变量初始化
const auto config = [&amp;]() {
    map&lt;string, int&gt; m;
    m[&quot;timeout&quot;] = 5000;
    m[&quot;retries&quot;] = 3;
    return m;
}();  // 注意尾部的 () — 立即调用
```

## std::function

```cpp
#include &lt;functional&gt;

// 类型擦除 — 可容纳任何可调用对象
function&lt;int(int, int)&gt; fn;

fn = [](int a, int b) { return a + b; };     // lambda
fn = plus&lt;int&gt;();                              // 函数对象
fn = [](int a, int b) { return a * b; };      // 换一个 lambda

// 存储回调
class Button {
    function&lt;void()&gt; m_callback;
public:
    void onClick(function&lt;void()&gt; cb) { m_callback = move(cb); }
    void click() { if (m_callback) m_callback(); }
};
```

## 性能考量

| 方式 | 开销 | 适用场景 |
|------|:--:|------|
| 裸 Lambda | 零（等同于函数对象） | 模板参数传递 |
| `std::function` | 堆分配 + 虚调用 | 类型擦除、存储回调 |
| 函数指针 | 不可捕获 | C API 兼容 |

```cpp
// ❌ std::function 有不必要的开销
template &lt;typename F&gt;
void forEach(vector&lt;int&gt; &amp;v, F fn) {  // 编译期确定类型，零开销
    for (auto &amp;x : v) fn(x);
}

// 只有在需要运行时多态时才用 std::function
vector&lt;function&lt;void()&gt;&gt; callbacks;
callbacks.push_back([=] { doA(); });
callbacks.push_back([=] { doB(); });
```

## 常见陷阱

```cpp
// 陷阱 1: 按引用捕获后，Lambda 比变量活得久
function&lt;void()&gt; createCallback() {
    int local = 42;
    return [&amp;local] { cout &lt;&lt; local; };  // ❌ 悬空引用！
}
// 修正：按值捕获 → [local]

// 陷阱 2: this 捕获 + 异步 = 对象可能已析构
// 修正：用 shared_from_this() 或 weak_ptr

// 陷阱 3: mutable Lambda 中按值捕获不会影响外部
int n = 0;
auto f = [n]() mutable { n++; };
f();  // n(内部副本) = 1
// n(外部) 还是 0
```</content:encoded></item><item><title>图的最短路径算法</title><link>https://kiyose.wiki/notes/graphshortestpath/</link><guid isPermaLink="true">https://kiyose.wiki/notes/graphshortestpath/</guid><description>BFS 无权图最短路径、Dijkstra 算法、A* 启发式搜索与复杂度对比</description><pubDate>Sun, 28 May 2023 00:00:00 GMT</pubDate><content:encoded>## 图表示

```cpp
// 邻接表（推荐 — 稀疏图 O(V+E) 空间）
vector&lt;vector&lt;int&gt;&gt; adj;  // adj[u] = {v1, v2, ...}

// 带权图
vector&lt;vector&lt;pair&lt;int, int&gt;&gt;&gt; adj;  // adj[u] = {{v1, w1}, {v2, w2}}

// 邻接矩阵 — 稠密图
vector&lt;vector&lt;int&gt;&gt; matrix(V, vector&lt;int&gt;(V, INF));
```

## BFS — 无权图最短路径

```cpp
// 从 src 到所有节点的最短距离
vector&lt;int&gt; bfsShortestPath(int V, const vector&lt;vector&lt;int&gt;&gt; &amp;adj, int src) {
    vector&lt;int&gt; dist(V, -1);
    queue&lt;int&gt; q;
    dist[src] = 0;
    q.push(src);

    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int v : adj[u]) {
            if (dist[v] == -1) {
                dist[v] = dist[u] + 1;
                q.push(v);
            }
        }
    }
    return dist;
}
// 时间 O(V+E)，空间 O(V)
// ⚠️ 仅适用于无权图（每条边权重为 1）
```

## Dijkstra — 非负权图

```cpp
// 优先队列优化版
vector&lt;int&gt; dijkstra(int V, const vector&lt;vector&lt;pair&lt;int,int&gt;&gt;&gt; &amp;adj, int src) {
    vector&lt;int&gt; dist(V, INT_MAX);
    using P = pair&lt;int, int&gt;;  // {距离, 节点}
    priority_queue&lt;P, vector&lt;P&gt;, greater&lt;P&gt;&gt; pq;

    dist[src] = 0;
    pq.push({0, src});

    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d &gt; dist[u]) continue;  // 过时的条目，跳过

        for (auto [v, w] : adj[u]) {
            if (dist[u] + w &lt; dist[v]) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
    return dist;
}
// 时间 O((V+E) log V)，空间 O(V)
```

### 为什么不能处理负权边

```
A ──(2)──→ B ──(-3)──→ C
│                        │
└──────────(1)───────────┘

Dijkstra: A→C = 1（直达） → 标记 C 完成
实际最短: A→B→C = -1（但 C 已被&quot;锁定&quot;）
Dijkstra 的贪心假设被负权打破。
```

## Bellman-Ford — 可处理负权

```cpp
// 检测负环：第 V 轮还能松弛 → 存在负环
vector&lt;int&gt; bellmanFord(int V, const vector&lt;tuple&lt;int,int,int&gt;&gt; &amp;edges, int src) {
    vector&lt;int&gt; dist(V, INT_MAX);
    dist[src] = 0;
    for (int i = 0; i &lt; V - 1; ++i) {          // 松弛 V-1 轮
        bool changed = false;
        for (auto [u, v, w] : edges) {
            if (dist[u] != INT_MAX &amp;&amp; dist[u] + w &lt; dist[v]) {
                dist[v] = dist[u] + w;
                changed = true;
            }
        }
        if (!changed) break;  // 早停优化
    }
    // 第 V 轮检测负环
    for (auto [u, v, w] : edges)
        if (dist[u] != INT_MAX &amp;&amp; dist[u] + w &lt; dist[v])
            return {};  // 存在负环
    return dist;
}
// 时间 O(VE)，空间 O(V)
```

## Floyd-Warshall — 全源最短

```cpp
void floydWarshall(vector&lt;vector&lt;int&gt;&gt; &amp;dist) {
    int V = dist.size();
    for (int k = 0; k &lt; V; ++k)
        for (int i = 0; i &lt; V; ++i)
            for (int j = 0; j &lt; V; ++j)
                if (dist[i][k] != INF &amp;&amp; dist[k][j] != INF)
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
// 时间 O(V³)，空间 O(V²) — 适用于 V ≤ 500
// 可处理负权，但不可有负环
```

## A* — 启发式搜索

```cpp
// 从 start 到 goal，使用启发函数 h(n) 预估剩余距离
int aStar(const vector&lt;vector&lt;pair&lt;int,int&gt;&gt;&gt; &amp;adj, int start, int goal,
          function&lt;int(int)&gt; heuristic) {
    using P = pair&lt;int, int&gt;;
    priority_queue&lt;P, vector&lt;P&gt;, greater&lt;P&gt;&gt; pq;
    vector&lt;int&gt; gScore(V, INF);
    gScore[start] = 0;
    pq.push({heuristic(start), start});

    while (!pq.empty()) {
        auto [f, u] = pq.top(); pq.pop();
        if (u == goal) return gScore[u];
        for (auto [v, w] : adj[u]) {
            int tentG = gScore[u] + w;
            if (tentG &lt; gScore[v]) {
                gScore[v] = tentG;
                pq.push({tentG + heuristic(v), v});  // f = g + h
            }
        }
    }
    return -1;
}
// Dijkstra 是 A* 的特例（h(n) = 0）
// h(n) ≤ 真实剩余距离 → 保证最优
```

## 选型速查

| 算法 | 时间 | 适用场景 |
|------|------|---------|
| BFS | O(V+E) | 无权图 / 等权 |
| Dijkstra | O((V+E) log V) | 非负权单源 |
| Bellman-Ford | O(VE) | 负权 + 负环检测 |
| Floyd-Warshall | O(V³) | 全源、小图、传递闭包 |
| A* | 启发式 | 有距离预估的场景 |</content:encoded></item><item><title>C++ 智能指针</title><link>https://kiyose.wiki/notes/smartpointer/</link><guid isPermaLink="true">https://kiyose.wiki/notes/smartpointer/</guid><description>unique_ptr、shared_ptr、weak_ptr 的使用场景与 RAII 内存管理</description><pubDate>Sat, 20 May 2023 00:00:00 GMT</pubDate><content:encoded>## 为什么需要智能指针

```cpp
// ❌ 裸指针：容易忘记 delete → 内存泄漏
void foo() {
    int *p = new int(42);
    // ... 抛异常 → delete 永远不执行 → 泄漏
    delete p;
}

// ✅ 智能指针：自动释放，异常安全
void foo() {
    auto p = make_unique&lt;int&gt;(42);
    // 离开作用域自动 delete，抛异常也释放
}
```

核心思想：**RAII**（Resource Acquisition Is Initialization）——资源获取即初始化，对象析构时自动释放资源。

## 三种智能指针

| 指针 | 所有权 | 场景 |
|------|--------|------|
| `unique_ptr` | 独占 | 工厂函数、成员变量、PIMPL |
| `shared_ptr` | 共享 | 多对象共享同一资源、图/树结构 |
| `weak_ptr` | 旁观 | 打破循环引用、缓存 |

---

## unique_ptr — 独占

```cpp
#include &lt;memory&gt;

// 创建（推荐 make_unique，C++14）
auto p1 = make_unique&lt;int&gt;(42);
auto p2 = make_unique&lt;string&gt;(&quot;hello&quot;);

// 不可拷贝，只能移动
auto p3 = move(p1);     // p1 变为 nullptr，p3 接管所有权

// 工厂函数：返回 unique_ptr
unique_ptr&lt;Base&gt; createFactory() {
    return make_unique&lt;Derived&gt;();  // 自动转换为基类指针
}

// 自定义删除器
auto deleter = [](FILE *f) { fclose(f); };
unique_ptr&lt;FILE, decltype(deleter)&gt; fp(fopen(&quot;test.txt&quot;, &quot;r&quot;), deleter);
```

**规则**：能用 unique_ptr 就别用 shared_ptr。独占所有权覆盖 80% 场景。

---

## shared_ptr — 共享

```cpp
auto sp1 = make_shared&lt;int&gt;(42);   // 引用计数 = 1
auto sp2 = sp1;                    // 引用计数 = 2
sp2.reset();                       // 引用计数 = 1
// sp1 离开作用域 → 引用计数 = 0 → delete

// 观察引用计数
cout &lt;&lt; sp1.use_count();           // 1

// 从 this 获取 shared_ptr（继承 enable_shared_from_this）
class Node : public enable_shared_from_this&lt;Node&gt; {
public:
    shared_ptr&lt;Node&gt; getPtr() {
        return shared_from_this();
    }
};
```

**代价**：
- 控制块额外 16 字节
- 引用计数是原子操作，有开销
- `make_shared` 一次分配（对象+控制块），`new` + `shared_ptr` 两次分配

---

## weak_ptr — 旁观

```cpp
auto sp = make_shared&lt;int&gt;(42);
weak_ptr&lt;int&gt; wp = sp;

// 使用时必须 lock() — 检查对象是否还活着
if (auto locked = wp.lock()) {
    cout &lt;&lt; *locked;    // 安全访问
} else {
    cout &lt;&lt; &quot;已被释放&quot;;
}

sp.reset();             // 引用计数归零
// wp.lock() 返回 nullptr
```

### 经典场景：打破循环引用

```cpp
class B;
class A {
public:
    shared_ptr&lt;B&gt; b_ptr;  // A 持有 B
    ~A() { cout &lt;&lt; &quot;A destroyed\n&quot;; }
};
class B {
public:
    // shared_ptr&lt;A&gt; a_ptr;  // ❌ 互相持有 → 永不释放
    weak_ptr&lt;A&gt; a_ptr;       // ✅ 弱引用 → A 可以正常析构
    ~B() { cout &lt;&lt; &quot;B destroyed\n&quot;; }
};
```

---

## 选择决策树

```
需要多个持有者共享所有权？
  ├─ 是 → shared_ptr
  │       └─ 有循环引用风险？
  │            └─ 是 → 其中一方用 weak_ptr
  └─ 否 → unique_ptr
           └─ 需要自定义删除器 → unique_ptr&lt;T, Deleter&gt;
```</content:encoded></item><item><title>树与二叉树遍历</title><link>https://kiyose.wiki/notes/treetraversal/</link><guid isPermaLink="true">https://kiyose.wiki/notes/treetraversal/</guid><description>二叉树存储、DFS（前中后序）递归/迭代、BFS 层序遍历与 Morris 遍历</description><pubDate>Tue, 25 Apr 2023 00:00:00 GMT</pubDate><content:encoded>## 二叉树节点定义

```cpp
struct TreeNode {
    int val;
    TreeNode *left, *right;
    TreeNode(int v) : val(v), left(nullptr), right(nullptr) {}
};
```

## DFS 深度优先（递归）

```cpp
// 前序：根 → 左 → 右
void preorder(TreeNode *root) {
    if (!root) return;
    cout &lt;&lt; root-&gt;val;         // 先处理根
    preorder(root-&gt;left);
    preorder(root-&gt;right);
}

// 中序：左 → 根 → 右（BST 输出有序序列）
void inorder(TreeNode *root) {
    if (!root) return;
    inorder(root-&gt;left);
    cout &lt;&lt; root-&gt;val;
    inorder(root-&gt;right);
}

// 后序：左 → 右 → 根（用于删除、计算高度）
void postorder(TreeNode *root) {
    if (!root) return;
    postorder(root-&gt;left);
    postorder(root-&gt;right);
    cout &lt;&lt; root-&gt;val;
}

// 复杂度：时间 O(n)，空间 O(h)（递归栈，h 为树高）
```

## DFS 迭代版

```cpp
// 前序迭代（栈模拟递归）
void preorderIter(TreeNode *root) {
    if (!root) return;
    stack&lt;TreeNode *&gt; st;
    st.push(root);
    while (!st.empty()) {
        auto *node = st.top(); st.pop();
        cout &lt;&lt; node-&gt;val;
        if (node-&gt;right) st.push(node-&gt;right);  // 右先进后出
        if (node-&gt;left) st.push(node-&gt;left);
    }
}

// 中序迭代（走到最左，回溯）
void inorderIter(TreeNode *root) {
    stack&lt;TreeNode *&gt; st;
    auto *cur = root;
    while (cur || !st.empty()) {
        while (cur) { st.push(cur); cur = cur-&gt;left; }  // 一路向左
        cur = st.top(); st.pop();
        cout &lt;&lt; cur-&gt;val;
        cur = cur-&gt;right;  // 转向右子树
    }
}

// 后序迭代（前序的逆：根→右→左 的反转）
void postorderIter(TreeNode *root) {
    if (!root) return;
    stack&lt;TreeNode *&gt; st;
    vector&lt;int&gt; result;
    st.push(root);
    while (!st.empty()) {
        auto *node = st.top(); st.pop();
        result.push_back(node-&gt;val);
        if (node-&gt;left) st.push(node-&gt;left);
        if (node-&gt;right) st.push(node-&gt;right);
    }
    reverse(result.begin(), result.end());  // 反转得后序
    for (int v : result) cout &lt;&lt; v;
}
```

## BFS 层序遍历

```cpp
void levelOrder(TreeNode *root) {
    if (!root) return;
    queue&lt;TreeNode *&gt; q;
    q.push(root);
    while (!q.empty()) {
        int n = q.size();  // 当前层节点数
        for (int i = 0; i &lt; n; ++i) {
            auto *node = q.front(); q.pop();
            cout &lt;&lt; node-&gt;val;
            if (node-&gt;left) q.push(node-&gt;left);
            if (node-&gt;right) q.push(node-&gt;right);
        }
        cout &lt;&lt; &apos;\n&apos;;  // 换层
    }
}
// 时间 O(n)，空间 O(w)（w 为最宽层节点数）
```

## Morris 遍历（O(1) 空间）

```cpp
// 中序 Morris：利用叶子节点的空右指针做线索
void morrisInorder(TreeNode *root) {
    auto *cur = root;
    while (cur) {
        if (!cur-&gt;left) {
            cout &lt;&lt; cur-&gt;val;   // 无左子树，访问当前
            cur = cur-&gt;right;
        } else {
            auto *pre = cur-&gt;left;
            while (pre-&gt;right &amp;&amp; pre-&gt;right != cur) pre = pre-&gt;right;
            if (!pre-&gt;right) {              // 建立线索
                pre-&gt;right = cur;
                cur = cur-&gt;left;
            } else {                        // 删除线索
                pre-&gt;right = nullptr;
                cout &lt;&lt; cur-&gt;val;
                cur = cur-&gt;right;
            }
        }
    }
}
// O(1) 额外空间，但遍历过程中临时修改了树结构
```

## 遍历结果重建树

```
已知前序 + 中序 → 可唯一确定二叉树
已知后序 + 中序 → 可唯一确定二叉树
已知前序 + 后序 → 不唯一（需要树是满的才行）

前序: [3, 9, 20, 15, 7]
中序: [9, 3, 15, 20, 7]
→ 根是 3（前序第一个）
→ 中序中 3 左边 [9] 是左子树，右边 [15,20,7] 是右子树
→ 递归构建
```

## 遍历应用场景

| 遍历 | 典型应用 |
|------|---------|
| 前序 | 序列化、复制树 |
| 中序 | BST 有序输出、验证 BST |
| 后序 | 删除树、计算子树高度 |
| 层序 | 最短路径（无权图 BFS）、宽度 |</content:encoded></item><item><title>C++ 移动语义</title><link>https://kiyose.wiki/notes/movesemantics/</link><guid isPermaLink="true">https://kiyose.wiki/notes/movesemantics/</guid><description>右值引用、std::move、移动构造/赋值、完美转发与零拷贝优化实践</description><pubDate>Thu, 20 Apr 2023 00:00:00 GMT</pubDate><content:encoded>## 为什么需要移动语义

```cpp
// 拷贝：深复制所有数据 → 慢
vector&lt;int&gt; a = {1, 2, 3, 4, 5};
vector&lt;int&gt; b = a;    // 5 个 int 拷贝 + 堆分配
// a 还在，b 有独立副本

// 移动：偷走资源 → 快
vector&lt;int&gt; c = std::move(a);  // 偷走 a 的指针，a 变空
// a 被&quot;掏空&quot;，c 接管了原 a 的堆内存（零拷贝）
```

核心思想：**不需要保留原对象时，直接转移资源而非拷贝**。

## 左值 vs 右值

```cpp
int x = 42;          // x 是左值（有名字、可取地址）
int &amp;ref = x;        // 左值引用

int &amp;&amp;rref = 42;     // 右值引用 — 绑定到临时对象
int &amp;&amp;rref2 = x + 1; // x+1 是临时结果，也是右值

// 判断口诀：
// 能取地址 → 左值
// 临时值、字面量、函数返回的非引用 → 右值
```

## 移动构造与移动赋值

```cpp
class Buffer {
    char *data;
    size_t size;
public:
    // 普通构造
    Buffer(size_t n) : data(new char[n]), size(n) {}

    // 拷贝构造（深拷贝）
    Buffer(const Buffer &amp;other)
        : data(new char[other.size]), size(other.size) {
        memcpy(data, other.data, size);
    }

    // 移动构造（偷资源）⚠️ noexcept 很关键！
    Buffer(Buffer &amp;&amp;other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;   // 原对象置空，防止 double free
        other.size = 0;
    }

    // 移动赋值
    Buffer &amp;operator=(Buffer &amp;&amp;other) noexcept {
        if (this != &amp;other) {
            delete[] data;          // 释放自己的旧资源
            data = other.data;      // 偷
            size = other.size;
            other.data = nullptr;   // 清空原对象
            other.size = 0;
        }
        return *this;
    }

    ~Buffer() { delete[] data; }
};

// 使用
Buffer createBuffer() { return Buffer(1024); }  // 返回值优化/移动
Buffer buf = createBuffer();   // 移动构造，不是拷贝
```

## std::move 到底做了什么

```cpp
// std::move 的本质：无条件转换为右值引用
// 它不移动任何东西，只是&quot;标记&quot;可以移动

template &lt;typename T&gt;
constexpr remove_reference_t&lt;T&gt; &amp;&amp;move(T &amp;&amp;t) noexcept {
    return static_cast&lt;remove_reference_t&lt;T&gt; &amp;&amp;&gt;(t);
}

string s1 = &quot;hello&quot;;
string s2 = std::move(s1);  // s1 被&quot;掏空&quot;
// std::move 只是把 s1 转成 string&amp;&amp;，
// 真正移动的是 string 的移动构造函数
```

## 完美转发

```cpp
// 问题：模板参数 T&amp;&amp; 既接收左值也接收右值，但转发时丢失了&quot;左右值&quot;信息
template &lt;typename T&gt;
void wrapper(T &amp;&amp;arg) {
    // arg 有名字 → 是左值！
    // target(arg) 永远走拷贝，不会走移动
    target(arg);                     // ❌ 总是左值
    target(std::forward&lt;T&gt;(arg));    // ✅ 保持原始值类别
}

// std::forward: 左值 → 左值引用，右值 → 右值引用
// 配合 T&amp;&amp; (转发引用/万能引用) 使用
```

## 实际收益

```cpp
// 场景：函数返回大对象
vector&lt;string&gt; buildList() {
    vector&lt;string&gt; v;
    v.push_back(&quot;hello&quot;);           // 拷贝 C 字符串
    v.push_back(std::move(someStr)); // 移动 string → 免拷贝
    return v;                        // RVO/NRVO — 编译器优化，甚至不需要移动
}

// 场景：容器存放不可拷贝对象
vector&lt;unique_ptr&lt;int&gt;&gt; pointers;
auto p = make_unique&lt;int&gt;(42);
pointers.push_back(std::move(p));  // unique_ptr 只能移动
```

## 规则总结

| 规则 | 说明 |
|------|------|
| **三五法则** | 定义了析构/拷贝/移动中的一个 → 考虑定义全部五个 |
| **noexcept** | 移动构造/赋值必须加 `noexcept`，否则 vector 扩容时退化为拷贝 |
| **move 后别用** | `std::move(x)` 后，x 处于&quot;有效但未指定&quot;状态，只能析构或赋值 |
| **RVO 优于 move** | `return std::move(x)` 反而阻止编译器优化，直接 `return x` |</content:encoded></item><item><title>哈希表原理与实现</title><link>https://kiyose.wiki/notes/hashtable/</link><guid isPermaLink="true">https://kiyose.wiki/notes/hashtable/</guid><description>哈希函数、冲突解决（链地址/开放寻址）、负载因子、std::unordered_map 源码级理解</description><pubDate>Tue, 28 Mar 2023 00:00:00 GMT</pubDate><content:encoded>## 核心思想

```
key ──→ [hash function] ──→ index ──→ O(1) 存取

关键设计：
1. 哈希函数：把 key 均匀映射到 [0, capacity)
2. 冲突解决：两个不同 key 映射到同一 index 怎么办
```

## 哈希函数

```cpp
// 字符串哈希 — DJB2（经典简单高效）
size_t hash(const string &amp;s) {
    size_t h = 5381;
    for (char c : s) h = ((h &lt;&lt; 5) + h) + c;  // h * 33 + c
    return h;
}

// 整数哈希 — 避免简单取模的低位冲突
size_t hash(int key, size_t capacity) {
    // 乘法哈希：用无理数打散
    const double A = 0.6180339887;  // (√5-1)/2
    return size_t(capacity * fmod(key * A, 1.0));
}
```

## 冲突解决

### 链地址法（std::unordered_map 采用）

```cpp
template &lt;typename K, typename V&gt;
class ChainedHashMap {
    vector&lt;list&lt;pair&lt;K, V&gt;&gt;&gt; buckets;
    size_t size_ = 0;
    const double MAX_LOAD = 0.75;

    size_t index(const K &amp;key) const {
        return hash&lt;K&gt;{}(key) % buckets.size();
    }

public:
    ChainedHashMap(size_t cap = 16) : buckets(cap) {}

    void put(const K &amp;key, const V &amp;val) {
        auto &amp;chain = buckets[index(key)];
        for (auto &amp;[k, v] : chain) {
            if (k == key) { v = val; return; }
        }
        chain.emplace_back(key, val);
        if (++size_ &gt; buckets.size() * MAX_LOAD) rehash();
    }

    V *get(const K &amp;key) {
        for (auto &amp;[k, v] : buckets[index(key)])
            if (k == key) return &amp;v;
        return nullptr;
    }
};
```

### 开放寻址法

```cpp
// 线性探测: index, index+1, index+2 ...
// 问题：主聚类（primary clustering）

// 二次探测: index + 1², index + 2², ...
// 问题：次聚类（secondary clustering）

// 双重哈希: h1(key) + i * h2(key)
// 优点：无聚类，但需要两个哈希函数
```

## 负载因子与 rehash

```cpp
void rehash() {
    auto oldBuckets = move(buckets);
    buckets.resize(oldBuckets.size() * 2);
    size_ = 0;
    for (auto &amp;chain : oldBuckets)
        for (auto &amp;[k, v] : chain)
            put(k, v);
}
```

| 负载因子 | 含义 | 行为 |
|:--:|------|------|
| &lt;0.5 | 稀疏 | 查找快，但浪费空间 |
| **0.75** | **平衡点** | **std::unordered_map 默认扩容阈值** |
| &gt;1.0 | 链地址可容忍 | 链表变长，退化为 O(n) |

## 复杂度分析

| 操作 | 平均 | 最坏 |
|------|:--:|:--:|
| 插入 | O(1) | O(n) |
| 查找 | O(1) | O(n) |
| 删除 | O(1) | O(n) |

最坏情况：所有 key 冲突到同一个桶 → 退化成链表。**防止方法**：好的哈希函数 + 低负载因子。

## std::unordered_map 使用

```cpp
unordered_map&lt;string, int&gt; m;

// 插入
m[&quot;alice&quot;] = 25;
m.insert({&quot;bob&quot;, 30});
m.try_emplace(&quot;carol&quot;, 28);  // C++17，已存在不覆盖

// 查找
if (m.count(&quot;alice&quot;)) { /* 存在 */ }
auto it = m.find(&quot;alice&quot;);
if (it != m.end()) cout &lt;&lt; it-&gt;second;

// 删除
m.erase(&quot;alice&quot;);

// 预留空间（避免 rehash）
m.reserve(1000);  // 至少 1000 个桶

// 遍历（无序！）
for (auto &amp;[k, v] : m) cout &lt;&lt; k &lt;&lt; &quot;: &quot; &lt;&lt; v;
```

## 自定义哈希

```cpp
struct Point { int x, y; };

struct PointHash {
    size_t operator()(const Point &amp;p) const {
        return hash&lt;int&gt;()(p.x) ^ (hash&lt;int&gt;()(p.y) &lt;&lt; 1);
    }
};
struct PointEq {
    bool operator()(const Point &amp;a, const Point &amp;b) const {
        return a.x == b.x &amp;&amp; a.y == b.y;
    }
};

unordered_map&lt;Point, string, PointHash, PointEq&gt; grid;
```</content:encoded></item><item><title>C++ STL 容器与算法</title><link>https://kiyose.wiki/notes/stl/</link><guid isPermaLink="true">https://kiyose.wiki/notes/stl/</guid><description>STL 六大组件速览：vector/list/map 容器对比、迭代器、常用算法及性能选择指南</description><pubDate>Wed, 15 Mar 2023 00:00:00 GMT</pubDate><content:encoded>## 六大组件

| 组件 | 作用 | 例子 |
|------|------|------|
| 容器 | 存储数据 | `vector`, `list`, `map`, `set` |
| 算法 | 操作数据 | `sort`, `find`, `transform` |
| 迭代器 | 连接容器与算法 | `begin()`, `end()` |
| 仿函数 | 行为像函数的对象 | `greater&lt;int&gt;()` |
| 适配器 | 改造接口 | `stack`, `queue`, `priority_queue` |
| 空间配置器 | 内存管理 | `allocator` |

## 三大核心容器

### vector — 动态数组

```cpp
vector&lt;int&gt; v = {1, 2, 3};
v.push_back(4);        // 尾部追加，O(1) 均摊
v.pop_back();          // 尾部删除
v[0] = 10;             // 随机访问 O(1)
v.insert(v.begin()+1, 99); // 中间插入 O(n)

// 遍历
for (auto &amp;x : v) cout &lt;&lt; x &lt;&lt; &quot; &quot;;
for (auto it = v.begin(); it != v.end(); ++it) cout &lt;&lt; *it;
```

**选择**：需要随机访问 + 尾部追加 → vector。

### list — 双向链表

```cpp
list&lt;int&gt; l = {1, 2, 3};
l.push_front(0);       // 头部插入 O(1)
l.push_back(4);        // 尾部插入 O(1)
l.insert(next(l.begin()), 99); // 中间插入 O(1) — 真正的优势

// ⚠️ 不能 l[0] 随机访问
// ✅ 自带 sort（比 std::sort 更适合链表）
l.sort();
```

**选择**：频繁中间插入/删除，不需要随机访问 → list。

### map — 红黑树

```cpp
map&lt;string, int&gt; m;
m[&quot;apple&quot;] = 5;                    // 插入或更新 O(log n)
m.insert({&quot;banana&quot;, 3});           // 插入（不覆盖已有键）

if (m.count(&quot;apple&quot;)) { }          // 判断存在
auto it = m.find(&quot;apple&quot;);         // 查找
if (it != m.end()) cout &lt;&lt; it-&gt;second;

// 遍历（按键排序）
for (auto &amp;[k, v] : m) cout &lt;&lt; k &lt;&lt; &quot;: &quot; &lt;&lt; v;
```

**选择**：键值对 + 有序遍历 → map。无序用 `unordered_map`（哈希表 O(1)）。

## 常用算法

```cpp
#include &lt;algorithm&gt;

vector&lt;int&gt; v = {3, 1, 4, 1, 5, 9, 2, 6};

sort(v.begin(), v.end());                          // 升序
sort(v.begin(), v.end(), greater&lt;int&gt;());          // 降序

auto it = find(v.begin(), v.end(), 5);             // 查找
int cnt = count(v.begin(), v.end(), 1);            // 计数

reverse(v.begin(), v.end());                       // 翻转
auto it2 = unique(v.begin(), v.end());             // 去重（需先排序）

// Lambda 配合算法
auto pos = find_if(v.begin(), v.end(),
                   [](int x) { return x &gt; 5; });   // 找第一个 &gt;5
transform(v.begin(), v.end(), v.begin(),
          [](int x) { return x * 2; });            // 全部 ×2
```

## 性能速查

| 操作 | vector | list | map | unordered_map |
|------|:--:|:--:|:--:|:--:|
| 随机访问 | O(1) | ❌ | ❌ | ❌ |
| 头插 | O(n) | O(1) | — | — |
| 尾插 | O(1) | O(1) | — | — |
| 中间插 | O(n) | O(1) | — | — |
| 查找 | O(n) | O(n) | O(log n) | O(1) |
| 内存 | 连续 | 碎片 | 树节点 | 桶+链表 |</content:encoded></item><item><title>二分查找与变体</title><link>https://kiyose.wiki/notes/binarysearch/</link><guid isPermaLink="true">https://kiyose.wiki/notes/binarysearch/</guid><description>经典二分、lower_bound/upper_bound、旋转数组、答案二分与边界处理</description><pubDate>Sat, 18 Feb 2023 00:00:00 GMT</pubDate><content:encoded>## 经典二分查找

```cpp
// 在有序数组中查找 target，返回索引，不存在返回 -1
int binarySearch(const vector&lt;int&gt; &amp;arr, int target) {
    int lo = 0, hi = arr.size() - 1;
    while (lo &lt;= hi) {
        int mid = lo + (hi - lo) / 2;  // 防溢出
        if (arr[mid] == target) return mid;
        else if (arr[mid] &lt; target) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}
// 时间 O(log n)，空间 O(1)
```

## lower_bound / upper_bound

```cpp
// 第一个 &gt;= target 的位置
int lowerBound(const vector&lt;int&gt; &amp;arr, int target) {
    int lo = 0, hi = arr.size();
    while (lo &lt; hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] &lt; target) lo = mid + 1;
        else hi = mid;
    }
    return lo;  // 可能 == arr.size()（没找到）
}

// 第一个 &gt; target 的位置
int upperBound(const vector&lt;int&gt; &amp;arr, int target) {
    int lo = 0, hi = arr.size();
    while (lo &lt; hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] &lt;= target) lo = mid + 1;
        else hi = mid;
    }
    return lo;
}

// 使用示例
vector&lt;int&gt; arr = {1, 2, 2, 2, 3, 4};
lowerBound(arr, 2);  // 1  （第一个 2）
upperBound(arr, 2);  // 4  （3 的位置）
// 元素 2 的个数 = upperBound - lowerBound = 3
```

### 边界模板速查

| 目标 | 条件 | lo 更新 | hi 更新 | 返回值 |
|------|------|---------|---------|--------|
| 第一个 &gt;= target | `arr[mid] &lt; target` | `mid+1` | `mid` | `lo` |
| 第一个 &gt; target | `arr[mid] &lt;= target` | `mid+1` | `mid` | `lo` |
| 最后一个 &lt; target | `arr[mid] &lt; target` | `mid` | `mid-1` | `hi` |
| 最后一个 &lt;= target | `arr[mid] &lt;= target` | `mid` | `mid-1` | `hi` |

**记忆口诀**：左闭右开 `[lo, hi)`，缩左用 `mid+1`，缩右用 `mid`。

## 旋转数组查找

```cpp
// [4,5,6,7,0,1,2] 中查找 target
int searchRotated(const vector&lt;int&gt; &amp;arr, int target) {
    int lo = 0, hi = arr.size() - 1;
    while (lo &lt;= hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] == target) return mid;

        if (arr[lo] &lt;= arr[mid]) {  // 左半有序
            if (arr[lo] &lt;= target &amp;&amp; target &lt; arr[mid]) hi = mid - 1;
            else lo = mid + 1;
        } else {                     // 右半有序
            if (arr[mid] &lt; target &amp;&amp; target &lt;= arr[hi]) lo = mid + 1;
            else hi = mid - 1;
        }
    }
    return -1;
}
```

## 二分答案

```cpp
// 场景：找满足条件的最小值 / 最大值
// 例：n 本书分给 m 个人，最小化最大阅读量
bool canSplit(const vector&lt;int&gt; &amp;pages, int m, int maxLoad) {
    int cnt = 1, sum = 0;
    for (int p : pages) {
        if (sum + p &gt; maxLoad) { cnt++; sum = p; }
        else sum += p;
    }
    return cnt &lt;= m;
}

int minMaxLoad(const vector&lt;int&gt; &amp;pages, int m) {
    int lo = *max_element(pages.begin(), pages.end());
    int hi = accumulate(pages.begin(), pages.end(), 0);
    while (lo &lt; hi) {
        int mid = lo + (hi - lo) / 2;
        if (canSplit(pages, m, mid)) hi = mid;
        else lo = mid + 1;
    }
    return lo;
}
```

## 常见陷阱

```cpp
// 陷阱 1: mid 溢出
int mid = (lo + hi) / 2;           // ❌ lo+hi 可能溢出
int mid = lo + (hi - lo) / 2;      // ✅

// 陷阱 2: 死循环
while (lo &lt; hi) {
    int mid = lo + (hi - lo) / 2;
    if (condition) lo = mid;       // ❌ lo 不前进 → 死循环
    // 修正：lo = mid + 1;
}

// 陷阱 3: 边界条件
// lo &lt;= hi   ← 找精确值
// lo &lt; hi    ← 找边界/位置
```</content:encoded></item><item><title>排序算法 — 快排、归并、堆排</title><link>https://kiyose.wiki/notes/sorting/</link><guid isPermaLink="true">https://kiyose.wiki/notes/sorting/</guid><description>三大 O(n log n) 排序算法实现、复杂度分析、稳定性与适用场景对比</description><pubDate>Sun, 15 Jan 2023 00:00:00 GMT</pubDate><content:encoded>## 复杂度总览

| 算法 | 最好 | 平均 | 最坏 | 空间 | 稳定 |
|------|:--:|:--:|:--:|:--:|:--:|
| 快速排序 | n log n | n log n | n² | log n | ❌ |
| 归并排序 | n log n | n log n | n log n | O(n) | ✅ |
| 堆排序 | n log n | n log n | n log n | O(1) | ❌ |
| `std::sort` | — | n log n | n log n | — | 混合(内省) |

## 快速排序

```cpp
// 分区：选 pivot，小的放左，大的放右
int partition(vector&lt;int&gt; &amp;arr, int lo, int hi) {
    int pivot = arr[hi];
    int i = lo - 1;
    for (int j = lo; j &lt; hi; ++j) {
        if (arr[j] &lt;= pivot) swap(arr[++i], arr[j]);
    }
    swap(arr[i + 1], arr[hi]);
    return i + 1;
}

void quickSort(vector&lt;int&gt; &amp;arr, int lo, int hi) {
    if (lo &gt;= hi) return;
    int p = partition(arr, lo, hi);
    quickSort(arr, lo, p - 1);
    quickSort(arr, p + 1, hi);
}
```

**退化到 O(n²) 的场景**：已排序数组 + 选最后一个元素做 pivot → 每次只减少 1 个元素。

**解决**：随机选 pivot 或三数取中 → 期望 O(n log n)。

## 归并排序

```cpp
void merge(vector&lt;int&gt; &amp;arr, int lo, int mid, int hi) {
    vector&lt;int&gt; left(arr.begin() + lo, arr.begin() + mid + 1);
    vector&lt;int&gt; right(arr.begin() + mid + 1, arr.begin() + hi + 1);
    int i = 0, j = 0, k = lo;
    while (i &lt; left.size() &amp;&amp; j &lt; right.size())
        arr[k++] = (left[i] &lt;= right[j]) ? left[i++] : right[j++];
    while (i &lt; left.size()) arr[k++] = left[i++];
    while (j &lt; right.size()) arr[k++] = right[j++];
}

void mergeSort(vector&lt;int&gt; &amp;arr, int lo, int hi) {
    if (lo &gt;= hi) return;
    int mid = lo + (hi - lo) / 2;
    mergeSort(arr, lo, mid);
    mergeSort(arr, mid + 1, hi);
    merge(arr, lo, mid, hi);
}
```

**空间 O(n)**：需要辅助数组。适合链表排序（不需要额外空间）。

## 堆排序

```cpp
void heapify(vector&lt;int&gt; &amp;arr, int n, int i) {
    int largest = i, left = 2 * i + 1, right = 2 * i + 2;
    if (left &lt; n &amp;&amp; arr[left] &gt; arr[largest]) largest = left;
    if (right &lt; n &amp;&amp; arr[right] &gt; arr[largest]) largest = right;
    if (largest != i) {
        swap(arr[i], arr[largest]);
        heapify(arr, n, largest);
    }
}

void heapSort(vector&lt;int&gt; &amp;arr) {
    int n = arr.size();
    // 建堆 O(n)
    for (int i = n / 2 - 1; i &gt;= 0; --i) heapify(arr, n, i);
    // 排序 O(n log n)
    for (int i = n - 1; i &gt; 0; --i) {
        swap(arr[0], arr[i]);
        heapify(arr, i, 0);
    }
}
```

**建堆复杂度为什么是 O(n) 而不是 O(n log n)**：底层节点多但高度小。数学推导：∑ h/2^h = 2，所以总操作正比于 n。

## 选择指南

```
需要稳定？
  ├─ 是 → 归并排序 或 std::stable_sort
  └─ 否 → 内存紧张？
            ├─ 是 → 堆排序
            └─ 否 → 快速排序 / std::sort

std::sort 实际实现：内省排序（IntroSort）
  = 快排 + 递归太深切换堆排 + 小数组切换插入排序
```</content:encoded></item></channel></rss>