<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Blog Feed</title><link href="https://www.bytecho.net/feed.xml" rel="self" /><link href="https://www.bytecho.net" /><id>https://www.bytecho.net</id><updated>2026-05-08T15:12:00.000Z</updated><entry><title>字节星球终于全栈上新！</title><link href="https://www.bytecho.net/archives/bytecho-renew.html" /><id>https://www.bytecho.net/archives/bytecho-renew.html</id><updated>2026-05-08T15:12:00.000Z</updated><summary>这次把博客全栈翻新了一遍，心情真的像给家里换了全新 MacBook Pro 一样，熟悉，但更耀眼了 🤩！至此，我的服务器也再无 PHP 的影子，也算是对一个时代的告别！画了几年的饼也终于完成！</summary><content type="html"># 博客程序换新

**这次把博客全栈翻新了一遍，心情真的像给家里换了全新 MacBook Pro 一样，熟悉，但更耀眼了 🤩！**

写代码的这几天很有生活感：早上先看接口返回，下午盯布局细节，晚上再把交互和文案一点点抛光。等页面终于顺起来那一刻，真的会忍不住在工位上小小欢呼一下！每天基本上都是深夜一两点才睡觉，但很舒服！

**至此，我的服务器也再无 PHP 的影子，也算是对一个时代的告别！画了几年的饼也终于完成！**

## 前台设计语言

这次前台基于 `yuelaiengine-blog` 的能力做了更完整的一体化呈现，不只是能看，而是努力做到好看、好用、好读。

### 1) 视觉气质：克制里带一点温柔

- 整体配色走清爽路线，主色稳，强调色轻轻点一下，不会喧宾夺主。
- 背景不是生硬纯色，而是有轻微层次感的渐变氛围，页面看起来更柔和。
- 卡片化内容区块更统一，信息边界清晰，阅读时不容易累。

简单说，就是希望它像一张整理好的**上床下桌**：有重点、有留白，也有一点点可爱的小心思。**对于各位大学生而言，应该懂上床下桌的含金量！**

### 2) 字体与阅读节奏：看得清，也看得久

- 标题和正文在字重、层级上更分明，读者一眼能抓到信息主次。
- 行高、段落间距、卡片内边距都做了统一，长文阅读更顺，不挤。
- 文章卡片里摘要、标签、元信息（时间/作者/阅读量）排布更整齐，扫读体验更友好。

我自己反复读了几轮，目标很简单：希望每个来访朋友的眼里只有博主所呈现的内容。

### 3) 信息结构：内容不只一条线

前台不再只是文章列表，而是把博客内容组织成多个生活化入口：

- 首页推荐 + 常规文章流
- 分类、标签、归档，方便按主题或时间找内容
- 说说（轻内容）、相册（图文氛围）、友链（社区连接）
- 独立页面能力（如关于页、说明页等）

这让博客更像一个有房间感的小站，而不是单页信息堆叠。

### 4) 交互细节：轻反馈，但不打扰

- 搜索、抽屉导航、下拉菜单、主题切换都做了更直觉的交互路径。
- 点赞、评论、目录折叠、复制分享、二维码等常用动作都更容易被发现。
- 动效以短促和克制为主，给反馈但不抢戏，尽量不打断阅读。

我很喜欢这种页面细腻但不过度设计的感觉，像是很懂分寸的搭档，**在这里吐槽一下有些魔改博客真的很辣眼睛！！！**

### 5) 多端与主题：白天夜里都舒服

- 桌面端保留双栏阅读效率，移动端改为更清晰的一栏逻辑。
- 深色/浅色/跟随系统主题都可用，夜间浏览不会刺眼。
- 细节上包括滚动条、边框对比、按钮状态，也做了统一处理。

## 项目架构及能力简要说明

### Yuelai Engine 通用开发平台

`yuelai-engine` 是我基于 **Golang + Gin + GORM + Casbin + Redis + RabbitMQ + OSS + Vue3/Element Plus** 独立开发的全栈平台，核心目标是提供一套可扩展的后台基础能力（认证、权限、菜单、日志、文件、系统管理）并在此基础上**通过插件承载具体业务域**。

### 基于 Yuelai Engine 的插件式博客程序*

这次能做得比较顺，核心基于 `yuelai-engine + yuelaiengine-blog plugin` 的全链路能力！

作为一个从 2017 年开始建设博客的老玩家，深知博客最需要什么功能，以及如何设计其通用扩展能力。

**可以很自豪的说，我设计的全作用域自定义字段为博客前台扩展提供了无限可能，从基础的功能，到渐进式定制个性化需求，或许这才是博客爱好者最需要的能力。**

**无后台-主题耦合，前后台独立部署，使得前端开发者也能在后台架构黑盒下轻松扩展自己的前台功能。**

- 前后端通过 blog 插件 API 对接：文章、分类、标签、评论、相册、友链、说说等能力协同。
- 自定义字段体系可扩展：站点名、SEO、导航、页脚、主题参数等都可配置。
- 支持服务端代理与生产部署方案：从本地开发到线上发布路径完整。

