[{"content":"用 Python 的 Django 开发 web 项目时, 开发环境下只用修改代码, 框架就会自动重启让修改生效.\n用 Go 开发 web 项目, 因为需要编译成二进制文件, 修改代码后需要 CTRL + C 退出进程, 手动编译, 手动执行编译后生成的可执行文件, 来让修改生效.\nAir 是为 Go 应用开发设计的热重载的命令行工具.\n安装 air go install github.com/cosmtrek/air@latest 在 $GOPATH/bin 目录下即可看到可执行文件 air\n使用 进入项目目录 (~/Documents/dpat)\n在没使用 air 前的编译步骤\n# 在当前目录下生成名为 dpat 的二进制文件 # main 函数位于 ./cmd/dpat 目录中 go build -o dpat ./cmd/dpat 运行\n# 传入 -c \u0026#34;./config.json\u0026#34; 参数 # 执行 runserver 子命令 ./dpat -c \u0026#34;./config.json\u0026#34; runserver 现在切换为使用 air 启动项目.\n生成 .air.toml 配置文件\nair init 修改配置文件\n[build] args_bin = [\u0026#34;-c\u0026#34;, \u0026#34;./config.json\u0026#34;, \u0026#34;runserver\u0026#34;] bin = \u0026#34;./dpat\u0026#34; cmd = \u0026#34;go build -o dpat ./cmd/dpat\u0026#34; 执行 air 即可\nair 参考 https://github.com/cosmtrek/air/blob/master/README-zh_cn.md https://www.cnblogs.com/loveshes/p/12890115.html ","permalink":"/post/2023/10/06/air-hot-reload/","summary":"用 Python 的 Django 开发 web 项目时, 开发环境下只用修改代码, 框架就会自动重启让修改生效. 用 Go 开发 web 项目, 因为需要编译成二进制文件, 修改代码后需要 CTRL + C 退出","title":"使用 Air 热加载 Gin 项目"},{"content":"RBAC (Role Based Access Control, 基于角色的访问控制)\n准备基础的 Gin 项目 新建 gin-rbac 文件夹\nmkdir gin-rbac 进入该目录, 初始化项目, 获取依赖\ngo mod init gin-rbac go get -u github.com/gin-gonic/gin go get -u github.com/casbin/casbin/v2 go get -u github.com/gin-contrib/authz 准备 Casbin 在 gin-rbac 目录下新建 auth 目录, 在该目录下\n新建 model.conf:\n[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) \u0026amp;\u0026amp; r.obj == p.obj \u0026amp;\u0026amp; r.act == p.act 新建 policy.csv:\np, admin, data1, read p, admin, data1, write p, data_entry, data1, write p, guest, data1, read g, alice, admin g, bob, data_entry g, charlie, guest 在 gin-rbac 目录下新建 casbin_test.go 并测试:\npackage main import ( \u0026#34;testing\u0026#34; \u0026#34;github.com/casbin/casbin/v2\u0026#34; ) func TestEnforce(t *testing.T) { e, _ := casbin.NewEnforcer(\u0026#34;auth/model.conf\u0026#34;, \u0026#34;auth/policy.csv\u0026#34;) sub := \u0026#34;alice\u0026#34; obj := \u0026#34;data1\u0026#34; act := \u0026#34;read\u0026#34; if res, _ := e.Enforce(sub, obj, act); res { // 允许 alice read data1 t.Log(\u0026#34;alice can read data1\u0026#34;) } else { // 拒绝请求 t.Log(\u0026#34;alice can NOT read data1\u0026#34;) } // alice can read data1 sub = \u0026#34;bob\u0026#34; obj = \u0026#34;data1\u0026#34; act = \u0026#34;read\u0026#34; if res, _ := e.Enforce(sub, obj, act); res { t.Log(\u0026#34;bob can read data1\u0026#34;) } else { t.Log(\u0026#34;bob can NOT read data1\u0026#34;) } // bob can NOT read data1 sub = \u0026#34;bob\u0026#34; obj = \u0026#34;data1\u0026#34; act = \u0026#34;write\u0026#34; if res, _ := e.Enforce(sub, obj, act); res { t.Log(\u0026#34;bob can write data1\u0026#34;) } else { t.Log(\u0026#34;bob can NOT write data1\u0026#34;) } // bob can write data1 } 中间件 gin-contrib/authz 其 github 主页 给出的例子\npackage main import ( \u0026#34;net/http\u0026#34; \u0026#34;github.com/casbin/casbin/v2\u0026#34; \u0026#34;github.com/gin-contrib/authz\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { e := casbin.NewEnforcer(\u0026#34;authz_model.conf\u0026#34;, \u0026#34;authz_policy.csv\u0026#34;) router := gin.New() router.Use(authz.NewAuthorizer(e)) } 一旦认证失败就返回 HTTP 403.\n查看源码 https://github.com/gin-contrib/authz/blob/master/authz.go\n得知其用到了 Basic 认证, 其中关键代码:\nfunc (a *BasicAuthorizer) CheckPermission(r *http.Request) bool { user := a.GetUserName(r) // 从 Basic 认证中拿到用户名 method := r.Method // http 请求方法, 例如 GET / POST path := r.URL.Path\t// 请求路径, 例如 /data1/read /data1/write allowed, err := a.enforcer.Enforce(user, path, method) if err != nil { panic(err) } return allowed } 整合 Casbin 和 Gin 新建 auth/authz_model.conf:\n[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) \u0026amp;\u0026amp; r.obj == p.obj \u0026amp;\u0026amp; r.act == p.act 新建 auth/authz_policy.csv:\np, admin, /data1/read, GET p, admin, /data1/write, POST p, data_entry, /data1/write, POST p, guest, /data1/read, GET g, alice, admin g, bob, data_entry g, charlie, guest 新建 gin-rbac/main.go:\npackage main import ( \u0026#34;github.com/casbin/casbin/v2\u0026#34; \u0026#34;github.com/gin-contrib/authz\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { r := gin.Default() enforcer, err := casbin.NewEnforcer(\u0026#34;auth/authz_model.conf\u0026#34;, \u0026#34;auth/authz_policy.csv\u0026#34;) if err != nil { panic(err) } r.Use(authz.NewAuthorizer(enforcer)) r.GET(\u0026#34;/data1/read\u0026#34;, func(c *gin.Context) { c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;You can read data1\u0026#34;}) }) r.POST(\u0026#34;/data1/write\u0026#34;, func(c *gin.Context) { c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;You can write data1\u0026#34;}) }) r.Run() } 使用 httpie 发送请求:\nalice 有读和写权限, 因为她的角色是 admin:\nhttp -v -a alice:alice GET \u0026#39;localhost:8080/data1/read\u0026#39; GET /data1/read HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic YWxpY2U6YWxpY2U= Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 200 OK Content-Length: 32 Content-Type: application/json; charset=utf-8 Date: Mon, 09 Oct 2023 09:04:41 GMT { \u0026#34;message\u0026#34;: \u0026#34;You can read data1\u0026#34; } someone 是匿名用户, 他什么权限都没有\nhttp -v -a someone:xx GET \u0026#39;localhost:8080/data1/read\u0026#39; GET /data1/read HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic c29tZW9uZTp4eA== Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 403 Forbidden Content-Length: 0 Date: Mon, 09 Oct 2023 09:05:52 GMT bob 只有写权限, 他的角色是 data_entry\nhttp -v -a bob:bob GET \u0026#39;localhost:8080/data1/read\u0026#39; GET /data1/read HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic Ym9iOmJvYg== Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 403 Forbidden Content-Length: 0 Date: Mon, 09 Oct 2023 09:06:32 GMT http -v -a bob:bob POST \u0026#39;localhost:8080/data1/write\u0026#39; Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic Ym9iOmJvYg== Connection: keep-alive Content-Length: 0 Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 200 OK Content-Length: 33 Content-Type: application/json; charset=utf-8 Date: Mon, 09 Oct 2023 09:07:21 GMT { \u0026#34;message\u0026#34;: \u0026#34;You can write data1\u0026#34; } 参考 https://gitee.com/pyq19/gin-rbac https://medium.com/@abhishekranjandev/creating-a-spring-security-like-rbac-system-for-your-gin-project-using-casbin-41364211ae67 https://dev.to/maxwellhertz/tutorial-integrate-gin-with-cabsin-56m0 https://github.com/casbin/casbin https://casbin.org/docs/tutorials/ https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication https://github.com/gin-contrib/authz ","permalink":"/post/2023/10/05/gin-rbac/","summary":"RBAC (Role Based Access Control, 基于角色的访问控制) 准备基础的 Gin 项目 新建 gin-rbac 文件夹 mkdir gin-rbac 进入该目录, 初始化项目, 获取依赖 go mod init gin-rbac go get -u github.com/gin-gonic/gin go get -u github.com/casbin/casbin/v2 go get -u github.com/gin-contrib/authz 准备 Casbin 在 gin-rbac 目录","title":"使用 Gin 实现 RBAC 访问控制"},{"content":"flex 布局练习 demo\n01. 垂直居中 \u0026lt;div id=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;div id=\u0026#34;item\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; #container { display: flex; width: 300px; height: 300px; outline: solid 1px; justify-content: center; align-content: center; align-items: center; background-color: aqua; } #item { width: 100px; height: 100px; outline: solid 1px; background-color: red; } 效果:\ndemo\n02. 两列等高 \u0026lt;div class=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;item\u0026#34; style=\u0026#34;height: 300px; background-color: red\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;item\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;br /\u0026gt; \u0026lt;div class=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;item\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;item\u0026#34; style=\u0026#34;height: 300px; background-color: yellow\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; .container { display: flex; width: 300px; justify-content: center; align-content: center; align-items: stretch; background-color: blue; } .item { width: 100px; outline: solid 1px; background-color: green; } 效果:\ndemo\n03. 自适应宽度 .parent { display: flex; width: 500px; height: 200px; } .child1 { width: 100px; flex: 2; background-color: lightblue; } .child2 { width: 100px; flex: 1; background-color: yellow; } \u0026lt;body\u0026gt; \u0026lt;div class=\u0026#34;parent\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;child1\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;child2\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; 效果:\ndemo\n04. 骰子布局 face 代表表面, pip 代表点数\n效果:\ndemo\n一点\n\u0026lt;div class=\u0026#34;first-face\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; .first-face { display: flex; justify-content: center; align-items: center; } justify-content: center 容器内项目横向居中\nalign-items: center 容器内项目纵向居中\n两点\n\u0026lt;div class=\u0026#34;second-face\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; .second-face { display: flex; justify-content: space-between; } .second-face .pip:nth-of-type(2) { align-self: flex-end; } justify-content: space-between 横向主轴上的两个元素, 首尾贴合容器边缘\n.pip:nth-of-type(2) 第二个 pip 元素\nalign-self: flex-end 与交叉轴的终点对齐\n三点\n\u0026lt;div class=\u0026#34;third-face\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; .third-face { display: flex; justify-content: space-between; } .third-face .pip:nth-of-type(2) { align-self: center; } .third-face .pip:nth-of-type(3) { align-self: flex-end; } 四点\n\u0026lt;div class=\u0026#34;fourth-face\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;column\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;column\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; .fourth-face { display: flex; justify-content: space-between; } .fourth-face .column { display: flex; flex-direction: column; justify-content: space-between; } 五点\n\u0026lt;div class=\u0026#34;fifth-face\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;column\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;column\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;column\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; .fifth-face { display: flex; justify-content: space-between; } .fifth-face .column { display: flex; flex-direction: column; justify-content: space-between; } .fifth-face .column:nth-of-type(2) { justify-content: center; } 六点\n\u0026lt;div class=\u0026#34;sixth-face\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;column\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;column\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;pip\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; .sixth-face { display: flex; justify-content: space-between; } .sixth-face .column { display: flex; flex-direction: column; justify-content: space-between; } 参考 极客时间 - 重学前端 - CSS Flex 排版: 为什么垂直居中这么难? 阮一峰的网络日志 - Flex 布局教程: 实例篇 ","permalink":"/post/2023/09/26/flex-demo/","summary":"flex 布局练习 demo 01. 垂直居中 \u0026lt;div id=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;div id=\u0026#34;item\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; #container { display: flex; width: 300px; height: 300px; outline: solid 1px; justify-content: center; align-content: center; align-items: center; background-color: aqua; } #item { width: 100px; height: 100px; outline: solid 1px; background-color: red; } 效果: demo 02. 两列等高 \u0026lt;div class=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;item\u0026#34; style=\u0026#34;height: 300px; background-color: red\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;item\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;br /\u0026gt;","title":"flex 布局 - 练习 demo"},{"content":"记录学习 flex 布局\n基本概念 flex 布局也被称为弹性布局, 定义容器的规则而尽可能不操作子元素, 通过父容器设置 flex 属性来控制子元素的排布方式.\nflex 布局的核心是父容器的 display: flex 属性和子元素的 flex 属性, 具有 display: flex 的元素称为 flex 容器, 子元素称为 flex 项.\n采用 flex 布局的元素, 称为 flex 容器 (flex container) 简称 \u0026ldquo;容器\u0026rdquo;. 它的所有子元素自动成为容器成员, 称为 flex 项目 (flex item) 简称 \u0026ldquo;项目\u0026rdquo;.\n主轴 交叉轴 flex 布局支持横向和纵向, 把 flex 延伸的方向称为 \u0026ldquo;主轴\u0026rdquo;, 把跟它垂直的方向称为 \u0026ldquo;交叉轴\u0026rdquo;, flex 项中的 width 和 height 就会称为交叉轴尺寸或者主轴尺寸\n默认主轴是水平方向\n左上角是起点\n容器 (父元素) 属性 flex-direction 定义主轴方向\n.container { display: flex; flex-direction: row / column; } justify-content 定义了子元素在主轴方向的 前, 后, 和 居中 的对齐方式\nalign-items 定义子元素在交叉轴 前, 后, 和 居中 的对齐方式\nflex-wrap 超出容器轴线长度后, 是否换行\nalign-content 定义了 多行项目 (多行子元素) 在交叉轴的对齐方式\n项目 (子元素) 属性 order 根据其数值定义元素的排序\nalign-self 允许项目自身有单独的交叉轴对齐方式, 让项目覆盖 (override) 容器的 align-items 值\nflex 是 flex-grow, flex-shrink, flex-basis 三个子元素属性的简写.\n默认值 0, 1, auto\nflex-grow 按照容器剩余空间的比例, 放大元素, 填充容器 (放大子元素, 填充父元素)\nflex-shrink 按被挤压空间的比例缩小元素\nflex-basis 元素初始大小, 默认 auto, 根据主轴长度分配, 简写和分开设置可能效果不一致, 建议使用简写\n参考 稀土掘金 - CSS 技术揭秘与实战通关 - CSS 布局的发展及演变 bilibili 视频 - flex 弹性布局 动画详解系列 css 科普教程 阮一峰的网络日志 - Flex 布局教程: 语法篇 ","permalink":"/post/2023/09/24/flex-note/","summary":"记录学习 flex 布局 基本概念 flex 布局也被称为弹性布局, 定义容器的规则而尽可能不操作子元素, 通过父容器设置 flex 属性来控制子元素的排布方式. flex 布局的核心是","title":"flex 布局 - 基础概念"},{"content":" linux 安装\nsudo apt install flameshot 使用\n截全屏\nflameshot full 选择区域截图\nflameshot gui 保存到某个目录下\nflameshot gui -p ~/Pictures 看到提示\nflameshot: info: Capture saved as /home/pyq/Pictures/2023-05-13_14-24.png\n延迟 n 秒进入截图模式\n延迟 5 秒\nflameshot gui -d 5000 添加到系统快捷键\n打开 Keyboard - Shotcuts, 选中 Custom Shotcuts\n添加快捷键 flameshot gui\n绑定到系统按键 Ctrl + Print\nwindows TODO\n参考 snipaste 替代品 \u0026amp; linux 截图解决方案-截图、贴图工具 Flameshot 视频: Flameshot 火焰截图安装与配置简明指南 ","permalink":"/post/2023/09/24/flameshot/","summary":"linux 安装 sudo apt install flameshot 使用 截全屏 flameshot full 选择区域截图 flameshot gui 保存到某个目录下 flameshot gui -p ~/Pictures 看到提示 flameshot: info: Capture saved as /home/pyq/Pictures/2023-05-13_14-24.png 延迟 n 秒进入截图模式 延迟 5 秒 flameshot gui -d 5000 添加到系统快捷键","title":"flameshot 截图软件"},{"content":"红米 AX6000 路由器开启 SSH 并安装 ShellClash 记录\n开启 SSH 准备文件\n下载好文件 Redmi-AX6000.zip 并解压缩\n降级固件到 1.0.60\n提示 出于安全考虑，不允许选择低于当前版本号的固件进行升级 时,\n将地址栏的 http://192.168.31.1/cgi-bin/luci/;stok=\u0026lt;stok\u0026gt;/web/syslock?flashtype=upload\u0026amp;downgrade=1 中的\ndowngrade=1 改为 downgrade=0\n等待 3 分钟后重新连接 wifi\n开启开发调试模式\n记住地址栏的 stok\nhttp://192.168.31.1/cgi-bin/luci/;stok=\u0026lt;stok\u0026gt;/api/misystem/set_sys_time?timezone=%20%27%20%3B%20zz%3D%24%28dd%20if%3D%2Fdev%2Fzero%20bs%3D1%20count%3D2%202%3E%2Fdev%2Fnull%29%20%3B%20printf%20%27%A5%5A%25c%25c%27%20%24zz%20%24zz%20%7C%20mtd%20write%20-%20crash%20%3B%20\n将这串 url 中的 \u0026lt;stok\u0026gt; 替换为生成的 stok (后面的步骤都要替换 stok)\n得到 {\u0026quot;code\u0026quot;: 0}\n重启路由器\nhttp://192.168.31.1/cgi-bin/luci/;stok=\u0026lt;stok\u0026gt;/api/misystem/set_sys_time?timezone=%20%27%20%3b%20reboot%20%3b%20\n开启 telnet\nhttp://192.168.31.1/cgi-bin/luci/;stok=\u0026lt;stok\u0026gt;/api/misystem/set_sys_time?timezone=%20%27%20%3B%20bdata%20set%20telnet_en%3D1%20%3B%20bdata%20set%20ssh_en%3D1%20%3B%20bdata%20set%20uart_en%3D1%20%3B%20bdata%20commit%20%3B%20\n得到 {\u0026quot;code\u0026quot;: 0}\n重启\nhttp://192.168.31.1/cgi-bin/luci/;stok=\u0026lt;stok\u0026gt;/api/misystem/set_sys_time?timezone=%20%27%20%3b%20reboot%20%3b%20\ntelnet 192.168.31.1 连接路由器\n修改 root 密码为 admin\necho -e \u0026#39;admin\\nadmin\u0026#39; | passwd root 固化 SSH\nnvram set ssh_en=1 nvram set telnet_en=1 nvram set uart_en=1 nvram set boot_wait=on nvram commit 永久开启 SSH (重启不会关闭)\nmkdir /data/auto_ssh \u0026amp;\u0026amp; cd /data/auto_ssh curl -O https://blog.impyq.com/post/2023/07/17/redmi-ax6000/auto_ssh.sh chmod +x auto_ssh.sh uci set firewall.auto_ssh=include uci set firewall.auto_ssh.type=\u0026#39;script\u0026#39; uci set firewall.auto_ssh.path=\u0026#39;/data/auto_ssh/auto_ssh.sh\u0026#39; uci set firewall.auto_ssh.enabled=\u0026#39;1\u0026#39; uci commit firewall 修改时区设置\ncd ~ uci set system.@system[0].timezone=\u0026#39;CST-8\u0026#39; uci set system.@system[0].webtimezone=\u0026#39;CST-8\u0026#39; uci set system.@system[0].timezoneindex=\u0026#39;2.84\u0026#39; uci commit 关闭开发调试模式并重启\nmtd erase crash reboot SSH 连接\nssh -oHostKeyAlgorithms=+ssh-rsa root@192.168.31.1 安装 ShellClash SSH 登录路由器并执行\nexport url=\u0026#39;https://fastly.jsdelivr.net/gh/juewuy/ShellClash@master\u0026#39; \u0026amp;\u0026amp; sh -c \u0026#34;$(curl -kfsSl $url/install.sh)\u0026#34; \u0026amp;\u0026amp; source /etc/profile \u0026amp;\u0026gt; /dev/null 1 Shellclash公测版 最新版本：1.7.9f 1 安装到 /data 目录(推荐，支持软固化功能) 1 在线生成 Clash 配置文件 请直接输入第1个链接或对应数字选项 \u0026gt; (这里直接复制粘贴订阅链接) 1 开始生成配置文件（原文件将被备份） 9 更新/卸载 4 安装本地Dashboard面板 3 安装Yacd面板(约1.1mb) 是否现在重启clash服务？(1/0) \u0026gt; 1 参考 红米路由器 AX6000 解锁 SSH github.com/juewuy/ShellClash ","permalink":"/post/2023/07/17/redmi-ax6000/","summary":"红米 AX6000 路由器开启 SSH 并安装 ShellClash 记录 开启 SSH 准备文件 下载好文件 Redmi-AX6000.zip 并解压缩 降级固件到 1.0.60 提示 出于安全考虑，不允许选择低于当前版本号的固件进行升级 时, 将地","title":"红米 AX6000 设置"},{"content":"参考:\n高薪之路 - 前端面试精选集 - 15 跨域与 Nginx Nginx 完全解读指南 - 17 什么是正向代理? 什么又是反向代理? 同源策略 协议 + 域名 + 端口三者皆相同可以说是同源\n同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互, 是用于隔离潜在恶意文件的重要安全机制\n浏览器同源策略是为了在用户打开网站时保护网站自身的 Cookie, Storage, 和服务器等隐私数据\nCORS CORS 是目前主流的跨域解决机制, 其是一个 W3C 标准, 全称是 \u0026ldquo;跨域资源共享\u0026rdquo; (Cross-origin resource sharing). 它可以使得浏览器向跨域服务器发出 XMLHttpRequest 请求并成功接收返回数据\n步骤:\n浏览器发送 OPTIONS 请求\n浏览器会先使用 OPTIONS 方法发送一个预检请求 (preflight request), 用以从服务器获取更多信息\n携带这些字段:\nAccess-Control-Request-Method: 表明请求的方法 Access-Control-Request-Headers: 表明请求的 Headers Origin: 表明请求发出的域 服务端收到请求后会以 Access-Control-* response headers 的形式对客户端进行回复\nAccess-Control-Allow-Origin: 能够被允许发出这个请求的域名, 也可以使用 * 来表明允许所有域名 Access-Control-Allow-Methods: 用逗号分隔的被允许的请求方法的列表 Access-Control-Allow-Headers: 用逗号分隔的被允许的请求头部字段的列表 Access-Control-Max-Age: 这个 preflight 能被缓存的最长时间, 在缓存时间内, 同一个请求不会再次发出 preflight 请求 从实践的角度来讲添加 CORS 支持基本上是服务端的工作\n正向代理, 反向代理 ","permalink":"/post/2023/05/31/%E8%B7%A8%E5%9F%9F/","summary":"参考: 高薪之路 - 前端面试精选集 - 15 跨域与 Nginx Nginx 完全解读指南 - 17 什么是正向代理? 什么又是反向代理? 同源策略 协议 + 域名 + 端口三者皆相同可以说是同源","title":"跨域"},{"content":"React Hooks 核心原理与实战笔记\nHooks 的出现 在 React 中, Hooks 就是把某个目标结果钩到某个可能会变化的数据源或者事件源上, 那么当被钩到的数据或事件发生变化时, 产生这个目标结果的代码会重新执行, 产生更新后的结果.\n所有的 Hooks 的最终结果都是导致 UI 的变化.\nHooks 的最大好处: 逻辑复用\nHooks 的另一大好处: 有助于关注分离\n参考:\nhttps://time.geekbang.org/column/article/378311 useState 保存组件状态和使用生命周期 useState 是用来管理 state, 让函数组件具有维持状态的能力\n在一个函数组件的多次渲染之间, 这个 state 是共享的\n// 定义一个年龄的 state, 初始值是 42 const [age, setAge] = useState(42); // 定义一个水果的 state, 初始值是 banana const [fruit, setFruit] = useState(\u0026#34;banana\u0026#34;); // 定一个一个数组 state, 初始值是包含一个 todo 的数组 const [todos, setTodos] = useState([{ text: \u0026#34;Learn Hooks\u0026#34; }]); useState 是一个非常简单的 Hook, 它让你很方便地去创建一个状态, 并提供一个特定的方法 (比如 setAge) 来设置这个状态\nstate 中永远不要保存可以通过计算得到的值\nuseEffect useEffect 用于执行一段副作用\n副作用是指一段和当前执行结果无关的代码, 比如说要修改函数外部的某个变量, 要发起一个请求, 等等\n在函数组件的当次执行过程中, useEffect 中代码的执行不影响渲染出来的 UI\nuseEffect 是每次组件 render 完后判断依赖并执行\nuseEffect(callback, dependencies); callback: 要执行的函数\ndependencies: 可选的依赖项数组\n依赖项是可选的\n没有依赖项, 则每次 render 后都会重新执行\nuseEffect(() =\u0026gt; { // 每次 render 完一定执行 console.log(\u0026#34;re-rendered\u0026#34;); }); 有依赖项, 且依赖项不为空数组\nimport React, { useState, useEffect } from \u0026#34;react\u0026#34;; function BlogView({ id }) { // 设置一个本地 state 用于保存 blog 内容 const [blogContent, setBlogContent] = useState(null); useEffect(() =\u0026gt; { // useEffect 的 callback 要避免直接的 async 函数, 需要封装一下 const doAsync = async () =\u0026gt; { // 当 id 发生变化时, 将当前内容清楚以保持一致性 setBlogContent(null); // 发起请求获取数据 const res = await fetch(`/blog-content/${id}`); // 将获取的数据放入 state setBlogContent(await res.text()); }; doAsync(); }, [id]); // 使用 id 作为依赖项, 变化时则执行副作用 // 如果没有 blogContent 则认为是在 loading 状态 const isLoading = !blogContent; return \u0026lt;div\u0026gt;{isLoading ? \u0026#34;Loading...\u0026#34; : blogContent}\u0026lt;/div\u0026gt;; } 空数组作为依赖项, 则只在首次执行时触发\nuseEffect(() =\u0026gt; { // 组件首次渲染时执行，等价于 class 组件中的 componentDidMount console.log(\u0026#34;did mount\u0026#34;); }, []); useEffect 还允许返回一个函数, 用于在组件销毁的时候做一些清理的操作, 机制就几乎等价于类组件中的 componentWillUnmount\n// 设置一个 size 的 state 用于保存当前窗口尺寸 const [size, setSize] = useState({}); useEffect(() =\u0026gt; { // 窗口大小变化事件处理函数 const handler = () =\u0026gt; { setSize(getSize()); }; // 监听 resize 事件 window.addEventListener(\u0026#34;resize\u0026#34;, handler); // 返回一个 callback 在组件销毁时调用 return () =\u0026gt; { // 移除 resize 事件 window.removeEventListener(\u0026#34;resize\u0026#34;, handler); }; }, []); 总结:\nuseEffect(() =\u0026gt; {}) 每次 render 后执行 useEffect(() =\u0026gt; {}, []) 仅第一次 render 后执行 useEffect(() =\u0026gt; {}, [deps]) 第一次 render 后及依赖项发生变化后执行 useEffect(() =\u0026gt; { return () =\u0026gt; {} }, []) 组件 unmount 后执行 参考:\nhttps://time.geekbang.org/column/article/379299 useCallback 缓存回调函数 useCallback(fn, deps); fn: 定义的回调函数 deps: 依赖的变量数组\n只有当某个依赖变量发生变化时, 才会重新声明 fn 这个回调函数\n// 修改前 // 每次组件状态发生变化的时候, 函数组件都会重新执行一遍 // 在每次执行的时候, 都会创建一个新的事件处理函数 handleIncrement function Counter() { const [count, setCount] = useState(0); const handleIncrement = () =\u0026gt; setCount(count + 1); // ... return \u0026lt;button onClick={handleIncrement}\u0026gt;+\u0026lt;/button\u0026gt;; } // 修改后 // 只有 count 发生变化的时候, 才需要重新创建一个回调函数, // 这样就保证了组件不会创建重复的回调函数. // 而接收这个回调函数作为属性的组件, 也不会频繁地需要重新渲染 function Counter() { const [count, setCount] = useState(0); const handleIncrement = useCallback( () =\u0026gt; setCount(count + 1), [count] // 只有当 count 发生变化时才会重新创建回调函数 ); // ... return \u0026lt;button onClick={handleIncrement}\u0026gt;+\u0026lt;/button\u0026gt;; } useMemo 缓存计算的结果 useCallback 缓存的是一个函数, 而 useMemo 缓存的是计算的结果\nuseMemo 两个好处:\n避免重复计算 避免子组件的重复渲染 useMemo(fn, deps); fn: 产生所需数据的计算函数, deps 依赖项\n如果某个数据是通过其它数据计算得到的, 那么只有当用到的数据, 也就是依赖的数据发生变化的时候, 才应该需要重新计算\nimport React, { useState, useEffect } from \u0026#34;react\u0026#34;; export default function SearchUserList() { const [users, setUsers] = useState(null); const [searchKey, setSearchKey] = useState(\u0026#34;\u0026#34;); useEffect(() =\u0026gt; { const doFetch = async () =\u0026gt; { // 组件首次加载时发请求获取用户数据 const res = await fetch(\u0026#34;https://reqres.in/api/users/\u0026#34;); setUsers(await res.json()); }; doFetch(); }, []); let usersToShow = null; /* 不使用 useMemo 无论组件为何刷新, 这里一定会对数组做一次过滤的操作 重新计算 usersToShow 的值 if (users) { usersToShow = users.data.filter((user) =\u0026gt; user.first_name.includes(searchKey) ); } */ // 使用 userMemo 缓存计算的结果 usersToShow // 只有在依赖项 users, searchKey 发生变化时 // 才会重新计算 usersToShow const usersToShow = useMemo(() =\u0026gt; { if (!users) return null; return users.data.filter((user) =\u0026gt; { return user.first_name.includes(searchKey); }); }, [users, searchKey]); return ( \u0026lt;div\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; value={searchKey} onChange={(evt) =\u0026gt; setSearchKey(evt.target.value)} /\u0026gt; \u0026lt;ul\u0026gt; {usersToShow \u0026amp;\u0026amp; usersToShow.length \u0026gt; 0 \u0026amp;\u0026amp; usersToShow.map((user) =\u0026gt; { return \u0026lt;li key={user.id}\u0026gt;{user.first_name}\u0026lt;/li\u0026gt;; })} \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; ); } useCallback 可以用 useMemo 实现, 它们都做了同一件事情: 建立了一个绑定某个结果到依赖数据的关系. 只有当依赖变了, 这个结果才需要被重新得到.\nconst myEventHandler = useMemo(() =\u0026gt; { // 返回一个函数作为缓存结果 return () =\u0026gt; { // 在这里进行事件处理 }; }, [dep1, dep2]); useRef 在多次渲染之间共享数据 const myRefContainer = useRef(initialValue); 可以把 useRef 看作是在函数组件之外创建的一个容器空间, 在这个容器上可以通过唯一的 current 属设置一个值, 从而在函数组件的多次渲染之间共享这个值\n// 使用了 useRef 来创建了一个保存 window.setInterval 返回句柄的空间 // 从而能够在用户点击暂停按钮时清除定时器 // 达到暂停计时的目的 import React, { useState, useCallback, useRef } from \u0026#34;react\u0026#34;; export default function Timer() { // 定义 time state 用于保存计时的累积时间 const [time, setTime] = useState(0); // 定义 timer 这样一个容器用于在跨组件渲染之间保存一个变量 const timer = useRef(null); // 开始计时的事件处理函数 const handleStart = useCallback(() =\u0026gt; { // 使用 current 属性设置 ref 的值 timer.current = window.setInterval(() =\u0026gt; { setTime((time) =\u0026gt; time + 1); }, 100); }, []); // 暂停计时的事件处理函数 const handlePause = useCallback(() =\u0026gt; { // 使用 clearInterval 来停止计时 window.clearInterval(timer.current); timer.current = null; }, []); return ( \u0026lt;div\u0026gt; {time / 10} seconds. \u0026lt;br /\u0026gt; \u0026lt;button onClick={handleStart}\u0026gt;Start\u0026lt;/button\u0026gt; \u0026lt;button onClick={handlePause}\u0026gt;Pause\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; ); } useRef 保存的数据一般是和 UI 的渲染无关的, 因此当 ref 的值发生变化时, 是不会触发组件的重新渲染的, 这也是 useRef 区别于 useState 的地方\n除了存储跨渲染的数据之外, useRef 还有一个重要的功能: 保存某个 DOM 节点的引用\nfunction TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () =\u0026gt; { // current 属性指向了真实的 input 这个 DOM 节点 // 从而可以调用 focus 方法 inputEl.current.focus(); }; return ( \u0026lt;\u0026gt; \u0026lt;input ref={inputEl} type=\u0026#34;text\u0026#34; /\u0026gt; \u0026lt;button onClick={onButtonClick}\u0026gt;Focus the input\u0026lt;/button\u0026gt; \u0026lt;/\u0026gt; ); } useContext 定义全局状态 React 组件之间通过 props 传值, 且只能在父子组件之间传值\nReact 提供了 Context 这样一个机制, 能够让所有在某个组件开始的组件树上创建一个 Context. 这样这个组件树上的所有组件就都能访问和修改这个 Context\n当这个 Context 的数据发生变化时, 使用这个数据的组件就能够自动刷新, 达到数据的绑定的目的\nuseContext:\nconst value = useContext(MyContext); 创建 Context\nconst MyContext = React.createContext(initialValue); MyContext 具有一个 Provider 的属性, 一般是作为组件树的根组件\nconst themes = { light: { foreground: \u0026#34;#000000\u0026#34;, background: \u0026#34;#eeeeee\u0026#34;, }, dark: { foreground: \u0026#34;#ffffff\u0026#34;, background: \u0026#34;#222222\u0026#34;, }, }; // 创建一个 Theme 的 Context const ThemeContext = React.createContext(themes.light); function App() { // 整个应用使用 ThemeContext.Provider 作为根组件 return ( // 使用 themes.dark 作为当前 Context \u0026lt;ThemeContext.Provider value={themes.dark}\u0026gt; \u0026lt;Toolbar /\u0026gt; \u0026lt;/ThemeContext.Provider\u0026gt; ); } // 在 Toolbar 组件中使用一个会使用 Theme 的 Button function Toolbar(props) { return ( \u0026lt;div\u0026gt; \u0026lt;ThemedButton /\u0026gt; \u0026lt;/div\u0026gt; ); } // 在 Theme Button 中使用 useContext 来获取当前的主题 function ThemedButton() { const theme = useContext(ThemeContext); return ( \u0026lt;button style={{ background: theme.background, color: theme.foreground, }} \u0026gt; I am styled by theme context! \u0026lt;/button\u0026gt; ); } themes.dark 是作为一个属性值传给 Provider 这个组件, 如果要让它变得动态, 只要用一个 state 来保存, 通过修改 state 就能实现动态的切换 Context 的值. 而且这么做所有用到这个 Context 的地方都会自动刷新\nfunction App() { // 使用 state 来保存 theme 从而可以动态修改 const [theme, setTheme] = useState(\u0026#34;light\u0026#34;); // 切换 theme 的回调函数 const toggleTheme = useCallback(() =\u0026gt; { setTheme((theme) =\u0026gt; (theme === \u0026#34;light\u0026#34; ? \u0026#34;dark\u0026#34; : \u0026#34;light\u0026#34;)); }, []); return ( // 使用 theme state 作为当前 Context \u0026lt;ThemeContext.Provider value={themes[theme]}\u0026gt; \u0026lt;button onClick={toggleTheme}\u0026gt;Toggle Theme\u0026lt;/button\u0026gt; \u0026lt;Toolbar /\u0026gt; \u0026lt;/ThemeContext.Provider\u0026gt; ); } 自定义 Hook 典型的四个使用场景\n抽取业务逻辑\n计数器的例子可以改成\nimport { useState, useCallback } from \u0026#34;react\u0026#34;; function useCounter() { // 定义 count 这个 state 用于保存当前数值 const [count, setCount] = useState(0); // 实现加 1 的操作 const increment = useCallback(() =\u0026gt; setCount(count + 1), [count]); // 实现减 1 的操作 const decrement = useCallback(() =\u0026gt; setCount(count - 1), [count]); // 重置计数器 const reset = useCallback(() =\u0026gt; setCount(0), []); // 将业务逻辑的操作 export 出去供调用者使用 return { count, increment, decrement, reset }; } import React from \u0026#34;react\u0026#34;; function Counter() { // 调用自定义 Hook const { count, increment, decrement, reset } = useCounter(); // 渲染 UI return ( \u0026lt;div\u0026gt; \u0026lt;button onClick={decrement}\u0026gt; - \u0026lt;/button\u0026gt; \u0026lt;p\u0026gt;{count}\u0026lt;/p\u0026gt; \u0026lt;button onClick={increment}\u0026gt; + \u0026lt;/button\u0026gt; \u0026lt;button onClick={reset}\u0026gt; reset \u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; ); } 封装通用逻辑\n场景: 从服务端获取用户列表, 并显示在界面上\n在处理这类请求的时候, 模式都是类似的, 通常都会遵循下面步骤:\n创建 data, loading, error 这 3 个 state 请求发出后, 设置 loading state 为 true 请求成功后, 将返回的数据放到某个 state 中, 并将 loading state 设为 false 请求失败后, 设置 error state 为 true, 并将 loading state 设为 false. 最后, 基于 data, loading, error 这 3 个 state 的数据, UI 就可以正确地显示数据, 或者 loading, error 这些反馈给用户 通过创建自定义 Hook useAsync 可以将这样的逻辑提取出来成为一个可重用的模块\nimport { useState } from \u0026#34;react\u0026#34;; const useAsync = (asyncFunction) =\u0026gt; { // 设置三个异步逻辑相关的 state const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // 定义一个 callback 用于执行异步逻辑 const execute = useCallback(() =\u0026gt; { // 请求开始时, 设置 loading 为 true, 清除已有数据和 error 状态 setLoading(true); setData(null); setError(null); return asyncFunction() .then((response) =\u0026gt; { // 请求成功时, 将数据写进 state, 设置 loading 为 false setData(response); setLoading(false); }) .catch((error) =\u0026gt; { // 请求失败时, 设置 loading 为 false, 并设置错误状态 setError(error); setLoading(false); }); }, [asyncFunction]); return { execute, loading, data, error }; }; import React from \u0026#34;react\u0026#34;; import useAsync from \u0026#39;./useAsync\u0026#39;; export default function UserList() { // 通过 useAsync 这个函数, 只需要提供异步逻辑的实现 const { execute: fetchUsers, data: users, loading, error, } = useAsync(async () =\u0026gt; { const res = await fetch(\u0026#34;https://reqres.in/api/users/\u0026#34;); const json = await res.json(); return json.data; }); return ( // 根据状态渲染 UI... ); } 监听浏览器状态\nconst useWindowWidth = () =\u0026gt; { const [width, setWidth] = useState(window.innerWidth); useEffect(() =\u0026gt; { const onResize = () =\u0026gt; setWidth(window.innerWidth); window.addEventListener(\u0026#34;resize\u0026#34;, onResize); return () =\u0026gt; window.removeEventListener(\u0026#34;resize\u0026#34;, onResize); }, []); return width; }; const MyComponent = () =\u0026gt; { const width = useWindowWidth(); return \u0026lt;div\u0026gt;Window width is: {width}\u0026lt;/div\u0026gt;; }; 拆分复杂组件\n参考:\n自定义 Hooks: 四个典型的使用场景 ","permalink":"/post/2023/05/30/react-note-hook-3/","summary":"React Hooks 核心原理与实战笔记 Hooks 的出现 在 React 中, Hooks 就是把某个目标结果钩到某个可能会变化的数据源或者事件源上, 那么当被钩到的数据或事件发生变化时, 产生这","title":"react 笔记 - hook (三)"},{"content":" 资料:\n慕课网 - 系统讲解 CSS, 工作应用 + 面试一步搞定 极客时间 - 重学前端 MDN - HTML 基础 World Wide Web Consortium (W3C) HTML \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; ... \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; ... \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 常见元素 例子\n出现在 head 中, 不会在页面上留下内容:\nmeta, title, style, link, script, base 出现在 body 中:\n区域: div / section / article / aside / header / footer 段落: p 行内元素: span / em / strong 表格: table / thead / tbody / tr / td / 列表: ul / ol / li / dl / dt / dd 链接: a 表单: form / input / select / textarea / button 元素分类 按默认样式分类 例子\nblock (块级) inline 内联 (行内 / 内联) inline-block (行内块级 / 内联块级) block 元素是方形的, 默认情况下占据一整行, 不会给其他元素留出空间\ninline 元素不是方形, 不一定是规则形状, 不会独占一行\ninline-block 像其他元素一样堆在一起, 对外表现像 inline 和别的元素在一行, 对内表现像 block 是方块形状, 有自己的尺寸宽高\n\u0026lt;!-- div 是块级元素, 独占一行 --\u0026gt; \u0026lt;div\u0026gt;独占一行\u0026lt;/div\u0026gt; \u0026lt;!-- p 也是独占一行的块级元素 --\u0026gt; \u0026lt;!-- span, em, strong 是内联元素 --\u0026gt; \u0026lt;!-- 当空间小后, 没有固定形状--\u0026gt; \u0026lt;!-- 不可以设置宽高 --\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span\u0026gt;行内元素\u0026lt;/span\u0026gt;\u0026lt;em\u0026gt;行内元素\u0026lt;/em\u0026gt;\u0026lt;strong\u0026gt;行内元素\u0026lt;/strong\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span style=\u0026#34;width: 100px\u0026#34;\u0026gt;行内元素 width 不生效\u0026lt;/span\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;!-- select 是 inline-block 元素 --\u0026gt; \u0026lt;!-- 它和 span 元素在同一行, 对外没有独占一行 --\u0026gt; \u0026lt;!-- 对内是个方形, 就算宽度不够也还是方块 --\u0026gt; \u0026lt;!-- 可以设置 width --\u0026gt; \u0026lt;p\u0026gt; \u0026lt;select style=\u0026#34;width: 100px\u0026#34;\u0026gt; \u0026lt;option\u0026gt;下拉框\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; \u0026lt;span\u0026gt;select 是 inline-block 元素\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; 按内容分类 元素嵌套关系 w3c 制定了严格的标准规定哪些元素可以包含那些元素, 但是浏览器对非标准行为有非常非常非常多的兼容.\n只能按照基本原则总结为:\n块级元素可以包含行内元素 块级元素不一定能包含块级元素 行内元素一般不能包含块级元素 默认样式和 reset CSS reset:\n\u0026lt;style\u0026gt; * { margin: 0; padding: 0; } \u0026lt;/style\u0026gt; 或者 normalize.css\n前者简单粗暴, 后者有认知成本, 需要花时间看它 reset 了什么东西\nCSS 基础 选择器 基本规则:\n选择器 { 属性: 值; 属性: 值; } 选择器分类:\n元素选择器 a{}\n伪元素选择器 ::before{}\n在页面中可以被显示的元素\n类选择器 .link{}\n属性选择器 [type=radio]{}\n伪类选择器 :hover{}\n伪类是一个元素的状态, 比如鼠标指向元素时, 该元素某个状态下的样式\nID 选择器 #id{}\n组合选择器 [type=checkbox] + label{}\n否定选择器 :not(.link){}\n通用选择器 *{}\n表示什么元素都匹配\n选择器权重 (从高到低排序):\n优先级高的选择器叠加在优先级低的选择器之上.\nID 选择器 #id{} 类选择器 .link{}, 属性选择器 [type=radio]{}, 伪类选择器 :hover{} 元素选择器 a{} 其它选择器 例子 例子\n!important 优先级最高 元素属性 优先级高 相同权重 后写的生效 例子\n非布局样式 字体, 字重, 颜色, 大小, 行高 背景, 边框 滚动, 换行 粗体, 斜体, 下划线 其它 非布局样式 - 字体 字体族\nserif sans-serif monospace cursive fantasy\n多字体 fallback\n网络字体, 自定义字体\niconfont\n非布局样式 - 行高 非布局样式 - 背景 非布局样式 - 边框 非布局样式 - 滚动 非布局样式 - 文本折行 非布局样式 - 装饰性属性 CSS 布局 布局是 CSS 知识体系的重中之重, 早期以 table 为主 (简单), 后来以技巧性布局为主 (难), 现在有 flexbox / grid (偏简单), 响应式布局是必备知识\n常用布局方法:\ntable 表格布局 float 浮动 + margin inline-block 布局 flexbox 布局 布局方式 - 表格 例子: 4-2 表格布局\n布局属性 盒模型\ndisplay / position\ndisplay 确定元素的显示类型\n常用的属性值为: block / inline / inline-block\n例子: 4-3 布局属性 display.html\nposition 确定元素的位置\n常用的属性值为: static / relative / absolute / fixed\n例子: 4-3 布局属性 position\nz-index\n相当于从屏幕到人眼有一个 z 轴, 值越大越能被看见\n只有 position: relative / absolute / fixed 的元素可以设置 z-index\nflexbox 布局 弹性盒子, 盒子之间并列, 只需要指定宽度\n例子: 4-4 flexbox 布局\n例子: 4-4 flexbox 布局 2\nfloat 布局 让元素 \u0026ldquo;浮动\u0026rdquo;, 会脱离文档流, 但不脱离文本流\n对自身元素的影响:\n元素形成一个 \u0026ldquo;块\u0026rdquo; (BFC) 元素位置会尽量靠上 元素位置尽量靠左 (右) 对兄弟元素的影响:\n上面贴非 float 元素 旁边贴 float 元素 不影响其它块级元素的位置 影响其它块级元素内部文本 对父级元素的影响:\n相当于从父级的空间里 \u0026ldquo;消失\u0026rdquo; 高度塌陷 例子: 4-5 float 布局\n例子: 4-5 float 布局 2\ninline-block 布局 像文本一样排列 block 元素, 没有清除浮动等问题, 但需要处理间隙\n例子: 4-6 inline-block 布局\n响应式布局 响应式设计指在不同设备上正常使用, 一般主要处理屏幕大小问题\n主要方法:\n隐藏 + 折行 + 自适应空间 rem / viewport / media query 例子: 4-7 响应式布局 1\n例子: 4-7 响应式布局 2\nrem:\n\u0026lt;style\u0026gt; /* html 元素有一个 font-size 属性, 默认情况下是 16 像素 (16px) */ /* 这里设置为 20px, 就表示这个页面 1rem = 20px */ html { font-size: 20px; } \u0026lt;/style\u0026gt; CSS 面试题 实现两栏 (三栏) 布局的方法\n表格布局 float + margin 布局 inline-block 布局 flexbox 布局 position:absolute / fixed 有什么区别?\n前者相对于最近的 absolute / relative 来进行定位\n后者相对于屏幕 (viewport) 定位\ndisplay:inline-block 的间隙\n原因是: 字符间距\n解决方法:\n消灭字符, 比如把标签写到一起不要留空白, 或者中间加上注释 消灭间距, 把字体大小设置为 0 (不要占据空间) 如何清除浮动\n需要清除浮动的原因是: 浮动的元素不会占据父元素的布局空间, 即父元素布局时不会管浮动元素, 有可能浮动元素就会超出父元素, 从而影响其它元素. 所以作为父元素必须清除浮动.\n清除方式:\n让盒子负责自己的布局\noverflow:hidden(auto)\n加一个元素放到浮动元素后面, 让父元素必须包含浮动元素\n::after{clear:both}\n如何适配移动端页面\nviewport 适配 rem / viewport / media query 设计上: 隐藏 折行 自适应 怎样在不使用 width 和 height 的情况下把 div 铺满页面\n答案有很多, 核心是考察对 CSS 属性是否熟悉\n绝对定位, 加四边都设为 0 两层 flex, 分别设置水平方向垂直方向 float + ?? 或者 grid 布局, 等等\u0026hellip; ","permalink":"/post/2023/05/23/css%E5%9F%BA%E7%A1%80/","summary":"资料: 慕课网 - 系统讲解 CSS, 工作应用 + 面试一步搞定 极客时间 - 重学前端 MDN - HTML 基础 World Wide Web Consortium (W3C) HTML \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; ... \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; ... \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 常见元素 例子 出现在 head 中, 不会在页面","title":"HTML / CSS 笔记"},{"content":" acpi 获取电池电量\nacpi Battery 0: Discharging, 43%, 03:27:41 remaining\ncd 返回之前的目录\ncd - cp 显示复制的进度 (复制大文件或者文件夹时)\ncp -v chmod 使某个文件变为可执行文件\nchmod u+x \u0026lt;文件名\u0026gt; df 文件系统磁盘空间使用情况\ndf -h Filesystem Size Used Avail Use% Mounted on tmpfs 1.5G 1.9M 1.5G 1% /run /dev/nvme0n1p4 92G 44G 43G 51% / tmpfs 7.5G 8.5M 7.5G 1% /dev/shm tmpfs 5.0M 4.0K 5.0M 1% /run/lock /dev/nvme0n1p1 256M 48M 209M 19% /boot/efi tmpfs 1.5G 92K 1.5G 1% /run/user/1000 find 找出包含某文本的文件\nfind . -name \u0026#34;*.java\u0026#34; -exec grep \u0026#34;文本\u0026#34; -Hn {} \\; git 查看当前远程仓库地址\ngit remote -v 替换新的远程仓库地址\n先删除旧的地址\ngit remote rm origin 重新添加新的\ngit remote add origin git@gitee.com:pyq19/blog.git 删除仓库内没用的大文件, 减小仓库体积\nTODO\n克隆某个仓库和他的子模块 (submodule)\n一次操作全部克隆\ngit clone --recursive git@github.com:pyq19/blog.git 这样会直接把 blog 项目和他的子模块一起克隆\n先克隆了主项目, 再克隆子模块\n假设已经克隆了主项目, 但是缺少子模块\n初始化子模块\ngit submodule init 更新子模块\ngit submodule update 添加子模块\ngit submodule add git@gitee.com:pyq19/hugo-paper themes/hugo-paper 不修改 .gitignore 文件而忽略文件\n编辑 .git/info/exclude, 添加需要忽略的本地文件\n操作系统换行符差异\nTODO\n切换到某一次 commit 时的代码仓库状态\nTODO\nhttp (httpie) 参考\nhttpie.io 接口调试神器 HTTPie HTTPie 命令介绍 指定请求的方法\n默认以 json 格式的字符串来传参\nhttp POST \u0026#39;http://localhost:3001/bar\u0026#39; aa=11 将会发送 {\u0026quot;aa\u0026quot;:11}\n指定 json 文件作为 request\nhttp POST \u0026#39;http://localhost:3001/foo\u0026#39; \u0026lt; config.json 将会发送 config.json 里面的内容\n设置请求头\n请求头部分用 : 比如 Authorization:'Bearer \u0026lt;token\u0026gt;', 参数部分用 = 比如 name=pyq\nhttp GET \u0026#39;localhost/api/xxx\u0026#39; Authorization:\u0026#39;Bearer \u0026lt;token\u0026gt;\u0026#39; name=pyq http POST \u0026#39;localhost:3001/api/xxx\u0026#39; project_name=\u0026#39;测试\u0026#39; Authorization:\u0026#39;Bearer xxxxx\u0026#39; 使用 session 保存状态\n比如保存 header 中的 token, 不必每次都指定请求头\nTODO\nls 倒序 (z 到 a 到数字) 显示目录中的内容\nls -r 根据文件的修改时间排序, 最近修改的文件考前\nls -t 递归显示子文件夹下的文件\nls -R 仅列出目录\nls -l \u0026lt;目录路径\u0026gt; | grep \u0026#39;^d\u0026#39; mycli 配置文件: https://www.mycli.net/config\nsql 语句执行结果直接输出 修改 ~/.myclirc: enable_pager = False\nsystemctl systemctl start sshd # 开启服务 systemctl stop sshd # 关闭服务 systemctl disable sshd # 禁止开机自动启动 systemctl enable sshd # 允许开机自动启动 systemctl status sshd # 查看 ssh 服务状态 tar 列出压缩文件中的内容\ntar -tvf \u0026lt;压缩文件.tar.gz\u0026gt; 解压缩 .tar.gz 文件\ntar -xvzf \u0026lt;压缩文件.tar.gz\u0026gt; 将某个目录打包成压缩文件\ntar czf \u0026lt;压缩文件.tar.gz\u0026gt; \u0026lt;想要打包的目录\u0026gt; 将 .tar.gz 中的内容解压缩到指定目录下\nTODO\ntmux 参考: http://www.ruanyifeng.com/blog/2019/10/tmux.html\n列出所有 tmux 会话 (session)\ntmux ls 进入某个 session\ntmux attach -t \u0026lt;0/1/2/...\u0026gt; 新建 session 并命名\ntmux new -s \u0026lt;session-name\u0026gt; 进入指定名称的 session\ntmux attach -t \u0026lt;session-name\u0026gt; 退出 tmux\ntmux detach ","permalink":"/post/2023/05/13/%E5%B8%B8%E7%94%A8bash%E5%91%BD%E4%BB%A4%E6%95%B4%E7%90%86/","summary":"acpi 获取电池电量 acpi Battery 0: Discharging, 43%, 03:27:41 remaining cd 返回之前的目录 cd - cp 显示复制的进度 (复制大文件或者文件夹时) cp -v chmod 使某个文件变为可执行文件 chmod u+x \u0026lt;文件名\u0026","title":"常用的 bash 命令整理"},{"content":"Hooks 是 React 实现组件逻辑的重要方式, 可以用来操作 state, 定义副作用, 更支持开发者自定义 Hooks.\nReact 对 UI 的理想模型是 UI=f(state), 其中 UI 是视图, state 是应用状态, f 则是渲染过程. 比起类组件, 函数组件更加贴近这一模型.\n参考 React Hooks (上): 为什么说在 React 中函数组件和 Hooks 是绝配? React Hooks (下): 用 Hooks 处理函数组件的副作用 代码复用: 如何设计开发自定义 Hooks 和高阶组件? 渲染阶段和提交阶段 useState import React, { useState } from \u0026#39;react\u0026#39;; // ...省略 function App() { const [showAdd, setShowAdd] = useState(false); // ------- ---------- ----- // ^ ^ ^ // | | | // state变量 state更新函数 state初始值 const [todoList, setTodoList] = useState([/* ...省略 */]); useEffect 副作用就是让一个函数不再是纯函数的各类操作,\n用法:\n只传入一个没有返回值的副作用回调函数 (Effect Callback)\nuseEffect(() =\u0026gt; {}); // -------- // ^ // | // 副作用回调函数 副作用的条件执行\n传入依赖值数组 (Dependencies) 作为第二个参数\nuseEffect(() =\u0026gt; {}, [var1, var2]); // -------- ----------- // ^ ^ // | | // 副作用回调函数 依赖值数组 同时定义副作用回调函数, 清除函数和依赖值数组\nuseEffect(() =\u0026gt; {/* 省略 */; return () =\u0026gt; {/* 省略 */};}, [status]); // ------------------------------------------ ------- // ^ ----------------- ^ // | ^ | // 副作用回调函数 清除函数 依赖值数组 useRef const Component = () =\u0026gt; { const myRef = useRef(null); // ----- ---- // ^ ^ // | | // 可变ref对象 可变ref对象current属性初始值 // 读取可变值 const value = myRef.current; // 更新可变值 myRef.current = newValue; return \u0026lt;div\u0026gt;\u0026lt;/div\u0026gt;; }; useMemo 和 useCallback useMemo 和 useCallback 都用于组件性能优化.\nuseEffect 在提交阶段执行, useMemo 和 useCallback 在渲染阶段执行, 它们的第二个参数都是依赖值数组.\nuseMemo:\nconst memoized = useMemo(() =\u0026gt; createByHeavyComputing(a, b), [a, b]); // -------- ---------------------------------- ------ // ^ ^ ^ // | | | // 工厂函数返回值 工厂函数 依赖值数组 useCallback:\nconst memoizedFunc = useCallback(() =\u0026gt; {}, [a, b]); // ------------ -------- ----- // ^ ^ ^ // | | | // 记忆化的回调函数 回调函数 依赖值数组 useCallback 相当于 useMemo 的另一个马甲\nconst memoizedFunc = useMemo(() =\u0026gt; () =\u0026gt; {}, [a, b]); // ------------ -------------- ----- // ^ ^ -------- ^ // | | ^ | // 工厂函数返回的回调函数 工厂函数 回调函数 依赖值数组 ","permalink":"/post/2023/04/28/react%E7%AC%94%E8%AE%B0-hook/","summary":"Hooks 是 React 实现组件逻辑的重要方式, 可以用来操作 state, 定义副作用, 更支持开发者自定义 Hooks. React 对 UI 的理想模型是 UI=f(state), 其中 UI 是视图, state 是应用状态, f 则是渲染过程.","title":"react 笔记 - hook"},{"content":" 原型 对象 (object) 和实例 (instance)\n对象是一个具有多种属性的内容结构 实例是类的具象化产品，可以使用 new 运算符在原型 (prototype) 基础上新建一个实例 function doSomething() {} var doSomething = function () {}; var doSomeInstancing = new doSomething(); // new 操作等价于 // var doSomeInstancing = {}; doSomeInstancing.__proto__ = doSomething.prototype 每一个对象拥有原型对象，对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型，并从中继承方法和属性，一层一层、以此类推。这种关系常被称为原型链 (prototype chain) 原型 (prototype)\n每个函数都有一个特殊的属性叫作原型 (prototype)\nfunction doSomething() {} console.log(doSomething.prototype); __proto__\n是每个实例 (instance) 上都有的属性\n实例的 __proto__ 就是函数的 prototype 属性\nfunction doSomething() {} doSomething.prototype.foo = \u0026#34;bar\u0026#34;; // add a property onto the prototype var doSomeInstancing = new doSomething(); doSomeInstancing.__proto__ === doSomething.prototype; // true // 实例 doSomeInstancing 的 __proto__ 属性就是函数 doSomething 的 prototype 属性 参考:\nMDN JavaScript 对象原型 说说原型（prototype）、原型链和原型继承 async await fetch(\u0026#34;https://blog.impyq.com\u0026#34;) .then((response) =\u0026gt; response.text()) .then(console.log) .catch(console.error); 等价于\nconst test = async () =\u0026gt; { try { const response = await fetch(\u0026#34;https://blog.impyq.com\u0026#34;); const data = await response.text(); console.log(data); } catch (error) { console.error(error); } }; test(); Promise Promise 是一个对象, 它代表了一个异步操作的最终完成或者失败.\n本质上 Promise 是一个函数返回的对象, 可以在它上面绑定回调函数, 这样就不需要把回调函数作为参数传入这个函数了.\nconst promise = doSomethingAsync(); promise.then(成功的回调, 失败的回调); // 简写为 doSomething().then(成功的回调, 失败的回调); then() 函数会返回一个和原来不同的新的 Promise, 这用就实现了链式调用 (chaining).\n每一个 Promise 都代表了链中另一个异步过程的完成.\ndoSomething() .then((result) =\u0026gt; doSomethingElse(result)) .then((newResult) =\u0026gt; doThirdThing(newResult)) .then((finalResult) =\u0026gt; { console.log(`Got the final result: ${finalResult}`); }) .catch(failureCallback); 例子 1:\n// 一秒后打印 123 setTimeout(function () { console.log(123); }, 1000); // 改成 Promise 形式 new Promise((resolve, reject) =\u0026gt; { setTimeout(function () { resolve(123); // 使用 resolve 接收正确结果 }, 1000); }).then((res) =\u0026gt; console.log(res)); // reject 接收错误, 倒入到 catch 中 new Promise((resolve, reject) =\u0026gt; { setTimeout(function () { reject(123); // reject 接收错误 }, 1000); }) .then((res) =\u0026gt; console.log(\u0026#34;success: \u0026#34; + res)) .catch((e) =\u0026gt; console.log(\u0026#34;error: \u0026#34; + e)); async 函数 async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为, 而无需刻意地链式调用 promise.\nasync await 是 promise then 的语法糖.\n例子 1:\nvar p1 = () =\u0026gt; new Promise((res, rej) =\u0026gt; { console.log(\u0026#34;这里是 Promise 构造函数, 在调用 p1 函数时会立刻执行\u0026#34;); // 阻塞 1 秒 setTimeout(() =\u0026gt; res(123), 1000); }); // 加上 async 关键字后, q1 还是普通函数, // 但在函数中可以使用 await 关键字 // await 关键字后面跟一个返回 Promise 对象的函数 // await 会拿到 resolve 结果, 是 then 函数的语法糖 async function q1() { var res = await p1(); console.log(res); } // 执行 q1, 可以在 1 秒后看到 123 q1(); // q1 还原成 then 形式 function q1() { p1().then((res) =\u0026gt; console.log(res)); } await 只能拿到 resolve 的值, 处理 reject 的错误需要 try catch.\n例子 2:\nvar successFunc = () =\u0026gt; new Promise((res, rej) =\u0026gt; { setTimeout(() =\u0026gt; res(123), 1000); }); var errFunc = () =\u0026gt; new Promise((res, rej) =\u0026gt; { setTimeout(() =\u0026gt; { res(456); }, 1000); // 1 秒 setTimeout(() =\u0026gt; { rej(\u0026#34;error crash\u0026#34;); }, 500); // 500 毫秒 }); async function foo() { try { var data1 = await successFunc(); console.log(\u0026#34;data1: \u0026#34; + data1); // data1 为传入 res 的值 var data2 = await errFunc(); // 这行不会执行因为 500 毫秒时先执行了 // rej(\u0026#34;error crash\u0026#34;), 这个 Promise 结束了 // 不会执行 1 秒后的 res(456) console.log(\u0026#34;data2: \u0026#34; + data1); } catch (err) { console.log(\u0026#34;catch: \u0026#34; + err); } } foo(); // 输出 // data1: 123 // catch: error crash 参考:\n【小知识】第 8 期 js 中 Promise 与 async/await 的用法简介 MDN - 使用 Promise MDN - Promise MDN - async 函数 几分钟搞明白 Promise, async await 的用法 5 分钟彻底掌握 async await 原理，面试手写再不用怕 对象和数组 析构对象\nconst person = { name: \u0026#34;张三\u0026#34;, age: 30, pet: { type: \u0026#34;猫\u0026#34;, }, }; let { name, pet: { type }, } = person; console.log(`名字: ${name}, 宠物类型: ${type}`); // 名字: 张三, 宠物: 猫 析构传给函数的参数:\nconst print = ({ name, pet: { type } }) =\u0026gt; { console.log(`名字: ${name}, 宠物类型: ${type}`); }; print(person); // 名字: 张三, 宠物: 猫 析构数组\nconst [first] = [\u0026#34;张三\u0026#34;, \u0026#34;李四\u0026#34;, \u0026#34;王五\u0026#34;]; console.log(first); // 张三 // 使用 , 跳过不需要的值 (列表匹配 list matching) const [, , third] = [\u0026#34;张三\u0026#34;, \u0026#34;李四\u0026#34;, \u0026#34;王五\u0026#34;]; console.log(\u0026#34;third:\u0026#34;, third); // third: 王五 对象字面量增强\n与析构相反, 对象字面量增强把对象重新组合成一体\nconst name = \u0026#34;张三\u0026#34;; const age = 30; const print = function () { console.log(`姓名: ${this.name}, 年龄: ${this.age}`); }; const person = { name, age, print }; console.log(person); person.print(); // { name: \u0026#39;张三\u0026#39;, age: 30, print: [Function: print] } // 姓名: 张三, 年龄: 30 展开运算符\n展开运算符是三个点 (\u0026hellip;)\n合并数组 const animal = [\u0026#34;goose\u0026#34;, \u0026#34;duck\u0026#34;]; const flower = [\u0026#34;rose\u0026#34;, \u0026#34;iris\u0026#34;]; const list = [...animal, ...flower]; console.log(\u0026#34;list:\u0026#34;, list); // list: [ \u0026#39;goose\u0026#39;, \u0026#39;duck\u0026#39;, \u0026#39;rose\u0026#39;, \u0026#39;iris\u0026#39; ] 无需改变原数组, 创建一个副本 const animal = [\u0026#34;goose\u0026#34;, \u0026#34;duck\u0026#34;]; const [a] = [...animal].reverse(); console.log(\u0026#34;a:\u0026#34;, a); console.log(\u0026#34;animal:\u0026#34;, animal); // a: duck // animal: [ \u0026#39;goose\u0026#39;, \u0026#39;duck\u0026#39; ] 获取数组剩余元素 const animal = [\u0026#34;cat\u0026#34;, \u0026#34;dog\u0026#34;, \u0026#34;goose\u0026#34;, \u0026#34;duck\u0026#34;]; const [first, ...others] = animal; console.log(\u0026#34;first:\u0026#34;, first); console.log(\u0026#34;others:\u0026#34;, others); 三个点号句法把函数参数收集到一个数组中 function print(...args) { console.log(\u0026#34;args:\u0026#34;, args); } print(\u0026#34;cat\u0026#34;, \u0026#34;dog\u0026#34;); // args: [ \u0026#39;cat\u0026#39;, \u0026#39;dog\u0026#39; ] 处理对象 与合并数组类似, 这次是合并对象\nconst morning = { breakfast: \u0026#34;皮蛋瘦肉粥\u0026#34;, lunch: \u0026#34;牛肉粉\u0026#34;, }; const dinner = \u0026#34;老友粉\u0026#34;; const oneday = { ...morning, dinner, }; console.log(\u0026#34;oneday:\u0026#34;, oneday); // oneday: { breakfast: \u0026#39;皮蛋瘦肉粥\u0026#39;, lunch: \u0026#39;牛肉粉\u0026#39;, dinner: \u0026#39;老友粉\u0026#39; } 参考:\nReact 学习手册 (第二版) Promise 与异步编程 参考: \u0026laquo;Understanding ECMAScript 6\u0026raquo; (深入理解 ES6, 作者 Nicholas C. Zakas)\njob queue, event loop js 引擎在同一时刻只能执行一段代码, 代码会被放置在 job queue (作业队列, 工作队列) 中.\n每当一段代码准备执行, 它就会被添加到 job queue.\nevent loop (事件循环) 是 js 引擎的一个内部处理线程, 它能监视代码的执行并管理 job queue.\n当 js 引擎结束当前代码的执行后, 事件循环就会执行队列中的下一个 job.\n事件模型 let button = document.getElementById(\u0026#34;my-btn\u0026#34;); button.onclick = function (event) { console.log(\u0026#34;clicked!\u0026#34;); }; 在这段代码中, console.log(\u0026quot;clicked!\u0026quot;) 直到 button 被点击后才会执行.\n当 button 被点击, 赋值给 onclick 的函数就被添加到 job queue 作业队列尾部, 并在队列前部所有任务结束之后再执行.\n回调模式 nodejs 错误优先 (error-first) 的回调函数风格.\nconst { readFile, writeFile } = require(\u0026#34;fs\u0026#34;); readFile(\u0026#34;test.txt\u0026#34;, (err, contents) =\u0026gt; { if (err) { throw err; } console.log(contents); const opt = { flag: \u0026#34;a\u0026#34;, // a: 追加写入; w: 覆盖写入 }; writeFile(\u0026#34;test.txt\u0026#34;, contents, opt, (err) =\u0026gt; { if (err) { throw err; } console.log(\u0026#34;file was written!\u0026#34;); }); }); console.log(\u0026#34;hi\u0026#34;); 使用回调函数模式, readFile() 会立即执行, 在读取磁盘时暂停.\nconsole.log(\u0026quot;hi\u0026quot;) 会在 readFile() 被调用后立即输出, 早于 console.log(contents);\nreadFile() 的成功调用引出了另一个异步调用 writeFile(), 当 readFile() 执行结束后, 添加一个 job 到 job queue 尾部, 导致 writeFile() 在之后被调用. writeFile() 也会在执行结束后向队列添加一个 job.\n这种嵌套层太多就会陷入回调地狱 (callback hell).\nPromise Promise 是为异步操作的结果准备的占位符.\nlet promise = readFile(\u0026#34;example.txt\u0026#34;); 期初 Promise 为挂起状态 (pending state), 表示异步操作未结束. 一旦异步操作结束, 会进入两种可能状态之一:\n已完成 (fulfilled): Promise 的异步操作已成功结束 已拒绝 (rejected): Promise 的异步操作未成功结束 可以使用 then() 方法在 Promise 的状态改变时执行一些操作.\nconst fs = require(\u0026#34;fs\u0026#34;); function myReadFile(path) { return new Promise((resolve, reject) =\u0026gt; { fs.readFile(path, (err, data) =\u0026gt; { if (err) { reject(err); // \u0026lt;-- 拒绝后的数据 } resolve(data); // \u0026lt;-- 完成后的数据 }); }); } let promise = myReadFile(\u0026#34;test.txt\u0026#34;); promise.then( (content) =\u0026gt; { console.log(\u0026#34;content\u0026#34;, content); }, (err) =\u0026gt; { console.log(\u0026#34;err\u0026#34;, err.message); } ); then() 方法接受两个参数, 一个是 Promise 被完成时要调用的函数, 另一个是 Promise 被拒绝时要调用的函数.\n任何与异步操作关联的附加数据都会被传入 resolve(), reject() 会被传入与拒绝相关的附加数据.\nthen() 也可以只传一部分参数\npromise.then((content) =\u0026gt; { // 完成 console.log(\u0026#34;content\u0026#34;, content); }); promise.then(null, (err) =\u0026gt; { // 拒绝 console.log(\u0026#34;err\u0026#34;, err.message); }); Promise 还有 catch() 方法, 其行文等同于只传递拒绝处理函数给 then()\nlet promise2 = myReadFile(\u0026#34;notexist.txt\u0026#34;); promise2.then(null, (err) =\u0026gt; { console.log(\u0026#34;err\u0026#34;, err.message); }); // 等同于 promise2.catch((err) =\u0026gt; { console.log(\u0026#34;err\u0026#34;, err.message); }); Utility Types 意思是充当工具的类型\n用法: 用范型给它传入一个其它类型, 然后 utility type 对这个类型进行某种操作\ntype Person = { name: string; age: number; }; // 使用 Partial 可以对 Person 这种类型不用传所有参数 (即不用传 age) // 而不需要修改原来的类型定义 const xiaoming: Partial\u0026lt;Person\u0026gt; = { name: \u0026#34;xiaoming\u0026#34; }; // Omit 可以 \u0026#34;删除\u0026#34; 原类型 Person 的某些属性 const shenmiren: Omit\u0026lt;Person, \u0026#34;name\u0026#34;\u0026gt; = { age: 20 }; // 合法 const shenmiren: Omit\u0026lt;Person, \u0026#34;name\u0026#34;\u0026gt; = { name: \u0026#34;报错!\u0026#34; }; // 报错, 因为 Omit 了 name 属性 参考:\nTypeScript 的 Utility Types, 你真的懂吗? ","permalink":"/post/2023/04/26/js-ts%E7%AC%94%E8%AE%B0/","summary":"原型 对象 (object) 和实例 (instance) 对象是一个具有多种属性的内容结构 实例是类的具象化产品，可以使用 new 运算符在原型 (prototype) 基础上新建一个实例 function doSomething() {} var doSomething = function () {}; var doSomeInstancing = new","title":"JavaScript \u0026 TypeScript 笔记"},{"content":"路由 react 前端路由的本质是路径和组件的一一对应关系, 即不同的 path 对应不同的 component, 访问不同路径时渲染对应的组件.\n安装\nnpm install --save react-router-dom 快速使用 引入 createBrowserRouter, RouterProvider\ncreateBrowserRouter: 创建路由实例, 在方法中定义路由 path 和组件的对应关系\nRouterProvider: 作为一个组件渲染, 并且传入 createBrowserRouter 方法执行后生成的 router 实例\n调用 createBrowserRouter, 生成 router 实例\n渲染 RouterProvider 组件并传入 router 实例\nsrc/index.js\nimport React from \u0026#34;react\u0026#34;; import ReactDOM from \u0026#34;react-dom/client\u0026#34;; import App from \u0026#34;./App\u0026#34;; import { createBrowserRouter, RouterProvider } from \u0026#34;react-router-dom\u0026#34;; const router = createBrowserRouter([ { path: \u0026#34;/\u0026#34;, element: \u0026lt;div\u0026gt;this is home\u0026lt;/div\u0026gt;, }, { path: \u0026#34;/login\u0026#34;, element: \u0026lt;div\u0026gt;this is login\u0026lt;/div\u0026gt;, }, ]); const root = ReactDOM.createRoot(document.getElementById(\u0026#34;root\u0026#34;)); root.render( \u0026lt;React.StrictMode\u0026gt; \u0026lt;RouterProvider router={router} /\u0026gt; \u0026lt;/React.StrictMode\u0026gt; ); npm start 后在浏览器输入 localhost:3000/ 和 localhost:3000/login 即可看到效果\n抽离单独组件和路由独立文件 目录结构 tree -I node_modules\n. ├── package.json ├── package-lock.json ├── public │ ├── favicon.ico │ └── index.html ├── README.md └── src ├── index.js ├── page │ ├── About.jsx │ ├── Home.jsx │ └── Login.jsx └── router └── index.jsx src/index.js\nimport React from \u0026#34;react\u0026#34;; import ReactDOM from \u0026#34;react-dom/client\u0026#34;; import { RouterProvider } from \u0026#34;react-router-dom\u0026#34;; import router from \u0026#34;./router\u0026#34;; // 引入 router 路由组件 const root = ReactDOM.createRoot(document.getElementById(\u0026#34;root\u0026#34;)); root.render( \u0026lt;React.StrictMode\u0026gt; \u0026lt;RouterProvider router={router} /\u0026gt; \u0026lt;/React.StrictMode\u0026gt; ); src/router/index.jsx\n// 配置路由对应关系 import { createBrowserRouter } from \u0026#34;react-router-dom\u0026#34;; import Home from \u0026#34;../page/Home\u0026#34;; import Login from \u0026#34;../page/Login\u0026#34;; import About from \u0026#34;../page/About\u0026#34;; const router = createBrowserRouter([ { path: \u0026#34;/\u0026#34;, element: \u0026lt;Home /\u0026gt;, }, { path: \u0026#34;/login\u0026#34;, element: \u0026lt;Login /\u0026gt;, }, { path: \u0026#34;/about\u0026#34;, element: \u0026lt;About /\u0026gt;, }, ]); export default router; src/page/Home.jsx\nconst Home = () =\u0026gt; { return \u0026lt;div\u0026gt;this is home\u0026lt;/div\u0026gt;; }; export default Home; 两种路由模式 react 函数 对应的路由模式 url 表现 底层原理 是否需要后端支持 兼容性 createBrowerRouter history url/login history 对象 + pushState 需要 ie10 createHashRouter hash url/#/login 监听 hashchange 事件 不需要 ie8 src/router/index.jsx\n// 引入 createHashRouter import { createBrowserRouter, createHashRouter } from \u0026#34;react-router-dom\u0026#34;; import Home from \u0026#34;../page/Home\u0026#34;; import Login from \u0026#34;../page/Login\u0026#34;; import About from \u0026#34;../page/About\u0026#34;; // hash 模式 const router = createHashRouter([ { path: \u0026#34;/\u0026#34;, element: \u0026lt;Home /\u0026gt;, }, { path: \u0026#34;/login\u0026#34;, element: \u0026lt;Login /\u0026gt;, }, { path: \u0026#34;/about\u0026#34;, element: \u0026lt;About /\u0026gt;, }, ]); export default router; url 从 localhost:3000/login 变为 localhost:3000/#/login\n编程式导航 通过 js 编程的方式进行路由页面跳转, 比如从首页跳转到登陆页\nimport { useNavigate } from \u0026#34;react-router-dom\u0026#34;; const Login = () =\u0026gt; { // 执行 useNavigate, 得到跳转函数 const navigate = useNavigate(); const goToAbout = () =\u0026gt; { // 执行跳转函数 navigate(\u0026#34;/about\u0026#34;); }; return ( \u0026lt;div\u0026gt; this is login \u0026lt;button onClick={goToAbout}\u0026gt;go to about\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; ); }; export default Login; 在 localhost:3000/#/login 点击 goToAbout 按钮即可跳转到 localhost:3000/#/about\n这是叠加状态, 即 /about 页面叠加在 /login\n如果在跳转时, 想要替换记录, 可以在 navigate 跳转函数的第二个参数加上 {replace: true}\nconst goToAbout = () =\u0026gt; { navigate(\u0026#34;/about\u0026#34;, { replace: true }); }; 这时候 localhost:3000/#/login 点击 goToAbout 跳转到 localhost:3000/#/about,\n但是在浏览器的后退按钮时, 会回到 localhost:3000 而不是 localhost:3000/#/login\n即 /about 页面替换了 /login 页面\n路由跳转传参 searchParams 传参\nscr/page/Login.jsx\nimport { useNavigate } from \u0026#34;react-router-dom\u0026#34;; const Login = () =\u0026gt; { const navigate = useNavigate(); const goToAbout = () =\u0026gt; { // 路由传参方式一: searchParams // 在跳转路由时拼接参数 navigate(\u0026#34;/about?id=1001\u0026#34;); }; return ( \u0026lt;div\u0026gt; this is login \u0026lt;button onClick={goToAbout}\u0026gt;go to about\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; ); }; export default Login; 登陆 localhost:3000/login 页面后, 点击 go to about 按钮, 会跳转到 localhost:3000/about?id=1001\n在 About 组件里获取参数\nsrc/page/About.jsx\n// 导入 useSearchParam 函数 import { useSearchParams } from \u0026#34;react-router-dom\u0026#34;; const About = () =\u0026gt; { // 获取通过 searchParams 传参过来的 id 参数 const [params] = useSearchParams(); const id = params.get(\u0026#34;id\u0026#34;); return \u0026lt;div\u0026gt;this is about {id}\u0026lt;/div\u0026gt;; }; export default About; 传递多个参数\nsrc/page/Login.jsx\nimport { useNavigate } from \u0026#34;react-router-dom\u0026#34;; const Login = () =\u0026gt; { const navigate = useNavigate(); const goToAbout = () =\u0026gt; { navigate(\u0026#34;/about?id=1001\u0026amp;name=pyq\u0026#34;); }; return ( \u0026lt;div\u0026gt; this is login \u0026lt;button onClick={goToAbout}\u0026gt;go to about\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; ); }; export default Login; src/page/About.jsx\nimport { useSearchParams } from \u0026#34;react-router-dom\u0026#34;; const About = () =\u0026gt; { const [params] = useSearchParams(); const id = params.get(\u0026#34;id\u0026#34;); const name = params.get(\u0026#34;name\u0026#34;); return ( \u0026lt;div\u0026gt; this is about {id} {name} \u0026lt;/div\u0026gt; ); }; export default About; params 传参\nscr/router/index.jsx\nimport { createBrowserRouter, createHashRouter } from \u0026#34;react-router-dom\u0026#34;; import Home from \u0026#34;../page/Home\u0026#34;; import Login from \u0026#34;../page/Login\u0026#34;; import About from \u0026#34;../page/About\u0026#34;; const router = createBrowserRouter([ { path: \u0026#34;/\u0026#34;, element: \u0026lt;Home /\u0026gt;, }, { path: \u0026#34;/login\u0026#34;, element: \u0026lt;Login /\u0026gt;, }, { path: \u0026#34;/about/:id\u0026#34;, // 首先用 id 占位符 element: \u0026lt;About /\u0026gt;, }, ]); export default router; src/page/Login.jsx\nimport { useNavigate } from \u0026#34;react-router-dom\u0026#34;; const Login = () =\u0026gt; { const navigate = useNavigate(); const goToAbout = () =\u0026gt; { navigate(\u0026#34;/about/1001\u0026#34;); // 在跳转时, 加上参数 }; return ( \u0026lt;div\u0026gt; this is login \u0026lt;button onClick={goToAbout}\u0026gt;go to about\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; ); }; export default Login; scr/page/About.jsx\nimport { useParams } from \u0026#34;react-router-dom\u0026#34;; const About = () =\u0026gt; { // 接受时, 使用 useParams const params = useParams(); return \u0026lt;div\u0026gt;this is about {params.id}\u0026lt;/div\u0026gt;; // 也可以 const { id } = useParams(); // const { id } = useParams(); // return \u0026lt;div\u0026gt;this is about {id}\u0026lt;/div\u0026gt;; }; export default About; 嵌套路由 如果当前组件的内容, 是在另一个组件模板中的局部进行切换, 那它就是一个嵌套路由\n在路由表中, 通过 children 属性进行二级路由配置\n通过内置组件 Outlet 渲染二级路由组件\n使用内置组件 Link 进行声明式导航配置\nscr/router/index.jsx\nimport { createBrowserRouter } from \u0026#34;react-router-dom\u0026#34;; import Layout from \u0026#34;../page/Layout\u0026#34;; import Login from \u0026#34;../page/Login\u0026#34;; import About from \u0026#34;../page/About\u0026#34;; import Article from \u0026#34;../page/Article\u0026#34;; import Board from \u0026#34;../page/Board\u0026#34;; const router = createBrowserRouter([ { path: \u0026#34;/\u0026#34;, element: \u0026lt;Layout /\u0026gt;, // 二级路由配置 children: [ { path: \u0026#34;article\u0026#34;, element: \u0026lt;Article /\u0026gt;, }, { path: \u0026#34;board\u0026#34;, element: \u0026lt;Board /\u0026gt;, }, ], }, { path: \u0026#34;/login\u0026#34;, element: \u0026lt;Login /\u0026gt;, }, { path: \u0026#34;/about/:id\u0026#34;, element: \u0026lt;About /\u0026gt;, }, ]); export default router; src/page/Layout.jsx\nimport { Link, Outlet } from \u0026#34;react-router-dom\u0026#34;; const Layout = () =\u0026gt; { return ( \u0026lt;div\u0026gt; 这是一级路由 layout 对应的模板 \u0026lt;div\u0026gt; \u0026lt;p\u0026gt; \u0026lt;Link to=\u0026#34;/board\u0026#34;\u0026gt;看板\u0026lt;/Link\u0026gt; \u0026lt;Link to=\u0026#34;/article\u0026#34;\u0026gt;文章\u0026lt;/Link\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p\u0026gt;这里是二级路由的出口位置\u0026lt;/p\u0026gt; \u0026lt;Outlet /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; ); }; export default Layout; 默认二级路由渲染 如果没有设置默认的二级路由\nlocalhost:3000 不会渲染二级路由的组件,\n只有访问 localhost:3000/board, localhost:3000/article 才会渲染二级路由组件\n设置了默认的二级路由就能实现 localhost:3000 直接渲染二级组件\nsrc/router/index.jsx\nimport { createBrowserRouter } from \u0026#34;react-router-dom\u0026#34;; import Layout from \u0026#34;../page/Layout\u0026#34;; import Login from \u0026#34;../page/Login\u0026#34;; import About from \u0026#34;../page/About\u0026#34;; import Article from \u0026#34;../page/Article\u0026#34;; import Board from \u0026#34;../page/Board\u0026#34;; const router = createBrowserRouter([ { path: \u0026#34;/\u0026#34;, element: \u0026lt;Layout /\u0026gt;, children: [ { path: \u0026#34;article\u0026#34;, element: \u0026lt;Article /\u0026gt;, }, { // 删除 path: \u0026#34;/board\u0026#34; // 加上 index: true // 将 Board 设置为默认的二级路由组件 index: true, element: \u0026lt;Board /\u0026gt;, }, ], }, { path: \u0026#34;/login\u0026#34;, element: \u0026lt;Login /\u0026gt;, }, { path: \u0026#34;/about/:id\u0026#34;, element: \u0026lt;About /\u0026gt;, }, ]); export default router; src/page/Layout.jsx\nimport { Link, Outlet } from \u0026#34;react-router-dom\u0026#34;; const Layout = () =\u0026gt; { return ( \u0026lt;div\u0026gt; 这是一级路由 layout 对应的模板 \u0026lt;div\u0026gt; \u0026lt;p\u0026gt; {/* 将 Board 的路径改为 /, 因为这是默认的二级路由组件 */} \u0026lt;Link to=\u0026#34;/\u0026#34;\u0026gt;看板\u0026lt;/Link\u0026gt; \u0026lt;Link to=\u0026#34;/article\u0026#34;\u0026gt;文章\u0026lt;/Link\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p\u0026gt;这里是二级路由的出口位置\u0026lt;/p\u0026gt; \u0026lt;Outlet /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; ); }; export default Layout; 404 页面配置 src/router/index.jsx\nimport { createBrowserRouter } from \u0026#34;react-router-dom\u0026#34;; // 省略 import NotFound from \u0026#34;../page/NotFound\u0026#34;; const router = createBrowserRouter([ // 省略 { path: \u0026#34;*\u0026#34;, element: \u0026lt;NotFound /\u0026gt;, }, ]); export default router; src/page/NotFound.jsx\nimport { Link } from \u0026#34;react-router-dom\u0026#34;; const NotFound = () =\u0026gt; { return ( \u0026lt;div\u0026gt; 你访问的页面飞往了月球 \u0026lt;Link to=\u0026#34;/\u0026#34;\u0026gt;回到首页\u0026lt;/Link\u0026gt; \u0026lt;/div\u0026gt; ); }; export default NotFound; 参考 bilibili 视频: React-Router v6 最新版入门 ","permalink":"/post/2023/04/22/react%E7%AC%94%E8%AE%B0-%E8%B7%AF%E7%94%B1/","summary":"路由 react 前端路由的本质是路径和组件的一一对应关系, 即不同的 path 对应不同的 component, 访问不同路径时渲染对应的组件. 安装 npm install --save react-router-dom 快速使用 引入 createBrowserRouter, RouterProvider createBrowserRouter: 创建路由实","title":"react 笔记 - 路由 React-Router v6"},{"content":"只记录软件安装与简单配置\nlinux 安装 ubuntu server https://iso.mirrors.ustc.edu.cn/ubuntu-releases/22.04.2/ubuntu-22.04.2-live-server-amd64.iso http://ftp.sjtu.edu.cn/ubuntu-cd/22.04.2/ubuntu-22.04.2-live-server-amd64.iso 如果用 Virtual Box 安装完后, 克隆出一个测试机器 (假设叫 ubuntu-test)\nsudo hostnamectl set-hostname ubuntu-test sudo rm -f /etc/machine-id sudo dbus-uuidgen --ensure=/etc/machine-id reboot 安装 linux mint TODO\n解决 windows / linux 双系统时间不同问题 linux 下执行:\ntimedatectl set-local-rtc 1 --adjust-system-clock 参考这篇\nlinux mint 常用快捷键 ctrl + shift + w 关闭窗口 shitf + win + [\u0026lt;- / -\u0026gt;] 将某个窗口移动到另一个显示屏 (monitor) ctrl + alt + [\u0026lt;- / -\u0026gt;] 切换工作空间 (workspace) apt 阿里云官方镜像站 上海交通大学 删除无用软件 sudo apt-get remove --purge ‘libreoffice*’ sudo apt-get clean sudo apt-get autoremove # zsh sudo apt-get remove --purge libreoffice\\* 参考: https://askubuntu.com/questions/180403/how-to-uninstall-libreoffice\n获得版本信息 \u0026amp;\u0026amp; 安装特定版本软件 apt-cache policy mysql-server # 版本信息 apt install mysql-server=5.7x # 安装 5.7 版本 修改 ubuntu 镜像源 备份\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.back 打开 /etc/apt/sources.list\nSJTUG 镜像源\ndeb https://mirror.sjtu.edu.cn/ubuntu/ jammy main restricted universe multiverse # deb-src https://mirror.sjtu.edu.cn/ubuntu/ jammy main restricted universe multiverse deb https://mirror.sjtu.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse # deb-src https://mirror.sjtu.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse deb https://mirror.sjtu.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse # deb-src https://mirror.sjtu.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse deb https://mirror.sjtu.edu.cn/ubuntu/ jammy-security main restricted universe multiverse # deb-src https://mirror.sjtu.edu.cn/ubuntu/ jammy-security main restricted universe multiverse # deb https://mirror.sjtu.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse # deb-src https://mirror.sjtu.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse 修改 linux mint 镜像源 TODO\n输入法 TODO\n命令行工具 sudo apt install -y build-essential vim htop git neofetch zsh tree openssh-server wget oh-my-zsh 下载 install.sh\nbash install.sh 或者\nsh -c \u0026#34;$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)\u0026#34; 禁止更新\nvim ~/.zshrc\nDISABLE_AUTO_UPDATE=\u0026#34;true\u0026#34; 插件配置 zsh-autosuggestions\ngit clone https://gitee.com/pyq19/zsh-autosuggestions.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions zsh-syntax-highlighting\ngit clone https://gitee.com/pyq19/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting vim ~/.zshrc\nplugins=( git zsh-autosuggestions zsh-syntax-highlighting ) go (golang) 下载地址: https://go.dev/dl/\ngo1.20.6.linux-amd64.tar.gz\n安装: https://go.dev/doc/install\n解压缩到 /usr/local/lib/go 目录下\nsudo rm -rf /usr/local/lib/go \u0026amp;\u0026amp; sudo tar -C /usr/local/lib -xzf ~/Downloads/go1.20.6.linux-amd64.tar.gz 环境变量\n# vim ~/.zshrc export GO=/usr/local/lib/go/bin # export GOPROXY=https://mirrors.aliyun.com/goproxy/ export GOPROXY=https://goproxy.io,direct export PATH=$GO:$PATH python pip (PyPI) apt install -y python3-pip 修改 ~/.zshrc 添加 bin 路径\nexport LOCAL_BIN=~/.local/bin export PATH=$LOCAL_BIN:$PATH 阿里源: https://mirrors.aliyun.com/pypi/\n新建 ~/.pip/pip.conf\nmkdir ~/.pip touch ~/.pip/pip.conf 内容\n[global] index-url = https://mirrors.aliyun.com/pypi/simple/ [install] trusted-host=mirrors.aliyun.com python3 使用 venv 创建虚拟环境 (python2 使用 virtualenv)\n# python3 -V # 输出: Python 3.10.12 apt install python3.10-venv 使用 venv 在当前文件夹新建 env 环境\npython3 -m venv env 激活虚拟环境\nsource env/bin/activate vscode https://code.visualstudio.com/\n多光标: 选中一个单词, ctrl + d 会多选重复的单词\nvim 插件 \u0026#34;vim.handleKeys\u0026#34;: { \u0026#34;\u0026lt;C-d\u0026gt;\u0026#34;: false, \u0026#34;\u0026lt;C-s\u0026gt;\u0026#34;: false, \u0026#34;\u0026lt;C-z\u0026gt;\u0026#34;: false, \u0026#34;\u0026lt;C-w\u0026gt;\u0026#34;: false } go 插件 vscode-icon 图标 File -\u0026gt; Preferences -\u0026gt; Settings\n在 User 标签页的 Extensions - vscode-icons configurations 找到\nFolders All Default Icon 选项, 打勾, 使用默认图标\n运行测试用例时, 显示 t.Log() 日志 在 .vscode/settings.json 中增加\n{ \u0026#34;go.testFlags\u0026#34;: [\u0026#34;-v\u0026#34;] } 参考这里\nmariaDB Server version: 10.6.12-MariaDB-0ubuntu0.22.04.1 Ubuntu 22.04\n参考:\nHow To Install MariaDB on Ubuntu 20.04 How To Install MariaDB 10.6 on Ubuntu 20.04|18.04 ERROR 1372 (HY000): Password hash should be a 41-digit hexadecimal number 安装\nsudo apt update sudo apt install mariadb-server -y sudo systemctl status mariadb.service # 确保服务运行 设置\nsudo mysql_secure_installation # 注意是大写的 Y # Enter current password for root (enter for none): 直接回车 # Switch to unix_socket authentication [Y/n] Y # Change the root password? [Y/n] Y 然后输入 root 密码 # Remove anonymous users? [Y/n] Y # Disallow root login remotely? [Y/n] n 允许远程访问 # Remove test database and access to it? [Y/n] Y # Reload privilege tables now? [Y/n] Y 远程 root 登录\nsudo vim /etc/mysql/mariadb.conf.d/50-server.cnf # 将 bind-address = 127.0.0.1 # 改为 bind-address = 0.0.0.0 systemctl restart mariadb # 重启 连接\nmysql -uroot -p\u0026#39;密码\u0026#39; 本地机器 (192.168.31.192):\nMariaDB [(none)]\u0026gt; select password(\u0026#39;asd\u0026#39;); # 生成密码 # +-------------------------------------------+ # | password(\u0026#39;asd\u0026#39;) | # +-------------------------------------------+ # | *F6DD0C0AC75395CB5BFC12C46B8880CD156B4799 | # +-------------------------------------------+ MariaDB [(none)]\u0026gt; grant all privileges on *.* to \u0026#39;root\u0026#39;@\u0026#39;%\u0026#39; identified by password \u0026#39;*F6DD0C0AC75395CB5BFC12C46B8880CD156B4799\u0026#39; with grant option; MariaDB [(none)]\u0026gt; flush privileges; 远程机器 (192.168.31.224) 连接:\nmysql -uroot -h192.168.31.192 -p\u0026#39;asd\u0026#39; 备份数据库 从 mysql#1 导出\nmysqldump 数据库名 -u\u0026#39;用户名\u0026#39; -p\u0026#39;密码\u0026#39; -h\u0026#39;地址\u0026#39; \u0026gt; 数据库名.sql 在 mysql#2 新建同名数据库\nCREATE DATABASE 数据库名 CHARACTER SET utf8 COLLATE utf8_general_ci; 数据库文件往 mysql#2 中导入\nmysql 数据库名 -u\u0026#39;用户名\u0026#39; -p\u0026#39;密码\u0026#39; \u0026lt; 数据库名.sql 参考:\nhttps://stackoverflow.com/questions/5551301/clone-mysql-database https://dba.stackexchange.com/questions/174/how-can-i-move-a-database-from-one-server-to-another code-server release 页面: https://github.com/coder/code-server/releases/\n上传到远程服务器 ~ 目录:\nscp ~/Downloads/code-server-4.16.1-linux-amd64.tar.gz root@\u0026lt;远程主机IP\u0026gt;:~/ (远程服务器) 解压缩\nmkdir /opt/code-server tar -C /opt/code-server/ -xvzf code-server-4.16.1-linux-amd64.tar.gz 新建 code-server 配置文件\ntouch ~/.config/code-server/config.yaml bind-addr: 127.0.0.1:5001 auth: password password: \u0026lt;密码\u0026gt; cert: false supervisor\ntouch /etc/supervisor/conf.d/code-server.conf [program:code-server] directory=/opt/code-server command=/opt/code-server/code-server-4.16.1-linux-amd64/bin/code-server stdout_logfile=/var/log/supervisor/code-server-stdout.log stderr_logfile=/var/log/supervisor/code-server-stderr.log 启动\nsupervisorctl reread supervisorctl update nginx\n/etc/nginx/conf.d/code-server.conf:\nserver { listen 80; charset utf-8; server_name code-server.impyq.com; location / { proxy_pass http://127.0.0.1:5001; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-Ip $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } } nginx -s reload supervisor 在 /etc/supervisor/conf.d 目录下新建配置文件 appname.conf\n[program:appname] directory=/opt/appname command=/opt/appname/bin/appname stdout_logfile=/var/log/supervisor/appname-stdout.log stderr_logfile=/var/log/supervisor/appname-stderr.log ssh 允许 root 连接 打开 /etc/ssh/sshd_config\n把 PermitRootLogin prohibit-password\n改为 PermitRootLogin yes\n连接不需要密码 ssh-copy-id -i ~/.ssh/id_rsa.pub 用户名@目标主机 nginx 配置目录 /etc/nginx/conf.d\n新建文件 team.conf\nserver { listen 80; charset utf-8; server_name team.impyq.com; location / { proxy_pass http://127.0.0.1:5002; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-Ip $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } } nginx -t # 测试 nginx -s reload # 重启 超时处理 TODO\n参考 nginx 常用超时设置\nhugo 地址: https://github.com/gohugoio/hugo/releases\n注意下载 hugo_extended 版本, 不然会报错\nError: Error building site: TOCSS: failed to transform \u0026#34;sass/jane.scss\u0026#34; (text/x-scss). Check your Hugo installation; you need the extended version to build SCSS/SASS with transpiler set to \u0026#39;libsass\u0026#39;.: this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information 下载 hugo_extended_0.111.3_linux-amd64.tar.gz\n# 将 .tar.gz 下载到 ~/Downloads 目录下 wget \u0026#39;https://github.com/gohugoio/hugo/releases/download/v0.111.3/hugo_extended_0.111.3_linux-amd64.tar.gz\u0026#39; -P ~/Downloads 解压缩\ncd ~/Downloads tar -xvzf hugo_extended_0.111.3_linux-amd64.tar.gz 将解压缩出来的可执行文件复制到 /usr/local/bin 目录下\ncp ~/Downloads/hugo /usr/local/bin node 官网: https://nodejs.org/en/download\n下载二进制文件: Linux Binaries (x64)\n参考: https://github.com/nodejs/help/wiki/Installation\n安装:\nsudo mkdir /usr/local/lib/nodejs sudo tar -C /usr/local/lib/nodejs -xJvf ~/Downloads/node-v18.17.0-linux-x64.tar.xz vim ~/.zshrc export NODEJS=/usr/local/lib/nodejs/node-v18.17.0-linux-x64/bin export PATH=$NODEJS:$PATH lazygit https://github.com/jesseduffield/lazygit/releases\n下载 lazygit_0.40.0_Linux_x86_64.tar.gz\ncd ~/Downloads tar xvzf lazygit_0.40.0_Linux_x86_64.tar.gz sudo mv lazygit /usr/local/bin ","permalink":"/post/2023/04/15/linux%E5%9F%BA%E7%A1%80%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AE/","summary":"只记录软件安装与简单配置 linux 安装 ubuntu server https://iso.mirrors.ustc.edu.cn/ubuntu-releases/22.04.2/ubuntu-22.04.2-live-server-amd64.iso http://ftp.sjtu.edu.cn/ubuntu-cd/22.04.2/ubuntu-22.04.2-live-server-amd64.iso 如果用 Virtual Box 安装完后, 克隆出一个测试机器 (假设叫 ubuntu-test) sudo hostnamectl set-hostname ubuntu-test sudo rm -f /etc/machine-id sudo dbus-uuidgen --ensure=/etc/machine-id reboot 安装 linux mint TODO 解决 windows / linux 双系统时间","title":"Linux 基础软件安装和配置"},{"content":"问题: 创建容器失败 Error response from daemon: driver failed programming external connectivity on endpoint sdwan_n60_u8_s161 (4672a1a28605479c6cb8a47f8d74dec73ed220cd5d38278bc7cf78706377f68f): Error starting userland proxy:.\n问题复现 只使用 10 个端口时候可以启动容器成功, 当使用 100 个端口时候就启动失败..\n问题原因猜测 docker 实现宿主机到容器的端口转发, 默认情况下, 是为每个端口启动 4 个 docker-proxy 进程来转发宿主机流量到容器对应的端口.\n当需要转发的端口数量很多时候, 非常占用系统资源, 并且导致启动容器非常慢.\n解决方法 docker 守护进程禁用 docker-proxy 转发端口流量, 使用宿主机 iptable 转发.\n修改 /etc/docker/daemon.json: {\u0026quot;userland-proxy\u0026quot;: false} 重启 docker 守护进程: service docker restart. 在部署和升级时候, 同样需要禁用 docker-proxy.. 禁用 docker-proxy 后使用可以使用 100 个端口正常启动容器, 并 ps -ef 不再有一堆 docker-proxy 进程.\n禁用 docker-proxy 之后带来新的问题 UDP 流量转发不了 参考文章 https://stackoverflow.com/questions/37770567/why-does-docker-run-so-many-processes-to-map-ports-though-to-my-application https://windsock.io/the-docker-proxy/ https://github.com/moby/moby/issues/14856 https://www.cnblogs.com/xiaojikuaipao/p/5634020.html https://tonybai.com/2016/01/15/understanding-container-networking-on-single-host/ https://www.jianshu.com/p/15c8ba38083e?utm_campaign=maleskine\u0026amp;utm_content=note\u0026amp;utm_medium=seo_notes\u0026amp;utm_source=recommendation ","permalink":"/post/2021/06/01/docker%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91%E9%97%AE%E9%A2%98/","summary":"问题: 创建容器失败 Error response from daemon: driver failed programming external connectivity on endpoint sdwan_n60_u8_s161 (4672a1a28605479c6cb8a47f8d74dec73ed220cd5d38278bc7cf78706377f68f): Error starting userland proxy:. 问题复现 只使用 10 个端口时候可以启动容器成功, 当使用 100 个端口时候就启动失败.. 问题原因猜测","title":"docker 端口转发问题"},{"content":"例子 1 可以把 defer ... 分成三部分:\n调用 defer, 计算函数的参数 执行 defer, 把函数推入栈 在 return 或者 panic 之后, 执行栈中的函数 例子:\nfunc test() (x int) { defer func(n int) { fmt.Printf(\u0026#34;in defer x as parameter: x = %d\\n\u0026#34;, n) fmt.Printf(\u0026#34;in defer x after return: x = %d\\n\u0026#34;, x) }(x) x = 7 return 9 } func main() { fmt.Println(\u0026#34;test\u0026#34;) fmt.Printf(\u0026#34;in main: x = %d\\n\u0026#34;, test()) } 这例子 defer 执行分为三部分:\n调用 defer, 计算 n 的值, n = x = 0, 所以作为参数的 x 为 0. 执行 defer, 将 func(n int)(0) 入栈. 在 return 9 之后执行 func(n int)(0), n 在 fmt.Printf(\u0026quot;in defer x as parameter: x = %d\\n\u0026quot;, n) 中已经被计算过了, x 在 fmt.Printf(\u0026quot;in defer x after return: x = %d\\n\u0026quot;, x) 将被计算得到 9. 例子的输出结果:\ntest in defer x as parameter: x = 0 in defer x after return: x = 9 in main: x = 9 例子 2 参考资料 Is golang defer statement execute before or after return statement?\nDefer, Panic, and Recover\n","permalink":"/post/2021/05/20/go-defer-panic-recover-%E7%AC%94%E8%AE%B0/","summary":"例子 1 可以把 defer ... 分成三部分: 调用 defer, 计算函数的参数 执行 defer, 把函数推入栈 在 return 或者 panic 之后, 执行栈中的函数 例子: func test() (x int) { defer func(n int) { fmt.Printf(\u0026#34;in defer x as parameter: x = %d\\n\u0026#34;, n) fmt.Printf(\u0026#34;in","title":"go defer panic recover 笔记"},{"content":"简介 WireGuard 是个 VPN, 多用于企业之间的数据安全传输.\n准备 使用 Parallels Desktop 新建两台 CentOS 7.9 的 Linux 虚拟机, 分别命名为 vm01, vm02.\n# 关闭防火墙 systemctl stop firewalld.service # 安装 yum install -y epel-release elrepo-release yum install -y yum-plugin-elrepo yum install -y kmod-wireguard wireguard-tools 入门 跟着WireGuard 官网中的视频一步一步操作\n# vm01 wg genkey \u0026gt; private ip link add wg0 type wireguard ip addr add 10.0.0.1/24 dev wg0 wg set wg0 private-key ./private ip link set wg0 up # vm02 wg genkey \u0026gt; private wg pubkey \u0026lt; private ip link add wg0 type wireguard ip addr add 10.0.0.2/24 dev wg0 wg set wg0 private-key ./private ip link set wg0 up 使用 ip addr 命令查看所有网络接口, 得到 vm01, vm02 的 eth0 网卡分配到的 ipv4 地址分别为 10.211.55.10, 10.211.55.11 并且互相能 ping 通, 还能看到 wg0 网卡信息.\n使用 wg 命令查看 WireGuard 信息\n# vm01 interface: wg0 public key: OgbUF/E21zi9qHf5h9H6N/iM92nnNYgRtOMhReFm730= private key: (hidden) listening port: 35304 # vm02 interface: wg0 public key: 4MIj+Te/pKnszjk9kc72E/YEhMeBdS8U1vPEg2PxSQI= private key: (hidden) listening port: 39114 配置 wg\n# vm01 wg set wg0 peer \u0026lt;vm02 的 public key\u0026gt; allowed-ips \u0026lt;vm02 的 wg0 地址/32\u0026gt; endpoint \u0026lt;vm02 的 eth0 ipv4 地址:listerning port\u0026gt; # vm02 wg set wg0 peer \u0026lt;vm01 的 public key\u0026gt; allowed-ips \u0026lt;vm01 的 wg0 地址/32\u0026gt; endpoint \u0026lt;vm01 的 eth0 ipv4 地址:listerning port\u0026gt; 验证两台虚拟机是否能用 wg0 网卡互联\n# vm01 ping 10.0.0.2 # vm02 ping 10.0.0.1 再次用 wg 命令发现 peer 的 lasted handshake 有信息即配置 wireguard 完成.\n一些有用的命令 查看配置好的网络情况 ip a | grep eth0\n2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 inet 10.211.55.11/24 brd 10.211.55.255 scope global noprefixroute dynamic eth0 查看各网卡当前路由ip route show\ndefault via 10.211.55.1 dev eth0 proto dhcp metric 100 10.211.55.0/24 dev eth0 proto kernel scope link src 10.211.55.11 metric 100 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 三台机器互联 需求: 假设有 A B C 三台机器, 要实现 A(client) -\u0026gt; B(server\u0026amp;client) -\u0026gt; C(server)\n实现:\n关闭 CentOS 7 的防火墙\nsystemctl stop firewalld.service 三台机器都生成秘钥\ncd /etc/wireguard wg genkey \u0026gt; private.key # 生成私钥 wg pubkey \u0026lt; ./private.key \u0026gt; public.key # 通过私钥生成公钥 配置 A(client)\n新建网卡\nip link add dev wg0-client type wireguard # wg0-client 为网卡名 ip addr add dev wg0-client 10.1.0.1/24 # 设置 10.1.x.x 网段下的 IP ip link set wg0-client up # 启动网卡 新建 wg0-client.conf, 内容:\n[Interface] ListenPort = 11111 PrivateKey = \u0026lt;A的秘钥\u0026gt; [Peer] PublicKey = \u0026lt;B的公钥\u0026gt; AllowedIPs = 0.0.0.0/0 Endpoint = \u0026lt;B的eth0网卡IP\u0026gt;:\u0026lt;B的wg0-server.conf写的ListenPort,即22222\u0026gt; PersistentKeepalive = 15 使用 wg0-client.conf 这个配置\nwg setconf wg0-client ./wg0-client.conf 配置 B(server)\n新建网卡\nip link add dev wg0-server type wireguard ip addr add dev wg0-server 10.1.0.2/24 # 设置 10.1.x.x 网段下的 IP ip link set wg0-server up 新建 wg0-server.conf, 内容:\n[Interface] ListenPort = 22222 PrivateKey = \u0026lt;B的秘钥\u0026gt; [Peer] PublicKey = \u0026lt;A的公钥\u0026gt; AllowedIPs = 0.0.0.0/0 Endpoint = \u0026lt;A的eth0网卡IP\u0026gt;:\u0026lt;A的wg0-client.conf写的ListenPort, 即11111\u0026gt; PersistentKeepalive = 15 使用 wg0-server.conf 这个配置\nwg setconf wg0-server ./wg0-server.conf 测试 A -\u0026gt; B 可以连通\n# A ping 10.1.0.2 # B tcpdump -i wg0-server 能看到 A 正常 ping 通, B tcpdump 有 request / reply, 反之也可以连通\n配置 B(client)\n新建网卡\nip link add dev wg1-client type wireguard ip addr add dev wg1-client 10.2.0.1/24 # !!! 注意这是新的网段 10.2.x.x 下的 IP ip link set wg1-client up # 启动作为客户端的网卡 新建 wg1-client.conf, 内容:\n[Interface] ListenPort = 23333 PrivateKey = \u0026lt;B的秘钥\u0026gt; [Peer] PublicKey = \u0026lt;C的公钥\u0026gt; AllowedIPs = 0.0.0.0/0 Endpoint = \u0026lt;C的eth0网卡IP\u0026gt;:\u0026lt;C的wg0-server.conf写的ListenPort,即33333\u0026gt; PersistentKeepalive = 15 使用 wg1-client.conf 这个配置\nwg setconf wg1-client ./wg1-client.conf 配置 C(server)\n新建网卡\nip link add dev wg0-server type wireguard ip addr add dev wg0-server 10.2.0.2/24 # 设置 10.2.x.x 网段下的 IP ip link set wg0-server up 新建 wg0-server.conf, 内容:\n[Interface] ListenPort = 33333 PrivateKey = \u0026lt;C的秘钥\u0026gt; [Peer] PublicKey = \u0026lt;B的公钥\u0026gt; AllowedIPs = 0.0.0.0/0 Endpoint = \u0026lt;B的eth0网卡IP\u0026gt;:\u0026lt;B的wg1-client.conf写的ListenPort, 即23333\u0026gt; PersistentKeepalive = 15 使用 wg0-server.conf 这个配置\nwg setconf wg0-server ./wg0-server.conf 测试 B -\u0026gt; C 可以连通\n实现 A -\u0026gt; C\n设置 A:\nip route add \u0026lt;C所在的网段即10.2.0.0\u0026gt;/24 via \u0026lt;wg0-server网卡的ipv4地址\u0026gt; 设置 C:\nip route add \u0026lt;A所在的网段即10.1.0.0\u0026gt;/24 via \u0026lt;wg1-client网卡的ipv4地址\u0026gt; 参考 网关和路由器的区别是什么？ Linux 下配置多网卡多网关 IP 地址是主机的还是网卡的 ? WireGuard Docs How to route between interfaces ","permalink":"/post/2020/12/15/wireguard%E7%AC%94%E8%AE%B0/","summary":"简介 WireGuard 是个 VPN, 多用于企业之间的数据安全传输. 准备 使用 Parallels Desktop 新建两台 CentOS 7.9 的 Linux 虚拟机, 分别命名为 vm01, vm02. # 关闭防火墙 systemctl stop firewalld.service # 安装 yum install -y epel-release elrepo-release yum install -y yum-plugin-elrepo yum install -y","title":"WireGuard 笔记"},{"content":"什么是线程 线程是一个进程内部的一个控制序列. 当在进程中创建一个新线程时, 新的执行线程将拥有自己的栈 (因此也有自己的局部变量), 但与它的创建者共享全局变量, 文件描述符, 信号处理函数, 当前目录状态. 线程切换的操作系统开销比进程之间的切换少的多.\n_REENTRANT 宏 最初设计 POSIX 规范时只考虑了一个进程只有一个执行线程, 这在多线程程序中会出问题, 比如用于获取某个函数调用失败的 errno 变量, 在多线程环境下会被多个线程共享, 一个线程准备获取刚才的错误代码时, 该变量很容易被另一个线程中的函数调用所改变. 为了解决这些问题需要编写可重入的代码, 即被多个线程调用仍然正常工作, 可重入部分只使用局部变量. _REENTRANT 宏告诉编译器需要可重入功能, 这个宏位于任何的 #include 语句之前.\n它会对部分函数重新定义它们的可安全重入版本, 只是在函数尾加上 _r 字符串, 比如 gethostbyname 变为 gethostbyname_r stdio.h 中原来以宏的形式实现的一些函数将变为可重入函数 在 errno.h 中定义的变量 errno 将成为一个函数调用, 它能够以多线程安全的方式获取 errno 值 第一个例子 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;pthread.h\u0026gt; // 定义一个函数原型, 这个函数接收任意参数, 返回任意类型 void *thread_function(void *arg); char message[] = \u0026#34;hello world\u0026#34;; int main() { int res; pthread_t a_thread; void *thread_result; // pthread_create 函数传递一个 pthread_t 类型对象的地址(a_thread), 之后可以用这个地址来引用新线程. // 第二个参数 NULL 表示不改变默认的线程属性. // 最后两个参数表示将要调用的函数和一个传递给该函数的参数. 注意这里的 message 是全局变量. res = pthread_create(\u0026amp;a_thread, NULL, thread_function, (void *)message); if (res != 0) { perror(\u0026#34;thread creation failed\u0026#34;); exit(EXIT_FAILURE); } printf(\u0026#34;waiting for thread to finish.\\n\u0026#34;); // pthread_join 将等到它所指定的线程(a_thread)终止后才返回. // 第二个参数是前面定义的线程的返回值 thread_result res = pthread_join(a_thread, \u0026amp;thread_result); if (res != 0) { perror(\u0026#34;thread join failed\u0026#34;); exit(EXIT_FAILURE); } printf(\u0026#34;thread joined, it returned %s\\n\u0026#34;, (char *)thread_result); printf(\u0026#34;message is now %s\\n\u0026#34;, message); // 全局变量 message 被改变了 exit(EXIT_SUCCESS); } void *thread_function(void *arg) { printf(\u0026#34;thread_function is running. argument was %s\\n\u0026#34;, (char *)arg); sleep(3); strcpy(message, \u0026#34;bye!\u0026#34;); pthread_exit(\u0026#34;thread_function finished.\u0026#34;); // 这里是线程的返回值. thread_result } /* 程序输出: waiting for thread to finish. (线程开始执行) thread_function is running. argument was hello world (3秒后) thread joined, it returned thread_function finished. message is now bye! /* 线程的同步, 信号量, 互斥量 用信号量进行同步 二进制信号量, 只有 0 和 1 两种取值, 一般常用来保护一段代码使其每次只能被一个执行线程运行.\n计数信号量, 允许有限数目的线程执行一段指定的代码\n计数信号量仅仅是二进制信号量的一种逻辑拓展, 两者实际调用的函数都一样.\n// 使用信号量同步 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;pthread.h\u0026gt; #include \u0026lt;semaphore.h\u0026gt; void *thread_function(void *arg); sem_t bin_sem; #define WORK_SIZE 1024 char work_area[WORK_SIZE]; int main() { int res; pthread_t a_thread; void *thread_result; // 初始化由 sem 指向的信号量对象, 设置它的共享选项, 并给它一个初始的整数值 0 res = sem_init(\u0026amp;bin_sem, 0, 0); if (res != 0) { perror(\u0026#34;semaphore initialization failed\u0026#34;); exit(EXIT_FAILURE); } res = pthread_create(\u0026amp;a_thread, NULL, thread_function, NULL); if (res != 0) { perror(\u0026#34;thread creation failed\u0026#34;); exit(EXIT_FAILURE); } // 从键盘读取一些文本并把它们放到工作区 work_area 数组中, 然后调用 sem_post 增加信号量的值. // 另一个线程此时已经调用 sem_wait 阻塞, sem_post 令另一个线程从 sem_wait 的等待中返回并开始执行. // 在统计完字符个数后, 它(另一个线程)再次调用 sem_wait 并再次阻塞, 直到主线程再次调用 sem_post 增加信号量的值. printf(\u0026#34;input some text. enter \u0026#39;end\u0026#39; to finish.\\n\u0026#34;); while (strncmp(\u0026#34;end\u0026#34;, work_area, 3) != 0) { fgets(work_area, WORK_SIZE, stdin); // sem_post 的作用是以原子操作的方式给信号量的值加 1 sem_post(\u0026amp;bin_sem); } // 在新线程中调用 sem_wait 等待信号量, 然后统计输入的字符个数 printf(\u0026#34;\\nwaiting for thread to finish.\\n\u0026#34;); res = pthread_join(a_thread, \u0026amp;thread_result); if (res != 0) { perror(\u0026#34;thread join failed\u0026#34;); exit(EXIT_FAILURE); } printf(\u0026#34;thread joined\\n\u0026#34;); sem_destroy(\u0026amp;bin_sem); exit(EXIT_SUCCESS); } // 在新线程中调用 sem_wait 等待信号量, 然后统计输入的字符个数 // sem_wait 会等待直到信号量有个非零值才会减 1 // 如果对值为 0 的信号量调用 sem_wait, 这个函数会等待(阻塞), 直到有其他线程增加了该信号量的值使其不再是 0 为止 void *thread_function(void *arg) { sem_wait(\u0026amp;bin_sem); while (strncmp(\u0026#34;end\u0026#34;, work_area, 3) != 0) { printf(\u0026#34;you input %d characters\\n\u0026#34;, strlen(work_area) - 1); sem_wait(\u0026amp;bin_sem); } pthread_exit(NULL); } /* gcc -D_REENTRANT thread3.c -lpthread ./a.out input some text. enter \u0026#39;end\u0026#39; to finish. asdfgh you input 6 characters aaa you input 3 characters asdf you input 4 characters end waiting for thread to finish. thread joined */ 用互斥量进行同步 为了控制对关键代码的访问, 必须在进入这段代码之前锁住一个互斥量, 然后在完成操作之后解锁它.\n下面的实例代码只是演示互斥量怎么用, 并不是最佳实践, 在实际编程中应该避免用轮询来获得结果.\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;pthread.h\u0026gt; #include \u0026lt;semaphore.h\u0026gt; void *thread_function(void *arg); pthread_mutex_t work_mutex; // 声明一个互斥量 #define WORK_SIZE 1024 char work_area[WORK_SIZE]; // 工作区 int time_to_exit = 0; int main() { int res; pthread_t a_thread; void *thread_result; // 初始化互斥量 res = pthread_mutex_init(\u0026amp;work_mutex, NULL); if (res != 0) { perror(\u0026#34;mutex initialization failed\u0026#34;); exit(EXIT_FAILURE); } // 启动新线程 res = pthread_create(\u0026amp;a_thread, NULL, thread_function, NULL); if (res != 0) { perror(\u0026#34;thread creation failed\u0026#34;); exit(EXIT_FAILURE); } // 主线程先给工作区(work_area字符数组)加锁, 读入文本到它里面 pthread_mutex_lock(\u0026amp;work_mutex); printf(\u0026#34;input some text. enter \u0026#39;end\u0026#39; to finish\\n\u0026#34;); while (!time_to_exit) { fgets(work_area, WORK_SIZE, stdin); // 然后解锁允许其他线程对它访问(统计字符串长度) pthread_mutex_unlock(\u0026amp;work_mutex); while (1) { // 周期性对互斥量再加锁, 检查字符串数目是否已经统计完成(另一个线程). pthread_mutex_lock(\u0026amp;work_mutex); if (work_area[0] != \u0026#39;\\0\u0026#39;) { // 如果还需要等待(用户没有输入 end, 第一个元素就不为 \\0 null) // 则释放互斥量 pthread_mutex_unlock(\u0026amp;work_mutex); sleep(1); } else { break; } } } pthread_mutex_unlock(\u0026amp;work_mutex); printf(\u0026#34;\\nwaiting for thread to finish.\\n\u0026#34;); res = pthread_join(a_thread, \u0026amp;thread_result); if (res != 0) { perror(\u0026#34;thread join failed\u0026#34;); exit(EXIT_FAILURE); } printf(\u0026#34;thread joined\\n\u0026#34;); pthread_mutex_destroy(\u0026amp;work_mutex); exit(EXIT_SUCCESS); } // 新线程中执行的函数 void *thread_function(void *arg) { sleep(1); // 新线程首先试图对互斥量加锁, 如果它已经被锁住, 这个调用将被阻塞直到互斥量被释放. pthread_mutex_lock(\u0026amp;work_mutex); // 获得访问权 // 如果用户不输入 end 则进入循环 while (strncmp(\u0026#34;end\u0026#34;, work_area, 3) != 0) { printf(\u0026#34;you input %d characters\\n\u0026#34;, strlen(work_area) - 1); work_area[0] = \u0026#39;\\0\u0026#39;; // 用第一个字符设置为 null 的方法通知已经完成统计 pthread_mutex_unlock(\u0026amp;work_mutex); sleep(1); pthread_mutex_lock(\u0026amp;work_mutex); while (work_area[0] == \u0026#39;\\0\u0026#39;) { pthread_mutex_unlock(\u0026amp;work_mutex); sleep(1); pthread_mutex_lock(\u0026amp;work_mutex); } } // 如果用户输入 end 则退出上面的循环, // 将工作区 work_area (就是用来存屏幕输入的字符数组) 的第一个字符设置为 \\0 (null) time_to_exit = 1; work_area[0] = \u0026#39;\\0\u0026#39;; pthread_mutex_unlock(\u0026amp;work_mutex); pthread_exit(0); } 线程的属性 TODO\n多线程 在一个程序中创建多个线程, 然后又以不同于其启动的顺序将他们合并到一起.\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;pthread.h\u0026gt; #define NUM_THREADS 6 void *thread_function(void *arg); int main() { int res; pthread_t a_thread[NUM_THREADS]; void *thread_result; int lots_of_threads; for (lots_of_threads = 0; lots_of_threads \u0026lt; NUM_THREADS; lots_of_threads++) { res = pthread_create(\u0026amp;(a_thread[lots_of_threads]), NULL, thread_function, (void *)lots_of_threads); if (res != 0) { perror(\u0026#34;thread creation failed\u0026#34;); exit(EXIT_FAILURE); } // sleep(1); } printf(\u0026#34;waiting for threads to finish.\\n\u0026#34;); for (lots_of_threads = NUM_THREADS - 1; lots_of_threads \u0026gt;= 0; lots_of_threads--) { res = pthread_join(a_thread[lots_of_threads], \u0026amp;thread_result); if (res == 0) { printf(\u0026#34;picked up a thread\\n\u0026#34;); } else { perror(\u0026#34;pthread_join failed\u0026#34;); } } printf(\u0026#34;all done\\n\u0026#34;); exit(EXIT_SUCCESS); } void *thread_function(void *arg) { int my_number = (int)arg; int rand_num; printf(\u0026#34;thread_function is running. argument was %d\\n\u0026#34;, my_number); rand_num = 1 + (int)(9.0 * rand() / (RAND_MAX + 1.0)); sleep(rand_num); printf(\u0026#34;bye from %d\\n\u0026#34;, my_number); pthread_exit(NULL); } /* thread_function is running. argument was 0 thread_function is running. argument was 3 thread_function is running. argument was 4 thread_function is running. argument was 1 thread_function is running. argument was 2 waiting for threads to finish. thread_function is running. argument was 5 bye from 5 picked up a thread bye from 3 bye from 0 bye from 4 bye from 1 picked up a thread picked up a thread bye from 2 picked up a thread picked up a thread picked up a thread all done */ 参考 \u0026laquo;Linux 程序设计(第四版)\u0026raquo; ","permalink":"/post/2020/10/12/posix%E7%BA%BF%E7%A8%8B/","summary":"什么是线程 线程是一个进程内部的一个控制序列. 当在进程中创建一个新线程时, 新的执行线程将拥有自己的栈 (因此也有自己的局部变量), 但与它的创建者","title":"POSIX 线程"},{"content":"什么是 V8? V8 是谷歌开源的 JavaScript 解析器.\n编译 通过 git 拉取的 V8 项目源码是不完整的, 缺少依赖, 只能通过 gclient 方式拉取源码.\nnodejs 是依赖 V8 作为 JavaScript 的运行环境的, 本文不通过 gclient 拉源码, 而是通过 nodejs 的源码编译出动态库, 这其中就包括了 V8 的函数实现.\n首先拉取 nodejs 源码:\ncd ~/Codes git clone https://github.com/nodejs/node.git 编译:\ncd node # 编译出 nodejs 动态库, 如果不加 --shared, 则会编译出可执行文件 ./configure --shared make -j 8 耗时大概 35 分钟, 编译完成. 可以看到 out/Release/obj.target 目录下有编译出的 libnode.so.86 即 node 的动态库.\nHello World! 新建一个 C++ 的项目:\ncd ~/Codes/temp mkdir include mkdir lib touch main.cpp # 把 nodejs 项目里的 V8 头文件复制到 include/v8 文件夹下: cp -r ~/Codes/node/deps/v8/include ~/Codes/temp/include/v8 # 复制编译好的动态库 # 这里不是很理解 linkname, soname, realname cp ~/Codes/node/out/Release/obj.target/libnode.so.86 ~/Codes/temp/lib/libnode.so # 注意 这里去掉了后缀 .86 sudo cp ~/Codes/node/out/Release/obj.target/libnode.so.86 /lib 打开 main.cpp 输入以下内容:\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026#34;libplatform/libplatform.h\u0026#34; #include \u0026#34;v8.h\u0026#34; int main(int argc, char *argv[]) { // 初始化v8 v8::V8::InitializeICUDefaultLocation(argv[0]); v8::V8::InitializeExternalStartupData(argv[0]); std::unique_ptr\u0026lt;v8::Platform\u0026gt; platform = v8::platform::NewDefaultPlatform(); v8::V8::InitializePlatform(platform.get()); v8::V8::Initialize(); // 创建一个isolate v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); v8::Isolate *isolate = v8::Isolate::New(create_params); //构建好isolate开始编程 { v8::Isolate::Scope isolate_scope(isolate); // 构建一个handle_scope管理即将分配的各种变量 v8::HandleScope handle_scope(isolate); // 构建一个上下文context v8::Local\u0026lt;v8::Context\u0026gt; context = v8::Context::New(isolate); // 将context放进scope中v8会进行管理 v8::Context::Scope context_scope(context); // 构建一段string类型的handle，这一段内容就是javascript了 v8::Local\u0026lt;v8::String\u0026gt; source = v8::String::NewFromUtf8(isolate, \u0026#34;\u0026#39;Hello\u0026#39; + \u0026#39;, World!\u0026#39;\u0026#34;, v8::NewStringType::kNormal) .ToLocalChecked(); // 编译代码 v8::Local\u0026lt;v8::Script\u0026gt; script = v8::Script::Compile(context, source).ToLocalChecked(); // 跑代码 v8::Local\u0026lt;v8::Value\u0026gt; result = script-\u0026gt;Run(context).ToLocalChecked(); // 转换成utf8的变量 v8::String::Utf8Value utf8(isolate, result); // c语言经典打印函数 printf(\u0026#34;%s\\n\u0026#34;, *utf8); // 输出v8版本号 printf(\u0026#34;v8 version: %s\\n\u0026#34;, v8::V8::GetVersion()); } // 关闭v8并且清理内存 isolate-\u0026gt;Dispose(); v8::V8::Dispose(); v8::V8::ShutdownPlatform(); delete create_params.array_buffer_allocator; return 0; } 编译:\ng++ -I ./include -I ./include/v8 ./main.cpp -L ./lib -lnode # -I 指定头文件目录 # -L ./lib 指定动态库目录 # -lnode 使用 ./lib 目录下的 libnode.so, 连接器 ld 会去 /lib 目录下找到 libnode.so.86 运行:\n./a.out 即可看到:\nHello, World! v8 version: 8.4.371.19-node.16 参考资料 V8 概念以及编程入门 how to build nodejs as a shared library from source code ","permalink":"/post/2020/10/10/javascript-v8-%E5%85%A5%E9%97%A8/","summary":"什么是 V8? V8 是谷歌开源的 JavaScript 解析器. 编译 通过 git 拉取的 V8 项目源码是不完整的, 缺少依赖, 只能通过 gclient 方式拉取源码. nodejs 是依赖 V8 作为 JavaScript 的运行环境的, 本文不","title":"JavaScript V8 引擎入门"},{"content":"记录一些阅读 \u0026laquo;SFML Game Development\u0026raquo; \u0026laquo;SFML Game Development By Example\u0026raquo; 等的样板代码, 以及做些笔记.\n主循环 void Application::run() { sf::Clock clock; sf::Time timeSinceLastUpdate = sf::Time::Zero; sf::Time timePerFrame = sf::seconds(1.f/60.f); while (mWindow.isOpen()) { sf::Time dt = clock.restart();\t// dt: deltaTime 两次循环时间差 timeSinceLastUpdate += dt;\t// 游戏世界中, 两帧之间的时间差 while (timeSinceLastUpdate \u0026gt; timePerFrame) { timeSinceLastUpdate -= timePerFrame; processInput();\t// 处理用户输入(比如键盘鼠标窗口) update(timePerFrame); if (mStateStack.isEmpty()) mWindow.close(); } updateStatistics(dt);\t// 更新 FPS 等信息 render();\t// 渲染 } } // 参考: http://sshpark.com.cn/2019/06/05/用C++和SFML写游戏-Game类的创建（2）/ 代码命名规则 类成员变量以 m 开头, 如: mWindow, mIsMovingUp. 类成员函数(方法)以小写开头: update(), 类静态变量及静态函数以大写开头: PlayerSpeed, 和 Java 驼峰命名一致.\n资源加载 资源加载一般在程序入口 Application (也叫 Game) 类初始化时一同初始化.\n// Application.h class Application { // 省略一些函数以及成员变量 private: sf::RenderWindow mWindow;\t// 窗口 TextureHolder mTextures;\t// 素材加载 FontHolder mFonts;\t// 字体加载 }; // Application.cpp Application::Application() : mWindow(sf::VideoMode(1024, 768), \u0026#34;Graphics\u0026#34;, sf::Style::Close) , mTextures() // 素材资源初始化 , mFonts() // 素材资源初始化 { mWindow.setKeyRepeatEnabled(false); mWindow.setFramerateLimit(60); mFonts.load(Fonts::Main, \u0026#34;Media/Sansation.ttf\u0026#34;); // 素材加载 mTextures.load(Textures::TitleScreen, \u0026#34;Media/Textures/TitleScreen.png\u0026#34;); // 素材加载 mTextures.load(Textures::Buttons, \u0026#34;Media/Textures/Buttons.png\u0026#34;);// 素材加载 } TextureHolder, FontHolder 在 ResourceHolder.hpp 中定义:\n// ResourceHolder.hpp template \u0026lt;typename Resource, typename Identifier\u0026gt; class ResourceHolder { public: template \u0026lt;typename Parameter\u0026gt; void load(Identifier id, const std::string\u0026amp; filename, const Parameter\u0026amp; secondParam); Resource\u0026amp; get(Identifier id); const Resource\u0026amp; get(Identifier id) const; private: std::map\u0026lt;Identifier, std::unique_ptr\u0026lt;Resource\u0026gt;\u0026gt;\tmResourceMap; }; typedef ResourceHolder\u0026lt;sf::Texture, Textures::ID\u0026gt; TextureHolder; // Textures 是命名空间, ID 是枚举类型 typedef ResourceHolder\u0026lt;sf::Font, Fonts::ID\u0026gt; FontHolder; namespace Textures { enum ID { Entities, Jungle, TitleScreen, Buttons, Explosion, Particle, FinishLine, }; } 在 Application 初始化时候, 调用 TextureHolder (也就是 ResourceHolder) 的 load 方法加载素材并存入 mResourceMap:\nmTextures.load(Textures::TitleScreen, \u0026#34;Media/Textures/TitleScreen.png\u0026#34;); 通过 mTextures.get(Textures::TitleScreen) 调用素材.\n","permalink":"/post/2020/10/07/c++sfml%E6%B8%B8%E6%88%8F%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BC%96%E7%A8%8B%E4%B8%AD%E7%9A%84%E6%A0%B7%E6%9D%BF%E4%BB%A3%E7%A0%81/","summary":"记录一些阅读 \u0026laquo;SFML Game Development\u0026raquo; \u0026laquo;SFML Game Development By Example\u0026raquo; 等的样板代码, 以及做些笔记. 主循环 void Application::run() { sf::Clock clock; sf::Time timeSinceLastUpdate = sf::Time::Zero; sf::Time timePerFrame = sf::seconds(1.f/60.f); while (mWindow.isOpen()) { sf::Time dt = clock.restart(); // dt: deltaTime 两次循环时间差 timeSinceLastUpdate += dt; // 游戏世界中","title":"C++ SFML 游戏客户端编程中的样板代码"},{"content":"位运算符 符号 描述 运算规则 \u0026amp; 与 AND 两个操作数都为 1 时,运算结果为 1 | 或 OR 两个操作数都为 0 时, 运算结果为 0 ^ 异或 XOR 两个操作数相同时运算结果为 0, 两个操作数不同结果为 1 ~ 取反 NOT 如果操作数为 0 运算结果为 1, 如果操作数是 1 运算结果为 0 示例:\nA B A\u0026amp;B A|B A^B ~A 0 0 0 0 0 1 1 0 0 1 1 0 0 1 0 1 1 1 1 1 1 1 0 0 位运算的运用 一个值表示多个状态(打标记):\n#include \u0026lt;iostream\u0026gt; using namespace std; const int FROZEN = 0b0001; // 冰冻状态 const int FIRE = 0b0001 \u0026lt;\u0026lt; 1; // 着火状态 const int STUCK = 0b0001 \u0026lt;\u0026lt; 2; // 行动受限制 void add_status(int \u0026amp;player_status, int new_status) { // player_status = player_status | new_status; player_status |= new_status; } void delete_status(int \u0026amp;player_status, int new_status) { // player_status = player_status \u0026amp; (~new_status); player_status \u0026amp;= ~new_status; } bool has_status(int \u0026amp;player_status, int status) { return (player_status \u0026amp; status) == status; } int main(int argc, char const *argv[]) { int status = 0; // 玩家状态 bool is_frozen = false; // 是否被冰冻 bool is_fire = false; // 是否着火 bool is_stuck = false; // 是否行动受限 cout \u0026lt;\u0026lt; \u0026#34;玩家状态: \u0026#34; \u0026lt;\u0026lt; status \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;是否被冰冻: \u0026#34; \u0026lt;\u0026lt; is_frozen \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;是否着火: \u0026#34; \u0026lt;\u0026lt; is_fire \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;是否行动受限: \u0026#34; \u0026lt;\u0026lt; is_stuck \u0026lt;\u0026lt; endl; add_status(status, FROZEN); add_status(status, FIRE); add_status(status, STUCK); delete_status(status, FIRE); is_frozen = has_status(status, FROZEN); is_fire = has_status(status, FIRE); is_stuck = has_status(status, STUCK); cout \u0026lt;\u0026lt; \u0026#34;玩家状态: \u0026#34; \u0026lt;\u0026lt; status \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;是否被冰冻: \u0026#34; \u0026lt;\u0026lt; is_frozen \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;是否着火: \u0026#34; \u0026lt;\u0026lt; is_fire \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;是否行动受限: \u0026#34; \u0026lt;\u0026lt; is_stuck \u0026lt;\u0026lt; endl; return 0; } /* 玩家状态: 0 是否被冰冻: 0 是否着火: 0 是否行动受限: 0 玩家状态: 5 是否被冰冻: 1 是否着火: 0 是否行动受限: 1 */ ","permalink":"/post/2020/09/26/%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%94%E8%AE%B0/","summary":"位运算符 符号 描述 运算规则 \u0026amp; 与 AND 两个操作数都为 1 时,运算结果为 1 | 或 OR 两个操作数都为 0 时, 运算结果为 0 ^ 异或 XOR 两个操作数相同时运算结果为 0, 两个","title":"位运算笔记"},{"content":"编译 编译分为多个步骤:\n预编译: 处理源文件里的预编译指令(以#开头), 如 #include 翻译: gcc -c, 对源代码进行语法检查并翻译成机器指令, 生成目标文件(.o) 链接: 将多个目标文件链接成可执行文件 也可以用源文件和目标文件生成可执行文件:\ngcc -c fun.c gcc main.c fun.o -o main GCC 的目标是生成可执行文件.\n假设有两个源文件 fun.c 和 main.c, fun.c 中包含了 main.c 中声明的函数主体, 可以通过 gcc fun.c main.c -o main 生成可执行程序 main.\ngcc -c fun.c gcc -c main.c gcc fun.o main.o -o main 过程: 源文件(.c) -\u0026gt; 编译(gcc) -\u0026gt; 可执行程序\n函数库 假设有 main.c:\nvoid write(char *); // 函数声明 int add(int, int); // 函数声明 int sub(int, int); // 函数声明 int main(void){ write(\u0026#34;123\\n\u0026#34;); int a = add(1, 2); int b = sub(1, 2); } main.c 的函数实现分散在 fun1.c, fun2.c, fun3.c 三个文件中, 执行 gcc -c fun1.c fun2.c fun3.c 得到三个中间文件 fun1.o fun2.o fun3.o, 链接 gcc fun1.o fun2.o fun3.o main.c 得到可执行文件.\n当生成的中间文件越来越多时, 可以用 ar 命令将所有中间文件存储在一起: ar r libxxx.a fun1.o fun2.o fun3.o, 得到函数库(库文件) libxxx.a. 最后通过 gcc main.c libxxx.a 生成可执行程序.\n函数库中的函数(实现)称为库函数.\n静态库和共享库 函数库分为两种, 以 .a 结尾的称为静态库, 以 .so 结尾的称为共享库(动态库/动态链接库).\n源文件和静态库链接生成的可执行程序中, 包含了程序执行所用到的函数体(函数实现)的代码, 因此程序体积相对较大. 可执行程序运行时, 只载入了它需要的函数, 而不是载入静态库中所有的函数.\n生成共享库的方式: gcc -shared -o libxxx.so fun1.o fun2.o fun3.o 源文件和共享库链接生成可执行程序: gcc main.c libxxx.so\n源文件和共享库链接生成的可执行程序中, 不包含它所用到的函数的代码, 因此程序体积相对较小. 程序启动后, 当执行到共享库中的函数时, 程序会找到共享库的位置, 然后将其所需要的函数从共享库中加载到内存中.\n程序找到共享库的方式: export LD_LIBRARY_PATH = ./. 环境变量 LD_LIBRARY_PATH 主要用于指定查找共享库时除了默认路径之外的其他路径.\n头文件, 预处理指令, 文件包含指令 当需要大量且频繁地使用库函数时, 在程序开头声明函数原型就变得非常麻烦, 因此可以把一个函数库中所有函数实现的函数原型的声明, 集中到一个 .h 结尾的文件中, 这个文件称为头文件.\n程序中 # 是预处理指令标识, #include 是其中的一种预处理指令: 文件包含指令, 作用是把指令自身替换为它所指定的文件的内容.\ninclude 的两种形式和预处理器的搜索路径 #include \u0026quot;stdio.h\u0026quot;\n源文件所在的路径 -I 选项所指定的路径 环境变量 C_INCLUDE_PATH 包含的路径 (export C_INCLUDE_PATH = ...) 预配置的路径 (预处理器 cpp -v) #include \u0026lt;stdio.h\u0026gt;\n-I 选项所指定的路径 环境变量 C_INCLUDE_PATH 包含的路径 预配置的路径 参考 https://www.limitedwish.org/threethings/2012/07/16/linkname-soname-realname/ ","permalink":"/post/2020/08/29/c%E8%AF%AD%E8%A8%80%E7%BC%96%E8%AF%91%E6%B5%81%E7%A8%8B/","summary":"编译 编译分为多个步骤: 预编译: 处理源文件里的预编译指令(以#开头), 如 #include 翻译: gcc -c, 对源代码进行语法检查并翻译成机器指令, 生成目标文件(.o)","title":"C 语言编译流程"},{"content":"准备 sudo apt-get install build-essential sudo apt-get install libgl1-mesa-dev # OpenGL 库 sudo apt-get intsall libglm-dev sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev GLFW 下载编译源码\ncd ~/Codes git clone https://github.com/glfw/glfw.git # git@gitee.com:pyq19/glfw.git mkdir -p ~/Codes/glfw/glfw-build cd ~/Codes/glfw/glfw-build cmake ~/Codes/glfw sudo make install 运行 example\n./examples/wave GLAD GLAD 是第三方的 OpenGL 开源库，需要到 https://glad.dav1d.de/ 生成 include 头文件.\n选好 C++ OpenGL 版本，生成压缩包并下载.\ncd ~/Downloads unzip glad.zip sudo cp -r glad /usr/include sudo cp -r KHR /usr/include src/glad.c 要保留.\n第一个测试程序 新建目录\nmkdir -p ~/Codes/glfw-test-application cd ~/Codes/glfw-test-application cp ~/Downloads/glad/src/glad.c . # 把 glad.c 复制到当前目录 新建 main.cpp\n#include \u0026lt;glad/glad.h\u0026gt; #include \u0026lt;GLFW/glfw3.h\u0026gt; #include \u0026lt;iostream\u0026gt; int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); GLFWwindow *window = glfwCreateWindow(800, 600, \u0026#34;LearnOpenGL\u0026#34;, NULL, NULL); if (window == NULL) { std::cout \u0026lt;\u0026lt; \u0026#34;Failed to create GLFW window\u0026#34; \u0026lt;\u0026lt; std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout \u0026lt;\u0026lt; \u0026#34;Failed to initialize GLAD\u0026#34; \u0026lt;\u0026lt; std::endl; return -1; } glViewport(0, 0, 800, 600); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; } 新建 CMakeLists.txt\nCMAKE_MINIMUM_REQUIRED(VERSION 3.16) PROJECT(test) FIND_PACKAGE(glfw3 REQUIRED) FIND_PACKAGE(OpenGL REQUIRED) SET(SOURCE_FILES main.cpp glad.c) ADD_EXECUTABLE(test ${SOURCE_FILES}) TARGET_LINK_LIBRARIES(test glfw) TARGET_LINK_LIBRARIES(test OpenGL::GL) 编译运行，即可看到暗绿色的窗口.\nmkdir build cd build cmake .. make ./test GLEW GLEW 是 OpenGL 的扩展工具集，很多教程用的是 GLFW + GLEW 而不是 GLFW + GLAD\nsudo apt-get install libglew-dev 另一个测试程序 准备\nmkdir -p ~/Codes/test-opengl #新建项目目录 cd ~/Codes/test-opengl touch main.cpp touch CMakeLists.txt 在 main.cpp 中输入以下代码(来源《Computer Graphics Programming in OpenGL with C++ by V. Scott Gordon》程序 2-1)\n#include \u0026lt;GL/glew.h\u0026gt; #include \u0026lt;GLFW/glfw3.h\u0026gt; #include \u0026lt;iostream\u0026gt; using namespace std; void init(GLFWwindow *window) {} void display(GLFWwindow *window, double currentTime) { glClearColor(1.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); } int main(void) { if (!glfwInit()) { exit(EXIT_FAILURE); } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); GLFWwindow *window = glfwCreateWindow(600, 600, \u0026#34;hello world\u0026#34;, NULL, NULL); glfwMakeContextCurrent(window); if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); } glfwSwapInterval(1); init(window); while (!glfwWindowShouldClose(window)) { display(window, glfwGetTime()); glfwSwapBuffers(window); glfwPollEvents(); } glfwDestroyWindow(window); glfwTerminate(); exit(EXIT_SUCCESS); } 在 CMakeLists.txt 中输入\nCMAKE_MINIMUM_REQUIRED(VERSION 3.16) PROJECT(test) FIND_PACKAGE(GLEW REQUIRED) FIND_PACKAGE(glfw3 REQUIRED) FIND_PACKAGE(OpenGL REQUIRED) SET(SOURCE_FILES main.cpp) ADD_EXECUTABLE(test ${SOURCE_FILES}) TARGET_LINK_LIBRARIES(test GLEW::GLEW) TARGET_LINK_LIBRARIES(test glfw) TARGET_LINK_LIBRARIES(test OpenGL::GL) 编译运行\nmkdir build cd build cmake .. make ./test 即可以看到一个红色的窗口.\n参考资料 GLFW-Building applications Linux(Ubuntu) OpenGL 开发环境 how-to-build-install-glfw-3-and-use-it-in-a-linux-project linking-glew-with-cmake ","permalink":"/post/2020/08/28/linuxubuntu-opengl-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/","summary":"准备 sudo apt-get install build-essential sudo apt-get install libgl1-mesa-dev # OpenGL 库 sudo apt-get intsall libglm-dev sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev GLFW 下载编译源码 cd ~/Codes git clone https://github.com/glfw/glfw.git # git@gitee.com:pyq19/glfw.git mkdir -p ~/Codes/glfw/glfw-build cd ~/Codes/glfw/glfw-build cmake ~/Codes/glfw sudo make install 运行 example ./examples/wave GLAD GLAD 是第三方的 OpenGL 开源库，需要到 https://glad.dav1d.de/ 生成","title":"Linux (Ubuntu) OpenGL 开发环境"},{"content":"Ubuntu Server 16.04 安装 MongoDB sudo apt install mongodb -y 配置文件在 /etc/mongodb.conf MacOS 安装 MongoDB 方法 1: brew 安装 brew tap mongodb/brew brew install mongodb-community brew untap mongodb/brew (删除仓库) 默认配置文件位置参考官方文档 方法 2: 二进制文件安装 从官方网站下载可执行文件 tar -xvzf mongodb-osx-ssl-x86_64-3.6.18.tgz -C /usr/local/share (解压从官网下载的可执行文件) export MONGO_BIN=/usr/local/share/mongodb-osx-x86_64-3.6.18/bin mkdir /usr/local/share/mongodb-osx-x86_64-3.6.18/data (新建数据目录) 编辑配置文件 sudo vim /etc/mongod.conf, 输入以下内容 systemLog: destination: file # path: \u0026#34;/var/log/mongod.log\u0026#34; path: \u0026#34;/tmp/mongod.log\u0026#34; logAppend: true storage: dbPath: \u0026#34;/usr/local/share/mongodb-osx-x86_64-3.6.18/data\u0026#34; journal: enabled: true # processManagement: # fork: true net: bindIp: 127.0.0.1 port: 27017 setParameter: enableLocalhostAuthBypass: false # security: # authorization: \u0026#34;enabled\u0026#34; 启动 sudo mongod -f /etc/mongod.conf 设置管理员账号 use admin db.createUser( { user: \u0026#34;root\u0026#34;, pwd: \u0026#34;********\u0026#34;, roles: [ { role: \u0026#34;userAdminAnyDatabase\u0026#34;, db: \u0026#34;admin\u0026#34; }, \u0026#34;readWriteAnyDatabase\u0026#34; ] } ) ","permalink":"/post/2020/05/04/macos-mongodb-%E5%AE%89%E8%A3%85/","summary":"Ubuntu Server 16.04 安装 MongoDB sudo apt install mongodb -y 配置文件在 /etc/mongodb.conf MacOS 安装 MongoDB 方法 1: brew 安装 brew tap mongodb/brew brew install mongodb-community brew untap mongodb/brew (删除仓库) 默认配置文件位置参考官方文档 方法 2: 二进制文件安装 从官方网","title":"MacOS MongoDB 安装"},{"content":" \u0026laquo; 图解TCP/IP(第五版) \u0026raquo; \u0026laquo; TCP/IP经典入门(第五版) \u0026raquo; 0. TCP 概述 面向连接 (connection-oriented) 可靠的 (reliable) 检验和 包的序列号解决乱序、重复 超时重传 流量控制、拥塞控制 基于字节流 (byte-stream) 全双工 (full-duplex) 1. OSI 参考模型 层数 分层名称 功能 7 应用层 为应用程序提供服务并规定应用程序中通信相关的细节，电子邮件-\u0026gt;电子邮件协议。 6 表示层 设备固有数据格式和网络标准数据格式的转换，管理数据加密与压缩。 5 会话层 通信管理，负责建立和断开通信连接。管理传输层以下的分层。在计算机的通信应用之间建立会话。 4 传输层 管理两个节点之间的数据传输，确保数据被可靠地传送到目标地址。为网络提供错误控制和数据流控制。 3 网络层 地址管理与路由选择。 2 数据链路层 互联设备之间传送和识别数据帧。将“0”、“1”序列划分为具有意义的数据帧传送给对方端。提供与网络适配器相连的接口，维护子网的逻辑链接。 1 物理层 负责“0”、“1”比特流（“0”、“1”序列）与电压的高低、光的闪灭之间的互换。界定连接器和网线的规格。把数据转换为传输介质上的电子流或模拟脉冲，并监视数据的传输。 2. TCP/IP 协议分层模型 层数 分层名称 功能 4 应用层 3 传输层 为网络提供了流量控制、错误控制和确认服务，充当网络应用程序的接口。 2 互联网层/网际层 提供独立于硬件的逻辑寻址，让数据能在具有不同物理结构的子网之间传递。 1 网卡层/网络访问层 提供了与物理网络连接的接口。针对传输介质设置数据的格式，根据硬件的物理地址实现数据的寻址，对数据在物理网络中的传递提供错误控制。 0 硬件 3. OSI 参考模型与 TCP/IP 的关系 OSI 参考模型注重“通信协议必要的功能是什么”，TCP/IP 更强调“在计算机上实现协议应该开发哪种程序”。\n4. 三次握手四次挥手 次数 客户端 方向 服务端 1 SYN -\u0026gt; 2 \u0026lt;- SYN + ACK 3 ACK -\u0026gt; 4 FIN -\u0026gt; 5 \u0026lt;- ACK 6 \u0026lt;- FIN 7 ACK -\u0026gt; ","permalink":"/post/2020/02/15/tcpip%E5%8D%8F%E8%AE%AE%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","summary":"\u0026laquo; 图解TCP/IP(第五版) \u0026raquo; \u0026laquo; TCP/IP经典入门(第五版) \u0026raquo; 0. TCP 概述 面向连接 (connection-oriented) 可靠的 (reliable) 检验和 包的序列号解决乱序、重复 超时重传 流量控制、拥","title":"TCP/IP 协议学习笔记"},{"content":"CentOS 8 cd /etc/sysconfig/network-scripts vim ifcfg-网卡名 写入如下内容\nTYPE=\u0026#34;Ethernet\u0026#34; PROXY_METHOD=\u0026#34;none\u0026#34; BROWSER_ONLY=\u0026#34;no\u0026#34; BOOTPROTO=\u0026#34;static\u0026#34; \u0026lt;-- 由 dhcp 动态分配 IP 改为 static DEFROUTE=\u0026#34;yes\u0026#34; IPV4_FAILURE_FATAL=\u0026#34;no\u0026#34; IPV6INIT=\u0026#34;yes\u0026#34; IPV6_AUTOCONF=\u0026#34;yes\u0026#34; IPV6_DEFROUTE=\u0026#34;yes\u0026#34; IPV6_FAILURE_FATAL=\u0026#34;no\u0026#34; IPV6_ADDR_GEN_MODE=\u0026#34;stable-privacy\u0026#34; NAME=\u0026#34;enp0s8\u0026#34; \u0026lt;-- 网卡名 UUID=\u0026#34;5b7a74a7-a5e6-4527-9cd6-6b11d4d26989\u0026#34; \u0026lt;-- 由 uuidgen 生成，必须修改 DEVICE=\u0026#34;enp0s8\u0026#34; \u0026lt;-- 网卡名 ONBOOT=\u0026#34;yes\u0026#34; IPADDR=\u0026#34;192.168.56.79\u0026#34; \u0026lt;-- 想要设置成的静态 IP NETMASK=\u0026#34;255.255.255.0\u0026#34; 对比 ifcfg-enp0s3\nTYPE=\u0026#34;Ethernet\u0026#34; PROXY_METHOD=\u0026#34;none\u0026#34; BROWSER_ONLY=\u0026#34;no\u0026#34; BOOTPROTO=\u0026#34;dhcp\u0026#34; DEFROUTE=\u0026#34;yes\u0026#34; IPV4_FAILURE_FATAL=\u0026#34;no\u0026#34; IPV6INIT=\u0026#34;yes\u0026#34; IPV6_AUTOCONF=\u0026#34;yes\u0026#34; IPV6_DEFROUTE=\u0026#34;yes\u0026#34; IPV6_FAILURE_FATAL=\u0026#34;no\u0026#34; IPV6_ADDR_GEN_MODE=\u0026#34;stable-privacy\u0026#34; NAME=\u0026#34;enp0s3\u0026#34; UUID=\u0026#34;7dadbb92-ca08-4a8c-b61f-ee93473f02ac\u0026#34; DEVICE=\u0026#34;enp0s3\u0026#34; ONBOOT=\u0026#34;yes\u0026#34; 重启网络服务让配置生效\nsystemctl restart NetworkManager.service Ubuntu 18.04 cd /etc/netplan vim 05-cloud-init.yaml # sudo netplan generate 输入以下内容\nnetwork: ethernets: enp3s0: \u0026lt;-- 网卡名 dhcp4: false \u0026lt;-- 不获取动态 IP addresses: - 192.168.1.97/24 \u0026lt;-- 静态 IP gateway4: 192.168.1.1 \u0026lt;-- 路由地址 nameservers: addresses: - 223.5.5.5 \u0026lt;-- 阿里云 DNS - 223.6.6.6 search: [] version: 2 应用修改使配置生效\nsudo netplan apply ","permalink":"/post/2019/12/24/linux%E9%85%8D%E7%BD%AE%E9%9D%99%E6%80%81ip/","summary":"CentOS 8 cd /etc/sysconfig/network-scripts vim ifcfg-网卡名 写入如下内容 TYPE=\u0026#34;Ethernet\u0026#34; PROXY_METHOD=\u0026#34;none\u0026#34; BROWSER_ONLY=\u0026#34;no\u0026#34; BOOTPROTO=\u0026#34;static\u0026#34; \u0026lt;-- 由 dhcp 动态分配 IP 改为 static DEFROUTE=\u0026#34;yes\u0026#34; IPV4_FAILURE_FATAL=\u0026#34;no\u0026#34; IPV6INIT=\u0026#34;yes\u0026#34; IPV6_AUTOCONF=\u0026#34;yes\u0026#34; IPV6_DEFROUTE=\u0026#34;yes\u0026#34; IPV6_FAILURE_FATAL=\u0026#34;no\u0026#34; IPV6_ADDR_GEN_MODE=\u0026#34;stable-privacy\u0026#34; NAME=\u0026#34;enp0s8\u0026#34; \u0026lt;-- 网卡名 UUID=\u0026#34;5b7a74a7-a5e6-4527-9cd6-6b11d4d26989\u0026#34; \u0026lt;-- 由 uuidgen 生成，必须修改 DEVICE=\u0026#34;enp0s8\u0026#34; \u0026lt;-- 网卡名 ONBOOT=\u0026#34;yes\u0026#34; IPADDR=\u0026#34;192.168.56.79\u0026#34; \u0026lt;-- 想要设置","title":"Linux 配置静态 IP"},{"content":"生成 dotnet 项目\ndotnet new console -o demo5 在 .vscode/launch.json 中加上\n{ \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;.NET Core Launch (console)\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;coreclr\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;preLaunchTask\u0026#34;: \u0026#34;build demo5\u0026#34;, \u0026#34;program\u0026#34;: \u0026#34;${workspaceFolder}/dotnet/demo5/bin/Debug/netcoreapp3.0/demo5.dll\u0026#34;, \u0026#34;args\u0026#34;: [], \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceFolder}\u0026#34;, \u0026#34;stopAtEntry\u0026#34;: false, \u0026#34;console\u0026#34;: \u0026#34;internalConsole\u0026#34;, \u0026#34;logging\u0026#34;: { \u0026#34;moduleLoad\u0026#34;: false } } ] } 在 .vscode/tasks.json 加上\n{ \u0026#34;version\u0026#34;: \u0026#34;2.0.0\u0026#34;, \u0026#34;tasks\u0026#34;: [ { \u0026#34;label\u0026#34;: \u0026#34;build demo5\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;process\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;dotnet\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;build\u0026#34;, \u0026#34;${workspaceFolder}/dotnet/demo5/demo5.csproj\u0026#34; ] } ] } 这一步的目的是在 debug 前编译项目\n打开 Program.cs, 打断点, 用 F5 调试\n","permalink":"/post/2019/11/15/%E7%94%A8vscode%E8%B0%83%E8%AF%95dotnet%E7%A8%8B%E5%BA%8F/","summary":"生成 dotnet 项目 dotnet new console -o demo5 在 .vscode/launch.json 中加上 { \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;.NET Core Launch (console)\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;coreclr\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;preLaunchTask\u0026#34;: \u0026#34;build demo5\u0026#34;, \u0026#34;program\u0026#34;: \u0026#34;${workspaceFolder}/dotnet/demo5/bin/Debug/netcoreapp3.0/demo5.dll\u0026#34;, \u0026#34;args\u0026#34;: [], \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceFolder}\u0026#34;, \u0026#34;stopAtEntry\u0026#34;: false, \u0026#34;console\u0026#34;: \u0026#34;internalConsole\u0026#34;, \u0026#34;logging\u0026#34;: { \u0026#34;moduleLoad\u0026#34;: false } } ] } 在 .vscode/tasks.json 加上 { \u0026#34;version\u0026#34;: \u0026#34;2.0.0\u0026#34;, \u0026#34;tasks\u0026#34;: [ { \u0026#34;label\u0026#34;: \u0026#34;build demo5\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;process\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;dotnet\u0026#34;, \u0026#34;args\u0026#34;: [","title":"用 vscode 调试 dotnet 程序"},{"content":"递归模版 def recursion(level, param1, param2, ...): # 递归终止条件 recursion terminator if level \u0026gt; MAX_LEVEL: print_result return # 到了这层梦境, 在这层梦境要做的事情 process logic in current level process_data(level, data, ...) # 下探新的梦境 drill down self.recursion(level + 1, p1, p2, ...) # 还原下探之后可能对本层的影响, 还原状态 reverse the current level status if needed reverse_status(level) DFS 深度优先遍历 - 递归写法 visited = set() def dfs(node, visited): visited.add(node) # 处理当前节点 process current node here ... for next_node in node.children(): if not next_node in visited: self.dfs(next_node, visited) BFS 广度优先遍历 - 递归写法 def BFS(gragh, start, end): queue = [] queue.append([start]) visited.add(start) while queue: node = queue.pop() visited.add(node) process(node) nodes = generate_related_nodes(node) queue.push(nodes) # other processing work ... 二分查找 left, right = 0, len(array) - 1 while left \u0026lt;= right: mid = left + (right - left) / 2 if array[mid] == target: # 如果查找到了就直接返回 find the target break or return result elif array[mid] \u0026lt; target: left = mid + 1 else: right = mid - 1 DP 动态规划 (TODO 这个不清楚) // 状态定义 dp = new int[m+1][n+1]; // 初始状态 dp[0][0] = x; dp[0][1] = y; ... // DP 状态的推导 for i = 0; i \u0026lt;= n; ++i { for j = 0; j \u0026lt;= m; ++j { // 递推方程示例 ... d[i][j] = min {dp[i - 1][j], dp[i][j - 1], etc.} } } return dp[m][n]; // 最优解 位运算 X \u0026amp; 1 == 1 OR == 0 判断奇偶 (X % 2 == 1) X = X \u0026amp; (X - 1) =\u0026gt; 清零最低位的 1 X \u0026amp; -X =\u0026gt; 得到最低位的 1 排序 选择排序 (Selection Sort) int main() { // i j int arr[] = {9, 5, 2, 7, 1, 6}; int length = sizeof(arr) / sizeof(arr[0]); // 数组长度 for (int i = 0; i \u0026lt; length; i++) { int minIndex = i; // 寻找 [j(i+1), length) 区间最小值 for (int j = i + 1; j \u0026lt; length; j++) { if (arr[j] \u0026lt; arr[minIndex]) { minIndex = j; } } // 交换 i 位置和 minIndex(找到的最小数的索引) 位置中的值 // swap(arr[i], arr[minIndex]); int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } // 打印排序后的结果 for (int i = 0; i \u0026lt; length; i++) { cout \u0026lt;\u0026lt; arr[i] \u0026lt;\u0026lt; \u0026#39; \u0026#39;; } return 0; } // 1 2 5 6 7 9 冒泡排序 (Bubble Sort) TODO\n插入排序 (Insertion Sort) TODO\n归并排序 (Merge Sort) TODO\n快速排序 (Quick Sort) TODO\n","permalink":"/post/2019/11/05/%E7%AE%97%E6%B3%95%E4%BB%A3%E7%A0%81%E6%A8%A1%E6%9D%BF/","summary":"递归模版 def recursion(level, param1, param2, ...): # 递归终止条件 recursion terminator if level \u0026gt; MAX_LEVEL: print_result return # 到了这层梦境, 在这层梦境要做的事情 process logic in current level process_data(level, data, ...) # 下探新的梦境 drill down self.recursion(level + 1, p1, p2, ...) # 还原下探","title":"算法代码模板"}]