提供基于 Nuxt3 SSR 的 SEO 友好、部署链路清晰的默认前台主题：[如你现在所见](https://www.bytecho.net/)。

## 此刻的心情

这次换新最让我开心的，不是某一个页面变好看了，而是整条链路真的更顺了。

从后端接口契约、到前台组件化渲染、再到部署上线和稳定运行，前后端是同一口气打通的。

**今天的我，带着一点点十年程序员的小骄傲！也带着一点点可爱满足感，继续把整个平台养得更好看、更耐看、更有温度。**
</content></entry><entry><title>欢迎试用 TodoList</title><link href="https://www.bytecho.net/archives/2394.html" /><id>https://www.bytecho.net/archives/2394.html</id><updated>2025-06-21T08:07:00.000Z</updated><summary></summary><content type="html">### 介绍

YuelaiEngine系列软件 - TodoList

「融合式协作/待办中心」，由 Gin + Element Plus 驱动。

### 项目特性

* 前后端分离架构，简易化部署，仅需一行命令
* 多样化待办清单，合作共享，Todo也能玩出花样
* 多维度待办清单，前端可设计的待办清单
* 邮件待办提醒，让待办不遗忘
* 合作待办清单，一起协作一项任务
* 共享待办清单，分享目前进度
* 支持 Markdown 的待办日志，记录每一次进步
* 待办清单截止锁定能力，不再错过DDL
* 大语言模型能力，一键生成待办清单
* 在线选号组件，简易而不失趣味
* 基于 RedisStream 轻量消息队列
* 轻量静态资源能力

### 技术说明

**「项目后端侧」消息队列感谢 redmq 提供思路。**

**「项目前端侧」侧边栏部分样式由大模型生成，与本项目设计无关！**

### 测试地址

系统下线，服务现已关闭！仅供内网访问！

### 网络要求

仅用于测试，故没有部署至云服务器，**需要IPv6**。

### 提议

反馈至评论区。

</content></entry><entry><title>记一次“说走就走”的成都行</title><link href="https://www.bytecho.net/archives/cd-2025.html" /><id>https://www.bytecho.net/archives/cd-2025.html</id><updated>2025-05-29T14:03:48.000Z</updated><summary></summary><content type="html">记录算是自己第一次的独自旅行，实则还是经历过不少犹豫不少纠结！

大概是在今年三月底，刷小红书刷到了草莓音乐节成都站的信息，看了一下阵容，发现有自己最近喜欢上的 DOUDOU，还有很早就在听的房东的猫，5月底时间正合适，那时候我课基本上完了，而且她们不算太红，所以说不太存在抢票或者说人太多的问题，这时候就有点想买票去试试现场的想法，学生票价也还好，200 来块钱，而且是最后一次用学生票看音乐节的机会了，超过 25 岁就不行了，这又加重了我去成都的决心。

看了下大麦，发现学生票早就售罄了，老闲鱼人首先想到的就是去闲鱼找票代，还算顺利，找到了靠谱的校园代理，拿下了学生票。由于那时候距离音乐节还太早了，后面很长一段时间都没有再去想这件事情。

很快到了5月，我大概在劳动节的时候就把去成都的动车票买好了，不贵，几十块钱（后面还捡漏到了50来块钱的动车票），这时候才开始规划去成都的几天该干嘛，首先想到的就是把成都主流的景区走一遍，虽然我已经去过很多次成都了，但都是和家人们一起去，很多时候得照顾大部分人的想法，所以或许有自己想去的地方，想做的事情没完成，再去走一遍应该会有一些收获。然而看了看门票价，哦～没钱，买不起！

在去成都的前几天，发现小红书推了去成都看演唱会/音乐节各大景区免票的通知，查了下成都文旅公众号，发现确有其事，只能说这波看音乐节算是这么些年做的唯一正确决定，什么好事都凑一块了。之前原本打算和高中同学一起在成都玩几天的，然而他是学医的，在实习，平时特别忙，在事前说没请到假，最后不了了之了，稍显遗憾！只能说随时年龄增大，朋友也好，自己也好，都会慢慢被琐事淹没，所以珍惜现在每一个闲暇时间，及时行乐。

后面就蛮顺利了，音乐现场确实不是什么耳机、音响和电影院能比的，除了天气太热把我手机搞过热了导致没拍全DOUDOU之外没出什么岔子，然后就是有点累。至于究竟去成都玩了啥，怎么玩的，就不细说了，不然就是流水账了，没什么意思。[Gallery](https://www.bytecho.net/albums) 专栏一句话没说但已然道出了一切，这次成都行总的来说消费蛮低！玩得蛮爽！

</content></entry><entry><title>Docker Desktop修改默认存储路径</title><link href="https://www.bytecho.net/archives/2324.html" /><id>https://www.bytecho.net/archives/2324.html</id><updated>2023-09-16T05:06:00.000Z</updated><summary></summary><content type="html">### 前言

最近二开了不少项目，基本都是用Docker部署，所以之前在电脑上装了Docker，不过在 Windows 上安装 Docker Desktop 后，Docker默认是把镜像保存到 `C:\Users\&lt;用户&gt;\AppData\Local\Docker\wsl\data\` 路径的`ext4.vhdx`文件下，在C盘放这些无关紧要的文件属实是浪费，所以整理了一下方法，把这些文件移动到其他磁盘去。

### 修改WSL

首次启动 Docker Desktop 会提示安装 WSL，没有WSL则无法启动 Docker Engine 服务。因为目前的 docker 依附 WSL 来进行文件映射，所以通过 WSL 来修改 docker 的文件映射路径，就可以把这些文件移动到其他磁盘中。

&gt; 默认情况下，Docker Desktop for Window 会创建如下两个发行版：
&gt; 
&gt; 1. docker-desktop (distro/ext4.vhdx)
&gt; 2. docker-desktop-data (data/ext4.vhdx)

目前WSL2已经全量发布，WSL2 下 docker-desktop-data 通常位于以下位置： `C:\Users\&lt;你当前用户名&gt;\AppData\Local\Docker\wsl\data\ext4.vhdx`，我电脑中这个文件夹高达`50GB+`，完全是浪费C盘空间！

在开始操作前，请先退出Docker Desktop，然后在terminal中输入`wsl --list -v`，确保两个服务都是停止状态：

![image.png](/api/v1/uploads/file/969818cac4a784b1e6461df45a2fbd74cd41e9d2.png)

#### 备份镜像

分别输入以下命令备份WSL，后面的备份路径可以自行修改，我这里是备份到了G磁盘：

```sh
wsl --export docker-desktop G:\docker-desktop.tar
wsl --export docker-desktop-data G:\docker-desktop-data.tar
```

#### 取消注册

```sh
wsl --unregister docker-desktop
wsl --unregister docker-desktop-data
```

#### 备份导入

执行命令前，请先更改命令中的`G:\docker-desktop.tar`，`G:\docker-desktop-data.tar`为自己之前备份的路径，挂载的路径`G:\docker\desktop`，`G:\docker\data`也需要改为自己想要挂载的路径（需提前创建好对应的文件夹，不然会提示找不到目录）：

```sh
wsl --import docker-desktop &quot;G:\docker\desktop&quot; &quot;G:\docker-desktop.tar&quot; --version 2
wsl --import docker-desktop-data &quot;G:\docker\data&quot; &quot;G:\docker-desktop-data.tar&quot; --version 2
```

### 测试

输入`wsl --list -v`查看其输出是否和修改之前一样，正常情况下会出现两个发行版，即：`docker-desktop`，`docker-desktop-data`。

然后启动你的Docker Desktop看看是否能够正常运行。之后制作或者拉取的镜像都会存储在新的目录，而不是C盘中的默认路径。

### Docker清理缓存

最后再搬运一个清理docker缓存的教程~

&gt; 在使用 docker build 构建镜像时，Docker 会按照 Dockerfile 中定义的步骤逐步生成 Docker 镜像。而镜像生成的过程中，每一步骤所生成的结果都会被缓存（cache）下来，以便下次镜像生成时不必再重新执行同一步骤以提高构建镜像的速度。

#### 使用 --no-cache

```
docker build --no-cache .
```

#### 使用 docker system prune

使用 `docker system prune` 命令来清理不再使用的资源，包括停止的容器、未被标记的镜像、未使用的网络和未使用的数据卷。

```
# 清理所有不再使用的资源
docker system prune
# 清理更加彻底，将未使用 Docker 镜像都删掉
docker system prune -a
```

---

Henry 23-09-16

</content></entry><entry><title>流程中心使用指南</title><link href="https://www.bytecho.net/archives/2316.html" /><id>https://www.bytecho.net/archives/2316.html</id><updated>2023-09-06T15:07:00.000Z</updated><summary></summary><content type="html">### 前言

很早之前就想让网站的某些操作自动化，今天终于实现了，话不多说，直接介绍吧。
本站工作流系统基于Ferry二次开发，采用Gin + ElementUI(Vue)前后端分离技术，集工单统计、任务钩子、权限管理、灵活设计流程与模版等能力于一身的开源工单系统。

项目二次开发源码已发布到：[Github项目仓库](https://www.bytecho.net/repository.html)

**由于字节星球已经使用全新的 Yuelai Engine，所以流程中心暂时弃用！**

### 如何登录（请以实际页面为准）

首先在[字节星球](https://www.bytecho.net/account/register.php)或其它已接入的平台注册一个账号（若已注册，请直接登录），由于流程中心与主站独立且暂未使用LDAP，所以需要等待系统将账号信息同步至流程中心（**该过程通常小于1分钟**），然后进入[流程中心首页](https://work.yuelaigroup.com/)：

![1720927123606.png](/api/v1/uploads/file/fe1991d126a6abab56a73e3fb943ebd72d066405.png)

**用户账号：同对应平台的账号/用户名**
**初始密码：注册时所用的邮箱**

目前支持的平台有[字节星球](https://www.bytecho.net)和[陌上花博客](https://moshanghua.net)。下面以字节星球账号为例：
![image.png](/api/v1/uploads/file/0198b0ce3984f737fae64516c646d22ac8a80ebb.png)

**用户名处所填写的内容才是你的账号，请勿将下方的Email作为账号，该Email仅用作邮件提醒。**

**首次进入系统后，请及时更改初始密码！请勿使用中文或符号作为用户名，避免登录失败！**

### 如何使用

使用方式很简单，点击工单系统，进入工单申请，选择需要申请的项目，按照表单提示填写后，即可发起申请。
下图以友情链接申请为例：

![image.png](/api/v1/uploads/file/5a3dd19343b87e9814eb23a248d14eabbbc2f017.png)

![image.png](/api/v1/uploads/file/ae12f1f73f3d6be040339592b477eac62d851301.png)

发起申请后，点击工单系统，进入我的工单，实时跟踪流程进度，直到流程结束。
**本系统已接入邮件提醒，所以在注册账号时请务必填写正确的邮箱**，字节星球账号所绑定的邮箱会同步到工作流系统，请注意来自`noreply#yuelaigroup.com`的邮件。

### 流程跟踪

通常一个工单流程有多个步骤，可能有需要申请人处理的流程，则需点击工单系统，进入**我的待办**，处理流转到自己的工单，是否有需要自己处理的工单请留意邮件提醒~

正在流转中的工单：

![image.png](/api/v1/uploads/file/b9cfba1b75f821772c1eb77d0ebf3907b81663d6.png)

已完成的工单：

![image.png](/api/v1/uploads/file/a171a5ad7d162122997c9424a4e0916ab61b83f1.png)

不通过而流程终止的工单：

![image.png](/api/v1/uploads/file/02f733fd3fb5595681f579216ea1144bfe949192.png)

### 已接入的板块

[友情链接](https://www.bytecho.net/links.html)与[星球认证](https://www.bytecho.net/archives/group.html)现已接入流程中心，申请表审核通过后，系统自动添加友情链接/认证信息，无需人工干预，后续会陆续将其他板块接入流程中心。

**由于字节星球已经使用全新的 Yuelai Engine，所以流程中心暂时弃用！**

---

Henry 2023-09-06

</content></entry><entry><title>微分方程小手册</title><link href="https://www.bytecho.net/archives/2307.html" /><id>https://www.bytecho.net/archives/2307.html</id><updated>2023-09-06T08:20:00.000Z</updated><summary></summary><content type="html">### 微分方程框架

![.png](/api/v1/uploads/file/39eae5ef13cf5da3246b7157af74f0a1229c1778.png)

### 一阶微分方程

#### 可分离变量型

形如：${\rm{y}}&apos; = f(x) \cdot g(y)$，有：

$$
{\rm{y}}&apos; = f(x) \cdot g(y) \Rightarrow \frac{{dy}}{{dx}} = f(x) \cdot g(y) \Rightarrow f(x) \Rightarrow \frac{{dy}}{{g(y)}} = f(x)dx \Rightarrow \int {\frac{{dy}}{{g(y)}}}  = \int {f(x)dx}
$$

进一步的，可通过换元得到以上形式的，也可以对其分离变量，如：

$$
{\rm{y}}&apos; = f(ax + by + c) \Rightarrow u{\rm{ = }}ax + by + c \Rightarrow \frac{{dy}}{{dx}} = f(u) \Rightarrow \frac{{du}}{{dx}} = a + bf(u) \Rightarrow \frac{{du}}{{a + bf(u)}} = dx \Rightarrow \int {\frac{{du}}{{a + bf(u)}}}  = \int {dx}
$$

#### 齐次型

形如$y&apos;=f(\frac{y}{x})$或$\frac{1}{y&apos;}=f(\frac{x}{y})$，按照上述方法换元转换为分离变量型，以$y&apos;=f(\frac{y}{x})$为例，令$u=\frac{y}{x}$，有：

$$
y = ux \Rightarrow \frac{{dy}}{{dx}} = x\frac{{du}}{{dx}} + u \Rightarrow y&apos; = \frac{{dy}}{{dx}} = f(u) = x\frac{{du}}{{dx}} + u \Rightarrow \int {\frac{1}{{f(u) - u}}du = \int {\frac{{dx}}{x}} }
$$

#### 一阶线性型

形如：$y&apos;+p(x)y=q(x)$，使用以下公式计算（由于是应试，推导步骤略）：

$$
y = {e^{ - \int p (x)dx}}\left[ {{{\int e }^{\int p (x)dx}} \cdot q(x)dx + C} \right]
$$

上式为一阶线性微分方程的通解公式，其中，式中的${\int p (x)dx}$为$p(x)$的某一个原函数。

注：上述公式中若$\int p (x)dx = \ln \left| {\varphi (y)} \right|$，该绝对值在上述公式中最后可以去掉，产生的$±$可以合并到常数$C$中得到常数$D$。

### 二阶微分方程（可降阶）

**形如：$y&apos;&apos;=f(x,y&apos;)$，即缺$y$型，令$y&apos;=p,y&apos;&apos;=p&apos;$，有：**

$$
y&apos;&apos; = \frac{{dp}}{{dx}} = f(x,y&apos;) = f(x,p)
$$

由上式降阶为一阶微分方程，按一阶微分方程方法求解得到$p=y&apos;=\varphi(x,C_1)$，则可求得原微分方程通解：

$$
y=\int \varphi(x,C_1)dx+C_2
$$

**形如：$y&apos;&apos;=f(y,y&apos;)$，即缺$x$型，令$y&apos;=p,y&apos;&apos;=p&apos;=\frac{dp}{dx}=\frac{dp}{dy} \cdot \frac{dy}{dx}=\frac{dp}{dy} \cdot p$，有：**

$$
y&apos;&apos;=\frac{dp}{dy} \cdot p=f(y,p)
$$

由上式降阶为一阶微分方程，按一阶微分方程方法求解得到$p=y&apos;=\varphi(y,C_1)$，分离变量后积分即可求得原微分方程的通解：

$$
\frac{{dy}}{{\varphi (y,{C_1})}} = dx \Rightarrow \int {\frac{{dy}}{{\varphi (y,{C_1})}}}  = \int {dx}  = x + {C_2}
$$

### 高阶常系数线性微分方程*

**对于形式为：$y&apos;&apos;+py&apos;+qy=f(x)$，$y&apos;&apos;+py&apos;+qy=f_1(x)+f_2(x)$求解步骤如下：**

1. 写出方程$\lambda^2+p\lambda+q=0$，解出$\lambda_1,\lambda_2$或共轭复根；
2. 根据以下类型，写出齐次线性微分方程的**通解**：
$$
y = \begin{cases} 
C_1 e^{\lambda_1 x} + C_2 e^{\lambda_2 x}, &amp; p^2 - 4q &gt; 0 \quad (\text{roots: } \lambda_1 \neq \lambda_2) \\
(C_1 + C_2 x) e^{\lambda x}, &amp; p^2 - 4q = 0 \quad (\text{roots: } \lambda_1 = \lambda_2 = \lambda) \\
e^{\alpha x} (C_1 \cos \beta x + C_2 \sin \beta x), &amp; p^2 - 4q &lt; 0 \quad (\text{roots: } \alpha \pm \beta i)
\end{cases}
$$
3. 对于第一种形式，直接根据自由项$f(x)$的形式设**特解**，对于第二种形式需分别根据自由项$f_1(x),f_2(x)$的形式设两个特解，然后相加得到微分方程的特解，特解形式如下： 
$$
y^* = \begin{cases} 
e^{\alpha x} Q_n(x) x^k, &amp; f(x) = P_n(x) e^{\alpha x} \\
e^{\alpha x} \left[ Q_l^{(1)}(x) \cos \beta x + Q_l^{(2)}(x) \sin \beta x \right] x^k, &amp; f(x) = e^{\alpha x} \left[ P_m(x) \cos \beta x + P_n(x) \sin \beta x \right]
\end{cases}
$$
   - 上式中的$e^{\alpha x}$直接从自由项中照抄，$Q_n$为$x$的$n$次一般多项式，$l=max\{m,n\}$，$Q_l^{(1)},Q_l^{(2)}$分别为$x$的两个不同的$l$次一般多项式。
   - $k$在${p^2} - 4q \ge 0$时：$\alpha$与所有特征根都不相等，此时$k=0$；与其中一个特征根相等，$k=1$；与所有特征根相等，$k=2$。
   - $k$在${p^2} - 4q &lt; 0$时：$\alpha \pm \beta i$不是特征根，此时$k=0$；$\alpha \pm \beta i$是特征根，$k=1$。

最后，将**齐次微分方程的通解加上该微分方程的一个特解**即是非齐次微分方程的通解，简单来说就是先写齐次通解再设非齐次特解，相加得非齐次通解。

**对于$y^{(n)}(n \ge 3)$的情形：**

形如$y&apos;&apos;&apos;+p_1y&apos;&apos;+p_2y&apos;+p_3y=0$，同样的写出特征方程：$\lambda ^3+p_1\lambda^2+p_2\lambda+p_3=0$，解得$\lambda_{1,2,3}$，然后根据以下不同情况直接写出通解：

1. 若$\lambda_i$为单实根：$Ce^{\lambda x}$;
2. 若$\lambda_i$为$k$重实根：$(C_1+C_2x+C_3x^2+\cdots+C_kx^{k-1})e^{\lambda x}$；
3. 若$\lambda_i$为单复根$\alpha\pm\beta i$：$e^{\alpha x}(C_1cos\beta x+C_2sin\beta x)$。

将上述每一个特征根产生的项相加，得到$y$的齐次通解。

---

Henry 2023-09-06

</content></entry><entry><title>近期小记</title><link href="https://www.bytecho.net/archives/2281.html" /><id>https://www.bytecho.net/archives/2281.html</id><updated>2023-07-22T10:18:00.000Z</updated><summary></summary><content type="html">&gt; 炎热的夏天，只能天天在家，没有一点能令人精神的清新而凉爽的空气，搞的一天昏昏沉沉！

家里台式已然是个老不死，还剩最后一口气只能说，死缠烂打之下家里台式机换了新的平台+AMD R7某系列CPU（顺便提一嘴，全能本夏天真难用，散热简直是灾难，不然我也没什么用台式的需求），然而之前的航嘉电源好像有些不稳定，寄去上海修了，也不知道上海那个 B 修电源的啥时候才能给你弄好，寄回来还得三四天，真的难受。

今天帮人搭建 OJ 系统，人家给了 100 左右，还白嫖到了高质量题库，满意！（哦不对，应该是昨天，已经过了12点了~

续上：又有信奥培训机构的判题后端服务遇到了疑难杂症，redis的docker容器反复重启...帮忙解决了，人家主动给了些辛苦费。

今天又发现上次给人搭建的评测机的安全沙箱跑不起来，排查了一下发现是CentOS7导致的问题，配置了一下解决。

此外不得不说，使用docker部署各类业务，使得自己的服务器干净了不少，对有电子洁癖的我非常友好，现在已经对自己的服务器爱不释手了，而不是跟之前一样嫌弃得都不想打开。

现阶段一天天盼望着能够工作，十多年的应试生活，早就已经厌倦了，你说真能学到什么？也就是培养了学习能力和拿到了找工作的敲门砖，实践还得自己来。目前我的好多想法都只能有稳定收入和足够的时间之后才敢尝试，期待这一天！

</content></entry><entry><title>字节星球（肥柴之家）搬家了！</title><link href="https://www.bytecho.net/archives/2274.html" /><id>https://www.bytecho.net/archives/2274.html</id><updated>2023-06-03T12:19:00.000Z</updated><summary></summary><content type="html">### 新闻

字节星球/肥柴之家业务现已迁移至新服务器，告别 106.54.176.177，**全面使用 Docker 部署~**

本次迁移的业务：主站、Blog（暂未使用，仅作为测试）二级、Api二级、Code（Flarum）二级。
~~注：以上服务有用的准确来说只有一个...~~

新增业务：[OnlineJudge](https://acm.bytecho.net)（信息学在线评测服务）欢迎注册使用、私有Git服务以及[服务探针](https://status.bytecho.net)。

![image.png](/api/v1/uploads/file/027b4aa0deb3137bbe8a3ac59719b71b738a2de3.png)
![image.png](/api/v1/uploads/file/0f1966e4f437bb80a50333cfa9163bce2dc43b8f.png)

后续会上线WePlanet Web端（Go+ElementUI，桌面端现已上线）、Home二级（用作字节星球生态导航）。

**注意，Images二级域已经废弃，所以大家的友情链接头像地址需要更新，新的头像地址已在[传送门](https://www.bytecho.net/links.html)页面更新。**

### 另外

106.54.176.177（腾讯云CVM）用了三年，也算是基本横跨了大学四年了，新的服务器（腾讯云）又将跨越我的研究生三年，蛮有意思！

注：本人腾讯忠实铁粉。

另外，在线判题服务器又再次开放了，在合适的时机投入使用！

### 新的开始

![大考（李庚希）](/api/v1/uploads/file/6d251f9503aed7bbad3e0a700bb793a5763f451a.png)
（附一张❤️李庚希的照片，她饰演的角色几乎都符合我心中的理想型，或许可以作为一种无形动力？）

还有十来天就算是本科正式毕业了，读完大学本科，发现个人似乎还是喜欢中学时的那种同学情谊，但教学模式和生活方式我还是更喜欢大学一点。

不管是喜欢与否，这些都已经成为回忆，我发现对于我而言，不论是快乐的还是负面的回忆，都让我觉得美好，始终还是一个喜欢念旧的人，以前的东西舍不得扔，以前的事情也舍不得忘。

继续自己的学术道路（可能是刚刚开始，毕竟本科算不上什么学术）吧，开启硕士研究生生活，本人现在最大的梦想还是能够留在重邮工作！

---

Henry 2023-06-03

</content></entry><entry><title>如何顺利注册ChatGPT？</title><link href="https://www.bytecho.net/archives/2257.html" /><id>https://www.bytecho.net/archives/2257.html</id><updated>2023-04-24T07:53:00.000Z</updated><summary></summary><content type="html">### ChatGPT

欢迎来到 ChatGPT，这里是一个 AI 智能助手，可以帮助您解答各种问题。其实这已经火了一段时间了，现在也推出了付费的ChatGPT Plus **USD $20/m**。

俗话说，**但凡和网红沾边的东西，都多少沾点**，GPT也不例外...因为注册用户太多，以及接口的滥用，现在已经封IP、封邮箱、封账号了，我在今天（2023-04-24）才选择注册ChatGPT，一直因为它是网红而不想去凑热闹。

### 开始注册

请自备一些地区的网络，其中`CN_MAINLAND,CN_HK,CN_TW`除外！

不然你第一步就阵亡了：

![image.png](/api/v1/uploads/file/c9096c8d797c83c9f2549e659fba79eaf7a302ff.png)

#### 访问官网

其网址为：https://chat.openai.com/

#### 关于邮箱

我这边给你们指一条明路（坑已经被我踩过了）：注册时，不要用什么QQ邮箱等烂大街的国内邮箱，大部分人为了方便都会选择这些邮箱，也不要用什么自己的域名邮箱（如bytecho.net邮箱），这些通通会在手机验证码步骤时出现如下错误：
`Your account was flagged for potential abuse. If you feel this is an error, please contact us at help.openai.com.`

**请直接用Google账号注册**，别用花里胡哨的邮箱去注册。
![image.png](/api/v1/uploads/file/5ea7ac93ddabf50ca19e5529197898cb39a0640b.png)

并且这样还不需要接收邮件验证码，直接进入下一步。

#### 填写个人信息

![image.png](/api/v1/uploads/file/64d7d5a00a129a9acee85c40f1078832018e8cc1.png)

这一步暂时无坑。

#### 手机号验证

![image.png](/api/v1/uploads/file/4554777f408ba0d2f729d777220deb7cc462b8f4.png)

首先国内手机号不用想了，用不了，只能用一些虚拟手机号平台，如：https://sms-activate.org/

![image.png](/api/v1/uploads/file/ca10cd955aced883940b60bec220d5d8e608eb10.png)

直接选择OpenAI，选择印度尼西亚的手机号（目前还没有出现滥用问题），如果出现`Your account was flagged for potential abuse.`点×取消订单就行，然后再次下单会有新的手机号产生，反复尝试。

**注：如果邮箱有问题，不管你用什么手机号都会出现`Your account was flagged for potential abuse.`**

#### Over

![image.png](/api/v1/uploads/file/49ff2c3c13ed5f0e02e24cd207f1cb3e8a1f17ff.png)

### 功能演示

![image.png](/api/v1/uploads/file/70d11d6a06e205e7da08f9843bcdf3b2469a3a12.png)

![image.png](/api/v1/uploads/file/4ce0e72255fae95f7342ede332f715d4dc24d522.png)

![image.png](/api/v1/uploads/file/2e4889e0094f170a58458b7f30bae452a4532851.png)

![image.png](/api/v1/uploads/file/7920d30b544383e4d893661aeee9c3eb823df41c.png)

---

字节星球 Henry 2023-04-24 **未经许可 严禁转载！**
https://www.bytecho.net/archives/2257.html

</content></entry><entry><title>披着CLion的外衣实则在讲CMake</title><link href="https://www.bytecho.net/archives/2225.html" /><id>https://www.bytecho.net/archives/2225.html</id><updated>2023-01-29T04:08:00.000Z</updated><summary></summary><content type="html">### CLion 配置

#### 安装和基础设置

至于 CLion 安装和基础设置，网上教程一大把，而且不是学习重点，根据自己需求配置即可。

#### 工具链配置

这个配置是进行 C++ 开发的关键，因为这个编译工具链就意味着 C++ 的编译环境。

按下图点开对应的信息，如果你任何编译工具链都没有添加，由于新版本的 CLion 它会自带一个 mingw 的编译套件，所以默认会有一个 CLion 自带的 mingw 编译工具链。
如下图所示我的编译工具链稍微有点丰富，有 msvc、g++、clang++、mingw，作为一个刚刚入门学编程的新手，我建议编译工具链这一块暂时就没必要了解了，但在 CLion 中编译的具体配置流程我认为还是有必要讲清楚。

![ECC2NHNK0W3FXP.png](/api/v1/uploads/file/cdfafa145b627b17777bc5d8e93063f178762545.png)

CLion 中添加编译工具链非常简单，你本机把对应工具链的路径加入到了环境变量，那么在你点击 `+` 对应编译链类型后，会自动扫描到，如果实在没有扫描到，那么也可以自己填入对应的路径，整个编译链包括：

1. cmake，用于跨平台以及简化底层编译脚本的工具。
2. cmake 生成更底层的编译命令(对应上述的 Build Tool)，比如 gmake 也就是解析.makefile 文件进行命令执行，比如 ninja 解析 .ninja 文件进行命令执行（编译速度比 makefile 更快）。
3. C 语言的编译器(clang/gcc/cl 等等)。
4. C++ 的编译器(clang++/g++/cl 等等)。

如果是 mingw，那么上述的一套都是包含的，只需要把 Toolset 这个选项选择为 mingw 对应的目录即可，选择好后，CLion 会自动识别上述四件套的位置。

接下来简单介绍如何添加一些工具链：

* 安装 msvc 编译工具链：直接到官网下载 VS2022，然后安装对应 C++ 环境，打开 CLion 后添加 msvc 环境时就会自动识别。官网：https://visualstudio.microsoft.com/zh-hans/vs/
* 安装 wsl2：其实 wsl2 的安装已经被简化到了极致，在 powershell 中 `wsl --install` 即可。
  具体的官方文档如下：https://learn.microsoft.com/zh-cn/windows/wsl/install
* 如果需要使用 CLion 进行 Qt 开发，可以查看视频讲解：[www.bilibili.com/video/BV18q…](https://link.juejin.cn?target=https%3A%2F%2Fwww.bilibili.com%2Fvideo%2FBV18q4y1i7kV%2F &quot;https://www.bilibili.com/video/BV18q4y1i7kV/&quot;)  ，对应的配置信息：[gitee.com/yuexingqin/…](https://link.juejin.cn?target=https%3A%2F%2Fgitee.com%2Fyuexingqin%2Ftemplate_qtclion &quot;https://gitee.com/yuexingqin/template_qtclion&quot;)
* 如果需要使用 CLion 进行 STM32 开发，那么可以查看稚晖君在知乎写的博客教程：[zhuanlan.zhihu.com/p/145801160](https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F145801160 &quot;https://zhuanlan.zhihu.com/p/145801160&quot;)

### CMake 配置项

![X04TEKS7F84K2TFNI1.png](/api/v1/uploads/file/66706d6f2391d497b198e43d72860c2a3ee9893d.png)

如上图所示，第二个 `CMake` 选项就是我们现在要讲的，而这两个正好也是整个开发环境中最重要的东西，第一个编译工具链决定了 CLion 中已经识别了本机有哪些编译环境，而第二个 `CMake` 选项，则是用于配置 cmake 基于哪些配置项生成。

所以我们现在应该了解了 CLion 是如何去编译项目生成可执行文件的了。

1. 通过 cmake 配置选项运行整个项目的 CMakeList.txt
2. 生成 makefile 或其他底层脚本后再通过对应的工具去执行这个脚本
3. 运行编译好的程序

而我们现在讲的就是添加 cmake 配置选项，如果你手动写 cmake 命令的话，那样对应的就是命令行参数了。

![S2MKM8_ZB3X_QBSOB.png](/api/v1/uploads/file/533db8c32f9591e6d8964639fad91d6a5a005290.png)

上述图片中已经解释了一些配置的作用。这些配置项一般是不常改动，使用默认值就行，比如 `Build options` 是执行最后的脚本所用的参数，默认为 `-j 12`，比如如果是 makefile，那么就是 `make -j12`。

下面是大家可能需要进行一些配置的选项：

1. Build type：这是程序最终编译的类型，意味着编译器该以何种程度对源代码进行优化，比如 Debug 版本一般再 gcc 中对应 o2 的优化，release 版本对应 o3 的优化，两者一般存在 10 倍左右的性能差距。
2. Toolchain：这是前面所说的编译工具链，一般来说，想要切换编译器，你切换这个选项就行了，默认使用 default 工具链。
3. Generator：这是前面所说的工具链中的较为底层的脚本的运行工具，可以是 makefile 或者 ninja，不选的话也是默认工具链里的那个。
4. CMake options：这个是 cmake 运行时可以加入的命令行参数，比如我们可以-D 来定义对应的变量控制对应的 cmake 行为，甚至于前面的 Build type 我们完全可以不写（当然这是 CLion，这个空必须得被填充），然后使用 `-DCMAKE_BUILD_TYPE=Release`，这个变量可以决定最终 cmake 生成的执行脚本是按照 release 的标准去运行的，又比如 `-DBUILD_SHARED_LIBS=ON`，那么最终是会生成动态库而不是静态库，我上图中的 `-DENABLE_TEST=ON` 是内部的 cmake 有定义一个变量默认为 OFF 值，如果为 ON 时会加入测试代码为子项目。

现在 cmake 在 CLion 中的配置项已经讲完了，简单实践一下来体验之前讲的 CLion 到整个运行的流程：

1. 通过 cmake 配置选项运行整个项目的 CMakeList.txt。
2. 生成 makefile 或其他底层脚本后再通过对应的工具去执行这个脚本。 我们先看一眼上一步 cmake 生成的文件（放出了两个不同的配置项产生的脚本，第一个使用的 Generator 为 ninja，第二个使用的为 gmake）：![UAF6YJ6JWP4U3DJF2K5XW.png](/api/v1/uploads/file/b7da526156ac1e07684d0bcaae7155a47fe9f778.png)
   如果想要继续执行这个脚本，应该在 CLion 中执行对应的源代码，CLion 会自动识别入口点函数，然后给出可执行的按钮。点击执行后，不仅会直接对应的 makefile 或 build.ninja 还会顺便把这个程序运行到 CLion 内置的终端环境中。
3. 运行编译好的程序：这一步已经在第二步一并执行了。

#### CMake 的使用与实战

经过上述文字和图片讲解，我们很自然的想到，整个 CLion 运行 C++ 代码其实就是在运行 cmake 和 makefile(或 build.ninja)，第二个过程我们参与不了，但是第一个 cmake 的编写过程我们却需要一直接触。

下面用 CLion 新建项目自动生成的 cmake 模板来简单对 cmake 语法热热身。

```cmake
cmake_minimum_required(VERSION 3.22)
project(untitled)

set(CMAKE_CXX_STANDARD 17)

add_executable(untitled main.cpp)
```

* `cmake_minimum_required` 命令：规定了编译本项目的 cmake 工具至少需要 3.22 版本。
* `project` 命令：规定了本项目的项目名称，同时也根据这个传入的值生成了一堆变量，常用的如下：
  
  1. `PROJECT_NAME` ：项目名称
  2. `PROJECT_BINARY_DIR` ：项目的二进制文件目录，即编译后的可执行文件和库文件的输出目录
  3. `PROJECT_SOURCE_DIR` ：项目的源文件目录，即包含 CMakeLists.txt 文件的目录
  
  举个简单例子说明上述变量的作用：
  比如一个测试的子项目中的 CMakeList.txt，可能需要写下面的语句（先不管 file 命令），由于是作为直接的子项目，那么里面肯定不会存在 project 语句，所以 PROJECT_SOURCE_DIR 变量表示的仍然是整个项目的根目录，直接通过 `${}` 的形式来使用它即可，这样就不需要关心相对或绝对路径了。
  
  ```cmake
  file(GLOB SONIC_TEST_FILES
       &quot;${PROJECT_SOURCE_DIR}/tests/*.h&quot;
       &quot;${PROJECT_SOURCE_DIR}/tests/*.cpp&quot;
  )
  ```
* `set` 命令：设置对应变量为对应的值，该变量存在，则修改该变量的值，如果不存在则会创建并初始化为对应的值，这里对 set 的使用是设置了 CMAKE_CXX_STANDARD 变量为 17，这个变量可以控制最终编译采用的 C++ 版本，这里是使用 C++17。
* `add_executable` 命令：这是用于生成可执行程序的命令，第一个参数为该执行程序最终编译后生成的文件名，后面跟着的都是需要编译的源代码。

对于新手而言，其实不太需要自己手写 cmake，因为 CLion 会在你新建源文件的时候把相应源文件添加到 add_excutable 命令的后面，但项目稍微大一点或者说引入了很多外部库，那么大概率会抛弃 CLion 的这种自动化了。

#### 常用的 CMake 变量

下面只列出了部分变量的作用，更多的变量请查看文档：https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html

* `PROJECT_NAME` ：项目名称
* `PROJECT_BINARY_DIR` ：项目的二进制文件目录，即编译后的可执行文件和库文件的输出目录
* `PROJECT_SOURCE_DIR` ：项目的源文件目录，即包含 CMakeLists.txt 文件的目录
* `CMAKE_BINARY_DIR` ：当前 CMake 运行的二进制文件目录，通常和 PROJECT_BINARY_DIR 是同一个目录
* `CMAKE_SOURCE_DIR` ：当前 CMake 运行的源文件目录，通常和 PROJECT_SOURCE_DIR 是同一个目录
* `CMAKE_C_STANDARD` ：指定 C 语言的标准版本
* `CMAKE_CXX_STANDARD` ：指定 C++ 语言的标准版本
* `CMAKE_CXX_FLAGS` ：指定编译 C++ 代码时使用的编译选项
* `CMAKE_C_FLAGS` ：指定编译 C 代码时使用的编译选项
* `CMAKE_EXE_LINKER_FLAGS` ：指定链接可执行文件时使用的链接选项
* `CMAKE_SYSTEM_NAME` ：指定当前操作系统名称（如 Windows、Linux 等）
* `CMAKE_SYSTEM_PROCESSOR` ：指定当前处理器的类型（如 x86、x86_64 等）
* `CMAKE_CXX_COMPILER_ID` ：指定了当前使用的 C++ 编译器，同理可得 C 的编译器对应的名字。

##### 对这些变量做一个简单的实践

通过 message 打印出 `PROJECT_BINARY_DIR、PROJECT_SOURCE_DIR、CMAKE_BINARY_DIR、CMAKE_SOURCE_DIR` 来加以验证，目录结构如下：

```
.
├── CMakeLists.txt
├── main.cpp
└── sub
    └── CMakeLists.txt
```

```cmake
main:
cmake_minimum_required(VERSION 3.14)
project(main)

add_subdirectory(sub)

message(STATUS &quot;main:${PROJECT_NAME}\n  pro-src:${PROJECT_SOURCE_DIR}\n pro-bin:${PROJECT_BINARY_DIR}\n cmake-src:${CMAKE_SOURCE_DIR}\n cmake-bin:${CMAKE_BINARY_DIR}&quot;)

sub:
project(sub)

message(STATUS &quot;sub:${PROJECT_NAME}\n  pro-src:${PROJECT_SOURCE_DIR}\n pro-bin:${PROJECT_BINARY_DIR}\n cmake-src:${CMAKE_SOURCE_DIR}\n cmake-bin:${CMAKE_BINARY_DIR}&quot;)
```

打印信息如下：我们发现 CMake 对应的变量没有变化，而 Prject 有了变量，因为我们在 sub 也使用了 project 命令。![QPUOIN6QRMBZK5OR.png](/api/v1/uploads/file/e740c1cfe20d008400479de4a0209b818eaa1ea6.png)

通过变量检测环境执行不同的 cmake 代码：

```cmake
# 判断当前的操作系统
if (CMAKE_SYSTEM_NAME MATCHES &quot;Linux&quot;)
target_link_libraries(my-logger PUBLIC fmt-header-only pthread)
message(STATUS &quot;Now is Linux&quot;)
elseif (CMAKE_SYSTEM_NAME MATCHES &quot;Windows&quot;)
target_link_libraries(my-logger PUBLIC fmt-header-only ws2_32)
message(STATUS &quot;Now is windows&quot;)
endif ()
# 判断当前使用的编译器
if (CMAKE_CXX_COMPILER_ID STREQUAL &quot;GNU&quot;)
# Do something for GCC
elseif (CMAKE_CXX_COMPILER_ID STREQUAL &quot;Intel&quot;)
# Do something for Intel C++
elseif (CMAKE_CXX_COMPILER_ID STREQUAL &quot;Microsoft&quot;)
# Do something for Microsoft Visual C++
elseif (CMAKE_CXX_COMPILER_ID STREQUAL &quot;Clang&quot;)
`# Do something for Clang`
endif()

# 判断当前的系统架构
if (CMAKE_SYSTEM_PROCESSOR MATCHES &quot;i.86|x86|x86_64|AMD64&quot;)
# Do something for x86 architecture
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES &quot;^(arm|aarch64)&quot;)
# Do something for ARM architecture
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES &quot;^(mips|mipsel|mips64)&quot;)
# Do something for MIPS architecture
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES &quot;^(powerpc|ppc64)&quot;)
# Do something for PowerPC architecture
endif()
```

通过调整链接时的 flag 防止动态链接，因为如果你是使用 Windows 平台下的编译工具链，CLion 有些时候最终链接并不是采用静态链接，导致你最终生成的可执行程序没法直接执行，这个时候你就需要使用下面的命令来强制静态链接了：

```
set(CMAKE_EXE_LINKER_FLAGS &quot;-static&quot;)
```

#### 常用的 CMake 命令

下列只列出了部分命令，如果你以后有需要用到的其他命令，请前往官网进行查询：[cmake.org/cmake/help/…](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fmanual%2Fcmake-commands.7.html &quot;https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html&quot;)

我个人较为常用的命令：

1. [project](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fproject.html &quot;https://cmake.org/cmake/help/latest/command/project.html&quot;)：用于定义项目名称、版本号和语言。
2. [add_executable](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fadd_executable.html &quot;https://cmake.org/cmake/help/latest/command/add_executable.html&quot;)：用于添加可执行文件。第一个参数很重要，被称为 target，可以作为 target_xxx 命令的接收对象。
3. [add_library](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fadd_library.html &quot;https://cmake.org/cmake/help/latest/command/add_library.html&quot;)：用于添加库文件，可以创建静态库或动态库。第一个参数很重要，被称为 target，可以作为 target_xxx 命令的接收对象。简单使用如下
   
   ```cmake
   add_library(test_lib a.cc b.cc) #默认生成静态库
   add_library(test_lib SHARED a.cc b.cc) #默认生成静态库
   ```
4. [add_definitions](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fadd_definitions.html &quot;https://cmake.org/cmake/help/latest/command/add_definitions.html&quot;)：用于添加宏定义，注意该命令没有执行顺序的问题，只要改项目中用了该命令定义宏，那么所有的源代码都会被定义这个宏 `add_definitions(-DFOO -DBAR ...)` 。
5. [add_subdirectory](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fadd_subdirectory.html &quot;https://cmake.org/cmake/help/latest/command/add_subdirectory.html&quot;)：用于添加子项目目录，如果有该条语句，就先会跑去执行子项目的 cmake 代码，这样会导致一些需要执行后立马生效的语句作用不到，比如 include_directories 和 link_directories 如果执行在这条语句后面，则他们添加的目录在子项目中无法生效。有些命令如 target_include_directories 和 target_link_directories 是根据目标 target 是否被链接使用来生效的，所以这些命令的作用范围与执行顺序无关，且恰好同一个 cmake 项目中产生的库文件是可以直接通过名称链接的，无论链接对象是在子目录还是父目录
6. [target_link_libraries](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Ftarget_link_libraries.html &quot;https://cmake.org/cmake/help/latest/command/target_link_libraries.html&quot;)：用于将可执行文件或库文件链接到库文件或可执行文件。身为 target_xxx 的一员，很明显第二个参数也可以进行权限控制。
7. [include_directories](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Finclude_directories.html &quot;https://cmake.org/cmake/help/latest/command/include_directories.html&quot;)：用于指定头文件搜索路径，优点是简单直接，缺点是无法进行权限控制，一旦被执行后，后续的所有代码都能搜索到对应的文件路径。
8. [target_include_directories](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Ftarget_include_directories.html &quot;https://cmake.org/cmake/help/latest/command/target_include_directories.html&quot;)：指定头文件搜索路径，并将搜索路径关联到一个 target 上，这里的 target 一般是指生成可执行程序命令里的 target 或者生成库文件的 target，与上一个命令的不同点在于可以设置导出权限，比如现在我写了一个项目，这个项目引入了其他库，但是我不想让其他库的符号暴露出去（毕竟使用这个项目的人只关注这个项目的接口，不需要关注其他依赖的接口）可以通过 PRIVATE 将头文件搜索目录设置不导出的权限。
9. [link_directories](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Flink_directories.html &quot;https://cmake.org/cmake/help/latest/command/link_directories.html&quot;)：与前面的 include_directories 命令类似，添加的是库的搜索路径。
10. [target_link_directories](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Ftarget_link_directories.html &quot;https://cmake.org/cmake/help/latest/command/target_link_directories.html&quot;)：和前面的 include 版本一样的，只是改成了库路径。
11. if\elseif\endif ，在编程语言立马已经用烂了，现在主要是了解 if(condition) 中的条件到底如何判断的，以及内部都支持哪些操作，比如大于等于啥的，这方面直接看官方文档吧，非常好懂：[cmake.org/cmake/help/…](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fif.html &quot;https://cmake.org/cmake/help/latest/command/if.html&quot;)
12. [aux_source_directory](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Faux_source_directory.html &quot;https://cmake.org/cmake/help/latest/command/aux_source_directory.html&quot;)：这个指令简单实用，第一个参数传递一个文件目录，它会扫描这里面所有的源文件放到第二个参数定义的变量名中。注意第一个参数只能是文件夹。`aux_source_directory(${PROJECT_SOURCE_DIR} SRC)`
13. [file](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Ffile.html &quot;https://cmake.org/cmake/help/latest/command/file.html&quot;)：可以说是上面那个命令的增强版本，但如果熟悉这个命令的朋友肯定很快站出来反对，因为这个命令实在是太强大了，你如果翻一翻这个官方文档就会发现它具备几乎文件系统的所有功能，什么读写文件啊，什么从网上下载文件，本地上传文件之类的它都有，计算文件的相对路径，路径转化等等。但我们平时用到的最多的命令还是用来获取文件到变量里。比如 file(GLOB FILES &quot;文件路径表示 1&quot; &quot;文件路径表示 2&quot; ...) GLOB 会产生一个由所有匹配 globbing 表达式的文件组成的列表，并将其保存到第二个参数定义的变量中。Globbing 表达式与正则表达式类似，但更简单，比如如果要实现前一个命令的功能可以这么写：`file(GLOB SRC &quot;${PROJECT_SOURCE_DIR}/*.cc&quot;)`，如果 GLOB 换成 GLOB_RECURSE ，那么上述命令将递归的搜寻其子目录的所有符合条件的文件，而不仅仅是一个层级。
14. [execute_process](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fexecute_process.html &quot;https://cmake.org/cmake/help/latest/command/execute_process.html&quot;)：用于执行外部的命令，如下的示例代码是执行 git clone 命令，执行命令的工作目录在 `${CMAKE_BINARY_DIR}/deps/`：
    
    ```cmake
    execute_process(COMMAND git clone https://github.com/&lt;username&gt;/&lt;repository&gt;.git
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/deps/&lt;repository&gt;)
    ```
15. [message](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fmessage.html &quot;https://cmake.org/cmake/help/latest/command/message.html&quot;)：打印出信息用于 debug。
16. [option](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Foption.html &quot;https://cmake.org/cmake/help/latest/command/option.html&quot;)：用于快速设置定义变量并赋值为对应的 bool 值，常被用于判断某些操作是否执行。
17. [find_package](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Ffind_package.html%23id5 &quot;https://cmake.org/cmake/help/latest/command/find_package.html#id5&quot;)：用于查找外界的 package，其实就是查找外界对应的 `&lt;package&gt;Config.cmake` 和 `Find&lt;package&gt;.cmake` 文件，这些文件里有外界包对应的变量信息以及库和头文件的各种路径信息。我们需要注意一些有关 `find_package` 命令查找 Config.cmake 路径的变量：
    
    * `CMAKE_PREFIX_PATH` 变量是一个路径列表，CMake 会在这些路径中搜索包的 `Config.cmake` 文件。
    * `&lt;Package&gt;_DIR` 变量是指向包的 `Config.cmake` 文件的路径。如果你手动设置了这个变量，那么 `find_package` 命令就可以找到包的信息。
    
    同时他的一些常用参数如下：
    
    * `CONFIG` ：显式指定 find_package 去查找 `&lt;package&gt;Config.cmake` 文件，一般只要你在变量里面指定了 `&lt;package&gt;Config.cmake` 的路径，那么该参数填不填都没差别。我建议最好还是带上该参数比较好。
    * `REQUIRED` ：该参数表示如果没找到，那么直接产生 cmake 错误，退出 cmake 执行过程，如果没有 REQUIRED，则即使没找到也不会终止编译。
    * `PATHS` ：这个参数的效果和前面的变量类似，也是指定查找的路径。
    * `COMPONENTS` ：用于指定查找的模块，模块分离在不同的文件中，需要使用哪个就指定哪个模块。典型的就是使用 Qt 时的 cmake 代码，比如 `find_package(Qt5 COMPONENT Core Gui Widgets REQUIRED)` 。
    * VERSION：可能有很多个不同版本的包，则需要通过该参数来指定，如：`find_package(XXX VERSION 1.2.3)`。
18. [include](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Finclude.html &quot;https://cmake.org/cmake/help/latest/command/include.html&quot;)：从文件或模块加载并运行 CMake 代码。我用这个命令实际上只是为了使用 [FetchContent](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fmodule%2FFetchContent.html%23id1 &quot;https://cmake.org/cmake/help/latest/module/FetchContent.html#id1&quot;) 这个 module 的功能，该功能是从 cmake3.11 开始支持的，使用该 module 前需要通过 include 命令加载该模块，命令如下：`include(FetchContent)`
19. [FetchContent](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fmodule%2FFetchContent.html%23id1 &quot;https://cmake.org/cmake/help/latest/module/FetchContent.html#id1&quot;)：这是一个模块功能，它用来从代码仓库中拉取代码，例如我要把最近写的日志库引入到当前的项目中使用（注意这中间不会有任何代理，所以拉取 GitHub 的仓库可能失败）：
    
    ```
    include(FetchContent)# 引入功能模块
    
    FetchContent_Declare(
            my-logger  		 #项目名称
            GIT_REPOSITORY https://github.com/ACking-you/my-logger.git #仓库地址
            GIT_TAG        v1.6.2  #仓库的版本tag
            GIT_SHALLOW TRUE    #是否只拉取最新的记录
    )
    FetchContent_MakeAvailable(my-logger)
    
    add_excutable(main ${SRC})
    # 链接到程序进行使用
    target_link_libraries(main my-logger)
    ```
    
    这样引入第三方库的好处显而易见，优点类似于包管理的效果了，但缺少了最关键的中心仓库来确保资源的有效和稳定。参考 golang 再做个 proxy 层级就好了。
    同样可以拉取最新的 googletest 可以使用下列语句：
    
    ```
    FetchContent_Declare(
            googletest
            GIT_REPOSITORY https://github.com/google/googletest.git
            GIT_TAG        release-1.12.1
            GIT_SHALLOW TRUE
    )
    # For Windows: Prevent overriding the parent project&apos;s compiler/linker settings
    set(gtest_force_shared_crt ON CACHE BOOL &quot;&quot; FORCE)
    FetchContent_MakeAvailable(googletest)
    
    target_link_libraries(main gtest_main)
    ```
20. [function/endfunction](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Ffunction.html &quot;https://cmake.org/cmake/help/latest/command/function.html&quot;) ：在 cmake 中用于定义函数，复用 cmake 代码的命令。第一个参数为函数的名称，后面为参数的名称，使用参数和使用变量时一样的，但是如果参数是列表类型，则在传入的时候就会被展开，然后与函数参数依次对应，多余的参数被 `ARGN` 参数吸收。

更多较为常用的命令：

* [add_custom_command](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fadd_custom_command.html &quot;https://cmake.org/cmake/help/latest/command/add_custom_command.html&quot;)：添加自定义规则命令，同样也是执行外界命令，但多了根据依赖和产物判断执行时机的作用。
* [install](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Finstall.html &quot;https://cmake.org/cmake/help/latest/command/install.html&quot;)：添加 install 操作。
* [string](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fstring.html &quot;https://cmake.org/cmake/help/latest/command/string.html&quot;)：对 string 的所有操作，比如字符串替换啥的。
* [list](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Flist.html &quot;https://cmake.org/cmake/help/latest/command/list.html&quot;)：对 list 的所有操作，比如列表处理之类的。
* [foreach](https://link.juejin.cn?target=https%3A%2F%2Fcmake.org%2Fcmake%2Fhelp%2Flatest%2Fcommand%2Fforeach.html &quot;https://cmake.org/cmake/help/latest/command/foreach.html&quot;)：cmake 中的 for 循环。
* ...

利用上述命令实现 Qt 开发中调用 uic 工具把 大量的 `.ui` 文件转化为 .cpp 和 .h 文件，并实现当 ui 文件更新时或 .cpp/.h 文件不存在时才创建对应的 .cpp/.h 文件。

```
# 函数功能实现

function(get_ui_source)
foreach (item ${ARGN})
set(UIC_EXE_PATH ${VCPKG_ROOT}/installed/x64-windows/tools/qt5/bin/uic.exe)
get_filename_component(name ${item} NAME_WLE)
string(PREPEND name &quot;ui_&quot;)
set(output_h ${PROJECT_SOURCE_DIR}/ui_gen/${name}.h)
set(output_cpp ${PROJECT_SOURCE_DIR}/ui_gen/${name}.cpp)
file(TIMESTAMP ${item} ui_time)

# 当.h 文件已经存在时，仅当.ui 文件被更新了才重新生成.h 文件

if (EXISTS ${output_h})
file(TIMESTAMP ${output_h} h_time)
if (ui_time GREATER h_time)
execute_process(COMMAND ${UIC_EXE_PATH} ${item} -o ${output_h})
endif ()
else ()
execute_process(COMMAND ${UIC_EXE_PATH} ${item} -o ${output_h})
endif ()

# 当.cpp 文件已经存在时，仅当.ui 文件被更新了才重新生成.cpp 文件

if (EXISTS ${output_cpp})
file(TIMESTAMP ${output_cpp} cpp_time)
if (ui_time GREATER cpp_time)
execute_process(COMMAND ${UIC_EXE_PATH} ${item} -o ${output_cpp})
endif ()
else ()
execute_process(COMMAND ${UIC_EXE_PATH} ${item} -o ${output_cpp})
endif ()
endforeach ()
endfunction()
```

---

Henry 2023-01-29 【此文自用】修改、转载自：https://juejin.cn/post/7184793007302901820

</content></entry><entry><title>守望之墓/电子骨灰盒</title><link href="https://www.bytecho.net/archives/ow.html" /><id>https://www.bytecho.net/archives/ow.html</id><updated>2023-01-23T12:50:00.000Z</updated><summary></summary><content type="html">### 来自上海网之易

&gt; https://ow.blizzard.cn/article/news/2047?blzcmp=app

致各位亲爱的暴雪游戏玩家：

感谢您一直以来给予暴雪游戏产品的支持与厚爱，我们很荣幸能与大家共同携手走过 14 年的历程，一起创造并分享了难忘的游戏体验，再次向大家致以最衷心的感谢！

由于我们与合作方暴雪娱乐的协议期限即将届满，在中国大陆地区由上海网之易网络科技发展有限公司所运营的《魔兽世界》《炉石传说》《守望先锋》《暗黑破坏神 III》《星际争霸 II》《魔兽争霸 III：重制版》《风暴英雄》（以下统称“暴雪游戏产品”），将于 2023 年 1 月 24 日 0 时终止运营，现将终止中国大陆地区运营相关事项通知如下：

* 2022 年 11 月 23 日起，关闭暴雪游戏产品在战网以及客户端内的充值服务及用户注册入口。
  * 在 2022 年 11 月 23 日至 2023 年 1 月 23 日期间，暴雪游戏产品的服务器将正常开放，《魔兽世界》“巨龙时代”内容更新、《炉石传说》“巫妖王的进军”以及《守望先锋》“归来”第 2 赛季内容更新将照常上线，用户可继续登录并体验游戏内容。同时，用户在账户中留存的战网点和虚拟货币将依旧可以在战网商城进行消耗。
* 2023 年 1 月 24 日 0 时起，正式停止暴雪游戏产品的运营，关闭战网登录以及所有游戏服务器，同时关闭客户端下载。
  * 游戏服务器关闭后，各游戏内的所有账号数据及角色资料等（包括但不限于人物角色、剩余游戏时间、各游戏道具、素材、充值信息等）游戏数据将被封存。我们将按照法律法规的要求妥善处理游戏数据，保障用户合法权益。
  * 针对玩家在游戏内已充值但未消耗的网络游戏虚拟货币以及付费购买且仍未失效的游戏时间（如有），我们将在暴雪游戏产品停止运营后开始安排退款，详情请关注与绑定“暴雪游戏服务中心”公众号。

以上所述，还请您周知并相互转告，如有任何疑义，请随时与网易暴雪游戏客服联系（在线支持：[www.battlenet.com.cn/support/zh/](http://www.battlenet.com.cn/support/zh/)；联系电话：0571-28090163）。

我们对此次终止运营给您造成的不便深表歉意！我们十分感谢您的理解和原谅，也衷心期待您继续支持和关注！

上海网之易网络科技发展有限公司

2022 年 11 月 17 日

---

### 来自暴雪中国

亲爱的国服玩家们:

我们想通过今天这封信，对暴雪娱乐在国服地区的游戏服务状况向大家做一些说明。

我们明白，自从网易的相关公告发布以来，这段时间大家都很煎熬，而我们自己也深感忧虑。我们永远都把玩家放在第一位，无论各位来自世界的哪个角落。大家对未来的不确定感，也让我们这些已服务国服玩家社区 20 年的暴雪同事们感到很痛苦。

许多暴雪的同事都是游戏玩家。我们通过电子游戏结识了许多终生挚友，收获了许多珍贵回忆。这些游戏空间对我们来说意义重大，让我们能尽情享受乐趣，成为一个热爱暴雪游戏的玩家。我们非常理解游戏对人们有多重要。许多国服玩家也发来消息和邮件分享你们的感受，你们不能玩到最喜欢游戏的痛苦，你们与暴雪游戏、乃至暴雪早期经典游戏的共同成长经历，你们从那时起就一直是暴雪游戏的玩家。每封邮件读来都让我们唏噓感叹。

正是出于我们作为游戏玩家的个人体会，以及国服玩家向我们所表达的懊恼，我们上周再次与网易接触并寻求协助，以探讨将现有的，基于网易于 2019 年已同意既定条款的协议，顺延六个月，从而使大家得以不受干扰地继续游戏，也让暴雪继续探寻未来在国服地区合理而长远的发展道路。

不幸的是，网易并未在上周的顺延谈判后，接受我们关于顺延现有游戏服务协议的提议。因此我们将不得不遵照网之易停服公告于 1 月 23 日中止国服游戏服务。

---

### 守望之墓（电子骨灰盒）

本人 **2016 至今** 守望生涯之记录，鉴于其承载我和王者级人物二号的太多回忆，故留下其**电子骨灰盒**用于后人瞻仰，让众人铭记于 2016 年诞生之旷世绝唱！

![_20230123_203035.png](/api/v1/uploads/file/2d460c406692e97f0de50140c10284c8_20260506213953.png)
![_20230123_210020.png](/api/v1/uploads/file/ef42d27be0369cff2ba235a513726ee7d226f0e8.png)
![_20230123_203159.png](/api/v1/uploads/file/6fcbe444f566e37e97318b99d19ecfa9c9b0694f.png)
![_20230123_203254.png](/api/v1/uploads/file/75aacc49983064b5c4b5bab1f4e4c4ef84417d1b.png)

“六年”生死两茫茫，不思量，自难忘。 千里孤坟，无处话凄凉。 纵使相逢应不识，尘满面，鬓如霜。

——江城子·乙卯正月二十日夜记梦 苏轼

**致 Blizzard！**

---

### 来自网易公众号 停服前的讣告

亲爱的暴雪游戏玩家：2023 年 1 月 24 日 0 时，由网之易代理的《魔兽世界》《炉石传说》《守望先锋》《暗黑破坏神 Ⅲ》《魔兽争霸 Ⅲ：重制版》《风暴英雄》《星际争霸》系列产品在中国大陆市场的所有运营将正式终止。届时，暴雪将关闭战网登录以及所有游戏服务器，同时关闭客户端下载。

相伴 14 年，说再见很难。我们一直清楚知道，对每个玩家，包括我们自己而言，所有的角色、账号、装备和好友列表，绝不仅仅是一串代码，而是我们的青春，我们的热血，我们的一段美好人生。所以，我们不会忘记对玩家的承诺，仍将尽最大努力，为暴雪国服玩家服务到最后一刻，与玩家共同走完最后一里路。我们将于停服后公布暴雪游戏产品的退款工作安排，请各位玩家关注“暴雪游戏服务中心”公众号。

与国服玩家相伴 14 年，除了感谢，我们更感荣幸。

感谢每个玩家对服务器的包容，对客服服务的理解，对黄金赛现场排队的耐心，甚至对暴雪游戏频道直播中的每一个广告都愿意忍受。

我们更荣幸，大家将人生最重要的青春时光，选择与我们共同度过。我们一起在游戏里与时间为敌，也在平凡的生活里打怪升级，一起创造不可复制的青春回忆。

我们永远记得，曾与每一个玩家在艾泽拉斯的世界里，迎战一个又一个强大的敌人；在炉石酒馆的闲暇中，思考、构筑、切磋牌技；在守望先锋和黑爪的战斗中，成为这个世界需要的英雄；也在庇护之地、在时空枢纽、在科普卢星区，书写篇章、挥洒热血。

这些美好的回忆，不会因停服而消逝，它们就像宝石一样，会在我们未来的平凡生活里闪闪发亮。这也是为什么我们由衷地希望，这次停服不是国服玩家的终点，而只是一次无奈的暂停。

**我们始终坚信，相逢的人总能再相逢。**衷心期待所有暴雪玩家重返国服的那一天。愿风指引我们的道路，愿星辰照亮我们前进的方向。

网易公司 1 月 23 日

---

***这个未来值得为之奋战！***
字节星球 Henry 2023-01-23

</content></entry><entry><title>Python笔记 第三章</title><link href="https://www.bytecho.net/archives/2084.html" /><id>https://www.bytecho.net/archives/2084.html</id><updated>2022-08-21T07:41:00.000Z</updated><summary></summary><content type="html">### for 循环语句

```
for i in range(5): #[1,5）
    print(i)
```

```
for i in range(-10,-100,-30): #步长-30
    print(i)

# &gt;&gt; -10 -40 -70(每个数单独一行)
```

```
for i in range(0)
    print(i) #&gt;&gt; 无输出
```

```
for i in range(2,2)
    print(i) #&gt;&gt; 无输出
```

（注意 `:`，print 前有四个空格）

### for 循环遍历列表

#### 写法一

```
a = [&apos;vx&apos;,&apos;QQ&apos;,&apos;YEB&apos;]
for i in range(len(a)):# len 求列表长度（元素个数）
    print(i,a[i])
#&gt;&gt;0 vx
#1 QQ
#2 YEB
```

- len 也可以用来求字符串长度，元组、集合、字典元素个数

```
print(len(&quot;abc&quot;)) #&gt;&gt;3
```

#### 写法二

```
a = [&apos;vx&apos;,&apos;QQ&apos;,&apos;YEB&apos;]
for i in a:
    print(i)
#&gt;&gt;vx QQ YEB(转行)
```

```
for letter in &apos;omage&apos;:
    print (letter)
#&gt;&gt;o m a g e(转行)
```

### break 语句

```
a = [&apos;vx&apos;,&apos;QQ&apos;,&apos;YEB&apos;]
for i in a:
    if i ==&apos;QQ&apos;:
        print(&quot;over&quot;)
        break #跳出循环
    print(i)
else:
    print(&quot;Not break&quot;)
print(&quot;Done!&quot;)
#&gt;&gt;vx
#over
#Done!
```

### continue 语句

```
for letter in &apos;omage&apos;:
    if letter == &apos;a&apos;:
        continue #不执行此次循环，执行下一次循环
    print (letter)
#&gt;&gt;o
m
g
e
```

### 补充

* 字符的编码
  
  * ord(x) 求字符 x 的编码
  * chr(x) 求编码为 x 的字符
  
  （使用 ASCII 编码方案）

```
for i in range(26):
    print(chr(ord(&apos;a&apos;) + i),end=&quot;&quot;)
#连续输出 26 个英文字符
```

### 例题

```
n = int(input(&quot;请输入将要输入数字的个数：&quot;))
total = 0
for i in range(n): #进行 n 次
    total +=int(input(&quot;请输入要加的数字：&quot;))
print(total)
#输入 n 个整数求和
```

```
n = int(input(&quot;请输入一个整数：&quot;))
for i in range (1,n+1):
    if n % i == 0:
        print(i)

# 输入一个正整数 n，从小到大输出它的公因数
```

```
n = int(input(&quot;请输入一个整数：&quot;))
for i in range (n,0,-1):
    if n % i == 0:
    print(i)
#从大到小
```

### 多重循环

```
for i in range n：
    # ...
    for j in range m：
        # ...
```

```
#从 n 里取两个数使其和等于 m，且每个数只能取一次
c = input(&quot;请输入 n m:&quot;).split()
n,m = int(c[0]),int(c[1])
for i in range(1,n):# n-1 种取法
    for j in range(i + 1,n + 1):#使 i&gt;j,避免重复
        if m % (i + j) == 0:
            print(i,j)
            break #后面的 j 不再取，换下一个 i
```

* 多重循环中的 break 只会跳出那重循环，不会跳出多重循环

### while 循环

```
count = 0
while count&lt;5:
    print(count,&quot;小于 5&quot;)
    count = count + 1
else:
    print(count,&quot;大于或等于 5&quot;)
```

```
i=0
while i&lt;26:
    print(chr(ord(&apos;a&apos;) + i),end=&quot;&quot;)
    i+=1
```

```
s = input().split()
x,y,z = int(s[0]),int(s[1]),int(s[2])
n = m =max(x,y,z)#从最大的数开始试
while not(n%x==0 and n%y==0 and n%z==0):
    n +=m #隔 m 个试一次
print(n)
#求三个数的最小公倍数
```

### 综合例题

```
#求斐波那契数列第 n 项
n =int(input())
c1=1
c2=1
for i in range (n-2):
   c3=c1+c2
    c1=c2
    c2=c3
print(c3)
```

```
#求阶乘的和
n = int(input())
s=0
for i in range (1,n+1):
    f=1
    for j in range (1,i+1):
        f*=j #重复计算多
        s+=f
print(s)
```

```
# 角谷猜想
n=int(input(&quot;请输入一个正整数：&quot;))
while n!=1:
    if n%2==1:
    print(str(n)+&quot;*3+1=&quot;+str(n*3+1))
    n=n*3+1
    else :
        print(str(n)+&quot;/2=&quot;+str(n//2))
    n=n//2
print(&quot;End&quot;)

# 法2：
n=int(input(&quot;请输入一个正整数：&quot;))
b = n
for i in range(n):#n 取多少合适
temp=b
if b==1:
break
if b%2==1:
b1=b*3+1
b =b1
print(&quot;%d = %d * 3 + 1&quot;%(b1,temp))
if b%2==0:
b1=b/2
b=b1
print(&quot;%d = %d / 2&quot;%(b1,temp))
#上式如何改正,已改;法 2：
```

```
#输入一个范围，找出 2 的个数
s=input().split()
ran1,ran2=int(s[0]),int(s[1])
tol=0
for i in range(ran1,ran2+1):
    s=str(i)
    for x in s:
        if x ==&quot;2&quot;:
            tol+=1
print(tol)
#法 2：
s=input().split()
ran1,ran2=int(s[0]),int(s[1])
tol=0
for i in range(ran1,ran2+1):
    if i/10==2 and i%10==2:
        tol+=2
        continue
    if i/10==2 or i%10==2:
        tol+=1
print(&quot;%d&quot;%(tol))
```

---

字节星球 林栈 2022-08-21
https://www.bytecho.net/archives/2084.html

</content></entry><entry><title>WePlanet现已发布！</title><link href="https://www.bytecho.net/archives/2105.html" /><id>https://www.bytecho.net/archives/2105.html</id><updated>2022-08-20T08:09:00.000Z</updated><summary></summary><content type="html">### WePlanet (Desktop) - 🚀适用于小型团体的协作系统


交流、工作、活动、分析、管理、审批...等的功能均集成于简约轻便的 **WePlanet(Desktop)**。

- 平台：Windows x64
- 开发环境：Visual Studio 2022
- 数据库：MySQL
- 语言：C++ 11
- 框架：Qt5.15.x

### 开发进度

**已完成：**

* [x] 用户系统
* [x] 考勤系统
* [x] 个人管理
* [x] 用户管理
* [x] 版本公告
* [x] 活动系统
* [x] 权限系统
* [x] 组织架构
* [x] Markdown 通知动态
* [x] 数据图表
* [x] 数据导出
* [x] 自动更新
* [x] 登录检测
* [x] ECharts 数据大屏
* [x] 认证系统
* [x] 好友系统（测试版）
* [x] 审批系统
* [x] 审批流程设计

**开发计划：**

* [ ] 积分商城
* [ ] 更多...

### 开发进度截图

![1.png](/api/v1/uploads/file/b5ad6c55631958285758b6d8a720c78efd4aa226.png)
![2.png](/api/v1/uploads/file/39b49a342c56bbdffd0017d905f2f3ba733dbeb4.png)
![3.png](/api/v1/uploads/file/8cda1d4b9b4a4b7ccc867204f8a636da8676bd2e.png)
![4.png](/api/v1/uploads/file/b0f94f874f01d918aaced884bdd1b88114af926a.png)
![5.png](/api/v1/uploads/file/f514553b8a863e5ca04904f74c86519742502025.png)
![6.png](/api/v1/uploads/file/939c966b2a7bfab0aa08fb77141e21fcb41e7025.png)

更多截图请查看：https://github.com/csthenry/weplanet-desktop/tree/master/screenshot

### 注意

项目已转换为 Visual Studio 项目，开发环境 Visual Studio 2022， 请使用 Visual Studio 2017+ 添加此项目。

在添加项目前，请先阅读：https://www.bytecho.net/archives/qt_mysql.html

### LICENSE

[GPL-3.0 License](https://github.com/csthenry/weplanet-desktop/blob/master/LICENSE)

### 🔥软件下载

**已经停止维护！期待在下一个项目中相遇！**

🔥**自动更新程序现已实装**，可下载安装后通过软件自动更新检测程序更新至最新版本。

**普通用户直接注册即可试用基本用户功能，评论区可申请试用管理员账号。**


---

Copyright (C) 2017-2023 [字节星球](https://www.bytecho.net/) [Henry](https://www.bytecho.net/about.html)

</content></entry><entry><title>简单选择排序和堆排序</title><link href="https://www.bytecho.net/archives/2090.html" /><id>https://www.bytecho.net/archives/2090.html</id><updated>2022-08-05T14:22:00.000Z</updated><summary></summary><content type="html">最近在全面学习数据结构，常用算法记录：简单选择排序和堆排序，简单选择排序的基本思想是每一趟在待排序元素中选取关键字最小的元素加入有序子序列，直到所有元素有序，总共进行 $n-1$ 趟。
堆排序的基本思想见文末图片。

简单选择排序为**不稳定**排序。
堆排序为**不稳定**排序。

**简单选择排序时间复杂度：**

时间复杂度：$O(n^2)$
空间复杂度：$O(1)$

**堆排序时间复杂度：**

一个节点每下降一层，最多只需要比较两次关键字。若树高度为 $h$，某节点在第 $i$ 层，则将这个节点向下调整最多只需要下降 $h-i$ 层，那么对比次数不超过 ${2}(h-i)$，$n$ 个节点的完全二叉树树高 $h = \left\lfloor {{{\log }_2}n} \right\rfloor  + 1$。

将整棵树调整为大根堆，关键字比较次数不超过：

$$
\sum\limits_{{\rm{i}} = h - 1}^1 {{2^{i - 1}}2(h - i) = } \sum\limits_{{\rm{i}} = h - 1}^1 {{2^i}(h - i) = } \sum\limits_{j = 1}^{h - 1} {{2^{h - j}}j \le 2n\sum\limits_{j = 1}^{h - 1} {\frac{j}{{{2^j}}}} }  \le 4n
$$

建堆的过程关键字的对比次数不超过 ${4}n$，建堆的时间复杂度：$O(n)$

`heapSort`总共需要 $n-1$ 趟，每一趟完成后都需要将根节点下坠，根节点最多下降 $h-1$ 层，因此，每一趟排序的复杂度不超过 $O(h)=O({{{\log }_2}n})$，总共 $n-1$ 趟，故总时间复杂度：$O(n{{{\log }_2}n})$

故堆排序的时间复杂度：$O(n)+O(n{{{\log }_2}n})=O(n{{{\log }_2}n})$
空间复杂度：$O(1)$

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

using namespace std;

void swap(int &amp;a, int &amp;b);
void selectSort(int arr[], int n);  //简单选择排序

void buildMaxHeap(int arr[], int len);  //建立大根堆
void headAdjust(int arr[], int k, int len);  //调整节点，使其较小节点下坠，使其符合大根堆的特性

void heapSort(int arr[], int len);  //堆排序（基于大根堆）

int main()
{
    int arr[] = {5, 7, 12, 6, 2, 0, 8, 15, 1, 11}, heap_arr[] = {-1, 5, 7, 12, 6, 2, 0, 8, 15, 1, 11};
    int length = (int)(sizeof(arr) / sizeof(int));  //数组长度
    selectSort(arr, length);
    for(auto item:arr)
        cout &lt;&lt; item &lt;&lt; &quot; &quot;;
    cout &lt;&lt; endl;

    heapSort(heap_arr, length);
    for(int i = 1; i &lt; length + 1; i++)
        cout &lt;&lt; heap_arr[i] &lt;&lt; &quot; &quot;;

    return 0;
}

void swap(int &amp;a, int &amp;b)
{
    int temp = a;
    a = b;
    b = temp;
}

void selectSort(int arr[], int n)
{
    for (int i = 0; i &lt; n - 1; i++)     //进行n-1次即可，最后一个数必然是最大的
    {
        int min = i;
        for (int j = i + 1; j &lt; n; j++)     //从i后面一个数开始，找到一个最小的数
        {
            if (arr[j] &lt; arr[min])
                min = j;    //记录最小数的下标
        }
        swap(arr[i], arr[min]);     //将最小数与i位置的数交换
    }
}

void buildMaxHeap(int arr[], int len)
{
    for (int i = len / 2; i &gt; 0; i--)  //从最后一个分支节点开始调整，0为暂存节点
        headAdjust(arr, i, len);
}

void headAdjust(int arr[], int k, int len)
{
    arr[0] = arr[k];  //将当前节点暂存给arr[0]
    for (int i = 2 * k; i &lt;= len; i *= 2)   //沿值较大的节点向下查找
    {
        if(i &lt; len &amp;&amp; arr[i] &lt; arr[i + 1])  //i &lt; len 保证当前节点有右孩子
            i++;    //记录左右子节点中较大的节点
        if(arr[0] &gt;= arr[i])
            break;    //如果当前节点大于等于左右子节点，则不需要调整
        else{
            arr[k] = arr[i];   //将左右子节点中较大的节点放入当前双亲节点
            k = i;  //替换为较大的节点，进入下一次循环，看是否满足大于左右孩子的条件
        }
    }
    arr[k] = arr[0];  //将待调整的节点的值放入最终位置
}

void heapSort(int arr[], int len)
{
    buildMaxHeap(arr, len);     //建立大根堆
    for (int i = len; i &gt; 1; i--)
    {
        swap(arr[1], arr[i]);   //堆顶和堆底交换，使堆底元素最大，注意堆顶是arr[1]，arr[0]是暂存节点
        headAdjust(arr, 1, i - 1);  //调整剩余的堆（i及其后面的序列已经有序），让堆顶元素下坠
    }
}
```

![image.png](/api/v1/uploads/file/973b41299ded8d482656b6523d3c88bf_20260506215333.png)

</content></entry><entry><title>希尔排序</title><link href="https://www.bytecho.net/archives/2087.html" /><id>https://www.bytecho.net/archives/2087.html</id><updated>2022-08-04T14:13:00.000Z</updated><summary></summary><content type="html">最近在全面学习数据结构，常用算法记录：希尔排序，基本思想是选定一个增量 $d&lt;n$，将元素按此增量分组（所有相距 $d$ 的元素为一组），然后在各个子序列内进行插入排序，完成后缩小增量 $d&apos;(d&apos;&lt;d)$，如此反复操作，直到增量 $d = 1$ 为止，此时就成了标准的插入排序，但此时大部分元素已经有序，只需要少量操作，甚至不用操作即可完成排序。该排序算法为**不稳定**排序。

希尔排序还是比较绕的，需要多看看，多画一画。

最坏时间复杂度：$O(n^2)$
$n$ 在某范围内可达：$O(n^{1.3})$
目前无法用数学手段证明确切的时间复杂度。

```
#include &lt;iostream&gt;

using namespace std;

void shellSort(int arr[], int n);

int main()
{
    int arr[] = {-1, 5, 7, 12, 6, 2, 0, 8, 15, 1, 11};
    int length = (int)(sizeof(arr) / sizeof(int));  //数组长度
    shellSort(arr, length);
    for (int i = 1; i &lt; length; i++)
        cout &lt;&lt; arr[i] &lt;&lt; &quot; &quot;;
    return 0;
}

void shellSort(int arr[], int n)
{
    int d, i, j;
    //arr[0]为暂存单元
    for (d = n / 2; d &gt; 0; d /= 2)  //d为步长
    {
        for (i = d + 1; i &lt;= n; i++)    //从子表中第二个元素开始
            if(arr[i] &lt; arr[i - d])     //小于子序列前一项
            {
                arr[0] = arr[i];    //暂存待插入元素
                for (j = i - d; j &gt; 0 &amp;&amp; arr[0] &lt; arr[j]; j -= d)
                    arr[j + d] = arr[j];    //向后移动，为待插入元素腾出空位
                arr[j + d] = arr[0];    //插入暂存元素
            }
    }
}
```

![image.png](/api/v1/uploads/file/36186589368b863791051c2cd69fd6df73f003c3.png)

</content></entry><entry><title>插入排序</title><link href="https://www.bytecho.net/archives/2078.html" /><id>https://www.bytecho.net/archives/2078.html</id><updated>2022-08-03T14:08:00.000Z</updated><summary></summary><content type="html">最近在全面学习数据结构，常用算法记录：插入排序，基本思想是将待排序的记录按其关键字的大小逐个插入到一个有序序列（通常为左半部分），直到所有记录插入完成，是一种**稳定**排序。
空间复杂度：$O(1)$
平均时间复杂度：$O(n^2)$

```cpp
#include &lt;iostream&gt;

using namespace std;

//直接插入排序（含哨兵）优点：不用判断j&gt;=0，哨兵即为循环结束标志
void insertSort_1(int arr[], int n);
//直接插入排序（不含哨兵）
void insertSort_2(int arr[], int n);
//折半（二分）插入排序 对直接插入排序的优化
void insertSort_3(int arr[], int n);

int main()
{
    int arr[] = {-1, 5, 7, 12, 6, 2, 0, 8, 15, 1, 11}, arr_2[] = {-1, 5, 7, 12, 6, 2, 0, 8, 15, 1, 11}, arr_normal[] = {5, 7, 12, 6, 2, 0, 8, 15, 1, 11};
    int length = (int)(sizeof(arr) / sizeof(int));  //数组长度
    insertSort_1(arr, length);
    insertSort_2(arr_normal, length - 1);
    for (int i = 1; i &lt; length; i++)
        cout &lt;&lt; arr[i] &lt;&lt; &quot; &quot;;
    cout &lt;&lt; endl;
    for(auto item:arr_normal)
        cout &lt;&lt; item &lt;&lt; &quot; &quot;;
    cout &lt;&lt; endl;
    insertSort_3(arr_2, length);
    for (int i = 1; i &lt; length; i++)
        cout &lt;&lt; arr_2[i] &lt;&lt; &quot; &quot;;
    return 0;
}

void insertSort_1(int arr[],int n)
{
    int i, j;
    for (i = 2; i &lt; n; i++)     //从第二个元素开始，arr[0]为哨兵
    {
        if(arr[i] &lt; arr[i - 1])     //arr[i - 1]为上一个有序序列中的最大元素
        {
            arr[0] = arr[i];
            for (j = i - 1; arr[0] &lt; arr[j]; --j)  //当前元素若小于或等于哨兵元素，则停止移动，哨兵插入该位置后
                arr[j + 1] = arr[j];    //往后移，为待插入元素腾出空位
            arr[j + 1] = arr[0];    //将哨兵插入
        }
    }
}

void insertSort_2(int arr[],int n)
{
    int i, j, temp;
    for (i = 1; i &lt; n; i++)     //从第二个元素开始，arr[0]为哨兵
    {
        if(arr[i] &lt; arr[i - 1])     //arr[i - 1]为上一个有序序列中的最大元素
        {
            temp = arr[i];  //临时存储待插入元素
            for (j = i - 1; j &gt;= 0 &amp;&amp; temp &lt; arr[j]; --j)  //当前元素若小于或等于待插入元素，则停止移动，待插入元素插入该位置后
                arr[j + 1] = arr[j];    //往后移，为待插入元素腾出空位
            arr[j + 1] = temp;    //待插入元素插入
        }
    }
}

void insertSort_3(int arr[], int n)
{
    int i, j, low, high, mid;
    for (i = 2; i &lt; n; i++)
    {
        arr[0] = arr[i];    //待插入元素存入哨兵节点
        low = 1, high = i - 1;  //折半查找的区域
        while(low &lt;= high){
            mid = (low + high) / 2;
            if(arr[mid] &gt; arr[0])
                high = mid - 1;     //查找左半部分
            else
                low = high + 1;     //查找右半部分，同时当arr[mid]==arr[0]时，继续在mid右方查找插入位置，保证算法稳定性
        }
        for (j = i - 1; j &gt;= high + 1; j--)     //循环大于哨兵的所有元素，均往后移动一位
            arr[j + 1] = arr[j];    //往后移，为待插入元素腾出空位
        arr[high + 1] = arr[0];    //待插入元素插入
    }
}
```

![image.png](/api/v1/uploads/file/57756162a219ed9528d5312fc416a2b4bbc4d4e5.png)

</content></entry><entry><title>快速排序</title><link href="https://www.bytecho.net/archives/2075.html" /><id>https://www.bytecho.net/archives/2075.html</id><updated>2022-08-03T12:47:00.000Z</updated><summary></summary><content type="html">最近在全面学习数据结构，常用算法记录：快速排序，即交换排序的一种，是对冒泡排序的一种改进，是一种**不稳定**排序。
平均时间复杂度：$O(nlogn)$
最坏时间复杂度（退化至冒泡排序）：$O(n^2)$

```cpp
#include &lt;iostream&gt;

using namespace std;

//快速排序
void quickSort(int arr[], int low, int high);
void quickSort_another(int *arr, int left, int right);
//划分函数
int partition(int arr[], int low, int high);

int main()
{
    int arr[] = {5, 2, 4, 6, 1, 3};
    quickSort(arr, 0, 5);
    for(auto cur:arr)
        cout &lt;&lt; cur &lt;&lt; &quot; &quot;;
    cout &lt;&lt; endl;
    quickSort_another(arr, 0, 5);
    for(auto cur:arr)
        cout &lt;&lt; cur &lt;&lt; &quot; &quot;;
    return 0;
}

int partition(int arr[], int low, int high)
{
    int pivot = arr[low];   //第一个元素作为基准
    while(low &lt; high)   //low == high时结束
    {
        while(low &lt; high &amp;&amp; arr[high] &gt;= pivot)
            --high;
        arr[low] = arr[high];   //此时arr[high]小于基准元素
        while (low &lt; high &amp;&amp; arr[low] &lt;= pivot)
            ++low;
        arr[high] = arr[low];   //此时arr[low]大于基准元素
    }
    arr[low] = pivot;   //此时low == high
    return low;     //返回基准元素的下标
}
void quickSort(int arr[], int low, int high)
{
    if(low &lt; high)
    {
        int pivot_pos = partition(arr, low, high);
        quickSort(arr, low, pivot_pos - 1);     //对基准元素左边的数组进行快速排序
        quickSort(arr, pivot_pos + 1, high);    //对基准元素右边的数组进行快速排序
    }
}

//写法二
void quickSort_another(int *arr, int left, int right)
{
    if (left &gt;= right)
        return;
    int pivot = arr[left];
    int i = left + 1, j = right;
    while (i &lt;= j)
    {
        while (i &lt;= right &amp;&amp; arr[i] &lt; pivot)
            i++;
        while (j &gt;= left &amp;&amp; arr[j] &gt; pivot)
            j--;
        if (i &lt;= j)
        {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
            i++;
            j--;
        }
    }
    arr[left] = arr[j];
    arr[j] = pivot;
    quickSort_another(arr, left, j - 1);
    quickSort_another(arr, j + 1, right);
}
```

![image.png](/api/v1/uploads/file/d01718fb66e9caa00fff38c8089b1c4ada6580e2.png)

</content></entry><entry><title>Python 笔记 第二章</title><link href="https://www.bytecho.net/archives/2058.html" /><id>https://www.bytecho.net/archives/2058.html</id><updated>2022-08-03T07:41:00.000Z</updated><summary></summary><content type="html">### 算法运算

`/` `+` `-` `*` （结果均为小数，）
`%`（取余） `//`（求商，往小取整）
`**`（求幂）

+ 有小数的算术表达式，结果就是小数(除非经过其他转换)

### 算术优先级

1. **
2. \* / // %
3. \+ -
   （多用 `()`可取代优先级）

### 算术运算的同时赋值

`+=`  实际意义: a+=s 即 a=a+s
（数学符号不可乱用于此）

### 关系运算符

`!=` ` ==` `&gt;` `&lt;&gt;=` `&lt;=`
(可用于字符串比较)

### 逻辑运算符及表达式

#### 逻辑运算符

`and` `or` `not` 三种
操作结果 `True`/`False`

1. 和 `and`

```
n = 4
n &gt;= 2 and n &lt; 5 #&gt;&gt;Ture
print(4 and True) #&gt;&gt;Ture
```

+ 0,&quot;&quot;,[] 都相当于 false（但不等于 false）
+ 非 0 的数，非空的字符串和非空列表都相当于 Ture
+ Ture 可以看作 1，False 可以看作 0

2. 或 `or`

+ 只要有一个为 True，结果为 True
+ 否则为 False

3. 非 `not`
   **在运算关系式中：**

+ 值为 True，结果为 False
+ 值为 False，结果为 True

优先级：
`not`&gt;`and`&gt;`or`

```
print(3&lt;4 or 4&gt;5 and 1&gt;2) #&gt;&gt;Ture
```

(***短路计算：*** 逻辑表达式的计算在整个表达式的值已经能判定的时候就会停止。)

#### 各种运算符的优先级

+ 算术运算符 + - * / // % **
+ 关系运算符 &lt; &gt; == != &gt;= &lt;=
+ 逻辑运算符 and or not
  (不记得就多使用括号)

### 条件分支语句

```
if 逻辑表达式1：
    语句组1
elif 逻辑表达式2：
    语句组2
···#可以有多个 elif
elif 逻辑表达式n：
    语句组n
else：
    语句组n+1
```

```
if 逻辑表达式1：
    语句组1
else (表达式2)：
    语句组2
```

```
if 逻辑表达式1：
    语句组1
```

（注意冒号）

+ 程序语句前不能加空格或制表符（除非在 if while for 语句中）
+ if 语句中的语句组，每条语句左边必须缩进，且格式一样
  
  ```python
  if int(input()) ==5
      print(&quot;a&quot;,end=&quot;&quot;)
  print(&quot;b&quot;)
  #&gt;&gt;b(没有对齐，出错)
  ```
  
  ```
  if 0
  print(0)
  #&gt;&gt; 无输出
  ```

+ if 语句的嵌套
  
  ```
  a =int(input())
  if a &gt;0:
      if a % 2:
          print(&quot;good&quot;)
  else:
      print(&quot;bad&quot;)
  ```

例题：

![image.png](/api/v1/uploads/file/d4aac0302fafabce9363a11115fcfba0a746f1bd.png)

+ 字符串切片（s[x:y]是 s 的从下标 x 到下标 y 的左边那个字符构成的子串）

    ```
    a = &quot;abcdef&quot;
    print(a[2:-1])
    #&gt;&gt; cde
    ```

(注意不要把 if ··· else 或 if ··· elif ··· else 写成多个并列 if )

### 输出格式控制

#### 字符串中的格式控制符

+ %s 表示此处要输出一个字符串
+ %d 表示此处要输出一个整数
+ %f 表示此处要输出一个小数
+ %.nf 表示此处要输出一个小树，保留小数点后 n 位，四舍六入，五不一定
  ```
  print(&quot;My name is %s,I am %.2fm tall.&quot;% (&quot;tom&quot;,1.746))
  #&gt;&gt; My name is tom,I am 1.75m tall.(操作失败)
  ```

---

#### 本章习题

```
s = int (input(&quot;请输入一个数：&quot;))
if s % 2 == 0:
    print (&quot;此数为偶数。&quot;)
else : 
    print (&quot;此数为奇数。&quot;)
#奇数偶数的判断
```

```
tangle = input().split()
a,b,c= int(tangle[0]),int(tangle[1]),int(tangle[2])#注意该行写法
if a+b&gt;c and a+c&gt;b and b+c&gt;a:
print(&quot;yes.&quot;)
else:
print(&quot;no.&quot;)
#输入三个数，判断是否为三角形的三条边。
```

```
s = input(&quot;请输入两个数字，一个符号：&quot;).split()
n1,n2,c = int(s[0]),int(s[1]),s[2]
if c in[&quot;+&quot;,&quot;-&quot;,&quot;*&quot;,&quot;/&quot;]:
if c ==&quot;+&quot;:
print(n1+n2)
elif c ==&quot;-&quot;:
print(n1-n2)
elif c ==&quot;*&quot;:
print(n1*n2)
elif c ==&quot;/&quot;:
if n2 == 0:
print(&quot;Divided by zero!&quot;)
else :
print(n1/n2)
else :
print(&quot;invalid operator!&quot;)
#输入两个数字一个符号进行运算，验证通过
#注意：符号要单独拿出来验证并计算，试搜寻可行计划将其放入数字之中进行运算
#另一种方法：
s = input(&quot;请输入两个数字，一个符号：&quot;).split()
if s[2] not in [&quot;+&quot;,&quot;-&quot;,&quot;*&quot;,&quot;/&quot;]:
print(&quot;invalid operator!&quot;)
elif s[2] ==&quot;/&quot; and int(s[1])==0:
print(&quot;Divided by zero!&quot;)
else:
print(eval(s[0]+s[2]+s[1]))#此行既为上述搜寻的方法
#此方法未将其用其他的字母代替
```

---

字节星球 林栈 2022-08-03 **未经允许，严禁转载！**

</content></entry><entry><title>Python笔记  第一章</title><link href="https://www.bytecho.net/archives/2033.html" /><id>https://www.bytecho.net/archives/2033.html</id><updated>2022-07-23T07:37:00.000Z</updated><summary></summary><content type="html">## 第一章 初步认识

### 符号

+ 均为英文（除非输出为中文字符）

### 注释(方便理解)

#### 单行注释

+ `#`开头

#### 多行注释

+ `Ctrl`+`/`（选中多行文字添加或取消注释）

### 变量（存储数据）

+ 命名：大小写字母、数字和下划线构成，中间不能有空格，长度不限，不能以数字开头。（py 预留的不可做变量名字）

**注意：** 变量名大小可代表不同变量，不可混为一谈

### 赋值语句（变量 = 表达式）

```python
a = &quot;he&quot;
print(a) # &gt;&gt;he（输出为he）
```

__注意：__ 程序从上到下顺序执行

```
a,b = &quot;12&quot;
print(a,b) # &gt;&gt;he 12
a,b=b,a #交换 a,b的值
print(a,b) # &gt;&gt;12 he
```

__注意：__ py 语句前不能随便加空格

### 字符串

- 必须用单引号、双引号或三引号括起来
  
  ```python
  x = &quot;I said:&apos;hello&apos;&quot;
  print(x) # &gt;&gt;I said:&apos;hello&apos;
  ```

__注意：__ 字符串里面不会包含变量且字符串必须用引号括起来。

```python
s = 1.83
print(s)
print(&quot;I am s m tall.&quot;) # &gt;&gt;I am s m tall.
```

+ 三双引号中可以包含换行符、制表符以及其他特殊字符
  
  ```python
  print(&quot;&quot;&quot;多行字符可以使用以下特殊字符: \t  \n &quot;&quot;&quot;)
  ```

- 字符串的下标（编号）
  
  - 每个字符的长度为 1
- 用&quot;+&quot;连接字符串
  （字符串中的字符不可修改）
- 用 `in,not in` 判断子串
  
  ```python
  a = &quot;hello&quot;
  b = &quot;python&quot;
  print(&quot;el&quot; in a) # &gt;&gt;True
  print(&quot;th&quot; not in b) # &gt;&gt;False
  ```

### 字符串与数的转化

- int(x)：把字符串转化为整数（去尾取整）
  - x 是小数就去尾取整
- float(x)：转化为小数
- str(x)： 转化为字符串
- eval(x)：看作一个 py 的表达式，求其值
  （`runtime error` 可能是做了不合法的转换）

### 输入输出

- 输出语句 print
  
  ```python
  print(1, 2, 3, end=&quot;&quot;) # end=&quot;&quot;使其不换行
  print(&quot;ok&quot;)
  # &gt;&gt;1 2 3ok
  ```
- 输入语句 input
  
  ```
  x = input()
  ```

（括号内的内容可提示所输入内容，输入的内容会被赋值给 x，input 每次只输入一行）

### 列表

- 列表可以有 0 到任意多个元素，元素可以通过下标访问
  
  ```python
  empty = [] # 空表
  list_1 = [1,2,3,4,5]
  ```
- 用 in  判断列表是否包含某个元素
  
  ```
  matrix_a = [[1, 2, 3, 1], [2, 6, 8, 1]]
  print([1, 2, 3, 1] in matrix_a) # True
  ```

***例题：***

```python
s = input()
numbers = s.split()
print(int(numbers[0]) + int(numbers[1]))
# 若输入8 9则输出17
```

（若 x 是字符串，则 x.split() 的值是一个列表，包含字符串 x 经空格、制表符、换行符分隔所得到的所有字串）

```python
print(&quot;a c hello&quot;.split) # &gt;&gt;[&apos;a&apos;,&apos;c&apos;,&apos;hello&apos;]
```

---

### 本章习题

```python
c = input(&quot;请输入一个构成三角形的字符：&quot;)
print(&quot;  &quot; + c) # 为什么c前要有+，不可用，会增加空格的数量
print(&quot; &quot; + c + c + c) # 输入空格需要&quot; &quot;
print(c + c + c + c + c) # 此处的五个c相加可用c * 5代替，此处的c为字符串
 # 输入一个字符，构成已知大小的等腰三角形
```

```
s = input(&quot;请输入三个数字：&quot;).split()
n1, n2, n3 = int(s[0]), int(s[1]), int(s[2])
print((n1 + n2) * n3)
# 输入三个数，输出(n1 + n2) * n3 的答案
```

```
n = int(input(&quot;请输入以一个三位数：&quot;))
w1 = int(n / 100)
w2 = int(n % 100 / 10)
w3 = n % 100 % 10
t = w1 + w2 * 10 + w3 * 100
print(t) # print((&quot;%d%d%d&quot;)%(w1,w2,w3))
# 输入三位数反向输出，此法不足：首位为 0 则失效，需再补足
# 法二：
n = input(&quot;请输入以一个三位数：&quot;) # 此处为字符串，不使用 split()可以达成目的
# 输入的字符串没有空格
w1, w2, w3 = n[0], n[1], n[2] # 或用 s = n[2]+n[1]+n[0],则 print(n)
print((&quot;%s%s%s&quot;) % (w3, w2, w1))
```

---

字节星球 林栈 2022-07-23 **未经允许，严禁转载！**
https://www.bytecho.net/archives/2033.html

</content></entry><entry><title>Web使用HarmonyOS字体的压缩方案</title><link href="https://www.bytecho.net/archives/2042.html" /><id>https://www.bytecho.net/archives/2042.html</id><updated>2022-07-11T04:10:00.000Z</updated><summary></summary><content type="html">### HarmonyOS 字体

&gt; https://developer.harmonyos.com/cn/docs/design/font-0000001157868583

通过研究用户在不同场景下对多终端设备的阅读反馈，综合考量不同设备的尺寸、使用场景等因素，同时也考虑用户使用设备时因视距、视角的差异带来的字体大小和字重的不同诉求，我们为 HarmonyOS 设计了全新系统默认的字体——HarmonyOS Sans（即鸿蒙字体）。

![image.png](/api/v1/uploads/file/bf294099dc2ca3c3898e6c76d3ea7b3928d6e7f0.png)
!!!

&lt;center&gt;HarmonyOS 字体效果&lt;/center&gt;
!!!

通过 BILIBILI（哔哩哔哩）主站的使用效果来看，能明显发现 HarmonyOS 字体在 Windows 低分辨率`pixel-ratio &lt; 1.5`屏幕上显示更加细腻。

### 网页加载速度的影响

如果需要全站使用同一种字体，那么我们或许需要同时加载 Regular、Medium、Bold 等不同字重的字体文件，这里给一个参考：
HarmonyOS_Sans_SC_Regular.ttf 文件大小高达 8068KB，注意，这仅仅是 Regular。
所以，如果不对字体文件进行压缩，将其作为 Web 字体是不合理的，这将极大拉缓网页加载速度，严重影响用户体验。

### 字体压缩

#### FontTools

&gt; What is this?

fontTools is a library for manipulating fonts, written in Python. The project includes the TTX tool, that can convert TrueType and OpenType fonts to and from an XML text format, which is also called TTX. It supports TrueType, OpenType, AFM and to an extent Type 1 and some Mac-specific formats. The project has an [MIT open-source licence](https://github.com/fonttools/fonttools/blob/main/LICENSE).

Among other things this means you can use it free of charge.

[User documentation](https://fonttools.readthedocs.io/en/latest/) and [developer documentation](https://fonttools.readthedocs.io/en/latest/developer.html) are available at [Read the Docs](https://fonttools.readthedocs.io/).

#### 如何压缩

借助以上工具，我们可以将 unicode 分为多个片段来对字体文件进行压缩：

| **字符集** | **字数** | **Unicode 编码** |
| -- | -- | -- |
| 拉丁字母 | -- | 0000-007F,0080-00FF等 |
| 基本汉字 | 20902 字 | 4E00-9FA5 |
| 中文字符 | -- | 3002,FF1F等 |

我们只需要对这两万多个基本汉字进行分段即可，至于数字、拉丁字母等，其实并不会过多影响字体文件大小。

将 unicode 合理分段后，使用 fonttools subset 对字体进行压缩，命令如下：

```shell
pyftsubset ./HarmonyOS_Sans_SC_Bold.ttf --unicodes-file=./unicodes.txt --with-zopfli --flavor=woff2

# 参数说明
# pyftsubset &lt;PATH&gt;        # 待压缩字体文件路径
# --unicodes-file=&lt;PATH&gt;   # unicode.txt 文件路径
# --with-zopfli            # 使用 Google 压缩算法
# --flavor=&lt;TYPE&gt;          # 输出字体格式
```

我们将 unicode 写入 unicode.txt 文件中，使用 `--unicodes-file=&lt;PATH&gt;`即可使用。
待所有字体压缩完成后，我们在 CSS 中使用 `unicode-range`属性来调用对应 unicode 区域的字体文件。

具体效果可参考本站 （Windows 且`pixel-ratio &lt; 1.5`环境下）的字体显示情况。

---

字节星球 Henry 2022-07-11 **未经许可 严禁转载！**
https://www.bytecho.net/archives/2042.html

</content></entry><entry><title>字节星球关于在评论区等位置展示IP属地的公告</title><link href="https://www.bytecho.net/archives/2032.html" /><id>https://www.bytecho.net/archives/2032.html</id><updated>2022-07-08T07:50:00.000Z</updated><summary></summary><content type="html">字节星球为工信部、公安部备案网站，有责任、有义务管理字节星球网站环境。

**为维护真实有序的讨论氛围，净化网络环境，减少恶意造谣、恶意广告等不良行为**，字节星球拟在评论区、留言板等位置展示账号 IP 属地，相关功能即日起进行测试，将根据测试情况逐步在其他场景全量上线。

平台展示的账号 IP 属地为用户最近一次发布评论时的网络位置，境内展示到省（区、市），境外展示到国家（地区）。

账号 IP 属地以运营商提供信息为准，相关展示**不支持手动开启或关闭**。

***注：站内认证用户不对外展示 IP 属地。***

**列举部分曾因发布过违规评论而被封禁的 IP 地址：**

106.112.178.237
171.110.238.94
60.172.247.147
117.30.167.48
171.210.201.165
113.116.216.178
139.162.57.142
171.110.239.142
...

---

字节星球 2022-07-08

</content></entry><entry><title>MATLAB简明教程#1</title><link href="https://www.bytecho.net/archives/2021.html" /><id>https://www.bytecho.net/archives/2021.html</id><updated>2022-07-07T09:54:00.000Z</updated><summary></summary><content type="html">### MATLAB 入门之旅

&gt; 若能熟练运用 MATLAB，无疑是开启了探索宇宙间万物之本源的大门。——Henry@(捂嘴笑)

#### 进入 MATLAB

![image.png](/api/v1/uploads/file/5180a96e22a5011291f881414a78f6ce83480187.png)

很好，当我们打开 MATLAB 后，最醒目的部分即是**命令行窗口**，我们试着在其中输入一些简单的命令，开始学习 MATLAB。

#### 基本命令

##### 简单计算

不同于其他高级语言（C++，Java，Python 等），MATLAB 不需要严格的变量定义，试着在**命令行窗口**中输入 `6*8` 并且运行，你会发现 MATLAB 输出了一个名为 ans 的变量值，这即是 `6*8` 的运算结果。

##### 定义变量

要在 MATLAB 中定义变量同样简单，试试以下语句：

```matlab
m = 3 * 5
```

这样就成功定义了一个名为 m 的变量。
再来看看赋值运算符，和我们学习的高级语言一样，它就是一个简单的等号：

```matlab
m = m + 1
```

不用惊喜，它的效果和 C++ 中一样，m 的值被改变为了 `m 本身 + 1`。

![1GIEPV2L4PTJOJ28.png](/api/v1/uploads/file/4509fb2d618d4073c1783e914ad103d2d3e2a361.png)

此时，我们打开 MATLAB 程序右侧的**工作区**，你会发现 ans 变量和我们定义的 m 变量都惊喜的出现在了其中，工作区显示了该变量的名称、值、大小和类型，非常醒目。

##### 语法对比

需要注意的是，或许我们已经发现，之前的命令中似乎不同于 C++，它并没有以分号结尾，事实上，分号在 MATLAB 中的作用于 C++ 等不太一样。

在命令的末尾添加分号将抑制输出，但仍会执行该命令，正如您在工作区中所看到的。当您输入命令而没有以分号结尾时，MATLAB 将会在命令提示符下显示结果（直接执行完成该命令）。

介绍一个小技巧，用过 Linux 终端的朋友应该很熟悉：你可以按键盘上的向上箭头键重新调用以前的命令。
请注意，要执行此操作，**命令行窗口**必须为活动窗口。

##### 保存和加载变量

您可以使用 `save` 命令将工作区中的变量保存到称为 MAT 文件的 MATLAB 特定格式文件中。

要将工作区保存到名为 `foo.mat` 的 MAT 文件中，请使用命令：

```
&gt;&gt; save foo
```
使用 `load` 命令从 MAT 文件加载变量。

```
&gt;&gt; load foo
```

加载完成后，变量 `data` 会在工作区中列出。你可以通过输入变量的名称来查看任何变量的内容。

```
myvar    //你的变量名
```

**Tips:**

使用 `clear` 将工作区清空。
`clear` 函数清理工作区而`clc` 命令清理**命令行窗口**。

好，今天就先介绍到这里。

---

字节星球 Henry 2022-07-07 **未经允许，严禁转载！**
https://www.bytecho.net/archives/2021.html



</content></entry><entry><title>解决Qt5无法连接MySQL数据库的问题</title><link href="https://www.bytecho.net/archives/qt_mysql.html" /><id>https://www.bytecho.net/archives/qt_mysql.html</id><updated>2021-12-13T14:55:00.000Z</updated><summary></summary><content type="html">### 引言

我最近打算开一个新项目，会用到 Qt5 和 MySQL，没想到刚开始就遇到了问题...

```
QSqlDatabase: QMYSQL driver not loaded
QSqlDatabase: available drivers: QSQLITE QODBC QODBC3 QPSQL QPSQL7
```

大体意思就是，这个 QSqlDatabase 里面压根就没有 QMySQL 这个驱动，在我印象中 Qt 肯定是自带了 MySQL 驱动的，搜索了一下知道了原因，在老版本的 Qt 中（5.9 还是 5.12？）在 `C:\Qt\Qt5.xx\5.xx\mingwxx_xx\plugins\sqldrivers` 这个目录下，有 qsqlmysql.dll 这个文件，我这边当然是没有了...所以 MySQL 肯定连不上了，既然没有那只能自己编译了，不可能去网上找吧，不同版本的文件也不同。

**注意：不同版本的 MySQL 里面包含不同的 libmysql.dll，不同的 libmysql.dll 必须和配套的 qsqlmysqld.dll （debug 版）或 qsqlmysql.dll（release 版） 一起才能正常工作！**

### Qt MySQL 驱动构建（使用 MinGW 编译套件）

#### 准备

编译前，请确认以下几点：

1. 你的 Qt 安装时是否选择了 Sources。
2. 你的 Qt 安装时是否选择的 64 位的 MinGW。

![image.png](/api/v1/uploads/file/2e00e7ad0fb16126ad141440539ca52b2b23b811.png)

很多人应该没有选择 Sources 这一项，问题不大，在控制面板-&gt; 卸载程序中找到 Qt 右键选择更改，自行添加 Sources 这个选项，具体做法可以借助搜索引擎，不怕麻烦的也可以直接重新按要求安装 Qt。

然后确定一下你的 `C:\Qt\Qt5.14.2\5.14.2\mingw73_64\plugins\sqldrivers` 文件夹里是否如我描述的那样没有 qsqlmysql.dll 文件，然后再开始下一步。

#### 编译

添加 Qt mingw 64 的环境变量，既然你都接触 Qt 了，环境变量对你来说应该是很熟悉的字眼了，就不说明怎么添加了，分别要添加的路径是：

```
C:\Qt\5.14.2\mingw73_64\bin\
C:\Qt\Tools\mingw730_64\bin\
```

将以上路径替换成你自己的 Qt 安装路径即可，一定不要搞错了。

然后准备好你的 Qt 路径和 MySQL 路径，在终端中分别执行以下四行命令：

```shell
cd C:\Qt\Qt5.14.2\5.14.2\Src\qtbase\src\plugins\sqldrivers
qmake -- MYSQL_INCDIR=&quot;C:\mysql-5.7.36-winx64\include&quot; MYSQL_LIBDIR=&quot;C:\mysql-5.7.36-winx64\lib&quot;
mingw32-make
mingw32-make install
```

上面的路径依然是我自己的路径，请务必更改为自己的 Qt 和 MySQL 路径！

执行完第二行命令后，正常情况会输出以下内容：

```
Info: creating stash file C:\Qt\Qt5.14.2\5.14.2\Src\qtbase\src\plugins\sqldrivers\.qmake.stash

Running configuration tests...
Checking for DB2 (IBM)... no
Checking for InterBase... no
Checking for MySQL... yes
Checking for OCI (Oracle)... no
Checking for ODBC... yes
Checking for PostgreSQL... no
Checking for SQLite (version 2)... no
Checking for TDS (Sybase)... no
Done running configuration tests.

Configure summary:

Qt Sql Drivers:
  DB2 (IBM) .............................. no
  InterBase .............................. no
  MySql .................................. yes
  OCI (Oracle) ........................... no
  ODBC ................................... yes
  PostgreSQL ............................. no
  SQLite2 ................................ no
  SQLite ................................. yes
    Using system provided SQLite ......... no
  TDS (Sybase) ........................... no

Qt is now configured for building. Just run &apos;mingw32-make&apos;.
Once everything is built, you must run &apos;mingw32-make install&apos;.
Qt will be installed into &apos;C:\Qt\Qt5.14.2\5.14.2\mingw73_64&apos;.

Prior to reconfiguration, make sure you remove any leftovers from
the previous build.
```

其中你需要关注你的 Checking for MySQL...后面和 Qt Sql Drivers 中的 MySql 是否都是 yes，如果不是，请检查你的路径和编译前的要求，无误后重新执行命令。

然后进行 make 和 install，中途可能会报一些 Warning，问题不大，只要命令执行完成后，你的 sqldrivers 文件夹中出现了如图所示的 qsqlmysql.dll 就代表编译完成了。

![image.png](/api/v1/uploads/file/c7757da72c9c324617730cc81d5dcb712a4c3dc7.png)

### Qt MySQL 驱动构建（使用 MSVC 编译套件）

#### 准备

与使用 `MinGW` 编译类似，不同的是在使用 QtCreator 配置这个项目时，需要选择对应版本的 MSVC 编译套件，这里以 Qt5.15.2 为例，首先打开 mysql 驱动的 Qt 工程文件：

```
C:\Qt\5.15.2\Src\qtbase\src\plugins\sqldrivers\mysql\mysql.pro
```

![image.png](/api/v1/uploads/file/e00cfc825d74e4e439e5ba19303ea57469a48ed9.png)

在这里我选择 `MSVC2019 64bit` （注意与自己的 Qt 版本对应，这里对应 Qt5.15.2），然后配置项目即可。
然后会出现一些 `error`，此时将 `mysql.pro` 文件中的部分代码修改为：

```
TARGET = qsqlmysql

HEADERS += $$PWD/qsql_mysql_p.h
SOURCES += $$PWD/qsql_mysql.cpp $$PWD/main.cpp

# QMAKE_USE += mysql  注释此处

#以下路径根据自己的mysql安装路径添加
LIBS += -LC:/mysql-5.7.36-winx64/lib -llibmysql
INCLUDEPATH += C:/mysql-5.7.36-winx64/include
DEPENDPATH += C:/mysql-5.7.36-winx64/include

OTHER_FILES += mysql.json

PLUGIN_CLASS_NAME = QMYSQLDriverPlugin
include(../qsqldriverbase.pri)
```

* `LIBS` 指定的本地 `mysql` 动态库路径和动态库的名字；
* `-L` 指定库的路径；
* `-l` 指定库的名字，不需要写后缀，对应的文件全名为 `libmysql.dll`；
* `INCLUDEPATH` 和 `DEPENDPATH` 指定的是本地 `mysql` 的头文件目录。

#### 编译

修改完成后，选择 Release 进行编译，会出现错误：`error: Cannot read C:/qtsqldrivers-config.pri: No such file or directory`，此时打开项目中 `qsqldriverbase.pri`文件：

```
# include($$shadowed($$PWD)/qtsqldrivers-config.pri) 注释此处
include(./configure.pri) # 添加本行
```

然后重新按上述方法编译即可，编译成功之后，就得到了该项目生成的库文件，库文件的位置在安装 Qt 所在盘符的 plugins 目录中。

* `qsqlmysql.dll` Release 版本的动态库
* `qsqlmysqld.dll` Debug 版本的动态库

### 最后

将 `mysql` 安装目录中的 `libmysql.dll` 放入你的 Qt 对应编译套件文件夹的 bin 目录或 exe 文件所在目录，MySQL 数据库即可成功连接，项目最终打包上线时，也别忘了将 libmysql.dll 打包进去。

也可以编辑 qmake 的.pro 文件，在其中把该链接库加进去，但打包时还是需要自行添加 libmysql.dll：

```
LIBS += &quot;C:\mysql-5.7.36-winx64\lib\libmysql.dll&quot;
# 或添加
LIBS += -LC:\mysql-5.7.36-winx64\lib\ -llibmysql
```

libmysql.dll 在你的 MySQL 目录下的 lib 文件夹内，不同版本也是不能混用，这里推荐 MySQL5.7。

其次，还需要将刚才我们编译好的驱动`qsqlmysql.dll`和`qsqlmysqld.dll`复制到以下路径：

```
C:\Qt\5.15.2\msvc2019_64\plugins\sqldrivers # 根据自己的版本选择对应路径
```

参考内容：https://subingwen.cn/qt/sql-driver/

---

Henry 2021-12-13 **未经授权 禁止转载**

```

```

</content></entry><entry><title>时隔多年，终于摆脱了控制台</title><link href="https://www.bytecho.net/archives/1829.html" /><id>https://www.bytecho.net/archives/1829.html</id><updated>2021-08-06T13:09:00.000Z</updated><summary></summary><content type="html">学习 C++ 多年，都是游走在小黑框（终端）里，今天开始了 Qt 学习，给我很早以前的快速/归并排序做了图形界面。

![20210806210240.png](/api/v1/uploads/file/78b7a1c7c181a4d5e93470aa99acf779fde9398d.png)

![20210806210706.png](/api/v1/uploads/file/01b45000daee277829a0c6d5401043b7_20260506215223.png)

学习各类高级语言，数据结构与算法，最终还得要有做项目的能力，一个像样的项目怎么能少的了图形界面。

---

Henry 2021-08-06
</content></entry><entry><title>论内卷</title><link href="https://www.bytecho.net/archives/1822.html" /><id>https://www.bytecho.net/archives/1822.html</id><updated>2021-07-24T08:43:00.000Z</updated><summary></summary><content type="html">**鉴于整天把内卷挂嘴边的人如此之多，我建议每个人都仔细阅读此文！看看你周围人是不是文中所描述的那种人，再看看你自已又是不是？**

&gt; 某种意义上来说，内卷只是弱者的借口词而已。

内卷真正说的是不理性的竞争，但现在却逐渐发展成了别人在努力，你却想躺平但又看见别人在努力而从心里上过不去，这时的你因内心的不平衡而想把你周围努力的人拉下水来让自己安心。你们所谓的“内卷”，以我看来，大概率是个人偏见下的低水平竞争，然而又被伪装成人人都不可避免的高端竞争。

如今一个个大学生，整天不是打游戏就是耍男女朋友，写写作业完成任务，临近期末抱佛脚而求不挂，考试前图书馆与教室满满的人奋笔疾书，这叫内卷吗？这只是自保罢了，内卷的前提是努力做好自己该做的事情来尽力得到属于自己的资源。当一个方向努力的人多了，但资源却没有改变，内卷现象这才产生，努力的人更加努力来争取有限的资源，内卷导致了个体“收益努力比”下降的现象。

我周围很多人整天都在说内卷内卷，其实他们大部分人的实力还达不到内卷的境界（同行间竞相付出更多努力以争夺有限资源那才叫内卷），那有多少人谈得上为自己所热爱而努力呢？把考试前的临时抱佛脚说成是内卷那真是可笑之极！**有时间去抱怨内卷还不如想想自己目前真正差的原因到底是社会上的内卷还是自身严重的惰性以及空洞的理想。**

没有理想何谈方向又何谈努力，整天安于现状只顾短暂的安定，优秀的人在为了未来而被迫内卷，而有的人还在为自己的一些如此简单的考试而担忧。还是那句话，很多人现阶段那水平还达不到与优秀者内卷的资格！当然，内卷并不是一件好事，但抱怨内卷前请先对得起自己。

喜欢的东西就要去努力争取，这样在我们失败的时候，就可以尽情地埋怨这个时代，而不必恼恨自己。

---

全文：Henry 2021-07-24 **未授权禁止转载！**

我的博客即将同步至腾讯云 + 社区，邀请大家一同入驻：https://cloud.tencent.com/developer/support-plan?invite_code=1fl7x472n9ep
</content></entry><entry><title>堆排序</title><link href="https://www.bytecho.net/archives/1819.html" /><id>https://www.bytecho.net/archives/1819.html</id><updated>2021-06-18T08:16:00.000Z</updated><summary></summary><content type="html">输入一个长度为 $n$ 的整数数列，从小到大输出前 $m$ 小的数。

#### 输入格式

第一行包含整数 $n$ 和 $m$。
第二行包含 $n$ 个整数，表示整数数列。

#### 输出格式

共一行，包含 $m$ 个整数，表示整数数列中前 $m$ 小的数。

#### 数据范围

$\rm{1} \le m \le n \le {10^5}$
$\rm{1} \le 数列中的元素 \le {10^9}$

#### 输入样例

```
5 3
4 5 1 3 2
```

#### 输出样例

```
1 2 3
```

#### 题解

**（堆）** 完全二叉树 数据结构

如何手写一个堆？

| 操作                      | 代码                                            |
| --------------------------- | ------------------------------------------------- |
| 插入一个数             | `heap[++size] = x; up(size);`                   |
| 求集合当中的最小值 | `heap[1];`                                      |
| 删除最小值             | `heap[1] = heap[size]; size--; down(1);`        |
| 删除任意一个元素    | `heap[k] = heap[size]; size--; down(k); up(k);` |
| 修改任意一个元素    | `heap[k] = x; down(k); up(k);`                  |

`up()` 函数 $O(logn)$：将当前元素与其父节点进行比较，若小于，则交换；
`down()` 函数 $O(logn)$：将当前元素与其左、右子节点进行比较，若大于，则交换。

#### C++ 代码

```cpp
#include &lt;iostream&gt;
#include &lt;algorithm&gt;

using namespace std;

const int N = 100010;

int n, m;
int heap[N], _size;

void down(int u)
{
    int t = u;
    if(u * 2 &lt;= _size &amp;&amp; heap[u * 2] &lt;= heap[t])//与左节点比较
        t = u * 2;
    if(u * 2 + 1 &lt;= _size &amp;&amp; heap[u * 2 + 1] &lt;= heap[t])//与右节点比较
        t = u * 2 + 1;
    if(u != t)
    {
        swap(heap[u], heap[t]);
        down(t);//继续递归
    }
}

int main()
{
    cin &gt;&gt; n &gt;&gt; m;
    for (int i = 1; i &lt;= n; i++)//从1开始较为方便
        cin &gt;&gt; heap[i];
    _size = n;
    for (int i = n &gt;&gt; 1; i; i--)//从倒数第二层开始down即可，最后一层不需要
        down(i);
    while(m--)
    {
        cout &lt;&lt; heap[1] &lt;&lt; &apos; &apos;;//输出堆顶（最小元素）
        heap[1] = heap[_size], _size--;//删除堆顶
        down(1);
    }
}
```

本题并不需要 `up()` 函数，故单独列出：

```cpp
void up(int u)
{
    while(u / 2 &amp;&amp; heap[u / 2] &gt; heap[u])
    {
        swap(heap[u / 2], heap[u]);
        u /= 2;
    }
}
```

</content></entry><entry><title>连通块中点的数量</title><link href="https://www.bytecho.net/archives/1818.html" /><id>https://www.bytecho.net/archives/1818.html</id><updated>2021-06-14T08:30:00.000Z</updated><summary></summary><content type="html">给定一个包含 $n$ 个点（编号为 $\rm{1} \sim {\rm{n}}$ ）的无向图，初始时图中没有边。
现在要进行 $m$ 个操作，操作共有三种：

1. “C a b”，在点 $a$ 和点 $b$ 之间连成一条边，$a$ 和 $b$ 可能相等；
2. “Q1 a b”，询问点 $a$ 和点 $b$ 是否在同一连通块中，$a$ 和 $b$ 可能相等；
3. “Q2 a”，询问点 $a$ 所在连通块中点的数量。

#### 输入格式

第一行输入整数 $n$ 和 $m$。
接下来 $m$ 行，每行包含一个操作指令，指令为以上三种中的其中一种。

#### 输出格式

对于每个询问指令“Q1 a b”，如果$a$ 和 $b$ 在同一连通块中，则输出“Yes”，否则输入“No”。
对于每个询问指令“Q2 a”，输出一个整数表示点 $a$ 所在连通块中点的数量。
每个结果占一行。

#### 数据范围

$\rm{1} \le n,m \le {10^5}$

#### 输入样例

```
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
```

#### 输出样例

```
Yes
2
3
```

#### 题解

**（并查集）** 数据结构

具体实现同：[合并集合](https://www.bytecho.net/archives/1814.html)

#### C++ 代码

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

using namespace std;

const int N = 100010;

int n, m;
int p[N], _size[N];  //size表示每一个集合的元素个数，只需根节点size有意义即可

int find(int x) //返回x所在集根节点 + 路径压缩优化
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin.tie(0); //优化cin
    ios::sync_with_stdio(false);    //优化cin
    scanf(&quot;%d%d&quot;, &amp;n, &amp;m);
    for (int i = 1; i &lt;= n; i++)
    {
        p[i] = i;//初始化，每个数各自在一个集合
        _size[i] = 1;
    }
    while (m--)
    {
        char op[5];
        int a, b;
        cin &gt;&gt; op;
        if(op[0] == &apos;C&apos;)
        {
            cin &gt;&gt; a &gt;&gt; b;
            if(find(a) == find(b))  //如果a和b以及在同一个集合中，则不需要合并
                continue;
            _size[find(b)] += _size[find(a)]; //合并集合时，同时合并两个集合的元素个数
            p[find(a)] = find(b);
        }
        else if(op[1] == &apos;1&apos;)
        {
            cin &gt;&gt; a &gt;&gt; b;
            find(a) == find(b) ? puts(&quot;Yes&quot;) : puts(&quot;No&quot;);
        }
        else
        {
            cin &gt;&gt; a;
            printf(&quot;%d\n&quot;, _size[find(a)]);
        }
    }
    return 0;
}
```

</content></entry><entry><title>合并集合（并查集）</title><link href="https://www.bytecho.net/archives/1814.html" /><id>https://www.bytecho.net/archives/1814.html</id><updated>2021-06-11T14:01:00.000Z</updated><summary></summary><content type="html">一共有 $n$ 个数，编号是 $\rm{1} \sim n$，最开始每个数各自在一个集合中。
现在要进行 $m$ 个操作，操作共有两种：

1. “M a b”，将编号为 $a$ 和 $b$ 的两个数所在的集合合并，如果两个数已经在一个集合中，则忽略这个操作；
2. “Q a b”，询问编号为 $a$ 和 $b$ 的两个数是否在同一集合中。

#### 输入格式

第一行输入整数 $n$ 和 $m$。
接下来 $m$，每行包含一个操作指令，指令为“M a b”或“Q a b”其中一种。

#### 输出格式

对于每个询问指令“Q a b”，都要输出一个结果，如果 $a$ 和 $b$ 在同一集合内，则输出“Yes”，否则输出“No”。
每个结果占一行。

#### 数据范围

$\rm{1} \le n,m \le {10^5}$

#### 输入样例

```
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
```

#### 输出样例

```
Yes
No
Yes
```

#### 题解

**（并查集）** 数据结构

并查集介绍：

1. 将两个集合合并
2. 询问两个元素是否在一个集合当中

基本原理：每个集合用一棵树来表示，树根的编号就是整个集合的编号，每个节点存储它的父节点，p[x]表示 x 的父节点。

问题 1：如何判断树根：`if(p[x] == x)`；
问题 2：如何求 x 的集合编号：`while(p[x] != x) x = p[x];`；
问题 3：如何合并两个集合：p[x]是 x 集合编号，p[y]是 y 的集合编号，`p[x] = y;`

![image.png](/api/v1/uploads/file/274544076465409273b617777f674c947e8f520d.png)

!!!
&lt;center&gt;图1 [并查集及其路径压缩优化] 闫学灿&lt;/center&gt;
!!!

#### C++ 代码

```cpp
#include &lt;iostream&gt;

using namespace std;

const int N = 100010;

int n, m;
int p[N];

int find(int x) //返回x所在集根节点 + 路径压缩优化
{
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}

int main()
{
    scanf(&quot;%d%d&quot;, &amp;n, &amp;m);
    for (int i = 1; i &lt;= n; i++)
        p[i] = i;//初始化，每个数各自在一个集合
    while (m--)
    {
        char op[2];
        int a, b;
        cin &gt;&gt; op &gt;&gt; a &gt;&gt; b;
        if(op[0] == &apos;M&apos;)
            p[find(a)] = find(b);
        else
            find(a) == find(b) ? puts(&quot;Yes&quot;) : puts(&quot;No&quot;);
    }
    return 0;
}
```

</content></entry><entry><title>Trie字符串统计</title><link href="https://www.bytecho.net/archives/1812.html" /><id>https://www.bytecho.net/archives/1812.html</id><updated>2021-06-11T12:23:00.000Z</updated><summary></summary><content type="html">维护一个字符串集合，支持两种操作：

1. “I x”向集合中插入一个字符串 $x$；
2. “Q x”询问一个字符串在集合中出现了多少次。

共有 $N$ 个操作，输入的字符串总长度不超过 $\rm{10^5}$，字符串仅包含小写英文字母。

#### 输入格式

第一行包含整数 $N$，表示操作数。
接下来 $N$ 行，每行包含一个操作指令，指令为“I x”和“Q x”中的一种。

#### 输出格式

对于每个操作指令“Q x”，都要输出一个整数作为结果，表示 $x$ 在集合中出现的次数。
每个结果占一行。

#### 数据范围

$\rm{1} \le N \le 2*{10^4}$

#### 输入样例

```
5
I abc
Q abc
Q ab
I ab
Q ab
```

#### 输出样例

```
1
0
1
```

#### 题解

**（Trie 树）** 数据结构
插入操作：
从根节点开始，枚举当前字符串，如果对应字母节点存在，则进入下一个节点，否则创建节点。字符串枚举完成后，创建当前节点单词结尾标记。
查询操作：
同插入操作，如果任意一个字母节点不存在，则意味着该字符串一定不存在，否则继续枚举，最终范围尾节点的单词结尾标记。

#### C++ 代码

```cpp
#include &lt;iostream&gt;

using namespace std;

const int N = 100010;

int son[N][26];//子节点最多26个（26个英文字母）
int cnt[N], idx;//下标为0的点即是根节点，又是空节点，cnt为以当前点结尾的单词数量，idx表示当前可用的节点
char str[N];

void insert(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i++)
    {
        int u = str[i] - &apos;a&apos;;//转化为字母顺序
        if(!son[p][u])//如果节点不存在，则创建
            son[p][u] = ++idx;
        p = son[p][u];//进入下一个点，即子节点
    }
    cnt[p]++;//单词结尾标记
}
int query(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i++)
    {
        int u = str[i] - &apos;a&apos;;
        if(!son[p][u])
            return 0;//中途有一个点不存在，就不存在
        p = son[p][u];
    }
    return cnt[p];//返回单词结尾标记，即以该节点结尾的单词数量
}
int main()
{
    int n;
    cin &gt;&gt; n;
    while(n--)
    {
        char op[2];
        scanf(&quot;%s%s&quot;, op, str);
        if(op[0] == &apos;I&apos;)
            insert(str);
        else
            cout &lt;&lt; query(str) &lt;&lt; endl;
    }
    return 0;
}
```

</content></entry><entry><title>KMP字符串</title><link href="https://www.bytecho.net/archives/1801.html" /><id>https://www.bytecho.net/archives/1801.html</id><updated>2021-06-09T07:23:00.000Z</updated><summary></summary><content type="html">给定一个模式串 $S$，以及一个模板串 $P$，所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 $P$ 在模式串 $S$ 中多次作为字串出现。
求出模板串 $P$ 在模式串 $S$ 中所有出现的位置的起始下标。

#### 输入格式

第一行输出整数 $N$,表示字符串 $P$ 的长度。
第二行输入字符串 $P$。
第三行输入整数 $M$，表示字符串 $S$ 的长度。
第四行输入字符串 $M$。

#### 输入格式

共一行，输出所有出现位置的起始下标（下标从 $\rm{0}$ 开始计数），整数之间用空格隔开。

#### 数据范围

$\rm{1} \le N \le 10^4$
$\rm{1} \le M \le 10^5$

#### 输入样例

```
3
aba
5
ababa
```

#### 输出列表

```
0 2
```

#### 题解

**（KMP）** $O(m+n)$
KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的，称之为 Knuth-Morria-Pratt 算法，简称 KMP 算法。该算法相对于 Brute-Force（暴力）算法有比较大的改进，主要是消除了主串指针的回溯，从而使算法效率有了某种程度的提高。

KMP 算法的关键就是求 next 数组：
next 数组长度与字符串长度一致，每个位置存储对应字符的最长匹配长度。
next 数组即 `next[i] = length`; 长度为 $i$ 的数组的**前缀和后缀相等**的最大长度。 例如 $abcdeabc$ 就是 `next[8] = 3`; 相等的前缀和后缀最长是 $abc$ 长度为 $\rm{3}$。
为了更透彻理解 next 数组，我们来举一个**例子**：
模板串 $P$ 为：$abababab$（令其下标从 $\rm{1}$ 开始）
`next[1]` 即模板串中取长度为 $\rm{1}$ 的串，如果匹配失败，则退回到起点，故 `next[1]=0`；
`next[2]` 即模板串中取长度为 $\rm{2}$ 的串，显然其前缀 $a$ 和后缀 $b$ 不相等，故 `next[2]=0`；
`next[3]` 即模板串中取长度为 $\rm{3}$ 的串，其前缀 $a$ 和后缀 $a$ 相等，$a$ 的长度为 $\rm{1}$，故 `next[3]=1`；

$$
\vdots
$$

`next[6]` 即模板串中取长度为 $\rm{6}$ 的串（$ababab$），其前缀 $abab$ 和后缀 $abab$ 相等，该字串长度为 $\rm{4}$ ，故 `next[6]=4`，以此类推，`next[7]=5`。

到此，我们应该明白 next 数组的意义了，现在我们加上模式串 $S$ 来看，假设 $S$ 为：$abababcab$（同样令其下标从 $\rm{1}$ 开始），以下矩阵代表当前两个字符串的对应关系：

$$
\left[ \begin{array}{l}
{\rm{a b a b a b c a b}}\\
{\rm{a b a b a b a b x}}
\end{array} \right]
$$

（$x$ 代表空格），可见在 s[7] 时 $c$ 与 模板串 p[j + 1] 中 $a$ 不匹配，此时，我们如果令 `j = next[j]`，当前 $j=6$ 令 `j=next[j]=4` 后会发生什么，请看矩阵：

$$
\left[ \begin{array}{l}
{\rm{a b a b a b c a b x x x}}\\
{\rm{x x a b a b a b a b x x}}
\end{array} \right]
$$

为什么要这样操作？
因为我们 `next[i]` 长度为 $i$ 的数组的**前缀和后缀相等**的最大长度，刚才我们已经判断得到 p[1] $ \sim$ p[6] 一定是和 s[1] $\sim$ s[6] 完全匹配的，又因为 `next[i]=4`，故 p[1] $ \sim$ p[6]的前 $\rm{4}$ 个字符与后 $\rm{4}$ 个字符是完全相同的，也就得到 p[1] $ \sim$ p[4] 与 p[3] $ \sim$ p[6]完全相同，则 p[1] $ \sim$ p[4] 也与 s[3] $ \sim$ s[6] 完全相同（因为 p[1] $ \sim$ p[6] 和 s[1] $\sim$ s[6] 完全匹配），所以我们就没必要再从 p[1] 开始枚举，直接从 p[4 + 1]开始即可。

这样操作我们就省去了主串指针的回溯所花费的不必要的时间，接下来的步骤以此类推，详见 C++ 代码部分的 kmp 匹配过程。

#### C++ 代码

```cpp
#include &lt;iostream&gt;

using namespace std;

const int N = 10010, M = 100010;

int n, m;
char p[N], s[M];
int ne[N]; //next数组

int main()
{
    cin &gt;&gt; n &gt;&gt; (p + 1) &gt;&gt; m &gt;&gt; (s + 1); //下标从1开始
    //求next数组过程
    for (int i = 2, j = 0; i &lt;= n; i++)//i从2开始即可，i=1不匹配的话即是退为0，不考虑
    {
        while(j &amp;&amp; p[i] != p[j + 1])
            j = ne[j];
        if(p[i] == p[j + 1])
            j++;
        ne[i] = j;
    }
    //kmp匹配过程
    for (int i = 1, j = 0; i &lt;= m; i++)
    {
        while (j &amp;&amp; s[i] != p[j + 1]) //j未回退到起点，且当前不匹配
            j = ne[j];                //ne[j]表示p[j+1]前端和s匹配的最小移动坐标
        if (s[i] == p[j + 1])
            j++;
        if (j == n)
        {
            printf(&quot;%d &quot;, i - n);//起始下标
            j = ne[j];
        }
    }
    return 0;
}
```

</content></entry></feed>