[{"content":"源自知乎问答 那些编程水平很高的程序员是怎么训练出来的? 。\n![[assets/pasted image 20250214121141.png|250]]\nwrite lots of code. clone existing things as exercises. learn deeply. alternate trying yourself and reading literature. be obsessive.\n\u0026ndash; john carmack(约翰.卡马克)\n编写大量代码。通过克隆现有项目作为练习。深入学习。在自己尝试和阅读文献之间交替进行。保持痴迷。\nai 技术向润色版\n以大量编码为核心训练方式。通过复刻成熟项目理解架构逻辑,在反复拆解与重构中完成深度学习。实践与文献交叉验证,保持对技术本质的持续专注。\n\u0026ndash; 约翰·卡马克(john carmack,id software 联合创始人,《毁灭战士》引擎开发者)\n![[assets/pasted image 20250214121247.png|250]] ![[assets/pasted image 20250214121316.png|243]]\nmost of my programming career has involved finding something neat(简洁的), writing my own version to understand it \u0026amp; often throwing it away.\ni program those \u0026ldquo;clones\u0026rdquo; like i read papers: change a core part; redesign it. gain progress or understanding why it is what it is.\n\u0026ndash; edward kmett(爱德华.克梅特)\n我的编程生涯大部分时间都在做这样的事情:发现一些有趣的东西,编写自己的版本来理解它,然后将它丢弃。\n我编写这些“克隆”项目的方式就像阅读论文一样:改变核心部分;重新设计它。取得进展,或者理解它为什么是现在这个样子。\nai 技术向润色版\n我的编程方法论始终遵循:发现优秀范式 → 自行实现代码以透彻理解 → 选择性重构或废弃。\n这种仿写过程如同学术研究:通过修改核心模块验证假设,重构系统设计探索可能性。最终实现认知跃迁,或逆向推导出原始设计的必然性。\n\u0026ndash; 爱德华·克梅特(edward kmett,haskell 委员会成员,透镜库 lens 作者)\n","date":"2025-02-14","permalink":"https://aituyaa.com/%E7%BC%96%E7%A8%8B%E6%B0%B4%E5%B9%B3%E6%98%AF%E6%80%8E%E4%B9%88%E8%AE%AD%E7%BB%83%E5%87%BA%E6%9D%A5%E7%9A%84/","summary":"\u003cp\u003e源自知乎问答 \u003ca href=\"https://www.zhihu.com/question/351504112\"\u003e那些编程水平很高的程序员是怎么训练出来的?\u003c/a\u003e 。\u003c/p\u003e","title":"编程水平是怎么训练出来的"},]
[{"content":"好嘛,让我们来试一下 neovim ~\n安装及配置 去官网 home - neovim 下载对应的版本,安装即可。\n其配置文件夹为 c:\\users\\\u0026lt;你的用户名\u0026gt;\\appdata\\local\\nvim\\ ,入口文件为 init.lua 。\nlazyvim 这里我们直接使用 lazyvim/lazyvim: neovim config for the lazy ,以下为初次安装时可能遇到的一些问题。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # required - 备份原有配置 move-item $env:localappdata\\nvim $env:localappdata\\nvim.bak # optional but recommended - 备份原有数据 move-item $env:localappdata\\nvim-data $env:localappdata\\nvim-data.bak # 克隆 lazyvim starter git clone --depth=1 https://github.com/lazyvim/starter $env:localappdata\\nvim # 删除 .git 以方便个人托管 remove-item $env:localappdata\\nvim\\.git -recurse -force # 启动 nvim nvim 你可能会遇到:\nno c compiler found! \u0026#34;cc\u0026#34;, \u0026#34;gcc\u0026#34;, \u0026#34;clang\u0026#34;, \u0026#34;cl\u0026#34;, \u0026#34;zig\u0026#34; are not executable. 这个错误表明 neovim 在尝试编译某些插件时,没有找到可用的 c 编译器。许多 neovim 插件(例如 nvim-treesitter)需要编译本地代码,因此需要安装 c 编译器。\n由于我并不想在电脑上安装 visual stadio 这个庞然大物,这里我们使用 llvm 的 clang 。\n1 2 3 4 choco install clang -y # 检查是否安装成功 clang -v :: 这里我们使用 chocolatey 安装, 也可以去 llvm 官网 下载。\n再次运行,有出现如下问题:\nerror detected while processing c:\\users\\jack\\appdata\\local\\nvim\\init.lua: nvim-treesitter[diff]: error during compilation clang: warning: unable to find a visual studio installation; try running clang from a developer command prompt [-wmsvc-not-found]^m in file included from src/parser.c:1:^m src\\tree_sitter/parser.h:10:10: fatal error: \u0026#39;stdlib.h\u0026#39; file not found^m 10 | #include \u0026lt;stdlib.h\u0026gt;^m | ^~~~~~~~~~^m 1 error generated.^m 这个错误表明 nvim-treesitter 在编译时找不到 stdlib.h,通常是由于 clang 没有正确找到 c 语言标准库。\n❓不是已经安装 clang 了吗\n原来,在 windows 上,clang 依赖 visual studio(msvc)或 mingw 来提供标准库,而你的系统似乎没有正确设置。\n1 2 3 4 choco instal mingw -y # 检查是否安装成功 gcc -v 好嘛,终于可以体验 neovim 了。\n不对,还需要安装一下 fzf - a command-line fuzzy finder 命令行模糊搜索工具,如下:\n1 2 3 4 5 6 choco install fzf -y choco install ripgrep -y # 检查是否安装成功 fzf --version rg --version okay 搜索的话还需要 ripgrep 。\n附录 windows 中的 neovim 的 mason 找不到 pyenv 安装的 python 使用 pyenv 安装的 python 指向默认是 c:\\users\\jack\\.pyenv\\pyenv-win\\shims ,这个环境变量 mason 识别不到。我们需要把 c:\\users\\jack\\.pyenv\\pyenv-win\\versions\\3.13.1\\python3.exe 加入到环境变量中,即可。\n","date":"2025-02-01","permalink":"https://aituyaa.com/neovim-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e好嘛,让我们来试一下 Neovim ~\u003c/p\u003e","title":"neovim 那些事儿"},]
[{"content":"poetry 是一个现代的 python 包管理工具,它不仅能够管理依赖,还可以构建和发布 python 包。poetry 的目标是简化 python 项目的管理,替代传统的 requirements.txt、setup.py 和 pip 等工具。下面是 poetry 的简要教程说明,包括安装、常用命令和基本使用。\n1. 安装 poetry poetry 可以通过 python 包管理工具 pip 或官方安装脚本安装。\n# 使用 `pip` 安装\rpip install poetry\r# 使用官方安装脚本安装(推荐 🌟)\rcurl -ssl https://install.python-poetry.org | python3 - 安装完成后,你可以通过以下命令确认 poetry 是否安装成功:\npoetry --version\r# 查看当前配置\rpoetry config --list 2. 初始化 poetry 项目 ❶ 初始化新的项目\npoetry 可以用来快速初始化一个新的 python 项目。通过 poetry new 命令创建一个项目。\n1 poetry new my_project 这将创建一个新的 my_project 目录,包含一个基本的 python 包结构:\n/my_project\r/my_project\r__init__.py\rpyproject.toml\rreadme.rst\rtests\r__init__.py\rtest_my_project.py pyproject.toml 文件是 poetry 的核心配置文件,用于描述项目的元数据、依赖关系、构建工具等。\n❷ 初始化已有项目\n还可以通过 poetry init:初始化当前目录为一个 poetry 项目(创建 pyproject.toml)。\n3. 管理项目依赖 poetry 提供了非常简洁的命令来管理项目的依赖,包括安装、添加、删除等操作。\n❶ 添加依赖:\n使用 poetry add 来添加依赖,支持通过指定版本号来精确安装依赖。\n1 2 poetry add requests # 添加最新版本的 requests 库 poetry add requests@2.25.1 # 添加指定版本的 requests 库 你也可以添加开发依赖(如测试框架):\n1 poetry add --dev pytest ❷ 查看当前依赖:\n查看项目的所有依赖,可以使用:\n1 poetry show ❸ 删除依赖:\n如果需要删除某个依赖,可以使用:\n1 poetry remove requests 4. 虚拟环境管理 poetry 会自动为每个项目创建一个独立的虚拟环境来隔离项目依赖。你可以通过以下命令查看虚拟环境的路径:\n1 poetry env info 💡 这里有一点需要注意:\npoetry 的虚拟环境默认放在默认的全局缓存位置 :\nwindows: %localappdata%\\pypoetry\\cache\\virtualenvs\\ macos/linux: ~/.cache/pypoetry/virtualenvs/ 如果我们想要把它放在当前项目中,应该怎么做呢?\n运行以下命令:\n1 2 3 4 poetry config virtualenvs.in-project true # 再次安装依赖(有时,可能需要删除全局缓存) poetry install 虚拟环境会存放在项目根目录的 .venv 文件夹中。\n❶ 激活虚拟环境:\n你可以通过以下命令激活虚拟环境,进入虚拟环境后执行 python 或其他命令:\n1 poetry shell ⚠️ 这个命令在 windows 下不可用❗️\n不能用该怎么办呢?手动激活喽~ 通过脚本〔 推荐 〕,或者像下面这样手动创建虚拟环境。\n如果 poetry shell 无法正常工作,可以尝试手动创建虚拟环境并指定 python 版本。可以使用 poetry env use 来指定 python 版本:\npoetry env use python3.8 如果虚拟环境已经存在,但无法进入,尝试删除现有的虚拟环境并重新创建:\npoetry env remove python3.x # 根据需要选择 python 版本 poetry env use python3.8 # 再次创建新的虚拟环境 ❷ 退出虚拟环境:\n使用以下命令退出虚拟环境:\n1 exit ❸ 如何不激活虚拟环境?\n使用 poetry 默认会为每个项目创建一个虚拟环境,但这在某些情况下不是我们想要的,比如在个人主机中。那么如何设置默认禁用虚拟环境呢?\n方法¹ 全局配置禁用虚拟环境\npoetry config virtualenvs.create false\r# 验证配置,确保 virtualenvs.create 的值为 false\rpoetry config --list 方法² 项目级别禁用虚拟环境\n# 编辑 pyproject.toml\r[tool.poetry]\r[tool.poetry.virtualenvs]\rcreate = false\r# 验证配置,确保 virtualenvs.create 的值为 false\rpoetry config --local --list 方法³ 临时禁用虚拟环境\npoetry install --no-venv 禁用虚拟环境后,poetry 将直接使用系统的 python 环境。请确保系统的 python 环境干净且与项目依赖兼容,避免依赖冲突。\n如果需要重新启用虚拟环境,可以运行以下命令:\npoetry config virtualenvs.create true 🪧 无论什么时候,都可以方便地使用 poetry env info 显示虚拟环境的启用状态。\n5. 安装项目依赖 当你克隆一个使用 poetry 管理的项目时,可以使用 poetry install 来安装项目的所有依赖:\n1 poetry install 这会根据 pyproject.toml 文件中的依赖信息安装所有必要的包。\n6. 运行命令 poetry 允许你在项目的虚拟环境中直接运行 python 命令或其他工具。例如,如果你想运行一个 python 脚本,可以使用:\n1 poetry run python my_script.py 7. 构建和发布包 poetry 可以自动构建和发布 python 包到 pypi。要构建包,只需运行:\n1 poetry build 这会在 dist/ 目录下生成 .tar.gz 和 .whl 格式的包。\n发布包\n要将包发布到 pypi,首先需要配置你的 pypi 凭据:\n1 poetry config pypi-token.pypi \u0026lt;your-token\u0026gt; 然后运行:\n1 poetry publish --build 8. pyproject.toml 配置文件 pyproject.toml 文件是 poetry 项目的核心配置文件,包含项目信息、依赖、构建工具等。下面是一个简单的 pyproject.toml 示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [tool.poetry] name = \u0026#34;my_project\u0026#34; version = \u0026#34;0.1.0\u0026#34; description = \u0026#34;a simple example project\u0026#34; authors = [\u0026#34;your name \u0026lt;your.email@example.com\u0026gt;\u0026#34;] [tool.poetry.dependencies] python = \u0026#34;^3.8\u0026#34; requests = \u0026#34;^2.25.0\u0026#34; [tool.poetry.dev-dependencies] pytest = \u0026#34;^6.2\u0026#34; [build-system] requires = [\u0026#34;poetry\u0026gt;=1.0\u0026#34;] build-backend = \u0026#34;poetry.masonry.api\u0026#34; [tool.poetry] 部分定义了项目的基本信息。 [tool.poetry.dependencies] 定义了项目的运行时依赖。 [tool.poetry.dev-dependencies] 定义了项目的开发时依赖。 [build-system] 定义了构建系统,poetry 使用的是 poetry.masonry.api。 9. 常用命令汇总 poetry new \u0026lt;project-name\u0026gt;:创建一个新的项目。 poetry init:初始化当前目录为一个 poetry 项目(创建 pyproject.toml)。 poetry add \u0026lt;package\u0026gt;:添加依赖包。 poetry remove \u0026lt;package\u0026gt;:移除依赖包。 poetry install:安装依赖。 poetry update:更新依赖。 poetry shell:激活虚拟环境。 poetry run \u0026lt;command\u0026gt;:在虚拟环境中运行命令。 poetry build:构建包。 poetry publish:发布包到 pypi。 10. poetry 的优势 简化依赖管理:poetry 自动管理所有依赖和虚拟环境,无需手动编辑 requirements.txt。 一致性:poetry 使用 pyproject.toml 来定义依赖,避免了传统的 setup.py 和 requirements.txt 文件的不一致性。 集成的发布功能:poetry 让发布包到 pypi 变得非常简单,几乎不需要配置。 总结 poetry 提供了一种现代、简洁且强大的方式来管理 python 项目,它解决了传统 python 包管理工具的一些痛点,并使得依赖管理、虚拟环境、包构建和发布等变得更加简单和统一。如果你在开发中遇到问题,poetry 的文档非常详细,并且社区支持活跃,可以随时查询或寻求帮助。\n希望这个简要教程能帮助你更好地使用 poetry!如果有任何问题,欢迎随时提问!\n","date":"2025-01-20","permalink":"https://aituyaa.com/poetry-%E7%9A%84%E7%AE%80%E8%A6%81%E8%AF%B4%E6%98%8E/","summary":"\u003cp\u003ePoetry 是一个现代的 Python 包管理工具,它不仅能够管理依赖,还可以构建和发布 Python 包。Poetry 的目标是简化 Python 项目的管理,替代传统的 \u003ccode\u003erequirements.txt\u003c/code\u003e、\u003ccode\u003esetup.py\u003c/code\u003e 和 \u003ccode\u003epip\u003c/code\u003e 等工具。下面是 Poetry 的简要教程说明,包括安装、常用命令和基本使用。\u003c/p\u003e","title":"poetry 的简要说明"},]
[{"content":"附录 shell 中的 if\u0026hellip;else 基本语法如下:\nif [ 条件1 ]; then\r# 条件1为真时执行的代码\relif [ 条件2 ]; then\r# 条件2为真时执行的代码\relse\r# 条件为假时执行的代码\rfi 其中:\n[ 条件 ]:[ ] 是 shell 中的条件测试语法,注意括号内两侧需要有空格 ❗️ then:表示条件成立时执行的代码块开始。 else:表示条件不成立时执行的代码块开始。 fi:表示 if 语句的结束。 常用的条件测试符号有哪些?\n1. 数值比较\r-eq:等于(equal)\r-ne:不等于(not equal)\r-gt:大于(greater than)\r-lt:小于(less than)\r-ge:大于等于(greater than or equal)\r-le:小于等于(less than or equal)\r2. 字符串比较\r= :字符串相等\r!=:字符串不相等\r-z:字符串长度为 0(空字符串)\r-n:字符串长度不为 0\r3. 文件测试\r-f:文件存在且是普通文件\r-d:文件是否存在且为目录\r-e:文件或目录是否存在\r-r:文件是否存在且可读\r-w:文件是否存在且可写\r-x:文件是否存在且可执行\r-s:文件是否存在且大小大于 0 还有常用的逻辑运算符,如下:\n逻辑运算符\r- \u0026amp;\u0026amp;:逻辑与(and)\r- ||:逻辑或(or)\r- ! :逻辑非(not) 比如我们要判断一个文件是否存在,如下:\n1 2 3 4 5 6 7 8 9 #!/bin/bash file=\u0026#34;example.txt\u0026#34; # if [ ! -f \u0026#34;$file\u0026#34; ]; then 不存在 if [ -f \u0026#34;$file\u0026#34; ]; then echo \u0026#34;$file 存在\u0026#34; else echo \u0026#34;$file 不存在\u0026#34; fi shell 怎么获取当前目录并赋值给变量 1 2 3 4 5 6 7 8 9 #!/bin/bash # 获取当前目录并赋值给变量 current_dir=$(pwd) # 打印变量值 echo \u0026#34;当前目录是: $current_dir\u0026#34; # → 当前目录是: /home/user/projects 其中:\npwd 会返回当前工作目录的绝对路径,如果当前目录是 /home/user/projects,pwd 会输出 /home/user/projects; $(pwd) 会将 pwd 命令的输出结果捕获,并赋值给变量。 如果只想获取当前目录的名称(而不是完整路径),可以使用 basename 命令:\n1 2 3 4 current_dir_name=$(basename $(pwd)) echo \u0026#34;当前目录名称是: $current_dir_name\u0026#34; # → 当前目录名称是: projects ","date":"2025-01-11","permalink":"https://aituyaa.com/shell-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"","title":"shell 那些事儿"},]
[{"content":"经过一段时间地体验后,最终选择了小鹤双拼,无它,体验好 🥳\n📘 小鹤双拼 - 官方网站\n说在前面的 纯双拼的话很容易学习,10 分钟左右就差不多了。怎么?不相信?去试一下就知道了。\n看看下面这张图:\n\u0026gt; 小鹤双拼键位图\n![[assets/pasted image 20250110153326.png|550]]\n官网也提供了口诀,但其实不一定非得按着它来。实际上我更推荐你自己给每一个键联想几个字,在这个过程中自然而然就记住了。\n比如,q 拥有 q 和 iu ,这不就是个 球 字吗?再看一个,x 拥有 ia 和 ua ,这不就是 下滑 吗?(其实几乎所有双拼方案中,ia 和 ua 这两个韵母都是在同一个键上)其它同理。\n所有的双拼方案都源自「 自然码方案 」。\n我们再来看一些它们的共性,几乎在所有的方案中,u、i、v 都对应 sh、ch、zh 。剩下的就没有多少东西了。对了,还有一个单韵母的规则,如 啊 对应的是 aa,即双韵母成字。类似的还有 oo - 哦、ee - 额 等。\n下面我们主要来看一下鹤形,因为我们要使用它作为辅助码。如果你对辅助码感兴趣的话,在 万象拼音 中提供了更多选择,如墨奇、自然码、五笔前二、虎码首末、汉心码等。\n当你了解更多一些之后,你也许会问为什么不直接学习小鹤音形呢?因为我已经是一个好多年的五笔 86 使用者,没有动力再去学习一个形码方案了(尽管我已经体验过了虎码和小鹤双形)。\n小鹤双形 双形即从每个字中提取首末两部分形态各异的组字单元,以区分同音字。\n鹤形包含以下三类:\n\u0026gt; 鹤形分类\n![[assets/pasted image 20250110163318.png|650]]\n部件字根,基本为偏旁部首,日常称谓定其键位,必须掌握。\n小字字根,基本为小且独立的字,规则推导字根,按其声母定键,理解为主。\n……\n好嘛,我是实在没有动力再去学习一个形码了,哪怕是用来做辅助码 🤣,有学习双形需求的朋友直接去 小鹤入门 · 双形 学习喽~\n结语 小鹤是一个相当不错的双拼方案,值得一学。\n对于五笔使用者来说,五笔前二是真的香 🎉。我一直想为什么其它辅助码都是取首末,而只有五笔取前二呢?仔细一想不难理解,五笔的末笔识别码重的太多了。\n🔔 输入法和码表这种玩意儿真的是太费时间了。\n","date":"2025-01-10","permalink":"https://aituyaa.com/%E5%B0%8F%E9%B9%A4%E5%8F%8C%E6%8B%BC%E5%8F%8A%E5%BD%A2%E8%BE%85/","summary":"\u003cp\u003e经过一段时间地体验后,最终选择了小鹤双拼,无它,体验好 🥳\u003c/p\u003e","title":"小鹤双拼及形辅"},]
[{"content":"说是“输入法那些事儿”,其实主要就是 rime 那些事儿。商业输入法不怎么用,也就没有什么太多好说的,不过做为参照物可能会略有提及。\n:: 目前可以看成是 rime home wiki 的汇总。 \u0026ndash; 🤣 好嘛,经过两三天的噼里啪啦,终于整合在了一起。原 wiki 毕竟是 wiki ,内容有些凌乱,后续有时间再慢慢整理、删减、补充吧。\ntodo rime 设计思想 rimewiththedesign · rime/home wiki lua 脚本开发指南 scripting · hchunhui/librime-lua wiki rime 佛振的序 认识 la rime。\n自序 做这个项目是发自对输入法创新的兴趣,为实验新的输入法而打造一款易于定制的智能输入软件。实现好用的方言拼音输入,也是开发这款软件主要的目标。\n乃定名为:中州韻輸入法引擎 / rime input method engine\n取义历史上通告的中州韵,愿写就一部汇集音韵学智慧的输入法经典之作。\n项目网站设在 http://rime.github.io/。\n感谢 github ,感谢 pages。\n创造应用价值是一方面,更要坚持对好技术的追求,希望能写出灵动而易于扩展的代码,使其成为一款修改十足的开源输入法。\n历史 2009 年底,佛振按这一思路,借助强大的 ibus 输入法框架和 python 脚本语言,快速开发出「中州韵」的原型。 2010 年,将这一算法引擎由 linux 移植到 windows 平台。 几大方言社区的输入法爱好者创作了粤语、吴语、中古音输入方案,竟做到了用曾经梦想的方式打字。\n如今有许多朋友关注并乐於帮忙完善这一软件。俺打盘接受同学们的建议用 c++来写一部正式版。\n概念 相对于最初的实验品,概念没有多大变化。佛振重新来归纳。\n组成这个软件系统的对象,我给他粗略分成三类: 逻辑对象、数据对象、交互对象。\n❶ 逻辑对象\n逻辑对象,各自表达解决输入法中某类问题的算法,以及描述输入法的工作流程。\n咱假定,从不同种类的输入法中,可归纳出几种类型的实现机制,即通用于一类输入法的算法和数据结构。\n输入法引擎/engine 佛振把他解读为用来实现输入功能的程序,是这些算法及相关数据结构的总和。\n输入法典型的工作流程,大致如此:\n获取并解释按键动作,每个按键包含键值、按键的状态等信息; 生成、分析按键序列,形成编码串;某些按键并非直接產生编码字符,而有时组成输入串的文字不是按键所对应的字符本身; 查字典,取得输入码对应的同码字词列表;合并、排序…… 有时,根据策略需要进一步组词造句; 可能会对结果做出场前的修饰处理,如繁简转换等; 至此,完成了从输入码到文字的翻译,结果是一组将在输入法介面展现的候选文字; 用户确认,文字上屏,完成了一次输入。 将这一流程中纯粹的逻辑部份用程序写出来,就是我所形容的输入引擎。\n其中不包括:\n实现编码到文字转换的字典数据,许多方家称「码表」; 经过操作系统与设备和输入目的程序交互的组件; 展现输入法信息的界面; 配置工具。 ❷ 数据对象\n输入法中的数据对象,有输入引擎处理用户输入动作所得的动态数据,又有预先配置到输入法中的输入方案。\n若要讲,输入引擎是跨输入法的通用程序,输入方案/schema 即是那差异的部份。 输入法引擎配置了不同的输入方案,便是用户视角下、统一框架内的不同输入法。 输入方案按一定的规格撰写,用户可於需要时导入到软件,这便是本项目软件开发者与输入方案创作者分工、协作的方式。\n输入方案包含:\n配置信息,控制着输入引擎的行为; 字典(码表),定义了编码与候选文字的对应关系。 为了足够灵活而能支持广泛的输入法类型,在输入方案中,利用 拼写运算/spelling algebra 机制在输入码与字典编码之间建立一组映射,以此将个别方案中的特殊检索方式统一到通用的算法。\n❸ 交互对象\n交互对象,承担与用户交换信息的功能。不同於输入引擎、输入方案的跨平臺特点,交互对象的实现是系统相关的。 具体地有,输入法框架通过操作系统与输入设备、输入目标程序通信;输入法介面显示输入法的状态和输入内容。\n项目构成 于是整个工程又可分为若干子项目:\nlibrime-输入法引擎; ibus-rime-linux 发行版; weasel/小狼毫-windows 发行版; squirrel/鼠须管-mac 发行版; plum/东风破-配置管理器及输入方案仓库; essay/八股文-预设词典及语言模型。 开发计划 伟大雄图,还是分期来完成吧。\n初创期,完成软件架构和基础功能。\n第一期,要把用户体验做到一款正式產品的标準。\n易用性:操作方式简明,有文档,有配置工具 性能和稳定性:适应主流配置+日常应用场景 输入效果:优於传统的码表输入法平臺,达到开源输入法的平均水平 第二期,兼容更多系统平台。可以尝试一些创新的设计,形成开发者暨输入方案创作者社群。\n第三期,添加网络功能,持续优化输入效果;建立输入法创作平臺。\n计划於八月初八发表 1.0 版。即完成初创期,进入第一期的初级阶段。\n万事开头难,虽然实验版的经验可做参考,让这项目高速运转起来,仍需要大的智慧。\n有同学索要文档,却真的没有。千头万绪,未及梳理,暂且概说一番,也可意会创作此软件的思路。\n是为之序。\nrime 架构设计解读 概念解读 ❶ 框架\n输入法工作于需要输入文本的程序中,后者就叫它做输入法的「 客户程序 」吧。\n如果输入法同时在不同的程序中工作,每个客户程序里的输入内容都是不同的。所以需要每个客户维护输入法状态,术语称输入法「 上下文 / context 」。\n逻辑复杂的输入法,常常有个独立的程序来做运算,便于集中管理词库等资源。rime 称其为「 服务 / service 」,形式为一个无界面表现的后台程序 / backend。\n于是输入法的「 前端 / frontend 」,即输入法在客户程序中的那部分,可依托于后台的服务来运作,自身只关注与操作系统交互,以及将消息向服务转发。\nrime 的 frontend / backend 模型,依照 ibus、imk 等输入法框架来设计:frontend 不含输入逻辑,甚至不负责绘制输入法界面。因此会比较容易适配现有的输入法框架,不需要自己写很多代码。\n在服务中,会为每一个客户建立一个输入法「 会话 / session 」。从功能上讲,会话是将一系列按键消息变(convert)为文字的问(request)答(response)过程。\n技术上讲,会话会负责搞定输入法前端与服务之间的跨进程通信,同时在服务端为前端所代表的客户分配必要的资源。这资源主要是指一部有状态的、懂得所有输入转换逻辑的输入法引擎「 engine 」。\n现在所写的 rime 库,不做那框架部分,专注于输入引擎的实现。名字也叫「 中州韵输入法引擎 / rime 」嘛。\n这部分定义的代码是 rime/librime ,under c++ namespace rime。抛开实际的输入法框架,俺先写个控制程序 rimeconsole 来模拟输入,观察 engine 的输出,以验证其功能是否符合设计。\n❷ 引擎\n输入法的转换逻辑自然是比较复杂,需要许多元件协同工作。要协同工作,必得组装到一起,外面再加个壳。通常的机器都是如此,有外壳做封装,看不到内部的元件,通过有限的几处开关来操作,用起来比较省心。\nengine 对象,便是 session 需要直接操作的界面。除此之外,rime 库还对外提供若干用于输入输出的数据对象。包括它所持有的代表输入法状态的「 上下文 / context 」对象、描述一份「 输入方案 / schema 」的配置对象、代表输入按键的 keyevent 对象等。\n开发者对 engine 的期望,一是将来可不断通过添加内部组件的方式增益其所不能,二是有能力动态地调整组件的调度,以在会话中切换到不同的输入方式。\n为了避免知道得太多,这引擎的内部构造必须精巧,他存在的意义在于接合内部的各种组件,并对外提供可靠的接口:engine 所表达的逻辑仅限于此。\n标准化 engine内部的「 组件 / component 」,明确与关键组件之间的对接方式,就可以满足可扩展、可配置这两点期望。\n设计与实现 ❶ 组件\n为有好的扩展能力,定义一个接口来表示具有某种能力的一类组件;为了达到可在运行时动态配置的目的,采取抽象工厂的设计模式。\nboost::factory 要求较高版本的 boost 库,所以我还是用了自家酿造的一套设施来做组件的包装。\n原则是,rime 中完成特定功能的对象,若只有一种实现方法,就写个 c++类 / class ;若有多种实现方法,就写个「 rime 类 」/ rime::class 。如:\n1 2 3 4 5 6 7 class processor : public class\u0026lt;processor, engine*\u0026gt; { public: processor(engine *engine) : enging_(engine) {} virtual bool processkeyevent(const keyevent \u0026amp;ke); private: engine *engine_; }; 然后,大家就可以写各式 processor 的实现啦…… 当然,还要把每一种实现注册为具名的「 组件 」。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class touppercase : public processor { public: touppercase(engine *engine); virtual bool processkeyevent(const keyevent \u0026amp;ke) { char ch = ke.toascii(); if (islower(ch)) { engine_-\u0026gt;commitchar(ch - \u0026#39;a\u0026#39; + \u0026#39;a\u0026#39;); return true; } return false; } }; void registerrimecomponents() { registry.instance().register(\u0026#34;upper\u0026#34;, new component\u0026lt;touppercase\u0026gt;); // 注册到各种组件 registry... } 用法:\n1 2 3 4 5 6 7 8 9 10 11 12 void usingaprocessor() { // class\u0026lt;\u0026gt; 模板提供了简便的方法,按名称取得組件 // class\u0026lt;t\u0026gt;::require() 从 registry中取得 t::component 的指針 // 而 component\u0026lt;t\u0026gt; 继承自 t::component,并实现了其中的纯虛函數 create() processor::component* component = processor::require(\u0026#34;upper\u0026#34;); // 利用组件生成所需的对象 keyevent key; // 输入 engine engine; // 上屏文字由此输出 processor* processor = component-\u0026gt;create(\u0026amp;engine); bool taken = processor-\u0026gt;processkeyevent(key); delete processor; } 实际的代码中,会将这个例子改造成 engine 持有 processor 对象,并转发输入按键给 processor。\n❷ 框架级组件和基础组件\n以 engine 的视角,可以把各种组件分为框架级组件和基础组件。前者会由 engine 创建并直接调用,故需要每一类组件定义明确的接口。后者作为实现具体功能的积木,由框架级组件的实现类使用。\n¹ 框架级组件\n目前设计中规划了三类框架级组件:\nprocessor,处理按键,编辑输入串; segmentor,解释输入串「 是什么 」,将输入串分段,标记输入内容的可能类型; translator,翻译一段输入,给出一组备选结果。 对于每一类组件,engine 将根据 schema 中的配置,创建若干实例,并按一定规则调度,从而将复杂的输入法逻辑分解到组件的各种实现里,按输入方案的需求组合。\n当 engine 接到前端传来的按键消息,会顺次调用一组 processor 的 processkeyevent() 方法。在这方法里,每个 processor 可决定是接受并处理该按键,放弃该按键还是给系统做默认处理,还是留给其他的 processor 来决定。\nprocessor 的处理结果表现为对 context 的修改。当某个 processor 修改了 context 的编码串时,engine 获得信号通知,开始切分和翻译的流程。\n首先按次序由各 segmentor 当尝试对编码串分段。通过 n 个回合,将编码串分为 n 个编码段。每一回合中,各 segmentor 给出从编码串指定位置开始可识别的最长编码序列,及对应的编码类型标签。回合中最长的分段将将被采纳,将该编码段的始末位置及一组编码类型标签记入 context 中的分段信息。优先级较高的 segmentor 也可以中止当前回合,从而跳过优先级较低的 segmentor。\n接下来,对每一个编码段,调用各 translator 做翻译。translator 可凭借编码类型标签来判断本 translator 能否完成该编码段的翻译。\n每个 translator 针对一个编码段的翻译结果为 translation 对象,是用来取得一组候选结果的迭代器。同一编码段由不同 translator 给出的 translations,存入 menu 对象。为了在有大量候选结果的情况下保持效率,menu 仅在需要时,譬如向后翻页时,才会从 translation 中取得一定数目的候选结果。translation 有和其他 translation 比较的方法,用于根据下一个候选结果的内容,或某种预定的策略来决定候选结果的排序。\ncontext 中的 composition,汇总了分段信息、使用者在各代码段对应的 menu 中所选结果等信息。经过使用者手动确认结果、且未发生编辑动作的编码段,将不再重新分段的处理。\n² 基础组件\ndictionary ,词典,由编码序列检索候选结果; userdictionary,动态的用户词典; prism,音节拼写至词典编码的映射; algebra,拼写运算规则; syllablifier,音节切分算法。 说明书📖 ❶ 专题:\n认识 la rime - [[#rime 佛振的序]] 下载与安装 定制指南(初阶)- [[#定制指南详解]] 输入方案设计书(中阶) - [[#rime 输入方案]] → [[#标准库]] 程序开发(高阶) ❷ 第三方文档\n鼠鬚管試玩心得/lionhoho 致第一次安裝 rime 的你/半月湾c 「鼠须管」配置方案分享、 「鼠须管」的调教笔记/scomper 鼠须管输入法 傻瓜版配置 - 基于 rime_pro 增强包/changsj ❸ 使用方案选单\n下载、安装完成后,试试:\n切换到 rime 输入法,按 f4 键或组合键 ctrl+ 唤出输入方案选单( 键常见於 1 的左方)。这里有 rime 输入法最常用的选项。\n您可立即从方案选单切换到已经启用的输入方案。\n另见:从 rime 输入方案仓库 获取输入方案\n如果需要启用其他已安装的方案,或自定义方案,请……\n❹ 输入法设定\n——参阅《定製指南》,从中瞭解如何自定义方案选单、每页候选个数等全局选项;修改界面外观;以及调整输入方案的工作方式如指定选字、换页的按键、标点符号映射等。\n目前仅【小狼毫】配有一组简单的设定面板,包含输入方案选单设定、界面风格设定、用户词典管理。其他发行版可通过修改配置文件后重新部署的方式来定製。\n打字入门 以下操作多数是可定製的,此说明书以默认配置为準。\n❶ 选字与换页\n使用 ↑↓键 定位高亮的候选字,以空格键确认。\n在【语句流】风格的输入方案里,以空格键确认后,字词并不立即上屏,而是在输入了句末的标点(,。?!等)或按下回车键(return)时整个句子上屏。\n通常,按数字键可选择序号为该数字的候选;但某些输入方案会以其他按键代替数字键,如【注音】以大写字母选字。\n除了可用标準的 page up、page down 换页之外,大多数输入方案里还设置了以下几组等效的换页键:\nshift+tab、tab 符号键 - = 符号键 , . ❷ 令输入码直接上屏\n输入编码后,如果不需要候选字而是希望输入码对应的键盘字符直接上屏,可使用回车键(return)。\n注意:【语句流】输入方案不适用。在【语句流】模式下,先按左 shift 键切换为西文编码,再按回车键确认。\n若要输出经过变换的输入码,如带声调的地球拼音、注音符号、仓頡字母等,请在输入编码后按 shift+回车键 或 control+回车键。\n❸ 使用编辑键\n使用 ←→ 键 定位光标插入点「‸」(或显示为「›」),编辑输入码。 也可用来缩短后选词所对应输入码的范围、确认词句的一部分。\nhome、end 键 快速跳至句首、句末。\nbackspace、delete 键 分别删除光标前、后的编码字符。注意在苹果键盘上前者标註为 delete,后者通常是组合键 fn+delete。\nescape 键 清空未完成的输入。\n❹ 删除误上屏的错词\n不慎上屏了错误的词组,再打同样的编码时,那错词出现在候选栏,令有洁癖的同学十分不爽。这时候可以:\n先把选字光标(用上、下键)移到要删除的用户词组上,再按下 shift+delete 或 control+delete(苹果键盘用 shift+fn+delete)。\n只能够从用户词典中删除词组。用於码表中原有的词组时,只会取消其调频效果。\n❺ emacs 风格的编辑键\n註:windows 版本 alt 组合键不可用。\n↑:control+p\r↓:control+n\r←:control+b\r→:control+f\r上页:alt+v\r下页:control+v\r句首:control+a\r句末:control+e\r回退:control+h\r删除:control+d\r清空:control+g\r删词:control+k ❻ 输入标点符号\n按键到标点符号的映射有三种形式:\n按键对应惟一的符号,按键后直接输出该符号,如「,」 按键对应一组配对的符号,符号交替出现,如「“”」 按键对应多种符号,按键后展现选单。此时可按空格键或回车键确认高亮的符号,反复按该键则选中下一种符号。 每一款输入方案,都可以定义两套符号表,以「方案选单」里的选项「半角←→全角」往复切换。\n❼ 中西文切换\n打开「方案选单」,使用选项「中文←→西文」可在两种转换状态间往复切换。\n此外,默认可用左右 shift 键快速切换。\n在输入了部分编码的情况下,左 shift 将这些编码临时转还为西文。编辑临时转换的西文并以回车键上屏后,自动回复中文状态。\n在输入了部分编码的情况下,右 shift 将上屏当前的候选字,并进入西文模式。\n以上可总结为:左 shift 切换输入法光标左面的编码内容,右 shift 切换光标右面即将输入的内容。\n注意:mac 系统上的鼠鬚管不能区分左、右 shift ,因此左、右 shift 键的作用一样。\n❽ 繁简字切换\nrime 输入法词库多以传统汉字编排。\n因为有 opencc 提供準确而高效的繁→简转换功能,大多数输入方案都可以从「方案选单」里选择「汉字←→汉字」的选项来启用或停用繁简转换。\n码表为简化字的方案如【五笔】、【袖珍简化字拼音】等,不提供这个选项。\n部分输入方案的用法 ❶ 朙月拼音\n与时下流行的拼音输入法相近。默认安装后将使用此方案。 其词典包含 rime 内置的【八股文】繁体词库。设有繁简转换的开关。\n敲 ~ 键开始用 hspnz 输入五笔画(横竖撇捺折),反查五笔编码。需要安装【五笔画】以启用反查。\n以下有若干输入方案衍生自朙月拼音,如【语句流】、各式双拼、【宫保拼音】。还有不少方案使用朙月拼音作反查码。\n《定製指南》提供了模糊音定製方法及代码模板。\n❷ 语句流\n揉合了语句输入的按键习惯,以及词组输入逐词确认的操作节奏。\n句中空格键的运用,既起到明确词边界、弥补长句转换精度不足的作用,又很好地模仿了西文以空格断词的惯用法。\n配合 mac、ibus 版本内嵌预编辑文字的特性,体验更佳。\n❸ 双拼\n基於【朙月拼音】製作了几种流行的双拼方案:自然码、mspy、智能 abc、小鹤双拼、拼音加加等。\n双拼与【朙月拼音】、语句流输入方案共用一份码表和用户词典。\n实现双拼的关键,是用 rime 的「拼写运算」技术建立双拼输入码到全拼的映射表,从而对接朙月拼音词典。在《输入方案设计书》中有一个完整的代码示例。\n❹ 宫保拼音\n仍是基於【朙月拼音】,採用源自西方速录技术的多键并击方式,双手共七指操作键盘,一击输入一个拼音音节。\n宫保拼音 / combo pinyin 自从 2007 年创製以来,经歷十数次修订,孜孜以求一套直观易用,并且兼顾 pc 键盘上操作舒适度的键位佈局。\n宫保拼音专题介绍\n❺ 地球拼音\n支持标註声调的拼音输入法。\n以形似的符号 - / \u0026lt; \\ 附加在拼音音节的末尾,分别表示阴平、阳平、上声、去声。轻声不标。\n为按键方便故,也可使用 ; / , \u0026gt; 来标註。\n声调、韵母在输入常用短语时可以省略,而成为简拼。因此本方案既兼容了无声调拼音输入法的打字习惯,又可在输入生僻字词时通过输入声调筛选出同音同调者。\n因为对应相同的音系,【地球拼音】与【注音】共享词典,却不与【朙月拼音】相通。\n❻ 注音\n注音符号输入法。使用最广泛的「大千式」键盘排佈。\n但其操作层面不同於传统的注音输入法,而是採取「无模式」的设计,与 rime 里的其他输入方案达成统一。 所谓「无模式」,是指输入注音码,会即时显示出候选字,而不似传统的注音输入法需要另行呼出选字窗口。\nrime【注音】输入方案,允许省略声调(包含作为第一声的空格键)以及某些音节(非零声母且声母不能自成音节者)的韵母部分。 当省略声调时,可连续输入后一个音节,或间以隔音符号「 \u0026rsquo; 」表明前一音节结束。\n此外,【注音】採用了语句流的按键习惯,以回车键确认上屏;在输入了声调的音节后,以空格键选定高亮的字词。\n为了在输入注音码的同时展现候选字,候选的序号改为大写字母 abcde 等,以避开定义为注音符号的数字键。 要选用当页第二个候选字,可按↓键、再按空格键选定第二字,又可按下 shift+b 键直接选取。\n一些标点符号由於所在按键用作输入注音符号的原因而有所调整:\n逗号, shift+, 句号。 shift+. 分号; shift+; /号 shift+/ 减号 shift+- 规则很简单:符号键被注音字母佔用时,加打上档键 shift 从符号列表中选取。 ❼ 仓頡\n第五代仓頡输入法。\n默认,通过仓頡码检出的候选字限制在「通用字符集」,排除了 unicode cjkv 扩展区汉字。\n需要输入大量古字、生僻字的专业用户,请在「方案选单」中切换一次形如「通用 → 增广」的字符集过滤开关,即可输入七万餘简繁汉字。\n註:您的系统可能需要安装专门的字体方可正确显示汉字全集。参考《定製指南》为 rime 输入法设定用於显示候选文字的字体。\n以拼音输入单字、常用词组,可反查仓頡码。需要安装【朙月拼音】以启用反查。仓頡码与拼音重码时,仓頡码查到的候选字优先。 亦可按引导键 ~ 进入拼音反查。\n看到候选字一旁有「阴阳鱼 ☯」记号?\n:-d 这表示,该候选词组并不存在於码表中,而是通过「混元编码器」產生、或由已知字码自动组合而成的结果。您需要确认一下这是否恰是你想要的词。 rime 的后续版本将不断改进算法,在输入码未命中码表时,推测出更有意义的文字,越来越「懂我心意」。\n❽ 速成\n取仓頡首尾二码,简单快速地连续输入词、句,且支持仓頡全码、速成码混合输入。编码虽是基於字形,智能组词、调频等特性与拼音输入法别无二致。\n❾ 五笔\n五笔字型 86 版。简化字优先,兼收繁体字。包含小规模的词库。\n敲 z 键开始转为简体拼音字词输入,反查五笔编码。需要安装【袖珍简化字拼音】以启用反查。\n另有【五笔·拼音】混合输入的方案。在这款方案里,可直接以五笔或拼音码输入字、词而无须转换。输入拼音码时,若候选字、词编入了五笔码表,则在候选字一旁提示其五笔码。\n❶⓿ 粤拼\n以「香港语言学学会粤语拼音方案(粤拼)」输入广府话。\n敲引导键 ~ 之后,开始输入汉语拼音(普通话)反查粤拼。需要安装【朙月拼音】以启用反查。\n输入不完整的粤拼编码、或当拼写错误被自动纠正时,候选字、词加註完整的粤拼编码提示。 (注意:由於码表并未全部经过人工校对,程序自动注音的结果可能使提示中多音字的标註出现错误!请不要把输入法的提示当作标準学习而受误导!)\n❶❷ 吴语\n以「吴语拉丁式注音法」输入上海话、苏州话。\n敲引导键 ~ 之后,开始输入汉语拼音(普通话)反查吴语拼写。需要安装【朙月拼音】以启用反查。\n《定製指南》提供了模糊音定製方法及模板。\n用户词典管理 从【小狼毫】开始菜单或托盘图标的右键菜单中打开「用户词典管理」介面。\n左侧列表为已使用的用户词典,以词典名表示,如 luna_pinyin 。 注意词典与输入方案可能是一对多的关系。\n❶ 命令行工具\n【中州韵】和【鼠鬚管】暂无图形工具,可取得 librime 编译產出的工具程序 bin/rime_dict_manager 通过命令行方式实现以下功能。\n执行 rime_dict_manager 之前需要关闭正在使用的输入法,释放以独佔方式打开的词典文件。\n请,将工作目录设为「rime 用户资料夹」。在此执行 rime_dict_manager 查看所支持的参数及命令格式;加上参数 \u0026ndash;list 查看用户词典列表。\n❷ 备份及合併词典快照\nrime 输入法在使用中会在一定时间自动将用户词典备份为快照文件 *.userdb.txt 。 也可使用管理工具,备份指定的用户词典到快照文件。\n备份到快照中的打字习惯,可回复到新建立的或其他系统上的用户词典。\n执行合併词典快照操作,只须选定要合併的 .userdb.txt 文件,因为快照中已记录了所属词典的名称。\n你不必担心来自快照的词条已存在於用户词典的情况;词频更新为二者的较大值,其他参数亦会按照合理的算法叠加。\n❸ 导出及导入文本码表\n将用户词典导出为文本码表,便於直接阅读,或批量添加词条到 *.dict.yaml 。\n导出的文本格式与 rime 静态码表的格式相同:以製表符(tab)分隔的三列,分别是文字、编码、使用频次。其中,编码是码表中定义的完全形式,多个音节间以空格。\n如此导出的文本码表,可反向导入到指定的用户词典。但是,文本码表所含信息不如快照全面,因此为了不损失输入效果,请儘量使用 合入快照 的方式转移用户资料。\n由於文本码表用户可以编辑,导入时无法检查码表内容是否与选定的词典相匹配,请 当心 莫要导入到错误的词典中去了。\n❹ 导入其他来源的码表\n推荐的作法是,将码表导入到固态词典,而非用户词典。\n请参考以下两个示例:\nhttps://gist.github.com/lotem/5443073\nhttps://github.com/rime-aca/dictionaries\n特别地,如果要用新码表的内容完全取代原有的 luna_pinyin 词典,则无须以上步骤: 将编写好的新词典命名为 luna_pinyin.dict.yaml 放置於 rime 的「用户文件夹」,重新部署即可。\n如果你认定要将码表导入已存在的用户词典,则要注意:\n码表文件的格式是否 rime 所要求的 utf-8 (no bom) 编码 文本行是否为製表符分隔,至少有文字、编码两列,及一列可选的频次 文字的字形(繁、简字)是否与源码表一致 编码的形式是否源码表中定义的标準形式 第一点,有一臺专业的文本编辑器就控制住啦。比如 vim 里面用命令\n:set fenc=utf8 nobomb ff=unix 转换、保存文件就中啦。\n第二点,如果来源文件的资料格式不同,就需要藉助 regex 批量替换的操作,或写脚本来完成转换。\n只有词条、没有编码?请重新考虑先时我提出的建议。因为,製作固态词典,可以利用【八股文】,以及自动编码器。\n第三点,如果字形与目标词典不一致,推荐用 opencc 完成码表的繁、简转换。\n第四点,凡是编码为源码表中未出现过的形式,如通过「拼写运算」实现的简拼、异拼,又如编码中的拼写错误,都将导致该条记录成为用户词典中的无效数据,因为无法通过正常的输入检索到。\n❺ 同步用户资料\n藉助移动存储设备,或在线存储服务如 dropbox,在多臺电脑及不同系统之间同步用户词典和用户设定。\n¹ 设定同步位置\n默认地,词典快照备份到 rime 用户文件夹\\sync\\uuid 这个地方。如果你要用 dropbox 或 u 盘在不同机器/系统之间同步用户词典,则需要设定同步的目标文件夹,如 d:\\dropbox\\rimesync 。\n直接编辑用户文件夹下的 installation.yaml ,添加:\nsync_dir: \u0026#39;d:\\dropbox\\rimesync\u0026#39; 又如 mac 上添加:\nsync_dir: \u0026#39;/users/fred/dropbox/rimesync\u0026#39; 又如使用 usb 存储来同步:(真实案例)\nsync_dir: \u0026#39;/volumes/usbdrive/rimesync\u0026#39; 默认地,每套 rime 会随机生成一个 uuid 作为标识。不同 installation id 可区分来自不同机器/系统的用户词典。\n与安装在其他系统上的 rime 同步后,同步文件夹呈如下佈局:\nd:\\dropbox\\rimesync\\id-xxx\\luna_pinyin.userdb.txt\rd:\\dropbox\\rimesync\\id-xxx\\terra_pinyin.userdb.txt\rd:\\dropbox\\rimesync\\id-xxx\\installation.yaml\rd:\\dropbox\\rimesync\\id-xxx\\default.custom.yaml\rd:\\dropbox\\rimesync\\id-xxx\\weasel.custom.yaml\rd:\\dropbox\\rimesync\\id-yyy\\terra_pinyin.userdb.txt\rd:\\dropbox\\rimesync\\id-yyy\\installation.yaml\rd:\\dropbox\\rimesync\\id-yyy\\default.custom.yaml\rd:\\dropbox\\rimesync\\id-yyy\\squirrel.custom.yaml\rd:\\dropbox\\rimesync\\id-zzz\\luna_pinyin.userdb.txt\rd:\\dropbox\\rimesync\\id-zzz\\installation.yaml\rd:\\dropbox\\rimesync\\id-zzz\\alternative.yaml\rd:\\dropbox\\rimesync\\id-zzz\\luna_pinyin.custom.yaml 同步时,依次将各子文件夹中的词典快照合併到用户词典,最后为合併后的用户词典生成一份新的快照文件。 另外,还会把用户文件夹中非自动生成的 yaml 文件及 .txt 文件单向 备份 到同步文件夹。\n有些特别讲究命名的用家,不喜随机生成的 uuid,可编辑 installation.yaml ,取一个有意义的 id,如:\ninstallation_id: \u0026#39;fred-win7-desktop\u0026#39; 又如:\ninstallation_id: \u0026#39;fred-macbook\u0026#39; 当心!因为 rime 要以这个 id 为名创建文件夹,因此 id 不得包含(所有涉及同步的文件系统)文件名中非法的字符;建议不要用中文,只用小写字母、数字、横线和下划线。\nrime 输入方案 rime 是什么呢? rime 不是一种输入法。它是从各种常见键盘输入法中提炼出来的抽象的输入算法框架。因为 rime 涵盖了大多数输入法的「 共性 」,所以在不同的设定下,rime 可以化身为不同的输入法来打字。\nrime 输入法方案又是什么?\n要让 rime 实现某种具体输入法的功能,就需要一些数据来描述这些输入法以何种形式工作。即,定义该输入法的 「 个性 」。\n如 「 汉语拼音、注音、仓颉码、五笔字型 」,这些方法可凭借 rime 提供的通用设施,给定不同的工作参数来实现。以本文介绍的规格写成一套套的方案,就是 rime 输入方案。\n文本为王。 rime 的配置文件、输入方案定义及词典文件,均为特定格式的文本文档。rime 中所有文本文档,均要求以 utf-8 编码,并建议使用 unix 换行符 lf 。\n鉴于一些文本编辑器会为 utf-8 编码的文件添加 bom 标记,为防止误将该字符混入文中,不要从文件每一行开始正文,而应该在该行行首以 # 符号起一行注释。如:\n1 2 3 # rime default settings # rime schema: my first cool schema # rime dictionary: lingua latina 也可继续以注释行写下方案简介、码表来源、制作者、个性记录等信息,再切入正文。\n必知必会 rime 输入法中,多用扩展名为 .yaml 的文本文档,这是以一种可读性高的数据描述语言 yaml 写成。\n你可以访问 yaml文档 了解 yaml 文档格式。正文只对部分语法作简要说明,而将重点放在对语义的解读上面。\nrime 输入方案亦会乃至正则表达式实现一些高级功能。它采用 perl 正则表达式语法,了解更多 perl 正则表达式。\nrime 中的数据文件分布及作用 除程序文件以外,rime 还包换多种数据文件。这些数据文件存在于以下位置:\n共享文件夹\n【中州韵】 /usr/share/rime-data/ 【小狼毫】 \u0026quot;安裝目錄\\data\u0026quot; 【鼠须管】 \u0026quot;/library/input methods/squirrel.app/contents/sharedsupport/\u0026quot; 用戶文件夹\n【中州韵】 ~/.config/ibus/rime/ (0.9.1 以下版本为 ~/.ibus/rime/;fcitx5 为 ~/.local/share/fcitx5/rime/) 【小狼毫】 %appdata%\\rime 【鼠须管】 ~/library/rime/ 共享文件夹包含预设输入方案的源文件。这些文件属于 rime 所发行软件的一部分,在访问权限控制较严格的系统上对用户是只读的,因此谢绝软件版本更新以处的任何修改 \u0026ndash; 一旦用户个性这里的文件,很可能影响后续的软件升级或在升级时丢失数据。\n在 部署 操作时,将用到这里的输入方案源文件、并组合用户定制的内容来编译预设输入方案。\n用户文件夹 则包含为用户准备的内容,如:\n〔全局设定〕 default.yaml 〔发行版设定〕 weasel.yaml 〔预设输入方案副本〕 \u0026lt;方案标识\u0026gt;.schema.yaml ※〔安装信息〕 installation.yaml ※〔用户状态信息〕 user.yaml 编译输入方案所产出的二进制文件:\n〔rime 棱镜〕 \u0026lt;方案标识\u0026gt;.prism.bin 〔rime 固定词典〕 \u0026lt;词典名\u0026gt;.table.bin 〔rime 反查词典〕 \u0026lt;词典名\u0026gt;.reverse.bin 记录用户写作习惯的文件:\n※〔用户词典〕 \u0026lt;词典名\u0026gt;.userdb/ 或 \u0026lt;词典名\u0026gt;.userdb.kct ※〔用户词典快照〕 \u0026lt;词典名\u0026gt;.userdb.txt、\u0026lt;词典名\u0026gt;.userdb.kct.snapshot 见于同步文件夹 以及用户自己设定的:\n※〔用户对全局设定的定制信息〕 default.custom.yaml ※〔用户对预设输入方案的定制信息〕 \u0026lt;方案標識\u0026gt;.custom.yaml ※〔用户自制输入方案〕及配套的詞典源文件 注:以上标有 ※ 号的文件,包含用户资料,在清理文件時要注意备份!\n共享文件夹 存放由本机多个用户共享的文件,通常由输入法安装程序写入。\nrime 输入法在查找一项资源的时候,会优先访问 用户文件夹 中的文件。 用户文件不存在时,再到共享文件夹中寻找。\n一些 linux 发行版可由 rime-data 软件包安装常用数据文件到这里。或用 /plum/ 编译安装。\n❶ 位置\nlibrime 允许输入法指定共享文件夹的位置。\n小狼毫: \u0026lt;安装目录\u0026gt;\\data 鼠鬚管: \u0026quot;/library/input methods/squirrel.app/contents/sharedsupport\u0026quot; ibus-rime, fcitx-rime: /usr/share/rime-data (编译时可配置) ❷ 内容\n输入方案、韵书、默认配置源文件:\n\u0026lt;输入方案代号\u0026gt;.schema.yaml: 用户下载或自定义的 输入方案。 \u0026lt;韵书代号\u0026gt;.dict.yaml: 用户下载或自定义的 韵书。 \u0026lt;词典名称\u0026gt;.txt: 文本格式的词典,如预设词汇表。 也可以包含编译后的机读格式,从而省去用户部署时从相同源文件再次编译的步骤:\nbuild/* 快取文件。为使输入法程序高效运行,预先将配置、韵书等编译为机读格式。 註: librime 1.3 版本之前,编译后的快取文件直接存放在共享文件夹,与源文件并列。\n其他:\nopencc/* - opencc 字形转换配置及字典文件。 用户文件夹 rime 从「用户文件夹」读取用家自订的配置。\n输入法运行时保存的数据如 用户词典、安装信息、选项状态等也放在这里。\n❶ 位置\nlibrime 允许输入法指定用户文件夹的位置。\n用户文件夹的位置应使用绝对路径。请勿使用相对路径。\n小狼毫: 用户文件夹的默认路径为 %appdata%\\rime。也可以通过「开始菜单\小狼毫输入法\用户文件夹」打开。 鼠鬚管: 用户文件夹的路径为 ~/library/rime。也可以通过「系统输入法菜单/鼠鬚管/用户设定…」打开。 ibus-rime: ~/.config/ibus/rime fcitx-rime: ~/.config/fcitx/rime fcitx5-rime: ~/.local/share/fcitx5/rime/ ❷ 内容\n用家下载或定製的文件:\n\u0026lt;输入方案代号\u0026gt;.schema.yaml - 用户下载或自定义的 输入方案。 \u0026lt;韵书代号\u0026gt;.dict.yaml - 用户下载或自定义的 韵书。 \u0026lt;词典名称\u0026gt;.txt - 文本格式的词典,如预设词汇表、用户 自定义词组。 \u0026lt;配置代号\u0026gt;.custom.yaml - 应用於配置文件 \u0026lt;配置代号\u0026gt;.schema.yaml 或 \u0026lt;配置代号\u0026gt;.yaml 的 补靪。 opencc/* - opencc 字形转换配置及字典文件。 输入法程序记录的使用习惯等信息:\n\u0026lt;输入法语言代号\u0026gt;.userdb/ - 输入法程序为保存用户的输入习惯而创建的 用户词典。 installation.yaml - 安装信息。输入法程序在首次运行及升级后写入安装、升级时间、程序版本等。 user.yaml - 用户状态信息。包括在 方案选单 选取的输入方案、输入法选项状态如「中/西」「简/繁」等。 部署时生成的文件:\nbuild/* - 快取文件。为使输入法程序高效运行,在部署过程中将配置、韵书等编译为机读格式。 trash/* - 失效的文件。因 rime 升级而不再使用的旧文件会自动移入这个文件夹。用家确认不再需要后可以删除。 註:librime 1.3 版本之前,编译后的快取文件(包括应用了补靪的 yaml 配置)直接存放在用户文件夹;librime 升级后将其移入 trash/。如果某个 yaml 源文件已经找不到了,无法在升级后重新编译,可以从 trash/ 里面找回一份副本。\n详解输入方案 想要了解更多更全面吗? 雪齐的文档 全面而详细解释了输入方案及词典中各项设定各设定项的含义及用法。\n方案定义 一套输入方案,通常包含 「 方案定义 」和 「 词典 」文件。\n方案定义,命名为 \u0026lt;方案标识\u0026gt;.schema.yaml,是一份包含输入方案配置信息的 yaml 文档。\n文档中需要有这样一组方案描述:\n1 2 3 4 5 6 7 8 9 10 # 以下代码片段节选自 luna_pinyin.schema.yaml schema: schema_id: luna_pinyin name: 朙月拼音 version: \u0026#34;0.9\u0026#34; author: - 佛振 \u0026lt;chen.sst@gmail.com\u0026gt; description: | rime 预设的拼音输入方案。 首先来为方案命名。schema/name 字段是显示在 〔 方案选单 〕中的名称。\n然后,重点是要定一个在整个 rime 输入法中唯一的「 方案标识 」,即 schema/schema_id 字段的内容。方案标识由小写字母、数字、下划线构成。仅于输入法内部使用,且会构成方案定义文件名的一部分,因此为了兼容不同的文件系统,不要用大写字母、汉字、空格和其他符号做方案标识。如:这款名为〔 朙月拼音 〕的输入方案,方案标识为 「 luna_pinyin 」。\n方案如做升级,通过版本号 schema/version 来区分相互兼容的新旧版本。版本号是以 . 分隔的整数或文字构成的字符串。如下都是版本号常见的形式:\n\u0026#34;1\u0026#34; # 最好加引号表明是字符串!\r\u0026#34;1.0\u0026#34; # 最好加引号表明是字符串!\r\u0026#34;0.9.8\u0026#34;\r\u0026#34;0.9.8.custom.86427531\u0026#34; # 这种形式是经过用户自定义的版本:自动生成 然而,若对方案的升级会导致原有的用户输入习惯无法在新的方案中继续使用,则需要换个新的方案标识。\n比如仓颉五代之于仓颉三代,五笔 98 之于五笔 86,其实已是互不兼容的输入法。\nschema/author ——列出作者和主要贡献者,格式为文字列表:\n1 2 3 4 5 schema: author: - 作者甲 \u0026lt;alpha@rime.org\u0026gt; - 作者乙 \u0026lt;beta@rime.org\u0026gt; - 作者丙 schema/description ——对方案作简要介绍的多行文字。\n以上 schema/schema_id、schema/version 字段用于在程序中识别输入方案,而 schema/name、schema/author、schema/description 则主要是展示给用户的信息。\n除方案描述外,方案定义文件中还包含各种功能设定,控制着输入法引擎的工作方式。\n输入法引擎与功能组件 以下是 rime 输入法的工作流程:\n按键消息 → 后台「 服务 」 → 分配给对应的「 会话 」→ 由「 方案选单 」或「 输入引擎 」处理…… 这里要点是,有会话的概念:多窗口、多线操作嘛,你懂得的,同时与好几位 mm 聊天时,有没有好几组会话。每一组会话中都有一部输入引擎完成按键序列到文字的变换。\nrime 可以在不同会话里使用不同输入方案。因为有「 方案选单 」。方案选单本身可响应一些按键。但由于它不会写字的缘故,更多时候要把按键传递给同一会话中的「 输入引擎 」继续处理。方案选单的贡献,就是给用户一个便捷的方案切换介面,再把用户挑中的输入方案加载到输入引擎。\n让我们看一下输入引擎的工作流程:\n加载输入方案、预设功能组件 → 各就各位之后就进入处理按键消息、处理按键消息……的循环 响应各种按键、产生各类结果的工作,由不同的功能组件分担。\n好,看代码:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # luna_pinyin.schema.yaml # ... engine: # === 输入引擎设定 === processors: # » ¹这批组件处理各类按键消息 - ascii_composer # ※ 处理西文模式及中西文切换 - recognizer # ※ 与 matcher 搭配,处理符合特定规则的输入码,如网址、反查等 - key_binder # ※ 在特定条件下将按键绑定到其他按键,如重定义逗号、句号为候选翻页键 - speller # ※ 拼写处理器,接受字符按键,编辑输入码 - punctuator # ※ 句读处理器,将单个字符按键直接映射为文字符号 - selector # ※ 选字处理器,处理数字选字键、上、下候选定位、换页键 - navigator # ※ 处理输入栏内的光标移动键 - express_editor # ※ 编辑器,处理空格、回车上屏、回退键等 segmentors: # » ²这批组件识别不同内容类型,将输入码分段 - ascii_segmentor # ※ 标识西文段落 - matcher # ※ 标识符合特定规则的段落,如网址、反查等 - abc_segmentor # ※ 标识常规的文字段落 - punct_segmentor # ※ 标识句读段落 - fallback_segmentor # ※ 标识其他未标识段落 translators: # » ³这批组件翻译特定类型的编码段为一组候选文字 - echo_translator # ※ 没有其他候选字时,回显输入码 - punct_translator # ※ 转换标点符号 - script_translator # ※ 脚本翻译器,用于拼音等基于音节表的输入方案 - reverse_lookup_translator # ※ 反查翻译器,用另一种编码方案查码 filters: # » ⁴这批组件过滤翻译的结果 - simplifier # ※ 繁简转换 - uniquifier # ※ 过滤重复的候选字,有可能来自繁简转换 注:除示例代码中引用的组件外,尚有\n1 2 3 - fluid_editor # ※ 句式编辑器,用于以空格断词、回车上屏的「 注音、语句流 」等输入方案,替换 express_editor,也可以写作 fluency_editor - chord_composer # ※ 和弦作曲家或曰并击处理器,用于「 宫保拼音 」等多键并击的输入方案 - table_translator # ※ 码表翻译器,用于仓颉、五笔等基于码表的输入方案,替换 script_translator 输入引擎把完成具体功能的逻辑拆分为可装卸、组合的部件。「 加载 」输入方案,即按该处方挂接所需的功能组件、令这些组件从输入方案定义中加载各自的设定、准备各司其职。而他们接下来要完成作业,由引擎收到的一份按键消息引发。\n理解 processors 输入引擎,作为整体来看,以按键消息为输入、输出包括三部分:\n对按键消息的处理结果:操作系统要一个结果、这按键、输入法接是不接? 暂存于输入法、尚未完成处理的内容,会展现在输入法候选窗中; 要「 上屏 」的文字,并不是每按一键都有输出。通常中文字都会伴随「 确认 」动作而上屏,有些按键则会导致符号上屏,而这些还要视具体场景而定。 那么第一类功能组件 processor s ,就是比较笼统地、起着「 处理 」按键消息的作用。\n按键消息依次送往列表中的 processor,由他给出对按键的处理意见:\n或曰「 收 」,即由 rime 响应该按键: 或曰「 拒 」,即回答操作系统 rime 不做响应、请对按键做默认处理; 或曰这个按键我不认得、请下一个 processor 继续看。 优先级依照 processors 列表顺序排定,接收按键者会针对按键消息做处理。\n虽然看起来 processor 通过组合可以承担引擎的全部任务,但为了将逻辑继续细分、rime 又为引擎设置了另外三类功能组件。这些组件都可以访问引擎中的数据对象 \u0026ndash; 输入上下文,并将各自所做处理的阶段成果在于其中。\nprocessor 最常见的处理,便是将按键所产生的字符废上下文中的「 输入码 」序列。当「 输入码 」发生变更时,下一组组件 segmentor s 开始一轮新的作业。\n理解 segmentors segment 是段落的意思,segmentor 即 分段器,将用户连续输入的文字、数字、符号等不同内容按照需要,识别不同格式的输入码,将输入码分成若干段分而治之。这通过数轮代码段划分操作完成。每一轮操作中、一众 segmentor s 分别给出起始于某一处、符合特定格式的代码段,识别到的最长代码段为本轮划分结果,而给出这划分的一个或多个 segmentor 组件则可为该代码段打上「 类型标签 」 ;从这一新代码段的结束位置,开始下一轮划分,直到整个输入码序列划分完毕。\n举例来说,「 朙月拼音 」中,输入码 2012nian\\ ,划分为三个编码段:\n- 2012 → 贴 number 标签 - nian → 贴 abc 标签\r- \\ → 贴 punct 标签 那些标签是初步划分后判定的类型,也可能有一个编码段贴多个标签的情况。下一个阶段中, translator s 会把特定类型的编码段翻译为文字。\n理解 translators 顾名思义, translator 完成由编码到文字的翻译。但有几个要点:\n翻译是对象是划分好的一个代码段; 某个 translator 组件往往只翻译具有特定标签的代码段; 翻译的结果可能有多条,每条结果成为一个展现给用户的候选项; 代码段可由几种 translator 分别翻译,翻译结果按一定规则合并成一列候选; 候选项所对应的编码未必是整个代码段。用拼音敲一个词组时,词组后面继续列出单字候选,即是此例。 让们看一下它们在内存中的存储,发现翻译的结果呈现这种形式:\ninput | tag | translations\r------+--------+-------------------------------------\r2012 | number | [ \u0026#34;2012\u0026#34; ], [ \u0026#34;二〇一二\u0026#34; ]\rnian | abc | [ \u0026#34;年\u0026#34;, \u0026#34;念\u0026#34;, \u0026#34;唸\u0026#34;,... ], [ \u0026#34;nian\u0026#34; ]\r\\ | punct | [ \u0026#34;、\u0026#34;, \u0026#34;\\\u0026#34; ] 输入串划分为多个代码段,每段代码又可具有多级翻译结果;取各代码段的首先结果连接起来,就是预备上屏的文字「 2012年、 」。\n且将以上所示的数据称为 「 作文 」。这是一篇未定稿(未搞定)的作文,输入法介面此时显示预备上屏的文字「 2012年、 」,并列出最末一个代码段上的候选「 、 」及「 \\ 」以供选择。\n有两款主力 translator 完成主要文字内容的翻译,其实现的方式很不一样:\n¹ script_translator 也叫做 r10n_translator 修炼罗马字分析法,以「 固定音节表 」为算法的基础,识别输入码的音节构成,推敲排列组合、完成遣词造句。\n² table_translator 修炼传承自上世纪的码表功夫,基于规则的动态码表,构成编码空间内一个开放的编码集合。\n拼音、注音、方言拼音,皆是以固定音节表上的拼写排列组合方式产生编码,故适用罗马字分析法。\n仓颉、五笔字型这类则是传统的码表输入法。\n如果以码表方式来写拼音输入方案,是怎么的效果呢?虽然仍可完成输入,但无法完全实现运行简拼、模糊拼音、使用隔音符号的动态高频、智能语句等有用的特性。\n反之,以罗马字方式使用码表输入法,则无法实现定长编码顶字上屏、按编码规则构词等功能。在 rime 各发行版预设输入方案中,有一款「 速成 」输入方案,即是以 script_translator 翻译仓颉码,实现全、简码混合的语句输入。\n概括起来,这是两种构造新编码的方式:罗马字式输入方案以一组固定的基本音节码创造新的组合而构词,而码表式输入方案则以一定码长为限创造新的编码映射而构词。\n理解 filters filter 即 过滤器。上一步已经收集到各个代码段的翻译结果,当输入法需要在介面呈现一页候选项时,就从最末一个代码段的结果集中挑选,直至取够方案中设定的页最大候选数。\n每从结果集选出一条字词,会经过一组 filter s 过滤。多个 filter 串行工作,最终产出的结果进入候选序列。\nfilter 可以:\n改装正在处理的候选项,修改某些属性值:简化字、火星文有没有? 消除当前候选项,比如检测到重复(由不同 translator 产生)的候选条目; 插入新的候选项,比如根据已有条目插入关联的结果; 修改已有的候选序列。 码表与词典 词典是 translator 的参考书。\n它往往与同名输入方案配套使用,如拼音的词典以拼音码查字,仓颉的词典以仓颉码查字。但也可由若干编码属于同一系统的输入方案共用,如各种双拼方案,都使用和拼音同样的词典,于是不仅复用了码表数据,也可共享用户以任一款此系列方案录入的自造词(仍以码表中的形式取全拼编码记录)。\nrime 的词典文件,命名为 \u0026lt;词典名\u0026gt;.dict.yaml,包含一份码表及对应的规则说明。词典文件的前半部分为一份 yaml 文档。\n1 2 3 4 5 6 7 8 # 注音这里以 --- ... 分别标记出 yaml 文档的起始与结束位置 # 在 ... 标记之后的部分就不会作 yaml 文档来解读 --- name: luna_pinyin version: \u0026#34;0.9\u0026#34; sort: by_weight use_preset_vocabulary: true ... 其中:\n¹ name ,词典名,内部使用,命名原则同 「 方案标识 」,可以与配套的输入方案标识一致,也可以不同。\n² version,管理词典的版本,规则同输入方案定义文件的版本号。\n³ sort,词条初始化排序方式,可选填 by_weight (按词频高低排序)或 original (保持原码表中的顺序)。\n⁴ use_preset_vocabulary ,填 true 或 false ,选择是否导入预设词汇表「 八股文 」。\n码表,定义了输入法中编码与文字的对应关系。\n码表位于词典文件中 yaml 结束标记之后的部分。其格式为以制表符分隔的值,每行定义一条(文字 - 编码)的对应关系:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 单字 你\tni 我\two 的\tde\t99% 的\tdi\t1% 地\tde\t10% 地\tdi\t90% 目\tmu 好\thao # 词组 你我 你的 我的 我的天 天地\ttian di 好天 好好地 目的\tmu di 目的地\tmu di di 注音:不要从网页复制以上代码到实际的词典文件!因为网页制作符被转换成空格从而不符合 rime 词典要求的格式。一些文本编辑器也会将使用者的制表符自动转换为空格,请注音检查和设置。\n码表部分,除了以上格式的编码行,还可以包含空行及注释行(行首为 # 符号)。\n¹ 以制表符(tab)分隔的第一个字段是所定义的文字,可以是单字或词组。\n² 第一个字段是与文字对应的编码。若该编码由多个音节组成,音节之间以空格分开。\n³ 第三个字段(可选的)是设定该字词的权重(非负整数),或相对于预设权值的百分比(浮点数%)。在拼音输入法中,往往多音字的若干种读音使用的场合不同,于是指定不同百分比来修正每个读音的使用频率。\n词组如果满足以下条件,则可以活动编码字段:\n词组中的每个单字均胡编码定义; 词组中不包含多音字(如:你我),或多音字在该词组中的读音的权值超过该多音字全部读音权值的 5% (如:我的)。 这种条件下,词组的编码可由单字编码组合推导出来。\n反之,则有必要给出词组的编码以消除自动注音的不确定性(如:天地)。\n当含有多音字的词组缺少编码字段时,自动注音程序会复用权重百分比高于 5% 的读音进行组合,生成全部可能的注音。如:\n「 好好地 」在编译时自动注音为 「 hao hao de 」、「 hao hao di 」。\n编译输入方案 将写好的输入方案部署到 rime 输入法的过程,称为 「 编译 」。\n为了查询效率,输入法工作时不直接加载文本格式的词典源文件,而是加载编译过程中为输入方案生成的专为高速查询设计的 「 .bin 」文件。\n编译时程序做以下几件事:\n将用户的定制内容合并到输入方案定义中,在用户文件夹生成 .schema.yaml 文档副本; 依照输入方案中指定的词典:求得音节表(不同编码的集合)、单字表; 对词典中未提供编码的词组自动注音,也包括从「 八股文 」导入的词组; 建立按音节编码检索词条的索引,制作 rime 固定词典; 建立按词条检索编码的索引,制作 rime 反查词典; 依照音节表选方案定义中指定的拼写运算规则,制作 rime 棱镜。 部署 初次安装 rime 输入法,没有任何输入方案选用户设定。因此安装的最后一个步骤即是把发行版预设的输入方案选设定文件部署到 rime 为该用户创建的工作目录,至此 rime 组成为一部可以发动的输入引擎。\n此后无论是修改已有方案的设定,或是添加了新的输入方案,都需要「 重新部署 」成功后方可使新的设定生效。\n重新部署的方法:\n小狼毫:开始菜单 » 重新部署;或右击托盘图标 » 重新部署; 鼠须管:在系统语言文字选单中选择重新部署; 中州韵:点击输入法状态样(或 ibus 菜单)上的 ⟲ (deploy) 按钮。 定制指南 rime 输入方案,将 rime 输入法的设定整理成完善的、可分发的形式。但并非一定要创作新的输入方案,才可以改变 rime 的行为。\n当用户需要对 rime 中的各种设定做小幅的调节,最直接、但不完全正确的做法是:编辑用户文件夹中的那些 .yaml 文档。\n这种方法有弊端:\n当 rime 软件升级时,也会升级各种设定档、预设输入方案。用户编辑过的文档会被覆写为更高版本,所做高速也便丢失了; 即使在软件升级后再手动恢复经过编辑的文件,也会因设定档的其他冷媒未得到更新而失去本次升级新增选修复的功能。 因此,对于随 rime 改造的设定档及预设输入方案,⭐️ 推荐的定制方法是:\n创建一个文件名的主体部分与要定制的文件相同、次级扩展名(位于 .yaml 之前)写作 .custom 的定制档,形如:\n1 2 3 4 5 6 7 8 9 patch: \u0026#34;一组设定项/二级设定项/三级设定项\u0026#34;: 新的设定值 \u0026#34;另一个设定项\u0026#34;: 新的设定值 \u0026#34;再一个设定项\u0026#34;: 新的设定值 \u0026#34;含列表的设定项/@0\u0026#34;: 列表第一个元素新的设定值 \u0026#34;含列表的设定项/@last\u0026#34;: 列表最后一个元素新的设定值 \u0026#34;含列表的设定项/@before 0\u0026#34;: 在列表第一个元素之前插入新的设定值(不建议在补丁中使用) \u0026#34;含列表的设定项/@after last\u0026#34;: 在列表最后一个元素之后插入新的设定值(不建议在补丁中使用) \u0026#34;含列表的设定项/@next\u0026#34;: 在列表最后一个元素之后插入新的设定值(不建议在补丁中使用) patch 定义了一组补丁,以源文件中的设定为底本,定入新的设定项、或以新的设定值取代旧有的值。\n以下这些例子,请参阅另一篇 定制指南 ,其中所介绍的知识选技巧,覆盖了不少本方未讨论的细节,想必对于创作新的输入方案会有启发。\n¹ 定制每页候选数 ² 定制标点符号 ³ 定制简化字输出 ⁴ 默认英文输出 ⁵ 定制方案选单 重要!创作了新的输入方案,最后一步就是在「 方案选单 」中启用它。可以在 [[#定制指南详解]] 中了解更多。\n拼写运算 💥 i.e. algebra\n📘 拼写运算 · rime/home wiki \u0026mdash; spellingalgebra · rime/home wiki\n应该算是 rime 输入法最主要的独创技术。\n概括来说就是将方案中的编码通过规则映射到一组全新的拼写形式!也就是说能让 rime 输入方案在不修改码表的情况下,适应不同的输入习惯。\n拼写运算能用来:\n改革拼写法:将编码映射到基于同一音系的其他拼写法,如注音、拼音、国语罗马字相互转换;自定义注音键盘、双拼方案; 实现简拼查询; 在音节表上灵活地定义模糊音规则; 实现音节自动纠错; 变换回显的输入码或提示码,如将输入码显示为字根、注音符号、带声调标的罗马字。 给力吗?必须的。\n常用术语 先来介绍本文所使用的术语:\n输入法:以输入信号的序列到输出文本序列的转换方法。rime 可搭载多种不同类型的输入法,称其为「 输入方案 」。\n字符集:构成目标输出文本的字符的集合,主要由汉字组成。\n编码:用于检索目标字符的字母序列。\n字母:构成编码的字符,亦称「 码元 」。\n字母表:字母的集合,亦称「 编码字符集 」,通常由小写拉丁字母组成。\n码表:目标字符集与编码集合之间的映射表。\n码长:单个编码所包含的字母个数。码表中所有编码码长一致,本文称其为「 定长编码 」。规定了最大编码的编码方案,本文称其为「 限长编码 」。\n编码空间:给定的字母表以有限的码长排列组合所得的可用编码数目。\n音节表:输入方案中所有编码的集合。拼音输入方案中,彼此不同的音节是可穷举的,其音节表是个固定的集合。多数形码输入法按照一定规则在给定的编码空间中为新生词组编码,故无法给出固定的音节表。\n重码:对应到多个输出字符的编码。\n输入码:用途输入的字符序列。\n候选:与输入码相关联的目标输出文字。当有重码时,候选文字形成一个列表可供用户挑选。\n拼写:与单个编码相对应的输入码。拼写可能不同于编码。\n拼写法:一种输入方案里,有效拼写的集合到编码集合的映射,亦称「 正字法 」。\n拼写运算:以拼写为运算元的一元运算。通过字符串匹配及替换操作对拼写实施文字变换,获得一个新的拼写,并可赋予结果附加的属性。\n投影:以拼写法为运算元的一元运算,获得一个衍生的拼写法。实际运用时,通常对拼写法连续执行一组投影操作。每一轮操作中,对拼写法里的每个有效拼写做一次拼写运算,从而获得新的有效拼写集合,并重新建立其与编码集合的映射。\n功能 传统的中文输入法是基于码表的,通过键盘输入特定字母序列,获得与该编码对应的文字。而大多数编码方案,都不会用尽编码空间内的所有编码。与字符集内的文字无对应的编码,就在编码空间中形成空位。为了提高输入效率,输入法会复用编码空间中的空位,编制简码、容错码等衍生编码,以在尽量多的场合为用户提供有意义的候选文字。\n:: 编码空间中空位,就是没有对应文字的编码。\n在某些输入方案里,简码、容错码可以从源码表按照一定规则产生。如:汉语拼音输入法中的简拼,取拼音音节的首字母,也可取以双这互表示的声母 zh、ch、sh 。这是因为 编码方案所遵循的语言学原理,决定了拼音音节在编码空间内的分布及留下的空位,呈某种规则的形式。 故,产生简码、容错码的规则是与输入方案相关的,一款通用的输入法软件要以与编码方案相适应的方式生成这些衍生编码。\n拼写运算,提供了描述产生式规则的能力! 🥳\n为了提供多样化的输入体验,rime 可藉由一份码表制作多款输入方案。譬如:注意符号、汉语拼音这两套注意方案是为相同音系设计的(忽略地区间字音标准的差异)。信有一份注意码表,按一定对应规则将注意转换为汉语拼音,可使注意、汉语拼音,甚至还有基于汉语拼音的双拼等输入方案复用同一部词典。\n拼写运算,提供为输入方案重构拼写法的能力! 🥳\n此外,改变编码回显的样式、按特定规则生成词组编码……拼写运算也可用来实现此等需要定制能力的算法。\n原理与实现 🌟 拼写运算,借助正则表达式实现其字符串处理能力。进一步,利用数学知识,构造出建立在输入法编码集合上的运算系统。\n拼写运算实现为 rime 程序库中的一套算法,可从 rime 配置文件导入一组算式,执行规定的计算步骤。运算步骤以 yaml 字符串列表的形式定义,每个列表为描述为描述一项运算的算式。算式中包含的正则表达式,遵照 perl 正则表达式的语法规范。\n❶ 拼写运算的算式\n格式为:\u0026lt;运算子\u0026gt;\u0026lt;分隔符\u0026gt;\u0026lt;参数1\u0026gt;\u0026lt;分隔符\u0026gt;\u0026lt;参数2\u0026gt;\u0026lt;分隔符\u0026gt;...\r如:\rxlit/abc/abc\r- 运算子为 xlit(转写)\r- 分隔符为 /\r- 两个参数 abc 和 abc\r其中,分隔符为单个 ascii 字符,通常用符号或空白字符。\r如果参数中不包含空格,也可写作:\rxlit abc abc 注意:作为分隔符的字符不能在参数中出现。不同于 perl 的 s///\\/ 语法,拼写运算式不支持在参数中将用作分隔符的字符用 \\ 转义表示。\n🪧 也可以使用 | 充当了 分隔符,和 / 作用相同。这种格式是 rime 兼容的拼写运算规则,并没有改变正则本质。\n为什么多此一举呢?如上面所说的不支持转义,如此,你怎么表示在以 / 为分隔符的算式中表示 / 这个符号呢?这个时候 | 就排上用场了❗️\n:: 这解决了在并击方案中如何表示 / 符号的问题 👻\n❷ 拼写运算的运算子\n¹ 转写 / transliteration\n依次将拼写中见于 \u0026lt;左字母表\u0026gt; 的字符替换为 \u0026lt;右字母表\u0026gt; 对应位置的字符。左、右字母表应包含相同数目的 unicode 字符。\n格式: xlit/\u0026lt;左字母表\u0026gt;/\u0026lt;右字母表\u0026gt;/\r如:\r算式 xlit/abs/abc/ 运算元 abracadabra\r结果 → abracadabra ² 变形 / transformation\n若拼写(或其子串)与 \u0026lt;模式\u0026gt; 匹配,则将所匹配的部分改写为 \u0026lt;替换式\u0026gt;,否则拼写保持不变。模式、替换式遵循 perl 正则表达式语法。\n格式:xform/\u0026lt;模式\u0026gt;/\u0026lt;替换式\u0026gt;\r如:\r算式 xform/^([nl]ue$)/$1ve/\r运算元 nue\r结果 → nve 效果:输入 nve(lve) 可以获取码表中与编码 nue(lue) 对应的候选。输入 nue(lue) 则无候选。\n:: 因为已经变形以嘛~\n³ 消除 / erasion\n若拼写与 \u0026lt;模式\u0026gt; 完全匹配,则将该拼写从有效拼写集合中消除。\n格式: erase/\u0026lt;模式\u0026gt;/\r如:\r算式 erase/^.\\d$/\r运算元 dang1\r结果 → 带声调的拼音不再可用 ⁴ 派生 / derivation\n若对拼写做正则模式匹配、替换而获得了新的拼写,则有效拼写集合同时包含派生前后的拼写,否则保留原拼写。\n格式: derive/\u0026lt;模式\u0026gt;/\u0026lt;替换式\u0026gt;/\r如:\r算式 derive/^([nl])ue$/$1ve/\r运算元 nue\r结果 → nve 效果:输入 nve、nue 或 lve、lue 均可获得源码表中与编码 nue、lue 对应的候选。\n如:\r算式 derive/^[nl](.*)$/l$1/\r运算元 na\r结果 → la 效果:输入 la 可获得源码表中与编码 na、la 对应的候选;输入 na ,候选仍为码表中编码为 na 的候选。\n⁵ 模糊 / fuzzing\n执行派生运算。派生出来的拼写将获得「 模糊 」属性,可设定将其用作构成词组的简码、但不用于输入单字。\n格式: fuzz/\u0026lt;模式\u0026gt;/\u0026lt;替换式\u0026gt;/\r如:\r算式 fuzz/^([a-z]).+([a-z])$/$1$2/ 效果:以首、尾码为多字母音节码的构词码。\n注:需要配合 script_translator 的选项 translator/strict_spelling: true 方可限定该拼写不用于输入单字。\n⁶ 缩略 / abbreviation\n执行派生运算。派生出的拼写将获得 「 缩略 」属性,会在音节切分时与通常的拼写做区分处理。\n格式: abbrev/\u0026lt;模式\u0026gt;/\u0026lt;替换式\u0026gt;\r如:\r算式 abbrev/^([a-z]).+$/$1/ 效果:以首字母为多字母音节码的编写。\n注解:\n转写 是拼写运算中目前唯一一则将运算元和参数作 utf-32 编码,而非 utf-8 编码处理的运算。意味着,字母表可以采用 ascii 范围以外的字符。字母表的长度按照 unicode 字符数来计算。\n转写 和 变形 两则运算,除在拼写法投影操作中起重要作用,还可用于对单个字符串进行变换。 消除、派生 和 缩略 用于定义拼写投影中非一一映射的情况。\n消除 就给定的模式,对运算元做完全匹配,即 regex match 操作。 变形、派生 和 缩略 则可做部分匹配,相当于 regtx search/global replace 操作。\n投影算法 在拼写法投影操作 p[x,y,z] 里,每项运算 x,y,z 作为投影的一个步骤,依次从作为运算元的拼写法中产生一套新的拼写法。将拼写法投影用于构建 “拼写-编码” 映射时,用户的输入是随意的,而码表中,音节表是固定的集合 a 。\n所以 rime 选音节表 a 上的初始拼写法(a → a)为投影的运算元,逐步推导出映射到音节表 a 的有效拼写集合 b ,即所求的拼写法 (b → a)。\n算法:\n记音节表为 a ,拼写运算为序列 [x,y,z],该投影的结果为 p[x,y,z](a -\u0026gt; a)\rsa = { a -\u0026gt; a | for a in a } = (a -\u0026gt; a)\rsx = p\u0026lt;x\u0026gt;(sa) = { x(a) -\u0026gt; a | for (a -\u0026gt; a) in (a -\u0026gt; a) } = (b -\u0026gt; a)\rsy = p\u0026lt;y\u0026gt;(sx) = { y(b) -\u0026gt; a | for (b -\u0026gt; a) in (b -\u0026gt; a) } = (c -\u0026gt; a)\rsz = p\u0026lt;z\u0026gt;(sy) = { z(c) -\u0026gt; a | for (c -\u0026gt; a) in (c -\u0026gt; a) } = (d -\u0026gt; a)\rp[x,y,z](sa) = sz 在 rime 输入方案中的用法 例¹:仓颉输入方案(cangjie5.schema.yaml),在编码区回显仓颉字母。\ntranslator:\rpreedit_format:\r- xlit|abcdefghijklmnopqrstuvwxyz|日月金木水火土竹戈十大中一弓人心手口尸廿山女田難卜符| 例²:朙月拼音(luna_pinyin.schema.yaml),显示拼音字母“ü”\ntranslator:\rpreedit_format:\r- xform/([nl])v/$1ü/ 这一处,拼写运算的作用对象是编码回显区的拼音串,串中可能包含多个拼音音节,并已经自动插入了隔音符号。为了替换该拼音段中所有匹配的字母,模式中并未用锚点匹配音节的头尾位置。\n例³:朙月拼音(luna_pinyin.schema.yaml),定义简拼、容错拼写。\nspeller:\ralgebra:\r- abbrev/^([a-z]).+$/$1/ # 简拼(首字母)\r- abbrev/^([zcs]h).+$/$1/ # 简拼(zh, ch, sh)\r- derive/^([nl])ve$/$1ue/ # 设 nue = nve, lue = lve - derive/ui$/uei/ # 设 guei = gui,...\r- derive/iu$/iou/ # 设 jiou = jiu,...\r- derive/([aeiou])ng$/$1gn/ # 容错 dagn = dang,...\r- derive/ong$/on/ # 容错 zhonguo = zhong guo\r- derive/ao$/oa/ # 容错 hoa = hao,...\r- derive/([iu])a(o|ng?)$/a$1$2/ # 容错 tain = tian,... 编译输入方案时,将运用这组运算规则完成音节表上的投影,求得可解析为音节代码的有效拼写集合。输入过程中,这组有效拼写决定着输入码在音节切分方式。\n例⁴:在拼音输入法中定义模糊音 zh=z, ch=c, sh=s, n=l, en=eng, in=ing\nspeller:\ralgebra:\r- derive/^([zcs])h/$1/\r- derive/^([zcs])([^h])/$1h$2/\r- derive/^n/l/\r- derive/^l/n/\r- derive/([ei])n$/$1ng/\r- derive/([ei])ng$/$1n/\r# 模糊音定义先于简拼定义,可令简拼支持以上模糊音\r- abbrev/^([a-z]).+$/$1/\r- abbrev/^([zcs]h).+$/$1/ 综合演练 牛刀小试 如果你安装好了 rime 却不会玩,就一步一步跟我学吧。\n本系列课程每个步骤的完整代码可由此查阅: rimeime/doc/tutorial 。\n¹一个简单的方案 每一个例子,总是最简单的(也是最傻的)。\n1 2 3 4 5 6 7 8 9 10 # rime schema - hello.schema.yaml # encoding: utf-8 # # 最简单的 rime 输入方案 # schema: schema_id: hello # 注意此 id 与文件名里 .schema.yaml 之前的部分相同 name: 大家好 # 将在〔方案选单〕中显示 version: \u0026#34;1\u0026#34; # 这是文字类型而非整数或小数,如 \u0026#34;1.2.3\u0026#34; 起始行是注释,而后只有一组必要的方案描述信息。\n这一课主要练习建立格式正确的 yaml 文档。\n始终使用 utf-8 格式编辑保存你的方案; 使用空格缩进(两个空格),而不是 tab 字符。 缩进表示设定所属的层次。\n缩进表示设定项所属的层次。在他处引用到此文档中的设定项,可分别以 schema/schema_id, schema/name, schema/version 来指称。\n我现在把写好的方案文档命名为 hello.schema.yaml,丢进用户文件夹,只要这一个文件就妥了。\n然后,启用它。\n有些版本会有「 方案选单设定 」这个介面,在那里勾选 「 大家好 」这个方案即可。若没有设定介面,则按照上文《定制方案选单 》一节来做。\n好运!我已建立了一款名为 「 大家好 」的新方案!虽然他没有实现任何效果,按键仍然会像没有输入法一样直接输出英文。\n²开始改装 为了处理字符按键、生成输入码,本例向输入引擎添加两个功能组件。\n以下代码仍是 id 为 hello 的新款输入方案,但更新了 schema/version 的数值。\n以后每个版本,都以前一个版本为基础改写,引文略去没有发动的部分,以突出重点。\n1 2 3 4 5 6 7 8 9 10 11 # ... schema: schema_id: hello name: 大家好 version: \u0026#34;2\u0026#34; engine: processors: - fluid_editor segmentors: - fallback_segmentor fluid_editor 将字符按键记入输入上下文, fallback_segementor 将输入码连缀成一段。于是重新部署后,按下字符键不再直接上屏,而是显示输入码。\n你会发现,该输入法只是收集了键盘上的可打印字符,并于按下空格、回车键时信输入码上屏。\n现在就好似写输入法程序的过程中,刚刚取得一点成果,还有很多逻辑没有实现。不同的是,在 rime 输入方案里定一行代码,顶 rime 开发者所写的上百上千行。因此键可以很快地组合各种逻辑组件,搭建出想要的输入法。\n³创建候选项 在上个版本(第二版)的方案「 大家好 」中,键们实现了将键盘上所有字符都记入输入码,这对整句输入有用,但是时政流行输入法只处理编码字符,其他字符直接上屏的形式。\n为了对编码字符做出区分,以下改用 speller + express_editor 的组合取代 fluid_editor 。\n1 2 3 4 5 6 7 8 9 10 11 12 # ... schema: # ... version: \u0026#34;3\u0026#34; engine: processors: - speller # 把字母追加到编码串 - express_editor # 空格确认当前输入,其他字符直接上屏 segmentors: - fallback_segmentor speller 默认只接受小写拉丁字母作为输入码。试试看,输入其他字符如大写字母、数字、标点,都会直接上屏。并且如果已经输入了编码时,下一个直接上屏的字符会将输入码顶上屏。\n再接着,创建一个最简单的候选项 \u0026ndash; 把编码串本身作为一个选项。故而会提供这个选项的新组件名叫 echo_translator。\n1 2 3 4 5 6 # ... engine: # ... translators: - echo_translator # (没有其他结果时)创建一个与编码串一模一样的候选项 至此,「 大家好 」看上去与一个真正的输入法形似啦,只是还不会打出“大家好”这个短语哦 ?\n⁴编写词典 那就写一部词典,码表中规定以 hello 作为短语“大家好”的编码:\n1 2 3 4 5 6 7 8 9 10 11 12 # rime dictionary # encoding: utf-8 --- name: hello version: \u0026#34;1\u0026#34; sort: original ... 大家好\thello 再见\tbye 再会\tbye 同时修改方案定义:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #... schema: # ... version: \u0026#34;4\u0026#34; engine: #... segmentors: - abc_segmentor # 标记输入码的类型 - fallback_segmentor translators: - echo_translator - table_translator # 码表式转换 translator: dictionary: hello # 设定 table_translator 使用的词典名 工作流程是这样的:\nspeller 将字母键加入输入码序列; abc_segmentor 给输入码打上标签 abc ; table_translator 把带有 abc 标签的输入码以查表的方式翻译为中文 table_translator 所查的码表在 translator/dictionary 所指定的词典里 现在可以敲 hello 而打出「大家好」。完工!\n⁵实现选字及换页 等一下。\n记得 hello 词典里,还有个编码叫做還 bye。敲 bye,rime 给出「再见」、「再会」两个候选短语。\n这时敲空格键,就会打出 「 再见 」;那么怎么打出 「 再会 」呢?\n大家首先想到的方法是,打完编码 bye ,按 1 选 「 再见 」,按 2 选「 再会 」。可是现在按下 2 去,却是上屏「 再见 」选数字 「 2 」。可见并没有完成数字键选字的处理,而是将数字同其他符号一样顶字上屏处理。\n增加一部 selector,即可实现以数字键选字。\n1 2 3 4 5 6 7 8 9 10 11 schema: # ... version: \u0026#34;5\u0026#34; engine: processors: - speller - selector # 选字、换页 - navigator # 移动插入点 - express_editor # ... selector 除了数字键,还响应前次面、上下方向键。因此选择第二候选「 再会 」,既可以按数字 2 ,又可以按方向键「 ↓ 」将 「 再会 」高亮、再按空格键确认。\nnavigator 处理左右方向键、home、end 键,实现移动插入点的编辑功能。有两种情况需要用到它:一是发现输入码有误需要定位修改;二是缩小选词对应的输入码的范围、精确地编辑新词组。\n接下来向词典添加一组重码,以检验换页的效果:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 --- name: hello version: \u0026#34;2\u0026#34; sort: original ... 大家好\thello 再见\tbye 再会\tbye 星期一\tmonday 星期二\ttuesday 星期三\twednesday 星期四\tthursday 星期五\tfriday 星期六\tsaturday 星期日\tsunday 星期一\tweekday 星期二\tweekday 星期三\tweekday 星期四\tweekday 星期五\tweekday 星期六\tweekday 星期日\tweekday 默认每页候选数为 5 ,输入 weekday,显示「星期一」至「星期五」。再敲 page_down 显示第二页候选词「星期六、星期日」。\n⁶输出中文标点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 schema: # ... version: \u0026#34;6\u0026#34; engine: processors: - speller - punctuator # 处理符号按键 - selector - navigator - express_editor segmentors: - abc_segmentor - punct_segmentor # 划界,与前后方其他的编码区分开 - fallback_segmentor translators: - echo_translator - punct_translator # 转换 - table_translator # ... punctuator: # 设定符号表,这里直接导入预设的 import_preset: default 这次的修改,要注意 punctuator, punct_segmentor, punct_translator 相对于其他组件的位置。\npunctuator/import_preset 告诉 rime 使用一套预设的符号表。它的值 default 可以换成其他名字如 xxx,則 rime 会读取 xxx.yaml 里面定义的符号表。\n如今再敲 hello. 就会得到「大家好。」\n⁷用符号键换页 早先流行用 - 和 = 这一对符号换页,如今流行用 , 和 . 。 在第六版中「,」「。」是会顶字上屏的。现在要做些处理以达到一键两用的效果。\nrime 提供了 key_binder 组件,他能夠在一定条件下,将指定按键绑定为另一个按键。对于本例就是:\n当展现候选菜单时,名号键(period)绑定为向后换页(page_down); 当已有(向后)换页动作时,逗号键(comma)绑定为向前换页(page_up)。 逗号键向前换页的条件之所以比名号键严格,是为了「,」仍可在未进行换页的情况下顶字上屏。\n经过 key_binder 的处理,用來换頁的逗号、句号键改头换面前、后换页键,从而绕过 punctuator,最终被 selector 当作换页来处理。\n最终的代码如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 schema: schema_id: hello name: 大家好 version: \u0026#34;7\u0026#34; engine: processors: - key_binder # 抢在其他 processor 处理之前判定是否换頁用的符号键 - speller - punctuator # 否则「,。」就会由此上屏 - selector - navigator - express_editor segmentors: - abc_segmentor - punct_segmentor - fallback_segmentor translators: - echo_translator - punct_translator - table_translator translator: dictionary: hello punctuator: import_preset: default key_binder: bindings: # 每条定义包含条件、接收按键(ibus 规格的键名,可加修饰符,如 「control+return」)、发送按键 - when: paging # 仅当已发生向后换页时 accept: comma # 将「逗号」键…… send: page_up # 关联到「向前换页」;于是 navigator 将收到一发 page_up - when: has_menu # 只要有候选字即满足条件 accept: period send: page_down 修炼之道 与 「 大家好 」这个方案不同。以下一组示例,主要演示如何活用符号键盘,以及罗马字转写式输入。\n¹改造键盘 不要以为 「 大家好 」是最最简单的输入方案。码表式输入法,不如键盘式输入法来得简单明快!\n用 punctuator 这一套組件,就可实现一款键盘输入法:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 # rime schema # encoding: utf-8 schema: schema_id: numbers name: 数字之道 version: \u0026#34;1\u0026#34; engine: processors: - punctuator - express_editor segmentors: - punct_segmentor translators: - punct_translator punctuator: half_shape: \u0026amp;symtable \u0026#34;1\u0026#34; : 一 \u0026#34;2\u0026#34; : 二 \u0026#34;3\u0026#34; : 三 \u0026#34;4\u0026#34; : 四 \u0026#34;5\u0026#34; : 五 \u0026#34;6\u0026#34; : 六 \u0026#34;7\u0026#34; : 七 \u0026#34;8\u0026#34; : 八 \u0026#34;9\u0026#34; : 九 \u0026#34;0\u0026#34; : 〇 \u0026#34;s\u0026#34; : 十 \u0026#34;b\u0026#34; : 百 \u0026#34;q\u0026#34; : 千 \u0026#34;w\u0026#34; : 萬 \u0026#34;n\u0026#34; : 年 \u0026#34;y\u0026#34; : [ 月, 元, 億 ] \u0026#34;r\u0026#34; : 日 \u0026#34;x\u0026#34; : 星期 \u0026#34;j\u0026#34; : 角 \u0026#34;f\u0026#34; : 分 \u0026#34;z\u0026#34; : [ 之, 整 ] \u0026#34;d\u0026#34; : 第 \u0026#34;h\u0026#34; : 號 \u0026#34;.\u0026#34; : 點 full_shape: *symtable 对,所谓「键盘输入法」,就是按键选字直接对应的输入方式。\n这次,不再写 punctuator/import_preset 这项,而是自订了一套符号表。\n哦!原来 punctuator 不单可以用来打出标点符号;还可以重定义空格以及全部 94 个可打印 ascii 字符(码位 0x20 至 0x7e)。\n在符号表代码里,用对应的 ascii 字符表示按键。记得这些按键字符要放在引号里面,yaml 才能夠正确解析喔。\n示例代码表演了两种符号的映射方式:一对一及一对多。一对多者,按键后符号不会立即上屏,而是……嘿嘿,自己体验吧 🤣\n关于代码里 symtable 的一点解释:\n这是 yaml 的一种语法,\u0026amp;symtable 叫做「锚点标签」,给紧随其后的内容起个名字叫 symtable; *symtable 则相当于引用了 symtable 所标记的那段內容,从而避免重复。\nrime 里的符号有「全角」、「半角」两种状态。本方案里暂不作区分,教 half_shape、full_shape 使用同一份符号表。\n²大写数字键盘 灵机一动,不如复用「全、半角」模式来区分「大、小写」中文数字!\n1 2 3 4 5 6 7 8 9 schema: # ... version: \u0026#34;2\u0026#34; switches: - name: full_shape states: [ 小写, 大寫 ] # ... 先来定义状态开关:0 能改「 半角 」为「 小写 」,1 能改「 全角 」为「 大寫 」。\n这样一改,再打开「 方案选单 」,方案「 数字之道 」底下就会多出个「 小写→大寫」的选项,每选定一次、状态随之反转一次。\n接着给 half_shape、full_shape 定义不同的符号表:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 punctuator: half_shape: \u0026#34;1\u0026#34; : 一 \u0026#34;2\u0026#34; : 二 \u0026#34;3\u0026#34; : 三 \u0026#34;4\u0026#34; : 四 \u0026#34;5\u0026#34; : 五 \u0026#34;6\u0026#34; : 六 \u0026#34;7\u0026#34; : 七 \u0026#34;8\u0026#34; : 八 \u0026#34;9\u0026#34; : 九 \u0026#34;0\u0026#34; : 〇 \u0026#34;s\u0026#34; : 十 \u0026#34;b\u0026#34; : 百 \u0026#34;q\u0026#34; : 千 \u0026#34;w\u0026#34; : 萬 \u0026#34;n\u0026#34; : 年 \u0026#34;y\u0026#34; : [ 月, 元, 億 ] \u0026#34;r\u0026#34; : 日 \u0026#34;x\u0026#34; : 星期 \u0026#34;j\u0026#34; : 角 \u0026#34;f\u0026#34; : 分 \u0026#34;z\u0026#34; : [ 之, 整 ] \u0026#34;d\u0026#34; : 第 \u0026#34;h\u0026#34; : 號 \u0026#34;.\u0026#34; : 點 full_shape: \u0026#34;1\u0026#34; : 壹 \u0026#34;2\u0026#34; : 貳 \u0026#34;3\u0026#34; : 參 \u0026#34;4\u0026#34; : 肆 \u0026#34;5\u0026#34; : 伍 \u0026#34;6\u0026#34; : 陸 \u0026#34;7\u0026#34; : 柒 \u0026#34;8\u0026#34; : 捌 \u0026#34;9\u0026#34; : 玖 \u0026#34;0\u0026#34; : 零 \u0026#34;s\u0026#34; : 拾 \u0026#34;b\u0026#34; : 佰 \u0026#34;q\u0026#34; : 仟 \u0026#34;w\u0026#34; : 萬 \u0026#34;n\u0026#34; : 年 \u0026#34;y\u0026#34; : [ 月, 圓, 億 ] \u0026#34;r\u0026#34; : 日 \u0026#34;x\u0026#34; : 星期 \u0026#34;j\u0026#34; : 角 \u0026#34;f\u0026#34; : 分 \u0026#34;z\u0026#34; : [ 之, 整 ] \u0026#34;d\u0026#34; : 第 \u0026#34;h\u0026#34; : 號 \u0026#34;.\u0026#34; : 點 哈,调出选单切换一下大小写,输出的字全变样!酷。\n但是要去选单切换,总不如按下 shift 就全都有了:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 punctuator: half_shape: # ... 添加以下這些 \u0026#34;!\u0026#34; : 壹 \u0026#34;@\u0026#34; : 貳 \u0026#34;#\u0026#34; : 參 \u0026#34;$\u0026#34; : [ 肆, ¥, \u0026#34;$\u0026#34;, \u0026#34;€\u0026#34;, \u0026#34;£\u0026#34; ] \u0026#34;%\u0026#34; : [ 伍, 百分之 ] \u0026#34;^\u0026#34; : 陸 \u0026#34;\u0026amp;\u0026#34; : 柒 \u0026#34;*\u0026#34; : 捌 \u0026#34;(\u0026#34; : 玖 \u0026#34;)\u0026#34; : 零 \u0026#34;s\u0026#34; : 拾 \u0026#34;b\u0026#34; : 佰 \u0026#34;q\u0026#34; : 仟 \u0026#34;y\u0026#34; : 圓 于是在「 小写 」状态,只要按 shift + 数字键 即可打出大写数字。\n用了几下,发现一处小小的不满意:敲 $ 这个键,可选的符号有一个之多。想要打出欧元、英磅符号只得多敲几下 $ 键使用想要的符号高亮。但是按上、下方向键并没有效果,按符号前面标示的数字序号,更是不仅上屏了错误的符号、还多上屏一个数字。\n这反映出两个问题。\n① 是 selector 组件缺席使得选字、移动选字光标的动作未得到响应。立即加上:\n1 2 3 4 5 6 7 8 # ... engine: processors: - punctuator - selector # 加在这里 - express_editor # ... 因为要让 punctuator 来转换数字键,所有 selector 得放在它后头。\n② 是无法用数字序号选字。为解决这个冲突,改用闲置的字母键来选字:\n1 2 3 4 # ... menu: alternative_select_keys: \u0026#34;acegi\u0026#34; 完工。\n³罗马字之道 毕竟,键盘上只有 7 个字符按键、94 个编码字符,对付百十个字还管使。可要输入上千个常用汉字,嫌键盘式输入的编码空间太小,必得采用字符编码。\n罗马字,以拉丁字母的特定排列作为汉语音节的转写形式。一个音节代表一组同音字,再由音节拼写组合词、句。\n凡此单字(音节)编码自然连用而生词、句的输入法,皆可用 script_translator 组件完成基于音节码切分的智能词句转换。它有个别名 r10n_translator —— r10n 为 romanization 的简写。但不限于「 拼音、注音、双拼」等一族基于语音编码的输入法。形式相似者,如「 速成 」,难以字形为本,亦可应用。\n现在来把「 数字之道 」改成 「 拼音→中文数字」的变换。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 schema: schema_id: numbers name: 数字之道 version: \u0026#34;3\u0026#34; engine: processors: - speller - punctuator - selector - express_editor segmentors: - abc_segmentor - punct_segmentor translators: - punct_translator - script_translator translator: dictionary: numbers punctuator: half_shape: \u0026amp;symtable \u0026#34;!\u0026#34; : 壹 \u0026#34;@\u0026#34; : 貳 \u0026#34;#\u0026#34; : 參 \u0026#34;$\u0026#34; : [ 肆, ¥, \u0026#34;$\u0026#34;, \u0026#34;€\u0026#34;, \u0026#34;£\u0026#34; ] \u0026#34;%\u0026#34; : [ 伍, 百分之 ] \u0026#34;^\u0026#34; : 陸 \u0026#34;\u0026amp;\u0026#34; : 柒 \u0026#34;*\u0026#34; : 捌 \u0026#34;(\u0026#34; : 玖 \u0026#34;)\u0026#34; : 零 \u0026#34;s\u0026#34; : 拾 \u0026#34;b\u0026#34; : 佰 \u0026#34;q\u0026#34; : 仟 \u0026#34;w\u0026#34; : 萬 \u0026#34;n\u0026#34; : 年 \u0026#34;y\u0026#34; : [ 月, 圓, 億 ] \u0026#34;r\u0026#34; : 日 \u0026#34;x\u0026#34; : 星期 \u0026#34;j\u0026#34; : 角 \u0026#34;f\u0026#34; : 分 \u0026#34;z\u0026#34; : [ 之, 整 ] \u0026#34;d\u0026#34; : 第 \u0026#34;h\u0026#34; : 號 \u0026#34;.\u0026#34; : 點 full_shape: *symtable 符号表里,把小写字母、数字键都空出来了。小写字母用来拼音,数字键用来选重。重点是本次用了 script_translator 这组件。与 table_translator 相似,该组件与 translator/dictionary 指名的词典相关联。\n编制词典:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # rime dictionary # encoding: utf-8 --- name: numbers version: \u0026#34;1\u0026#34; sort: by_weight use_preset_vocabulary: true ... 一\tyi 二\ter 三\tsan 四\tsi 五\twu 六\tliu 七\tqi 八\tba 九\tjiu 〇\tling 零\tling 十\tshi 百\tbai 千\tqian 萬\twan 億\tyi 年\tnian 月\tyue 日\tri 星\txing 期\tqi 時\tshi 分\tfen 秒\tmiao 元\tyuan 角\tjiao 之\tzhi 整\tzheng 第\tdi 號\thao 點\tdian 是\tshi 码表里给出了一个「 示例 」规格的小字集。其中包含几组重码字。\n要诀 sort: by_weight 意图是不以码表的顺序排列码字,而是比较字频。那字频呢?没写出来。\n要诀 use_preset_vocabulary: true 用在输入方案需要支持输入词组、而码表中词组相对匮乏时。编译输入方案期间引入 rime 预设的「 八股文 」词汇 - 及词频资料!这就是码表中未具体字频的原因。\n使用「 八股文 」,要注音码表所用的字形是否与该词汇表一致。八股文的词汇及词频统计都遵照 opencc 繁体字形标准。\n如果缺少单字的编码定义,自然也无法导入某些词汇。所以本方案吸有导入这个数字「 小字集 」上的词汇。\n⁴用拼写运算定义简码 如今有了一款专门输入数字的拼音输入法。比一比升阳拼音、朙月拼音和地球拼音,还有哪里不一样?\n很快我发现敲 xingqiwu 或 xingqiw 都可得到来自「 八股文 」的词组 “星期五”,这很好。可以敲 xqw 怎会不中呢?\n原来 script_translator 罗马字中译的方法是,将输入码序列切分为音节表中的拼写形式,再按音节查词典。不信你找本词典瞧瞧,是不是按完整的拼音(注音)编排的。rime 词典也一样,并没有 xqw 这样的检索码。\n现在我要用 rime 独门绝活「 拼写运算 」 来定义一种 「 音序查字法 」。令 x 作 xing 的简码, q 作数字之道所有音节中起首为 q 者的简码,即略代音节 qi 与 qian。\n汉语拼音里还有三个双字母的声符, zh、ch、sh 也可以做简码。\n添加拼写运算规则:\n1 2 3 4 5 6 7 8 9 10 schema: # ... version: \u0026#34;4\u0026#34; #... speller: algebra: - \u0026#39;abbrev/^([a-z]).+$/$1/\u0026#39; - \u0026#39;abbrev/^([zcs]h).+$/$1/\u0026#39; 如此 rime 便知,除了码表里那些拼音,还有若干简码也是行得通的拼写形式。再输入 xqw,rime 将它拆开 x'q'w,再默默对应到音节码 xing'qi'wan、xing'qi'wu、xing'qian'wan 等等,一翻词典就得到了一个好词「 星期五 」,而其他的组合都说不通。\n现在有没有悟到,罗马字转写式输入法与码表式输入法理念上的不同?\n哈,做中了。试试看 sss,sss,sssss,sssss 。\n却好像不是我要的 「四是四,十是十,十四是十四,四十是四十」……\n好办。如果某些词汇在方案里很重要,「 八股文 」又未收录,那么,请添加到码表:\n1 2 3 4 5 6 7 8 9 10 11 12 13 --- name: numbers version: \u0026#34;2\u0026#34; sort: by_weight use_preset_vocabulary: true ... # ... 四是四 十是十 十四是十四 四十是四十 善哉。演示完毕。当然休想就此把 rime 全盘掌握了。一本《指南书》,若能让读者入门,我只能说「 善哉~ 」\n再往后,就只有多读代码,就能见识到各种新颖、有趣的玩法。\n最高武艺 ⚠️ 警告,最后这部戏,对智力、技术功底的要求不一般。如果读不下去,不要怪我、不要怀疑自己的智商。\n即使路过本节也无妨,只是不可忽略了正文「 关于调试 」这一节。(重要哇……)\n请检查是否:\n已将前两组实例分析透彻 学习完了《 拼写运算 》 知道双拼是什么 预习 rime 预设输入方案之「 朙月拼音 」 设计一款「 智能 abc 双拼 」输入方案做练习!\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 # rime schema # encoding: utf-8 schema: schema_id: double_pinyin_abc # 专有的方案标识 name: 智能abc双拼 version: \u0026#34;0.9\u0026#34; author: - 佛振 \u0026lt;chen.sst@gmail.com\u0026gt; description: | 朙月拼音,兼容智能abc双拼方案。 switches: - name: ascii_mode reset: 0 states: [ 中文, 西文 ] - name: full_shape states: [ 半角, 全角 ] - name: simplification states: [ 漢字, 汉字 ] engine: processors: - ascii_composer - recognizer - key_binder - speller - punctuator - selector - navigator - express_editor segmentors: - ascii_segmentor - matcher - abc_segmentor - punct_segmentor - fallback_segmentor translators: - echo_translator - punct_translator - script_translator - reverse_lookup_translator filters: - simplifier - uniquifier speller: alphabet: zyxwvutsrqponmlkjihgfedcba # 呃,倒背字母表完全是个人喜好 delimiter: \u0026#34; \u0026#39;\u0026#34; # 隔音符号用「\u0026#39;」;第一位的空白用來自动插入到音节边界处 algebra: # 拼写运算规则,这个就是实现双拼方案的重点。写法有很多种,当然也可以把四百多个音节码一条条都列举 - erase/^xx$/ # 码表中有几個拼音不明的字,编码成xx了,消灭它 - derive/^([jqxy])u$/$1v/ - xform/^zh/a/ # 替换声母键,用大写以防与原有的字母混淆 - xform/^ch/e/ - xform/^sh/v/ - xform/^([aoe].*)$/o$1/ # 添上固定的零声母o,先标记为大写o - xform/ei$/q/ # 替换韵母键 - xform/ian$/w/ # ※2 - xform/er$|iu$/r/ # 对应两种韵母的;音节 er 现在变为 or 了 - xform/[iu]ang$/t/ # ※1 - xform/ing$/y/ - xform/uo$/o/ - xform/uan$/p/ # ※3 - xform/i?ong$/s/ - xform/[iu]a$/d/ - xform/en$/f/ - xform/eng$/g/ - xform/ang$/h/ # 检查一下在此之前是否已转换过了帶介音的ang;好,※1处有了 - xform/an$/j/ # 如果※2、※3还没有出现在上文中,应该把他们提到本行之前 - xform/iao$/z/ # 对——像这样让iao提前出场 - xform/ao$/k/ - xform/in$|uai$/c/ # 让uai提前出场 - xform/ai$/l/ - xform/ie$/x/ - xform/ou$/b/ - xform/un$/n/ - xform/[uv]e$|ui$/m/ - xlit/qwertyopasdfghjklzxcvbnm/qwertyopasdfghjklzxcvbnm/ # 最后把双拼码全部变小写 translator: dictionary: luna_pinyin # 与【朙月拼音】共用词典 prism: double_pinyin_abc # prism 要以本输入方案的名称来命名,以免把朙月拼音的拼写映射表覆盖掉 preedit_format: # 这段代码用来将输入的双拼码反转为全拼显示;若想直接显示双拼码可以把这段注释掉 - xform/o(\\w)/0$1/ # 零声母先改为0,以方便后面转换 - xform/(\\w)q/$1ei/ # 双拼第二码转换为韵母 - xform/(\\w)n/$1un/ # 提前转换双拼码 n 和 g,因为转换后的拼音里就快要出现这两个字母了,那时将难以分辨出双拼码 - xform/(\\w)g/$1eng/ # 当然也可以采取事先将双拼码变为大写的办法来与转换过的拼音做区分,可谁让我是高手呢 - xform/(\\w)w/$1ian/ - xform/([dtnljqx])r/$1iu/ # 对应多种总线的双拼码,按搭配的声母做区分(最好别用排除式如 [^o]r 容易出状况) - xform/0r/0er/ # 另一种情况,注音先不消除0,以防后面把e当作声母转换为ch - xform/([nljqx])t/$1iang/ - xform/(\\w)t/$1uang/ # 上一行已经把对应到 iang 的双拼码 t 消减,于是这里不用再列举相配的声母 - xform/(\\w)y/$1ing/ - xform/([dtnlgkhaevrzcs])o/$1uo/ - xform/(\\w)p/$1uan/ - xform/([jqx])s/$1iong/ - xform/(\\w)s/$1ong/ - xform/([gkhaevrzcs])d/$1ua/ - xform/(\\w)d/$1ia/ - xform/(\\w)f/$1en/ - xform/(\\w)h/$1ang/ - xform/(\\w)j/$1an/ - xform/(\\w)k/$1ao/ # 默默检查:双拼码 o 已经转换过了 - xform/(\\w)l/$1ai/ - xform/(\\w)z/$1iao/ - xform/(\\w)x/$1ie/ - xform/(\\w)b/$1ou/ - xform/([nl])m/$1ve/ - xform/([jqxy])m/$1ue/ - xform/(\\w)m/$1ui/ - \u0026#34;xform/(^|[ \u0026#39;])a/$1zh/\u0026#34; # 复原声母,音节开始处的双拼字母 a 改写为 zh;其他位置的才真正是 a - \u0026#34;xform/(^|[ \u0026#39;])e/$1ch/\u0026#34; - \u0026#34;xform/(^|[ \u0026#39;])v/$1sh/\u0026#34; - xform/0(\\w)/$1/ # 好了,現在可以把零声母拿掉啦 - xform/([nljqxy])v/$1ü/ # 这要就是汉语拼音 :-) reverse_lookup: dictionary: cangjie5 prefix: \u0026#34;`\u0026#34; tips: 〔仓颉〕 preedit_format: - \u0026#34;xlit|abcdefghijklmnopqrstuvwxyz|日月金木水火土竹戈十大中一弓人心手口尸廿山女田難卜符|\u0026#34; comment_format: - xform/([nl])v/$1ü/ punctuator: import_preset: default key_binder: import_preset: default recognizer: import_preset: default patterns: reverse_lookup: \u0026#34;`[a-z]*$\u0026#34; 完毕。\n这是一道大题。通过改造拼写法而创作出新的输入方案。\n标准库 如果需要制作完全属于自己的输入方案,少不了要了解 rime 的标准库。些时,可以参阅 《rime方案制作详解》。更多新意,就在你的笔下!\n关于调试 如此复杂的输入方案,很可能需要反复调试方可达到想要的结果。\n请于试验时及时查看日志中是否包含错误信息。日志文件位于:\n【中州韵】 /tmp/rime.ibus.* 【小狼毫】 %temp%\\rime.weasel.* 【鼠须管】 $tmpdir/rime.squirrel.* 各发行版的早期版本 用戶資料夾/rime.log 按照日志级别分为 info/信息、warning/警告、error/错误。后两类应重点关注,如果新方案部署后不可用或输出与设计不一致,原因可能在此。\n没有任何错误信息,就是不好使,有可能是码表本身的问题,比如把码表中文字和编码两列弄颠倒了 \u0026ndash; rime 等你输入由汉字组成的编码,然而键盘没有可能做到这一点(否则也不再需要输入法了)。\n后续有计划为输入方案创作者开发名为 「拼写运算调试器」 的工具,能够较直观都看到第一步拼写运算的结果。有助于定义双拼这样大量使用拼写运算的方案。\n方案制作详解 更多参阅 雪齐的文档 。\n开始之前 # rime schema\r# encoding: utf-8 描述档 ¹ name:方案的显示名称(即出现于方案选单中的方案名称,通常为中文)\n² schema_id:方案内部名,在代码中引用此方案时以此名为正,通常由英文、数字、下划线组成。\n³ author :发明人、撰写者。如果你对方案做出了修改,请保留原作者名,并将自己的名字加在后面。\n⁴ description:简要描述方案历史、码表来源、该方案规则等。\n⁵ dependencies:如果本方案信赖于基字方案(通常来说会信赖其它方案做为反查,抑或是两种或多种方案混用时)。\n⁶ version:版本号,在发布新版前请确保已升级版本号。\nschema:\rname: \u0026#34;仓颉检字法\u0026#34;\rschema_id: cangjie6\rauthor:\r- \u0026#34;发明人 朱邦復先生、沈紅蓮女士\u0026#34;\rdependencies:\r- luna_pinyin\r- jyutping\r- zyenpheng\rdescription: |\r第六代仓颉输入法\r码表由雪齋、惜緣和crazy4u整理\rversion: 0.19 开关 通常包含以下数个,但并不限于此,自定义滤镜皆可设置开关调控。\n¹ ascii_mode:是中英文转换开关,默认 0 为中文,1 为英文。\n² full_shape:是全角符号 / 半角符号开关。0 为半角,1 为全角。注音,开启全角时英文字母亦为全角。\n³ extended_charset:是字符集开关。0 为 cjk 基本字符集,1 为 cjk 全字符集。(仅 table_translator 可用)\n⁴ ascii_punct:是中西文标点转换开关,0 为中文标点,1 为英文标点。\n⁵ simplification :是转化字开关。一般情况下与上同,0 为不开启转化,1 为开启转化。\n所有开关名称可自定义,可用快捷键切换。该名称可用于 key_binder/bindings 中的 toggle: 后。\n- name: simplification\rstates: [\u0026#34;漢字\u0026#34;, \u0026#34;汉字\u0026#34;]\rreset: 0 亦可使用多选开关,同样支持快捷键:options 名称用于 key_binder/bindings 时,使用 set_option: 或 unset_option: 开启或关闭某一个。\n- options: [ zh_trad, zh_cn, zh_mars ]\rstates:\r- 字型 → 漢字\r- 字型 → 汉字\r- 字型 → 䕼茡\rreset: 0 其中:\nname / options 名:需与 simplifier 中 option_name 相同; states:可不写,如不写则此开关存在但不可见,可由快捷键操作; reset:设定默认状态(reset 可不写,此时切换窗口时不会重置到默认状态)。 switches:\r- name: ascii_mode\rreset: 0\rstates: [\u0026#34;中文\u0026#34;, \u0026#34;西文\u0026#34;]\r- name: full_shape\rstates: [\u0026#34;半角\u0026#34;, \u0026#34;全角\u0026#34;]\r- name: extended_charset\rstates: [\u0026#34;通用\u0026#34;, \u0026#34;增廣\u0026#34;]\r- name: simplification\rstates: [\u0026#34;漢字\u0026#34;, \u0026#34;汉字\u0026#34;]\r- name: ascii_punct\rstates: [\u0026#34;句讀\u0026#34;, \u0026#34;符號\u0026#34;] 引擎 以下加粗项为可细配者,斜体为不常用者。\n引擎分四组。\n❶ processors\n这批组件处理各类按键消息。\n¹ ascii_composer 处理西文模式及中西文切。\n² recognizer 与 matcher 搭配,处理符合特定规则的输入码,如网址、反查等 tags。\n³ key_binder 在特定条件下将按键绑定到其他按键,如重定义逗号、句号为候选翻页、开关快捷键等。\n⁴ speller 拼写处理器,接受字符按键,编辑输入。\n⁵ punctuator 句读处理器,将单个字符按键直接映射为标点符号或文字。\n⁶ selector 选字处理器,处理数字选字键(可以换成别的)、上、下选定位、换页。\n⁷ navigator 处理输入栏内的光标移动。\n⁸ express_editor 编辑器,处理空格、回车上屏、回退键。\n⁹ fluid_editor 句式编辑器,用于以空格断词、回车上屏的「 注音、语句流 」等输入方案,替换 express_editor。\n¹⁰ chord_composer 和弦作曲家或曰并击处理器,用于「 宫保拼音 」等多键并击的输入方案。\n¹¹ lua_processor 使用 lua 自定义按键,后接 @ + lua 函数名 。lua 函数名 即用户文件夹内 rime.lua 中的函数名,参数为 (key, env)。\n:: 当然,现在的版本已经支持 *.lua 脚本自动执行了。\n❷ segmentors\n这批组件识别不同内容类型,将输入码分段并加上 tag 。\n¹ ascii_segmentor 标识西文段落(比如在西文模式下)字母直接上屏。\n² matcher 配合 recognizer 标识符合特定规则的段落,如网址、反查等,加上选定 tag 。\n³ abc_segmentor 标识常规的文字段落,加上 abc 这个 tag 。\n⁴ punct_segmentor 标识句读段落(键入标点符号用),加上 punct 这个 tag。\n⁵ fallback_segmentor 标识其他示标识段落。\n⁶ affix_segmentor 用户自定义 tag。此项可加载多个实例,后接 @ + tag 名。\n⁷ lua_segmentor 使用 lua 自定义切分,后接 @ + tag 函数名。\n❸ translators\n这批组件翻译特定类型的编码段为一组候选文字。\n¹ echo_translator 没有其它候选字时,回显输入码(输入码可以 shift + enter 上屏)。\n² punct_translator 配合 punct_segmentor 转换标点符号。\n³ table_translator 码表翻译器,用于仓颉、五笔等基于码表的输入方案。此项可加载多个实例,后接 @ + 翻译器名 (如 cangjie 、wubi)等。\n⁴ script_translator 脚本翻译器,用于拼音、粤拼等基于音节表的输入方案。此项可加载多个实例,后接 @ + 翻译器名 (如 pinyin 、jyutping)等。\n⁵ reverse_lookup_translator 反查翻译器,用另一种编码方案查码。\n⁶ history_translator 产生 commit 历史候选。其中:\ntag 同 translator 说明; initial_quality 同 translator 说明; size 设定候选记录量,预设为 0 等同(commit_record 最大储存量为 20); input 触发此翻译器的字串。 ⁷ lua_translator 使用 lua 自定义输入,例如动态输入当前日期、时间,后接 @ + lua 函数名。其中:\nlua 函数名即用户文件夹内 rime.lua 中函数名,参数为 (input, seg, env); 可以 env.engine.context:get_option(\u0026quot;option_name\u0026quot;) 方式绑定到 switch 开关 / key_binder 快捷键。 ❹ filters\n这批组件过滤翻译的结果,自定义滤镜皆可使用开关调控。\n¹ uniquifier 过滤重复的候选字,有可能来自 simplifier。\n² cjk_minifier 字符集过滤(仅用于 script_translator,使之支援 extended_charset 开关)。\n³ single_char_filter 单字过滤器,如加载此组件,则屏蔽词典中的词组(仅 table_translator 有效)。\n⁴ simplifier 用字转换。\n⁵ reverse_lookup_filter 反查滤镜,以更灵活的方式反查,rime1.0 后替代 reverse_lookup_translator。此项可加载多个实例,后接 @ +滤镜名〔如:pinyin_lookup、jyutping_lookup 等〕。\n⁶ lua_filter 使用 lua 自定义过滤,例如过滤字符集、高速排序,后接 @ + lua 函数名。其中:\nlua 函数名即用户文件夹内 rime.lua 中函数名,参数为 (input, seg, env); 可以 env.engine.context:get_option(\u0026quot;option_name\u0026quot;) 方式绑定到 switch 开关 / key_binder 快捷键。 \u0026gt; cangjie6.schema.yaml\nengine:\rprocessors:\r- ascii_composer\r- recognizer\r- key_binder\r- speller\r- punctuator\r- selector\r- navigator\r- express_editor\rsegmentors:\r- ascii_segmentor\r- matcher\r- affix_segmentor@pinyin\r- affix_segmentor@jyutping\r- affix_segmentor@pinyin_lookup\r- affix_segmentor@jyutping_lookup\r- affix_segmentor@reverse_lookup\r- abc_segmentor\r- punct_segmentor\r- fallback_segmentor\rtranslators:\r- punct_translator\r- table_translator\r- script_translator@pinyin\r- script_translator@jyutping\r- script_translator@pinyin_lookup\r- script_translator@jyutping_lookup\r- lua_translator@get_date\rfilters:\r- simplifier@zh_simp\r- uniquifier\r- cjk_minifier\r- reverse_lookup_filter@middle_chinese\r- reverse_lookup_filter@pinyin_reverse_lookup\r- reverse_lookup_filter@jyutping_reverse_lookup\r- lua_filter@single_char_first 细项配置 凡 comment_format、preedit_format、speller/algebra 所用之正则表达式,请参阅 「perl 正则表达式」 。\n引擎中所举之加粗者堍可在正文详细描述,格式为:\nname:\rbranches: configurations 或\nname:\rbranches:\r- configurations speller ¹ alphabet: 定义本方案定义本方案输入键。\n² initials: 定义仅作始码之键。\n³ finals: 定义仅作末码之键。\n⁴ delimiter: 上屏时的音节间分音符。\n⁵ algebra: 拼写运算规则,由之算出的拼写汇入 prism 中。\n⁶ max_code_length: 形码最大码长,超过则顶字上屏(number)。\n⁷ auto_select: 自动上屏( true 或 false)。\n⁸ auto_select_pattern: 自动上屏规则,以正则表达式描述,当输入串可以被匹配时自动顶字上屏。\n⁹ use_space: 以空格作输入码(true 或 false)。\nspeller 的演算包含:\nxform -- 改写〔不保留原形〕\rderive -- 衍生〔保留原形〕\rabbrev -- 简拼〔出字优先级较上两组更低〕\rfuzz -- 略拼〔此种简拼仅组词,不出单字〕\rxlit -- 变换〔适合大量一对一变换〕\rerase -- 刪除 \u0026gt; luna_pinyin.schema.yaml\nspeller:\ralphabet: zyxwvutsrqponmlkjihgfedcba\rdelimiter: \u0026#34; \u0026#39;\u0026#34;\ralgebra:\r- erase/^xx$/\r- abbrev/^([a-z]).+$/$1/\r- abbrev/^([zcs]h).+$/$1/\r- derive/^([nl])ve$/$1ue/\r- derive/^([jqxy])u/$1v/\r- derive/un$/uen/\r- derive/ui$/uei/\r- derive/iu$/iou/\r- derive/([aeiou])ng$/$1gn/\r- derive/([dtngkhrzcs])o(u|ng)$/$1o/\r- derive/ong$/on/\r- derive/ao$/oa/\r- derive/([iu])a(o|ng?)$/a$1$2/ segmentor segmentor 配合 recognizer s 标记出 tag,这里会用到 affix_segmentor 和 abc_segmentor。\ntag 用在 translator、reverse_lookup_filter、simplifier 中用以标定各自作用范围。如果不需要用到 extra_tags 则不需要单独配置 segmentor。其中:\ntag: 设定其 tag ; prefix: 设定其前缀标识,可不填,不填则无前缀; suffix: 设定其尾缀标识,可不填,不填则无尾缀; tips: 设定其输入前提示符,可不填,不填则无提示符; closing_tips: 设定其结束输入提示符,可不填,不填则无提示符; extra_tags: 为此 segmentor 所标记的段落插上其它 tag 。 当 affix_segmentor 和 translator 重名时,两者可并在一直配置,此处1-5 条对应下面 21-25 条。abc_segmentor 仅可设 extra_tags。\n\u0026gt; cangjie6.schema.yaml\nreverse_lookup:\rtag: reverse_lookup\rprefix: \u0026#34;`\u0026#34;\rsuffix: \u0026#34;;\u0026#34;\rtips: \u0026#34;【反查】\u0026#34;\rclosing_tips: \u0026#34;【蒼頡】\u0026#34;\rextra_tags:\r- pinyin_lookup\r- jyutping_lookup translator 每个方案都有一个主 translator,在引擎列表不以 @ + 翻译器名定义,在细项配置时直接以 translator: 命名。以下加粗项为可以主 translator 中定义之项,其它可在副〔以 @ + 翻译器名命名〕 translator 中定义。\n¹ enable_charset_filter: 是否开启字符集过滤〔仅 table_translator 有效。启用 cjk_minifier 后可适用于 script_translator 〕\n² enable_encoder: 是否开启自动造词〔仅 table_translator 有效〕\n³ encode_commit_history: 是否对已上屛词自动成词〔仅 table_translator 有效〕\n⁴ max_phrase_length: 最大自动成词词长〔仅 table_translator 有效〕\n⁵ enable_completion: 提前显示尚未输入完整码的字〔仅 table_translator 有效〕\n⁶ enable_correction: 启用自动纠错〔仅 script_translator 有效〕\n⁷ sentence_over_completion: 在无全码对应字而仅有逐键提示时也开启智能组句〔仅 table_translator 有效〕\n⁸ strict_spelling: 配合 speller 中的 fuzz 规则,仅以略拼码组词〔仅 table_translator 有效〕\n⁹ disable_user_dict_for_patterns: 禁止某些编码录入用户词典\n¹⁰ enable_sentence: 是否开启自动造句\n¹¹ enable_user_dict: 是否开启用户词典〔 用户词典记录动态字词频、用户词〕,以上选填 true 或 false 。\n¹². dictionary: 翻译器将调取此字典文件\n¹³ prism: 设定由此主翻译器的 speller 生成的棱鏡文件名,或此副编译器调用的棱鏡名\n¹⁴ user_dict: 设定用戶詞典名\n¹⁵ db_class: 设定用戶词典类型,可设 tabledb 〔文本〕或 userdb 〔二进制〕\n¹⁶ preedit_format: 上屛码自定义\n¹⁷ comment_format: 提示码自定义\n¹⁸ spelling_hints: 设定多少字以內候选标注完整带拼音〔仅 script_translator 有效〕\n¹⁹ always_show_comments: 始终显示提示码〔仅 script_translator 有效〕\n²⁰ initial_quality: 设定此翻译器出字优先级\n²¹ tag: 设定此翻译器针对的 tag。可不填,不填则仅针对 abc\n²² prefix: 设定此翻译器的前缀标识,可不填,不填则无前缀\n²³ suffix: 设定此翻译器的尾缀标识,可不填,不填则无尾缀\n²⁴ tips: 设定此翻译器的输入前提示符,可不填,否则则无提示符\n²⁵ closing_tips: 设定此翻译器的结束输入提示符,可不填,不填则无提示符\n²⁶ contextual_suggestions: 是否使用语言模型优化输出结果〔需配合 grammar 使用〕\n²⁷ max_homophones: 最大同音簇长度〔需配合 grammar 使用〕\n²⁸ max_homographs: 最大同形簇长度〔需配合 grammar 使用〕\n\u0026gt; cangjie6.schema.yaml 仓颉主翻译器\ntranslator:\rdictionary: cangjie6\renable_charset_filter: true\renable_sentence: true\renable_encoder: true\rencode_commit_history: true\rmax_phrase_length: 5\rpreedit_format:\r- xform/^([a-z ])$/$1|\\u$1\\e/\r- xform/(?\u0026lt;=[a-z])\\s(?=[a-z])//\r- \u0026#34;xlit|abcdefghijklmnopqrstuvwxyz|日月金木水火土竹戈十大中一弓人心手口尸廿山女田止卜片|\u0026#34;\rcomment_format:\r- \u0026#34;xlit|abcdefghijklmnopqrstuvwxyz~|日月金木水火土竹戈十大中一弓人心手口尸廿山女田止卜片・|\u0026#34;\rdisable_user_dict_for_patterns:\r- \u0026#34;^z.$\u0026#34;\rinitial_quality: 0.75 \u0026gt; cangjie6.schema.yaml 拼音副翻译器\npinyin:\rtag: pinyin\rdictionary: luna_pinyin\renable_charset_filter: true\rprefix: \u0026#39;p\u0026#39; # 需配合recognizer\rsuffix: \u0026#39;;\u0026#39; # 需配合recognizer\rpreedit_format:\r- \u0026#34;xform/([nl])v/$1ü/\u0026#34;\r- \u0026#34;xform/([nl])ue/$1üe/\u0026#34;\r- \u0026#34;xform/([jqxy])v/$1u/\u0026#34;\rtips: \u0026#34;【漢拼】\u0026#34;\rclosing_tips: \u0026#34;【蒼頡】\u0026#34; \u0026gt; pinyin_simp.schema.yaml 拼音・简化字主翻译器\ntranslator:\rdictionary: luna_pinyin\rprism: luna_pinyin_simp\rpreedit_format:\r- xform/([nl])v/$1ü/\r- xform/([nl])ue/$1üe/\r- xform/([jqxy])v/$1u/ \u0026gt; luna_pinyin.schema.yaml 朙月拼音用戶短语\ncustom_phrase: #這是一個table_translator\rdictionary: \u0026#34;\u0026#34;\ruser_dict: custom_phrase\rdb_class: tabledb\renable_sentence: false\renable_completion: false\rinitial_quality: 1 reverse_lookup_filter 此滤镜需挂在 translator 上,不影响该 translator 工作。\n¹ tags: 设定其作用范围。\n² overwrite_comment: 是否覆盖其他提示。\n³ dictionary: 反查所得提示码之码表。\n⁴ comment_format: 自定义提示码格式。\n⁵ apply_comment:\n\u0026gt; cangjie6.schema.yaml\npinyin_reverse_lookup: # 该反查滤镜名\rtags: [ pinyin_lookup ] # 挂在这个 tag 所对应的翻译器上\roverwrite_comment: true\rdictionary: cangjie6 # 反查所得为仓颉码\rcomment_format:\r- \u0026#34;xform/$/〕/\u0026#34;\r- \u0026#34;xform/^/〔/\u0026#34;\r- \u0026#34;xlit|abcdefghijklmnopqrstuvwxyz |日月金木水火土竹戈十大中一弓人心手口尸廿山女田止卜片、|\u0026#34; simplifier ¹ option_name: 对应 switches 中设定的切换项名,即 key_binder/binding 中所用名。\n² opencc_config: 用字转换配置文件。位于 rime_dir/opencc/,自带配置文件包含:\n繁转简〔默认〕:t2s.json 繁转台湾:t2tw.json 繁转香港:t2hk.json 简转繁:s2t.json ³ tags: 设定转换范围。\n⁴ tips: 设定是否显示转换前的字,可填 none(或不填)、char(仅对单字有效)、all 。\n⁵ comment_format: 自定义提示码格式。\n⁶ allow_erase_comment: 是否允许返回空提示码(默认 false)。\n⁷ show_in_comment: 设定是否将转换结果显示在备注中。\n⁸ excluded_types: 取消选定范围(一般为 reverse_lookup_translator )转化用字。\n修改自 luna_pinyin_kunki.schema\nzh_tw:\roption_name: zh_tw\ropencc_config: t2tw.json\rtags: [ abc ] # abc 对应 abc_segmentor\rtips: none\rallow_erase_comment: true\rcomment_format:\r- xform/.*// chord_composer 并击把键盘分两半,相当于两块键盘。两边同时击键,系统默认在其中一半上按的键等于另一半,由此得出上屏码。\n¹ alphabet: 字母表,包含用于并击的按键。击键虽有先后,形成并击时,一律以字母表顺序排列。\n² algebra: 拼写运算规则,将一组并击编码转换为拼音音节。\n³ output_format: 并击完成后套用的式样,追加隔音符号。\n⁴ prompt_format: 并击过程中套用的式样,加方括号弧。\n\u0026gt; combo_pinyin.schema.yaml\nchord_composer:\r# 字母表,包含用于并击的按键\r# 击键虽有先后,形成并击时,一律以字母表顺序排列\ralphabet: \u0026#34;swxdecfrvgtbnjum ki,lo.\u0026#34;\r# 拼写运算规则,将一级并击编码转换为拼音音节\ralgebra:\r# 先将物理按键字符对应到宫保拼音键位中的拼音字母\r- \u0026#39;xlit|swxdecfrvgtbnjum ki,lo.|sczhlfgdbktpriuvaniueoe|\u0026#39;\r# 以下根据宫保拼音的键位分别变换声母、韵母部分\r# 组合声母\r- xform/^zf/zh/\r- xform/^cl/ch/\r- xform/^fb/m/\r- xform/^ld/n/\r- xform/^hg/r/\r……\r# 声母独用时补足隐含的韵母\r- xform/^([bpf])$/$1u/\r- xform/^([mdtnlgkh])$/$1e/\r- xform/^([mdtnlgkh])$/$1e/\r- xform/^([zcsr]h?)$/$1i/\r# 并击完成后套用的式样,追加隔音符号\routput_format:\r- \u0026#34;xform/^([a-z]+)$/$1\u0026#39;/\u0026#34;\r# 并击过程中套用的式样,加方括弧\rprompt_format:\r- \u0026#34;xform/^(.*)$/[$1]/\u0026#34; lua 可以参考 extending rime with lua scripts 以寻求更多灵感。\nlua_translator lua_filter lua_processor lua_segmentor \u0026gt; rime.lua\nfunction get_date(input, seg, env)\r--- 以 show_date 为开关名或 key_binder 中 toggle 的对象\ron = env.engine.context:get_option(\u0026#34;show_date\u0026#34;)\rif (on and input == \u0026#34;date\u0026#34;) then\r--- candidate(type, start, end, text, comment)\ryield(candidate(\u0026#34;date\u0026#34;, seg.start, seg._end, os.date(\u0026#34;%y年%m月%d日\u0026#34;), \u0026#34; 日期\u0026#34;))\rend\rend\r---\rfunction single_char_first(input, env)\r--- 以 single_char 为开关名或 key_binder 中 toggle 的对象\ron = env.engine.context:get_option(\u0026#34;single_char\u0026#34;)\rlocal cache = {}\rfor cand in input:iter() do\rif (not on or utf8.len(cand.text) == 1) then\ryield(cand)\relse\rtable.insert(cache, cand)\rend\rend\rfor i, cand in ipairs(cache) do\ryield(cand)\rend\rend 其它 包括 recognizer、key_binder、punctuator。标点、快捷键、二三选重、特殊字符等均于此设置。\n¹ import_preset: 由外部统一文件导入。\n² grammar: 下设:\nlanguage: 取值 zh-han[ts]-t-essay-bg[wc] ; collocation_max_length: 最大搭配长度(整句输入可忽略此项); collocation_min_length: 最小搭配长度(整句输入可忽略此项)。 ³ recognizer: 下设 patterns: 配合 segmentor 的 prefix 和 suffix 完成段落划分、tag 分配。 前字段可以为以 affix_segmentor@sometag 定义的 tag 名,或者 punct、reverse_lookup 两个内设的字段。其它字段不调用输入法引擎,输入即输出(如 url 等字段)。\n⁴ key_binder: 下设 bindings: 设置功能性快捷键。\n每一条 binding 包含: when 作用范围、accept 实际所按之键,以及期望的操作。\n操作可为以下任意一个:send 输出按键、toggle 切换开关、send_sequence 输出一串按键、set_option 开某多选开关、unset_option 开某种多选开关、select 选候选字。\ntoggle 可用字段包含各开关名; set_option、unset_option 可用字段包含多选开关名; when 可用字段包含: - paging 翻页用\r- has_menu 操作候选项用\r- composing 操作输入码用\r- always 全域 accept 选 send 可用字段除 a-za-z0-9 外,还包含以下键盘上实际有的键: backspace\t退格\rtab\t水平定位符(制表符)\rlinefeed\t换行\rclear\t清除\rreturn\t回車\rpause\t暫停\rsys_req\t印屏\rescape\t退出\rdelete\t刪除\rhome\t原位\rleft\t左箭头\rup\t上箭头\rright\t右箭头\rdown\t下箭头\rprior、page_up\t上翻\rnext、page_down\t下翻\rend\t末位\rbegin\t始位\rshift_l\t左shift\rshift_r\t右shift\rcontrol_l\t左ctrl\rcontrol_r\t右ctrl\rmeta_l\t左meta\rmeta_r\t右meta\ralt_l\t左alt\ralt_r\t右alt\rsuper_l\t左super\rsuper_r\t右super\rhyper_l\t左hyper\rhyper_r\t右hyper\rcaps_lock\t大写锁\rshift_lock\t上档锁\rscroll_lock\t滚动锁\rnum_lock\t小键盘锁\rselect\t选定\rprint\t打印\rexecute\t执行\rinsert\t插入\rundo\t还原\rredo\t重做\rmenu\t菜单\rfind\t查寻\rcancel\t取消\rhelp\t帮助\rbreak\t中断\rspace\rexclam\t!\rquotedbl\t\u0026#34;\rnumbersign\t#\rdollar\t$\rpercent\t%\rampersand\t\u0026amp;\rapostrophe\t\u0026#39;\rparenleft\t(\rparenright\t)\rasterisk\t*\rplus\t+\rcomma\t,\rminus\t-\rperiod\t.\rslash\t/\rcolon\t:\rsemicolon\t;\rless\t\u0026lt;\requal\t=\rgreater\t\u0026gt;\rquestion\t?\rat\t@\rbracketleft\t[\rbackslash\tbracketright ]\rasciicircum\t^\runderscore\t_\rgrave\t`\rbraceleft\t{\rbar\t|\rbraceright\t}\rasciitilde\t~\rkp_space\t小键板空格\rkp_tab\t小键板水平定位符\rkp_enter\t小键板回车\rkp_delete\t小键板刪除\rkp_home\t小键板原位\rkp_left\t小键板左箭头\rkp_up\t小键板上箭头\rkp_right\t小键板右箭头\rkp_down\t小键板下箭头\rkp_prior、kp_page_up\t小键板上翻\rkp_next、kp_page_down\t小键板下翻\rkp_end\t小键板末位\rkp_begin\t小键板始位\rkp_insert\t小键板插入\rkp_equal\t小键板等于\rkp_multiply\t小键板乘号\rkp_add\t小键板加号\rkp_subtract\t小键板減号\rkp_divide\t小键板除号\rkp_decimal\t小键板小数点\rkp_0\t小键板0\rkp_1\t小键板1\rkp_2\t小键板2\rkp_3\t小键板3\rkp_4\t小键板4\rkp_5\t小键板5\rkp_6\t小键板6\rkp_7\t小键板7\rkp_8\t小键板8\rkp_9\t小鍵板9 ⁵ editor 用以定制操作键(不支持 import_preset:),键盘键名同 key_binder/bindings 中的 accept 和 send,效果定义如下:\nconfirm\t上屏候选项\rcommit_comment\t上屏候选项備注\rcommit_raw_input\t上屏原始输入\rcommit_script_text\t上屏变换后输入\rcommit_composition\t语句流单字上屏\rrevert\t撤消上次输入\rback\t按字符回退\rback_syllable\t按音节回退\rdelete_candidate\t刪除候选项\rdelete\t向后刪除\rcancel\t取消输入\rnoop\t空 ⁶ punctuator: 下设 full_shape: 选 half_shape: 分别控制全角模式下的符号选半角模式下的符号,另有 use_space: 空格顶字(true 或 false)。\n每条标点项可加 commit 直接上屏选 pair 交替上屏两种模式,默认为选单模式。\n\u0026gt; 修改自 cangjie6.schema.yaml\nkey_binder:\rimport_preset: default\rbindings:\r- {accept: semicolon, send: 2, when: has_menu} # 分号选第二重码\r- {accept: apostrophe, send: 3, when: has_menu} # 引号选第三重码\r- {accept: \u0026#34;control+1\u0026#34;, select: .next, when: always}\r- {accept: \u0026#34;control+2\u0026#34;, toggle: full_shape, when: always}\r- {accept: \u0026#34;control+3\u0026#34;, toggle: simplification, when: always}\r- {accept: \u0026#34;control+4\u0026#34;, toggle: extended_charset, when: always}\reditor:\rbindings:\rreturn: commit_comment\rpunctuator:\rimport_preset: symbols\rhalf_shape:\r\u0026#34;\u0026#39;\u0026#34;: {pair: [\u0026#34;「\u0026#34;, \u0026#34;」\u0026#34;]} # 第一次按是「,第二次是」\r\u0026#34;(\u0026#34;: [\u0026#34;〔\u0026#34;, \u0026#34;[\u0026#34;] # 弹出选单\r.: {commit: \u0026#34;。\u0026#34;} # 无选单,直接上屏。优先级最高\rrecognizer:\rimport_preset: default\rpatterns:\remail: \u0026#34;^[a-z][-_.0-9a-z]*@.*$\u0026#34;\rurl: \u0026#34;^(www[.]|https?:|ftp:|mailto:).*$\u0026#34;\rreverse_lookup: \u0026#34;`[a-z]*;?$\u0026#34;\rpinyin_lookup: \u0026#34;`p[a-z]*;?$\u0026#34;\rjyutping_lookup: \u0026#34;`j[a-z]*;?$\u0026#34;\rpinyin: \u0026#34;(?\u0026lt;!`)p[a-z\u0026#39;]*;?$\u0026#34;\rjyutping: \u0026#34;(?\u0026lt;!`)j[a-z\u0026#39;]*;?$\u0026#34;\rpunct: \u0026#34;/[a-z]*$\u0026#34; # 配合 symbols.yaml 中的特殊字符输入 ⁷ 外观\nrime 还为每个方案提供选单选一定的外面定制能力。\n通常情况下 menu 在 default.yaml 中定义(或用户修改档 default.custom.yaml),style 在 squirrel.yaml 或 weasel.yaml (或用户修改档 squirrel.custom.yaml 或 weasel.custom.yaml)。\nmenu:\ralternative_select_labels: [ ①, ②, ③, ④, ⑤, ⑥, ⑦, ⑧, ⑨ ] # 修改候选标签\ralternative_select_keys: asdfghjkl # 如编码字符占用数字键则需另设选字键\rpage_size: 5 # 选单每页显示个数\rstyle:\rfont_face: \u0026#34;hanamina, hanaminb\u0026#34; # 字体〔小狼毫得且仅得设一个字体;鼠须管得设多个字体,后面的字体自动补前面字体中不含的字〕\rfont_point: 15 # 字号\rlabel_format: \u0026#39;%s\u0026#39; # 候选标签格式\rhorizontal: false # 橫/直排\rline_spacing: 1 # 行距\rinline_preedit: true # 输入码內嵌 词典详解 # rime dict\r# encoding: utf-8\r〔你还可以在这注释字典来源、变动记录等〕 描述档 ¹ name: 内部字典名,也即 schma 所引用的字典名,确保与文件名相一致。\n² version: 如果发布,请确保每次发动升级版本号。\nname: \u0026#34;cangjie6.extended\u0026#34;\rversion: \u0026#34;0.1\u0026#34; 配置 ¹ sort: 字典 初始 排序,可选 original 或 by_weight 。\n² use_preset_vocabulary: 是否引入「 八股文 」(含字词频、词库)。\n³ vocabulary: 引入其他词库(含字词频、词库),此时 use_preset_vocabulary 不可设定为 true 。\n⁴ max_phrase_length: 配合 use_preset_vocabulary: ,设定导入词条最大词长。\n⁵ min_phrase_weight: 配合 use_preset_vocabulary: ,设定导入词条最小词频。\n⁶ columns: 定义码表以 tab 分隔出的各列,可设 text(文本)、code(码)、weight(权重)、stem(造词码)。\n⁷ import_tables: 加载其它外部码表。\n⁸ encoder: 形码造词规则。\nexclude_patterns: ; rules: 可用 length_equal: 和 length_in_range: 定义。大写字母表示字序,小写字母表示其所跟随的大写字母所以表的字中的编码序; tail_anchor: 造词码包含结构分隔符(仅用于仓颉); exclude_patterns 取消某编码的造词资格。 \u0026gt; cangjie6.extended.dict.yaml\nsort: by_weight\ruse_preset_vocabulary: false\rimport_tables:\r- cangjie6 # 单字码表由 cangjie6.dict.yaml 导入\rcolumns: # 此字典为纯词典,无单字编码,仅有字和词频\r- text # 字/词\r- weight # 字/词频\rencoder:\rexclude_patterns:\r- \u0026#39;^z.*$\u0026#39;\rrules:\r- length_equal: 2 # 对于二字詞\rformula: \u0026#34;aaazbabbbz\u0026#34; # 取第一字首尾码、第二字首次尾码\r- length_equal: 3 # 对于三字詞\rformula: \u0026#34;aaazbayzzz\u0026#34; # 取第一字首尾码、第二字首尾码、第三字尾码\r- length_in_range: [4, 5] # 对于四至五字詞\rformula: \u0026#34;aabzcayzzz\u0026#34; # 取第一字首码,第二字尾码、第三字首码、倒数第二字尾码、最后一字尾码\rtail_anchor: \u0026#34;\u0026#39;\u0026#34; 码表 以 tab 分隔各列,各列依 columns: 定义排列。\n\u0026gt; cangjie6.dict.yaml\ncolumns:\r- text # 第一列字/词\r- code # 第二列码\r- weight # 第三列字/词频\r- stem # 第四列造词码 \u0026gt; cangjie6.dict.yaml\n個\towjr\t246268\tow\u0026#39;jr\r看\thqbu\t245668\r中\tl\t243881\r呢\trsp\t242970\r來\tdoo\t235101\r嗎\trsqf\t221092\r爲\tbhnf\t211340\r會\towfa\t209844\r她\tvpd\t204725\r與\txyc\t203975\r給\tvfor\t193007\r等\thgdi\t183340\r這\tyymr\t181787\r用\tbq\t168934\tb\u0026#39;q 词典扩展包 概述 librime 1.6.0 增加了一种扩展词典内容的机制——词典扩展包。可用於为固定音节表的输入方案添加词汇。\n固态词典包含 *.table.bin 和 *.prism.bin 两个文件。 前者用於存储来自词典源文件*.dict.yaml 的数据,后者综合了从词典源文件中提取的音节表和输入方案定义的拼写规则。\n扩充固态词典内容,旧有在词典文件的 yaml 配置中使用 import_tables 从其他词典源文件导入码表的方法。 此法相当於将其他源文件中的码表内容追加到待编译的词典文件中,再将合併的码表编译成二进制词典文件。\n词典扩展包可以达到相似的效果。其实现方式有所不同。 编译过程中,将额外的词典源文件 *.dict.yaml 生成对应的 *.table.bin,其音节表与主码表的音节表保持一致。 使用输入方案时,按照 translator/packs 配置列表中的包名加载额外的 *.table.bin 文件,多表并用,从而一併查得扩充的词汇。\n示例 我有一例,请诸位静观。\n# 在 linux 环境做出 librime 及命令行工具\rcd librime\rmake\r# 準备示例文件(有三)\r# 做一个构建扩展包专用的方案,以示如何独立於主词典的构建流程。\r# 如果不需要把扩展包和主词典分开製备,也可以用原有的输入方案。\rcat \u0026gt; build/bin/luna_pinyin_packs.schema.yaml \u0026lt;\u0026lt;eof\r# rime schema\rschema:\rschema_id: luna_pinyin_packs\rtranslator:\rdictionary: luna_pinyin_packs\rpacks:\r- sample_pack\reof\r# 代用的主词典。因为本示例只构建扩展包。\r# 做这个文件的目的是不必费时地编译导入了预设词汇表的朙月拼音主词典。\r# 如果在主词典的构建流程生成扩展包,则可直接使用主词典文件。\rcat \u0026gt; build/bin/luna_pinyin_packs.dict.yaml \u0026lt;\u0026lt;eof\r# rime dict\r---\rname: luna_pinyin_packs\rversion: \u0026#39;1.0\u0026#39;\rsort: original\ruse_preset_vocabulary: false\rimport_tables:\r- luna_pinyin\r...\reof\r# 扩展包源文件\rcat \u0026gt; build/bin/sample_pack.dict.yaml \u0026lt;\u0026lt;eof\r# rime dict\r---\rname: sample_pack\rversion: \u0026#39;1.0\u0026#39;\rsort: original\ruse_preset_vocabulary: false\r...\r粗鄙之语\tcu bi zhi yu\reof\r# 製作扩展包\r(cd build/bin; ./rime_deployer --compile luna_pinyin_packs.schema.yaml)\r# 构建完成后可丢弃代用的主词典,只留扩展包\rrm build/bin/build/luna_pinyin_packs.*\r# 重新配置朙月拼音输入方案,令其加载先时生成的词典扩展包\r(cd build/bin; ./rime_patch luna_pinyin \u0026#39;translator/packs\u0026#39; \u0026#39;[sample_pack]\u0026#39;)\r# 验证词典可查到扩展包中的词语\recho \u0026#39;cubizhiyu\u0026#39; | (cd build/bin; ./rime_console) 总结 与编译词典时导入码表的方法相比,使用词典扩展包有两项优势:\n扩展包可以独立於主词典及其他扩展包单独构建,增量添加扩展包不必重复编译完整的主词典,减少编译时间及资源开销; 词典扩展包的编译单元与词典源文件粒度一致,方便组合使用,增减扩展包只须重新配置输入方案。 需要注意的是,查询时使用主词典的音节表,这要求扩展包使用相同的音节表构建。 目前 librime 并没有机制保证加载的扩展包与主词典兼容。用家须充分理解该功能的实现机制,保证数据文件的一致性。 这也意味着二进制扩展包不宜脱离於主词典而製作和分发。\n定制指南详解 先来一个阅读自检吧~\n[[#必知必会]] [[#rime 中的数据文件分布及作用]] [[#方案制作详解]] [[#定制指南]] patch:\r\u0026#34;一组设定项/二级设定项/三级设定项\u0026#34;: 新的设定值\r\u0026#34;另一个设定项\u0026#34;: 新的设定值\r\u0026#34;再一个设定项\u0026#34;: 新的设定值\r\u0026#34;含列表的设定项/@0\u0026#34;: 列表第一个元素新的设定值\r\u0026#34;含列表的设定项/@last\u0026#34;: 列表最后一个元素新的设定值\r\u0026#34;含列表的设定项/@before 0\u0026#34;: 在列表第一个元素之前插入新的设定值(不建议在补丁中使用)\r\u0026#34;含列表的设定项/@after last\u0026#34;: 在列表最后一个元素之后插入新的设定值(不建议在补丁中使用)\r\u0026#34;含列表的设定项/@next\u0026#34;: 在列表最后一个元素之后插入新的设定值(不建议在补丁中使用) 定制每页候选数 rime 中,默认每页至多显示 5 个候选项,而允许的范围是 1~9(个别 rime 发行版可支持 10 个候选)。\n设定每页候选个数的默认值为 9,在用户目录建立文档 default.custom.yaml :\n1 2 patch: \u0026#34;menu/page_size\u0026#34;: 9 重新布置即可生效。\n〔注意〕 如果 default.custom.yaml 里面已经有其他设定内容,只要以相同的缩进方式添加 patch: 以下部分,不可重复 patch: 这一行。\n若只需要将单独一个输入方案的每页候选数设为 9 ,以朙月拼音为例,建立文档 luna_pinyin.custom.yaml 写入相同内容,重新部署即可生效。\n定制标点符号 有的用户习惯以 / 键输入标点 、。\n仍以朙月拼音为例,输入方案有以下设定:\n1 2 3 4 5 # luna_pinyin.schema.yaml # ... punctuator: import_preset: default 解释:\npunctuator 是 rime 中负责转换标点符号的组件。该组件会从设定中读取符号映射表,而知道该做哪些转换。\npunctuator/import_preset 是说,本方案要继承一组预设的符号映射表,要从另一个设定档 default.yaml 导入。\n查看 default.yaml ,确有如下符号表:\n1 2 3 4 5 6 7 8 9 punctuator: full_shape: # ……其他…… \u0026#34;/\u0026#34; : [ /, \u0026#34;/\u0026#34;, ÷ ] # ……其他…… half_shape: # ……其他…… \u0026#34;/\u0026#34; : [ \u0026#34;/\u0026#34;, /, ÷ ] # ……其他…… 可见按键 / 是被指定到 \u0026quot;/\u0026quot;, /, ÷ 等一组符号了。并且全角和半角状态下,符号有不同的定义。\n欲令 / 键直接输出 、,可如此定制 luna_pinyin.custom.yaml:\n1 2 3 4 5 patch: punctuator/full_shape: \u0026#34;/\u0026#34; : \u0026#34;、\u0026#34; punctuator/half_shape: \u0026#34;/\u0026#34; : \u0026#34;、\u0026#34; 以上在输入方案设定中写入两组新值,合并后的输入方案成为:\n1 2 3 4 5 6 7 8 9 # luna_pinyin.schema.yaml # ... punctuator: import_preset: default full_shape: \u0026#34;/\u0026#34; : \u0026#34;、\u0026#34; half_shape: \u0026#34;/\u0026#34; : \u0026#34;、\u0026#34; 含义是在由 default 导入的符号表之上,覆写对按键 / 的定义。\n通过这种方法,即直接继承了大多数符号的默认定义,又做到了局部的修改化。\n定制简化字输出 注意,如果你只需要 rime 输出简化字,敲 ctrl+~ 组合键,从菜单中选择「漢字→汉字」即可!\n本例说明旨在说明其中原理,以及通过设定档修改预设输出字形的方法。\nrime 预设的词汇表中使用传统汉字。这是因为传统汉字较简化字提供了更多信息,做「繁→简」转换能够保证较高的精度。\nrime 中的过滤器组件 simplifier,完成对候选词的繁简转换。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # luna_pinyin.schema.yaml # ... switches: - name: ascii_mode reset: 0 states: [ 中文, 西文 ] - name: full_shape states: [ 半角, 全角 ] - name: simplification # 转换开关 states: [ 漢字, 汉字 ] engine: filters: - simplifier # 必要组件一 - uniquifier # 必要组件二 以上是朙月拼音中有关繁简转换功能的设定。\n在 engine/filters 中,除了 simplifier,还用了一件 uniquifier。 这是因为有些时候,不同的候选会转化为相同的简化字,例如「鐘→钟」、「鍾→钟」。 uniquifier 的作用是在 simplifier 执行转换之后,将文字相同的候选项合并。\n该输入方案设有三个状态开关:中/西文、全/半角、繁简字。即 switches 之下三项。\n每个开关可以在两种状态(states)之间转换,simplifier 依据名为 simplification 的开关状态来决定是否做简化:\n初始状态下,输出为传统汉字,〔 方案选单 〕中的开关选项显示为「漢字→汉字」; 选择该项后,输出为简化汉字,〔 方案选单 〕中显示 「汉字→漢字」; rime 会记忆你的选择,下次打开输入法时,直接切换到所选的字形; 亦可无视上次记住的选择,在方案中重设初始值:reset 设为 0 或 1,分别选中 states 列表中的两种状态。 如日常应用以简化字为主,则每每在〔 方案选单 〕中切换十分不便。\n1 2 3 4 5 6 7 8 9 10 11 12 # luna_pinyin.custom.yaml patch: switches: # 注意缩进 - name: ascii_mode reset: 0 # reset 0 的作用是当从其他输入方案切换到本方案时, states: [ 中文, 西文 ] # 重设为指定的状态,而不保留在前一 个方案中设定的状态。 - name: full_shape # 选择输入方案后通常需要立即输入中文,故重设 ascii_mode = 0; states: [ 半角, 全角 ] # 而全/半角則可沿用之前方案中的用法。 - name: simplification reset: 1 # 增加这一行:默認啓用「繁→简」转换。 states: [ 漢字, 汉字 ] 其实预设输入方案中就提供一丈朙月拼音的简化字版本,名为简化字,以应大家“填表”之需。看他的代码如何与上篇定制档写得不同:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # luna_pinyin_simp.schema.yaml # ... switches: - name: ascii_mode reset: 0 states: [ 中文, 西文 ] - name: full_shape states: [ 半角, 全角 ] - name: zh_simp # 注意这里(※1) reset: 1 states: [ 漢字, 汉字 ] simplifier: option_name: zh_simp # 和这里(※2) 前方说,simplifier 这个组件会检查名为 simplification 的开关状态;而这款简化字方案却用了一个不同名的开关 zh_simp ,即 ※1 处所示;并通过在 ※2 行设定 simplifier/option_name 告知 simplifier 组件所需关注的开关名字。\n何故?\n还记是否,前文对「全/半角」这个开关的讨论 \u0026ndash; 当切换方案时,未明确使用 reset 重置的开关,会保持之前设定过的状态。\n朙月拼音等多数方案,并未重设 simplification 这个选项 \u0026ndash; 因为用户换了一种输入编码的方式,并不意味着需要变更输出的字形。\n而简化字这一方案不同,恰恰是表达变更输出字形的需求;用户再从简化字切回朙月拼音时,一定是为了回到繁体输出模式。所以令简化字使用独立命名的开关、而非方案间共用的 simplification 开关,以避免影响其他输入方案的繁简转换状态。\n默认英文输出 有些用户习惯默认英文输出,在需要用中文时再做切换。这就需要键们在方案中重设状态开关初始值。\n还记得吗?我们可用 reset 设定项在方案中为某些状态开关重设初始值:reset 设为 0 或 1,分别选中 states 列表中的两种状态。\n我们以朙月拼音为例:\n1 2 3 4 # luna_pinyin.custom.yaml patch: \u0026#34;switches/@0/reset\u0026#34;: 1 # 表示将 switcher 列表中的第一个元素(即 ascii_mode 开关)的初始值重設为状态1(即「英文」)。 定制方案选单 在「 小狼毫 」方案选单设定介面上勾勾选选,就可以如此定制输入方案列表:\n1 2 3 4 5 6 7 8 9 # default.custom.yaml patch: schema_list: # 对于列表类型,现在没有办法指定如何添加、消除或单一修改某项,于是要在定制档中将整个列表替换 - schema: luna_pinyin - schema: cangjie5 - schema: luna_pinyin_fluency - schema: luna_pinyin_simp - schema: my_coolest_ever_schema # 这样就启用了未曾有过的高级输入方案,其实这么好的方案应该排在最前面哈 没有设定界面时,又想启用、禁用某个输入方案,手写这样一份定制档,重新部署就好啦。\n定制唤出方案选单的快捷键 呼出方案选单,默认快捷键为 ctrl+~ 或 f4 。\n不过有些同学电脑上默认快捷键与其他软件有冲突,那么如何定义更好的键位呢?\n1 2 3 4 5 6 7 # default.custom.yaml patch: \u0026#34;switcher/hotkeys\u0026#34;: # 这个列表里第项定义一个快捷键,使哪个都可以 - \u0026#34;control+s\u0026#34; # 添加 ctrl+s - \u0026#34;control+grave\u0026#34; # 你看写法并不是 ctrl+` 而是与 ibus 一致的表示法 - f4 按键定义的格式为「 修饰符甲 + 修饰符乙 + \u0026hellip; + 按键名称 」,加号为分隔符,要写出。\n所谓修饰符,就是以下组合键的状态标识或是按键弹起的标识:\nrelease —— 按键被放开,而不是按下; shift control alt——windows 上 alt+字母 会被系统优先识别为程序菜单项的快捷键,当然 alt+tab 也不可用; 嗯,linux 发行版还支持 super, meta 等組合键,不过最好选每个平台都能用的啦。 按键的名称,大小写字母选数字都用它们自己表示,其他按键名称参考 [[#其它|细项配置-按键]] 章节。\n定制小狼毫字体字号 虽然与输入方案无关,也在此列出以作参考。\n1 2 3 4 5 # weasel.custom.yaml patch: \u0026#34;style/font_face\u0026#34;: \u0026#34;明兰\u0026#34; # 字体名称,从记事本等处的系统字体对话框里能看到 \u0026#34;style/font_point\u0026#34;: 14 # 字号,只认数字,不认「五号」、「小五」这样的 定制小狼毫配色方案 注:这款配色已经在新版本的小狼毫里预设了,做练习时,你可以将文中 starcraft 换成自己命名的标识。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # weasel.custom.yaml patch: \u0026#34;style/color_scheme\u0026#34;: starcraft # 这项用于选中下面定义的新方案 \u0026#34;preset_color_schemes/starcraft\u0026#34;: # 在配色方案列表里加入标识为 starcraft 的新方案 name: 星際我爭霸/starcraft author: contralisk \u0026lt;contralisk@gmail.com\u0026gt;, original artwork by blizzard entertainment text_color: 0xccaa88 # 编码行文字顏色,24位色值,用十六进制书写方便些,順序是蓝绿红 0xbbggrr candidate_text_color: 0x30bb55 # 候选项文字颜色,当与文字颜色不同时指定 back_color: 0x000000 # 底色 border_color: 0x1010a0 # 边框颜色,与底色相同则为无边框的效果 hilited_text_color: 0xfecb96 # 高亮文字,即与当前高亮候选对应的那部份输入码 hilited_back_color: 0x000000 # 设定高亮文字的底色,可起到突显高亮部份的作用 hilited_candidate_text_color: 0x60ffa8 # 高亮候选项的文字颜色,要醒目! hilited_candidate_back_color: 0x000000 # 高亮候选项的底色,若与背景色不同就会显出光棒 效果自己看!\n也可以参照这张比较直观的图:\n![[assets/pasted image 20250109111833.png]]\n另,此处有现成的配色方案工具供用户调配:\n🌟 小狼毫: https://bennyyip.github.io/rime-see-me/ 鼠须管: https://gjrobert.github.io/rime-see-me-squirrel/ 配置文件详解 rime 配置文件,用於设置输入法引擎、输入法客户端的运行参数,也包括输入方案及词典配置。\n文件格式:採用 utf-8 编码的 yaml 文本。\n位置及组织方式: rime 所使用的配置文件在 用户文件夹 及 共享文件夹。\n语法 在 yaml 语法的基础上,增设以下编译指令:\n包含 __include: 指令在当前位置包含另一 yaml 节点的内容。\n可写在配置源文件任一 yaml map 节点下。其语法为\ninclude_example_1:\r__include: local/node\rlocal:\rnode: contents to include 被引用的节点可以来自另一个配置文件。 目标配置节点的路径以 \u0026lt;filename\u0026gt;:/ 开始,可省略扩展名 .yaml。\ninclude_example_2:\r__include: config:/external/node\rinclude_example_3:\r__include: config.yaml:/external/node 包含整个文件,可指定路径为目标配置文件的根节点:\ninclude_example_4:\r__include: config.yaml:/ 包含另一个 yaml map 节点后,源文件中 __include: 指令所在 map 除编译指令外的其他数据与被包含的 map 合併:\ninclude_example_5:\r__include: some_map\roccupation: journalist # new key and value\rsimplicity: very # override value for included key\rsome_map:\rsimplicity: somewhat\rnaivety: sometimes 合併发生在 __include: 指令所在节点,不会修改被引用节点 some_map 的内容。\n包含 yaml 列表,则指令所在 map 节点替换为所引用的 yaml 列表。 该 map 节点不应包含任何编译指令以外的 key-value,因为不相容於 yaml 列表类型。\n也不能直接在该节点下追加列表项,因为 yaml 语法不允许混合 map 与 list。 在包含列表后追加、修改列表项,必须使用下文介绍的 __append: 或 __patch: 指令。\ninclude_example_6:\r__include: some_list\r__append:\r- someone else\rsome_list:\r- youngster\r- elder 补靪 修改某一相对路径下的配置节点,而非当前节点的整体。 基本语法为:\n__patch:\rkey/alpha: value a\rkey/beta: value b 目标节点路径的写法为将各级 map 的 key 用 / 分隔。 因此 key 如果包含 / 字符,则不能作为节点路径的一部分。\n可在节点路径末尾添加 /+ 操作符,表示合併 list 或 map 节点; 或者(可选地)添加 /= 表示用指定的值替换目标节点原有的值。 若未指定操作符,__patch: 指令的默认操作为替换。\n以下是一些示例:\npatch_example_1:\rsibling: old value\rappend_to_list:\r- existing item\rmerge_with_map:\rkey: value\rreplace_list:\r- item 1\r- item 2\rreplace_map:\ra: value\rb: value\r__patch:\rsibling: new value\rappend_to_list/+:\r- appended item\rmerge_with_map/+:\rkey: new value\rnew_key: value\rreplace_list/=:\r- only item\rreplace_map/=:\ronly_key: value 以上示例仅为表现 __patch: 的作用方式。 实际上在当前节点和补靪内容均为字面值的情况下,没有打补靪的必要。 字面值补靪通常用於当前节点包含了其他节点,并需要修改部份配置项的场景:\npatch_example_2:\r__include: patch_example_1\r__patch:\rsibling: even newer value\rappend_to_list/+:\r- another appended item 由於 yaml map 的 key 是无序的,书写顺序并不决定编译指令的先后。\n同一节点下,编译指令的执行顺序为: __include: 包含指定节点 → 合併当前节点下的其他 key-value 数据 → __patch: 修改子节点。\n__patch: 指令的另一种主要用法是引用另一个节点中的补靪内容,并作用於指令所在节点:\npatch_example_3:\r__patch: changes\rsome_list:\r- youngster\r- elder\rsome_map:\rsimplicity: somewhat\rnaivety: sometimes\rchanges:\rsome_list/+:\r- someone else\rsome_map/simplicity: too much yaml 语法不允许 map 有重复的 key。 如果要引用不同位置的多项补靪,可以为 __patch: 指定一个列表,其中每项通过节点引用定义一组补靪:\npatch_example_4:\r__include: base_config\r__patch:\r- company_standard\r- team_convention\r- personal_preference\rbase_config:\ractors: []\rcompany_info:\rbased_in: unknown location\rfavorites: {}\rcompany_standard:\rcompany_info/based_in: american san diego\rteam_convention:\ractors/+:\r- feifei\r- meimei\r- riri\rpersonal_preference:\rfavorites/fertilizer: jinkela 用补靪指令修改列表 补靪指令中,目标节点路径由各级节点的 key 组成。 若某一节点为 list 类型,可以 @\u0026lt;下标\u0026gt; 形式指定列表项。下标从 0 开始计数。 无论列表长度,末位列表元素可表示为 @last。\npatch_list_example_1:\rsome_list/@0/simplicity: very\rsome_list/@last/naivety: always\rsome_list:\r- simplicity: somewhat\r- naivety: sometimes 在指定元素之前、之后插入列表元素,用 @before \u0026lt;下标\u0026gt;、@after \u0026lt;下标\u0026gt;。 @after last 可简写为 @next,向列表末尾添加元素:\npatch_list_example_2:\r\u0026#39;some_list/@before 0/youthfulness\u0026#39;: too much\r\u0026#39;some_list/@after last/velocity\u0026#39;: greater than westerners\rsome_list/@next/questions: no good 可选的包含与补靪 若包含或补靪指令的目标是以 ? 结尾的节点路径, 则当该路径对应的节点(或所属外部配置文件)不存在时,不產生编译错误。\n如:\n__patch: default.custom:/patch?\rnice_to_have:\r__include: optional_config? 追加与合併 追加指令 __append: 将其下的列表项追加到该指令所在的节点。 合併指令 __merge: 将其下的 map 合併到该指令所在的节点。\n这两条指令只能用在 __include: 指令所在节点及其(字面值)子节点。\nappend_merge_example_1:\r__include: starcraft\r__merge:\rmade_by: blizzard entertainment\rraces:\r__append:\r- protoss\r- zerg\rstarcraft:\rfirst_release: 1998\rraces:\r- terrans 实际书写配置时,__merge: 指令往往省略。 因为 __include: 指令自动合併其所在节点下的 key-value 并递归地合併所有 map 类型的子节点。\n而对於类型为 list 的子节点,默认操作是替换整个列表。 如果要以向后追加列表项的方式合併,除了 __append: 指令之外,还可以採用 /+ 操作符:\nappend_merge_example_2:\r__include: starcraft\rmade_by: blizzard entertainment\rraces/+:\r- protoss\r- zerg 在 __include: 指令自动合併的节点树中,如果要对某个 map 类型的子节点整体替换,可使用 /= 操作符:\nrevealed_map:\r__include: old_map\rterran_command_center/=:\rx: 3.14\ry: 6.28\rold_map:\rterran_command_center:\rlocation: unexplored\rprotoss_nexus: {x: 128, y: 256}\rzerg_hatchary: {x -1024, y: 0} 案例解析:\nhttps://github.com/rime/librime/pull/192#issuecomment-371202389\n配置编译器插件 自动添加一些隐含的编译指令,用来实现对原有补靪机制以及导入成套组件配置等语法的兼容。\n这些插件的作用是将当前输入方案所需的全部配置内容在部署期间汇总到一份编译结果文件里。使输入法程序不必在运行时打开众多的配置文件。\n自动应用补靪 配置文件的根节点如果没有使用 __patch: 指令,则在源文件编译完成后,自动插入以下指令:\n(註:请将实际的配置名称代入 \u0026lt;config\u0026gt;)\n# \u0026lt;config\u0026gt;.yaml 或 \u0026lt;config\u0026gt;.schema.yaml 的根节点\r__patch: \u0026lt;config\u0026gt;.custom:/patch? 如果存在与旧版本 librime 兼容的补靪文件,则从中加载补靪:\n# \u0026lt;config\u0026gt;.custom.yaml\rpatch:\rkey: value 以上插件的效果相当於\n# \u0026lt;config\u0026gt;.yaml 或 \u0026lt;config\u0026gt;.schema.yaml 的根节点\r__patch:\rkey: value 如果源文件的根节点使用了 __patch: 指令,则不论其是否加载 \u0026lt;config\u0026gt;.custom:/patch,都不再添加自动补靪指令。 如果这种情况下仍希望支持补靪文件,须将其列为 __patch: 列表中的一项:\n# \u0026lt;config\u0026gt;.yaml 或 \u0026lt;config\u0026gt;.schema.yaml 的根节点\r__patch:\r- other_patch # ...\r- \u0026lt;config\u0026gt;.custom:/patch? 应用默认配置 输入方案中未指定以下配置项时,自动导入默认配置 default.yaml 的定义:\nmenu:\rpage_size: # ... 导入成套组件配置 将部份组件配置中的 \u0026lt;component\u0026gt;/import_preset: \u0026lt;config\u0026gt; 语法翻译为\n(註:请将实际的配置名称和组件名称代入 \u0026lt;config\u0026gt;、\u0026lt;component\u0026gt;)\n\u0026lt;component\u0026gt;:\r__include: \u0026lt;config\u0026gt;:/\u0026lt;component\u0026gt;\r# 以下为输入方案覆盖定义的内容 注意:如果指定的配置节点 \u0026lt;config\u0026gt;:/\u0026lt;component\u0026gt; 不存在会导致输入方案编译错误。\n导入韵书配置 (尚未实现)导入 *.dict.yaml 的 yaml 配置部份。\n加载规则 以上介绍的编译指令及编译器插件,仅对交给 配置编译器 处理的 源文件 有效。\n配置源文件的位置详见 用户文件夹 及 共享文件夹。\n输入法程序运行时读取的配置文件是 编译结果文件(可能是 yaml 格式或二进制格式)。 编译结果只包含直接供程序读取的配置内容,而不再包含有特殊含义的编译指令。\n配置的编译结果文件与源文件并非一一对应的关系, 而是合併重组为编译后的默认配置 default 以及各输入方案的配置。\n其他不经过编译处理而直接在运行时由输入法程序存取的配置文件有: installation.yaml, user.yaml 等。\n韵书 文件中的 yaml 配置部份目前也不支持配置编译指令。\ntodo(rime/docs): 详解 yaml 节点树及编译指令的解析、执行顺序。\n配置组件调用方式 todo(rime/engine): 完成本节\n代码风格 yaml 书写样式参照 yaml.org 的示例。推荐以下风格:\n配置文件开头用註释行简述文件的内容和使用方法。\n缩进:用两个空格缩进。\n字符串值:无特殊字符时不使用引号; 需要使用引号时,优先用单引号,以减少双引号引起的字符转义问题。\nflow-style list: 仅在节点树的最内层使用。不嵌套使用。元素较多时不用。\nflow-style map: 仅在节点树的最内层使用。不嵌套使用。元素较多时不用。\n仅包含一对 key-value 的 map 作为列表项时,省略 { } 并与 - 写在同一行。\n不推荐使用 yaml 的锚点(\u0026amp;)和别名引用(*)。请用本文介绍的 __include: 编译指令。\n错误处理 部署后出现错误,请查看 info 日誌(参考), 找到行首字符为 e 的记录,根据错误信息以及上下文排查出错的配置文件。\n未出现错误信息,配置亦未达到预期效果,请对照 \u0026lt;用户文件夹\u0026gt;/build/ 文件夹内的编译结果文件,检查配置源文件与补靪。\n版本控制 输入方案及配置的版本可以用文件中的一项 字符串值 记录。如:version: \u0026lsquo;3.14\u0026rsquo;\n版本号习惯以形为 x.y.z 的多个数字组成。 为避免将版本号解析为 yaml 数值类型而发生错误,如 0.10(〇点十)不同於 0.1(〇点一), 应一律为版本号加上引号 \u0026lsquo;3.14\u0026rsquo; 以示其为字符串类型。\n分发 输入方案设计师完成输入方案及配套韵书后,将源文件发佈在一间 github 代码库, 用家便可通过 rime 配置管理工具 东风破 获取输入方案的最新版本并安装到输入法。\ndiy 处方集 已将一些定制 rime 的常见问题、解法及定制档链接收录于此。\n建议你首先读完《 定制指南 》,通晓相关原理,以正确运用这些处方。\n初始设定 在方案选单中添加五笔、双拼 https://gist.github.com/2309739\n在 rime 输入方案选单中添加五笔、双拼、粤拼、注音,保留你需要的。\n# default.custom.yaml\r# save it to: # ~/.config/ibus/rime (linux)\r# ~/library/rime (macos)\r# %appdata%\\rime (windows)\rpatch:\rschema_list:\r- schema: luna_pinyin # 朙月拼音\r- schema: luna_pinyin_simp # 朙月拼音 简化字模式\r- schema: luna_pinyin_tw # 朙月拼音 臺灣正體模式\r- schema: terra_pinyin # 地球拼音 dì qiú pīn yīn\r- schema: bopomofo # 注音\r- schema: bopomofo_tw # 注音 臺灣正體模式\r- schema: jyutping # 粵拼\r- schema: cangjie5 # 倉頡五代\r- schema: cangjie5_express # 倉頡 快打模式\r- schema: quick5 # 速成\r- schema: wubi86 # 五笔86\r- schema: wubi_pinyin # 五笔拼音混合輸入\r- schema: double_pinyin # 自然碼雙拼\r- schema: double_pinyin_mspy # 微軟雙拼\r- schema: double_pinyin_abc # 智能abc雙拼\r- schema: double_pinyin_flypy # 小鶴雙拼\r- schema: wugniu # 吳語上海話(新派)\r- schema: wugniu_lopha # 吳語上海話(老派)\r- schema: sampheng # 中古漢語三拼\r- schema: zyenpheng # 中古漢語全拼\r- schema: ipa_xsampa # x-sampa 國際音標\r- schema: emoji # emoji表情 看这个例子,可以应用任一预设或自定义输入方案,如【粵拼】、【注音】等。(详解:参见前方 [[#定制方案选单]] 一节)。\n如果下载自己制作了非预设输入方案,将源文件复制到用户文件夹后,也用上面的方法将方案标识加入选单。\n修改后重新部署生效。\n【小狼毫】外观设定 上文已介绍设定字体、字号、制作配色方案的方法。\n使用横向候选栏,嵌入式编码行:\n1 2 3 4 5 # weasel.custom.yaml patch: style/horizontal: true # 候选橫排 style/inline_preedit: true # 內嵌编码(仅支持tsf) style/display_tray_icon: true # 显示托盘图标 【鼠须管】外观与键盘设定 鼠须管从 0.9.6 版本开始支持选择配色方案,用 squirrel.custom.yaml 保存用户的设定。\nhttps://gist.github.com/2290714\n\u0026gt; 【鼠鬚管】定製檔\n# 適用於【鼠鬚管】0.9.13+\r# 位置:~/library/rime/squirrel.custom.yaml\r# 用法:想要哪項生效,就刪去該行行首的#字符,但注意保留用於縮進的空格\rpatch:\r# us_keyboard_layout: true # 鍵盤選項:應用美式鍵盤佈局\r# show_notifications_when: growl_is_running # 狀態通知,默認裝有growl時顯示,也可設爲全開(always)全關(never)\r# style/horizontal: true # 候選窗横向顯示\r# style/inline_preedit: false # 非內嵌編碼行\r# style/font_face: \u0026#34;儷黑 pro\u0026#34; # 我喜歡的字體名稱\r# style/font_point: 21 # 字號\r# style/corner_radius: 10 # 窗口圓角半徑\r# style/border_height: 0 # 窗口邊界高度,大於圓角半徑才有效果\r# style/border_width: 0 # 窗口邊界寬度,大於圓角半徑才有效果\r# style/color_scheme: luna # 選擇配色方案\r# 註:預設的配色方案及代碼(指定爲 style/color_scheme )\r# 系統默認色系 - native\r# 碧水 - aqua\r# 青天 - azure\r# 明月 - luna\r# 墨池 - ink\r# 孤寺 - lost_temple\r# 暗堂 - dark_temple\r# 星際我爭霸 - starcraft\r# 谷歌 - google\r# 曬經石 - solarized_rock\r# 简约白 - clean_white ibus用户: ibus_rime.custom.yaml 不包含控制配色、字体字号等外观样式的设定。\n在特定程序里开关中文输入 【鼠须管】0.9.9 开始支持这项设定:\n这指定的应用程序中,改变输入法的初始转换状态。如在:\n终端 terminal / iterm ; 代码编辑器 macvim ; 快速启动工具 quicksilver / alfred 等程序里很少需要輸入中文,于是鼠须管在這些程序里置信不开启中文输入。 自定义 mac 应用程序的初始转换状态,首先查看应用的 info.plist 文件得到该应用的 bundle identifier,通常是形如 com.apple.xcode 的字符串。\n例如,要在 xcode 里面默认开关中文输入,又要在 alfred 里面恢复开启中文输入,可如此设定:\n1 2 3 4 5 # example squirrel.custom.yaml patch: app_options/com.apple.xcode: ascii_mode: true app_options/com.alfredapp.alfred: {} 注:一些版本的 xcode 标识为 com.apple.dt.xcode,请注意查看 info.plist。\n【小狼毫】0.9.16 亦开始支持这项设定。\n例如,要在 gvim 里面默认开关中文输入,可如此设定:\n1 2 3 4 # example weasel.custom.yaml patch: app_options/gvim.exe: # 程序名字全用小写字母 ascii_mode: true 输入习惯 使用 control 键切换中西文 https://gist.github.com/2981316\n\u0026gt; 使用 control 键切换中西文,上屏已输入的编码;令 caps lock 改变字母的大小写\n# 中西文切换键的默认设置写在 default.yaml 里面\r# 以下的 default.custom.yaml 在全局范围重定义该组快速键\r#\r# 可用的按键有 caps_lock, shift_l, shift_r, control_l, control_r\r# mac 系统上的鼠鬚管不能区分左、右,因此只有对 shift_l, control_l 的设定起作用\r#\r# 已输入编码时按切换键,可以进一步设定输入法中西文切换的形式。\r# 可选的临时切换策略有三:\r# inline_ascii 在输入法的临时西文编辑区内输入字母、数字、符号、空格等,回车上屏后自动复位到中文\r# commit_text 已输入的候选文字上屏并切换至西文输入模式\r# commit_code 已输入的编码字符上屏并切换至西文输入模式\r# 设为 noop,屏蔽该切换键\r#\r# 如果要把 caps lock 设为只改变字母的大小写而不做中西文切换,可将 caps_lock 对应的切换方式设为 noop\r# 如果要以 caps lock 切换到西文模式,默认输出小写字母,请置 ascii_composer/good_old_caps_lock: false\r# 如果要以 caps lock 切换到西文模式,默认输出大写字母,请使用以下设置:\rpatch:\rascii_composer/good_old_caps_lock: true\rascii_composer/switch_key:\rcaps_lock: commit_code\rshift_l: noop\rshift_r: noop\rcontrol_l: commit_code\rcontrol_r: commit_code 以及修改 caps lock、左右 shift、左右 control 键的行为,提供三种切换方式。 详見 gist 代码注释。\n方便地输入含数字的西文用戶名 通常,输入以小写拉丁字母组成的编码后,数字键的作用是选择相应序号的候选字。\n假设我的邮箱地址是 rime123@company.com,则需要在输入 rime 之后上屏或做临时中西文切换,方便可输入数字部分。\n为了更方便输入我的用户名 rime123,设置一组特例,将 rime 与其后的数字优先识别西文。\nhttps://gist.github.com/3076166\n\u0026gt; 自动识别西文及数字组成的用户名\n# default.custom.yaml\r# 全局范围识别输入串为 rime + 任意数字序列,以及形如 rimeime-1.2.3 的常用西文短语\r# 也可将本组 patch 写入 \u0026lt;输入方案id\u0026gt;.custom.yaml 使这组规则仅在一款输入方案中有效\r#\r# 第一例,输入 rime 之后,再输入任意一个数字,则立即识别为西文输入\r# 再加上 default.yaml 原有的 email 规则,识别包含 @ 字符的邮箱,於是可以一气呵成 rime123@company.com # 第二例,输入到 rimeime 时,立即识别为西文输入,并可跟随任意位数字及指定的符号\rpatch:\rrecognizer/patterns/rime123: \u0026#34;^rime[0-9]+$\u0026#34;\rrecognizer/patterns/rimeime: \u0026#34;^rimeime[-_.0-9]*$\u0026#34; 以方括号键换页 https://gist.github.com/2316704\n在 rime 中加入\u0026quot;[\u0026ldquo;和\u0026rdquo;]\u0026ldquo;翻页按键绑定(以【明月拼音】为例)\n# luna_pinyin.custom.yaml\r# save it to: # ~/.config/ibus/rime (linux)\r# ~/library/rime (macos)\r# %appdata%\\rime (windows)\rpatch:\r\u0026#34;key_binder/bindings\u0026#34;:\r- { when: paging, accept: bracketleft, send: page_up }\r- { when: has_menu, accept: bracketright, send: page_down } 添加 mac 风格的翻页键 [ ] 。这是比较直接的设定方式。下一则示例给出了麦种更系统、可重用的设定方式。\n使用西文标点兼以方括号键换页 https://gist.github.com/2334409\n\u0026gt; rime 别样设定,使用西文标点、[]键换页\n# rime alternative settings\r# encoding: utf-8\r#\r# difference from default settings:\r# 1. ascii-style punctuation in half-shape mode\r# 2. [ ] as paging keys\r#\r# save this file as:\r# (linux) ~/.config/ibus/rime/alternative.yaml\r# (mac os) ~/library/rime/alternative.yaml\r# (windows) \u0026#34;%appdata%\\rime\\alternative.yaml\u0026#34;\r# # edit \u0026lt;schema_id\u0026gt;.custom.yaml:\r# \u0026gt;\u0026gt; patch:\r# \u0026gt;\u0026gt; \u0026#39;punctuator/import_preset\u0026#39;: alternative\r# \u0026gt;\u0026gt; \u0026#39;key_binder/import_preset\u0026#39;: alternative\r#\r# for detailed explanation, refer to:\r# http://code.google.com/p/rimeime/wiki/customizationguide#%e4%bd%bf%e7%94%a8%e5%85%a8%e5%a5%97%e8%a5%bf%e6%96%87%e6%a8%99%e9%bb%9e config_version: \u0026#34;0.3\u0026#34;\rpunctuator:\rfull_shape:\r\u0026#34; \u0026#34; : { commit: \u0026#34; \u0026#34; }\r\u0026#34;,\u0026#34; : { commit: , }\r\u0026#34;.\u0026#34; : { commit: 。 }\r\u0026#34;\u0026lt;\u0026#34; : [ 《, 〈, «, ‹ ]\r\u0026#34;\u0026gt;\u0026#34; : [ 》, 〉, », › ]\r\u0026#34;/\u0026#34; : [ 、, /, \u0026#34;/\u0026#34;, ÷ ]\r\u0026#34;?\u0026#34; : { commit: ? }\r\u0026#34;;\u0026#34; : { commit: ; }\r\u0026#34;:\u0026#34; : :\r\u0026#34;\u0026#39;\u0026#34; : { pair: [ \u0026#34;‘\u0026#34;, \u0026#34;’\u0026#34; ] }\r\u0026#34;\\\u0026#34;\u0026#34; : { pair: [ \u0026#34;“\u0026#34;, \u0026#34;”\u0026#34; ] }\r\u0026#34;\\\\\u0026#34; : [ 、, \, \u0026#34;\\\\\u0026#34; ]\r\u0026#34;|\u0026#34; : [ ・, |, \u0026#34;|\u0026#34;, \u0026#34;§\u0026#34;, \u0026#34;¦\u0026#34; ]\r\u0026#34;`\u0026#34; : [ `, \u0026#34;`\u0026#34; ]\r\u0026#34;~\u0026#34; : [ 〜, \u0026#34;~\u0026#34;, ~, 〰 ]\r\u0026#34;!\u0026#34; : { commit: ! }\r\u0026#34;@\u0026#34; : [ @, \u0026#34;@\u0026#34;, ☯ ]\r\u0026#34;#\u0026#34; : [ #, \u0026#34;#\u0026#34;, ⌘ ]\r\u0026#34;%\u0026#34; : [ %, \u0026#34;%\u0026#34;, \u0026#34;°\u0026#34;, \u0026#34;℃\u0026#34; ]\r\u0026#34;$\u0026#34; : [ ¥, \u0026#34;$\u0026#34;, \u0026#34;€\u0026#34;, \u0026#34;£\u0026#34;, \u0026#34;¥\u0026#34;, \u0026#34;¢\u0026#34;, \u0026#34;¤\u0026#34; ]\r\u0026#34;^\u0026#34; : { commit: …… }\r\u0026#34;\u0026amp;\u0026#34; : [ &, \u0026#34;\u0026amp;\u0026#34; ]\r\u0026#34;*\u0026#34; : [ *, \u0026#34;*\u0026#34;, ・, ×, ※, ❂, · ]\r\u0026#34;(\u0026#34; : (\r\u0026#34;)\u0026#34; : )\r\u0026#34;-\u0026#34; : [ -, \u0026#34;-\u0026#34; ]\r\u0026#34;_\u0026#34; : ——\r\u0026#34;+\u0026#34; : [ +, \u0026#34;+\u0026#34; ]\r\u0026#34;=\u0026#34; : [ =, \u0026#34;=\u0026#34; ]\r\u0026#34;[\u0026#34; : [ 「, 【, 〔, [ ]\r\u0026#34;]\u0026#34; : [ 」, 】, 〕, ] ]\r\u0026#34;{\u0026#34; : [ 『, 〖, { ]\r\u0026#34;}\u0026#34; : [ 』, 〗, } ]\rhalf_shape:\r\u0026#34;,\u0026#34; : { commit: \u0026#34;,\u0026#34; }\r\u0026#34;.\u0026#34; : { commit: \u0026#34;.\u0026#34; }\r\u0026#34;\u0026lt;\u0026#34; : \u0026#34;\u0026lt;\u0026#34;\r\u0026#34;\u0026gt;\u0026#34; : \u0026#34;\u0026gt;\u0026#34;\r\u0026#34;/\u0026#34; : \u0026#34;/\u0026#34;\r\u0026#34;?\u0026#34; : { commit: \u0026#34;?\u0026#34; }\r\u0026#34;;\u0026#34; : { commit: \u0026#34;;\u0026#34; }\r\u0026#34;:\u0026#34; : { commit: \u0026#34;:\u0026#34; }\r\u0026#34;\u0026#39;\u0026#34; : \u0026#34;\u0026#39;\u0026#34;\r\u0026#34;\\\u0026#34;\u0026#34; : \u0026#34;\\\u0026#34;\u0026#34;\r\u0026#34;\\\\\u0026#34; : \u0026#34;\\\\\u0026#34;\r\u0026#34;|\u0026#34; : \u0026#34;|\u0026#34;\r\u0026#34;`\u0026#34; : \u0026#34;`\u0026#34;\r\u0026#34;~\u0026#34; : \u0026#34;~\u0026#34;\r\u0026#34;!\u0026#34; : { commit: \u0026#34;!\u0026#34; }\r\u0026#34;@\u0026#34; : \u0026#34;@\u0026#34;\r\u0026#34;#\u0026#34; : \u0026#34;#\u0026#34;\r\u0026#34;%\u0026#34; : \u0026#34;%\u0026#34;\r\u0026#34;$\u0026#34; : \u0026#34;$\u0026#34;\r\u0026#34;^\u0026#34; : \u0026#34;^\u0026#34;\r\u0026#34;\u0026amp;\u0026#34; : \u0026#34;\u0026amp;\u0026#34;\r\u0026#34;*\u0026#34; : \u0026#34;*\u0026#34;\r\u0026#34;(\u0026#34; : \u0026#34;(\u0026#34;\r\u0026#34;)\u0026#34; : \u0026#34;)\u0026#34;\r\u0026#34;-\u0026#34; : \u0026#34;-\u0026#34;\r\u0026#34;_\u0026#34; : \u0026#34;_\u0026#34;\r\u0026#34;+\u0026#34; : \u0026#34;+\u0026#34;\r\u0026#34;=\u0026#34; : \u0026#34;=\u0026#34;\r\u0026#34;[\u0026#34; : \u0026#34;[\u0026#34;\r\u0026#34;]\u0026#34; : \u0026#34;]\u0026#34;\r\u0026#34;{\u0026#34; : \u0026#34;{\u0026#34;\r\u0026#34;}\u0026#34; : \u0026#34;}\u0026#34;\rkey_binder:\rbindings:\r# commonly used paging keys\r- { when: composing, accept: iso_left_tab, send: page_up }\r- { when: composing, accept: shift+tab, send: page_up }\r- { when: composing, accept: tab, send: page_down }\r- { when: has_menu, accept: minus, send: page_up }\r- { when: has_menu, accept: equal, send: page_down }\r- { when: paging, accept: comma, send: page_up }\r- { when: has_menu, accept: period, send: page_down }\r- { when: paging, accept: bracketleft, send: page_up }\r- { when: has_menu, accept: bracketright, send: page_down } 详见上文「使用全套西文标点」一节。\n以回车键清除编码兼以分号、单引号选字 https://gist.github.com/2390510\n\u0026gt; rime 设定:以回车清除编码串,分号、单引号键选择 2、3 候选\n# cangjie5.custom.yaml\r# save it to:\r# ~/.config/ibus/rime (linux)\r# ~/library/rime (macos)\r# %appdata%\\rime (windows)\rpatch:\r\u0026#34;key_binder/bindings\u0026#34;:\r- { when: composing, accept: return, send: escape }\r- { when: has_menu, accept: semicolon, send: 2 }\r- { when: has_menu, accept: apostrophe, send: 3 } 适合一些形码输入法(如五笔、郑码)的快手。\n开关逐键提示 table_translator 默认开启逐键提示。若要只出精确匹配输入码的候选字,可关闭这一选项。\n以【仓颉五代】为例:\n1 2 3 # cangjie5.custom.yaml patch: translator/enable_completion: false 关闭用戶词典和字频调整 以【五笔 86】为例:\n1 2 3 # wubi86.custom.yaml patch: translator/enable_user_dict: false 关闭码表输入法连打 注:这个选项仅针对 table_translator,用于屏蔽仓颉、五笔中带有太级图章「☯」的连打词句选项,不可作用于拼音、注音、速成等输入方案。\n以「 仓颉 」为例:\n1 2 3 # cangjie5.custom.yaml patch: translator/enable_sentence: false 开关仓颉与拼音混打 默认,给出仓颉与拼音候选的混合列表。\n如此设定,直接敲字母只认作仓颉码,但仍可在敲 ~ 之后输入拼音:\n1 2 3 # cangjie5.custom.yaml patch: abc_segmentor/extra_tags: {} 空码时按空格键清空输入码 首先需要关闭码表输入法连打(参见上文),这样才可以在打空时不出候选词。\n然后设定(以五笔 86 为例):\n1 2 3 4 5 6 # wubi86.custom.yaml patch: translator/enable_sentence: false key_binder/bindings: - {when: has_menu, accept: space, send: space} - {when: composing, accept: space, send: escape} 模糊音 【朙月拼音】模糊音定制模板 https://gist.github.com/2320943\n\u0026gt; 朙月拼音 模糊音定制模板\n# luna_pinyin.custom.yaml\r#\r# 【朙月拼音】模糊音定製模板\r# 佛振配製 :-)\r#\r# 位置:\r# ~/.config/ibus/rime (linux)\r# ~/library/rime (mac os)\r# %appdata%\\rime (windows)\r#\r# 於重新部署后生效\r#\rpatch:\r\u0026#39;speller/algebra\u0026#39;:\r- erase/^xx$/ # 第一行保留\r# 模糊音定义\r# 需要哪组就删去行首的 # 号,单双向任选\r#- derive/^([zcs])h/$1/ # zh, ch, sh =\u0026gt; z, c, s\r#- derive/^([zcs])([^h])/$1h$2/ # z, c, s =\u0026gt; zh, ch, sh\r#- derive/^n/l/ # n =\u0026gt; l\r#- derive/^l/n/ # l =\u0026gt; n\r# 这两组一般是单向的\r#- derive/^r/l/ # r =\u0026gt; l\r#- derive/^ren/yin/ # ren =\u0026gt; yin, reng =\u0026gt; ying\r#- derive/^r/y/ # r =\u0026gt; y\r# 下面 hu \u0026lt;=\u0026gt; f 这组写法复杂一些,分情况讨论\r#- derive/^hu$/fu/ # hu =\u0026gt; fu\r#- derive/^hong$/feng/ # hong =\u0026gt; feng\r#- derive/^hu([in])$/fe$1/ # hui =\u0026gt; fei, hun =\u0026gt; fen\r#- derive/^hu([ao])/f$1/ # hua =\u0026gt; fa, ...\r#- derive/^fu$/hu/ # fu =\u0026gt; hu\r#- derive/^feng$/hong/ # feng =\u0026gt; hong\r#- derive/^fe([in])$/hu$1/ # fei =\u0026gt; hui, fen =\u0026gt; hun\r#- derive/^f([ao])/hu$1/ # fa =\u0026gt; hua, ...\r# 韵母部份\r#- derive/^([bpmf])eng$/$1ong/ # meng = mong, ...\r#- derive/([ei])n$/$1ng/ # en =\u0026gt; eng, in =\u0026gt; ing\r#- derive/([ei])ng$/$1n/ # eng =\u0026gt; en, ing =\u0026gt; in\r# 样例足够了,其他请自己总结……\r# 反模糊音?\r# 谁说方言没有普通话精确、有模糊音,就能有反模糊音。\r# 示例为分尖团的中原官话:\r#- derive/^ji$/zii/ # 在设计者安排下鳩佔鹊巢,尖音i只好双写了\r#- derive/^qi$/cii/\r#- derive/^xi$/sii/\r#- derive/^ji/zi/\r#- derive/^qi/ci/\r#- derive/^xi/si/\r#- derive/^ju/zv/\r#- derive/^qu/cv/\r#- derive/^xu/sv/\r# 韵母部份,只能从大面上覆盖\r#- derive/^([bpm])o$/$1eh/ # bo =\u0026gt; beh, ...\r#- derive/(^|[dtnlgkhzcs]h?)e$/$1eh/ # ge =\u0026gt; geh, se =\u0026gt; sheh, ...\r#- derive/^([gkh])uo$/$1ue/ # guo =\u0026gt; gue, ...\r#- derive/^([gkh])e$/$1uo/ # he =\u0026gt; huo, ...\r#- derive/([uv])e$/$1o/ # jue =\u0026gt; juo, lve =\u0026gt; lvo, ...\r#- derive/^fei$/fi/ # fei =\u0026gt; fi\r#- derive/^wei$/vi/ # wei =\u0026gt; vi\r#- derive/^([nl])ei$/$1ui/ # nei =\u0026gt; nui, lei =\u0026gt; lui\r#- derive/^([nlzcs])un$/$1vn/ # lun =\u0026gt; lvn, zun =\u0026gt; zvn, ... #- derive/^([nlzcs])ong$/$1iong/ # long =\u0026gt; liong, song =\u0026gt; siong, ...\r# 这个办法虽从拼写上做出了区分,然而受词典制约,候选字仍是混的。\r# 只有真正的方音输入方案纔能做到!但「反模糊音」这个玩法快速而有效!\r# 模糊音定义先於简拼定义,方可令简拼支持以上模糊音\r- abbrev/^([a-z]).+$/$1/ # 简拼(首字母)\r- abbrev/^([zcs]h).+$/$1/ # 简拼(zh, ch, sh)\r# 以下是一组容错拼写,《汉语拼音》方案以前者为正\r- derive/^([nl])ve$/$1ue/ # nve = nue, lve = lue\r- derive/^([jqxy])u/$1v/ # ju = jv,\r- derive/un$/uen/ # gun = guen,\r- derive/ui$/uei/ # gui = guei,\r- derive/iu$/iou/ # jiu = jiou,\r# 自动纠正一些常见的按键错误\r- derive/([aeiou])ng$/$1gn/ # dagn =\u0026gt; dang - derive/([dtngkhrzcs])o(u|ng)$/$1o/ # zho =\u0026gt; zhong|zhou\r- derive/ong$/on/ # zhonguo =\u0026gt; zhong guo\r- derive/ao$/oa/ # hoa =\u0026gt; hao\r- derive/([iu])a(o|ng?)$/a$1$2/ # tain =\u0026gt; tian\r# 分尖团后 v =\u0026gt; ü 的改写条件也要相应地扩充:\r#\u0026#39;translator/preedit_format\u0026#39;:\r# - \u0026#34;xform/([nljqxyzcs])v/$1ü/\u0026#34; 【明月拼音·简化字/臺灣正體/语句流】也適用, 只需将模板保存到 luna_pinyin_simp.custom.yaml 、 luna_pinyin_tw.custom.yaml 或 luna_pinyin_fluency.custom.yaml 。\n对比模糊音定制模板与 【朙月拼音】方案原件, 可见模板的做法是,在 speller/algebra 原有的规则中插入了一些定义模糊音的代码行。\n类似方案如双拼、粤拼等可参考模板演示的方法改写 speller/algebra 。\n【吳語】模糊音定制模板 https://gist.github.com/2015335\n\u0026gt; rime 吴语输入方案模糊音定制档\n# 文件名: wugniu.custom.yaml\r# 为 wugniu.schema.yaml 打补靪\r# #号为註释符号,要是不需要开始某一行模糊功能请在该行前头加#号,要是需要该行模糊功能请去掉前面#号。注意要使用的行的对齐。\rpatch:\r\u0026#34;speller/algebra\u0026#34;:\r- abbrev/^([bcdfghjklprstvwxyz]).+$/$1/ # 对齐标準行\r- derive/^tzi/ci/ # 以下四行是模糊分尖团音\r- derive/^tsi/chi/\r- derive/^zi/ji/\r- derive/^si/xi/\r- derive/^(ch|d?j|gn|x|y)i([aeou])/$1$2/ # 本行不要改动\r- derive/^tzyu/tzy/ # 以下四行是模糊“书”“诗”\r- derive/^tsyu/tsy/\r- derive/^zyu/zy/\r- derive/^syu/sy/\r- derive/(.*)ij/$1i/ # 模糊“烟”“衣”\r- derive/(.*)iaq/$1iq/ # 模糊“约”“一”\r- derive/^yaq/yiq/ # 模糊“药”“页”\r- derive/(.*)aon/$1an/ # 模糊前后an\r- derive/(.*)aq/$1eq/ # 模糊“搭”“得”\r- derive/(.*)eq/$1aq/\r- derive/^yeu/yu/ # 模糊“远”“与”\r- derive/(.*)ieu/$1iu/ # 模糊“宛”“餵”\r- derive/^vu/wu/ # 模糊“无”“何”\r- derive/(.*)ueu/$1eu/ # 模糊“碗”“按”\r- derive/^weu/reu/ # 模糊“换”“汗”\r#- derive/^au/u/ # 模糊“查”“坐” 编码反查 设定【速成】的反查码为粵拼 https://gist.github.com/2944320\n\u0026gt; rime 定制档:设定「 速成 」的反查码为粤拼\n# 将反查词典改为粤拼。\r# 注意【速成】支持全码、简码混合输入,反查出来的实为仓頡全码。\rpatch:\rreverse_lookup/dictionary: jyutping\rreverse_lookup/tips: 〔粤拼〕\rreverse_lookup/preedit_format: [] 设定【仓颉】的反查码为双拼 https://gist.github.com/2944319\n\u0026gt; rime 定制档:设定「 仓颉 」的反查码为双拼\n# 因为【双拼】使用的词典就是【朙月拼音】的词典,所以输入方案中原有的\r# reverse_lookup/dictionary: luna_pinyin 保持不变,另外指定一项 prism\rpatch:\rreverse_lookup/prism: double_pinyin\rreverse_lookup/tips: 〔双拼〕\rreverse_lookup/preedit_format: [] 在 mac 系统上输入 emoji 表情 以下配置方法已过时,新的 emoji 用法见 https://github.com/rime/rime-emoji\n五笔简入繁出 【小狼毫】用家请到 下载页取得「扩展方案集」。\n安裝完成后,执行输入法设定,添加【五筆·簡入繁出】输入方案。\n其他版本请参考这篇说明:\nhttps://gist.github.com/3467172\n\u0026gt; readme\n一,安装简体-\u0026gt;繁体映射码表:(该项目由opencc维护:http://code.google.com/p/opencc)\r文件1:http://code.google.com/p/rimeime/source/browse/trunk/misc/opencc/data/simp_to_trad_characters.ocd\r文件2:http://code.google.com/p/rimeime/source/browse/trunk/misc/opencc/data/simp_to_trad_phrases.ocd\r安装位置:共享资料目录\r如:/library/input methods/squirrel.app/contents/sharedsupport/opencc\r二,添加opencc简繁配置文件:\r详见:zhs2zht.ini\r安装位置:共享资料目录\r如:/library/input methods/squirrel.app/contents/sharedsupport/opencc\r三,自定义五笔拼音输入法配置(同时可用於86版,本文仅提供五笔拼音配置文件)\r详见:wubi_pinyin.custom.yaml\r安装位置:用户资料目录\r如:~/library/rime \u0026gt; wubi_pinyin.custom.yaml\npatch:\rswitches:\r- name: ascii_mode\rreset: 0\rstates: [ 中文, 西文 ]\r- name: full_shape\rstates: [ 半角, 全角 ]\r- name: simplification\rstates: [ 漢字, 汉字 ]\rsimplifier/opencc_config: zhs2zht.ini\rengine/filters:\r- simplifier\r- uniquifier \u0026gt; zhs2zht.ini\n; open chinese convert\r;\r; copyright 2010 byvoid \u0026lt;byvoid.kcp@gmail.com\u0026gt;\r;\r; licensed under the apache license, version 2.0 (the \u0026#34;license\u0026#34;);\r; you may not use this file except in compliance with the license.\r; you may obtain a copy of the license at\r;\r; http://www.apache.org/licenses/license-2.0\r;\r; unless required by applicable law or agreed to in writing, software\r; distributed under the license is distributed on an \u0026#34;as is\u0026#34; basis,\r; without warranties or conditions of any kind, either express or implied.\r; see the license for the specific language governing permissions and\r; limitations under the license.\rtitle = simp_to_trad\rdescription = standard configuration for conversion from simplified chinese to traditional chinese\rdict0 = ocd opencc/simp_to_trad_phrases.ocd\rdict0 = ocd opencc/simp_to_trad_characters.ocd 修正不对称繁简字 繁→简即时转换比简体转繁体要轻松许多,却也免不了个别的错误。\n比如这一例,「 乾 」字是一繁对多简的典型。由它组成的常用词组,opencc 做了了仔细分辨。但是遇到比较生僻的词组、专名,就比较头疼:\nhttp://tieba.baidu.com/p/1909252328\n活用标点创建自定义词组 在【朙月拼音】里添加一些自定义文字、符号。可以按照上文设定「 emoji 表情 」的方式为自定义词组创建一个专门的词典。\n可是建立词典稍显繁琐,而活用自定义标点,不失为一个便捷的方法:\n1 2 3 4 5 6 7 8 # luna_pinyin.custom.yaml # 如果不需要 `键的仓颉反查拼音功能,則可利用 ` 键输入自定义词组 patch: recognizer/patterns/reverse_lookup: \u0026#39;punctuator/half_shape/`\u0026#39;: - \u0026#39;佛振 \u0026lt;chen.sst@gmail.com\u0026gt;\u0026#39; - \u0026#39;http://rime.github.io\u0026#39; - 上天赋予你高的智商,教你用到有用的地方。 上例 recognizer/patterns/reverse_lookup: 作用是开关 ~ 键的反查功能。若选用其他符号则不需要这行。又一例:\n1 2 patch: \u0026#39;punctuator/half_shape/*\u0026#39;: \u0026#39;*_*\u0026#39; 'punctuator/half_shape/*' 因为字符串包含符号,最好用 单引号 括起來;尽量不用双引号以避免符号的转义问题。\n然而,重定义「/」「+」「=」这些符号时,因其在节点路径中有特殊含义,无法用上面演示的路径连写方式。因此对于标点符号,推荐的定制方法为在输入方案里覆盖定义 half_shape 或 full_shape 节点:\n1 2 3 4 5 patch: punctuator/half_shape: \u0026#39;/\u0026#39;: [ \u0026#39;/\u0026#39;, \u0026#39;/hello\u0026#39;, \u0026#39;/bye\u0026#39;, \u0026#39;/* todo */\u0026#39; ] \u0026#39;+\u0026#39;: \u0026#39;+_+\u0026#39; \u0026#39;=\u0026#39;: \u0026#39;=_=\u0026#39; lua 脚本开发指南 librime-lua 是 rime输入法引擎的一个插件,它为用户提供了使用 lua 脚本语言扩展输入法的能力。通过 lua ,你可以实现单纯使用配置文件难以做到的灵活多样的功能,比如输入任意动态短语(日期时间、大写数字、计算器……)、自由重排 / 过滤候选词、甚至云输入等。\n要理解本项目的工作原理,需先了解 rime 功能组件的基本概念与工作流程,见 rime 输入方案。简而言之,librime-lua 提供了处理器 processor 、分段器 segmentor 、翻译器 translator 和过滤器 filter 这四类组件的开发接口。\n本文通过一个例子来说明使用 librime-lua 开发组件的完整流程,然后在下一个章节中详细介绍各类组件的编程接口。\n例子:输入今天的日期 我们通过「 输入今天的日期 」这个例子,来说明开发定制的流程。一共分三步:编写代码、配置方案、重新部署。\n❶ 编写代码\n我们希望在输入 “date” 之后,在输入法候选框中得到今天的日期。为此,我们需要一个能产生今天日期的翻译器。我们在 rime 的用户目录下新建一个 rime.lua 文件,这是 lua 脚本的总入口。在文件中添加以下内容:\n1 2 3 4 5 6 7 -- rime.lua function date_translator(input, segment) if (input == \u0026#34;date\u0026#34;) then --- candidate(type, start, end, text, comment) yield(candidate(\u0026#34;date\u0026#34;, segment.start, segment._end, os.date(\u0026#34;%y年%m月%d日\u0026#34;), \u0026#34; 日期\u0026#34;)) end end 上面实现了一个叫做 date_translator 的函数。它的输入是 input 和 segment ,分别传入了翻译器需要翻译的内容和它在输入串中的位置。这个函数会判断输入的是否是 date ,如果是,则生成一个内容为今天日期的候选项。\n候选项的构造函数为 candidate ,该函数有五个参数:\n类型 ,可以是任意字符串,这里用了 'date' ; 开始、结束位置,一般用 segment.start 和 segment._end 就可以,它表示了我们要将整个待翻译的输入串替换为候选内容; 候选内容,是使用 lua 的函数库生成的日期; 候选注释,是 日期 ,这会显示在候选框中展示在候选内容旁边。 候选项生成以后是通过 yield 发出的,yield 在这里可以简单理解为 发送一个选项到候选框中 。一个函数可以 yield 任意次,这里我们只有一个候选项,所以只有一个 yield 。\n❷ 配置方案\n我们已经编写了输入日期的翻译器。为了让它生效,需要修改输入方案的配置文件。以朙月拼音为例,找到 luna_pinyin.schema.yaml,在 engine/translators 中加入一项 lua_translator@date_translator ,如下:\n# luna_pinyin.schema.yaml\rengine:\r...\rtranslators:\r- lua_translator@date_translator\r... 这样就完成了配置。它表示从 lua 执行环境中找到名为 date_translator 的对象,将它作为一种翻译器添加到引擎之中。其他类型的组件配置类推,分别为 lua_processor、lua_segmentor、lua_filter 。\n❸ 重新部署\n以上完成了所有开发和配置。点击 「 重新部署 」 ,输入 date ,就可以看到候选框中出现了今天的日期。\n在 本项目的示例目录 下,还有更多的例子。配合其中的注释并稍加修改,就可以满足大部分的日常需求。\n模块化 到目前为止,我们的代码完全集中在 rime.lua 中,本节说明 librime-lua 对模块化的支持。\n模块化是指把脚本分门别类,放到独立的文件中,避免各自的修改互相干扰,也方便把自己的作品分享给他人使用。\n我们仍以 date_translator 为例。\n❶ 分离脚本内容\n首先需要把原来写在 rime.lua 的脚本搬运到独立的文件中。删掉 rime.lua 原有的内容后,在 rime 的用户目录下新建一个 lua 目录,在该目录下再建立一个 date_translator.lua 文件,录入以下内容:\n1 2 3 4 5 6 7 8 9 -- lua/date_translator.lua local function translator(input, seg) if (input == \u0026#34;date\u0026#34;) then --- candidate(type, start, end, text, comment) yield(candidate(\u0026#34;date\u0026#34;, seg.start, seg._end, os.date(\u0026#34;%y年%m月%d日\u0026#34;), \u0026#34; 日期\u0026#34;)) end end return translator 可以看到主要内容与之前一致,但有两点不同:\n¹ 使用 local 。lua 脚本的变量作用域默认是全局的,如果不同模块的变量或函数正好用了相同的名字,就会导致互相干扰,出现难以排查的问题。因此,尽量使用 local 把变量作用域限制在局部。\n² 新增 return 。 librime-lua 是借助 lua 的 require 机制实现模块加载。引用模块时,整个文件被当作一个函数执行,返回模块内容。在被里返回的是一个翻译器组件。\n❷ 配置方案直接引用模块\n我们已经建立了一个名叫 date_translator 的模块。我们可以在输入方案配置文件中写入以下内容来直接引用被个模块所返回的组件,而不是从全局的 rime.lua 中的获取组件:\n# luna_pinyin.schema.yaml\rengine:\r...\rtranslators:\r- lua_translator@*date_translator\r... 与之前的区别是第一个 @ 之后多了一个 * 。👻 它表示什么?\n这个星号 * 表示后面的名字是模块名而还是全局变量名。当遇到星号时,librime-lua 会使用 require 机制加载模块,然后将其返回值作为 rime 组件加载到输入法框架中。\n❸ 更多引用方式\n首先,你可以对同一个 lua 组件加载多个实例,此时需要用 @namespace 的语法标注出命名空间以区分不同的实例:\nengine:\rtranslators: - lua_translator@*date_translator # name_space \u0026#34;date_translator\u0026#34;\r- lua_translator@*date_translator@date # namespace \u0026#34;date\u0026#34; 其次,你还可以引用位于 lua/ 文件夹下的子文件夹中的模块,此时路径的分隔符可以用 . ,也可以用 / ,例如:\nengine:\rtranslators:\r- lua_translator@*date.translator # lua/date/translator.lua\r- lua_translator@*date/translator # lua/date/translator.lua\r- lua_translator@*date/subdir/translator # lua/date/subdir/translator.lua 最后,若一个文件通过 lua 表的方式导出了多个组件,使用 * 还可以在表中进一步搜索,例如:\nengine:\rtranslators:\r- lua_translator@*module_name*subtable1*subtable2*tran@name_space\rprocessors:\r- lua_translator@*module_name*subtable1*subtable2*proc@name_space 对应了以下的用表组织起来的多个组件:\n1 2 3 4 5 6 7 8 9 10 11 12 -- lua/module_name.lua return { subtable1 = { subtable2 = { tran = function (input, segment, env) ... end, proc = function (key, env) ... end, filter = function (translation, env) ... end, }, }, subtable2 = { ... }, subtable3 = { ... }, } ❹ 旧版 librime-lua 的使用方式\n如果使用的是较旧的 librime-lua ,则配置文件不能直接载入在子目录中的 lua 模块。在将模块分离出去之后,仍然需要在 rime.lua 内输入以下内容:\n1 date_translator = require(\u0026#34;date_translator\u0026#34;) 前一个 date_translator 是全局变量名,后一个是模块名。librime-lua 将 rime 的用户目录和 rime 共享目录下的 lua 目录加入了模块搜索路径,因此本句的意义是搜索 date_translator 模块,并将返回的组件绑定到同名的全局变量上。被样就可以输入方案配置文件中使用了,方法与之前一致。\n❺ 配方补丁\n以上我们实现了 lua 脚本的模块化。但为引用组件,仍需修改 schema.yaml 配置文件。那么如何模块化都修改配置文件呢?\n请参考 rime 配置文件 中关于补丁的讲解。\n编程接口 本节详细介绍编程接口。根据我们此前讲过的约定,以下假定所编写的 lua 组件位于 lua/my_rime 子文件夹下,即为 lua/my_ime/xxx.lua,且导入时不指定相关的命名空间。你也可以使用上面介绍过的命空间、子文件夹以及 lua 表等方式来更灵活都组织你的脚本。\n❶ lua 处理器 lua_processor\nlua_processor 提供了处理器的开发接口,它在配置文件的 engine/processors 中配置:\nengine:\rprocessors:\r- lua_processor@*my_ime.my_processor my_processor 所指对象有多种形式:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- 简化形式 1 local function my_processor(key_event) ... end --- 简化形式 2 local function my_processor(key_event, env) ... end --- 完整形式 local my_processor = { init = function(env) ... end, func = function(key_event, env) ... end, fini = function(env) ... end } 简化形式是一个 lua 函数,此函数执行处理器的逻辑,参数为:\nkey_event : keyevent 对象,为待处理的按键(包括带修饰符的组合键); env:env 对象,包括 engine 和 name_space 两个成员,分别是 engine 对象和前述 name_space 配置字符串。 返回值为 0、1、2 :\n¹ 0 表示 krejected,声称本组件和其他组件都不响应该输入事件,结束处理流程,交还操作系统按默认方式响应(例如 ascii 字符上屏、方向翻页等功能作用于客户程序或系统全局等)。\n注意:如果组件已响应该输入事件但返回 krejected ,则按键会再被操作系统处理一次,有可能产生 「 处理了两次 」的效果。\n² 1 表示 kaccepted,声称本组件已响应该输入事件,结束处理流程,之后的组件以及操作系统都不再响应应该输入事件。\n注意:如何组件未响应该输入事件但返回 kaccepted,相当于禁用这个按键。\n³ 2 表示 knoop,声称本函数不响应该输入事件,交给接下来的处理器决定。\n注意:如果组件已响应该输入事件但返回 knoop,则按键会被接下来的组件处理一次或多次,有可能产生 「 处理了多次 」的效果。如果所有处理器都返回 knoop,则交还由操作系统按默认方式响应。\n完整形式是一个 lua 表,其中 func 与简化形式意义相同。init 与 fini 分别在组件构造与析构时调用。\n❷ lua 分段器 lua_segmentor\nlua_segmentor 提供了分段器的开发接口,它在配置文件的 engine/segmentor 中配置:\nengine:\rsegmentors:\r- lua_segmentor@*my_ime.my_segmentor my_segmentor 所指对象有多种形式:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- 简化形式 1 local function my_segmentor(segmentation) ... end --- 简化形式 2 local function my_segmentor(segmentation, env) ... end --- 完整形式 local my_segmentor = { init = function (env) ... end, func = function (segmentation, env) ... end, fini = function (env) ... end } 简化形式是一个 lua 函数,此函数执行分段器的逻辑,参数为:\nsegmentation: segmentation 对象; env:env 对象,含义同前。 而返回值为布尔类型:\ntrue: 交由下一个分段器处理; false: 终止分段器处理流程。 而完整形式是一个 lua 表,其中 func 与简化形式意义相同。init 与 fini 分别在组件构造与析构时调用。\n❸ lua 翻译器 lua_translator\nlua_translator 提供了翻译器的开发接口。它在配置文件的 engine/translator 中配置:\n1 2 3 engine: translators: - lua_translator@*my_ime.my_translator my_translator 所指对象有多种形式:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- 简化形式 1 local function my_translator(input, segment) ... end --- 简化形式 2 local function my_translator(input, segment, env) ... end --- 完整形式 local my_translator = { init = function (env) ... end, func = function (input, segment, env) ... end, fini = function (env) ... end } 简化形式是一个 lua 函数,此函数执行翻译器的逻辑,参数为:\ninput:string 类型的字符串,为待翻译串; segment: segment 对象; env:env 对象,含义同前。 函数无返回值,而是通过 yield 发送 candidate 对象或其他的候选对象。\n而完整形式是一个 lua 表,其中 func 与简化形式意义相同。init 与 fini 分别在 lua_translator 构造与析构时调用。\n❹ lua 过滤器 lua_filter\nlua_filter 提供了过滤器的开发接口。它在配置文件的 engine/filters 中配置:\n1 2 3 engine: filters: - lua_filter@*my_ime.my_filter my_filter 所指对象有多种形式:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 --- 简化形式 1 local function my_filter(translation) ... end --- 简化形式 2 local function my_filter(translation, env) ... end --- 完整形式 local my_filter = { init = function (env) ... end, func = function (translation, env) ... end, fini = function (env) ... end, tags_match = function (segment, env) ... end --- 可选 } 简化形式是一个 lua function,此函数执行过滤器的逻辑,参数为\ntranslation: translation 对象,为待过滤的 candidate 流; env:env 对象,含义同前。 函数无返回值,而是通过 yield 发送 candidate 对象或其他的候选对象。\n而完整形式是一个 lua 表,其中 func 与简化形式意义相同。init 与 fini 分别在 lua_filter 构造与析构时调用。另外,tags_match 出现时可以指定该过滤器只在某种特定情况下工作,通常是使用 segment 所包含的 tag 来判断。\n对象接口 librime-lua 封装了 librime c++ 对象到 lua 中供脚本访问。需注意随着项目的开发,以下文档可能是不完整或过时的,敬请各位参与贡献文档。\n这个章节请直接参考 原链接 🔗 。\n附录 cand:get_genuine().text = 't_cand' 为什么没有修改候选项的内容 在 lua 的 rime api 中,直接修改候选项(candidate 对象)的 text 属性通常无效,因为 candidate 对象是只读的。你需要通过创建一个新的 candidate 对象来替换现有的候选项,而不是试图直接修改它。\n❶ 原因\ncand:get_genuine() 返回的是原始候选项(cand),但 rime 的候选项属性(如 text 和 comment)是不可变的,不能直接修改。 想要更改候选项的内容,你需要创建一个新的候选项,并在必要时将其插入到 segment 中。 ❷ 解决方法\n创建新的候选项 使用 candidate 构造函数创建一个新的候选项,并用新的 text 替换现有候选项。\n示例代码 假设你想在 filter 函数中修改候选项内容:\n1 2 3 4 5 6 7 function filter(input, env) for cand in input:iter() do -- 创建新的候选项,修改内容 local new_cand = candidate(cand.type, cand.start, cand._end, \u0026#34;t_cand\u0026#34;, cand.comment) yield(new_cand) -- 使用 yield 替换原始候选项 end end candidate 构造函数:\n1 candidate(type, start, end, text, comment) type: 候选项类型,例如 \u0026quot;completion\u0026quot;。 start 和 end: 候选项在输入字符串中的范围。 text: 候选项的实际显示文本。 comment: 候选项的附加注释(可选)。 处理 cand:get_genuine() 如果需要从原始候选项提取某些属性,可以使用 cand:get_genuine() 提取真正的候选项,然后基于这些信息创建新的候选项。\n❸ 注意事项\n顺序问题:确保修改后的候选项按需要的顺序插入到候选列表中。 性能考虑:在大规模处理候选项时,尽量优化逻辑,避免不必要的重复操作。 调试日志:在修改候选项时,添加调试日志可以帮助确认候选项是否正确被替换。例如: 1 print(\u0026#34;original:\u0026#34;, cand.text, \u0026#34;-\u0026gt; modified:\u0026#34;, new_cand.text) 通过上述方式,你可以正确修改 rime 的候选项内容。\n五笔码表开头的那些属性是什么意思 ---\rname: wubi86 # 码表名称\rversion: \u0026#34;0.7\u0026#34; # 版本号\rsort: by_weight # 词条按权重排序\rcolumns: # 定义了数据表中每一列的含义\r- text # ... 字词\r- code # ... 对应的五笔编码\r- weight # ... 词条权重(用于排序和优先级)\r- stem # ... 词干编码(可能是词组的简化编码)\rencoder: # 定义编码生成的规则和排除模式\rexclude_patterns: # ... 排除某些编码模式 - \u0026#39;^z.*$\u0026#39; # ... 排除所有以字母 `z` 开头的编码\rrules: # 定义了不同长度词组的〔编码生成规则〕\r- length_equal: 2 # ... 长度为 2 的词组使用公式 `aaabbabb` 生成编码\rformula: \u0026#34;aaabbabb\u0026#34;\r- length_equal: 3 # ... 长度为 3 的词组使用公式 `aabacacb` 生成编码\rformula: \u0026#34;aabacacb\u0026#34;\r- length_in_range: [4, 10] # ... 长度为 3 的词组使用公式 `aabacaza` 生成编码\rformula: \u0026#34;aabacaza\u0026#34; 上述 yaml 配置文件定义了一个 五笔 86 输入法 的架构。\n公式说明\n公式中的字母代表编码的生成规则:\na、b、c 等表示从词组的第 1、2、3 个字中提取编码。 a、b、c 等表示提取编码的特定部分(如首字母或特定位置的字母)。 z 可能表示从整个词组中提取编码。 例如:\naaabbabb 表示: 从第 1 个字提取编码的第 1 部分(a)和第 2 部分(a)。 从第 2 个字提取编码的第 1 部分(b)和第 2 部分(b)。 这个配置文件定义了一个五笔 86 输入法的编码规则和数据结构,支持不同长度的词组编码生成,并通过权重排序优化用户体验。排除模式和编码公式的设计使得输入法更加灵活和高效。\n","date":"2025-01-06","permalink":"https://aituyaa.com/rime-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e说是“输入法那些事儿”,其实主要就是 Rime 那些事儿。商业输入法不怎么用,也就没有什么太多好说的,不过做为参照物可能会略有提及。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: 目前可以看成是 Rime Home Wiki 的汇总。 \u0026ndash; 🤣 好嘛,经过两三天的噼里啪啦,终于整合在了一起。原 Wiki 毕竟是 wiki ,内容有些凌乱,后续有时间再慢慢整理、删减、补充吧。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"rime 那些事儿"},]
[{"content":"最近配置 [[rime_jk 指尖上的旋律|rime]] 的时候遇到了不少 lua 脚本,加上比较流行的 neovim 也是使用的这个嵌入式脚本,java 和一些游戏中也使用了这个脚本语言……\n![[assets/pasted image 20250103183548.png|500]]\n这难免让人想要多了解一下她,不是吗?所以,让我们开始吧 🎉\n📘 lua 5.3 参考手册\n📗 lua 教程 | 菜鸟教程\n这里我们以实用性为中心,简要了解一下其基本用法 ~\n基本概念 注释 以两个连字符 -- 开头。\n1 2 3 4 5 6 -- 单行注释 --[[ 多行注释 多行注释 --]] 标识符 以一个字母 a-za-z 或下划线 _ 开头后加上 0 个或多个字母,下划线,数字(0 到 9),且区分大小写。\n⚠️ lua 不允许使用特殊字符如 @, $, 和 % 来定义标识符。\nlua 中有 8 个 基本类型 分别为: nil、boolean、number、string、userdata、function、thread 和 table 。\n可以使用 type 函数测试给定变量或者值的类型。\n字符串 由一对双引号或单引号来表示,可以用 2 个方括号 [[]] 来表示\u0026quot;一块\u0026quot;字符串。字符串连接使用的是 .. ,使用 # 来计算字符串的长度,放在字符串前面,如:#\u0026quot;www.runoob.com\u0026quot; 。\n在 lua 里,表 table 的创建是通过\u0026quot;构造表达式\u0026quot;来完成,其实是一个\u0026quot;关联数组\u0026quot;(associative arrays),数组的索引可以是数字或者是字符串。\n1 2 3 4 5 6 7 8 9 10 11 -- 创建一个空的 table local tbl1 = {} -- 直接初始表 local tbl2 = {\u0026#34;apple\u0026#34;, \u0026#34;pear\u0026#34;, \u0026#34;orange\u0026#34;, \u0026#34;grape\u0026#34;} local tal3 = {key1=\u0026#34;val1\u0026#34;,key2=\u0026#34;val2\u0026#34;} -- 使用 for...in 来遍历 for k, v in pairs(a) do print(k .. \u0026#34; : \u0026#34; .. v) end 对 table 的索引使用方括号 [],也提供了 . 操作。\nuserdata 是一种用户自定义数据,用于表示一种由应用程序或 c/c++ 语言库所创建的类型,可以将任意 c/c++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 lua 变量中调用。\nlua 变量 有三种类型:全局变量、局部变量、表中的域。\n⚠️ lua 中的变量全是全局变量,哪怕是语句块或是函数里,除非用 local 显式声明为局部变量。\n局部变量的作用域为从声明位置开始到所在语句块结束。默认值均为 nil。\nlua 语言提供了以下几种循环处理方式:\nwhile (条件) do...end;\rfor...do...end;\rrepeat...until (条件); 可以使用 break 退出当前循环,也可以使用 goto 将程序的控制点转移到一个标签处。\nlua 提供了以下控制结构语句:\nif (条件) then ... end\rif ... else ...\rif ... else if ... 在 lua 中,函数 是对语句和表达式进行抽象的主要方法。可以返回多个值,每个值以逗号隔开。\nlua 提供了以下几种运算符类型:\n算术运算符 关系运算符 逻辑运算符 其他运算符(.. 连接字符串,# 返回字符串或表的长度) 大多数与其它编程语言大同小异,其中使用 ~= 表示不等于,而非 != 。\n字符串 :: 字符串、数组、表是最最常用的数据结构了。\n在 lua 中,字符串可以使用以下三种方式来表示:\n- 单引号间的一串字符\r- 双引号间的一串字符\r- [[ 与 ]] 间的一串字符 要计算字符串的长度(即字符串中字符的个数),包含中文的一般用 utf8.len,string.len 函数用于计算只包含 ascii 字符串的长度。\n我们主要来看一下字符串的相关操作。\n字符串截取 string.sub(s, i [, j]) 其中:\ns 表示要截取的字符串; i 截取开始的位置; j 截取结束的位置,默认为 -1 - 最后一个字符。 字符串替换 string.gsub(mainstring,findstring,replacestring,num) 在字符串中替换,其中:\nmainstring 为要操作的字符串; findstring 为被替换的字符; replacestring 为要替换的字符; num 替换次数(可以忽略,忽略则全部替换)。 1 2 string.gsub(\u0026#34;aaaa\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;z\u0026#34;, 3); --- zzza 3 字符串查找(匹配) ¹ 返回匹配子串的索引位置\n在一个指定的目标字符串 str 中搜索指定的内容 substr,如果找到了一个匹配的子串,就会返回这个子串的起始索引和结束索引,不存在则返回 nil。\nstring.find (str, substr, [init, [plain]]) 其中:\nstr 目标字符串(要在这个字符串中搜索); substr 要匹配的子串; init 搜索的起始位置,默认为 1,可以是一个负数,表示从后往前的字符个数; plain ,默认为 false ,表示使用正则模式匹配;true 表示只作简单的查找子串的操作。 1 2 string.find(\u0026#34;hello lua user\u0026#34;, \u0026#34;lua\u0026#34;, 1) -- 7 9 ² 返回匹配的子串\nstring.match(str, pattern, init) string.match() 只寻找源字符串 str 中的第一个配对,参数 init 可选, 指定搜寻过程的起点, 默认为 1 。\n在成功匹配时,函数将返回配对表达式中的所有捕获结果;如果没有设置捕获标记,则返回整个配对字符串。\n如果没有成功的配对呢?返回 nil 。\n1 2 3 4 5 string.match(\u0026#34;i have a questions for you.\u0026#34;, \u0026#34;%d+ %a+\u0026#34;) -- 2 questions string.format(\u0026#34;%d, %q\u0026#34;, string.match(\u0026#34;i have 2 questions for you.\u0026#34;, \u0026#34;(%d+) (%a+)\u0026#34;)) -- 2, \u0026#34;questions\u0026#34; ³ 返回迭代器函数\n那么, string.gmatch() 呢?\nstring.gmatch(str, pattern) 它并不返回字符串,而是返回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回 nil 。\n1 2 3 4 5 6 7 for word in string.gmatch(\u0026#34;hello lua user\u0026#34;, \u0026#34;%a+\u0026#34;) do print(word) end --- hello --- lua --- user 匹配模式 lua 中的匹配模式直接用常规的字符串来描述,它用于模式匹配函数 string.find 、string.match、string.gmatch、string.gsub 。\n还可以在模式串中使用字符类。什么是 字符类 呢?\n字符类指可以匹配一个特定字符集合内任何字符的模式项,比如,字符类 %d 匹配任意数字。下面的表列出了 lua 支持的所有字符类:\n单个字符(除 ^$()%.[]*+-? 外): 与该字符自身配对\n- .(点): 与任何字符配对\r- %a: 与任何字母配对\r- %c: 与任何控制符配对(例如\\n)\r- %d: 与任何数字配对\r- %l: 与任何小写字母配对\r- %p: 与任何标点(punctuation)配对\r- %s: 与空白字符配对\r- %u: 与任何大写字母配对\r- %w: 与任何字母/数字配对\r- %x: 与任何十六进制数配对\r- %z: 与任何代表 0 的字符配对\r- %x(此处x是非字母非数字字符): 与字符x配对. 主要用来处理表达式中有功能的字符(^$()%.[]*+-?)的配对问题, 例如%%与%配对\r- [数个字符类]: 与任何[]中包含的字符类配对. 例如[%w_]与任何字母/数字, 或下划线符号(_)配对\r- [^数个字符类]: 与任何不包含在[]中的字符类配对. 例如[^%s]与任何非空白字符配对 \u0026rsquo; % \u0026rsquo; 用作特殊字符的转义字符,因此 \u0026rsquo; %. \u0026rsquo; 匹配点;\u0026rsquo; %% \u0026rsquo; 匹配字符 \u0026rsquo; % \u0026lsquo;。转义字符 \u0026rsquo; % \u0026lsquo;不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。\n模式条目可以是:\n单个字符类匹配该类别中任意单个字符; 单个字符类跟一个 \u0026rsquo; * \u0026lsquo;, 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串; 单个字符类跟一个 \u0026rsquo; + \u0026lsquo;, 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串; 单个字符类跟一个 \u0026rsquo; - \u0026lsquo;, 将匹配零或更多个该类的字符。 和 \u0026rsquo; * \u0026rsquo; 不同, 这个条目总是匹配尽可能短的串; 单个字符类跟一个 \u0026rsquo; ? \u0026lsquo;, 将匹配零或一个该类的字符。 只要有可能,它会匹配一个; %n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。 %bxy, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持平衡的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式。 %f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 \u0026rsquo; \\0 \u0026rsquo; 一样。 :: 哎哎呀,都什么乱七八糟的 👻\n模式 指一个模式条目的序列。 在模式最前面加上符号 ^ 将锚定从字符串的开始处做匹配。 在模式最后面加上符号 $ 将使匹配过程锚定到字符串的结尾。 如果 ^ 和 $ 出现在其它位置,它们均没有特殊含义,只表示自身。\n模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。\n当匹配成功时,由捕获物匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号。\n例如,对于模式 \u0026ldquo;(a*(.)%w(%s*))\u0026rdquo; , 字符串中匹配到 \u0026ldquo;a*(.)%w(%s*)\u0026rdquo; 的部分保存在第一个捕获物中 (因此是编号 1 ); 由 \u0026ldquo;.\u0026rdquo; 匹配到的字符是 2 号捕获物, 匹配到 \u0026ldquo;%s*\u0026rdquo; 的那部分是 3 号。\n作为一个特例,空的捕获 () 将捕获到当前字符串的位置(它是一个数字)。 例如,如果将模式 \u0026ldquo;()aa()\u0026rdquo; 作用到字符串 \u0026ldquo;flaaap\u0026rdquo; 上,将产生两个捕获物: 3 和 5 。\n字符串格式化 lua 提供了 string.format() 函数来生成具有特定格式的字符串, 函数的第一个参数是格式 , 之后是对应格式中每个代号的各种数据。\n%c - 接受一个数字, 并将其转化为 ascii 码表中对应的字符; %d, %i - 接受一个数字并将其转化为有符号的整数格式; %f - 接受一个数字并将其转化为浮点数格式; %q - 接受一个字符串并将其转化为可安全被 lua 编译器读入的格式; %s - 接受一个字符串并按照给定的参数格式化该字符串。 数组 在 lua 中,数组不是一种特定的数据类型,而是一种用来存储一组值的数据结构。实际上,lua 中并没有专门的数组类型,而是使用一种被称为 \u0026ldquo;table\u0026rdquo; 的数据结构来实现数组的功能。\n⚠️ 在 lua 索引值是以 1 为起始,但你也可以指定 0 开始。\n一维数组可以用 for 循环出数组中的元素,如下实例:\n1 2 3 4 5 6 7 -- 创建一个数组 local myarray = {10, 20, 30, 40, 50} -- 循环遍历数组 for i = 1, #myarray do print(myarray[i]) end 表 table table 是 lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。\n下面来看一些 table 的常见操作:\n❶ 连接\ntable.concat (table [, sep [, start [, end]]]): table.concat() 函数列出参数中指定 table 的数组部分从 start 位置到 end 位置的所有元素, 元素间以指定的分隔符(sep)隔开。\n1 2 3 4 5 6 7 8 9 10 11 12 fruits = {\u0026#34;banana\u0026#34;,\u0026#34;orange\u0026#34;,\u0026#34;apple\u0026#34;} -- 返回 table 连接后的字符串 print(\u0026#34;连接后的字符串 \u0026#34;,table.concat(fruits)) -- 连接后的字符串 bananaorangeapple -- 指定连接字符 print(\u0026#34;连接后的字符串 \u0026#34;,table.concat(fruits,\u0026#34;, \u0026#34;)) -- 连接后的字符串 banana, orange, apple -- 指定索引来连接 table print(\u0026#34;连接后的字符串 \u0026#34;,table.concat(fruits,\u0026#34;, \u0026#34;, 2,3)) -- 连接后的字符串 orange, apple ❷ 插入和移除\ntable.insert (table, [pos,] value) 在 table 的数组部分指定位置(pos)插入值为 value 的一个元素, pos 参数可选, 默认为数组部分末尾。\ntable.remove (table [, pos]) 返回 table 数组部分位于 pos 位置的元素. 其后的元素会被前移。pos 参数可选, 默认为 table 长度, 即从最后一个元素删起。\n❸ 排序\ntable.sort (table [, comp]) 对给定的 table 进行升序排序。\n文件 io lua i/o 库用于读取和处理文件,分为简单模式(和 c 一样)、完全模式。\n简单模式,拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。\n完全模式,使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法。\n简单模式在做一些简单的文件操作时较为合适,但在进行一些高级的文件操作的时候,就显得力不从心。如要同时读取多个文件这样的操作,使用完全模式则较为合适。\n打开文件操作语句如下:\nfile = io.open(filename [, mode]) mode 的值有哪些呢?\n- r 以只读方式打开文件(文件必须存在)\r- w 以只写方式打开文件\r- a 以附加方式打开只写文件\r- r+ 以读写方式打开文件(文件必须存在)\r- w+ 以读写方式打开文件\r- a+ 以读写方式附加文件\r- b 以二进制模式\r- + 表示对 文件既可以读,也可以写 简单模式 简单模式使用标准的 i/o 或使用一个当前输入文件和一个当前输出文件。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -- 以只读方式打开文件 ifile = io.open(\u0026#34;input.lua\u0026#34;, \u0026#34;r\u0026#34;) -- 设置默认输入文件为 input.lua io.input(file) -- 输出文件第一行 print(io.read()) -- 关闭打开的文件 io.close(file) -- 以附加方式打开只写文件 ofile = io.open(\u0026#34;output.lua\u0026#34;, \u0026#34;a\u0026#34;) -- 设置默认输出文件为 output.lua io.output(file) -- 在文件最后一行添加 lua 注释 io.write(\u0026#34;-- output.lua 文件末尾注释\u0026#34;) -- 关闭打开的文件 io.close(file) 下面来看一些常用的 io 方法:\n¹ io.read() 用来输出文件的一行,它也可以传入参数。\n*n - 读取一个数字并返回它,如 file.read(\u0026#34;*n\u0026#34;)\r*a - 从当前位置读取整个文件,如 file.read(\u0026#34;*a\u0026#34;)\r*| - (默认值)读取下一行,在文件尾(eof)处返回 nil\rnumber - 返回一个指定字符个数的字符串,或在 eof 时返回 nil ² io.tmpfile() 返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除。\n³ io.type(file) 检测 obj 是否一个可用的文件句柄。\n⁴ io.flush() 向文件写入缓冲中的所有数据。\n⁵ io.lines(optional file name) 返回一个迭代函数,每次调用将获得文件中的一行内容,到文件末尾时返回 nil,但不关闭文件。\n完全模式 通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name 来代替 io.function_name 方法。\n1 2 3 4 5 6 7 ifile = io.open(\u0026#34;input.lua\u0026#34;, \u0026#34;r\u0026#34;) print(ifile:read()) ifile:close() ofile = io.open(\u0026#34;output.lua\u0026#34;, \u0026#34;a\u0026#34;) ofile:write(\u0026#34;--test\u0026#34;) ofile:close() 其中,read 的参数与简单模式一致。它还有以下几个常见的方法:\n¹ file:seek(optional whence, optional offset) 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回 nil 加错误信息。其中,whence 值可以是:\n- set 从文件头开始\r- cur 从当前位置开始(默认)\r- end 从文件尾开始\r- offset 默认为 0 不带参数 file:seek() 则返回当前位置, file:seek(\u0026quot;set\u0026quot;) 则定位到文件头, file:seek(\u0026quot;end\u0026quot;) 则定位到文件尾并返回文件大小。\n² file:flush() 向文件写入缓冲中的所有数据。\n³ io.lines(optional file name) 打开指定的文件 filename 为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil ,并自动关闭文件。\n不带参数时 io.lines() \u0026lt;=\u0026gt; io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文件。\n","date":"2025-01-03","permalink":"https://aituyaa.com/lua-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e最近配置 [[Rime_JK 指尖上的旋律|Rime]] 的时候遇到了不少 lua 脚本,加上比较流行的 Neovim 也是使用的这个嵌入式脚本,Java 和一些游戏中也使用了这个脚本语言……\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20250103183548.png|500]]\u003c/p\u003e\n\u003cp\u003e这难免让人想要多了解一下她,不是吗?所以,让我们开始吧 🎉\u003c/p\u003e\n\u003cp\u003e📘 \u003ca href=\"https://www.runoob.com/manual/lua53doc/\"\u003eLua 5.3 参考手册\u003c/a\u003e\u003cbr\u003e\n📗 \u003ca href=\"https://www.runoob.com/lua/lua-tutorial.html\"\u003eLua 教程 | 菜鸟教程\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e这里我们以实用性为中心,简要了解一下其基本用法 ~\u003c/p\u003e\n\u003c/blockquote\u003e","title":"lua 那些事儿"},]
[{"content":"一种获取五笔大词库的方式 ~\n![[assets/pasted image 20241225110558.png]]\n我们在这里对五笔词库的获取做一个阶段性的总结,算是让输入法的折腾再搞一段落。\n简介 该五笔大词库是基于极爽词库和白霜拼音转换而来的,没有具体统计数量,估计有个一两百万条吧 ~ 转换的同时,保留了原始词频,所以并不用担心重码问题。\n快速使用 你可以从这里直接获取到转化好的词库 极爽白霜五笔词库 - gitee 或 极爽白霜五笔词库,然后按需引用它们即可。\nimport_tables:\r- wubi86 # 极爽词库\r# --- ↓白霜词库转换的五笔大词库 ---\r- cn_dicts/8105 # 字表\r# - cn_dicts/41448 # 大字表(按需启用)\r- cn_dicts/base # 基础词库\r- cn_dicts/ext # 扩展词库\r- cn_dicts/tencent # 腾讯词向量(大词库,部署时间较长)\r- cn_dicts/others # 一些杂项\r# 细胞词库\r- cn_dicts_cell/medication\r- cn_dicts_cell/industry_product\r- cn_dicts_cell/exthot\r- cn_dicts_cell/chess\r- cn_dicts_cell/chess2\r- cn_dicts_cell/animal\r- cn_dicts_cell/game\r- cn_dicts_cell/idiom\r- cn_dicts_cell/sport\r- cn_dicts_cell/media\r- cn_dicts_cell/shulihua\r- cn_dicts_cell/food\r- cn_dicts_cell/inputmethod\r- cn_dicts_cell/history\r- cn_dicts_cell/place\r- cn_dicts_cell/geography\r- cn_dicts_cell/name2\r- cn_dicts_cell/literature\r- cn_dicts_cell/music\r- cn_dicts_cell/computer\r- cn_dicts_cell/composite\r- cn_dicts_cell/name 如果感觉还是不够大,怎么办?\n在仓库中我们附上了制作的 python 转换脚本 - py2wb.py,你只需要执行它,就可以自己将现在拼音词库转换五笔词库了。\n# py py2wb.py [拼音词库所在目录] [五笔词库输出目录] [拼音词库目录文件筛选]\rpy py2wb.py src out 8105.dict.yaml 带 [] 中的参数都是可选的,但输入顺序不能乱。其中通过 [拼音词库目录文件筛选] ,你可以实现筛选转换的拼音词库文件,默认为 .dict.yaml 。\n还不够?\n好嘛,折腾过程中使用的一些元码表和脚本也附上 - rime-utils。\n关于形码大词库 相对于拼音来说,形码对于大词库的需求并不算太强烈,尤其是使用拥有自造词和动态词频的输入法平台 - 比如 rime 。\n关于 rime 相关配置,可以参考我的另一篇文章 - 『 [[rime_jk 指尖上的旋律]] 』。\n既然不强烈,为什么又制作了一个五笔的大词库呢?\n只是为了让五笔使用者多一种选择,仅此而已。在保留原始词频的前提下,大词库并不会产生重码率升高的问题,相反它极大减小了“打词打空”的概率。如下图所示:\n![[assets/pasted image 20241225114240.png|150]] ![[assets/pasted image 20241225114207.png|150]] ![[assets/pasted image 20241225115225.png|150]]\n看,效果还不错~\n怎么才能算是足够大?\n实际上这是没有定量的,国标都没个一定,制作五笔的单字元码表字典的时候,我合并了好几个超集词库去重取单,最后转化成拼音词库的时候,还是会有几个异形或者类似部首的那种字不在字典中。需要注意的是,此时单字元码字典已经有小十万字了,理论上它已经包含了 18030 和 8105 的所有单字。但还是会遇到一些漏网之鱼,当然,这些“鱼”并不重要,因为你可能永远都不会有使用它们的场景。比如下面这些字:\n...\r𧮙 oguy 0\r𧲭 eeda 0\r𨀨 khvq 0\r𨐳 ynku 0\r𨝨 rajb 0\r𨞒 tajb 0\r𩛠 wyvf 0\r𪎇 ogut 0\r𪎲 ogus 0\r... 什么时候能能用到?答案是显而易见的 - 不会用到!🥳\n自用词库 我自用的 10万+五笔词库 ,体验相当不错,可能是与我喜欢打短词来保证确定性有关。它是基于 rime 官方、极点五笔及黄狗超级大词库合并、去重、按字长排列的,大小不到 2m ,短小精干且强力。\n它包含且只包含国标通用规范字 8105 中的所有三级字词,其他相关 18030 标准中的繁体字及异形字、cjk 类假名全部清除。因为它们除了增加重码率,对于我来说毫无用处。\n超大词库中,仅包含 8105 中的词条是达 200 多万条,如下:\n![[assets/32a11f6d954f5a53666325093403682e.png|400]]\n构建一次,耗时 5 分钟+ ,如下:\n![[assets/ece35e725c0f7c9cfe56ccb4ee3033d3_720.png|700]]\n把这类词库加入到 rime 配置中,重新部署一次的耗时也是相对很长的,这并不是一件令人愉快的事情。\n与之相反的是,自用的 10 万+ 字库,部署一次只需要两三秒,对于使用者来说简直是无感的。\n结语 所有文本配置性的软件,在扩展性方面都很有一套,比如 emacs,vim,rime 等。折腾配置的过程很有趣,但也很消耗时间和精力 ~ 总之,想要折腾的朋友合理安排时间和精力 ~\n","date":"2024-12-25","permalink":"https://aituyaa.com/%E4%B8%80%E7%A7%8D%E8%8E%B7%E5%8F%96%E4%BA%94%E7%AC%94%E5%A4%A7%E8%AF%8D%E5%BA%93%E7%9A%84%E6%96%B9%E5%BC%8F/","summary":"\u003cp\u003e一种获取五笔大词库的方式 ~\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20241225110558.png]]\u003c/p\u003e","title":"一种获取五笔大词库的方式"},]
[{"content":"![[assets/pasted image 20241215124829.png]]\n官方文档请参阅 📖 python 教程 — python 3.13.1 文档。\n本语言的命名源自 bbc 的 “monty python 飞行马戏团”,与爬行动物无关(python 原义为“蟒蛇”)。\n基础 计算机编程语言和我们日常使用的自然语言有所不同,最大的区别就是,自然语言在不同的语境下有不同的理解,而计算机要根据编程语言执行任务,就必须保证编程语言写出的程序决不能有歧义。所以,任何一种编程语言都有自己的一套语法,编译器或者解释器就是负责把符合语法的程序代码转换成 cpu 能够执行的机器码,然后执行。\n数据类型 \u0026gt; python 中常用的数据类型\n类别 数据类型 描述 示例 基本数据类型 int 整数类型,表示整数值 42, -10 float 浮点数类型,表示带小数点的数值 3.14, -0.001 bool 布尔类型,表示真或假 true, false str 字符串类型,表示文本数据 \u0026quot;hello\u0026quot;, 'python' 复合数据类型 list 列表类型,有序且可变的集合 [1, 2, 3], ['a', 'b', 'c'] tuple 元组类型,有序且不可变的集合 (1, 2, 3), ('a', 'b', 'c') set 集合类型,无序且不重复的集合 {1, 2, 3}, {'a', 'b', 'c'} dict 字典类型,键值对的集合 {'name': 'alice', 'age': 25} 特殊数据类型 nonetype 空值类型,表示没有值 none bytes 字节类型,表示二进制数据 b'hello' bytearray 字节数组类型,可变的二进制数据 bytearray(b'hello') range 范围类型,表示一个不可变的数字序列 range(0, 10) 自定义数据类型 class 类类型,用户自定义的数据结构 class myclass: pass object 对象类型,类的实例 obj = myclass() 其他数据类型 complex 复数类型,表示复数 1 + 2j enum.enum 枚举类型,表示一组命名的常量 class color(enum): red = 1 function 函数类型,表示可调用的代码块 def my_function(): pass 你可能不知道的:\n¹ python允许在数字中间以 _ 分隔,因此,写成 10_000_000_000 和 10000000000 是完全一样的。\n² python还允许用 r'' 表示 '' 内部的字符串默认不转义。\n³ python允许用 '''...''' 的格式表示多行内容。同样,它前面也可以加上 r 和 f 执行非转义和便捷格式化。\n字符串 关于字符编码,可以阅读另一篇文章「 [[字符集和字符编码]] 」。\n在最新的 python 3 版本中,字符串是以 unicode 编码的。对于单个字符的编码,python 提供了 ord() 函数获取字符的整数表示,chr() 函数把编码转换为对应的字符。\n由于 python 的字符串类型是 str,在内存中以unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把 str 变为以字节为单位的 bytes。\npython 对 bytes 类型的数据用带 b 前缀的单引号或双引号表示,如 b'abc' 。\n以 unicode 表示的 str 通过 encode() 方法可以编码为指定的 bytes。\n反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是 bytes。要把 bytes 变为 str,就需要用 decode() 方法。\n\u0026gt;\u0026gt;\u0026gt; \u0026#39;中文\u0026#39;.encode(\u0026#39;utf-8\u0026#39;) b\u0026#39;\\xe4\\xb8\\xad\\xe6\\x96\\x87\u0026#39; \u0026gt;\u0026gt;\u0026gt; b\u0026#39;\\xe4\\xb8\\xad\\xe6\\x96\\x87\u0026#39;.decode(\u0026#39;utf-8\u0026#39;) \u0026#39;中文\u0026#39; len() 函数计算的是 str 的字符数,如果换成 bytes,len() 函数就计算字节数。\n🔔 在操作字符串时,我们经常遇到 str 和 bytes 的互相转换。为了避免乱码问题,应当始终坚持使用 utf-8 编码对 str 和 bytes 进行转换。\nutf-8 编码把一个 unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,只有很生僻的字符才会被编码成 4-6 个字节。\n如何输出格式化的字符串? 和 c 语言是一致的,用 % 实现。\n1 print(\u0026#39;%2d-%02d\u0026#39; % (3, 1)) # 3-01 还有?\n使用字符串的 format() 方法,它会用传入的参数依次替换字符串内的占位符 {0}、{1} ……\n\u0026gt;\u0026gt;\u0026gt; \u0026#39;hello, {0}, 成绩提升了 {1:.1f}%\u0026#39;.format(\u0026#39;小明\u0026#39;, 17.125) \u0026#39;hello, 小明, 成绩提升了 17.1%\u0026#39; 🌟 使用以 f 开头的字符串,称之为 f-string,它和普通字符串不同之处在于,字符串如果包含 {xxx},就会以对应的变量替换。\n\u0026gt;\u0026gt;\u0026gt; r = 2.5 \u0026gt;\u0026gt;\u0026gt; s = 3.14 * r ** 2 \u0026gt;\u0026gt;\u0026gt; print(f\u0026#39;the area of a circle with radius {r} is {s:.2f}\u0026#39;) the area of a circle with radius 2.5 is 19.62 :: 最后一种最 shuhu 👻\nstr 不能直接和整数比较,必须先把 str 转换成整数。python提供了 int() 函数来完成这件事情,如果 int() 函数发现一个字符串并不是合法的数字时就会报错,程序就会退出。\n:: 不像 javascript 那样在比较的时候可以自动转型。\n\u0026gt; str 方法一览\n方法 描述 示例 capitalize() 将字符串的第一个字符大写 \u0026quot;hello\u0026quot;.capitalize() → \u0026quot;hello\u0026quot; casefold() 将字符串转换为小写,支持更多语言(如德语) \u0026quot;hello\u0026quot;.casefold() → \u0026quot;hello\u0026quot; center(width[, fillchar]) 返回居中后的字符串,width 为总宽度,fillchar 为填充字符(默认为空格) \u0026quot;hi\u0026quot;.center(5, '-') → \u0026quot;--hi--\u0026quot; count(sub[, start[, end]]) ⭐️返回子字符串 sub 在字符串中出现的次数,可选参数 start 和 end 指定范围 \u0026quot;hello\u0026quot;.count('l') → 2 encode(encoding='utf-8', errors='strict') 将字符串编码为字节对象 \u0026quot;hello\u0026quot;.encode() → b'hello' endswith(suffix[, start[, end]]) 检查字符串是否以 suffix 结尾,可选参数 start 和 end 指定范围 \u0026quot;hello\u0026quot;.endswith('o') → true expandtabs(tabsize=8) 将字符串中的制表符(\\t)替换为空格,tabsize 指定空格数 \u0026quot;hello\\tworld\u0026quot;.expandtabs(4) → \u0026quot;hello world\u0026quot; find(sub[, start[, end]]) ⭐️返回子字符串 sub 第一次出现的索引,未找到返回 -1 \u0026quot;hello\u0026quot;.find('l') → 2 format(*args, **kwargs) 格式化字符串,替换 {} 中的内容 \u0026quot;{} {}\u0026quot;.format(\u0026quot;hello\u0026quot;, \u0026quot;world\u0026quot;) → \u0026quot;hello world\u0026quot; index(sub[, start[, end]]) ⭐️返回子字符串 sub 第一次出现的索引,未找到抛出 valueerror \u0026quot;hello\u0026quot;.index('l') → 2 isalnum() 检查字符串是否只包含字母和数字 \u0026quot;hello123\u0026quot;.isalnum() → true isalpha() 检查字符串是否只包含字母 \u0026quot;hello\u0026quot;.isalpha() → true isascii() 检查字符串是否只包含 ascii 字符 \u0026quot;hello\u0026quot;.isascii() → true isdecimal() 检查字符串是否只包含十进制数字 \u0026quot;123\u0026quot;.isdecimal() → true isdigit() 检查字符串是否只包含数字 \u0026quot;123\u0026quot;.isdigit() → true isidentifier() 检查字符串是否是有效的 python 标识符 \u0026quot;hello\u0026quot;.isidentifier() → true islower() 检查字符串是否全部为小写 \u0026quot;hello\u0026quot;.islower() → true isnumeric() 检查字符串是否只包含数字字符 \u0026quot;123\u0026quot;.isnumeric() → true isprintable() 检查字符串是否全部为可打印字符 \u0026quot;hello\u0026quot;.isprintable() → true isspace() 检查字符串是否只包含空白字符 \u0026quot; \u0026quot;.isspace() → true istitle() 检查字符串是否每个单词首字母大写 \u0026quot;hello world\u0026quot;.istitle() → true isupper() 检查字符串是否全部为大写 \u0026quot;hello\u0026quot;.isupper() → true join(iterable) ⭐️将可迭代对象中的元素用字符串连接 \u0026quot;,\u0026quot;.join([\u0026quot;a\u0026quot;, \u0026quot;b\u0026quot;, \u0026quot;c\u0026quot;]) → \u0026quot;a,b,c\u0026quot; ljust(width[, fillchar]) 返回左对齐后的字符串,width 为总宽度,fillchar 为填充字符 \u0026quot;hi\u0026quot;.ljust(5, '-') → \u0026quot;hi---\u0026quot; lower() 将字符串转换为小写 \u0026quot;hello\u0026quot;.lower() → \u0026quot;hello\u0026quot; lstrip([chars]) ⭐️去除字符串左侧的空白字符或指定字符 \u0026quot; hello\u0026quot;.lstrip() → \u0026quot;hello\u0026quot; partition(sep) ⭐️将字符串按 sep 分割为三部分(分隔符前、分隔符、分隔符后) \u0026quot;hello world\u0026quot;.partition(' ') → ('hello', ' ', 'world') replace(old, new[, count]) 将字符串中的 old 替换为 new,可选参数 count 指定替换次数 \u0026quot;hello\u0026quot;.replace('l', 'l') → \u0026quot;hello\u0026quot; rfind(sub[, start[, end]]) ⭐️返回子字符串 sub 最后一次出现的索引,未找到返回 -1 \u0026quot;hello\u0026quot;.rfind('l') → 3 rindex(sub[, start[, end]]) ⭐️返回子字符串 sub 最后一次出现的索引,未找到抛出 valueerror \u0026quot;hello\u0026quot;.rindex('l') → 3 rjust(width[, fillchar]) 返回右对齐后的字符串,width 为总宽度,fillchar 为填充字符 \u0026quot;hi\u0026quot;.rjust(5, '-') → \u0026quot;---hi\u0026quot; rpartition(sep) 将字符串按 sep 从右分割为三部分 \u0026quot;hello world\u0026quot;.rpartition(' ') → ('hello', ' ', 'world') rsplit(sep=none, maxsplit=-1) 从右开始分割字符串,sep 为分隔符,maxsplit 为最大分割次数 \u0026quot;a,b,c\u0026quot;.rsplit(',') → ['a', 'b', 'c'] rstrip([chars]) ⭐️去除字符串右侧的空白字符或指定字符 \u0026quot;hello \u0026quot;.rstrip() → \u0026quot;hello\u0026quot; split(sep=none, maxsplit=-1) 分割字符串,sep 为分隔符,maxsplit 为最大分割次数 \u0026quot;a,b,c\u0026quot;.split(',') → ['a', 'b', 'c'] splitlines([keepends]) ⭐️按行分割字符串,keepends 指定是否保留换行符 \u0026quot;hello\\nworld\u0026quot;.splitlines() → ['hello', 'world'] startswith(prefix[, start[, end]]) ⭐️检查字符串是否以 prefix 开头,可选参数 start 和 end 指定范围 \u0026quot;hello\u0026quot;.startswith('he') → true strip([chars]) ⭐️去除字符串两侧的空白字符或指定字符 \u0026quot; hello \u0026quot;.strip() → \u0026quot;hello\u0026quot; swapcase() 将字符串中的大小写互换 \u0026quot;hello\u0026quot;.swapcase() → \u0026quot;hello\u0026quot; title() 将字符串中每个单词的首字母大写 \u0026quot;hello world\u0026quot;.title() → \u0026quot;hello world\u0026quot; translate(table) 根据映射表 table 转换字符串中的字符 \u0026quot;hello\u0026quot;.translate(str.maketrans('el', 'el')) → \u0026quot;hello\u0026quot; upper() 将字符串转换为大写 \u0026quot;hello\u0026quot;.upper() → \u0026quot;hello\u0026quot; zfill(width) 在字符串左侧填充 0,直到字符串长度为 width \u0026quot;42\u0026quot;.zfill(5) → \u0026quot;00042\u0026quot; 列表和元组 list 是一种有序的集合,可以随时添加和删除其中的元素。另一种有序列表叫元组:tuple。tuple 和 list 非常类似,但是 tuple 一旦初始化就不能修改。\n🪧 也就是说,tuple 是个只读状态,什么 append 啦,pop 啦肯定都是不能用的了。只有 1 个元素的 tuple 定义时必须加一个逗号 , ,如 t = (1,) 。\n\u0026gt; 列表 list 方法一览\n方法 描述 示例 append(x) 在列表末尾添加元素 x lst = [1, 2]; lst.append(3) → [1, 2, 3] extend(iterable) 将可迭代对象 iterable 中的所有元素添加到列表末尾 lst = [1, 2]; lst.extend([3, 4]) → [1, 2, 3, 4] insert(i, x) 在索引 i 处插入元素 x lst = [1, 2]; lst.insert(1, 1.5) → [1, 1.5, 2] remove(x) 删除列表中第一个值为 x 的元素,如果不存在则抛出 valueerror lst = [1, 2, 2]; lst.remove(2) → [1, 2] pop([i]) 删除并返回索引 i 处的元素,如果未指定 i,则删除并返回最后一个元素 lst = [1, 2, 3]; lst.pop() → 3,lst → [1, 2] clear() 清空列表中的所有元素 lst = [1, 2]; lst.clear() → [] index(x[, start[, end]]) ⭐️ 返回第一个值为 x 的元素的索引,可选参数 start 和 end 指定搜索范围 lst = [1, 2, 3, 2]; lst.index(2) → 1 count(x) 返回值为 x 的元素在列表中出现的次数 lst = [1, 2, 2, 3]; lst.count(2) → 2 sort(key=none, reverse=false) 对列表进行排序,key 指定排序规则,reverse 控制是否降序 lst = [3, 1, 2]; lst.sort() → [1, 2, 3] reverse() 反转列表中的元素顺序 lst = [1, 2, 3]; lst.reverse() → [3, 2, 1] copy() 返回列表的浅拷贝 lst = [1, 2]; lst_copy = lst.copy() → lst_copy 是 [1, 2] 的副本 分支和循环 python 的循环有两种,一种是 for\u0026hellip;in 循环,依次把 list 或 tuple 中的每个元素迭代出来;第二种循环是 while 循环,只要条件满足,就不断循环,条件不满足时退出循环。\n在循环中,break 语句可以提前退出循环。也可以通过 continue 语句,跳过当前的这次循环,直接开始下一次循环。\n字典和集合 python 内置了字典:dict 的支持,dict 全称 dictionary,在其他语言中也称为 map,使用键-值(key-value)存储,具有极快的查找速度。\n正确使用 dict 非常重要,需要牢记的第一条就是 dict 的 key 必须是不可变对象。\n这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。 这个通过key计算位置的算法称为哈希算法(hash)。 要保证hash的正确性,作为key的对象就不能变。在python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key。 set 和 dict 类似,也是一组 key 的集合,但不存储 value。由于 key 不能重复,所以,在 set 中,没有重复的 key。\n\u0026gt; 字典 dict 的常用方法\n方法 描述 示例 clear() 清空字典中的所有键值对 d = {'a': 1}; d.clear() → {} copy() 返回字典的浅拷贝 d = {'a': 1}; d_copy = d.copy() → d_copy 是 {'a': 1} 的副本 fromkeys(seq[, value]) 创建一个新字典,seq 为键,value 为值(默认为 none) dict.fromkeys(['a', 'b'], 1) → {'a': 1, 'b': 1} get(key[, default]) 返回键 key 对应的值,如果键不存在则返回 default(⭐️ 默认为 none) d = {'a': 1}; d.get('a') → 1,d.get('b', 0) → 0 items() 返回字典中所有键值对的视图((key, value) 对) d = {'a': 1}; d.items() → dict_items([('a', 1)]) keys() 返回字典中所有键的视图 d = {'a': 1}; d.keys() → dict_keys(['a']) values() 返回字典中所有值的视图 d = {'a': 1}; d.values() → dict_values([1]) pop(key[, default]) 删除并返回键 key 对应的值,如果键不存在且未提供 default 则抛出 keyerror d = {'a': 1}; d.pop('a') → 1,d.pop('b', 0) → 0 popitem() 删除并返回字典中的最后一对键值对,如果字典为空则抛出 keyerror d = {'a': 1}; d.popitem() → ('a', 1) setdefault(key[, default]) 如果键 key 存在则返回其值,否则插入 key 并设置值为 default(默认为 none) d = {'a': 1}; d.setdefault('b', 2) → 2,d → {'a': 1, 'b': 2} update([other]) 将字典 other 中的键值对更新到当前字典中 d = {'a': 1}; d.update({'b': 2}) → {'a': 1, 'b': 2} __contains__(key) ⭐️检查字典中是否包含键 key(通常用 in 操作符) d = {'a': 1}; 'a' in d → true 切片、迭代、生成器 在 python 中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。\n❶ 切片\n取一个 str、list 或 tuple 的部分元素是非常常见的操作。对这种经常取指定索引范围的操作,用循环十分繁琐,因此,python 提供了切片(slice)操作符。\n操作 描述 示例 sequence[start:stop] 从 start 到 stop-1 的子序列 \u0026quot;hello\u0026quot;[1:4] → \u0026quot;ell\u0026quot; sequence[start:] 从 start 到序列末尾的子序列 \u0026quot;hello\u0026quot;[2:] → \u0026quot;llo\u0026quot; sequence[:stop] 从序列开头到 stop-1 的子序列 \u0026quot;hello\u0026quot;[:3] → \u0026quot;hel\u0026quot; sequence[::step] 从序列开头到末尾,按 step 步长提取子序列 \u0026quot;hello\u0026quot;[::2] → \u0026quot;hlo\u0026quot; sequence[::-1] 反转序列 \u0026quot;hello\u0026quot;[::-1] → \u0026quot;olleh\u0026quot; sequence[start:stop:step] 从 start 到 stop-1,按 step 步长提取子序列 \u0026quot;hello\u0026quot;[1:5:2] → \u0026quot;el\u0026quot; ❷ 迭代\npython 中只要是可迭代对象,无论有无下标,都可以迭代。它是通过 for ... in 来完成的,而很多语言比如 c 语言,迭代 list 是通过下标完成的。\n默认情况下,dict 迭代的是 key。如果要迭代 value,可以用 for value in d.values(),如果要同时迭代 key 和 value,可以用 for k, v in d.items()。\n如何判断一个对象是可迭代对象呢? 方法是通过 collections.abc 模块的 iterable 类型判断,如:\n1 2 3 4 from collections.abc import iterable isinstance(\u0026#39;abc\u0026#39;, iterable) # → true isinstance(123, iterable) # → false 如果要对 list 实现类似 c 那样的下标循环怎么办?👻\npython 内置的 enumerate 函数可以把一个 list 变成索引-元素对,这样就可以在 for 循环中同时迭代索引和元素本身。\n\u0026gt;\u0026gt;\u0026gt; for i, value in enumerate([\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;]): ... print(i, value) ... 0 a 1 b 2 c ❸ 列表生成式\ni.e.list comprehensions\n列表生成式可以看成是循环生成列表的语法糖 🍬,如:\n1 2 3 4 5 6 7 list = list(range(0, 9)) # [0, 1, 2, 3, 4, 5, 6, 7, 8] l = [x * x for x in list] # [0, 1, 4, 9, 16, 25, 36, 49, 64] # 等同于 l = [] for x in list: l.append(x * x) 如果不想每个元素都转换呢?如何用条件过滤?\n1 2 3 4 5 6 7 8 9 l = [x * x for x in list if x % 2 == 0] # [0, 4, 16, 36, 64] # 等同于 for x in list: if x % 2 == 0: l.append(x * x) l = [x * x if x % 2 for x in list] # ❌❓ l = [x * x if x % 2 else 0 for x in list] # ✔️ 把 if 写在 for 前面必须加 else,否则报错。 为什么呢?\n这是因为 for 前面的部分是一个表达式,它必须根据 x 计算出一个结果。因此,考察表达式:x if x % 2 == 0,它无法根据 x 计算出结果,因为缺少 else,必须加上 else 。\n❹ 生成器\n通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。那么,可不可以在循环的过程中不断推算出后续的元素呢? 这样就不必创建完整的 list,从而节省大量的空间。\n一边循环一边计算的机制,称为生成器(generator) 。那么,如何创建一个 generator 呢?\n¹ 第一种方法很简单,只要把一个列表生成式的 [] 改成 (),就创建了一个 generator。如下:\n\u0026gt;\u0026gt;\u0026gt; g = (x * x for x in range(10)) \u0026gt;\u0026gt;\u0026gt; g \u0026lt;generator object \u0026lt;genexpr\u0026gt; at 0x1022ef630\u0026gt; 可以通过 next() 函数获得 generator 的下一个返回值,但正确的方法是使用 for 循环。\n² 如果一个函数定义中包含 yield 关键字,那么这个函数就不再是一个普通函数,而是一个 generator 函数,调用一个 generator 函数将返回一个 generator 。\n想要了解更多关于生成器的内容,可以阅读另一篇 「 [[生成器]] 」原理都是相通的。\n❺ 迭代器\n可以被 next() 函数调用并不断返回下一个值的对象称为迭代器:iterator。\n🪧 生成器都是 iterator 对象,但 list、dict、str 虽然是 iterable,却不是 iterator。\n为什么呢?\n这是因为 python 的 iterator 对象表示的是一个数据流,可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next() 函数实现按需计算下一个数据,所以 iterator 的计算是惰性的,只有在需要返回下一个数据时它才会计算。\n当然,如果要把 list、dict、str 等 iterable 变成 iterator 可以使用 iter() 函数,如:\n\u0026gt;\u0026gt;\u0026gt; isinstance(iter([]), iterator) true \u0026gt;\u0026gt;\u0026gt; isinstance(iter(\u0026#39;abc\u0026#39;), iterator) true 函数 在上个章节中,我们提到了定义中包含 yield 关键字的函数是一个 generator 函数,那么具体什么是函数呢?\n函数就是最基本的一种代码抽象的方式。看看它是怎么定义的,如下:\ndef 函数名(参数): 函数体 return 返回值 # 若没有 return 语句,默认返回 none 如果想定义一个什么事也不做的空函数,可以用 pass 语句:\n1 2 def nop(): pass 调用函数时,如果参数个数不对,python 解释器会自动检查出来,并抛出 typeerror。但是如果参数类型不对,python 解释器就无法帮我们检查。\n数据类型检查可以用内置函数 isinstance() 实现。\n1 2 3 4 5 6 7 def my_abs(x): if not isinstance(x, (int, float)): # 类型检查 raise typeerror(\u0026#39;bad operand type\u0026#39;) if x \u0026gt;= 0: return x else: return -x 函数可以返回多个值吗? 答案是肯定的。但真的是多个值吗?但其实这只是一种假象,python 函数返回的仍然是单一值(元组罢了👻)。\n函数式编程 函数式编程 就是一种抽象程度很高的编程范式。\n纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。\n函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!\npython 对函数式编程提供部分支持。由于 python 允许使用变量,因此,python 不是纯函数式编程语言。\n高阶函数 一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数。\n编写高阶函数,就是让函数的参数能够接收别的函数。下面我们就看几个经典的高阶函数。\n❶ map\nmap() 函数接收两个参数,一个是函数,一个是 iterable,map 将传入的函数依次作用到序列的每个元素,并把结果作为新的 iterator 返回。\n\u0026gt;\u0026gt;\u0026gt; def f(x): ... return x * x ... \u0026gt;\u0026gt;\u0026gt; r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) # r 是一个迭代器 \u0026gt;\u0026gt;\u0026gt; list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81] map() 传入的第一个参数是 f,即函数对象本身。\n由于结果 r 是一个 iterator,iterator 是惰性序列,因此通过 list() 函数让它把整个序列都计算出来并返回一个 list。\n你可能会想,不需要 map() 函数,写一个循环,也可以计算出结果,如下:\n1 2 3 4 l = [] for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]: l.append(f(n)) print(l) 看,事实上 map() 只是把运算规则抽象了。让你一眼就能看明白 “是把 f(x) 作用在 list 的每一个元素并把结果生成一个新的 list ” 。\n❷ reduce\nreduce 把一个函数作用在一个序列 [x1, x2, x3, ...] 上,这个函数必须接收两个参数,reduce 把结果继续和序列的下一个元素做累积计算,其效果就是:\n1 reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) ❸ filter\n和 map() 类似,filter() 也接收一个函数和一个序列。和 map() 不同的是,filter() 把传入的函数依次作用于每个元素,然后根据返回值是 true 还是 false 决定保留还是丢弃该元素。\n可见用 filter() 这个高阶函数,关键在于正确实现一个“筛选”函数。\n注意到 filter() 函数返回的是一个 iterator,也就是一个惰性序列,所以要强迫 filter() 完成计算结果,也需要用 list() 函数获得所有结果并返回 list。\n❹ sorted\n排序是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个 dict 呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。\npython 内置的 sorted() 函数就可以对 list 进行排序:\n\u0026gt;\u0026gt;\u0026gt; sorted([36, 5, -12, 9, -21]) [-21, -12, 5, 9, 36] sorted() 函数也是一个高阶函数,它还可以接收一个 key 函数来实现自定义的排序,例如按绝对值大小排序:\n\u0026gt;\u0026gt;\u0026gt; sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36] key 指定的函数将作用于 list 的每一个元素上,并根据 key 函数返回的结果进行排序。\n返回函数 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。\n来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的,如下:\n1 2 3 4 5 6 7 def calc_sum(*args): ax = 0 for n in args: ax = ax + n return ax calc_sum(1,2,3,4,5) # → 15 但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?\n可以不返回求和的结果,而是返回求和的函数。\n1 2 3 4 5 6 7 8 9 10 def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum f = lazy_sum(1,2,3,4,5) # → \u0026lt;function lazy_sum.\u0026lt;locals\u0026gt;.sum at 0x101c6ed90\u0026gt; f() # → 15 在这个例子中,我们在函数 lazy_sum 中又定义了函数 sum,并且,内部函数 sum 可以引用外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种称为 “闭包(closure)” 的程序结构拥有极大的威力。\n注意,当我们调用 lazy_sum() 时,每次调用都会返回一个新的函数,即使传入相同的参数。\n🔔 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。\n1 2 3 4 5 6 7 8 9 10 11 12 13 def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count() f1() # → 9 f2() # → 9 f3() # → 9 什么情况?全部都是 9!原因就在于返回的函数引用了变量 i,但它并非立刻执行。 等到3个函数都返回时,它们所引用的变量 i 已经变成了 3,因此最终结果为 9。\n如果一定要引用循环变量怎么办?\n方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。\n1 2 3 4 5 6 7 8 9 10 11 12 13 def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f() return fs f1() # → 1 f2() # → 4 f3() # → 9 💡 使用闭包时,对外层变量赋值前,需要先使用 nonlocal 声明该变量不是当前函数的局部变量。\n使用闭包,就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值,我们会发现返回的闭包函数调用一切正常。\n1 2 3 4 5 6 7 8 9 10 def inc(): x = 0 def fn(): # 仅读取x的值: return x + 1 return fn f = inc() print(f()) # 1 print(f()) # 1 但是,如果对外层变量赋值,由于python解释器会把 x 当作函数 fn() 的局部变量,它会报错。\n1 2 3 4 5 6 7 8 9 10 11 def inc(): x = 0 def fn(): # nonlocal x x = x + 1 return x return fn f = inc() print(f()) # 1 print(f()) # 2 原因是 x 作为局部变量并没有初始化,直接计算 x+1 是不行的。但我们其实是想引用 inc() 函数内部的 x,所以需要在 fn() 函数内部加一个 nonlocal x 的声明。加上这个声明后,解释器把 fn() 的 x 看作外层函数的局部变量,它已经被初始化了,可以正确计算 x+1。\n匿名函数 lambda x: x * x 就是一个匿名函数,关键字 lambda 表示匿名函数,冒号前面的 x 表示函数参数。\n它没有函数名,所以不用担心命名冲突。只有一个表达式,不用 return ,返回值就是表达式的结果。\n装饰器 本质上,装饰器(decorator) 就是一个返回函数的高阶函数。\n:: 其实就是代理。\n假设,我们有一个 now 函数,如下:\ndef now(): print(\u0026#39;2025-01-02\u0026#39;) now() # → 2025-01-02 now.__name__ # → \u0026#39;now\u0026#39; 现在我们要增强 now() 的功能,如在函数调用前后打印日志,但又不希望修改 now() 函数的定义。怎么办呢?\n通过装饰器!这种在代码运行期间动态增加功能的方式,称之为“装饰器”(decorator)。\n本质上,装饰器(decorator) 就是一个返回函数的高阶函数。 我们先来定义一个打印日志的装饰器,如下:\n1 2 3 4 5 def log(func): def wrapper(*args, **kw): print(\u0026#39;call %s():\u0026#39; % func.__name__) return func(*args, **kw) return wrapper 现在我们有一个装饰器 - log() 了,如何使用它呢?借助 python 的@语法,把 decorator 置于函数的定义处即可。\n1 2 3 @log def now(): print(\u0026#39;2024-6-1\u0026#39;) 现在,我们再来调用一下 now() 函数,在调用函数之前就会输出调用的函数名这条日志了。\n\u0026gt;\u0026gt;\u0026gt; now() call now(): 2024-6-1 \u0026gt;\u0026gt;\u0026gt; now.__name__ wrapper # ❓ 变成 wrapper 了 把 @log 放到 now() 函数的定义处,相当于执行了语句:now = log(now) 。\n看,现在调用的 now 不再是原来的 now 了,而是 log 函数返回的同名函数。不信你看,函数的 __name__ 属性变成了 wrapper 。\n所以,需要把原始函数的 __name__ 等属性复制到 wrapper() 函数中,否则,有些依赖函数签名的代码执行就会出错。\n不需要编写 wrapper.__name__ = func.__name__ 这样的代码,python 内置的 functools.wraps 就是干这个事的,所以,一个完整的 decorator 的写法如下:\n1 2 3 4 5 6 7 8 import functools def log(func): @functools.wraps(func) def wrapper(*args, **kw): print(\u0026#39;call %s():\u0026#39; % func.__name__) return func(*args, **kw) return wrapper 如果,装饰器本身需要传入参数呢? 那就需要 编写一个返回 decorator 的高阶函数。 比如,要自定义 log 的文本:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import functools # 定义 log def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, (**kw)): print(\u0026#39;%s %s():\u0026#39; % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator # 使用 log @log(\u0026#39;执行函数\u0026#39;) def now(): print(\u0026#39;2025-01-02) 现在,我们来调用一个 now() ,如下:\n\u0026gt;\u0026gt;\u0026gt; now() 执行函数 now(): 2025-01-02 偏函数 所谓偏函数,就是把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。\nfunctools.partial 就是做这个的。\n1 2 3 4 import functools int2 = functools.partial(int, base=2) int2(\u0026#39;1000000\u0026#39;) # → 64 它就是就相当于替我们实现了下面这样一个函数:\n1 2 def int2(x, base=2): return int(x, base) 仅此而已。\n模块 在 python 中,一个 .py 文件就称之为一个模块(module)。\n使用模块,大大提高了代码的可维护性,还可以函数名和变量名冲突。为了避免模块名冲突,python 又引入了按目录来组织模块的方法,称为包(package)。\n引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。\nmycompany ├─ __init__.py ├─ abc.py └─ xyz.py 每一个包目录下面都会有一个 __init__.py 的文件,这个文件是必须存在的,否则,python就把这个目录当成普通目录,而不是一个包。\n__init__.py 可以是空文件,也可以有 python 代码,因为 __init__.py 本身就是一个模块,而它的模块名就是 mycompany。\nabc.py 模块的名字就变成了 mycompany.abc,类似的,xyz.py 的模块名变成了 mycompany.xyz。\n类似的,可以有多级目录,组成多级层次的包结构。\n常用内建模块 venv 默认情况下,所有第三方的包都会被 pip 安装到python3的 site-packages 目录下。\n如果要同时开发多个应用程序,应用 a 需要 jinja 2.7,而应用 b 需要 jinja 2.6 怎么办?\n这种情况下,每个应用可能需要各自拥有一套“独立”的 python 运行环境。venv 就是用来为一个应用创建一套“隔离”的 python 运行环境。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # ① 创建项目目录 jack@jk:/d/wp$ mkdir pyvenv jack@jk:/d/wp$ cd pyvenv/ # ② 创建一个独立的 python 运行环境 jack@jk:/d/wp/pyvenv$ python3 -m venv . jack@jk:/d/wp/pyvenv$ ls include lib scripts pyvenv.cfg # ③ 激活 venv 环境 jack@jk:/d/wp/pyvenv$ source scripts/activate # ④ 激活后命令提示符多了 (pyvenv) 前缀 # ..下面可正常安装各种第三方包 # ..安装的包都被安装到当前 pyvenv 这个环境下 # ../d/wp/pyvenv/lib/site-packages 目录中 (pyvenv) jack@jk:/d/wp/pyvenv$ pip3 install jinja2 collecting jinja2 ... successfully installed markupsafe-3.0.2 jinja2-3.1.5 (pyvenv) jack@jk:/d/wp/pyvenv$ python python 3.13.1 (tags/v3.13.1:0671451, dec 3 2024, 19:06:28) [msc v.1942 64 bit (amd64)] on win32 type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; import jinja2 \u0026gt;\u0026gt;\u0026gt; exit() # ⑤ 退出当前的 pyvenv 环境 (pyvenv) jack@jk:/d/wp/pyvenv$ cd scripts/ (pyvenv) jack@jk:/d/wp/pyvenv/scripts$ deactivate :: 跟 node_modules 是一样一样的。\n附录 python 项目组织结构 python 项目的组织结构有很多种方式,但有一些经典且广泛使用的组织结构,可以帮助你创建易于维护、扩展和协作的项目。以下是一些经典的 python 项目结构示例,适用于不同类型的项目。\n❶ 最简单的 python 脚本项目\n最简单的项目结构,适合只有一个或几个脚本的小项目。\n/my_project /script.py /another_script.py ❷ 典型的 python 包结构\n适用于将项目组织为一个 python 包,并且可能会包含多个模块和脚本。\n/my_project /my_project # 主包目录 __init__.py # 包的初始化文件 module1.py # 模块 1 module2.py # 模块 2 /tests # 测试目录 test_module1.py # 测试模块 1 test_module2.py # 测试模块 2 /setup.py # 安装脚本 /readme.md # 项目文档 /requirements.txt # 依赖库 ❸ python 应用程序项目结构\n适用于较大的 python 应用程序,包含多个模块、脚本、配置文件、日志文件等。它的组织结构支持大规模开发。\n/my_project /my_project # 主包目录 __init__.py /core # 核心模块 __init__.py main.py # 主程序入口 /models # 数据模型 __init__.py model1.py /services # 服务层,例如 api 调用 __init__.py service1.py /utils # 工具函数 __init__.py utils.py /tests # 测试目录 __init__.py test_core.py # 核心模块的测试 test_models.py # 模型模块的测试 /config # 配置文件 config.yaml # 配置文件 logging.conf # 日志配置 /logs # 日志目录 /setup.py # 安装脚本 /readme.md # 项目文档 /requirements.txt # 依赖库 /dockerfile # 如果需要容器化 /makefile # 可选,自动化构建任务 ❹ cli 工具项目结构\n如果你要开发一个命令行工具(cli),这通常包括命令的定义、解析和执行。\n/my_project /my_project # 主包目录 __init__.py cli.py # 定义命令行工具 commands.py # 定义命令 /tests # 测试文件 test_cli.py /setup.py # 安装脚本 /requirements.txt # 依赖库 /readme.md # 项目文档 还有一些经典的框架项目组织结构,如 django、flask 等等……\n以下是项目组织结构中常见的文件和目录:\nreadme.md:项目文档,描述项目的目的、使用方法和任何其他相关信息。 requirements.txt:列出所有 python 包依赖,通常用于 pip install -r requirements.txt 来安装依赖。 setup.py:安装脚本,通常用于将包发布到 pypi 或安装到虚拟环境中。 dockerfile:容器化配置,用于 docker 部署。 tests/:测试文件目录,包含单元测试、集成测试等。 config/:配置文件,通常包括数据库配置、日志配置等。 对于小型项目,你可以使用简单的脚本结构或者单一模块结构。对于中型到大型项目,通常建议按照功能模块进行划分,并且将配置文件、日志文件、文档等独立成不同的目录。\n❺ 项目脚手架\npython 生态系统中有一些优秀的项目脚手架工具和框架,它们帮助开发者快速创建结构化、可扩展的项目,提升开发效率。这些工具通常为项目提供了清晰的目录结构、常用的功能模块和配置文件等。\n¹ cookiecutter\ncookiecutter 是一个非常流行的项目模板生成器,支持从模板快速创建新的 python 项目结构。它的主要优点是模板可自定义,开发者可以根据需要创建模板,或者使用社区中已有的模板(如 django、flask、data science 项目等)。\n² poetry 🌟 〔 推荐 〕\npoetry 是一个现代化的 python 包管理和构建工具,支持依赖管理、包发布和项目脚手架生成。poetry 具有类似于 node.js 的 npm 的功能,但它更适合 python 开发。\n我们将在另一篇文章 - [[ poetry 的简要说明 ]] 中系统扼要的说明一下。\n特点:\n自动化依赖管理:poetry 自动处理依赖关系,避免手动编辑 requirements.txt。 包发布:poetry 可以方便地将包发布到 pypi。 集成虚拟环境:poetry 自带虚拟环境的管理功能。 # 安装 pip install poetry # 创建项目 poetry new my_project # 管理依赖 poetry add requests 其他还有一些脚手架,这里就不作过多介绍了,感兴趣的可以自行了解。\ncookiecutter:最为通用的 python 项目脚手架工具,支持多种类型的项目结构。 poetry:除了包管理功能外,还支持项目脚手架生成,非常适合现代 python 项目。 pyscaffold:提供一个标准化的 python 项目结构,适合各种应用和库开发。 flask-skeleton 和 django cookiecutter:专为 flask 和 django 项目提供的脚手架。 fastapi starter template:专为 fastapi 项目提供的高效开发模板。 根据你的需求(例如 web 开发、数据科学、api 开发等),你可以选择适合的脚手架工具,快速启动项目。如果你有特定的框架或需求,可以让我进一步帮助你选择。\npython 中获取时间和格式化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from datetime import datetime # 获取当前日期 current_date = datetime.now().date() # 格式化日期 formatted_date = current_date.strftime(\u0026#34;%y年%m月%d日\u0026#34;) print(\u0026#34;格式化后的日期:\u0026#34;, formatted_date) # 其他格式 formatted_date_2 = current_date.strftime(\u0026#34;%a, %d %b %y\u0026#34;) print(\u0026#34;另一种格式:\u0026#34;, formatted_date_2) # » 格式化后的日期: 2023年10月05日 # » 另一种格式: thursday, 05 october 2023 pyenv 版本管理 首先,通过 powershell 安装 pyenv-win ,如下:\ninvoke-webrequest -usebasicparsing -uri \u0026#34;https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1\u0026#34; -outfile \u0026#34;./install-pyenv-win.ps1\u0026#34;; \u0026amp;\u0026#34;./install-pyenv-win.ps1\u0026#34; 然后,重新打开终端,运行 pyenv --version 检查是否安装成功。注意,你总是可能通过 pyenv help 了解更多。\n更多详情参考 ➭ pyenv-win installation 。\n下面来安装一个版本的 python 吧。\n# 列出可安装的 python 版本 pyenv install -l # 安装选定的可用版本 pyenv install \u0026lt;version\u0026gt; # 将安装的版本设置为全局可用 pyenv global \u0026lt;version\u0026gt; # 显示当前使用的版本 pyenv version # → \u0026lt;version\u0026gt; (set by \\path\\to\\.pyenv\\pyenv-win\\.python-version) # 检查 python 是否正常执行 python -c \u0026#34;import sys; print(sys.executable)\u0026#34; # → \\path\\to\\.pyenv\\pyenv-win\\versions\\\u0026lt;version\u0026gt;\\python.exe pypi 换源 pypi | 镜像站使用帮助 | 清华大学开源软件镜像站 | tsinghua open source mirror\n在使用 python 的 pip 安装包时,默认的包下载源是国外的 pypi(python package index)服务器,可能会因为网络问题导致下载速度慢或失败。为了提高下载速度,可以将 pip 的镜像源设置为国内的镜像站点。\n以下是设置 pip 镜像的几种方法:\n方法 1:临时使用镜像源\n如果你只想临时使用某个镜像源,可以在使用 pip 命令时添加 -i 参数指定镜像源。\n1 2 # 用法 pip install \u0026lt;package_name\u0026gt; -i https://pypi.tuna.tsinghua.edu.cn/simple pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple 方法 2:永久设置镜像源\n如果你希望永久使用某个镜像源,可以通过修改 pip 的配置文件来实现。\n2.1 修改全局配置文件\n在终端中运行以下命令,将镜像源写入 pip 的全局配置文件:\n1 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 2.2 手动编辑配置文件\n找到 pip 的配置文件:\nwindows:c:\\users\\\u0026lt;你的用户名\u0026gt;\\pip\\pip.ini macos/linux:~/.pip/pip.conf 如果没有配置文件,可以手动创建。\n在配置文件中添加以下内容:\n1 2 [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple 方法 3:使用环境变量\n你可以通过设置环境变量来指定 pip 的镜像源。在命令提示符中运行以下命令:\n1 2 3 4 5 # windows set pip_index_url=https://pypi.tuna.tsinghua.edu.cn/simple # macos/linux export pip_index_url=https://pypi.tuna.tsinghua.edu.cn/simple 常用的国内镜像源\n以下是一些常用的国内镜像源,你可以根据自己的需求选择:\n镜像源名称 url 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple 阿里云 https://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 华为云 https://repo.huaweicloud.com/repository/pypi/simple 总结\n临时使用:使用 -i 参数。 永久使用:修改 pip 配置文件或设置环境变量。 推荐镜像源:清华大学、阿里云、中国科技大学等。 通过设置镜像源,可以显著提高 pip 的下载速度,避免因网络问题导致的安装失败。\n切片 可以这样理解切片,索引指向的是字符之间,第一个字符的左侧标为 0,最后一个字符的右侧标为 n ,n 是字符串长度。例如:\n+---+---+---+---+---+---+ | p | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 斐波那契数列 \u0026gt; 以斐波那契数为边的正方形拼成的近似的黄金矩形 (1:1.618)\n![[assets/pasted image 20241223155202.png|325]]\n在数学上,斐波那契数是以递归的方法来定义:\n![[assets/pasted image 20241223155423.png|200]]\n用白话文来说,就是斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。首几个斐波那契数是:\n1、 1、 2、 3、 5、 8、 13、 21、 34、 55、 89、 144、 233、 377、 610、 987…… 特别指出:0 不是第一项,而是第零项( ![[assets/pasted image 20241223155936.png]])。\n","date":"2024-12-15","permalink":"https://aituyaa.com/python-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e![[assets/Pasted image 20241215124829.png]]\u003c/p\u003e\n\u003cp\u003e官方文档请参阅 📖 \u003ca href=\"https://docs.python.org/zh-cn/3/tutorial/index.html\"\u003ePython 教程 — Python 3.13.1 文档\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e本语言的命名源自 BBC 的 “Monty Python 飞行马戏团”,与爬行动物无关(Python 原义为“蟒蛇”)。\u003c/p\u003e","title":"python 那些事儿"},]
[{"content":"call、apply 和 bind 是 javascript 中用于控制函数执行上下文(即 this 的值)的三个重要方法。它们的主要作用是允许你显式地指定函数内部的 this 指向。\n基础 call call 方法用于立即调用函数,并指定函数内部的 this 值。它还可以传递参数给函数。\n语法:\n1 function.call(thisarg, arg1, arg2, ...) thisarg:指定函数执行时的 this 值。 arg1, arg2, ...:传递给函数的参数。 示例:\n1 2 3 4 5 6 7 8 9 10 11 12 const person = { name: \u0026#34;alice\u0026#34;, greet: function(greeting) { console.log(`${greeting}, ${this.name}!`); } }; const anotherperson = { name: \u0026#34;bob\u0026#34; }; person.greet.call(anotherperson, \u0026#34;hello\u0026#34;); // 输出: hello, bob! 在这个例子中,call 将 greet 函数的 this 指向了 anotherperson,因此 this.name 变成了 \u0026quot;bob\u0026quot;。\napply apply 方法与 call 类似,也是用于立即调用函数,并指定函数内部的 this 值。不同的是,apply 传递参数时使用数组形式。\n语法:\n1 function.apply(thisarg, [argsarray]) thisarg:指定函数执行时的 this 值。 argsarray:传递给函数的参数数组。 示例:\n1 2 3 4 5 6 7 8 9 10 11 12 const person = { name: \u0026#34;alice\u0026#34;, greet: function(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } }; const anotherperson = { name: \u0026#34;bob\u0026#34; }; person.greet.apply(anotherperson, [\u0026#34;hello\u0026#34;, \u0026#34;!\u0026#34;]); // 输出: hello, bob! 在这个例子中,apply 将 greet 函数的 this 指向了 anotherperson,并通过数组传递了参数。\nbind bind 方法不会立即调用函数,而是返回一个新函数,并将 this 绑定到指定的对象。你可以稍后调用这个新函数。\n语法:\n1 const newfunction = function.bind(thisarg, arg1, arg2, ...) thisarg:指定函数执行时的 this 值。 arg1, arg2, ...:传递给函数的参数(可选)。 示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 const person = { name: \u0026#34;alice\u0026#34;, greet: function(greeting) { console.log(`${greeting}, ${this.name}!`); } }; const anotherperson = { name: \u0026#34;bob\u0026#34; }; const greetbob = person.greet.bind(anotherperson, \u0026#34;hello\u0026#34;); greetbob(); // 输出: hello, bob! 在这个例子中,bind 返回了一个新函数 greetbob,它的 this 被绑定到 anotherperson,并且预设了参数 \u0026quot;hello\u0026quot;。\n总结对比 方法 是否立即执行 参数传递方式 返回值 call 是 逐个传递(逗号分隔) 无 apply 是 数组形式 无 bind 否 逐个传递(可选) 返回一个新函数 使用场景\ncall 和 apply:\n当你需要立即调用一个函数,并指定 this 时。 例如:借用方法(从一个对象借用方法到另一个对象)。 bind:\n当你需要创建一个新函数,并绑定 this 时。 例如:事件处理函数、回调函数。 示例:借用方法\n1 2 3 const array = [1, 2, 3]; const max = math.max.apply(null, array); // 使用 apply 借用 math.max console.log(max); // 输出: 3 在这个例子中,math.max 方法通常接受多个参数,但通过 apply,我们可以将数组作为参数传递。\n注意事项\nthisarg 为 null 或 undefined:\n如果 thisarg 为 null 或 undefined,this 将指向全局对象(在浏览器中是 window)。 bind 的永久绑定:\n一旦使用 bind 绑定 this,后续无法通过 call 或 apply 改变 this 的值。 通过掌握 call、apply 和 bind,你可以更灵活地控制函数的行为,尤其是在处理对象方法和回调函数时非常有用。\n附录 如何理解“bind 的永久绑定”? ❓问:如何理解“一旦使用 bind 绑定 this,后续无法通过 call 或 apply 改变 this 的值” ?\n❗答: bind 的实现原理是创建一个新的函数,并将 this 硬编码到这个新函数中。因此,无论你如何调用这个新函数,它的 this 值都不会改变。\n下面让我们看一下其 「底层原理 」:\n当你使用 bind 时,javascript 引擎会生成一个新的函数,类似于以下伪代码:\n1 2 3 function boundfunction() { return originalfunction.call(boundthis, ...arguments); } 其中:\nboundthis 是 bind 绑定的对象。 originalfunction 是原始函数。 在这个新函数中,this 已经被硬编码为 boundthis,因此无法通过 call 或 apply 改变。\n❓问:那可不可以再次使用 bind 改变 this 的指向呢?\n❗答:不可以! bind 的绑定是不可变的:即使你多次使用 bind,也不会覆盖之前的绑定。\n这种特性使得 bind 非常适合用于创建回调函数或事件处理函数,因为你可以确保 this 的值在任何情况下都不会改变。\n","date":"2024-12-13","permalink":"https://aituyaa.com/callapply-%E5%92%8C-bind/","summary":"\u003cp\u003e\u003ccode\u003ecall\u003c/code\u003e、\u003ccode\u003eapply\u003c/code\u003e 和 \u003ccode\u003ebind\u003c/code\u003e 是 JavaScript 中用于控制函数执行上下文(即 \u003ccode\u003ethis\u003c/code\u003e 的值)的三个重要方法。它们的主要作用是允许你显式地指定函数内部的 \u003ccode\u003ethis\u003c/code\u003e 指向。\u003c/p\u003e","title":"call、apply 和 bind"},]
[{"content":"➡️ 本文内容主体部分来自 浏览器的渲染过程 - 知乎\n通常,我们只需要编写 html,css,javascript,浏览器上就能呈现出漂亮的网页了,但是浏览器是如何使用我们的代码在屏幕上渲染像素的呢?\n请先看一张大图 浏览器将 html,css,javascript 代码转换成屏幕上所能呈现的实际像素,这期间所经历的一系列步骤,叫做关键渲染路径(critical rendering path)。其中包含:\n构建 对象模型(dom,cssom) 构建渲染树(rendertree) 布局 渲染 在构建对象模型到构建 渲染树的这一过程,还穿插着 js 脚本的加载和执行。如下图所示:\n![[assets/pasted image 20241213170156.png]]\ndomtree 的构建 浏览器的渲染从解析 html 文档开始,宏观上,可以分为下面几个步骤:\n第一步(解析):从网络或者磁盘下读取的 html 原始 字节码,通过设置的 charset 编码,转换成相应字符。\n![[assets/pasted image 20241213170126.png]]\n第二步(token 化):通过 词法分析器,将字符串解析成 token,token 中会标注出当前的 token 是 开始标签,还是 结束标签,或者 文本标签 等。\n第三步(生成 nodes 并构建 dom 树):浏览器会根据 tokens 里记录的 开始标签,结束标签,将 tokens 之间相互串联起来(带有结束标签的 token 不会生成 node)。\nnode 包含了这个节点的所有属性。例如 \u0026lt;img src=\u0026quot;xxx.png\u0026quot; \u0026gt; 标签最终生成出的节点对象中会保存图片地址等信息。\n事实上,在构建 dom 树时,不是要等所有的 tokens 都转换成 nodes 后才开始,而是一边生成 token 一边采取 深度遍历算法 消耗 token 来生成 node,如下图所示:\n图中有颜色的小数字代表构建的具体步骤,可以看出,首先生成出 html token,并消耗 token 创建出 html 节点对象,接着生成 head token 并消耗 token 创建出 head节点对象\u0026hellip;\u0026hellip;,当所有的 tokens 都消耗完了,紧接着 dom 树也就构建完了。\n![[assets/pasted image 20241213170055.png]]\n这里抛出个小问题,为什么有时在 js 中访问 dom 时浏览器会报错呢?\n因为在上述的解析的过程中,如果碰到了 script 或者 link 标签,就会根据 src 对应的地址去加载资源,在 script 标签没有设置 async/defer 属性时,这个加载过程是 下载并执行完全部的代码,此时,dom 树还没有完全创建完毕,这个时候如果 js 企图访问 script 标签后面的 dom 元素,浏览器就会抛出找不到该 dom 元素的错误。\n值得注意的是:从 bytes 到 tokens 的这个过程,浏览器都可以交给其他单独的线程去处理,不会堵塞浏览器的 渲染线程。但是后面的部分就都在渲染线程下进行了,也就是我们常说的 js 单线程环境。\ncssomtree 的构建 dom 会记录页面的内容,但是浏览器还需要知道这些内容该用什么样式去展示,所以还需要构建 cssomtree。cssom 的生成过程和 dom 的生成过程十分相似,也是:1.解析,2. token 化,3.生成 nodes 并构建 cssomtree:\n假设浏览器收到了下面这样一段 css:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 body { font-size: 16px; } p { font-weight: bold; } p span { display: none; } span { color: red; } img { float: right; } 最终会生成如下的 cssomtree:\n从图中可以看出,最开始 body 有一个样式规则是 font-size:16px,之后,在 body 这个样式基础上每个 子节点还会添加自己单独的样式规则,比如 span 又添加了一个样式规则 color:red。正是因为样式这种类似于继承的特性,浏览器设定了一条规则:cssomtree 需要等到完全构建后才可以被使用,因为后面的属性可能会覆盖掉前面的设置。比如在上面的 css 代码基础上再添加一行代码 p {font-size:12px},那么之前设置的 16px 将会被覆盖成 12px。\n下面是官方给的一种解释:\n未构建完的 cssomtree 是不准确的,浏览器必须等到 cssomtree 构建完毕后才能进入下一阶段。\n所以,css 的加载速度与构建 cssomtree 的速度将直接影响首屏渲染速度,因此在默认情况下 css 被视为阻塞渲染的资源,需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。\n那么回到上面生成 dom 时提到的 js 问题:在标签没有设置 async/defer 属性时,js 会阻塞 dom 的生成。原因是 js 会改变 domtree 的内容,如果不阻塞,会出现一边生成 dom 内容,一边修改 dom 内容的情况,无法确保最终生成的 domtree 是确定唯一的。\n同理,js 也会可以修改 css 样式,影响 cssomtree 最终的结果。而我们前面提到,不完整的 cssomtree 是不可以被使用的,如果 js 试图在浏览器还未完成 cssomtree 的下载和构建时去操作 css 样式,浏览器会暂停脚本的运行和 dom 的构建,直至浏览器完成了 cssom 的下载和构建。也就是说,js 脚本的出现会让 cssom 的构建阻塞 dom 的构建。\n平时谈及页面性能优化,经常会强调 css 文件应该放在 html 文档中的前面引入,js 文件应该放在后面引入,这么做的原因是什么呢?\n举个例子:本来,dom 构建和 cssom 构建是两个过程,井水不犯河水。假设 dom 构建完成需要 1s,cssom 构建也需要 1s,在 dom 构建了 0.2s 时发现了一个 link 标签,此时完成这个操作需要的时间大概是 1.2s,如下图所示:\n而此时我们在 html 文档的中间插中入了一段 js 代码,在 dom 构建中间的过程中发现了这个 script 标签,假设这段 js 代码只需要执行 0.0001s,那么完成这个操作需要的时间就会变成:\n![[assets/pasted image 20241213170001.png]]\n那如果我们把 css 放到前面,js 放到最后引入时,构建时间会变成:\n![[assets/pasted image 20241213165908.png|600]]\n由此可见,虽然只是插入了小小的一段只运行 0.0001s 的 js 代码,不同的引入时机也会严重影响 domtree 的构建速度。\n简而言之,如果在 dom,cssom 和 javascript 执行之间引入大量的依赖关系,可能会导致浏览器在处理渲染资源时出现大幅度延迟:\n当浏览器遇到一个 script 标签时,domtree 的构建将被暂停,直至脚本执行完毕 javascript 可以查询和修改 domtree 与 cssomtree 直至 cssom 构建完毕,javascript 才会执行 脚本在文档中的位置很重要 渲染树的构建 现在,我们已经拥有了完整的 dom 树和 cssom 树。dom 树上每一个节点对应着网页里每一个元素,cssom 树上每个节点对应着网页里每个元素的样式,并且此时浏览器也可以通过 javascript 操作 dom/cssom 树,动态改变它的结构。但是 dom/ cssom 树本身并不能直接用于排版和渲染,浏览器还会生成另外一棵树:render 树。\n接下来我们来谈几条概念\nrender 树上的每一个节点被称为:renderobject。 renderobject 跟 dom 节点几乎是一一对应的,当一个 可见的 dom 节点 被添加到 dom 树上时,内核就会为它生成对应的 renderoject 添加到 render 树上。 其中,可见的 dom 节点不包括: 一些不会体现在渲染输出中的节点(\u0026lt;html\u0026gt;\u0026lt;script\u0026gt;\u0026lt;link\u0026gt;….),会直接被忽略掉。 通过 css 隐藏的节点。例如上图中的 span 节点,因为有一个 css 显式规则在该节点上设置了 display:none 属性,那么它在生成 renderobject 时会被直接忽略掉。 render 树是衔接浏览器排版引擎和渲染引擎之间的桥梁,它是排版引擎的输出,渲染引擎的输入。 此时的 render 树上,已经包含了网页上所有可见元素的内容和位置信息 排版引擎会根据 render 树的内容和结构,准确的计算出元素该在网页上的什么位置。到此,我们已经具备进入布局的一切准备条件,但是通过上面我们知道,布局后面还有一个渲染过程,那么 render 树是衔接浏览器排版引擎和渲染引擎之间的桥梁,它是排版引擎的输出,渲染引擎的输入。这句话是什么意思呢?\nrenderobject 和 renderlayer 浏览器渲染引擎并不是直接使用 render 树进行绘制,为了方便处理 positioning,clipping,overflow-scroll,css transfrom/opacrity/animation/filter,mask or reflection,z-indexing 等属性,浏览器需要生成另外一棵树:layer 树 。\n浏览器会为一些特定的 renderobject 生成对应的 renderlayer,其中的规则是:\n是否是页面的根节点 是否有 css 的一些布局属性 是否透明 是否有溢出 是否有 css 滤镜 是否包含一个 canvas 元素使得节点拥有视图上下文 是否包含一个 video 元素 当满足上面其中一个条件时,这个 rrenderobject 就会被浏览器选中生成对应的 renderlayer。至于那些没有被命运选中的 rrenderobject,会从属与父节点的 renderlayer。最终,每个 rrenderobject 都会直接或者间接的属于一个 renderlayer。\n浏览器渲染引擎在布局和渲染时会遍历整个 layer 树,访问每一个 renderlayer,再遍历从属于这个 renderlayer 的 rrenderobject,将每一个 renderobject 绘制出来。可以理解为:layer 树决定了网页绘制的层次顺序,而从属于 renderlayer 的 rrenderobject 决定了这个 layer 的内容,所有的 renderlayer 和 rrenderobject 一起就决定了网页在屏幕上最终呈现出来的内容。\n布局 到目前为止,浏览器计算出了哪些节点是可见的以及它的信息和样式,接下来就需要计算这些节点在设备视口内的确切位置和大小,这个过程我们称之为“布局”。\n布局最后的输出是一个“盒模型”:将所有相对测量值都转换成屏幕上的绝对像素。\n渲染 最后,既然我们知道了哪些节点可见、它们的计算样式以及几何信息,我们终于可以将这些信息传递给最后一个阶段:将渲染树中的每个节点转换成屏幕上的实际像素:浏览器通过发出“paint setup”和“paint”事件,将渲染树转换成屏幕上的像素。\n至此,我们就能够在浏览器上看到漂亮的网页了\n谈及页面性能优化,我们也常说要尽量减少浏览器的重排和重绘,浏览器重排和重绘时究竟做了哪些工作呢?\n我们平时常说的重排,其实就是浏览器计算 render 树,布局到渲染的这个过程,而重绘就是计算 layer 树到渲染的这个过程,每当触发一次重绘和重排时,浏览器都需要重新经过一遍上述的计算。很显然,重排会产生比重绘更大的开销,但无论是重排还是重绘,都会给浏览器渲染线程造成很大的负担,所以,我们在实际生产中要严格注意减少重排和重绘的触发。至于如何减少重排和重绘的次数,这里就不多做展开了,详细请听下回分解~\n总结 经过 1.构建对象模型(dom,cssom),2.构建渲染树(rendertree),3.布局,4.渲染 这几个步骤后,我们就能在浏览器上看到漂亮的网页啦。\ncss 被视为阻塞渲染的资源,应放到代码的头部尽快加载。\n同步的 javascript 会暂停 domtree 的构建,应放到代码的尾部最后加载,或者使用 async/defer 属性 异步加载 javascript。\n重排和重绘会给浏览器渲染线程造成很大的负担,尽量减少重排和重绘的触发次数。\n","date":"2024-12-13","permalink":"https://aituyaa.com/%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B/","summary":"\u003cp\u003e➡️ 本文内容主体部分来自 \u003ca href=\"https://zhuanlan.zhihu.com/p/74792085\"\u003e浏览器的渲染过程 - 知乎\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e通常,我们只需要编写 HTML,CSS,JavaScript,浏览器上就能呈现出漂亮的网页了,但是浏览器是如何使用我们的代码在屏幕上渲染像素的呢?\u003c/p\u003e\n\u003c/blockquote\u003e","title":"浏览器的渲染过程"},]
[{"content":"如何设置锚点,使得我们可以跳转到网页的指定位置呢?往下看。\n简单的锚点 首先,我们需要设置一个锚点链接(跳转去锚点的连接) - \u0026lt;a href=\u0026quot;#maodian\u0026quot;\u0026gt;跳转去下锚位置\u0026lt;/a\u0026gt; 。看,只需要在锚点链接的 href 属性值最前面加 # (如 #maodian)就可以了。\n那么,如何设置锚点呢?\n1 2 3 4 5 6 7 \u0026lt;!-- 设置锚点链接 --\u0026gt; \u0026lt;a href=\u0026#34;#maodian\u0026#34;\u0026gt;跳转去下锚位置\u0026lt;/a\u0026gt; \u0026lt;!-- 两种设置锚点的方式 --\u0026gt; \u0026lt;a name=\u0026#34;maodian\u0026#34;\u0026gt;锚点\u0026lt;/a\u0026gt; \u0026lt;!-- 或 --\u0026gt; \u0026lt;div id=\u0026#34;maodian\u0026#34;\u0026gt;锚点\u0026lt;/div\u0026gt; 看,只需要一个带有 name 属性(其值为锚点链接 href 属性值去掉 #,如 maodian)的 \u0026lt;a\u0026gt; 标签;或者是一个带有 id 属性(其值也为锚点链接 href 属性值去掉 #,如 maodian)的普通标签即可。\n这种方法有一个不算缺点的缺点 - 浏览器的 url 会发生变化。\n有没有办法让 url 不变化呢?有,使用 javascript ,这里我们只介绍一个最佳实践的办法。\n使用 scrollintoview 如果滚动页面也是 dom 没有解决的一个问题。为了解决这个问题,浏览器实现了一下方法,以方便开发人员如何更好的控制页面的滚动。\n在各种专有方法中,html5 选择了 scrollintoview() 作为标准方法。\nscrollintoview() 可以在所有的 html 元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视窗中。\n如果给该方法传入 true 作为参数,或者不传入任何参数,那么窗口滚动之后会让调动元素顶部和视窗顶部尽可能齐平。如果传入 false 作为参数,调用元素会尽可能全部出现在视口中(可能的话,调用元素的底部会与视口的顶部齐平。)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 \u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;!-- 锚点链接 --\u0026gt; \u0026lt;button id=\u0026#34;md-link-1\u0026#34;\u0026gt;scrollintoview(true)\u0026lt;/button\u0026gt; \u0026lt;button id=\u0026#34;md-link-2\u0026#34;\u0026gt;scrollintoview(false)\u0026lt;/button\u0026gt; \u0026lt;!-- ... --\u0026gt; \u0026lt;!-- 锚点 --\u0026gt; \u0026lt;div id=\u0026#34;maodian1\u0026#34;\u0026gt;scrollintoview(ture) 元素上边框与视窗顶部齐平\u0026lt;/div\u0026gt; \u0026lt;div id=\u0026#34;maodian2\u0026#34;\u0026gt;scrollintoview(false) 元素下边框与视窗底部齐平\u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script\u0026gt; // 元素上边框与视窗顶部齐平 document.queryselector(\u0026#34;#md-link-1\u0026#34;).onclick = function () { // document.queryselector(\u0026#34;#maodian1\u0026#34;).scrollintoview(); // 或 document.queryselector(\u0026#34;#maodian1\u0026#34;).scrollintoview(true); }; // 元素下边框与视窗底部齐平 document.queryselector(\u0026#34;#md-link-2\u0026#34;).onclick = function () { document.queryselector(\u0026#34;#maodian2\u0026#34;).scrollintoview(true); }; \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 除了手动触发滚动外,也可以在渲染页面的时候使其完成自动跳转到锚点处,快去实现一下吧。\n","date":"2024-12-13","permalink":"https://aituyaa.com/%E7%BD%91%E9%A1%B5%E4%B8%AD%E7%9A%84%E9%94%9A%E7%82%B9%E8%B7%B3%E8%BD%AC/","summary":"\u003cp\u003e如何设置锚点,使得我们可以跳转到网页的指定位置呢?往下看。\u003c/p\u003e","title":"网页中的锚点跳转"},]
[{"content":"source map 是一种文件格式,用于将压缩、混淆或转换后的代码映射回原始代码。它通常用于调试生产环境中的 javascript 代码,帮助开发者在浏览器中查看和调试原始源代码,而不是压缩或混淆后的代码。\n为什么需要 source map? 在生产环境中,javascript 代码通常会经过以下处理:\n压缩(minification):移除空格、注释和不必要的字符,减小文件大小。 混淆(obfuscation):重命名变量和函数,使代码难以阅读。 转换(transpilation):将现代 javascript 代码(如 es6+)转换为兼容性更好的旧版本代码(如 es5)。 这些处理虽然提高了性能和安全性,但也使得调试变得困难。source map 通过提供一个映射文件,将压缩或混淆后的代码映射回原始代码,从而方便开发者调试。\nsource map 的工作原理 source map 文件是一个 json 文件,通常以 .map 结尾。它包含以下信息:\n原始文件和生成文件的映射关系:将压缩或混淆后的代码的每一行和每一列映射到原始代码的对应位置。 文件路径:指定原始文件和生成文件的路径。 内容映射:使用一种紧凑的格式(如 vlq 编码)来表示映射关系。 浏览器在加载 javascript 文件时,如果发现文件末尾有 //# sourcemappingurl=... 注释,就会加载对应的 .map 文件,并在开发者工具中显示原始代码。\n如何生成 source map? 大多数现代构建工具(如 webpack、babel、typescript 等)都支持生成 source map。以下是一些常见的工具和配置方法:\n1. webpack\n在 webpack.config.js 中配置 devtool 选项:\n1 2 3 4 5 6 7 8 9 module.exports = { mode: \u0026#39;production\u0026#39;, devtool: \u0026#39;source-map\u0026#39;, // 生成 source map entry: \u0026#39;./src/index.js\u0026#39;, output: { filename: \u0026#39;bundle.js\u0026#39;, path: __dirname + \u0026#39;/dist\u0026#39;, }, }; 生成的 bundle.js.map 文件会与 bundle.js 一起发布。\n2. babel\n在 .babelrc 或 babel.config.js 中配置 sourcemaps 选项:\n1 2 3 4 { \u0026#34;presets\u0026#34;: [\u0026#34;@babel/preset-env\u0026#34;], \u0026#34;sourcemaps\u0026#34;: \u0026#34;inline\u0026#34; // 生成内联 source map } 3. typescript\n在 tsconfig.json 中配置 sourcemap 选项:\n1 2 3 4 5 { \u0026#34;compileroptions\u0026#34;: { \u0026#34;sourcemap\u0026#34;: true // 生成 source map } } source map 的类型 source map 有多种类型,不同的类型会影响生成的文件大小和性能:\n类型 描述 source-map 生成独立的 .map 文件,性能最佳,但文件较大。 inline-source-map 将 source map 嵌入到生成的文件中,文件较大,但不需要单独加载 .map 文件。 eval-source-map 使用 eval 生成内联 source map,性能较差,但调试体验较好。 cheap-source-map 只映射到行,不映射到列,文件较小,但调试精度较低。 cheap-module-source-map 类似于 cheap-source-map,但支持模块映射。 如何在浏览器中使用 source map? 启用开发者工具:\n在浏览器中打开开发者工具(通常按 f12 或 ctrl+shift+i)。 确保启用了 source map 功能(通常默认启用)。 加载 source map:\n如果生成的 javascript 文件末尾包含 //# sourcemappingurl=... 注释,浏览器会自动加载对应的 .map 文件。 调试原始代码:\n在开发者工具的 \u0026ldquo;sources\u0026rdquo; 面板中,可以看到原始代码,而不是压缩或混淆后的代码。 source map 的安全性 由于 source map 文件包含原始代码的映射关系,如果将其发布到生产环境中,可能会暴露敏感信息(如未混淆的变量名、函数名等)。因此,通常建议:\n不要在生产环境中发布 source map:\n在生产环境中,将 devtool 设置为 false 或 none,避免生成 source map。 使用混淆工具:\n在发布前,使用工具(如 uglifyjs 或 terser)对代码进行混淆,并移除 source map 注释。 限制访问:\n如果必须发布 source map,可以将其放在受限的 cdn 或服务器上,并限制访问权限。 示例:生成和使用 source map 1. 生成 source map\n假设你使用 webpack 构建项目,配置如下:\n1 2 3 4 5 6 7 8 9 module.exports = { mode: \u0026#39;production\u0026#39;, devtool: \u0026#39;source-map\u0026#39;, entry: \u0026#39;./src/index.js\u0026#39;, output: { filename: \u0026#39;bundle.js\u0026#39;, path: __dirname + \u0026#39;/dist\u0026#39;, }, }; 构建后,会生成以下文件:\ndist/bundle.js:压缩后的 javascript 文件。 dist/bundle.js.map:对应的 source map 文件。 2. 在浏览器中使用 source map\n打开浏览器开发者工具。 在 \u0026ldquo;sources\u0026rdquo; 面板中,可以看到原始的 src/index.js 文件。 设置断点、调试代码,就像调试开发环境中的代码一样。 总结 source map 是一个强大的工具,用于在生产环境中调试压缩或混淆后的代码。通过生成和使用 source map,开发者可以:\n提升调试效率:在浏览器中查看和调试原始代码。 支持现代工具:大多数构建工具(如 webpack、babel、typescript)都支持生成 source map。 灵活配置:根据需求选择不同的 source map 类型。 然而,在生产环境中发布 source map 可能会带来安全风险,因此需要谨慎处理。\n","date":"2024-12-12","permalink":"https://aituyaa.com/source-map/","summary":"\u003cp\u003e\u003ccode\u003eSource Map\u003c/code\u003e 是一种文件格式,用于将压缩、混淆或转换后的代码映射回原始代码。它通常用于调试生产环境中的 JavaScript 代码,帮助开发者在浏览器中查看和调试原始源代码,而不是压缩或混淆后的代码。\u003c/p\u003e","title":"source map"},]
[{"content":"fetch 是现代 javascript 中用于发起网络请求的一个全局 api。它提供了一种简单、灵活的方式来与服务器进行通信,基于 promise,因此非常适合处理异步操作。fetch 是 xmlhttprequest 的替代品,广泛用于前端开发中。\n基础 基本用法 fetch 的基本语法如下:\n1 2 3 4 5 6 7 fetch(resource, options) .then(response =\u0026gt; { // 处理响应 }) .catch(error =\u0026gt; { // 处理错误 }); resource: 请求的资源,通常是一个 url。 options: 可选的配置对象,用于指定请求的方法、头信息、请求体等。 示例:get 请求\n1 2 3 4 5 6 7 8 fetch(\u0026#39;https://jsonplaceholder.typicode.com/posts\u0026#39;) .then(response =\u0026gt; response.json()) // 将响应转换为 json .then(data =\u0026gt; { console.log(data); // 输出: 包含所有帖子的数组 }) .catch(error =\u0026gt; { console.error(\u0026#39;error:\u0026#39;, error); }); 示例:post 请求\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fetch(\u0026#39;https://jsonplaceholder.typicode.com/posts\u0026#39;, { method: \u0026#39;post\u0026#39;, // 请求方法 headers: { \u0026#39;content-type\u0026#39;: \u0026#39;application/json\u0026#39; // 请求头 }, body: json.stringify({ title: \u0026#39;foo\u0026#39;, body: \u0026#39;bar\u0026#39;, userid: 1 }) // 请求体 }) .then(response =\u0026gt; response.json()) .then(data =\u0026gt; { console.log(data); // 输出: 新创建的帖子对象 }) .catch(error =\u0026gt; { console.error(\u0026#39;error:\u0026#39;, error); }); 处理响应\nfetch 返回的 response 对象包含多种方法来处理不同类型的响应数据:\nresponse.json(): 将响应体解析为 json 格式。 response.text(): 将响应体解析为文本格式。 response.blob(): 将响应体解析为 blob 对象。 response.arraybuffer(): 将响应体解析为 arraybuffer 对象。 1 2 3 4 5 6 7 8 fetch(\u0026#39;https://jsonplaceholder.typicode.com/posts/1\u0026#39;) .then(response =\u0026gt; response.json()) .then(data =\u0026gt; { console.log(data); // 输出: 单个帖子的对象 }) .catch(error =\u0026gt; { console.error(\u0026#39;error:\u0026#39;, error); }); 处理错误 fetch 只有在网络错误(如无法连接到服务器)时才会进入 catch 块。如果服务器返回一个错误状态码(如 404 或 500),fetch 不会自动抛出错误,你需要手动检查 response.ok 或 response.status。\n1 2 3 4 5 6 7 8 9 10 11 12 13 fetch(\u0026#39;https://jsonplaceholder.typicode.com/nonexistent\u0026#39;) .then(response =\u0026gt; { if (!response.ok) { throw new error(\u0026#39;network response was not ok\u0026#39;); } return response.json(); }) .then(data =\u0026gt; { console.log(data); }) .catch(error =\u0026gt; { console.error(\u0026#39;error:\u0026#39;, error); }); 使用 async/await fetch 与 async/await 结合使用可以使代码更加简洁和易读。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 async function fetchposts() { try { const response = await fetch(\u0026#39;https://jsonplaceholder.typicode.com/posts\u0026#39;); if (!response.ok) { throw new error(\u0026#39;network response was not ok\u0026#39;); } const data = await response.json(); console.log(data); } catch (error) { console.error(\u0026#39;error:\u0026#39;, error); } } fetchposts(); 配置选项 fetch 的 options 参数可以包含以下常用选项:\nmethod: 请求方法(如 get, post, put, delete)。 headers: 请求头(如 content-type, authorization)。 body: 请求体(通常用于 post 或 put 请求)。 mode: 请求模式(如 cors, no-cors, same-origin)。 credentials: 是否发送 cookies(如 omit, same-origin, include)。 cache: 缓存模式(如 default, no-store, reload, no-cache, force-cache)。 redirect: 重定向模式(如 follow, error, manual)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 fetch(\u0026#39;https://jsonplaceholder.typicode.com/posts\u0026#39;, { method: \u0026#39;post\u0026#39;, headers: { \u0026#39;content-type\u0026#39;: \u0026#39;application/json\u0026#39; }, body: json.stringify({ title: \u0026#39;foo\u0026#39;, body: \u0026#39;bar\u0026#39;, userid: 1 }), mode: \u0026#39;cors\u0026#39;, credentials: \u0026#39;same-origin\u0026#39; }) .then(response =\u0026gt; response.json()) .then(data =\u0026gt; { console.log(data); }) .catch(error =\u0026gt; { console.error(\u0026#39;error:\u0026#39;, error); }); 总结 fetch 是一个强大且灵活的 api,用于发起网络请求。它基于 promise,因此非常适合处理异步操作。通过 fetch,你可以轻松地与服务器进行通信,处理各种类型的响应数据,并结合 async/await 使代码更加简洁和易读。\n附录 fetch 相较于传统的 xhr 的优势和不足 优势的方面 1、基于 promise\n优势: fetch 基于 promise,使得异步代码的编写更加简洁和直观。可以使用 then() 和 catch() 链式调用,或者结合 async/await 编写更易读的代码。 1 2 3 4 fetch(\u0026#39;https://api.example.com/data\u0026#39;) .then(response =\u0026gt; response.json()) .then(data =\u0026gt; console.log(data)) .catch(error =\u0026gt; console.error(error)); 对比 xhr: xhr 使用回调函数处理异步操作,容易导致“回调地狱”,代码可读性较差。 2、更简洁的 api\n优势: fetch 的 api 设计更加简洁,语法更现代化。只需调用 fetch(url, options),而不需要像 xhr 那样手动设置 open()、send() 和事件监听器。 1 2 3 4 5 fetch(\u0026#39;https://api.example.com/data\u0026#39;, { method: \u0026#39;post\u0026#39;, headers: { \u0026#39;content-type\u0026#39;: \u0026#39;application/json\u0026#39; }, body: json.stringify({ key: \u0026#39;value\u0026#39; }) }); 对比 xhr: xhr 的 api 较为冗长,需要手动设置请求方法、头信息和事件监听器。 3、内置支持多种数据格式\n优势: fetch 提供了内置方法来处理不同类型的响应数据(如 response.json()、response.text()、response.blob() 等),使得处理响应更加方便。 1 2 3 fetch(\u0026#39;https://api.example.com/data\u0026#39;) .then(response =\u0026gt; response.json()) // 自动解析 json .then(data =\u0026gt; console.log(data)); 对比 xhr: xhr 需要手动解析响应数据(如 json.parse(xhr.responsetext))。 4、更现代的错误处理\n优势: fetch 的错误处理更加现代化。只有网络错误(如无法连接到服务器)才会触发 catch 块,而 http 错误(如 404 或 500)不会自动抛出错误,需要手动检查 response.ok 或 response.status。 1 2 3 4 5 6 7 8 fetch(\u0026#39;https://api.example.com/data\u0026#39;) .then(response =\u0026gt; { if (!response.ok) { throw new error(\u0026#39;network response was not ok\u0026#39;); } return response.json(); }) .catch(error =\u0026gt; console.error(error)); 对比 xhr: xhr 的错误处理依赖于 status 属性,需要手动检查状态码。 5、支持跨域请求(cors)\n优势: fetch 默认支持跨域请求(cors),并且可以通过 mode 选项(如 cors、no-cors、same-origin)灵活控制请求的行为。 1 2 3 fetch(\u0026#39;https://api.example.com/data\u0026#39;, { mode: \u0026#39;cors\u0026#39; }); 对比 xhr: xhr 也支持 cors,但配置相对复杂。 6、支持流式数据\n优势: fetch 支持流式数据处理,可以逐步读取大文件或流式响应。 1 2 3 4 5 fetch(\u0026#39;https://api.example.com/large-file\u0026#39;) .then(response =\u0026gt; response.body.getreader()) .then(reader =\u0026gt; { // 逐步读取数据 }); 对比 xhr: xhr 不支持流式数据处理。 不足的方面 1、不支持进度事件\n不足: fetch 不支持进度事件(如 xhr.upload.onprogress 或 xhr.onprogress),因此无法实时监控文件上传或下载的进度。 1 2 3 4 const xhr = new xmlhttprequest(); xhr.upload.onprogress = function (event) { console.log(`uploaded ${event.loaded} of ${event.total} bytes`); }; 对比 xhr: xhr 提供了 onprogress 事件,可以方便地监控上传或下载的进度。 2、默认不发送 cookies\n不足: fetch 默认不会发送跨域请求的 cookies(除非显式设置 credentials: 'include')。 1 2 3 fetch(\u0026#39; https://api.example.com/data \u0026#39;, { credentials: \u0026#39;include\u0026#39; // 需要显式开启 }); 对比 xhr: xhr 默认会发送 cookies,无需额外配置。 3、不支持超时设置\n不足: fetch 没有内置的超时机制,需要手动实现超时逻辑。 1 2 3 4 5 6 7 const controller = new abortcontroller(); const signal = controller.signal; settimeout(() =\u0026gt; controller.abort(), 5000); // 5 秒超时 fetch(\u0026#39; https://api.example.com/data \u0026#39;, { signal }) .catch(error =\u0026gt; console.error(\u0026#39;request timed out\u0026#39;)); 对比 xhr: xhr 提供了 timeout 属性,可以轻松设置请求超时。 4、兼容性问题\n不足: fetch 在某些旧版浏览器(如 ie11)中不被支持,需要使用 polyfill 来兼容。 1 2 \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 对比 xhr: xhr 在所有现代浏览器和旧版浏览器中都得到了广泛支持。 5、复杂场景下功能不足\n不足: 在某些复杂场景下(如需要精细控制请求行为或处理二进制数据),fetch 的功能可能不如 xhr 灵活。 对比 xhr: xhr 提供了更底层、更精细的控制能力。 总结 特性 fetch 优势 fetch 不足 api 设计 基于 promise,语法简洁,支持链式调用和 async/await。 不支持进度事件,无法实时监控上传或下载进度。 错误处理 现代化错误处理,结合 promise 更易读。 默认不发送 cookies,需要显式配置。 数据处理 内置支持多种数据格式(如 json、文本、blob)。 不支持超时设置,需要手动实现。 兼容性 现代浏览器支持良好,但需要 polyfill 兼容旧版浏览器。 在旧版浏览器(如 ie11)中不被支持。 功能灵活性 支持流式数据处理,适合处理大文件或流式响应。 在复杂场景下功能不如 xhr 灵活。 通过以上对比可以看出,fetch 是一个更现代化、更简洁的 api,适合大多数场景。但在需要精细控制请求行为或兼容旧版浏览器的场景下,xhr 仍然是一个可靠的选择。\n","date":"2024-12-12","permalink":"https://aituyaa.com/%E5%85%B3%E4%BA%8E-fetch/","summary":"\u003cp\u003e\u003ccode\u003efetch\u003c/code\u003e 是现代 JavaScript 中用于发起网络请求的一个全局 API。它提供了一种简单、灵活的方式来与服务器进行通信,基于 \u003ccode\u003ePromise\u003c/code\u003e,因此非常适合处理异步操作。\u003ccode\u003efetch\u003c/code\u003e 是 \u003ccode\u003eXMLHttpRequest\u003c/code\u003e 的替代品,广泛用于前端开发中。\u003c/p\u003e","title":"关于 fetch"},]
[{"content":"promise 是 javascript 中用于处理异步操作的一种对象。它代表了一个异步操作的最终完成(或失败)及其结果值。promise 提供了一种更清晰、更结构化的方式来处理异步代码,避免了传统的回调地狱(callback hell)问题。\n基础 基本概念 状态(state):\npending: 初始状态,既不是成功也不是失败。 fulfilled: 操作成功完成。 rejected: 操作失败。 结果(result):\n当 promise 的状态变为 fulfilled 时,会有一个结果值。 当 promise 的状态变为 rejected 时,会有一个错误原因。 创建一个 promise 你可以使用 promise 构造函数来创建一个新的 promise 对象。构造函数接受一个执行函数(executor function),该函数有两个参数:resolve 和 reject。\n1 2 3 4 5 6 7 8 9 10 11 const mypromise = new promise((resolve, reject) =\u0026gt; { // 异步操作 settimeout(() =\u0026gt; { const success = true; // 模拟成功或失败 if (success) { resolve(\u0026#39;operation succeeded\u0026#39;); } else { reject(\u0026#39;operation failed\u0026#39;); } }, 1000); }); 使用 promise 你可以通过 then() 方法来处理 promise 的成功状态,通过 catch() 方法来处理 promise 的失败状态。\n1 2 3 4 5 6 7 mypromise .then(result =\u0026gt; { console.log(result); // 输出: operation succeeded }) .catch(error =\u0026gt; { console.error(error); // 输出: operation failed }); 1、 promise 链式调用\nthen() 方法返回一个新的 promise,因此你可以链式调用多个 then() 方法。\n1 2 3 4 5 6 7 8 9 10 11 mypromise .then(result =\u0026gt; { console.log(result); // 输出: operation succeeded return \u0026#39;next step\u0026#39;; }) .then(nextresult =\u0026gt; { console.log(nextresult); // 输出: next step }) .catch(error =\u0026gt; { console.error(error); // 输出: operation failed }); 2、 promise.all()\npromise.all() 方法接受一个包含多个 promise 的数组,并返回一个新的 promise。这个新的 promise 在所有输入的 promise 都成功完成时才会完成,返回一个包含所有结果的数组。如果任何一个 promise 失败,则返回的 promise 会立即失败。\n1 2 3 4 5 6 7 8 9 10 11 const promise1 = promise.resolve(1); const promise2 = promise.resolve(2); const promise3 = promise.resolve(3); promise.all([promise1, promise2, promise3]) .then(results =\u0026gt; { console.log(results); // 输出: [1, 2, 3] }) .catch(error =\u0026gt; { console.error(error); }); 3、 promise.race()\npromise.race() 方法接受一个包含多个 promise 的数组,并返回一个新的 promise。这个新的 promise 在任何一个输入的 promise 成功或失败时立即完成,返回第一个完成的 promise 的结果。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const promise1 = new promise((resolve, reject) =\u0026gt; { settimeout(resolve, 100, \u0026#39;one\u0026#39;); }); const promise2 = new promise((resolve, reject) =\u0026gt; { settimeout(resolve, 50, \u0026#39;two\u0026#39;); }); promise.race([promise1, promise2]) .then(result =\u0026gt; { console.log(result); // 输出: two }) .catch(error =\u0026gt; { console.error(error); }); 4、 promise.resolve() 和 promise.reject() 🌟\npromise.resolve() 和 promise.reject() 是两个静态方法,用于快速创建已经成功或失败的 promise。\n1 2 3 4 5 6 7 8 9 const resolvedpromise = promise.resolve(\u0026#39;resolved\u0026#39;); resolvedpromise.then(result =\u0026gt; { console.log(result); // 输出: resolved }); const rejectedpromise = promise.reject(\u0026#39;rejected\u0026#39;); rejectedpromise.catch(error =\u0026gt; { console.error(error); // 输出: rejected }); 错误处理 你可以使用 catch() 方法来捕获 promise 链中的任何错误。\n1 2 3 4 5 6 7 8 9 10 11 mypromise .then(result =\u0026gt; { console.log(result); throw new error(\u0026#39;something went wrong\u0026#39;); }) .then(nextresult =\u0026gt; { console.log(nextresult); }) .catch(error =\u0026gt; { console.error(error.message); // 输出: something went wrong }); 总结 promise 是 javascript 中处理异步操作的重要工具,它提供了一种更清晰、更结构化的方式来处理异步代码。通过 then()、catch()、promise.all() 和 promise.race() 等方法,你可以轻松地处理复杂的异步操作,避免回调地狱,并提高代码的可读性和可维护性。\n","date":"2024-12-12","permalink":"https://aituyaa.com/%E5%85%B3%E4%BA%8E-promise/","summary":"\u003cp\u003e\u003ccode\u003ePromise\u003c/code\u003e 是 JavaScript 中用于处理异步操作的一种对象。它\u003cstrong\u003e代表了一个异步操作的最终完成(或失败)及其结果值\u003c/strong\u003e。\u003ccode\u003ePromise\u003c/code\u003e 提供了一种更清晰、更结构化的方式来处理异步代码,避免了传统的回调地狱(callback hell)问题。\u003c/p\u003e","title":"关于 promise"},]
[{"content":"函数柯里化(currying) 是一种将多参数函数转换为一系列单参数函数的技术。通过柯里化,可以将一个接受多个参数的函数分解为多个只接受一个参数的函数,每个函数返回一个新的函数,直到所有参数都被传递完毕。\n什么是函数柯里化? 柯里化的核心思想是:\n将一个多参数函数转换为一系列单参数函数。 每个单参数函数返回一个新的函数,等待接收下一个参数。 例如,一个接受两个参数的函数 add(a, b) 可以被柯里化为 add(a)(b)。\n柯里化的优点 参数复用:\n柯里化允许你部分应用函数,复用部分参数。 例如,可以创建一个预定义参数的函数,方便后续调用。 提高代码的可读性和灵活性:\n柯里化可以使代码更具声明性,减少重复代码。 函数组合:\n柯里化是函数式编程中的重要概念,常用于函数组合(function composition)。 柯里化的实现 示例 1:简单的柯里化\n以下是一个简单的柯里化示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 原始函数 function add(a, b) { return a + b; } // 柯里化后的函数 function curryadd(a) { return function(b) { return a + b; }; } // 使用柯里化函数 const add5 = curryadd(5); // 返回一个新函数,等待接收下一个参数 console.log(add5(3)); // 输出: 8 示例 2:通用的柯里化函数\n以下是一个通用的柯里化函数实现:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function curry(fn) { return function curried(...args) { if (args.length \u0026gt;= fn.length) { // 如果参数数量足够,直接调用原始函数 return fn.apply(this, args); } else { // 如果参数数量不足,返回一个新的函数,等待接收剩余参数 return function(...moreargs) { return curried.apply(this, args.concat(moreargs)); }; } }; } // 示例函数 function add(a, b, c) { return a + b + c; } // 柯里化 add 函数 const curriedadd = curry(add); // 使用柯里化函数 console.log(curriedadd(1)(2)(3)); // 输出: 6 console.log(curriedadd(1, 2)(3)); // 输出: 6 console.log(curriedadd(1)(2, 3)); // 输出: 6 console.log(curriedadd(1, 2, 3)); // 输出: 6 柯里化的应用场景 1. 参数复用\n柯里化可以让你创建一个预定义参数的函数,方便后续调用。\n1 2 3 4 5 6 function multiply(a, b) { return a * b; } const double = curry(multiply)(2); // 预定义参数 a = 2 console.log(double(5)); // 输出: 10 2. 函数组合\n柯里化是函数组合的基础,可以将多个函数组合成一个新的函数。\n1 2 3 4 5 6 7 8 9 10 function add(a, b) { return a + b; } function multiply(a, b) { return a * b; } const addandmultiply = curry(multiply)(curry(add)(2)); console.log(addandmultiply(3)); // 输出: 10 (2 + 3) * 2 3. 延迟执行\n柯里化可以延迟函数的执行,直到所有参数都准备好。\n1 2 3 4 5 6 7 8 9 10 11 function log(date, importance, message) { console.log(`[${date.toisostring()}] [${importance}] ${message}`); } const curriedlog = curry(log); // 创建一个预定义日期和重要性的日志函数 const lognow = curriedlog(new date(), \u0026#34;info\u0026#34;); // 延迟执行日志 lognow(\u0026#34;this is a test message.\u0026#34;); 柯里化的局限性 性能开销:\n柯里化会增加函数的调用栈深度,可能导致性能开销。 可读性问题:\n对于不熟悉柯里化的开发者,柯里化后的代码可能难以理解。 适用场景有限:\n柯里化更适合函数式编程风格,对于命令式编程风格的代码,可能并不适用。 总结 函数柯里化是一种将多参数函数转换为一系列单参数函数的技术,具有以下优点:\n参数复用:可以创建预定义参数的函数,方便后续调用。 提高代码的可读性和灵活性:使代码更具声明性,减少重复代码。 函数组合:是函数式编程中的重要概念,常用于函数组合。 然而,柯里化也有一些局限性,如性能开销和可读性问题。在实际开发中,应根据具体场景选择是否使用柯里化。\n附录 如何理解“可以创建预定义参数的函数,方便后续调用” “可以创建预定义参数的函数,方便后续调用” 是柯里化的一个重要特性。它的核心思想是:通过部分应用(partial application)参数,生成一个新的函数,这个新函数已经预定义了部分参数,等待接收剩余的参数。\n什么是“预定义参数的函数”? “预定义参数的函数”是指一个函数已经固定了部分参数,但仍然可以接收剩余的参数。通过这种方式,可以创建一个更具体的函数,方便后续调用。\n例如:\n原始函数:add(a, b) 需要两个参数 a 和 b。 预定义参数的函数:add5 = add(5),这个函数已经预定义了 a = 5,只需要传入 b 即可。 示例:创建预定义参数的函数\n示例 1:简单的预定义参数\n1 2 3 4 5 6 7 8 9 10 // 原始函数 function add(a, b) { return a + b; } // 创建一个预定义参数的函数 const add5 = (b) =\u0026gt; add(5, b); // 使用预定义参数的函数 console.log(add5(3)); // 输出: 8 在这个例子中,add5 是一个预定义了 a = 5 的函数,只需要传入 b 即可完成计算。\n示例 2:使用柯里化创建预定义参数的函数\n通过柯里化,可以更方便地创建预定义参数的函数。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // 柯里化函数 function curry(fn) { return function curried(...args) { if (args.length \u0026gt;= fn.length) { return fn.apply(this, args); } else { return function(...moreargs) { return curried.apply(this, args.concat(moreargs)); }; } }; } // 原始函数 function multiply(a, b, c) { return a * b * c; } // 柯里化 multiply 函数 const curriedmultiply = curry(multiply); // 创建预定义参数的函数 const multiplyby2 = curriedmultiply(2); // 预定义 a = 2 const multiplyby2and3 = multiplyby2(3); // 预定义 b = 3 // 使用预定义参数的函数 console.log(multiplyby2and3(4)); // 输出: 24 (2 * 3 * 4) 在这个例子中:\ncurriedmultiply(2) 返回一个新函数,预定义了 a = 2。 multiplyby2(3) 返回一个新函数,预定义了 b = 3。 multiplyby2and3(4) 最终计算 2 * 3 * 4。 为什么“预定义参数的函数”方便后续调用? 1. 减少重复代码\n通过预定义参数,可以避免在每次调用时重复传入相同的参数。\n例如,假设你需要多次调用一个函数,且每次都需要传入相同的参数:\n1 2 3 4 5 6 7 function log(date, importance, message) { console.log(`[${date.toisostring()}] [${importance}] ${message}`); } // 每次调用都需要传入相同的日期和重要性 log(new date(), \u0026#34;info\u0026#34;, \u0026#34;this is a test message.\u0026#34;); log(new date(), \u0026#34;info\u0026#34;, \u0026#34;another message.\u0026#34;); 通过预定义参数,可以简化调用:\n1 2 3 4 5 const lognow = (message) =\u0026gt; log(new date(), \u0026#34;info\u0026#34;, message); // 只需要传入 message lognow(\u0026#34;this is a test message.\u0026#34;); lognow(\u0026#34;another message.\u0026#34;); 2. 提高代码的可读性\n预定义参数的函数可以使代码更具声明性,更易于理解。\n例如,假设你需要计算多个数的乘积:\n1 2 3 4 5 6 function multiply(a, b, c, d) { return a * b * c * d; } // 调用时需要传入所有参数 console.log(multiply(2, 3, 4, 5)); // 输出: 120 通过预定义参数,可以更清晰地表达意图:\n1 2 3 4 5 6 const multiplyby2 = (b, c, d) =\u0026gt; multiply(2, b, c, d); const multiplyby2and3 = (c, d) =\u0026gt; multiplyby2(3, c, d); const multiplyby2and3and4 = (d) =\u0026gt; multiplyby2and3(4, d); // 只需要传入最后一个参数 console.log(multiplyby2and3and4(5)); // 输出: 120 3. 延迟执行\n预定义参数的函数可以延迟执行,直到所有参数都准备好。\n例如,假设你需要在某个条件满足时才执行函数:\n1 2 3 4 5 6 7 8 9 10 function sendrequest(url, data) { console.log(`sending request to ${url} with data: ${data}`); } // 预定义 url const sendtoapi = (data) =\u0026gt; sendrequest(\u0026#34;https://api.example.com\u0026#34;, data); // 延迟执行,直到 data 准备好 const data = { name: \u0026#34;john\u0026#34;, age: 30 }; sendtoapi(data); 总结 “可以创建预定义参数的函数,方便后续调用” 是柯里化的一个重要特性,它通过部分应用参数生成一个新的函数,具有以下优点:\n减少重复代码:避免在每次调用时重复传入相同的参数。 提高代码的可读性:使代码更具声明性,更易于理解。 延迟执行:可以延迟函数的执行,直到所有参数都准备好。 在实际开发中,预定义参数的函数可以显著简化代码,提升开发效率和代码质量。\n","date":"2024-12-12","permalink":"https://aituyaa.com/%E5%87%BD%E6%95%B0%E6%9F%AF%E9%87%8C%E5%8C%96/","summary":"\u003cp\u003e\u003cstrong\u003e函数柯里化(Currying)\u003c/strong\u003e 是一种将多参数函数转换为一系列单参数函数的技术。通过柯里化,可以将一个接受多个参数的函数分解为多个只接受一个参数的函数,每个函数返回一个新的函数,直到所有参数都被传递完毕。\u003c/p\u003e","title":"函数柯里化"},]
[{"content":"在 node.js 中,generator 是一种特殊的函数,它允许你通过 yield 关键字暂停和恢复函数的执行。generator 函数使用 function* 语法来定义,并且返回一个 generator 对象。这个 generator 对象可以通过 next() 方法来控制函数的执行流程。\n基础 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function* mygenerator() { yield 1; yield 2; yield 3; } const gen = mygenerator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true } // 也可以使用 for...of 循环迭代 generator 对象 // for (let res of gen) { // console.log(res); // } // 1 // 2 // 3 如上所示,关键点有:\nfunction* 语法: 定义一个 generator 函数。 yield 关键字: 暂停函数的执行,并返回一个值。 next() 方法: 恢复函数的执行,并返回一个对象,包含 value 和 done 两个属性。 value: 当前 yield 返回的值。 done: 表示 generator 函数是否已经执行完毕。 当执行到 done 为 true 时,这个generator对象就已经全部执行完毕,不要再继续调用 next() 了。我们还可以直接用 for...of 循环迭代 generator 对象,这种方式不需要我们自己判断 done 了。\n传递参数 🌟 你可以通过 next() 方法向 generator 函数传递参数,这些参数会被当作上一次 yield 表达式的返回值。\n1 2 3 4 5 6 7 8 9 10 11 function* mygenerator() { const x = yield 1; console.log(x); // 输出: 42 yield x; } const gen = mygenerator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next(42)); // { value: 42, done: false } console.log(gen.next()); // { value: undefined, done: true } 错误处理 generator 函数可以通过 try...catch 来捕获错误。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 function* mygenerator() { try { yield 1; yield 2; } catch (e) { console.log(\u0026#39;caught error:\u0026#39;, e); } } const gen = mygenerator(); console.log(gen.next()); // { value: 1, done: false } gen.throw(new error(\u0026#39;something went wrong\u0026#39;)); // 输出: caught error: error: something went wrong console.log(gen.next()); // { value: undefined, done: true } 与异步操作结合 generator 函数可以与异步操作结合使用,通常与 co 库或 async/await 一起使用。虽然 async/await 是更现代的解决方案,但在某些情况下,generator 仍然有用。\n1 2 3 4 5 6 7 8 9 10 11 const co = require(\u0026#39;co\u0026#39;); function* mygenerator() { const result1 = yield promise.resolve(1); const result2 = yield promise.resolve(2); return result1 + result2; } co(mygenerator).then(result =\u0026gt; { console.log(result); // 输出: 3 }); 总结 generator 是一种强大的工具,允许你以一种更灵活的方式控制函数的执行流程。虽然 async/await 已经成为处理异步操作的主流方式,但 generator 仍然在某些场景下非常有用,尤其是在需要手动控制执行流程的情况下。\n附录 co 库是什么 co 是一个基于 generator 的异步流程控制库,由 tj holowaychuk 开发。它允许你使用 generator 函数来编写看起来像同步代码的异步代码,从而简化异步编程。co 的核心思想是将 generator 函数与 promise 结合,自动处理异步操作的执行流程。\n你可以通过 npm 安装 co:\n1 npm install co 基本用法 co 接受一个 generator 函数作为参数,并返回一个 promise。co 会自动执行 generator 函数中的 yield 表达式,并将其结果包装为 promise。\n1 2 3 4 5 6 7 8 9 10 11 const co = require(\u0026#39;co\u0026#39;); co(function* () { const result1 = yield promise.resolve(1); const result2 = yield promise.resolve(2); return result1 + result2; }).then(result =\u0026gt; { console.log(result); // 输出: 3 }).catch(err =\u0026gt; { console.error(err); }); 支持的 yield 类型 co 支持多种类型的 yield 表达式:\npromise: co 会等待 promise 完成,并返回其结果。 thunk: 一个返回单个参数函数的函数(类似于回调函数)。 array: 并行执行多个 promise,返回一个包含所有结果的数组。 object: 并行执行多个 promise,返回一个包含所有结果的对象。 示例:并行执行\n1 2 3 4 5 6 7 8 9 10 11 co(function* () { const [result1, result2] = yield [ promise.resolve(1), promise.resolve(2) ]; return result1 + result2; }).then(result =\u0026gt; { console.log(result); // 输出: 3 }).catch(err =\u0026gt; { console.error(err); }); 示例:对象并行执行\n1 2 3 4 5 6 7 8 9 10 11 co(function* () { const results = yield { a: promise.resolve(1), b: promise.resolve(2) }; return results.a + results.b; }).then(result =\u0026gt; { console.log(result); // 输出: 3 }).catch(err =\u0026gt; { console.error(err); }); 错误处理 co 会自动捕获 generator 函数中的错误,并将其传递给 promise 的 catch 方法。\n1 2 3 4 5 6 7 8 co(function* () { const result = yield promise.reject(new error(\u0026#39;something went wrong\u0026#39;)); return result; }).then(result =\u0026gt; { console.log(result); }).catch(err =\u0026gt; { console.error(err.message); // 输出: something went wrong }); 与 async/await 的对比 co 的功能与 async/await 非常相似,但 async/await 是原生支持的语法,而 co 是基于 generator 的库。以下是两者的一个简单对比:\nco 示例\n1 2 3 4 5 6 7 8 9 10 11 const co = require(\u0026#39;co\u0026#39;); co(function* () { const result1 = yield promise.resolve(1); const result2 = yield promise.resolve(2); return result1 + result2; }).then(result =\u0026gt; { console.log(result); // 输出: 3 }).catch(err =\u0026gt; { console.error(err); }); async/await 示例\n1 2 3 4 5 6 7 8 9 10 11 async function myasyncfunction() { const result1 = await promise.resolve(1); const result2 = await promise.resolve(2); return result1 + result2; } myasyncfunction().then(result =\u0026gt; { console.log(result); // 输出: 3 }).catch(err =\u0026gt; { console.error(err); }); 总结 co 是一个非常强大的库,它通过 generator 和 promise 的结合,简化了异步编程的复杂性。虽然 async/await 已经成为现代 javascript 中处理异步操作的主流方式,但 co 仍然在某些场景下非常有用,尤其是在需要与旧代码或特定库兼容的情况下。\n","date":"2024-12-12","permalink":"https://aituyaa.com/%E7%94%9F%E6%88%90%E5%99%A8/","summary":"\u003cp\u003e在 Node.js 中,\u003ccode\u003egenerator\u003c/code\u003e 是一种特殊的函数,它允许你通过 \u003ccode\u003eyield\u003c/code\u003e 关键字暂停和恢复函数的执行。\u003ccode\u003egenerator\u003c/code\u003e 函数使用 \u003ccode\u003efunction*\u003c/code\u003e 语法来定义,并且返回一个 \u003ccode\u003eGenerator\u003c/code\u003e 对象。这个 \u003ccode\u003eGenerator\u003c/code\u003e 对象可以通过 \u003ccode\u003enext()\u003c/code\u003e 方法来控制函数的执行流程。\u003c/p\u003e","title":"生成器"},]
[{"content":"\u0026gt; 自然码双拼键位\n![[assets/pasted image 20241210160722.png]]\n版权声明:本文为博主原创文章,遵循 cc 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。\r本文链接:https://blog.csdn.net/liuchuo/article/details/53932679 摘要 本教程仅针对双拼中自然码方案的辅助码。\n在学习辅助码之前,你应该已经能够熟练使用双拼输入法。点击这里: 【双拼】双拼输入法入门指南 – 柳婼 の blog 有一份双拼输入法入门指南。\n使用辅助码需要您使用的输入法软件支持双拼且支持辅助码,我使用的是手心输入法 mac 版和 ios 版(手心输入法也支持 win 和安卓)。\n使用辅助码与不使用辅助码不冲突,也就是说可以在想用辅助码的时候添加一个辅助码字母,不想用的时候就正常当作双拼没有辅助码的时候打字。\n基本原理 汉语中同音字太多,用拼音打字重码率高,引入辅助码后,将汉字的偏旁部首发音的声母(比如:“像”的部首是“人”,“人”的声母是 r)作为拼音之后的补充部分,所以输入\u0026quot;xdr\u0026quot;(xd 是“像”的自然码双拼)就能自动筛选出符合声母是 r 的偏旁的字,“像”这个字也就自然出现在了候选词的第一个(前几个)。\n![[assets/v2-40cd9b8e733b3d342de514395a7ca7c0_r.jpg]]\n部件拆分原理 独体字 一般是部首汉字,如:“金木水火土辶皿马皮日月目衣耳”等。独体字全部看成部首,不能进一步拆分出部件,只能由笔画构成。\n自然码中的笔画码:\n①以横竖起笔的在 a 键上: “一丨亅レ乛フㄥ”\n②以点起笔的在 d 键上:“丶冫氵”【d 就是点的意思啦】\n③以撇起笔的在 p 键上:“丿彡”【p 就是撇的意思啦】\n举例:金【jnp,jn 是金的自然码双拼,辅助码 p 是金的第一笔撇 p】\n有明显部首的汉字 如 “极、版、码、程、想、 福、袋、鳌、游、洪、递”,辅助码就是部首的声母。(注意:部首以新华字典上的为标准,不是以从上到下从左到右的顺序看部首的,比如“架”的部首是“木”)\n举例:架【jwm,jw 是架的自然码双拼,辅助码 m 是木的声母 m】\n说明:如果一个字有明显两个部首,比如“杏”的部首可以是“木”和“口”,所以随意哪个部首当作辅助码都行,也就是输入【xym】或【xyk】两者皆可。\n不认识或不是整体字部件的汉字 如“录、芈、暨、 释、稽、躅、摭、谧、荔”,这类字的部首或部件可以用首笔画(尾部用末笔画)或能认识的汉字代替。 【比如 躅=足+虫 】\n基础辅助形码表 【a】一 丨 亅 レ 乛 フ ㄥ\n【b】 八 丷 卜 冖 宀 匕 比 白 贝 疒 鼻\n【c】 艹 卄 廾 廿 屮 卝 寸\n【d】丶 冫 氵 刀 刂 リ ㄍ ⺈ 丁 歹 癶\n【e】 二 儿 阝 耳 卩 \n【f】 扌 丰 反 方 风 父 缶 巿\n【g】 乚 ㄅ ㄋ 勹 弓 工 广 艮 戈 瓜 谷 革 骨 鬼 夬 罓\n【h】 灬 火 禾 户 虍 黑 乊 厷\n【i】 厂 川 巛 亍 车 虫 臣 辰 赤 齿 髟 豖\n【j】 几 九 己 巾 斤 钅 金 见 臼 角\n【k】 コ 凵 匚 冂 口 囗 丂\n【l】 力 六 立 龙 耒 卤 鹿\n【m】 木 门 毛 马 米 矛 母 皿 尨 麻 丏\n【n】 女 牛 牜 ⺧ 鸟\n【o】 日 曰 月 目\n【p】 ノ 彡 片 皮 疋 ⺪ 攴\n【q】 七 犭 犬 丌 欠 气 且\n【r】 亻 人 入 肉\n【s】 三 罒 巳 纟 糹 糸 厶 \n【t】 土 田\n【u】 水 手 食 飠饣示 礻山 石 尸 十 士 矢 殳 舌 身 豕 鼠\n【v】 隹 ⺮ 爫 爪 豸 止 至 舟\n【w】 文 亠 攵 夂 夊 ㄨ 王 韦 瓦\n【x】 彳 小 心 忄 血 彐 夕 习 西 辛\n【y】 乙 又 已 讠言 幺 尤 尢 冘 衣 衤羊 牙 业 由 用 页 酉 鱼 雨 羽 聿 乑 乂\n【z】 辶 廴 子 自 走 足 ⻊卆\n需要特殊记忆的部件 “日、月、曰、目”辅助码为圆圆的【o】\n“扌”辅助码为扶手的【f】\n“ 彳”辅助码为行人的【x】\n“一、丨、亅、乛”横竖折都是【a】\n“亠”文字头【d】\n“灬”火的变体【h】\n“艮”根【g】\n“肀”聿 yu【y】\n“耒”垒【l】——比如耕的双拼+辅助码是【ggl】\n“爿”片【p】\n“豕”适【u】\n“髟”長 chang 的 ch【i】\n“隹”锥 zhui 的 zh 也就是【v】\n“リ”刀的变体【d】\n“”艹的变体【c】\n“尢”尤的变体【y】\n“丌”齐的变体【q】\n“弋”弋读 yi 所以是【y】\n“厶”私 si 的【s】\n“ㄨ”叉 x 的【x】或者勿的【w】\n","date":"2024-12-10","permalink":"https://aituyaa.com/%E8%87%AA%E7%84%B6%E7%A0%81%E8%BE%85%E5%8A%A9%E7%A0%81%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/","summary":"\u003cp\u003e\u003ccode\u003e\u0026gt; 自然码双拼键位\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20241210160722.png]]\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。\r\n本文链接:https://blog.csdn.net/liuchuo/article/details/53932679\n\u003c/code\u003e\u003c/pre\u003e","title":"自然码辅助码入门教程"},]
[{"content":"\u0026gt; hello, java!\n![[assets/obsidian 调整透明度.png]]\njava 介于编译型语言和解释型语言之间。编译型?解释型?\n编译型 ➡️ 源码 → 机器码 → 运行机器码 解释型 ➡️ 源码 → 解释器 → 解释运行源码 java 呢? ➡️ 源码 → 字节码 → 虚拟机(jvm) → 加载执行字节码 \u0026gt; jdk › jre › jvm\n![[assets/snipaste_2024-12-03_10-17-36.png|350]]\n下面让我们先来看一些名词吧~\n一些名词 jvm(java virtual machine) 即 java 虚拟机,它是 java 运行的核心基础,提供了运行 java 字节码文件(.class 文件)的运行环境。\njava 程序在编译后会生成字节码,这些字节码并不能直接被操作系统识别和执行,而是需要依靠 jvm 来进行解释或者编译执行,相当于在不同操作系统和 java 程序之间搭建起了一座桥梁,使得 java 实现了 “一次编写,到处运行” 的跨平台特性。\njvm 的主要组成部分是什么?\n- 类加载器(class loader)» 负责加载字节码文件到 jvm 中 - 运行时数据区(runtime data areas) - 方法区(method area)» 线程共享,存储已被加载的类信息、常量、静态变量等 - 堆(heap)» 线程共享,存放对象实例以及数组等数据 - 虚拟机栈(jvm stacks)» 线程私有,存储局部变量表、操作数栈、动态链接、方法出口等信息 - 本地方法栈(native method stacks)» 执行非 java 语言编写的方法,如 jni 调用的 c 方法 - 程序计数器(program counter register)» 线程私有,记录当前线程执行到的字节码指令的位置 - 执行引擎(execution engine)» 负表执行加载到 jvm 中的字节码指令(解释执行、即时编译 jit) - 垃圾回收(garbage collection,gc)» jvm 具备自动的垃圾回收机制 jre(java runtime environment) ,即 java 运行时环境。它是运行 java 程序的最小环境需求,是 java 程序能够在计算机上运行的基础条件。\n它由 [[#jvm]] 和 java 核心类库组成。\n想要了解更多吗?请参考 📚 java api 中文文档 。\njdk(java development kit) ,即 java 开发工具包。它是 java 程序员进行开发的工具集合,不仅包含了 jre(java 运行时环境),还提供了一系列的开发工具,用于编写、编译和调试 java 程序。\n- jre(jvm + runtime library) - 编译器(javac):编译器会检查源代码的语法错误,并将其转换为字节码,以便 jvm 能够执行; - 调试器(jdb):允许开发人员在程序运行过程中暂停执行,检查变量的值、跟踪方法的调用等; - java 文档生成器(javadoc):用于从 java 源文件中的注释生成 api 文档; - 其他工具:jar(创建和管理 java 归档文件)、jps(查看 java 进程信息)…… :: 虽然多数时候这些都由 ide 帮助做了,但是了解一下其最本质的流程是非常有必要的。\njsr(java specification requests) 即 java 规范请求,它是指向 jcp(java community process)提出的对于 java 平台新增规范或者对现有规范进行改进的请求。\n提出请求 jsr(提供 ri 和 tck)➡️ jcp 审核与评估 ➡️ 制定规范 ➡️ 实现和推广 一个 jsr 规范发布时,为了让大家有个参考,还要同时发布一个“参考实现”(ri - reference implementation),以及一个“兼容性测试套件”(tck - technology compatibility kit)。\nri(reference implementation) - 参考实现,是一种对于 java 技术规范的完整实现示例。它是由 java 技术规范的制定者或者相关的官方组织提供的,用于展示如何按照规范来构建一个功能完整的软件组件或系统。\n例如,对于一个新的 java api 规范,ri 会展示如何正确地实现接口、处理异常、管理资源等,就像是一份详细的 “参考答案”。\ntck(technology compatibility kit) - 技术兼容性套件,是一套用于测试某个软件实现是否符合特定 java 技术规范的工具和文档集合。它包含了一系列的测试用例、测试脚本以及测试框架,用于检查被测试的软件在功能、接口、性能等各个方面是否与规范要求一致。\n例如,对于一个新的 java 图形处理 api 规范,tck 会有测试用例来检查 api 的各种方法是否按照规定的参数和返回值进行工作,图形渲染的结果是否符合预期等。\n","date":"2024-12-02","permalink":"https://aituyaa.com/java-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e\u003cstrong\u003e\u003ccode\u003e\u0026gt; Hello, Java!\u003c/code\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e![[assets/Obsidian 调整透明度.png]]\u003c/p\u003e","title":"java 那些事儿"},]
[{"content":"为什么会有这个“那些事儿”系列?\n因为许多时候其实是碎片化的学习,对内容深度并没有什么需求,但需要记录一些学习过程中遇到的问题及其解决方案。\n之前是全部记录在 [[百科]] 中,然后定期整理成专项的内容,但实际上有些知识就是碎片化的,把它们分散到不同的文章中不仅没有解决其“碎片化”的本质,反而让它们更加“分散”了。\n所以,每一个标签项中都创建一个“那些事儿”,用以折衷“过于分散”和“潜在集中化”的需求。可以这么认为,“那些事儿”中内容是分散的,但整体框架是集中的。\n相信我,没有什么知识管理体系是毕其功于一役的,都需要周期性的去整理。\n\u0026gt; 附一豆包 ai 生成的图片\n![[assets/23423424.png]]\n","date":"2024-12-02","permalink":"https://aituyaa.com/%E5%85%B3%E4%BA%8E%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF%E7%B3%BB%E5%88%97/","summary":"\u003cp\u003e\u003cstrong\u003e为什么会有这个“那些事儿”系列?\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e因为许多时候其实是碎片化的学习,对内容深度并没有什么需求,但需要记录一些学习过程中遇到的问题及其解决方案。\u003c/p\u003e\n\u003cp\u003e之前是全部记录在 [[百科]] 中,然后定期整理成专项的内容,但实际上有些知识就是碎片化的,把它们分散到不同的文章中不仅没有解决其“碎片化”的本质,反而让它们更加“分散”了。\u003c/p\u003e\n\u003cp\u003e所以,每一个标签项中都创建一个“那些事儿”,用以折衷“过于分散”和“潜在集中化”的需求。可以这么认为,“那些事儿”中内容是分散的,但整体框架是集中的。\u003c/p\u003e\n\u003cp\u003e相信我,没有什么知识管理体系是毕其功于一役的,都需要周期性的去整理。\u003c/p\u003e","title":"关于“那些事儿”系列"},]
[{"content":"你可以在这里找到完整的路线图 frontend developer roadmap ~ 注册了账号后,还可以同步进度,nice ~\n","date":"2024-11-26","permalink":"https://aituyaa.com/devmaps/frontend/","summary":"\u003cp\u003e你可以在这里找到完整的路线图 \u003ca href=\"https://roadmap.sh/frontend\"\u003eFrontend Developer Roadmap\u003c/a\u003e ~ 注册了账号后,还可以同步进度,Nice ~\u003c/p\u003e","title":"frontend"},]
[{"content":"哲学常被理解为 “爱智慧”,这一概念源于古希腊语 “philosophia”。它意味着对智慧的热爱与追求,并非简单地掌握某种具体知识,而是不断探索关乎世界、人生等根本性问题背后的深刻道理,试图站在更为宏观、抽象的层面去洞察万物。\n哲学是什么 哲学的定义 哲学是什么?\n哲学是[[#系统化]]的世界观和方法论。\n世界观,是人们对整个世界以及人与世界关系的总的看法和根本观点。它涉及到人们如何认识世界的本原、世界的状态等根本性问题。\n方法论,是人们认识世界和改造世界的根本方法的理论。它指导人们在面对各种具体问题时,应该遵循怎样的方式去思考、去行动,以实现预期的目标。\n世界观和方法论在哲学中是有机统一的整体。一定的世界观决定了与之对应的方法论,而方法论又反过来体现和影响着世界观。\n:: 哲学不仅提供世界观,还相应地蕴含着与之匹配的方法论。因为有什么样的世界观,往往就会引导出什么样的认识和改造世界的方法。例如,辩证唯物主义世界观强调世界是物质的,物质处于普遍联系和永恒发展之中,基于这样的世界观,相应的方法论就是要用联系的、发展的、全面的观点看问题,在分析社会现象时,不能孤立、静止、片面地看待,而是要看到不同事物之间的相互关联、事物自身的发展变化趋势等。马克思主义哲学就是将辩证唯物主义世界观和唯物辩证法方法论高度统一的典范,它指导人们正确地认识世界,更有效地去改造世界,比如运用矛盾分析法来分析社会经济发展中不同利益群体之间的矛盾关系,进而找到解决问题、推动发展的合理途径。\n哲学的基本问题 哲学的基本问题是什么?\n思维和存在(意识和物质)的关系的问题。\n哪些问题?\n1、思维和存在何者为第一性的问题?\n意识和物质,谁才是世界的本原(本体论),是划分唯物主义和唯心主义的唯一标准。\n2、思维和存在是否具有同一性的问题?\n意识能否正确认识物质(认识论),是划分可知论和不可知论的依据。\n马克思主义哲学是如何回答这个问题的?\n马克思主义哲学秉持唯物主义的立场,明确指出物质第一性,意识第二性,物质决定意识。\n马克思主义哲学属于可知论,坚定地认为思维和存在具有同一性,即意识能够正确认识物质,人类可以通过实践不断地认识世界和改造世界。\n马克思主义主哲学 结合上面两个章节,我们要问……\n马克思主义哲学是什么?\n马克思主义哲学是关于自然、社会和思维发展一般规律的科学(研究对象),是唯物论和辩证法的统一、唯物辩证的自然观和历史观的统一(理论构成),是无产阶级和广大人民群众认识世界和改造世界的强大思想武器(阶级属性与实践作用)。\n研究对象 从研究对象的角度,马克思主义哲学着眼于整个世界,涵盖了自然界、人类社会以及人类思维这三大领域,致力于揭示它们发展的一般规律。\n:: 它并非聚焦于个别、特殊的现象或局部规律,而是站在宏观、普遍的层面去探寻那些贯穿于万事万物发展之中的一般性规律。\n1、在自然界方面,它研究物质的存在形式、运动变化等规律,例如解释地球生态系统历经漫长岁月的演变过程遵循怎样的内在逻辑。\n2、在人类社会领域,剖析社会形态更替、社会基本矛盾运动以及不同阶级在历史中的作用等规律,像阐释资本主义社会向社会主义社会过渡的必然性根源。\n3、对于人类思维,探讨思维的形成、发展以及与客观世界相互作用的规律,比如分析人们如何通过实践将感性认识上升为理性认识等。\n马克思主义主哲学都有哪些规律呢?\n唯物辩证法的规律:(一)对立统一规律:也称为矛盾规律,是唯物辩证法的实质和核心。它揭示了事物内部对立双方的统一和斗争是事物普遍联系的根本内容,是事物发展的源泉和动力。例如,在资本主义社会中,资产阶级和无产阶级之间存在着不可调和的矛盾,这种矛盾推动了社会的发展和变革。(二)质量互变规律:事物的发展是由量变到质变,又由质变到新的量变的循环往复、不断前进的过程。量变是事物数量的增减和场所的变更,是一种渐进的、不显著的变化;质变则是事物根本性质的变化,是事物由一种质态向另一种质态的飞跃,是显著的变化。比如,因温度的量变导致水在固态、液态、气态间转化,在社会发展中,生产力的不断发展积累到一定程度,就会引起生产关系的质的变革 。(三)否定之否定规律:任何事物的发展都不是一帆风顺的,都要经历肯定、否定、否定之否定的过程。这一规律揭示了事物发展的方向和道路是曲折前进的。例如,在人类社会的发展过程中,原始公有制被私有制所否定,而私有制在发展到一定阶段后又会被社会主义公有制所否定,这种否定之否定并不是简单地回到原点,而是在更高层次上的发展和进步。\n:: 如是理解“对立统一规律(矛盾律)”是唯物辩证法的实质和核心呢?详见[[#如何理解“对立统一规律”]]\n社会发展规律:(一)社会基本矛盾运动规律:生产力与生产关系、经济基础与上层建筑之间的矛盾,是人类社会的基本矛盾。生产力决定生产关系,生产关系反作用于生产力;经济基础决定上层建筑,上层建筑反作用于经济基础。当生产关系适应生产力的发展时,会推动生产力的进步;反之,则会阻碍生产力的发展。这一矛盾运动推动着人类社会从低级向高级不断发展,是人类社会发展的根本动力。例如,资本主义社会的基本矛盾是生产的社会化与生产资料的资本主义私人占有之间的矛盾,这一矛盾导致了经济危机的周期性爆发,最终必然会被社会主义社会所取代。(二)阶级斗争规律:在阶级社会中,阶级斗争是社会发展的直接动力。不同阶级之间由于经济利益的根本对立,必然会产生阶级矛盾和阶级斗争。被压迫阶级为了争取自身的解放,会不断地进行斗争,推动社会形态的更替。如奴隶社会中奴隶与奴隶主的斗争、封建社会中农民与地主的斗争、资本主义社会中无产阶级与资产阶级的斗争等,都在一定程度上推动了社会的发展。(三)社会形态演进规律:马克思主义认为,人类社会的发展依次经历原始社会、奴隶社会、封建社会、资本主义社会、社会主义社会,并最终走向共产主义社会。这是一个由低级到高级、由简单到复杂的发展过程,每个社会形态都有其自身的特点和发展规律,前一种社会形态为后一种社会形态的产生创造条件,后一种社会形态则是在前一种社会形态的基础上发展而来的。\n认识发展规律:(一)实践与认识的辩证关系原理:实践是认识的基础,实践决定认识,实践是认识的来源、动力、目的和检验标准。认识对实践具有反作用,正确的认识能够指导实践取得成功,错误的认识则会导致实践的失败。例如,通过科学实验等实践活动,人们获得了对自然规律的认识,然后将这些认识应用于生产实践,推动了生产力的发展。(二)认识发展的辩证过程原理:认识的发展是一个从实践到认识、再从认识到实践的多次反复和无限发展的过程。人们对事物的认识往往不是一次就能完成的,而是需要经过由感性认识到理性认识、再由理性认识到实践的多次反复,才能不断地深化和完善对事物的认识。(三)真理发展律:真理是一个不断发展的过程,它既具有绝对性,又具有相对性。真理的绝对性是指真理的客观性和无限性,即任何真理都包含着不依赖于人和人类的客观内容,都是对客观事物及其规律的正确反映;真理的相对性是指真理的有条件性和有限性,即人们在一定条件下对客观事物及其规律的正确认识总是有限的。真理的绝对性和相对性是辩证统一的,它们相互依存、相互包含,并在一定条件下相互转化。人们对真理的认识是一个不断深化和扩展的过程,随着实践的发展和科学技术的进步,人们对真理的认识会越来越接近客观实际。\n理论构成 从理论构成角度,马克思主义哲学是唯物论和[[#辩证法|辩证法]] 的统一、唯物辩证的自然观和历史观的统一。\n1、唯物论与辩证法的统一:马克思主义哲学坚持世界的物质本原,即认为世界是物质的,物质是第一性的,意识是第二性的,意识是物质的反映,这体现了唯物论的基本立场。同时,它又融入了辩证法,强调物质世界是普遍联系和永恒发展的,事物的发展是由内部矛盾推动的,像用矛盾分析法去看待社会现象,认识到生产力与生产关系、经济基础与上层建筑之间既对立又统一的关系,正是这种矛盾运动推动着人类社会不断向前发展。这种唯物论和辩证法的有机结合,区别于以往旧哲学中要么只有唯物主义却缺乏辩证思维,要么只有辩证法却陷入唯心主义的情况,使其理论更加科学、完备。\n2、唯物辩证的自然观和历史观的统一:在自然观上,马克思主义哲学依据科学事实,认为自然界是客观存在且按照自身规律运动变化的物质世界,否定了那种将自然现象归结为超自然力量或精神主宰的观点。而在历史观方面,它创立了历史唯物主义,提出社会存在决定社会意识,社会基本矛盾是推动社会发展的根本动力,人民群众是历史的创造者等重要论断,把对人类社会历史发展的认识建立在唯物主义的基础之上,改变了以往历史观往往被唯心主义所主导的局面。这两个方面的统一,意味着马克思主义哲学对整个世界(包括自然世界和人类社会世界)有着连贯、一致且科学的理解与阐释。\n:: 什么是历史观?什么是历史唯物主义?详见[[#历史观及历史唯物主义]] 。\n阶级属性与实践作用 从阶级属性与实践作用角度,是无产阶级和广大人民群众认识世界和改造世界的强大思想武器。\n什么是阶级属性?哲学都具有阶级属性吗?哲学和阶级的关系是什么?详见[[#哲学和阶级]] 。\n1、无产阶级的思想武器:马克思主义哲学具有鲜明的阶级性,它站在无产阶级和广大人民群众的立场上,是为无产阶级争取解放、推翻资本主义剥削制度而服务的理论。与以往大多服务于统治阶级、为既有剥削秩序作辩护的哲学不同,它揭示了无产阶级被剥削压迫的根源以及社会发展的方向,鼓舞无产阶级团结起来,通过革命等实践活动去改变自身命运,建立公平、公正的社会主义和共产主义社会。\n2、认识和改造世界的工具:它强调[[#如何理解“实践”|实践]]的重要性,不仅是帮助人们去认识世界的本质、规律等,更是指导人们在实践中积极地改造世界。无产阶级及广大人民群众可以运用马克思主义哲学的原理,分析社会现实中的各种问题,制定合理的行动策略,比如在社会主义建设过程中,依据生产力与生产关系的辩证关系,适时调整生产关系以适应生产力的发展要求,从而推动社会不断进步,实现经济发展、社会和谐等目标。\n总的来说,马克思主义哲学以其科学性、革命性、实践性等特点,在人类哲学发展历程以及社会变革实践中都占据着极为重要的地位,是一种有着深刻内涵、广泛影响且不断与时俱进的科学哲学理论体系。\n结语 哲学是系统化的世界观和方法论,它的基本问题是“思维与存在的关系” - 第一性、同一性。\n马克思主义哲学认为物质是世界的本原,物质决定意识,意识只是一种特殊的物质,即“存在是第一(物质第一性)”。\n马克思主义哲学认为意识能够正确认识物质,即“思维与存在具备同一性”(物质和意识具备同一性)。\n简而言之,马克思主义哲学认为:“世界是物质的,物质是运动的,运动是有规律的,规律是可以认识的。”\n运动是物质的根本属性和存在方式,物质都是运动的,不存在绝对静止的物质。对立统一规律(矛盾律)是物质运动规律的核心,它强调事物内部对立双方的统一和斗争是其发展的根本动力。质变量变规律、否定之否定规律都是对立统一规律的不同范畴下的展现。\n哲学的根本目的在于认识世界、改造世界。认识最终要归于实践,实践是检验真理的唯一标准。\n社会是随着劳动实践的发展从自然界中分化而来的,社会存在决定社会意识。人类社会的基本矛盾(发展的原动力)是生产力与生产关系的矛盾。经济基础是指一个社会中占统治地位的生产关系各个方面的总和,它又决定上层建筑的形态,并被上层建筑所影响。\n附录 系统化 “系统化”这个词同时修饰“世界观”和“方法论”。这句话的意思是哲学提供了一种系统化的方式来理解和解释世界(世界观),并且提供了一套系统化的方法来探究和分析问题(方法论)。这里的“系统化”强调了哲学的组织性和逻辑性,意味着它不是零散或随意的观点和方法,而是一个内在一致、结构化的整体。\n辩证法 辩证法既是世界观又是方法论!\n辩证法具体是什么?\n辩证法是一种关于普遍联系和永恒发展的学说,是用联系的、发展的、全面的观点来考察世界的世界观和方法论。\n世界观层面,辩证法认为,(一)世界是一个有机的整体,万事万物之间存在着普遍的、客观的、多样的联系,没有孤立存在的事物。同时,世界处于永恒的运动、变化和发展之中,发展的本质是新事物对旧事物的取代,这种发展是有规律可循的,而不是随机和混乱的;(二)强调事物内部包含着相互对立又相互统一的矛盾双方,矛盾是事物发展的根本动力和源泉。矛盾的双方既相互排斥、相互斗争,又相互依存、相互转化。例如,在生产力与生产关系的矛盾中,生产力的发展会促使生产关系进行相应的变革,而生产关系的变革又会反过来影响生产力的进一步发展。\n方法论层面,辩证法提供了一系列认识和处理问题的方法,主要包括:\n归纳与演绎相结合:归纳是从个别到一般的推理方法,通过对大量具体事例的观察和分析,概括出一般性的结论;演绎则是从一般到个别的推理方法,以一般性的原理为前提,推导出关于个别事物的结论。二者相互补充,共同推动对事物的认识不断深化; 分析与综合相统一:分析是把事物分解为各个部分、方面、要素,分别加以研究的方法;综合则是把分解开来的各个部分、方面、要素结合起来,形成对事物整体认识的方法。分析是综合的基础,综合是分析的完成,两者相辅相成,帮助人们全面、系统地把握事物; 抽象与具体相联系:抽象是对事物本质的抽取和概括,形成概念、判断等思维形式;具体则是思维对事物多方面属性、特点和关系的综合把握,包括感性具体和理性具体两个阶段。认识是从感性具体到抽象,再从抽象到理性具体的循环往复过程,使人们对事物的理解不断丰富和深入; 逻辑与历史相一致:逻辑指的是思维的规律和形式,历史则是指客观事物的发展过程。要求思维的逻辑进程与客观事物的历史发展进程相符合,逻辑分析以历史的考察为基础,历史的考察以逻辑分析为指导,从而更准确地把握事物的本质和规律。 历史观及历史唯物主义 历史观是什么?\n历史观又称 “社会历史观”,是人们对社会历史的根本观点、总的看法,它涉及到对人类社会历史发展的诸多根本性问题的认识与理解。\n历史观的主要内容:(一)历史发展的动力问题:探讨是什么因素推动着人类社会历史不断向前发展。不同的历史观有着不同的见解,例如,唯心主义历史观可能将历史发展的动力归结为英雄人物的意志、上帝或某种神秘的精神力量等。像黑格尔认为历史是 “绝对精神” 自我发展、自我展现的过程,“绝对精神” 是推动历史演进的根本动力;而唯物主义历史观,特别是马克思主义的历史唯物主义则强调生产力与生产关系、经济基础与上层建筑的矛盾运动是人类社会发展的根本动力,生产力的发展促使生产关系不断变革,进而带动整个社会形态的更替与进步。(二) 历史发展的规律问题:思考人类社会历史发展是否存在客观规律以及是怎样的规律。有些历史观觉得历史发展是杂乱无章、充满偶然性的,没有什么固定规律可言;但马克思主义历史观指出人类社会的发展是有客观规律的,社会形态会依照原始社会、奴隶社会、封建社会、资本主义社会、社会主义社会并最终向共产主义社会这样的顺序依次更替,这种更替是由社会基本矛盾运动所决定的,是一个自然历史过程,并非由人的主观意志随意改变。(三)历史创造者问题(英雄史观和人民史观):关注究竟是谁在历史发展过程中起决定性作用,是少数英雄豪杰还是广大人民群众。英雄史观认为历史主要是由英雄人物创造的,他们凭借自身的智慧、才能和非凡的行动力,决定了历史的走向,例如尼采的 “超人哲学” 就带有一定的英雄史观色彩,强调少数强者对历史的主宰;而马克思主义的历史观鲜明地提出人民群众是历史的创造者,人民群众通过自己的物质生产活动创造了社会的物质财富,在社会实践中创造了社会的精神财富,并且是社会变革的决定力量,在历次重大的社会革命和历史变迁中,都是广大人民群众发挥了主力军的作用。\n历史观都有哪些主要类型呢?\n唯心主义历史观:(一)主观唯心主义历史观:把历史发展的原因归结于人的主观意识、思想动机等精神因素,特别是个别英雄人物的意志和愿望。比如,有的观点认为某个伟大的帝王凭借自己的雄才大略改变了整个国家的命运、推动了历史的进步,却忽视了背后更广泛的社会经济基础以及广大人民群众的力量。(二)客观唯心主义历史观:将历史的发展看作是由某种超自然、超人类的客观精神力量所支配的。像前面提到的黑格尔的 “绝对精神”,还有古代社会中把王朝兴衰归结为 “天命” 等观念,都属于此类,认为人类社会的历史进程是在这些神秘的、抽象的精神力量操控下完成的。\n:: 细想,所谓的“超精神”根本就是站不住脚的。一种力量,无论它再神秘、抽象,这种力量也是有主体的,那么这个主体也不过是另一种形式的“人”罢了。\n唯物主义历史观(主要是马克思主义历史观):它建立在对人类社会客观存在和发展规律的科学认识基础之上。坚持社会存在决定社会意识,从物质生产实践出发去解释历史,通过分析生产力与生产关系、经济基础与上层建筑的矛盾运动来揭示社会历史发展的规律,强调人民群众在历史创造中的主体地位,是一种科学、系统且具有革命性的历史观,对近现代社会科学以及人类认识自身历史发展都有着极为深远的影响。\n哲学和阶级 哲学都具有阶级属性吗?\n哲学并非在所有情况下都具有阶级性,不过在阶级社会中,哲学往往会呈现出阶级性。\n目前还存在非阶级社会吗?\n现在的社会形态并不都是阶级社会。下面我们来看一下阶级社会的界定及典型形态。\n阶级社会,是指以阶级对立为基础的社会形态,其特点是社会成员因对生产资料的占有关系不同等因素,分化为不同的阶级,不同阶级在经济、政治、社会地位等方面有着明显差异,并且存在着阶级矛盾与阶级斗争。\n典型的阶级社会形态有哪些?\n奴隶社会:奴隶主阶级占有生产资料(如土地、奴隶等),奴隶则被视为奴隶主的财产,没有任何人身自由,被迫从事繁重的体力劳动,奴隶主完全掌控着社会的政治权力和绝大部分财富,奴隶与奴隶主之间存在着尖锐的阶级对立与剥削关系。 封建社会:地主阶级占有大量土地等主要生产资料,农民阶级虽有一定人身自由,但要依附于地主的土地,通过租种地主土地并上缴地租来维持生计,地主凭借土地所有权对农民进行经济剥削,同时在政治上也处于统治地位,阶级矛盾主要围绕土地、赋税等问题展开。 资本主义社会:资产阶级掌握着工厂、机器设备等生产资料,无产阶级只能靠出卖自己的劳动力获取工资,以维持生活,资产阶级通过无偿占有无产阶级创造的剩余价值来实现资本积累,无产阶级与资产阶级之间存在着雇佣与被雇佣、剥削与被剥削的关系,阶级斗争围绕劳动权益、社会财富分配等方面激烈展开。 :: 当下社会是“房东阶级”和“资产阶级”的双重压迫~\n当今世界存在的非阶级社会形态呢?\n原始部落形态的残留情况:在世界一些极为偏远的地区,还存在着少量保持原始部落生活方式的群体,他们的社会形态更接近原始社会,还未形成明显的阶级分化。这些部落以渔猎、采集或简单农耕等方式维持生计,成员之间通常遵循平等、互助的原则,共同劳动、平均分配劳动成果,并没有因生产资料占有不同而划分出不同阶级,更侧重于应对自然环境、延续部落生存等问题。不过这种情况在当今全球化的大背景下已越来越少,且这些部落也在逐步与现代社会接轨并发生着诸多改变。 社会主义社会的性质转变:社会主义社会是向着无阶级社会过渡的社会形态。在社会主义国家,生产资料公有制为主体的经济制度逐步建立,全体人民成为生产资料的主人,从根本上消除了人剥削人的制度基础。虽然在社会主义初级阶段,由于多种所有制经济共同发展等原因,还存在着一定的阶层差异(如不同行业劳动者在收入、社会分工等方面有区别),但这种差异不同于阶级社会中的阶级对立,阶层之间的根本利益是一致的,都是致力于社会主义建设、推动国家发展以及实现共同富裕,并不存在剥削与被剥削的关系。例如,我国的工人、农民、知识分子以及其他各阶层群体,都在各自岗位上为国家的现代化建设贡献力量,大家共享发展成果,共同朝着实现中华民族伟大复兴的目标迈进。 :: 讽刺地是,我们将长期处于社会主义初级阶段,虽然这是由生产力发展水平决定的,但也难免让人有些沮丧。\n如何理解,社会主义社会中,全体人民成为生产资料的主人?\n在社会主义社会中,全体人民成为生产资料的主人,这是社会主义制度区别于以往剥削制度的核心特征之一,以下从多个方面来深入理解这一重要论断:\n生产资料所有制层面,(一)公有制主体地位的确立:社会主义社会以生产资料公有制为主体,包括国有经济、集体经济等多种形式。国有经济掌控着国家的关键命脉行业,像能源、交通、通信、金融等领域,这些生产资料归全体人民共同所有,由国家代表人民进行管理和运营。例如,国家电网作为国有企业,其电网设施等大量生产资料是为了保障全国人民的用电需求,通过合理的电网布局、电力调配,让每一个地区的人民都能用上电,体现了生产资料为全体人民服务的性质。集体经济则常见于农村地区,比如农村的集体土地、集体企业等,村里的每一位村民都是这些生产资料的主人,大家共同参与决策、共享收益,像一些农村通过集体土地流转发展特色农业产业,所得收益按照相应的分配机制分给全体村民,实现共同富裕的部分目标。(二)多种所有制共同发展的协同作用:虽然社会主义坚持公有制为主体,但同时也鼓励、支持和引导多种所有制经济共同发展,包括个体经济、私营经济、外资经济等。不过,这些非公有制经济是在公有制经济的主导和影响下发挥作用的,是对公有制经济的有益补充,并不会改变全体人民作为生产资料主人的根本地位。例如,个体商户经营的小店丰富了人们的消费选择,私营企业吸纳就业、创造税收,它们在社会主义市场经济中参与经济活动的同时,也要遵循社会主义制度下保障人民利益的相关法律法规,其发展也是为了更好地服务于全体人民,促进整个社会经济的繁荣,进而巩固全体人民对生产资料的掌控这一基础。\n:: 怕就怕“公器私用,禁之不绝”!\n经济权益保障层面,(一)参与生产经营决策的权利:全体人民作为生产资料的主人,有权利通过合法途径参与到生产经营的决策过程中。在国有企业中,职工代表大会就是职工参与企业管理决策的重要平台,职工们可以推选代表,就企业的发展规划、职工福利、劳动保护等重要事项发表意见、提出建议,这些建议能够影响企业的最终决策,充分体现了工人阶级作为生产资料主人参与管理的权利。在农村集体经济组织里,村民们通过村民大会等形式,对集体土地的承包、流转,集体企业的开办、经营等重大事务进行民主表决,保障每一位村民的权益,使大家切实感受到自己对生产资料的所有权以及相应的决策权。(二)公平分享经济发展成果的权益:社会主义社会致力于保障全体人民能够公平地分享经济发展带来的成果。随着国家经济的不断增长,通过税收调节、社会保障体系完善、再分配政策实施等一系列举措,让不同地区、不同行业、不同群体的人们都能受益。比如,国家通过财政转移支付,加大对中西部地区基础设施建设、教育、医疗等方面的投入,缩小地区间的发展差距;建立覆盖城乡的社会保障体系,包括养老保险、医疗保险等,让全体人民在年老、生病等情况下都能得到相应的保障,这种公平分享成果的模式彰显了全体人民作为生产资料主人的地位,确保经济发展是为了人民的幸福生活。\n:: 只能说制度是个好制度,就看执行不执行了。现实生活中,所谓的“参与生产经营决策”很多时候都沦为了空谈。\n政治与社会层面,(一)政治制度保障人民地位:社会主义国家建立了一整套民主的政治制度来保障全体人民对生产资料的主人地位。人民代表大会制度是我国的根本政治制度,人民选举代表组成各级人民代表大会,这些代表来自各行各业,他们将人民的意愿和诉求带到国家权力机关,参与制定法律法规、决定国家重大事务,从政治层面确保生产资料的使用、管理等都符合人民的利益和意志。此外,还有基层民主制度,像城市的居民委员会、农村的村民委员会,让老百姓在基层社会事务管理中充分发挥主人作用,进一步夯实了全体人民作为生产资料主人的根基。(二)社会观念与价值认同:在社会主义社会的文化和价值体系中,全体人民作为生产资料主人的观念深入人心。从学校教育到社会宣传,都在强调人民当家作主、共同创造美好生活的理念,这使得人们在思想意识上认同自己的主人地位,积极投身于社会主义建设事业。例如,在各行各业涌现出大量的劳动模范、先进工作者,他们以主人公的姿态努力工作,为国家的发展贡献力量,同时也带动身边的人共同维护和巩固全体人民对生产资料的掌控,促进社会主义经济不断向前发展。\n:: 我是谁?又是谁代表了我?我什么时候选择了这个代表人?\n总之,在社会主义社会中,全体人民成为生产资料的主人体现在经济、政治、社会等多个维度,是一种实实在在的权利保障和价值体现,也是社会主义制度优越性的重要彰显,推动着社会主义社会不断朝着实现共同富裕、人民幸福的目标迈进。\n哲学为什么在阶级社会中具有阶级属性?\n阶级性的根源:哲学作为一种社会意识形式,是由社会存在所决定的。在阶级社会中,不同的阶级处于不同的社会经济地位,有着不同的利益诉求、生活方式以及对世界的认知角度。这些差异会反映在他们所秉持的哲学思想上,使得哲学不可避免地带有阶级的烙印。\n:: 哲学的本质是一种社会意识形式。\n不同阶级有不同的哲学体现(社会意识体现):(一)统治阶级的哲学:往往会倾向于维护现有的社会秩序和统治地位,为既得利益进行辩护。例如,在封建统治时期,一些官方推崇的哲学思想强调等级制度的合理性、君权神授等观念,通过论证封建统治结构的天然正当性,让被统治阶级接受并安于现状,以此巩固统治阶级的权力和利益。(二)被统治阶级的哲学:特别是那些代表先进生产力发展要求但处于被压迫地位的阶级,其哲学思想通常带有反抗性和批判性,旨在揭示社会的不合理之处,寻求改变自身命运、打破现有剥削秩序的途径。\n哲学和阶级的关系?\n阶级斗争对哲学发展的影响:(一)推动哲学思想的变革:激烈的阶级斗争往往是社会矛盾激化的表现,它会促使人们去深入思考社会的本质、现状以及未来走向等根本性问题,进而引发哲学思想的更新与变革。例如,在资本主义社会发展过程中,无产阶级与资产阶级的矛盾日益尖锐,无产阶级为了争取自身解放,迫切需要科学的理论指导,这就推动了马克思和恩格斯站在无产阶级立场上,批判继承以往的优秀思想成果,创立了马克思主义哲学,为无产阶级斗争提供了强大的思想武器。(二)决定哲学思想的传播与兴衰:阶级斗争的形势会影响不同哲学思想在社会中的传播范围和接受程度。如果某一阶级在斗争中处于上升期,力量不断壮大,那么与之相关联、代表其利益的哲学思想往往也会得到更广泛的传播和发展;相反,当一个阶级走向衰落,其秉持的哲学观念也会随之逐渐失去影响力。比如,资产阶级在反对封建统治的斗争中,启蒙运动时期的资产阶级哲学思想(强调理性、自由、平等、民主等)得到了广泛传播,有力地冲击了封建神学等旧思想体系,因为这些思想契合了当时资产阶级争取政治权力、发展资本主义经济的阶级斗争需求。\n哲学对阶级斗争的反作用:(一)提供理论指导:哲学能够为阶级斗争提供世界观和方法论层面的指导。不同阶级依据自己的哲学思想来认识世界、分析社会矛盾,并制定相应的斗争策略。马克思主义哲学就是无产阶级认识世界和改造世界的锐利武器,它揭示了社会发展的客观规律,让无产阶级明白资本主义制度的内在矛盾以及自身肩负的历史使命,指导无产阶级通过革命等方式去推翻资本主义统治,建立社会主义社会,使阶级斗争沿着科学、合理的方向开展。(二)凝聚阶级力量:某种哲学思想一旦在一个阶级内部得到广泛认同,就能起到凝聚人心、统一思想的作用,使整个阶级形成强大的斗争合力。例如,在民族解放运动中,那些倡导民族独立、人民解放的哲学和思想理念,激发了广大被压迫人民的爱国热情和斗争意志,促使大家团结起来,为摆脱殖民统治等进行坚决的斗争。\n总之,哲学和阶级斗争相互影响、相互作用。阶级斗争是哲学产生阶级性以及不断发展变化的重要社会背景,而哲学又反过来为阶级斗争提供思想引导和精神动力,二者在阶级社会的历史演进过程中都扮演着极为重要的角色。\n如何理解“实践” 实践是什么?\n实践,是人类能动地改造世界的客观物质活动,它有着多方面的特点和表现形式。比如人们进行生产劳动,工人在工厂操作机器制造产品、农民在田间耕种收获农作物,这都是实实在在的实践活动;科学家在实验室开展各种实验,探索自然规律,同样属于实践;还有人们参与社会交往、文化艺术创作等诸多能够对客观世界产生实际影响、带有主观能动性的活动,都可称之为实践。\n它强调的是主体(人)通过一定的行为、活动去作用于客体(外部世界),从而达成某种改变或获得相应成果的客观过程。\n实践和方法论有什么关系?\n实践不完全属于方法论,但与方法论有着紧密且复杂的关系。\n实践与方法论的联系:(一)方法论对实践的指导作用体现联系:方法论是人们认识世界和改造世界的根本方法的理论,它为人们的实践活动提供思路、原则和具体方式等方面的指导。例如,唯物辩证法作为一种科学的方法论,其中分析与综合相统一的方法,指导人们在研究复杂的社会现象时,先把现象分解成各个部分、方面进行分析(比如分析社会经济发展情况时,分别考察不同产业、不同地区的发展状况),然后再综合起来把握整体特征和规律,从而更好地开展相应的实践活动,像制定更科学合理的经济发展政策等。从这个角度看,实践的有效开展往往依赖于方法论的正确指引,二者存在紧密关联。(二)实践是检验方法论正确与否的途径体现联系:实践不仅受方法论指导,同时也是检验方法论是否科学、有效的重要手段。一种方法论是否合理,不能仅仅依靠理论上的自圆其说,最终要放到实践中去验证。例如,某种管理方法(属于方法论范畴)声称可以提高企业的运营效率,只有将其应用到企业的实际管理实践中,观察是否真的能带来诸如生产效率提升、成本降低、员工满意度提高等积极效果,才能判断该方法论的优劣。所以,实践与方法论之间存在着相互验证、相互影响的关系。\n实践与方法论的区别:(一)概念侧重不同:实践侧重于描述人类实际进行的改造世界的客观活动本身,重点在行为以及行为产生的客观结果上;而方法论更强调的是一种理论性的、关于认识和改造世界方法的归纳、总结与阐释,是一种思维层面的指导原则和方式。比如工人在生产线上制造汽车是实践,而指导工人如何合理安排生产流程、提高生产效率的诸如精益生产等理论方法才是方法论层面的内容。(二)存在形式不同:实践是以实实在在的活动形式存在的,是看得见、摸得着、能直接产生实际影响的;方法论通常是以思想、理论、学说等抽象形式呈现出来,需要人们去学习、理解,然后运用到实践当中。例如,科学实验实践就是科研人员在实验室里操作仪器、观察记录数据等具体行为;而实验设计的逻辑、数据分析的方法等这些方法论内容,则是以知识、规则的形式存在于学术著作、科研教程等载体之中。\n综上所述,实践和方法论相互联系又相互区别,不能简单地将实践归为方法论,但二者在人类认识世界和改造世界的过程中协同发挥着极为重要的作用。\n如何理解“对立统一规律” 对立统一规律是唯物辩证法的实质与核心。\n1.揭示了事物发展的内在动力\n对立统一规律揭示了事物内部对立双方的统一和斗争是事物普遍联系的根本内容,也是事物发展的源泉和动力。任何事物都包含着相互对立又相互统一的两个方面,如生产力与生产关系、经济基础与上层建筑等,它们之间的矛盾运动推动着社会的发展。\n2.是其他规律和范畴的基础\n1、与质量互变规律的关系:量变和质变是事物发展的两种状态,量变是事物数量的增减和场所的变更,质变是事物根本性质的变化。而量变和质变的关系,本质上是对立统一的关系。量变是质变的必要准备,质变是量变的必然结果,量变和质变相互渗透、相互转化。例如,因温度的量变导致水在固态、液态、气态间转化,在社会发展中,生产力的不断发展积累到一定程度,就会引起生产关系的质的变革 。\n2、与否定之否定规律的关系:否定之否定规律揭示了事物发展的方向和道路是曲折前进的,而这一过程的根源也在于事物内部的矛盾运动。任何事物的发展都要经历肯定、否定、否定之否定的过程,在这个过程中,矛盾双方既对立又统一,推动着事物不断向前发展。例如,在人类社会的发展过程中,原始公有制被私有制所否定,而私有制在发展到一定阶段后又会被社会主义公有制所否定,这种否定之否定并不是简单地回到原点,而是在更高层次上的发展和进步 。\n3、与唯物辩证法基本范畴的关系:唯物辩证法的一系列基本范畴,如本质与现象、内容与形式、原因与结果、必然性与偶然性等,也都是对立统一关系的具体体现。例如,本质和现象是事物的两个不同方面,本质决定现象,现象反映本质,但二者又存在着差异和对立;内容决定形式,形式反作用于内容,二者相互依存又相互制约。\n3.区分辩证法和形而上学的关键\n辩证法和形而上学是两种根本对立的发展观,二者最根本的区别在于是否承认对立统一规律。 形而上学发展观认为发展就是单纯的数量意义上的增加或减少,并把事物发展的动力归结为来自事物自身之外某种它力的推动;而唯物辩证法的发展观则强调发展绝不是一种毫无实质性变化的简单重复,而是一种更高的重复,事物发展的动力是事物自身所蕴含的矛盾推动的结果。\n4.提供根本的方法论\n对立统一规律的方法论呈现就是矛盾分析法,它要求我们敢于承认矛盾、分析矛盾、解决矛盾,这是我们运用辩证法去认识问题和解决问题的根本方法。例如,马克思在《资本论》中运用矛盾分析法分析了商品的二重性,指出其最终会发展为对立和矛盾,还分析了商品与货币的关系,阐明了商品交换如何导致二者的内在紧张或对立关系。\n马克思主义都有哪些范畴 范畴是什么?\n范畴是一个哲学概念,通常指的是人类思维对客观事物的本质属性和普遍联系的概括与反映,是一些最基本、最普遍的概念,人们借助这些概念来认识、把握和描述世界以及各类现象之间的关系。简单来说,范畴就是把具有共同特征或处于同一类关系中的事物、现象等进行归纳提炼后形成的抽象概念。\n马克思主义哲学的范畴有哪些?\n马克思主义哲学的范畴是对马克思主义哲学中基本概念和重要关系的概括,主要包括以下几个方面。\n一、唯物论范畴\n物质:物质是不依赖于人的意识,并能为人的意识所反映的客观实在。它是世界的本原,是构成宇宙间一切事物和现象的基础,具有客观实在性、可知性等特性,如山川河流、日月星辰等具体的物质形态,以及磁场、电场等看不见摸不着但客观存在的物质现象。\n意识:意识是物质世界长期发展的产物,是人脑的机能和属性,是物质世界的主观映象。意识具有主观性、能动性等特点,它能够能动地认识世界和改造世界,例如人们对客观事物的感觉、思维、情感、意志等都是意识的表现形式。\n运动:运动是物质的根本属性和存在方式,世界上的一切事物都处于运动变化之中,没有不运动的物质,也没有离开物质的运动。运动包括宇宙间的一切变化和过程,如物理运动、化学运动、生命运动、社会运动等。\n规律:规律是事物运动过程中固有的本质的、必然的、稳定的联系。规律是客观的,不以人的意志为转移,既不能被创造,也不能被消灭,但人们可以认识和利用规律,例如牛顿发现的万有引力定律,就是自然界的一种客观规律。\n:: 本质的、必然的、稳定的,可以认识和利用。\n二、辩证法范畴\n:: 辩证法讲的就是联系和发展!在联系中对立统一,在发展中质量互变,否定之否定。\n对立统一:即矛盾,是指事物内部或事物之间既对立又统一的关系。对立性表现为矛盾双方相互排斥、相互分离的趋势;统一性表现为矛盾双方相互依存、相互贯通,并在一定条件下相互转化。例如,在资本主义社会中,资产阶级和无产阶级之间存在着剥削与被剥削的对立关系,但同时二者又相互依存,共同构成了资本主义社会的生产关系。\n:: 相互排斥,相互分离(你是你,我是我,部分),相互依存,相互贯通,相互转化(整体)。\n质量互变:任何事物都是质和量的统一体,量变和质变是事物发展变化的两种基本状态或形式。量变是事物数量的增减和场所的变更,是一种渐进的、不显著的变化;质变是事物根本性质的变化,是事物由一种质态向另一种质态的飞跃,是显著的变化。量变是质变的必要准备,质变是量变的必然结果,量变和质变相互渗透、相互转化。例如,因温度的量变导致水在固态、液态、气态间转化,在社会发展中,生产力的不断发展积累到一定程度,就会引起生产关系的质的变革。\n:: 任何事物都是质和量的统一体!\n否定之否定:事物的发展是一个由肯定到否定,再到否定之否定的过程。肯定是事物保持自身存在的方面,否定是事物促使自身灭亡的方面,否定之否定则是在更高层次上对事物的肯定。这一过程体现了事物发展的前进性与曲折性的统一,表明事物的发展不是直线式前进,而是螺旋式上升的。例如,在人类社会的发展过程中,原始公有制被私有制所否定,而私有制在发展到一定阶段后又会被社会主义公有制所否定,这种否定之否定并不是简单地回到原点,而是在更高层次上的发展和进步。\n本质和现象:本质(根本和内在)是事物的根本性质和内在联系,是同类现象中一般的、共同的东西;现象(表面)是事物的外部联系和表面特征,是本质的外在表现。本质决定现象,现象反映本质,但现象比本质丰富、生动,本质比现象深刻、稳定。例如,苹果落地这一现象背后的本质是万有引力的作用。\n内容和形式:内容(要素)是指构成事物的一切要素的总和,是事物存在的基础;形式(要素的组织结构和表现方式)是指把内容诸要素统一起来的结构或表现内容的方式。内容决定形式,形式反作用于内容,当形式适合内容时,会促进内容的发展,反之则会阻碍内容的发展。例如,一部文学作品的主题、情节、人物等是内容,而其体裁、结构、语言表达等则是形式。\n原因和结果:原因是引起某种现象的现象,结果是被某种现象所引起的现象。二者相互依存、相互作用,在一定条件下相互转化。因果联系具有普遍性、客观性和复杂性等特点,正确把握因果关系,有助于人们总结经验教训,预见事物的发展趋势。例如,因为下雨,所以地面湿了,下雨是原因,地面湿是结果。\n必然性和偶然性:必然性(趋势)是指事物发展过程中确定不移的趋势,是由事物的根本矛盾决定的;偶然性是指事物发展过程中不确定的趋势,是由事物的非根本矛盾和外部条件造成的。必然性通过大量的偶然性表现出来,并为自己开辟道路;偶然性背后隐藏着必然性,受必然性的支配。例如,抛一枚硬币,每次出现正面或反面是偶然的,但当抛的次数足够多时,正面和反面出现的次数大致相等,这又体现了一种必然性。\n:: 这个趋势是必然的,包含着大量必然的偶然。偶然吗?这种偶然是必然的。\n可能性和现实性:可能性是指事物发展过程中潜在的、尚未实现的趋势;现实性是指已经实现了的可能性,是客观事物和现象的实际存在性。可能性和现实性相互依存、相互转化,在一定条件下,可能性会转化为现实性。例如,一个学生努力学习,就有取得好成绩的可能性,当他通过考试取得了好成绩,这种可能性就转化为了现实性。\n三、认识论范畴\n实践:实践是人类能动地改造世界的客观物质性活动,具有直接现实性、自觉能动性和社会历史性等基本特征。实践是认识的基础,是认识的来源、动力、目的和检验标准,它在认识过程中起着决定性的作用。\n认识:认识是主体在实践基础上对客体的能动反映,包括感性认识和理性认识两个阶段。感性认识是认识的初级阶段(现象和形式),是人们通过感觉器官对事物的表面现象和外部联系的认识,具有直接性、具体性的特点;理性认识是认识的高级阶段(本质和内容),是人们借助抽象思维对事物的本质和内部联系的认识,具有间接性、抽象性的特点。认识的根本任务就是经过感性认识上升到理性认识,透过现象抓住事物的本质和规律。\n真理:真理是标志主观与客观相符合的哲学范畴,是人们对客观事物及其规律的正确反映。真理具有客观性、绝对性和相对性等特性,真理的客观性是指真理的内容是客观的,检验真理的标准——实践也是客观的;真理的绝对性是指真理的无条件性、无限性,任何真理都含着不依赖于人和人类的客观内容,都是对客观事物及其规律的正确反映;真理的相对性是指真理的有条件性、有限性,人们在一定条件下对客观事物及其规律的正确认识总是有限的。\n:: 思维和存在具有“同一性”,即意识都能正确认识物质。如何认识?通过实践。认识是主体在实践(人类能动地改造世界的客观物质性活动)基础上对客体的能动反映。通过不断地实践,使得客体之于主体的主观映象与客观实在趋近于符合,这就是探求真理的过程。\n四、历史观范畴\n社会存在:社会存在是指社会生活的物质方面,主要包括地理环境、人口因素和物质资料的生产方式。其中,物质资料的生产方式是社会存在的核心内容,它决定着社会的性质和面貌,是人类社会存在和发展的基础。\n社会意识:社会意识是指社会生活的精神方面,是社会存在的反映,包括政治、法律、思想、道德、艺术、宗教、哲学等各种意识形式。社会意识具有相对独立性,它对社会存在具有能动的反作用,先进的社会意识能够促进社会的发展,落后的社会意识则会阻碍社会的发展。\n生产力:[[#生产力到底是什么|生产力]] 是人类在生产实践中形成的改造和影响自然以使其适合社会需要的物质力量,包括劳动者、劳动资料和劳动对象等基本要素。生产力是社会发展的最终决定力量,它决定着生产关系的性质和变革,进而推动整个社会的发展。\n生产关系:生产关系是人们在物质生产过程中形成的不以人的意志为转移的经济关系,包括生产资料所有制关系、生产中人与人的关系和产品分配关系。生产关系是社会关系中最基本的关系,它对生产力具有反作用,当生产关系适应生产力发展状况时,会推动生产力的发展;反之,则会阻碍生产力的发展。\n经济基础:经济基础是指由社会一定发展阶段的生产力所决定的生产关系的总和,是构成一定社会的基础或骨骼系统。经济基础决定上层建筑,它的性质和变化决定着上层建筑的性质和变化。\n上层建筑:上层建筑是建立在一定经济基础之上的意识形态以及相应的制度、组织和设施。上层建筑包括政治上层建筑和思想上层建筑两个部分,政治上层建筑主要包括国家政权、政治法律制度等,思想上层建筑主要包括政治、法律、思想、道德、艺术、宗教、哲学等社会意识形式。上层建筑对经济基础具有反作用,它适应经济基础时会维护和促进经济基础的巩固和发展,反之则会破坏经济基础。\n阶级:阶级是一个历史范畴,是在一定社会经济结构中处于不同地位的社会集团,由于对生产资料的关系不同,其中一个集团能够占有另一个集团的劳动。阶级斗争是阶级社会发展的直接动力,不同阶级之间的矛盾和斗争推动着社会形态的更替。\n国家:国家是阶级矛盾不可调和的产物和表现,是一个阶级统治另一个阶级的工具。 国家具有政治统治和社会管理的职能,其本质是经济上占统治地位的阶级进行阶级统治的暴力机构。\n社会革命:社会革命是指社会形态的根本变革,是用新的社会制度代替旧的社会制度的过程。社会革命的根源在于社会基本矛盾的尖锐化,其根本问题是国家政权问题,它是阶级斗争的最高形式,能够推动社会历史的发展和进步。\n社会改革:社会改革是在一定社会制度下,为了解决生产关系不适应生产力、上层建筑不适应经济基础的某些部分或环节,使该社会制度得到自我完善或持续存在与发展,而对社会体制进行的改善与革新。社会改革是推动社会发展的重要动力之一。\n人民群众:人民群众是历史的创造者,是社会物质财富和精神财富的创造者,是社会变革的决定力量。人民群众的实践活动是推动社会历史发展的根本动力,他们在创造历史的过程中起着主体作用。\n生产力到底是什么 生产力到底是什么呢?\n生产力是人类在生产实践中形成的改造和影响自然以使其适合社会需要的物质力量,它体现了人与自然之间的关系,对于社会的存在和发展起着基础性、决定性作用。\n它包括劳动者、劳动资料和劳动对象等基本要素。\n生产力的构造要素(内容)是什么?\n劳动者:劳动者是生产力中起主导作用的要素。他们具有一定的生产经验、劳动技能以及知识水平,通过运用自身的体力和智力,操作劳动工具,作用于劳动对象,从而实现生产过程。例如,技艺精湛的工匠能利用专业工具打造出精美的手工艺品,经验丰富的农民懂得如何根据时节、土壤条件等种植并收获农作物,科研人员凭借专业知识研发新的技术、产品等,这些都是劳动者凭借自身能力参与生产活动的体现。而且随着时代的发展,劳动者素质尤其是智力因素、科技素养等在生产力发展中的作用愈发凸显,高素质的劳动者往往能更高效地推动生产进步。\n劳动资料:劳动资料也称劳动手段,是人们在劳动过程中用以改变或影响劳动对象的一切物质资料和物质条件,其中最主要的是生产工具。生产工具的先进程度是衡量生产力发展水平的重要标志。从原始社会的石器工具,到封建社会的铁制农具,再到现代工业社会的自动化生产线、智能化机器人等,生产工具的不断革新极大地提高了人类改造自然的能力,带动着生产力的持续跃升。除了生产工具,劳动资料还包括生产过程中的基础设施,像厂房、道路、运河等,它们为生产活动的顺利开展提供了必要的物质基础和外部条件。\n:: 表述成劳动手段就比较容易让人理解,生产工具、基础设施,属于“君子假于物”的“物”。\n劳动对象:劳动对象是指人们在劳动过程中对之施加劳动作用的一切物质资料,它可以分为两类。一类是天然存在的劳动对象,例如未经开垦的土地、原始森林里的树木、地下的矿产资源等,这些是自然界本身就有的,人类通过劳动将其纳入生产过程,使其成为满足社会需要的产品来源;另一类是经过人类加工过的劳动对象,也就是常说的原材料,比如钢铁厂炼钢用的铁矿石,纺织厂织布用的棉花等,经过进一步加工后成为各种工业制成品或生活用品,劳动对象的范围和种类随着生产力的发展也在不断拓展,为生产活动提供了更丰富的物质内容。\n生产力的性质和特点是什么?\n客观性:生产力是一种客观的物质力量,它的存在和发展不以人的主观意志为转移。劳动者作为生产力的主体,本身就是客观存在的人,有着实实在在的体力、智力状况;劳动资料和劳动对象更是具体的物质实体,无论是古代的手工工具还是现代的大型机械设备,以及各种自然资源等,都是可感知、可触摸的客观事物。而且生产力的发展也是遵循客观规律的,是在人类长期的生产实践过程中,基于对自然规律的认识和把握逐步提升的,不是人们随意想象就能改变的。\n社会性:生产力不是孤立个体的活动能力,而是在一定的社会关系下形成和发展起来的。在社会生产中,人们需要分工协作,不同劳动者从事不同环节的工作,共同完成整个生产过程,像现代化工厂里流水线的各个工位上的工人相互配合,才能高效地生产出产品;而且生产力的发展也受到社会经济制度、科学技术水平、文化教育等诸多社会因素的影响,比如一个国家重视科技创新、加大对教育的投入,培养出大量高素质人才,往往就能推动本国生产力更好地发展。\n:: 如何提高生产力呢?从其构成的三个要素着手:(一)劳动者,提高劳动者技能和素质;(二)劳动手段,改进生产工具、优化生产方式;(三)劳动对象,加深认识。\n历史性:生产力是随着历史的发展不断变化的,不同历史时期有着不同的生产力水平和表现形式。原始社会生产力极其低下,人们主要依靠简单的石器工具,通过集体狩猎、采集等方式获取生存资料;到了资本主义社会,随着机器大工业的兴起,生产力得到了前所未有的迅猛发展,能够大规模生产出种类繁多的商品;如今,在信息技术、生物技术、新能源技术等高科技推动下,生产力又迈向了新的高度,呈现出智能化、绿色化等新特点,生产力的这种历史性演变反映了人类改造自然能力的不断进步。\n生产力的重要作用是什么?\n决定生产关系:生产力的状况决定着生产关系的性质和形式。有什么样的生产力,就会有与之相适应的生产关系。比如,在以手工劳动为基础的较低生产力水平下,封建的生产关系(地主占有土地,农民租种土地并缴纳地租)与之相适应;而当生产力发展到机器大工业阶段,资本主义的生产关系(资产阶级占有生产资料,无产阶级出卖劳动力)就逐渐形成并发展起来,以满足生产力进一步发展的要求,生产力的发展变化是推动生产关系变革的根本动力。\n推动社会发展:生产力是社会发展的最终决定力量,整个社会形态的更替以及社会的进步都离不开生产力的发展。从原始社会向奴隶社会、封建社会、资本主义社会,再到社会主义社会的演进,背后的根本原因就是生产力在不断突破旧有的束缚,持续向前发展,带动生产关系以及上层建筑等方面相应变革,从而实现社会从低级向高级的逐步发展,一个国家或地区生产力越发达,往往其社会发展程度也就越高,人们的生活水平等各方面也会更优越。\n总之,生产力是人类社会存在和发展的核心要素,它的不断发展塑造着人类社会的面貌,推动着人类不断迈向更高的文明阶段。\n","date":"2024-11-21","permalink":"https://aituyaa.com/%E5%93%B2%E5%AD%A6%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e哲学常被理解为 “爱智慧”,这一概念源于古希腊语 “philosophia”。它意味着对智慧的热爱与追求,并非简单地掌握某种具体知识,而是不断探索关乎世界、人生等根本性问题背后的深刻道理,试图站在更为宏观、抽象的层面去洞察万物。\u003c/p\u003e","title":"哲学那些事儿"},]
[{"content":"论认识和实践的关系——知和行的关系。(写于 1937.7)\n在中国共产党内,曾经有一部分教条主义的同志长期拒绝中国革命的经验,否认“马克思主义不是教条而是行动的指南”这个真理,而只生吞活剥马克思主义书籍中的只言片语,去吓唬人们。还有另一部分经验主义的同志长期拘守于自身的片断经验,不了解理论对于革命实践的重要性,看不见革命的全局,虽然也是辛苦地——但却是盲目地在工作。这两类同志的错误思想,特别是教条主义思想,曾经在一九三一年至一九三四年使得中国革命受了极大的损失,而教条主义者却是披着马克思主义的外衣迷惑了广大的同志。毛泽东的《实践论》,是为着用马克思主义的认识论观点去揭露党内的教条主义和经验主义——特别是教条主义这些主观主义的错误而写的。因为重点是揭露看轻实践的教条主义这种主观主义,故题为《实践论》。毛泽东曾以这篇论文的观点在延安的抗日军事政治大学作过讲演。\n:: 教条主义❌,经验主义❌。\n马克思以前的唯物论,离开人的社会性,离开人的历史发展,去观察认识问题,因此不能了解认识对社会实践的依赖关系,即认识对生产和阶级斗争的依赖关系。\n首先,马克思主义者认为人类的生产活动是最基本的实践活动,是决定其它一切活动的东西。人的认识,主要地依赖于物质的生产活动,逐渐地了解自然的现象、自然的性质、自然的规律性、人和自然的关系;而且经过生产活动,也在各种不同程度上逐渐地认识了人和人的一定的相互关系。一切这些知识,离开生产活动是不能得到的。在没有阶级的社会中,每个人以社会一员的资格,同其它社会成员协力,结成一定的生产关系,从事生产活动,以解决人类物质生活问题。在各种阶级的社会中,各阶级的社会成员,则又以各种不同的方式,结成一定的生产关系,从事生产活动,以解决人类物质生活问题。这是人的认识发展的基本来源。\n人的社会实践,不限于生产活动一种形式,还有多种其它的形式,阶级斗争,政治生活,科学和艺术的活动,总之社会实际生活的一切领域都是社会的人所参加的。因此,人的认识,在物质生活以外,还从政治生活文化生活中(与物质生活密切联系),在各种不同程度上,知道人和人的各种关系。其中,尤以各种形式的阶级斗争,给予人的认识发展以深刻的影响。在阶级社会中,每一个人都在一定的阶级地位中生活,各种思想无不打上阶级的烙印。\n马克思主义者认为人类社会的生产活动,是一步又一步地由低级向高级发展,因此,人们的认识,不论对于自然界方面,对于社会方面,也都是一步又一步地由低级向高级发展,即由浅入深,由片面到更多的方面。在很长的历史时期内,大家对于社会的历史只能限于片面的了解,这一方面是由于剥削阶级的偏见经常歪曲社会的历史,另方面,则由于生产规模的狭小,限制了人们的眼界。人们能够对于社会历史的发展作全面的历史的了解,把对于社会的认识变成了科学,这只是到了伴随巨大生产力——大工业而出现近代无产阶级的时候,这就是马克思主义的科学。\n马克思主义者认为,只有人们的社会实践,才是人们对于外界认识的真理性的标准。实际的情形是这样的,只有在社会实践过程中(物质生产过程中,阶级斗争过程中,科学实验过程中),人们达到了思想中所预想的结果时,人们的认识才被证实了。人们要想得到工作的胜利即得到预想的结果,一定要使自己的思想合于客观外界的规律性,如果不合,就会在实践中失败。人们经过失败之后,也就从失败取得教训,改正自己的思想使之适合于外界的规律性,人们就能变失败为胜利,所谓“失败者成功之母”,“吃一堑长一智”,就是这个道理。辩证唯物论的认识论把实践提到第一的地位,认为人的认识一点也不能离开实践,排斥一切否认实践重要性、使认识离开实践的错误理论。列宁这样说过:“实践高于(理论的)认识,因为它不但有普遍性的品格,而且还有直接现实性的品格。”[1]马克思主义的哲学辩证唯物论有两个最显着的特点:一个是它的阶级性,公然申明辩证唯物论是为无产阶级服务的;再一个是它的实践性,强调理论对于实践的依赖关系,理论的基础是实践,又转过来为实践服务。判定认识或理论之是否真理,不是依主观上觉得如何而定,而是依客观上社会实践的结果如何而定。真理的标准只能是社会的实践。实践的观点是辩证唯物论的认识论之第一的和基本的观点[2]。\n然而人的认识究竟怎样从实践发生,而又服务于实践呢?这只要看一看认识的发展过程就会明了的。\n原来人在实践过程中,开始只是看到过程中各个事物的现象方面,看到各个事物的片面,看到各个事物之间的外部联系。例如有些外面的人们到延安来考察,头一二天,他们看到了延安的地形、街道、屋宇,接触了许多的人,参加了宴会、晚会和群众大会,听到了各种说话,看到了各种文件,这些就是事物的现象,事物的各个片面以及这些事物的外部联系。这叫做认识的感性阶段,就是感觉和印象的阶段。也就是延安这些各别的事物作用于考察团先生们的感官,引起了他们的感觉,在他们的脑子中生起了许多的印象,以及这些印象间的大概的外部的联系,这是认识的第一个阶段。在这个阶段中,人们还不能造成深刻的概念,作出合乎论理(即合乎逻辑)的结论。\n社会实践的继续,使人们在实践中引起感觉和印象的东西反复了多次,于是在人们的脑子里生起了一个认识过程中的突变(即飞跃),产生了概念。概念这种东西已经不是事物的现象,不是事物的各个片面,不是它们的外部联系,而是抓着了事物的本质,事物的全体,事物的内部联系了。概念同感觉,不但是数量上的差别,而且有了性质上的差别。循此继进,使用判断和推理的方法,就可产生出合乎论理的结论来。《三国演义》上所谓“眉头一皱计上心来”,我们普通说话所谓“让我想一想”,就是人在脑子中运用概念以作判断和推理的工夫。这是认识的第二个阶段。外来的考察团先生们在他们集合了各种材料,加上他们“想了一想”之后,他们就能够作出“共产党的抗日民族统一战线的政策是彻底的、诚恳的和真实的”这样一个判断了。在他们作出这个判断之后,如果他们对于团结救国也是真实的的话,那末他们就能够进一步作出这样的结论:“抗日民族统一战线是能够成功的。”这个概念、判断和推理的阶段,在人们对于一个事物的整个认识过程中是更重要的阶段,也就是理性认识的阶段。认识的真正任务在于经过感觉而到达于思维,到达于逐步了解客观事物的内部矛盾,了解它的规律性,了解这一过程和那一过程间的内部联系,即到达于论理的认识。重复地说,论理的认识所以和感性的认识不同,是因为感性的认识是属于事物之片面的、现象的、外部联系的东西,论理的认识则推进了一大步,到达了事物的全体的、本质的、内部联系的东西,到达了暴露周围世界的内在的矛盾,因而能在周围世界的总体上,在周围世界一切方面的内部联系上去把握周围世界的发展。\n这种基于实践的由浅入深的辩证唯物论的关于认识发展过程的理论,在马克思主义以前,是没有一个人这样解决过的。马克思主义的唯物论,第一次正确地解决了这个问题,唯物地而且辩证地指出了认识的深化的运动,指出了社会的人在他们的生产和阶级斗争的复杂的、经常反复的实践中,由感性认识到论理认识的推移的运动。列宁说过:“物质的抽象,自然规律的抽象,价值的抽象以及其它等等,一句话,一切科学的(正确的、郑重的、非瞎说的)抽象,都更深刻、更正确、更完全地反映着自然。”[3]马克思列宁主义认为:认识过程中两个阶段的特性,在低级阶段,认识表现为感性的,在高级阶段,认识表现为论理的,但任何阶段,都是统一的认识过程中的阶段。感性和理性二者的性质不同,但又不是互相分离的,它们在实践的基础上统一起来了。我们的实践证明:感觉到了的东西,我们不能立刻理解它,只有理解了的东西才更深刻地感觉它。感觉只解决现象问题,理论才解决本质问题。这些问题的解决,一点也不能离开实践。无论何人要认识什么事物,除了同那个事物接触,即生活于(实践于)那个事物的环境中,是没有法子解决的。不能在封建社会就预先认识资本主义社会的规律,因为资本主义还未出现,还无这种实践。马克思主义只能是资本主义社会的产物。马克思不能在自由资本主义时代就预先具体地认识帝国主义时代的某些特异的规律,因为帝国主义这个资本主义最后阶段还未到来,还无这种实践,只有列宁和斯大林才能担当此项任务。马克思、恩格斯、列宁、斯大林之所以能够作出他们的理论,除了他们的天才条件之外,主要地是他们亲自参加了当时的阶级斗争和科学实验的实践,没有这后一个条件,任何天才也是不能成功的。“秀才不出门,全知天下事”,在技术不发达的古代只是一句空话,在技术发达的现代虽然可以实现这句话,然而真正亲知的是天下实践着的人,那些人在他们的实践中间取得了“知”,经过文字和技术的传达而到达于“秀才”之手,秀才乃能间接地“知天下事”。如果要直接地认识某种或某些事物,便只有亲身参加于变革现实、变革某种或某些事物的实践的斗争中,才能触到那种或那些事物的现象,也只有在亲身参加变革现实的实践的斗争中,才能暴露那种或那些事物的本质而理解它们。这是任何人实际上走着的认识路程,不过有些人故意歪曲地说些反对的话罢了。世上最可笑的是那些“知识里手”[4],有了道听途说的一知半解,便自封为“天下第一”,适足见其不自量而已。知识的问题是一个科学问题,来不得半点的虚伪和骄傲,决定地需要的倒是其反面——诚实和谦逊的态度。你要有知识,你就得参加变革现实的实践。你要知道梨子的滋味,你就得变革梨子,亲口吃一吃。你要知道原子的组织同性质,你就得实行物理学和化学的实验,变革原子的情况。你要知道革命的理论和方法,你就得参加革命。一切真知都是从直接经验发源的。但人不能事事直接经验,事实上多数的知识都是间接经验的东西,这就是一切古代的和外域的知识。这些知识在古人在外人是直接经验的东西,如果在古人外人直接经验时是符合于列宁所说的条件“科学的抽象”,是科学地反映了客观的事物,那末这些知识是可靠的,否则就是不可靠的。所以,一个人的知识,不外直接经验的和间接经验的两部分。而且在我为间接经验者,在人则仍为直接经验。因此,就知识的总体说来,无论何种知识都是不能离开直接经验的。任何知识的来源,在于人的肉体感官对客观外界的感觉,否认了这个感觉,否认了直接经验,否认亲自参加变革现实的实践,他就不是唯物论者。“知识里手”之所以可笑,原因就是在这个地方。中国人有一句老话:“不入虎穴,焉得虎子。”这句话对于人们的实践是真理,对于认识论也是真理。离开实践的认识是不可能的。\n为了明了基于变革现实的实践而产生的辩证唯物论的认识运动——认识的逐渐深化的运动,下面再举出几个具体的例子。\n无产阶级对于资本主义社会的认识,在其实践的初期——破坏机器和自发斗争时期,他们还只在感性认识的阶段,只认识资本主义各个现象的片面及其外部的联系。这时,他们还是一个所谓“自在的阶级”。但是到了他们实践的第二个时期——有意识有组织的经济斗争和政治斗争的时期,由于实践,由于长期斗争的经验,经过马克思、恩格斯用科学的方法把这种种经验总结起来,产生了马克思主义的理论,用以教育无产阶级,这样就使无产阶级理解了资本主义社会的本质,理解了社会阶级的剥削关系,理解了无产阶级的历史任务,这时他们就变成了一个“自为的阶级”。\n中国人民对于帝国主义的认识也是这样。第一阶段是表面的感性的认识阶段,表现在太平天国运动和义和团运动等笼统的排外主义的斗争上[5]。第二阶段才进到理性的认识阶段,看出了帝国主义内部和外部的各种矛盾,并看出了帝国主义联合中国买办阶级和封建阶级以压榨中国人民大众的实质,这种认识是从一九一九年五四运动[6]前后才开始的。\n我们再来看战争。战争的领导者,如果他们是一些没有战争经验的人,对于一个具体的战争(例如我们过去十年的土地革命战争)的深刻的指导规律,在开始阶段是不了解的。他们在开始阶段只是身历了许多作战的经验,而且败仗是打得很多的。然而由于这些经验(胜仗,特别是败仗的经验),使他们能够理解贯串整个战争的内部的东西,即那个具体战争的规律性,懂得了战略和战术,因而能够有把握地去指导战争。此时,如果改换一个无经验的人去指导,又会要在吃了一些败仗之后(有了经验之后)才能理会战争的正确的规律。\n常常听到一些同志在不能勇敢接受工作任务时说出来的一句话:没有把握。为什么没有把握呢?因为他对于这项工作的内容和环境没有规律性的了解,或者他从来就没有接触过这类工作,或者接触得不多,因而无从谈到这类工作的规律性。及至把工作的情况和环境给以详细分析之后,他就觉得比较地有了把握,愿意去做这项工作。如果这个人在这项工作中经过了一个时期,他有了这项工作的经验了,而他又是一个肯虚心体察情况的人,不是一个主观地、片面地、表面地看问题的人,他就能够自己做出应该怎样进行工作的结论,他的工作勇气也就可以大大地提高了。只有那些主观地、片面地和表面地看问题的人,跑到一个地方,不问环境的情况,不看事情的全体(事情的历史和全部现状),也不触到事情的本质(事情的性质及此一事情和其它事情的内部联系),就自以为是地发号施令起来,这样的人是没有不跌交子的。\n由此看来,认识的过程,第一步,是开始接触外界事情,属于感觉的阶段。第二步,是综合感觉的材料加以整理和改造,属于概念、判断和推理的阶段。只有感觉的材料十分丰富(不是零碎不全)和合于实际(不是错觉),才能根据这样的材料造出正确的概念和论理来。\n这里有两个要点必须着重指明。第一个,在前面已经说过的,这里再重复说一说,就是理性认识依赖于感性认识的问题。如果以为理性认识可以不从感性认识得来,他就是一个唯心论者。哲学史上有所谓“唯理论”一派,就是只承认理性的实在性,不承认经验的实在性,以为只有理性靠得住,而感觉的经验是靠不住的,这一派的错误在于颠倒了事实。理性的东西所以靠得住,正是由于它来源于感性,否则理性的东西就成了无源之水,无本之木,而只是主观自生的靠不住的东西了。从认识过程的秩序说来,感觉经验是第一的东西,我们强调社会实践在认识过程中的意义,就在于只有社会实践才能使人的认识开始发生,开始从客观外界得到感觉经验。一个闭目塞听、同客观外界根本绝缘的人,是无所谓认识的。认识开始于经验——这就是认识论的唯物论。\n第二是认识有待于深化,认识的感性阶段有待于发展到理性阶段——这就是认识论的辩证法[7]。如果以为认识可以停顿在低级的感性阶段,以为只有感性认识可靠,而理性认识是靠不住的,这便是重复了历史上的“经验论”的错误。这种理论的错误,在于不知道感觉材料固然是客观外界某些真实性的反映(我这里不来说经验只是所谓内省体验的那种唯心的经验论),但它们仅是片面的和表面的东西,这种反映是不完全的,是没有反映事物本质的。要完全地反映整个的事物,反映事物的本质,反映事物的内部规律性,就必须经过思考作用,将丰富的感觉材料加以去粗取精、去伪存真、由此及彼、由表及里的改造制作工夫,造成概念和理论的系统,就必须从感性认识跃进到理性认识。这种改造过的认识,不是更空虚了更不可靠了的认识,相反,只要是在认识过程中根据于实践基础而科学地改造过的东西,正如列宁所说乃是更深刻、更正确、更完全地反映客观事物的东西。庸俗的事务主义家不是这样,他们尊重经验而看轻理论,因而不能通观客观过程的全体,缺乏明确的方针,没有远大的前途,沾沾自喜于一得之功和一孔之见。这种人如果指导革命,就会引导革命走上碰壁的地步。\n理性认识依赖于感性认识,感性认识有待于发展到理性认识,这就是辩证唯物论的认识论。哲学上的“唯理论”和“经验论”都不懂得认识的历史性或辩证性,虽然各有片面的真理(对于唯物的唯理论和经验论而言,非指唯心的唯理论和经验论),但在认识论的全体上则都是错误的。由感性到理性之辩证唯物论的认识运动,对于一个小的认识过程(例如对于一个事物或一件工作的认识)是如此,对于一个大的认识过程(例如对于一个社会或一个革命的认识)也是如此。\n然而认识运动至此还没有完结。辩证唯物论的认识运动,如果只到理性认识为止,那末还只说到问题的一半。而且对于马克思主义的哲学说来,还只说到非十分重要的那一半。马克思主义的哲学认为十分重要的问题,不在于懂得了客观世界的规律性,因而能够解释世界,而在于拿了这种对于客观规律性的认识去能动地改造世界。在马克思主义看来,理论是重要的,它的重要性充分地表现在列宁说过的一句话:“没有革命的理论,就不会有革命的运动。”[8]然而马克思主义看重理论,正是,也仅仅是,因为它能够指导行动。如果有了正确的理论,只是把它空谈一阵,束之高阁,并不实行,那末,这种理论再好也是没有意义的。认识从实践始,经过实践得到了理论的认识,还须再回到实践去。认识的能动作用,不但表现于从感性的认识到理性的认识之能动的飞跃,更重要的还须表现于从理性的认识到革命的实践这一个飞跃。抓着了世界的规律性的认识,必须把它再回到改造世界的实践中去,再用到生产的实践、革命的阶级斗争和民族斗争的实践以及科学实验的实践中去。这就是检验理论和发展理论的过程,是整个认识过程的继续。理论的东西之是否符合于客观真理性这个问题,在前面说的由感性到理性之认识运动中是没有完全解决的,也不能完全解决的。要完全地解决这个问题,只有把理性的认识再回到社会实践中去,应用理论于实践,看它是否能够达到预想的目的。许多自然科学理论之所以被称为真理,不但在于自然科学家们创立这些学说的时候,而且在于为尔后的科学实践所证实的时候。马克思列宁主义之所以被称为真理,也不但在于马克思、恩格斯、列宁、斯大林等人科学地构成这些学说的时候,而且在于为尔后革命的阶级斗争和民族斗争的实践所证实的时候。辩证唯物论之所以为普遍真理,在于经过无论什么人的实践都不能逃出它的范围。人类认识的历史告诉我们,许多理论的真理性是不完全的,经过实践的检验而纠正了它们的不完全性。许多理论是错误的,经过实践的检验而纠正其错误。所谓实践是真理的标准,所谓“生活、实践底观点,应该是认识论底首先的和基本的观点”[9],理由就在这个地方。斯大林说得好:“理论若不和革命实践联系起来,就会变成无对象的理论,同样,实践若不以革命理论为指南,就会变成盲目的实践。”[10]\n说到这里,认识运动就算完成了吗?我们的答复是完成了,又没有完成。社会的人们投身于变革在某一发展阶段内的某一客观过程的实践中(不论是关于变革某一自然过程的实践,或变革某一社会过程的实践),由于客观过程的反映和主观能动性的作用,使得人们的认识由感性的推移到了理性的,造成了大体上相应于该客观过程的法则性的思想、理论、计划或方案,然后再应用这种思想、理论、计划或方案于该同一客观过程的实践,如果能够实现预想的目的,即将预定的思想、理论、计划、方案在该同一过程的实践中变为事实,或者大体上变为事实,那末,对于这一具体过程的认识运动算是完成了。例如,在变革自然的过程中,某一工程计划的实现,某一科学假想的证实,某一器物的制成,某一农产的收获,在变革社会过程中某一罢工的胜利,某一战争的胜利,某一教育计划的实现,都算实现了预想的目的。然而一般地说来,不论在变革自然或变革社会的实践中,人们原定的思想、理论、计划、方案,毫无改变地实现出来的事,是很少的。这是因为从事变革现实的人们,常常受着许多的限制,不但常常受着科学条件和技术条件的限制,而且也受着客观过程的发展及其表现程度的限制(客观过程的方面及本质尚未充分暴露)。在这种情形之下,由于实践中发现前所未料的情况,因而部分地改变思想、理论、计划、方案的事是常有的,全部地改变的事也是有的。即是说,原定的思想、理论、计划、方案,部分地或全部地不合于实际,部分错了或全部错了的事,都是有的。许多时候须反复失败过多次,才能纠正错误的认识,才能到达于和客观过程的规律性相符合,因而才能够变主观的东西为客观的东西,即在实践中得到预想的结果。但是不管怎样,到了这种时候,人们对于在某一发展阶段内的某一客观过程的认识运动,算是完成了。\n然而对于过程的推移而言,人们的认识运动是没有完成的。任何过程,不论是属于自然界的和属于社会的,由于内部的矛盾和斗争,都是向前推移向前发展的,人们的认识运动也应跟着推移和发展。依社会运动来说,真正的革命的指导者,不但在于当自己的思想、理论、计划、方案有错误时须得善于改正,如同上面已经说到的,而且在于当某一客观过程已经从某一发展阶段向另一发展阶段推移转变的时候,须得善于使自己和参加革命的一切人员在主观认识上也跟着推移转变,即是要使新的革命任务和新的工作方案的提出,适合于新的情况的变化。革命时期情况的变化是很急速的,如果革命党人的认识不能随之而急速变化,就不能引导革命走向胜利。\n然而思想落后于实际的事是常有的,这是因为人的认识受了许多社会条件的限制的缘故。我们反对革命队伍中的顽固派,他们的思想不能随变化了的客观情况而前进,在历史上表现为右倾机会主义。这些人看不出矛盾的斗争已将客观过程推向前进了,而他们的认识仍然停止在旧阶段。一切顽固党的思想都有这样的特征。他们的思想离开了社会的实践,他们不能站在社会车轮的前头充任向导的工作,他们只知跟在车子后面怨恨车子走得太快了,企图把它向后拉,开倒车。\n我们也反对“左”翼空谈主义。他们的思想超过客观过程的一定发展阶段,有些把幻想看作真理,有些则把仅在将来有现实可能性的理想,勉强地放在现时来做,离开了当前大多数人的实践,离开了当前的现实性,在行动上表现为冒险主义。\n唯心论和机械唯物论,机会主义和冒险主义,都是以主观和客观相分裂,以认识和实践相脱离为特征的。以科学的社会实践为特征的马克思列宁主义的认识论,不能不坚决反对这些错误思想。马克思主义者承认,在绝对的总的宇宙发展过程中,各个具体过程的发展都是相对的,因而在绝对真理的长河中,人们对于在各个一定发展阶段上的具体过程的认识只具有相对的真理性。无数相对的真理之总和,就是绝对的真理[11]。客观过程的发展是充满着矛盾和斗争的发展,人的认识运动的发展也是充满着矛盾和斗争的发展。一切客观世界的辩证法的运动,都或先或后地能够反映到人的认识中来。社会实践中的发生、发展和消灭的过程是无穷的,人的认识的发生、发展和消灭的过程也是无穷的。根据于一定的思想、理论、计划、方案以从事于变革客观现实的实践,一次又一次地向前,人们对于客观现实的认识也就一次又一次地深化。客观现实世界的变化运动永远没有完结,人们在实践中对于真理的认识也就永远没有完结。马克思列宁主义并没有结束真理,而是在实践中不断地开辟认识真理的道路。我们的结论是主观和客观、理论和实践、知和行的具体的历史的统一,反对一切离开具体历史的“左”的或右的错误思想。\n社会的发展到了今天的时代,正确地认识世界和改造世界的责任,已经历史地落在无产阶级及其政党的肩上。这种根据科学认识而定下来的改造世界的实践过程,在世界、在中国均已到达了一个历史的时节——自有历史以来未曾有过的重大时节,这就是整个儿地推翻世界和中国的黑暗面,把它们转变过来成为前所未有的光明世界。无产阶级和革命人民改造世界的斗争,包括实现下述的任务:改造客观世界,也改造自己的主观世界——改造自己的认识能力,改造主观世界同客观世界的关系。地球上已经有一部分实行了这种改造,这就是苏联。他们还正在促进这种改造过程。中国人民和世界人民也都正在或将要通过这样的改造过程。所谓被改造的客观世界,其中包括了一切反对改造的人们,他们的被改造,须要通过强迫的阶段,然后才能进入自觉的阶段。世界到了全人类都自觉地改造自己和改造世界的时候,那就是世界的共产主义时代。\n通过实践而发现真理,又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识,又从理性认识而能动地指导革命实践,改造主观世界和客观世界。实践、认识、再实践、再认识,这种形式,循环往复以至无穷,而实践和认识之每一循环的内容,都比较地进到了高一级的程度。这就是辩证唯物论的全部认识论,这就是辩证唯物论的知行统一观。\n注释\n[1] 见列宁《黑格尔〈逻辑学〉一书摘要》。新的译文是:“实践高于(理论的)认识,因为它不仅具有普遍性的品格,而且还具有直接现实性的品格。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 183 页)\n[2] 参见马克思《关于费尔巴哈的提纲》(《马克思恩格斯选集》第 1 卷,人民出版社 1972 年版,第 16—19 页)和列宁《唯物主义和经验批判主义》第二章第六节(《列宁全集》第 18 卷,人民出版社 1988 年版,第 144 页)。\n[3] 见列宁《黑格尔〈逻辑学〉一书摘要》(《列宁全集》第 55 卷,人民出版社 1990 年版,第 142 页)。\n[4] 里手,湖南方言,内行的意思。\n[5] 一九五一年三月二十七日,毛泽东在致李达的信中说:“《实践论》中将太平天国放在排外主义一起说不妥,出选集时拟加修改,此处暂仍照原。”\n[6] 五四运动是一九一九年五月四日发生的反帝反封建的爱国运动。当时,第一次世界大战刚刚结束,英、美、法、日、意等战胜国在巴黎召开对德和会,决定由日本继承德国在中国山东的特权。中国是参加对德宣战的战胜国之一,但北洋军阀政府却准备接受这个决定。五月四日,北京学生游行示威,反对帝国主义的这一无理决定和北洋军阀政府的妥协。这次运动迅速地获得了全国人民的响应,到六月三日以后,发展成为有工人阶级、城市小资产阶级和民族资产阶级参加的广大群众性的反帝反封建的爱国运动。五四运动也是反对封建文化的新文化运动。以一九一五年《青年杂志》(后改名《新青年》)创刊为起点的新文化运动,竖起“民主”和“科学”的旗帜,反对旧道德,提倡新道德,反对旧文学,提倡新文学。五四运动中的先进分子接受了马克思主义,使新文化运动发展成为马克思主义思想运动,他们致力于马克思主义同中国工人运动相结合,在思想上和干部上准备了中国共产党的成立。\n[7] 参见列宁《黑格尔〈逻辑学〉一书摘要》:“要理解,就必须从经验开始理解、研究,从经验上升到一般。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 175 页)\n[8] 见列宁《俄国社会民主党人的任务》(《列宁全集》第 2 卷,人民出版社 1984 年版,第 443 页);并见列宁《怎么办?》第一章第四节(《列宁全集》第 6 卷,人民出版社 1986 年版,第 23 页)。\n[9] 见列宁《唯物主义和经验批判主义》第二章第六节(《列宁全集》第 18 卷,人民出版社 1988 年版,第 144 页)。\n[10] 见斯大林《论列宁主义基础》第三部分《理论》。新的译文是:“离开革命实践的理论是空洞的理论,而不以革命理论为指南的实践是盲目的实践。”(《斯大林选集》上卷,人民出版社 1979 年版,第 199—200 页)\n[11] 参见列宁《唯物主义和经验批判主义》第二章第五节。原文是:“人类思维按其本性是能够给我们提供并且正在提供由相对真理的总和所构成的绝对真理的。”(《列宁全集》第 18 卷,人民出版社 1988 年版,第 135 页)\n","date":"2024-11-18","permalink":"https://aituyaa.com/%E5%AE%9E%E8%B7%B5%E8%AE%BA/","summary":"\u003cp\u003e论认识和实践的关系——知和行的关系。(写于 1937.7)\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e在中国共产党内,曾经有一部分教条主义的同志长期拒绝中国革命的经验,否认“马克思主义不是教条而是行动的指南”这个真理,而只生吞活剥马克思主义书籍中的只言片语,去吓唬人们。还有另一部分经验主义的同志长期拘守于自身的片断经验,不了解理论对于革命实践的重要性,看不见革命的全局,虽然也是辛苦地——但却是盲目地在工作。这两类同志的错误思想,特别是教条主义思想,曾经在一九三一年至一九三四年使得中国革命受了极大的损失,而教条主义者却是披着马克思主义的外衣迷惑了广大的同志。毛泽东的《实践论》,是为着用马克思主义的认识论观点去揭露党内的教条主义和经验主义——特别是教条主义这些主观主义的错误而写的。因为重点是揭露看轻实践的教条主义这种主观主义,故题为《实践论》。毛泽东曾以这篇论文的观点在延安的抗日军事政治大学作过讲演。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: 教条主义❌,经验主义❌。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"实践论"},]
[{"content":"写于 1937.8\n这篇哲学论文,是毛泽东继《实践论》之后,为了同一的目的,即为了克服存在于中国共产党内的严重的教条主义思想而写的,曾在延安的抗日军事政治大学作过讲演。在收入本书第一版的时候,作者作了部分的补充、删节和修改。\n事物的矛盾法则,即对立统一的法则,是唯物辩证法的最根本的法则。列宁说:“就本来的意义讲,辩证法是研究对象的本质自身中的矛盾。”[1]列宁常称这个法则为辩证法的本质,又称之为辩证法的核心[2]。因此,我们在研究这个法则时,不得不涉及广泛的方面,不得不涉及许多的哲学问题。如果我们将这些问题都弄清楚了,我们就在根本上懂得了唯物辩证法。这些问题是:两种宇宙观;矛盾的普遍性;矛盾的特殊性;主要的矛盾和主要的矛盾方面;矛盾诸方面的同一性和斗争性;对抗在矛盾中的地位。\n苏联哲学界在最近数年中批判了德波林学派[3]的唯心论,这件事引起了我们的极大的兴趣。德波林的唯心论在中国共产党内发生了极坏的影响,我们党内的教条主义思想不能说和这个学派的作风没有关系。因此,我们现在的哲学研究工作,应当以扫除教条主义思想为主要的目标。\n一 两种宇宙观 在人类的认识史中,从来就有关于宇宙发展法则的两种见解,一种是形而上学的见解,一种是辩证法的见解,形成了互相对立的两种宇宙观。列宁说:“对于发展(进化)所持的两种基本的(或两种可能的?或两种在历史上常见的?)观点是:(一)认为发展是减少和增加,是重复;(二)认为发展是对立的统一(统一物分成为两个互相排斥的对立,而两个对立又互相关联着)。”[4]列宁说的就是这两种不同的宇宙观。\n形而上学,亦称玄学。这种思想,无论在中国,在欧洲,在一个很长的历史时间内,是属于唯心论的宇宙观,并在人们的思想中占了统治的地位。在欧洲,资产阶级初期的唯物论,也是形而上学的。由于欧洲许多国家的社会经济情况进到了资本主义高度发展的阶段,生产力、阶级斗争和科学均发展到了历史上未有过的水平,工业无产阶级成为历史发展的最伟大的动力,因而产生了马克思主义的唯物辩证法的宇宙观。于是,在资产阶级那里,除了公开的极端露骨的反动的唯心论之外,还出现了庸俗的进化论,出来对抗唯物辩证法。\n所谓形而上学的或庸俗进化论的宇宙观,就是用孤立的、静止的和片面的观点去看世界。这种宇宙观把世界一切事物,一切事物的形态和种类,都看成是永远彼此孤立和永远不变化的。如果说有变化,也只是数量的增减和场所的变更。而这种增减和变更的原因,不在事物的内部而在事物的外部,即是由于外力的推动。形而上学家认为,世界上各种不同事物和事物的特性,从它们一开始存在的时候就是如此。后来的变化,不过是数量上的扩大或缩小。他们认为一种事物永远只能反复地产生为同样的事物,而不能变化为另一种不同的事物。在形而上学家看来,资本主义的剥削,资本主义的竞争,资本主义社会的个人主义思想等,就是在古代的奴隶社会里,甚至在原始社会里,都可以找得出来,而且会要永远不变地存在下去。说到社会发展的原因,他们就用社会外部的地理、气候等条件去说明。他们简单地从事物外部去找发展的原因,否认唯物辩证法所主张的事物因内部矛盾引起发展的学说。因此,他们不能解释事物的质的多样性,不能解释一种质变为他种质的现象。这种思想,在欧洲,在十七世纪和十八世纪是机械唯物论,在十九世纪末和二十世纪初则有庸俗进化论。在中国,则有所谓“天不变,道亦不变”[5]的形而上学的思想,曾经长期地为腐朽了的封建统治阶级所拥护。近百年来输入了欧洲的机械唯物论和庸俗进化论,则为资产阶级所拥护。\n和形而上学的宇宙观相反,唯物辩证法的宇宙观主张从事物的内部、从一事物对他事物的关系去研究事物的发展,即把事物的发展看做是事物内部的必然的自己的运动,而每一事物的运动都和它的周围其它事物互相联系着和互相影响着。事物发展的根本原因,不是在事物的外部而是在事物的内部,在于事物内部的矛盾性。任何事物内部都有这种矛盾性,因此引起了事物的运动和发展。事物内部的这种矛盾性是事物发展的根本原因,一事物和他事物的互相联系和互相影响则是事物发展的第二位的原因。这样,唯物辩证法就有力地反对了形而上学的机械唯物论和庸俗进化论的外因论或被动论。这是清楚的,单纯的外部原因只能引起事物的机械的运动,即范围的大小,数量的增减,不能说明事物何以有性质上的千差万别及其互相变化。事实上,即使是外力推动的机械运动,也要通过事物内部的矛盾性。植物和动物的单纯的增长,数量的发展,主要地也是由于内部矛盾所引起的。同样,社会的发展,主要地不是由于外因而是由于内因。许多国家在差不多一样的地理和气候的条件下,它们发展的差异性和不平衡性,非常之大。同一个国家吧,在地理和气候并没有变化的情形下,社会的变化却是很大的。帝国主义的俄国变为社会主义的苏联,封建的闭关锁国的日本变为帝国主义的日本,这些国家的地理和气候并没有变化。长期地被封建制度统治的中国,近百年来发生了很大的变化,现在正在变化到一个自由解放的新中国的方向去,中国的地理和气候并没有变化。整个地球及地球各部分的地理和气候也是变化着的,但以它们的变化和社会的变化相比较,则显得很微小,前者是以若干万年为单位而显现其变化的,后者则在几千年、几百年、几十年、甚至几年或几个月(在革命时期)内就显现其变化了。按照唯物辩证法的观点,自然界的变化,主要地是由于自然界内部矛盾的发展。社会的变化,主要地是由于社会内部矛盾的发展,即生产力和生产关系的矛盾,阶级之间的矛盾,新旧之间的矛盾,由于这些矛盾的发展,推动了社会的前进,推动了新旧社会的代谢。唯物辩证法是否排除外部的原因呢?并不排除。唯物辩证法认为外因是变化的条件,内因是变化的根据,外因通过内因而起作用。鸡蛋因得适当的温度而变化为鸡子,但温度不能使石头变为鸡子,因为二者的根据是不同的。各国人民之间的互相影响是时常存在的。在资本主义时代,特别是在帝国主义和无产阶级革命的时代,各国在政治上、经济上和文化上的互相影响和互相激动,是极其巨大的。十月社会主义革命不只是开创了俄国历史的新纪元,而且开创了世界历史的新纪元,影响到世界各国内部的变化,同样地而且还特别深刻地影响到中国内部的变化,但是这种变化是通过了各国内部和中国内部自己的规律性而起的。两军相争,一胜一败,所以胜败,皆决于内因。胜者或因其强,或因其指挥无误,败者或因其弱,或因其指挥失宜,外因通过内因而引起作用。一九二七年中国大资产阶级战败了无产阶级,是通过中国无产阶级内部的(中国共产党内部的)机会主义而起作用的。当着我们清算了这种机会主义的时候,中国革命就重新发展了。后来,中国革命又受了敌人的严重的打击,是因为我们党内产生了冒险主义。当着我们清算了这种冒险主义的时候,我们的事业就又重新发展了。由此看来,一个政党要引导革命到胜利,必须依靠自己政治路线的正确和组织上的巩固。\n辩证法的宇宙观,不论在中国,在欧洲,在古代就产生了。但是古代的辩证法带着自发的朴素的性质,根据当时的社会历史条件,还不可能有完备的理论,因而不能完全解释宇宙,后来就被形而上学所代替。生活在十八世纪末和十九世纪初期的德国著名哲学家黑格尔,对于辩证法曾经给了很重要的贡献,但是他的辩证法却是唯心的辩证法。直到无产阶级运动的伟大的活动家马克思和恩格斯综合了人类认识史的积极的成果,特别是批判地吸取了黑格尔的辩证法的合理的部分,创造了辩证唯物论和历史唯物论这个伟大的理论,才在人类认识史上起了一个空前的大革命。后来,经过列宁和斯大林,又发展了这个伟大的理论。这个理论一经传到中国来,就在中国思想界引起了极大的变化。\n这个辩证法的宇宙观,主要地就是教导人们要善于去观察和分析各种事物的矛盾的运动,并根据这种分析,指出解决矛盾的方法。因此,具体地了解事物矛盾这一个法则,对于我们是非常重要的。\n二 矛盾的普遍性 为了叙述的便利起见,我在这里先说矛盾的普遍性,再说矛盾的特殊性。这是因为马克思主义的伟大的创造者和继承者马克思、恩格斯、列宁、斯大林他们发现了唯物辩证法的宇宙观,已经把唯物辩证法应用在人类历史的分析和自然历史的分析的许多方面,应用在社会的变革和自然的变革(例如在苏联)的许多方面,获得了极其伟大的成功,矛盾的普遍性已经被很多人所承认,因此,关于这个问题只需要很少的话就可以说明白;而关于矛盾的特殊性的问题,则还有很多的同志,特别是教条主义者,弄不清楚。他们不了解矛盾的普遍性即寓于矛盾的特殊性之中。他们也不了解研究当前具体事物的矛盾的特殊性,对于我们指导革命实践的发展有何等重要的意义。因此,关于矛盾的特殊性的问题应当着重地加以研究,并用足够的篇幅加以说明。为了这个缘故,当着我们分析事物矛盾的法则的时候,我们就先来分析矛盾的普遍性的问题,然后再着重地分析矛盾的特殊性的问题,最后仍归到矛盾的普遍性的问题。\n矛盾的普遍性或绝对性这个问题有两方面的意义。其一是说,矛盾存在于一切事物的发展过程中;其二是说,每一事物的发展过程中存在着自始至终的矛盾运动。\n恩格斯说:“运动本身就是矛盾。”[6]列宁对于对立统一法则所下的定义,说它就是“承认(发现)自然界(精神和社会两者也在内)的一切现象和过程都含有互相矛盾、互相排斥、互相对立的趋向”[7]。这些意见是对的吗?是对的。一切事物中包含的矛盾方面的相互依赖和相互斗争,决定一切事物的生命,推动一切事物的发展。没有什么事物是不包含矛盾的,没有矛盾就没有世界。\n矛盾是简单的运动形式(例如机械性的运动)的基础,更是复杂的运动形式的基础。\n恩格斯这样说明过矛盾的普遍性:“如果简单的机械的移动本身包含着矛盾,那末,物质的更高的运动形式,特别是有机生命及其发展,就更加包含着矛盾。……生命首先就在于:生物在每一个瞬间是它自身,但却又是别的什么。所以,生命也是存在于物体和过程本身中的不断地自行产生并自行解决的矛盾;这一矛盾一停止,生命亦即停止,于是死就来到。同样,我们看到了,在思维的范围以内我们也不能避免矛盾,并且我们看到了,例如,人的内部无限的认识能力与此种认识能力仅在外部被局限的而且认识上也被局限的个别人们身上的实际的实现二者之间的矛盾,是在人类世代的无穷的——至少对于我们,实际上是无穷的——连续系列之中,是在无穷的前进运动之中解决的。”\n“高等数学的主要基础之一,就是矛盾……”\n“就是初等数学,也充满着矛盾。……”[8]\n列宁也这样说明过矛盾的普遍性:“在数学中,正和负,微分和积分。\n在力学中,作用和反作用。\n在物理学中,阳电和阴电。\n在化学中,原子的化合和分解。\n在社会科学中,阶级斗争。”[9]\n战争中的攻守,进退,胜败,都是矛盾着的现象。失去一方,他方就不存在。双方斗争而又联结,组成了战争的总体,推动了战争的发展,解决了战争的问题。\n人的概念的每一差异,都应把它看作是客观矛盾的反映。客观矛盾反映入主观的思想,组成了概念的矛盾运动,推动了思想的发展,不断地解决了人们的思想问题。\n党内不同思想的对立和斗争是经常发生的,这是社会的阶级矛盾和新旧事物的矛盾在党内的反映。党内如果没有矛盾和解决矛盾的思想斗争,党的生命也就停止了。\n由此看来,不论是简单的运动形式,或复杂的运动形式,不论是客观现象,或思想现象,矛盾是普遍地存在着,矛盾存在于一切过程中,这一点已经弄清楚了。但是每一过程的开始阶段,是否也有矛盾存在呢?是否每一事物的发展过程具有自始至终的矛盾运动呢?\n从苏联哲学界批判德波林学派的文章中看出,德波林学派有这样一种见解,他们认为矛盾不是一开始就在过程中出现,须待过程发展到一定的阶段才出现。那末,在那一时间以前,过程发展的原因不是由于内部的原因,而是由于外部的原因了。这样,德波林回到形而上学的外因论和机械论去了。拿这种见解去分析具体的问题,他们就看见在苏联条件下富农和一般农民之间只有差异,并无矛盾,完全同意了布哈林的意见。在分析法国革命时,他们就认为在革命前,工农资产阶级合组的第三等级中,也只有差异,并无矛盾。德波林学派这类见解是反马克思主义的。他们不知道世界上的每一差异中就已经包含着矛盾,差异就是矛盾。劳资之间,从两阶级发生的时候起,就是互相矛盾的,仅仅还没有激化而已。工农之间,即使在苏联的社会条件下,也有差异,它们的差异就是矛盾,仅仅不会激化成为对抗,不取阶级斗争的形态,不同于劳资间的矛盾;它们在社会主义建设中形成巩固的联盟,并在由社会主义走向共产主义的发展过程中逐渐地解决这个矛盾。这是矛盾的差别性的问题,不是矛盾的有无的问题。矛盾是普遍的、绝对的,存在于事物发展的一切过程中,又贯串于一切过程的始终。\n新过程的发生是什么呢?这是旧的统一和组成此统一的对立成分让位于新的统一和组成此统一的对立成分,于是新过程就代替旧过程而发生。旧过程完结了,新过程发生了。新过程又包含着新矛盾,开始它自己的矛盾发展史。\n事物发展过程的自始至终的矛盾运动,列宁指出马克思在《资本论》中模范地作了这样的分析。这是研究任何事物发展过程所必须应用的方法。列宁自己也正确地应用了它,贯彻于他的全部著作中。\n“马克思在《资本论》中,首先分析的是资产阶级社会(商品社会)里最简单的、最普通的、最基本的、最常见的、最平常的、碰到亿万次的关系——商品交换。这一分析在这个最简单的现象之中(资产阶级社会的这个‘细胞’之中)暴露了现代社会的一切矛盾(以及一切矛盾的胚芽)。往后的叙述又向我们表明了这些矛盾和这个社会各个部分总和的自始至终的发展(增长与运动两者)。”\n列宁说了上面的话之后,接着说道:“这应该是一般辩证法的……叙述(以及研究)方法。”[10]\n中国共产党人必须学会这个方法,才能正确地分析中国革命的历史和现状,并推断革命的将来。\n三 矛盾的特殊性 矛盾存在于一切事物发展的过程中,矛盾贯串于每一事物发展过程的始终,这是矛盾的普遍性和绝对性,前面已经说过了。现在来说矛盾的特殊性和相对性。\n这个问题,应从几种情形中去研究。\n首先是各种物质运动形式中的矛盾,都带特殊性。人的认识物质,就是认识物质的运动形式,因为除了运动的物质以外,世界上什么也没有,而物质的运动则必取一定的形式。对于物质的每一种运动形式,必须注意它和其它各种运动形式的共同点。但是,尤其重要的,成为我们认识事物的基础的东西,则是必须注意它的特殊点,就是说,注意它和其它运动形式的质的区别。只有注意了这一点,才有可能区别事物。任何运动形式,其内部都包含着本身特殊的矛盾。这种特殊的矛盾,就构成一事物区别于他事物的特殊的本质。这就是世界上诸种事物所以有千差万别的内在的原因,或者叫做根据。自然界存在着许多的运动形式,机械运动、发声、发光、发热、电流、化分、化合等等都是。所有这些物质的运动形式,都是互相依存的,又是本质上互相区别的。每一物质的运动形式所具有的特殊的本质,为它自己的特殊的矛盾所规定。这种情形,不但在自然界中存在着,在社会现象和思想现象中也是同样地存在着。每一种社会形式和思想形式,都有它的特殊的矛盾和特殊的本质。\n科学研究的区分,就是根据科学对象所具有的特殊的矛盾性。因此,对于某一现象的领域所特有的某一种矛盾的研究,就构成某一门科学的对象。例如,数学中的正数和负数,机械学中的作用和反作用,物理学中的阴电和阳电,化学中的化分和化合,社会科学中的生产力和生产关系、阶级和阶级的互相斗争,军事学中的攻击和防御,哲学中的唯心论和唯物论、形而上学观和辩证法观等等,都是因为具有特殊的矛盾和特殊的本质,才构成了不同的科学研究的对象。固然,如果不认识矛盾的普遍性,就无从发现事物运动发展的普遍的原因或普遍的根据;但是,如果不研究矛盾的特殊性,就无从确定一事物不同于他事物的特殊的本质,就无从发现事物运动发展的特殊的原因,或特殊的根据,也就无从辨别事物,无从区分科学研究的领域。\n就人类认识运动的秩序说来,总是由认识个别的和特殊的事物,逐步地扩大到认识一般的事物。人们总是首先认识了许多不同事物的特殊的本质,然后才有可能更进一步地进行概括工作,认识诸种事物的共同的本质。当着人们已经认识了这种共同的本质以后,就以这种共同的认识为指导,继续地向着尚未研究过的或者尚未深入地研究过的各种具体的事物进行研究,找出其特殊的本质,这样才可以补充、丰富和发展这种共同的本质的认识,而使这种共同的本质的认识不致变成枯槁的和僵死的东西。这是两个认识的过程:一个是由特殊到一般,一个是由一般到特殊。人类的认识总是这样循环往复地进行的,而每一次的循环(只要是严格地按照科学的方法)都可能使人类的认识提高一步,使人类的认识不断地深化。我们的教条主义者在这个问题上的错误,就是,一方面,不懂得必须研究矛盾的特殊性,认识各别事物的特殊的本质,才有可能充分地认识矛盾的普遍性,充分地认识诸种事物的共同的本质;另一方面,不懂得在我们认识了事物的共同的本质以后,还必须继续研究那些尚未深入地研究过的或者新冒出来的具体的事物。我们的教条主义者是懒汉,他们拒绝对于具体事物做任何艰苦的研究工作,他们把一般真理看成是凭空出现的东西,把它变成为人们所不能够捉摸的纯粹抽象的公式,完全否认了并且颠倒了这个人类认识真理的正常秩序。他们也不懂得人类认识的两个过程的互相联结——由特殊到一般,又由一般到特殊,他们完全不懂得马克思主义的认识论。\n不但要研究每一个大系统的物质运动形式的特殊的矛盾性及其所规定的本质,而且要研究每一个物质运动形式在其发展长途中的每一个过程的特殊的矛盾及其本质。一切运动形式的每一个实在的非臆造的发展过程内,都是不同质的。我们的研究工作必须着重这一点,而且必须从这一点开始。\n不同质的矛盾,只有用不同质的方法才能解决。例如,无产阶级和资产阶级的矛盾,用社会主义革命的方法去解决;人民大众和封建制度的矛盾,用民主革命的方法去解决;殖民地和帝国主义的矛盾,用民族革命战争的方法去解决;在社会主义社会中工人阶级和农民阶级的矛盾,用农业集体化和农业机械化的方法去解决;共产党内的矛盾,用批评和自我批评的方法去解决;社会和自然的矛盾,用发展生产力的方法去解决。过程变化,旧过程和旧矛盾消灭,新过程和新矛盾发生,解决矛盾的方法也因之而不同。俄国的二月革命和十月革命所解决的矛盾及其所用以解决矛盾的方法是根本上不相同的。用不同的方法去解决不同的矛盾,这是马克思列宁主义者必须严格地遵守的一个原则。教条主义者不遵守这个原则,他们不了解诸种革命情况的区别,因而也不了解应当用不同的方法去解决不同的矛盾,而只是千篇一律地使用一种自以为不可改变的公式到处硬套,这就只能使革命遭受挫折,或者将本来做得好的事情弄得很坏。\n为要暴露事物发展过程中的矛盾在其总体上、在其相互联结上的特殊性,就是说暴露事物发展过程的本质,就必须暴露过程中矛盾各方面的特殊性,否则暴露过程的本质成为不可能,这也是我们作研究工作时必须十分注意的。\n一个大的事物,在其发展过程中,包含着许多的矛盾。例如,在中国资产阶级民主革命过程中,有中国社会各被压迫阶级和帝国主义的矛盾,有人民大众和封建制度的矛盾,有无产阶级和资产阶级的矛盾,有农民及城市小资产阶级和资产阶级的矛盾,有各个反动的统治集团之间的矛盾等等,情形是非常复杂的。这些矛盾,不但各各有其特殊性,不能一律看待,而且每一矛盾的两方面,又各各有其特点,也是不能一律看待的。我们从事中国革命的人,不但要在各个矛盾的总体上,即矛盾的相互联结上,了解其特殊性,而且只有从矛盾的各个方面着手研究,才有可能了解其总体。所谓了解矛盾的各个方面,就是了解它们每一方面各占何等特定的地位,各用何种具体形式和对方发生互相依存又互相矛盾的关系,在互相依存又互相矛盾中,以及依存破裂后,又各用何种具体的方法和对方作斗争。研究这些问题,是十分重要的事情。列宁说:马克思主义的最本质的东西,马克思主义的活的灵魂,就在于具体地分析具体的情况[11]。就是说的这个意思。我们的教条主义者违背列宁的指示,从来不用脑筋具体地分析任何事物,做起文章或演说来,总是空洞无物的八股调,在我们党内造成了一种极坏的作风。\n研究问题,忌带主观性、片面性和表面性。所谓主观性,就是不知道客观地看问题,也就是不知道用唯物的观点去看问题。这一点,我在《实践论》一文中已经说过了。所谓片面性,就是不知道全面地看问题。例如:只了解中国一方、不了解日本一方,只了解共产党一方、不了解国民党一方,只了解无产阶级一方、不了解资产阶级一方,只了解农民一方、不了解地主一方,只了解顺利情形一方、不了解困难情形一方,只了解过去一方、不了解将来一方,只了解个体一方、不了解总体一方,只了解缺点一方、不了解成绩一方,只了解原告一方、不了解被告一方,只了解革命的秘密工作一方、不了解革命的公开工作一方,如此等等。一句话,不了解矛盾各方的特点。这就叫做片面地看问题。或者叫做只看见局部,不看见全体,只看见树木,不看见森林。这样,是不能找出解决矛盾的方法的,是不能完成革命任务的,是不能做好所任工作的,是不能正确地发展党内的思想斗争的。孙子论军事说:“知彼知己,百战不殆。”[12]他说的是作战的双方。唐朝人魏征说过:“兼听则明,偏信则暗。”[13]也懂得片面性不对。可是我们的同志看问题,往往带片面性,这样的人就往往碰钉子。《水浒传》上宋江三打祝家庄[14],两次都因情况不明,方法不对,打了败仗。后来改变方法,从调查情形入手,于是熟悉了盘陀路,拆散了李家庄、扈家庄和祝家庄的联盟,并且布置了藏在敌人营盘里的伏兵,用了和外国故事中所说木马计[15]相像的方法,第三次就打了胜仗。《水浒传》上有很多唯物辩证法的事例,这个三打祝家庄,算是最好的一个。列宁说:“要真正地认识对象,就必须把握和研究它的一切方面、一切联系和‘媒介’。我们决不会完全地作到这一点,可是要求全面性,将使我们防止错误,防止僵化。”[16]我们应该记得他的话。表面性,是对矛盾总体和矛盾各方的特点都不去看,否认深入事物里面精细地研究矛盾特点的必要,仅仅站在那里远远地望一望,粗枝大叶地看到一点矛盾的形相,就想动手去解决矛盾(答复问题、解决纠纷、处理工作、指挥战争)。这样的做法,没有不出乱子的。中国的教条主义和经验主义的同志们所以犯错误,就是因为他们看事物的方法是主观的、片面的和表面的。片面性、表面性也是主观性,因为一切客观事物本来是互相联系的和具有内部规律的,人们不去如实地反映这些情况,而只是片面地或表面地去看它们,不认识事物的互相联系,不认识事物的内部规律,所以这种方法是主观主义的。\n不但事物发展的全过程中的矛盾运动,在其相互联结上,在其各方情况上,我们必须注意其特点,而且在过程发展的各个阶段中,也有其特点,也必须注意。\n事物发展过程的根本矛盾及为此根本矛盾所规定的过程的本质,非到过程完结之日,是不会消灭的;但是事物发展的长过程中的各个发展的阶段,情形又往往互相区别。这是因为事物发展过程的根本矛盾的性质和过程的本质虽然没有变化,但是根本矛盾在长过程中的各个发展阶段上采取了逐渐激化的形式。并且,被根本矛盾所规定或影响的许多大小矛盾中,有些是激化了,有些是暂时地或局部地解决了,或者缓和了,又有些是发生了,因此,过程就显出阶段性来。如果人们不去注意事物发展过程中的阶段性,人们就不能适当地处理事物的矛盾。\n例如,自由竞争时代的资本主义发展为帝国主义,这时,无产阶级和资产阶级这两个根本矛盾着的阶级的性质和这个社会的资本主义的本质,并没有变化;但是,两阶级的矛盾激化了,独占资本和自由资本之间的矛盾发生了,宗主国和殖民地的矛盾激化了,各资本主义国家间的矛盾即由各国发展不平衡的状态而引起的矛盾特别尖锐地表现出来了,因此形成了资本主义的特殊阶段,形成了帝国主义阶段。列宁主义之所以成为帝国主义和无产阶级革命时代的马克思主义,就是因为列宁和斯大林正确地说明了这些矛盾,并正确地作出了解决这些矛盾的无产阶级革命的理论和策略。\n拿从辛亥革命[17]开始的中国资产阶级民主革命过程的情形来看,也有了若干特殊阶段。特别是在资产阶级领导时期的革命和在无产阶级领导时期的革命,区别为两个很大不同的历史阶段。这就是:由于无产阶级的领导,根本地改变了革命的面貌,引出了阶级关系的新调度,农民革命的大发动,反帝国主义和反封建主义的革命彻底性,由民主革命转变到社会主义革命的可能性,等等。所有这些,都是在资产阶级领导革命时期不可能出现的。虽然整个过程中根本矛盾的性质,过程之反帝反封建的民主革命的性质(其反面是半殖民地半封建的性质),并没有变化,但是,在这长时间中,经过了辛亥革命失败和北洋军阀统治,第一次民族统一战线的建立和一九二四年至一九二七年的革命,统一战线破裂和资产阶级转入反革命,新的军阀战争,土地革命战争,第二次民族统一战线建立和抗日战争等等大事变,二十多年间经过了几个发展阶段。在这些阶段中,包含着有些矛盾激化了(例如土地革命战争和日本侵入东北四省[18]),有些矛盾部分地或暂时地解决了(例如北洋军阀的被消灭,我们没收了地主的土地),有些矛盾重新发生了(例如新军阀之间的斗争,南方各革命根据地丧失后地主又重新收回土地)等等特殊的情形。\n研究事物发展过程中的各个发展阶段上的矛盾的特殊性,不但必须在其联结上、在其总体上去看,而且必须从各个阶段中矛盾的各个方面去看。\n例如国共两党。国民党方面,在第一次统一战线时期,因为它实行了孙中山的联俄、联共、援助工农的三大政策,所以它是革命的、有朝气的,它是各阶级的民主革命的联盟。一九二七年以后,国民党变到了与此相反的方面,成了地主和大资产阶级的反动集团。一九三六年十二月西安事变[19]后又开始向停止内战、联合共产党共同反对日本帝国主义这个方面转变。这就是国民党在三个阶段上的特点。形成这些特点,当然有种种的原因。中国共产党方面,在第一次统一战线时期,它是幼年的党,它英勇地领导了一九二四年至一九二七年的革命;但在对于革命的性质、任务和方法的认识方面,却表现了它的幼年性,因此在这次革命的后期所发生的陈独秀主义[20]能够起作用,使这次革命遭受了失败。一九二七年以后,它又英勇地领导了土地革命战争,创立了革命的军队和革命的根据地,但是它也犯过冒险主义的错误,使军队和根据地都受了很大的损失。一九三五年以后,它又纠正了冒险主义的错误,领导了新的抗日的统一战线,这个伟大的斗争现在正在发展。在这个阶段上,共产党是一个经过了两次革命的考验、有了丰富的经验的党。这些就是中国共产党在三个阶段上的特点。形成这些特点也有种种的原因。不研究这些特点,就不能了解两党在各个发展阶段上的特殊的相互关系:统一战线的建立,统一战线的破裂,再一个统一战线的建立。而要研究两党的种种特点,更根本的就必须研究这两党的阶级基础以及因此在各个时期所形成的它们和其它方面的矛盾的对立。例如,国民党在它第一次联合共产党的时期,一方面有和国外帝国主义的矛盾,因而它反对帝国主义;另一方面有和国内人民大众的矛盾,它在口头上虽然允许给予劳动人民以许多的利益,但在实际上则只给予很少的利益,或者简直什么也不给。在它进行反共战争的时期,则和帝国主义、封建主义合作反对人民大众,一笔勾销了人民大众原来在革命中所争得的一切利益,激化了它和人民大众的矛盾。现在抗日时期,国民党和日本帝国主义有矛盾,它一面要联合共产党,同时它对共产党和国内人民并不放松其斗争和压迫。共产党则无论在哪一时期,均和人民大众站在一道,反对帝国主义和封建主义;但在现在的抗日时期,由于国民党表示抗日,它对国民党和国内封建势力,也就采取了缓和的政策。由于这些情况,所以或者造成了两党的联合,或者造成了两党的斗争,而且即使在两党联合的时期也有又联合又斗争的复杂的情况。如果我们不去研究这些矛盾方面的特点,我们就不但不能了解这两个党各各和其它方面的关系,也不能了解两党之间的相互关系。\n由此看来,不论研究何种矛盾的特性——各个物质运动形式的矛盾,各个运动形式在各个发展过程中的矛盾,各个发展过程的矛盾的各方面,各个发展过程在其各个发展阶段上的矛盾以及各个发展阶段上的矛盾的各方面,研究所有这些矛盾的特性,都不能带主观随意性,必须对它们实行具体的分析。离开具体的分析,就不能认识任何矛盾的特性。我们必须时刻记得列宁的话:对于具体的事物作具体的分析。\n这种具体的分析,马克思、恩格斯首先给了我们以很好的模范。\n当马克思、恩格斯把这事物矛盾的法则应用到社会历史过程的研究的时候,他们看出生产力和生产关系之间的矛盾,看出剥削阶级和被剥削阶级之间的矛盾以及由于这些矛盾所产生的经济基础和政治及思想等上层建筑之间的矛盾,而这些矛盾如何不可避免地会在各种不同的阶级社会中,引出各种不同的社会革命。\n马克思把这一法则应用到资本主义社会经济结构的研究的时候,他看出这一社会的基本矛盾在于生产的社会性和占有制的私人性之间的矛盾。这个矛盾表现于在各别企业中的生产的有组织性和在全社会中的生产的无组织性之间的矛盾。这个矛盾的阶级表现则是资产阶级和无产阶级之间的矛盾。\n由于事物范围的极其广大,发展的无限性,所以,在一定场合为普遍性的东西,而在另一一定场合则变为特殊性。反之,在一定场合为特殊性的东西,而在另一一定场合则变为普遍性。资本主义制度所包含的生产社会化和生产资料私人占有制的矛盾,是所有有资本主义的存在和发展的各国所共有的东西,对于资本主义说来,这是矛盾的普遍性。但是资本主义的这种矛盾,乃是一般阶级社会发展在一定历史阶段上的东西,对于一般阶级社会中的生产力和生产关系的矛盾说来,这是矛盾的特殊性。然而,当着马克思把资本主义社会这一切矛盾的特殊性解剖出来之后,同时也就更进一步地、更充分地、更完全地把一般阶级社会中这个生产力和生产关系的矛盾的普遍性阐发出来了。\n由于特殊的事物是和普遍的事物联结的,由于每一个事物内部不但包含了矛盾的特殊性,而且包含了矛盾的普遍性,普遍性即存在于特殊性之中,所以,当着我们研究一定事物的时候,就应当去发现这两方面及其互相联结,发现一事物内部的特殊性和普遍性的两方面及其互相联结,发现一事物和它以外的许多事物的互相联结。斯大林在他的名著《论列宁主义基础》一书中说明列宁主义的历史根源的时候,他分析了列宁主义所由产生的国际环境,分析了在帝国主义条件下已经发展到极点的资本主义的诸矛盾,以及这些矛盾使无产阶级革命成为直接实践的问题,并造成了直接冲击资本主义的良好的条件。不但如此,他又分析了为什么俄国成为列宁主义的策源地,分析了沙皇俄国当时是帝国主义一切矛盾的集合点以及俄国无产阶级所以能够成为国际的革命无产阶级的先锋队的原因。这样,斯大林分析了帝国主义的矛盾的普遍性,说明列宁主义是帝国主义和无产阶级革命时代的马克思主义;又分析了沙俄帝国主义在这一般矛盾中所具有的特殊性,说明俄国成了无产阶级革命理论和策略的故乡,而在这种特殊性中间就包含了矛盾的普遍性。斯大林的这种分析,给我们提供了认识矛盾的特殊性和普遍性及其互相联结的模范。\n马克思和恩格斯,同样地列宁和斯大林,他们对于应用辩证法到客观现象的研究的时候,总是指导人们不要带上任何的主观随意性,而必须从客观的实际运动所包含的具体的条件,去看出这些现象中的具体的矛盾、矛盾各方面的具体的地位以及矛盾的具体的相互关系。我们的教条主义者因为没有这种研究态度,所以弄得一无是处。我们必须以教条主义的失败为鉴戒,学会这种研究态度,舍此没有第二种研究法。\n矛盾的普遍性和矛盾的特殊性的关系,就是矛盾的共性和个性的关系。其共性是矛盾存在于一切过程中,并贯串于一切过程的始终,矛盾即是运动,即是事物,即是过程,也即是思想。否认事物的矛盾就是否认了一切。这是共通的道理,古今中外,概莫能外。所以它是共性,是绝对性。然而这种共性,即包含于一切个性之中,无个性即无共性。假如除去一切个性,还有什么共性呢?因为矛盾的各各特殊,所以造成了个性。一切个性都是有条件地暂时地存在的,所以是相对的。\n这一共性个性、绝对相对的道理,是关于事物矛盾的问题的精髓,不懂得它,就等于抛弃了辩证法。\n四 主要的矛盾和主要的矛盾方面 在矛盾特殊性的问题中,还有两种情形必须特别地提出来加以分析,这就是主要的矛盾和主要的矛盾方面。\n在复杂的事物的发展过程中,有许多的矛盾存在,其中必有一种是主要的矛盾,由于它的存在和发展规定或影响着其它矛盾的存在和发展。\n在资本主义社会中,无产阶级和资产阶级这两个矛盾着的力量是主要的矛盾;其它的矛盾力量,例如,残存的封建阶级和资产阶级的矛盾,农民小资产者和资产阶级的矛盾,无产阶级和农民小资产者的矛盾,自由资产阶级和垄断资产阶级的矛盾,资产阶级的民主主义和资产阶级的法西斯主义的矛盾,资本主义国家相互间的矛盾,帝国主义和殖民地的矛盾,以及其它的矛盾,都为这个主要的矛盾力量所规定、所影响。\n半殖民地的国家如中国,其主要矛盾和非主要矛盾的关系呈现着复杂的情况。\n当着帝国主义向这种国家举行侵略战争的时候,这种国家的内部各阶级,除开一些叛国分子以外,能够暂时地团结起来举行民族战争去反对帝国主义。这时,帝国主义和这种国家之间的矛盾成为主要的矛盾,而这种国家内部各阶级的一切矛盾(包括封建制度和人民大众之间这个主要矛盾在内),便都暂时地降到次要和服从的地位。中国一八四○年的鸦片战争[21],一八九四年的中日战争[22],一九○○年的义和团战争[23]和目前的中日战争,都有这种情形。\n然而在另一种情形之下,则矛盾的地位起了变化。当着帝国主义不是用战争压迫而是用政治、经济、文化等比较温和的形式进行压迫的时候,半殖民地国家的统治阶级就会向帝国主义投降,二者结成同盟,共同压迫人民大众。这种时候,人民大众往往采取国内战争的形式,去反对帝国主义和封建阶级的同盟,而帝国主义则往往采取间接的方式去援助半殖民地国家的反动派压迫人民,而不采取直接行动,显出了内部矛盾的特别尖锐性。中国的辛亥革命战争,一九二四年至一九二七年的革命战争,一九二七年以后的十年土地革命战争,都有这种情形。还有半殖民地国家各个反动的统治集团之间的内战,例如在中国的军阀战争,也属于这一类。\n当着国内革命战争发展到从根本上威胁帝国主义及其走狗国内反动派的存在的时候,帝国主义就往往采取上述方法以外的方法,企图维持其统治:或者分化革命阵线的内部,或者直接出兵援助国内反动派。这时,外国帝国主义和国内反动派完全公开地站在一个极端,人民大众则站在另一极端,成为一个主要矛盾,而规定或影响其它矛盾的发展状态。十月革命后各资本主义国家援助俄国反动派,是武装干涉的例子。一九二七年的蒋介石的叛变,是分化革命阵线的例子。\n然而不管怎样,过程发展的各个阶段中,只有一种主要的矛盾起着领导的作用,是完全没有疑义的。\n由此可知,任何过程如果有多数矛盾存在的话,其中必定有一种是主要的,起着领导的、决定的作用,其它则处于次要和服从的地位。因此,研究任何过程,如果是存在着两个以上矛盾的复杂过程的话,就要用全力找出它的主要矛盾。捉住了这个主要矛盾,一切问题就迎刃而解了。这是马克思研究资本主义社会告诉我们的方法。列宁和斯大林研究帝国主义和资本主义总危机的时候,列宁和斯大林研究苏联经济的时候,也告诉了这种方法。万千的学问家和实行家,不懂得这种方法,结果如堕烟海,找不到中心,也就找不到解决矛盾的方法。\n不能把过程中所有的矛盾平均看待,必须把它们区别为主要的和次要的两类,着重于捉住主要的矛盾,已如上述。但是在各种矛盾之中,不论是主要的或次要的,矛盾着的两个方面,又是否可以平均看待呢?也是不可以的。无论什么矛盾,矛盾的诸方面,其发展是不平衡的。有时候似乎势均力敌,然而这只是暂时的和相对的情形,基本的形态则是不平衡。矛盾着的两方面中,必有一方面是主要的,他方面是次要的。其主要的方面,即所谓矛盾起主导作用的方面。事物的性质,主要地是由取得支配地位的矛盾的主要方面所规定的。\n然而这种情形不是固定的,矛盾的主要和非主要的方面互相转化着,事物的性质也就随着起变化。在矛盾发展的一定过程或一定阶段上,主要方面属于甲方,非主要方面属于乙方;到了另一发展阶段或另一发展过程时,就互易其位置,这是依靠事物发展中矛盾双方斗争的力量的增减程度来决定的。\n我们常常说“新陈代谢”这句话。新陈代谢是宇宙间普遍的永远不可抵抗的规律。依事物本身的性质和条件,经过不同的飞跃形式,一事物转化为他事物,就是新陈代谢的过程。任何事物的内部都有其新旧两个方面的矛盾,形成为一系列的曲折的斗争。斗争的结果,新的方面由小变大,上升为支配的东西;旧的方面则由大变小,变成逐步归于灭亡的东西。而一当新的方面对于旧的方面取得支配地位的时候,旧事物的性质就变化为新事物的性质。由此可见,事物的性质主要地是由取得支配地位的矛盾的主要方面所规定的。取得支配地位的矛盾的主要方面起了变化,事物的性质也就随着起变化。\n在资本主义社会中,资本主义已从旧的封建主义社会时代的附庸地位,转化成了取得支配地位的力量,社会的性质也就由封建主义的变为资本主义的。在新的资本主义社会时代,封建势力则由原来处在支配地位的力量转化为附庸的力量,随着也就逐步地归于消灭了,例如英法诸国就是如此。随着生产力的发展,资产阶级由新的起进步作用的阶级,转化为旧的起反动作用的阶级,以至于最后被无产阶级所推翻,而转化为私有的生产资料被剥夺和失去权力的阶级,这个阶级也就要逐步归于消灭了。人数比资产阶级多得多、并和资产阶级同时生长、但被资产阶级统治着的无产阶级,是一个新的力量,它由初期的附属于资产阶级的地位,逐步地壮大起来,成为独立的和在历史上起主导作用的阶级,以至最后夺取政权成为统治阶级。这时,社会的性质,就由旧的资本主义的社会转化成了新的社会主义的社会。这就是苏联已经走过和一切其它国家必然要走的道路。\n就中国的情形来说,帝国主义处在形成半殖民地这种矛盾的主要地位,压迫中国人民,中国则由独立国变为半殖民地。然而事情必然会变化,在双方斗争的局势中,中国人民在无产阶级领导之下所生长起来的力量必然会把中国由半殖民地变为独立国,而帝国主义则将被打倒,旧中国必然要变为新中国。\n旧中国变为新中国,还包含着国内旧的封建势力和新的人民势力之间的情况的变化。旧的封建地主阶级将被打倒,由统治者变为被统治者,这个阶级也就会要逐步归于消灭。人民则将在无产阶级领导之下,由被统治者变为统治者。这时,中国社会的性质就会起变化,由旧的半殖民地和半封建的社会变为新的民主的社会。\n这种互相转化的事情,过去已有经验。统治中国将近三百年的清朝帝国,曾在辛亥革命时期被打倒;而孙中山领导的革命同盟会,则曾经一度取得了胜利。在一九二四年至一九二七年的革命战争中,共产党和国民党联合的南方革命势力,曾经由弱小的力量变得强大起来,取得了北伐的胜利;而称雄一时的北洋军阀则被打倒了。一九二七年,共产党领导的人民力量,受了国民党反动势力的打击,变得很小了;但因肃清了自己内部的机会主义,就又逐步地壮大起来。在共产党领导的革命根据地内,农民由被统治者转化为统治者,地主则作了相反的转化。世界上总是这样以新的代替旧的,总是这样新陈代谢、除旧布新或推陈出新的。\n革命斗争中的某些时候,困难条件超过顺利条件,在这种时候,困难是矛盾的主要方面,顺利是其次要方面。然而由于革命党人的努力,能够逐步地克服困难,开展顺利的新局面,困难的局面让位于顺利的局面。一九二七年中国革命失败后的情形,中国红军在长征[24]中的情形,都是如此。现在的中日战争,中国又处在困难地位,但是我们能够改变这种情况,使中日双方的情况发生根本的变化。在相反的情形之下,顺利也能转化为困难,如果是革命党人犯了错误的话。一九二四年至一九二七年的革命的胜利,变为失败了。一九二七年以后在南方各省发展起来的革命根据地,至一九三四年都失败了。\n研究学问的时候,由不知到知的矛盾也是如此。当着我们刚才开始研究马克思主义的时候,对于马克思主义的无知或知之不多的情况,和马克思主义的知识之间,互相矛盾着。然而由于努力学习,可以由无知转化为有知,由知之不多转化为知之甚多,由对于马克思主义的盲目性改变为能够自由运用马克思主义。\n有人觉得有些矛盾并不是这样。例如,生产力和生产关系的矛盾,生产力是主要的;理论和实践的矛盾,实践是主要的;经济基础和上层建筑的矛盾,经济基础是主要的:它们的地位并不互相转化。这是机械唯物论的见解,不是辩证唯物论的见解。诚然,生产力、实践、经济基础,一般地表现为主要的决定的作用,谁不承认这一点,谁就不是唯物论者。然而,生产关系、理论、上层建筑这些方面,在一定条件之下,又转过来表现其为主要的决定的作用,这也是必须承认的。当着不变更生产关系,生产力就不能发展的时候,生产关系的变更就起了主要的决定的作用。当着如同列宁所说“没有革命的理论,就不会有革命的运动”[25]的时候,革命理论的创立和提倡就起了主要的决定的作用。当着某一件事情(任何事情都是一样)要做,但是还没有方针、方法、计划或政策的时候,确定方针、方法、计划或政策,也就是主要的决定的东西。当着政治文化等等上层建筑阻碍着经济基础的发展的时候,对于政治上和文化上的革新就成为主要的决定的东西了。我们这样说,是否违反了唯物论呢?没有。因为我们承认总的历史发展中是物质的东西决定精神的东西,是社会的存在决定社会的意识;但是同时又承认而且必须承认精神的东西的反作用,社会意识对于社会存在的反作用,上层建筑对于经济基础的反作用。这不是违反唯物论,正是避免了机械唯物论,坚持了辩证唯物论。\n研究矛盾特殊性的问题中,如果不研究过程中主要的矛盾和非主要的矛盾以及矛盾之主要的方面和非主要的方面这两种情形,也就是说不研究这两种矛盾情况的差别性,那就将陷入抽象的研究,不能具体地懂得矛盾的情况,因而也就不能找出解决矛盾的正确的方法。这两种矛盾情况的差别性或特殊性,都是矛盾力量的不平衡性。世界上没有绝对地平衡发展的东西,我们必须反对平衡论,或均衡论。同时,这种具体的矛盾状况,以及矛盾的主要方面和非主要方面在发展过程中的变化,正是表现出新事物代替旧事物的力量。对于矛盾的各种不平衡情况的研究,对于主要的矛盾和非主要的矛盾、主要的矛盾方面和非主要的矛盾方面的研究,成为革命政党正确地决定其政治上和军事上的战略战术方针的重要方法之一,是一切共产党人都应当注意的。\n五 矛盾诸方面的同一性和斗争性 在懂得了矛盾的普遍性和特殊性的问题之后,我们必须进而研究矛盾诸方面的同一性和斗争性的问题。\n同一性、统一性、一致性、互相渗透、互相贯通、互相依赖(或依存)、互相联结或互相合作,这些不同的名词都是一个意思,说的是如下两种情形:第一、事物发展过程中的每一种矛盾的两个方面,各以和它对立着的方面为自己存在的前提,双方共处于一个统一体中;第二、矛盾着的双方,依据一定的条件,各向着其相反的方面转化。这些就是所谓同一性。\n列宁说:“辩证法是这样的一种学说:它研究对立怎样能够是同一的,又怎样成为同一的(怎样变成同一的),——在怎样的条件之下它们互相转化,成为同一的,——为什么人的头脑不应当把这些对立看作死的、凝固的东西,而应当看作生动的、有条件的、可变动的、互相转化的东西。”[26]\n列宁这段话是什么意思呢?\n一切过程中矛盾着的各方面,本来是互相排斥、互相斗争、互相对立的。世界上一切事物的过程里和人们的思想里,都包含着这样带矛盾性的方面,无一例外。单纯的过程只有一对矛盾,复杂的过程则有一对以上的矛盾。各对矛盾之间,又互相成为矛盾。这样地组成客观世界的一切事物和人们的思想,并推使它们发生运动。\n如此说来,只是极不同一,极不统一,怎样又说是同一或统一呢?\n原来矛盾着的各方面,不能孤立地存在。假如没有和它作对的矛盾的一方,它自己这一方就失去了存在的条件。试想一切矛盾着的事物或人们心中矛盾着的概念,任何一方面能够独立地存在吗?没有生,死就不见;没有死,生也不见。没有上,无所谓下;没有下,也无所谓上。没有祸,无所谓福;没有福,也无所谓祸。没有顺利,无所谓困难;没有困难,也无所谓顺利。没有地主,就没有佃农;没有佃农,也就没有地主。没有资产阶级,就没有无产阶级;没有无产阶级,也就没有资产阶级。没有帝国主义的民族压迫,就没有殖民地和半殖民地;没有殖民地和半殖民地,也就没有帝国主义的民族压迫。一切对立的成分都是这样,因一定的条件,一面互相对立,一面又互相联结、互相贯通、互相渗透、互相依赖,这种性质,叫做同一性。一切矛盾着的方面都因一定条件具备着不同一性,所以称为矛盾。然而又具备着同一性,所以互相联结。列宁所谓辩证法研究“对立怎样能够是同一的”,就是说的这种情形。怎样能够呢?因为互为存在的条件。这是同一性的第一种意义。\n然而单说了矛盾双方互为存在的条件,双方之间有同一性,因而能够共处于一个统一体中,这样就够了吗?还不够。事情不是矛盾双方互相依存就完了,更重要的,还在于矛盾着的事物的互相转化。这就是说,事物内部矛盾着的两方面,因为一定的条件而各向着和自己相反的方面转化了去,向着它的对立方面所处的地位转化了去。这就是矛盾的同一性的第二种意义。\n为什么这里也有同一性呢?你们看,被统治的无产阶级经过革命转化为统治者,原来是统治者的资产阶级却转化为被统治者,转化到对方原来所占的地位。苏联已经是这样做了,全世界也将要这样做。试问其间没有在一定条件之下的联系和同一性,如何能够发生这样的变化呢?\n曾在中国近代历史的一定阶段上起过某种积极作用的国民党,因为它的固有的阶级性和帝国主义的引诱(这些就是条件),在一九二七年以后转化为反革命,又由于中日矛盾的尖锐化和共产党的统一战线政策(这些就是条件),而被迫着赞成抗日。矛盾着的东西这一个变到那一个,其间包含了一定的同一性。\n我们实行过的土地革命,已经是并且还将是这样的过程,拥有土地的地主阶级转化为失掉土地的阶级,而曾经是失掉土地的农民却转化为取得土地的小私有者。有无、得失之间,因一定条件而互相联结,二者具有同一性。在社会主义条件之下,农民的私有制又将转化为社会主义农业的公有制,苏联已经这样做了,全世界将来也会这样做。私产和公产之间有一条由此达彼的桥梁,哲学上名之曰同一性,或互相转化、互相渗透。\n巩固无产阶级的专政或人民的专政,正是准备着取消这种专政,走到消灭任何国家制度的更高阶段去的条件。建立和发展共产党,正是准备着消灭共产党和一切政党制度的条件。建立共产党领导的革命军,进行革命战争,正是准备着永远消灭战争的条件。这许多相反的东西,同时却是相成的东西。\n知道,战争与和平是互相转化的。战争转化为和平,例如第一次世界大战转化为战后的和平,中国的内战现在也停止了,出现了国内的和平。和平转化为战争,例如一九二七年的国共合作转化为战争,现在的世界和平局面也可能转化为第二次世界大战。为什么是这样?因为在阶级社会中战争与和平这样矛盾着的事物,在一定条件下具备着同一性。\n一切矛盾着的东西,互相联系着,不但在一定条件之下共处于一个统一体中,而且在一定条件之下互相转化,这就是矛盾的同一性的全部意义。列宁所谓“怎样成为同一的(怎样变成同一的),——在怎样的条件之下它们互相转化,成为同一的”,就是这个意思。\n“为什么人的头脑不应当把这些对立看作死的、凝固的东西,而应当看作生动的、有条件的、可变动的、互相转化的东西”呢?因为客观事物本来是如此的。客观事物中矛盾着的诸方面的统一或同一性,本来不是死的、凝固的,而是生动的、有条件的、可变动的、暂时的、相对的东西,一切矛盾都依一定条件向它们的反面转化着。这种情况,反映在人们的思想里,就成了马克思主义的唯物辩证法的宇宙观。只有现在的和历史上的反动的统治阶级以及为他们服务的形而上学,不是把对立的事物当作生动的、有条件的、可变动的、互相转化的东西去看,而是当作死的、凝固的东西去看,并且把这种错误的看法到处宣传,迷惑人民群众,以达其继续统治的目的。共产党人的任务就在于揭露反动派和形而上学的错误思想,宣传事物的本来的辩证法,促成事物的转化,达到革命的目的。\n所谓矛盾在一定条件下的同一性,就是说,我们所说的矛盾乃是现实的矛盾,具体的矛盾,而矛盾的互相转化也是现实的、具体的。神话中的许多变化,例如《山海经》中所说的“夸父追日”[27],《淮南子》中所说的“羿射九日”[28],《西游记》中所说的孙悟空七十二变[29]和《聊斋志异》[30]中的许多鬼狐变人的故事等等,这种神话中所说的矛盾的互相变化,乃是无数复杂的现实矛盾的互相变化对于人们所引起的一种幼稚的、想象的、主观幻想的变化,并不是具体的矛盾所表现出来的具体的变化。马克思说:“任何神话都是用想象和借助想象以征服自然力,支配自然力,把自然力加以形象化;因而,随着这些自然力之实际上被支配,神话也就消失了。”[31]这种神话中的(还有童话中的)千变万化的故事,虽然因为它们想象出人们征服自然力等等,而能够吸引人们的喜欢,并且最好的神话具有“永久的魅力”[32](马克思),但神话并不是根据具体的矛盾之一定的条件而构成的,所以它们并不是现实之科学的反映。这就是说,神话或童话中矛盾构成的诸方面,并不是具体的同一性,只是幻想的同一性。科学地反映现实变化的同一性的,就是马克思主义的辩证法。\n为什么鸡蛋能够转化为鸡子,而石头不能够转化为鸡子呢?为什么战争与和平有同一性,而战争与石头却没有同一性呢?为什么人能生人不能生出其它的东西呢?没有别的,就是因为矛盾的同一性要在一定的必要的条件之下。缺乏一定的必要的条件,就没有任何的同一性。\n为什么俄国在一九一七年二月的资产阶级民主革命和同年十月的无产阶级社会主义革命直接地联系着,而法国资产阶级革命没有直接地联系于社会主义的革命,一八七一年的巴黎公社终于失败了呢?为什么蒙古和中亚细亚的游牧制度又直接地和社会主义联系了呢?为什么中国的革命可以避免资本主义的前途,可以和社会主义直接联系起来,不要再走西方国家的历史老路,不要经过一个资产阶级专政的时期呢?没有别的,都是由于当时的具体条件。一定的必要的条件具备了,事物发展的过程就发生一定的矛盾,而且这种或这些矛盾互相依存,又互相转化,否则,一切都不可能。\n同一性的问题如此。那末,什么是斗争性呢?同一性和斗争性的关系是怎样的呢?\n列宁说:“对立的统一(一致、同一、合一),是有条件的、一时的、暂存的、相对的。互相排斥的对立的斗争则是绝对的,正如发展、运动是绝对的一样。”[33]\n列宁这段话是什么意思呢?\n一切过程都有始有终,一切过程都转化为它们的对立物。一切过程的常住性是相对的,但是一种过程转化为他种过程的这种变动性则是绝对的。\n无论什么事物的运动都采取两种状态,相对地静止的状态和显着地变动的状态。两种状态的运动都是由事物内部包含的两个矛盾着的因素互相斗争所引起的。当着事物的运动在第一种状态的时候,它只有数量的变化,没有性质的变化,所以显出好似静止的面貌。当着事物的运动在第二种状态的时候,它已由第一种状态中的数量的变化达到了某一个最高点,引起了统一物的分解,发生了性质的变化,所以显出显着地变化的面貌。我们在日常生活中所看见的统一、团结、联合、调和、均势、相持、僵局、静止、有常、平衡、凝聚、吸引等等,都是事物处在量变状态中所显现的面貌。而统一物的分解,团结、联合、调和、均势、相持、僵局、静止、有常、平衡、凝聚、吸引等等状态的破坏,变到相反的状态,便都是事物在质变状态中、在一种过程过渡到他种过程的变化中所显现的面貌。事物总是不断地由第一种状态转化为第二种状态,而矛盾的斗争则存在于两种状态中,并经过第二种状态而达到矛盾的解决。所以说,对立的统一是有条件的、暂时的、相对的,而对立的互相排除的斗争则是绝对的。\n前面我们曾经说,两个相反的东西中间有同一性,所以二者能够共处于一个统一体中,又能够互相转化,这是说的条件性,即是说在一定条件之下,矛盾的东西能够统一起来,又能够互相转化;无此一定条件,就不能成为矛盾,不能共居,也不能转化。由于一定的条件才构成了矛盾的同一性,所以说同一性是有条件的、相对的。这里我们又说,矛盾的斗争贯串于过程的始终,并使一过程向着他过程转化,矛盾的斗争无所不在,所以说矛盾的斗争性是无条件的、绝对的。\n有条件的相对的同一性和无条件的绝对的斗争性相结合,构成了一切事物的矛盾运动。\n我们中国人常说:“相反相成。”[34]就是说相反的东西有同一性。这句话是辩证法的,是违反形而上学的。“相反”就是说两个矛盾方面的互相排斥,或互相斗争。“相成”就是说在一定条件之下两个矛盾方面互相联结起来,获得了同一性。而斗争性即寓于同一性之中,没有斗争性就没有同一性。\n在同一性中存在着斗争性,在特殊性中存在着普遍性,在个性中存在着共性。拿列宁的话来说,叫做“在相对的东西里面有着绝对的东西”[35]。\n六 对抗在矛盾中的地位 在矛盾的斗争性的问题中,包含着对抗是什么的问题。我们回答道:对抗是矛盾斗争的一种形式,而不是矛盾斗争的一切形式。\n在人类历史中,存在着阶级的对抗,这是矛盾斗争的一种特殊的表现。剥削阶级和被剥削阶级之间的矛盾,无论在奴隶社会也好,封建社会也好,资本主义社会也好,互相矛盾着的两阶级,长期地并存于一个社会中,它们互相斗争着,但要待两阶级的矛盾发展到了一定的阶段的时候,双方才取外部对抗的形式,发展为革命。阶级社会中,由和平向战争的转化,也是如此。\n炸弹在未爆炸的时候,是矛盾物因一定条件共居于一个统一体中的时候。待至新的条件(发火)出现,才发生了爆炸。自然界中一切到了最后要采取外部冲突形式去解决旧矛盾产生新事物的现象,都有与此相仿佛的情形。\n认识这种情形,极为重要。它使我们懂得,在阶级社会中,革命和革命战争是不可避免的,舍此不能完成社会发展的飞跃,不能推翻反动的统治阶级,而使人民获得政权。共产党人必须揭露反动派所谓社会革命是不必要的和不可能的等等欺骗的宣传,坚持马克思列宁主义的社会革命论,使人民懂得,这不但是完全必要的,而且是完全可能的,整个人类的历史和苏联的胜利,都证明了这个科学的真理。\n但是我们必须具体地研究各种矛盾斗争的情况,不应当将上面所说的公式不适当地套在一切事物的身上。矛盾和斗争是普遍的、绝对的,但是解决矛盾的方法,即斗争的形式,则因矛盾的性质不同而不相同。有些矛盾具有公开的对抗性,有些矛盾则不是这样。根据事物的具体发展,有些矛盾是由原来还非对抗性的,而发展成为对抗性的;也有些矛盾则由原来是对抗性的,而发展成为非对抗性的。\n共产党内正确思想和错误思想的矛盾,如前所说,在阶级存在的时候,这是阶级矛盾对于党内的反映。这种矛盾,在开始的时候,或在个别的问题上,并不一定马上表现为对抗性的。但随着阶级斗争的发展,这种矛盾也就可能发展为对抗性的。苏联共产党的历史告诉我们:列宁、斯大林的正确思想和托洛茨基、布哈林等人的错误思想的矛盾,在开始的时候还没有表现为对抗的形式,但随后就发展为对抗的了。中国共产党的历史也有过这样的情形。我们党内许多同志的正确思想和陈独秀、张国焘[36]等人的错误思想的矛盾,在开始的时候也没有表现为对抗的形式,但随后就发展为对抗的了。目前我们党内的正确思想和错误思想的矛盾,没有表现为对抗的形式,如果犯错误的同志能够改正自己的错误,那就不会发展为对抗性的东西。因此,党一方面必须对于错误思想进行严肃的斗争,另方面又必须充分地给犯错误的同志留有自己觉悟的机会。在这样的情况下,过火的斗争,显然是不适当的。但如果犯错误的人坚持错误,并扩大下去,这种矛盾也就存在着发展为对抗性的东西的可能性。\n经济上城市和乡村的矛盾,在资本主义社会里面(那里资产阶级统治的城市残酷地掠夺乡村),在中国的国民党统治区域里面(那里外国帝国主义和本国买办大资产阶级所统治的城市极野蛮地掠夺乡村),那是极其对抗的矛盾。但在社会主义国家里面,在我们的革命根据地里面,这种对抗的矛盾就变为非对抗的矛盾,而当到达共产主义社会的时候,这种矛盾就会消灭。\n列宁说:“对抗和矛盾断然不同。在社会主义下,对抗消灭了,矛盾存在着。”[37]这就是说,对抗只是矛盾斗争的一种形式,而不是它的一切形式,不能到处套用这个公式。\n七 结 论 说到这里,我们可以总起来说几句。事物矛盾的法则,即对立统一的法则,是自然和社会的根本法则,因而也是思维的根本法则。它是和形而上学的宇宙观相反的。它对于人类的认识史是一个大革命。按照辩证唯物论的观点看来,矛盾存在于一切客观事物和主观思维的过程中,矛盾贯串于一切过程的始终,这是矛盾的普遍性和绝对性。矛盾着的事物及其每一个侧面各有其特点,这是矛盾的特殊性和相对性。矛盾着的事物依一定的条件有同一性,因此能够共居于一个统一体中,又能够互相转化到相反的方面去,这又是矛盾的特殊性和相对性。然而矛盾的斗争则是不断的,不管在它们共居的时候,或者在它们互相转化的时候,都有斗争的存在,尤其是在它们互相转化的时候,斗争的表现更为显着,这又是矛盾的普遍性和绝对性。当着我们研究矛盾的特殊性和相对性的时候,要注意矛盾和矛盾方面的主要的和非主要的区别;当着我们研究矛盾的普遍性和斗争性的时候,要注意矛盾的各种不同的斗争形式的区别。否则就要犯错误。如果我们经过研究真正懂得了上述这些要点,我们就能够击破违反马克思列宁主义基本原则的不利于我们的革命事业的那些教条主义的思想;也能够使有经验的同志们整理自己的经验,使之带上原则性,而避免重复经验主义的错误。这些,就是我们研究矛盾法则的一些简单的结论。\n注释\n[1] 见列宁《黑格尔〈哲学史讲演录〉一书摘要》(《列宁全集》第 55 卷,人民出版社 1990 年版,第 213 页)。\n[2] 参见列宁《谈谈辩证法问题》:“统一物之分为两个部分以及对它的矛盾着的部分的认识……,是辩证法的实质(是辩证法的‘本质’之一,是它的基本的特点或特征之一,甚至可说是它的最基本的特点或特征)。”并参见《黑格尔〈逻辑学〉一书摘要》中关于“辩证法的要素”部分:“可以把辩证法简要地规定为关于对立面的统一的学说。这样就会抓住辩证法的核心,可是这需要说明和发挥。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 305、192 页)\n[3] 德波林(一八八一——一九六三),苏联哲学家。一九二九年当选为苏联科学院院士。三十年代初,苏联哲学界发动对德波林学派的批判,认为他们犯了理论脱离实践、哲学脱离政治等唯心主义性质的错误。\n[4] 见列宁《谈谈辩证法问题》。新的译文是:“有两种基本的(或两种可能的?或两种在历史上常见的?)发展(进化)观点:认为发展是减少和增加,是重复;以及认为发展是对立面的统一(统一物之分为两个互相排斥的对立面以及它们之间的相互关系)。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 306 页)\n[5] 见《汉书•董仲舒传》。董仲舒(公元前一七九——前一○四)是孔子学派在西汉的主要代表,他曾经对汉武帝说:“道之大原出于天,天不变,道亦不变。”“道”是中国古代哲学家的通用语,它的意义是“道路”或“道理”,可作“法则”或“规律”解说。\n[6] 见恩格斯《反杜林论》第一编第十二节《辩证法。量和质》(《马克思恩格斯选集》第 3 卷,人民出版社 1972 年版,第 160 页)。\n[7] 见列宁《谈谈辩证法问题》。新的译文是:“承认(发现)自然界的(也包括精神的和社会的)一切现象和过程具有矛盾着的、相互排斥的、对立的倾向。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 306 页)\n[8] 以上所引恩格斯的三段话,均见恩格斯《反杜林论》第一编第十二节《辩证法。量和质》。其中第二段“高等数学的主要基础之一,就是矛盾……”,《反杜林论》中的原文是:“我们已经提到,高等数学的主要基础之一是这样一个矛盾:在一定条件下直线和曲线应当是一回事。高等数学还有另一个矛盾:在我们眼前相交的线,只要离开交点五六厘米,就应当认为是平行的、即使无限延长也不会相交的线。可是,高等数学利用这些和其它一些更加尖锐的矛盾获得了不仅是正确的、而且是初等数学所完全不能达到的成果。”(《马克思恩格斯选集》第 3 卷,人民出版社 1972 年版,第 160—161 页)\n[9] 见列宁《谈谈辩证法问题》(《列宁全集》第 55 卷,人民出版社 1990 年版,第 305—306 页)。\n[10] 见列宁《谈谈辩证法问题》(《列宁全集》第 55 卷,人民出版社 1990 年版,第 307 页)。\n[11] 参见本卷《中国革命战争的战略问题》注〔11〕。\n[12] 见《孙子•谋攻》。\n[13] 魏征(五八○——六四三),唐代初期的政治活动家和历史学家。本文引语见《资治通鉴》卷一百九十二。\n[14] 《水浒传》是中国描写农民战争的著名小说。宋江是这部小说中农民武装的主要领袖。祝家庄在农民武装根据地梁山泊的附近,这个庄的统治者祝朝奉,是一个大恶霸地主。\n[15] 木马计是希腊神话中的一个著名故事。据传说,古希腊人攻打特洛伊城,很久打不下来。后来,他们伪装撤退,在城下营房中留下了一匹腹内藏有一批勇士的大木马。特洛伊人不知道这是敌人的计策,把木马作为战利品拉进城去。深夜,勇士们走出木马,利用特洛伊人毫无戒备的时机,配合城外的军队,迅速地夺取了特洛伊城。\n[16] 见列宁《再论工会、目前局势及托洛茨基同志和布哈林同志的错误》。新的译文是:“要真正地认识事物,就必须把握住、研究清楚它的一切方面、一切联系和‘中介’。我们永远也不会完全做到这一点,但是,全面性这一要求可以使我们防止犯错误和防止僵化。”(《列宁全集》第 40 卷,人民出版社 1986 年版,第 291 页)\n[17] 见本卷《湖南农民运动考察报告》注〔3〕。\n[18] 见本卷《论反对日本帝国主义的策略》注〔5〕。\n[19] 参见本卷《关于蒋介石声明的声明》注〔1〕。\n[20] 见本卷《中国革命战争的战略问题》注〔4〕。\n[21] 见本卷《论反对日本帝国主义的策略》注〔35〕。\n[22] 一八九四年(甲午年)发生的中日战争,也称甲午战争。这次战争是日本军国主义者蓄意挑起的。日本军队先向朝鲜发动侵略并对中国的陆海军进行挑衅,继即大举侵入中国的东北。在战争中,中国军队曾经英勇作战,但是由于清朝政府的腐败以及缺乏坚决反对侵略的准备,中国方面遭到了失败。一八九五年,清朝政府和日本订立了可耻的马关条约,这个条约的主要内容是:中国割让台湾全岛及所有附属各岛屿、澎湖列岛和辽东半岛(后来在俄、德、法三国干涉下,日本同意由清政府偿付白银三千万两“赎还”该半岛),赔偿军费银二万万两,允许日本人在中国通商口岸开设工厂,开辟沙市、重庆、苏州、杭州等地为商埠。\n[23] 见本卷《论反对日本帝国主义的策略》注〔37〕。\n[24] 参见本卷《论反对日本帝国主义的策略》注〔22〕。\n[25] 见列宁《俄国社会民主党人的任务》(《列宁全集》第 2 卷,人民出版社 1984 年版,第 443 页);并见列宁《怎么办?》第一章第四节(《列宁全集》第 6 卷,人民出版社 1986 年版,第 23 页)。\n[26] 见列宁《黑格尔〈逻辑学〉一书摘要》。新的译文是:“辩证法是一种学说,它研究对立面怎样才能够同一,是怎样(怎样成为)同一的——在什么条件下它们是相互转化而同一的,——为什么人的头脑不应该把这些对立面看作僵死的、凝固的东西,而应该看作活生生的、有条件的、活动的、彼此转化的东西。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 90 页)\n[27] 《山海经》是一部中国古代地理著作,其中记载了不少远古的神话传说。夸父是《山海经•海外北经》上记载的一个神人。据说:“夸父与日逐走。入日,渴欲得饮,饮于河渭。河渭不足,北饮大泽。未至,道渴而死。弃其杖,化为邓林。”\n[28] 羿是中国古代传说中的英雄,“射日”是关于他善射的著名故事。据西汉淮南王刘安(公元前二世纪人)及其门客所著《淮南子》一书说:“尧之时,十日并出,焦禾稼,杀草木,而民无所食。猰豸、凿齿、九婴、大风、封狶、修蛇,皆为民害。尧乃使羿……上射十日而下杀猰豸。……万民皆喜。”东汉著作家王逸(公元二世纪人)关于屈原诗篇《天问》的注释说:“淮南言,尧时十日并出,草木焦枯。尧命羿仰射十日,中其九日……留其一日。”\n[29] 《西游记》是明代作家吴承恩着的一部神话小说。孙悟空是书中的主角。他是一个神猴,有七十二变的法术,能够随意变成各式各样的鸟兽虫鱼草木器物或者人形。\n[30] 《聊斋志异》是清代文学家蒲松龄着的短篇小说集,大部分是叙述神仙狐鬼的故事。\n[31] 见马克思《〈政治经济学批判〉导言》(《马克思恩格斯选集》第 2 卷,人民出版社 1972 年版,第 113 页)。\n[32] 见马克思《〈政治经济学批判〉导言》(《马克思恩格斯选集》第 2 卷,人民出版社 1972 年版,第 114 页)。\n[33] 见列宁《谈谈辩证法问题》。新的译文是:“对立面的统一(一致、同一、均势)是有条件的、暂时的、易逝的、相对的。相互排斥的对立面的斗争是绝对的,正如发展、运动是绝对的一样。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 306 页)\n[34] 见东汉著名史学家班固(三二——九二)所著《汉书•艺文志》,原文是:“诸子十家,其可观者,九家而已。皆起于王道既微,诸侯力政,时君世主,好恶殊方。是以九家之术,蜂出并作,各引一端,崇其所善,以此驰说,取合诸侯。其言虽殊,辟犹水火,相灭亦相生也。仁之与义,敬之与和,相反而皆相成也。”\n[35] 见列宁《谈谈辩证法问题》。新的译文是:“相对中有绝对。”(《列宁全集》第 55 卷,人民出版社 1990 年版,第 307 页)\n[36] 见本卷《论反对日本帝国主义的策略》注〔24〕。\n[37] 见列宁《在尼•布哈林〈过渡时期经济学〉一书上作的批注和评论》(《列宁全集》第 60 卷,人民出版社 1990 年版,第 282 页)。\n","date":"2024-11-18","permalink":"https://aituyaa.com/%E7%9F%9B%E7%9B%BE%E8%AE%BA/","summary":"\u003cp\u003e写于 1937.8\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e这篇哲学论文,是毛泽东继《实践论》之后,为了同一的目的,即为了克服存在于中国共产党内的严重的教条主义思想而写的,曾在延安的抗日军事政治大学作过讲演。在收入本书第一版的时候,作者作了部分的补充、删节和修改。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"矛盾论"},]
[{"content":"——(讲授提纲) 内含《实践论》与《矛盾论》的原始版本。\n按:《辩证法唯物论(讲授提纲)》写于1937年,曾在《抗战大学》第6期至第8期(1938年4月至6月)连载。据以录入的中国人民解放军政治学院训练部翻印本,年代不详。第二章第十一节和第三章中的部分章节分别为《实践论》与《矛盾论》的最初版本,与后来的毛选版本有较多出入。 文中所有注释均为录入者所加,文中不再说明。\n第一章 唯心论与唯物论 本章讨论下列各问题: (一)哲学中的两军对战;\n(二)唯心论与唯物论的区别;\n(三)唯心论发生与发展的根源;\n(四)唯物论发生与发展的根源。\n(一)哲学中的两军对战 全部哲学史,都是唯心论和唯物论这两个互相对抗的哲学派别的斗争和发展的历史,一切哲学思潮和派别,都是这两个基本派别的变相。\n各种哲学学说,都是隶属于一定社会阶级的人们所创造的。这些人们的意识,又是历史地被一定的社会生活所决定。所有的哲学学说,表现着一定社会阶级的需要,反映着社会生产力发展的水平和人类认识自然的历史阶段。哲学的命运,看哲学满足社会阶级的需要之程度如何而定。\n唯心论和唯物论的社会根源,存在于阶级的矛盾的社会结构中。最初唯心论之发生是原始野蛮人类迷妄无知的产物。此后生产力发展,促使科学知识也随之发展,唯心论理应衰退,唯物论理应起而代之。然而从古至今,唯心论不但不曾衰退,反而发展起来,同唯物论竟长争高,互不相下,原因就在于社会有阶级的划分。一方面压迫阶级为着自己的利益,不得不发展与巩固其唯心论学说;一方面被压迫阶级同样为着自己的利益,不得不发展与巩固其唯物论学说。唯心论和唯物论学说都是作为阶级斗争的工具而存在,在阶级没有消灭以前,唯心论和唯物论的对战是不会消灭的。唯心论在自己的历史发展过程中,代表剥削阶级的意识形态,起着反动的作用。唯物论则是革命阶级的宇宙观,他在阶级社会内,从对反动哲学的唯心论之不断的战斗中生长与发展起来。由此,哲学中唯心论与唯物论的斗争,始终反映着反动阶级与革命阶级在利害上的斗争。哲学中的某一倾向,不管哲学者自身意识到与否,结局总是被他们所属阶级的政治方向所左右的。哲学上的任何倾向,总是直接间接助长着他们所属阶级的根本的政治利害。在这个意义下,哲学中的一定倾向的贯彻,便是他们所属阶级的政策之特殊形态。\n马克思主义的哲学——辩证法唯物论的特征在于,在于要明确地理解一切社会意识(哲学也在内)的阶级性,公然声明它那无产阶级的性质,向有产阶级的唯心论哲学作坚决的斗争,并且把自己的特殊任务,从属于推翻资本主义组织、建立无产阶级专政,与建设社会主义社会的一般任务之下。在中国目前阶段上,哲学的任务,是从属于推翻帝国主义与半封建制度、彻底实现资产阶级的民主主义,并准备转变到社会主义与共产主义社会去的一般任务之下,哲学的理论与政治的实践是应该密切联系着的。\n(二)唯心论与唯物论的区别 唯心论与唯物论的根本区别在那里呢?在对于哲学的根本问题,即精神与物质的关系问题(意识与存在的关系问题)之相反的回答。唯心论认精神(意识,观念,主体)为世界一切的根源,物质(自然界及社会客体)不过为其附属物。唯物论认物质离精神而独立存在,精神不过为其附属物。从这个根本问题的相反的回答出发,就生出一切问题上的分歧意见来。\n在唯心论看来,世界或者是我们各种知觉的综合,或者是我们的或世界的理性所创造的精神过程。对外面的物质世界或者完全把它看成虚构的幻象,或者把它看成精神元素之物质的外壳。人类的认识,是主体的自动,是精神的自己产物。\n唯物论相反,认宇宙的统一就在它的物质性。精神(意识)是物质的本性之一,是物质发展到一定阶段时才发生的。自然,物质,客观世界存在于精神之外,离精神而独立。人的认识,是客观外界的反映。\n(三)唯心论发生与发展的根源 唯心论认物质为精神的产物,颠倒着实在世界的姿态,这种哲学的发生与发展的根源何在?\n前面说过,最初唯心论之发生是原始野蛮人类迷妄无知的产物。但在生产发展之后促使唯心论形成哲学思潮之首先的条件,乃是体力劳动与精神劳动的分裂。社会生产力发展的结果,社会发生分工,分工再发展,分出了专门从事精神劳动的人们。但在生产力贫弱时期,两者的分裂还没有达到完全分离的程度。到了阶级出现、私产发生,剥削成为支配阶级存在的基础之时,就起了大变化了,精神劳动成为支配阶级的特权,体力劳动成为被压迫阶级的命运。支配阶级开始颠倒地去考察自己与被压迫阶级之间的相互关系,不是劳动者给他们以生活资料,反而是他们以生活资料给与劳动者,因此他们鄙视体力劳动,发生了唯心论的见解。消灭体力劳动与精神劳动的区别是消灭唯心论哲学的条件之一。\n使唯心论哲学能够发展的社会根源,主要的还在于这种哲学意识它表现剥削阶级的利害。唯心论哲学在一切文化领域的优越,应该拿这个去说明。假如没有剥削阶级的存在,唯心论就会失掉它的社会根据。唯心论哲学之最后消灭,必须在阶级消灭与共产主义社会成立之后。\n使唯心论能够发达、深化,并有能力同唯物论斗争,还须在人类的认识过程中找寻其本源。人类在使用概念来思考的时候,存在着溜到唯心论去的可能性。人类在思考时不能不使用概念,这就容易使我们的认识分裂为二方面:一方面,是个别的与特殊性质的事物,一方面是一般性质的概念(例如“延安是城”这个判断)。特殊和一般本来是互相联系不可分裂的,分裂就脱离了客观真理。客观真理是表现于一般与特殊之一致的。没有特殊,一般就不存在;没有一般,也不会有特殊。把一般同特殊脱离开来,即把一般当作客观实体看待,把特殊只当作一般之存在的形式,这就是一切唯心论所采用的方法。一切唯心论者都是拿意识、精神或观念来代替离开人的意识而独立存在的客观实体的。从这里出发,唯心论者便强调人类意识在社会实践中的能动性,他们不能指出意识受物质限制的这种唯物论的真理,却主张只有意识是能够动的,物质不过是不动的集合体。加上被阶级的本性所驱策,唯心论者便用一切方法把意识的能动性夸张起来,片面地发展了它,使这一方面在心智之中无限制地胀大,成为支配的东西,掩蔽着别一方面并使之服从,而把这一人工地胀大的东西确定为一般的宇宙观,以至化为物神或偶象。经济学上的唯心论,过分夸大交换中非本质的一方面,把供求法则提高到资本主义的根本法则。许多人看到科学在社会生活上发生了能动的作用,不知道这种作用受一定的社会生产关系所规定与限制,而作出科学是社会发动力的结论。唯心论历史家把英雄看成历史的创造者,唯心论政治家把政治看成万能的东西,唯心论军事家实行拼命主义的作战,唯心论革命家主张白朗基主义,[1]说要复兴民族惟有恢复旧道德,都是过分夸张主观能动性的结果。我们的思维不能以一次反映来当作全体看的对象,而是构成一个具有接近于现实的,一切种类的无数色调的,生动的,认识之辩证法的过程。唯心论依据于思维的这种特性,夸大其个别方面,不能给过程以正确的反映,反把过程弄弯曲了。列宁说:“人类的认识不是直线的,而是曲线的。这一曲线之任何一段,都可以变为一段单独的完整的直线,这段直线就有引你陷入迷阵的可能。直线性和片面性是见树不见林和呆板固执性,主观主义和主观盲动性——这些就是唯心论的认识论的根源”[2]。哲学的唯心论是将认识的一个片段或一个方面,片面地夸张成为一种脱离物质脱离自然的神化的绝对体。唯心论就是宗教的教义,这是很对的。\n马克思以前的唯物论(机械唯物论)没有强调思维在认识上的能动性,仅给以被动作用,把它当作反映自然的镜子看。机械唯物论对唯心论采取横暴的态度,不注意其认识论的根源,因此不能克服唯心论。只有辩证法唯物论,才正确地指出思维的能动性,同时又指出思维受物质的限制;指出思维从社会实践中发生,同时又能动地指导实践。只有这种辩证法的“知行合一”论,才能彻底地克服唯心论。\n(四)唯物论发生与发展的根源 承认离意识而独立存在于外界的物质是唯物论的基础。这一基础是人类从实践中得到的。劳动生产的实践,阶级斗争的实践,科学实验的实践,使人类逐渐脱离迷信与妄想(唯心论),逐渐认识世界之本质,而到达于唯物论。\n屈服于自然力之前,而只能使用简单工具的原始人类,不能说明周围的事变,因而求助于神灵,这就是宗教同唯心论的起源。\n然而人类在长期生产过程中同周围的自然界接触,作用于自然界,变化着自然界,创造着衣食住用的东西,使之适合于人类的利益,使人类深信物质是客观地存在着。\n人类在社会生活中,人同人之间互相发生关系与影响,在阶级社会中并且实行着阶级斗争。被压迫阶级考虑形势,估计力量,建立计划,在他们的斗争成功时,使他们确信自己的见解并不是幻想的产物,而是客观上存在着的物质世界的反映。被压迫阶级因为采取错误的计划而失败,又因为改正其计划而成功,使他们懂得只有主观的计划依靠于对客观世界的物质性与规律性的正确的认识,才能达到目的。\n科学的历史给人类证明了世界的物质性及其规律性,使人类觉悟到宗教与唯心论的幻想之无用,而到达于唯物论的结论。\n总之,人类的实践史——自然斗争史、阶级斗争史、科学史,在长久年月中,为了生活与斗争的必要,考虑物质的现实及其法则,证明了唯物论哲学的正确性,找到了自己斗争的思想工具——唯物论哲学。社会的生产发展越发进到高度,阶级斗争越发发展,科学认识越发暴露了自然的“秘密”,唯物论哲学就越发发展与巩固,人类便能逐渐从自然与社会的双重压迫下解放出来。\n资产阶级在为了向封建阶级斗争的必要及无产阶级还没有威胁他们的时候,也曾经找到了并使用了唯物论作为自己斗争的工具,也曾经确信周围的事物是物质的产物,而不是精神的产物。直至他们自己变成了统治者,无产阶级的斗争又威胁着他们时,才放弃这个“无用”的工具,重新拿起另一个工具——哲学的唯心论。中国资产阶级的代言人戴季陶、吴稚晖,在 1927 年以前及其以后思想的变化——从唯物论到唯心论的变化,就是眼前的活证据。\n资本主义的掘墓人——无产阶级,他们本质上是唯物论的。但由于无产阶级是历史上最进步的阶级,这就使得无产阶级的唯物论不同于资产阶级的唯物论,是更彻底更深刻的,只有辩证法的性质,没有机械论的性质。无产阶级吸收了人类全历史中的一切实践,经过他们的代言人与领导者——马克思、恩格斯之手,造成了辩证法唯物论,不但主张物质离人的意识而独立存在,而且主张物质是变化的,成为整个完整系统的崭新的世界观与方法论,这就是马克思主义的哲学。\n:: 物质是客观存在的,意识是物质发展到一定阶段的产物,是客观世界的反映。\n注释:\n[1] 此处省略的为“蒋介石”。\n[2] 出自《谈谈辩证法问题》。参见《列宁全集》中文第 2 版,第 55 卷,第 311 页:“人的认识不是直线(也就是说,不是沿着直线进行的),而是无限地近似于一串圆圈、近似于螺旋的曲线。这一曲线的任何一个片断、碎片、小段都能被变成(被片面地变成)独立的完整的直线,而这条直线能把人们(如果只见树木不见森林的话)引到泥坑里去,引到僧侣主义那里去(在那里统治阶级的阶级利益就会把它巩固起来)。直线性和片面性,死板和僵化,主观主义和主观盲目性就是唯心主义的认识论根源。”\n第二章 辩证法唯物论 这个题目中准备讨论下列各问题:\n(一)无产阶级革命的武器——辩证法唯物论;\n(二)过去哲学遗产同辩证法唯物论的关系;\n(三)在辩证法唯物论中宇宙观和方法论的一致;\n(四)哲学对象问题;\n(五)物质论;\n(六)运动论;\n(七)时空论;\n(八)意识论;\n(九)反映论;\n(十)真理论;\n(十一)实践论。\n下面简述这些问题的观点。\n(一)辩证法唯物论是无产阶级革命的武器 这个问题在第一章中已经说过,这里再简单地说一点。\n辩证法唯物论,是无产阶级的宇宙观。历史给予无产阶级以消灭阶级的任务,无产阶级就用辩证法唯物论作为他们斗争的精神上的武器,作为他们各种见解之哲学基础。辩证法唯物论这种宇宙观,只有当我们站在无产阶级的立场去认识世界的时候,才能够被我们正确地和完整地把握住;只有从这种立场出发,现实世界才能真正客观地被认识。这是因为一方面只有无产阶级才是最先进与最革命的阶级;又一方面,只有辩证法唯物论才是高度的和严密的科学性同彻底的和不妥协的革命性密切地结合着的一种最正确的和最革命的宇宙观和方法论。\n中国无产阶级担负了经过资产阶级民主革命到达社会主义与共产主义的历史任务,必须采取辩证法唯物论作为自己精神的武器。如果辩证法唯物论——一种最正确最革命的宇宙观和方法论被中国共产党、及一切愿意站在无产阶级立场的广大革命份子所掌握,他们就能够正确地了解革命运动的发展变化,提出革命的任务,团结自己和同盟者的队伍,战胜反动的理论,采取正确的行动,避免工作的错误,达到解放中国与改造中国的目的。辩证法唯物论对于指导革命运动的干部人员尤属必修的科目,因为主观主义与机械观这两种错误的理论与工作方法,常常在干部人员中间存在着,因此常常引导干部人员违犯马克思主义,在革命运动中走入歧途。要避免与纠正这种缺点,只有自觉地研究与了解辩证法唯物论,把自己的头脑重新武装起来。\n(二)旧的哲学遗产同辩证法唯物论的关系 现代的唯物论,不是过去各种哲学学说的简单的继承者,它是从反对过去统治哲学的斗争中,从科学解除其唯心论和神秘性的斗争中产生和成长起来的。马克思主义的哲学——辩证法唯物论,不但继承了唯心论的最高产物——黑格尔学说的成果,同时还克服了这一学说的唯心论,唯物地改造了他的辩证法。马克思主义又不但是一切过去唯物论发展的继续和完成,同时还是一切过去唯物论的狭隘性之反对者,即机械的直觉的唯物论(主要的是法国唯物论与费尔巴哈唯物论)之反对者。马克思主义的哲学——辩证法唯物论,继承了过去文化之科学的遗产,同时又给此种遗产以革命的改造,形成了一种历史上从来没有过的最正确最革命和最完备的哲理的科学。\n:: 毛主席始终是最坚定的无产阶级舵手,他始终和无产阶级队伍站在一起,始终和贫下中农站在一起,始终和最广大、最底层的人民站在一起。他不愿意看到中国再出现一部分人骑在人民头上的现象,一部分人剥削人民的现象,一部分人自诩高于人民的现象。他是做好了粉身碎骨的准备了的。\n中国在 1919 年五四运动以后,随着中国无产阶级自觉地走上政治舞台及科学水平之提高,发生了与发展着马克思主义的哲学运动。然而在它的第一时期,中国的唯物论思潮中唯物辩证法的了解还很微弱,受资产阶级影响的机械唯物论,和德波林派的主观主义风气占着主要的成分。1927 年革命失败以后,马克思列宁主义的了解进了一步,唯物辩证法的思想逐渐发展起来。到了最近,由于民族危机与社会危机的严重性,也由于苏联哲学清算运动的影响,在中国思想界发展了一个广大的唯物辩证法运动。这个运动,目前虽还在青年的阶段上,然从其广大的姿态来看,它将随着中国与世界无产阶级同革命人民的革命斗争之发展,以横扫的阵势树立自己的权威,指导中国革命运动,勇往迈进,定下中国无产阶级领导中国革命进入胜利之途的基础。\n由于中国社会进化的落后,中国今日发展着的辩证法唯物论哲学思潮,不是从继承与改造自己哲学的遗产而来的,而是从马克思列宁主义的学习而来的。然而要使辩证法唯物论思潮在中国深入与发展下去,并确定地指导中国革命向着彻底胜利之途,便必须同各种现存的反动哲学作斗争,在全国思想战线上树立批判的旗帜,并因而清算中国古代的哲学遗产,才能达到目的。\n:: 从这个方面来说,“批孔”就不奇怪的,谁让孔夫子是封建哲学遗产的代表人物呢。但是要注意主席所说的“批孔”的目的是为了深化发展辩证法唯物论,而不是“一棍子打死”。\n(三)辩证法唯物论中宇宙观和方法论的一致性 辩证法唯物论是无产阶级的宇宙观,同时又是无产阶级认识周围世界的方法和革命行动的方法,它是宇宙观和方法论的一致体。唯心论的马克思主义修正派认为辩证法唯物论的全部实质只在于它的“方法”。他们把方法从一般哲学的宇宙观割裂开来,把辩证法从唯物论割裂开来。他们不了解马克思主义的方法论——辩证法,不是如同黑格尔一样的唯心的辩证法,而是唯物的辩证法,马克思主义的方法论是丝毫也不能离开它的宇宙观的,另一方面机械唯物论者却又把马克思主义的哲学看作一般哲学的宇宙观,割去了它的辩证法,而且认为这种宇宙观就是机械的自然科学之各种结论。他们不了解马克思主义的唯物论不是简单的唯物论,而是辩证法的唯物论。对于马克思主义哲学之这两种割裂的看法都是错误的,辩证法唯物论是宇宙观和方法论的一致体。\n:: 唯心论马克思主义只要辩证法,机械唯物论只要唯物论,呵。\n(四)唯物辩证法的对象问题——唯物辩证法是研究什么的? 列宁把(作为马克思主义的哲理科学看的)唯物辩证法看做关于客观世界的发展法则及(在辩证法各范畴中反映这客观世界的)认识的发展法则的学问。他说:论理学不是关于思维的外在形式的学问,而是关于一切物质的,自然的,及精神的事物之发展法则的学问,即关于世界的一切具体内容及其认识之发展法则的学问。换言之,论理学是关于世界认识之历史的总计、总和、结论。列宁虽然把作为一般的科学方法论看的唯物辩证法的意义强调起来,然而这是因为辩证法系由世界认识的历史中得出来的结论。因此他说:“辩证法就是认识的历史”[1]。\n上述列宁对于当作科学看的唯物辩证法及其对象所给与的定义,他的意思是说:第一、唯物辩证法与其他任何科学一样,有它的研究对象,这个对象便是自然、历史和人类思维之最一般的发展法则。并且研究的时候,唯物辩证法的任务,不是从头脑里想出存在于各现象间的关联,而是要在各现象本身中观察出它们之间的关系来。列宁的这种见解同少数派唯心论者把(事实上离开了具体科学及具体知识的)范畴的研究当做唯物辩证法的对象之间,存在着根本的区别,因为少数派唯心论者企图建立一个从认识历史社会科学和自然科学的现实发展中游离了的各范畴的哲学体系,这样他们就事实上放弃了唯物辩证法。第二、各个科学分科(数学、力学、化学、物理学、生物学、经济学及其他自然科学、社会科学),是研究物质世界及其认识之发展的各个方面。因此各个科学的法则是狭隘的,片面的,被各个具体研究领域所限制了的。唯物辩证法则不然,它是一切具体科学中的一切有价值的一般内容,及人类的其它一切科学认识之总计、结论、加工和普遍化。这样,唯物辩证法的概念、判断和法则,是极其广泛的(包含着一切科学的最一般的法则,因此也包含着物质世界的本质的)各种规律性和规定,这是一方面。在这方面,它是宇宙观。另一方面,唯物辩证法是从一切空想、僧侣、主义、和形而上学解放出来的真正科学认识上的论理学和认识论的基础,因此它同时又是研究具体科学的唯一确实的、有客观正确性的方法论。我们说唯物论辩证法或辩证法唯物论是宇宙观和方法论的一致体,在这里更加明白了。这样对于否认哲学存在权的马克思主义哲学的歪曲者和庸俗化者的错误也可以懂得了。\n关于哲学对象问题,马克思、恩格斯和列宁,都反对使哲学脱离实在的现实,使哲学变为某种独立实质的东西。指出了那根据实在生活和实在关系的分析而生长出来的哲学之必然性,反对单单以论理观念和论理观念的自然做研究的对象,如同形式论理学及少数派唯心论的那种干法。所谓根据实在生活和实在关系的分析生长出来的哲学就是唯物辩证法这种论发展的学说。马克思,恩格斯和列宁,都解说唯物辩证法为论发展的学说。恩格斯称唯物辩证法为“论自然社会及思维之一般的发展法则”[2]的学说。列宁把唯物辩证法看作“最多方面的,内容最丰富的,和最深刻的发展学说。”[3]他们都认为在这种学说以外的其他一切哲学学说所述一切发展原则的公式,概属狭隘的无内容的“截去了自然和社会之实际发展过程的东西”[4](列宁)。至于唯物辩证法之所以被称为最多方面的,内容最丰富的和最深刻的发展学说的原故,乃是因为唯物辩证法是最多方面地和最丰富地、最深刻地反映了自然和社会变化过程中的矛盾性和飞跃性,而不是因为别的东西。\n在哲学对象问题中还要解决一个问题,就是辩证法、论理学及认识论的一致性的问题。\n列宁着重指出辩证法、论理学及认识论的同一性,说这是“极其重要的问题”,说“三个名词是多余的,它们只是一个东西”[5],根本反对那些马克思主义修正派把三者当做完全各别独立的学说去处理的那种干法。\n唯物辩证法是唯一科学的认识论,也是唯一科学的论理学。唯物辩证法研究吾人对外界认识的发生及发展,研究由不知到知,由不完全的知到更完全的知的转移,研究自然及社会的发展法则在人类头脑中日益深刻和日益增多的反映,这就是唯物辩证法与认识论的一致。唯物辩证法研究客观世界最一般的发展法则,研究客观世界最发展的姿态在思维中的反映形态。这就是唯物辩证法研究现实事物的各过程及各现象的发生、发展,消灭及相互转化的法则,同时又研究反映客观世界发展法则的人类思维的形态,这就是唯物辩证法与论理学的一致。\n要彻底了解辩证法、论理学、认识论三者为什么是一个东西,我们看下面唯物辩证法怎样解决关于论理的东西与历史的东西之相互关系这个问题,就可以明白了。\n恩格斯说:“对于一切哲学家的思维方法来说,黑格尔思维方法的长处就在于横亘在根底面的极其丰富的历史感,他的形式虽说是抽象的唯心论的,然而他的思想的发展却常常是与世界历史的发展平行着的。并且历史原来就是思想的检证。”[6]“历史常常在飞跃地错杂地进行着。因为有这种情形,所以假若常常要依从历史的话,不但要注意许多不重要的材料,而且会不得不使思想行程中断。这时唯一适当的方法,就是论理的方法。然而这一论理的方法根本仍然是历史的方法,不过舍去了它那历史的形态与偶然性罢了”[7]。这种“论理发展与历史发展一致”的思想,是被马克思、恩格斯、列宁充分注意了的。“论理学的范畴,是外在的与活动之无数个别性的简约”[8]。“范畴就是分离的阶段,帮助我们去认识这一个网和网的结节点的”[9]。“人的实践活动,把人类的意识几十亿次反复不息地应用到各种各样的论理学式子里面,这样,这些式子就得到了所谓公理的意义了”[10]。“人类的实践,反复了几十亿次,才当做论理的式子固定在人类意识中。这些式子,都有着成见的永续性,因为是反复了几十亿次的结果,才有着公理的性质”[11]。上述列宁的那些话,指明唯物辩证法的论理学的特点,不象形式论理学那样,把它的法则和范畴看成空虚的,脱离内容而独立的,对于内容无关心的形式,也不象黑格尔那样,把他看成脱离物质世界而独立发展的观念要素,而是把它当做反映到和移植到我们头脑里,并且经由头脑加工制造过的,物质运动的表现去处理。黑格尔立脚在存在和思维的同一性上,把辩证法、论理学和认识论的同一性当做唯心论的同一性去处理。反之,马克思主义的哲学里,辩证法、论理学和认识论的同一性,是建立在唯物论基础上的。只有用唯物论解决存在与思维的关系问题,只有站在反映论的立场上,才能使辩证法、论理学和认识论的问题得到彻底的解决。\n用辩证法唯物论去解决论理的东西和历史的东西的相互关系的最好的模范,首先要算马克思的《资本论》。《资本论》中包含了资本主义社会的历史发展,同时又包含了这一社会的论理发展。《资本论》所分析的,是那把资本主义社会的发生、发展及消灭反映出来的各经济范畴的发展的辩证法。这问题之解决的唯物论性质,在于他以物质的客观历史做基础,在于把概念和范畴当做这一现实历史的反映。资本主义的理论[12]和历史的一致,资本主义的社会的论理学和认识论的一致,模范地表现在《资本论》里面,我们可以从它懂得一点辩证法、论理学和认识论一致的门径。\n以上是辩证法唯物论的对象问题。\n(五)物质论 马克思主义继续和发展哲学中的唯物论路线,正确地解决了思维与存在的关系问题,即彻底唯物地指出世界的物质性,物质的客观实在性,和物质对于意识的根源性(或意识对于存在的依赖关系)。\n承认物质对于意识的根源性是以世界的物质性及其客观存在为前提的。隶属于唯物论营垒的第一个条件就承认物质世界离人的意识而独立存在——人类出现以前它就存在,人类出现以后也是离开人的意识而独立存在的。承认这一点是一切科学研究的根本前提。\n拿什么来证明这一点呢?证据是多得很的。人类时刻同外界接触;还须用残酷的手段去对付外界(自然界同社会)的压迫和反抗;还不但应该而且能够克服这些压迫和反抗——所有这些在人类社会的历史发展中表现出来的人类社会实践的实在情形,就是最好的证据。经过了万里长征的红军,不怀疑经过地区连同长江大河雪山草地以及和它作战的敌军等等的客观存在,也不怀疑红军自己的客观存在,中国人不怀疑侵略中国的日本帝国主义同中国人自己的客观存在,抗日军政大学的学生也不怀疑这个大学和学生自己的客观存在。这些东西都是客观地离开我们意识而独立存在的物质的东西,这是一切唯物论的基本观点,也就是哲学的物质观。\n哲学的物质观同自然科学的物质观是不相同的。如果说哲学的物质观在于指出物质的客观存在,所谓物质就是说的离开人的意识而独立存在的整个世界(这个世界作用于人的感官,引起人的感觉,并在感觉中得到反映)。那么这种说法是永远不起变化的,是绝对的。自然科学的物质观则在于研究物质的构造,例如从前的原子论,后来的电子论等等,这些说法是随着自然科学的进步而变化的,是相对的。\n根据辩证法唯物论的见地去区别哲学的物质观与自然科学的物质观,是彻底贯彻哲学的唯物论方向之必须条件。在向唯心论和机械唯物论作斗争方面,有着重要的意义。\n唯心论者根据电子论的发见轰传物质消灭的谬说,他们不知道关于物质构造之科学知识的进步,正是证明辩证法唯物论的物质论之正确性。因为表现在旧的物质概念中的某些物质属性(重量硬度,不可入性,惰性等等),经过现代自然科学的发现,即电子论的发现,证明这些属性仅存在于某几种物质形态中,而在其它物质形态中则不存在,这种事实,破除了旧唯物论对于物质观念的片面性与狭隘性,而对于承认世界的物质性及其客观存在之辩证法唯物论的物质观,却恰好证明其正确。原来辩证法唯物论的物质观,正是以多样性去看物质的世界的统一,就是物质多样性的统一。这种物质观,对于物质由一形态转化到另一形态之永久普遍的运动变化这一种事实,丝毫也没有矛盾。以太、电子、原子、分子、结晶体、细胞、社会现象、思维现象——这些都是物质发展的种种阶段,是物质发展史中的种种暂时形态。科学研究的深入,各种物质形态的发现(物质多样性的发现),只是丰富了辩证法唯物论的物质观的内容,那里还会有什么矛盾?区别哲学的物质观同自然科学的物质观是必要的,因为二者有广狭之别然而是不相矛盾的,因为广义的物质包括了狭义的物质。\n辩证法唯物论的物质观,不承认世界有所谓非物质的东西(独立的精神的东西)。物质是永久与普遍存在的,不论在时间与空间上都是无阻的,如果说世界上有一种“从来如此”与“到处如此”的东西(就其统一性而言),那就是哲学上的所谓客观存在的物质。用彻底的唯物论见地(即唯物论辩证法见地)来看意识这种东西,那么所谓意识不是别的,它是物质运动的一种形态,是人类物质头脑的一种特殊性质,是使意识以外的物质过程反映到意识之中来的那种物质头脑的特殊性质。由此可知,我们区别物质同意识并把二者对立起来是有条件的。就是说:只在认识论的见地有意义。因为意识或思维只是物质(头脑)的属性,所以认识与存在的对立就是认识的物质同被认识的物质的对立,不会多一点。这种主体同客体的对立,离开认识论领域就毫无意义。假如在认识论以外还把意识同物质对立起来,就无异于背叛唯物论。世界上只有物质同它的各种表现,主体自身也是物质的,所谓世界的物质性(物质是永久与普遍的),物质的客观实在性与物质对于意识的根源性,就是这个意思。一句话,物质是世界的一切。“一统归于司马懿”,我们说“一统归于物质”。这就是世界的统一原理。\n以上是辩证法唯物论的物质论。\n(六)运动论(发展论) 辩证法唯物论的第一个基本原则在于它的物质论,即承认世界的物质性、物质客观实在性和物质对于意识的根源性,这种世界的统一原理,在前面物质论中已经解决了。\n辩证法唯物论的第二个基本原则在于它的运动论(或发展论),即承认运动是物质存在的形式,是物质内在的属性,是物质多样性的表现,这就是世界的发展原理。世界的发展原理同上述世界的统一原理相结合,就成为辩证法唯物论的整个的宇宙观。世界不是别的,就是无限发展的物质世界(或物质世界是无限发展的)。\n:: 世界是物质的,物质的运动的,运动是有规律的。\n辩证法唯物论的运动观,对于(一)离开物质而思考运动,(二)离开运动而思考物质,(三)物质运动的简单化,都是不能容许的,辩证法唯物论的运动论,就是同这些唯心的、形而上学的、及机械的观点作明确而坚决的斗争建立起来的。\n辩证法唯物论的运动论,首先是同哲学的唯心论及宗教的神道主义相对立的。一切哲学的唯心论及宗教的神道主义的本质,在于它们从否认世界的物质统一性出发,设想世界的运动及发展是没有物质的、或在最初是没有物质的、而是精神作用或上帝神力的结果。德国唯心论哲学家黑格尔认为现在的世界是从所谓“世界理念”发展而来的,中国的周易哲学及宋、明理学都作出唯心论的宇宙发展观。基督教说上帝创造世界,佛教及中国一切拜物教都把宇宙万物的运动发展归之于神力。所有这些离物质而思考运动的说法都和辩证法唯物论根本不相容。不但唯心论与宗教,就是马克思以前的一切唯物论及现在一切反马克思主义的机械唯物论,当他们说到自然现象时,是唯物论的运动论者,但一说到社会现象时,就无不离开物质的原因而归着于精神的原因了。\n辩证法唯物论坚决驳斥所有这些错误的运动观,指出他们的历史限制性——阶级地位的限制与科学发展程度的限制,而把自己的运动观建设在以无产阶级立场及最发达的科学水准为基础的、彻底的唯物论上面。辩证法唯物论首先指出运动是物质存在的形式、是物质内在的属性(不是由外力推动的),设想没有物质的运动,同设想没有运动的物质是一样不可思议的事。把唯物的运动观同唯心的及唯神的运动观尖锐地对立着。\n离开运动而思考物质,则有形而上学的宇宙不动论或绝对均衡论,他们认为物质是永远不变的,在物质中没有发展这回事,认为绝对的静止是物质的一般状态或原始状态。辩证法唯物论坚决反对这种意见,认为运动是物质存在的最普遍的形式,是物质内在的不可分离的属性。一切的静止与均衡仅有相对的意义,而运动则是绝对的。辩证法唯物论承认一切物质形态均有相对的静止或均衡之可能,并认为这是辨别物质,因而亦即辨别生命的最重要条件(恩格斯)。但认为静止或均衡只是运动的要素之一,是运动的一种特殊情况。离开运动而考察物质的错误,就在于把这种静止要素或均衡要素夸张起来,把它掩蔽了并代替了全体,把运动的特殊情况一般化、绝对化起来。中国古代形而上学思想家爱说的一句话:“天不变,道亦不变”,就是这样的宇宙不动论。他们也承认宇宙及社会现象的变动,但否认其本质的变动,在他们看来,宇宙及社会的本质是永远不变动的。他们之所以如此,主要的原因在于他们的阶级限制性,封建地主阶级如果也承认宇宙及社会的本质是运动与发展的,就无异在理论上宣布他们自己阶级的死刑。一切的反动势力,他们的哲学都是不动论。 革命的阶级同民众,却眼睛看到了世界的发展原理,因而主张改造这个社会及世界,他们的哲学是辩证法唯物论。\n:: 封建地主阶级,现代房东阶级,这些直接或间接的既得利益者,是不愿、更不会主动把利益交出来的,那跟要了它们的命没有什么区别。\n此外辩证法唯物论也不承认简单化的运动观,就是说把一切的运动都归结到一种形式上去,即归结到机械的运动,这是旧唯物论宇宙观的特点。旧唯物论(十七八世纪的法国唯物论,十九世纪的德国费尔巴哈唯物论)也承认物质的永久存在和永久运动(承认运动的无限性),但仍然没有跳出形而上学的宇宙观,不去说他们在社会论上的见解依然是唯心论的发展观。就在自然论上,也把物质世界的统一,归结到某种片面的属性,即归结到运动的一个形态——机械的运动,这种运动的原因在外力,象机械一样,由外力推之而运动。他们不从本质上,也不从内部原因上去说明物质或运动、本质或关系的一切多样性,而从单纯的外面的发现形式上从外力原因上去说明它,这样在实际上就失掉了世界的多样性。他们把世界一切的运动,都解作场所的移动与数量的增减。物质某一瞬间在某一场所,另一瞬间则在另一场所,这样就叫做运动。如果有变化,也只是数量增减的变化,没有性质的变化,变化是循环的,是反复产生同一结果的。辩证法唯物论与此相反,不把运动看作单纯的场所移动及循环运动,而把它看作无限的质的多样性,看作由一形态向他一形态的转化,世界物质的统一和物质的运动,便是世界物质无限多样性的统一与运动。恩格斯说:“运动的一切高级形态必然同力学的(外的或分子的)运动形态结合着,例如:如果没有热和电气的变化,化学的作用就不可能,如果没有力学的(分子的)热量的、电气的、化学的变化等等,有机的生命也不可能,这当然是不能否认的。然而如果只有某些低级运动形态的存在,是决不能包括各种状态中主要形态的物体的”。这话是千真万确地合于事实。即使就单纯机械运动而论,也不能从形而上学的观点去解释它。须知一切运动形态都是辩证法的,虽然它们之间的辩证法内容的深度与多面性有着很大的差异。机械运动仍然是辩证法的运动,所谓物体某一瞬间“在”某处,其实是同时“在”某处,同时又不在某处,所谓“在”某处,所谓“不动”,仅是运动的一种特殊情况,它根本上依然是在运动,物体在被限制着的时间内和被限制着的空间内运动着。物体总是不绝地克服这种限制性跑出这种一定的有限的时间及空间的界限以外去成为不绝的运动之流。而且机械运动只是物质的运动形态之一,在实在的现实世界中,没有它的绝对独立的存在,它总是联系于别种运动形态的。热、化学的反应,光、电气,一直到有机现象与社会现象,都是质地上特殊的物质运动形态。十九世纪与二十世纪交界时期的自然科学的划时代的大功劳,就在于发现了运动转化法则,指出物质的运动总是由一形态转化成为另一形态,这样的转化的新形态是与旧形态本质上不同的。物质所以转化的原因不在外部而在内部,不是由于外部机械力的推动,而是由于内部存在着性质不同的互相矛盾的两种因素相争相斗推动着物质的运动与发展。由于这个运动转化法则的发现,辩证法唯物论就能够把世界的物质统一原理扩大到自然与社会的历史上去,不但把世界当作永远运动的物质去考察,而且把世界当做由低级形态到高级形态的无限前进运动的物质去考察,即把世界当作发展,当作过程去考察,做一句话来说:“统一的物质世界是一个发展的过程”。这样就把旧唯物论的循环论击破了。辩证法唯物论深刻地多方面地观察了自然及社会的运动形态,认为当作全体看的世界之发展过程是永久的(无始无终的)。但同时各个历史地进行的具体的运动形态又是暂时的(有始有终的),就是说它是在一定的条件下发生,并在一定的条件下消灭的。认为世界的发展过程由低级的运动形态生出高级的运动形态,表示了它的历史性与暂时性,但同时任何一个运动形态无不是处在永久的长流中(无始无终的长流中)。依据着对立斗争的法则(自己运动的原因),使每一运动形态总是较之先行形态进到了高一级的阶段,它是向前直进的,但同时就各个运动形态来说(就各个具体的发展来说),却也会发生转向运动或后退运动,前进运动同后退运动相结合,在全体上就成为复杂的螺旋运动,认为新的运动形态是作为旧的运动形态的对立物(反对物)而发生的,但同时新的运动形态又必然保存着旧的运动形态中的许多要素,新东西是从旧东西里面生长出来的。认为事物的新形态、新性质、新属性的出现,是由连续性的中断即经过冲突和破局而飞跃地产生的,但同时事物的连结和相互关系又决不会绝对破坏。最后辩证法唯物论认为世界无穷尽(无限),不但就其全体来看是这样的,同时就其局部来看也是这样的,电子不是同原子分子一样表现着一个复杂而无穷尽的世界么?\n物质运动的根本形态,又规定根本的自然科学与社会科学各科目。辩证法唯物论把世界的发展当作无机界经过有机界而达到最高物质运动形态(社会)的一个前进运动去考察,这一运动形态的从属关系就成了和它相应的科学(无机界科学,有机界科学,社会科学的从属关系的基础)。恩格斯说:“各种分类的科学是把特定的运动形态或相互关联相互推移的一联的运动形态拿来分析,因此科学的分类就在于要依从着运动的固有顺序去把各个运动分类排列起来,仅在这一点来说,分类才有意义。”[13]\n整个世界包括人类社会在内,是采取质地不相同的各种形式的物质的运动,因此也就不能忘记物质运动的各种具体形式这个问题。所谓“物质一般”与“运动一般”是没有的,世界上只有各种不同形式的具体的物质或运动。“物质和运动这些字眼只是一些简写的名词,在这些名词中,我们依照它们的共同特性是把各种不同的被感觉的事物一概包括在内的。”[14](恩格斯)\n以上就是辩证法唯物论的世界运动论或世界发展原理。这个学说是马克思主义哲学的精髓,是无产阶级的宇宙观与方法论,无产阶级及一切革命的人们如果拿着这个彻底科学的武器,他们就能够理解这个世界并改造这个世界。\n(七)时空论 运动是物质存在的形式,空间和时间也是物质存在的形式,运动的物质存在于空间和时间中,并且物质的运动本身是以空间和时间这两种物质存在的形式为前提的。空间和时间不能与物质相分离。“物质存在于空间”这句话,是从物质本身具有伸张性,物质世界是内部存在着伸张性的世界,不是说物质被放在一种非物质的空虚的空间中。空间和时间都不是独立的非物质的东西,也不是我们感觉性的主观形式。它们是客观物质世界存在的形式。它们是客观的,不存在物质以外,物质也不存在于它们以外。\n把空间和时间看作物质存在的形式的这种见解,是彻底的唯物论的见解。这种时空观,同下列几种唯心论的时空观是根本相反的:(一)康德主义的时空观,认时间和空间不是客观的实在,而是人类的直觉形式;(二)黑格尔主义的时空观,认发展着的时间和空间的概念,日益接近于绝对观念;(三)马赫主义的时空观,认时间和空间是“感觉的种类”,“使经验和谐化的工具”。所有这些唯心论观点,都不承认时间和空间的客观实在性,都不承认时间和空间的概念在自身发展中反映着物质存在的形式。这些错误理论,都被辩证法唯物论一个一个地驳翻了。\n辩证法唯物论在时空问题上,不但要同上述那些唯心论观点作斗争,而且要同机械唯物论作斗争。特别显著的是牛顿的(机械论),他把空间看做同时间无关系的不动的空架子,物质被安置到这种空架子里面去。辩证法唯物论反对这种机械论,指出我们的时空观念是在发展的。“世界上除了运动的物质以外便没有别的东西,而运动的物质若不在空间和时间中便无运动之可能。人类关于空间和时间的概念是相对的,但是这些相对的概念积集起来就成为绝对的真理。这些相对的概念不断发展着,循着绝对真理的路线而前进,日益走近于绝对真理。人类关于空间时间概念的变动性,始终不能推翻二者的客观实在性,这正和关于物质的运动形式及其组织之科学知识的变动性,不能推翻外界的客观实在性,是一样的。”[15](列宁)\n以上是辩证唯物的时空论。\n(八)意识论 辩证唯物论认意识是物质的产物,是物质发展之一形式,是一定物质形态的特性。这种唯物主义同历史主义的意识论是和一切唯心论及机械唯物论对于这个问题的观点根本相反的。\n依照马克思主义的见解,意识的来源,是由无意识的无机界发展到具有低级意识形态的动物界,再发展到具有高级意识形态的人类。高级意识形态不但同生理发展中的高级神经系统不可分离,而且同社会发展中的劳动生产不可分离。 马克思、恩格斯曾经着重指出意识对物质生产发展的依赖关系,和意识同人类言语发展的关系。\n所谓意识是一定物质形态的特性,这种物质形态就是组织复杂的神经系统,这样的神经系统只能发生于自然界进化的高级阶段上。整个无机界、植物界和低级的动物界,都没有认识在他们内面或外面发生着的那些过程的能力,它们是没有意识的。仅在有高级神经系统的动物体,才具有认识过程的能力,即具有自内反映或领悟这些过程的能力。吾人神经系统中的客观生理过程,是同它之内部取意识形式的主观表现相随而行的。凡就本身论是客观的东西,是某种物质过程,它对于具有头脑的实体却同时又是主观的心理的行为。\n特殊思想实质的精神是没有的,有的只是思想的物质——脑子。这种思想的物质是有特别质地的物质,这种物质随着人类社会生活中言语的发展而达到高度的发展。这种物质具有思想这一种特殊性质,这是任何别的物质所不具备的。\n然而庸俗唯物论者却认思想是脑子分泌出来的物质,这种见解歪曲了我们关于这个问题的观念。须知思想感情和意志的行为,不是具有重量和伸张性的东西,意识同重量伸张性等是同一物质之不同的性质。意识是运动的物质之内部状态,是反映着在运动的物质中所发生的生理过程的特殊性质。这种特殊性,同客观的神经作用过程不可分离,但又不与这过程相同,把这二者混同起来,推翻意识的特殊性,这就是庸俗唯物论的观点。\n和这同样冒牌的马克思主义的机械论,附和心理学中某些资产阶级的左翼学派的见解,实质上也完全推翻了意识。他们把意识解作理化的生理的过程,认为高级实体的行为之研究,可以由客观生理学和生物学的研究去执行。他们不了解意识的本质之质的特殊性,看不到意识是人类社会实践的产物。他们把客体和主体之具体历史的一致,代之以主客的等同,代之以片面的机械的客观的世界。这种把意识混同于生理过程的观点,无异取消了思维与存在关系这个哲学中的根本的问题。\n孟塞维克的唯心论企图用一种妥协理论去代替马克思主义的意识论,把唯物论同唯心论调和起来,他们拿客观主义同主观主义的原则,而这种原则既非机械的客观主义,也非唯心的主观主义,而是客观和主观之具体历史的一致。\n可是还有怀疑论,这就是普列汉诺夫关于意识问题的物活论的见解。在他的“石子也是有意识的”一句名言中充分表现着。照他的意见,意识不是发生于物质发展过程中的,而是最初就存在于一切物质的。石子的、低级有机体的和人的意识之间,仅仅在于程度上的区别。这种反历史的见解,对于辩证唯物论认为意识是最后发生的具备着质的特殊性的见解,也是根本相反的。\n只有辩证唯物论的意识论才是意识问题上的正确的理论。\n(九)反映论 做一个彻底的唯物论者,单承认物质对于意识的根源性是不够的,还须承认意识对于物质的可认识性。\n关于物质能否被认识的问题,是一个复杂的问题,是一切过去哲学都觉得无力对付的问题,只有辩证法唯物论能够给予正确的解决。在这个问题上,辩证法唯物论的立场既同不可知论相反,又同直率的实在论不同。\n休谟同康德的不可知论,把认识的主体隔离开来,认为越出本体的界限是不可能的,“自在之物”和它的形象之间存在着不可跳过的深沟。\n马赫主义的直率实在论,则把客体同感觉等同起来,认为真理在感觉中就已经成就了完成的形态。同时,他们不但不了解感觉是外界作用的结果,而且不了解主体在认识过程中的积极作用,即外界作用在主体的感觉机关和思想的脑子中所做的改造工夫(取印象和概念的形式表现出来)。\n只有辩证法唯物论的反映论,肯定地答复了可认识性问题,成为马克思主义认识论的“灵魂”。根据这一理论,指明我们的印象和概念不但被客观事物所引起,而且还反映客观事物。指明印象和概念,既不象唯心论者所说的那样,是主体自动发展的产物,也不是不可知论者所说的那样,是客观事物的标符,而是客观事物的反映、照象和样本。\n客观的真理是不依靠主体而独立存在的,它虽然反映在我们的感觉和概念中,但不是一下子就取完成的形态,而是一步一步完成的,认为客观真理在感觉中就已经取着完成形态,而被我们获得的那种直率实在论的见解是一种错误的见解。\n客观真理在我们感觉和概念中虽不是一次就取完成的形态,然而不是不能认识的。辩证唯物论的反映论,反对不可知论的见解,认为意识是能够在认识过程中反映客观真理的。认识过程是一个复杂的过程,在这个过程中,当未被认识的“自在之物”,反映到我们的感觉印象、概念上来时,就变成“为我之物”了。感觉和思维,并不是如同康德所说的那样,把我们同外界隔离开来,而是把我们同外界联系起来的。感觉和思维就是客观外界的反映。思想的东西(印象和概念)并非别的,不过是“人类头脑中所转现出来和改造过来的物质的东西”(马克思)。在认识过程中,物质世界是愈走而愈接近地愈精确地愈多方面地和愈深刻地反映在我们的认识中。向着马赫主义和康德主义作两条战线的斗争,揭破直率实在论和不可知论的错误,是马克思主义认识论的任务。\n唯物辩证法的反映论认为我们认识客观世界的能力是无限度的,这和不可知论者认为人的认识能力是有限度的那种见解根本相反。但我们之接近绝对真理,却每一次有其历史上的确定界限。列宁这样说:吾人知识之接近客观的绝对真理,是历史地有限度的。但是这一真理的存在是绝对的,我们不断地向真理接近也是绝对的。图画的外形是历史地有条件的,但这张图画描绘着客观上存在的模型则是绝对的,我们承认人的认识受历史条件的限制,真理是不能一次获得的。但我们不是不可知论者,我们又承认真理能够完成于人类认识的历史运动中。列宁还说:对于自然人类思想中的反映,不要死板板地或绝对地去了解他,认识不是无运动与无矛盾的,认识是处于永久的运动过程中,“即矛盾之发生和解决的永久的运动过程中”[16]。认识运动时一个复杂的充满着矛盾与斗争的运动,这就是辩证唯物论的认识论之见解。\n一切哲学在认识论上的反历史的观点,都不把认识当作过程看待,因此都带着狭隘性。感觉主义的经验论之狭隘性,在感觉和概念之间挖开了深沟。理性主义学派的狭隘性,则使概念脱离了感觉。只有把认识当作过程看待的辩证唯物论的认识论(反映论) 才彻底除去了这样狭隘性,把认识放在唯物的与辩证的地位。\n反映论指出:反映过程不限于感觉和印象,也存在于思维中(抽象的概念中),认识是一个由感觉到思维的运动过程。列宁曾说:“反映自然的认识,不是简单的,直接的整体的反映,而是许多抽象的思考、概念、法则等等之形成过程”[17]。\n同时列宁还指出:由感觉到思维的认识过程,是飞跃式地进行的,在这一点上,列宁精确地阐明了:认识中的经验元素和理性元素相互关系之辩证唯物论的见解。许多哲学家都不了解认识的运动过程中,即从感觉到思维(从印象到概念)的运动过程中所发生的突变。因此理解这一由矛盾而产生的飞跃式的转变,即理解感觉和思维的一致为辩证的一致,便是理解了列宁反映论的本质之最重要的元素。\n(十)真理论 真理是客观的,相对的,又是绝对的。这就是唯物辩证法的真理观。\n真理首先是客观的。在承认了物质的客观实在性及物质对于意识的根源性之后,就等于承认了真理的客观性。所谓客观真理,就是说:客观存在的物质世界,是我们的知识或概念的内容之唯一来源,再也没有别的来源;只有唯心论者否认物质世界离人的意识而独立存在——这一唯物论的基本原则,才主张知识或概念是主观自主的,不要任何客观的内容,因而承认主观真理,否认客观真理。然而这是不合事实的。任何一种知识或一个概念,如果它不是反映客观世界的规律性,它就不是科学的知识,不是客观真理,而是主观地自欺欺人的迷信或妄想。 人类以改变环境为目的之一切实际行动,不管是生产行动也罢,阶级斗争或民族斗争的行动也罢,其他任何一种行动也罢,都是受着思想(知识)的指挥的。这种思想如果不适合于客观的规律性,即客观规律性没有反映到行动的人的脑子里去,没有构成他的思想或知识的内容,那么这种行动是一定不能达到目的的。革命运动中所谓主观指导犯错误,就是指的这种情形。马克思主义所以成为革命的科学知识,就是因为它正确地反映了客观世界的实际规律,它是客观的真理。一切反马克思主义的思想所以都是错的东西,就是因为它们不根据于正确的客观规律,完全是主观的妄想。有人说,一般公认的就是客观真理(主观唯心论者波格达诺夫[18]这样说)。照这种意见,那么,宗教和偏见也是客观真理了,因为宗教和偏见虽然实质上是谬见,可是却常常为多数人所公认;有时正确的科学思想反不及这些谬见的普及。唯物辩证法根本反对这种意见,认为只有正确地反映客观规律性的科学知识,才能被称为真理,一切真理必须是客观的。真理与谬说是绝对对立的,判断一切知识是否为真理,唯一的看他们是否反映客观的规律。如果不合乎客观规律,尽管是一般人都承认的,或革命运动中某些说得天花乱坠的理论,都只能把它当作谬说看待。\n唯物辩证法真理论的第一个问题,是主观真理和客观真理的问题,它的答复是否认前者而承认后者。唯物辩证法真理论的第二个问题,是绝对真理和相对真理的问题,它的答复不是片面地承认或否认某一方面,而是同时承认它们,并指出它们正确的相互关系,即指出它们的辩证性。\n唯物辩证法在承认客观真理时,就是承认了绝对真理的。因为当我们说知识的内容是客观世界的反映时,这就等于承认了我们知识的对象是那个永久的绝对的世界。“关于自然之一切真理的认识,就是永久的无穷的认识,因此它实质上是绝对的”[19](恩格斯)。然而客观的绝对的真理不是一下子全部成为我们的知识,而是在我们认识之无穷的发展过程中,经过无数相对真理的介绍,而到达于绝对的真理。这无数相对真理之总和,就是绝对真理的表现。人类的思维,就它的本性说,能给我们以绝对真理,绝对真理乃由许多相对真理积集而成,科学发展的每一阶段,增加新的种子到这个绝对真理的总和中去。但是每一科学原理的真理界限却总是相对的。绝对真理仅能表现在无数相对真理之上,如果不经过相对真理的表现,绝对真理就无从认识。唯物辩证法不否认一切知识之相对性,但这只是指吾人知识接近于客观绝对真理的限度之历史条件性而言,而不是说知识本身只是相对的。一切科学上的发明,都是历史地有限度的和相对的,但是科学知识跟谬说不同,它显示着描画着客观的绝对的真理,这就是绝对真理与相对真理相互关系之辩证法的见解。\n有两种见解:一种是形而上学的唯物论;另一种是唯心论的相对论。对于绝对真理与相对真理之相互关系问题都是不正确的。\n形而上学的唯物论者,根据于他们的“物质世界无变化”的形而上学的基本原则,认为人类思维也是不变化的,即认为在人的意识中这一不变的客观世界,是一下子整个被摄取了。这就是说他们承认绝对真理,而这个绝对真理是一次被人获得的,他们把真理看成不动的,死的,不发展的东西。他们的错误不在于他们承认有绝对真理——承认这一点是正确的,而在于他们不了解真理的历史性,不把真理的获得看作一个认识的过程。不了解所谓绝对真理者,只能在人类认识的发展过程中一步一步地开发出来,而每一步向前的认识,都表现着绝对真理的内容,但对于全部真理说来,它具有相对的意义,并不能一下子获得绝对真理的全部。形而上学的唯物论关于真理的见解,表现了认识论一个极端。\n认识论中关于真理问题的再一个极端,就是唯心论的相对论。他们否认知识之绝对真理,只承认它的相对意义。他们认为一切科学的发明,都不包含绝对真理,因而也不是客观真理,真理只是主观的与相对的。既然这样,那末一切谬说就都有存在的权利了,帝国主义侵略弱小民族,统治阶级剥削劳动群众,这些侵略主义与剥削制度也就是真理,因为真理横直只是主观的与相对的。否认客观真理与绝对真理的结果,必然到达这样的结论。并且唯心论的相对论,他们的目的本来就是要替统治阶级作辩护的,例如相对论的实用主义(或实验主义)之目的就在于此。\n这样看来,不论是形而上学的唯物论,或是唯心论的相对论,都不能正确解决绝对真理和相对真理的相互关系的问题。只有唯物论辩证法,既给思维与存在相互关系问题以正确的解答,并且随之而来又确定了科学知识的客观性,再则,还同时给了绝对与相对真理以正确的理解。这就是唯物辩证法的真理论。\n(十一)实践论 ——(认识与实践的关系,理论与实际的关系,知与行的关系)\n马克思以前的唯物论,离开人的社会性,离开人的历史发展,去观察认识问题,因此不能了解认识对社会实践的依赖关系,即认识对生产与阶级斗争的依赖关系。\n首先,马克思主义者认为人类的生产活动是最基本的实践活动,是决定其他一切活动的东西。人的认识,主要的依赖物质的生产活动,逐渐了解自然的现象、自然的性质(自然的规律性)、人与自然的关系;而且经过生产活动,同时也认识了人与人的相互关系。一切这些知识,离开生产活动是不能得到的。每个人以社会一员的资格,与其他社会成员协力从事生产活动,以解决人类物质生活问题,这是人的认识发展的基本来源。\n人的社会实践,不限于生产活动一种形式,还有多种其他的形式,阶级斗争,政治生活,科学活动,总之,社会实际生活的一切领域都是社会的人所参加的。因此,人的认识,在物质生活以外,还从政治文化生活中(与物质生活密切联系)了解了人与人的各种复杂的关系。其中尤以各种形式的阶级斗争,给予人的认识发展以深刻的影响。在阶级社会中,各种思想无不打上阶级的烙印,就是这个原故。\n因此,马克思主义者认为只有人们的社会实践,提给人们对于外界认识之真理性的标准。实际的情形是这样的,只有在社会实践过程中(物质生产过程中、阶级斗争过程中、科学实验过程中),人们达到了思想中所预想的结果时,人们的认识才会发生力量。农民如果得不到收获,工人如果做不成器物,罢工斗争,军队作战,民族革命,如果也都得不到胜利,那末这是为什么呢?这是因为人们的认识没有外界的过程的实况去反映这些过程的规律性。因而在他们的实践活动中不能达到预想的结果。人们要想得到胜利(即得到预想的结果),一定要自己的思想合于客观外界的规律性。如果不合,就会在实践中失败,人们经过失败之后,也就从失败取得教训,改正自己的思想使之适合于外界的规律性,人们就能变失败为胜利,所谓“失败者成功之母”,“吃一堑长一智”,就是这个道理。辩证唯物论的认识论把实践提到第一的地位,认为人的认识一点也不能离开实践,排斥一切否认实践重要性、使认识离开实践的错误理论。列宁这样说过:“实践高于(理论的)认识,因为它不但有一般性的价值,而且还有直接现实性的价值”[20]。马克思主义的哲学辩证唯物论的最显著的特点有两个:一个是它的阶级性,公然申明辩证唯物论是为无产阶级服务的;再一个是它的实践性,强调理论对于实践的依赖关系,理论来源于实践,又转过来为实践服务。判定认识或理论之是否真理,不是依主观上觉得如何而定,而是依客观上社会实践的结果如何而定。真理的标准只能是社会的实践。实践的观点是辩证唯物论的认识论之第一的与基本的观点。\n然而人的认识究竟怎样从实践发生,而又服务于实践呢?这只要看一看认识的发展过程就会明了的。\n原来人在实践过程中,开始只是看到过程中各个事物的现象方面,看到各个事物的片面,看到各个事物之间的外部联系。例如国民党考察团到延安的头一二天,看到了延安的地形、街道、屋宇,接触了许多的人,参加了宴会、晚会与群众大会,听到了各种说话,看到了各种文件,这些就是事物的现象,事物的各个片面以及这些事物的外部联系。这叫做认识的感性阶段,就是感觉与印象的阶段。也就是延安这些各别的事物作用于考察团先生们的感官,引起了他们的感觉,在他们的脑子中生起了许多的印象,以及这些印象间的大概的外部的联系,这是认识的第一个阶段。在这个阶段中人们还不能造成深刻的概念,作出理论的结论。\n社会实践的继续,使人们在实践中引起感觉与印象的东西反复了多次,于是在人们的脑子里生起了一个认识过程中的突变,产生了概念。概念这种东西已经不是事物的现象,不是事物的各个片面,不是它们外部的联系,而是抓着了事物的本质,事物的全体,事物的内部联系了。概念同感觉,不但是数量上的差别,而且有了性质上的差别。循此继进,使用判断与推理的方法,就可生出理论的结论来。《三国演义》上所谓“眉头一皱计上心来”,我们普通说话所谓“让我想一想”,就是人在脑子中运用概念以作判断与推理的工夫。这是认识的第二个阶段,或叫论理阶段,是认识的第二个阶段。考察团先生们在他们集合了各种材料,加上他们“想了一想”之后,他们就能够作出“共产党抗日民族抗一战线与国共合作的政策是彻底的、诚恳的与真实的”这样一个判断了。在他们作出这个判断之后,如果他们对于团结救国也是真实的话,那末他们就能够进一步作出这样的结论:“国共合作是能够成功的”。这个概念、判断与推理的阶段,在人对于一个事物的整个认识过程中是最重要的一个阶段。认识之真正任务不在感性的认识,而在理性的认识。认识之真正任务在于经过感觉而达到于思维,到达于了解客观事物的内部矛盾,了解它的规律性,了解这一过程与那一过程间的内部联系,即到达于理论的认识。再重复地说,理性的认识所以和感性的认识不同,是因为感性的认识是属于事物之片面的、现象的、外部联系的东西,理性的认识则推进了一大步,到达了事物之全体的、本质的、内部联系的东西,到达了暴露周围世界之内的矛盾,因而能在周围世界之总体上,在周围世界一切方面之内部联系上,去把握周围世界的发展。\n这种基于实践之由浅入深的唯物辩证法的认识发展过程的理论,在马克思主义以前,是没有一个人这样解决过的。马克思主义的辩证唯物论,第一次正确地解决了这个问题,唯物地而且辩证地指出了认识之深化的运动,指出了社会的人在他们的生产与阶级斗争之复杂的、经常反复的实践中,由感性认识到理性认识之推移的运动。列宁说过:“物质的抽象,自然的法则,价值的抽象及其他等等,即一切科学的(正确的、重要的、非瞎说的)抽象,都比较深刻、比较正确、比较完全地反映自然。”[21]列宁又曾这样指出:认识过程中两个阶段的特性,在低级阶段,认识表现为感性的,在高级阶段,认识表现为理性的,但任何阶段,都是统一的认识过程中的阶段。感性与理性二者的性质不同,但又不是互相分离的,它们在实践的基础上统一起来了。我们的实践证明:感觉到了的东西,我们不能立刻理解它,只有理解了的东西才更深刻地感觉它。感觉只解决现象问题,理解才解决本质问题。这些问题的解决,一点也不能离开实践。无论何人要认识什么事物,除了同那个事物接触,即生活于(实践于)那个事物的环境中,是没有法子解决的。不能在封建社会就预先认识资本主义社会的规律,因为资本主义还未出现,还无这种实践。马克思主义只能是资本主义社会的产物。不能在自由资本主义时代就预先认识帝国主义时代的某些特异的规律,因为帝国主义还未出现,还无这种实践,只有列宁和斯大林才能担当此项任务。马克思与列宁也不能在经济落后的殖民地产生,这是因为虽然同时但不同地。马克思、恩格斯、列宁之所以能够作出他们的理论,除了他们的天才条件之外,主要地是他们亲身参加了当时的阶级斗争与科学实验的实践,没有这后一个条件,任何天才也是不能成功的。“秀者不出门,全知天下事”,在技术不发达的古代只是一句空话,在技术发达的现代虽然可以实现这句话,然而真正亲知的是天下实践的人,那些人在他们实践中间取得了“知”,经过文字与技术的传达而到达于“秀才”之手,秀才乃能间接地“知天下事”。如果要直接地认识某种或某些事物,便只有亲身参加于变革现实、变革某种或某些事物的实践中,才能触到那种或那些事物的现象,也只有在亲身参加变革现实的实践中,才能暴露那种或那些事物的本质而理解它。这是任何人实际上走着的认识路程,不过有些人故意歪曲地说些反对的话罢了。世上最可笑的是那些“知识份子”,有了道听途说的一知半解,便自封为“天下第一”,多见其不自量而已。知识的问题是一个科学问题,来不得半点虚伪与骄傲,决定地需要的到是他的反面——诚实与谦逊的态度。你要有知识,你就得参加变革现实的实践。你要知道梨子的滋味,你就得变革梨子,亲口吃一吃。你要知道原子的组织同性质,你就得实行化学家的实验,变革原子的情况。你要知道革命的具体理论与方法,你就得参加革命。一切真知都是从直接经验发源来的。但人不能事事直接经验,事实上多数的知识都是间接经验的东西,这就是一切古代的与外域的知识。这些知识在古人在外人是直接经验的东西,如果在古人外人直接经验时是附合于列宁所说的条件:“科学的(正确的、重要的、非瞎说的)抽象”,那末它们是可靠的,否则便是不可靠。所以一个人的知识,不外直接经验与间接经验的两部分。而且在我为间接经验者,在人则仍属直接经验。因此,就知识的总体说来,无论何种知识都是不能离开直接经验的。任何知识的来源,在于人的肉体感官对客观外界的感觉,否认了这个感觉,否认了直接经验,否认了亲身参加变革现实的实践,他就不是唯物论者。“知识份子”之所以可笑,原因就在这个地方。中国商人有一句话:“要赚畜生钱,要跟畜生眠”。这句话对于商人赚钱是真理,对于认识论也是真理,离开实践的认识是不可能的。\n为明了基于变革现实的实践而产生的唯物辩证法的认识运动——认识之逐渐深化的运动,下面再举出几个具体的例子。\n无产阶级对于资本主义过程的认识,在其实践的初期——破坏机器与自发斗争时期,他们还只在感性认识的阶段,只认识资本主义个别现象的片面及其外部的联系。这时,他们还是一个所谓“自在的阶级”。但到了他们实践的后期——有意识有组织的阶级斗争与政治斗争的时期,由于实践,由于长期斗争的经验,教训了他们,他们就理解了资本主义社会的本质,理解了社会阶级的剥削关系,产生了马克思主义的理论,这时他们就造成了一个“自为的阶级”。\n中国人民对于帝国主义的认识也是这样。第一阶段是表面的感性的认识,表现在太平天国运动与义和团运动等笼统的排外主义的斗争上。第二阶段才进到理性的认识,看出了帝国主义内部与外部的各种矛盾,并看出了帝国主义联合中国封建阶级以压榨中国人民大众的实质,这种认识是从五四运动前后才开始的。\n我们再来看战争。战争的领导者,如果他们是一些没有战争经验的人,对于一个具体的战争(例如我们过去十年的苏稚埃战争)的深刻的指导规律,在开始阶段是不了解的。他们在开始阶段只是身历了许多作战的经验,而且败仗是很多的。然而由于这些经验(胜仗,特别是败仗的经验),使他们能够理解贯串整个战争的内部的东西,即那个具体的战争之规律性,懂得了战略与战术,因而能够有把握地去指导战争。此时,如果改换一个无经验的人去指导,又会要在吃了一些败仗之后(有了经验之后),才能理会战争的正确的规律。\n常常听到一些同志在不能勇敢接受工作任务时说出来的一句话,就是说:他没有把握。为什么没有把握呢?因为他对这项工作的内容与环境没有规律性的了解,或者他从来就没有接触过这类工作,或者接触得不多,因而无从说到了解这类工作的规律性。及至把工作的情况同环境给以详细分析之后,他就觉得比较有了把握,愿意去做这项工作。如果这个人在这项工作中经过了一个时期(他有了这项工作的经验),而他又是一个肯虚心体察客观情况的人,不是一个主观地、片面地、表面地看问题的人,他就能够自己做出应该怎样进行工作的结论,他的工作勇气也就可以大大地提高。只有那些主观地、片面地与表面地看问题的人,跑到一个地方,不问环境的情况,不看事情的全体(事情的历史与全部现状),也不触到事情的本质(事情的性质及此一事情与其他事情的内部联系),就“自以为是”地发号施令起来,这样的人是没有不跌交子的。\n由此看来,认识的过程,第一步是开始接触外界事情,属于感觉的阶段。第二步是综合感觉的材料加以改造和整顿,属于概念、判断、与推理的阶段。只有感觉的材料十分丰富(不是零碎不全)与合于实际(不是错觉),才能根据这样的材料造出正确的概念与理论来。\n这里有两个要点须着重指明:第一个,在前面已经说过的,这里再重复说一说,就是理性认识依赖于感性认识的问题。如果以为理性认识可以不从感性认识得来,他就是一个唯心论者。哲学史上有所谓“唯理论”一派,就是只承认理性的实在性,不承认经验的实在性,以为只有理性靠得住,而感觉的经验是靠不住的。这一派的错误在于颠倒了事实。理性的东西所以靠得住,正由于它来源于感性,否则理性的东西就成了无源之水,无本之木,而只是主观自生的靠不住的东西了,从认识过程的秩序说来,感觉经验是第一的东西,我们强调社会实践在认识过程中的意义,就在于只有社会实践才能使人的认识开始发生,开始从客观外界得到感觉经验。一个闭目塞听、同客观外界根本绝缘的人,是无所谓认识的。认识发源于经验——这就是认识论的唯物论。\n第二是认识有待于深化,有待于发展到理性阶段——这就是认识论的辩证法。如果以为认识可以停顿在低级的感性阶段,以为只有感性认识可靠,而理性认识是靠不住的,这便重复了历史上“经验论”的理论。这种理论的错误,在于不知道感觉材料固然是客观外界某些真实性的反映(不去说“经验只是内省体验的那种唯心的经验论”),但它们仅是片面的与表面的东西,这种反映是不完全的,是没有反映事物本质的。要完全地反映整个的事物,反映事物的本质,反映其内部联系规律性,就非经过思考作用,将丰富的感觉材料加以去粗取精、去伪存真、由此及彼、由表及里的改造制作工夫,造成概念及理论的系统不可,非从感性认识,改变到理性认识不可。这种改造过的认识,不是更空虚更不可靠了的认识,相反地,只要是在认识过程中根据于实践基础而科学地改造过的东西,正如列宁所说:它是更深刻、更正确、更完全地反映客观事物的东西。[22]庸俗的事物主义家不是这样,他们尊重经验而看轻理论,因而不能通观客观过程的全体,缺乏明确的方针,没有远大的前途,沾沾自喜于一得之功与一孔之见。这种人如果指导革命,就会引导革命走上碰壁的地步。\n理性认识依赖于感性认识,感性认识有待于发展到理性认识,这就是唯物辩证法的认识论。哲学上的认识论与经验论,都不懂得认识的历史性或辩证性,虽然各有片面的真理(对于唯物的唯理论与经验论而言,非指唯心的唯理论与经验论),但在认识论的全体上则都是错误的。由感性到理性之唯物辩证法的认识运动,对于一个小的认识过程(例如一个事物或一件工作)是如此,对于一个大的认识过程(例如一个社会或一个革命)也是如此。\n然而认识运动至此还没有完结。唯物辩证法的认识运动,如果只到理性认识为止,那么还只说到问题的一半。而且对于马克思主义的哲学说来,还只说到非十分重要的那一半。马克思主义哲学认为十分重要的问题,不在于懂得了客观世界的规律性,因而能够解释宇宙,而在于拿了这种对于客观规律性的认识去改造宇宙。在马克思主义看来,理论是重要的,它的重要性充分地表现在列宁说过的一句话:“没有革命的理论,就没有革命的运动”[23]。人的一切行动(实践)都是受人的思想指导的,没有思想,当然就没有任何的行动。然而马克思主义看重理论,正是,也仅仅是,因为它能够指导行动。如果有了正确的理论,只在把它空谈一会,束之高阁,并不实行,那么这种理论再好也是没有用的。认识从实践始,经过实践得到了理论的认识,还须再回到实践去。认识的能动作用,不但表现于从感性的认识到理性的认识之能动的飞跃,更重要的还须表现于从理性的认识到革命的实践这一个飞跃。抓住了世界现实规律性的认识,必须把它再用到改造世界的实践中去,再用到生产的实践、革命的阶级斗争与民族斗争的实践以及科学实验的实践中去。这就是检验理论与发展理论的过程,是整个认识过程的继续。理论的东西或理性的认识之是否符合于客观真理性这个问题,在前面说的由感性到理性之认识运动中是没有完全解决的,也不能完全解决的。要完全地解决此问题,只有把理性的认识再回到社会实践中去,应用理论于实际,看它是否能够达到预想的目的。许多自然科学理论之所以被认为真理,不但在于发现此学说时,而且在于为尔后的科学实践所证实。马克思主义之所以被称为真理,也不但在于马克思等人科学地构成此学说时,而且在于为尔后革命的阶级斗争与民族斗争的实践所证实。辩证唯物论之是否为真理,在于经过无论什么人的实践都不能逃出它的范围。认识史的实践告诉我们,许多理论的真理性是不完全的,经过实践的检验而纠正了它们的不完全性。许多理论是错误的,经过实践的检验而纠正其错误。所谓“实践是真理的标准”,所谓“实践是认识论第一与基本的观点”,理由就在这个地方。斯大林说的好:“离开实践的理论,是空洞的理论,离开理论的实践,是盲目的实践”[24]。\n说到这里,认识运动就完成了吗?我们的答复是完成了,又没有完成。社会的人投身于变革在某一一定发展阶段内之某一一定客观过程的实践中(不论是关于变革某一自然过程的实践,或变革某一社会过程的实践),由于客观过程的反映与主现能动性的作用,使得人的认识由感性的推移到了理性的,造成了大体上相应于该客观过程之法则性的理论、思想、计划、或方案,然后再应用这种理论、思想、计划或方案于该同一客观过程的实践,如果能够实现预想的目的,即将预定的理论、思想、计划、方案于该同一过程的实践中变为事实,或大体上变为事实,那末,对于这一具体过程的认识运动算是完成了。例如,在变革自然的过程中,某一工程计划的实现,某一科学假想的证实,某一器物的制成,某一农产的收获,在变革社会过程中,某一罢工的胜利,某一战争的胜利,某一教育计划的实现,某一救国团体的成立,都算实现了预想的目的。然而一般说来,不论在变革自然或变革社会的实践中,人们原定的理论、思想、计划、方案,毫无改变地实现出来之事,是很少的。这是因为从事变革现实的人们,常常受着许多的限制,不但常常受着科学条件与技术条件的限制,而且也受着客观过程表现程度的限制(客观过程的方面及本质尚未充分暴露)。在这种情形之下,由于实践中发现前所未料的情况,因而部分地改变理论、思想、计划、方案的事是常有的,全部地改变的事也是有的。即是说原定的理论、思想、计划、方案,部分或全部不合于实际,部分错了或全部错了的事,都是有的。许多时候须反复失败过多次,才能纠正错误的认识,才能到达于同客观过程的规律性相符合,因而才能够变主观的东西为客观的东西(即在实践中得与预想结果之正确的认识)。但不管怎样,到了这种时候,人们对于在某一一定发展阶段内之某一一定客观过程的认识运动,算是完成了。\n然而对于过程之推移而言,人的认识运动是没有完成的。任何过程,不论是属于自然界的与属于社会的,由于内部的矛盾与斗争,都是向前推移向前发展的,人的认识运动也应跟着推移与发展。依社会运动来说,所贵于革命的指导者,不但在于当自己的理论、思想、计划、方案有错误时须得善于加以改正,如同上面已经说到的,而且在于当某一一定的客观过程已经从某一一定的发展阶段向另一一定的发展阶段推移转变的时候,须得善于使自己及参加革命的人员在主观认识上也跟着推移转变,即是要使新的革命任务与新的工作方案的提出,适合于新的情况的变化。革命时期情况的变化是很急速的,如果革命党人的认识不能随之而急速变化,就不能引导革命走向胜利。然而思想落后于实际的事是常有的,这是因为人的认识受了许多限制的原故。许多人受了阶级条件的限制(反动的剥削阶级,他们已无认识任何真理的能力,因而也没有改造宇宙的能力,相反地,他们变成了阻碍认识真理与改造世界的敌人),有些人受了劳动分工的限制(劳心、劳力的分工,各业之间的分工),有些人受了原来的错误思想的限制(唯心论与机械论等多属于剥削份子;但也有被剥削份子,由于剥削份子的教育而来),而一般的原因则在受限制于技术水平与科学水平的历史条件。无产阶级及其政党,应该利用自己天然优胜的阶级条件(这是任何别的阶级所没有的),利用新的技术与科学,利用马克思主义的世界观与方法论,紧密地依靠革命实践的基础,使自己的认识跟着客观情况的变化而变化,使理论的东西随历史的东西,平行并进,达到完满地改造世界的目的。\n我们反对革命队伍中的顽固派,他们的思想不能随变化了的客观情况而前进,在历史上表现为右倾机会主义。中国 1927 年的陈独秀主义,苏联的布哈林主义,都属于这一类。这些人看不出矛盾的斗争已将客观过程推向前进了,而他们的认识仍然停止在旧阶段。一切顽固派的思想都有这样的特征。他们的思想离开了社会的实践,他们不能站在社会车轮的前头充任向导的工作,他们只知跟在车轮后面怨恨车轮走的太快了,企图把它向后拉,开倒车。\n我们也反对“左”翼清谈主义。中国 1930 年的李立三主义,苏联在尚可作为一个共产主义派别看待时的托洛斯基主义(现在则已成最反动的派别),以及世界各国的超左思想,都属于这一类。他们的思想超过客观过程的一定发展阶段,有些把幻想看作真理,有些则把仅在将来有现实可能性的理想,强迫放在现时来做,离开了当前大多数人的实践,离开了当前的现实性,行动上表现为冒险主义。\n唯心论与机械论,机会主义与冒险主义,都没有唯物辩证的认识论的根据,他们都是以主观同客观相分裂,以认识与实践相舍离为特征的。以科学的社会实践为特征的马克思主义的认识论,不能不坚决反对这些错误思想。马克思主义者承认,在绝对的总的宇宙发展过程中,各个具体过程的发展都是相对的,因而人的认识也在绝对的真理中对于在各个一定发展阶段上的具体过程之认识只有相对的真理。客观过程的发展是充满着矛盾与斗争的发展,人的认识运动也是充满着矛盾与斗争的发展。一切客观世界的辩证法的运动,都或先或后地能够反映到认识中来。实践中之发展与消灭的过程是无穷的,人的认识之发生、发展与消灭的过程也是无穷。根据于一定的理论、思想、计划、方案以从事于变革客观现实的实践,一次又一次地向前,人对客观现实的认识也就一次又一次地深化。客观现实世界的变化运动永远没有完结,人在实践中对真理的认识也永远没有完结。马克思主义没有结束真理,而是在实践中不断地开辟认识真理的道路。我们的结论是主观与客观、理论与实践、知与行的具体历史的统一,反对一切离开具体历史的“左”的或“右”的错误思想。\n大宇宙中自然发展与社会发展到了今日的时代,正确地认识宇宙与改造宇宙的责任,已经历史地落在无产阶级及其政党的肩上。这种根据科学认识而定下来的改造世界的实践过程,在世界、在中国均已到达了一个历史的时节——自有历史以来未曾有过的重大时节,这就是整个儿地推翻世界与中国的黑暗面,把它转变过来成为前所未有的光明世界。无产阶级及革命人民改造世界的斗争,包括实现下述的任务:改造客观世界,也改造自己的主观世界——改造自己的认识能力,改造主观世界同客观世界的关系。地球上已经有一部分实行了这种改造,这就是苏联。他们还正在为自己为世界推进这种改造过程。中国人民与世界人民也都正开始或将要通过这样的改造过程。所谓被改造的客观世界,其中包括了一切反对改造的人们,他们的被改造,须通过强迫的阶段,然后才能进入自觉的阶段。世界到了全人类都自觉地改进自己与改造世界的时候,那就是世界的共产主义时代。\n通过实践而发现真理,又通过实践而证实真理与发展真理。从感性认识而能动地发展到理性认识,又从理性认识而能动地指导革命实践,改造主观世界与客观世界。实践、认识、再实践、再认识的形式,循环发展以至无穷,而实践与认识之每一循环的内容,都比较地进到高一级的程度——这就是唯物辩证法的全部认识论,这就是唯物辩证法的知行统一观。(第二章完)\n注释:\n[1] 参见《黑格尔〈逻辑学〉一书摘要》中《第 2 版序言》:“逻辑不是关于思维的外在形式的学说,而是关于“一切物质的、自然的和精神的事物”的发展规律的学说,即关于世界的全部具体内容的以及对它的认识的发展规律的学说,即对世界的认识的历史的总计、总和、结论。”(《列宁全集》中文第 2 版,第 55 卷,第 77 页)\n[2] 出自《自然辩证法》中《[辩证法作为科学]•辩证法》。参见《马克思恩格斯选集》中文第 3 版,第 3 卷,第 907 页:“自然界、社会和思维的发展的一个一般规律”。\n[3] 出自《卡尔•马克思(传略和马克思主义概述)》中《辩证法》。参见《列宁全集》中文第 2 版,第 26 卷,第 55 页:“最全面、最富有内容、最深刻的发展学说。”\n[4] 出自《卡尔•马克思(传略和马克思主义概述)》中《辩证法》。参见《列宁全集》中文第 2 版,第 26 卷,第 55 页:“把自然界和社会的实际发展过程(往往伴有飞跃、剧变、革命)弄得残缺不全。”\n[5] 出自《黑格尔辩证法(逻辑学)的纲要》。参见《列宁全集》中文第 2 版,第 55 卷,第 290 页:“不必要三个词:它们是同一个东西。”\n[6] 出自《卡尔•马克思〈政治经济学批判。第一分册〉》第 2 部分。参见《马克思恩格斯选集》中文第 3 版,第 2 卷,第 12 页:“黑格尔的思维方式不同于所有其他哲学家的地方,就是他的思维方式有巨大的历史感作基础。形式尽管是那么抽象和唯心,他的思想发展却总是与世界历史的发展平行着,而后者按他的本意只是前者的验证。”\n[7] 出自《卡尔•马克思〈政治经济学批判。第一分册〉》第 2 部分。参见《马克思恩格斯选集》中文第 3 版,第 2 卷,第 13 页:“历史常常是跳跃式地和曲折地前进的,如果必须处处跟随着它,那就势必不仅会注意许多无关紧要的材料,而且也会常常打断思想进程;并且,写经济学史又不能撇开资产阶级社会的历史,这就会使工作漫无止境,因为一切准备工作都还没有做。因此,逻辑的方式是唯一适用的方式。但是,实际上这种方式无非是历史的方式,不过摆脱了历史的形式以及起扰乱作用的偶然性而已。”\n[8] 出自《黑格尔〈逻辑学〉一书摘要》中《第 2 版序言》。参见《列宁全集》中文第 2 版,第 55 卷,第 75 页:“逻辑的范畴是‘外部存在和活动的’‘无数’‘细节’的简化”。\n[9] 出自《黑格尔〈逻辑学〉一书摘要》中《第 2 版序言》。参见《列宁全集》中文第 2 版,第 55 卷,第 78 页:“范畴是区分过程中的梯级,是帮助我们认识和掌握自然现象之网的网上纽结。”\n[10] 出自《黑格尔〈逻辑学〉一书摘要》中《主观逻辑或概念论•第 2 篇 客观性》。参见《列宁全集》中文第 2 版,第 55 卷,第 160 页:“人的实践活动必须亿万次地使人的意识去重复不同的逻辑的式,以便这些式能够获得公理的意义。”\n[11] 出自《黑格尔〈逻辑学〉一书摘要》中《主观逻辑或概念论•第 3 篇 观念》。参见《列宁全集》中文第 2 版,第 55 卷,第 186 页:“人的实践经过亿万次的重复,在人的意识中以逻辑的式固定下来。这些式正是(而且只是)由于亿万次的重复才有着先入之见的巩固性和公理的性质。”\n[12] 原文如此,恐为“论理”(即“逻辑”)误。\n[13] 出自《自然辩证法》中《[物质的运动形式以及各门科学的联系]》。参见《马克思恩格斯选集》中文第 3 版,第 3 卷,第 943 页:“每一门科学都是分析某一个别的运动形式或一系列互相关联和互相转化的运动形式的,因此,科学分类就是这些运动形式本身依其内在序列所进行的分类、排序,科学分类的重要性也正在于此。”\n[14] 出自《自然辩证法》中《[辩证法作为科学]•[认识]》。参见《马克思恩格斯选集》中文第 3 版,第 3 卷,第 939 页:“‘物质’和‘运动’这样的词无非是简称,我们就用这种简称把可感知的许多不同的事物依照其共同的属性概括起来。”\n[15] 出自《唯物主义和经验批判主义》第 3 章第 5 节。参见《列宁全集》中文第 2 版,第 18 卷,第 180 页:“世界上除了运动着的物质,什么也没有,而运动着的物质只能在空间和时间中运动。人类的时空观念是相对的,但绝对真理是由这些相对的观念构成的;这些相对的观念在发展中走向绝对真理,接近绝对真理。正如关于物质的构造和运动形式的科学知识的可变性并没有推翻外部世界的客观实在性一样,人类的时空观念的可变性也没有推翻空间和时间的客观实在性。”\n[16] 出自《黑格尔〈逻辑学〉一书摘要》中《主观逻辑或概念论•第 3 篇 观念》。参见《列宁全集》中文第 2 版,第 55 卷,第 165 页:“矛盾的发生和解决的永恒过程中”。\n[17] 出自《黑格尔〈逻辑学〉一书摘要》中《主观逻辑或概念论•第 1 篇 主观性》。参见《列宁全集》中文第 2 版,第 55 卷,第 152 页:“认识是人对自然界的反映。但是,这并不是简单的、直接的、完整的反映,而是一系列的抽象过程,即概念、规律等等的构成、形成过程”。\n[18] 即波格丹诺夫。\n[19] 出自《自然辩证法》中《[辩证法作为科学]•[认识]》。参见《马克思恩格斯选集》中文第 3 版,第 3 卷,第 938 页:“对自然界的一切真实的认识,都是对永恒的东西、对无限的东西的认识,因而本质上是绝对的。”\n[20] 出自《黑格尔〈逻辑学〉一书摘要》中《主观逻辑或概念论•第 3 篇 观念》。参见《列宁全集》中文第 2 版,第 55 卷,第 183 页:“实践高于(理论的)认识,因为它不仅具有普遍性的品格,而且还具有直接现实性的品格。”\n[21] 出自《黑格尔〈逻辑学〉一书摘要》中《主观逻辑或概念论•概念总论》。参见《列宁全集》中文第 2 版,第 55 卷,第 142 页:“物质的抽象,自然规律的抽象,价值的抽象以及其它等等,一句话,一切科学的(正确的、郑重的、非瞎说的)抽象,都更深刻、更正确、更完全地反映着自然。”\n[22] 参见前注。\n[23] 出自《俄国社会民主党人的任务》以及《怎么办?》第 1 章第 4 节。分别参见《列宁全集》中文第 2 版,第 2 卷,第 443 页;第 6 卷,第 23 页:“没有革命的理论,就不会有革命的运动。”\n[24] 出自《论列宁主义基础》第 3 部分《理论》。参见《斯大林选集》上卷,人民出版社,1979 年,第 199—200 页:“离开革命实践的理论是空洞的理论,而不以革命理论为指南的实践是盲目的实践。”\n第三章 唯物辩证法 前面简述了“唯心论与唯物论”及“辩证法唯物论”两个问题。关于辩证法问题,仅有概略的提到,现在来系统地讲这个问题。\n马克思主义的世界观(或叫宇宙观),是辩证法唯物论,不是形而上学的唯物论(或叫机械的唯物论)。这一点区别,是一个天翻地复[1]的大问题。世界是一个什么样子的?从古至今有三种主要的答案:第一种是唯心论(不管是形而上学的唯心论,或辩证法的唯心论),说世界是心造的,引申起来又可说是神造的。第二种是机械唯物论,否认世界是心的世界,说世界是物质的世界,但物质是不发展的,不变化的。第三种是马克思主义的答案,推翻了前面两种,说世界不是心造的,也不是不发展的物质,而是发展的物质世界,这就是辩证法唯物论。马克思主义这样地看世界,把世界在从来人们眼睛中的样子翻转了过来,这不是天翻地复的大议论吗?世界是发展的物质世界,这种议论,在西洋古代的希腊就有人说过了,不过因为时代的限制,还只简单地笼统地说了一说,叫做朴素的唯物论。没有(也不可能有)科学的基础,然而议论是基本上正确的。黑格尔创造了辩证的唯心论,说世界是发展的,但是心造的,他是唯心发展论,其正确是发展论(即辩证论),其错误是唯心发展论。西洋十七、十八、十九三个世纪,法德等国的资产阶级唯物论,则是机械观的唯物论。他们说世界是物质世界,这是对的,说是象机械一样的运动,只有增减或位置的变化,没有性质上的变化,这是不对的。马克思继承了希腊朴素的辩证唯物论,改造了机械唯物论与辩证唯心论,造成了从古以来没有过的、放在科学基础之上的辩证唯物论,成为全世界无产阶级及一切被压迫人民的革命的武器。\n唯物辩证法是马克思主义的科学方法论,是认识的方法,是论理的方法,然而它就是世界观。世界本来是发展的物质世界,这是世界观。拿了这样的世界观转过来去看世界,去研究世界上的问题,去想世界上的问题,去解决世界上的问题,去指导革命,去做工作,去从事生产,去指挥作战,去议论人家长短,这就是方法论。此外并没有别的什么单独的方法论。所以在马克思主义者手里,世界观同方法论是一个东西,辩证法、认识论、论理学,也是一个东西。\n我们要系统地来讲唯物辩证法,就要讲到唯物辩证法的许多问题,这就是它的许多范畴、许多规律、许多法则(这几个名词是一个意思)。\n唯物辩证法究竟有些什么法则呢?这些法则中那些是根本法则,哪些是附从于根本法则而又为唯物辩证法学说中不可缺少不可不解决的方面、侧面或问题呢?所有这些法则,为什么不是主观自造的,而是客观世界本来的法则呢?对于这些法则的学习、了解,是为了什么呢?\n这个完整的革命的唯物辩证法学说,创造于马克思与恩格斯,列宁发展了这个学说。到了现在苏联社会主义胜利与世界革命时期,这个学说又走上了新的发展阶段,更加丰富了它的内容。这个学说中包含的范畴首先是如下各项:\n矛盾统一法则; 质量互变法则; 否定之否定法则。 以上是唯物辩证法的根本法则。除古代希腊的朴素唯物论曾经简单地无系统地指出了这些法则的某些意义,及黑格尔唯心地发展了这些法则外,都是被一切形而上学(所谓形而上学,就是反发展论的学说)所否定了的。直到马克思、恩格斯,才唯物地改造了黑格尔的这些法则,成为马克思主义世界观与方法论之最基本的部份。\n唯物辩证法所包含的范畴,除了上述根本法则外,同这些根本法则联系着,还有如下各范畴:\n本质与现象; 形式与内容; 原因与结果; 根据与条件; 可能与现实; 偶然与必然; 必然与自由; 链与环、等等。\n这些范畴,有些是从来形而上学及唯心辩证法所着重研究过的,有些是从来哲学片面地研究过的,有些则是马克思主义新提出的。这些范畴,在马克思主义的革命理论家与实践家手里,揭去了从来哲学唯心的及形而上学的外衣,克服其片面性,发现了它们的真实形态,并且随着时代的进步,极大地丰富了它们的内容,成为革命的科学方法论中重要的成份。拿这些范畴同上述根本的范畴合在一起,就形成一个完整的深刻的唯物辩证法的系统。 所有这些法则或范畴,都不是人的思想自己造出来的,而是客观世界本来的法则。一切唯心论都说精神造出物质,那末,在他们看来,哲学的法则、原则、规律或范畴,自然更是心造的了。发挥了辩证法系统的黑格尔,就是这样的去看辩证法的。在他看来,辩证法不是从自然和社会的历史中抽取出来的法则,而是纯粹思想上的论理系统。人的思想造出了这一套系统之后,再把它们套到自然和社会上去。马克思、恩格斯揭去黑格尔的神秘的外衣,丢弃了它们的唯心论,把辩证法放在唯物论的地位。恩格斯说;“辩证法的法则,是从自然和人类历史抽取出来的,但他们并非别的,就是这两个历史发展领域的最普遍的发展法则,就实质论,可以归纳为质量互变,矛盾统一,否定之否定这三个根本法则”[2]。辩证法法则是客观世界的法则,同时也是主观思想里头的法则,因为人的思想里头的法则不是别的,就是客观世界的法则通过实践在人类头脑中的反映。辩证法、认识论、论理学是一个东西,前面已经讲过了。\n我们学习辩证法是为了什么呢?不为别的,单单为了要改造这个世界,要改造这个世界上面人与人、人与物的老关系。这个世界上面的人类,大多数过着苦难的日子,受着少数人所控制的各种政治经济制度的压迫。在我们中国这个地方生活着的人类,受着惨无人道的双重性制度的压迫——民族压迫与社会压迫,我们必须改变这些老关系,争取民族解放与社会解放。\n要达到改造中国同世界的目的,为什么要学习辩证法呢?因为辩证法是自然同社会的最普遍的发展法则,我们明了它,就得到了一种科学的武器。在改造自然同社会的革命实践中,就有了同这种实践相适应的理论同方法。唯物辩证法本身是一种科学(一种哲理的科学),它是一切科学的出发点,又是方法论。我们的革命实践本身也是一种科学,叫做社会科学或政治科学。如果不懂得辩证法,则我们的事情是办不好的。革命中间的错误,无一不违反辩证法。但如懂得了它,那就能生出绝大的效果。一切做对了的事,考究起来,都是合乎辩证法的。因此,一切革命的同志们,首先是干部,都应用心地研究辩证法。\n有人说:许多人懂得实际的辩证法,而且也是实际的唯物论者,他们虽没有读过辩证法书,可是做起事来是做得对的,实际上合乎唯物辩证法,他们就没有特别研究辩证法的必要了。这种话是不对的。唯物辩证法是一种完备的深刻的科学,实际上具有唯物的与辩证的头脑之革命者,他们虽从实践中学得了许多辩证法,但是没有系统化,没有如同已经成就的唯物辩证法那样的完备性与深刻性,因此还不能洞察运动的远大前途,不能分析复杂的发展进程,不能捉住重要的政治关节,不能处理各方面的革命工作,因此仍有学习辩证法的必要。\n又有人说,辩证法是深奥难懂的,一般人没有学会的可能。这话也是不对的。辩证法是自然、社会与思想的法则,任何有了一些社会经验(生产与阶级斗争的经验)的人,他就本来了解了一些辩证法。社会经验更多的人,他本来了解的辩证法就更多些,不过还处在零乱的常识状态,没有完备的深刻的了解。拿着这种常识辩证法加以整理与深造,是并不困难的。辩证法之所以使人觉得困难,是因为没有善于讲解的辩证法书,中国许多辩证法书,不是错了,就是写的不好或不大好,使人望而生畏。所谓善于讲解的书,在于以通俗的言语,讲亲切的经验,这种书将来总是要弄出来的。我这个讲义也不是好的,因为我自己还在开始研究辩证法,还没有可能写出一本好书,也许将来有此可能,我也有这个志愿,但要依研究的情形才能决定。\n以下分述辩证法的各个法则。\n矛盾统一法则 这个法则,是辩证法最根本的法则。列宁说:“就根本意义上来讲,辩证法就是研究客体本质中的矛盾”[3]。所以列宁常称这个法则为辩证法的实质,又称之为辩证法的核心。因此,我们的辩证法,就从这个问题讲起,并且把这个问题讲得比其他问题详细一些。\n这个问题中,包含着许多问题,这些问题就是:\n两种发展观; 形式论理学的同一律,与辩证法的矛盾律; 矛盾的普遍性; 矛盾的特殊性; 主要的矛盾与主要的矛盾方面; 矛盾的同一性与斗争性; 对抗在矛盾中的地位。 下面逐一说明这些问题。\n(一)两种发展观 人类思想史中,从来就有关于世界发展的两种见解,一种是形而上学的发展观;一种是辩证法的发展观。这两种发展观的区别何在呢?\n形而上学的发展观\n形而上学,亦称玄学,在历来的思想中,占着统治的地位,这种哲学的内容,是说明他们所谓处于经验以外的事物,即论绝对体、论实质等等的学说。在近代哲学中,所谓形而上学,是用静的观点去观察事物的一种思想方法,把世界一切事物的形态和种类看成是永远不变化的。这种思想,统治于十七十八世纪的欧洲。由于阶级斗争和科学发展的结果,到了现代,即十九、二十世纪,辩证法的思想就一日千里地走上了世界舞台,但形而上学却又以庸俗的进化论(庸俗的、即谓鄙陋的、简单的)的形态,顽固地对抗着辩证法。\n所谓形而上学的与庸俗进化论的发展观,概括说来,是说发展就是数量的增减,外力的推动,场所的变化。一切事物及这些事物在人的思想上的反映,都是永远如此的。事物的特性,是事物原来就有的,不过开头取萌芽状态,后来进到显著的地步而已。说到社会的发展,他们就认为是某些永远不变其性质的特点之增长和反复。这些特点,例如资本主义的剥削、竞争、个人主义等等,就是在古代奴隶社会,甚至原始野蛮社会,都可以找得出来。说到社会发展的原因,就用社会外部的地理、气候条件去说明它。这种发展观,从事物外部去找发展的原因,反对事物因内部矛盾引起发展的学说,它就不能解释事物之质的多样性,不能解释一种质变化到他种质的现象。这种思想,在十七、十八世纪是自然绝对不变论(机械唯物论),在二十世纪是庸俗进化论(布哈林的均衡论)等。\n辩证法的发展观\n主张从事物自己里头,从一事物对他事物的关系里头,去研究事物的发展,即把事物的发展看做是事物内部必然的、独立的、自己的运动,即事物的自动。事物发展的根本原因,不在外面而在内面,在于事物内部的矛盾性,任何事物内部都有这种矛盾性,因此引起了事物的运动与发展。\n这样看起来,辩证法的发展观,反对了形而上学的与庸俗进化论的外因论,或被动论。这是清楚的,单纯的外部原因只能引起事物之机械的运动,即范围之大或小,数量之增或减,不能说明世界上事物何以有性质上的千差万别。事实上,即便是外力推动的机械运动,也要通过事物内部的矛盾性。植物动物之单纯的增长,也不只是数量的增加,同时就发生性质的变化,单纯增长也是矛盾引起的发展。至于社会的发展,同样主要地不是外因而是内因。许多国家在差不多一样的地理气候条件下,各个国家发展的差异性和不平衡性,却非常之大。设同一个国家罢,在地理气候并没有变化的情形下,社会变化却是很大的。地球各国都有此种情形。旧俄帝国变为社会主义的苏联,单纯封建的闭关锁国的日本变为帝国主义的日本,封建的西班牙正在变化到人民民主的西班牙,这些国家的地理气候并没有变。几千年封建制度的中国是变化最少的,然而近来却起了大变动,正在变化到自由解放的新中国去,难道中国今天的地理气候同数十年前的有什么两样?很明显的,不是外因而是内因。自然界的变化,由于自然界事物内部矛盾的发展。社会变化,由于社会内部矛盾的发展。生产力与生产关系的矛盾,阶级之间的矛盾,推动了社会的前进。辩证法排除外因吗?并不排除的。外因是变化的条件,内因是变化的根据,外因通过内因而起作用。鸡蛋因得适当温度而变化为鸡子,但温度不能使石头变为鸡子,因为内的根据不同。帝国主义的压力加速了中国社会的变化,也是通过中国内部自己的规律性而起变化的。两军相争,一胜一败,所以胜败,皆决于内因,胜者或因其强,或因其指挥无误,败者或因其弱,或因其指挥失宜,外因通过内因而引起变化。1927 年资产阶级战败了无产阶级,是通过了无产阶级内部的(共产党内部的)机会主义而起作用的。一个阶级或一个政党要引导革命归于胜利,依靠自己没有政治路线的错误,依靠自己政治上组织上的巩固。中国东北沦亡,华北危急,主要由于中国之弱(1927 年革命失败,政权不在人民手里,造成了内战与独裁制度),日本帝国主义乃得乘机而入。驱逐日寇,主要依靠民族统一战线执行坚决的革命战争。“物必先腐也,而后虫生之,人必先疑也,而后谗入之”,这是苏东坡的名言。“内省不疚,夫何忧何惧”,这也是孔夫子的实话。一个人少年充实,他就不容易感受风寒;苏联至今没有受日本的侵袭,全是因为他的强固;雷公打豆腐,拣着软的欺了,全在自强,怨天尤人,都没有用,人定胜天,困难可以克服,外界的条件可以改变,这就是我们的哲学。\n我们反对形而上学的发展观,主张辩证法的发展观。我们是变化论者,反对不变论,我们是内因论者,反对外因论。\n(二)形式论理学的同一律与辩证法的矛盾律 上面说了形而上学的发展观与辩证法的发展观,这两种对于世界观上面的斗争,就形成了思想方法上面形式论理与辩证论理的斗争。\n资产阶级的形式论理学上有三条根本规律,第一条叫做同一律,第二条叫做矛盾律,第三条叫做排中律。什么是同一律呢?同一律说:在思想过程中概念是始终不变化的,它永远等于自己。例如原素永远等于原素,中国永远等于中国,某人永远等于某人。它的公式是:甲等于甲。这一规律是形而上学的。恩格斯说它是旧宇宙观的根本规律。它的错误,在于不承认事物的矛盾与变化,因而从概念中除去了暂时性相对性,给与了永久性、绝对性。不知事物同反映事物的概念都是相对的变化的,某一原素并不永远等于某一原素,各种原素都在变化着。中国也不永远等于中国,中国在变化着,过去古老封建的中国同今后自由解放的中国是两个东西。某人也不永远等于某人,人的体格思想都在变化着。1925—1927 年的蒋介石不等于 1927 年以后的蒋介石,现在以后的蒋介石又将不等于以前。思想中的概念是客观事物的反映,客观事物在变化,概念的内容也在变化。事实上永久等于自身的概念,世界上一个也没有。\n什么是矛盾律呢?矛盾律说:概念自身不能同时包含二个或二个以上互相矛盾的意义,假如某一个概念中包含了二个矛盾的意义,就算是论理的错误。矛盾的概念,不能同时两边都对,或两边都不对,对的只能是其中的一边,它的公式是:甲不等于非甲。康德曾举出如下四种矛盾思想:世界在时间上是有始终的,在空间上是有限度的;世界在时间上没有始终,在空间上亦无限度。这是第一种。世上一切都是单纯的(不可再分的)物性组成的,世上没有单纯的东西,一切都是复杂的(可以再分的)。这是第二种。世上存在着自由的原因,世上没有任何的自由,一切都是必然的。这是第三种。世上存在着某种必然的实质,世上没有必然的东西,一切都是偶然的。这是第四种。康德把这些不可调和的,互相反对的原理,名之曰“二律矛盾”。但是他说这些只是人的思想上的矛盾,实际世界里是并不存在的。依照形式论理学的矛盾律,这些矛盾乃是一种错误,必须加以排除。但是实际上思想是事物的反映,事物无一不包含着矛盾,因之概念也无一不包含矛盾。这不是思想的错误,正是思想的正确。辩证论理的矛盾统一律,就在这个基础上面建立起来。只有形式论理排除矛盾的矛盾律,乃是真正的错误思想。矛盾律在形式论理学中只是同一律之消极的表现,作为同一律的一种补充,目的在于巩固所谓概念等于自身,甲等于甲的同一律。\n排中律是什么呢?排中律说:在概念之两相反的意义中,正确的不是这个就是那个,决不会两个都不正确,而跑出第三个倒是正确的东西来。它的公式是:甲等于乙,或不等于乙,但不会等于丙。他们不知道事物同概念是发展着的,在事物同概念的发展过程中,不但表现其内部的矛盾因素,而且可以看见这些矛盾因素的移去、否定、解决,而转变成为非甲非乙的第三者,转变成为较高一级的新事物或新概念。正确的思想,不应排除第三者,不应排除否定之否定律。无产阶级同资产阶级矛盾着,照排中律说来,正确的不是前者,就是后者,不会是没有阶级的社会;然而恰好社会进化的过程不是停止于阶级斗争,而要走到无阶级的社会中去。中国同日本帝国主义矛盾着,但我们不但反对日本帝国主义的侵略,也不赞同中国独立后同日本处于永久敌对的地位,而主张经过民族革命及日本国内的革命,把两个民族进到自由联合的阶段去。资产阶级的民主主义同无产阶级的民主主义的对立也是一样,它们的更高一级是无国家无政府的时代,经过无产阶级民主去达到它。形式论理的排中律,也是它的同一律的补充,只承认概念的固定状态,反对它的发展,反对革命的飞跃,反对否定之否定的法则。\n由此看来,整个形式论理学的规律,都是反对矛盾性,主张同一性,反对概念及事物的发展变化,主张概念及事物的凝固静止,是同辩证法正相反对的东西。\n形式论理家为什么这样做?因为他们在事物的联系以外,在事物不间断的相互作用以外去看事物,即在静止中看事物,不在运动中看事物;在割断中看事物,不在联系中看事物。所以他们以为承认事物及概念中的矛盾性及否定之否定的关系,是不可能的,而主张了死板凝固的同一律。\n辩证法则不然,在运动中联系中看事物,和形式论理学的同一律针锋相对,主张了革命的矛盾律。\n辩证法认为思想上的矛盾不是别的,乃客观外界矛盾的反映。辩证法不拘泥于两条原则外表上似乎相冲突的情形(例如康德所举的四条矛盾原理及上面我所举的许多矛盾思想),而透视到事物内部的本质中。辩证法家的任务,在于做那些形式论理家所不做的工作,向着研究的对象,集中注意于找出它的矛盾的力量、矛盾的倾向、矛盾的方面、矛盾的定性之内部的联系来。客观世界与人的思想都是动的、辩证的,不是静的、形而上学的。革命的矛盾律(即矛盾统一法则)在辩证法中所以占据着最主要的位置,理由就在这个地方。\n全部形式论理学只有一个中心,就是反动的同一律。全部辩证法也只有一个中心,就是革命的矛盾律。辩证法是否反对事物或概念的同一性呢?不反对的,辩证法承认事物或概念之相对的同一性。那末,辩证法为什么要反对形式论理学的同一律呢?因为形式论理学的同一律,是排除矛盾的绝对的同一律。辩证法承认事物或概念的同一性,说的是同时包含矛盾,同时又互相联结;这种同一性就是指矛盾之互相联结,它是相对的、暂时的。形式论理的同一律既然是排除矛盾的绝对的同一律,它就不得不提出反对一概念转变到它概念,一事物转变到它事物的排中律。而辩证法却把事物或概念的同一性看作暂时的、相对的、有条件的,而因矛盾的斗争引导事物或概念变化发展的这种规律,则是永久的、绝对的、无条件的。因为形式论理不反映事物的真相,因此辩证法不能容许其存在。科学的真理只有一个,这真理就是辩证法。\n(三)矛盾的普遍性 这个问题,有两方面的意义:其一是说,矛盾存在于一切过程中;其二是说,每一过程中存在着自始至终的矛盾运动。这就叫做矛盾的普遍性或绝对性。\n恩格斯说:“矛盾就是运动”[4]。列宁对于矛盾统一法则所下的定义,说它就是“承认(发见)一切自然(社会和精神也在内)现象和过程中的相互排除的对立倾向。”[5]这些意见是对的吗?是对的。一切事物中包含的矛盾方面之相互依赖和相互斗争,决定一切事物的生命,推动一切事物的发展。没有矛盾,就没有世界。因此,这一法则,是最普遍的法则,适用于客观世界的一切现象,也适用于思想现象。它在辩证法中,是一个最根本的、最具有决定意义的法则。\n为什么说矛盾就是运动?恩格斯的说法,不是有人反驳过了的吗?这是因为马克思、恩格斯、列宁论矛盾的学说,变成无产阶级革命之最重要的理论基础,因此,引起了资产阶级理论家之拼命的攻击,总想推翻恩格斯这个“运动即矛盾”的定律,举起了他们的反驳,并且搬出了下述的理由。他们说:实在世界中运动的事物,是在各个不同的瞬间,经过各个不同的空间点,当事物处于某一点时,它就占据那一点,到另一点时,又占据另一点。这样,事物的运动是在空间和时间上分成许多段落的,这里没有任何的矛盾;如有矛盾就不能运动。\n列宁指出这种说法的全部荒谬性。指出这种说法,事实上把不断的运动,看成在空间和时间上的许多段落,许多静止状态,结果是否定了运动。他们不知事物处于某一个新位置,是因为事物从空间的某一点走到另一点的结果,即运动的结果。所谓运动,就是处于一点,同时又不处于一点。没有这一个矛盾,没有这个连续和中断的统一,动和静,止和行的统一,运动就根本不可能。否定矛盾,就是否定运动。一切自然、社会和思想的运动,都是这样一种矛盾统一的运动。\n矛盾,不只是简单的运动形式(例如上述的机械性的运动的基础),而且也是世界一切复杂的运动形式的基础。\n生的过程,同它相反的死的过程,不可分的联系着,这不仅在各种有机体的生命中,或有机体内细胞们的生命中,都是如此。新与陈之代谢、生与死之更迭,这一矛盾统一的运动,是一切有机体的生活和发展的必要条件。如果没有这种矛盾,生命现象是不能想象的。\n机械学中,任何一种“动作”,都带着内部的矛盾性,引起“反动作”,没有反动作,动作就无从说起。\n数学中,任何一个数量都带有内部的矛盾性,都可能成为正数与负数,整数与零数。正数与负数,整数与零数,组成了数学的矛盾运动。\n化学中分化化合的矛盾统一律,组成了化学变化的无量的运动,没有这一矛盾,化学现象就不能存在。\n社会生活中,任何一种现象,都带有阶级的矛盾性,劳动的买卖如此,国家的组织如此,哲学的内容也是如此。阶级斗争,是阶级社会的根本规律。\n战争中的攻守、进退、胜败,都是矛盾现象。失去一方,他方就不存在,双方斗争而又联结,组成了战争的总体,推动了战争的发展。\n人的概念之每一差异,都应把它看作客观矛盾的反映。客观矛盾反映人主观的思想,组成了概念的矛盾运动,推动了思想的发展。\n党内不同思想之对立与斗争是经常发生的,这是社会阶层的矛盾在党内的反映。党内没有矛盾和解决矛盾的思想斗争,党的生命也就停止了。\n不论是简单的运动形式,或复杂的运动形式,不论是客观现象,或思想现象,矛盾是普遍地存在着,矛盾存在于一切过程中。\n说到这里,有人要说:可以承认恩格斯同列宁的原则,矛盾即是运动,矛盾存在于一切过程中。但是所谓每一过程中自始至终的矛盾运动,那就未必然罢?不是德波林等人明明说过,在每一过程中并无所谓自始至终的矛盾运动吗?按照德波林的说法,矛盾是存在的,但只存在于过程发展之一定阶段上,不是一开始就在过程中发现。根据德波林,过程的发展循着如次的阶段:开始是简单的差异,随后发生对立,最后成为矛盾。这种公式究竟是对的,还是错的呢?\n这是错的。所谓矛盾的普遍性,不但存在于一切过程中,而且存在于每一过程之一切发展阶段中,这才是马克思主义的革命的矛盾律。根据德波林一派,矛盾不是一开始就在过程中出现,须待过程发展到一定阶段才出现,那末,在那一瞬间以前,过程发展的原因不是由于内在矛盾即过程的分裂,而是由于外在原因了。这样,德波林回到形而上学的外因论、机械论去了。拿这种见解去分析具体问题,他们就看见在苏联的条件下工农之间只有差异,并无矛盾,完全同意于布哈林的意见。在分析法国革命时,他们就认为在革命前,工、农资产阶级合组的第三等级中,也只有差异,并无矛盾(郭列夫[6]的说法)。他们不知道世上的每一差异中就已经包含着矛盾,差异就是矛盾。劳资之间,从两阶级发生的瞬间起,就是互相矛盾的,仅仅没有激化而已。工农之间,即使在苏联条件下,他们的差异就是矛盾,仅仅不会激化成为对抗,不取阶级斗争的形态,不同于劳资间的矛盾,这是矛盾的差别性,而不是有无矛盾的问题。矛盾是普遍的、绝对的,存在于一切过程中,又贯串于一切过程的始终。新过程的发生是什么呢?乃是旧的统一和组成此统一的对立体,让位于新的统一和组成此统一的对立体,新过程就代替旧过程而发生,新过程包含着新矛盾,开始它自己的矛盾发展史。\n过程之自始至终的矛盾运动,列宁指出马克思在资本论中模范地应用了这个原则。他指出,这是研究任何过程所必须应用的方法,列宁自己也正确地应用了它,贯彻于他的全部著作中。\n“马克思在《资本论》中,首先,分析资产阶级社会(商品社会)之最单纯的、最普遍的、最根本的、最经常的、最日常的、数十亿万回被人亲眼看见的关系——商品交换。在这最单纯的现象之中(资产阶级社会的细胞之中),暴露了现代社会之一切矛盾(或一切矛盾的胚芽)。从那里开始的叙述,把这个矛盾的发展(成长及运动),这个社会的发展,在其个别部分的总和上,自始至终地指示于我们。”\n列宁说了上面的话之后,接着说道:“这正是辩证法的一般的叙述方法或研究方法。”[7]\n好,我们不用读桐城派的古文义法了,列宁告诉了我们更好的义法,这就是马克思主义的科学研究法。\n(四)矛盾的特殊性 矛盾存在于一切过程中,矛盾贯串于每一过程之始终,这是矛盾的普遍性与绝对性,前面已经说过了。现在说的,是关于矛盾的特殊性与相对性。这个问题,应从几种情形中研究它。\n首先是各种物质运动形式中的矛盾,都带特殊性。人的认识物质,就是认识物质的运动形式,因为除了运动的物质以外,世界上什么也没有。对于每一种运动形式,应当注意它和其它各种运动形式的共同点。但尤其重要的,成为我们认识事物的基础的东西,乃是注意它的特殊点,就是说,注意它同其它运动形式之质的区别。只有注意这点,才有可能区别事物。唯物辩证法指明:任何运动形式,其内部都包含着本身特殊的矛盾。这种特殊矛盾,就构成一事物区别于他事物之特殊的质。自然界存在着许多运动形式,机械运动、发声、发光、发热、电流、化分、化合等等都是。所有这些物质的运动形式,都是互相依存的,又是本质上互相区别的。每一运动形式所具有的特殊的本质,为它自己的特殊矛盾所规定。这种情形,不但自然界,社会现象和思想现象也是一样。每一社会形式和思想形式,都有它的特殊矛盾和特殊本质。\n科学研究的区分,就是根据科学对象所具有的特殊矛盾性。因此,对于某一现象领域所特有的某种矛盾之研究,就构成某一门科学的对象。例如数学中的正数与负数,机械学中的作用与反作用,物理学中的阴电与阳电,化学中的化分与化合,社会科学中的生产力与生产关系,阶级斗争,军事学中的攻击与防御,哲学中的唯心与唯物、形而上学观与辩证观等等,都是因为具有特殊矛盾与特殊本质,才构成了不同的科学研究的对象。固然,如果不研究矛盾的普遍性,就无从发现事物运动发展的普遍原因;但如果不研究矛盾的特殊性,就无从确定一事物与他事物的特殊的本质,就无从发现事物运动发展的特殊原因.也就无从辨别事物,无从区分科学研究的领域。\n不但要研究每一大系统的物质运动形式之特殊的矛盾性及其所规定的本质;而且要研究每一物质运动形式在其发展的长途中,每一过程的特殊矛盾及其本质。一切运动形式之每一发展过程内,都是不同质的,天下没有同型的矛盾,研究要着重一点。\n不同质的矛盾,只有用不同质的方法才能解决。例如无产阶级与资产阶级的矛盾,用社会主义革命的方法去解决;人民大众与封建制度的矛盾.用民主革命的方法去解决;殖民地与帝国主义的矛盾,用民族战争去解决;无产阶级与农民的矛盾,用农业社会化去解决;共产党内的矛盾,用思想斗争去解决;社会与自然的矛盾,用发展生产力去解决,过程变化,旧过程与旧矛盾消灭,新过程与新矛盾发生,解决矛盾的方法也因之而不同。俄国二月革命与十月革命,所用以解决矛盾的方法是根本不同的。用不同的方法去对付不同的矛盾,这是原则。\n暴露过程中的矛盾在其总体上、在其相互联结上的特殊性,就是说暴露过程的本质,必须暴露过程中矛盾各方面的特殊性,否则暴露过程本质为不可能,这是研究问题要十分注意的。\n一个大过程中包含着许多矛盾。例如在中国资产阶级民主革命过程中,有整个中国社会对帝国主义的矛盾,在中国社会内部有封建制度同人民大众的矛盾,有无产阶级同资产阶级的矛盾,有农民小资产阶级同资产阶级的矛盾,有各个统治集团间的矛盾等等,情形是非常复杂的。这些矛盾不但各个有其特殊性,不能一律看待,而且每一矛盾的两方两,又各有其特点,也是不能一律看待的。我们从事中国革命的人,不但要对各个矛盾总体即矛盾之相互联结,了解其特殊性,而且只有从矛盾的各个方面着手研究,才能了解其总体。所谓了解矛盾之各个方面,就是了解它们每一方面各占何等特定的地位,各用何种具体形式同对方发生依存关系,在依存中及依存破裂后又各用何种具体方法同对方作斗争。研究这些问题,乃是十分重要的事情。列宁主义的主要特点,就是研究无产阶级同资产阶级作斗争之各种具体形式的科学。\n研究问题,忌带主观性、片面性与表面性。所谓主观性,就是不知道客观地看问题,也就是不知道用唯物的观点去看问题。这一点,第二章中已经说过,本节末尾也还要说。现在来说片面性与表面性。所谓片面性,就是不知道全面地看问题。例如只了解中国一方、不了解日本一方,只了解共产党一方、不了解国民党一方,只了解无产阶级一方.不了解资产阶级一方,只了解农民一方、不了解地主一方,只了解顺利情形一方、不了解困难情形一方,只了解正人君子一方、不了解奸巧狡诈一方,只了解现在一方。不了解将来一方,只了解自己一方、不了解他人一方,只了解骄傲一方、不了解谦逊一方,只了解缺点一方、不了解成绩一方,只了解原告一方、不了解被告一方,只了解秘密工作一方、不了解公开工作一方,如此等等。一句话,不了解矛盾各方的特点。这就叫做片面地看问题,或叫做只看见局部,不看见全体,是不能找出解决矛盾的方法的(是不能完成革命任务的,是不能做好所任工作的,是不能正确发展党内思想斗争的)。孙子论军事说:“知己知彼,百战百胜。”他说的是矛盾的双方。唐太宗也说:“兼听则明,偏听则暗。”也懂得片面性不对。可是我们的同志看问题,往往带片面性,这样的人就往往碰钉子。乡下两家或两族相争,做和事老的,须熟识双方争论的原因、争点、现状、要求等等,才能思出和解的办法来。乡下有那种善于和事的人,遇有纠纷,总请他到,这种人实在懂得我们说的要了解矛盾各方面特点这一条辩证法。水浒传上宋公明三打祝家庄,两次都因情况不明,方法不对,打了败仗。后来改变方法,从调查情形入手,于是熟悉了盘陀路,拆散了李家庄、扈家庄与祝家庄的联盟,并且布置了藏在敌人营盘里的伏兵,第三次就打了胜仗。水浒传上有很多唯物辩证的范例,这个三打祝家庄,算是最好的一例,列宁屡次说到对问题要全面去看,坚决反对片面性,我们应该记得他的话。表面性,是说对矛盾总体与矛盾各方的特点,都不去看,否认深入事物里面精细研究矛盾特点的必要,仅仅远远地望一望,粗枝大叶地看到一点矛盾的形相,就想动手去解决矛盾(答复问题,解决纠纷,处理工作,指挥战争)。这样的干法,没有不出乱子的。不但全过程中矛盾运动在其相互联结上,在其各方面情况上,应该注意其特点;过程发展的各阶段,也有特点,也应该注意。过程的根本矛盾及为此根本矛盾所规定的过程之本质,非到过程完结之日是不会消灭的;但是过程的各个发展阶段,情形又往往互相区别。这是因为过程之根本矛盾的性质及过程的本质虽没有变化,但根本矛盾在各个发展阶段上采取逐渐激化的形式,并且,为根本矛盾所规定的许多大小矛盾中.有些是激化了,有些是暂时地局部地解决了,或缓和了,又有些是发生了,因此过程就显出阶段性来。\n例如帝国主义之别于自由资本主义,无产阶级与资产阶级这个根本矛盾的性质及这个社会之资本主义的本质,并没有变;但是两阶级的矛盾激化了,独占资本与自由资本之间的矛盾发生了,各独占集团之间的矛盾发生了,资本输出与商品输出的矛盾发生了,宗主国与殖民地的矛盾激化了,各资本主义国家间的矛盾,即各国不平衡发展状态激化了,因此形成了帝国主义的特殊阶段。\n拿从辛亥革命开始的中国民主革命过程的情形来看,也表现了若干特殊阶段。直到这一革命完成为止,也许还要经过若干阶段,虽然整个过程中根本矛盾的性质及过程之反帝反封建的民主革命的本质(其反面是半殖民地半封建的本质),并没有变;但中间经过辛亥失败,北洋军阀统治,第一次民族统一战线建立与大革命,统一战线的破裂与资产阶级的转入反革命,军阀战争,苏维埃战争,东四省丧失,苏维埃战争停止,国民党政策转变,第二次统一战线建立等等大事变,过去二十多年间已经通过了四五个发展阶段。这些阶段中,包含着有些矛盾激化(例如中日矛盾),有些矛盾部份地暂时地解决(例如北洋军阀的消灭,苏区没收地主土地),有些矛盾又重新发生(例如新军阀之间的斗争,苏区丧失后地主又重新收回土地)等等特殊的情形。\n研究过程各阶段上矛盾的特性,不但在其联结上、在其总体上去看,也同样要从各个方面去看。\n例如国共两党。国民党方面,在第一次统一战线时是革命的、有朝气的,它是各阶级的民主革命联盟。1927 年以后,变到相反的方面,成为地主资产阶级的反动集团。西安事变后,又开始向新的方面转变。这就是国民党在三个阶段上的特点。形成这些特点,当然有种种的原因。共产党方面,第一次统一战线时期,它是幼年的党,对于革命的性质、任务、方法等等的认识,均表现了它的幼年性,因此发生了陈独秀主义;但是它领导了第一次大革命。1927 年以后,领导了苏维埃战争,在同国际国内敌人斗争中锻炼了自己,创造了苏区与红军,但它也犯过一些政治上军事上的错误。1935 年以后,它又领导了统一战线,提出了抗日民族战争与民主共和国的口号。这就是共产党在三个阶段上特点。形成这些特点,也有种种的原因。不研究这些特点,就不能了解两党在各个发展阶段上的特殊的相互关系(统一战线,统一战线破裂,再一个统一战线)。不但两党间,而且更根本的,还有这两个党向其他方面形成矛盾的对立。例如国民党同国外帝国主义的矛盾(有时取妥协形态),同国内人民大众的矛盾。共产党同国外帝国主义的矛盾,同国内剥削阶级的矛盾。由于这些矛盾,所以造成了两党的斗争,又造成了两党的合作。不了解这些矛盾方面的特点,不但不能了解这两个党各个同其他方面的关系,也不能了解两党之间的关系。国民党为什么有与共产党重新合作之可能?就是因为国民党受了日本压迫与人民不满而发生了自己内部变动的原故。\n由此看来,不论研究何种矛盾的特性——各个物质运动形式的矛盾,各运动形式在各个发展过程的矛盾,各个发展过程的矛盾之各方面,各个发展过程在其各个发展阶段上的矛盾,以及各个发展阶段上矛盾之各方面,研究所有这些矛盾的特性,都不能带主观随意性,必须以对它们的具体分析为前提。离开具体分析,就决不能认识矛盾的特性。\n这种具体分析,马克思、恩格斯给了我们以很好的模范。\n当马克思.恩格斯把这一矛盾统一法则应用到社会历史过程的研究时,他们看出社会发展的根本原因,在于生产力和生产关系之间的矛盾,阶级斗争的矛盾,以及由这些矛盾所产生的经济基础同政治及思想的上层建筑之间的矛盾。\n马克思把这—法则应用到资本主义社会经济结构的研究时,他看出这一社会的基本矛盾在于生产的社会性和占有的私人性之间的矛盾。这个矛盾,表现于在个别企业中生产的有组织性和在全社会中生产的无组织性之间的矛盾。这个矛盾的阶级表现则是资产阶级与无产阶级之间的矛盾。\n马克思、恩格斯对于应用辩证法到客观现象的研究时,是这样不带任何主观随意性,而从客观现象的实际运动所包含的具体条件,去看出这些现象中的具体矛盾,矛盾各方面之具体的地位,矛盾之具体的相互关系等等。这种研究态度,是我们应当学习的,舍此便没有第二种研究法。\n矛盾的普遍性与矛盾的特殊性之关系,就是矛盾的共性与个性之关系。其共性是矛盾存在于一切过程中,贯串于一切过程的始终,矛盾即是运动,即是事物,即是过程,即是世界,也即是思想。否认矛盾就是否认了一切。这是共通的道理,古今中外,概莫能例外。所以它是共性,是绝对性。然而这种共性,即包含于个性之中,共性表现于一切个性之中,无个性之存在,也就不能有共性之存在。假如除去了一切个性,还有什么共性呢?因矛盾之各各特殊,大宇长宙,无一同者,变化无穷,其存在也暂,所以是相对的。苏东坡说,“自其变者而观之,则天地曾不能以一瞬”。照现在的意思来说,可以说他说的是矛盾的特殊性,相对性。“自其不变者而观之,则物与我皆无尽。”说的是矛盾的普遍性,绝对性。这一共性个性、绝对相对的道理,是矛盾学说的精髓,懂得了它,就可以一通百通。古人所谓闻道,以今观之,就是闻这个矛盾之道。\n(五)主要的矛盾与主要的矛盾方面 在矛盾特殊性问题中,有两种情形应该特别提出研究的,这就是主要的矛盾与主要的矛盾方面。\n在复杂的过程中.有许多矛盾存在,其中一个是主要矛盾,由于它的存在与发展,规定或影响其他矛盾的存在与发展。\n例如资本主义社会中,无产阶级与资产阶级的矛盾是主要的矛盾;其他如残存的封建势力与资产阶级的矛盾,农民小资产者与资产阶级的矛盾,无产阶级与农民小资产者的矛盾,自由资产阶级与金融资产阶级的矛盾,资产阶级民主主义与法西斯主义的矛盾,资本主义国家相互间的矛盾,帝国主义与殖民地的矛盾,以及其他矛盾等等,都为这个主要矛盾所规定、所影响。\n半殖民地的社会如中国,其主要矛盾与非主要矛盾的关系呈现着复杂的情况。当半殖民地没有遭受帝国主义压迫时,其主要矛盾是封建或半封建制度与人民大众的矛盾,一切其他矛盾都受这个主要矛盾所规定。但当这种社会遭受帝国主义压迫时,内部的主要矛盾能够暂时地转化到非主要地位,而帝国主义与整个或差不多整个半殖民地社会之间的矛盾,能够占据主要的地位,规定一切其他矛盾的发展。这种时候,依帝国主义压迫及半殖民地人民革命的程度,而变化着或主要或非主要矛盾的地位。\n例如当帝国主义向这种国家举行侵略战争,这种国家的内部各阶级,能够暂时地团结起来进行民族战争去反对帝国主义。这时,帝国主义与这种国家之间的矛盾成为主要矛盾,而这种国家内部各阶层的一切矛盾(包括封建制度与人民大众之间这个主要矛盾在内),便都暂时地降到次要与服从的地位。中国的鸦片战争,义和团战争,甲午中日战争,目前的中日战争,在外国,有美国的独立战争,南非洲同英国的战争,菲律宾同西班牙的战争等等,都是如此。\n然而在另一种情形,则矛盾的地位起了变化。当着帝国主义不用战争压迫而用政治、经济、文化的形式进行比较温和的压迫,半殖民地国家的统治阶级就向帝国主义投降,二者之间结成同盟,由二者的对抗变成二者的统一,共同压迫人民大众。这时,人民大众往往采取用国内战争的形式,去反对帝国主义与封建阶级的联盟,而帝国主义则往往采取秘密援助国内的统治阶级压迫国内的革命战争,而不直接行动,显出了内部矛盾的特别尖锐性。例如中国的太平军战争,辛亥革命,1925—1927 年的大革命,1927 年以后的苏维埃战争。在外国,则有俄国的二月革命与十月革命(俄国也带了若干半殖民地性),中美洲南美洲若干带革命性的内战等等的情形,都是如此.还有半殖民地各统治集团之间的内战,也表现了内部矛盾尖锐的情况。在中国,在中南美,也是很多的,也属这一类。\n当着国内战争发展到根本威胁帝国主义及其走狗国内统治者的存在时,帝国主义就往往采取上述方法以外的方法,企图维持其统治;或者分化革命阵线的内部,例如 1927 年中国资产阶级的叛变,或者直接出兵援助国内统治者,例如苏联内战的末期,今日的西班牙战争。这时,帝国主义与国内封建阶级乃至资产阶级完全站在一个极端,人民大众则站在另一极端,这时帝国主义与全殖民地之间这个外部的主要矛盾,封建势力与人民大众之间这个内部的主要矛盾,就几乎合并起来,成为一个主要矛盾,而规定其他矛盾的发展地位,情形是非常明显的。\n然而不管怎样,过程发展之各个阶段中,只有一个主要矛盾起着领导的作用,是完全没有疑义的。\n由此可知,任何过程如果有多数矛盾的话,其中必定有一个是主要的,起着领导的、决定的作用,其他则处于次要与服从的地位。因此,研究任何过程,首先要弄清它是单纯的过程,还是复杂的过程。如果是存在着二个以上矛盾的复杂过程的话,就要用全力找出它的主要矛盾。捉住了这个主要矛盾,一切问题就迎刃而解了。这是马克思研究资本主义社会告诉我们的方法。列宁研究帝国主义时,列宁和斯大林研究苏联过渡期经济时,也同样告诉了我们这种方法。万千的学问家、实行家,不懂得这种方法,结果如堕烟海.找不到中心,也就找不到解决矛盾的方法。\n不能把过程中的矛盾平均看待,应把它们区别为主要的与次要的两类,着重于捉住主要矛盾,既如上述。但是矛盾之中,不论主要的或次要的,矛盾着的两个方面或侧面又是否可平均看待呢?也是不可以的。无论什么矛盾,也无论在什么时候。矛盾的方面或侧面,其发展是不平衡的。有时候似乎势均力敌,然而这是暂时的与相对的情形,基本形态则是不平衡,就是在似乎平衡之时,实际上也没有绝对的平衡。矛盾着的两方面中,必有一方是主要的,他方是次要的。其主要方面,即所谓矛盾起主导作用的方面。\n然而这种情形不是固定的,矛盾的主要与非主要的方面互相转化着。在矛盾发展的一定过程或一定阶段上,主导方面属于甲方,非主导方面属于乙方;及到另一发展阶段或另一发展过程时,就互易其位置,这是依靠双方斗争的力量来决定的。\n例如资本主义社会,在长时期中,资产阶级处于主要地位,起着主导的作用,无产阶级则服从之;但到革命前夜及革命之后,无产阶级就转化到主要地位,起着主导的作用,而资产阶级作了相反的转化。十月革命前后的苏联就是这种情形。\n在资本主义社会中,资本主义已从过去封建社会时的附庸地位,转化成了主要力量,封建势力则由主要化为附庸。但何以解释日本及革命前的俄国呢?他们依然是封建势力占着优势,资本主义尚不起决定一切的作用。这是因为他们的矛盾方面尚未完成其决定的转化的原故。这种转化,因为时代的关系,已经不能走历史的老路,而为另一种情形的转化所代替,即是把地主阶级与资产阶级整个儿地转到被统治的地位,而由无产阶级与农民起来占据主导的方面。目前一切尚未完成资本主义转化的国家(中国也在内)都将走向这条新路,虽然并不跳过民主革命的阶段,可是这种革命是由无产阶级领导执行的。\n帝国主义与整个中国社会的矛盾中,主导的方面属于前者,它在双方斗争中占着优势。然而事情也正在变化,在彼此对立的局面中,中国一方正由被压迫地位向自由独立的地位转化,而帝国主义则将转化到被打倒的地位。\n中国国内封建势力同人民大众对抗的情况也正在变化,人民将依靠革命斗争把自己转化为主要与统治的力量。过去已有过例证,这就是南方革命势力由次要地位转化到主要地位,而北洋军阀则作了相反的转化。苏区中也有此种情形,农民由被统治者转化为统治者,地主则作了相反的转化。\n以中国无产阶级与资产阶级的关系而言,资产阶级因握有生产手段与统治权,至今还居于主导地位,然在反帝反封建的革命领导上说来,由于无产阶级觉悟程度与革命的彻底性,却较之动摇的资产阶级反居于主导地位,这一点将影响到中国革命之前途。无产阶级要在政治上物质上都居于主导地位,只有联合农民与小资产阶级。果能如此,革命之决定的主导的作用就属于无产阶级了。\n在农民与工人的矛盾中,目前工人的主导地位,曾经是由附庸地位转化而来,而农民作了相反的转化。在产业工人与手工工人的矛盾中,在熟练工人与非熟练工人的矛盾中,在城市与乡村的矛盾中,在劳心与劳力的矛盾中,在唯物论与唯心论的矛盾中,都作了同样的转化。\n革命斗争中某些时候,困难条件超过顺利条件,这时,困难是矛盾的主要方面,顺利是其次要方面。然而由于革命党人的努力,利用已有的若干顺利条件作基础,能够逐渐克服困难,开展顺利的新局面,困难的主导地位转化到以顺利为主导。1927 年革命失败后的情形,红军长征中的情形,都是如此。今日的中日战争,中国又处在十分困难的地位,但我们应该也能够努力于它的转变。在相反的情形,顺利也能转化为困难,如果是革命党人犯了错误的话。1925 至 27 年的大革命的胜利转化为失败,中央苏区一、二、三、四次战争粉碎围剿的胜利转化为五次围剿的失败,等等皆是。\n研究学问时,由不知到知的矛盾也是如此。没有研究马克思主义的人,不知或知之不多是矛盾的主要方面,精深博大的马克思主义则是矛盾的另一方面,然而由于学习的努力,可以由不知转化到知,由知之不多转化到知之甚多,我们的许多同志正是走的这条路。在相反的情形也一样,如果中途拒绝前进,或甚至想入非非走了邪路,已有的知可以化为不知,正确可以化为错误。考茨基、普列哈诺夫[8]、陈独秀等人就是走了这条路。我们队伍中的若干自大主义者,如果他不改变,也有这种危险。\n据我看来,一切矛盾方面之主导与非主导的地位,都是这样互相转化的。\n有人觉得有些矛盾并不是这样。例如生产力与生产关系的矛盾,生产力是主导;理论与实践的矛盾,实践是主导;经济基础与上层建筑的矛盾,经济基础是主导。如此等等,它们并不互相转化。须知这是就一般情形而言,站在唯物论的基点上,它们确是不转化的绝对的东西。然而就历史上许多特殊情形而言,它们仍在转化着。生产力、实践、经济基础,一般表现主导的决定的作用,谁不承认这一点,谁就不是唯物论者。然而,生产关系、理论、上层建筑这些方面,有时亦表现其主导的决定的作用,这也是应该承认的。当着不变更生产关系,生产力就不能发展之时,生产关系的变更就起了主导的决定的作用。当着如同列宁所说的“没有革命理论,就没有革命运动”[9]之时,革命理论的提倡就起了主导的决定的作用。当着某一件事情(任何事情都是一样)要做,但还没有方针、方法、计划或政策之时,确定方针、方法、计划或政策,也就是主导的决定的东西。当着政治文化等等上层建筑阻碍着经济基础的发展时,对于政治文化上面的革新就成为主导的决定的东西了。这样来说,是否违反唯物论呢?不违反的。因为我们承认总的历史发展中是物质的东西决定精神的东西;但同时又承认而且应该承认,精神的东西之反作用。这不是违反唯物论,而正是避免机械唯物论,坚持了辩证唯物论。\n在研究矛盾特殊性问题中,如果不研究过程中主要矛盾与非主要矛盾,及矛盾之主要方面与非主要方面这两种情形,也就是说,研究这两种的差别性,那就仍将陷入于抽象的研究,不能具体地懂得矛盾,因而也不能找出解决矛盾的正确方法来。这两种差别性或特殊性,都是矛盾的不平衡性。世界没有绝对地平衡发展的东西,所以成其为世界,我们应该反对平衡论(或均衡论)。矛盾之各种不平衡中,对于主要与非主要的矛盾、主要与非主要的矛盾方面之研究,成为革命政党正确决定其政治上战略战术的基本方法之一(军事上也是一样)。所以不能不充分注意这个问题。\n(六)矛盾的同一性与斗争性 在解决了矛盾的普遍性与特殊性的问题之后,必须进而研究矛盾的同一性与斗争性的问题,矛盾统一律的研究才算全部地解决了。\n同一性、统一性、一致性、互相渗透、互相贯通、互相依赖(或依存)、互相联结或互相合作,这些不同的名词都是一个意思,说的是如下两种情形:第一、过程每一矛盾的两方面,各以它方面为自己存在的前提,共处于一个不可分的统一体中;第二、矛盾的双方依据一定条件,各向着其相反的方面转化。这些就是所谓同一性。\n列宁说:“辩证法是关于矛盾怎样能够是同一性,又怎样是同一性(怎样变成同一性),在怎样的条件之下矛盾变成同一性而互相转化。为什么人的思想不把这些矛盾当作死的、凝固了的东西去看,却当作生动的、附条件的、可变动的、互相转化的东西去看等等问题的学说。”[10]\n列宁这句话,说的是什么意思呢?\n一切过程中矛盾着的各方面,本是互相对立的,是彼此不融洽、不对头、不相好、不和气的,都是些充满怨气的冤家。世上一切过程、现象、事物、思想里面,都包含着选样带冤家性的方面,没有一个例外。单纯的过程只有一对冤家,复杂的过程却有二对以上的冤家。各对冤家之间,又互相成为冤家。这样组成过程、现象、事物,并推使发生运动。\n如此说来,只是极不同一,极不统一,怎样又说是同一或统一呢?世事之怪就怪在这里,妙也就妙在这里。\n原来矛盾的各方面,不能孤立存在。假如没有冤家一方,它自己这方就失掉了存在的条件。试想一切矛盾的事物,或人的心中矛盾的概念,矛盾的任何一方面能够独立存在吗?不能够的。没有生,死就不见;没有死,生也不见。没有上,就无所谓下;没有下,也无所谓上。没有祸,就无所谓福;没有福,就无所谓祸。没有顺利,就无所谓困难;没有困难,也无所谓顺利。没有资产阶级,就没有无产阶级;没有无产阶级,也没有资产阶级。没有殖民地,就不能有帝国主义的压迫;没有帝国主义的压迫,也就不能有殖民地。一切过程、现象、事物之内的对立,对立的双方都是这样,因一定的条件,一面互相对立,一面又互相联结、互相贯通、互相渗透、互相依赖、互相勾搭、又是冤家又聚头,这种性质,叫做同一性。一切矛盾都因一定条件具备着不同一性,所以称为矛盾。然而又具备着同一性,所以互相联结。列宁所谓辩证法研究怎样能够是同一性,就是说的这种情形。这是同一性的第一个意义。\n然而单说了矛盾双方互为存在条件,双方之间有同一性,因而能够共处于一个统一体中,这样就够了吗?那是不够的。事情不是矛盾互相依存就完了,还没有完,重要的事情,还在矛盾之互相转化。事物内部矛盾的方面,因一定的条件而向着相反的方面转化了去,这就是矛盾的同一性之第二个意义。\n为什么这里也有同一性呢?你看,生死关系中生向死转化,不论是有机体中或有机体内细胞的生命中,生总不能长久,而在一定条件下走向它的反对方面,变为死。死呢?也不是一死完事,又必在一定条件下产出新生命来,死变为生。试问如果没有联系、没有瓜葛、没有亲属关系,就是说没有同一性,为什么生死这样相反的东西之间能够互相转化呢?\n被压迫被剥夺的无产阶级向着无产阶级专政,即不再被压迫、不再被剥夺的方面转化,而资产阶级却经过阶级崩溃转到受无产阶级国家统治方面。苏联已经这样做了,全世界也都将要这样做。试问其间没有在一定条件之下的联系与同一性,如何能够发生这样的变化?\n帝国主义压迫殖民地与殖民地受帝国主义压迫的命运都不能长久,帝国主义者将要被殖民地人民与他本国人民的革命势力所推翻而站在人民的统治之下。殖民地和帝国主义内部的人民呢?却有解除压迫走到自由解放(被压迫的反面)之一日,二者之间由于一定条件有共同性、同一性。\n1927 年大革命的正规战争,转化为苏维埃的游击战争;开始时期的苏维埃游击战,又转化为后来的正规战争;今后又正在由苏维埃战争向着抗日的民族战争转化了去。这其间都因在一定条件下而发生同一性,相反的东西中间互相渗透、贯通、勾搭着。\n国民党的带革命性的三民主义,因为它的阶级性及帝国主义的引诱(这就是条件),在 1927 年以后转化成为反动政策。又由于中日矛盾的尖锐化及共产党的统一战线政策(这也是条件),而被迫着转向抗日救亡的方面去。矛盾的东西这一个变到那一个,其间包含了这样的同一性。\n苏区的土地革命,已经是并将要是这样的过程:拥有土地的地主阶级转化成为失掉土地的阶级,而曾经是失掉土地的农民却转化到取得土地的小私有者。有无、得失之间,因一定条件而互相联结,变为同一性。在社会主义条件之下,农民的私有制又将转化到社会主义农业的公有制,苏联已经这样做了,我们将来也会要这样做。私产与公产之间有一条由此达彼的桥梁,哲学上名之曰同一性,或互相渗透。\n资产阶级民主主义与无产阶级民主主义是相反的,然而前者必会转化为后者。相反的东西中间,在一定条件下,就产生了相成的因素。\n提高民族文化,正是准备转化到国际文化的条件。争取民主共和国,正是准备取消民主共和国转向新的国家制度的条件。巩固无产阶级专政,正是准备取消这种专政走到消灭任何国家制度的条件。建立与发展共产党,正是准备消灭共产党及一切党派的条件。建立革命军进行革命战争,正是准备了永远消灭战争的条件。这许多相反的东西,却同时又是相成的东西。\n有些人说:共产党是国际主义者,决不会也不能同时又是爱国主义者。我们却宣称:我们是国际主义者,但同时因为我们是殖民地的党(条件),所以必须为着保卫祖国反对帝国主义的压迫而斗争,因为必须首先脱离帝国主义的压迫,才能参加世界的共产主义社会,这就使二者构成了同一性。爱国主义与国际主义,在一定条件下,相反而又相成。为什么帝国主义国家的共产党坚决反对爱国主义,因为那里的爱国主义只同资产阶级的利益有同一性,它同无产阶级的利益则是根本相反的。\n有些人说:共产党不会也不能,同时又相信三民主义。我们却宣称:我们是坚持共产主义的党纲的,但是当前阶段的共产主义运动不是别的,正是坚决领导反帝反封建的民族民主运动(这就是条件),因此我们不但不反对,而且早已执行了真正的三民主义纲领(反帝的民族主义,工农苏维埃的民权主义,土地革命的民生主义);并且十年来真正的三民主义传统也仅仅在于共产党一方面。国民党除若干分子如宋庆龄、何香凝、李锡九等人而外,抛弃了这个传统。共产党的民主革命政纲不与真正的三民主义冲突,而且就是彻底的急进的三民主义,我们将经过民主阶段转变到共产主义。三民主义与共产主义不是一个东西,二者矛盾着,现在阶段与将来阶段不是一个东西,二者矛盾着,但是相反而又相成,因一定的条件造成了同一性。\n还可以说一些最眼前的事情。战争与和平是矛盾的,但又是联结的。战争转化为和平(例如第一次大战转化为凡尔赛条约,中国的国内战争在西安事变后转化为国内和平),和平转化为战争(目前的世界和平是暂时的,即将转化为第二次大战;日本侵略东四省后几年的和平是暂时的,现已开始转化为大陆战争)。为什么?因为在一定条件下具备了同一性。中国无产阶级同资产阶级订立抗日的统一战线,这是矛盾的一方面;无产阶级须得提高政治的警觉性,密切注视资产阶级的政治动摇及其对于共产党的腐化作用与破坏作用,以保证党与阶级的独立性,这是矛盾的又一方面。各党的统一战线与各党的独立性,这样矛盾着的两方面,组成了当前的政治运动,两方面中去掉一方面,就没有党的政策,就没有统一战线了。我们给人民以自由,这是一方面,我们又给汉奸卖国贼破坏者以压制,这是又一方面。自由与不自由二者因一定条件而联系着,缺一就不行,这是矛盾的统一或同一性。共产党、苏维埃,以及我们主张的抗日政府之组织形式,是民主集中制的。它们是民主的,但又是集中的,二者矛盾而又统一着,因为在一定条件之下有同一性。苏联的无产阶级民主专政,我们过去十年的工农民主专政,它们是民主的,对于革命阶级而设;它们又是专制的(或叫独裁的),对于反革命阶级而设,极端相反的东西之间有同一性。\n军队的休息、训练,同时就是作战胜利的条件。“养兵千日”,正是为了“用在一朝”。分开前进,同时就是到达协同攻击的条件(分进合击)。退却与防御,同时就是为着反攻与进攻(以退为进,以守为攻)。迂回不是别的,就是最有效地消灭敌人的方法(以迂为直)。向东方打一打,为的要在西方得手(声东击西)。分兵以争取群众,为了便于集中以消灭敌人;集中以消灭敌人,为了便于分兵以争取群众。要坚决执行命令,又容许在统一意图下有机动的自由。要严格执行纪律,又要发扬自觉自动性。允许陈述个人志趣,但最后还是要服从团体的决定。前方工作要紧,但后方工作不能抛弃不顾。身体不好需要调养,但紧张时候又要讲牺牲。谁不赞成生活优裕,但经济困难却要准备吃苦。军事操练是重要的,非此不能破敌;但政治工作又重要,非此也就要打败仗。老兵、老干部经验丰富,是值得宝贵的;但如果没有新兵、新干部,战争与工作就不能继续。勇猛要紧,也还要智谋;张飞虽不错,到底不如赵子龙。自己领导的局部工作是重要的;但他人领导的局部及全体工作也重要或更重要,小团体主义是不正确的。自己的意见与团体的或上级机关的意见相矛盾时,可以而且应该陈述自己的意见,可是绝不容许在自己意见未被团体或上级批准时,向任何其他人员自由发表;或甚至煽动下级人员反对上级的意见。这种少数服从多数,下级服从上级的纪律,是共产党与红军的起码的纪律。“良药苦口利于病,忠言逆耳利于行”;“祸兮福所倚,福兮祸所伏”;“爱而知其恶,恶而知其美”。顾前不顾后,叫做莽夫。知一不知二,未为贤者。\n一切矛盾的东西,互相联系着,不但在一定条件之下共处于一个统一体中,而且在一定条件之下互相转化,这就是矛盾的同一性之全部意义。列宁所谓怎样是同一性,在怎样条件之下变成同一性而互相转化,就是这个意思。\n“为什么人的思想不把这些矛盾当作死的、凝固了的东西去看,却当作生动的、附条件的、可变动的、互相转化的东西去看”[11]呢?因为客观事物本来是如此的。客观事物中矛盾的统一或同一性,本来不是死的、凝固的,而是生动的、附条件的、可变动的、暂时的、相对的东西,一切矛盾都依一定条件向它们的反面转化着。\n为什么鸡蛋转化为鸡子,而石头不能转化为鸡子呢?为什么战争与和平有同一性,而战争与石头却没有同一性呢?为什么人能生人却不能生狗呢?没有别的,就是因为矛盾的同一性要在一定条件之下,缺乏一定的必要的条件,就没有任何的同一性。\n为什么俄国的民主革命与社会主义革命直接地联系着,而法国的民主革命没有直接联系社会主义革命,巴黎公社到底失败了呢?为什么外蒙古与中亚细亚的游牧制度又直接与社会主义联系了呢?为什么中国的革命可以避免资本主义前途,可以同社会主义直接联系起来,而避免再走英美法等的历史老路呢?为什么俄国 1905 年的革命同中国 1911 及 1927 年的革命都不与革命的胜利联系,却与失败联系了呢?为什么拿破仑一生的战争大都与胜利联系着,而滑铁炉[12]一战却军败身俘一蹶不振呢?为什么“可以修一条铁路往新疆,却不能修一条铁路往月球”呢?为什么德苏亲交变为敌视,而法苏敌视却又变为暂时的亲交呢?所有这些问题,没有别的,都是当前的具体条件的问题。一定的必要的条件具备,过程就发生矛盾,而且矛盾互相依存,又互相变化,否则一切都不可能。唐吉诃德的奋力同风车作战、孙悟空的十万八千里的筋斗云、阿丽斯的漫游奇境、鲁滨孙的漂流孤岛、阿 q 的精神胜利、希特勒的世界统治、黑格尔的绝对精神、布哈林的均衡论、托洛茨基的不断革命、御用学者的思想统一、陈独秀的机会主义、亲日派的唯武器论以及中国古代传说中的杞人忧天、夸父追日等等,都不能成为矛盾的同一性,不能成为具体的矛盾,反在人间添些麻烦与笑话的资料,也就是这个道理。\n同一性的问题如此。那末,什么是斗争性呢?同一性同斗争性的关系怎样呢?\n列宁说:“矛盾的统一(合致,同一,均势),是有条件的、一时的、暂存的、相对的。互相排除的斗争则是绝对的,发展运动是绝对的”[13]。这话怎讲?\n一切过程都有始有终,一切过程都转化为它们的对立物。一切过程的常住性是相对的,但是一种过程转化为他种过程则是绝对的。矛盾的统一、同一、一致、常住性、联合性,被包含于矛盾的斗争之中,成为矛盾斗争之一因素.这就是列宁这句话的意思。\n这就是说,单只承认矛盾引起运动是不够的,还须明白矛盾在那些状态引起运动。\n矛盾在第一种统一(同一)状态引起运动,那是运动的特殊状态,日常生活中叫做静止、有常不变、不动、死、停顿、僵局、相持、和平、平衡、均势、调和、妥协、联合,等等,这些都是相对的、暂时的、有条件的。还须承认矛盾在第二种统一状态引起运动,即运动之一般状态。这就是统一物的分裂、斗争、生动、无常、活跃、变化、不和平、不平衡、不调和、不妥协、甚至冲突、对抗、或战争,这是绝对的。同一、统一、静、死等等相对的矛盾状态,包含于绝对的斗争的矛盾状态中,因为斗争贯彻于过程的始终,贯彻于一切过程之中,所以成其为绝对的东西。不懂这个道理,就是形而上学、机械论,实质上拒绝了辩证法。\n国际间的和平条约是相对的,国际间的斗争是绝对的。阶级间的统一战线是相对的,阶级间的斗争是绝对的。党内思想上的一致是相对的,党内思想上的斗争是绝对的。自然现象中的平衡、凝聚、吸引、化合等等是相对的,而不平衡、不凝聚、排斥、分解等等是绝对的。当着过程在和平条约、统一战线、团结一致、平衡、凝聚、吸引、化合等等状态之时,矛盾与斗争也仍然存在着,不过没有取激化的形式,并不是没有了矛盾,停止了斗争。由于斗争,不绝地破坏一个相对状态而转到另一个相对状态去,破坏一种过程而转到另一种过程去,这种无所不在的斗争性,就是矛盾的绝对性。\n前面我们说,两个相反的东西中间有同一性,所以二者能够共处于一个统一体中,又能够互相转化,这是说的条件性,即谓在一定条件之下,矛盾的东西能够统一起来,又能够互相转化;无此一定条件,就不能成为矛盾,不能共居,也不能转化。由于一定的条件才构成了矛盾的同一性,所以说同一性是有条件的、相对的。这里我们又说,斗争贯彻于过程的始终,并使一过程向着他过程转化,斗争无所不在,所以说斗争性是无条件的、绝对的。\n有条件的相对的同一性与无条件的绝对的斗争性相结合,就构成了一切事物的矛盾运动。\n为明了这一论点,下面再举出生死关系及劳资关系以为例。\n有机体中旧细胞的死亡,是新细胞产生的前提,也是生活过程的前提。这里生死两矛盾方面互相统一于有机体中,而又互相转变着。生细胞变为死细胞,死细胞变为生细胞(生细胞从死细胞中脱胎出来)。但这种生死的统一,生死的共处于有机体中,是有条件的、暂时的、相对的。而生死的不并存,互相排斥、斗争、否定、转化、则始终如此,它是无条件的、永久的、绝对的。有机体中生的原素总是不绝地战胜死的原素,并且统治着死的原素,表示了斗争的绝对性。生,在一定条件之下转化为死;死,又在一定条件之下转化为生。这种条件使生死有同一性,能够互相转化。由于生死二矛盾物互相斗争,使得生必然转化为死,死必然转化为生。这种必然性,是无条件的、绝对的。由此看来,必须在一定的发展阶段上,必须有一定的温度环境等等条件,生死才能互相转变,互相有同一性。这是一个问题。所谓使生或死都带暂时性、相对性、就是条件不变也不能长生或长死,原因在于两者的斗争、否定、互相排除,这种情形是永久的、绝对的。这又是一个问题。\n无产阶级替资产阶级创造剩余价值,资产阶级剥削无产阶级的劳动力,这是一个决定资本主义生存的统一的过程,劳资双方互为存在的条件。然而这种条件有一定的限度,就是要资本主义发展还在一定限度之内,过此限度,统一的过程发生破裂,出现了社会主义的革命。这种破裂是突然发生的,但又不是突然发生的,是从两阶级存在的一天起就开始准备着,双方的斗争是不断的,由此准备了突变。由此看来,两阶级的共存,由于一定的条件而保存着,这种一定条件下的共存,造成了两阶级的统一或同一性。两阶级又在一定的条件之下互相转化,使得剥削者被剥削、被剥削者变为剥削剥削者,资本主义社会变为社会主义社会,二矛盾物有一定条件下的同一性。这是一个问题。双方总是斗争,不但在统一体中是斗争的,尤其是革命的斗争,这种不可避免的状态是无条件的、绝对的、必然的。这又是一个问题。\n在同一性中存在着斗争性。拿着列宁的话来说,叫做在“相对中存在着绝对”[14]。因此,矛盾的统一本身,也就是矛盾的斗争之一种表现或一种因素.这就是我们对于这个问题的结论。\n根据这种结论,所谓阶级调和论与思想统一论是否还有立足之余地,也就不言而喻了。国际的阶级调和论,形成了各国工人运动中的机会主义派。他们没有别的作用,单单充当了资产阶级的走狗。中国也有所谓阶级调和论,却从资产阶级改良主义的口中唱出来,他们的目的也不为别的,在于专们欺骗无产阶级,使之永为资产阶级的奴隶。所谓思想统一的滥调,则由若干直接间接依靠官场吃饭、“文人学者”们吹打出来,目的无非在抹煞真理,阻碍革命的前进。真的科学的理论不是这些调儿,而是唯物辩证法的矛盾统一律。\n(七)对抗在矛盾中的地位 在矛盾斗争性问题中,包含着对抗是什么的问题。我们回答道:一切过程是自始至终存在着矛盾的,矛盾的双方之间也是自始至终存在着斗争的。对抗是斗争的一种形式,不是一切矛盾都有,而是某些矛盾在其发展过程中到达了采取外部物体力量的形式而互相冲突时,矛盾的斗争便表现为对抗,对抗是矛盾斗争的特殊表现。\n例如剥削阶级同被剥削阶级之间的矛盾,无论在奴隶社会也好,封建社会也好,资本主义社会也好,互相矛盾的两阶级长期并存于一个社会中,它们互相斗争着,但要待两阶级的矛盾发展到一定阶段时,双方才取外部对抗的形式,此时社会破裂,革命的战争就出现了。\n炸弹的爆炸,小鸡的出卵,动物的脱胎,都是矛盾物共居于一个统一体中,待至一定时机,才取冲突、破局、决裂的形式。\n各国之间的和平共居,乃至社会主义国家同资本主义国家也是一样,矛盾与斗争无日不存在,但战争却要在一定发展阶段上才能出现。\n苏联的新经济政策容许资本主义成分的相当发展,列宁认为那时有在无产阶级专政下利用国家资本主义的可能。就是说,利用某些资产阶级成分发展生产力,同时使之受苏维埃法律的支配,并随时限制和排斥他们,这时社会主义与资本主义两矛盾体共处于社会主义社会之内,互相斗争,又互相联系。待到消灭富农及消灭资本主义残余的任务提出之后,两种成分的并存就成为不可能,而生死斗争的外部对抗形式就发生了。\n国共两党第一次统一战线的情况也是如此。\n然而许多过程、现象、事物中的矛盾是不发展成为对抗的。\n例如共产党内正确思想与错误思想的矛盾,文化上先进与落后的矛盾,经济上城市与乡村的矛盾,生产力与生产关系的矛盾,生产与消费的矛盾,交换价值与使用价值的矛盾,各种技术分工的矛盾,阶级关系中工农的矛盾,自然界中的生与死、遗传与变异、寒与暑、昼与夜等类的矛盾,者没有对抗形态的存在。\n布哈林把矛盾和对抗同一看待,因此,认为在完成了的社会主义社会中,对抗没有了,矛盾也没有了。列宁回答道:“这是极端不正确的,对抗和矛盾断然不同。在社会主义下,对抗消灭了,矛盾存在着。”[15]布哈林是否认事物发展由于内部矛盾的推动之均衡论者,认为社会主义下没有矛盾,社会也可发展。\n托洛茨基从另一极端出发,也把矛盾和对抗同一看待。因此,认为在社会主义下,工农之间不但存在着矛盾,而且将发展到对抗,如同劳资间的矛盾一样,只有用革命的方法才能解决。然而苏联却用农业社会化的方法解决了,并且是在一国社会主义的情况下解决了,无须如托派所谓要待至国际革命之时。\n布哈林把矛盾降低到消灭,托派把矛盾提升到对抗,右倾与左倾的两极端,都不了解矛盾的问题。\n解决一般矛盾的方法与解决对抗的方法是根本不同的,这是矛盾特殊性与解决矛盾的方法之特殊性应该有具体认识的问题。凡对抗都包含矛盾性,但凡矛盾不一定都取对抗的形态,总的区别就在这里。\n矛盾统一律是宇宙的根本法则,也是思想方法的根本法则。列宁称之为辩证法的核心。它是与形而上学的发展观相反的,它是与形式论理学的绝对的同一律相反的。矛盾存在于一切客观与主观事物的过程中,矛盾贯彻于一切过程的始终,这是矛盾的普遍性、绝对性。矛盾及矛盾的侧面各有其特点,人心之不同如其面,矛盾之不同如其形,这是矛盾之特殊性、相对性。矛盾着的东西依一定的条件有同一性,因此能够共居于一个统一体中,又能够互相转化到相反方面、这又是矛盾的特殊性、相对性。然而矛盾的斗争则是不绝的,不管在其共居时或其转化时,都有斗争的存在,尤其是表现在矛盾的转化时,这又是矛盾的普遍性、绝对性。研究矛盾的特殊性相对性时,要注意矛盾及矛盾方面之主要与非主要的区别;研究矛盾的斗争性时要注意矛盾的一般斗争形式与特殊斗争形式——即矛盾发展为对抗这种区别。这就是我们对于矛盾统一律的总结论。\n(论矛盾统一律完。1937,8,7)\n注释:\n[1] 原文如此。\n[2] 出自《自然辩证法》中《[辩证法作为科学]•辩证法》。参见《马克思恩格斯选集》中文第 3 版,第 3 卷,第 901 页:“辩证法的规律是从自然界的历史和人类社会的历史中抽象出来的。辩证法的规律无非是历史发展的这两个方面和思维本身的最一般的规律。它们实质上可归结为下面三个规律:量转化为质和质转化为量的规律;对立的相互渗透的规律;否定的否定的规律。”\n[3] 出自《黑格尔(哲学史讲演录)一书摘要》。参见《列宁全集》中文第 2 版,第 55 卷,第 213 页:“就本来的意义讲,辩证法是研究对象的本质自身中的矛盾。”\n[4] 出自《反杜林论》第 1 编第 12 节《辩证法。量和质》。参见《马克思恩格斯选集》中文第 3 版,第 3 卷,第 498 页:“运动本身就是矛盾。”\n[5] 出自《谈谈辩证法问题》。参见《列宁全集》中文第 2 版,第 55 卷,第 306 页:“承认(发现)自然界的(也包括精神的和社会的一切现象和过程具有矛盾着的、相互排斥的、对立的倾向。”\n[6] 即波里斯•伊萨科维奇•哥列夫(戈尔德曼)〔борис исаакович горев, 1874—1937〕,原为孟什维克,苏联历史学家和哲学家。\n[7] 出自《谈谈辩证法问题》。参见《列宁全集》中文第 2 版,第 55 卷,第 307 页:“这应该是一般辩证法的……叙述(以及研究)方法。”\n[8] 即普列汉诺夫。——录入者注\n[9] 出自《俄国社会民主党人的任务》以及《怎么办?》第 1 章第 4 节。分别参见《列宁全集》中文第 2 版,第 2 卷,第 443 页;第 6 卷,第 23 页:“没有革命的理论,就不会有革命的运动。”\n[10] 出自列宁《黑格尔〈逻辑学〉一书摘要》。参见《列宁全集》中文第 2 版,第 55 卷,第 90 页:“辩证法是一种学说,它研究对立面怎样才能够同一,是怎样(怎样成为)同一的——在什么条件下它们是相互转化而同一的,——为什么人的头脑不应该把这些对立面看作僵死的、凝固的东西,而应该看作活生生的、有条件的、活动的、彼此转化的东西。”\n[11] 参见前注。\n[12] 原文如此,即滑铁卢。\n[13] 出自列宁《谈谈辩证法问题》。参见《列宁全集》中文第 2 版,第 55 卷,第 306 页:“对立面的统一(一致、同一、均势)是有条件的、暂时的、易逝的、相对的。相互排斥的对立面的斗争是绝对的,正如发展、运动是绝对的一样。”\n[14] 出自《谈谈辩证法问题》。参见《列宁全集》中文第 2 版,第 55 卷,第 307 页:“相对中有绝对”。\n[15] 出自《在尼•布哈林〈过渡时期经济学〉一书上作的批注和评论》。参见《列宁全集》中文第 2 版,第 60 卷,第 281—282 页:“极不确切。对抗和矛盾完全不是一回事。在社会主义下,对抗将会消失,矛盾仍将存在。”。\n","date":"2024-11-18","permalink":"https://aituyaa.com/%E8%BE%A9%E8%AF%81%E6%B3%95%E5%94%AF%E7%89%A9%E8%AE%BA/","summary":"\u003cp\u003e——(讲授提纲) 内含《实践论》与《矛盾论》的原始版本。\u003c/p\u003e\n\u003cp\u003e按:《辩证法唯物论(讲授提纲)》写于1937年,曾在《抗战大学》第6期至第8期(1938年4月至6月)连载。据以录入的中国人民解放军政治学院训练部翻印本,年代不详。第二章第十一节和第三章中的部分章节分别为《实践论》与《矛盾论》的最初版本,与后来的毛选版本有较多出入。 文中所有注释均为录入者所加,文中不再说明。\u003c/p\u003e","title":"辩证法唯物论"},]
[{"content":"\u0026gt; 该文章内容由豆包 ai 驱动 ^_^\n文章体裁主要分为文学体裁和实用体裁两大类,如下:\n文学体裁: - 诗歌:古体诗、近体诗、词、曲、现代诗歌(-自由诗、-散文诗) - 散文:叙事散文、抒情散文、哲理散文、写景散文 - 小说:长篇小说、中篇小说、短篇小说、微型小说(小小说)、按体裁分类(武侠、科幻、言情、历史) - 戏剧:话剧、歌剧、舞剧、戏曲、按题材分类(历史、现代、童话)、按结构分类(多幕、独幕) 实用体裁: - 记叙文:写人、记事、写景、状物、新闻 - 说明文:事物、事理、文艺性(科学小品) - 议论文:立论文、驳论文、政论文、评论文章 文学体裁 诗歌 特点:高度凝练的语言,具有节奏感和韵律美。通过丰富的想象、抒情等表达作者强烈的情感。它在形式上较为灵活,有古体诗和近体诗之分(在汉语诗歌中)。例如,古体诗可以不受格律的严格限制,像《古诗十九首》中的《行行重行行》,“行行重行行,与君生别离。相去万余里,各在天一涯”,句式较为自由,在押韵上也相对灵活。近体诗则对格律要求严格,如律诗要求颔联(第三、四句)和颈联(第五、六句)必须对仗工整,像杜甫的《登高》“无边落木萧萧下,不尽长江滚滚来” 就是很典型的对仗句,同时它在平仄和押韵上也有细致的规定。\n分类:按内容可分为抒情诗、叙事诗;按形式可分为格律诗、自由诗、散文诗等。抒情诗如徐志摩的《再别康桥》,整首诗充满了对康桥的留恋之情;叙事诗如古代的《木兰诗》,讲述了花木兰代父从军的故事。\n古体诗:不受格律的严格限制,篇幅长短不限,句式有四言、五言、七言等多种形式,押韵较自由。例如《诗经》中的《关雎》“关关雎鸠,在河之洲。窈窕淑女,君子好逑”,句式以四言为主。\n近体诗:包括律诗(五言律诗、七言律诗)和绝句(五言绝句、七言绝句)。律诗要求颔联和颈联对仗工整,平仄、押韵等有严格规则。如杜甫《春望》“国破山河在,城春草木深。感时花溅泪,恨别鸟惊心。烽火连三月,家书抵万金。白头搔更短,浑欲不胜簪” 是五言律诗。绝句每首四句,也有平仄、押韵要求,如王昌龄《从军行・其四》“青海长云暗雪山,孤城遥望玉门关。黄沙百战穿金甲,不破楼兰终不还。” 是七言绝句。\n词:有特定的词牌名,每个词牌有固定的格律、字数、句数、平仄、押韵等要求。如苏轼《水调歌头・明月几时有》,词牌是 “水调歌头”,在句式上长短相间,“明月几时有?把酒问青天。不知天上宫阙,今夕是何年”。\n曲:包括散曲和杂剧。散曲又分小令和套数。小令是独立的只曲,如马致远《天净沙・秋思》“枯藤老树昏鸦,小桥流水人家,古道西风瘦马。夕阳西下,断肠人在天涯”。套数是由同一宫调的若干曲子联缀而成的组曲。杂剧是一种综合性的戏曲形式,有完整的故事情节和人物角色,像关汉卿的《窦娥冤》。\n现代诗歌:\n自由诗:形式自由,不受格律约束,语言自然流畅。如艾青《我爱这土地》“假如我是一只鸟,我也应该用嘶哑的喉咙歌唱”。 散文诗:具有散文和诗的特点,形式上像散文,有散文的篇幅和结构,但语言富有诗意,有强烈的情感和意境。如鲁迅《野草》中的篇章。 散文 特点:形散神聚。“形散” 是指散文的取材广泛,不受时间和空间的限制,例如秦牧的《土地》,内容涉及古今中外关于土地的种种事情;同时它的表现手法多样,可以记叙、描写、抒情、议论等多种方式并用。“神聚” 是指散文有一个明确的主题,所有的内容都是围绕主题展开的。它的语言优美,富有文采,如朱自清的《荷塘月色》,文中对月下荷塘和塘上月色的描写语言细腻、生动。\n分类:根据内容和性质可分为叙事散文、抒情散文、哲理散文等。叙事散文如鲁迅的《藤野先生》,通过回忆作者在日本留学时与藤野先生的交往,展现藤野先生的高尚品质;抒情散文像冰心的《寄小读者》,字里行间洋溢着对儿童的关爱和思念等情感;哲理散文如周国平的一些散文,在讲述生活现象等内容的过程中蕴含深刻的哲理。\n叙事散文:以叙述事件为主,通过对一个或多个事件的描述来表达主题。如归有光《项脊轩志》,围绕项脊轩的变迁,叙述了家庭琐事和亲人之间的情感。\n抒情散文:侧重于抒发作者的情感,情感浓郁。像郁达夫《故都的秋》,通过描绘北平秋天的景色,抒发了对故都之秋的眷恋和赞美之情。\n哲理散文:以阐述哲理为主要目的,在叙述或议论中蕴含深刻的道理。如培根《论读书》,论述了读书的目的、方法等诸多哲理。\n写景散文:主要描绘自然风景或人文景观,注重对景物的描写和氛围的营造。如朱自清《绿》,细致地描写了梅雨潭的绿。\n小说 特点:以刻画人物形象为中心,通过完整的故事情节和具体的环境描写来反映社会生活。它的人物形象鲜明,如《红楼梦》中的贾宝玉、林黛玉等众多人物,性格各异,栩栩如生。故事情节丰富多样,包括开端、发展、高潮、结局等环节,有的还有序幕和尾声。环境描写分为自然环境和社会环境,自然环境描写能够烘托气氛,如《林教头风雪山神庙》中对风雪的描写,为林冲的反抗起到了推动作用;社会环境描写有助于揭示作品的时代背景,像巴尔扎克的《人间喜剧》系列小说,对当时法国社会的种种现象进行了细致描绘。\n:: 人物形象(外形举止、性格)、故事情节(序幕、开端、发展、高潮、结局、尾声)、环境描写(自然环境、社会环境) ---\u0026gt; 反映社会生活\n分类:按篇幅长短可分为长篇小说(篇幅较长,内容丰富,如《战争与和平》)、中篇小说(篇幅适中,如《阿 q 正传》)、短篇小说(篇幅较短,情节相对简单集中,如欧・亨利的《麦琪的礼物》)和微型小说(篇幅极短,情节简单但意味深长,如星新一的一些微型小说)。按内容可分为武侠小说、科幻小说、言情小 说、历史小说等众多类别。\n长篇小说:篇幅长,容量大,情节复杂,人物众多,能够展现广阔的社会生活画卷。如列夫・托尔斯泰《战争与和平》,描绘了 19 世纪初俄国社会的战争与和平生活,人物形象众多,故事线索纵横交错。\n中篇小说:篇幅介于长篇和短篇之间,情节相对完整,人物关系较为集中。如路遥《人生》,围绕高加林的人生经历展开,展现了他在城乡交叉地带的矛盾与选择。\n短篇小说:篇幅较短,情节简洁集中,通常刻画一两个主要人物,能迅速反映生活中的一个片段或侧面。如莫泊桑《项链》,通过玛蒂尔德借项链、丢项链、赔项链的故事,展现了小资产阶级的虚荣和生活的无奈。\n微型小说(小小说):篇幅极短,往往只有几百字到一千多字,以一个巧妙的情节或细节来表达主题。如欧・亨利《最后一片叶子》,通过一片叶子的故事展现了人性的美好。\n按题材分类的小说:\n武侠小说:以侠客和江湖为背景,描写武林门派、武功秘籍、江湖恩怨等。如金庸《射雕英雄传》。 科幻小说:以科学幻想为内容,涉及未来科技、外星生物、时空穿越等元素。如刘慈欣《三体》。 言情小说:以爱情为主题,描写男女之间的情感故事,包括古代言情和现代言情。如琼瑶《还珠格格》(小说版)。 历史小说:以历史事件或人物为蓝本进行创作,在尊重历史的基础上进行虚构和想象。如二月河《康熙大帝》。 :: 不难看出,小说的核心便在于“人物”和“情节”的构建,结合各种环境,不同篇幅不同复杂度。\n戏剧 特点:主要通过演员在舞台上的表演来展现故事。它有综合性的艺术特点,融合了文学、表演、音乐、舞蹈、美术等多种艺术形式。剧本是戏剧的文学基础,它包括台词(对白、独白、旁白等)和舞台说明。台词要具有个性化、动作性和口语化的特点,像莎士比亚的戏剧《哈姆雷特》中哈姆雷特的经典独白 “生存还是毁灭,这是一个值得考虑的问题”,深刻地展现了人物的内心矛盾。舞台说明则对人物的动作、表情、舞台布景等进行说明,帮助演员更好地理解角色和场景。\n分类:按表现形式可分为话剧(以对话为主,如老舍的《茶馆》)、歌剧(以歌唱为主,如莫扎特的《费加罗的婚礼》)、舞剧(以舞蹈为主,如中国的《丝路花雨》)等;按题材可分为历史剧、现代剧、童话剧等;按容量大小可分为多幕剧和独幕剧。\n话剧:以对话和表演为主要手段,舞台布景等辅助手段相对简单。如曹禺《雷雨》,通过人物之间的对白和激烈的冲突展现了一个封建家庭的悲剧。\n歌剧:以歌唱为主,结合音乐、舞蹈、舞台美术等多种艺术形式。如威尔第《茶花女》,剧中有大量经典的唱段。\n舞剧:主要通过舞蹈动作来表达剧情和人物情感,音乐是重要的组成部分。如中国的《红色娘子军》,以舞蹈展现了娘子军的战斗历程。\n戏曲:中国传统的戏剧形式,包括京剧、越剧、黄梅戏等多种剧种。每个剧种有自己独特的唱腔、表演方式和剧目。如京剧《贵妃醉酒》,有独特的京剧唱腔和表演程式。\n按题材分类的戏剧:\n历史剧:以历史事件或人物为题材,通过戏剧形式再现历史。如郭沫若《屈原》。 现代剧:反映当代社会生活和人们的思想观念。如老舍《龙须沟》。 童话剧:以童话故事为蓝本,适合儿童观看,富有幻想色彩。如安徒生童话改编的《白雪公主》。 按结构分类的戏剧: 多幕剧:由多个幕组成,每一幕有相对完整的情节,幕与幕之间情节连贯。如莎士比亚《哈姆雷特》。 独幕剧:全剧在一幕内完成,情节集中紧凑。如丁西林《一只马蜂》。 实用体裁 记叙文 特点:以记叙为主要表达方式,记录人物的经历、事物的发展变化等。它要求有时间、地点、人物、事件的起因、经过和结果等六要素。例如在《背影》中,时间是作者回家奔丧后回北京上学的时候,地点有火车站等,人物是作者和他的父亲,事件的起因是作者要回北京上学,父亲去送他,经过是父亲在车站为他买橘子等一系列行为,结果是作者看到父亲的背影后深受感动。它的语言一般比较平实、简洁,注重事实的陈述。\n应用场景:常见于日记、回忆录、新闻报道(新闻中的消息部分主要是记叙文性质)等。新闻报道中的记叙文部分要遵循真实性原则,如在报道一场体育比赛时,会按照比赛的进程如实记录比分变化、运动员的表现等情况。\n写人记叙文:以人物为中心,通过描写人物的外貌、语言、行为、心理等方面来展现人物形象和性格。如《藤野先生》,通过对藤野先生外貌、教学行为等方面的描写,展现了藤野先生严谨、正直等品质。\n记事记叙文:以事件为主要内容,详细叙述事件的过程。如《走一步,再走一步》,详细描述了作者在悬崖上遇险及脱险的过程。\n写景记叙文:主要描写自然景观或人文景观,注重对景物的观察和描写。如《济南的冬天》,对济南冬天的山、水等景物进行了细致的描绘。\n状物记叙文:侧重于对物体的描写,通过对物体的形状、颜色、质地等方面的描述来展现物体的特点。如《核舟记》,对核舟的大小、雕刻内容等进行了详细描述。\n新闻记叙文:用于新闻报道,要求真实、及时、准确。它有一定的结构,包括标题、导语、主体、背景和结语。如新闻消息报道体育赛事,会按照事件的重要性和时间顺序进行记叙。\n说明文 特点:以说明为主要目的,向读者介绍事物的特征、性质、功能、成因、关系等。它的语言要求准确、简明、通俗易懂。例如在介绍一种新型电子产品时,会说明它的外观尺寸、重量、性能参数(如处理器型号、内存大小等)、操作方法等内容。它常常运用多种说明方法,如列数字(这款手机屏幕尺寸为 6.7 英寸)、打比方(地球像一个蓝色的宝石)、作比较(这款汽车的油耗比同类型汽车低 10%)、举例子(例如,竹子可以用于制作多种生活用品)等。\n应用场景:常见于产品说明书、科普文章、旅游指南等。产品说明书会详细说明产品的使用方法和注意事项,科普文章可以帮助读者了解科学知识,旅游指南会介绍旅游景点的位置、特色等信息。\n事物说明文:以具体事物为说明对象,介绍事物的形状、构造、性质、种类、功能等方面。如《中国石拱桥》,详细说明了中国石拱桥的形式优美、结构坚固等特点以及它的历史等内容。\n事理说明文:以抽象的事理为说明对象,解释事物的本质、内在联系、发展变化规律等。如《大自然的语言》,说明了物候现象的成因和意义。\n文艺性说明文(科学小品):采用文艺的形式来介绍科学知识,语言生动形象,富有情趣。如法布尔《昆虫记》,以生动的语言介绍昆虫的生活习性等知识。\n议论文 特点:通过摆事实、讲道理来表达作者的观点和主张。它有论点、论据和论证三要素。论点是作者的观点,如 “我们应该保护环境”;论据是用来证明论点的材料,可以是事实论据(具体的事例,如某个地区因为破坏环境导致生态恶化的案例),也可以是理论论据(名人名言、科学定理等,如 “天行健,君子以自强不息” 可以作为激励人们奋发向上的理论论据);论证是用论据证明论点的过程,常见的论证方法有举例论证、道理论证、对比论证、比喻论证等。例如在对比论证中,可以通过对比保护环境和破坏环境的不同后果来论证保护环境的重要性。\n应用场景:常见于学术论文、评论文章(如影评、书评等)、演讲稿等。学术论文需要严谨地论证作者的研究观点,评论文章会对作品或事件发表作者的看法,演讲稿可以通过论证观点来达到说服听众的目的。\n立论文:提出自己的观点,并通过论据和论证来证明观点的正确性。如毛泽东《论持久战》,针对抗日战争的形势,提出了抗日战争是持久战的观点,并进行了详细论证。\n驳论文:通过反驳对方的观点来树立自己的观点。如鲁迅《中国人失掉自信力了吗》,反驳了当时一些人认为 “中国人失掉自信力” 的观点,提出 “我们有并不失掉自信力的中国人在” 的观点。\n政论文:针对政治问题或现象发表观点和主张,具有较强的政治性和理论性。如贾谊《过秦论》,通过对秦朝兴衰的分析,提出了治国理政的观点。\n评论文章:包括影评、书评、时评等,对文艺作品、书籍、时事等进行评价。如一篇电影评论会对电影的剧情、演员表演、拍摄手法等方面进行评价。\n","date":"2024-11-14","permalink":"https://aituyaa.com/%E6%96%87%E5%AD%A6%E4%BD%93%E8%A3%81/","summary":"\u003cp\u003e\u003ccode\u003e\u0026gt; 该文章内容由豆包 AI 驱动 ^_^\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e文章体裁主要分为文学体裁和实用体裁两大类,如下:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e文学体裁:\n- 诗歌:古体诗、近体诗、词、曲、现代诗歌(-自由诗、-散文诗)\n- 散文:叙事散文、抒情散文、哲理散文、写景散文\n- 小说:长篇小说、中篇小说、短篇小说、微型小说(小小说)、按体裁分类(武侠、科幻、言情、历史)\n- 戏剧:话剧、歌剧、舞剧、戏曲、按题材分类(历史、现代、童话)、按结构分类(多幕、独幕)\n\n实用体裁:\n- 记叙文:写人、记事、写景、状物、新闻\n- 说明文:事物、事理、文艺性(科学小品)\n- 议论文:立论文、驳论文、政论文、评论文章\n\u003c/code\u003e\u003c/pre\u003e","title":"文学体裁"},]
[{"content":"一些关于 emacs 配置和使用的……\nloveminimal/emacs.d: all in emacs.\n一些问题 emacs 中 markdown-mode 中代码块字体设置 emacs 编辑 .md 文件时,代码块默认字体很‘丑’,但是设置的默认字体在这里又不生效……\n解决方案如下:\nm-x customize-face 回车,输入 markdown-code-face 回车,点击 show all attributes ,勾选 font family 后输入你想设置的字体,即可。\n![[assets/pasted image 20230821145158.png]]\nemacs 终端真彩显示 https://stackoverflow.com/questions/63950/how-to-make-emacs-terminal-colors-the-same-as-emacs-gui-colors?r=searchresults\n设置 term 在 . bashrc 文件中,如下:\n1 export term=xterm-256color 如此,便设置好了。\n加入我们使用在终端中使用 emacs ,执行 m-x list-colors-display ,便可以看到 256 色已经全部激活,如此,终端下使用 emacs 和 emacs gui 的颜色便相差无几了。\nemacs 宏操作 https://www.jianshu.com/p/6ad946eb8ebc\nkey/command description c-x ( 开启宏记录 c-x ) 关闭宏记录 c-x e 执行刚录制的宏 c-u n c-x e 执行 n 次刚录制的宏 m-x name-last-kbd-marco 给刚记录的宏命名 m-x insert-kbd-marco 把刚命名的宏记录写入到文件中 可以设置一个专门的文件(如 ~/. emacs. d/macro. el )来记录宏,然后在 init. el 中加载改文件( (load-file \u0026quot;~/. emacs. d/macro. el\u0026quot;) ),如此便可以实现持久化。\n如这个例子:用宏定义了下翻 15 行和上翻 15 行的快捷键。\n1 2 3 4 5 ;; macro. el (fset \u0026#39;next-lines \u0026#34;\\c-u15\\c-n\u0026#34;) (fset \u0026#39;previous-lines \u0026#34;\\c-u15\\c-p\u0026#34;) 1 2 3 4 5 6 7 8 ;; init. el ;; ... ;; 加载 macro. el (load-file \u0026#34;~/. emacs. d/macro. el\u0026#34;) ;; 绑定快捷键 (global-set-key (kbd \u0026#34;c-x n ret\u0026#34;) \u0026#39;next-lines) (global-set-key (kbd \u0026#34;c-x p ret\u0026#34;) \u0026#39;previous-lines) ","date":"2024-11-09","permalink":"https://aituyaa.com/emacs-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e一些关于 Emacs 配置和使用的……\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/loveminimal/emacs.d\"\u003eloveminimal/emacs.d: All in emacs.\u003c/a\u003e\u003c/p\u003e","title":"emacs 那些事儿"},]
[{"content":"一些关于 vscode 配置和使用的……\n一些问题 如何禁止 vscode 自动升级 运行菜单 file 中 preferences 子菜单中选择 settings 项,搜索 update mode,将其设置为 none 。\nvscode 如何禁止空格自动替换 tab 打开设置 settings,在选择框中输入 editor.detect ,如下\n![[assets/pasted image 20240528173417.png]]\n将 detect indentation 和 insert spaces 前的框取消勾选。\n对于某些语言,可能某些插件或者使用者做了某些设置,比如 yaml ,只需要在设置中输入 @lang:yaml tab ,检查上述相关选项,进行调整即可。\n如何修改 vscode 窗体的默认字体 记得以前在设置中就可以修改,不知道是记错了,还是后来改了……\n在 vscode 的安装路径,本机如下:\nc:\\program files\\microsoft vs code insiders\\resources\\app\\out\\vs\\workbench 其他主机类似。在这个目录下有一个文件 workbench.desktop.main.css ,可以通过修改它改变窗体样式。\n这里,我们修改了它的字体,在该文件末尾添加:\n1 2 3 .split-view-view { font-family: \u0026#39;lxgw wenkai mono\u0026#39;; } 如果,你想修改其他样式自然也是没有问题的,但是不建议修改太多,不方便备份,后续升级版本可能会覆盖掉。\nvscode 折叠策略 vscode 默认的折叠策略(wrapping strategy)为 simple ,如下:\n![[assets/pasted image 20230525161637.png]]\n偶尔更换了下 editor 的字体,以同一编辑器字体和网页字体,出现了自动换行不能正确截断的问题。如图所示,总有一部分会显示不完全。\n![[assets/pasted image 20230525161652.png]]\n经过测试,只需要把折叠策略,修改为 advanced 即可。\n:: 不得不说,vscode 是真的好用哦,处理文本真的很方便。\n","date":"2024-11-09","permalink":"https://aituyaa.com/vscode-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e一些关于 VSCode 配置和使用的……\u003c/p\u003e","title":"vscode 那些事儿"},]
[{"content":"typescript 闪念随手记……\n:: 不要试图把一门语言的边边角角都一次性都搞清楚,没啥用,你又不准备成为“参考书”。一则平时使用的部分并没有那么多,二则脱离了实际应用的抽象文字并不容易记忆。\n为什么需要 typescript javascript 的每个值执行不同的操作时会有不同的行为。这听起来有点抽象,所以让我们举个例子,假设我们有一个名为 message 的变量,试想我们可以做哪些操作:\n1 2 3 4 5 // accessing the property \u0026#39;tolowercase\u0026#39; // on \u0026#39;message\u0026#39; and then calling it message.tolowercase(); // calling \u0026#39;message\u0026#39; message(); 第一行代码是获取属性 tolowercase ,然后调用它。第二行代码则是直接调用 message 。\n但其实我们连 message 的值都不知道呢,自然也不知道这段代码的执行结果。每一个操作行为都先取决于我们有什么样的值。\nmessage 是可调用的吗? message 有一个名为 tolowercase 的属性吗? 如果有,tolowercase 是可以被调用的吗? 如果这些值都可以被调用,它们会返回什么? 当我们写 javascript 的时候,这些问题的答案我们需要谨记在心,同时还要期望处理好所有的细节。\n让我们假设 message 是这样定义的:\n1 const message = \u0026#34;hello world!\u0026#34;; 你完全可以猜到这段代码的结果,如果我们尝试运行 message.tolowercase() ,我们可以得到这段字符的小写形式。\n那第二段代码呢?如果你对 javascript 比较熟悉,你肯定知道会报如下错误:\ntypeerror: message is not a function 如果我们能避免这样的报错就好了。\n当我们运行代码的时候,javascript 会在运行时先算出值的类型(type),然后再决定干什么。所谓值的类型,也包括了这个值有什么行为和能力。 当然 typeerror 也会暗示性的告诉我们一点,比如在这个例子里,它告诉我们字符串 hello world 不能作为函数被调用。\n对于一些值,比如基本值 string 和 number,我们可以使用 typeof 运算符确认他们的类型。但是对于其他的比如函数,就没有对应的方法可以确认他们的类型了,举个例子,思考这个函数:\n1 2 3 function fn(x) { return x.flip(); } 我们通过阅读代码可以知道,函数只有被传入一个拥有可调用的 flip 属性的对象,才会正常执行。但是 javascript 在代码执行时,并不会把这个信息体现出来。在 javascript 中,唯一可以知道 fn 在被传入特殊的值时会发生什么,就是调用它,然后看会发生什么。这种行为让你很难在代码运行前就预测代码执行结果,这也意味着当你写代码的时候,你会更难知道你的代码会发生什么。\n从这个角度来看,类型就是描述什么样的值可以被传递给 fn,什么样的值则会导致崩溃。javascript 仅仅提供了动态类型(dynamic typing),这需要你先运行代码然后再看会发生什么。\n替代方案就是使用静态类型系统(static type system),在代码运行之前就预测需要什么样的代码。\n在代码运行之前就找到错误,这就是静态类型检查器比如 typescript 做的事情。静态类型系统(static types systems)描述了值应有的结构和行为。一个像 typescript 的类型检查器会利用这个信息,并且在可能会出错的时候告诉我们。\ntypescript 不仅在我们犯错的时候,可以找出错误,还可以防止我们犯错。\n类型检查器因为有类型信息,可以检查比如说是否正确获取了一个变量的属性。也正是因为有这个信息,它也可以在你输入的时候,列出你可能想要使用的属性。\n这意味着 typescript 对你编写代码也很有帮助,核心的类型检查器不仅可以提供错误信息,还可以提供代码补全功能。\ntypescript 的功能很强大,除了在你输入的时候提供补全和错误信息。还可以支持“快速修复”功能,即自动的修复错误,重构成组织清晰的代码。同时也支持导航功能,比如跳转到变量定义的地方,或者找到一个给定的变量所有的引用。\n所有这些功能都建立在类型检查器上,并且跨平台支持。\n类型注解 记住,我们并不需要总是写类型注解,大部分时候,typescript 可以自动推断出类型。这是一个特性,如果类型系统可以正确的推断出类型,最好就不要手动添加类型注解了。\n类型注解并不是 javascript 的一部分。所以并没有任何浏览器或者运行环境可以直接运行 typescript 代码。这就是为什么 typescript 需要一个编译器,它需要将 typescript 代码转换为 javascript 代码,然后你才可以运行它。\n谨记:类型注解并不会更改程序运行时的行为。\n有哪些类型 类型可以出现在很多地方,不仅仅是在类型注解 (type annotations)中。我们不仅要学习类型本身,也要学习在什么地方使用这些类型产生新的结构。\n我们先复习下最基本和常见的类型,这些是构建更复杂类型的基础。\n原始类型, javascript 有三个非常常用的原始类型 :string,number 和 boolean,每一个类型在 typescript 中都有对应的类型。它们的名字跟你在 javascript 中使用 typeof 操作符得到的结果是一样的。\n7 种原始数据类型:number bigint string boolean undefined null symbol 数组类型 - string[] 或 array\u0026lt;string\u0026gt; 。\nany, typescript 有一个特殊的类型,any,当你不希望一个值导致类型检查错误的时候,就可以设置为 any 。\n当你使用 const、var 或 let 声明一个变量时,你可以选择性的添加一个类型注解,显式指定变量的类型。不过大部分时候,这不是必须的,因为 typescript 会自动推断类型。\n函数类型,是 javascript 传递数据的主要方法。typescript 允许你指定函数的输入值和输出值的类型(参数类型注解和返回值类型注解)。\n对象类型,定义一个对象类型,我们只需要简单的列出它的属性和对应的类型。多个属性之间可以使用 , 或者 ; 分开属性,最后一个属性的分隔符加不加都行。\n除了原始类型,最常见的类型就是对象类型了。\n其中, 对象类型可以指定一些甚至所有的属性为可选的,你只需要在属性名后添加一个 ? 。\n联合类型,typescript 类型系统允许你使用一系列的操作符,基于已经存在的类型构建新的类型。\n注意,typescript 会要求 你做的事情,必须对每个联合的成员都是有效的。解决方案是用代码收窄联合类型。\n后面,我们还会提到[[#字面量类型]] 。\n类型别名和接口 我们已经学会在类型注解里直接使用对象类型和联合类型,这很方便,但有的时候,一个类型会被使用多次,此时我们更希望通过一个单独的名字来引用它。\n:: 本质上都是为了方便复用。\n你可以使用类型别名给任意类型一个名字。如:\n1 2 3 4 5 6 7 8 // 为对象类型设置别名 type point = { x: number; y: number; }; // 为联合类型设置别名 type id = number | string; 注意,别名是唯一的别名,你不能使用类型别名创建同一个类型的不同版本。\n接口声明(interface declaration)是命名对象类型的另一种方式,如下:\n1 2 3 4 interface point { x: number; y: number; } 类型别名和接口有什么不同呢?\n大部分时候,你可以任意选择使用。两者最关键的差别在于类型别名本身无法添加新的属性,而接口是可以扩展的。\n但其实我们也可以通过组合类型别名来实现类似扩展的效果。\n类型断言 有的时候,你知道一个值的类型,但 typescript 不知道。typescript 仅仅允许类型断言转换为一个更加具体或者更不具体的类型。 就像类型注解一样,类型断言也会被编译器移除,并且不会影响任何运行时的行为。\n谨记:因为类型断言会在编译的时候被移除,所以运行时并不会有类型断言的检查,即使类型断言是错误的,也不会有异常或者 null 产生。\n前面说过,typescript 仅仅允许类型断言转换为一个更加具体或者更不具体的类型。有的时候,这条规则会显得非常保守,阻止了你原本有效的类型转换。如果发生了这种事情,你可以使用双重断言,先断言为 any (或者是 unknown),然后再断言为期望的类型。\n1 const a = (expr as any) as t; 非空断言操作符 !, typescript 提供了一个特殊的语法,可以在不做任何检查的情况下,从类型中移除 null 和 undefined,这就是在任意表达式后面写上 ! ,这是一个有效的类型断言,表示它的值不可能是 null 或者 undefined。\n1 2 3 4 function livedangerously(x?: number | null) { // no error console.log(x!.tofixed()); } 就像其他的类型断言,这也不会更改任何运行时的行为。重要的事情说一遍,只有当你明确的知道这个值不可能是 null 或者 undefined 时才使用 ! 。\n字面量类型 除了常见的类型 string 和 number ,我们也可以将类型声明为更具体的数字或者字符串。\n字面量类型本身并没有什么太大用,如果结合联合类型,就显得有用多了。举个例子,当函数只能传入一些固定的字符串时:\n1 2 3 4 5 6 function printtext(s: string, alignment: \u0026#34;left\u0026#34; | \u0026#34;right\u0026#34; | \u0026#34;center\u0026#34;) { // ... } printtext(\u0026#34;hello, world\u0026#34;, \u0026#34;left\u0026#34;); printtext(\u0026#34;g\u0026#39;day, mate\u0026#34;, \u0026#34;centre\u0026#34;); // argument of type \u0026#39;\u0026#34;centre\u0026#34;\u0026#39; is not assignable to parameter of type \u0026#39;\u0026#34;left\u0026#34; | \u0026#34;right\u0026#34; | \u0026#34;center\u0026#34;\u0026#39;. 当你初始化变量为一个对象的时候,typescript 会假设这个对象的属性的值未来会被修改,举个例子,如果你写下这样的代码:\n1 2 3 4 const obj = { counter: 0 }; if (somecondition) { obj.counter = 1; } typescript 并不会认为 obj.counter 之前是 0, 现在被赋值为 1 是一个错误。换句话说,obj.counter 必须是 number 类型,但不要求一定是 0,因为类型可以决定读写行为。\n这也同样应用于字符串:\n1 2 3 4 5 6 declare function handlerequest(url: string, method: \u0026#34;get\u0026#34; | \u0026#34;post\u0026#34;): void; const req = { url: \u0026#34;https://example.com\u0026#34;, method: \u0026#34;get\u0026#34; }; handlerequest(req.url, req.method); // argument of type \u0026#39;string\u0026#39; is not assignable to parameter of type \u0026#39;\u0026#34;get\u0026#34; | \u0026#34;post\u0026#34;\u0026#39;. 在上面这个例子里,req.method 被推断为 string ,而不是 \u0026quot;get\u0026quot;,因为在创建 req 和 调用 handlerequest 函数之间,可能还有其他的代码,或许会将 req.method 赋值一个新字符串比如 \u0026quot;guess\u0026quot; 。所以 typescript 就报错了。\n:: 这里写的多少是有点绕了,其实,就是 req 的类型没有确定导致的。在 req 中,method 属性只会被 typescript 推断为 string ,因为并没有根据知道它应该用在哪里,自然无从推断。只需要在定义和使用的时候,给它一个明确的类型就可以了。\n有两种方式可以解决:\n1.添加一个类型断言改变推断结果:\n// change 1:\rconst req = { url: \u0026#34;https://example.com\u0026#34;, method: \u0026#34;get\u0026#34; as \u0026#34;get\u0026#34; };\r// change 2\rhandlerequest(req.url, req.method as \u0026#34;get\u0026#34;); 修改 1 表示“我有意让 req.method 的类型为字面量类型 \u0026quot;get\u0026quot;,这会阻止未来可能赋值为 \u0026quot;guess\u0026quot; 等字段”。修改 2 表示“我知道 req.method 的值是 \u0026quot;get\u0026quot;”.\n2.你也可以使用 as const 把整个对象转为一个类型字面量:\nconst req = { url: \u0026#34;https://example.com\u0026#34;, method: \u0026#34;get\u0026#34; } as const;\rhandlerequest(req.url, req.method); as const 效果跟 const 类似,但是对类型系统而言,它可以确保所有的属性都被赋予一个字面量类型,而不是一个更通用的类型比如 string 或者 number 。\n类型收窄 在上一个章节,我们已经提到这方面的知识。也不准备多讲,基本上是符合直觉的。\n函数 让我们来学习一下如何书写描述函数的类型(types)。\n最简单描述一个函数的方式是使用 函数类型表达式(function type expression)。 它的写法有点类似于箭头函数:\n1 2 3 4 5 6 7 8 9 function greeter(fn: (a: string) =\u0026gt; void) { fn(\u0026#34;hello, world\u0026#34;); } function printtoconsole(s: string) { console.log(s); } greeter(printtoconsole); 语法 (a: string) =\u0026gt; void 表示一个函数有一个名为 a ,类型是字符串的参数,这个函数并没有返回任何值。\n如果一个函数参数的类型并没有明确给出,它会被隐式设置为 any。\n当然了,我们也可以使用类型别名(type alias)定义一个函数类型:\n1 2 3 4 type greetfunction = (a: string) =\u0026gt; void; function greeter(fn: greetfunction) { // ... } 函数类型表达式并不能支持声明属性,如果我们想描述一个带有属性的函数,我们可以在一个对象类型中写一个 调用签名(call signature)。\n1 2 3 4 5 6 7 8 type describablefunction = { description: string; (somearg: number): boolean; } function dosomething(fn: describablefunction) { console.log(fn.description + \u0026#34; returned \u0026#34; + fn(6)); } 注意这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是 : 而不是 =\u0026gt;。\njavascript 函数也可以使用 new 操作符调用,当被调用的时候,typescript 会认为这是一个构造函数(constructors),因为他们会产生一个新对象。你可以写一个 构造签名(construct signatures),方法是在调用签名前面加一个 new 关键词:\n1 2 3 4 5 6 type someconstructor = { new (s: string): someobject; }; function fn(ctor: someconstructor) { return new ctor(\u0026#34;hello\u0026#34;); } 一些对象,比如 date 对象,可以直接调用,也可以使用 new 操作符调用,而你可以将调用签名和构造签名合并在一起:\n1 2 3 4 interface callorconstruct { new (s: string): date; (n?: number): number; } :: 实际应用场景中,函数表达式是最常用的,调用签名可能会用,构造签名基本不用卅~\n泛型函数,我们经常需要写这种函数,即函数的输出类型依赖函数的输入类型,或者两个输入的类型以某种形式相互关联。\n让我们考虑这样一个函数,它返回数组的第一个元素:\n1 2 3 function firstelement(arr: any[]) { return arr[0]; } 注意此时函数返回值的类型是 any,如果能返回第一个元素的具体类型就更好了。\n在 typescript 中,泛型就是被用来描述两个值之间的对应关系。我们需要在函数签名里声明一个类型参数 (type parameter):\n1 2 3 function firstelement\u0026lt;type\u0026gt;(arr: type[]): type | undefined { return arr[0]; } 通过给函数添加一个类型参数 type,并且在两个地方使用它,我们就在函数的输入(即数组)和函数的输出(即返回值)之间创建了一个关联。现在当我们调用它,一个更具体的类型就会被判断出来。\n1 2 3 4 5 6 // s is of type \u0026#39;string\u0026#39; const s = firstelement([\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;]); // n is of type \u0026#39;number\u0026#39; const n = firstelement([1, 2, 3]); // u is of type undefined const u = firstelement([]); 尽管写泛型函数很有意思,但也容易翻车。如果你使用了太多的类型参数,或者使用了一些并不需要的约束,都可能会导致不正确的类型推断。如何写好泛型函数是一个值得进一步学习的问题,好在实际应用场景中,并不怎么需要我们书写泛型函数。\n可选参数 javascript 中的函数经常会被传入非固定数量的参数,我们可以使用 ? 表示这个参数是可选的:\n1 2 3 4 5 function f(x?: number) { // ... } f(); // ok f(10); // ok 需要注意的是,尽管这个参数被声明为 number 类型,x 实际上的类型为 number | undefiend,这是因为在 javascript 中未指定的函数参数就会被赋值 undefined。\n回调中的可选参数 在你学习过可选参数和函数类型表达式后,你很容易在包含了回调函数的函数中,犯下面这种错误:\n1 2 3 4 5 function myforeach(arr: any[], callback: (arg: any, index?: number) =\u0026gt; void) { for (let i = 0; i \u0026lt; arr.length; i++) { callback(arr[i], i); } } 将 index? 作为一个可选参数,本意是希望下面这些调用是合法的:\n1 2 myforeach([1, 2, 3], (a) =\u0026gt; console.log(a)); myforeach([1, 2, 3], (a, i) =\u0026gt; console.log(a, i)); 但 typescript 并不会这样认为,typescript 认为想表达的是回调函数可能只会被传入一个参数!\n:: typescript:“我不要你以为,按我以为的来!” 😂\n换句话说,myforeach 函数也可能是这样的:\n1 2 3 4 5 6 function myforeach(arr: any[], callback: (arg: any, index?: number) =\u0026gt; void) { for (let i = 0; i \u0026lt; arr.length; i++) { // i don\u0026#39;t feel like providing the index today callback(arr[i]); } } typescript 会按照这个意思理解并报错,尽管实际上这个错误并无可能。\n那如何修改呢?不设置为可选参数其实就可以:\n1 2 3 4 5 6 7 8 9 function myforeach(arr: any[], callback: (arg: any, index: number) =\u0026gt; void) { for (let i = 0; i \u0026lt; arr.length; i++) { callback(arr[i], i); } } myforeach([1, 2, 3], (a, i) =\u0026gt; { console.log(a); }); 在 javascript 中,如果你调用一个函数的时候,传入了比需要更多的参数,额外的参数就会被忽略。typescript 也是同样的做法。\n当你写一个回调函数的类型时,不要写一个可选参数, 除非你真的打算调用函数的时候不传入实参。\n剩余参数 不准备多讲~\n对象类型 在 javascript 中,最基本的将数据成组和分发的方式就是通过对象。在 typescript 中,我们通过对象类型(object types)来描述对象。\n对象类型可以是匿名的:\n1 2 3 function greet(person: { name: string; age: number }) { return \u0026#34;hello \u0026#34; + person.name; } 也可以使用接口进行定义:\n1 2 3 4 5 6 7 8 interface person { name: string; age: number; } function greet(person: person) { return \u0026#34;hello \u0026#34; + person.name; } 或者通过类型别名:\n1 2 3 4 5 6 7 8 type person = { name: string; age: number; }; function greet(person: person) { return \u0026#34;hello \u0026#34; + person.name; } :: 实际应用场景中,对象类型用的最多,单独拎出来重视一下。\n对象类型中的每个属性可以说明它的类型、属性是否可选、属性是否只读等信息。\n可选属性, 我们可以在属性名后面加一个 ? 标记表示这个属性是可选的。\n只读属性,在 typescript 中,属性可以被标记为 readonly,这不会改变任何运行时的行为,但在类型检查的时候,一个标记为 readonly 的属性是不能被写入的。\n属性继承,对接口使用 extends 关键字允许我们有效的从其他声明过的类型中拷贝成员,并且随意添加新成员。\n交叉类型,typescript 也提供了名为交叉类型(intersection types)的方法,用于合并已经存在的对象类型。交叉类型的定义需要用到 \u0026amp; 操作符。\n:: 一直感觉这里叫 “交叉” 很奇怪,因为明明应该是两个类型组合的,结果,它指的是两个类型组合时,其内部属性相同时,取其类型的交集。如此,便合理了。\n泛型对象类型,见泛型。\n类和模块 类就不多说了,基本不用系列。模块详见另一篇博文 「 [[模块化编程]] 」。\n","date":"2024-10-25","permalink":"https://aituyaa.com/typescript-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003eTypeScript 闪念随手记……\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: 不要试图把一门语言的边边角角都一次性都搞清楚,没啥用,你又不准备成为“参考书”。一则平时使用的部分并没有那么多,二则脱离了实际应用的抽象文字并不容易记忆。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"typescript 那些事儿"},]
[{"content":"规范一下 git 的提交方式 ~\n开始 commit message 和 change log 编写指南 - 阮一峰的网络日志\n\u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;subject\u0026gt; // !header // 空一行 \u0026lt;body\u0026gt; // ?body // 空一行 \u0026lt;footer\u0026gt; // ?footer 其中,header 是必须的,body 和 footer 可省略。\nheader\n其中, type 用于说明 commit 的类别,只允许使用下面 7 个标识。\n- feat :新功能 feature - fix/to :修补 bug - docs :文档 - style :格式(不影响代码运行的改动) - refactor :重构(即不是新增功能,也不是修改 bug 的代码改动) - test :增加测试 - chore :构建过程或辅助工具的变动 - perf :优化相关 - revert :回滚 - merge :代码合并 - sync :同步主线或分支的 bug scope 可选,用于说明 commit 的范围,比如数据层、控制层、视图层等等,视项目不同而不同。\nsubject 是 commit 消息的简短描述,其有以下几个约定,如下:\n以动词开头,使用第一人称现在时,比如 change,而不是 changed 或 changes ; 第一个字母小写; 结尾不加句号。 body 部分是对本次 commit 的详细描述,可以分成多行。\nfooter 部分只用于两种情况:\n不兼容变动; 关闭 issue 。 关于 revert 的情况\n另,如果当前 commit 用于撤销以前的 commit ,则必须以 revert: 开头,后面跟着被撤销 commit 的 header 。\n1 2 3 revert: feat(pencil): add \u0026#39;graphitewidth\u0026#39; option this reverts commit 667ecc1654a317a13331b17617d973392f415f02. body 部分的格式是固定的,必须写成 this reverts commit \u0026lt;hash\u0026gt; ,其中 hash 是被撤销的 commit 的 sha 标识符。\n工具 commitizen 是一个撰写合格 commit message 的工具。\nnpm install -g commitizen # 现在你可以使用 git cz 、git-cz 或 cz 来取代 git commit # 初始化您的项目以使用 cz-conventional-changelog 适配器 # npm commitizen init cz-conventional-changelog --save-dev --save-exact # yarn commitizen init cz-conventional-changelog --yarn --dev --exact # pnpm commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact conventional-changelog 是生成 change log 的工具。\n结语 可以使用上述给到的工具,也可以手动按照其规范格式书写。\n参考 commit message 和 change log 编写指南 - 阮一峰的网络日志 git commit 之道:规范化 commit message 写作指南_git commit feat-csdn博客 git 提交消息约定 \u0026mdash; git commit message conventions ","date":"2024-08-07","permalink":"https://aituyaa.com/git-%E6%8F%90%E4%BA%A4%E6%B6%88%E6%81%AF%E7%BA%A6%E5%AE%9A/","summary":"\u003cp\u003e规范一下 Git 的提交方式 ~\u003c/p\u003e","title":"git 提交消息约定"},]
[{"content":"流程 搭建基础 vite 项目 搭建一个 vite 项目,详细参考开始 | vite 官方中文文档\nnpm create vite@latest 如下:\nps e:\\workspace\\proj__finance-taxation\u0026gt; npm create vite \u0026gt; npx \u0026gt; create-vite √ project name: ... finance-taxation √ select a framework: » react √ select a variant: » typescript + swc scaffolding project in e:\\workspace\\proj__finance-taxation\\finance-taxation... done. now run: cd finance-taxation npm install npm run dev 运行后,你就得到了一个由 vite 管理的使用 typescript 的 react 项目。\nnpm run dev 引入 ant design ant design - 一套企业级 ui 设计语言和 react 组件库\nnpm install antd --save 引入 scss npm i sass --save-dev 引入路由 此处,我们不考虑服务端渲染(ssr),采用传统的客户端渲染,故选用 react router6 来管理路由。\nnpm install react-router-dom 部署 项目开发完成,现在我们要把它部署到生产环境上面。假如,我们的项目要存放到服务器的如下路径 - /usr/html/tax-app/ ,访问时的域名类似 https://domain.com/tax-app/xxx 。\n也就是说在生产环境下,站点的 base_url 是 /tax-app/,而不是默认的根路径 / ,后者也是我们开发环境下的默认路径。\n我们以 vite 为例,来展示一下如何正确的配置,使其在开发和生产环境下都得以正确渲染。\n首先,在默认的 defineconfig 的配置函数中根据 mode 来区分不同环境下路径,并配置如下。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // --- vite.config.ts --- import {defineconfig} from \u0026#39;vite\u0026#39; import react from \u0026#39;@vitejs/plugin-react-swc\u0026#39; // https://vitejs.dev/config/ export default defineconfig(({ mode }) =\u0026gt; { return { base: mode === \u0026#39;production\u0026#39; ? \u0026#39;/tax-app/\u0026#39; : \u0026#39;/\u0026#39;, server: { proxy: { \u0026#39;/api\u0026#39;: { target: \u0026#39;http://192.168.51.211:10100/\u0026#39;, changeorigin: true, rewrite: (path) =\u0026gt; path.replace(/^\\/api/, \u0026#39;\u0026#39;) }, }, }, plugins: [react()], } }) 其次,我们还需要同步修改一下路由的生成配置。\n1 2 3 4 5 6 7 // ... // --- 路由配置 src/router/index.tsx --- const router = createbrowserrouter(routes, { basename: import.meta.env.base_url }); export default router; 如此,即可。\n","date":"2024-08-06","permalink":"https://aituyaa.com/react-%E5%9F%BA%E7%A1%80%E8%84%9A%E6%89%8B%E6%9E%B6%E7%A4%BA%E4%BE%8B/","summary":"","title":"react 基础脚手架示例"},]
[{"content":"身体“湿”的很哦,疲惫的紧,真是年龄上来了,问题陆陆续续地都浮现了出来…… 学习一些相关方面的知识吧 ~\n湿气症状 炎炎夏日,天气闷热,气候潮湿,稍不注意,就会给体内带来过多的湿气,而体内湿气如不及时去除,就会给身体带来一系列不舒服的症状。\n![[assets/pasted image 20240605172206.png|300]]\n自我检查\n如果你在阴湿天气时出现下列变化,说明你身体内的湿气太重了:肠胃不佳、精神不振、四肢沉重、皮肤起疹子、雀斑加重。\n“湿”从何来 四季养生,夏季重在防暑湿。“湿”是自然界六气之一,湿与脾相应,故湿多伤脾。湿性黏浊,会影响人体的气血运行及脏腑的运化功能。\n中医把“湿”分为两种:内湿和外湿。湿邪难缠,有不易祛除的特性。\n内湿是人体的气机运行过缓,形成了壅滞,有些人体形较胖、爱出汗,走几步路就会气喘吁吁,容易犯困,打不起精神,经常出现腹泻等症状。本来热的时候人体出出汗很有好处,可现代人夏天多用空调,不出汗,会导致皮肤毛孔开闭功能失常,“湿”无法排出体外,会引起体内气血循环不畅,损伤脾胃的运化水湿功能。\n外湿是由于长期处于潮湿环境,外界的湿邪侵入人体而产生的。常年生活在潮湿环境中的人,应该警惕因外湿诱发的各种疾病,如肩周炎、膝关节积液、骨关节疼痛等。\n另外,爱吃冷饮也会导致水湿代谢受到阻滞;食物过于油腻,过食肥厚食物,摄入的能量远远多于身体所需,也易聚湿生痰,引发糖尿病、高血脂、高血压等问题。精神因素也是“内湿”的源头。生活工作繁忙,精神压力大,出现脾虚征象,也会导致湿气。\n如何袪湿 第1招:越懒越要运动\n体内湿气重的人大多数都是饮食油腻、缺乏运动的人。这些人常常会感觉身体沉重、四肢无力而不愿活动,但越是不爱运动,体内淤积的湿气就越多,久而久之,必然就会导致湿气攻入脾脏,引发一系列的病症。\n运动可以缓解压力,促进身体器官运作,加速湿气排出体外。跑步、健走、游泳、瑜珈、太极等运动,有助活化气血循环,增加水分代谢。\n第2招:饮食清淡适量\n肠胃系统关系到营养及水分代谢,最好的方式就是适量、均衡饮食。酒、牛奶、肥甘厚味等油腻食物不易消化,容易造成肠胃闷胀、发炎。甜食油炸品会让身体产生过氧化物,加重发炎反应。\n生冷食物、冰品或凉性蔬果,会让肠胃消化吸收功能停滞,不宜经常食用,如生菜、沙拉、西瓜、大白菜、苦瓜等,最好在烹调时加入葱、姜,降低蔬菜的寒凉性质。\n第3招:避环境的湿气\n我们人体内产生湿气,除了自身代谢的问题以外,有很大一部分和环境有关。经常在潮湿、阴冷的环境中,就容易导致湿气入侵体内。\n日常生活中应留心下列事项:1.不要直接睡地板。地板湿气重,容易入侵体内,造成四肢酸痛。2.潮湿下雨天减少外出。3.不要穿潮湿未干的衣服,不要盖潮湿的被子,洗完澡后要充分擦干身体,吹干头发。4.房间内的湿气如果很重,建议多开窗透气。如果外界湿气也很重,还可以打开风扇、空调,借助这些电器保持空气的对流。\n第4招: 薏米煮粥,淮山煲汤\n祛除体内的湿气,其实有很多我们常吃的食物可以起作用。比如薏米,性味甘淡微寒,有利水消肿、健脾去湿、舒筋除痹、清热排脓等功效;红豆性平,味甘酸,有健脾止泻、利水消肿的功效,将薏米和红豆加水煮熟后食用,可以利尿、除湿,甚至还可以起到美容的效用。\n此外,还可以选择红豆、茯苓、淮山、党参等,放到煲汤材料中,或者煲成粥、煮水喝,都可以利尿、除湿,对水肿的人尤其有效。值得注意的是,这两种方法都有利尿的作用,不适宜尿多的人食用。\n第5招: 妙用葱、姜、蒜\n葱、姜、蒜不仅是家里常用的调味料,还具有不可替代的药用价值。例如我们可以尝试在家里煮一碗热辣辣的姜汤,用姜汤的绝妙效用将体内的湿气逼散出来,待到全身发过汗以后,病症就会有所缓解,这个办法同样适用于淋雨后预防感冒。\n参考 夏季祛湿有妙招(中医养生)\u0026ndash;健康·生活\u0026ndash;人民网 湿气有不同 祛湿分体质 5个信号提示体内有湿气 手把手教你祛除\u0026ndash;健康·生活\u0026ndash;人民网 你的“湿气”重吗?中医教你夏季祛湿妙招 教你四招 给身体“除湿”\u0026ndash;健康·生活\u0026ndash;人民网 教你5招逼出体内湿气\u0026ndash;健康·生活\u0026ndash;人民网 ","date":"2024-06-05","permalink":"https://aituyaa.com/%E6%8B%A5%E6%8A%B1%E6%B8%85%E7%88%BD%E8%BF%9C%E7%A6%BB%E6%B9%BF%E6%B0%94/","summary":"\u003cp\u003e身体“湿”的很哦,疲惫的紧,真是年龄上来了,问题陆陆续续地都浮现了出来…… 学习一些相关方面的知识吧 ~\u003c/p\u003e","title":"拥抱清爽,远离湿气"},]
[{"content":"之前一直使用五笔 86,现在,我们来跟 虎码 打个招呼吧 ~\n![[assets/tc.jpg]]\n认识虎码 虎码是 26 键的四码、乱序(为了更好的性能和手感)方案,双编码,小码音托。\n乱序。 什么是性能?选重+码长。什么是手感?!\n什么是双编码? 一个字根对应 2 个编码 - 大码(字根所在键位) + 小码(音托)。啥是 音托 嘞?大部分是声母,少量是韵母,极少需硬记。如下:\n读音为 yi wu yu 的字根,小码设定为 i u v ; 声母为 z 开头的字根,取韵母为小码(“走 pz” 例外,仍取声母 z)。 虎码的 取码规则 非常简单:拆字后,取 “前三根+末根”,若不足“四根”,则补小码。如:\n字根字:aa 二根字:abb 三根字:abcc 四根字:abcd 大于四根:abcz 部分 字根字会 让位 给其他字的 二简 ,这些字会出现在 次选 位置。如下:\n己鱼骨乌甲毛禾龟乡兔虎毋耒甫鬲臼氏隹戊黾卯曰聿芈缶丿丶彳亻卅卌〇囗罒冫亠礻衤冂殳勹疒饣讠扌彐廴爿攴壴頁車\n💡 这些字,也可重复末码后,用空格首选输出(推荐)。\n词的规则呢? 虎码的词的规则和五笔一样。\n键名 q都 w得 e也 r了 t我 y到 u的 i为 o是 p行\ra来 s说 d中 f一 g就 h道 j人 k能 l而\rz可 x和 c不 v要 b如 n在 m大 “都得也了,我到的为是行?”\n“来说中医,就道人能而?”\n“可和不要如,在大!”\n字根 “虎码是乱序方案,大码没有规律没有分区。先记住字根,再去打字,没记住之前,最好一个字都别打。” \u0026ndash; 官方建议\n对~ 先从字根开刀。大概看了一遍,确实没有什么规律,那就找一些似是而非的规律来记吧。\n\u0026gt; 没有任何道理的字根歌\n都q\t田目耳 老牛食 井齐臣 得w\t五乍冫宀 象兔页彳龟鱼 也e\t彡木干 欠𠂉 见穴 了r\t竹囗巾 又非七皮瓦 殳之 我t\t三斤阝衣 丿𤴔 弗用革雨龙 到y\t小车至 爪瓜 酉四曲卜 的u\t白手工 廴⻌隹 为i\t糸乡幺 立丶册 丩予艮舟 是o\t尤自矢几日 川贝 行p\t刀足长 黑米鼠儿走 臼弋戈 来a\t丰耒未 六门疒 乙丁鬼片 舌皿缶 说s\t言止力屮 各尚甲 中d\t由口黾里 无民 一f\t且示辰㡀 亡骨 就g\t土士高羊 歹句韦 道h\t夫八谷子 夂心攵壴 人j\t亼入斗仑豕 能k\t亥毋水母 厶寸九㠯 而l\t艹卅卌夕 廿方生千牙齿 可z\t金羽虎 亠身甘习麻豆 和x\t厂广鹿 乌鸟束 禾𠂊音 不c\t业火古 尸虫飞 支及 要v\t巳已己 匕月西山 乃禸毛文气㐄 如b\t氏𠂎卯卵 女乂彐聿 云面弓 在n\t十王马匚勹 戊辛鬲 黄甫 大m\t二豸两石犬 冂巴光户 q 都 ![[assets/pasted image 20240602203610.png|150]]\n田目耳\n食老牛\n井齐臣 都\nw 得 ![[assets/pasted image 20240602203532.png|150]]\n五乍冫宀\n象兔页彳龟鱼 得\nxiang tu ye chi gui yu\ne 也 ![[assets/pasted image 20240602203649.png|150]]\n木彡干\n欠𠂉\nqian ti\n见穴 也\nr 了 ![[assets/pasted image 20240602203734.png|150]]\n竹囗巾\n又非七皮瓦\n殳之 了\nshu zhi\nt 我 ![[assets/pasted image 20240602203759.png|150]]\n三斤阝衣 丿𤴔\nsan jin er i pie shu\n弗用革龙雨 我\ny 到 ![[assets/pasted image 20240602203822.png|150]]\n小车至\n爪瓜\n酉四曲卜 到\nyou qu bu dao\nu 的 ![[assets/pasted image 20240602203839.png|150]]\n白手工\n廴⻌⾫ 的\nyan chi zhui de\ni 为 ![[assets/pasted image 20240602203850.png|150]]\n糸乡幺\n立丶册\n丩予艮舟 为\njiu yu gen zhou\no 是 ![[assets/pasted image 20240602203901.png|150]]\n尤自矢几日\n川贝 是\np 行 ![[assets/pasted image 20240602203913.png|150]]\n刀足长\n黑米鼠儿走\n臼弋戈 行\njiu i ge xing\na 来 ![[assets/pasted image 20240602203925.png|150]]\n丰耒未\n六门疒\n丁乙片\n舌缶皿 来\ns 说 ![[assets/pasted image 20240602203943.png|150]]\n言止力屮\nyan zhi li che\n各尚甲 说\nd 中 ![[assets/pasted image 20240602203958.png|150]]\n由口里黾\n无民 中\nf 一 ![[assets/pasted image 20240602204009.png|150]]\n且示辰㡀\n亡骨 一\ng 就 ![[assets/pasted image 20240602204021.png|150]]\n土士高羊\n歹韦句 就\nh 道 ![[assets/pasted image 20240602204030.png|150]]\n夫八谷子\n夂心攵壴** 道\nyi xin pu zhu dao\nj 人 ![[assets/pasted image 20240602204041.png|150]]\n亼入斗仑豕 人\nji ru dou lun si\nk 能 ![[assets/pasted image 20240602204054.png|150]]\n亥毋水母\n厶寸九㠯 能\nshi cun jiu i neng\nl 而 ![[assets/pasted image 20240602204108.png|150]]\n艹卅卌夕\ncao sa xi xi\n廿(龷)方生千牙齿 而\nnian\nz 可 ![[assets/pasted image 20240602204130.png|150]]\n金羽虎\n亠身甘习麻豆 可\ntou\nx 和 ![[assets/pasted image 20240602204139.png|150]]\n厂广鹿\n乌鸟束\n禾𠂊音 和\nhe de yin he\nc 不 ![[assets/pasted image 20240602204149.png|150]]\n业火古\n尸虫飞\n支及 不\nv 要 ![[assets/pasted image 20240602204206.png|150]]\n巳已己\nsi i j\n匕月西山\n乃禸毛文气㐄 要\nnai rou mao wen k qi yao\nb 如 ![[assets/pasted image 20240602204233.png|150]]\n氏𠂎卯卵\nshi ?\n女乂彐聿\nnv i ji lv\n云面弓 如\nn 在 ![[assets/pasted image 20240602204252.png|150]]\n十马王匚勹(k?bao)\n戊辛鬲\n黄甫 在\nm 大 ![[assets/pasted image 20240602204303.png|150]]\n二豸两石犬\ner i\n冂巴光户 大\n练习 \u0026gt; ok,虎码的 241 个字根完成了\n![[assets/pasted image 20240604092352.png]]\n必拆字练习还在进行中。现在就是用虎码在编辑,好慢。\n","date":"2024-06-02","permalink":"https://aituyaa.com/%E5%97%A8%E8%99%8E%E7%A0%81/","summary":"\u003cp\u003e之前一直使用五笔 86,现在,我们来跟 \u003ca href=\"https://tiger-code.com/\"\u003e虎码\u003c/a\u003e 打个招呼吧 ~\u003c/p\u003e\n\u003cp\u003e![[assets/tc.jpg]]\u003c/p\u003e","title":"嗨,虎码"},]
[{"content":"这个是迟来的 loveminimal/rime-jk 的 readme ,照例单开一篇,方便记录和更新~\n更新日志 - 2025-02-07 15:55 [⭐] 发布版本 v6.6.0.lts\r- 2025-01-19 00:40 并击版本 v6.4.0.bj+\r- 2024-12-19 00:18 [⭐] 终极大一统版本 v6.1.0 更新\r- 2024-12-08 18:02 终极版本 v6.0.0 发布\r- 2024-12-06 13:04 版本 v5.0.0 发布 - 基于万象词库重构\r- 2024-12-01 21:10 版本 v4.0.0 发布 - 知心大鹤\r- 2024-11-24 14:14 版本 v3.0.0 发布 - 2024-11-20 11:44 添加了基于朙月拼音的全拼方案 - 知心拼音\r- 2024-11-19 18:01 添加了小鹤双拼方案 - 知心小鹤\r- 2024-07-05 16:53 更新简化五笔字根拆分码表(移除拼音显示)\r- 2024-07-03 14:50 增加了键盘式方案 - 用来快捷输入预设字符和数字\r- 2024-06-21 14:32 [⭐推荐] 版本 v2.0.0 发布\r- 2024-06-20 22:14 引入五笔、虎码反查时的拆分字根顺序 - 支持显示、隐藏\r- 2024-06-06 11:54 优化配置,把方案共享相关提取到 default.custom.yaml\r- 2024-06-05 12:52 修改了 ico 显示,更新键位绑定 - 快速切换输入方案\r- 2024-06-04 17:05 添加了支持虎码的方案 - 知心老虎\r- 2024-05-29 10:00 版本 v1.0.0 发布,已经比较好用了~ 🚩 〔 v6.6.0.lts 〕\n好嘛,整个假期都“废”在输入法上了…… 好吧,上内容:\n方案:五笔、拼音〔 支持全拼、小鹤双拼 〕、并击〔 支持知心码指法、空明码指法 〕; 脚本:优化用户词库脚本〔 累积式 〕、注释美化脚本、一级简码置顶脚本; 优化了中英互译词典显示及触发〔 长词性分行 〕; \u0026gt; 英文字典注释美化\n![[assets/pasted image 20250207160709.png|400]]\n制作了拼音和五笔的精炼词库,配合用户词库转化脚本会越来越懂你; …… 就这样了,后续主要集中在词库积累、和既有功能的优化。 👻\n🚩 〔 v6.4.0.bj+ 〕\n这中间的版本,因为体验并击的原因,恢复并优化了小鹤双拼方案。概略内容如下:\n方案:五笔 、小鹤双拼〔 五笔前二辅助码 〕、并击〔 兼容小鹤双拼 〕; 脚本:同步用户词库、本地命令别名; 制作了中英互译词典,修改 lua 脚本优化其显示效果; 自定义 ascii 方案,实现表情、符号等快捷输入; …… 🚩 〔 v6.1.0+ 〕\n📌 v6.1.0+ 及其之后版本为个人使用版本,推荐用户使用 v6.1.0 。\n该版本为重大精简版本:\n方案:五笔、字符; 主题色调:绛紫、天蓝、主蓝; 词库:五笔 (极爽 6.0) 。 🚩 〔 ⭐推荐大一统版本 v6.1.0 〕\n该版本是一个功能完善的版本:\n包含多种输入方案:五笔、虎码、小鹤、鹤形、自然码、全拼; 包含多种辅助码方案:墨奇、小鹤、自然码、五笔前二、虎码首末、汉心等。 如果你想要一个配置完备的方案,那这个版本就比较适合。\n🚩 **〔 v5.0.x 〕** 2024-12-06 13:05\r该版本主要更新内容如下:\r- 拼音字典同一为万象词库,支持多种辅助码;\r- 增加了自然码双拼方案;\r- 重构优化其他原有输入方案;\r- 添加语言大模型(万象词库及大模型 3.0 正在测试中);\r- 基于万象词库提取独立的拼写规则;\r- 整理集中配色方案模块,便于管理使用。\r🚩 **〔 v4.0.x 〕** 2024-12-01 21:12\r该版本新增了新的双拼方案 - 知心大鹤:\r- 提取了墨奇码作为辅助码;\r- 使用 pinyin_simp 替代了简码回显;\r- 默认替代知心小鹤方案。\r🚩 **〔 v3.0.x 〕** 2024-11-24 14:26\r该版本目前包含以下几种方案:\r- 五笔|拼音混输方案(86);\r- 虎码|拼音混输方案;\r- 全拼方案(雾淞拼音词库);\r- 双拼方案(小鹤双拼);\r- 字符输入快捷方案。\r🚩 **〔 v2.0.x 〕** 2024-06-21 14:36\r该版本目前包含以下几种方案:\r- 五笔|拼音混输方案(86);\r- 虎码|拼音混输方案。 写在前面的 输入法也可以是“养成系”的,rime 就是!它的 词频调整 和 自动造词 功能是最吸引我的,它的 同步算法 更是具有魔力,以上便是成就 rime 具备养成系功能的源泉。\n最初接触 rime 是在使用 emacs 的过程中。我的 emacs 配置中集成了 evil 插件,它提供给 emacs 类 vim 的操作体验,但是和 vim 一样,在中文状态的时候切换回命令模式的时候,esc 还需要先切换为英文状态才可以顺利地执行后续操作。\n有没有什么方法可以在切换回命令模式的时候,让输入法的状态也自动调整为英文状态呢?有 - emacs-rime !那个时候的我还比较热衷于 “all in eamcs” ,emacs-rime 的出现简直正中我的下怀,按照文档配置好它,并引入了极点五笔的词库,体验相当不错。\n使用过 linux 的朋友很容易发现,这里基本上是中文输入法的荒野之地。除了一个移植版的搜狗输入法,好像别无其他了。如果你是一个五笔的使用者,貌似就不得不忍耐那只提供了基础输入功能的 ibus-wubi 或是 fictx-wubi 。\nrime 的出现对于跨平台的输入体验来说,是一道 光 。基于相同的理念设计,它在不同平台都有相应的实现。同一份输入法设定,很好地保证了相似的输入体验。强大的词频调整和自动造词,更像一个贴心的朋友陪在你身边,让你如沐春风、心旷神怡。\n在 rime 之前,我使用多许多不同的五笔输入法,微软五笔、搜狗五笔、冰凌五笔等等,它们也好用,但却不够好用。某些时刻,你总会觉得它们冷冰冰的,不那么聪明的样子 😅……\n寻寻觅觅之后,我开始使用和配置 rime。相信我,这期间经历了好一番折腾。确切来说,rime 是一个输入法引擎,要想使用它输入,必须加载相应的输入法方案(scheme)。在安装 rime 的过程中,它内置加载了一些输入法方案,只需要按需选择就可以正常地使用了。\n鲁迅先生说过:“了解一个 scheme 的最佳方法,就是去亲自创造一个 scheme 。”\n这就是 rime_jk(我称之为 知心五笔)的由来了~\n快速开始 rime_jk 是基于五笔拼音的混输方案改造、扩展而来的,按需加入了:\n- 自定义预选项 - opencc/misc.txt\r- 自定义缩写扩展 - custom_phrase.txt\r- 词典库相关扩展 - dicts/wubi86_district.dict.yaml ..\r- 基于 lua 的日期时间插入 - lua/data-translator.lua\r- emoji 候选支持 - opencc/emoji.json ..\r- 简约清爽的定制配色 - jk_wubi.custom.yaml ..\r- 新增了虎码方案支持\r- …… ok,你可以安装好 rime 并加载了 rime_jk 之后慢慢体验 ~\n1.安装 rime\nwindows 平台上 weasel 小狼毫的版本 - releases · rime/weasel,目前最新版本为 ⬇️ 0.16.3。\n2.部署 rime_jk\n一般安装 rime 后,其用户文件夹为 %appdata%\\rime ,在我的电脑上为 c:\\users\\jack\\appdata\\roaming\\rime (其他平台详见 rime 用户文件夹 )。跳转到 roaming 目录后,删除或备份已有的 rime 文件夹,然后执行以下操作:\ngit clone https://github.com/loveminimal/rime-jk.git rime\r# 或者通过 gitee 镜像\rgit clone https://gitee.com/loveminimal/rime-jk.git rime 之后,重新部署 即可。\n💡 这里我们假设你已经具备了基础的 rime 使用经验,如果你是刚接触 rime 的朋友,可以先阅读 rime wiki 了解一下 rime 的基础概念,或者你可以接着往下看。\n“她”的由来 rime_jk 的文件层级结构(详见 loveminimal/rime-jk 仓库)。\n知心五笔的 schema 主要基于 wubi_pinyin.schema.yaml 修改而来,并参考雾淞拼音和极点五笔的相关配置精简、添加了一些所需功能。如,我们引用了雾淞拼音方案中对于 emoji 的设置、修改了其时间日期插入脚本,引用了极点五笔中行政区域相关的附属词典等等。\n![[assets/pasted image 20241120105445.png|150]] ![[assets/pasted image 20241120151532.png|150]]\n为了区别于 rime 内置的配色方案,我们定制了知心五笔自己的配色(详见 jk_wubi.custom.yaml ),简约风格。每页设置了 4 个候选项,对应(1-space 2-; 3-' 4-/)4 种便捷映射。\n修改一下五笔方案的色调~ 当然,旧的主题色调也得到了保留,只需要在 jk_wubi.custom.yaml 修改为 color_schema: aqjk 即可。\n键位调整 详见 rime-jk/default.custom.yaml 。\n我们在保留 rime 部分原生支持键位的情况下,添加了一些新热键。如可以快速切换输入方案的 .next 和中英文状态的 ascii_mode ,分别绑定到 c-o(others) 和 c-i(input schema)。虽然,我们对 4 个候选项都做了对应的按键映射,但还是新增了 c-j/k 用于在候选词之间移动,它比默认的 c-n/p 要好按(它们也得到了保留支持)。\n按照你的心意和习惯去调整即可 ~\n配色方案 在〔v5.0.0 〕中,我将之前陆陆续续制作的一些方案配色提取出来,放在了 style.yaml 文件中,自左至右依次是 pujk、orjk、skjk、sljk、emjk (绛紫色 - 默认、橙黄色、天蓝色、板岩灰、翠绿色),目前它们分别应用于五笔、虎码、小鹤双拼、自然码双拼、全拼。\n![[assets/pasted image 20241206103538.png|130]] ![[assets/pasted image 20241206103556.png|130]] ![[assets/pasted image 20241206103616.png|130]] ![[assets/pasted image 20241206103635.png|130]] ![[assets/pasted image 20241206120555.png|130]]\n下面这个配色方案是最初魔改的第一个配色方案,它是基于 aque 修改而来的,所以就把它命名为 aqjk 碧水色保留,目前它应用在知心字符方案。\n![[assets/pasted image 20241206103651.png|130]]\n虎码方案 几天前,我开始学习虎码的旅程 - 「 [[嗨,虎码]] 」,它也是一种形码方案。在 虎码e盘 中有官方托管的各类教程、码表及软件包。\n虎码在各个平台也有相应的实现,windows 平台下有一个绿色免安装的软件 - bime 虎码。当然,最吸引我的地方还是在于它可以挂载在 rime 中。\n切换输入法难以避免有阵痛期,但是工作需要又不可能总是慢悠悠的。因为我已经有了心爱的 rime_jk,我并不想直接切换到官方提供的挂载方案,所以做了个简单的移植方案 - tiger_rime_jk( 知心老虎 🐯 放心 ~ 功能是 100% 的)。\n![[assets/pasted image 20240605142725.png|150]]\ntiger_rime_jk 保留了完整的官方词库,精简了不必要的冗文件,反查和 emoji 保持了和 rime_jk 的一致性。当然,同样制作了一个贴近虎码 bime 原色的简约风格的配色方案。nice ~\n双拼方案 随着把拼音的字典换成了万象词库,对双拼方案也做了一些修改,如:\n- 合并了小鹤、大鹤方案 →(同一为)知心小鹤\r- 增加了自然码双拼方案 万象词库同时支持多个辅助码,如墨奇、五笔前二、虎码首末、鹤形、自然辅助码等。如此,我们就不用同时维护多个拼音词库,如之前我们就同时使用雾淞拼音词库和墨奇词库。\n另一方面,万象词库的作者更新很不错,目前正在测试 3.0 的优化词库和大模型,期待一下吧~\n![[assets/pasted image 20241206103616.png|150]] ![[assets/pasted image 20241206103635.png|150]]\n\u0026gt; :: `\u0026gt; 2024-12-02 10:32` 人生充满着“打脸”,我一个习惯了使用五笔的人,入门了双拼之后浪费了整个周末去折腾辅助码的配置……\r![[assets/pasted image 20241202111334.png|150]]\r这是一个强迫症式的周末,我在纯双拼的基础上对比了若干库,最终从 [gaboolic/rime-shuangpin-fuzhuma: 墨奇音形](https://github.com/gaboolic/rime-shuangpin-fuzhuma) 中提取了小鹤双拼的辅助码,并把它集成到了既有的配置中,将小鹤升级到了大鹤 😂 。\r输入法的折腾到些告一段落了~ (希望不会被再次“打脸”)。\r\u0026gt; :: 双拼方案仅限于音拼喽,~~不会往音形扩展了~~,一则已经有了成熟的五笔 86,二则懒得再去记辅助码,三则再优秀的音码词频也不会有形码来得舒爽。\r下午没事,制作了一个简单的小鹤双拼方案 - 知心小鹤 flypy_rime_jk。如下:\r![[assets/pasted image 20241120151612.png|150]]\r怎么说呢?双拼确实有它的可取之处,上手也很快(找到规律,大概半个小时左右),毕竟是大家基本上都有全拼的基础,而双拼只是在些基础上对于韵母的重新做了下分布。\r拼音输入不像形码输入,它比较依赖于较大的词库,或者你比较有耐心地进行养成系~ ~~这个双拼方案我没有选择引入大的词库,一是日常还是用五笔多一点,二是小词库有小词库的优势,比如重词相对较少。~~\r好嘛,双拼全靠养成不是个事,引入了雾凇拼音的词典的子库。实际上,`py_rime_jk.dict.yaml` 就是 `rime_ice.dict.yaml` 的拷贝修改。制作双拼方案的时候,借鉴了 rime 的小鹤双拼方案并在此基础上做了精简优化,新增了一些功能。 感兴趣的朋友可以学习一下小鹤双拼 - 小鹤入门。\n全拼方案 鉴于通用性,~~在朙月拼音的基础上,~~制作了全拼方案。现在拼音方案(双拼、全拼)统一应用万象词库。\n![[assets/pasted image 20241120105541.png|150]]\n全拼方案和双拼方案的词库是共用的,都是 py.dict.yaml ,因为双拼本质上使用的就是拼音的词库。\n结语 rime 真的是挺方便的,熟悉它比想象中的简单,不要被其看似内容很多的 wiki 吓到。使用它,你就只需要了解一小部分知识就行了,定制它就再多一点点。 hack rime ,她越来越懂你。\n附录资源 📌 6.x 版本启动 »»»\n我们选用 rime-wubi 极爽词库 6.0 作为默认的五笔词库(它也是 rime 官方内置的五笔词库),搭配极点五笔的地域扩展词库,长期实践,体验非常好。\n虎码词库使用虎码官方提供的 ✅ 虎码字根表作为默认码表。\n拼音词库选用基于白霜(» 雾凇)拼音词库的万象版本,该版本的词量和词频体验良好,只使用基础词库的情况下便是如此,故我们只保留基础词库和 8105 通用单字表即可。\n五笔词库\n✅ rime/rime-wubi: 【五笔字型】输入方案 \u0026gt; 极爽词库 6.0 kylebing/rime-wubi86-jidian: 86五笔极点码表 cnman/rime-data: 新世纪五笔字型 for rime 王码原版五笔 2018 大一统码表 \u0026gt; 原版王码 本王码五笔字型大一统 2018 高级版(产品代号:wm18),支持 gb-18030 大字符集 27533 个汉字,包括有:简体、繁体、日、韩、香港地区汉字。集成王码五笔字型(86 版+98 版+新世纪版)三个版本输入法。\n:: 一个极具个人主观性的建议 - 形码方面五笔 86 的通用性最好 ~\n全拼词库\nidvel/rime-ice: rime 配置:雾凇拼音 | 长期维护的简体词库 gaboolic/rime-frost: 白霜拼音:蒹葭苍苍,白露为霜。 双拼词库\nbigshans/rime-zrm: 自然码+辅码的 rime 配置方案,可以使用 \u0026lsquo;;\u0026rsquo; 应用辅码。 ksqsf/rime-moran: 自然码双拼+辅助码 rime 配置 amzxyz/rime_wanxiang_zrm: 自然万象\u0026mdash;随意码 全拼、双拼兼容词库\n✅ gaboolic/rime-shuangpin-fuzhuma: 雾凇鹤|雾凇自然|墨奇码|墨奇音形 amzxyz/rime_wanxiang_pro: rime万象拼音输入方案增强版 ","date":"2024-05-29","permalink":"https://aituyaa.com/rime_jk-%E6%8C%87%E5%B0%96%E4%B8%8A%E7%9A%84%E6%97%8B%E5%BE%8B/","summary":"\u003cp\u003e这个是迟来的 \u003ca href=\"https://github.com/loveminimal/rime-jk\"\u003eloveminimal/rime-jk\u003c/a\u003e 的 \u003ccode\u003eREADME\u003c/code\u003e ,照例单开一篇,方便记录和更新~\u003c/p\u003e","title":"rime_jk 指尖上的旋律"},]
[{"content":"不可否认,windows 是一款非常优秀的操作系统,但日常使用起来仍有不少可以改善的地方,尤其是当它偶尔抽风开倒车的时候。在这篇文章中,我会列出自己经常使用并感觉比较舒服的一些软件,分享给大家试一试,或许你也会有意想不到的收获~\n优秀软件 \u0026gt; 快捷一览\n软件 官网、文档 场景、描述 收费? listary 官网 、中文文档 本地检索、网络搜索 snipaste 官网、 中文文档 截图 7-zip 官网 压缩 todesk 官网 远程控制 window centering helper 官网 窗口居中 wgestures 官网 系统级鼠标手势 mydockfinder 官网 类 mac 桌面 是 carnac github 桌面按键显示、用于录屏 lively wallpaper 官网 动态桌面 freefilesync 官网 同步备份 autohotkey 官网 开源的脚本语言和自动化工具 screentogif 官网 gif 录制工具 ……\n附录 windows 终端怎么自动滚动日志 在 windows 终端(windows terminal)中,自动滚动日志通常是指在查看日志文件时,终端能够自动滚动到文件的末尾,以便实时查看最新的日志内容。这在监控应用程序日志或系统日志时非常有用。以下是一些常用的方法来实现这一功能:\n❶ 使用 tail 命令(适用于 windows 10/11)\nwindows 10 和 windows 11 自带了 tail 命令,可以用来实时查看日志文件的最新内容。\n打开 windows 终端。 使用以下命令来查看日志文件,并自动滚动到最新内容: 1 2 # tail -f \u0026lt;日志文件路径\u0026gt; tail -f c:\\logs\\app.log ❷ 使用 get-content 命令(适用于 powershell)\n如果你使用的是 powershell,可以使用 get-content 命令来实现类似的功能。\n打开 powershell。 使用以下命令来查看日志文件,并自动滚动到最新内容: 1 2 # get-content \u0026lt;日志文件路径\u0026gt; -wait get-content c:\\logs\\app.log -wait ❸ 使用 notepad++(适用于简单需求)\n如果你只是偶尔需要查看日志文件,可以使用 notepad++ 的 “always on top” 和 “follow tail” 功能。\n下载并安装 notepad++。 打开 notepad++,点击 “view” \u0026gt; “always on top”。 打开日志文件,点击 “view” \u0026gt; “follow tail”。 notepad++ 会自动滚动到文件的末尾,并实时更新最新的日志内容。 通过以上方法,你可以根据自己的需求选择最适合的方式来实现 windows 终端自动滚动日志的功能。\n关于 windows 睡眠、休眠禁止鼠标、键盘的唤醒问题 📘 一劳永逸解决win10所有睡眠问题 - 知乎]\n❶ 查看上次唤醒电脑的设备\n# 查看上次唤醒电脑的设备 powercfg -lastwake c:\\users\\jack\u0026gt;powercfg -lastwake 唤醒历史记录计数 - 1 唤醒历史记录 [0] 唤醒源计数 - 1 唤醒源 [0] 类型: 固定功能 电源按钮 ❷ 查看哪些硬件能唤醒电脑\n# 查看哪些硬件能唤醒电脑 powercfg /devicequery wake_armed c:\\users\\jack\u0026gt;powercfg /devicequery wake_armed hid-compliant mouse hid keyboard device realtek gaming 2.5gbe family controller hid-compliant mouse (001) hid keyboard device (001) ❸ 禁止指定硬件唤醒主机\npowercfg /devicedisablewake \u0026#34;hid-compliant mouse\u0026#34; powercfg /devicedisablewake \u0026#34;hid-compliant mouse (001)\u0026#34; powercfg /devicedisablewake \u0026#34;hid keyboard device\u0026#34; powercfg /devicedisablewake \u0026#34;hid keyboard device (001)\u0026#34; 如何再次允许指定硬件唤醒主机呢?\npowercfg /deviceenablewake \u0026#34;hid-compliant mouse\u0026#34; powercfg /deviceenablewake \u0026#34;hid-compliant mouse (001)\u0026#34; powercfg /deviceenablewake \u0026#34;hid keyboard device\u0026#34; powercfg /deviceenablewake \u0026#34;hid keyboard device (001)\u0026#34; 这两条允许和禁止命令与 右键开始菜单 » 设备管理器 » 鼠标 » 电源管理 » 取消勾选允许鼠标唤醒电脑 的效果是一样的。如下:\n![[assets/pasted image 20250105195143.png|500]]\n❹ 禁止后再次查看哪些硬件能唤醒电脑\npowercfg /devicequery wake_armed c:\\users\\jack\u0026gt;powercfg /devicequery wake_armed realtek gaming 2.5gbe family controller 当前,鼠标、键盘都无法主机唤醒主机的休眠、睡眠状态了。\n如何解决华硕主板(b760m)关机 usb 仍带电? 📘 华硕主板关机usb仍带电?轻松几步教你解决问题\n❶ bios〔高级模式 〕» 高级选项 » 高级电源管理(apm)» erp 支持 设置为 关闭 。\nerp(energy related products)是欧洲的一项能源相关产品指令,它要求设备在关机或待机状态下减少能耗。将erp支持禁用,可以使得主板在关机后不再遵循这一指令,从而实现usb接口的断电。\n本机默认为关闭状态。\n❷ bios〔高级模式 〕 » 内置设备 » usb power delivery in soft off state (s5) 设置为 关闭 。\n这一设置控制的是在主机关闭(s5状态)时,是否继续为usb接口供电。将其禁用后,主板在关机状态下将不再为usb接口供电。\n❸ 控制面板 » 系统和安全 » 更改电源按钮的功能 » 更改当前不可用的设置 » 启用快速启动(推荐) 取消勾选。\n快速启动是一项加速开机速度的功能,但它可能会导致关机后usb接口仍然带电。(本机开启也不影响)\n","date":"2024-05-13","permalink":"https://aituyaa.com/windows-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e不可否认,Windows 是一款非常优秀的操作系统,但日常使用起来仍有不少可以改善的地方,尤其是当它偶尔抽风开倒车的时候。在这篇文章中,我会列出自己经常使用并感觉比较舒服的一些软件,分享给大家试一试,或许你也会有意想不到的收获~\u003c/p\u003e","title":"windows 那些事儿"},]
[{"content":"在页面渲染的过程中,获取一个窗口或元素的尺寸大小及位置信息是必要的。鉴于市面上浏览器的多种多样以及某些历史原因,相关的一些元素属性和 api 可能有差异,这里我们以它们在 chrome 中的表现为准,展开聊一下相关方面的东东 ~\n⭐ 写在最前面的 这个章节属于增补内容,在后续查询资料的过程中无意中发现在 mdn 上有更加详细易懂的相关知识。放在这里,以有利于后续内容的深度认识。详见📔 坐标系 - css:层叠样式表 | mdn\n标准 css 对象模型坐标系\ncss 对象模型使用四种标准坐标系。为了帮助直观地理解这些主要坐标系,下图显示了一台显示器,显示器上有一个浏览器窗口,窗口中的内容滚动到了视口之外。在视口外滚动的页面内容在浏览器窗口上方显示为半透明,以指示“页面”坐标的原点在哪里。“客户端”、“页面”和“视口”坐标系的原点已突出显示。\n![[assets/pasted image 20240516144020.png]]\n偏移\n使用“偏移”模型指定的坐标使用的是被检查元素或发生事件的元素的左上角。\n例如,当发生鼠标事件时,事件的 offsetx 和 offsety 属性中指定的鼠标位置相对于事件发生节点的左上角。原点的嵌入距离由 padding-left 和 padding-top 指定。\n视口\n你可以在 视口概念 - css:层叠样式表 | mdn 了解更多关于视口的信息。\n“视口”(或“客户端”)坐标系以发生事件的视口或浏览上下文的左上角为原点。这就是呈现文档的整个视图区域。\n例如,在台式电脑上,mouseevent.clientx 和 mouseevent.clienty 属性表示事件发生时鼠标光标相对于 window 左上角的位置。使用触控笔或指针时,触摸事件中的 touch.clientx 和 touch.clienty 坐标相对于同一原点。\n窗口的左上角始终是 (0,0),与文档内容或任何滚动无关。换句话说,滚动文档会改变文档中给定位置的视口坐标。\n页面\n“页面”坐标系给出了一个元素相对于整个渲染文档左上角的位置。这意味着用户在文档中横向或纵向滚动元素后,除非元素通过布局变化移动,否则文档中元素的某一点将保持相同的坐标。\n鼠标事件的 pagex 和 pagey 属性提供了事件发生时鼠标相对于文档左上角的位置。触摸事件中的 touch.pagex 和 touch.pagey 坐标相对于同一原点。\n屏幕\n最后,来介绍“屏幕”模型,原点是用户屏幕空间的左上角。该坐标系中的每个点都代表一个逻辑像素,因此每个坐标轴上的值都以整数递增或递减。如果文档中包含的窗口被移动,或者用户的屏幕几何形状发生变化(通过改变显示分辨率或在系统中添加或删除显示器),文档中给定点的位置就会发生变化。\nmouseevent.screenx 和 mouseevent.screeny 属性给出了鼠标事件相对于屏幕原点的位置坐标。触摸事件中的 touch.screenx 和 touch.screeny 坐标相对于同一原点。\n:: 不得不说,mdn 你值得拥有~\n基础了解 💡 本章节内容主要摘录自《javascript 高级程序设计》的第 12 章的 12.1 和 12.4 小节。建议先阅读一下摘录或对应章节的内容,当然如果你想直接跳过这一章也无不可,在后续章节中我们会进一步总结相关方面的知识点。\n下面让我们先来了解一下浏览器中窗口和元素的尺寸大小及位置的基础内容吧。\nbom 的核心是 window 对象,表示浏览器的实例。\nwindow 对象的位置可以通过不同的属性和方法来确定。现代浏览器提供了 screenleft 和 screentop 属性,用于表示窗口相对于屏幕左侧和顶部的位置 ,返回值的单位是 css 像素。\n在不同浏览器中确定浏览器窗口大小没有想象中那么容易。所有现代浏览器都支持 4 个属性:innerwidth、 innerheight、 outerwidth 和 outerheight。 outerwidth 和 outerheight 返回浏 览器窗口自身的大小(不管是在最外层 window 上使用,还是在窗格 \u0026lt;frame\u0026gt; 中使用)。innerwidth 和 innerheight 返回浏览器窗口中页面视口的大小(不包含浏览器边框和工具栏)。\ndocument.documentelement.clientwidth 和 document.documentelement.clientheight 返回页面视口的宽度和高度。\n浏览器窗口自身的精确尺寸不好确定,但可以确定页面视口的大小,如下所示:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 let pagewidth = window.innerwidth, pageheight = window.innerheight; // 检查 pagewidth 是不是一个数值 if (typeof pagewidth != \u0026#34;number\u0026#34;) { // 检查页面是否处于标准模式 if (document.compatmode == \u0026#34;css1compat\u0026#34;) { pagewidth = document.documentelement.clientwidth; pageheight = document.documentelement.clientheight; } else { pagewidth = document.body.clientwidth; pageheight = document.body.clientheight; } } 在移动设备上, window.innerwidth 和 window.innerheight 返回视口的大小,也就是屏幕上页面可视区域的大小。 mobile internet explorer 支持这些属性,但在 document.documentelement.clientwidth 和 document.documentelement.clientheight 中提供了相同的信息。\n在其他移动浏览器中, document.documentelement.clientwidth 和 document.documentelement.clientheight 返回的布局视口的大小,即渲染页面的实际大小。\n:: 经测试,这两个属性在移动浏览器上返回的为可见视口大小。页面实际大小放在了 document.body 对象中。\n布局视口是相对于可见视口的概念,可见视口只能显示整个页面的一小部分 。 mobile internet explorer 把布局视口的信息保存在 document.body.clientwidth 和 document.body.clientheight 中。\n浏览器窗口尺寸通常无法满足完整显示整个页面,为此用户可以通过滚动在有限的视口(可见视口)中查看文档。度量文档相对于视口滚动距离的属性有两对,返回相等的值: window.pagexoffset/window.scrollx 和 window.pageyoffset/window.scrolly。\nwindow 的另一个属性 screen 对象,是为数不多的几个在编程中很少用的 javascript 对象。这个对象中保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度。每个浏览器都会在 screen 对象上暴露不同的属性。\n属 性 说 明 availheight availleft availtop availwidth colordepth height left pixeldepth top width orientation 屏幕像素高度减去系统组件高度(只读) 没有被系统组件占用的屏幕的最左侧像素(只读) 没有被系统组件占用的屏幕的最顶端像素(只读) 屏幕像素宽度减去系统组件宽度(只读) 表示屏幕颜色的位数;多数系统是 32(只读) 屏幕像素高度 当前屏幕左边的像素距离 屏幕的位深(只读) 当前屏幕顶端的像素距离 屏幕像素宽度 返回 screen orientation api 中屏幕的朝向 窗口和视口 \u0026gt; 浏览器中的尺寸大小和位置\n![[assets/distance2.png]]\n如上图所示,我们对所要遇到的窗口和视口进行了简单的标准。其中:\ns 屏幕窗口 b 浏览器窗口 p 页面 = l 布局视口 + 滚动条 v 可见视口(即上个章节中描述的 viewport ) e 元素 对于浏览器窗口(b 窗口)而言,其窗口大小用 outerwidth 和 outerheight 来表示,我们称之为浏览器窗口的实际大小。该窗口的位置则由其到屏幕左上角的距离来表示,分别为 screenleft 和 screentop 。\n页面视口 p (不包含浏览器边框和工具栏等,包含滚动条)的大小由 innerwidth 和 innerheight 表示。它的位置是由浏览器位置及边框、工具栏等的尺寸计算而来的。\n布局视口 l(页面视口 p 除去滚动条)的大小由 document.documentelement.clientwidth 和 document.documentelement.clientheight 决定。确切地说,在桌面端使用“可见视口”好一些,因为在移动端布局视口指的是实际渲染页面的大小(由 document.body.clientwidth 和 document.body.clientheight 表示)。\n:: 一般情况下,可以认为页面视口和布局视口是同一个东东。\n💡 布局视口是相对于可见视口的概念,可见视口只能显示整个页面的一小部分 。\n文档内容往往不能在可见视口中完整地展示出来,这里就需要我们滚动页面。浏览器允许文档在可见视口的 x 轴和 y 轴方向滚动。其相对于视口滚动距离的属性有两对,返回相等的值: window.pagexoffset /window.scrollx 和 window.pageyoffset / window.scrolly。\n关于元素视口,即页面中的 htmlelement 大小及位置,比较重要,我们放在下个章节单独说明。\n元素视口 如图所示,e 视口即粉色条框是一个描述 github 仓库的项目框。\nhtmlelement 接口表示所有的 html 元素。一些 html 元素直接实现了 htmlelement 接口,其他的间接实现 htmlelement 接口。详见 htmlelement - web api | mdn。\nclientwidth 和 clientheight ![[assets/pasted image 20240510090654.png]]\n只读属性 element.clientwidth 对于内联元素以及没有 css 样式的元素为 0;否则,它是元素内部的宽度(以像素为单位)。该属性包括内边距(padding),但不包括边框(border)、外边距(margin)和垂直滚动条(如果存在)。\n只读属性 element.clientheight 对于没有定义 css 或者内联布局盒子的元素为 0;否则,它是元素内部的高度(以像素为单位),包含内边距,但不包括边框、外边距和水平滚动条(如果存在)。\nclientheight 可以通过 css height + css padding - 水平滚动条高度(如果存在)来计算。\n❓clientleft 和 clienttop clientleft 表示一个元素的左边框的宽度,以像素表示。clientleft 不包括左外边距和左内边框。clientleft 是只读的。\nclienttop 一个元素顶部边框的宽度(以像素表示)。不包括顶部外边距或内边框。clienttop 是只读的。\nscrollwidth 和 scrollheight element.scrollwidth 这个只读属性是元素内容宽度的一种度量,包括由于 overflow 溢出而在屏幕上不可见的内容。\n![[assets/pasted image 20240510091057.png]]\nscrollwidth 值等于元素在不使用水平滚动条的情况下适合视口中的所有内容所需的最小宽度。宽度的测量方式与 clientwidth 相同:它包含元素的内边距,但不包括边框,外边距或垂直滚动条(如果存在)。它还可以包括伪元素的宽度,例如 ::before 或 ::after。如果元素的内容可以适合而不需要水平滚动条,则其 scrollwidth 等于 clientwidth 。\nelement.scrollheight 只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。\nscrollheight 的值等于该元素在不使用滚动条的情况下为了适应视口中所用内容所需的最小高度。高度的度量方式与 clientheight 相同:包括元素的内边距,但不包括元素的边框、外边距以及水平滚动条(如果存在)。它也包括 ::before 和 ::after 这样的伪元素的高度。如果元素的内容不需要垂直滚动条就可以容纳,则其 scrollheight 等于 clientheight。\nscrollleft 和 scrolltop element.scrollleft 属性可以读取或设置元素滚动条到元素左边的距离。\nelement.scrolltop 属性可以获取或设置一个元素的内容垂直滚动的像素数。\n一个元素的 scrolltop 值是这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrolltop 值为 0。\n这个 element.scrollleftmax 是只读的属性返回一个 number 表示一个元素横向滚动条可滚动的最大距离。element.scrolltopmax 返回一个只读 number 表示元素所能滚动的最大高度。这两个属性还没纳入规范。\noffsetwidth 和 offsetheight ![[assets/pasted image 20240510085915.png]]\nhtmlelement.offsetwidth 是一个只读属性,返回一个元素的布局宽度。一个典型的(译者注:各浏览器的 offsetwidth 可能有所不同)offsetwidth 是测量包含元素的边框 (border)、水平线上的内边距 (padding)、竖直方向滚动条 (scrollbar)(如果存在的话)、以及 css 设置的宽度 (width) 的值。\nhtmlelement.offsetheight 是一个只读属性,它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。通常,元素的 offsetheight 是一种元素 css 高度的衡量标准,包括元素的边框、内边距和元素的水平滚动条(如果存在且渲染的话),不包含 :before 或 :after 等伪类元素的高度。\noffsetleft 和 offsettop htmlelement.offsetleft 是一个只读属性,返回当前元素左上角相对于 htmlelement.offsetparent 节点的左边界偏移的像素值。\nhtmlelement.offsetparent 是一个只读属性,返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table, td, th, body 元素。当元素的 style.display 设置为 \u0026ldquo;none\u0026rdquo; 时,offsetparent 返回 null。offsetparent 很有用,因为 offsettop 和 offsetleft 都是相对于其内边距边界的。\nhtmlelement.offsettop 为只读属性,它返回当前元素相对于其 offsetparent 元素的顶部内边距的距离。\n事件中的位置 mouseevent 当定点设备的按钮(通常是鼠标的主键)在一个元素上被按下和放开时,click 事件就会被触发。\n如果在一个元素上按下按钮,而将指针移到元素外再释放按钮,则在包含这两个元素的最具体的父级元素上触发事件。\nclick 事件会在 mousedown 和 mouseup 事件依次触发后触发。\nmousedown 事件在定点设备(如鼠标或触摸板)按钮在元素内按下时,会在该元素上触发。\nmouseup 事件在定点设备(如鼠标或触摸板)按钮在元素内释放时,在该元素上触发。其与 mousedown 事件相对应。\nmouseenter 事件在定点设备(通常指鼠标)首次移动到元素的激活区域内时,在该元素上触发。\nmouseleave 事件在定点设备(通常是鼠标)的指针移出某个元素时被触发。\nmouseout 事件在定点设备(通常是鼠标)移动至元素或其子元素之外时,会在该元素上触发。当指针从一个元素移入其子元素时,因为子元素遮盖了父元素的可视区域,所以 mouseout 也会被触发。\n当一个定点设备(通常指鼠标)在一个元素本身或者其子元素上移动时,mouseover 事件在该元素上触发。\nmousemove 事件在定点设备(通常指鼠标)的光标在元素内移动时,会在该元素上触发。\n以上事件,都是 mouseevent ,即鼠标事件。其涉及到的与光标点位置有关的属性如下:\n属性 说明 mouseevent.screenx 鼠标指针相对于屏幕的 x 轴坐标。 mouseevent.screeny 鼠标指针相对于屏幕的 y 轴坐标。 mouseevent.offsetx 鼠标指针相对于目标节点的内填充边的 x 轴坐标。 mouseevent.offsety 鼠标指针相对于目标节点的内填充边的 y 轴坐标。 mouseevent.pagex 鼠标指针相对于整个文档的 x 轴坐标。 mouseevent.pagey 鼠标指针相对于整个文档的 y 轴坐标。 mouseevent.clientx\nmouseevent.x 鼠标指针相对于局部 dom 元素的 x 轴坐标。 mouseevent.clienty\nmouseevent.y 鼠标指针相对于局部 dom 元素的 y 轴坐标。 mouseevent.movementx 鼠标指针相对于最后一次 mousemove事件位置的 x 轴坐标。 mouseevent.movementy 鼠标指针相对于最后一次 mousemove事件位置的 y 轴坐标。 touchevent touchstart 事件在一个或多个触点与触控设备表面接触时被触发。\ntouchend 事件在一个或多个触点从触控平面上移开时触发。注意,也有可能触发 touchcancel 事件。\ntouchcancel 事件在触点被中断时触发,中断方式基于特定实现而有所不同(例如,创建了太多的触点)。\ntouchmove 事件在触点于触控平面上移动时触发。\n以上事件,都是 touchevent ,即鼠标事件。其涉及到的与触摸点位置有关的属性如下:\n属性 描述 touchevent.touches 一个包含所有的 touch对象的 touchlist,这些 touch 对象表示当前与表面接触的触点(不论事件目标或状态变化)。 touchevent.targettouches 一个包含所有的 touch对象的 touchlist,这些 touch 对象表示当前与触摸表面接触的触点,且触点起始于事件发生的目标元素。 touchevent.changedtouches 一个包含所有的 touch对象的 touchlist,这些 touch 对象表示在前一个 touch 事件和当前的事件之间,状态发生变化的独立触点。 这里我们附上一个 touch 对象的实例,如下: 1. clientx: 177.60000610351562 2. clienty: 118.4000015258789 3. force: 1 4. identifier: 0 5. pagex: 177.60000610351562 6. pagey: 118.4000015258789 7. radiusx: 18.399999618530273 8. radiusy: 18.399999618530273 9. rotationangle: 0 10. screenx: 410 11. screeny: 235 scroll 事件 当用户滚动某个元素的内容时 scroll 事件将会被触发。element.onscroll 同等于 element.addeventlistener(\u0026quot;scroll\u0026quot; ... )。\n其中 e.target.scrolltop 和 e.target.scrollleft 为滚动过程中元素的滚动位移。\n我要说的是… 是不是已经晕乎乎了?放心,只需要适当的练习就可以掌握它们了。认真观察一下,不难发现,有几个词汇是经常出现的,如 screen、page、client、offset、scroll 等。弄清了它们几个的区别,基本上就可以着手开发一些你想要的功能了。\n与 screen 相关的属性,顾名思义就是与显示器窗口有关的。如 window.screen.width 、window.screen.height 表示的就是显示器屏幕的宽、高。\n后面元素事件中的 screenx 、screeny 便是触发事件的光标点距离显示器屏幕的 x 轴、y 轴坐标了。\n💡 如无特殊说明,一般默认左上角为坐标原点 (0, 0)。\n类似,与 page 有关的是与实际文档大小有关。与 client 有关的是可见窗口区域。与 offset 相关的一般是偏移距离。与 scroll 有关的一般是滚动距离。\n在实际开发过程中, 我们一般处理与元素事件相关的位置,多数又是与包含可定位元素的低级元素的相对位置关系。故一般使用与 offset 相关的属性的场合多比较多。\n此类内容,还是需要在实际的练习中慢慢掌握。建议初学者,只需要在 chrome 中练习即可,不用考虑浏览器的兼容性。\n实例分析 ……\n","date":"2024-05-09","permalink":"https://aituyaa.com/%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%AD%E7%9A%84%E5%B0%BA%E5%AF%B8%E5%A4%A7%E5%B0%8F%E5%92%8C%E4%BD%8D%E7%BD%AE/","summary":"\u003cp\u003e在页面渲染的过程中,获取一个窗口或元素的尺寸大小及位置信息是必要的。鉴于市面上浏览器的多种多样以及某些历史原因,相关的一些元素属性和 API 可能有差异,这里我们以它们在 Chrome 中的表现为准,展开聊一下相关方面的东东 ~\u003c/p\u003e","title":"浏览器中的尺寸大小和位置"},]
[{"content":"云盘?很长一段时间内,它对于我来说都不是一个问题,直到我渐渐习惯了多端同步……\n起因 不久前,陪伴了我多年的 magic2 终于在是不知道第几次的摔打中罢工了…… 惋惜 3 秒钟。经过简单对比之后,入手了 magic6 ,但这也产生了一个问题。\nmagic2 使用的是荣耀从华为剥离之前的机型,其使用的也是华为的服务,所以我一直使用的是华为云。手机端的华为云盘直接集成在了文件管理 app 中,windows 平台的客户端同步表现也不错(没有 linux 版,但有网页端可以勉强使用)。\n现在问题来了。因为荣耀已经从华为剥离,其独立出自己的荣耀云,但这玩意儿根本就没有 pc 端…… 继续使用华为云?结果华为云盘又不能在其他品牌手机上正常使用…… f u c k ~\n经历了一番折腾,无果。所以,还得找个替代的云盘来同步一些日常使用的数据。\n有哪些云盘 诸如百度网盘、阿里网盘等直接排除掉,上传、下载、存储文件类型都有限制,用着恶心。\n坚果云 首先想到的是坚果云,简单试用了一下之后,它的多端做的相当不错,连 linux 都有相应的版本。\n![[assets/pasted image 20240506171415.png]]\n可惜的是它的免费版每月只有 1g 的上传流量,而我的当前同步资料大小近 2g,这就尴尬了…… (其各版本收费详见 坚果云各版本收费),当然可以选择分两个月上传同步这些资料,许多用户也确实这样干的,因为它只限制了每个月的上传流量,并不限制你累积上传了多少存储的文件(应该是对总量也有限制,不知道有没有用户一直积累使用撞墙的)。还有一点需要注意的是,免费版每月的下载流量只有 3g ,所以就算你是累积使用,当存储量超过 3g 后,也别想一次下载完。当然,如果你可以控制存储文件的体量不超过这个额度,那是可以的。\n我并不想分月上传我的文件,也担心将来的文件数量超过 3g 这个限额,所以不考虑它。买专业版?当然是可以的,但按着我自己预估的文件体量来看,它大概率不会超过 5g,所以每年投入 ¥200 大洋并不是一个好的选择。\ndropbox 先不说,在国内不“科学上网”根本用不了,就算能用速度也是个问题,更何况其免费版本只有 2g 。pass !\n它的个人 rpo 版按年付费的话,每月 $9.99 ,容量有 2t 。汇率算一下…… 不过有一说一,老外开发的软件,尤其的付费的,服务态度一般都挺好,没那么多弯弯绕。\nonedrive ✔️ 现在,让我们把目光移到大漂亮微软身上。最新的 windows 系统一般都内置安装了 onedrive ,有微软背书,技术上也可靠许多,至少短期内不用担心服务商哪天突然就拨线跑路。\n免费用户有 5g 的存储容量(够用了),并且也不限制上传和下载流量,国内的速度还可以。如果不够,还可以购买它的 microsoft 365 基础版,每个月 ¥15 (不支持按年付费)。个人版提供了 1tb 的容量,每年 ¥398,支持更多的桌面版本,如下图。\n![[assets/pasted image 20240506175716.png]]\n当然还有更高级的版本,提供的容量更大,价格也更贵。\n最后 最终,选择了 onedrive ,它非常符合我目前的需求。怎么说呢?商用软件的免费版本主要作用在于引流,培养用户的使用习惯,提高主场份额等等。服务商不是天使,也不是什么吸血鬼,生意而已,无关其他。用户在考虑付费使用的时候,考虑的问题方方面面,比如投入产出比、速度、稳定性、免费容量等等。\n总之呢,没有需求,不要创造需求,当你真正需要的时候,按需选择和购入即可。\n","date":"2024-05-06","permalink":"https://aituyaa.com/%E4%BA%91%E7%9B%98%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e云盘?很长一段时间内,它对于我来说都不是一个问题,直到我渐渐习惯了多端同步……\u003c/p\u003e","title":"云盘那些事儿"},]
[{"content":"windows 系统的默认字体很丑!浏览器中的字体也不好看!如何方便地修改浏览器的各个页面的默认字体呢?如果你有这个需求,接着向下看。\n字体 如 [[一款简约的 obsidian 主题#字体]] 中描述的那样,字体精美与否在很大程度上影响网上冲浪的爽感。编辑中是如此,浏览器中更是如此。\nlxgw · 落霞与孤鹜齐飞,秋水共长天一色 这款字体是真的精美,它是我主机上应用的默认替代字体(如果应用支持设置自定义字体)。下载并安装到本地即可。\n⏳ 下载链接: https://pan.baidu.com/s/1lcaxcovd5wjnxdllozcssa ,提取码: 6666\n在 google chrome 中,输入 chrome://settings/fonts ,可以修改浏览器的默认字体。但这种方式是“强”侵入式的,它适用于每一个页面,你甚至不能设置个白名单来过滤指定页面。当你要开发一个新的页面时,你就不得不重新改回其默认的设置字体。\n综上,我们需要这样一种方式:\n通过脚本注入样式代码,修改页面默认字体; 可以设置白名单过滤指定页面。 脚本 这里,我们通过 油猴 | tampermonkey 可以很容易实现上面这两点。脚本内容如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // ==userscript== // @name active lxgw font // @namespace http://tampermonkey.net/ // @version 2024-04-11 // @description try to take over the world! // @author you // @match https://*/* // @icon https://www.google.com/s2/favicons?sz=64\u0026amp;domain=qzxdp.cn // @grant none // ==/userscript== (function() { \u0026#39;use strict\u0026#39;; // ⚙️ 设置你想使用的字体 const fonts = \u0026#34;\u0026#39;code new roman\u0026#39;, \u0026#39;lxgw wenkai gb screen r\u0026#39;, \u0026#39;segoe ui emoji\u0026#39;\u0026#34;; let ele = document.queryselector(\u0026#39;head\u0026#39;); var styl = document.createelement(\u0026#34;style\u0026#34;); styl.innertext = ` *:not(i) { font-family: ${fonts} !important; } `; ele.appendchild(styl); })(); 在油猴中运行上述脚本即可。当你想要把当前页面加入到白名单中时,只需要点击插件图标,排除对应站点即可,如下:\n![[assets/pasted image 20240412172042.png]]\n效果 附一份修改字体前后的页面对比,以被我吐槽多次的 gitee 页面为例。\n\u0026gt; 修改前\n![[assets/pasted image 20240412173230.png]]\n\u0026gt; 修改后\n![[assets/pasted image 20240412173033.png]]\n","date":"2024-04-12","permalink":"https://aituyaa.com/%E4%B8%80%E4%B8%AA%E4%BF%AE%E6%94%B9%E6%B5%8F%E8%A7%88%E5%99%A8%E9%BB%98%E8%AE%A4%E5%AD%97%E4%BD%93%E7%9A%84%E6%B2%B9%E7%8C%B4%E8%84%9A%E6%9C%AC/","summary":"\u003cp\u003eWindows 系统的默认字体很丑!浏览器中的字体也不好看!如何方便地修改浏览器的各个页面的默认字体呢?如果你有这个需求,接着向下看。\u003c/p\u003e","title":"一个修改浏览器默认字体的油猴脚本"},]
[{"content":"与其它领域相比,web 开发更像是一个全栈化的工程,其使用的技术基本上涵盖了目前大部分应用所需。\n分类 从端的职能来说,整个 web 开发流程大致可以分为产品设计、前端、后端、测试及运维部署,其中产品设计又可以细分为功能性的需求设计和实现性的架构设计。\n产品设计 产品设计是一个很重要的环节,它决定着一个产品的价值(\u0026gt; 价值是在价值的交换过程中得以体现的),即其是否可以最大程度地满足市场需求。换句话说,从一开始,你需要确保开发出来的产品不是所谓的“屠龙之术”。\n在进行功能性的需求设计时,要以实际需求为导向,界定产品适用范围,不应盲目追求大而全。如此,在进行后续架构抽象的时候,才能有的放矢。架构设计要充分考虑预采用技术栈的成熟性、健壮性及开发难度,力求保证产品的稳定性、易维护性、可扩展性。\n前端开发 在现有 web 生态下,无论多么新奇的前端技术,最终目的都是生成可供 web 浏览器正确渲染的页面。让我们来简单回顾一下前端领域的发展历程(详细可参考 《 [[前端浅谈]] 》)。\n最初的 web 应用就是一个简单的页面 - http://info.cern.ch/ ,由浏览器渲染一份同样简单的 .html 文档生成。如上所述,现阶段我们可以认为前端开发的终点就是 html 技术。\n后续发展过程中,我们使用 css 为 html 元素添加了样式,更进一步使用 javascript 为其添加了更加丰富的交互。\n之后的所有前端概念都是围绕 html + css + javascript 展开的了,如封装了基础 js api 的 jquery、underscore.js、loadsh.js,又如可转译为 javascript 的 coffeescript、typescript,可转译为 css 的 sass、less 等等。\n在 es6 之前,javascript 没有原生的模块化标准(详细可参考《 [[模块化编程]] 》),但是随着前端项目复杂度的增加,其模块化的迫切性与日倶增。nodejs 为 javascript 家族带来了基于 commonjs 规范的模块化方法,但是它是运行在 node 平台中的,并不能直接被浏览器使用。老规矩,转译,最终目的生成浏览器引擎可以直接解析的 javascript 。grunt、browserify、gulp、webpack 等一系列流程管理、打包工具及其配套的插件生态就是干这个的。甚至于后来将样式文件,图片等静态资源文件也抽象化成了模块,一股脑地全塞在了里面。\n开发者们是永不满足的,因为懒,angluar、react、vue 等响应式框架开始涌现,并在短时间内受到人们的广泛推崇。再然后,好像就没什么新鲜事儿了,其实本来也不是多新鲜的事儿。\n所有的一切都开始于 html + css + javascript ,经过一翻折腾,最终又回到了原点而已。折腾当然不是白费的,它极大程度提高了开发者们的效率和热情,降低了项目开发的难度,使项目运行稳定且易于维护。\n后端开发 前端和后端是怎么界定的呢?\n早期的 web 开发领域,用户通过浏览器访问某个站点,浏览器通过用户的请求从相应服务器获取网页文件并渲染出来。所有的资源都是放在服务器端的,只是在用户请求的时候,通过网络传递过去。浏览器作为客户端,某个角度来说其本身并不是 web 开发的产品,它只是 web 产品得以呈现的平台宿主。\n如前所述,随着页面的复杂度越来越高,便分离出来了专门编辑页面的开发人员(虽然最初的时候往往是由同一个开发者兼职)。开发响应服务的人员通过某种方式识别编辑好的页面模板,将必要的信息(数据)注入到模板中,最终转译为浏览器可识别的页面文件,供用户请求。\n再后来,ajax 异步请求诞生了,用户可以通过局部更新页面来获取自己想要的信息了。现在,大体流程变成了下面这样。\n用户在初次请求的时候,从服务器端获取到足够的页面文件,并将相关的资源存储在客户端,如此,在不需要从服务端获取新的信息数据的时候,整个交互过程都只是发生在客户端了。不需要通过网络传递信息数据,自然也就不需要网络了,我们可以称之为可离线使用的 web 应用。\n当客户端页面需要更新的数据的时候,我们并不需要重新从服务器端获取页面文件,只需要拿到必需的数据(目前多以 json 格式传递),并在客户端更新页面即可。\n如此,现代后端(前后端分离后)的职能渐渐明确了 - 为客户端提供所需的信息数据。\n通常情况下,后端开发人员通过网络或其他方式获取到客户端请求,通过一系列方式解析请求,转发请求到相应的处理逻辑,并将最终处理好的数据响应给客户端,便完成了整个过程。问题来了,如下:\n通过什么方式解析请求? 如何转发请求到相应的处理逻辑? 怎么编写相关的处理逻辑程序? 数据都存储在哪儿? 如何传递数据? …… 我们以常见的后端开发来描述一下大体的过程。客户端(用户)发来的请求,由 nginx 统一接收(此时 nginx 便是服务端代理,也就是所谓的反向代理),部分静态资源直接响应给客户端,动态资源则转发给相应的服务端程序(如 tomcat)来处理。\n正向、反向,都是从用户的角度出发来看的。\n不妨就以 tomcat 来举例,其支持运行适配相应版本 servlet api 的 java servlet 程序,用以接收处理转发自 nginx 的请求。是的,核心的服务端面程序逻辑都是写在 servlet 中的。servlet 负责接收请求,并从数据库获取相应数据,最终生成客户端请求所需的数据,响应给客户端。\n前端有一系列针对 html + css + javascript 的“魔改”,后端也出不了这个圈,spring 家族生态就是干这个活的。我们不需要自己去编写一个又一个的 servlet 程序,只需要关注具体的处理逻辑就好了,spring 最终会动态生成 servlet 程序供 tomcat 使用。\nspring 的核心是什么?ioc(控制反转)和依赖注入。不要被这些陌生的抽象名词吓到了,其本质不过是把本来需要开发者手动维护的模块体系全部交由 spring 去动态完成罢了。\n看,无论是直接编写 servlet ,还是通过 spring ,亦或是其他方式生成 servlet ,最终都会回归到响应基本的信息数据这一点。\n测试及运维部署 接口测试多数是由开发人员通过编写测试代码完成的。这里的测试,主要是指功能性测试,即产品应用整体是否达到了设计要求,是否运行稳定。\n一切都没有问题,就由运维人员部署到指定的服务器上,运行发布。\n总结 纵观 web 开发的技术分支多如牛毛,但其核心本质不过了了。我们要抓住本质,去理解、把握主要技术栈的实现原理,才能更好地了解现存技术,面对或是开发新的技术栈。\n","date":"2024-03-01","permalink":"https://aituyaa.com/web-%E5%BC%80%E5%8F%91%E7%9B%B8%E5%85%B3%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84%E9%9A%8F%E6%83%B3/","summary":"\u003cp\u003e与其它领域相比,Web 开发更像是一个全栈化的工程,其使用的技术基本上涵盖了目前大部分应用所需。\u003c/p\u003e","title":"web 开发相关体系结构随想"},]
[{"content":"文章转载自作者:肆拾貳_ https://www.bilibili.com/read/cv16298195/ 。(部分翻译问题,已作堪误。)\n![[assets/pasted image 20240228115412.png]]\n\u0026gt; stay hungry, stay foolish.\n相关视频资源:乔布斯在斯坦福大学毕业典礼上的演讲(中英字幕)哔哩哔哩bilibili\n经典段落 sometimes life hits you in the head with a brick. don\u0026rsquo;t lose faith. i\u0026rsquo;m convinced that the only thing that kept me going was that i loved what i did. you\u0026rsquo;ve got to find what you love. and that is as true for your work as it is for your lovers. your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work. and the only way to do great work is to love what you do. if you haven\u0026rsquo;t found it yet, keep looking. don\u0026rsquo;t settle. as with all matters of the heart, you\u0026rsquo;ll know when you find it. and, like any great relationship, it just gets better and better as the years roll on. so keep looking until you find it. don\u0026rsquo;t settle.\n有时候生活会给你当头一棒,不要失去信仰。唯一支撑着我一路走来的是我爱我所做的事,你必须找到你的真爱,对工作如此对爱人亦然。工作将占据你生命中很大的部分,只有相信自己所做的是伟大的工作,你才能活得快乐。而伟大的工作就源自你的爱,如果你还没有找到,继续寻找不要停下脚步,全心全意的寻找,当你遇到它时你就会明白,就像那些美好的爱情,随着岁月的流逝愈加醇美。所以继续寻找不要停下脚步。\nwhen i was 17, i read a quote that went something like: \u0026ldquo;if you live each day as if it was your last, someday you\u0026rsquo;ll most certainly be right.\u0026rdquo; it made an impression on me, and since then, for the past 33 years, i have looked in the mirror every morning and asked myself: \u0026ldquo;if today were the last day of my life, would i want to do what i am about to do today?\u0026rdquo; and whenever the answer has been\u0026quot;no\u0026quot; for too many days in a row, i know i need to change something.\n我 17 岁的时候读到过一句话: “如果你把每一天都当作是生命的最后一天,总有一天你会是对的。” 这话给我留下了深刻的印象。在那之后的 33 年里,每天早晨我都对着镜子问自己: “如果今天是我生命中的最后一天我还会做我今天要做的这些事吗?” 如果连续很多天答案都是“不”,我就知道需要改变了。\nremembering that i\u0026rsquo;ll be dead soon is the most important tool i\u0026rsquo;ve ever encountered to help me make the big choices in life. because almost everything – all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important. remembering that you are going to die is the best way i know to avoid the trap of thinking you have something to lose. you are already naked. there is no reason not to follow your heart.\n记住自己终将会死去是我最重要的自我检验,并帮助我做出了许多人生中的重要决定。因为几乎任何事,所有的期望、所有的荣耀、所有对失败的恐惧,一切都会在死亡面前淡去,只留下真正重要的东西。时刻提醒自己将会死去,便可以帮助你避开担心失去身外之物的恐惧。你本就一无所有,没有理由不去追随你的内心。\nyour time is limited, so don\u0026rsquo;t waste it living someone else\u0026rsquo;s life. don\u0026rsquo;t be trapped by dogma - which is living with the results of other people\u0026rsquo;s thinking. don\u0026rsquo;t let the noise of other\u0026rsquo;s opinions drown out your own inner voice. and most important, have the courage to follow your heart and intuition. they somehow already know what you truly want to become. everything else is secondary.\n光阴如梭(生命有限),所以,不要浪费时间过你不想要的生活,不要被教条束缚,那意味着按照别人的想法生活,不要被世人的喧嚣掩盖了你的心声,还有最重要的,要有跟随你的内心和直觉的勇气,它们知道你想做一个什么样的人,其他的一切都是次要的。\n“stay hungry. stay foolish.”\n求知若愚。\n\u0026gt; 以下是演讲正文\nthank you!\n感谢大家!\ni am honored to be with you today for your commencement from one of the finest universities in the world. truth be told, i never graduated from college, and this is the closest i\u0026rsquo;ve ever gotten to a college graduation. today i want to tell you three stories from my life. that\u0026rsquo;s it. no big deal. just three stories.\n我今天很荣幸能和你们一起参加这所世界上最好的大学的毕业典礼。说实话,我从未大学毕业,这该算是我一生中离大学毕业最近的一次了。今天我想向你们讲述我人生中的三个故事,没什么大不了的,只是三个故事而已。\n串联生命中的点点滴滴 🍎 the first story is about connecting the dots.\n第一个故事是关于串联生命中的点点滴滴。\ni dropped out of reed college after the first 6 months, but then stayed around as a drop-in for another 18 months or so before i really quit. so why did i drop out?\n我在里德大学只读了六个月就退学了,但之后仍作为旁听生呆了一年半后才最终离开。我为什么要退学呢?\nit started before i was born. my biological mother was a young, unwed college graduate student, and she decided to put me up for adoption. she felt very strongly that i should be adopted by college graduates, so everything was all set for me to be adopted at birth by a lawyer and his wife. except that when i popped out they decided at the last minute that they really wanted a girl.\n那要从我出生的时候讲起。我的生母是一个年轻的、没有结婚的大学毕业生。她决定让别人收养我,她坚定的认为应当找一对受过高等教育的夫妇。在我出生时她已经安排好让一个律师家庭收养我。但当我出生之时,律师夫妇突然决定想要个女孩。\nso my parents, who were on a waiting list, got a call in the middle of the night asking: \u0026ldquo;we have an unexpected baby boy; do you want him?\u0026rdquo; they said: \u0026ldquo;of course.\u0026rdquo; my biological mother later found out that my mother had never graduated from college and that my father had never graduated from high school. she refused to sign the final adoption papers. she only relented a few months later when my parents promised that i would someday go to college.\n所以在候选名单上我的养父母突然在半夜接到了一个电话\u0026quot;我们这儿多了一个男孩,你们想要他吗?“,他们回答 \u0026ldquo;当然!\u0026quot;。但是我生母随后发现,我的养母没有大学毕业,我的养父甚至高中就辍学了。她拒绝在最终的收养文件上签字。几个月后她才勉强同意,因为我的父母承诺一定会送我上大学。\nthis was a start in my life.\n这是我人生的开始。\nand 17 years later i did go to college. but i naively chose a college that was almost as expensive as stanford, and all of my working-class parents\u0026rsquo;savings were being spent on my college tuition. after six months, i couldn\u0026rsquo;t see the value in it. i had no idea what i wanted to do with my life and no idea how college was going to help me figure it out.\n十七年之后,我真的上了大学。但是我很天真地选择了一所几乎和斯坦福一样贵的学校,我的父母都是蓝领阶层,他们倾其所有资助我的学业。在六个月后,我已经看不到上学的价值。我不知道我真正想要什么,也不知道大学如何能帮我找到答案。\nand here i was spending all of the money my parents had saved their entire life. so i decided to drop out and trust that it would all work out ok. it was pretty scary at the time, but looking back it was one of the best decisions i ever made. the minute i dropped out i could stop taking the required classes that didn\u0026rsquo;t interest me, and begin dropping in on the ones that looked interesting.\n而我在这里上学,几乎花光了父母这一辈子的积蓄。所以我决定退学,相信车到山前必有路。我那时很害怕,但现在看来,那是我人生中做得最正确的决定。我退学的那一刻,就再也不用上毫无兴趣的必修课,然后我开始旁听那些我喜欢的课。\n:: 注意,老乔是退学了,但是他并没有停止学习,反而是更专注于自己万事兴趣的事物。\n\u0026gt; 2024-02-28 14:00\nit wasn\u0026rsquo;t all romantic. i didn\u0026rsquo;t have a dorm room, so i slept on the floor in friends\u0026rsquo; rooms, i returned coke bottles for the 5¢ deposits to buy food with, and i would walk the 7 miles across town every sunday night to get one good meal a week at the hare krishna temple. i loved it. and much of what i stumbled into by following my curiosity and intuition turned out to be priceless later on. let me give you one example:\n但是事情并没有想象的那样容易,我无处可住,只能睡在朋友宿舍的地板上,我去捡五美分的可乐罐,仅仅为了填饱肚子。每周日晚上,我走七英里的路,穿过小镇,只为能在 krishna 教堂(注:位于纽约 brooklyn 下城)吃顿像样的饭菜。我乐此不疲。这些跟随好奇心和直觉所做的事情后来都被证明是无价之宝,我给你们举个例子:\nreed college at that time offered perhaps the best calligraphy instruction in the country. throughout the campus every poster, every label on every drawer, was beautifully hand calligraphed. because i had dropped out and didn\u0026rsquo;t have to take the normal classes, i decided to take a calligraphy class to learn how to do this.\n那时的里德大学拥有也许是全美最好的书法课。校园里的每一幅海报,每一个抽屉的标签都是漂亮的美术字。因为我退学了不必去上必修课,所以我决定去学一门书法课,学习怎么写出这样的字体。\ni learned about serif and san serif typefaces, about varying the amount of space between different letter combinations, about what makes great typography great. it was beautiful, historical, artistically subtle in a way that science can\u0026rsquo;t capture. and i found it fascinating.\n我学到衬线和无衬线字体,英文字母组合的间距规则,学到了是什么让这些印刷体变得如此美丽。那种美感,给人的历史感和艺术享受,是科学无法捕捉到的。我被完全吸引了。\nnone of this had even a hope of any practical application in my life. but ten years later, when we were designing the first macintosh computer, it all came back to me. and we designed it all into the mac. it was the first computer with beautiful typography. if i had never dropped in on that single course in college, the mac would have never had multiple typefaces or proportionally spaced fonts.\n当时看起来这些东西好像对我的人生来说没有任何实用价值。但是十年之后,当我们在设计第一台 macintosh 电脑的时候,它一下子浮现了出来。我们将这些东西全部设计进了 mac ,于是漂亮的印刷体第一次出现在电脑上。如果我当年没有旁听这一门课,苹果就不会有如此丰富的字体以及漂亮的字间距。\nand since windows just copied the mac, its likely that no personal computer would have them. if i had never dropped out, i would have never dropped in on this calligraphy class, and personal computers might not have the wonderful typography that they do. of course it was impossible to connect the dots looking forward when i was in college. but it was very, very clear looking backwards ten years later.\n鉴于微软只知道抄袭苹果,估计所有个人电脑都不会有这些。如果我没有退学,没有旁听这门书法课,也许所有电脑都不会有如此美丽的印刷体。当然在大学的时候我不可能预见到它们之间的联系,但今天回首往事一切都非常明了。\nagain, you can\u0026rsquo;t connect the dots looking forward; you can only connect them looking backwards. so you have to trust that the dots will somehow connect in your future. you have to trust in something - your gut, destiny, life, karma, whatever. because believing in the dots will connect down the road will give you the confidence to follow your heart even when they leave you off the well-worn path. and it has made all the difference.\n同样的,你们也无法预知未来,只有回头看时才会发现它们的关系,所以你必须相信你现在获得的点滴会在未来连结起来。你必须相信一些东西:直觉、命运、人生、因果,什么都没关系,因为如果你相信这些点滴会连接起你未来的道路,你就会拥有跟随自己内心的自信,即使你的选择不被主流认同,这将使一切都变得不一样。\n:: 厚积薄发,不断积累,水到渠成。 \u0026gt; 2024-02-28 14:07\n爱和损失 🍎 my second story is about love and loss.\n我的第二个故事是关于爱和损失的。\ni was lucky. i found what i loved to do early in life. woz and i started apple in my parents garage when i was 20. we worked hard, and in 10 years apple had grown from just the two of us in a garage into a billion company with over 4000 employees. we just released our finest creation - the macintosh - a year earlier, and i had just turned 30.\n我很幸运,在很早的时候就找到了喜欢做的事情。20 岁的时候沃茨和我在车库里面开创了苹果公司。我们努力地工作,用了仅仅 10 年时间,苹果就从只有两个穷小子发展成超过 4000 人价值 20 亿的公司。那个时候我们最棒的产品—— macintosh 刚刚推出一年,而我刚刚 30 岁,这时我被解雇了。\nhow can you get fired from a company you started? well, as apple grew we hired someone who i thought was very talented to run the company with me, and for the first year or so things went well. but then our visions of the future began to diverge and eventually we had a falling out. when we did, our board of directors sided with him. so at 30 i was out. and very publicly out. what had been the focus of my entire adult life was gone, and it was devastating.\n你怎么可能被自已创立的公司解雇呢?在苹果快速成长的时候,我们请了一个我当时认为很有才能的家伙,第一年一切都很顺利。但后来我们的看法发生了分歧,我们吵翻了。而这时董事会站在了他那一边,所以在三十岁的时候, 我被炒了。所以,而立之年,我在众目睽睽下被开除了。我整个成年生活的重心突然丢失了,这是个致命的打击。\ni really didn\u0026rsquo;t know what to do for a few months. i felt that i had let the previous generation of entrepreneurs down - that i had dropped the baton as it was being passed to me. i met with david packard and bob noyce and tried to apologize for screwing up so badly.\n有好几个月的时间我不知所措,我感到自己让上一代的创业者们失望了——我弄丢了他们的接力棒。我跟 david packard 与 bob noyce 见面,试图为我的糟糕表现向他们道歉。\ni was a very public failure, and i even thought about running away from the valley. but something slowly began to dawn on me – i still loved what i did. the turn of events at apple had not changed that one bit. i had been rejected, but i was still in love. and so i decided to start over.\n我变成了人尽皆知的失败者,甚至想逃离硅谷。然而有一件事开始将我慢慢带回,那就是——我仍然爱着我所从事的行业。在苹果的失败丝毫没有改变这一点,虽然被赶走了,但是我的爱还在。于是我决定从头再来。\n:: 挫折面前,唯有热爱。 \u0026gt; 2024-02-28 14:14\ni didn\u0026rsquo;t see it then, but it turned out that getting fired from apple was the best thing that could have ever happened to me. the heaviness of being successful was replaced by the lightness of being a beginner again, less sure about everything. it freed me to enter one of the most creative periods of my life.\n当时很难想象,事实上,离开苹果是我这辈子里发生的最好的事情。成功的沉重被重新出发的轻盈所代替,我不再执迷于任何事情,这让我变得自由,并进入了人生中最富创造动的时期。\nduring the next five years, i started a company named next, another company named pixar, and fell in love with an amazing woman who would become my wife. pixar went on to create the worlds first computer animated feature film, toy story, and is now the most successful animation studio in the world.\n在接下来的 5 年里, 我创立了一家名叫 next 的公司和另一家名叫皮克斯的公司,与一位美丽的女人恋爱——她后来成为了我的妻子。皮克斯制作了世界上第一个全电脑制作的动画电影《玩具总动员》,如今已是世界上最知名的动画工作室。\nin a remarkable turn of events, apple bought next, i returned to apple, and the technology we developed at next is at the heart of apple\u0026rsquo;s current renaissance. and laurene and i have a wonderful family together.\n后来阴差阳错苹果收购 next,我重返苹果。而我们在 next 开发的技术,对苹果的复兴起到了关键作用,laurene 也跟我也组建了幸福的家庭。\ni\u0026rsquo;m pretty sure none of this would have happened if i hadn\u0026rsquo;t been fired from apple. it was awful tasting medicine, but i guess the patient needed it. sometimes life hits you in the head with a brick. don\u0026rsquo;t lose faith. i\u0026rsquo;m convinced that the only thing that kept me going was that i loved what i did. you\u0026rsquo;ve got to find what you love. and that is as true for your work as it is for your lovers. your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work. and the only way to do great work is to love what you do. if you haven\u0026rsquo;t found it yet, keep looking. don\u0026rsquo;t settle. as with all matters of the heart, you\u0026rsquo;ll know when you find it. and, like any great relationship, it just gets better and better as the years roll on. so keep looking until you find it. don\u0026rsquo;t settle.\n我很确定,如果不被苹果开除,所有这一切都不会发生。良药苦口,但我想这正是我需要的。有时候生活会给你当头一棒,不要失去信仰。唯一支撑着我一路走来的是我爱我所做的事,你必须找到你的真爱,对工作如此对爱人亦然。工作将占据你生命中很大的部分,只有相信自己所做的是伟大的工作,你才能活得快乐。而伟大的工作就源自你的爱,如果你还没有找到,继续寻找不要停下脚步,全心全意的寻找,当你遇到它时你就会明白,就像那些美好的爱情,随着岁月的流逝愈加醇美。所以继续寻找不要停下脚步。\n死亡 🍎 my third story is about death.\n我的第三个故事是关于死亡。\nwhen i was 17, i read a quote that went something like: \u0026ldquo;if you live each day as if it was your last, someday you\u0026rsquo;ll most certainly be right.\u0026rdquo; it made an impression on me, and since then, for the past 33 years, i have looked in the mirror every morning and asked myself: \u0026ldquo;if today were the last day of my life, would i want to do what i am about to do today?\u0026rdquo; and whenever the answer has been\u0026quot;no\u0026rdquo; for too many days in a row, i know i need to change something.\n我 17 岁的时候读到过一句话: “如果你把每一天都当作是生命的最后一天,总有一天你会是对的。” 这话给我留下了深刻的印象。在那之后的 33 年里,每天早晨我都对着镜子问自己: “如果今天是我生命中的最后一天我还会做我今天要做的这些事吗?” 如果连续很多天答案都是“不”,我就知道需要改变了。\n:: 以终为始,要事第一。我们都知道人终有一死,却总感觉自己可以永生。 \u0026gt; 2024-02-28 14:17\nremembering that i\u0026rsquo;ll be dead soon is the most important tool i\u0026rsquo;ve ever encountered to help me make the big choices in life. because almost everything – all external expectations, all pride, all fear of embarrassment or failure - these things just fall away in the face of death, leaving only what is truly important. remembering that you are going to die is the best way i know to avoid the trap of thinking you have something to lose. you are already naked. there is no reason not to follow your heart.\n记住自己终将会死去是我最重要的自我检验,并帮助我做出了许多人生中的重要决定。因为几乎任何事,所有的期望、所有的荣耀、所有对失败的恐惧,一切都会在死亡面前淡去,只留下真正重要的东西。时刻提醒自己将会死去,便可以帮助你避开担心失去身外之物的恐惧。你本就一无所有,没有理由不去追随你的内心。\nabout a year ago i was diagnosed with cancer. i had a scan at 7:30 in the morning, and it clearly showed a tumor on my pancreas. i didn\u0026rsquo;t even know what a pancreas was. the doctors told me this was almost certainly a type of cancer that is incurable, and that i should expect to live no longer than three to six months. my doctor advised me to go home and get my affairs in order, which is doctor\u0026rsquo;s code for prepare to die. it means to try to tell your kids everything you thought you\u0026rsquo;d have the next 10 years to tell them in just a few months. it means to make sure everything is buttoned up so that it will be as easy as possible for your family. it means to say your goodbyes.\n大约一年前我被诊断出患有癌症。我在早晨七点半做了一次扫描,清楚显示出我的胰腺里有个肿瘤。我之前连胰腺是什么都不知道,医生告诉我,胰腺癌基本上无法治愈,我可能只剩下三到六个月的时间。医生让我回家“安排一下自己的事情“,这是医生对所有临终病人的话,这意味着对于你的孩子们,你要把未来十年的话在几个月里说完,意味着做好所有的准备,让你的家人尽可能的轻松一些,意味着跟所有人说再见。\ni lived with that diagnosis all day. later that evening i had a biopsy, where they stuck an endoscope down my throat, through my stomach and into my intestines, put a needle into my pancreas and got a few cells from the tumor. i was sedated, but my wife, who was there, told me that when they viewed the cells under a microscope the doctors started crying because it turned out to be a very rare form of pancreatic cancer that is curable with surgery. i had the surgery and i\u0026rsquo;m fine now.\n我一整天都在想着这个诊断,那天傍晚我做了一个活体检查,医生将一个内窥镜伸进喉咙,通过我的胃进入肠道,用一根针在我的肿瘤上取了一些细胞,我当时是被麻醉的但妻子告诉我,当医生在显微镜下观察细胞的时候,他们喜极而泣,原来这是一种非常罕见的,可以通过手术治愈的癌症,我接受了这个手术现在我痊愈了。\nthis was the closest i\u0026rsquo;ve been to facing death, and i hope its the closest i get for a few more decades. having lived through it, i can now say this to you with a bit more certainty than when death was a useful but purely intellectual concept.\n这是我最接近死亡的时刻,希望这也是未来几十年里最近的一次。与死神擦肩而过,让我更加确信一点,因为对我来说死亡不再只是一个抽象的概念。\nno one wants to die. even people who want to go to heaven don\u0026rsquo;t want to die to get there. and yet death is the destination we all share. no one has ever escaped it. and that is as it should be, because death is very likely the single best invention of life. it is life\u0026rsquo;s change agent. it clears out the old to make way for the new. right now the new is you, but someday not too long from now, you will gradually become the old and be cleared away. sorry to be so dramatic, but it is quite true.\n没人愿意死,就算那些想上天堂的人也不想为此去死。但是死亡是每个人共同的终点,从来没有人能逃脱它。因为死亡就是生命最好的发明,是生命更迭的动力,它去除老旧、拥抱新生。现在你们就是新生的力量,但是用不了太久,你们会变成老旧然后被渐渐清除掉。抱歉说得夸张了点,但这都是事实。\nyour time is limited, so don\u0026rsquo;t waste it living someone else\u0026rsquo;s life. don\u0026rsquo;t be trapped by dogma - which is living with the results of other people\u0026rsquo;s thinking. don\u0026rsquo;t let the noise of other\u0026rsquo;s opinions drown out your own inner voice. and most important, have the courage to follow your heart and intuition. they somehow already know what you truly want to become. everything else is secondary.\n光阴如梭(生命有限),所以,不要浪费时间过你不想要的生活,不要被教条束缚,那意味着按照别人的想法生活,不要被世人的喧嚣掩盖了你的心声,还有最重要的,要有跟随你的内心和直觉的勇气,它们知道你想做一个什么样的人,其他的一切都是次要的。\nwhen i was young, there was an amazing publication called the whole earth catalog, which was one of the bibles of my generation. it was created by a fellow named stewart brand not far from here in menlo park, and he brought it to life with his poetic touch. this was in the late 1960\u0026rsquo;s, before personal computers and desktop publishing, so it was all made with typewriters, scissors, and polaroid cameras. it was sort of like google in paperback form, 35 years before google came along: it was idealistic, and overflowing with neat tools and great notions.\n当我年轻的时候,有一本非常棒的杂志叫《全球概览》,它是我们那一代人的圣经之一。它的创办人叫 stewart brand,就在离这不远的 menlo park,他以艺术般的手法为刊物注入了生命。那是 60 年代末,早在在个人电脑出现以前,所以这本书完全是用打字机、剪刀还有老相机制作的,它就像书籍版的 google,在 google 出现的 35 年之前,它充斥着理想的色彩、简洁的工具和伟大的想法。\nstewart and his team put out several issues of the whole earth catalog, and then when it had run its course, they put out a final issue. it was the mid-1970s, and i was your age. on the back cover of their final issue was a photograph of an early morning country road, the kind you might find yourself hitchhiking on if you were so adventurous. beneath it were the words: \u0026ldquo;stay hungry. stay foolish.\u0026rdquo; it was their farewell message as they signed off. \u0026ldquo;stay hungry. stay foolish. \u0026quot; and i have always wished that for myself. and now, as you graduate to begin anew, i wish that for you.\nstewart 和他的伙伴出版了几期的《全球概览》,当它完成了自己使命的时候,他们出版了最后的一期。那是 70 年代中期,我正是你们现在的年纪,最后一期的封底上,是一幅清晨乡村公路的照片,是那种你去搭便车旅行就会看到的景色,照片下面有一句话 “stay hungry. stay foolish.” 这是他们停刊的告别语,“stay hungry. stay foolish.” 。我总是以此自勉,在你们即将展开新的旅程之时,我将这句话也赠予你们,“stay hungry. stay foolish.”。\nthank you all very much.\n非常感谢大家。\n","date":"2024-02-28","permalink":"https://aituyaa.com/%E4%B9%94%E5%B8%83%E6%96%AF-%E6%96%AF%E5%9D%A6%E7%A6%8F%E5%A4%A7%E5%AD%A6%E6%BC%94%E8%AE%B2/","summary":"\u003cp\u003e文章转载自作者:肆拾貳_ \u003ca href=\"https://www.bilibili.com/read/cv16298195/\"\u003ehttps://www.bilibili.com/read/cv16298195/\u003c/a\u003e 。(部分翻译问题,已作堪误。)\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20240228115412.png]]\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; Stay Hungry, Stay Foolish.\u003c/code\u003e\u003c/p\u003e","title":"乔布斯·斯坦福大学演讲"},]
[{"content":"夕阳西下之时,除了暮色和晚霞,回顾一天的生活,是否充实呢?\n时间管理 就是用技巧、技术和工具帮助人们完成工作,实现目标。时间管理并不是要把所有事情做完,而是更有效的运用时间。时间管理的目的除了要决定该做些什么事情之外,另一个很重要的目的也是决定什么事情不应该做;时间管理不是完全的掌控,而是降低变动性。时间管理最重要的功能是透过事先的规划,做为一种提醒与指引。\n发展阶段 时间管理的发展可以分为四个阶段:\n第一代理论着重利用便条与备忘录,在忙碌中调配时间与精力; 第二代理论强调行事历与日程表,反映出时间管理已注意到规划未来的重要; 第三代理论正是目前流行的优先级观念。也就是依据轻重缓急设定短、中、长期目标; 第四代理论跳出“时间管理”的定义,主张把重心放在维持产出与产能的平衡上。 最近有一些观点(2001,david allen)指出,“时间”和“管理”连在一起会误导人们以为“时间管理”是要对时间进行管理,而时间是无法进行管理的。这一概念实际上意味着它是“管理我们自己的活动,以确保它们是在规定期限内完成,这是一个难以管理的持续的资源”。要管理时间,提升工作效率,其实也与管理自己的情绪与内心有关,利用一点仪式,让身体知道准备进入工作状态,或者为自己提供一些诱因,都能更有效运用时间,提升工作效率。\n:: 所谓“时间管理”的本质,其实是“自我管理”。 \u0026gt; 2024-02-28 10:16\n流行体系 尽管去做 尽管去做(getting things done,gtd)的基本理论是把一个人搁在脑中的所有事情从大脑中移出来,记录到纸上。这样,大脑便会不被多余的事情打搅而集中于现在所需完成的事情上面。 同时建立一个可靠的个人时间管理系统,通过收集、整理、组织、回顾与行动五个步骤来有条不紊而高效率地处理个人生活中的所有事情。 强调从小处入手,强调速度和效率,建立了一套工具和方法。 gtd 的创始人:戴维·艾伦(david allen) 对于 gtd,我在另一篇文章《 [[gtd 管理系统]] 》中作了更为详细的阐述。\n要事第一 ![[assets/pasted image 20230526092649.png|550]]\n要事第一(first things first,ftf)是以原则为中心的方法,不是教导读者如何提高速度,而是给读者一个指南针为读者指明方向。 强调根据七个习惯的原则和自己的使命、价值观,来规划自己的人生,根据这些原则决定哪些事情是真正对实现自己的人生意义重大的要事,然后处理要事。 强调从大局着眼,强调效果和方向,更多的是原则。 ftf 的创始人:史蒂芬·柯维 :: 史蒂芬·柯维的“高效能人士的 7 个习惯”系列确实是不错的书籍。 \u0026gt; 2024-02-28 10:23\n艾森豪威尔法则 艾森豪威尔法则是源自一般认为是德怀特·艾森豪威尔所说的话:“我手中的待办事项可分为两个种类,‘紧急’和‘重要’,重要的事情永远不会紧急,紧急的事情不会重要。”\n利用艾森豪威尔决策法则,各个待办事项可依照“重要/不重要”和“紧急/不紧急”来划分所在的决策矩阵象限。\n待办事项详细的区分方式如下:\n位于“紧急且重要”的待办事项须立即亲自开始动手完成。例如:危机、截止日期迫在眉睫。 为“重要但不紧急的事情”设立完成日,并且亲自完成 。例如:人际关系、订计划、休闲放松等。 “不重要但紧急”的事情可委托他人代办。 “不重要又不紧急”的事情就移除不做。例如:单纯浪费时间的事情。 时间“四象限”法 时间“四象限”法是美国的管理学家史蒂芬·柯维提出的一个时间管理的理论,把工作按照重要和紧急两个不同的程度进行了划分,基本上可以分为四个“象限”。\n:: 是柯维在艾森豪威尔法则的基础上扩展而来的吗? \u0026gt; 2024-02-28 10:34\n![[assets/pasted image 20240228095532.png|550]]\n按处理顺序划分:先是重要且紧急的,接着是重要但不紧急的,再到紧急但不重要的,最后才是不紧急也不重要的。\n“四象限”法的关键在于第二和第三类的顺序问题,必须非常小心区分。另外也要注意划分好第一和第三类事,都是紧急的,分别就在于前者带来价值,实现某种重要目标,而后者不能。\n![[assets/pasted image 20240228104332.png|550]]\n第一象限 a:重要且紧急 有比较急迫的时间限制,如果没有按期完成会有严重影响的任务。如事故危机,紧急救火的任务,或者一些重要的前置任务。只能优先马上去做,这类任务越少越好。如果较多,则说明风险控制不足、规划不够。\n这是考验我们的经验、判断力的时刻,也是可以用心耕耘的园地。如果荒废了,我们很可能会变成行尸走肉。但我们也不能忘记,很多重要的事都是一拖再拖或事前准备不足,而变成迫在眉睫的。\n该象限的本质是缺乏有效的工作计划导致本处于第二象限“重要不紧急”的事情转变过来的,这也是传统思维状态下的管理者的通常状况,就是“忙”。\n第二象限 b:重要不紧急 收益很大,或长期收益,非常重要,对时间要求不是非常急迫,可以自行安排的任务。如团队规划、工作计划,学习培训、健康锻炼,团队建设、流程规范、工作总结复盘、风险管理等都是重要的事情,不需要立刻就要完成,但如果做得不好或缺失就很容易导致紧急的事情发生。\n主要的精力和时间都在这里,做好规划和风险管理。这样才可以未雨绸缪,防患于未然。做的真好,紧急的事情就会越少。\n也就是说,第二象限的事情和生活品质是息息相关的,包括学习新技能、建立人际关系、保持身体健康、长期的规划、问题的发掘与预防、参加培训、向上级提出问题处理的建议等等事项。荒废这个领域将使第一象限日益扩大,使我们陷入更大的压力,在危机中疲于应付。反之,多投入一些时间在这个领域有利于提亮实践能力,缩小第一象限的范围。做好事先的规划、准备与预防措施,很多急事将无从产生。\n这个领域的事情不会对我们造成催促力量,所以必须主动去做,这是发挥个人领导力的领域。更是传统低效管理者与高效管理者的重要区别标志,建议管理者要把 80% 的精力投入到该象限的工作,以使第一象限的“急”事无限 变少,不再瞎“忙”。\n第三象限 c:紧急不重要 临时性、突发性的人或事,需要马上处理,但做了其实收益不大。如某些会议、应付的报告、突发的沟通或支持等。调低优先级、延后或者授权给他人去做。\n此类事件表面看似第一象限,因为迫切的呼声会让我们产生“这件事很重要”的错觉 - 实际上就算重要也是对别人而言。我们花很多时间在这个里面打转,自以为是在第一象限,其实不过是在满足别人的期望与标准。\n第四象限 d:不紧急也不重要 没什么时间要求、也没有什么收益的事情,或者实际上都和你没什么关系。如无聊、没有意义的谈话、会议、酒局,一些不合理的工作安排,或者一些推销、刷手机等无聊琐事。这类事情尽量不做!对内自控,对外要学会说 no!\n简而言之,此类事件就是浪费生命,所以根本不值得花半点时间在这个象限。但我们往往在第一、三象限来回奔走,忙得焦头烂额,不得不在第四象限去疗养一番再出发。这部分范围不见得都是休闲活动,因为真正有创造意义的休闲活动是很有价值的。然而像阅读令人上瘾的无聊小说、毫无内容的电视节目、办公室聊天等。这样的休息不但不是为了走更长的路,反而是对身心的毁损,刚开始时也许有滋有味,到后来你就会发现其实是很空虚的。\n本章小结 “高效能人士总是避免陷入第三和第四类事务,因为无论是否紧急,这些事情都是不重要的,他们还通过花费更多时间在第二类事务来减少第一类事务的数量。” \u0026ndash; 史蒂芬·柯维\n总结来说,就是 重视 b 区,减少 a 区、c 区,干掉 d 区 。我们也要辩证地看问题,任务的重要程度、紧急程度都是动态的,相互关联的。重要的事情如果处理不好就会转变成紧急的事情,紧急的事情可以复盘总结转为重要的事情。\n![[assets/pasted image 20240228112733.png|500]]\n不同类的事情要如何去安排,时间如何加以调整、运用,这些事情某种程度上决定了你的生活风格,有四种可以参考。\nb 从容人 a 压力人 d 懒人 c 无用人 压力人(a)认为每样事情都很重要、很紧迫,应该做的是有条有理、有条不紊地去完成你的工作,应该学习投资你的时间,去做一个从容不迫的人(b)。千万不要去做那种很紧急但不重要的事务,那种叫做没有用的人(c),总是在应付一些杂事。当然,更不要做不重要也不紧急的事情,那是懒人(d)。注重哪一类事务,你就成为哪一类人。\n如何区分事务的重要、紧急呢?它需要对业务、目标、客户非常了解,并且有较强的分析和判断能力,需要站在更高的视角去思考和判断。这里提供两个简单的原则:\n重要程度:看收益是否足够大,收益越大就越重要,这里收益要综合短期、长期一起来看。越重要的任务也就越需要给予相匹配的优先级、资源和关注度。 紧急程度:看损失是否足够大,损失越大越紧急,以止损为核心目的进行安排工作。相反,如果损失可按或损失很小,紧急程度就低。 番茄工作法 番茄工作法(英语:pomodoro technique)是一种时间管理方法,在 1980 年代由 francesco cirillo 创立。该方法使用一个定时器来分割出一个一般为 25 分钟的工作时间和 5 分钟的休息时间,而那些时间段被称为 pomodoros,为意大利语单词 pomodoro(中文:番茄)。\n:: 相对于 gtd 和四象限时间法来说,它是一个更加具体到实际行动中的方法。 \u0026gt; 2024-02-28 11:36\n番茄工作法有五个步骤:\n决定需要先完成的任务; 设定番茄工作法定时器至 n 分钟(通常为 25 分钟); 持续工作直至定时器提示,记下一个番茄; 短暂休息 3-5 分钟; 每四个番茄,休息 15-30 分。 番茄工作法的关键是规划,追踪,记录,处理,以及可视化。在规划阶段,任务被根据优先级排入“每日待办清单”。 这允许用户预计每个任务的工作量。当每个番茄时结束后,成果会被记录下来以提高参与者的成就感并为未来的自我观察和改进提供原始数据。\n番茄时意指每个工作时段的时长。当任务完成后,所有番茄计时器剩下的时间会被用于过度学习。短休息时间可以辅助达到心理学上的同化作用,3-5 分钟的短休息间隔开每个番茄工作时段。四个番茄工作时组成一组。一个 15-50 分钟的长休息间隔开每组作业\n这一时间管理技术的本质目的是减少内生和外在的干扰对意识流的影响。一个单位的番茄工作时不可再细分。当在番茄工作时中被打断的情况下,只可能有两种情况:干扰的活动被推迟(告知 - 协商 - 安排日程 - 回访),或者当前的番茄工作时废弃,必须重新开始。\n总结 一个人的时间和精力都是有限的,更好的利用和分配它们,才能让我们在有限的生命中更好地完善自我,实现人生价值。\n参考资料 时间管理 - 维基百科,自由的百科全书 时间“四象限”法 - mba智库百科 (mbalib.com) 计划安排:时间管理的“四象限法则” - 知乎 番茄工作法 - 维基百科,自由的百科全书 ","date":"2024-02-28","permalink":"https://aituyaa.com/%E6%97%B6%E9%97%B4%E7%AE%A1%E7%90%86/","summary":"\u003cp\u003e夕阳西下之时,除了暮色和晚霞,回顾一天的生活,是否充实呢?\u003c/p\u003e","title":"时间管理"},]
[{"content":"我的世界(minecraft)记录 123 \u0026hellip;\n游戏记录 1.18.2 原版生存 资源汇总 相关资源站点 mc百科|最大的minecraft中文mod百科 中文 minecraft wiki minecraft(我的世界)中文论坛——minecraft中文站,我的世界中文论坛,我的世界论坛 - minecraft mods \u0026amp; modpacks - curseforge planet minecraft community | creative fansite for everything minecraft! 我的世界中文站 - 国内知名minecraft中文主题网站 材质包和光影 i.e. texture \u0026amp; sharders\n光影:\ncomplementary ✅ bsl 材质包:\nfaithful 32x faithful 64x 模组 i.e. mods\njei(just enough items)物品管理器 rech(roughly enough characters)- 支持 jei 搜索中使用拼音模糊搜索 replaymod - minecraft replay mod - craft your moment 游戏内视角录制回放 replay mod 的使用说明书 - 啾译版 - minecraft - mc - 哔哩哔哩 【minecraft】从零开始,replaymod超详细全面基础教程_哔哩哔哩bilibili_我的世界 replay mod教程-哔哩哔哩_bilibili ht\u0026rsquo;s treechop 砍树(通过反复破坏单个方块的方式来砍倒树木) 前期材料收集 选什么版本 [minecraft]萌新入坑要选什么版本?|各大主流版本推荐 - 哔哩哔哩 (2 条消息) java版的minecraft哪个版本mod之类的会比较多比较好玩? - 知乎 [mc]我的世界原版 (minecraft)更新日志 - mc 百科|最大的 minecraft 中文 mod 百科 我的世界minecraft历代版本回顾(2009年——2023年)我的世界 minecraft 的版本及适配终端很多,用户群体比较大的是 java 版和基岩版。这里选择前者,无它,因为一开始入坑就是 java 版。\njava 版版本记录(启动器) - pre-classic(2009年5月10日-2009年5月16日) - classic(2009年5月17日-2009年12月23日) - indev(2009年12月23日-2010年2月27日) - infdev(2010年2月27日-2010年6月30日) - alpha(2010年6月30日-2010年12月20日) - beta(2010年12月20日-2011年11月18日) - 正式版或java版(2011年11月18日至今) 开发版本(2010年10月29日至今) 基岩版版本记录 - 携带版alpha(2011年8月16日-2016年12月19日) - 携带版(2016年12月19日-2017年9月20日) - 基岩版(2017年9月20日至今) 开发版本(2013年11月22日至今) minecraft:java 版(minecraft: java edition)是 minecraft 的最初版本,由 mojang studios 为 windows、macos 与 linux 开发。\njava 版有自己的启动器。除了最新的版本和最新的快照之外,大多数以前版本的 java 版本也可以通过启动器来启动。启动器也允许单独配置启动文件,这对 mod、开发版本和旧版本来说非常友好。\njava 版的代码比其他版本来说更容易修改,同时由于加入了混淆映射表,所以它是迄今为止 mod 和自定义服务器最多的版本。不过 java 版内置的 realms 是一个单独的服务,它和基岩版的不一样。\n“我觉得,一个简单而且充满生机的游戏,很有潜力成为一个真正的好游戏,而且它可以随时修改我想要的东西并添加我想要添加的东西。” \u0026ndash; notch 评论于 minecraft.net\n下面,让我们来看一下更详细的 java版版本记录 - minecraft wiki。\n- 1.0 冒险更新 - 1.1 1.2 1.3 - 1.4 骇人更新 - 1.5 红石更新 - 1.6 马匹更新 - 1.7 改变世界的世界 - 1.8 缤纷更新 - 1.9 战斗更新 - 1.10 炙霜更新 - 1.11 探险更新 - 1.12 多彩世界更新 - 1.13 水域更新 - 1.14 村庄与掠夺 - 1.15 嗡嗡蜂群 - 1.16 下界更新 - 1.17 洞穴与悬崖(第一部分) - 1.18 洞穴与悬崖(第二部分) - 1.19 荒野更新 - 1.20 足迹与故事 :: 版本太多了,也不好选哦……\n","date":"2024-01-04","permalink":"https://aituyaa.com/mclogs/","summary":"\u003cp\u003e我的世界(Minecraft)记录 123 \u0026hellip;\u003c/p\u003e","title":"mclogs"},]
[{"content":"《太上老君说常清静经》仅三百九十一字。是道教炼养术重要资料之一。《清静经》正文起首一句就是“大道无形”而定了全经格调。经文不讲有为的修养方法,而是要人从心地下手,以“清静”法门去澄心遣欲,去参悟大道。经中以发挥“清静”两字为主,简明地叙述了道家修心养性的基本原则。\n“夫人神好清,而心扰之;人心好静,而欲牵之。”要去除这些牵扰,获得“清静”,必须“遣其欲而心自静,澄其心而神自清”。达到“心无其心”、“形无其形”、“物无其物”,“唯见于空”的境界。但是,有“空”存在,还不究竟,还要“观空亦空”、“所空既无”、“无无既无”才能“湛然常寂”。“寂无所寂,欲岂能生,欲既不生,即是真静”,直至进入“常清静”的境界,如此“渐入真道”“名为得道”。\n经文 老君曰:大道无形,生育天地;大道无情,运行日月;大道无名,长养万物;吾不知其名,强名曰道。夫道者:有清有浊,有动有静;天清地浊,天动地静。男清女浊,男动女静。降本流末,而生万物。清者浊之源,动者静之基。人能常清静,天地悉皆归。 夫人神好清,而心扰之;人心好静,而欲牵之。常能遣其欲,而心自静,澄其心而神自清。自然六欲不生,三毒消灭。所以不能者,为心未澄,欲未遣也。能遣之者,内观其心,心无其心;外观其形,形无其形;远观其物,物无其物。三者既悟,唯见於空;观空亦空,空无所空;所空既无,无无亦无;无无既无,湛然常寂;寂无所寂,欲岂能生?欲既不生,即是真静。真常应物,真常得性;常应常静,常清静矣。如此清静,渐入真道;既入真道,名为得道,虽名得道,实无所得;为化众生,名为得道;能悟之者,可传圣道。\n老君曰:上士无争,下士好争;上德不德,下德执德。执著之者,不名道德。众生所以不得真道者,为有妄心。既有妄心,即惊其神;既惊其神,即著万物;既著万物,即生贪求;既生贪求,即是烦恼。烦恼妄想,忧苦身心。但遭浊辱。流浪生死,常沉苦海,永失真道。真常之道,悟者自得,得悟道者,常清静矣。\n仙人葛翁曰:吾得真道,曾诵此经万遍。此经是天人所习,不传下士。吾昔受之于东华帝君,东华帝君受之于金阙帝君,金阙帝君受之于西王母。西王母皆口口相传,不记文字。吾今于世,书而录之。上士悟之,升为天官;中士修之,南宫列仙;下士得之,在世长年。游行三界,升入金门。\n左玄真人曰:学道之士,持诵此经,即得十天善神,拥护其神。然后玉符保神,金液炼形。形神俱妙,与道合真。\n正一真人曰:人家有此经,悟解之者,灾障不干,众圣护门。神升上界,朝拜高尊。功满德就,相感帝君。诵持不退,身腾紫云。\n注解 老君曰:大道无形,生育天地;大道无情,运行日月;大道无名,长养万物;吾不知其名,强名曰道。\n老子说:大道本来无有形象,但是能够生养天地。本来无有情感,可是能够运转日月的周流。本来没有名称可以标记,但是能够燮理阴阳,生发消长养成天地间的万物。如此玄妙,我不知道他的名字是什么,又怕后人无所适从,就依据天理的推测,起了一个定而不可移的名字,就叫作“道”。\n夫道者:有清有浊,有动有静;天清地浊,天动地静。男清女浊,男动女静。降本流末,而生万物。\n这包罗万象的“道”,无微不至,无所不合。也有纯洁,也有浑浊;也有动机,也有静意。不过在它没有发现出来的时候,凭俗人的眼光,是察不到的。到了生育天地,才知道天能周行不息,地能载物不辞。到了生人的时候,才知道男秉乾道而成,所以为清为动。女秉坤道而成,所以为静为浊。一切动静清浊的真理,都是由根本上分赋下来。阴阳相感,真理寓于其中,自然会生出天地间的万物来。\n清者浊之源,动者静之基。人能常清静,天地悉皆归。\n这玄妙无边的大道理,蕴含着静浊动静。可是细究清轻的天,却是重浊之地的起源。因为九重天,本是一气流动而属于动,却是地静之根基。人若是能够至诚无息的常清常静,自然连天地都要归纳在你的本性之中。\n夫人神好清,而心扰之;人心好静,而欲牵之。\n上帝所赐给人的元神,本来是纯洁无染的,虚空无碍的。因为受到人心的扰乱,便将灵明的元神给蒙蔽着了。人降生后的初心,无嗜无欲,也是很安静的。因为渐渐的长大起来,知识渐开,薰陶渐染,有了私欲之念,受到俗情物欲的牵动引诱,便将人心驱使的如失缰的劣马了。\n常能遣其欲,而心自静,澄其心而神自清。自然六欲不生,三毒消灭。\n如果能够永久的将一切私欲杂念格除了去,人心自能平静不妄动了。心不妄动,再加上澄清的功夫,元神自能洒洒脱脱,虚空寂静,一点挂碍也没有了,不用再去造作勉强。色、声、香、味、触、法,决不会再发生出来。贪、嗔、痴,也就消灭无踪了。\n所以不能者,为心未澄,欲未遣也。\n之所以不能做到这样的原因,是因为心中的杂念没有澄清,私欲没有除尽的缘故。\n能遣之者,内观其心,心无其心;外观其形,形无其形;远观其物,物无其物。三者既悟,唯见於空;观空亦空,空无所空;所空既无,无无亦无;无无既无,湛然常寂;寂无所寂,欲岂能生?欲既不生,即是真静。\n能够遣除物欲的人,回光返照。内观自心连心也没有了,哪还有什么欲呢?外观其形连形也没有了。远观其天下的物连物也没有了。心、形、物,其没有的原因,是因为自己一性圆明,超然物外。不为浮俗所缠,明了身心幻境,万物无常。不被声色所迷,朗然天真,妙意得存。这样玄之又玄的性理,真是不能一言而尽其意的。\n如果能空到极点,无所再空了,所余者是一个“无”字。然而这个“无”字亦应用功夫把它没有了,到此地步,这个“无”字功夫都不应存在。因为有所住,即不为真空,如何能生妙有呢?既然“无”的名字,“无”的功夫都没有了,这时方入于清幽常久的安静境地。但是寂静到了极点,亦不知其所寂了。此时的心性,已然离开尘俗的境界。私欲杂念,怎能够再生起来呢?私欲杂念之心,既然不能再生,这才是真实的清静。\n真常应物,真常得性;常应常静,常清静矣。\n用那真常不变的理,来应付万事万物,能够真实不虚的去作,便能得万物的性理。不但是平时如此,即便应于万事,亦是有事则应,事去则静。果能这样平静,自强不息作去,便能永常虚空无碍寂然安止不动了。\n如此清静,渐入真道;既入真道,名为得道,虽名得道,实无所得;为化众生,名为得道;能悟之者,可传圣道。\n既然能达到如此的清静,就可以渐次的步入真道了。既然得入真道的门径,便可以说是得道了真道。虽如此说,确乎毫无所得,还必须普化有情,同登彼岸,方可称为得道。能悟透了这个道理的人,才可以传布圣人的心法。\n老君曰:上士无争,下士好争;上德不德,下德执德。执著之者,不名道德。\n老子说:上等的贤人,因他深明大义,故没有什么争贪。下等的愚人,因执着己见,不察情理,总是好起争贪。上等有德的人,行了有功于世有恩于人的事还不以为是德。下等无德的人,有心种德,作一点有德的事,便要自持有德了。因他不明道德的真义。\n众生所以不得真道者,为有妄心。\n众生总是认假不认真,皆因妄心所致。因妄心一动,道心难现,离道愈远,所以不能得着真道。\n既有妄心,即惊其神;既惊其神,即著万物;既著万物,即生贪求;既生贪求,即是烦恼。\n有了贪妄心,就要惊动那喜动而不喜静的“识神”。识神被惊,心意外驰,便要着于万物了。既然接触了万物,就要生出不想离开,而要索取的心。但是天下的事,哪能尽如所愿呢?如果求之不得,立刻就是无穷的烦恼。\n烦恼妄想,忧苦身心。但遭浊辱。流浪生死,常沉苦海,永失真道。\n对于万事万物,如果有求不得的时候,便会生出烦闷恼怒的情绪来。由烦闷恼怒中,便要想入非非,妄念一动给身心添无穷的苦恼。便要遭到许多的污浊耻辱,惹得漂泊下流,转变生死永久沉沦在无边的苦恼境遇中。永远的迷失了真常不朽的“道”。\n真常之道,悟者自得,得悟道者,常清静矣。\n真实不虚,常久不变的圣道,能够有悟性的人,自然会得着真意。得能悟透道理的人,便能永久皈依清静,不生不死了。\n仙人葛翁曰:吾得真道,曾诵此经万遍。此经是天人所习,不传下士。吾昔受之于东华帝君,东华帝君受之于金阙帝君,金阙帝君受之于西王母。西王母皆口口相传,不记文字。吾今于世,书而录之。上士悟之,升为天官;中士修之,南宫列仙;下士得之,在世长年。游行三界,升入金门。\n左玄真人曰:学道之士,持诵此经,即得十天善神,拥护其神。然后玉符保神,金液炼形。形神俱妙,与道合真。\n正一真人曰:人家有此经,悟解之者,灾障不干,众圣护门。神升上界,朝拜高尊。功满德就,相感帝君。诵持不退,身腾紫云。\n参考资料 《太上老君说常清静经》全文-『白云居』 太上老君说常清静经_百度百科 太上老君清静经 译文_清静经_佚名_在线阅读_中华典藏 ","date":"2024-01-02","permalink":"https://aituyaa.com/%E5%A4%AA%E4%B8%8A%E8%80%81%E5%90%9B%E5%B8%B8%E6%B8%85%E9%9D%99%E7%BB%8F/","summary":"\u003cp\u003e《太上老君说常清静经》仅三百九十一字。是道教炼养术重要资料之一。《清静经》正文起首一句就是“大道无形”而定了全经格调。经文不讲有为的修养方法,而是要人从心地下手,以“清静”法门去澄心遣欲,去参悟大道。经中以发挥“清静”两字为主,简明地叙述了道家修心养性的基本原则。\u003c/p\u003e\n\u003cp\u003e“夫人神好清,而心扰之;人心好静,而欲牵之。”要去除这些牵扰,获得“清静”,必须“遣其欲而心自静,澄其心而神自清”。达到“心无其心”、“形无其形”、“物无其物”,“唯见于空”的境界。但是,有“空”存在,还不究竟,还要“观空亦空”、“所空既无”、“无无既无”才能“湛然常寂”。“寂无所寂,欲岂能生,欲既不生,即是真静”,直至进入“常清静”的境界,如此“渐入真道”“名为得道”。\u003c/p\u003e","title":"太上老君常清静经"},]
[{"content":"《高上玉皇心印妙经》,也称《无上玉皇心印妙经》,简称《玉皇心印经》或《心印经》。《玉皇心印经》是道士每日功课必诵经典,是修道之径路,是“命功”修炼功法。按照经中所讲悉心做去,小则有益身心,大则证道登真。诵持不退,不但能开通妙理,渐悟真诠,且能感格高真上圣,资助道力。\n吕祖曰“只修性,不修命,此是修行第一病;只修祖性不修丹,万劫阴灵难入圣”。本经便是命功修行之法。\n此经阐明中华道家修炼学的理法,为中华道家丹法“修命”的代表作。此经与《[[太上老君常清静经]]》珠连壁合,为中华道家修炼学“性命双修”的重要经典。\n经文 上药三品,神与炁精。恍恍惚惚,杳杳冥冥。存无守有,顷刻而成。回风混合,百日功灵。 默朝上帝,一纪飞升。知者易悟,昧者难行。履践天光,呼吸育清。出玄入牝,若亡若存。 绵绵不绝,固蒂深根。人各有精,精合其神。神合其炁,炁合体真。不得其真,皆是强名。 神能入石,神能飞形。入水不溺,入火不焚。神依形生,精依炁盈。不凋不残,松柏青青。 三品一理,妙不可听。其聚则有,其散则零。七窍相通,窍窍光明。圣日圣月,照耀金庭。 一得永得,自然身轻。太和充溢,骨散寒琼。得丹则灵,不得则倾。丹在身中,非白非青。 诵持万遍,妙理自明。 注解 上药三品,神与炁精。\n上药指最好的药物,三品即三种。内丹所用的三种药物,即神、炁、精三者,又称为“三宝”。有先天三宝、后天三宝之分别:先天三宝指先天真一之神、先天真一之炁和先天真一之精;后天三宝指思虑神、呼吸气和交感精。先天三宝可以成丹,后天三宝不能成丹。炼丹之人,妙在用性命双修之手段,使后天三宝返为先天三宝,再使先天三宝抟结成丹,则大道圆成。\n恍恍惚惚,杳杳冥冥。\n《老子》第21章曰:“道之为物,惟恍惟惚。惚兮恍兮,其中有象。恍兮惚兮,其中有物。窈兮冥兮,其中有精。其精甚真,其中有信。”即是描述此中景象。恍恍惚惚,指炁生之时,神住恍惚,方可采得真一之炁;杳杳冥冥,指精生之时,神入杳冥,方可采得真一之精。此两句皆指先天三宝发生之时,修持之中出现的景象。\n存无守有,顷刻而成。\n无者,性也,神也。有者,命也,炁也,精也(先天精即炁)。存无即存养元神,归于虚无。守有即保守元炁,谨藏勿失。神、炁、精三者抟聚一处,和合凝集,成丹之功,只在片刻之间。此谓“结丹”,结丹之所在下丹田。\n回风混合,百日功灵。\n风,指呼吸。回风,指呼吸内转。先天三宝抟结成丹之功,还需运用呼吸,调节火候,混合一处,方可炼养大丹。大丹成就,约需百日时间。此即丹家所谓“百日筑基”之说,修炼至此则命功基本完成。然后“养丹”,养丹之所在中丹田。\n默朝上帝,一纪飞升。\n上帝,指上丹田。经过结丹、养丹之功,就要将丹移至上丹田,准备出神。出神之后,即可身外有身,超脱生死,飞升天界,合于大道。修为至此,约需一纪(12年)之功。\n知者易悟,昧者难行。\n知者,指智者,指有智慧的人。昧者,指愚昧的人。有智慧的人容易领悟,而愚昧的人则难以实行。道谚曰:“道不传匪(非)人”,匪人并不仅指坏人,愚昧之人亦属匪人,难以成道。故此修道之事,特别讲究选择人才。\n履践天光,呼吸育清。\n修持功深,与天地之炁合为一体。则周身包裹天光,举足如踏云端。呼吸培育,而炁愈清纯。浊阴之气日去,而清阳之炁日长。\n出玄入牝,若亡若存。\n玄牝,指丹田。呼吸真炁,出入于丹田之间。此时由于身中真炁,变得至清至纯,所以感觉炁机似有如无,若亡若存。\n绵绵不绝,固蒂深根。\n真炁绵绵不断,皆归丹田之中。丹田即吾人之根蒂,真炁进入,则根蒂坚固,长生之道得矣。\n人各有精,精合其神。\n人人各自都有元精在身,元精应当配合元神。\n神合其炁,炁合体真。\n元神还应当进一步配合元炁,元神、元炁、元精三者配合一处,吾人身体方可脱化,成为真人之身。\n不得其真,皆是强名。\n真,指先天之元神、元炁、元精。如果达不到元神、元炁、元精三者配合的真实效果,一切都是牵强附会之空谈。\n神能入石,神能飞形。\n元神变化,能够入于石内,能够飞形天外。所谓“散则成炁,聚则成形。”神通变化,不一而足。\n入水不溺,入火不焚。\n修炼成功,乃为真人。此时入于水中而不溺,入于火中而不焚。超载于生死之外,可以不惧一切水火灾难。\n神依形生,精依炁盈。\n神依于形体而存在,形须神立,形去神离。精依于内炁而充盈,精由炁生,炁竭精枯。归根结底,三者皆依于形体而生存。所以中华道家强调性命双修,且以形体长生作为基础。如果没有坚固不坏的形体,则神、炁、精三者失去依托,一切修为无从谈起。\n不凋不残,松柏青青。\n能够保住形体坚固不坏,则神、炁、精三宝常常充盈。就象青青松柏一样,不畏严寒,永不凋残,而生机长存。\n三品一理,妙不可听。\n三品指神、炁、精三宝,一理即一源也。炼丹所用三宝,乃为先天真一之神、先天真一之炁和先天真一之精。既属先天真一,都是同一源头,故曰三品一理。其实乃为先天大道生化而来,其中精微,妙不可言。惟实修可以知之,非拟议可以知之。\n其聚则有,其散则零。\n神、炁、精三者既来自同一本源,则三者不可分离。聚之则共存,散之则俱亡。故此修真之人,当常使神、炁、精三者常凝常聚,神不可离炁,炁不可离精,精不可离神。三宝既不离散,则丹基牢固,而大道可成。\n七窍相通,窍窍光明。\n七窍,指耳、目、口、鼻等七窍,本来各有各的功能,不能互相代替。神、炁、精三宝抟聚一处,结成大丹。则人身各处,再无滞碍。玄关窍开,则窍窍相通,自然七窍相通,皆有光明,内外照彻。\n圣日圣月,照耀金庭。\n圣日圣月,指身中之日月,乃为人身阴阳二炁之精华。金庭,指黄庭,即丹田。修炼进入妙境,人身造化同乎天地,身中自可出现日月光芒,照彻于丹田之内。丹经云:“人身为一小天地。”\n一得永得,自然身轻。\n得丹之后,一切神通都会出现,所谓“得其一,万事毕”。比之起初修道之时产生的小小变化,实有霄壤之别。此时浊重之躯,脱换为轻清之体。自然感觉身轻如羽,举步似飞。\n太和充盈,骨散寒琼。\n太和,指太和之炁。寒琼,指仙界。修炼至此,已然脱胎换骨。太和之炁,充盈全身。全身骨质,尽化为炁。周身之炁,与仙界打成一片。\n得丹则灵,不得则倾。\n得丹之后,自会出现以上灵验。如果不能得丹,则有生命倾覆之危。故此丹道修持,皆须真修实证方可知之。倘若缺乏实际功修,仅凭道听途说或望文生义,妄谈效验,反有殒身之虞。\n丹在身中,非白非青。\n内丹产于身中,不可于身外求之。而且无形无象,非白非青,亦不可以形色求之。\n诵持万遍,妙理自明。\n诵,指诵念。持,指行持。万遍,即很多遍。这里告诉人们,学习此经不光要诵念,即不光从字面上去理解;同时还要在实际修炼之中去反复领悟,才能明白经文的真实含义。\n参考资料 黄信阳书法《高上玉皇心印妙经》并注解_道家文化 《高上玉皇心印妙经》注解_后天 高上玉皇心印妙经_百度百科 道教经文:《高上玉皇心印妙经》原文及注解-九天玄宗丨易学堂 中国道教协会 ","date":"2024-01-02","permalink":"https://aituyaa.com/%E9%AB%98%E4%B8%8A%E7%8E%89%E7%9A%87%E5%BF%83%E5%8D%B0%E5%A6%99%E7%BB%8F/","summary":"\u003cp\u003e《高上玉皇心印妙经》,也称《无上玉皇心印妙经》,简称《玉皇心印经》或《心印经》。《玉皇心印经》是道士每日功课必诵经典,是修道之径路,是“命功”修炼功法。按照经中所讲悉心做去,小则有益身心,大则证道登真。诵持不退,不但能开通妙理,渐悟真诠,且能感格高真上圣,资助道力。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e吕祖曰“只修性,不修命,此是修行第一病;只修祖性不修丹,万劫阴灵难入圣”。本经便是命功修行之法。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e此经阐明中华道家修炼学的理法,为中华道家丹法“修命”的代表作。此经与《[[太上老君常清静经]]》珠连壁合,为中华道家修炼学“性命双修”的重要经典。\u003c/p\u003e","title":"高上玉皇心印妙经"},]
[{"content":"2023 年相关工作整理及总结。\n周报汇总 - 签章系统 4.2.0 迭代功能开发(已完成); - 查看印章权限弹窗---优化分配印章权限功能(成员信息数据量过大,页面卡死) - 前台页面滚轮翻页事件优化(五矿项目出现的bug优化) - 文件管理--发起签署--签署输入交互优化(签署人信息默认为当前登录用户) - 签章系统 4.3.0 迭代功能开发(进行中)。 - 创建国密印章交互逻辑梳理 - 管理后台--创建云印章---自动生成--法定代表人名章(20*20mm) - 优化完善客户招银的横竖版混编签章 - adobe、wps 网页签章插件跳转同步文档至签章系统页面 - 签章系统 4.3.0 前端横竖版混排签章功能迁入(已完成); - 签章系统 4.3.0 后台证书类型标识功能开发(已完成)。 - 签章系统 4.3.0 后台法定代表人名章生成(已完成); - 签章系统 4.4.0 迭代功能开发(进行中)。 - 签章系统 4.4.0 第三方跳转(office)免登录签章功能开发(已完成); - 签章系统 4.4.0 迭代功能开发打印功能(进行中); - 签章系统 4.5.0 迭代开发 (进行中) ; - 打印功能升级为c++的打印控件 - 签章系统 4.6.0 迭代开发 (进行中) ; - 新增一类签章接口--(平台签) - 盖章页面组件封装 - 招银理财 - 招银理财部分扫描件旋转位置偏移问题(已完成)。 - 招银理财项目前后台增加渠道标识(已完成); - 招银理财个性化多页签章功能开发(已完成)。 - 招银理财后台管理新增 rtx 和 no 交互功能(已完成); - 招银理财大文件加载问题处理(进行中)。 - 招银理财后台管理证书管理更新(已完成); - 招银理财多文件多外印章次数功能调试(已完成)。 - 生产运维系统 - 生产运维系统打印密码纸线上问题处理(已完成)。 - 生产运维系统图片库迁移兼容处理(已完成); - 生产运维图片转码问题修复(已完成); - 合同平台 - 合同平台 ukey 续费线上问题处理(已完成); - 电子合同平台线上问题处理(已完成); - 电子合同云签组件线上问题优化(已完成); - 电子合同平台图片转码问题修复(已完成); - 合同平台图片展示功能格式转化相关(已完成)。 - 合同平台个人证照图片展示功能格式转化相关(已完成)。 - 合同平台 h5 认证功能升级(进行中); - 合同平台深圳电子印章新版本开发(进行中)。 - 电子合同平台官网信息修改(已完成); - 签章客户端\u0026amp;合同平台 ukey 鉴权及联系人信息修改(进行中); - 新疆税局 sm2 和 rsa2 接口轮询发包测试(已完成)。 - 新疆不动产二期标准功能调试(已完成); - 邮政系统 - 邮政系统嵌入页面接口调试(进行中); - 邮政系统 ukey 本地签章跨域问题调试(已完成)。 - 邮政签章接口协议更新 ws(进行中); - 官网下载中心驱动信息变更(已完成); - 三级等保反馈问题调试升级(已完成)。 - ukey 申请小程序开发(进行中); - 珠海小程序上传图片问题处理(进行中); - 正中集团签单系统后台授权优化(已完成)。 - netca 预约系统新增渠道查询及信息采集功能(已完成); - 周大福鉴权失败跳转错误问题处理(进行中); 项目汇总 2023 相关项目汇总如下:\n签章系统 4.2.0、4.3.0、4.4.0、4.5.0、4.6.0 相关迭代的开发; 招银理财定制化需求功能的开发; 正中集团定制化需求功能的开发; 新疆税局国密 sm2 协议的兼容性支持; 周大福鉴权失败相关问题处理; 电子合同平台 web 及移动端相关功能的开发; 珠海 ukey 申请小程序开发; 官网相关展示信息优化及更新; netca 预约系统增渠道查询及信息采集功能; 邮政系统本地签章跨域问题处理,等等。 项目相关问题及思考 安全性、数据密集性、规范性。\n安全性 电子印章是数字技术在文件签署和身份验证领域的应用,旨在提供与传统印章相似的法律效力和安全性。\n以往,数据加密方面多是国外的一些协议标准,如 rsa、pgp 等,为了更好地确保我国的信息安全和数据保护,中国国家密码管理局发布了国家密码算法标准 sm2,广泛用于中国政府和相关领域的安全通信和加密应用。\n新疆税局项目的相关功能开发,最初便是使用的 rsa 协议标准,后续会逐步替换为 sm2 协议。过渡期,采用同时兼容两种协议的做法,以保证过往接口的正确运行。如何做呢?\n直接在历史代码中的接口请求中依次添加相关的兼容性代码,工作量很大且容易有遗漏,后续测试工作压力也不小。采用的办法是对历史接口请求进行拦截,加了个‘套子’(类似过滤器),统一处理各个接口的协议兼容性问题。\n另外,浏览器不断升级,对底层相关网络传输的安全性也日渐收紧。如,chrome 浏览器的新版本基本上禁止了在 https 协议的页面中直接调用 http 的接口。我司之前的本地化签章功能开发都是基于 c++ 的本地化进程服务实现的,使用的就是 http 协议,给用户私有化部署的时候经常性地会遇到用户环境强制支持 https 而导致跨域的问题。升级协议就变得势在必行,目前拟采用 websocket 协议替代为现存 http/https 相关的异步请求接口封装。\n怎么说呢?安全总是必要的,但它有时候也是一把双刃剑。安全性与灵活性,一对矛盾体。\n数据密集性 任何稳定运行的程序,大概率都会随着数据量的上涨出现问题。\n系统的初次开发,可能只适用于小型企业用户(500 人以内),运行良好,渲染速度也不错。如果直接把当前系统部署给一个大型集团(5000 人以上),原来的软件设计可能就需要做出不小的变动。如,系统有一张给每位员工的权限进行控制的序列表,之前用户量少的时候,可能都不需要分页,对用户的权限控制能在一个请求集合中全部完成。用户量增加之后(超过 2000 人),一般配置的客户机,表格本身的渲染都会成为一个问题,大概率会直接导致页面卡死。对于数据的渐进加载就成了一个迫切需要解决的问题 - 逐次请求,后续合并,状态追踪及响应……\n除了上述用户量激增造成的负载超过设计容量,另一种较常见的就是由于数据本身体积过大对存储、传输等带来的不便。如,对于一份 1mb 的文件,无论是网络传输还是本地读取,都不是问题。但当文件是 1gb 的时候呢?又或者你要传输 1000 份 1mb 文件的时候呢?速度慢,耗时长,中途失败……\n看,无论是以什么形式,数据量太过于密集了,一些本不是问题的问题,就变得很是问题了。\n规范性 如果一个项目,够“小” - 独立开发,后期几乎不存在优化升级或不存在较长时间后的功能改动,那么,基本上可以按照任何风格去编辑程序,只要它能成功运行,再加上适当的操作权限控制,就可以了。如果是协作式的,包括横向上的多人协作及纵向上的多次协作,当然,更多情况是两者的综合体。这种情况下,‘规范’就是一个不得不提上日程的话题。\n产品的设计规范,开发架构规范,代码编写风格规范,测试规范……\n假如产品 a 最初是为了满足场景 a 的需要,开发成功后也满足了场景 a 的需要,但用户体验下来大概率会对产品 a 提出更多的需求,这在软件开发中很常见。认识,总是随着正比于你对事物的了解程度。所以,前期的对于产品 a 的“抽象”界定就很重要,包括但不限于功能抽象、流程抽象等等,这有助于控制项目的整个成本。\n以我司为例,目前的业务场景主要有两个,一种是基于线上服务的电子合同平台(云端服务),另一种是基于本地服务的私有化签章系统(内网服务)。内部在不短优化迭代标准版的签章功能,但冗余性不小,其中合同平台、云签和签章系统的核心签章组件是并行存在的。\n如签章系统,由于其定制化的特性,存在多个软件变体版本,当需要把变体中的功能合并到标准版时,就会出现不少问题。迁移的功能不是新增的独立模块,而是对于标准版的修改,又或是标准版中根本就没有提供相同的接口服务等等。代码合并过程中也会存在让人费解的冲突问题……\n规范很重要,只有规范了,才存在合理的抽象。抽象,抽象,抽象。\n结语 生活中,总会遇到这样那样的问题,人就是在不断处理问题的过程中慢慢成熟。一个问题可能有许多不同的答案,或对或错。也许,答案的对错并不重要,重要的是找寻答案过程中的心境,找寻答案过程中所经历的事和人。\n","date":"2023-12-28","permalink":"https://aituyaa.com/docs/2023-%E5%B7%A5%E4%BD%9C%E6%80%BB%E7%BB%93/","summary":"\u003cp\u003e2023 年相关工作整理及总结。\u003c/p\u003e","title":"2023 工作总结"},]
[{"content":"以下为史蒂夫·乔布斯《遗失的访谈》全文。视频访谈\n![[assets/pasted image 20231219172620.png|400]]\n我是 bob cringely。16 年前(1995 年),我制作《书呆子的胜利》节目时采访了乔布斯。1985 年乔布斯被他自己引荐的 ceo john sculley 排挤出苹果,接受采访时,乔布斯正在经营他创办的 next 公司。18 个月后苹果收购了 next,又过了半年乔布斯重新掌管苹果。\n当年的节目只用了一小段采访,九十年代末采访母带从伦敦运往美国途中遗失。多年来我们一直以为再也看不到完整的采访,然而几天前导演 paul sen 在车库里发现了一份 vhs 拷贝。\n乔布斯生前很少接受电视采访,如此精彩的访谈更是罕见,它记录了乔布斯的坦率、非凡的魅力和独特的视野。为了向这位奇人致敬,我们几乎一刀未剪,大部分内容是首次公布于众。\n一、结缘计算机与创业搭档 bob:你是怎么与个人计算机结缘的?\n乔布斯:我第一次见到计算机是 10 或 11 岁,很难回忆当年的情景,我可不是故作老成……大约 30 多年前,见过电脑的人不多,即使见到,也是在电影里。电影里的计算机都是装有开盘机的大柜子,闪闪发光,真正了解计算机功能和原理的人不多,有机会接触计算机的人更是寥寥无几。\n我有幸在 nasa ames 研究中心见到一台,那不是一台完整的计算机,只是一台分时共享的终端机。设备非常简陋,连显示器都没有,只是一台带键盘的电传打印机:你在键盘上输入指令耐心等待,然后它会哒哒哒地输出结果。\n即便如此这玩意也太奇妙了,尤其是对十岁的男孩而言。你可以用 basic 语言或 fortran 语言编写程序,机器接受并执行你的设想,然后把结果告诉你,如果结果和设想的一样,说明程序见效了。这太让人激动了,我完全给计算机迷住了,当然计算机对我而言仍然有些神秘,因为真正的计算机藏在电缆的另一端,而我从未见过。\n打那以后我总想着计算机,后来我参加了在惠普组织的兴趣小组,12 岁时我打电话给 bill hewlett(惠普创始人比尔·休利特),他当时住在惠普。当时所有电话号码都印在号码簿里,只要翻电话号码簿,就能查到他的电话。他接了电话,我说我叫 steve jobs,你不认识我,我 12 岁,打算做频率计数器,需要些零件。我们聊了大概 20 分钟,我永远记得他不但给了零件,还邀请我夏天去惠普打工。\n我才 12 岁,这件事对我产生了不可思议的影响。惠普是我见过的第一家公司,它让我懂得了什么是公司,如何善待员工。那时还没有胆固醇偏高一说,每天上午十点公司拖来满满一卡车的甜面圈和咖啡,大家停下工作喝杯咖啡,品尝甜面圈,很明显惠普明白公司真正的价值在于员工。\n之后我每周二晚都去惠普的 palo alto 实验室,与一些研究人员见面。我见到了第一台台式计算机——hp9100,大概有行李箱那么大,装着小小的 crt 显示器。它是一台可以独立工作的一体机,我很喜欢。它使用 basic 或 apl 编程,我常常数小时地守着它编程。\n差不多也是在那时我认识了 steve(woz,苹果联合创始人斯蒂夫·沃兹尼亚克),我大约十四五岁,可能还要小些,我俩很投缘。他是我遇到的第一个比我更懂电子知识的人,他大概比我大五岁,我很喜欢他。他因为制造恶作剧被大学开除,刚刚回到父母家,正在修大专的结业课程,我们成了最要好的朋友,开始一起做项目。\n当时《esquire》杂志报道有个叫 captain crunch 的人,据说他有办法打免费电话,你肯定也听说过,我们很好奇,怎么可能做到呢?多半是吹牛。我们开始泡图书馆,寻找打免费电话的秘密。一天晚上我们去了斯坦福线性加速中心,在科技图书馆角落的最后一排书架上,我们找到一份 at\u0026amp;t 技术手册,揭开了所有的秘密。\n我永远忘不了那一刻,我们看着这份手册,心想老天这一切都是真的,于是我们着手制作能够发出这种音频的装置。原理是这样的:我们打长途电话时会听到嘟嘟的声音,听起来像拨电话的按键音,只是频率不同,但可以模拟,实际上那是从一台计算机传到另一台计算机的信号,它可以控制交换机的工作。at\u0026amp;t 公司设计的数字电话网络有严重漏洞,他们使用与声音相同的频段来发送控制信号,也就是说只要你模拟出相同的音频信号,通过听筒发送出去,整个 at\u0026amp;t 国际电话网就会把你当成一台 at\u0026amp;t 计算机。\n三周后我们做出了这样的一个装置,真的管用。我记得第一个电话想打给 woz 住在洛杉矶的亲戚,我们拨错了号码,大半夜把某个家伙吵醒了,我们兴奋地冲他嚷嚷:打这个电话是免费的,对方一点也不感激我们,但这已经是奇迹了。\n我们做出了这个称为“蓝盒子”的装置,盒底部贴着我们的 logo,写着“世界握在手中”。这是世界上最好的“蓝盒子”,全数字化、简便易用。你可以拿着它去电话亭轻松拨打长途电话,打卫星电话去欧洲去土耳其,然后接有线电话打回亚特兰大;你可以满世界跑,跑五六趟,因为我们知道所有的交换密码;你可以给家门口的电话亭打电话,在家喊话,隔一会电话亭就能听到,真是奇妙。\n你也许会问这样做有意思吗?它的意义在于虽然我们年纪还小,但已经意识到我们有能力做出控制庞大系统的工具,这就是我们得到的启发。我们两个人尽管懂得不多,但我们制造的小玩意可以控制庞然大物,这是不可思议的经历,没有“蓝盒子”就不会有苹果电脑。\nbob:woz 说你们给教皇打了电话?\n乔布斯:没错,他冒充亨利·基辛格给教皇打电话。我们弄到梵蒂冈的电话号码,打电话给教皇,教会的重要人物逐个被叫醒,最后终于派人把教皇叫起来,要不是我们憋不住哈哈大笑起来,他们还真以为是基辛格。虽然没跟教皇通上话,但实在是搞笑。\nbob:你们是怎么从‘蓝盒子’想到做个人电脑的?\n乔布斯:这很自然。当时 mountain view 有分时共享计算机,我们可以免费上机,但我们需要一个终端,买不起就自己动手设计制作,这个终端是我们的第一件作品,apple i 是这台终端的扩展。它用微处理器代替了后台主机,就像是把两个独立的项目整合在一起。一开始是做终端,然后才是 apple i,自己动手做完全是因为我们买不起。\n我们四处收集零件,全部手工制作,做一台大概要 40~80 小时,那些小零件太难安装了。后来周围很多朋友也想要,虽然他们也能弄到零件,但他们不具备制作经验和技能,我们只好替他们做。这事占用了我们所有时间,于是我们想到制作印刷电路板,就是在镀铜的玻璃纤维板两面腐蚀出铜导线,采用印刷电路板,只要几小时就能做出一台 apple i。\n我们打算把电路板以成本价卖给朋友,把钱赚回来,这样皆大欢喜,我们也可以休息休息。说干就干,我把大众 microbus 卖了,woz 卖了他的计算器,我们凑够了钱,请朋友设计印刷电路板。电路板做出来后,卖了一部分给朋友,我想把剩下的也卖了,把 microbus 和计算器赎回来。\n我去了最早的计算机商店,mountain view 的字节商店,那时它藏在一家成人书店里。我见到了老板 paul,paul 说“我预订 50 套”,我说“太好了”,“但我要完全组装好的计算机”。\n我们从没想过出售整机,不过还是答应了,何乐而不为呢?我花了好几天打电话联系电子元件批发商,告诉对方需要哪些零件,我们完全是摸着石头过河。我们打算买 100 套零件,做好后以两倍的成本价卖给字节商店 50 台,剩下 50 台就是我们的利润。我们说服批发商赊给我们零件,30 天后还款,我俩就这样懵懵懂懂地拿到了零件。apple i 做好后,卖了 50 台给字节商店,第 29 天才收到账款,第 30 天正好付清赊零件的钱。\n我们就这样做起了生意,不过也碰到利润危机。我们的利润不是现金,而是堆在角落的 50 台电脑,我们不得不考虑如何实现利润,我们想继续寻找批发商,是不是还有其他计算机商店?我们打电话给全国的计算机商店,就这样做起了生意。\nbob:苹果的第三位创始人是英特尔前高管 mike(迈克·马库拉,曾任苹果 ceo、董事长),他是怎么入伙的?\n乔布斯:当时我们正在设计 apple ii,对它充满了期待,woz 希望增加彩色图形界面,我希望……当时有一大群硬件爱好者,他们自己组装电脑,或者用我们的主板,自己安装电源、键盘等等。还有许多人是软件爱好者,只想写程序,就像我 10 岁刚刚接触计算机那样。所以我希望 apple ii 成为第一款功能齐备的个人电脑,就算你不懂硬件也能轻松使用,这就是我们对 apple ii 的基本设想。\n我找到设计师,设计了所有细节,我们还打算使用塑料机身,什么都想好了,可我们资金不足,还缺几万美元,于是我开始寻找风险投资。我找到 don valentine,他还来参观了我的车库,他说我看起来像人类的叛逆者,这话成了他的名言。虽然他不打算投资,但推荐了几个人给我,其中就有 mike markkula,于是我约了 mike。\nmike 以前是英特尔的产品经理,他大概 30 岁离开英特尔,手里有英特尔的股票,他靠股票期权赚了一百多万,当时非常富有,他在家投资石油、天然气之类的生意,我感觉他想干一番大事业。我俩聊得很投机,最后 mike 答应投资,我说我们不光要钱,还希望你入伙,于是 mike 成了我们的合作伙伴。他不仅投资,还参与创业,我们就这样起步了。\n我们拿出 apple ii 的设计,召开新闻发布会,几个月后 apple ii 首次在西海岸计算机展览会上露面。——“情况怎么样?”——妙不可言,当时西海岸展览会规模不大,但我们觉得已经很大了,我们在展台上用投影展示 apple ii 和图形界面,现在看有些简单,但当时是 pc 上最先进的图形界面,我们出尽了风头,批发商和经销商蜂拥而至,进展非常顺利。\n二、如何学会“做生意”:多问为什么 bob:当时你多大?\n乔布斯:21 岁。\nbob:21 岁就这么大成功,可你从来没有这方面的经验,完全是凭直觉,你是怎么学会管理公司的?\n乔布斯:在生意场多年,我发现一个现象。我做事前总问为什么,可得到答案永远是“我们向来这样做”,没人反思为什么这么做。\n我给你举个例子,我们在车库里组装 apple i 时,成本算得清清楚楚,可工厂生产 apple ii 时,财务部门用的是标准成本,每个季度估算标准成本,然后根据实际情况调整。于是我不断追问,为什么要这样做?得到的答复是,这是一贯的做法,6 个月后我发现其实是因为无法精确计算成本,所以只能先估算,然后进行修正,根本原因是信息管理系统不够完善,但没人承认这一点。\n后来设计 macintosh 的自动化工厂,我们抛开这些陋习,做到了精确控制所有成本。生意场上有很多约定俗成的规定,我称为陈规陋习,因为以前这样做,所以就一直这样做下去。所以只要你多提问多思考,脚踏实地工作,你很快就能的学会经商,这不是什么难事。\n:: 思考,找到现状的局限,然后尝试改进它。\nbob:不是什么深奥的技术?\n乔布斯:不是。\nbob:最早接触 hp1900 时,你谈到自己编程的事,都是些什么样的程序?用途是什么?\n乔布斯:我举个简单的例子,我们设计“蓝盒子”时,写了很多程序,用来处理繁琐的计算工作,计算主频、分频之类的东西,还计算不同频率的差错率和容错性,编程可以帮助我们完成工作,它没有明确的实用性,重要的是我们把它看作思考的镜子,学习如何思考,我认为学习思考最大的价值在于……\n所有美国人都应该学习编程,学习一门编程语言,学习编程教你如何思考,就像学法律一样。学法律的人未必都成为律师,但法律教你一种思考方式。同样,编程教你另一种思考方式,所以我把计算机科学看成基础教育,是每个人都应该花一年时间学习的课程。\nbob:我学了 apl,很明显它丰富了我的人生。\n乔布斯:你有没有觉得它教给你独特的思考方式?\nbob:其他语言也许更明显些,我最先学的 apl。显然 apple ii 很成功,公司飞速发展,成功上市,你们都成了富翁,富有的感觉如何?\n乔布斯:很有趣,我 23 岁拥有超过 100 万美元的财产,24 岁超过了一千万,25 岁超过了一亿。但这不重要,我不是冲着钱去的。钱允许你做想做的事,钱让你实现那些短期内看不到效益的创意,但钱不是最重要的,重要的是公司、人才、产品,是产品带给客户的价值。 所以我不太看重金钱,我从不出售苹果的股票,我相信公司会发展得越来越好。\nbob:1979 年乔布斯第一次拜访施乐 palo alto 研究中心,在 pc 成形之初,palo alto 研究中心起到了关键作用。\n乔布斯:同事一直怂恿我去施乐公司,看看他们在做什么,于是我去了。对方非常友好地展示了他们的研究,他们展示了三个项目,但我完全被第一个项目吸引了,另两个没怎么看。我记得有一个项目是面向对象编程,我没怎么看;还有一个是计算机网络系统,当时他们已经有上百台联网的电脑,可以互发 email,也没有吸引我。\n吸引我的是图形用户界面,那是我见过的最漂亮的东西,虽然现在看来它还很粗糙,有瑕疵,但是当时我们还看不出来,这个创意太棒了,他们做得很好。很快我就意识到所有计算机都应该变成这样,我们可以争论要多久后能现实、谁会是最后的赢家,但是没人会质疑图形界面是必然的发展方向,如果你当时在场,你也会这样想的。\nbob:paul allen(微软联合创始人保罗·艾伦)也说过同样的话,真有趣。\n乔布斯:是的,显而易见。\nbob:听说你去参观了两次,第二次你带了些人去,对方是不是让你们坐了冷板凳?\n乔布斯:没有。\nbob:她说她负责向你们展示的图形界面,起先她拒绝展示,大约僵持了 3 个钟头。这期间对方只好先带你们参观其他的项目。\n乔布斯:你是说他们不太乐意让我们参观?这个我一点不知道,没印象了。\nbob:看来对方掩饰得很巧妙。\n乔布斯:是的,不过他们还是让我们参观了,还好他们让我们参观了,因为施乐后来被拖垮了。\n三、科技公司衰落的原因:产品不再主导,人才因循守旧 bob:为什么施乐垮了?\n乔布斯:我一直在思考这个问题。认识 sculley 以后,我现在有了清晰的答案(编者注:约翰·斯考利,前百事可乐 ceo,被乔布斯那句著名的“想卖糖水还是想改变世界”请到苹果当 ceo)。就像 sculley 一样,他以前在百事可乐工作,他们的产品可以数十年不变,顶多更换一下可乐瓶子,所以产品部门的人说话没什么份量。\n在百事公司谁最有发言权?是营销部门的人,他们很容易升职从而掌管公司。对百事来说,这不是件坏事,问题是垄断科技公司也有这种情况,比如 ibm 和施乐,即便 ibm 和施乐的产品经理能做出更棒的产品,那又怎么样?这些已经垄断市场的公司很难再提高业绩,要想提高业绩还得依靠营销部门,于是他们逐渐控制公司,而产品部门的人被边缘化,公司就丧失了打造优秀的产品热情和能力。产品部门的功臣慢慢被不懂产品的人排挤,后者通常缺少研发产品的技术和能力,而且也并非打心底愿意替客户解决问题。\n:: 库克,说的就是你,别成天在那 “only apple can do” 了,好好做产品吧!\n施乐公司就是这样。施乐研究院的人私底下把管理层叫做墨粉脑袋,而这些管理人员完全不明白为什么被嘲笑。\nbob:观众可能不清楚墨粉是什么?\n乔布斯:就是复印机里用的墨粉。\nbob:那种黑色的东西?\n乔布斯:是的,这些墨粉脑袋压根不知道计算机能做什么,他们不过是碰巧赶上了计算机产业的顺风车。施乐本来有机会把规模扩大 10 倍,独占整个行业,就像 90 年代的 ibm 或微软,不过都已经过去了,不重要了。\nbob:确实,你提到 ibm,ibm 进入 pc 市场是不是对苹果构成了威胁?\n乔布斯:那当然,苹果当时的市值只有 10 亿,而 ibm 大约是 300 亿,确实让人胆寒,尽管 ibm 的第一款产品十分糟糕,但我们太轻敌了,我们忽略了很多人的利益与 ibm 捆绑在一起,如果没有这些帮助,ibm 早就输了。ibm 的确很高明,它建立了强大的同盟阵营,终于救了它一命。\nbob:你从施乐研究中心找到了灵感,如何付诸行动呢?\n乔布斯:马上召集身边的骨干来实现这个创意,问题是从惠普跳槽来的几个人不理解图形界面,我跟他们大吵过几次。他们觉得图形界面就是在屏幕下方加上几个按钮,完全不明白比例字体和鼠标的重要性。我记得他们和我争执不下,冲我大嚷大叫,说什么研发鼠标至少要 5 年,成本不会低于 300 美元,把我搞烦了。我找到 david kelly 设计公司,对方 90 天后设计出了质量稳定的鼠标,成本只要 15 美元。\n我这才发现苹果没有足够人才来实现这个创意,核心团队有这个能力,但是许多从惠普跳槽来的员工不行。\nbob:这涉及到职业分工的问题,每个人特长不同,不是吗?\n乔布斯:不,这不是擅长与否的问题,而是他们犯糊涂,公司在犯糊涂。公司规模扩大之后,就会变得因循守旧,他们觉得只要遵守流程,就能奇迹般地继续成功,于是开始推行严格的流程制度,很快员工就把遵守流程和纪律当作工作本身。\nibm 就是这样走下坡路的,ibm 的员工是世界上最守纪律的,他们恰恰忽略了产品。苹果也有这个问题,我们有很多擅长管理流程的人才,但是他们忽略了产品本身。\n经验告诉我,优秀的人才是那些一心想着产品的人,虽然这些人很难管理,但是我宁愿和他们一起工作,光靠流程和制度做不出好产品。 苹果也有这方面的问题,这些问题最终导致 lisa 电脑失败。\nlisa 是一款非常超前的产品,但是它过于超前了,以致偏离了产品的宗旨。在这些从惠普跳槽来的人眼里,1 万美元的零售价不贵,但是市场和经销商觉得这个价格太离谱了,lisa 的定位彻底背离了苹果的企业文化,背离了公司的形象,也背离了经销商与消费者的期待,苹果的老顾客根本买不起这么贵的产品,所以它失败了。\nbob:就如同你同 john couch 对领导权的争夺一般?\n乔布斯:是的,我输了。\nbob:为什么会起争执?\n乔布斯:我认为 lisa 当时面临困境,而且越陷越深,我没能争取到大多数高管的支持,所以我也无能为力,只能服从团队的决定。我失败了,那段时间我很消沉,但我很快意识到如果不振作起来,apple ii 会重蹈覆辙,应该尽快利用这些新技术,否则苹果将止步不前。所以我组织了一个小组研发 macintosh,就像是奉了上帝的旨意来拯救苹果,其他人并不这样想,但事实证明我们做的没错。\n在研发 mac 的过程中,我越发觉得我们是在重建苹果。我们大刀阔斧地改革,重新设计了生产线;我去日本参观了大约 80 家自动化工厂,回加州建了世界上第一条生产计算机的自动生产线;我们采购了 1 万 6 千颗最先进的微处理器,由于数采购量大,价格不到 lisa 的 1/5。我们打算把 macintosh 打造成一款平价产品,可惜没成功,原定价格是 2000 美元,最终价格是 2500 美元,这款产品花了我们 4 年时间,搭建了自动化工厂和生产线,采用了全新的销售渠道和营销方法,我觉得我们干得很出色。\n四、什么对产品最重要:创意的具体实现、团队人才碰撞 bob:是你在鞭策这个团队,引导他们……\n乔布斯:团队是我们建立的。\nbob:你建立了团队,而且负责鞭策和引导它,我们采访过很多 macintosh 团队成员,他们都提到你的工作热情和独特的想法,你如何处理工作的轻重缓急,你觉得什么对产品最重要?\n乔布斯:(思考很久)我离开苹果以后,发生了一件几乎毁掉苹果的事。sculley 有个严重的毛病,我在其他人身上也见到过,就是盲目乐观,以为光凭创意就能取得成功,他觉得只要想到绝妙的主意,公司就一定可以实现。\n问题在于,优秀的创意与产品之间隔着巨大的鸿沟,实现创意的过程中,想法会变化甚至变得面目全非,因为你会发现新东西,思考也更深入。你不得不一次次权衡利弊,做出让步和调整,总有些问题是电子设备解决不了的,是塑料、玻璃材料无法实现的,或者是工厂和机器人做不到的。设计一款产品,你得把五千多个问题装进脑子里,必须仔细梳理,尝试各种组合,才能获得想要的结果。每天都会发现新问题,也会产生新灵感。这个过程很重要,无论开始时有多少绝妙的主意。\n我一直觉得团队的合作就像是……我小时候,街上住着一位独居老人,他大概 80 岁,看上去凶巴巴的,我认识他,我想让他雇我帮他修剪草坪。有一天他说“到我车库来,我给你看点东西”,他拖出一台布满灰尘的磨石机,一边是马达,一边是研磨罐,用皮带连着。他说“跟我来”,我们到屋后捡了些很普通的石头,我们把石头倒进研磨罐,加上溶剂和沙砾,他盖好盖子,开动电机,对我说“明天再来”。磨石机开始研磨石头。第二天我又去了,我们打开罐子,看到了打磨得异常圆润美丽的石头,看上去普普通通的石头就像这样互相磨擦着,互相碰撞,发出噪音,最终变成了光滑美丽的石头。\n我一直用这件事比喻竭尽全力工作的团队。正是通过团队合作,通过这些精英的相互碰撞,通过辩论、对抗、争吵、合作,相互打磨,磨砺彼此的想法,才能创造出美丽的“石头”。这很难解释,但显然这并不是某个人的成就。人们喜欢偶像,大家只关注我,但为 mac 奋斗的是整个团队。\n我以前在苹果就发现一种现象,很难表达出来,更像是一种感觉:生活中多数东西,最好与普通之间的差距不超过两倍。好比说纽约的出租车司机,最棒的司机与普通司机之间的差距大概是 30%,最好与普通之间的差距有多大呢?20%?最棒的 cd 机与普通 cd 机的差距有多大?20%?这种差距很少超过两倍。但是在软件行业,还有硬件行业,这种差距有可能超过 15 倍,甚至 100 倍。这种现象很罕见。能进入这个行业,我感到很幸运。\n我成功得益于发现了许多才华横溢,不甘平庸的人才。而且我发现只要召集到五个这样的人,他们就会喜欢上彼此合作的感觉,前所未有的感觉,他们会不愿再与平庸者合作,只招聘一样优秀的人。所以你只要找到几个精英,他们就会自己扩大团队。mac 团队就是这样,大家才华横溢,都很优秀。\nbob:但是有人说他们再也不愿意为你工作了。\n乔布斯:呃,的确,大多数人认为那是他们这辈子最辛苦的日子,有些人觉得那是一生中最幸福的日子,但是没人否认那是这辈子最难忘、最珍贵的经历。\nbob:没错。\n乔布斯:只是有些人无法长时间忍受这样的工作。\nbob:你说别人“工作很烂”时,想表达什么?\n乔布斯:嗯……就是他们干得很烂。有时是我认为你干得很烂,也许我错了。一般是说他们的工作很不合格。\nbob:bill atkinson 说这话的真正含义是‘我听不懂,请再解释一遍’。\n乔布斯:哈哈,不是的,我不是这个意思。与优秀自信的人合作,不用太在乎他们的自尊。大家的心思都放在工作上,每个人负责一块很具体的任务。如果他们的工作不合格,你要做的无非是提醒他们,清晰明了地提醒他们恢复工作状态,同时不能让对方怀疑你的权威性。要用无可置疑的方式告诉他们工作不合格,这很不容易,所以我总是采取最直截了当的方式。 有些同事觉得这种方式很好,但有些人接受不了。\n我是那种只想成功,不在乎是非的人,所以无论我原来的想法多么顽固,只要反驳的人拿出可信的事实,五分钟内我就会改变观点。我就是这样,不怕犯错。我经常承认错误,没什么大不了的,我只在乎结果。\nbob:苹果为什么研发桌面排版,mac 最受欢迎的应用?\n乔布斯:我们是全美第一个试用佳能激光打印引擎的公司,早在惠普和 adobe 之前,我们就已经把它用在 lisa 上了。后来我听说有人在施乐的车库里捣腾类似的玩意,我去参观,发现他们比我们做得更好,他们打算成立一家硬件公司,生产打印机。我劝他们成立一家软件公司,就是 adobe。\n两三周后我撤消了苹果内部的桌面出版项目,有些人恨死我了,但还是撤消了。苹果和 adobe 达成协议,买下了他们 19.9%的股份,然后买下佳能的激光打印引擎,自己开发驱动软件,接着从 adobe 购买排版软件,激光打印机就这样面市了。\n除了 mac 团队,公司其他人都不看好激光打印机,他们觉得一台打印机定价 7000 美元太贵,可他们忘了客户可以通过 apple talk 共享打印机,虽然他们知道这项功能,但看不到它的潜力,毕竟他们对 lisa 电脑糟糕的市场表现记忆犹新。\n我们坚持上马打印机项目,得罪了不少人,第一台激光打印机就这样面市了。我离开时,苹果是世界上最大的激光打印机公司,只过了三四年惠普就追上来了,真可惜。\nbob:你预见到桌面出版的前景吗,还是显而易见的?\n乔布斯:是的,我预见到了,但是我们同时还想推广网络办公,所以 1995 年发布新产品时,我犯了这辈子最大的营销错误。\nbob:是 1985 年。\n乔布斯:1985 年,对不起。我们发布了 macintosh office 办公系统,其中包括桌面出版。当时应该集中力量推桌面出版,而不是所有功能一拥而上。\n五、被逐出苹果的往事 bob:1985 年被 ceo john sculley 排挤,离开了苹果。说说你离开苹果的情况?\n乔布斯:很痛苦,我都不太愿意聊这事。怎么说呢?我招错了人。\nbob:是指 sculley?\n乔布斯:是的,他毁了我十年的心血,他逼我离职,但这还不是最糟糕的。如果苹果能按我的设想发展,我会很开心。他侥幸登上了一艘正要发射的火箭,他还以为自己建造了火箭,轻率改变火箭的飞行轨道,结果是箭毁人亡。\nbob:可是在 macintosh 时期,你俩总是一起出现在媒体上,几乎形影不离。\n乔布斯:没错。\nbob:后来怎么会产生矛盾呢?\n乔布斯:1984 年底 it 行业进入萧条期,销售业绩大幅下降,john 开始惊慌失措。这时苹果公司正好群龙无首,各个部门的负责人都很强势,互不相让。我管理 macintosh 部门,有人管理 apple ii 部门,还有些部门已经濒临关闭,比如存储部门。公司百废待兴,市场疲软又进一步激化了公司的内部矛盾,大家各自为阵。\n董事会对公司业绩很不满意,john 的职位岌岌可危。那时我才发现 john 有一种很强烈的自救本能,有人曾提醒我百事前总裁绝非善茬儿,他说得没错。john 把一切问题都归咎到我头上,我们因此反目。董事会一向很信任 john,所以我被扫地出门了。\nbob:你们对公司的发展有不同的看法?\n乔布斯:是,也不是,因为 john 根本没有自己的看法。\nbob:我想问的是,你当时的愿景是什么?\n乔布斯:这不是愿景的问题,而是执行的问题。我认为苹果应该有一位强势的领袖,团结各个部门,mac 才是苹果的未来,应该削减 apple ii 的项目开支,加大对 mac 的投资力度。john 的愿景是不惜一切代价保住他的 ceo 位置。1985 年苹果处在一种瘫痪的状态,我那时才 30 岁,觉得自己没有能力打理苹果。我担心自己无法管理 20 亿资产的公司,可惜 john 也没这个能力。总之他们说没有适合我的职位了,太悲剧了。\nbob:像是流放西伯利亚?\n乔布斯:是的,他们完全可以让我留下的,我申请过成立研发部门,每年给我几百万预算,网罗优秀人才干一番大事业。他们拒绝了我的申请。\nbob:真可惜。\n乔布斯:我被赶出自己的办公室,再聊下去我会发狂的,但这还不是最糟的,毕竟公司是大家的,不是我的,最糟糕的是苹果的企业文化在随后几年里被毁了。\nbob:(补充旁白)接着我问他怎么看苹果的现状。请注意当时是 1995 年,是他重返苹果的前一年,苹果收购 next 之后,他马上卖掉了到手的苹果股票。\n乔布斯:苹果正在衰落,非常痛苦地衰落,原因在于……我离开时,苹果领先业界整整 10 年,微软 10 年后才赶上我们,他们能赶上来是因为苹果止步不前。今天的 macintosh 与 10 年前的几乎没有区别。苹果每年的研发费用数千万,累计已经超过 50 亿,有什么效果?我没看到。\n他们不懂如何利用新技术,不懂如何创造新产品,优秀的员工被困在公司里,束手无策。因为缺少有眼光的管理者,所以苹果在各个方面都落后了,包括市场份额,产品的优势已经被微软赶超,现在只剩下一群老用户,而且数量在缓慢递减。老用户带来的收益还能再撑几年,但是逐年减少,很糟糕,而且我现在看不到挽回的希望。\n六、工作的动力:追求极致,人类才能共同进步 bob:你觉得微软怎么样?它的处境有点像福特,肯定不是凯迪拉克,也不是宝马,他们干得怎么样?\n乔布斯:微软起家全靠了 ibm,比尔听我这么说会很生气,但这是事实。比尔和微软抓住了机会,创造成了更多机会,人们忘了微软在 1984 年之前根本不做应用软件,那时是 lotus 的天下。微软确实很有胆量,冒险为 mac 编写应用程序。刚开始他们的应用程序非常糟,但他们不断改进,最终占领了 mac 的应用市场,然后借助 windows 这块跳板,打开了 pc 市场的大门。\n现在他们已经占领了 pc 市场,我觉得他们有两大优势:首先,擅长扑捉机会;其次,像日本人一样锲而不舍。他们起家全靠跟 ibm 合作,但是他们很擅长利用机会,这一点我很佩服。微软唯一的问题是没品位,完全没有品位可言,只会一味模仿,产品缺少文化和内涵。 为什么这很重要?比例字体的灵感来自字体设计和精美书籍,如果没有 mac,微软永远不会想到这些。\n:: 精准吐槽。不过不得不说,有时候,用户需要这种‘粗糙’的东西。\n让我难过的并非微软的成功,我一点不嫉妒他们,他们的成功基本上是靠勤奋工作换来的。我难过的是他们做的是三流产品,他们的产品没有灵魂和魅力,太平庸,更让人难过的是用户居然毫无察觉。但人活着是要追求极致,并分享给同类的,这样人类才能共同进步,学会欣赏更美的东西。微软不过是另一个麦当劳,这才是我难过的原因,不是因为微软赢了,而是因为微软的产品缺少创意。\nbob:你打算改变这种局面吗?next 有什么计划?\n乔布斯:暂时没有。next 太小了,我只能眼睁睁看着,无能为力。\nbob:(补充旁白)接着我们聊到乔布斯正在经营的 next 公司。next 被苹果收购后,很快成为 mac os 10 的研发主力。\n乔布斯:你大概没兴趣听我聊 next 吧?\nbob:我想听。\n乔布斯:好吧,我直接说 next 目前在做什么吧。很显然,计算机产业创新要靠软件,但是长久以来,软件开发方式没有本质变化,对不起,软件开发方式 20 年来一直没有变化。不但没有变化,反而越来越糟。macintosh 降低了用户的使用难度,这是一项创举,但增加了程序员的工作难度,软件开发越来越复杂。\n软件正在向各行各业渗透,成为重要的商业竞争武器。mci 与 at\u0026amp;t 十年来的竞争是就是最好的例证,mci 做了什么?不过是率先采用客户账单软件,18 个月内就抢走了 at\u0026amp;t 数百万美元的市场份额。at\u0026amp;t 并非毫不知情,可就是搞不定软件。软件正在释放不可思议的力量,新的软件产品和软件服务将改变我们的社会。\n我们借鉴了施乐 parc 的另一项研究成果,也是 1979 年看到的,当时只了解一点皮毛,这项研究叫面向对象编程。next 已经将其商业化,成为最大的供应商,它可以将软件开发速度提高十倍,而且质量更好。这就是 next 目前做的事,公司有 300 人,资产是 5000~7500万美元。\nbob:第四频道要求三期节目结束前请嘉宾展望一下未来,你怎么看未来 10 年的技术发展趋势?\n乔布斯:我看好互联网和 web。 软件行业正在发生两件激动人心的事:一个是面向对象编程,另一个就是 web。web 将实现我们盼望已久的梦想,计算机不再仅仅充当计算工具,开始承担通信功能。可喜的是微软还没发现这一点。\n:: 小知识:世界上第一个网站,便是在 next 电脑上完成的。\n创新的机会很多,web 将深刻改变我们的社会。你知道美国有 15%的商品是通过电视购物销售的,电视购物很快会被 web 取代。网络销售的潜力巨大,网络将成为最直接的销售渠道,而且在网络上小公司与大公司看起来没有区别。如果将来回顾计算机发展历史,web 技术必然成为重要的里程碑,它的潜力很大,会吸引更多年轻人进入计算机行业。\n:: 只能叹服,成功没有偶然!伟大也没有偶然!\nbob:你们正在开发……\n乔布斯:不仅是我们,web 为 it 行业开启了新的大门。\nbob:放在 5 年之前,谁能想象得到呢?\n乔布斯:没错,多么奇妙的行业呀!\nbob:(补充旁白)我很想知道他的工作热情来自哪里?是什么在激励他?\n乔布斯:我小时候读过《科学美国人》杂志的一篇文章,杂志比较了地球上不同物种的移动效率,比如熊、猩猩、浣熊、鸟类、鱼类等,计算它们每移动一公里消耗的热量,还有人类。最后秃鹫赢了,它的移动效率最高,作为万物之灵的人类,排在倒数第几位。但是杂志特地测量了人类骑自行车的效率,结果把秃鹫远远甩在了身后,在排名上遥遥领先。这篇文章给我留下了深刻的印象,人类擅长发明工具,工具赋予我们奇妙的能力。\n苹果以前有一条广告:计算机是思想的自行车。我坚信如果将来回顾人类历史,计算机将是人类最伟大的发明之一,我觉得自己非常幸运,在硅谷参与这项发明。这就好比画几何向量,开始时失之毫厘,结果会谬以千里,我们刚刚起步,只要找对方向,以后就会非常顺利,我们已经尝试了几次,结果让人非常满意。\nbob:你怎么知道哪个方向是正确的?\n乔布斯:最终得由你的品味来决定。你要熟悉人类在各种领域的优秀成果,尝试把它们运用到你的工作里。毕加索说过:拙工抄,巧匠盗。我从来不觉得借鉴别人的创意可耻。\nmacintosh 团队里有音乐家,有诗人、艺术家、动物学家、历史学家,这些人也懂计算机,所以 macintosh 才这么出色。如果没有计算机,他们也会在其他领域造创奇迹。大家各自贡献自己的专业知识,macintosh 因此吸收了各个领域的优秀成果,否则的话,它很可能是一款非常狭隘的产品。\nbob:最后我问一个规定问题:你是嬉皮士,还是书呆子?\n乔布斯:如果必须二选一的话,我肯定是嬉皮士,我所有的同事都属于嬉皮士。\nbob:真的吗?\n乔布斯:是的。\nbob:为什么?你有意招聘嬉皮士吗?他们吸引你?\n乔布斯:你觉得什么是嬉皮士?不同的人有不同的理解,但是对我来说,60~70 年代的嬉皮士运动给我留下了深刻印象,有些活动就是在我家后院举行的,嬉皮士运动启发了我。\n有些东西是超越日常忙碌琐碎的生活的。生活不仅仅是工作、家庭、财产、职业,它更丰富,就像硬币还有另一面。虽然大家嘴上不说,但在生活的间隙,尤其是在不如意的时候,我们都能感受到某种冲动,许多人想找回生活的意义。有人去流浪,有人在印度神秘仪式里寻找答案。\n嬉皮士运动大概就是这样,他们想寻找生活的真相,生活不应该是父母过的那样。当然,后来运动变得太极端了,但是他们的出发点是可贵的。正是因为这种精神,有人宁愿当诗人也不愿做银行家。\n我很欣赏这种精神,我想把这种精神溶入产品里,只要用户使用产品,就能感受到这种精神。macintosh 的用户真心喜欢我们的产品,在此之前,你很少听人说真心喜欢商业产品,但你可以从 macintosh 感受到某种奇妙的东西,我觉得优秀的同事都不是为了计算机而工作,而是因为计算机是传达某种情感的最佳媒介。他们渴望分享,你理解吗?\nbob:当然。\n乔布斯:如果没有计算机,我们可能会从事其他行业,是计算机让我们这些从小接触它的人走到了一起,计算机就是我传达情感的媒介。\n七、结语 采访结束一年后(1996 年),steve 将 next 出售给苹果。在苹果即将破产之际,乔布斯重新掌管了公司,随后展开了美国商业史上绝无仅有的拯救行动,随着 imac、ipod、itunes、iphone、ipad 等创新产品的推出,乔布斯将一家濒临破产的企业改造成全美最有价值的公司。\n正如他在采访中所言,他追求极致,分享给同类,这样人类才能共同进步。\n作者:大唐炼金师\r链接:https://xueqiu.com/8959246745/173703587\r来源:雪球\r著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ","date":"2023-12-19","permalink":"https://aituyaa.com/%E4%B9%94%E5%B8%83%E6%96%AF-%E9%81%97%E5%A4%B1%E7%9A%84%E8%AE%BF%E8%B0%88/","summary":"\u003cp\u003e以下为史蒂夫·乔布斯《遗失的访谈》全文。\u003ca href=\"https://www.bilibili.com/video/BV1Qp4y1H7TT/?vd_source=a6f6452712ce1cd91d115827d0148715\"\u003e视频访谈\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20231219172620.png|400]]\u003c/p\u003e","title":"乔布斯·遗失的访谈"},]
[{"content":"相信每天写博文并且喜欢多平台发布的人,或早或晚都会遇到‘图床’的相关问题。折腾图床好多天了,仅以此篇做一个阶段性的总结或终止符。\n图床是什么,为什么需要它 顾名思义,‘图床’就是用来放‘图’的‘床’。它要允许你‘放’上去,还要允许你‘拿’下来用。简单来说,它是一个方便你从外部引用的在线图片仓库。\n为什么需要它?主要原因在于方便文章的迁移和多平台发布。\n没有配图的文章略显枯燥,不少时候,图像的表现能力不是文字可以比拟的。插入合适的配图非常有利于提升文章的格调和阅读体验。\n![[assets/pasted image 20231108101526.png]]\n引用在线的图片,图片本身的插入相对于文件来说就是一串字符,多平台迁移的时候,直接粘贴过去就行了,无需考虑图片的路径问题。\n:: 这里要批评一下简书,这个平台对于从其他平台迁移文章真的是极度不友好,或者说就是个纯纯的垃圾!\n为什么不直接引用别人的图片资源?\n首先,你无法确定引用的图片资源在将来的某个时间会不会被删除或更改;其次,鉴于流量费用的问题,别人的图片资源可能并不允许其他的站点加载;最后,图片资源可能有私密性,并不想在自己不主动引用的时候被其他人查看。\n有哪些图床,怎么选 这里只例举几日来我使用和了解的图床部署 - sm.ms 和 七牛云,其他诸如腾讯的 cos、阿里的 oos 等不多讨论,它们的原理都是大同小异的。\nsm.ms 是一个国外的图床平台,个人用户注册后会有 5g 大小的存储空间,上传后的图片可以缩略预览,提供了国内的访问备用链接 smms.app ,速度还可以。\n:: 不要小看这个在线图片的缩略预览,试用的几个平台都没有这个功能,你只能一张张的打开图片查看详情……\n七牛云为实名认证的个人用户提供 10g 的免费空间和每月 10g 的 http 上下行网络流量,横向对比来看,算是比较良心的了。不过需要注意的是,只对 http 协议免费,对 https 是需要收取费用的。具体查阅其付费标准即可,这些并不是本文的关注点。\n通常,使用 http 协议即可,当你迁移粘贴到其他平台的时候,该平台会根据你的图片引用地址自动下载到其本身的图片存储空间,并不会导致无法被正常显示的问题。\n腾讯的 cos 对新用户也有差不多半年的免费试用期。\n图床平台,基本都是使用基于对象存储的模式运行的。\n具体怎么使用图床 试想,如果你要插入一张图片到文章中,是下面这样一个过程:\n1. 找到自己想要的图片(通常还需要在线检索);\r2. 拷贝或另存到本地再拷贝;\r3. 上传到图床平台生成外部引用链接;\r4. 复制图片链接,再编辑相应语法格式插入文章…… 是不是要疯?肯定的!好在我们有工具可以简化这个过程,许多编辑器都提供了相关插件支持。\n我使用 obsidian 来编辑和管理文章内容,它有 image auto upload plugin 这个插件来实现粘贴图片到文章时,调用相关工具自动上传当前图片,并格式化合适的图片插入语法插入到文章中。\n![[assets/pasted image 20231108110726.png]]\n这个插件调用的是什么工具呢?picgo 或 piclist,后者是基于前者的二次开发,提供的更多的功能(推荐直接使用后者)。\n![[assets/pasted image 20231108111317.png]]\n我们这里以 piclist 来做进一步的说明,它基本支持目前所有的主流图床平台。除了上述所说的在 obsidian 中通过插件调用,你还可以直接使用它提供的 gui 工具来上传图片。\n具体的图床配置,这里就不多说了,查阅相应的文档即可。\npiclist 比较让中意的地方在于,它的 管理 项支持在线观看相应的图床平台中已经上传了的图片(同样不支持图片预览),再者,相册 项允许在当前主机实时预览上传的图片项,并且提供了同时删除云端图片的功能。\npiclist 还提供了,上传图片时重命名的功能,可以手动,也可以自动(支持使用 md5、时间戳、uuid 等多种命名格式)。如上所述,这些平台都不支持已上传图片的缩略预览(通过其他脚本可以曲线支持),如果图片名称不易查询的话,后期图片资源的管理就会成为一个问题。\n关于图片重命名,个人不怎么在意图片的名称是什么,给图片命名也是一个挺消耗脑力的事情。如果,你不希望图片重复存储而占用图床空间,那么就使用类似于 md5 这种针对文件本身解析,如此每次都会产生同样的字串,就可以了。如果,你不介意多占用一些空间,那么默认的命名设置也是完全可以满足需求的。\n关于已上传图片在平台存储桶中无法预览的问题,可以通过 chrome 的插件来解决,详情参阅 七牛在线管理图片预览chrome插件,亲测可用。\n:: 貌似上述插件已经挂了\u0026hellip;.\n题外话,图床这玩意儿,你还得提防哪天崩了。数据世界从来就没有绝对的安全,做好备份永远是一个较好的选择。\n结语 综上,就没有一个让你不用费神就拥有完美体验的软件!目前使用 obsidian + image auto upload plugin + piclist + 七牛云及相应的 chrome 插件 ,只能说差强人意。\nps:折腾了几天之后 ,弄完了,也让我对图床的使用变得敬而远之了。想想,还要迁移编辑之间的历史文章,脑袋就疼。目前,还是采用原来本地图片根目录图片引用的方式,体验不错。看,回过头,你才发现,‘旧’不一定不如‘新’。\n","date":"2023-11-03","permalink":"https://aituyaa.com/%E5%9B%BE%E5%BA%8A%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e相信每天写博文并且喜欢多平台发布的人,或早或晚都会遇到‘图床’的相关问题。折腾图床好多天了,仅以此篇做一个阶段性的总结或终止符。\u003c/p\u003e","title":"图床那些事儿"},]
[{"content":"📘 cdn回源流出流量是什么意思?\n今天以对象存储 + cdn 为例,跟大家讲下 cdn 回源流出流量是什么意思?我们在使用 cdn 时会在管理后台的资源监控页面中,发现回源流出流量较大,新人可能会不大清楚这个概念,接下来跟大家详细分享。\ncdn 是什么 cdn(content delivery network,内容分发网络)是构建在现有互联网基础之上的一层智能虚拟网络,通过在网络各处部署节点服务器,实现将源站内容分发至所有 cdn 节点,使用户可以就近获得所需的内容。cdn 服务缩短了用户查看内容的访问延迟,提高了用户访问网站的响应速度与网站的可用性,解决了网络带宽小、用户访问量大、网点分布不均等问题。\n![[assets/pasted image 20231102120358.png]]\n工作过程 用户浏览器会先检查本地的缓存是否过期,如果过期,则向 cdn 边缘节点发起请求,cdn 边缘节点会检测用户请求数据的缓存是否过期,如果没有过期,则直接响应用户请求,此时一个完成 http 请求结束;如果数据已经过期,那么 cdn 还需要向源站发出回源请求(back to the source request),来拉取最新的数据。而 cdn 服务商会提供“刷新缓存”这个功能来清理 cdn 边缘节点的缓存,这样我们在更新数据后,可以使用“刷新缓存”功能来让 cdn 节点上的数据更新,保证客户端在访问时,拉取到最新的数据。\ncnd 回源流出流量 ![[assets/pasted image 20231102120429.png]]\n\u0026gt; 存储cdn访问流程图\n以上是 对象存储+cdn 访问的简化流程图,接下来的步骤号码将对应流程图中数字。\n场景一:cdn 节点「没有缓存」时,需要回源站拉取资源并缓存,访问步骤为:\n1、客户访问端发送访问请求到 cdn 节点。 2、cdn 节点进行回源,发送请求到云存储。 3、云存储返回数据给 cdn 节点。 4、cdn 节点缓存数据,并由 cdn 返回数据给客户端。 这里回源产生的费用说明:该过程中,产生的计费项目为 流程 3 产生的存储 cdn 回源流出流量 + 流程 4 产生的 cdn 访问流量。\n场景二:cdn 节点「有缓存」时,访问步骤为:\n1、客户访问端发送访问请求到 cdn 节点。 4、cdn 节点返回数据给客户端。 这个过程产生的计费项目为 流程 4 产生的 cdn 访问流量,所以如果 cdn 有缓存就不会进行回源,也就没有产生存储 cdn 回源流出流量。\n","date":"2023-11-02","permalink":"https://aituyaa.com/cdn-%E5%9B%9E%E6%BA%90%E6%B5%81%E5%87%BA%E6%B5%81%E9%87%8F/","summary":"\u003cp\u003e\u003ca href=\"https://78moban.com/article/2235.html\"\u003e📘 CDN回源流出流量是什么意思?\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e今天以对象存储 + CDN 为例,跟大家讲下 CDN 回源流出流量是什么意思?我们在使用 CDN 时会在管理后台的资源监控页面中,发现回源流出流量较大,新人可能会不大清楚这个概念,接下来跟大家详细分享。\u003c/p\u003e","title":"cdn 回源流出流量"},]
[{"content":"java 使用 `enum` 定义枚举类型,它被编译器编译为 `final class xxx extends enum { … }`;\r- 通过 `name()` 获取常量定义的字符串,注意不要使用 `tostring()`;\r- 通过 `ordinal()` 返回常量定义的顺序(无实质意义);\r- 可以为 `enum` 编写构造方法、字段和方法:\r- `enum` 的构造方法要声明为 `private`,字段强烈建议声明为 `final`;\r- `enum` 适合用在 `switch` 语句中。 具体参考: 枚举类 - 廖雪峰的官方网站\n","date":"2023-11-01","permalink":"https://aituyaa.com/%E6%9E%9A%E4%B8%BE/","summary":"Java 使用 `enum` 定义枚举类型,它被编译器编译为 `final class Xxx extends Enum { … }`; - 通过 `name()` 获取常量定义的字符串,注意不要使用 `toString()`; - 通过 `ordinal()` 返回常量定义的顺序(无实质","title":"枚举"},]
[{"content":"摘录自 《易经》入门及全文解析\n学习易经 《易经》是一个取之不尽用之不竭的智慧源泉,它的思想贯穿于中国古代所有文化中,是中国文化的总枢纽。\n《易经》很难学?\n读《易经》之前,我们要知道两个问题:一、《易经》思想是“百姓日用而不知”的,二、高深的道理都在生活中的小事小物上有“映像”。\n《易经》中确实有一些读了哲学博士后可能还不明白的东西,但更多的是中学毕业就可以轻松掌握的内容。问题的关键在于你要获取它的哪个侧面。\n《易经》虽然神奇,但它并没有人们想象的那些“神通力”。不过,有些人宁愿相信它的神通,而不愿相信它的平凡。《易经》有的是“预知力”,且范围很广:测知过去、了解当下、预知未来;测事业、财运、婚姻……不过,这些是懂得《易经》原理后才能做到的。\n对于初学者,先要驱除上面那些妄念,将它的作者看成是平常人,将《易经》看成是平常的书。\n《易经》是否符合科学依据?\n一些科学家给出了回答,只是不太好懂。比如心理学家荣格认为,心理和物理事件的发生在时间上存在平行的关系。这是一种共时性现象,《易经》的“同声相应,同气相求”反映的就是这种规律。宇宙形成时,万物都遵循共同的规则生成,因此多种独立系统在功能结构和信息传递上有“同步共振”的关系。在这些系统中,可归为同类的事物在平行地运行,所以我们可以举一反三、闻一知十。\n学习《易经》对我们有什么用?\n很多人觉得《易经》好像只有占筮一种用途。其实非也。马克思曾说:“人类把握世界的方式有四种:哲学、科学、劳动实践、宗教信仰。”而用孔子的话说,《易经》有四种运用之道:学习哲理、指导行动、运用器物、卜筮预测。《易经》的伟大就在于它在这四大领域都登峰造极。\n:: 大道万千,殊途同归!\n一、学习哲理:《易经》64 卦、384 爻中蕴藏着极其丰富的人生哲理,它就像一本字典,只要对号入座,所有人生课题都能从中找到答案。\n二、指导行动:《易经》的卦辞爻辞不仅对人生事象做出了描述,而且还有针对性地给出了处事之法、应变之策、化解之道,学完《易经》的人绝不会成为书呆子。\n三、运用器物:《易经》中不仅蕴藏着抽象的精神性的东西,还有很多具体形象的规律,它可以激发我们艺术创作、工具发明、科技探索的灵感。\n四、卜筮预测:追根溯源,《易经》是本卜筮之书。卜筮就是向《易经》这位无所不知的万能老师求教。卜筮在“二诸葛、赵半仙”的手中是骗人的把戏,但在智者手中则是帮助我们透视人生、优化行为的良师益友。\n如何用最少的时间掌握《易经》中最重要、最根本的知识?这些知识又是什么呢?\n简单地说,《易经》的核心知识有三类:64 卦、384 爻、4096 种卦变,其用途如下:\n第一步,了解当下:64 卦是有关人生的 64 个专题,只要摇得一卦,就可以从《易经》中找到你所需的专题,获知自己当前处于什么境况中。\n第二步,指导行为:知道了现况,该怎么做呢?64 卦中的 384 爻描述了 384 种处境,《易经》分别提供了最佳对策,由此你知道了应该怎么做。\n第三步,预测未来:64 卦中的每一卦都可以变出 64 卦,结果共有 4096 种卦象转变,代表事情在未来的 4096 种变化。你得到的卦一定是其中的一种,因此你知道了未来是什么样。\n认识易经 《易经》也称《周易》,在古代是帝王之学,是政治家、军事家、哲学家的必修之术。但从本质上来讲,它是一本关于“卜筮”的书。它对中国文化产生了极其巨大的影响。\n易经的地位 华夏文化博大精深,源远流长。在这大海般辉煌灿烂的文化宝藏中,有一朵奇葩被誉为华夏文明之源,它就是《易经》,后人称为“经典中之经典,哲学中之哲学,智慧中之智慧”。\n《易经》:世界上最智慧的书\n在人类历史上,公认最具智慧的书籍有三本:中国的《易经》、印度的《吠陀》和欧洲的《圣经》。其中,《吠陀》和《圣经》兼有史书和宗教典籍的性质,而《易经》则与历史和宗教没有直接关系,它探讨的是整个世界的运行规律。\n![[assets/pasted image 20231027144100.png]]\n《易经》是中国传统文化的开山之作,也是凝结着远古先民睿智卓识的哲学著作。《易经》上通天文,下通地理,中通人事,以无所不包的终极原理来统摄万物。通过对《易经》的研究,不但可以穷究天人之理、通晓古今之变,还可以学到基本的处世法则,对指导人生有重大意义。\n“周易”为什么叫“周易”?\n简单来说,《易经》一般又称《周易》。关于《周易》的“周”字,历来有多种说法。有人认为,“周”是“易道周普无所不备”的意思,因为《易经》以“六十四卦三百八十四爻”笼罩万物,卦爻的含义必然具有极其广泛的普适性。也有人认为,“周”特指周朝,是年代的意思。对“易”字的解释则更多,主要包括三种观点:“易”这个字是把“日”、“月”两个字重叠而来的,上面是日,下面是月;易是飞鸟的形象;易是蜥蜴,蜥蜴因环境而改变自身颜色,这就是易——变化。\n今天的学者们大多认为“易”一词包含三种内容:简易、变易、不易。\n![[assets/pasted image 20231027144645.png]]\n“周易”两个字无论怎么解释,其根本都是在讲阴、阳两种势力的相互作用,“刚柔相推,变在其中”。\n《易经》对后世有哪些影响?\n《易经》是中华文化之根,它大约产生于新石器时代早期,是中国进入文明社会的标志。它是我国最早的文化典籍,对文字、儒学、道教、中医、术数、哲学、民俗文化等产生了重要影响。此外,现代科学的各个分支中也都能找到《易经》的身影。\n![[assets/pasted image 20231027144905.png|325]]\n\u0026gt; 计算机与《易经》\n:: 最下面自右到左,正好是先天八卦之数 1 ~ 8 ,按 8 取补,自左至右正好是 0 ~ 7 。\n其中,1703 年 5 月 5 日,莱布尼茨拖延多年的关于二进制的论文正式发表 - 《关于仅用 0 与 1 两个记号的二进制算术的说明并附有其效用及关于据此解释古代中国伏羲图的探讨》,很有趣。详细内容可以参阅 二进制中国发明权辩识 ——《易经》二进制发明及莱布尼茨对发明的“发现”。\n易经的由来 对于《易经》产生的年代,历来说法不一。据学者们考证,它大约产生在 5000 年前,也有人认为是 7000 年前。其成书的年代可能在商末周初。对于《易经》的内容,一般认为它源于河图、洛书。\n《易经》的产生有“人更三圣,世历三古”的说法。它的成书经历了上古、中古、下古三个时代,即“三古”;它由伏羲、文王、孔子三个圣人完成,即“三圣”。也有人认为爻辞是周文王的儿子周公写的。\n![[assets/pasted image 20231027150846.png]]\n最原始的《易经》有三种,即“三易”。一是神农时代的《连山易》,二是黄帝时代的《归藏易》,三是周文王被囚时所著的《周易》。\n易经的观念 古人将人和自然看成一个有机整体,认为人和宇宙万物都存在着密切的联系,人是万物之“灵”,是宇宙之精华,每个人都是一个小宇宙。“天人合一”是大小“两个宇宙”之间的固有联系和内在统一。\n《易经》中“天人合一”的观念\n《易经》中用乾、坤二卦分别代表天和地,万物“统”于天。地与天相辅相成、对立统一,但它毕竟是“顺承天”的。天为父,是生命之源;地为母,是一切生命得以存在的基础。人在天地间,与它们不可割离。尽管人受天地所生,只是万物中的一员,但人不同于万物,因为人有超越万物的感情和理智,这种灵性被赋予了神圣的使命,用《易经》的话说就是:“裁成天地之道,辅相天地之宜。”天地提供了人类生存所需要的一切,人类在从中汲取能量的同时就要“裁成”、“辅佐”天地顺利地运行,这是“天人合一”思想的宗旨。\n![[assets/pasted image 20231027151331.png]]\n\u0026gt; 卦象中的天地人三才\n“天人合一”思想的核心是:天地有其运行的规律,人类社会也有其运行的规律,不是哪种规律顺应另一种规律,而是两者相互对应、并行不悖。它们的关系的本质是“映射”,而不是“决定”。\n![[assets/pasted image 20231027151431.png]]\n《易经》描绘的这种“映射”的规律把整个世界都囊括在内,使所有事物都处于普遍联系的链条中。这种“全息性”或“相互感应”的宇宙思维模式为人们构建了一个包罗天、地、人、事、物在内的不可分割的世界。\n为何会有“天人合一”?\n宇宙运转的根本规律被古人称为“道”,它高度抽象,难以名状;人在宇宙中顺势而为的品性被称为“德”。对“道”的了解和对“德”的把握若达到一定程度,不仅能识破天机,还能在宇宙的大旋涡中顺势借力增强自身小宇宙的能量。\n易经的内核 《系辞》中说:“一阴一阳之谓道。”这与马克思主义哲学中的“矛盾对立统一规律”如出一辙。与后者不同的是,在《易经》的庞杂体系之中,阴阳之间的相互作用被演化得极其复杂,万物的变化规律都在阴阳的范围内。\n《易经》的核心:阴阳\n《易经》认为,整个世界在阴阳两种相反相成的力量的互相作用下不断运动、变化:生成-更新-消亡-再造……《易经》的卦象就建立在阴阳变化的基础上:阴阳符号按阴阳二气消长的规律,经过排列组合而成为八卦。八卦的构成与排列反映了阴阳对立统一的思想。八卦又经过重叠而组合为六十四卦。六十四卦又有错综复杂的变化,可以演化至无穷。但不管卦象如何演变,阴阳爻都是组成卦象的最基本元素。所以说阴阳是《易经》的核心。\n除了卦象上的一阴一阳,《易经》还将阴阳作为万物的性质及其变化的法则,把具体的事物赋予了阴阳的含义。从自然现象上看,《易经》以天为阳,以地为阴;日为阳,月为阴;暑为阳,寒为阴;明为阳,暗为阴;昼为阳,夜为阴。\n此外,《易经》还对自然和人类社会中共有的抽象规律赋予了阴阳的含义,如:刚柔、进退、开合、伸屈、贵贱、高低等,都是阴阳思想的反映。\n阴阳:矛盾的对立统一\n易经的唯物性 《系辞》中的“生生之谓易”是对“易是什么”的最好回答,也是对《易经》核心思想的透彻说明。“生生”的观点驳斥了“神创论”,是彻底的唯物主义,常被称为“古代朴素唯物主义”。\n《易经》认为,世界不是由一个万能的“主宰者”来创造生命,而是万物遵循一定的变化法则使得自身不断地更新,这就是“生生”。事物都处在连续不断、时刻不停的变化过程中。\n《易经》还认为,天地以“生”为“德”,这就把“生”的客观运动附加上了主观意义,把没有智慧和灵性的事物演化融入了人类有情有智的社会中,这包含着《易经》作者认为的人类生命的目的,以及人类作为万物之灵的与众不同的本质。\n易经的四大法门 古人将易象、易数、易义、易理称为“易之四德”。简单地说,“象”是卦及其所象征的事物的形象,“数”反映卦象中的数理关系,“义”表达了伦理道义的思想,“理”是对万物的规律所做的探讨。\n六十四卦最初没有文字说明。孔子注《易传》,从象数角度解释卦爻辞,赋予其哲理。在孔子之后,易学发展出“象数”、“义理”两大学派。根据研究思路的不同,《易经》研究者分为四大派:象、数、义、理;而象与数、义与理又可分别归为一类。有人认为,象数派接近自然科学,义理派接近社会科学。象数讲的是事实的“必然”性,义理则着重讲为何会“必然”。象数是《易经》的基础,《易传》的义理由象数变化而出。\n![[assets/pasted image 20231027154231.png]]\n象:体现《易经》符号能象征的事物及时间方位关系,含有现象、意象、法象等含义,是宇宙统一理论模式,除卦象、爻象外,太极图、八卦图、六十四卦图等都为象。\n数:是《易经》占筮求卦的基础,是《易经》的数理表达,是对“象”的定量研究,除筮数、爻象外,还有阴阳数、大衍数、天地数、卦数、河图数、洛书数等。\n义:体现《易经》的原意。有学者认为,卦辞、爻辞为第一意义系统,《彖》、《象》、《文言》为第二意义系统,《说卦》、《序卦》、《杂卦》、《系辞》为第三意义系统。\n理:体现《易经》的哲学思想,包括天道观、人道观、天人观、辩证法等。\n占:象、数、义、理在社会生活、人生实践中的具体运用,其分支众多。\n《易经》象数学派、义理学派\n《易经》中的“象”有三种含义:一是八卦及六十四卦的形状,二是八卦所象征的事物的形象,三是卦辞和爻辞中提到的具体事物的形象。“数”也有三种含义:一是表示卦中各爻属性的数,二是表示爻位顺序的数,三是占卜过程中的一种计算方法。两汉时期,《易经》被尊为儒家五经之首,成为显学。以孟喜、京房为代表的两汉易学家认为,《易经》中最重要的是象,一切卦辞和爻辞都是以象为基础的,所以他们从象数角度解释《易经》。经过一段时间的衰落,北宋时期的华山道士陈抟重新振兴了象数学派。陈抟创制了包括太极图在内的各种图式,用以解说《易经》卦象爻辞。他的继承者邵雍则以创制的“皇极经世”图取代汉朝的“天人感应”说。\n汉末儒生以象数解《易经》,过于追求卦辞、爻辞与卦象的一一对应,这使得他们的体系变得非常复杂。孔子写易传时就出现了义理派,但成体系的义理派开始于三国时的少年天才王弼。王弼针对当时象数学派的弊病,提出了“一爻为主说”“爻变说”和“适时说”,驳斥汉儒牵强附会的“按文责卦”的方法,主张着重领会和把握《易经》的根本义理。北宋王安石等人则从历史角度解《易经》,不追究卦象本身的意义,而是引用历史事实证明《易经》理论的正确性。\n两派六宗\n《四库全书提要》把《易经》研习流派分为两大类:象数派和义理派。象数派分为占卜、祥、图书三宗;义理派分为老庄、儒理、史事三宗。\n![[assets/pasted image 20231027153905.png]]\n国学大师南怀瑾把易学的“两派六宗”扩充成“两派十宗”,两派包括道家易学、儒家易学;十宗包括占卜、祥、图书、老庄、儒理、史事、医药、丹道、堪舆、星相。\n易经的影响 《易经》自产生以来,一直对中国的政治、经济、军事、民俗发挥着巨大的影响。今天,它仍然在众多方面发挥着作用。很多学者将《易经》与中医、建筑、物理学、遗传学等学科联系起来,希望从中获得启发。\n中医与《易经》有什么渊源?\n中医的理论基础是与《易经》哲学思想相通的,人们称之为“医易同源”。中医理论经典《黄帝内经》充分汲取了《易经》中的精华,特别是其中的阴阳五行学说,直接来源于《易经》。\n![[assets/pasted image 20231027155145.png]]\n西医把人体视为孤立的封闭系统,认为人体在与环境中的致病因子抗衡。中医则把人体看成是与外界沟通的开放系统,认为人体内各要素之间的不平衡是疾病的根源……\n![[assets/pasted image 20231027155518.png]]\n\u0026gt; 阴阳与五行的结合\n当阴阳观念与中医结合后,形成了一种方法论。阴阳之间的各种规律在五行之间同样适用。五行规律是对阴阳规律的一次继承性的发展。中医认为人体疾病的产生、发展超越不了这个规律。\n《易经》对中国建筑的影响\n《易经》对中国建筑的影响也极为深远。它以其“意”与“象”结合的思维模式,以及阴阳协调的思想,孕育了世界上独一无二的中国古代风水学,同时也使得这一学问在中国建筑领域发挥得淋漓尽致。\n![[assets/pasted image 20231027155650.png]]\n\u0026gt; 故宫的建筑布局\n易经中的五术 五术即“山、医、命、相、卜”,源于“一阴一阳之谓道”的思想。“五术”的概念产生于唐朝,它以《易经》为哲学根基,以追求人与自然和谐统一为目的,探窥天道运行的秩序和人生奥秘。\n“山”是指通过修道、修炼寻求身心超脱的一种学问,包括玄典、养生、修密三类。“医”是医术,利用方剂、针灸、灵治等方术来保持健康、治疗疾病。“命”是通过推命的方式来了解人生、穷达自然。“相”是指通过观察人、地、物等形象来研究人类命运的方法,主要包括名相、人相、风水(地相)等。“卜”包括占卜、选吉、测局三种,如纳甲筮法、梅花易数、三式(六壬、奇门遁甲、太乙神数)等。还有占梦、测字、签贴等方术,统称为“杂卜”。\n五术是古人从《易经》中衍生出来的认识世界、认识自我、适应世界、改变人生的思想和方法,被广泛运用于社会生活的各个方面。\n易经的主要内容 《易经》包括《经》和《传》两部分内容。《经》是全书的核心,《传》则是解释《经》的。一般认为《经》是周文王和周公共著的,《传》则是后人积累并由孔子整理成的。\n《经》是一部占筮书,《传》是一部哲学书,也可以说《经》是带有哲学色彩的卜筮书,《传》是带有卜筮色彩的哲学书。两者合为一体才叫《周易》,现在也称为《易经》。\n![[assets/pasted image 20231026115448.png]]\n《经》\n《经》分为《上经》和《下经》,《上经》三十卦,《下经》三十四卦,共六十四卦。每一卦由卦画、标题、卦辞、爻辞组成。六十四卦是由“乾坎艮震巽离坤兑”这八卦重叠演变而来的。每个卦画都有六个爻,爻分阴阳,阳性之爻简称为“九”,阴性之爻简称为“六”。卦爻从下向上排列成六行,依次叫做“初、二、三、四、五、上”。六十四卦共有三百八十四爻。每卦的标题与卦辞、爻辞的内容有关。卦辞在爻辞之前,起说明题义的作用。爻辞是每卦内容的主要部分,其内容按六爻的顺序排列。\n![[assets/pasted image 20231026115536.png]]\n《传》\n《传》共有七种十篇,分别是:《彖》(tuàn)上下篇、《象》上下篇、《文言》《系辞》(上、下篇)、《说卦》《杂卦》和《序卦》。古人把这十篇叫做 “十翼”,意思是:《传》是附属于《经》的羽翼,用来解说《经》的内容。\n《彖》是对《易经》卦名和卦辞的注释。\n《象》是对《易经》卦名和爻辞的注释。\n《文言》对乾坤二卦作了进一步的解释。\n《系辞》与《彖》《象》不同,它不是对卦辞、爻辞的逐项注释,而是对《易经》的整体评释,是《易经》的哲学纲领和必读之篇,是“十翼”中最重要、最有代表性的文字。它也是古代第一部对《易经》的产生、原理、意义及易卦占法的全面系统的说明,它是《易经》的哲学纲领,阐发了许多《易经》经文中没有的思想。\n《说卦》是对八卦卦象的具体说明,是研究术数的理论基础之一。\n《杂卦》将六十四卦以相反或相错的形态排成两两相对的综卦和错卦,从卦象中看卦与卦之间的联系。\n《序卦》讲述了六十四卦的排列次序。\n易经的基础 从源头上讲起,内容涉及河图洛书、阴阳五行、八卦与六十四卦、天干地支等,这些内容是《易经》的基础。\n河图洛书 河图与洛书是中国古代流传下来的两幅神秘图像,历来被认为是《易经》、阴阳五行乃至中华文明之源。太极、八卦、九星等术数学基本概念皆可追源至此。它被誉为“宇宙魔方”。\n![[assets/pasted image 20231027161444.png]]\n河图与洛书之间是有联系的,如果将河图四方的八个数旋转而排成八方,每方一个数纳地支十二象,就是洛书。\n传说中,河图与洛书都是源自上古时期,在几千年的历史中,人们对它们的理解并不完全一致,各时代的古籍中所记载的河图洛书的图式也有很大的差别。\n太极 古人认为,太极是万物的本源,但在太极前还有无极。太极是有限的,无极是无限的。有限来自于无限,所以无极是比太极更原始终极的状态。\n“太极”是什么意思?\n太极也叫太初、太一,它是天地未分之前的原始而无穷的混沌状态,也是阴阳相合的统一状态。极是运动不息的,动则产生阳气,动到一定程度就会相对静止,静止则产生阴气。这样一动一静,阴阳之气互为其根,运转至无穷。\n太极之理涵盖万物。它在天上是天理,在地上是地理,在万物是物理,在医学是医理,在命运是命理,在人伦是伦理……无穷无尽,但其本质是“一”。\n《老子》中说:“一生二,二生三,三生万物。”一可以形象地把太极比喻为“一”。“一生二,二生三”,二就是阴阳两仪。阴阳之间的对立斗争又相互滋生依存的关系,是事物的纲领和由来,也是事物产生与毁灭的根由。比如八卦中的乾坤,乾为阳,坤为阴,“乾道成男,坤道成女”,阴阳交合导致“二生三”;万物按此规律生生不息,变化无穷,就是“三生万物”。\n人类作为万物中的一员,可以根据太极的演变规律倒推回去,了解万物的初始。\n太极是万物的总根源,它是混沌而又能包含一切的。《说文解字》中说:“惟初太极,道立于一,造分天地,化成万物。”太极是天地、乾坤、刚柔、阴阳等一切相对事物的混合体,可以不断二分,化生万物。但无论经过多少次的二分,其分子永远是太极。所以朱熹说:“在天地言,则天地中有太极;在万物言,则万物中各有太极。”\n阴阳学说的完美诠释:太极图\n![[assets/pasted image 20231027163302.png]]\n\u0026gt; 阴阳的属性\n阴阳理论渗透到了中国传统文化的方方面面,如哲学、宗教、历法、中医、书法、文学、建筑、占卜等。对阴阳思想的最形象的图示就是北宋时出现的太极图。它以一条曲线将圆形分为两半,形成一半白一半黑,白者像阳,黑者像阴,白中又有一个黑点,黑中又有一个白点,表示阳中有阴,阴中有阳。分开的两半像两条鱼,俗称“阴阳鱼”。\n![[assets/pasted image 20231027172509.png|400]]\n\u0026gt; 阴阳的关系\n太极图是怎么形成的?\n太极图是对阴阳学说的完美诠释。“太极”一词首见于《系辞》,但无图形。直到宋代才由陈抟传出太极图。\n详见 太极图是怎么形成的?- 易经太极\n阴阳 《易经》中把对立统一的事物称为两仪,即阴阳。古人认为:“无阳则阴无以生,无阴则阳无以化。”阴阳两者间存在着相互依靠、相互制约、相互转化等关系。\n阴阳与五行的结合\n阴阳五行学说是中国古代朴素的唯物论和辩证法思想,它认为世界在阴阳二气的推动下产生、发展和变化;五行这五种基本物质是构成世界不可缺少的元素,它们与阴阳一同处于不断的运动变化中。\n阴阳:古老的辩证法\n两仪,是一切可以二分的、相对的事物和规律。如:天地、日月、昼夜、寒暑、男女、上下等,概括起来叫“阴阳”。代表阳的事物如:天、父、雄性、热、昼、表面、过去、破坏力、单数;代表阴的事物如:地、母、雌性、寒、夜、里面、未来、包容力、复数。对人来说,意志为阳,躯体为阴;理智为阳,欲望为阴。对社会来说,大众为阳,小我为阴;公益为阳,私利为阴……\n阴阳虽是划分万物对立统一的依据,但对立的双方的阴阳属性不是任意的,而是按照一定规律固定下来的。所以,“乾道成男,坤道成女”,而不能是“乾道成女,坤道成男”。\n详细来说,事物的阴阳具有以下属性:\n1.相对性:阴阳属性是相对的。如:中原十月份的气候较之七月份的炎夏,属阴;但较之十二月份的严冬,又属阳。\n2.相关性:阴阳属性不是孤立的,用阴阳分析的事物应该在同一范畴、层次或交点上。不相关的事物或现象不宜分阴阳。如:理智为阳,欲望为阴;炙热为阳,寒冷为阴,而寒冷和理智之间不存在阴阳关系。\n3.普遍性:阴阳属性不是特殊的,而是普遍的。凡属于相关的事物或现象,都可以用阴阳对其各自的属性加以概括分析。如:水与火、动与静。\n4.变化性:阴阳属性不是一成不变的,而是相对的,在一定条件下可转化。如:乐极生悲,悲极生乐。\n5.可分性:阴阳之中可再分阴阳。如:以天而言,昼为阳,夜为阴;白昼又可再分,上午为阳中之阳,下午为阳中之阴;黑夜也可再分,前半夜为阴中之阴,后半夜为阴中之阳。\n阴阳学说\n阴阳学说早在夏朝就已形成。此说认为,任何事物都包含着对立统一的两个方面。“阴阳”就是对相关事物或同一事物内部对立双方的属性的概括。阴阳之间是对立统一、互根互用的,它们始终处于运动变化中。\n阴与阳之间有如下几种关系:\n1.交感相错:交感指阴阳的交互作用,相错指这种相互作用十分复杂。阴阳交感是万物得以产生和变化的前提。\n2.对立制约:阴与阳是对立、矛盾的。如:上与下、水与火。在属性对立的基础上,阴阳还存在着相互抑制、约束的性质,表现为“阴强则阳弱、阳胜则阴退”的动态联系。\n3.互根互用:阴阳任何一方都不能脱离对方而单独存在。如:没有上就没有下。在相互依存的基础上,某些范畴的阴阳还体现出相互滋生、相互为用的特点。\n4.消长平衡:消意为减少,长意为增多。消长可分为四种情况:阴消阳长,阳消阴长,阴阳皆长,阴阳皆消。“平衡”指阴阳的消长在一定范围内相对稳定而不易察觉。\n5.相互转化:阴阳可各自向其对立面转化。阴阳的孰主孰次决定了事物的主要特性。但这不是一成不变的,一旦消长变化达到一定界限值,会导致转化,即“物极必反”。如果说“阴阳消长”是量变,“阴阳转化”就是质变。\n中医学中的“阴阳”\n阴阳学说在中医学中有广泛的应用。它被用来解释人体生理现象及病理变化的规律。简单地说,阴指人体实质性的物质,如血液、津液、泪水、鼻水、内分泌液等;阳指人体非实质的物质,即身体的机能和气。阴阳协调,则身体健康;阴阳失调,则百病丛生。人体内若阳气偏旺,阴气就必然受损;相反,阴气过旺,阳气则受抑制。阳气旺盛会产生热证,阴气至极会产生寒证。寒到极点会生内热,热到极点也会生内寒。如《黄帝内经》中所说:“阳胜则阴病,阴胜则阳病。阳胜则热,阴胜则寒。重寒则热,重热则寒。”\n四象 阴阳两仪对立统一,阴中有阳,阳中有阴,阳极阴生,阴极阳生,此消彼长,相互转化。根据阴阳的成分和程度的轻重,两仪又被分为四象:少阴、少阳、太阳(老阳)、太阴(老阴)。\n象,是形象、状态、象征和比拟的意思。天体的运行状态被称为天象,地面的地形状态被称为地象,春夏秋冬被称为四象,又叫四时。在《易经》中,卦和爻的状态称为卦象和爻象。\n在《易经》的本义中,四象特指“少阴、少阳、太阳(老阳)、太阴(老阴)”。少阴和少阳是事物的初始状态,较为稳定。少阳是阳性逐渐增加而达到阴阳平衡的状态,少阴是阴性逐渐增加而达到阴阳平衡的状态。太阳与太阴分别是阳极与阴极的表现,是事物发展的终极状态,不稳定,即将产生变化,向相反的方向运动。这也是常说的“物极必反”的道理。\n![[assets/pasted image 20231027174252.png]]\n\u0026gt; 四象的变化\n少阳—老阳—少阴—老阴”这四象之间的变化关系非常复杂。在图中,少阳和老阴看似一样,实则不同。在少阳时,阳占据主动、积极、上升的状态;而老阴时,阴占据上升状态。少阴和老阳的关系同理。\n可一分为四的事物不都是《易经》中的“四象”。企图与“四象”挂钩的各种说法大都是把《易经》中高度抽象的规律细化、具体化。结果是可以立“一家之言”,却挂一漏万。\n八卦 八卦源于古人对宇宙生成、日月与地球的关系、农业社会和人生哲学的认识。它们代表了构成天地万物的八种元素。\n八经卦\n古人在长期的生产实践中发现,用语言文字来表达思想是有局限的,于是圣人设计出卦象来表达自己对世界的认识,最后在下面加上文字说明,形成一个完整的系统。目的是用有限的卦对应无限的世界和对世界的认识。他们把世界总结为八种现象,分别由八个卦来表示,它们也叫经卦或单卦。\n八经卦中,乾卦代表天,坤卦代表地,离卦代表太阳,坎卦代表月亮,它们像球一样,不停运转,代表了时间、空间。时空不停地运转,于是又有四个卦产生了:震卦代表雷,雷震动以后,有了气流,就是风;巽卦代表风,气流震动得太厉害,一摩擦又会产生电,这就是“雷风相薄”;卦艮卦代表高山陆地,兑卦代表海洋河流。\n八卦分为两种:先天八卦和后天八卦。\n先天八卦揭示了宏观世界的规律,反映了宇宙、地球诞生初期的万物状态。后天八卦反映的是自然界和人类社会的具体状况。先天与后天在时间、空间上都有区分。先天指规律,后天指现象;先天为未成,后无为既成;尚未形成者可以改变,既成事实不可改变;未来可以改变,现实不可改变。\n它们的五行属性、卦象属性相同,但也有各自的数字属性、方位属性等,如下图所示。\n先天八卦又称伏羲八卦,是伏羲氏观物取象而创作的。在实际应用中,要以后天八卦图为“用”。\n![[assets/pasted image 20231027174923.png]]\n\u0026gt; 先天八卦\n后天八卦又称文王八卦,是周文王创制的。周文王在研究先天八卦的过程中发现它与实际有不符的地方,于是改变了方位,使其符合自然万物的变化规律。他还在卦中增加了数字九,同时多出了中土的位置。后人在实际应用中,大多以先天八卦图为“体”,后天八卦图为“用”。天干、地支、五行生克等要素都以后天八卦图为依据。后天八卦图对应于洛书九宫图。\n![[assets/pasted image 20231027175030.png]]\n\u0026gt; 后天八卦\n后天八卦表明的是万物在宇宙大环境中的运动变化,描述的是日、月、地三者之间联系规律,是以人、地作为中心来观察、描述宇宙的。后天八卦是从四时的推移、万物的生长收藏得出的规律,从《说卦传》中可以看出,万物春生、夏长、秋收、冬藏,每周天 360 日,八卦各主 45 日,其转换点在四正四隅的八节上,每卦有三爻,八卦共二十四爻,即一年二十四个节气。\n八卦的属性和代表的意思\n八卦的基本单位是爻,爻是记述阴阳变化的专用符号,它分为阴阳两类,阳爻“ ⚊”表示阳性的事物,阴爻“⚋ ”表示阴性的事物。\n1.数字属性 先天八卦之数:乾为一,兑为二,离为三,震为四,巽为五,坎为六,艮为七,坤为八;后天八卦之数:坎为一,坤为二,震为三,巽为四,中为五,乾为六,兑为七,艮为八,离为九。\n2.阴阳属性 乾、坎、艮、震四卦为阳卦,其中乾为父,艮为少男,坎为中男,震为长男(震、坎、艮卦中阴多阳少,阴从阳,所以为阳卦)。坤、兑、离、巽四卦为阴卦,其中坤为母,兑为少女,离为中女,巽为长女(兑、离、巽卦中阳多阴少,阳从阴,所以为阴卦)。\n:: 此处,使用的是后天八卦的方位,后天以“用”。\n3.方向属性 先天八卦:乾南,坤北,离东,坎西,兑东南,震东北,巽西南,艮西北。后天八卦:震东,兑西,离南,坎北,乾西北,坤西南,艮东北,巽东南。\n4.五行属性 乾兑属金;震巽属木;坤艮属土;离属火;坎属水。\n:: 水火不相容 - 坎离不容别的卦,独为水火。\n5.旺衰属性 乾兑旺于秋,衰于冬;震巽旺于春,衰于夏;坤艮旺于四季,衰于秋;离旺于夏,衰于四季;坎旺于冬,衰于春。\n八卦释义\n![[assets/pasted image 20231027180405.png]]\n宇宙的现象可总结出八种,分别由“乾、坤、震、巽、坎、离、艮、兑”八个卦来表示。\n重卦 由八个单卦以不同的次序两两重合就产生了六十四卦,也叫别卦或重卦。其中由八个单卦自身相叠所成的卦叫纯卦,其卦名同单卦。历代学者对六十四卦有不同的阐发,六十四卦的排序也就有了很多种。\n先后天六十四卦\n八卦互相重叠可得到六十四卦,六十四卦也有先后天之别:伏羲六十四卦属于先天六十四卦体系;而文王六十四卦、京房的八宫卦属于后天六十四卦。六十四卦有很多种排序图,古人以“伏羲六十四卦大横图”来说明六十四卦的成卦原理。\n![[assets/1a941h59-0.jpg]]\n\u0026gt; 伏羲六十四卦大横图\n该图以右方乾卦开始,阳气向左方依次减弱,直到最右方的坤卦为阴气最盛,共六十四个卦象,排列规整,错落有致。如果以0表示阴爻,以1表示阳爻,则这幅“大横图”正好体现了现代数学的二进制原理。\n六十四卦除了《周易》中起于“乾坤”终于“未济”的排序外,还有圆图排列,方阵排列,圆中布方的排列,以及八宫法的排列等。\n先天八卦阐明的是天地变化的规律,因此由先天八卦演变而来的伏羲六十四卦也主要用于占筮世界的大演变过程,这种演变是不以人的意志为转移的。例如用于占筮国运的《皇极经世》,用的主要就是先天八卦。如果要占筮人事,推测一般事情的吉凶,则要用后天八卦。本书中的预测理论用到的就是后天八卦体系。\n《易经》中的卦序\n朱熹在《周易本义》中作了《卦名次序歌》,有助于我们记忆六十四卦:\n乾坤屯蒙需讼师,比小畜兮履泰否; 同人大有谦豫随,蛊临观兮噬嗑贲; 剥复无妄大畜颐,大过坎离三十备; 咸恒遁兮及大壮,晋与明夷家人睽; 蹇解损益夬姤萃,升困井革鼎震继; 艮渐归妹丰旅巽,兑涣节兮中孚至; 小过既济兼未济,是为下经三十四。 六十四卦起于乾坤两卦,是在“乾为天、坤为地,有天地然后有万物”的思想指导下排列的。其他六十二卦的次序是以卦象相错(i.e. 综卦)的方法排列的,如第三卦的“水雷屯”与第四卦的“山水蒙”两个卦象。此外,还有的卦按由小到大、由大到小的规律排列,这在《序卦》中有详细的论述。如“盈天地之间者唯万物,故受之以屯。屯者,物之始生也”。由于屯卦是万物开始生长的时期,所以排在第三位。“物生必蒙,故受之以蒙”:万物开始生长时期,先必有个萌芽的阶段,所以屯卦后为蒙卦。\n六十四卦方圆图\n《易经》有很多图,其中最重要的一幅就是六十四卦方圆图。按照排列的原则不同,六十四卦图有先天后天之分,先天六十四卦方圆图反映了事物的总体变化规律,后天六十四卦图则反映了事物变化的具体规律。常用的先天图又称“伏羲六十四卦方圆图”,简称“六十四卦方圆图”。它包含了六十四卦的变化之妙,蕴涵了天地方位的变化之理。该图包含了先天八卦、六十四卦方位、河图、洛书等内容,以及日月五星的运行规律,十二纲纪、二十八宿也暗藏其中,它几乎囊括了《易经》的所有内容。\n![[assets/pasted image 20230526102028.png]]\n在方图中,西北至东南这条斜线被称为“子午线”,西南至东北这条斜线被称为“卯酉线”。方图从外向内由四层组成,每层沿子午线方向的对角两卦数字之和都是九,且卦象相反。一般认为,第一层乾坤相对,表示“天地定位”;第二层兑艮相对,表示“山泽通气”;第三层离坎相对,表示“水火不相射”;第四层震巽相对,表示“雷风相薄”。\n![[assets/pasted image 20231030100409.png|400]]\n在六十四卦方圆图中,方图代表地,为空间,由四个层次组成;圆图代表天,为时间,方位与先天八卦相同。\n圆图的卦序就是伏羲六十四卦次序,从乾卦始逆时针排列一周而成圆形。\n圆图是一个天体运行图,也可以说是八卦的“历法”,它精确地说明了年月日和四季的运行规律。此图按照伏羲六十四卦大横图的次序排列,左半圆从午位以乾卦开始,复卦结束,逆时针排列到子位;右半圆从午位以卦开始,坤卦结束,顺时针排列到子位。以八纯卦为间隔共分八宫,正好形成先天八卦方位图。“金木水火土”五行依次运行而成春夏秋冬四季,配地支和二十四节气,便是一幅完整的天体运行图。易学大师陈抟极力推崇伏羲六十四卦方圆图,认为《易经》只有这一张图。\n后天六十四卦图有很多种,其中以文王创造的图为重点。文王的六十四卦卦序包含着事物发生发展的顺序,可以用来预测人事。有学者认为,其中还包含着殷周时代的历史。文王六十四卦有一张圆图,但不载于《周易》中,该图载于道教典籍《周易参同契》中,“乾、坤、坎、离”四卦不计爻象。\n八宫卦序\n在古人对六十四卦的众多排序中,汉代京房的“八宫学说”独树一帜。八宫卦,又称“京房六十四卦”,与伏羲六十四卦、文王六十四卦都不同。它以八卦为八宫,八卦自身重复、逐爻变化而变出本宫的其他七卦。每宫八卦,八宫共六十四卦。这种排法属于后天八卦系统。依据八宫卦序可推知各爻的世爻和应爻,这是六爻预测的理论元素之一。\n![[assets/pasted image 20231030101702.png]]\n\u0026gt; 八宫卦\n八宫的各宫变化规律都相同,以乾宫为例来说明:六爻纯阳的乾卦,世爻在上九爻。其初爻变为阴爻后就便成了卦,因为初爻为变,所以卦的世爻为初爻,简称卦为“一世卦”,或“一世”。卦的二爻变为阴爻时,便形成了遁卦,因为此卦的变爻是根据卦变来的,变爻为二爻,所以世爻为二爻,简称“二世卦”,或“二世”。遁卦三爻变为阴爻时,便变成了否卦,其世爻为三爻,简称“三世卦”,或“三世”。变至四爻时,便变成了观卦,其世爻为四爻,简称“四世卦”,或“四世”。五爻及以下全是阴爻时便变成了剥卦,其世爻为五爻,简称“五世卦”,或“五世”。接下来若再往上变就成了坤卦,不属于乾宫了。所以接下来返回而变第四爻,变成了晋卦。此时并没有变完,晋卦就好像正走在回家的途中一样,所以又叫“游魂卦”,此卦四爻为世爻。晋卦继续往回变第三爻,变成了大有卦,其世爻为三爻。至此已经变到了乾宫的结尾,大有卦也称“归魂卦”。其他七宫的变化规律同理。这样,变化每宫首卦的初爻至五爻,再返变四爻,最后变三爻,就形成了京氏六十四卦的卦序。明白此规律后,熟记卦在各宫的排列顺序便可推算出每一卦的几爻为世爻,与世爻上隔两位的为应爻。世爻与应爻是断卦的重要因素,在后面的篇章中将介绍它们在六爻预测中的作用。\n其中四种卦较为特殊,六冲卦多为不吉,六合卦多为吉,游魂卦和归魂卦在预测疾病时多为不吉,且游魂比归魂更凶。\n![[assets/pasted image 20231030101746.png]]\n\u0026gt; 四种较特殊的卦\n再言 六十四卦方圆图\n伏羲六十四卦方圆图实际上就是先天六十四卦卦序。古人以六十四卦卦序反映一年的阴、阳二气变化。冬至一阳来,夏至一阴至。先天六十四卦主要反映事物总体的变化规律。\n![[assets/pasted image 20231030101250.png]]\n此图简称方圆图或先天图。圆图为天为时间,方图为地为空间。事物总是由阴(坤)到阳(乾),由阳(乾)到阴(坤)周而复始地变化。事物的阴阳既对立(在圆图中以上下示之)又统一(统一在一圆内)。\n十二消息卦\n在六十四卦中,有十二个卦较为特殊,人们称为“十二消息卦”,它们的卦爻变化完美地体现了阴阳消长的规律。\n![[assets/pasted image 20231030101522.png]]\n此图简明地画出了一年十二个月与十二地支、二十四节气的关系,并且与六十四卦对应起来。自复卦一阳生,至乾卦达到极盛而生出阴性,阴性又至坤卦达到极盛。\n十二消息卦是用复、临、泰、大壮、夬、乾、姤、遁、否、观、剥、坤十二卦,分别依次代表一年的十二个月。\n![[assets/pasted image 20231030101619.png]]\n五行 五行学说最早在道家学说中出现。它强调整体观念,描绘事物的结构关系和运动形式。如果说“阴阳学说”是古代的对立统一学说,则“五行学说”就是原始的系统循环论。\n五行学说的起源\n五行学说的起源一直争议颇多。第一种观点认为:五行说是古人自发的观念,并无确切的创始人,春秋时期以前就有朴素的五元素说。第二种观点认为:五行说出自中国现存最早的医学典籍《黄帝内经》,该书以五行配五脏,对五行学说的内容有详尽的记载。第三种观点认为:孟子创造了五行学说。《荀子》里记载子思、孟子是五行的创始者。现代学者范文澜认为:“孟子是五行学说的创始者,孟子有‘五百年必有王者兴,由尧舜至于汤五百年有余岁……由汤至于文王五百年有余岁……由文王至孔子五百年有余岁……’等近乎五行推运的说法。比孟子稍后的邹衍扩大了五行学说,成为阴阳五行家。”\n五行学说认为金、木、水、火、土五种元素构成宇宙万物。它们各有不同的属性,分别对应太阳系的五颗行星:金星、木星、水星、火星、土星;以及人间的五德:义、仁、智、礼、信。古人认为,这五类事物在天、地、人之间形成映射关系,比如天上的木星有了变化,地上的木类之物,以及人的“仁”性也会有相应的变化。\n五行之间存在着生、克、乘、侮等关系。五行的“相生”“相克”关系可以解释事物之间的相互联系,而五行的“相乘”“相侮”则可以用来表示事物之间平衡被打破后的相互影响。\n五行具有哪些属性和特性?\n五行作为五种符号,代表可以划分为五类的事物状态。五行的特性虽然来自木、火、土、金、水五种自然物质,但实际上已远远超越了这五种具体物质本身,具有更广泛而普遍的意义。\n1.金的特性:金主义,其性刚,其质烈。具有清洁、肃杀、收敛性质的事物,均可归属于金。金曰“从革”,从为顺从、服从,是金的“柔和”特性的体现;革为变革、改革,是金的“刚强”之性的表达。\n2.木的特性:木主仁,其性直,其质和。具有生长升发、条达性质的事物,均可归属于木。木曰“曲直”,曲为屈,直为伸,所以木有能屈能伸的特征,伸则舒其条达之性,屈则还其柔和之质。木可纳水土之气,树木的主干挺直向上生长,树枝曲折向外舒展,生长繁茂,随风招摇。\n3.水的特性:水主智,其性聪,其质善。具有寒凉、滋润、向下的性质的事物,均可归属于水。水曰“润下”,润为湿润,下为向下,所以水具有滋润寒凉、性质柔顺、流动趋下的特性。\n4.火的特性:火主礼,其性急,其质恭。具有温热、升腾性质的事物,均可归属于火。火曰“炎上”,炎为热,上为向上。火在燃烧时能发光放热,火焰飘浮于上,光热四散于外,所以火有发热、向上的性质,有驱寒保温之功,锻炼金属之能。\n5.土的特性:土主信,其性重,其质厚。具有承载、生化、受纳性质的事物,均可归属于土。土曰“稼穑”,播种为稼,收获为穑,土有播种庄稼、收获五谷、生长万物的作用。引申为具有生长、承载、化生、长养的特性。所以土载四方,为万物之母。\n五行和太阳系中的“金木水火土”五星有什么关系?\n易经源自于河图洛书,而河图洛书为上古时期的星象图。因此易经中的公理性知识也源自于星象,比如五行学说。古人认为,宇宙万物虽各有不同的属性,但都可纳入五大类,并分别对应太阳系中的“金木水火土”五星。\n![[assets/pasted image 20231030102226.png|400]]\n古人认为,这五类事物在天、地、人之间形成映射关系,比如天上的木星有了变化,地上的木类之物,以及人的“仁”性也会有相应的变化。\n五行怎么对万物进行划分归类?\n将万物进行类比,便都可以纳入五行的范畴,其方法有两种:\n一、归类法。事物的五行属性是将事物的性质与五行的特性相类比得出的。如某物与木的特性相似,则归属于木;与火的特性相似,则归属于火等。如果配属五脏,则肝主升而归属于木,心主温煦而归属于火,脾主运化而归属于土,肺主降而归属于金,肾主排泄而归属于水。\n二、推演法。如果某一事物被归属于某种五行,那么与此物相连的、可划为一个系统之内的其他事物也应被归入这种五行。如肝主筋、肝开窍于目,而肝属木,所以“筋”和“目”也属木;同理,心属火,则“脉”和“舌”也属于火;脾属土,则“肉”和“口”也属于土;肺属于金,则“皮毛”和“鼻”也属金;肾属于水,则“骨”和“耳”也属水。\n![[assets/pasted image 20231030102530.png|400]]\n根据归类法,绿色、东方、春季、巽卦、鸡、树、风、肝脏都属于“木”的范畴;根据推演法,肝脏主怒,华荣于爪,开窍于目,与胆互为表里,所以怒、爪、目、胆也都属“木”。\n五行相生相克之间的意义\n五行之间存在生克的关系:金生水,水生木,木生火,火生土,土生金;金克木,木克土,土克水,水克火,火克金。然而,五行系统之所以能保持循环运动和动态平衡,在于其内部存在着两种自行调节机制,一种是正常情况下的“制化”;一种是反常情况下的“胜复”。\n制即制约,化即生化。“制化”指五行系统在正常状态下通过生克而产生的调节作用。比如:\n木能克土,但土能生金,金又能克木,使木不亢不衰,从而滋养生化火。\r火能克金,但金能生水,水又能克火,使火不亢不衰,从而滋养生化土。\r土能克水,但水能生木,木又能克土,使土不亢不衰,从而滋养生化金。\r金能克木,但木能生火,火又能克金,使金不亢不衰,从而滋养生化水。\r水能克火,但火能生土,土又能克水,使水不亢不衰,从而滋养生化木。 五行的引申意义\n五行作为五种符号,代表可以划分为五类的事物状态。五行的特性虽然来自木、火、土、金、水五种自然物质,但实际上已远远超越了这五种具体物质本身,具有更广泛而普遍的意义。\n![[assets/pasted image 20231030102941.png]]\n正是这种相反相成的生克制化,调节并保持了事物结构的相对协调和平衡。相生相克的过程是事物消长的过程,在此过程中,当出现不平衡的情况时,事物自身会通过相生相克的调节而重新出现平衡。正是这种在不平衡之中求得平衡,而平衡又立刻被新的不平衡所替代的循环运动,推动着事物的变化发展。\n五行的胜复\n“胜复”指五行系统在反常情况下,即在局部出现较大不平衡的情况下,通过相克而产生的一种大循环的调节。胜复调节可使一时偏盛或偏衰的五行由不平衡而恢复平衡。\n“胜”指因为某行之气太过而导致对自己所克之行的过度克制。胜气一旦出现,势必招致一种相反的力量将其压抑下去,即所谓“复气”。胜气重,则复气重。胜气轻,则复气轻。所以《素问》说:“有胜之气,其必来复也。”\n![[assets/pasted image 20231030103141.png|500]]\n例如,火气太过,则过分克金,使金气偏衰;金衰不能制木,则木偏胜而加剧制土,土受制则减弱制水之力,水便越发旺盛,而把太过的火克伐下去,使其恢复正常。\n反之,如果火不足则不能克金,引发金偏盛,金盛则加强制木,使木衰而无法制土,这将引起土盛制水,水衰则制火力减弱,从而使火气得到恢复,以维持系统的正常运转。\n![[assets/pasted image 20231030103317.png|500]]\n如果只有“胜”而没有“复”,即五行之中的一行太过而没有另一行制约时,则系统的和谐关系就被破坏,导致紊乱。生多为克、泄多为克、相乘、相侮都是五行关系走极端的情况,相似于四象中的老阴和老阳。五行的生与克是互相依存、互相制约的。没有生,则没有事物的发生与成长;没有克则没有在协调稳定下的变化。只有生中有克、克中有生,相反相成、协调平衡,事物才能生化不息。\n阴阳五行观的发展\n“阴阳家”是春秋战国百家争鸣中的一家,后来逐渐消失了。因为他们的学说被充分融入易学、儒学、道学、医学当中,没有独立存在的必要。但也正是因为这种融入,使得阴阳学说在中国传统文化中随处可见、举足轻重。\n在易学史上,邹衍最早把阴阳和五行概念结合起来,提出系统的阴阳五行说——五德终始说,即阴阳五行的象数与天命观相结合的一种哲学。邹衍“深观阴阳消息”,以阴阳、五行概念为核心,以阴阳、五行之气的运动为宇宙的普遍规律,以此讨论极小至无限大的空间,以及从天地生成到现在的时间。他在讨论空间时说:“必先验小物,推而大之,至于无限”;在讨论时间时说:“先序今以上至黄帝,学者所共术,大并世盛衰,因载其祥度制,推而远之,至天地未生,窈冥不可考而原也”。成书于战国的《吕氏春秋》将当时广为流行的五行说和阴阳说进一步具体化;《十二纪》中描述了一年五气运行对应的天象、气象、物象,并以此为据制定出一年十二个月的“政令之所行”律令。\n至秦汉,阴阳五行学说开始成为统摄万物的哲学体系。西汉时,阴阳五行学说逐渐被世人所公认,其表述体系最终的完成者是《淮南子》和董仲舒,他们分别代表“道”和“儒”两大学派,占据着不同学术领域,但又互通、互补、互融。道、儒在发展中给易学增添了新的元素,儒学、道学、易学的融通共进,促进了中国传统文化的繁盛。\n五行与“天地人”的关系\n![[assets/pasted image 20231030103601.png]]\n易经的结构 《易经》的核心理论,也就是对卦和爻的分析。本类目是《易经》原文的重点。\n六爻 爻分为阴爻和阳爻,它们是组成卦象的基本单位,构成了卦象的交错变化。六十四卦中,每卦都由六个爻组成,不同的爻代表不同的寓意。\n什么是“爻”\n爻是效仿天地变化的符号。爻又可以解释为“交”,代表“易”的变化交错。宇宙万物时时都在相互交流、作用,发生关系,产生变化。\n爻有两种,即阳爻和阴爻,阳爻以“⚊”表示,阴爻以“⚋”表示。阴阳二爻象征由太极衍生的阴阳两仪。两个爻组合在一起,即象征两仪和合产生的四象。三个爻组合,即可构成八卦,代表天地之间的八种事物形态。八卦两两组合,即构成有六个爻的六十四卦。\n六十四卦的每一卦都由六个爻组成,六爻法也是以此为基础进行预测的。“爻”这一个字代表六十四卦中的三百八十四爻,并以此穷尽天下万物之象。\n对于每一卦来说,卦爻应从下往上数,依次为初爻、二爻、三爻、四爻、五爻和上爻。古人以“九”代表阳数,“六”代表阴数,因此又把阳爻称为初九、九二、九三、九四、九五、上九;把阴爻称为初六、六二、六三、六四、六五、上六。这样,通过爻的名称就可以知道其阴阳性质。如火地晋卦,从下到上依次为:初六、六二、六三、九四、六五、上九。由此可知,晋卦只有四爻和上爻是阳爻,其余都是阴爻。\n“天地人”三才\n此外,古人还将六爻分成三部分,初爻、二爻为地;三爻、四爻为人;五爻、六爻为天,这三部分合为“天、地、人”三才。天之道说的是阴和阳,地之道说的是柔和刚,人之道说的是仁和义。这样,在六爻之中,就包含了天、地、人的万物万象。\n不同数量的爻的组合\n不同数量的爻组合在一起会得到不同的“象”:两个爻组合在一起构成四象;三个爻组合在一起构成八卦;六个爻组合在一起构成六十四卦。实际上,卦爻的数量并不固定,可以有十几个甚至几十个爻的卦,但由于太过复杂,其实用性较差。\n爻位:将事物划分为六个层级\n一个六爻卦中,不同的爻位表示不同状态的事物,或同一事物的不同状态,也可以表示系统中的不同层次或事物的不同阶段。但其前提是可以将这些事物分成六等份。在一卦中,从初爻到上爻,对应于事物的产生-发展-成长-壮大-成功-衰败的全过程。\n![[assets/pasted image 20231030104400.png]]\n\u0026gt; 事物变化的六个阶段\n上爻为变之“终”:代表事物发展到最终阶段,已经回到最后的归宿。\r五爻为变之“成”:代表事物发展到成功完满的阶段,但也快结束了。\r四爻为变之“动”:代表事物有了大转变,可以展开决定性的大动作。\r三爻为变之“通”:代表事物进入顺畅的阶段,脉络清楚,可以行动。\r二爻为变之“显”:代表事物有了明显的变化,能与其他事物区分开。\r初爻为变之“始”:代表事物产生的起始阶段,此时难以判断其未来。 :: 原来‘四世三公’的‘三公’是从这里来的啊~\n乾卦卦辞中的事物阶段\n乾卦六个爻的爻辞说明了不同爻位的普遍状况,突出反映了事物的发展阶段。下面就以乾卦爻辞为例,说明人的一生与不同爻位之间的对应关系。\n![[assets/pasted image 20231030105217.png]]\n卦爻的这六个阶段也反映出“中庸”的道理:不能偏左,也不能偏右;不可过头,也不要不及。其中的“度”是最难把握的,稍有偏差就会带来忧患,所以《易经》的经文中充满了忧患意识。\n六个爻分为三类\n一卦六爻可以按照位置分为三类:\n第一类是处在卦象中间的两个爻,即三爻和四爻。作为整个卦象的“中央”,它们反映了事物的核心与本质。如同社会中的中坚力量、中流砥柱。\n第二类是处在“中位”的卦爻,即二爻和五爻。作为上面三个爻和下面三个爻的核心,它们占据着最吉的位置。如同社会中得心应手、八面玲珑的两个阶层。\n第三类是最下面和最上面的“本末”之爻,即初爻和上爻。它们是事物萌芽和消亡的阶段,力量较弱,重要性较低。如同社会中“老”“幼”两种弱势群体。\n此外,古人认为:五爻、上爻可以表示天或天上的事物;初爻、二爻可以表示地或地下的事物;三爻、四爻可以表示人类及其周围的事物。易卦系统把“天地人”统一在一起来表述,是对整个宇宙进行的整体而统一的概括。\n后面将要介绍“互卦”的概念,其中,三、四两爻的阴阳搭配变化是非常重要的,经过两次连续的“四爻连互”后,最后形成的卦只有四种可能:乾为天卦,坤为地卦,水火既济卦,火水未济卦。六十四卦的排序正是以乾、坤两卦开始,以既济、未济两卦结束。\n爻位的属性:难知、誉、凶、惧、功、易知\n爻位不同,反映事物的基本状态也不同,如同《易传》中所说:“其初难知,其上易知” “二爻多誉,三爻多凶,四爻多惧,五爻多功”。\n![[assets/pasted image 20231030105550.png]]\n\u0026gt; 六爻——事物的六种属性\n初爻难知 - 古人对初爻的评价是:“其初难知”“初爻多朦”。“初”指“初爻”,初爻表示事物处于刚刚产生的时候,它将来向什么方向发展、怎么发展、能不能发展,都难以预料。因此,《易经》中的初爻爻辞大都是含糊、不太被肯定的词句。就如同一个孩子刚生下来,他未来能成为什么样的人物,有无限的可能性,不适合在此时对他的人生做评判和推断。\n二爻多誉 - 二爻和四爻都是阴爻的位置,但位置不同导致一个多誉,一个多惧。二爻处在内卦(下面三个爻组成的卦)的中间,居于核心地位,它还与五爻相应,相当于有后台支持,所以多誉。在古代,二爻相当于贵族的“门客”,这些人很得上层及当权人物的赏识。他们几乎什么都会,天文、地理、政治、经济、军事、医学、术数……什么问题都能处理。二爻与五爻相应,说明“士大夫”可获得领导者的支持,他们的工作因此而得心应手。他们除了为主人出谋划策、平定祸乱外,还有时间陶冶情操,玩弄琴棋书画,活得不亦乐乎。所以说“二爻多誉”。\n三爻多凶 - 三爻和五爻都是阳爻的位置,但一个多凶,一个多功,因为它们在卦中的贵贱位置不同。三爻是内卦的最上位。对于内卦来说,三爻表示事物发展到了最终阶段,已经穷途末路。三爻与上爻相应,希望得到上爻的支持,但上爻是外卦(上面三个爻组成的卦)的最上面的一个爻,也代表事物发展到了穷途末路的状态,自身难保而无力支援三爻,而且两个弱者在一起反而会带来更多凶险。此时的三爻叫天天不灵,叫地地不应,所以说“三爻多凶”。如果三爻是阳爻,凶险略小,如果是阴爻,不利的情况可能更重。\n三爻和五爻的位置应当由阳爻占据,这如同“天尊地卑”“男尊女卑”是天经地义的道理一样。如果被阴爻占据,则相当于“男卑女尊”,如同武则天、慈禧太后掌握朝政大权。\n四爻多惧 - 四爻不仅不处在核心的“中位”,而且贴近至尊的五爻君王,伴君如伴虎,所以多惧。四爻与初爻相应,初爻在内卦的最下位,表示事物刚刚产生,非常脆弱。四爻是外卦的最下位,是外卦的萌芽阶段,同样脆弱。两个弱者虽然在位置上相应,也都有上进的想法,但难下决心,既不甘心,又不知道该怎么办。周围环境又限制重重,难以自主。\n四爻靠近君王之位,处在“承”五爻的位置上,要烘托、支持君王。君王在其头顶,直接指挥他。他终日看人眼色行事,谨小慎微、提心吊胆。因为与君王太近,君王可以觉察到他的一切言行,一不留神就犯了欺君之罪。此外,四爻不仅要衬托五爻,还要压制三爻。稍有差错,五爻怪罪下来,三爻挤对上来,说斩就斩,斩完也不知道为什么斩的。如果四爻与五爻的阴阳属性相反,异性相吸,情况可以相对缓和,如果同为阴性或阳性,则如履薄冰。\n五爻多功 - 五爻得外卦的核心位置,是君王、至尊之位。在一个六爻卦中,五爻是最吉的位置。因此人们也把皇帝称为“九五之尊”,这里的“九”是指阳爻,“五”是指五爻的爻位。五爻与二爻相应,二爻处在内卦的核心位置,五爻与这样的爻相应,自然为吉。如果五爻是阳爻,二爻是阴爻,它们阴阳相应,就更吉。此外,五爻又“据”在四爻阴位上,有支撑者,其所处环境就更好。所以《易经》中的五爻爻辞大多是比较好的。\n上爻易知 - 上爻可代表宗庙、祖宗、太上皇等。它们没有力量直接影响事物,但它们有种无形的凝聚力。《易传》中说:“其上易知”,“上”就是指上爻。事物到了最终状态时,发展的全过程及结果都能看得清清楚楚,可以盖棺定论。因此,代表事情结尾的上爻爻辞大都非常肯定。\n六爻反映了事物从小到大、从始到终的完整发展过程。在一卦中,下层的卦爻代表事物的根本、基础、原动力;上层的卦爻代表事物的结尾、走向消亡的阶段。所以,要想知道事物是从哪里发展来的,要由上往下看,从卦的上爻向初爻的方向寻找;要想知道事物将发展到哪儿,要由下往上看,从卦的初爻向上爻方向寻找。这两个方向代表两种不同的发展过程:一种是由里向外,代表事物的“顺向成长”;另一种是由外向里,可以对事物“追根溯源”。两者反映出的规律相同,但分析思路相反。\n六爻中的“中庸之道”\n可以把初爻看做事物的产生阶段,二、三、四、五爻看成变化阶段,上爻看成消亡阶段;也可以把三爻、四爻看成事物的核心和发展的开始,所以压力重重;它们分别向上下变化,二爻和五爻是发展的中间阶段,不偏不倚而为吉;初爻和上爻是消亡阶段,过犹不及,物极必反。\n当位 判断吉凶的基本前提\n爻和爻位都有阴阳之分,阳爻应在阳性的位置上,阴爻应在阴性的位置上。这就是《易经》中的“当位”的含义。\n当位与不当位\n在一个六爻卦中,一、三、五爻的爻位为“阳位”,表示阳性、刚性事物该处的位置;二、四、六爻的爻位为“阴位”,表示阴性、柔性事物该处的位置。如果阳爻处在一、三、五的爻位上,或阴爻处在二、四、六的爻位上,被称为“得位”“得正”“当位”等,为吉、正确、应该、合理等意思。如果二、四、六这些阴爻应在的位置被阳爻占据,或一、三、五这些阳爻应在的位置被阴爻占据,就被称为“不当位”“不得正”“失位”“失正”等,为不吉、不应该、不正当、不合理等意思。\n![[assets/pasted image 20231030110411.png|300]]\n\u0026gt; 火风鼎各爻的当位与失位\n如火水未济卦,所有阳爻都处在阴位上,所有阴爻都处在阳位上,六个爻都“失位”。因此其卦辞指出:事物都处在不应该在的位置上,干了自己不应该干的事。由于无法密切配合、协调一致,事情处于不和谐的“未济”状态。\n“当位”与“失位”是判断卦爻吉凶的基本条件,但不是唯一条件,不能说“当位”必然吉,“失位”必然凶。\n知道自己能吃几碗饭\n“当位”的概念对于职业来说,相当于人们知道自己适合干什么,能干什么,“知道自己能吃几碗饭”,各处其位,各得其所。不论工作高低贵贱,都有敬业精神。不管偏好如何,都知道自己在固定的时间、地点、环境、事情中应该有什么样的心态,以及如何面对变化。同时也要知道别人处在什么位置上,以及为何处在那种位置。反之,如果适合当教师的人把心思放在如何成为厨师上;适合当领导的人把心思放在研发网络游戏中;适合当会计的人却琢磨航天飞机如何升空……如果人们都干着不适合自己的工作,或三心二意、左顾右盼、玩忽职守,或大材小用、小材大用……则效率低下、人心不定、社会不稳。\n“当位”与“失位”的概念虽然宣传了封建等级秩序的观念,但对于今天的人来说仍有借鉴意义。\n承 承:阴爻对阳爻的支撑与帮助\n“承”是烘托、支撑、承上启下之意。如同天在上、地在下;阳在上、阴在下。古人认为这是大自然的基本规律。\n在一个六爻卦中,如果阴爻与阳爻紧邻,且阴爻在阳爻之下,就称此阴爻“承”此阳爻。在下的阴爻帮助、支持、支撑在上的阳爻,这是有利的。如同一个君王需要众多的臣属、群众支持一样,没有群众支持便无法站得稳。成语“承上启下”中的“承上”就来源于《易经》中这个“承”的概念。\n汉朝以前的著作分析卦爻辞时,较多见“承”的概念。借助这样的“爻位之象”,用很简单的语言就可以把卦爻的关系、内涵及状态表示出来。比如仅凭“初承二”三个字,就可知初爻是阴爻,二爻是阳爻,而且二者都“失位”。尽管如此,阴爻对上面的阳爻仍起着支持、帮助、支撑、烘托及承上启下的作用。\n![[assets/pasted image 20231030110745.png]]\n如图所示,“承”分为如下三种情况:\n一个阴爻对一个阳爻的“承” - 一个六爻卦中,如果一个阳爻在上,一个阴爻紧贴其下,则此阴爻对于上面的阳爻来说,称为“承”。如火水未济卦。九二爻在上,初六爻在其下。初六爻对九二爻来说可称“承”,简称为“初承二”。此外,六三爻还承九四爻,简称为“三承四”;六五爻承上九爻,简称“五承上”。\n一个阴爻对几个阳爻的“承” - 一个六爻卦中,如果一个阴爻在下,接连几个阳爻紧贴其上,则下面的这个阴爻对于上面的阳爻来说都可称为“承”。如天风姤卦,卦中只有初六爻是阴爻,所以初六爻对它上面的五个阳爻来说都可称为“承”,即初承二、初承三、初承四、初承五、初承上。\n几个阴爻对一个阳爻的“承” - 一个六爻卦中,一个阳爻在上,数个阴爻紧贴其下。则下面的这几个阴爻对上面的阳爻来说都可称为“承”。如地山谦卦,九三爻是阳爻。初六爻及六二爻都是阴爻,并且都在九三爻之下。初六爻和六二爻对于九三爻来说都可称“承”,简称初承三、二承三。\n乘 乘:阳爻对阴爻的压制与占据\n“乘”即乘虚而入,占据到对方的上面去。“乘”与“承”的意思相反,但两者都是阴爻对阳爻来说的。如果说“承”是支持,那么“乘”就是压制。成语“乘人之危”中的“乘”字就来源于《易经》中这个“乘”的概念。\n在一个六爻卦中,如果阴爻与阳爻紧邻,且阳爻在阴爻之下,就称此阴爻“乘”此阳爻。“乘”如同臣属命令君王、小人压制君子,这被古人视为不正当的现象。阴性事物应该在下,阳性事物应该在上。如同乾天在上、坤地在下一样,这是自然界的规律。但如果表示天的阳爻在表示地的阴爻之下,便是本末倒置。如同小人骑到了君子之上,或臣下骑到了君主之上。这被认为是不吉利、不顺利的状况。\n![[assets/pasted image 20231030111258.png]]\n如图所示,“承”有三种情况,而“乘”只有两种情况。\n一个阴爻对一个阳爻的“乘” - 一个六爻卦中,如果一个阴爻在上,一个阳爻在其下,则此阴爻对于下面的阳爻称为“乘”。如在水地比卦中,上六爻对九五爻就称为“乘”,简称“上乘五”。这两个爻虽然阴阳性质相反,可是它们都“当位”,这使不吉的状况有所改善。\n几个阴爻对一个阳爻的“乘” - 一个六爻卦中,接连几个阴爻都在一个阳爻上面,这几个阴爻对这个阳爻都可称“乘”。如地山谦卦,六四、六五、上六爻都在九三爻的上面。这三个阴爻对九三爻来说都为“乘”,简称为四乘三、五乘三、上乘三。它们都乘九三爻之危,占据其上的位置,欺压它。对于九三爻来说,这是很被动的局面。\n可以做个比喻,“乘”相当于俗话说的“小二管大王”,大王的级别本来比小二高,应该是小二接受大王的管制。但小二的父亲是大王的顶头上司,或大王有一些把柄落在小二的手上,所以,大王也不能不把小二放在心上,这使得大王在行为上会显得很被动。\n一个阴爻对几个阳爻不为“乘” - 一个六爻卦中,一个阴爻在几个接连的阳爻上面,这个阴爻对这几个阳爻不可称为“乘”。如泽天夬卦,上六爻对下面几个阳爻的关系不以“乘”论。可见,“乘”和“承”不同,“承”有三种情况,而“乘”只有两种。\n据 据:从相反的角度看“承”,阳爻对阴爻的压制\n“据”有占据、压制、居高临下等含义。同样都是阳爻位于阴爻之上,“承”是阴爻对于阳爻来说的,而“据”则是阳爻对于阴爻来说的。\n![[assets/pasted image 20231030111740.png]]\n如雷地豫卦,只有九四爻是阳爻,它又在卦中偏上的位置,它对其余五个阴爻都可称“据”。所以,豫卦九四爻的爻辞是“据有五阴”。此外,初爻到三爻组成的坤卦也有顺从之意,所以卦辞中还有“坤以众顺”之辞。九四爻在众多的阴爻包围中,象征受到众人拥戴维护。众人主动承担各种负担,齐心合力将事情完成,事情当然顺利。但这也把九四爻惯坏了,它会由此养成贪图安逸、喜欢享受的习惯,厌恶艰苦朴素。这样的人一般适合做公关性质的工作,而不适合埋头苦干。\n“据”与其他的卦爻性质合在一起,可以有更多的推断。如山水蒙卦,上爻与二爻是阳爻,其余都是阴爻,所以可以称二爻“应五据初”:二爻与五爻“阴阳”相应,所以叫“应五”,二爻在初爻之上,所以叫“据初”。再如火雷噬嗑卦,其上爻爻辞“据五应三”,因为上爻阳爻在五爻阴爻之上,所以上爻对五爻可以称“据”,简称“上据五”或“据五”;上爻阳爻与三爻阴爻“阴阳”相应,简称“应三”,统称为“据五应三”。\n![[assets/pasted image 20231030112322.png]]\n前面介绍的“承”与“乘”都是阴爻对阳爻来说的,而“据”是阳爻对阴爻来说的。“据”有占据、压制、居高临下等含义。如果某阴爻“承”某阳爻,也可以说此阳爻“据”此阴爻。\n比 相邻卦爻的关系\n在一个六爻卦中,相邻两爻在位置上的接近关系称之为“比”。就如同两个朋友在一起“肩并肩”地前行一样,容易产生摩擦或互助。\n比为对比、比较、亲近之意。只有类似、有共性的事物才可以比较。所以,人可以和人比身高,但不能和茶杯比。在卦象中,只有相邻两爻才可以“比”,而相隔的两爻一般不以“比”来看待,因为相隔遥远而难以了解、理解、沟通、比较。在群体中,每个成员的作用都要通过分析它左邻右舍的配合来决定。从卦爻的“比”的关系中就可以看出邻近的组织或个人的配合关系。\n“比”的原则\n“比”的原则是“同性相斥,异性相吸”:一个阳爻与一个阴爻会“异性相吸”,所以容易接近与亲近,这种“比”叫“亲比”;而两个阳爻或两个阴爻则会“同性相斥”,不容易接近与亲近,难以融洽相处,如同两个人不团结,常吵嘴打架,或互相排挤,这种“比”叫“敌比”。\n如在鼎卦中,九四爻与六五爻、上九爻与六五爻、初六爻与九二爻之间,是异性相吸的“亲比”关系;而九三爻与九四爻、九二爻与九三爻之间,是同性相斥的“敌比”。\n最重要的“比”\n一卦六爻中,共有五对“比”的关系,其中只有一组“比”最重要,即四爻与五爻的“比”。五爻是君王之爻,是一卦中最重要的爻。因此它的“比”的关系比其他爻的“比”更重要。可以和它相比之爻为上爻和四爻,而上爻象征处于虚位的事物,如太上皇、宗庙、祖宗等,已经退居幕后,虽然还有资格和影响力,但已经没有实权。所以四爻与它的“比”最重要。从现实中来看也是如此,处于四爻的人物与五爻的人物的关系配合好了,对大局最为有利。\n![[assets/pasted image 20231030113131.png]]\n在不同的机构或群体中,通过卦爻的“比”可以看出相邻单位、部门和人员之间的配合关系。无论是集团之间的大体情况,还是个人之间的细小关系,都可以通过卦爻的“亲比”与“敌比”来分析。\n应 相隔两位之爻的响应\n“应”是相应、相互呼应、支援、观望之意。在一个六爻卦中,相隔两爻的两个爻之间都有相互呼应的关系,这被称为“应”。\n在一卦中,初爻与四爻分别处于内外卦的最下位;二爻与五爻分别处于内外卦的中位;三爻与上爻分别处于内外卦的最上位。由于处在相似的位置上,它们互相间有很多共性,能有共同的感受和体会,可以相互理解、照顾、呼应,这种关系称之为“应”。\n应的原则一:同性相斥,异性相吸\n与“比”相同,应也符合“同性相斥,异性相吸”的原则。在相应的两个爻中,如果一个是阳爻,一个是阴爻,为“阴阳相应”或“得应”,是一种相互吸引的“应”,就像一方的观点或行为与另一方能一拍即合、相互呼应、心有灵犀、引起共鸣。如果相应的两爻都是阴爻或阳爻,则“同性相斥”,是一种相互排斥的“应”,被称为“敌应”或“不应”。如果二爻与五爻的阴阳性质相反,则两者既“得中”又“得应”,大吉。\n应的原则二:物以稀为贵\n应还有一个原则:“物以稀为贵”。如果卦中阴阳爻数量不等,哪种爻的数量少就以哪种爻为主。\n如风天小畜卦,六四爻是阴爻,其余五个阳爻都与六四爻相应,是众阳应一阴的状况。如果女性占得此卦,有身边男人众多之象。\n![[assets/pasted image 20231030113545.png|150]]\n再如火天大有卦,六五爻的卦辞为“柔得尊位,大中,上下应之”。六五爻处在君王之尊位,所以叫“柔得尊位”;“大中”指六五爻在外卦中间的核心位置;“上下应之”指除了六五爻与九二爻阴阳相应以外,所有的阳爻都与六五爻相应,即“五阳应一阴”。这对六五爻非常有利。\n“世应”关系\n在京房的六爻预测理论中,“应”的关系被发挥为“世应”关系。卦中的主体爻叫世爻,与它相应的另一个爻叫应爻。“世应”爻之间永远相隔两个爻的位置。事物的本源、主体通过世爻来表达,与它相对应、呼应的客体用应爻来表达。\n应:与“比”类似的关系\n“比”是相邻的两个爻的关系,“应”则是相隔两爻的两个爻的关系,两者的共同之处是,都符合“同性相斥,异性相吸”的原则。\n中 内外卦的两个吉位\n在一个六爻卦中,二爻与五爻可以视为阴阳两种事物的核心。不论阳爻或阴爻,只要处在二爻或五爻的位置就叫“得中”“处中”“中位”等。\n“中位”的概念《易传》中认为,“二爻多誉”、“五爻多功”,所以二爻和五爻的爻辞大都比较好。因为二爻在内卦的中间,是内卦的核心;五爻在外卦的中间,是外卦的核心。五爻的位置被称做君位、至尊之位。如果五爻是阳爻,又与内卦的核心(二爻)阴阳相应,就是既“得中”“得正”,又“得应”的大好状态,这在《易经》中被称为“大中”。蒋介石字中正,他的“中”“正”两字就取自《易经》中五爻处“尊位”之意。\n阳爻处在一、三、五爻的位置上为吉,如果处在二爻位上为“失位”,不吉。但因为它占据了“中位”,可以遇难成祥。比如地水师卦,二爻为阳爻,这是不正当的。但二爻为“中位”,又与六五爻呈阴阳相应之势,能获得高层的有力支持,可以逢凶化吉。\n“阳得中”和“阴得中”\n分析卦爻时应先看卦爻是“阳得中”还是“阴得中”。如果二爻为阴爻、五爻为阳爻,这是最吉的情况。\n如果阳爻在四爻或上爻位置上,或阴爻在初爻或三爻位置上,都为“不得中”又“失位”,为不吉。但这种判断只是初级的判断,吉有最吉、吉、小吉之分;凶有最凶、凶、小凶之分。不同程度的吉凶要靠卦爻的位置和变化来综合分析。\n比如鼎卦,唯一的“当位”者是九三爻,它是阳爻,又处在阳位上。除九三爻外,其他所有爻都“失位”。二爻和五爻虽为中位,但二爻为阳爻得中位;五爻为阴爻得中位。此外,初六爻与九四爻相应;九二爻与六五爻相应,它们都是阴阳相应,为相互吸引的相应;而九三爻与上九爻都是阳爻,它们的应是同性相斥的“敌应”,是相互排斥的“应”。\n二爻和五爻:内外卦的核心\n二爻和五爻是卦的“中位”,它们分别为内外卦的核心,所以《易经》中的二爻和五爻的爻辞大都比较好。\n![[assets/pasted image 20231030114703.png|210]] ![[assets/pasted image 20231030114722.png|200]] ![[assets/pasted image 20231030114736.png|200]]\n是否“得中”只是判断卦爻吉凶的一个角度,要想全面判断一卦六爻的吉凶,要把前面介绍的所有概念糅合到一起,相互参考,下面以鼎卦为例来综合分析卦爻。\n![[assets/pasted image 20231030114829.png]]\n鼎卦中,唯一的“当位”者是九三爻,此外所有爻都“失位”。在二爻和五爻中,二爻为阳爻得中位;五爻为阴爻得中位。初六爻与九四爻相应;九二爻与六五爻相应,它们都是阴阳相应,为相互吸引的相应;九三爻与上九爻都是阳爻,是同性的应,为相互排斥的“敌应”。\n:: 好嘛,也没说具体怎么样……\n五爻:阳爻的至尊\n如果在一、三、五爻的位置上都是阳爻,那么一爻不如三爻的位置更好,三爻不如五爻的位置更好,即阳爻越靠近五爻越好。因为五爻是君王尊位,它是一、三、五数中最大的一个阳数,是阳气最充盛的位置,既“多功”又“得中”,还与下卦的核心——二爻相应,所以是六个爻位中最好的。\n二爻:阴爻的至吉\n“阴”性事物的吉凶与“阳”性事物是相反的。阳象征天、活力及能量,以升发扩大为吉,越向上越好;而阴象征地、收敛及聚拢,以沉降凝聚为吉,越向下越好。所以,卦中阴爻离二爻越近越好,二、四、六爻以二爻最吉,四爻次吉,六爻最凶。\n事物的发散和聚集\n可以把阳爻比喻为清气,以向上升为运动趋势;阴爻为浊气,以向下降为运动趋势。那么阴气由外向内、由上而下、向凝聚的方向发展则吉,在卦中表现为阴爻由外卦向内卦发展;阳气由内向外、由下而上、向发散的方向发展则吉,在卦中表现为由内卦向外卦发展。\n阴阳爻的这种反向的运动趋势与“宇宙大爆炸”及“黑洞”“白洞”的理论相似。爆炸初始,混沌的宇宙呈现扩展的“阳性”状态。随着阳性事物的不断扩大旋转,形成了星系、恒星。恒星继续旋转压缩,会把较轻的粒子抛出,形成中子星。中子星再旋转,将较轻的粒子抛出,形成了“阴性”的黑洞。黑洞有极强的吸引力、凝聚力,任何物质能量都会被吸进去。吸得越多,能量越充足,但积蓄到一定的程度时,又会产生能量的释放,重新转化成扩展的“阳性”状态,即白洞。\n在黑洞与白洞的属性中,黑洞的“收缩”性相似于阴爻的下降趋势,白洞的“释放”性相似于阳爻的上升趋势。\n爻位吉凶与阴阳爻的性质\n在一卦中,五爻是阳气最充盛的位置,二爻是阴气最充盛的位置,所以二爻与五爻是卦中的大吉之位。阴阳的吉凶位置取决于阴阳之气的运动趋势,就如同宇宙中的黑洞与白洞。\n影响爻位吉凶的因素\n![[assets/pasted image 20231030115453.png]]\n阴阳之气的运动趋势\n阳爻越靠近五爻越好,阴爻越靠近二爻越好。这种性质是由阴阳爻的运动趋势决定的。\n![[assets/pasted image 20231030115610.png]]\n阳爻如同清气,以向上升为运动趋势;阴爻如同浊气,以向下降为运动趋势。物理学中,黑洞的“收缩”性相似于阴爻的下降趋势,白洞的“释放”性相似于阳爻的上升趋势。\n象形 根据卦形寻找可类比的事物\n某些事物的结构、形象可以用来比喻六十四卦中的卦象,这种比喻称之为“象形”。\n六十四卦中,有很多卦的卦名都是根据“象形”的规律起出来的,卦辞爻辞中也有很多词句与“象形”的含义有关。这种象形的比喻一方面有助于后人记住其卦象,另一方面也有助于人们理解其卦辞。下面举三个卦的例子说明“象形”的含义。\n鼎卦之象\n鼎是青铜器时代最重要的器种之一,它本来是古代的烹饪之器,相当于现在的锅,用以烹煮和盛贮肉类。一般来说,鼎有三足的圆鼎和四足的方鼎两类,又可分为有盖的和无盖的两种。但后来,鼎逐渐退出了炊具的位置,而演变为国家重器、权力的象征,“鼎”字也被赋予“显赫”“尊贵”“盛大”等引申意义。这种传统从夏朝、商朝、周朝直至秦汉,延续了两千多年。这期间,鼎一直是最常见和最神秘的礼器。\n![[assets/pasted image 20231030115834.png]]\n在《易经》中,火风“鼎”卦的卦象结构就与鼎的象形结构相对应。鼎卦初爻是阴爻,与鼎的脚相对应,它上面的三个阳爻象征鼎身的大肚子。鼎身上面是鼎口,这与三至五爻的兑卦的卦意(兑为口)相合。鼎卦五爻象征鼎的两个提耳,这与四至上爻的离卦的卦意相合。\n两种吃的卦象\n![[assets/pasted image 20231030115910.png]]\n山雷颐卦象征嘴和吃。卦中只有两个阳爻,分别在最上面和最下面,象征嘴唇,中间的四个阴爻象征牙齿。\n![[assets/pasted image 20231030115925.png]]\n而火雷噬嗑卦象征的也是吃的形象,两卦的不同在于:噬嗑卦的四爻是阳爻,象征吃东西时被食物到了牙齿,还没有咽下去,要继续吃就必须把食物咬碎,因此会发出声音,并带来一定的危险。噬嗑卦中,三至五爻为坎卦,坎代表险陷、危险的事物。而二至四爻为艮卦,代表山,有阻止之意。有石头那么硬的东西在口中阻止下咽,要小心,慢慢吃才安全。\n吉凶断语 《易经》的结构及作者\n上分《经部》和《传部》两部分。《经部》依照文王六十四卦卦序对六十四卦进行一一解读,其中《上经》收录三十卦,《下经》收录三十四卦。每卦都包含卦画、标题、卦辞、爻辞等内容。一般认为,《经部》成书于西周初期,而《传部》则被认为是春秋时期孔子及其弟子的作品。\n《易经》是几代人的集体创作。《经部》是《易经》最早形成的部分,一般认为,六十四卦的卦画由伏羲推演而来,周文王作卦辞,即对卦象整体的吉凶解释,并确定了《易经》以“乾”为第一卦;后来周文王之子、周武王之弟周公又进一步对每卦六爻进行分析,创制了“爻辞”。《经部》的卦辞和爻辞都非常简单精练,但往往让后人不知所云。\n《经部》的内容包括各卦的卦象、卦名,以及对卦名的解释。如“乾为天,乾上乾下”,点明了乾卦的属性和卦象。随后的简单精练的卦辞是对这一卦总体吉凶的判断。如乾卦的卦辞是“元、亨、利、贞”。在卦辞之后是与该卦相应的“彖辞”和“象辞”。“彖辞”和“象辞”之后是六爻的爻辞。每卦的六爻象征不同的事物,也代表不同的吉凶。卦辞和爻辞是《易经》的精华,是判断吉凶的主要依据。\n《易经》经文的作者\n从《易经》各卦卦象的确定到卦辞、爻辞、文言等文字内容的完成,经历了很长的时间。《易经》是几代人的集体创作,是古代先贤共同的智慧结晶。\n一般认为,六十四卦的卦画由伏羲推演而来,卦辞由周文王所作,即对整体卦象的吉凶解释。后来周文王之子周公又进一步对各卦六爻进行分析,创制了“爻辞”。下面以乾卦为例来说明。\n一般认为,“象”“彖”和“文言”是孔子所作,但附于《传部》内。东汉的郑玄将“象”和“彖”与六十四卦经文合在一起,并列于每卦爻辞之后。魏晋时期的王弼将“文言”拆开,附于“乾”“坤”两卦之后,直至今日。\n总断卦辞的符号:元亨利贞\n“元、亨、利、贞”四字是《易经》卦辞的总断符号,是最基本的占断用语。这四个字在《易经》中出现的频率非常高,其重要性非同一般。它们原本被用来形容人的品德,后来人们又发展出了“四德解说”“四时解说”“四个阶段解说”等说法说明其功用。\n《易经》六十四卦,有的卦的卦辞中“元亨利贞”四字全有;有的卦有“元亨利”“元利贞”“亨利贞”三个字;有的卦有“元亨”“元贞”“亨利”“亨贞”或“利贞”两个字;有的卦有“亨”“利”“贞”一个字;也有的卦四个字全都没有。“元亨利贞”四个字是分开解释的,简单来说,它们的意思分别是“大”“通”“有利”“正固”,详细来说,分为以下几种情况:\n一、元:有开始、大吉之意,也象征从无到有、从有到小、从小到大、从大到强、从强到久的过程。元分为三种:1.“元吉”是大吉;2.“元亨”是大亨;3.“元夫”有元老之意。\n二、亨:有向上、亨通、通达之意,组织筹划并执行控制,将“元”的精神落实,就为亨。亨分为三种:1.“亨”是亨通;2.“元亨”是大亨;3.“小亨”是古人的一种祭祀。\n三、利:有有益、收获、和谐、凝聚人心之意。如同振臂一呼,应者云集。利分为四种:1.“无不利”指所做之事很有利;2.“无所利”指所做的事情皆无利;3.“利某”或“不利某”指对某人或某事有利或不利;4.“利贞”指所做之事有利。\n四、贞:有收藏、正固、占筮之意。可理解为对理想的坚持与忠诚,遇到挫折不回头,不会顾此失彼、三心二意、见异思迁、遗忘目标。贞分为五种:1.“贞吉”是所占之事吉;2.“贞凶”是所占之事凶;3.“贞厉”是所占事有危险;4.“可贞”是所占之事可行;5.“利贞”是所占之事有利。\n吉凶的六个等级:吉吝厉悔咎凶\n“吉、吝、厉、悔、咎、凶”不仅是卦辞和爻辞的吉凶标志,也是对吉凶程度的具体划分。只有懂得这几个术语的基本含义,才能初步理解卦辞爻辞的内涵。\n生活中,我们常常用吉来表示好的事情,凶表示不好的事情。在占筮中,吉凶也是代表着好与不好最基本的术语。吉指吉祥、吉利,预示着有所得,会走向成功等;凶指凶险、凶恶,预示着有所失,会走向失败等。吉凶因程度不同而分为六种,按由好到坏的顺序依次是:吉、吝、厉、悔、咎、凶。\n![[assets/pasted image 20231030140057.png]]\n一、吉:1.“吉”是吉祥、吉利;2.“初吉”指事情的开始时吉,此外还有“中吉”“终吉”,都是指事情不同阶段的吉祥;3.“贞吉”指占筮得到此卦为吉;4.“大吉”指非常吉祥;5.“元吉”同“大吉”。\n二、吝:1.“吝”是艰难、羞辱之意;2.“小吝”是遭遇小人而艰难;3.“终吝”指到最后还是艰难;4.“贞吝”是所占的事将遇艰难。\n三、厉:1.“厉”是危险,但吉凶未定;2.“有厉”是有危险的意思;3.“贞厉”指所占筮之事有危险。\n四、悔:1.“悔”是后悔、忧虑,有烦恼;2.“有悔”是有困厄;3.“悔有悔”是困扰之事接踵而至;4.“无悔”是无困扰;5.“悔亡”是过去的困扰已经消失。\n五、咎:1.“咎”是出了过失、灾患,要承担责任,但比“凶”的结果要好一些;2.“为咎”是将成为灾患;3.“匪咎”是指不是灾患;4.“何咎”是不构成什么灾患;5.“无咎”即无灾患。\n六、凶:1.“凶”是祸殃,凶险,是最坏的结果。2.“终凶”指事情的最终结果为凶;3.“有凶”指有灾祸;4.“贞凶”指占筮得到此卦为凶。\n《易经》中没有“祸福”的概念\n本类目(吉凶断语)所讲断辞的目的是让人们通过不同的概念来辨别吉凶的差异,这带有积极的精神,与“祸福”的消极态度不同。《易经》中没有“祸福”的字眼。“祸福”是后人强加于《易经》的概念。在《易经》看来,吉凶是相互转化的,没有永恒的吉或凶。\n转换了角度、条件、标准来判断,得到的结论往往差别很大。从某个角度看是吉,从另一个角度看可能就是凶。吉凶的这种变化性、多角度性比吉凶的程度更重要。如果不慎始如终、慎终如始,任何事情都可以转为凶险。而“祸福”的含义则相对固定,无法表达出这种变化性。\n易经原文 第一卦 乾 乾为天 乾上乾下 ![[assets/pasted image 20231030142734.png]]\n乾:元,亨,利,贞。\n初九:潜龙,勿用。\n九二:见龙再田,利见大人。\n九三:君子终日乾乾,夕惕若,厉无咎。\n九四:或跃在渊,无咎。\n九五:飞龙在天,利见大人。\n上九:亢龙有悔。用九:见群龙 无首,吉。\n彖曰:大哉乾元,万物资始,乃统天。云行雨施,品物流形。大明始终,六位时成,时乘六龙以御天。乾道变化,各正性命,保合大和,乃利贞。首出庶物,万国咸宁。\n象曰:天行健,君子以自强不息。潜龙勿用,阳在下也。见龙再田,德施普也。终日乾乾,反复道也。或跃在渊,进无咎也。飞龙在天,大人造也。亢龙有悔,盈不可久也。用 九,天德不可为首也。\n文言曰:「元者,善之长也,亨者,嘉之会也,利者,义之和也,贞者,事之干也。君子体仁,足以长人;嘉会,足以合礼;利物,足以和义;贞固,足以干事。君子行此四者, 故曰:乾:元亨利贞。」\n初九曰:「潜龙勿用。」何谓也?\n子曰:「龙德而隐者也。不易乎世,不成乎名;遯世而无闷,不见是而无闷;乐则行之,忧则违之;确乎其不可拔,乾龙也。」\n九二曰:「见龙在田,利见大人。」何谓也?\n子曰:「龙德而正中者也。庸言之信,庸行之谨,闲邪存其诚,善世而不伐,德博而化。易曰:「见龙在田,利见大人。」君德 也。」\n九三曰:「君子终日乾乾,夕惕若,厉无咎。」何谓也?\n子曰:「君子进德修业,忠信,所以进德也。修辞立其诚,所以居业也。知至至之,可与几也。知终终之,可与存义 也。是故,居上位而不骄,在下位而不忧。故乾乾,因其时而惕,虽危而无咎矣。」\n九四:「或跃在渊,无咎。」何谓也?\n子曰:「上下无常,非为邪也。进退无恒,非离群也。君子进德修业,欲及时也,故无咎。」\n九五曰:「飞龙在天,利见大人。」何谓也?\n子曰:「同声相应,同气相求;水流湿,火就燥;云从龙,风从虎。圣人作,而万物覩,本乎天者亲上,本乎地者亲下,则各从其 类也。\n上九曰:「亢龙有悔。」何谓也?\n子曰:「贵而无位,高而无民,贤人在下而无辅,是以动而有悔也。」\n乾龙勿用,下也。见龙在田,时舍也。终日乾乾,行事也。或跃在渊,自试也。飞龙在天,上治也。亢龙有悔,穷之灾也。乾元用九,天下治也。\n乾龙勿用,阳气潜藏。见龙在田,天下文明。终日乾乾,与时偕行。或跃在渊,乾道乃革。飞龙在天,乃位乎天德。亢龙有悔,与时偕极。乾元用九,乃见天则。\n乾元者,始而亨者也。利贞者,性情也。乾始能以美利利天下,不言所利。大矣哉!大哉乾乎?刚健中正,纯粹精也。六爻发挥,旁通情也。时乘六龙,以御天也。云行雨施,天 下平也。\n君子以成德为行,日可见之行也。潜之为言也,隐而未见,行而未成,是以君子弗用也。\n君子学以聚之,问以辩之,宽以居之,仁以行之。易曰:「见龙在田,利见大人。」君德也。\n九三,重刚而不中,上不在天,下不在田。故乾乾,因其时而惕,虽危无咎矣。\n九四,重刚而不中,上不在天,下不在田,中不在人,故或之。或之者,疑之也,故无咎。\n夫大人者,与天地合其德,与日月合其明,与四时合其序,与鬼神合其吉凶。先天下而天弗违,后天而奉天时。天且弗违,而况於人乎?况於鬼神乎?\n亢之为言也,知进而不知退,知存而不知亡,知得而不知丧。其唯圣人乎?知进退存亡,而不失其正者,其为圣人乎?\n第二卦 坤 坤为地 坤上坤下 ![[assets/pasted image 20231030142811.png]]\n坤:元,亨,利牝马之贞。君子有攸往,先迷后得主,利西南得朋,东北丧朋。安贞,吉。\n彖曰:至哉坤元,万物资生,乃顺承天。坤厚载物,德合无疆。含弘光大,品物咸亨。\n牝马地类,行地无疆,柔顺利贞。君子攸行,先迷失道,后顺得常。西南得朋,乃与类行;东北丧朋,乃终有庆。安贞之吉,应地无疆。\n象曰:地势坤,君子以厚德载物。\n初六:履霜,坚冰至。象曰:履霜坚冰,阴始凝也。驯致其道,至坚冰也。\n六二:直,方,大,不习无不利。象曰:六二之动,直以方也。不习无不利,地道光也。\n六三:含章可贞。或从王事,无成有终。象曰:含章可贞;以时发也。或从王事,知光大也。\n六四:括囊;无咎,无誉。象曰:括囊无咎,慎不害也。\n六五:黄裳,元吉。象曰:黄裳元吉,文在中也。\n上六:战龙於野,其血玄黄。象曰:战龙於野,其道穷也。\n用六:利永贞。象曰:用六永贞,以大终也。\n文言曰:坤至柔,而动也刚,至静而德方,后得主而有常,含万物而化光。坤其道顺乎?承天而时行。\n积善之家,必有馀庆;积不善之家,必有馀殃。臣弑其君,子弑其父,非一朝一夕之故,其所由来者渐矣,由辩之不早辩也。易曰:「履霜坚冰至。」盖言顺也。\n直其正也,方其义也。君子敬以直内,义以方外,敬义立,而德不孤。「直,方,大,不习无不利」;则不疑其所行也。\n阴虽有美,含之;以从王事,弗敢成也。地道也,妻道也,臣道也。地道无成,而代有终也。\n天地变化,草木蕃;天地闭,贤人隐。易曰:「括囊;无咎,无誉。」盖言谨也。\n君子黄中通理,正位居体,美在其中,而畅於四支,发於事业,美之至也。\n阴疑於阳,必战。为其嫌於无阳也,故称龙焉。犹未离其类也,故称血焉。夫玄黄者,天地之杂也,天玄而地黄。\n第三卦 屯 水雷屯 坎上震下 ![[assets/pasted image 20231030142848.png]]\n屯:元,亨,利,贞,勿用,有攸往,利建侯。\n彖曰:屯,刚柔始交而难生,动乎险中,大亨贞。雷雨之动满盈,天造草昧,宜建侯而不宁。\n象曰:云,雷,屯;君子以经纶。\n初九:磐桓;利居贞,利建侯。象曰:虽磐桓,志 行正也。以贵下贱,大得民也。\n六二:屯如(辶颤)如,乘马班如。匪寇婚媾,女子贞不字,十年乃字。象曰:六二之难,乘 刚也。十年乃字,反常也。\n六三:既鹿无虞,惟入于林中,君子几不如舍,往吝。象曰:既鹿无虞,以纵禽也。君子舍之,往吝穷也。\n六四:乘马班如,求婚媾,无不利。象曰:求而往,明也。\n九五:屯其膏,小贞吉,大贞凶。象曰:屯其膏,施未光也。\n上六:乘马班如,泣血涟如。象曰:泣血涟如,何可长也。\n第四卦 蒙 山水蒙 艮上坎下 ![[assets/pasted image 20231030142957.png]]\n蒙:亨。匪我求童蒙,童蒙求我。初噬告,再三渎,渎则不告。利贞。\n彖曰:蒙,山下有险,险而止,蒙。蒙亨,以亨行时中也。匪我求童蒙,童蒙求我,志应也。初噬告,以刚中也。再三渎,渎则不告,渎蒙也。蒙以养正,圣功也。\n象曰:山下出 泉,蒙;君子以果行育德。\n初六:发蒙,利用刑人,用说桎梏,以往吝。象曰:利用刑人,以正法也。\n九二:包蒙吉;纳妇吉;子克家。象曰:子克家,刚柔接也。\n六三:勿用娶女;见金夫,不有躬,无攸利。象曰:勿用娶女,行不顺也。\n六四:困蒙,吝。象曰:困蒙之吝,独远实也。\n六五:童蒙,吉。象曰:童蒙之吉,顺以巽也。\n上九:击蒙;不利为寇,利御寇。象曰:利用御寇,上下顺也。\n第五卦 需 水天需 坎上乾下 ![[assets/pasted image 20231030143023.png]]\n需:有孚,光亨,贞吉。利涉大川。\n彖曰:需,须也;险在前也。刚健而不陷,其义不困穷矣。需有孚,光亨,贞吉。位乎天位,以正中也。利涉大川,往有功也。\n象曰:云上於天,需;君子以饮食宴乐。\n初九:需 于郊。利用恒,无咎。象曰:需于郊,不犯难行也。利用恒,无咎;未失常也。\n九二:需于沙。小有言,终吉。象曰:需于沙,衍在中也。虽小有言,以终吉也。\n九三:需于泥,致寇至。象曰:需于泥,灾在外也。自我致寇,敬慎不败也。\n六四:需于血,出自穴。象曰:需于血,顺以听也。\n九五:需于酒食,贞吉。象曰:酒食贞吉,以中正也。\n上六:入于穴,有不速之客三人 来,敬之终吉。象曰:不速之客来,敬之终吉。虽不当位,未大失也。\n第六卦 讼 天水讼 乾上坎下 ![[assets/pasted image 20231030143054.png]]\n讼:有孚,窒。惕中吉。终凶。利见大人,不利涉大川。\n彖曰:讼,上刚下险,险而健讼。讼有孚窒,惕中吉,刚来而得中也。终凶;讼不可成也。利见大人;尚中正也。不利涉大川;入于渊也。\n象曰:天与水违行,讼;君子以作事谋始。\n初六:不永所事,小有言,终吉。象曰:不 永所事,讼不可长也。虽有小言,其辩明也。\n九二:不克讼,归而逋,其邑人三百户,无眚。象曰:不克讼,归而逋也。自下讼上,患至掇也。\n六三:食旧德,贞厉,终吉,或从王事,无成。象曰:食旧德,从上吉也。\n九四:不克讼,复自命,渝安贞,吉。象曰:复即命,渝安贞;不失也。\n九五:讼元吉。象曰:讼元吉,以中正也。\n上九:或锡之鞶带,终朝三褫之。象曰:以讼受服,亦不足敬也。\n第七卦 师 地水师 坤上坎下 ![[assets/pasted image 20231030143121.png]]\n师:贞,丈人,吉无咎。\n彖曰:师,众也,贞正也,能以众正,可以王矣。刚中而应,行险而顺,以此毒天下,而民从之,吉又何咎矣。\n象曰:地中有水,师;君子以容民畜众。\n初六:师出以律,否臧凶。象曰:师出以律, 失律凶也。\n九二:在师中,吉无咎,王三锡命。象曰:在师中吉,承天宠也。王三锡命,怀万邦也。\n六三:师或舆尸,凶。象曰:师或舆尸,大无功也。\n六四:师左次,无咎。象曰:左次无咎,未失常也。\n六五:田有禽,利执言,无咎。长子帅师,弟子舆尸,贞凶。象曰:长子帅师,以中行也。弟子舆师,使不当也。\n上六:大君有命,开国承家,小人勿用。象曰:大君有命,以正功也。小人勿用,必乱邦也。\n第八卦 比 水地比 坎上坤下 ![[assets/pasted image 20231030143155.png]]\n比:吉。原筮元永贞,无咎。不宁方来,后夫凶。\n彖曰:比,吉也,比,辅也,下顺从也。原筮元永贞,无咎,以刚中也。不宁方来,上下应也。后夫凶,其道穷也。\n象曰:地上有水,比;先王以建万国,亲诸侯。\n初六:有孚比之,无咎。有孚盈缶,终 来有他,吉。象曰:比之初六,有他吉也。\n六二:比自内,贞吉。象曰:比之自内,不自失也。\n六三:比之匪人。象曰:比之匪人,不亦伤乎!\n六四:外比之,贞吉。象曰:外比於贤,以从上也。\n九五:显比,王用三驱,失前禽。邑人不诫,吉。象曰:显比之吉,位正中也。舍逆取顺,失前禽也。邑人不诫,上使中也。\n上六:比之无首,凶。象曰:比之无首,无所终也。\n第九卦 小畜 风天小畜 巽上乾下 ![[assets/pasted image 20231030143228.png]]\n小畜:亨。密云不雨,自我西郊。\n彖曰:小畜;柔得位,而上下应之,曰小畜。健而巽,刚中而志行,乃亨。密云不雨,尚往也。自我西郊,施未行也。\n象曰:风行天上,小畜;君子以懿文德。\n初九:复自道,何其咎,吉。象曰:复自道, 其义吉也。\n九二:牵复,吉。象曰:牵复在中,亦不自失也。\n九三:舆说辐,夫妻反目。象曰:夫 妻反目,不能正室也。\n六四:有孚,血去惕出,无咎。象曰:有孚惕出,上合志也。\n九五:有孚挛如,富以其邻。象曰:有孚挛如,不独富也。\n上九:既雨既处,尚德载,妇贞厉。月几望,君子征凶。象曰:既雨既处,德积载也。君子征凶,有所疑也。\n第十卦 履 天泽履 乾上兑下 ![[assets/pasted image 20231030143249.png]]\n履:履虎尾,不咥人,亨。\n彖曰:履,柔履刚也。说而应乎乾,是以履虎尾,不咥人,亨。刚中正,履帝位而不疚,光明也。\n象曰:上天下泽,履;君子以辨上下,安民志。\n初九:素履,往无咎。象曰:素履之 往,独行愿也。\n九二:履道坦坦,幽人贞吉。象曰:幽人贞吉,中不自乱也。\n六三:眇能视,跛能履,履虎尾,咥人,凶。武人为于大君。象曰:眇能视;不足以有明也。跛能履;不足以与行也。咥人之凶;位不当也。武人为于大君;志刚也。\n九四:履虎尾,愬愬终吉。象曰:愬愬终吉,志行也。\n九五:夬履,贞厉。象曰:夬履贞厉,位正当也。\n上九:视履考祥,其旋元吉。象曰:元吉在上,大有庆也。\n第十一卦 泰 天地泰 坤上乾下 ![[assets/pasted image 20231030143323.png]]\n泰:小往大来,吉亨。\n彖曰:泰,小往大来,吉亨。则是天地交,而万物通也;上下交,而其志同也。内阳而外阴,内健而外顺,内君子而外小人,君子道长,小人道消也。\n象曰:天地交泰,后以财(裁)成天地之道,辅相天地之宜,以左右民。\n初九:拔茅 茹,以其夤,征吉。象曰:拔茅征吉,志在外也。\n九二:包荒,用冯河,不遐遗,朋亡,得尚于中行。象曰:包荒,得尚于中行,以光大也。\n九三:无平不陂,无往不复,艰贞无咎。勿恤其孚,于食有福。象曰:无往不复,天地际也。\n六四:翩翩不富,以其邻,不戒以孚。象曰:翩翩不富,皆失实也。不戒以孚,中心愿也。\n六五:帝乙归妹,以祉元吉。象曰:以祉元吉,中以行愿也。\n上六:城复于隍,勿用师。自邑告命,贞吝。象曰:城复于隍,其命乱也。\n第十二卦 否 地天否 乾上坤下 ![[assets/pasted image 20231030143332.png]]\n否:否之匪人,不利君子贞,大往小来。\n彖曰:否之匪人,不利君子贞。大往小来,则是天地不交,而万物不通也;上下不交,而天下无邦也。内阴而外阳,内柔而外刚,内小人而外君子。小人道长,君子道消也。\n象曰:天地不交,否;君子以俭德辟难,不可荣以禄。\n初六:拔茅茹,以其夤,贞吉 亨。象曰:拔茅贞吉,志在君也。\n六二:包承。小人吉,大人否亨。象曰:大人否亨,不乱群也。\n六三:包羞。象曰:包羞,位不当也。\n九四:有命无咎,畴离祉。象曰:有命无咎,志行也。\n九五:休否,大人吉。其亡其亡,系于苞桑。象曰:大人之吉,位正当也。\n上九:倾否,先否后喜。象曰:否终则倾,何可长也。\n第十三卦 同人 天火同人 乾上离下 ![[assets/pasted image 20231030143357.png]]\n同人:同人于野,亨。利涉大川,利君子贞。\n彖曰:同人,柔得位得中,而应乎乾,曰同人。同人曰,同人于野,亨。利涉大川,乾行也。文明以健,中正而应,君子正也。唯君子为能通天下之志。\n象曰:天与火,同人;君子以类族辨物。\n初九:同人于门,无咎。象曰:出门同人,又 谁咎也。\n六二:同人于宗,吝。象曰:同人于宗,吝道也。\n九三:伏戎于莽,升其高陵,三岁不兴。象曰:伏戎于莽,敌刚也。三岁不兴,安行也。\n九四:乘其墉,弗克攻,吉。象曰:乘其墉,义弗克也,其吉,则困而反则也。\n九五:同人,先号啕而后笑。大师克相遇。象曰:同人之先,以中直也。大师相遇,言相克也。\n上九:同人于郊,无悔。象曰:同人于郊,志未得也。\n第十四卦 大有 火天大有 离上乾下 ![[assets/pasted image 20231030143414.png]]\n大有:元亨。 彖曰:大有,柔得尊位,大中而上下应之,曰大有。其德刚健而文明,应乎天而时行,是以元亨。\n象曰:火在天上,大有;君子以竭恶扬善,顺天休命。\n初九:无交害,匪咎,艰则无 咎。象曰:大有初九,无交害也。\n九二:大车以载,有攸往,无咎。象曰:大车以载,积中不败也。\n九三:公用亨于天子,小人弗克。象曰:公用亨于天子,小人害也。九四:匪其彭,无咎。象曰:匪其彭,无咎;明辨晰也。\n六五:厥孚交如,威如;吉。象曰:厥孚交如,信以发志也。威如之吉,易而无备也。\n上九:自天佑之,吉无不利。象曰:大有上吉,自天佑也。\n第十五卦 谦 地山谦 坤上艮下 ![[assets/pasted image 20231030143445.png]]\n谦:亨,君子有终。\n彖曰:谦,亨,天道下济而光明,地道卑而上行。天道亏盈而益谦,地道变盈而流谦,鬼神害盈而福谦,人道恶盈而好谦。谦尊而光,卑而不可逾,君子之终也。\n象曰:地中有山,谦;君子以裒多益寡,称物平施。\n初六:谦谦君子,用涉大川,吉。 象曰:谦谦君子,卑以自牧也。\n六二:鸣谦,贞吉。象曰:鸣谦贞吉,中心得也。\n九三:劳谦君子,有终吉。象曰:劳谦君子,万民服也。\n六四:无不利,(扌为)谦。象曰:无不利,(扌为)谦;不违则也。\n六五:不富,以其邻,利用侵伐,无不利。象曰:利用侵伐,征不服也。\n上六:鸣谦,利用行师,征邑国。象曰:鸣谦,志未得也。可用行师,征邑国也。\n第十六卦豫 雷地豫 震上坤下 ![[assets/pasted image 20231030143427.png]]\n豫:利建侯行师。\n彖曰:豫,刚应而志行,顺以动,豫。豫,顺以动,故天地如之,而况建侯行师乎?天地以顺动,故日月不过,而四时不忒;圣人以顺动,则刑罚清而民服。豫之时义大矣哉!\n象曰:雷出地奋,豫。先王以作乐崇德,殷荐之上帝,以配祖考。\n初六:鸣豫,凶。象 曰:初六鸣豫,志穷凶也。\n六二:介于石,不终日,贞吉。象曰:不终日,贞吉;以中正也。\n六三:盱豫,悔。迟有悔。象曰:盱豫有悔,位不当也。\n九四:由豫,大有得。勿疑。朋盍簪。象曰:由豫,大有得;志大行也。\n六五:贞疾,恒不死。象曰:六五贞疾,乘刚也。恒不死,中未亡也。\n上六:冥豫,成有渝,无咎。象曰:冥豫在上,何可长也。\n第十七卦 随 泽雷随 兑上震下 ![[assets/pasted image 20231030143517.png]]\n随:元亨利贞,无咎。\n彖曰:随,刚来而下柔,动而说,随。大亨贞,无咎,而天下随时,随之时义大矣哉!\n象曰:泽中有雷,随;君子以向晦入宴息。\n初九:官有渝,贞吉。出门交有功。象曰: 官有渝,从正吉也。出门交有功,不失也。\n六二:系小子,失丈夫。象曰:系小子,弗兼与也。\n六三:系丈夫,失小子。随有求得,利居贞。象曰:系丈夫,志舍下也。\n九四:随有获,贞凶。有孚在道,以明,何咎。象曰:随有获,其义凶也。有孚在道,明功也。\n九五:孚于嘉,吉。象曰:孚于嘉,吉;位正中也。\n上六:拘系之,乃从维之。王用亨于西山。象曰:拘系之,上穷也。\n第十八卦 蛊 山风蛊 艮上巽下 ![[assets/pasted image 20231030143601.png]]\n蛊:元亨,利涉大川。先甲三日,后甲三日。\n彖曰:蛊,刚上而柔下,巽而止,蛊。蛊,元亨,而天下治也。利涉大川,往有事也。先甲三日,后甲三日,终则有始,天行也。\n象曰:山下有风,蛊;君子以振民育德。\n初六:干父之蛊,有子,考无咎,厉终吉。象 曰:干父之蛊,意承考也。\n九二:干母之蛊,不可贞。象曰:干母之蛊,得中道也。\n九三:干父小有晦,无大咎。象曰:干父之蛊,终无咎也。\n六四:裕父之蛊,往见吝。象曰:裕父之蛊,往未得也。\n六五:干父之蛊,用誉。象曰:干父之蛊;承以德也。\n上九:不事王侯,高尚其事。象曰:不事王侯,志可则也。\n第十九卦 临 地泽临 坤上兑下 ![[assets/pasted image 20231030143657.png]]\n临:元,亨,利,贞。至于八月有凶。\n彖曰:临,刚浸而长。说而顺,刚中而应,大亨以正,天之道也。至于八月有凶,消不久也。\n象曰:泽上有地,临;君子以教思无穷,容保民无疆。\n初九:咸临,贞吉。象曰:咸临 贞吉,志行正也。\n九二:咸临,吉无不利。象曰:咸临,吉无不利;未顺命也。\n六三:甘临,无攸利。既忧之,无咎。象曰:甘临,位不当也。既忧之,咎不长也。\n六四:至临,无咎。象曰:至临无咎,位当也。\n六五:知临,大君之宜,吉。象曰:大君之宜,行中之谓也。\n上六:敦临,吉无咎。象曰:敦临之吉,志在内也。\n第二十卦 观 风地观 巽上坤下 ![[assets/pasted image 20231030143710.png]]\n观:盥而不荐,有孚顒若。\n彖曰:大观在上,顺而巽,中正以观天下。观,盥而不荐,有孚颙若,下观而化也。观天之神道,而四时不忒,圣人以神道设教,而天下服矣。\n象曰:风行地上,观;先王以省方,观民设教。\n初六:童观,小人无咎,君子吝。象 曰:初六童观,小人道也。\n六二:窥观,利女贞。象曰:窥观女贞,亦可丑也。\n六三:观我生,进退。象曰:观我生,进退;未失道也。\n六四:观国之光,利用宾于王。象曰:观国之光,尚宾也。\n九五:观我生,君子无咎。象曰:观我生,观民也。\n上九:观其生,君子无咎。象曰:观其生,志未平也。\n第二十一卦 噬嗑 火雷噬嗑 离上震下 ![[assets/pasted image 20231030143847.png]]\n噬嗑:亨。利用狱。\n彖曰:颐中有物,曰噬嗑,噬嗑而亨。刚柔分,动而明,雷电合而章。柔得中而上行,虽不当位,利用狱也。\n象曰:雷电噬嗑;先王以明罚敕法。\n初九:履校灭趾,无咎。象曰:履校灭趾,不行 也。\n六二:噬肤灭鼻,无咎。象曰:噬肤灭鼻,乘刚也。\n六三:噬腊肉,遇毒;小吝,无咎。象曰:遇毒,位不当也。\n九四:噬乾胏,得金矢,利艰贞,吉。象曰:利艰贞吉,未光也。六五:噬乾肉,得黄金,贞厉,无咎。象曰:贞厉无咎,得当也。\n上九:何校灭耳,凶。象曰:何校灭耳,聪不明也。\n第二十二卦 贲 山火贲 艮上离下 ![[assets/pasted image 20231030143801.png]]\n贲:亨。小利有所往。\n彖曰:贲,亨;柔来而文刚,故亨。分刚上而文柔,故小利有攸往。天文也;文明以止,人文也。观乎天文,以察时变;观乎人文,以化成天下。\n象曰:山下有火,贲;君子以明庶政,无敢折狱。\n初九:贲其趾,舍车而徒。象曰:舍 车而徒,义弗乘也。\n六二:贲其须。象曰:贲其须,与上兴也。\n九三:贲如濡如,永贞吉。象曰:永贞之吉,终莫之陵也。\n六四:贲如皤如,白马翰如,匪寇婚媾。象曰:六四,当位疑也。匪寇婚媾,终无尤也。\n六五:贲于丘园,束帛戋戋,吝,终吉。象曰:六五之吉,有喜也。\n上九:白贲,无咎。象曰:白贲无咎,上得志也。\n第二十三卦 剥 山地剥 艮上坤下 ![[assets/pasted image 20231030143923.png]]\n剥:不利有攸往。\n彖曰:剥,剥也,柔变刚也。不利有攸往,小人长也。顺而止之,观象也。君子尚消息盈虚,天行也。\n象曰:山附地上,剥;上以厚下,安宅。\n初六:剥(爿木)以足,蔑贞凶。象曰:剥(爿木)以足,以灭下也。\n六二:剥(爿木)以辨,蔑贞凶。象曰:剥(爿木)以辨,未有与也。\n六三:剥之,无咎。象曰:剥之无咎,失上下也。\n六四:剥(爿木)以肤,凶。象曰:剥(爿木)以肤,切近灾也。\n六五:贯鱼,以宫人宠,无不利。象曰:以宫人宠,终无尤也。\n上九:硕果不食,君子得舆,小人剥庐。象曰:君子得舆,民所载也。小人剥庐,终不可用也。\n第二十四卦 复 地雷复 坤上震下 ![[assets/pasted image 20231030143934.png]]\n复:亨。出入无疾,朋来无咎。反复其道,七日来复,利有攸往。\n彖曰:复亨;刚反,动而以顺行,是以出入无疾,朋来无咎。反复其道,七日来复,天行也。利有攸往,刚长也。复其见天地之心乎?\n象曰:雷在地中,复;先王以至日闭关,商旅不行,后不省方。\n初九:不复远,无只 悔,元吉。象曰:不远之复,以修身也。\n六二:休复,吉。象曰:休复之吉,以下仁也。\n六三:频复,厉无咎。象曰:频复之厉,义无咎也。\n六四:中行独复。象曰:中行独复,以从道也。\n六五:敦复,无悔。象曰:敦复无悔,中以自考也。\n上六:迷复,凶,有灾眚。用行师,终有大败,以其国君,凶;至于十年,不克征。象曰:迷复之凶,反君道也。\n第二十五卦 无妄 天雷无妄 乾上震下 ![[assets/pasted image 20231030143946.png]]\n无妄:元,亨,利,贞。其匪正有眚,不利有攸往。\n彖曰:无妄,刚自外来,而为主於内。动而健,刚中而应,大亨以正,天之命也。其匪正有眚,不利有攸往。无妄之往,何之矣?天命不佑,行矣哉?\n象曰:天下雷行,物与无妄;先王以茂对时,育万物。\n初九:无妄,往吉。象曰:无妄 之往,得志也。\n六二:不耕获,不菑畲,则利有攸往。象曰:不耕获,未富也。\n六三:无妄之灾,或系之牛,行人之得,邑人之灾。象曰:行人得牛,邑人灾也。\n九四:可贞,无咎。象曰:可贞无咎,固有之也。 九五:无妄之疾,勿药有喜。象曰:无妄之药,不可试也。\n上九:无妄,行有眚,无攸利。象曰:无妄之行,穷之灾也。\n第二十六卦 大畜 山天大畜 艮上乾下 ![[assets/pasted image 20231030144010.png]]\n大畜:利贞,不家食吉,利涉大川。\n彖曰:大畜,刚健笃实辉光,日新其德,刚上而尚贤。能止健,大正也。不家食吉,养贤也。利涉大川,应乎天也。\n象曰:天在山中,大畜;君子以多识前言往行,以畜其德。\n初九:有厉利已。象曰:有 厉利已,不犯灾也。\n九二:舆说辐。象曰:舆说辐,中无尤也。\n九三:良马逐,利艰贞。曰闲舆卫,利有攸往。象曰:利有攸往,上合志也。\n六四:童豕之牿,元吉。象曰:六四元吉,有喜也。\n六五:豶豕之牙,吉。象曰:六五之吉,有庆也。\n上九:何天之衢,亨。象曰:何天之衢,道大行也。\n第二十七卦 颐 山雷颐 艮上震下 ![[assets/pasted image 20231030144022.png]]\n颐:贞吉。观颐,自求口实。\n彖曰:颐贞吉,养正则吉也。观颐,观其所养也;自求口实,观其自养也。天地养万物,圣人养贤,以及万民;颐之时义大矣哉!\n象曰:山下有雷,颐;君子以慎言语,节饮食。\n初九:舍尔灵龟,观我朵颐,凶。象 曰:观我朵颐,亦不足贵也。\n六二:颠颐,拂经,于丘颐,征凶。象曰:六二征凶,行失类也。\n六三:拂颐,贞凶,十年勿用,无攸利。象曰:十年勿用,道大悖也。\n六四:颠颐吉,虎视眈眈,其欲逐逐,无咎。象曰:颠颐之吉,上施光也。\n六五:拂经,居贞吉,不可涉大川。象曰:居贞之吉,顺以从上也。\n上九:由颐,厉吉,利涉大川。象曰:由颐厉吉,大有庆也。\n第二十八卦 大过 泽风大过 兑上巽下 ![[assets/pasted image 20231030144051.png]]\n大过:栋桡,利有攸往,亨。\n彖曰:大过,大者过也。栋桡,本末弱也。刚过而中,巽而说行,利有攸往,乃亨。大过之时义大矣哉!\n象曰:泽灭木,大过;君子以独立不惧,(辶豚)世无闷。\n初六:藉用白茅,无咎。象曰:藉用白茅,柔在下也。\n九二:枯杨生稊,老夫得其女妻,无不利。象曰:老夫女妻,过以相与也。\n九三:栋桡,凶。象曰:栋桡之凶,不可以有辅也。\n九四:栋隆,吉;有它吝。象曰:栋隆之吉,不桡乎下也。\n九五:枯杨生华,老妇得士夫,无咎无誉。象曰:枯杨生华,何可久也。老妇士夫,亦可丑也。\n上六:过涉灭顶,凶,无咎。象曰:过涉之凶,不可咎也。\n第二十九卦 坎 坎为水 坎上坎下 ![[assets/pasted image 20231030144107.png]]\n坎:习坎,有孚,维心亨,行有尚。\n彖曰:习坎,重险也。水流而不盈,行险而不失其信。维心亨,乃以刚中也。行有尚,往有功也。天险不可升也,地险山川丘陵也,王公设险以守其国,坎之时用大矣哉!\n象曰:水洊至,习坎;君子以常德行,习教事。\n初六:习坎,入于坎窞,凶。象曰:习坎入坎,失道凶也。\n九二:坎有险,求小得。象曰:求小得,未出中也。\n六三:来之坎坎,险且枕,入于坎窞,勿用。象曰:来之坎坎,终无功也。\n六四:樽酒簋贰,用缶,纳约自牖,终无咎。象曰:樽酒簋贰,刚柔际也。\n九五:坎不盈,只既平,无咎。象曰:坎不盈,中未大也。\n上六:用徽绎,置于丛棘,三岁不得,凶。象曰:上六失道,凶三岁也。\n第三十卦 离 离为火 离上离下 ![[assets/pasted image 20231030144120.png]]\n离:利贞,亨。畜牝牛,吉。\n彖曰:离,丽也;日月丽乎天,百谷草木丽乎土,重明以丽乎正,乃化成天下。柔丽乎中正,故亨;是以畜牝牛吉也。\n象曰:明两作离,大人以继明照于四方。\n初九:履错然,敬之无咎。象曰:履错之敬, 以辟咎也。\n六二:黄离,元吉。象曰:黄离元吉,得中道也。\n九三:日昃之离,不鼓缶而歌,则大耋之嗟,凶。象曰:日昃之离,何可久也。\n九四:突如其来如,焚如,死如,弃如。象曰:突如其来如,无所容也。\n六五:出涕沱若,戚嗟若,吉。象曰:六五之吉,离王公也。\n上九:王用出征,有嘉折首,获其匪丑,无咎。象曰:王用出征,以正邦也。\n第三十一卦 咸 泽山咸 兑上艮下 ![[assets/pasted image 20231030144129.png]]\n咸:亨,利贞,取女吉。\n彖曰:咸,感也。柔上而刚下,二气感应以相与,止而说,男下女,是以亨利贞,取女吉也。天地感而万物化生,圣人感人心而天下和平;观其所感,而天地万物之情可见矣!\n象曰:山上有泽,咸;君子以虚受人。\n初六:咸其拇。象曰:咸其拇,志在外也。\n六二:咸其腓,凶,居吉。象曰:虽凶,居吉,顺不害也。\n九三:咸其股,执其随,往吝。象曰:咸其股,亦不处也。志在随人,所执下也。\n九四:贞吉悔亡,憧憧往来,朋从尔思。象曰:贞吉悔亡,未感害也。憧憧往来,未光大也。\n九五:咸其脢,无悔。象曰:咸其脢,志末也。\n上六:咸其辅,颊,舌。象曰:咸其辅,颊,舌,滕口说也。\n第三十二卦 恒 雷风恒 震上巽下 ![[assets/pasted image 20231030144146.png]]\n恒:亨,无咎,利贞,利有攸往。\n彖曰:恒,久也。刚上而柔下,雷风相与,巽而动,刚柔皆应,恒。恒亨无咎,利贞;久於其道也,天地之道,恒久而不已也。利有攸往,终则有始也。日月得天,而能久照,四 时变化,而能久成,圣人久於其道,而天下化成;观其所恒,而天地万物之情可见矣!\n象曰:雷风,恒;君子以立不易方。\n初六:浚恒,贞凶,无攸利。象曰:浚恒之凶,始 求深也。\n九二:悔亡。象曰:九二悔亡,能久中也。\n九三:不恒其德,或承之羞,贞吝。象曰:不恒其德,无所容也。\n九四:田无禽。象曰:久非其位,安得禽也。\n六五:恒其德,贞,妇人吉,夫子凶。象曰:妇人贞吉,从一而终也。夫子制义,从妇凶也。\n上六:振恒,凶。象曰:振恒在上,大无功也。\n第三十三卦 遁 天山遁 乾上艮下 ![[assets/pasted image 20231030144201.png]]\n遁:亨,小利贞。 彖曰:遁亨,遁而亨也。刚当位而应,与时行也。小利贞,浸而长也。遁之时义大矣哉!\n象曰:天下有山,遁;君子以远小人,不恶而严。\n初六:遁尾,厉,勿用有攸往。象曰:遁尾之厉,不往何灾也。\n六二:执之用黄牛之革,莫之胜说。象曰:执用黄牛,固志也。\n九三:系遁,有疾厉,畜臣妾吉。象曰:系遁之厉,有疾惫也。畜臣妾吉,不可大事也。\n九四:好□君子吉,小人否。象曰:君子好遁,小人否也。\n九五:嘉遁,贞吉。象曰:嘉遁贞吉,以正志也。\n上九:肥遁,无不利。象曰:肥遁,无不利;无所疑也。\n第三十四卦 大壮 雷天大壮 震上乾下 ![[assets/pasted image 20231030144213.png]]\n大壮:利贞。 彖曰:大壮,大者壮也。刚以动,故壮。大壮利贞;大者正也。正大而天地之情可见矣!\n象曰:雷在天上,大壮;君子以非礼勿履。\n初九:壮于趾,征凶,有孚。象曰:壮于 趾,其孚穷也。\n九二:贞吉。象曰:九二贞吉,以中也。\n九三:小人用壮,君子用罔,贞厉。羝羊触藩,羸其角。象曰:小人用壮,君子罔也。\n九四:贞吉悔亡,藩决不羸,壮于大舆之輹。象曰:藩决不羸,尚往也。\n六五:丧羊于易,无悔。象曰:丧羊于易,位不当也。\n上六:羝羊触藩,不能退,不能遂,无攸利,艰则吉。象曰:不能退,不能遂,不祥也。艰则吉,咎不长也。\n第三十五卦 晋 火地晋 离上坤下 ![[assets/pasted image 20231030144224.png]]\n晋:康侯用锡马蕃庶,昼日三接。\n彖曰:晋,进也。明出地上,顺而丽乎大明,柔进而上行。是以康侯用锡马蕃庶,昼日三接也。\n象曰:明出地上,晋;君子以自昭明德。\n初六:晋如,摧如,贞吉。罔孚,裕无咎。象 曰:晋如,摧如;独行正也。裕无咎;未受命也。\n六二:晋如,愁如,贞吉。受兹介福,于其王母。象曰:受之介福,以中正也。\n六三:众允,悔亡。象曰:众允之,志上行也。\n九四:晋如硕鼠,贞厉。象曰:硕鼠贞厉,位不当也。\n六五:悔亡,失得勿恤,往吉无不利。象曰:失得勿恤,往有庆也。\n上九:晋其角,维用伐邑,厉吉无咎,贞吝。象曰:维用伐邑,道未光也。\n第三十六卦 明夷 地火明夷 坤上离下 ![[assets/pasted image 20231030144240.png]]\n明夷:利艰贞。 彖曰:明入地中,明夷。内文明而外柔顺,以蒙大难,文王以之。利艰贞,晦其明也,内难而能正其志,箕子以之。\n象曰:明入地中,明夷;君子以莅众,用晦而明。\n初九:明夷于飞,垂其翼。君子于 行,三日不食,有攸往,主人有言。象曰:君子于行,义不食也。\n六二:明夷,夷于左股,用拯马壮,吉。象曰:六二之吉,顺以则也。\n九三:明夷于南狩,得其大首,不可疾贞。象曰:南狩之志,乃大得也。\n六四:入于左腹,获明夷之心,出于门庭。象曰:入于左腹,获心意也。\n六五:箕子之明夷,利贞。象曰:箕子之贞,明不可息也。\n上六:不明晦,初登于天,后入于地。象曰:初登于天,照四国也。后入于地,失则也。\n第三十七卦 家人 风火家人 巽上离下 ![[assets/pasted image 20231030144252.png]]\n家人:利女贞。\n彖曰:家人,女正位乎内,男正位乎外,男女正,天地之大义也。家人有严君焉,父母之谓也。父父,子子,兄兄,弟弟,夫夫,妇妇,而家道正;正家而天下定矣。\n象曰:风自火出,家人;君子以言有物,而行有恒。\n初九:闲有家,悔亡。象曰:闲有家,志未变也。\n六二:无攸遂,在中馈,贞吉。象曰:六二之吉,顺以巽也。\n九三:家人嗃嗃,悔厉吉;妇子嘻嘻,终吝。象曰:家人嗃嗃,未失也;妇子嘻嘻,失家节也。\n六四:富家,大吉。象曰:富家大吉,顺在位也。\n九五:王假有家,勿恤吉。象曰:王假有家,交相爱也。\n上九:有孚威如,终吉。象曰:威如之吉,反身之谓也。\n第三十八卦 睽 火泽睽 离上兑下 ![[assets/pasted image 20231030144311.png]]\n睽:小事吉。\n彖曰:睽,火动而上,泽动而下;二女同居,其志不同行;说而丽乎明,柔进而上行,得中而应乎刚;是以小事吉。天地睽,而其事同也;男女睽,而其志通也;万物睽,而其事 类也;睽之时用大矣哉!\n象曰:上火下泽,睽;君子以同而异。\n初九:悔亡,丧马勿逐,自复;见恶人无咎。象 曰:见恶人,以辟咎也。\n九二:遇主于巷,无咎。象曰:遇主于巷,未失道也。\n六三:见舆曳,其牛掣,其人天且劓,无初有终。象曰:见舆曳,位不当也。无初有终,遇刚也。\n九四:睽孤,遇元夫,交孚,厉无咎。象曰:交孚无咎,志行也。\n六五:悔亡,厥宗噬肤,往何咎。象曰:厥宗噬肤,往有庆也。\n上九:睽孤,见豕负涂,载鬼一车,先张之弧,后说之弧,匪寇婚媾,往遇雨则吉。象曰:遇雨之吉,群疑亡也。\n第三十九卦 蹇 水山蹇 坎上艮下 ![[assets/pasted image 20231030144326.png]]\n蹇:利西南,不利东北;利见大人,贞吉。\n彖曰:蹇,难也,险在前也。见险而能止,知矣哉!蹇利西南,往得中也;不利东北,其道穷也。利见大人,往有功也。当位贞吉,以正邦也。蹇之时用大矣哉!\n象曰:山上有水,蹇;君子以反身修德。\n初六:往蹇,来誉。象曰:往蹇来誉,宜待也。\n六二:王臣蹇蹇,匪躬之故。象曰:王臣蹇蹇,终无尤也。\n九三:往蹇来反。象曰:往蹇来反,内喜之也。\n六四:往蹇来连。象曰:往蹇来连,当位实也。\n九五:大蹇朋来。象曰:大蹇朋来,以中节也。\n上六:往蹇来硕,吉;利见大人。象曰:往蹇来硕,志在内也。利见大人,以从贵也。\n第四十卦 解 雷水解 震上坎下 ![[assets/pasted image 20231030144339.png]]\n解:利西南,无所往,其来复吉。有攸往,夙吉。\n彖曰:解,险以动,动而免乎险,解。解利西南,往得众也。其来复吉,乃得中也。有攸往夙吉,往有功也。天地解,而雷雨作,雷雨作,而百果草木皆甲坼,解之时义大矣哉!\n象曰:雷雨作,解;君子以赦过宥罪。\n初六:无咎。象曰:刚柔之际,义无咎也。\n九二:田获三狐,得黄矢,贞吉。象曰:九二贞吉,得中道也。\n六三:负且乘,致寇至,贞吝。象曰:负且乘,亦可丑也,自我致戎,又谁咎也。\n九四:解而拇,朋至斯孚。象曰:解而拇,未当位也。\n六五:君子维有解,吉;有孚于小人。象曰:君子有解,小人退也。\n上六:公用射隼,于高墉之上,获之,无不利。象曰:公用射隼,以解悖也。\n第四十一卦 损 山泽损 艮上兑下 ![[assets/pasted image 20231030144406.png]]\n损:有孚,元吉,无咎,可贞,利有攸往?曷之用,二簋可用享。\n彖曰:损,损下益上,其道上行。损而有孚,元吉,无咎,可贞,利有攸往。曷之用?二簋可用享;二簋应有时。损刚益柔有时,损益盈虚,与时偕行。\n象曰:山下有泽,损;君子以惩忿窒欲。\n初九:已事遄往,无咎,酌损之。象曰:已事 遄往,尚合志也。\n九二:利贞,征凶,弗损益之。象曰:九二利贞,中以为志也。\n六三:三人行,则损一人;一人行,则得其友。象曰:一人行,三则疑也。\n六四:损其疾,使遄有喜,无咎。象曰:损其疾,亦可喜也。\n六五:或益之,十朋之龟弗克违,元吉。象曰:六五元吉,自上佑也。\n上九:弗损益之,无咎,贞吉,利有攸往,得臣无家。象曰:弗损益之,大得志也。\n第四十二卦 益 风雷益 巽上震下 ![[assets/pasted image 20231030144416.png]]\n益:利有攸往,利涉大川。\n彖曰:益,损上益下,民说无疆,自上下下,其道大光。利有攸往,中正有庆。利涉大川,木道乃行。益动而巽,日进无疆。天施地生,其益无方。凡益之道,与时偕行。\n象曰:风雷,益;君子以见善则迁,有过则改。\n初九:利用为大作,元吉,无咎。象 曰:元吉无咎,下不厚事也。\n六二:或益之,十朋之龟弗克违,永贞吉。王用享于帝,吉。象曰:或益之,自外来也。\n六三:益之用凶事,无咎。有孚中行,告公用圭。象曰:益用凶事,固有之也。\n六四:中行,告公从。利用为依迁国。象曰:告公从,以益志也。\n九五:有孚惠心,勿问元吉。有孚惠我德。象曰:有孚惠心,勿问之矣。惠我德,大得志也。\n上九:莫益之,或击之,立心勿恒,凶。象曰:莫益之,偏辞也。或击之,自外来也。\n第四十三卦 夬 泽天夬 兑上乾下 ![[assets/pasted image 20231030144428.png]]\n夬:扬于王庭,孚号,有厉,告自邑,不利即戎,利有攸往。\n彖曰:夬,决也,刚决柔也。健而说,决而和,扬于王庭,柔乘五刚也。孚号有厉,其危乃光也。告自邑,不利即戎,所尚乃穷也。利有攸往,刚长乃终也。\n象曰:泽上于天,夬;君子以施禄及下,居德则忌。\n初九:壮于前趾,往不胜为吝。 象曰:不胜而往,咎也。\n九二:惕号,莫夜有戎,勿恤。象曰:莫夜有戎,得中道也。\n九三:壮于,有凶。君子夬夬,独行遇雨,若濡有愠,无咎。象曰:君子夬夬,终无咎也。\n九四:臀无肤,其行次且。牵羊悔亡,闻言不信。象曰:其行次且,位不当也。闻言不信,聪不明也。\n九五:苋陆夬夬,中行无咎。象曰:中行无咎,中未光也。\n上六:无号,终有凶。象曰:无号之凶,终不可长也。\n第四十四卦 姤 天风姤 乾上巽下 ![[assets/pasted image 20231030144447.png]]\n姤:女壮,勿用取女。\n彖曰:姤,遇也,柔遇刚也。勿用取女,不可与长也。天地相遇,品物咸章也。刚遇中正,天下大行也。姤之时义大矣哉!\n象曰:天下有风,姤;后以施命诰四方。\n初六:系于金柅,贞吉,有攸往,见凶, 羸豕踟躅。象曰:系于金柅,柔道牵也。\n九二:包有鱼,无咎,不利宾。象曰:包有鱼,义不及宾也。\n九三:臀无肤,其行次且,厉,无大咎。象曰:其行次且,行未牵也。\n九四:包无鱼,起凶。象曰:无鱼之凶,远民也。\n九五:以杞包瓜,含章,有陨自天。象曰:九五含章,中正也。有陨自天,志不舍命也。\n上九:姤其角,吝,无咎。象曰:姤其角,上穷吝也。\n第四十五卦 萃 泽地萃 兑上坤下 ![[assets/pasted image 20231030144508.png]]\n萃:亨。王假有庙,利见大人,亨,利贞。用大牲吉,利有攸往。\n彖曰:萃,聚也;顺以说,刚中而应,故聚也。王假有庙,致孝享也。利见大人亨,聚以正也。用大牲吉,利有攸往,顺天命也。观其所聚,而天地万物之情可见矣。\n象曰:泽上於地,萃;君子以除戎器,戒不虞。\n初六:有孚不终,乃乱乃萃,若号一握 为笑,勿恤,往无咎。象曰:乃乱乃萃,其志乱也。\n六二:引吉,无咎,孚乃利用禴。象曰:引吉无咎,中未变也。\n六三:萃如,嗟如,无攸利,往无咎,小吝。象曰:往无咎,上巽也。\n九四:大吉,无咎。象曰:大吉无咎,位不当也。\n九五:萃有位,无咎。匪孚,元永贞,悔亡。象曰:萃有位,志未光也。\n上六:赍咨涕洟,无咎。象曰:赍咨涕洟,未安上也。\n第四十六卦 升 地风升 坤上巽下 ![[assets/pasted image 20231030144519.png]]\n升:元亨,用见大人,勿恤,南征吉。\n彖曰:柔以时升,巽而顺,刚中而应,是以大亨。用见大人,勿恤;有庆也。南征吉,志行也。\n象曰:地中生木,升;君子以顺德,积小以高大。\n初六:允升,大吉。象曰:允升大 吉,上合志也。\n九二:孚乃利用禴,无咎。象曰:九二之孚,有喜也。\n九三:升虚邑。象曰:升虚邑,无所疑也。\n六四:王用亨于岐山,吉无咎。象曰:王用亨于岐山,顺事也。\n六五:贞吉,升阶。象曰:贞吉升阶,大得志也。\n上六:冥升,利于不息之贞。象曰:冥升在上,消不富也。\n第四十七卦 困 泽水困 兑上坎下 ![[assets/pasted image 20231030144551.png]]\n困:亨,贞,大人吉,无咎,有言不信。\n彖曰:困,刚掩也。险以说,困而不失其所,亨;其唯君子乎?贞大人吉,以刚中也。有言不信,尚口乃穷也。\n象曰:泽无水,困;君子以致命遂志。\n初六:臀困于株木,入于幽谷,三岁不见。象 曰:入于幽谷,幽不明也。\n九二:困于酒食,朱绂方来,利用亨祀,征凶,无咎。象曰:困于酒食,中有庆也。\n六三:困于石,据于蒺藜,入于其宫,不见其妻,凶。象曰:据于蒺藜,乘刚也。入于其宫,不见其妻,不祥也。\n九四:来徐徐,困于金车,吝,有终。象曰:来徐徐,志在下也。虽不当位,有与也。\n九五:劓刖,困于赤绂,乃徐有说,利用祭祀。象曰:劓刖,志未得也。乃徐有说,以中直也。利用祭祀,受福也。\n上六:困于葛藟,于臲卼,曰动悔。有悔,征吉。象曰:困于葛藟,未当也。动悔,有悔吉,行也。\n第四十八卦 井 水风井 坎上巽下 ![[assets/pasted image 20231030144604.png]]\n井:改邑不改井,无丧无得,往来井井。汔至,亦未繘井,羸其瓶,凶。\n彖曰:巽乎水而上水,井;井养而不穷也。改邑不改井,乃以刚中也。汔至亦未繘井,未有功也。羸其瓶,是以凶也。\n象曰:木上有水,井;君子以劳民劝相。\n初六:井泥不食,旧井无禽。象曰:井泥不 食,下也。旧井无禽,时舍也。\n九二:井谷射鲋,瓮敝漏。象曰:井谷射鲋,无与也。\n九三:井渫不食,为我民恻,可用汲,王明,并受其福。象曰:井渫不食,行恻也。求王明,受福也。\n六四:井甃,无咎。象曰:井甃无咎,修井也。\n九五:井冽,寒泉食。象曰:寒泉之食,中正也。\n上六:井收勿幕,有孚无吉。象曰:元吉在上,大成也。\n第四十九卦 革 泽火革 兑上离下 ![[assets/pasted image 20231030144640.png]]\n革:己日乃孚,元亨利贞,悔亡。\n彖曰:革,水火相息,二女同居,其志不相得,曰革。己日乃孚;革而信也。文明以说,大亨以正,革而当,其悔乃亡。天地革而四时成,汤武革命,顺乎天而应乎人,革之时 义大矣哉! 象曰:泽中有火,革;君子以治历明时。初九:巩用黄牛之革。\n象曰:巩用黄牛,不可 以有为也。\n六二:己日乃革之,征吉,无咎。象曰:己日革之,行有嘉也。\n九三:征凶,贞厉,革言三就,有孚。象曰:革言三就,又何之矣。\n九四:悔亡,有孚改命,吉。象曰:改命之吉,信志也。\n九五:大人虎变,未占有孚。象曰:大人虎变,其文炳也。\n上六:君子豹变,小人革面,征凶,居贞吉。象曰:君子豹变,其文蔚也。小人革面,顺以从君也。\n第五十卦 鼎 火风鼎 离上巽下 ![[assets/pasted image 20231030144630.png]]\n鼎:元吉,亨。\n彖曰:鼎,象也。以木巽火,亨饪也。圣人亨以享上帝,而大亨以养圣贤。巽而耳目聪明,柔进而上行,得中而应乎刚,是以元亨。\n象曰:木上有火,鼎;君子以正位凝命。\n初六:鼎颠趾,利出否,得妾以其子,无咎。 象曰:鼎颠趾,未悖也。利出否,以从贵也。\n九二:鼎有实,我仇有疾,不我能即,吉。象曰:鼎有实,慎所之也。我仇有疾,终无尤也。\n九三:鼎耳革,其行塞,雉膏不食,方雨亏悔,终吉。象曰:鼎耳革,失其义也。\n九四:鼎折足,覆公□,其形渥,凶。象曰:覆公束,信如何也。\n六五:鼎黄耳金铉,利贞。象曰:鼎黄耳,中以为实也。\n上九:鼎玉铉,大吉,无不利。象曰:玉铉在上,刚柔节也。\n第五十一卦 震 震为雷 震上震下 ![[assets/pasted image 20231030144701.png]]\n震:亨。震来虩虩,笑言哑哑。震惊百里,不丧匕鬯。\n彖曰:震,亨。震来虩虩,恐致福也。笑言哑哑,后有则也。震惊百里,惊远而惧迩也。出可以守宗庙社稷,以为祭主也。\n象曰:洊雷,震;君子以恐惧修身。\n初九:震来虩虩,后笑言哑哑,吉。象曰: 震来虩虩,恐致福也。笑言哑哑,后有则也。\n六二:震来厉,亿丧贝,跻于九陵,勿逐,七日得。象曰:震来厉,乘刚也。\n六三:震苏苏,震行无眚。象曰:震苏苏,位不当也。\n九四:震遂泥。象曰:震遂泥,未光也。\n六五:震往来厉,亿无丧,有事。象曰:震往来厉,危行也。其事在中,大无丧也。\n上六:震索索,视矍矍,征凶。震不于其躬,于其邻,无咎。婚媾有言。象曰:震索索,未得中也。虽凶无咎,畏邻戒也。\n第五十二卦 艮 艮为山 艮上艮下 ![[assets/pasted image 20231030144758.png]]\n艮:艮其背,不获其身,行其庭,不见其人,无咎。\n彖曰:艮,止也。时止则止,时行则行,动静不失其时,其道光明。艮其止,止其所也。上下敌应,不相与也。是以不获其身,行其庭不见其人,无咎也。\n象曰:兼山,艮;君子以思不出其位。\n初六:艮其趾,无咎,利永贞。象曰:艮其趾, 未失正也。\n六二:艮其腓,不拯其随,其心不快。象曰:不拯其随,未退听也。\n九三:艮其限,列其夤,厉薰心。象曰:艮其限,危薰心也。\n六四:艮其身,无咎。象曰:艮其身,止诸躬也。\n六五:艮其辅,言有序,悔亡。象曰:艮其辅,以中正也。\n上九:敦艮,吉。象曰:敦艮之吉,以厚终也。\n第五十三卦 渐 风山渐 巽上艮下 ![[assets/pasted image 20231030144807.png]]\n渐:女归吉,利贞。\n彖曰:渐之进也,女归吉也。进得位,往有功也。进以正,可以正邦也。其位刚,得中也。止而巽,动不穷也。\n象曰:山上有木,渐;君子以居贤德,善俗。\n初六:鸿渐于干,小子厉,有言,无咎。 象曰:小子之厉,义无咎也。\n六二:鸿渐于磐,饮食衍衍,吉。象曰:饮食衍衍,不素饱也。\n九三:鸿渐于陆,夫征不复,妇孕不育,凶;利御寇。象曰:夫征不复,离群丑也。妇孕不育,失其道也。利用御寇,顺相保也。\n六四:鸿渐于木,或得其桷,无咎。象曰:或得其桷,顺以巽也。\n九五:鸿渐于陵,妇三岁不孕,终莫之胜,吉。象曰:终莫之胜,吉;得所愿也。\n上九:鸿渐于逵,其羽可用为仪,吉。象曰:其羽可用为仪,吉;不可乱也。\n第五十四卦 归妹 雷泽归妹 震上兑下 ![[assets/pasted image 20231030144827.png]]\n归妹:征凶,无攸利。\n彖曰:归妹,天地之大义也。天地不交,而万物不兴,归妹人之终始也。说以动,所归妹也。征凶,位不当也。无攸利,柔乘刚也。\n象曰:泽上有雷,归妹;君子以永终知敝。\n初九:归妹以娣,跛能履,征吉。象曰:归 妹以娣,以恒也。跛能履吉,相承也。\n九二:眇能视,利幽人之贞。象曰:利幽人之贞,未变常也。\n六三:归妹以须,反归以娣。象曰:归妹以须,未当也。\n九四:归妹愆期,迟归有时。象曰:愆期之志,有待而行也。\n六五:帝乙归妹,其君之袂,不如其娣之袂良,月几望,吉。象曰:帝乙归妹,不如其娣之袂良也。其位在中,以贵行也。\n上六:女承筐无实,士刲羊无血,无攸利。象曰:上六无实,承虚筐也。\n第五十五卦 丰 雷火丰 震上离下 ![[assets/pasted image 20231030144849.png]]\n丰:亨,王假之,勿忧,宜日中。\n彖曰:丰,大也。明以动,故丰。王假之,尚大也。勿忧宜日中,宜照天下也。日中则昃,月盈则食,天地盈虚,与时消息,而况人於人乎?况於鬼神乎?\n象曰:雷电皆至,丰;君子以折狱致刑。\n初九:遇其配主,虽旬无咎,往有尚。象曰: 虽旬无咎,过旬灾也。\n六二:丰其蔀,日中见斗,往得疑疾,有孚发若,吉。象曰:有孚发若,信以发志也。\n九三:丰其沛,日中见昧,折其右肱,无咎。象曰:丰其沛,不可大事也。折其右肱,终不可用也。\n九四:丰其蔀,日中见斗,遇其夷主,吉。象曰:丰其蔀,位不当也。日中见斗,幽不明也。遇其夷主,吉;行也。\n六五:来章,有庆誉,吉。象曰:六五之吉,有庆也。\n上六:丰其屋,蔀其家,窥其户,阒其无人,三岁不见,凶。象曰:丰其屋,天际翔也。窥其户,阒其无人,自藏也。\n第五十六卦 旅 火山旅 离上艮下 ![[assets/pasted image 20231030144914.png]]\n旅:小亨,旅贞吉。\n彖曰:旅,小亨,柔得中乎外,而顺乎刚,止而丽乎明,是以小亨,旅贞吉也。旅之时义大矣哉!\n象曰:山上有火,旅;君子以明慎用刑,而不留狱。\n初六:旅琐琐,斯其所取灾。象 曰:旅琐琐,志穷灾也。\n六二:旅即次,怀其资,得童仆贞。象曰:得童仆贞,终无尤也。\n九三:旅焚其次,丧其童仆,贞厉。象曰:旅焚其次,亦以伤矣。以旅与下,其义丧也。\n九四:旅于处,得其资斧,我心不快。象曰:旅于处,未得位也。得其资斧,心未快也。\n六五:射雉一矢亡,终以誉命。象曰:终以誉命,上逮也。\n上九:鸟焚其巢,旅人先笑后号啕。丧牛于易,凶。象曰:以旅在上,其义焚也。丧牛于易,终莫之闻也。\n第五十七卦 巽 巽为风 巽上巽下 ![[assets/pasted image 20231030144926.png]]\n巽:小亨,利攸往,利见大人。\n彖曰:重巽以申命,刚巽乎中正而志行。柔皆顺乎刚,是以小亨,利有攸往,利见大人。\n象曰:随风,巽;君子以申命行事。\n初六:进退,利武人之贞。象曰:进退,志疑也。 利武人之贞,志治也。\n九二:巽在床下。用史巫纷若,吉无咎。象曰:纷若之吉,得中也。\n九三:频巽,吝。象曰:频巽之吝,志穷也。\n六四:悔亡,田获三品。象曰:田获三品,有功也。\n九五:贞吉悔亡,无不利。无初有终,先庚三日,后庚三日,吉。象曰:九五之吉,位正中也。\n上九:巽在床下,丧其资斧,贞凶。象曰:巽在□下,上穷也。丧其资斧,正乎凶也。\n第五十八卦 兑 兑为泽 兑上兑下 ![[assets/pasted image 20231030144936.png]]\n兑:亨,利贞。\n彖曰:兑,说也。刚中而柔外,说以利贞,是以顺乎天,而应乎人。说以先民,民忘其劳;说以犯难,民忘其死;说之大,民劝矣哉!\n象曰:丽泽,兑;君子以朋友讲习。\n初九:和兑,吉。象曰:和兑之吉,行未疑也。\n九二:孚兑,吉,悔亡。象曰:孚兑之吉,信志也。\n六三:来兑,凶。象曰:来兑之凶,位不当也。\n九四:商兑,未宁,介疾有喜。象曰:九四之喜,有庆也。\n九五:孚于剥,有厉。象曰:孚于剥,位正当也。\n上六:引兑。象曰:上六引兑,未光也。\n第五十九卦 涣 风水涣 巽上坎下 ![[assets/pasted image 20231030144945.png]]\n涣:亨。王假有庙,利涉大川,利贞。\n彖曰:涣,亨。刚来而不穷,柔得位乎外而上同。王假有庙,王乃在中也。利涉大川,乘木有功也。\n象曰:风行水上,涣;先王以享于帝立庙。\n初六:用拯马壮,吉。象曰:初六之吉,顺 也。\n九二:涣奔其机,悔亡。象曰:涣奔其机,得愿也。\n六三:涣其躬,无悔。象曰:涣其躬,志在外也。\n六四:涣其群,元吉。涣有丘,匪夷所思。象曰:涣其群,元吉;光大也。\n九五:涣汗其大号,涣王居,无咎。象曰:王居无咎,正位也。\n上九:涣其血,去逖出,无咎。象曰:涣其血,远害也。\n第六十卦 节 水泽节 坎上兑下 ![[assets/pasted image 20231030144956.png]]\n节:亨。苦节不可贞。\n彖曰:节,亨,刚柔分,而刚得中。苦节不可贞,其道穷也。说以行险,当位以节,中正以通。天地节而四时成,节以制度,不伤财,不害民。\n象曰:泽上有水,节;君子以制数度,议德行。\n初九:不出户庭,无咎。象曰:不出户 庭,知通塞也。\n九二:不出门庭,凶。象曰:不出门庭,失时极也。\n六三:不节若,则嗟若,无咎。象曰:不节之嗟,又谁咎也。\n六四:安节,亨。象曰:安节之亨,承上道也。\n九五:甘节,吉;往有尚。象曰:甘节之吉,居位中也。\n上六:苦节,贞凶,悔亡。象曰:苦节贞凶,其道穷也。\n第六十一卦 中孚 风泽中孚 巽上兑下 ![[assets/pasted image 20231030145005.png]]\n中孚:豚鱼吉,利涉大川,利贞。\n彖曰:中孚,柔在内而刚得中。说而巽,孚,乃化邦也。豚鱼吉,信及豚鱼也。利涉大川,乘木舟虚也。中孚以利贞,乃应乎天也。\n象曰:泽上有风,中孚;君子以议狱缓死。\n初九:虞吉,有他不燕。象曰:初九虞吉, 志未变也。\n九二:鸣鹤在阴,其子和之,我有好爵,吾与尔靡之。象曰:其子和之,中心愿也。\n六三:得敌,或鼓或罢,或泣或歌。象曰:可鼓或罢,位不当也。\n六四:月几望,马匹亡,无咎。象曰:马匹亡,绝类上也。\n九五:有孚挛如,无咎。象曰:有孚挛如,位正当也。\n上九:翰音登于天,贞凶。象曰:翰音登于天,何可长也。\n第六十二卦 小过 雷山小过 震上艮下 ![[assets/pasted image 20231030145033.png]]\n小过:亨,利贞,可小事,不可大事。飞鸟遗之音,不宜上宜下,大吉。\n彖曰:小过,小者过而亨也。过以利贞,与时行也。柔得中,是以小事吉也。刚失位而不中,是以不可大事也。有飞鸟之象焉,有飞鸟遗之音,不宜上宜下,大吉;上逆而下顺 也。\n象曰:山上有雷,小过;君子以行过乎恭,丧过乎哀,用过乎俭。\n初六:飞鸟以凶。象 曰:飞鸟以凶,不可如何也。\n六二:过其祖,遇其妣;不及其君,遇其臣;无咎。象曰:不及其君,臣不可过也。\n九三:弗过防之,从或戕之,凶。象曰:从或戕之,凶如何也。\n九四:无咎,弗过遇之。往厉必戒,勿用永贞。象曰:弗过遇之,位不当也。往厉必戒,终不可长也。\n六五:密云不雨,自我西郊,公弋取彼在穴。象曰:密云不雨,已上也。\n上六:弗遇过之,飞鸟离之,凶,是谓灾眚。象曰:弗遇过之,已亢也。\n第六十三卦 既济 水火既济 坎上离下 ![[assets/pasted image 20231030145043.png]]\n既济:亨,小利贞,初吉终乱。\n彖曰:既济,亨,小者亨也。利贞,刚柔正而位当也。初吉,柔得中也。终止则乱,其道穷也。\n象曰:水在火上,既济;君子以思患而预防之。\n初九:曳其轮,濡其尾,无咎。象曰: 曳其轮,义无咎也。\n六二:妇丧其茀,勿逐,七日得。象曰:七日得,以中道也。\n九三:高宗伐鬼方,三年克之,小人勿用。象曰:三年克之,惫也。\n六四:(纟需)有衣袽,终日戒。象曰:终日戒,有所疑也。\n九五:东邻杀牛,不如西邻之禴祭,实受其福。象曰:东邻杀牛,不如西邻之时也;实受其福,吉大来也。\n上六:濡其首,厉。象曰:濡其首厉,何可久也。\n第六十四卦 未济 火水未济 离上坎下 ![[assets/pasted image 20231030145103.png]]\n未济:亨,小狐汔济,濡其尾,无攸利。\n彖曰:未济,亨;柔得中也。小狐汔济,未出中也。濡其尾,无攸利;不续终也。虽不当位,刚柔应也。\n象曰:火在水上,未济;君子以慎辨物居方。\n初六:濡其尾,吝。象曰:濡其尾,亦不 知极也。\n九二:曳其轮,贞吉。象曰:九二贞吉,中以行正也。\n六三:未济,征凶,利涉大川。象曰:未济征凶,位不当也。\n九四:贞吉,悔亡,震用伐鬼方,三年有赏于大国。象曰:贞吉悔亡,志行也。\n六五:贞吉,无悔,君子之光,有孚,吉。象曰:君子之光,其晖吉也。\n上九:有孚于饮酒,无咎,濡其首,有孚失是。象曰:饮酒濡首,亦不知节也。\n附录 系辞 上篇\n1\n天尊地卑,乾坤定矣。卑高以陈,贵贱位矣。动静有常,刚柔断矣。方以类聚,物以群分,吉凶生矣。在天成象,在地成形,变化见矣。\n是故,刚柔相摩,八卦相荡。鼓之以雷霆,润之以风雨,日月运行,一寒一暑,乾道成男,坤道成女。乾知大始,坤作成物。乾以易知,坤以简能。\n易则易知,简则易从。易知则有亲,易从则有功。有亲则可久,有功则可大。可久则贤人之德,可大则贤人之业。易简,而天下之理得矣;天下之理得,而成位乎其中矣。\n2\n圣人设卦观象,系辞焉而明吉凶,刚柔相推而生变化。\n是故,吉凶者,失得之象也。悔吝者,懮虞之象也。变化者,进退之象也。刚柔者,昼夜之象也。六爻之动,三极之道也。是故,君子所居而安者,易之序也。所乐而玩者,爻之辞也。\n是故,君子居则观其象,而玩其辞;动则观其变,而玩其占。是以自天佑之,吉无不利。\n3\n彖者,言乎象者也。爻者,言乎变者也。吉凶者,言乎其失得也。悔吝者,言乎其小疵也。无咎者,善补过也。\n是故,列贵贱者存乎位。齐小大者,存乎卦。辩吉凶者,存乎辞。懮悔吝者,存乎介。震无咎者,存乎悔。是故,卦有小大,辞有险易。辞也者,各指其所之。\n4\n易与天地准,故能弥纶天地之道。仰以观于天文,俯以察于地理,是故知幽明之故。原始反终,故知死生之说。精气为物,游魂为变,是故知鬼神之情状。与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不懮。安土敦乎仁,故能爱。范围天地之化而不过,曲成万物而不遗,通乎昼夜之道而知,故神无方而易无体。\n5\n一阴一阳之谓道,继之者善也,成之者性也。仁者见之谓之仁,知者见之谓之知。百姓日用而不知,故君子之道鲜矣。显诸仁,藏诸用,鼓万物而不与圣人同懮,盛德大业至矣哉。富有之谓大业,日新之谓盛德。生生之谓易,成象之谓乾,效法之为坤,极数知来之谓占,通变之谓事,阴阳不测之谓神。\n6\n夫易,广矣大矣,以言乎远,则不御;以言乎迩,则静而正;以言乎天地之间,则备矣。夫乾,其静也专,其动也直,是以大生焉。夫坤,其静也翕,其动也辟,是以广生焉。广大配天地,变通配四时,阴阳之义配日月,易简之善配至德。\n7\n子曰:“易其至矣乎!”,夫易,圣人所以崇德而广业也。知崇礼卑,崇效天,卑法地。天地设位,而易行乎其中矣,成性存存,道义之门。\n8\n圣人有以见天下之赜,而拟诸其形容,象其物宜,是故谓之象。圣人有以见天下之动,而观其会通,以行其典礼。系辞焉,以断其吉凶,是故谓之爻。言天下之至赜,而不可恶也。言天下之至动,而不可乱也。拟之而后言,议之而后动,拟议以成其变化。“鸣鹤在阴,其子和之,我有好爵,吾与尔靡之。”\n子曰:“君子居其室,出其言,善则千里之外应之,况其迩者乎,居其室,出其言不善,则千里之外违之,况其迩者乎,言出乎身,加乎民,行发乎迩,见乎远。言行君子之枢机,枢机之发,荣辱之主也。言行,君子之所以动天地也,可不慎乎。”\n“同人,先号啕而后笑。”子曰:“君子之道,或出或处,或默或语,二人同心,其利断金。同心之言,其臭如兰。”\n“初六,藉用白茅,无咎。”子曰:“苟错诸地而可矣。藉之用茅,何咎之有?慎之至也。夫茅之为物薄,而用可重也。慎斯术也以往,其无所失矣。”\n“劳谦君子,有终吉。”子曰:“劳而不伐,有功而不德,厚之至也,语以其功下人者也。德言盛,礼言恭,谦也者,致恭以存其位者也。”\n“亢龙有悔”,子曰:“贵而无位,高而无民,贤人在下位而无辅,是以动而有悔也。”\n“不出户庭,无咎。”子曰:“乱之所生也,则言语以为阶。君不密,则失臣;臣不密,则失身;几事不密,则害成。是以君子慎密而不出也。”\n子曰:“作易者其知盗乎?易曰:负且乘,致寇至。负也者,小人之事也。乘也者,君子之器也。小人而乘君子之器,盗思夺之矣!上慢下暴,盗思伐之矣!慢藏诲盗,冶容诲淫,易曰:“负且乘,致寇至,盗之招也。”\n9\n天一地二,天三地四,天五地六,天七地八,天九地十。天数五,地数五,五位相得而各有合。天数二十有五,地数三十,凡天地之数,五十有五,此所以成变化,而行鬼神也。大衍之数五十,其用四十有九。分而为二以象两,挂一以象三,揲之以四以象四时,归奇于扐以象闰。五岁再闰,故再扐而后挂。\n乾之策,二百一十有六;坤之策,百四十有四,凡三百有六十,当期之日。二篇之策,万有一千五百二十,当万物之数也。是故,四营而成易,十有八变而成卦。八卦而小成,引而伸之,触类而长之,天下之能事毕矣。显道神德行,是故可与酬酢,可与佑神矣。子曰:“知变化之道者,其知神之所为乎。”\n10\n易有圣人之道四焉;以言者尚其辞,以动者尚其变,以制器者尚其象,以卜筮者尚其占。以君子将有为也,将有行也,问焉而以言,其受命也如响,无有远近幽深,遂知来物。非天下之至精,其孰能与于此。\n参伍以变,错综其数,通其变,遂成天下之文。极其数,遂定天下之象。非天下之至变,其孰能与于此。易无思也,无为也,寂然不动,感而遂通天下之故。非天下之至神,其孰能与于此。\n夫易,圣人之所以极深而研几也。唯深也,故能通天下之志。唯几也,故能成天下之务。唯神也,故不疾而速,不行而至。子曰:“易有圣人之道四焉”者,此之谓也。\n11\n子曰:“夫易,何为者也?夫易开物成务,冒天下之道,如斯而已者也。是故,圣人以通天下之志,以定天下之业,以断天下之疑。”是故,蓍之德,圆而神;卦之德,方以知;六爻之义,易以贡。圣人以此洗心,退藏于密,吉凶与民同患。神以知来,知以藏往,其孰能与此哉!古之聪明睿知神武而不杀者夫?\n是以,明于天之道,而察于民之故,是兴神物以前民用。圣人以此齐戒,以神明其德夫!是故,阖户谓之坤;辟户谓之乾;一阖一辟谓之变;往来不穷谓之通;见乃谓之象;形乃谓之器;制而用之,谓之法;利用出入,民咸用之,谓之神。\n是故,易有太极,是生两仪,两仪生四象,四象生八卦,八卦定吉凶,吉凶生大业。是故,法象莫大乎天地,变通莫大乎四时,县象著明莫大乎日月,崇高莫大乎富贵;备物致用,立成器以为天下利,莫大乎圣人;探赜索隐,钩深致远,以定天下之吉凶,成天下之亹亹者,莫大乎蓍龟。\n是故,天生神物,圣人则之;天地变化,圣人效之;天垂象,见吉凶,圣人象之。河出图,洛出书,圣人则之。易有四象,所以示也。系辞焉,所以告也。定之以吉凶,所以断也。\n12\n易曰:“自天佑之,吉无不利。”子曰:“佑者,助也。天之所助者,顺也;人之所助者,信也。履信思乎顺,又以尚贤也。是以自天佑之,吉无不利也。”\n子曰:“书不尽言,言不尽意。然则圣人之意,其不可见乎。”子曰:“圣人立象以尽意,设卦以尽情伪,系辞以尽其言,变而通之以尽利,鼓之舞之以尽神。”\n乾坤其易之縕邪?乾坤成列,而易立乎其中矣。乾坤毁,则无以见易,易不可见,则乾坤或几乎息矣。是故,形而上者谓之道,形而下者谓之器。化而裁之谓之变,推而行之谓之通,举而错之天下之民,谓之事业。\n是故,夫象,圣人有以见天下之赜,而拟诸其形容,象其物宜,是故谓之象。圣人有以见天下之动,而观其会通,以行其典礼,系辞焉,以断其吉凶,是故谓之爻。极天下之赜者,存乎卦;鼓天下之动者,存乎辞;化而裁之,存乎变;推而行之,存乎通;神而明之,存乎其人;默而成之,不言而信,存乎德行。\n下篇\n1\n八卦成列,象在其中矣。因而重之,爻在其中矣。刚柔相推,变在其中矣。系辞焉而命之,动在其中矣。\n吉凶悔吝者,生乎动者也。刚柔者,立本者也。变通者,趣时者也。\n吉凶者,贞胜者也。天地之道,贞观者也。日月之道,贞明者也,天下之动,贞夫一者也。\n夫乾,确然示人易矣。夫坤,隤然示人简矣。爻也者,效此者也。象也者,像此者也。\n爻象动乎内,吉凶见乎外,功业见乎变,圣人之情见乎辞。\n天地之大德曰生,圣人之大宝曰位。何以守位曰仁,何以聚人曰财。理财正辞,禁民为非曰义。\n2\n古者包牺氏之王天下也,仰则观象于天,俯则观法于地,观鸟兽之文,与地之宜,近取诸身,远取诸物,于是始作八卦,以通神明之德,以类万物之情。\n作结绳而为罔罟,以佃以渔,盖取诸离。\n包牺氏没,神农氏作,斫木为耜,揉木为耒,耒耨之利,以教天下,盖取诸益。\n日中为市,致天下之民,聚天下之货,交易而退,各得其所,盖取诸噬嗑。\n神农氏没,黄帝、尧、舜氏作,通其变,使民不倦,神而化之,使民宜之。易穷则变,变则通,通则久。是以自天佑之,吉无不利,黄帝、尧、舜垂衣裳而天下治,盖取诸乾坤。\n刳木为舟,剡木为楫,舟楫之利,以济不通,致远以利天下,盖取诸涣。\n服牛乘马,引重致远,以利天下,盖取诸随。\n重门击柝,以待暴客,盖取诸豫。\n断木为杵,掘地为臼,臼杵之利,万民以济,盖取诸小过。\n弦木为弧,剡木为矢,弧矢之利,以威天下,盖取诸睽。\n上古穴居而野处,后世圣人易之以宫室,上栋下宇,以待风雨,盖取诸大壮。\n古之葬者,厚衣之以薪,葬之中野,不封不树,丧期无数。后世圣人易之以棺椁,盖取诸大过。\n上古结绳而治,后世圣人易之以书契,百官以治,万民以察,盖取诸夬。\n3\n是故,易者,象也,象也者像也。彖者,材也,爻也者,效天下之动者也。是故,吉凶生,而悔吝著也。\n4\n阳卦多阴,阴卦多阳,其故何也?阳卦奇,阴卦偶。其德行何也?阳一君而二民,君子之道也。阴二君而一民,小人之道也。\n5\n易曰:“憧憧往来,朋从尔思。”子曰:“天下何思何虑?天下同归而殊涂,一致而百虑,天下何思何虑?”\n“日往则月来,月往则日来,日月相推而明生焉。寒往则暑来,暑往则寒来,寒暑相推而岁成焉。往者屈也,来者信也,屈信相感而利生焉。”\n“尺蠖之屈,以求信也。龙蛇之蛰,以存身也。精义入神,以致用也。利用安身,以崇德也。过此以往,未之或知也。穷神知化,德之盛也。”\n易曰:“困于石,据于蒺蔾,入于其宫,不见其妻,凶。”子曰:“非所困而困焉,名必辱。非所据而据焉,身必危。既辱且危,死期将至,妻其可得见耶?”\n易曰:“公用射隼,于高墉之上,获之无不利。”子曰:“隼者禽也,弓矢者器也,射之者人也。君子藏器于身,待时而动,何不利之有?动而不括,是以出而有获,语成器而动者也。”\n子曰:“小人不耻不仁,不畏不义,不见利不劝,不威不惩,小惩而大诫,此小人之福也。易曰:‘履校灭趾无咎,此之谓也’。”\n“善不积,不足以成名;恶不积,不足以灭身。小人以小善为无益,而弗为也,以小恶为无伤,而弗去也,故恶积而不可掩,罪大而不可解。易曰:‘何校灭耳凶’。”\n子曰:“危者,安其位者也;亡者,保其存者也;乱者,有其治者也。是故,君子安而不忘危,存而不忘亡,治而不忘乱;是以身安而国家可保也。易曰:‘其亡其亡,系于苞桑’。”\n子曰:“德薄而位尊,知小而谋大,力小而任重,鲜不及矣,易曰:‘鼎折足,覆公餗,其形渥,凶。’言不胜其任也。”\n子曰:“知几其神乎?君子上交不谄,下交不渎,其知几乎,几者动之微,吉之先见者也,君子见几而作,不俟终日。易曰:‘介于石,不终日,贞吉。’介如石焉,宁用终日,断可识矣,君子知微知彰,知柔知刚,万夫之望。”\n子曰:“颜氏之子,其殆庶几乎?有不善未尝不知,知之未尝复行也。易曰:‘不远复,无祇悔,元吉。’”\n天地絪縕,万物化醇,男女构精,万物化生,易曰:‘三人行,则损一人;一人行,则得其友。’言致一也。\n子曰:“君子安其身而后动,易其心而后语,定其交而后求,君子修此三者,故全也,危以动,则民不与也,惧以语,则民不应也,无交而求,则民不与也,莫之与,则伤之者至矣。易曰:‘莫益之,或击之,立心勿恒,凶。’。”\n6\n子曰:“乾坤其易之门邪?乾,阳物也;坤,阴物也;阴阳合德,而刚柔有体,以体天地之撰,以通神明之德,其称名也杂而不越,于稽其类,其衰世之意邪?”夫易,彰往而察来,而微显阐幽,开而当名,辨物正言,断辞则备矣,其称名也小,其取类也大,其旨远,其辞文,其言曲而中,其事肆而隐,因贰以济民行,以明失得之报。\n7\n易之兴也,其于中古乎,作易者,其有忧患乎。\n是故,履,德之基也;谦,德之柄也;复,德之本也;恒,德之固也;损德之修也;益,德之裕也;困,德之辨也;井,德之地也;巽,德之制也。\n履,和而至;谦,尊而光;复,小而辨于物;恒,杂而不厌;损,先难而后易;益,长裕而不设;困,穷而通;井,居其所而迁,巽,称而隐。\n履以和行,谦以制礼,复以自知,恒以一德,损以远害,益以兴利,困以寡怨,井以辩义,巽以行权。\n8\n易之为书也不可远,为道也屡迁,变动不居,周流六虚,上下无常,刚柔相易,不可为典要,唯变所适,其出入以度,外内使知惧,又明于忧患与故,无有师保,如临父母,初率其辞,而揆其方,既有典常,苟非其人,道不虚行。\n9\n易之为书也,原始要终,以为质也,六爻相杂,唯其时物也,其初难知,其上易知,本末也,初辞拟之,卒成之终,若夫杂物撰德,辨是与非,则非其中爻不备。\n噫,亦要存亡吉凶,则居可知矣,知者观其彖辞,则思过半矣。\n二与四同功,而异位,其善不同,二多誉,四多惧,近也,柔之为道,不利远者,其要无咎,其用柔中也,三与五同功,而异位,三多凶,五多功,贵贱之等也,其柔危,其刚胜邪?\n10\n易之为书也,广大悉备,有天道焉,有人道焉,有地道焉。兼三材而两之,故六六者,非它也,三材之道也,道有变动,故曰爻,爻有等,故曰物,物相杂,故曰文,文不当,故吉凶生焉。\n11\n易之兴也,其当殷之末世,周之盛德邪,当文王与纣之事邪,是故其辞危,危者使平,易者使倾,其道甚大,百物不废,惧以终始,其要无咎,此之谓易之道也。\n12\n夫乾,天下之至健也,德行恒易以知险,夫坤,天下之至顺也,德行恒简以知阻。\n能说诸心,能研诸侯之虑,定天下之吉凶,成天下之亹亹者,是故,变化云为,吉事有祥,象事知器,占事知来。天地设位,圣人成能。人谋鬼谋,百姓与能。\n八卦以象告,爻彖以情言,刚柔杂居,而吉凶可见矣。\n变动以利言,吉凶以情迁。是故爱恶相攻而吉凶生,远近相取而悔吝生,情伪相感而利害生。凡易之情,近而不相得则凶,或害之,悔且吝。\n将叛者其辞惭,中心疑者其辞枝,吉人之辞寡,躁人之辞多,诬善之人其辞游,失其守者其辞屈。\n说卦传 1\n昔者圣人之作《易》也,幽赞于神明而生蓍,参天两地而倚数,观变于阴阳而立卦,发挥于刚柔而生爻,和顺于道德而理于义,穷理尽性以至于命。\n2\n昔者圣人之作《易》也,将以顺性命之理,是以立天之道曰阴与阳,立地之道曰柔与刚,立人之道曰仁与义。兼三才而两之,故《易》六画而成卦。分阴分阳,迭用柔刚,故《易》六位而成章。\n3\n天地定位,山泽通气,雷风相薄,水火不相射,八卦相错。数往者顺,知来者逆,是故《易》逆数也。\n4\n雷以动之,风以散之,雨以润之,日以烜之,艮以止之,兑以说之,乾以君之,坤以藏之。\n5\n帝出乎震,齐乎巽,相见乎离,致役乎坤,说言乎兑,战乎乾,劳乎坎,成言乎艮。万物出乎震,震东方也。齐乎巽,巽东南也,齐也者、言万物之洁齐也。离也者、明也,万物皆相见,南方之卦也。圣人南面而听天下,向明而治,盖取诸此也。坤也者、地也,万物皆致养焉,故曰:致役乎坤。兑、正秋也,万物之所说也,故曰:说言乎兑。战乎乾,乾、西北之卦也,言阴阳相薄也。坎者、水也,正北方之卦也,劳卦也,万物之所归也,故曰:劳乎坎。艮、东北之卦也。万物之所成终而所成始也。故曰:成言乎艮。\n6\n神也者、妙万物而为言者也。动万物者莫疾乎雷,桡万物者莫疾乎风,燥万物者莫熯乎火,说万物者莫说乎泽,润万物者莫润乎水,终万物、始万物者、莫盛乎艮。故水火相逮,雷风不相悖,山泽通气,然后能变化,既成万物也。\n7\n乾,健也;坤,顺也;震,动也;巽,入也;坎,陷也;离,丽也;艮,止也;兑,说也。\n8\n乾为马。坤为牛。震为龙。巽为鸡。坎为豕。离为雉。艮为狗。兑为羊。\n9\n乾为首。坤为腹。震为足。巽为股。坎为耳。离为目。艮为手。兑为口。\n10\n乾,天也,故称乎父。坤,地也,故称乎母。震一索而得男,故谓之长男。巽一索而得女,故谓之长女。坎再索而得男,故谓之中男。离再索而得女,故谓之中女。艮三索而得男,故谓之少男。兑三索而得女,故谓之少女。\n11\n乾为天,为圜,为君,为父,为玉,为金,为寒,为冰,为大赤,为良马,为老马,为瘠马,为驳马,为木果。\n坤为地,为母,为布,为釜,为吝啬,为均,为子母牛,为大舆,为文,为众,为柄,其于地也为黑。\n震为雷,为龙,为玄黄,为旉,为大涂,为长子,为决躁,为苍筤竹,为萑苇。其于马也,为善鸣,为馵足,为作足,为的颡。其于稼也,为反生。其究为健,为蕃鲜。\n巽为木,为风,为长女,为绳直,为工,为白,为长,为高,为进退,为不果,为臭。其于人也,为寡发,为广颡,为多白眼,为近利市三倍,其究为躁卦。\n坎为水,为沟渎,为隐伏,为矫輮,为弓轮。其于人也,为加忧,为心病,为耳痛,为血卦,为赤。其于马也,为美脊,为亟心,为下首,为薄蹄,为曳。其于舆也,为多眚,为通,为月,为盗。其于木也,为坚多心。\n离为火,为日,为电,为中女,为甲胃,为戈兵。其于人也,为大腹。为乾卦,为鳖,为蟹,为蠃,为蚌,为龟。其于木也,为科上槁。\n艮为山,为径路,为小石,为门阙,为果蓏,为阍寺,为指,为狗,为鼠,为黔喙之属。其于木也,为坚多节。\n兑为泽,为少女,为巫,为口舌,为毁折,为附决。其于地也,为刚卤。为妾,为羊。\n序卦传 上篇\n有天地,然后万物生焉。盈天地之间者唯万物,故受之以《屯》。\n《屯》者,盈也;物之始生也。物生必蒙,故受之以《蒙》。\n《蒙》者,蒙也;物之稚也。物稚不可不养也,故受之以《需》。\n《需》者,饮食之道也。饮食必有讼,故受之以《讼》。\n讼必有众起,故受之以《师》。\n《师》者,众也。众必有所比,故受之以《比》。\n《比》者,比也。比必有所畜,故受之以《小畜》。\n物畜然后有礼,故受之以《履》。\n履而泰然后安,故受之以《泰》。\n《泰》者,通也。物不可以终通,故受之以《否》。\n物不可以终否,故受之以《同人》。\n与人同者物必归焉,故受之以《大有》。\n有大者不可以盈,故受之以《谦》。\n有大而能谦必豫,故受之以《豫》。\n豫必有随,故受之以《随》。\n以喜随人者必有事,故受之以《蛊》。\n《蛊》者,事也。有事而后可大,故受之以《临》。\n《临》者,大也。物大然后可观,故受之以《观》。\n可观而后有所合,故受之以《噬嗑》。\n嗑者,合也。物不可苟合而已,故受之以《贲》。\n《贲》者,饰也。致饰然后亨则尽矣,故受之以《剥》。\n《剥》者,剥也。物不可以终尽,剥,穷上反下,故受之以《复》。\n复则不妄矣,故受之以《无妄》。\n有无妄然后可畜,故受之以《大畜》。\n物畜然后可养,故受之以《颐》。\n《颐》者,养也。不养则不可动,故受之以《大过》。\n物不可以终过,故受之以《坎》。\n《坎》者,陷也。陷必有所丽,故受之以《离》。\n《离》者,丽也。\n下篇\n有天地,然后有万物;有万物,然后有男女;有男女,然后有夫妇;有夫妇,然后有父子;有父子,然后有君臣;有君臣,然后有上下;有上下,然后礼义有所错。\n夫妇之道,不可以不久也,故受之以《恒》。\n《恒》者,久也。物不可以久居其所,故受之以《遁》。\n《遁》者,退也。物不可以终遁,故受之以《大壮》。\n物不可以终壮,故受之以《晋》。\n《晋》者,进也。晋必有所伤,故受之以《明夷》。\n夷者,伤也。伤于外者必反其家,故受之以《家人》。\n家道穷必乖,故受之以《睽》。\n《睽》者,乖也。乖必有难,故受之以《蹇》。\n《蹇》者,难也。物不可以终难,故受之以《解》。\n《解》者,缓也。缓必有所失,故受之以《损》。\n损而不已必益,故受之以《益》。\n益而不已必决,故受之以《夬》。\n《夬》者,决也。决必有所遇,故受之以《姤》。\n《姤》者,遇也。物相遇而后聚,故受之以《萃》。\n《萃》者,聚也。聚而上者谓之升,故受之以《升》。\n升而不已必困,故受之以《困》。\n困乎上者必反下,故受之以《井》。\n井道不可不革,故受之以《革》。\n革物者莫若鼎,故受之以《鼎》。\n主器者莫若长子,故受之以《震》。\n《震》者,动也。物不可以终动,止之,故受之以《艮》。\n《艮》者,止也。物不可以终止,故受之以《渐》。\n《渐》者,进也。进必有所归,故受之以《归妹》。\n得其所归者必大,故受之以《丰》。\n《丰》者,大也。穷大者必失其所居,故受之以《旅》。\n旅而无所容,故受之以《巽》。\n《巽》者,入也。入而后说之,故受之以《兑》。\n《兑》者,说也。说而后散之,故受之以《涣》。\n《涣》者,离也。物不可以终离,故受之以《节》。\n节而信之,故受之以《中孚》。\n有信者必行之,故受之以《小过》。\n有过物者必济,故受之以《既济》。\n物不可穷也,故受之以《未济》终焉。\n杂卦 《乾》刚《坤》柔,《比》乐《师》忧。\n《临》、《观》之义,或与或求。\n《屯》见而不失其居,《蒙》杂而著。\n《震》,起也。\n《艮》,止也。\n《损》、《益》盛衰之始也。\n《大畜》时也。\n《无妄》灾也。\n《萃》聚而《升》不来也。\n《谦》轻而《豫》怠也。\n《噬嗑》食也,《贲》无色也。\n《兑》见而《巽》伏也。\n《随》无故也,《蛊》则饬也。\n《剥》烂也,《复》反也。\n《晋》昼也。\n《明夷》诛也。\n《井》通而《困》相遇也。\n《咸》速也。\n《恒》久也。\n《涣》离也。\n《节》止也。\n《解》缓也。\n《蹇》难也。\n《睽》外也。\n《家人》内也。\n《否》、《泰》反其类也。\n《大壮》则止,《遁》则退也。\n《大有》众也。\n《同人》亲也。\n《革》去故也。\n《鼎》取新也。\n《小过》过也。\n《中孚》信也。\n《丰》多故也。\n亲寡《旅》也。\n《离》上而《坎》下也。\n《小畜》寡也。\n《履》不处也。\n《需》不进也。\n《讼》不亲也。\n《大过》颠也。\n《姤》遇也,柔遇刚也,《渐》女归待男行也。\n《颐》养正也。\n《既济》定也。\n《归妹》女之终也。\n《未济》男之穷也。\n《夬》决也,刚决柔也,君子道长,小人道忧也。\n六十四卦圆图的来历 “伏羲先天六十四卦圆图”是怎么来的呢?“太极图”又是怎么来的呢?从下图中可以看出,两个图是可以合为一体的,它们都来自于太阳运动轨迹对地球的影响,所以两图中的重要节点都与二十四节气吻合。\n![[assets/pasted image 20231030140328.png]]\n![[assets/pasted image 20231030140335.png]]\n","date":"2023-10-27","permalink":"https://aituyaa.com/%E6%98%93%E7%BB%8F%E5%85%A5%E9%97%A8/","summary":"摘录自 《易经》入门及全文解析 学习易经 《易经》是一个取之不尽用之不竭的智慧源泉,它的思想贯穿于中国古代所有文化中,是中国文化的总枢纽。 《易经》很难学? 读《易经》之","title":"易经入门"},]
[{"content":"📘 三十六计\n《三十六计》或称“三十六策”,是指中国古代三十六个兵法策略,语源于南北朝,成书于明清。它是根据我国古代卓越的军事思想和丰富的斗争经验总结而成的兵书,是中华民族悠久文化遗产之一。\n--- 胜战计 ---\r瞒天过海 围魏救赵 借刀杀人 以逸待劳 趁火打劫 声东击西\r--- 敌战计 ---\r无中生有 暗渡陈仓 隔岸观火 笑里藏刀 李代桃僵 顺手牵羊\r--- 攻战计 ---\r打草惊蛇 借尸还魂 调虎离山 欲擒故纵 抛砖引玉 擒贼擒王\r--- 混战计 ---\r釜底抽薪 混水摸鱼 金蝉脱壳 关门捉贼 远交近攻 假道伐虢\r--- 并战计 ---\r偷梁换柱 指桑骂槐 假痴不癫 上屋抽梯 树上开花 反客为主\r--- 败战计 ---\r美人计 空城计 反间计 苦肉计 连环计 走为上计 胜战计 瞒天过海 备周则意怠,常见则不疑。阴在阳之内,不在阳之对。太阳,太阴。\n围魏救赵 共敌不如分敌,敌阳不如敌阴。\n借刀杀人 敌已明,友未定,引友杀敌,不自出力,以《损》推演。\n以逸待劳 困敌之势,不以战。损刚益柔。\n趁火打劫 敌之害大,就势取利,刚夬柔也。\n声东击西 敌志乱萃,不虞。坤下兑上之象,利其不自主而取之。\n敌战计 无中生有 诳也,非诳也,实其所诳也。少阴、太阴、太阳。\n暗渡陈仓 示之以动,利其静而有主,“益动而巽”。\n隔岸观火 阳乖序乱,阴以待逆。暴戾恣睢,其势自毙。顺以动豫,豫顺以动。\n笑里藏刀 信而安之,阴以图之。备而后动,勿使有变。刚中柔外也。\n李代桃僵 势必有损,损阴以益阳。\n顺手牵羊 微隙在所必乘,微利在所必得。少阴,少阳。\n攻战计 打草惊蛇 疑以叩实,察而后动。复者,阴之媒也。\n借尸还魂 有用者,不可借;不能用者,求借。借不能用者而用之。匪我求童蒙,童蒙求我。\n调虎离山 待天以困之,用人以诱之,往蹇来返。\n欲擒故纵 逼则反兵,走则减势。紧随勿迫,累其气力,消其斗志,散而后擒,兵不血刃。需,有孚,光。\n抛砖引玉 类以诱之,击蒙也。\n擒贼擒王 摧其坚,夺其魁,以解其体。龙战于野,其道穷也。\n混战计 釜底抽薪 不敌其力,而消其势,兑下乾上之象。\n混水摸鱼 乘其阴乱,利其弱而无主,随,以向晦入宴息。\n金蝉脱壳 存其形,完其势;友不疑,敌不动。巽而止蛊。\n关门捉贼 小敌困之。剥,不利有攸往。\n远交近攻 形禁势格,利从近取,害以远隔。上火下泽。\n假道伐虢 两大之间,敌胁以从,我假以势。困,有言不信。\n并战计 偷梁换柱 频更其阵,抽其劲旅,待其自败,而后乘之。曳其轮也。\n指桑骂槐 大凌小者,警以诱之。刚中而应,行险而顺。\n假痴不癫 宁伪作不知不为,不伪作假知妄为。静不露机,云雷屯也。\n上屋抽梯 假之以便,唆之使前,断其援应,陷之死地。遇毒,位不当也。\n树上开花 借局布势,力小势大。鸿渐于陆,其羽可以为仪也。\n反客为主 乘隙插足,扼其主机,渐之进也。\n败战计 美人计 兵强者,攻其将;将智者,伐其情。将弱兵颓,其势自萎。利用御寇,顺相保也。\n空城计 虚者虚之,疑中生疑。刚柔之际,奇而复奇。\n反间计 疑中之疑。比之自内,不自失也。\n苦肉计 人不自害,受害必真。假真真假,间以得行。童蒙之吉,顺以巽也。\n连环计 将多兵众,不可以敌,使其自累,以杀其势。在师中吉,承天宠也。\n走为上计 全师避敌。左次无咎,未失常也。\n","date":"2023-09-18","permalink":"https://aituyaa.com/%E4%B8%89%E5%8D%81%E5%85%AD%E8%AE%A1/","summary":"\u003cp\u003e\u003ca href=\"https://so.gushiwen.cn/guwen/book_46653FD803893E4F5BEE801AE7C631E7.aspx\"\u003e📘 三十六计\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e《三十六计》或称“三十六策”,是指中国古代三十六个兵法策略,语源于南北朝,成书于明清。它是根据我国古代卓越的军事思想和丰富的斗争经验总结而成的兵书,是中华民族悠久文化遗产之一。\u003c/p\u003e","title":"三十六计"},]
[{"content":"📘 孙子兵法\n孙子兵法,是中国现存最早的兵书,也是世界上最早的军事著作,早于克劳塞维茨《战争论》约2300年,被誉为“兵学圣典”。共有六千字左右,一共十三篇。作者为春秋时祖籍齐国乐安的吴国将军孙武。《孙子兵法》是中国古代军事文化遗产中的璀璨瑰宝,优秀传统文化的重要组成部分,是古代军事思想精华的集中体现。\n始计篇 作战篇 谋攻篇 军形篇 兵势篇 虚实篇 军争篇 九变篇 行军篇 地形篇 九地篇 火攻篇 用间篇 始计篇 孙子曰:兵者,国之大事,死生之地,存亡之道,不可不察也。\n故经之以五事,校之以计,而索其情:一曰道,二曰天,三曰地,四曰将、五曰法。道者,令民与上同意也,故可与之死,可与之生,而不畏危也。天者,阴阳、寒暑、时制也。地者,远近、险易、广狭、死生也。将者,智、信、仁、勇、严也。法者,曲制、官道、主用也。凡此五者,将莫不闻,知之者胜,不知者不胜。故校之以计,而索其情,曰:主孰有道?将孰有能?天地孰得?法令孰行?兵众孰强?士卒孰练?赏罚孰明?吾以此知胜负矣。\n将听吾计,用之必胜,留之;将不听吾计,用之必败,去之。计利以听,乃为之势,以佐其外。势者,因利而制权也。\n兵者,诡道也。故能而示之不能,用而示之不用,近而示之远,远而示之近;利而诱之,乱而取之,实而备之,强而避之,怒而挠之,卑而骄之,逸而劳之,亲而离之。攻其无备,出其不意。此兵家之胜,不可先传也。\n夫未战而庙算胜者,得算多也;未战而庙算不胜者,得算少也。多算胜,少算不胜,而况于无算乎?吾以此观之,胜负见矣。\n作战篇 孙子曰:凡用兵之法,驰车千驷,革车千乘,带甲十万,千里馈粮,则内外之费,宾客之用,胶漆之材,车甲之奉,日费千金,然后十万之师举矣。\n其用战也胜,久则钝兵挫锐,攻城则力屈,久暴师则国用不足。夫钝兵挫锐,屈力殚货,则诸侯乘其弊而起,虽有智者,不能善其后矣。故兵闻拙速,未睹巧之久也。夫兵久而国利者,未之有也。故不尽知用兵之害者,则不能尽知用兵之利也。\n善用兵者,役不再籍,粮不三载,取用于国,因粮于敌,故军食可足也。国之贫于师者远输,远输则百姓贫;近师者贵卖,贵卖则百姓财竭,财竭则急于丘役。力屈财殚,中原内虚于家,百姓之费,十去其七;公家之费,破军罢马,甲胄矢弩,戟楯蔽橹,丘牛大车,十去其六。\n故智将务食于敌,食敌一钟,当吾二十钟;萁秆一石,当吾二十石。\n故杀敌者,怒也;取敌之利者,货也。车战得车十乘以上,赏其先得者,而更其旌旗,车杂而乘之,卒善而养之,是谓胜敌而益强。\n故兵贵胜,不贵久。故知兵之将,生民之司命,国家安危之主也。\n谋攻篇 孙子曰:夫用兵之法,全国为上,破国次之,全军为上,破军次之;全旅为上,破旅次之;全卒为上,破卒次之;全伍为上,破伍次之。是故百战百胜,非善之善者也;不战而屈人之兵,善之善者也。\n故上兵伐谋,其次伐交,其次伐兵,其下攻城。攻城之法为不得已。修橹轒辒,具器械,三月而后成,距堙,又三月而后已。将不胜其忿而蚁附之,杀士卒三分之一而城不拔者,此攻之灾也。\n故善用兵者,屈人之兵而非战也,拔人之城而非攻也,毁人之国而非久也,必以全争于天下。故兵不顿而利可全,此谋攻之法也。\n故用兵之法,十则围之,五则攻之,倍则分之,敌则能战之,少则能逃之,不若则能避之。故小敌之坚,大敌之擒也。\n夫将者,国之辅也。辅周,则国必强;辅隙,则国必弱。\n故君之所以患于军者三:不知军之不可以进而谓之进,不知军之不可以退而谓之退,是谓“縻军”;不知三军之事,而同三军之政者,则军士惑矣;不知三军之权,而同三军之任,则军士疑矣。三军既惑且疑,则诸侯之难至矣,是谓“乱军引胜”。\n故知胜有五:知可以战与不可以战者胜,识众寡之用者胜,上下同欲者胜,以虞待不虞者胜,将能而君不御者胜。此五者,知胜之道也。\n故曰:知彼知己者,百战不殆;不知彼而知己,一胜一负,不知彼,不知己,每战必殆。\n军形篇 孙子曰:昔之善战者,先为不可胜,以待敌之可胜。不可胜在己,可胜在敌。故善战者,能为不可胜,不能使敌之必可胜。故曰:胜可知,而不可为。\n不可胜者,守也;可胜者,攻也。守则不足,攻则有余。善守者,藏于九地之下,善攻者,动于九天之上,故能自保而全胜也。\n见胜不过众人之所知,非善之善者也;战胜而天下曰善,非善之善者也。故举秋毫不为多力,见日月不为明目,闻雷霆不为聪耳。古之所谓善战者,胜于易胜者也。故善战者之胜也,无智名,无勇功。故其战胜不忒,不忒者,其所措必胜,胜已败者也。故善战者,立于不败之地,而不失敌之败也。是故胜兵先胜而后求战,败兵先战而后求胜。善用兵者,修道而保法,故能为胜败之政。\n兵法:一曰度,二曰量,三曰数,四曰称,五曰胜。地生度,度生量,量生数,数生称,称生胜。故胜兵若以镒称铢,败兵若以铢称镒。胜者之战民也,若决积水于千仞之溪者,形也。\n兵势篇 孙子曰:凡治众如治寡,分数是也;斗众如斗寡,形名是也;三军之众,可使必受敌而无败者,奇正是也;兵之所加,如以碫投卵者,虚实是也。 凡战者,以正合,以奇胜。故善出奇者,无穷如天地,不竭如江海。终而复始,日月是也。死而更生,四时是也。声不过五,五声之变,不可胜听也;色不过五,五色之变,不可胜观也;味不过五,五味之变,不可胜尝也;战势不过奇正,奇正之变,不可胜穷也。奇正相生,如循环之无端,孰能穷之哉! 激水之疾,至于漂石者,势也;鸷鸟之疾,至于毁折者,节也。故善战者,其势险,其节短。势如扩弩,节如发机。纷纷纭纭,斗乱而不可乱;浑浑沌沌,形圆而不可败。乱生于治,怯生于勇,弱生于强。治乱,数也;勇怯,势也;强弱,形也。 故善动敌者,形之,敌必从之;予之,敌必取之。以利动之,以卒待之。故善战者,求之于势,不责于人,故能择人而任势。任势者,其战人也,如转木石。木石之性,安则静,危则动,方则止,圆则行。 故善战人之势,如转圆石于千仞之山者,势也。\n虚实篇 孙子曰:凡先处战地而待敌者佚,后处战地而趋战者劳,故善战者,致人而不致于人。能使敌人自至者,利之也;能使敌人不得至者,害之也,故敌佚能劳之,饱能饥之,安能动之。出其所不趋,趋其所不意。行千里而不劳者,行于无人之地也。\n攻而必取者,攻其所不守也;守而必固者,守其所不攻也。故善攻者,敌不知其所守;善守者,敌不知其所攻。微乎微乎,至于无形。神乎神乎,至于无声,故能为敌之司命。进而不可御者,冲其虚也;退而不可追者,速而不可及也。故我欲战,敌虽高垒深沟,不得不与我战者,攻其所必救也;我不欲战,虽画地而守之,敌不得与我战者,乖其所之也。\n故形人而我无形,则我专而敌分。我专为一,敌分为十,是以十攻其一也,则我众而敌寡;能以众击寡者,则吾之所与战者,约矣。吾所与战之地不可知,不可知,则敌所备者多;敌所备者多,则吾之所与战者,寡矣。\n故备前则后寡,备后则前寡,备左则右寡,备右则左寡,无所不备,则无所不寡。寡者,备人者也;众者,使人备己者也。\n故知战之地,知战之日,则可千里而会战。不知战地,不知战日,则左不能救右,右不能救左,前不能救后,后不能救前,而况远者数十里,近者数里乎?\n以吾度之,越人之兵虽多,亦奚益于胜败哉?故曰:胜可为也。敌虽众,可使无斗。故策之而知得失之计,作之而知动静之理,形之而知死生之地,角之而知有余不足之处。故形兵之极,至于无形。无形,则深间不能窥,智者不能谋。因形而措胜于众,众不能知;人皆知我所以胜之形,而莫知吾所以制胜之形。故其战胜不复,而应形于无穷。\n夫兵形象水,水之形,避高而趋下,兵之形,避实而击虚。水因地而制流,兵因敌而制胜。故兵无常势,水无常形,能因敌变化而取胜者,谓之神。\n故五行无常胜,四时无常位,日有短长,月有死生。\n军争篇 孙子曰:凡用兵之法,将受命于君,合军聚众,交和而舍,莫难于军争。军争之难者,以迂为直,以患为利。 故迂其途,而诱之以利,后人发,先人至,此知迂直之计者也。军争为利,军争为危。举军而争利则不及,委军而争利则辎重捐。是故卷甲而趋,日夜不处,倍道兼行,百里而争利,则擒三将军,劲者先,疲者后,其法十一而至;五十里而争利,则蹶上将军,其法半至;三十里而争利,则三分之二至。是故军无辎重则亡,无粮食则亡,无委积则亡。故不知诸侯之谋者,不能豫交;不知山林、险阻、沮泽之形者,不能行军;不用乡导者,不能得地利。故兵以诈立,以利动,以分合为变者也。故其疾如风,其徐如林,侵掠如火,不动如山,难知如阴,动如雷震。掠乡分众,廓地分利,悬权而动。先知迂直之计者胜,此军争之法也。 《军政》曰:“言不相闻,故为之金鼓;视不相见,故为之旌旗。”夫金鼓旌旗者,所以一民之耳目也。民既专一,则勇者不得独进,怯者不得独退,此用众之法也。故夜战多金鼓,昼战多旌旗,所以变人之耳目也。 三军可夺气,将军可夺心。是故朝气锐,昼气惰,暮气归。善用兵者,避其锐气,击其惰归,此治气者也。以治待乱,以静待哗,此治心者也。以近待远,以佚待劳,以饱待饥,此治力者也。无邀正正之旗,勿击堂堂之阵,此治变者也。 故用兵之法,高陵勿向,背丘勿逆,佯北勿从,锐卒勿攻,饵兵勿食,归师勿遏,围师遗阙,穷寇勿迫,此用兵之法也。\n九变篇 孙子曰:凡用兵之法,将受命于君,合军聚众。圮地无舍,衢地交合,绝地无留,围地则谋,死地则战,途有所不由,军有所不击,城有所不攻,地有所不争,君命有所不受。 故将通于九变之利者,知用兵矣;将不通九变之利,虽知地形,不能得地之利矣;治兵不知九变之术,虽知五利,不能得而用之矣。 是故智者之虑,必杂于利害,杂于利而务可信也,杂于害而患可解也。是故屈诸侯者以害,役诸侯者以业,趋诸侯者以利。故用兵之法,无恃其不来,恃吾有以待之;无恃其不攻,恃吾有所不可攻也。 故将有五危,必死可杀,必生可虏,忿速可侮,廉洁可辱,爱民可烦。凡此五者,将之过也,用兵之灾也。覆军杀将,必以五危,不可不察也。\n行军篇 孙子曰:凡处军相敌:绝山依谷,视生处高,战隆无登,此处山之军也。绝水必远水;客绝水而来,勿迎之于水内,令半济而击之,利;欲战者,无附于水而迎客;视生处高,无迎水流,此处水上之军也。绝斥泽,惟亟去无留;若交军于斥泽之中,必依水草而背众树,此处斥泽之军也。平陆处易,而右背高,前死后生,此处平陆之军也。凡此四军之利,黄帝之所以胜四帝也。\n凡军好高而恶下,贵阳而贱阴,养生而处实,军无百疾,是谓必胜。丘陵堤防,必处其阳,而右背之。此兵之利,地之助也。\n上雨,水沫至,欲涉者,待其定也。\n凡地有绝涧、天井、天牢、天罗、天陷、天隙,必亟去之,勿近也。吾远之,敌近之;吾迎之,敌背之。\n军行有险阻、潢井、葭苇、山林、蘙荟者,必谨覆索之,此伏奸之所处也。\n敌近而静者,恃其险也;远而挑战者,欲人之进也;其所居易者,利也。\n众树动者,来也;众草多障者,疑也;鸟起者,伏也;兽骇者,覆也;尘高而锐者,车来也;卑而广者,徒来也;散而条达者,樵采也;少而往来者,营军也。\n辞卑而益备者,进也;辞强而进驱者,退也;轻车先出居其侧者,陈也;无约而请和者,谋也;奔走而陈兵车者,期也;半进半退者,诱也。\n杖而立者,饥也;汲而先饮者,渴也;见利而不进者,劳也;鸟集者,虚也;夜呼者,恐也;军扰者,将不重也;旌旗动者,乱也;吏怒者,倦也;粟马肉食,军无悬缻,不返其舍者,穷寇也;谆谆翕翕,徐与人言者,失众也;数赏者,窘也;数罚者,困也;先暴而后畏其众者,不精之至也;来委谢者,欲休息也。兵怒而相迎,久而不合,又不相去,必谨察之。\n兵非益多也,惟无武进,足以并力、料敌、取人而已。夫惟无虑而易敌者,必擒于人。\n卒未亲附而罚之,则不服,不服则难用也。卒已亲附而罚不行,则不可用也。故令之以文,齐之以武,是谓必取。令素行以教其民,则民服;令不素行以教其民,则民不服。令素行者,与众相得也。\n地形篇 孙子曰:地形有通者,有挂者,有支者,有隘者,有险者,有远者。我可以往,彼可以来,曰通;通形者,先居高阳,利粮道,以战则利。可以往,难以返,曰挂;挂形者,敌无备,出而胜之;敌若有备,出而不胜,难以返,不利。我出而不利,彼出而不利,曰支;支形者,敌虽利我,我无出也;引而去之,令敌半出而击之,利。隘形者,我先居之,必盈之以待敌;若敌先居之,盈而勿从,不盈而从之。险形者,我先居之,必居高阳以待敌;若敌先居之,引而去之,勿从也。远形者,势均,难以挑战,战而不利。凡此六者,地之道也;将之至任,不可不察也。\n故兵有走者,有弛者,有陷者,有崩者,有乱者,有北者。凡此六者,非天之灾,将之过也。夫势均,以一击十,曰走;卒强吏弱,曰弛,吏强卒弱,曰陷;大吏怒而不服,遇敌怼而自战,将不知其能,曰崩;将弱不严,教道不明,吏卒无常,陈兵纵横,曰乱;将不能料敌,以少合众,以弱击强,兵无选锋,曰北。凡此六者,败之道也;将之至任,不可不察也。\n夫地形者,兵之助也。料敌制胜,计险厄远近,上将之道也。知此而用战者必胜,不知此而用战者必败。\n故战道必胜,主曰无战,必战可也;战道不胜,主曰必战,无战可也。故进不求名,退不避罪,唯人是保,而利合于主,国之宝也。\n视卒如婴儿,故可与之赴深溪;视卒如爱子,故可与之俱死。厚而不能使,爱而不能令,乱而不能治,譬若骄子,不可用也。\n知吾卒之可以击,而不知敌之不可击,胜之半也;知敌之可击,而不知吾卒之不可以击,胜之半也;知敌之可击,知吾卒之可以击,而不知地形之不可以战,胜之半也。故知兵者,动而不迷,举而不穷。故曰:知彼知己,胜乃不殆;知天知地,胜乃不穷。\n九地篇 孙子曰:用兵之法,有散地,有轻地,有争地,有交地,有衢地,有重地,有圮地,有围地,有死地。诸侯自战其地,为散地。入人之地不深者,为轻地。我得则利,彼得亦利者,为争地。我可以往,彼可以来者,为交地。诸侯之地三属,先至而得天下之众者,为衢地。入人之地深,背城邑多者,为重地。行山林、险阻、沮泽,凡难行之道者,为圮地。所由入者隘,所从归者迂,彼寡可以击吾之众者,为围地。疾战则存,不疾战则亡者,为死地。是故散地则无战,轻地则无止,争地则无攻,交地则无绝,衢地则合交,重地则掠,圮地则行,围地则谋,死地则战。\n所谓古之善用兵者,能使敌人前后不相及,众寡不相恃,贵贱不相救,上下不相收,卒离而不集,兵合而不齐。合于利而动,不合于利而止。敢问:“敌众整而将来,待之若何?”曰:“先夺其所爱,则听矣。”\n兵之情主速,乘人之不及,由不虞之道,攻其所不戒也。\n凡为客之道:深入则专,主人不克;掠于饶野,三军足食;谨养而勿劳,并气积力,运兵计谋,为不可测。投之无所往,死且不北,死焉不得,士人尽力。兵士甚陷则不惧,无所往则固。深入则拘,不得已则斗。是故其兵不修而戒,不求而得,不约而亲,不令而信,禁祥去疑,至死无所之。吾士无余财,非恶货也;无余命,非恶寿也。令发之日,士卒坐者涕沾襟。偃卧者涕交颐。投之无所往者,诸、刿之勇也。\n故善用兵者,譬如率然;率然者,常山之蛇也。击其首则尾至,击其尾则首至,击其中则首尾俱至。敢问:“兵可使如率然乎?”曰:“可。”夫吴人与越人相恶也,当其同舟而济,遇风,其相救也如左右手。是故方马埋轮,未足恃也;齐勇若一,政之道也;刚柔皆得,地之理也。故善用兵者,携手若使一人,不得已也。\n将军之事:静以幽,正以治。能愚士卒之耳目,使之无知。易其事,革其谋,使人无识;易其居,迂其途,使人不得虑。帅与之期,如登高而去其梯;帅与之深入诸侯之地,而发其机,焚舟破釜,若驱群羊,驱而往,驱而来,莫知所之。聚三军之众,投之于险,此谓将军之事也。九地之变,屈伸之利,人情之理,不可不察。\n凡为客之道:深则专,浅则散。去国越境而师者,绝地也;四达者,衢地也;入深者,重地也;入浅者,轻地也;背固前隘者,围地也;无所往者,死地也。\n是故散地,吾将一其志;轻地,吾将使之属;争地,吾将趋其后;交地,吾将谨其守;衢地,吾将固其结;重地,吾将继其食;圮地,吾将进其途;围地,吾将塞其阙;死地,吾将示之以不活。\n故兵之情,围则御,不得已则斗,过则从。是故不知诸侯之谋者,不能预交;不知山林、险阻、沮泽之形者,不能行军;不用乡导者,不能得地利。四五者,不知一,非霸王之兵也。夫霸王之兵,伐大国,则其众不得聚;威加于敌,则其交不得合。是故不争天下之交,不养天下之权,信己之私,威加于敌,故其城可拔,其国可隳。施无法之赏,悬无政之令,犯三军之众,若使一人。犯之以事,勿告以言;犯之以利,勿告以害。\n投之亡地然后存,陷之死地然后生。夫众陷于害,然后能为胜败。\n故为兵之事,在于顺详敌之意,并敌一向,千里杀将,此谓巧能成事者也。\n是故政举之日,夷关折符,无通其使;厉于廊庙之上,以诛其事。敌人开阖,必亟入之。先其所爱,微与之期。践墨随敌,以决战事。是故始如处女,敌人开户,后如脱兔,敌不及拒。\n火攻篇 孙子曰:凡火攻有五:一曰火人,二曰火积,三曰火辎,四曰火库,五曰火队。行火必有因,烟火必素具。发火有时,起火有日。时者,天之燥也;日者,月在箕、壁、翼、轸也。凡此四宿者,风起之日也。\n凡火攻,必因五火之变而应之。火发于内,则早应之于外。火发兵静者,待而勿攻,极其火力,可从而从之,不可从而止。火可发于外,无待于内,以时发之。火发上风,无攻下风。昼风久,夜风止。凡军必知有五火之变,以数守之。\n故以火佐攻者明,以水佐攻者强。水可以绝,不可以夺。夫战胜攻取,而不修其功者凶,命曰费留。故曰:明主虑之,良将修之。非利不动,非得不用,非危不战。主不可以怒而兴师,将不可以愠而致战;合于利而动,不合于利而止。怒可以复喜,愠可以复悦;亡国不可以复存,死者不可以复生。故明君慎之,良将警之,此安国全军之道也。\n用间篇 孙子曰:凡兴师十万,出征千里,百姓之费,公家之奉,日费千金;内外骚动,怠于道路,不得操事者,七十万家。相守数年,以争一日之胜,而爱爵禄百金,不知敌之情者,不仁之至也,非人之将也,非主之佐也,非胜之主也。故明君贤将,所以动而胜人,成功出于众者,先知也。先知者,不可取于鬼神,不可象于事,不可验于度,必取于人,知敌之情者也。\n故用间有五:有因间,有内间,有反间,有死间,有生间。五间俱起,莫知其道,是谓神纪,人君之宝也。因间者,因其乡人而用之。内间者,因其官人而用之。反间者,因其敌间而用之。死间者,为诳事于外,令吾间知之,而传于敌间也。生间者,反报也。\n故三军之事,莫亲于间,赏莫厚于间,事莫密于间。非圣智不能用间,非仁义不能使间,非微妙不能得间之实。微哉!微哉!无所不用间也。间事未发,而先闻者,间与所告者皆死。\n凡军之所欲击,城之所欲攻,人之所欲杀,必先知其守将,左右,谒者,门者,舍人之姓名,令吾间必索知之。\n必索敌人之间来间我者,因而利之,导而舍之,故反间可得而用也。因是而知之,故乡间、内间可得而使也;因是而知之,故死间为诳事,可使告敌。因是而知之,故生间可使如期。五间之事,主必知之,知之必在于反间,故反间不可不厚也。\n昔殷之兴也,伊挚在夏;周之兴也,吕牙在殷。故惟明君贤将,能以上智为间者,必成大功。此兵之要,三军之所恃而动也。\n","date":"2023-09-18","permalink":"https://aituyaa.com/%E5%AD%99%E5%AD%90%E5%85%B5%E6%B3%95/","summary":"\u003cp\u003e\u003ca href=\"https://so.gushiwen.cn/guwen/book_46653FD803893E4F5B7146187204196D.aspx\"\u003e📘 孙子兵法\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e孙子兵法,是中国现存最早的兵书,也是世界上最早的军事著作,早于克劳塞维茨《战争论》约2300年,被誉为“兵学圣典”。共有六千字左右,一共十三篇。作者为春秋时祖籍齐国乐安的吴国将军孙武。《孙子兵法》是中国古代军事文化遗产中的璀璨瑰宝,优秀传统文化的重要组成部分,是古代军事思想精华的集中体现。\u003c/p\u003e","title":"孙子兵法"},]
[{"content":"德国心理学家、实验学习心理学的创始人赫尔曼·艾宾浩斯(hermann ebbinghaus) 1850 年 1 月 24 日 - 1909 年 2 月 26 日)研究发现,遗忘在学习之后立即开始,而且遗忘的进程并不是均匀的,最初遗忘速度很快,以后逐渐缓慢。他认为"保持和遗忘是时间的函数",并根据他的实验结果绘成描述遗忘进程的曲线,即著名的艾宾浩斯记忆遗忘曲线。\n规律对于自然人改造世界的行为,只能起一个催化的作用。\n![[assets/pasted image 20230918143737.png|550]]\n\u0026gt; 艾宾浩斯记忆遗忘曲线\n这就是非常著名的揭示遗忘规律的曲线:艾宾浩斯记忆遗忘曲线,图中竖轴表示学习中记住的知识数量,横轴表示时间 (天数),曲线表示记忆量变化的规律。\n遗忘是什么 遗忘,就是我们对于曾经记忆过的东西不能再认起来,或不能回忆起来,或者是错误的再认和错误的回忆。\n他是怎么实验的 记忆是一种高级的心理过程,受许多因素影响。旧联想主义者只从结果推论原因,没有给予科学的解释;艾宾浩斯则从严格控制原因来观察结果,对记忆过程进行定量分析。\n艾宾浩斯在做这个实验的时候是拿自己作为测试对象的,他发觉,用散文或诗词作为记忆材料存在着一定的困难,因为各人的文化背景和知识经验不同,且理解语言的人容易把意义或联想跟词形成联系,这些已形成的联想可以有助材料的学习,这样便不能在意义方面加以控制。\n为此,艾宾浩斯寻找一些没有形成联想的、完全同类的、对被试来说同样不熟悉的材料,用这些材料做实验就不可能有任何过去的联想。这种材料便是无意义音节,也就是那些不能拼出单词来的众多字母的组合,比如 asww,cfhhj,ijikmb,rfyjbc 等等。\n艾宾浩斯在关于记忆的实验中发现,记住 12 个无意义音节,平均需要重复 16.5 次;为了记住 36 个无意义章节,需重复 54 次;而记忆六首诗中的 480 个音节,平均只需要重复 8 次!\n艾宾浩斯的研究方法是客观的、实验的、通过细致观察和记录可以量化的。他的程序是把数据基础置于经过时间考验的联想和学习的研究之上。他推想出,对于学习材料的难度,可以用学习材料时所需要重复的次数来测量它,而计算起来的这个重复的次数也可以作为完全再现的标准。为使实验有条不紊,他连续 5 年(约 1879-1884)调节了自己的个人习惯,尽量使个人习惯保持常态,按照同样严格的日常做法去工作,学习材料时总是恰在每天的同一时间。\n:: 5 年啊,5 年,真心是一个有毅力的人!\n结语 艾宾浩斯记忆曲线是艾宾浩斯在实验室中经过了大量测试后,产生了不同的记忆数据,从而生成的一种曲线,是一个具有共性的群体规律。\n此记忆曲线并不考虑接受试验个人的个性特点,而是寻求一种处于平衡点的记忆规律。但是记忆规律可以具体到我们每个人,因为我们的生理特点、生活经历不同,可能导致我们有不同的记忆习惯、记忆方式、记忆特点。\n规律对于自然人改造世界的行为,只能起一个催化的作用,如果与每个人的记忆特点相吻合,那么就如顺水扬帆,一日千里;如果与个人记忆特点相悖,记忆效果则会大打折扣。因此,我们要根据每个人的不同特点,寻找到属于自己的艾宾浩斯记忆曲线。\n不过,艾宾浩斯的实验向我们充分证实了一个道理,学习要勤于复习,而且记忆的理解效果越好,遗忘也越慢。\n","date":"2023-09-18","permalink":"https://aituyaa.com/%E8%89%BE%E5%AE%BE%E6%B5%A9%E6%96%AF%E8%AE%B0%E5%BF%86%E9%81%97%E5%BF%98%E6%9B%B2%E7%BA%BF/","summary":"\u003cp\u003e德国心理学家、实验学习心理学的创始人\u003ca href=\"https://wiki.mbalib.com/wiki/%E8%B5%AB%E5%B0%94%E6%9B%BC%C2%B7%E8%89%BE%E5%AE%BE%E6%B5%A9%E6%96%AF\" title=\"赫尔曼·艾宾浩斯\"\u003e赫尔曼·艾宾浩斯\u003c/a\u003e(Hermann Ebbinghaus) 1850 年 1 月 24 日 - 1909 年 2 月 26 日)研究发现,\u003cstrong\u003e遗忘在学习之后立即开始,而且遗忘的进程并不是均匀的,最初遗忘速度很快,以后逐渐缓慢\u003c/strong\u003e。他认为"保持和遗忘是时间的函数",并根据他的实验结果绘成描述遗忘进程的曲线,即著名的艾宾浩斯记忆遗忘曲线。\u003c/p\u003e","title":"艾宾浩斯记忆遗忘曲线"},]
[{"content":"最近在在改进站点的部署脚本,调试的时候有一些中间状态信息想要高亮显示,就需要对终端 terminal 字符的输出样式进行修改,如何修改呢?往下看。\n修改终端字符输出样式 bash 命令 输出预览 echo \u0026quot;hello world\u0026quot; echo -e \u0026quot;\\e[31mhello world\\e[0m\u0026quot; ![[assets/pasted image 20230907120819.png]] echo -e \u0026quot;\\e[31mhello\\e[0m world\u0026quot; ![[assets/pasted image 20230907120736.png]] echo -e \u0026quot;\\e[31;47mhello\\e[0m world\u0026quot; 如上表中所描述的这样,我们改变了输出字符的字体颜色(前景色)和背景颜色(后景色)。通常,达到这种效果,已经满足了我此次的需求。它是怎么实现的呢?\n在终端中运行 help echo ,我们可以看到 echo 命令的简要使用说明,部分如下:\n$ help echo\recho: echo [-nee] [arg ...]\rwrite arguments to the standard output.\rdisplay the args, separated by a single space character and followed by a\rnewline, on the standard output.\roptions:\r-e enable interpretation of the following backslash escapes\r允许解释后面的反斜杠转义\r-e explicitly suppress interpretation of backslash escapes\r禁止解释后面的反斜杠转义\r`echo\u0026#39; interprets the following backslash-escaped characters:\r\\e escape character\r\u0026lt;esc\u0026gt;\r...\rexit status:\rreturns success unless a write error occurs. 现在我们明白了, -e 就是‘允许解释后面的反斜杠转义’,而 \\e 代表的是 \u0026lt;esc\u0026gt; 。\necho \u0026#34;hello world\u0026#34;\techo -e \u0026#34;\\e[31mhello world\\e[0m\u0026#34;\recho -e \u0026#34;\\e[31mhello\\e[0m world\u0026#34;\recho -e \u0026#34;\\e[31;47mhello\\e[0m world\u0026#34; 我们再来分析一下这个格式,排除直接输出的字符 hello world ,就是诸如 \\e[31m 、\\e[0m 、\\e[31;47m 这种字符,改变字符输出样式的就是它们了!\n那么,它们是什么呢?csi !\ncsi csi(control sequence introducer,控制顺序介接子;控制序列引导码)。\ncsi 序列由 esc [ (又写作 \\e[ 或 \\033)、若干个(包括 0 个)“参数字节”、若干个“中间字节”,以及一个“最终字节”组成。\n:: 没那么复杂,起码不需要记得那么复杂,知道用 \\e[ 来引导一个 csi 序列就可以了。\n所有常见的序列都只是把参数用作一系列分号分隔的数字,如 1;2;3。缺少的数字视为 0(如 1;;3 相当于中间的数字是 0,esc[m 这样没有参数的情况相当于参数为 0)。\ncsi 是 ansi 转义序列的一种,后者是一种带内信号的转义序列标准,用于控制视频文本终端上的光标位置、颜色和其他选项。\nansi escape sequences - 在文本中嵌入确定的字节序列,大部分以 esc [ 字符开始,终端会把这些字节序列解释为相应的指令,而不是普通的字符编码。\n在 csi 中,参数方式 csi n m (如 \\e31m), 称为 sgr 。\nsgr sgr (select graphic rendition,选择图形再现),通过设置相应参数,以改变字符输出样式,如字体颜色、背景着色、粗体、斜体等等。\n其部分常用参数如下:\n代码 作用 备注 示例 0 重置/正常 关闭所有属性。 1 粗体或增加强度 2 弱化(降低强度) 未广泛支持。 3 斜体 未广泛支持。有时视为反相显示。 4 下划线 5 缓慢闪烁 低于每分钟 150 次。 6 快速闪烁 ms-dos ansi. sys;每分钟 150 以上;未广泛支持。 7 反显 前景色与背景色交换。 8 隐藏 未广泛支持。 9 划除 字符清晰,但标记为删除。未广泛支持。 10 主要(默认)字体 21 关闭粗体或双下划线 关闭粗体未广泛支持;双下划线几乎无支持。 22 正常颜色或强度 不强不弱。 23 非斜体、非尖角体 24 关闭下划线 去掉单双下划线。 25 关闭闪烁 27 关闭反显 28 关闭隐藏 29 关闭划除 30–37 设置前景色 参见下面的颜色表。 ![[assets/pasted image 20230907120819.png]] 38 设置前景色 下一个参数是 5;n 或 2;r;g;b ,见下。 39 默认前景色 由具体实现定义(按照标准)。 40–47 设置背景色 参见下面的颜色表。 48 设置背景色 下一个参数是 5;n 或 2;r;g;b ,见下。 49 默认背景色 由具体实现定义(按照标准)。 认真观察 1-9 和 21-29 ,它们一一对应,后者恰好是关闭前者的效果。下面让我们把注意力放在前景色和背景色上,看一下,它们是怎么分配的。\n初始的规格只有 8 种颜色,只给了它们的名字。sgr 参数 30-37 选择前景色,40-47 选择背景色。\n相当多的终端将“粗体”(sgr 代码 1)实现为更明亮的颜色而不是不同的字体,从而提供了 8 种额外的前景色,但通常情况下并不能用于背景色,虽然有时候反显(sgr 代码 7)可以允许这样。例如:在白色背景上显示黑色文字使用 esc[30;47m,显示红色文字用 esc[31m,显示明亮的红色文字用 esc[1;31m。重置为默认颜色用 esc[39;49m(某些终端不支持),重置所有属性用 esc[0m。后来的终端新增了功能,可以直接用 90-97 和 100-107 指定“明亮”的颜色。\n![[assets/pasted image 20230907152530.png]]\n\u0026gt; 3/4 位 8 色 - 黑、红、绿、黄、蓝、品红、青、白\n随着256色查找表在显卡上越来越常见,相应的转义序列也增加了,以从预定义的256种颜色中选择。总之,原理是一样的。如:\nbash 命令 输出预览 echo -e \u0026quot;\\e[38;5;183mhello world\\e[m\u0026quot; ![[assets/pasted image 20230907154854.png]] echo -e \u0026quot;\\e[48;5;125mhello world\\e[m\u0026quot; ![[assets/pasted image 20230907154936.png]] \\e[38;5;183m\n![[assets/pasted image 20230907154428.png]]\n\u0026gt; 8 位 256 色模式\n如果,你不想记这么多颜色号,但是恰好熟悉 rgb ,那么,你也可以使用它 (如 \\e[38;2;r;g;bm)来方便地设置颜色,如:\nbash 命令 输出预览 echo -e \u0026quot;\\e[38;2;255;1;1mhello world\\e[m\u0026quot; ![[assets/pasted image 20230907120819.png]] echo -e \u0026quot;\\e[48;2;255;1;1mhello world\\e[m\u0026quot; ![[assets/pasted image 20230907154252.png]] 到这里,你基本上已经可以轻松地实现实现你想输出的字符效果了。🎉\n参考 introduction \u0026amp; ansi escape codes - intellij idea guide ansi escape code - wikipedia ansi转义序列 - 维基百科,自由的百科全书 shell bash终端中输出的颜色和格式详解(超详细) shell echo 显示颜色 - 知乎 ","date":"2023-09-07","permalink":"https://aituyaa.com/%E7%BB%88%E7%AB%AF-terminal-%E5%AD%97%E7%AC%A6%E7%9A%84%E5%A4%9A%E5%BD%A9%E8%BE%93%E5%87%BA/","summary":"\u003cp\u003e最近在在改进站点的部署脚本,调试的时候有一些中间状态信息想要高亮显示,就需要对终端 Terminal 字符的输出样式进行修改,如何修改呢?往下看。\u003c/p\u003e","title":"终端 terminal 字符的多彩输出"},]
[{"content":"内容一览 封面 作者简介 数字版权声明 扉页 版权页 献词 译者序 序 前言 致谢 目录 第1章 什么是javascript 1.1 简短的历史回顾 1.2 javascript实现 1.3 javascript版本 1.4 小结 第2章 html中的javascript 2.1 \u0026lt;script\u0026gt;元素 2.2 行内代码与外部文件 2.3 文档模式 2.4 \u0026lt;noscript\u0026gt;元素 2.5 小结 第3章 语言基础 3.1 语法 3.2 关键字与保留字 3.3 变量 3.4 数据类型 3.5 操作符 3.6 语句 3.7 函数 3.8 小结 第4章 变量、作用域与内存 4.1 原始值与引用值 4.2 执行上下文与作用域 4.3 垃圾回收 4.4 小结 第5章 基本引用类型 5.1 date 5.2 regexp 5.3 原始值包装类型 5.4 单例内置对象 5.5 小结 第6章 集合引用类型 6.1 object 6.2 array 6.3 定型数组 6.4 map 6.5 weakmap 6.6 set 6.7 weakset 6.8 选代与扩展操作 6.9 小结 第7章 选代器与生成器 7.1 理解迭代 7.2 迭代器模式 7.3 生成器 7.4 小结 第8章 对象、类与面向对象编程 8.1 理解对象 8.2 创建对象 8.3 继承 8.4 类 8.5 小结 第9章 代理与反射 9.1 代理基础 9.2 代理捕获器与反射方法 9.3 代理模式 9.4 小结 第10章 函数 10.1 箭头函数 10.2 函数名 10.3 理解参数 10.4 没有重载 10.5 默认参数值 10.6 参数扩展与收集 10.7 函数声明与函数表达式 10.8 函数作为值 10.9 函数内部 10.10 函数属性与方法 10.11 函数表达式 10.12 递归 10.13 尾用优化 10.14 包 10.15 立即调用的函数表达式 10.16 私有变量 10.17 小结 第11章 期约与异步函数 11.1 异步编程 11.2 期约 11.3 异步函数 11.4 小结 第12章 bom 12.1 window对象 12.2 location对象 12.3 navigator对象 12.4 screen对象 12.5 history对象 12.6 小结 第13章 客户端检测 13.1 能力检测 13.2 用户代理检测 13.3 软件与硬件检测 13.4 小结 第14章 dom 14.1节点层级 14.2 dom编程 14.3 mutationobserver接口 14.4 小结 第15章 dom扩展 15.1 selectors api 15.2 元素遍历 15.3 html5 15.4 专有扩展 15.5 小结 第16章 dom2和dom3 16.1 dom的演进 16.2 样式 16.3 遍历 16.4 范围 16.5 小结 第17章 事件 17.1 事件流 17.2 事件处理程序 17.3 事件对象 17.4 事件类型 17.5 内存与性能 17.6 模拟事件 17.7 小结 第18章 动画与canvas图形 18.1 使用requestanimationframe 18.2 基本的画布功能 18.3 2d绘图上下文 18.4 webgl 18.5 小结 第19章 表单脚本 19.1 表单基础 19.2 文本框编程 19.3 选择框编程 19.4 表单序列化 19.5 富文本编辑 19.6 小结 第20章 javascript api 20.1 atomics与sharedarraybuffer 20.2 跨上下文消息 20.3 encoding api 20.4 file api与blob api 20.5 媒体元素 20.6 原生拖放 20.7 notifications api 20.8 page visibility api 20.9 streams api 20.10 计时api 20.11 web组件 20.12 web cryptography api 20.13 小结 第21章 错误处理与调试 21.1 浏览器错误报告 21.2 错误处理 21.3 调试技术 21.4 旧版ie的常见错误 21.5 小结 第22章处理xml 22.1 浏览器对xml dom的支持 22.2 浏览器对xpath的支持 22.3 浏览器对xslt的支持 22.4 小结 第23章 json 23.1 语法 23.2 解析与序列化 23.3 小结 第24章 网络请求与远程资源 24.1 xmlhttprequest对象 24.2 进度事件 24.3 跨域资源共享 24.4 替代性跨源技术 24.5 fetch api 24.6 beacon api 24.7 web socket 24.8 安全 24.9 小结 第25章 客户端存储 25.1 cookie 25.2 web storage 25.3 indexeddb 25.4 小结 第26章 模块 26.1 理解模块模式 26.2 凑合的模块系统 26.3 使用es6之前的模块加载器 26.4 使用es6模块 26.5 小结 第27章 工作者线程 27.1 工作者线程简介 27.2 专用工作者线程 27.3 共享工作者线程 27.4 服务工作者线程 27.5 小结 第28章 最佳实践 28.1 可维护性 28.2 性能 28.3 部署 28.4 小结 附录a es2018 和es2019 a.1 异步选代 a.2 对象字面量的剩余操作符和扩展操作符 a.3 promise.prototype.finally() a.4 正则表达式相关特性 a.5 数组打平方法 a.6 object.fromentries() a.7 字符串修理方法 a.8 symbolprototype.description a.9 可选的catch绑定 a.10 其他新增内容 附录b 严格模式 b.1 选择使用 b.2 变量 b.3 对象 b.4 函数 b.5 this强制转型 b.6 类与模块 b.7 其他变化 附录c javascript库和框架 c.1 框架 c.2 通用库 c.3 动画与特效 附录d javascript工具 d.1 包管理 d.2 模块加载器 d.3 模块打包器 d.4 编译/转译工具及静态类型系统 d.5 高性能脚本工具 d.6 编辑器 d.7 构建工具、自动化系统和任务运行器 d.8 代码检查和格式化 d.9 压缩工具 d.10 单元测试 d.11 文档生成器 dom 第14章 dom 14.1节点层级 14.2 dom编程 14.3 mutationobserver接口 14.4 小结 第15章 dom扩展 15.1 selectors api 15.2 元素遍历 15.3 html5 15.4 专有扩展 15.5 小结 第16章 dom2和dom3 16.1 dom的演进 16.2 样式 16.3 遍历 16.4 范围 16.5 小结 ","date":"2023-09-04","permalink":"https://aituyaa.com/js-%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E6%91%98%E5%BD%95/","summary":"内容一览 封面 作者简介 数字版权声明 扉页 版权页 献词 译者序 序 前言 致谢 目录 第1章 什么是JavaScript 1.1 简短的历史回顾 1.2 JavaScript实现 1.3 JavaScrip","title":"js 高级程序设计·摘录"},]
[{"content":"📘 loveminimal/obsidian-theme-virgo: a obsidian theme.\n更新日志 - 2024-04-12 15:29 重构主题,精简冗余代码 快速使用 后续你可以直接从 obsidian 的主题仓库中直接搜索 virgo 安装即可(上传中,待官方合并 限制太多,不允许上传自定义字体,不再上传官方主题仓库 😠 新版本将 已上传到官方主题仓库)。\n或者你可以手动操作(不推荐 😅), 如下:\n1 2 # 进行你的 obsidian 仓库根目录中的 .obsidian git clone https://github.com/loveminimal/obsidian-theme-virgo.git themes/virgo 之后在 obsidian “设置/外观”(settings \u0026gt; appearance)中选择该主题(virgo)即可。\n简介 为什么叒制作了一款 obsidian 主题呢?\n之前一直使用 mado-miniflow 这款主题,相当精美简约(当然,要是内置一款精美的字体就更完美了),当然现在也会偶尔切换使用它。它默认隐去了两侧即顶部的一些快捷操作按钮,当鼠标指针悬浮其上时才会显示,很好。\n![[assets/pasted image 20230904095324.png]]\n\u0026gt; mado miniflow - obsidian-theme-mado-miniflow\n本主题 virgo 借鉴了这一点,并更加“激进”,它连文件目录、大纲两个边栏都隐去了,以让页面主体内容更加突出。\n![[assets/pasted image 20230904095923.png]]\n\u0026gt; virgo - obsidian-theme-virgo\n还做了什么 所有动机都来源于让自己用的更爽 🤣 !\n自己制作主题的乐趣在于对代码可以全方位的掌握,以实现自己想要的效果,并且换设备使用的时候,可以方便地安装它。\n字体 另外一个主要原因就是 字体 !\n\u0026gt; 新版本内容\n新的版本(主分支)中, 我们引用了推荐的字体(落霞孤鹜)。你可以下载后,直接安装到本地,如此,你在别的应用中也可以使用这两款相当精美的字体。\n⏳ 下载链接: https://pan.baidu.com/s/1lcaxcovd5wjnxdllozcssa ,提取码: 6666\n本机安装还有一个好处,你可以在 obsidian 的设置中(settings \u0026gt; appearance \u0026gt; font )应用它,如此,你使用其他主题的时候,这款字体也是有效的。\n![[assets/pasted image 20240412154932.png]]\n💡 尽管放心,就算你不安装上述字体,我们也内置了一套替代字体(仓耳今楷)作为默认,也非常漂亮,详见后续内容。\n\u0026gt; 旧版本内容\n是的,我是一个绝对的‘字体颜粉’,windows 自带的字体很‘丑陋’(个人观感),这会影响写点东西的心情!后来发现 lxgw · 落霞与孤鹜齐飞,秋水共长天一色 这款字体,真的很‘沉浸’!但是它的 .ttf 整合下来有 16m ,系统本地安装没有问题,内置就……\nobsidian 的字体内置就很扯…… 它并不允许你引入外部的字体文件,可以是为了控制主题包的大小。通过 cdn 也不行,会跨域……\n了解字体资源引用限制,可以访问 theme guidelines - developer documentation。\n经过了一系列搜索之后,发现可以把字体文件用 base64 的格式引入,直接包含在主题样式文件中,完美。但是在转换字体格式的时候,又遇到了问题,字体文件太大了(不能超过 15m),这…… 好在我们有备选字体 - 仓耳今楷(大小 5m 左右),最初是在懒猫的博客中发现它的,中英文显示也相当不错。\n![[assets/pasted image 20230904104846.png|350]]\n英文呢,使用了 code new roman ,毕竟代码还是要用等宽字体好些,当然,这款英文字体也是相当不错的。\n顶栏 再就是 顶栏 了!\n显示器的纵向空间是很宝贵的,起码要节约!这里我把标签样和下面的标题栏合成了一个,并保留了所有的操作选项,仅移除了一个并没有什么作用的当前文件名称显示(本来就显示标题了)。\n……\n其他 调整表格宽度为 100% 。\n修改头部属性样式,隐藏 properties 文本显示,悬浮光标设置为“小手”指针。\n总结 对于‘强迫症’来说,制作主题是快乐的,毕竟可以显示成自己想要的样子,也是痛苦的,因为总想显示成自己想要的样子……\n","date":"2023-08-31","permalink":"https://aituyaa.com/%E4%B8%80%E6%AC%BE%E7%AE%80%E7%BA%A6%E7%9A%84-obsidian-%E4%B8%BB%E9%A2%98/","summary":"📘 loveminimal/obsidian-theme-virgo: A obsidian theme. 更新日志 - 2024-04-12 15:29 重构主题,精简冗余代码 快速使用 后续你可以直接从 Obsidian 的主题仓库中直接搜索 Virgo 安装即可(上传中,待官方合并 限制太多,不允许上传自定义字体,不再上","title":"一款简约的 obsidian 主题"},]
[{"content":"计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。虽然系统的具体实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件组件,它们又执行着相似的功能。一些程序员希望深人了解这些组件是如何工作的以及这些组件是如何影响程序的正确性和性能的,以此来提高自身的技能。本书便是为这些读者而写的。\n1 2 3 4 5 6 7 #include \u0026lt;stdio.h\u0026gt; int main() { printf(\u0026#34;hello, world\\n\u0026#34;); return 0; } \u0026gt; code/intro/hello.c\n尽管 hello 程序非常简单,但是为了让它实现运行,系统的每个主要组成部分都需要协调工作。从某种意义上来说,本书的目的就是要帮助你了解当你在系统上执行 hello 程序时,系统发生了什么以及为什么会这样。\n我们通过跟踪 hello 程序的生命周期来开始对系统的学习 ——从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止。\n1.1 信息就是‘位+上下文’ hello 程序的生命周期是从一个源程序(或者说源文件)开始的,即程序员通过编辑器创建并保存的文本文件,文件名是 hello.c。源程序实际上就是一个由值 0 和 1 组成的位(又称为比特)序列,8 个位被组织成一组,称为字节。每个字节表示程序中的某些文本字符。\n大部分的现代计算机系统都使用 ascii 标准来表示文本字符,这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符 。 比如,图 1 -2 中给出了 hello.c 程序的 ascii 码表示。\n![[assets/pasted image 20230830150257.png]]\nhello.c 程序是以字节序列的方式储存在文件中的。每个字节都有一个整数值,对应于某些字符。例如,第一个字节的整数值是 35, 它对应的就是字符 \u0026lsquo;#\u0026rsquo;。第二个字节的整数值为 105, 它对应的字符是 \u0026lsquo;i\u0026rsquo; 依此类推。注意,每个文本行都是以一个看不见的换行符 \u0026lsquo;\\n\u0026rsquo; 来结束的,它所对应的整数值为 10 。像 hello.c 这样只由 ascii 字符构成的文件称为文本文件,所有其他文件都称为二进制文件 。\n:: 注意,每个文本行都是以一个看不见的换行符 \u0026lsquo;\\n\u0026rsquo; 来结束的,它所对应的整数值为 10 。\n🌟 hello.c 的表示方法说明了一个基本思想:系统中所有的信息 - 包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。比如,在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。\n作为程序员,我们需要了解数字的机器表示方式,因为它们与实际的整数和实数是不同的。它们是对真值的有限近似值,有时候会有意想不到的行为表现。这方面的基本原理将在第 2 章中详细描述。\n:: 写的真是好,简单明了!信息 = 具体上下文下的位序列。\n1.2 程序被其他程序翻译成不同的格式 目录 第 1 章 计算机系统漫游\r1.1 信息就是位+上下文\r1.2 程序被其他程序翻译成不同的格式 1.3 了解编译系统如何工作是大有益处的 1.4 处理器读并解释储存在内存中的指令\r1.4.1 系统的硬件组成\r1.4.2 运行 hello 程序\r1.5 高速缓存至关重要\r1.6 储存设备形成层次结构\r1.7 操作系统管理硬件\r1.7.1 进程\r1.7.2 线程\r1.7.3 虚拟内存\r1.7.4 文件\r1.8 系统之间利用网络通信\r1.9 重要主题\r1.9.1 amdahl 定律\r1.9.2 并发和并行\r1.9.3 计算机系统中抽象的重要性\r1.10 小结\r参考答案、练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/01-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E6%BC%AB%E6%B8%B8/","summary":"\u003cp\u003e计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。虽然系统的具体实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件组件,它们又执行着相似的功能。一些程序员希望深人了解这些\u003cstrong\u003e组件是如何工作的\u003c/strong\u003e以及这些\u003cstrong\u003e组件是如何影响程序的正确性和性能的\u003c/strong\u003e,以此来提高自身的技能。本书便是为这些读者而写的。\u003c/p\u003e","title":"01 计算机系统漫游"},]
[{"content":"第一部分:程序结构和执行\r第 2 章 信息的表示和处理\r2.1 信息存储\r2.2 整数表示\r2.3 整数运算\r2.4 浮点数\r2.5 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/02-%E4%BF%A1%E6%81%AF%E7%9A%84%E8%A1%A8%E7%A4%BA%E5%92%8C%E5%A4%84%E7%90%86/","summary":"第一部分:程序结构和执行 第 2 章 信息的表示和处理 2.1 信息存储 2.2 整数表示 2.3 整数运算 2.4 浮点数 2.5 小结 参考文献说明 家庭作业 练习题答案","title":"02 信息的表示和处理"},]
[{"content":"第 3 章 程序的机器级表示\r3.1 历史观点\r3.2 程序编码\r3.3 数据格式\r3.4 访问信息\r3.5 算数和逻辑操作\r3.6 控制\r3.7 过程\r3.8 数组分配和访问\r3.9 异数的数据结构\r3.10 在机器级程序中将控制与数据结合起来\r3.11 浮点代码\r3.12 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/03-%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%9C%BA%E5%99%A8%E7%BA%A7%E8%A1%A8%E7%A4%BA/","summary":"第 3 章 程序的机器级表示 3.1 历史观点 3.2 程序编码 3.3 数据格式 3.4 访问信息 3.5 算数和逻辑操作 3.6 控制 3.7 过程 3.8 数组分配和访问 3.9 异数的数据结构 3.10 在机器级程序中将控制与数据结合起来 3.11","title":"03 程序的机器级表示"},]
[{"content":"第 4 章 处理器体系结构\r4.1 y86-64指令集体系结构\r4.2 辑设计和硬件控制语言hcl\r4.3 y86-64的顺序现实\r4.4 流水线的通用原理\r45 y86-64的流水线实现\r4.6 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/04-%E5%A4%84%E7%90%86%E5%99%A8%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/","summary":"第 4 章 处理器体系结构 4.1 Y86-64指令集体系结构 4.2 辑设计和硬件控制语言HCL 4.3 Y86-64的顺序现实 4.4 流水线的通用原理 45 Y86-64的流水线实现 4.6 小结 参考文献说","title":"04 处理器体系结构"},]
[{"content":"第 5 章 优化程序性能\r5.1 优化编译器的能力和局限性\r5.2 表示程序性能\r5.3 程序示例\r5.4 消除循环的低效率\r5.5 减少过程调用\r5.6 消除不必要的内存引用\r5.7 理解现代处理器\r5.8 循环展开\r5.9 提高并行性\r5.10 优化合并代码的结果小结\r5.11 一些限制因素\r5.12 理解内存性能\r5.13 应用:性能提高技术\r5.14 确认和消除性能瓶颈\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/05-%E4%BC%98%E5%8C%96%E7%A8%8B%E5%BA%8F%E6%80%A7%E8%83%BD/","summary":"第 5 章 优化程序性能 5.1 优化编译器的能力和局限性 5.2 表示程序性能 5.3 程序示例 5.4 消除循环的低效率 5.5 减少过程调用 5.6 消除不必要的内存引用 5.7 理解现代处理器 5.8 循环展开 5.9 提高并行性","title":"05 优化程序性能"},]
[{"content":"第 6 章 存储器层次结构\r6.1 存储技术\r6.2 局部性\r6.3 存储器层次结构\r6.4 高速缓存存储器\r6.5 编写高速缓存友好的代码\r6.6 综合:高速缓存对程序性能的影响\r6.7 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/06-%E5%AD%98%E5%82%A8%E5%99%A8%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84/","summary":"第 6 章 存储器层次结构 6.1 存储技术 6.2 局部性 6.3 存储器层次结构 6.4 高速缓存存储器 6.5 编写高速缓存友好的代码 6.6 综合:高速缓存对程序性能的影响 6.7 小结 参考文献说明 家庭作业 练习题答","title":"06 存储器层次结构"},]
[{"content":"第二部分 在系统上运行程序\r第 7 章 链接\r7.1 编译器驱动程序\r7.2 静态链接\r7.3目标文件\r7.4 可重定位目标文件\r7.5 符号和符号表\r7.6 符号解析\r7.7 重定位\r7.8 可执行目标文件\r7.9 加载可执行目标文件\r7.10 动态链接共享库\r7.11 从应用程序中加载和链接共享库\r7.12 位置无关代码\r7.13 库打桩机制\r7.14 处理目标文件的工具\r7.15 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/07-%E9%93%BE%E6%8E%A5/","summary":"第二部分 在系统上运行程序 第 7 章 链接 7.1 编译器驱动程序 7.2 静态链接 7.3目标文件 7.4 可重定位目标文件 7.5 符号和符号表 7.6 符号解析 7.7 重定位 7.8 可执行目标文件 7.9 加载可执行目标文件","title":"07 链接"},]
[{"content":"第 8 章 异常控制流\r8.1异常\r8.2 进程\r8.3 系统调用错误处理\r8.4 进程控制\r8.5 信号\r8.6 非本地跳转\r8.7 操作进程的工具\r8.8 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/08-%E5%BC%82%E5%B8%B8%E6%8E%A7%E5%88%B6%E6%B5%81/","summary":"第 8 章 异常控制流 8.1异常 8.2 进程 8.3 系统调用错误处理 8.4 进程控制 8.5 信号 8.6 非本地跳转 8.7 操作进程的工具 8.8 小结 参考文献说明 家庭作业 练习题答案","title":"08 异常控制流"},]
[{"content":"第 9 章 虚拟内存\r9.1 物理和虚拟寻址\r9.2 地址空间\r9.3 虚拟内存作为缓存的工具\r9.4 虚拟内存为内存管理的工具\r9.5 虚拟内存作为内存保护的工具\r9.6 地址翻译\r9.7 案例研究: intel core i7/linux内存系续\r9.8 内存映射\r9.9 动态内存分配\r9.10 垃圾收集\r9.11 c程序中常见的与内存有关的错误\r9.12 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/09-%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98/","summary":"第 9 章 虚拟内存 9.1 物理和虚拟寻址 9.2 地址空间 9.3 虚拟内存作为缓存的工具 9.4 虚拟内存为内存管理的工具 9.5 虚拟内存作为内存保护的工具 9.6 地址翻译 9.7 案例研究: Intel Core i7/Linux","title":"09 虚拟内存"},]
[{"content":"第三部分 程序间的交流和通信\r第 10 章 系统级 i/o\r10.1 unix i/0\r10.2 文件\r10.3 打开和关闭文件\r10.4 读和写文件\r10.5 用rio包健壮地读写\r10.6 读取文件元数据\r10.7 读取目录和内容\r10.8 共享文件\r10.9 i/0重定向\r10.10 标准i/0\r10.11 综合:我该使用哪些i/o函数?\r10.12 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/10-%E7%B3%BB%E7%BB%9F%E7%BA%A7-io/","summary":"第三部分 程序间的交流和通信 第 10 章 系统级 I/O 10.1 Unix I/0 10.2 文件 10.3 打开和关闭文件 10.4 读和写文件 10.5 用RIO包健壮地读写 10.6 读取文件元数据 10.7 读取目录和内容 10.8 共享文件 10.9 I/0重定向 10.10","title":"10 系统级 i/o"},]
[{"content":"第 11 章 网络编程\r11.1 客户端-服务器编程模型\r11.2 网络\r11.3 全球ip因特网\r11.4 套接字接口\r11.5 web服务器\r11.6 综合: tiny web服务器\r11.7 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/11-%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/","summary":"第 11 章 网络编程 11.1 客户端-服务器编程模型 11.2 网络 11.3 全球IP因特网 11.4 套接字接口 11.5 Web服务器 11.6 综合: TINY Web服务器 11.7 小结 参考文献说明 家庭作业 练习题答案","title":"11 网络编程"},]
[{"content":"第 12 章 并发编程\r12.1 基于进程的并发编程\r12.2 基于i/0多路复用的并发编程\r12.3 基于线程的并发编程\r12.4 多线程程序中的共享变量\r12.5 用信号量同步线程\r12.6 使用线程提高并行性\r12.7 其他并发问题\r12.8 小结\r参考文献说明\r家庭作业\r练习题答案 ","date":"2023-08-30","permalink":"https://aituyaa.com/csapp/12-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/","summary":"第 12 章 并发编程 12.1 基于进程的并发编程 12.2 基于I/0多路复用的并发编程 12.3 基于线程的并发编程 12.4 多线程程序中的共享变量 12.5 用信号量同步线程 12.6 使用线程提高并行性 12.7 其他并发问题","title":"12 并发编程"},]
[{"content":"","date":"2023-08-29","permalink":"https://aituyaa.com/%E6%95%B0%E6%8D%AE%E5%AF%86%E9%9B%86%E5%9E%8B%E5%BA%94%E7%94%A8%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/","summary":"","title":"数据密集型应用系统设计"},]
[{"content":"![[assets/pasted image 20230525153608.png|150]]\n系列章节 [[01 计算机系统漫游]] [[02 信息的表示和处理]] [[03 程序的机器级表示]] [[04 处理器体系结构]] [[05 优化程序性能]] [[06 存储器层次结构]] [[07 链接]] [[08 异常控制流]] [[09 虚拟内存]] [[10 系统级 io]] [[11 网络编程]] [[12 并发编程]] 前言 封面\r作者简介\r出版者的话\r中文版序一\r中文版序二\r译者序\r前言\r关于作者 ![[assets/pasted image 20230830092414.png|200]] ![[assets/pasted image 20230830092449.png|190]]\n\u0026gt; randal e. bryant \u0026amp; david r. o'hallaron\n如何阅读此书 从程序员的角度学习计算机系统是如何工作的会非常有趣,主要是因为你可以主动地做这件事情。无论何时你学到一些新的东西,都可以马上试验并且直接看到运行结果。事实上,我们相信学习系统的唯一方法就是做(do) 系统,即在真正的系统上解决具体的问题,或是编写和运行程序。\n练习题和家庭作业?\n:: 见鬼,真没想到有一天要主动找作业做…… 而且还是要认真完成的那种!\n\u0026gt; 难度等级\n符号 用时 描述 * 只需要几分钟 几乎或完全不需要编程 ** 可能需要将近 20 分钟 通常包括编写和测试一些代码 *** 需要很大的努力,也许是 1~2 个小时 一般包括编写和测试大量的代码 **** -个实验作业,需要将近 10 个小时 💡 所有的源程序代码都可以从 cs:app3e, bryant and o\u0026rsquo;hallaron 上的 cs: app 主页上获取。\n本书概述 本书由 12 章组成,旨在阐述计算机系统的核心概念。内容概述如下 :\n第 1 章:计算机系统漫符。这一章通过研究 \u0026ldquo;hello, world\u0026rdquo; 这个简单程序的生命周期,介绍计算机系统的主要概念和主题。\n\u0026gt; 第一部分:程序结构和执行 (2 - 6)\n笫 2 章 :信息的表示和处理。 我们讲述了计算机的算术运算,重点描述了会对程序员有影响的无符号数和数的补码表示的特性。我们考虑数字是如何表示的,以及由此确定对于一个给定的字长,其可能编码值的范围。我们探讨有符号和无符号数字之间类型转换的效果,还阐述算术运算的数学特性。菜鸟级程序员经常很惊奇地了解到(用补码表示的)两个正数的和或者积可能为负。另一方面,补码的算术运算满足很多整数运算的代数特性,因此,编译器可以很安全地把一个常量乘法转化为一系列的移位和加法。我们用 c 语言的位级操作来说明布尔代数的原理和应用。我们从两个方面讲述了 ieee 标准的浮点格式 : 一是如何用它来表示数值,一是浮点运算的数学属性。\n:: 无符号数?数的补码?算数运算?\n对计算机的算术运算有深刻的理解是写出可靠程序的关键。比如,程序员和编译器不能用表达式 (x - y \u0026lt; 0) 来替代 (x \u0026lt; y), 因为前者可能会产生溢出。甚至也不能用表达式 ( -y \u0026lt; -x ) 来替代,因为在补码表示中负数和正数的范围是不对称的。算术溢出是造成程序错误和安全漏洞的一个常见根源,然而很少有书从程序员的角度来讲述计算机算术运算的特性。\n第 3 章 :程序的机器级表示。 我们教读者如何阅读由 c 编译器生成的 x86-64 机器代码。我们说明为不同控制结构(比如条件、循环和开关语句)生成的基本指令模式。我们还讲述过程的实现,包括栈分配、寄存器使用惯例和参数传递。我们讨论不同数据结构(如结构、联合和数组)的分配和访问方式。我们还说明实现整数和浮点数算术运算的指令。我们还以分析程序在机器级的样子作为途径,来理解常见的代码安全漏洞(例如缓冲区溢出),以及理解程序员、编译器和操作系统可以采取的减轻这些威胁的措施。学习本章的概念能够帮助读者成为更好的程序员,因为你们懂得程序在机器上是如何表示的。另外一个好处就在于读者会对指针有非常全面而具体的理解。\n第 4 章 : 处理器体系结构。 这一章讲述基本的组合和时序逻辑元素,并展示这些元素如何在数据通路中组合到一起,来执行 x86-64 指令集的一个称为 \u0026ldquo;y86-64\u0026rdquo; 的简化子集。我们从设计单时钟周期数据通路开始。这个设计概念上非常简单,但是运行速度不会太快。然后我们引入流水线的思想,将处理一条指令所需要的不同步骤实现为独立的阶段。这个设计中,在任何时刻,每个阶段都可以处理不同的指令。我们的五阶段处理器流水线更加实用。本章中处理器设计的控制逻辑是用一种称为 hcl 的简单硬件描述语言来描述的。用 hcl 写的硬件设计能够编译和链接到本书提供的模拟器中,还可以根据这些设计生成 verilog 描述,它适合合成到实际可以运行的硬件上去。\n第 5 章:优化程序性能。 在这一章里,我们介绍了许多提高代码性能的技术,主要思想就是让程序员通过使编译器能够生成更有效的机器代码来学习编写 c 代码。我们一开始介绍的是减少程序需要做的工作的变换,这些是在任何机器上写任何程序时都应该遵循的。然后讲的是增加生成的机器代码中指令级并行度的变换,因而提高了程序在现代“超标量”处理器上的性能。为了解释这些变换行之有效的原理,我们介绍了一个简单的操作模型,它描述了现代乱序处理器是如何工作的,然后给出了如何根据一个程序的图形化表示中的关键路径来测量一个程序可能的性能。你会惊讶于对 c 代码做一些简单的变换能给程序带来多大的速度提升。\n第 6 章:存储器层次结构。 对应用程序员来说,存储器系统是计算机系统中最直接可见的部分之一。到目前为止,读者一直认同这样一个存储器系统概念模型,认为它是一个有一致访问时间的线性数组。实际上,存储器系统是一个由不同容量、造价和访问时间的存储设备组成的层次结构。我们讲述不同类型的随机存取存储器(ram) 和只读存储器(rom), 以及磁盘和固态硬盘的几何形状和组织构造。我们描述这些存储设备是如何放置在层次结构中的,讲述访问局部性是如何使这种层次结构成为可能的。我们通过一个独特的观点使这些理论具体化,那就是将存储器系统视为一个“存储器山\u0026quot;,山脊是时间局部性,而斜坡是空间局部性。最后,我们向读者阐述如何通过改善程序的时间局部性和空间局部性来提高应用程序的性能。\n\u0026gt; 第二部分:在系统上运行程序(7 - 9)\n第 7 章:链接。 本章讲述静态和动态链接,包括的概念有可重定位的和可执行的目标文件、符号解析、重定位、静态库、共享目标库、位置无关代码,以及库打桩 。大多数讲述系统的书中都不讲链接,我们要讲述它是出于以下原因。第一,程序员遇到的最令人迷惑的间题中,有一些和链接时的小故障有关,尤其是对那些大型软件包来说。第二,链接器生成的目标文件是与一些像加载、虚拟内存和内存映射这样的概念相关的。\n第 8 章:异常控制流。 在本书的这个部分,我们通过介绍异常控制流(即除正常分支和过程调用以外的控制流的变化)的一般概念,打破单一程序的模型。我们给出存在于系统所有层次的异常控制流的例子,从底层的硬件异常和中断,到并发进程的上下文切换,到由于接收 linux 信号引起的控制流突变,到 c 语言中破坏栈原则的非本地跳转。\n在这一章,我们介绍进程的基本概念,进程是对一个正在执行的程序的一种抽象。读者会学习进程是如何工作的,以及如何在应用程序中创建和操纵进程。我们会展示应用程序员如何通过 linux 系统调用来使用多个进程。学完本章之后,读者就能够编写带作业控制的 linux shell 了。同时,这里也会向读者初步展示程序的并发执行会引起不确定的行为。\n:: 要有自己的 shell 了,真好!\n笫 9 章 : 虚拟内存。 我们讲述虚拟内存系统是希望读者对它是如何工作的以及它的特性有所了解。我们想让读者了解为什么不同的并发进程各自都有一个完全相同的地址范围,能共享某些页,而又独占另外一些页。我们还讲了一些管理和操纵虚拟内存的问题。特别地,我们讨论了存储分配操作,就像标准库的 malloc 和 free 操作。阐述这些内容是出于下面几个目的。它加强了这样一个概念,那就是虚拟内存空间只是一个字节数组,程序可以把它划分成不同的存储单元。它可以帮助读者理解当程序包含存储泄漏和非法指针引用等内存引用错误时的后果。最后, 许多应用程序员编写自己的优化了的存储分配操作来满足应用程序的需要和特性 。 这一章比其他任何一章都更能展现将计算机系统中的硬件和软件结合起来阐述的优点。而传统的计算机体系结构和操作系统书籍都只讲述虚拟内存的某一方面 。\n\u0026gt; 第三部分:程序间的交流和通信\n笫 10 章:系统级 i / 0 。 我们讲述 unix i/0 的基本概念,例如文件和描述符。我们描述如何共享文件, i / 0 重定向是如何工作的,还有如何访问文件的元数据。我们还开发了一个健壮的带缓冲区的 i/0 包,可以正确处理一种称为 short counts 的奇特行为,也就是库函数只读取一部分的输入数据。我们阐述 c 的标准 1/0 库,以及它与 linux i/0 的关系,重点谈到标准 i/0 的局限性,这些局限性使之不适合网络编程。总的来说,本章的主题是后面两章 - 网络和并发编程的基础。\n笫 11 章:网络编程。 对编程而言,网络是非常有趣的 i/0 设备,它将许多我们前面文中学习的概念(比如进程、信号、字节顺序、内存映射和动态内存分配)联系在一起。网络程序还为下一章的主题 - 并发,提供了一个很令人信服的上下文。本章只是网络编程的一个很小的部分,使读者能够编写一个简单的 web 服务器。我们还讲述位于所有网络程序底层的客户端-服务器模型。我们展现了一个程序员对 internet 的观点,并且教读者如何用套接字接口来编写 internet 客户端和服务器。最后,我们介绍超文本传输协议 (http), 并开发了一个简单的迭代式 web 服务器。\n笫 12 章 : 并发编程。 这一章以 internet 服务器设计为例介绍了并发编程。我们比较对照了三种编写并发程序的基本机制(进程、 i/0 多路复用和线程),并且展示如何用它们来建造并发 internet 服务器。我们探讨了用 p 、 v 信号量操作来实现同步、线程安全和可重入、竞争条件以及死锁等的基本原则。对大多数服务器应用来说,写并发代码都是很关键的。我们还讲述了线程级编程的使用方法,用这种方法来表达应用程序中的并行性,使得程序在多核处理器上能执行得更快。使用所有的核解决同一个计算问题需要很小心谨慎地协调并发线程,既要保证正确性,又要争取获得高性能。\n经过课堂验证的实验练习 这门课非常有趣,令人兴奋:主要就是因为相关的实验练习。这些实验练习可以从 cs:app 的主页上获得。下面是本书提供的一些实验的示例。\n数据实验。 这个实验要求学生实现简单的逻辑和算术运算函数,但是只能使用一个非常有限的 c 语言子集。比如,只能用位级操作来计算一个数字的绝对值。这个实验可帮助学生了解 c 语言数据类型的位级表示,以及数据操作的位级行为。\n二进制炸弹实验。 二进制炸弹是一个作为目标代码文件提供给学生的程序。运行时,它提示用户输入 6 个不同的字符串。如果其中的任何一个不正确,炸弹就会“爆炸",打印出一条错误消息,并且在一个打分服务器上记录事件日志。学生必须通过对程序反汇编和逆向工程来测定应该是哪 6 个串,从而解除各自炸弹的雷管。该实验能教会学生理解汇编语言,并且强制他们学习怎样使用调试器。\n缓冲区溢出实验。 它要求学生通过利用一个缓冲区溢出漏洞,来修改一个二进制可执行文件的运行时行为。这个实验可教会学生栈的原理,并让他们了解写那种易于遭受缓冲区溢出攻击的代码的危险性。\n体系结构实验。 第 4 章的几个家庭作业能够组合成一个实验作业,在实验中,学生修改处理器的 hcl 描述,增加新的指令,修改分支预测策略,或者增加、删除旁路路径和寄存器端口。修改后的处理器能够被模拟,并通过运行自动化测试检测出大多数可能的错误。这个实验使学生能够体验处理器设计中令人激动的部分,而不需要掌握逻辑设计和硬件描述语言的完整知识。\n性能实验。 学生必须优化应用程序的核心函数(比如卷积积分或矩阵转置)的性能。这个实验可非常清晰地表明高速缓存的特性,并带给学生低级程序优化的经验。\ncache 实验。 这个实验类似于性能实验,学生编写一个通用高速缓存模拟器,并优化小型矩阵转置核心函数,以最小化对模拟的高速缓存的不命中次数。我们使用 valgrind 为矩阵转置核心函数生成真实的地址访问记录。\nshell 实验。 学生实现他们自己的带有作业控制的 unix shell 程序,包括 ctrl + c 和 ctrl+z 按键, fg 、 bg 和 jobs 命令。这是学生第一次接触并发,并且让他们对 unix 的进程控制、信号和信号处理有清晰的了解。\nmalloc 实验。 学生实现他们自己的 malloc 、 free 和 realloc ( 可选)版本。这个实验可让学生们清晰地理解数据的布局和组织,并且要求他们评估时间和空间效率的各种权衡及折中。\n代理实验。 实现一个位于浏览器和万维网其他部分之间的并行 web 代理。这个实验向学生们揭示了 web 客户端和服务器这样的主题,并且把课程中的许多概念联系起来,比如字节排序、文件 i/ 0 、进程控制、信号、信号处理、内存映射、套接字和并发。学生很高兴能够看到他们的程序在真实的 web 浏览器和 web 服务器之间起到的作用。\n💡 cs: app 的教师手册中有对实验的详细讨论,还有关于下载支待软件的说明。\n目录 \u0026gt; 好长的目录,好厚的书……\n封面\r作者简介\r出版者的话\r中文版序一\r中文版序二\r译者序言\r关于作者\r目录\r第 1 章 计算机系统漫游\r1.1 信息就是位+上下文\r1.2 程序被其他程序翻译成不同的格式 1.3 了解编译系统如何工作是大有益处的 1.4 处理器读并解释储存在内存中的指令\r1.5 高速缓存至关重要\r1.6 储存设备形成层次结构\r1.7 操作系统管理硬件\r1.8 系统之间利用网络通信\r1.9 要主\r1.10 小结\r参考答案、练习题答案\r第一部分:程序结构和执行\r第 2 章 信息的表示和处理\r2.1 信息存储\r2.2 整数表示\r2.3 整数运算\r2.4 浮点数\r2.5 小结\r参考文献说明\r家庭作业\r练习题答案\r第 3 章 程序的机器级表示\r3.1 历史观点\r3.2 程序编码\r3.3 数据格式\r3.4 访问信息\r3.5 算数和逻辑操作\r3.6 控制\r3.7 过程\r3.8 数组分配和访问\r3.9 异数的数据结构\r3.10 在机器级程序中将控制与数据结合起来\r3.11 浮点代码\r3.12 小结\r参考文献说明\r家庭作业\r练习题答案\r第 4 章 处理器体系结构\r4.1 y86-64指令集体系结构\r4.2 辑设计和硬件控制语言hcl\r4.3 y86-64的顺序现实\r4.4 流水线的通用原理\r45 y86-64的流水线实现\r4.6 小结\r参考文献说明\r家庭作业\r练习题答案\r第 5 章 优化程序性能\r5.1 优化编译器的能力和局限性\r5.2 表示程序性能\r5.3 程序示例\r5.4 消除循环的低效率\r5.5 减少过程调用\r5.6 消除不必要的内存引用\r5.7 理解现代处理器\r5.8 循环展开\r5.9 提高并行性\r5.10 优化合并代码的结果小结\r5.11 一些限制因素\r5.12 理解内存性能\r5.13 应用:性能提高技术\r5.14 确认和消除性能瓶颈\r参考文献说明\r家庭作业\r练习题答案\r第 6 章 存储器层次结构\r6.1 存储技术\r6.2 局部性\r6.3 存储器层次结构\r6.4 高速缓存存储器\r6.5 编写高速缓存友好的代码\r6.6 综合:高速缓存对程序性能的影响\r6.7 小结\r参考文献说明\r家庭作业\r练习题答案\r第二部分 在系统上运行程序\r第 7 章 链接\r7.1 编译器驱动程序\r7.2 静态链接\r7.3目标文件\r7.4 可重定位目标文件\r7.5 符号和符号表\r7.6 符号解析\r7.7 重定位\r7.8 可执行目标文件\r7.9 加载可执行目标文件\r7.10 动态链接共享库\r7.11 从应用程序中加载和链接共享库\r7.12 位置无关代码\r7.13 库打桩机制\r7.14 处理目标文件的工具\r7.15 小结\r参考文献说明\r家庭作业\r练习题答案\r第 8 章 异常控制流\r8.1异常\r8.2 进程\r8.3 系统调用错误处理\r8.4 进程控制\r8.5 信号\r8.6 非本地跳转\r8.7 操作进程的工具\r8.8 小结\r参考文献说明\r家庭作业\r练习题答案\r第 9 章 虚拟内存\r9.1 物理和虚拟寻址\r9.2 地址空间\r9.3 虚拟内存作为缓存的工具\r9.4 虚拟内存为内存管理的工具\r9.5 虚拟内存作为内存保护的工具\r9.6 地址翻译\r9.7 案例研究: intel core i7/linux内存系续\r9.8 内存映射\r9.9 动态内存分配\r9.10 垃圾收集\r9.11 c程序中常见的与内存有关的错误\r9.12 小结\r参考文献说明\r家庭作业\r练习题答案\r第三部分 程序间的交流和通信\r第 10 章 系统级 i/o\r10.1 unix i/0\r10.2 文件\r10.3 打开和关闭文件\r10.4 读和写文件\r10.5 用rio包健壮地读写\r10.6 读取文件元数据\r10.7 读取目录和内容\r10.8 共享文件\r10.9 i/0重定向\r10.10 标准i/0\r10.11 综合:我该使用哪些i/o函数?\r10.12 小结\r参考文献说明\r家庭作业\r练习题答案\r第 11 章 网络编程\r11.1 客户端-服务器编程模型\r11.2 网络\r11.3 全球ip因特网\r11.4 套接字接口\r11.5 web服务器\r11.6 综合: tiny web服务器\r11.7 小结\r参考文献说明\r家庭作业\r练习题答案\r第 12 章 并发编程\r12.1 基于进程的并发编程\r12.2 基于i/0多路复用的并发编程\r12.3 基于线程的并发编程\r12.4 多线程程序中的共享变量\r12.5 用信号量同步线程\r12.6 使用线程提高并行性\r12.7 其他并发问题\r12.8 小结\r参考文献说明\r家庭作业\r练习题答案\r附录 a 错误处理\r参考文献\r推荐阅读\r如何使用本书\r封底 ","date":"2023-08-29","permalink":"https://aituyaa.com/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/","summary":"\u003cp\u003e![[assets/Pasted image 20230525153608.png|150]]\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e系列章节\u003c/th\u003e\n\u003cth\u003e\u003c/th\u003e\n\u003cth\u003e\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e[[01 计算机系统漫游]]\u003c/td\u003e\n\u003ctd\u003e[[02 信息的表示和处理]]\u003c/td\u003e\n\u003ctd\u003e[[03 程序的机器级表示]]\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e[[04 处理器体系结构]]\u003c/td\u003e\n\u003ctd\u003e[[05 优化程序性能]]\u003c/td\u003e\n\u003ctd\u003e[[06 存储器层次结构]]\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e[[07 链接]]\u003c/td\u003e\n\u003ctd\u003e[[08 异常控制流]]\u003c/td\u003e\n\u003ctd\u003e[[09 虚拟内存]]\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e[[10 系统级 IO]]\u003c/td\u003e\n\u003ctd\u003e[[11 网络编程]]\u003c/td\u003e\n\u003ctd\u003e[[12 并发编程]]\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e","title":"深入理解计算机系统"},]
[{"content":"费曼学习法的灵感源于诺贝尔物理奖获得者理查德•费曼(richard feynman),运用费曼技巧, 你只需花上 20 分钟就能深入理解知识点,而且记忆深刻,难以遗忘。\n知识有两种类型,我们绝大多数人关注的都是错误的那类。第一类知识注重了解某个事物的名称,第二类知识注重了解某件事物,这可不是一回事儿。著名的诺贝尔物理学家理查德·费曼(richard feynman)能够理解这二者间的差别,这也是他成功最重要的原因之一。事实上,他创造了一种学习方法,确保他会比别人对事物了解的更透彻。\n:: 去了解事物的本质!\n费曼学习法可以简化为四个单词:concept (概念)、teach (教给别人)、review (评价)、simplify (简化)。\n![[assets/pasted image 20230721141638.png|250]]\n费曼学习法的核心是 - 当你准备学习一门新知识时,必须站在传授者的立场,假设自己要向别人讲解这门知识。那么,你一定要用最简洁、清晰和易于理解的语言表达出来,才能让行外的人也能听懂。费曼说:“最好是几岁的小孩也能明白你在说什么。”\n:: 只有只够了解,才会游刃有余。以教促学,深入浅出。\n为此,他制定了一个简单易行的流程:\n第一,确立你要学习的目标\r第二,理解你要学习的对象\r第三,以教代学,用输出代替输入\r第四,进行回顾和反思\r第五,实现知识的简化和吸收 第一,确立你要学习的目标。 找到和列出自己想要了解的知识,可以是一本书,也可以是一门技术,甚至是你能想象到的任意领域和事物。\n第二,理解你要学习的对象。 针对这个目标,准备好和筛选相关的资料,选择可靠和多个角度的信息来源,把这些内容系统化地归纳整理出来。\n第三,以教代学,用输出代替输入。 模拟一个传授的场景,用自己的语言把这些知识讲给别人,用以检查自己是否已经掌握了这些知识。\n:: 这一步是关键,‘以教代学’的实质上就是强化在对于学习对象本质的深化理解。\n第四,进行回顾和反思。 对其中遇到阻碍、模糊不清和有疑义的知识重新学习、回顾和反思。如有必要,可以重整旗鼓,进行再一次输出。\n第五,实现知识的简化和吸收。 最后,通过针对性的简化和整合,实现这些知识的内化和有效的应用。\n通过这一个步骤,我们很容易便能获得最高的内容留在率,加强学习的效果,让自己顺利达到学习和输出知识的目的。\n学习和应用是互为一体的,无法对外输出的学习就不能称为学习。学习费曼的思路,是要让我们拥有出色的应用知识的能力,能够输出知识,创造知识,并用知识改造世界。\n:: 认识世界,改造世界。\n:: 总结一下,就是确定和理解你要学习的对象,用一种容易理解的方式描述出来,反复思考描述的过程,进一步总结、提炼、内化。\n","date":"2023-07-21","permalink":"https://aituyaa.com/%E8%B4%B9%E6%9B%BC%E5%AD%A6%E4%B9%A0%E6%B3%95/","summary":"\u003cp\u003e费曼学习法的灵感源于诺贝尔物理奖获得者理查德•费曼(Richard Feynman),运用费曼技巧, 你只需花上 20 分钟就能深入理解知识点,而且记忆深刻,难以遗忘。\u003c/p\u003e\n\u003cp\u003e知识有两种类型,我们绝大多数人关注的都是错误的那类。第一类知识注重了解某个事物的名称,第二类知识注重了解某件事物,这可不是一回事儿。著名的诺贝尔物理学家理查德·费曼(Richard Feynman)能够理解这二者间的差别,这也是他成功最重要的原因之一。事实上,他创造了一种学习方法,确保他会比别人对事物了解的更透彻。\u003c/p\u003e","title":"费曼学习法"},]
[{"content":" 实体社保卡 居住证 生育建档 前湾社康 南方医科大学深圳医院前湾社区健康服务站\n二、三类医保用不了?前湾社康解君愁!\n基本医疗二、三档参保人变更医疗机构的,当月变更,次月生效。\n社康位置: 前海深港创新商务中心d组团304-306 社康工作时间: 周一至周五9:00-12:00 14:00-18:00(国家法定节假日除外) 社康电话: 0755-88986052 ![[assets/pasted image 20230719094722.png]]\n生育建档 医院选择 深圳市宝安区中医院(宝安区 30 区裕安二路 25 号,产科停诊,问下什么时候开诊) 深圳市宝安区人民医院(宝安区新安街道龙井二路 118 号,创业二路 4 号) \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;- 南方医科大学深圳医院(宝安区新湖路 1333 号) 深圳市宝安区中心医院(宝安区西乡街道乐园街 60 号) 深圳市宝安区妇幼保健院(宝安中心区玉律路 56 号) 深圳恒生医院(宝安区西乡街道碧海中心区银田路 20 号) 从35家医院里胜出🉐前5名生产医院❗\n南方医科大学深圳医院产科怎么样 - 小红书搜索 南方医科大学深圳医院好吗?来看孕妈怎么说 ⭐ 南方医科大学深圳医院产科yyds 一篇说清南方医科大学深圳医院生孩子那些事\n南方医科大学深圳医院建档攻略 南医大❗️非深户❗️建档+生育险全攻略\n好像南方医科大学深圳医院也不错,主要是顺地铁。\n南方医科大学深圳医院生育建档 - 小红书搜索\n![[assets/pasted image 20230616180130.png]]\n注意事项 社区生育登记证明(现在一般是电子版的)要 3 个工作日左右才会生效,生效之后才能用生育保险。 非深户宝安中医院建档\n盐田西乡街道 29615829 宝安区西乡街道盐田股份综合楼七楼\n案例 1 终于建档啦,来分享下热乎的宝中医建档经验 案例 2 深圳市宝安区中医院产科建档流程攻略分享 实体社保卡 金融社保卡申请\n提交制卡资料后,什么时候可以领到金融社保卡?\n我市金融社保卡的制卡周期限定为 12 个工作日。\n参保人的金融社保卡已激活,就医时收费窗口却告知“无此信息”,应如何引导?\n此问题可能出自银行网点工作人员操作不当没有同步激活社保区域功能导致无法使用,可以在核对其社保卡激活记录后引导参保人前往发卡银行任意网点进行激活确认。\n参保人未设置过使用密码,但就医时却告知需输入密码,应如何处理?\n在办卡银行自助终端设置过使用密码的,回办卡银行自助终端重置使用密码; 在社保局征收窗口设置使用密码,回社保局征收窗口重置使用密码。 💡 更多了解,查询 金融社保卡申请 。\n农业银行\n![[assets/pasted image 20230617093645.png|400]]\n","date":"2023-06-14","permalink":"https://aituyaa.com/docs/%E7%94%9F%E8%82%B2%E5%BB%BA%E6%A1%A3%E8%B5%84%E6%96%99/","summary":"\u003cul\u003e\n\u003cli\u003e\u003cinput disabled=\"\" type=\"checkbox\"\u003e 实体社保卡\u003c/li\u003e\n\u003cli\u003e\u003cinput checked=\"\" disabled=\"\" type=\"checkbox\"\u003e 居住证\u003c/li\u003e\n\u003cli\u003e\u003cinput checked=\"\" disabled=\"\" type=\"checkbox\"\u003e 生育建档\u003c/li\u003e\n\u003c/ul\u003e","title":"生育建档资料"},]
[{"content":"中文dota 2 wiki - 刀塔百科\n","date":"2023-06-10","permalink":"https://aituyaa.com/%E6%B8%B8%E6%88%8F/","summary":"\u003cp\u003e\u003ca href=\"https://dota2.fandom.com/zh/wiki/Dota_2_Wiki\"\u003e中文Dota 2 Wiki - 刀塔百科\u003c/a\u003e\u003c/p\u003e","title":"游戏"},]
[{"content":"微信小程序开发的一些记录……\n相关参考 框架文档 说明 备注 微信官方文档 微信开放文档 tdesign tdesign ![[assets/pasted image 20230608144221.png|102]] vant 介绍 - vant weapp ![[assets/pasted image 20230608144251.png|134]] taro taro 文档、 taro 文档 ![[assets/pasted image 20230608144347.png|95]] uniapp uni-app官网 ![[assets/pasted image 20230608144441.png|148]] 后两种都是可以生成多平台的框架,并且让你可以使用 vue 或 react 来编写。\n简介 这里只是一些并不系统的摘录,你总可以在 微信开放文档 里获取更多详细的信息。\n小程序的技术发展史\njs api (如 weixinjsbridage) \u0026gt; js-sdk \u0026gt; 微信 web 资源离线存储 (提升移动网页体验) \u0026gt; 小程序\n:: todo 扩展了解 webview 相关的知识\n小程序与普通网页开发的区别\n网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。\n网页开发者可以使用到各种浏览器暴露出来的 dom api,进行 dom 选中和操作。小程序的逻辑层和渲染层是分开的,逻辑层运行在 jscore 中,并没有一个完整浏览器对象,因而缺少相关的 dom api 和 bom api。这一区别导致了前端开发非常熟悉的一些库,例如 jquery、 zepto 等,在小程序中是无法运行的。同时 jscore 的环境同 nodejs 环境也是不尽相同,所以一些 npm 的包在小程序中也是无法运行的。\n运行环境 逻辑层 渲染层 ios javascriptcore wkwebview 安卓 v8 chromium 定制内核 小程序开发者工具 nwjs chrome webview \u0026gt; 小程序的运行环境\n小程序代码构成\n小程序主要有以下四种格式的文件组成:\n.json 后缀的 json 配置文件 .wxml 后缀的 wxml 模板文件 .wxss 后缀的 wxss 样式文件 .js 后缀的 js 脚本逻辑文件 小程序的宿主环境\n我们称微信客户端给小程序所提供的环境为宿主环境。\n小程序的运行环境分成渲染层和逻辑层,其中 wxml 模板和 wxss 样式工作在渲染层,js 脚本工作在逻辑层。它们分别由 2 个线程管理:渲染层的界面使用了 webview 进行渲染;逻辑层采用 jscore 线程运行 js 脚本。\n一个小程序存在多个界面,所以渲染层存在多个 webview 线程,这两个线程的通信会经由微信客户端(下文中也会采用 native 来代指微信客户端)做中转,逻辑层发送网络请求也经由 native 转发,小程序的通信模型下图所示。\n![[assets/pasted image 20230609100400.png]]\n微信客户端在打开小程序之前,会把整个小程序的代码包下载到本地。\n紧接着通过 app.json 的 pages 字段就可以知道你当前小程序的所有页面路径,继而微信客户端会把首页的代码装载进来,通过小程序底层的一些机制,就可以渲染出这个首页。\n小程序启动之后,在 app.js 定义的 app 实例的 onlaunch 回调会被执行。整个小程序只有一个 app 实例,是全部页面共享的。\n那么,页面是如何装载、渲染的呢?\n微信客户端会先根据 page.json 配置生成一个界面,顶部的颜色和文字你都可以在这个 json 文件里边定义好。紧接着客户端就会装载这个页面的 wxml 结构和 wxss 样式,最后客户端会装载 page.js (一个 page 页面构造器)。\npage 是一个页面构造器,这个构造器就生成了一个页面。在生成页面的时候,小程序框架会把 data 数据和 index.wxml 一起渲染出最终的结构,于是就得到了你看到的小程序的样子。\n在渲染完界面之后,页面实例就会收到一个 onload 的回调,你可以在这个回调处理你的逻辑。\n组件 \u0026amp; api\n小程序提供了丰富的基础组件给开发者,使用组件的时候,还可以通过属性传递值给组件,让组件可以以不同的状态去展现,组件的内部行为也会通过事件的形式让开发者可以感知。\n为了让开发者可以很方便的调起微信提供的能力,例如获取用户信息、微信支付等等,小程序提供了很多 api 给开发者去使用。需要注意的是:多数 api 的回调都是异步,你需要处理好代码逻辑的异步问题。\n目录结构 小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。\n一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:\n文件 必需 作用 app.js 是 小程序逻辑 app.json 是 小程序公共配置 app.wxss 否 小程序公共样式表 一个小程序页面由四个文件组成,分别是:\n文件类型 必需 作用 js 是 页面逻辑 wxml 是 页面结构 json 否 页面配置 wxss 否 页面样式表 注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。\n配置小程序 全局配置\n小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。\n页面配置\n每一个小程序页面也可以使用同名 .json 文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json 的 window 中相同的配置项。\nsitemap 配置\n微信现已开放小程序内搜索,开发者可以通过 sitemap.json 配置,或者管理后台页面收录开关来配置其小程序页面是否允许微信索引。当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。\n……\n小程序框架 小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 app 体验的服务。\n整个小程序框架系统分为两部分: 逻辑层(app service)和视图层(view)。\n小程序提供了自己的视图层描述语言 wxml 和 wxss,以及基于 javascript 的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,让开发者能够专注于数据与逻辑。\n:: 所有的 mvvm 系统基本上都是这个原理。\n框架的核心是一个响应的数据绑定系统,可以让数据与视图非常简单地保持同步。\n框架管理了整个小程序的页面路由,可以做到页面间的无缝切换,并给以页面完整的生命周期。开发者需要做的只是将页面的数据、方法、生命周期函数注册到框架中,其他的一切复杂的操作都交由框架处理。\n框架提供了一套基础组件,这些组件自带微信风格的样式以及特殊的逻辑,开发者可以通过组合基础组件,创建出强大的微信小程序。还提供丰富的微信原生 api,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。\n逻辑层 小程序开发框架的逻辑层使用 javascript 引擎为小程序提供开发 javascript 代码的运行环境以及微信小程序的特有功能。\n逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。\n💡 开发者写的所有代码最终将会打包成一份 javascript 文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似 serviceworker,所以逻辑层也称之为 app service。\n在 javascript 的基础上,我们增加了一些功能,以方便小程序的开发:\n增加 app 和 page 方法,进行程序注册和页面注册。 增加 getapp 和 getcurrentpages 方法,分别用来获取 app 实例和当前页面栈。 提供丰富的 api,如微信用户数据,扫一扫,支付等微信特有能力。 提供模块化能力,每个页面有独立的作用域。 注意:小程序框架的逻辑层并非运行在浏览器中,因此 javascript 在 web 中一些能力都无法使用,如 window,document 等。\n如何注册页面呢?\n使用 page 构造器注册页面 使用 component 构造器构造页面 page 构造器适用于简单的页面。但对于复杂的页面, page 构造器可能并不好用。此时,可以使用 component 构造器来构造页面。 component 构造器的主要区别是:方法需要放在 methods: { } 里面。\n在页面中使用 behaviors\n页面可以引用 behaviors , behaviors 可以用来让多个页面有相同的数据字段和方法。\n:: 类似 mixin 机制\n页面 page 实例的生命周期\n![[assets/pasted image 20230609111716.png|550]]\n页面路由\n在小程序中所有页面的路由全部由框架进行管理,框架以页面栈的形式维护了当前的所有页面,开发者可以使用 getcurrentpages() 函数获取当前页面栈。\n:: 对于路由的触发方式以及页面生命周期函数,有一些小技巧可以注意一下,即:\n所有的页面出现在前台时都会触发 onshow 只有新页面入栈时(页面创建),路由后页面才会触发 onload 页面出栈, 路由前页面就会触发 onunload 只有打开新页面时,路由前页面才会触发 onhide 注意事项:\nnavigateto, redirectto 只能打开非 tabbar 页面。 switchtab 只能打开 tabbar 页面。 relaunch 可以打开任意页面。 页面底部的 tabbar 由页面决定,即只要是定义为 tabbar 的页面,底部都有 tabbar。 调用页面路由带的参数可以在目标页面的 onload 中获取。 模块化?\n可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。模块只有通过 module.exports 或者 exports 才能对外暴露接口。\n在需要使用这些模块的文件中,使用 require 将公共代码引入。\n:: 对,就是 commonjs 那一套。\napi ?\n事件监听 api 同步 api 异步 api 事件监听 api。我们约定,以 on 开头的 api 用来监听某个事件是否触发。这类 api 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。\n同步 api 。我们约定,以 sync 结尾的 api 都是同步 api。\n异步 api 。大多数 api 都是异步 api,这类 api 接口通常都接受一个 object 类型的参数,这个参数都支持按需指定以下字段来接收接口调用结果。异步 api 的执行结果需要通过 object 类型的参数中传入的对应回调函数获取。部分异步 api 也会有返回值,可以用来实现更丰富的功能。\n异步 api 可以返回 promise 吗?\n基础库 2.10.2 版本起,异步 api 支持 callback \u0026amp; promise 两种调用方式。当接口参数 object 对象中不包含 success/fail/complete 时将默认返回 promise,否则仍按回调方式执行,无返回值。\n注意:部分接口如 downloadfile, request, uploadfile, connectsocket, createcamera(小游戏)本身就有返回值,它们的 promisify 需要开发者自行封装。\n1 2 3 4 5 6 7 8 9 10 // callback 形式调用 wx.chooseimage({ success(res) { console.log(\u0026#39;res:\u0026#39;, res) } }) // promise 形式调用 wx.chooseimage().then(res =\u0026gt; console.log(\u0026#39;res: \u0026#39;, res)) // 也可使用 async \u0026amp; await 小程序支持去开发 api 了 ?\n是的(收费 💸),开通并使用微信云开发,即可使用云开发 api,在小程序端直接调用服务端的云函数。\n视图层 框架的视图层由 wxml 与 wxss 编写,由组件来进行展示,如:\nwxml (weixin markup language) 用于描述页面的结构。 wxs (weixin script) 是小程序的一套脚本语言,结合 wxml,可以构建出页面的结构。 wxss (weixin style sheet) 用于描述页面的样式。 将逻辑层的数据反映成视图,同时将视图层的事件发送给逻辑层。\n💡 组件 (component)是视图的基本组成单元。\n:: 微信小程序提供的这套框架,最终还是要打包成一个 js 文件,这和 vue、react 及像 webpack、 rollup 这类打包工具是一样的。这也是,为什么现在有许多不使用微信小程序框架开发,可以使用相应的 vue、 react 语法来开发的原因了,如 taro , uniapp 等。\nwxml 是什么?\nwxml(weixin markup language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。\n具体的能力以及使用方式在以下章节查看:\n数据绑定、列表渲染、条件渲染、模板、引用\nwxss 是什么?\nwxss (weixin style sheets)是一套样式语言,用于描述 wxml 的组件样式。\n为了适应广大的前端开发者,wxss 具有 css 大部分特性。同时为了更适合开发微信小程序,wxss 对 css 进行了扩充以及修改。\n与 css 相比,wxss 扩展的特性有:\n尺寸单位 样式导入 尺寸单位 rpx(responsive pixel) ,可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx。如在 iphone6 上,屏幕宽度为 375px,共有 750 个物理像素,则 750rpx = 375px = 750 物理像素,1rpx = 0.5px = 1 物理像素。\n样式导入,使用 @import 语句可以导入外联样式表,@import 后跟需要导入的外联样式表的相对路径,用 ; 表示语句结束。\n框架组件上支持使用 style (动态样式)、 class (静态样式) 属性来控制组件的样式。\nwxs ? ? ?\nwxs(weixin script)是小程序的一套脚本语言,结合 wxml,可以构建出页面的结构。\nwxs 与 javascript 是不同的语言,有自己的语法,并不和 javascript 一致。wxs 的运行环境和其他 javascript 代码是隔离的,wxs 中不能调用其他 javascript 文件中定义的函数,也不能调用小程序提供的 api。\n\u0026lt;!--wxml--\u0026gt;\r\u0026lt;wxs module=\u0026#34;m1\u0026#34;\u0026gt;\rvar msg = \u0026#34;hello world\u0026#34;;\rmodule.exports.message = msg;\r\u0026lt;/wxs\u0026gt;\r\u0026lt;view\u0026gt; {{m1.message}} \u0026lt;/view\u0026gt; :: 怎么说呢?是不是很像 \u0026lt;script\u0026gt; ? 又有点类似于 computed 。\n事件系统 什么是事件?\n事件是视图层到逻辑层的通讯方式; 事件可以将用户的行为反馈到逻辑层进行处理; 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数; 事件对象可以携带额外信息,如 id, dataset, touches。 也可以使用 wxs 函数响应事件,但不常用。\n事件分为冒泡事件和非冒泡事件:\n冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。 有哪些冒泡事件呢?\n类型 触发条件 最低版本 touchstart 手指触摸动作开始 touchmove 手指触摸后移动 touchcancel 手指触摸动作被打断,如来电提醒,弹窗 touchend 手指触摸动作结束 tap 手指触摸后马上离开 longpress 手指触摸后,超过 350ms 再离开,如果指定了事件回调函数并触发了这个事件,tap 事件将不被触发 1.5.0 longtap 手指触摸后,超过 350ms 再离开(推荐使用 longpress 事件代替) transitionend 会在 wxss transition 或 wx. createanimation 动画结束后触发 animationstart 会在一个 wxss animation 动画开始时触发 animationiteration 会在一个 wxss animation 一次迭代结束时触发 animationend 会在一个 wxss animation 动画完成时触发 touchforcechange 在支持 3d touch 的 iphone 设备,重按时会触发 1.9.90 注:除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如 form 的 submit 事件,input 的 input 事件,scroll-view 的 scroll 事件。(详见各个组件)\n如何进行事件绑定呢?\n事件绑定的写法类似于组件的属性,如:\n\u0026lt;!-- 点击 view ,页面的 handletap 会被调用 --\u0026gt;\r\u0026lt;view bindtap=\u0026#34;handletap\u0026#34;\u0026gt;\rclick here!\r\u0026lt;/view\u0026gt; 事件绑定函数可以是一个数据绑定(并不推荐),如:\n\u0026lt;view bindtap=\u0026#34;{{ handlername }}\u0026#34;\u0026gt;\rclick here!\r\u0026lt;/view\u0026gt; 此时,页面的 this.data.handlername 必须是一个字符串,指定事件处理函数名;如果它是个空字符串,则这个绑定会失效(可以利用这个特性来暂时禁用一些事件)。\n自基础库版本 1.5.0 起,在大多数组件和自定义组件中, bind 后可以紧跟一个冒号,其含义不变,如 bind: tap 。基础库版本 2.8.1 起,在所有组件中开始提供这个支持。\n:: 这更新有什么用?一开始就应该用冒号!\n绑定并阻止事件冒泡\n除 bind 外,也可以用 catch 来绑定事件。与 bind 不同, catch 会阻止事件向上冒泡。\n自基础库版本 2.8.2 起,除 bind 和 catch 外,还可以使用 mut-bind 来绑定事件。一个 mut-bind 触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind 绑定函数不会被触发,但 bind 绑定函数和 catch 绑定函数依旧会被触发。\n事件的捕获阶段?\n捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用 capture-bind、capture-catch 关键字,后者将中断捕获阶段和取消冒泡阶段。\n如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。这个事件对象是什么样的呢?\n⭐ 事件对象\nbaseevent 基础事件对象属性列表:\n属性 类型 说明 基础库版本 type string 事件类型 timestamp integer 事件生成时的时间戳 target object 触发事件的组件的一些属性值集合 currenttarget object 当前组件的一些属性值集合 mark object 事件标记数据 2.7.1 customevent 自定义事件对象属性列表(继承 baseevent):\n属性 类型 说明 detail object 额外的信息 自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息,详见组件定义中各个事件的定义。\n点击事件的 detail 带有的 x, y 同 pagex, pagey 代表距离文档左上角的距离。\ntouchevent 触摸事件对象属性列表(继承 baseevent):\n属性 类型 说明 touches array 触摸事件,当前停留在屏幕中的触摸点信息的数组 changedtouches array 触摸事件,当前变化的触摸点信息的数组 特殊事件: canvas 中的触摸事件不可冒泡,所以没有 currenttarget。\n这里也有 target 和 currenttarget ,它们有什么不同呢?\n\u0026lt;view id=\u0026#34;outer\u0026#34; bindtap=\u0026#34;handletap1\u0026#34;\u0026gt;\router view\r\u0026lt;view id=\u0026#34;middle\u0026#34; catchtap=\u0026#34;handletap2\u0026#34;\u0026gt;\rmiddle view\r\u0026lt;view id=\u0026#34;inner\u0026#34; bindtap=\u0026#34;handletap3\u0026#34;\u0026gt;\rinner view\r\u0026lt;/view\u0026gt;\r\u0026lt;/view\u0026gt;\r\u0026lt;/view\u0026gt; 说明: target 和 currenttarget 可以参考上例中,点击 inner view 时,handletap3 收到的事件对象 target 和 currenttarget 都是 inner,而 handletap2 收到的事件对象 target 就是 inner,currenttarget 就是 middle。\n原来, target 指的是触发事件的源组件;而 currenttarget 是指事件绑定的当前组件。\n组件 属性 类型 说明 target id string 事件源组件的 id dataset object 事件源组件上由 data- 开头的自定义属性组成的集合 \u0026mdash; \u0026mdash; \u0026mdash; \u0026mdash; currenttarget id string 当前组件的 id dataset object 当前组件上由 data- 开头的自定义属性组成的集合 在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。\n在 wxml 中,这些自定义数据以 data- 开头,多个单词由连字符 - 连接。这种写法中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。\n:: 基本上和 html 的一样的。\nmark 又是个啥?\n在基础库版本 2.7.1 以上,可以使用 mark 来识别具体触发事件的 target 节点。此外, mark 还可以用于承载一些自定义数据(类似于 dataset )。\n当事件触发时,事件冒泡路径上所有的 mark 会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark 。)\n\u0026lt;view mark:mymark=\u0026#34;last\u0026#34; bindtap=\u0026#34;bindviewtap\u0026#34;\u0026gt;\r\u0026lt;button mark:anothermark=\u0026#34;leaf\u0026#34; bindtap=\u0026#34;bindbuttontap\u0026#34;\u0026gt;按钮\u0026lt;/button\u0026gt;\r\u0026lt;/view\u0026gt; 在上述 wxml 中,如果按钮被点击,将触发 bindviewtap 和 bindbuttontap 两个事件,事件携带的 event.mark 将包含 mymark 和 anothermark 两项。\n1 2 3 4 5 6 page({ bindviewtap: function(e) { e.mark.mymark === \u0026#34;last\u0026#34; // true e.mark.anothermark === \u0026#34;leaf\u0026#34; // true } }) mark 和 dataset 很相似,主要区别在于: mark 会包含从触发事件的节点到根节点上所有的 mark: 属性值;而 dataset 仅包含一个节点的 data- 属性值。\ntouches \u0026amp; changedtouches\ntouches 是一个数组,每个元素为一个 touch 对象(canvas 触摸事件中携带的 touches 是 canvastouch 数组),表示当前停留在屏幕上的触摸点。\n属性 类型 说明 identifier number 触摸点的标识符 pagex, pagey number 距离文档左上角的距离,文档的左上角为原点,横向为 x 轴,纵向为 y 轴 clientx, clienty number 距离页面可显示区域(屏幕除去导航条)左上角距离,横向为 x 轴,纵向为 y 轴 \u0026gt; touch 对象\n属性 类型 说明 特殊说明 identifier number 触摸点的标识符 x, y number 距离 canvas 左上角的距离,canvas 的左上角为原点,横向为 x 轴,纵向为 y 轴 \u0026gt; canvastouch 对象\nchangedtouches 数据格式同 touches ,表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)。\n为什么需要 wxs 响应事件?\n有频繁用户交互的效果在小程序上表现是比较卡顿的!\nwxs响应事件 | 微信开放文档\n……\n简易双向绑定 在 wxml 中,普通的属性的绑定是单向的。例如:\n\u0026lt;input value=\u0026#34;{{value}}\u0026#34; /\u0026gt; 如果使用 this.setdata({ value: 'leaf' }) 来更新 value ,this.data.value 和输入框的中显示的值都会被更新为 leaf ;但如果用户修改了输入框里的值,却不会同时改变 this.data.value 。\n如果需要在用户输入的同时改变 this.data.value 呢?借助简易双向绑定机制。\n此时,可以在对应项目之前加入 model: 前缀:\n\u0026lt;input model:value=\u0026#34;{{value}}\u0026#34; /\u0026gt; 这样,如果输入框的值被改变了, this.data.value 也会同时改变。同时, wxml 中所有绑定了 value 的位置也会被一同更新, 数据监听器 也会被正常触发。\n如果想在自定义组件中传递双向绑定呢?一样的。\n基础组件 什么是组件?\n组件是视图层的基本组成单元。 组件自带一些功能与微信风格一致的样式。 一个组件通常包括 开始标签 和 结束标签,属性 用来修饰这个组件,内容 在两个标签之内。 注意:所有组件与属性都是小写,以连字符 - 连接。\n所有组件都有以下属性:\n属性名 类型 描述 注解 id string 组件的唯一标示 保持整个页面唯一 class string 组件的样式类 在对应的 wxss 中定义的样式类 style string 组件的内联样式 可以动态设置的内联样式 hidden boolean 组件是否显示 所有组件默认显示 data-* any 自定义属性 组件上触发的事件时,会发送给事件处理函数 bind* catch* eventhandler 组件的事件 详见事件 几乎所有组件都有各自定义的属性,可以对该组件的功能或样式进行修饰,请参考各个组件的定义。\n获取界面上的节点信息 获取界面上的节点信息 | 微信开放文档\n……\n后续还有一些章节,这里先不做讨论。\n小程序运行时 微信小程序运行在多种平台上:ios/ipados 微信客户端、android 微信客户端、windows pc 微信客户端、mac 微信客户端、小程序硬件框架和用于调试的微信开发者工具等。\n不同运行环境下,脚本执行环境以及用于组件渲染的环境是不同的,性能表现也存在差异。\n运行机制 小程序的生命周期\n小程序从启动到最终被销毁,会经历很多不同的状态,小程序在不同状态下会有不同的表现。\n![[assets/pasted image 20230609160107.png]]\n1. 小程序的启动\n从用户认知的角度看,广义的小程序启动可以分为两种情况,一种是冷启动,一种是热启动。\n冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。 热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。 从小程序生命周期的角度来看,我们一般讲的「启动」专指冷启动,热启动一般被称为后台切前台。\n2. 前台与后台\n小程序启动后,界面被展示给用户,此时小程序处于「前台」状态。\n当用户「关闭」小程序时,小程序并没有真正被关闭,而是进入了「后台」状态,此时小程序还可以短暂运行一小段时间,但部分 api 的使用会受到限制。\n3. 挂起\n小程序进入「后台」状态一段时间后(目前是 5 秒),微信会停止小程序 js 线程的执行,小程序进入「挂起」状态。此时小程序的内存状态会被保留,但开发者代码执行会停止,事件和接口回调会在小程序再次进入「前台」时触发。\n当开发者使用了后台音乐播放、后台地理位置等能力时,小程序可以在「后台」持续运行,不会进入到「挂起」状态。\n4. 小程序销毁\n如果用户很久没有使用小程序,或者系统资源紧张,小程序会被「销毁」,即完全终止运行。\n5. 退出状态\n小程序冷启动时,如果启动时不带 path(a 类场景),默认情况下将会进入小程序的首页。在页面对应的 json 文件中(也可以全局配置在 app. json 的 window 段中),指定 restartstrategy 配置项可以改变这个默认的行为,使得从某个页面退出后,下次 a 类场景的冷启动可以回到这个页面。\n可选值 含义 homepage (默认值)如果从这个页面退出小程序,下次将从首页冷启动 homepageandlatestpage 如果从这个页面退出小程序,下次冷启动后立刻加载这个页面,页面的参数保持不变(不可用于 tab 页) \u0026gt; restartstrategy 可选值\n无论如何,页面中的状态并不会被保留,如输入框中的文本内容、 checkbox 的勾选状态等都不会还原。如果需要还原或部分还原,需要利用退出状态。\n每当小程序可能被销毁之前,页面回调函数 onsaveexitstate 会被调用。如果想保留页面中的状态,可以在这个回调函数中“保存”一些数据,下次启动时可以通过 exitstate 获得这些已保存数据。\nonsaveexitstate 返回值可以包含两项:\n字段名 类型 含义 data any 需要保存的数据(只能是 json 兼容的数据) expiretimestamp number 超时时刻,在这个时刻后,保存的数据保证一定被丢弃,默认为 (当前时刻 + 1 天) 更新机制 开发者在管理后台发布新版本的小程序之后,微信客户端会有若干个时机去检查本地缓存的小程序有没有新版本,并进行小程序的代码包更新。但如果用户本地有小程序的历史版本,此时打开的可能还是旧版本。\n……\n注意: 开发者在后台发布新版本之后,无法立刻影响到所有现网用户,但最差情况下,也在发布之后 24 小时之内覆盖绝大多数用户。\n自定义组件 开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。\n如何创建一个自定义组件呢?\n类似于页面,一个自定义组件由 json wxml wxss js 4 个文件组成。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可将这一组文件设为自定义组件):\n1 2 3 { \u0026#34;component\u0026#34;: true } 同时,还要在 wxml 文件中编写组件模板,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似。\n注意:在组件 wxss 中不应使用 id 选择器、属性选择器和标签名选择器。\n在自定义组件的 js 文件中,需要使用 component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。\n组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。\n如何创使用自定义组件呢?\n使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:\n1 2 3 4 5 { \u0026#34;usingcomponents\u0026#34;: { \u0026#34;component-tag-name\u0026#34;: \u0026#34;path/to/the/custom/component\u0026#34; } } 这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。\n组件模板和样式 组件模板的写法与页面模板相同。组件模板与组件数据结合后生成的节点树,将被插入到组件的引用位置上。\n与普通的 wxml 模板类似,可以使用数据绑定,这样就可以向子组件的属性传递动态数据。\n在组件模板中可以提供一个 \u0026lt;slot\u0026gt; 节点,用于承载组件引用时提供的子节点。默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。\n1 2 3 4 5 6 7 component({ options: { multipleslots: true // 在组件定义时的选项中启用多slot支持 }, properties: { /* ... */ }, methods: { /* ... */ } }) 此时,可以在这个组件的 wxml 中使用多个 slot ,以不同的 name 来区分。\n关于组件样式的及样式隔离相关方面的知识,我们先不讨论。\ncomponent 构造器 component 构造器可用于定义组件,调用 component 构造器时可以指定组件的属性、数据、方法等。\n事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用 component 构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应 json 文件中包含 usingcomponents 定义段。\n此时,组件的属性可以用于接收页面的参数,如访问页面 /pages/index/index?parama=123\u0026amp;paramb=xyz ,如果声明有属性 parama 或 paramb ,则它们会被赋值为 123 或 xyz 。\n页面的生命周期方法(即 on 开头的方法),应写在 methods 定义段中。\n💡 使用 component 构造器来构造页面的一个好处是可以使用 behaviors 来提取所有页面中公用的代码段。\n组件间通信与事件 组件间的基本通信方式有以下几种:\nwxml 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 json 兼容数据; 事件:用于子组件向父组件传递数据,可以传递任意数据; 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectcomponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。 监听事件\n事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。\n监听自定义组件事件的方法与监听基础组件事件的方法完全一致。\n触发事件\n自定义组件触发事件时,需要使用 triggerevent 方法,指定事件名、detail 对象和事件选项。\n获取组件实例\n可在父组件里调用 this.selectcomponent ,获取子组件的实例对象。\n若需要自定义 selectcomponent 返回的数据,可使用内置 behavior: wx://component-export 。\n组件生命周期 组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。\n其中,最重要的生命周期是 created attached detached ,包含一个组件实例生命流程的最主要时间点。\n组件实例刚刚被创建好时, created 生命周期被触发。此时,组件数据 this.data 就是在 component 构造器中定义的数据 data 。 此时还不能调用 setdata 。 通常情况下,这个生命周期只应该用于给组件 this 添加一些自定义属性字段。 在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。 在组件离开页面节点树后, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 会被触发。 在 behaviors 中也可以编写生命周期方法,同时不会与其他 behaviors 中的同名生命周期相互覆盖。但要注意,如果一个组件多次直接或间接引用同一个 behavior ,这个 behavior 中的生命周期函数在一个执行时机内只会执行一次。\n可用的全部生命周期如下表所示:\n生命周期 参数 描述 最低版本 created 无 在组件实例刚刚被创建时执行 1.6.3 attached 无 在组件实例进入页面节点树时执行 1.6.3 ready 无 在组件在视图层布局完成后执行 1.6.3 moved 无 在组件实例被移动到节点树另一个位置时执行 1.6.3 detached 无 在组件实例被从页面节点树移除时执行 1.6.3 error object error 每当组件方法抛出错误时执行 2.4.1 还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pagelifetimes 定义段中定义。\n生命周期 参数 描述 最低版本 show 无 组件所在的页面被展示时执行 2.2.3 hide 无 组件所在的页面被隐藏时执行 2.2.3 resize object size 组件所在的页面尺寸变化时执行 2.4.0 routedone 无 组件所在页面路由动画完成时执行 2.31.2 注意:自定义 tabbar 的 pagelifetime 不会触发。\nbehaviors behaviors 是用于组件间代码共享的特性,类似于一些编程语言中的 “mixins” 或 “traits”。\n每个 behavior 可以包含一组属性、数据、生命周期函数和方法。组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。 每个组件可以引用多个 behavior ,behavior 也可以引用其它 behavior 。\n自定义组件可以通过引用内置的 behavior 来获得内置组件的一些行为。\n组件间关系 ……\n数据监听器 数据监听器(observers)可以用于监听和响应任何属性和数据字段的变化。\n纯数据字段 纯数据字段是一些不用于界面渲染的 data 字段,可以用于提升页面更新性能。\n有些情况下,某些 data 中的字段(包括 setdata 设置的字段)既不会展示在界面上,也不会传递给其他组件,仅仅在当前组件内部使用。\n此时,可以指定这样的数据字段为“纯数据字段”,它们将仅仅被记录在 this.data 中,而不参与任何界面渲染过程,这样有助于提升页面更新性能。\n指定“纯数据字段”的方法是在 component 构造器的 options 定义段中指定 puredatapattern 为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段。\n抽象节点 有时,自定义组件模板中的一些节点,其对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的。这时可以把这个节点声明为“抽象节点”。\n例如,我们现在来实现一个“选框组”(selectable-group)组件,它其中可以放置单选框(custom-radio)或者复选框(custom-checkbox)。\n自定义组件扩展 为了更好定制自定义组件的功能,可以使用自定义组件扩展机制。\n所谓‘自定义组件的扩展’其实就是提供了修改自定义组件定义段的能力。behavior() 构造器提供了新的定义段 definitionfilter ,用于支持自定义组件扩展。\n开发第三方自定义组件 ……\n占位组件 基础库尝试渲染一个组件时,会首先递归检查 usingcomponents,收集其将使用到的所有组件的信息;在这个过程中,如果某个被使用到的组件不可用,基础库会先检查其是否有对应的占位组件。如果没有,基础库会中断渲染并抛出错误;如果有,则会标记并在后续渲染流程中使用占位组件替换该不可用的组件进行渲染。不可用的组件会在当前渲染流程结束后尝试准备(下载分包或注入代码等);等到准备过程完成后,再尝试渲染该组件(实际上也是在执行这个流程),并替换掉之前渲染的占位组件。\n查看自定义组件数据 wxml 面板中可以查看自定义组件在渲染时的 data 数据。在 wxml 中先选中需要查看的自定义组件,然后切换到 component data 即可实时查看当前自定义组件的数据。\n基础能力 网络 在小程序/小游戏中使用网络相关的 api 时,需要注意下列问题,请开发者提前了解。\n存储 同一个微信用户,同一个小程序 storage 上限为 10mb。\n文件系统 文件系统是小程序提供的一套以小程序和用户维度隔离的存储以及一套相应的管理接口。通过 wx.getfilesystemmanager() 可以获取到全局唯一的文件系统管理器,所有文件系统的管理操作通过 filesystemmanager 来调用。\n文件主要分为两大类:\n代码包文件:代码包文件指的是在项目目录中添加的文件。 本地文件:通过调用接口本地产生,或通过网络下载下来,存储到本地的文件。 其中本地文件又分为三种:\n本地临时文件:临时产生,随时会被回收的文件。运行时最多存储 4gb,结束运行后,如果已使用超过 2gb,会以文件为维度按照最近使用时间从远到近进行清理至少于 2gb。 本地缓存文件:小程序通过接口把本地临时文件缓存后产生的文件,不能自定义目录和文件名。跟本地用户文件共计,小程序(含小游戏)最多可存储 200mb。 本地用户文件:小程序通过接口把本地临时文件缓存后产生的文件,允许自定义目录和文件名。跟本地缓存文件共计,小程序(含小游戏)最多可存储 200mb。 代码包文件\n代码包文件的访问方式是从项目根目录开始写文件路径,不支持相对路径的写法。如:/a/b/c、a/b/c 都是合法的,./a/b/c ../a/b/c 则不合法。\n本地文件\n本地文件指的是小程序被用户添加到手机后,会有一块独立的文件存储区域,以用户维度隔离。即同一台手机,每个微信用户不能访问到其他登录用户的文件,同一个用户不同 appid 之间的文件也不能互相访问。\ncanvas canvas 组件 提供了绘制界面,可以在之上进行任意绘制。\n分包加载 某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。\n在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/tabbar 页面,以及一些所有分包都需用到公共资源/js 脚本;而分包则是根据开发者的配置进行划分。\n在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。\n前小程序分包大小有以下限制:\n整个小程序所有分包大小不超过 20m 单个分包/主包大小不能超过 2m 使用分包\n开发者通过在 app. json subpackages 字段声明项目分包结构。\n:: 这个对于稍微大点的项目,是很必要的。\n独立分包\n独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。\n一个小程序中可以有多个独立分包。\n开发者通过在 app.json 的 subpackages 字段中对应的分包配置项中定义 independent 字段声明对应分包为独立分包。\n分包预下载\n开发者可以通过配置,在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度。对于独立分包,也可以预下载主包。\n分包预下载目前只支持通过配置方式使用,暂不支持通过调用 api 完成。\n预下载分包行为在进入某个页面时触发,通过在 app.json 增加 preloadrule 配置来控制。\n分包异步化\n跨分包自定义组件引用 ,一个分包使用其他分包的自定义组件时,由于其他分包还未下载或注入,其他分包的组件处于不可用的状态。通过为其他分包的自定义组件设置 占位组件,我们可以先渲染占位组件作为替代,在分包下载完成后再进行替换。\n跨分包 js 代码引用,一个分包中的代码引用其它分包的代码时,为了不让下载阻塞代码运行,我们需要异步获取引用的结果。\n按需注入和用时注入 ……\n","date":"2023-06-08","permalink":"https://aituyaa.com/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/","summary":"\u003cp\u003e微信小程序开发的一些记录……\u003c/p\u003e","title":"微信小程序"},]
[{"content":"📙 转载自 “积久而成学”\u0026ndash;党史频道-人民网\n毛泽东是伟大的革命家、战略家和理论家,也是一位通晓古今、学贯中西的学者。他一生与书相伴,在长期的读书生涯中,形成了一套科学合理、独具特色的读书观。通过研究和学习毛泽东读书观,对加快学习型政党建设、深化“四史”学习教育以及促进个人自我发展都具有十分重要的意义和价值。\n读书先立志 生命不息,读书不止。毛泽东的一生读书范围十分广泛,涉及政治、经济、军事、文学、历史、艺术、哲学、地理、科学等各个方面。但是,他读书并不是漫无目的、随意而为,而是有着十分明确的目标和方向的。\n一是为国为民,探求真理。在少年时期,毛泽东在去湘乡东山高等小学堂读书前,就写过一首诗——“孩儿立志出乡关,学不成名誓不还”,送给他的父亲,来表达自己矢志求学、报效祖国的远大志向。他还曾给自己取了一个别号——“子任”,以示其“以天下兴亡为己任”的理想抱负,并将“为人,为国人,为世界人而学”作为自己读书的目的。\n二是立足实践,学以致用。他曾明确指出,精通书本知识的目的,“全在于应用”,要将读书学习和解决中国的实际问题结合起来。在新民主主义革命时期,毛泽东向全党明确提出了“马克思主义中国化”的重大理论命题。他强调对于中国共产党来说,“就是要学会把马克思列宁主义的理论应用于中国的具体的环境”,“使马克思主义在中国具体化,使之在其每一表现中带着必须有的中国的特性”,实现了将马克思主义普遍真理与中国具体革命实际相结合的伟大飞跃,极大地促进了中国革命的胜利。\n读书贵有恒 读书学习是一条艰辛而漫长之路,不是一朝一夕就能完成的。所以,读书人必须要静得下心、耐得住寂寞、坐得住冷板凳,循序渐进、持之以恒。毛泽东在长沙读书之时就曾写过一副自勉联——“贵有恒,何必三更起五更眠;最无益,只怕一日曝十日寒”,来劝诫自己要坚持读书,绝不懈怠。\n要谦虚,不能自满。 毛泽东指出:“学习的敌人是自己的满足,要认真学习一点东西,必须从不自满开始。”一个人如果自满、自大,就会停止学习,逐渐落后于别人,落伍于社会。只有虚心好学,刻苦钻研,才能聚沙成塔,不断进步。古人云:吾生也有涯,而知也无涯。知识的天空是无边无际的,学习求知是永无止境的。在中国共产党第八次全国代表大会开幕词中,毛泽东再次强调了要谦虚谨慎、不能自满自大的重要性。他指出,中国作为一个农业大国,在迈向先进工业国的过程中,我们经验是有限的,要善于向苏联等先进国家以及向世界各国学习。因此,大到一个国家、一个社会,小到一个团体、一个个人,都必须保持虚怀若谷、勤奋好学的良好态度,时刻牢记“谦虚使人进步,骄傲使人落后”的金玉良言。\n:: “虚怀乃若谷,水深则流缓。”\n要有“挤”和“钻”的精神。在读书的过程中,我们经常会遇到“工作太忙,没时间读书”“书太难了,看不懂”等实际问题。如何解决“忙”和“难”的问题呢?1939 年 5 月,在延安在职干部教育动员大会上的讲话中,毛泽东就特别针对这个问题发表了自己的见解。他指出,在忙的中间,想一个法子,叫做“挤”,用“挤”来对付忙……在每天工作、吃饭、休息中间,挤出两小时来学习,把工作向两方面挤一挤,一个往上一个往下,一定可以挤出两小时来学习的。再一个问题是看不懂……看不懂也有一个办法,叫做“钻”,如木匠钻木头一样地“钻”进去。看不懂的东西我们不要怕,就用“钻”来对付。\n要有“活到老”“学到老”的勇气。毛泽东认为,读书学习无止境,要坚持活到老、学到老。在延安时期,经常有同志以年龄大为理由,不愿意读书。毛泽东严肃地指出了这种观点的错误,认为“人到五十五,才是出山虎”,真正的学问是要在“无期大学”里点滴积累起来的。同时,号召大家都要到“无期大学”里面读书学习。1972 年,在书房会见日本首相田中角荣时,毛泽东就曾言及读书早已与其生活融为一体,成为其生活的重要组成部分。1975 年,因眼睛患病,他还专门请一位大学老师帮他念书。1976 年 9 月 8 日,在他逝世的前一天,全身插满管子,神志时而清醒,时而昏迷,但是只要是在清醒时,他就开始阅读书籍和文件,真正做到了生命不息、读书不止。\n读书须得法 读书方法是学习者理解书本内容、获取书本知识、实现读书目的的方式、手段和途径。毛泽东十分重视读书方法和策略的运用,他曾将方法比喻成“桥”与“船”,他指出:“不解决桥或船的问题,过河就是一句空话。不解决方法问题,任务也只是瞎说一顿。”\n广采博览。毛泽东一生,博览群书,学遍天下。关于广采博览主要包括两层含义:一是在读书的范围上,要不限领域广泛涉猎。他曾说,一个人的知识面要宽一些,“有了学问,好比站在山上,可以看到很远很多东西。没有学问,如在暗沟里走路,摸索不着,那会苦煞人”。正是秉持这种读书理念,他广泛阅读,不断丰富和更新自己的知识库存,为形成自己的思想体系奠定了坚实的理论根基。二是在读书的次数上,经典之书要反复温读。正所谓“书读百遍,其义自见”。对于经典书籍,尤其是马列著作,毛泽东是常读常新。在延安,他曾讲道,《共产党宣言》,我看了不下一百遍,遇到问题,我就翻阅马克思的《共产党宣言》,有时只阅读一两段,有时全篇都读,每阅读一次,我都有新的启发。\n善于思考。学而不思则罔,思而不学则殆。毛泽东读书非常善于思考,他主张要将书本内容和现实社会相联系,将理论和实践结合起来。同时,对于书中的内容和观点要有自己的思考和看法,要有创造性的发挥,而非“尽信书”。概括起来,主要包括两个方面,一是当好“联系员”。毛泽东喜欢读史,尤其是《二十四史》,是他读了一生的书。他强调:学习我们的历史遗产,要用马克思主义的方法给以批判的总结,吸收其精华,剔除其糟粕。学习历史是为了以古鉴今,汲取历史经验教训,为革命和建设服务。二是做好“评论员”。他读《聊斋志异》时,认为蒲松龄十分注重调查研究,所以才能写出如此丰富的鬼怪离奇故事;在读《老子》时,他说“祸兮福之所倚,福兮祸之所伏”蕴含着辩证法的思想,告诫人们要用全面的观点看待问题,既要看到正面,又要看到反面;在读到《水浒传》中的“三打祝家庄”的章节时,他说宋江之所以能够获胜,是因为和扈家庄建立了统一战线。\n勤动笔墨。古人云:“思读十遍,不如写一遍。”善于读书者,总是书不离手,手不离笔,手眼并用。毛泽东的老师徐特立有一条重要的治学经验“不动笔墨不读书”。这个读书方法对毛泽东的影响十分巨大,他一生都在践行和坚持。一是内容摘录。在求学时期,毛泽东嗜书如命,在读到好的句子、好的段落、好的文章之时,他总会饶有兴趣地将其抄录下来。在延安时期,毛泽东在读艾思奇的《哲学与生活》之时,受益良多,特意用毛笔抄录了 4000 多字的内容。二是符号标记。毛泽东喜欢在读书的时候标记各种符号,如问号、三角、斜线、方框、单杠、双杠、波浪线等等,每个符号都代表不同的意思,反映了他当时阅读时的思想状态。如早年时期阅读的《韩昌黎全集》《伦理学原理》,延安时期攻读的马列著作,都做了各种符号标记。三是文字批注。毛泽东的批注既有对原文的总结、提炼、质疑和评价,也有自己在读书过程中的心得体会和独特见解等创新性发展。在读《辩证法唯物论教程》时,他写下了 1 万多字的文字批注。针对书中的“矛盾的主要方面”这个概念,他结合我国当时抗战的实际情况,对抗战时期我国存在的主要矛盾以及矛盾的主要方面进行了创造性发挥。\n","date":"2023-05-30","permalink":"https://aituyaa.com/%E7%A7%AF%E4%B9%85%E8%80%8C%E6%88%90%E5%AD%A6/","summary":"\u003cp\u003e📙 转载自 \u003ca href=\"http://dangshi.people.com.cn/n1/2020/1111/c85037-31926429.html\"\u003e“积久而成学”\u0026ndash;党史频道-人民网\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e毛泽东是伟大的革命家、战略家和理论家,也是一位通晓古今、学贯中西的学者。他一生与书相伴,在长期的读书生涯中,形成了一套科学合理、独具特色的读书观。通过研究和学习毛泽东读书观,对加快学习型政党建设、深化“四史”学习教育以及促进个人自我发展都具有十分重要的意义和价值。\u003c/p\u003e","title":"积久而成学"},]
[{"content":"i.e. early computing\nhello world!我是 carrie anne,欢迎收看计算机科学速成课(crash course computer science)!\n![[assets/pasted image 20230526114242.png|350]]\n\u0026gt; carrie anne 小姐姐\n在这个系列中,我们会学习 bits(位),bytes(字节),晶体管(transistors),逻辑门(logic gates),一直到操作系统,虚拟现实和机器人!我们要学很多东西,但预先说明,我们 不会 教你怎么编程,我们会从高层次上纵览一系列计算机话题。\n计算机是当今世界的命脉,如果突然关掉所有的计算机,电网会关闭,车辆会相撞,飞机会坠毁,净水厂会关闭,证券市场会停止运作,装满食物的卡车不知运往何方,员工得不到薪水,甚至很多和计算机无关的东西,例如 dftba 的 t 恤和我现在坐的椅子也都是在计算机管理的工厂中制造的。\n计算机改变了我们生活中几乎所有方面。\n我们也不是第一次遇到推动全球发展的科技了。\n工业革命(industrial revolution)中生产能力的提高,大幅提升了农业,工业,畜牧业的规模。机械化导致更好的收成,更多的食物,商品可以大批量生产。旅行和通讯变得更便宜更快,生活质量变得更好。\n![[assets/pasted image 20230526114254.png|400]]\n\u0026gt; 蒸气车\n计算机和工业革命有一样的影响。\n从自动化农业和医疗设备到全球通信和教育机会,还有虚拟现实和无人驾驶汽车等新领域,现在这个时代很可能会被后人总结成 “信息时代”。\n你的智能手机中有数十亿个晶体管,看起来好像很复杂,但实际上它是很简单的机器,通过一层层的 抽象(abstraction) 来做出复杂操作。\n在这个系列中,我们会一层层讲解,从最底层的 1 和 0,到逻辑门,cpu,操作系统,整个互联网,以及更多~~\n不用担心,正如在网上买 t 恤的人不用知道网站代码是怎么写的,设计师不用知道数据包(packets)是怎么传输的,设计路由器的工程师不用理解晶体管的逻辑。\n本系列中每个视频会接着上集继续讲,但并不依赖前面的视频。等这个系列结束后,希望你能了解计算机在你的人生以及社会中扮演什么角色,以及这个人类史上最伟大的发明(可以这样说啦)是怎么开始的。\n它对未来还会有更大的影响。\n但深入之前,我们应该从计算的起源讲起,虽然电子计算机才出现不久,但人类对计算的需求早就有了。\n公认最早的计算设备是算盘(abacus),发明于\u0026quot;美索不达米亚\u0026quot;,大约公元前 2500 年。它是手动计算器,用来帮助加减数字,它存储着当前的计算状态,类似于如今的硬盘。人们制造算盘,是因为社会的规模已经超出个人心算的能力。一个村庄可能有上千个人和上万头牛。\n:: “公认”?算盘起源,此处存疑,待求证。\n算盘有很多变种,但我们来看一个基础版,每行代表 10 的不同次方。最底下那行,一个珠子代表 10 的 0 次方,也就是 1,再上面一行是 10 的 1 次方(也就是 10),再上面一行是 10 的 2 次方 (以此类推)……\n假设最底部的 3 颗珠子,代表 3 头牛。假设再买 4 头牛,只需要向右移动 4 颗珠子,共 7 个珠子。但如果再买 5 头,珠子就不够用了,所以把所有珠子移回左边。在第二排把 1 颗珠子向右移动,代表 10,然后最底下那行,向右移动 2 颗珠子,代表 12。\n这种方法处理大数字很有效。假设要表示 1251,从下往上:第一行移 1 个,第二行移 5 个,第三行移 2 个,第四行移 1 个。\n![[assets/pasted image 20230526114317.png|400]]\n\u0026gt; 计量‘牛牛’\n我们不用记在脑子里,算盘会记住。\n在接下来 4000 年,人类发明了各种巧妙的计算设备。比如星盘,让船只可以在海上计算纬度,或计算尺,帮助计算乘法和除法。人们还创造了上百种时钟,算日出,潮汐,天体的位置,或纯粹拿来计时。这些设备让原先很费力的事变得更快,更简单,更精确,降低了门槛,加强了我们的能力。\n记笔记!(敲黑板)这个系列会多次提到这一点。\n计算机先驱 charles babbage 说过:“随着知识的增长和新工具的诞生,人工劳力会越来越少”。\n然而,这些设备那时都不叫\u0026quot;计算机\u0026quot;。最早使用 “计算机” 一词的文献来自 1613 年的一本书,作者 richard braithwait。然而指的不是机器,而是一种职业。\n![[assets/pasted image 20230526114329.png|400]]\n\u0026gt; hi, computers.\nbraithwait 说:“我听说过的计算者里最厉害的,能把好几天的工作量大大缩减。”\n那时,“computer” 指负责计算的人。“computer” 偶尔会用机器帮忙,但大部分时候靠自己。这个职位一直到 1800 年代还存在,之后 “computer” 逐渐开始代表机器。\n![[assets/pasted image 20230526114338.png|400]]\n\u0026gt; 步进计算器\n其中\u0026quot;步进计算器\u0026quot;(step reckoner)最有名,由德国博学家戈特弗里德·莱布尼茨建造于 1694 年。\n:: 对,就是和牛顿先后发明微积分的那个莱布尼茨……\n莱布尼茨说过 “… 让优秀的人浪费时间算数简直侮辱尊严,农民用机器能算得一样准。”\n“步进计算器\u0026quot;有点像汽车里的里程表,不断累加里程数。它有一连串可以转动的齿轮(gears),每当一个齿轮转过 9,它会转回 0,同时让旁边的齿轮前进 1 个齿,就像算盘超过 10 一样。做减法时,机器会反向运作。利用一些巧妙的机械结构,步进计算器也能做乘法和除法。\n乘法和除法实际上只是多个加法和减法。举例,17 除以 5,我们只要减 5,减 5,再减 5,直到不能再减 5,就知道了 17=5x3+2 。步进计算器可以自动完成这种操作,它是第一台能做\u0026quot;加减乘除\u0026quot;全部四种运算的机器。它的设计非常成功,以至于沿用了 3 个世纪。\n不幸的是,即使有机械计算器,许多现实问题依然需要很多步,算一个结果可能要几小时甚至几天,而且这些手工制作的机器非常昂贵,大部分人买不起。所以在 20 世纪以前,大部分人会用预先算好的计算表,这些计算表由之前说的 “人力计算器” 编撰。如果你想知道 867,5309 的平方根,与其花一整天来手摇 “步进计算器”,你可以花一分钟在表里找答案。\n速度和准确性(speed and accuracy)在战场上尤为重要,因此军队很早就开始用计算解决复杂问题。如何精确瞄准炮弹是一个很难的问题。19 世纪,这些炮弹的射程可以达到 1 公里以上(比半英里多一点),因为风力,温度,大气压力会不断变化,想打中船一样大的物体也非常困难。于是出现了射程表(range tables),炮手可以查环境条件和射击距离,然后这张表会告诉他们,角度要设成多少。这些射程表很管用,二战中被广泛应用。问题是如果改了大炮或炮弹的设计,就要算一张新表,这样很耗时而且会出错。\ncharles babbage 在 1822 年写了一篇论文,向皇家天文学会指出了这个问题,标题叫: “机械在天文与计算表中的应用”(“note on the application of machinery to the computation of astronomical and mathematical tables”)。\n💭 让我们进入思想泡泡。\ncharles babbage 提出了一种新型机械装置叫 “差分机”(the difference engine),一个更复杂的机器,能近似多项式(polynomials)。多项式描述了几个变量之间的关系,比如射程和大气压力,或者 carrie anne 要吃多少披萨才开心。多项式也可以用于近似对数(logarithmic)和三角函数(trigonometric functions),这些函数手算相当麻烦。\n![[assets/pasted image 20230526114356.png|400]]\n\u0026gt; ‘分析机’进行时...\ncharles babbage 在 1823 年开始建造差分机,并在接下来二十年,试图制造和组装 25,000 个零件,总重接近 15 吨。不幸的是,该项目最终放弃了,但在 1991 年,历史学家根据 charles babbage 的草稿做了一个差分机,而且它还管用!但更重要的是,在差分机的建造期间,charles babbage 构想了一个更复杂的机器 - 分析机。不像差分机,步进计算器 和以前的其他计算设备,分析机是 “通用计算机”(“general purpose computer”)。它可以做很多事情,不只是一种特定运算,甚至可以给它数据,然后按顺序执行一系列操作。它有内存,甚至一个很原始的打印机。就像差分机,这台机器太超前了,所以没有建成,然而,这种 “自动计算机”(“automatic computer”)的概念。\n计算机可以自动完成一系列操作,是个跨时代的概念,预示着计算机程序的诞生。\n英国数学家 ada lovelace 给分析机写了假想的程序,她说:“未来会诞生一门全新的,强大的,专为分析所用的语言。” 因此 ada 被认为是世上第一位程序员(programmer)。\n分析机激励了(可以这么讲)第一代计算机科学家,这些计算机科学家把很多 charles babbage 的点子融入到他们的机器。所以 charles babbage 经常被认为是 “计算之父”。\n谢啦!思想泡泡\n到了 19 世纪末,科学和工程领域中的特定任务会用上计算设备。但公司,政府,家庭中很少见到计算设备。然而,美国政府在 1890 年的人口普查中面临着严重的问题,只有计算机能提供所需的效率。美国宪法要求 10 年进行一次人口普查,目的是分配联邦资金,国会代表,等等。到 1880 年代,美国人口迅速增长,大部分因为移民,人口普查要七年时间来手工编制,等做完都过时了,而且 1890 年的人口普查,预计要 13 年完成,但人口普查可是 10 年一次啊!\n人口普查局找了 herman hollerith,他发明了打孔卡片制表机(tabulating machine),他的机器是 “电动机械的”,用传统机械来计数,结构类似莱布尼茨的乘法器,但用电动结构连接其他组件。hollerith 的机器用打孔卡(punch cards) - 一种纸卡,上面有网格,用打孔来表示数据。\n![[assets/pasted image 20230526114404.png|400]]\n\u0026gt; herman hollerith 和 ‘打孔卡片制表机’\n举个例子,有一连串孔代表婚姻状况。如果你结婚了,就在 “结婚” 的位置打孔。当卡插入 hollerith 的机器时,小金属针会到卡片上。如果有个地方打孔了,针会穿过孔。泡入一小瓶汞,联通电路,电路会驱动电机,然后给 “已婚” 的齿轮 + 1 。\n:: 一个简单程序的威力 - 控制联通!\n![[assets/pasted image 20230526114419.png|400]]\n\u0026gt; 是不是最早的‘程序’卡?\nhollerith 的机器速度是手动的 10 倍左右,使人口普查在短短两年半内完成,给人口普查办公室省了上百万美元。企业开始意识到计算机的价值,可以提升劳动力以及数据密集型任务来提升利润。比如会计,保险评估和库存管理等行业。为了满足这一需求,hollerith 成立了制表机器公司,这家公司后来在 1924 年与其它机械制造商合并,成为了 “国际商业机器公司”,简称 ibm(the international business machines corporation)。\n:: 利润!!!恐怕没有什么比它更吸引企业的了!\n你可能听过 ibm 😂\n这些电子机械的 “商业机器” 取得了巨大成功,改变了商业和政府。到了 1900 年代中叶,世界人口的爆炸和全球贸易的兴起。要求更快,更灵活的工具来处理数据,为电子计算机的发展奠定了基础。\n我们下周讨论。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/01-%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%97%A9%E6%9C%9F%E5%8E%86%E5%8F%B2/","summary":"\u003cp\u003ei.e. Early Computing\u003c/p\u003e\n\u003cp\u003eHello world!我是 Carrie Anne,欢迎收看计算机科学速成课(Crash Course Computer Science)!\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20230526114242.png|350]]\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; Carrie Anne 小姐姐\u003c/code\u003e\u003c/p\u003e","title":"01 计算机早期历史"},]
[{"content":"i.e. electronic computing\n上集讲到 20 世纪初,当时的早期计算设备都针对特定用途,比如制表机(tabulating machines),大大推进了政府和企业。它们帮助,甚至代替了人工。然而人类社会的规模在以前所未有的速度增长,20 世纪上半叶,世界人口几乎翻倍。一战动员 7 千万人,二战 1 亿多人。全球贸易和运输更加紧密,工程和科学的复杂度也达到新高。我们甚至开始考虑造访其他行星,复杂度的增高导致数据量暴增,人们需要更多自动化,更强的计算能力。\n![[assets/pasted image 20230526114513.png|400]]\n很快,柜子大小的计算机变成房间大小,维护费用高,而且容易出错,而正是这些机器为未来的创新打下基础。\n继电器 最大的机电计算机之一是哈佛马克一号,ibm 在 1944 完成建造,给二战同盟国建造的。它有 76 万 5 千个组件,300 万个连接点和 500 英里长的导线。为了保持内部机械装置同步,它有一个 50 英尺的传动轴,由一个 5 马力的电机驱动,这台机器最早的用途之一 是给\u0026quot;曼哈顿计划\u0026quot;跑模拟。\n![[assets/pasted image 20230526114521.png|400]]\n这台机器的大脑是 “继电器”(relays),继电器是:用电控制的机械开关。继电器里,有根\u0026quot;控制线路\u0026quot;,控制电路是开还是关。“控制线路\u0026quot;连着一个线圈,当电流流过线圈,线圈产生电磁场,吸引金属臂,从而闭合电路。你可以把继电器想成水龙头,把控制线路想成水龙头把,打开水龙头,水会流出来,关闭水龙头,水就没有了。继电器是一样的,只不过控制的是电子而不是水。这个控制电路可以连到其他电路,比如马达,马达让计数齿轮 +1,就像上集中 hollerith 的制表机一样。\n:: 电磁原理…… 另外,看,最初的开始就是为了 ‘控制连通’ ,区分开关,划分阴阳。\n不幸的是,继电器内的机械臂有质量,因此无法快速开关。\n1940 年代一个好的继电器 1 秒能翻转 50 次,看起来好像很快,但还不够快,不足以解决复杂的大问题。哈佛马克一号,1 秒能做 3 次加法或减法运算,一次乘法要花 6 秒,除法要花 15 秒。更复杂的操作,比如三角函数,可能要一分钟以上。\n除了速度慢,另一个限制是齿轮磨损。\n任何会动的机械都会随时间磨损,有些部件会完全损坏,有些则是变黏,变慢,变得不可靠,并且随着继电器数量增加,故障概率也会增加。哈佛马克一号有大约 3500 个继电器,哪怕假设继电器的使用寿命是 10 年,也意味着平均每天得换一个故障继电器!这个问题很严重,因为有些重要运算要运行好几天,而且还有更多其他问题要考虑。\n这些巨大,黑色,温暖的机器也会吸引昆虫 🦟。\n![[assets/pasted image 20230526114544.png|400]]\n\u0026gt; ha, bug...\n1947 年 9 月,哈佛马克 2 型的操作员从故障继电器中,拔出一只死虫。grace hopper(这位我们以后还会提到)曾说,“从那时起,每当电脑出了问题,我们就说它出了 bug(虫子)\u0026quot;,这就是术语 “bug” 的来源。\n显然,如果想进一步提高计算能力,我们需要更快更可靠的东西,来替代继电器。幸运的是,替代品已经存在了!\n真空管 在 1904 年,英国物理学家 “约翰·安布罗斯·弗莱明”,开发了一种新的电子组件,叫 “热电子管”。把两个电极(electrodes)装在一个气密的玻璃灯泡里,这是世上第一个真空管(vacuum tube)。其中一个电极可以加热,从而发射电子(electrons),这叫 “热电子发射”。另一个电极会吸引电子,形成\u0026quot;电龙头\u0026quot;的电流,但只有带正电才行。如果带负电荷或中性电荷,电子就没办法被吸引,越过真空区域,因此没有电流。\n电流只能单向流动的电子部件叫 “二极管”(diode),但我们需要的是,一个能开关电流的东西。\n:: ⚡️ 控制联通!控制联通!控制联通!\n幸运的是,不久之后在 1906 年,美国发明家 “李·德富雷斯特”,他在\u0026quot;弗莱明\u0026quot;设计的两个电极之间,加入了第三个 “控制” 电极,向\u0026quot;控制\u0026quot;电极施加正电荷,它会允许电子流动,但如果施加负电荷,它会阻止电子流动。因此通过控制线路,可以断开或闭合电路,和继电器的功能一样。\n![[assets/pasted image 20230526114603.png|400]]\n\u0026gt; 真空管\n但重要的是,真空管内没有会动的组件,这意味着更少的磨损。更重要的是,每秒可以开闭数千次。因此这些 “三极真空管”(triode vacuum tubes)成为了无线电,长途电话以及其他电子设备的基础,持续了接近半个世纪。我应该提到,真空管不是完美的,它们有点脆弱,并且像灯泡一样会烧坏,但比起机械继电器是一次巨大进步。\n![[assets/pasted image 20230526114613.png|400]]\n起初,真空管非常昂贵,收音机一般只用一个,但计算机可能要上百甚至上千个电气开关。但到了 1940 年代,它的成本和可靠性得到改进,可以用在计算机里,至少有钱人负担得起,比如政府。\n这标志着计算机从机电转向电子。\n💭 我们来进入思想泡泡\n第一个大规模使用真空管的计算机是 “巨人 1 号”,由工程师 tommy flowers 设计,完工于 1943 年 12 月。巨人 1 号 在英国的\u0026quot;布莱切利园”, 用于破解纳粹通信,听起来可能有点熟,因为 2 年前 阿兰·图灵(他经常被称为\u0026quot;计算机科学之父”)也在\u0026quot;布莱切利园\u0026quot;做了台机电装置,叫 “bombe”,这台机器的设计目的是破解纳粹\u0026quot;英格码\u0026quot;通讯加密设备,但 bombe 严格来说不算计算机。我们之后会讨论\u0026quot;阿兰·图灵\u0026quot;的贡献。总之,巨人 1 号有 1600 个真空管,总共造了 10 台巨人计算机,来帮助破解密码。巨人被认为是第一个可编程的电子计算机,编程的方法是把几百根电线插入插板(plugboards),有点像老电话交换机,这是为了让计算机执行正确操作。虽然\u0026quot;可编程\u0026quot;(programmable) ,但还是要配置它。\n![[assets/pasted image 20230526114625.png|400]]\n\u0026gt; eniac - 世上第一个真正的通用、可编程的电子计算机\n电子数值积分计算机 “eniac\u0026quot;几年后在 1946 年,在\u0026quot;宾夕法尼亚大学\u0026quot;完成建造,设计者是 john mauchly 和 j. presper eckert,这是世上第一个真正的通用,可编程,电子计算机。eniac 每秒可执行 5000 次十位数加减法,比前辈快了很多倍,它运作了十年。据估计,它完成的运算,比全人类加起来还多。因为真空管很多,所以故障很常见,eniac 运行半天左右就会出一次故障。\n谢了 思想泡泡\n到 1950 年代,真空管计算机都达到了极限。美国空军的 an/fsq-7 计算机于 1955 年完成,是 “sage” 防空计算机系统的一部分,之后的视频还会提到。\n为了降低成本和大小,同时提高可靠性和速度,我们需要一种新的电子开关。\n晶体管 1947 年,贝尔实验室科学家 john bardeen,walter brattain,william shockley,发明了 晶体管(transistor),一个全新的计算机时代诞生了!\n![[assets/pasted image 20230526114633.png|400]]\n晶体管的物理学相当复杂,牵扯到量子力学(quantum mechanics),所以我们只讲基础。晶体管就像之前提过的\u0026quot;继电器\u0026quot;或\u0026quot;真空管”,它是一个开关,可以用控制线路来控制开或关。晶体管有两个电极,电极之间有一种材料隔开它们,这种材料有时候导电,有时候不导电,这叫\u0026quot;半导体\u0026quot;(semiconductor)。控制线连到一个 “门”(gate)电极,通过改变 “门” 的电荷。我们可以控制半导体材料的导电性,来允许或不允许电流流动,就像之前的水龙头比喻。\n![[assets/pasted image 20230526114645.png|400]]\n\u0026gt; 第一个晶体管\n贝尔实验室(bell labs)的第一个晶体管就展示了巨大的潜力,每秒可以开关 10,000 次,而且,比起玻璃制成小心易碎的真空管,晶体管是固态的。晶体管可以远远小于继电器或真空管,导致更小更便宜的计算机,比如 1957 年发布的 ibm 608 - 第一个完全用晶体管,而且消费者也可以买到的计算机,它有 3000 个晶体管,每秒执行 4500 次加法,每秒能执行 80 次左右的乘除法。ibm 很快把所有产品都转向了晶体管,把晶体管计算机带入办公室,最终引入家庭,如今,计算机里的晶体管小于 50 纳米,而一张纸的厚度大概是 10 万纳米。晶体管不仅小,还超级快 - 每秒可以切换上百万次,并且能工作几十年。\n很多晶体管和半导体的开发在\u0026quot;圣克拉拉谷\u0026quot;,这个地方在加州,位于\u0026quot;旧金山\u0026quot;和\u0026quot;圣荷西\u0026quot;之间,而生产半导体最常见的材料是 “硅”,所以这个地区被称为 “硅谷”。甚至 william shockley 都搬了过去,创立了\u0026quot;肖克利半导体\u0026quot;,里面的员工后来成立了\u0026quot;仙童半导体\u0026quot;,这里面的员工后来创立了英特尔 - 当今世界上最大的计算机芯片制造商。\n好了,我们从\u0026quot;继电器\u0026quot;到\u0026quot;真空管\u0026quot;到\u0026quot;晶体管\u0026quot;,我们可以让电路开闭得非常非常快,但我们是如何用晶体管做计算的?我们没有马达和齿轮啊?\n:: 开关的过程,就是阴阳转换的过程,信息变化的过程!\n我们接下来几集会讲,感谢观看,下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/02-%E7%94%B5%E5%AD%90%E8%AE%A1%E7%AE%97%E6%9C%BA/","summary":"\u003cp\u003ei.e. Electronic Computing\u003c/p\u003e\n\u003cp\u003e上集讲到 20 世纪初,当时的早期计算设备都针对特定用途,比如制表机(tabulating machines),大大推进了政府和企业。它们帮助,甚至代替了人工。然而人类社会的规模在以前所未有的速度增长,20 世纪上半叶,世界人口几乎翻倍。一战动员 7 千万人,二战 1 亿多人。全球贸易和运输更加紧密,工程和科学的复杂度也达到新高。我们甚至开始考虑造访其他行星,复杂度的增高导致数据量暴增,人们需要更多自动化,更强的计算能力。\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20230526114513.png|400]]\u003c/p\u003e\n\u003cp\u003e很快,柜子大小的计算机变成房间大小,维护费用高,而且容易出错,而正是这些机器为未来的创新打下基础。\u003c/p\u003e","title":"02 电子计算机"},]
[{"content":"i.e. boolean logic and logic gates\n今天我们开始\u0026quot;抽象\u0026quot;(abstraction)的旅程!不用管底层细节,把精力用来构建更复杂的系统。\n上集,我们谈了计算机最早是机电设备(electromechanical),一般用十进制(decimal)计数,比如用齿轮数来代表十进制, 再到晶体管计算机。\n幸运的是,只用 ‘开/关’ 两种状态也可以代表信息,这叫二进制,意思是\u0026quot;用两种状态表示\u0026quot;,就像自行车有两个轮,双足动物有两条腿。你可能觉得两种状态不多,你是对的!但如果只需要表示 true 和 false,两个值就够了。电路闭合,电流流过,代表 “真”;电路断开,无电流流过,代表\u0026quot;假\u0026quot;。二进制也可以写成 1 和 0 而不是 true 和 false,只是不同的表达方式罢了。\n我们下集(episode)会讲更多细节。\n晶体管的确可以不只是开/关,还可以让不同大小的电流通过。一些早期电子计算机是三进制的,有 3 种状态,甚至五进制,5 种状态。问题是,状态越多,越难区分信号。如果手机快没电了或者附近有电噪音,因为有人在用微波炉(microwave),信号可能会混在一起。.. 而每秒百万次变化的晶体管会让这个问题变得更糟!所以我们把两种信号尽可能分开,只用\u0026quot;开\u0026quot;和\u0026quot;关\u0026quot;两种状态,可以尽可能减少这类问题。\n![[assets/pasted image 20230526114830.png|400]]\n\u0026gt; on \u0026amp; off\n计算机用二进制的另一个原因是,有一整个数学分支存在,专门处理\u0026quot;真\u0026quot;和\u0026quot;假\u0026quot;。它已经解决了所有法则和运算 - 叫\u0026quot;布尔代数\u0026quot;(boolean algebra)!\n乔治·布尔(george boole)是布尔二字的由来,是一位 19 世纪自学成才的英国数学家,他有兴趣用数学式子扩展亚里士多德基于哲学的逻辑方法,布尔用逻辑方程系统而正式的证明真理 (truth)。他在 1847 年的第一本书\u0026quot;逻辑的数学分析\u0026quot;中介绍过,在\u0026quot;常规\u0026quot;代数里 - 你在高中学的那种 - 变量的值是数字,可以进行加法或乘法之类的操作,但在布尔代数中,变量的值是 true 和 false,能进行逻辑操作。\n布尔逻辑 布尔代数中有三个基本操作:not, and 和 or 。这些操作非常有用,我们一个个来看:\n:: 布尔逻辑的本质是什么呢?很简单,一个或若干个输入(真或假),经过若干次逻辑运算,最终得到一个确定的输出(真或假)的过程!\nnot not 操作把布尔值反转,把 true 进行 not 就会变成 false,反之亦然。我们可以根据 not 操作的输入和输出,做出这个表。\ninput output ture false false true \u0026gt; not 真值表\n酷的地方是 - 用晶体管可以轻松实现这个逻辑。\n:: 晶体管的用武之地~~\n上集说过,晶体管只是电控制的开关 - 有 3 根线:2 根电极和 1 根控制线。\n控制线通电时,电流就可以从一个电极流到另一个电极,就像水龙头一样 - 打开水龙头,就有水流出来,关掉水龙头,就没水了。可以把控制线当做输入 ( input ),底部的电极当做输出(output),所以 1 个晶体管,有一个输入和一个输出。如果我们打开输入(input on) ,输出也会打开(output on),因为电流可以流过。如果关闭输入(input off),输出也会关闭(output off),因为电流无法通过。或者用布尔术语来说,输入为真,输出为真,输入为假,输出为假,我们也可以把这个做成\u0026quot;真值表\u0026quot;。\n这个电路没什么意思,因为它没做什么事 - 输入和输出是一样的。\n但我们可以稍加修改,实现 not 。\n![[assets/pasted image 20230526114853.png|400]]\n与其把下面那根线当做 输出,我们可以把 输出 放到上面。如果打开 输入,电流可以流过然后 “接地”,输出就没有电流,所以输出是 off 。如果用水来举例,就像家里的水都从一个大管子流走了,打开淋浴头一点水也没有。\n如果输入是 on,输出是 off 。当输入是 off,电流没法接地,就流过了输出,所以输出是 on ;当输入是 off,电流没法接地,就流过了输出,所以输出是 on 。\n如果输入是 off,输出是 on 。\n和 not 操作表一样!太棒了!我们做了个有点用的电路!🎉\n我们叫它 “not 门”,之所以叫 “门”,是因为它能控制电流的路径 。\nand “and\u0026quot;操作有 2 个输入,1 个输出。如果 2 个输入都是 true,输出才是 true 。和上次一样,可以给\u0026quot;and\u0026quot;做个表 。\ninput a input b output true true true true false false false true false false false false \u0026gt; and 真值表\n![[assets/pasted image 20230526114907.png|400]]\n为了实现 “and 门”,我们需要 2 个晶体管连在一起,这样有 2 个输入和 1 个输出。如果只打开 a,不打开 b ,电流无法流到 output,所以输出是 false ;如果只打开 b,不打开 a ,也一样,电流无法流到 output ;只有 a 和 b 都打开了,output 才有电流 。\nor 最后一个是 or (前面讲了 not 和 and)。\ninput a input b output true true true true false true false true true false false false \u0026gt; or 真值表\n只要 2 个输入里,其中 1 个是 true,输出就是 true ,只有 2 个输入都是 false,or 的结果才是 false 。\n![[assets/pasted image 20230526114916.png|400]]\n实现 “or 门” 除了晶体管还要额外的线,不是串联起来,而是并联。然后左边这条线有电流输入,我们用\u0026quot;小拱门\u0026quot;代表 2 条线没连在一起,只是跨过而已,虽然看起来像连在一起。\n如果 a 和 b 都是 off,电流无法流过,所以输出是 off ;如果只打开 a,电流可以流过,输出是 on ;如果只打开 b 也一样;只要 a or b 是 on, 输出就是 on 。\n好,现在 not 门,and 门,or 门 都搞定了。\n我们可以进行一次抽象!\n![[assets/pasted image 20230526114935.png|120]] ![[assets/pasted image 20230526114949.png|120]] ![[assets/pasted image 20230526115000.png|120]]\nnot 门的画法是三角形前面一个圆点,and 门用 d 表示 ,or 门用太空船表示 。“d 形状和太空船\u0026quot;不是标准叫法,只是我喜欢这样叫而已。我们可以用这种方法表示它们,构建更大的组件,就不会变得很复杂。\nxor 晶体管和电线依然在那里,我们只是用符号来代表而已。除了前面说的三个、n 另一个有用的布尔操作叫 “异或”,简称 xor 。\ninput a input b output true true false true false true false true true false false false \u0026gt; xor 真值表\nxor 就像普通 or,但有一个区别:如果 2 个输入都是 true,xor 输出 false 。想要 xor 输出 true ,一个输入必须是 true,另一个必须是 false 。\n就像你出去吃晚饭,你点的饭要么配沙拉,要么配汤 - 你不能两个都要!\n用晶体管实现 xor 门有点烧脑子,但我可以展示一下,怎么用前面提到的 3 种门来做 xor 门。\n![[assets/pasted image 20230526115017.png|425]]\n\u0026gt; xor 的组成\n组合的威力!\n我们有 2 个输入,a 和 b ,还有 1 个输出。我们先放一个 or 门,因为 or 和 xor 的逻辑表很像。只有 1 个问题 - 当 a 和 b 都是 true 时 , or 的输出和想要的 xor 输出不一样。我们想要 false,xor 超有用的,我们下次再说它。因为超有用,工程师给了它一个符号,一个 or 门 + 一个笑脸。\n![[assets/pasted image 20230526115031.png|145]]\n重要的是,现在可以把 xor 放入\u0026quot;工具箱\u0026quot;了。不用担心 xor 具体用了几个门,这几个门又是怎么用晶体管拼的,或电子是怎么流过半导体的。\n再次向上抽象。\n工程师设计处理器(processors)时,很少在晶体管的层面上思考,而是用更大的组件,比如逻辑门,或者由逻辑门组成的更大组件。我们以后会讲,就算是专业程序员,也不用考虑逻辑是怎样在物理层面实现的。\n我们从电信号开始,到现在第一次表示数据 - 真和假 - 开始有点\u0026quot;计算\u0026quot;的感觉了。\n仅用这集讲的 逻辑门,我们可以判断复杂的语句 比如:[如果是 john green] and [下午 5 点后] or [周末] and [在比萨店附近],那么 \u0026quot;john 想要比萨\u0026quot; = 真 。\n我都说饿了,下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/03-%E5%B8%83%E5%B0%94%E9%80%BB%E8%BE%91%E5%92%8C%E9%80%BB%E8%BE%91%E9%97%A8/","summary":"\u003cp\u003ei.e. Boolean Logic and Logic Gates\u003c/p\u003e\n\u003cp\u003e今天我们开始\u0026quot;抽象\u0026quot;(abstraction)的旅程!不用管底层细节,把精力用来构建更复杂的系统。\u003c/p\u003e","title":"03 布尔逻辑和逻辑门"},]
[{"content":"i.e. representing numbers and letters with binary\n今天,我们讲计算机如何存储和表示数字,所以会有一些数学,不过别担心,你们的数学水平绝对够用了。\n上集我们讲了,怎么用晶体管(transistors)做逻辑门(logic gates),逻辑门可以判断布尔语句,布尔代数只有两个值:true 和 false 。\n但如果只有两个值,我们怎么表达更多东西 ❓ 这就需要数学了!\n进制和运算 上集提到,1 个二进制值可以代表 1 个数,我们可以把真和假 ,当做 1 和 0 。如果想表示更多东西,加位数就行了。和我们熟悉的十进制(decimal)一样,十进制只有 10 个数(0 到 9),要表示大于 9 的数,加位数就行了。\n二进制也可以这样玩。\n拿 263 举例,这个数字 “实际” 代表什么?2 个 100、6 个 10、3 个 1,加在一起,就是 263。注意每列有不同的乘数 - 100、10、1 ,每个乘数都比右边大十倍,因为每列有 10 个可能的数字(0 到 9),如果超过 9,要在下一列进 1 ,因此叫 “基于十的表示法” 或 十进制。\n二进制也一样,只不过是基于 2 而已。因为二进制只有两个可能的数, 1 和 0 ,意味着每个乘数必须是右侧乘数的两倍,就不是之前的 100、10、1 ,而是 4、2、1 。\n拿二进制数 101 举例,意味着有 1 个 “4”、 0 个 “2 、1 个 “1”,加在一起,得到十进制的 5 。\n为了表示更大的数字,二进制需要更多位数。拿二进制数 10110111 举例,我们可以用相同的方法转成十进制 - 1 x 128 ,0 x 64 ,1 x 32 ,1 x 16,0 x 8 ,1 x 4 ,1 x 2 ,1 x 1 ,加起来等于 183。\n![[assets/pasted image 20230526115156.png|400]]\n二进制数的计算也不难。\n以十进制数 183 加 19 举例,首先 3 + 9,得到 12,然后位数记作 2,向前进 1,现在算 8+1+1=10,所以位数记作 0,再向前进 1,最后 1+1=2,位数记作 2,所以和是 202。\n二进制也一样。\n和之前一样,从个位开始,1+1=2,在二进制中也是如此,但二进制中没有 2,所以位数记作 0 ,进 1。就像十进制的例子一样,1+1,再加上进位的 1,等于 3,用二进制表示是 11。所以位数记作 1,再进 1,以此类推,最后得到这个数字,跟十进制 202 是一样的。\n:: 二进制中,逢二进一。\n![[assets/pasted image 20230526115208.png|425]]\n位和字节 二进制中,一个 1 或 0 叫一 “位”(bit)。\n上个例子我们用了 8 位 , 8 位能表示的最小数是 0, 8 位都是 0,最大数是 255,8 位都是 1,能表示 256 个不同的值,2 的 8 次方。你可能听过 8 位机,8 位图像,8 位音乐,意思是计算机里大部分操作都是 8 位 8 位这样处理的。但 256 个值不算多,意味着 8 位游戏只能用 256 种颜色。\n8 位是如此常见,以至于有专门的名字: 字节(byte) ❗\n1 字节 = 8 位 1 bytes = 8 bits 如果有 10 个字节,意味着有 80 位。你听过 千字节(kb)兆字节(mb)千兆字节(gb)等等。不同前缀代表不同数量级,就像 1 千克 = 1000 克,1 千字节 = 1000 字节,或 8000 位。mega 是百万字节(mb), giga 是十亿字节(gb)。如今你可能有 1 tb 的硬盘,8 万亿个 1 和 0。等等,我们有另一种计算方法 - 二进制里,1 千字节 = 2 的 10 次方 = 1024 字节。1000 也是千字节(kb)的正确单位,1000 和 1024 都对。\n你可能听过 32 位 或 64 位计算机,你现在用的电脑几乎肯定是其中一种,意思是 一块块处理数据,每块是 32 位或 64 位,这可是很多位。32 位能表示的最大数,是 43 亿左右 - 也就是 32 个 1,所以 instagram 照片很清晰,它们有上百万种颜色,因为如今都用 32 位颜色。当然,不是所有数都是正数,比如我上大学时的银行账户 t_t ……\n:: cpu 一次性读取的位数。\n数的表示 我们需要有方法表示正数(positive)和负数(negative),大部分计算机用第一位表示正负 :1 是负,0 是正。用剩下 31 位来表示符号外的数值,能表示的数的范围大约是正 20 亿到负 20 亿。虽然是很大的数,但许多情况下还不够用,全球有 70 亿人口,美国国债近 20 万亿美元。所以 64 位数很有用,64 位能表达最大数大约是 9.2 × 10 ^ 18,希望美国国债在一段时间内不会超过这个数!\n重要的是(我们之后的视频会深入讲),计算机必须给内存(memory)中每一个位置,做一个 “标记”,这个标记叫 “地址”(addresses)(也叫‘位址’), 目的是为了方便存取(store and retrieve)数据。如今硬盘已经增长到 gb 和 tb,上万亿个字节!内存地址也应该有 64 位。\n除了负数和正数,计算机也要处理非整数(not whole numbers),比如 12.7 和 3.14,或\u0026quot;星历 43989.1” - 这叫 浮点数(floating point numbers)。因为小数点可以在数字间浮动,有好几种方法 表示浮点数,最常见的是 ieee 754 标准,你以为只有历史学家取名很烂吗?它用类似科学计数法的方法,来存十进制值,例如,625.9 可以写成 0.6259 × 10 ^ 3 ,这里有两个重要的数:.6259 叫 “有效位数”(significand) , 3 是指数(exponent)。\n![[assets/pasted image 20230526115229.png]]\n\u0026gt; 计算机中浮点数的表示方法\n在 32 位浮点数中,第 1 位表示数的符号 —— 正或负,接下来 8 位存指数,剩下 23 位存有效位数。\n:: 以上为正、负 \u0026amp; 整数、浮点数在计算机中的表示,‘数’的表示任务已达成 🎉\n字符的表示 好了,聊够数了,但你的名字是字母(letters)组成的 !所以我们也要表示文字(text),与其用特殊方式来表示字母,计算机可以用数表示字母 - 最直接的方法是给字母编号:a 是 1,b 是 2,c 是 3,以此类推。\n![[assets/pasted image 20230526115242.png]]\n\u0026gt; 人才啊·培根\n著名英国作家 弗朗西斯·培根(francis bacon),曾用 5 位序列 来编码英文的 26 个字母。在十六世纪传递机密信件,五位(bit)可以存 32 个可能值(2^5) - 这对 26 个字母够了,但不能表示 标点符号,数字和大小写字母。\n:: 在内存中,字符也是用数字表示的,本质上是一张数字与字符一一对应的编码表!\nascii,美国信息交换标准代码(the american standard code for information interchange),发明于 1963 年,ascii 是 7 位代码,足够存 128 个不同值。范围扩大之后,可以表示大写字母,小写字母,数字 0 到 9, @ 这样的符号,以及标点符号(punctuation marks)。举例,小写字母 a 用数字 97 表示,大写字母 a 是 65,: (colon)是 58, ) 是 41。\na a : ) 97 65 58 41 ascii 甚至有特殊命令符号,比如换行符(newline),用来告诉计算机换行。在老计算机系统中,如果没换行符,文字会超出屏幕。\n因为 ascii 是个很早的标准,所以它被广泛使用,让不同公司制作的计算机,能互相交换数据,这种通用交换信息的能力叫 “互操作性”。但有个限制:它是为英语设计的。\n幸运的是,一个字节有 8 位,而不是 7 位,128 到 255 的字符渐渐变得常用,这些字符以前是空的,是给各个国家自己 “保留使用的” 。在美国,这些额外的数字主要用于编码附加符号,比如数学符号,图形元素和常用的重音字符。另一方面,虽然拉丁字符被普遍使用,在俄罗斯,他们用这些额外的字符表示西里尔字符,而希腊电脑用希腊字母,等等。这些保留下来给每个国家自己安排的空位,对大部分国家都够用,问题是,如果在土耳其电脑上打开拉脱维亚语 写的电子邮件,会显示乱码。\n![[assets/pasted image 20230526115253.png]]\n\u0026gt; 8 位远远不够啊。..\n随着计算机在亚洲兴起,这种做法彻底失效了,中文和日文这样的语言有数千个字符,根本没办法用 8 位来表示所有字符!为了解决这个问题,每个国家都发明了多字节编码方案,但相互不兼容,日本人总是碰到编码问题,以至于专门有词来称呼:“mojibake” 意思是 乱码。\n所以 unicode 诞生了 - 统一所有编码的标准。设计于 1992 年,解决了不同国家不同标准的问题,unicode 用一个统一编码方案,最常见的 unicode 是 16 位的,有超过一百万个位置,对所有语言的每个字符都够了,100 多种字母表加起来占了 12 万个位置,还有位置放数学符号,甚至 emoji 。\n就像 ascii 用二进制来表示字母一样,其他格式 - 比如 mp3 或 gif,用二进制编码声音/颜色,表示照片,电影,音乐。重要的是,这些标准归根到底是一长串位 。短信,这个 youtube 视频,互联网上的每个网页,甚至操作系统,只不过是一长串 1 和 0 。\n下周,我们会聊计算机怎么操作二进制,初尝\u0026quot;计算\u0026quot;的滋味。\n感谢观看,下周见。\n----------------\n你可以在我的另一篇博文中了解更多关于字符编码的知识 – [[字符集和字符编码]] 。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/04-%E4%BA%8C%E8%BF%9B%E5%88%B6/","summary":"\u003cp\u003ei.e. Representing Numbers and Letters with Binary\u003c/p\u003e\n\u003cp\u003e今天,我们讲计算机如何存储和表示数字,所以会有一些数学,不过别担心,你们的数学水平绝对够用了。\u003c/p\u003e\n\u003cp\u003e上集我们讲了,怎么用晶体管(transistors)做逻辑门(logic gates),逻辑门可以判断布尔语句,布尔代数只有两个值:\u003ccode\u003eTrue\u003c/code\u003e 和 \u003ccode\u003eFalse\u003c/code\u003e 。\u003c/p\u003e\n\u003cp\u003e但如果只有两个值,我们怎么表达更多东西 ❓ 这就需要数学了!\u003c/p\u003e","title":"04 二进制"},]
[{"content":"i.e. how computers calculate-the alu\n上集,我们谈了如何用二进制表示数字,比如二进制 00101010 是十进制的 42,表示和存储数字是计算机的重要功能。但真正的目标是计算(computation),有意义的处理数字,比如把两个数字相加,这些操作由计算机的 “算术逻辑单元 (arithmetic and logic unit)“处理,但大家会简称:alu。\nalu 是计算机的数学大脑,等你理解了 alu 的设计和功能之后,你就理解了现代计算机的基石。\nalu 就是计算机里负责运算的组件,基本其他所有部件都用到了它,先来看看这个美人。这可能是最著名的 alu,英特尔 74181 。1970 年发布时,它是第一个封装在单个芯片内的完整 alu ,这在当时是惊人的工程壮举。\n![[assets/pasted image 20230526115402.png|300]]\n\u0026gt; 英特尔 74181\n今天我们用上周学的布尔逻辑门,做一个简单的 alu 电路,功能和 74181 一样。然后接下来几集,用它从头做出一台电脑,所以会有点复杂,但我觉得你们搞的定。\nalu 有 2 个单元,1 个算术单元和 1 个逻辑单元。\n算术单元 我们先讲\u0026quot;算术单元”,它负责计算机里的所有数字操作。比如加减法,它还做很多其他事情,比如给某个数字+1,这叫增量运算,我们之后会说。\n今天的重点是一切的根本 - “把两个数字相加” 。\n我们可以用单个晶体管一个个拼,把这个电路做出来,但很快就会复杂的难以理解,所以与其用晶体管,我们会像第 3 集 - 用更高层的抽象,用逻辑门来做。我们会用到 and,or,not 和 xor 逻辑门,最简单的加法电路, 是拿 2 个 bit 加在一起(bit 是 0 或 1),有 2 个输入:a 和 b, 1 个输出:就是两个数字的和。需要注意的是:a, b, 输出 ,这 3 个都是单个 bit ( 0 或 1 )。\n![[assets/pasted image 20230526115414.png]]\n输入只有四种可能:\n前三个是 0 + 0 = 0 、 1 + 0 = 1 、0 + 1 = 1 。记住二进制里,1 与 true 相同,0 与 false 相同,这组输入和输出,和 xor 门的逻辑完全一样,所以我们可以把 xor 用作 1 位加法器(adder)。\n但第四个输入组合,1+1 是个特例 1 + 1 = 2(显然),但二进制里没有 2,上集说过,二进制 1+1 的结果是 0,1 进到下一位,和是 10 (二进制)。\n![[assets/pasted image 20230526115423.png|400]]\n\u0026gt; 1 + 1 = 10 呢 ?\nxor 门的输出,只对了一部分, 1+1 输出 0 ,但我们需要一根额外的线代表 “进位”,只有输入是 1 和 1 时,进位才是 “true” ( 也就是 1 )。因为算出来的结果用 1 个 bit 存不下,方便的是,我们刚好有个逻辑门能做这个事!没那么复杂 - 就两个逻辑门而已。\n![[assets/pasted image 20230526115437.png|400]]\n\u0026gt; 1 + 1 = 10 了(carry = 1, sum = 0)\n让我们抽象化。\n把 “半加器” 封装成一个单独组件 - 两个输入 a 和 b 都是 1 位 ,两个输出 “总和” 与 “进位”,这进入了另一层抽象。\n![[assets/pasted image 20230526115449.png|400]]\n\u0026gt; 抽象出了‘半加器’\n如果想处理超过 1+1 的运算,我们需要\u0026quot;全加器”,半加器输出了进位,意味着,我们算下一列的时候,还有之后的每一列,我们得加 3 个位在一起,并不是 2 个。\n![[assets/pasted image 20230526115522.png]]\n全加器复杂了一点点。\n全加器表格,有 3 个输入:a, b, c (都是 1 个 bit),所以最大的可能是 1 + 1 + 1,“总和\u0026quot;1 “进位\u0026quot;1 ,所以要两条输出线: “总和\u0026quot;和\u0026quot;进位” 。\n![[assets/pasted image 20230526115530.png|400]]\n我们可以用 半加器 做 全加器,我们先用半加器将 a 和 b 相加,然后把 c 输入到第二个半加器,最后用一个 or 门检查进位是不是 true,这样就做出了一个全加器!\n![[assets/pasted image 20230526115547.png]]\n\u0026gt; emm... ‘全加器’的诞生\n我们可以再提升一层抽象,把全加器作为独立组件。全加器会把 a,b,c 三个输入加起来,输出 “总和” 和 “进位”。\n![[assets/pasted image 20230526115604.png]]\n现在有了新组件,我们可以相加两个 8 位数字。叫两个数字叫 a 和 b 好了,我们从 a 和 b 的第一位开始,叫 a0 和 b0 好了,现在不用处理任何进位,因为是第一次加法,所以我们可以用半加器,来加这 2 个数字,输出叫 sum0 ;现在加 a1 和 b1,因为 a0 和 b0 的结果有可能进位,所以这次要用全加器,除了 a1 和 b1,还要连上进位,输出叫 sum1 ;然后,把这个全加器的进位连到下个全加器的输入,处理 a2 和 b2;以此类推,把 8 个 bit 都搞定…… 注意每个进位是怎么连到下一个全加器的,所以叫 “8 位行波进位加法器” ,注意最后一个全加器有 “进位” 的输出。如果第 9 位有进位,代表着 2 个数字的和太大了,超过了 8 位,这叫 “溢出” (overflow)。一般来说 “溢出” 的意思是,两个数字的和太大了,超过了用来表示的位数,这会导致错误和不可预期的结果。\n![[assets/pasted image 20230526115612.png]]\n\u0026gt; 8 位行波进位加法器\n著名的例子是,吃豆人用 8 位存当前关卡数,如果你玩到了第 256 关( 8 位 bit 最大表示 255),alu 会溢出,造成一连串错误和乱码(glitches),使得该关卡无法进行,这个 bug 成了厉害吃豆人玩家的代表。如果想避免溢出,我们可以加更多全加器,可以操作 16 或 32 位数字,让溢出更难发生,但代价是更多逻辑门,另外一个缺点是,每次进位都要一点时间,当然时间不久,因为电子移动的很快,但如今的量级是每秒几十亿次运算,所以会造成影响。所以,现代计算机用的加法电路有点不同,叫 “超前进位加法器”。它更快,做的事情是一样的 - 把二进制数相加。\n![[assets/pasted image 20230526115627.png]]\n\u0026gt; 牛‘爆’的吃豆人\nalu 的算术单元,也能做一些其他数学运算,一般支持这 8 个操作 - 见下图。\n:: 哪 8 个 ❓\n![[assets/pasted image 20230526115640.png]]\n就像加法器一样,这些操作也是由逻辑门构成的。有趣的是,你可能注意到没有乘法和除法。因为简单的 alu 没有专门的电路来处理,而是把乘法用多次加法来实现。假设想算 12 x 5 ,这和把 “12” 加 5 次是一样的,所以要 5 次 alu 操作来实现这个乘法。很多简单处理器(processors)都是这样做的,比如恒温器,电视遥控器和微波炉,慢是慢,但是搞的定。然而笔记本和手机有更好的处理器,有专门做乘法的算术单元,你可能猜到了,乘法电路比加法复杂 - 没什么魔法,只是更多逻辑门,所以便宜的处理器没有。\n:: 是的,只是逻辑门而已 ✔️\n好了,我们现在讲 alu 的另一半:逻辑单元。\n逻辑单元 逻辑单元执行逻辑操作,比如之前讨论过的 and,or 和 not 操作,它也能做简单的数值测试,比如一个数字是不是负数。\n例如,这是检查 alu 输出是否为 0 的电路,它用一堆 or 门检查其中一位是否为 1,哪怕只有一个 bit (位) 是 1,我们就知道那个数字肯定不是 0,然后用一个 not 门取反,所以只有输入的数字是 0,输出才为 1 。\n![[assets/pasted image 20230526115702.png|500]]\n\u0026gt; 检查 alu 输出是否为 0 的电路\n以上就是 alu 的一个高层次概括。\nalu 我们甚至从零做了几个主要组件,比如行波进位加法器(ripple adder),它们只是一大堆逻辑门巧妙的连在一起而已。\n让我们回到视频开始时的 alu,英特尔 74181,和我们刚刚做的 8 位 alu 不同,74181 只能处理 4 位输入。也就是说,你刚做了一个比英特尔 74181 还好的 alu !\n其实 差不多啦。我们虽然没有全部造出来,但你理解了整体概念。\n74181 用了大概 70 个逻辑门,但不能执行乘除。但它向小型化迈出了一大步,让计算机可以更强大更便宜。4 位 alu 已经要很多逻辑门了,但我们的 8 位 alu 会需要数百个逻辑门。工程师不想在用 alu 时去想那些事情,所以想了一个特殊符号来代表它,看起来像一个大 “v” 。\n又一层抽象!\n我们的 8 位 alu 有两个输入,a 和 b,都是 8 位 (bits)。\n![[assets/pasted image 20230526115726.png|525]]\n我们还需要告诉 alu 执行什么操作 ,例如加法或减法,所以我们用 4 位的操作代码。我们之后的视频会再细说。简言之,“1000\u0026quot;可能代表加法命令,“1100\u0026quot;代表减法命令,操作代码告诉 alu 执行什么操作,输出结果是 8 位的。alu 还会输出一堆标志(flag),“标志\u0026quot;是 1 位的,代表特定状态。比如相减两个数字,结果为 0 ,我们的零测试电路(前面做的)会将 零标志 设为 true(1),如果想知道两个数字是否相等,这个非常有用。如果想知道: a 是否小于 b,可以用 alu 来算 a 减 b,看 负标志 是否为 true 。如果是 true,我们就知道 a 小于 b 。最后,还有一条线连到加法器的进位,如果有溢出,我们就知道,这叫 溢出标志 。\n高级 alu 有更多标志,但这 3 个标志是 alu 普遍用的。其实,我们之后的视频会用到它们。\n现在你知道了,计算机是怎样在没有齿轮或杠杆的情况下进行运算。\n接下来两集,我们会用 alu 做 cpu ,但在此之前,计算机需要一些 “记忆” !\n我们下周会讲。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/05-%E7%AE%97%E6%95%B0%E9%80%BB%E8%BE%91%E5%8D%95%E5%85%83/","summary":"\u003cp\u003ei.e. How Computers Calculate-the ALU\u003c/p\u003e\n\u003cp\u003e上集,我们谈了如何用二进制表示数字,比如二进制 \u003ccode\u003e00101010\u003c/code\u003e 是十进制的 42,表示和存储数字是计算机的重要功能。但\u003cstrong\u003e真正的目标是计算(computation)\u003c/strong\u003e,有意义的处理数字,比如把两个数字相加,这些操作由计算机的 “算术逻辑单元 (Arithmetic and Logic Unit)“处理,但大家会简称:ALU。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eALU 是计算机的数学大脑,等你理解了 ALU 的设计和功能之后,你就理解了现代计算机的基石。\u003c/strong\u003e\u003c/p\u003e","title":"05 算数逻辑单元"},]
[{"content":"i.e. registers and ram\n上集,我们用逻辑门做了个简单 alu,它能执行算术 (arithmetic) 和逻辑 (logic) 运算 ,alu 里的 a 和 l 因此得名。当然,算出来之后如果扔掉就没什么意义了,得找个方法存起来,可能还要进行多个连续操作,这就用到计算机内存了。\n:: 算出来,存起来。\n如果你在主机上打过一场长时间的对局,或玩困难模式的 “扫雷”(minesweeper),然后狗跑过来,被电源线绊倒,把插头拔了出来,你知道失去进度的痛苦 😈 真同情你 :(\n你损失数据的原因是,电脑用的是\u0026quot;随机存取存储器\u0026quot;,简称\u0026quot;ram\u0026quot;(random access memory),它只能在有电的情况下存储东西,比如游戏状态,另一种存储 (memory) 叫持久存储,电源关闭时数据也不会丢失,它用来存其他东西,我们之后会讨论存储 (memory) 的持久性问题。\n今天我们从简单开始 - 做只能存储 1 位的电路,之后再扩大,做出我们的内存模块,下次和 alu 结合起来,做出 cpu !\n只能存储 0 和 1 的电路 我们至今说过的电路都是单向的 - 总是向前流动,比如上集的 8 位 “脉动进位加法器”。但也可以做回向电路,把输出连回输入。\n我们拿一个 or 门试试,把输出连回输入,看看会发生什么。\n首先,两个输入都设为 0 ,“0 or 0” 是 0,所以电路输出 0 ;如果将 a 变成 1 ,“1 or 0” 为 1,所以输出 1 ;一转眼的功夫,输出回到 b ,or 门看到两个输入都是 1 ,“1 or 1” 仍然为 1,所以输出不变 。 如果将 a 变成 0,or 门依然输出 1 ,现在我们有个电路能记录 “1” ,然而有个小问题:这是永久的 ! 无论怎么试,都没法从 1 变回 0 。\n我们换成 and 门看看会怎样。\n开始时,a 和 b 都设 1 ,“1 and 1” 永远输出 1 ;如果之后 a 设为 0,由于是 and 门,输出会变成 0 ,这个电路能记录 0,和之前那个相反。就像之前,无论 a 设什么值,电路始终输出 0 。\n现在有了能存 0 和 1 的电路。\n:: 或门存 1 ,与门存 0\n锁存器 为了做出有用的存储 (memory),我们把两个电路结合起来,这叫 “and-or 锁存器” 。它有两个输入:“设置\u0026quot;输入,把输出变成 1;“复位\u0026quot;输入,把输出变成 0 。如果\u0026quot;设置\u0026quot;和\u0026quot;复位\u0026quot;都是 0,电路会输出最后放入的内容,也就是说,它存住了 1 位的信息!\n![[assets/pasted image 20230526115938.png|500]]\n\u0026gt; and-or 锁存器\n存储!这叫\u0026quot;锁存”(latch), 因为它\u0026quot;锁定\u0026quot;了一个值。放入数据的动作叫 “写入”,拿出数据的动作叫 “读取”。现在我们终于有办法存一个位了!超棒 !\n麻烦的是,用两条线 “设置\u0026quot;和\u0026quot;复位” 来输入,有点难理解。\n⬇️ ⬇️ ⬇️\n为了更容易用,我们希望只有一条输入线,将它设为 0 或 1 来存储值;还需要一根线来\u0026quot;启用\u0026quot;内存,启用时允许写入,没启用时就 “锁定” - 这条线叫 “允许写入线”。加一些额外逻辑门,可以做出这个电路,这叫 “门锁”,因为门可以打开和关上。\n:: 1. 写什么 (0、1)\n:: 2. 是否允许写入\n现在有点复杂了,我们不想关心单独的逻辑门,所以我们提升一层抽象。\n把 “门锁” 放到盒子里 - 这个盒子能存一个 bit 。\n![[assets/pasted image 20230526120031.png|500]]\n\u0026gt; 门锁\n我们来测一下新组件!一切从 0 开始,数据输入从 0 换到 1, 从 1 换到 0,什么也不会发生 - 输出依然是 0,因为 “允许写入线” 是关闭的,所以内容不会变化。所以要给 “允许写入线” 输入 1, “打开” 门,现在往 “数据线” 放 1,1 就能存起来了,注意输出现在是 1 了,成功!现在可以关掉 “允许写入线” ,输出会保持 1,现在不管给 “数据线” 什么值,输出都不会变。值存起来了!现在又打开 “允许写入线” “数据线” 设为 0,完成,“允许写入线” 关闭,输出 0,成功了!\n:: 不难发现,允许写入线关闭的时候,上面的 and 门输出总为 0 。\n寄存器 当然,只能存 1 bit 没什么大用,肯定玩不了游戏,或做其它事情,但我们没限制只能用一个锁存器。如果我们并排放 8 个锁存器,可以存 8 位信息,比如一个 8 bit 数字,一组这样的锁存器叫 “寄存器” 。寄存器能存一个数字,这个数字有多少位,叫 “位宽” 。\n:: 寄存器的‘位宽’就是你放了多少个‘锁存器’~~\n![[assets/pasted image 20230526120105.png|500]]\n\u0026gt; 8 位寄存器 -\u0026gt; d 数据输入 q 数据输出 e 允许写入线\n早期电脑用 8 位寄存器,然后是 16 位,32 位,如今许多计算机都有 64 位宽的寄存器。写入寄存器前,要先启用里面所有锁存器,我们可以用一根线连接所有 “允许写入线”, 把它设为 1 ,然后用 8 条数据线发数据,然后将 “允许写入线” 设回 0 ,现在 8 位的值就存起来了。\n如果只有很少的位 (bits),把锁存器并排放置,也勉强够用了。\n64 位寄存器要 64 根数据线,64 根连到输出端。幸运的是,我们只要 1 根线启用所有锁存器,但加起来也有 129 条线了。如果存 256 位要 513 条线!而这需要不少的钱 💵,怎么办?解决方法是矩阵!\n:: 哈,二维化!\n![[assets/pasted image 20230526120124.png|500]]\n\u0026gt; 16*16 门锁矩阵\n在矩阵中,我们不并列排放锁存器,而是做成网格,存 256 位,我们用 16x16 网格的锁存器,有 16 行 16 列,要启用某个锁存器,就打开相应的 行线 和 列线。\n放大看看怎么做的。\n![[assets/pasted image 20230526120146.png|500]]\n\u0026gt; 锁存器\n我们只想打开交叉处锁存器的 “允许写入线”,所有其他锁存器,保持关闭,我们可以用 and 门!只有 行线和列线 均为 1 ,and 门才输出 1,所以可以用选择单个锁存器。这种行/列排列法,用一根 “允许写入线” 连所有锁存器,为了让锁存器变成 “允许写入”, 行线,列线和 “允许写入线” 都必须是 1 ,每次只有 1 个锁存器会这样。代表我们可以只用一根 “数据线” 连所有锁存器来传数据。因为只有一个锁存器会启用,只有那个会存数据,其他锁存器会忽略数据线上的值,因为没有 “允许写入”。我们可以用类似的技巧,做\u0026quot;允许读取线\u0026quot;来读数据,从一个指定的锁存器,读取数据。所以对于 256 位的存储,只要 35 条线 - 1 条\u0026quot;数据线”, 1 条\u0026quot;允许写入线\u0026quot;, 1 条\u0026quot;允许读取线\u0026quot;,还有 16 行 16 列的线用于选择锁存器 (16+16=32, 32+3=35),这省了好多线!🤑\n💡 看,只需要这些线 :\r\u0026gt; 1 条数据线\r\u0026gt; 1 条允许写入线\r\u0026gt; 1 条允许读取线\r\u0026gt; 16 条行线\r\u0026gt; 16 条列线 但我们需要某种方法来唯一指定交叉路口。\n我们可以想成城市,你可能想和别人在第 12 大道和第 8 街的交界碰面 - 这是一个交叉点的地址,我们刚刚存了一位的地址是 “12 行 8 列”,由于最多 16 行,用 4 位就够了,12 用二进制表示为 1100 ,列地址也可以这样: 8 用二进制表示为 1000 ,刚才说的\u0026quot;12 行 8 列\u0026quot;可以写成 11001000 。\n为了将地址转成行和列,我们需要 “多路复用器” - 这个名字起码比 alu 酷一点,多路复用器有不同大小,因为有 16 行,我们需要 1 到 16 多路复用器。工作方式是:输入一个 4 位数字,它会把那根线,连到相应的输出线,如果输入 0000,它会选择第一列,如果输入 0001,会选择下一列,依此类推……\n![[assets/pasted image 20230526120205.png|500]]\n\u0026gt; 会‘寻址’的多路复用器\n一个多路复用器处理行 (row) ,另一个多路复用器处理列 (column)。\n好吧,开始有点复杂了,那么把 256 位内存当成一个整体好了。 又提升了一层抽象! 它输入一个 8 位地址 :4 位代表列,4 位代表行(纵横经纬,用来唯一确定启动的锁存器位置)。我们还需要 “允许写入线” 和 “允许读取线”,最后,还需要一条数据线,用于读/写数据 。\n:: 💡 提示,这个 ‘8 位地址’ 的含义是 - 定位使用 256 个锁存器中的哪一个。也就是说,虽然我们的 256 位‘寄存器’包含 256 个锁存器,但是同一时间,我们只能使用其中地址相对应的那一个而已。即同一时间,只能存储 1bit 的数。\n:: ❓ 这里可能会产生疑惑?既然只能存 1bit 的数,为什么不直接使用 1 个锁存器来存储???\n![[assets/pasted image 20230526120222.png]]\n:: 关于‘寄存器’和‘内存’的区别,需要留意一下。‘寄存器’一般是指 cpu 上的高速存储,造价不菲,一般不大(128m 都算很大的了),‘内存’你可以理解为内存条,即 ram ,造价相对便宜,常见单条有 8g 、16g 了。\n可寻址内存 不幸的是,256 位的内存也没法做什么事,所以还要扩大规模,把它们并排放置,就像寄存器一样。一行 8 个,可以存一个 8 位数字,8 位也叫一个字节(byte)。为了存一个 8 位数字,我们同时给 8 个 256 位内存一样的地址,每个地址存 1 位,意味着这里总共能存 256 个字节 (byte)。\n![[assets/pasted image 20230526120231.png]]\n\u0026gt; 看,我们获得了一个 1 byte(8 bit) 大小的可寻址内存了\n:: 这里,我们将 8 个‘256 位的寄存器’并排连接,就可以存储 8 bit 的数。这个数的每一位,分别存在‘8 位地址’对应的 8 个 256 位寄存器的相应的锁存器中。\n:: ❕ 也就是说,上图中的‘8 个并排寄存器’,共有 256 个 8 位地址,每个地址可以存储 1 byte(8 bit) 的数,最多可以存储 256 byte 的数。看,锁存器还是得到了充分利用的,这就解答了上面的疑问。\n:: 好的吧,其实这里我是有点震惊的!原来一个 8 位可寻址内存就需要用 8 个 256 内存,每个内存的相同行列处(相同地址)的锁存器启用 - 存储 1 bit 。\n再次,为了简单,我们不管内部,不看作是一堆独立的存储模块和电路,而是看成一个整体的 可寻址内存 。我们有 256 个地址,每个地址能读或写一个 8 位值。\n![[assets/pasted image 20230526120332.png]]\n\u0026gt; 256 byte 大小的内存 - 又抽象了\n:: 上图其实就是前面 8 个并排‘256 位寄存器’的抽象。\n我们下集做 cpu 时会用到这个内存。\n现代计算机的内存扩展到上兆字节(mb)和千兆字节(gb)的方式,和我们这里做的一样,不断把内存打包到更大规模。随着内存地址增多,内存地址也必须增长。8 位最多能代表 256 个内存地址(1111 1111 是 255,0~255 一共 256 个数字),只有这么多。要给千兆或十亿字节的内存寻址,需要 32 位的地址。\n:: 额,指数级增长,就离谱!指数 🐮 🍺 !\n内存的一个重要特性是:可以随时访问任何位置,因此叫 “随机存取存储器” ,简称 ram 。\n当你听到有人说 ram 有多大,他的意思是内存有多大。ram 就像人类的短期记忆,记录当前在做什么事。比如吃了午饭没,或有没有交电话费。\n这是一条真的内存,上面焊了 8 个内存模块。\n![[assets/pasted image 20230526120345.png|450]]\n如果打开其中一个,然后放大,会看到 32 个内存方块。\n![[assets/pasted image 20230526120358.png|450]]\n\u0026gt; 其中一个芯片\n放大其中一个方块,可以看到有 4 个小块。\n![[assets/pasted image 20230526120412.png|400]]\n如果再放大,可以看到存一个\u0026quot;位\u0026quot;的矩阵,这个矩阵是 128 位 x 64 位,总共 8192 位。\n:: 哈哈,这里很好的解答了阅览前方内容时产生的一个疑惑,是矩阵,而不一定是方阵。*\n![[assets/pasted image 20230526120426.png|400]]\n每个方格 4 个矩阵,所以一个方格有 32768 个位 (8192 x 4 = 32768),而一共 32 个方格。总而言之,1 个芯片大约存 100 万位。ram 有 8 个芯片,所以总共 800 万位,也就是 1 兆字节(1 mb)。\n1 mb 如今不算大 - 这是 1980 年代的 ram。\n:: 现在基本是 8g 、16g 大小的了,那么一个 16g 大小的内存就有 16 * 1024 * 1024 * 8 = 2^27 = 134,217,728 位。\n:: 目前最大为单条 64g,也就是 2^29 位,理论上目前电脑支持的最大运行内存为 128g (2^30)。\n如今你可以买到千兆字节(gb)的 ram,那可是数十亿字节的内存。\n今天,我们用锁存器做了一块 sram(静态随机存取存储器),还有其他类型的 ram,如 dram,闪存和 nvram ,它们在功能上与 sram 相似,但用不同的电路存单个位 - 比如用不同的逻辑门,电容器,电荷捕获或忆阻器。但根本上,这些技术都是矩阵层层嵌套,来存储大量信息。 就像计算机中的很多事情,底层其实都很简单,让人难以理解的,是一层层精妙的抽象。\n像一个越来越小的俄罗斯套娃。\n下周见。\n:: 该部分的内存相对来说是有些抽象的,尤其是一些相关的概念可能会混淆 - 如‘寄存器’和‘内存’,它们的原理是一样的,只有‘寄存器’用的材料更好更贵,当然速度也更快。文章的后半部分,我们在纸上模拟出了一块可以存储 256 byte 的内存(8 个并排的 256 位寄存器组成 - 同样,这里用寄存器来描述好像是不太准确)。在真实的示例中,我们也看到了可以存储 1mb 的内存条组成。只能说,学无止境哦!不过基本原理相对来说是简单的,不得不说,现在的许多计算机课程跑偏的挺厉害的,基础原理和应用才是永远的神!\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/06-%E5%AF%84%E5%AD%98%E5%99%A8%E5%92%8C%E5%86%85%E5%AD%98/","summary":"\u003cp\u003ei.e. Registers and RAM\u003c/p\u003e\n\u003cp\u003e上集,我们用逻辑门做了个简单 ALU,它能执行算术 (Arithmetic) 和逻辑 (Logic) 运算 ,ALU 里的 A 和 L 因此得名。当然,算出来之后如果扔掉就没什么意义了,得找个方法存起来,可能还要进行多个连续操作,这就用到计算机内存了。\u003c/p\u003e","title":"06 寄存器和内存"},]
[{"content":"i.e. the central processing unit(cpu)\n今天我们讲 处理器(processors),提示下 - 这集可能是最难的一集,所以一旦你理解了,就会变得超厉害 der~ 😈\n我们已经做了一个算术逻辑单元(alu,arithmetic and logic unit),输入二进制,它会执行计算。我们还做了两种内存:寄存器(registers) - 很小的一块内存,能存一个值;之后我们增大做出了 ram ,ram 是一大块内存,能在不同地址存大量数字。 现在是时候把这些放在一起,组建计算机的 “心脏” 了,但这个 “心脏” 不会有任何包袱,比如人类情感。\n:: 这一部分的描述真好,对上个章节的迷惑内容做了一个很不错的总结!*\n计算机的心脏是\u0026quot;中央处理单元\u0026quot;,简称 “cpu”(central processing unit)。\ncpu 负责执行程序,比如 office,safari 浏览器,你最爱的《半条命 2》。程序由一个个操作组成,这些操作叫 “指令” (instruction),因为它们\u0026quot;指示\u0026quot;计算机要做什么。如果是数学指令,比如加/减(add/subtract),cpu 会让 alu 进行数学运算,也可能是内存指令,cpu 会和内存通信,然后读/写值。\ncpu 里有很多组件,所以我们一边说一边建。我们把重点放在功能,而不是一根根线具体怎么连。当我们用一条线连接两个组件时,这条线只是所有必须线路的一个抽象,这种高层次视角叫 “微体系架构” (microarchitecture)。\n好,我们首先要一些内存,把上集做的 ram 拿来就行。为了保持简单,假设它只有 16 个位置,每个位置存 8 位;再来四个 8 位寄存器,叫 a,b,c,d,寄存器用来 临时存数据 和 操作数据。\n![[assets/pasted image 20230526123425.png|575]]\n我们已经知道数据是以二进制值存在内存里,程序也可以存在内存里。我们可以给 cpu 支持的所有指令,分配一个 id。\n![[assets/pasted image 20230526123431.png|575]]\n在这个假设的例子,我们用前四位存 “操作代码” (operation code),简称 “操作码” (opcode);后四位代表数据来自哪里 - 可以是寄存器或内存地址。\n我们还需要两个寄存器,来完成 cpu:\n一个寄存器追踪程序运行到哪里了,我们叫它 “指令地址寄存器” (instruction address register),顾名思义,存当前指令的内存地址; 另一个寄存器存当前指令,叫 “指令寄存器” (instruction register)。 当启动计算机时,所有寄存器从 0 开始。\n![[assets/pasted image 20230526123505.png|575]]\n\u0026gt; 初始状态\n为了举例,我们在 ram 里放了一个程序,我们今天会过一遍。\ncpu 的第一个阶段叫 “取指令阶段” (fetch phase),负责拿到指令。首先,将 “指令地址寄存器” 连到 ram,寄存器的值为 0,因此 ram 返回地址 0 的值, 0010 1110 会复制到 “指令寄存器” 里,现在指令拿到了。\n\u0026gt; 取指令阶段\n要弄清是什么指令,才能执行(execute),而不是杀死(kill)它,这是 “解码阶段” 。\n![[assets/pasted image 20230526123522.png|575]]\n\u0026gt; 解码阶段\n前 4 位 0010 是 load a 指令,意思是,把 ram 的值放入寄存器 a ;后 4 位 1110 是 ram 的地址,转成十进制是 14 。接下来,指令由 “控制单元” 进行解码,就像之前的所有东西,“控制单元” 也是逻辑门组成的 。比如,为了识别 “load a” 指令,需要一个电路,检查操作码是不是 0010 ,我们可以用很少的逻辑门来实现。\n![[assets/pasted image 20230526123542.png|400]]\n\u0026gt; 检查操作码是否为 load a 的电路\n现在知道了是什么指令,就可以开始执行了,开始 “执行阶段” ,用 “检查是否 load_a 指令的电路”,可以打开 ram 的 “允许读取线”, 把地址 14 传过去,ram 拿到值, 0000 0011 ,十进制的 3 。因为是 load_a 指令,我们想把这个值只放到寄存器 a,其他寄存器不受影响,所以需要一根线,把 ram 连到 4 个寄存器,用 “检查是否 load_a 指令的电路” 启用寄存器 a 的 “允许写入线”,这就成功了 - 把 ram 地址 14 的值,放到了寄存器 a 。\n![[assets/pasted image 20230526123556.png|600]]\n\u0026gt; 执行阶段\n既然指令完成了,我们可以关掉所有线路,去拿下一条指令,我们把 “指令地址寄存器”+1,“执行阶段\u0026quot;就此结束。\nload_a 只是 cpu 可以执行的各种指令之一, 不同指令由不同逻辑电路解码 ,这些逻辑电路会配置 cpu 内的组件来执行对应操作。具体分析这些解码电路太繁琐了,既然已经看了 1 个例子,干脆把 “控制单元 “包成一个整体,简洁一些。\n![[assets/pasted image 20230526123615.png|600]]\n\u0026gt; 抽象了的‘控制单元’\n没错,一层新抽象。\n控制单元就像管弦乐队的指挥,“指挥” cpu 的所有组件, “取指令→解码→执行” 完成后。现在可以再来一次,从 “取指令” 开始,“指令地址寄存器” 现在的值是 1,所以 ram 返回地址 1 里的值:0001 1111 ;到 “解码” 阶段!0001 是 load b 指令,从 ram 里把一个值复制到寄存器 b ,这次内存地址是 1111,十进制的 15;现在到 “执行阶段”!“控制单元” 叫 ram 读地址 15,并配置寄存器 b 接收数据,成功,我们把值 0000 1110 ,也就是十进制的 14 存到了寄存器 b ;最后一件事是 “指令地址寄存器” +1 ,我们又完成了一个循环。 🥳\n![[assets/pasted image 20230526123630.png|600]]\n下一条指令有点不同,来取它吧。\n1000 0100 1000 是 add 指令,这次后面的 4 位不是 ram 地址, 而是 2 位 2 位,分别代表 2 个寄存器。2 位可以表示 4 个值,所以足够表示 4 个寄存器。第一个地址是 01, 代表寄存器 b ,第二个地址是 00, 代表寄存器 a ,因此,1000 0100,代表把寄存器 b 的值,加到寄存器 a 里 。\n![[assets/pasted image 20230526123648.png|600]]\n\u0026gt; add b 到 a\n为了执行这个指令,我们要整合第 5 集的 alu ,“控制单元” 负责选择正确的寄存器作为输入,并配置 alu 执行正确的操作。对于 “add” 指令, “控制单元” 会启用寄存器 b,作为 alu 的第一个输入,还启用寄存器 a,作为 alu 的第二个输入。之前说过,alu 可以执行不同操作,所以控制单元必须传递 add 操作码告诉它要做什么,最后,结果应该存到寄存器 a ,但不能直接写入寄存器 a ,这样新值会进入 alu ,不断和自己相加,因此,控制单元用一个自己的寄存器暂时保存结果,关闭 alu,然后把值写入正确的寄存器。这里 3+14=17,二进制是 0001 0001 ,现在存到了寄存器 a ,和之前一样,最后一件事是把指令地址 + 1 ,这个循环就完成了。\n好,来看最后一个指令:0100 1101 ,解码得知是 store a 指令(把寄存器 a 的值放入内存), ram 地址 13 ,接下来,把地址传给 ram ,但这次不是 “允许读取” ,而是 “允许写入” 。同时,打开寄存器 a 的 “允许读取” ,这样就可以把寄存器 a 里的值,传给 ram 。\n![[assets/pasted image 20230526123700.png|600]]\n\u0026gt; 存储 a 到 ram\n恭喜,我们刚运行了第一个电脑程序!它从内存中加载两个值,相加,然后把结果放回内存。\n刚刚是我一步步来讲的,我们人工切换 cpu 的状态 “取指令→解码→执行” ,但不是每台电脑里都有一个迷你 carrie anne ,其实是 “时钟” 来负责管理 cpu 的节奏。时钟以精确的间隔触发电信号,控制单元会用这个信号,推进 cpu 的内部操作,确保一切按步骤进行 - 就像罗马帆船的船头,有一个人负责按节奏的击鼓,让所有划船的人同步。.. 就像节拍器一样。节奏不能太快,因为就算是电也要一定时间来传输。cpu “取指令→解码→执行” 的速度叫 “时钟速度” ,单位是赫兹 - 赫兹是用来表示频率的单位,1 赫兹代表一秒 1 个周期。因为我花了大概 6 分钟,给你讲了 4 条指令 - 读取→读取→相加→存储 ,所以我的时钟速度大概是 0.03 赫兹,我承认我算数不快,但哪怕有人算数很快,最多也就是一秒一次,或 1 赫兹。\n![[assets/pasted image 20230526123714.png|600]]\n\u0026gt; ‘时钟’ 哦\n第一个单芯片 cpu 是 “英特尔 4004” ,1971 年发布的 4 位 cpu ,它的微架构很像我们之前说的 cpu 。\n![[assets/pasted image 20230526123726.png|600]]\n\u0026gt; 英特尔 4004 的微架构\n![[assets/pasted image 20230526123739.png|300]]\n\u0026gt; 英特尔 4004\n虽然是第一个单芯片的处理器,但它的时钟速度达到了 740 千赫兹 - 每秒 74 万次,你可能觉得很快,但和如今的处理器相比不值一提,一兆赫兹是 1 秒 1 百万个时钟周期,你现在看视频的电脑或手机,肯定有几千兆赫兹 - 1 秒 10 亿次时钟周期。你可能听过有人会把计算机超频(overclocking),意思是修改时钟速度,加快 cpu 的速度 - 就像罗马帆船要撞另一艘船时,鼓手会加快敲鼓速度。芯片制造商经常给 cpu 留一点余地,可以接受一点超频,但超频太多会让 cpu 过热或产生乱码,因为信号跟不上时钟。你可能很少听说降频,但降频其实很有用,有时没必要让处理器全速运行,可能用户走开了,或者在跑一个性能要求较低的程序,把 cpu 的速度降下来,可以省很多电。省电对用电池的设备很重要,比如笔记本和手机。为了尽可能省电,很多现代处理器可以按需求加快或减慢时钟速度,这叫 “动态调整频率” ,加上时钟后,cpu 才是完整的。\n现在可以放到盒子里,变成一个独立组件。\n![[assets/pasted image 20230526123751.png|600]]\n\u0026gt; 抽象的 cpu\n对!一层新的抽象!\nram,上集说过,是在 cpu 外面的独立组件,cpu 和 ram 之间用 “地址线”、“数据线” 和 “允许读/写线” 进行通信。\n虽然今天我们设计的 cpu 是简化版的,但我们提到的很多机制,依然存在于现代处理器里。\n下一集,我们要加强 cpu,给它扩展更多指令,同时开始讲软件。\n下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/07-%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E5%99%A8/","summary":"\u003cp\u003ei.e. The Central Processing Unit(CPU)\u003c/p\u003e\n\u003cp\u003e今天我们讲 处理器(processors),提示下 - 这集可能是最难的一集,所以一旦你理解了,就会变得超厉害 der~ 😈\u003c/p\u003e","title":"07 中央处理器"},]
[{"content":"i.e. instructions \u0026amp; programs\n上集我们把 alu, 控制单元,ram, 时钟 结合在一起,做了个基本,但可用的\u0026quot;中央处理单元\u0026quot;, 简称 cpu ,它是计算机的核心。\n我们已经用电路做了很多组件,这次我们给 cpu 一些指令来运行! cpu 之所以强大,是因为它是可编程的(programmable)- 如果写入不同指令,就会执行不同任务。cpu 是一块硬件,可以被软件控制!\n我们重新看一下上集的简单程序,内存里有这些值,每个地址可以存 8 位数据。因为我们的 cpu 是假设的,这里前 4 位是\u0026quot;操作码\u0026quot;,后 4 位指定一个内存地址,或寄存器,内存地址 0 是 0010 1110 ,前 4 位代表 load_a 指令 - 意思是:把后 4 位指定的内存地址的值,放入寄存器 a ,后 4 位是 1110,十进制的 14 ,我们来把 0010 1110 看成 “load_a 14” 指令,这样更好理解!也更方便说清楚!可以对内存里剩下的数也这样转换,这里,我们的程序只有 4 个指令,还有数字 3 和 14 。\n![[assets/pasted image 20230526123909.png]]\n现在一步步看:\n“load_a 14” 是从地址 14 中拿到数字 3,放入寄存器 a ; “load_b 15” 是从地址 15 中拿到数字 14,放入寄存器 b ; 下一个是 add 指令 - “add b a” 告诉 alu 把寄存器 b 和寄存器 a 里的数字加起来,(b 和 a 的)顺序很重要,因为结果会存在第二个寄存器 - 也就是寄存器 a ; 最后一条指令是 “store_a 13” ,把寄存器 a 的值存入内存地址 13 。 好棒!我们把 2 个数加在了一起!毕竟只有 4 个指令,也只能做这个了 。\n加多一些指令吧!\n![[assets/pasted image 20230526123915.png|450]]\n\u0026gt; 更多的指令\nsub 是减法,和 add 一样也要 2 个寄存器来操作。\n还有 jump(跳转)- 让程序跳转到新位置,如果想改变指令顺序,或跳过一些指令,这个很实用。举例,jump 0 可以跳回开头。 jump 在底层的实现方式 - 是把指令后 4 位代表的内存地址的值覆盖掉 “指令地址寄存器” 里的值 。 还有一个特别版的 jump 叫 jump_negative,它只在 alu 的 “负数标志” 为真时,进行 jump ,第 5 集讲过,算术结果为负,“负数标志\u0026quot;才是真,结果不是负数时,“负数标志\u0026quot;为假。如果是假,jump_negative 就不会执行,程序照常进行。\n我们之前的例子程序,其实应该是这样,才能正确工作。否则跑完 store_a 13 之后, cpu 会不停运行下去,处理后面的 0 ,因为 0 不是操作码,所以电脑会崩掉 !\n我还想指出一点,指令和数据都是存在同一个内存里的,它们在根本层面上毫无区别 - 都是二进制数 。halt 很重要,能区分指令和数据。\n好,现在用 jump 让程序更有趣一些,我们还把内存中 3 和 14 两个数字,改成 1 和 1 ,现在来从 cpu 的视角走一遍程序。\n首先 load_a 14,把 1 存入寄存器 a (因为地址 14 里的值是 1);然后 load_b 15,把 1 存入寄存器 b (因为地址 15 里的值也是 1);然后 add b a 把寄存器 b 和 a 相加 结果放到寄存器 a 里,现在寄存器 a 的值是 2 (当然是以二进制存的);然后 store_a 13 指令,把寄存器 a 的值存入内存地址 13。\n现在遇到 jump 2 指令 ,cpu 会把\u0026quot;指令地址寄存器\u0026quot;的值,现在是 4,改成 2 ,因此下一步不再是 halt ,而是读内存地址 2 里的指令,也就是 add b a 。\n我们跳转了!\n寄存器 a 里是 2,寄存器 b 里是 1 ,1+2=3,寄存器 a 变成 3 ,存入内存 ,又碰到 jump 2,又回到 add b a 。 1+3=4 ,现在寄存器 a 是 4 。\n发现了吗?每次循环都+1 ,不断增多 ,酷! 但没法结束啊 ,永远不会碰到 halt ,总是会碰到 jump ,这叫无限循环 - 这个程序会永远跑下去。.. 下去。.. 下去。.. 下去。.. 为了停下来,我们需要有条件的 jump , 只有特定条件满足了,才执行 jump 。比如 jump negative 就是条件跳转的一个例子,还有其他类型的条件跳转,比如、 jump if equal(如果相等)、jump if greater(如果更大)。\n现在把代码弄花哨一点,再过一遍代码。\n就像之前,程序先把内存值放入寄存器 a 和 b 。寄存器 a 是 11,寄存器 b 是 5 ;sub b a,用 a 减 b,11-5=6 ,6 存入寄存器 a ;jump negative 出场,上一次 alu 运算的结果是 6 ,是正数,所以 “负数标志” 是假 ,因此处理器不会执行 jump 。\n![[assets/pasted image 20230526123933.png]]\n继续下一条指令 - jump 2 , jump 2 没有条件,直接执行!又回到寄存器 a-b,6-5=1 ,a 变成 1 ;\n下一条指令又是 jump negative ,因为 1 还是正数,因此 jump negative 不会执行 ;来到下一条指令,jump 2 ,又来减一次 ,这次就不一样了 1-5=-4 ,这次 alu 的 “负数标志” 是真,现在下一条指令, jump negative 5,cpu 的执行跳到内存地址 5 ,跳出了无限循环!\n现在的指令是 add b a,-4+5=1,1 存入寄存器 a ;下一条指令 store_a 13,把 a 的值存入内存地址 13 ,最后碰到 halt 指令,停下来。\n虽然程序只有 7 个指令,但 cpu 执行了 13 个指令,因为在内部循环了 2 次。\n这些代码其实是算余数的,11 除 5 余 1 。\n如果加多几行指令,我们还可以跟踪循环了多少次,11 除 5,循环 2 次,余 1 。当然,我们可以用任意 2 个数,7 和 81,18 和 54,什么都行,这就是软件的强大之处。\n软件还让我们做到硬件做不到的事,alu 可没有除法功能,是程序给了我们这个功能。别的程序也可以用我们的除法程序,来做其他事情。\n这意味着一层新抽象!\n我们这里假设的 cpu 很基础,所有指令都是 8 位,操作码只占了前面 4 位,即便用尽 4 位,也只能代表 16 个指令,而且我们有几条指令,是用后 4 位来指定内存地址。因为 4 位最多只能表示 16 个值,所以我们只能操作 16 个地址,这可不多。我们甚至不能 jump 17 ,因为 4 位二进制无法表示数字 17 ,因此,真正的现代 cpu 用两种策略: 最直接的方法是用更多位来代表指令,比如 32 位或 64 位 - 这叫 “指令长度” ;第二个策略是 “可变指令长度” 。\n举个例子,比如某个 cpu 用 8 位长度的操作码,如果看到 halt 指令,halt 不需要额外数据,那么会马上执行。如果看到 jump,它得知道位置值,这个值在 jump 的后面,这叫 “立即值”。这样设计,指令可以是任意长度,但会让读取阶段复杂一点点。要说明的是,我们拿来举例的 cpu 和指令集都是假设的,是为了展示核心原理。\n![[assets/pasted image 20230526123942.png]]\n所以我们来看个真的 cpu 例子。1971 年,英特尔发布了 4004 处理器。这是第一次把 cpu 做成一个芯片,给后来的英特尔处理器打下了基础。它支持 46 个指令,足够做一台能用的电脑,它用了很多我们说过的指令,比如 jump add sub load ,它也用 8 位的\u0026quot;立即值\u0026quot;来执行 jump, 以表示更多内存地址。处理器从 1971 年到现在发展巨大,现代 cpu, 比如英特尔酷睿 i7, 有上千个指令和指令变种,长度从 1 到 15 个字节。\n举例,光 add 指令就有很多变种!指令越来越多,是因为给 cpu 设计了越来越多功能。\n下集我们会讲。\n下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/08-%E6%8C%87%E4%BB%A4%E5%92%8C%E7%A8%8B%E5%BA%8F/","summary":"\u003cp\u003ei.e. Instructions \u0026amp; Programs\u003c/p\u003e\n\u003cp\u003e上集我们把 ALU, 控制单元,RAM, 时钟 结合在一起,做了个基本,但可用的\u0026quot;中央处理单元\u0026quot;, 简称 CPU ,它是计算机的核心。\u003c/p\u003e\n\u003cp\u003e我们已经用电路做了很多组件,这次我们给 CPU 一些指令来运行! CPU 之所以强大,是因为它是可编程的(programmable)- 如果写入不同指令,就会执行不同任务。CPU 是一块硬件,可以被软件控制!\u003c/p\u003e","title":"08 指令和程序"},]
[{"content":"i.e. advanced cpu designs\n随着本系列进展,我们知道计算机进步巨大,从 1 秒 1 次运算,到现在有千赫甚至兆赫的 cpu ,你现在看视频的设备八成也有 ghz 速度 ,1 秒十亿条指令 ,这是很大的计算量!\n早期计算机的提速方式是减少晶体管的切换时间 ,晶体管组成了逻辑门,alu 以及前几集的其他组件,但这种提速方法最终会碰到瓶颈,所以处理器厂商发明各种新技术来提升性能,不但让简单指令运行更快,也让它能进行更复杂的运算。\n上集我们写了个做除法的程序,给 cpu 执行,方法是做一连串减法,比如 16 除 4 会变成 - 16-4 -4 -4 -4 ,碰到 0 或负数才停下 。但这种方法要多个时钟周期,很低效。所以现代 cpu 直接在硬件层面设计了除法,可以直接给 alu 除法指令,这让 alu 更大也更复杂一些,但也更厉害 。 复杂度 vs 速度的平衡 在计算机发展史上经常出现。\n举例,现代处理器有专门电路来处理图形操作,解码压缩视频,加密文档 等等。如果用标准操作来实现,要很多个时钟周期。你可能听过某些处理器有 mmx, 3dnow, sse ,它们有额外电路做更复杂的操作,用于游戏和加密等场景。指令不断增加,人们一旦习惯了它的便利就很难删掉,所以为了兼容旧指令集,指令数量越来越多。\n英特尔 4004,第一个集成 cpu,有 46 条指令,足够做一台能用的计算机,但现代处理器有上千条指令,有各种巧妙复杂的电路,超高的时钟速度带来另一个问题 - 如何快速传递数据给 cpu 。就像有强大的蒸汽机,但无法快速加煤。ram 成了瓶颈,ram 是 cpu 之外的独立组件,意味着数据要用线来传递,叫\u0026quot;总线\u0026quot;,总线可能只有几厘米,别忘了电信号的传输接近光速,但 cpu 每秒可以处理上亿条指令,很小的延迟也会造成问题。ram 还需要时间找地址取数据,配置,输出数据,一条\u0026quot;从内存读数据\u0026quot;的指令可能要多个时钟周期,cpu 空等数据。\n缓存 解决延迟的方法之一是给 cpu 加一点 ram - 叫 “缓存” ,因为处理器里空间不大,所以缓存一般只有 kb 或 mb ,而 ram 都是 gb 起步。缓存提高了速度,cpu 从 ram 拿数据时,ram 不用传一个,可以传一批,虽然花的时间久一点,但数据可以存在缓存,这很实用,因为数据常常是一个个按顺序处理。\n![[assets/pasted image 20230526124024.png|400]]\n\u0026gt; cpu 缓存\n举个例子,算餐厅的当日收入,先取 ram 地址 100 的交易额,ram 与其只给 1 个值,直接给一批值,把地址 100 到 200 都复制到缓存。当处理器要下一个交易额时,地址 101,缓存会说:“我已经有了,现在就给你”,不用去 ram 取数据,因为缓存离 cpu 近,一个时钟周期就能给数据 - cpu 不用空等!比反复去 ram 拿数据快得多。如果想要的数据已经在缓存,叫 缓存命中 ,如果想要的数据不在缓存,叫 缓存未命中 。缓存也可以当临时空间,存一些中间值,适合长/复杂的运算。继续餐馆的例子,假设 cpu 算完了一天的销售额,想把结果存到地址 150,就像之前,数据不是直接存到 ram ,而是存在缓存,这样不但存起来快一些,如果还要接着算,取值也快一些。\n但这样带来了一个有趣的问题,缓存和 ram 不一致了 😈。这种不一致必须记录下来,之后要同步,因此缓存里每块空间有一个特殊标记,叫 “脏位” (dirty bit) - 这可能是计算机科学家取的最贴切的名字。同步一般发生在 当缓存满了而 cpu 又要缓存时,在清理缓存腾出空间之前,会先检查 “脏位”。如果是\u0026quot;脏\u0026quot;的,在加载新内容之前,会把数据写回 ram 。\n指令流水线 另一种提升性能的方法叫 “指令流水线” 。\n想象下你要洗一整个酒店的床单,但只有 1 个洗衣机,1 个干燥机,选择 1:按顺序来,放洗衣机等 30 分钟洗完,然后拿出湿床单,放进干燥机等 30 分钟烘干,这样 1 小时洗一批;另外一说:如果你有 30 分钟就能烘干的干燥机,请留言告诉我是什么牌子,我的至少要 90 分钟。即使有这样的神奇干燥机,我们可以用\u0026quot;并行处理\u0026quot;进一步提高效率。就像之前,先放一批床单到洗衣机,等 30 分钟洗完,然后把湿床单放进干燥机,但这次,与其干等 30 分钟烘干,可以放另一批进洗衣机,让两台机器同时工作,30 分钟后,一批床单完成,另一批完成一半,另一批准备开始,效率 x2 !🎉\n处理器也可以这样设计。\n第 7 集,我们演示了 cpu 按序处理 - 取指 → 解码 → 执行,不断重复。这种设计,三个时钟周期执行 1 条指令,但因为每个阶段用的是 cpu 的不同部分,意味着可以并行处理(parallelize)!“执行\u0026quot;一个指令时,同时\u0026quot;解码\u0026quot;下一个指令,“读取\u0026quot;下下个指令,不同任务重叠进行,同时用上 cpu 里所有部分。\n![[assets/pasted image 20230526124045.png|550]]\n这样的流水线每个时钟周期执行 1 个指令,吞吐量 x 3 。\n和缓存一样,这也会带来一些问题。\n第一个问题是指令之间的依赖关系,举个例子,你在读某个数据,而正在执行的指令会改这个数据,也就是说拿的是旧数据,因此流水线处理器要先弄清数据依赖性,必要时停止流水线,避免出问题。高端 cpu,比如笔记本和手机里那种,会更进一步,动态排序有依赖关系的指令,最小化流水线的停工时间,这叫 “乱序执行” 。和你猜的一样,这种电路非常复杂,但因为非常高效,几乎所有现代处理器都有流水线。\n第二个问题是 “条件跳转”,比如上集的 jump negative ,这些指令会改变程序的执行流。简单的流水线处理器,看到 jump 指令会停一会儿,等待条件值确定下来,一旦 jump 的结果出了,处理器就继续流水线。因为空等会造成延迟,所以高端处理器会用一些技巧,可以把 jump 想成是 “岔路口”,高端 cpu 会猜哪条路的可能性大一些,然后提前把指令放进流水线,这叫 “推测执行” 。当 jump 的结果出了,如果 cpu 猜对了,流水线已经塞满正确指令,可以马上运行,如果 cpu 猜错了,就要清空流水线,就像走错路掉头。让 gps 不要再!叫!了!为了尽可能减少清空流水线的次数,cpu 厂商开发了复杂的方法,来猜测哪条分支更有可能,叫 “分支预测” ,现代 cpu 的正确率超过 90% 。\n理想情况下,流水线一个时钟周期完成 1 个指令,然后\u0026quot;超标量处理器\u0026quot;出现了,一个时钟周期完成多个指令。即便有流水线设计,在指令执行阶段,处理器里有些区域还是可能会空闲。比如,执行一个 “从内存取值” 指令期间, alu 会闲置,所以一次性处理多条指令(取指令+解码) 会更好。如果多条指令要 alu 的不同部分,就多条同时执行。我们可以再进一步,加多几个相同的电路,执行出现频次很高的指令。举例,很多 cpu 有四个,八个甚至更多完全相同的 alu ,可以同时执行多个数学运算。\n![[assets/pasted image 20230526124057.png|550]]\n好了,目前说过的方法,都是优化 1 个指令流的吞吐量。\n另一个提升性能的方法是同时运行多个指令流,用多核处理器。你应该听过双核或四核处理器,意思是一个 cpu 芯片里,有多个独立处理单元,很像是有多个独立 cpu,但因为它们整合紧密,可以共享一些资源,比如缓存,使得多核可以合作运算,但多核不够时,可以用多个 cpu 。高端计算机,比如现在给你传视频的 youtube 服务器,需要更多马力,让上百人能同时流畅观看,2 个或 4 个 cpu 是最常见的。\n![[assets/pasted image 20230526124112.png]]\n\u0026gt; 多核处理器\n但有时人们有更高的性能要求,所以造了超级计算机!如果要做怪兽级运算,比如模拟宇宙形成,你需要强大的计算能力,给普通台式机加几个 cpu 没什么用,你需要很多处理器! 不…不…还要更多,更多。截止至视频发布,世上最快的计算机在中国无锡的国家超算中心 - 神威·太湖之光有 40960 个 cpu,每个 cpu 有 256 个核心,总共超过 1 千万个核心,每个核心的频率是 1.45ghz ,每秒可以进行 9.3 亿亿次浮点数运算,也叫 每秒浮点运算次数 (flops)。相信我,这个速度很可怕,没人试过跑最高画质的《孤岛危机》但我估计没问题。\n![[assets/pasted image 20230526124139.png]]\n\u0026gt; 神威·太湖之光\n长话短说,这些年处理器不但大大提高了速度,而且也变得更复杂,用各种技巧,榨干每个时钟周期 做尽可能多运算。我们的任务是利用这些运算能力,做又酷又实用的事。\n编程就是为了这个,我们下集说。\n下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/09-%E9%AB%98%E7%BA%A7-cpu-%E8%AE%BE%E8%AE%A1/","summary":"\u003cp\u003ei.e. Advanced CPU Designs\u003c/p\u003e\n\u003cp\u003e随着本系列进展,我们知道计算机进步巨大,从 1 秒 1 次运算,到现在有千赫甚至兆赫的 CPU ,你现在看视频的设备八成也有 GHz 速度 ,1 秒十亿条指令 ,这是很大的计算量!\u003c/p\u003e\n\u003cp\u003e早期计算机的提速方式是减少晶体管的切换时间 ,晶体管组成了逻辑门,ALU 以及前几集的其他组件,但这种提速方法最终会碰到瓶颈,所以处理器厂商发明各种新技术来提升性能,不但让简单指令运行更快,也让它能进行更复杂的运算。\u003c/p\u003e","title":"09 高级 cpu 设计"},]
[{"content":"i.e. early programming\n前几集我们把重点放在计算机的原理,怎么从内存读写数据,执行操作,比如把两个数字加在一起,还简单讲了下指令的执行,也就是计算机程序。\n但我们还没讲的是:程序如何\u0026quot;进入\u0026quot;计算机。你应该记得在第 7, 8 集,我们一步步讲了例子程序,当时为了简单,我们假设程序已经魔法般在内存里了,但事实是,程序需要加载进内存,这不是魔法,是计算机科学!\n给机器编程这个需求,早在计算机出现之前就有了。\n最著名的例子来自纺织业,如果你只想织一块红色大桌布,可以直接放红线进织布机,但如果想要图案怎么办?比如条纹或者方格。工人要每隔一会儿调整一次织布机,因为非常消耗劳动力,所以图案纺织品很贵。特定位置有没有穿孔,决定了线是高是低,横线是从上/从下穿过,为了让每行图案不同,纸卡连成长条,形成连续指令,听起来很熟?\n![[assets/pasted image 20230526124243.png|400]]\n\u0026gt; 雅卡尔织布机\n很多人认为雅卡尔织布机是最早的编程。\n事实证明,穿孔纸卡便宜、可靠、也易懂。近一个世纪后,穿孔纸卡用于 1890 年美国人口普查,我们在第一集提过,一张卡存一个人的信息,比如种族、婚姻状况、子女数量、出生国家等等。针对每个问题,人口普查工作者会在对应位置打孔,当卡片插入汇总机,孔会让对应总和值+1 ,可以插入整个国家人口的卡片,在结束后得到各个总值。值得注意的是,早期汇总机不算计算机,因为它们只做一件事 - 汇总数据,操作是固定的,不能编程,穿孔纸卡存的是数据,不是程序。\n![[assets/pasted image 20230526124252.png|400]]\n\u0026gt; 穿孔纸卡 \u0026amp; 汇总机\n之后 60 年,这些机器被加强,可以做减、乘、除,甚至可以做一些小决定,决定何时执行某指令。为了正确执行不同计算,程序员需要某种控制面板,面板有很多小插孔,程序员可以插电线,让机器的不同部分互相传数据和信号,因此也叫 “插线板”。\n![[assets/pasted image 20230526124301.png|400]]\n\u0026gt; 插线板\n不幸的是,这意味着运行不同程序要重新接线,所以到 1920 年代,控制面板变成了可拔插,让编程更方便,可以给机器插入不同程序。比如,一个插线板算销售税,另一个算工资单,但给插线板编程很复杂,图中乱成一团的线负责算盈亏总额,用于 ibm 402 核算机。在 1940 年代这样做很流行,用插线板编程不只在机电计算机流行。世上第一台通用电子计算机,eniac,完成于 1946 年,用了一大堆插线板,程序在纸上设计好之后,给 eniac 连线,最多可能花三个星期。因为早期计算机非常昂贵,停机几个星期只为换程序完全无法接受。人们急需更快、更灵活的新方式来编程。\n幸运的是,到 1940 年代晚期 1950 年代初,内存变得可行,价格下降,容量上升。与其把程序存在插线板,存在内存变得可行,这样程序易于修改、方便 cpu 快速读取,这类机器叫 “存储程序计算机”。如果内存足够,不仅可以存要运行的程序,还可以存程序需要的数据,包括程序运行时产生的新数据。\n程序和数据都存在一个地方,叫 “冯诺依曼结构” ,命名自 约翰·冯·诺依曼 - 杰出的数学家和物理学家 ,参与了曼哈顿计划和早期电子计算机项目。他曾说:我在思考比炸弹重要得多的东西 - 计算机 。\n冯诺依曼计算机的标志是,一个处理器(有算术逻辑单元) + 数据寄存器 + 指令寄存器 + 指令地址寄存器 + 内存(负责存数据和指令) 。\n希望这听起来很耳熟,因为第 7 集我们造了一个冯诺依曼计算机。\n第一台冯诺依曼架构的\u0026quot;储存程序计算机\u0026quot;,由曼彻斯特大学于 1948 年建造完成,绰号\u0026quot;宝宝\u0026quot;,甚至你现在看视频的计算机,也在用一样的架构。虽然有内存很棒,但程序和数据依然需要某种方式输入计算机,所以用穿孔纸卡。\n让我们进入思维泡泡\n到 1980 年代,几乎所有的计算机都有穿孔纸卡读取器,可以吸入一张卡片,把卡片内容写进内存。如果放了一叠卡片,读取器会一个个写进内存,一旦程序和数据写入完毕,电脑会开始执行,即便简单程序也有几百条指令,要用一叠纸卡来存。如果不小心摔倒弄撒了,要花上几小时、几天、甚至几周来整理。\n有个小技巧是在卡片侧面画对角线,如果弄散了,整理起来会方便很多。\n用纸卡的最大型程序是美国空军的 sage 防空系统,于 1955 年完成。据称顶峰时期雇佣了世上 20% 程序员,主控制程序用了 62500 张穿孔纸卡,等同于大约 5mb 的数据,以如今的标准,不值一提。穿孔纸卡不仅可以往计算机放数据,还可以取出数据,程序运行到最后,结果可以输到纸卡上,方式嘛,当然是打孔,然后人可以分析结果,或者再次放进计算机,做进一步计算。\n谢了 思维泡泡\n穿孔纸卡的亲戚是纸带。基本是一回事,只不过更连续,不是一张张卡。当然我们还没提硬盘,只读光盘,dvd, u 盘等等,以后我们会讲这些更先进的存储方法。\n![[assets/pasted image 20230526124312.png|400]]\n\u0026gt; 纸带\n最后,除了插线板和穿孔纸卡,在 1980 年代前,还有一种常见编程方式,面板编程。与其插一堆线到插线板,可以用一大堆开关和按钮,做到一样的效果,面板上有指示灯,代表各种函数的状态和内存中的值。50 和 60 年代的计算机,一般都有这样巨大的控制台,很少有人只用开关来输入一整个程序,但技术上是可行的。早期针对计算机爱好者的家用计算机,大量使用了开关,因为大多数家庭用户负担不起昂贵的外围设备,比如穿孔纸卡读取器。\n![[assets/pasted image 20230526124323.png|400]]\n\u0026gt; 面板编程\n第一款取得商业成功的家用计算机是 altair 8800 ,有两种版本可以买:1. 预先装好的整机 ;2. 需要组装的组件。计算机爱好者 喜欢买组件版,售价极低,在 1975 年卖 400 美元左右,相当于 2017 年的 2000 美元。为了给 8800 编程,你要拨动面板上的开关,输入二进制操作码,然后按 “存储键” 把值存入内存,然后会到下一个内存位置,你可以再次拨开关,写下一个指令,重复这样做,把整个程序都写入内存之后,可以推动开关,回到内存地址 0 ,然后按运行按钮,灯会闪烁,这就是 1975 年的家用计算机,哇。\n![[assets/pasted image 20230526124336.png|400]]\n\u0026gt; altair 8800\n不管是插线板、开关或穿孔纸卡,早期编程都是专家活。不管是全职还是技术控,都要非常了解底层硬件,比如 操作码,寄存器等,才能写程序,所以编程很难,很烦。哪怕工程师和科学家都无法 完全发挥计算机的能力,我们需要一种更简单方式告诉计算机要做什么。\n一种更简单的编程方式!\n这带领我们到下一个话题 - 编程语言,我们下集会讲。\n下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/10-%E6%97%A9%E6%9C%9F%E7%9A%84%E7%BC%96%E7%A8%8B%E6%96%B9%E5%BC%8F/","summary":"\u003cp\u003ei.e. Early Programming\u003c/p\u003e\n\u003cp\u003e前几集我们把重点放在计算机的原理,怎么从内存读写数据,执行操作,比如把两个数字加在一起,还简单讲了下指令的执行,也就是计算机程序。\u003c/p\u003e\n\u003cp\u003e但我们还没讲的是:程序如何\u0026quot;进入\u0026quot;计算机。你应该记得在第 7, 8 集,我们一步步讲了例子程序,当时为了简单,我们假设程序已经魔法般在内存里了,但事实是,程序需要加载进内存,这不是魔法,是计算机科学!\u003c/p\u003e","title":"10 早期的编程方式"},]
[{"content":"i.e. the first programming languages\n之前我们把重点放在硬件 - 组成计算机的物理组件,比如电,电路,寄存器,ram,alu,cpu ,但在硬件层面编程非常麻烦。\n所以程序员想要一种更通用的方法编程 - 一种\u0026quot;更软的\u0026quot;媒介。\n没错,我们要讲软件!\n第 8 集我们一步步讲了一个简单程序,第一条指令在内存地址 0:0010 1110 ,之前说过,前 4 位是操作码 ,简称 opcode 。对于这个假设 cpu,0010 代表 load_a 指令 - 把值从内存复制到寄存器 a ,后 4 位是内存地址,1110 是十进制的 14 ,所以这 8 位表达的意思是 “读内存地址 14,放入寄存器 a”。\n只是用了两种不同语言,可以想成是英语和摩尔斯码的区别 - “你好” 和 “…. . .-.. .-.. —” 是一个意思:你好。只是编码方式不同,英语和摩尔斯码的复杂度也不同,英文有 26 个字母以及各种发音,摩尔斯码只有\u0026quot;点\u0026quot;和\u0026quot;线\u0026quot;,但它们可以传达相同的信息,计算机语言也类似。\n机器语言 计算机能处理二进制,二进制是处理器的\u0026quot;母语\u0026quot;,事实上,它们只能理解二进制,这叫 “机器语言” 或 “机器码”。\n在计算机早期阶段,必须用机器码写程序。具体来讲,会先在纸上用英语写一个\u0026quot;高层次版\u0026quot;。举例:“从内存取下一个销售额,然后加到天、周、年的总和,然后算税”,等等。.. 这种对程序的高层次描述,叫 “伪代码” 。在纸上写好后,用\u0026quot;操作码表\u0026quot;把伪代码转成二进制机器码,翻译完成后,程序可以喂入计算机并运行。\n汇编语言 你可能猜到了,很快人们就厌烦了。所以在 1940~1950 年代,程序员开发出一种新语言, 更可读、更高层次,每个操作码分配一个简单名字,叫\u0026quot;助记符\u0026quot;,“助记符\u0026quot;后面紧跟数据,形成完整指令。与其用 1 和 0 写代码,程序员可以写\u0026quot;load_a 14”,我们在第 8 集用过这个助记符,因为容易理解得多!\n当然,cpu 不知道 load_a 14 是什么,它不能理解文字,只能理解二进制。所以程序员想了一个技巧,写二进制程序来帮忙,它可以读懂文字指令,自动转成二进制指令,这种程序叫 - 汇编器(assembler)。汇编器读取用\u0026quot;汇编语言\u0026quot;写的程序,然后转成\u0026quot;机器码\u0026quot; ,“load_a 14” 是一个汇编指令的例子。\n:: 对的,把翻译工作交给了‘汇编器’ - 一个二进制程序。🤣*\n随着时间推移,汇编器有越来越多功能,让编程更容易。\n其中一个功能是自动分析 jump 地址。\n这里有一个第 8 集用过的例子:注意,jump negative 指令跳到地址 5 ,jump 指令跳到地址 2 ,问题是,如果在程序开头多加一些代码,所有地址都会变,更新程序会很痛苦!所以汇编器不用固定跳转地址,而是让你插入可跳转的标签,当程序被传入汇编器,汇编器会自己搞定跳转地址。程序员可以专心编程,不用管底层细节。\n![[assets/pasted image 20230526124425.png|400]]\n隐藏不必要细节来做更复杂的工作。\n我们又提升了一层抽象!🔆\n然而,即使汇编器有这些厉害功能,比如自动跳转。汇编只是修饰了一下机器码,一般来说,一条汇编指令对应一条机器指令,所以汇编码和底层硬件的连接很紧密。汇编器仍然强迫程序员思考用什么寄存器和内存地址,如果你突然要一个额外的数,可能要改很多代码。\n让我们进入思考泡泡\n葛丽丝·霍普博士 也遇到了这个问题,作为美国海军军官,她是哈佛 1 号计算机的首批程序员之一,这台机器我们在第 2 集提过。这台巨大机电野兽在 1944 年战时建造完成,帮助盟军作战,程序写在打孔纸带上,放进计算机执行。顺便一说,如果程序里有漏洞,真的就 直接用胶带来补\u0026quot;漏洞\u0026quot;。\n![[assets/pasted image 20230526124434.png|400]]\n\u0026gt; 补‘漏洞’\nmark 1 的指令集非常原始,甚至没有 jump 指令,如果代码要跑不止一次,得把带子的两端连起来做成循环。换句话说,给 mark 1 编程简直是噩梦!\n战后,霍普继续在计算机前沿工作,为了释放电脑的潜力,她设计了一个高级编程语言,叫\u0026quot;算术语言版本 0\u0026quot;,简称\u0026quot;a-0\u0026quot; 。\n汇编与机器指令是一一对应的,但一行高级编程语言 可能会转成几十条二进制指令。为了做到这种复杂转换 hopper 在 1952 年创造了第一个编译器,编译器专门把高级语言 转成低级语言,比如汇编或机器码(cpu 可以直接执行机器码)。\n谢了 思想泡泡\n尽管\u0026quot;使编程更简单\u0026quot;很诱人,但很多人对霍普的点子持怀疑态度。她曾说 “我有能用的编译器,但没人愿意用,他们告诉我计算机只能做算术,不能运行程序”。\n但这个点子是好的,不久,很多人尝试创造新编程语言 - 如今有上百种语言!\n可惜的是,没有任何 a-0 的代码遗留下来,所以我们用 python 举例(一门现代编程语言)。\n假设我们想相加两个数字,保存结果。记住,如果用汇编代码,我们得从内存取值,和寄存器打交道,以及其他底层细节,但同样的程序可以用 python 这样写:\n![[assets/pasted image 20230526124447.png|600]]\n不用管寄存器或内存位置 - 编译器会搞定这些细节,不用管底层细节。\n程序员只需要创建 代表内存地址的抽象,叫 “变量” ,给变量取名字,现在可以把两个数 存在变量里,这里取名 a 和 b, 实际编程时你可以随便取名;然后相加两个数,把结果存在变量 c 。底层操作时,编译器可能把变量 a 存在寄存器 a ,但我不需要知道这些!眼不见心不烦 !😼 这是个重要历史里程碑,但 a-0 和之后的版本没有广泛使用。\n高级程序语言 fortran,名字来自 “公式翻译”(formula translation),这门语言数年后由 ibm 在 1957 年发布,主宰了早期计算机编程。\njohn backus, the fortran project director, fortran 项目总监 john backus 说过,“我做的大部分工作都是因为懒,我不喜欢写程序,所以我写这门语言,让编程更容易”\n你懂的,典型的\u0026quot;懒人\u0026quot;,😎 创造自己的编程语言。\n平均来说,fortran 写的程序,比等同的手写汇编代码短 20 倍,然后 fortran 编译器会把代码转成机器码。人们怀疑性能是否比得上手写代码,但因为能让程序员写程序更快,所以成了一个更经济的选择,运行速度慢一点点,编程速度大大加快。\n当时 ibm 在卖计算机,因此最初 fortran 代码只能跑在 ibm 计算机上。1950 年代大多数编程语言和编译器只能运行在一种计算机上,如果升级电脑,可能要重写所有代码!因此工业界,学术界,政府的计算机专家在 1959 年组建了一个联盟 - 数据系统语言委员会,grace hopper 担任顾问,开发一种通用编程语言,可以在不同机器上通用,最后诞生了一门高级,易于使用,“普通面向商业语言”,简称 cobol(common business-oriented language) 。\n为了兼容不同底层硬件,每个计算架构需要一个 cobol 编译器。最重要的是,这些编译器都可以接收相同 cobol 代码,不管是什么电脑,这叫\u0026quot;一次编写,到处运行\u0026quot; 。如今大多数编程语言都是这样,不必接触 cpu 特有的汇编码和机器码,减小了使用门槛。\n在高级编程语言出现之前,编程只是计算机专家和爱好者才会做的事,而且通常是主职,但现在,科学家,工程师,医生,经济学家,教师等等,都可以把计算机用于工作。\n感谢这些语言!\n计算机科学从深奥学科 变成了大众化工具,同时,编程的抽象也让计算机专家,现在叫\u0026quot;专业程序员\u0026quot;,制作更复杂的程序,如果用汇编写可能要上百万行。\n当然,计算机的历史没有在 1959 年结束,编程语言设计的黄金时代才刚刚开始,和硬件一起飞速发展。\nin the 1960s, we had languages like algol, lisp and basic. 在 1960 年代,有 algol, lisp 和 basic 等语言,70 年代有:pascal,c 和 smalltalk ,80 年代有:c++,objective-c 和 perl ,90 年代有:python,ruby 和 java ,新千年 swift, c#, go 在崛起 。 有些语言你可能听起来耳熟 - 很多现在还存在,你现在用的浏览器很可能是 c++ 或 objective-c 写的。\n我刚才说的编程语言名字只是冰山一角,新的编程语言在不断诞生。新语言想用更聪明的抽象,让某些方面更容易或更强大,或利用新技术和新平台带来的优势,让更多人能快速做出美妙的事情。许多人认为编程的\u0026quot;圣杯\u0026quot;是直接用英文,直接对计算机说话,然后它会理解并执行,这种智能系统目前只存在于科幻小说,“2001:太空漫游” 的粉丝可能没什么意见。\n现在你理解了编程语言,接下来几集我们会深入了解编程语言和用语言写的软件,是怎么做到那些酷事。\n下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/11-%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%E5%8F%91%E5%B1%95%E5%8F%B2/","summary":"\u003cp\u003ei.e. The First Programming Languages\u003c/p\u003e\n\u003cp\u003e之前我们把重点放在硬件 - 组成计算机的物理组件,比如电,电路,寄存器,RAM,ALU,CPU ,但在硬件层面编程非常麻烦。\u003c/p\u003e\n\u003cp\u003e所以程序员想要一种更通用的方法编程 - 一种\u0026quot;更软的\u0026quot;媒介。\u003c/p\u003e\n\u003cp\u003e没错,我们要讲软件!\u003c/p\u003e","title":"11 编程语言发展史"},]
[{"content":"i.e. programming basics - statements \u0026amp; functions\n上集讲到用机器码写程序,还要处理那么多底层细节,对写大型程序是个巨大障碍。为了脱离底层细节,开发了编程语言,让程序员专心解决问题,不用管硬件细节。\n:: 底层各类太多了,编写麻烦,调试麻烦,改动麻烦……\n语句 今天我们讨论大多数编程语言都有的基本元素,就像口语一样,编程语言有\u0026quot;语句\u0026quot;。语句表达单个完整思想,比如\u0026quot;我想要茶\u0026quot;或者\u0026quot;在下雨\u0026quot;,用不同词汇可以代表不同含义,比如\u0026quot;我想要茶\u0026quot;变成\u0026quot;我想要独角兽\u0026quot;,但没法把\u0026quot;我想要茶\u0026quot;改成\u0026quot;我想要雨\u0026quot;- 语法毫无意义。\n规定句子结构的一系列规则叫 语法 (syntax),英语有语法,所有编程语言也都有语法。 a = 5 是一个编程语言语句,意思是创建一个叫 a 的变量,把数字 5 放里面,这叫\u0026quot;赋值语句\u0026quot;,把一个值赋给一个变量。\n为了表达更复杂的含义,需要更多语句,比如 a = 5 b = 10 c = a + b ,意思是,变量 a 设为 5,变量 b 设为 10 ,把 a 和 b 加起来,把结果 15 放进变量 c 。注意,变量名可以随意取。除了 a b c,也可以叫苹果、梨、水果。计算机不在乎你取什么名,只要不重名就行,当然取名最好还是有点意义,方便别人读懂。\n程序由一个个指令组成 ,有点像菜谱:烧水、加面,等 10 分钟,捞出来就可以吃了。程序也是这样,从第一条语句开始,一句一句运行到结尾。\n刚才我们只是把两个数字加在一起,无聊,我们来做一款游戏吧 !🤖🔫 当然,现在这个学习阶段,来编写一整个游戏还太早了,所以我们只写一小段一小段的代码,来讲解一些基础知识。\n假设我们在写一款老派街机游戏:grace hopper 拍虫子,阻止虫子飞进计算机造成故障,关卡越高,虫子越多。grace 要在虫子损坏继电器之前抓住虫子,好消息是她有几个备用继电器。\n开始编写时,我们需要一些值来保存游戏数据,比如当前关卡数、分数、剩余虫子数、grace 还剩几个备用继电器,所以我们要\u0026quot;初始化\u0026quot;变量 ,“初始化\u0026quot;的意思是设置最开始的值。\n关卡=1 分数=0 虫子数=5 备用继电器=4 玩家名=andre 为了做成交互式游戏,程序的执行顺序要更灵活,不只是从上到下执行,因此用 “控制流语句”。控制流语句有好几种,最常见的是 if 语句,可以想成是 “如果 x 为真,那么执行 y”,用英语举例就是 “如果累了,就去喝茶”,如果 “累了” 为真,就去喝茶,如果 “累了” 为假,就不喝茶。\nif 语句就像岔路口,走哪条路取决于 “表达式” 的真假,因此这些表达式又叫 “条件语句”。在大多数编程语言中,if 语句看起来像这样: if [条件], then [一些代码],结束 if 语句 。比如, if [第一关],then [分数设为 0] ,因为玩家才刚开始游戏,同时把虫子数设为 1,让游戏简单些。\n注意,依赖于 if 条件的代码,要放在 if 和 end if 之间,当然,条件表达式 可以改成别的,比如: \u0026quot;分数 \u0026gt;10\u0026quot; 或者 \u0026quot;虫子数 \u0026lt;1\u0026quot; 。\nif 还可以和 else 结合使用,条件为假会执行 else 里的代码。如果不是第 1 关,else 里的指令就会被执行,grace 要抓的虫子数,是当前关卡数 * 3 ,所以第 2 关有 6 个虫子,第 3 关有 9 个虫子,以此类推。else 中没有改分数,所以 grace 的分数不会变。\n![[assets/pasted image 20230526124545.png]]\n这里列了一些热门编程语言 if-then-else 的具体语法。具体语法略有不同,但主体结构一样。\nif 语句 根据条件执行一次。如果希望根据条件执行多次,需要\u0026quot;条件循环”。比如 while 语句,也叫 “while 循环”,当 while 条件为真,代码会重复执行。不管是哪种编程语言,结构都是这样。假设到达一定分数会冒出一个同事,给 grace 补充继电器,棒极了!把继电器补满到最大数 4 个 , 我们可以用 while 语句来做。\n来过一遍代码。\n假设同事入场时, grace 只剩一个继电器。当执行 while 循环,第一件事是检查条件 - 继电器数量\u0026lt;4? ,继电器数量现在是 1,所以是真,进入循环!\n碰到这一行: 继电器数量 = 继电器数量 + 1 。看起来有点怪,变量的赋值用到了自己。\n我们讲下这个,总是从等号右边开始,\u0026quot;继电器数量+1\u0026quot; 是多少? 当前值是 1,所以 1+1=2 ,结果存到\u0026quot;继电器数量\u0026quot;,覆盖旧的值,所以现在继电器数量是 2 。\n现在到了结尾,跳回开始点。和之前一样,先判断条件,看要不要进入循环 - 继电器数量\u0026lt;4? 。是,继电器数量是 2,所以再次进入循环! 2+1=3 ,3 存入\u0026quot;继电器数量\u0026quot; 。回到开头 。3\u0026lt;4? 是!进入循环。 3+1=4 ,4 存入\u0026quot;继电器数量\u0026quot;,回到开头。 4\u0026lt;4? ,不!现在条件为假,退出循环,执行后面的代码。\nwhile 循环就是这样运作的!\n另一种常见的叫 “for 循环”,不判断条件,判断次数,会循环特定次数,看起来像上图。现在放些真正的值进去,上图例子会循环 10 次,因为设了变量 i ,从 1 开始,一直到 10 。for 的特点是,每次结束, i 会 +1 ,当 i 等于 10,就知道循环了 10 次,然后退出。我们可以用任何数字,10, 42, 10 亿 。\n假设每关结束后给玩家一些奖励分,奖励分多少取决于继电器剩余数量,随着难度增加,剩下继电器会越来越难。因此奖励分会根据当前关卡数,指数级增长,我们要写一小段代码来算指数。指数是一个数乘自己,乘特定次数。用循环来实现简直完美!\n首先,创建一个叫\u0026quot;奖励分\u0026quot;的新变量,设为 1 (看上图),然后 for 循环,从 1 到 [当前关卡数] ,[奖励分] x [继电器剩余数],结果存入 [奖励分] ,比如继电器数是 2,关卡数是 3 ,for 会循环 3 次,奖励分会乘 继电器数量 x 继电器数量 x 继电器数量 ,也就是 1×2×2×2,奖励分是 8,2 的 3 次方。这个指数代码很实用,其他地方可能会用到。如果每次想用就复制粘贴,会很麻烦,每次都要改变量名。如果代码发现问题,要补漏洞时,要把每一个复制黏贴过的地方都找出来改,而且会让代码更难懂。\n少即是多!\n我们想要某种方法,把代码\u0026quot;打包\u0026quot;,可以直接使用,得出结果,不用管内部复杂度。\n这又提升了一层抽象!\n函数 为了隐藏复杂度,可以把代码打包成 “函数”,也叫 “方法” 或 “子程序”(有些编程语言这么叫)。其他地方想用这个函数,直接写函数名就可以了。\n现在我们把指数代码变成函数。\n第一步,取名。叫什么都行,比如\u0026quot;快乐独角兽\u0026quot;,但因为是算指数,直接叫\u0026quot;指数\u0026quot;合适一些。\n还有,与其用特定变量名,比如 “继电器” 和 “关卡数”,用更通用的名字,比如 底数 (base) 和 指数 (exp),base 和 exp 的初始值需要外部传入,剩余代码和之前一样。现在完成了,有函数名和新变量名。\n最后,我们还需要把结果 交给使用这个函数的代码,所以用 return 语句,指明返回什么。\n完整版代码是这样!\n现在可以随意用这个函数,只需要写出名字 然后传入 2 个数字就可以了。如果要算 2 的 44 次方,写 exponent(2,44) ,结果是 18 万亿左右。幕后原理是,2 和 44 存进 base 和 exp ,跑循环,然后返回结果。我们来用这个新函数 算奖励分,首先,奖励分初始化为 0 ,然后用 if 语句,看剩不剩继电器(看上图的 \u0026gt; 0),如果还剩,用指数函数,传入 [继电器数] 和 [关卡数] ,它会算 [继电器数] 的 [关卡数] 次方,存入奖励分。这段算奖励分的代码,之后可能还会用,也打包成一个函数。没错,这个函数 (calcbonus) 会调用另一个函数 (exponent) 。还有!这个 calcbonus 函数,可以用在其他更复杂的函数 。\n我们来写一个函数,每一关结束后都会调用,叫 levelfinished (关卡结束)- 需要传入 [剩余继电器数] 、 [关卡数] 、[当前分] ,这些数据必须传入。里面用 calcbonus 算奖励分,并加进总分,还有,如果 当前分 \u0026gt; 游戏最高分 ,把新高分和玩家名 存起来,现在代码变得蛮\u0026quot;花哨\u0026quot;了。函数调函数调函数。\n我们写这样一行代码时,复杂度都隐藏起来了,不需要知道内部的循环和变量,只知道结果会像魔术一样返回,总分 53 。 但是这不是魔术,是抽象的力量! 如果你理解了这个例子,就明白了函数的强大之处和现代编程的核心。\n比如浏览器这样的复杂程序,用一长串语句来写是不可能的,会有几百万行代码,没人能理解。所以现代软件由上千个函数组成,每个负责不同的事。如今超过 100 行代码的函数很少见,如果多于 100 行,应该有东西可以拆出来做成一个函数。模块化编程不仅可以让单个程序员独立制作 app,也让团队协作可以写更大型的程序。不同程序员写不同函数,只需要确保自己的代码工作正常,把所有人的拼起来,整个程序也应该能正常运作!\n现实中,程序员不会浪费时间写指数函数这种东西,现代编程语言有很多预先写好的函数集合,叫 “库” ,由专业人员编写,不仅效率高,而且经过了仔细检查。几乎做所有事情都有库,网络、图像、声音。\n我们之后会讲这些主题。\n但在此之前,我们先讲算法。好奇吗?应该!\n下周见\n:: 该章节其实没有多少内容,简单介绍了语句和函数,其中的例子用语言描述起来简直‘累’,还是动画来的直观。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/12-%E7%BC%96%E7%A8%8B%E5%8E%9F%E7%90%86-%E8%AF%AD%E5%8F%A5%E5%92%8C%E5%87%BD%E6%95%B0/","summary":"\u003cp\u003ei.e. Programming Basics - Statements \u0026amp; Functions\u003c/p\u003e\n\u003cp\u003e上集讲到用机器码写程序,还要处理那么多底层细节,对写大型程序是个巨大障碍。为了脱离底层细节,开发了编程语言,让程序员专心解决问题,不用管硬件细节。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: 底层各类太多了,编写麻烦,调试麻烦,改动麻烦……\u003c/p\u003e\n\u003c/blockquote\u003e","title":"12 编程原理 - 语句和函数"},]
[{"content":"i.e. intro to algorithms\n前两集,我们\u0026quot;初尝\u0026quot;了高级编程语言(比如 python 和 java),我们讨论了几种语句 - 赋值语句,if 语句,循环语句,以及把代码打包成 “函数”,比如算指数。重要的是,之前写的指数函数只是无数解决方案的一种,还有其它方案 - 用不同顺序写不同语句,也能得到一样结果。\n不同的是\u0026quot;算法\u0026quot;,意思是:解决问题的具体步骤,即使结果一致,有些算法会更好。 一般来说,所需步骤越少越好,不过有时我们也会关心其他因素,比如占多少内存。\n“算法” 一词来自 波斯博识者 阿尔·花拉子密, 1000 多年前的代数之父之一 ,如何想出高效算法 - 是早在计算机出现前就有的问题,诞生了专门研究计算的领域,然后发展成一门现代学科。\n你猜对了!计算机科学!\n记载最多的算法之一是\u0026quot;排序\u0026quot; ,比如给名字、数字排序。排序到处都是,找最便宜的机票、按最新时间排邮件、按姓氏排联系人,这些都要排序。你可能想\u0026quot;排序看起来不怎么难… 能有几种算法呢?“答案是超多!\n计算机科学家花了数十年发明各种排序算法。还起了酷酷的名字,“冒泡排序” (bubble sort)、“意面排序”(spaghetti sort),我们来试试排序!\n选择排序 试想有一堆机票价格,都飞往印第安纳波利斯 (美国地名),数据具体怎么在内存中表示 下周再说。\n上图的这样一组数据 叫\u0026quot;数组”(array)。\n![[assets/pasted image 20230526124703.png|400]]\n来看看怎么排序,先从一种简单算法开始。\n- 先找到最小数,从最上面的 307 开始,因为现在只看了这一个,所以它是最小数\r- 下一个是 239,比 307 小,所以新的最小数变成 239\r- 下一个是 214 ,新的最小数\r- 250 不是,384, 299, 223, 312 都不是\r- 现在扫完了所有数字,214 是最小的\r- 为了升序排列(从小到大排序),把 214 和最上面的数字,交换位置 好棒!刚排序了一个数字!现在重复同样的过程!\n- 这次不从最上面开始,从第 2 个数开始,先看到 239,我们当作是 \u0026#34;最小数\u0026#34;\r- 扫描剩下的部分,发现 223 最小,所以把它和第 2 位交换\r- 重复这个过程,从第 3 位数字开始,让 239 和 307 互换位置\r- 重复直到最后一个数字 瞧,数字排好了,可以买机票了!\n刚刚这种方法,或者说算法,叫 选择排序 - 非常基础的一种算法。\n以下是\u0026quot;伪代码\u0026quot;(pseudo-code)。\n这个函数可以排序 8 个,80 个或 8 千万个数字,函数写好了就可以重复使用,这里用循环遍历数组,每个数组位置都跑一遍循环,找最小数然后互换位置。可以在代码中看到这一点 (一个 for 循环套另一个 for 循环),这意味着,大致来说,如果要排 n 个东西,要循环 n 次,每次循环中再循环 n 次,共 n*n 。\n算法的 输入大小 和 运行步骤 之间的关系,叫算法的 复杂度 ,表示运行速度的量级。计算机科学家们把算法复杂度叫 - 没开玩笑 - 大 o 表示法 。\n算法复杂度 o(n) 效率不高。前面的例子有 8 个元素(n=8), 8 = 64,如果 8 个变 80 个,运行时间变成 80 = 6400,虽然大小只增长了 10 倍(8 到 80),但运行时间增加了 100 倍!(64 到 6400 )。随着数组增大,对效率的影响会越来越大,这对大公司来说是个问题,比如 谷歌,要对几十亿条信息排序。\n:: 数据量上来了,一切都会变得复杂了!*\n作为未来的计算机科学家你可能会问:有没有更高效的排序算法?\n归并排序 回到未排序的数组,试另一个算法 “归并排序”。\n- 第一件事是检查数组大小是否 \u0026gt; 1\r- 如果是,就把数组分成两半\r- 因为数组大小是 8,所以分成两个数组,大小是 4\r- 但依然大于 1,所以再分成大小是 2 的数组\r- 最后变成 8 个数组,每个大小为 1\r- 现在可以\u0026#34;归并\u0026#34;了,\u0026#34;归并排序\u0026#34;因此得名\r- - 从前两个数组开始,读第一个(也是唯一一个)值\r- 307 和 239\r- 239 更小,所以放前面\r- 剩下的唯一数字是 307 ,所以放第二位\r- 成功合并了两个数组 重复这个过程,按序排列,然后再归并一次。\n- 同样,取前两个数组,比较第一个数\r- 239 和 214 - 214 更小,放前面\r- - 再看两个数组里的第一个数:239 和 250\r- 239 更小,所以放下一位\r- - 看剩下两个数:307 和 250\r- 250 更小,所以放下一位\r- - 最后剩下 307 ,所以放最后\r- - 每次都以 2 个数组开始\r- 然后合并成更大的有序数组 我们把刚隐藏起来的,下面的数组也这样做。\n现在有两个大小是 4 的有序数组,就像之前,比较两个数组的第一个数,取最小数,重复这个过程,直到完成,就排好了!\n但坏消息是:无论排多少次,你还是得付 214 美元到印第安纳波利斯。总之,“归并排序\u0026quot;的算法复杂度是 o(n * log n),\nn 是需要 比较+合并 的次数,和数组大小成正比 log n 是合并步骤的次数 例子中把大小是 8 的数组,分成四个数组,然后分成 2 个,最后分成 1 个,分了 3 次。重复切成两半,和数量成对数关系\n相信我!\nlog_2 8=3\n如果数组大小变成 16 - 之前的两倍,也只要多分割 1 次,因为 log_2 16=4 ,即使扩大一千倍,从 8 到 8000,分割次数也不会增大多少 - log_2 8000≈13 ,13 比 3 只是 4 倍多一点,然而排序的元素多得多,因此\u0026quot;归并排序\u0026quot;比\u0026quot;选择排序\u0026quot;更有效率。\n这下我收藏的陶瓷猫 可以更快排序了!\n有好几十种排序算法,但没时间讲。\n图搜索 所以我们来谈一个经典算法问题:图搜索(graph search)。\n“图” 是用线连起来的一堆 “节点”,可以想成地图,每个节点是一个城市,线是公路。一个城市到另一个城市,花的时间不同,可以用 成本 (cost) 或 权重 (weight) 来代称,代表要几个星期。假设想找\u0026quot;高庭\u0026quot;到\u0026quot;凛冬城\u0026quot;的最快路线,最简单的方法是尝试每一条路,计算总成本,这是蛮力方法。假设用蛮力方法 来排序数组,尝试每一种组合,看是否排好序,这样的时间复杂度是 o(n!),n 是节点数,n! 是 n 乘 n-1 乘 n-2… 一直到 1,比 o(n ) 还糟糕。\n我们可以更聪明些!\n图搜索问题的经典算法发明者是理论计算机科学的伟人 edsger dijkstra,所以叫 “dijkstra 算法”。从\u0026quot;高庭\u0026quot;开始,此时成本为 0,把 0 标在节点里,其他城市标成问号,因为不知道成本多少,dijkstra 算法总是从成本最低的节点开始,目前只知道一个节点 “高庭”, 所以从这里开始,跑到所有相邻节点,记录成本,完成了一轮算法,但还没到\u0026quot;凛冬城”,所以再跑一次 dijkstra 算法,“高庭” 已经知道了,下一个成本最低的节点,是 “君临城”。就像之前,记录所有相邻节点的成本,到\u0026quot;三叉戟河\u0026quot;的成本是 5,然而我们想记录的是,从\u0026quot;高庭\u0026quot;到这里的成本,所以\u0026quot;三叉戟河\u0026quot;的总成本是 8+5=13 周,现在走另一条路到\u0026quot;奔流城\u0026quot;,成本高达 25 ,总成本 33,但 “奔流城” 中最低成本是 10,所以无视新数字,保留之前的成本 10,现在看了\u0026quot;君临城\u0026quot;的每一条路,还没到\u0026quot;凛冬城\u0026quot; 所以继续。下一个成本最低的节点,是\u0026quot;奔流城\u0026quot;,要 10 周,先看 “三叉戟河” 成本: 10+2=12,比之前的 13 好一点,所以更新 “三叉戟河” 为 12,“奔流城\u0026quot;到\u0026quot;派克城\u0026quot;成本是 3,10+3=13,之前是 14,所以更新 “派克城” 为 13。“奔流城\u0026quot;出发的所有路径都走遍了, 你猜对了,再跑一次 dijkstra 算法。下一个成本最低的节点,是\u0026quot;三叉戟河”,从\u0026quot;三叉戟河\u0026quot;出发,唯一没看过的路,通往\u0026quot;凛冬城”!成本是 10,加\u0026quot;三叉戟河\u0026quot;的成本 12,总成本 22。再看最后一条路,“派克城\u0026quot;到\u0026quot;凛冬城”,成本 31。现在知道了最低成本路线,让军队最快到达,还绕过了\u0026quot;君临城\u0026quot;!\n![[assets/pasted image 20230526124727.png]]\ndijkstra 算法的原始版本,构思于 1956 年,算法复杂度是 o(n )。前面说过这个效率不够好,意味着输入不能很大,比如美国的完整路线图,幸运的是,dijkstra 算法几年后得到改进,变成 o(n log n + l)。\n\u0026gt; n 是节点数,l 是多少条线\n虽然看起来更复杂,但实际更快一些。用之前的例子,可以证明更快(6 个节点 9 条线),从 36 减少到 14 左右。\n就像排序,图搜索算法也有很多,有不同优缺点。每次用谷歌地图时,类似 dijkstra 的算法就在服务器上运行,找最佳路线,算法无处不在,现代世界离不开它们。\n这集只触及了算法的冰山一角。\n但成为计算机科学家的核心,是根据情况合理决定,用现有算法,还是自己写新算法。\n希望这集的小例子能让你体会到这点。\n下周见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/13-%E7%AE%97%E6%B3%95%E5%85%A5%E9%97%A8/","summary":"\u003cp\u003ei.e. Intro to Algorithms\u003c/p\u003e\n\u003cp\u003e前两集,我们\u0026quot;初尝\u0026quot;了高级编程语言(比如 Python 和 Java),我们讨论了几种语句 - 赋值语句,if 语句,循环语句,以及把代码打包成 “函数”,比如算指数。重要的是,之前写的指数函数只是无数解决方案的一种,还有其它方案 - 用不同顺序写不同语句,也能得到一样结果。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e不同的是\u0026quot;算法\u0026quot;,意思是:解决问题的具体步骤,即使结果一致,有些算法会更好。\u003c/strong\u003e 一般来说,所需步骤越少越好,不过有时我们也会关心其他因素,比如占多少内存。\u003c/p\u003e","title":"13 算法入门"},]
[{"content":"i.e. data structures\n上集讲了一些经典算法,比如给数组排序,找图的最短路径,而上集没讲的是 算法处理的数据 存在内存里的格式是什么。\n你肯定不想数据像 john green 的大学宿舍一样乱 ,到处都是食物,衣服和纸,我们希望数据是结构化的,方便读取,因此计算机科学家发明了 “数据结构”!\n上集已经介绍了一种基本数据结构:数组(array),也叫列表(list)或向量(vector)(在其它编程语言里)。数组的值一个个连续存在内存里,所以不像之前,一个变量里只存一个值(比如 j = 5),我们可以把多个值存在数组变量里。为了拿出数组中某个值,我们要指定一个下标(index),大多数编程语言里,数组下标都从 0 开始,用方括号 [ ] 代表访问数组。如果想相加数组 j 的第一个和第三个元素,把结果存在变量 a,可以写上图这样一行代码。\n数组存在内存里的方式十分易懂。\n为了简单,假设编译器从内存地址 1000 开始存数组,数组有 7 个数字,像上图一样按顺序存。写 j[0],会去内存地址 1000 加 0 个偏移,得到地址 1000,拿值:5 ,如果写 j[5],会去内存地址 1000 加 5 个偏移,得到地址 1005,拿值: 4 。很容易混淆 “数组中第 5 个数” 和 “数组下标为 5 的数”,它们不是一回事,记住,下标 5 其实是数组中第 6 个数,因为下标是从 0 开始算的。\n数组的用途广泛。所以几乎所有编程语言都自带了很多函数来处理数组,举例,数组排序函数很常见,只需要传入数组,就会返回排序后的数组,不需要写排序算法。\n数组的亲戚是 字符串 (string),其实就是字母,数字,标点符号等 组成的数组。第 4 集讨论过计算机怎么存储字符,写代码时 用引号括起来就行了 j = \u0026quot;stan rocks\u0026quot; 。虽然长的不像数组,但的确是数组,幕后看起来像这样。\n注意,字符串在内存里以 0 结尾。不是\u0026quot;字符 0\u0026quot;,是\u0026quot;二进制值 0\u0026quot;,这叫字符\u0026quot;null\u0026quot;,表示字符串结尾。这个字符非常重要,如果调用 print 函数,print 在屏幕上输出字符串,会从开始位置,逐个显示到屏幕,但得知道什么时候停下来!否则会把内存里所有东西 都显示出来,0 告诉函数何时停下。\n因为计算机经常处理字符串,所以有很多函数专门处理字符串,比如连接字符串的 strcat,strcat 接收两个字符串,把第二个放到第一个结尾。\n我们可以用数组做一维列表(one dimensional lists),但有时想操作二维数据,比如电子表格,或屏幕上的像素,那么需要 矩阵(matrix)- 可以把矩阵看成 数组的数组!\n一个 3x3 矩阵就是一个长度为 3 的数组 ,数组里每个元素都是一个长度为 3 的数组。可以这样初始化,内存里是这样排列的。为了拿一个值,需要两个下标,比如 j[2][1] ,告诉计算机在找数组 2 里,位置是 1 的元素,得到数字 12 。\n矩阵酷的地方是,不止能做 3x3 的矩阵,任何维度都行,可以做一个 5 维矩阵,然后这样访问 a = j[2][0][18][18][3] 。现在你知道了,怎么读一个 5 维矩阵,快去告诉你的朋友!\n目前我们只存过单个数字/字符,存进数组或矩阵,但有时,把几个有关系的变量存在一起,会很有用。比如银行账户号和余额,多个变量打包在一起叫 结构体 (struct)。现在多个不同类型数据,可以放在一起,甚至可以做一个数组,里面放很多结构体,这些数据在内存里会自动打包在一起。如果写 j[0],能拿到 j[0] 里的结构体,然后拿银行账户和余额。存结构体的数组,和其它数组一样,创建时就有固定大小,不能动态增加大小,还有,数组在内存中 按顺序存储,在中间插入一个值很困难。\n结构体可以创造更复杂的数据结构,消除这些限制,我们来看一个结构体,叫 节点 (node),它存一个变量 - 一个指针(pointer)。“指针” 是一种特殊变量,指向一个内存地址,因此得名。用 节点 可以做 链表(linked list),链表是一种灵活数据结构,能存很多个 节点 (node),灵活性是通过每个节点 指向 下一个节点实现的。\n假设有三个节点,在内存地址 1000,1002, 1008,隔开的原因 可能是创建时间不同,它们之间有其他数据。可以看到第一个节点,值是 7,指向地址 1008,代表下一个节点,位于内存地址 1008,现在来到下一个节点,值是 112,指向地址 1002,如果跟着它,会看到一个值为 14 的节点,这个节点 指回地址 1000,也就是第一个节点,这叫 循环链表 。\n但链表也可以是非循环的,最后一个指针是 0 - “null”,代表链表尽头。当程序员用链表时,很少看指针具体指向哪里,而是用链表的抽象模型,就像上图,更容易看懂。\n数组大小需要预先定好,链表大小可以动态增减。可以创建一个新节点,通过改变指针值,把新节点插入链表;链表也很容易重新排序,两端缩减,分割,倒序等。超方便!\n链表也适合上集的排序算法,因为灵活,很多复杂数据结构 都用链表,最出名的是 队列(queue)和 栈。“队列” 就像邮局排队,谁先来就排前面,虽然你可能只想买邮票,而前面的人要寄 23 个包裹,这叫 先进先出(fifo)。我指队列,不是指那 23 个包裹。想象有个指针叫\u0026quot;邮局队列\u0026quot;,指向链表第一个节点。第一个节点是 hank,服务完 hank 之后 读取 hank 的指针,把\u0026quot;邮局队列\u0026quot;指向下一个人,这样就把 hank “出队”(dequeue)了。如果我们想把某人\u0026quot;入队\u0026quot;(enqueue) - 意思是加到队列里,要遍历整个链表到结尾,然后把结尾的指针,指向新人(nick)。\n只要稍作修改,就能用链表做 栈,栈是后进先出 (lifo)。可以把\u0026quot;栈\u0026quot;想成一堆松饼。做好一个新松饼,就堆在之前上面,吃的时候,是从最上面开始。美味!栈就不叫\u0026quot;入队\u0026quot;“出队\u0026quot;了,叫\u0026quot;入栈”(push) “出栈”(pop)。对,这些是正确术语!\n如果节点改一下,改成 2 个指针,就能做 树(tree)。很多算法用了 “树” 这种数据结构。同样,程序员很少看指针的具体值,而是把\u0026quot;树\u0026quot;抽象成这样:最高的节点叫\u0026quot;根节点\u0026quot;(root),“根节点\u0026quot;下的所有节点都叫\u0026quot;子节点”(children)。任何子节点的直属上层节点,叫\u0026quot;母节点\u0026quot;(parent node)。没有任何\u0026quot;子节点\u0026quot;的节点,也就是\u0026quot;树\u0026quot;结束的地方,叫\u0026quot;叶节点\u0026quot;(leaf)\n在这里的例子中,节点最多只可以有 2 个子节点,因此叫 二叉树(binary tree)。但你可以随便改,弄成 3 个,4 个,或更多,甚至节点 可以用链表存所有子节点。“树\u0026quot;的一个重要性质是(不管现实中还是数据结构中),“根\u0026quot;到\u0026quot;叶\u0026quot;是 单向 的。如果根连到叶,叶连到根,就很奇怪。\n如果数据随意连接,包括循环,可以用\u0026quot;图\u0026quot;表示,还记得上集用路连接城市的\u0026quot;图\u0026quot;吗?这种结构可以用有多个指针的节点表示,因此没有 根、叶、子节点、父节点这些概念,可以随意指向!\n以上概述了计算机科学中,最主要的一些数据结构,这些基本结构之上,程序员做了各种新变体,有不同性质。比如\u0026quot;红黑树\u0026quot;和\u0026quot;堆”,我们没时间讲。\n不同数据结构适用于不同场景,选择正确数据结构会让工作更简单,所以花时间考虑用什么数据结构是值得的。幸运的是,大多数编程语言自带了预先做好的数据结构。比如,c++有\u0026quot;标准模板库”,java 有\u0026quot;java 类库\u0026quot;,程序员不用浪费时间从零写,时间可以花在更有趣的事情。\n又提升了一层抽象!\n下周见!\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/14-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","summary":"\u003cp\u003ei.e. Data Structures\u003c/p\u003e\n\u003cp\u003e上集讲了一些经典算法,比如给数组排序,找图的最短路径,而上集没讲的是 算法处理的数据 存在内存里的格式是什么。\u003c/p\u003e\n\u003cp\u003e你肯定不想数据像 John Green 的大学宿舍一样乱 ,到处都是食物,衣服和纸,我们希望数据是结构化的,方便读取,因此计算机科学家发明了 “数据结构”!\u003c/p\u003e","title":"14 数据结构"},]
[{"content":" 后续章节为概念性章节,统一汇总在该章节内。\n15. 阿兰·图灵 i.e. alan turing\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n前几集我们聊了基础,比如函数,算法和数据结构,今天,我们来看一位对计算机理论 贡献巨大的人,计算机科学之父,长得不怎么像本尼的阿兰·图灵。\n\u0026gt; 阿兰·图灵\n阿兰·马蒂森·图灵 于 1921 年出生在伦敦, 从小就表现出惊人数学和科学能力。他对计算机科学的建树始于 1935 年,当时他是剑桥国王学院的硕士生,他开始解决德国数学家大卫·希尔伯特提出的问题 - 叫 entscheidungsproblem (德语),即\u0026quot;可判定性问题\u0026quot;: 是否存在一种算法,输入正式逻辑语句,输出准确的\u0026quot;是\u0026quot;或\u0026quot;否\u0026quot;答案?\n如果这样的算法存在,可以回答比如 “是否有一个数大于所有数”?不,没有。我们知道答案,但有很多其他数学问题,我们想知道答案,所以如果这种算法存在,我们想知道。\n美国数学家阿隆佐·丘奇于 1935 年 首先提出解决方法,开发了一个叫\u0026quot;lambda 算子\u0026quot;的数学表达系统,证明了这样的算法不存在。虽然\u0026quot;lambda 算子\u0026quot;能表示任何计算,但它使用的数学技巧难以理解和使用。同时在大西洋另一边,阿兰·图灵 想出了自己的办法来解决\u0026quot;可判定性问题\u0026quot;,提出了一种假想的计算机,现在叫\u0026quot;图灵机\u0026quot;。\n图灵机提供了简单又强大的数学计算模型,虽然用的数学不一样,但图灵机的计算能力和 lambda 算子一样,同时因为图灵机更简单,所以在新兴的计算机领域更受欢迎。因为它如此简单,我现在就给你解释。\n图灵机是一台理论计算设备,还有一个状态变量,保存当前状态,还有一组规则,描述机器做什么,规则是根据当前状态+读写头看到的符号,决定机器做什么,结果可能是在纸带写入一个符号,或改变状态,或把读写头移动一格,或执行这些动作的组合。\n为了更好理解,讲个简单例子:让图灵机读一个以零结尾的字符串,并计算 1 的出现次数是不是偶数。如果是,在纸带上写一个 1 ;如果不是,在纸带上写一个 0 。\n首先要定义\u0026quot;图灵机\u0026quot;的规则,如果当前状态是\u0026quot;偶数\u0026quot;, 当前符号是 1 ,那么把状态更新为\u0026quot;奇数\u0026quot;,把读写头向右移动;如果当前状态为偶数,当前符号是 0 ,意味着到了字符串结尾 。那么在纸带上写一个 1,并且把状态改成停机 (halt),状态改为\u0026quot;停机\u0026quot; 是因为图灵机已完成计算。但我们还需要 2 条规则,来处理状态为奇数的情况,一条处理奇数+纸带是 0 的情况, 一条处理奇数+纸带是 1 的情况,最后,要决定机器的初始状态,这里定成\u0026quot;偶数\u0026quot;。\n:: 初始状态:偶\n偶 + 1 - 状态更新为奇数,读写头向右移动一格\n偶 + 0 - 状态更新为停机,在纸带上写一个 1\n奇 + 1 - 状态更新为偶数,读写头向右移动一格\n奇 + 0 - 在纸带上写一个 0\n💡 真的是需要天赋的……\n定义好了起始状态+规则,就像写好了程序,现在可以输入了。\n假设把 110 放在纸带上,有两个 1,是偶数,注意,规则只让读写头向右移动,其他部分无关紧要,为了简单所以留空。\n“图灵机\u0026quot;准备好了,开始吧。\n机器起始状态为\u0026quot;偶数”,看到的第一个数是 1,符合最上面那条规则,所以执行对应的步骤 - 把状态更新到\u0026quot;奇数\u0026quot;, 读写头向右移动一格;然后又看到 1, 但机器状态是\u0026quot;奇数\u0026quot;,所以执行第三条规则,使机器状态变回\u0026quot;偶数\u0026quot;,读写头向右移动一格;现在看到 0,并且机器状态是偶数,所以执行第二条规则,在纸带上写 1,表示\u0026quot;真\u0026quot; 的确有偶数个 1,然后机器停机。\n这就是图灵机的原理,很简单对吧?你可能想知道有什么大不了的。\n图灵证明了这个简单假想机器。如果有足够时间和内存,可以执行任何计算。它是一台通用计算机!刚才的程序就是个简单例子,只要有足够的规则,状态和纸带,可以创造任何东西 - 浏览器,魔兽世界 任何东西!当然 这样做效率很低,但理论上可行,所以图灵机是很强大的计算模型。\n事实上,就可计算和不可计算而言,没有计算机比图灵机更强大。和图灵机一样强大的,叫 “图灵完备”。每个现代计算系统,比如笔记本电脑,智能手机,甚至微波炉和恒温器内部的小电脑,都是\u0026quot;图灵完备\u0026quot;的。\n为了回答可判定性问题,他把图灵机用于一个有趣计算问题:“停机问题”。简单说就是,“给定图灵机描述和输入纸带,是否有算法可以确定机器会永远算下去还是到某一点会停机?” 我们知道输入 110,图灵机会停机,因为刚做过这个例子,它最后停机了,但如果是更复杂的问题呢?有没有办法在不执行的情况,弄清会不会停机?一些程序可能要运行好几年,所以在运行前知道 会不会出结果很有用,否则就要一直等啊等,忧虑到底会不会出结果,当几十年后变老了,再按强制结束。好悲伤!\n图灵通过一个巧妙逻辑矛盾证明了停机问题是无法解决的。\n我们来看看他的推理,想象有一个假想图灵机, 输入:问题的描述 + 纸带的数据,输出 yes 代表会\u0026quot;停机\u0026quot;,输出 no 代表不会。我要给这台机器一个有趣的名字叫 h,来自\u0026quot;停机\u0026quot;的第一个字母,不用担心它具体怎么工作,假设这样的机器存在就好,毕竟重点是推论。图灵推理说: 如果有个程序, h 无法判断是否会\u0026quot;停机\u0026quot;,意味着\u0026quot;停机问题\u0026quot;无法解决。为了找到这样的程序,图灵用 h 设计了另一个图灵机。如果 h 说程序会\u0026quot;停机\u0026quot;,那么新机器会永远运行(即不会停机)。如果 h 的结果为 no,代表不会停机,那么让新机器输出 no,然后\u0026quot;停机\u0026quot;。实质上是一台和 h 输出相反的机器,如果程序不停机,就停机,如果程序停机,就永远运行下去。我们还需要在机器前面加一个分离器,让机器只接收一个输入, 这个输入既是程序,也是输入,我们把这台新机器叫异魔😈。目前为止,这个机器不难理解,但接下来马上会变复杂,会有点难懂。如果把异魔的描述,作为本身的输入会怎样?意味着在问 h ,当异魔的输入是自己时会怎样,但如果 h 说异魔会停机,那么异魔会进入无限循环,因此不会停机,如果 h 说异魔不会停机,那么异魔会输出 no 然后停机,所以 h 不能正确判定停机问题,因为没有答案。这是一个悖论!意味着\u0026quot;停机问题\u0026quot;不能用图灵机解决!\n还记得刚刚说: 图灵证明了图灵机可以实现任何计算。“停机问题\u0026quot;证明了,不是所有问题都能用计算解决。哇,好难理解!我都可能要再看一遍。\n长话短说,丘奇和图灵证明了计算机的能力有极限。无论有多少时间或内存,有些问题是计算机无法解决的。丘奇和图灵证明了计算是有极限的,起步了可计算性理论,现在叫\u0026quot;丘奇-图灵论题”。\n当时是 1936 年,图灵只有 24 岁,他的职业生涯才刚刚开始。从 1936 年到 1938 年 在丘奇指导下,他在普林斯顿拿到博士学位,毕业后回到剑桥。1939 年后不久,英国卷入第二次世界大战,图灵的才能很快被投入战争。事实上,在战争开始前一年,他已经在英国政府的密码破译学校兼职 - 位于\u0026quot;布莱切利园\u0026quot;的一个密码破译组织。他的工作内容之一是破解德国的通信加密,特别是\u0026quot;英格玛机\u0026quot;加密的信息,简单说,英格玛机会加密明文,如果输入字母 h-e-l-l-o ,机器输出 x-w-d-b-j ,这个过程叫\u0026quot;加密\u0026quot;,文字不是随便打乱的。加密由\u0026quot;英格玛机\u0026quot;顶部的齿轮组合决定,每个齿轮有 26 个可能位置,机器前面还有插板,可以将两个字母互换,总共有上十亿种可能,如果你有\u0026quot;英格玛机\u0026quot;,并且知道正确的齿轮和插头设置,输入 x-w-d-b-j,机器会输出 hello ,解密了这条消息。\n当然,德军不会把机器设置发到微博上,盟军必须自己破译密码,有数十亿种组合,根本没法手工尝试所有组合。幸运的是,英格玛机和操作员不是完美的,一个大缺陷是:字母加密后绝不会是自己,h 加密后绝对不是 h 。图灵接着之前波兰破译专家的成果继续工作,设计了一个机电计算机,叫 bombe,利用了这个缺陷,它对加密消息尝试多种组合。如果发现字母解密后和原先一样,我们知道英格玛机决不会这么做,这个组合会被跳过,接着试另一个组合。bombe 大幅减少了搜索量,让破译人员把精力花在更有可能的组合,比如在解码文本中找常见的德语单词。德国人时不时会怀疑有人在破解,然后升级英格玛机,比如加一个齿轮,创造更多可能组合,他们甚至还做了全新的加密机。整个战争期间,图灵和同事在布莱切利园努力破解加密,解密得到的德国情报,为盟军赢得了很多优势,些史学家认为他们把战争减短了好几年。战后,图灵回到学术界,为许多早期计算机工作做出贡献,比如曼彻斯特 1 号,一个早期有影响力的存储程序计算机。但他最有名的战后贡献是\u0026quot;人工智能\u0026quot;,这个领域很新,直到 1956 年才有名字,这个话题很大,以后再谈(第 34 集)。\n1950 年,图灵设想了未来的计算机,拥有和人类一样的智力,或至少难以区分。图灵提出,如果计算机能欺骗人类相信它是人类,才算是智能,这成了智能测试的基础,如今叫\u0026quot;图灵测试\u0026quot;。想像你在和两个人沟通,不用嘴或面对面,而是来回发消息,可以问任何问题,然后会收到回答,但其中一个是计算机,如果你分不出哪个是人类,哪个是计算机,那么计算机就通过了图灵测试。这个测试的现代版叫 “公开全自动图灵测试,用于区分计算机和人类”,简称\u0026quot;验证码\u0026quot;。防止机器人发垃圾信息等,我承认 有时我都认不出那些扭曲的东西是什么字,这难道意味着我是计算机?🤔\n通常这个系列我们不会深入历史人物的个人生活,但图灵与悲剧密不可分,所以他的故事值得一提。图灵那个时代,同性恋是违法的,英国和大部分国家都是。1952 年调查他家的入室盗窃案时,向当局暴露了他的性取向,被起诉 “行为严重不检点”,图灵被定罪,有 2 个选择:1. 入狱; 2. 接受激素来压制性欲。他选了后者,部分原因是为了继续学术工作,但药物改变了他的情绪和性格,虽然确切情况永远无法得知。图灵于 1954 年服毒自尽,年仅 41 岁。\n:: 图灵真的是惨……看看现在西方,lgbt 都上天了快……\n由于图灵对计算机科学贡献巨大,许多东西以他命名,其中最出名的是\u0026quot;图灵奖\u0026quot; - 计算机领域的最高奖项。相当于物理,化学等其它领域的诺贝尔奖。虽然英年早逝,但图灵激励了第一代计算机科学家,而且为如今便利的数字时代 做出了重要基石性工作。\n我们下周见。\n16. 软件工程 i.e. software engineering\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n之前花了很多时间讲排序,也写了些 10 行左右的排序代码,对 1 个程序员来说很容易写。而且代码很短,不必用专门工具 - 记事本就够了。真的!但排序算法很少会是独立程序 ,更可能是大项目的一小部分,举个例子,微软的 office 大约有 4000 万代码,4000 万!太多了,一个人不可能做到,为了写大型程序,程序员用各种工具和方法,所有这些形成了\u0026quot;软件工程\u0026quot;学科 - 这个词由工程师 margaret hamilton 创造,她帮助 nasa 在阿波罗计划中避免了严重问题。\nshe once explained it this way: 她曾说过:“有点像牙根管治疗:你总是拖到最后才做,但有些事可以预先做好。有点像预防性体检,只不过是预防软件出错。”\n第 12 集提过,把大项目分解成小函数,可以让多人同时工作,不用关心整个项目,关心自己的函数就好了。如果你的任务是写排序算法,你只需要确保高效和正确就可以了,然而把代码打包成函数 依然不够。如果只是这样,微软 office 会有几十万个函数,虽然比 4000 万行代码要好一些,但还是太多了。解决办法是:把函数打包成层级,把相关代码都放在一起,打包成对象(objects)。\n例如,汽车软件中 可能有几个和定速巡航有关的函数,比如 设定速度,逐渐加速减速,停止定速巡航,因为这些函数都相关,可以包装成一个\u0026quot;定速巡航对象\u0026quot;,但不止如此,我们还可以做更多。“定速巡航\u0026quot;只是引擎软件的一部分,可能还有 “火花塞点火” “燃油泵” 和 “散热器”,我们可以做一个\u0026quot;引擎对象” 来包括所有\u0026quot;子\u0026quot;对象,除了子对象,“引擎对象\u0026quot;可能有自己的函数,比如 开关引擎,它也会有自己的变量,比如汽车行驶了多少英里。\n总的来说,对象可以包其它对象,函数和变量。\n当然,“引擎对象\u0026quot;只是\u0026quot;汽车对象\u0026quot;的一部分,还有传动装置,车轮,门,窗等。作为程序员,如果想设\u0026quot;定速巡航”,要一层层向下,从最外面的对象往里找,最后找到想执行的函数:\n\u0026#34;car, then engine, then cruise control, then set cruise speed to 55\u0026#34;.\rcar. engine. cruisecontrol. setcruisespeed(55) 编程语言经常用类似这样的语法,把函数打包成对象的思想叫 “面向对象编程” 。这种思想和之前类似,通过封装组件,隐藏复杂度。之前把晶体管打包成了逻辑门,现在软件也这样做。\n又提升了一层抽象!\n把大型软件(如汽车软件) 拆成一个个更小单元,适合团队合作,一个团队负责定速巡航系统,团队里的一位程序员负责其中一些函数。类似建摩天大楼,有电工装电线、管道工配管、焊接工焊接、油漆工涂油漆,还有成百上千人做其他事情,在不同岗位同时工作,各尽其能,直到整栋楼完成。\n回到定速巡航的例子,定速巡航 要用到引擎的其它函数,来保持车速。定速巡航 团队不负责这些代码,另一个团队负责,因为是其他团队的代码,定速巡航 团队需要文档 帮助理解代码都做什么,以及定义好的 “程序编程接口” -简称 api。api 帮助不同程序员合作,不用知道具体细节,只要知道怎么使用就行了。例如\u0026quot;点火控制\u0026quot;对象中,可能有\u0026quot;设置发动机转数\u0026quot;函数、“检查火花塞电压\u0026quot;函数、“点燃单个火花塞\u0026quot;函数。“设置发动机转速\u0026quot;非常有用,“定速巡航\u0026quot;团队要用到这个函数,但他们对点火系统不怎么了解,让他们调用\u0026quot;点燃单个火花塞\u0026quot;函数,不是好主意,引擎可能会炸!可能啦 !👻 api 控制哪些函数和数据让外部访问,哪些仅供内部。“面向对象\u0026quot;的编程语言可以指定函数是 public 或 private,来设置权限。如果函数标记成 private ,意味着 只有同一个对象内的其他函数能调用它。在这个例子里,只有内部函数比如 setrpm 才能调用 firesparkplug 函数 ,而 setrpm 函数是 public,所以其它对象可以调用它,比如 定速巡航。\n“面向对象编程\u0026quot;的核心是 隐藏复杂度,选择性的公布功能。因为做大型项目很有效,所以广受欢迎。计算机上几乎所有软件,游戏机里几乎所有游戏,都是 “面向对象” 编程语言写的,比如 c++, c#, objective-c 等。其他流行 oo 语言,你可能听过 python 和 java 。\n有一点很重要:代码在编译前就只是文字而已,前面提过,你可以用记事本或任何文字处理器,有人确实这样做。😳 但一般来说,现代软件开发者会用专门的工具来写代码,工具里集成了很多有用功能 帮助写代码,整理,编译和测代码,因为集成了所有东西,因此叫 集成开发环境,简称 ide 。所有 ide 都有写代码的界面,还带一些有用功能,比如代码高亮,来提高可读性。许多 ide 提供实时检查,比如拼写。大型项目有很多源代码文件,ide 帮助开发者整理和看代码,很多 ide 还可以直接编译和运行代码。\n如果程序崩了,因为你还没写完呢,ide 可以定位到出错代码,还会提供信息 帮你解决问题,这叫 调试(debug)。调试很重要,大多数程序员会花 70%~80% 时间调试,而不是在写代码。好工具能极大帮助程序员防止和解决错误,很多开发者只用一款 ide,但承认吧,vim 才是最棒的编辑器,如果你知道怎么退出的话。\n除了写代码和调试,程序员工作的另一个重要部分是给代码写文档,文档一般放在一个叫 readme 的文件里,告诉其他程序员,看代码前先看这个文件。文档也可以直接写成\u0026quot;注释”,放在源代码里,注释是标记过的一段文字,编译代码时注释会被忽略,注释存在的唯一作用 - 就是帮助开发者理解代码。好文档能帮助开发者 ,几个月后理解自己的代码,对其他人也很重要。我想花一秒再强调一下注释很重要!最糟糕的就是拿到一堆代码,没有任何注释和文档,结果得逐行读代码,理解到底干嘛的。我是认真的,别做那种人!文档还可以提高复用性,与其让程序员一遍遍写同样的东西,可以直接用别人写好的来解决问题,读文档看怎么用就行,不用读代码,“读文档啊”!\n除了 ide,还有一个重要软件帮助团队协作,源代码管理,也叫\u0026quot;版本控制”!\n苹果或微软这样的大型软件公司,会把代码放到一个中心服务器上,叫\u0026quot;代码仓库”。程序员想改一段代码时,可以 check out ,有点像从图书馆借书 。一般这种操作,可以直接在 ide 内完成,然后开发者在自己的电脑上编辑代码,加新功能,测试。如果代码没问题了,所有测试通过了,可以把代码放回去,这叫 提交 (commit) 。当代码被 check out,而且可能被改过了,其他开发者不会动这段代码,防止代码冲突和重复劳动,这样多名程序员可以同时写代码,建立庞大的系统。重要的是,你不希望提交的代码里有问题,因为其他人可能用到了这些代码,导致他们的代码崩溃,造成困惑而且浪费时间。代码的主版本 (master),应该总是编译正常,尽可能少 bug ,但有时 bug 还是会出现 。幸运的是,源代码管理可以跟踪所有变化,如果发现 bug ,全部或部分代码,可以\u0026quot;回滚\u0026quot;到之前的稳定版 。“源代码管理” 也记录了谁改了什么代码,所以同事可以给你发 讨厌的,我是说\u0026quot;有帮助的” 邮件给写代码的人。\n写代码和测代码密不可分,测试一般由个人或小团队完成,测试可以统称 “质量保证测试”,简称 qa 。严格测试软件的方方面面,模拟各种可能情况,看软件会不会出错,基本上就是找 bug 。解决大大小小的错误需要很多工作,但对确保软件质量至关重要,让软件在各种情况下按预期运行。你可能听过 “beta 版” 软件,意思是软件接近完成,但不是 100% 完全测试过。公司有时会向公众发布 beta 版,以帮助发现问题,用户就像免费的 qa 团队。你听过比较少的是 beta 版之前的版本:alpha 版本,alpha 版一般很粗糙,错误很多, 经常只在公司内部测试。\n以上只是软件工程师用的工具和技巧的冰山一角。它们帮助软件工程师制作令人喜爱的软件,如 youtube,gta5 和 ppt 等等。如你所料,这些代码要强大的处理能力 才能高速速度运行。\n所以下集讨论,计算机怎么发展到如今这么快。\n到时见。\n17. 集成电路 \u0026amp; 摩尔定律 i.e. integrated circuits \u0026amp; moore’s law\n(。・∀・)ノ゙嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n过去 6 集我们聊了软件,从早期编程方式到现代软件工程。在大概 50 年里,软件从纸带打孔,变成面向对象编程语言,在集成开发环境中写程序,但如果没有硬件的大幅度进步,软件是不可能做到这些的。\n为了体会硬件性能的爆炸性增长 我们要回到电子计算机的诞生年代。\n大约 1940 年代~1960 年代中期这段时间里,计算机都由独立部件组成,叫\u0026quot;分立元件”,然后不同组件再用线连在一起。举例,eniac 有 1 万 7 千多个真空管,7 万个电阻,1 万个电容器,7 千个二极管,5 百万个手工焊点。如果想提升性能,就要加更多部件,这导致更多电线,更复杂,这个问题叫 “数字暴政” 。\n1950 年代中期,晶体管开始商业化(市场上买得到),开始用在计算机里。晶体管比电子管更小更快更可靠,但晶体管依然是分立元件。\n1959 年,ibm 把 709 计算机从原本的电子管 全部换成晶体管,诞生的新机器 ibm 7090,速度快 6 倍,价格只有一半。晶体管标志着\u0026quot;计算 2.0 时代\u0026quot;的到来。虽然更快更小 但晶体管的出现 还是没有解决\u0026quot;数字暴政\u0026quot;的问题,有几十万个独立元件的计算机不但难设计 而且难生产。\n1960 年代,这个问题的严重性达到顶点 电脑内部常常一大堆电线缠绕在一起。\n看看这个 1965 年 pdp-8 计算机的内部。\n解决办法是引入一层新抽象,封装复杂性。\n突破性进展在 1958 年 当时 jack killby 在德州仪器工作,演示了一个电子部件: “电路的所有组件都集成在一起”,简单说就是: 与其把多个独立部件用电线连起来,拼装出计算机,我们把多个组件包在一起,变成一个新的独立组件,这就是 集成电路(ic)。几个月后,在 1959 年 robert noyce 的仙童半导体 让集成电路变为现实,kilby 用锗来做集成电路,锗很稀少而且不稳定,仙童半导体公司用硅 硅的蕴藏量丰富,占地壳四分之一,也更稳定可靠,所以 noyce 被公认为现代集成电路之父,开创了电子时代,创造了硅谷(仙童公司所在地),之后有很多半导体企业都出现在硅谷。\n起初,一个 ic 只有几个晶体管,例如这块早期样品,由西屋公司制造。即使只有几个晶体管 也可以把简单电路,第 3 集的逻辑门,能封装成单独组件。ic 就像电脑工程师的乐高积木,可以组合出无数种设计,但最终还是需要连起来, 创造更大更复杂的电路,比如整个计算机,所以工程师们再度创新:印刷电路板,简称 pcb 。pcb 可以大规模生产,无需焊接或用一大堆线。它通过蚀刻金属线的方式,把零件连接到一起,把 pcb 和 ic 结合使用 可以大幅减少独立组件和电线,但做到相同的功能,而且更小,更便宜,更可靠。三赢!\n许多早期 ic 都是把很小的分立元件 封装成一个独立单元,例如这块 1964 年的 ibm 样品,不过,即使组件很小 塞 5 个以上的晶体管还是很困难。为了实现更复杂的设计,需要全新的制作工艺 “光刻\u0026quot;登场!简单说就是用光把复杂图案印到材料上,比如半导体,它只有几个基础操作,但可以制作出复杂电路。\n下面用一个简单例子,来做一片这个!\n我们从一片硅开始,叫\u0026quot;晶圆” 长得像薄饼干一样。美味!我们在第 2 集讨论过 硅很特别,它是半导体,它有时导电,有时不导电,我们可以控制导电时机,所以硅是做晶体管的绝佳材料。我们可以用晶圆做基础,把复杂金属电路放上面,集成所有东西,非常适合做。. 集成电路!\n下一步是在硅片顶部加一层薄薄的氧化层,作为保护层,然后加一层特殊化学品,叫 “光刻胶”,光刻胶被光照射后 会变得可溶,可以用一种特殊化学药剂洗掉。单单光刻胶本身,并不是很有用,但和\u0026quot;光掩膜\u0026quot;配合使用会很强大,光掩膜就像胶片一样,只不过不是 吃墨西哥卷饼的可爱仓鼠,而是要转移到晶圆上的图案。把光掩膜盖到晶圆上,用强光照射 挡住光的地方,光刻胶不会变化,光照到的地方,光刻胶会发生化学变化 洗掉它之后,暴露出氧化层,用另一种化学物质 - 通常是一种酸 可以洗掉\u0026quot;氧化层\u0026quot;露出的部分,蚀刻到硅层。注意,氧化层被光刻胶保护住了。为了清理光刻胶,我们用另一种化学药品洗掉它,是的,光刻法用很多化学品,每种都有特定用途,现在硅又露出来了,我们想修改硅露出来的区域,让它导电性更好,所以用一种化学过程来改变它,叫\u0026quot;掺杂”。不是开玩笑!我们继续!“掺杂” 通常用高温气体来做,比如磷 渗透进暴露出的硅,改变电学性质。\n半导体的具体物理和化学性质我们不会深究,如果你感兴趣,描述里有个视频链接 视频制作者是 derek muller 他的频道叫 veritasium 。\n但我们还需要几轮光刻法 来做晶体管,过程基本一样,先盖氧化层,再盖光刻胶,然后用新的光掩膜,这次图案不同 在掺杂区域上方开一个缺口,洗掉光刻胶,然后用另一种气体掺杂 把一部分硅转成另一种形式。为了控制深度,时机很重要,我们不想超过之前的区域,现在,所有需要的组件都有了。\n最后一步,在氧化层上做通道 这样可以用细小金属导线,连接不同晶体管,再次用光刻胶和光掩膜蚀刻出小通道。现在用新的处理方法 叫\u0026quot;金属化” 放一层薄薄的金属,比如铝或铜,但我们不想用金属盖住所有东西 我们想蚀刻出具体的电路,所以又是类似的步骤 用光刻胶+光掩膜,然后溶掉暴露的光刻胶,暴露的金属。咻~\n晶体管终于做好了! 它有三根线,连接着硅的三个不同区域,每个区域的掺杂方式不同,这叫双极型晶体管。这个 1962 年的真实专利,永远改变了世界!\n用类似步骤,光刻可以制作其他电子元件 比如电阻和电容,都在一片硅上,而且互相连接的电路也做好了。再见了,分立元件!\n之前的例子 只做了一个晶体管,但现实中 光刻法一次会做上百万个细节。芯片放大是这样的,导线上下交错,连接各个元件,尽管可以把光掩膜投影到一整片晶圆上 但光可以投射成任意大小,就像投影仪可以投满荧幕一样,我们可以把光掩膜 聚焦到极小的区域,制作出非常精细的细节。一片晶圆可以做很多 ic 整块都做完后,可以切割然后包进微型芯片,微型芯片就是在电子设备中那些小长方体,记住,芯片的核心都是一小片 ic 。\n随着光刻技术(photolithography techniques)发展,晶体管变小,密度变高。1960 年代初,ic 很少超过 5 个晶体管,因为塞不下,但 1960 年代中期 市场上开始出现超过 100 个晶体管的 ic 。1965 年,戈登·摩尔看到了趋势:每两年左右,得益于材料和制造技术的发展 同样大小的空间,能塞进两倍数量的晶体管!这叫 摩尔定律 ,然而这个名字不太对 因为它不是定律,只是一种趋势,但它是对的!\n芯片的价格也急剧下降 1962 年平均 50 美元,下降到 1968 年 2 美元左右。如今 几美分就能买到 ic ,晶体管更小密度更高 还有其他好处。晶体管越小,要移动的电荷量就越少 能更快切换状态,耗电更少。电路更紧凑 还意味着信号延迟更低,导致时钟速度更快。\n1968 年,罗伯特·诺伊斯 和 戈登·摩尔 联手成立了一家新公司,结合 intergrated(集成) 和 electronics(电子) 两个词,取名 intel, 如今最大的芯片制造商!\nintel 4004 cpu, 在第 7, 8 集介绍过 是个重要里程碑,发布于 1971 年 是第一个用 ic 做的处理器,也叫微型处理器,因为真的非常小!它有 2300 个晶体管,人们惊叹于它的整合水平 整个 cpu 在一个芯片里,而仅仅 20 年前,用分立元件会占满整个屋子。\n集成电路的出现 尤其是用来做微处理器,开启了计算 3.0 。\n而 intel 4004 只是个开始,cpu 晶体管数量爆发增长。1980 年,3 万晶体管。1990 年,100 万晶体管。2000 年,3000 万个晶体管。2010 年,10 亿个晶体管! 在一个芯片里!我的天啊!\n为了达到这种密度,光刻的分辨率 从大约一万纳米,大概是人类头发直径的 1/10 ,发展到如今的 14 纳米 比血红细胞小 400 倍!\n当然,cpu 不是唯一受益的元件。大多数电子器件都在指数式发展: 内存,显卡,固态硬盘,摄像头感光元件,等等。如今的处理器,比如 iphone 7 的 a10 cpu 有 33 亿个晶体管,面积仅有 1cm x 1cm,比一张邮票还小。\n现代工程师设计电路时,当然不是手工一个个设计晶体管 这不是人力能做到的,1970 年代开始,超大规模集成 (vlsi) 软件 用来自动生成芯片设计,用比如 “逻辑综合” 这种技术,可以放一整个高级组件,比如内存缓存。软件会自动生成电路,做到尽可能高效,许多人认为这是计算 4.0 的开始。\n坏消息是,专家们几十年来 一直在预言摩尔定律的终结,现在可能终于接近了。进一步做小,会面临 2 个大问题:\n用光掩膜把图案弄到晶圆上 因为光的波长,精度已达极限。所以科学家在研制波长更短的光源,投射更小的形状; 当晶体管非常小,电极之间可能只距离几个原子,电子会跳过间隙,这叫:量子隧穿效应。 如果晶体管漏电,就不是好开关。科学家和工程师在努力找解决方法,实验室中已造出小至 1 纳米的晶体管,能不能商业量产依然未知,未来也许能解决。\n我非常期待!下周见!\n18. 操作系统 i.e. operating systems\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n1940, 1950 年代的电脑,每次只能运行一个程序。程序员在打孔纸卡上写程序,然后拿到一个计算机房间,交给操作员,等计算机空下来了,操作员会把程序放入,然后运行,输出结果,停机。以前计算机慢,这种手动做法可以接受,运行一个程序通常要几小时,几天甚至几周。但上集说过,计算机越来越快,越来越快 - 指数级增长!很快,放程序的时间比程序运行时间还长,我们需要一种方式让计算机自动运作,于是\u0026quot;操作系统\u0026quot;诞生了。\n操作系统,简称 os(operating systems),其实也是程序,但它有操作硬件的特殊权限,可以运行和管理其它程序。操作系统一般是开机第一个启动的程序,其他所有程序都由操作系统启动。\n操作系统开始于 1950 年代,那时计算机开始变得更强大更流行。第一个操作系统加强了程序加载方式,之前只能一次给一个程序,现在可以一次多个。当计算机运行完一个程序,会自动运行下一个程序,这样就不会浪费时间找下一个程序的纸卡,这叫 批处理 。电脑变得更快更便宜,开始在出现在世界各地,特别是大学和政府办公室。很快,人们开始分享软件,但有一个问题 - 在哈佛 1 号和 eniac 那个时代,计算都是一次性的。程序员只需要给那\u0026quot;一台\u0026quot;机器写代码,处理器,读卡器,打印机都是已知的。但随着电脑越来越普遍,计算机配置并不总是相同的,比如计算机可能有相同 cpu,但不同的打印机。这对程序员很痛苦,不仅要担心写程序,还要担心程序怎么和不同型号打印机交互,以及计算机连着的其他设备,这些统称\u0026quot;外部设备\u0026quot; 。和早期的外部设备交互,是非常底层的,程序员要了解设备的硬件细节。加重问题的是,程序员很少能拿到所有型号的设备来测代码,所以一般是阅读手册来写代码,祈祷能正常运行。现在是\u0026quot;即插即用\u0026quot;,以前是\u0026quot;祈祷能用\u0026quot;。\n这很糟糕,所以为了程序员写软件更容易,操作系统充当软件和硬件之间的媒介。\n更具体地说,操作系统提供 api 来抽象硬件,叫 “设备驱动程序”,程序员可以用标准化机制和输入输出硬件(i/o)交互。比如,程序员只需调用 print(highscore),操作系统会处理输到纸上的具体细节。\n到 1950 年代尾声,电脑已经非常快了。处理器经常闲着,等待慢的机械设备(比如打印机和读卡器)。程序阻塞在 i/o 上,而昂贵的处理器则在度假,就是放松啥也不做。50 年代后期,英国曼彻斯特大学 开始研发世界上第一台超级计算机,atlas ,他们知道机器会超级快,所以需要一种方式来最大限度的利用它,他们的解决方案是一个程序叫 atlas supervisor ,于 1962 年完成。这个操作系统不仅像更早期的批处理系统那样,能自动加载程序,还能在单个 cpu 上同时运行几个程序,它通过调度来做到这一点。假设 atlas 上有一个游戏在运行,并且我们调用一个函数 print(highscore),它让 atlas 打印一个叫 highscore 的变量值,让朋友知道 我是最高分冠军。 print 函数运行需要一点时间,大概上千个时钟周期,但因为打印机比 cpu 慢,与其等着它完成操作,atlas 会把程序休眠,运行另一个程序,最终,打印机会告诉 atlas, 打印已完成,atlas 会把程序标记成可继续运行,之后在某时刻会安排给 cpu 运行,并继续 print 语句之后的下一行代码。这样,atlas 可以在 cpu 上运行一个程序,同时另一个程序在打印数据,同时另一个程序读数据。atlas 的工程师做的还要多,配了 4 台纸带读取器,4 台纸带打孔机,多达 8 个磁带驱动器。\n使多个程序可以同时运行,在单个 cpu 上共享时间,操作系统的这种能力叫 “多任务处理” 。同时运行多个程序有个问题,每个程序都会占一些内存,当切换到另一个程序时,我们不能丢失数据,解决办法是给每个程序分配专属内存块。\n举个例子,假设计算机一共有 10000 个内存位置,程序 a 分配到内存地址 0 到 999,而程序 b 分配到内存地址 1000 到 1999,以此类推。如果一个程序请求更多内存,操作系统会决定是否同意,如果同意,分配哪些内存块。这种灵活性很好,但带来一个奇怪的后果,程序 a 可能会分配到非连续的内存块,比如内存地址 0 到 999,以及 2000 到 2999。这只是个简单例子,真正的程序可能会分配到内存中数十个地方,你可能想到了,这对程序员来说很难跟踪。也许内存里有一长串销售额,每天下班后要算销售总额,但列表 存在一堆不连续的内存块里…… 🤬\n为了隐藏这种复杂性,操作系统会把内存地址进行 “虚拟化”,这叫 “虚拟内存” ,程序可以假定内存总是从地址 0 开始,简单又一致,而实际物理位置被操作系统隐藏和抽象了。\n一层新的抽象!!!\n用程序 b 来举例 它被分配了内存地址 1000 到 1999,对程序 b 而言,它看到的地址是 0 到 999,操作系统会自动处理 虚拟内存和物理内存之间的映射。如果程序 b 要地址 42,实际上是物理地址 1042,这种内存地址的虚拟化对程序 a 甚至更有用。在例子中,a 被分配了两块隔开的内存,程序 a 不知道这点,以 a 的视角,它有 2000 个连续地址。当程序 a 读内存地址 999 时 会刚好映射到物理内存地址 999,但如果程序 a 读下一个地址 1000 ,会映射到物理地址 2000 ,这种机制使程序的内存大小可以灵活增减 叫 “动态内存分配” 。对程序来说,内存看起来是连续的。它简化了一切,为操作系统同时运行多个程序 提供了极大的灵活性,给程序分配专用的内存范围,另一个好处是 这样隔离起来会更好。如果一个程序出错,开始写乱七八糟的数据,它只能捣乱自己的内存,不会影响到其它程序,这叫 “内存保护” 。防止恶意软件(如病毒)也很有用,例如,我们不希望其他程序有能力 读或改邮件程序的内存,如果有这种权限 恶意软件可能以你的名义发邮件,甚至窃取个人信息,一点都不好!\natlas 既有\u0026quot;虚拟内存\u0026quot;也有\u0026quot;内存保护\u0026quot;,是第一台支持这些功能的计算机和操作系统!\n到 1970 年代,计算机足够快且便宜,大学会买电脑让学生用。计算机不仅能同时运行多个程序,还能让多用户能同时访问。多个用户用\u0026quot;终端\u0026quot;来访问计算机,“终端\u0026quot;只是键盘+屏幕,连到主计算机 终端本身没有处理能力。冰箱大小的计算机可能有 50 个终端,能让 50 个用户使用,这时操作系统不但要处理多个程序,还要处理多个用户。为了确保其中一个人不会占满计算机资源,开发了 分时操作系统 ,意思是 每个用户只能用一小部分处理器,内存等。因为电脑很快 即使拿到 1/50 的资源也足以完成许多任务。\n早期分时操作系统中,最有影响力的是 multics(多任务信息与计算系统),于 1969 年发布。multics 是第一个,从设计时就考虑到安全的操作系统,开发人员不希望恶意用户访问不该访问的数据,比如学生假装成教授,访问期末考试的文件,这导致 multics 的复杂度超过当时的平均水准,操作系统会占大约 1 mb 内存,这在当时很多!可能是内存的一半,只拿来运行操作系统!\nmultics 的研究人员之一 dennis ritchie 曾说过,“阻碍 multics 获得商业成功的一个明显问题是 - 从某种方面来说,它被过度设计了,功能太多了” 。所以 dennis 和另一个 multics 研究员 ken thompson 联手打造新的操作系统,叫 unix 。他们想把操作系统分成两部分:\n首先是操作系统的核心功能,如内存管理,多任务和输入/输出处理 这叫 “内核” ; 第二部分是一堆有用的工具,但它们不是内核的一部分(比如程序和运行库)。 紧凑的内核意味着功能没有那么全面。\nmultics 的另一个开发者 tom van vleck 回忆说:“我对 dennis 说,我在 multics 写的一半代码都是错误恢复代码”。他说:“unix 不会有这些东西,如果有错误发生,我们就让内核\u0026quot;恐慌”(panic),当调用它时,机器会崩溃,你得在走廊里大喊,“嘿,重启电脑”!😹\n你可能听过 “内核恐慌”(kernel panic),这就是这个词的来源。内核如果崩溃,没有办法恢复,所以调用一个叫\u0026quot;恐慌”(panic)的函数。起初只是打印\u0026quot;恐慌\u0026quot;一词,然后无限循环,这种简单性意味着 unix 可以在更便宜更多的硬件上运行。\nunix 在 dennis 和 ken 工作的贝尔实验室大受欢迎,越来越多开发人员用 unix 写程序和运行程序,工具数量日益增长。1971 年发布后不久,就有人写了不同编程语言的编译器,甚至文字处理器,使得 unix 迅速成为 1970~80 年代最流行的操作系统之一。\n到 1980 年代早期,计算机的价格 降到普通人买得起,这些叫\u0026quot;个人电脑\u0026quot;或\u0026quot;家庭电脑\u0026quot;。这些电脑比大型主机简单得多,主机一般在大学,公司和政府,因此操作系统也得简单。举例,微软的磁盘操作系统(ms-dos)只有 160 kb 一张磁盘就可以容纳,于 1981 年发布,成为早期家用电脑最受欢迎的操作系统,虽然缺少\u0026quot;多任务\u0026quot;和\u0026quot;保护内存\u0026quot;这样功能,意味着程序经常使系统崩溃。虽然很讨厌但还可以接受,因为用户可以重启,哪怕是微软 1985 年发布的早期 windows 虽然在 90 年代很流行,但却缺乏\u0026quot;内存保护\u0026quot;,当程序行为不当时,就会\u0026quot;蓝屏\u0026quot;,代表程序崩溃的非常严重,把系统也带崩溃了…… 👻👻👻\n幸运的是,新版 windows 有更好的保护,不会经常崩溃。如今的计算机 有现代操作系统,比如 mac os x,windows 10 ,linux,ios 和 android 。虽然大部分设备只有一个人使用 - 你! 操作系统依然有\u0026quot;多任务,“虚拟内存”, “内存保护”,因此可以同时运行多个程序: 一边在浏览器看 youtube,一边在 photoshop 修图,用 spotify 放音乐,同步 dropbox 。如果没有操作系统这几十年的发展,这些都不可能,当然,我们也需要地方放程序 。\n下周会讨论。\n19. 内存 \u0026amp; 储存介质 i.e. memory \u0026amp; storage\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n系列中 我们多次谈到内存(memory), 甚至在第 6 集设计了一个简单内存。一般来说,电脑内存是 “非永久性”,如果 xbox 电源线不小心拔掉了,内存里所有数据都会丢失,所以内存叫\u0026quot;易失性\u0026quot;存储器。\n我们还没谈过的话题 是存储器(storage),存储器(storage)和内存(memory)有点不同 - 任何写入\u0026quot;存储器\u0026quot;的数据,比如你的硬盘 数据会一直存着,直到被覆盖或删除,断电也不会丢失。存储器是\u0026quot;非易失性\u0026quot;的,以前是\u0026quot;易失性\u0026quot;的速度快,“非易失性\u0026quot;的速度慢。但随着技术发展,两者的差异越来越小。\n如今我们认为稀松平常的技术,比如这个 u 盘能低成本+可靠+长时间 存储上 gb 的数据。但以前可不是这样的,最早的存储介质是 打孔纸卡 以及纸卡的亲戚 - 打孔纸带。到 1940 年代,纸卡标准是 80 列 x 12 行,一张卡能存 960 位数据 (80x12=960) 。据我们所知的最大纸卡程序是美国军方的\u0026quot;半自动地面防空系统”,简称 sage - 一个在 1958 年投入使用的防空系统,主程序存储在 62,500 个纸卡上,大小 5mb 左右,相当如今手机拍张照。\n纸卡用了十几年,因为不用电而且便宜耐用,然而坏处是读取慢,只能写入一次,打的孔无法轻易补上。对于存临时值,纸卡不好用,我们需要更快更大更灵活的存储方式。\nj. presper eckert 在 1944 年建造 eniac 时发明了一种方法,叫\u0026quot;延迟线存储器\u0026quot;(delay line memory),原理如下:拿一个管子装满液体,如水银,管子一端放扬声器,另一端放麦克风,扬声器发出脉冲时会产生压力波,压力波需要时间传播到另一端的麦克风,麦克风将压力波转换回电信号。我们可以用压力波的传播延迟来存储数据!假设有压力波代表 1,没有代表 0 ,扬声器可以输出 1010 0111 。压力波沿管子传播,过了一会儿,撞上麦克风,将信号转换回 1 和 0 ,如果加一个电路,连接麦克风和扬声器,再加一个放大器(amplifier)来弥补信号衰弱,就能做一个存储数据的循环。信号沿电线传播几乎是瞬时的,所以任何时间点只显示 1 bit 数据,但管子中可以存储多个位 (bit) 。忙完 eniac 后,eckert 和同事 john mauchly 着手做一个更大更好的计算机叫 edvac,使用了延迟线存储器,总共有 128 条延迟线,每条能存 352 位(bits),总共能存 45,000 位 (bit) ,对 1949 年来说还不错!这使得 edvac 成为最早的 “存储程序计算机” 之一,我们在第 10 集讨论过。\n但\u0026quot;延迟线存储器\u0026quot;的一大缺点是 - 每一个时刻只能读一位 (bit) 数据,如果想访问一个特定的 bit,比如第 112 位 (bit) 你得等待它从循环中出现,所以又叫 “顺序存储器\u0026quot;或\u0026quot;循环存储器”,而我们想要的是 “随机存取存储器” 可以随时访问任何位置。增加内存密度也是一个挑战,把压力波变得更紧密,意味着更容易混在一起,所以出现了其他类型的 “延迟线存储器”,如 “磁致伸缩延迟存储器”,用金属线的振动来代表数据,通过把线卷成线圈,1 英尺× 1 英尺的面积能存储大概 1000 位 (bit),然而,延迟线存储器在 1950 年代中期就基本过时了,因为出现了新技术,性能,可靠性和成本都更好 - “磁芯存储器” 🧲 ,用了像甜甜圈的小型磁圈,如果给磁芯绕上电线,并施加电流,可以将磁化在一个方向,如果关掉电流,磁芯保持磁化;如果沿相反方向施加电流,磁化的方向(极性)会翻转,这样就可以存 1 和 0!如果只存 1 位不够有用,所以把小甜甜圈排列成网格,有电线负责选行和列,也有电线贯穿每个磁芯,用于读写一位 (bit)。我手上有一块磁芯存储器,每个黄色方格 有 32 行 x32 列的磁芯 每个磁芯存 1 位数据,所以能存 1024 位 (bit) (32x32=1024),总共 9 个黄色方格,所以这块板子最多能存 9216 位 (bit) (1024x9=9216),换算过来大约是 9 千字节 (9216 bit ~= 9 kb)。磁芯内存的第一次大规模运用是 1953 年麻省理工学院的 whirlwind 1 计算机,磁芯排列是 32×32,用了 16 块板子,能存储大约 16000 位 (bit)。更重要的是,不像\u0026quot;延迟线存储器\u0026quot; 磁芯存储器能随时访问任何一位 (bit),这在当时非常了不起!\n“磁芯存储器” 从 1950 年代中期开始成为主流 流行了 20 多年,而且一般还是手工编织的!刚开始时,存储成本大约 1 美元 1 位 (bit) 到 1970 年代,下降到 1 美分左右,不幸的是,即使每位 1 美分也不够便宜,之前提过,现代手机随便拍张照片都有 5 mb,5mb 约等于 4000 万 bit。你愿意花 40 万美元在\u0026quot;磁芯存储器\u0026quot;上存照片吗?如果你有这么多钱,你知道 crash course 在 patreon 有赞助页吗?对吧?你懂的!🤑\n总之,当时对存储技术进行了大量的研究。到 1951 年,eckert 和 mauchly 创立了自己的公司,设计了一台叫 univac 的新电脑,最早进行商业销售的电脑之一。它推出了一种新存储:磁带,磁带是纤薄柔软的一长条磁性带子 卷在轴上,磁带可以在\u0026quot;磁带驱动器\u0026quot;内前后移动,里面有一个\u0026quot;写头\u0026quot;绕了电线,电流通过产生磁场,导致磁带的一小部分被磁化。电流方向决定了极性,代表 1 和 0 。还有一个\u0026quot;读头\u0026quot;,可以非破坏性地检测极性 。univac 用了半英寸宽,8 条并行的磁带,磁带每英寸可存 128 位数据,每卷有 1200 英尺长,意味着一共可以存 1500 万位左右 - 接近 2 兆字节!(2 mb) 虽然磁带驱动器很贵,但磁带又便宜又小,因此磁带至今仍用于存档。磁带的主要缺点是访问速度 - 磁带是连续的,必须倒带或快进到达特定位置,可能要几百英尺才能得到某个字节 (byte),这很慢!🐢!\n1950,60 年代,有个类似技术是 “磁鼓存储器”。有金属圆筒,盖满了磁性材料以记录数据,滚筒会持续旋转,周围有数十个读写头,等滚筒转到正确的位置读写头会读或写 1 位 (bit) 数据,为了尽可能缩短延迟,鼓轮每分钟上千转!到 1953 年,磁鼓技术飞速发展,可以买到存 80,000 位的\u0026quot;磁鼓存储器\u0026quot; - 也就是 10 kb 。但到 1970 年代 “磁鼓存储器” 不再生产,然而,磁鼓导致了硬盘的发展。硬盘和磁鼓很相似\nwhich are very similar, but use a different geometric configuration. 然而,磁鼓导致了硬盘的发展 硬盘和磁鼓很相似,不过硬盘用的是盘,不像磁鼓用圆柱体,因此得名。原理是一样的,磁盘表面有磁性,写入头和读取头,可以处理上面的 1 和 0 。硬盘的好处是薄,可以叠在一起,提供更多表面积来存数据。ibm 对世上第一台磁盘计算机就是这样做的 - ramac 305 ,顺便一说名字不错,它有 50 张 24 英寸直径的磁盘,总共能存 5 mb 左右…… 太棒啦!终于能存一张现代手机的照片了 🖼 !这年是 1956 年!要访问某个特定 bit ,一个读/写磁头会向上或向下移动,找到正确的磁盘,然后磁头会滑进去,就像磁鼓存储器一样,磁盘也会高速旋转,所以读写头要等到正确的部分转过来。ramac 305 访问任意数据,平均只要六分之一秒左右,也叫寻道时间。虽然六分之一秒对存储器来说算不错,但对内存来说还不够快,所以 ramac 305 还有\u0026quot;磁鼓存储器\u0026quot;和\u0026quot;磁芯存储器\u0026quot;。\n这是\u0026quot;内存层次结构\u0026quot;的一个例子,一小部分高速+昂贵的内存 ,一部分稍慢+相对便宜些的内存 ,还有更慢+更便宜的内存,这种混合在成本和速度间取得平衡。\n1970 年代,硬盘大幅度改进并变得普遍,如今的硬盘可以轻易容纳 1tb 的数据,能存 20 万张 5mb 的照片!网上最低 40 美元就可以买到,每 bit 成本 0.0000000005 美分,比磁芯内存 1 美分 1 bit 好多了!另外,现代硬盘的平均寻道时间低于 1/100 秒。我简单地提一下硬盘的亲戚,软盘,除了磁盘是软的,其他基本一样。你可能见过某些程序的保存图标是一个软盘,软盘曾经是真实存在的东西!软盘是为了便携,在 1970~1990 非常流行,如今当杯垫挺不错的。密度更高的软盘,如 zip disks,在 90 年代中期流行起来,但十年内就消失了。\n光学存储器于 1972 年出现,12 英寸的\u0026quot;激光盘\u0026quot;。你可能对后来的产品更熟:光盘(compact disk)(简称 cd),以及 90 年代流行的 dvd,功能和硬盘软盘一样,都是存数据,但用的不是磁性。光盘表面有很多小坑,造成光的不同反射,光学传感器会捕获到,并解码为 1 和 0。如今,存储技术在朝固态前进,没有机械活动部件。比如这个硬盘,以及 u 盘,里面是集成电路,我们在第 15 集讨论过。\n第一个 ram 集成电路出现于 1972 年,成本每比特 1 美分,使\u0026quot;磁芯存储器\u0026quot;迅速过时。如今成本下降了更多,机械硬盘 被 固态硬盘 逐渐替代,简称 ssd(solid state drives)。由于 ssd 没有移动部件,磁头不用等磁盘转,所以 ssd 访问时间低于 1/1000 秒,这很快!🐇 但还是比 ram 慢很多倍,所以现代计算机 仍然用存储层次结构。\n我们从 1940 年代到现在进步巨大,就像在第 14 集讨论过的 晶体管数量和摩尔定律,内存和存储技术也有类似的趋势,从早期每 mb 成本上百万美元,下滑到 2000 年只要几分钱,如今远远低于 1 分钱,完全没有打孔纸卡,你能想象 sega 的纸卡房间风一吹会怎样吗? 62,500 张卡 …… 我想都不敢想 😂\n:: 不同的介质,不同的方法,归其本质,都是为了区分两种显著的状态,分别标识 0 和 1 。有了 0 和 1 ,就有了整个数据世界。\n我们下周见。\n20. 文件系统 i.e. files \u0026amp; file systems\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n上集我们讲了数据存储,磁带和硬盘这样的技术,可以在断电状态长时间存上万亿个位,非常合适存一整块有关系的数据,或者说\u0026quot;文件\u0026quot;。你肯定见过很多种文件,比如文本文件,音乐文件,照片和视频。今天,我们要讨论文件到底是什么 以及计算机怎么管理文件。\n随意排列文件数据完全没问题,但按格式排会更好,这叫 “文件格式” 。你可以发明自己的文件格式,程序员偶尔会这样做,但最好用现成标准,比如 jpeg 和 mp3 。来看一些简单文件格式,最简单的是文本文件,也叫 txt 文件,里面包含的是。.. 文字 (惊喜吧)。就像所有其它文件, 文本文件只是一长串二进制数 ,原始值看起来会像这样:\n可以转成十进制看,但帮助不大,解码数据的关键是 ascii 编码 - 一种字符编码标准,第 4 集讨论过。第一个值 72 在 ascii 中是大写字母 h ,以此类推解码其他数字 。\n来看一个更复杂的例子:波形 (wave) 文件,也叫 wav 它存音频数据。在正确读取数据前,需要知道一些信息,比如码率 (bit rate),以及是单声道还是立体声。关于数据的数据,叫 “元数据” (meta data) 。元数据存在文件开头,在实际数据前面,因此也叫 文件头 (header) 。wav 文件的前 44 个字节长这样:\n有的部分总是一样的,比如写着 wave 的部分,其他部分的内容,会根据数据变化。音频数据紧跟在元数据后面,是一长串数字,数字代表每秒捕获多次的声音幅度。如果想学声音的基础知识,可以看物理速成课(crash course physics)第 18 集,举个例子,看一下\u0026quot;你好\u0026quot;的波形,现在捕获到了一些声音,我们放大看一下。\n电脑和手机麦克风,每秒可以对声音进行上千次采样,每次采样可以用一个数字表示,声压越高数字越大,也叫\u0026quot;振幅\u0026quot;,wave 文件里存的就是这些数据!每秒上千次的振幅!播放声音文件时,扬声器会产生相同的波形 - “你好!”\n现在来谈谈位图 (bitmap),后缀 .bmp,它存图片,计算机上,图片由很多个叫\u0026quot;像素\u0026quot;的方块组成,每个像素由三种颜色组成:红,绿,蓝,叫\u0026quot;加色三原色\u0026quot;,混在一起可以创造其它颜色。就像 wav 文件一样,bmp 文件开头也是元数据有:图片宽度,图片高度,颜色深度,举例,假设元数据说图是 4 像素宽 x 4 像素高,颜色深度 24 位、8 位红色,8 位绿色,8 位蓝色,提醒一下,8 位 (bit) 和 1 字节 (byte) 是一回事。一个字节能表示的最小数是 0,最大 255 。图像数据看起来会类似这样:来看看第一个像素的颜色 - 红色是 255,绿色是 255,蓝色也是 255,这等同于全强度红色,全强度绿色和全强度蓝色,混合在一起变成白色,所以第一个像素是白色!下一个像素的红绿蓝值,或 rgb 值 255,255,0 是黄色! 下一个像素的红绿蓝值,或 rgb 值 255,255,0 是黄色! 下一个像素是 0,0,0 ,黑色 !下一个是黄色 !因为元数据说图片是 4x4 我们知道现在到了第一行结尾,所以换一行,下一个 rgb 值是 255,255,0 ,又是黄色!好,我们读完剩下的像素 - 一个低分辨率的吃豆人。\n刚才显然只是一个简单例子,但这张图片也可以用 bmp 存。我想再次强调,不管是文本文件,wav,bmp,或是我们没时间讨论的其他格式。 文件在底层全是一样的: 一长串二进制 。为了知道文件是什么,文件格式至关重要。\n现在你对文件更了解了 我们接下来讨论计算机怎么存文件。\n虽然硬件可能是磁带,磁鼓,磁盘或集成电路,通过软硬件抽象后,可以看成一排能存数据的桶。在很早期时,计算机只做一件事,比如算火炮射程表。整个储存器就像一整个文件,数据从头存到尾,直到占满,但随着计算能力和存储容量的提高存多个文件变得非常有用,最简单的方法是把文件连续存储。这样能用,但怎么知道文件开头和结尾在哪里?储存器没有文件的概念,只是存储大量位,所以为了存多个文件,需要一个特殊文件,记录其他文件的位置,这个特殊文件有很多名字,这里泛称 “目录文件” 🗃 。这个文件经常存在最开头,方便找 - 位置 0!目录文件里,存所有其他文件的名字,格式是 文件名 + 一个句点 + 扩展名 ,比如 bmp 或 wav 。\n扩展名帮助得知文件类型,目录文件还存文件的元数据,比如创建时间,最后修改时间,文件所有者是谁、是否能读/写、或读写都行。最重要的是,目录文件有文件起始位置和长度,如果要添加文件,删除文件,更改文件名等,必须更新目录文件。就像书的目录,如果缩短或移动了一个章节,要更新目录,不然页码对不上。\n文件名 + 一个句点 + 扩展名\n文件起始位置和长度\n创建时间\n最后修改时间\n文件所有者是谁\n是否能读/写、或读写都行\n……\n目录文件,以及对目录文件的管理,是一个非常简单的文件系统例子!文件系统专门负责管理文件!\n刚刚的例子叫\u0026quot;平面文件系统\u0026quot; 因为文件都在同一个层次,平的!当然,把文件前后排在一起有个问题。如果给 todo.txt 加一点数据,会覆盖掉后面 carrie.bmp 的一部分,所以现代文件系统会做两件事:\n把空间划分成一块块,导致有一些 “预留空间” 可以方便改动,同时也方便管理(用这样的方案,目录文件要记录文件在哪些块里); 拆分文件,存在多个块里。 假设打开 todo.txt 加了些内容、文件太大存不进一块里,我们不想覆盖掉隔壁的块,所以文件系统会分配 一个没使用的块,容纳额外的数据。目录文件会记录不止一个块,而是多个块,只要分配块,文件可以轻松增大缩小,如果你看了第 18 集 操作系统 这听起来很像\u0026quot;虚拟内存\u0026quot;,概念上讲的确很像!假设想删掉 carrie.bmp 只需要在目录文件删掉那条记录,让一块空间变成了可用,注意这里没有擦除数据,只是把记录删了,之后某个时候,那些块会被新数据覆盖,但在此之前,数据还在原处,所以计算机取证团队可以\u0026quot;恢复\u0026quot;数据,虽然别人以为数据已经\u0026quot;删了\u0026quot;, 狡猾!😈 假设往 todo.txt 加了更多数据,所以操作系统分配了一个新块,用了刚刚 carrie.bmp 的块,现在 todo.txt 在 3 个块里,隔开了,顺序也是乱的,这叫 碎片。碎片是增/删/改文件导致的,不可避免,对很多存储技术来说,碎片是坏事。如果 todo.txt 存在磁带上,读取文件要先读块 1, 然后快进到块 5,然后往回转到块 2 ,来回转个半天。现实世界中,大文件可能存在数百个块里,你可不想等五分钟才打开文件,答案是碎片整理!这个词听起来好像很复杂,但实际过程很简单。以前看计算机做碎片整理,真的很有趣!计算机会把数据来回移动,排列成正确的顺序,整理后 todo.txt 在 1 2 3,方便读取。\n目前只说了平面文件系统,文件都在同一个目录里。如果存储空间不多,这可能就够用了,因为只有十几个文件。但上集说过,容量爆炸式增长,文件数量也飞速增长,很快,所有文件都存在同一层变得不切实际。就像现实世界、相关文件放在同一个文件夹会方便很多,然后文件夹套文件夹,这叫 “分层文件系统” ,你的计算机现在就在用这个。\n实现方法有很多种,我们用之前的例子来讲重点好了,最大的变化是 目录文件不仅要指向文件,还要指向目录。我们需要额外元数据来区分开文件和目录,这个目录文件在最顶层,因此叫根目录,所有其他文件和文件夹,都在根目录下。\n图中可以看到根目录文件有 3 个文件 2 个子文件夹:“音乐\u0026quot;和\u0026quot;照片”,如果想知道\u0026quot;音乐\u0026quot;文件夹里有什么 必须去那边读取目录文件(格式和根目录文件一样),有很多好歌啊!\n除了能做无限深度的文件夹,这个方法也让我们可以轻松移动文件,如果想把 theme.wav 从根目录移到音乐目录,不用移动任何数据块,只需要改两个目录文件,一个文件里删一条记录,另一个文件里加一条记录,theme.wav 依然在块 5 。\n文件系统的几个重要概念,现在介绍完了。它提供了一层新抽象!\n文件系统使我们不必关心 文件在磁带或磁盘的具体位置,整理和访问文件更加方便。我们像普通用户一样直观操纵数据,比如打开和整理文件,接下来几集也会从用户角度看问题。\n下周见。\n21. 压缩 i.e. compression\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n上集我们讨论了文件格式,如何编码文字,声音,图片,还举了具体例子 .txt .wav .bmp 。这些格式虽然管用,而且现在还在用,但它们的简单性意味着效率不高。我们希望文件能小一点,这样能存大量文件,传输也会快一些。等邮件附件下载烦死人了,解决方法是 压缩,把数据占用的空间压得更小,用更少的位 (bit) 来表示数据。\n听起来像魔法,但其实是计算机科学!\n我们继续用上集的 吃豆人例子,图像是 4 像素 x4 像素。之前说过,图像一般存成一长串像素值,为了知道一行在哪里结束 图像要有元数据,写明尺寸等属性,但为了简单起见,今天忽略这些细节,如果红绿蓝都是 255 会得到白色,如果混合 255 红色和 255 绿色,会得到黄色,这个图像有 16 个像素 (4x4), 每个像素 3 个字节,总共占 48 个字节(16x3=48),但我们可以压缩到少于 48 个字节。\n一种方法是 减少重复信息,最简单的方法叫 游程编码 (run-length encoding),适合经常出现相同值的文件。比如吃豆人 有 7 个连续黄色像素,与其全存下来:黄色,黄色,黄色。.. 可以插入一个额外字节,代表有 7 个连续黄色像素,然后删掉后面的重复数据。为了让计算机能分辨哪些字节是\u0026quot;长度\u0026quot;,哪些字节是\u0026quot;颜色\u0026quot;,格式要一致,所以我们要给所有像素前面标上长度。有时候数据反而会变多,但就这个例子而言,我们大大减少了字节数,之前是 48 现在是 24 ,小了 50%!省了很多空间!还有,我们没有损失任何数据 我们可以轻易恢复到原来的数据,这叫 “无损压缩” ,没有丢失任何数据,解压缩后,数据和压缩前完全一样。\n我们来看另一种无损压缩,它用更紧凑的方式表示数据块,有点像 “别忘了变厉害” 简写成 dftba ,为此,我们需要一个字典,存储\u0026quot;代码\u0026quot;和\u0026quot;数据\u0026quot;间的对应关系。我们看个例子,我们可以把图像看成一块块,而不是一个个像素。为了简单,我们把 2 个像素当成 1 块(占 6 个字节),但你也可以定成其他大小。我们只有四对: 白黄 黑黄 黄黄 白白 ,我们会为这四对 生成紧凑代码 (compact codes) 。有趣的是,这些块的出现频率不同。1950 年代 大卫·霍夫曼 发明了一种高效编码方式叫 “霍夫曼树”(huffman tree),当时他是麻省理工学院的学生,算法是这样的。\n首先,列出所有块和出现频率,每轮选两个最低的频率,这里 黑黄 和 白白 的频率最低,它们都是 1 ,可以把它们组成一个树,总频率 2 ,现在完成了一轮算法。现在我们重复这样做,这次有 3 个可选,就像上次一样,选频率最低的两个,放在一起,并记录总频率。好,我们快完成了。这次很简单,因为只有 2 个选择,把它们组合成一棵树就完成了!\n现在看起来像这样,它有一个很酷的属性:按频率排列,频率低的在下面。现在有了一棵树,你可能在想 “怎么把树变成字典?”\n我们可以把每个分支用 0 和 1 标注,就像这样。现在可以生成字典,黄黄 编码成 0 ,白黄 编码成 10 ,黑黄 编码成 110 ,白白 编码成 111 。酷的地方是它们绝对不会冲突,因为树的每条路径是唯一的,意味着代码是\u0026quot;无前缀\u0026quot;的,没有代码是以另一个代码开头的,现在我们来压缩!注意是位 (bit)! 不是字节 (byte)! 14 位 (bit) 还不到 2 个字节 (byte)!但,先别急着开香槟!字典也要保存下来,否则 14 bit 毫无意义,所以我们把字典 加到 14 bit 前面,就像这样。\n现在加上字典,图像是 30 个字节 (bytes) 比 48 字节好很多。\n“消除冗余\u0026quot;和\u0026quot;用更紧凑的表示方法”,这两种方法通常会组合使用。几乎所有无损压缩格式都用了它们,比如 gif, png, pdf, zip 。\n游程编码 和 字典编码 都是无损压缩!压缩时不会丢失信息,解压后,数据和之前完全一样。无损对很多文件很重要,比如我给你发了个压缩的 word 文档你解压之后发现内容变了,这就很糟糕了。但其他一些文件,丢掉一些数据没什么关系,丢掉那些人类看不出区别的数据。大多数有损压缩技术,都用到了这点。实际细节比较复杂,所以我们讲概念就好。以声音为例,你的听力不是完美的,有些频率我们很擅长,其他一些我们根本听不见,比如超声波,除非你是蝙蝠。举个例子,如果录音乐,超声波数据都可以扔掉 因为人类听不到超声波,另一方面,人类对人声很敏感,所以应该尽可能保持原样。低音介于两者之间,人类听得到,但不怎么敏感,一般是感觉到震动。有损音频压缩利用这一点,用不同精度编码不同频段,听不出什么区别,不会明显影响体验,音乐发烧友估计要吐槽了!日常生活中你会经常碰到这类音频压缩,所以你在电话里的声音和现实中不一样,压缩音频是为了让更多人能同时打电话,如果网速变慢了,压缩算法会删更多数据,进一步降低声音质量,所以 skype 通话有时听起来像机器人,和没压缩的音频格式相比,比如 wav 或 flac ( 这下音乐发烧友满意了),压缩音频文件如 mp3,能小 10 倍甚至更多。省了超多空间!\n所以我的旧 ipod 上有一堆超棒的歌,别批判我!\n这种删掉人类无法感知的数据的方法,叫 “感知编码” 。它依赖于人类的感知模型,模型来自\u0026quot;心理物理学\u0026quot;领域,这是各种\u0026quot;有损压缩图像格式\u0026quot;的基础,最著名的是 jpeg 。就像听力一样,人的视觉系统也不是完美的。我们善于看到尖锐对比,比如物体的边缘,但我们看不出颜色的细微变化。jpeg 利用了这一点,把图像分解成 8x8 像素块,然后删掉大量高频率空间数据。举个例子,这是导演的狗,面面,超可爱!我们来看其中一个 8x8 像素,几乎每个像素都和相邻像素不同,用无损技术很难压缩,因为太多不同点了,很多小细节,但人眼看不出这些细节,因此可以删掉很多,用这样一个简单的块来代替。这看起来一样,但可能只占 10%的原始数据。我们可以对所有 8x8 块做一样的操作,图片依然可以认出是一只狗,只是更粗糙一些,以上例子比较极端,进行了高度压缩,只有原始大小的八分之一。通常你可以取得平衡,图片看起来差不多,但文件小不少。你看得出两张图的区别吗?估计看不出。\n但我想提一下,视频压缩也造成了影响。毕竟你现在在看视频啊,视频只是一长串连续图片 所以图片的很多方面也适用于视频,但视频可以做一些小技巧。因为帧和帧之间很多像素一样,但视频可以做一些小技巧 因为帧和帧之间很多像素一样,比如我后面的背景!这叫 时间冗余 ,视频里不用每一帧都存这些像素,可以只存变了的部分,当帧和帧之间有小小的差异时,比如后面这个频率发生器,很多视频编码格式,只存变化的部分,这比存所有像素更有效率 - 利用了帧和帧之间的相似性。更高级的视频压缩格式会更进一步,找出帧和帧之间相似的补丁,然后用简单效果实现,比如移动和旋转,变亮和变暗。如果我这样摆手,视频压缩器会识别到相似性,用一个或多个补丁代表我的手,然后帧之间直接移动这些补丁,所以你看到的是我过去的手(不是实时的),有点可怕,但数据量少得多。mpeg-4 是常见标准,可以比原文件小 20 倍到 200 倍,但用补丁的移动和旋转来更新画面。当压缩太严重时会出错,没有足够空间更新补丁内的像素。即使补丁是错的,视频播放器也会照样播放,导致一些怪异又搞笑的结果,你肯定见过这些。\n总的来说,压缩对大部分文件类型都有用\n从这个角度来讲,人类不完美的视觉和听觉 也算有用。学习压缩非常重要 因为可以高效存储图片,音乐,视频。如果没有压缩,在 youtube 看\u0026quot;明星拼车唱歌\u0026quot;几乎不可能,因为你的带宽可能不够(会很卡) 而且供应商不愿意免费传输那么多数据。现在你知道为什么打 skype 电话,有时像在和恶魔通话。\n下周见。\n22. 命令行界面 i.e. keyboards \u0026amp; command line interfaces\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n我们之前讨论过输入输出 ,但都是计算机组件互相输入输出,比如 ram 输出数据,或输指令进 cpu 。我们还没讲来自人类的输入,也没讲怎么从电脑中拿出信息,除了用打孔纸卡。当然,有很多种 “输入输出设备” , 让我们和计算机交互,它们在人类和机器间提供了界面。如今有整个学科专门研究这个,叫 “人机交互”。界面对用户体验非常重要,所以是我们接下来几集的重点。\n在系列开头的几集,我们提过,早期机械计算设备用齿轮,旋钮和开关等机械结构来输入输出,这些就是交互界面。甚至早期电子计算机,比如 colossus 和 eniac,也是用一大堆机械面板和线来操作,输入一个程序可能要几星期,还没提运行时间。运行完毕后想拿出数据,一般是打印到纸上。打印机超有用,甚至查尔斯·巴贝奇都给差分机专门设计了一个,那可是 1820 年代!\n然而,到 1950 年代,机械输入完全消失,因为出现了打孔纸卡和磁带,但输出仍然是打印到纸上,还有大量指示灯,在运行中提供实时反馈。那个时代的特点是 ,尽可能迁就机器 ,对人类好不好用是其次。打孔纸带就是个好例子,就是为了方便计算机读取,纸带是连续的,方便机器处理。纸孔可以方便地用机械或光学手段识别,纸孔可以编码程序和数据,当然,人类不是以纸孔方式思考的。所以负担放到了程序员身上,他们要花额外时间和精力转成计算机能理解的格式,一般需要额外人员和设备帮忙。要注意的是,基本上 1950 年前的早期计算机,“输入\u0026quot;的概念很原始,是的,的确是人类负责输入程序和数据,但计算机不会交互式回应。程序开始运行后会一直运行,直到结束。因为机器太贵了,不能等人类慢慢敲命令和给数据,要同时放入程序和数据。\n这在 1950 年代晚期开始发生变化。一方面,小型计算机变得足够便宜,让人类来回和计算机交互变得可以接受,交互式就是人和计算机之间来回沟通。而另一方面,大型计算机变得更快,能同时支持多个程序和多个用户,这叫\u0026quot;多任务\u0026quot;和\u0026quot;分时系统”。\n但交互式操作时,计算机需要某种方法来获得用户输入,所以借用了当时已经存在的数据录入机制:键盘!当时,打字机已经存在几个世纪了,但现代打字机是克里斯托弗·莱瑟姆·肖尔斯在 1868 年发明的,虽然到 1874 年才完成设计和制造,但之后取得了商业成功。肖尔斯的打字机用了不寻常的布局,qwerty ,名字来自键盘左上角按键,为什么这么设计?有很多猜测。最流行的理论是,这样设计是为了把常见字母放得远一些,避免按键卡住。这个解释虽然省事,但可能是错的,或至少不够全面。事实上,qwerty 把很多常见字母放在了一起,比如 th 和 er 。我们知道 肖尔斯和他的团队设计了很多版才进化到这个布局。总之,肖尔斯的打字机取得了成功 ,所以其它公司很快开始抄他的设计。\n:: 这……\n过去一个世纪有不少新的键盘布局被发明,宣称各种好处,但人们已经熟悉了 qwerty 布局 ,根本不想学新布局,这是经济学家所说的 转换成本。所以现在都快 1 个半世纪了 ,我们还在用 qwerty 键盘布局。我应该提一下,qwerty 不是通用的,有很多变体,比如法国 azerty 布局,以及中欧常见的 qwertz 布局。有趣的是,肖尔斯根本没想到打字会比手写快,手写速度大约是每分钟 20 个,打字机主要为了易读性和标准化,而不是速度,然而随着打字机成为办公室标配 ,对快速打字的渴望越来越大。\n有两个重大进步解放了打字的潜力。\n1880 年左右,辛辛那提速记学院一名叫伊丽莎白·朗利的老师开始推广十指打字,比一个手指打字要移动的距离短得多,所以速度更快。几年后,弗兰克·爱德华·麦克格林 盐湖城的一位联邦法庭书记学会了盲打,打字时不用看键盘。1888 年,麦格高林赢了备受关注的打字速度比赛,之后\u0026quot;十指盲打\u0026quot;开始流行。专业打字员每分钟 100 字以上,比手写快多了!而且清晰又整洁!虽然人类擅长用打字机,但我们没法把打字机塞到计算机面前,让它打字,计算机又没有手指。所以早期计算机用了一种特殊打字机,是专门用来发电报的,叫电传打字机。这些打字机是强化过的,可以用电报线发送和接收文本,按一个字母,信号会通过电报线,发到另一端,另一端的电传打字机会打出来,使得两人可以长距离沟通,基本是个蒸汽朋克版聊天室。因为电传打字机有电子接口,稍作修改就能用于计算机,电传交互界面在 1960~1970 很常见,用起来很简单,输入一个命令,按回车,然后计算机会输回来。用户和计算机来回\u0026quot;对话\u0026quot;,这叫 “命令行界面” 。它是最主要的人机交互方式,一直到 1980 年代,用电传打字机的命令行交互,类似这样:\n\u0026gt; 用户可以输入各种命令\r\u0026gt; 我们来看几个命令,先看当前目录有什么文件\r\u0026gt; 输入命令 ls,名字来自 list 的缩写\r\u0026gt; 然后计算机会列出 当前目录里的所有文件\r\u0026gt; 如果想看 secretstartrekdiscoverycast.txt 有什么\r\u0026gt; 要用另一个命令 显示文件内容\r\u0026gt; unix 用 cat 命令显示文件内容 cat 是连接 (concatenate) 的缩写\r\u0026gt; 然后指定文件名,指定的方法是写在 cat 命令后面 传给命令的值叫 参数\r\u0026gt; \u0026gt; 如果同一个网络里有其他人\r\u0026gt; 你可以用 finger 命令找朋友 就像是个很原始的\u0026#34;找朋友\u0026#34; app\r…… 电传打字机直到 1970 年代左右都是主流交互方式,尽管屏幕最早出现在 1950 年代,但对日常使用太贵而且分辨率低,然而因为针对普通消费者的电视机开始量产,同时处理器与内存也在发展。到 1970 年代,屏幕代替电传打字机变得可行,但与其为屏幕专门做全新的标准,工程师直接用现有的电传打字机协议,屏幕就像无限长度的纸,除了输入和输出字,没有其它东西。 协议是一样的,所以计算机分不出是纸还是屏幕 ,这些\u0026quot;虚拟电传打字机\u0026quot;或\u0026quot;玻璃电传打字机\u0026quot;叫 终端 。到 1971 年,美国大约有 7 万台电传打字机 以及 7 万个终端,屏幕又好又快又灵活。如果删一个错别字,会立刻消失,所以到 1970 年代末,屏幕成了标配。你也许会想,命令行界面太原始了,做不了什么有意思的事。即便只有文字,程序员也找到了一些方法,让它变得有趣一些。\n早期的著名交互式文字游戏 zork ,出现于 1977 年。早期游戏玩家需要丰富的想象力,想像自己身在虚构世界,比如\u0026quot;四周漆黑一片,附近可能有怪物会吃掉你\u0026quot;。我们用命令行玩玩看,就像之前,我们可以用 ls 命令,看当前目录有什么,然后用 cd 命令,进入游戏文件夹 cd 的意思是 “改变文件夹”(change directory),再用 ls 看有哪些游戏。超棒!我们有\u0026quot;冒险旅程\u0026quot;!(adventure)。想运行这个程序,只需要输入它的名字。在程序自行停止或我们主动退出前,它会接管命令行。\n你现在看到的,是\u0026quot;巨大洞穴冒险\u0026quot;这款游戏的真实输出,由 will crowther 在 1976 年开发。游戏中,玩家可以输入 1 个词或 2 个词的命令来移动人物,和其他东西交互,捡物品等,然后游戏会像旁白一样,输出你的当前位置,告诉你能做什么动作,以及你的动作造成的结果,有些动作会导致死亡!原始版本只有 66 个地方可供探索,但它被广泛认为是最早的互动式小说。游戏后来从纯文字进化成多人游戏 简称 mud,或多人地牢游戏(multi-user dungeons),是如今 mmorpg 的前辈 (大型多人在线角色扮演游戏, massive, multiplayer online role playing games)。如果你想了解游戏史,我们有游戏速成课 主持人 andre meadows 。\n命令行界面虽然简单,但十分强大。\n编程大部分依然是打字活, 所以用命令行比较自然,因此,即使是现在大多数程序员工作中依然用命令行界面,而且用命令行访问远程计算机 是最常见的方式, 比如服务器在另一个国家。如果你用 windows, macos, linux ,你的计算机有命令行界面,但你可能从来没用过,你可以在 windows 搜索栏中输入 cmd,或在 mac 上搜 terminal ,然后你可以装 zork 玩!\n现在你知道了,早期计算机的发展是如何影响到现在的。\n想想要是手机没有 qwerty 键盘 ,在 instagram 给图片配标题可就麻烦了。但我们还有一个重要话题没讲,美妙的图形界面!这是下周的主题!\n下周见。\n23. 屏幕 \u0026amp; 2d 图形显示 i.e. screens\u0026amp;2d graphics\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n这台 1960 年的 pdp-1 是一个早期图形计算机的好例子,你可以看到左边是柜子大小的电脑,中间是电传打字机,右边是一个圆形的屏幕,注意它们是分开的,因为当时文本任务和图形任务是分开的。事实上,早期的屏幕无法显示清晰的文字,而打印到纸上有更高的对比度和分辨率。早期屏幕的典型用途是跟踪程序的运行情况,比如寄存器的值,如果用打印机一遍又一遍打印出来没有意义,不仅费纸而且慢。另一方面,屏幕更新很快,对临时值简直完美。但屏幕很少用于输出计算结果,结果一般都打印到纸上,或其它更永久的东西上。\n但屏幕超有用!到 1960 年代,人们开始用屏幕做很多酷炫的事情。几十年间出现了很多显示技术,但最早最有影响力的是阴极射线管(crt,cathode ray tubes)。原理是把电子发射到有磷光体涂层的屏幕上,当电子撞击涂层时,会发光几分之一秒,由于电子是带电粒子,路径可以用磁场控制,屏幕内用板子或线圈把电子引导到想要的位置,上下左右都行。既然可以这样控制,有 2 种方法绘制图形 :\n引导电子束描绘出形状,这叫 “矢量扫描” 。因为发光只持续一小会儿,如果重复得足够快,可以得到清晰的图像; 按固定路径,一行行来,从上向下,从左到右,不断重复,只在特定的点打开电子束,以此绘制图形,这叫 “光栅扫描” 。用这种方法,可以用很多小线段绘制形状,甚至文字。 最后,因为显示技术的发展,我们终于可以在屏幕上显示清晰的点,叫\u0026quot;像素\u0026quot; 。液晶显示器,简称 lcd(liquid crystal displays),和以前的技术相当不同,但 lcd 也用光栅扫描,每秒更新多次,像素里红绿蓝的颜色。有趣的是,很多早期计算机不用像素 - 不是技术做不到,而是因为像素占太多内存。 200 像素×200 像素的图像,有 40,000 个像素,哪怕每个像素只用一个 bit 表示 代表黑色或白色,连灰度都没有!会占 40,000 bit ,内存比 pdp-1 全部内存的一半还多,所以计算机科学家和工程师,得想一些技巧来渲染图形,等内存发展到足够用。所以 早期计算机不存大量像素值,而是存符号,80x25 个符号最典型,总共 2000 个字符。如果每个字符用 8 位表示,比如用 ascii ,总共才 16000 位,这种大小更合理。为此,计算机需要额外硬件来从内存读取字符,转换成光栅图形,这样才能显示到屏幕上,这个硬件叫 “字符生成器”,基本算是第一代显卡。它内部有一小块只读存储器,简称 rom ,存着每个字符的图形,叫 “点阵图案”。如果图形卡看到一个 8 位二进制,发现是字母 k ,那么会把字母 k 的点阵图案光栅扫描显示到屏幕的适当位置。为了显示,“字符生成器” 会访问内存中一块特殊区域,这块区域专为图形保留,叫 屏幕缓冲区 。程序想显示文字时,修改这块区域里的值就行,这个方案用的内存少得多,但也意味着只能画字符到屏幕上。即使有这样限制 ,人们用 ascii 艺术发挥了很多创意!也有人用字符模仿图形界面,用下划线和加号来画盒子,线,和其他简单形状,但字符集实在太小,做不了什么复杂的事,因此对 ascii 进行了各种扩展,加新字符,比如上图的 ibm cp437 字符集,用于 dos。\n:: 用来读取字符,转换成光栅图形的硬件 - 字符生成器(‘第一代显卡’) ,有一个 rom,存储着每个字符的图形(‘点阵图案’) - 为了显示,访问内存中的一块特殊区域(‘屏幕缓冲区’)。\n屏幕缓冲区,是不是类似于内存和屏幕显示光栅的映射 ❓ 也是一层抽象 ❓\n某些系统上可以用额外的 bit 定义字体颜色和背景颜色,做出这样的 dos 界面 这界面只用了刚刚提到的字符集。字符生成器是一种省内存的技巧,但没办法绘制任意形状。制任意形状很重要,因为电路设计,建筑平面图,地图,好多东西都不是文字!为了绘制任意形状,同时不吃掉所有内存,计算机科学家用 crt 上的\u0026quot;矢量模式\u0026quot;。概念非常简单:所有东西都由线组成,没有文字这回事,只有线条,没有别的。明白了吗?好,我们举个实例吧!\n假设这个视频是一个 笛卡尔平面 200 个单位宽,100 个单位高,原点 (0,0) 在左上角,我们可以画形状,用如下矢量命令,这些命令来自 vectrex,一个早期矢量显示系统。首先,reset ,这个命令会清空屏幕,把电子枪的绘图点移动到坐标 (0,0),并把线的亮度设为 0 , move_to 50 50 把绘图点移动到坐标 (50,50) ,intensity 100 把强度设为 100 ,现在亮度提高了,移动到 (100,50) 然后 (60,75) 然后 (50,50) ,最后把强度设回 0 。酷,我们画了一个三角形!这些命令占 160 bit 比存一个庞大的像素矩阵更好。就像之前的\u0026quot;字符生成器\u0026quot; 把内存里的字符转成图形一样,这些矢量指令也存在内存中,通过矢量图形卡画到屏幕上。数百个命令可以按序存在屏幕缓冲区,画出复杂图形,全是线段组成的!由于这些矢量都在内存中,程序可以更新这些值,让图形随时间变化 - 动画!\n最早的电子游戏之一,spacewar! 是 1962 年在 pdp-1 上用矢量图形制作的。它启发了许多后来的游戏,比如 爆破彗星 (asteroids),甚至第一个商业街机游戏:太空大战 。\n1962 年是一个大里程碑 - sketchpad 诞生,一个交互式图形界面,用途是计算机辅助设计 (cad,computer-aided design),它被广泛认为是第一个完整的图形程序,发明人伊万·萨瑟兰后来因此获得图灵奖。为了与图形界面交互 ,sketchpad 用了当时发明不久的输入设备 - 光笔,就是一个有线连着电脑的触控笔,笔尖用光线传感器,可以检测到显示器刷新,通过判断刷新时间,电脑可以知道笔的位置,有了光笔和各种按钮,用户可以画线和其他简单形状。sketchpad 可以让线条完美平行,长度相同,完美垂直 90 度,甚至动态缩放,这些在纸上很费力,在计算机上非常简单!用户还可以保存设计结果,方便以后再次使用,甚至和其他人分享。你可以有一整个库,里面有电子元件和家具之类的,可以直接拖进来用。从如今的角度来看,好像很普通,但在 1962 年 计算机还是吃纸带的大怪兽,有柜子般大小,sketchpad 和光笔让人大开眼界,它们代表了人机交互方式的关键转折点 - 电脑不再是关在门后负责算数的机器了,可以当助手帮人类做事。\n最早用真正像素的计算机和显示器出现于 1960 年代末,内存中的位 (bit) 对应屏幕上的像素,这叫 位图显示。现在我们可以绘制任意图形了,你可以把图形想成一个巨大像素值矩阵。就像之前,计算机把像素数据存在内存中一个特殊区域 叫 “帧缓冲区”。早期时,这些数据存在内存里,后来存在高速视频内存里,简称 vram 。vram 在显卡上,这样访问更快,如今就是这样做的。在 8 位灰度屏幕上,我们可用的颜色范围是 0 强度(黑色),到 255 强度(白色)。其实更像绿色或橙色 ,因为许多早期显示器不能显示白色。我们假设这个视频在低分辨率的位图屏幕上,分辨率 60x35 像素。如果我们想把 (10,10) 的像素设为白色 可以用这样的代码,…… ,如果想画一条线,假设从 (30,0) 到 (30,35) 可以用这样一个循环,……,把整列像素变成白色,如果想画更复杂的图形,比如矩形,那么需要四个值:\n1. 起始点 x 坐标 2. 起始点 y 坐标 3. 宽度 4. 高度 目前只试了白色,这次画矩形试下灰色,灰色介于 0 到 255 中间 所以我们用 127 (255/2=127.5),然后用两个循环,一个套另一个,这样外部每跑一次,内部会循环多次 ,可以画一个矩形。计算机绘图时会用指定的颜色 127 ,我们来包装成 “画矩形函数”,就像这样:\n……\n假设要在屏幕的另一边,画第二个矩形,这次可能是黑色矩形,可以直接调用 “画矩形函数”, 超棒!\n就像之前说的其他方案,程序可以操纵\u0026quot;帧缓冲区\u0026quot;中的像素数据,实现交互式图形。当然,程序员不会浪费时间从零写绘图函数 而是用预先写好的函数来做,画直线,曲线,图形,文字等\n一层新抽象!\n位图的灵活性,为交互式开启了全新可能,但它的高昂成本持续了十几年,上集提到,1971 年 整个美国也只有大约 7 万个电传打字机和 7 万个终端,令人惊讶的是 只有大约 1000 台电脑有交互式图形屏幕,这可不多!\nsketchpad 和 太空大战 这样的先驱,推动了图形界面发展,帮助普及了计算机显示器 由此,图形界面的曙光初现,帮助普及了计算机显示器 由此,图形界面的曙光初现。\n接下来讲图形界面。\n下周见。\n24. 冷战和消费主义 i.e. the cold war and consumerism\n(。・∀・)ノ゙嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n之前介绍了计算机历史 从人类文明的曙光开始 (第 1 集),一直到 1940 年代中期电子计算机诞生,过去 23 集里讲的很多东西,比如编程语言和编译器,算法和集成电路,软盘(floppy disks)和操作系统,电报机和屏幕,全都是 1940~1970 年代,大概这 30 年间里出现的。那时苹果和微软还不存在,也没有推特,谷歌或者 uber 。还没到个人电脑时代,而万维网,无人驾驶汽车,虚拟现实等主题,这个系列的后半部分会讲。\n今天,我们不管电路和算法 来聊聊这个影响力巨大的时代!\n我们会把重点放在 冷战,太空竞赛,全球化,消费主义的兴起。1945 年二战结束后不久,两个超级大国的关系越发紧张,美国和苏联开始了冷战,因此政府往科学和工程学 投入大量资金。计算机在战时已经证明了自身的价值, 比如曼哈顿计划 和 破解纳粹通讯加密,所以政府大量投入资源 各种雄心勃勃的项目得以进行,比如之前提过的 eniac, edvac, atlas, whirlwind ,这种高速发展,如果仅靠商业运作是根本无法做到的 - 要依靠销售收回开发成本。\n1950 年代,事情开始发生变化,特别是 univac 1,它是第一台取得商业成功的电脑,不像 eniac 或 atlas univanc 1 不是一台机器,而是一个型号,一共造了 40 多台,大部分 univac 去了政府或大公司,成为美国日益增长的军事工业综合体的一部分,因为政府有钱承担这些尖端科技。一个著名的例子是,一台给 美国原子能委员会 生产的 univac 1 ,被 cbs 用来预测 1952 年美国总统大选的结果,仅用 1%的选票,univac 1 正确预测了结果。艾森豪威尔 获得压倒性胜利,而专家预测 史蒂文森 会赢,这次事件把计算机推到了公众面前。\n计算机和以前的机器不一样,以前的机器 增强人类的物理能力,比如卡车能带更多东西,自动织布机更快,机床更精确 等等。这些东西代表了工业革命。而计算机增强的是人类智力,范内瓦·布什 看到了这种潜力。他在 1945 年发表了一篇文章,描述了一种假想计算设备叫 memex。可以用这个设备 存自己所有的书,其他资料 以及和别人沟通,而且数据是按照格式存储,所以可以快速查询,有很大灵活性,可以辅助我们的记忆。他还预测会出现新的百科全书形式,信息之间相互链接,听起来是不是很熟悉?(维基百科)\nmemex 启发了之后几个重要里程碑,比如上集 伊万·萨瑟兰 的 sketchpad(画板),以及后面很快会讲到 dough engelbart 的 on-line 系统(第 26 集)。\n范内瓦·布什 做过\u0026quot;美国科学研究与开发办公室\u0026quot;的头头,这个部门负责在二战期间 资助和安排科学研究。冷战时, 范内瓦·布什 到处游说,想建立一个职责类似,但是在和平时期运作的部门,因此 国家科学基金会 于 1950 年成立,至今,国家科学基金会 依然负责给科学研究 提供政府资金。美国的科技领先全球,主要原因之一就是这个机构。\n1950 年代,消费者开始买晶体管设备,其中值得注意的是 收音机,它又小又耐用,用电池就够了,而且便携,不像 1940 年代之前的收音机,用的是真空管。收音机非常成功,卖的像\u0026quot;菲比精灵\u0026quot;和 iphone 一样畅销。日本政府也在寻求工业机会,想振兴战后经济,他们很快动手从贝尔实验室 取得晶体管的授权,帮助振兴日本的半导体和电子行业。1955 年,索尼的第一款产品面世 - tr-55 晶体管收音机。他们把重心放在质量和价格,因此日本公司在短短 5 年内,就占有了美国便携式收音机市场的一半。这为日本成为美国的强大工业对手,埋下伏笔。\n1953 年,整个地球大概有 100 台计算机,苏联这时的计算机科技只比西方落后几年。苏联在 1950 年 ,完成了第一个可编程电子计算机,但苏联在太空竞赛远远领先。\n我们进入思想泡泡\n苏联在 1957 年 把第一个卫星送上轨道,史波尼克 1 号。不久,在 1961 年,苏联宇航员 尤里·加加林 第一个进入太空,美国民众对此不满,使得肯尼迪总统 在加加林太空任务一个月后,提出要登陆月球。登月很贵的!nasa 的预算增长了几乎十倍,在 1966 年达到顶峰,占了政府预算的 4.5% ,如今,nasa 的预算只占 0.5% 。nasa 用这笔钱资助各种科学研究,阿波罗计划花的钱最多,雇了 40 万人左右,而且有 2 万多家大学和公司参与。其中一个挑战是 怎样在太空中导航,nasa 需要电脑计算复杂的轨道来引导太空船,因此,他们造了 “阿波罗导航计算机”,有 3 个重要要求:\n计算机要快,这在意料之中; 计算机要又小又轻。太空船里的空间不多,而且要飞去月球,能轻一点是一点; 要超级可靠。 这对太空船非常重要,因为太空中有很多震动,辐射,极端温度变化。如果东西坏掉了,可没办法去\u0026quot;百思买\u0026quot;买新的。那时的主流科技 真空管和晶体管 无法胜任这些要求,所以 nasa 用全新科技:集成电路。\n我们几集前聊过,阿波罗导航计算机 首先使用了集成电路,nasa 是唯一负担得起集成电路的组织。最初,一个芯片差不多 50 美金,导航计算机需要上千个芯片,但美国也因此成功登月,打败苏联。\n谢了 思想泡泡\n虽然人们经常把集成电路的发展 归功于阿波罗导航计算机,但它们的产量很低,一共只有 17 次阿波罗任务。实际上是军事 大大推进了集成电路发展,特别是洲际导弹和核弹,使集成电路大规模生产。美国建造强大计算机时,也进一步推进了集成电路,一般叫\u0026quot;超级计算机\u0026quot;,因为它们经常比全球最快电脑还快 10 倍以上,但 cdc,cray,ibm 制造的计算机非常昂贵,几乎只有政府负担得起,这些计算机用于政府机构,比如美国国家安全局,以及实验室比如 劳伦斯·利弗莫尔 实验室 、 洛斯·阿拉莫斯 国家实验室。\n最初,美国的半导体行业 靠高利润政府合同起步,因此忽略了消费者市场,因为利润小。因此日本半导体行业在 1950 和 1960 年代 靠低利润率占领了消费者市场,日本人投入大量资金,大量制造以达到规模经济,同时研究技术,提高质量和产量 以及用自动化来降低成本。1970 年代,太空竞赛和冷战逐渐消退 高利润的政府合同变少,美国的半导体和电子设备公司发现更难竞争了。虽然很多计算机组件商品化了,但并没有什么帮助。dram 就是 dram ,能从日立买便宜的,干嘛要从英特尔买贵的? 1970 年代 美国公司开始缩小,合并,或直接倒闭 。1974 年 英特尔不得不裁员三分之一 ,知名的仙童半导体也在 1979 年濒临倒闭 ,被其他公司收购了。为了生存,很多公司把生产外包出去,降低成本。英特尔不再把精力放在 内存集成电路, 而是把精力放在处理器,这个决定最后挽救了公司。美国公司的无力 ,导致 夏普 和 卡西欧 这样的日本公司占领了 1970 年代的主流产品 - 手持计算器。因为集成电路,计算机又小又便宜。,取代了办公室里昂贵的桌面计算器。对大多数人 这是他们第一次不必用纸笔和计算尺来做计算,手持计算机因此大卖,进一步降低了集成电路的成本,使得微处理器被广泛使用,比如之前讨论过的 intel 4004 。intel 在 1971 年 应日本计算器公司 busicom 的要求做了这个芯片,很快,日本电子产品到处都是,从电视到手表到随身听,而廉价的微处理器,也催生了全新的产品,比如街机游戏。1972 年诞生了 pong,1976 年诞生了打砖块。因为成本不断下降,很快,普通人也买得起计算机了,这段期间,第一批家用电脑开始出现,比如 1975 年的 altair 8800,以及第一款家用游戏机,比如 1977 年的 atari 2600 。家用!我再说一遍 家用!如今没什么大不了的,但那时是计算机的全新时代!\n在短短三十年内,计算机从大到人类可以在 cpu 里走来走去(当然,你要有政府许可你这样做),发展到小到小孩都能拿住的手持玩具,而且微处理器还快得多。这种巨大变化是由两种力量推动的:政府和消费者!政府资金,比如冷战期间美国投入的钱,推动了计算机的早期发展,并且让计算机行业活得足够久,使得技术成熟到可以商用。然后是公司,最后是消费者,把计算机变成了主流。冷战虽然结束了,但这种关系今天仍在继续。政府依然在资助科学研究,情报机构依然在超级计算机,人类仍然被发射到太空里,而你依然在买电视,xbox,playstation,笔记本电脑和手机。\n因此,计算机会继续飞速发展。\n我们下周见。\n25. 个人计算机革命 i.e. the personal computer revolution\n(。・∀・)ノ゙嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n上周说过\u0026quot;个人计算机\u0026quot;的概念 ,在计算机发展的头 30 年难以想象,如果只让一个人用,成本实在太高。但到 70 年代初,各种组件的成本都下降了 可以做出低成本 同时性能足够强大的计算机。不是玩具级计算机,是真正能用的计算机。这个转变中 最有影响力的是 单芯片 cpu 的出现,强大 + 体积小 + 便宜 ,集成电路的进步,也提供了低成本固态存储器,可以用于计算机的 ram 和 rom 。忽然间 把整台计算机做到一张电路板上成为可能,大大地降低了制造成本,而且,那时有便宜可靠的储存介质, 比如磁带和软盘,最后是 低成本的显示器 ,通常是电视机稍作改装而成。如果在 1970 年代 将这四种原料混在一起,就得到了\u0026quot;微型计算机\u0026quot;。因为和那个时代的\u0026quot;普通\u0026quot;计算机相比 ,这些计算机很小,“普通\u0026quot;计算机就是公司或大学里的那种。但比大小更重要的是成本,这是有史以来第一次,计算机的价格足够低,“一个人专用\u0026quot;的想法变得可行,不用划分时间和别人公用计算机,没有多用户登录,计算机只属于一个人,只有一个用户,个人计算机时代到来!\n计算机成本下降+性能提升,让个人计算机成为可能,但这个时间点很难准确定义,并没有一个具体时间点,因此\u0026quot;第一台个人计算机\u0026quot;这个名号,有很多竞争者,比如 kenback-1 和 mcm/70 。不过第一台取得商业成功的个人计算机 争议较小:altair 8800,首次亮相在 1975 年《popular electronics》封面,售价 $439 美元,需要自己组装。计算通货膨胀后,相当如今的 2000 美元左右,不算小钱,但比起 1975 年的其它计算机,算是非常便宜了!\n各种需要自己组装的组件包卖给了计算机爱好者,因为买的人多,很快相关产品出现了,比如内存,纸带读取器,甚至电传接口,让你可以从纸带上读取更长更复杂的程序,然后用电传终端交互,但程序还是要用 机器码 写,写起来很麻烦,即使计算机爱好者也讨厌写,这没有吓跑年轻的比尔·盖茨和保罗·艾伦!他们当时是 19 岁和 22 岁,他们联系了制造 altair 8800 的 mits 公司,建议说,如果能运行 basic 程序 会对爱好者更有吸引力。basic 是一门更受欢迎更简单的编程语言,为此,他们需要一个程序 把 basic 代码转成可执行机器码,这叫 解释器 (interpreter)。“解释器\u0026quot;和\u0026quot;编译器\u0026quot;类似,区别是\u0026quot;解释器\u0026quot;运行时转换, 而\u0026quot;编译器\u0026quot;提前转换。\n让我们进入思想泡泡!\nmits 表示感兴趣,同意与 bill 和 paul 见个面,让他们演示一下。问题是,他们还没写好解释器,所以他们花了几个星期赶工 ,而且还不是在 altair 8800 上写的,最后在飞机上完成了代码。他们在墨西哥 阿尔伯克基(城市) 的 mits 总部做演示时,才知道代码可以成功运行。幸运的是进展顺利 mits 同意在计算机上搭载他们的软件,altair basic 成了微软的第一个产品。\n虽然 1975 年之前就有计算机爱好者,但 altair 8800 大量催生了更多计算机爱好者,爱好者们组成各种小组 分享知识,软件,以及对计算机的热爱,最具传奇色彩的小组是\u0026quot;家酿计算机俱乐部”。第一次小组聚会在 1975 年 3 月,看一台第一批运来加州的 altair 8800 。第一次聚会上,24 岁的 steve wozniak 被 altair 8800 大大激励,开始想设计自己的计算机。1976 年 5 月,他向小组展示了原型机,并且把电路图分享给感兴趣的其他会员,他的设计不同寻常 要连到电视显示,并提供文本界面,在低成本计算机上还是第一次见。同是俱乐部成员和大学朋友的 史蒂夫·乔布斯 建议说与其免费分享设计,不如直接出售装好的主板,但用户依然需要自己加键盘,电源和机箱。1976 年 7 月开始发售,价格 $666.66 美元,它叫 apple-i ,苹果计算机公司的第一个产品。\n谢了 思想泡泡\n就像 altair 8800 一样,apple-i 也是作为套件出售,apple-i 吸引了业余爱好者 不介意机器买回来自己组装,但个人消费者和公司对 apple-i 不感兴趣。\n这在 1977 年发生变化 市场上有了三款开箱即用的计算机。\n第一款是 apple-ii ,苹果公司第一个提供全套设备的产品,设计和制造工艺都是专业的,它还提供了简单彩色图形和声音输出,这些功能对低成本机器非常了不起。apple-ii 卖了上百万套,把苹果公司推到了个人计算机行业的前沿。第二款是\u0026quot;trs-80 1 型”,由 tandy 公司生产,由 radioshack 销售,所以叫 trs。虽然不如 apple-ii 先进 但因为价格只有一半,所以卖得很火爆。最后一款是 commodore pet 2001 ,有一体化设计,集成了计算机,显示器,键盘和磁带驱动器,目标是吸引普通消费者。\n计算机和家用电器之间的界限开始变得模糊,这 3 台计算机被称为 1977 年的\u0026quot;三位一体” 。它们都自带了 basic 解释器,让不那么精通计算机的人也能用 basic 写程序,针对消费者的软件行业 开始腾飞。市场上出现了各种 针对个人计算机的游戏和生产力工具,比如计算器和文字处理器,最火的是 1979 年的 visicalc - 第一个电子表格程序,比纸好无数倍,是微软 excel 和 google sheets 的老祖先。\n但这些计算机带来的最大影响 也许是他们的营销策略,它们针对普通消费者, 而不是企业和爱好者。这是第一次大规模地,计算机出现在家庭,小公司,以及学校中,这引起了全球最大计算机公司 ibm 的注意,其市场份额从 1970 年的 60% 在 1980 年降到了 30%左右,因为 ibm 忽略了增长的\u0026quot;微型计算机\u0026quot;市场,这个市场每年增长约 40% 。随着微型计算机演变成个人计算机 ibm 知道他们需要采取行动,但要做到这一点 公司要从根本上重新思考战略和设计 。1980 年 ibm 最便宜的计算机 “5120\u0026quot;的价格大概是一万美元,永远也没法和 apple-ii 这样的计算机竞争,意味着要从头开始。一个由十二名工程师组成的精干团队(后来叫\u0026quot;肮脏十二人”),被派往佛罗里达州的 博卡拉顿(boca raton)办公室,让他们独立工作。不受 ibm 内部的政治斗争干扰 他们想怎么设计怎么设计,没用 ibm 的 cpu,选了 intel 的芯片,也没用 ibm 的首选操作系统 cp/m ,而是用了微软的 dos 。依此类推,从屏幕到打印机都这样自由选择 。ibm 第一次不得不与外部公司竞争,来给新计算机做硬件和软件,这和 ibm 的传统做法不同:自己做硬件来节省成本,然后和其它公司合作,经过短短一年,ibm 个人计算机发布了,简称 ibm pc,产品立马取得了成功。长期信任 ibm 品牌的企业买了很多,但最有影响力的是 它使用 “开放式架构”,有良好的文档和扩展槽,使得第三方可以做硬件/外设 - 包括显卡,声卡,外置硬盘,游戏控制杆 以及无数其它组件,这刺激了创新,激发了竞争,产生了巨大的生态系统,这个开放架构叫 ibm compatible\u0026quot;( ibm 兼容 ),意味着如果买了\u0026quot;ibm 兼容\u0026quot;的计算机,你可以用庞大生态系统中的其它软硬件。开放架构也意味着 竞争对手公司可以遵循这个标准,做出自己的\u0026quot;ibm 兼容\u0026quot;计算机。很快,康柏和戴尔也开始卖 pc ,微软很乐意把 ms-dos 授权给他们,使 dos 迅速成为最受欢迎的 pc 操作系统。仅在前三年 ibm 就卖出了 200 万台 pc ,超过了苹果。有了庞大用户群,软件和硬件开发人员 把精力放在\u0026quot;ibm 兼容\u0026quot;平台,因为潜在用户更多,同时,想买计算机的人 也会看哪种计算机的软硬件选择更多,就像雪球效应一样,而那些生产非\u0026quot;ibm 兼容\u0026quot;计算机的公司 (一般性能更好),都失败了。只有苹果公司在没有\u0026quot;ibm 兼容\u0026quot;的情况下 保持了足够市场份额,苹果公司最终选了相反的方式:“封闭架构”,即自己设计一切,用户一般无法加新硬件到计算机中,意味着苹果公司要做自己的计算机,自己的操作系统,还有自己的外围设备,如显示器,键盘和打印机。通过控制整个范围,从硬件到软件,苹果能控制用户体验并提高可靠性。不同的商业策略是 “mac vs pc 谁更好” 这种争论的起源,这些争论如今还存在 不过\u0026quot;mac vs pc\u0026quot;用词不对,因为它们都是个人计算机!但是随便啦!\n为了在低成本个人计算机的竞争冲击下生存下来,苹果需要提高自身水平 提供比 pc 和 dos 更好的用户体验,他们的答案是 macintosh,于 1984 年发布 - 一台突破性 价格适中的一体式计算机 ,用的不是命令行界面,而是图形界面!\n我们下周讨论图形界面。到时见。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/15-cscc-%E5%85%B6%E4%BB%961/","summary":"\u003cblockquote\u003e\n\u003cp\u003e后续章节为概念性章节,统一汇总在该章节内。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"15 cscc 其他1"},]
[{"content":" 好吧,内容不少,为了后续插入图片之后,页面太大,我们这里拆分到两个页面中。\n26. 图形用户界面 i.e. graphical user interfaces\n(。・∀・)ノ゙嗨 我是 carrie anne 欢迎收看计算机科学速成课。\n我们上集最后 ,谈了苹果在 1984 年发布的 macintosh ,这是普通人可以买到的 第一台带图形用户界面的计算机,还带一个鼠标。那时的计算机全是命令行, 图形界面是个革命性进展,不必记住或猜正确的命令,图形界面直接显示了,你可以做什么,只要在屏幕上找选项就行了。这是一个\u0026quot;选择并点击\u0026quot;的界面,突然间计算机更直观了。不只是爱好者或科学家能用计算机 ,任何人都可以用计算机解决问题。\n人们认为是 macintosh 把图形用户界面(gui)变成主流,但实际上图形界面是数十年研究的成果。前几集,我们讨论了早期的交互式图形程序,比如 sketchpad 和太空战争 都是 1962 年制作的,但都是一次性项目,不是整合良好的体验,现代图形界面的先驱 可以说是 道格拉斯·恩格尔巴特。\n让我们进入思想泡泡!\n二战期间 恩格尔巴特 驻扎在菲律宾做雷达操作员,他读了 万尼瓦尔·布什 的 memex 文章,这些文章启发了他。当他海军服役结束时,他回到学校 1955 年在 ucb 取得博士学位,他沉溺于新兴的计算机领域,他在 1962 年一份开创性报告中 汇集了各种想法,报告名为:“增强人类智力”。恩格尔巴特认为,人类面临的问题 比解决问题的能力增长得更快,因此,找到增强智力的方法 似乎是必要且值得一做的目标。他构想计算机不仅做自动化工作,也可以成为未来知识型员工 应对复杂问题的工具。\n伊凡·苏泽兰 的\u0026quot;几何画板\u0026quot; 进一步启发了 恩格尔巴特,他决定动手把愿景变为现实 开始招募团队来做 on-line system ,他意识到如果只有键盘 ,对他想搭建的程序来说是不够的。用他的话说:“我们设想人们用计算机辅助工作站来增强工作,用户需要和屏幕上的信息互动,用某种设备在屏幕上移动 [光标]\u0026quot;。\n1964 年,和同事比尔·英格利希的共同努力下,他创造了第一个计算机鼠标,尾部有一根线,看起来很像老鼠 因此\u0026quot;鼠标\u0026quot;这个名字沿用了下来。\n谢了思想泡泡!\n1968 年 恩格尔巴特 在\u0026quot;秋季计算机联合会议\u0026quot;展示了他的系统 ,这次演示 被视为如今所有演示的祖先,演示有 90 分钟 展现了现代计算机的许多功能:包括 位图图像、视频会议、文字处理和实时协作编辑文件,还有现代图形界面的原型 - 比如鼠标和多窗口, 不过窗口不能重叠,远远先于那个时代。\n就像其它\u0026quot;跨时代\u0026quot;的产品一样,它最终失败了,至少商业上是这样,但它对当时的计算机研究者影响巨大,恩格尔巴特 因此获得 1997 年图灵奖。\n政府资金在 1970 年代初开始减少,我们在两集前说过(第 24 集:冷战和消费主义)。那时,恩格尔巴特团队里的许多人,包括比尔·英格利希去了施乐公司新成立的\u0026quot;帕洛阿尔托研究中心”,更为人熟知的名字是 xerox parc 。他们在这里开发了第一台带真正 gui 的计算机:施乐奥托 , 于 1973 年完成。为了让计算机易于使用,需要的不只是花哨的图形,还要借助一些人们已经熟悉的概念,让人们不用培训 就能很快明白如何使用。施乐的答案是将 2d 屏幕当作\u0026quot;桌面\u0026quot;,就像桌面上放很多文件一样,用户可以打开多个程序 每个程序都在一个框里,叫\u0026quot;窗口\u0026quot;,就像桌上的文件一样。窗口可以重叠,挡住后面的东西,还有桌面配件,比如计算器和时钟。用户可以把配件在屏幕上四处移动,它不是现实桌面的完美复制,而是用桌面这种隐喻,因此叫\u0026quot;桌面隐喻\u0026quot;。有很多方法来设计界面, 但 alto 团队用窗口,图标,菜单和指针来做 - 因此叫 wimp 界面。如今大部分图形界面都用这个,它还提供了一套基本部件,可复用的基本元素, 比如按钮,打勾框,滑动条和标签页,这些也来自现实世界,让人们有熟悉感,gui 程序就是这些小组件组成的。\n让我们试着写一个简单例子。\n首先,我们必须告诉操作系统 为程序创建一个窗口\r我们通过 gui api 实现 需要指定窗口的名字和大小\r假设是 500×500 像素\r现在再加一些小组件,一个文本框和一个按钮\r创建它们需要一些参数\r首先要指定出现在哪个窗口 因为程序可以有多个窗口\r还要指定默认文字窗口中的 x,y 位置 以及宽度和高度\r好,现在我们有个 看起来像 gui 程序的东西\r但它还没有功能\r如果点 roll 按钮,什么也不会发生\r在之前的例子中,代码是从上到下执行的\r但 gui 是 \u0026#34;事件驱动编程\u0026#34;\r代码可以在任意时间执行 以响应事件\r这里是用户触发事件 比如点击按钮,选一个菜单项,或滚动窗口\r或一只猫踩过键盘\r就会一次触发好多事件!\r假设当用户点 roll 按钮\r我们产生 1 到 20 之间的随机数\r然后在文本框中,显示这个数字\r我们可以写一个函数来做\r我们还可以让它变有趣些,假设随机数是 20 就把背景颜色变成血红色!\r最后,把代码与\u0026#34;事件\u0026#34;相连 每次点按钮时 都触发代码\r那么,要设置事件触发时 由哪个函数来处理\r我们可以在初始化函数中,加一行代码来实现\r我们要处理的,是\u0026#34;点击\u0026#34;事件 然后函数会处理这个事件\r现在完成了 可以点按钮点上一整天 每次都会执行 rolld20 函数,这就是程序背后的原理。在编辑器里点 粗体 ,或菜单里选 关机 ,一个处理该事件的函数会触发,希望不会随机到 20,啊!!!\n好,现在回到施乐奥托!\n大约制作了 2000 台奥托有的在施乐公司内部用,有的送给大学实验室,从来没有商业出售过,然而,parc 团队不断完善硬件和软件,最终于 1981 年发布了 施乐之星系统,施乐之星扩展了\u0026quot;桌面隐喻\u0026quot;。现在文件看起来就像一张纸 ,还可以存在文件夹里,这些都可以放桌面上,或数字文件柜里,这样来隐喻底层的文件系统。\n从用户角度来看,是一层新抽象!\n施乐卖的是印刷机 但在文本和图形制作工具领域也有领先,例如,他们首先使用了 “剪切\u0026quot;“复制\u0026quot;“粘贴\u0026quot;这样的术语,这个比喻来自编辑打字机文件,真的是剪刀\u0026quot;剪切” 然后胶水\u0026quot;粘贴” 到另一个文件,然后再复印一次,新文件就是一层了,看不出编辑的痕迹……\n感谢计算机的出现!\n文字处理软件出现后 这种手工做法就消失了。apple ii 和 commodore pet 上有文字处理软件,但施乐在这点上走的更远。无论你在计算机上做什么, 文件打印出来应该长得一样,他们叫这个\u0026quot;所见即所得”。不幸的是,就像恩格尔巴特的 on-line system ,施乐之星也领先于那个时代,销售量不高,因为在办公室里配一个,相当如今 20 万美元 。ibm 同年推出了 ibm pc ,之后便宜的\u0026quot;ibm 兼容\u0026quot;计算机席卷市场,但 parc 研究人员花了十几年做的这些 没有被浪费。\n1979 年 12 月,施乐之星出货前一年半,有个人去施乐公司参观 你可能听说过这个人:史蒂夫·乔布斯。这次参观有很多传闻,许多人认为乔布斯和苹果偷走了施乐的创意,但那不是事实。事实上是施乐公司主动找苹果,希望合作,最终施乐还买了苹果的一百万美元股份,在苹果备受瞩目的 首次公开募股 (ipo) 前买的,但一个额外条款是: “公布一切施乐研究中心正在进行的酷工作” 。史蒂夫知道他们很厉害,但他完全没预想到这些,其中有个演示是,一个清晰的位图显示器上,运行着施乐公司的图形界面 ,操作全靠鼠标直观进行。史蒂夫后来说:“就像拨开了眼前的一层迷纱,我可以看到计算机产业的未来”。史蒂夫和随行的工程师回到苹果公司,开始开发新功能,比如菜单栏和垃圾桶,垃圾桶存删除文件,满了甚至会膨胀 - 再次使用了隐喻。苹果第一款有图形界面和鼠标的产品,是 1983 年发行的 apple lisa ,一台超级先进的机器,标了\u0026quot;超级先进\u0026quot;的价格 - 差不多是如今的 25000 美元。虽然比施乐之星便宜不少,但在市场上同样失败。幸运的是,苹果还有另一个项目: macintosh,于 1984 年发布,价格大约是如今的 6000 美元 - lisa 的四分之一。它成功了,开售 100 天就卖了 7 万台,但在最初的狂潮后,销售额开始波动。苹果公司卖的 apple ii 比 mac 多,一个大问题是:没人给这台新机器做软件,之后情况变得更糟,竞争对手赶上来了。不久,其它价格只有 mac 几分之一的个人计算机 有了原始但可用的图形界面,消费者认可它们, pc 软件开发者也认可。随着苹果的财务状况日益严峻 以及和苹果新 ceo 约翰·斯卡利 的关系日益紧张,史蒂夫乔布斯被赶出了苹果公司。几个月后,微软发布了 windows 1.0 ,它也许不如 mac os 漂亮,但让微软在市场中站稳脚跟 奠定了统治地位。十年内,95%的个人计算机上都有微软的 windows。最初,mac os 的爱好者还可以说 mac 有卓越的图形界面和易用性,windows 早期版本都是基于 dos 而 dos 设计时 ,没想过运行图形界面,但 windows 3.1 之后,微软开始开发新的,面向消费者的 gui 操作系统,叫 windows 95,这是一个意义非凡的版本 ,不仅提供精美的界面,还有 mac os 没有的高级功能,比如\u0026quot;多任务\u0026quot;和\u0026quot;受保护内存\u0026quot;。windows 95 引入了许多 如今依然见得到的 gui 元素,比如开始菜单,任务栏和 windows 文件管理器。\n不过微软也失败过,为了让桌面更简单友好, 微软开发了一个产品叫 microsoft bob ,将比喻用到极致。现在屏幕上有了一个虚拟房间,程序是物品,可以放在桌子和书架上,甚至还有噼啪作响的壁炉 和提供帮助的虚拟狗狗,你看到那边的门没?,是的,那些门通往不同房间 房间里有不同程序,你可能猜到了,它没有获得成功。这是一个好例子,说明如今的用户界面是自然选择后的结果。无论你用的是 windows,mac,linux 或其他 gui,几乎都是施乐奥托 wimp 的变化版。一路上,人们试了各种做法并失败了。一切都必须发明,测试,改进,适应或抛弃,如今,图形界面无处不在 使用体验一般只是可以接受,而不是非常好,你肯定体验过差劲的设计,比如下载了很烂的 app,用过别人糟糕的手机,或者看到过很差的网站,因此计算机科学家和界面设计师 会继续努力工作,做出更好更强大的界面,朝着恩格尔巴特\u0026quot;增强人类智能\u0026quot;的愿景努力。\n我们下周见。\n27. 3d 图形 i.e. 3d graphics\n嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n在过去五集,我们从基于电传打字机的命令行界面讲到图形怎么显示到屏幕上,再到上集的 图形用户界面(gui),以及图形界面的美味。\n之前的例子都是 2d, 但我们生活的世界是 3d 的,我也是个三维 girl~\n所以今天,我们讲 3d 图形的基础知识,以及如何渲染 3d 图形到 2d 屏幕上。24 集中说过,可以写一个函数,从 a 到 b 画一条线,通过控制 a 和 b 的 (x,y) 坐标,可以控制一条线。在 3d 图像中,点的坐标不再是两点,而是三点,x,y,z ,或读\u0026quot;zee\u0026quot;,但我之后会读成\u0026quot;zed\u0026quot; 。当然,2d 的电脑屏幕上、不可能有 xyz 立体坐标轴,所以有图形算法 负责把 3d 坐标\u0026quot;拍平\u0026quot;显示到 2d 屏幕上,这叫 “3d 投影” 。所有的点都从 3d 转成 2d 后,就可以用画 2d 线段的函数 来连接这些点,这叫 “线框渲染” 。想象用筷子做一个立方体,然后用手电筒照它,墙上的影子就是投射,是平的。如果旋转立方体,投影看起来会像 3d 物体,尽管是投影面是平的,电脑也是这样 3d 转 2d ,只不过用大量数学,而不是筷子。\n3d 投影有好几种,你现在看到的,叫 正交投影 。立方体的各个边,在投影中互相平行,在真实 3d 世界中,平行线段会在远处收敛于一点,就像远处的马路汇聚到一点,这叫 透视投射 。过程是类似的,只是数学稍有不同。有时你想要透视投影,有时不想,具体取决于开发人员。\n如果想画立方体这种简单图形,直线就够了,但更复杂的图形,三角形更好,在 3d 图形学中 我们叫三角形\u0026quot;多边形\u0026quot;(polygons),看看这个多边形组成的 漂亮茶壶。一堆多边形的集合叫 网格 。网格越密,表面越光滑,细节越多,但意味着更多计算量。游戏设计者要平衡角色的真实度 和多边形数量,如果数量太多 帧率会下降到肉眼可感知,用户会觉得卡,因此有算法用来简化网格。\n之所以三角形更常用 而不是用正方形,或其它更复杂的图形,是因为三角形的简单性。空间中三点定义一个平面,如果给 3 个 3d 点,我能画出一个平面,而且只有这一个答案,4 个或多于 4 个点就不一定了,而 2 个点不够定义平面,只能定义线段,所以 3 是最完美的数字,三角形万岁。\n线框渲染 虽然很酷,但 3d 图像需要填充,填充图形的经典算法叫 扫描线渲染 (scanline rendering) ,于 1967 年诞生在犹他州大学。为了例子简单,我们只看一个多边形。我们要思考这个多边形如何转成一块填满像素的区域,我们先铺一层像素网格,扫描线算法 先读多边形的 3 个点,找最大和最小的 y 值,只在这两点间工作,然后算法从上往下,一次处理一行,计算每一行和多边形相交的 2 个点。因为是三角形,如果相交一条边,必然相交另一条,扫描线算法 会填满 2 个相交点之间的像素。\n来看个具体例子。第一行 相交于这里和这里,算法把两点间填满颜色,然后下一行,再下一行,所以叫 扫描线渲染,扫到底部就完成了。填充的速度叫 fillrate(填充速率)。当然 这样的三角形比较丑,边缘满是锯齿,当像素较小时 就不那么明显,但尽管如此,你肯定在游戏里见过这种效果,特别是低配电脑。一种减轻锯齿的方法叫 抗锯齿 (antialiasing),与其每个像素都涂成一样的颜色,可以判断多边形切过像素的程度,来调整颜色,如果像素在多边形内部,就直接涂颜色,如果多边形划过像素,颜色就浅一些,这种边缘羽化的效果看着更舒服些。抗锯齿 被广泛使用,比如字体和图标,如果你把脸贴近屏幕,近点, 再近点,你能看到浏览器里字体是抗锯齿的,超平滑。\n在 3d 场景中,多边形到处都是,但只有一部分能看见,因为其它的被挡住了,这叫 遮挡 。最直接的处理办法是用排序算法,从远到近排列,然后从远到近渲染,这叫 画家算法 。因为画家也是先画背景,然后再画更近的东西。\n看这个例子,有 3 个重叠的多边形。为了简单,我们画成不同颜色,同时,假设 3 个多边形都和屏幕平行,但在实际应用中,比如游戏里,多边形可能是倾斜的,3 个多边形 a,b,c,距离 20,12,14 。画家算法的第一件事,是从远到近排序,现在有序了,我们可以用 扫描线算法 填充多边形,一次填一个。我们从最远的 a 开始,然后重复这个过程,填充第二远的 c ,然后是 b 。现在完成了,可以看到顺序是对的,近的多边形在前面!\n还有一种方法叫 深度缓冲 ,它和之前的算法做的事情一样,但方法不同。我们回到之前的例子,回到排序前的状态。因为这个算法不用排序,所以速度更快。简而言之,z-buffering 算法会记录场景中每个像素和摄像机的距离,在内存里存一个数字矩阵。首先,每个像素的距离被初始化为\u0026quot;无限大\u0026quot;,然后 z-buffering 从列表里第一个多边形开始处理,也就是 a ,它和扫描线算法逻辑相同,但不是给像素填充颜色,而是把多边形的距离 和 z-buffer 里的距离进行对比,它总是记录更低的值 ,a 距离 20,20 小于\u0026quot;无限大\u0026quot;,所以缓冲区记录 20 ,算完 a 之后算下一个,以此类推 。因为没对多边形排序,所以后处理的多边形并不总会覆盖前面的,对于多边形 c ,缓冲区里只有一部分值会被多边形 c 的距离值覆盖。z 缓冲区完成后,会和\u0026quot;扫描线\u0026quot;算法的改进高级版配合使用,不仅可以勘测到线的交叉点,还可以知道某像素是否在最终场景中可见。如果不可见,扫描线算法会跳过那个部分,当两个多边形距离相同时,会出现一个有趣问题,比如多边形 a 和 b 距离都是 20, 哪个画上面?多边形会在内存中移来移去,访问顺序会不断变化。另外,计算浮点数有舍入误差,所以哪一个画在上面,往往是不可预测的,导致出现 z-fighting 效果 如果你玩过 3d 游戏,肯定见过。\n说起 故障,3d 游戏中有个优化叫 背面剔除 。你想想,三角形有两面,正面和背面,游戏角色的头部或地面,只能看到朝外的一面,所以为了节省处理时间,会忽略多边形背面,减了一半多边形面数。这很好,但有个 bug 是 如果进入模型内部往外看,头部和地面会消失。\n继续,我们讲灯光,也叫 明暗处理 ,因为 3d 场景中,物体表面应该有明暗变化。我们回到之前的茶壶网格,用\u0026quot;扫描线\u0026quot;算法渲染所有多边形后,茶壶看起来像这样,没什么 3d 感。我们来加点灯光,提高真实感。为了举例,我们从茶壶上挑 3 个不同位置的多边形,和之前的例子不同,这次要考虑这些多边形面对的方向,它们不平行于屏幕,而是面对不同方向,他们面对的方向叫 “表面法线” 。我们可以用一个垂直于表面的小箭头来显示这个方向,现在加个光源,每个多边形被照亮的程度不同,有的更亮,因为面对的角度导致更多光线反射到观察者。举个例子,底部的多边形向下倾斜,远离光源,所以更暗一些。类似的,最右的多边形更背对光源,所以只有部分照亮。最后是左上角的多边形,因为它面对的角度 意味着会把光线反射到我们这里,所以会显得更亮。如果对每个多边形执行同样的步骤,看上去会更真实!这叫 平面着色 ,是最基本的照明算法。不幸的是,这使多边形的边界非常明显,看起来不光滑,因此开发了更多算法,比如 高洛德着色 和 冯氏着色,不只用一种颜色给整个多边形上色,而是以巧妙的方式改变颜色得到更好的效果。\n我们还要说下 “纹理” ,纹理在图形学中指外观,而不是手感,就像照明算法一样,纹理也有多种算法,来做各种花哨效果。最简单的是 纹理映射 ,为了理解纹理映射,回到单个多边形,用\u0026quot;扫描线算法\u0026quot;填充时,可以看看内存内的纹理图像,决定像素用什么颜色。为了做到这点,需要把多边形坐标和纹理坐标对应起来,我们来看看\u0026quot;扫描线算法\u0026quot;要填充的第一个像素,纹理算法会查询纹理,从相应区域取平均颜色,并填充多边形,重复这个过程,就可以获得纹理。\n如果结合这集提到的所有技巧 会得到一个精美的小茶壶。这个茶壶可以放进更大的场景里,场景由上百万个多边形组成。渲染这样的场景需要大量计算,但重要的是,再大的场景,过程都是一样的,一遍又一遍,处理所有多边形,扫描线填充,抗锯齿,光照,纹理化,然而,有几种方法可以加速渲染:\n首先,我们可以为这种特定运算做专门的硬件来加快速度,让运算快如闪电 其次,我们可以把 3d 场景分解成多个小部分,然后并行渲染,而不是按顺序渲染。 cpu 不是为此设计的,因此图形运算不快,所以,计算机工程师为图形做了专门的处理器,叫 gpu “图形处理单元” 。gpu 在显卡上,周围有专用的 ram ,所有网格和纹理都在里面,让 gpu 的多个核心可以高速访问。现代显卡,如 geforce gtx 1080 ti 有 3584 个处理核心,提供大规模并行处理,每秒处理上亿个多边形!\n好了,本集对 3d 图形的介绍到此结束。下周我们聊全新的主题。\n我到时会 ping 你~\n28. 计算机网络 i.e. computer networks\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n互联网太棒啦,键盘敲几下就能在 youtube 直播–哈喽!在维基百科上阅读文章,在亚马逊买东西,和朋友视频发一条天气推特。毫无疑问,用户在全球网络中发送和接收信息的能力,永远改变了这个世界。150 年前 发一封信件从伦敦到加州 要花 2~3 周,而且还是特快邮件,如今,电子邮件只要几分之一秒。“时延\u0026quot;改善了上百万倍 (时延指传播一条信息所需的时间),振兴了全球经济,帮助现代世界在遍布全球的光纤中快速发展。\n你可能觉得计算机和网络密切相关,但事实上,1970 年以前 大多数计算机是独立运行的,然而 ,因为大型计算机开始随处可见,廉价机器开始出现在书桌上,分享数据和资源渐渐变得有用起来,首个计算机网络出现了。\n今天起,我们花 3 集视频讲网络是如何发展成现在的样子,以及支撑它们的基础原理和技术。\n第一个计算机网络出现在 1950~1960 年代,通常在公司或研究室内部使用,为了方便信息交换,比把纸卡或磁带送到另一栋楼里更快速可靠,这叫 “球鞋网络” 。\n第二个好处是能共享物理资源。举个例子,与其每台电脑配一台打印机,大家可以共享一台联网的打印机。早期网络也会共享存储空间,因为每台电脑都配存储器太贵了。\n计算机近距离构成的小型网络叫局域网,简称 lan(local area networks)。局域网能小到是同一个房间里的两台机器,或大到校园里的上千台机器。尽管开发和部署了很多不同 lan 技术,其中最著名和成功的是 “以太网”, 开发于 1970 年代 ,在施乐的\u0026quot;帕洛阿尔托研究中心\u0026quot;诞生,今日仍被广泛使用。\n以太网的最简单形式是:一条以太网电线连接数台计算机,当一台计算机要传数据给另一台计算机时,它以电信号形式,将数据传入电缆,当然 因为电缆是共享的,连在同一个网络里的其他计算机也看得到数据,但不知道数据是给它们的,还是给其他计算机的。为了解决这个问题,以太网需要每台计算机有唯一的媒体访问控制地址,简称 mac 地址。这个唯一的地址放在头部,作为数据的前缀发送到网络中,所以,计算机只需要监听以太网电缆,只有看到自己的 mac 地址,才处理数据。这运作得很好,现在制造的每台计算机都自带唯一的 mac 地址,用于以太网和无线网络。\n多台电脑共享一个传输媒介,这种方法叫 “载波侦听多路访问” ,简称\u0026quot;csma” 。载体 (carrier) 指运输数据的共享媒介,以太网的\u0026quot;载体\u0026quot;是铜线,wifi 的\u0026quot;载体\u0026quot;是传播无线电波的空气。很多计算机同时侦听载体,所以叫\u0026quot;侦听\u0026quot;和\u0026quot;多路访问\u0026quot;,而载体传输数据的速度 叫 “带宽” 。不幸的是,使用共享载体有个很大的弊端 - 当网络流量较小时 计算机可以等待载体清空,然后传送数据,但随着网络流量上升,两台计算机想同时写入数据的概率也会上升,这叫冲突,数据全都乱套了、就像两个人同时在电话里讲话,幸运的是,计算机能够通过监听电线中的信号检测这些冲突,最明显的解决办法是停止传输,等待网络空闲,然后再试一遍。问题是 其他计算机也打算这样做,其他等着的计算机可能在任何停顿间隙闯入,导致越来越多冲突。很快,每个人都一个接一个地讲话,而且有一堆事要说,就像在家庭聚餐中和男朋友分手一样,馊主意!\n以太网有个超简单有效的解决方法,当计算机检测到冲突,就会在重传之前等待一小段时间,因为要举例,假设是 1 秒好了,当然 如果所有计算机用同样的等待时间 是不行的,它们会在一秒后再次冲突,所以加入一个随机时间 一台计算机可能等 1.3 秒,另一台计算机等待 1.5 秒,要是运气好 等 1.3 秒的计算机会醒来,发现载体是空闲的 然后开始传输,当 1.5 秒的计算机醒来后,会发现载体被占用,会等待其他计算机完成,这有用,但不能完全解决问题,所以要用另一个小技巧。 正如我刚才说的,如果一台计算机在传输数据期间检测到冲突,会等一秒+随机时间,然而 ,如果再次发生冲突 表明有网络拥塞,这次不等 1 秒,而是等 2 秒,如果再次发生冲突 等 4 秒 然后 8 秒 16 秒等等,直到成功传输。因为计算机的退避,冲突次数降低了,数据再次开始流动起来,网络变得顺畅,家庭晚餐有救啦! 这种指数级增长等待时间的方法叫: 指数退避。以太网和 wifi 都用这种方法,很多其他传输协议也用。但即便有了\u0026quot;指数退避\u0026quot;这种技巧,想用一根网线链接整个大学的计算机还是不可能的,为了减少冲突+提升效率,我们需要减少同一载体中设备的数量。载体和其中的设备总称 “冲突域” 。\n让我们回到之前以太网的例子,一根电缆连 6 台计算机,也叫一个冲突域。为了减少冲突,我们可以用交换机把它拆成两个冲突域,交换机位于两个更小的网络之间,必要时才在两个网络间传数据。交换机会记录一个列表,写着哪个 mac 地址在哪边网络。如果 a 想传数据给 c ,交换机不会把数据转发给另一边的网络,没必要。如果 e 想同一时间传数据给 f,网络仍然是空的,两个传输可以同时发生,但如果 f 想发数据给 a 数据会通过交换机,两个网络都会被短暂占用。\n大的计算机网络也是这样构建的,包括最大的网络 - 互联网,也是多个连在一起的稍小一点网络,使不同网络间可以传递信息。这些大型网络有趣之处是,从一个地点到另一个地点通常有多条路线,这就带出了另一个话题 路由 。\n连接两台相隔遥远的计算机或网路,最简单的办法 是分配一条专用的通信线路,早期电话系统就是这样运作的。假设\u0026quot;印第安纳波利斯\u0026quot;和\u0026quot;米苏拉\u0026quot;之间,有五条电话线,如果在 1910 年代,john 想打电话给 hank,john 要告诉操作员他想打到什么地方,然后工作人员手动将 john 的电话连到 通往米苏拉的未使用线路。通话期间,这条线就被占用了,如果五条线都被占用了 john 要等待某条线空出来,这叫 “电路交换” ,因为是把电路连接到正确目的地。能用倒是能用 ,但不灵活而且价格昂贵 ,因为总有闲置的线路。好处是 如果有一条专属于自己的线路 你可以最大限度地随意使用,无需共享。因此军队,银行和其他一些机构,依然会购买专用线路来连接数据中心。\n传输数据的另一个方法是 “报文交换” ,“报文交换” 就像邮政系统一样,不像之前 a 和 b 有一条专有线路,消息会经过好几个站点。 如果 john 写一封信给 hank,信件可能从\u0026quot;印第安纳波利斯\u0026quot;到\u0026quot;芝加哥\u0026quot;,然后\u0026quot;明尼阿波利斯\u0026quot; ,然后\u0026quot;比林斯\u0026quot; 最后到\u0026quot;米苏拉\u0026quot;。每个站点都知道下一站发哪里 ,因为站点有表格,记录到各个目的地,信件该怎么传。报文交换的好处是 可以用不同路由 ,使通信更可靠更能容错。\n回到邮件的例子,如果\u0026quot;明尼阿波利斯\u0026quot;有暴风雪中断了通信 “芝加哥\u0026quot;可以传给\u0026quot;奥马哈”,在这个例子里,城市就像路由器一样,消息沿着路由跳转的次数 叫 “跳数” (hop count)。记录跳数很有用,因为可以分辨出路由问题。举例,假设芝加哥认为 去米苏拉的最快路线是 奥马哈,但奥马哈认为 去米苏拉的最快路线是 芝加哥,这就糟糕了,因为 2 个城市看到目的地是米苏拉,结果报文会在 2 个城市之间 不停传来传去,不仅浪费带宽 ,而且这个路由错误需要修复! 这种错误会被检测到,因为跳数记录在消息中 ,而且传输时会更新跳数。如果看到某条消息的跳数很高 ,就知道路由肯定哪里错了,这叫 “跳数限制” 。\n报文交换的缺点之一是有时候报文比较大,会堵塞网络 ,因为要把整个报文从一站传到下一站后 才能继续传递其他报文。传输一个大文件时,整条路都阻塞了,即便你只有一个 1kb 的电子邮件要传输 ,也只能等大文件传完,或是选另一条效率稍低的路线,这就糟了。\n解决方法是 将大报文分成很多小块,叫 “数据包” ,就像报文交换 ,每个数据包都有目标地址 ,因此路由器知道发到哪里。报文具体格式由\u0026quot;互联网协议\u0026quot;定义,简称 ip 。这个标准创建于 1970 年代,每台联网的计算机都需要一个 ip 地址。你可能见过,以点分隔的 4 组数字,例如 172.217.7.238 是 google 其中一个服务器的 ip 地址。数百万台计算机在网络上不断交换数据 ,瓶颈的出现和消失是毫秒级的,路由器会平衡与其他路由器之间的负载, 以确保传输可以快速可靠,这叫 “阻塞控制” 。\n有时,同一个报文的多个数据包 会经过不同线路,到达顺序可能会不一样,这对一些软件是个问题。幸运的是,在 ip 之上还有其他协议,比如 tcp/ip, 可以解决乱序问题。我们下周会讲。\n将数据拆分成多个小数据包,然后通过灵活的路由传递,非常高效且可容错,如今互联网就是这么运行的,这叫 “分组交换” 。有个好处是 它是去中心化的,没有中心权威机构,没有单点失败问题。事实上 ,因为冷战期间有核攻击的威胁,所以创造了分组交换。如今,全球的路由器协同工作,找出最高效的线路,用各种标准协议运输数据,比如 “因特网控制消息协议”(icmp) 和 “边界网关协议”(bgp)。世界上第一个分组交换网络以及现代互联网的祖先是 arpanet(advanced research projects agency),名字来源于赞助这个项目的机构,美国高级研究计划局。\n这是 1974 年整个 arpanet 的样子,每个小圆表示一个地点, 比如大学或实验室,那里运行着一个路由器,并且有一台或多台计算机,能看到 “pdp-1” 和\u0026quot;ibm 360 系统\u0026quot;,甚至还有一个伦敦的 atlas 是通过卫星连到网络里的。显然 ,互联网在这几十年间发展迅速,如今不再只有几十台计算机联网 据估计 有接近 100 亿台联网设备,而且互联网会继续快速发展,特别是如今各种智能设备层出不穷 ,比如联网冰箱,恒温器,以及其他智能家电,它们组成了\u0026quot;物联网\u0026quot;。\n第一部分到此结束 我们对计算机网络进行了概览。\n网络是一堆管子组成的吗?额 算是吧。下周我们会讨论一些高级传输协议,然后讲万维网。\n到时见啦。\n29. 互联网 i.e. the internet\n(。・∀・)ノ゙嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n上集讲到,你的计算机和一个巨大的分布式网络连在一起,这个网络叫互联网。\n你现在就在网上看视频呀。互联网由无数互联设备组成,而且日益增多。计算机为了获取这个视频 ,首先要连到局域网,也叫 lan ,你家 wifi 路由器连着的所有设备,组成了局域网。局域网再连到广域网,广域网也叫 wan (wide area network),wan 的路由器一般属于你的\u0026quot;互联网服务提供商\u0026quot;,简称 isp(internet service provider),比如 comcast,at\u0026amp;t 和 verizon 这样的公司。\n广域网里,先连到一个区域性路由器,这路由器可能覆盖一个街区。然后连到一个更大的 wan,可能覆盖整个城市。可能再跳几次,但最终会到达互联网主干。互联网主干由一群超大型、带宽超高路由器组成,为了从 youtube 获得这个视频,数据包(packet)要先到互联网主干,沿着主干到达有对应视频文件的 youtube 服务器,数据包从你的计算机跳到 youtube 服务器,可能要跳个 10 次,先跳 4 次到互联网主干,2 次穿过主干,主干出来可能再跳 4 次,然后到 youtube 服务器。如果你在用 windows, mac os 或 linux 系统,可以用 traceroute 来看跳了几次,更多详情看视频描述(youtube 原视频下)。\n我们在\u0026quot;印第安纳波利斯\u0026quot;的 chad\u0026amp;stacy emigholz 工作室,访问加州的 dftba 服务器,经历了 11 次中转。从 192.168.0.1 出发,这是我的电脑在 局域网(lan)里的 ip 地址,然后到工作室的 wifi 路由器,然后穿过一个个地区路由器,到达主干。然后从主干出来,又跳了几次,到达\u0026quot;dftba.com”的服务器,ip 地址是 104.24.109.186 。\n但数据包到底是怎么过去的 ?如果传输时数据包被弄丢了,会发生什么?如果在浏览器里输 “dftba.com”,浏览器怎么知道服务器的地址多少?\n我们今天会讨论这些话题。\n上集说过,互联网是一个巨型分布式网络 ,会把数据拆成一个个数据包来传输。如果要发的数据很大,比如邮件附件, 数据会被拆成多个小数据包。举例,你现在看的这个视频 ,就是一个个到达你电脑的数据包,而不是一整个大文件发过来。数据包(packet)想在互联网上传输 ,要符合\u0026quot;互联网协议\u0026quot;的标准,简称 ip 。就像邮寄手写信一样,邮寄是有标准的每封信需要一个地址,而且地址必须是独特的,并且大小和重量是有限制的,违反这些规定,信件就无法送达。\nip 数据包也是如此,因为 ip 是一个非常底层的协议,数据包的头部(或者说前面)只有目标地址,头部存 “关于数据的数据” 也叫 元数据 (metadata),这意味着当数据包到达对方电脑 ,对方不知道把包交给哪个程序,是交给 skype 还是使命召唤?因此需要在 ip 之上,开发更高级的协议。\n这些协议里 最简单最常见的叫\u0026quot;用户数据报协议\u0026quot;,简称 udp 。udp 也有头部,这个头部位于数据前面,头部里包含有用的信息。信息之一是端口号,每个想访问网络的程序 ,都要向操作系统申请一个端口号,比如 skype 会申请端口 3478 。当一个数据包到达时 ,接收方的操作系统会读 udp 头部,读里面的端口号,如果看到端口号是 3478,就把数据包交给 skype。\n总结:ip 负责把数据包送到正确的计算机, udp 负责把数据包送到正确的程序。\nudp 头部里还有\u0026quot;校验和\u0026quot;,用于检查数据是否正确,正如\u0026quot;校验和\u0026quot;这个名字所暗示的, 检查方式是把数据求和来对比。\n以下是个简单例子。假设 udp 数据包里 原始数据是 89 111 33 32 58 41 ,在发送数据包前 ,电脑会把所有数据加在一起,算出\u0026quot;校验和\u0026quot; - 89+111+33+… 以此类推,得到 364,这就是\u0026quot;校验和\u0026quot;。 udp 中,“校验和” 以 16 位形式存储 (就是 16 个 0 或 1),如果算出来的和,超过了 16 位能表示的最大值, 高位数会被扔掉,保留低位。当接收方电脑收到这个数据包,它会重复这个步骤 把所有数据加在一起,89+111+33… 以此类推,如果结果和头部中的校验和一致 ,代表一切正常。如果不一致,数据肯定坏掉了。也许传输时碰到了功率波动,或电缆出故障了。\n不幸的是,udp 不提供数据修复或数据重发的机制,接收方知道数据损坏后,一般只是扔掉。而且,udp 无法得知数据包是否到达。发送方发了之后,无法知道数据包是否到达目的地,这些特性听起来很糟糕,但是有些程序不在意这些问题,因为 udp 又简单又快。\n拿 skype 举例 ,它用 udp 来做视频通话,能处理坏数据或缺失数据,所以网速慢的时候 skype 卡卡的 因为只有一部分数据包到了你的电脑。但对于其他一些数据,这个方法不适用。 比如发邮件,邮件不能只有开头和结尾 ,没有中间,邮件要完整到达收件方!\n如果\u0026quot;所有数据必须到达\u0026quot; ,就用\u0026quot;传输控制协议\u0026quot;,简称 tcp(transmission control protocol)。tcp 和 udp 一样,头部也在存数据前面,因此,人们叫这个组合 tcp/ip 。就像 udp ,tcp 头部也有\u0026quot;端口号\u0026quot;和\u0026quot;校验和\u0026quot;,但 tcp 有更高级的功能,我们这里只介绍重要的几个。\n1、 tcp 数据包有序号\n15 号之后是 16 号,16 号之后是 17 号,以此类推 发上百万个数据包也是有可能的。序号使接收方可以把数据包排成正确顺序,即使到达时间不同。哪怕到达顺序是乱的,tcp 协议也能把顺序排对。\n2、 tcp 要求接收方的电脑收到数据包 并且\u0026quot;校验和\u0026quot;检查无误后(数据没有损坏)给发送方发一个确认码,代表收到了\n“确认码” 简称 ack . 得知上一个数据包成功抵达后,发送方会发下一个数据包。假设这次发出去之后,没收到确认码 ,那么肯定哪里错了。如果过了一定时间还没收到确认码, 发送方会再发一次。注意 ,数据包可能的确到了,只是确认码延误了很久,或传输中丢失了,但这不碍事 ,因为收件方有序列号,如果收到重复的数据包就删掉。\n还有,tcp 不是只能一个包一个包发,可以同时发多个数据包,收多个确认码 ,这大大增加了效率,不用浪费时间等确认码。有趣的是,确认码的成功率和来回时间 可以推测网络的拥堵程度,tcp 用这个信息,调整同时发包数量,解决拥堵问题。\n简单说,tcp 可以处理乱序和丢失数据包,丢了就重发,还可以根据拥挤情况自动调整传输率。相当厉害!\n你可能会奇怪,既然 tcp 那么厉害,还有人用 udp 吗?tcp 最大的缺点是 ,那些\u0026quot;确认码\u0026quot;数据包把数量翻了一倍,但并没有传输更多信息,有时候这种代价是不值得的 ,特别是对时间要求很高的程序,比如在线射击游戏。如果你玩游戏很卡,你也会觉得这样不值!\n当计算机访问一个网站时 需要两个东西:1.ip 地址, 2. 端口号 。\n例如 172.217.7.238 的 80 端口 ,这是谷歌的 ip 地址和端口号。事实上,你可以输到浏览器里,然后你会进入谷歌首页。有了这两个东西就能访问正确的网站, 但记一长串数字很讨厌,google.com 比一长串数字好记,所以互联网有个特殊服务 ,负责把域名和 ip 地址一一对应,就像专为互联网的电话簿 它叫 “域名系统” ,简称 dns 。\n它的运作原理你可能猜到了,在浏览器里输 youtube.com ,浏览器会去问 dns 服务器,它的 ip 地址是多少。一般 dns 服务器 是互联网供应商提供的,dns 会查表,如果域名存在,就返回对应 ip 地址。如果你乱敲键盘加个。com, 然后按回车,你很可能会看到 dns 错误,因为那个网站不存在,所以 dns 无法返回给你一个地址。如果你输的是有效地址,比如 youtube.com ,dns 按理会返回一个地址,然后浏览器会给这个 ip 地址 发 tcp 请求。如今有三千万个注册域名,所以为了更好管理,dns 不是存成一个超长超长的列表,而是存成树状结构。顶级域名(简称 tld)在最顶部,比如 .com 和 .gov ,下一层是二级域名,比如 .com 下面有 google.com 和 dftba.com ,再下一层叫子域名,比如 images.google.com, store.dftba.com ,这个树超!级!大!\n我前面说的\u0026quot;三千万个域名\u0026quot;只是二级域名 ,不是所有子域名,因此,这些数据散布在很多 dns 服务器上,不同服务器负责树的不同部分。\n好了 我知道你肯定在等这个梗:我们到了一层新抽象!\n过去两集里 我们讲了线路里的电信号,以及无线网络里的无线信号,这些叫\u0026quot;物理层\u0026quot;,而\u0026quot;数据链路层\u0026quot; 负责操控 “物理层”,数据链路层有:媒体访问控制地址(mac),碰撞检测,指数退避,以及其他一些底层协议。再上一层是\u0026quot;网络层\u0026quot;,负责各种报文交换和路由。而今天,我们讲了\u0026quot;传输层\u0026quot;里一大部分, 比如 udp 和 tcp 这些协议,负责在计算机之间进行点到点的传输,而且还会检测和修复错误。我们还讲了一点点\u0026quot;会话层\u0026quot;,“会话层” 会使用 tcp 和 udp 来创建连接,传递信息,然后关掉连接,这一整套叫\u0026quot;会话\u0026quot;。查询 dns 或看网页时,就会发生这一套流程。这是 开放式系统互联通信参考模型 (osi,open system interconnection) 的底下 5 层,这个概念性框架 把网络通信划分成多层,每一层处理各自的问题。如果不分层 直接从上到下捏在一起实现网络通信,是完全不可能的!\n抽象使得科学家和工程师能分工同时改进多个层 不被整体复杂度难倒。而且惊人的是!我们还没讲完呢!\nosi 模型还有两层,“表示层\u0026quot;和\u0026quot;应用程序层”,其中有浏览器,skype,html 解码,在线看电影等。\n我们下周说,到时见。\n30. 万维网 i.e. the world wide web\n(。・∀・)ノ゙嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n前两集我们深入讨论了电线 信号 交换机 数据包 路由器以及协议,它们共同组成了互联网。今天我们向上再抽象一层,来讨论万维网。\n万维网 (world wide web) 和互联网 (internet) 不是一回事,尽管人们经常混用这两个词。万维网在互联网之上运行,互联网之上还有 skype, minecraft 和 instagram 。互联网是传递数据的管道,各种程序都会用,其中传输最多数据的程序是万维网,分布在全球数百万个服务器上,可以用\u0026quot;浏览器\u0026quot;来访问万维网,这集我们会深入讲解万维网。\n万维网的最基本单位,是单个页面。页面有内容,也有去往其他页面的链接 ,这些链接叫\u0026quot;超链接\u0026quot;。你们都见过:可以点击的文字或图片,把你送往另一个页面,这些超链接形成巨大的互联网络,这就是\u0026quot;万维网\u0026quot;名字的由来。现在说起来觉得很简单,但在超链接做出来之前,计算机上每次想看另一个信息时,你需要在文件系统中找到它 ,或是把地址输入搜索框,有了超链接,你可以在相关主题间轻松切换。超链接的价值早在 1945 年 就被 vannevar bush 意识到了,在第 24 集中我们说过,他发过一篇文章 ,描述一个假想的机器 memex ,bush 的形容是\u0026quot;关联式索引 - 选一个物品会引起另一个物品被立即选中\u0026quot;。他解释道:“将两样东西联系在一起的过程十分重要,在任何时候,当其中一件东西进入视线,只需点一下按钮,立马就能回忆起另一件” 。1945 年的时候计算机连显示屏都没有,所以这个想法非常超前!因为文字超链接是如此强大,它得到了一个同样厉害的名字:“超文本”!如今超文本最常指向的,是另一个网页,然后网页由浏览器渲染,我们待会会讲。\n为了使网页能相互连接,每个网页需要一个唯一的地址,这个地址叫 “统一资源定位器”,简称 url(uniform resource locator)。一个网页 url 的例子是 “thecrashcourse.com/courses” ,就像上集讨论的,当你访问一个网站时,计算机首先会做\u0026quot;dns 查找\u0026quot;,“dns 查找\u0026quot;的输入是一个域名 比如 thecrashcourse.com ,会输出对应的 ip 地址,现在有了 ip 地址 ,你的浏览器会打开一个 tcp 连接到这个 ip 地址,这个地址运行着\u0026quot;网页服务器”,网页服务器的标准端口是 80 端口,这时,你的计算机连到了 thecrashcourse.com 的服务器。下一步是向服务器请求\u0026quot;courses\u0026quot;这个页面,这里会用\u0026quot;超文本传输协议\u0026quot;(http)。\nhttp 的第一个标准,http 0.9,创建于 1991 年,只有一个指令,“get” 指令。幸运的是,对当时来说也够用,因为我们想要的是\u0026quot;courses\u0026quot;页面,我们向服务器发送指令:“get /courses”,该指令以\u0026quot;ascii 编码\u0026quot;发送到服务器,服务器会返回该地址对应的网页 ,然后浏览器会渲染到屏幕上。如果用户点了另一个链接,计算机会重新发一个 get 请求。你浏览网站时,这个步骤会不断重复。\n在之后的版本,http 添加了状态码,状态码放在请求前面。举例,状态码 200 代表 “网页找到了,给你”。状态码 400~499 代表客户端出错,比如网页不存在,就是可怕的 404 错误。\n“超文本\u0026quot;的存储和发送都是以普通文本形式。举个例子,编码可能是 ascii 或 utf-16 , 我们在第 4 集和第 20 集讨论过,因为如果只有纯文本 ,无法表明什么是链接,什么不是链接,所以有必要开发一种标记方法,因此开发了 超文本标记语言(html)。\nhtml 第一版的版本号是 0.8,创建于 1990 年,有 18 种 html 指令,仅此而已!\n我们来做一个网页吧!\n\u0026gt; 首先,给网页一个大标题\r\u0026gt; 我们输 h1 代表一级标题,然后用\u0026lt;\u0026gt;括起来\r\u0026gt; 这就是一个 html 标签\r\u0026gt; \u0026gt; 然后输入想要的标题\r\u0026gt; 我们不想一整页都是标题 所以加 \u0026lt;/h1\u0026gt; 作为结束标签\r\u0026gt; \u0026gt; 现在来加点内容\r\u0026gt; 读者可能不知道\u0026#34;克林贡\u0026#34;是什么,所以我们给这个词\r\u0026gt; 加一个超链接到\u0026#34;克林贡语言研究院\u0026#34;\r\u0026gt; 我们用 \u0026lt;a\u0026gt; 标签来做,它有一个 href 属性\r\u0026gt; 说明链接指向哪里,当点击链接时就会进入那个网页\r\u0026gt; 最后用 \u0026lt;/a\u0026gt; 关闭标签\r\u0026gt; \u0026gt; 接下来用 \u0026lt;h2\u0026gt; 标签做二级标题\r\u0026gt; \u0026gt; html 也有做列表的标签\r\u0026gt; 我们先写\u0026lt;ol\u0026gt; 代表 有序列表(ordered list)\r\u0026gt; 然后想加几个列表项目 , 就加几个 用 \u0026lt;li\u0026gt; 包起来就行\r\u0026gt; 读者可能不知道 bat\u0026#39;leth 是什么,那么也加上超链接\r\u0026gt; 最后,为了保持良好格式,用\u0026lt;/ol\u0026gt;代表列表结束\r\u0026gt; \u0026gt; 这就完成了 - 一个很简单的网页! 如果把这些文字存入记事本或文本编辑器,然后文件取名 “test.html”,就可以拖入浏览器打开。\n当然,如今的网页更复杂一些,最新版的 html,html5,有 100 多种标签,图片标签,表格标签,表单标签,按钮标签,等等。还有其他相关技术就不说了比如 层叠样式表 (css) 和 javascript,这俩可以加进网页,做一些更厉害的事。\n让我们回到浏览器,网页浏览器可以和网页服务器沟通,浏览器不仅获取网页和媒体,获取后还负责显示。\n第一个浏览器和服务器,是 tim berners-lee 在 1990 年写的,一共花了 2 个月。那时候他在瑞士的\u0026quot;欧洲核子研究所\u0026quot;工作,为了做出来,他同时建立了几个最基本的网络标准 - url, html 和 http。两个月能做这些很不错啊!不过公平点说,他研究超文本系统已经有十几年了,和同事在 cern 内部使用一阵子后,在 1991 年发布了出去,万维网就此诞生。\n重要的是,万维网有开放标准,大家都可以开发新服务器和新浏览器,因此\u0026quot;伊利诺伊大学香槟分校\u0026quot;的一个小组在 1993 年做了 mosaic 浏览器,第一个可以在文字旁边显示图片的浏览器,之前浏览器要单开一个新窗口显示图片,还引进了书签等新功能,界面友好,使它很受欢迎。尽管看上去硬邦邦的,但和如今的浏览器长的差不多。\n1990 年代末有许多浏览器面世,netscape navigator, internet explorer opera, omniweb, mozilla ,也有很多服务器面世,比如 apache 和 微软互联网信息服务 (iis)。每天都有新网站冒出来,如今的网络巨头,比如亚马逊和 ebay,创始于 1990 年代中期,那是个黄金时代!\n随着万维网日益繁荣,人们越来越需要搜索。如果你知道网站地址, 比如 ebay.com,直接输入浏览器就行,如果不知道地址呢?比如想找可爱猫咪的图片,现在就要!去哪里找呢?\n起初人们会维护一个目录,链接到其他网站,其中最有名的叫\u0026quot;jerry 和 david 的万维网指南”,1994 年改名为 yahoo 。随着网络越来越大,人工编辑的目录变得不便利,所以开发了搜索引擎。\n让我们进入思想泡泡!\n长的最像现代搜索引擎的最早搜素引擎,叫 jumpstation ,由 jonathon fletcher 于 1993 年在斯特林大学创建,它有 3 个部分:\n第一个是爬虫,一个跟着链接到处跑的软件,每当看到新链接,就加进自己的列表里;第二个部分是不断扩张的索引,记录访问过的网页上,出现过哪些词;最后一个部分,是查询索引的搜索算法,举个例子,如果我在 jumpstation 输入\u0026quot;猫\u0026quot;,每个有\u0026quot;猫\u0026quot;这个词的网页都会出现。\n早期搜索引擎的排名方式 非常简单,取决于 搜索词在页面上的出现次数。刚开始还行,直到有人开始钻空子,比如在网页上写几百个\u0026quot;猫\u0026quot;,把人们吸引过来。谷歌成名的一个很大原因是, 创造了一个聪明的算法来规避这个问题。与其信任网页上的内容 ,搜索引擎会看其他网站 有没有链接到这个网站。如果只是写满\u0026quot;猫\u0026quot;的垃圾网站,没有网站会指向它,如果有关于猫的有用内容,有网站会指向它,所以这些\u0026quot;反向链接\u0026quot;的数量,特别是有信誉的网站,代表了网站质量。\ngoogle 一开始时是 1996 年斯坦福大学 一个叫 backrub 的研究项目,两年后分离出来,演变成如今的谷歌。\n谢谢思想泡泡!\n最后 我想讲一个词,你最近可能经常听到 - 网络中立性 。现在你对数据包,路由和万维网,有了个大体概念,足够你理解这个争论的核心点,至少从技术角度。简单说\u0026quot;网络中立性\u0026quot;是应该平等对待所有数据包,不论这个数据包是我的邮件,或者是你在看视频,速度和优先级应该是一样的,但很多公司会乐意让它们的数据优先到达。拿 comcast 举例,它们不但是大型互联网服务提供商而且拥有多家电视频道,比如 nbc 和 the weather channel,可以在线看。我不是特意找 comcast 麻烦 ,但要是没有网络中立性,comcast 可以让自己的内容优先到达 ,节流其他线上视频。节流 (throttled) 意思是故意给更少带宽和更低优先级。再次重申,这只是举例,不是说 comcast 很坏。支持网络中立性的人说 没有中立性后,服务商可以推出提速的\u0026quot;高级套餐\u0026quot;,给剥削性商业模式埋下种子。互联网服务供应商成为信息的\u0026quot;守门人\u0026quot;,它们有着强烈的动机去碾压对手,另外,netflix 和 google 这样的大公司可以花钱买特权,而小公司,比如刚成立的创业公司,会处于劣势,阻止了创新。另一方面,从技术原因看,也许你会希望不同数据传输速度不同,你希望 skype 的优先级更高,邮件晚几秒没关系。而反对\u0026quot;网络中立性\u0026quot;的人认为,市场竞争会阻碍不良行为,如果供应商把客户喜欢的网站降速 ,客户会离开供应商。\n这场争辩还会持续很久,就像我们在 crash course 其他系列中说过,你应该自己主动了解更多信息,因为\u0026quot;网络中立性\u0026quot;的影响十分复杂而且广泛。\n我们下周再见\n31. 计算机安全 i.e. cybersecurity\n(。・∀・)ノ゙嗨,我是 carrie anne ,欢迎收看计算机科学速成课!\n过去 3 集 我们讲了计算机如何互连,让我们能瞬时跨全球沟通,但不是每个使用网络的人都会规规矩矩,不损害他人利益。就像现实世界中我们用锁和栅栏保证物理安全,有警察减少犯罪,我们需要网络安全减少虚拟世界中的犯罪 🚨。\n计算机没有道德观念。只要给计算机写清具体问题 , 它们很乐意地闪电般算出答案。破坏医院计算机系统的代码 和 保持病人心跳的代码 ,对计算机来说没有区别,就像\u0026quot;原力\u0026quot;一样 ,计算机可以被拉到\u0026quot;光明面\u0026quot;或\u0026quot;黑暗面\u0026quot;。网络安全就像 绝地武士团 ,给网络世界带来和平与正义。\n计算机安全的范围,和计算能力的发展速度一样快。我们可以把计算机安全,看成是保护系统和数据的:保密性,完整性和可用性 。我们逐个细说:\n“保密性\u0026quot;是只有有权限的人 ,才能读取计算机系统和数据。黑客泄露别人的信用卡信息,就是攻击保密性。\n“完整性\u0026quot;是只有有权限的人 ,才能使用和修改系统和数据。黑客知道你的邮箱密码,假冒你发邮件,就是攻击\u0026quot;完整性”。\n“可用性\u0026quot;是有权限的人 ,应该随时可以访问系统和数据。拒绝服务攻击 (ddos) 就是黑客发大量的假请求到服务器,让网站很慢或者挂掉,这就是攻击\u0026quot;可用性”。\n为了实现这三个目标,安全专家会从 抽象层面想象\u0026quot;敌人\u0026quot;可能是谁,这叫\u0026quot;威胁模型分析”,模型会对攻击者有个大致描述:能力如何,目标可能是什么,可能用什么手段 。攻击手段又叫\u0026quot;攻击矢量\u0026quot; ,“威胁模型分析\u0026quot;让你能为特定情境做准备,不被可能的攻击手段数量所淹没 ,因为手段实在有太多种了。假设你想确保笔记本计算机的\u0026quot;物理安全” ,你的威胁模型是\u0026quot;好管闲事的室友\u0026quot;。为了保证保密性,完整性和可用性, 你可以藏在脏兮兮的洗衣篮里。但如果威胁模型是调皮的兄弟姐妹,知道你喜欢藏哪里,那么你需要更多保护:比如锁在保险箱里。换句话说,要怎么保护,具体看对抗谁。当然,威胁模型通常比\u0026quot;好管闲事的室友\u0026quot;更正式一些,通常威胁模型分析里 会以能力水平区分,比如\u0026quot;某人可以物理接触到笔记本计算机,而且时间无限\u0026quot;。在给定的威胁模型下,安全架构师要提供解决方案,保持系统安全。只要某些假设不被推翻,比如没人会告诉攻击者密码,保护计算机系统,网络和数据的方法有很多。\n很多安全问题可以总结成 2 个问题: 你是谁?你能访问什么?\n权限应该给合适的人,拒绝错误的人,比如银行员工可以打开取款机来补充现金。但我不应该有权限打开,因为我会把钱拿走 全拿走!陶瓷猫收藏品可不会从天上掉下来哟!所以,为了区分谁是谁,我们用 “身份认证”(authentication) - 让计算机得知使用者是谁。\n身份认证有三种,各有利弊:你知道什么、你有什么、你是什么。\n“你知道什么” 是基于某个秘密,只有用户和计算机知道,比如 用户名和密码,这是如今使用最广泛的,因为最容易实现,但如果黑客通过猜测或其他方式,知道你的密码,就惨了。有些密码很容易猜中,比如 12356 或 qwerty 。但有些密码对计算机很容易,比如 pin 码:2580 ,看起来很难猜中 - 起码对人类来说是这样,但 4 位数字,只有一万种可能。一台计算机可以尝试 0000,然后 0001,然后 0002,然后到 9999,不到一秒内试完,这叫\u0026quot;暴力攻击\u0026quot;,因为只是试遍一切可能,这种算法没什么聪明的地方。如果你错误尝试 3 次,有些系统会阻止你继续尝试,或让你等一会儿,这个策略普遍而且合理。对于一般的攻击者确实很难,但假设黑客控制了数以万计的计算机,形成一个僵尸网络,用这么多计算机尝试密码 2580 ,同时尝试很多银行账户,即使每个账户只试一次,也很可能,碰到某个账户刚好用这个 pin,事实上,看视频的某人可能刚好用这个 pin 。增加密码长度有帮助,但即使 8 位数字的 pin 码也很容易破解,这就是为什么现在很多网站 要求大写+小写字母,还有特殊符号等,大大增加可能的密码。8 位数字的 pin 只有一亿种组合,对计算机轻而易举,但包含各种字符的 8 位长度密码,有超过 600 万亿种组合。当然,这些密码会难以记住,所以更好的方法是 选一些更好记的东西,比如三个单词连在一起:“格林兄弟好厉害\u0026quot;或\u0026quot;披萨尝起来好好吃”。英文大约有 10 万个单词,所以三个单词连一起大概有 1 亿亿种可能,想猜中的话,祝你好运!另外使用不在字典内的单词被猜中的可能性更低,但我们没时间细说这个。computerphile 频道有个视频讲怎么选择好密码 - 链接请看 youtube 描述。\n“你有什么\u0026quot;这种验证方式,是基于用户有特定物体,比如钥匙和锁。如果你有钥匙,就能开门,这避免了被人\u0026quot;猜中\u0026quot;的问题,而且通常需要人在现场,所以远程攻击就更难了。另一个国家的人,得先来佛罗里达州,才能到你家前门。但如果攻击者离你比较近,那么也不安全,钥匙可以被复制,手机可能被偷,锁可以撬开。\n最后,“你是什么\u0026quot;这种验证,是基于你把特征展示给计算机进行验证,生物识别验证器,比如指纹识别器和虹膜扫描仪就是典型例子,这些非常安全,但最好的识别技术仍然很贵,而且,来自传感器的数据每次会不同。\n“你知道什么\u0026quot;和\u0026quot;你有什么”。这两种验证是\u0026quot;确定性\u0026quot;的 - 要么正确,要么错误。如果你知道密码,或有钥匙,那么 100%能获得访问权限,如果没有,就绝对进不去,但\u0026quot;生物识别\u0026quot;是概率性的,系统有可能认不出你,可能你戴了帽子,或者光线不好。更糟的是,系统可能把别人错认成你,比如你的邪恶双胞胎。当然,在现实世界中几率很低,但不是零。\n生物认证的另一个问题是无法重设。你只有这么多手指,如果攻击者拿到你的指纹数据怎么办,你一辈子都麻烦了。最近还有研究人员表示,拍个照都有可能伪造虹膜,所以也不靠谱。\n所有认证方法都有优缺点,它们都可以被攻破,所以,对于重要账户,安全专家建议用两种或两种以上的认证方式,这叫\u0026quot;双因素\u0026quot;或\u0026quot;多因素\u0026quot;认证。攻击者可能猜出你密码,或偷走你的手机:但两个都做到,会比较难。\n“身份验证\u0026quot;后,就来到了\u0026quot;访问控制”。一旦系统知道你是谁,它需要知道你能访问什么,因此应该有个规范,说明谁能访问什么,修改什么,使用什么。这可以通过\u0026quot;权限\u0026quot;或\u0026quot;访问控制列表”(acl)来实现,其中描述了用户对每个文件,文件夹和程序的访问权限。\n\u0026#34;读\u0026#34;权限允许用户查看文件内容,\r\u0026#34;写\u0026#34;权限允许用户修改内容,\r\u0026#34;执行\u0026#34;权限允许用户运行文件,比如程序 有些组织需要不同层级的权限,比如间谍机构,“访问控制列表\u0026quot;的正确配置非常重要,以确保保密性,完整性和可用性。假设我们有三个访问级别:公开,机密,绝密。\n第一个普遍的好做法是,用户不能\u0026quot;读上”, 不能读等级更高的信息,如果用户能读\u0026quot;机密\u0026quot;文件那么不应该有权限读\u0026quot;绝密\u0026quot;文件,但能访问\u0026quot;机密\u0026quot;和\u0026quot;公开\u0026quot;文件\n第二个法则是用户不能\u0026quot;写下”,如果用户等级是\u0026quot;绝密\u0026quot;,那么能写入或修改\u0026quot;绝密\u0026quot;文件,但不能修改\u0026quot;机密\u0026quot;或\u0026quot;公共\u0026quot;文件。听起来好像很奇怪 ,有最高等级也不能改等级更低的文件,但这样确保了\u0026quot;绝密\u0026quot; 不会意外泄露到\u0026quot;机密\u0026quot;文件或\u0026quot;公共\u0026quot;文件里。\n这个\u0026quot;不能向上读,不能向下写\u0026quot;的方法叫 bell-lapadula 模型,它是为美国国防部\u0026quot;多层安全政策\u0026quot;制定的,还有许多其他的访问控制模型 - 比如\u0026quot;中国墙\u0026quot;模型和\u0026quot;比伯\u0026quot;模型。哪个模型最好,取决于具体情况。\n身份验证\u0026quot;和\u0026quot;访问控制\u0026quot;帮助计算机知道\u0026quot;你是谁\u0026quot;,以及\u0026quot;你可以访问什么\u0026quot;,但做这些事情的软硬件必须是可信的,这个依赖很重要。如果攻击者给计算机装了恶意软件 - 控制了计算机的操作系统。我们怎么确定安全程序没有给攻击者留后门?短回答是 - 无法确定!我们仍然无法保证程序或计算机系统的安全,因为安全软件在理论上可能是\u0026quot;安全的\u0026quot;,实现时可能会不小心留下漏洞,但我们有办法减少漏洞出现的可能性,比如一找到就马上修复,以及当程序被攻破时尽可能减少损害。大部分漏洞都是具体实现的时候出错了,为了减少执行错误,减少执行。\n系统级安全的圣杯之一是\u0026quot;安全内核\u0026quot;,或\u0026quot;可信计算基础\u0026quot;:一组尽可能少的操作系统软件。安全性都是接近可验证的,构建安全内核的挑战在于 决定内核应该有什么。记住,代码越少越好!在最小化代码数量之后,要是能\u0026quot;保证\u0026quot;代码是安全的,会非常棒。\n正式验证代码的安全性 是一个活跃的研究领域,我们现在最好的手段,叫 “独立安全检查和质量验证” 。让一群安全行业内的软件开发者来审计代码,这就是为什么安全型代码几乎都是开源的,写原始代码的人通常很难找到错误,但外部开发人员有新鲜的眼光和不同领域的专业知识,可以发现问题。 另外还有一些安全大会,安全专家可以相互认识,分享想法。 一年一次在拉斯维加斯举办的 def con 是全球最大的安全大会。\n最后,即便尽可能减少代码 ,并进行了安全审计。聪明的攻击者还是会找到方法入侵,因为如此,优秀的开发人员应该计划当程序被攻破后,如何限制损害,控制损害的最大程度,并且不让它危害到计算机上其他东西,这叫 “隔离” 。要实现隔离,我们可以\u0026quot;沙盒\u0026quot;程序,这好比把生气的小孩放在沙箱里,他们只能摧毁自己的沙堡,不会影响到其他孩子。操作系统会把程序放到沙盒里,方法是给每个程序独有的内存块,其他程序不能动。一台计算机可以运行多个虚拟机,虚拟机模拟计算机,每个虚拟机都在自己的沙箱里。如果一个程序出错,最糟糕的情况是它自己崩溃或者搞坏它处于的虚拟机。计算机上其他虚拟机是隔离的,不受影响。\n好,一些重要安全概念的概览 我们到此就介绍完了。我都还没讲网络安全,比如防火墙。下集我们会讨论 黑客侵入系统的一些方法,然后我们学加密。在此之前,别忘了加强你的密码,打开两步验证,永远不要点可疑邮件。\n我们下周见。\n32. 黑客 \u0026amp; 攻击 i.e. hackers \u0026amp; cyber attacks\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n上集我们讲了计算机安全的基础知识,包括各种原则和技术,但尽管尽了最大努力,新闻上还是各种 个人,公司,政府被黑客攻击的故事。那些黑客凭技术知识闯入计算机系统,不是所有黑客都是坏人,有些黑客会寻找并修复软件漏洞 ,让系统更安全,他们经常被公司和政府雇来做安全评估,这些黑客叫\u0026quot;白帽子\u0026quot;,他们是好人。另一方面,也有\u0026quot;黑帽\u0026quot;黑客,他们窃取,利用和销售计算机漏洞和数据。黑客的动机有很多种,有些是好玩和好奇,而网络罪犯一般是为了钱。还有的叫\u0026quot;黑客行动主义者\u0026quot;,通过黑客手段影响社会或达到政治目的,这只是冰山一角。\n一般对黑客的刻板印象是某个不受欢迎的小孩在黑暗的房间里,到处都是吃完的比萨盒,这个印象是错的,形容约翰·格林的宿舍还更贴切些。\n今天,我们不会教你如何成为黑客,而是讨论一些入侵原理,给你一个大概概念。\n黑客入侵最常见的方式,不是通过技术,而是欺骗别人,这叫\u0026quot;社会工程学\u0026quot;,欺骗别人让人泄密信息,或让别人配置电脑系统,变得易于攻击。\n最常见的攻击是网络钓鱼,你可能见过,银行发邮件叫你点邮件里的链接,登陆账号,然后你会进入一个像官网的网站,但实际上是个假网站,当你输入用户名和密码时,信息会发给黑客,然后黑客就可以假扮你登陆网站,坏消息!即使成功率只有 1/1000,发一百万封钓鱼邮件,也有一千个帐户中招。\n另一种方法叫 假托 (pretexting),攻击者给某个公司打电话,假装是 it 部门的人,攻击者的第一通电话一般会叫人转接,这样另一个人接的时候,电话看起来像内部的,然后让别人把电脑配置得容易入侵,或让他们泄露机密信息,比如密码或网络配置。\n\u0026gt; 不好意思,等一下。\r\u0026gt; 嘿,我是 it 部门的苏珊\r\u0026gt; 我们遇到一些网络问题,你能帮我检查一个配置吗?\r\u0026gt; 然后就开始了…… 只要预先做一点研究,攻击者可以装得很像真的,比如关键员工的名字。也许要 10 通电话才能找到一个受害者,但只要一个人上当就够了。\n邮件里带\u0026quot;木马\u0026quot;也是常见手段,木马会伪装成无害的东西,比如照片或发票,但实际上是恶意软件。恶意软件有很多种,有的会偷数据,比如银行凭证,有的会加密文件,交赎金才解密,也就是\u0026quot;勒索软件\u0026quot;。如果攻击者无法用木马或电话欺骗,攻击者只能被迫用其他手段,方法之一是暴力尝试,我们上集讨论过,尝试所有可能的密码,直到进入系统。大多数现代系统会加长等待时间,来抵御这种攻击,每次失败就加长等待时间,甚至失败超过一定次数后,完全锁住。\n最近出现一种攻破方法叫 “nand 镜像”,如果能物理接触到电脑,可以往内存上接几根线,复制整个内存,复制之后,暴力尝试密码,直到设备让你等待,这时只要把复制的内容覆盖掉内存,本质上重置了内存,就不用等待,可以继续尝试密码了,这项方法在 iphone 5c 上管用,更新的设备有机制阻止这种攻击。\n如果你无法物理接触到设备,就必须远程攻击,比如通过互联网。 远程攻击一般需要攻击者利用系统漏洞,来获得某些能力或访问权限,这叫\u0026quot;漏洞利用\u0026quot;(exploit)。一种常见的漏洞利用叫\u0026quot;缓冲区溢出\u0026quot;,“缓冲区\u0026quot;是一种概称,指预留的一块内存空间。\n我们在第 23 集,讨论过存像素数据的视频缓冲区。举个简单例子,假设我们在系统登陆界面,要输入用户名和密码。在幕后,系统用缓冲区存输入的值,假设缓冲区大小是 10 ,两个文本缓冲区看起来会像这样:\n当然,操作系统记录的远不止用户名和密码,所以缓冲区前后 肯定有其他数据。当用户输入用户名和密码时,这些值会复制到缓冲区,然后验证是否正确。缓冲区溢出\u0026quot;正如名字所暗示的:它会溢出缓冲区。在这个例子中,超过十个字符的密码会覆盖掉相邻的数据,有时只会让程序或系统崩溃,因为重要值被垃圾数据覆盖了。系统崩溃是坏事,但也许恶作剧黑客就只是想系统崩溃,当个讨厌鬼。但攻击者可以更巧妙地利用这个漏洞 (bug),注入有意义的新值到程序的内存中,比如把\u0026quot;is_admin\u0026quot;的值改成 true。有了任意修改内存的能力,黑客可以绕过\u0026quot;登录\u0026quot;之类的东西,甚至使用那个程序劫持整个系统。有很多方法阻止缓冲区溢出,最简单的方法是,复制之前先检查长度,这叫 “边界检查”。 许多现代编程语言自带了边界检查,程序也会随机存放变量在内存中的位置, 比如我们之前假设的\u0026quot;is_admin”,这样黑客就不知道应该覆盖内存的哪里,导致更容易让程序崩溃,而不是获得访问权限。程序也可以在缓冲区后,留一些不用的空间,然后跟踪里面的值,看是否发生变化,如果发生了变化,说明有攻击者在乱来,这些不用的内存空间叫\u0026quot;金丝雀\u0026quot;,因为以前矿工会带金丝雀下矿,金丝雀会警告危险。\n另一种经典手段叫\u0026quot;代码注入\u0026quot;,最常用于攻击用数据库的网站,几乎所有大网站都用数据库。我们这个系列中不会讲解数据库,所以以下是个简单例子。\n我们会用\u0026quot;结构化查询语言\u0026quot;,也叫 sql,一种流行的数据库 api。假设网页上有登录提示,当用户点击\u0026quot;登录\u0026quot;时,值会发到服务器,服务器会运行代码,检查用户名是否存在,如果存在,看密码是否匹配。 为了做检查,服务器会执行一段叫 “sql 查询” 的代码,看起来像这样。\n首先,语句要指定从数据库里查什么数据。在这个例子中,我们想查的是密码 (password) (select password),还要指定从哪张表查数据 (from users)。在这个例子里,我们假设所有用户数据都存在 “users” 表里,最后,服务器不想每次取出一个巨大密码列表,包含所有用户密码,所以用 username = ‘用户名’代表只要这个用户,用户输的值会复制到\u0026quot;sql 查询\u0026quot;,所以实际发到 sql 数据库的命令,是这样的 - where username=‘philbin’ 。还要注意,sql 命令以分号结尾,那怎么破解这个? 做法是把\u0026quot;sql 命令\u0026quot;输入到用户名里! 比如我们可以发这个奇怪的用户名:\n当服务器把值复制到 sql 查询中,会变成这样:\n正如之前提的,分号用于分隔命令,所以第一条被执行的命令是:\n如果有个用户叫\u0026quot;whateer\u0026quot;,数据库将返回密码。当然,我们不知道密码是什么,所以会出错,服务器会拒绝我们。如果没有一个用户叫\u0026quot;whatever\u0026quot;,数据库会返回 空密码或直接错误,服务器也会拒绝我们。总之 ,我们不在乎,我们感兴趣的是下一个 sql 命令: “drop table users” - 我们注入的命令。这条命令的意思是删掉 users 这张表,全删干净!这会造成很多麻烦,不管是银行或什么其他地方,注意,我们甚至不需要侵入系统,我们没有猜到正确的用户名和密码,即使没有正式访问权限,还是可以利用 bug 来制造混乱,这是代码注入的一个简单例子。如今几乎所有服务器都会防御这种手段,如果指令更复杂一些,也许可以添加新记录到数据库 - 比如一个新管理员帐户 - 甚至可以让数据库泄露数据,使得黑客窃取信用卡号码,社会安全号码,以及各种其他信息,但我们不会教你具体怎么做 。\n就像缓冲区溢出攻击一样,应该总是假设外部数据是危险的,应该好好检查。\n很多用户名和密码表单,不让你输入特殊字符,比如分号或者括号,作为第一道防御。好的服务器也会清理输入,比如修改或删除特殊字符,然后才放到数据库查询语句里。管用的漏洞利用 (exploits) 一般会在网上贩卖或分享,如果漏洞很流行,或造成的危害很大,价格会越高,或者名气越大,有时甚至政府也会买漏洞利用,让他们侵入系统做间谍工作。当软件制造者不知道软件有新漏洞被发现了,那么这个漏洞叫 “零日漏洞”。黑帽黑客经常赶时间,抢在白帽程序员做出补丁之前,尽可能利用漏洞,所以保持系统更新非常重要,很多更新都是安全性补丁。如果有足够多的电脑有漏洞,让恶意程序可以在电脑间互相传播,那么叫\u0026quot;蠕虫\u0026quot;。如果黑客拿下大量电脑,这些电脑可以组成\u0026quot;僵尸网络\u0026quot;,可以用于很多目的,比如发大量垃圾邮件,用别人电脑的计算能力和电费挖 bitcoin,或发起\u0026quot;拒绝服务攻击\u0026quot;简称 ddos,攻击服务器。ddos 就是僵尸网络里的所有电脑发一大堆垃圾信息,堵塞服务器,要么迫使别人交钱消灾,或纯粹为了作恶。\n尽管白帽黑客非常努力工作,漏洞利用的文档都在网上,编写软件有很多\u0026quot;最佳实践\u0026quot;,网络攻击每天都在发生,每年损害全球经济差不多 5000 亿,并且随着我们越来越依赖计算机系统,这个数字只会增加。 这使得政府非常担心,因为基础设施越来越电脑化,比如电力厂,电网,交通灯,水处理厂,炼油厂,空管,还有很多其他关键系统。\n很多专家预测下一次大战会主要是网络战争。国家不是被物理攻击打败,而是因为网络战争导致经济和基础设施崩溃。也许不会发射一颗子弹,但是人员伤亡的可能性依然很高,甚至可能高于传统战争,所以大家都应该知道一些方法保证网络安全。\n全球社区因为互联网而互相连接,我们应该确保自己的电脑安全,抵御其他想做坏事的人,也许不要再忽略更新提示?\n我们下周见。\n33. 加密 i.e. cryptography\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n在过去两集,我们聊了很多计算机安全话题,但事实是 ,世上不存在 100%安全的系统,总会有漏洞存在,而且安全专家知道这一点,所以系统架构师会部署\u0026quot;多层防御\u0026quot;,用多层不同的安全机制来阻碍攻击者。有点像城堡的设计一样,首先要避开弓箭手,穿过护城河,翻过城墙,避开热油,打败守卫,才能达到王座。不过我们这里要说的是,计算机安全中最常见的防御形式 - 密码学!\n密码学 (cryptography) 一词 来自 crypto 和 graphy,大致翻译成\u0026quot;秘密写作\u0026quot;。为了加密信息,要用加密算法 (cipher) 把明文转为密文,除非你知道如何解密,不然密文看起来只是一堆乱码。把明文转成密文叫\u0026quot;加密\u0026quot;,把密文恢复回明文叫\u0026quot;解密\u0026quot;(decryption),加密算法早在计算机出现前就有了。\n朱利叶斯·凯撒 用如今我们叫\u0026quot;凯撒加密\u0026quot;的方法 来加密私人信件,他会把信件中的字母 向前移动三个位置,所以 a 会变成 d,brutus 变成 euxwxv。为了解密,接收者要知道 1. 用了什么算法 2. 要偏移的字母位数。\n有一大类算法叫\u0026quot;替换加密\u0026quot;,凯撒密码是其中一种,算法把每个字母替换成其他字母。但有个巨大的缺点是,字母的出现频率是一样的,举个例子,e 在英语中是最常见的字母,如果把 e 加密成 x ,那么密文中 x 的出现频率会很高,熟练的密码破译师可以从统计数据中发现规律,进而破译密码。 1587 年,正因为一个\u0026quot;替换加密\u0026quot;的密文被破译,导致杀伊丽莎白女王的阴谋暴露,使得玛丽女王被处决。\n另一类加密算法叫 “移位加密”。我们来看一个简单例子叫 “列移位加密”,我们把明文填入网格,网格大小我们这里选择 5x5,为了加密信息,我们换个顺序来读,比如从左边开始,从下往上,一次一列。加密后字母的排列不同了,解密的关键是,知道读取方向和网格大小是 5x5 。就像之前,如果接收者知道密文和加密方法 才能解密得到原始消息。\n到了 1900 年代,人们用密码学做了加密机器,其中最有名的是德国的英格玛(enigma)纳粹在战时用英格玛加密通讯信息。正如第 15 集中说过,enigma 是一台像打字机的机器,有键盘和灯板,两者都有完整的字母表,而且它有一系列\u0026quot;转子\u0026quot;(rotros) ,是加密的关键。首先,我们只看一个转子,它一面有 26 个接触点,代表 26 个字母,然后线会连到另一面,替换字母,如果输入’h’,‘k’会从另一边出来,如果输入’k’,‘f’会从另一边出来,以此类推,这个字母替换过程你应该听起来很熟悉:它是\u0026quot;替换加密\u0026quot;! 但英格玛 (enigma) 更复杂一些,因为它有 3 个或更多转子,一个转子的输出作为下一个转子的输入。 转子还有 26 个起始位置,还可以按不同顺序放入转子,提供更多字母替换映射。转子之后是一个叫\u0026quot;反射器\u0026quot;的特殊电路,它每个引脚会连到另一个引脚,并把信号发回给转子。最后,机器前方有一个插板,可以把输入键盘的字母预先进行替换,又加了一层复杂度。\n让我们用这里的简化版电路,加密一些字母。如果我们按下\u0026quot;h\u0026quot;键,电流会先通过插板,然后通过转子,到达反射器,然后回来转子,回来插板,并照亮键盘灯板的字母\u0026quot;l\u0026quot;,h 就加密成了 l 。注意,电路是双向的,所以如果我们按下 l,h 会亮起来。换句话说,加密和解密的步骤是一样的,你只需要确保 发送机和接收机的初始配置一样就行。如果你有仔细观察,会注意到字母加密后一定会变成另一个字母,之后这成为最大的弱点。最后,为了让英格玛不只是简单的\u0026quot;替换加密\u0026quot;,每输入一个字母,转子会转一格,有点像汽车里程表。如果你输入 a-a-a,可能会变成 b-d-k,映射会随着每次按键而改变。英格玛当然是一块难啃的骨头,但正如我们第 15 集中说的,艾伦·图灵和同事破解了英格玛加密,并把大部分破解流程做成了自动化。\n但随着计算机出现,加密从硬件转往软件,早期加密算法中,应用最广泛的是 ibm 和 nsa 于 1977 年开发的\u0026quot;数据加密标准\u0026quot;(data encryption standard)。des 最初用的是 56 bit 长度的二进制密钥,意味着有 2 的 56 次方,或大约 72 千万亿个不同密钥。在 1977 年时,也许 nsa 有这能力,但没有其他人有足够计算能力 来暴力破解所有可能密钥。但到 1999 年,一台 25 万美元的计算机能在两天内 把 des 的所有可能密钥都试一遍,让 des 算法不再安全。因此 2001 年出了:高级加密标准(aes,advanced encryption standard)。\naes 用更长的密钥 - 128 位/192 位/256 位 - 让暴力破解更加困难。128 位的密钥,哪怕用现在地球上的所有计算机也要上万亿年才能试遍所有组合。你最好赶紧开始!aes 将数据切成一块一块,每块 16 个字节,然后用密钥进行一系列替换加密和移位加密,再加上一些其他操作,进一步加密信息,每一块数据,会重复这个过程 10 次或以上。你可能想知道:为什么只重复 10 次?为什么用 128 位密钥,而不是 10000 位?这其实是基于性能的权衡。如果要花几小时加密和发邮件,或几分钟载入网站,没人愿意用。aes 在性能和安全性间取得平衡,如今 aes 被广泛使用,比如 iphone 上加密文件,用 wpa2 协议在 wifi 中访问 https 网站。\n到目前为止 ,我们讨论过的加密技术依赖于发送者和接收者都知道密钥,发件人用密钥加密,收件人用相同的密钥解密。以前,密钥可以口头约定,或依靠物品,比如德国人给英格玛配了密码本,上面有每天的配置,但互联网时代没法这样做,你能想象 要打开密码本才能访问 youtube 吗?我们需要某种方法 在公开的互联网上传递密钥给对方,这看起来好像不安全,如果密钥被黑客拦截了,黑客不就能解密通信了吗?解决方案是 “密钥交换”!\n密钥交换是一种不发送密钥,但依然让两台计算机在密钥上达成共识的算法,我们可以用\u0026quot;单向函数\u0026quot;来做。单项函数是一种数学操作,很容易算出结果,但想从结果逆向推算出输入非常困难。为了让你明白单项函数,我们拿颜色作比喻,将颜色混合在一起很容易,但想知道混了什么颜色很难,要试很多种可能才知道,用这个比喻,那么我们的密钥是一种独特的颜色。首先,有一个公开的颜色,所有人都可以看到,然后,约翰和我各自选一个秘密颜色,只有自己知道。为了交换密钥,我把我的 秘密颜色 和 公开颜色 混在一起,然后发给约翰,可以写信发,用信鸽发,什么方式都行。约翰也这样做,把他的秘密颜色和公开颜色混在一起,然后发我。我收到约翰的颜色之后,把我的秘密颜色加进去, 现在 3 种颜色混合在一起。john 也一样做。瞧!我们有了一样的颜色,我们可以把这个颜色当密钥,尽管我们从来没有给对方发过这颜色。外部窥探者可以知道部分信息,但无法知道最终颜色,当然,计算机要传输数据时,混合颜料和发颜料不太合适,但幸运的是,数学单向函数是完美的,我们可以用 “迪菲-赫尔曼密钥交换”。在 diffie-hellman 中,单向函数是模幂运算,意思是先做幂运算,拿一个数字当底数,拿一个数字当指数,比如 a\nb\n然后除以第三个数字,最后拿到我们想要的余数。\n举个例子,假设我们想算 3 的 5 次方,模 31 ,我们先算 3 的 5 次方,得到 243,然后除 31,取余数,得到 26 。重点是如果只给余数和基数,很难得知指数是多少。如果我告诉你,3 的某次方 模 31,余数是 7 。你要试很多次,才能知道次方是多少。如果把数字变长一些,比如几百位长,想找到秘密指数是多少,几乎是不可能的。\n现在我们来讨论 diffie-hellman 是怎么,用模幂运算 算出双方共享的密钥。首先,我们有公开的值 - 基数和模数,就像公开的油漆颜色,所有人都看的到,甚至坏人!为了安全向 john 发信息,我选一个秘密指数:x ,然后算 b^x mod m 的结果,然后把这个大数字发给 john 。john 也一样做,选一个秘密指数 y,然后把 b^y mod m 的结果发我,为了算出 双方共用的密钥,我把 john 给我的数,用我的秘密指数 x,进行模幂运算 (看上图),数学上相等于 b 的 xy 次方 模 m 。john 也一样做,拿我给他的数 进行模幂运算,最终得到一样的数。双方有一样的密钥,即使我们从来没给对方发过各自的秘密指数。我们可以用这个大数字当密钥,用 aes 之类的加密技术来加密通信,“diffie-hellman 密钥交换\u0026quot;是建立共享密钥的一种方法。\n双方用一样的密钥加密和解密消息,这叫\u0026quot;对称加密”, 因为密钥一样,凯撒加密,英格玛,aes 都是\u0026quot;对称加密\u0026quot;,还有\u0026quot;非对称加密\u0026quot;,有两个不同的密钥,一个是公开的,另一个是私有的。人们用公钥加密消息 ,只有有私钥的人能解密。换句话说,知道公钥只能加密但不能解密 - 它是\u0026quot;不对称\u0026quot;的!\n想象一个可以锁上的盒子,为了收到安全的信息,我们可以给别人箱子和锁,别人把信息放箱子,然后锁起来,把盒子寄回给我,只有我的钥匙能打开。上锁后,如果发件人或其他人想打开盒子 除了暴力尝试没有其他办法。和盒子例子一样,公钥加密后只能私钥来解密。反过来也是可以的:私钥加密后 ,用公钥解密。这种做法用于签名,服务器可以用私钥加密,任何人都可以用服务器的公钥解密,就像一个不可伪造的签名,因为只有私钥的持有人 能加密,这能证明数据来自正确的服务器或个人,而不是某个假冒者。\n目前最流行的\u0026quot;非对称加密\u0026quot;技术是 rsa ,名字来自发明者: rivest, shamir, adleman 。\n现在你学会了现代密码学的所有\u0026quot;关键\u0026quot;部分: 对称加密,密钥交换,公钥密码学。\n当你访问一个安全的网站,比如银行官网,绿色锁图标代表 用了公钥密码学,验证服务器的密钥,然后建立临时密钥,然后用对称加密保证通信安全。不管你是网上购物,发邮件给朋友,还是看猫咪视频,不管你是网上购物,发邮件给朋友,还是看猫咪视频,密码学都在保护你的隐私和安全。\n谢啦密码学!\n34. 机器学习 \u0026amp; 人工智能 i.e. machine learning \u0026amp; artificial intelligence\n(。・∀・)ノ゙嗨,我是 carrie anne,欢迎收看计算机科学速成课!\n我们之前说过 计算机很擅长存放,整理,获取和处理大量数据,很适合有上百万商品的电商网站,或是存几十亿条健康记录,方便医生看,但如果想根据数据做决定呢?这是机器学习的本质,机器学习算法让计算机可以从数据中学习,然后自行做出预测和决定,能自我学习的程序很有用 。比如判断是不是垃圾邮件?这人有心律失常吗?youtube 的下一个视频该推荐哪个?虽然有用,但我们不会说它有人类一般的智能,虽然 ai 和 ml 这两词经常混着用。\n大多数计算机科学家会说 ,机器学习是为了实现人工智能这个更宏大目标的技术之一,人工智能简称 ai(artificial intelligence)。机器学习和人工智能算法一般都很复杂,所以我们不讲具体细节,重点讲概念。\n我们从简单例子开始:判断飞蛾是\u0026quot;月蛾\u0026quot;还是\u0026quot;帝蛾\u0026quot;,这叫\u0026quot;分类\u0026quot;,做分类的算法叫 “分类器”。虽然我们可以用 照片和声音 来训练算法,很多算法会减少复杂性,把数据简化成 “特征”,“特征\u0026quot;是用来帮助\u0026quot;分类\u0026quot;的值。对于之前的飞蛾分类例子我们用两个特征:“翼展\u0026quot;和\u0026quot;重量”,为了训练\u0026quot;分类器\u0026quot;做出好的预测,我们需要\u0026quot;训练数据”,为了得到数据,我们派昆虫学家到森林里 收集\u0026quot;月蛾\u0026quot;和\u0026quot;帝蛾\u0026quot;的数据。专家可以认出不同飞蛾,所以专家不只记录特征值,还会把种类也写上,这叫 “标记数据”。因为只有两个特征,很容易用散点图把数据视觉化。红色标了 100 个帝蛾蓝色标了 100 个月蛾,可以看到大致分成了两组,但中间有一定重叠,所以想完全区分两个组比较困难,所以机器学习算法登场 - 找出最佳区分。我用肉眼大致估算下,然后判断 翼展小于 45 毫米的 很可能是帝蛾,可以再加一个条件,重量必须小于 75 ,才算是帝蛾。这些线叫 “决策边界”,如果仔细看数据,86 只帝蛾在正确的区域,但剩下 14 只在错误的区域。另一方面,82 只月蛾在正确的区域,18 个在错误的区域。这里有个表 记录正确数和错误数,这表叫\u0026quot;混淆矩阵\u0026quot;。“黑客帝国三部曲\u0026quot;的后两部也许该用这个标题。注意我们没法画出 100% 正确分类的线,降低翼展的决策边界,会把更多\u0026quot;帝蛾\u0026quot;误分类成\u0026quot;月蛾”。如果提高,会把更多月蛾分错类。\n机器学习算法的目的,是最大化正确分类 + 最小化错误分类。在训练数据中,有 168 个正确,32 个错误,平均准确率 84% 。用这些决策边界,如果我们进入森林,碰到一只不认识的飞蛾,我们可以测量它的特征,并绘制到决策空间上,这叫 “未标签数据”。决策边界可以猜测飞蛾种类,这里我们预测是\u0026quot;月蛾\u0026quot;,这个把决策空间 切成几个盒子的简单方法,可以用\u0026quot;决策树\u0026quot;来表示。画成图像,会像左侧 用 if 语句写代码,会像右侧。生成决策树的 机器学习算法需要选择用什么特征来分类,每个特征用什么值。“决策树\u0026quot;只是机器学习的一个简单例子,如今有数百种算法,而且新算法不断出现,一些算法甚至用多个\u0026quot;决策树\u0026quot;来预测,计算机科学家叫这个\u0026quot;森林”,因为有多颗树嘛。也有不用树的方法,比如\u0026quot;支持向量机\u0026quot;,本质上是用任意线段来切分\u0026quot;决策空间\u0026quot;,不一定是直线,可以是多项式或其他数学函数。就像之前,机器学习算法负责找出最好的线,最准的决策边界。之前的例子只有两个特征,人类也可以轻松做到,如果加第 3 个特征,比如\u0026quot;触角长度\u0026quot;,那么 2d 线段,会变成 3d 平面,在三个维度上做决策边界,这些平面不必是直的,而且 真正有用的分类器 会有很多飞蛾种类。你可能会同意 现在变得太复杂了,但这也只是个简单例子 - 只有 3 个特征和 5 个品种,我们依然可以用 3d 散点图 画出来。不幸的是,一次性看 4 个或 20 个特征,没有好的方法,更别说成百上千的特征了,但这正是机器学习要面临的问题。你能想象靠手工 在一个上千维度的决策空间里给超平面 (hyperplane) 找出一个方程吗?大概不行。但聪明的机器学习算法可以做到, google,facebook,微软和亚马逊的计算机里整天都在跑这些算法。\n“决策树\u0026quot;和\u0026quot;支持向量机\u0026quot;这样的技术 发源自统计学,统计学早在计算机出现前,就在用数据做决定。\n有一大类机器学习算法用了统计学,但也有不用统计学的算法,其中最值得注意的是 人工神经网络,灵感来自大脑里的神经元,想学习神经元知识的人,可以看这 3 集。神经元是细胞,用电信号和化学信号 来处理和传输消息,它从其他细胞 得到一个或多个输入,然后处理信号并发出信号,形成巨大的互联网络,能处理复杂的信息。就像你的大脑 在看这个视频,人造神经元很类似,可以接收多个输入,然后整合并发出一个信号,它不用电信号或化学信号,而是吃数字进去,吐数字出来。它们被放成一层层,形成神经元网络,因此得名神经网络。\n回到飞蛾例子,看如何用神经网络分类。\n\u0026gt; 我们的第一层 - 输入层 -\r\u0026gt; 提供需要被分类的单个飞蛾数据\r\u0026gt; 同样,这次也用重量和翼展\r\u0026gt; \u0026gt; 另一边是输出层,有两个神经元:\r\u0026gt; 一个是帝蛾,一个是月蛾\r\u0026gt; 2 个神经元里最兴奋的 就是分类结果\r\u0026gt; \u0026gt; 中间有一个隐藏层\r\u0026gt; 负责把输入变成输出,负责干分类这个重活\r\u0026gt; 为了看看它是如何分类的\r\u0026gt; 我们放大\u0026#34;隐藏层\u0026#34;里的一个神经元\r\u0026gt; 神经元做的第一件事 是把每个输入乘以一个权重\r\u0026gt; \u0026gt; 假设 2.8 是第一个输入,0.1 是第二个输入。\r\u0026gt; 然后它会相加输入\r\u0026gt; 总共是 9.74\r\u0026gt; \u0026gt; 然后对这个结果,用一个偏差值处理\r\u0026gt; 意思是 加或减一个固定值\r\u0026gt; 比如-6,得到 3.74\r\u0026gt; \u0026gt; 做神经网络时,这些偏差和权重,一开始会设置成随机值\r\u0026gt; 然后算法会调整这些值 来训练神经网络\r\u0026gt; 使用\u0026#34;标记数据\u0026#34;来训练和测试\r\u0026gt; 逐渐提高准确性\r\u0026gt; - 很像人类学习的过程\r\u0026gt; \u0026gt; 最后,神经元有激活函数,它也叫传递函数,\r\u0026gt; 会应用于输出,对结果执行最后一次数学修改\r\u0026gt; 例如,把值限制在-1 和+1 之间\r\u0026gt; 或把负数改成 0\r\u0026gt; 我们用线性传递函数,它不会改变值\r\u0026gt; 所以 3.74 还是 3.74\r\u0026gt; \u0026gt; 所以这里的例子\r\u0026gt; 输入 0.55 和 82,输出 3.74\r\u0026gt; 这只是一个神经元,\r\u0026gt; 但加权,求和,偏置,激活函数\r\u0026gt; 会应用于一层里的每个神经元\r\u0026gt; 并向前传播,一次一层\r\u0026gt; \u0026gt; 数字最高的就是结果:\r\u0026gt; 月蛾 重要的是,隐藏层不是只能有一层,可以有很多层,“深度学习\u0026quot;因此得名。训练更复杂的网络, 需要更多的计算量和数据。尽管神经网络 50 多年前就发明了,深层神经网络直到最近才成为可能,感谢强大的处理器和超快的 gpu,感谢游戏玩家对帧率的苛刻要求!\n几年前,google 和 facebook 展示了深度神经网络 在照片中识别人脸的准确率,和人一样高 - 人类可是很擅长这个的! 这是个巨大的里程碑! 现在有深层神经网络开车,翻译,诊断医疗状况等等,这些算法非常复杂,但还不够\u0026quot;聪明”,它们只能做一件事,分类飞蛾,找人脸,翻译,这种 ai 叫\u0026quot;弱 ai\u0026quot;或\u0026quot;窄 ai”,只能做特定任务。但这不意味着它没用,能自动做出诊断的医疗设备,和自动驾驶的汽车真是太棒了!但我们是否需要这些计算机来创作音乐,在空闲时间找美味食谱呢?也许不要! 如果有的话 还挺酷的,真正通用的,像人一样聪明的 ai,叫 “强 ai”,目前没人能做出来 接近人类智能的 ai,有人认为不可能做出来,但许多人说 数字化知识的爆炸性增长 - 比如维基百科,网页和 youtube 视频 - 是\u0026quot;强 ai\u0026quot;的完美引燃物。你一天最多只能看 24 小时的 youtube ,计算机可以看上百万小时,比如,ibm 的沃森吸收了 2 亿个网页的内容,包括维基百科的全文。虽然不是\u0026quot;强 ai\u0026quot;, 但沃森也很聪明 。在 2011 年的知识竞答中碾压了人类,ai 不仅可以吸收大量信息 也可以不断学习进步,而且一般比人类快得多。2016 年 google 推出 alphago - 一个会玩围棋的窄 ai ,它和自己的克隆版下无数次围棋 ,从而打败最好的人类围棋选手。\n学习什么管用,什么不管用 自己发现成功的策略,这叫 “强化学习” ,是一种很强大的方法,和人类的学习方式非常类似。人类不是天生就会走路,是上千小时的试错学会的,计算机现在才刚学会反复试错来学习,对于很多狭窄的问题,强化学习已被广泛使用,有趣的是,如果这类技术可以更广泛地应用,创造出类似人类的\u0026quot;强 ai\u0026quot; 能像人类小孩一样学习,但学习速度超快。 如果这发生了,对人类可能有相当大的影响 - 我们以后会讨论。\n感谢收看。我们下周见。\n35. 计算机视觉 i.e. computer vision\n(。・∀・)ノ゙嗨 我是 carrie anne,欢迎收看计算机科学速成课\n今天, 我们来思考视觉的重要性,大部分人靠视觉来做饭,越过障碍,读路牌,看视频,以及无数其它任务。视觉是信息最多的感官 ,比如周围的世界是怎样的,如何和世界交互,因此半个世纪来计算机科学家一直在想办法让计算机有视觉,因此诞生了\u0026quot;计算机视觉\u0026quot;这个领域,目标是让计算机理解图像和视频。用过相机或手机的都知道 ,可以拍出有惊人保真度和细节的照片 - 比人类强得多,但正如计算机视觉教授 李飞飞 最近说的,“听到\u0026quot;不等于\u0026quot;听懂”,“看到\u0026quot;不等于\u0026quot;看懂”。\n复习一下,图像是像素网格,每个像素的颜色通过三种基色定义:红,绿,蓝,通过组合三种颜色的强度 ,可以得到任何颜色, 也叫 rgb 值。\n最简单的计算机视觉算法,最合适拿来入门的,是跟踪一个颜色物体,比如一个粉色的球。首先,我们记下球的颜色,保存最中心像素的 rgb 值,然后给程序喂入图像,让它找最接近这个颜色的像素。算法可以从左上角开始,逐个检查像素,计算和目标颜色的差异,检查了每个像素后,最贴近的像素,很可能就是球。不只是这张图片, 我们可以在视频的每一帧图片跑这个算法,跟踪球的位置。当然,因为光线,阴影和其它影响,球的颜色会有变化,不会和存的 rgb 值完全一样,但会很接近。如果情况更极端一些 比如比赛是在晚上,追踪效果可能会很差。如果球衣的颜色和球一样,算法就完全晕了,因此很少用这类颜色跟踪算法,除非环境可以严格控制。\n颜色跟踪算法是一个个像素搜索, 因为颜色是在一个像素里,但这种方法 不适合占多个像素的特征,比如物体的边缘,是多个像素组成的。为了识别这些特征,算法要一块块像素来处理,每一块都叫\u0026quot;块\u0026quot;。举个例子,找垂直边缘的算法,假设用来帮无人机躲避障碍。为了简单,我们把图片转成灰度 ,不过大部分算法可以处理颜色,放大其中一个杆子,看看边缘是怎样的,可以很容易地看到 杆子的左边缘从哪里开始,因为有垂直的颜色变化。我们可以弄个规则说,某像素是垂直边缘的可能性 取决于左右两边像素的颜色差异程度,左右像素的区别越大,这个像素越可能是边缘,如果色差很小,就不是边缘,这个操作的数学符号 看起来像这样,这叫\u0026quot;核\u0026quot;或\u0026quot;过滤器\u0026quot;,里面的数字用来做像素乘法,总和 存到中心像素里。\n我们来看个实际例子。我已经把所有像素转成了灰度值,现在把\u0026quot;核\u0026quot;的中心,对准感兴趣的像素,这指定了每个像素要乘的值,然后把所有数字加起来。在这里,最后结果是 147,成为新像素值,把 核 应用于像素块,这种操作叫\u0026quot;卷积\u0026quot;。现在我们把\u0026quot;核\u0026quot;应用到另一个像素,结果是 1,色差很小,不是边缘。如果把\u0026quot;核\u0026quot;用于照片中每个像素,结果会像这样,垂直边缘的像素值很高,注意,水平边缘(比如背景里的平台),几乎看不见。如果要突出那些特征,要用不同的\u0026quot;核\u0026quot;,用对水平边缘敏感的\u0026quot;核\u0026quot;,这两个边缘增强的核叫 “prewitt 算子”,以发明者命名,这只是众多\u0026quot;核\u0026quot;的两个例子。\n“核\u0026quot;能做很多种图像转换,比如这个\u0026quot;核\u0026quot;能锐化图像,这个\u0026quot;核\u0026quot;能模糊图像。“核\u0026quot;也可以像饼干模具一样,匹配特定形状,之前做边缘检测的\u0026quot;核”,会检查左右或上下的差异,但我们也可以做出 擅长找线段的\u0026quot;核”,或者包了一圈对比色的区域,这类\u0026quot;核\u0026quot;可以描述简单的形状,比如鼻梁往往比鼻子两侧更亮,所以线段敏感的\u0026quot;核\u0026quot;对这里的值更高,眼睛也很独特 - 一个黑色圆圈被外层更亮的一层像素包着,有其它\u0026quot;核\u0026quot;对这种模式敏感。\n当计算机扫描图像时,最常见的是用一个窗口来扫,可以找出人脸的特征组合,虽然每个\u0026quot;核\u0026quot;单独找出脸的能力很弱 ,但组合在一起会相当准确,不是脸但又有一堆脸的特征在正确的位置,这种情况不太可能。这是一个早期很有影响力的算法的基础,叫 维奥拉·琼斯 人脸检测算法,如今的热门算法是 “卷积神经网络”。\n我们上集谈了神经网络,如果需要可以去看看,总之,神经网络的最基本单位,是神经元,它有多个输入,然后会把每个输入 乘一个权重值,然后求总和。听起来好像挺耳熟,因为它很像\u0026quot;卷积\u0026quot;。实际上,如果我们给神经元输入二维像素,完全就像\u0026quot;卷积\u0026quot;,输入权重等于\u0026quot;核\u0026quot;的值,但和预定义\u0026quot;核\u0026quot;不同,神经网络可以学习对自己有用的\u0026quot;核\u0026quot;,来识别图像中的特征。“卷积神经网络\u0026quot;用一堆神经元处理图像数据,每个都会输出一个新图像,本质上是被不同的\u0026quot;核\u0026quot;处理了,输出会被后面一层神经元处理,卷积卷积再卷积,第一层可能会发现\u0026quot;边缘\u0026quot;这样的特征,单次卷积可以识别出这样的东西,之前说过,下一层可以在这些基础上识别,比如由\u0026quot;边缘\u0026quot;组成的角落,然后下一层可以在\u0026quot;角落\u0026quot;上继续卷积,下一些可能有识别简单物体的神经元,比如嘴和眉毛,然后不断重复,逐渐增加复杂度,直到某一层把所有特征放到一起: 眼睛,耳朵,嘴巴,鼻子,然后说:“啊哈,这是脸!”\n“卷积神经网络\u0026quot;不是非要很多很多层,但一般会有很多层,来识别复杂物体和场景,所以算是\u0026quot;深度学习” 。“维奥拉·琼斯\u0026quot;和\u0026quot;卷积神经网络\u0026quot;不只是认人脸,还可以识别手写文字,在 ct 扫描中发现肿瘤,监测马路是否拥堵,但我们这里接着用人脸举例。\n不管用什么算法,识别出脸之后,可以用更专用的计算机视觉算法 来定位面部标志,比如鼻尖和嘴角,有了标志点,判断眼睛有没有张开就很容易了,只是点之间的距离罢了,也可以跟踪眉毛的位置,眉毛相对眼睛的位置 可以代表惊喜或喜悦,根据嘴巴的标志点,检测出微笑也很简单,这些信息可以用\u0026quot;情感识别算法\u0026quot;来识别。让电脑知道你是开心,忧伤,沮丧,困惑等等,然后计算机可以做出合适的行为。比如当你不明白时 给你提示,你心情不好时,就不弹更新提示了,这只是计算机通过视觉感知周围的一个例子。不只是物理环境 - 比如是不是在上班,或是在火车上,还有社交环境 - 比如是朋友的生日派对,还是正式商务会议。你在不同环境会有不同行为,计算机也应如此,如果它们够聪明的话。..\n面部标记点 也可以捕捉脸的形状,比如两只眼睛之间的距离,以及前额有多高,做生物识别,让有摄像头的计算机能认出你。不管是手机解锁, 还是政府用摄像头跟踪人,人脸识别有无限应用场景。另外,跟踪手臂和全身的标记点,最近也有一些突破,让计算机理解用户的身体语言,比如用户给联网微波炉的手势。\n正如系列中常说的,抽象是构建复杂系统的关键。\n计算机视觉也是一样。硬件层面,有工程师在造更好的摄像头 ,让计算机有越来越好的视力,我自己的视力却不能这样。用来自摄像头的数据 ,可以用视觉算法找出脸和手,然后可以用其他算法接着处理,解释图片中的东西,比如用户的表情和手势。有了这些,人们可以做出新的交互体验,比如智能电视和智能辅导系统 会根据用户的手势和表情来回应。\n这里的每一层都是活跃的研究领域,每年都有突破,这只是冰山一角。如今, 计算机视觉无处不在 - 商店里扫条形码, 等红灯的自动驾驶汽车,或是 snapchat 里添加胡子的滤镜。令人兴奋的是,一切才刚刚开始。最近的技术发展,比如超快的 gpu,会开启越来越多可能性。视觉能力达到人类水平的计算机 会彻底改变交互方式,当然,如果计算机能听懂我们然后回话,就更好了。\n我们下周讨论,到时见。\n36. 自然语言处理 i.e. natural language processing\n(。・∀・)ノ゙嗨 我是 carrie anne,欢迎收看计算机科学速成课\n上集我们讨论了计算机视觉 - 让电脑能看到并理解,今天我们讨论怎么让计算机理解语言。你可能会说:计算机已经有这个能力了,在第 9 和第 12 集,我们聊了机器语言和更高层次的编程语言。虽然从定义来说 ,它们也算语言,但词汇量一般很少,而且非常结构化,代码只能在拼写和语法完全正确时,编译和运行。\n当然,这和人类语言完全不同 - 人类语言叫\u0026quot;自然语言”。自然语言有大量词汇,有些词有多种含义,不同口音,以及各种有趣的文字游戏。人们在写作和说话时也会犯错,比如单词拼在一起发音,关键细节没说 ,导致意思模糊两可,以及发错音。但大部分情况下,另一方能理解,人类有强大的语言能力,因此,让计算机拥有语音对话的能力,这个想法从构思计算机时就有了。\n“自然语言处理\u0026quot;因此诞生,简称 nlp(natural language processing),结合了计算机科学和语言学的 一个跨学科领域。单词组成句子的方式有无限种,我们没法给计算机一个字典,包含所有可能句子,让计算机理解人类在嘟囔什么,所以 nlp 早期的一个基本问题是 怎么把句子切成一块块,这样更容易处理。\n上学时,老师教你 英语单词有九种基本类型:名词,代词,冠词,动词,形容词,副词,介词,连词和感叹词,这叫\u0026quot;词性”。还有各种子类,比如单数名词 vs 复数名词 ,副词最高级 vs 副词比较级,但我们不会深入那些。\n了解单词类型有用,但不幸的是,很多词有多重含义 比如 rose 和 leaves ,可以用作名词或动词。仅靠字典,不能解决这种模糊问题,所以电脑也要知道语法,因此开发了 “短语结构规则” 来代表语法规则。例如,英语中有一条规则,句子可以由一个名词短语和一个动词短语组成,名词短语可以是冠词,如 the ,然后一个名词,或一个形容词后面跟一个名词。你可以给一门语言制定出一堆规则,用这些规则,可以做出\u0026quot;分析树”,它给每个单词标了可能是什么词性,也标明了句子的结构。数据块更小 ,更容易处理,每次语音搜索,都有这样的流程,比如 “最近的披萨在哪里”,计算机能明白这是\u0026quot;哪里\u0026quot;(where)的问题,知道你想要名词\u0026quot;披萨\u0026quot;(pizza),而且你关心的维度是\u0026quot;最近的\u0026quot;(nearest)。“最大的长颈鹿是什么?“或\u0026quot;thriller 是谁唱的?” 也是这样处理。\n把语言像乐高一样拆分,方便计算机处理,计算机可以回答问题 ,以及处理命令,比如\u0026quot;设 2:20 的闹钟”,或\u0026quot;用 spotify 播放 t-swizzle\u0026quot;,但你可能体验过,如果句子复杂一点,计算机就没法理解了。\n嘿 siri ...... 俺觉得蒙古人走得太远了\r在这个最温柔的夏日的日子里,你觉得怎么样?\rsiri:我没明白 还有,“短语结构规则\u0026quot;和其他把语言结构化的方法,可以用来生成句子。数据存在语义信息网络时,这种方法特别有效,实体互相连在一起,提供构造句子的所有成分。siri:thriller 于 1983 年发行,由迈克尔杰克逊演唱,google 版的叫\u0026quot;知识图谱”。\n在 2016 年底,包含大概七百亿个事实,以及不同实体间的关系,处理, 分析, 生成文字 ,是聊天机器人的最基本部件 - 聊天机器人就是能和你聊天的程序。早期聊天机器人大多用的是规则,专家把用户可能会说的话,和机器人应该回复什么,写成上百个规则,显然,这很难维护,而且对话不能太复杂。\n一个著名早期例子叫 eliza ,1960 年代中期 诞生于麻省理工学院,一个治疗师聊天机器人。它用基本句法规则 来理解用户打的文字,然后向用户提问,有时候会感觉像和人类沟通一样,但有时会犯简单甚至很搞笑的错误。\n聊天机器人和对话系统,在过去五十年发展了很多,如今可以和真人很像!如今大多用机器学习,用上 gb 的真人聊天数据 来训练机器人,现在聊天机器人已经用于客服回答,客服有很多对话可以参考,人们也让聊天机器人互相聊天。在 facebook 的一个实验里,聊天机器人甚至发展出自己的语言,很多新闻把这个实验 报导的很吓人,但实际上只是计算机 在制定简单协议来帮助沟通,这些语言不是邪恶的,而是为了效率。\n但如果听到一个句子 - 计算机怎么从声音中提取词汇?这个领域叫\u0026quot;语音识别\u0026quot;。这个领域已经重点研究了几十年,贝尔实验室在 1952 年推出了第一个语音识别系统,绰号 audrey,自动数字识别器。如果你说得够慢,它可以识别全部十位数字,这个项目没有实际应用,因为手输快得多。十年后,1962 年的世界博览会上,ibm 展示了一个鞋盒大小的机器,能识别 16 个单词,为了推进\u0026quot;语音识别\u0026quot;领域的研究,darpa 在 1971 年启动了一项雄心勃勃的五年筹资计划,之后诞生了卡内基梅隆大学的 harpy。harpy 是第一个可以识别 1000 个单词以上的系统,但那时的电脑,语音转文字,经常比实时说话要慢十倍或以上。幸运的是,1980,1990 年代 计算机性能的大幅提升,实时语音识别变得可行,同时也出现了处理自然语言的新算法,不再是手工定规则,而是用机器学习,从语言数据库中学习。\n如今准确度最高的语音识别系统 用深度神经网络,我们在第 34 集讲过。为了理解原理,我们来看一些对话声音,先看元音,比如 a 和 e ,这是两个声音的波形,我们在第 21 集(文件格式)说过。\n这个信号来自 麦克风内部隔膜震动的频率,在这个视图中,横轴是时间,竖轴是隔膜移动的幅度,或者说振幅,虽然可以看到 2 个波形有区别,但不能看出,“啊!这个声音肯定是 e”。为了更容易识别,我们换个方式看:\n谱图\n这里横轴还是时间,但竖轴不是振幅,而是不同频率的振幅,颜色越亮,那个频率的声音越大,这种波形到频率的转换 是用一种很酷的算法做的 - 快速傅立叶变换(fft)。如果你盯过立体声系统的 eq 可视化器,它们差不多是一回事,谱图是随着时间变化的,你可能注意到,信号有种螺纹图案,那是我声道的回声。为了发出不同声音,我要把声带,嘴巴和舌头变成不同形状,放大或减少不同的共振,可以看到有些区域更亮,有些更暗。如果从底向上看,标出高峰 - 叫\u0026quot;共振峰\u0026quot; - 可以看到有很大不同,所有元音都是如此,这让计算机可以识别元音,然后识别出整个词。\n让我们看一个更复杂的例子,当我说\u0026quot;她很开心\u0026quot;的时候,可以看到 e 声,和 a 声,以及其它不同声音,比如 she 中的 shh 声,was 中的 wah 和 sss,等等,这些构成单词的声音片段叫\u0026quot;音素\u0026quot;。语音识别软件 知道这些音素,英语有大概 44 种音素,所以本质上变成了音素识别,还要把不同的词分开,弄清句子的开始和结束点,最后把语音转成文字,使这集视频开头里讨论的那些技术成为可能。因为口音和发音错误等原因,人们说单词的方式略有不同,所以结合语言模型后,语音转文字的准确度会大大提高。里面有单词顺序的统计信息,比如:“她\u0026quot;后面很可能跟一个形容词,比如\u0026quot;很开心”,“她\u0026quot;后面很少是名词,如果不确定是 happy 还是 harpy,会选 happy,因为语言模型认为可能性更高。\n最后,我们来谈谈 “语音合成”,让计算机输出语音,它很像语音识别,不过反过来。把一段文字,分解成多个声音,然后播放这些声音。早期语音合成技术,可以清楚听到音素是拼在一起的,比如这个 1937 年贝尔实验室的手动操作机器,不带感情的说 “她看见了我”。\n到了 1980 年代,技术改进了很多,但音素混合依然不够好,产生明显的机器人声。如今,电脑合成的声音,比如 siri, cortana, alexa 好了很多,但还不够像人,但我们非常非常接近了,这个问题很快会被解决。\n现在语音界面到处都是,手机里,汽车里,家里,也许不久之后耳机也会有。这创造一个正反馈循环,人们用语音交互的频率会提高,这又给了谷歌,亚马逊,微软等公司更多数据来训练语音系统,提高准确性。准确度高了,人们更愿意用语音交互,越用越好,越好越用。很多人预测,语音交互会越来越常见,就像如今的屏幕,键盘,触控板等设备,这对机器人发展是个好消息。\n机器人就不用走来走去时带个键盘和人类沟通。\n下周我们讲机器人。到时见。\n37. 机器人 i.e. robots\n嗨,我是 carrie anne,欢迎收看计算机速成课。\n今天 我们要讨论机器人,你脑中冒出来的第一个印象估计是 类人机器人,经常在电视剧和电影里看到。有时候它们是朋友和同事,但更常见的是阴险无情,身经百战。我们经常把机器人看成未来科技,但事实是:机器人时代已经来临了 - 它们是同事,帮我们把困难的工作,做得更快更好。机器人的定义有很多种,但总的来说,机器人由计算机控制,可以自动执行一系列动作的机器,外观并不重要,可以是给汽车喷漆的机械臂,无人机,或辅助外科医生的蛇状机器人,以及人形机器人。\n有时我们叫虚拟人物\u0026quot;机器人”,但叫 bot 甚至 agent 会更合适,因为\u0026quot;机器人\u0026quot;的潜在含义是存在于现实世界中的机器。robot (机器人) 一词 ,首次出现在 1920 年的一部捷克戏剧,代表人造的类人角色。robot 源于斯拉夫语词汇 robota 代表强迫劳动,代表农民在十九世纪 欧洲封建社会的强迫劳动,戏剧没讲太多技术细节。但即使一个世纪后,这种描述依然很普遍:机器人都是大规模生产,高效不知疲倦,看起来像人的东西,但毫无情感,不会保护自己,没有创造力。\n更广义的自动运行机器,早在 1920 年代前就有了。很多古代发明家 发明了能自动运行的机械装置,比如计时和定时敲钟。有很多装置 ,有动物和人类的形象 ,能跳舞,唱歌,打鼓等。这些不用电,而且肯定没有电子部件的机器,叫\u0026quot;自动机\u0026quot;。\n举个例子 1739 年法国人 jacques de vaucans 做了个自动机,法语叫 canard digerateur,翻译过来是 “吃饭鸭”,一个像鸭子的机器,能吃东西然后排便。伏尔泰在 1739 年写,“如果没有吃饭鸭的声音,还有什么能提醒你法国的荣光呢?”\n一个名声很臭的例子是\u0026quot;土耳其行棋傀儡\u0026quot;,一个能下国际象棋的人形机器人,在 1770 年建造完成后,就在欧洲各地展览。好棋艺惊叹观众,像某种机械人工智能,不幸的是,这是个骗局 - 机器里有人控制。\n第一台计算机控制的机器,出现在 1940 年代晚期,这些计算机数控的机器,简称 cnc (computer numerical control)机器,可以执行一连串 程序指定的操作,精细的控制 ,让我们能生产之前很难做的物品,比如从一整块铝 加工出复杂的螺旋桨 - 这用普通机械工具很难做到,并且误差容忍度很小,无法手工加工。cnc 机器大大推进了制造业,不仅提高了制造能力和精确度 ,还降低了生产成本 - 我们之后会深入讨论这个(第 40 集)。\n第一个商业贩卖的 可编程工业机器人 叫 unimate,于 1960 年卖给通用汽车公司,它可以把压铸机做出来的热金属成品提起来,然后堆起来,机器人行业由此开始。很快,机器人开始堆叠货盘,焊接,给汽车喷漆等等。对于简单运动 - 比如机器爪子 在轨道上来回移动,可以指示它移动到特定位置,它会一直朝那个方向移动,直到到达 然后停下来,这种行为 可以用简单控制回路做。\n首先,判断机器人的位置,我们到了吗?\r没有\r那么继续前进\r再次判断位置\r我们到了吗?\r没有,所以继续前进\r我们到了吗?\r是的!\r现在可以停下来了,别问了! 因为我们在不断缩小 当前位置和目标位置的距离,这个控制回路 更准确的叫 “负反馈回路”。负反馈回路 有三个重要部分:\n首先是一个传感器,可以测量现实中的东西,比如水压,马达位置,气温,或任何你想控制的东西,根据传感器,计算和目标值相差多大,得到一个\u0026quot;错误\u0026quot;,然后\u0026quot;控制器\u0026quot;会处理这个\u0026quot;错误\u0026quot;,决定怎么减小错误,然后用泵,电机,加热元件,或其他物理组件来做出动作。在严格控制的环境中,这种简单控制回路也够用了,但在很多现实应用中,情况复杂得多,假设爪子很重,哪怕控制回路叫停了,惯性让爪子超过了预期位置,然后控制回路又开始运行,叫爪子移动回去。一个糟糕的控制回路 可能会让爪子不断来回移动,甚至永远循环。更糟糕的是,现实世界中,机器人会受到各种外力影响,比如摩擦力,风,等等。为了处理这些外力,我们需要更复杂的控制逻辑。\n一个使用广泛的机制,有控制回路和反馈机制,叫 “比例-积分-微分控制器”。这个有点绕口,所以一般简称 “pid 控制器”,它以前是机械设备,现在全是纯软件了。想象有一个机器人,端咖啡给客人,设计目标是 每秒两米的速度在顾客间穿行,这个速度是理想速度,安全又合适。当然,环境是会变化的,有时候有风,有时候有上坡下坡,以及其他影响机器人速度的因素,所以,给马达的动力要加大或减少,以保持目标速度。用机器人的速度传感器,我们可以把当前速度和目标速度画张图,pid 控制器根据这些数据,算出 3 个值,首先是 “比例值” ,就是\u0026quot;实际值\u0026quot;和\u0026quot;理想值\u0026quot;差多少,“实际值\u0026quot;可能有一定滞后,或者是实时的。之前的简单控制回路,用的就是这个值,“实际值\u0026quot;和\u0026quot;理想值\u0026quot;的差距越大,就越用力。换句话说,它是\u0026quot;比例控制\u0026quot;的。接下来,算 “积分值” ,就是一段时间内 误差的总和,比如最近几秒,帮助弥补误差,比如上坡时可能就会产生误差。如果这个值很大,说明比例控制不够,要继续用力前进。最后有 “导数值” ,是期望值与实际值之间的变化率,有助于解决 未来可能出现的错误, 有时也叫\u0026quot;预期控制”,比如前进的太快,要稍微放松一点,避免冲过头。\n这三个值会一起使用,它们有不同权重,然后用来控制系统。pid 控制器到处都是,比如汽车里的巡航控制,无人机调整螺旋桨速度,以保持水平,以及一些更奇怪的机器人,比如这个用球来平衡和移动的机器人。更高级的机器人一般需要多个控制回路同时运行,来保持机器人平衡,调整肢体位置,等等。\n之前说过,控制回路负责把机器人的属性(比如当前位置)变成期望值,你可能好奇这些值 是哪里来的,这是更高层软件的责任。软件负责做出计划 并让机器人执行动作,比如制定一条路线来绕过障碍物,或者把任务分成一步步,比如把拿起一个球,分解成一个个简单连续动作。用这些技术,机器人已经取得不少令人印象深刻的成就 - 它们潜到了海洋最深处,在火星上跑了十几年。\n但有趣的是,许多对人类来说很简单的任务,对机器人很困难: 比如两条腿走路,开门,拿东西时不要捏碎了,或是穿 t 恤,或是摸狗,这些你可能想都不用想,但有超级计算机能力的机器人却做不到。机器人研究领域在全力解决这些问题,我们前几集聊过的 人工智能,最有可能解决这些问题。例如,谷歌在进行一项实验,让一堆机器人手臂把各种东西,从一个盒子拿到另一个盒子,不断试错学习。经过数千小时的练习,机器人把错误率降低了一半。不像人类,机器人可以 24 小时全天运行,而且多个手臂同时练习,所以机器人擅长抓东西只是时间问题,但现在,小婴儿都比机器人更会抓东西。\n近年最大的突破之一,是无人驾驶汽车。如果你仔细想想,汽车没几个输入 - 只是加速减速,左转右转,难的问题是 判断车道,理解路标,预测车流,车流中穿行,留心行人和骑自行车的,以及各种障碍。车上布满了传感器,无人驾驶汽车非常依赖计算机视觉算法,我们在第 35 集讨论过。\n现在也开始出现类人机器人 - 外貌和行为像人类的机器人,不过现在两个目标都没接近(外貌和行为),因为看起来一般怪怪的,行为也怪怪的。但至少有《西部世界》可以看看。无论如何,对机器人研究者来说,把各种技术结合起来,比如人工智能,计算机视觉和自然语言处理,来让机器人越来越像人,是个诱人的目标。至于人类为什么如此着迷 做出和我们一样的机器人,你得去看《哲学速成课》。在未来好一段时间里,和人类一样的机器人 依然只能存在科幻小说里。军队也对机器人很有兴趣 - 因为机器人可以替换, 而且力量,耐力,注意力,准确性可以远超人类。拆弹机器人和无人侦察机如今很常见,但完全自主决定,全副武装的机器人也在慢慢出现,比如韩国的三星 sgr-a1 哨兵炮,有智力并且可以杀人的机器人,叫 “致命自主武器”,这种武器是复杂又棘手的问题。毫无疑问,它们可以把士兵从战场带离 挽救生命,甚至阻止战争的发生。值得注意的是 人们对炸药和核弹也说过一样的话。另一方面,我们可能会不小心创造出 无情又高效的杀人机器,没有人类般的判断力和同情心,战争的硝烟会变得更加黑暗和复杂。机器人会接受命令并高效执行,但有时人类的命令是错的。\n这场辩论会持续很长时间,而且随着机器人技术的进步,两边的辩论会越来越激烈,这也是个老话题了。\n科幻作家 艾萨克·阿西莫夫 早预见了这种危险,他在 1942 年短篇小说 runaround 中写了\u0026quot;机器人三定律”,之后又加了\u0026quot;定律 0\u0026quot;。简单说 这些定律指导机器人的行为准则 或者说道德指南,让机器人不要伤害,特别是不要伤害人类,这些规则实践起来相当不足,并且有很多模糊的地方,但阿西莫夫三定律 激发了大量科幻小说讨论和学术讨论, 如今有专门讨论机器人伦理的会议。重要的是,阿西莫夫写这些虚构规则,是为了反对 “机器人都很邪恶” 这种常见描述。他童年读的小说里,这样的场景很常见,机器人脱离控制,然后伤害甚至毁灭创造者。阿西莫夫认为 机器人有用,可靠,甚至可以让人喜爱。\n我想让你思考这种两面性。我们讨论过的许多技术,有好的一面也有坏的一面,我们要认真思考计算机的潜力和危害,来改善这个世界,而机器人最能提醒我们这一点了。\n我 们 下 周 见。\n38. 计算机心理学 i.e. psychology of computing\n(。・∀・)ノ゙嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n在这个系列中,我们聊的话题几乎全是计算机-比如电路和算法,毕竟这是计算机速成课,但归根结底,计算机只是给人用的工具,而人类有点。.. 乱。\n人类不是被工程师设计的,没有具体性能规格。我们一会儿是理性的,一会儿是不理性的。你有没有对导航生过气?或是漫无目的的刷维基百科?求浏览器加载快点?给扫地机器人取名?这些是人类行为!\n为了做出使用愉快的计算机,我们需要了解计算机和人类的优缺点。优秀的系统设计师在创造软件时,会运用社会心理学,认知心理学,行为心理学,感知心理学的原理。你肯定见过难用的物理界面/计算机界面 阻碍你做事,甚至糟糕到放弃使用,那个界面的\u0026quot;易用度\u0026quot;很差。“易用度\u0026quot;指的是人造物体,比如软件 达到目的的效率有多高。为了帮助人类工作,我们需要了解人类 - 怎么看,思考,反应和互动。\n举个例子,心理学家已经对 人类的视觉系统做了全面的研究,我们知道人类擅长给颜色强度排序,这里有三个颜色,你能从浅色到深色排序吗?你可以轻易做到,所以颜色强度很适合显示连续值。另一方面,人类很不擅长排序颜色,这是另一个例子,把橙色放到蓝色前面还是后面?绿色放哪里?你可能想通过光的波长排序 ,就像彩虹一样,但这样太累了。大部分人会很慢而且容易出错,由于视觉系统天生是这样,所以用不同颜色显示连续性数据,是个糟糕的选择,你得经常看表格来对比数据。 然而,如果数据没有顺序,用不同颜色就很合适,比如分类数据,也许这些看起来很明显 ,但你会惊讶有多少设计把这些基本事情搞错。\n除了视觉 ,理解人类的认知系统能帮我们设计更好的界面,比如,如果信息分块了 ,会更容易读,更容易记。分块是指把信息分成更小,更有意义的块,人类的短期记忆能记住 5 到 9 个东西,保守一点,分组一般是 5 个或更少,所以电话号码一般分块,比如 317-555-3897 。10 个连续数可能会忘,分成 3 块更好记。从计算机的角度来看,分块更费时费空间,效率更低,但这对人类更有效率 - 碰到这种抉择时,我们总是以人类优先。现在我们还是老大,暂时啦。\n界面设计用了分块, 比如下拉菜单 和带按钮的菜单栏,对电脑来说,全部挤在一起更有效率,分块浪费内存 浪费屏幕,但这样设计更容易扫视,记住和访问。界面设计中另一个重点概念是\u0026quot;直观功能”,don norman 让这个词在计算机界流行起来,根据他的说法,“直观功能 为如何操作物体提供线索,平板用来推,旋钮用来转,插槽用来插东西,[…] 直观功能做的好,用户只需要看一眼就知道怎么做: 不需要图片,标签或指南来说明”。\n如果你拉过门把手打不开,然后意识到要推开才对,那么你发现了一个坏掉的\u0026quot;直观功能\u0026quot;。平板是更好的设计,因为只能推开,门是简单的东西,如果你要贴指示让人们明白怎么用,那么也许你应该重新设计。\n“直观功能\u0026quot;广泛用于图形界面,我们在第 26 集讨论过,这是图形界面比命令行更容易用的原因之一。你不用猜测屏幕上什么东西是可点的,可点的会看起来像按钮,他们弹出来,只是等着你压他们!\n我最喜欢的\u0026quot;直观功能\u0026quot;之一,是向用户表明元素是可拖动的,“滚花” - 一种视觉纹理,告诉用户哪里可以拖动,这个点子来自现实世界中的工具,和\u0026quot;直观功能\u0026quot;相关的一个心理学概念是 “认出与回想”。如果你考过试,肯定感受过这个,这就是为什么选择题比填空题容易。一般来说,用感觉来触发记忆会容易得多,比如文字,图片或声音,所以我们用图标代表功能 - 比如\u0026quot;垃圾桶\u0026quot;图标 代表里面放着被删除的文件。我们不用去回想图标的功能是什么,只要能认出来就行了,比命令行好得多,命令行得依靠记忆来输命令,到底是输入\u0026quot;删除\u0026quot;“移除\u0026quot;“垃圾\u0026quot;还是\u0026quot;射出”?可能是任何命令! 顺带一说,在 linux 里删除文件的命令是 “rm” 。\n回到正题,让所有菜单选项好找好记,有时候意味着用的时候会慢一些,这与另一个心理学概念冲突:“专业知识”。当你用界面熟悉之后,速度会更快一些,建立如何高效完成事情的\u0026quot;心理模型”,所以 好的界面应该提供多种方法来实现目标。一个好例子是复制粘贴,可以在\u0026quot;编辑\u0026quot;的下拉菜单中找到,也可以用快捷键,一种适合新手,一种适合专家,两者都不耽误,鱼和熊掌兼得!\n除了让人类做事更高效,我们也希望电脑能有一点情商,能根据用户的状态做出合适地反应,能根据用户的状态做出合适地反应,让使用电脑更加愉快。rosalind picard 在 1995 年关于\u0026quot;情感计算\u0026quot;的论文中,阐述了这一愿景,这篇论文开创了心理学,社会科学和计算机科学的跨学科结合,促进了让计算机理解人类情感的研究,这很重要,因为情绪会影响日常活动,比如学习,沟通和决策。情感系统会用传感器,录声音,录像(你的脸)以及生物指标,比如出汗和心率,得到的数据和计算模型结合使用。模型会写明人类如何表达情感,怎么是快乐 ,怎么是沮丧,以及社交状态,比如友谊和信任。模型会估算用户的情绪,以及怎样以最好的回应用户,以达到目标,比如让用户冷静下来,建立信任,或帮忙完成作业。\nfacebook 在 2012 年进行了一项\u0026quot;影响用户\u0026quot;的研究,数据科学家在一个星期内,修改了很多用户 时间线上显示的内容,有些人会看到更多积极向上的内容,有些人会看到更多负面消极的内容。研究人员分析了那一周内人们的发帖,发现看到积极向上内容的用户,发的帖子往往更正面。另一方面,看到负面内容的用户,发的内容也更负面。显然,facebook 和其他网站向你展示的内容,绝对会对你有影响。作为信息的守门人,这是巨大的机会 ,同时也是责任,研究结果相当有争议性。而且它还产生了一个有趣的问题: 计算机程序如何回应人类?\n如果用户的情绪比较负面,也许电脑不应该以一种烦人的 “你要振作起来呀” 的态度回答问题。或者,也许电脑应该试着积极正面的回应用户,即使这有点尴尬。什么行为是\u0026quot;正确的”,是个开放性的研究问题。\n既然说到 facebook,这是一个\u0026quot;以计算机为媒介沟通\u0026quot;的好例子,简称 “cmc”(computer-mediated communication),也是一个很大的研究领域。这包括同步通信 - 所有参与者同时在线进行视频通话,以及异步通信 - 比如推特,邮件,短信,人们可以随时随地回复信息。研究人员还研究用户怎么用表情包,怎么轮换发言,以及用不同沟通渠道时,用词有什么区别。\n一个有趣的发现是,比起面对面沟通,人们更愿意在网上透露自己的信息。所以如果想知道用户 真正花了多少小时看\u0026quot;大英烘培大赛\u0026quot;(电视节目),比起做个带脸的虚拟助理 做 聊天机器人 是个更好的选择。心理学研究也表明,如果想说服,讲课,或引起注意 眼神注视非常重要。在谈话时看着别人叫 相互凝视,这被证明可以促进参与感 帮助实现谈话目标,不管是学习,交朋友,还是谈生意。在录像讲座中,老师很少直视相机, 一般是看在场学生,对他们没问题,但这会让在线看视频的人没什么参与感,为此,研究人员开发了计算机视觉和图形软件 来纠正头部和眼睛,视频时会觉得对方在直视摄像头,看着他们,这叫\u0026quot;增强凝视\u0026quot;。类似技术也用于视频会议,纠正摄像头位置,因为摄像头几乎总在屏幕上方,因为你一般会盯着屏幕上的另一方 ,而不是盯着摄像头,所以视频里看起来像在向下看。没有相互凝视 - 这会导致各种不幸的副作用,比如权力不平衡,幸运的是,可以用软件修正,看起来像在凝视着对方的眼睛。人类也喜欢\u0026quot;拟人化\u0026quot;的物体,对计算机也不例外,特别是会动的计算机,比如上集说的机器人。\n在过去一个世纪,除了工业用途机器人,有越来越多机器人用于医疗,教育和娱乐 。它们经常和人类互动 - 人机交互,简称 hri(human-robot interaction) - 是一个研究人类和机器人交互的领域,比如人类如何感受 机器人的不同形式和不同行为,或是机器人如何明白人类暗示来社交,而不是尴尬的互动。\n正如上集说的,我们有追求。把机器人的外表和行为,做得尽可能像人一样。\n工程师在 1940 1950 年代刚开始做机器人时,看起来完全不像人,是完完全全的工业机器。随着时间的推移,工程师越来越擅长做类人机器人,它们有头,而且用两条腿走路,但它们做不到伪装成人类去餐馆点餐。随着机器人可以做得越来越像人类,用人造眼球代替摄像头,用人工肌肉盖住金属骨架,事情会开始变得有些奇怪,引起一种怪异不安的感觉,这个\u0026quot;几乎像人类\u0026quot;和\u0026quot;真的人类\u0026quot;之间的小曲线,叫 “恐怖谷”。\n对于机器人是否应该有人类一样的行为,也存在争议。很多证据表明,即使机器人的行为不像人类,人类也会用社交习俗对待它们,而当机器人违反习俗时 - 比如插队或踩了脚不道歉 人们会很生气!毫无疑问,心理学+计算机科学是强大的组合,可以影响日常生活的巨大潜力,这也带来了很多开放式问题,比如你可能会对计算机撒谎,但计算机应不应该对你撒谎?如果撒谎能让你更高效更快乐呢?或社交媒体公司 是否应该精心挑选展示给你的内容,让你在网站上多待一会儿,买更多东西?\n顺带一说,他们的确有这样做!!!\n这类道德问题不容易回答,但心理学至少可以帮助我们理解不同选择 带来的影响和意义。但从积极的方面来说,了解设计背后的心理学,能增加易用性,让更多人可以明白和使用电脑,如今计算机比以往更加直观,线上会议和虚拟教室的体验越来越好。随着机器人技术不断提高,互动也会越来越舒适。另外,感谢心理学,让我们能分享对\u0026quot;滚花\u0026quot;的热爱。\n我们下周见。\n39. 教育科技 i.e. educational technology\n(。・∀・)ノ゙嗨,我是 carrie anne ,欢迎收看计算机科学速成课!\\n\n计算机带来的最大改变之一 , 是信息的创造和传播能力。目前有 13 亿个网站在互联网上,仅维基百科就有 500 万篇英文文章,涵盖从\u0026quot;1518 年的舞蹈瘟疫\u0026quot;,到\u0026quot;正确的纸卷方向\u0026quot;。每天,google 提供 40 亿次搜索来访问这些信息,youtube 上每分钟有 350 万个视频被观看,每分钟用户上传 400 小时的新视频,很多观看量都是 gangnam style 和 despacito,但剩下的 大部分是教育型内容,就像你现在看的这个。\n如今只要手机上点几下 就能访问到这些宝藏,任何时间,任何地点,但能获取到信息和学习不是一回事。\n先说清楚,我们 crash course 喜欢互动式课堂学习,课上提问,以及上手实践,它们是很棒的学习途径,但我们也相信教育型技术在课内课外带来的帮助。今天我们要在这个教育型视频里 聊教育型科技,具体讲解计算机怎么帮助我们学习。从纸和笔 到用机器学习的智能系统,科技几千年来一直在辅助教育,甚至早期人类 在洞穴里画狩猎场景也是为了后代。远距离教育一直推动着教育科技的发展,例如公元 50 年左右,圣保罗就发书信 给亚洲设立的新教堂提供宗教课程,从那以后,有几大技术浪潮,自称要改变教育,从广播和电视,到 dvd 和光碟。事实上,在 1913 年 托马斯·爱迪生 预测说,“书籍很快会过时,用影片来教授所有知识是可能的,学校体系将在未来十年彻底改变”。当然,他的预测没有成真,但发布教育视频变得越来越流行。在讨论教育技术可以帮你做什么之前,有研究表明 - 有些简单事情 ,可以显著提高学习效率:\n把速度调整到适合你,youtube 的速度设置在右下角,让你能理解视频 有足够的时间思考; 暂停!在困难的部分暂停,问自己一些问题,看能不能回答,或想想视频接下来可能讲什么 然后继续播放,看猜对没有; 做视频中的提供的练习。 即使不是程序员,你也可以在纸上写伪代码,或试试学编程,这些主动学习的技巧已被证明 ,可以把学习效率提升 10 倍或以上。如果想学学习技巧,有整个系列专门讲这个。\n把高质量教育内容做成视频传播 ,在过去一个世纪吸引了很多人,这个老想法的新化身,以\u0026quot;大型开放式在线课程\u0026quot;(mooc,massive open online courses)的形式出现,纽约时报宣称 2012 年是 mooc 年!很多早期视频 直接录制著名教授上课,有段时间,有些人以为大学要终结了。不管你是担心还是开心,这暂时还没成为现实,现在热度也淡去了,这可能是因为加大规模时, 同时教百万名学生,但老师数量很少,甚至完全没有老师 - 会遇到很多问题。幸运的是,这引起了计算机科学家,或具体一点 “教育科技家\u0026quot;的兴趣,他们在想办法解决这些问题,比如,为了有效学习,学生要及时获得反馈。但如果有几百万学生,只有一名老师,怎么提供好的反馈?一个老师怎么给一百万份作业打成绩?为了解决问题,很多时候需要把科技和人类都用上,一种有用 但有些争议的做法是学生互相之间提供反馈。不幸的是,学生一般做不好,他们既不是专家也不是老师,但我们可以用技术来帮助他们,比如通过算法,从数百万个选择里 匹配出最完美的学习伙伴,另外,有些部分可以机器打分,剩下的让人类打分,例如,给 sat 写作部分打分的电脑算法,已被证实和人工打分一样准确,还有些算法提供个性化学习体验,类似于 netflix 的电影推荐 或 google 的个性化搜索结果,为了个性化推荐,软件需要了解用户知道什么,不知道什么,在正确的时间提供正确的资料。\n让用户练习没理解的难的部分,而不是给出用户已经学会的内容,这种系统一般用 ai 实现,泛称叫法是\u0026quot;智能辅导系统”。我们现在讲一个假想的辅导系统,假设学生在这个假想的辅导系统中,研究一个代数问题,正确的下一步是两边-7,我们可以用 “判断规则” 来表示这一步,用 if-then 语句来描述,伪代码是:\n*如果* 变量和常数在同一边\r*那么* 两侧都减去这个常数\r\u0026#34;判断规则\u0026#34; 酷的地方是也可以用来代表学生的常犯错误\r这些\u0026#34;判断规则\u0026#34;叫\u0026#34;错误规则\u0026#34;\r例如,学生可能不去减常数,而是去减系数\r这不行! 学生做完一个步骤后可能触发多个\u0026quot;判断规则\u0026quot;,系统不能完全弄清 是什么原因让学生选了那个答案,所以\u0026quot;判断规则\u0026quot;会和算法结合使用,判断可能原因,让学生得到有用反馈。\n“判断规则”+选择算法,组合在一起成为 “域模型”,它给知识,解决步骤和一门学科 ,比如代数,用一种\u0026quot;正式写法\u0026quot;来表示。域模型可以用来 帮助学习者解决特定问题,但它无法带着学习者 以正确顺序搞定整个学科该上的所有课程,因为域模型不记录进度,因此智能辅导系统 负责创建和维护学生模型 - 记录学生已经掌握的判断规则,以及还需练习的生疏部分,这正是个性化辅导系统需要的。\n听起来好像不难,但只靠学生对一些问题的回答,来弄清学生知道什么,不知道什么,是很大的挑战。“贝叶斯知识追踪” 常用来解决这个问题,这个算法把学生的知识 当成一组隐藏变量,这些变量的值,对外部是不可见的,比如我们的软件。\n这在现实中也是一样的,老师无法知道 学生是否完全掌握了某个知识点,老师会出考题,测试学生能否答对,同样,“贝叶斯知识追踪” 会看学生答题的正确度,更新学生掌握程度的估算值,它会记录四个概率:首先是 “学生已经学会的概率”,比如从代数方程的两边减去常数,假设学生正确将两边-7,做对了,我们可以假设她知道怎么做,但也有可能她是瞎蒙的,没有真的学会怎么解决问题,这叫 “瞎猜的概率”。类似的,如果学生答错了,你可能会假设她不会做,但她可能知道答案,只是不小心犯了个错,这叫 “失误的概率”。最后一个概率,是学生一开始不会做,但是在解决问题的过程中,学会了怎么做,这叫 “做题过程中学会的概率”。有一组方程,会用这四个概率,更新学生模型,对学生应该学会的每项技能进行持续评估。\n第一个等式问:学生已经知道某技能的概率是多少?等式里有 “之前已经学会的概率\u0026quot;和\u0026quot;做题过程中学会的概率”,就像老师一样,“之前已经学会的概率”,取决于学生回答问题正确与否,回答正确和错误分别有 2 个公式,算出结果之后,我们把结果放到第一个方程,更新\u0026quot;之前已经学会的概率\u0026quot;,然后存到学生模型里。\n虽然存在其他方法,但\u0026quot;智能辅导系统\u0026quot;通常用 贝叶斯知识追踪,让学生练习技能,直到掌握。为了高效做到这点,软件要选择合适的问题呈现给学生,让学生学,这叫:自适应式程序,个性化算法的形式之一,但我们的例子只是一个学生的数据。\n现在有 app 或网站,让教师和研究人员 收集上百万学习者的数据,从数据中可以发现常见错误一般哪里难倒学生,除了学生的回答,还可以看回答前暂停了多久,哪个部分加速视频,以及学生如何在论坛和其他人互动,这个领域叫 “教育数据挖掘”,它能用上学生所有的\u0026quot;捂脸\u0026quot;和\u0026quot;啊哈\u0026quot;时刻,帮助改善未来的个性化学习。\n谈到未来,教育技术人员经常从科幻小说中获得灵感,具体来说,neal stephenson 的\u0026quot;钻石时代\u0026quot;这本书激励了很多研究人员,里面说一个年轻女孩从书中学习,书中有一些虚拟助手会和她互动,教她知识,这些助手和她一起成长,直到她学会了什么,以及感觉如何,给她正确的反馈和支持,帮助她学习。如今 有非科幻小说研究者,比如 贾斯汀卡塞尔,在制作虚拟教学助手,助手可以\u0026quot;像人类一样沟通, 有人类一样的行为,在陪伴过程中和学习者建立信任,相处融洽,甚至和人类学生成为朋友\u0026quot;。\n2040 年的\u0026quot;速成课\u0026quot; 可能会有一个 john green ai,活在你的 iphone 30 上,教育科技和设备如今在逐渐扩展到笔记本和台式电脑之外,比如巨大桌面设备,让学生可以团队合作,以及小型移动设备,让学生路上也能学习。\n“虚拟现实\u0026quot;和\u0026quot;增强现实\u0026quot;也让人们兴奋不已,它们可以为学习者提供全新的体验 - 深潜海洋,探索太空,漫游人体,或是和现实中难以遇见的生物互动。如果猜想遥远的未来,教育可能会完全消失,直接在大脑层面进行,把新技能直接下载到大脑,这看起来可能很遥远,但科学家们已经在摸索 - 比如,仅仅通过检测大脑信号,得知某人是否知道什么。\n这带来了一个有趣的问题:如果我们可以把东西下载到大脑里,我们能不能上传大脑里的东西?\n下周的最后一集,我们会讨论计算的未来。\n到时见。\n40. 奇点,天网,计算机的未来 i.e. the singularity, skynet, and the future of computing\n(。・∀・)ノ゙嗨,我是 carrie anne 欢迎收看计算机科学速成课!\n我们到了 最后一集!\n如果你看了整个系列,希望你对计算机影响的深度和广度 有全新的认知和欣赏。难以相信 我们从简单的晶体管和逻辑门开始,一直到计算机视觉,机器学习,机器人以及更多。\n我们站在巨人的肩膀上 - charles 、babbage 、ada 、lovelac 、herman 、hollerith 、alan turing 、j. presper eckert、 grace hopper 、 ivan sutherland douglas 、engelbart 、vannevar 、bush (memex) 、berners-lee (万维网) 、bill gates (微软)、steve wozniak (苹果),和许多其他先驱。\n我最大的希望是 这些视频能激励你 去了解这些东西如何影响你的人生,甚至开始学编程,或找一份计算机职业,这很棒!这是未来的技能!\n我在第一集说过,计算机科学不是魔法但它有点像魔法,学习使用电脑和编程,是 21 世纪的巫术,只不过用的不是咒语 ,而是代码。懂得运用的人,能创造出伟大的东西,不仅改善自己的生活,还有当地社区乃至整体人类。计算机会随处可见 - 不仅是放在桌上 ,带在包里,而是在所有可想象的东西里 - 厨房用具里,墙里,食物里,编织进衣服里,在你的血液里,这是\u0026quot;普适计算\u0026quot;的愿景。\n从某种角度来讲, 它已经来临了而换一个角度 , 还要几十年。有些人把这种未来看成 反乌托邦,到处都有监视器,有无数东西想吸引我们的注意力,但 1990 年代提出这个想法的 马克·维泽尔,看到了非常不同的潜力:\u0026quot;[五十] 年来,大多数界面和计算机设计,都是朝\u0026quot;戏剧性\u0026quot;方向前进,想把计算机做得超好,让人一刻也不想离开。另一条少有人走的路 是\u0026quot;无形\u0026quot;的,把计算机整合到所有东西里 ,用的时候很自然完全注意不到。最厉害的科技是看不见的科技,它们融入到日常生活的每一部分 ,直到无法区分”。\n如今我们还没达到这样 - 人们在电脑前连续坐好几小时,吃晚餐被手机推送通知打扰,但它可以描述计算的未来本系列最后一个主题,人们思考计算机的未来时,经常会直接想到人工智能。毫无疑问,接下来几十年人工智能会有巨大进步,但不是所有东西都要做成 ai ,或需要 ai,车有自动驾驶 ai,但门锁依然会很简单。人工智能可能只是增强现有设备,比如汽车,ai 带来了一个全新的产品种类,刚出现电力时也是这样,灯泡取代了蜡烛。但电气化也导致上百种新的电动小工具诞生。当然 我们如今仍然有蜡烛。最可能的情况是 ai 变成 计算机科学家手中的另一门新工具,但真正让人深思和担忧的是,人工智能是否会超越人类智能?\n这个问题很难 有多方面原因,比如 “智能的准确定义是什么?” 一方面,有会开车的计算机,几秒就能识别歌的 app 。翻译几十种语言,还称霸了一些游戏,比如象棋,知识竞答和围棋,听起来很聪明!但另一方面,计算机连一些简单事情都做不了,比如走楼梯,叠衣服,在鸡尾酒派对和人聊天,喂饱自己。人工智能成长到和人类一样通用,还有很长的路,因为\u0026quot;智能\u0026quot;是难以量化的指标,人们更喜欢用处理能力来区分,但这种衡量智能的方法比较\u0026quot;以计算为中心\u0026quot;,但如果把视频中出现过的电脑和处理器 画张图,可以看到 如今的计算能力粗略等同于一只老鼠。公平点说,老鼠也不会叠衣服,但如果真的会叠 ,就太可爱了。\n人类的计算能力在这儿,多 10 的 5 次方,也就是比如今电脑强 10 万倍。听起来差距很大,但按如今的发展速度,也许十几年就可以赶上了。虽然现在处理器的速度不再按摩尔定律增长了,我们在第 17 集讨论过,假设趋势继续保持下去,在本世纪结束前,计算机的处理能力/智能 会比全人类加起来还多。然后人的参与会越来越少,人工超级智能会开始改造自己,智能科技的失控性发展叫 “奇点”。\n第 10 集 约翰·冯·诺伊曼 最早用这个词,他说:“越来越快的技术发展速度和人类生活方式的改变,看起来会接近人类历史中某些重要的奇点,这个势头不会永远继续下去”。冯诺依曼在 1950 年代说的这话,那时计算机比现在慢得多,六十年后的今天,奇点仍然在遥远的地平线上。一些专家认为 发展趋势会更平缓一些,更像是 s 型,而不是指数型,而随着复杂度增加,进步会越来越难。微软联合创始人 保罗·艾伦 叫这个\u0026quot;复杂度刹车\u0026quot;,但当作思维练习,我们假设 超智能计算机会出现。这对人类意味着什么,是个讨论激烈的话题,有些人迫不及待,有些人则努力阻止它,最直接的影响可能是\u0026quot;技术性失业\u0026quot;。很多工作被计算机,比如 ai 和机器人,给代替掉了,它们的效率更高,成本更低。虽然计算机出现没多久,但\u0026quot;技术性失业\u0026quot;不是新事,还记得第 10 集里 雅卡尔的织布机 吗?它让 1800 年代的纺织工人失业,导致了骚乱,当时美国和欧洲 大部分人都是农民,如今农民占人口比例 \u0026lt;5%,因为有合成肥料和拖拉机等等技术。时间更近一些的例子是\u0026quot;电话接线员\u0026quot;,在 1960 年被自动接线板代替了,还有 1980 年代的\u0026quot;机器喷漆臂\u0026quot;替代了人工喷漆,这样的例子还有很多。一方面,因为自动化失去了工作,另一方面,我们有大量产品,衣服,食物,自行车,玩具等,因为可以廉价生产,但专家认为人工智能,机器人 以及更广义的计算,比之前更有破坏性。\n工作可以用两个维度概括,首先,手工型工作,比如组装玩具,或思维型工作 - 比如选股票,还有重复性工作,一遍遍做相同的事,或非重复性,需要创造性的解决问题。我们知道 重复性手工工作,可以让机器自动化,现在有些已经替代了,剩下的在逐渐替代。让人担心的是\u0026quot;非重复性手工型工作\u0026quot;,比如厨师,服务员,保安。思维型工作也一样,比如客服,收银员,银行柜员和办公室助理。剩下一个暂时比较安全的象限,非重复性思维型工作,包括教师和艺术家,小说家和律师,医生和科学家。这类工作占美国劳动力大概 40% ,意味着剩下 60%工作容易受自动化影响。有人认为这种规模的技术失业是前所未有的,会导致灾难性的后果,大部分人会失业,其他人则认为很好,让人们从无聊工作解脱,去做更好的工作,同时享受更高生活水平,有更多食物和物品,都是计算机和机器人生产的。\n没人知道未来到底会怎样,但如果历史有指导意义,长远看 一切会归于平静。毕竟,现在没人嚷嚷着让 90%的人 回归耕田和纺织,政界在讨论的棘手问题是怎么处理数百万人突然失业 造成的短期经济混乱。\n除了工作,计算机很可能会改变我们的身体,举个例子,未来学家 ray kurzweil 认为,“奇点会让我们超越 肉体和大脑的局限性,我们能掌控自己的命运,可以想活多久活多久 。 我们能完全理解并扩展大脑思维,超人类主义者认为会出现\u0026quot;改造人”,人类和科技融合在一起,增强智力和身体。如今已经有脑电接口了,而 google glass 和 微软 hololens 这样的穿戴式计算机 也在模糊这条界线,也有人预见到\u0026quot;数字永生\u0026quot;。jaron lanier 的说法是,“人类的肉体死去,意识上传到计算机”,从生物体变成数字体 可能是下一次进化跨越。\n一层新的抽象!\n其他人则预测,人类大体会保持原样,但超智能电脑会照顾我们,帮我们管农场,治病,指挥机器人收垃圾,建房子以及很多其他事情,让我们在这个可爱蓝点上(地球)好好享受。另一些人对 ai 持怀疑态度 - 为什么超级人工智能 会费时间照顾我们?人类不也没照顾蚂蚁吗?也许会像许多科幻电影一样,和计算机开战。我们无法知道未来到底会怎样,但现在已经有相关讨论了,这非常好。所以等这些技术出现后,我们可以更好地计划,不论你把计算机视为未来的朋友或敌人,更有可能的是,它们的存在时间会超过人类。许多未来学家和科幻作家猜测,机器人会去太空殖民,无视时间,辐射 以及一些其他让人类难以长时间太空旅行的因素。亿万年后太阳燃尽 ,地球成为星尘 ,也许我们的机器人孩子会继续努力探索宇宙每一个角落,以纪念它们的父母,同时让宇宙变得更好,大胆探索无人深空。\n与此同时,计算机还有很长的路要走,计算机科学家们在努力推进过去 40 集谈到的话题。在接下来的十几年,vr 和 ar,无人驾驶车,无人机,可穿戴计算机,和服务型机器人 会变得主流。互联网会继续诞生新服务,在线看新媒体,用新方式连接人们,会出现新的编程语言和范例,帮助创造令人惊叹的新软件,而新硬件能让复杂运算快如闪电 ,比如神经网络和 3d 图形。个人电脑也会创新,不像过去 40 年着重宣传 “桌面” 电脑,而是变成无处不在的虚拟助手。\n这个系列,我们还有很多话题没谈,比如加密货币,无线通讯,3d 打印,生物信息学和量子计算,我们正处于计算机的黄金时代,有很多事情在发生,全部总结是不可能的,但最重要的是 你可以学习计算机 成为这个惊人转型的一部分,把世界变得更好!\n感谢收看!\n结语 真的好长,也很有趣。知其先后,以预未来。\n","date":"2023-05-26","permalink":"https://aituyaa.com/cscc/16-cscc-%E5%85%B6%E4%BB%962/","summary":"\u003cblockquote\u003e\n\u003cp\u003e好吧,内容不少,为了后续插入图片之后,页面太大,我们这里拆分到两个页面中。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"16 cscc 其他2"},]
[{"content":"感谢小伙伴 whatacold 的创意 a bookmarklet for copying a link as an org-mode link - whatacold’s space - whatacold.io ,可以方便地复制当前页面的地址并格式化 .md 链接格式,当然,也可以格式化成 .org 链接格式。\n* 配置链接类型 在源码中的 copy.js 中,2 ~ 10 行,你可以看到如下代码片段。\n1 2 3 4 5 6 7 8 9 let md = `[${(document.title || document.location.hostname)}](${document.location.href})` let org = `[[${document.location.href}][${(document.title || document.location.hostname)}]]` // ----------------------------------------------------------- // 🛠️ 链接类型设置 // org - org mode link, eg. [[https://ovirgo.com/ship/][ship]] // md - markdown link, eg. [ship](https://ovirgo.com/ship/) let type = md // ----------------------------------------------------------- 这里,我们提供了 .md 和 .org 两种文件的链接格式,并通过 type 来设置它。此处,默认 type 为 md ,如果,你经常编辑的是 .org 文件,只需要把 type 设为 org 即可。\n为什么我们这里不提供一个直接在插件设置中的配置选项?\n首先,通常情况下,频繁地切换编辑 .org 和 .md 文件的场景很少,它们有各自的粘性用户;其次,尽可能的减少操作步骤,不想赋予插件图标过多的功能交互;最后,因为是本地导入,你完全可以拷贝一份文件夹,分别配置,导入成两个插件。\n快速使用 浏览器插件方式 loveminimal/copy-link: a browser extension to copy current url as a markdown link etc.\n![[assets/pasted image 20230526112431.png|525]]\n下载 copy link 📥 到本地,解压缩后,打开浏览器的‘扩展程序’,打开开发者模式,加载已解压的扩展程序,选择你解压后的文件夹,即可添加插件到浏览器。\n点击图标,或使用 ctrl + b 即可使用该功能。\n标签方式 在浏览器的‘书签管理器’中添加新书签,内容如下图所示:\n![[assets/pasted image 20230526112447.png]]\n以下为压缩并添加前缀之后的代码,复制粘贴至上图剪头处即可。\njavascript:function copy(){const e=document.queryselector(\u0026#34;#btn\u0026#34;),t=document.queryselector(\u0026#34;#ipt\u0026#34;),o=document.queryselector(\u0026#34;#cont\u0026#34;);t.select(),document.execcommand(\u0026#34;copy\u0026#34;)?(document.execcommand(\u0026#34;copy\u0026#34;),console.log(\u0026#34;copy success\u0026#34;),e.innertext=\u0026#34;🎉 success\u0026#34;,e.style.background=\u0026#34;#67c23a\u0026#34;,e.style.color=\u0026#34;#fff\u0026#34;):(console.log(\u0026#34;copy failed\u0026#34;),e.innertext=\u0026#34;❌ faild\u0026#34;,e.style.background=\u0026#34;#f56c6c\u0026#34;,e.style.color=\u0026#34;#fff\u0026#34;),settimeout(()=\u0026gt;{document.body.removechild(o)},1500)}!function(){let e=\u0026#34;[\u0026#34;+(document.title||document.location.hostname)+\u0026#34;](\u0026#34;+document.location.href+\u0026#34;)\u0026#34;,t=document.createelement(\u0026#34;div\u0026#34;);t.id=\u0026#34;btn\u0026#34;,t.innertext=\u0026#34;🥳 copy\u0026#34;,t.style=\u0026#34;background: #e6a23c; box-shadow: 1px 1px 3px #333; width: 120px; height: 40px; text-align: center; line-height: 40px; border-radius: 4px; color: #333; cursor: pointer; font-weight: 700; font-family: segoe script, courier new;font-size: 16px;\u0026#34;,t.addeventlistener(\u0026#34;click\u0026#34;,copy);let o=document.createelement(\u0026#34;input\u0026#34;);o.value=e,o.id=\u0026#34;ipt\u0026#34;,o.style=\u0026#34;opacity: 0;\u0026#34;,o.select();let c=document.createelement(\u0026#34;div\u0026#34;);c.id=\u0026#34;cont\u0026#34;,c.style=\u0026#34;position: fixed; right: 32px; top: 16px; width: 100px;z-index: 10000;\u0026#34;,c.appendchild(t),c.appendchild(o),document.body.appendchild(c)}(); 完成后,点击标签后,会在当前网页左上角弹出复制按钮;\n![[assets/pasted image 20230526112510.png]] ![[assets/pasted image 20230526112519.png]] ![[assets/pasted image 20230526112527.png]]\n点击复制后,按钮会更新状态及样式,并在 1.5s 自动消失。\n当然,也可能失败,但希望你永远都不会看到它。\n源码解析 请访问 copy-link/copy.js at master · loveminimal/copy-link/copy.js 。\n📌 结语\njust for fun 🎉\n","date":"2023-05-26","permalink":"https://aituyaa.com/a-simple-bookmark-copying/","summary":"\u003cp\u003e感谢小伙伴 Whatacold 的创意 \u003ca href=\"https://whatacold.io/blog/2022-08-08-org-link-bookmarklet/\"\u003eA Bookmarklet for Copying a Link as an Org-mode Link - whatacold’s space - whatacold.io\u003c/a\u003e ,可以方便地复制当前页面的地址并格式化 \u003ccode\u003e.md\u003c/code\u003e 链接格式,当然,也可以格式化成 \u003ccode\u003e.org\u003c/code\u003e 链接格式。\u003c/p\u003e","title":"a simple bookmark copying"},]
[{"content":" :: 好的吧,我承认,对于 c 语言,我有一种莫名其妙的偏爱!是因为可以直接操作内存吗?或许是!可能吧,计算机有什么神奇的东西呢?最终就是围绕那几个概念在来回打转!\n编译器 1\n:: 计算机说:“无论你怎么写,怎么编,怎么译,最终都要爷能看得懂才行!😈”\n什么是编译器 可执行程序(executable program) 的内部是一系列计算机指令和数据的集合,它们是二进制形式的,cpu 可以直接识别,但对于程序员来说几乎不具备可读性。\n比如,在屏幕上输出“vip 会员”,c 语言的写法为: puts(\u0026quot;vip 会员\u0026quot;); ,但其二进制写法如下:\n![[assets/pasted image 20230526091303.png]]\n直接使用二进制指令编程对程序员来说简直是噩梦!\n于是,编程语言就诞生了。比如,c 语言代码由固定的词汇按照固定的格式组织起来,简单直观,程序员容易识别和理解,但是对于 cpu 来说,c 语言代码就是天书(cpu 只认识几百个二进制形式的指令)!\n:: 💻:“不好意思,爷只认二进制!”\n这就需要一个工具,将 c 语言代码转换成 cpu 能够识别的二进制指令(即可执行程序),这个工具是一个特殊的软件,叫做 编译器(compiler) 。\n用来保存代码文件叫做 源文件 ,它就一个纯文本文件,内部并没有特殊格式(其后缀仅仅是为了表明文件中保存的是某种语言的代码,易于程序员区分和编译器识别)。\n:: 就一纯文本文件 📝\n在实际开发中,程序员将代码分门别类地放到多个源文件中。\nide(集成开发工具) 会为每一个程序都创建一个专门的目录,将用到的所有文件都集中到这个目录下进行管理。 不同的程序对应不同的项目类型(i.e. 工程类型) ,不同的工程类型本质上是对 ide 各个参数的不同设置 。当然,我们也可以创建一个空白的工程类型,然后自己去设置各种参数(不过一般没有会这样做)。\n源代码要经过编译(compile)和链接(link)两个过程才能变成可执行文件。\n编译器一次只能编译一个源文件(如果当前程序包含了多个源文件,那么就需要编译多次),编译器每次编译的结果是产生一个中间文件(不是最终的可执行文件,但已经非常接近可执行文件了,它们都是二进制格式,内部结构也非常相似)。\n将当前程序的所有中间文件以及系统库组合在一起,才能形成最终的可执行文件,这个组合的过程就叫做 链接(link) ,完成链接功能的软件叫做链接器(linker)。\n不管有多少个源文件(哪怕只有一个),都必须经过编译和链接两个过程才能生成可执行文件。(为什么呢?因为你至少还需要和系统库组合。)\n综上可以发现,一个完整的编程过程是:\n编写源文件(保证代码语法正确,否则编译不通过); 预处理(processing); 编译(compile 将源文件转换为目标文件); 汇编(assembly); 链接(linking 将目标文件和系统库组合在一起,转换为可执行文件); 运行(可以检验代码的正确性)。 默认情况下, gcc 指令会直接将源代码转变为可执行代码(2-5 四个过程),且不会保留各个阶段产生的中间文件。\ngcc 是什么 gcc 编译器是 linux 系统下最常用的 c/c++ 编译器,大部分 linux 发行版中都会默认安装。\n早期的 gcc 全拼为 gnu c compiler ,最初定位确实只用于编译 c 语言。经过不断迭代扩展,gcc 现在还可以处理 c++、go、ojbect-c 等多种编译语言编写的程序,故其全称被重新定义为 gnu compiler collection,即 gnu 编译器套件。\n👉 更多 gcc 和 clang / llvm 的区别\n可以通过 gcc --help 查看其常用指令选项如下:\n--version display compiler version information.\r-std=\u0026lt;standard\u0026gt; assume that the input sources are for \u0026lt;standard\u0026gt;.\r-e preprocess only; do not compile, assemble or link.\r-s compile only; do not assemble or link.\r-c compile and assemble, but do not link.\r-o \u0026lt;file\u0026gt; place the output into \u0026lt;file\u0026gt;.\r-pie create a dynamically linked position independent\rexecutable.\r-shared create a shared library.\r-x \u0026lt;language\u0026gt; specify the language of the following input files.\rpermissible languages include: c c++ assembler none\r\u0026#39;none\u0026#39; means revert to the default behavior of\rguessing the language based on the file\u0026#39;s extension. 前面说过 gcc 是支持编译多种编程语言的,可以通过 -x 选项指定要编译的语言类型,如 gcc -xc++ xxx 表示以编译 c++ 代码的方式编译 xxx 文件。\n使用 gcc 编译器编译 c 或者 c++程序,必须经历 4 个过程: 预处理 → 编译 → 汇编 → 链接 (通常 gcc/g++ 支持该过程的自动化)。\ng++ 是什么?可以认为 g++ →(等价于) gcc -xc++ -lstdc++ -shared-libgcc (因为 gcc 不会自动引入 c++ 相关的库,必须手动引入)。\n用于手动指定链接环节中程序可以调用的库文件,如 -lstdc++ ,不建议 -l 和 library 之间有空格。\n默认情况下, gcc 指令会一气呵成将源代码历经这 4 个过程转变为可执行代码,且不会保留各个阶段产生的中间文件。\n如果我们想查看这 4 个阶段各自产生的中间文件,该怎么办呢?最简单直接的方式就是对源代码进行“分步编译”。即控制 gcc 编译器逐步对源代码进行预处理、编译、汇编及链接操作。\ngcc/g++ 指令选项 功能 -e 预处理指定的源文件,不进行编译 -s 编译指定的源文件,不进行汇编 -c 编译、汇编指定的源文件,但是不进行链接 -o 指定生成文件的文件名 -llibrary 用于手动指定链接环节中程序可以调用的库文件,如 -lstdc++ ,不建议 -l 和 library 之间有空格 -ansi 对于 c 语言程序来说,其等价于 -std=c90;对于 c++ 程序来说,其等价于 -std=c++98 -std= 手动指令编程语言所遵循的标准 \u0026gt; gcc 常用的编译选项\n假如我们编写了一个 source.c 的源程序,如下:\n1 2 3 4 5 6 #include \u0026lt;stdio.h\u0026gt; int main() { printf(\u0026#34;c program.\\n\u0026#34;); return 0; } 1. 预处理 - 生成预处理文件 *.i\n通过为 gcc 指令添加 -e 选项,即可控制 gcc 编译器仅对源代码做预处理操作。默认情况下, gcc -e 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件,因此,该指令往往会和 -o 选项连胜,将结果导入到指定文件中。\n1 2 3 4 5 6 7 8 9 10 jack@jk:~/cemo/cporj$ gcc -e source.c -o source.i jack@jk:~/cemo/cporj$ ls source.c source.i jack@jk:~/cemo/cporj$ cat source.i # 1 \u0026#34;source.c\u0026#34; # 1 \u0026#34;\u0026lt;built-in\u0026gt;\u0026#34; # 1 \u0026#34;\u0026lt;command-line\u0026gt;\u0026#34; # 31 \u0026#34;\u0026lt;command-line\u0026gt;\u0026#34; # 1 \u0026#34;/usr/include/stdc-predef.h\u0026#34; 1 3 4 # .... linux 系统中通常用 \u0026quot;.i\u0026quot; 作为 c 语言程序预处理后所得文件的后缀名。显然, source.i 中的内容不是那么容易看懂的,好在可以为 gcc 指令再添加一个 -c 选项,来阻止 gcc 删除源文件和头文件中的注释,即 gcc -e -c source.c -o source.i 。\n2. 编译 - 生成汇编文件 *.s\n1 2 3 4 5 6 7 8 9 10 jack@jk:~/cemo/cporj$ gcc -s source.i jack@jk:~/cemo/cporj$ ls source.c source.i source.s jack@jk:~/cemo/cporj$ cat source.s .file \u0026#34;source.c\u0026#34; .text .section .rodata .lc0: .string \u0026#34;c program.\u0026#34; .... 通过执行 gcc -s 指令,生成了个名为 source.s 的文件,这就是经过编译的汇编代码文件。(默认情况下,编译操作会自行新建一个文件名和指定文件相同、后缀名为 .s 的文件,并将编译的结果保存在该文件中。)\n同样,想要提高文件内汇编代码的可读性,可以借助 -fverbose-asm 选项,gcc 编译器会自动为汇编代码添加必要的注释,即 gcc -s source.i -fverbose-asm 。\n3. 汇编 - 生成目标文件 *.o\n1 2 3 jack@jk:~/cemo/cporj$ gcc -c source.s jack@jk:~/cemo/cporj$ ls source.c source.i source.o source.s 上面生成的 source.o 文件就是目标文件,其本质为二进制文件(但尚未经过链接操作,所以无法直接运行)。\n4. 链接 - 生成可执行文件\ngcc 通过 -o 选项来指定输出文件,缺省默认输出 a.out ,其语法格式如下:\ngcc [-e|-s|-c] [infile] [-o outfile] 通过 -l 选项手动添加链接库\n链接器把多个二进制的目标文件(object file)链接成一个单独的可执行文件。在链接过程中,它必须把符号(变量名、函数名等一些列标识符)用对应的数据的内存地址(变量地址、函数地址等)替代,以完成程序中多个模块的外部引用。\n而且,链接器也必须将程序中所用到的所有 c 标准库函数加入其中。对于链接器来说,链接库不过是一个具在许多目标文件的集合,它们放在一个文件中以方便处理。\n标准库的大部分函数通常放在文件 libc.a 中(文件名后缀 .a 代表 achieve 读取),或者放在共享的动态链接文件 libc.so 中(文件名后缀 .so 代表 share object 共享对象)。\n如,通过 gcc source.c -o source.out -lm 链接数学库 libm.a ,前缀 lib 和后缀 .a 是标准的, m 是基本名称。(gcc 会在 -l 选项后紧跟着的基本名称的基础上自动添加这些前缀、后缀)\ngcc 使用静态链接库和动态链接库\n库文件的产生,极大的提高了程序员的开发效率,因为很多功能根本不需要从 0 开发,直接调取包含该功能的库文件即可。并且,库文件的调用方法也很简单,以 c 语言中的 printf() 输出函数为例,程序中只需引入 \u0026lt;stdio.h\u0026gt; 头文件,即可调用 printf() 函数。\n调用库文件为什么还要牵扯到头文件呢?\n头文件和库文件并不是一码事,它们最大的区别在于:\n头文件只存储变量、函数或者类等这些功能模块的声明部分,库文件才负责存储各模块具体的实现部分; 所有的库文件都提供有相应的头文件作为调用它的接口,即库文件是无法直接使用的,只能通过头文件间接调用。 头文件和库文件相结合的访问机制,最大的好处在于,有时候我们只想让别人使用自己实现的功能,并不想公开实现功能的源码,就可以将其制作为库文件,这样用户获取到的是二进制文件,而头文件又只包含声明部分,这样就实现了“将源码隐藏起来”的目的,且不会影响用户使用。\n:: 其实,就是一种封装。\n事实上,库文件只是一个统称,代指的是一类压缩包,它们都包含有功能实用的目标文件。要知道,虽然库文件用于程序的链接阶段,但编译器提供有 2 种实现链接的方式,分别称为静态链接方式和动态链接方式,其中采用静态链接方式实现链接操作的库文件,称为 静态链接库 ;采用动态链接方式实现链接操作的库文件,称为 动态链接库 。\n它们有什么不同呢?\n静态链接库 实现链接操作的方式很简单,即程序文件中哪里用到了库文件中的功能模块,gcc 编译器就会 将该模板代码直接复制到程序文件的适当位置 ,最终生成可执行文件。\n好处是生成的可执行文件不再需要任何静态库文件的支持就可以独立运行(可移植性强),坏处如果程序文件中多次调用库中的同一个模块,则该模块代码会被复制多次(冗余),生成的可执行文件体积更大(与使用动态链接库生成的可执行文件相比)。\n在 linux 发行版中,静态链接库文件的后缀通常用 .a 表示;在 windows 系统中,静态链接库文件的后缀名为 .lib 。\n动态链接库 ,又称为共享链接库。和静态链接库不同,采用动态链接库实现链接操作时,程序文件中哪里需要库文件的功能模块,gcc 编译器不会直接将该功能模块的代码拷贝到文件中,而是 将功能模块的位置信息记录到文件中,直接生成可执行文件。\n显然,这样生成的可执行文件是无法独立运行的。\n采用动态链接库生成的可执行文件运行时,gcc 编译器会将对应的动态链接库一同加载在内存中,由于可执行文件中事先记录了所需功能模块的位置信息,所以在现有动态链接库的支持下,也可以成功运行。\n在 linux 系统中,动态链接库的后缀名通常用 .so 表示;在 windows 系统中,动态链接库的后缀名为 .dll 。\n值得一提的是,gcc 编译器生成可执行文件时,默认情况下会优先使用动态链接库实现链接操作,除非当前系统环境中没有程序文件所需要的动态链接库,gcc 编译器才会选择相应的静态链接库。如果两种都没有(或者 gcc 编译器未找到),则链接失败。\ngdb 调试器 gnu symbolic debugger,简称「gdb 调试器」,是 linux 平台下最常用的一款程序调试器。\n要知道,哪怕是开发经验再丰富的程序员,编写的程序也避免不了出错。程序中的错误主要分为 2 类,分别为:\n语法错误(可以借助编译器解决); 逻辑错误(只能程序员\u0026lt;自己或借助调试工具\u0026gt;调试解决)。 调试是每个程序员必须掌握的基本技能,没有选择的余地!\n所谓调试(debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。\ngdb 就是 linux 下使用最多的一款调试器,也有 windows 的移植版。\n总的来说,借助 gdb 调试器可以实现以下几个功能:\n程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量; 可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(如当前变量的值,函数执行的结果等),即支持断点调试; 程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。 默认情况下,程序不会进行调试模式,代码会瞬间从开关执行到末尾。要想观察程序运行的内部细节,可以借助 gdb 调试器在程序中的某个地方设置断点(breakpoint),如此当程序执行到这个地方时就会停下来。\ngdb 调试器支持在程序中打 3 种断点:\n普通断点(break):指定打断点的具体位置; 观察断点(watch):可以监控程序中某个变量或者表达式的值,只要发生改变,程序就会停止执行; 捕捉断点(catch):监控程序中某一事件的发生。 ……\n:: 具体调试细节,略过……\n数据 关于数据 数据是放在内存中的,变量是给这块内存起的名字,有了变量就可以找到并使用这份数据。\n诸如数字、文字、符号、图形、音频、视频等数据都是以二进制形式存储在内存中的,它们并没有本质上的区别。我们需要用数据类型用来说明数据的类型,确定了 数据的解释方式 ,让计算机和程序员不会产生歧义。另外在 c 语言中,每一种数据类型所占用的字节数都是固定的,知道了数据类型,也就知道了 数据的长度 。\n:: 反正就是一个二进制串,解释权在编译器,反正乱解释肯定出问题。\n数据是放在内存中的,在内存中存取数据要明确三件事情:数据存储在哪里、数据的长度以及数据的处理方式。\n打印输出各种类型的数据\nputs (output string) 只能用来输出字符串; printf (print format)格式化输出,功能强大,不仅可以输出字符串,还可以输出整数、小数、单个字符等,并且输出格式可以自定义。\n数据类型 1. 整数类型 short、 int、 long\nshort(短整型)、int(整型)、long(长整型) 是 c 语言中常见的整数类型。c 语言并没有严格规定 short、int、long 的长度,只做了宽泛的限制:\n2 ≤ short ≤ int ≤ long 其中,int 建议为一个机器字长。32 位环境下机器字长为 4 字节,64 位环境下机器字长为 8 字节。\n获取某个数据类型的长度可以使用 sizeof 操作符,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026lt;stdio.h\u0026gt; int main() { short a = 10; int b = 100; int short_length = sizeof a; int int_length = sizeof(b); int long_length = sizeof(long); int char_length = sizeof(char); printf(\u0026#34;short=%d, int=%d, long=%d, char=%d\\n\u0026#34;, short_length, int_length, long_length, char_length); return 0; } // 在 64 位 linux 下的输出 → short=2, int=4, long=8, char=1 *注意, sizeof 是 c 语言中的操作符,不是函数(故可不带括号)。\n2. 浮点类型 float、 double\n一个数字,是有默认类型的:对于整数,默认是 int 类型;对于小数,默认是 double 类型。\n……\n3. 字符类型 char\n字符类型由单引号 ' ' 包围,字符串由双引号 \u0026quot; \u0026quot; 包围。\n计算机在存储字符时并不是真的要存储字符实体,而是存储该字符在字符集中的编号(也可以叫编码值)。对于 char 类型来说,它实际上存储的就是字符的 ascii 码。\n可以说,是 ascii 码表将英文字符和整数关联了起来。\n无论在哪个字符集中,字符编号都是一个整数;从这个角度考虑,字符类型和整数类型本质上没有什么区别。\n:: 在 c 语言中,并没有单独定义字符串类型,字符串实际上是使用空字符 \\0 结尾的一维字符数组,如 char site[7] = {'r', 'u', 'n', 'o', 'o', 'b', '\\0'}; 。\n4. 构造类型 - 数组\n数组(array)就是一些列具有相同类型的数据的集合,这些数据在内存中依次挨着存放,彼此之间没有缝隙。\n数组的定义方式:\ndatatype arrayname[length];\r// - datatype 为数据类型\r// - arrayname 为数组名称\r// - length 为数组长度\r// 数组中每个元素都有一个索引(下标),从 0 开始,使用元素时指明下标即可:\rarrayname[index]\r// - index 为下标 数组的初始化:\n1 2 3 4 5 6 // 当赋值的元素少于数组总体元素的时候,剩余的元素自动初始化为 0 int arr[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // 给全部元素赋值,那么在定义数组时可以不给出数组长度 int arr[] = {1, 2, 3, 4, 5}; // 等价于 int arr[5] = {1, 2, 3, 4, 5}; 如何获取数组的长度呢? 通过 sizeof arr / sizeof arr[0] 。\n5. 字符数组\n在 c 语言中,没有专门的字符串变量,没有 string 类型,通常就用一个字符数组来存放一个字符串。\n在 c 语言中,字符串总是以 '\\0' 作为结尾,所以 '\\0' 也被称为字符串结束标志,或者字符串结束符。\n'\\0' 是 ascii 码表中的第 0 个字符,英文称为 nul,中文称为“空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在 c 语言中唯一的作用就是作为字符串结束标志。\n由 \u0026quot; \u0026quot; 包围的字符串会自动在末尾添加 '\\0' 。例如,“abc123” 从表面看起来只包含了 6 个字符,其实不然,c 语言会在最后隐式地添加一个 '\\0',这个过程是在后台默默地进行的,所以我们感受不到。\n1 2 3 char str[6] = \u0026#34;abc123\u0026#34;; // ✘ char str[7] = \u0026#34;abc123\u0026#34;; // ✔,别忘记 \u0026#39;\\0\u0026#39;,使用 \u0026#34;xyzbnm..\u0026#34; 赋值会自动在末尾添加 \u0026#39;\\0\u0026#39; char str[7] = { \u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;3\u0026#39;, \u0026#39;\\0\u0026#39; }; // 不嫌烦,你也可以这样 *另外,要注意字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了。如:\n1 2 3 4 5 char str[7]; str = \u0026#34;abc123\u0026#34;; // ✘ //正确 ✔ str[0] = \u0026#39;a\u0026#39;; str[1] = \u0026#39;b\u0026#39;; str[2] = \u0026#39;c\u0026#39;; str[3] = \u0026#39;1\u0026#39;; str[4] = \u0026#39;2\u0026#39;; str[5] = \u0026#39;3\u0026#39;; 所谓 字符串长度 ,就是字符串包含了多少个字符( 不包含最后的结束字符 '\\0' ),如 \u0026quot;abc\u0026quot; 的长度是 3 ,而不是 4。(注意和定义时的数组长度做区分)\n在 c 语言中,我们使用 string.h 头文件中的 strlen() 函数来求字符串的长度,它的用法为:\nlength strlen(strname);\r// - length 字符串长度,一个整数\r// - strname 字符串的名字或字符数组的名字 6. 指针\n所谓指针,也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。\n7. 构造类型 - 结构体\nc 语言结构体(struct)从本质上讲是一种自定义的数据类型,只不过这种数据类型比较复杂,是由 int、char、float 等基本类型组成的。\n结构体定义形式为:\nstruct tag { member-list } variable-list;\r// - tag 结构体名(标签)\r// - member-list 结构体成员(列表)\r// - variable-list 该结构体定义的类型变量 *注意,结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include \u0026lt;stdio.h\u0026gt; int main() { struct student { char *name; // 姓名 int age; // 年龄 float score; // 成绩 } stu1, stu2; // 可以通过 =.= 获取和操作单个结构体成员 stu1.name = \u0026#34;tom\u0026#34;; str1.age = 18; stu1.score = 99.5; printf(\u0026#34;%s 的分数是: %d\\n\u0026#34;, stu1.name, stu1.score); return 0; } 8. 构造类型 - 共用体(联合体)\n联合体定义形式为:\nunion tag { member-list } variable-list;\r// - tag 联合体名(标签)\r// - member-list 联合体成员(列表)\r// - variable-list 该联合体定义的类型变量 结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。\n输入输出 i/o 在控制台程序中,输出一般是指将数据(包括数字、字符等)显示在屏幕上,输入一般是指获取用户在键盘上输入的数据。\n输出 在 c 语言中,有三个函数可以用来在显示器上输出数据:\nputs() - 只能输出字符串,并且输出结束后自动换行; putchar() - 只能输出单个字符; printf() - 格式化输出各种类型的数据。 printf() 格式控制符的完整形式如下:\n%[flag][width][.precision]type\r// - type 表示输出类型\r// - width 表示最小输出宽度(不足时左边以空格补齐)\r// - .precision 表示输出精度,也就是小数的位数;\r// 也可以用于整数和字符串,但是功能相反:\r// - 用于整数时,表示最小输出宽度(不足时左边以 0 补齐)\r// - 用于字符串时,表示最大输出宽度\r// - flag 是标志字符:\r// - - 表示左对齐(默认右对齐)\r// - + 表示输出正负号(默认只有负数输出符号)\r// - 空格 输出正时以空格,输出负时以负号\r// - # 输出八进制、十六进制前缀;对于小数表示强迫输出小数点 关于 printf() 不能立即输出的问题\n从本质上讲, printf() 执行结束后数据并没有直接输出到显示器上,而是放入了缓冲区,直到遇见换行符 \\n 才将缓冲区中的数据输出到显示器上。\n输入输出的“命门”就在于缓存。\n输入 在 c 语言中,有多个函数可以从键盘获得用户输入:\ngets() - 获取一行数据,并作为字符串处理(可以读取含有空格的字符串); getchar() - 用于输入单个字符(就是 scanf(\u0026quot;%c\u0026quot;, c) 的简化版); scanf() - 可以格式化输入多种类型的数据。 对于 scanf() 输入数据的格式要和控制字符串的格式保持一致。\n从本质上讲,从键盘输入的数据并没直接交给 scanf() ,而是放了缓冲区中,直到我们按下回车键, scanf() 才到缓冲区中读取数据。\n文件操作 c 语言具有操作文件的能力,比如打开文件、读取/追加/插入/删除数据、关闭文件、删除文件等。\nc 语言中的文件是什么 文件是数据源的一种,最主要的作用是保存数据。\n在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。如:\n文件 硬件设备 stdin 标准输入文件,一般指键盘; scanf()、getchar() 等函数默认从 stdin 获取输入 stdout 标准输出文件,一般指显示器; printf()、putchar() 等函数默认从 stdout 输出数据 stderr 标准错误文件,一般指显示器; perror() 等函数默认向 stderr 输出数据 stdprn 标准打印文件,一般指打印机 \u0026gt; 常见硬件设备所对应的文件\n此处不去探讨硬件设备是如何被映射成文件的,只需记住,在 c 语言中硬件设备可以看成文件,有些输入输出函数不需要你指明到底读写哪个文件,系统已经为它们设置了默认的文件(当然你也可以更改,如让 printf 向磁盘上的文件输出数据)。\n操作文件的正确流程为:打开文件 → 读写文件 → 关闭文件(使用完毕要记得关闭哦)。\n关于文件流 所有的文件都要载入内存才能处理,所有的数据必须写入文件才不会丢失。\n数据在文件和内存之间传递的过程叫做文件流 ,数据从文件复制到内存的过程叫做输入流,从内存保存到文件的过程叫做输出流。\n文件是数据源的一种,除了文件,还有数据库、网络、键盘等;数据传递到内存也就是保存到 c 语言的变量(如整数、字符串、数组、缓冲区等)。我们把数据在数据源和程序(内存)之间传递的过程叫做 数据流(data stream) ,相应的,数据从数据源到程序(内存)的过程叫做输入流(input stream),从程序(内存)到数据源的过程叫做输出流(output stream)。\n输入输出(input outpt, io)是指程序(内存)与外部设备(键盘、显示器、磁盘、其他计算机等)进行交互的操作。\n我们可以说,打开文件就是打开了一个流。\n打开/关闭文件 在 c 语言中,操作文件之前必须先打开文件。\n所谓 “打开文件” ,就是让程序和文件建立连接的过程,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,这些信息会被保存到一个 file 类型的结构体变量中; “关闭文件” 就是断开与文件之间的联系,释放结构体变量,同时禁止再对文件进行操作。\n标准输入文件 stdin(表示键盘)、标准输出文件 stdout(表示显示器)、标准错误文件 stderr(表示显示器)是由系统打开的,可直接使用。\n使用 \u0026lt;stdio.h\u0026gt; 头文件中的 fopen() 函数即可打开文件,它的用法为:\nfile *fopen(char *filename, char *mode);\r// - filename 表示文件名称\r// - mode 表示打开方式 fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 file 类型的结构体变量中,然后将该变量的地址返回。\nfile 是 \u0026lt;stdio.h\u0026gt; 头文件中的一个结构体,它专门用来保存文件信息。如果希望接收 fopen() 的返回值,就需要定义一个 file 类型的指针。\n下面我们来看一段文件操作的规范写法:\n1 2 3 4 5 6 7 file *fp; if ((fp = fopen(\u0026#34;d:\\\\demo.txt\u0026#34;, \u0026#34;rb\u0026#34;)) == null) { printf(\u0026#34;fail to open file!\\n\u0026#34;); exit(0); // 结束程序 } 我们在打开文件时 一定要 通过判断 fopen() 的返回值是否和 null 相等来判断是否打开失败。\n关于文件打开方式 mode\n不同的操作需要不同的文件权限(只读、读写等),另外,文件也有不同的类型,按照数据的存储方式可以分为二进制文件和文本文件,它们的操作细节是不同的。\n在调用 fopen() 函数时,这些信息都必须提供,称为 文件打开方式 ,具体如下:\n打开方式 说明 控制读写权限的字符串 必须指明 ———————— ———————————————————————————————————- “r” (read) 以“只读”方式打开文件。文件必须存在,否则打开失败。 “w” (write) 以“写入”方式打开文件。文件若不存在,新建;若存在,则清空文件内容。 “a” (append) 以“追加”方式打开文件。文件若不存在,新建;若存在,则(保留原有的文件内容)将写入的数据追加到文件的末尾。 “r+” 以“读写”方式打开文件。文件必须存在,否则打开失败。 “w+” 以“写入/更新”方式打开文件。相当于 w 和 r+ ,文件若不存在,新建;若存在,清空。 “a+” 以“追加/更新”方式打开文件。相当于 a 和 r+ ,文件若不存在,新建;若存在,追加。 ———————— ———————————————————————————————————- 控制读写方式的字符串 可选 ———————— ———————————————————————————————————- “t” (text) 文本文件(默认) “b” (binary) 二进制文件 *注意,读写权限和读写方式可以组合使用,但是不能将读写方式放在读写权限的开头(可以放末尾和中间)。\n文件一旦使用完毕,应该使用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。文件正常关闭时, fclose() 的返回值 为 0 ,如果返回非零值则表示有错误发生。\nint fclose(file *fp); 读写文件 在 c 语言中,文件有多种读写方式,可以一个字符一个字符地读取,也可以读写一个字符串,还可以读取若干个字节(数据块)。文件的读写位置也非常灵活,可以从文件开头读取,也可以从中间位置读取。\n先来个完整的轮廓看看,如下:\n// 以字符形式读、写文件\rint fgetc(file *fp);\rint foutc(int ch, file *fp);\r// 以字符串形式读、写文件\rchar *fgets(char *str, int n, file *fp);\rint fputs (char *str, file *fp);\r// 以数据块形式读、写文件\rsize_t fread (void *ptr, size_t size, size_t count, file *fp);\rsize_t fwrite(void *ptr, size_t size, size_t count, file *fp);\r// 格式化读、写文件\rint fscanf (file *fp, char * format, ... );\rint fprintf(file *fp, char * format, ... );\r// 随机读、写文件\rvoid rewind(file *fp); // 用来将位置指针移动到文件开关\rint fseek(file *fp, long offset, int origin); // 用来将指针移动到任意位置 1. 以字符形式读写文件\n1 2 3 4 5 6 7 8 // int fgetc(file *fp); // - 成功时返回读取到的字符; // - 读取到文件末尾或读取失败时返回 eof (一个负数,通常为 -1) // 从 demo.txt 文件中读取一个字符,并保存到变量 ch 中 char ch; file *fp = fopen(\u0026#34;demo.txt\u0026#34;, \u0026#34;r+\u0026#34;); ch = fgetc(fp); 关于 eof (end of line),表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,往往是 -1 。 fgetc() 的返回值类型之所以为 int ,就是为了容纳这个负数(char 不能是负数)。\n在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。在文件打开时,该指针总是指向文件的第一个字节。使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。\n*注意:这个文件内部的位置指针与 c 语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。\n下面我们来看一个示例,在屏幕上显示 demo.txt 文件的内容。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; int main() { file *fp; char ch; // 如果文件不存在,给出提示并退出 if ((fp=fopen(\u0026#34;demo.txt\u0026#34;, \u0026#34;rt\u0026#34;)) == null) { puts(\u0026#34;fail to open file!\u0026#34;); exit(0); } // 每次读取一个字节,直到读取完毕 while ((ch=fgetc(fp)) != eof) { putchar(ch); } putchar(\u0026#39;\\n\u0026#39;); // 输出换行符 fclose(fp); // 关闭文件 return 0; } 写字符函数 fputc\n再看一个写入的示例,从键盘输入一行字符,写入文件。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // int foutc(int ch, file *fp); // - 成功时返回写入的字符 // - 失败时返回 eof (一个负数) #include \u0026lt;stdio.h\u0026gt; int main(){ file *fp; char ch; // 判断文件是否打开成功 if ((fp=fopen(\u0026#34;demo.txt\u0026#34;, \u0026#34;wt+\u0026#34;)) == null) { puts(\u0026#34;fail to open file!\u0026#34;); exit(0); } printf(\u0026#34;input a string:\\n\u0026#34;); // 每次从键盘读取一个字符并写入文件 while ((ch=getchar()) != \u0026#39;\\n\u0026#39;) { fputc(ch, fp); } fclose(fp); return 0; } 2. 以字符串形式读写文件\nfgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,用法如下:\nchar *fgets(char *str, int n, file *fp);\r// - str 为字符数组(长度为 n+1 ,不要忘了读取到的字符串会在末尾自动添加 \u0026#39;\\0\u0026#39;)\r// - n 为要读取的字符数目\r// - fp 为文件指针\r// 读取成功时返回字符数组的首地址,也即 str\r// 读取失败时,返回 null\r// 如果开始读取时,文件内部指针已经指向了文件末尾,将读取不到任何字符,也返回 null 来看一个示例:\n1 2 3 4 5 6 // 从 demo.txt 中读取 100 个字符,并保存到字符数组 str 中 #define n 101 char str[n]; file *fp = fopen(\u0026#34;demo.txt\u0026#34;, \u0026#34;r\u0026#34;); fgets(str, n, fp); *需要重点说明的是,在读取 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管 n 的值多大, fgets() 最多只能读取一行数据,不能跨行。\n在 c 语言中,没有按行读取文件的函数,我们可以借助 fgets() ,将 n 的值设置地足够大,每次就可以读取到一行数据了。\n再来看一个示例,一行一行地读取文件:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #define n 100 int main() { file *fp; char str[n+1]; if ((fp=fopen(\u0026#34;demo.txt\u0026#34;, \u0026#34;rt\u0026#34;)) == null) { puts(\u0026#34;fial to open file!\u0026#34;); exit(0); } while(fgets(str, n, fp) != null) { printf(\u0026#34;%s\u0026#34;, str); } fclose(fp); return 0; } 写字符串函数 fputs\nfputs() 函数用来向指定的文件写入一个字符串,它的用法为:\nchar *fputs(char *str, file *fp);\r// - str 为要写入的字符串)\r// - fp 为文件指针\r// 写入成功时返回非负数\r// 写入失败时,返回 eof 来看一个示例,向上例中建立的 demo.txt 文件中追加一个字符串:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include \u0026lt;stdio.h\u0026gt; int main() { file *fp; char str[102] = {0}, strtemp[100]; if ((fp=fopen(\u0026#34;demo.txt\u0026#34;, \u0026#34;at+\u0026#34;)) == null) { puts(\u0026#34;fail to open file!\u0026#34;); exit(0); } printf(\u0026#34;input a string:\u0026#34;); gets(strtemp); strcat(str, \u0026#34;\\n\u0026#34;); strcat(str, strtemp); fputs(str, fp); fclose(fp); return 0; } 3. 以数据块形式读写文件\n// 以数据块形式读、写文件\rsize_t fread (void *ptr, size_t size, size_t count, file *fp);\rsize_t fwrite(void *ptr, size_t size, size_t count, file *fp);\r// - ptr 为内存区块的指针,它可以是数组、变量、结构体等\r// - fread() 中的 ptr 用来存放读取到的数据\r// - fwrite() 中的 ptr 用来存放要写入的数据\r// - size 表示每个数据块的字节数\r// - count 表示要读写的数据块的块数\r// - fp 表示文件指针\r// 理论上,每次读写 size*count 个字节的数据\r// 返回成功读写的块数,即 count\r// 如果返回值小于 count\r// - 对于 fwrite() 来说,不用发生了写入错误,可以用 ferror() 函数检测\r// - 对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测 size_t 是什么呢?\nsize_t 是在 stdio.h 和 stdlib.h 头文件中使用 typedef 定义的数据类型,表示无符号整数,即非负数,常用来表示数量。\n来看一个示例,从键盘输入一个数组,将数组写入文件,再读取出来:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include \u0026lt;stdio.h\u0026gt; #define n 5 int main() { // 从键盘输入的数据放入 a,从文件读取的数据放入 b int a[n], b[n]; int i, size = sizeof(int); file *fp; if((fp=open(\u0026#34;demo.txt\u0026#34;, \u0026#34;rb+\u0026#34;)) == null) { // 以二进制方式打开 puts(\u0026#34;fail to pen file!\u0026#34;); exit(0); } // 从键盘输入数据,并保存于数组 a for (i=0; i\u0026lt;n; i++) { scanf(\u0026#34;%d\u0026#34;, \u0026amp;a[i]); } // 将数组 a 的内容写入到文件 fwrite(a, size, n, fp); // 将文件中的位置指针重新定位到文件开头 rewind(fp); // 从文件读取内容并保存到数组 b fread(b, size, n, fp); // 在屏幕上显示数组 b 的内容 for (i=0; i\u0026lt;n; i++) { printf(\u0026#34;%d\u0026#34;, b[i]); } printf(\u0026#34;\\n\u0026#34;); fclose(fp); return 0; } 打开 demo.txt,发现文件内容根本无法阅读。这是因为我们使用 “rb+” 方式打开文件,数组会原封不动地以二进制形式写入文件,一般无法阅读。\n再来看一个示例,从键盘输入两个学生数据,写入一个文件中,再读出这两个学生的数据显示到屏幕上:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include \u0026lt;stdio.h\u0026gt; #define n 2 struct stu { char name[10]; int num; int age; float score; } boya[n], boyb[n], *pa, *pb; int main() { file *fp; int i; pa = boya; pb = boyb; if ((fp=fopen(\u0026#34;demo.txt\u0026#34;, \u0026#34;wb+\u0026#34;)) == null) { puts(\u0026#34;fail to pen file!\u0026#34;); exit(0); } // 从键盘输入数据 printf(\u0026#34;input data:\\n\u0026#34;); for (i=0; i\u0026lt;n; i++, pa++) { scanf(\u0026#34;%s %d %d %f\u0026#34;, pa-\u0026gt;name, \u0026amp;pa-\u0026gt;num, \u0026amp;pa-\u0026gt;age, \u0026amp;pa-\u0026gt;score); } // 将数组 boya 的数据写入文件 fwrite(boya, sizeof(struct stu), n, fp); // 将文件中的位置指针重置到文件开头 rewind(fp); // 从文件读取数据并保存到数据 boyb fread(boyb, sizeof(struct stu), n, fp); // 输出数组 boyb 中的数据 for (i=0; i\u0026lt;n; i++, pb++) { printf(\u0026#34;%s %d %d %f\\n\u0026#34;, pb-\u0026gt;name, pb-\u0026gt;num, pb-\u0026gt;age, pb-\u0026gt;score); } fclose(fp); return 0; } 4. 格式化读写文件\nfscanf() 和 fprintf() 函数与前面使用的 scanf() 和 printf() 功能相似,都是格式化读写函数,两者的区别在于 fscanf() 和 fprintf() 的读写对象不是键盘和显示器,而是磁盘文件。\n// 格式化读、写文件\rint fscanf (file *fp, char * format, ... );\rint fprintf(file *fp, char * format, ... );\r// - fp 为文件指针\r// - format 为格式控制字符串\r// - ... 表示参数列表\r// 成功 返回写入的字符的个数\r// 失败 返回负数 来看一个简单的示例:\n1 2 3 4 5 6 file *fp; int i, j; char *str, ch; fscanf(fp, \u0026#34;%d %s\u0026#34;, \u0026amp;i, str); fprintf(fp, \u0026#34;%d %c\u0026#34;, j, ch); 5. 随机读写文件\n移动文件内部位置指针的函数主要有两个,即 rewind() 和 fseek() 。\n// 随机读、写文件\rvoid rewind(file *fp); // 用来将位置指针移动到文件开关\rint fseek (file *fp, long offset, int origin); // 用来将指针移动到任意位置\r// - fp 为文件指针,也就是被移动的文件\r// - offset 为偏移量,也就是要移动的字节数,正向后移,负向前移\r// - origin 为起始位置,c 语言规定起始位置有三种:\r// - 文件开头, 常量名 seek_set, 值为 0\r// - 当前位置, 常量名 seek_cur, 值为 1\r// - 文件末尾, 常量名 seek_end, 值为 2 如 fseek(fp, 100, 0); 表示把位置指针移动到离文件开头 100 个字节处。\n","date":"2023-05-26","permalink":"https://aituyaa.com/c/","summary":":: 好的吧,我承认,对于 C 语言,我有一种莫名其妙的偏爱!是因为可以直接操作内存吗?或许是!可能吧,计算机有什么神奇的东西呢?最终就是围绕那几个概念在来回打转! 编译","title":"c"},]
[{"content":"📕 转载自 emacs lisp 简明教程 - 水木社区 emacs 版\n这是 叶文彬(水木 id: happierbee) 写的一份 emacs lisp 的教程,深入浅出,非常适合初学者。文档的 tex 代码及 pdf 文档可在此处下载 。\nemacs 的高手不能不会 elisp。但是对于很多人来说 elisp 学习是一个痛苦的历程,至少我是有这样一段经历。因此,我写了这一系列文章,希望能为后来者提供一点捷径。\n一个 hello world 例子 自从 k\u0026amp;r 以来,hello world 程序历来都是程序语言教程的第一个例子。我也用一个 hello world 的例子来演示 emacs 里执行 elisp 的环境。下面就是这个语句:\n1 (message \u0026#34;hello world\u0026#34;) 前面我没有说这个一个程序,这是因为,elisp 不好作为可执行方式来运行(当然也不是不可能),所有的 elisp 都是运行在 emacs 这个环境下。\n首先切换到 *scratch* 缓冲区里,如果当前模式不是 lisp-interaction-mode,用 m-x lisp-interaction-mode 先转换到 lisp-interaction-mode。然后输入前面这一行语句。在行尾右括号后,按 c-j 键。如果 minibuffer 里显示 hello world,光标前一行也显示 \u0026quot;hello world\u0026quot;,那说明你的操作没有问题。我们就可以开始 elisp 学习之旅了。\n注:elisp 里的一个完整表达式,除了简单数据类型(如数字,向量),都是用括号括起来,称为一个 s-表达式。让 elisp 解释器执行一个 s-表达式除了前一种方法之外,还可以用 c-x c-e。它们的区别是,c-x c-e 是一个全局按键绑定,几乎可以在所有地方都能用。它会将运行返回值显示在 minibuffer 里。这里需要强调一个概念是返回值和作用是不同的。比如前面 message 函数它的作用是在 minibuffer 里显示一个字符串,但是它的返回值是 \u0026quot;hello world\u0026quot; 字符串。\n基础知识 这一节介绍一下 elisp 编程中一些最基本的概念,比如如何定义函数,程序的控制结构,变量的使用和作用域等等。\n函数和变量 elisp 中定义一个函数是用这样的形式:\n(defun function-name (arguments-list)\r\u0026#34;document string\u0026#34;\rbody) 比如:\n1 2 3 (defun hello-world (name) \u0026#34;say hello to user whose name is name.\u0026#34; (message \u0026#34;hello, %s\u0026#34; name)) 其中函数的文档字符串是可以省略的。但是建议为你的函数(除了最简单,不作为接口的)都加上文档字符串。这样将来别人使用你的扩展或者别人阅读你的代码或者自己进行维护都提供很大的方便。\n在 emacs 里,当光标处于一个函数名上时,可以用 c-h f 查看这个函数的文档。比如前面这个函数,在 *help* 缓冲区里的文档是:\nhello-world is a lisp function.\r(hello-world name)\rsay hello to user whose name is name. 如果你的函数是在文件中定义的。这个文档里还会给出一个链接能跳到定义的地方。\n要运行一个函数,最一般的方式是:\n(function-name arguments-list) 比如前面这个函数:\n1 (hello-world \u0026#34;emacser\u0026#34;) ; =\u0026gt; \u0026#34;hello, emacser\u0026#34; 每个函数都有一个返回值。这个返回值一般是函数定义里的最后一个表达式的值。\nelisp 里的变量使用无需象 c 语言那样需要声明,你可以用 setq 直接对一个变量赋值。\n1 2 (setq foo \u0026#34;i\u0026#39;m foo\u0026#34;) ; =\u0026gt; \u0026#34;i\u0026#39;m foo\u0026#34; (message foo) ; =\u0026gt; \u0026#34;i\u0026#39;m foo\u0026#34; 和函数一样,你可以用 c-h v 查看一个变量的文档。比如当光标在 foo 上时用 c-h v 时,文档是这样的:\nfoo\u0026#39;s value is \u0026#34;i\u0026#39;m foo\u0026#34;\rdocumentation:\rnot documented as a variable. 有一个特殊表达式(special form)defvar,它可以声明一个变量,一般的形式是:\n(defvar variable-name value\r\u0026#34;document string\u0026#34;) 它与 setq 所不同的是,如果变量在声明之前,这个变量已经有一个值的话, 用 defvar 声明的变量值不会改变成声明的那个值 。另一个区别是 defvar 可以为变量提供文档字符串,当变量是在文件中定义的话,c-h v 后能给出变量定义的位置。比如:\n1 2 3 4 5 6 (defvar foo \u0026#34;did i have a value?\u0026#34; \u0026#34;a demo variable\u0026#34;) ; =\u0026gt; foo foo ; =\u0026gt; \u0026#34;i\u0026#39;m foo\u0026#34; (defvar bar \u0026#34;i\u0026#39;m bar\u0026#34; \u0026#34;a demo variable named \\\u0026#34;bar\\\u0026#34;\u0026#34;) ; =\u0026gt; bar bar ; =\u0026gt; \u0026#34;i\u0026#39;m bar\u0026#34; 用 c-h v 查看 foo 的文档,可以看到它已经变成:\nfoo\u0026#39;s value is \u0026#34;i\u0026#39;m foo\u0026#34;\rdocumentation:\ra demo variable 由于 elisp 中函数是全局的,变量也很容易成为全局变量(因为全局变量和局部变量的赋值都是使用 setq 函数),名字不互相冲突是很关键的。所以除了为你的函数和变量选择一个合适的前缀之外,用 c-h f 和 c-h v 查看一下函数名和变量名有没有已经被使用过是很关键的。\n局部作用域的变量 如果没有局部作用域的变量,都使用全局变量,函数会相当难写。elisp 里可以用 let 和 let* 进行局部变量的绑定。let 使用的形式是:\n(let (bindings)\rbody) bingdings 可以是 (var value) 这样对 var 赋初始值的形式,或者用 var 声明一个初始值为 nil 的变量。比如:\n1 2 3 4 5 6 (defun circle-area (radix) (let ((pi 3.1415926) area) (setq area (* pi radix radix)) (message \u0026#34;直径为 %.2f 的圆面积是 %.2f\u0026#34; radix area))) (circle-area 3) c-h v 查看 area 和 pi 应该没有这两个变量。\nlet* 和 let 的使用形式完全相同,唯一的区别是在 let* 声明中就能使用前面声明的变量,比如:\n1 2 3 4 (defun circle-area (radix) (let* ((pi 3.1415926) (area (* pi radix radix))) (message \u0026#34;直径为 %.2f 的圆面积是 %.2f\u0026#34; radix area))) lambda 表达式 可能你久闻 lambda 表达式的大名了。其实依我的理解,lambda 表达式相当于其它语言中的匿名函数。比如 perl 里的匿名函数。它的形式和 defun 是完全一样的:\n(lambda (arguments-list)\r\u0026#34;documentation string\u0026#34;\rbody) 调用 lambda 方法如下:\n1 2 (funcall (lambda (name) (message \u0026#34;hello, %s!\u0026#34; name)) \u0026#34;emacser\u0026#34;) 你也可以把 lambda 表达式赋值给一个变量,然后用 funcall 调用:\n1 2 3 (setq foo (lambda (name) (message \u0026#34;hello, %s!\u0026#34; name))) (funcall foo \u0026#34;emacser\u0026#34;) ; =\u0026gt; \u0026#34;hello, emacser!\u0026#34; lambda 表达式最常用的是作为参数传递给其它函数,比如 mapc。\n控制结构 顺序执行 一般来说程序都是按表达式顺序依次执行的。这在 defun 等特殊环境中是自动进行的。但是一般情况下都不是这样的。比如你无法用 eval-last-sexp 同时执行两个表达式,在 if 表达式中的条件为真时执行的部分也只能运行一个表达式。这时就需要用 progn 这个特殊表达式。它的使用形式如下:\n(progn a b c ...) 它的作用就是让表达式 a, b, c 顺序执行。比如:\n1 2 3 (progn (setq foo 3) (message \u0026#34;square of %d is %d\u0026#34; foo (* foo foo))) 条件判断 elisp 有两个最基本的条件判断表达式 if 和 cond。使用形式分别如下:\n(if condition\rthen\relse)\r(cond (case1 do-when-case1)\r(case2 do-when-case2)\r...\r(t do-when-none-meet)) :: cond 类似于其他语言中的 switch ,而且明显 elisp 的语义化更好。\n使用的例子如下:\n1 2 3 4 5 6 7 8 9 10 11 (defun my-max (a b) (if (\u0026gt; a b) a b)) (my-max 3 4) ; =\u0026gt; 4 (defun fib (n) (cond ((= n 0) 0) ((= n 1) 1) (t (+ (fib (- n 1)) (fib (- n 2)))))) (fib 10) ; =\u0026gt; 55 还有两个宏 when 和 unless,从它们的名字也就能知道它们是作什么用的。使用这两个宏的好处是使代码可读性提高,when 能省去 if 里的 progn 结构,unless 省去条件为真子句需要的的 nil 表达式。\n循环 循环使用的是 while 表达式。它的形式是:\n(while condition\rbody) 比如:\n1 2 3 4 5 6 7 (defun factorial (n) (let ((res 1)) (while (\u0026gt; n 1) (setq res (* res n) n (- n 1))) res)) (factorial 10) ; =\u0026gt; 3628800 逻辑运算 条件的逻辑运算和其它语言都是很类似的,使用 and、or、not。and 和 or 也同样具有短路性质。很多人喜欢在表达式短时,用 and 代替 when,or 代替 unless。当然这时一般不关心它们的返回值,而是在于表达式其它子句的副作用。比如 or 经常用于设置函数的缺省值,而 and 常用于参数检查:\n1 2 3 4 5 6 7 8 9 10 11 (defun hello-world (\u0026amp;optional name) (or name (setq name \u0026#34;emacser\u0026#34;)) (message \u0026#34;hello, %s\u0026#34; name)) ; =\u0026gt; hello-world (hello-world) ; =\u0026gt; \u0026#34;hello, emacser\u0026#34; (hello-world \u0026#34;ye\u0026#34;) ; =\u0026gt; \u0026#34;hello, ye\u0026#34; (defun square-number-p (n) (and (\u0026gt; n 0) (= (/ n (sqrt n)) (sqrt n)))) (square-number-p -1) ; =\u0026gt; nil (square-number-p 25) ; =\u0026gt; t 函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (defun name arglist [docstring] body...) (defvar symbol \u0026amp;optional initvalue docstring) (setq sym val sym val ...) (let varlist body...) (let* varlist body...) (lambda args [docstring] [interactive] body) (progn body ...) (if cond then else...) (cond clauses...) (when cond body ...) (unless cond body ...) (when cond body ...) (or conditions ...) (and conditions ...) (not object) 基本数据类型之一 – 数字 elisp 里的对象都是有类型的,而且每一个对象它们知道自己是什么类型。 你得到一个变量名之后可以用一系列检测方法来测试这个变量是什么类型(好像没有什么方法来让它说出自己是什么类型的)。内建的 emacs 数据类型称为 primitive types,包括整数、浮点数、cons、符号 (symbol)、字符串、向量 (vector)、散列表 (hash-table)、subr(内建函数,比如 cons, if, and 之类)、byte-code function,和其它特殊类型,例如缓冲区(buffer)。\n在开始前有必要先了解一下读入语法和输出形式。所谓读入语法是让 elisp 解释器明白输入字符所代表的对象,你不可能让 elisp 读入 .#@!? 这样奇怪的东西还能好好工作吧(perl 好像经常要受这样的折磨:))。简单的来说,一种数据类型有(也可能没有,比如散列表)对应的规则来让解释器产生这种数据类型,比如 123 产生整数 123, (a . b) 产生一个 cons。所谓输出形式是解释器用产生一个字符串来表示一个数据对象。比如整数 123 的输出形式就是 123,cons cell (a . b) 的输出形式是 (a . b)。与读入语法不同的是,数据对象都有输出形式。比如散列表的输出可能是这样的:\n#\u0026lt;hash-table \u0026#39;eql nil 0/65 0xa7344c8\u0026gt; 通常一个对象的数据对象的输出形式和它的读入形式都是相同的。现在就先从简单的数据类型──数字开始吧。\nemacs 的数字分为整数和浮点数(和 c 比没有双精度数 double)。1, 1.,+1, -1, 536870913, 0, -0 这些都是整数。整数的范围是和机器是有关的,一般来最小范围是在 -268435456 to 268435455(29 位,-2**28 ~ 2**28-1)。可以从 most-positive-fixnum 和 most-negative-fixnum 两个变量得到整数的范围。\n你可以用多种进制来输入一个整数。比如:\n1 2 3 #b101100 =\u0026gt; 44 ; 二进制 #o54 =\u0026gt; 44 ; 八进制 #x2c =\u0026gt; 44 ; 十六进制 最神奇的是你可以用 2 到 36 之间任意一个数作为基数,比如:\n1 #24r1k =\u0026gt; 44 ; 二十四进制 之所以最大是 36,是因为只有 0-9 和 a-z 36 个字符来表示数字。但是我想基本上不会有人会用到 emacs 的这个特性。\n1500.0, 15e2, 15.0e2, 1.5e3, 和 .15e4 都可以用来表示一个浮点数 1500.。遵循 ieee 标准,elisp 也有一个特殊类型的值称为 nan (not-a-number)。你可以用 (/ 0.0 0.0) 产生这个数。\n测试函数 整数类型测试函数是 integerp,浮点数类型测试函数是 floatp。数字类型测试用 numberp。你可以分别运行这几个例子来试验一下:\n1 2 3 4 5 (integerp 1.) ; =\u0026gt; t (integerp 1.0) ; =\u0026gt; nil (floatp 1.) ; =\u0026gt; nil (floatp -0.0e+nan) ; =\u0026gt; t (numberp 1) ; =\u0026gt; t 还提供一些特殊测试,比如测试是否是零的 zerop ,还有非负整数测试的 wholenump 。\n注:elisp 测试函数一般都是用 p 来结尾,p 是 predicate 的第一个字母。如果函数名是一个单词,通常只是在这个单词后加一个 p,如果是多个单词,一般是加 -p。\n数的比较 常用的比较操作符号是我们在其它言中都很熟悉的,比如 \u0026lt;, \u0026gt;, \u0026gt;=, \u0026lt;=,不一样的是,由于赋值是使用 set 函数,所以 = 不再是一个赋值运算符了,而是测试数字相等符号。和其它语言类似, 对于浮点数的相等测试都是不可靠的 。比如:\n1 2 3 (setq foo (- (+ 1.0 1.0e-3) 1.0)) ; =\u0026gt; 0.0009999999999998899 (setq bar 1.0e-3) ; =\u0026gt; 0.001 (= foo bar) ; =\u0026gt; nil 所以一定要确定两个浮点数是否相同,是要在一定误差内进行比较。这里给出一个函数:\n1 2 3 4 5 6 7 (defvar fuzz-factor 1.0e-6) (defun approx-equal (x y) (or (and (= x 0) (= y 0)) (\u0026lt; (/ (abs (- x y)) (max (abs x) (abs y))) fuzz-factor))) (approx-equal foo bar) ; =\u0026gt; t 还有一个测试数字是否相等的函数 eql ,这是函数不仅测试数字的值是否相等,还测试数字类型是否一致,比如:\n1 2 (= 1.0 1) ; =\u0026gt; t (eql 1.0 1) ; =\u0026gt; nil elisp 没有 +=, -=, /=, *= 这样的命令式语言里常见符号,如果你想实现类似功能的语句,只能用赋值函数 setq 来实现了。 /= 符号被用来作为不等于的测试了。\n数的转换 整数向浮点数转换是通过 float 函数进行的。而浮点数转换成整数有这样几个函数:\ntruncate 转换成靠近 0 的整数 floor 转换成最接近的不比本身大的整数 ceiling 转换成最接近的不比本身小的整数 round 四舍五入后的整数,换句话说和它的差绝对值最小的整数 很晕是吧。自己用 1.2, 1.7, -1.2, -1.7 对这四个函数操作一遍就知道区别了(可以直接看 info。按键顺序是 c-h i m elisp ret m numeric conversions ret。以后简写成 info elisp - numeric conversions)。\n这里提一个问题,浮点数的范围是无穷大的,而整数是有范围的,如果用前面的函数转换 1e20 成一个整数会出现什么情况呢?试试就知道了。\n数的运算 四则运算没有什么好说的,就是 + - * /。值得注意的是,和 c 语言类似,如果参数都是整数,作除法时要记住 (/ 5 6) 是会等于 0 的。如果参数中有浮点数,整数会自动转换成浮点数进行运算,所以 (/ 5 6.0) 的值才会是 5/6。\n没有 ++ 和 -- 操作了,类似的两个函数是 1+ 和 1- 。可以用 setq 赋值来代替 ++ 和 --:\n1 2 3 (setq foo 10) ; =\u0026gt; 10 (setq foo (1+ foo)) ; =\u0026gt; 11 (setq foo (1- foo)) ; =\u0026gt; 10 注:可能有人看过有 incf 和 decf 两个实现 ++ 和 -- 操作。这两个宏是可以用的。这两个宏是 common lisp 里的,emacs 有模拟的 common lisp 的库 cl。但是 rms 认为最好不要使用这个库。但是你可以在你的 elisp 包中使用这两个宏,只要在文件头写上:\n1 2 (eval-when-compile (require \u0026#39;cl)) 由于 incf 和 decf 是两个宏,所以这样写不会在运行里导入 cl 库。有点离题是,总之一句话,教主说不好的东西,我们最好不要用它。其它无所谓,只可惜了两个我最常用的函数 remove-if 和 remove-if-not 。不过如果你也用 emms 的话,可以在 emms-compat 里找到这两个函数的替代品。\nabs 取数的绝对值。\n有两个取整的函数,一个是符号 % ,一个是函数 mod 。这两个函数有什么差别呢?一是 % 的第一个参数必须是整数,而 mod 的第一个参数可以是整数也可以是浮点数。二是即使对相同的参数,两个函数也不一定有相同的返回值:\n(+ (% dividend divisor)\r(* (/ dividend divisor) divisor)) 和 dividend 是相同的。而:\n(+ (mod dividend divisor)\r(* (floor dividend divisor) divisor)) 和 dividend 是相同的。\n三角运算有函数: sin, cos, tan, asin, acos, atan。开方函数是 sqrt。\nexp 是以 e 为底的指数运算,expt 可以指定底数的指数运算。log 默认底数是 e,但是也可以指定底数。log10 就是 (log x 10)。logb 是以 2 为底数运算,但是返回的是一个整数。这个函数是用来计算数的位。\nrandom 可以产生随机数。可以用 (random t) 来产生一个新种子。虽然 emacs 每次启动后调用 random 总是产生相同的随机数,但是运行过程中,你不知道调用了多少次,所以使用时还是不需要再调用一次 (random t) 来产生新的种子。\n位运算这样高级的操作我就不说了,自己看 info elisp - bitwise operations on integers 吧。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ;; 测试函数 (integerp object) (floatp object) (numberp object) (zerop number) (wholenump object) ;; 比较函数 (\u0026gt; num1 num2) (\u0026lt; num1 num2) (\u0026gt; :: num1 num2) (\u0026lt;= num1 num2) (= num1 num2) (eql obj1 obj2) (/= num1 num2) ;; 转换函数 (float arg) (truncate arg \u0026amp;optional divisor) (floor arg \u0026amp;optional divisor) (ceiling arg \u0026amp;optional divisor) (round arg \u0026amp;optional divisor) ;; 运算 (+ \u0026amp;rest numbers-or-markers) (- \u0026amp;optional number-or-marker \u0026amp;rest more-numbers-or-markers) (* \u0026amp;rest numbers-or-markers) (/ dividend divisor \u0026amp;rest divisors) (1+ number) (1- number) (abs arg) (% x y) (mod x y) (sin arg) (cos arg) (tan arg) (asin arg) (acos arg) (atan y \u0026amp;optional x) (sqrt arg) (exp arg) (expt arg1 arg2) (log arg \u0026amp;optional base) (log10 arg) (logb arg) ;; 随机数 (random \u0026amp;optional n) 变量列表 most-positive-fixnum\rmost-negative-fixnum 基本数据类型之二 – 字符和字符串 在 emacs 里字符串是有序的字符数组。和 c 语言的字符串数组不同,emacs 的字符串可以容纳任何字符,包括 \\0:\n1 (setq foo \u0026#34;abc\\000abc\u0026#34;) ; =\u0026gt; \u0026#34;abc^@abc\u0026#34; 关于字符串有很多高级的属性,例如字符串的表示有单字节和多字节类型,字符串可以有文本属性(text property)等等。但是对于刚接触字符串,还是先学一些基本操作吧。\n首先 构成字符串的字符其实就是一个整数 。一个字符 ‘a’ 就是一个整数 65。但是目前字符串中的字符被限制在 0-524287 之间。字符的读入语法是在字符前加上一个问号,比如 ?a 代表字符 ‘a’。\n1 2 ?a ; =\u0026gt; 65 ?a ; =\u0026gt; 97 对于标点来说,也可以用同样的语法,但是最好在前面加上转义字符 \\ ,因为有些标点会有岐义,比如 ?\\(。 \\ 必须用 ?\\ 表示。控制字符,退格、制表符,换行符,垂直制表符,换页符,空格,回车,删除和 escape 表示为 ?\\a, ?\\b, ?\\t, ?\\n, ?\\v, ?\\f, ?\\s, ?\\r, ?\\d, 和 ?\\e。对于没有特殊意义的字符,加上转义字符 \\ 是没有副作用的,比如 ?\\+ 和 ?+ 是完全一样的。所以标点还是都用转义字符来表示吧。\n1 2 3 4 5 6 7 8 9 10 11 ?\\a =\u0026gt; 7 ; control-g, `c-g\u0026#39; ?\\b =\u0026gt; 8 ; backspace, \u0026lt;bs\u0026gt;, `c-h\u0026#39; ?\\t =\u0026gt; 9 ; tab, \u0026lt;tab\u0026gt;, `c-i\u0026#39; ?\\n =\u0026gt; 10 ; newline, `c-j\u0026#39; ?\\v =\u0026gt; 11 ; vertical tab, `c-k\u0026#39; ?\\f =\u0026gt; 12 ; formfeed character, `c-l\u0026#39; ?\\r =\u0026gt; 13 ; carriage return, \u0026lt;ret\u0026gt;, `c-m\u0026#39; ?\\e =\u0026gt; 27 ; escape character, \u0026lt;esc\u0026gt;, `c-[\u0026#39; ?\\s =\u0026gt; 32 ; space character, \u0026lt;spc\u0026gt; ?\\\\ =\u0026gt; 92 ; backslash character, `\\\u0026#39; ?\\d =\u0026gt; 127 ; delete character, \u0026lt;del\u0026gt; 控制字符可以有多种表示方式,比如 c-i,这些都是对的:\n?\\^i ?\\^i ?\\c-i ?\\c-i 它们都对应数字 9。\nmeta 字符是用 修饰键(通常就是 alt 键)输入的字符。之所以称为修饰键,是因为这样输入的字符就是在其修饰字符的第 27 位由 0 变成 1 而成,也就是如下操作:\n1 2 (logior (lsh 1 27) ?a) ; =\u0026gt; 134217793 ?\\m-a ; =\u0026gt; 134217793 你可以用 \\m- 代表 meta 键,加上修饰的字符就是新生成的字符。比如:?\\m-a, ?\\m-\\c-b. 后面这个也可以写成 ?\\c-\\m-b。\n如果你还记得前面说过字符串里的字符不能超过 524287 的话,这就可以看出字符串是不能放下一个 meta 字符的。所以按键序列在这时只能用 vector 来储存。\n其它的修饰键也是类似的。emacs 用 2**25 位来表示 shift 键,2**24 对应 hyper,2**23 对应 super,2**22 对应 alt。\n测试函数 字符串测试使用 stringp ,没有 charp,因为字符就是整数。 string-or-null-p 当对象是一个字符或 nil 时返回 t。 char-or-string-p 测试是否是字符串或者字符类型。比较头疼的是 emacs 没有测试字符串是否为空的函数。这是我用的这个测试函数,使用前要测试字符串是否为 nil:\n1 2 (defun string-emptyp (str) (not (string\u0026lt; \u0026#34;\u0026#34; str))) 构造函数 产生一个字符串可以用 make-string 。这样生成的字符串包含的字符都是一样的。要生成不同的字符串可以用 string 函数。\n1 2 (make-string 5 ?x) ; =\u0026gt; \u0026#34;xxxxx\u0026#34; (string ?a ?b ?c) ; =\u0026gt; \u0026#34;abc\u0026#34; 在已有的字符串生成新的字符串的方法有 substring, concat 。 substring 的后两个参数是起点和终点的位置。如果终点越界或者终点比起点小都会产生一个错误。这个在使用 substring 时要特别小心。\n1 2 3 (substring \u0026#34;0123456789\u0026#34; 3) ; =\u0026gt; \u0026#34;3456789\u0026#34; (substring \u0026#34;0123456789\u0026#34; 3 5) ; =\u0026gt; \u0026#34;34\u0026#34; (substring \u0026#34;0123456789\u0026#34; -3 -1) ; =\u0026gt; \u0026#34;78\u0026#34; concat 函数相对简单,就是把几个字符串连接起来。\n:: 字符串的切片和拼接,编程必备!\n字符串比较 char-equal 可以比较两个字符是否相等。与整数比较不同,这个函数还考虑了大小写。如果 case-fold-search 变量是 t 时,这个函数的字符比较是忽略大小写的。编程时要小心,因为通常 case-fold-search 都是 t,这样如果要考虑字符的大小写时就不能用 char-equal 函数了。\n字符串比较使用 string= ,string-equal 是一个别名。\nstring\u0026lt; 是按字典序比较两个字符串, string-less 是它的别名。空字符串小于所有字符串,除了空字符串。前面 string-emptyp 就是用这个特性。当然直接用 length 检测字符串长度应该也可以,还可以省去检测字符串是否为空。没有 string\u0026gt; 函数。\n转换函数 字符转换成字符串可以用 char-to-string 函数,字符串转换成字符可以用 string-to-char ,当然只是返回字符串的第一个字符。\n数字和字符串之间的转换可以用 number-to-string 和 string-to-number 。其中 string-to-number 可以设置字符串的进制,可以从 2 到 16。 number-to-string 只能转换成 10 进制的数字。如果要输出八进制或者十六进制,可以用 format 函数:\n1 2 3 4 (string-to-number \u0026#34;256\u0026#34;) ; =\u0026gt; 256 (number-to-string 256) ; =\u0026gt; \u0026#34;256\u0026#34; (format \u0026#34;%#o\u0026#34; 256) ; =\u0026gt; \u0026#34;0400\u0026#34; (format \u0026#34;%#x\u0026#34; 256) ; =\u0026gt; \u0026#34;0x100\u0026#34; 如果要输出成二进制,好像没有现成的函数了。calculator 库倒是可以,这是我写的函数:\n1 2 3 4 5 6 (defun number-to-bin-string (number) (require \u0026#39;calculator) (let ((calculator-output-radix \u0026#39;bin) (calculator-radix-grouping-mode nil)) (calculator-number-to-string number))) (number-to-bin-string 256) ; =\u0026gt; \u0026#34;100000000\u0026#34; 其它数据类型现在还没有学到,不过可以先了解一下吧。 concat 可以把一个字符构成的列表或者向量转换成字符串, vconcat 可以把一个字符串转换成一个向量, append 可以把一个字符串转换成一个列表。\n1 2 3 4 (concat \u0026#39;(?a ?b ?c ?d ?e)) ; =\u0026gt; \u0026#34;abcde\u0026#34; (concat [?a ?b ?c ?d ?e]) ; =\u0026gt; \u0026#34;abcde\u0026#34; (vconcat \u0026#34;abdef\u0026#34;) ; =\u0026gt; [97 98 100 101 102] (append \u0026#34;abcdef\u0026#34; nil) ; =\u0026gt; (97 98 99 100 101 102) 大小写转换使用的是 downcase 和 upcase 两个函数。这两个函数的参数既可以字符串,也可以是字符。capitalize 可以使字符串中单词的第一个字符大写,其它字符小写。 upcase-initials 只使第一个单词的第一个字符大写,其它字符小写。 这两个函数的参数如果是一个字符,那么只让这个字符大写。比如:\n1 2 3 4 5 6 (downcase \u0026#34;the cat in the hat\u0026#34;) ; =\u0026gt; \u0026#34;the cat in the hat\u0026#34; (downcase ?x) ; =\u0026gt; 120 (upcase \u0026#34;the cat in the hat\u0026#34;) ; =\u0026gt; \u0026#34;the cat in the hat\u0026#34; (upcase ?x) ; =\u0026gt; 88 (capitalize \u0026#34;the cat in the hat\u0026#34;) ; =\u0026gt; \u0026#34;the cat in the hat\u0026#34; (upcase-initials \u0026#34;the cat in the hat\u0026#34;) ; =\u0026gt; \u0026#34;the cat in the hat\u0026#34; 💡 这里 upcase-initials 的作用应该是使单词的第一个字符大写,其它字符大小写保持不变。\n格式化字符串 format 类似于 c 语言里的 printf 可以实现对象的字符串化。数字的格式化和 printf 的参数差不多,值得一提的是 \u0026quot;%s\u0026quot; 这个格式化形式,它可以把对象的输出形式转换成字符串,这在调试时是很有用的。\n查找和替换 字符串查找的核心函数是 string-match 。这个函数可以 从指定的位置对字符串进行正则表达式匹配 ,如果匹配成功,则返回匹配的起点,如:\n1 2 (string-match \u0026#34;34\u0026#34; \u0026#34;01234567890123456789\u0026#34;) ; =\u0026gt; 3 (string-match \u0026#34;34\u0026#34; \u0026#34;01234567890123456789\u0026#34; 10) ; =\u0026gt; 13 注意 string-match 的参数是一个 regexp。 emacs 好象没有内建的查找子串的函数。如果你想把 string-match 作为一个查找子串的函数,可以先用 regexp-quote 函数先处理一下子串。比如:\n1 2 (string-match \u0026#34;2*\u0026#34; \u0026#34;232*3=696\u0026#34;) ; =\u0026gt; 0 (string-match (regexp-quote \u0026#34;2*\u0026#34;) \u0026#34;232*3=696\u0026#34;) ; =\u0026gt; 2 事实上, string-match 不只是查找字符串,它更重要的功能是捕捉匹配的字符串。如果你对正则表达式不了解,可能需要先找一本书,先了解一下什么是正则表达式。 string-match 在查找的同时,还会记录下每个要捕捉的字符串的位置。这个位置可以在匹配后用 match-data、 match-beginning 和 match-end 等函数来获得。先看一下例子:\n1 2 3 (progn (string-match \u0026#34;3\\\\(4\\\\)\u0026#34; \u0026#34;01234567890123456789\u0026#34;) (match-data)) ; =\u0026gt; (3 5 4 5) 最后返回这个数字是什么意思呢?正则表达式捕捉的字符串按括号的顺序对应一个序号,整个模式对应序号 0,第一个括号对应序号 1,第二个括号对应序号 2,以此类推。所以 “3(4)” 这个正则表达式中有序号 0 和 1,最后 match-data 返回的一系列数字对应的分别是要捕捉字符串的起点和终点位置,也就是说子串 “34” 起点从位置 3 开始,到位置 5 结束,而捕捉的字符串 “4” 的起点是从 4 开始,到 5 结束。这些位置可以用 match-beginning 和 match-end 函数用对应的序号得到。要注意的是,起点位置是捕捉字符串的第一个字符的位置,而终点位置不是捕捉的字符串最后一个字符的位置,而是下一个字符的位置。这个性质对于循环是很方便的。比如要查找上面这个字符串中所有 34 出现的位置:\n1 2 3 4 (let ((start 0)) (while (string-match \u0026#34;34\u0026#34; \u0026#34;01234567890123456789\u0026#34; start) (princ (format \u0026#34;find at %d\\n\u0026#34; (match-beginning 0))) (setq start (match-end 0)))) 查找会了,就要学习替换了。替换使用的函数是 replace-match 。这个函数既可以用于字符串的替换,也可以用于缓冲区的文本替换。对于字符串的替换, replace-match 只是按给定的序号把字符串中的那一部分用提供的字符串替换了而已:\n1 2 3 4 5 (let ((str \u0026#34;01234567890123456789\u0026#34;)) (string-match \u0026#34;34\u0026#34; str) (princ (replace-match \u0026#34;x\u0026#34; nil nil str 0)) (princ \u0026#34;\\n\u0026#34;) (princ str)) 可以看出 replace-match 返回的字符串是替换后的新字符串,原字符串被没有改变。\n如果你想挑战一下,想想怎样把上面这个字符串中所有的 34 都替换掉?如果想就使用同一个字符串来存储,可能对于固定的字符串,这个还容易一些,如果不是的话,就要花一些脑筋了,因为替换之后,新的字符串下一个搜索起点的位置就不能用 (match-end 0) 给出来的位置了,而是要扣除替换的字符串和被替换的字符串长度的差值。\nemacs 对字符串的替换有一个函数 replace-regexp-in-string 。这个函数的实现方法是把每次匹配部分之前的子串收集起来,最后再把所有字符串连接起来。\n单字符的替换有 subst-char-in-string 函数。但是 emacs 没有类似 perl 函数或者程序 tr 那样进行字符替换的函数。只能自己建表进行循环操作了。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ;; 测试函数 (stringp object) (string-or-null-p object) (char-or-string-p object) ;; 构建函数 (make-string length init) (string \u0026amp;rest characters) (substring string from \u0026amp;optional to) (concat \u0026amp;rest sequences) ;; 比较函数 (char-equal c1 c2) (string= s1 s2) (string-equal s1 s2) (string\u0026lt; s1 s2) ;; 转换函数 (char-to-string char) (string-to-char string) (number-to-string number) (string-to-number string \u0026amp;optional base) (downcase obj) (upcase obj) (capitalize obj) (upcase-initials obj) (format string \u0026amp;rest objects) ;; 查找与替换 (string-match regexp string \u0026amp;optional start) (replace-match newtext \u0026amp;optional fixedcase literal string subexp) (replace-regexp-in-string regexp rep string \u0026amp;optional fixedcase literal subexp start) (subst-char-in-string fromchar tochar string \u0026amp;optional inplace) 基本数据类型之三 – cons cell 和列表 如果从概念上来说,cons cell 其实非常简单的,就是两个有顺序的元素。第一个叫 car,第二个就 cdr。car 和 cdr 名字来自于 lisp。它最初在 ibm 704 机器上的实现。在这种机器有一种取址模式,使人可以访问一个存储地址中的“地址(address)”部分和“减量(decrement)”部分。car 指令用于取出地址部分,表示 (contents of address part of register),cdr 指令用于取出地址的减量部分 (contents of the decrement part of register)。cons cell 也就是 construction of cells。car 函数用于取得 cons cell 的 car 部分,cdr 取得 cons cell 的 cdr 部分。cons cell 如此简单,但是它却能衍生出许多高级的数据结构,比如链表,树,关联表等等。\ncons cell 的读入语法是用 . 分开两个部分,比如:\n1 2 3 4 5 \u0026#39;(1 . 2) ; =\u0026gt; (1 . 2) \u0026#39;(?a . 1) ; =\u0026gt; (97 . 1) \u0026#39;(1 . \u0026#34;a\u0026#34;) ; =\u0026gt; (1 . \u0026#34;a\u0026#34;) \u0026#39;(1 . nil) ; =\u0026gt; (1) \u0026#39;(nil . nil) ; =\u0026gt; (nil) 注意到前面的表达式中都有一个 ' 号,这是什么意思呢?其实理解了 eval-last-sexp 的作用就能明白了。 eval-last-sexp 其实包含了两个步骤, 一是读入前一个 s-表达式,二是对读入的 s-表达式求值 。这样如果读入的 s-表达式是一个 cons cell 的话,求值时会把这个 cons cell 的第一个元素作为一个函数来调用。而事实上,前面这些例子的第一个元素都不是一个函数,这样就会产生一个错误 invalid-function。之所以前面没有遇到这个问题,那是因为前面数字和字符串是一类特殊的 s-表达式,它们求值后和求值前是不变,称为 自求值表达式 (self-evaluating form)。 ' 号其实是一个特殊的函数 quote ,它的作用是 将它的参数返回而不作求值 。 '(1 . 2) 等价于 (quote (1 . 2))。为了证明 cons cell 的读入语法确实就是它的输出形式,可以看下面这个语句:\n1 (read \u0026#34;(1 . 2)\u0026#34;) ; =\u0026gt; (1 . 2) 列表包括了 cons cell。但是列表中有一个特殊的元素 - 空表 nil 。\n1 2 nil ; =\u0026gt; nil \u0026#39;() ; =\u0026gt; nil 空表不是一个 cons cell,因为它没有 car 和 cdr 两个部分,事实上空表里没有任何内容。但是为了编程的方便,可以认为 nil 的 car 和 cdr 都是 nil:\n1 2 (car nil) ; =\u0026gt; nil (cdr nil) ; =\u0026gt; nil 按列表最后一个 cons cell 的 cdr 部分的类型分,可以把列表分为三类。如果它是 nil 的话,这个列表也称为“真列表”(true list)。如果既不是 nil 也不是一个 cons cell,则这个列表称为“点列表”(dotted list)。还有一种可能,它指向列表中之前的一个 cons cell,则称为环形列表 (circular list)。这里分别给出一个例子:\n1 2 3 \u0026#39;(1 2 3) ; =\u0026gt; (1 2 3) \u0026#39;(1 2 . 3) ; =\u0026gt; (1 2 . 3) \u0026#39;(1 . #1=(2 3 . #1#)) ; =\u0026gt; (1 2 3 . #1) 从这个例子可以看出前两种列表的读入语法和输出形式都是相同的,而环形列表的读入语法是很古怪的,输出形式不能作为环形列表的读入形式。\n如果把真列表最后一个 cons cell 的 nil 省略不写,也就是 (1 . nil) 简写成 (1) ,把 ( obj1 . ( obj2 . list)) 简写成 (obj1 obj2 . list) ,那么列表最后可以写成一个用括号括起的元素列表:\n1 \u0026#39;(1 . (2 . (3 . nil))) ; =\u0026gt; (1 2 3) 尽管这样写是清爽多了,但是,我觉得看一个列表时还是在脑子里反映的前面的形式,这样在和复杂的 cons cell 打交道时就不会搞不清楚这个 cons cell 的 cdr 是一个列表呢,还是一个元素或者是嵌套的列表。\n测试函数 测试一个对象是否是 cons cell 用 consp ,是否是列表用 listp 。\n1 2 3 4 5 6 (consp \u0026#39;(1 . 2)) ; =\u0026gt; t (consp \u0026#39;(1 . (2 . nil))) ; =\u0026gt; t (consp nil) ; =\u0026gt; nil (listp \u0026#39;(1 . 2)) ; =\u0026gt; t (listp \u0026#39;(1 . (2 . nil))) ; =\u0026gt; t (listp nil) ; =\u0026gt; t 没有内建的方法测试一个列表是不是一个真列表。通常如果一个函数需要一个真列表作为参数,都是在运行时发出错误,而不是进行参数检查,因为检查一个列表是真列表的代价比较高。\n测试一个对象是否是 nil 用 null 函数。只有当对象是空表时,null 才返回空值。\n构造函数 生成一个 cons cell 可以用 cons 函数。比如:\n1 2 (cons 1 2) ; =\u0026gt; (1 . 2) (cons 1 \u0026#39;()) ; =\u0026gt; (1) 也是在列表前面增加元素的方法。比如:\n1 2 (setq foo \u0026#39;(a b)) ; =\u0026gt; (a b) (cons \u0026#39;x foo) ; =\u0026gt; (x a b) 值得注意的是前面这个例子的 foo 值并没有改变。事实上有一个宏 push 可以加入元素的同时改变列表的值:\n1 2 (push \u0026#39;x foo) ; =\u0026gt; (x a b) foo ; =\u0026gt; (x a b) 生成一个列表的函数是 list 。比如:\n1 (list 1 2 3) ; =\u0026gt; (1 2 3) 可能这时你有一个疑惑,前面产生一个列表,我常用 quote (也就是 ' 符号)这个函数,它和这个 cons 和 list 函数有什么区别呢?其实区别是很明显的,quote 是把参数直接返回不进行求值,而 list 和 cons 是对参数求值后再生成一个列表或者 cons cell。看下面这个例子:\n1 2 \u0026#39;((+ 1 2) 3) ; =\u0026gt; ((+ 1 2) 3) (list (+ 1 2) 3) ; =\u0026gt; (3 3) 前一个生成的列表的 car 部分是 (+ 1 2) 这个列表,而后一个是先对 (+ 1 2) 求值得到 3 后再生成列表。\n思考题\n如果你觉得你有点明白的话,我提一个问题考考你:怎样用 list 函数构造一个 (a b c) 这样的列表呢?\n前面提到在列表前端增加元素的方法是用 cons ,在列表后端增加元素的函数是用 append 。比如:\n1 (append \u0026#39;(a b) \u0026#39;(c)) ; =\u0026gt; (a b c) append 的功能可以认为它是把第一个参数最后一个列表的 nil 换成第二个参数,比如前面这个例子,第一个参数写成 cons cell 表示方式是 (a . (b . nil)) ,把这个 nil 替换成 (c) 就成了 (a . (b . (c))) 。对于多个参数的情况也是一样的,依次把下一个参数替换新列表最后一个 nil 就是最后的结果了。\n1 (append \u0026#39;(a b) \u0026#39;(c) \u0026#39;(d)) ; =\u0026gt; (a b c d) 一般来说 append 的参数都要是列表,但是最后一个参数可以不是一个列表,这也不违背前面说的,因为 cons cell 的 cdr 部分本来就可以是任何对象:\n1 (append \u0026#39;(a b) \u0026#39;c) ; =\u0026gt; (a b . c) 这样得到的结果就不再是一个真列表了,如果再进行 append 操作就会产生一个错误。\n如果你写过 c 的链表类型,可能就知道如果链表只保留一个指针,那么链表只能在一端增加元素。elisp 的列表类型也是类似的,用 cons 在列表前增加元素比用 append 要快得多。\nappend 的参数不限于列表,还可以是字符串或者向量。前面字符串里已经提到可以把一个字符串转换成一个字符列表,同样可能把向量转换成一个列表:\n1 (append [a b] \u0026#34;cd\u0026#34; nil) ; =\u0026gt; (a b 99 100) 注意前面最后一个参数 nil 是必要的,不然你可以想象得到的结果是什么 – (a b . \u0026quot;cd\u0026quot;)。\n把列表当数组用 要得到列表或者 cons cell 里元素,唯一的方法是用 car 和 cdr 函数。很容易明白,car 就是取得 cons cell 的 car 部分,cdr 函数就是取得 cons cell 的 cdr 部分。通过这两个函数,我们就能访问 cons cell 和列表中的任何元素。\n思考题\n你如果知道 elisp 的函数如果定义,并知道 if 的使用方法,不妨自己写一个函数来取得一个列表的第 n 个 cdr。\n通过使用 elisp 提供的函数,我们事实上是可以把列表当数组来用。依惯例,我们用 car 来访问列表的第一个元素,cadr 来访问第二个元素,再往后就没有这样的函数了,可以用 nth 函数来访问:\n1 (nth 3 \u0026#39;(0 1 2 3 4 5)) ; =\u0026gt; 3 获得列表一个区间的函数有 nthcdr、last 和 butlast。nthcdr 和 last 比较类似,它们都是返回列表后端的列表。nthcdr 函数返回第 n 个元素后的列表:\n1 (nthcdr 2 \u0026#39;(0 1 2 3 4 5)) ; =\u0026gt; (2 3 4 5) last 函数返回倒数 n 个长度的列表:\n1 (last \u0026#39;(0 1 2 3 4 5) 2) ; =\u0026gt; (4 5) butlast 和前两个函数不同,返回的除了倒数 n 个元素的列表。\n1 (butlast \u0026#39;(0 1 2 3 4 5) 2) ; =\u0026gt; (0 1 2 3) 思考题\n如何得到某个区间(比如从 3 到 5 之间)的列表(提示列表长度可以用 length 函数得到):\n1 (my-subseq \u0026#39;(0 1 2 3 4 5) 2 5) ; =\u0026gt; (2 3 4) 使用前面这几个函数访问列表是没有问题了。但是你也可以想象,链表这种数据结构是不适合随机访问的,代价比较高,如果你的代码中频繁使用这样的函数或者对一个很长的列表使用这样的函数,就应该考虑是不是应该用数组来实现。\n直到现在为止,我们用到的函数都不会修改一个已有的变量。这是函数式编程的一个特点。只用这些函数编写的代码是很容易调试的,因为你不用去考虑一个变量在执行一个代码后就改变了,不用考虑变量的引用情况等等。下面就要结束这样轻松的学习了。\n首先学习怎样修改一个 cons cell 的内容。首先 setcar 和 setcdr 可以修改一个 cons cell 的 car 部分和 cdr 部分。比如:\n1 2 3 4 5 (setq foo \u0026#39;(a b c)) ; =\u0026gt; (a b c) (setcar foo \u0026#39;x) ; =\u0026gt; x foo ; =\u0026gt; (x b c) (setcdr foo \u0026#39;(y z)) ; =\u0026gt; (y z) foo ; =\u0026gt; (x y z) 思考题\n好像很简单是吧。我出一个比较 bt 的一个问题,下面代码运行后 foo 是什么东西呢?\n1 2 (setq foo \u0026#39;(a b c)) ; =\u0026gt; (a b c) (setcdr foo foo) 现在来考虑一下,怎样像数组那样直接修改列表。使用 setcar 和 nthcdr 的组合就可以实现了:\n1 2 3 4 5 (setq foo \u0026#39;(1 2 3)) ; =\u0026gt; (1 2 3) (setcar foo \u0026#39;a) ; =\u0026gt; a (setcar (cdr foo) \u0026#39;b) ; =\u0026gt; b (setcar (nthcdr 2 foo) \u0026#39;c) ; =\u0026gt; c foo ; =\u0026gt; (a b c) 把列表当堆栈用 前面已经提到过可以用 push 向列表头端增加元素,在结合 pop 函数,列表就可以做为一个堆栈了。\n1 2 3 4 5 (setq foo nil) ; =\u0026gt; nil (push \u0026#39;a foo) ; =\u0026gt; (a) (push \u0026#39;b foo) ; =\u0026gt; (b a) (pop foo) ; =\u0026gt; b foo ; =\u0026gt; (a) 重排列表 如果一直用 push 往列表里添加元素有一个问题是这样得到的列表和加入的顺序是相反的。通常我们需要得到一个反向的列表。reverse 函数可以做到这一点:\n1 2 (setq foo \u0026#39;(a b c)) ; =\u0026gt; (a b c) (reverse foo) ; =\u0026gt; (c b a) 需要注意的是使用 reverse 后 foo 值并没有改变。不要怪我太啰唆,如果你看到一个函数 nreverse,而且确实它能返回逆序的列表,不明所以就到处乱用,迟早会写出一个错误的函数。这个 nreverse 和前面的 reverse 差别就在于它是一个有破坏性的函数,也就是说它会修改它的参数。\n1 2 (nreverse foo) ; =\u0026gt; (c b a) foo ; =\u0026gt; (a) 为什么现在 foo 指向的是列表的末端呢?如果你实现过链表就知道,逆序操作是可以在原链表上进行的,这样原来头部指针会变成链表的尾端。列表也是(应该是,我也没有看过实现)这个原理。使用 nreverse 的唯一的好处是速度快,省资源。所以如果你只是想得到逆序后的列表就放心用 nreverse,否则还是用 reverse 的好。\nelisp 还有一些是具有破坏性的函数。最常用的就是 sort 函数:\n1 2 3 (setq foo \u0026#39;(3 2 4 1 5)) ; =\u0026gt; (3 2 4 1 5) (sort foo \u0026#39;\u0026lt;) ; =\u0026gt; (1 2 3 4 5) foo ; =\u0026gt; (3 4 5) 这一点请一定要记住,我就曾经在 sort 函数上犯了好几次错误。那如果我既要保留原列表,又要进行 sort 操作怎么办呢?可以用 copy-sequence 函数。这个函数只对列表进行复制,返回的列表的元素还是原列表里的元素,不会拷贝列表的元素。\nnconc 和 append 功能相似,但是它会修改除最后一个参数以外的所有的参数,nbutlast 和 butlast 功能相似,也会修改参数。这些函数都是在效率优先时才使用。总而言之,以 n 开头的函数都要慎用。\n把列表当集合用 列表可以作为无序的集合。合并集合用 append 函数。去除重复的 equal 元素用 delete-dups。查找一个元素是否在列表中,如果测试函数是用 eq,就用 memq,如果测试用 equal,可以用 member。删除列表中的指定的元素,测试函数为 eq 对应 delq 函数,equal 对应 delete。还有两个函数 remq 和 remove 也是删除指定元素。它们的差别是 delq 和 delete 可能会修改参数,而 remq 和 remove 总是返回删除后列表的拷贝。注意前面这是说的是可能会修改参数的值,也就是说可能不会,所以保险起见,用 delq 和 delete 函数要么只用返回值,要么用 setq 设置参数的值为返回值。\n1 2 3 4 5 6 7 (setq foo \u0026#39;(a b c)) ; =\u0026gt; (a b c) (remq \u0026#39;b foo) ; =\u0026gt; (a c) foo ; =\u0026gt; (a b c) (delq \u0026#39;b foo) ; =\u0026gt; (a c) foo ; =\u0026gt; (a c) (delq \u0026#39;a foo) ; =\u0026gt; (c) foo ; =\u0026gt; (a c) 把列表当关联表 用在 elisp 编程中,列表最常用的形式应该是作为一个关联表了。所谓关联表,就是可以用一个字符串(通常叫关键字,key)来查找对应值的数据结构。由列表实现的关联表有一个专门的名字叫 association list。尽管 elisp 里也有 hash table,但是 hash table 相比于 association list 至少这样几个缺点:\nhash table 里的关键字(key)是无序的,而 association list 的关键字 可以按想要的顺序排列 hash table 没有列表那样丰富的函数,只有一个 maphash 函数可以遍历列 表。而 association list 就是一个列表,所有列表函数都能适用 hash table 没有读入语法和输入形式,这对于调试和使用都带来很多不便 所以 elisp 的 hash table 不是一个首要的数据结构,只要不对效率要求很高,通常直接用 association list。数组可以作为关联表,但是数组不适合作为与人交互使用数据结构(毕竟一个有意义的名字比纯数字的下标更适合人脑)。所以关联表的地位在 elisp 中就非比寻常了,emacs 为关联表专门用 c 程序实现了查找的相关函数以提高程序的效率。在 association list 中关键字是放在元素的 car 部分,与它对应的数据放在这个元素的 cdr 部分。根据比较方法的不同,有 assq 和 assoc 两个函数,它们分别对应查找使用 eq 和 equal 两种方法。例如:\n1 2 (assoc \u0026#34;a\u0026#34; \u0026#39;((\u0026#34;a\u0026#34; 97) (\u0026#34;b\u0026#34; 98))) ; =\u0026gt; (\u0026#34;a\u0026#34; 97) (assq \u0026#39;a \u0026#39;((a . 97) (b . 98))) ; =\u0026gt; (a . 97) 通常我们只需要查找对应的数据,所以一般来说都要用 cdr 来得到对应的数据:\n1 2 (cdr (assoc \u0026#34;a\u0026#34; \u0026#39;((\u0026#34;a\u0026#34; 97) (\u0026#34;b\u0026#34; 98)))) ; =\u0026gt; (97) (cdr (assq \u0026#39;a \u0026#39;((a . 97) (b . 98)))) ; =\u0026gt; 97 assoc-default 可以一步完成这样的操作:\n1 (assoc-default \u0026#34;a\u0026#34; \u0026#39;((\u0026#34;a\u0026#34; 97) (\u0026#34;b\u0026#34; 98))) ; =\u0026gt; (97) 如果查找用的键值(key)对应的数据也可以作为一个键值的话,还可以用 rassoc 和 rassq 来根据数据查找键值:\n1 2 (rassoc \u0026#39;(97) \u0026#39;((\u0026#34;a\u0026#34; 97) (\u0026#34;b\u0026#34; 98))) ; =\u0026gt; (\u0026#34;a\u0026#34; 97) (rassq \u0026#39;97 \u0026#39;((a . 97) (b . 98))) ; =\u0026gt; (a . 97) 如果要修改关键字对应的值,最省事的作法就是用 cons 把新的键值对加到列表的头端。但是这会让列表越来越长,浪费空间。如果要替换已经存在的值,一个想法就是用 setcdr 来更改键值对应的数据。但是在更改之前要先确定这个键值在对应的列表里,否则会产生一个错误。另一个想法是用 assoc 查找到对应的元素,再用 delq 删除这个数据,然后用 cons 加到列表里:\n1 2 3 4 5 6 7 8 9 10 (setq foo \u0026#39;((\u0026#34;a\u0026#34; . 97) (\u0026#34;b\u0026#34; . 98))) ; =\u0026gt; ((\u0026#34;a\u0026#34; . 97) (\u0026#34;b\u0026#34; . 98)) ;; update value by setcdr (if (setq bar (assoc \u0026#34;a\u0026#34; foo)) (setcdr bar \u0026#34;this is a\u0026#34;) (setq foo (cons \u0026#39;(\u0026#34;a\u0026#34; . \u0026#34;this is a\u0026#34;) foo))) ; =\u0026gt; \u0026#34;this is a\u0026#34; foo ; =\u0026gt; ((\u0026#34;a\u0026#34; . \u0026#34;this is a\u0026#34;) (\u0026#34;b\u0026#34; . 98)) ;; update value by delq and cons (setq foo (cons \u0026#39;(\u0026#34;a\u0026#34; . 97) (delq (assoc \u0026#34;a\u0026#34; foo) foo))) ; =\u0026gt; ((\u0026#34;a\u0026#34; . 97) (\u0026#34;b\u0026#34; . 98)) 如果不对顺序有要求的话,推荐用后一种方法吧。这样代码简洁,而且让最近更新的元素放到列表前端,查找更快。\n把列表当树用 列表的第一个元素如果作为结点的数据,其它元素看作是子节点,就是一个树了。由于树的操作都涉及递归,现在还没有说到函数,我就不介绍了。(其实是我不太熟,就不班门弄斧了)。\n遍历列表 遍历列表最常用的函数就是 mapc 和 mapcar 了。它们的第一个参数都是一个函数,这个函数只接受一个参数,每次处理一个列表里的元素。这两个函数唯一的差别是前者返回的还是输入的列表,而 mapcar 返回的函数返回值构成的列表:\n1 2 (mapc \u0026#39;1+ \u0026#39;(1 2 3)) ; =\u0026gt; (1 2 3) (mapcar \u0026#39;1+ \u0026#39;(1 2 3)) ; =\u0026gt; (2 3 4) 另一个比较常用的遍历列表的方法是用 dolist。它的形式是:\n1 (dolist (var list [result]) body...) 其中 var 是一个临时变量,在 body 里可以用来得到列表中元素的值。使用 dolist 的好处是不用写 lambda 函数。一般情况下它的返回值是 nil,但是你也可以指定一个值作为返回值(我觉得这个特性没有什么用,只省了一步而已):\n1 2 3 4 5 (dolist (foo \u0026#39;(1 2 3)) (incf foo)) ; =\u0026gt; nil (setq bar nil) (dolist (foo \u0026#39;(1 2 3) bar) (push (incf foo) bar)) ; =\u0026gt; (4 3 2) 其它常用函数 如果看过一些函数式语言教程的话,一定对 fold(或叫 accumulate、reduce)和 filter 这些函数记忆深刻。不过 elisp 里好像没有提供这样的函数。remove-if 和 remove-if-not 可以作 filter 函数,但是它们是 cl 里的,自己用用没有关系,不能强迫别人也跟着用,所以不能写到 elisp 里。如果不用这两个函数,也不用别人的函数的话,自己实现不妨用这样的方法:\n1 2 3 4 5 6 7 (defun my-remove-if (predicate list) (delq nil (mapcar (lambda (n) (and (not (funcall predicate n)) n)) list))) (defun evenp (n) (= (% n 2) 0)) (my-remove-if \u0026#39;evenp \u0026#39;(0 1 2 3 4 5)) ; =\u0026gt; (1 3 5) fold 的操作只能用变量加循环或 mapc 操作来代替了:\n1 2 3 4 (defun my-fold-left (op initial list) (dolist (var list initial) (setq initial (funcall op initial var)))) (my-fold-left \u0026#39;+ 0 \u0026#39;(1 2 3 4)) ; =\u0026gt; 10 这里只是举个例子,事实上你不必写这样的函数,直接用函数里的遍历操作更好一些。\n产生数列常用的方法是用 number-sequence(这里不禁用说一次,不要再用 loop 产生 tab-stop-list 了,你们 too old 了)。不过这个函数好像 在 emacs21 时好像还没有。\n解析文本时一个很常用的操作是把字符串按分隔符分解,可以用 split-string 函数:\n1 (split-string \u0026#34;key = val\u0026#34; \u0026#34;\\\\s-*=\\\\s-*\u0026#34;) ; =\u0026gt; (\u0026#34;key\u0026#34; \u0026#34;val\u0026#34;) 与 split-string 对应是把几个字符串用一个分隔符连接起来,这可以用 mapconcat 完成。比如:\n1 (mapconcat \u0026#39;identity \u0026#39;(\u0026#34;a\u0026#34; \u0026#34;b\u0026#34; \u0026#34;c\u0026#34;) \u0026#34;\\t\u0026#34;) ; =\u0026gt; \u0026#34;a b c\u0026#34; identity 是一个特殊的函数,它会直接返回参数。mapconcat 第一个参数是一个函数,可以很灵活的使用。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 ;; 列表测试 (consp object) (listp object) (null object) ;; 列表构造 (cons car cdr) (list \u0026amp;rest objects) (append \u0026amp;rest sequences) ;; 访问列表元素 (car list) (cdr list) (cadr x) (caar x) (cddr x) (cdar x) (nth n list) (nthcdr n list) (last list \u0026amp;optional n) (butlast list \u0026amp;optional n) ;; 修改 cons cell (setcar cell newcar) (setcdr cell newcdr) ;; 列表操作 (push newelt listname) (pop listname) (reverse list) (nreverse list) (sort list predicate) (copy-sequence arg) (nconc \u0026amp;rest lists) (nbutlast list \u0026amp;optional n) ;; 集合函数 (delete-dups list) (memq elt list) (member elt list) (delq elt list) (delete elt seq) (remq elt list) (remove elt seq) ;; 关联列表 (assoc key list) (assq key list) (assoc-default key alist \u0026amp;optional test default) (rassoc key list) (rassq key list) ;; 遍历函数 (mapc function sequence) (mapcar function sequence) (dolist (var list [result]) body...) ;; 其它 (number-sequence from \u0026amp;optional to inc) (split-string string \u0026amp;optional separators omit-nulls) (mapconcat function sequence separator) (identity arg) 问题解答 用 list 生成 (a b c) 答案是 (list 'a 'b 'c)。很简单的一个问题。从这个例子可以看出为什么要想出 用 ’ 来输入列表。这就是程序员“懒”的美德呀!\nnthcdr 的一个实现 1 2 3 4 (defun my-nthcdr (n list) (if (or (null list) (= n 0)) (car list) (my-nthcdr (1- n) (cdr list)))) 这样的实现看上去很简洁,但是一个最大的问题的 elisp 的递归是有限的,所以如果想这个函数没有问题,还是用循环还实现比较好。\nmy-subseq 函数的定义 1 2 3 (defun my-subseq (list from \u0026amp;optional to) (if (null to) (nthcdr from list) (butlast (nthcdr from list) (- (length list) to)))) (setcdr foo foo) 是什么怪东西? 可能你已经想到了,这就是传说中的环呀。这在 info elisp - circular objects 里有介绍。elisp 里用到这样的环状列表并不多见,但是也不是没有,org 和 session 那个 bug 就是由于一个环状列表造成的。\n基本数据类型之四 – 数组和序列 序列是列表和数组的统称,也就是说列表和数组都是序列。它们的共性是内部的元素都是有序的。elisp 里的数组包括字符串、向量、char-table 和布尔向量。它们的关系可以用下面图表示:\n_____________________________________________\r| |\r| sequence |\r| ______ ________________________________ |\r| | | | | |\r| | list | | array | |\r| | | | ________ ________ | |\r| |______| | | | | | | |\r| | | vector | | string | | |\r| | |________| |________| | |\r| | ____________ _____________ | |\r| | | | | | | |\r| | | char-table | | bool-vector | | |\r| | |____________| |_____________| | |\r| |________________________________| |\r|_____________________________________________| 组有这样一些特性:\n数组内的元素都对应一个下标,第一个元素下标为 0,接下来是 1。数组内 的元素可以在常数时间内访问。 数组在创建之后就无法改变它的长度。 数组是自求值的。 数组里的元素都可以用 aref 来访问,用 aset 来设置。 向量可以看成是一种通用的数组,它的元素可以是任意的对象。而字符串是一种特殊的数组,它的元素只能是字符。如果元素是字符时,使用字符串相比向量更好,因为字符串需要的空间更少(只需要向量的 1/4),输出更直观,能用文本属性(text property),能使用 emacs 的 io 操作。但是有时必须使用向量,比如存储按键序列。\n由于 char-table 和 bool-vector 使用较少,而且较难理解,这里就不介绍了。\n测试函数 sequencep 用来测试一个对象是否是一个序列。arrayp 测试对象是否是数组。vectorp、char-table-p 和 bool-vector-p 分别测试对象是否是向量、char-table、bool-vector。\n序列的通用函数 一直没有提到一个重要的函数 length,它可以得到序列的长度。但是这个函数只对真列表有效。对于一个点列表和环形列表这个函数就不适用了。点列表会出参数类型不对的错误,而环形列表就更危险,会陷入死循环。如果不确定参数类型,不妨用 safe-length。比如:\n1 2 (safe-length \u0026#39;(a . b)) ; =\u0026gt; 1 (safe-length \u0026#39;#1=(1 2 . #1#)) ; =\u0026gt; 3 思考题\n写一个函数来检测列表是否是一个环形列表。由于现在还没有介绍 let 绑定和循环,不过如果会函数定义,还是可以用递归来实现的。\n取得序列里第 n 个元素可以用 elt 函数。但是我建议,对于已知类型的序列,还是用对应的函数比较好。也就是说,如果是列表就用 nth,如果是数组就用 aref。这样一方面是省去 elt 内部的判断,另一方面读代码时能很清楚知道序列的类型。\ncopy-sequence 在前面已经提到了。不过同样 copy-sequence 不能用于点列表和环形列表。对于点列表可以用 copy-tree 函数。环形列表就没有办法复制了。 好在这样的数据结构很少用到。\n数组操作 创建字符串已经说过了。创建向量可以用 vector 函数:\n1 (vector \u0026#39;foo 23 [bar baz] \u0026#34;rats\u0026#34;) 当然也可以直接用向量的读入语法创建向量,但是由于数组是自求值的,所以这样得到的向量和原来是一样的,也就是说参数不进行求值,看下面的例子就明白了:\n1 2 3 foo ; =\u0026gt; (a b) [foo] ; =\u0026gt; [foo] (vector foo) ; =\u0026gt; [(a b)] 用 make-vector 可以生成元素相同的向量。\n1 (make-vector 9 \u0026#39;z) ; =\u0026gt; [z z z z z z z z z] fillarray 可以把整个数组用某个元素填充。\n1 (fillarray (make-vector 3 \u0026#39;z) 5) ; =\u0026gt; [5 5 5] aref 和 aset 可以用于访问和修改数组的元素。如果使用下标超出数组长度的话,会产生一个错误。所以要先确定数组的长度才能用这两个函数。\nvconcat 可以把多个序列用 vconcat 连接成一个向量。但是这个序列必须是真列表。这也是把列表转换成向量的方法。\n1 (vconcat [a b c] \u0026#34;aa\u0026#34; \u0026#39;(foo (6 7))) ; =\u0026gt; [a b c 97 97 foo (6 7)] 把向量转换成列表可以用 append 函数,这在前一节中已经提到。\n思考题\n如果知道 elisp 的 let 绑定和循环的使用方法,不妨试试实现一个 elisp 的 tr 函数,它接受三个参数,一是要操作的字符串,另外两个分别是要替换的字符集,和对应的替换后的字符集(当它是空集时,删除字符串中所有对应的字符)。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ;; 测试函数 (sequencep object) (arrayp object) (vectorp object) (char-table-p object) (bool-vector-p object) ;; 序列函数 (length sequence) (safe-length list) (elt sequence n) (copy-sequence arg) (copy-tree tree \u0026amp;optional vecp) ;; 数组函数 (vector \u0026amp;rest objects) (make-vector length init) (aref array idx) (aset array idx newelt) (vconcat \u0026amp;rest sequences) (append \u0026amp;rest sequences) 问题解答 测试列表是否是环形列表 这个算法是从 safe-length 定义中得到的。你可以直接看它的源码。下面是我写的函数。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 (defun circular-list-p (list) (and (consp list) (circular-list-p-1 (cdr list) list 0))) (defun circular-list-p-1 (tail halftail len) (if (eq tail halftail) t (if (consp tail) (circular-list-p-1 (cdr tail) (if (= (% len 2) 0) (cdr halftail) halftail) (1+ len)) nil))) 转换字符的 tr 函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (defun my-tr (str from to) (if (= (length to) 0) ; 空字符串 (progn (setq from (append from nil)) (concat (delq nil (mapcar (lambda (c) (if (member c from) nil c)) (append str nil))))) (let (table newstr pair) ;; 构建转换表 (dotimes (i (length from)) (push (cons (aref from i) (aref to i)) table)) (dotimes (i (length str)) (push (if (setq pair (assoc (aref str i) table)) (cdr pair) (aref str i)) newstr)) (concat (nreverse newstr) nil)))) 这里用到的 dotimes 函数相当于一个 c 里的 for 循环。如果改写成 while 循环,相当于:\n1 2 3 4 5 (let (var) (while (\u0026lt; var count) body (setq var (1+ var))) result) 从这个例子也可以看出,由于列表具有丰富的函数和可变长度,使列表比数组使用更方便,而且效率往往更高。\n基本数据类型之五 – 符号 符号是有名字的对象。可能这么说有点抽象。作个不恰当的比方,符号可以看作是 c 语言里的指针。通过符号你可以得到和这个符号相关联的信息,比如值,函数,属性列表等等。\n首先必须知道的是符号的命名规则。符号名字可以含有任何字符。大多数的符号名字只含有字母、数字和标点“-+=*/”。这样的名字不需要其它标点。名字前缀要足够把符号名和数字区分开来,如果需要的话,可以在前面用 \\ 表示为符号,比如:\n1 2 3 (symbolp \u0026#39;+1) ; =\u0026gt; nil (symbolp \u0026#39;\\+1) ; =\u0026gt; t (symbol-name \u0026#39;\\+1) ; =\u0026gt; \u0026#34;+1\u0026#34; 其它字符 _~!@$%^\u0026amp;:\u0026lt;\u0026gt;{}? 用的比较少。但是也可以直接作为符号的名字。任何其它字符都可以用 \\ 转义后用在符号名字里。但是和字符串里字符表示不同,\\ 转义后只是表示其后的字符,比如 \\t 代表的字符 t,而不是制表符。如果要在符号名里使用制表符,必须在 \\ 后加上制表符本身。\n符号名是区分大小写的。这里有一些符号名的例子:\n1 2 3 4 5 6 7 8 foo ; 名为 `foo\u0026#39; 的符号 foo ; 名为 `foo\u0026#39; 的符号,和 `foo\u0026#39; 不同 char-to-string ; 名为 `char-to-string\u0026#39; 的符号 1+ ; 名为 `1+\u0026#39; 的符号 (不是整数 `+1\u0026#39;) \\+1 ; 名为 `+1\u0026#39; 的符号 (可读性很差的名字) \\(*\\ 1\\ 2\\) ; 名为 `(* 1 2)\u0026#39; 的符号 (更差劲的名字). +-*/_~!@$%^\u0026amp;=:\u0026lt;\u0026gt;{} ; 名为 `+-*/_~!@$%^\u0026amp;=:\u0026lt;\u0026gt;{}\u0026#39; 的符号。 ; 这些字符无须转义 创建符号 一个名字如何与数据对应上呢?这就要了解一下符号是如何创建的了。符号名要有唯一性,所以一定会有一个表与名字关联,这个表在 elisp 里称为 obarray。从这个名字可以看出这个表是用数组类型,事实上是一个向量。当 emacs 创建一个符号时,首先会对这个名字求 hash 值以得到一个在 obarray 这个向量中查找值所用的下标。hash 是查找字符串的很有效的方法。这里强调的是 obarray 不是一个特殊的数据结构,就是一个一般的向量。全局变量 obarray 里 emacs 所有变量、函数和其它符号所使用的 obarray(注意不同语境中 obarray 的含义不同。前一个 obarray 是变量名,后一个 obarray 是数据类型名)。也可以自己建立向量,把这个向量作为 obarray 来使用。这是一种代替散列的一种方法。它比直接使用散列有这样一些好处:\n符号不仅可以有一个值,还可以用属性列表,后者又可以相当于一个关联列表。这样有很高的扩展性,而且可以表达更高级的数据结构。 emacs 里有一些函数可以接受 obarray 作为参数,比如补全相关的函数。 当 lisp 读入一个符号时,通常会先查找这个符号是否在 obarray 里出现过,如果没有则会把这个符号加入到 obarray 里。这样查找并加入一个符号的过程称为是 intern。intern 函数可以查找或加入一个名字到 obarray 里,返回对应的符号。默认是全局的 obarray,也可以指定一个 obarray。intern-soft 与 intern 不同的是,当名字不在 obarray 里时,intern-soft 会返回 nil,而 intern 会加入到 obarray 里。为了不污染 obarray,我下面的例子中尽量在 foo 这个 obarray 里进行。一般来说,去了 foo 参数,则会在 obarray 里进行。其结果应该是相同的:\n1 2 3 4 (setq foo (make-vector 10 0)) ; =\u0026gt; [0 0 0 0 0 0 0 0 0 0] (intern-soft \u0026#34;abc\u0026#34; foo) ; =\u0026gt; nil (intern \u0026#34;abc\u0026#34; foo) ; =\u0026gt; abc (intern-soft \u0026#34;abc\u0026#34; foo) ; =\u0026gt; abc lisp 每读入一个符号都会 intern 到 obarray 里,如果想避免,可以用在符号名前加上 #::\n1 2 3 4 5 6 (intern-soft \u0026#34;abc\u0026#34;) ; =\u0026gt; nil \u0026#39;abc ; =\u0026gt; abc (intern-soft \u0026#34;abc\u0026#34;) ; =\u0026gt; abc (intern-soft \u0026#34;abcd\u0026#34;) ; =\u0026gt; nil \u0026#39;#:abcd ; =\u0026gt; abcd (intern-soft \u0026#34;abcd\u0026#34;) ; =\u0026gt; nil 如果想除去 obarray 里的符号,可以用 unintern 函数。unintern 可以用符号名或符号作参数在指定的 obarray 里去除符号,成功去除则返回 t,如果没有查找到对应的符号则返回 nil:\n1 2 3 (intern-soft \u0026#34;abc\u0026#34; foo) ; =\u0026gt; abc (unintern \u0026#34;abc\u0026#34; foo) ; =\u0026gt; t (intern-soft \u0026#34;abc\u0026#34; foo) ; =\u0026gt; nil 和 hash-table 一样,obarray 也提供一个 mapatoms 函数来遍历整个 obarray。比如要计算 obarray 里所有的符号数量:\n1 2 3 4 5 6 (setq count 0) ; =\u0026gt; 0 (defun count-syms (s) (setq count (1+ count))) ; =\u0026gt; count-syms (mapatoms \u0026#39;count-syms) ; =\u0026gt; nil count ; =\u0026gt; 28371 (length obarray) ; =\u0026gt; 1511 思考题\n由前面的例子可以看出 elisp 中的向量长度都是有限的,而 obarray 里的符号有成千上万个。那这些符号是怎样放到 obarray 里的呢?\n符号的组成 每个符号可以对应四个组成部分,一是符号的名字,可以用 symbol-name 访问。二是符号的值。符号的值可以通过 set 函数来设置,用 symbol-value 来访问。\n1 2 (set (intern \u0026#34;abc\u0026#34; foo) \u0026#34;i\u0026#39;m abc\u0026#34;) ; =\u0026gt; \u0026#34;i\u0026#39;m abc\u0026#34; (symbol-value (intern \u0026#34;abc\u0026#34; foo)) ; =\u0026gt; \u0026#34;i\u0026#39;m abc\u0026#34; 可能大家最常见到 setq 函数,而 set 函数确很少见到。setq 可以看成是一个宏,它可以让你用 (setq sym val) 代替 (set (quote sym) val)。事实上这也是它名字的来源 (q 代表 quoted)。但是 setq 只能设置 obarray 里的变量,前面这个例子中就只能用 set 函数。\n思考题\n参考 assoc-default 的代码,写一个函数从一个关联列表中除去一个关键字对应的元素。这个函数可以直接修改关联列表符号的值。要求可以传递一个参数作为测试关键字是否相同的函数。比如:\n1 2 3 (setq foo \u0026#39;((?a . a) (?a . c) (?b . d))) (remove-from-list \u0026#39;foo ?b \u0026#39;char-equal) ; =\u0026gt; ((97 . a) (65 . c)) foo ; =\u0026gt; ((97 . a) (65 . c)) 如果一个符号的值已经有设置过的话,则 boundp 测试返回 t,否则为 nil。对于 boundp 测试返回 nil 的符号,使用符号的值会引起一个变量值为 void 的错误。\n符号的第三个组成部分是函数。它可以用 symbol-function 来访问,用 fset 来设置\n1 2 (fset (intern \u0026#34;abc\u0026#34; foo) (symbol-function \u0026#39;car)) ; =\u0026gt; #\u0026lt;subr car\u0026gt; (funcall (intern \u0026#34;abc\u0026#34; foo) \u0026#39;(a . b)) ; =\u0026gt; a 类似的,可以用 fboundp 测试一个符号的函数部分是否有设置。\n符号的第四个组成部分是属性列表 (property list)。通常属性列表用于存储和符号相关的信息,比如变量和函数的文档,定义的文件名和位置,语法类型。属性名和值可以是任意的 lisp 对象,但是通常名字是符号,可以用 get 和 put 来访问和修改属性值,用 symbol-plist 得到所有的属性列表:\n1 2 3 (put (intern \u0026#34;abc\u0026#34; foo) \u0026#39;doc \u0026#34;this is abc\u0026#34;) ; =\u0026gt; \u0026#34;this is abc\u0026#34; (get (intern \u0026#34;abc\u0026#34; foo) \u0026#39;doc) ; =\u0026gt; \u0026#34;this is abc\u0026#34; (symbol-plist (intern \u0026#34;abc\u0026#34; foo)) ; =\u0026gt; (doc \u0026#34;this is abc\u0026#34;) 关联列表和属性列表很相似。符号的属性列表在内部表示上是用 (prop1 value1 prop2 value2 …) 的形式,和关联列表也是很相似的。属性列表在查找和这个符号相关的信息时,要比直接用关联列表要简单快捷的多。所以变量的文档等信息都是放在符号的属性列表里。但是关联表在头端加入元素是很快的,而且它可以删除表里的元素。而属性列表则不能删除一个属性。\n如果已经把属性列表取出,那么还可以用 plist-get 和 plist-put 的方法来访问和设置属性列表\n1 2 3 4 5 (plist-get \u0026#39;(foo 4) \u0026#39;foo) ; =\u0026gt; 4 (plist-get \u0026#39;(foo 4 bad) \u0026#39;bar) ; =\u0026gt; nil (setq my-plist \u0026#39;(bar t foo 4)) ; =\u0026gt; (bar t foo 4) (setq my-plist (plist-put my-plist \u0026#39;foo 69)) ; =\u0026gt; (bar t foo 69) (setq my-plist (plist-put my-plist \u0026#39;quux \u0026#39;(a))) ; =\u0026gt; (bar t foo 69 quux (a)) 思考题\n你能不能用已经学过的函数来实现 plist-get 和 plist-put?\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (symbolp object) (intern-soft name \u0026amp;optional obarray) (intern string \u0026amp;optional obarray) (unintern name \u0026amp;optional obarray) (mapatoms function \u0026amp;optional obarray) (symbol-name symbol) (symbol-value symbol) (boundp symbol) (set symbol newval) (setq sym val sym val ...) (symbol-function symbol) (fset symbol definition) (fboundp symbol) (symbol-plist symbol) (get symbol propname) (put symbol propname value) 问题解答 obarray 里符号数为什么大于向量长度 其实这和散列的的实现是一样的。obarray 里的每一个元素通常称为 bucket。 一个 bucket 是可以容纳多个相同 hash 值的字符串和它们的数据。我们可以用 这样的方法来模拟一下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (defun hash-string (str) (let ((hash 0) c) (dotimes (i (length str)) (setq c (aref str i)) (if (\u0026gt; c #o140) (setq c (- c 40))) (setq hash (+ (setq hash (lsh hash 3)) (lsh hash -28) c))) hash)) (let ((len 10) str hash) (setq foo (make-vector len 0)) (dotimes (i (1+ len)) (setq str (char-to-string (+ ?a i)) hash (% (hash-string str) len)) (message \u0026#34;i put %s in slot %d\u0026#34; str hash) (if (eq (aref foo hash) 0) (intern str foo) (message \u0026#34;i found %s is already taking the slot: %s\u0026#34; (aref foo hash) foo) (intern str foo) (message \u0026#34;now i\u0026#39;am in the slot too: %s\u0026#34; foo)))) 在我这里的输出是\ni put a in slot 7\ri put b in slot 8\ri put c in slot 9\ri put d in slot 0\ri put e in slot 1\ri put f in slot 2\ri put g in slot 3\ri put h in slot 4\ri put i in slot 5\ri put j in slot 6\ri put k in slot 7\ri found a is already taking the slot: [d e f g h i j a b c]\rnow i\u0026#39;am in the slot too: [d e f g h i j k b c] 当然,这个 hash-string 和实际 obarray 里用的 hash-string 只是算法上是 相同的,但是由于数据类型和 c 不是完全相同,所以对于长一点的字符串结果 可能不一样,我只好用单个字符来演示一下。\n根据关键字删除关联列表中的元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (defun remove-from-list (list-var key \u0026amp;optional test) (let ((prev (symbol-value list-var)) tail found value elt) (or test (setq test \u0026#39;equal)) (if (funcall test (caar prev) key) (set list-var (cdr prev)) (setq tail (cdr prev)) (while (and tail (not found)) (setq elt (car tail)) (if (funcall test (car elt) key) (progn (setq found t) (setcdr prev (cdr tail))) (setq tail (cdr tail) prev (cdr prev))))) (symbol-value list-var))) 注意这个函数的参数 list-var 是一个符号,所以这个函数不能直接传递一个列表。这和 add-to-list 的参数是一样的。\nplist-get 和 plist-put 的实现 1 2 3 4 5 6 7 8 (defun my-plist-get (plist prop) (cadr (memq plist prop))) (defun my-plist-put (plist prop val) (let ((tail (memq prop plist))) (if tail (setcar (cdr tail) val) (setcdr (last plist) (list prop val)))) plist) my-plist-put 函数没有 plist-put 那样 robust,如果属性列表是 ‘(bar t foo) 这样的话,这个函数就会出错。而且加入一个属性的时间复杂度比 plist 更高(memq 和 last 都是 o(n)),不过可以用循环来达到相同的时间复杂度。\n求值规则 至此,elisp 中最常见的数据类型已经介绍完了。我们可以真正开始学习怎样写一个 elisp 程序。如果想深入了解一下 lisp 是如何工作的,不妨先花些时间看看 lisp 的求值过程。当然忽略这一部分也是可以的,因为我觉得这个求值规则是那么自然,以至于你会认为它就是应该这样的。\n求值是 lisp 解释器的核心,理解了求值过程也就学会了 lisp 编程的一半。正因为这样,我有点担心自己说得不清楚或者理解错误,会误导了你。所以如果真想深入了解的话,还是自己看 info elisp - evaluation 这一章吧。\n一个要求值的 lisp 对象被称为表达式(form)。所有的表达式可以分为三种:符号、列表和其它类型(废话)。下面一一说明各种表达式的求值规则。\n第一种表达式是最简单的,自求值表达式。前面说过数字、字符串、向量都是自求值表达式。还有两个特殊的符号 t 和 nil 也可以看成是自求值表达式。\n第二种表达式是符号。符号的求值结果就是符号的值。如果它没有值,就会出现 void-variable 的错误。\n第三种表达式是列表表达式。而列表表达式又可以根据第一个元素分为函数调用、宏调用和特殊表达式(special form)三种。列表的第一个表达式如果是一个符号,解释器会查找这个表达式的函数值。如果函数值是另一个符号,则会继续查找这个符号的函数值。这称为“symbol function indirection”。最后直到某个符号的函数值是一个 lisp 函数(lambda 表达式)、byte-code 函数、原子函数(primitive function)、宏、特殊表达式或 autoload 对象。如果不是这些类型,比如某个符号的函数值是前面出现的某个符号导致无限循环,或者某个符号函数值为空,都会导致一个错误 invalid-function。\n这个函数显示 indirection function\n1 2 3 4 (symbol-function \u0026#39;car) ; =\u0026gt; #\u0026lt;subr car\u0026gt; (fset \u0026#39;first \u0026#39;car) ; =\u0026gt; car (fset \u0026#39;erste \u0026#39;first) ; =\u0026gt; first (erste \u0026#39;(1 2 3)) ; =\u0026gt; 1 对于第一个元素是 lisp 函数对象、byte-code 对象和原子函数时,这个列表也称为函数调用(funtion call)。对这样的列表求值时,先对列表中其它元素先求值,求值的结果作为函数调用的真正参数。然后使用 apply 函数用这些参数调用函数。如果函数是用 lisp 写的,可以理解为把参数和变量绑定到函数后,对函数体顺序求值,返回最后一个 form 的值。\n如果第一个元素是一个宏对象,列表里的其它元素不会立即求值,而是根据宏定义进行扩展。如果扩展后还是一个宏调用,则会继续扩展下去,直到扩展的结果不再是一个宏调用为止。例如\n1 2 (defmacro cadr (x) (list \u0026#39;car (list \u0026#39;cdr x))) 这样 (cadr (assq 'handler list)) 扩展后成为 (car (cdr (assq 'handler list)))。\n第一个元素如果是一个特殊表达式时,它的参数可能并不会全求值。这些特殊表达式通常是用于控制结构或者变量绑定。每个特殊表达式都有对应的求值规则。这在下面会提到。\n最后用这个伪代码来说明一下 elisp 中的求值规则:\n1 2 3 4 5 6 7 8 9 10 11 12 (defun (eval exp) (cond ((numberp exp) exp) ((stringp exp) exp) ((arrayp exp) exp) ((symbolp exp) (symbol-value exp)) ((special-form-p (car exp)) (eval-special-form exp)) ((fboundp (car exp)) (apply (car exp) (cdr exp))) (t (error \u0026#34;unknown expression type -- eval %s\u0026#34; exp)))) 变量 在此之前,我们已经见过 elisp 中的两种变量,全局变量和 let 绑定的局部变量。它们相当于其它语言中的全局变量和局部变量。\n关于 let 绑定的变量,有两点需要补充的。当同一个变量名既是全局变量也是局部变量,或者用 let 多层绑定,只有最里层的那个变量是有效的,用 setq 改变的也只是最里层的变量,而不影响外层的变量。比如\n1 2 3 4 5 6 7 8 9 (progn (setq foo \u0026#34;i\u0026#39;m global variable!\u0026#34;) (let ((foo 5)) (message \u0026#34;foo value is: %s\u0026#34; foo) (let (foo) (setq foo \u0026#34;i\u0026#39;m local variable!\u0026#34;) (message foo)) (message \u0026#34;foo value is still: %s\u0026#34; foo)) (message foo)) 另外需要注意一点的是局部变量的绑定不能超过一定的层数,也就是说,你不能把 foo 用 let 绑定 10000 层。当然普通的函数是不可能写成这样的,但是递归函数就不一定了。限制层数的变量在 max-specpdl-size 中定义。如果你写的递归函数有这个需要的话,可以先设置这个变量的值。\nemacs 有一种特殊的局部变量 ── buffer-local 变量。\nbuffer-local 变量 emacs 能有如此丰富的模式,各个缓冲区之间能不相互冲突,很大程度上要归功于 buffer-local 变量。\n声明一个 buffer-local 的变量可以用 make-variable-buffer-local 或用 make-local-variable。这两个函数的区别在于前者是相当于在所有变量中都产生一个 buffer-local 的变量。而后者只在声明时所在的缓冲区内产生一个局部变量,而其它缓冲区仍然使用的是全局变量。一般来说推荐使用 make-local-variable。\n为了方便演示,下面的代码我假定你是在 *scratch* 缓冲区里运行。我使用另一个一般都会有的缓冲区 *messages* 作为测试。先介绍两个用到的函数( with-current-buffer 其实是一个宏)。\nwith-current-buffer 的使用形式是\n1 2 (with-current-buffer buffer body) 其中 buffer 可以是一个缓冲区对象,也可以是缓冲区的名字。它的作用是使其中的 body 表达式在指定的缓冲区里执行。\nget-buffer 可以用缓冲区的名字得到对应的缓冲区对象。如果没有这样名字的缓冲区会返回 nil。\n下面是使用 buffer-local 变量的例子:\n1 2 3 4 5 6 (setq foo \u0026#34;i\u0026#39;m global variable!\u0026#34;) ; =\u0026gt; \u0026#34;i\u0026#39;m global variable!\u0026#34; (make-local-variable \u0026#39;foo) ; =\u0026gt; foo foo ; =\u0026gt; \u0026#34;i\u0026#39;m global variable!\u0026#34; (setq foo \u0026#34;i\u0026#39;m buffer-local variable!\u0026#34;) ; =\u0026gt; \u0026#34;i\u0026#39;m buffer-local variable!\u0026#34; foo ; =\u0026gt; \u0026#34;i\u0026#39;m buffer-local variable!\u0026#34; (with-current-buffer \u0026#34;*messages*\u0026#34; foo) ; =\u0026gt; \u0026#34;i\u0026#39;m global variable!\u0026#34; 从这个例子中可以看出,当一个符号作为全局变量时有一个值的话,用 make-local-variable 声明为 buffer-local 变量时,这个变量的值还是全局变量的值。这时候全局的值也称为缺省值。你可以用 default -value 来访问这个符号的全局变量的值\n1 (default-value \u0026#39;foo) ; =\u0026gt; \u0026#34;i\u0026#39;m global variable!\u0026#34; 如果一个变量是 buffer-local,那么在这个缓冲区内使用用 setq 就只能用改变当前缓冲区里这个变量的值。setq-default 可以修改符号作为全局变量的值。通常在 .emacs 里经常使用 setq-default,这样可以防止修改的是导入 .emacs 文件对应的缓冲区里的 buffer-local 变量,而不是设置全局的值。\n测试一个变量是不是 buffer-local 可以用 local-variable-p\n1 2 (local-variable-p \u0026#39;foo) ; =\u0026gt; t (local-variable-p \u0026#39;foo (get-buffer \u0026#34;*messages*\u0026#34;)) ; =\u0026gt; nil 如果要在当前缓冲区里得到其它缓冲区的 buffer-local 变量可以用 buffer-local-value\n1 2 3 (with-current-buffer \u0026#34;*messages*\u0026#34; (buffer-local-value \u0026#39;foo (get-buffer \u0026#34;*scratch*\u0026#34;))) ; =\u0026gt; \u0026#34;i\u0026#39;m buffer local variable!\u0026#34; 变量的作用域 我们现在已经学习这样几种变量:\n全局变量 buffer-local 变量 let 绑定局部变量 如果还要考虑函数的参数列表声明的变量,也就是 4 种类型的变量。那这种变量的作用范围 (scope) 和生存期(extent)分别是怎样的呢?\n作用域(scope)是指变量在代码中能够访问的位置。emacs lisp 这种绑定称为 indefinite scope。indefinite scope 也就是说可以在任何位置都可能访问一个变量名。而 lexical scope(词法作用域)指局部变量只能作用在函数中和一个块里(block)。\n比如 let 绑定和函数参数列表的变量在整个表达式内都是可见的,这有别于其它语言词法作用域的变量。先看下面这个例子:\n1 2 3 4 5 6 7 (defun binder (x) ; `x\u0026#39; is bound in `binder\u0026#39;. (foo 5)) ; `foo\u0026#39; is some other function. (defun user () ; `x\u0026#39; is used \u0026#34;free\u0026#34; in `user\u0026#39;. (list x)) (defun foo (ignore) (user)) (binder 10) ; =\u0026gt; (10) 对于词法作用域的语言,在 user 函数里无论如何是不能访问 binder 函数中绑定的 x。但是在 elisp 中可以。\n生存期是指程序运行过程中,变量什么时候是有效的。全局变量和 buffer-local 变量都是始终存在的,前者只能当关闭 emacs 或者用 unintern 从 obarray 里除去时才能消除。而 buffer-local 的变量也只能关闭缓冲区或者用 kill-local-variable 才会消失。而对于局部变量,emacs lisp 使用的方式称为动态生存期:只有当绑定了这个变量的表达式运行时才是有效的。这和 c 和 pascal 里的 local 和 automatic 变量是一样的。与此相对的是 indefinite extent,变量即使离开绑定它的表达式还能有效。比如:\n1 2 3 4 5 (defun make-add (n) (function (lambda (m) (+ n m)))) ; return a function. (fset \u0026#39;add2 (make-add 2)) ; define function `add2\u0026#39; ; with `(make-add 2)\u0026#39;. (add2 4) ; try to add 2 to 4. 其它 lisp 方言中有闭包,但是 emacs lisp 中没有。\n说完这些概念,可能你还是一点雾水。我给一个判断变量是否有效的方法吧:\n看看包含这个变量的 form 中是否有 let 绑定这个局部变量。如果这个 form 不是在定义一个函数,则跳到第 3 步。 如果是在定义函数,则不仅要看这个函数的参数中是否有这个变量,而且还要看所有直接或间接调用这个函数的函数中是否有用 let 绑定或者参数列表里有这个变量名。这就没有办法确定了,所以你永远无法判断一个函数中出现的没有用 let 绑定,也不在参数列表中的变量是否是没有定义过的。但是一般来说这不是一个好习惯。 看这个变量是否是一个全局变量或者是 buffer-local 变量。 对于在一个函数中绑定一个变量,而在另一个函数中还在使用,manual 里认为这两个种情况下是比较好的:\n这个变量只有相关的几个函数中使用,在一个文件中放在一起。这个变量起程序里通信的作用。而且需要写好注释告诉其它程序员怎样使用它。 如果这个变量是定义明确、有很好文档作用的,可能让所有函数使用它,但是不要设置它。比如 case-fold-search。(我怎么觉得这里是用全局变量呢。) 思考题\n先在 *scratch* 缓冲区里运行了 (kill-local-variable 'foo) 后,运行几次下面的表达式,你能预测它们结果吗?\n1 2 3 4 5 6 7 (progn (setq foo \u0026#34;i\u0026#39;m local variable!\u0026#34;) (let ((foo \u0026#34;i\u0026#39;m local variable!\u0026#34;)) (set (make-local-variable \u0026#39;foo) \u0026#34;i\u0026#39;m buffer-local variable!\u0026#34;) (setq foo \u0026#34;this is a variable!\u0026#34;) (message foo)) (message foo)) 其它函数 一个符号如果值为空,直接使用可能会产生一个错误。可以用 boundp 来测试一个变量是否有定义。这通常用于 elisp 扩展的移植(用于不同版本或 xemacs)。对于一个 buffer-local 变量,它的缺省值可能是没有定义的,这时用 default-value 函数可能会出错。这时就先用 default-boundp 先进行测试。\n使一个变量的值重新为空,可以用 makunbound。要消除一个 buffer-local 变量用函数 kill-local-variable。可以用 kill-all-local-variables 消除所有的 buffer-local 变量。但是有属性 permanent-local 的不会消除,带有这些标记的变量一般都是和缓冲区模式无关的,比如输入法。\n1 2 3 4 5 6 7 foo ; =\u0026gt; \u0026#34;i\u0026#39;m local variable!\u0026#34; (boundp \u0026#39;foo) ; =\u0026gt; t (default-boundp \u0026#39;foo) ; =\u0026gt; t (makunbound \u0026#39;foo) ; =\u0026gt; foo foo ; this will signal an error (default-boundp \u0026#39;foo) ; =\u0026gt; t (kill-local-variable \u0026#39;foo) ; =\u0026gt; foo 变量名习惯 对于变量的命名,有一些习惯,这样可以从变量名就能看出变量的用途:\nhook 一个在特定情况下调用的函数列表,比如关闭缓冲区时,进入某个模式时。 function 值为一个函数 functions 值为一个函数列表 flag 值为 nil 或 non-nil predicate 值是一个作判断的函数,返回 nil 或 non-nil program 或 -command 一个程序或 shell 命令名 form 一个表达式 forms 一个表达式列表。 map 一个按键映射(keymap) 函数列表 1 2 3 4 5 6 7 8 9 10 11 12 (make-local-variable variable) (make-variable-buffer-local variable) (with-current-buffer buffer \u0026amp;rest body) (get-buffer name) (default-value symbol) (local-variable-p variable \u0026amp;optional buffer) (buffer-local-value variable buffer) (boundp symbol) (default-boundp symbol) (makunbound symbol) (kill-local-variable variable) (kill-all-local-variables) 变量列表 max-specpdl-size 问题解答 同一个表达式运行再次结果不同? 运行第一次时,foo 缺省值为 “i’m local variable!\u0026quot;,而 buffer-local 值为 “this is a variable!\u0026quot;。第一个和第二个 message 都会显示 “this is a variable!\u0026quot;。运行第二次时,foo 缺省值和 buffer-local 值都成了 “i’m local variable!\u0026quot;,而第一次 message 显示 “this is a variable!\u0026quot;,第二次 显示 “i’m local variable!\u0026quot;。这是由于 make-local-variable 在这个符号是 否已经是 buffer-local 变量时有不同表现造成的。如果已经是一个 buffer-local 变量,则它什么也不做,而如果不是,则会生成一个 buffer-local 变量,这时在这个表达式内的所有 foo 也被重新绑定了。希望你 写的函数能想到一点。\n函数和命令 在 elisp 里类似函数的对象很多,比如:\n函数。这里的函数特指用 lisp 写的函数。 原子函数(primitive)。用 c 写的函数,比如 car、append。 lambda 表达式 特殊表达式 宏 (macro)。宏是用 lisp 写的一种结构,它可以把一种 lisp 表达式转换成等价的另一个表达式。 命令。命令能用 command-execute 调用。函数也可以是命令。 以上这些用 functionp 来测试都会返回 t。\n我们已经学过如何定义一个函数。但是这些函数的参数个数都是确定。但是你可以看到 emacs 里有很多函数是接受可选参数,比如 random 函数。还有一些函数可以接受不确定的参数,比如加减乘除。这样的函数在 elisp 中是如何定义的呢?\n参数列表的语法 这是参数列表的方法形式:\n(required-vars...\r[\u0026amp;optional optional-vars...]\r[\u0026amp;rest rest-var]) 它的意思是说,你必须把必须提供的参数写在前面,可选的参数写在后面,最后用一个符号表示剩余的所有参数。比如\n1 2 3 4 5 6 (defun foo (var1 var2 \u0026amp;optional opt1 opt2 \u0026amp;rest rest) (list var1 var2 opt1 opt2 rest)) (foo 1 2) ; =\u0026gt; (1 2 nil nil nil) (foo 1 2 3) ; =\u0026gt; (1 2 3 nil nil) (foo 1 2 3 4 5 6) ; =\u0026gt; (1 2 3 4 (5 6)) 从这个例子可以看出,当可选参数没有提供时,在函数体里,对应的参数值都是 nil。同样调用函数时没有提供剩余参数时,其值也为 nil,但是一旦提供了剩余参数,则所有参数是以列表的形式放在对应变量里。\n思考题\n写一个函数测试两个浮点数是否相等,设置一个可选参数,如果提供这个参数,则用这个参数作为测试误差,否则用 1.0e-6 作为误差。\n关于文档字符串 最好为你的函数都提供一个文档字符串。关于文档字符串有一些规范,最好遵守这些约定。\n字符串的第一行最好是独立的。因为 apropos 命令只能显示第一行的文档。所以最好用一行(一两个完整的句子)总结这个函数的目的。\n文档的缩进最好要根据最后的显示的效果来调用。因为引号之类字符会多占用一个字符,所以在源文件里缩进最好看,不一定显示的最好。\n如果你想要让你的函数参数显示的与函数定义的不同(比如提示用户如何调用这个函数),可以在文档最后一行,加上一行:\n\\(fn arglist) 注意这一行前面要有一个空行,这一行后不能再有空行。比如\n1 2 3 4 5 (defun foo (var1 var2 \u0026amp;optional opt1 opt2 \u0026amp;rest rest) \u0026#34;you should call the function like: \\(fn v1 v2)\u0026#34; (list var1 var2 opt1 opt2 rest)) 还有一些有特殊标记功能的符号,比如 ``’引起的符号名可以生成一个链接,这样可以在help 中更方便的查看相关变量或函数的文档。{major-mode-map}` 可以显示扩展成这个模式按键的说明,例如:\n1 2 3 4 5 6 7 8 9 10 (defun foo () \u0026#34;a simple document string to show how to use `\u0026#39; and \\\\=\\\\{}. you can press this button `help\u0026#39; to see the document of function \\\u0026#34;help\\\u0026#34;. this is keybind of text-mode(substitute from \\\\=\\\\{text-mode-map}): \\\\{text-mode-map} see also `substitute-command-keys\u0026#39; and `documentation\u0026#39;\u0026#34; ) 调用函数 通常函数的调用都是用 eval 进行的,但是有时需要在运行时才决定使用什么函数,这时就需要用 funcall 和 apply 两个函数了。这两个函数都是把其余的参数作为函数的参数进行调用。那这两个函数有什么参数呢?唯一的区别就在于 funcall 是直接把参数传递给函数,而 apply 的最后一个参数是一个列表,传入函数的参数把列表进行一次平铺后再传给函数,看下面这个例子就明白了\n1 2 (funcall \u0026#39;list \u0026#39;x \u0026#39;(y) \u0026#39;(z)) ; =\u0026gt; (x (y) (z)) (apply \u0026#39;list \u0026#39;x \u0026#39;(y ) \u0026#39;(z)) ; =\u0026gt; (x (y) z) 思考题\n如果一个 list 作为一个树的结构,任何是 cons cell 的元素都是一个内部节点(不允许有 dotted list 出现),任何不是 cons cell 的元素都是树的叶子。请写一个函数,调用的一个类似 mapcar 的函数,调用一个函数遍历树的叶子,并收集所有的结果,返回一个结构相同的树,比如:\n1 (tree-mapcar \u0026#39;1+ \u0026#39;(1 (2 (3 4)) (5))) ; =\u0026gt; (2 (3 (4 5)) (6)) 宏 前面在已经简单介绍过宏。宏的调用和函数是很类似的,它的求值和函数差不多,但是有一个重要的区别是,宏的参数是出现在最后扩展后的表达式中,而函数参数是求值后才传递给这个函数:\n1 2 3 4 5 6 7 8 9 10 11 (defmacro foo (arg) (list \u0026#39;message \u0026#34;%d %d\u0026#34; arg arg)) (defun bar (arg) (message \u0026#34;%d %d\u0026#34; arg arg)) (let ((i 1)) (bar (incf i))) ; =\u0026gt; \u0026#34;2 2\u0026#34; (let ((i 1)) (foo (incf i))) ; =\u0026gt; \u0026#34;2 3\u0026#34; 也许你对前面这个例子 foo 里为什么要用 list 函数很不解。其实宏可以这样看,如果把宏定义作一个表达式来运行,最后把参数用调用时的参数替换,这样就得到了宏调用最后用于求值的表达式。这个过程称为扩展。可以用 macroexpand 函数进行模拟\n1 (macroexpand \u0026#39;(foo (incf i))) ; =\u0026gt; (message \u0026#34;%d %d\u0026#34; (incf i) (incf i)) 上面用 macroexpand 得到的结果就是用于求值的表达式。\n使用 macroexpand 可以使宏的编写变得容易一些。但是如果不能进行 debug 是很不方便的。在宏定义里可以引入 declare 表达式,它可以增加一些信息。目前只支持两类声明:debug 和 indent。debug 可选择的类型很多,具体参考 info elisp - edebug 一章,一般情况下用 t 就足够了。indent 的类型比较简单,它可以使用这样几种类型:\nnil 也就是一般的方式缩进 defun 类似 def 的结构,把第二行作为主体,对主体里的表达式使用同样的缩进 整数 表示从第 n 个表达式后作为主体。比如 if 设置为 2,而 when 设置为 1 符号 这个是最坏情况,你要写一个函数自己处理缩进。 看 when 的定义就能知道 declare 如何使用了\n1 2 3 (defmacro when (cond \u0026amp;rest body) (declare (indent 1) (debug t)) (list \u0026#39;if cond (cons \u0026#39;progn body))) 实际上,declare 声明只是设置这个符号的属性列表\n1 (symbol-plist \u0026#39;when) ; =\u0026gt; (lisp-indent-function 1 edebug-form-spec t) 思考题\n一个比较常用的结构是当 buffer 是可读情况下,绑定 inhibit-read-only 值为 t 来强制插入字符串。请写一个这样的宏,处理好缩进和调用。\n从前面宏 when 的定义可以看出直接使用 list,cons,append 构造宏是很麻烦的。为了使记号简洁,lisp 中有一个特殊的宏 “`\u0026quot;,称为 backquote。在这个宏里,所有的表达式都是引起(quote)的,如果要让一个表达式不引起(也就是列表中使用的是表达式的值),需要在前面加 “,”,如果要让一个列表作为整个列表的一部分(slice),可以用 “,@\u0026quot;。\n1 2 3 `(a list of ,(+ 2 3) elements) ; =\u0026gt; (a list of 5 elements) (setq some-list \u0026#39;(2 3)) ; =\u0026gt; (2 3) `(1 ,some-list 4 ,@some-list) ; =\u0026gt; (1 (2 3) 4 2 3) 有了这些标记,前面 when 这个宏可以写成\n1 2 3 (defmacro when (cond \u0026amp;rest body) `(if ,cond (progn ,@body))) 值得注意的是这个 backquote 本身就是一个宏,从这里可以看出宏除了减少重复代码这个作用之外的另一个用途:定义新的控制结构,甚至增加新的语法特性。\n命令 emacs 运行时就是处于一个命令循环中,不断从用户那得到按键序列,然后调用对应命令来执行。lisp 编写的命令都含有一个 interactive 表达式。这个表达式指明了这个命令的参数。比如下面这个命令\n1 2 3 (defun hello-world (name) (interactive \u0026#34;swhat you name? \u0026#34;) (message \u0026#34;hello, %s\u0026#34; name)) 现在你可以用 m-x 来调用这个命令。让我们来看看 interactive 的参数是什么意思。这个字符串的第一个字符(也称为代码字符)代表参数的类型,比如这里 s 代表参数的类型是一个字符串,而其后的字符串是用来提示的字符串。如果这个命令有多个参数,可以在这个提示字符串后使用换行符分开,比如:\n1 2 3 4 5 6 7 (defun hello-world (name time) (interactive \u0026#34;swhat you name? \\nnwhat the time? \u0026#34;) (message \u0026#34;good %s, %s\u0026#34; (cond ((\u0026lt; time 13) \u0026#34;morning\u0026#34;) ((\u0026lt; time 19) \u0026#34;afternoon\u0026#34;) (t \u0026#34;evening\u0026#34;)) name)) interactive 可以使用的代码字符很多,虽然有一定的规则,比如字符串用 s,数字用 n,文件用 f,区域用 r,但是还是很容易忘记,用的时候看 interactive 函数的文档还是很有必要的。但是不是所有时候都参数类型都能使用代码字符,而且一个好的命令,应该尽可能的让提供默认参数以让用户少花时间在输入参数上,这时,就有可能要自己定制参数。\n首先学习和代码字符等价的几个函数。s 对应的函数是 read-string。比如\n1 (read-string \u0026#34;what your name? \u0026#34; user-full-name) n 对应的函数是 read-number,文件对应 read-file-name。很容易记对吧。其实大部分代码字符都是有这样对应的函数或替换的方法(见下表)。\n代码字符 代替的表达式 a (completing-read prompt obarray ‘fboundp t) b (read-buffer prompt nil t) b (read-buffer prompt) c (read-char prompt) c (read-command prompt) d (point) d (read-directory-name prompt) e (read-event) f (read-file-name prompt nil nil t) f (read-file-name prompt) g 暂时不知道和 f 的差别 k (read-key-sequence prompt) k (read-key-sequence prompt nil t) m (mark) n (read-number prompt) n (if current-prefix-arg (prefix-numeric-value current-prefix-arg) (read-number prompt)) p (prefix-numeric-value current-prefix-arg) p current-prefix-arg r (region-beginning) (region-end) s (read-string prompt) s (completing-read prompt obarray nil t) v (read-variable prompt) x (read-from-minibuffer prompt nil nil t) x (eval (read-from-minibuffer prompt nil nil t)) z (read-coding-system prompt) z (and current-prefix-arg (read-coding-system prompt)) 知道这些表达式如何用于 interactive 表达式里呢?简而言之,如果 interactive 的参数是一个表达式,则这个表达式求值后的列表元素对应于这个命令的参数。请看这个例子:\n1 2 3 4 5 6 7 (defun read-hiden-file (file arg) (interactive (list (read-file-name \u0026#34;choose a hiden file: \u0026#34; \u0026#34;~/\u0026#34; nil nil nil (lambda (name) (string-match \u0026#34;^\\\\.\u0026#34; (file-name-nondirectory name)))) current-prefix-arg)) (message \u0026#34;%s, %s\u0026#34; file arg)) 第一个参数是读入一个以 “.” 开头的文件名,第二个参数为当前的前缀参数(prefix argument),它可以用 c-u 或 c-u 加数字提供。list 把这两个参数构成一个列表。这就是命令一般的自定义设定参数的方法。\n需要注意的是 current-prefix-arg 这个变量。这个变量当一个命令被调用,它就被赋与一个值,你可以用 c-u 就能改变它的值。在命令运行过程中,它的值始终都存在。即使你的命令不用参数,你也可以访问它\n1 2 3 (defun foo () (interactive) (message \u0026#34;%s\u0026#34; current-prefix-arg)) 用 c-u foo 调用它,你可以发现它的值是 (4)。那为什么大多数命令还单独为它设置一个参数呢?这是因为命令不仅是用户可以调用,很可能其它函数也可以调用,单独设置一个参数可以方便的用参数传递的方法调用这个命令。事实上所有的命令都可以不带参数,而使用前面介绍的方法在命令定义的部分读入需要的参数,但是为了提高命令的可重用性和代码的可读性,还是把参数分离到 interactive 表达式里好。\n从现在开始可能会遇到很多函数,它们的用法有的简单,有的却复杂的要用大段篇幅来解释。我可能就会根据需要来解释一两个函数,就不一一介绍了。自己看 info elisp,用 i 来查找对应的函数。\n思考题\n写一个命令用来切换 major-mode。要求用户输入一个 major-mode 的名字,就切换到这个 major-mode,而且要提供一种补全的办法,去除所有不是 major-mode 的符号,这样用户需要输入少量词就能找到对应的 major-mode。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 (functionp object) (apply function \u0026amp;rest arguments) (funcall function \u0026amp;rest arguments) (defmacro name arglist [docstring] [decl] body...) (macroexpand form \u0026amp;optional environment) (declare \u0026amp;rest specs) (` arg) (interactive args) (read-string prompt \u0026amp;optional initial-input history default-value inherit-input-method) (read-file-name prompt \u0026amp;optional dir default-filename mustmatch initial predicate) (completing-read prompt collection \u0026amp;optional predicate require-match initial-input hist def inherit-input-method) (read-buffer prompt \u0026amp;optional def require-match) (read-char \u0026amp;optional prompt inherit-input-method seconds) (read-command prompt \u0026amp;optional default-value) (read-directory-name prompt \u0026amp;optional dir default-dirname mustmatch initial) (read-event \u0026amp;optional prompt inherit-input-method seconds) (read-key-sequence prompt \u0026amp;optional continue-echo dont-downcase-last can-return-switch-frame command-loop) (read-number prompt \u0026amp;optional default) (prefix-numeric-value raw) (read-from-minibuffer prompt \u0026amp;optional initial-contents keymap read hist default-value inherit-input-method) (read-coding-system prompt \u0026amp;optional default-coding-system) 变量列表 current-prefix-arg 问题解答 可选误差的浮点数比较 1 2 3 4 5 6 7 8 (defun approx-equal (x y \u0026amp;optional err) (if err (setq err (abs err)) (setq err 1.0e-6)) (or (and (= x 0) (= y 0)) (\u0026lt; (/ (abs (- x y)) (max (abs x) (abs y))) err))) 这个应该是很简单的一个问题。\n遍历树的函数 1 2 3 4 5 6 (defun tree-mapcar (func tree) (if (consp tree) (mapcar (lambda (child) (tree-mapcar func child)) tree) (funcall func tree))) 这个函数可能对于树算法比较熟悉的人一点都不难,就当练手吧。\n宏 with-inhibit-read-only-t 1 2 3 4 (defmacro with-inhibit-read-only-t (\u0026amp;rest body) (declare (indent 0) (debug t)) (cons \u0026#39;let (cons \u0026#39;((inhibit-read-only t)) body))) 如果用 backquote 来改写一个就会发现这个宏会很容易写,而且更容易读了。\n切换 major-mode 的命令 1 2 3 4 5 6 7 8 9 10 11 12 13 (defvar switch-major-mode-history nil) (defun switch-major-mode (mode) (interactive (list (intern (completing-read \u0026#34;switch to mode: \u0026#34; obarray (lambda (s) (and (fboundp s) (string-match \u0026#34;-mode$\u0026#34; (symbol-name s)))) t nil \u0026#39;switch-major-mode-history)))) (setq switch-major-mode-history (cons (symbol-name major-mode) switch-major-mode-history)) (funcall mode)) 这是我常用的一个命令之一。这个实现也是一个使用 minibuffer 历史的例子。\n正则表达式 如果你不懂正则表达式,而你还想进一步学习编程的话,那你应该停下手边的事情,先学学正则表达式。即使你不想学习编程,也不喜欢编程,学习一点正则表达式,也可能让你的文本编辑效率提高很多。\n在这里,我不想详细介绍正则表达式,因为我觉得这类的文档已经很多了,比我写得好的文章多的是。如果你找不到一个好的入门教程,我建议你不妨看看 perlretut。我想说的是和 emacs 有关的正则表达式的内容,比如,和 perl 正则表达式的差异、语法表格(syntax table)和字符分类(category)等。\n与 perl 正则表达式比较 perl 是文本处理的首选语言。它内置强大而简洁的正则表达式,许多程序也都兼容 perl 的正则表达式。说实话,就简洁而言,我对 emacs 的正则表达式是非常反感的,那么多的反斜线经常让我抓狂。首先,emacs 里的反斜线构成的特殊结构(backslash construct)出现是相当频繁的。在 perl 正则表达式里,()[]{}| 都是特殊字符,而 emacs 它们不是这样。所以它们匹配字符时是不用反斜线,而作为特殊结构时就要用反斜线。而事实上()|作为字符来匹配的情形远远小于作为捕捉字符串和作或运算的情形概率小。而 emacs 的正则表达式又没有 perl 那种简洁的记号,完全用字符串来表示,这样使得一个正则表达式常常一眼看去全是 \\\\。\n到底要用多少个\\? 经常会记不住在 emacs 的正则表达式中应该用几个 \\。有一个比较好的方法,首先想想没有引号引起时的正则表达式是怎样。比如对于特殊字符 $ 要用 \\$,对于反斜线结构是 \\(, \\{,\\| 等等。知道这个怎样写之后,再把所有 \\ 替换成 \\\\,这就是最后写到双引号里形式。所以要匹配一个 \\,应该用 \\\\,而写在引号里就应该用 \\\\\\\\ 来匹配。\nemacs 里匹配的对象不仅包括字符串,还有 buffer,所以有一些对字符串和 buffer 有区分的结构。比如 $ 对于字符串是匹配字符串的末尾,而在 buffer 里是行尾。而 \\' 匹配的是字符串和 buffe 的末尾。对应 ^ 和 ``` 也是这样。\nemacs 对字符有很多种分类方法,在正则表达式里也可以使用。比如按语法类型分类,可以用 “\\s” 结构匹配一类语法分类的字符,最常用的比如匹配空格的 \\s- 和匹配词的 \\sw(等价于 \\w)。这在 perl 里是没有的。另外 emacs 里字符还对应一个或多个分类(category),比如所有汉字在分类 c 里。这样可以用 \\cc 来匹配一个汉字。这在 perl 里也有类似的分类。除此之外,还有一些预定义的字符分类,可以用 [:class:] 的形式,比如 [:digit:] 匹配 0-9 之间的数,[:ascii:] 匹配所有 ascii 字符等等。在 perl 里只定义几类最常用的字符集,比如 \\d, \\s, \\w,但是我觉得非常实用。比 emacs 这样长的标记好用的多。\n另外在用 [] 表示一个字符集时,emacs 里不能用 \\ 进行转义,事实上 \\ 在这里不是一个特殊字符。所以 emacs 里的处理方法是,把特殊字符提前或放在后面,比如如果要在字符集里包括 ] 的话,要把 ] 放在第一位。而如果要包括 -,只能放在最后一位,如果要包括 ^ 不能放在第一位。如果只想匹配一个 ^,就只能用 \\^ 的形式。比较拗口,希望下面这个例子能帮你理解\n1 2 3 (let ((str \u0026#34;abc]-^]123\u0026#34;)) (string-match \u0026#34;[]^0-9-]+\u0026#34; str) (match-string 0 str)) ; =\u0026gt; \u0026#34;]-^]123\u0026#34; 最后提示一下,emacs 提供一个很好的正则表达式调试工具:m-x re-builder。它能显示 buffer 匹配正则表达式的字符串,并用不同颜色显示捕捉的字符串。\n语法表格和分类表格简介 语法表格指的是 emacs 为每个字符都指定了语法功能,这为解析函数,复杂的移动命令等等提供了各种语法结构的起点和终点。语法表使用的数据结构是一种称为字符表(char-table)的数组,它能以字符作为下标(还记得 emacs 里的字符就是整数吗)来得到对应的值。语法表里一个字符对应一个语法分类的列表。每一个分类都有一个助记字符(mnemonic character)。一共有哪几类分类呢?\n名称 助记符 说明 空白(whitespace) - 或 ’ ' 词(word) w 符号(symbol) _ 这是除 word 之外其它用于变量和命令名的字符。 标点(punctuation) . open 和 close ( 和 ) 一般是括号 ()[]{} 字符串引号(string quote) \u0026quot; 转义符(escape-syntax) |用于转义序列,比如 c 和 lisp 字符串中的 \\。 字符引号(character quote) / paired delimiter $ 只有 tex 模式中使用 expression prefix ' 注释开始和注释结束 \u0026lt; 和 \u0026gt; inherit standard syntax @ generic comment delimiter ! 语法表格可以继承,所以基本上所有语法表格都是从 standard-syntax-table 继承而来,作少量修改,加上每个模式特有的语法构成就行了。一般来说记住几类重要的分类就行了,比如,空白包括空格,制表符,换行符,换页符。词包括所有的大小写字母,数字。符号一般按使用的模式而定,比如 c 中包含 _,而 lisp 中是 $\u0026amp;*+-_\u0026lt;\u0026gt;。可以用 m-x describe-syntax 来查看所有字符的语法分类。\n字符分类(category)是另一种分类方法,每个分类都有一个名字,对应一个从 ``到 ~ 的 ascii 字符。可以用 m-x describe-categories 查看所有字符的分类。每一种分类都有说明,我就不详细解释了。\n几个常用的函数 如果你要匹配的字符串中含有很多特殊字符,而你又想用正则表达式进行匹配,可以使用 regexp-quote 函数,它可以让字符串中的特殊字符自动转义。\n一般多个可选词的匹配可以用或运算连接起来,但是这有两个不好的地方,一是要写很长的正则表达式,还含有很多反斜线,不好看,容易出错,也不好修改,二是效率很低。这时可以使用 regexp-opt 还产生一个更好的正则表达式\n1 (regexp-opt \u0026#39;(\u0026#34;foobar\u0026#34; \u0026#34;foobaz\u0026#34; \u0026#34;foo\u0026#34;)) ; =\u0026gt; \u0026#34;foo\\\\(?:ba[rz]\\\\)?\u0026#34; 函数列表 1 2 (regexp-quote string) (regexp-opt strings \u0026amp;optional paren) 命令列表 describe-syntax\rdescribe-categories 操作对象之一 – 缓冲区 缓冲区(buffer)是用来保存要编辑文本的对象。通常缓冲区都是和文件相关联的,但是也有很多缓冲区没有对应的文件。emacs 可以同时打开多个文件,也就是说能同时有很多个缓冲区存在,但是在任何时候都只有一个缓冲区称为当前缓冲区(current buffer)。即使在 lisp 编程中也是如此。许多编辑和移动的命令只能针对当前缓冲区。\n缓冲区的名字 emacs 里的所有缓冲区都有一个不重复的名字。所以和缓冲区相关的函数通常都是可以接受一个缓冲区对象或一个字符串作为缓冲区名查找对应的缓冲区。下面的函数列表中如果参数是 buffer-or-name 则是能同时接受缓冲区对象和缓冲区名的函数,否则只能接受一种参数。有一个习惯是名字以空格开头的缓冲区是临时的,用户不需要关心的缓冲区。所以现在一般显示缓冲区列表的命令都不会显示这样的变量,除非这个缓冲区关联一个文件。\n要得到缓冲区的名字,可以用 buffer-name 函数,它的参数是可选的,如果不指定参数,则返回当前缓冲区的名字,否则返回指定缓冲区的名字。更改一个缓冲区的名字用 rename-buffer,这是一个命令,所以你可以用 m-x 调用来修改当前缓冲区的名字。如果你指定的名字与现有的缓冲区冲突,则会产生一个错误,除非你使用第二个可选参数以产生一个不相同的名字,通常是在名字后加上 \u0026lt;序号\u0026gt; 的方式使名字变得不同。你也可以用 generate-new-buffer-name 来产生一个唯一的缓冲区名。\n当前缓冲区 当前缓冲区可以用 current-buffer 函数得到。当前缓冲区不一定是显示在屏幕上的那个缓冲区,你可以用 set-buffer 来指定当前缓冲区。但是需要注意的是,当命令返回到命令循环时,光标所在的缓冲区 会自动成为当前缓冲区。这也是单独在 *scratch* 中执行 set-buffer 后并不能改变当前缓冲区,而必须使用 progn 语句同时执行多个语句才能改变当前缓冲区的原因\n1 2 3 4 5 (set-buffer \u0026#34;*messages*\u0026#34;) ; =\u0026gt; #\u0026lt;buffer *messages*\u0026gt; (message (buffer-name)) ; =\u0026gt; \u0026#34;*scratch*\u0026#34; (progn (set-buffer \u0026#34;*messages*\u0026#34;) (message (buffer-name))) ; \u0026#34;*messages*\u0026#34; 但是你不能依赖命令循环来把当前缓冲区设置成使用 set-buffer 之前的。因为这个命令很可以会被另一个程序员来调用。你也不能直接用 set-buffer 设置成原来的缓冲区,比如\n1 2 3 4 5 (let (buffer-read-only (obuf (current-buffer))) (set-buffer ...) ... (set-buffer obuf)) 因为 set-buffer 不能处理错误或退出情况。正确的作法是使用 save-current-buffer、with-current-buffer 和 save-excursion 等方法。save-current-buffer 能保存当前缓冲区,执行其中的表达式,最后恢复为原来的缓冲区。如果原来的缓冲区被关闭了,则使用最后使用的那个当前缓冲区作为语句返回后的当前缓冲区。lisp 中很多以 with 开头的宏,这些宏通常是在不改变当前状态下,临时用另一个变量代替现有变量执行语句。比如 with-current-buffer 使用另一个缓冲区作为当前缓冲区,语句执行结束后恢复成执行之前的那个缓冲区\n1 2 (with-current-buffer buffer-or-name body) 相当于\n1 2 3 (save-current-buffer (set-buffer buffer-or-name) body) save-excursion 与 save-current-buffer 不同之处在于,它不仅保存当前缓冲区,还保存了当前的位置和 mark。在 *scratch* 缓冲区中运行下面两个语句就能看出它们的差别了\n1 2 3 4 5 6 7 8 9 (save-current-buffer (set-buffer \u0026#34;*scratch*\u0026#34;) (goto-char (point-min)) (set-buffer \u0026#34;*messages*\u0026#34;)) (save-excursion (set-buffer \u0026#34;*scratch*\u0026#34;) (goto-char (point-min)) (set-buffer \u0026#34;*messages*\u0026#34;)) 创建和关闭缓冲区 产生一个缓冲区必须用给这个缓冲区一个名字,所以两个能产生新缓冲区的函数都是以一个字符串为参数:get-buffer-create 和 generate-new-buffer。这两个函数的差别在于前者如果给定名字的缓冲区已经存在,则返回这个缓冲区对象,否则新建一个缓冲区,名字为参数字符串,而后者在给定名字的缓冲区存在时,会使用加上后缀 \u0026lt;n\u0026gt;(n 是一个整数,从 2 开始) 的名字创建新的缓冲区。\n关闭一个缓冲区可以用 kill-buffer。当关闭缓冲区时,如果要用户确认是否要关闭缓冲区,可以加到 kill-buffer-query-functions 里。如果要做一些善后处理,可以用 kill-buffer-hook。\n通常一个接受缓冲区作为参数的函数都需要参数所指定的缓冲区是存在的。如果要确认一个缓冲区是否依然还存在可以使用 buffer-live-p。\n要对所有缓冲区进行某个操作,可以用 buffer-list 获得所有缓冲区的列表。\n如果你只是想使用一个临时的缓冲区,而不想先建一个缓冲区,使用结束后又需要关闭这个缓冲区,可以用 with-temp-buffer 这个宏。从这个宏的名字可以看出,它所做的事情是先新建一个临时缓冲区,并把这个缓冲区作为当前缓冲区,使用结束后,关闭这个缓冲区,并恢复之前的缓冲区为当前缓冲区。\n在缓冲区内移动 在学会移动函数之前,先要理解两个概念:位置(position)和标记(mark)。位置是指某个字符在缓冲区内的下标,它从 1 开始。更准确的说位置是在两个字符之间,所以有在位置之前的字符和在位置之后的字符之说。但是通常我们说在某个位置的字符都是指在这个位置之后的字符。\n标记和位置的区别在于位置会随文本插入和删除而改变位置。一个标记包含了缓冲区和位置两个信息。在插入和删除缓冲区里的文本时,所有的标记都会检查一遍,并重新设置位置。这对于含有大量标记的缓冲区处理是很花时间的,所以当你确认某个标记不用的话应该释放这个标记。\n创建一个标记使用函数 make-marker。这样产生的标记不会指向任何地方。你需要用 set-marker 命令来设置标记的位置和缓冲区\n1 2 (setq foo (make-marker)) ; =\u0026gt; #\u0026lt;marker in no buffer\u0026gt; (set-marker foo (point)) ; =\u0026gt; #\u0026lt;marker at 3594 in *scratch*\u0026gt; 也可以用 point-marker 直接得到 point 处的标记。或者用 copy-marker 复制一个标记或者直接用位置生成一个标记\n1 2 3 (point-marker) ; =\u0026gt; #\u0026lt;marker at 3516 in *scratch*\u0026gt; (copy-marker 20) ; =\u0026gt; #\u0026lt;marker at 20 in *scratch*\u0026gt; (copy-marker foo) ; =\u0026gt; #\u0026lt;marker at 3502 in *scratch*\u0026gt; 如果要得一个标记的内容,可以用 marker-position,marker-buffer\n1 2 (marker-position foo) ; =\u0026gt; 3502 (marker-buffer foo) ; =\u0026gt; #\u0026lt;buffer *scratch*\u0026gt; 位置就是一个整数,而标记在一般情况下都是以整数的形式使用,所以很多接受整数运算的函数也可以接受标记为参数。比如加减乘。\n和缓冲区相关的变量,有的可以用变量得到,比如缓冲区关联的文件名,有的只能用函数来得到,比如 point。point 是一个特殊的缓冲区位置,许多命令在这个位置进行文本插入。每个缓冲区都有一个 point 值,它总是比函数 point-min 大,比另一个函数 point-max 返回值小。注意,point-min 的返回值不一定是 1,point-max 的返回值也不定是比缓冲区大小函数 buffer-size 的返回值大 1 的数,因为 emacs 可以把一个缓冲区缩小(narrow)到一个区域,这时 point-min 和 point-max 返回值就是这个区域的起点和终点位置。所以要得到 point 的范围,只能用这两个函数,而不能用 1 和 buffer-size 函数。\n和 point 类似,有一个特殊的标记称为 “the mark”。它指定了一个区域的文本用于某些命令,比如 kill-region,indent-region。可以用 mark 函数返回当前 mark 的值。如果使用 transient-mark-mode,而且 mark-even-if-inactive 值是 nil 的话,在 mark 没有激活时(也就是 mark-active 的值为 nil),调用 mark 函数会产生一个错误。如果传递一个参数 force 才能返回当前缓冲区 mark 的位置。mark-marker 能返回当前缓冲区的 mark,这不是 mark 的拷贝,所以设置它的值会改变当前 mark 的值。set-mark 可以设置 mark 的值,并激活 mark。每个缓冲区还维护一个 mark-ring,这个列表里保存了 mark 的前一个值。当一个命令修改了 mark 的值时,通常要把旧的值放到 mark-ring 里。可以用 push-mark 和 pop-mark 加入或删除 mark-ring 里的元素。当缓冲区里 mark 存在且指向某个位置时,可以用 region-beginning 和 region-end 得到 point 和 mark 中较小的和较大的值。当然如果使用 transient-mark-mode 时,需要激活 mark,否则会产生一个错误。\n思考题\n写一个命令,对于使用 transient-mark-mode 时,当选中一个区域时显示区域 的起点和终点,否则显示 point-min 和 point-max 的位置。如果不使用 transient-mark-mode,则显示 point 和 mark 的位置。\n按单个字符位置来移动的函数主要使用 goto-char 和 forward-char、backward-char。前者是按缓冲区的绝对位置移动,而后者是按 point 的偏移位置移动比如\n1 2 3 (goto-char (point-min)) ; 跳到缓冲区开始位置 (forward-char 10) ; 向前移动 10 个字符 (forward-char -10) ; 向后移动 10 个字符 可能有一些写 elisp 的人没有读文档或者贪图省事,就在写的 elisp 里直接用 beginning-of-buffer 和 end-of-buffer 来跳到缓冲区的开头和末尾,这其实是不对的。因为这两个命令还做了其它事情,比如设置标记等等。同样,还有一些函数都是不推荐在 elisp 中使用的,如果你准备写一个要发布 elisp,还是要注意一下。\n按词移动使用 forward-word 和 backward-word。至于什么是词,这就要看语法表格的定义了。\n按行移动使用 forward-line。没有 backward-line。forward-line 每次移动都是移动到行首的。所以,如果要移动到当前行的行首,使用 (forward-line 0)。如果不想移动就得到行首和行尾的位置,可以用 line-beginning-position 和 line-end-position。得到当前行的行号可以用 line-number-at-pos。需要注意的是这个行号是从当前状态下的行号,如果使用 narrow-to-region 或者用 widen 之后都有可能改变行号。\n由于 point 只能在 point-min 和 point-max 之间,所以 point 位置测试有时是很重要的,特别是在循环条件测试里。常用的测试函数是 bobp(beginning of buffer predicate)和 eobp(end of buffer predicate)。对于行位置测试使用 bolp(beginning of line predicate)和 eolp(end of line predicate)。\n缓冲区的内容 要得到整个缓冲区的文本,可以用 buffer-string 函数。如果只要一个区间的文本,使用 buffer-substring 函数。point 附近的字符可以用 char-after 和 char-before 得到。point 处的词可以用 current-word 得到,其它类型的文本,比如符号,数字,s 表达式等等,可以用 thing-at-point 函数得到。\n思考题\n参考 thing-at-point 写一个命令标记光标处的 s 表达式。这个命令和 mark-sexp 不同的是,它能从整个 s 表达式的开始标记。\n修改缓冲区的内容 要修改缓冲区的内容,最常见的就是插入、删除、查找、替换了。下面就分别介绍这几种操作。\n插入文本最常用的命令是 insert。它可以插入一个或者多个字符串到当前缓冲区的 point 后。也可以用 insert-char 插入单个字符。插入另一个缓冲区的一个区域使用 insert-buffer-substring。\n删除一个或多个字符使用 delete-char 或 delete-backward-char。删除一个区间使用 delete-region。如果既要删除一个区间又要得到这部分的内容使用 delete-and-extract-region,它返回包含被删除部分的字符串。\n最常用的查找函数是 re-search-forward 和 re-search-backward。这两个函数参数如下\n1 2 (re-search-forward regexp \u0026amp;optional bound noerror count) (re-search-backward regexp \u0026amp;optional bound noerror count) 其中 bound 指定查找的范围,默认是 point-max(对于 re-search-forward)或 point-min(对于 re-search-backward),noerror 是当查找失败后是否要产生一个错误,一般来说在 elisp 里都是自己进行错误处理,所以这个一般设置为 t,这样在查找成功后返回区配的位置,失败后会返回 nil。count 是指定查找匹配的次数。\n替换一般都是在查找之后进行,也是使用 replace-match 函数。和字符串的替换不同的是不需要指定替换的对象了。\n思考题\n从 openoffice 字处理程序里拷贝到 emacs 里的表格通常都是每一个单元格就是一行的。写一个命令,让用户输入表格的列数,把选中区域转换成用制表符分隔的表格。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 (buffer-name \u0026amp;optional buffer) (rename-buffer newname \u0026amp;optional unique) (generate-new-buffer-name name \u0026amp;optional ignore) (current-buffer) (set-buffer buffer-or-name)) (save-current-buffer \u0026amp;rest body) (with-current-buffer buffer-or-name \u0026amp;rest body) (save-excursion \u0026amp;rest body) (get-buffer-create name) (generate-new-buffer name) (kill-buffer buffer-or-name) (buffer-live-p object) (buffer-list \u0026amp;optional frame) (with-temp-buffer \u0026amp;rest body) (make-marker) (set-marker marker position \u0026amp;optional buffer) (point-marker) (copy-marker marker \u0026amp;optional type) (marker-position marker) (marker-buffer marker) (point) (point-min) (point-max) (buffer-size \u0026amp;optional buffer) (mark \u0026amp;optional force) (mark-marker) (set-mark pos) (push-mark \u0026amp;optional location nomsg activate) (pop-mark) (region-beginning) (region-end) (goto-char position) (forward-char \u0026amp;optional n) (backward-char \u0026amp;optional n) (beginning-of-buffer \u0026amp;optional arg) (end-of-buffer \u0026amp;optional arg) (forward-word \u0026amp;optional arg) (backward-word \u0026amp;optional arg) (forward-line \u0026amp;optional n) (line-beginning-position \u0026amp;optional n) (line-end-position \u0026amp;optional n) (line-number-at-pos \u0026amp;optional pos) (narrow-to-region start end) (widen) (bobp) (eobp) (bolp) (eolp) (buffer-string) (buffer-substring start end) (char-after \u0026amp;optional pos) (char-before \u0026amp;optional pos) (current-word \u0026amp;optional strict really-word) (thing-at-point thing) (insert \u0026amp;rest args) (insert-char character count \u0026amp;optional inherit) (insert-buffer-substring buffer \u0026amp;optional start end) (delete-char n \u0026amp;optional killflag) (delete-backward-char n \u0026amp;optional killflag) (delete-region start end) (delete-and-extract-region start end) (re-search-forward regexp \u0026amp;optional bound noerror count) (re-search-backward regexp \u0026amp;optional bound noerror count) 问题解答 可选择区域也可不选择区域的命令 1 2 3 4 5 6 7 (defun show-region (beg end) (interactive (if (or (null transient-mark-mode) mark-active) (list (region-beginning) (region-end)) (list (point-min) (point-max)))) (message \u0026#34;region start from %d to %d\u0026#34; beg end)) 这是通常那种如果选择区域则对这个区域应用命令,否则对整个缓冲区应用命令的方法。我喜欢用 transient-mark-mode,因为它让这种作用于区域的命令更灵活。当然也有人反对,无所谓了,emacs 本身就是很个性化的东西。\n标记整个 s 表达式 1 2 3 4 5 6 7 8 9 (defun mark-whole-sexp () (interactive) (let ((bound (bounds-of-thing-at-point \u0026#39;sexp))) (if bound (progn (goto-char (car bound)) (set-mark (point)) (goto-char (cdr bound))) (message \u0026#34;no sexp found at point!\u0026#34;)))) 学习过程中应该可以看看其它一些函数是怎样实现的,从这些源代码中常常能学到很多有用的技巧和方法。比如要标记整个 s 表达式,联想到 thing-at-point 能得到整个 s 表达式,那自然能得到整个 s 表达式的起点和终点了。所以看看 thing-at-point 的实现,一个很简单的函数,一眼就能发现其中最关键的函数是 bounds-of-thing-at-point,它能返回某个语法实体(syntactic entity)的起点和终点。这样这个命令就很容易就能写出来了。从这个命令中还应该注意到的是对于错误应该很好的处理,让用户明白发生什么错了。比如这里,如果当前光标下没有 s 表达式时,bound 变量为 nil,如果不进行判断,会出现错误:\nwrong type argument: integer-or-marker-p, nil 加上这个判断,用户就明白发生什么事了。\noowriter 表格转换 实现这个目的有多种方法:\n一行一行移动,删除回车,替换成制表符 1 2 3 4 5 6 7 8 9 10 11 12 13 (defun oowrite-table-convert (col beg end) (interactive \u0026#34;ncolumns of table: \\nr\u0026#34;) (setq col (1- col)) (save-excursion (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (while (not (eobp)) (dotimes (i col) (forward-line 1) (backward-delete-char 1) (insert-char ?\\t 1)) (forward-line 1))))) 用 subst-char-in-region 函数直接替换 1 2 3 4 5 6 7 8 9 10 (defun oowrite-table-convert (col beg end) (interactive \u0026#34;ncolumns of table: \\nr\u0026#34;) (save-excursion (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (while (not (eobp)) (subst-char-in-region (point) (progn (forward-line col) (1- (point))) ?\\n ?\\t))))) 用 re-search-forward 和 replace-match 查找替 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (defun oowrite-table-convert (col beg end) (interactive \u0026#34;ncolumns of table: \\nr\u0026#34;) (let (start bound) (save-excursion (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (while (not (eobp)) (setq start (point)) (forward-line col) (setq bound (copy-marker (1- (point)))) (goto-char start) (while (re-search-forward \u0026#34;\\n\u0026#34; bound t) (replace-match \u0026#34;\\t\u0026#34;)) (goto-char (1+ bound))))))) 之所以要给出这三种方法,是想借此说明 elisp 编程其实要实现一个目的通常有 很多种方法,选择一种适合的方法。比如这个问题较好的方法是使用第二种方法, 前提是你要知道有 subst-char-in-region 这个函数,这就要求你对 emacs 提供 的内置的函数比较熟悉了,没有别的办法,只有自己多读 elisp manual,如果你 真想学习 elisp 的话,读 manual 还是值得的,我每读一遍都会有一些新的发 现。如果你不知道这个函数,只知道常用的函数,那么相比较而言,第一种方法 是比较容易想到,也比较容易实现的。但是事实上第三种方法才是最重要的方法, 因为这个方法是适用范围最广的。试想一下你如果不是替换两个字符,而是字符 串的话,前面两种方法都没有办法使用了,而这个方法只要稍微修改就能用了。\n另外,需要特别说明的是这个命令中 bound 使用的是一个标记而不是一个位置, 如果替换的字符串和删除的字符串是相同长度的,当前用什么都可以,否则就要 注意了,因为在替换之后,边界就有可能改变。这也是写查找替换的函数中很容 易出现的一个错误。解决的办法,一是像我这样用一个标记来记录边界位置。另 一种就是用 narrow-to-region 的方法,先把缓冲区缩小到查找替换的区域,结 束后用 widen 展开。当然为了省事,可以直接用 save-restriction。\n操作对象之二 – 窗口 首先还是要定义一下什么是窗口(window)。窗口是屏幕上用于显示一个缓冲区 的部分。和它要区分开来的一个概念是 frame。frame 是 emacs 能够使用屏幕的 部分。可以用窗口的观点来看 frame 和窗口,一个 frame 里可以容纳多个(至 少一个)窗口,而 emacs 可以有多个 frame。(可能需要和通常所说的窗口的概 念要区分开来,一般来说,我们所说的其它程序的窗口更类似于 emacs 的一个 frame,所以也有人认为这里 window 译为窗格更好一些。但是窗格这个词是一个 生造出来的词,我还是用窗口比较顺一些,大家自己注意就行了。)在任何时候, 都有一个被选中的 frame,而在这个 frame 里有一个被选中的窗口,称为选择的 窗口(selected window)。\n分割窗口 刚启动时,emacs 都是只有一个 frame 一个窗口。多个窗口都是用分割窗口的函 数生成的。分割窗口的内建函数是 split-window。这个函数的参数如下:\n(split-window \u0026amp;optional window size horizontal) 这个函数的功能是把当前或者指定窗口进行分割,默认分割方式是水平分割,可 以将参数中的 horizontal 设置为 non-nil 的值,变成垂直分割。如果不指定 大小,则分割后两个窗口的大小是一样的。分割后的两个窗口里的缓冲区是同 一个缓冲区。使用这个函数后,光标仍然在原窗口,而返回的新窗口对象:\n1 2 (selected-window) ; =\u0026gt; #\u0026lt;window 136 on *scratch*\u0026gt; (split-window) ; =\u0026gt; #\u0026lt;window 138 on *scratch*\u0026gt; 需要注意的是,窗口的分割也需要用树的结构来看分割后的窗口,比如这样一个过程:\n+---------------+ +---------------+\r| | | | |\r| win1 | | win1 | win2 |\r| | --\u0026gt; | | |\r| | | | |\r+---------------+ +---------------+\r|\rv\r+---------------+ +---------------+\r| win1 | | | | |\r| | win2 | | win1 | win2 |\r|--------| | \u0026lt;-- |-------| |\r| 3 | 4 | | | win3 | |\r| | | | | | |\r+---------------+ +---------------+ 可以看成是这样一种结构:\n(win1) -\u0026gt; (win1 win2) -\u0026gt; ((win1 win3) win2) -\u0026gt; ((win1 (win3 win4)) win2) 事实上可以用 window-tree 函数得到当前窗口的结构,如果忽略 minibuffer 对应的窗口,得到的应该类似这样的一个结果:\n1 2 3 4 5 (nil (0 0 170 42) (t (0 0 85 42) #\u0026lt;win 3\u0026gt; (nil (0 21 85 42) #\u0026lt;win 8\u0026gt; #\u0026lt;win 10\u0026gt;)) #\u0026lt;win 6\u0026gt;) window-tree 返回值的第一个元素代表子窗口的分割方式,nil 表示水平分割, t 表示垂直分割。第二个元素代表整个结构的大小,这四个数字可以看作是左上 和右下两个顶点的坐标。其余元素是子窗口。每个子窗口也是同样的结构。所以 把前面这个列表还原成窗口排列应该是这样:\n(0,0) +-------------------+\r| | |\r| win 3 | win6 |\r| | |\r(0,21) |---------| |\r| | | |\r| 8 | 10 | |\r| | | |\r+-------------------+ (170, 42)\r(85, 42) 由上面的图可以注意到由 window-tree 返回的结果一些窗口的大小不能确定, 比较上面的 win 8 和 win 10 只能知道它们合并起来的大小,不能确定它们分 别的宽度是多少。\n删除窗口 如果要让一个窗口不显示在屏幕上,要使用 delete-window 函数。如果没有指定 参数,删除的窗口是当前选中的窗口,如果指定了参数,删除的是这个参数对应 的窗口。删除的窗口多出来的空间会自动加到它的邻接的窗口中。如果要删除除 了当前窗口之外的窗口,可以用 delete-other-windows 函数。\n当一个窗口不可见之后,这个窗口对象也就消失了\n1 2 3 4 (setq foo (selected-window)) ; =\u0026gt; #\u0026lt;window 90 on *scratch*\u0026gt; (delete-window) (windowp foo) ; =\u0026gt; t (window-live-p foo) ; =\u0026gt; nil 窗口配置 窗口配置 (window configuration) 包含了 frame 中所有窗口的位置信息:窗口 大小,显示的缓冲区,缓冲区中光标的位置和 mark,还有 fringe,滚动条等等。 用 current-window-configuration 得到当前窗口配置,用 set-window-configuration 来还原\n1 2 3 (setq foo (current-window-configuration)) ;; do sth to make some changes on windows (set-window-configuration foo) 选择窗口 可以用 selected-window 得到当前光标所在的窗口\n1 (selected-window) ; =\u0026gt; #\u0026lt;window 104 on *scratch*\u0026gt; 可以用 select-window 函数使某个窗口变成选中的窗口\n1 2 3 4 5 6 7 (progn (setq foo (selected-window)) (message \u0026#34;original window: %s\u0026#34; foo) (other-window 1) (message \u0026#34;current window: %s\u0026#34; (selected-window)) (select-window foo) (message \u0026#34;back to original window: %s\u0026#34; foo)) 两个特殊的宏可以保存窗口位置执行语句:save-selected-window 和 with-selected-window。它们的作用是在执行语句结束后选择的窗口仍留在执行 语句之前的窗口。with-selected-window 和 save-selected-window 几乎相同, 只不过 save-selected-window 选择了其它窗口。这两个宏不会保存窗口的位置 信息,如果执行语句结束后,保存的窗口已经消失,则会选择最后一个选择的窗 口。\n1 2 3 4 ;; 让另一个窗口滚动到缓冲区开始 (save-selected-window (select-window (next-window)) (goto-char (point-min))) 当前 frame 里所有的窗口可以用 window-list 函数得到。可以用 next-window 来得到在 window-list 里排在某个 window 之后的窗口。对应的用 previous-window 得到排在某个 window 之前的窗口。\n1 2 3 4 5 6 (selected-window) ; =\u0026gt; #\u0026lt;window 245 on *scratch*\u0026gt; (window-list) ;; =\u0026gt; (#\u0026lt;window 245 on *scratch*\u0026gt; #\u0026lt;window 253 on *scratch*\u0026gt; #\u0026lt;window 251 on *info*\u0026gt;) (next-window) ; =\u0026gt; #\u0026lt;window 253 on *scratch*\u0026gt; (next-window (next-window)) ; =\u0026gt; #\u0026lt;window 251 on *info*\u0026gt; (next-window (next-window (next-window))) ; =\u0026gt; #\u0026lt;window 245 on *scratch*\u0026gt; walk-windows 可以遍历窗口,相当于 (mapc proc (window-list))。 get-window-with-predicate 用于查找符合某个条件的窗口。\n窗口大小信息 窗口是一个长方形区域,所以窗口的大小信息包括它的高度和宽度。用来度量窗 口大小的单位都是以字符数来表示,所以窗口高度为 45 指的是这个窗口可以容 纳 45 行字符,宽度为 140 是指窗口一行可以显示 140 个字符。\nmode line 和 header line 都包含在窗口的高度里,所以有 window-height 和 window-body-height 两个函数,后者返回把 mode-line 和 header line 排除后 的高度。\n1 2 (window-height) ; =\u0026gt; 45 (window-body-height) ; =\u0026gt; 44 滚动条和 fringe 不包括在窗口的亮度里,window-width 返回窗口的宽度\n1 (window-width) ; =\u0026gt; 72 也可以用 window-edges 返回各个顶点的坐标信息\n1 (window-edges) ; =\u0026gt; (0 0 73 45) window-edges 返回的位置信息包含了滚动条、fringe、mode line、header line 在内,window-inside-edges 返回的就是窗口的文本区域的位置\n1 (window-inside-edges) ; =\u0026gt; (1 0 73 44) 如果需要的话也可以得到用像素表示的窗口位置信息\n1 2 (window-pixel-edges) ; =\u0026gt; (0 0 511 675) (window-inside-pixel-edges) ; =\u0026gt; (7 0 511 660) 思考题\ncurrent-window-configuration 可以将当前窗口的位置信 息保存到一个变量中以便将来恢复窗口。但是这个对象没有读入形式,所以不 能保存到文件中。请写一个函数可以把当前窗口的位置信息生成一个列表,然 后用一个函数就能从这个列表恢复窗口。提示:这个列表结构用窗口的分割顺 序表示。比如用这样一个列表表示对应的窗口:\n;; +---------------+\r;; | | | |\r;; |-------| |\r;; | | |\r;; +---------------+\r;; =\u0026gt;\r(horizontal 73\r(vertical 22\r(horizontal 36 win win)\rwin)\rwin) 窗口对应的缓冲区 窗口对应的缓冲区可以用 window-buffer 函数得到:\n1 2 (window-buffer) ; =\u0026gt; #\u0026lt;buffer *scratch*\u0026gt; (window-buffer (next-window)) ; =\u0026gt; #\u0026lt;buffer *info*\u0026gt; 缓冲区对应的窗口也可以用 get-buffer-window 得到。如果有多个窗口显示同一 个缓冲区,那这个函数只能返回其中的一个,由 window-list 决定。如果要得到 所有的窗口,可以用 get-buffer-window-list\n1 2 3 4 (get-buffer-window (get-buffer \u0026#34;*scratch*\u0026#34;)) ;; =\u0026gt; #\u0026lt;window 268 on *scratch*\u0026gt; (get-buffer-window-list (get-buffer \u0026#34;*scratch*\u0026#34;)) ;; =\u0026gt; (#\u0026lt;window 268 on *scratch*\u0026gt; #\u0026lt;window 270 on *scratch*\u0026gt;) 让某个窗口显示某个缓冲区可以用 set-window-buffer 函数。 让选中窗口显示某个缓冲区也可以用 switch-to-buffer,但是一般不要在 elisp 编程中用这个命令,如果需要让某个缓冲区成为当前缓冲区使用 set-buffer 函数,如果要让当前窗口显示某个缓冲区,使用 set-window-buffer 函数。\n让一个缓冲区可见可以用 display-buffer。默认的行为是当缓冲区已经显示在某 个窗口中时,如果不是当前选中窗口,则返回那个窗口,如果是当前选中窗口, 且如果传递的 not-this-window 参数为 non-nil 时,会新建一个窗口,显示缓 冲区。如果没有任何窗口显示这个缓冲区,则新建一个窗口显示缓冲区,并返回 这个窗口。display-buffer 是一个比较高级的命令,用户可以通过一些变量来改 变这个命令的行为。比如控制显示的 pop-up-windows, display-buffer-reuse-frames,pop-up-frames,控制新建窗口高度的 split-height-threshold,even-window-heights,控制显示的 frame 的 special-display-buffer-names,special-display-regexps, special-display-function,控制是否应该显示在当前选中窗口 same-window-buffer-names,same-window-regexps 等等。如果这些还不能满 足你的要求(事实上我觉得已经太复杂了),你还可以自己写一个函数,将 display-buffer-function 设置成这个函数。\n思考题\n前一个思考题只能还原窗口,不能还原缓冲区。请修改一下使它能保存缓冲区信息,还原时让对应的窗口显示对应的缓冲区。\n改变窗口显示区域 每个窗口会保存一个显示缓冲区的起点位置,这个位置对应于窗口左上角光标在 缓冲区里的位置。可以用 window-start 函数得到某个窗口的起点位置。可以通 过 set-window-start 来改变显示起点位置。可以通过 pos-visible-in-window-p 来检测缓冲区中某个位置是否是可见的。 但是直接通过 set-window-start 来控制显示比较容易出现错误,因为 set-window-start 并不会改变 point 所在的位置,在窗口调用 redisplay 函 数之后 point 会跳到相应的位置。如果你确实有这个需要,我建议还是用: (with-selected-window window (goto-char pos)) 来代替。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 (windowp object) (split-window \u0026amp;optional window size horflag) (selected-window) (window-tree \u0026amp;optional frame) (delete-window \u0026amp;optional window) (delete-other-windows \u0026amp;optional window) (current-window-configuration \u0026amp;optional frame) (set-window-configuration configuration) (other-window arg \u0026amp;optional all-frames) (save-selected-window \u0026amp;rest body) (with-selected-window window \u0026amp;rest body) (window-list \u0026amp;optional frame minibuf window) (next-window \u0026amp;optional window minibuf all-frames) (previous-window \u0026amp;optional window minibuf all-frames) (walk-windows proc \u0026amp;optional minibuf all-frames) (get-window-with-predicate predicate \u0026amp;optional minibuf all-frames default) (window-height \u0026amp;optional window) (window-body-height \u0026amp;optional window) (window-width \u0026amp;optional window) (window-edges \u0026amp;optional window) (window-inside-edges \u0026amp;optional window) (window-pixel-edges \u0026amp;optional window) (window-inside-pixel-edges \u0026amp;optional window) (window-buffer \u0026amp;optional window) (get-buffer-window buffer-or-name \u0026amp;optional frame) (get-buffer-window-list buffer-or-name \u0026amp;optional minibuf frame) (set-window-buffer window buffer-or-name \u0026amp;optional keep-margins) (switch-to-buffer buffer-or-name \u0026amp;optional norecord) (display-buffer buffer-or-name \u0026amp;optional not-this-window frame) (window-start \u0026amp;optional window) (set-window-start window pos \u0026amp;optional noforce) 问题解答 保存窗口位置信息 这是我的答案。欢迎提出改进意见\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 (defun my-window-tree-to-list (tree) (if (windowp tree) \u0026#39;win (let ((dir (car tree)) (children (cddr tree))) (list (if dir \u0026#39;vertical \u0026#39;horizontal) (if dir (my-window-height (car children)) (my-window-width (car children))) (my-window-tree-to-list (car children)) (if (\u0026gt; (length children) 2) (my-window-tree-to-list (cons dir (cons nil (cdr children)))) (my-window-tree-to-list (cadr children))))))) (defun my-window-width (win) (if (windowp win) (window-width win) (let ((edge (cadr win))) (- (nth 2 edge) (car edge))))) (defun my-window-height (win) (if (windowp win) (window-height win) (let ((edge (cadr win))) (- (nth 3 edge) (cadr edge))))) (defun my-list-to-window-tree (conf) (when (listp conf) (let (newwin) (setq newwin (split-window nil (cadr conf) (eq (car conf) \u0026#39;horizontal))) (my-list-to-window-tree (nth 2 conf)) (select-window newwin) (my-list-to-window-tree (nth 3 conf))))) (defun my-restore-window-configuration (winconf) (delete-other-windows) (my-list-to-window-tree winconf)) (defun my-current-window-configuration () (my-window-tree-to-list (car (window-tree)))) ;; test code here (setq foo (my-current-window-configuration)) ;; do sth to change windows (my-restore-window-configuration foo) 改进的保存窗口信息的函数 由于缓冲区对象也是没有读入形式的,所以返回的列表里只能用缓冲区名来代表 缓冲区,只要没有修改过缓冲区的名字,就能正确的还原缓冲区。如果对于访问 文件的缓冲区,使用文件名可能是更好的想法。保存信息只要对 my-window-tree-to-list 函数做很小的修改就能用了。而恢复窗口则要做较大 改动。my-list-to-window-tree 加了一个函数参数,这样这个函数的可定制性 更高一些。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 (defun my-window-tree-to-list (tree) (if (windowp tree) (buffer-name (window-buffer tree)) (let ((dir (car tree)) (children (cddr tree))) (list (if dir \u0026#39;vertical \u0026#39;horizontal) (if dir (my-window-height (car children)) (my-window-width (car children))) (my-window-tree-to-list (car children)) (if (\u0026gt; (length children) 2) (my-window-tree-to-list (cons dir (cons nil (cdr children)))) (my-window-tree-to-list (cadr children))))))) (defun my-list-to-window-tree (conf set-winbuf) (let ((newwin (split-window nil (cadr conf) (eq (car conf) \u0026#39;horizontal)))) (if (eq (car conf) \u0026#39;horizontal) (progn (funcall set-winbuf (selected-window) (nth 2 conf)) (select-window newwin) (if (listp (nth 3 conf)) (my-list-to-window-tree (nth 3 conf)) (funcall set-winbuf newwin (nth 3 conf)))) (if (listp (nth 2 conf)) (my-list-to-window-tree (nth 2 conf)) (funcall set-winbuf (selected-window) (nth 2 conf))) (select-window newwin) (funcall set-winbuf newwin (nth 3 conf))))) (defun my-restore-window-configuration (winconf) (let ((buf (current-buffer))) (delete-other-windows) (my-list-to-window-tree winconf (lambda (win name) (set-window-buffer win (or (get-buffer name) buf)))))) 操作对象之三 – 文件 作为一个编辑器,自然文件是最重要的操作对象之一。这一节要介绍有关文件的一系列命令,比如查找文件,读写文件,文件信息、读取目录、文件名操作等。\n打开文件的过程 当你打开一个文件时,实际上 emacs 做了很多事情:\n把文件名展开成为完整的文件名 判断文件是否存在 判断文件是否可读或者文件大小是否太大 查看文件是否已经打开,是否被锁定 向缓冲区插入文件内容 设置缓冲区的模式 这还只是简单的一个步骤,实际情况比这要复杂的多,许多异常需要考虑。而且 为了所有函数的可扩展性,许多变量、handler 和 hook 加入到文件操作的函数 中,使得每一个环节都可以让用户或者 elisp 开发者可以定制,甚至完全接管 所有的文件操作。\n这里需要区分两个概念:文件和缓冲区。它们是两个不同的对象,文件是在计算 机上可持久保存的信息,而缓冲区是 emacs 中包含文件内容信息的对象,在 emacs 退出后就会消失,只有当保存缓冲区之后缓冲区里的内容才写到文件中去。\n文件读写 打开一个文件的命令是 find-file。这命令使一个缓冲区访问某个文件,并让这 个缓冲区成为当前缓冲区。在打开文件过程中会调用 find-file-hook。 find-file-noselect 是所有访问文件的核心函数。与 find-file 不同,它只返 回访问文件的缓冲区。这两个函数都有一个特点,如果 emacs 里已经有一个缓冲 区访问这个文件的话,emacs 不会创建另一个缓冲区来访问文件,而只是简单返 回或者转到这个缓冲区。怎样检查有没有缓冲区是否访问某个文件呢?所有和文 件关联的缓冲区里都有一个 buffer-local 变量 buffer-file-name。但是不要直 接设置这个变量来改变缓冲区关联的文件。而是使用 set-visited-file-name 来 修改。同样不要直接从 buffer-list 里搜索 buffer-file-name 来查找和某个文 件关联的缓冲区。应该使用 get-file-buffer 或者 find-buffer-visiting。\n1 2 3 4 5 6 (find-file \u0026#34;~/temp/test.txt\u0026#34;) (with-current-buffer (find-file-noselect \u0026#34;~/temp/test.txt\u0026#34;) buffer-file-name) ; =\u0026gt; \u0026#34;/home/ywb/temp/test.txt\u0026#34; (find-buffer-visiting \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; #\u0026lt;buffer test.txt\u0026gt; (get-file-buffer \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; #\u0026lt;buffer test.txt\u0026gt; 保存一个文件的过程相对简单一些。首先创建备份文件,处理文件的位模式,将 缓冲区写入文件。保存文件的命令是 save-buffer。相当于其它编辑器里另存为 的命令是 write-file。在这个过程中会调用一些函数或者 hook。 write-file-functions 和 write-contents-functions 几乎功能完全相同。它们 都是在写入文件之前运行的函数,如果这些函数中有一个返回了 non-nil 的值, 则会认为文件已经写入了,后面的函数都不会运行,而且也不会使用再调用其它 写入文件的函数。这两个变量有一个重要的区别是 write-contents-functions 在 改变主模式之后会被修改,因为它没有 permanent-local 属性,而 write-file-functions 则会仍然保留。before-save-hook 和 write-file-functions 功能也比较类似,但是这个变量里的函数会逐个执行,不 论返回什么值也不会影响后面文件的写入。after-save-hook 是在文件已经写入 之后才调用的 hook,它是 save-buffer 最后一个动作。\n但是实际上在 elisp 编程过程中经常遇到的一个问题是读取一个文件中的内容, 读取完之后并不希望这个缓冲区还留下来,如果直接用 kill-buffer 可能会把 用户打开的文件关闭。而且 find-file-noselect 做的事情实在超出我们的需要 的。这时你可能需要的是更底层的文件读写函数,它们是 insert-file-contents 和 write-region,调用形式分别是\n1 2 (insert-file-contents filename \u0026amp;optional visit beg end replace) (write-region start end filename \u0026amp;optional append visit lockname mustbenew) insert-file-contents 可以插入文件中指定部分到当前缓冲区中。如果指定 visit 则会标记缓冲区的修改状态并关联缓冲区到文件,一般是不用的。 replace 是指是否要删除缓冲区里其它内容,这比先删除缓冲区其它内容后插入文 件内容要快一些,但是一般也用不上。insert-file-contents 会处理文件的编 码,如果不需要解码文件的话,可以用 insert-file-contents-literally。\nwrite-region 可以把缓冲区中的一部分写入到指定文件中。如果指定 append 则是添加到文件末尾。和 insert-file-contents 相似,visit 参数也会把缓冲 区和文件关联,lockname 则是文件锁定的名字,mustbenew 确保文件存在时会 要求用户确认操作。\n思考题\n写一个函数提取出某个 c 头文件中的函数声明中的函数名和声明位置。\n文件信息 文件是否存在可以使用 file-exists-p 来判断。对于目录和一般文件都可以用 这个函数进行判断,但是符号链接只有当目标文件存在时才返回 t。\n如何判断文件是否可读或者可写呢?file-readable-p、file-writable-p, file-executable-p 分用来测试用户对文件的权限。文件的位模式还可以用 file-modes 函数得到。\n1 2 3 4 5 (file-exists-p \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; t (file-readable-p \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; t (file-writable-p \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; t (file-executable-p \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; nil (format \u0026#34;%o\u0026#34; (file-modes \u0026#34;~/temp/test.txt\u0026#34;)) ; =\u0026gt; \u0026#34;644\u0026#34; 文件类型判断可以使用 file-regular-p、file-directory-p、file-symlink-p, 分别判断一个文件名是否是一个普通文件(不是目录,命名管道、终端或者其它 io 设备)、文件名是否一个存在的目录、文件名是否是一个符号链接。其中 file-symlink-p 当文件名是一个符号链接时会返回目标文件名。文件的真实名 字也就是除去相对链接和符号链接后得到的文件名可以用 file-truename 得到。 事实上每个和文件关联的 buffer 里也有一个缓冲区局部变量 buffer-file-truename 来记录这个文件名。\n1 2 3 4 5 6 $ ls -l t.txt lrwxrwxrwx 1 ywb ywb 8 2007-07-15 15:51 t.txt -\u0026gt; test.txt (file-regular-p \u0026#34;~/temp/t.txt\u0026#34;) ; =\u0026gt; t (file-directory-p \u0026#34;~/temp/t.txt\u0026#34;) ; =\u0026gt; nil (file-symlink-p \u0026#34;~/temp/t.txt\u0026#34;) ; =\u0026gt; \u0026#34;test.txt\u0026#34; (file-truename \u0026#34;~/temp/t.txt\u0026#34;) ; =\u0026gt; \u0026#34;/home/ywb/temp/test.txt\u0026#34; 文件更详细的信息可以用 file-attributes 函数得到。这个函数类似系统的 stat 命令,返回文件几乎所有的信息,包括文件类型,用户和组用户,访问日 期、修改日期、status change 日期、文件大小、文件位模式、inode number、 system number。这是我写的方便使用的帮助函数:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 (defun file-stat-type (file \u0026amp;optional id-format) (car (file-attributes file id-format))) (defun file-stat-name-number (file \u0026amp;optional id-format) (cadr (file-attributes file id-format))) (defun file-stat-uid (file \u0026amp;optional id-format) (nth 2 (file-attributes file id-format))) (defun file-stat-gid (file \u0026amp;optional id-format) (nth 3 (file-attributes file id-format))) (defun file-stat-atime (file \u0026amp;optional id-format) (nth 4 (file-attributes file id-format))) (defun file-stat-mtime (file \u0026amp;optional id-format) (nth 5 (file-attributes file id-format))) (defun file-stat-ctime (file \u0026amp;optional id-format) (nth 6 (file-attributes file id-format))) (defun file-stat-size (file \u0026amp;optional id-format) (nth 7 (file-attributes file id-format))) (defun file-stat-modes (file \u0026amp;optional id-format) (nth 8 (file-attributes file id-format))) (defun file-stat-guid-changep (file \u0026amp;optional id-format) (nth 9 (file-attributes file id-format))) (defun file-stat-inode-number (file \u0026amp;optional id-format) (nth 10 (file-attributes file id-format))) (defun file-stat-system-number (file \u0026amp;optional id-format) (nth 11 (file-attributes file id-format))) (defun file-attr-type (attr) (car attr)) (defun file-attr-name-number (attr) (cadr attr)) (defun file-attr-uid (attr) (nth 2 attr)) (defun file-attr-gid (attr) (nth 3 attr)) (defun file-attr-atime (attr) (nth 4 attr)) (defun file-attr-mtime (attr) (nth 5 attr)) (defun file-attr-ctime (attr) (nth 6 attr)) (defun file-attr-size (attr) (nth 7 attr)) (defun file-attr-modes (attr) (nth 8 attr)) (defun file-attr-guid-changep (attr) (nth 9 attr)) (defun file-attr-inode-number (attr) (nth 10 attr)) (defun file-attr-system-number (attr) (nth 11 attr)) 前一组函数是直接由文件名访问文件信息,而后一组函数是由 file-attributes 的返回值来得到文件信息。\n修改文件信息 重命名和复制文件可以用 rename-file 和 copy-file。删除文件使用 delete-file。创建目录使用 make-directory 函数。不能用 delete-file 删除 目录,只能用 delete-directory 删除目录。当目录不为空时会产生一个错误。\n设置文件修改时间使用 set-file-times。设置文件位模式可以用 set-file-modes 函数。set-file-modes 函数的参数必须是一个整数。你可以用位 函数 logand、logior 和 logxor 函数来进行位操作。\n思考题\n写一个函数模拟 chmod 命令的行为。\n文件名操作 虽然 mswin 的文件名使用的路径分隔符不同,但是这里介绍的函数都能用于 mswin 形式的文件名,只是返回的文件名都是 unix 形式了。路径一般由目录和 文件名,而文件名一般由主文件名 (basename)、文件名后缀和版本号构成。 emacs 有一系列函数来得到路径中的不同部分\n1 2 3 4 5 6 (file-name-directory \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; \u0026#34;~/temp/\u0026#34; (file-name-nondirectory \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; \u0026#34;test.txt\u0026#34; (file-name-sans-extension \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; \u0026#34;~/temp/test\u0026#34; (file-name-extension \u0026#34;~/temp/test.txt\u0026#34;) ; =\u0026gt; \u0026#34;txt\u0026#34; (file-name-sans-versions \u0026#34;~/temp/test.txt~\u0026#34;) ; =\u0026gt; \u0026#34;~/temp/test.txt\u0026#34; (file-name-sans-versions \u0026#34;~/temp/test.txt.~1~\u0026#34;) ; =\u0026gt; \u0026#34;~/temp/test.txt\u0026#34; 路径如果是从根目录开始的称为是绝对路径。测试一个路径是否是绝对路径使用 file-name-absolute-p。如果在 unix 或 gnu/linux 系统,以 ~ 开头的路径也是绝对路径。在 mswin 上,以 “/” 、 “\u0026quot;、“x:” 开头的路径都是绝对路径。如果不是绝对路径,可以使用 expand-file-name 来得到绝对路径。把一个绝对路径转换成相对某个路径的相 对路径的可以用 file-relative-name 函数。\n1 2 3 4 5 6 (file-name-absolute-p \u0026#34;~rms/foo\u0026#34;) ; =\u0026gt; t (file-name-absolute-p \u0026#34;/user/rms/foo\u0026#34;) ; =\u0026gt; t (expand-file-name \u0026#34;foo\u0026#34;) ; =\u0026gt; \u0026#34;/home/ywb/foo\u0026#34; (expand-file-name \u0026#34;foo\u0026#34; \u0026#34;/usr/spool/\u0026#34;) ; =\u0026gt; \u0026#34;/usr/spool/foo\u0026#34; (file-relative-name \u0026#34;/foo/bar\u0026#34; \u0026#34;/foo/\u0026#34;) ; =\u0026gt; \u0026#34;bar\u0026#34; (file-relative-name \u0026#34;/foo/bar\u0026#34; \u0026#34;/hack/\u0026#34;) ; =\u0026gt; \u0026#34;../foo/bar\u0026#34; 对于目录,如果要将其作为目录,也就是确保它是以路径分隔符结束,可以用 file-name-as-directory。不要用 (concat dir “/”) 来转换,这会有移植问题。 和它相对应的函数是 directory-file-name\n1 2 (file-name-as-directory \u0026#34;~rms/lewis\u0026#34;) ; =\u0026gt; \u0026#34;~rms/lewis/\u0026#34; (directory-file-name \u0026#34;~lewis/\u0026#34;) ; =\u0026gt; \u0026#34;~lewis\u0026#34; 如果要得到所在系统使用的文件名,可以用 convert-standard-filename。比如 在 mswin 系统上,可以用这个函数返回用 “\u0026quot; 分隔的文件名\n1 (convert-standard-filename \u0026#34;c:/windows\u0026#34;) ;=\u0026gt; \u0026#34;c:\\\\windows\u0026#34; 临时文件 如果需要产生一个临时文件,可以使用 make-temp-file。这个函数按给定前缀产 生一个不和现有文件冲突的文件,并返回它的文件名。如果给定的名字是一个相 对文件名,则产生的文件名会用 temporary-file-directory 进行扩展。也可以 用这个函数产生一个临时文件夹。如果只想产生一个不存在的文件名,可以用 make-temp-name 函数\n1 2 (make-temp-file \u0026#34;foo\u0026#34;) ; =\u0026gt; \u0026#34;/tmp/foo5611dxf\u0026#34; (make-temp-name \u0026#34;foo\u0026#34;) ; =\u0026gt; \u0026#34;foo5611q7l\u0026#34; 读取目录内容 可以用 directory-files 来得到某个目录中的全部或者符合某个正则表达式的 文件名。\n1 2 3 4 5 6 7 8 9 10 11 12 13 (directory-files \u0026#34;~/temp/dir/\u0026#34;) ;; =\u0026gt; ;; (\u0026#34;#foo.el#\u0026#34; \u0026#34;.\u0026#34; \u0026#34;.#foo.el\u0026#34; \u0026#34;..\u0026#34; \u0026#34;foo.el\u0026#34; \u0026#34;t.pl\u0026#34; \u0026#34;t2.pl\u0026#34;) (directory-files \u0026#34;~/temp/dir/\u0026#34; t) ;; =\u0026gt; ;; (\u0026#34;/home/ywb/temp/dir/#foo.el#\u0026#34; ;; \u0026#34;/home/ywb/temp/dir/.\u0026#34; ;; \u0026#34;/home/ywb/temp/dir/.#foo.el\u0026#34; ;; \u0026#34;/home/ywb/temp/dir/..\u0026#34; ;; \u0026#34;/home/ywb/temp/dir/foo.el\u0026#34; ;; \u0026#34;/home/ywb/temp/dir/t.pl\u0026#34; ;; \u0026#34;/home/ywb/temp/dir/t2.pl\u0026#34;) (directory-files \u0026#34;~/temp/dir/\u0026#34; nil \u0026#34;\\\\.pl$\u0026#34;) ; =\u0026gt; (\u0026#34;t.pl\u0026#34; \u0026#34;t2.pl\u0026#34;) directory-files-and-attributes 和 directory-files 相似,但是返回的列表 中包含了 file-attributes 得到的信息。file-name-all-versions 用于得到某 个文件在目录中的所有版本,file-expand-wildcards 可以用通配符来得到目录 中的文件列表。\n思考题\n写一个函数返回当前目录包括子目录中所有文件名。\n神奇的 handle 如果不把文件局限在存储在本地机器上的信息,如果有一套基本的文件操作,比 如判断文件是否存在、打开文件、保存文件、得到目录内容之类,那远程的文件 和本地文件的差别也仅在于文件名表示方法不同而已。在 emacs 里,底层的文件 操作函数都可以托管给 elisp 中的函数,这样只要用 elisp 实现了某种类型文 件的基本操作,就能像编辑本地文件一样编辑其它类型文件了。\n决定何种类型的文件名使用什么方式来操作是在 file-name-handler-alist 变 量定义的。它是由形如 (regexp . handler) 的列表。如果文件名匹配这个 regexp 则使用 handler 来进行相应的文件操作。这里所说的文件操作,具体的 来说有这些函数:\n`access-file\u0026#39;, `add-name-to-file\u0026#39;, `byte-compiler-base-file-name\u0026#39;,\r`copy-file\u0026#39;, `delete-directory\u0026#39;, `delete-file\u0026#39;,\r`diff-latest-backup-file\u0026#39;, `directory-file-name\u0026#39;, `directory-files\u0026#39;,\r`directory-files-and-attributes\u0026#39;, `dired-call-process\u0026#39;,\r`dired-compress-file\u0026#39;, `dired-uncache\u0026#39;,\r`expand-file-name\u0026#39;, `file-accessible-directory-p\u0026#39;, `file-attributes\u0026#39;,\r`file-directory-p\u0026#39;, `file-executable-p\u0026#39;, `file-exists-p\u0026#39;,\r`file-local-copy\u0026#39;, `file-remote-p\u0026#39;, `file-modes\u0026#39;,\r`file-name-all-completions\u0026#39;, `file-name-as-directory\u0026#39;,\r`file-name-completion\u0026#39;, `file-name-directory\u0026#39;, `file-name-nondirectory\u0026#39;,\r`file-name-sans-versions\u0026#39;, `file-newer-than-file-p\u0026#39;,\r`file-ownership-preserved-p\u0026#39;, `file-readable-p\u0026#39;, `file-regular-p\u0026#39;,\r`file-symlink-p\u0026#39;, `file-truename\u0026#39;, `file-writable-p\u0026#39;,\r`find-backup-file-name\u0026#39;, `find-file-noselect\u0026#39;,\r`get-file-buffer\u0026#39;, `insert-directory\u0026#39;, `insert-file-contents\u0026#39;,\r`load\u0026#39;, `make-auto-save-file-name\u0026#39;, `make-directory\u0026#39;,\r`make-directory-internal\u0026#39;, `make-symbolic-link\u0026#39;,\r`rename-file\u0026#39;, `set-file-modes\u0026#39;, `set-file-times\u0026#39;,\r`set-visited-file-modtime\u0026#39;, `shell-command\u0026#39;, `substitute-in-file-name\u0026#39;,\r`unhandled-file-name-directory\u0026#39;, `vc-registered\u0026#39;,\r`verify-visited-file-modtime\u0026#39;,\r`write-region\u0026#39;. 在 handle 里,可以只接管部分的文件操作,其它仍交给 emacs 原来的函数来完 成。举一个简单的例子。比如最新版本的 emacs 把 *scratch* 的 auto-save-mode 打开了。如果你不想这个缓 冲区的自动保存的文件名散布得到处都是,可以想办法让这个缓冲区的自动保存 文件放到指定的目录中。刚好 make-auto-save-file-name 是在上面这个列表里 的,但是不幸的是在函数定义里 make-auto-save-file-name 里不对不关联文件 的缓冲区使用 handler(我觉得是一个 bug 呀),继续往下看,发现生成保存文 件名是使用了 expand-file-name 函数,所以解决办法就产生了:\n1 2 3 4 5 6 7 8 9 10 (defun my-scratch-auto-save-file-name (operation \u0026amp;rest args) (if (and (eq operation \u0026#39;expand-file-name) (string= (car args) \u0026#34;#*scratch*#\u0026#34;)) (expand-file-name (concat \u0026#34;~/.emacs.d/backup/\u0026#34; (car args))) (let ((inhibit-file-name-handlers (cons \u0026#39;my-scratch-auto-save-file-name (and (eq inhibit-file-name-operation operation) inhibit-file-name-handlers))) (inhibit-file-name-operation operation)) (apply operation args)))) 函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 (find-file filename \u0026amp;optional wildcards) (find-file-noselect filename \u0026amp;optional nowarn rawfile wildcards) (set-visited-file-name filename \u0026amp;optional no-query along-with-file) (get-file-buffer filename) (find-buffer-visiting filename \u0026amp;optional predicate) (save-buffer \u0026amp;optional args) (insert-file-contents filename \u0026amp;optional visit beg end replace) (insert-file-contents-literally filename \u0026amp;optional visit beg end replace) (write-region start end filename \u0026amp;optional append visit lockname mustbenew) (file-exists-p filename) (file-readable-p filename) (file-writable-p filename) (file-executable-p filename) (file-modes filename) (file-regular-p filename) (file-directory-p filename) (file-symlink-p filename) (file-truename filename) (file-attributes filename \u0026amp;optional id-format) (rename-file file newname \u0026amp;optional ok-if-already-exists) (copy-file file newname \u0026amp;optional ok-if-already-exists keep-time preserve-uid-gid) (delete-file filename) (make-directory dir \u0026amp;optional parents) (delete-directory directory) (set-file-modes filename mode) (file-name-directory filename) (file-name-nondirectory filename) (file-name-sans-extension filename) (file-name-sans-versions name \u0026amp;optional keep-backup-version) (file-name-absolute-p filename) (expand-file-name name \u0026amp;optional default-directory) (file-relative-name filename \u0026amp;optional directory) (file-name-as-directory file) (directory-file-name directory) (convert-standard-filename filename) (make-temp-file prefix \u0026amp;optional dir-flag suffix) (make-temp-name prefix) (directory-files directory \u0026amp;optional full match nosort) (dired-files-attributes dir) 问题解答 提取头文件中函数名 这是我写的一个版本,主要是函数声明的正则表达式不好写,函数是很简单的。 从这个例子也可以看出它错误的把那个 typedef void 当成函数声明了。如果你 知道更好的正则表达式,请告诉我一下。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 (defvar header-regexp-list \u0026#39;((\u0026#34;^\\\\(?:\\\\(?:g_const_return\\\\|extern\\\\|const\\\\)\\\\s-+\\\\)?[a-za-z][_a-za-z0-9]*\\ \\\\(?:\\\\s-*[*]*[ \\t\\n]+\\\\|\\\\s-+[*]*\\\\)\\\\([a-za-z][_a-za-z0-9]*\\\\)\\\\s-*(\u0026#34; . 1) (\u0026#34;^\\\\s-*#\\\\s-*define\\\\s-+\\\\([a-za-z][_a-za-z0-9]*\\\\)\u0026#34; . 1))) (defun parse-c-header (file) \u0026#34;extract function name and declaration position using `header-regexp-list\u0026#39;.\u0026#34; (interactive \u0026#34;fheader file: \\np\u0026#34;) (let (info) (with-temp-buffer (insert-file-contents file) (dolist (re header-regexp-list) (goto-char (point-min)) (while (re-search-forward (car re) nil t) (push (cons (match-string (cdr re)) (line-beginning-position)) info)))) info)) (parse-c-header \u0026#34;/usr/include/glib-2.0/gmodule.h\u0026#34;) ;; =\u0026gt; ;; ((\u0026#34;g_module_name\u0026#34; . 1788) ;; (\u0026#34;g_module_open\u0026#34; . 1747) ;; (\u0026#34;g_module_export\u0026#34; . 1396) ;; (\u0026#34;g_module_export\u0026#34; . 1317) ;; (\u0026#34;g_module_import\u0026#34; . 1261) ;; (\u0026#34;g_module_build_path\u0026#34; . 3462) ;; (\u0026#34;g_module_name\u0026#34; . 2764) ;; (\u0026#34;g_module_symbol\u0026#34; . 2570) ;; (\u0026#34;g_module_error\u0026#34; . 2445) ;; (\u0026#34;g_module_make_resident\u0026#34; . 2329) ;; (\u0026#34;g_module_close\u0026#34; . 2190) ;; (\u0026#34;g_module_open\u0026#34; . 2021) ;; (\u0026#34;g_module_supported\u0026#34; . 1894) ;; (\u0026#34;void\u0026#34; . 1673)) 模拟 chmod 的函数 这是一个改变单个文件模式的 chmod 版本。递归版本的就自己作一个练习吧。最 好不要直接调用这个函数,因为每次调用都要解析一次 mode 参数,想一个只解 析一次的方法吧。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 (defun chmod (mode file) \u0026#34;a elisp function to simulate command chmod. note that the command chmod can accept mode match `[ugoa]*([-+=]([rwxxst]*|[ugo]))+\u0026#39;, but this version only can process mode match `[ugoa]*[-+=]([rwx]*|[ugo])\u0026#39;. \u0026#34; (cond ((integerp mode) (if (\u0026gt; mode #o777) (error \u0026#34;unknown mode option: %d\u0026#34; mode))) ((string-match \u0026#34;^[0-7]\\\\{3\\\\}$\u0026#34; mode) (setq mode (string-to-number mode 8))) ((string-match \u0026#34;^\\\\([ugoa]*\\\\)\\\\([-+=]\\\\)\\\\([rwx]*\\\\|[ugo]\\\\)$\u0026#34; mode) (let ((users (append (match-string 1 mode) nil)) (mask-func (string-to-char (match-string 2 mode))) (bits (append (match-string 3 mode) nil)) (oldmode (file-modes file)) (user-list \u0026#39;((?a . #o777) (?u . #o700) (?g . #o070) (?o . #o007))) mask) (when bits (setq bits (* (cond ((= (car bits) ?u) (lsh (logand oldmode #o700) -6)) ((= (car bits) ?g) (lsh (logand oldmode #o070) -3)) ((= (car bits) ?o) (logand oldmode #o007)) (t (+ (if (member ?r bits) 4 0) (if (member ?w bits) 2 0) (if (member ?x bits) 1 0)))) #o111)) (if users (setq mask (apply \u0026#39;logior (delq nil (mapcar (lambda (u) (assoc-default u user-list)) users)))) (setq mask #o777)) (setq mode (cond ((= mask-func ?\\=) (logior (logand bits mask) (logand oldmode (logxor mask #o777)))) ((= mask-func ?\\+) (logior oldmode (logand bits mask))) (t (logand oldmode (logxor (logand bits mask) #o777)))))))) (t (error \u0026#34;unknow mode option: %s\u0026#34; mode))) (set-file-modes file mode)) 列出目录中所有文件 为了让这个函数更类似 directory-files 函数,我把参数设置为和它一样的:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (defun my-directory-all-files (dir \u0026amp;optional full match nosort) (apply \u0026#39;append (delq nil (mapcar (lambda (file) (if (and (not (string-match \u0026#34;^[.]+$\u0026#34; (file-name-nondirectory file))) (file-directory-p (expand-file-name file dir))) (if full (my-directory-all-files file full match nosort) (mapcar (lambda (f) (concat (file-name-as-directory file) f)) (my-directory-all-files (expand-file-name file dir) full match nosort))) (if (string-match match file) (list file)))) (directory-files dir full nil nosort))))) 操作对象之四 – 文本 文本的插入删除,查找替换操作已经在缓冲区一节中讲过了。这一节主要介绍文 本属性。\n如果使用过其它图形界面的文本组件进行编程,它们对于文本的高亮一般都是采 用给对应文本贴上相应标签的方法。emacs 的处理方法也是类似的,但是相比之 下,要强大的多。在 emacs 里,在不同位置上的每个字符都可以有一个属性列表。 这个属性列表和符号的属性列表很相似,都是由一个名字和值构成的对组成。名 字和值都可以是一个 lisp 对象,但是通常名字都是一个符号,这样可以用这个 符号来查找相应的属性值。复制文本通常都会复制相应的字符的文本属性,但是 也可以用相应的函数只复制文本字符串,比如 substring-no-properties、 insert-buffer-substring-no-properties、buffer-substring-no-properties。\n产生一个带属性的字符串可以用 propertize 函数\n1 (propertize \u0026#34;abc\u0026#34; \u0026#39;face \u0026#39;bold) ; =\u0026gt; #(\u0026#34;abc\u0026#34; 0 3 (face bold)) 如果你在一个 text-mode 的缓冲区内用 m-x eval-expression 用 insert 函数 插入前面这个字符串,就会发现插入的文本已经是粗体字了。之所以不能在 *scratch* 产生这种效果,是因为通常我们是开启了 font-lock-mode,在 font-lock-mode 里,文本的 face 属性是实时计算出来的。 在插入文本之后,它的 face 属性已经很快地被改变了。你可以在关闭 font-lock-mode 后再测试一次应该是可以看到 *scratch* 里也是可以用这种方法插入带 face 属性的文本的。\n虽然文本属性的名字可以是任意的,但是一些名字是有特殊含义的。\n属性名 含义 category 值必须是一个符号,这个符号的属性将作为这个字符的属性 face 控制文本的字体和颜色 font-lock-face 和 face 相似,可以作为 font-lock-mode 中静态文本的 face mouse-face 当鼠标停在文本上时的文本 face fontified 记录是否使用 font lock 标记了 face display 改变文本的显示方式,比如高、低、长短、宽窄,或者用图片代替 help-echo 鼠标停在文本上时显示的文字 keymap 光标或者鼠标在文本上时使用的按键映射 local-map 和 keymap 类似,通常只使用 keymap syntax-table 字符的语法表 read-only 不能修改文本,通过 stickness 来选择可插入的位置 invisible 不显示在屏幕上 intangible 把文本作为一个整体,光标不能进入 field 一个特殊标记,有相应的函数可以操作带这个标记的文本 cursor (不知道具体用途) pointer 修改鼠标停在文本上时的图像 line-spacing 新的一行的距离 line-height 本行的高度 modification-hooks 修改这个字符时调用的函数 insert-in-front-hooks 与 modification-hooks 相似,在字符前插入调用的函数 insert-behind-hooks 与 modification-hooks 相似,在字符后插入调用的函数 point-entered 当光标进入时调用的函数 point-left 当光标离开时调用的函数 composition 将多个字符显示为一个字形 正是由于 emacs 的文本有如此丰富的属性,使得 emacs 里的文字才变得多彩, 变得人性化。\n查看文本属性 由于字符串和缓冲区都可以有文本属性,所以下面的函数通常不提供特定参数就是检 查当前缓冲区的文本属性,如果提供文本对象,则是操作对应的文本属性。\n查看文本对象在某处的文本属性可以用 get-text-property 函数。\n1 2 3 4 5 6 7 (setq foo (concat \u0026#34;abc\u0026#34; (propertize \u0026#34;cde\u0026#34; \u0026#39;face \u0026#39;bold))) ; =\u0026gt; #(\u0026#34;abccde\u0026#34; 3 6 (face bold)) (get-text-property 3 \u0026#39;face foo) ; =\u0026gt; bold (save-excursion (goto-char (point-min)) (insert foo)) (get-text-property 4 \u0026#39;face) ; =\u0026gt; bold get-char-property 和 get-text-property 相似,但是它是先查找 overlay 的 文本属性。overlay 是缓冲区文字在屏幕上的显示方式,它属于某个缓冲区,具 有起点和终点,也具有文本属性,可以修改缓冲区对应区域上文本的显示方式。\nget-text-property 是查找某个属性的值,用 text-properties-at 可以得到某 个位置上文本的所有属性。\n修改文本属性 put-text-property 可以给文本对象添加一个属性。比如\n1 2 3 (let ((str \u0026#34;abc\u0026#34;)) (put-text-property 0 3 \u0026#39;face \u0026#39;bold str) str) ; =\u0026gt; #(\u0026#34;abc\u0026#34; 0 3 (face bold)) 和 put-text-property 类似,add-text-properties 可以给文本对象添加一系 列的属性。和 add-text-properties 不同,可以用 set-text-properties 直接 设置文本属性列表。你可以用 =(set-text-properties start end nil)= 来除去 某个区间上的文本属性。也可以用 remove-text-properties 和 remove-list-of-text-properties 来除去某个区域的指定文本属性。这两个函 数的属性列表参数只有名字起作用,值是被忽略的。\n1 2 3 4 5 6 7 8 9 (setq foo (propertize \u0026#34;abcdef\u0026#34; \u0026#39;face \u0026#39;bold \u0026#39;pointer \u0026#39;hand)) ;; =\u0026gt; #(\u0026#34;abcdef\u0026#34; 0 6 (pointer hand face bold)) (set-text-properties 0 2 nil foo) ; =\u0026gt; t foo ; =\u0026gt; #(\u0026#34;abcdef\u0026#34; 2 6 (pointer hand face bold)) (remove-text-properties 2 4 \u0026#39;(face nil) foo) ; =\u0026gt; t foo ; =\u0026gt; #(\u0026#34;abcdef\u0026#34; 2 4 (pointer hand) 4 6 (pointer hand face bold)) (remove-list-of-text-properties 4 6 \u0026#39;(face nil pointer nil) foo) ; =\u0026gt; t foo ; =\u0026gt; #(\u0026#34;abcdef\u0026#34; 2 4 (pointer hand)) 查找文本属性 文本属性通常都是连成一个区域的,所以查找文本属性的函数是查找属性变化的 位置。这些函数一般都不作移动,只是返回查找到的位置。使用这些函数时最好 使用 limit 参数,这样可以提高效率,因为有时一个属性直到缓冲区末尾也没 有变化,在这些文本中可能就是多余的。\nnext-property-change 查找从当前位置起任意一个文本属性发生改变的位置。 next-single-property-change 查找指定的一个文本属性改变的位置。 next-char-property-change 把 overlay 的文本属性考虑在内查找属性发生改 变的位置。next-single-property-change 类似的查找指定的一个考虑 overlay 后文本属性改变的位置。这四个函数都对应有 previous- 开头的函数,用于查 找当前位置之前文本属性改变的位置\n1 2 3 4 5 6 7 8 (setq foo (concat \u0026#34;ab\u0026#34; (propertize \u0026#34;cd\u0026#34; \u0026#39;face \u0026#39;bold) (propertize \u0026#34;ef\u0026#34; \u0026#39;pointer \u0026#39;hand))) ;; =\u0026gt; #(\u0026#34;abcdef\u0026#34; 2 4 (face bold) 4 6 (pointer hand)) (next-property-change 1 foo) ; =\u0026gt; 2 (next-single-property-change 1 \u0026#39;pointer foo) ; =\u0026gt; 4 (previous-property-change 6 foo) ; =\u0026gt; 4 (previous-single-property-change 6 \u0026#39;face foo) ; =\u0026gt; 4 text-property-any 查找区域内第一个指定属性值为给定值的字符位置。 text-property-not-all 和它相反,查找区域内第一个指定属性值不是给定值的 字符位置。\n1 2 3 4 (text-property-any 0 6 \u0026#39;face \u0026#39;bold foo) ; =\u0026gt; 2 (text-property-any 0 6 \u0026#39;face \u0026#39;underline foo) ; =\u0026gt; nil (text-property-not-all 2 6 \u0026#39;face \u0026#39;bold foo) ; =\u0026gt; 4 (text-property-not-all 2 6 \u0026#39;face \u0026#39;underline foo) ; =\u0026gt; 2 思考题\n写一个命令,可在 text-mode 里用指定模式给选中的文本添加高亮。\n函数列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 (propertize string \u0026amp;rest properties) (get-text-property position prop \u0026amp;optional object) (get-char-property position prop \u0026amp;optional object) (text-properties-at position \u0026amp;optional object) (put-text-property start end property value \u0026amp;optional object) (add-text-properties start end properties \u0026amp;optional object) (set-text-properties start end properties \u0026amp;optional object) (remove-text-properties start end properties \u0026amp;optional object) (remove-list-of-text-properties start end list-of-properties \u0026amp;optional object) (next-property-change position \u0026amp;optional object limit) (next-single-property-change position prop \u0026amp;optional object limit) (next-char-property-change position \u0026amp;optional limit) (next-single-char-property-change position prop \u0026amp;optional object limit) (previous-property-change position \u0026amp;optional object limit) (previous-single-property-change position prop \u0026amp;optional object limit) (previous-char-property-change position \u0026amp;optional limit) (previous-single-char-property-change position prop \u0026amp;optional object limit) (text-property-any start end property value \u0026amp;optional object) (text-property-not-all start end property value \u0026amp;optional object) 问题解答 手工高亮代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 (defun my-fontify-region (beg end mode) (interactive (list (region-beginning) (region-end) (intern (completing-read \u0026#34;which mode to use: \u0026#34; obarray (lambda (s) (and (fboundp s) (string-match \u0026#34;-mode$\u0026#34; (symbol-name s)))) t)))) (let ((buf (current-buffer)) (font-lock-verbose nil) (start 1) face face-list) (set-text-properties beg end \u0026#39;(face nil)) (with-temp-buffer (goto-char (point-min)) (insert-buffer-substring buf beg end) (funcall mode) (font-lock-fontify-buffer) (or (get-text-property start \u0026#39;face) (setq start (next-single-property-change start \u0026#39;face))) (while (and start (\u0026lt; start (point-max))) (setq end (or (next-single-property-change start \u0026#39;face) (point-max)) face (get-text-property start \u0026#39;face)) (and face end (setq face-list (cons (list (1- start) (1- end) face) face-list))) (setq start end))) (when face-list (dolist (f (nreverse face-list)) (put-text-property (+ beg (car f)) (+ beg (cadr f)) \u0026#39;face (nth 2 f)))))) 但是直接从那个临时缓冲区里把整个代码拷贝出来也可以了,但是可能某些情况 下,不好修改当前缓冲区,或者不想把那个模式里其它文本属性拷贝出来,这个 函数还是有用的。当然最主要的用途是演示使用查找和添加文本属性的方法。事 实上这个函数也是我用来高亮 muse 模式里 src 标签内源代码所用的方法。但是 不幸的是 muse 模式里这个函数并不能产生很好的效果,不知道为什么。\n后记 到现在为止,我计划写的 elisp 入门内容已经写完了。如果你都看完看懂这些内 容,我想写一些简单的 elisp 应用应该是没有什么问题了。还有一些比较重要的 内容没有涉及到,我在这列一下,如果你对此有兴趣,可以自己看 elisp manual 里相关章节:\n按键映射 (keymap) 和菜单 minibuffer 和补全 进程 调试 主模式 (major mode) 和从属模式 (minor mode) 定制声明 修正函数 (advising function) 非 ascii 字符 其实看一遍 elisp manual 也是很好的选择。我在写这些文字时就是一边参考 elisp manual 一边写的。写的时候我一直有种不安的感觉,这近 3m 的文字被 我压缩到这么一点点是不是太过份了。在 elisp manual 里一些很重要的说明经 常被我一两句话就带过了,有时根本就没有提到,这会不会让刚学 elisp 的人 误入歧途呢?每每想到这一点,我就想就此停住。但是半途而废总是不好的。所 以我还是决定写完应该写的就好了。其它的再说吧。\n如果你是一个新手,我很想知道你看完这个入门教程的感受。当然如果实在没有 兴趣看,也可以告诉我究竟哪里写的不好。我希望在这份文档上花的时间和精力没有白费。\n","date":"2023-05-26","permalink":"https://aituyaa.com/emacs-lisp/","summary":"\u003cp\u003e📕 转载自 \u003ca href=\"http://smacs.github.io/elisp/\"\u003eEmacs Lisp 简明教程 - 水木社区 Emacs 版\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e这是 叶文彬(水木 ID: happierbee) 写的一份 Emacs Lisp 的教程,深入浅出,非常适合初学者。文档的 TeX 代码及 PDF 文档可在\u003ca href=\"http://www.newsmth.net/nForum/article/Emacs/58338?s=58338\"\u003e此处下载\u003c/a\u003e 。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eemacs 的高手不能不会 elisp。但是对于很多人来说 elisp 学习是一个痛苦的历程,至少我是有这样一段经历。因此,我写了这一系列文章,希望能为后来者提供一点捷径。\u003c/p\u003e","title":"emacs lisp"},]
[{"content":"emoji(えもじ 绘文字),就是表情符号,来自日语词汇“絵文字”(假名为“えもじ”,读音即 emoji)。最早是由栗穰崇于 1999 年创作,并在日本网络及手机用户中流行。自苹果公司发布的 ios5 输入法中加入 emoji 后,这种表情符号开始席卷全球,现已被大多数计算机系统所兼容的 unicode 编码采纳,得以普遍运用。\n💡 loveminimal/emojing: emojing - github\n现代浏览器对 emoji 的支持越来越广泛,并且 emoji 也很有趣!\n先前一直使用 emoji 表情符号大全 ,本来布局很紧凑,某天打开就变大了…… 这就很🤕,自己撰一个,以便使用。\n这里,我们实现一个简单的复制 emoji 的页面 🎉 ➭ emojing 。\n用法 它本身就是一个工具页,你可以很方便地使用它 emojing 。\n配置 config.js :\n1 2 3 export default { en: false } 默认为中文界面,如果你想设置为英文,设置 en: true 即可。\n预览 ![[assets/pasted image 20230526112328.png]]\n记录 我们用到了插件 clipboard.js 和 toastify ,并参考完善了 emoji 表情大全_武恩赐的博客 的 emoji 表情集合。\n","date":"2023-05-26","permalink":"https://aituyaa.com/emojings/","summary":"\u003cp\u003eEmoji(えもじ 绘文字),就是表情符号,来自日语词汇“絵文字”(假名为“えもじ”,读音即 emoji)。最早是由栗穰崇于 1999 年创作,并在日本网络及手机用户中流行。自苹果公司发布的 ios5 输入法中加入 emoji 后,这种表情符号开始席卷全球,现已被大多数计算机系统所兼容的 Unicode 编码采纳,得以普遍运用。\u003c/p\u003e","title":"emojings"},]
[{"content":"https://jwt.io/\n相关参考:\njson web token introduction - jwt.io json web token 入门教程 - 阮一峰的网络日志 jwt 介绍 - step by step - 技术译民 - 博客园 ","date":"2023-05-26","permalink":"https://aituyaa.com/json-web-tokens/","summary":"\u003cp\u003e\u003ca href=\"https://jwt.io/\"\u003ehttps://jwt.io/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e相关参考:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://jwt.io/introduction\"\u003eJSON Web Token Introduction - jwt.io\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html\"\u003eJSON Web Token 入门教程 - 阮一峰的网络日志\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.cnblogs.com/ittranslator/p/14595165.html\"\u003eJWT 介绍 - Step by Step - 技术译民 - 博客园\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"json web tokens"},]
[{"content":"manjaro linux 安装之后的初始化配置。\n安装 去 manjaro 官网下载镜像 iso ,使用 rufus 刻录,安装过程……\n换源 1. 编辑\n1 2 3 cd /etc/pacman.d cp mirrorlist mirrorlist.bak nano mirrorlist 文件头部添加如下内容:\n## country : china\rserver = http://mirrors.tuna.tsinghua.edu.cn/manjaro/stable/$repo/$arch\r## country : china\rserver = https://mirrors.ustc.edu.cn/manjaro/stable/$repo/$arch #. 添加 archlinuxcn 源\n1 2 cd /etc nano pacman.conf 文件尾部添加如下内容:\n## 清华大学 (ipv4, ipv6, http, https)\r[archlinuxcn]\rserver = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch 安装 archlinuxcn-keyring ,如下:\n1 sudo pacman -s archlinuxcn-keyring 2. 更新\n1 2 3 sudo pacman -syy # 滚动升级一下(可选) sudo pacman -syyu 映射 caps lock 编辑 .zshrc 或 .bashrc ,添加如下内容:\nsetxkbmap -option ctrl:swapcaps 然后,执行 source ~/.zshrc 或 source ~/.bashrc 使配置生效。\n使用 xmodmap 工具 2 3\n1 2 3 4 5 6 7 8 9 10 11 # 自定义映射表 xmodmap -pke \u0026gt; ~/.xmodmap # 在 ~/.xmodmap 中做好想要的修改 # 如,把 `shift_r` 映射为 `esc` keycode 62 = escape nosymbol escape # 如,把 `caps_lock` 映射为 `control_l` keycode 66 = control_l nosymbol control_l # 测试新的配置文件 xmodmap ~/.xmodmap 中文输入法 安装 fcitx 及其相关依赖,如下:\n1 sudo pacman -s fcitx fcitx-im fcitx-configtool 然后添加输入法配置文件 ~/.xprofile ,添加以下内容:\nexport gtk_im_module=fcitx\rexport qt_im_module=fcitx\rexport xmodifiers=\u0026#34;@im=fcitx\u0026#34; 之后,重启电脑后,添加新的输入法即可。\n五笔输入 windows 上使用的极品五笔,感觉很不错。 linux 上的 fcitx 也是包含 wubi 输入的,但是词库有点可怜, 所以 rime 是一个不错的选择,它是一个输入法框架,可以按需引入。\n1. fcitx rime\n去 rime 官网下载,如果使用拼音输入的话,按照其教程操作后即可,以下内容针对 fcitx 五笔的初始化(ibus 的皮肤不好看,官方的是针对 ibus 的), fcitx 的安装见 中文输入法 ↑ 。\n:: 永远记住先把工具用起来,再慢慢研究配置。\n1 2 3 4 5 6 sudo pacman -s fcitx-rime cd ~/.config/fcitx/rime/ # 克隆极点五笔的配置,足够好用 git clone https://github.com/kylebing/rime-wubi86-jidian.git cp rime-wubi86-jidian/* ./ 如此,重启电脑后,在 fcitx config 工具中添加 rime 后,注销电脑再次登录后即生效。\n2. ibus rime\n1 2 # 安装 sudo pacman -s ibus ibus-rime rime-wubi 附上 ibus 在 .xrpofile 中的值,如下:\n#ibus\rexport gtk_im_module=ibus\rexport xmodifiers=@im=ibus\rexport qt_im_module=ibus\r# 自动重启 ibus 后台服务\ribus-daemon -x -d 中文字体 1\n1 2 # 文泉驿字体 sudo pacman -s wqy-bitmapfont wqy-microhei wqy-microhei-lite wqy-zenhei 美化 字段 选项 window decorations arc osx white transparent global theme blur-glassy maia plasma style blur-glassy 工具 工具 描述 plank 类 mac 的 doc 栏 albert 搜索 mac 的 spotlight faq obs 录制窗口撕裂 system settings → display and monitor → compositor → tearing prevention (\u0026#34;vsync\u0026#34;) 修改为 never 。\n","date":"2023-05-26","permalink":"https://aituyaa.com/manjaro-linux/","summary":"\u003cp\u003eManjaro Linux 安装之后的初始化配置。\u003c/p\u003e","title":"manjaro linux"},]
[{"content":" 这里是 hugo 解析 markdown 内容的展示,如果你使用的是 org mode ,可以跳转 org mode 查看相关样式。\n以下 markdown 正文内容,摘自 markdown 测试文本 ,并添加、修改了一些章节。\nthis post is originated from here and is used for testing markdown style. this post contains nearly every markdown usage. make sure all the markdown elements below show up correctly.\nheaders 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # h1 ## h2 ### h3 #### h4 ##### h5 ###### h6 alternatively, for h1 and h2, an underline-ish style: # alt-h1 ## alt-h2 h1 h2 h3 h4 h5 h6 alternatively, for h1 and h2, an underline-ish style:\nalt-h1 alt-h2 emphasis 1 2 3 4 5 6 7 emphasis, aka italics, with _asterisks_ or _underscores_. strong emphasis, aka bold, with **asterisks** or **underscores**. combined emphasis with **asterisks and _underscores_**. strikethrough uses two tildes. ~~scratch this.~~ emphasis, aka italics, with asterisks or underscores.\nstrong emphasis, aka bold, with asterisks or underscores.\ncombined emphasis with asterisks and underscores.\nstrikethrough uses two tildes. scratch this.\nlists 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 1. first ordered list item 2. another item - unordered sub-list. 1. actual numbers don\u0026#39;t matter, just that it\u0026#39;s a number 1. ordered sub-list 1. and another item. you can have properly indented paragraphs within list items. notice the blank line above, and the leading spaces (at least one, but we\u0026#39;ll use three here to also align the raw markdown). to have a line break without a paragraph, you will need to use two trailing spaces. note that this line is separate, but within the same paragraph. (this is contrary to the typical gfm line break behaviour, where trailing spaces are not required.) - unordered list can use asterisks * or minuses - or pluses * paragraph in unordered list for example like this. common paragraph with some text. and more text. first ordered list item another item unordered sub-list. actual numbers don’t matter, just that it’s a number\nordered sub-list\nand another item.\nyou can have properly indented paragraphs within list items. notice the blank line above, and the leading spaces (at least one, but we’ll use three here to also align the raw markdown).\nto have a line break without a paragraph, you will need to use two trailing spaces. note that this line is separate, but within the same paragraph. (this is contrary to the typical gfm line break behaviour, where trailing spaces are not required.)\nunordered list can use asterisks\nor minuses\nor pluses\nparagraph in unordered list\nfor example like this.\ncommon paragraph with some text. and more text.\ninline html 1 \u0026lt;p\u0026gt;to reboot your computer, press \u0026lt;kbd\u0026gt;ctrl\u0026lt;/kbd\u0026gt;+\u0026lt;kbd\u0026gt;alt\u0026lt;/kbd\u0026gt;+\u0026lt;kbd\u0026gt;del\u0026lt;/kbd\u0026gt;.\u0026lt;/p\u0026gt; to reboot your computer, press ctrl+alt+del.\n1 2 3 4 5 6 7 8 \u0026lt;dl\u0026gt; \u0026lt;dt\u0026gt;definition list\u0026lt;/dt\u0026gt; \u0026lt;dd\u0026gt;is something people use sometimes.\u0026lt;/dd\u0026gt; \u0026lt;dt\u0026gt;markdown in html\u0026lt;/dt\u0026gt; \u0026lt;dd\u0026gt;does *not* work **very** well. use html \u0026lt;em\u0026gt;tags\u0026lt;/em\u0026gt;.\u0026lt;/dd\u0026gt; \u0026lt;/dl\u0026gt; definition list\nis something people use sometimes.\n\u0026lt;dt\u0026gt;markdown in html\u0026lt;/dt\u0026gt;\r\u0026lt;dd\u0026gt;does *not* work **very** well. use html \u0026lt;em\u0026gt;tags\u0026lt;/em\u0026gt;.\u0026lt;/dd\u0026gt; links 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [i\u0026#39;m an inline-style link](https://www.google.com) [i\u0026#39;m an inline-style link with title](https://www.google.com \u0026#34;google\u0026#39;s homepage\u0026#34;) [i\u0026#39;m a reference-style link][arbitrary case-insensitive reference text] [i\u0026#39;m a relative reference to a repository file](../blob/master/license) [you can use numbers for reference-style link definitions][1] or leave it empty and use the [link text itself] some text to show that the reference links can follow later. [arbitrary case-insensitive reference text]: https://hexo.io [1]: https://hexo.io/docs/ [link text itself]: https://hexo.io/api/ i’m an inline-style link\ni’m an inline-style link with title\ni’m a reference-style link\ni’m a relative reference to a repository file\nyou can use numbers for reference-style link definitions\nor leave it empty and use the link text itself\nsome text to show that the reference links can follow later.\nimages 1 2 3 4 5 6 7 8 9 10 hover to see the title text: inline-style:  reference-style: ![alt text][logo] [logo]: https://hexo.io/icon/favicon-196x196.png \u0026#39;logo title text 2\u0026#39; hover to see the title text:\ninline-style:\nreference-style: code and syntax highlighting inline code has back-ticks around it.\n1 2 var s = \u0026#39;javascript syntax highlighting\u0026#39;; alert(s); 1 2 s = \u0026#34;python syntax highlighting\u0026#34; print s no language indicated, so no syntax highlighting.\rbut let\u0026#39;s throw in a \u0026lt;b\u0026gt;tag\u0026lt;/b\u0026gt;. tables 1 2 3 4 5 | | ascii | html | |------------------|---------------------------------|-------------------------------| | single backticks | `\u0026#39;isn\u0026#39;t this fun?\u0026#39;` | \u0026#39;isn\u0026#39;t this fun?\u0026#39; | | quotes | `\u0026#34;isn\u0026#39;t this fun?\u0026#34;` | \u0026#34;isn\u0026#39;t this fun?\u0026#34; | | dashes | `-- is en-dash, --- is em-dash` | -- is en-dash, --- is em-dash | ascii html single backticks 'isn't this fun?' ‘isn’t this fun?’ quotes \u0026quot;isn't this fun?\u0026quot; “isn’t this fun?” dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash colons can be used to align columns.\n1 2 3 4 5 | tables | are | cool | |---------------|:-------------:|-----:| | col 3 is | right-aligned | | | col 2 is | centered | | | zebra stripes | are neat | | tables are cool col 3 is right-aligned col 2 is centered zebra stripes are neat the outer pipes (|) are optional, and you don’t need to make the raw markdown line up prettily. you can also use inline markdown.\n1 2 3 4 | markdown | less | pretty | |----------|-----------|------------| | _still_ | `renders` | **nicely** | | 1 | 2 | 3 | markdown less pretty still renders nicely 1 2 3 you can find more information about latex mathematical expressions here.\nblockquotes blockquotes are very handy in email to emulate reply text. this line is part of the same quote.\nquote break.\nthis is a very long line that will still be quoted properly when it wraps. oh boy let’s keep writing to make sure this is long enough to actually wrap for everyone. oh, you can put markdown into a blockquote.\nhorizontal rule three or more…\n1 2 3 4 5 6 7 8 9 --- hyphens --- asterisks --- underscores hyphens\nasterisks\nunderscores\nline breaks 1 2 3 4 5 6 here\u0026#39;s a line for us to start with. this line is separated from the one above by two newlines, so it will be a _separate paragraph_. this line is also a separate paragraph, but... this line is only separated by a single newline, so it\u0026#39;s a separate line in the _same paragraph_. here’s a line for us to start with.\nthis line is separated from the one above by two newlines, so it will be a separate paragraph.\nthis line is also a separate paragraph, but… this line is only separated by a single newline, so it’s a separate line in the same paragraph.\n1 2 3 4 5 6 7 8 9 this is a regular paragraph. \u0026lt;table\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;foo\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/table\u0026gt; this is another regular paragraph. this is a regular paragraph.\nfoo this is another regular paragraph.\noh essay 在编辑博文的时候,经常想插入一些突然闪现出来的内容,或是于行文无关的吐槽等。为了更好地与正文内容做区分,做了一个定制模式,以 html 格式插入。\n1 2 3 \u0026lt;div class=\u0026#34;oh-essay\u0026#34;\u0026gt; 这就是我们插入的随笔喽…… blablablabla…… \u0026lt;/div\u0026gt; :: 这就是我们插入的随笔喽…… blablablabla……\nbilibili videos :: youbube ? no no no ! bilibili ? yes !\nbilibili 真的很不错,体验上比 youtube 要好,内容类型嘛,则没有后者丰富,这个没办法,生态大环境决定的。\n## youtube videos\r```markdown\r\u0026lt;a href=\u0026#34;https://www.youtube.com/watch?feature=player_embedded\u0026amp;v=arted4rniau\r\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt;\u0026lt;img src=\u0026#34;https://img.youtube.com/vi/arted4rniau/0.jpg\u0026#34;\ralt=\u0026#34;image alt text here\u0026#34; width=\u0026#34;240\u0026#34; height=\u0026#34;180\u0026#34; border=\u0026#34;10\u0026#34; /\u0026gt;\u0026lt;/a\u0026gt;\rpure markdown version:\r[](https://www.youtube.com/watch?v=arted4rniau)\r\u0026lt;a href=\u0026#34;https://www.youtube.com/watch?feature=player_embedded\u0026amp;v=arted4rniau\r\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt;\u0026lt;img src=\u0026#34;https://img.youtube.com/vi/arted4rniau/0.jpg\u0026#34;\ralt=\u0026#34;image alt text here\u0026#34; width=\u0026#34;240\u0026#34; height=\u0026#34;180\u0026#34; border=\u0026#34;10\u0026#34; /\u0026gt;\u0026lt;/a\u0026gt;\rpure markdown version:\r[](https://www.youtube.com/watch?v=arted4r\r``` ","date":"2023-05-26","permalink":"https://aituyaa.com/markdown/","summary":"这里是 Hugo 解析 Markdown 内容的展示,如果你使用的是 Org Mode ,可以跳转 Org Mode 查看相关样式。 以下 markdown 正文内容,摘自 Markdown 测试文本 ,并添加、修改了一些章节。 This post is originated from here and is used for testing markdown style. This post contains","title":"markdown"},]
[{"content":"🔔 摘录自 廖雪峰老师的 sql 教程\nsql,结构化查询语言(structured query language ),是一种数据库查询和程序设计语言,用于访问和操作数据库系统。sql 语句既可以查询数据库中的数据,也可以插入、更新和删除数据库中的数据,还可以对数据库进行管理和维护操作。\n:: 数据库能听懂的语言 – sql\nsql 关系数据库概述 为什么需要数据库? 为了优化数据的读写及管理。 应用程序不需要自己管理数据,而是通过数据库软件提供的接口来读写数据。至于数据本身如何存储到文件,那就是数据库软件的事情了。\n数据库按照数据结构来组织、存储和管理数据,有三种模型:层次模型、网关模型和关系模型。\n关系数据库的关系模型是基于数学理论建立的(有兴趣的可以深入下)。其支持的标准数据类型包括数值、字符串、时间等,如下表:\n名称 类型 说明 int 整型 4 字节,约 +/-21 亿 bigint 长整型 8 字节,约 +/-922 亿 real/float(24) 浮点型 4 字节 double 浮点型 8 字节 decimal(m,n) 高精度小数 decimal(20,10) 表示一共 20 位,其中小数 10 位,通常用于财务计算 char(n) 定长字符串 char(100) 总是存储 100 个字符的字符串 varchar(n) 变长字符串 varchar(100) 可以存储 0~100 个字符的字符串 boolean 布尔类型 存储 true 或者 false date 日期类型 2018-06-22 time 时间类型 12:20:59 datetime 日期和时间类型 2018-06-22 12:20:59 通常来说, bigint 能满足整数存储的需求, varchar(n) 能满足字符串存储的需求。其他,还有一些不常用的数据类型,如 tinyint (范围在 0~255)。各数据库厂商还会支持特定的数据类型,如 json 。\nsql 语言关键字不区分大小写!\n:: 一般约定 sql 关键字总是大写,以示突出,表名和列名均使用小写。\n安装 mysql 1\n选择国内镜像下载(如 华为的 mysql 镜像),官网太慢了……\n如果,你要也要下载 mysql-8.0.26 ,可以直接 点此下载 。\n安装完成之后,将执行路径,如 c:\\program files\\mysql\\mysql server 8.0\\bin 加入到电脑环境变量中(具体方法自行百度),以管理员权限打开 cmd 或 powershell:\n输入 mysqld --initialize --console ,进行初始化,该步可以得到 mysql 的初始密码; 输入 mysqld --install ,如果需要安装多个 mysql 服务,这步输入 mysql --install [服务名]; 输入 net start mysql ,启动 mysql 服务; 输入 mysql -u root -p ,使用初始密码登录 mysql ; 登录后,把初始密码改掉,输入 alter user 'root'@'localhost' identified with mysql_native_password by '你自己的密码'; (注意带分号); 输入 show databases; ,可以查看包含哪些数据库了。 win10 具体安装 mysql 8.0.26 如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 # 1. 初始化 ps c:\\windows\\system32\u0026gt; mysqld --initialize --console 2022-01-17t12:32:47.721393z 0 [system] [my-013169] [server] c:\\program files\\mysql\\mysql server 8.0\\bin\\mysqld.exe (mysqld 8.0.26) initializing of server in progress as process 16088 2022-01-17t12:32:47.739476z 1 [system] [my-013576] [innodb] innodb initialization has started. 2022-01-17t12:32:48.147715z 1 [system] [my-013577] [innodb] innodb initialization has ended. 2022-01-17t12:32:49.184664z 0 [warning] [my-013746] [server] a deprecated tls version tlsv1 is enabled for channel mysql_main 2022-01-17t12:32:49.187008z 0 [warning] [my-013746] [server] a deprecated tls version tlsv1.1 is enabled for channel mysql_main 2022-01-17t12:32:49.384847z 6 [note] [my-010454] [server] a temporary password is generated for root@localhost: gmlai+axi2rn # 2. 安装服务 ps c:\\windows\\system32\u0026gt; mysqld --install service successfully installed. # 3. 启动服务 ps c:\\windows\\system32\u0026gt; net start mysql mysql 服务正在启动 . mysql 服务已经启动成功。 # 4. 使用初始化密码登录 ps c:\\windows\\system32\u0026gt; mysql -u root -p enter password: ************ welcome to the mysql monitor. commands end with ; or \\g. your mysql connection id is 8 server version: 8.0.26 copyright (c) 2000, 2021, oracle and/or its affiliates. oracle is a registered trademark of oracle corporation and/or its affiliates. other names may be trademarks of their respective owners. type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; show databases; error 1820 (hy000): you must reset your password using alter user statement before executing this statement. # 5. 为 root 设置自己的密码,方便下次登录 mysql\u0026gt; alter user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; identified with mysql_native_password by \u0026#39;your_password\u0026#39;; query ok, 0 rows affected (0.00 sec) mysql\u0026gt; show databases; +--------------------+ | database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.01 sec) # 使用 exit 退出 mysql\u0026gt; exit bye # 下次使用新设置的密码 \u0026#39;your_password\u0026#39; 登录即可 ps c:\\windows\\system32\u0026gt; mysql -u root -p enter password: ************* welcome to the mysql monitor. commands end with ; or \\g. your mysql connection id is 10 server version: 8.0.26 mysql community server - gpl copyright (c) 2000, 2021, oracle and/or its affiliates. oracle is a registered trademark of oracle corporation and/or its affiliates. other names may be trademarks of their respective owners. type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; 其他版本的安装过程,大同小异,出现的问题可能是形形色色,直接百度就 ok 了。\nubuntu20 安装相关 3\n1 2 3 4 5 6 sudo apt update \u0026amp;\u0026amp; sudo apt install mysql-server # 安装 sudo service mysql status # 查看服务状态 # or systemctl status mysql sudo mysql # ubuntu 默认 root@localhost 是无密码的 mysql\u0026gt; exit; 如何为 root@localhost 设置密码呢? 4\n1 2 mysql\u0026gt; alert user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; identified with mysql_native_password by \u0026#39;你自己的mysql密码\u0026#39;; mysql\u0026gt; flush privileges; 如何当前 mysql 服务可能远程登录呢? 5\n1 2 3 mysql\u0026gt; create user \u0026#39;root\u0026#39;@\u0026#39;%\u0026#39; identified by \u0026#39;你自己的mysql密码\u0026#39;; mysql\u0026gt; grant all privileges on *.* to \u0026#39;root\u0026#39;@\u0026#39;%\u0026#39;; mysql\u0026gt; flush privileges; 然后:\ncd /etc/mysql/mysql.conf.d ; 修改相应的配置文件 mysqld.cnf ; 将 bind-address 127.0.0.1修改为bind-address 0.0.0.0 ; service mysql restart 重启 mysql 服务; netstat -lntp 查看状态。 关系模型 关系数据库是建立在关系模型上,关系模型本质上就是若干存储数据的二维表。表的每一行称为记录(record),每一列称为字段(column)。\n字段定义了数据类型,以及是否允许为 null ( 表示字段不存在)。\n:: 通常情况下,字段应该避免允许为 null。不允许为 null 可以简化查询条件,加快查询速度,也利于应用程序读取数据后无需判断是否为 null。\n关系数据库的表和表之间需要建立“一对多”,“多对一”和“一对一”的关系,关系是通过主键和外键来维护的。\n1. 主键\n主键是关系表中记录的唯一标识,其选取非常重要,注意不要带有业务含义,而应该使用 bigint 自增或者 guid 类型,主键也不应该允许 null 。\n2. 外键\n在一张表中,通过某个字段,可以把数据与另一张表关联起来,这种列称为外键。关系数据库通过外键可以实现一对多、多对多(需要通过中间表)和一对一的关系。外键既可以通过数据库来约束,也可以不设置约束,仅依靠应用程序的逻辑来保证。\n下面来看一个外键约束:\n1 2 3 4 5 6 7 8 9 -- 定义一个外键约束 alter table students add constraint fk_class_id -- 外键约束名称 fk_class_id,任意 foreign key (class_id) -- 指定了 class_id 作为外键 references classes (id); -- 指定了这个关键将关联到 classes 表的 id 列 -- 删除一个外键约束 alert table students drop foreign key fk_class_id; 3. 索引\n在查找记录的时候,想要获得非常快的速度,就需要使用索引。索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。无论是否创建索引,对于用户和应用程序来说,使用关系数据库不会有任何区别。\n查询数据 在关系数据库中,最常用的操作就是查询。\n1. 基本查询\n使用 select 查询的基本语句 select * from \u0026lt;表名\u0026gt; 可以查询一个表的所有行和所有列的数据,查询结果是一个二维表。\n1 2 3 4 -- 语法: -- select * from \u0026lt;表名\u0026gt; -- 如 select * from students; -- 所有的学生记录 2. 条件查询\nselect 语句可以通过 where 条件来设定查询条件,查询结果是满足查询条件的记录,而不是整个表的所有记录。\n1 2 3 4 -- 语法: -- select * from \u0026lt;表名\u0026gt; where \u0026lt;条件表达式\u0026gt; -- 如 select * from students where score \u0026gt; :: 80; -- 分数大于 80 的学生记录 条件表达式可以包含逻辑运算,优先级由高到低为 not \u0026gt; and \u0026gt; or ,当然还有括号。\n:: 此处可以用谐音记忆, not(not) at(and) all(or) - 一点也不,别客气。\n3. 投影查询\n如果我们只希望返回某些列的数据,而不是所有列的数据,可以使用投影查询。\n1 2 3 4 5 6 -- 语法: -- select 列1 [别名1], 列2, 列3 from \u0026lt;表名\u0026gt; [...] -- 如 select id, score, name from students; -- 没设置别名 select id, score points, name from students; -- 设置别名 select id, score points, name from students where gender = \u0026#39;m\u0026#39;; -- 可接 where条件 4. 排序\n使用 order by 可以对结果集进行排序,可以对多列进行升序、倒序排序。\n1 2 3 4 5 6 7 8 9 10 select id, name, gender, score from students order by score; -- 默认 asc 升序,可以省略 select id, name, gender, score from students order by score desc; -- 降序 -- 多列,先按 score ,有相同分数的再按 gender select id, name, gender, score from students order by score desc, gender; -- 如果有 where 子句,那么 order by 子句要放到 where 子句后面(因为是对结果集排序嘛) select id, name, gender, score from students where class_id = 1 order by score desc; 5. 分页查询\n分页实际上就是从结果集中“截取”出第 m~n 条记录。\n1 2 3 4 5 6 7 -- 语法: -- limit \u0026lt;n-m\u0026gt; offset \u0026lt;m\u0026gt; -- 如 select * from students limit 3 offset 0; -- 查询第 1 页 select * from students limit 3 offset 3; -- 查询第 2 页 select * from students limit 3 offset 6; -- 查询第 3 页 -- ... 可见,分页查询的关键在于,首先要确定每页需要显示的结果数量 pagesize (这里为 3),然后根据当前页的索引 pageindex (从 1 开始,页码啦),确定 limit 和 offset 应该设定的值:\nlimit 总是设定为 pagesize ; offset 计算公式为 pagesize * (pageindex - 1) 。 另外:\noffset 是可选的,如果只写 limit 15 ,那么相当于 limit 15 offset 0 ; 在 mysql 中, limit 15 offset 30 还可以简写成 limit 30, 15 ; 使用 limit \u0026lt;m\u0026gt; offset \u0026lt;n\u0026gt; 分页时,随着 n 越来越大,查询效率也会越来越低。 6. 聚合查询\n对于统计总数、平均数这类计算,sql 提供了专门的聚合函数,使用聚合函数进行查询,就是聚合查询。\n函数 说明 count 查询所有列的行数 sum/avg 计算某一数值类型列的合计值/平均值 max/min 计算某一列的最大值/最小值 1 2 select count(*) from students; select avg(score) average from students where gender = \u0026#39;m\u0026#39;; 注意:\n如果 where 条件没有匹配到任何行, count() 会返回 0 ,而其他会返回 null ; 聚合的计算结果虽然是一个数字,但查询的结果仍然是一个二维表,只是这个二维表只有一行一列; 通常,使用聚合查询时,我们应该给列名设置一个别名,便于处理结果。 对于聚合查询,sql 还提供了“分组聚合”的功能。\n1 2 select class_id, count(*) num from students group by class_id; -- count() 的结果不再是一个,因为 group by 子句指定了按 class_id 分组 执行上述 select 语句时,会把 class_id 相同的列先分组,再分别计算,因些会得到不止一行结果(当然,我们此处假设 class_id 有几个不同的值)。\n7. 多表查询\n使用多表查询可以获取 m x n 行记录,查询的结果也是一个二维表。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 -- 语法: -- select * from \u0026lt;表1\u0026gt; [表1别名], \u0026lt;表2\u0026gt; [表2别名]; -- 如 select * from students, classes; -- 可以利用投影查询设置别名 select students.id sid, students.name, students.gender, students.score, classes.id cid, classes.name cname from students, classes; -- sql还允许给表设置一个别名,以在投影查询中引用起来稍微简洁一点 select s.id sid, s.name, s.gender, s.score, c.id cid, c.name cname from students s, classes c; 8. 连接查询\n连接查询是另一种类型的多表查询,对多个表进行 join 运算,简单地说,就是先确定一个主表作为结果集,然后,把其他表的行有选择地“连接”在主表结果集上。\n1 2 3 4 5 6 7 8 9 10 11 -- 语法: -- select ... from \u0026lt;表1\u0026gt; inner join \u0026lt;表2\u0026gt; on \u0026lt;条件...\u0026gt;; -- 如 select s.id, s.name, s.class_id, c.name class_name, -- s.gender, s.score from students s -- 先确定主表,仍然使用 from \u0026lt;表1\u0026gt; inner join classes c -- 再确定需要连接的表,使用 inner join \u0026lt;表2\u0026gt; on s.class_id = c.id; -- 然后确定连接条件,使用 on \u0026lt;条件……\u0026gt; -- 可选,加上 where 子句、 order by 等子句 inner join 是最常用的一种 join 查询,除此之外还有 left/right/full outer join 。\n修改数据 关系数据库的基本操作就是 crud(create 增、retrieve 查、update 改、delete 删)。对于增、删、改,对应的 sql 语句分别是:\ninsert 插入新记录; update 更新已有记录; delete 删除已有记录。 1. insert\n1 2 3 4 5 6 -- 语法: -- insert into \u0026lt;表名\u0026gt; (字段1, 字段2, ...) values (值1, 值2, ...) -- 如 insert into students (class_id, name, gender, score) values (1, \u0026#39;大宝\u0026#39;, \u0026#39;m\u0026#39;, 87), (2, \u0026#39;二宝\u0026#39;, \u0026#39;m\u0026#39;, 81); -- 可以一次性添加多条记录 注意,字段顺序不必和数据库表的字段顺序一致,但值的顺序必须和字段顺序一致。\n2. update\n1 2 3 4 5 6 -- 语法: -- update \u0026lt;表名\u0026gt; set 字段1=值1, 字段2=值2, ... where ...; -- 如 update students set name=\u0026#39;大牛\u0026#39;, score=score+10 -- 可以一次性更新多条记录,且可以使用表达式 where id=1; 3. delete\n使用 delete ,我们就可以一次删除表中的一条或多条记录。\n1 2 3 4 -- 语法: -- delete from \u0026lt;表名\u0026gt; where ...; -- 如 delete from students where id=1; mysql 安装完 mysql 后,除了 mysql server,即真正的 mysql 服务器外,还附赠一个 mysql client 程序。mysql client 是一个命令行客户端,可以通过 mysql client 登录 mysql,然后,输入 sql 语句并执行。\nmysql client 的可执行程序是 mysql ,mysql server 的可执行程序是 mysqld (后台运行)。\n![[assets/pasted image 20230526091940.png]]\n在 mysql client 中输入的 sql 语句通过 tcp 连接发送到 mysql server。默认端口号是 3306,即如果发送到本机 mysql server,地址就是 127.0.0.1:3306 。\n也可以只安装 mysql client,然后连接到远程 mysql server。\n1 2 # mysql -h \u0026lt;mysql-server-ip\u0026gt; -u root -p mysql -h 10.0.1.99 -u root -p 除了命令行客户端外,也有 gui 客户端可供使用,如 mysql workbench、 navicat 等。\n1. 数据库\n在一个运行的 mysql 服务器上,实际上可以创建多个数据库(database)。相关操作如下:\n操作 说明 show databases; 列出所有数据库 create database test; 创建一个新数据库 drop database test; 删除一个数据库 use test; 切换到数据库 test ——————————- ———————— 表 show tables; 列出当前数据库的所有表 desc students; 查看一个表的结构 show create table students; 查看创建表的 sql 语句 drop table students; 删除表 修改表就比较复杂。\n1 2 3 4 5 6 -- 给 students 表新增一列 birth alert table students add column birth varchar(10) not null; -- 修改 birth 列名为 birthday,类型改为 varchar(20) alert table students change column birth birthday varchar(20) not null; -- 删除列 alert table students drop column birthday; todo 2. 实用的 sql 语句\n……\n事务 这种把多条语句作为一个整体进行操作的功能,被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败,那么效果就和没有执行这些 sql 一样,不会对数据库数据有任何改动。\n对于单条 sql 语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。\n要手动把多条 sql 语句作为一个事务执行,使用 begin 开启一个事务,使用 commit 提交一个事务,这种事务被称为显式事务。如:\n1 2 3 4 5 begin; update accounts set balance = balance - 100 where id = 1; update accounts set balance = balance + 100 where id = 2; commit; -- 试图把事务内的所有sql所做的修改永久保存 -- rollback; -- 可以主动用 rollback 回滚事务 数据库事务具有 acid 共 4 个特性:\natomic 原子性(要么全部执行,要么全部不执行)、 consistent 一致性(事务完成后,所有数据的状态都是一致的)、 isolation 隔离性(多个事务并发执行,每个事务作出的修改必须与其他事务隔离)、 duration 持久性(即事务完成后,对数据库的修改被持久化存储)。 对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。sql 标准定义了 4 种隔离级别:\nisolation level 脏读(dirty read) 不可重复读(non repeatable read) 幻读(phantom read) read uncommitted yes yes yes read committed - yes yes repeatable read - - yes serializable - - - jdbc 2\njdbc 是什么 jdbc(java database connectivity),是 java 程序访问数据库的标准接口。\n使用 java 程序访问数据库时,java 代码并不是直接通过 tcp 连接去访问数据库,而是通过 jdbc 接口(java 标准库自带)来访问,而 jdbc 接口则通过 jdbc 驱动(数据库厂商提供,里面是接口的具体实现类)来实现真正对数据库的访问。如下:\n![[assets/pasted image 20230526092033.png|275]]\n实际上,一个 mysql 的驱动就是一个 jar 包,它本身也是纯 java 编写的。我们自己编写的代码只需要引用 java 标准库提供的 java.sql 包下面的相关接口,由此再间接地通过 mysql 驱动的 jar 包通过网络访问 mysql 服务器,所有复杂的网络通讯都被封装到 jdbc 驱动中,因此,java 程序本身只需要引入一个 mysql 驱动的 jar 包就可以正常访问 mysql 服务器,如下:\n![[assets/pasted image 20230526092049.png|400]]\n*注意:java 程序编译期仅依赖 java.sql 包,不依赖具体数据库的 jar 包,可随时替换底层数据库,访问数据库管理员的 java 代码基本不变。\n前面已经说过,所谓的 jdbc 驱动,其实就是一个第三方的 jar 包,我们可能通过添加一个 maven 依赖引入它:\n1 2 3 4 5 6 \u0026lt;dependency\u0026gt; \u0026lt;groupid\u0026gt;mysql\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;mysql-connector-java\u0026lt;/artifactid\u0026gt; \u0026lt;version\u0026gt;5.1.47\u0026lt;/version\u0026gt; \u0026lt;!-- 与安装的 mysql 版本对应 --\u0026gt; \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; \u0026lt;!-- 注意是 runtime --\u0026gt; \u0026lt;/dependency\u0026gt; 其中,添加依赖的 scope 是 runtime ,因为编译 java 程序并不需要 mysql 的这个 jar 包,只有在运行期才需要使用。如果改为 compile ,虽然也能正常编译,但是在 ide 里写程序的时候,会多出一大堆类似 com.mysql.jdbc.connection 这样的类,非常容易与 java 标准库的 jdbc 接口混淆,所以坚决不要设置为 compile 。\njdbc 连接 connection 代表一个 jdbc 连接,它相当于 java 程序到数据库的连接(通常是 tcp 连接)。打开一个 connection 时,需要准备 url、用户名和口令,才能成功连接到数据库。\nurl 是由数据库厂商指定的格式,例如,mysql 的 url 是:\njdbc:mysql://\u0026lt;hostname\u0026gt;:\u0026lt;port\u0026gt;/\u0026lt;db\u0026gt;?key1=value1\u0026amp;key2=value2 假设数据库运行在本机 localhost ,端口使用标准的 3306 ,数据库名称是 learnjdbc ,那么 url 如下:\njdbc:mysql://localhost:3306/learnjdbc?usessl=false\u0026amp;characterencoding=utf8 其中,后面的两个参数表示不使用 ssl 加密,使用 utf-8 作为字符编码。\n要获取数据库连接,使用如下代码:\n1 2 3 4 5 6 7 8 9 // jdbc 连接的 url, 不同数据库有不同的格式: string jdbc_url = \u0026#34;jdbc:mysql://localhost:3306/test\u0026#34;; string jdbc_user = \u0026#34;root\u0026#34;; string jdbc_password = \u0026#34;password\u0026#34;; // 获取连接: connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password); // todo: 访问数据库... // 关闭连接: conn.close(); 核心代码是 drivermanager 提供的静态方法 getconnection() , drivermanager 会自动扫描 classpath,找到所有的 jdbc 驱动,然后根据我们传入的 url 自动挑选一个合适的驱动。\n因为 jdbc 连接是一种昂贵的资源,所以使用后要及时释放。使用 try (resource) 来自动释放 jdbc 连接是一个好方法:\n1 2 3 try (connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password)) { // ... } jdbc 查询 获取到 jdbc 连接后,下一步就可以查询数据库了。查询数据库分以下几步:\n通过 connection 提供的 createstatemetn() 方法创建一个 statement 对象,用于执行一个查询; 执行 statement 对象提供的 executequery(\u0026quot;select * from students\u0026quot;) 并传入 sql 语句,执行查询返回的结果集,使用 resultset 来引用这个结果集; 反复调用 resultset 的 next() 方法来读取每一行结果。如下: 1 2 3 4 5 6 7 8 9 10 11 12 try (connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password)) { try (statement stmt = conn.createstatement()) { try (resultset rs = stmt.executequery(\u0026#34;select id, grade, name, gender from students where gender=1\u0026#34;)) { while (rs.next()) { long id = rs.getlong(1); // 注意:索引从1开始 long grade = rs.getlong(2); string name = rs.getstring(3); int gender = rs.getint(4); } } } } 需要注意以下几点:\nstatement 和 resultset 都是需要关闭的资源,因此嵌套使用 try (resource) 确保及时关闭; rs.next() 用于判断是否有下一行记录,如果有,将自动把当前行移动到下一行(一开始获得 resultset 时当前行不是第一行); resultset 获取列时,索引从 1 开始而不是 0 ;(思考一下,这是为什么?😏) 必须根据 select 的列对应的位置来调用 getlong(1),getstring(2) 这些方法,否则对应位置的数据类型不对,将报错。 关于 sql 注入攻击\n使用 statement 拼字符串非常容易引发 sql 注入的问题,因为 sql 参数往往是从方法参数传入的。\n如何避免 sql 注入攻击呢?一个办法是对所有字符串进行转义(很麻烦,推荐),另一个办法就是使用 preparedstatement (比 statement 更快更安全)。\n使用 preparedstatement 可以完全避免 sql 注入的问题,因为 preparedstatement 始终使用 ? 作为占位符,并且把数据连同 sql 本身传给数据库,这样可以保证每次传给数据库的 sql 语句是相同的,只是占位符的数据不同,还能高效利用数据库本身对查询的缓存。\n如此,上面使用 statement 的代码改为使用 preparedstatement ,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 try (connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password)) { try (preparedstatement ps = conn.preparestatement(\u0026#34;select id, grade, name, gender from students where gender=? and grade=?\u0026#34;)) { // 必须首先调用setobject() 设置每个占位符 ? 的值 ps.setobject(1, \u0026#34;m\u0026#34;); // 注意:索引从1开始 ps.setobject(2, 3); try (resultset rs = ps.executequery()) { while (rs.next()) { long id = rs.getlong(\u0026#34;id\u0026#34;); long grade = rs.getlong(\u0026#34;grade\u0026#34;); string name = rs.getstring(\u0026#34;name\u0026#34;); string gender = rs.getstring(\u0026#34;gender\u0026#34;); } } } } :: 在 resultset 获取列时,索引从 1 开始而不是 0 ,在 preparestatement 的设置占位符时索引也是从 1 开始。\n注意到 jdbc 查询的返回值总是 resultset ,即使我们写这样的聚合查询 select sum(score) from ... ,也需要按结果集读取:\n1 2 3 4 resultset rs = ... if (rs.next()) { double sum = rs.getdouble(1); } jdbc 更新 数据库操作总结起来就四个字:增查改删,crud(create,retrieve,update 和 delete)。\n查询,就是使用 preparedstatement 进行各种 select ,然后处理结果集。\n插入\n插入操作是 insert ,即插入一条新记录。通过 jdbc 进行插入,本质上了也是用 preparedstatement 执行一条 sql 语句,不过最后执行的不是 exectequery() ,而是 execteupdate() 。示例代码如下:\n1 2 3 4 5 6 7 8 9 10 try (connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password)) { try (preparedstatement ps = conn.preparestatement(\u0026#34;insert into students (id, grade, name, gendar) values (?,?,?,?)\u0026#34;)) { ps.setobject(1, 999); // 注意:索引从1开始 ps.setobject(2, 1); // grade ps.setobject(3, \u0026#34;bob\u0026#34;); // name ps.setobject(4, \u0026#34;m\u0026#34;); // gender int n = ps.executeupdate(); // 1 返回 int,表示插入的记录数量 } } 插入并获取主键\n如果数据库的表设置了自增主键,那么在执行 insert 语句时,并不需要指定主键,数据库会自动分配主键。\n对于使用自增主键的程序,有个额外的步骤,就是如何获取插入后的自增主键的值?要获取自增主键,不能先插入,再查询。因为两条 sql 执行期间可能有别的程序也插入了同一个表。获取自增主键的正确写法是在创建 preparedstatement 的时候,指定一个 return_generated_keys 标志位,表示 jdbc 驱动必须返回插入的自增主键。示例代码如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try (connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password)) { try (preparestatement ps = conn.preparestatement( \u0026#34;insert into students (grade, name, gender) values (?,?,?)\u0026#34;, statement.return_generated_keys)) { ps.setobject(1, 1); // grade ps.setobject(2, \u0026#34;bob\u0026#34;); // name ps.setobject(3, \u0026#34;m\u0026#34;); // gender int n = ps.executeupdate(); // 1 try (resultset rs = ps.getgeneratedkeys()) { if (rs.next()) { long id = rs.getlong(1); // 注意:索引从1开始 } } } } 观察上述代码,有两点注意事项:\n调用 preparestatement() 时,第二个参数必须传入常量 statement.return_generated_keys ,否则 jdbc 驱动不会返回自增主键; 执行 execteupdate() 方法后,必须调用 getgeneratedkeys() 获取一个 resultset 对象,这个对象包含了数据库自动生成的主键的值,读取该对象的每一行来获取自增主键的值。注意,如果一次插多条记录,那么这个 resultset 对象就会有多行返回值。如果插入时有多列自增,那么 resultset 对象的每一行都会对应多个自增值(自增列不一定必须是主键)。 更新\n更新操作是 update 语句,它可以一次更新若干列的记录。更新操作和插入操作在 jdbc 代码的层面上实际上没有区别,除了 sql 语句不同:\n1 2 3 4 5 6 7 try (connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password)) { try (preparedstatement ps = conn.preparestatement(\u0026#34;update students set name=? where id=?\u0026#34;)) { ps.setobject(1, \u0026#34;bob\u0026#34;); // 注意:索引从1开始 ps.setobject(2, 999); int n = ps.executeupdate(); // 返回更新的行数 } } *注意, execteupdate() 返回数据库实际更新的行数。返回结果可能是正数,也可能是 0 (表示没有任何记录更新)。\n删除\n删除操作是 delete 语句,它可以一次删除若干列。和更新一样,除了 sql 语句不同外,jdbc 代码都是相同的:\n1 2 3 4 5 6 try (connection conn = drivermanager.getconnection(jdbc_url, jdbc_user, jdbc_password)) { try (preparedstatement ps = conn.preparestatement(\u0026#34;delete from students where id=?\u0026#34;)) { ps.setobject(1, 999); // 注意:索引从1开始 int n = ps.executeupdate(); // 删除的行数 } } jdbc 事务 要在 jdbc 中执行事务,本质上就是如何把多条 sql 包裹在一个数据库事务中执行。如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 connection conn = openconnection(); try { // 关闭自动提交 conn.setautocommit(false); // 执行多条 sql 语句 insert(); update(); delete(); // 提交事务 conn.commit(); } catch (sqlexception e) { // 回滚事务 conn.rollback(); } finally { conn.setautocommit(true); conn.close(); } 实际上,默认情况下,我们获取到 connection 连接后,总是处于“自动提交”模式(一条 sql 语句其实就是一个“隐式事务”)。只要关闭了 connection 的 autocommit ,那么就可以在一个事务中执行多条语句,事务以 commit() 方法结束。\n如果要设定事务的隔离级别,可以使用如下代码:\n1 2 // 设定隔离级别为read committed: conn.settransactionisolation(connection.transaction_read_committed); 如果没有调用上述方法,那么会使用数据库的默认隔离级别。mysql 的默认隔离级别是repeatable read。\njdbc batch 通过一个循环来执行每个 preparedstatement 虽然可靠,但是性能很低。sql 数据库对 sql 语句相同但参数不同的若干语句可以作为 batch 执行(批量执行),这种操作有特别优化,速度远远快于循环执行每个 sql。示例代码如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 try (preparedstatement ps = conn.preparedstatement(\u0026#34;insert into students (name, gender, grade, score) values (?,?,?,?)\u0026#34;)) { // 对同一个 preparedstatement 反复设置参数并调用 addbatch(): for (student s : students) { ps.setstring(1, s.name); ps.setboolean(2, s.gender); ps.setint(3, s.grade); ps.setint(4.s.score); ps.addbatch(); // 添加到 batch } // 执行 batch int[] ns = ps.executebatch(); for(int n : ns) { system.out.println(n + \u0026#34; insert.\u0026#34;); // batch 中每个 sql 执行的结果数量 } } 执行 batch 和执行一个 sql 不同点在于,需要对同一个 preparedstatement 反复设置参数并调用 addbatch() ,这样就相当于给一个 sql 加上了多组参数,相当于变成了“多行”sql。\n第二个不同点是调用的不是 executeupdate() ,而是 executebatch() ,因为我们设置了多组参数,相应地,返回结果也是多个 int 值,因此返回类型是 int[] ,循环 int[] 数组即可获取每组参数执行后影响的结果数量。\njdbc 连接池 在执行 jdbc 的增删改查的操作时,如果每一次操作都来一次“打开连接,操作,关闭连接”,那么创建和销毁 jdbc 连接的开销就太大了。\n为了避免频繁地创建和销毁 jdbc 连接,我们可能通过连接池(connection pool)复用已经创建好的连接。jdbc 连接池有一个标准的接口 javax.sql.datasource 。要使用 jdbc 连接池,我们必须选择一个 jdbc 连接池的实现。常用的 jdbc 连接池有:\nhikaricp(使用最广泛) c3p0 bonecp druid(阿里的,国内使用最多) 此处,我们以 hikaricp 为例,要使用 jdbc 连接池,先添加 hikaricp 的依赖:\n1 2 3 4 5 \u0026lt;dependency\u0026gt; \u0026lt;groupid\u0026gt;com.zaxxer\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;hikaricp\u0026lt;/artifactid\u0026gt; \u0026lt;version\u0026gt;2.7.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 紧接着,创建一个 datasource 实例,这个实例就是连接池:\n1 2 3 4 5 6 7 8 9 10 hikariconfig config = new hikariconfig(); config.setjdbcurl(\u0026#34;jdbc:mysql://localhost:3306/test\u0026#34;); config.setusername(\u0026#34;root\u0026#34;); config.setpassword(\u0026#34;password\u0026#34;); config.adddatasourceproperty(\u0026#34;connectiontimeout\u0026#34;, \u0026#34;1000\u0026#34;); // 连接超时:1秒 config.adddatasourceproperty(\u0026#34;idletimeout\u0026#34;, \u0026#34;60000\u0026#34;); // 空闲超时:60秒 config.adddatasourceproperty(\u0026#34;maximumpoolsize\u0026#34;, \u0026#34;10\u0026#34;); // 最大连接数:10 datasource ds = new hikaridatasource(config); 有了连接池以后,我们通过 ds.getconnection() 来代替 drivermanager.getconnection() 获取一个连接,如下:\n1 2 3 try (connection conn = ds.getconnection()) { // 在此处获取连接 // ... } // 在此处“关闭”连接(不是真正的关闭,只是释放到连接池中,以便下次获取连接时直接返回) 连接池内部维护了若干个 connection 实例,如果调用 ds.getconnection() ,就选择一个空闲连接,并标记它为“正在使用”然后返回,如果对 connection 调用 close() ,那么就把连接再次标记为“空闲”从而等待下次调用。这样一来,我们就通过连接池维护了少量连接,但可以频繁地执行大量的 sql 语句。\n通常连接池提供了大量的参数可以配置,例如,维护的最小、最大活动连接数,指定一个连接在空闲一段时间后自动关闭等,需要根据应用程序的负载合理地配置这些参数。此外,大多数连接池都提供了详细的实时状态以便进行监控。\n","date":"2023-05-26","permalink":"https://aituyaa.com/sql/","summary":"\u003cp\u003e🔔 摘录自 \u003ca href=\"https://www.liaoxuefeng.com/wiki/1177760294764384\"\u003e廖雪峰老师的 SQL 教程\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eSQL,结构化查询语言(Structured Query Language ),是一种数据库查询和程序设计语言,用于访问和操作数据库系统。SQL 语句既可以查询数据库中的数据,也可以插入、更新和删除数据库中的数据,还可以对数据库进行管理和维护操作。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: 数据库能听懂的语言 – SQL\u003c/p\u003e\n\u003c/blockquote\u003e","title":"sql"},]
[{"content":"当前博文以 org mode 格式编辑。\ni have a vim, i have a emacs.\neh~ vim-emacs!\ni have a code, i have a vim-emacs.\neh~ code-vim-emacs!\ncve!\n简介 在 [[键位映射那些事儿]] 中, blablabla…… 说了好多。之前,我一直在个人电脑上使用 vscode ,各种插件和键位设置都是可以自动同步的。然而,当切换到别人的电脑时,就又不得不面对原版的键位模式。还是那句话,不是不能接受,只是有点困扰,哎,处女座……\n于是,我开发了一款简单易用的 vscode 插件 – cve keymap ,本文做一些插件开发过程中遇到问题的记录。\n![[assets/pasted image 20230526112823.png]]\n基础 磨刀不误砍柴工,先补了一下基础知识和概念。\n一次偶然的机会,看到极客教程中的 vscode 模块 ,里面系统性的介绍了 vscode 的一些实现原理( 部分命令可能有些老旧,但无伤大雅)。大家如果感兴趣,可以点进去阅读一下。\n当然,你也可以直接阅读官方文档。\n安装 开发和编译 vscode 插件,需要做一些准备工作,确保当前环境已经安装了 node.js 和 git ,然后执行以下命令安装 yeoman 和 vscode extension generator 。\n1 2 3 4 npm install -g yo generator-code # 使用脚手架创建插件模板, myextension 是你的插件名称 yo code myextension 注意, node.js 的版本不能太低,本机使用的是 16+ ,最低不要低于 14 ,不然有些依赖不兼容。如果,你的已有项目依赖低版本的 node.js (哎,历史项目啦),那么你需要 nvm 。\n![[assets/pasted image 20230526112833.png]]\n我们选择 new keymap 模板,其他模板请自行了解 😅。\n:: 这个过程中,唯一可能遇到的问题,就是我们提到的 node.js 版本的问题。\n一系列操作之后,就生成了我们的插件目录。如下:\n.\r├── changelog.md\r├── .git\r├── .gitattributes\r├── .gitignore\r├── package.json\r├── readme.md\r├── vsc-extension-quickstart.md\r├── .vscode\r└── .vscodeignore\r2 directories, 7 files 使用 vscode 打开该目录,即可进行开发工作了。具体细节,请参考官方文档。\n开发 开发过程中,可以使用 f5 进入到调试预览模式,它会新打开一个 vscode 窗口(微软良心开发工具 🥰)。\n发布 在插件开发完成之后呢,就可以打包成 vsix 格式的插件包分享给其他小伙伴使用了。更多了解 publishing extensions。\n:: 如果,你是第一次制作插件,并准备发布它,恭喜你,坑才刚刚开始,你还有一系列准备工作要完成。\n打包、发布在配置完成之后,后续执行都是很简单的。\n首先,我们先准备一个 cli 工具 – vsce ,来管理我们的 vscode 插件。安装:\n1 npm install -g vsce 而后执行 vsce package 即可完成打包。but 你也大概率可能会遇到以下问题,来看看吧。\n# 问题 1\rerror:make sure to edit the readme.md file before you publish your extension. 直接删除目录中的 readme.md ,新建一个写些说明保存提交即可。 不要用原文件进行修改,我这边试了好多次都通不过,一直报上述错误。\n注意:同于安全考虑, vsce 不允许插件中包含用户自己添加的 svg 格式图片,且远程图片链接要使用 https urls 。\n1. 获取 pat\n因为 vscode 使用 azure devops 为插件市场提供服务,所以去申请一个账号吧,如果你还没有的话。\n:: 此处,建议使用你平时使用的微软账号。\nvsce 可以通过 personal access tokens 发布扩展,所以,如果我们想要更加方便的发布插件,就需要先创建一个 pat(personal access tokens ,私人访问令牌) 。如何创建呢?点击这里查看 。\n注意:给 pat 复制下来记录一下,关了页面之后 ,你就再也找不到了。\n2.创建 publisher\n# 问题 2\rerror:missing publisher name. learn more:https://code.visualstudio.com... publisher 自然就是发布者了,它是插件市场的一种身份认证,随后你需要在 package.json 中插入 \u0026quot;publisher\u0026quot;: \u0026quot;xxxx\u0026quot; 类似行,更多详情查看 package.json 。\n我们可以通过插件市场的 management page 来创建一个 publisher 。\n注意,这里需要使用申请 pat 时的那个微软账号(要一致)。\n创建完成之后 ,我们可以使用通过下述命令进行验证:\n1 vsce login \u0026lt;publisher name\u0026gt; 3. 发布\n通过验证后,后续只需要执行如下命令,就可以方便地发布你的插件到插件市场了。\n1 vsce publish 当然,我们也可以使用 vsce package 打包的 vsix 文件,手动上传到插件市场,但是如果,你后续大频率更新插件的话,就会很困扰了。\n最后 怎么说呢?初看可能很繁琐,但其实按照官方教程一步步进行操作,遇到问题后再搜索查询一下,整个过程还是很容易完成的。just go ! 🌟\n","date":"2023-05-26","permalink":"https://aituyaa.com/vscode-%E6%8F%92%E4%BB%B6-cve-keymap-%E7%9A%84%E5%BC%80%E5%8F%91%E8%AE%B0%E5%BD%95/","summary":"\u003cp\u003e\u003cstrong\u003e当前博文以 \u003ccode\u003eOrg Mode\u003c/code\u003e 格式编辑。\u003c/strong\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI have a vim, I have a emacs.\u003cbr\u003e\nEh~ vim-emacs!\u003cbr\u003e\nI have a code, I have a vim-emacs.\u003cbr\u003e\nEh~ code-vim-emacs!\u003cbr\u003e\nCVE!\u003c/p\u003e\n\u003c/blockquote\u003e","title":"vscode 插件 cve keymap 的开发记录"},]
[{"content":" 哎,这让人费解的强迫症……\n博客没写几篇,博客系统倒是折腾了不少,哎,有点本末倒置了,希望这次是最后一次啦(不可能的),单开一篇碎碎念一下前前后后的那些事儿。\n“史前” 最初使用的“日志”系统就是 qq 空间了 😅(我是没有用过人人网的),那个时候一个与众不同的空间装扮是不少人心心念的。陆陆续续用了好多年,就是个情绪收集器,“为赋新词”强说的愁。随着 qq 的废弃,一时冲动就清空了所有内容,不时想起,难免有些遗憾,我的青春啊(冲动是魔鬼~~)。\n后来新浪博客可谓是红红火火,也不知道现在还有几个人用,反正我是连账号都不记得了。\n第三方博客平台 csdn 用的很少,界面太 low 了,广告一大堆,不过这个平台的 seo 做的真的不错,随便搜点什么,它的词条都排在很前面。感觉不少人直接把它当成备忘录用了,君不见,多少博客内容就只一个链接。\n博客园相对前者来说就清爽了很多,而且在一定程度上支持 diy ,可以添加不同的 css 模式,申请之后还可以加入 js 脚本。里面的内容也不错,至少不会是一两句话就自成一篇。\n简书最初的时候是个小可爱,简约美观,编辑内容的时候提供了两种模式,富文本和 markdown 模式,色调也柔和。只不过它的 markdown 解析引擎有点差劲,表现力不那么好。\n……\n第三方博客平台对博客内容的审核有时让人很困扰,ai 不够智能。像是 wordpress 之类的,生态是丰富,但也是折腾,而且编辑内容的体验让人不敢恭维。\n静态博客 markdown 是一种轻量级的标记语言,语法简单,书写清爽,表现力也还可以,用的范围挺广。许多静态博客生成器都只支持 markdown 的内容解析,如 jekyll 和 hexo。hugo 也支持 markdown,但它同时还支持 org mode 。\norg mode 是 emacs 编辑器中的一个插件,它拥有更加强大的表现力和直观性,配合 emacs 简直是纯文本编辑的无双利器。但是,成就它的同样也会束缚它,只有在 emacs 中才能发挥出它最大的威力。我知道有不少人都是为了 org mode 才开始接触并学习了 emacs 。\njekyll 在国内的网络环境下不是很友好,gem 不加镜像是真的慢(pass),虽然也没少折腾,但最初的时候真的是让人心累。hexo 就很好了,它基于 nodejs ,生态丰富完善,除了速度不如 hugo (但我们真的需要那么快的编译速度吗?),其他方面都让人很满意。使用了 hexo 挺长一段时间,甚至还写了一个主题 hexo-theme-zero。\n鉴于对 emacs 的一往情深,当时的博客内容都是用 org mode 写的,配合自制的一个 emacs 插件,把写好的 org mode 转成 markdown 输出到 hexo 进行解析生成。很繁琐是吧,听着就繁琐。\n后来,在阅读 org mode 文档的时候,发现了 ox-publish 这个功能,突然间感觉新世界的大门打开了。当时的念头是,如果可以借助这个功能直接从 org mode 生成页面,不就不需要繁琐的格式转换了嘛,当然后来我了解到已经有不少人已经对这个功能的增强和封装。在实现自定义封装的过程中,我参考和借鉴了不少别人的封装,具体实现 init-site.el,在这里主要实现了几个常用的功能,如文件保存的时候自动编译当前文件,配合一个 simple-httpd 实现当前文件实时预览,其他如删除当前文件对应的 html 文件,同时删除当前文件及其对应的 html 文件等等。配合第三方的 live-server ,封装并写一了一个简单的 bat 命令脚本,双击快速启动服务器,并开启浏览器本地开发。好吧,当时还做了一个视频 闲聊一种使用 org mode 生成静态博客的方式,尬 🤪 。\n如此,使用了有一段时间,也算是满足了个人对于博客的几点要求,如图片尺寸自定义大小等。它同样也有让人不爽的地方,就是慢,比 hexo 还慢,可怜的 elisp 。人性总是不安分的,有时候只是需要一个理由。在 hugo 经过了一段时间迭代之后 ,我又再次迁移到了它,先通览了一遍官方文档,看得我“飘飘然”。在经过了一周多的折腾之后,总算是有了个阶段性的稳定(主要自己给自己整得很疲惫,呵,处女座),hugo-theme-virgo 终于完工了,当然还制作了一个很随意的视频 virgo。\n这次应该能持续比较长的时间,大部分刚需功能在 hugo template 的帮助下都得以很快实现,最初的念头是好的,想着写一个朴素的主题就好。但你知道,这东西一旦开始,就停不来了,你会不断地想要重构它,增加一些功能。总之,就是事前“很会”,事后“很累”,自己折腾自己。目前终归是告一段落了,本地搜索,文章归档,同时具备朴素和炫酷、亮暗切换两种模式。hugo 原生支持 org mode,这非常好,我对它做了一些小小的增强,很爽。迫使我做出迁移的原因在于 windows 下的 emacs 问题多多,对中文字符的渲染尤其让人不爽。当下,对 vscode 进行了适当程度的“调教”,无论是书写 org mode 还是书写 markdown ,都到了差强人意的地步。\n:: 呼,都不知道这些碎碎念有什么用,碎碎念能有什么用呢?\n好了,牢骚也发的差不多了,以后要减少无用的折腾,把时间和精力用在更有益的地方 ,比如深入学习、写博客等等。\n结语 事实上,这个章节是后来( 2023-04-17 16:00 )添加,使用 typora 进行编辑的。什么?又换编辑器了?是,也不是 😂。为什么呢?看了 [[一款 typora 主题]] 你就明白了。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E4%B8%80%E5%9C%BA%E7%96%B2%E6%83%AB%E7%9A%84%E4%B8%BB%E9%A2%98%E5%88%B6%E4%BD%9C%E4%B9%8B%E6%97%85/","summary":"\u003cblockquote\u003e\n\u003cp\u003e哎,这让人费解的强迫症……\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e博客没写几篇,博客系统倒是折腾了不少,哎,有点本末倒置了,希望这次是最后一次啦(不可能的),单开一篇碎碎念一下前前后后的那些事儿。\u003c/p\u003e","title":"一场疲惫的主题制作之旅"},]
[{"content":"为什么会制作这款 typora 的主题呢?\n📙 virgo , 仓库地址 loveminimal/typora-theme-virgo: a typora theme for relative hugo.\n更新日志 - 2023-04-17 20:48 新增 virgo dark 暗色主题样式 快速开始 现在,它已经上传到了 typora 官方的主题仓库 - virgo ,但是我首次提交 pull request 的时候写错了 homepage 和 download 的链接…… 😅\n我已经提交了新 pull request ,后续官方合并后就没有问题了。幸运的是,它并影响你使用这个主题,毕竟你正在浏览器这个页面。\n官方合并的速度还挺快的,目前 virgo 中的链接已经是正确的了。\n在 偏好设置 / 外观 中点击 打开主题文件夹 ,如下:\n![[assets/pasted image 20230526112217.png]]\n该文件夹下存放着 typora 的主题文件,在文件夹下,执行如下命令:\ngit clone https://github.com/loveminimal/typora-theme-virgo.git 然后,将文件夹 typora-theme-virgo 中的 virgo 文件夹和 virgo.css 、 virgo-dark.css 拷贝一份到当前目录下,重启 typora ,即可选择主题 virgo 、 virgo dark 。\n有点不明白,作者不什么不直接遍历 themes 文件下的所有第一级文件夹,然后指定其中的默认加载样式文件,如此,也方便使用 git 管理啊,省了手动拷贝的这一步。 😶\n为什么需要 之前是在 vscode 中编辑博客文件的,使用体验也很好,基本上没有什么不足之处。那为什么要使用 typora 呢?\n一是, typora 的预览效果十分接近于网页渲染,因为当前的主题就是从站点的主题适配而来的,除了部分借助于 javascript 实现的动态交互,其渲染结果有着 90%+ 的相似性。‘所见即所得’,很不错的体验。\n二是,vscode 中对表格的处理没有 typora 优雅,尤其是中英文混输的时候,光是对齐就让人‘崩溃’,尽管有一些所谓有对齐表格的办法,也是不那么让人满意。\n这是 vscode 中对于 table 的编辑状态,很乱很乱,如下:\n![[assets/pasted image 20230526112225.png]]\n\u0026gt; 这是用 vs 编辑器中的 table\n我们来看看,当前主题中对于 table 的编辑和渲染,如下:\n![[assets/pasted image 20230526112230.png]]\n\u0026gt; typora 下当前主题 virgo 的渲染和编辑\n对比很‘惨烈’! 😈\n最后, typora 对于图片的处理也很优秀,不仅实现了直接从剪切板复制粘贴,最近的更新中还优化了对图片相关操作(改、删)的状态同步。\n其他 typora 内置了几个主题,各有特色,在 themes gallery — typora 中也有不少第三方的精美主题可以选择。总之呢,基本上可以满足各类用户的喜好。\n当前主题目前已经提交 pr 到该主题仓库,具体生效日期不定,其实就算合并成功,你还是得按照 快速开始 中描述的步骤那样使其生效。\n结语 最后,使用了一段时间了,对 typora 做一些简评吧。总体来说,纯文本编辑的话,还是不错的。就个人而言,希望在后续更新中可以改进或开放以下两个方面。\n开放设置项,允许用户快速打开文件的时候,可以选择在当前窗口打开,而不是新开一个窗口。\n可以增强快捷键设置,当前的开放支持太弱了,希望可以增加更多的按键接口,尤其的光标的上下左右移动的映射。编辑的时候,习惯使用 alt + h/j/k/l 来移动了,使用 箭头 的话就不得不移动一下右手……\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E4%B8%80%E6%AC%BE-typora-%E4%B8%BB%E9%A2%98/","summary":"\u003cp\u003e为什么会制作这款 Typora 的主题呢?\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://theme.typora.io/theme/Virgo/\"\u003e📙 Virgo\u003c/a\u003e , 仓库地址 \u003ca href=\"https://github.com/loveminimal/typora-theme-virgo\"\u003eloveminimal/typora-theme-virgo: A typora theme for relative hugo.\u003c/a\u003e\u003c/p\u003e","title":"一款 typora 主题"},]
[{"content":" 🚨 后又陆陆续续加了些辅助功能,可以点击下载最新完整的 search.js 源码(另存后使用编辑器打开就不中文乱码了)。\n更新日志 - 2024-03-20 11:42 修复多 sections 时站点文章内容索引不完全的问题\r- 2022-07-13 11:02 修改了站点内容结构解析方式,以解决随着文章数量增长导致搜索性能下降的问题 简介 近来稍闲,实现了一个 hugo 本地搜索的小功能,分享一下 🍧 。\n:: 这个功能写了两遍,第一次不知道怎么着就在一个临时分支(切换回主分支会自动消失的那种 😠 )中开发了,然后就没什么然后了…… 感觉是 ide 的锅,对,就是它的,不是也要是!\n让我们先来看一下,它可以做什么吧:\n内容实时搜索; 搜索内容摘要显示; 搜索词高亮。 都是一些搜索时常用的功能。稍后,我们来看一下上述功能的一些细节,以及开发过程中的一些糗事 😄 。你也可以,先在这里体验一下它的大概使用效果 search 。\n使用 如何实时获取站点的所有内容呢?这里有两个方面,一就是获取站点的所有页面内容,二是实时获取。\n搜索模板页 一个大概的思路,就是创建一个模板页,如 _search.html 文件(详见 _search.html),利用 hugo 模板本身的变量(如 .site)来获取站点所有的页面内容。\n\u0026gt; 废弃 {{ range where .site.pages \u0026quot;kind\u0026quot; \u0026quot;section\u0026quot; }}\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 \u0026lt;div class=\u0026#34;container-search\u0026#34;\u0026gt; \u0026lt;div id=\u0026#34;data\u0026#34; style=\u0026#34;display: none;\u0026#34;\u0026gt; \u0026lt;!-- 遍历所有的站点页面 --\u0026gt; {{ range where .site.pages \u0026#34;kind\u0026#34; \u0026#34;page\u0026#34; }} {{ if (and (ne .section \u0026#34;snippets\u0026#34;))}} [{{- dict \u0026#34;title\u0026#34; (lower .title) \u0026#34;permalink\u0026#34; .permalink \u0026#34;date\u0026#34; (.date | time.format \u0026#34;2006-01-02\u0026#34;) \u0026#34;summary\u0026#34; .summary \u0026#34;content\u0026#34; (lower .plain) | jsonify -}},] {{ end }} {{ end }} \u0026lt;/div\u0026gt; \u0026lt;!-- 搜索框 --\u0026gt; \u0026lt;div id=\u0026#34;search\u0026#34;\u0026gt; \u0026lt;!-- 🔎 --\u0026gt; \u0026lt;span class=\u0026#34;sc-icon\u0026#34;\u0026gt;\u0026lt;img src=\u0026#34;/imgs/icons/search.svg\u0026#34; width=\u0026#34;48\u0026#34;\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;span id=\u0026#34;sc-clear\u0026#34; onclick=\u0026#34;clearinputval()\u0026#34;\u0026gt;✖\u0026lt;/span\u0026gt; \u0026lt;input id=\u0026#34;sc-input\u0026#34; oninput=\u0026#34;search()\u0026#34; type=\u0026#34;text\u0026#34; placeholder=\u0026#34;here search search...\u0026#34; /\u0026gt; \u0026lt;div id=\u0026#34;sc-res\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;!-- 加载所需搜索脚本 --\u0026gt; \u0026lt;script src=\u0026#34;/js/search.js\u0026#34; defer\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; 当然,你可以进行按需进行一些修改,过滤掉一些,你不想被搜索到的页面。\n下面,我们来看一下核心的 js 搜索脚本 search.js (这里我们放在了 static/js/search.js)。其中的注释,是开发过程中帮助记忆和理清思路和一些碎碎念,不要在意。😅\n解析站点页面内容 1 2 3 4 let data = document.queryselector(\u0026#39;#data\u0026#39;).innertext.trim(); data = data.slice(0, data.length - 2) + \u0026#39;]\u0026#39;; data = data.replace(/\\]\\s+\\[/g, \u0026#39;\u0026#39;); let map = json.parse(data); 我们使用 hugo 模板提供的相关功能,组织站点内容映射,以文本形式放在元素 #data 中,后反序列化以得到当前站点所有页面内容的一个集合 map 。\n如何搜索 我们的核心就是搜索函数 search() 。在 map 的生成过程中,我们对信息串的 content 做了一些处理,如将所有字符转化为小写。在 search() 中,我们也对搜索词 scval 进行同样的处理,以实现不区分大小写的内容搜索。\n在这之前,我们定义了另一个辅助函数,用来返回搜索词 scval 在对应页面中出现的所有索引位置 _arrindex 。\n1 2 3 4 5 6 7 8 9 10 11 12 13 function scanstr(content, str) { // content 页面内容信息串 let index = content.indexof(str); // str 出现的位置 let num = 0; // str 出现的次数 let arrindex = []; // str 出现的位置集合 while(index !== -1) { arrindex.push(index); num += 1; index = content.indexof(str, index + 1); // 从 str 出现的位置下一位置继续 } return arrindex; } 有它 scanstr 我们就可以方便的知道搜索词都出现在了哪里,以方便后续的内容摘要截取及高亮。\n内容摘要截取\n通过 scanstr ,我们得到了搜索词在页面内容出现的所有位置,我们默认截取每个位置前后 100 个字符长度(后续我称之后 截取半径)的内容进行罗列展示(可以自定义长度)。这里,我们做了一些小小的优化操作,当后续搜索词的索引位置与当前搜索词的索引位置之差仍小于截取半径的时候,将不再对该位置前后内容进行截取(因为它已经包含在了之前的截取内容中),以避免大量重复性内容的展示。\n具体逻辑,还是直接看代码吧,其实不需要了解,因为它并没有什么太大的通用性,都是对字符串的蹂躏和被蹂躏。😿\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 let scinput = document.queryselector(\u0026#39;#sc-input\u0026#39;); let scres = document.queryselector(\u0026#39;#sc-res\u0026#39;) let scval = \u0026#39;\u0026#39;; scinput.focus(); // 自动聚集搜索框 function search() { let post = \u0026#39;\u0026#39;; scval = scinput.value.trim().tolowercase(); map.foreach(item =\u0026gt; { if (!scval) return; if (item.content.indexof(scval) \u0026gt; -1) { let _arrindex = scanstr(item.content, scval); let strres = \u0026#39;\u0026#39;; let _radius = 100; // 搜索字符前后截取的长度 let _strstyle0 = \u0026#39;\u0026lt;span style=\u0026#34;background: yellow;\u0026#34;\u0026gt;\u0026#39; let _strstyle1 = \u0026#39;\u0026lt;/span\u0026gt;\u0026#39; let _strseparator = \u0026#39;\u0026lt;hr\u0026gt;\u0026#39; // 统计与首个与其前邻的索引(不妨称为基准索引)差值小于截取半径的索引位小于截取半径的索引的个数 // 如果差值小于半径,则表示当前索引内容已包括在概要范围内,则不重复截取,且 // 下次比较的索引应继续与基准索引比较,直到大于截取半径, _count重新置 为 0; let _count = 0; for (let i = 0, len = _arrindex.length; i \u0026lt; len; i++) { let _idxitem = _arrindex[i]; let _relidx = i; // 如果相邻搜索词出现的距离小于截取半径,那么忽略后一个出现位置的内容截取(因为已经包含在内了) if (_relidx \u0026gt; 0 \u0026amp;\u0026amp; (_arrindex[_relidx] - _arrindex[_relidx - 1 - _count] \u0026lt; _radius)) { _count += 1; continue; } _count = 0; // 概要显示 // _startidx, _endidx 会在超限时自动归限(默认,无需处理) strres += _strseparator; let _startidx = _idxitem - _radius + (_relidx + 1) * _strseparator.length; let _endidx = _idxitem + _radius + (_relidx + 1) * _strseparator.length; strres += item.content.substring(_startidx, _endidx); } // 进一步对搜索摘要进行处理,高亮搜索词 let _arrstrres = scanstr(strres, scval); // console.log(_arrstrres) for (let i = 0, len = _arrstrres.length; i \u0026lt; len; i++) { let _idxitem = _arrstrres[i]; let _realidx = i; strres = strres.slice(0, (_idxitem + _realidx * (_strstyle0.length + _strstyle1.length))) + // 当前索引位置之前的部分 _strstyle0 + scval + _strstyle1 + strres.slice(_idxitem + scval.length + _realidx * (_strstyle0.length + _strstyle1.length)); // 之后的部分 } post += ` \u0026lt;div class=\u0026#34;item\u0026#34; \u0026gt; \u0026lt;a href=\u0026#34;${item.permalink}\u0026#34;\u0026gt; \u0026lt;span\u0026gt;📄\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;date\u0026#34;\u0026gt;${item.date}\u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;${item.title}\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;div\u0026gt;${strres}\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; ` } }) let res = `\u0026lt;div class=\u0026#34;list\u0026#34;\u0026gt;${post}\u0026lt;/div\u0026gt;`; scres.innerhtml = res; 高亮显示\n在遍历获取了所有的搜索摘要 strres 后,我们需要对其进行进一步的处理,以实现搜索词高亮显示,如下:\n![[assets/pasted image 20230526104035.png]]\n同样,这也是和字符串长度之间的征战,没什么太大意思。\n最后 只需要把 search.js 放在 static/js/ 目录下,或者其他你喜欢的路径,但要保证 _search.html 可以正常引用。再使用 hugo 创建一个对应的 search.md 页面,用来启用 _search.html 模板即可。比如我把 _search.html 模板放在了 single.html 模板中,并且设置只有 /search 路径才加载这部分内容。\n{{ $issearch := eq .title \u0026#34;search\u0026#34;}}\r{{ if $issearch }}\r{{- partial \u0026#34;partials/_search.html\u0026#34; . -}}\r{{ end }} 上述代码块中的代码,是完整可用的,复制粘贴即可。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E4%B8%80%E7%A7%8D%E7%AE%80%E5%8D%95%E7%9A%84-hugo-%E6%9C%AC%E5%9C%B0%E6%90%9C%E7%B4%A2%E5%AE%9E%E7%8E%B0/","summary":"\u003cblockquote\u003e\n\u003cp\u003e🚨 后又陆陆续续加了些辅助功能,可以点击下载最新完整的 \u003ca href=\"https://github.com/loveminimal/hugo-theme-virgo/blob/1.x/static/js/search.js\"\u003esearch.js\u003c/a\u003e 源码(另存后使用编辑器打开就不中文乱码了)。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"一种简单的 hugo 本地搜索实现"},]
[{"content":"🔔 参考 中文技术文档的写作规范 ,及个人写作习惯。\n简介 本文旨在学习和思考如何形成良好统一的文档写作习惯,原则是做到简洁、易懂,便于检索。\n文档结构 目录/文件 必备/类型 说明 简介 introduction 是/目录 提供对产品和文档本身的总体的、扼要的说明 快速上手 getting started 否/文件 如何最快速地使用产品 入门篇 basics 是/目录 又称“使用篇”,提供初级的使用教程 环境准备 - prerequisite 是/文件 软件使用需要满足的前置条件 安装 - installation 否/文件 软件的安装方法 设置 - configuration 是/文件 软件的设置 进阶篇 advanced 否/目录 又称“开发篇”,提供中高级的开发教程 api reference 否/目录、文件 软件 api 的逐一介绍 faq 否/文件 常见问题解答 附录 appendix 否/目录 不属于教程本身,但对阅读教程有帮助的内容 名词解释 - glossary 否/文件 最佳实践 - recipes 否/文件 故障处理 - troubleshooting 否/文件 版本说明 - changelog 否/文件 反馈方式 - feedback 否/文件 \u0026gt; 文档结构\n两个真实范例,可参考 redux 手册 和 atom 手册 。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E4%B8%AD%E6%96%87%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3/","summary":"\u003cp\u003e🔔 参考 \u003ca href=\"https://www.ruanyifeng.com/blog/2016/10/document_style_guide.html\"\u003e中文技术文档的写作规范\u003c/a\u003e ,及个人写作习惯。\u003c/p\u003e","title":"中文技术文档"},]
[{"content":"📖 本文主要摘录自 问问学五笔。\n![[assets/201910301744wubi.jpg]]\n五笔是什么 我们主要讲 86 版的五笔,它的通用性最好。\n五笔的基本原理是什么呢?\n汉字都是由笔划或部首组成的。为了输入汉字,我们把汉字折成一些最常用的基本单位 – 字根,字根可以是汉字的偏旁部首,可以是部首的一部分,甚至是笔划。\n取出这引起字根后,把它们按一定的规律分类,再把这些字根依据科学原理分配在键盘上,作为输入汉字的基本单位。\n当要输入汉字时,我们就按照汉字的书写顺序依次按键盘上与字根对应的键,组成一个代码,系统根据输入字根组成的代码,在五笔输入的字库中检索出所要的字。\n:: 拆字 → 分配 → 组代 → 字库检索\n我们已经了解了五笔的基本原理,那么究竟应该如何学习五笔呢?\n三个方面,如下:\n知道键盘上的每个键位对应着哪些字根; 学习如何把汉字拆分成五笔字根; 输入字根对应的字母,必要时要键入识别码。 :: 分三步:把冰箱门打开,把大象装进去,把冰箱门关上。\n键盘分区和区位号 五笔基本字根有 130 种,加上一些基本字根的变型,共有 200 个左右,这些字根分配在键盘上的 25 个键(除 z 外)上,每个键位都对应着几个甚至几十个字根。\n我们知道,汉字有五种基本笔划:横(提)、竖(竖勾)、撇、捺(点)、折,所有的字根都是由这五种笔划组成的。\n按照每个字根的起笔笔划,把这些字根分为五个区,如下:\n![[assets/pasted image 20230526095515.png]]\n每个区有五个位,按一定的顺序编号,就叫区位号(都是从键盘中间开始,向外扩展进行编号)\n如何输入这五个基本笔划?\n笔划 一 丨 丿 丶 乙 代码 ggll hhll ttll yyll nnll 字根的输入 ➭ 更多详查 五笔字根分解图详解。\n键名字根 让我们再来看看下面这张图中标红的字体,每个区位选取了一个最常用的字根作为键的名字,该字根称为键名字根。\n![[assets/pasted image 20230526095521.png]]\n:: hmm\u0026hellip; 怎么说呢,“被”这个字我经常使用,但每次都要输入“puhc”四个键\u0026hellip;\n如何输入键名字根呢?对应的键按四下就行了,如:\n键名字根 王 土 大 … 代码 gggg ffff dddd xxxx 字根歌 31 t 禾竹一撇双人立,反文条头共三一 41 y 言文方广在四一,高头一捺谁人去 32 r 白手看头三二斤 42 u 立辛两点六门疒 33 e 月彡(衫)乃用家衣底(爱头豹脚舟字底) 43 i 水旁兴头小倒立 34 w 人和八,登祭头 44 o 火业头,四点米 35 q 金勺缺点无尾鱼,犬旁留叉儿一点夕,氏无七 45 p 之字军,摘礻(示)衤(衣)(之字军盖建道底) 11 g 王旁青头戋(兼)五一(“兼”与“戋”同音) 21 h 目具上止卜虎皮(“具上”指具字的上部) 12 f 土士二干十寸雨(可别忘了革字底) 22 j 日早两竖与虫依 13 d 大犬三羊古石厂(羊底龙头套上套下) 23 k 口与川,字根稀 14 s 木丁西 24 l 田甲方框四车力(“方框”即“口”) 15 a 工戈草头右框七(“右框”即“匚”) 25 m 山由贝,下框几 51 c 又巴马,丢矢矣,(“矣”去“矢”为“厶”) 54 n 已半巳满不出己,左框折尸心和羽 52 x 慈母无心弓和匕,幼无力(“幼无力”即“幺”) 55 b 子耳了也框向上。(“框向上”即“凵”) 53 v 女刀九臼山朝西。(“山朝西”即“彐”) 字根间的结构关系 拆字是学习五笔的一个重要环节,光背会了字根,有的汉字不知道拆成什么样的字根,也是无法输入的。\n我们是由浅入深地开始讲,先谈谈字根间的结构关系 – 单、散、连、交。\n单,就是指这个字根本身就是一个汉字,包括:\n五种基本笔划(一、丨、丿、丶、乙); 25 个键名字根; 字根中的汉字。 散,就是指构成汉字折字根不止一个,且汉字之间有一定的距离,比如“苗”、“汉”字等。\n连,是指一个字根与一个单笔划相连,比如“且”、“尺”字等。\n交,是指两个或多个字根交叉重叠构成的汉字,比如“本”、“申”字等。\n由两个以上字根组成的汉字,称为合体字。\n其中,对于四个或四个以上的字根的合体字,它的输入方法是按照书写顺序,取第一、二、三、末四个字根的编码。\n![[assets/pasted image 20230526095556.png|231]] ![[assets/pasted image 20230526095607.png|275]]\n成字字根 在字根总表中,除了键名字根外,本身就是汉字的字根,称为成字字根,比如“马、手、刀”等。这样的成字字根一共有 65 个,但这些字经常要输入,所以需要掌握其输入方法。\n怎么输入呢?\n成字字根的输入方法是:先打一下该字根所在的键,再打该字根的第一、第二及最末一个单笔画。\n![[assets/pasted image 20241223114927.png|300]]\n![[assets/pasted image 20241223114933.png|300]]\n即: 键名+首笔代码+次笔代码+末笔代码。\n汉字的拆分 在拆分汉字的时,先要注意按书写顺序来拆分汉字,然后对里面的一些复杂字根,按照它的自然界限进行拆分,对界线不明显的,就要按拆分原则进行拆分。\n那么,书写顺序、拆分原则,指的都是什么呢?\n在书写汉字时,讲究“先左后右,先上后下,先横后竖,先撇后捺,先内后外,先中间后两边,先进门后关门”等。这些都是语文的基本知识,就不多说了。在拆字时,同样要注意书写顺序。\n![[assets/pasted image 20230526095635.png|196]]\n拆分原则,归纳起来有四点:“取大优先,兼顾直观,能散不连,能连不交。”\n有些汉字,它们所含的字根相同,但字根之间的相对位置不同,比如“旭”和“旮”等。我们把汉字各部分间的位置关系类型叫做字型,在五笔中,把汉字分为三种字型:左右型、上下型、杂合型。\n![[assets/pasted image 20230526095649.png|200]]\n识别码 我们知道,一个合字体的取码规则是这个字的一、二、三、末字根,这只是针对四个字根以上的汉字。如果是这个字只有二个字根或三个字根构成,比如“叭”,这时怎么输入呢?\n我们试试,在五笔状态下键入“叭”的两个字根的编码,kw,我们发现出现在第一条的是“只”字,原来“叭”与“只”都是由字根“口”和“八”组成的,其编码也是 kw。\n由于编码少,信息量不足,会造成重码。\n如何消除重码呢?\n![[assets/pasted image 20230526095726.png|375]]\n汉字的笔画有 5 种,字型有 3 种,所以末笔字型交叉识别码共 15 种,也就是每个区位的前三位是作为识别码来用的。\n![[assets/pasted image 20230526095737.png]]\n末笔约定 我们在使用识别码输入汉字时,对汉字的末笔有一些约定,需要注意。\n首先,为了有足够多的区分能力,对“辶”、“廴”的字和全包围字,它们的“末笔”规定为被包围部分的末笔。为什么要这样约定呢?\n例如,如果“辶”为末笔的话,“边”、“连”的识别码都是 v,就无法区分了,所以才这样约定。\n![[assets/pasted image 20241223114135.png|325]]\n:: 哈,约定末笔是为了便于打识别码。\n其次,对“九、刀、七、力、匕”等字根,当它们参加“识别”时一律用“折笔”作为末笔。\n![[assets/pasted image 20241223114355.png|325]]\n:: 其实,除了 刀 这个特殊,其他几个字本来就是折笔为末。\n然后,“我”、“贱”、“成”等字的“末笔”,遵循“从上到下”的原则,末笔应该是“丿”。\n![[assets/pasted image 20241223114722.png|325]]\n![[assets/pasted image 20241223114735.png|275]]\n最后,还有带单独点的字,比如“义”,“太”,“勺”等,我们把点当作末笔,并且认为“丶”与附近的字根是“连”的关系,所以为杂合型,识别码为 43,也就是 i。\n![[assets/pasted image 20241223114830.png|300]]\n我们在学习五笔输入法的过程中,识别码的判断是一个难点,虽然只有很少的字需要加识别码,但为了提高录入速度,还是要掌握这部分内容的。\n一级简码 ![[assets/pasted image 20230526095750.png]]\n在五笔中,挑出了在汉语中使用频率最高的 25 个汉字,把它们分布在键盘的 25 个字母上,并称之为 一级简码 。\n输入一级简码的方法是: 按一下简码字所在的键,再按一下空格。\n二级简码 五笔的二简其实不用刻意去记,如果你真的想去记忆,日常多打一些 “五笔 - 单” 就可以了。打字这个东西就是手感,反正一个字最多也就是四码而已,不是吗?\n![[assets/pasted image 20240629182051.png]]\n\u0026gt; 86版五笔字型二级简码表(取码时先行后列)\n🤖 a b c d e f g h i j k l m n o p q r s t u v w x y g 开 屯 到 天 表 于 五 下 不 理 事 画 现 与 来 ★ 列 珠 末 玫 平 妻 珍 互 玉 f 载 地 支 城 圾 寺 二 直 示 进 吉 协 南 志 赤 过 无 垢 霜 才 增 雪 夫 ★ 坟 d 左 顾 友 大 胡 夺 三 丰 砂 百 右 历 面 成 灰 达 克 原 厅 帮 磁 肆 春 龙 太 s 械 李 权 枯 极 村 本 相 档 查 可 楞 机 杨 杰 棕 构 析 林 格 样 要 检 楷 术 a 式 节 芭 基 菜 革 七 牙 东 划 或 功 贡 世 ★ 芝 区 匠 苛 攻 燕 切 共 药 芳 h 虎 ╳ 皮 睚 肯 睦 睛 止 步 旧 占 卤 贞 卢 眯 瞎 餐 睥 盯 睡 瞳 眼 具 此 眩 j 虹 最 紧 晨 明 时 量 早 晃 昌 蝇 曙 遇 电 显 晕 晚 蝗 果 昨 暗 归 蛤 昆 景 k 呀 啊 吧 顺 吸 叶 呈 中 吵 虽 吕 另 员 叫 噗 喧 史 听 呆 呼 啼 哪 只 哟 嘛 l 轼 囝 轻 因 胃 轩 车 四 ★ 辊 加 男 轴 思 辚 边 罗 斩 困 力 较 轨 办 累 罚 m 曲 邮 凤 央 骨 财 同 由 峭 则 ★ 崭 册 岂 赕 迪 风 贩 朵 几 赠 ╳ 内 嶷 凡 t 长 季 么 知 秀 行 生 处 秒 得 各 务 向 秘 秋 管 称 物 条 笔 科 委 答 第 入 r 找 报 反 拓 扔 持 后 年 朱 提 扣 押 抽 所 搂 近 换 折 打 手 拉 扫 失 批 扩 e 肛 服 肥 须 朋 肝 且 胩 膛 胆 肿 肋 肌 甩 膦 爱 胸 遥 采 用 胶 妥 脸 脂 及 w 代 他 公 估 仍 会 全 个 偿 介 保 佃 仙 亿 伙 ★ 你 伯 休 作 们 分 从 化 信 q 氏 凶 色 然 角 针 钱 外 乐 旬 名 甸 负 包 炙 锭 多 铁 钉 儿 匀 争 欠 ★ 久 y 度 离 充 庆 衣 计 主 让 就 刘 训 为 高 记 变 这 义 诉 订 放 说 良 认 率 方 u 并 闻 冯 关 前 半 闰 站 冰 间 部 曾 商 决 普 帝 交 瓣 亲 产 立 妆 闪 北 六 i 江 池 汉 尖 肖 法 汪 小 水 浊 澡 渐 没 沁 淡 学 光 泊 洒 少 洋 当 兴 涨 注 o 煤 籽 烃 类 粗 灶 业 粘 炒 烛 炽 烟 灿 断 炎 迷 炮 煌 灯 烽 料 娄 粉 糨 米 p 宽 字 ★ 害 家 守 定 寂 宵 审 宫 军 宙 官 灾 之 宛 宾 宁 客 实 安 空 它 社 n 民 敢 怪 居 ★ 导 怀 收 悄 慢 避 惭 届 忆 屡 忱 懈 怕 ★ 必 习 恨 愉 尼 心 b 陈 子 取 承 阴 际 卫 耻 孙 阳 职 阵 出 也 耿 辽 隐 孤 阿 降 联 限 队 陛 防 v 姨 奶 婚 好 妈 姑 妨 姆 姐 妯 如 舅 寻 录 旭 杂 九 嫌 妇 媚 婚 奶 妈 姨 姐 c 驮 马 对 参 骠 骒 能 难 允 牟 驼 驻 骈 矣 双 戏 邓 劝 观 台 联 办 权 艰 好 x 经 比 纺 线 结 约 红 弛 级 纪 综 绍 维 纲 丝 紫 紧 累 绿 给 细 张 弱 纱 旨 备注:\n86 版五笔字型的二级简码共有 25×25-19=606 个。\n二级简码中没有的字有 7 个:cu 骈,eh 胩,eo 膦,ko 噗,la 轼,mo 赕,vh 叟;都是三级简码字(骈 cua;胩 ehh;膦 eoq;噗 kog;轼 laa;赕 moo;叟 vhc),但在输入两码后按空格键时自动出现的字,相当于二级简码了,准确地说:在 86 版五笔编码中有 599 个二级简码字。\n有 ╳ 的为无字二码域,有 3 个:hb、mv、co 。\n有 ★ 的的二码域可输入词组,有 16 个:gp 不定期;fx 超级大国;ao 工业区;li 团党委;mk 风吹草动;wp 倾家荡产;qx 煞费苦心;pc 客观存在;ne 避孕药;ns 尽可能;vr 忍气吞声;vw 群众观点;ch 能上能下;ci 邓小平;cx 又红又专;xs 纵横驰骋。\n同时为一级简码字的有 11 个:不,地,要,中,同,主,为,这,产,民,经。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E4%BA%94%E7%AC%94%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003e📖 本文主要摘录自 \u003ca href=\"http://www.wubizi.net/xuewubi/\"\u003e问问学五笔\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e![[assets/201910301744wubi.jpg]]\u003c/p\u003e","title":"五笔那些事儿"},]
[{"content":" “圣人不治已病,治未病;不治已乱,治未乱。”\n-- 《黄帝内经·素问·四气调神大论》\n生老病死,成住坏空,古往皆如是。生命的前几十年,身体是在上升期,经得起小风小浪小消耗。然岁月不饶人,专治各种不服 …… 😅\n我们要爱护自己的身体,守护好自己的健康。养生之道,任重道远,却是千里之行,始于足下。生活中的每一点有益的改变,都会让我们身体受用。现代医学多是西方那一套,不是说不好,心中却总是有一种其“治标不治本”的“偏见”。西医的“头痛医头,脚痛医脚”,着实有其局限,它把人体的大系统割裂式的看待,感觉还是太局部了。之前也没有看过什么医学著作,主要是感觉看了也看不懂,不知为何心血来潮,突然就想了解一二(也就只能学一点皮毛常识)。就想着从我国传统医学著作着手,毕竟几千年的实践检验了,至少不会给自己带坑里去,而且感觉上中医侧重养生,比较符合目前的状态。再不济,全当强化一下古文学习了,当成一种休闲消遣也不错。\n百科一下,传统医学四大经典著作:《黄帝内经》、《难经》、《伤寒杂病论》、《神农本草经》,让我们择其一二先一窥究竟。\n一二经典 ![[assets/pasted image 20230526093238.png|400]]\n\u0026gt; 黄帝内经\n《黄帝内经》分《灵枢》、《素问》两部分,是一本综合性的医书,在黄老道家理论上建立了中医学上的“阴阳五行学说”、“脉象学说”、“藏象学说”、“经络学说”、“病因学说”、“病机学说”、“病症”、“诊法”、“论治”及“养生学”、“运气学”等学说。 其基本素材来源于中国古人对生命现象的长期观察、大量的临床实践以及简单的解剖学知识。《黄帝内经》奠定了人体生理、病理、诊断以及治疗的认识基础,是中国影响极大的一部医学著作,被称为医之始祖。\n![[assets/pasted image 20230526093247.png|400]]\n\u0026gt; 难经\n《难经》是战国时期成书,作者是扁鹊 ,原名《黄帝八十一难经》,古代中医学著作之一,传说为战国时期秦越人(扁鹊)所作。本书以问答解释疑难的形式编撰而成,共讨论了81个问题,故又称《八十一难》,全书所述以基础理论为主,还分析了一些病证。其中一至二十二难为脉学,二十三至二十九难为经络,三十至四十七难为脏腑,四十八至六十一难为疾病,六十二至六十八为腧穴,六十九至八十一难为针法。\n![[assets/pasted image 20230526093334.png|400]]\n\u0026gt; 四圣心源\n《四圣心源》是清·黄元御撰写于1753年的医书,又名《医圣心源》。作者将黄帝、岐伯、秦越人、张仲景视为医中四圣。本书阐发《内经》、《难经》、《伤寒论》、《金匮要略》诸书蕴义,卷一天人解;卷二六气解;卷三脉法解;卷四劳伤解;卷五至卷七杂病解;卷八七窍解;卷九疮疡解;卷十妇人解。是一部包括中医基本理论和部分临床医学的综合性著作。\n![[assets/pasted image 20230526093343.png|400]]\n\u0026gt; 奇经八脉考\n奇经八脉是指十二经脉之外的八条经脉,包括任脉、督脉、冲脉、带脉、阴跷脉、阳跷脉、阴维脉、阳维脉。奇者,异也。因其异于十二正经,故称“奇经”。它们既不直属脏腑,又无表里配合。其生理功能,主要是对十二经脉的气血运行起着溢蓄、调节作用。(看小说的都爱这个,长长见识 🧐)\n其他如《金匮要略》、《温病条辨》、《伤寒杂病论》和《神农本草经》太过专项了,概览了一下,实在是看不懂,暂且不去管它,后续有一定基础后再啃。\n结语 无论做什么事情,思而后行,行而方有所得。看,你其实有很多事情值得去做,总可以变得更好。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E4%BD%A0%E5%8F%AF%E8%83%BD%E9%9C%80%E8%A6%81%E5%AD%A6%E4%B8%80%E4%BA%9B%E5%8C%BB%E5%AD%A6%E7%9F%A5%E8%AF%86/","summary":"\u003cblockquote\u003e\n\u003cp\u003e“圣人不治已病,治未病;不治已乱,治未乱。”\u003cbr\u003e\n\u003ccode\u003e-- 《黄帝内经·素问·四气调神大论》\u003c/code\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e生老病死,成住坏空,古往皆如是。生命的前几十年,身体是在上升期,经得起小风小浪小消耗。然岁月不饶人,专治各种不服 …… 😅\u003c/p\u003e","title":"你可能需要学一些医学知识"},]
[{"content":"约两个月前,午休后的一个小意外不小心拉伤了右手姆指,当时也没有太注意,想着过几天就好了。俗话说,“伤筋动骨一百天嘛”。一周又一周,情况并没有得到好转。正好周末安排了体检,就顺带就诊一下。\n为什么拖了那么久?一是不在意,二是天太热、人太懒。\n不得不说“二档的医保不如狗”,限制太多 😿。医院的医生呢?现在离开各种检查就跟个“白痴”一样,当然,医生也要吃饭,但水平确实有待提一提。经历过一系列 blabla… 的检查,医生瞄了半眼片子,真的就半眼(无力吐槽…),来了句不用吃药,做三周(3 次)的冲击波治疗… 好的,不是情绪上的对立,不过现在的医患关系总给我一种下图的感觉,反正 1000 大洋就这样交待了。\n![[assets/pasted image 20230526093508.png]]\n做护理治疗的小姐姐还可以,挺温柔,说可能有些疼。过程中痛感也还好,感觉可能是轻微拉伤的原因。好吧,5 分钟 200 大洋 💰 。目前效果还不显著,估计是在恢复期,希望可以完美恢复。\n总之呢,身体已经在敲警钟了。\n想想,不知道有多少人长期处于亚健康的状态,一天又一天,像是一辆“衣衫褴褛”的自行车(除了铃铛不响)歪歪扭扭地向前行驶。当身体给我们发信号的时候,千万不要忽视它,意味着你的健康已欠费了,请充值。“人生不过数十载,无病无灾过一生”,真好!\n言归正传,之前没检查还好,一检查,感觉整个手都不太好了,搞得现在基本上是“一指禅”输入。作为一个程序猿,这真的是被缴了枪了。听我说,一定要爱护好你的手,脱离输入效率神教,“远离 emacs” 😂,好吧,少用点。业余时间,不要长时间玩手机或敲击键盘,可以做一些手工 diy 、弹吉他、吹笛子、下象棋、读纸质书等,总之就是不要让你的手过渡地重复性劳损。\n治疗后续\n第二次是真的有感觉痛了,医生小姐姐说是因为内部组织重建、血液流通了,痛觉更强烈…… 好吧,真的痛,后悔没有接受小姐姐的建议,在那里冷敷一下。好吧,下次一定…… 不过效果是真的感觉到了这次,估计再有一次就可以完全恢复了,这倒是一件让人欣慰的事情了。\n如何养护 如何养护你的手,先停止输入请敲击键盘 🤣 …… 好吧,一指禅太弱了,先这样,后续再补充。\n新情况 \u0026gt; 2023-08-29 17:01\n好的嘛,上次是右手,这次换成左手了…… 右手好歹还被拉伤了一下才出现不适,这左手纯粹是突然性的,一觉醒来就这样了…… 到现在也不清楚原因是什么,朋友说是腱鞘炎……\n哎,真的是服气……\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E4%BF%9D%E6%8A%A4%E5%A5%BD%E4%BD%A0%E7%9A%84%E6%89%8B/","summary":"\u003cp\u003e约两个月前,午休后的一个小意外不小心拉伤了右手姆指,当时也没有太注意,想着过几天就好了。俗话说,“伤筋动骨一百天嘛”。一周又一周,情况并没有得到好转。正好周末安排了体检,就顺带就诊一下。\u003c/p\u003e","title":"保护好你的手"},]
[{"content":"在 [[gtd 管理系统]] 和 [[极简主义生活]] 中,我们已经聊过类似的内容,然而“个人管理”终究是个没有止境的话题……\n\u0026gt; 这里不妨抛弃‘结构’,想到哪儿,说到哪儿……\n为什么需要‘个人管理’ 一万年太久,只争朝夕。\n时间看似有很多,却是不经用的很,而且,生活往往如一团乱麻,理不清,扯不顺。稍不留意,哪天顾镜自盼,也许就青丝霜雪了。\n你可以懒,但不能懒不自知,更不应知懒而不欲改之,毕竟,是你的人生不是?且大概率只有一次,不可回溯,就算可以轮回,你又有什么资格透支下辈子的人生呢?自欺欺人,可不是一件值得让人‘骄傲’的事。聪明如你,自不会如此愚蠢的,对吧?\n虽说‘难得糊涂’,却不应‘如此糊涂’,更不应‘一直糊涂’,保持清醒,了解自己。\n‘理清生活’的首要之举便在于探索收集生活中的方方面面,对其有过去及现在有一个基本的了解和掌握,当然,这不是一蹴而就的事情,也完全没有必要毕其功于一役,稍加思考就会发现,它本就贯穿你的一生,因其本就是生活。\n世事纷繁,人生苦短,‘善假物’者多少会轻松些,过程本身也会多些乐趣,毕竟我们不是‘苦行僧’,不用证那‘宏愿道’。\n切记,成功无捷径,必须得有的话,只会是 – 戒骄戒躁,实事求是 !\n如何管理 如何管理呢?日程表?事务巨细,尽皆预排?这可是一个大工程,基本上是很难坚持下来的,它本身对身心就是一个不小的消耗。相信,关注个人管理的,多少都有过类似的心中历程。费了大气力写的计划,不多日,便只能束之以高阁。叹兮,悲兮,奈若何?\n尽收所有‘悬而未决’之事!何为‘悬而未决’?乱你心者,搅你意者,困你心者,扰你念者!一句话就是,所有,你感觉要解决的或将要解决的,一网打尽,统统先记在‘小本本’上。\n然后,从上而下,逐次分析,是否需要采取行动,不需要行动的该怎么办,需要行动的又当如何做。分了门类,进一步组织管理它们,大而化小,小而具象,行之检之,周而往复。(具体步骤,详见 [[gtd 管理系统]])\n这里的‘行、检、周往’是很重要的,精进很始于此。生活,本就不应是简单地重复,只要保持向上之心,并身体力行,许多‘看似的后退’,其实质都是‘曲折的前进’。\n全面收集,认真分析,细致管理,定期回顾,踏实行动,实是缺一不可!\n少即是多 在不断地管理过程中,自然而然地就会发现,我们的生活‘太满了’,以至于显得‘拥挤’,变得‘混乱’。多而无序,杂乱无章,许多‘方便’反而成了‘负担’,以致让人积重难行,疲惫不堪。\n生活,应该“减负”了!\n极简主义生活方式得到了不少人的倾心,我个人也很喜欢这种理念,‘少即是多’,确保有限的选择对处女座简直是一种救赎。当然,我感觉,要追求‘适度的少’,而不是‘绝对的少’,太‘少’了,真的不够用了,反而失去了‘极简主义’本身服务于生活的初心。不能为了‘简’而简,一切最终要回归到生活本身,以人为本。\n适度的‘少’!少于何处?\n其实很简单,从生活的日常开始。如果你经常熬夜,那就要注意了,你肯定有很多需要‘少’的地方,比如,少刷或不刷短视频,少看或不看头条‘震惊榜’,少…… 如此,熬夜‘少’了,睡眠自然‘多’了,精力也就‘多’了,做事情更有效率,收获当然可预期地增‘多’……\n舍得舍得,少即是多哦 ~\n提到极简主义,就很难不涉及到“断、舍、离”,就我理解来看,没必要太纠结这三字分是何意,实则是同出而异名,谓之一物。它的内涵是什么呢?是放下包袱,是停止内耗,是平衡取舍,以达到与自己身心的和解。\n以此,愈精简,愈益于收集、分析和管理,良好的感觉又反馈自身,继而实现更深层次的精简。循环往复,平衡便在其中。\n具体的‘术’ 我们已经确定了基本的宗旨和理念 – 尽收‘悬而未决’,以至‘少即是多’,如何采取具体的行动呢,有没有‘万能药’拿来一试?\n有,也没有!\n所谓‘有’,是指其理念本身就是一种绝佳的指导;所谓‘没有’,是指从来存在什么‘一剑破万法’,凡事皆需要“具体问题,具体分析”,不可有思想上的怠惰。任何行之有效的具体方法,皆可拿来学习、借鉴和模仿,目的在于体会、理解其真义,但切忌一味地‘照猫画虎’,不求甚解。\n正所谓,“一切具象,皆为虚妄”, 本就没有“可道之常道,可名之常名”。\n如此,‘术’何以求?‘术’乃自生!\n在求‘道’的过程中,真正合适本身的‘术’自然就诞生了。当然,这并不是提倡什么不假外求,固步自封。正相反,‘道’存于心,求之在内、外,老祖宗自古就讲求一个“天人合一”,便是此理。\n未结之语 以上各种,难免‘言不及意’,哈,你不能要求一个初窥门径的人能说的清楚,道的明白,不过是抛砖引玉之举,探讨二三事罢了。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E5%86%8D%E8%B0%88%E4%B8%AA%E4%BA%BA%E7%AE%A1%E7%90%86/","summary":"\u003cp\u003e在 [[GTD 管理系统]] 和 [[极简主义生活]] 中,我们已经聊过类似的内容,然而“个人管理”终究是个没有止境的话题……\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; 这里不妨抛弃‘结构’,想到哪儿,说到哪儿……\u003c/code\u003e\u003c/p\u003e","title":"再谈“个人管理”"},]
[{"content":"🔔 摘录自 冴羽写博客之深入系列 ,写的很不错。\n深入系列 从原型到到原型链 先来一张“震山总图”,后续我们会慢慢讲解它是什么得到的。\n![[assets/pasted image 20230526104137.png|500]]\nprototype 每个函数都有一个 prototype 属性。那么这个函数到底指向的是什么?是这个函数的原型吗?\n先来看个简单的示例:\n1 2 3 4 5 6 7 8 9 10 function person() { } // 虽然写在注释里,但是你要注意: // prototype 是函数才会有的属性 person.prototype.name = \u0026#39;kevin\u0026#39;; var person1 = new person(); var person2 = new person(); console.log(person1.name)\t// kevin console.log(person2.name)\t// kevin 其实,函数的 prototype 属性指向了一个对象,这个对象正是调用构造函数面创建的实例的原型,也就是上述示例中 person1 和 person2 的原型。\n说了这么多, 什么是原型呢? 你可以这样理解:每一个 javascript 对象( null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型“继承”属性。\n“继承” ❓ 继承意味着复制操作,然而 javascript 默认并不会复制对象的属性,相反,它只是在两个对象之间创建一个关联,如此一个对象就可能通过委托访问另一个对象的属性和函数,所以,与其叫继承,委托的说法反而更准确些。\n![[assets/pasted image 20230526104158.png|400]]\n上图表示构造函数和实例原型(此处我们用 object.prototype 表示实例原型)之间的关系。那么,我们该怎么表示实例与实例原型,即 person 和 person.prototype 之间的关系呢?__proto__ ❗\n__proto__ 每一个 javascript 对象(除了 null)都具有一个属性 – __proto__ ,这个属性会指向该对象的原型。\n关于 __proto__ ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 person.prototype 中,实际上,它是来自于 object.prototype ,与其说是一个属性,不如说是一个 getter/setter ,当使用 obj.__proto__ 可以理解成返回了 object.getprototypeof(obj) 。\n为了证明这一点,我们可以在 firefox 或 chrome 浏览器中输入:\n1 2 3 4 5 function person() { } var person = new person(); console.log(person.__proto__ === person.prototype);\t// true :: chrome 的开发工具,真是一个神器 🎉\nok,现在让我们更新一下关系图,如下:\n![[assets/pasted image 20230526104217.png|400]]\n既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数呢?\n:: 原型:是啊,凭什么只能你指我,不服 👺\nconstructor 指向实例的倒是没有,因为一个构造函数可以生成多个实例。\n:: 物以稀为贵,多了就是“原罪”……\n但是原型指构造函数倒是有的 – constructor ,每个原型都有一个 constructor 属性指向关联的构造函数。如图:\n![[assets/pasted image 20230526104239.png|400]]\n综上我们已经得出:\n1 2 3 4 5 6 7 8 9 10 function person() { } var person = new person(); console.log(person.__proto__ == person.prototype) // true console.log(person.prototype.constructor == person) // true // 顺便学习一个 es5 的方法,可以获得对象的原型 console.log(object.getprototypeof(person) === person.prototype) // true 实例与原型 当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。\n1 2 3 4 5 6 7 8 9 10 11 12 13 function person() { } person.prototype.name = \u0026#39;kevin\u0026#39;; var person = new person(); person.name = \u0026#39;daisy\u0026#39;; console.log(person.name) // daisy delete person.name; console.log(person.name) // kevin 原型的原型是什么呢 ❓ 最顶层也没查到怎么办?\n在前面,我们已经讲了原型也是一个对象,即然是对象,我们就可以用最原始的方式创建它。如下:\n1 2 3 var obj = new object(); obj.name = \u0026#39;kevin\u0026#39; console.log(obj.name) // kevin 上述示例中的 obj 就是 person.prototype 。其实原型对象就是通过 object 构造函数生成的。\n![[assets/pasted image 20230526104253.png|400]]\n原型链 这样,就又产生了一个问题, ojbect.prototype 的原型是什么呢? null ❗ 不妨打印一下:\n1 console.log(object.prototype.__proto__ === null)\t// true 然而 null 到底是个什么东东?\nnull 表示“没有对象”,即该处不应该有值。所以 object.prototype.__proto__ 的值为 null 跟 object.prototype 没有原型,其实表达了一个意思。\n所以,查找属性的时候查到的 object.prototype 就可以停止查找了。\n然后,我们就得到了最初的这张图:\n![[assets/pasted image 20230526104304.png|425]]\n:: 其实,这些都是规定好了的,以上各项都是产生这种规定的可能的过程。\n词法作用域和动态作用域 什么是作用域?作用域是指程序源代码中定义变量的区域。作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。\njavascript 采用词法作用域(lexical scoping),即静态作用域。\n:: 其实,这个概念并没有那么多弯弯绕,它是 javascript 这门语言的运行机制和实现所自然而然会出现的结果。\n所谓“词法作用域”,就是函数的作用域在函数定义的时候就决定了;而与之相对对应的“动态作用域”,函数的作用域则是在函数调用的时候才决定的。\n我们先来看一个示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 var value = 1; function foo() { console.log(value); } function bar() { var value = 2; foo(); } bar(); // 结果是 ??? 结果是 1 !你答对了吗? 让我们分析一下具体的执行过程。\n执行 bar 函数,其在内部调用了 foo 函数,foo 函数执行输出 value 。输出哪个 value ?(上面说过,所谓“词法作用域”,就是函数的作用域在函数定义的时候就决定了)在 foo 定义的时候,其内部并没有局部变量 value ,就根据书写的位置查找上一层的代码,(在这里也就是 var value = 1)即 value 等于 1 ,所以结果会打印 1 。\n试想,如果 javascript 采用的是动态作用域,会输出什么?\n明白了吗?😏\n好的,我们再来看一下示例,如下:\n1 2 3 4 5 6 var value = 10; foo(); var value = 20; function foo() { console.log(value); } 思考一下,上面的代码会输出什么?为什么?\n❗️ 10 为什么会是 10 呢?不应该是 20 吗? 是这样的!在 javascript 中,函数声明和变量声明都会被提升,且函数声明会被先提升。 提升之后,上述代码的实际执行顺序就如下面这样了(伪代码): --------- var value; function foo; value = 10; foo = function() { console.log(value); } foo(); value = 20; --------- 看,现在很容易就可能看出输出 10 了,对吧。 💡 注意:提升的只是声明,并不是初始定义哦 有不清楚的地方,继续阅读后续章节。😸\n执行上下文栈 思考一下:javascript 中的程序是怎么执行的呢?顺序执行吗?\n实际上,javascript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个“准备工作”,如变量提升、函数提升等。\n那么,这个“段”究竟是如何划分的呢?javascript 引擎遇到一段怎样的代码时才会做“准备工作” ?\n可执行代码 这就要说到 javascript 的可执行代码(executable code)的类型有哪些了?\n其实很简单,就有三种:全局代码、函数代码、eval 代码。\n举个 🌰 ,当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”专业一点的说法就是“执行上下文(execution context)” 。\n执行上下文栈 接下来问题来了,我们写的函数多了去了,如何管理创建的那么多执行上下文呢???\njavascript 引擎🎙:“我”创建了执行上下文栈(execution context stack, ecs)来管理执行上下文。\n为了模拟执行上下文,我们不妨定义执行上下文栈为一个数组,如下 :\n1 ecstack = []; 试想,当 javascript 开始要执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalcontext 表示它,并且只有当整个应用程序结束的时候,ecstack 才会被清空。也就是说,在程序结束之前,ecstack 最底部永远有个 globalcontext :\n1 2 3 ecstack = [ globalcontext ]; :: 可见,环境是很重要的,是程序设计语言所必须实现的。\n我们来看一下示例,现在 javascript 引擎遇到下面的这段代码了:\n1 2 3 4 5 6 7 8 9 10 11 12 13 function fun3() { console.log(\u0026#39;fun3\u0026#39;) } function fun2() { fun3(); } function fun1() { fun2(); } fun1(); 当执行一个函数的时候,就会创建一个执行上下文,并且压入 ecs(执行上下文栈),当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。知道了工作原理,让我们来看看如何处理上面这段代码:\n// 伪代码 // fun1() ecstack.push(\u0026lt;fun1\u0026gt; functioncontext); // fun1 中竟然调用了 fun2 ,还要创建 fun2 的执行上下文 ecstack.push(\u0026lt;fun2\u0026gt; functioncontext); // f**k, fun2 还调用了 fun3 ... ecstack.push(\u0026lt;fun3\u0026gt; functioncontext); // fun3 执行完毕 ecstack.pop(); // fun2 执行完毕 ecstack.pop(); // fun1 执行完毕 ecstack.pop(); // javascript 接着执行后续的代码,但是 ecstack 底层永远有个 globalcontext ,直到程序结束 okay ,趁热打铁,让我们来看一段《javascript 高级程序设计》中的示例:\n1 2 3 4 5 6 7 8 9 var scope = \u0026#34;global scope\u0026#34;; function checkscope(){ var scope = \u0026#34;local scope\u0026#34;; function f(){ return scope; } return f(); } checkscope(); ecstack.push(\u0026lt;checkscope\u0026gt; functioncontext); ecstack.push(\u0026lt;f\u0026gt; functioncontext); ecstack.pop(); 再来看另一段示例:\n1 2 3 4 5 6 7 8 9 var scope = \u0026#34;global scope\u0026#34;; function checkscope(){ var scope = \u0026#34;local scope\u0026#34;; function f(){ return scope; } return f; } checkscope()(); ecstack.push(\u0026lt;checkscope\u0026gt; functioncontext); ecstack.pop(); ecstack.push(\u0026lt;f\u0026gt; functioncontext); ecstack.pop(); 上述两个示例的输出是一样的,都是 'local scope' (若不明白,回顾一下上个章节),但是他们的执行的过程却是有区别的。请好好体会一下其中的不同。\n是不是意犹未尽呢?为了更详细了解两个函数执行上的区别,我们在后续章节继续探究一下执行上下文到底包含了哪些内容。\n变量对象 在上个章节中讲到,当 javascript 代码执行一段可执行代码(excutable code)时,会创建对应的执行上下文(execution context)。\n对于每个执行上下文,都有三个重要属性:\n变量对象(variable object,vo); 作用域链(scope chain); this 这里,我们重点讲讲创建变量对象的过程。\n变量对象,是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。因为不同执行上下文下的变量对象稍有不同,所以我们来聊聊全局上下文下的变量对象和函数上下文下的变量对象。\n全局上下文 我们先了解一个概念 – 全局对象。以下是 w3school 中的介绍:\n全局对象是预定义的对象,作为 javascript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。\n在顶层 javascript 代码中,可以用关键字 this 引用全局对象。因为全局对象是作用域链的头,这意味着所有非限定的变量和函数名都会作为该对象的属性来查询。\n例如,当 javascript 代码引用 parseint() 函数时,它引用的是全局对象的 parseint 属性。全局对象是作用域链的头,还意味着在顶层 javascript 代码中声明的所有变量都将成为全局对象的属性。\n下面让我们来看一些关于全局对象的具体示例吧。\n1、可能通过 this 引用,在客户端 javascript 中,全局对象就是 window 对象。\nconsole.log(this); 2、全局对象是由 object 构造函数实体化的一个对象。\nconsole.log(this instanceof object); 3、预定义了一堆,嗯,一大堆函数和属性。\n// 都能生效 console.log(math.random()); console.log(this.math.random()); 4、作为全局变量的宿主。\nvar a = 1; console.log(this.a); 5、客户端 javascript 中,全局对象有 window 对象指向自身。\nvar a = 1; console.log(window.a); this.window.b = 2; console.log(this.b); 看,全局上下文中的变量对象就是全局对象呐!\n:: 可以认为全局对象就是一级掌控者,凡是游离在全局上下文的变量也好、函数也好,都归它管。🔱\n函数上下文 在函数上下文中,我们用活动对象(activation object,ao)来表示变量对象。\n❓ 活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎上实现的,不可在 javascript 环境中访问,只有当进行一个执行上下文中,这个执行上下文的变量对象才会被激活,而只有被激活的变量对象(即活动对象)上的各种属性才能被访问。\n❗ 未进入执行阶段之前,变量对象(vo)中的属性都不能访问 !但是进入执行阶段之后,变量对象(vo)转变为了活动对象(ao),里面的属性都能被访问了,然后开始进行执行阶段的操作。它们其实都是同一个对象,只是处于执行上下文的不同生命周期。\n:: 静则 v ,动则 a\n活动对象是在进入函数上下文时被创建的,它通过函数的 arguments 属性初始化。 arguments 属性值是 arguments 对象。\n执行过程 执行上下文的代码会分成两个阶段进行处理 – 分析和执行:\n进入执行上下文; 代码执行。 进入执行上下文\n当进入执行上下文时,这时候还没有执行代码。\n变量对象会包括:\n函数的所有形参(如果是函数上下文) 由名称和对应值组成的一个变量对象的属性被创建 没有实参,属性值设为 undefined 函数声明 由名称和对应值(函数对象 function-object)组成一个变量对象属性被创建 如果变量对象已经存在相同名称的属性,则完全替换这个属性 变量声明 由名称和对应值(undefined)组成一个变量对象的属性被创建 如果变量对象跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性 好吧,还是来看例子 🍩。\n1 2 3 4 5 6 7 8 9 function foo(a) { var b = 2; function c() {} var d = function() {}; b = 3; } foo(1); 在进入执行上下文后,这时候的 ao 如下:\nao = { // 进入上下文,活动对象创建 arguments: { 0: 1, length: 1 }, a: 1, // 形参函数的 `arguments` 属性初始化, b: undefined, // 没有对应实参的属性值设为 `undefined` c: reference to function c() {}, // 函数声明 d: undefined // 变量声明。.. } 代码执行\n在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值。\n依然使用上面的例子,当代码执行完后,这时候的 ao 是:\nao = { auguments: { 0: 1, length: 1 }, a: 1, b: 3, c: reference to function c(){}, d: reference to functionsexpression \u0026#34;d\u0026#34; } 到这里,变量对象的创建过程就介绍完了,来个小结吧:\n全局上下文的变量对象初始化是全局对象; 函数上下文的变量对象初始化只包括 arguments 对象; 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始化的属性值; 在代码执行阶段,会再次修改变量对象的属性值。 作用域链 当 javascript 代码执行一段可执行代码(excutable code)时,会创建对应的执行上下文(execution context)。\n对于每个执行上下文,都有三个重要属性:\n- 变量对象(variable object,vo); - 作用域链(scope chain); - this 是的,我们将在这个章节来看一下什么是作用域链。\n在上个章节中,我们知道,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做 作用域链。\n下面,让我们以一个函数的创建和激活两个时期来讲解作用域链是如何创建和变化的。\n函数创建 我们知道,在 javascript 中,函数的作用域在函数定义的时候就决定了。\n这是因为函数有一个内部属性 [scope](http://localhost:1313/posts/deep-js/scope),当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [scope](http://localhost:1313/posts/deep-js/scope) 就是所有父变量对象的层级链,但是注意, [scope](http://localhost:1313/posts/deep-js/scope) 并不代表完整的作用域链。\n举个例子:\n1 2 3 4 5 function foo() { function bar() { // ... } } 函数创建时,各自的 [scope](http://localhost:1313/posts/deep-js/scope) 为:\nfoo.[[scope]] = [ globalcontext.vo ]; bar.[[scope]] = [ foocontext.ao, globalcontext.vo ] 函数激活 当函数激活时,进入函数上下文 ,创建 vo/ao 后,就会将活动对象添加到作用域链的前端 。\n这时候执行正文的作用域链,我们命名为 scope :\nscope = [ao].concat([[scope]]); // 引自的 ao 为当前执行函数的变量对象, // [[scope]] 为其所有父变量对象的层级链 // 如:上例中 bar 的完整作用域链为 [ao, foocontext.ao, globalcontext.vo] 至此,作用域链创建完毕。\n🌰 我们来看一个示例,结合之前讲的变量对象和执行上下文栈,来总结一下函数执行上下文中作用域链和变量对象的创建过程:\n1 2 3 4 5 6 var scope = \u0026#34;global scope\u0026#34;; function checkscope() { var scope2 = \u0026#39;local scope\u0026#39;; return scope2; } checkscope(); 执行过程如下:\n1、 checkscope 函数被创建,保存作用域链到内部属性 [scope](http://localhost:1313/posts/deep-js/scope)\ncheckscope.[[scope]] = [ globalcontext.vo ]; 2、执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈\necstack = [ checkscopecontext, globalcontext ]; 3、checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数 [scope](http://localhost:1313/posts/deep-js/scope) 属性创建作用域链\ncheckscopecontext = { scope: checkscope.[[scope]], } 4、第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明\ncheckscopecontext = { ao: { arguments: { length: 0 }, scope2: undefined }, scope: checkscope.[[scope]] } 5、第三步:将活动对象压入 checkscope 作用域链顶端\ncheckscopecontext = { ao: { arguments: { length: 0 }, scope2: undefined }, scope: [ao, [[scope]]] } 6、准备工作做完,开始执行函数,随着函数的执行,修改 ao 的属性值\ncheckscopecontext = { ao = { arguments: { length: 0 }, scope2: \u0026#39;local scope\u0026#39; }, scope: [ao, [[scope]]] } 7、查找到 scope2 的值 ,返回函数执行完毕,函数上下文从执行上下文栈中弹出\necstack = [ globalcontext ]; 从 ecmascript 规范解读 this :: 好吧,这个 cd 的 this 😂\n当 javascript 代码执行一段可执行代码(excutable code)时,会创建对应的执行上下文(execution context)。\n对于每个执行上下文,都有三个重要属性:\n- 变量对象(variable object,vo); - 作用域链(scope chain); - this 是的,我们将在这个章节来看一下什么是 this 。让我们从 ecmascript5 规范开始讲起,附上 ecmascript 5.1 规范地址:英文版、中文版 。okay,让我们开始了解规范吧。\nes(ecmascript)的类型分为语言类型和规范类型。\nes 语言类型是开发者直接使用 es 可以操作的,其实就是我们常说的 undefined, null, boolean, string, number 和 object 。\nes 规范类型相当于 meta-values (元类型),是用来用算法描述 es 语言结构和 es 语言类型的。规范类型包括:reference, list, completion, property descriptor, property identifier, lexical environment 和 environment record 。\n:: 这都是什么东东 ❓\n没懂?没关系,我们只要知道在 es 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。今天我们要讲的重点便是其中的 reference 类型,它与 this 的指向有着密切的关联。\nreference 什么是 reference 呢?\nreference 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的。它是一个 specification type ,也就是“只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。\nreference 由三个部分组成:\nbase value ,属性所在的对象或者就是 environmentrecord ,其值只可能是 undefined, object, boolean, string, number 或 environmentrecord 中的一种; referenced name ,属性的名称; strict reference ,…… 来看个例子:\n1 2 3 4 5 6 7 8 var foo = 1; // 对应的 reference 是: var fooreference = { base: environmentrecord, name: \u0026#39;foo\u0026#39;, strict: false } 又如:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 var foo = { bar: function() { return this; } }; foo.bar(); // foo // bar 对应的 reference 是: var barreference = { base: foo, propertyname: \u0026#39;bar\u0026#39;, strict: false }; 而且规范中还提供了获取 reference 组成部分的方法,比如:\ngetbase ,返回 reference 的 base value ; ispropertyreference ,如果 base value 是一个对象,就返回 true ; getvalue ,用于从 reference 类型获取对应值。 我们来简单模拟 getvalue 的使用:\n1 2 3 4 5 6 7 8 9 var foo = 1; var fooreference = { base: environmentrecord, name: \u0026#39;foo\u0026#39;, strict: false }; getvalue(foorefence); // 1 可以看到, getvalue 返回对象属性真正的值,而不再是一个 reference ,切记切记。\n如何确定 this 的值 我们说了那么多关于 reference 的事情,它跟本文的主题 this 有哪些关联呢?\n看规范 11.2.3 function calls:这里讲了当函数调用的时候,如何确定 this 的取值。让我们简单描述一下:\n计算 memberexpression 的结果赋值给 ref ; 判断 ref 是不是一个 reference 类型: 2.1 如果 ref 是 reference ,并且 ispropertyreference(ref) 是 true ,那么 this 的值为 getbase(ref) ; 2.2 如果 ref 是 reference ,并且 base value 的值是 environmentrecord,那么 this 的值为 implicitthisvalue(ref) ; 2.3 如果 ref 不是 reference ,那么 this 的值为 undefined 。 下面,让我们一步一步来具体分析一下。\n1、计算 memberexpression 的结果赋值给 ref\n什么是 memberexpression ?规范 11.2 left-hand-side expressions 说明如下:\nmemberexpression :\nprimaryexpression ,原始表达式 functionexpression ,函数定义表达式 memberexpression[expression] ,属性访问表达式 memberexpression.indentifiername ,属性访问表达式 new memberexpression arguments ,对象创建表达式 来看一个例子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function foo() { console.log(this) } foo(); // memberexpression 是 foo function foo() { return function() { console.log(this) } } foo()(); // memberexpression 是 foo() var foo = { bar: function() { return this; } } foo.bar(); // memberexpression 是 foo.bar 所以简单理解 memberexpression 其实就是 () 左边的部分。\n2、判断 ref 是不是一个 reference 类型\n关键就在于看规范是如何处理各种 memberexpression ,返回的结果是不是一个 reference 类型。来看一个示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var value = 1; var foo = { value: 2, bar: function() { return this.value; } } // e.g.1 console.log(foo.bar()); // 2 // e.g.2 console.log((foo.bar)()); // 2 // e.g.3 console.log((foo.bar = foo.bar)()); // 1 // e.g.4 console.log((false || foo.bar)()); // 1 // e.g.5 console.log((foo.bar, foo.bar)()); // 1 foo.bar()\n在 e.g.1 中,memberexpression 计算的结果是 foo.bar ,那么 foo.bar 是不是一个 reference 呢?\n查看规范 11.2.1 property accessors 得知该表达式返回了一个 reference 类型,其值为:\nvar reference = { base: foo, name: \u0026#39;bar\u0026#39;, strict: false }; 2.1 如果 ref 是 reference ,并且 ispropertyreference(ref) 是 true ,那么 this 的值为 getbase(ref)\n该值是 reference 类型,那么 ispropertyreference(ref) 的结果是多少呢? base value 为 foo ,是一个对象,所以 ispropertyreference(ref) 返回 true 。\n此时,我们可以确定 this 的值了:\nthis = getbase(ref) // getbase 返回 reference 的 `base value` ,即 foo 所以,在这个例子中, this 的值就是 foo ,e.g.1 的结果就是 2 !\n:: 好吧,依然云里雾里,看来真的有必要系统去阅读一遍规范……\n(foo.bar)()\n实际上, () 并没有对 memberexpression 进行计算,所以其实跟 e.g.1 的结果是一样的。\n(foo.bar = foo.bar)()\n看 e.g.3 ,有赋值操作符,返回的值不是 reference 类型。\n2.3 如果 ref 不是 reference ,那么 this 的值为 undefined\nthis 为 undefined ,非严格模式下, this 的值为 undefined 的时候,其值会被隐式转换为全局对象。\n(false || foo.bar)()\n看 e.g.4 ,逻辑与算法,返回的值不是 reference 类型, this 为 undefined 。\n(foo.bar, foo.bar)()\n看 e.g.5 ,逗号操作符,返回的值不是 reference 类型, this 为 undefined 。\n注意,以上 是在非严格模式下的结果,严格模式下因为 this 返回 undefined , e.g.3 会报错。\n:: ………………\n最后,我们来看一种最普通的情况:\n1 2 3 4 5 function foo() { console.log(this); } foo(); memberexpression 是 foo ,解析标识符,查看规范 10.3.1 identifier resolution,会返回一个 reference 类型的值:\nvar fooreference = { base: environmentrecord, name: \u0026#39;foo\u0026#39;, strict: false } 2.1 如果 ref 是 reference ,并且 ispropertyreference(ref) 是 true ,那么 this 的值为 getbase(ref)\n因为 base value 是 environmentrecord ,并不是一个 object 类型, ispropertyreference(ref) 的结果为 false ,继续判断\n2.2 如果 ref 是 reference ,并且 base value 的值是 environmentrecord,那么 this 的值为 implicitthisvalue(ref)\nbase value 正是 environmentrecord ,调用 implicitthisvalue(ref) ,查看规范 10.2.1.1.6,implicitthisvalue 方法的介绍:该函数始终返回 undefined 。\n所以最后 this 的值就是 undefined 。\n🍨 最后 尽管我们可以简单地理解 this 为调用函数的对象,如果是这样的话,如何解释下面这个例子呢?\n1 2 3 4 5 6 7 8 9 var value = 1; var foo = { value: 2, bar: function () { return this.value; } } console.log((false || foo.bar)()); // 1 此外,又如何确定调用函数的对象是谁呢?\n在写这个章节之初,我(冴羽)就面临着这些问题,最后还是放弃从多个情形下给大家讲解 this 指向的思路,而是追根溯源地从 es 规范讲解 this 的指向。尽管从这个角度写起来和读起来都比较吃力,但是一旦多读几遍,明白原理,绝对会给你一个全新的视角看待 this 。而你也就能明白,尽管 foo() 和 (foo.bar = foo.bar)() 最后结果都指向了 undefined ,但是两者从规范的角度上却有着本质的区别。\n此篇讲解执行上下文的 this ,即便不是很理解此篇的内容,依然不影响大家了解执行上下文这个主题下其他的内容。所以,依然可以安心的看下一篇文章。\n:: 好吧,其实在实际应用中,你大概率不会遇到太多关于 this 的刁难,常见的应用场景就那几个。一切的一切都要为了解决实际问题而服务。\n我们必须明白,规范或参考书之类的东西,晦涩繁琐几乎是它们的特定属性,也就是说,时间多就通读一下学习,后续逐点参考即可。\n执行上下文 当 javascript 代码执行一段可执行代码(excutable code)时,会创建对应的执行上下文(execution context)。\n对于每个执行上下文,都有三个重要属性:\n- 变量对象(variable object,vo); - 作用域链(scope chain); - this 让我们来看一段《javascript 高级程序设计》中的示例:\n1 2 3 4 5 6 7 8 9 10 11 // e.g.1 var scope = \u0026#34;global scope\u0026#34;; function checkscope(){ var scope = \u0026#34;local scope\u0026#34;; function f(){ return scope; } return f(); } checkscope(); ecstack.push(\u0026lt;checkscope\u0026gt; functioncontext); ecstack.push(\u0026lt;f\u0026gt; functioncontext); ecstack.pop(); 再来看另一段示例:\n1 2 3 4 5 6 7 8 9 10 11 // e.g.2 var scope = \u0026#34;global scope\u0026#34;; function checkscope(){ var scope = \u0026#34;local scope\u0026#34;; function f(){ return scope; } return f; } checkscope()(); ecstack.push(\u0026lt;checkscope\u0026gt; functioncontext); ecstack.pop(); ecstack.push(\u0026lt;f\u0026gt; functioncontext); ecstack.pop(); 上述两段代码都会打印 'local scope' ,虽然执行结果一样,但执行过程却有区别(其执行上下文栈的变化不一样),我们在这个章节就详细的解析执行上下文栈和执行上下文的具体变化过程。\n以 e.g.1 的示例来说,其执行过程如下 :\n1、执行全局代码,创建全局执行上下文,全局上下文被存款利率执行上下文栈\necstack = [ globalcontext ]; 2、全局上下文初始化\nglobalcontext = { vo: [global], scope: [globalcontext.vo], this: globalcontext.vo } 初始化的同时,checkscope 函数被创建,保存作用域链到函数的内部属性 [scope](http://localhost:1313/posts/deep-js/scope)\ncheckscope.[[scope]] = [ globalcontext.vo ]; 3、执行 checkscope 函数,创建 checkscope 函数执行上下文, checkscope 函数执行上下文被存款利率执行上下文栈\necstack = [ checkscopecontext, globalcontext ]; 4、 checkscope 函数执行上下文初始化\n复制函数 [[scope]] 属性创建作用域链; 用 arguments 创建活动对象; 初始化活动对象,即加入形参、函数声明、变量声明; 将活动对象压入 checkscope 作用域链顶端。 同时 f 函数被创建,保存作用域到 f 函数的内部属性[scope](http://localhost:1313/posts/deep-js/scope)\ncheckscopecontext = { ao: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, scope: [ao, globalcontext.vo], this: undefined } 5、执行 f 函数,创建 f 函数执行上下文, f 函数执行上下文被压入上下文栈\necstack = [ fcontext, checkscopecontext, globalcontext ]; 6、 f 函数执行上下文初始化,以下跟第 4 步相同\n复制函数 [[scope]] 属性创建作用域链; 用 arguments 创建活动对象; 初始化活动对象,即加入形参、函数声明、变量声明; 将活动对象压入 f 作用域链顶端。 fcontext = { ao: { arguments: { length: 0 } }, scope: [ao, checkscopecontext.ao, globalcontext.vo], this: undefined } 7、 f 函数执行,沿着作用域链查找 scope 值,返回 scope 值\n8、 f 函数执行完毕, f 函数上下文从执行上下文栈中弹出\necstack = [ checkscopecontext, globalcontext ]; 9、 checkscope 函数执行完毕, checkscope 执行上下文从执行上下文栈中弹出\necstack = [ globalcontext ]; okay,就这样。\n对于 e.g.2 ,大家自己尝试模拟它的执行过程吧。\n:: 好的吧,要返工了 😂\n深入闭包 什么是闭包?\nthis combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.\nes 中,闭包指的是:\n从理论角度:所有的函数。因为它们都在创建的时候就将上下文的数据保存起来了,哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于在访问自由变量,这个时候使用最外层的作用域。 从实践角度:以下函数才算是闭包: 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回), 在代码中引用了自由变量 \u0026gt; 闭包是指那些能够访问自由变量的函数;自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。\n\u0026gt; 闭包 = 函数 + 函数能够访问的自由变量\n我们在实际应用中,基本上只关注实践角度的闭包 🤗\n来看看我们的老朋友:\n1 2 3 4 5 6 7 8 9 10 11 var scope = \u0026#34;global scope\u0026#34;; function checkscope(){ var scope = \u0026#34;local scope\u0026#34;; function f(){ return scope; } return f; } var foo = checkscope(); foo(); 让我们再次分析一下这段代码中执行上下文栈和执行上下文的变化情况,其简要的执行过程如下:\n进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈; 全局执行上下文初始化; 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈; checkscope 执行上下文初始化,创建变量对象、作用域链、this 等; checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出; 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈; f 执行上下文初始化,创建变量对象、作用域链、this 等; f 函数执行完毕,f 函数上下文从执行上下文栈中弹出。 你可能会有一个疑问 - 当 f 函数执行的时候, checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢 ❓\n当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:\nfcontext = { scope: [ao, checkscopecontext.ao, globalcontext.vo], } 对的,就是因为这个作用域链,f 函数依然可以读取到 checkscopecontext.ao 的值,说明当 f 函数引用了 checkscopecontext.ao 中的值的时候,即使 checkscopecontext 被销毁了,但是 javascript 依然会让 checkscopecontext.ao 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 javascript 做到了这一点,从而实现了闭包这个概念。\n所以,让我们再看一遍实践角度上闭包的定义:\n即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回), 在代码中引用了自由变量。 看,没什么太多玄妙的东西,只是 js 底层做了某些实现。同样的代码,在 php 中,就会报错,因为在 php 中,f 函数只能读取到自己作用域和全局作用域里的值,所以读不到 checkscope 下的 scope 值。\n接下来,看这道刷题必刷,面试必考的闭包题 💯\n1 2 3 4 5 6 7 8 9 10 11 var data = []; for (var i = 0; i \u0026lt; 3; i++) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2](); 答案是都是 3,让我们分析一下原因。\n当执行到 data[0] 函数之前,此时全局上下文的 vo 为:\nglobalcontext = { vo: { data: [...], i: 3 } } 当执行 data[0] 函数的时候,data[0] 函数的作用域链为:\ndata[0]context = { scope: [ao, globalcontext.vo] } data[0]context 的 ao 并没有 i 值,所以会从 globalcontext.vo 中查找,i 为 3,所以打印的结果就是 3。\ndata[1] 和 data[2] 是一样的道理。\n所以让我们改成闭包看看:😈\n1 2 3 4 5 6 7 8 9 10 11 12 13 var data = []; for (var i = 0; i \u0026lt; 3; i++) { data[i] = (function (i) { return function(){ console.log(i); } })(i); } data[0](); data[1](); data[2](); 当执行到 data[0] 函数之前,此时全局上下文的 vo 为:\nglobalcontext = { vo: { data: [...], i: 3 } } 跟没改之前一模一样 ❓ 不然 ❗️\n当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:\ndata[0]context = { scope: [ao, 匿名函数 context.ao, globalcontext.vo] } 匿名函数执行上下文的 ao 为:\n匿名函数 context = { ao: { arguments: { 0: 0, length: 1 }, i: 0 } } data[0]context 的 ao 并没有 i 值,所以会沿着作用域链从匿名函数 context.ao 中查找,这时候就会找 i 为 0,找到了就不会往 globalcontext.vo 中查找了,即使 globalcontext.vo 也有 i 的值(值为 3),所以打印的结果就是 0。\ndata[1] 和 data[2] 是一样的道理。\n参数按值传递 ecmascript 中所有函数的参数都是按值传递的。\n\u0026gt; 按值传递,就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。\n什么是引用传递 ❓\n当值是一个复杂的数据结构的时候,就按引用传递,即传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。\n:: 其实,就是一个是变量值,一个是地址值(引用内存地址),都是值啦 👻\ncall、apply、bind 的应用 \u0026gt; 该章节为附加章节,旨在说明其使用场景 ,可以参考 js 中的 call、apply、bind 方法详解 和 javascript 中 call、apply、bind 的区别 。\nbind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。\napply \u0026amp; call 在 javascript 中, call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。\njavascript 的一大特点是,函数存在 定义时上下文 和 运行时上下文 以及 上下文是可以改变的 这样的概念。\n1 2 3 4 5 6 7 8 9 10 11 function fruits() {} fruits.prototype = { color: \u0026#39;red\u0026#39;, say: function() { console.log(\u0026#39;my color is \u0026#39; + this.color); } } var apple = new fruits; apple.say(); // my color is red 如果我们有一个对象 banana = { color: 'yellow' } ,但我们不想对它重新定义 say 方法,那么我们就可以通过 call 或 apply 来调用 apple 的 say 方法,如下:\n1 2 3 4 5 6 banana = { color: \u0026#39;yellow\u0026#39; } apple.say.call(banana); // my color is yellow apple.say.apply(banana); // my color is yellow :: 这里 apply 的语义就更符合人们的直觉,表示把一个对象的方法应用在另一个对象上;相对来说, call 就比较反直觉(用 called 都比 call 强)!好的吧,js 就是个坑货 😅\n那么,apply 和 call 有什么区别呢?\n它们的作用完全一样,只是接受参数的方式不太一样。老规矩,上例子 🌰\n1 2 3 var func = function(arg1, arg2) { // ... }; 就可以通过如下方式来调用:\n1 2 func.call(this, arg1, arg2); func.apply(this, [arg1, arg2]); 其中, this 就是想指定的上下文对象(可以是任何一个 javascript 对象), call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里面。\n:: 其实这个很好记忆,只需要知道 apply 和 array 都是 5 个字母,所以其参数要放在数组中,就可以了。\n一个相对常遇到的场景 - 类(伪)数组使用数组的方法。\n我们来几个面试中可能遇到的问题:\neg.1 定义一个 log 方法,让它可以代理 console.log 方法,常见的解决方法是:\n1 2 3 4 5 6 function log(msg) { console.log(msg); } log(1); // 1 log(1, 2); // 1 2 接下来的要求是给每一个 log 消息添加一个 '(app)' 的前缀,比如:\nlog(\u0026#34;hello world\u0026#34;); // (app)hello world 如何做比较优雅呢?我们知道 arguments 参数是个伪数组,通过 array.prototype.slice.call 转化为标准数组,即可使用数组的方法 unshift ,如下:\n1 2 3 4 5 6 function log() { var args = array.protype.slice.call(arguments); args.unshift(\u0026#39;(app)\u0026#39;); console.log.apply(console, args); } :: 哎,怎么说呢,原理的理解是非常重要的,但学以致用,了解怎么用原理的具体应用才能真正的落地!\nbind 在讨论 bind() 方法之前,我们先来看一道题目,如下:\n1 2 var altwrite = document.write; altwrite(\u0026#39;hello\u0026#39;); 结果: uncaught typeerror: illegal invocation 怎么样,你答对了吗?为什么会是这样的结果呢?\n原来 altwrite() 函数改变 this 的指向 global 或 window 对象,导致执行时提示非法调用异常,正确的方案就是使用 bind() 方法,使其重新指向 document ,如下:\n1 2 3 altwrite.bind(document)(\u0026#39;hello\u0026#39;); // 或者 altwrite.call(document, \u0026#39;hello\u0026#39;) :: 在 react 中,你会经常碰到 bind …\nbind() 都有什么用呢???\n绑定函数 bind() 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的 this 值。\n常见的错误就像上面的例子一样,将方法从对象中拿出来,然后调用,并且希望 this 指向原来的对象。如果不做特殊处理,一般会丢失原来的对象。使用 bind() 方法能够很漂亮的解决这个问题。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 this.num = 9; var mymodule = { num: 81, getnum: function() { console.log(this.num); } }; mymodule.getnum(); // 81 var getnum = mymodule.getnum; getnum(); // 9, 因为在这个例子中,\u0026#34;this\u0026#34;指向全局对象 var boundgetnum = getnum.bind(mymodule); boundgetnum(); // 81 mdn 的解释是: bind() 方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind() 方法的第一个参数作为 this ,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的能数来调用原函数。\n:: 是不是也是一种代理 proxy 呢 ❓\n具体是个什么东东呢???\n在常见的单体模式中,通常我们会使用 _this , that , self 等保存 this ,这样我们可以在改变了上下文之后继续引用到它。 像这样:\n1 2 3 4 5 6 7 8 9 10 var foo = { bar : 1, eventbind: function(){ var _this = this; $(\u0026#39;.someclass\u0026#39;).on(\u0026#39;click\u0026#39;,function(event) { /* act on the event */ console.log(_this.bar); //1 }); } } 由于 javascript 特有的机制,上下文环境在 eventbind:function(){ } 过渡到 $('.someclass').on('click',function(event) { }) 发生了改变,上述使用变量保存 this 这些方式都是有用的,也没有什么问题。当然使用 bind() 可以更加优雅的解决这个问题:\n1 2 3 4 5 6 7 8 9 var foo = { bar : 1, eventbind: function(){ $(\u0026#39;.someclass\u0026#39;).on(\u0026#39;click\u0026#39;,function(event) { /* act on the event */ console.log(this.bar); //1 }.bind(this)); } } 在上述代码里,bind() 创建了一个函数,当这个 click 事件绑定在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用 bind() 时传入的参数)。因此,这里我们传入想要的上下文 this(其实就是 foo ),到 bind() 函数中。然后,当回调函数被执行的时候, this 便指向 foo 对象。再来一个简单的栗子:\n1 2 3 4 5 6 7 8 9 10 11 12 var bar = function(){ console.log(this.x); } var foo = { x:3 } bar(); // undefined var func = bar.bind(foo); func(); // 3 这里我们创建了一个新的函数 func,当使用 bind() 创建一个绑定函数之后,它被执行的时候,它的 this 会被设置成 foo , 而不是像我们调用 bar() 时的全局作用域。\n偏函数(partial functions) bind() 的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为 bind() 的参数写在 this 后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。\n1 2 3 4 5 6 7 8 9 10 11 function list() { return array.prototype.slice.call(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3] // 预定义参数 37 var leadingthirtysevenlist = list.bind(undefined, 37); var list2 = leadingthirtysevenlist(); // [37] var list3 = leadingthirtysevenlist(1, 2, 3); // [37, 1, 2, 3] :: 知道这些就行了,其他的不常用……\napply call bind 的比较 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个栗子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 var obj = { x: 81, }; var foo = { getx: function() { return this.x; } } console.log(foo.getx.bind(obj)()); //81 console.log(foo.getx.call(obj)); //81 console.log(foo.getx.apply(obj)); //81 三个输出的都是 81,但是注意看使用 bind() 方法的,他后面多了对括号。\n也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。\n最后,来个小结吧:\napply 、 call 、bind 三者都是用来改变函数的this对象的指向的; apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文; apply 、 call 、bind 三者都可以利用后续参数传参; bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。 call 和 apply 的模拟实现 call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。\n举个例子:\n1 2 3 4 5 6 7 8 9 var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call(foo); // 1 注意两点:\ncall 改变了 this 的指向,指向到 foo bar 函数执行了 如何实现模拟一下 call 呢???思考一下 😎\n……\nbind 的模拟实现 bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 mdn )\n由此我们可以首先得出 bind 函数的两个特点:\n返回一个函数 可以传入参数 如何实现模拟一下 bind 呢???思考一下 😎\n……\nnew 的模拟实现 ……\n类数组对象与 arguments 类数组对象 什么是类数组对象呢?– 拥有一个 length 属性和若干索引属性的对象。像这样:\n1 2 3 4 5 6 7 8 var array = [\u0026#39;name\u0026#39;, \u0026#39;age\u0026#39;, \u0026#39;sex\u0026#39;]; var arraylike = { 0: \u0026#39;name\u0026#39;, 1: \u0026#39;age\u0026#39;, 2: \u0026#39;sex\u0026#39;, length: 3 } 类数组对象在读写、获取长度、遍历三个方基本是相同的,但类数组对象可以使用数组的一些方法会报错,如:\narraylike.push(\u0026#39;4\u0026#39;); // ❌ arraylike.push is not a function 所以终归还是类数组呐…… 错付了呀 😭\n调用数组方法 如果类数组就是任性的想用数组的方法怎么办呢?可以用 function.call 间接调用,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var arraylike = {0: \u0026#39;name\u0026#39;, 1: \u0026#39;age\u0026#39;, 2: \u0026#39;sex\u0026#39;, length: 3 } array.prototype.join.call(arraylike, \u0026#39;\u0026amp;\u0026#39;); // name\u0026amp;age\u0026amp;sex // 类数组转数组 // 1. slice array.prototype.slice.call(arraylike); // [\u0026#34;name\u0026#34;, \u0026#34;age\u0026#34;, \u0026#34;sex\u0026#34;] // 2. splice array.prototype.splice.call(arraylike, 0); // [\u0026#34;name\u0026#34;, \u0026#34;age\u0026#34;, \u0026#34;sex\u0026#34;] // 3. es6 array.from array.from(arraylike); // [\u0026#34;name\u0026#34;, \u0026#34;age\u0026#34;, \u0026#34;sex\u0026#34;] // 4. apply array.prototype.concat.apply([], arraylike) array.prototype.map.call(arraylike, function(item){ return item.touppercase(); }); // [\u0026#34;name\u0026#34;, \u0026#34;age\u0026#34;, \u0026#34;sex\u0026#34;] 看, call 改变了当前函数调用者的 this 指向到了 arraylike 。\narguments 对象 arguments 对象就是一个类数组对象!\narguments 对象只定义在函数体中,包括了函数的参数和其他属性。在函数体中,arguments 指代该函数的 arguments 对象。\n翠花,上 🌰\n1 2 3 4 5 function foo(name, age, sex) { console.log(arguments); } foo(\u0026#39;name\u0026#39;, \u0026#39;age\u0026#39;, \u0026#39;sex\u0026#39;) 打印结果如下:\nlength 属性\n其中,arguments 对象的 length 属性,表示 实参的长度\n1 2 3 4 5 6 7 8 9 10 function foo(b, c, d){ console.log(\u0026#34;实参的长度为:\u0026#34; + arguments.length) } console.log(\u0026#34;形参的长度为:\u0026#34; + foo.length) foo(1) // 形参的长度为:3 // 实参的长度为:1 callee 属性\narguments 对象的 callee 属性,通过它可以调用函数自身。\n讲个闭包经典面试题使用 callee 的解决方法:\n1 2 3 4 5 6 7 8 9 10 11 var data = []; for (var i = 0; i \u0026lt; 3; i++) { (data[i] = function () { console.log(arguments.callee.i) }).i = i; } data[0](); // 0 data[1](); // 1 data[2](); // 2 arguments 和对应参数的绑定\n传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享。\n* 如果是在严格模式下,实参和 arguments 是不会共享的。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function foo(name, age, sex, hobbit) { console.log(name, arguments[0]); // name name // 改变形参 name = \u0026#39;new name\u0026#39;; console.log(name, arguments[0]); // new name new name // 改变 arguments arguments[1] = \u0026#39;new age\u0026#39;; console.log(age, arguments[1]); // new age new age // 测试未传入的是否会绑定 console.log(sex); // undefined sex = \u0026#39;new sex\u0026#39;; console.log(sex, arguments[2]); // new sex undefined arguments[3] = \u0026#39;new hobbit\u0026#39;; console.log(hobbit, arguments[3]); // undefined new hobbit } foo(\u0026#39;name\u0026#39;, \u0026#39;age\u0026#39;) 传递参数\n将参数从一个函数传递到另一个函数,如下:\n1 2 3 4 5 6 7 8 9 // 使用 apply 将 foo 的参数传递给 bar function foo() { bar.apply(this, arguments); } function bar(a, b, c) { console.log(a, b, c); } foo(1, 2, 3) // 1 2 3 使用 es6 的 ... 运算符,我们可以把 arguments 轻松转成数组。\n结语 好的吧,好像学到了一些东西,又好像什么都没学到 🤪 ,后续还有一些篇章,但对我来说已经没有多大吸引力,有兴趣的小伙伴可以参阅 冴羽写博客 javascript 深入系列 。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E5%86%B4%E7%BE%BD%E5%86%99%E5%8D%9A%E5%AE%A2%E4%B9%8B%E6%B7%B1%E5%85%A5%E7%B3%BB%E5%88%97/","summary":"\u003cp\u003e🔔 摘录自 \u003ca href=\"https://github.com/mqyqingfeng/Blog\"\u003e冴羽写博客之深入系列\u003c/a\u003e ,写的很不错。\u003c/p\u003e","title":"冴羽写博客之深入系列"},]
[{"content":"![[assets/pasted image 20230802145832.png|300]]\n🔔 http://www.ruanyifeng.com/blog/2019/10/tmux.html\nlayouts 布局、window 窗口、pane 窗格 布局 层级 描述 窗口 命令 描述 窗格 命令 描述 * server 服务 * c 新建窗口 * % 水平分屏 * session 会话 * \u0026amp; 关闭窗口 * \u0026quot; 垂直分屏 * window 窗口 * l 切换窗口 * x 关闭窗格 * pane 窗格 * n 切换到下一个窗口 * ; 切换窗格 * * p 切换到上一个窗口 * o 顺时针切换窗格 * * w 窗口的菜单列表 * c-o 逆时针转换窗格 * * * m-o 顺时针转换窗格 配置 r1r2\n新建 ~/.tmux.conf 文件,并写入:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # set new default prefix # 修改主键 unbind c-b set-option -g prefix c-j # mouse # 激活鼠标模式 set-option -g -q mouse on # easy split pane commands # 修改分屏按键 bind h split-window -h bind v split-window -v unbind \u0026#39;\u0026#34;\u0026#39; unbind % ","date":"2023-05-26","permalink":"https://aituyaa.com/%E5%8D%81%E5%88%86%E9%92%9F%E6%8E%8C%E6%8F%A1-tmux/","summary":"\u003cp\u003e![[assets/Pasted image 20230802145832.png|300]]\u003c/p\u003e\n\u003cp\u003e🔔 \u003ca href=\"http://localhost:1313/posts/tmux/%E5%8F%8B%E6%83%85%E9%93%BE%E6%8E%A5\"\u003ehttp://www.ruanyifeng.com/blog/2019/10/tmux.html\u003c/a\u003e\u003c/p\u003e","title":"十分钟掌握 tmux"},]
[{"content":"![[assets/pasted image 20230526111442.png|325]]\n🔔 本文摘录自 如何维护更新日志 – 更新日志绝对不应该是 git 日志的堆砌物\n简介 version 1.0.0\n# changelog\rall notable changes to this project will be documented in this file.\rthe format is based on [keep a changelog](https://keepachangelog.com/en/1.0.0/),\rand this project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).\r## [unreleased]\r## [1.0.0] - 2017-06-20\r### added\r- new visual identity by [@tylerfortune8](https://github.com/tylerfortune8).\r- version navigation.\r- links to latest released version in previous versions.\r- \u0026#34;why keep a changelog?\u0026#34; section.\r- \u0026#34;who needs a changelog?\u0026#34; section.\r- \u0026#34;how do i make a changelog?\u0026#34; section.\r- \u0026#34;frequently asked questions\u0026#34; section.\r- new \u0026#34;guiding principles\u0026#34; sub-section to \u0026#34;how do i make a changelog?\u0026#34;.\r- simplified and traditional chinese translations from [@tianshuo](https://github.com/tianshuo).\r- german translation from [@mpbzh](https://github.com/mpbzh) \u0026amp; [@art4](https://github.com/art4).\r- italian translation from [@azkidenz](https://github.com/azkidenz).\r- swedish translation from [@magol](https://github.com/magol).\r- turkish translation from [@karalamalar](https://github.com/karalamalar).\r- french translation from [@zapashcanon](https://github.com/zapashcanon).\r- brazilian portugese translation from [@webysther](https://github.com/webysther).\r- polish translation from [@amielucha](https://github.com/amielucha) \u0026amp; [@m-aciek](https://github.com/m-aciek).\r- russian translation from [@aishek](https://github.com/aishek).\r- czech translation from [@h4vry](https://github.com/h4vry).\r- slovak translation from [@jkostolansky](https://github.com/jkostolansky).\r- korean translation from [@pierceh89](https://github.com/pierceh89).\r- croatian translation from [@porx](https://github.com/porx).\r- persian translation from [@hameds](https://github.com/hameds).\r- ukrainian translation from [@osadchyi-s](https://github.com/osadchyi-s).\r### changed\r- start using \u0026#34;changelog\u0026#34; over \u0026#34;change log\u0026#34; since it\u0026#39;s the common usage.\r- start versioning based on the current english version at 0.3.0 to help\rtranslation authors keep things up-to-date.\r- rewrite \u0026#34;what makes unicorns cry?\u0026#34; section.\r- rewrite \u0026#34;ignoring deprecations\u0026#34; sub-section to clarify the ideal\rscenario.\r- improve \u0026#34;commit log diffs\u0026#34; sub-section to further argument against\rthem.\r- merge \u0026#34;why can’t people just use a git log diff?\u0026#34; with \u0026#34;commit log\rdiffs\u0026#34;\r- fix typos in simplified chinese and traditional chinese translations.\r- fix typos in brazilian portuguese translation.\r- fix typos in turkish translation.\r- fix typos in czech translation.\r- fix typos in swedish translation.\r- improve phrasing in french translation.\r- fix phrasing and spelling in german translation.\r### removed\r- section about \u0026#34;changelog\u0026#34; vs \u0026#34;changelog\u0026#34;.\r## [0.3.0] - 2015-12-03\r### added\r- ru translation from [@aishek](https://github.com/aishek).\r- pt-br translation from [@tallesl](https://github.com/tallesl).\r- es-es translation from [@zeliosariex](https://github.com/zeliosariex).\r## [0.2.0] - 2015-10-06\r### changed\r- remove exclusionary mentions of \u0026#34;open source\u0026#34; since this project can\rbenefit both \u0026#34;open\u0026#34; and \u0026#34;closed\u0026#34; source projects equally.\r## [0.1.0] - 2015-10-06\r### added\r- answer \u0026#34;should you ever rewrite a change log?\u0026#34;.\r### changed\r- improve argument against commit logs.\r- start following [semver](https://semver.org) properly.\r## [0.0.8] - 2015-02-17\r### changed\r- update year to match in every readme example.\r- reluctantly stop making fun of brits only, since most of the world\rwrites dates in a strange way.\r### fixed\r- fix typos in recent readme changes.\r- update outdated unreleased diff link.\r## [0.0.7] - 2015-02-16\r### added\r- link, and make it obvious that date format is iso 8601.\r### changed\r- clarified the section on \u0026#34;is there a standard change log format?\u0026#34;.\r### fixed\r- fix markdown links to tag comparison url with footnote-style links.\r## [0.0.6] - 2014-12-12\r### added\r- readme section on \u0026#34;yanked\u0026#34; releases.\r## [0.0.5] - 2014-08-09\r### added\r- markdown links to version tags on release headings.\r- unreleased section to gather unreleased changes and encourage note\rkeeping prior to releases.\r## [0.0.4] - 2014-08-09\r### added\r- better explanation of the difference between the file (\u0026#34;changelog\u0026#34;)\rand its function \u0026#34;the change log\u0026#34;.\r### changed\r- refer to a \u0026#34;change log\u0026#34; instead of a \u0026#34;changelog\u0026#34; throughout the site\rto differentiate between the file and the purpose of the file — the\rlogging of changes.\r### removed\r- remove empty sections from changelog, they occupy too much space and\rcreate too much noise in the file. people will have to assume that the\rmissing sections were intentionally left out because they contained no\rnotable changes.\r## [0.0.3] - 2014-08-09\r### added\r- \u0026#34;why should i care?\u0026#34; section mentioning the changelog podcast.\r## [0.0.2] - 2014-07-10\r### added\r- explanation of the recommended reverse chronological release ordering.\r## [0.0.1] - 2014-05-31\r### added\r- this changelog file to hopefully serve as an evolving example of a\rstandardized open source project changelog.\r- cname file to enable github pages custom domain\r- readme now contains answers to common questions about changelogs\r- good examples and basic guidelines, including proper date formatting.\r- counter-examples: \u0026#34;what makes unicorns cry?\u0026#34;\r[unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.0...head\r[1.0.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.3.0...v1.0.0\r[0.3.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.2.0...v0.3.0\r[0.2.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.1.0...v0.2.0\r[0.1.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.8...v0.1.0\r[0.0.8]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.7...v0.0.8\r[0.0.7]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.6...v0.0.7\r[0.0.6]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.5...v0.0.6\r[0.0.5]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.4...v0.0.5\r[0.0.4]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.3...v0.0.4\r[0.0.3]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.2...v0.0.3\r[0.0.2]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.1...v0.0.2\r[0.0.1]: https://github.com/olivierlacan/keep-a-changelog/releases/tag/v0.0.1 更新日志是什么?\n更新日志(change log)是一个由人工编辑,以时间为倒序的列表, 以记录一个项目中所有版本的显著变动。\n为何要提供更新日志?\n为了让用户和开发人员更简单明确的知晓项目在不同版本之间有哪些显著变动。\n哪些人需要更新日志?\n人人需要更新日志。无论是消费者还是开发者,软件的最终用户都关心软件所包含什么。 当软件有所变动时,大家希望知道改动是为何、以及如何进行的。\n怎样制作高质量的更新日志? 指导原则:\n记住日志是写给人的,而非机器 每个版本都应该有独立的入口 同类改动应该分组放置 版本与章节应该相互对应 新版本在前,旧版本在后 应包括每个版本的发布日期 注明是否遵守 [[语义化版本]] 格式 变动类型 备注 added 新添加的功能 changed 对现有功能的变更 deprecated 已经不建议使用,准备很快移除的功能 removed 已经移除的功能 fixed 对 bug 的修复 security 对安全的改进 \u0026gt; 表:变动类型\n如何减少维护更新日志的精力? 在文档最上方提供 unreleased 区块以记录即将发布的更新内容。\n这样有两大意义:\n大家可以知道在未来版本中可能会有哪些变更 在发布新版本时,可以直接将 unreleased 区块中的内容移动至新发布版本的描述区块就可以了 有很糟糕的更新日志吗? 当然有,下面就是一些糟糕的方式。\n使用 git 日志\n使用 git 日志作为更新日志是个非常糟糕的方式:git 日志充满各种无意义的信息, 如合并提交、语焉不详的提交标题、文档更新等。\n提交的目的是记录源码的演化。 一些项目会清理提交记录,一些则不会。\n更新日志的目的则是记录重要的变更以供最终受众阅读,而记录范围通常涵盖多次提交。\n无视即将弃用功能\n当从一个版本升级至另一个时,人们应清楚(尽管痛苦)的知道哪些部分将出现问题。 应该允许先升级至一个列出哪些功能将会被弃用的版本,待去掉那些不再支持的部分后, 再升级至把那些弃用功能真正移除的版本。\n即使其他什么都不做,也要在更新日志中列出 derecations,removals 以及其他重大变动。\n易混淆的日期格式\n在美国,人们将月份写在日期的开头 (06-02-2012 对应 2012 年 6 月 2 日), 与此同时世界上其他地方的很多人将至写作 2 june 2012,并拥有不同发音。 2012-06-02 从大到小的排列符合逻辑,并不与其他日期格式相混淆,而且还 符合 iso 标准。因此,推荐在更新日志中采用使用此种日期格式。\n还有更多内容。请通过 发布问题 或发布 pull 请求帮助我收集更多异常模式。\nfaq 是否有一个标准化的更新日志格式?\n并没有。虽然 gnu 提供了更新日志样式指引,以及那个仅有两段长的 gnu news 文件“指南”, 但两者均远远不够。\n此项目意在提供一个 更好的更新日志惯例 所有点子都来自于在开源社区中对优秀实例的观察与记录。\n对于所有建设性批评、讨论及建议,我们都非常 欢迎。\n更新日志文件应被如何命名?\n可以叫做 changelog.md。 一些项目也使用 history、news 或 releases。\n当然,你可以认为更新日志的名字并不是什么要紧事,但是为什么要为难那些仅仅是想看到都有哪些重大变更的最终用户呢?\n对于 github 发布呢?\n这是个非常好的倡议。releases 可通过手动添加发布日志或将带 有注释的 git 标签信息抓取后转换的方式,将简单的 git 标签(如一个叫 v1.0.0 的标签) 转换为信息丰富的发布日志。\ngithub 发布会创建一个非便携、仅可在 github 环境下显示的更新日志。尽管会花费更 多时间,但将之处理成更新日志格式是完全可能的。\n现行版本的 github 发布不像哪些典型的大写文件 (readme, contributing, etc.),仍可以认为是不利于最终用户探索的。 另一个小问题则是界面并不提供不同版本间 commit 日志的链接。\n更新日志可以被自动识别吗?\n非常困难,因为有各种不同的文件格式和命名。\nvandamme 是一个 ruby 程序,由 gemnasium 团队制作,可以解析多种 (但绝对不是全部)开源库的更新日志。\n那些后来撤下的版本怎么办?\n因为各种安全/重大 bug 原因被撤下的版本被标记 ‘yanked’。 这些版本一般不出现在更新日志里,但建议他们出现。 显示方式应该是: ## 0.0.5 - 2014-12-13 [yanked] 。\n[yanked] 的标签应该非常醒目。人们应该非常容易就可以注意到他。 并且被方括号所包围也使其更易被程序识别。\n是否可以重写更新日志?\n当然可以。总会有多种多样的原因需要我们去改进更新日志。 对于那些有着未维护更新日志的开源项目,我会定期打开 pull 请求以加入缺失的发布信息。\n另外,很有可能你发现自己忘记记录一个重大功能更新。这种情况下显然你应该去重写更新日志。\n如何贡献?\n本文档并非真理。而是我深思熟虑后的建议,以及我收集的信息与典例。\n我希望我们的社区可以对此达成一致。我相信讨论的过程与最终结果一样重要。\n所以欢迎 贡献.\n访谈 我在 更新日志播客 上讲述了为何维护者与贡献者应关心更新日志, 以及支持我进行此项目的诸多因素。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E5%A6%82%E4%BD%95%E7%BB%B4%E6%8A%A4%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97/","summary":"\u003cp\u003e![[assets/Pasted image 20230526111442.png|325]]\u003c/p\u003e\n\u003cp\u003e🔔 本文摘录自 \u003ca href=\"https://keepachangelog.com/zh-CN/1.0.0/\"\u003e如何维护更新日志 – 更新日志绝对不应该是 git 日志的堆砌物\u003c/a\u003e\u003c/p\u003e","title":"如何维护更新日志"},]
[{"content":"i.e. charset and character encoding\n字符是什么?字母、汉字、标点符号、控制字符、假名……\n计算机中储存的信息都是二进制数表示的,我们在屏幕上看到的英文、汉字等字符都是二进制转换之后的结果。按照何种规则将字符存储在计算机中,如 a 用什么表示,称为“编码”;反之,将存储在计算机中的二进制数解析显示出来,称为“解码”。\n:: 可以这样说,人类可读的即为“解”,计算机可读的即为“编”。\n严格来说,字符集和字符编码不是一个概念,字符集定义了字符和二进制的对应关系,为字符分配了唯一的编号,而字符编码规定了如何将字符的编号存储到计算机中。\n:: 字符集是一张码表,它记录了字符和对应二进制一对一的映射关系。\u0026gt; 2024-12-05 12:02\n也就是说,字符编码是依赖于字符集的,就像代码中的接口实现依赖于接口一样;一个字符集可以有多个编码实现,就像一个接口可以有多个实现类一样。如下图所示:\n![[assets/pasted image 20230526090550.png]]\n为什么要严格区分字符集与字符编码这两个概念呢?\n在早期,字符集与字符编码是一对一的(如 ascii、gbk)。但随着时间的发展,出现了一对多的情形,即一种字符集可能有了多种编码实现。如上图所示,unicode 字符集就有 utf-8、utf-16、utf-32 多种编码方式。\n如果你想要了解更多关于字符集及字符编码相关的历史,可以阅读 该文档 。\n常用字符集 \u0026amp; 编码 知道了字符、字符集及字符编码的基本概念,哪到底都有什么字符集及其编码规则呢 ❓\nascii ascii(american standard code for information interchange,美国信息交换标准代码),是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语(ascii)和其他西欧语言 (eascii,基于 ascii 的扩展)。它由 ansi(american national standard insitute,美国国家标准学会)制定的,是一种标准的 单字节字符编码方案 。\n:: 好吧,还是缩写好用 😅……\n单字节?1 个字节 包含 8 位,也就是最多编码 256 (2^8)个字符喽。事实上,基础的 ascii 只使用了 7 位,共 128 (2^7) 个字符,后续的 eascii 扩展为了表示更多欧洲常用字符,才使用了第 8 位。\n下图为基础版的 ascii :\n![[assets/pasted image 20230526090610.png]]\ngb* 系 256 种表示?在汉字面前微不足道!不够用啊!!!\n其实对于汉字的个数并没有个准确的数字,不完全统计汉字大约有十万个左右。根据 1988 年颁布的《现代汉语常用字表》,在我们日常生活中常用的汉字有 2500 个,次常用的汉字有 1000 个。\n一个字节不够用?那就再加一个,65536(2^16 = 65536) 种表示,勉强基本够用了……\n前后经历了,gb2312 → gbk → gb18030 ,具体细节请自行查阅哦。你可能还见过 big5 ,它是繁体中文常用的汉字字符标准。\ngbk 并非国标,是微软搞出来的在 gb13000(国标过渡版)基础上扩展的(编码方式不同,emm…),最初实现于 windows95 简体中文版。\nps:最新的中文字符集国标版本是 2022 年 7 月发布、2023 年 8 月 1 日起实施的 gb18030-2022 。\nunicode 全世界有上百种语言,各国有各国的标准,会冲突的!!!多语言混合的文本中,就成了“一锅粥”,乱码了……\n![[assets/pasted image 20230526090622.png|400]]\n有没有一种字符集,收录了世界上所有的字符,统一编码呢 ❓ 有,unicode !\nunicode 编码系统为表达任意语言的任意字符而设计,它使用 4 字节数来表达每个字母、符号,或者表意文字。\n4 个字节,4,294,967,296 (2^32 = 4,294,967,296) 种表示,遇到外星人 📡 也够用了……\nutf-32 就是用 4 个字节,utf-16 用的是 2 个字节,那 utf-8 就是 1 个字节了? 不,utf-8 是变长的(可变长度字符编码)。\n![[assets/pasted image 20230526090636.png|400]]\n就左边这们大佬(肯·汤普森)搞出来的,他还做了 b 语言,基于 b 语言的 unix ,c 语言,后又用 c 语言重新编写了 unix ,现在又搞了个 golang …… 右边这位好基友(丹尼斯·里奇)也是个神,unix 和 c 语言的共同创始人。\n:: 谢祖师爷……歇歇吧,卷不动了…… 😱\n为什么我们需要 utf-8 呢?如果用 utf-16(最常用的 unicode 标准),如果你写的都全部是英文的话,使用它编码就需要多出一倍的存储空间,在存储和传输上就十分不划算。\n硬盘不贵,带宽贵啊!\n本着节约的精神,可变长编码的 utf-8 诞生了,它把一个 unicode 字符根据不同的字符大小编码成 1~6 个字节,常用的英文字母被编码成 1 个字节(ascii 的超集),汉字通常是 3 个字节,只有很生僻的字符才会被编码成 4~6 个字节。\n实际上,现在计算机系统通用的字符编码工作方式:在计算机内存中,统一使用 unicode 编码(utf-16),当需要保存到硬盘或者需要传输的时候,就转换为 utf-8 编码。\n用记事本编辑的时候,从文件读取的 utf-8 字符被转换为 unicode 字符到内存里,编辑完成后,保存的时候再把 unicode 转换为 utf-8 保存到文件:\n![[assets/pasted image 20230526090654.png|232]] ![[assets/pasted image 20230526090704.png|242]]\n浏览网页的时候,服务器会把动态生成的 unicode 内容转换为 utf-8 再传输到浏览器。\n互联网工程工作小组(ietf)要求所有互联网协议都必须支持 utf-8 编码。\nutf-8 的实现原理 上文,我们知道 utf-8 是可变长度编码,那么在解码时,如何知道当前字符占用几个字节呢?通过解析第一个字节获取信息。\n1 个字节\n如果第一个字节的最高位是 0 ,那么表示当前字符占一个字节,如下:\n![[assets/pasted image 20230526090742.png|300]]\n这里也可以看出 utf-8 是完全兼容 ascii 码的,因为 ascii 码的最高位也是 0 。\n2 个字节\n如果第一个字节的最高位是 110 ,那么表示这个字符占 2 个字节,第二个字节的最高 2 位是 10 ,如下:\n![[assets/pasted image 20230526090757.png|550]]\n蓝色部分的数字组合在一起,就是实际的码位值。假如,要表示的字符,其码位值为 413 (对应填制为 00110011101 ),其表示就如下:\n![[assets/pasted image 20230526090817.png|550]]\n3 个字节\n如果第一个字节的最高位是 1110,那么第 2 和第 3 个字节的最高位是 10 ,如下:\n![[assets/pasted image 20230526090831.png|550]]\n4 个字节\n原理同上,只是第一个字节的最高位是 11110 ,如下 :\n![[assets/pasted image 20230526090843.png|525]]\n6 个字节\n![[assets/pasted image 20230526090857.png|525]]\n不同字节对应的码位范围如下图,左侧 bits 栏表示用于表示码位的 bit 数,如 4 个字节,其中有 21 位用于表示码位,即上图中的蓝色部分 。\n![[assets/pasted image 20230526090908.png]]\n不难看出,utf-8 的产生是循序渐进的, 其拥有很高的灵活性,而且可以进行扩展,能够表示的字符范围很大。\n中文字符的 unicode 分布 中文字符在 unicode 中的分布范围较广,主要集中在以下几个区域:\n类别 范围 描述 示例 基本汉字(常用字) \\u4e00 到 \\u9fff 包含了大部分常用的中文字符 \u0026quot;一\u0026quot; (\\u4e00), \u0026quot;龟\u0026quot; (\\u9f9f) 扩展汉字(补充字) \\u3400 到 \\u4dbf 包含了一些不常用的汉字或古汉字 \u0026quot;㐀\u0026quot; (\\u3400), \u0026quot;䶵\u0026quot; (\\u4dbf) 扩展汉字(扩展a区) \\u20000 到 \\u2a6df 包含了更多的罕见汉字 \u0026quot;𠀀\u0026quot; (\\u20000), \u0026quot;𪚠\u0026quot; (\\u2a6df) 扩展汉字(扩展b区) \\u2a700 到 \\u2b73f 包含了更多的罕见汉字和古汉字 \u0026quot;𪜀\u0026quot; (\\u2a700), \u0026quot;𫝀\u0026quot; (\\u2b73f) 扩展汉字(扩展c区) \\u2b740 到 \\u2b81f 包含了更多的罕见汉字和古汉字 \u0026quot;𫝁\u0026quot; (\\u2b740), \u0026quot;𫠠\u0026quot; (\\u2b81f) 扩展汉字(扩展d区) \\u2b820 到 \\u2ceaf 包含了更多的罕见汉字和古汉字 \u0026quot;𫠡\u0026quot; (\\u2b820), \u0026quot;\u0026quot; (\\u2ceaf) 扩展汉字(扩展e区) \\u2ceb0 到 \\u2ebef 包含了更多的罕见汉字和古汉字 \u0026quot;𬺰\u0026quot; (\\u2ceb0), \u0026quot;𮯠\u0026quot; (\\u2ebef) 扩展汉字(扩展f区) \\u2ceb0 到 \\u2ebef 包含了更多的罕见汉字和古汉字 \u0026quot;𬺰\u0026quot; (\\u2ceb0), \u0026quot;𮯠\u0026quot; (\\u2ebef) 兼容汉字 \\uf900 到 \\ufaff 包含了一些兼容汉字,通常用于与其他字符集(如 gbk)的兼容 \u0026quot;豈\u0026quot; (\\uf900), \u0026quot;\u0026quot; (\\ufaff) 其他汉字 \\u3007, \\u3005, \\u3006 包含了一些特殊的汉字或符号 \u0026quot;〇\u0026quot; (\\u3007), \u0026quot;々\u0026quot; (\\u3005) 如果需要判断一个字符是否为中文字符,可以检查其 unicode 编码是否在上述范围内。\n1 2 3 4 5 6 7 8 9 10 11 def is_chinese_char(char): code = ord(char) return (0x4e00 \u0026lt;= code \u0026lt;= 0x9fff) or \\ (0x3400 \u0026lt;= code \u0026lt;= 0x4dbf) or \\ (0x20000 \u0026lt;= code \u0026lt;= 0x2a6df) or \\ (0x2a700 \u0026lt;= code \u0026lt;= 0x2b73f) or \\ (0xf900 \u0026lt;= code \u0026lt;= 0xfaff) # 测试 print(is_chinese_char(\u0026#39;中\u0026#39;)) # true print(is_chinese_char(\u0026#39;a\u0026#39;)) # false 对于单个字符的编码,python提供了 ord() 函数获取字符的整数表示,chr() 函数把编码转换为对应的字符。\n🪧释义:\nord ➭ ordinal 序数,表示字符在 unicode 编码中的序号(即编码值); chr ➭ character 字符,表示根据 unicode 编码值返回对应的字符。 结语 一切都是在发展的,一切都是在改善的,有时候,只要一点奇思妙想,就会让世界变得更加美好。\n参考链接 https://blog.csdn.net/weixin_44198965/article/details/93125017 https://zhuanlan.zhihu.com/p/260192496 https://www.runoob.com/w3cnote/charset-encoding.html https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896 https://blog.csdn.net/whahu1989/article/details/118314154 ","date":"2023-05-26","permalink":"https://aituyaa.com/%E5%AD%97%E7%AC%A6%E9%9B%86%E5%92%8C%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81/","summary":"\u003cp\u003ei.e. Charset and Character Encoding\u003c/p\u003e\n\u003cp\u003e字符是什么?字母、汉字、标点符号、控制字符、假名……\u003c/p\u003e\n\u003cp\u003e计算机中储存的信息都是二进制数表示的,我们在屏幕上看到的英文、汉字等字符都是二进制转换之后的结果。按照何种规则将字符存储在计算机中,如 \u003ccode\u003ea\u003c/code\u003e 用什么表示,称为“编码”;反之,将存储在计算机中的二进制数解析显示出来,称为“解码”。\u003c/p\u003e","title":"字符集和字符编码"},]
[{"content":" 我并不喜欢王垠这个人,甚至有些反感,但他的某些博文我很喜欢,也因此而受益,emm…\ntip. 非原文摘录,各别语句进行了删减和改动,建议看原文(点击章节标题)。\nblog essays 什么是“对用户友好” 什么是“对用户友好”\n![[assets/pasted image 20230526105114.png|150]]\nany intelligent fool can make things bigger, more complex, and more violent. it takes a touch of genius - and a lot of courage - to\u0026gt; move in the opposite direction.\n– albert einstein\n任何聪明的傻瓜都能把事情做得更大、更复杂、更暴力,而在相反的方向上前进则需要一点天才和很大的勇气。\n“对用户不友好”的背后,其实是程序设计的不合理使得它们 缺少抽象 ,而不是用户的问题。\n:: 这点确实,比如,我就总是搞不清楚冰箱中的致冷程度数字、洗衣机面板上的各种设置开关……\n如何对用户更加友好呢? 统一、抽象!\ngtf - great teacher friedman gtf - great teacher friedman\n程序语言的研究者们往往追逐一些“新概念”,却未能想到很多这些新概念早在几十年前就被 friedman 想到了。\n知识的深度是无止境的。\nfriedman 研究一个东西的时候总是全身心的投入,执着的热爱。\n在 friedman 的课上,我利用它们(如 closure 、cps 等概念)来完成有实际意义的目标,才真正的体会到这些概念的内涵和价值。\n一个例子就是课程进入到没几个星期的时候,我们开始 写解释器 来执行简单的 scheme 程序。然后我们把这个解释器 进行 cps 变换 ,引入全局变量作为 “寄存器” (register),把 cps 产生的 continuation 转换成 数据结构(也就是堆栈) 。最后我们得到的是一个 抽象机 (abstract machine),而这在本质上相当于一个真实机器里的中央处理器(cpu)或者虚拟机(比如 jvm)。所以我们其实从无到有,“发明”了 cpu!从这里,我才真正的理解到寄存器,堆栈等的本质,以及我们为什么需要它们。我才真正的明白了,冯诺依曼体系构架为什么要设计成这个样子。后来他让我们去看一篇他的好朋友 olivier danvy 的论文,讲述如何从各种不同的解释器经过 cps 变换得出不同种类的抽象机模型。这是我第一次感觉到程序语言的理论对于现实世界的巨大威力,也让我理解到, 机器并不是计算的本质 。机器可以用任何可行的技术实现,比如集成电路,激光,分子,dna…… 但是无论用什么作为机器的材料, 我们所要表达的语义,也就是计算的本质,却是不变的。\n当然, 重新发明 东西并不会给我带来论文发表,但是它却给我带来了更重要的东西,这就是独立的思考能力。一旦一个东西被你“想”出来,而不是从别人那里 “学”过来,那么你就知道这个想法是如何产生的。这比起直接学会这个想法要有用很多,因为你知道这里面所有的细节和犯过的错误。而最重要的,其实是由此得到的直觉。如果直接去看别人的书或者论文,你就很难得到这种直觉,因为一般人写论文都会把直觉埋藏在一堆符号公式之下,让你看不到背后的真实想法。如果得到了直觉,下一次遇到类似的问题,你就有可能很快的利用已有的直觉来解决新的问题。\n什么是语义学 什么是语义学\n一个程序的“语义” 通常是由另一个程序(“解释器”)决定的。程序只是一个数据结构,通常表示为语法树(abstract syntax tree)或者指令序列。 这个数据结构本身其实没有意义,是解释器让它产生了意义,对同一个程序可以有不同的解释。\n解释器接受一个“程序”(program),输出一个“值”(value)。这个所谓的“值”可以具有非常广泛的含义。它可能是一个整数,一个字符串,也有可能是更加奇妙的东西。\ncpu 其实也是一个解释器,它的输入是以二进制表示的机器指令,输出是一些电信号。人脑也是一个解释器,它的输入是图像或者声音,输出是神经元之间产生的“概念”。\n所以“语义学”,基本上就是研究各种解释器。\n解释器的原理其实很简单,但是结构非常精巧微妙,如果你从复杂的语言入手,恐怕永远也学不会。最好的起步方式是写一个基本的 lambda calculus 的解释器。lambda calculus 只有三种元素,却可以表达所有程序语言的复杂结构。\n专门讲语义的书很少,现在推荐一本我觉得深入浅出的:《programming languages and lambda calculi》。只需要看完前半部分(part i 和 ii,100 来页)就可以了。这书好在什么地方呢?它是从非常简单的布尔表达式(而不是 lambda calculus)开始讲解 什么是递归定义,什么是解释,什么是 church-rosser,什么是上下文 (evaluation context) 。在让你理解了这种简单语言的语义,有了足够的信心之后,才告诉你更多的东西。比如 lambda calculus 和 cek,secd 等抽象机 (abstract machine)。理解了这些概念之后,你就会发现所有的程序语言都可以比较容易的理解了。\n怎样写一个解释器 怎样写一个解释器\n待细读……\n解密设计模式 解密设计模式\n有些人问我,你说学习操作系统的最好办法是学习程序设计。那我们是不是应该学习一些“设计模式”(design patterns)?\n总的来说,如果光从字面上讲,程序里确实是有一些“模式”可以发掘的。因为你总是可以借鉴以前的经验,用来构造新的程序。你可以把这种经验叫做“模式”。\n可是自从《设计模式》(通常叫做 gof,“gang of four”,“四人帮”)这本书在 1994 年发表以来,“设计模式”这个词有了新的,扭曲的含义。它变成了一种教条,带来了公司里程序的严重复杂化以及效率低下。\n:: 教条主义不能有!当然,gof 还是值得读的!\n照搬模式东拼西凑,而不能抓住事物的本质,没有“灵感”,其实是设计不出好东西的。\npeter norvig 在 1998 年就做了一个演讲,指出在“动态语言”里面,gof 的 20 几个模式,其中绝大部分都“透明”了。也就是说,你根本感觉不到它们的存在。\n谈 linux, windows 和 mac 谈 linux, windows 和 mac\n这段时间受到很多人的来信。他们看了我很早以前写的推崇 linux 的文章,想知道如何“抛弃 windows,学习 linux”。天知道他们在哪里找到那么老的文章,真是好事不出门…… 我觉得我有责任消除我以前的文章对人的误导,洗清我这个“linux 狂热分子”的恶名。\n:: emm\u0026hellip; 我也受这篇文章影响过……\n学习操作系统最好的办法是 学会(真正的)程序设计思想 ,而不是去“学习”各种古怪的工具。所有操作系统,数据库,internet,以至于 web 的设计思想(和缺陷),几乎都能用程序语言的思想简单的解释。\n一个好的工具,应该只有少数几条需要记忆的规则,就像象棋一样。\n:: 这里其实是一个悖论,工具实现的其实是心中所想,而如果你要进行精细化的操作,就很难仅用很简单的规则就可以。\n有些人鄙视图形界面,鄙视 ide,鄙视含有垃圾回收的语言(比如 java),鄙视一切“容易”的东西。他们却不知道,把自己沉浸在别人设计的繁复的规则中,是始终无法成为大师的。\n容易的东西不一定是坏的,而困难的东西也不一定是好的。\n学习计算机(或者任何其它工具),应该“只选对的,不选难的”。记忆一堆的命令,乌七八糟的工具用法,最后脑子里什么也不会留下。 学习“原理性”的东西,才是永远不会过时的。\n谈语法 谈语法\n![[assets/pasted image 20230526105411.png]]\n使用和研究过这么多程序语言之后,我觉得几乎不包含多余功能的语言,只有一个: scheme。\n我觉得 scheme (lisp) 的基于“s 表达式”(s-expression)的语法,是世界上最完美的设计。为什么我喜欢这样一个“全是括号,前缀表达式”的语言呢?这是出于对语言结构本质的考虑。\n其实,我觉得语法是完全不应该存在的东西。即使存在,也应该非常的简单。\n语法其实只是对语言的本质结构,“抽象语法树”(abstract syntax tree,ast)的一种编码。 一个良好的编码,应该极度简单,不引起歧义,而且应该容易解码。在程序语言里,这个“解码”的过程叫做“语法分析”(parse)。\n为什么我们却又需要语法呢?\n因为受到现有工具(操作系统,文本编辑器)的限制,到目前为止,几乎所有语言的程序都是用字符串的形式存放在文件里的。为了 让字符串能够表示“树”这种结构,人们才给程序语言设计了“语法”这种东西。\n但是人们喜欢耍小聪明,在有了基本的语法之后,他们开始在这上面大做文章,使得简单的问题变得复杂……\n:: 语法糖?但有时候真的便捷易用啊……\n最老的是 fortran 的程序,最早的时候都是用打孔机打在卡片上的,所以它其实是几乎没有语法可言的。\n:: lisp (scheme 的前身)是世界上第二老的程序语言。\n![[assets/pasted image 20230526105506.png]]\n自己想一下,如果要表达一颗“树”,最简单的编码方式是什么?就是用括号把每个节点的“数据”和“子节点”都括起来放在一起。lisp 的设计者们就是这样想的。他们把这种完全用括号括起来的表达式,叫做“s 表达式”(s 代表 “symbolic”)。这貌似很“粗糙”的设计,甚至根本谈不上“设计”。奇怪的是,在用过一段时间之后,他们发现自己已经爱上了这个东西,再也不想设计更加复杂的语法。于是 s 表达式就沿用至今。\n首先,把所有的结构都用括号括起来,轻松地避免了别的语言里面可能发生的“歧义”。程序员不再需要记忆任何“运算符优先级”。\n其次,把“操作符”全都放在表达式的最前面,使得基本算术操作和函数调用,在语法上发生 完美的统一 ,而且使得程序员可以使用几乎任何符号作为函数名。\n:: 拓展阅读“前缀表达式”和“中缀表达式”相关知识。\n在其他的语言里,函数调用看起来像这个样子: f(1) ,而算术操作看起来是这样: 1+2 。在 lisp 里面,函数调用看起来是这样 (f 1) ,而算术操作看起来也是这样 (+ 1 2) 。你发现有什么共同点吗?那就是 f 和 + 在位置上的对应。实际上,加法在本质也是一个函数。这样做的好处,不但是突出了加法的这一本质,而且它让人可以用跟定义函数一模一样的方式,来定义“运算符”!这比起 c++ 的“运算符重载”强大很多,却又极其简单。\nlisp 的很多其它的设计,比如“垃圾回收”,后来被很多现代语言(比如 java)所借鉴。可是人们遗漏了一个很重要的东西:lisp 的语法,其实才是世界上最好的语法。\n:: 我也感觉 s-expression 很好用!\n程序语言的常见设计错误 - \u0026gt; 片面追求短小 程序语言的常见设计错误 - \u0026gt; 片面追求短小\n我的程序的“短小”是建立在 语义明确,概念清晰 的基础上的。在此基础上,我力求去掉冗余的,绕弯子的,混淆的代码,让程序更加直接,更加高效的表达我心中设想的“模型”。这是一种在概念级别的优化,而程序的短小精悍只是它的一种“表象”。\n我的这种短小往往是在 语义和逻辑层面 的,而不是在语法上死抠几行代码。我绝不会为了程序显得短小而让它变得难以理解或者容易出错。\n:: 文中举了两个小例子,这里只做简要概括。\n1.自增减操作\n从理论上讲, 自增减操作本身就是错误的设计 。因为它们把对变量的“读”和“写”两种根本不同的操作,毫无原则的合并在一起。这种对读写操作的混淆不清,带来了非常难以发现的错误。相反,一种等价的,“笨”一点的写法, i = i + 1 ,不但更易理解,而且在逻辑上更加清晰。\n有些人很在乎 i++ 与 ++i 的区别,去追究 i++ 与 ++i 谁的效率更高。这些其实都是徒劳的。比如,i++ 与 ++i 的效率差别,其实来自于早期 c 编译器的愚蠢。\n:: 以下是正确讲解 i++ 的办法!!!它曾是多少人的痛……\n因为 i++ 需要在增加之后返回 i 原来的值 ,所以它其实被编译为:\n(tmp = i, i = i + 1, tmp) 但是在 for (int i = 0; i \u0026lt; max; i++) 中,其实你并不需要在 i++ 之后得到它自增前的值。所以有人说,在这里应该用 ++i 而不是 i++ ,否则你就会浪费一次对中间变量 tmp 的赋值。\n而其实呢,一个良好设计的编译器应该在两种情况下都生成相同的代码。\n# 在 i++ 的情况,代码其实先被转化为\rfor (int i = 0; i \u0026lt; max; (tmp = i, i = i + 1, tmp))\r# ↓↓↓\r# 由于 tmp 这个临时变量从来没被用过,\r# 所以它会被编译器的“dead code elimination”消去,\r# 编译器最后实际上得到了\rfor (int i = 0; i \u0026lt; max; i = i + 1) 所以,“精通”这些细微的问题,并不能让你成为一个好的程序员。很多人所认为的高明的技巧,经常都是因为早期系统设计的缺陷所致。一旦这些系统被改进,这些技巧就没什么用处了。\n真正正确的做法其实是:完全不使用自增减操作,因为它们本来就是错误的设计。\n2.赋值语句返回值\n在几乎所有像 c,c++,java 的语言里,赋值语句都可以被作为值。\ny = 0 不应该具有一个值。它的作用应该是“赋值”这种“动作”,而不应该具有任何“值”。即使牵强一点硬说它有值,它的值也应该是 void 。这样一来 x = y = 0 和 if (y = 0) 就会因为“类型不匹配”而被编译器拒绝接受,从而避免了可能出现的错误。\n:: if (y == 0) 写成 if (y = 0) , emm……\n“解决问题”与“消灭问题” “解决问题”与“消灭问题”\n如果你仔细观察就会发现,很多“难题”,其实是“人造”出来的,而不是“必然”的。它们的存在,往往是由于一些早期的“设计错误”。\n如果我们转换一下思路,或者改变一下“设计”,很多问题就可以不解自消。这就是我所谓的“消灭问题”的能力。\n所以,在解决问题之前,我们应该先问自己三个问题:\n这问题是否真的“存在”? 如果解决了这个问题,会给我和他人在合理的时间之内带来什么实际的好处? 这问题是否可以在简单的改变某些“设计”或者“思路”之后,不复存在? :: 本质上是认真思考一下问题产生的原因!\nlisp 已死,lisp 万岁! lisp 已死,lisp 万岁!\n1.lisp 的优点:\nlisp 的语法是世界上最精炼,最美观,也是语法分析起来最高效的语法。这是 lisp 独一无二的,其他语言都没有的优点。有些人喜欢设计看起来很炫的语法,其实都是自找麻烦。为什么这么说呢,请参考这篇《谈语法》 。\nlisp 是第一个可以 在程序的任何位置定义函数 ,并且可以 把函数作为值传递 的语言。这样的设计使得它的表达能力非常强大。这种理念被 python,javascript,ruby 等语言所借鉴。\nlisp 有世界上最强大的宏系统(macro system)。这种宏系统的表达力几乎达到了理论所允许的极限。如果你只见过 c 语言的“宏”,那我可以告诉你它是完全没法跟 lisp 的宏系统相提并论的。\nlisp 是世界上第一个使用垃圾回收( garbage collection)的语言。这种超前的理念,后来被 java,c# 等语言借鉴。\n想不到吧,现代语言的很多优点,其实都是来自于 lisp — 世界上第二古老的程序语言。所以有人才会说,每一种现代语言都在朝着 lisp 的方向“进化”。\n如果你相信了这话,也许就会疑惑,为什么 lisp 今天没有成为主流?为什么 lisp machine 会被 unix 打败?其实除了商业原因之外,还有技术上的问题。\n2.dynamic scoping\n早期的 lisp 其实普遍存在一个非常严重的问题:它使用 dynamic scoping 。\n所谓 dynamic scoping 就是说,如果你的函数定义里面有 “自由变量” ,那么这个自由变量的值,会随着函数的“调用位置”的不同而发生变化。\n1 2 3 (setq f (let ((x 1)) (lambda (y) (* x y)))) 这里的 x 对于函数 (lambda (y) (* x y)) 来说就是个“自由变量”(free variable),因为它不是它的参数。\n……\ntips: 详细论证过程就参考原文……\n话说回来,为什么早期的 lisp 会使用 dynamic scoping 呢?\n原来,emacs lisp 直接把函数定义处的 s 表达式 '(lambda (y) (* x y)) 作为了函数的“值”!\n如果你在 emacs 里面显示 f 的值,它会打印出:\n\u0026#39;(lambda (y) (* x y)) 这说明 f 的值其实是一个 s 表达式,而不是像 scheme 一样的“闭包”(closure)。\n简单倒是简单,麻烦事接着就来了。调用 f 的时候,比如 (funcall f 2) ,y 的值当然来自参数 2,可是 x 的值是多少呢?答案是:不知道!不知道怎么办?到“外层环境”去找呗,看到哪个就用哪个,看不到就报错。所以你就看到了之前出现的现象,函数的行为随着一个完全无关的变量而变化。如果你单独调用 (funcall f 2) 就会因为找不到 x 的值而出错。\n那么正确的实现函数的做法是什么呢?是制造“闭包”(closure)!这也就是 scheme,common lisp 以及 python,c# 的做法。\n在 函数定义被解释或者编译 的时候,当时的自由变量(比如 x)的值,会跟函数的代码绑在一起,被放进一种叫做“闭包”的结构里。比如上面的函数,就可以表示成这个样子: (closure '(lambda (y) (* x y)) '((x . 1))) 。\n在这里我用 (closure …) 表示一个“结构”(就像 c 语言的 struct)。它的第一个部分,是这个函数的定义。第二个部分是 '((x . 1)) ,它是一个 “环境” ,其实就是一个从变量到值的映射(map)。利用这个映射,我们记住函数定义处的那个 x 的值,而不是在调用的时候才去瞎找。\n3.lexical scoping\n与 dynamic scoping 相对的就是“lexical scoping”。我刚才告诉你的闭包,就是 lexical scoping 的实现方法。\n你也许发现了,lisp 其实不是一种语言,而是很多种语言。这些被人叫做“lisp 家族”的语言,其实共同点只是它们的“语法”:它们都是基于 s 表达式。如果你因此对它们同样赞美的话,那么你赞美的其实只是 s 表达式,而不是这些语言本身。\n因为 一个语言的本质应该是由它的语义决定的,而跟语法没有很大关系。 你甚至可以给同一种语言设计多种不同的语法,而不改变这语言的本质。\nchez scheme 的传说 chez scheme 的传说\n在我看来,早期 lisp 编译器出现的主要问题,其实在于对编译的本质的理解,以及编译器与解释器的根本区别。\n解释器之所以大部分时候比编译器慢,是因为解释器“问太多的问题”。 每当看到一个构造,解释器就会问:“这是一个整数吗?”“这是一个字符串吗?”“这是一个函数吗?”…… 然后根据问题的结果进行不同的处理。这些问题,在编译器的理论里面叫做 “解释开销” (interpretive overhead)。\n编译的本质,其实就是在程序运行之前进行“静态分析”,试图一劳永逸的回答这些问题。\n早期的 lisp 编译器,以及现在的很多 scheme 编译器出现的问题其实在于,它们并没有干净的消除这些问题,甚至根本没有消除这些问题。\n编译的过程,就是将输入程序经过一系列的变换之后,转化为机器代码。\n什么是“脚本语言” 什么是“脚本语言”\n其实“脚本语言”与“非脚本语言”并没有语义上,或者执行方式上的区别。它们的区别只在于它们设计的初衷:脚本语言的设计,往往是作为一种临时的“补丁”。相反,“非脚本”的通用程序语言,往往由经过严格训练的专家甚至一个小组的专家设计,它们从一开头就考虑到了“通用性”,以及在大型工程中的可靠性和可扩展性。\n“脚本”这个概念是如何产生的?\n使用 unix 系统的人都会敲入一些命令,而命令貌似都是“一次性”或者“可抛弃”的。然而不久,人们就发现这些命令其实并不是那么的“一次性”,自己其实一直在重复的敲入类似的命令,所以有人就发明了“脚本”这东西。它的 设计初衷是“批量式”的执行命令 ,你在一个文件里把命令都写进去,然后执行这个文件。可是不久人们就发现,这些命令行其实可以用更加聪明的方法构造,比如定义一些变量,或者根据系统类型的不同执行不同的命令。于是,人们为这脚本语言加入了变量,条件语句,数组,等等构造。“脚本语言”就这样产生了。\nscheme 编程环境的设置 scheme 编程环境的设置\n:: 学习 sheme 的时候可以参考下……\n关于语言的思考 关于语言的思考\n怎么说呢,我觉得每个程序员的生命中都至少应该有几个月在静心学习 haskell。学会 haskell 就像吃几天素食一样。每天吃素食显然会缺乏全面的营养,但是每天都吃荤的话,你恐怕就永远意识不到身体里的毒素有多严重。\n我今天想说其实就是,没有任何一种语言值得你用毕生的精力去“精通”它。“精通”其实代表着“脑残”——你成为了一个高效的机器,而不是一个有自己头脑的人。你必须对每种语言都带有一定的怀疑态度,而不是完全的拥抱它。 每个人都应该学习多种语言 ,这样才不至于让自己的思想受到单一语言的约束,而没法接受新的,更加先进的思想。这就像每个人都应该学会至少一门外语一样,否则你就深陷于自己民族的思维方式。有时候这种民族传统的思想会让你深陷无须有的痛苦却无法自拔。\n原因与证明 原因与证明\n一个东西具有如此的性质,并不是因为你证明了它。这性质是它天生就有的,不管你是否能证明它。\n大部分的教育过分的重视了“证明”,却忽略了比证明更重要的东西——“原因”。\n原因往往比证明来得更加简单,更加深刻,但却更难发现。 对于一个事实往往有多种多样的证明,然而导致这个事实的原因却往往只有一个。如果你只知道证明却不知道原因,那你往往就被囚禁于别人制造的理论里面,无法自拔。你能证明一个事物具有某种特性,然而你却没有能力改变它。你无法对它加入新的,好的特性,也无法去掉一个不好的特性。你也无法发明新的理论。有能力发明新的事物和理论的人,他们往往不仅知道“证明”,而且知道“原因”。\n古人说的“知其然”与“知其所以然”的区别,也就是同样的道理吧。\n丘奇和图灵 丘奇和图灵\n丘奇代表了“逻辑”和“语言”,而图灵代表着“物理”和“机器”。完全投靠丘奇,或者完全投靠图灵,貌似都是错误的做法。\n据我的经验,丘奇的理论让很多事情变得简单,而图灵的机器却过度的复杂。丘奇所发明的 lambda calculus 以及后续的工作,是几乎一切程序语言的理论基础。\n图灵机永远的停留在了理论的领域,绝大多数被用在“计算理论”(theory of computation)中。\n计算理论其实包括两个主要概念: “可计算性理论”(computability) 和 “复杂度理论”(complexity) 。\n这两个概念在通常的计算理论书籍(比如 sipser 的经典教材)里,都是用图灵机来叙述的。其实几乎所有计算理论的原理,都可以用 lambda calculus ,或者程序语言和解释器的原理来描述。\n所谓“通用图灵机”(universal turing machine),其实就是一个 可以解释自己的解释器 ,叫做“元解释器”(meta-circular interpreter)。\n然而我的“元解释器”却是基于 lambda calculus 的,所以我后来发现了一种方法,可以完全的用 lambda calculus 来解释计算理论里面几乎所有的定理。\n在我的头脑里面并存着丘奇和图灵的影子。我觉得丘奇的 lambda calculus 是比图灵机简单而强大的描述工具,然而我却又感染到了图灵对于“物理”和“机器”的执着。我觉得逻辑学家们对 lambda calculus 的解释过于复杂,而通过把它理解为物理的“电路元件”,让我对 lambda calculus 做出了更加简单的解释,把它与“现实世界”联系在了一起。\n所以到最后,丘奇和图灵这两种看似矛盾的思想,在我的脑海里得到了和谐的统一。这些精髓的思想帮助我解决了许多的问题。\n我和权威的故事 我和权威的故事\ndonald knuth\n![[assets/pasted image 20230526105807.png|400]]\n有一句话说得好:“跟真正的大师学习,而不是跟他们的徒弟。” 如果你真的要学一个算法,就应该直接去读那算法的发明者的论文,而不是转述过来的“二手知识”。二手的知识往往把发明者原来的动机和思路都给去掉了,只留下苍白无味,没有什么启发意义的“最后结果”。\n我跟 knuth 的最后一次“联系”是在我就要离开清华的时候。我从 email 告诉他我觉得中国的研究环境太浮躁了,不是做学问的好地方,想求点建议。结果他回纸信说:“可我为什么看到中国学者做出那么多杰出的研究?计算机科学不是每个人都可以做的。如果你试了这么久还不行,那说明你注定不是干这行的料。”还好,我从来没有相信他的这段话,我下定了决心要证明这是错的。多年的努力还真没有白费,今天我可以放心的说,knuth 你错了,因为我已经在你引以为豪的多个方面超过了你。\n:: emm …… 王垠有有点‘罗味十足’,典型的‘你不尊重我’……\nunix\n所谓的“unix 哲学”,也就是进程间通信主要依靠无结构字符串,造成了一大批过度复杂,毛病众多的工具和语言的产生: awk,sed,perl,……\nlisp 程序员早就明白这个道理,所以他们尽一切可能避免使用字符串。他们设计了 s 表达式,用于结构化的传输数据。实际上 s 表达式不是“设计”出来的,它是每个人都应该首先想到的,最简单的可以 表示树结构 的编码方法。lisp 的设计原则里面有一条就是:do not encode。它的意思是,尽量不要把有用的数据编码放进字符串。\ngo 语言\n……\ncornell\n……\n图灵奖\n说到这里应该有人会问这个问题,我是不是也属于那种没找到导师走投无路的人。答案是,对的,我确实没有在 cornell 找到可以做我导师的人。\n……\n再见了,权威们\n几经颠簸的求学生涯,让我获得了异常强大的力量。我的力量不仅来自于老师们的教诲,而且在于我自己不懈的追求,因为机会只亲睐有准备的头脑。\n王垠指出了现存的许多问题和弊端,也提出了许多改进和设想,然而只到现在也没有一件可用的…… 我倒觉得他应该听 knuth 的,如他现在这样,开班教学也不错。\n程序语言与…… 程序语言与……\n程序语言的设计类似于其它很多东西的设计,有些微妙的地方只有用过更好的设计的人才能明白。\n……\n程序语言与减肥\n我的方法就是一句话:让每天吃进去的热量比消耗的少一些,但是不至于难受,另外适当运动来增加热量的消耗。很显然嘛,根据热力学定律,每天消耗的能量比摄入的多,多出来的部分只能通过分解你身上的物质(脂肪)来产生。\n:: 少吃多消耗,瘦身的不二法门。\n程序员的心理疾病 程序员的心理疾病\n1.无自知之明\n由于程序员的工作最近几年比较容易找,工资还不错,所以很多程序员往往只看到自己的肚脐眼,看不到自己在整个社会里的位置其实并不是那么的关键和重要。很多程序员除了自己会的那点东西,几乎对其它领域和事情完全不感兴趣,看不起其他人……\n:: 这一点确实需要警醒!!!\n2.垃圾当宝贝\n按照 dijkstra 的说法,“软件工程”是穷途末路的领域,因为它的目标是: 如果 我不会写 程序的话,怎么样才 能写出 程序?\n为了达到这个愚蠢的目的,很多人开始兜售各种像减肥药一样的东西。面向对象方法,软件“重用”,设计模式,关系式数据库,nosql,大数据…… 没完没了。\n:: 不赞同!\n3.宗教斗争\n为什么有人说在软件行业里需要不停地“学习”,因为不断地有人为了制造新的理念而制造新的理念。\n![[assets/pasted image 20230526110015.png]]\n……\n4. 以语言取人\n很多程序员都以自己会用最近流行的一些新语言为豪,以为有了它们自己就成了更好的程序员。他们看不到,用新的语言并不能让他们成为更好的程序员。其实最厉害的程序员无论用什么语言都能写出很好的代码。在他们的头脑里其实只有一种很简单的语言,他们首先用这种语言把 问题建模 出来,然后根据实际需要“翻译”成最后的代码。这种在头脑里的建模过程的价值,是很难用他最后用语言的优劣来衡量的。\n……\n一个对 dijkstra 的采访视频 一个对 dijkstra 的采访视频\n(可以访问 youtube 或者从源地址下载 mpeg1,300m)\n现在看来,任何一个语言里面没有递归函数都是不可思议的事情,然而在 1950~60 年代的时候,居然很少有人知道它有什么用!所以你就发现,所谓的“主流”和“大多数人”一直都是比较愚蠢的。现在,同样的故事发生在 lambda 身上。多年以后,没有 lambda 的语言将是不可接受的。\n在这里只摘录他提到的几个要点:\n软件测试可以确定软件里有 bug,但却不可能用来确定它们没有 bug。\n程序的优雅性不是可以或缺的奢侈品,而是决定成功还是失败的一个要素。优雅并不是一个美学的问题,也不是一个时尚品味的问题,优雅能够被翻译成可行的技术。牛津字典对 elegant 的解释是: pleasingly ingenious and simple。如果你的程序真的优雅,那么它就会容易管理。第一是因为它比其它的方案都要短,第二是因为它的组件都可以被换成另外的方案而不会影响其它的部分。很奇怪的是,最优雅的程序往往也是最高效的。\n为什么这么少的人追求优雅?这就是现实。如果说优雅也有缺点的话,那就是 你需要艰巨的工作才能得到它,需要良好的教育才能欣赏它 。\n当没有计算机的时候,编程不是问题。当有了比较弱的计算机时,编程成了中等程度的问题。现在我们有了巨大的计算机,编程就成了巨大的问题。\n我最开头编程的日子跟现在很不一样,因为我是给一个还没有造出来的计算机写程序。造那台机器的人还没有完工,我在同样的时间给它做程序,所以没有办法测试我的代码。于是我发现自己做的东西必须要能放进自己的脑子里。\n我的母亲是一个优秀的数学家。有一次我问她几何难不难,她说一点也不难,只要你用“心”来理解所有的公式。如果你需要超过 5 行公式,那么你就走错路了。\n:: 凡事,只要用‘心’,就没有解决不了的。\n学术腐败是历史的必然 学术腐败是历史的必然\n学术腐败是历史的必然,是人类历史的发展趋势和技术进步的结果。\n为什么这么说呢?\n首先想想在资本主义社会里人靠什么过活?钱 一般人怎么得到钱?工作 谁是人最大的工作竞争对手?机器,电脑,互联网,机器人…… 自己的工作被机器取代了怎么办?寻找机器干不了的工作! 什么是机器仍然干不了,而且不久的将来也干不了的工作?搞研究! 搞研究是为了什么?制造更高效更智能的机器! 然后你就明白了,这是一个让人类越来越痛苦的怪圈。\n关系式模型的实质 关系式模型的实质\n……\n谈创新 谈创新\n有人告诉我,我所说的很多事情只是在已有的事物上面挑出毛病来,那不能引起真正的“创新”。\n什么是创新?创新真的那么重要吗,它的意义何在?\n世界上并不缺少创新,而是创新过剩了!大量的所谓“创新”,让人们的生活变得纷繁复杂,导致他们需要记住更多事物的用法,而无法专注于利用已有的设施,最大限度的享受生活的乐趣。\n最缺乏创造力的人,往往是最爱标榜创新的。\n创新往往也是与良好的设计理念背道而驰的。一个好的设计,总是力求减少“新”的感觉,而着重于让整个设计浑然一体,天衣无缝,用起来顺手。最好的设计就是让设计的目标消失掉,或者感觉不到它的存在。\n:: 这里王垠有些偷换概念了……\n……\n美国和中国\n在这里提到美国的优秀设计,并不是说我更喜欢美国。每次提到这些,总有朋友感觉不平,仿佛觉得我是“美帝的走狗”一样。 我其实对任何国家都没有特别的感情和归属感,我的感情只针对个人,而不是国家。实际上,我认为国家这种东西是不必要存在的。 美国人对我显然没有很多中国人对我好,然而 技术和设计是没有国界的,好的东西不学就等于永远落后。很多中国人喜欢用所谓的“民族自豪感”来代替理性的思考,看不到自己的问题。中国为什么到现在还属于第三世界国家,恐怕就有这里面的原因。没有用心,就不能提高。中国的经济发展了,国家的总资产可以说已经很多了,然而有很多东西不是钱就可以买来的,它需要用心设计。看,我在美国受了这么多的苦和委屈才学会了这些,如果你们不理解消化,那多可惜啊。\n:: 你的朋友说的是对的!!!都 2021 了,还持有这种思想(“技术和设计是没有国界的”)的人,在我看来不是坏透了,就是个纯傻 b 🤡 ……\n一味的试图创新而不仔细思考,是人们的生活由于各种“新事物”而变得复杂的重要原因。\n只有你能从已有的东西里面看到实质的问题,你才有可能达到天衣无缝的设计。设计不需要全新的,它必须最大限度的让人可以方便的生活,而不需要记忆很多不必要的指令。否则如果你不吸取历史的教训,做出所谓“全新”的设计,那么你很有可能不是解决了问题,而是制造了问题。我觉得有一句话说得好,忘记历史就是毁灭未来。\n所谓“人为错误” 所谓“人为错误”\n在我看来,整个软件行业基本就是建立在一堆堆的设计失误之上。做程序员如此困难和辛苦,大部分原因就是因为软件系统里面积累了大量前人的设计失误,所以我们需要做大量的工作来弥补或者绕过。\n然而一般程序员都没有意识到这里面的设计错误,知道了也不敢指出来,他们反而喜欢显示自己死记硬背得住这些稀奇古怪的规则。这就导致了软件行业的“皇帝的新装现象”——没有人敢说工具的设计有毛病,因为如果你说出来,别人就会认为你在抱怨,那你不是经验不足,就是能力不行。\n我体会很深的一个例子就是 git 版本控制工具。有人很把这种东西当回事,引以为豪记得住如何用一些稀奇古怪的 git 命令(比如 git rebase, git submodule 之类)。好像自己知道了这些就真的是某种专家一样,每当遇到不会用这些命令的人,都在心底默默地鄙视他们。 作为一个比 git 的作者还要高明的程序员,我却发现自己永远无法记住那些命令 。在我看来,这些命令晦涩难懂,很有可能是因为没设计好造成的。因为如果一个东西设计好了,以我的能力是不可能不理解的。可是 linus torvalds 的名气之大,威望之高,有谁敢说:“我就是不会用你设计的破玩意儿!你把我怎么着?\n:: emm…… 如果你高明到让自己并不高明,是不是说明你并不如自己想象的那么高明……\n怎样尊重一个程序员 怎样尊重一个程序员\n1.认识和承认技术领域的历史遗留糟粕\n很多不尊重人现象的起源,都是因为某些人偏执的相信某种技术就是世界上最好的,每个人都必须知道这些东西,否则他就不是一个合格的程序员。\n如果你对计算机科学理解到一定程度,就会发现我们其实仍然生活在计算机的石器时代。特别是软件系统,建立在一堆历史遗留的糟糕设计之上。\n各种蹩脚脑残的操作系统(比如 unix,linux),程序语言(比如 c++,javascript,php,go),数据库,编辑器,版本控制工具,…… 时常困扰着我们,这就是为什么你需要那么多的所谓“经验”和“知识”。\n2.分清精髓知识和表面知识,不要太拿经验当回事\n在任何领域,都只有少数知识是精髓的,另外大部分都是表面的,肤浅的,是从精髓知识衍生出来的。\n精髓知识和表面知识都是有用的,然而它们的分量和重要性却是不一样的。所以必须区分精髓知识和表面知识,不能混为一谈,对待它们的态度应该是不一样的。由于表面知识基本是死的,而且很容易从精髓知识推导衍生出来。我们不应该因为自己知道很多表面知识,就自以为比掌握了精髓知识的人还要强。不应该因为别人不知道某些表面知识,就以为自己高人一等。\n……\n编程的宗派 编程的宗派\n总是有人喜欢争论这类问题,到底是“函数式编程”(fp)好,还是“面向对象编程”(oop)好……\n1.面向对象编程(object-oriented programming)\n如果你看透了表面现象就会发现,其实“面向对象编程”本身没有引入很多新东西。\n所谓“面向对象语言”,就是经典的“过程式语言”(比如 pascal),加上一点抽象能力。所谓“类”和“对象”,基本是过程式语言里面的记录(record,或者叫结构,structure),它 本质其实是一个从名字到数据的“映射表”(map) 。\n你可以用名字从这个表里面提取相应的数据。\n所谓“对象思想”(区别于“面向对象”),实际上就是对这种数据访问方式的进一步抽象。\n“对象思想”的价值,它让你可以通过“间接”(indirection,或者叫做 “抽象” )来改变 point.x 和 point.y 的语义,从而让使用者的代码 完全不用修改 。虽然你的实际数据结构里面 可能没有 x 和 y 这两个成员,但由于 .x 和 .y 可以被重新定义 ,所以你可以通过改变 .x 和 .y 的定义来“模拟”它们。在你使用 point.x 和 point.y 的时候,系统内部其实在运行两片代码(所谓 getter),它们的作用是从 r 和 angle 计算出 x 和 y 的值。这样你的代码就感觉 x 和 y 是实际存在的成员一样,而 其实它们是被临时算出来的 。\n对象思想的价值也就到此为止了。你见过的所谓“面向对象思想”,几乎无一例外可以从这个想法推广出来。\n“对象思想”作为数据访问的方式,是有一定好处的。然而“面向对象”(多了“面向”两个字),就是把这种本来良好的思想东拉西扯,牵强附会,发挥过了头。\n很多面向对象语言号称“所有东西都是对象”(everything is an object), 把所有函数都放进所谓对象里面,叫做“方法”(method),把普通的函数叫做“静态方法”(static method) 。\n实际上呢,就像我之前的例子,只有极少需要抽象的时候,你需要使用内嵌于对象之内,跟数据紧密结合的“方法”。其他的时候,你其实只是想表达数据之间的变换操作,这些完全可以用普通的函数表达,而且这样做更加简单和直接。\n这种把所有函数放进方法的做法是本末倒置的,因为函数并不属于对象。 绝大部分函数是独立于对象的,它们不能被叫做“方法”。强制把所有函数放进它们本来不属于的对象里面,把它们全都作为“方法”,导致了面向对象代码逻辑过度复杂。\n面向对象语言不仅有自身的根本性错误,而且由于面向对象语言的设计者们常常是半路出家,没有受到过严格的语言理论和设计训练却又自命不凡,所以经常搞出另外一些奇葩的东西。比如在 javascript 里面,每个函数同时又可以作为构造函数(constructor),所以每个函数里面都隐含了一个 this 变量,你嵌套多层对象和函数的时候就发现没法访问外层的 this ,非得“bind”一下。python 的变量定义和赋值不分,所以你需要访问全局变量的时候得用 global 关键字,后来又发现如果要访问“中间层”的变量,没有办法了,所以又加了个 nonlocal 关键字……\n有些人问我为什么有些语言设计成那个样子,我只能说,很多语言设计者其实根本不知道自己在干什么。\n2.函数式编程(functional programming)\n有人盲目的相信函数式编程能够奇迹般的解决并发计算的难题,而看不到实质存在的,独立于语言的问题。\n函数式编程当然提供了它自己的价值。函数式编程相对于面向对象最大的价值,莫过于对于函数的正确理解。\n在函数式语言里面,函数是“一类公民”(first-class)。它们可以像 1, 2, “hello”,true,对象…… 之类的“值”一样,在任意位置诞生,通过变量,参数和数据结构传递到其它地方,可以在任何位置被调用。这些是很多过程式语言和面向对象语言做不到的事情。\n很多所谓“面向对象设计模式”(design pattern),都是因为面向对象语言没有 first-class function,所以导致了 每个函数必须被包在一个对象里面才能传递到其它地方 。\n函数式编程的另一个贡献,是它们的类型系统。\n函数式语言对于类型的思维,往往非常的严密。函数式语言的类型系统,往往比面向对象语言来得严密和简单很多,它们可以帮助你对程序进行严密的逻辑推理。然而类型系统一是把双刃剑,如果你对它看得太重,它反而会带来不必要的复杂性和过度工程。\n3.符号必须简单的对世界建模\n在我的心目中其实只有一个概念,它叫做“编程”(programming),它不带有任何附加的限定词(比如“函数式”或者“面向对象”)。我研究的领域称叫做“programming languages”,它研究的内容不局限于某一个语言,也不局限于某一类语言,而是所有的语言。在我的眼里, 所有的语言都不过是各个特性的组合 。所以最近出现的所谓“新语言”,其实不大可能再有什么真正意义上的创新。我不喜欢说“发明一个程序语言”,不喜欢使用“发明”这个词,因为不管你怎么设计一个语言,所有的特性几乎都早已存在于现有的语言里面了。我更喜欢使用“设计”这个词,因为虽然一个语言没有任何新的特性,它却有可能在细节上更加优雅。\n编程最重要的事情,其实是让写出来的符号,能够简单地对实际或者想象出来的“世界”进行建模。\n一个程序员最重要的能力,是直觉地看见符号和现实物体之间的对应关系。不管看起来多么酷的语言或者范式,如果必须绕着弯子才能表达程序员心目中的模型,那么它就不是一个很好的语言或者范式。\n关于建模的另外一个问题是,你心里想的模型,并不一定是最好的,也不一定非得设计成那个样子。\n有些人心里没有一个清晰简单的模型,觉得某些语言“好用”,就因为它们能够对他那种扭曲纷繁的模型进行建模。所以你就跟这种人说不清楚,为什么这个语言不好,因为显然这个语言对他是有用的!\n所谓软件工程 所谓软件工程\n:: 这是一个比较有趣的话题,后续可以了解一下。\n有人把软件工程领域的本质总结为:“how to program if you cannot?”(如果你不会编程,那么你如何编程?)我觉得这句话说得很好,因为我发现软件工程这整个领域,基本就是吹牛扯淡卖“减肥药”的。软件行业的大部分莫名其妙的愚昧行为,很多是由所谓“软件工程专家”发明的。\n打破软件工程幻觉的一个办法,就是实地去看看“专家”们用自己的方法论做出了什么好东西。你会惊奇的发现,这些提出各种新名词的所谓“专家”,几乎都是从不知道什么旮旯里冒出来的民科。他们跟真正的计算机科学家或者高明的程序员没有任何关系,也没有做出过什么有技术含量的东西,他们根本没有资格对别人编程的方式做出指导。这些人做出来少数有点用的东西(比如 junit),其实非常容易,以至于每个初学编程的人都应该做得出来。一个程序员见识需要低到什么程度,才会在乎这种人说的话?\n可世界上就是有这样划算的行当,虽然写不出好的代码,对计算的理解非常肤浅,却可以通过嘴里说说,得到评价别人“代码质量”的权力,占据软件公司的管理层位置。久而久之,别人还以为他们是什么泰斗。你仔细看过提出 design pattern 的“四人帮”(gof),做出过什么有实质价值的东西吗?提出“dry principle”的作者,做出过什么吗?再看看 agile,pair programming,tdd 的提出者?他们其实不懂很多编程,写出文章和书来也是极其肤浅。\n:: 这里不禁要问一句,‘王垠,如果你上面的内容说的都对,那么,你这些年做了些什么呢?’\n\u0026gt; 2023-05-28 11:01\ndry 原则的误区 dry 原则的误区\n简言之,dry(don’t repeat yourself)原则鼓励对代码进行抽象,但是鼓励得过了头。\n1.抽象与可读性的矛盾\n代码的“抽象”和它的“可读性”(直观性),其实是一对矛盾的关系。适度的抽象和避免重复是有好处的,它甚至可以提高代码的可读性,然而如果你尽“一切可能”从代码里提取模板,甚至把一些微不足道的“共同点”也提出来进行“共享”,它就开始有害了。\n这是因为, 模板并不直接显示在“调用”它们的位置 。提取出模板,往往会使得阅读代码时不能一目了然。如果由此带来的直观性损失超过了模板所带来的好处时,你就应该考虑避免抽象了。\n2.抽象的时机问题\n抽象的思想,关键在于“发现两个东西是一样的”。然而很多时候,你开头觉得两个东西是一回事,结果最后发现,它们其实只是肤浅的相似,而本质完全不同。 防止过早抽象 的方法其实很简单,它的名字叫做“等待”。\n谈程序的正确性 谈程序的正确性\n100% 可靠的代码,这是多么完美的理想!然而它并不存在!!!\n1.衡量程序最重要的标准\n许多人其实不明白一个重要的道理: 你得先写出程序,才能开始谈它的正确性 。看一个程序好不好,最重要的标准,是看它能否有效地解决问题,而不是它是否正确。如果你的程序没有解决问题,或者解决了错误的问题,或者虽然解决问题但却非常难用,那么这程序再怎么正确,再怎么可靠,都不是好的程序。\n正确不等于简单,不等于优雅,不等于高效。一个不简单,不优雅,效率低的程序,就算你费尽周折证明了它的正确,它仍然不会很好的工作。\n2.如何提高程序的正确性\n话说回来,虽然程序的正确性相对于解决问题,处于相对次要的地位,然而它确实是不可忽视的。\n如果你深入研究过程序的逻辑推导就会知道,测试和形式化证明的能力都是非常有限的。\n那么提高程序正确性最有效的方法是什么呢?在我看来,最有效的方法莫过于对代码反复琢磨推敲,让它变得简单,直观,直到你一眼就可以看得出它不可能有问题。\n:: 具体如何做呢?\n对 parser 的误解 对 parser 的误解\n1. 什么是 parser\n所谓 parser,一般是指把某种格式的文本(字符串)转换成某种数据结构的过程。\n最常见的 parser,是把程序文本转换成编译器内部的一种叫做“抽象语法树”(ast)的数据结构。也有简单一些的 parser,用于处理 csv,json,xml 之类的格式。\n之所以需要做这种从字符串到数据结构的转换,是因为编译器是无法直接操作 “1+2” 这样的字符串的。实际上, 代码的本质根本就不是字符串,它本来就是一个具有复杂拓扑的数据结构,就像电路一样。“1+2” 这个 字符串只是对这种数据结构的一种“编码” ,就像 zip 或者 jpeg 只是对它们压缩的数据的编码一样。\n这种编码可以方便你把代码存到磁盘上,方便你用文本编辑器来修改它们,然而你必须知道,文本并不是代码本身。 所以从磁盘读取了文本之后,你必须先“解码”,才能方便地操作代码的数据结构。\n对于程序语言,这种解码的动作就叫做 parsing ,用于解码的那段代码就叫做 parser 。\n2.parser 在编译器中的地位\n那么貌似这样说来,parser 是编译器里面很关键的一个部分了?显然,parser 是必不可少的,然而它并不像很多人想象的那么重要。parser 的重要性和技术难度,被很多人严重的夸大了。\n我喜欢把 parser 称为“万里长征的第 0 步”,因为等你 parse 完毕得到了 ast,真正的编译技术才算开始。\n一个编译器包含许多的步骤:语义分析,类型检查/推导,代码优化,机器代码生成,…… 这每个步骤都是在对某种中间数据结构(比如 ast )进行分析或者转化,它们完全不需要知道代码的字符串形式。也就是说,一旦代码通过了 parser,在后面的编译过程里,你就可以完全忘记 parser 的存在。\nparser 虽然必不可少,然而它比起编译器里面最重要的过程,是处于一种辅助性的地位。\nast 数据结构才是程序本身,而程序的文本只是这种数据结构的一种编码形式。\n3.parser 技术发展的误区\n很多人盲目地设计复杂的语法,然后用越来越复杂的 parser 技术去 parse 它们,这就是 parser 技术仍然在发展的原因。\n制造复杂难懂的语法,没有什么真正的好处。不但给程序员的学习造成了不必要的困难,让代码难以理解,而且也给 parser 的作者带来了严重的挑战。\n4.编译原理课程的误导\n一般大学里上编译原理课,都是捧着一本大部头的“龙书”或者“虎书”,花掉一个学期 1/3 甚至 2/3 的时间来学写 parser。由于 parser 占据了大量时间,以至于很多真正精华的内容都被一笔带过:语义分析,代码优化,类型推导,静态检查,机器代码生成,…… 以至于很多人上完了编译原理课程,记忆中只留下写 parser 的痛苦回忆。\n我从来就不认为自己是“编译器”专业的,我认为自己是“pl 专业”。编译器领域照本宣科成分更多一些,pl 专业更加注重本质的东西。\n如果你想真的深入理解编译理论,最好是从 pl 课程的读物,比如 eopl 开始。\n我可以说 pl 这个领域,真的和编译器的领域很不一样。请不要指望编译器的作者(比如 llvm 的作者)能够设计出好的语言,因为他们可能根本不理解很多语言设计的东西,他们只是会实现某些别人设计的语言。可是反过来,理解了 pl 的理论, 编译器的东西只不过是把一种语言转换成另外一种语言(机器语言)而已 。工程的细枝末节很麻烦,可是当你掌握了精髓的原理,那些都容易摸索出来。\n:: 我想了好久,也没有办法让自己同意你上面说的前半段话!\n5.我写 parser 的心得和秘诀\n很多人都觉得写 parser 很难,一方面是由于语言设计的错误思想导致了复杂的语法,另外一方面是由于人们对于 parser 构造过程的思维误区。很多人不理解 parser 的本质和真正的用途,所以他们总是试图让 parser 干一些它们本来不应该干的事情,或者对 parser 有一些不切实际的标准。当然,他们就会觉得 parser 非常难写,非常容易出错。\n……\n所以你看到了,parser 并不是编译器,它甚至不属于编译里很重要的东西。\nparser 的研究其实是在解决一些根本不存在或者人为制造的问题。复杂的语法导致了复杂的 parser 技术,它们仍然在给计算机世界带来不必要的困扰和麻烦。对 parser 写法的很多误解,过度工程和过早优化,造成了很多人错误的高估写 parser 的难度。\n图灵的光环 图灵的光环\n:: 王垠的博文写的真长,一定读了不少的书,并且作了验证(对错暂不论),就这一点也是非常值得学习的。\n编程的智慧 编程的智慧\n编程是一种创造性的工作,是一门艺术。精通任何一门艺术,都需要很多的 练习和领悟 ,所以这里提出的“智慧”,并不是号称一天瘦十斤的减肥药,它并不能代替你自己的勤奋。\n1.反复推敲代码 有些人喜欢炫耀自己写了多少多少万行的代码,仿佛代码的数量是衡量编程水平的标准。然而,如果你总是匆匆写出代码,却从来不回头去推敲,修改和提炼,其实是不可能提高编程水平的。\n就像文学作品一样,代码是不可能一蹴而就的。灵感似乎总是零零星星,陆陆续续到来的。\n所以如果反复提炼代码已经不再有进展,那么你可以暂时把它放下。过几个星期或者几个月再回头来看,也许就有焕然一新的灵感。这样反反复复很多次之后,你就积累起了灵感和智慧,从而能够在遇到新问题的时候直接朝正确,或者接近正确的方向前进。\n2.写优雅的代码 人们都讨厌“面条代码”(spaghetti code),因为它就像面条一样绕来绕去,没法理清头绪。\n那么优雅的代码一般是什么形状的呢?\n如果我们忽略具体的内容,从大体结构上来看,优雅的代码看起来就像是一些整整齐齐,套在一起的盒子。\n优雅的代码的另一个特征是,它的逻辑大体上看起来,是枝丫分明的树状结构(tree)。这是因为程序所做的几乎一切事情,都是信息的传递和分支。你可以把代码看成是一个电路,电流经过导线,分流或者汇合。\n3.写模块化的代码 有些人吵着闹着要让程序“模块化”,其实并不理解什么叫做“模块”。肤浅的把代码切割开来,分放在不同的位置,其实非但不能达到模块化的目的,而且制造了不必要的麻烦。\n真正的模块化,并不是文本意义上的,而是逻辑意义上的。\n一个模块应该像一个电路芯片,它有定义良好的输入和输出。实际上一种很好的模块化方法早已经存在,它的名字叫做“函数”。每一个函数都有明确的输入(参数)和输出(返回值),同一个文件里可以包含多个函数,所以你其实根本不需要把代码分开在多个文件或者目录里面,同样可以完成代码的模块化。\n想要达到很好的模块化,你需要做到以下几点:\n1) 避免写太长的函数\n如果发现函数太大了,就应该把它拆分成几个更小的。\n2) 制造小的工具函数\n如果你仔细观察代码,就会发现其实里面有很多的重复。这些常用的代码,不管它有多短,提取出去做成函数,都可能是会有好处的。有些帮助函数也许就只有两行,然而它们却能大大简化主要函数里面的逻辑。\n3) 每个函数只做一件简单的事情\n有些人喜欢制造一些“通用”的函数,既可以做这个又可以做那个,它的内部依据某些变量和条件,来“选择”这个函数所要做的事情。这种“复用”其实是有害的。\n如果一个函数可能做两种事情,它们之间共同点少于它们的不同点,那你最好就写两个不同的函数,否则这个函数的逻辑就不会很清晰,容易出现错误。\n如果你发现两件事情大部分内容相同,只有少数不同,多半时候你可以把相同的部分提取出去,做成一个辅助函数。\n:: 具体示例,可以参考原文,确实简洁明了!\n4) 避免使用全局变量和类成员(class member)来传递信息,尽量使用局部变量和参数\n:: 这个确实,使用全局变量,就不得不考虑该变量是不是在别处作了改变。\n依赖全局的数据,函数不再有明确的输入和输出,依赖于当前的上下文。全局的数据还有可能被其他代码改变,代码变得难以理解,难以确保正确性。\n4.写可读的代码 有些人以为写很多注释就可以让代码更加可读,然而却发现事与愿违。注释不但没能让代码变得可读,反而由于大量的注释充斥在代码中间,让程序变得障眼难读。而且代码的逻辑一旦修改,就会有很多的注释变得过时,需要更新。修改注释是相当大的负担,所以大量的注释,反而成为了妨碍改进代码的绊脚石。\n实际上,真正优雅可读的代码,是几乎不需要注释的。\n如果你发现需要写很多注释,那么你的代码肯定是含混晦涩,逻辑不清晰的。其实,程序语言相比自然语言,是更加强大而严谨的,它其实具有自然语言最主要的元素:主语,谓语,宾语,名词,动词,如果,那么,否则,是,不是,…… 所以如果你充分利用了程序语言的表达能力,你完全可以用程序本身来表达它到底在干什么,而不需要自然语言的辅助。\n有少数的时候,你也许会为了绕过其他一些代码的设计问题,采用一些违反直觉的作法。这时候你可以使用很短注释,说明为什么要写成那奇怪的样子。这样的情况应该少出现,否则这意味着整个代码的设计都有问题。\n如果没能合理利用程序语言提供的优势,你会发现程序还是很难懂,以至于需要写注释。\n所以我现在告诉你一些要点,也许可以帮助你大大减少写注释的必要:\n1) 使用有意义的函数和变量名字\n如果你的函数和变量的名字,能够切实的描述它们的逻辑,那么你就不需要写注释来解释它在干什么。比如:\n// put elephant1 into fridge2\rput(elephant1, fridge2); 2) 局部变量应该尽量接近使用它的地方\n这种短距离,可以加强读者对于这里的“计算顺序”的理解。读者就就清楚的知道,这个变量并不是保存了什么可变的值,而且它算出来之后就没变过。\n如果你看透了局部变量的本质——它们就是电路里的导线,那你就能更好的理解近距离的好处。变量定义离用的地方越近,导线的长度就越短。你不需要摸着一根导线,绕来绕去找很远,就能发现接收它的端口,这样的电路就更容易理解。\n3) 局部变量名字应该简短\n因为它们处于局部,再加上第 2 点已经把它放到离使用位置尽量近的地方,所以根据上下文你就会容易知道它的意思。\n4) 不要重用局部变量\n我认为这是逻辑意义上的局部,比如 a 逻辑嵌套了 b 和 c ,a 中定义了一个变量 x ,在 b 和 c 中改变后,在 a 内后续使用该变量的值,则“不算重用”。\n不过仍然需要注意,变量定义和最终使用距离不要太远,否则,就应该考虑其他方式。\n5) 把复杂的逻辑提取出去,做成“帮助函数”\n有些人写的函数很长,以至于看不清楚里面的语句在干什么,所以他们误以为需要写注释。如果你仔细观察这些代码,就会发现不清晰的那片代码,往往可以被提取出去,做成一个函数,然后在原来的地方调用。由于函数有一个名字,这样你就可以使用有意义的函数名来代替注释。\n举一个例子:\n1 2 3 4 5 6 7 8 9 10 ... // put elephant1 into fridge2 opendoor(fridge2); if (\u0008elephant1.\u0008alive()) { ... } else { ... } closedoor(fridge2); ... 如果你把这片代码提出去定义成一个函数:\n1 2 3 4 5 6 7 8 9 void put(elephant elephant, fridge fridge) { opendoor(fridge); if (\u0008elephant.alive()) { ... } else { ... } closedoor(fridge); } 这样原来的代码就可以改成:\n1 2 3 ... put(elephant1, fridge2); ... 更加清晰,而且注释也没必要了。\n6) 把复杂的表达式提取出去,做成中间变量\n1 2 3 4 5 6 pizza pizza = makepizza(crust(salt(), butter()), topping(onion(), tomato(), sausage())); // ↓↓↓ crust crust = crust(salt(), butter()); topping topping = topping(onion(), tomato(), sausage()); pizza pizza = makepizza(crust, topping); 有效地控制了单行代码的长度,而且由于引入的中间变量具有“意义”,步骤清晰,变得很容易理解。\n7) 在合理的地方换行\n:: 是的,现在用自动化的格式工具,确实没有手动让语义更易读。\n5.写简单的代码 程序语言都喜欢标新立异,提供这样那样的“特性”,然而有些特性其实并不是什么好东西。很多特性都经不起时间的考验,最后带来的麻烦,比解决的问题还多。\n并不是语言提供什么,你就一定要把它用上的。实际上你只需要其中很小的一部分功能,就能写出优秀的代码。我一向反对“充分利用”程序语言里的所有特性。\n实际上,我心目中有一套最好的构造。不管语言提供了多么“神奇”的,“新”的特性,我基本都只用经过千锤百炼,我觉得值得信赖的那一套。\n现在针对一些有问题的语言特性,我介绍一些我自己使用的代码规范,并且讲解一下为什么它们能让代码更简单。\n1) 避免使用自增减表达式( i++, ++i, i–-, -–i )\n这种自增减操作表达式其实是历史遗留的 设计失误 。\n它们把读和写这两种完全不同的操作,混淆缠绕在一起,把语义搞得乌七八糟。含有它们的表达式,结果可能取决于求值顺序,所以它可能在某种编译器下能正确运行,换一个编译器就出现离奇的错误。\n其实这两个表达式完全可以分解成两步,把读和写分开:一步更新 i 的值,另外一步使用 i 的值。\n1 2 3 4 5 6 7 8 9 10 11 foo(i++); // ↓↓↓ let t = i; i += 1; foo(t); // ------- foo(++i); // ↓↓↓ i += 1; foo(i); 不难看出, i++ 其实是使用更新前的值,而 ++i 是使用更新后的值。\n拆开之后的代码,含义完全一致,却清晰很多。到底更新是在取值之前还是之后,一目了然。\n自增减表达式只有在两种情况下才可以安全的使用。一种是在 for 循环的 update 部分,比如 for(int i = 0; i \u0026lt; 5; i++) 。另一种情况是写成单独的一行,比如 i++; 。这两种情况是完全没有歧义的。\n你需要避免其它的情况,比如用在复杂的表达式里面,比如 foo(i++),foo(++i) + foo(i),…… 没有人应该知道,或者去追究这些是什么意思。\n2) 永远不要省略花括号\n3) 合理使用括号,不要盲目依赖操作符优先级\n4) 避免使用 continue 和 break\n循环语句(for,while)里面出现 return 是没问题的,然而如果你使用了 continue 或者 break ,就会让循环的逻辑和终止条件变得复杂,难以确保正确。\n出现 continue 或者 break 的原因,往往是对循环的逻辑没有想清楚。如果你考虑周全了,应该是几乎不需要 continue 或者 break 的。如果你的循环里出现了 continue 或者 break ,你就应该考虑改写这个循环。\n改写循环的办法有多种:\n如果出现了 continue ,你往往只需要把 continue 的条件反向,就可以消除 continue ; 如果出现了 break ,你往往可以把 break 的条件,合并到循环头部的终止条件里,从而去掉 break ; 有时候你可以把 break 替换成 return,从而去掉 break ; 如果以上都失败了,你也许可以把循环里面复杂的部分提取出来,做成函数调用,之后 continue 或者 break 就可以去掉了。 :: 详细示例见原文。\n6.写直观的代码 我写代码有一条重要的原则:如果有更加直接,更加清晰的写法,就选择它,即使它看起来更长,更笨,也一样选择它。\n比如,人们往往容易滥用了逻辑操作 \u0026amp;\u0026amp; 和 || 的短路特性。这两个操作符可能不执行右边的表达式,原因是为了机器的执行效率,而不是为了给人提供这种“巧妙”的用法。这两个操作符的本意,只是作为逻辑操作,它们并不是拿来给你代替 if 语句的。\n……\n7.写无懈可击的代码 在之前一节里,我提到了自己写的代码里面很少出现只有一个分支的 if 语句。我写出的 if 语句,大部分都有两个分支。使用这种方式,其实是为了无懈可击的处理所有可能出现的情况,避免漏掉 corner case。所以我的代码很多看起来是这个样子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 推荐 ✔ if (...) { if (...) { ... return false; } else { return true; } } else if (...) { ... return false; } else { return true; } 缺了 else 分支的 if 语句,控制流自动“掉下去”,到达最后的 return true 。这种写法看似更加简洁,避免了重复,然而却很容易出现疏忽和漏洞。\n1 2 3 4 5 6 7 8 9 10 11 // 不推荐 ✘ if (...) { if (...) { ... return false; } } else if (...) { ... return false; } return true; 嵌套的 if 语句省略了一些 else,依靠语句的“控制流”来处理 else 的情况,是很难正确的分析和推理的。如果你的 if 条件里使用了 \u0026amp;\u0026amp; 和 || 之类的逻辑运算,就更难看出是否涵盖了所有的情况。\n由于疏忽而漏掉的分支,全都会自动“掉下去”,最后返回意想不到的结果。即使你看一遍之后确信是正确的,每次读这段代码,你都不能确信它照顾了所有的情况,又得重新推理一遍。这简洁的写法,带来的是反复的,沉重的头脑开销。这就是所谓“面条代码”,因为程序的逻辑分支,不是像一棵枝叶分明的树,而是像面条一样绕来绕去。\n另外一种省略 else 分支的情况是这样:\n1 2 3 4 5 // 不推荐 ✘ string s = \u0026#34;\u0026#34;; if (x \u0026lt; 5) { s = \u0026#34;ok\u0026#34;; } 写这段代码的人,脑子里喜欢使用一种“缺省值”的做法。s 缺省为 null ,如果 x\u0026lt;5 ,那么把它改变(mutate)成“ok”。这种写法的缺点是,当 x\u0026lt;5 不成立的时候,你需要往上面看,才能知道 s 的值是什么。这还是你运气好的时候,因为 s 就在上面不远。很多人写这种代码的时候,s 的初始值离判断语句有一定的距离,中间还有可能插入一些其它的逻辑和赋值操作。\n1 2 3 4 5 6 7 8 9 10 11 // 推荐 ✔ string s; if (x \u0026lt; 5) { s = \u0026#34;ok\u0026#34;; } else { s = \u0026#34;\u0026#34;; } // 这个情况比较简单,你还可以把它写成这样 // (对于更加复杂的情况,我建议还是写成 if语句为好) string s = x \u0026lt; 5 ? \u0026#34;ok\u0026#34; : \u0026#34;\u0026#34;; 8.正确处理错误 使用有两个分支的 if 语句,只是我的代码可以达到无懈可击的其中一个原因。这样写 if 语句的思路,其实包含了使代码可靠的一种 通用思想:穷举所有的情况 ,不漏掉任何一个。\n……\n:: 关于异常,这里先不谈……\n9.正确处理 null 指针 ……\n10.防止过度工程 过度工程即将出现的一个重要信号,就是当你过度的思考“将来”,考虑一些还没有发生的事情,还没有出现的需求。另外一种过度工程的来源,是过度的关心“代码重用”。过度地关心“测试”,也会引起过度工程。\n根据这些,我总结出来的防止过度工程的原则如下:\n先把眼前的问题解决掉,解决好,再考虑将来的扩展问题; 先写出可用的代码,反复推敲,再考虑是否需要重用的问题; 先写出可用,简单,明显没有 bug 的代码,再考虑测试的问题。 :: 真知灼见!!!好好思考下,值得反复阅读和学习!\n给 java 说句公道话 给 java 说句公道话\njava 超越了所有咒骂它的“动态语言”!\njava 的“继承人”没能超越它!\njava 没有特别讨厌的地方。\n编程使用什么工具是重要的,然而工具终究不如自己的技术重要。很多人花了太多时间,折腾各种新的语言,希望它们会奇迹一般的改善代码质量,结果最后什么都没做出来。选择语言最重要的条件,应该是“够好用”就可以,因为项目的成功最终是靠人,而不是靠语言。既然 java 没有特别大的问题,不会让你没法做好项目,为什么要去试一些不靠谱的新语言呢?\n我为什么不再做 pl 人 我为什么不再做 pl 人\npl 看似计算机科学最精髓的部分,事实确实也是这样的。没有任何一个其它领域,可以让你对程序的本质形成如此深入的领悟。\n……\n测试的道理 测试的道理\n在长期的程序语言研究和实际工作中,我摸索出了一些关于测试的道理。\n在我心目中,代码本身的地位大大的高于测试。我不忽视测试,但我不会本末倒置,过分强调测试,我并不推崇测试驱动开发(tdd)。\n:: 看,别人的经验也不是空中楼阁,是自己一步步领悟出来的。\nps.我不怎么写测试……\n现在我就把这些自己领悟到的关于测试的道理总结一下,其中有一些是鲜为人知或者被误解的。\n不要以为你处处显示出“重视代码质量”的态度,就能提高代码质量; 真正的编程高手不会被测试捆住手脚; 在程序和算法定型之前,不要写测试; 不要为了写测试而改变本来清晰的编程方式; 不要测试“实现细节”,因为那等同于把代码写两遍; 并不是每修复一个 bug 都需要写测试; 避免使用 mock,特别是多层的 mock; 不要过分重视“测试自动化”,人工测试也是测试; 避免写太长,太耗时的测试; 一个测试只测试一个方面,避免重复测试; 避免通过比较字符串来进行测试; 认知“测试能帮助后来人”的误区。 ……\n经验和洞察力 经验和洞察力\n很多人很在乎“经验”,比如号称自己在某领域有 30 年的经验,会用这样那样的技术。我觉得经验是有价值的,我也有经验,各个领域的都有点。然而我并不把经验放在很重要的位置,因为我拥有大部分人都缺乏而且忽视的一种东西:洞察力(insight)。\n什么是洞察力?洞察力就是透过现象看到本质的能力。\n:: 一切的东西走到最后,都会成为一个哲学问题。\n其实,经验和洞察力并不是矛盾的,王垠想表达的是他得到了“道”,所以可以很快的掌握“术”。\n如何掌握所有的程序语言 如何掌握所有的程序语言\n重视语言特性,而不是语言 任何一种“语言”,都是各种“语言特性”的组合。\n举一些语言特性的例子:\n- 变量定义\r- 算术运算\r- for 循环语句,while 循环语句\r- 函数定义,函数调用\r- 递归\r- 静态类型系统\r- 类型推导\r- lambda 函数\r- 面向对象\r- 垃圾回收\r- 指针算术\r- goto 语句\r- …… 对于初学者来说,其实没必要纠结到底要先学哪一种语言,再学哪一种。\n初学者往往不理解, 每一种语言里面必然有一套“通用”的特性 。比如变量,函数,整数和浮点数运算,等等。这些是每个通用程序语言里面都必须有的,一个都不能少。你只要通过“某种语言”学会了这些特性,掌握这些特性的根本概念,就能随时把这些知识应用到任何其它语言。你为此投入的时间基本不会浪费。所以初学者纠结要“先学哪种语言”,这种时间花的很不值得,还不如随便挑一个语言,跳进去。\n如果你不能用一种语言里面的基本特性写出好的代码,那你换成另外一种语言也无济于事。你会写出一样差的代码。\n很多初学者不了解,一个高明的程序员如果开始用一种新的程序语言,他往往不是去看这个语言的大部头手册或者书籍,而是先有一个需要解决的问题。手头有了问题,他可以用两分钟浏览一下这语言的手册,看看这语言大概长什么样。然后,他直接拿起一段例子代码来开始修改捣鼓,想法把这代码改成自己正想解决的问题。在这个简短的过程中,他很快的掌握了这个语言,并用它表达出心里的想法。\n在这个过程中,随着需求的出现,他可能会问这样的问题:\n这个语言的“变量定义”是什么语法,需要“声明类型”吗,还是可以用“类型推导”? 它的“类型”是什么语法?是否支持“泛型”?泛型的 “variance” 如何表达? 这个语言的“函数”是什么语法,“函数调用”是什么语法,可否使用“缺省参数”? …… 注意到了吗?上面每一个引号里面的内容,都是一种语言特性(或者叫概念)。这些概念可以存在于任何的语言里面,虽然语法可能不一样,它们的本质都是一样的。\n这些实际问题都是随着写实际的代码,解决手头的问题,自然而然带出来的,而不是一开头就抱着语言手册看得仔仔细细。\n掌握了语言特性的人都知道,自己需要的特性,在任何语言里面一定有对应的表达方式。 如果没有直接的方式表达,那么一定有某种“绕过方式”。如果有直接的表达方式,那么它只是语法稍微有所不同而已。所以,他是带着问题找特性,就像查字典一样,而不是被淹没于大部头的手册里面,昏昏欲睡一个月才开始写代码。\n掌握了通用的语言特性,剩下的就只剩某些语言“特有”的特性了。\n研究语言的人都知道,要设计出新的,好的,无害的特性,是非常困难的。所以一般说来,一种好的语言,它所特有的新特性,终究不会超过一两种。如果有个语言号称自己有超过 5 种新特性,那你就得小心了,因为它们带来的和可能不是优势,而是灾难!\n最好的语言研究者,往往不是某种语言的设计者,而是某种 关键语言特性的设计者 (或者支持者)。\n合理的入门语言 所以初学者要想事半功倍,就应该从 一种“合理”的,没有明显严重问题的语言 出发, 掌握最关键的语言特性,然后由此把这些概念应用到其它语言 。哪些是合理的入门语言呢?我个人觉得这些语言都可以用来入门:\n- scheme\r- c\r- java\r- python\r- javascript 那么相比之下,我不推荐用哪些语言入门呢?\n- shell\r- powershell\r- awk\r- perl\r- php\r- basic\r- go\r- rust 掌握关键语言特性,忽略次要特性 为了达到我之前提到的融会贯通,一通百通的效果,初学者应该专注于语言里面最关键的特性,而不是被次要的特性分心。\n……\n自己动手实现语言特性 在基本学会了各种语言特性,能用它们来写代码之后,下一步的进阶就是去实现它们。只有实现了各种语言特性,你才能完全地拥有它们,成为它们的主人。否则你就只是它们的使用者,你会被语言的设计者牵着鼻子走。\n有个大师说得好, 完全理解一种语言最好的方法就是自己动手实现它,也就是自己写一个解释器来实现它的语义 。但我觉得这句话应该稍微修改一下: 完全理解一种“语言特性”最好的方法就是自己亲自实现它 。\n注意我在这里把“语言”改为了“语言特性”。你并不需要实现整个语言来达到这个目的,因为我们最终使用的是语言特性。 只要你自己实现了一种语言特性,你就能理解这个特性在任何语言里的实现方式和用法。\n举个例子,学习 sicp 的时候,大家都会亲自用 scheme 实现一个面向对象系统。用 scheme 实现的面向对象系统,跟 java,c++,python 之类的语言语法相去甚远,然而它却能帮助你理解任何这些 oop 语言里面的“面向对象”这一概念,它甚至能帮助你理解各种面向对象实现的差异。\n这种效果是你直接学习 oop 语言得不到的,因为在学习 java,c++,python 之类语言的时候,你只是一个用户,而用 scheme 自己动手实现了 oo 系统之后,你成为了一个创造者。\n类似的特性还包括类型推导,类型检查,惰性求值,如此等等。我实现过几乎所有的语言特性,所以任何语言在我的面前,都是可以被任意拆卸组装的玩具,而不再是凌驾于我之上的神圣。\n总结 写了这么多,重要的话重复三遍: 语言特性,语言特性,语言特性,语言特性! 不管是初学者还是资深程序员,应该专注于语言特性,而不是纠结于整个的“语言品牌”。只有这样才能达到融会贯通,拿起任何语言几乎立即就会用,并且写出高质量的代码。\n解谜计算机科学 解谜计算机科学\n解谜英语语法 解谜英语语法\n学习的智慧 学习的智慧\n1.死知识,活知识\n大部分人从学校,从书籍,从文献学知识,结果学到一堆“死知识”。要检验知识是不是死的,很简单。如果你遇到前所未见的问题,却不能把这些知识运用出来解决问题,那么这些知识就很可能是死的。\n:: 实践出真知!\n死知识可能来源于真正聪明的人,但普通人往往是间接得到它。从知识的创造者到你之间,经过了多次的转手倒卖。就算你直接跟知识的鼻祖学习都不容易得到真传,普通人还得经过多次转手。每一次转手都损失里面的信息含量,增加“噪音”,甚至完全被误传。所以到你这里的时候,里面的“信噪比”就很低了。这就是为什么你学了东西,到时候却没法用出来。\n追根溯源之后,你会发现这知识最初的创造者经过了成百上千的错误。\n没有这些失败的经验,你就少了所谓“思路”,那你是不大可能从一个知识发展出新的知识的。\n死知识是脆弱的。面对现实的问题,死知识的拥有者往往不知所措,他们的内心充满了恐惧。\n世界上最重大的发现,往往产生于对非常基础的问题的思考。\n活知识必须靠自己创造出来,要经过许许多多的失败。如果没有经过失败,是不可能得到活知识的。\n2.知识的来源\n知识的来源最好是自己的头脑,但也不尽然。有些东西成本太高,没条件做实验就没法得到,所以还是得先获取现成的死知识。\n有些人说到“学习”,总是喜欢认认真真上课,抄笔记,看书。有些人喜欢勾书,把书上整整齐齐画满了横杠。兢兢业业不辞辛苦,最后却发现没学会什么。\n为什么会这样呢?\n首先因为他们没有理智的选择知识的来源。其次,他们不明白如何有效的“提取”知识。这第一点属于“品位”问题,第二点则属于“方法”问题。\n很多人没有意识到,对于同一个问题有很多不同的书,不同的作者对于问题的见解深度是不一样的。如果一个主题你看得头大,最好的办法是放下这书,去寻找对同一主题的更简单的解释。这些东西可以来源于网络,也可以来自其它书籍,也可以来自身边的人。\n同时保留多个这样的资源,你就可以对任何主题采用同样的“广度优先”搜索,获得深入理解的机会就会增加。\n3.英语的重要性\n不是我崇洋媚外,可是实话说,这几年中文内容虽然改进了很多,可是很多方向上的专业程度还是比英文的低很多,很多不准确甚至根本就是错的。\n我不排斥看中文内容,但我建议不要片面的只看中文内容。事无巨细都应该同时参考英文信息,多方面分析之后再做决定。生活的决策如此,专业知识的学习当然也一样。对于同一个知识点,看到中文的时候你最好搜索它的英文,对比各种资料,这样你就更容易得到准确的信息。\ntalk is not cheap talk is not cheap\n![[assets/pasted image 20230526111131.png]]\n“苦干,用代码说话,忽视想法”,是很多程序员的误区。\n人的思想不一定需要代码来证明,甚至很多的想法无法简单的用代码表示,只有靠人的头脑才能想得清楚。思想是首要的,代码只是对思想的一种实现。\n我们先得要有思想(算法),才可能有代码。\n代码不能代替思想交流和讨论。代码不能清晰的表达一个人的想法,也不能显示一个人的思维深度。\n代码是死的,它是对已有问题的解决方案。而你想要知道的是这个人在面对新的问题的时候,他会怎样去解决它。所以你必须知道这个人的思维方式,看清楚他是否真的知道他声称“精通”的那些东西。\n:: 凡事皆有度,物极必反。\n我不是编译器专家 我不是编译器专家\n我不是编译器专家,而且我看不起编译器这个领域。我一般不会居高临下看低其它人,然而对于认识肤浅却又自视很高的人,我确实会表示出藐视的态度。现在我的态度是针对编译器这整个领域。真的,我看这些人不顺眼很多年了。\n:: “哈哈,不要误会,我不是针对某个人,我是说在座的各位都是垃圾……”\n就最后研究的领域,我是一个编程语言(pl)研究者,从更广的角度来看,我是一个计算机科学家。\nit 业人士经常混淆编程语言(pl)和编译器两个领域,而其实 pl 和编译器是很不一样的。真懂 pl 的人去做编译器也会比较顺手,而编译器专业的却不一定懂 pl。为什么呢?因为 pl 研究涵盖了计算最本质的原理,它不但能解释语言的语义,而且能解释处理器的构架和工作原理 。当然它也能解释编译器是怎么回事,因为编译器只不过是把一种语言的语义,利用另外一种语言表达出来,也就是翻译一下。pl 研究所用的编程范式和技巧,很多可以用到编译器的构造中去,但却比编译器的范畴广阔很多。\n深入研究过 pl 的人,能从本质上看明白编译器里在做什么。所以编译器算是 pl 思想的一种应用,然而 pl 的应用却远远不止做编译器。\n实际上做编译器是很无聊的工作,大部分时候只是把别人设计的语言,翻译成另外的人设计的硬件指令。所以编译器领域处于编程语言(pl)和计算机体系构架(computer architecture)两个领域的夹缝中,上面的语言不能改,下面的指令也不能改,并没有很大的创造空间。\n我的事业计划 我的事业计划\n为了建立起最高水准的,真正的教育机构,我的初期计划是做一个顾问或者导师。\n在计划中的课程内容可能包括:\n- 计算机科学入门\r- 掌握所有的编程语言\r- c++,java,python,javascript,haskell\r- 编程的智慧——如何写出优雅的代码\r- 算法\r- 编程语言理论\r- 操作系统\r- 计算机体系构架\r- 编译器设计和实现\r- 函数式编程\r- 逻辑式编程\r- 机器学习(深度学习,计算机视觉等)\r- …… 每一个课程,我都会试图用最简单直观的方式来讲解。\n如何阅读别人的代码 如何阅读别人的代码\n比起阅读代码,我更喜欢别人给我讲解他们的代码,用简单的语言或者图形来解释他们的思想。有了思想,我自然知道如何把它变成代码,而且是优雅的代码。很多人的代码我不会去看,但如果他们给我讲,我是可以接受的。\n如果有同事请我帮他改进代码,我不会拿起代码埋头就看,因为我知道看代码往往是事倍功半,甚至完全没用。我会让他们先在白板上给我解释那些代码是什么意思。我的同事们都发现,把我讲明白是需要费一番工夫的。因为我的要求非常高,只要有一点不明白,我就会让他们重新讲。还得画图,我会让他们反复改进画出来的图,直到我能一眼看明白为止。如果图形是 3d 的,我会让他们给我压缩成 2d 的,理解了之后再推广到 3d。\n我无法理解复杂的,高维度的概念,他们必须把它给我变得很简单。\n所以跟我讲代码可能需要费很多时间,但这是值得的。我明白了之后,往往能挖出其他人都难以看清楚的要点。给我讲解事情,也能提升他们自己的思维和语言能力,帮助他们简化思想。很多时候我根本没看代码,通过给我讲解,后来他们自己就把代码给简化了。节省了我的脑力和视力,他们也得到了提高。\n我最近一次看别人的代码是在 intel,我们改了 pytorch 的代码。那不是一次愉悦的经历,因为虽然很多人觉得 pytorch 好用,它内部的代码却是晦涩而难以理解的。\npytorch 之类的深度学习框架,本质上是某种简单编程语言的解释器,只不过这些语言写出来的函数可以求导而已。\n很多人都不知道,有一天我用不到一百行 scheme 代码就写出了一个「深度学习框架」,它其实是一个小的编程语言。虽然没有性能可言,没有 gpu 加速,功能也不完善,但它抓住了 pytorch 等大型框架的本质——用这个语言写出来的函数能自动求导。这种洞察力才是最关键的东西,只要抓住了关键,细节都可以在需要的时候琢磨出来。几十行代码反复琢磨,往往能帮助你看透上百万行的项目里隐藏的秘密。\n很多人以为看大型项目可以提升自己,而没有看到大型项目不过是几十行核心代码的扩展,很多部分是低水平重复。几十行平庸甚至晦涩的代码,重复一万次,就成了几十万行。看那些低水平重复的部分,是得不到什么提升的。\n造就我今天的编程能力和洞察力的,不是几百万行的大型项目,而是小到几行,几十行之短的练习。\n不要小看了这些短小的代码,它们就是编程最精髓的东西。反反复复琢磨这些短小的代码,不断改进和提炼里面的结构,磨砺自己的思维。逐渐的,你的认识水平就超越了这些几百万行,让人头痛的项目。\n所以我如何阅读别人的代码呢?don’t。如果有条件,我就让代码的作者给我讲,而不是去阅读它。如果作者不合作,而我真的要使用那个项目的代码,我才会去折腾它。那么如何折腾别人的代码呢?我有另外一套办法。\n英语学习的一些经验 英语学习的一些经验\n对智商的怀疑 对智商的怀疑\n计算机科学入门班报名 计算机科学入门班报名\n1.为什么重视“零基础”教育\n有些人可能不大明白我为什么喜欢讲“零基础”课程。一方面,真正好的教育应该是能让完全无基础的人顺利掌握的。就像爱因斯坦说:“如果你不能给一个六岁小孩解释清楚,那你并不真的懂。” 所以“零基础”的学生能够检验我是否达到了这个“真懂”的目标。\n实际上, 我的很多深刻理解,都是通过反复琢磨非常基础的概念获得的 ,而不是通过很“高级”,很复杂的概念。我最常用的“心理模型”,其实跟初学者第一节课学的内容差不多。\n在我心里并没有“初学者”和“资深者”的差别。我发现很多工作了几十年的工程师,很多连最基本的概念都是一知半解的,这也许就是为什么他们在工作中无法找准正确的方向,经常瞎撞。\n2.课程内容\n课程计划涵盖计算机科学的主要思想,大概会包括以下内容:\n- 基础语言构造,包含最常用几种语言的主要特性。\r- 递归思想,递归数据结构的处理。\r- 基本数据结构,少量基础算法。\r- 函数式编程基本思想。\r- 抽象的思维方式。\r- 基础的解释器原理。 如果从书籍的覆盖面来看,我试图包括以下书籍的精华内容:\n- sicp(前 4 章)\r- the little schemer\r- a little java, a few patterns 3.你将受到的训练\n掌握系统化的思维方法,严密的推理技巧; 写出简单,优雅,容易理解,可靠的代码; 从无到有,不依赖于任何语言的特性,解决各种计算问题的思路。 新丑陋的中国人 新丑陋的中国人\n![[assets/pasted image 20230526111239.png]]\n:: 我一定是年龄大了,比以前成熟点了。但看到这个题目,我还是忍不住想说: “去你妈的!你才丑陋,你全家都丑陋,操!!!”\n计算机科学基础班(第三期)报名 计算机科学基础班(第三期)报名\n……\n课程大纲,下面简要说一下课程的内容:\n教学语言\n课程目前使用 javascript 作为教学语言,但并不是教 javascript 语言本身,不会使用 javascript 特有的任何功能。课程教的思想不依赖于 javascript 的任何特性,它可以应用于任何语言,课程可以在任何时候换成任何语言。学生从零开始,学会的是计算机科学最核心的思想,从无到有创造出各种重要的概念,直到最后实现出自己的编程语言和类型系统。\n课程强度\n课程的设计是一个逐渐加大难度,比较辛苦,却很安全的山路,它通往很高的山峰。要参加课程,请做好付出努力的准备。在两个月的时间里,你每天需要至少一个小时来做练习,有的练习需要好几个小时才能做对。跟其他的计算机教学不同,学生不会因为缺少基础而放弃,不会误入歧途,也不会掉进陷阱出不来。学生需要付出很多的时间和努力,但没有努力是白费的。\n第一课:函数\n跟一般课程不同,我不从所谓“hello world”程序开始,也不会叫学生做一些好像有趣而其实无聊的小游戏。\n一开头我就讲最核心的内容:函数。\n关于函数只有很少几个知识点,但它们却是一切的核心。只知道很少的知识点的时候,对它们进行反复的练习,让头脑能够自如地对它们进行思考和变换,这是教学的要点。我为每个知识点设计了恰当的练习。\n第一课的练习每个都很小,只需要一两行代码,却蕴含了深刻的原理。练习逐渐加大难度,直至超过博士课程的水平。我把术语都改头换面,要求学生不上网搜索相关内容,为的是他们的思维不受任何已有信息的干扰,独立做出这些练习。练习自成系统,一环扣一环。后面的练习需要从前面的练习获得的灵感,却不需要其它基础。有趣的是,经过正确的引导,好些学生把最难的练习都做出来了,完全零基础的学生也能做出绝大部分,这是我在世界名校的学生里都没有看到过的。具体的内容因为不剧透的原因,我就不继续说了。\n第二课:递归\n递归可以说是计算机科学(或数学)最重要的概念。\n我从最简单的递归函数开始,引导理解递归的本质,掌握对递归进行系统化思考的思路。递归是一个很多人自以为理解了的概念,而其实很多人都被错误的教学方式误导了。很多人提到递归,只能想起“汉诺塔”或者“八皇后”问题,却不能拿来解决实际问题。很多编程书籍片面强调递归的“缺点”,教学生如何“消除递归”,却看不到问题的真正所在——某些语言(比如 c 语言)早期的函数调用实现是错误而效率低下的,以至于学生被教导要避免递归。由于对于递归从来没有掌握清晰的思路,在将来的工作中一旦遇到复杂点的递归函数就觉得深不可测。\n第三课:链表\n从零开始,学生不依赖于任何语言的特性,实现最基本的数据结构。\n第一个数据结构就是链表,学生会在练习中实现许多操作链表的函数。这些函数经过了精心挑选安排,很多是函数式编程语言的基本函数,但通过独立把它们写出来,学生掌握的是递归的系统化思路。这使得他们能自如地对这类数据结构进行思考,解决新的递归问题。\n与一般的数据结构课程不同,这个课程实现的大部分都是「函数式数据结构」,它们具有一些特别的,有用的性质。因为它们逻辑结构清晰,比起普通数据结构书籍会更容易理解。与 haskell 社区的教学方式不同,我不会宗教式的强调纯函数的优点,而是客观地让学生领会到其中的优点,并且发现它们的弱点。学会了这些结构,在将来也容易推广到非函数式的结构,把两种看似不同的风格有机地结合在一起。\n第四课:树结构\n从链表逐渐推广出更复杂的数据结构——树。\n在后来的内容中,会常常用到这种结构。树可能是计算机科学中最常用,最重要的数据结构了,所以理解树的各种操作是很重要的。我们的树也都是纯函数式的。\n第五课:计算器\n在熟悉了树的基本操作之后,实现一个比较高级的计算器,它可以计算任意嵌套的算术表达式。算术表达式是一种“语法树”,从这个练习学生会理解“表达式是一棵树”这样的原理。\n第六课:查找结构\n理解如何实现 =key-value= 查找结构,并且亲手实现两种重要的查找数据结构。我们的查找结构也都是函数式数据结构。这些结构会在后来的解释器里派上大的用场,对它们的理解会巩固加深。\n第七课:解释器\n利用之前打好的基础,亲手实现计算机科学中最重要,也是通常认为最难理解的概念——解释器。\n解释器是理解各种计算机科学概念的关键,比如编程语言,操作系统,数据库,网络协议,web 框架。计算机最核心的部件 cpu 其实就是一个解释器,所以解释器的认识能帮助你理解「计算机体系构架」,也就是计算机的“硬件”。你会发现这种硬件其实和软件差别不是很大。你可以认为解释器就是「计算」本身,所以它非常值得研究。对解释器的深入理解,也能帮助理解很多其它学科,比如自然语言,逻辑学。\n第八课:类型系统\n在解释器的基础上,学生会理解并实现一个相当高级的类型系统(type system)和类型检查器(typechecker)。\n这相当于实现一个类似 java 的静态类型语言,但比 java 在某些方面还要高级和灵活。我们的类型系统包含了对于类型最关键的要素,而不只是照本宣科地讲解某一种类型系统。当你对现有的语言里的类型系统不满意的时候,这些思路可以帮助你设计出自己的类型系统。学生会用动手的方式去理解静态类型系统的原理,其中的规则,却不含有任何公式。\n类型系统的规则和实现,一般只会在博士级别的研究中才会出现,可以写成一本厚书(比如 tapl 那样的),其中有各种神秘的逻辑公式。而我的学生从零开始,一节课就可以掌握这门技术的关键部分,实现出正确的类型系统,并且推导出正确的公式。有些类型规则是如此的微妙,以至于微软这么大的公司在 21 世纪做一个新的语言(typescript),仍然会在初期犯下类型专家们早已熟知的基本错误。上过这个课程的很多同学,可以说对这些基础原理的理解已经超过了 typescript 的设计者,但由于接受的方式如此自然,他们有一些人还没有意识到自己的强大。\n关于面向对象\n虽然课程不会专门讲“面向对象”的思想,但面向对象思想的本质(去掉糟粕)会从一开头就融入到练习里。上过课的同学到后来发现,虽然我从来没直接教过面向对象,而其实他们已经理解了面向对象的本质是什么。在将来的实践中,他们可以用这个思路去看破面向对象思想的本质,并且合理地应用它。\n奖励练习\n途中我会通过“奖励练习”的方式补充其它内容。比如第二期的课程途中,我临时设计了一个 parser 的练习,做完了其它练习的同学通过这个练习,理解了 parser 的原理,写出了一个简单但逻辑严密的 parser。奖励练习之所以叫“奖励”,因为并不是所有学生都能得到这个练习,只有那些付出了努力,在其他练习中做到融会贯通,学有余力的学生才会给这个练习。这样会鼓励学生更加努力地学习。\n一个朋友看了我的课程内容说,这不叫“基础班”,只能叫“大师班”。他不相信零基础的学生能跟上,但事实却是可行的。\n为什么不能即是“基础班”又是“大师班”呢?\n有句话说得好,大师只不过是把基础的东西理解得很透彻的人而已。\n我希望这个基础班能帮助人们获得本质的原理,帮助他们看透很多其它内容。所以上了“基础班”,可能在很长时间之内都不需要“高级班”了,因为他们已经获得了很强的自学能力,能够自己去探索未知的世界,攀登更高的山峰。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E5%BD%93%E7%84%B6%E6%88%91%E5%9C%A8%E6%89%AF%E6%B7%A1/","summary":"\u003cblockquote\u003e\n\u003cp\u003e我并不喜欢王垠这个人,甚至有些反感,但他的某些博文我很喜欢,也因此而受益,Emm…\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eTip. 非原文摘录,各别语句进行了删减和改动,建议看原文(点击章节标题)。\u003c/p\u003e","title":"当然我在扯淡"},]
[{"content":"在 canvas 中,我们介绍了画布的基本概念和使用方式,现在,我们来用它实现一个基本的手写板 - 你可以在 tablet 查看源码。\n\u0026gt; 原生 js 实现一下喽 ~\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;!-- 画布 --\u0026gt; \u0026lt;canvas id=\u0026#34;cvs\u0026#34; width=\u0026#34;600\u0026#34; height=\u0026#34;300\u0026#34; ontouchstart=\u0026#34;touchstart(event)\u0026#34; ontouchmove=\u0026#34;touchmove(event)\u0026#34; ontouchend=\u0026#34;touchend(event)\u0026#34; onmousedown=\u0026#34;mousedown(event)\u0026#34; onmousemove=\u0026#34;mousemove(event)\u0026#34; onmouseup=\u0026#34;mouseup(event)\u0026#34; \u0026gt; \u0026lt;/canvas\u0026gt; 一般来说,手写板什么的多在移动设备(触摸)上使用,只涉及 touch*** 相关事件,当然,如上所示,pc 端使用 mouse*** 事件模拟即可。\n下面,让我们看一下具体实现吧(以 mouse*** 事件为例)。\n基本思路 手写板应用的核心,就是使用 canvas 实时绘制路径(path),我们先来简单回顾一下这方面的知识,如下:\nbeginpath()\r- 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径\rmoveto(x, y)\r- 把画笔移动到指定的坐标 (x, y),相当于设置路径的起始点坐标\rlineto(x, y)\r- 添加一个新点,然后创建从该点到画面中最后指定点的线条\rclosepath()\r- 闭合路径之后,图形绘制命令又重新指向到上下文中\rstroke()\r- 通过线条来绘制图形轮廓 通过以上绘制路径的方法,我们使用 mousestart 结合 beginpath() 和 moveto(x, y) 方法,开始绘制路径,并在 mousemove 事件触发的过程中结合 lineto(x, y) 和 stroke() 实时绘制路径。\n核心解析 1. 先准备一下吧\n1 2 let cvs = document.queryselector(\u0026#39;#cvs\u0026#39;);\t// 获取画布 let ctx = cvs.getcontext(\u0026#39;2d\u0026#39;); // 上下文 2. 看看相关的事件\n1 2 3 4 5 6 7 8 9 10 function mousedown(e) { drawstart(e.pagex - cvs.offsetleft, e.pagey - cvs.offsettop); } // function drawstart(x, y) { // document.body.classlist.add(\u0026#39;body-fix\u0026#39;);\t// 书写时禁止页面滚动 // ctx.beginpath(); // ctx.moveto(x, y); // } mousedown 和 mousemove 事件中,我们可以方便获取鼠标指针相对于其第一个父级元素(带有 position 属性)的相对位置 (x, y),如果没有,就是相对于 body 了(本例中即是如此)。\n![[assets/pasted image 20230526112628.png|500]]\n1 2 3 4 5 6 7 8 9 10 function mousemove(e) { if (e.buttons === 1) {\t// 鼠标左键按下时 drawmove(e.pagex - cvs.offsetleft, e.pagey - cvs.offsettop); } } // function drawmove(x, y) { // ctx.lineto(x, y); // ctx.stroke(); // } 实时坐标的获取同上,此外需要注意的是,这里我们限制了 仅当鼠标左键按下时 才会绘制路径,否则,绘制出来的路径只会是个鬼画符。当然,如果你是在触摸设备中使用 touch*** 事件,则不存在这个问题。\n不同浏览器对于鼠标事件的监听指示可能有所不同,chrome 中,当 e.buttons 为 1 时,表示左键是按下状态,如果你想做兼容,请查看相关文档。\n1 2 3 4 5 6 7 8 9 function mouseup(e) { drawend(); } // function drawend() { // // ctx.closepath() // document.body.classlist.remove(\u0026#39;body-fix\u0026#39;);\t// 书写完成恢复页面滚动 // } 你可能已经注意到了,在 mousedown 和 mouseup 中,我们针对 body 元素做了一些类别修改 - 添加/删除 body-fix ,它有什么作用呢?\n1 2 3 .body-fix { overflow: hidden; } ![[assets/pasted image 20230526112641.png|500]]\n很简单,就是为了防止在书写签名时页面滚动,导致你写不成字 ~ 当然,别忘记在 mouseup 时,移除该类,否则,你就滚动不了页面喽。\n辅助功能 1. 生成签名\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 生成签名 function expcvs() { let src = cvs.todataurl(\u0026#39;image/png\u0026#39;, 1); /* * canvas.todataurl(type, encoderoptions) * 返回: * - 该方法返回一串 uri 字符串(canvas 中图像数据的 base64 编码) * * 参数: * - type:图像格式,默认为\u0026#34;image/png\u0026#34; * - encoderoptions:数值为 0 ~ 1,表示图片质量,仅在 type 为 \u0026#34;image/jpeg\u0026#34; 或 \u0026#34;image/webp\u0026#34; 时有效 * * 其他: * png 默认生成图片无背景,jpeg 默认生成图片为黑色背景 * 如果需要白色背景,可以在绘制前先绘制背景: * ctx.fillstyle = \u0026#39;#fff\u0026#39;; * ctx.fillrect(0, 0, canvas.width, canvas.height); */ console.log(src); img.src = src; } canvas 可以方便地生成图片格式(base64)文件 - 通过 todataurl 方法,如此,我们就可以方便的传递数据或将其作为图片标签的 src 属性使用。\n这里注意,做为签名来说,我们通常需要生成背景透明的图像,所以默认即为 png 格式的。\n2. 清除签名\n1 2 3 4 5 // 清除签名 function clrcvs() { ctx.clearrect(0, 0, 600, 300); img.src = \u0026#39;\u0026#39;; } 很简单,直接使用 clearrect 清空一个画布就可以了。\n3. 选择颜色\n1 2 3 4 5 // 选择签名颜色 function selectcolor(e) { console.log(e.target.value); ctx.strokestyle = e.target.value; } 可选功能,用来设置画笔颜色,当然,还有其他设置项,你完全可以按需添加。\n结语 基本原理,就是讲的这些,具体项目中实现可能会稍有改变,但难不到你的,对吧 🥳\n参考链接 canvas 生成一张图片在后,图片背景颜色默认是黑色 怎么改成其他颜色呢? - 知乎 - www.zhihu.com 解决 canvas 转 base64/jpeg 时透明区域变成黑色背景的方法_html5_网页制作_脚本之家 - www.jb51.net szimek/signature_pad: html5 canvas based smooth signature drawing - github.com h5 前端实现移动端手写 canvas 签名(支持横竖屏,自定义图片旋转角度)_canvas 手写签名横屏 h5 canvas 签名板 - 简书 - www.jianshu.com ","date":"2023-05-26","permalink":"https://aituyaa.com/%E6%89%8B%E5%86%99%E6%9D%BF/","summary":"\u003cp\u003e在 \u003ca href=\"http://localhost:1313/posts/canvas/\"\u003eCanvas\u003c/a\u003e 中,我们介绍了画布的基本概念和使用方式,现在,我们来用它实现一个基本的手写板 - 你可以在 \u003ca href=\"https://github.com/loveminimal/tablet\"\u003eTablet\u003c/a\u003e 查看源码。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; 原生 JS 实现一下喽 ~\u003c/code\u003e\u003c/p\u003e","title":"手写板"},]
[{"content":"🔔 转载自 如何打造一款标准的 js-sdk\n![[assets/pasted image 20230526111947.png|350]]\nsdk(software development kit,软件开发工具包),一般都是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。简单讲, 面向开发者,针对特定领域的软件包 ,基于它,开发人员可以快速构建自己的应用 app 。\n注:比较规范的 sdk 一般都会包含若干的 api 、开发工具集和说明文档。\n本文主要介绍如何基于 javascript 来开发的 sdk ,鉴于 js 语言本身的特性,基于它封装的 sdk 更多常见于 ui 组件库、统计分析、web 服务接口封装、前端稳定性和性能监控等场景。\n设计原则 如何设计 sdk ? 更多取决于其应用场景和最终用途。比如,实现一个给网页调用的 sdk 与用于服务端的 sdk 就有明显的差异,但综们之间存在着一些共通的原则:\n最小可用性原则,即用最少的代码,如无必要勿增实体; 最少依赖性原则,即最低限度的外部依赖,如无必要勿增依赖。 进一步阐述,即我们打造的 sdk 要符合以下的要求。\n1. 满足功能需求\nsdk 一般都是偏向于某个领域,所以,同时在设计和实现的时候明确职责和边界很重要,同时还应该足够精简,专注领域的业务。\n2. 足够稳定\n绝不能导致宿主应用崩溃(最基础、最严格的要求); 较好的性能,比如 sdk 体积应尽量小,运行速度尽量快; 可测试,保障每一次变更; 向后兼容,不轻易出现 breakchange 。 3. 少依赖,易扩展\n最小程度的第三方依赖,尽可能自行实现,确实无法避免则最小化引入; 插件化,最大限度支持扩展; hook 机制,满足个性化诉求。 如何实现 下面我们将通过剖析岳鹰前端监控 sdk 的设计过程,来看看上述的设计原则是如何应用到实际的开发过程中的。\n明职责,定边界 岳鹰前端监控 sdk 是前端稳定性和性能监控的 sdk,主要面向前端 h5 领域。因此,稍加分析即可得出以下结论:\n前端领域,稳定性方面主要的关注点: js 异常; 资源加载异常; api 请求异常; 白屏异常; 性能方面,核心的关注点: 白屏时间; 可交互时间(tti); 首屏时间; fp/fmp/fcp 等。 上述监控内容实际上都相对独立,因此我们可以把 ta 们横向划分为如下几大部分:\n![[assets/pasted image 20230526112009.png|500]]\n明确了 sdk 的边界以及各部分的职责,结合前端监控的特性,我们可以开始设计 sdk 的整体框架了。\n筑框架,夯基础 1. 确定 sdk 的引用形式\n前端模块有多种表现形式:\n- es module - commonjs - amd/cmd/umd 而在引用方面大体分为:cdn 和 npm 两种分发方式,即无论我们实现的是哪种形式的模块,最终都是通过这两种方式提供给用户引用。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 import wpkreporter from \u0026#39;wpkreporter\u0026#39; // commonjs const wpkreporter = require(\u0026#39;wpkreporter\u0026#39;) // amd requirejs 引用 require.config({ paths: { \u0026#39;wpk\u0026#39;: \u0026#39;https://g.alicdn.com/woodpeckerx/jssdk/wpkreporter.js\u0026#39;, } }) require([\u0026#39;wpk\u0026#39;, \u0026#39;test\u0026#39;], function(wpk) { // do your business }) 有点烦琐,但事实上今时今日的前端工程领域,已有很多利器可以帮助我们达到目的。比如, webpack 通过简单的配置就可以构建出一个 umd 的 bundule,可以自动适配所有形式的模块:\n1 2 3 4 5 6 7 8 9 module.exports = { output: { filename: \u0026#39;[name].js\u0026#39;, path: `${__dirname}/dist`, globalobject: \u0026#39;this\u0026#39;, library: \u0026#39;[name]\u0026#39;, librarytarget: \u0026#39;umd\u0026#39; } } 2. 确定 sdk 的版本管理机制\n现有较成熟的版本管理机制当属 语义化版本号 ,表现形式为 {主版本}.{次版本}.{补丁版本} ,简单易记好管理。\n一般重大的变更才会触发主版本号的更替,而且很可能新旧版本不兼容; 次版本主要对应新特性或者较大的调整,因此也有可能出现 breakchange ; 其他小的优化或 bugfix 就基本都是在补丁版本号体现。 所有 npm 模块都遵循语义化版本规范,因此结合第一点,我们可以将 sdk 初始化为一个 npm 模块,结合 webpack 的能力就可以实现基础的版本管理及模块构建。\n3. 确定 sdk 的基础接口\n接口是 sdk 和用户沟通的桥梁,每一个接口对应着一个独立的 sdk 功能,并且有明确的输入和输出。\n我们可以先来看看岳鹰前端监控 sdk 的核心接口有哪些?\n1 2 3 4 5 6 7 8 9 wpk.report(logdata) wpk.reportjserror(error) wpk.reportapierror(apidata) // 配置变更 wpk.setconfig(data) // sdk 诊断 wpk.diagnose() // 添加插件 wpk.addplugin(plugin) 总结一下接口的设计原则,大致如下:\n职责单一,一个接口只做一件事情; 命名简单清晰,参数尽量少但可扩展; 参数尽可能使用 object 封装; 做好参数校验和逻辑保护。 好的接口命名就是最好的注释,一看即明白其用处。\n领域分析,模块划分 确定边界的时候,我们已经清楚划分了 sdk 的几个关键部分:全局异常、api 异常、页面性能和白屏, 每一块都对应一个专业的领域 ,因此对应到 sdk 也是每一个独立的模块。\n实际上监控 sdk 通常也会内置对页面就是的监控,以方便用户对异常的影响面做出评估。除了这些核心的偏领域的模块,sdk 还需要更基础的与领域无关的模块,包括:\n- sdk 内核: - 构造方法 - 插件机制 - 与下游的交互 - 上报队列机制 - 不同环境和管理等等 - 工具库 我们来看一下岳鹰前端监控 sdk 最后的整体模块划分:\n![[assets/pasted image 20230526112031.png|525]]\nsdk 底层提供基础的能力,包括上面提到的内核、插件机制的实现、工具类库以及暴露给用户的基础 api 。\n可以看到,我们前面提到的所有模块都 以插件的形式存在 ,即各个领域的功能都各自松散的做实现,这样使得底层能力更具有通用性,同时扩展能力也更强,用户甚至也可以封装自己的插件。\nbiz 部分更多是对于不同宿主环境的多入口适配,当前支持浏览器 、weex 以及 nodejs 。\n测试覆盖,线上无忧 sdk 是一个基础服务,相对于前台业务而言可能更底层些。其影响面跟应用的范围是正比的关系,更多的用户意味着更大的责任。所以 sdk 的质量保障也是很重要的一个环节。 岳鹰前端监控 sdk 的质量保障策略很简单,只有两条:\n核心接口 100% 的单元测试覆盖率; 发布卡点:再小的版本发布也需要走集成测试回归。 事实上,除了核心接口,工具类库的所有功能我们都实现了 100% 的单元测试覆盖,我们采用的前端测试工具是轻量好用的 jest 。\n1 2 3 4 test(\u0026#39;iserror: real error\u0026#39;, function () { var err = new error(\u0026#39;this is an error\u0026#39;) expect(util.iserror(err)).tobetruthy() }) 细节打磨,极致体验 快捷引入:\n极尽所能提高用户引用的效率; 一行代码,快速引入,享用监控全家桶功能。 1 2 3 \u0026lt;script\u0026gt; !(function(c,i,e,b){var h=i.createelement(\u0026#34;script\u0026#34;);var f=i.getelementsbytagname(\u0026#34;script\u0026#34;)[0];h.type=\u0026#34;text/javascript\u0026#34;;h.crossorigin=true;h.onload=function(){c[b]||(c[b]=new c.wpkreporter({bid:\u0026#34;dta_1_203933078\u0026#34;}));c[b].installall()};f.parentnode.insertbefore(h,f);h.src=e})(window,document,\u0026#34;https://g.alicdn.com/woodpeckerx/jssdk/wpkreporter.js\u0026#34;,\u0026#34;__wpk\u0026#34;); \u0026lt;/script\u0026gt; 动态采样:\n即通过云端下发数据采样率的方式,控制客户端上报数据的频率; 更好的保护监控下游。 自我诊断:\n除了接口,sdk 整体对用户而言就是一个黑盒,因此用户在遇到问题时很容易蒙圈 (如:为啥没有上报数据); sdk 可以提供一个自我诊断的接口,快速排除基础问题。比如,sdk 是否已正常初始化、关键参数是否正常设置等。 增加调试模式,输出更详细的过程日志,方便定位问题。\n渐进式的指引文档:\n图文并茂,循序渐进; 入门,一步步引导用户初识 sdk,领略概貌,学会基本的使用; 进阶,安利 sdk 的深度用法,帮助用户更好的使用 sdk 。 结语 实际在 sdk 的设计和开发过程中,要处理的问题还远不止本文所述的内容,比如 npm 模块开发时本地如何引用,构建的 bundle 大小如何调优等等。不过还是希望阅完此文,对你有所启发。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E6%89%93%E9%80%A0-sdk/","summary":"\u003cp\u003e🔔 转载自 \u003ca href=\"https://zhuanlan.zhihu.com/p/272614462\"\u003e如何打造一款标准的 JS-SDK\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20230526111947.png|350]]\u003c/p\u003e\n\u003cp\u003eSDK(Software Development Kit,软件开发工具包),一般都是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。简单讲, \u003cstrong\u003e面向开发者,针对特定领域的软件包\u003c/strong\u003e ,基于它,开发人员可以快速构建自己的应用 App 。\u003c/p\u003e","title":"打造 sdk"},]
[{"content":"🔔 文中内容,多数摘录自南怀瑾先生的《易经杂说》。\n书不尽言,言不尽意。自觉圣智,完成人格。 – 南怀瑾\n洁净精微 “洁净精微,易教也。”\n“洁净”包括了宗教的、哲学的含义,也就是说学了《易经》,他的心理、思想、情绪无论在任何情况下,都会非常宁静,澄洁。“精微”两字则是科学的,是无比的细密精确,所以学《易》的人,要头脑非常冷静。\n三易 何为三易?《连山易》、《归藏易》和《周易》。\n《连山易》是神农时代的《易》,所画八卦的先后位置,和《周易》的八卦位置是不一样的。黄帝时代的《易》为《归藏易》。《连山易》以艮卦开始,《归藏易》以坤卦开始,到了《周易》则以乾卦开始,这是三易的不同之处。\n《易经》的三原则 《易经》三大原则:变易、简易和不易。\n变易 第一,所谓变易,是《易经》告诉我们,世界上的事,世界上的人,乃至宇宙万物,没有一样东西是不变的。在时、空当中,没有一事、没有一物、没有一情况、没有一思想是不变的,不可能不变,一定要变的。时间不同,环境不同,情感亦不同,万事万物,随时随地,都在变中,非变不可,没有不变的事物。\n印度佛学中的“无常”,是一种佛理,意思是世界上没有一种东西能永恒存在的,和《易经》中的变易有异曲同工之妙。中华文化中的《易经》,是讲原则,宇宙中的万事万物,没有不变的,非变不可,这是原则。印度人则是就现象而言,见现象有感,遂而名之为“无常”。\n简易 第二简易,也是最高的原则,宇宙间无论如何奥秘的事物,当我们的智慧够了,了解它以后,就变得很平常,很平凡而且非常简单。这就是把复杂的道理,予以简化,所以叫作简易。那么,《易经》首先告诉我们宇宙间的事物无时不变,尽管变得法则及其复杂,宇宙万事万物再错综复杂得现象,在我们懂了原理、原则以后,就非常简单了。\n不易 第三不易,万事万物随时随地都在变得,可是却有一项永远不变的东西存在,就是能变出来万象的那个东西却是不变的,那是永恒存在的。它能变万有、万物、万事,永远不变。\n:: 千变万化,化繁为简,以不变应万变。\n理、象、数 《易经》的三个法则(内涵):理、象、数。\n理是属于哲学的,宇宙间万事万物既都有它的理,也必有它的象;反过来说,宇宙间任何一个现象,也一定有它的理,同时每个现象,又一定有它的数。万事万物都有它的理、它的象和它的数。《易经》的每一卦、每一爻、每一点,都包含有理、象、数三种涵义在内。人处在世界上,与这个世界的关系,不停地在变,只要发生了变,便包含了它的理、象、数。人的智慧如果懂了事物的理、象、数,就会知道这事物的变,每个现象,到了一定的数,一定会变,为什么会变,有它的道理,完全明白了这些,就万事通达了。理、象、数通了,就能知变、通、达,万事前知了。\n:: ‘理’是本质,‘象’是表现,‘数’是界定。\n卦与八卦 什么叫作卦?卦就是挂起来的现象,八卦就是告诉我们宇宙间有八个东西,这个东西的现象挂出来,就是八卦。\n卦 ☰ 乾 天 ☷ 坤 地 ☲ 离 日、火 ☵ 坎 月、水 ☳ 震 雷 ☴ 巽 风 ☶ 艮 山 ☱ 兑 泽 乾为天,坤为地,☰ 、☷ 这两个符号代表了时间、空间、宇宙。天地以内,日月旋转,遂生雷风。雷风相薄,雷动为风,风卷成雷。山泽。\n在宇宙间,除了这八个大现象以外,没有九个,已不能七个,只有八个卦,而且都是对立的。\n先天八卦 天地定位。山泽通气。雷风相薄。水火不相射。八卦相错。 – 《说卦传》\n什么叫 “先天”?以哲学的观点说,宇宙万物没有形成以前,即是所谓的先天,有了宇宙万物,那就是 “后天”了。无为先天,有为后天。先天、后天,只是一种代号的作用,以逻辑来说,这只是一种界说,用以划分出阶段范围而已。\n字是写的,卦是画的,所以我们叫作画卦。卦的图案,每个卦都有三画,我们成为三卦画,卦中的画叫 “爻” 。爻者,交也。为什么“爻”就是交?这是说明卦在告诉我们,宇宙间万事万物,时时都在交流,不停地发生关系,引起变化,所以叫作“爻”。\n大象无形,不能拘泥。\n乾、坤、离、坎四个大卦,挂在那里大家都看得见的,就是天、地、日、月四个大象。\n天上云动,以为 ☳ ,震为雷。天下云卷,以为 ☴ ,巽为风。雷电震动以后,阳变为阴,阴变成阳,就变成了巽,气流摩擦,又发生雷电,这两个不断地互相转化。这两个卦的位置相对,名为“对宫卦”。古人说“宫”,就是位置、方位。同样,艮 ☶ 的对宫卦兑 ☱ 。\n其次要注意的,是先天八卦图的“数”,乃依据八卦排列的秩序产生的。“数”在《易经》里是很奇妙的,人们在遇到不如意的事之后,往往认为这些事的发生,是有定数的。\n乾一 → 兑二 → 离三 → 震四,此为‘顺’。巽五 → 坎六 → 艮七 → 八坤,必为‘逆’。九在中央。\n兑二 乾一 巽五 离三 九中 坎六 震四 坤八 艮七 乾三连,坤六断,震仰盂,艮覆碗,离中虚,坎中满,兑上缺,巽下断。\n这八个大卦,是古人告诉我们,天地间就是这八大现象在变化,这些图案都是相对的。\n后天八卦 假使学《易经》学到需要在某一方面应用,而且用得有功效,就要特别研究后天八卦了。“先天八卦”等于是表明宇宙形成的那个大现象,“后天八卦”是说明宇宙以内的变化和运用的法则。\n一数坎兮二数坤,三震四巽数中分,五寄中宫六乾是,七兑八艮九离门。\n巽四 离九 坤二 震三 五中 兑七 艮八 坎一 乾六 监本《易经》 《易经》为什么不容易看懂?因为对象、数方面没有基本的认识,所以必须把《易经》的象认识清楚。宋以后的《易经》注解,多数是走物理的路线,就是用儒家的学术思想来解释《易经》,明以后《易经》监本,是明朝以后那些儒家采用了朱熹的思想编的。故《易经》的理不必太偏重它,但并不是不重视。譬如乾卦,朱熹认为是那样,我们亦可以认为是这样,各有各的理,正理只有一条,歪理可有千条。而《易》的象与数,却是科学的,没有办法讲歪的,就非要学会它的规矩、法则,才能懂得《易经》。\n六十四卦的来源 《易》的象、数,该如何开始学起?从中国学术史上看,唐宋以前,还没有分宫卦象次序,宋以后,才把这个次序列出来。这个次序的排列,是有一定道理的,是由每一卦变化出了八个卦,八个卦变成六十四卦。\n先天卦,有三爻,名三爻卦,是伏羲画的卦,亦是中国有文字的开始。后来人类社会越发展,人事越复杂,三爻卦已经不够用,就变成了六爻卦。后天卦统统是六爻的图案,这六爻卦是很精细的,亦是很科学的。\n为什么要用六爻?因为一直到现代的科学时代为止,宇宙间的事情、物理,没有超过六个阶段的。一切的变,只能变到第六个阶段,第七个变是另外一个局面开始。孔子在《系传》中说六爻的道理是:“六爻之动,三极之道也。”什么是“三极”?就是天地人三才。三才有阴阳相对,三二就得六,这是孔老夫子的心得报告,几千年来,没有脱离他的范围。\n:: 三才各有阴阳相对,所以成六爻,而非是九爻~\n要注意,画八卦是从下面画起。那《易经》的卦为什么要这样画?第一个道理,天下的事情发生变动,都是从下面开始变,换言之是从基层变起;第二个道理,《易经》的卦,原来只是三爻,后来变成六爻,名称上就有了分别:下面三爻的卦为‘内卦’,上面三爻的卦为‘外卦’,内外两卦相连起来。自下面开始画卦,亦说明了宇宙事物的变。一切东西都是从内变开始,所以画卦是由下往上,由内而外。\n物必自腐,然后虫生。\n#+caption: 分宫卦象次序\n乾为天 天风姤 天山遁 天地否 风地观 山地剥 火地晋 火天大有 坎为水 水泽节 水雷屯 水火既济 泽火革 雷火丰 地火明夷 地水师 艮为山 山火贲 山天大畜 山泽损 火泽睽 天泽履 风泽中孚 风山渐 震为雷 雷地豫 雷水解 雷风恒 地风升 水风井 泽风大过 泽雷随 巽为风 风天小畜 风火家人 风雷益 天雷无妄 火雷噬嗑 山雷颐 山风蛊 离为火 火山旅 火风鼎 火水未济 山水蒙 风水涣 天水讼 天火同人 坤为地 地雷复 地泽临 地天泰 雷天大壮 泽天夬 水天需 水地比 兑为泽 泽水困 泽地萃 泽山咸 水山蹇 地山谦 雷山小过 雷泽归妹 乾坎艮震为阳四宫,巽离坤兑为阴四宫,每宫阴阳八卦。\n:: 顺序为后天八卦,自乾卦始,前四卦其数为奇,为阳四宫;后四卦其数为偶,为阴四宫。\n乾宫的八个卦就是这样变得,简单地说,分宫卦象次序的变就是这样的:一、本体卦,二、初爻变,三、第二幺变,四、第三幺变,五、第四爻变,六、第五爻变,七、第四爻变回原爻(游魂),八、内卦变回本体卦(归魂)。\n:: 八卦中,每卦为三爻,最多三变,三变成错。如,乾为天,一变风,二变山,三变地;坤为地,一变雷,二变泽,三变天。如此类推,再言水火,坎为水,一变泽,二变雷,三变火;离为火,一变山,二变风,三变水。其余四卦如是。\n:: “天风山(地雷泽),山火天(泽水地)。火山风(水泽雷),风天火(雷地水)。” 这样记也可以,三变相错,只记前两变就可以了。\n错综复杂 “错综复杂”的语源,来自《易经》。不三不四也是根据《易经》而来,因为《易经》中的第三爻、第四爻最重要,这两爻在卦的正中间,亦是中心的位置,如果一个人不成样子,就被形容为“不三不四”。又如“乱七八糟”,即是从游魂卦、归魂卦来的,中国人处处都在引用《易经》的话,只是自己不知道而已。\n错综 – 相对与反对 卦的错综复杂是什么意思?现在先说综卦,以乾卦为例来说明。乾卦的第一爻变为姤卦,如果把这个卦倒转过来看,就成了夬卦,这就是姤卦的综卦。\n综卦 是相对的,全部六十四卦,除了八个卦以外,没有不相对的,这综卦是象,而综卦的理,是告诉我们万事要客观,因为立场不同,观念就完全两样。另外有八个卦是绝对的,无论单方面看或相对地看,都是同一个样子,这八个卦就是 乾、坤、坎、离、大过(泽风)、小过(雷山)、颐(山雷)、中孚(风泽),除此之外,其余五十六卦都是相对的,这表明宇宙间事物都是相对的,这就是综卦的道理。\n:: 六十四卦,八绝(同)余相(综),皆相错之,三五为交,二四为互。\n错卦 是阴阳交错的意思,错卦的理是立场相同,目标一致,可是看问题的角度不同,所见也就不同了。\n天风姤卦,它的第一爻是阴爻,其余五爻都是阳爻,那么在阴阳交错之后,就变成了地雷复卦,所以天风姤卦的对错卦就是地雷复卦。六十四卦,每卦都有对错的。因此学了《易经》以后,以《易经》的道理去看人生,一举一动,都有相对、正反、交错,有得意就有失意,有人赞成就有人反对,人事物理都一定是这样的,离不开这个宇宙的大原则。\n综卦可以称之为反对的或相对的,错卦可称之为正对的。有人说《易经》动辄有黑格尔的辩证法的思想,他说的正、反、合,就是《易经》的原则,这是乱讲。他们说黑格尔的正、反、合是三段论法,我告诉他们《易经》是八段论法,比起来黑格尔就显得粗糙得很,又算得了什么。《易经》看东西是八面玲珑得。现在已经看了四面了,仍以天风姤卦来说,综卦是泽天夬,错卦是地雷复,而复卦亦有它的综卦,就是山地剥,这岂不是看了四面,所以《易经》的头脑,一件事初到手,处理起来,四面都要注意到,不但要注意四面,还要八面玲珑。\n复杂的道理 《易经》还有一个道理 – 复杂,亦即等于 交互卦 的道理,我们都讲究互助,这个互象就是《易经》的图案,像同样的挂钩交相挂住,就是一个“互”字。什么是“交互”?就是六爻内部的变化,如第二爻上连到第四爻,下面挂到上面去为互,第五爻下连到第三爻,上面交至下面来为交,这就是交互的不同,每卦的纵深内在,发生了交互的变化,又产生了卦。换句话说,这是告诉我们看事情,不要看绝了,不要只看一面,一件事情正面看了,再看反面,反面看了,再把旁边看清楚,同时旁边亦要看反面,这样四面都注意到了,这还不算完备,因为内在还有变化,而内在的变化,又生出一个卦了。除了乾、坤两卦外,别的卦把重心拿出来交互,又变了一种现象。这现象的本身,又有综卦,又有错卦,这就是八面看东西,还要加上下一共十面。\n交互卦 现在谈交互卦,以火雷噬嗑为例说明如下:火雷噬嗑的第二爻、第三爻、第四爻卦配上去,便成为 ☶ 代表山的艮卦,这就是噬嗑卦的互卦;又把噬嗑卦的第三爻、第四爻、第五爻配上去,便成为 ☵ 卦,这就是噬嗑卦的交卦。再把噬嗑卦的交卦 ☵ 和互卦 ☶ 重叠起来,便成为水山骞卦,于是我们知道,噬嗑卦的交互卦就是蹇卦。\n至于复杂,复就和综卦一样,是重复的意思,杂是指彼此的相互关系,六十四卦可发展到无数的卦,每一卦牵一发而动全身,都有彼此相互的关系。\n周易六十四卦的排列,并不是照八宫卦象的次序。它的排列次序,是周文王研究《易经》所整理出来的一个学术思想系统,后人编之成歌 – 《上下经卦名次序歌》。\n乾坤屯蒙需讼师,比小畜兮履泰否,\r同人大有谦豫随,蛊临观兮噬嗑贲,\r剥复无妄大畜颐,大过坎离三十备。\r咸恒遁兮及大壮,晋与明夷家人睽,\r蹇解损益夬姤萃,升困井革鼎震继,\r艮渐归妹丰旅巽,兑涣节兮中孚至,\r小过既济兼未济,是为下经三十四。 六十四卦的方圆图 ![[assets/pasted image 20230526102028.png]]\n这个方圆图,圆图是管宇宙的时间,代表宇宙的运行法则,亦可以说代表太阳系统时间运行的法则或原理,圆图中的方图管空间,代表方位方向,这就是前人的秘诀了。\n方图 #+caption: 六十四卦方图数字图\n8/8 7/8 6/8 5/8 4/8 3/8 2/8 1/8 坤 8 8/7 7/7 6/7 5/7 4/7 3/7 2/7 1/7 艮 7 8/6 7/6 6/6 5/6 4/6 3/6 2/6 1/6 坎 6 8/5 7/5 6/5 5/5 4/5 3/5 2/5 1/5 巽 5 8/4 7/4 6/4 5/4 4/4 3/4 2/4 1/4 震 4 8/3 7/3 6/3 5/3 4/3 3/3 2/3 1/3 离 3 8/2 7/2 6/2 5/2 4/2 3/2 2/2 1/2 兑 2 8/1 7/1 6/1 5/1 4/1 3/1 2/1 1/1 乾 1 坤 8 艮 7 坎 6 巽 5 震 4 离 3 兑 2 乾 1 ←/↑ 这六十四卦的方图,变化无穷。这个方图的数字,则是这样一纵一横,慢慢向上走的,构成了如此错综复杂的关系。可是亦是同时告诉我们,宇宙间的万事万物,看来是非常复杂,但懂了《易经》以后,从《易经》的观点,任何乱七八糟的事物,都有它的法则。在关键上轻轻一点,问题就解决了。\n圆图 围绕在这个方图外的圆图,亦是六十四卦,圆图是代表时间,和代表空间的方图配起来,某一空间在某一时间会起作用。那么这个圆图的六十四卦,是用什么方法排列起来的呢?\n圆图最上面左边第一个卦是乾卦,最下面右边第一个卦是坤卦,在这乾、坤之间有一条线,代表夜间天空中的银河,亦代表地球南极、北极的磁场,然后再来排列圆图。自下而上前四横,自右向左依次排列,直到坤;自上而下,后四横,自左向右依次排列,直到乾。\n学《易》之前,先善上事。\n附录+ 太极 阴爻 阳爻 太阳 少阴 少阳 太阴 ☯ ⚋ ⚊ ⚌ ⚍ ⚎ ⚏ \u0026gt; 太极、阴阳(两仪)、四象\n乾 兑 离 震 巽 坎 艮 坤 ☰ ☱ ☲ ☳ ☴ ☵ ☶ ☷ \u0026gt; 八卦\n䷀ ䷁ ䷂ ䷃ ䷄ ䷅ ䷆ ䷇ 乾为天 坤为地 水雷屯 山水蒙 水天需 天水讼 地水师 水地比 ䷈ ䷉ ䷊ ䷋ ䷌ ䷍ ䷎ ䷏ 风天小畜 天泽履 地天泰 天地否 天火同人 火天大有 地山谦 雷地豫 ䷐ ䷑ ䷒ ䷓ ䷔ ䷕ ䷖ ䷗ 泽雷随 山风蛊 地泽临 风地观 火雷噬嗑 山火贲 山地剥 地雷复 ䷘ ䷙ ䷚ ䷛ ䷜ ䷝ ䷞ ䷟ 天雷无妄 山天大畜 山雷颐 泽风大过 坎为水 离为火 泽山咸 雷风恒 ䷠ ䷡ ䷢ ䷣ ䷤ ䷥ ䷦ ䷧ 天山遁 雷天大壮 火地晋 地火明夷 风火家人 火泽睽 水山蹇 雷水解 ䷨ ䷩ ䷪ ䷫ ䷬ ䷭ ䷮ ䷯ 山泽损 风雷益 泽天夬 天风姤 泽地萃 地风升 泽水困 水风井 ䷰ ䷱ ䷲ ䷳ ䷴ ䷵ ䷶ ䷷ 泽火革 火风鼎 震为雷 艮为山 风山渐 雷泽归妹 雷火丰 火山旅 ䷸ ䷹ ䷺ ䷻ ䷼ ䷽ ䷾ ䷿ 巽为风 兑为泽 风水涣 水泽节 风泽中孚 雷山小过 水火既济 火水未济 \u0026gt; 六十四卦\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E6%98%93%E7%BB%8F%E6%9D%82%E8%AF%B4/","summary":"🔔 文中内容,多数摘录自南怀瑾先生的《易经杂说》。 书不尽言,言不尽意。自觉圣智,完成人格。 – 南怀瑾 洁净精微 “洁净精微,易教也。” “洁净”包括了宗教的、哲学的含义,","title":"易经杂说"},]
[{"content":"我理解中的极简主义,核心“断、舍、离”,理念“少即是多”,重点“有序”。\n极简主义是一种生活和思考模式,实现它的方式有很多, [[gtd 管理系统]] 是其中一种,也是目前我感觉比较好的一种。它很有趣,但并不容易坚持,毕竟形成一种全新的生活习惯是一个长期的过程。开始很容易,难在持续。\n真正的“少” 为什么强调“少”?是因为,日常生活中往往充斥了过多不必要的东西,它们毫无意义,却占用了过多的时间,消耗掉过多的精力。所以,我们需要强调“少”,一般情况下,这样做总是没有错的。但是也不要走入另一个极端,我们应该始终明白,之所以强调“少”,是因为“过多了”,而不是因为要“足够少”。切忌为了“少”而少,适度、平衡才是我们真正的追求。\n现实生活中没有遇到过,网上到是见了不少“极简主义”的拥趸者,他们往往大肆鼓吹宣扬过度的“少”,有甚者还以清空所有家具为荣。也许,他们是真正的享受吧,但我并认为这是可持续的。如同,我认为吃面要用筷子而不是勺子,喝汤要用勺子而不是筷子一样。再次强调,真正的“少”,归于平衡、有序,而不是“空”。\n极简主义的目标在于形成一个“最少需要”,你可拥有很多,但我们说的“最少”是一个临界,比“最少”再少一些,就会带来不便,比“最少”再多一点,虽然不会带来不便,但可能产生毫无意义的消耗。“最少需要”的观念是也只能是一个指导,因人而异,要根据自己当前的现状去调整,持续性的调整,以达到一个相对舒适和谐的生活节奏。\n“最少需要”是动态的,其内容在不断地更新,但原则是一致的 – 但(仅)取所需。精简是一个过程,这过程本身就是一种享受。结果是重要的,但结果也只不过是阶段性的一个时刻,是水到渠成,是自然而然。\n有序 gtd 之所以是一种优秀的极简主义实现方式,在于其“应收尽收,拒绝悬而未决”的品质。它使得你生活中的事务更加系统化、层次化,有主次、分轻重缓急,非常有助于“减少”不必要的消耗。你知道自己当下最适合做的事情是什么,焦虑自然而然地远离你。因为我们焦虑的原因往往不在于要做的事很多,而在于不知道要做什么。\n安全感和掌控来自“有序”,“有序”是系统性的收集、组织和管理的结果。\n未完待续……\n《 [[再谈个人管理]] 》\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E6%9E%81%E7%AE%80%E4%B8%BB%E4%B9%89%E7%94%9F%E6%B4%BB/","summary":"\u003cp\u003e我理解中的极简主义,核心“断、舍、离”,理念“少即是多”,重点“有序”。\u003c/p\u003e\n\u003cp\u003e极简主义是一种生活和思考模式,实现它的方式有很多, [[GTD 管理系统]] 是其中一种,也是目前我感觉比较好的一种。它很有趣,但并不容易坚持,毕竟形成一种全新的生活习惯是一个长期的过程。开始很容易,难在持续。\u003c/p\u003e","title":"极简主义生活"},]
[{"content":" 很强大,也让人很头大…… 这只是一个精简的摘要,相信我,即使总结的再全面,一段时间之后,也需要借助参考手册,才能正确的使用它。\n简介 正则表达式是什么?一个表达式,它定义了一种规则,用来匹配(符合规则的)字符串。\n正则表达式本身也是用字符串表示的,如何表示呢?\n:: 必须搭配例子一起食用,味道才好 😸 ……\n简单匹配 1.精确匹配\n在正则表达式中,如果直接给出字符,就是精确匹配。其他,还有:\n元字符 说明 \\d 匹配一个数字 \\w 匹配一个字母或数字 \\s 匹配一个空白符 . 匹配任意字符 当然,还有其他的,用的时候再查好了,反正是记不住的。\n看一些示例:\n'00\\d' 可以匹配 '007' ,但无法匹配 +'00a'+ ; '\\d\\d\\d' 可以匹配 '010' ; '\\w\\w\\d' 可以匹配 'py3' ; 'py.' 可以匹配 'pyc'、 'pyo'、 'py!' 等等。 2.匹配变长字符\n元字符 说明 * 匹配任意个字符 (0 或 0+) + 匹配至少一个字符 (1 或 1+) ? 匹配 0 或 1 个字符 {n} 匹配 n 个字符 {m,n} 匹配 (m 到 n) 个字符 看一些示例:\n\\d{3} 表示匹配 3 个数字,如 '010' ; \\s+ 表示至少有一个空格,可以匹配 ' '、 ' ' 等; \\d{3,8} 表示匹配 3-8 个数字,如 '123'、 '12345'、 '12345678' 等。 如果要匹配 010-12345 这样的号码呢?由于 - 是特殊字符 ,在正则表达式中,要用 \\ 转义,故上述的答案是 \\d{3}\\-\\d{3-8} 。\n如果,还不够,继续……\n更精确的匹配 要做更精确的匹配,可以用 [] 表示范围,如 [0-9a-za-z\\_] 可匹配一个数字、字母或者下划线。 其他,有:\n元字符 说明 (a|b) 匹配 a 或 b ^ 表示行的开头 $ 表示行的结束 再来看一些示例:\n[a-za-z\\_][0-9a-za-z\\_]* 可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串(看,这不就是 python 的合法变量嘛); (p|p)ython 可以匹配 'python' 或者 'python' 。 进阶 分组 除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用 () 表示的就是要提取的分组(group)。\n比如, ^(\\d{3})-(\\d{3-8})$ 分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码。\n1 2 3 4 5 6 7 m = re.match(r\u0026#39;^(\\d{3})-(\\d{3-8})$\u0026#39;, \u0026#39;010-12345\u0026#39;) m.group(0) # \u0026#39;010-12345\u0026#39; m.group(1) # \u0026#39;010\u0026#39; m.group(2) # \u0026#39;12345\u0026#39; # group(0) 永远是与整个正则表达式相匹配的字符串 # group(1)、group(2)... 表示第 1、2 个子串 5.贪婪匹配\n需要注意的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符,加个 ? 可以切换成非贪婪匹配。\n比如,我们要匹配出数字后面的 0 :\n1 re.match(r\u0026#39;^(\\d+)(0*)$\u0026#39;, \u0026#39;102300\u0026#39;).groups() # → (\u0026#39;102300\u0026#39;, \u0026#39;\u0026#39;) 由于 \\d+ 默认采用贪婪匹配,直接把后面的 0 全部匹配了,结果 0* 只能匹配空字符串了。\n必须让 \\d+ 采用非贪婪匹配(也就是尽可能少的匹配),才能把后面的 0 匹配出来,如下:\n1 re.match(r\u0026#39;^(\\d+?)(0*)$\u0026#39;, \u0026#39;102300\u0026#39;).groups() # → (\u0026#39;1023\u0026#39;, \u0026#39;00\u0026#39;) 结语 以上内容主要来源于 廖雪峰老师的博客中关于正则表达式的章节 ,很短但很精致。如果,你想进一步练习的话,推荐:\nhttps://regexr.com/ https://regexr-cn.com/ (中文站) ","date":"2023-05-26","permalink":"https://aituyaa.com/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/","summary":"\u003cblockquote\u003e\n\u003cp\u003e很强大,也让人很头大…… 这只是一个精简的摘要,相信我,即使总结的再全面,一段时间之后,也需要借助参考手册,才能正确的使用它。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"正则表达式"},]
[{"content":"linux 下源代码的编译安装,及其编译过程和可能遇到的问题。\n![[assets/pasted image 20230526085019.png]]\n如何编译安装 1\n下载源码包,解压、进入源代码目录后,执行以下命令:\n1 2 3 4 ./configure make make install # 多数时候需要 `sudo` 以上是典型的使用 gnu 的 autoconf 和 automake 产生的程序的安装步骤。\n如何理解编译过程 2 3\n./configure configure 脚本是用来检查当前安装平台的开发环境,比如是不是有 cc 或 gcc ,也用来供用户指定程序包的编译参数、启用特性、安装路径等等。\n一般用来生成 makefile ,为下一步的编译做准备。 如下所示,可以通过在 configure 后加上参数来对安装进行控制,比如:\n1 ./configure --prefix=/usr 意味着将该软件安装在 /usr 下面,执行文件就会安装在 /usr/bin (而不是默认的 /usr/local/bin ),资源文件就会安装在 /usr/share (而不是默认的 /usr/local/share )。\n# 通用的几个选项 --prefix= # 指定安装的路径 --sysconfdir= # 指定配置文件目录 --enable-feature= # 启用某个特性 --disable-feature # 禁用某个特性 --with-function # 启用某个功能 --without-function # 禁用某个功能 你可以通过 ./configure --help 查看更多。\nmake make 是 linux 开发套件里面自动化编译的一个控制程序。每个源代码都有专用的 makefile ,在执行 make 时依据 makefile 这个配置文件,调用指定的预处理器做处理、调用指定的编译器做处理、编译文件的顺序操作等。\n一般情况下,它所使用的 makefile 控制代码是由 configure 这个设置脚本根据给定的参数和系统环境生成的。\ncmake 5\ncmake 就是一个与 make 同级别的编译工具,只不过它依靠的不是 makefile 作为编译规则,而是根据 cmakelists.txt 来编译的。它比 make 更高级,可以根据不同平台、不同的编译器,通过编写 cmakelists.txt ,可以控制生成的 makefile ,从而控制编译过程。\n![[assets/pasted image 20230526085142.png]]\n如果有嵌套目录,子目录下可以有自己的 cmakelists.txt 。\nmake install 这条命令用来进行安装(当然有些软件需要先运行 make check 或 make test 来进行一些测试),一般需要你有 root 权限(因为要向系统写入文件)。\n其实是一些脚本,根据 makefile 文件中的设置将编译完成的文件安装到预定目录,如将创建出的二进制文件放到指定的二进制目录、库文件放到指定的库目录等等。\n扩展 - c/c++ 编译过程 4\n![[assets/pasted image 20230526085149.png]]\n相关名词 编译 ,是读取源程序(字符流),对之进行词法和语法的分析,将高级语言转换为等效的汇编代码,再转换为机器代码,保存到目标文件 *.obj 中(如果编译通过)。\n链接,是将有关的目标文件(库文件、 .o 文件)彼此互相连接,即在一个文件中引用的符号同在另一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。\n编译过程 编译分为两个过程: 预处理阶段和编译、优化阶段 。\n预处理阶段:\n宏 #define ; 条件编译指令,如 #ifdef, #ifndef, #else, #elif, #endif 等; 头文件包含 #include \u0026lt;iostream\u0026gt; ; 特殊符号,如 line 标识被解释为当前行号, file 被解释为当前被编译的 c 源程序的名称。 编译、优化阶段:\n针对代码优化,不依赖具体计算机; 针对计算机的优化。 汇编把汇编语言代码翻译成目标机器指令,生成目标文件( .o 或 .obj 文件),至少需要表提供 3 张表:\n导出符号表 - 该目标文件可以提供的符号及地址; 未解决符号表 - 该目标文件告诉链接器哪些符号没找到地址; 地址重定向表。 链接的时候,链接器会为目标文件的 未解决符号表 里的符号在其他文件里寻找地址,但是每个目标文件的地址都是从 0x0000 开始的,这就导致直接将对方文件中符号拿过来用会是不正确的。为了区分不同的文件,链接器在链接时就会结每个目标文件的地址进行调整,如为 .o 导出的符号地址都加上起始地址。然而,因为加上了起始地址,符号在自身文件中的实际地址就不对了,需要再用一张地址重定向表记录符号相对自身文件的地址。\n链接过程 链接方式分为:静态链接和动态链接。\n静态链接 ,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中,这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。\n动态链接 ,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所做的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚拟地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。\ntodo 进阶 - linux 下源码编译安装详解 6\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/","summary":"\u003cp\u003eLinux 下源代码的编译安装,及其编译过程和可能遇到的问题。\u003c/p\u003e","title":"源码编译安装"},]
[{"content":"简谱 2 是指一种简易的记谱法,在字母简谱和数字简谱两种。其起源于 18 世纪的法国,后经德国人改良,遂成今日之貌。一般所称的简谱,系指数字简谱。\n数字简谱以可动唱名法为基础,用 1、2、3、4、5、6、7 代表音阶中的 7 个基本级,读音为 do、re、mi、fa、sol、la、si ,英文由 c、d、e、f、g、a、b 表示,休止以 0 表示。每一个数字的时值相当于五线谱的 4 分音符。\n基本要素 一般来说,所有音乐的构成有四个基本要素,而其中最重要的是“音的高低”和“音的长短”。\n1. 音的高低\n任何一首曲子都是高低相间的音组成的,从钢琴上直观看就是越往左面的键盘音越低,越往右面的键盘音越高。就数字简谱来说,在数字的正上方或下方有若干个点 . 、 : 符,点的多少可以理解为强度,越多越强,在其上方代表高音,在其下方代表低音。原理表现为物体振动频率,越高音振动频率越高,反之亦然。\n2. 音的长短\n除了音的高低外,还有一个重要的因素就是音的长短。音的高低和长短的标注决定了该曲子有别于另外的曲子,因此成为构成音乐的最重要的基础元素。原理表现为声音的时值长短。\n3. 音的力度\n音乐的力度很容易理解,也叫强度。一首音乐作品总是会有一些音符的力度比较强一些,有些地方弱一些。而力度的变化是音乐作品中表达情感的因素之一。原理表现为振幅的大小。\n4. 音质\n也可以称为音色。也就是发出音乐的乐器或人声。同样旋律的音高,男生和女生唱就不一样的音色,小提琴和钢琴的音色就不一样。\n上述四项构成了任一首乐曲的基础元素,应该说简谱基本可以将这些基础元素确标注。\n![[assets/pasted image 20230526101120.png]]\n基础知识 1. 音符\n音符,表示音的高低的基本符号,用七个阿拉伯数字标记,它们的写法和读法如下:\n写法 1 2 3 4 5 6 7 i 读法 do re mi fa sol la si do 哆 来 米 发 索 拉 西 哆 以上各音其相对关系都是固定的,除了 3--4、7--i 是半音外,其它相邻两个音都是全音。\n音符是和音高紧密相连的,没有一个不带音高的音符。\n音符是数字符号如 1、2、3、4、5、6、7 就表示不同的音高,广义上说音乐里总共就有 7 个音符。\n1.1 音符高低\n为了标记更高或更低音,则在基本符号的上面或下面加上小圆点。在简谱中,不带点的基本符号叫做中音;在基本符号上面加上一个点叫高音,加两个点叫做倍高音,加三个点叫超高音;在基本符号下面加一个点叫低音,加两个点叫倍低音,加三个点叫超低音。\n记在简谱基本音符号下面的小圆点,叫低音点,它表示将基本音符降低一个音组,取降低一个纯八度。记两个圆点,表示将基本音符号降低两个音组,即降低两个纯八度。\n记在简谱基本音符号上面的小圆点,叫高音点,它表示将基本音符号升高一个音组,即升高一个纯八度。记两个圆点,表示升高两个音组,即升高两个纯八度。\n1.2 音符长短\n音乐中的音符除了有高低之分外,当然还要表示长短之分。这里引用一个基础的音乐术语 – 拍子。拍子是表示音符长短的重要概念。\n表示音乐的长短需要有一个相对固定的时间概念。简谱里将音符分为全音符、二分音符、四分音符、八分音符、十六分音符、三十二分音符等。\n在这几个音符里面最重要的是四分音符,它是一个基本参照度量长度,即四分音符为一拍。这里的一拍是一个相对时间度量单位。一拍的长度没有限制,可以是 1 秒也可以是 2 秒或半秒。假如一拍是一秒的长度,那么二拍就是两秒;一拍定为半秒的话,两拍就是一秒的长度。一旦这个基础的一拍定下来,那么比一拍长或短的符号就相对容易了。\n用一条横线 - 在四分音符的右面或下面来标注,以此来定义音符的长短。下面列出了学用音符和它们的长度标记:\n音符名称 写法 时值 全音符 x--- 四拍 二分音符 x- 二拍 四分音符 x 一拍 ————– ——– ———— 八分音符 x 半拍 - ————– ——– ———— 十六分音符 x 四分之一拍 = ————– ——– ———— 三十二分音符 x 八分之一拍 ≡ 通过上表可以看出:横线有标注在音符后面的,也有标注在音符下面的,横线标记的位置不同,被标记的音符的时值也不同。从表中可以发现一个规律,就是:要使音符时值延长,在四分音符右边加横线 - ,这时的横线叫延时线。延时线越多,音持续的时间(时值)越长。相反,音符下面的横线越多,则该音符时间越短。\n1.3 半音与全音\n![[assets/pasted image 20230526101147.png]]\n音符与音符之间是有“距离”的,这个距离是一个相对可计算的数值。在音乐中,相邻的两个音之间最小的距离叫半音,两个半音距离构成一个全音。表现在钢琴上就是钢琴键盘上紧密相连的两个键盘就构成半音,而隔一个键盘的两个键盘就是全音。\n1.4 附点音符\n附点就是记在音符右边的小圆点,表示增加前面音符时值的一半,带附点的音符叫做附点音符。\n1.5 反复记号\n表示记号内的曲调反复唱(奏)。\n1.6 休止符\n音乐中除了有音的高低,长短之外,也有音的休止。表示声音休止的符号叫 休止符 ,用 0 标记。通俗点说就是没有声音,不出声的符号。\n休止符与音符基本相同,也有六种。但一般直接用 0 代替增加的横线,每增加一个 0 ,就增加一个四分休止符时的时值。\n2. 增时线和减时线\n简谱中,音的长短是在基本音符的基础上加短横线、附点、延音线和连音符号表示的。\n短横线的用法有两种:写在基本音符右边的短横线叫 增时线 ,增时线越多,音的时值就越长。不带增时线的基本音符叫四分音符,每增加一条增时线,表示延长一个四分音符的时间。写在基本音符下面的短横线叫 减时线 ,减时线越多,音就越短,每增加一条减时线,就表示缩短为原音符音长的一半。\n写在音符右边的小圆点叫做 附点 ,表示延长前面音符时值的一半。附点往往用于四分音符和少于四分音符的各种音符,带附点的音符叫附点音符。\n3. 变化音\n临时改变音的高低的符号叫临时变音记号,主要有升号、降号、还原记号等。升号定在音符左上方,表示该音要升高半音,如 #1 表示将 1 升高半音,在吉他上的奏法就是向高品位方向进一格。降号写在音符左上方,表示该音要降低半音,如 b3 表示将 3 降低半音,在吉他上的奏法就是向低品位方向退一格,空弦音降半音就要退到低一弦上去。还原号是将一小节内 # 或 b 过的某个音回到原来的位置。\n以上临时变音记号都是一小节内才起作用,过了这小节就不起作用了,一小节也就是 |xxxx| 。\n将标准的音符升高或降低得来的音,就是变化音。将音符升高半音,叫升音,用 # (升号) 来表示;将标准的音降低半音,叫降音,用 b (降号)表示;基本音升高一个全音叫重升音,用 x (重升)表示;基本音降一个全音叫重降音,用 bb (重降音)表示;将已经升高(包括重升)或降低(包括重降)的音,要变成原始的音,则用还原记号 ♮ 表示。\n4. 调号\n![[assets/pasted image 20230526101232.png]]\n按照一定的次序和位置记在谱号的后面的记号叫做 调号 。调号总是只用同类的变音记号,即升记号或降记号。简谱的调号一般是用 1 等于 a、b、c、d、e、f、g 来表示,如 1c则表示该简谱是 c 调来记谱,如果要表示升降号的调,则在字母前加#号或者b` 号,可以参考一些简谱图片示例。\n5. 节奏和节拍\n掌握读谱,首先要掌握节奏,练习掌握节奏要能准确的击拍。击拍的方法是:手往下拍是半拍,手掌拿起有半拍,一下一上是一拍。\n乐曲或歌曲中,音的强弱有规律地循环出现,就形成节拍。节拍和节奏的关系,就像列队行进为整齐的步伐(节拍)和变化着的鼓点(节奏)之间的关系。\n单拍指每小节一个强拍。复拍每小节有一个强的,有若干个次强的。\n6. 调式音阶\n按照一定关系结合在一起的几个音(一般是七个音左右)组成一个有主音(中心音)的音列体系,构成一个调式。\n把调式中的个音,从主音到主音,按一定的音高关系排列起来的音列,叫 音阶 。\n6.1 大调式\n![[assets/pasted image 20230526101309.png]]\n凡是音阶排列符合 全、全、半、全、全、全、半 结构的音阶,就是自然大调。这是使用的最广泛的调式。\n一般来说,一首音乐作品的开始音符是使用 1、3 或 5 的,而结束在 1 上的就是大调音乐,比如国歌就是大调音乐。要想真正搞懂大调音乐,必须要学习和声知识。\n6.2 小调式\n小调式有三种形式:\n自然小调:凡是音阶符合 全、半、全、半、全、全 结构的音阶,叫自然小调; 和声小调:升高自然小调音阶的第 vii 级,叫和声小调; 旋律小调:在自然小调音阶上行时升高它的 4、5 ,而下行时还原 5、4 叫旋律小调。 小调音乐一般第一个音符是从 6 或 3 开始,而结束在 6 上。比如《莫斯科郊外的晚上》就是小调音乐。同大调一样,要想真正搞懂小调音乐,必须要学习和声知识。\n7. 装饰音\n装饰音的作用主要是用来装饰旋律。它们用记号或小音符表示,装饰音的时值很短。有:\n倚音:指一个或数个依附于主要音符的音,倚音时值短暂。有前倚音、后倚音之分; 颤音:由主要音和它相邻的音快速均匀地交替演奏; 波音:由主要音和它上方或下方相邻的音快速一次或再次交替而成; 滑音:主要音向上或向下滑向某个音。滑音分上滑音、下滑音两种。滑音除声乐演唱这一技巧外,一切弦乐器都可演奏,但钢琴等键盘乐器是无法演奏这一技巧的。 8. 其他相关符号\n8.1 顿音记号\n用三角符标记在音符的上面,表示这个音要唱(奏)得短促、跳跃。\n8.2 连音线\n用上弧线标记在音符的上面,它有两种用法:\n延音线:如果是同一个音,则按照拍节弹奏完成即可,不用再弹奏; 连接两个以上不同的音,也称圆滑线,要求唱(奏)得连贯、圆滑。 8.3 重音记号\n用 \u0026gt; 或 □ 或 sf 标记在音符的上面,表示这个音要唱(奏)得坚强有力;当 \u0026gt; 与 □ 两个记号同时出现时,表示更强。\n8.4 保持音记号\n用 - 标记在音符的上面,表示这个音在唱(奏)时要保持足够的时值和一定的音量。\n8.5 小节线\n用竖线将每一小节划分开线叫小节线。\n8.6 终止线\n终止线是在乐曲最后,将要结束的地方,这里就不能单纯的画小节线,而是要区别一般的小节线来表示,这种表示的方法是用两条竖线,其中一条细线,一条粗线并行,细的一条在前,粗的一条在后,这叫“终止线”,表明乐曲终了。\n8.7 换气号\n用记号 v 标记,不服水土在此处换气。\n乐理识谱 1\n基本乐理是学习乐器的基础。初学者学习笛箫等乐器,在能够吹响并熟练掌握指法后,除了需要重点学习和掌握吹奏技巧和基本功如口风控制,气息把握等,还需要对基本乐理进行学习,因为想学好乐器,唱谱、节奏很重要。本文重点讲述基本乐理与识谱。\n![[assets/pasted image 20230526101402.png|250]]\n笛箫类乐器不同于钢琴等乐器,除少数专业的笛子谱用五线谱记谱外,一般笛箫大多用简谱记谱。简谱所适用的规则,在笛箫谱上同样适用。但是笛箫谱不仅具有简谱的一般特征,还具有本身的专业特征。在谱面上还会标注笛箫演奏所涉及的各种演奏技法,如吐音(单吐、双吐)叠音、打音、颤音、指法等,而在单纯的简谱中则不会有这些内容。\n音的高低 在简谱中,用以表示音的高低及相互关系的基本符号为七个阿拉伯数字,即 1、2、3、4、5、6、7 ,唱作 do、re、mi、fa、sol、la、si ,称为 唱名 。单用以上七个音是无法表现众多音乐形象的。在实际作品中,还有一些更高或更低的音,如在基本音符上方加记一个 . ,表示该音升高一个八度,称为高音;加记两个 : ,则表示该音升高两个八度,称为倍高音。在基本音符下方加记一个 . ,表示该音降低一个八度,称为低音;加记两个 : ,则表示该音降低两个八度,称为倍低音。\n![[assets/pasted image 20230526101418.png]]\n音的长短 在简谱中,如果音符时值的长短表示方法如下图:\n![[assets/pasted image 20230526101426.png]]\n带有两个附点的单纯音符为复附点音符,第二个附点表示增长第一个附点时值的一半,即音符时值的四分之一。复附点常用于器乐曲中,在歌曲中很少使用。\n连音线和延音线 延音线 是一条向上或向下弯曲的弧线,其作用是:将两个或两个以上具有相同音高的音符相连,在演唱或演奏时作为一个音符,它的长度等于所有这些音符的总和。在单声部音乐中,连线永远写在和符干相反的方向。\n连音线 表示的是演奏法,可以把几个不同音高的音连在一起,表示为:在钢琴上是连音奏法,表示这几个音要演奏得连贯、圆滑。前者只是相同的音,而后者则不是。\n切分音 除了常用的强弱变化外,还有用“切分法”来进行强弱变化,所谓的切分法就是通过延长弱拍音符的时值使强拍的重音位置向前移到弱拍,改变了乐曲中的“以强拍为重音”的规律这个原弱拍上的音被称为切分音。切分音的记法是在一小节或一拍之内记成一个音,跨小节的记成加连线的两个音。\n休止符 在乐谱中表示音乐的休止(停顿)的符号称为 休止符 。简谱的休止符用 o 表示。休止符是一种不发声的符号,又称为“无声的音符”。在音乐中,休止符一般起句逗作用,并能加强歌(乐)曲的表现力,变化歌(乐)曲的情绪,使曲调的进行表现出对比的效果。\n休止符停顿时间的长短与音符的时值基本相同,只是不用增时线,而用更多的 o 来代替,每增加一个 o ,表示增加一个相当于四分休止符的停顿时间, o 越多,停顿的时间越长。同音符的音长一样,一个 o 表示休止一拍。\n反复记号 音乐反复记号有以下四种:\n第一种:终止线前一个冒号。分两种情况演奏:如果之前有一个终止线,在终止线之后有一个冒号的,则反复这两个反复记号中夹着的一段;如果之前没有反复记号,则从头开始反复。\n第二种:d.c. 记号。意大利语: da capo ,从头反复。当出现 fine 记号时则反复至 fine 记号结束。\n第三种:d.s. 记号。意大利语: dal segno ,从记号处重复。记号为 ※ ,从该记号处重复。\n第四种:反复跳越记号。反复跳越记号是段落反复记号的一种补充。一般有 1、2 两段,弹奏时从头到 1 结束,再从头跳过 1 弹 2 ,然后结束。\n拍子的拍号 将旋律的强拍与弱拍用固定音值进行强弱循环,有规律地组合,称为 拍子 。拍子分为单拍子与复拍子两种。每小节的每一拍由一个完整音符即单纯音符组成的拍子,称为单拍子;每小节的每一拍由一个附点音符或与其等值的音符组成的拍子,称为复拍子。用以表示不同拍子的符号称为拍号。拍号一般标记在调号的后边。例如: 1=c 2/4, 1=g 3/4 。\n标准的乐谱前面会标出速度,例如 “120” 表示每分钟 120 拍,那么 1拍=0.5秒 ,演奏者则需要对节奏(音长)进行掌控,这一点笔者认为都需要不断的学习才能掌握节奏的控制。但很多笛箫曲并不像歌曲一样有明确的拍长,比如曲谱并没有标出拍子的时值,而是标有“自由地”或者“欢快地”、“节奏自由(或者曲谱上有草字头的标记)”等等字眼,这就是笛箫的魅力所在,每个人对曲子的理解不同,可以结合作者的原作和自己的理解,自己把握曲子的节奏,这也并不是节奏全无的演奏,使乐曲的美感完全丧失,演奏者也需要不断的学习才能够演奏出一支富有情感的曲子。\n调与调号 ![[assets/pasted image 20230526101438.png|275]]\n调由两部分组成,即主音的高度与调式类别。如自然大调音阶 1、2、3、4、5、6、7、i , 1(do) 的高音等同于键盘中的 c 音,则此音阶称为 c 自然大调音阶;自然小调音阶 6、7、1、2、3、4、5、6 中, 6(la) 的音高等同于键盘中的 a 音,则此音阶称为 a 自然小调音阶。在简谱中,歌曲、乐曲调的高低均按大调的高低确定,即按憗(do)音的音高确定调的高低。因此 c 大调与 a 小调在简谱中的调号均为 1=c ,它不代表歌曲的调式。\n调号是用以确定歌曲、乐曲(或调子)高度的定音记号。在简谱中,调号是用以确定 1(do) 音的音高位置的符号。例如当一首简谱歌曲为 d 调是,其调号就为 1=d 。\n常用技术符号 笛箫音的强弱是与通过风门送进乐器内的空气量有关的。吹奏同一个音时,一般来说,强奏时的用气量比弱奏时用的气量大。乐谱中符号 p 表示弱音,符号 p 的数量越多表示音越弱;符号 f 表示强音,符号 f 的数量越多表示音越强。渐强符号为 \u0026lt; ,渐弱记号为 \u0026gt; 。其余的笛箫谱中常见的大部分符号如下图所示:\n![[assets/pasted image 20230526101452.png]]\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E7%AE%80%E8%B0%B1/","summary":"\u003cp\u003e简谱 \u003ca href=\"https://baike.baidu.com/item/%E7%AE%80%E8%B0%B1\"\u003e2\u003c/a\u003e 是指一种简易的记谱法,在字母简谱和数字简谱两种。其起源于 18 世纪的法国,后经德国人改良,遂成今日之貌。一般所称的简谱,系指数字简谱。\u003c/p\u003e\n\u003cp\u003e数字简谱以可动唱名法为基础,用 \u003ccode\u003e1、2、3、4、5、6、7\u003c/code\u003e 代表音阶中的 7 个基本级,读音为 \u003ccode\u003edo、re、mi、fa、sol、la、si\u003c/code\u003e ,英文由 \u003ccode\u003eC、D、E、F、G、A、B\u003c/code\u003e 表示,休止以 \u003ccode\u003e0\u003c/code\u003e 表示。每一个数字的时值相当于五线谱的 4 分音符。\u003c/p\u003e","title":"简谱"},]
[{"content":"![[assets/pasted image 20230526111634.png|300]]\ni.e.semantic versioning\n🔔 本文摘录自 语义化版本 2.0.0 | semantic versioning - semver.org\n:: emm… 直接借用文章中的一段话 – 为供他人使用的软件编写适当的文档,是你作为一名专业开发者应尽的职责。*\n摘要 版本格式: 主版本号.次版本号.修订号 ,版本号递增规则如下:\n主版本号:当你做了不兼容的 api 修改, 次版本号:当你做了向下兼容的功能性新增, 修订号:当你做了向下兼容的问题修正。 先行版本号及版本编译信息可以加到“主版本号。次版本号。修订号”的后面,作为延伸。\n简介 在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。\n:: 在维护陈旧的历史项目时尤为让人烦扰……\n在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。\n作为这个问题的解决方案之一,我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。 为了让这套理论运作,你必须先有定义好的公共 api。 这可能包括文档或代码的强制要求。无论如何,这套 api 的清楚明了是十分重要的。一旦你定义了公共 api,你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式: x.y.z(主版本号。次版本号。修订号) 修复问题但不影响 api 时,递增修订号;api 保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。\n我称这套系统为 “语义化的版本控制” ,在这套约定下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。\n语义化版本控制规范(semver) 以下关键词 must、must not、required、shall、shall not、should、should not、 recommended、may、optional 依照 rfc 2119 的叙述解读。\n使用语义化版本控制的软件必须(must)定义公共 api。 该 api 可以在代码中被定义或出现于严谨的文档内。无论何种形式都应该力求精确且完整。 标准的版本号必须(must)采用 x.y.z 的格式,其中 x、y 和 z 为非负的整数,且禁止(must not)在数字前方补零。x 是主版本号、y 是次版本号、而 z 为修订号。每个元素必须(must)以数值来递增。例如: 1.9.1 -\u0026gt; 1.10.0 -\u0026gt; 1.11.0。 标记版本号的软件发行后,禁止(must not)改变该版本软件的内容。任何修改都必须(must)以新版本发行。 主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 api 不应该被视为稳定版。 1.0.0 的版本号用于界定公共 api 的形成。 这一版本之后所有的版本号更新都基于公共 api 及其修改内容。 修订号 z(x.y.z | x \u0026gt; 0)必须(must)在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。 次版本号 y(x.y.z | x \u0026gt; 0)必须(must)在有向下兼容的新功能出现时递增。在任何公共 api 的功能被标记为弃用时也必须(must)递增。也可以(may)在内部程序有大量新功能或改进被加入时递增,其中可以(may)包括修订级别的改变。每当次版本号递增时,修订号必须(must)归零。 主版本号 x(x.y.z | x \u0026gt; 0)必须(must)在有任何不兼容的修改被加入公共 api 时递增。其中可以(may)包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须(must)归零。 先行版本号可以(may)被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须(must)由 ascii 字母数字和连接号 [0-9a-za-z-] 组成,且禁止(must not)留白。数字型的标识符禁止(must not)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例:1.0.0-alpha、 1.0.0-alpha.1、 1.0.0-0.3.7、 1.0.0-x.7.z.92。 版本编译信息可以(may)被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须(must)由 ascii 字母数字和连接号 [0-9a-za-z-] 组成,且禁止(must not)留白。当判断版本的优先层级时,版本编译信息可(should)被忽略。因此当两个版本只有在版本编译信息有差别时,属于相同的优先层级。范例:1.0.0-alpha+001、 1.0.0+20130313144700、 1.0.0-beta+exp.sha.5114f85。 版本的优先层级指的是不同版本在排序时如何比较。 判断优先层级时,必须(must)把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译信息不在这份比较的列表中)。 由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较。例如:1.0.0 \u0026lt; 2.0.0 \u0026lt; 2.1.0 \u0026lt; 2.1.1。 当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。 有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级必须(must)透过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后决定: 只有数字的标识符以数值高低比较。 有字母或连接号时则逐字以 ascii 的排序来比较。 数字的标识符比非数字的标识符优先层级低。 若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。例如:1.0.0-alpha \u0026lt; 1.0.0-alpha.1 \u0026lt; 1.0.0-alpha.beta \u0026lt; 1.0.0-beta \u0026lt; 1.0.0-beta.2 \u0026lt; 1.0.0-beta.11 \u0026lt; 1.0.0-rc.1 \u0026lt; 1.0.0。 合法语义化版本的巴科斯范式语法 巴科斯范式 以美国人巴科斯 (backus) 和丹麦人诺尔 (naur) 的名字命名的一种形式化的语法表示方法,用来描述语法的一种形式体系,是一种典型的元语言。又称巴科斯-诺尔形式 (backus-naur form)。它不仅能严格地表示语法规则,而且所描述的语法是与上下文无关的。它具有语法简单,表示明确,便于语法分析和编译的特点。\rbnf 表示语法规则的方式为:非终结符用尖括号括起。每条规则的左部是一个非终结符,右部是由非终结符和终结符组成的一个符号串,中间一般以 “::=” 分开。具有相同左部的规则可以共用一个左部,各右部之间以直竖 “|” 隔开。 :: 规范!规范!还是 tmd 规范!\n\u0026lt;valid semver\u0026gt; ::= \u0026lt;version core\u0026gt;\r| \u0026lt;version core\u0026gt; \u0026#34;-\u0026#34; \u0026lt;pre-release\u0026gt;\r| \u0026lt;version core\u0026gt; \u0026#34;+\u0026#34; \u0026lt;build\u0026gt;\r| \u0026lt;version core\u0026gt; \u0026#34;-\u0026#34; \u0026lt;pre-release\u0026gt; \u0026#34;+\u0026#34; \u0026lt;build\u0026gt;\r\u0026lt;version core\u0026gt; ::= \u0026lt;major\u0026gt; \u0026#34;.\u0026#34; \u0026lt;minor\u0026gt; \u0026#34;.\u0026#34; \u0026lt;patch\u0026gt;\r…… \u0026lt;valid semver\u0026gt; ::= \u0026lt;version core\u0026gt;\r| \u0026lt;version core\u0026gt; \u0026#34;-\u0026#34; \u0026lt;pre-release\u0026gt;\r| \u0026lt;version core\u0026gt; \u0026#34;+\u0026#34; \u0026lt;build\u0026gt;\r| \u0026lt;version core\u0026gt; \u0026#34;-\u0026#34; \u0026lt;pre-release\u0026gt; \u0026#34;+\u0026#34; \u0026lt;build\u0026gt;\r\u0026lt;version core\u0026gt; ::= \u0026lt;major\u0026gt; \u0026#34;.\u0026#34; \u0026lt;minor\u0026gt; \u0026#34;.\u0026#34; \u0026lt;patch\u0026gt;\r\u0026lt;major\u0026gt; ::= \u0026lt;numeric identifier\u0026gt;\r\u0026lt;minor\u0026gt; ::= \u0026lt;numeric identifier\u0026gt;\r\u0026lt;patch\u0026gt; ::= \u0026lt;numeric identifier\u0026gt;\r\u0026lt;pre-release\u0026gt; ::= \u0026lt;dot-separated pre-release identifiers\u0026gt;\r\u0026lt;dot-separated pre-release identifiers\u0026gt; ::= \u0026lt;pre-release identifier\u0026gt;\r| \u0026lt;pre-release identifier\u0026gt; \u0026#34;.\u0026#34; \u0026lt;dot-separated pre-release identifiers\u0026gt;\r\u0026lt;build\u0026gt; ::= \u0026lt;dot-separated build identifiers\u0026gt;\r\u0026lt;dot-separated build identifiers\u0026gt; ::= \u0026lt;build identifier\u0026gt;\r| \u0026lt;build identifier\u0026gt; \u0026#34;.\u0026#34; \u0026lt;dot-separated build identifiers\u0026gt;\r\u0026lt;pre-release identifier\u0026gt; ::= \u0026lt;alphanumeric identifier\u0026gt;\r| \u0026lt;numeric identifier\u0026gt;\r\u0026lt;build identifier\u0026gt; ::= \u0026lt;alphanumeric identifier\u0026gt;\r| \u0026lt;digits\u0026gt;\r\u0026lt;alphanumeric identifier\u0026gt; ::= \u0026lt;non-digit\u0026gt;\r| \u0026lt;non-digit\u0026gt; \u0026lt;identifier characters\u0026gt;\r| \u0026lt;identifier characters\u0026gt; \u0026lt;non-digit\u0026gt;\r| \u0026lt;identifier characters\u0026gt; \u0026lt;non-digit\u0026gt; \u0026lt;identifier characters\u0026gt;\r\u0026lt;numeric identifier\u0026gt; ::= \u0026#34;0\u0026#34;\r| \u0026lt;positive digit\u0026gt;\r| \u0026lt;positive digit\u0026gt; \u0026lt;digits\u0026gt;\r\u0026lt;identifier characters\u0026gt; ::= \u0026lt;identifier character\u0026gt;\r| \u0026lt;identifier character\u0026gt; \u0026lt;identifier characters\u0026gt;\r\u0026lt;identifier character\u0026gt; ::= \u0026lt;digit\u0026gt;\r| \u0026lt;non-digit\u0026gt;\r\u0026lt;non-digit\u0026gt; ::= \u0026lt;letter\u0026gt;\r| \u0026#34;-\u0026#34;\r\u0026lt;digits\u0026gt; ::= \u0026lt;digit\u0026gt;\r| \u0026lt;digit\u0026gt; \u0026lt;digits\u0026gt;\r\u0026lt;digit\u0026gt; ::= \u0026#34;0\u0026#34;\r| \u0026lt;positive digit\u0026gt;\r\u0026lt;positive digit\u0026gt; ::= \u0026#34;1\u0026#34; | \u0026#34;2\u0026#34; | \u0026#34;3\u0026#34; | \u0026#34;4\u0026#34; | \u0026#34;5\u0026#34; | \u0026#34;6\u0026#34; | \u0026#34;7\u0026#34; | \u0026#34;8\u0026#34; | \u0026#34;9\u0026#34;\r\u0026lt;letter\u0026gt; ::= \u0026#34;a\u0026#34; | \u0026#34;b\u0026#34; | \u0026#34;c\u0026#34; | \u0026#34;d\u0026#34; | \u0026#34;e\u0026#34; | \u0026#34;f\u0026#34; | \u0026#34;g\u0026#34; | \u0026#34;h\u0026#34; | \u0026#34;i\u0026#34; | \u0026#34;j\u0026#34;\r| \u0026#34;k\u0026#34; | \u0026#34;l\u0026#34; | \u0026#34;m\u0026#34; | \u0026#34;n\u0026#34; | \u0026#34;o\u0026#34; | \u0026#34;p\u0026#34; | \u0026#34;q\u0026#34; | \u0026#34;r\u0026#34; | \u0026#34;s\u0026#34; | \u0026#34;t\u0026#34;\r| \u0026#34;u\u0026#34; | \u0026#34;v\u0026#34; | \u0026#34;w\u0026#34; | \u0026#34;x\u0026#34; | \u0026#34;y\u0026#34; | \u0026#34;z\u0026#34; | \u0026#34;a\u0026#34; | \u0026#34;b\u0026#34; | \u0026#34;c\u0026#34; | \u0026#34;d\u0026#34;\r| \u0026#34;e\u0026#34; | \u0026#34;f\u0026#34; | \u0026#34;g\u0026#34; | \u0026#34;h\u0026#34; | \u0026#34;i\u0026#34; | \u0026#34;j\u0026#34; | \u0026#34;k\u0026#34; | \u0026#34;l\u0026#34; | \u0026#34;m\u0026#34; | \u0026#34;n\u0026#34;\r| \u0026#34;o\u0026#34; | \u0026#34;p\u0026#34; | \u0026#34;q\u0026#34; | \u0026#34;r\u0026#34; | \u0026#34;s\u0026#34; | \u0026#34;t\u0026#34; | \u0026#34;u\u0026#34; | \u0026#34;v\u0026#34; | \u0026#34;w\u0026#34; | \u0026#34;x\u0026#34;\r| \u0026#34;y\u0026#34; | \u0026#34;z\u0026#34; :: 版本驱动开发 edd ?有点意思……\n为什么要使用语义化的版本控制? 这并不是一个新的或者革命性的想法。实际上,你可能已经在做一些近似的事情了。 问题在于只是“近似”还不够。 如果没有某个正式的规范可循,版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义,让你对软件使用者传达意向变得容易。一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。\n举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去。假设有个名为“救火车”的函数库,它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当救火车创建时,梯子的版本号为 3.1.0。因为救火车使用了一些版本 3.1.0 所新增的功能,你可以放心地指定依赖于梯子的版本号大于等于 3.1.0 但小于 4.0.0。这样,当梯子版本 3.1.1 和 3.2.0 发布时,你可以将直接它们纳入你的包管理系统,因为它们能与原有依赖的软件兼容。\n作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。\n如果你对此认同,希望立即开始使用语义化版本控制,你只需声明你的函数库正在使用它并遵循这些规则就可以了。请在你的 readme 文件中保留此页链接,让别人也知道这些规则并从中受益。\nfaq 在 0.y.z 初始开发阶段,我该如何进行版本控制?\n最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。\n如何判断发布 1.0.0 版本的时机?\n当你的软件被用于正式环境,它应该已经达到了 1.0.0 版。如果你已经有个稳定的 api 被使用者依赖,也会是 1.0.0 版。如果你很担心向下兼容的问题,也应该算是 1.0.0 版了。\n这不会阻碍快速开发和迭代吗?\n主版本号为零的时候就是为了做快速开发。如果你每天都在改变 api,那么你应该仍在主版本号为零的阶段(0.y.z),或是正在下个主版本的独立开发分支中。\n对于公共 api,若即使是最小但不向下兼容的改变都需要产生新的主版本号,岂不是很快就达到 42.0.0 版?\n这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版,意味着你必须为这些改变所带来的影响深思熟虑,并且评估所涉及的成本及效益比。\n为整个公共 api 写文档太费事了!\n为供他人使用的软件编写适当的文档,是你作为一名专业开发者应尽的职责。保持项目高效的一个非常重要的部份是掌控软件的复杂度,如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的,要掌控复杂度会是困难的。长远来看,使用语义化版本控制以及对于公共 api 有良好规范的坚持,可以让每个人及每件事都运行顺畅。\n万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?\n一旦发现自己破坏了语义化版本控制的规范,就要修正这个问题,并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况,也不能去修改已发行的版本。可以的话,将有问题的版本号记录到文档中,告诉使用者问题所在,让他们能够意识到这是有问题的版本。\n:: git 也是这种思想,每一次修改都是一次新增!\n如果我更新了自己的依赖但没有改变公共 api 该怎么办?\n由于没有影响到公共 api,这可以被认定是兼容的。若某个软件和你的包有共同依赖,则它会有自己的依赖规范,作者也会告知可能的冲突。要判断改版是属于修订等级或是次版等级,是依据你更新的依赖关系是为了修复问题或是加入新功能。对于后者,我经常会预期伴随着更多的代码,这显然会是一个次版本号级别的递增。\n如果我变更了公共 api 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)\n自行做最佳的判断。如果你有庞大的使用者群在依照公共 api 的意图而变更行为后会大受影响,那么最好做一次主版本的发布,即使严格来说这个修复仅是修订等级的发布。记住, 语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的,那就透过版本号来向他们说明。\n我该如何处理即将弃用的功能?\n弃用现存的功能是软件开发中的家常便饭,也通常是向前发展所必须的。当你弃用部份公共 api 时,你应该做两件事:(1)更新你的文档让使用者知道这个改变,(2)在适当的时机将弃用的功能透过新的次版本号发布。在新的主版本完全移除弃用功能前,至少要有一个次版本包含这个弃用信息,这样使用者才能平顺地转移到新版 api。\n语义化版本对于版本的字符串长度是否有限制呢?\n没有,请自行做适当的判断。举例来说,长到 255 个字符的版本已过度夸张。再者,特定的系统对于字符串长度可能会有他们自己的限制。\n“v1.2.3” 是一个语义化版本号吗?\n“v1.2.3” 并不是的一个语义化的版本号。但是,在语义化版本号之前增加前缀 “v” 是用来表示版本号的常用做法。在版本控制系统中,将 “version” 缩写为 “v” 是很常见的。比如: git tag v1.2.3 -m \u0026quot;release version 1.2.3\u0026quot; 中,“v1.2.3” 表示标签名称,而 “1.2.3” 是语义化版本号。\n是否有推荐的正则表达式用以检查语义化版本号的正确性?\n有两个推荐的正则表达式。第一个用于支持按组名称提取的语言(pcre[perl 兼容正则表达式,比如 perl、php 和 r]、python 和 go)。\n参见:https://regex101.com/r/ly7o1x/3/\n^(?p\u0026lt;major\u0026gt;0|[1-9]\\d*)\\.(?p\u0026lt;minor\u0026gt;0|[1-9]\\d*)\\.(?p\u0026lt;patch\u0026gt;0|[1-9]\\d*)(?:-(?p\u0026lt;prerelease\u0026gt;(?:0|[1-9]\\d*|\\d*[a-za-z-][0-9a-za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-za-z-][0-9a-za-z-]*))*))?(?:\\+(?p\u0026lt;buildmetadata\u0026gt;[0-9a-za-z-]+(?:\\.[0-9a-za-z-]+)*))?$ 第二个用于支持按编号提取的语言(与第一个对应的提取项按顺序分别为:major、minor、patch、prerelease、buildmetadata)。主要包括 ecma script(javascript)、pcre(perl 兼容正则表达式,比如 perl、php 和 r)、python 和 go。\n参见:https://regex101.com/r/vkijkf/1/\n^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-za-z-][0-9a-za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-za-z-][0-9a-za-z-]*))*))?(?:\\+([0-9a-za-z-]+(?:\\.[0-9a-za-z-]+)*))?$ 关于 语义化版本控制的规范是由 gravatars 创办者兼 github 共同创办者 tom preston-werner 所建立。\n如果您有任何建议,请到 github 上提出您的问题。\n许可证 知识共享 署名 3.0 (cc by 3.0)\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E8%AF%AD%E4%B9%89%E5%8C%96%E7%89%88%E6%9C%AC/","summary":"\u003cp\u003e![[assets/Pasted image 20230526111634.png|300]]\u003c/p\u003e\n\u003cp\u003ei.e.Semantic Versioning\u003c/p\u003e\n\u003cp\u003e🔔 本文摘录自 \u003ca href=\"https://semver.org/lang/zh-CN/\"\u003e语义化版本 2.0.0 | Semantic Versioning - semver.org\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: Emm… 直接借用文章中的一段话 – 为供他人使用的软件编写适当的文档,是你作为一名专业开发者应尽的职责。*\u003c/p\u003e\n\u003c/blockquote\u003e","title":"语义化版本"},]
[{"content":"健身训练的项目有很多,而有一些运动是大多数人所热爱的。跑步 🏃 就是一项大众所熟悉的,门槛比较低的运动项目,无论男女老少都可以跑起来。\n那么,每天坚持跑步,身材可以瘦下来吗?可以 ❗ 跑步的可以实现的目标很多,想要达到减肥的目的,需要注意以下一些方面。\n跑步速度 跑步可以分为快跑跟慢跑,快跑属于无氧运动,是无法持续坚持的运动,容易力竭并且出现小粗腿,这并不是你跑步的初衷。\n而慢跑属于可持续进行的训练,可以达到瘦腿塑形的效果。因此,跑步的时候你的速度控制为 6-9km/h 的速度即可。\n如果你的体重基数太大,很难坚持下来,建议你可以慢跑结合快走,坚持一段时间后心肺功能会加强,运动能力会提升,这个时候尝试慢跑训练,你会发现持久力有所提升。\n![[assets/pasted image 20230526093554.png]]\n跑步时长 慢跑一小时大概可以消耗 500 大卡 的热量,而一斤脂肪的热量是 3900 大卡,如果你想要减掉 1 斤脂肪,那么要跑步将近 8 小时,如果你想要减掉 5 斤脂肪,那么你需要跑 40 小时左右。\n:: 任重而道远!!!\n如果你每天跑步 1 小时,那么要坚持 40 天左右才能减脂 5 斤。所以,跑步减肥是一项需要坚持才能有所成效的事情。\n拉伸训练 跑步后肌肉会出现充血问题,很多人误以为是肌肉腿,其实,跑步后进行充分的拉伸放松腿部肌群,这样双腿也会越来越细长好看,跑步后肌肉酸疼问题也会减轻。\n所以,跑步后不要马上坐着,一定要针对腿部肌群进行拉伸,这样还能提升双腿的灵活性跟柔软度。\n![[assets/pasted image 20230526093608.png]]\n饮食管理 跑步的时候,如果你能结合饮食管理,那么减肥效果也会翻倍。如果你胡吃海喝没有管理好饮食,那么你每天多摄入的热量可能比你跑步消耗的热量更高,比如:一份薯条的热量有 320 大卡,一杯可乐的热量是 180 大卡,一个炸鸡汉堡的热量达到了 400 大卡以上。\n![[assets/pasted image 20230526093618.png|400]]\n只有管理好饮食,远离各种高热量、过度加工的食物,多吃一些低热量、天然的食物,才能控制卡路里摄入,提升减肥效率。\n参考链接 https://new.qq.com/omn/20220718/20220718a09w9900.html ","date":"2023-05-26","permalink":"https://aituyaa.com/%E8%B7%91%E6%AD%A5/","summary":"\u003cp\u003e健身训练的项目有很多,而有一些运动是大多数人所热爱的。跑步 🏃 就是一项大众所熟悉的,门槛比较低的运动项目,无论男女老少都可以跑起来。\u003c/p\u003e\n\u003cp\u003e那么,每天坚持跑步,身材可以瘦下来吗?可以 ❗ 跑步的可以实现的目标很多,想要达到减肥的目的,需要注意以下一些方面。\u003c/p\u003e","title":"跑步"},]
[{"content":"i.e. base and conversion\n👉 推荐一个不错的网站 - c 语言中文网\n关于进制 在计算机内部,数据都是以二进制的形式存储的。\n![[assets/pasted image 20230526090235.png]]\n进制是什么?\n进制(system of numeration),是人为定义的带进位的计数方法。对于任何一种进制(如 x 进制),就表示每一位上的数运算时都是逢 x 进一位。比如,我们常用的十进制,基数为 10,数码由 0-9 组成,计数规律逢十进一。\n人类天然选择了十进制,思考一下这是为什么?\n同理,二进制的基数为 2,数码由 0-1 组成,计数规律逢二进一。八进制的基数为 8,数码由 0~7 组成,计数规律逢八进一。十六进制的基数为 16,数码由 0~9a~f 组成,计数规律逢十六进一。\n进制转换 n 进制转十进制 二进制、八进制和十六进制向十进制转换都非常简单,就是 “按权相加”。所谓“权”,即“位权”。\n假设当前数字是 n 进制,那么:\n对于整数部分,从右往左看,第 i 位的位权等于 ni-1 ; 对于小数部分,恰好相反,要从左往右看,第 j 位的位权为 n-j 然而,将十进制转换为其它进制时比较复杂,整数部分和小数部分的算法不一样。\n十进制转 n 进制 十进制整数转换为 n 进制整数采用 “除 n 取余,逆序排列” 法。如下图:\n![[assets/pasted image 20230526090249.png|600]]\n十进制数字 36926 转换成八进制的结果为 110076 。\n十进制小数转换成 n 进制小数采用 “乘 n 取整,顺序排列” 法。如下图:\n![[assets/pasted image 20230526090308.png|600]]\n十进制小数 0.6875 转换成二进制小数的结果为 0.1011 。\n二进制和八进制、十六进制的转换 从上文,我们知道,以十进制为中转,可以完成所有进制的转换。但由于某种必然,将二进制转换为八进制和十六进制时有更加简洁的方法。\n1. 二进制整数和八进制整数之间的转换\n二进制整数转换为八进制整数时,每三位二进制数字转换为一位八进制数字,运算的顺序从低位向高位依次进行,高位不足三位用零补齐。\n![[assets/pasted image 20230526090326.png|450]]\n二进制整数 1110111100 转换为八进制的结果为 1674 。\n八进制整数转换为二进制整数时,思路是相反的,每一位八进制数字转换为三位二进制数字,运算的顺序也是从低位向高位依次进行。\n![[assets/pasted image 20230526090339.png|425]]\n八进制整数 2743 转换为二进制的结果为 10111100011。\n2. 二进制整数和十六进制整数之间的转换\n二进制整数转换为十六进制整数时,每四位二进制数字转换为一位十六进制数字,运算的顺序是从低位向高位依次进行,高位不足四位用零补齐。\n![[assets/pasted image 20230526090400.png|550]]\n二进制整数 10 1101 0101 1100 转换为十六进制的结果为 2d5c。\n十六进制整数转换为二进制整数时,思路是相反的,每一位十六进制数字转换为四位二进制数字,运算的顺序也是从低位向高位依次进行。\n![[assets/pasted image 20230526090414.png|525]]\n十六进制整数 a5d6 转换为二进制的结果为 1010 0101 1101 0110。\n实际工作中,二进制、八进制、十六进制之间几乎不会涉及小数的转换,八进制和十六进制之间也极少直接转换。\n数据存储 计算机要处理的信息是多种多样的,如数字、文字、符号、图形、视音频等,这些信息在人们的眼里是不同的。但对于计算机来说,它们在内存中都是一样的,皆 以二进制的形式来表示 。\n在运行时,数据和指令都要载入到内存中才可以。内存依托于内存条,后者是一个非常精密的部件,包含了上亿个电子元器件。这些元器件,实际上就是电路,依据电路电压的不同(断电 0v、通电5v)状态,表示为 0 和 1 。\n看,物理世界与数字世界连接了。🧲 以此为基点,我们可以模拟整个宇宙了。\n一个元器件为 1 bit ,表现力太小了,一般情况下,我们将 8 个元器件看做一个单位 (8 bit = 1 byte),这样每个单位就有 28 = 256 种表示。\n答前所问,人类天然选择了十进制,计算机则天然选择了二进制,因为人类有十根手指,而计算机只有两种状态。(可以如此认为 😸)\n计算机以二进制的形式来存储数据,它只认识 0 和 1 两个数字,我们在屏幕上看到的文字,在存储之前都被转换成了二进制,在显示时也要根据二进制找到对应的字符。\n可想而知,特定的文字必然对应着固定的二进制,否则在转换时将发生混乱。那么, 怎样将文字与二进制对应起来呢?\n这就需要有一套规范,计算机公司和软件开发者都必须遵守,这样的一套规范就称为字符集(character set)或者字符编码(character encoding)。\n我们将在[[字符集和字符编码]]中认识它们。\n参考链接 http://c.biancheng.net/view/1724.html https://www.interviewcake.com/article/python/data-structures-coding-interview?course=fc1\u0026amp;section=algorithmic-thinking ","date":"2023-05-26","permalink":"https://aituyaa.com/%E8%BF%9B%E5%88%B6%E5%8F%8A%E8%BD%AC%E6%8D%A2/","summary":"\u003cp\u003ei.e. Base and Conversion\u003c/p\u003e\n\u003cp\u003e👉 推荐一个不错的网站 - \u003ca href=\"http://c.biancheng.net/\"\u003eC 语言中文网\u003c/a\u003e\u003c/p\u003e","title":"进制及转换"},]
[{"content":" 道法自然!\n关于《老子》 《老子》一书,原著不过五千言,可以说,几乎是一字就涵盖一个观念的好文章,一句就涵盖有三玄三要的妙义。它告诉了我们许多法则。\n1 道可道,非常道。名可名,非常名。无,名天地之始。有,名万物之母。故常无,欲以观其妙。常有,欲以观其徼。此两者,同出而异名,同谓之玄。玄之又玄,众妙之门。\n2 天下皆知美之为美,斯恶已。皆知善之为善,斯不善已。故有无相生,难易相成,长短相较,高下相倾,音声相和,前后相随。是以圣人处无为之事,行不言之教,万物做焉而不辞。生而不有,为而不恃,功成而弗居。夫唯弗居,是以不去。\n3 不尚贤,使民不争;不贵难得之货,使民不为盗;不见可欲,使民心不乱。\n是以圣人之治,虚其心,实其腹,弱其志,强其骨。常使民无知、无欲。使夫智者不敢为也,为无为,则无不治。\n4 道冲而用之或不盈,渊兮似万物之宗。挫其锐,解其纷,和其光,同其尘。湛兮似或存,吾不知谁之子,象帝之先。\n5 天地不仁,以万物为刍狗;圣人不仁,以百姓为刍狗。天地之间,其犹橐籥乎,虚而不屈,动而愈出。多言数穷,不如守中。\n6 谷神不死,是谓玄牝,玄牝之门,是为天地根。绵绵若存,用之不勤。\n7 天长地久,天地所以能长且久者,以其不自生,故能长生。是以圣人后其身而身先,外其身而身存。非以其无私邪?故能成其私。\n8 上善若水,水善利万物而不争,处众人之所恶,故几于道。居善地,心善渊,与善仁,言善信,正善治,事善能,动善时。夫唯不争,故无尤。\n9 持而盈之,不如其已;揣而棁之,不可长保。金玉满堂,莫之能守;富贵而骄,自遗其咎。功遂身退,天之道。\n10 载营魄抱一,能无离乎?专气致柔,能婴儿乎?涤除玄览,能无疵乎?爱民治国,能无知乎?天门开阖,能无雌乎?明白四达,能无为乎?生之,畜之,生而不有,为而不恃,长而不宰,是谓玄德。\n11 三十辐共一毂,当其无,有车之用。埏埴以为器,当其无,有器之用。凿户牖以为室,当其无,有室之用。故有之以为利,无之以为用。\n12 五色令人目盲,五音令人耳聋,五味令人口爽。驰骋畋猎,令人心发狂。难得之货,令人行妨。是以圣人为腹不为目,故去彼取此。\n13 宠辱若惊,贵大患若身。何谓宠辱若惊?宠为下,得之若惊,失之若惊,是为宠辱若惊。何谓贵大患若身?吾所以有大患者,为吾有身。及吾无身,吾有何患?故贵以身为天下,若可寄天下。爱以身为天下,若可托天下。\n14 视之不见名曰夷,听之不闻名曰希,搏之不得名曰微,此三者不可致诘,故混而为一。其上不皦,其下不昧,绳绳不可名,复归于无物。是谓无状之状,无物之象,是谓惚恍。迎之不见其首,随之不见其后。执古之道,以御今之有。能知古始,是谓道纪。\n15 古之善为士者,微妙玄通,深不可识。夫唯不可识,故强为之容。豫兮若冬涉川,犹兮若畏四邻,俨兮其若容,涣兮若冰之将释,敦兮其若朴,旷兮其若谷,浑兮其若浊。孰能浊以静之徐清,孰能安以动之徐生。保此道者不欲盈。夫唯不盈,故能蔽不新成。\n16 致虚极,守静笃。万物并作,吾以观复。夫物芸芸,各复归其根。归根曰静,是谓复命。复命曰常,知常曰明。不知常,妄作凶。知常容,容乃公,公乃王,王乃天,天乃道,道乃久。没身不殆。\n17 太上,下知有之,其次,亲而誉之;其次,畏之;其次,侮之。信不足焉,有不信焉。悠兮其贵言,功成事遂,百姓皆谓我自然。\n18 大道废,有仁义。智慧出,有大伪。六亲不和有孝慈。国家昏乱有忠臣。\n19 绝圣弃智,民利百倍。绝仁弃义,民复孝慈。绝巧弃利,盗贼无有。此三者以为文不足,故令有所属。见素抱朴,少私寡欲。\n20 绝学无忧,唯之与阿,相去几何?善之为恶,相去若何?人之所畏,不可不畏。荒兮其未央哉!众人熙熙,如享太牢,如登春台。我独泊兮其未兆,如婴儿之未孩,儽儽兮若无所归。众人皆有余,而我独若遗。我愚人之心也哉!沌沌兮。俗人昭昭,我独昏昏,俗人察察,我独闷闷。澹兮其若海,飂兮若无止。众人皆有以,而我独顽且鄙。我独异于人,而贵食母。\n21 孔德之容,惟道是从。道之为物,惟恍惟惚。惚兮恍兮,其中有象;恍兮惚兮,其中有物;窈兮冥兮,其中有精。其精甚真,其中有信。自古及今,其名不去,以阅众甫。吾何以知众甫之状哉?以此。\n22 曲则全,枉则直,洼则盈,敝则新,少则得,多则惑,是以圣人抱一为天下式。不自见故明,不自是故彰,不自伐故有功,不自矜故长。夫唯不争,故天下莫能与之争,古之所谓曲则全者,岂虚言哉!诚全而归之。\n23 希言自然。故飘风不终朝,骤雨不终日。孰为此者?天地。天地尚而不能久,而况于人乎?故从事于道者,道者同于道,德者同于德,失者同于失。同于道者,道亦乐得之。同于德者,德亦乐得之。同于失者,失亦乐得之。信不足焉,有不信焉。\n24 企者不立,跨者不行,自见者不明,自是者不彰,自伐者无功,自矜者不长,其在道也,曰:余食赘行,物或恶之,故有道者不处。\n25 有物混成,先天地生。寂兮!寥兮!独立而不改,周行而不殆,可以为天下母,吾不知其名,字之曰道,强为之名曰大。大曰逝,逝曰远,远曰反。故道大,天大,地大,王亦大。域中有四大,而王居其一焉。人法地,地法天,天法道,道法自然。\n26 重为轻根,静为躁君。是以圣人终日行而不离辎重,虽有荣观,燕出超然。奈何万乘之主,而以身轻天下?轻则失本,躁则失君。\n27 善行无辙迹,善言无瑕谪,善数不用筹策,善闭无关楗而不可开,善结无绳约而不可解。是以圣人常善救人,故无弃人;常善救物,故无弃物。是谓袭明。故善人者,不善人之师;不善人者,善人之资。不贵其师,不爱其资,虽智大迷,是谓要妙。\n28 知其雄,守其雌,为天下溪;为天下溪,常德不离,复归于婴儿。知其白,守其黑,为天下式;为天下式,常德不忒,复归于无极。知其荣,守其辱,为天下谷;为天下谷,常德乃足,复归于朴。朴散则为器,圣人用之,则为官长,故大制不割。\n29 将欲取天下而为之,吾见其不得已。天下神器,不可为也,为者败之,执者失之。故物或行、或随,或歔、或吹,或强、或羸,或挫、或隳。是以圣人去甚、去奢、去泰。\n30 以道佐人主者,不以兵强天下。其事好还,师之所处,荆棘生焉。大军之后,必有凶年。善者果而已,不敢以取强。果而勿矜,果而勿伐,果而勿骄,果而不得已,果而勿强。物壮则老,是谓不道,不道早已。\n31 夫佳兵者,不祥之器,物或恶之,故有道者不处。君子居则贵左,用兵则贵右。兵者不祥之器,非君子之器,不得已而用之。恬淡为上,胜而不美,而美之者,是乐杀人。夫乐杀人者,则不可得志于天下矣。吉事尚左,凶事尚右。偏将军居左,上将军居右,言以丧礼处之。杀人之众,以哀悲泣之,战胜以丧礼处之。\n32 道常无名,朴虽小,天下莫能臣也。侯王若能守之,万物将自宾。天地相合,以降甘露,民莫之令而自均。始制有名,名亦既有,夫亦将知止,知止可以不殆。譬道之在天下,犹川谷之于江海。\n33 知人者智,自知者明。胜人者有力,自胜者强。知足者富,强行者有志。不失其所者久,死而不亡者寿。\n34 大道泛兮,其可左右。万物恃之而生而不辞,功成不名有,衣养万物而不为主。常无欲,可名于小;万物归焉而不为主,可名为大。以其终不自为大,故能成其大。\n35 执大象天下往。往而不害,安平太。乐与饵,过客止。道之出口,淡乎其无味,视之不足见,听之不足闻,用之不足既。\n36 将欲歙之,必固张之。将欲弱之,必固强之。将欲废之,必固兴之。将欲夺之,必固与之。是谓微明。柔弱胜刚强。鱼不可脱于渊,国之利器不可以示人。\n37 道常无为而无不为,侯王若能守之,万物将自化。化而欲作,吾将镇之以无名之朴。无名之朴,夫亦将无欲。不欲以静,天下将自定。\n38 自三十八章开始,是《老子》下篇,又名下经,整个连起来,上经讲道,勉强给它一个范围,是讲“道之体”,讲“道”的根本。下经讲“德”,德是讲用,在古代文学上解释“德”为“得”,好像一个东西得到手里,所以是“德者得也”。现代的名词是说其“成果”“效用”。\n下经开始讲“德”,就是讲道的作用,以及他的现象。下面很多的话,看起来是一样,仔细研究起来,有很大的差别。因为上经讲“体”,下经讲“用”。旧的观念说,上经讲“道”,下经讲“德”。所以,同样德字句,从道的角度看,与德的角度看是不同的。\n上德不德,是以有德,下德不失德,是以无德。上德无为而无以为,下德为之而有以为。上仁为之而无以为,上义为之而有以为。上礼为之而莫之应,则攘臂而扔之。故失道而后德,失德而后仁,失仁而后义,失义而后礼。夫礼者,忠信之薄而乱之首。前识者,道之华而愚之始。是以大丈夫处其厚,不居其薄,处其实,不居其华,故去彼取此。\n39 昔之得一者,天得一以清,地得一以宁,神得一以灵,谷得一以盈,万物得一以生,侯王得一以为天下贞。其致之,天无以清将恐裂,地无以宁将恐发,神无以灵将恐歇,谷无以盈将恐竭,万物无以生将恐灭,侯王无以贵高将恐蹶。故贵以贱为本,高以下为基。是以侯王自谓孤、寡、不谷,此非以贱为本邪?非乎!故致数舆无舆。不欲琭琭如玉,珞珞如石。\n40 反者道之动,弱者道之用。天下万物生于有,有生于无。\n41 上士闻道,勤而行之;中士闻道,若存若亡;下士闻道,大笑之。不笑,不足以为道。故建言有之,明道若昧,进道若退,夷道若颣。上德若谷,大白若辱,广德若不足,建德若偷,质真若渝。大方无隅,大器晚成,大音希声,大象无形。道隐无名,夫唯道,善贷且成。\n42 道生一,一生二,二生三,三生万物。万物负阴而抱阳,冲气以为和。人之所恶,唯孤寡不谷,而王公以为称。故物或损之而益,或益之而损。人之所教,我亦教之。强梁者不得其死,吾将以为教父。\n43 天下之至柔,驰骋天下之至坚。无有入无闲,吾是以知无为之有益。不言之教,无为之益,天下希及之。\n44 名与身孰亲,身与货孰多,得与亡孰病,是故甚爱必大费,多藏必厚亡。知足不辱,知止不殆,可以长久。\n45 大成若缺,其用不弊;大盈若冲,其用不穷。大直若屈,大巧若讷。躁胜寒,静胜热,清静为天下正。\n46 天下有道,却走马以粪;天下无道,戎马生于郊。祸莫大于不知足,咎莫大于欲得。故知足之足,常足矣。\n47 不出户,知天下;不窥牖,见天道。其出弥远,其知弥少。是以圣人不行而知,不见而名,不为而成。\n48 为学日益,为道日损,损之又损,以至于无为,无为而不为。取天下常以无事,及其有事,不足以取天下。\n49 圣人无常心,以百姓心为心。善者吾亦信之,不善者吾亦善之,德善。信者吾信之,不信者吾亦信之,德信。圣人在天下歙歙,为天下浑其心,圣人皆孩之。\n50 出生入死,生之徒十有三,死之徒十有三。人之生,动之死地亦十有三。夫何故,以其生生之厚。\n盖闻善摄生者,陆行不遇兕虎,入军不被甲兵。兕无所投其角,虎无所措其爪,兵无所容其刃。夫何故,以其无死地。\n51 道生之,德畜之,物形之,势成之,是以万物莫不尊道而贵德。道之尊,德之贵,夫莫之命而常自然。故道生之,德畜之,长之育之,亭之毒之,养之覆之。生而不有,为而不恃,长而不宰,是谓元德。\n52 天下有始,以为天下母。既得其母,以知其子;既知其子,复守其母,没身不殆。塞其兑,闭其门,终身不勤;开其兑,济其事,终身不救。见小曰明,守柔曰强。用其光,复归其明,无遗身殃。是谓习常。\n53 使我介然有知,行于大道,惟施是畏。大道甚夷,而民好径。朝甚除,田甚芜,仓甚虚。服文彩,带利剑,厌饮食,财货有余,是谓盗夸,非道也哉。\n54 善建者不拔,善抱者不脱,子孙以祭祀不辍。修之于身,其德乃真;修之于家,其德乃余;修之于乡,其德乃长;修之于国,其德乃丰;修之于天下,其德乃普。故以身观身,以家观家,以乡观乡,以国观国,以天下观天下。吾何以知天下然哉?以此。\n55 含德之厚,比于赤子。蜂虿(chai4)虺(hui3)蛇不螫(zhe1),猛兽不据,攫(jue2)鸟不搏。骨弱筋柔而握固,未知牝牡之合而全(朘juan1)作,精之至也。终日号而不嗄(sha4),和之至也。知和曰常,知常曰明,益生曰祥。心使气曰强,物壮则老,谓之不道,不道早已。\n56 知者不言,言者不知。塞其兑,闭其门,挫其锐,解其分,和其光,同其尘,是谓元同。故不可得而亲,不可得而疏,不可得而利,不可得而害,不可得而贵,不可得而贱,故为天下贵。\n57 以正治国,以奇用兵,以无事取天下。吾何以知其然哉?以此。天下多忌讳,而民弥贫。民多利器,国家滋昏。人多伎巧,奇物滋起。法令滋彰,盗贼多有。故圣人云:我无为而民自化,我好静而民自正,我无事而民自富,我无欲而民自朴。\n58 其政闷闷,其民淳淳;其政察察,其民缺缺。祸兮福之所倚,福兮祸之所伏。孰知其极?其无正。正复为奇,善复为妖,人之迷,其是固久。是以圣人方而不割,廉而不刿,直而不肆,光而不耀。\n59 治人事天,莫若啬。夫唯啬,是谓早服。早服谓之重积德,重积德,则无不克。无不克,则莫知其极。莫知其极,可以有国,有国之母,可以长久。是谓深根固柢,长生久视之道。\n60 治大国若烹小鲜,以道莅天下,其鬼不神;非其鬼不神,其神不伤人;非其神不伤人,圣人亦不伤人。夫两不相伤,故德交归焉。\n61 大国者下流,天下之交,天下之牝。牝常以静胜牡,以静为下。故大国以下小国,则取小国;小国以下大国,则取大国。故或下以取,或下而取。大国不过欲兼畜人,小国不过欲入事人。夫两者各得所欲,大者宜为下。\n62 道者,万物之奥,善人之宝,不善人之所保。美言可以市,尊行可以加人。人之不善,何弃之有。故立天子,置三公,虽有拱璧以先驷马,不如坐进此道。古之所以贵此道者何,不曰以求得,有罪以免邪,故为天下贵。\n63 为无为,事无事,味无味。大小多少,报怨以德。图难于其易,为大于其细。天下难事,必作于易;天下大事,必作于细。是以圣人终不为大,故能成其大。夫轻诺必寡信,多易必多难,是以圣人犹难之,故终无难矣。\n64 其安易持,其未兆易谋。其脆易泮,其微易散。为之于未有,冶之于未乱。合抱之木生于毫末,九层之台起于累土,千里之行始于足下。为者败之,执者失之。是以圣人无为故无败,无执故无失。民之从事,长于几成而败之,慎终如始,则无败事。是以圣人欲不欲,不贵难得之货;学不学,复众人之所过,以辅万物之自然,而不敢为。\n65 古之善为道者,非以明民,将以愚之。民之难治,以其智多。故以智治国,国之贼;不以智治国,国之福。知此两者亦楷式,常知楷式,是谓玄德。玄德深矣,远矣,与物反矣。然后乃至大顺。\n66 江海所以能为百谷王者,以其善下之,故能为百谷王。是以欲上民,必以言下之;欲先民,必以身后之。是以圣人处上而民不重,处前而民不害,是以天下乐推而不厌。以其不争,故天下莫能与之争。\n67 天下皆谓我道大,似不肖。夫唯大,故似不肖。若肖,久矣其细也夫。我有三宝,持而保之:一曰慈,二曰俭,三曰不敢为天下先。慈故能勇,俭固能广,不敢为天下先,死矣。夫慈,以战则胜,以守则固。天将救之,以慈卫之。\n68 善为士者不武,善战者不怒,善胜敌者不与,善用人者为之下。是谓不争之德,是谓用人之力,是谓配天,古之极。\n69 用兵有言:“吾不敢为主而为客,不敢进寸而退尺。”是谓行无行,攘无臂,执无兵,扔无敌。祸莫大于轻敌,轻敌几丧吾宝。故抗兵相加,哀者胜矣。\n70 吾言甚易知,甚易行。天下莫能知,莫能行。言有宗,事有君。夫唯无知,是以不我知。知我者希,则我者贵,是以圣人被褐怀玉。\n71 知不知上,不知知病。夫唯病病,是以不病。圣人不病,以其病病,是以不病。\n72 民不畏威,则大威至。无狎其所居,无厌其所生。夫唯不厌,是以不厌。是以圣人自知不自见,自爱不自贵,故去彼取此。\n73 勇于敢则杀,勇于不敢则活,此两者或利或害。天之所恶,孰知其故,是以圣人犹难之。天之道,不争而善胜,不言而善应,不召而自来,繟然而善谋。天网恢恢,疏而不失。\n74 民不畏死,奈何以死惧之。若使民常畏死,而为奇者,吾得执而杀之,孰敢。常有司杀者杀,夫代司杀者杀,是谓代大匠斫,夫代大匠斫者,希有不伤其手矣。\n75 民之饥,以其上食税之多,是以饥。民之难治,以其上之有为,是以难治。民之轻死,以其上求生之厚,是以轻死。夫唯无以生为者,是贤于贵生。\n76 人之生也柔弱,其死也坚强。万物草木之生也柔脆,其死也枯槁。故坚强者死之徒,柔弱者生之徒。是以兵强则不胜,木强则兵。强大处下,柔弱处上。\n77 天之道,其犹张弓与!高者抑之,下者举之;有余者损之,不足者补之。天之道,损有余而补不足;人之道则不然,损不足以奉有余。孰能有余以奉天下?唯有道者。是以圣人为而不恃,功成而不处,其不欲见贤。\n78 天下莫柔弱于水,而攻坚强者莫之能胜,以其无以易之。弱之胜强,柔之胜刚,天下莫不知,莫能行。是以圣人云:受国之垢,是谓社稷主;受国不祥,是为天下王。正言若反。\n79 和大怨,必有余怨,安可以为善?是以圣人执左契,而不责于人。有德司契,无德司彻。天道无亲,常与善人。\n80 小国寡民,使有什伯之器而不用,使民重死而不远徙。虽有舟舆,无所乘之;虽有甲兵,无所陈之;使民复结绳而用之。甘其食,美其服,安其居,乐其俗。邻国相望,鸡犬之声相闻,民至死不相往来。\n81 信言不美,美言不信。善者不辩,辩者不善。知者不博,博者不知。圣人不积,既以为人已愈有,既以与的已愈多。天之道,利而不害。圣人之道,为而不争。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E9%81%93%E5%BE%B7%E7%BB%8F/","summary":"\u003cblockquote\u003e\n\u003cp\u003e道法自然!\u003c/p\u003e\n\u003c/blockquote\u003e","title":"道德经"},]
[{"content":"vim 的键位不错,无论是移动光标还是进行编辑操作,但是如果是使用中文输入的话,就很尬 – 中文状态下命令模式不能用,虽然通过一些方式可以使得在切换为非插入模式时自动切换为英文状态,但总是不那么尽如人意。\nemacs 的键位模式,就很适应中英文的混合输入,唯一的缺点就是有点费小手指。在 emacs 中,我一般会启用 evil 插件,如此,可以实现如果是只读文件和做一些删减工作的时候,就直接使用 vim 的键位模式,而在插入模式下,就使用 emacs 的键位模式,很不错,附上一份个人的 evil 插件的配置 init-evil.el 。唯一不足的是,这种方式只有在 emacs 中才是可用的,如果你使用其他编辑器时,就没有这份待遇了。不过,单纯的 emacs 键位也足够好用了。\n在 ide 中,基本上都可以配置键位映射,如 jetbrains 家的系列产品中的 emacs 键位映射都很好用,映射的也比较完整。\n但是在 visual studio 中,就不那么喜人了,vs 恪守着在 windows 系列下的那一套操作模式,不是说不好,而是你好歹让人定制一下。和 vs 比起来,vscode 就良心的多了,丰富的插件仓库,美观的主题,流畅度也很好。因为 vim 存在中英文混输时候的不便,我之前一直使用 emacs 的键位映射,通过下面这两个插件的一种。\n![[assets/pasted image 20230526113110.png]]\n让人遗憾的是,这两个插件,无论哪一个都不能带来完美的体验。在通常的编辑区还可以,但是在诸如搜索、替换等操作时让人很不爽。只能说有实现方式,但实现的比较曲线。\n正如在 [[一场疲惫的主题制作之旅]] 中描述的那样,window 下的 emacs 也或多或少的有一些问题,小问题,但让人很强迫。出于此种原因,我当下只在 linux 中使用它。\n目前的文字编辑工作主力是 vscode ,它有一个非常靠谱的功能,就是你可以自定义键位映射。你完全可以在原版键位的基础上进行个性化的定制,使之变为你想要的样子。\n![[assets/pasted image 20230526113117.png|375]]\n由于原版按键的兼容性最好,所以这里尽量避免了与原有按键的冲突,充分发挥了 alt 键的使用。它像是一个 emacs 和 vim 的混合体,😄 如下:\n![[assets/pasted image 20230526113137.png]]\n上图对应的 keybindings.json 如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 // place your key bindings in this file to override the defaults [ { \u0026#34;key\u0026#34;: \u0026#34;alt+p\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.quickopen\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+n\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.terminal.toggleterminal\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;terminal.active\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+x\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.showcommands\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+x\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.quickopennavigatepreviousinfilepicker\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;infilespicker \u0026amp;\u0026amp; inquickopen\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+i\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;editor.action.insertsnippet\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+j ctrl+b\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.toggleactivitybarvisibility\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+h\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;cursorleft\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+l\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;cursorright\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+k\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;cursorup\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+j\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;cursordown\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+g\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;cursorhome\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+oem_1\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;cursorend\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+oem_1\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;editor.action.commentline\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;editortextfocus \u0026amp;\u0026amp; !editorreadonly\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+oem_2\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;undo\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+j ctrl+z\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.togglezenmode\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+j ctrl+e\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.view.explorer\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;viewcontainer.workbench.view.explorer.enabled\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+j ctrl+g\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.view.scm\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;workbench.scm.active\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+j ctrl+f\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.replaceinfiles\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+s\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.replaceinfiles\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+oem_7\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;editor.action.triggersuggest\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;editorhascompletionitemprovider \u0026amp;\u0026amp; textinputfocus \u0026amp;\u0026amp; !editorreadonly\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+oem_7\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;togglesuggestiondetails\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;suggestwidgetvisible \u0026amp;\u0026amp; textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+oem_7\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;editor.action.triggersuggest\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;editorhascompletionitemprovider \u0026amp;\u0026amp; textinputfocus \u0026amp;\u0026amp; !editorreadonly\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;ctrl+oem_7\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;togglesuggestiondetails\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;suggestwidgetvisible \u0026amp;\u0026amp; textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+j\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.quickopenselectnext\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+k\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;workbench.action.quickopenselectprevious\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+j\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;selectnextsuggestion\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;suggestwidgetmultiplesuggestions \u0026amp;\u0026amp; suggestwidgetvisible \u0026amp;\u0026amp; textinputfocus\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;alt+k\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;selectprevsuggestion\u0026#34;, \u0026#34;when\u0026#34;: \u0026#34;suggestwidgetmultiplesuggestions \u0026amp;\u0026amp; suggestwidgetvisible \u0026amp;\u0026amp; textinputfocus\u0026#34; }, ] 体验非常之不错,如果,你也使用 vscode ,快些动手开始定制你自己的键位映射吧!🌟\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E9%94%AE%E4%BD%8D%E6%98%A0%E5%B0%84%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003eVim 的键位不错,无论是移动光标还是进行编辑操作,但是如果是使用中文输入的话,就很尬 – 中文状态下命令模式不能用,虽然通过一些方式可以使得在切换为非插入模式时自动切换为英文状态,但总是不那么尽如人意。\u003c/p\u003e\n\u003cp\u003eEmacs 的键位模式,就很适应中英文的混合输入,唯一的缺点就是有点费小手指。在 Emacs 中,我一般会启用 Evil 插件,如此,可以实现如果是只读文件和做一些删减工作的时候,就直接使用 Vim 的键位模式,而在插入模式下,就使用 Emacs 的键位模式,很不错,附上一份个人的 Evil 插件的配置 \u003ca href=\"https://github.com/loveminimal/emacs.d/blob/master/lisp/init-evil.el\"\u003einit-evil.el\u003c/a\u003e 。唯一不足的是,这种方式只有在 Emacs 中才是可用的,如果你使用其他编辑器时,就没有这份待遇了。不过,单纯的 Emacs 键位也足够好用了。\u003c/p\u003e","title":"键位映射那些事儿"},]
[{"content":" :: 饮食调整,贵在平衡。保证必需营养吸收,减少不必要的食物摄入。\n![[assets/pasted image 20230526101545.png|300]]\n七分饱 什么是七分饱呢?\n![[assets/pasted image 20230526101554.png|525]]\n如何把握七分饱呢?\n七成饱,大部分人找不到这个点,经常把胃里感觉满的八成饱当成最低标准,甚至到了多吃一口就觉得胀的九成饱。\n![[assets/pasted image 20230526101607.png|400]]\n因为大多数人都边吃边说笑,边吃边谈生意,边吃边上网看电视,分散注意力后,就很难感受到饱感的变化,不知不觉地饮食过量。\n![[assets/pasted image 20230526101617.png|400]]\n怎么能感觉这么细致的差异呢?\n专心致志细嚼慢咽,放慢速度是关键。\n从第一口开始,感受自己对食物的急迫感,每吃一口后感受饥饿感的逐渐消退,胃里面逐渐的充实,体会这些不同饱感程度的区别。\n然后,找到七成饱的点,把它作为自己的日常食量,就能预防饮食过量。\n早餐 早餐是一天所有能量之源,一定要吃好早餐才能有充足的力量来进行一天的工作和生活。\n鸡蛋猪排三明治 ![[assets/pasted image 20230526101636.png|193]] ![[assets/pasted image 20230526101653.png|350]]\n简单的早餐也会有大营养,简易三明治加一杯热牛奶一份水果,这样食材很普遍却是可以满足人体内需求的重要营养。早餐一定要吃,而且要吃的丰富,别再用没时间找借口伤害你的身体。\n午餐 午餐吃个 5 ~ 7 分饱。\n晚餐 晚餐,少吃或不吃(以果蔬代之)。\n减肥不等于饿肚子 为了瘦下来,你还是节食挨饿吗?减肥不是节食。相信我,饿不了几顿的……\n减肥的关键是提升身体的热量缺口,也就是提升热量输出,降低热量摄入,以此来促进脂肪的分解。而挨饿只会让身体感到饥荒,从而导致身体基础代谢值下降,正确地饮食方法要牢记以下几个方面。\n1、用低热量的食物代替各种高热量的食物\n这样不用减少食物分量,也能控制卡路里摄入,还能避免饥饿感的出现。\n我们要选择清蒸、水煮的方法代替各种红烧、煎炸的做法,比如水煮土豆代替红烧土豆,可以避免食物热量大大飙升,选择饱腹感强、体积大的食物代替饱腹感差,体积小的食物,比如生菜、西蓝花代替各种加工零食。\n![[assets/pasted image 20230526101708.png|350]]\n2、学会多喝水\n水是没有热量的,不会让你发胖。多喝水可以减缓饥饿感的出现,控制进食量,还能促进身体代谢循环,加速废物的排出,有助于脂肪的代谢。\n建议,每天的喝水量在 2l 左右,多个时间段补充,尽量在饭前喝,饭后跟睡前要少喝水。喝水要喝热水、温水,不要喝冷水,拒绝各种冷饮跟加工果汁,避免多余热量的摄入。\n3、三餐要规律,不要路过任何一餐。\n不吃早餐,跳过晚餐,平时饥一餐饱一餐的行为,容易诱发肠胃疾病,还容易在下一餐进行报复性进食,胃容量也会被撑大,不利于减肥。而规律的饮食习惯,有助于肠胃健康,让肠胃运转更加高效,从而减少脂肪的堆积。我们要三餐定时,保持饭吃八分饱,可以有效控制胃容量,健康地瘦下来。\n4、不要拒绝主食\n主食可以给身体补充碳水化合物,提供代谢动力。正常人每天的碳水主食摄入量在 250-300g 左右,减肥期间,我们可以降为 180-200g 左右,补充身体所需的碳水能力。\n此外,主食的选择方面,我们可以粗细粮结合,少吃一些面食跟米饭,适当吃一些糙米、燕麦、全麦包等粗粮,以此来控制升糖系数,延长饱腹时间,这样可以有效降低暴食几率,提升减肥成功率。\n![[assets/pasted image 20230526101724.png|350]]\n至于量呢?七分饱 ❗️❗️❗️\n参考链接 https://new.qq.com/omn/20220507/20220507a0d3n900.html https://baijiahao.baidu.com/s?id=1626245545642571828\u0026amp;wfr=spider\u0026amp;for=pc https://mini.eastday.com/mobile/171210000039438.html https://jingyan.baidu.com/article/fcb5aff7c2e094adaa4a71b1.html ","date":"2023-05-26","permalink":"https://aituyaa.com/%E9%A5%AE%E9%A3%9F%E8%B0%83%E6%95%B4/","summary":"\u003cblockquote\u003e\n\u003cp\u003e:: 饮食调整,贵在平衡。保证必需营养吸收,减少不必要的食物摄入。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e![[assets/Pasted image 20230526101545.png|300]]\u003c/p\u003e","title":"饮食调整"},]
[{"content":" 认识世界,改造世界。 ⬇️ 马克思主义基本原理概论\n绪论 马克思主义是关于工人阶级和人类解放的科学 p1 → p41\n马克思主义是时代的产物,它产生于资本主义社会化大生产已经成为主导趋势,资本主义社会内部各种社会矛盾充分暴露,无产阶级以独立的政治力量登上历史舞台争取自身和人类解放的历史时代。\n:: 时代?!大势(时势),主次矛盾(社会),核心变革力量(个人、组织)。\n马克思主义是对人类文明成果的继承与创新,德国古典哲学、英国古典经济学、19 世纪英法两国的空想社会主义,是它的直接理论来源,马克思主义的产生还与当时自然科学的巨大成就密切相关。\n马克思主义既然是时代的产物和实践经验的总结,它就必然随着时代的发展、实践的拓展、科学的进步而不断丰富和发展自身。\n:: 穷,变,通,达。居安思危,未雨绸缪。*\n马克思主义是由马克思、恩格斯创立的。为他们的后继者所发展的,以反对资本主义、建设社会主义和共产主义为目标的科学的理论体系。简要地说,它是关于工人阶级和人类解放的科学。\n以实践为基础的科学性和革命性的统一,是马克思主义的根本理论特征。其革命性表现为彻底的批判精神和鲜明的政治立场;其科学性主要表现为它按照世界的本来面具认识世界,揭示了自然界和人类社会的发展规律。它的科学性和革命性在实践的基础上达到统一。\n:: 实践是什么?马克思主义又是如何在实践的基础上实现了科学性(实事求是、揭示规律)和革命性(彻底批判、立场鲜明)的统一?\n:: 实践,是主观见之于客观,是人们能动地改造和探索现实世界一切客观物质的社会性活动。\n与时俱进是马克思主义的理论品质。与时俱进就是党的全部理论和工作要体现时代性,把握规律性,富于创造性。马克思主义的创始人和继承者都是与时俱进的典范。\n:: 时代,规律,创造(抓住时代脉搏,认识规律、利用规律)。\n理想是人生的奋斗目标。马克思主义最高的社会理想是推翻资本主义,实现共产主义。最高理想和共同理想既相互区别又相互联系,二者是辩证统一的关系。\n马克思主义不是教条,而是行动指南。它提供研究的方法,而不提供对一切问题的现成答案。必须把马克思主义的一般原理与本国的具体实际相结合。\n:: 实事求是!具体问题,具体分析!\n学习马克思主义的目的在于树立正确的世界观、人生观、价值观,掌握认识世界和改造世界的伟大工具,全面提高人的素质。理论联系实际是学习马克思主义的根本方法。\n:: 认识世界,改造世界。\n现今时代发生了许多引人注目的变化,向马克思主义提出了许多新的研究课题,并提供了不少回答这些课题的实践经验,为在实践中发展马克思主义提供了契机。\n第一章 物质世界及其发展规律 p43 → p120\n![[assets/pasted image 20230526094956.png]]\n哲学是系统化、理论化的世界观,思维与存在的关系问题是哲学的基本问题,物质是世界的本质或本原,世界统一于物质。物质是不依赖于意识又能为意识所反映的客观实在。运动是物质的根本属性,时间和空间是物质运动的存在方式。\n把实践的观点作为根本观点是马克思主义哲学同旧唯物主义和唯心主义的根本区别。实践是主体能动地改造和探索客体的客观物质活动,它具有客观性、自觉能动性、社会历史性等基本特点,物质生产实践、处理社会关系的实践和科学实验是实践的三种基本形式。人的实践活动使自然分化为自在自然和人化自然,社会历史是人们的实践活动创造的,实践是人的存在方式,社会生活在本质上是实践的。\n:: 实践,讲求的是放弃空想,实事求是,不断地探索、认识、更新!\n物质世界是普遍联系和永恒发展的,联系的观点和发展的观点是唯物辩证法的总特征。唯物辩证法和形而上学是两种根本对立的发展观。对立统一规律、质量互变规律、否定之否定规律是联系和发展的基本规律,唯物辩证法的范畴是提示事物联系和发展的基本环节,对立统一规律是唯物辩证法的实质与核心。\n:: 💡 五大基本范畴,是如何定义的呢?基本环节?透过现象看本质,思其因果,了‘然’(必然?偶然?),有无可能性、现实否?以何内容、表现形式?\n物质的运动发展是有规律的。规律是物质运动发展过程中本质的、必然的、稳定的联系。规律具有客观性和普遍性两个特点。社会规律与自然规律有本质区别。社会规律同自然规律一样,也是客观的。意识是自然界长期发展的产物,意识依赖于物质。从本质上看,意识是人脑的机能,是客观世界的主观映像,是社会的产物。意识对物质的具有能动的反作用。尊重客观规律和发挥主观能动性是辩证的统一。\n:: 世界是物质的,物质是运动的(联系和发展),运动是有规律的(对立统一、质量互变、否定之否定)。\n第二章 认识的本质及其规律 p121 → p176\n认识是指人们特有的一种能力和活动,认识的产生是一个长期的历史过程。个人思想的发展史再现着整个人类思维的发展过程。\n实践的观点是马克思主义认识论之第一的和基本的观点。马克思把实践观点引入认识论,引起了认识论的革命。实践是认识的基础,认识是主体对客体的反映,马克思主义认识论是革命的能动反映论。\n:: 认识是主体对客体的反映,所以要正确认识,实践、认识,再实践、再认识……\n感性认识是认识的初级阶段,包括感觉、知觉、表象三种形式。理性认识是认识的高级阶段,包括概念、判断、推理三种形式。认识的辩证过程包括从感性认识到理性认识和从理性认识到实践两个阶段。一个正确的认识需要经过“实践-认识-实践”多次反复才能完成,整个人类的认识是有限和无限的辩证统一。\n:: 感性(感觉、知觉、表象) → 理性(概念、判断、推理)\n真理是主体对客观的正确反映,谬误是主体对客体的歪曲反映。真理和谬误是认识运动中既对立又统一的两个方面。真理是客观的,既有绝对性又有相对性,是绝对性和相对性的统一。人的认识通过相对真理而走向绝对真理。实践是检验真理的唯一标准。真理与价值是统一的。\n认识世界与改造世界是紧密联系在一起的。马克思主义认识论是党的思想路线的理论基础。党的群众路线是马克思主义认识论在实际工作中的运用。\n第三章 人类社会及其发展 p177 → p245\n![[assets/pasted image 20230526095115.png]]\n社会存在和社会意识的关系问题是历史观的基本问题,社会存在决定社会意识,社会意识对社会存在具有相对独立和能动的反作用,生产方式是社会存在和发展的基础。\n生产方式是人类借以向自然界谋取必需的生活资料的方式;生产力是人类利用自然、改造自然、从自然界获取物质资料的能力(反映的是人与自然界的关系);生产关系是指人们在物质生产过程中结成的经济关系(生产资料的所有制形式 - 起决定性的作用、人在生产中的地位及相互关系、产品的分配方式)。\n:: 只是人与自然的关系吗?一直在攫取自然界?\n科学技术是第一生产力,生产资料的所有制形式是整个生产关系的基础。生产关系必须适合生产力性质的规律包括生产力决定生产关系、生产关系反作用于生产力、生产力和生产关系之间的矛盾运动三项内容。\n:: 呜呼,生产资料?!?!\n经济基础是指一个社会中占统治地位的生产关系各个方面的总和,上层建筑分为政治上层建筑和观念上层建筑两部分。上层建筑必须适合经济基础发展要求的规律包括经济基础决定上层建筑、上层建筑反作用于经济基础、经济基础和上层建筑之间的矛盾运动三项内容。\n社会形态最基本的划分法有两种,即经济社会形态划分法和技术社会形态划分法。社会形态的发展是一种自然历史过程。社会形态的发展既具有统一性,又具有多样性。\n生产力和生产关系之间的矛盾、经济基础和上层建筑之间的矛盾是人类社会的基本矛盾,这两对矛盾互相制约,互相影响。社会基本矛盾是社会发展的基本动力。\n物质利益的对立是阶级斗争的根源,阶级斗争是阶级社会发展的直接动力。阶级斗争推动社会发展的作用,既表现在社会形态更替的过程中,也表现在同一社会形态的量变过程中。\n:: 社会的所有问题,归根结底在于资源分配(物质利益)问题!\n社会革命是社会制度的根本质变,社会改革是同一社会制度量变过程中的部分质变。革命的根本问题是国家政权问题,社会主义社会的改革是社会主义制度的自我完善。\n从本质上说,文化是人和社会的具体存在方式,文化具有创造性、自由性、兼容性等根本特性,文化在社会发展中具有多方面的功能。要大力发展社会主义先进文化。\n:: 文化是什么?它是如何产生的?解决了什么问题?文化是相对于经济、政治而言的人类全部精神活动及其产物,教育、科学、艺术皆属广义的文化。\n马克思从三个方面对人的本质作了界定,即劳动是人的本质,人的本质是一切社会关系的总和,人的需要即人的本质。人与社会是具体的历史的统一。\n人有自然属性(人的肉体及其特性)和社会属性(在社会实践活动中人与人结成的各种社会关系),人的本质是由人的社会属性决定的 – 人的本质是一切社会关系(劳动中结成的)的总和。\n历史唯物主义和历史唯心主义在谁是历史的创造者的问题上的观点是根本对立的。人民群众是物质财富和精神财富的创造者,是实现社会变革的决定力量。坚持无产阶级政党的群众观点和群众路线的工作方法。\n个人分为普通个人和历史人物,历史人物又区分为正面人物和反面人物。正面人物即杰出人物,杰出人物在历史发展中起着重要作用,要用正确的观点和方法分析和评价杰出人物的历史作用。\n第四章 资本主义的形成及其本质 p247 → p309\n资本主义经济是在商品经济基础上产生和发展起来的,商品经济成为资本主义社会中占统治地位的和最普遍的经济形式。建立在分析商品货币关系基础上的科学劳动价值论,深刻阐明了劳动二重性、商品价值构成与创造、货币本质与职能、商品经济基本矛盾,以及价值规律等一系列重要经济理论问题。\n资本主义生产关系最初是在封建社会末期的小商品生产者两极分化基础上产生的,商业的发展促进了资本主义生产关系的产生,而资本原始积累则大大加速了资本主义生产关系的形成。通过资产阶级革命初步确立了资本主义制度,而资本主义制度的完全确立是通过产业革命实现的。\n\u0026gt; 生产关系 = 生产资料的所有制形式 + 人在生产中的地位及相互关系 + 产品的分配方式\n资本主义经济制度是以生产资料资本主义私有制为基础,通过雇佣劳动制度剥削工人创造的剩余价值的经济制度。资本主义生产过程的本质是价值增殖过程,生产剩余价值是资本主义生产方式的基本规律。\n资本积累的实质是资本家利用无偿占有的剩余价值进行资本积累,从而占有更多的剩余价值。随着资本积累的进行,必然造成大量失业人口和严重的贫富两极分化。资本积累的历史趋势是社会主义公有制必然取代资本主义私有制。\n:: 剥削?分配!分配!分配!\n资本主义的政治制度是资产阶级为实现其阶级专政而采取的统治方式和方法及各种相关制度的总和,它主要包括资本主义的国家制度、政党制度、选举制度、三权分立制度、民主制度等,其中国家制度是资本主义政治制度的核心。\n资本主义的意识形态是资产阶级对世界和社会的系统看法和见解,具有鲜明的阶级性。利己主义是资本主义意识形态的核心。利己主义体现在资产阶级的人生观、价值观、道德观等各个方面。\n第五章 资本的流通过程和剩余价值的分配 p311 → p365\n单个资本的运动包括资本的循环和周转。资本只有连续不断地循环运动,才能使资本家源源不断地获得剩余价值,而资本周转速度的快慢则影响着剩余价值生产的数量和年剩余价值率的高低…… 社会资本的运动表现为社会资本再生产。社会资本再生产的顺利实现,必须按照社会资本再生产实现条件的客观要求,保持两大类之间的一定比例关系,使社会总产品的各个构成部分都能在实物形态上得到替换,在价值形态上得到补偿。资本主义基本矛盾导致社会资本再生产的实现条件遭到破坏,周期性地爆发经济危机。在剩余价值各种具体形式中,产业利润是其他各种剩余价值具体形式(如商业利润、利息、银行利润、股息、地租等)的基础。随着资本主义的发展,产业资本的剩余价值转化为利润,剩余价值率利润率,利润转化为平均利润,商品的价值转化为生产价格。商业资本是在资本主义流通领域中发生作用的职能资本,它所执行的就是产业资本中的商品资本的职能,即销售商品,实现价值和剩余价值。商业利润相当于平均利润,它来源于雇佣工人所创造的剩余价值的一部分…… 借贷资本是从职能资本运动中的货币资本独立出来,并服务于职能资本的资本形式,它是为取得利息而暂时贷给职能资本家使用的货币资本。利息和银行利润来源于产业工人所创造的剩余价值的一部分。股份公司是现代企业的一种重要经营组织形式。\n:: emm… 资本的流通和剩余价值的分配……\n资本主义地租是农业资本家为取得土地的使用权,而付给土地所有者的超出平均利润的超额利润。农业资本家获得平均利润。资本主义地租有级差地租和绝对地租两种基本形式。\n第六章 资本主义发展的历史进程 p366 → p411\n资本主义的发展可分为自由竞争资本主义和垄断资本主义两个阶段。在自由竞争中生产和资本不断集中,当生产集中和资本集中发展到一定程度时就会走向垄断。垄断是为了获取高额垄断利润,它是通过各种垄断组织和垄断价格取得的。垄断并没有消除竞争。垄断是帝国主义的经济实质。\n垄断资本主义的经济特征是:垄断在经济生活中占统治地位;金融资本和金融寡头的统治;资本输出在经济生活中占重要地位;国际垄断同盟在经济上瓜分世界;垄断资本主义列强瓜分和重新瓜分世界。\n私人垄断资本主义进一步发展为国家垄断资本主义,即垄断资本与国家政权相结合的资本主义。它的基本形式有资本主义的国有经济、国家与私人资本在企业内外的结合等。国家垄断资本主义实行对国民经济的干预和调控,一定程度上调整了经济运行、缓和了资本主义社会中的各种矛盾。它是资本主义经济关系的局部调整和部分质变。垄断资本主义的发展,促进了生产社会化、国际化程序的提高,加强了各国间的经济联系,形成了经济全球化的发展趋势,其主要内容是生产全球化、贸易全球化和资本全球化。在生产国际化、经济全球化的条件下,当代资本主义生产力、生产关系和上层建筑各方面,较之自由竞争和私人垄断资本主义时期,发生了很多变化。\n在资本主义发展进程中,生产社会化程序不断加强,这与资本主义私人占有制形成了资本主义的基本矛盾,它决定了资本主义终将为社会主义所取代。\n第七章 社会主义社会及其发展 p412 → p461\n社会主义经历了从空想到科学、从理论到实践的发展。科学社会主义理论是马克思主义的重要组成部分,是无产阶级、社会主义和共产主义事业的指导思想和理论武器。\n社会主义理论与实践是通过社会主义革命实现的。对于社会主义革命的发生,马克思主义者有一个认识不断深化的过程,马克思恩格斯曾提出同时胜利论,列宁则根据垄断资本主义时期的新形势提出了一国或数国首先胜利论,并成功地领导了俄国十月革命。列宁和斯大林在向社会主义过渡和建设社会主义等方面进行了探索。社会主义从一国到多国发展壮大,第二次世界大战后出现了一批社会主义国家,他们在社会主义建设中进行了多方面的探索。\n无产阶级专政和社会主义民主是科学社会主义的核心内容。实行无产阶级专政是最终目标是消灭剥削、消灭阶级,进入无阶级社会。建设高度的社会主义是我们重要的奋斗目标和任务。\n社会主义在实践中发展完善。人们对社会主义的基本特征的认识不断深化。社会主义的本质是解放生产力,发展生产力,消灭剥削,消除两极分化,最终达到共同富裕。\n社会主义在一些经济文化落后的国家取得胜利有其历史必然性,这些国家建设社会主义的任务更重更艰巨。社会主义的发展道路是多样的,要在实践中不断探索。\n马克思主义政党是社会主义事业的领导核心,是工人阶级的先锋队,是新型的革命政党,是为实现共产主义而奋斗的政党,是为人民谋利益的政党。\n第八章 共产主义社会是人类最崇高的社会理想 p462 → p492\n共产主义社会是人类最理想最美好的社会制度。马克思主义依据社会发展规律、分析了资本主义社会内存矛盾的运动,阐明了由资本主义转向社会主义的必然性,并对未来共产主义社会进行了展望,科学预见了共产主义社会的基本特征。\n共产主义社会形态包括社会主义社会和共产主义社会前后相互衔接的两个阶段,人类社会发展的历史趋势,必然由资本主义社会过渡到社会主义社会,将来再由社会主义社会发展为共产主义社会。实现共产主义是人类最伟大的事业,需经过一个不断实践的长期过程。\n社会主义是走向共产主义的必由之路,只有经过社会主义历史阶段长期和充分发展,为实现共产主义创造出各种主客观条件,才能最终过渡到共产主义社会。\n共产主义远大理想是人们从事各种社会实践活动的精神支柱和思想动力。共产主义远大理想与中国特色社会主义共同理想是相互联系和相互促进的。要积极投身于中国特色社会主义事业,在建设中国特色社会主义事业中为实现共产主义而奋斗。\n","date":"2023-05-26","permalink":"https://aituyaa.com/%E9%A9%AC%E5%85%8B%E6%80%9D%E4%B8%BB%E4%B9%89%E7%AE%80%E8%A7%88/","summary":"\u003cblockquote\u003e\n\u003cp\u003e认识世界,改造世界。 \u003ca href=\"http://ys-g.ysepan.com/627140855/015120531/jfgLjKm8J435I6I4XP4Gf0/%E9%A9%AC%E5%85%8B%E6%80%9D%E4%B8%BB%E4%B9%89%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E6%A6%82%E8%AE%BA%E6%95%99%E6%9D%90%E7%94%B5%E5%AD%90%E7%89%88.pdf\"\u003e⬇️ 马克思主义基本原理概论\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e","title":"马克思主义简览"},]
[{"content":"开发环境 jenkins 的 git branches 和项目名称对照表及其说明和备忘。\n相关账号地址 项目 账号/密码 描述 公司 erp liuzengxi / 123456 http://192.168.4.223:34567/erp/login.aspx 开发 jenkins user: admin pswd: admin http://192.168.51.218:6024/ 测试 jenkins liuzengxi 123456789 http://iyin.jenkins.oa.com/ 生产 jenkins liuzengxi/ liuzengxi@i-yin.com.cn http://jenkinsiyin.oa.com/ git 仓库 loveminimal / xxx http://new-git.oa.com/ 部署环境 最新 共享部署石墨文档 测试 elk 192.168.51.223 添加到 hosts http://kibana-test.i-yin.com.cn 生产 elk user/pswd: saas / abcd@2019 esignsys.i-yin.com.cn 对应的 elk 如果访问不了在 hosts 文件中增加 192.168.51.223 jenkinsiyin.oa.com\n新疆税局 工作时间:10:00-13:30 15:30-19:30\n该项目为独立项目,兼容 ie8 兼容模式,原生 html+css+js 😱\ngit 仓库: http://new-git.oa.com/zhizhi/xinjiangshuiwu.git\n签章系统 签章系统的私有化部署项目较多,以不同分支进行管理。\n对外演示:\n管理后台 http://esignsys.i-yin.com.cn/sign-sys-admin/#/login 应用前台 http://esignsys.i-yin.com.cn/sign-sys/#/login 以下是签章系统环境,每套都是独立的,不要再混了\n- 10.9.2.29 sign.oa.com(标准版)\n- 10.9.2.28(北京五矿)\n- xjbdc.i-yin.com.cn(不动产外网环境)\n- 192.168.49.224(招银外网环境)\n- 192.168.51.251(新钢项目)\n招银理财外网地址: 121.201.40.131:8810/sign-sys/#/login\njenkins 的 git branches 项目名称 是否需要本地构建 说明 sign-sys-front 签章系统前端 ✔ http://new-git.oa.com/liangqiang/sign-system.git sign-sys-front-admin 签章系统后台管理系统 ✔ http://new-git.oa.com/liangqiang/sign-system-admin.git iyin-sign-front-operation 签章系统运营平台 ✔ http://new-git.oa.com/zhizhi/sign-system-operation.git sign-sys-demos 签署演示 http://new-git.oa.com/zhizhi/sign-sys-demos.git sign-verify-zylc 招银理财合同查验 刘增玺 / sign-verify-zylc · gitlab 签章系统有许多针对不同客户的定制化需求,如:招银理财,新疆税局,正中集团,北京五矿,周大福,龙华医院等等。\n安印财税云平台 jenkins 的 git branches 项目名称 是否需要本地构建 说明 finance-taxation 安印财税云平台应用前台 刘增玺 / finance-taxation · gitlab saas 生产运维系统 jenkins 的 git branches 项目名称 是否需要本地构建 说明 saas-front-develop 生产运维系统前端代码 ✘ http://new-git.oa.com/saasplatform/front/saas.git 云签 jenkins 的 git branches 项目名称 是否需要本地构建 说明 cloud-sign-admin-front 云签后台管理前端 ✔ http://new-git.oa.com/liangqiang/cloud-sign-admin-front.git cloud-sign-front 云签前端 ✔ http://new-git.oa.com/liangqiang/cloud-sign-front.git 合同平台 jenkins 的 git branches 项目名称 是否需要本地构建 说明 contract_web_front 合同平台前端 ✘ http://new-git.oa.com/tanggz/contract.git contract-platform-admin-font 合同平台 admin 项目 ✘ http://new-git.oa.com/liangqiang/contract-platform-admin-font.git contract-small1 合同平台小程序 http://new-git.oa.com/tanggz/contract_small1.git contract-ukey-localsign 合同平台本地签 http://new-git.oa.com/loveminimal/contract-ukey-localsign.git contract-sign-sys-demos 合同签署演示 http://new-git.oa.com/loveminimal/sign-sys-demos.git 官网项目 jenkins 的 git branches 项目名称 是否需要本地构建 说明 ay-mobile 安印官网移动端 ✘ http://new-git.oa.com/hongjin/ay-mobile.git new-iyin-website 安印新官网 http://new-git.oa.com/frontend/iyin/new-iyin-website.git evidence 存证 http://new-git.oa.com/tanggz/evidence.git sign-verify 验签 http://new-git.oa.com/hongjin/sign-verify.git iyin-demo http://new-git.oa.com/liangqiang/iyin-demo.git ","date":"2023-05-25","permalink":"https://aituyaa.com/docs/%E5%B7%A5%E4%BD%9C%E8%B5%84%E6%96%99/","summary":"\u003cp\u003e开发环境 Jenkins 的 Git Branches 和项目名称对照表及其说明和备忘。\u003c/p\u003e","title":"工作资料"},]
[{"content":"i.e. teach yourself computer science\n🔔 转载 英文原文、中文译本\n书单 『 [[深入理解计算机系统]] 』 『 [[数据密集型应用系统设计]] 』 简介 本文档是对 teachyourselfcs 内容的中文翻译,原作者为 ozan onay 和 myles byrne 。\n如果你是一个自学成才的工程师,或者从编程培训班毕业,那么你很有必要学习计算机科学。幸运的是,不必为此花上数年光阴和不菲的费用去攻读一个学位:仅仅依靠自己,你就可以获得世界一流水平的教育 💸。\n互联网上,到处都有很多的学习资源,然而精华与糟粕并存。你所需要的,不是一个诸如“200+免费在线课程”的清单,而是以下问题的答案:\n你应该学习 哪些科目 ,为什么? 对于这些科目, 最好的书籍或者视频课程 是什么? :: 解决了“学什么?从哪儿学?”的问题!\n在这份指引中,我们尝试对这些问题做出确定的回答。\n书籍视频 大致按照列出的顺序,借助我们所建议的教材或者视频课程(但是最好二者兼用),学习如下的九门科目。目标是先花 100 到 200 个小时学习完每一个科目,然后在你职业生涯中,不时温习其中的精髓 🚀。\n:: 空想毫无意义,行动改变人生。 \u0026gt; 2024-02-27 10:52\n:: 学而时习之,温故而知新。\n:: 100 - 200 小时,hmm…… 可以入门吧,比起 10000 小时,这不算什么。\n科目 为何要学? 最佳书籍 最佳视频 编程 不要做一个“永远没彻底搞懂”诸如递归等概念的程序员 《计算机程序的构造和解释》 brian harvey’s berkeley cs 61a 计算机系统结构 如果你对于计算机如何工作没有具体的概念,那么你所做出的所有高级抽象都是空中楼阁 ⭐️ 《深入理解计算机系统》 berkeley cs 61c 算法与数据结构 如果你不懂得如何使用栈、队列、树、图等常见数据结构,遇到有难度的问题时,你将束手无策 《算法设计手册》 steven skiena’s lectures 数学知识 计算机科学基本上应用数学的一个“跑偏的”分支,因此学习数学将会给你带来竞争优势 《计算机科学中的数学》 tom leighton’s mit 6.042j 操作系统 你所写的代码,基本上都由操作系统来运行,因此你应当了解其工作原理 《操作系统导论》 berkeley cs 162 计算机网络 互联网已然势不可挡:理解工作原理才能解锁全部潜力 《计算机网络:自顶向下方法》 stanford cs 144 数据库 对于多数重要程序,数据是其核心,然而很少人理解数据库系统的工作原理 《readings in database systems》 (暂无中文译本) joe hellerstein’s berkeley cs 186 编程语言与编译器 若你懂得编程语言和编译器如何工作,你就能写出更好的代码,更轻松地学习新的编程语言 《crafting interpreters》 alex aiken’s course on lagunita 分布式系统 如今,多数系统都是分布式的 ⭐️ 《数据密集型应用系统设计》 mit 6.824 还是太多?\n如果花几年时间自学 9 门科目让人望而却步,我们建议你只专注于两本书: 《深入理解计算机系统》 和 《数据密集型应用系统设计》 。根据我们的经验,投入到这两本书的时间可以获得极高的效率,特别适合从事网络应用开发的自学工程师。这两本书也可以作为上面表格中其他科目的纲领。\n:: 有点武学总纲的意思~ \u0026gt; 2024-02-27 11:36\n为什么要学习计算机科学 软件工程师分为两种:一种充分理解了计算机科学,从而有能力应对充满挑战的创造性工作;另一种仅仅凭着对一些高级工具的熟悉而勉强应付。\n:: 唯有足够了解,方能游刃有余。\n这两种人都自称软件工程师,都能在职业生涯早期挣到差不多的工资。然而,随着时间流逝,第一种工程师不断成长,所做的事情将会起来真有意义且更为高薪,不论是有价值的商业工作、突破性的开源项目、技术上的领导力或者高质量的个人贡献。第一种工程师总是寻求深入学习计算机科学的方法,或是通过传统的方法学习,或是在职业生涯中永无止息地学习。\n第二种工程师通常浮于表面,只学习某些特定的工具和技术,而不研究其底层的基本原理,仅仅在技术潮流的风向改变时学习新的技能。如今,涌入计算机行业的人数激增,然而计算机专业的毕业生数量基本上未曾改变。第二种工程师的供过于求正在开始减少他们的工作机会,使他们无法涉足行业内更加有意义的工作。\n对你而言,不论正在努力成为第一种工程师,还是想让自己的职业生涯更加安全,学习计算机科学是唯一可靠的途径。\n分科目指引 编程 大多数计算机专业本科教学以 程序设计“导论” 作为开始。这类课程的最佳版本不仅能满足初学者的需要,还适用于那些在初学编程阶段遗漏了某些有益的概念和程序设计模式的人。\n![[assets/pasted image 20230525153144.png|200]]\n对于这部分内容,我们的标准推荐是这部经典著作:《计算机程序的构造和解释》。在网络上,这本书既可供 免费阅读(英文版) ,也作为 mit 的免费视频课程 。不过尽管这些视频课程很不错,我们对于视频课程的推荐实际上是 brian harvey 开设的 sicp 课程 (即 berkeley 的 61a 课程)。比起 mit 的课程,它更加完善,更适用于初学者。\n中文翻译新增:\n关于 sicp 国内视频观看地址 mit 的免费视频课程(中英字幕) brian harvey 开设的 sicp 课程(中英字幕) scheme 学习的相关资源 更多 mit 经典公开课(中英字幕) 伯克利 scheme 版本最后一次公开课(英文字幕) 伯克利 2018 年春课程(英文字幕,有部分字幕缺失) 伯克利 2019 年夏课程(中英字幕,中文机翻) 自从 2016 年首次发布这份指南以来,最常被问的一个问题是,我们是否推荐 john denero 讲授的 cs 61a 课程,以及配套的书籍 《composing programs》 ,这本书“继承自 sicp 但使用 python 讲解”。我们认为 denero 的课程也很不错,有的学生可能更喜欢,但我们还是建议把 sicp、 scheme 和 brain harvey 的视频课程作为首选。\n为什么这么说呢?因为 sicp 是独一无二的,它可以 – 至少很有可能 – 改变你对计算机和编程的基本认识。不是每个人都有这样的经验,有的讨厌这本书,有的人看了前几页就放弃了,但潜在的回报让它值得一读。\n:: 初读确实有点晦涩,尤其是在你对 lisp 毫无认识的情况下。lisp 很容易学习,坚持下去,很多东西都会豁然开朗。 \u0026gt; 2024-02-27 11:59\n如果你觉得 sicp 过于难,试试《composing programs》,如果还是不合适,那我们推荐《程序设计方法》(中文版,英文版);如果你觉得 sicp 过于简单,那我们推荐 《concepts, techniques, and models of computer programming》 ;如果读这些让你觉得没有收获,也许你应该先学习其他科目,一两年后再重新审视编程的理念。\n新版原文删除了对《concepts, techniques, and models of computer programming》一书的推荐,但这本书对各种编程模型有深入的见解,值得一读。所以译文中依然保留。 — 译者注\n最后,有一点要说明的是:本指南不适用于完全不懂编程的新手。我们假定你是一个没有计算机专业背景的程序员,希望填补一些知识空白。事实上,我们把“编程”章节包括进来只是提醒你还有更多知识需要学习。对于那些从来没有学过编程,但又想学的人来说,这份 指南 更合适。\n计算机系统结构 计算机系统结构 – 有时候又被称为“计算机系统”或者“计算机组成” – 是了解软件底层的重要视角。根据我们的经验,这是自学的软件工程师最容易忽视的领域。\n我们最喜欢是入门书是 《深入理解计算机系统》 ,典型的 计算机体系结构导论课程 会涵盖本书的 1-6 章。\n我们喜爱《深入理解计算机系统》,因为它的实用性,并且站在程序员的视角。虽然计算机体系的内容比本书所涉及的内容多得多,但对于那些想了解计算机系统以求编写更快、更高效、更可靠的软件的人来说,这本书是很好的起点。\n![[assets/pasted image 20230525153608.png|200]] ![[assets/pasted image 20230525153623.png|230]]\n对那些既想了解这个主题又想兼顾硬件和软件的知识的人来说,我们推荐 《计算机系统要素》,又名“从与非门到俄罗斯方块” (nand2tetris),这本书规模宏大,让读者对计算机内的所有部分如何协同工作有完全的认识。这本书的每一章节对应如何构建计算机整体系统中的一小部分,从用 hdl (硬件描述语言)写基本的逻辑门电路出发,途径 cpu 和汇编,最终抵达诸如俄罗斯方块这般规模的应用程序。\n:: “书山有路勤为径,学海无涯苦作舟。” \u0026gt; 2024-02-27 14:07\n:: 是我们这个时代获取知识太容易了吗?为什么那么多人守着金山乞讨呢?书中自有‘黄金屋’哦。\n我们推荐把此书的前六章读完,并完成对应的项目练习。这么做,你将更加深入地理解计算机体系结构和运行其上的软件之间的关系。\n:: 为什么以前不喜欢做练习?逃避呗,没学透,又担心暴露,不过是一种自欺欺人的办法。😅\n这本书的前半部分(包括所有对应的项目)均可从 nand2tetris 的网站上 免费获得。同时,在 coursera 上,这是一门 视频课程 。\n为了追求简洁和紧凑,这本书牺牲了内容上的深度。尤其值得注意的是,流水线和存储层次结构是现代计算机体系结构中极其重要的两个概念,然而这本书对些几乎毫无涉及。\n当你掌握了 nand2tetris 的内容后,我们推荐要么回到《深入理解计算机系统》,或者考虑 patterson 和 hennessy 二人所著的 《计算机组成与设计》,一本优秀的经典著作。这本书中的不同章节重要程度不一,因此我们建议根据 berkeley 的 cs61c 课程 “计算机体系结构中的伟大思想”来着重阅读一些章节。这门课的笔记和实验在网络上可以免费获得,并且在 互联网档案 中有这门课程的过往资料。\n硬件是平台。 – mike acton 在 cppcon 上的演说\n算法与数据结构 正如几十年来的共识,我们认为,计算机科学教育所赋予人们的最大能量在于对常见算法和数据结构的熟悉。此外,这也可以训练一个人对于各种问题的解决能力,有助于其他领域的学习。\n![[assets/pasted image 20230525153732.png|200]] ![[assets/pasted image 20230525153745.png|168]]\n关于算法与数据结构,有成百上千的书可供使用,但是我们的最爱是 steven skiena 编写的 《算法设计手册》 。显而易见,他对此充满热爱,迫不及待地想要帮助其他人理解。在我们看来,这本书给人一种焕然一新的体验,完全不同于那些更加经常被推荐的书(比如 cormen,leiserson,rivest 和 stein,或者 sedgewick 的书,后两者充斥着过多的证明,不适合以解决问题为导向的学习)。\n如果你更喜欢视频课程, skiena 慷慨地提供了他的课程 。此外,tim roughgarden 的课程也很不错,在 stanford 的 mooc 平台 lagunita ,或者 coursera 上均可获得。skiena 和 roughgarden 的这两门课程没有优劣之分,选择何者取决于个人口味。\n至于练习,我们推荐学生在 leetcode 上解决问题,leetcode 上的问题往往有趣且带有良好的解法和讨论。此外,在竞争日益激烈的软件行业,这些问题可以帮助你评估自己应对技术面试中常见问题的能力。我们建议大约 100 道随机挑选的 leetcode 问题,作为学习的一部分。\n最后,我们强烈推荐 《怎样解题》 ,这本书极为优秀且独特,指导人们解决广义上的问题,因而一如其适用于数学,它适用于计算机科学。\n我可以广泛推荐的方法只有一个: 写之前先思考。 – richard hamming\n数学知识 从某个角度说,计算机科学是应用数学的一个“发育过度”的分支。尽管许多软件工程师试图 – 并且在不同程度上成功做到 – 忽视这一点,我们鼓励你用学习来拥抱数学。如若成功,比起那些没有掌握数学的人,你将获得巨大的竞争优势。\n对于计算机科学,数学中最相关的领域是“离散数学”,其中的“离散”与“连续”相对立,大致上指的是应用数学中那些有趣的主题,而不是微积分之类的。由于定义比较含糊,试图掌握离散数学的全部内容是没有意义的。较为现实的学习目标是, 了解逻辑、排列组合、概率论、集合论、图论以及密码学相关的一些数论知识 。考虑到 线性代数 在计算机图形学和机器学习中的重要性,该领域同样值得学习。\n学习离散数学,我们建议从 lászló lovász 的课程笔记 开始。lovász 教授成功地让这些内容浅显易懂且符合直觉,因此,比起正式的教材,这更适合初学者。\n对于更加高阶的学习,我们推荐 《计算机科学中的数学》 ,mit 同名课程的课程笔记,篇幅与书籍相当(事实上,现已出版)。这门课程的视频同样可 免费获得 ,是我们所推荐的学习视频。\n![[assets/pasted image 20230525153906.png|200]]\n对于线性代数,我们建议从 essence of linear algebra 系列视频开始,然后再学习 gilbert strang 的 《线性代数导论》 和 视频课程 。\n如果人们不相信数学是简单的,那么只能是因为他们没有意识到生活有多么复杂。 — john von neumann\n操作系统 《操作系统概念》 (“恐龙书”)和 《现代操作系统》 是操作系统领域的经典书籍,二者都因为写作风格和对学生不友好而招致了一些批评。\n![[assets/pasted image 20230525153934.png|201]]\n《操作系统导论》 (operating systems: three easy pieces) 是一个不错的替代品,并且可在网上 [免费获得(英文版)](http://pages.cs.wisc.edu/~remzi/ostep/。我们格外喜欢这本书的结构,并且认为这本书的习题很值得一做。\n在读完《操作系统导论》后,我们鼓励你探索特定操作系统的设计。可以借助 “{os name} internals” 风格的书籍,比如 [lion’s commentary on unix](https://www.amazon.com/lions-commentary-unix-john/dp/1573980137/, [the design and implementation of the freebsd operating system](https://www.amazon.com/design-implementation-freebsd-operating-system/dp/0321968972/,以及 mac os x internals。对于 linux ,我们推荐 robert love 的 《linux 内核设计与实现》。\n为了巩固对操作系统的理解,阅读小型系统内核的代码并且为其增加特性是一个很不错的方法。比如 xv6 ,由 mit 的一门课程所维护的从 unix v6 到 ansi c 和 x86 的移植,就是一个很棒的选择。《操作系统导论》有一个附录,记载了一些可能的 xv6 实验项目,其中关于潜在项目的很棒想法。\n计算机网络 鉴于有那么多关于网络服务端和客户端的软件工程,计算机网络是计算机科学中价值最为“立竿见影”的领域之一。我们的学生,系统性的学习了计算机网络,最终能够理解那些曾困扰他们多年的术语、概念和协议。\n![[assets/pasted image 20230525154005.png|200]]\n在这一主题上,我们最爱的书籍是 《计算机网络:自顶向下方法》。书中的小项目和习题相当值得练习,尤其是其中的“wireshark labs”(这部分在 网上可以获得)。\n如果更喜欢视频课程,我们推荐 stanford 的 introduction to computer networking ,可在他们的 mooc 平台 lagunita 上免费观看。\n对于计算机网络的学习,做项目比完成小的习题更有益。一些可能的项目有: http 服务器,基于 udp 的聊天 app ,迷你 tcp 栈,代理,负载均衡器,或者分布式哈希表。\n你无法盯着水晶球预见未来,未来的互联网何去何从取决于社会。 – bob kahn\n数据库 比起其他主题,自学数据库系统需要更多的付出。这是一个相对年轻的研究领域,并且出于很强的商业动机,研究者把想法藏在紧闭的门后。此外,许多原本有潜力写出优秀教材的作者反而选择了加入或创立公司。\n鉴于如上情况,我们鼓励自学者大体上抛弃教材,而是从 2015 年春季学期的 cs 186 课程 (joe hellerstein 在 berkeley 的数据库课程)开始,然后前往阅读论文。\n对于初学者,有一篇格外值得提及的论文:“architecture of a database system”。这篇论文提供了独特的对关系型数据库管理系统(rdbms)如何工作的高层次观点,是后续学习的实用梗概。\n![[assets/pasted image 20230525154037.png|200]] ![[assets/pasted image 20230525154047.png|185]]\n《readings in database systems》,或者以 数据库“红书” 更为人知,是由 peter bailis,joe hellerstein 和 michael stonebraker 编纂的论文合集。对于那些想要在 cs 186 课程的水平更进一步的学习者,“红书”应当是下一步。\n如果你坚持一定要一本导论教材,那我们推荐 ramakrishnan 和 gehrke 所著的 《数据库管理系统:原理与设计》。如需更深一步,jim gray 的经典著作 《transaction processing: concepts and techniques》 值得一读,不过我们不建议把这本书当作首要资源。\n如果没有编写足够数量的代码,很难巩固数据库理论。 cs 186 课程的学生给 spark 添加特性,倒是不错的项目,不过我们仅仅建议从零实现一个简单的关系型数据库管理系统。自然,它将不会有太多的特性,但是即便只实现典型的关系型数据库管理系统每个方面最基础的功能,也是相当有启发的。\n最后,数据模型往往是数据库中一个被忽视的、教学不充分的方面。关于这个主题,我们推荐的书籍是 data and reality: a timeless perspective on perceiving and managing information in our imprecise world。\n编程语言与编译器 多数程序员学习编程语言的知识,而多数计算机科学家学习编程语言相关的知识。这使得计算机科学家比起程序员拥有显著的优势,即便在编程领域!因为他们的知识可以推而广之:相较只学习过特定编程语言的人,他们可以更深入更快速地理解新的编程语言。\n我们推荐的入门书是 bob nystrom 所著的优秀的 crafting interpreters,可在网上免费获取。这本书条理清晰,富有趣味性,非常适合那些想要更好地理解语言和语言工具的人。我们建议你花时间读完整本书,并尝试任何一个感兴趣的“挑战”。\n另一本更为传统的推荐书籍是 《编译原理》,通常称为“龙书”。不幸的是,这本书不是为自学者而设计的,而是供教师从中挑选一些主题用于 1-2 学期的教学。\n![[assets/pasted image 20230525154146.png|200]]\n如果你选择使用龙书进行自学,你需要从中甄选主题,而且最好是在导师的帮助下。我们建议依据某个视频课程来设定学习的结构,然后按需从龙书中获取深入的内容。我们推荐的在线课程是 alex aiken 在 mooc 平台 edx 所开设的。\n不要做一个只写样板代码的程序员。相反,给用户和其他程序员创造工具。从纺织工业和钢铁工业中学习历史教训:你想制造机器和工具,还是操作这些机器? — ras bodik 在他的编译器课程伊始\n分布式系统 随着计算机在数量上的增加,计算机同样开始分散。尽管商业公司过去愿意购买越来越大的大型机,现在的典型情况是,甚至很小的应用程序都同时在多台机器上运行。思考这样做的利弊权衡,即是分布式系统的研究所在,也是越来越重要的一项技能。\n![[assets/pasted image 20230525154232.png|325]]\n我们推荐的自学参考书是 martin kleppmann 的 《数据密集型应用系统设计》。与传统的教科书相比,它是一本为实践者设计的具有很高的可读性的书,并且保持了深度和严谨性。\n对于那些偏爱传统教材,或者希望可以从网上免费获取的人,我们推荐的教材是 maarten van steen 和 andrew tanenbaum 所著的《分布式系统原理与范型》(中文第二版,英文第三版)。\n对于喜欢视频课程的人,mit 的 6.824 是一门很好的在线视频课程,由 robert morris 教授的研究生课程,在这里可以看到课程安排。\n不管选择怎样的教材或者其他辅助资料,学习分布式系统必然要求阅读论文。这里 有一个不错的论文清单,而且我们强烈建议你出席你当地的 papers we love (仅限美国)。\nfaq 常见问题解答 这份指引的目标受众是? 我们面向自学的软件工程师、培训班学生、“早熟的”高中生或者想要通过自学补充正式教育的大学生。关于何时开启这段自学旅程,完全取决于个人,不过多数人在有一定的职业经历后深入学习计算机科学理论会获益匪浅。比如,我们注意到,如果学生在工作中曾经使用过数据库,他们会喜爱学习数据库系统课程;如果学生从事过一两个 web 项目,他们会喜爱学习计算机网络。\n人工智能/计算机图形学/xx 主题怎么样? 我们试图把计算机科学主题清单限制到那些我们认为每一个软件工程师都应该了解的内容,不限于专业或行业。拥有了这些基础,你将能更加轻松地挑选教材或论文,然而无需指引地学习核心概念。在这里,我们给出一些其他常见主题的自学起点:\n人工智能:通过观看视频并完成 pacman 项目来学习 berkeley 的 ai 课程。至于教材,使用 russell 和 norvig 编写的 《人工智能:一种现代方法》。 机器学习:学习吴恩达在 coursera 上的课程。耐心学习,先确保理解了基础概念再奔向类如深度学习的诱人新主题。 计算机图形学:学习 berkeley cs 184 课程 的材料,使用 《计算机图形学:原理及实践》 作为教材。 一定要严格遵守推荐的学习次序吗? 事实上,所有主题之间都有一定程度的重叠,彼此循环引用。以离散数学和算法的关系为例:先学习数学可以帮助你更深入地分析和理解算法,然而先学习算法可以为学习离散数学提供更大的动力和应用背景。理想情况下,你将在你的职业生涯多次重温二者。\n因此,我们所推荐的次序主要是为了帮助你起步……如果你出于某种强烈的原因而倾向以不同的顺序学习,那也没有关系,勇敢开始吧!不过在我们看来,最重要的“先决条件”是: 先学计算机体系结构再学操作系统或数据库,先学计算机网络和操作系统再学分布式系统。\n:: 计算机体系 \u0026gt; 操作系统 \u0026amp; 数据库, 计算机网络 \u0026amp; 操作系统 \u0026gt; 分布式系统。 \u0026gt; 2024-02-27 14:24\n和其他指引比起来,这份指引? aka. open source society、freecodecamp curricula\noss 指引 涵盖太多主题,在许多主题中推荐劣质资源,没有就特定课程哪些方面有价值提供原因或指引。我们努力对这份指引中的课程加以限制,仅仅包括那些你作为软件工程师确实需要了解的,不论你的专业方向,并且对每门课程为何必要做出了解释以帮助你理解。\nfreecodecamp 主要关注编程,而不是计算机科学。至于你为什么要学习计算机科学,参见上文。如果你是个新手,我们建议先学 freecodecamp 的课程,一两年后再回归本指南。\nxx 编程语言怎么样? 学习一门特定的编程语言和学习计算机科学的一个领域 完全不在一个维度 —— 相比之下,学习语言容易且缺乏价值。如果你已经了解了一些语言,我们强烈建议遵照我们的指引,然后在学习的空当中习得语言,或者暂且不管以后再说。如果你已经把编程学得不错了(比如学完了《计算机程序的构造和解释》),尤其是如果你学习过编译器,那么面对一门新的语言,你只需要花一个周末稍多的时间即可基本掌握,之后你可以在工作中学习相关的类库/工具/生态。\nxx 流行技术怎么样? 没有任何一种技术的重要程度可以达到学习其使用足以成为计算机科学教学的核心部分。不过,你对学习那门技术充满热情,这很不错。诀窍是先从特定的技术回退到基本的领域或概念,判断这门流行技术在技术的宏观大局中位于何处,然后才深入学习这门技术。\n:: 计算机科学的核心是什么?\n为什么你们还在推荐 sicp? 先尝试读一下,有些人觉得 sicp 让人神魂颠倒,这在其他书很少见。如果你不喜欢,你可以尝试其他的东西,也许以后再回到 sicp。\n为什么你们还在推荐龙书? 龙书依旧是内容最为完整的编译器单本书籍。由于过分强调一些如今不够时新的主题的细节,比如解析,这本书招致了恶评。然而事实上,这本书从未打算供人一页一页的学习,而仅仅是为了给教师准备一门课程提供足够的材料。类似地,自学者可以从书中量身按需挑选主题,或者最好依照公开课授课教师在课程大纲中的建议。\n如何便宜获取教材? 我们所建议的许多教材在网上都可以免费获得,这多亏了作者们的慷慨。对于那些不免费的书籍,我们建议购买旧版本的二手书籍。广而言之,如果一本教材有多个版本,旧版本大概率是完全足够使用的。即便新版本的价格是旧版本的 10 倍,新版本也绝不可能比旧版本好 10 倍!\n中文翻译新增: 事实上,比起美国,在国内购买技术书籍可以说是相当“廉价”了。如果仍旧寻求更加便宜的购买渠道,可以参考这篇 v2ex 上的 讨论帖子,其中提到了一些不错的购买渠道。\n这份指引是谁写的? 这份指引由 bradfield school of computer science (旧金山)的两位教员: ozan onay 和 myles byrne 编写,并由 oz 于 2020 年更新。这份指引基于我们对数千名自学成才的工程师和培训班学生教授计算机科学基础的经验。感谢我们所有学生对自学资源的持续反馈。\n只要有足够的时间和动力,我们非常有信心,你可以自学完以上所有课程。如果你喜欢一个集中式、结构化、由教师指导的课程,你可能对我们的 计算机科学强化班 感兴趣。我们 不建议 你去攻读硕士学位。\n这份指引是谁翻译的? 这份指引的中文翻译是 社区共同贡献的成果,我们欢迎任何反馈和改进!\n","date":"2023-05-25","permalink":"https://aituyaa.com/%E5%A6%82%E4%BD%95%E5%AD%A6%E4%B9%A0%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6/","summary":"\u003cp\u003ei.e. Teach Yourself Computer Science\u003c/p\u003e\n\u003cp\u003e🔔 转载 \u003ca href=\"https://teachyourselfcs.com/\"\u003e英文原文\u003c/a\u003e、\u003ca href=\"https://github.com/keithnull/TeachYourselfCS-CN/blob/master/TeachYourselfCS-CN.md\"\u003e中文译本\u003c/a\u003e\u003c/p\u003e","title":"⭐️如何学习计算机科学"},]
[{"content":"什么是 ajax ?\n![[assets/pasted image 20230525181742.png]]\najax(asynchronous javascript and xml),是一种异步请求数据的 web 开发技术。它不是新的编程语言,而是一种使用现有标准的新方法。其最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。\n注:ajax 不需要任何浏览器插件,但需要用户允许 javascript 在浏览器上执行。\n进程、线程 1\n:: 在讲述 ajax 之前,有必要先了解一些进程和线程方面的知识。\n进程(process)和线程(thread)是操作系统的基本概念,比较抽象,不易掌握。\n对于操作系统,一个任务就是一个 进程 (process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就是启动了两个记事本进程。\n有些进程不止同时只做一件事,比如 word ,它可以同时进行打字、拼写检查、打印等事情。\n在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为 线程 (thread)。\n注:一个进程至少有一个线程。\n:: 哈哈,线程才是真正做事的喽。\n线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。\n多进程和多线程的程序涉及到同步、数据共享的问题,编写进来更复杂。\n阮一峰老师写过一篇 进程与线程的一个简单解释 也不错,浅显易懂,推荐一下。\n操作系统的设计,可以归结为三点:\n以多进程形式,允许多个任务同时进行; 以多线程形式,允许单个任务分成不同的部分进行; 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。 由此,也就容易理解,如果有很多任务需要执行,不外乎三种解决办法:\n排队 。因为一个进程一次只能执行一个任务,只好等前面的任务执行完了,再执行后面的任务; 新建进程 。使用 fork 命令,为每个任务新建一个进程; 新建线程 。因为进程太耗费资源,所以如今的程序往往允许一个进程包含多个线程,由线程去完成任务。 *注:一般情况下,一个进程一次只能执行一个任务(即包含一个线程)。\n单线程的 js 以 javascript 语言为例,它是一种单线程语言,所有任务都在一个线程上完成,任务只好排队喽。一旦遇到大量任务或者遇到一个耗时的任务,网页就会出现“假死”(javascript 停不下来,也就无法响应用户的行为)。\n![[assets/pasted image 20230525181835.png|350]]\n那么,javascript 是如何解决这个问题的呢? 事件循环(event loop)。\nevent loop 是一个程序结构,用于等待和发送消息和事件。\n简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为“主线程”;另一个负责主线程与其他线程(主要是各种 i/o 操作)的通信,被称为“event loop 线程”(或“消息线程”)。\n如右图所示,主线程的绿色部分,表示运行时间,橙色部分表示空闲时间。每当遇到 i/o 的时候,主线程就让 event loop 线程去通知相应的 i/o 程序,然后接着往后运行,所以不存在红色的等待时间。等到 i/o 程序完成操作,event loop 线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。\n不难看出,由于多出了橙色的空闲时间,所以主线程得以运行更多的任务,这就提高了效率。这种运行模式称为“异步模式”(asynchronous i/o)或“非堵塞模式”(non-blocking mode)。\n这正是 javascript 语言的运行方式 。\n我们前面说过了,执行多任务的话,还可以新建进程或线程,为什么 javascript 是单线程,难道不能实现多线程吗?有兴趣的话,可以自己去捉摸一下。\n多线程的浏览器 2\n了解更多浏览器底层运行机制,可以阅读另一篇文章 - 浏览器 🌐\n前面我们已经大概了解了进程和线程,稍微回顾下相关概念。\n处理器(cpu)是计算机的核心,其负责承担计算机的计算任务。\n进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。\n注:任一时刻,一个单核 cpu 只能运行一个进程,其他进程则处于非运行状态。\n线程是程序执行中单一和顺序流程,是程序执行的最小单元。\n那么,进程和线程是什么关系呢?\n(1)进程是操作系统分配资源的最小单位,线程是程序执行的最小单位;\n(2)一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;\n(3)进程之间相互独立,但同一个进程下和各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号);\n(4)调试和切换:线程上下文切换比进程上下文切换要快得多。\n![[assets/pasted image 20230525181912.png|175]]\n浏览器内核 是通过取得页面内容、整理信息(应用 css)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。\nchrome 浏览器为每个 tab 页面单独启用进程,在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程)。\n浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:\n- gui 渲染线程; - js 引擎线程; - 事件触发线程; - 定时触发线程; - 异步 http 请求线程。 下面我们分别来了解下浏览器的这些常驻线程。\ngui 渲染线程 gui 渲染线程负责渲染浏览器界面 html 元素,解析 html、css,构建 dom 树和 renderobject 树,布局和绘制等。\n当界面需要重绘(repaint)或由于某种操作引发回流(重排 reflow)时,该线程就会执行。\n在 javascript 引擎运行脚本期间,gui 渲染线程都是处于挂起状态的,gui 更新会被保存在一个队列中等到 js 引擎空闲时立即被执行。\njavascript 引擎线程 javascript 引擎,也可以称为 js 内核,主要负责处理 javascript 脚本程序,如 v8 引擎。\njs 引擎一直等待着任务队列中的任务的到来,然后加以处理,一个 tab 页面(render 进程)中无论什么时候都只有一个 js 线程在运行 js 程序(因为 js 是单线程的啊)。\n注:gui 渲染线程和 javascript 引擎线程互斥!\n但是,为什么要互斥呢?\n由于 javascript 中可操纵 dom 的,如果在修改这些元素同时渲染界面(即 javascript 线程和 gui 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此,为了防止渲染出现不可预期的结果,浏览器就设置 gui 渲染线程与 javascript 引擎为互斥的关系了。\n事件触发线程 当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 js 引擎的处理。\n这些事件可以是当前执行的代码块,如定时任务,也可以是来自浏览器内核的其他线程,如鼠标点击、ajax 异步请求等,但由于 js 的单线程关系,所有这些事件都得排队等待 js 引擎处理。\n定时触发器线程 浏览器定时计数器并不是由 javascript 引擎计数的,因为 javascript 引擎是单线程的,如果处于阻塞线程状态就会影响计时的准确。\n通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待 js 引擎空闲后执行)。\n*注:w3c 在 html 标准中规定 settimeout 中低于 4ms 的时间间隔算为 4ms 。\n异步 http 请求线程 xmlhttprequest 在连接后是通过浏览器新开一个线程请求,在检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调放入事件队列中,再由 javascript 引擎执行。\njavascript 运行机制 3\n:: 学习一门语言,再怎么了解它的运行机制都是不为过的。\n在了解 ajax 之前,我们再通过以下几个方面,来加强一下对 javascript 的运行机制的认识。\n- javascript 是单线程的; - 任务队列; - 事件和回调函数; - event loop ; - 定时器; - nodejs 中的 event loop 。 为什么 js 是单线程 javascript 语言的一大特点就是单线程,即同一时间只能做一件事。前面我们已经讲述过为什么 javascript 不能有多个线程(多线程能提高效率啊),我们这里再稍微赘述一下。\n原来,javascript 的单线程,与它的用途有关。\n作为浏览器的脚本语言,javascript 的主要用途是与用户互动,以及操作 dom 。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 javascript 同时有两个线程,一个线程在某个 dom 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?\n所以,为了避免复杂性,从一诞生,javascript 就是单线程,这已经成了这门语言的核心特征。\n为了利用多核 cpu 的计算能力,html5 提出了 web worker 标准,允许 javascript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 dom 。所以,这个新标准并没有改变 javascript 单线程的本质。\n任务队列 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。\n如果排队是因为计算量大,cpu 忙不过来,倒也算了,但是多数时候 cpu 是闲着的,因为 i/o 设备很慢(比如 ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。\n:: 明显对 cpu “压榨”的不够啊,嘿嘿……\njavascript 语言的设计者意识到,这时主线程完全可以不管 i/o 设备,挂起处于等待中的任务,先运行排在后面的任务。等待 i/o 设备返回了结果,再回过头,把挂起的任务继续执行下去。\n于是,所有任务可以分成两种: 同步任务 (synchronous)和 异步任务 (asynchronous)。\n同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。\n异步任务指的是,不进入主线程,而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。\n![[assets/pasted image 20230525182019.png]]\n具体来说,异步执行的运行机制如下:\n(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。\n(2)主线程之外,还存在一个“任务队列”(task queue)。只要异步任务有了运行结果,就在“任务队列”这中放置一个事件。\n(3)一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件,哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。\n(4)主线程不断重复上面的第三步。\n只要主线程空了,就会去读取“任务队列”,这就是 javascript 的运行机制,且这个过程会不断重复。\n:: 有一点需要进一步了解,就是类似于 async/await 这种语法糖的底层执行机制 ❓\n事件和回调函数 “任务队列”是一个事件的队列(也可以理解成消息的队列),i/o 设备完成一项任务,就在“任务队列”中添加一个事件,表示相关的异步任务可以进入“执行栈”了。主线程读取“任务队列”,就是读取里面有哪些事件。\n“任务队列”中的事件,除了 i/o 设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入“任务队列”,等待主线程读取。\n所谓“回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始异步任务,就是执行对应的回调函数。\n“任务队列”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上的自动的,只要执行栈一清空,“任务队列”上第一位的事件就自动进入主线程。但是,由于存在后文提到的“定时器”功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。\nevent loop 主线程从“任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为 event loop (事件循环)。\n![[assets/pasted image 20230525182036.png|400]]\n如上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部 api,它们在“任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取“任务队列”,依次执行那些事件所对应的回调函数。\n执行栈中的代码(同步任务),总是在读取“任务队列”(异步任务)之前执行。\n定时器 除了放置异步任务的事件,“任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做“定时器”(timer)功能,也就是定时执行的代码。\n定时器功能主要由 settimeout() 和 setinterval() 这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。\n语法如下:\nsettimeout(fn, time) html5 标准规定了 settimeout() 的第二个参数的最小值(最短间隔),不得低于 4ms ,如果低于这个值,就会自动增加。\n*注: settimeout() 只是将事件插入了“任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在 settimeout() 指定的时间执行。\nnodejs 的 event loop nodejs 也是单线程的 event loop,但是它的运行机制不同于浏览器环境。\n![[assets/pasted image 20230525182054.png|550]]\n根据上图,nodejs 的运行机制如下:\n1. v8 引擎解析 javascript 脚本; 2. 解析后的代码,调用 node api; 3. libuv 库负责 node api 的执行; 4. v8 引擎再将结果返回给用户。 其中, libuv 将不同的任务分配给不同的线程,形成一个 event loop ,以异步的方式将任务的执行结果返回给 v8 引擎。\n除了 settimeout 和 setinterval 这两个方法,nodejs 还提供了另外两个与“任务队列”有关的方法: process.nexttick 和 setimmediate ,它们可以帮助我们加深对“任务队列”的理解。\nprocess.nexttick 方法可以在当前“执行栈”的尾部 – 下一次 event loop (主线程读取“任务队列”)之前 – 触发回调函数。也就是说, 它指定的任务总是发生在所有异步任务之前。\nsetimmediate 方法则是当前“任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次 event loop 时执行,这与 settimeout(fn, 0) 很像。\n*注:如果有多个 process.nexttick 语句(不管它们是否嵌套),将全部在当前“执行栈”执行。即多个 process.nexttick 语句总是在当前“执行栈”一次执行完。\n由于 process.nexttick 指定的回调函数是在本次\u0026quot;事件循环\u0026quot;触发,而 setimmediate 指定的是在下次\u0026quot;事件循环\u0026quot;触发,所以很显然,前者总是比后者发生得早,而且执行效率也高(因为不用检查\u0026quot;任务队列\u0026quot;)。\najax 异步的原理 :: hmm\u0026hellip; 终于讲到 ajax 了……\n在正式了解 ajax 之前,我们先来看看它的原理。前面的章节中,我们了解了浏览器的多线程:gui 渲染线程、javascript 引擎线程、事件触发线程、http 请求线程、定时器触发线程。\n对于一个 ajax 请求:\n(1)javascript 引擎首先生成 xmlhttprequest 实例对象, open 过后再调用 send 方法。至此,所有的语句都是同步执行。\n(2)但是从 send 内部开始,浏览器为将要发生的网络请求创建了新的 http 请求线程,这个线程独立于 javascript 引擎线程,于是网络请求异步被发送出去了。另一方面,javascript 引擎并不会等待 ajax 发起的 http 请求收到结果,而是直接顺序往下执行。\n(3)当 http 请求收到 response 后,浏览器事件触发线程捕获到了 ajax 的回调事件,该回调事件并不会立即被执行,而是以先进先出的方式添加到任务队列的末尾,等到 javascript 引擎空闲时,任务队列中排队的任务将会依次被执行,循环读取事件。这些事件回调包括 settimeout、setinterval、click、ajax 异步请求 等。\n(4)在回调事件内部,有可能对 dom 进行操作,此时浏览器便会挂起 javascript 引擎线程,转而执行 gui 渲染线程,进行页面重绘(repaint)或者回流(reflow)。当 javascript 引擎重新执行时,gui 渲染线程又会被挂起,gui 更新将被保存起来,等到 javascript 引擎空闲时立即被执行。\ngui 渲染线程和 javascript 引擎线程是互斥的。 其他线程相互之间,都是可能并行执行的,ajax 并没有破坏 javascript 的单线程机制。\najax 的请求过程 ![[assets/pasted image 20230525182118.png]]\n创建 xmlhttprequest 对象; 浏览器与服务器建立连接 open ; 设置超时,回调函数; 浏览器向服务器发送请求 send ; 服务器向浏览器响应请求,客户端获取异步调用返回的数据; 实现局部刷新。 下面我们就按照这个过程来逐步地认识一下 ajax 喽。\n创建 xmlhttprequest 对象 注:以下 xmlhttprequest 均简写为 xhr。\n所有现代浏览器均支持 xmlhttprequest 对象(ie5 和 ie6 使用 activexobject )。\n为了应对所有的现代浏览器,包括 ie5 和 ie6,请检查浏览器是否支持 xmlhttprequest 对象。如果支持,则创建 xmlhttprequest 对象。如果不支持,则创建 activexobject ,我们不妨自己封装一个函数来实现获取 xhr 对象的目的,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 function getxhr() { var xhr; if (window.xmlhttprequest) { xhr = new xmlhttprequest(); } else { // ie5/6 xhr = new activexobject(\u0026#39;microsoft.xmlhttp\u0026#39;); } return xhr; } xhr 请求 其中,2、3、4 三步都为 xhr 请求,我们放在一起来讲述。\nxhr 对象用于和服务器交换数据,如需将请求发送到服务器,使用 xhr 对象的 open() 和 send() 方法即可,如下:\nopen(method, url, async) # 规定请求的类型、url 以及是否异步处理请求 # - method 请求的类型:get 或 post 等 # - url :文件在服务器上的位置 # - async :true (异步,默认值) false (同步) send([data]) # 将请求发送到服务器 # - data:可选,仅用于 post 请求 来看个实例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 // get xhr.open(\u0026#39;get\u0026#39;, \u0026#39;/try/ajax/demo_get\u0026#39;, true); xhr.send(); // post xhr.open(\u0026#39;post\u0026#39;, \u0026#39;/try/ajax/demo_post\u0026#39;, true); xhr.send(); // 如果需要像 html 表单那样 post 数据, // 请使用 setrequestheader() 来添加 http 头, // 然后在 send() 方法中传入要发送的数据 xhr.open(\u0026#39;post\u0026#39;, \u0026#39;/try/ajax/demo_post\u0026#39;, true); xhr.setrequestheader(\u0026#39;content-type\u0026#39;, \u0026#39;application/x-www-form-urlencode\u0026#39;); xhr.send(\u0026#39;fname=jack\u0026amp;lname=liu\u0026#39;); xhr 对象如果要用于 ajax 的话,其 open() 方法的 async 参数必须设置为 true ,当使用 async=true 时,请规定在响应处于 onreadystatechange 事件中的就绪状态时执行的函数:\n1 2 3 4 5 6 7 8 xhr.open(\u0026#39;get\u0026#39;, \u0026#39;/try/ajax/ajax_info.txt\u0026#39;, true); xhr.onreadystatechange = function () { if (xhr.readystate == 4 \u0026amp;\u0026amp; xhr.status == 200) { // xhr.reponsetext console.log(xhr.responsetext); } }; xhr.send(); xhr 响应 如需获得来自服务器的响应,请使用 xhr 对象的 responsetext (字符串形式的响应数据)或 responsexml (xml 形式的响应数据)属性。\n当请求被发送到服务器时,我们需要执行一些基于响应的任务,每当 readystate 改变时,就会触发 onreadystatechange 事件,其中, readystate 属性存有 xhr 的状态信息。\n属性 描述 onreadystatechange 存储函数(或函数名),每当 readystate 属性改变时,就会调用该函数 ———————- ——————————————————————– readystate 存有 xhr 的状态: 0 :请求未初始化 1 :服务器连接已建立 2 :请求已接收 3 :请求处理中 4 :请求已完成,且响应已就绪 ———————- ——————————————————————– status 200 :ok 404 : 未找到页面 当 readystate 等于 4 且状态为 200 时,表示响应已就绪。\n1 2 3 4 5 xhr.onreadystatechange = function () { if (xhr.readystate == 4 \u0026amp;\u0026amp; xhr.status == 200) { // ... } }; *注: onreadystatechange 事件被触发 4 次(0 - 4),分别是: 0-1、1-2、2-3、3-4 对应着 readystate 的每个变化。\n*http 协议 :: 我们已经初步了解 ajax 了,深入地话,就需要进一步认识一下 http 协议喽。\nhttp 协议(hypertext transfer protocol,超文本传输协议),是一个基于 tcp/ip 通信协议来传递数据(html 文件、图片、查询结果等)的协议。\n![[assets/pasted image 20230525182156.png]]\nhttp 协议工作于“客户端-服务端”架构上,如浏览器(http 客户端)通过 url 向 web 服务器( http 服务端)发送所有请求。\nhttp 三点注意事项:\n(1)http 是无连接:无连接的含义是限制每次连接只处理一个请求,采用这种方式可以节省传输时间。服务器处理完客户的请求,并收到客户的应答后,即断开连接。\n:: 无连接???\n(2)http 是媒体独立的:只要客户端和服务器 知道如何处理 的数据内容,任何类型的数据都可以通过 http 发送,客户端以及服务器指定使用适合的 mime-type 内容类型。\n(3)http 是无状态:http 协议是无状态协议。 无状态 是指协议对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要前面的信息,则它 必须重传 ,这样可能导致每次连接传送的数据量增大;另一方面,在服务器不需要先前信息时它的应答就较快。\nhttp 消息结构 http 是基于“客户端/服务端”的架构模型,通过一个可靠的链接来交换信息,是一个 无状态的请求/响应 协议。\n一个 http 客户端,是一个应用程序,如 web 浏览器或其他任何客户端,它通过连接服务器达到向服务器发送一个或多个 http 请求的目的。\n一个 http 服务器,同样是一个应用程序,通常是一个 web 服务器,如 apache、nginx 或 iis 服务器等,它通过接收客户端的请求并向客户端发送 http 响应数据。\n一个可靠的链接,http 使用 uri(uniform resource identifiers,统一资源标识符)来传输数据和建立连接。\n客户端请求消息 ![[assets/pasted image 20230525182218.png]]\n客户端发送一个 http 请求到服务器的请求消息包括以下格式:\n请求行(request line); 请求头部(header); 空行; 请求数据。 服务器响应消息 ![[assets/pasted image 20230525182224.png]]\nhttp 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。\nhttp 请求方法 \u0026gt; http 请求方法\n方法 描述 get 请求指定的页面信息,并返回实体主体 head 类似于 get ,只不过返回的响应中没有具体的内容,用于获取报头 post 向指定资源提交数据进行处理请求(如提交表单或者上传文件),数据被包含在请求体中 ———– ——————————————————————————– put 从客户端向服务器传送的数据取代指定的文档的内容 delete 请求服务器删除指定的页面 connect http/1.1 协议中预留给能够将连接改为管道方式的代理服务器 options 允许客户端服务器的性能 http 响应头信息 响应头信息 说明 allow 服务器支持哪些请求方法(如 get、post 等) content-encoding 文档的编码(encode)方法 content-length 表示内容长度(只有当浏览器 使用持久 http 连接时才需要这个数据) content-type 表示后面的档属于什么 mime 类型 date 当前的 gmt 时间,可以用 setdateheader 来设置这个头以避免转换时间格式的麻烦 expires 应该在什么时候认为文档已经过期,从而不再缓存它 last-modified 文档的最后改动时间 location 表示服务器应当到哪里提取文档,通常不是直接设置的 refresh 表示浏览器应该在多少时间之后刷新文档,以秒计 server 服务器名字。servlet 一般不设置这个值,面是由 web 服务器自己设置 set-cookie 设置和页面关联的 cookie www-authenticate 客户应该在 authorization 中提供什么类型的授权信息 content-type 其中, content-type (内容类型),用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。也就是说, content-type 标头告诉客户端实际返回的内容的内容类型。\n语法格式如下:\ncontent-type: text/html; charset=utf-8 content-type: multipart/form-data; boundary=something \u0026gt; 常见的媒体格式类型\n类型 描述 text/html html 格式 text/plain 纯文本格式 text/xml xml 格式 image/gif gif 图片格式 image/jpeg jpg 图片格式 image/png png 图片格式 ————————————- ———————————————- 以 application 开头的媒体格式类型 ————————————- ———————————————- application/xhtml+xml xhtml 格式 application/xml xml 格式 application/atom+xml atom xml 聚合格式 application/json json 数据格式 application/pdf pdf 格式 application/msword word 文档格式 application/octet-stream 二进制流数据(如常见的文件下载) application/x-www-form-urlencoded 表单默认的提交数据的格式 ————————————- ———————————————- application/form-data 需要在表单中进行文件上传时,就需要使用该格式 http 状态码 i.e. http status code\n当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当 浏览器接收并显示网页前 ,此网页所在的服务器会返回一个包含 http 状态码的信息头(server header)用以响应浏览器的请求。\nhttp 状态码 由三个十进制数字组成,其中,第一个数字定义了状态码的类型,后两个数字没有分类的作用,总共分为 5 种类型:\n#+caption: http 状态码分类\n分类 描述 1** 信息,服务器收到请求,需要请求者继续执行操作 2** 成功,操作被成功接收并处理 3** 重定向,需要进一步的操作以完成请求 4** 客户端错误,请求包含语法错误或无法完成请求 5** 服务器错误,服务器在处理请求的过程中发生了错误 下面是常见的状态码:\n200 - 请求成功; 301 - 资源(网页等)被永久转移到其它 url; 404 - 请求的资源(网页等)不存在; 500 - 内部服务器错误。 jquery 中的 ajax 6\njquery 提供多个与 ajax 有关的方法。\n通过 jquery ajax 方法,您能够使用 http get 和 http post 从远程服务器上请求文本、html、xml 或 json - 同时您能够把这些外部数据直接载入网页的被选元素中。\n编写常规的 ajax 代码并不容易,因为不同的浏览器对 ajax 的实现并不相同。这意味着您必须编写额外的代码对浏览器进行测试。不过,jquery 团队为我们解决了这个难题,我们只需要一行简单的代码,就可以实现 ajax 功能。\n:: jquery 也算是 dom 时代的霸主了……\n在现代 web 项目中,我们已经很少使用 jquery 做主力了,除非你要和万恶的 ie 打交道,我们这里只稍微认识一下 jquery 中 ajax 的一般应用。\n方法 描述 $.ajax() 执行异步 ajax 请求 $.ajaxsetup() 为将来的 ajax 请求设置默认值 $.ajaxstart() 规定第一个 ajax 请求开始时运行的函数 $.ajaxstop() 规定所有的 ajax 请求完成时运行的函数 $.ajaxsend() 规定 ajax 请求发送之前运行的函数 $.ajaxsuccess() 规定 ajax 请求成功完成时运行的函数 $.ajaxerror() 规定 ajax 请求失败时运行的函数 $.load() 从服务器加载数据,并把返回的数据放置到指定的元素中 $.serialize() 编码表单元素集为字符串以便提交 … …\naxios 4\n:: 来看看这个比较火热的 ajax 封装库吧。\naxios 是一个基于 promise 的 http 库,可以用在浏览器和 nodejs 中,它具有以下特性:\n- 从浏览器中创建 xmlhttprequests - 从 nodejs 创建 http 请求 - 支持 promise api - 拦截请求和响应 - 转换请求数据和响应数据 - 取消请求 - 自动转换 json 数据 - 客户端支持防御 xsrf 想了解更多关于 promise 的内容,可以阅读另一篇文件 - promise 。\n注:axios 依赖原生的 es6 promise 实现而被支持,如果你的环境不支持 es6 promise ,可以使用 polyfill 。\naxios 起步 先安装喽……\nnpm install axios # npm \u0026lt;script src=\u0026#34;https://unpkg.com/axios/dist/axios.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; # cdn :: 我们提供 javascript 软件包的时候,一般也需要提供 npm 和 cdn 两种分发方式。\n在项目(模块化开发)中,我们一般通过如下两种方式引入 axios :\n1 2 import axios from \u0026#39;axios\u0026#39;; // es6 module const axios = require(\u0026#39;axios\u0026#39;); // commonjs module 当我们引入 axios 之后,会默认导出一个 axios 实例,一般情况下就可以满足需求了。可以通过向 axios 传递相关配置来创建请求,如下:\n1 2 3 4 5 6 7 8 9 10 11 import axios from \u0026#39;axios\u0026#39;; // axios(config) axios({ method: \u0026#39;post\u0026#39;, url: \u0026#39;/user/12345\u0026#39;, data: { firstname: \u0026#39;jack\u0026#39;, lastname: \u0026#39;liu\u0026#39;, }, }); 当然,如果默认的 axios 实例不能满足要求时,可以使用自定义配置创建一个新的 axios 实例(如 instance ):\n1 2 3 4 5 6 7 8 import axios from \u0026#39;axios\u0026#39; // axios.create([config]) const instance = axios.create({ baseurl: \u0026#39;https://some-domain.com/api/\u0026#39;, timeout: 1000, headers: {\u0026#39;x-custom-header\u0026#39;: \u0026#39;foobar\u0026#39;} }) *注:使用创建的 axios 实例请求时,请求的配置项将与实例的配置合并。\n:: 实际应用场景中,强烈建议重新创建一个新的 axios 实例!方便后续预设配置,自带实例有时候会有一些莫名奇妙的问题……\naxios 请求配置 5\n这些都是创建请求时可以用的配置选项, 只有 url 是必需的 ,如果没有指定的 method ,请求将默认使用 get 方法。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 { // url 是用于请求的服务器 url url: \u0026#39;/user\u0026#39; // method 是创建请求时使用的方法 method: \u0026#39;get\u0026#39;, // default // baseurl 将自动加在 url 前面,除非 url 是一个绝对 url // 它可以通过设置一个 baseurl 便于为 axios 实例的方法传递相对 url baseurl: \u0026#39;https://some-domain.com/api/\u0026#39;, // transformrequest 允许在向服务器发送前,修改请求数据 // 只能用在 put、post 和 patch 这几个请求方法 // 后面数组中的函数必须返回一个字符串,或 arraybuffer,或 stream transformrequest: [function (data, headers) { // 对 data 进行任意转换处理 return data; }], // transformresponse 在传递给 then/catch 前,允许修改响应数据 transformresponse: [function (data) { // 对 data 进行任意转换处理 return data; }], // ... } axios 响应结构 某个请求的响应包含以下信息:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { // data 由服务器提供的响应 data: {}, // status 来自服务器响应的 http 状态码 status: 200, // statustext 来自服务器响应的 http 状态信息 statustext: \u0026#39;ok\u0026#39;, // headers 服务器响应的头 headers: {}, // config 是为请求提供的配置信息 config: {}, // request 是生成当前响应的请求 // 在 node.js 中是最后一个 clientrequest 实例 (在重定向中) // 在浏览器中是 xmlhttprequest 实例 request: {} } 使用 then 时,你将接收下面这样的响应:\n1 2 3 4 5 6 7 axios.get(\u0026#39;/user/12345\u0026#39;).then(function (response) { console.log(response.data); console.log(response.status); console.log(response.statustext); console.log(response.headers); console.log(response.config); }); 配置默认值 你可以指定将被用在各个请求的配置默认值。\n全局的 axios 默认值 1 2 3 axios.defaults.baseurl = \u0026#39;http://api.example.com\u0026#39;; axios.defaults.headers.common[\u0026#39;authorization\u0026#39;] = auth_token; axios.defaults.headers.post[\u0026#39;content-type\u0026#39;] = \u0026#39;application/x-www-form-urlencoded\u0026#39;; 自定义实例默认值 1 2 3 4 5 6 7 // 创建实例时设置配置默认值 const instance = axios.create({ baseurl: \u0026#39;https://api.example.com\u0026#39;, }); // 实例创建之后可修改默认配置 instance.defaults.headers.common[\u0026#39;authorization\u0026#39;] = auth_token; 配置的优先顺序 配置会以一个优先顺序进行合并。这个顺序是:在 lib/defaults.js 找到的库的默认值,然后是实例的 defaults 属性,最后是请求的 config 参数,后者将优先于前者。如下:\n1 2 3 4 5 6 7 8 9 10 11 12 // 使用由库提供的配置默认值来创建实例 // 此时超时配置的默认值是 0 const instance = axios.create(); // 覆写库的超时默认值 // 现在,在超时前,所有请求都会等待 2.5 秒 instance.defaults.timeout = 2500; // 为已知需要花费很长时间的请求覆写超时设置 instance.get(\u0026#39;/longrequest\u0026#39;, { timeout: 5000, }); 拦截器 在请求或响应被 then 或 catch 处理前拦截它们。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 添加请求拦截器 axios.interceptors.request.use( function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return promise.reject(error); } ); // 添加响应拦截器 axios.interceptors.response.use( function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return promise.reject(error); } ); 使用 application/x-www-form-urlencoded 格式 默认情况下,axios 将 javascript 对象序列化为 json。要以 application/x-www-form-urlencoded 格式发送数据,使用 qs 库编码数据,如下:\n1 2 const qs = require(\u0026#39;qs\u0026#39;); axios.post(\u0026#39;/foo\u0026#39;, qs.stringify({ bar: 123 })); 框架、插件 如果你想了解更多,请参考 w3cschool axios 。\n","date":"2023-05-25","permalink":"https://aituyaa.com/ajax/","summary":"\u003cp\u003e什么是 Ajax ?\u003c/p\u003e","title":"ajax"},]
[{"content":"\u0026gt; 近来要开发一个手写板程序,顺便来系统回顾一下 canvas 这个东东……\n🔔 本篇摘录自 《学习 html5 canvas 这一篇文章就够了》 ,写的真不错。\n\u0026lt;canvas\u0026gt; 是 html5 新增的一个可以使用脚本(通常为 javascript)在其中绘制图像的 html 元素。\n![[assets/pasted image 20230525170231.png|400]]\n\u0026gt; 上图就是使用 canvas 绘制出来的\ncanvas 是由 html 代码配合高度和宽度属性而定义出的可绘制区域。javascript 代码可以访问该区域,类似于其他通用的二维 api,通过一套完整的绘图函数来动态生成图形。\n基本使用 1. \u0026lt;canvas\u0026gt; 元素\n1 \u0026lt;canvas id=\u0026#34;tutorial\u0026#34; width=\u0026#34;300\u0026#34; height=\u0026#34;300\u0026#34;\u0026gt;\u0026lt;/canvas\u0026gt; \u0026lt;canvas\u0026gt; 标签只有两个可选的属性 width 和 height ,如果省略,则默认 width 为 300 ,height 为 150 ,单位是 px 。也可以使用 css 属性来设置宽高,但是如宽高属性和初始比例不一致,会出现扭曲(强烈不推荐 ❌)。\n2. 渲染上下文 context\n\u0026lt;canvas\u0026gt; 会创建一个固定大小的画布,会公开一个或多个渲染上下文(画笔),使用渲染上下文来绘制和处理要展示的内容。\n我们重点研究 2d 渲染上下文。如何获取它呢?\n1 2 3 var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if(!canvas.getcontext) return;\t// 检测支持性 var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;);\t// 获得 2d 上下文对象 绘制形状 1. 栅格(grid)和坐标空间\n如下图所示,canvas 元素默认被网格所覆盖。通常来说网格中的一个单元相当于 canvas 元素中的一像素。栅格的起点为左上角,坐标为 (0,0) 。所有元素的位置都相对于原点来定位。所以图中蓝色方形左上角的坐标为距离左边(x 轴)x 像素,距离上边(y 轴)y 像素,坐标为 (x,y)。\n![[assets/pasted image 20230525170309.png]]\n后面我们会涉及到坐标原点的平移、网格的旋转以及缩放等。\n2. 绘制矩形\n\u0026lt;canvas\u0026gt; 只支持一种原生的图形绘制:矩形。\n\u0026gt; 所有其他图形都至少需要生成一种路径 (path)。\ncanvas 提供了有三种方法绘制矩形:\n// 绘制一个填充的矩形 1. fillrect(x, y, width, height) // 绘制一个矩形的边框 2. strokerect(x, y, width, height) // 清除指定的矩形区域,然后这块区域会变的完全透明 3. clearrect(x, y, widh, height) 这 3 个方法具有相同的参数。其中, x, y 指矩形左上角的坐标, width, height 指绘制的矩形的宽高。\n上 🌰 ,如下:\n1 2 3 4 5 6 7 8 9 10 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if(!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.fillrect(10, 10, 100, 50);\t//绘制矩形,填充的默认颜色为黑色 ctx.strokerect(10, 70, 100, 50);\t//绘制矩形边框 ctx.clearrect(15, 15, 50, 25); } draw(); ![[assets/pasted image 20230525170332.png|300]]\n绘制路径 图形的基本元素是路径。\n路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。\n一个路径,甚至一个子路径,都是闭合的。\n使用路径绘制图形需要一些额外的步骤:\n创建路径起始点; 调用绘制方法去绘制出路径; 把路径封闭; 一旦路径生成,通过描边或填充路径区域来渲染图形。 下面是需要用到的方法:\nbeginpath() - 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径 moveto(x, y) - 把画笔移动到指定的坐标 (x, y),相当于设置路径的起始点坐标 lineto(x, y) - 添加一个新点,然后创建从该点到画面中最后指定点的线条 closepath() - 闭合路径之后,图形绘制命令又重新指向到上下文中 stroke() - 通过线条来绘制图形轮廓 fill() - 通过填充路径的内容区域生成实心的图形 1. 绘制线段\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath();\t// 新建一条 path ctx.moveto(50, 50);\t// 把画笔移动到指定的坐标 ctx.lineto(200, 50);\t// 绘制一条从当前位置到指定坐标 (200, 50) 的直线 // 闭合路径 // 会拉一条从当前点到 path 起始点的直线, // 如果当前点与起始点重合,则什么都不做 ctx.closepath(); ctx.stroke(); //绘制路径 } draw(); 2. 绘制三角形边框\n1 2 3 4 5 6 7 8 9 10 11 12 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath(); ctx.moveto(50, 50); ctx.lineto(200, 50); ctx.lineto(200, 200); ctx.closepath();\t//虽然我们只绘制了两条线段,但是 closepath 会自动闭合,仍然是一个 3 角形 ctx.stroke();\t//描边,stroke 不会自动 closepath() } draw(); ![[assets/pasted image 20230525170400.png|250]]\n3. 填充三角形\n1 2 3 4 5 6 7 8 9 10 11 12 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath(); ctx.moveto(50, 50); ctx.lineto(200, 50); ctx.lineto(200, 200); ctx.fill();\t//填充闭合区域,如果 path 没有闭合,则 fill() 会自动闭合路径。 } draw(); ![[assets/pasted image 20230525170432.png|205]]\n4. 绘制圆弧\n有两个方法可以绘制圆弧:\n1. arc(x, y, r, startangle, endangle, anticlockwise) - 以 (x, y) 为圆心,以 r 为半径,从 startangle 弧度开始到 endangle 弧度结束, - anticlosewise 是布尔值,true 表示逆时针,false 表示顺时针(默认是顺时针) * 这里的度数都是弧度,0 弧度是指 x 轴正向 * radias = (math.pi/180)*degrees // 角度转换成弧度 2. arcto(x1, y1, x2, y2, radius) - 根据给定的控制点和半径画一段圆弧,最后再以直线连接两个控制点 * 这个方法可以这样理解,绘制的弧形是由两条切线所决定 * - 第 1 条切线:起始点和控制点 1 决定的直线 * - 第 2 条切线:控制点 1 和控制点 2 决定的直线 * - 其实绘制的圆弧就是与这两条直线相切的圆弧 来看几个示例 🍩\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath(); ctx.arc(50, 50, 40, 0, math.pi / 2, false); ctx.stroke(); ctx.beginpath(); ctx.arc(150, 50, 40, 0, -math.pi / 2, true); ctx.closepath(); ctx.stroke(); ctx.beginpath(); ctx.arc(50, 150, 40, -math.pi / 2, math.pi / 2, false); ctx.fill(); ctx.beginpath(); ctx.arc(150, 150, 40, 0, math.pi, false); ctx.fill(); } draw(); ![[assets/pasted image 20230525170500.png|250]]\n再来看一下关于 arcto 的方法示例如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath(); ctx.moveto(50, 50); //参数 1、2:控制点 1 坐标 参数 3、4:控制点 2 坐标 参数 5:圆弧半径 ctx.arcto(200, 50, 200, 200, 100); ctx.lineto(200, 200) ctx.stroke(); ctx.beginpath(); ctx.rect(50, 50, 10, 10); ctx.rect(200, 50, 10, 10) ctx.rect(200, 200, 10, 10) ctx.fill() } draw(); ![[assets/pasted image 20230525170528.png|400]]\n5. 绘制贝塞尔曲线\n贝塞尔曲线 (bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。\n一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。\n贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具如 photoshop 等。在 flash4 中还没有完整的曲线工具,而在 flash5 里面已经提供出贝塞尔曲线工具。\n贝塞尔曲线于 1962,由法国工程师皮埃尔·贝塞尔(pierre bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由 paul de casteljau 于 1959 年运用 de casteljau 演算法开发,以稳定数值的方法求出贝兹曲线。\n一次贝塞尔曲线其实是一条直线\n![[assets/bazier1.gif|260]]\n二次贝塞尔曲线\n![[assets/b2.gif]] ![[assets/pasted image 20230525170848.png|193]]\n三次贝塞尔曲线\n![[assets/b3.gif]] ![[assets/pasted image 20230525170936.png|250]]\n好的,我们已初步了解了贝塞尔曲线是什么东东,那么如何绘制它呢?\n// 二次贝塞尔曲线 quadraticcurveto(cp1x, cp1y, x, y) - 参数 1 和 2:控制点坐标 - 参数 3 和 4:结束点坐标 // 三次贝塞尔曲线 beziercurveto(cp1x, cp1y, cp2x, cp2y, x, y) - 参数 1 和 2:控制点 1 的坐标 - 参数 3 和 4:控制点 2 的坐标 - 参数 5 和 6:结束点的坐标 像下面这样:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath(); ctx.moveto(10, 200); // 起始点 var cp1x = 40, cp1y = 100; // 控制点 var x = 200, y = 200; // 结束点 //绘制二次贝塞尔曲线 ctx.quadraticcurveto(cp1x, cp1y, x, y); ctx.stroke(); ctx.beginpath(); ctx.rect(10, 200, 10, 10); ctx.rect(cp1x, cp1y, 10, 10); ctx.rect(x, y, 10, 10); ctx.fill(); ctx.beginpath(); ctx.moveto(40, 200); // 起始点 var cp1x = 20, cp1y = 100; // 控制点 1 var cp2x = 100, cp2y = 120; // 控制点 2 var x = 200, y = 200; // 结束点 //绘制三次贝塞尔曲线 ctx.beziercurveto(cp1x, cp1y, cp2x, cp2y, x, y); ctx.stroke(); ctx.beginpath(); ctx.rect(40, 200, 10, 10); ctx.rect(cp1x, cp1y, 10, 10); ctx.rect(cp2x, cp2y, 10, 10); ctx.rect(x, y, 10, 10); ctx.fill(); } draw(); 好的,我们已初步了解了 贝塞尔曲线 是什么东东,那么如何绘制它呢?\n// 二次贝塞尔曲线 quadraticcurveto(cp1x, cp1y, x, y) - 参数 1 和 2:控制点坐标 - 参数 3 和 4:结束点坐标 // 三次贝塞尔曲线 beziercurveto(cp1x, cp1y, cp2x, cp2y, x, y) - 参数 1 和 2:控制点 1 的坐标 - 参数 3 和 4:控制点 2 的坐标 - 参数 5 和 6:结束点的坐标 像下面这样:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath(); ctx.moveto(10, 200); // 起始点 var cp1x = 40, cp1y = 100; // 控制点 var x = 200, y = 200; // 结束点 //绘制二次贝塞尔曲线 ctx.quadraticcurveto(cp1x, cp1y, x, y); ctx.stroke(); ctx.beginpath(); ctx.rect(10, 200, 10, 10); ctx.rect(cp1x, cp1y, 10, 10); ctx.rect(x, y, 10, 10); ctx.fill(); ctx.beginpath(); ctx.moveto(40, 200); // 起始点 var cp1x = 20, cp1y = 100; // 控制点 1 var cp2x = 100, cp2y = 120; // 控制点 2 var x = 200, y = 200; // 结束点 //绘制三次贝塞尔曲线 ctx.beziercurveto(cp1x, cp1y, cp2x, cp2y, x, y); ctx.stroke(); ctx.beginpath(); ctx.rect(40, 200, 10, 10); ctx.rect(cp1x, cp1y, 10, 10); ctx.rect(cp2x, cp2y, 10, 10); ctx.rect(x, y, 10, 10); ctx.fill(); } draw(); ![[assets/pasted image 20230525171235.png|300]] ![[assets/pasted image 20230525171244.png|300]]\n添加样式和颜色 在前面的绘制矩形章节中,只用到了默认的线条和颜色。\n如果想要给图形上色,有两个重要的属性可以做到:\n1. fillstyle = color // 设置图形的填充颜色 2. strokestyle = color // 设置图形轮廓的颜色 备注: - color 可以是表示 css 颜色值的字符串、渐变对象或者图案对象 - 默认情况下,线条和填充颜色都是黑色 - 一旦您设置了 strokestyle 或者 fillstyle 的值,那么这个新值就会成为新绘制的图形的默认值, - 如果你要给每个图形上不同的颜色,你需要重新设置 fillstyle 或 strokestyle 的值 1. fillstyle\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); for (var i = 0; i \u0026lt; 6; i++){ for (var j = 0; j \u0026lt; 6; j++){ ctx.fillstyle = \u0026#39;rgb(\u0026#39; + math.floor(255 - 42.5 * i) + \u0026#39;,\u0026#39; + math.floor(255 - 42.5 * j) + \u0026#39;,0)\u0026#39;; ctx.fillrect(j * 50, i * 50, 50, 50); } } } draw(); ![[assets/pasted image 20230525171310.png|450]]\n2. strokestyle\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); for (var i = 0; i \u0026lt; 6; i++){ for (var j = 0; j \u0026lt; 6; j++){ ctx.strokestyle = `rgb( ${randomint(0, 255)}, ${randomint(0, 255)} )`; ctx.strokerect(j * 50, i * 50, 40, 40); } } } draw(); /** * 返回随机的 [from, to] 之间的整数(包括 from,也包括 to) */ function randomint(from, to){ return parseint(math.random() * (to - from + 1) + from); } ![[assets/pasted image 20230525171331.png|186]]\n3. transparency (透明度)\nglobalalpha = transparencyvalue - 这个属性影响到 canvas 里所有图形的透明度 - 有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0 - globalalpha 属性在需要绘制大量拥有相同透明度的图形时候相当高效, - 不过,我认为使用 rgba() 设置透明度更加好一些 4. line style\n线宽 linewidth = value 关于 value : - 只能是正值,默认是 1.0 - 起始点和终点的连线为中心,上下各占线宽的一半 1 2 3 4 5 6 7 8 9 10 11 ctx.beginpath(); ctx.moveto(10, 10); ctx.lineto(100, 10); ctx.linewidth = 10; ctx.stroke(); ctx.beginpath(); ctx.moveto(110, 10); ctx.lineto(160, 10) ctx.linewidth = 20; ctx.stroke() ![[assets/pasted image 20230525171356.png|275]]\n线条末端样式 linecap = type 关于 type : - butt:线段末端以方形结束 - round:线段末端以圆形结束 - square:线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var linecaps = [\u0026#34;butt\u0026#34;, \u0026#34;round\u0026#34;, \u0026#34;square\u0026#34;]; for (var i = 0; i \u0026lt; 3; i++){ ctx.beginpath(); ctx.moveto(20 + 30 * i, 30); ctx.lineto(20 + 30 * i, 100); ctx.linewidth = 20; ctx.linecap = linecaps[i]; ctx.stroke(); } ctx.beginpath(); ctx.moveto(0, 30); ctx.lineto(300, 30); ctx.moveto(0, 100); ctx.lineto(300, 100) ctx.strokestyle = \u0026#34;red\u0026#34;; ctx.linewidth = 1; ctx.stroke(); ![[assets/pasted image 20230525171419.png|300]]\n同一个 path 内,设定线条与线条间接合处的样式 linejoin = type 关于 type : - round :通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。 圆角的半径是线段的宽度 - bevel :在相连部分的末端填充一个额外的以三角形为底的区域, 每个部分都有各自独立的矩形拐角 - miter(默认) :通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); var linejoin = [\u0026#39;round\u0026#39;, \u0026#39;bevel\u0026#39;, \u0026#39;miter\u0026#39;]; ctx.linewidth = 20; for (var i = 0; i \u0026lt; linejoin.length; i++){ ctx.linejoin = linejoin[i]; ctx.beginpath(); ctx.moveto(50, 50 + i * 50); ctx.lineto(100, 100 + i * 50); ctx.lineto(150, 50 + i * 50); ctx.lineto(200, 100 + i * 50); ctx.lineto(250, 50 + i * 50); ctx.stroke(); } } draw(); ![[assets/pasted image 20230525171441.png|300]]\n4. 虚线\n用 setlinedash 方法和 linedashoffset 属性来制定虚线样式,如下:\nsetlinedash 方法接受一个数组,来指定线段与间隙的交替; linedashoffset 属性设置起始偏移量。 1 2 3 4 5 6 7 8 9 10 11 12 function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.setlinedash([20, 5]); // [实线长度,间隙长度] ctx.linedashoffset = -0; ctx.strokerect(50, 50, 210, 210); } draw(); // getlinedash(): 返回一个包含当前虚线样式,长度为非负偶数的数组 ![[assets/pasted image 20230525171512.png|300]]\n绘制文本 canvas 提供了两种方法来渲染文本:\nfilltext(text, x, y [, maxwidth]) - 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的 stroketext(text, x, y [, maxwidth]) - 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的 1 2 3 4 5 6 7 8 9 10 var ctx; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.font = \u0026#34;100px sans-serif\u0026#34; ctx.filltext(\u0026#34;天若有情\u0026#34;, 10, 100); ctx.stroketext(\u0026#34;天若有情\u0026#34;, 10, 200) } draw(); ![[assets/pasted image 20230525171536.png|300]]\n我们都可以给文本添加哪些样式呢?\n1. 绘制文本的样式 font = value - 这个字符串使用和 css font 属性相同的语法 - 默认的字体是 10px sans-serif 2. 文本对齐选项 textalign = value - 可选的值包括:start, end, left, right or center - 默认值是 start。 3. 基线对齐选项 textbaseline = value - 可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom - 默认值是 alphabetic 4. 文本方向 direction = value - 可能的值包括:ltr, rtl, inherit - 默认值是 inherit 绘制图片 我们也可以在 canvas 上直接绘制图片。\n1. 由零开始创建图片\n1 2 3 4 5 6 7 8 9 10 11 12 var img = new image(); // 创建 img 元素 img.src = \u0026#39;myimage.png\u0026#39;; // 设置图片源地址 img.onload = function(){ // 参数 1:要绘制的 img // 参数 2、3:绘制的 img 在 canvas 中的坐标 ctx.drawimage(img, 0, 0) } // 关于 onload // - 考虑到图片是从网络加载,如果 drawimage 的时候图片还没有完全加载完成,则什么都不做,个别浏览器会抛异常, // - 所以我们应该保证在 img 绘制完成之后再 drawimage 2. 绘制 img 标签元素中的图片\nimg 可以 new 也可以来源于我们页面的 \u0026lt;img\u0026gt; 标签。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \u0026lt;img src=\u0026#34;./美女。jpg\u0026#34; alt=\u0026#34;\u0026#34; width=\u0026#34;300\u0026#34;\u0026gt;\u0026lt;br\u0026gt; \u0026lt;canvas id=\u0026#34;tutorial\u0026#34; width=\u0026#34;600\u0026#34; height=\u0026#34;400\u0026#34;\u0026gt;\u0026lt;/canvas\u0026gt; \u0026lt;script type=\u0026#34;text/javascript\u0026#34;\u0026gt; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); var img = document.queryselector(\u0026#34;img\u0026#34;); ctx.drawimage(img, 0, 0); } document.queryselector(\u0026#34;img\u0026#34;).onclick = function (){ draw(); } \u0026lt;/script\u0026gt; ![[assets/pasted image 20230525171704.png]]\n\u0026gt; 上图就是页面中的 \u0026lt;img\u0026gt; 标签\n![[assets/pasted image 20230525171724.png]]\n3. 缩放图片\ndrawimage() 也可以再添加两个参数:\ndrawimage(image, x, y, width, height) - width 和 height,这两个参数用来控制 当像 canvas 画入时应该缩放的大小 ![[assets/pasted image 20230525171741.png]]\n4. 切片\ndrawimage(image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight) - 第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用 其他 8 个参数: - 前 4 个是定义图像源的切片位置和大小 - 后 4 个则是定义切片的目标显示位置和大小 ![[assets/pasted image 20230525171756.png]]\n状态的保存和恢复 canvas 的状态就是当前画面应用的所有样式和变形的一个快照,状态的保存和恢复是绘制复杂图形时必不可少的操作。save() 和 restore() 方法是用来保存和恢复 canvas 状态的,都没有参数。\n1. save()\ncanvas 状态存储在栈中,每当 save() 方法被调用后,当前的状态就被推送到栈中保存。\n\u0026gt; canvas 都有哪些状态呢?\n一个绘画状态包括:\n当前应用的变形(即移动,旋转和缩放); strokestyle, fillstyle, globalalpha, linewidth, linecap, linejoin, miterlimit, shadowoffsetx, shadowoffsety, shadowblur, shadowcolor, globalcompositeoperation 的值; 当前的裁切路径(clipping path)。 可以调用任意多次 save 方法(类似数组的 push())。\n2. restore()\n每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复(类似数组的 pop())。\n上个例子吧 🍩\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var ctx; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.fillrect(0, 0, 150, 150); // 使用默认设置绘制一个矩形 ctx.save(); // 保存默认状态 ctx.fillstyle = \u0026#39;red\u0026#39; // 在原有配置基础上对颜色做改变 ctx.fillrect(15, 15, 120, 120); // 使用新的设置绘制一个矩形 ctx.save(); // 保存当前状态 ctx.fillstyle = \u0026#39;#fff\u0026#39; // 再次改变颜色配置 ctx.fillrect(30, 30, 90, 90); // 使用新的配置绘制一个矩形 ctx.restore(); // 重新加载之前的颜色状态 ctx.fillrect(45, 45, 60, 60); // 使用上一次的配置绘制一个矩形 ctx.restore(); // 加载默认颜色配置 ctx.fillrect(60, 60, 30, 30); // 使用加载的配置绘制一个矩形 } draw(); 变形 1. translate\ntranslate(x, y) - 用来移动 canvas 的原点到指定的位置 - 接受两个参数,x 是左右偏移量,y 是上下偏移量 ![[assets/pasted image 20230525171817.png]]\n\u0026gt; 注意:translate 移动的是 canvas 的坐标原点(坐标变换)。\n在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore() 方法比手动恢复原先的状态要简单得多。又如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。\n1 2 3 4 5 6 7 8 9 10 11 12 13 var ctx; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial1\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.save(); //保存坐原点平移之前的状态 ctx.translate(100, 100); ctx.strokerect(0, 0, 100, 100); ctx.restore(); //恢复到最初状态 ctx.translate(220, 220); ctx.fillrect(0, 0, 100, 100) } draw(); ![[assets/pasted image 20230525171833.png|300]]\n2. rotate\nrotate(angle) - 旋转坐标轴, 旋转的中心是坐标原点 - 这个方法只接受一个参数:旋转的角度 (angle),它是顺时针方向的,以弧度为单位的值 ![[assets/pasted image 20230525171855.png]]\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var ctx; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial1\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.fillstyle = \u0026#34;red\u0026#34;; ctx.save(); ctx.translate(100, 100); ctx.rotate(math.pi / 180 * 45); ctx.fillstyle = \u0026#34;blue\u0026#34;; ctx.fillrect(0, 0, 100, 100); ctx.restore(); ctx.save(); ctx.translate(0, 0); ctx.fillrect(0, 0, 50, 50) ctx.restore(); } draw(); ![[assets/pasted image 20230525171911.png|300]]\n3. scale\n我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。\nscale(x, y) - scale 方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值 - 值比 1.0 小表示缩 小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有 默认情况下,canvas 的 1 单位就是 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。\n4. transform(变形矩阵)\ntransform(a, b, c, d, e, f) - a (m11): horizontal scaling. - b (m12): horizontal skewing. - c (m21): vertical skewing. - d (m22): vertical scaling. - e (dx): horizontal moving. - f (dy): vertical moving. ![[assets/pasted image 20230525171934.png]]\n1 2 3 4 5 6 7 8 9 var ctx; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial1\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.transform(1, 1, 0, 1, 0, 0); ctx.fillrect(0, 0, 100, 100); } draw(); ![[assets/pasted image 20230525171948.png|300]]\n合成 在前面的所有例子中、,我们总是将一个图形画在另一个之上,对于其他更多的情况,仅仅这样是远远不够的。比如,对合成的图形来说,绘制顺序会有限制。不过,我们可以利用 globalcompositeoperation 属性来改变这种状况。\nglobalcompositeoperation = type 其中 type 值有 13 个,如下: - source-over (默认值), source-in, source-out, source-atop - destination-over, destination-in, destination-out, destination-atop - lighter, darken, lighten - xor, copy 下面我们分别来看一下这些值的表示。\n1. source-over\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 var ctx; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial1\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.fillstyle = \u0026#34;blue\u0026#34;; ctx.fillrect(0, 0, 200, 200); ctx.globalcompositeoperation = \u0026#34;source-over\u0026#34;; //全局合成操作 ctx.fillstyle = \u0026#34;red\u0026#34;; ctx.fillrect(100, 100, 200, 200); } draw(); 注:下面的展示中,蓝色是原有的,红色是新的。\n这是默认设置,新图像会覆盖在原有图像。\n![[assets/pasted image 20230525172011.png|300]]\n2. source-in\n仅仅会出现新图像与原来图像重叠的部分,其他区域都变成透明的。(包括其他的老图像区域也会透明)\n![[assets/pasted image 20230525172027.png|300]]\n3. source-out\n仅仅显示新图像与老图像没有重叠的部分,其余部分全部透明。(老图像也不显示)\n![[assets/pasted image 20230525172058.png|300]]\n4. source-atop\n新图像仅仅显示与老图像重叠区域。(老图像仍然可以显示)\n![[assets/pasted image 20230525172114.png|300]]\n5. destination-over\n新图像会在老图像的下面。\n![[assets/pasted image 20230525172131.png|300]]\n6. destination-in\n仅仅新老图像重叠部分的老图像被显示,其他区域全部透明。\n![[assets/pasted image 20230525172147.png|300]]\n7. destination-out\n仅仅老图像与新图像没有重叠的部分。 (注意显示的是老图像的部分区域)\n![[assets/pasted image 20230525172207.png|300]]\n8. destination-atop\n老图像仅仅仅仅显示重叠部分,新图像会显示在老图像的下面。\n![[assets/pasted image 20230525172228.png|300]]\n9. lighter\n新老图像都显示,但是重叠区域的颜色做加处理。\n![[assets/pasted image 20230525172247.png|300]]\n10. darken\n保留重叠部分最黑的像素。(每个颜色位进行比较,得到最小的)\nblue: #0000ff red: #ff0000 // 所以重叠部分的颜色:#000000 ![[assets/pasted image 20230525172305.png|300]]\n11. lighten\n保证重叠部分最量的像素。(每个颜色位进行比较,得到最大的)\nblue: #0000ff red: #ff0000 // 所以重叠部分的颜色:#ff00ff ![[assets/pasted image 20230525172325.png|300]]\n12. xor\n重叠部分会变成透明。\n![[assets/pasted image 20230525172348.png|300]]\n13. copy\n只有新图像会被保留,其余的全部被清除(边透明)。\n![[assets/pasted image 20230525172405.png|300]]\n裁剪路径 clip() - 把已经创建的路径转换成裁剪路径 裁剪路径的作用是遮罩。只显示裁剪路径内的区域,裁剪路径外的区域会被隐藏。\n注意:clip() 只能遮罩在这个方法调用之后绘制的图像,如果是 clip() 方法调用之前绘制的图像,则无法实现遮罩。\n![[assets/pasted image 20230525172425.png]]\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 var ctx; function draw(){ var canvas = document.getelementbyid(\u0026#39;tutorial1\u0026#39;); if (!canvas.getcontext) return; var ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); ctx.beginpath(); ctx.arc(20,20, 100, 0, math.pi * 2); ctx.clip(); ctx.fillstyle = \u0026#34;pink\u0026#34;; ctx.fillrect(20, 20, 100,100); } draw(); 动画 动画的基本步骤:\n清空 canvas 再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是 clearrect() 方法; 保存 canvas 状态 如果在绘制的过程中会更改 canvas 的状态(颜色、移动了坐标原点等), 又在绘制每一帧时都是原始状态的话,则最好保存下 canvas 的状态; 绘制动画图形这一步才是真正的绘制动画帧; 恢复 canvas 状态如果你前面保存了 canvas 状态,则应该在绘制完成一帧之后恢复 canvas 状态。 控制动画\n我们可用通过 canvas 的方法或者自定义的方法把图像绘制到 canvas 上。正常情况,我们能看到绘制的结果是在脚本执行结束之后。例如,我们不可能在一个 for 循环内部完成动画。\n也就是,为了执行动画,我们需要一些可以定时执行重绘的方法。\n一般用到下面三个方法:\nsetinterval() settimeout() requestanimationframe() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 let sun; let earth; let moon; let ctx; init(); function init() { sun = new image(); earth = new image(); moon = new image(); sun.src = \u0026#34;sun.png\u0026#34;; earth.src = \u0026#34;earth.png\u0026#34;; moon.src = \u0026#34;moon.png\u0026#34;; let canvas = document.queryselector(\u0026#34;#solar\u0026#34;); ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); sun.onload = function (){ draw() } } function draw() { ctx.clearrect(0, 0, 300, 300); // 清空所有内容 // 绘制太阳 ctx.drawimage(sun, 0, 0, 300, 300); ctx.save(); ctx.translate(150, 150); //绘制 earth 轨道 ctx.beginpath(); ctx.strokestyle = \u0026#34;rgba(255,255,0,0.5)\u0026#34;; ctx.arc(0, 0, 100, 0, 2 * math.pi) ctx.stroke() let time = new date(); //绘制地球 ctx.rotate( 2 * math.pi / 60 * time.getseconds() + 2 * math.pi / 60000 * time.getmilliseconds() ); ctx.translate(100, 0); ctx.drawimage(earth, -12, -12); //绘制月球轨道 ctx.beginpath(); ctx.strokestyle = \u0026#34;rgba(255,255,255,.3)\u0026#34;; ctx.arc(0, 0, 40, 0, 2 * math.pi); ctx.stroke(); //绘制月球 ctx.rotate( 2 * math.pi / 6 * time.getseconds() + 2 * math.pi / 6000 * time.getmilliseconds() ); ctx.translate(40, 0); ctx.drawimage(moon, -3.5, -3.5); ctx.restore(); requestanimationframe(draw); } ![[assets/4.gif]]\n:: emm… 不错,再来一个吧 🍪\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 // \u0026lt;canvas id=\u0026#34;solar\u0026#34; width=\u0026#34;300\u0026#34; height=\u0026#34;300\u0026#34;\u0026gt;\u0026lt;/canvas\u0026gt; init(); function init(){ let canvas = document.queryselector(\u0026#34;#solar\u0026#34;); let ctx = canvas.getcontext(\u0026#34;2d\u0026#34;); draw(ctx); } function draw(ctx){ requestanimationframe(function step(){ drawdial(ctx); //绘制表盘 drawallhands(ctx); //绘制时分秒针 requestanimationframe(step); }); } /*绘制时分秒针*/ function drawallhands(ctx){ let time = new date(); let s = time.getseconds(); let m = time.getminutes(); let h = time.gethours(); let pi = math.pi; let secondangle = pi / 180 * 6 * s; //计算出来 s 针的弧度 let minuteangle = pi / 180 * 6 * m + secondangle / 60; //计算出来分针的弧度 let hourangle = pi / 180 * 30 * h + minuteangle / 12; //计算出来时针的弧度 drawhand(hourangle, 60, 6, \u0026#34;red\u0026#34;, ctx); //绘制时针 drawhand(minuteangle, 106, 4, \u0026#34;green\u0026#34;, ctx); //绘制分针 drawhand(secondangle, 129, 2, \u0026#34;blue\u0026#34;, ctx); //绘制秒针 } /*绘制时针、或分针、或秒针 * 参数 1:要绘制的针的角度 * 参数 2:要绘制的针的长度 * 参数 3:要绘制的针的宽度 * 参数 4:要绘制的针的颜色 * 参数 4:ctx * */ function drawhand(angle, len, width, color, ctx){ ctx.save(); ctx.translate(150, 150); //把坐标轴的远点平移到原来的中心 ctx.rotate(-math.pi / 2 + angle); //旋转坐标轴。 x 轴就是针的角度 ctx.beginpath(); ctx.moveto(-4, 0); ctx.lineto(len, 0); // 沿着 x 轴绘制针 ctx.linewidth = width; ctx.strokestyle = color; ctx.linecap = \u0026#34;round\u0026#34;; ctx.stroke(); ctx.closepath(); ctx.restore(); } /*绘制表盘*/ function drawdial(ctx){ let pi = math.pi; ctx.clearrect(0, 0, 300, 300); //清除所有内容 ctx.save(); ctx.translate(150, 150); //一定坐标原点到原来的中心 ctx.beginpath(); ctx.arc(0, 0, 148, 0, 2 * pi); //绘制圆周 ctx.stroke(); ctx.closepath(); for (let i = 0; i \u0026lt; 60; i++){ //绘制刻度。 ctx.save(); ctx.rotate(-pi / 2 + i * pi / 30); //旋转坐标轴。坐标轴 x 的正方形从 向上开始算起 ctx.beginpath(); ctx.moveto(110, 0); ctx.lineto(140, 0); ctx.linewidth = i % 5 ? 2 : 4; ctx.strokestyle = i % 5 ? \u0026#34;blue\u0026#34; : \u0026#34;red\u0026#34;; ctx.stroke(); ctx.closepath(); ctx.restore(); } ctx.restore(); } ![[assets/5.gif]]\n结语 canvas 画布的功能还是挺强大的,go go go!\n","date":"2023-05-25","permalink":"https://aituyaa.com/canvas/","summary":"\u003cp\u003e\u003ccode\u003e\u0026gt; 近来要开发一个手写板程序,顺便来系统回顾一下 Canvas 这个东东……\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e🔔 本篇摘录自 \u003ca href=\"https://www.runoob.com/w3cnote/html5-canvas-intro.html\"\u003e《学习 HTML5 Canvas 这一篇文章就够了》\u003c/a\u003e ,写的真不错。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026lt;canvas\u0026gt;\u003c/code\u003e 是 HTML5 新增的一个可以使用脚本(通常为 JavaScript)在其中绘制图像的 HTML 元素。\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20230525170231.png|400]]\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; 上图就是使用 canvas 绘制出来的\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eCanvas 是由 HTML 代码配合高度和宽度属性而定义出的可绘制区域。JavaScript 代码可以访问该区域,类似于其他通用的二维 API,通过一套完整的绘图函数来动态生成图形。\u003c/p\u003e","title":"canvas"},]
[{"content":"好吧,试了,但真的没找到 “佚名” 是谁 😂 ,希望原作者能感受到我的原力感谢吧…… 不少网站都有转载该篇文章,以下是图片较清晰的几个页面链接。\nhttps://www.sohu.com/a/255397866_468626 https://www.jb51.net/hardware/cpu/611229.html https://www.jb51.net/hardware/cpu/610350.html 1 寸 = 33.3333 mm 集成电路 ic (integrated circuit )\n引言 cpu 是现代计算机的核心部件,又称为“微处理器”。对于 pc 而言,cpu 的规格与频率常常被用来作为衡量一台电脑性能强弱重要指标。\n许多对电脑知识略知一二的朋友大多会知道 cpu 里面最重要的东西就是晶体管了,提高 cpu 的速度,最重要的一点说白了就是如何在相同的 cpu 面积里面放进去更加多的晶体管,由于 cpu 实在太小,太精密,里面组成了数目相当多的晶体管,所以人手是绝对不可能完成的,只能够通过光刻工艺来进行加工的。\n这就是为什么一块 cpu 里面为什么可以数量如此之多的晶体管。晶体管其实就是一个双位的开关:即开和关。如果您回忆起基本计算的时代,那就是一台计算机需要进行工作的全部。两种选择,开和关,对于机器来说即 0 和 1。\n![[assets/pasted image 20230525160029.png|400]]\n那么如何制作一个 cpu 呢?\n大家可能只知道制作 ic 芯片的硅来源于沙子,但是为什么沙子做的 cpu 却卖那么贵?下面本文以 intel 电脑 cpu 作为例子,讲述沙子到 cpu 简要的生产工序流程,希望大家对 cpu 制作的过程有一个大体认识,其它 cpu 或者手机 cpu 制造原理也大抵相同。]\n一、硅圆片的制作 1. 硅的重要来源:沙子 作为半导体材料,使用得最多的就是硅元素,其在地球表面的元素中储量仅次于氧,含硅量在 27.72%,其主要表现形式就是沙子(主要成分为二氧化硅),沙子里面就含有相当量的硅。因此硅作为 ic 制作的原材料最合适不过,想想看地球上有几个浩瀚无垠的沙漠,来源既便宜又方便。\n![[assets/pasted image 20230525160101.png|400]]\n2. 硅熔炼、提纯 不过实际在 ic 产业中使用的硅纯度要求必须高达 99.999999999%。目前主要通过将二氧化硅与焦煤在 1600-1800℃中,将二氧化硅还原成纯度为 98% 的冶金级单质硅,紧接着使用氯化氢提纯出 99.99% 的多晶硅。虽然此时的硅纯度已经很高,但是其内部混乱的晶体结构并不适合半导体的制作,还需要经过进一步提纯、形成固定一致形态的单晶硅。\n![[assets/pasted image 20230525160116.png|400]]\n3. 制备单晶硅锭 单晶的意思是指原子在三维空间中呈现规则有序的排列结构,而单晶硅拥有“金刚石结构”,每个晶胞含有 8 个原子,其晶体结构十分稳定。\n![[assets/pasted image 20230525160136.png|250]] ![[assets/pasted image 20230525160146.png|400]]\n通常单晶硅锭都是采用直拉法制备,在仍是液体状态的硅中加入一个籽晶,提供晶体生长的中心,通过适当的温度控制,就开始慢慢将晶体向上提升并且逐渐增大拉速,上升同时以一定速度绕提升轴旋转,以便将硅锭控制在所需直径内。结束时,只要提升单晶硅炉温度,硅锭就会自动形成一个锥形尾部,制备就完成了,一次性产出的 ic 芯片更多。\n\u0026gt; 籽晶是具有和所需晶体相同晶向的小晶体,是生长单晶的种子,也叫晶种。\n![[assets/pasted image 20230525160244.png]]\n制备好的单晶硅锭直径约在 300mm 左右,重约 100kg。而目前全球范围内都在生产直径 12 寸的硅圆片,硅圆片尺寸越大,效益越高。\n4. 硅锭切片 将制备好的单晶硅锭一头一尾切削掉,并且对其直径修整至目标直径,同时使用金刚石锯把硅锭切割成一片片厚薄均匀的晶圆(1mm)。有时候为了定出硅圆片的晶体学取向,并适应 ic 制作过程中的装卸需要,会在硅锭边缘切割出“取向平面”或“缺口”标记。\n![[assets/pasted image 20230525160300.png|400]]\n5. 研磨硅圆片 切割后的晶圆其表面依然是不光滑的,需要经过仔细的研磨,减少切割时造成的表面凹凸不平,期间会用到特殊的化学液体清洗晶圆表面,最后进行抛光研磨处理,还可以在进行热处理,在硅圆片表面成为“无缺陷层”。一块块亮晶晶的硅圆片就这样被制作出来,装入特制固定盒中密封包装。\n![[assets/pasted image 20230525160318.png|400]]\n\u0026gt; 制作完成的硅圆片\n通常半导体 ic 厂商是不会自行生产这种晶圆,通常都是直接从硅圆片厂中直接采购回来进行后续生产。\n二、前工程 – 制作带有电路的芯片 6. 涂抹光刻胶 买回来的硅圆片经过检查无破损后即可投入生产线上,前期可能还有各种成膜工艺,然后就进入到涂抹光刻胶环节。微影光刻工艺是一种图形影印技术,也是集成电路制造工艺中一项关键工艺。首先将光刻胶(感光性树脂)滴在硅晶圆片上,通过高速旋转均匀涂抹成光刻胶薄膜,并施加以适当的温度固化光刻胶薄膜。\n\u0026gt; 光刻胶是一种对光线、温度、湿度十分敏感的材料,可以在光照后发生化学性质的改变,这是整个工艺的基础。\n![[assets/pasted image 20230525160352.png|400]]\n7. 紫外线曝光 就单项技术工艺来说,光刻工艺环节是最为复杂的,成本最为高昂的。因为光刻模板、透镜、光源共同决定了“印”在光刻胶上晶体管的尺寸大小。\n将涂好光刻胶的晶圆放入步进重复曝光机的曝光装置中进行掩模图形的“复制”。掩模中有预先设计好的电路图案,紫外线透过掩模经过特制透镜折射后,在光刻胶层上形成掩模中的电路图案。一般来说在晶圆上得到的电路图案是掩模上的图案 1/10、1/5、1/4,因此步进重复曝光机也称为“缩小投影曝光装置”。\n![[assets/pasted image 20230525160409.png|400]]\n一般来说,决定步进重复曝光机性能有两大要素:一个是光的波长,另一个是透镜的数值孔径。如果想要缩小晶圆上的晶体管尺寸,就需要寻找能合理使用的波长更短的光(euv,极紫外线)和数值孔径更大的透镜(受透镜材质影响,有极限值)。\n![[assets/pasted image 20230525160427.png]]\n\u0026gt; asml 公司 twinscan nxe:3300b\n8. 溶解部分光刻胶 对曝光后的晶圆进行显影处理。以正光刻胶为例,喷射强碱性显影液后,经紫外光照射的光刻胶会发生化学反应,在碱溶液作用下发生化学反应,溶解于显影液中,而未被照射到的光刻胶图形则会完整保留。显影完毕后,要对晶圆表面的进行冲洗,送入烘箱进行热处理,蒸发水分以及固化光刻胶。\n\u0026gt; 将光刻胶分为两类,一类是负性光刻胶,一类是正性光刻胶。在显影后,正性光刻胶未曝光的部分将被保留下来,而负性光刻胶则在显影后保留下来。也就是说,在曝光结束后,用显影液显影的是正性光刻胶的感光区、负性光刻胶的非感光区,将溶解在显影液中,此步骤完成后,光刻胶涂层上的图象便可显示出来。为提高分辨率,几乎每种光刻胶都配备了专用显影液,以保证显影效果。\n![[assets/pasted image 20230525160445.png|450]]\n\u0026gt; 负性光刻胶一般采用负性显影液。在无界面活性剂的情况下,正性光刻胶采用正性显影液显影。正性和负性显影液都是碱性溶剂,其主要区别是负性显影液中含有表面活性剂,而正性显影液中没有这种成分。\n![[assets/pasted image 20230525160503.png|400]]\n9. 蚀刻 将晶圆浸入内含蚀刻药剂的特制刻蚀槽内,可以溶解掉暴露出来的晶圆部分,而剩下的光刻胶保护着不需要蚀刻的部分。期间施加超声振动,加速去除晶圆表面附着的杂质,防止刻蚀产物在晶圆表面停留造成刻蚀不均匀。\n![[assets/pasted image 20230525160518.png|400]]\n10. 清除光刻胶 通过氧等离子体对光刻胶进行灰化处理,去除所有光刻胶。此时就可以完成第一层设计好的电路图案。\n![[assets/pasted image 20230525160540.png|400]]\n11. 重复第 6-8 步 由于现在的晶体管已经 3d finfet finfet 的原理与工艺 设计,不可能一次性就能制作出所需的图形,需要重复第 6-8 步进行处理,中间还会有各种成膜工艺(绝缘膜、金属膜)参与到其中,以获得最终的 3d 晶体管。\n![[assets/pasted image 20230525160555.png|400]]\n12. 离子注入 在特定的区域,有意识地导入特定杂质的过程称为“杂质扩散”。通过杂质扩散可以控制导电类型(p 结、n 结)pn 结原理简述之外,还可以用来控制杂质浓度以及分布。\n现在一般采用离子注入法进行杂质扩散,在离子注入机中,将需要掺杂的导电性杂质导入电弧室,通过放电使其离子化,经过电场加速后,将数十到数千 kev 能量的离子束由晶圆表面注入。离子注入完毕后的晶圆还需要经过热处理,一方面利用热扩散原理进一步将杂质“压入”硅中,另一方面恢复晶格完整性,活化杂质电气特性。\n![[assets/pasted image 20230525160614.png|400]]\n离子注入法具有加工温度低,可均匀、大面积注入杂质,易于控制等优点,因此成为超大规模集成电路中不可缺少的工艺。\n10. 再次清除光刻胶 完成离子注入后,可以清除掉选择性掺杂残留下来的光刻胶掩模。此时,单晶硅内部一小部分硅原子已经被替换成“杂质”元素,从而产生可自由电子或空穴。\n![[assets/pasted image 20230525160642.png]]\n\u0026gt; 左:硅原子结构; 中:掺杂砷,多出自由电子; 右:掺杂硼,形成电子空穴\n:: 真的是复杂……\n11. 绝缘层处理 此时晶体管雏形已经基本完成,利用气相沉积法,在硅晶圆表面全面地沉积一层氧化硅膜,形成绝缘层。同样利用光刻掩模技术在层间绝缘膜上开孔,以便引出导体电极。\n![[assets/pasted image 20230525160701.png|400]]\n12. 沉淀铜层 利用溅射沉积法,在晶圆整个表面上沉积布线用的铜层,继续使用光刻掩模技术对铜层进行雕刻,形成场效应管的源极、漏极、栅极。最后在整个晶圆表面沉积一层绝缘层以保护晶体管。\n![[assets/pasted image 20230525160723.png]]\n:: okay ,现在我们有晶体管了……\n13. 构建晶体管之间连接电路 经过漫长的工艺,数以十亿计的晶体管已经制作完成。剩下的就是如何将这些晶体管连接起来的问题了。同样是先形成一层铜层,然后光刻掩模、蚀刻开孔等精细操作,再沉积下一层铜层。…..,这样的工序反复进行多次,这要视乎芯片的晶体管规模、复制程度而定。最终形成极其复杂的多层连接电路网络。\n![[assets/pasted image 20230525160740.png]]\n由于现在 ic 包含各种精细化的元件以及庞大的互联电路,结构非常复杂,实际电路层数已经高达 30 层,表面各种凹凸不平越来越多,高低差异很大,因此开发出 cmp 化学机械抛光技术。每完成一层电路就进行 cmp 磨平。\n另外为了顺利完成多层 cu 立体化布线,开发出大马士革法新的布线方式,镀上阻挡金属层后,整体溅镀 cu 膜,再利用 cmp 将布线之外的 cu 和阻挡金属层去除干净,形成所需布线。\n![[assets/pasted image 20230525160753.png]]\n\u0026gt; 大马士革法多层布线\n芯片电路到此已经基本完成,其中经历几百道不同工艺加工,而且全部都是基于精细化操作,任何一个地方出错都会导致整片晶圆报废,在 100 多平方毫米的晶圆上制造出数十亿个晶体管,是人类有文明以来的所有智慧的结晶。\n三、后工程 – 从划片到成品销售 14. 晶圆级测试 前工程与后工程之间,夹着一个 good-chip/wafer 检测工程,简称 g/w 检测。目的在于检测每一块晶圆上制造的一个个芯片是否合格。通常会使用探针与 ic 的电极焊盘接触进行检测,传输预先编订的输入信号,检测 ic 输出端的信号是否正常,以此确认芯片是否合格。\n由于目前 ic 制造广泛采用冗余度设计,即便是“不合格”芯片,也可以采用冗余单元置换成合格品,只需要使用激光切断预先设计好的熔断器即可。当然,芯片有着无法挽回的严重问题,将会被标记上丢弃标签。\n![[assets/pasted image 20230525160829.png]]\n15. 晶圆切片、外观检查 ic 内核在晶圆上制作完成并通过检测后后,就进入了划片阶段。划片使用的划刀是粘附有金刚石颗粒的极薄的圆片刀,其厚度仅为人类头发的 1/3。将晶圆上的每一个 ic 芯片切划下来,形成一个内核 die。\n裂片完成后还会对芯片进行外观检查,一旦有破损和伤痕就会抛弃,前期 g/w 检查时发现的瑕疵品也将一并去除。\n![[assets/pasted image 20230525160843.png]]\n![[assets/pasted image 20230525160849.png]]\n\u0026gt; 未裂片的一个个 cpu 内核\n16. 装片 芯片进行检测完成后只能算是一个半成品,因为不能被消费者直接使用。还需要经过装片作业,将内核装配固定到基片电路上。装片作业全程由于计算机控制的自动固晶机进行精细化操作。\n![[assets/pasted image 20230525160913.png]]\n17. 封装 装片作业仅仅是完成了芯片的固定,还未实现电气的连接,因此还需要与封装基板上的触点结合。现在通常使用倒装片形式,即有触点的正面朝下,并预先用焊料形成凸点,使得凸点与相应的焊盘对准,通过热回流焊或超声压焊进行连接。\n封装也可以说是指安装半导体集成电路芯片用的外壳,它不仅起着安放、固定、密封、保护芯片,还可以增强导热性能的作用。目前像 intel 近些年都采用 lga 封装,在核心与封装基板上的触点连接后,在核心涂抹散热硅脂或者填充钎焊材料,最后封装上金属外壳,增大核心散热面积,保护芯片免受散热器直接挤压。\n至此,一颗完整的 cpu 处理器就诞生了。\n![[assets/pasted image 20230525160935.png]]\n18. 等级测试 cpu 制造完成后,还会进行一次全面的测试。测试出每一颗芯片的稳定频率、功耗、发热,如果发现芯片内部有硬件性缺陷,将会做硬件屏蔽措施,因此划分出不同等级类型 cpu,例如 core i7、i5、i3。\n19. 装箱零售 cpu 完成最终的等级划测试后,就会分箱进行包装,进入 oem、零售等渠道。\n![[assets/pasted image 20230525161005.png]]\n结语 现在进入了科技时代,极度依赖计算机科学与技术,其中的 cpu 又是各种计算机必不可少重要部件。暂且不论架构上的设计,仅仅在 cpu 的制作上就凝聚了全人类的智慧,基本上当今世界上最先进的工艺、生产技术、尖端机械全部都投入到了该产业中。因此半导体产业集知识密集型、资本密集型于一身的高端工业。\n![[assets/pasted image 20230525161022.png]]\n\u0026gt; cpu 制造全过程图解\n一条完整而最先进 cpu 生产线投资起码要数十亿人民币,而且其中占大头的是前工程里面的光刻机、掩膜板、成膜机器、扩散设备,占到总投资的 70%,这些都是世界上最精密的仪器,每一台都价值不菲。作为参照,cpu 工厂建设、辅助设备、超净间建设费用才占到 20%。\n不知道大家看到这里,觉得最低几百块就可以买到一颗汇聚人类智慧结晶的 cpu,还值不值呢?\n","date":"2023-05-25","permalink":"https://aituyaa.com/cpu-%E6%98%AF%E5%A6%82%E4%BD%95%E5%88%B6%E9%80%A0%E5%87%BA%E6%9D%A5%E7%9A%84/","summary":"\u003cp\u003e好吧,试了,但真的没找到 “佚名” 是谁 😂 ,希望原作者能感受到我的原力感谢吧…… 不少网站都有转载该篇文章,以下是图片较清晰的几个页面链接。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.sohu.com/a/255397866_468626\"\u003ehttps://www.sohu.com/a/255397866_468626\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.jb51.net/hardware/cpu/611229.html\"\u003ehttps://www.jb51.net/hardware/cpu/611229.html\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.jb51.net/hardware/cpu/610350.html\"\u003ehttps://www.jb51.net/hardware/cpu/610350.html\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003e1 寸 = 33.3333 mm\u003c/code\u003e \u003ccode\u003e集成电路 ic (integrated circuit )\u003c/code\u003e\u003c/p\u003e","title":"cpu 是如何制造出来的"},]
[{"content":" 转载自 https://www.jb51.net/hardware/cpu/610074.html\n我们知道,cpu 性能是主要由 cpu 构架、核心线程数量、主频、缓存等诸多因素共同决定,而“缓存”是很多网友容易忽视的一个地方。那么,cpu 缓存是什么?在电脑 cpu 中,一二三级缓存究竟谁对 cpu 性能影响最重要呢?\ncpu 缓存是什么 cpu 缓存(cache memory)是位于 cpu 与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。cpu 高速缓存的出现主要是为了解决 cpu 运算速度与内存读写速度不匹配的矛盾,因为 cpu 运算速度要比内存读写速度快很多,这样会使 cpu 花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内 cpu 即将访问的,当 cpu 调用大量数据时,就可先缓存中调用,从而加快读取速度。\n![[assets/pasted image 20230525155030.png|350]]\n\u0026gt; cpu 缓存\n缓存大小是 cpu 的重要指标之一,而且缓存的结构和大小对 cpu 速度的影响非常大,cpu 内缓存的运行频率极高,一般是和处理器同频运作,工作效率远远大于系统内存和硬盘。实际工作时,cpu 往往需要重复读取同样的数据块,而缓存容量的增大,可以大幅度提升 cpu 内部读取数据的命中率,而不用再到内存或者硬盘上寻找,以此提高系统性能。但是从 cpu 芯片面积和成本的因素来考虑,缓存都很小。\n![[assets/pasted image 20230525155050.png|500]]\n\u0026gt; cpu 缓存设计示意图\n按照数据读取顺序和与 cpu 结合的紧密程度,cpu 缓存可以分为一级缓存,二级缓存,如今主流 cpu 还有三级缓存,甚至有些 cpu 还有四级缓存。每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。\n为什么 cpu 会有 l1、l2、l3 这样的缓存设计?主要是因为现在的处理器太快了,而从内存中读取数据实在太慢(一个是因为内存本身速度不够,另一个是因为它离 cpu 太远了,总的来说需要让 cpu 等待几十甚至几百个时钟周期),这个时候为了保证 cpu 的速度,就需要延迟更小速度更快的内存提供帮助,而这就是缓存,如下图所示。\n![[assets/pasted image 20230525155119.png|450]]\n当 cpu 要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。一般来说,每级缓存的命中率大概都在 80%左右,也就是说全部数据量的 80%都可以在一级缓存中找到,只剩下 20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个 cpu 缓存架构中最为重要的部分。\ncpu 的一二三级缓存哪个最重要 一般来说,每级缓存的命中率大概都在 80%左右,也就是说全部数据量的 80%都可以在一级缓存中找到,只剩下 20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个 cpu 缓存架构中最为重要的部分。\n但是,现在 cpu 的一级缓存几乎都一样,容量都比较小,多为 64k,因此如今的 cpu 基本很少提一级缓存,主要是大家都一样,虽然最重要,但却不值得一提。\n二级缓存,对 cpu 是很重要的,不过很多朋友会发现,如今很多 intel 的 cpu 也都不怎么提二级缓存,只标注三级缓存。而 amd 的不少新 cpu 也多为标注三级缓存为主,二级缓存只有部分型号会标注,比如 amd 锐龙 5 2600x 提供 3m 三级缓存和 16m 三级缓存,r7 2700x 则也只有 16m 三级缓存。而 intel 酷睿 i3 8100 则只有 6m 三级缓存,高端的 i7 8700k 则只标注 12mb。\n![[assets/pasted image 20230525155206.png|550]]\n因此,在目前的新款 cpu 中,二级缓存的重要性在减弱,三级缓存则成为重点。\n现代 cpu 的高速缓存体系结构是非常复杂的,其中包括硬件预取和数据转发,以便能提供最佳的高速缓存命中机会,有些 cpu 甚至还加入了 l4 缓存。\n以上就是电脑 cpu 一二三级缓存的知识科普。对于普通电脑用户来说,只要知道 cpu 缓存是决定 cpu 性能的因素之一,普通用户对 cpu 缓存并不太敏感,游戏玩家更为在意的 cpu 核心数、主频等因素,而对于一些 3d 制图、视频渲染用户来说,比较考验 cpu 综合性能,这个时候大缓存会显得更优势。\n","date":"2023-05-25","permalink":"https://aituyaa.com/cpu-%E7%BC%93%E5%AD%98%E6%98%AF%E4%BB%80%E4%B9%88/","summary":"\u003cblockquote\u003e\n\u003cp\u003e转载自 \u003ca href=\"https://www.jb51.net/hardware/cpu/610074.html\"\u003ehttps://www.jb51.net/hardware/cpu/610074.html\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e我们知道,CPU 性能是主要由 CPU 构架、核心线程数量、主频、缓存等诸多因素共同决定,而“缓存”是很多网友容易忽视的一个地方。那么,CPU 缓存是什么?在电脑 CPU 中,一二三级缓存究竟谁对 CPU 性能影响最重要呢?\u003c/p\u003e","title":"cpu 缓存是什么"},]
[{"content":"css 中的 transform(变换)、transition(过渡) 和 animation(动画) ,简称 tta。\n:: css 什么的这种逻辑性略弱、规则性很强的领域,边用边深入就好,不常用的记了也会忘。\n简介 transform 其实是描述元素的静态属性,一旦写到 style 里面,将会直接显示作用(变换后的效果),本身不会呈现任何动画效果。\ntransition 和 animation 两者都能实现动画效果, transfrom 通常是配合这两者使用的。\n那么, transition 和 animation 又有什么不同呢?\n其主要区别在于:\n动画不需要事件触发,过渡需要; 过渡只有一组(两个:开始-结束) 关键帧,动画可以设置多个。 transform 变换 transform 属性应用于元素的 2d 或 3d 转换,这个属性允许你将元素旋转,缩放,平移,倾斜(拉伸)等。\ntransform: none|transform-functions; transform-functions 是什么呢?如下表:\n#+caption: 常用的变换函数值\n值 描述 … matrix(n,n,n,n,n,n) 定义 2d 变换,使用六个值的矩阵 translate(x,y) 定义 2d 平移变换 scale(x[,y]) 定义 2d 缩放变换 rotate(angle) 定义 2d 旋转变换,在参数中规定角度 skew(x-angle,y-angle) 定义沿着 x 和 y 轴的 2d 倾斜变换 … transition 过渡 transition 属性设置元素当过渡效果。\ntransition: property duration timing-function delay; #+caption: 过渡效果属性值\n值 描述 transition-property 指定 css 属性的名称 transition-duration 指定过渡持续时间 transition-timing-function 指定过渡函数 transition-delay 指定过渡延迟时间 💡 注:应始终指定 transition-duration 属性,否则持续时间为 0 , transition 不会有任何效果。\n1 2 3 4 5 6 7 8 9 10 .select { ... width: 100px; transition: width 2s; ... } .select:hover { width: 200px; } transition 的优点在于简单易用,但是它有几个在的局限:\n需要事件触发,所以没法在网页加载时自动发生; 是一次性的,不能重复发生,除非一再触发; 只能定义开始状态和结束状态,不能定义中间状态; 一条规则只能定义一个属性的变化,不能涉及多个属性。 :: 自古有矛必有盾!\ncss animation 就是为了解决这些问题而提出的。\nanimation 动画 1\ncss3 可以创建动画,它可以取代许多网页动画图像、flash 动画和 javascript 实现效果。\n那么,css3 动画是什么呢?\n动画是使元素从一种样式逐渐变化为另一种样式的效果,你可以改变任意多的样式次数,且应该用百分比来规定变化发生的时间,或用关键词 from (0%)和 to (100%)。\nanimation: name duration timing-function delay iteration-count direction fill-mode play-state; #+caption: 动画属性表\n值 描述 animation-name 指定关键帧动画的名称 animation-duration 指定动画播放所需时间 animation-timing-function 指定动画播放方式 animation delay 指定动画开始时间 animation-iteration-count 指定动画的播放次数,默认为 1 ,若为 infinite ,则无限次循环播放 animation-direction 指定动画的播放方向,默认为 normal ,若为 alternate ,则轮流反射播放 animation-fill-mode 规定当动画不播放时(完成或有延迟未开始播放时),要应用到元素的样式 … :: hmm\u0026hellip; 其实这种属性表是令人讨厌的,还是具体的例子看起来更加明了,另外,大多数情况下,你并不需要了解这么多属性。\n注:必须定义动画的名称和动画的持续时间,如果省略持续时间,动画将无法运行。\n还是来看个例子吧。\n1 2 3 4 5 6 7 8 9 10 11 12 .select { ... animation: myanimation 5s; ... } @keyframes myanimation { 0% {background: red;} 25% {background: yellow;} 50% {background: blue;} 100% {background: green;} } 看!关键在于 @keyframes 可以让你定义指定的帧! 你只需要定义的个动画名称和持续时间,然后用 @keyframes 去按百分比指定帧效果就可以了。\n:: css 的水很深,保持敬畏!\n","date":"2023-05-25","permalink":"https://aituyaa.com/css-%E4%B8%AD%E7%9A%84%E5%8A%A8%E6%95%88/","summary":"\u003cp\u003eCSS 中的 \u003ccode\u003etransform\u003c/code\u003e(变换)、\u003ccode\u003etransition\u003c/code\u003e(过渡) 和 \u003ccode\u003eanimation\u003c/code\u003e(动画) ,简称 TTA。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: CSS 什么的这种逻辑性略弱、规则性很强的领域,边用边深入就好,不常用的记了也会忘。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"css 中的动效"},]
[{"content":" 不可或缺的工具!!!\n本文旨在记录个人使用过程中遇到的相关 git 命令,非教程式的,详细学习请参阅 《pro git》 的中文文档。\n入门篇 ![[assets/pasted image 20230525223448.png]]\n安装设置 去 git 官网 下载对应版本的 git 安装即可,此处不再赘述。\n安装完成后,需要设置当前用户的名字和 email 地址:\n1 2 git config --global user.name \u0026#34;your name\u0026#34; git config --global user.email \u0026#34;email@example.com\u0026#34; 你总是可以通过以下方式获取帮助:\n1 2 3 git help \u0026lt;verb\u0026gt; git \u0026lt;verb\u0026gt; -h # --help 最常用,如 git config --help man git-\u0026lt;verb\u0026gt; 也许,你还想设置一下 rsa 密钥对:\nssh-keygen -t rsa -c \u0026#34;email@example.com\u0026#34; 基本操作 1 2 3 4 git init # 初始化仓库 # …… git add . # 添加工作区所有变更到暂存区 git commit -m \u0026#34;some commit log.\u0026#34; # 提交缓存区内变更到本地仓库 进阶篇 远程仓库 我们就以 站点仓库 为例。\n本地仓库和远程仓库之间的传输是通过 ssh 加密的,在使用远程仓库之前,我们需要先创建 ssh key 。\n1 ssh-keygen -t rsa -c \u0026#34;youremail@example.com\u0026#34; 在用户主目录下,会生成 .ssh 目录,包含 id_rsa (私钥)和 id_rsa.pub (公钥)等文件。\n……\n1 2 3 4 5 6 7 8 9 # 列出远程仓库(名称及地址) git remote -v # --verbose # 添加远程仓库 git remote add \u0026lt;name\u0026gt; \u0026lt;url\u0026gt; # 移除远程仓库 git remote remove \u0026lt;name\u0026gt; # 重命名远程仓库分支 git remote rename \u0026lt;old\u0026gt; \u0026lt;new\u0026gt; 关于远程仓库的其他操作,会分散在后续章节中,不在此处单独列出。\n分支 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 列出分支 git branch git branch -l # --list 列出本地分支 git branch -a # --all 列出远程、本地分支 # 创建分支 git checkout -b \u0026lt;branch\u0026gt; # 切换分支 git checkout \u0026lt;branch\u0026gt; # 删除分支 git branch -d \u0026lt;branch\u0026gt; # --delete 删除合并完全的分支 git branch -d \u0026lt;branch\u0026gt; # 强制删除分支(包含未合并完全) # 删除远程分支 git branch -d \u0026lt;remote-repo\u0026gt; \u0026lt;remote-branch\u0026gt; # --delete 删除远程仓库的指定分支 git push \u0026lt;remote-repo\u0026gt; :\u0026lt;remote-branch\u0026gt; # or 推送一个空分支到远程分支,其实就相当于删除远程分支 git push \u0026lt;remote-repo\u0026gt; --delete \u0026lt;remote-brach\u0026gt; # 重命名分支 git branch -m \u0026lt;branch\u0026gt; # --move # 合并分支 git merge \u0026lt;other-branch\u0026gt; \u0026lt;current-branch\u0026gt; 如果你得到以下错误消息,可能是因为其他人已经删除了这个分支。\n1 error: unable to push to unqualified destination: remotebranchname the destination refspec neither matches an existing ref on the remote nor begins with refs/, and we are unable to guess a prefix based on the source ref. error: failed to push some refs to \u0026#39;git@repository_name\u0026#39; 此时,使用 git fetch -p 同步分支列表就可以了,如此,你的分支列表里就不会显示已远程被删除的分支了。\n我们从一个远程仓库拉取分支,默认会在本地创建 master 分支,并关联到远程仓库的主分支。\n1 2 3 4 5 6 7 8 # 拉取远程仓库并关联到自定义本地分支 git clone -b \u0026lt;local-branch\u0026gt; \u0026lt;remote-repo-url\u0026gt; # 摘取远程仓库指定分支并关联自定义本地分支 git remote add origin \u0026lt;remote-repo-url\u0026gt; git fetch origin \u0026lt;remote-branch\u0026gt; git checkout -b \u0026lt;local-branch\u0026gt; origin/\u0026lt;remote-branch\u0026gt; # !关键 git pull origin \u0026lt;remote-branch\u0026gt; 强制推送本地分支到远程仓库分支(两者分支名称不同时),如下:\n1 2 3 # git remote add origin https://github.com/loveminimal/loveminimal.github.io.git # git push -f origin master:main # 本地分支 master ,远程分支 main git push -f origin master # 本地分支与远程分支名称相同,皆为 master !!永远不要试图一蹴而就,在使用的过程中慢慢补充完善即可!\ngit 设置本地与远程分支关联:\n1 2 3 4 5 # 查看本地与远程分支关联的情况 git branch -vv # 设置本地与远程分支关联 git branch --set-upstream-to=origin/\u0026lt;remote-branch\u0026gt; \u0026lt;local-branch\u0026gt; 标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # === 查看标签 === git tag # -l, --list 列出标签 git tag -l \u0026#34;v1.8.5*\u0026#34; git tag -n # 列出标签,并显示描述信息 git ls-remote --tags origin # 查看远程所有标签 # 列出标签并及其对应的提交日志简要信息 git tag -l --format=\u0026#39;%(refname:short) %(objectname:short) %(contents:subject)\u0026#39; # === 创建标签 === git tag v1.4-lw # 创建轻量标签 git tag -a v2.0 -m \u0026#39;描述信息\u0026#39; # 创建附注标签 git tag -a v1.2 9fceb02 # 后期对指定提交打标签 # === 发布标签 === git push origin v2.0.0 # 其中 origin 为远程仓库名 git push origin --tags # 将本地标签一次性提交到远程仓库 # === 删除标签 === git tag -d v1.4-lw # 删除标签 git push origin --delete [tagname] # 删除远程标签 git push origin :[tagname] # 删除远程标签 # === 检出标签 === # 拉取远程仓库指定标签到本地, # 并创建 master 分支跳转 git clone --branch v1.0.0 https://github.com/loveminimal/hugo-theme-virgo.git git switch -c master git checkout -b \u0026lt;branchname\u0026gt; \u0026lt;tagname\u0026gt; # 检出某个标签 # === 切换标签 === git checkout [tagname] # 切换到指定标签 git show [tagname] # 查看标签版本信息 git submodule 子模块 参考 https://zhuanlan.zhihu.com/p/87053283\n:: 果然,可以模块的地方,最终都会模块化。\n假定我们有两个项目: project-main (主项目)和 project-sub-1 (子模块项目)。\n我们可以使用 git submodule add \u0026lt;submodule_url\u0026gt; [\u0026lt;local_dir_name\u0026gt;] 命令在项目中创建一个子模块。\n💡 其中, \u0026lt;local_dir_name\u0026gt; 可选。\n上述命令执行之后,项目仓库会多出两个文件: .gitmodules (子模块的相关信息)和 project-sub-1 (子模块当前版本的版本号信息)。\n如何获取 submodule 呢?\n为了方便,我们不妨称主模块为 main ,子模块为 sub 。\n对于主项目 main 使用普通的 clone 操作并不会拉取到子模块 sub 中的实际代码(它是空的)。如果希望子模块代码也获取到,可以使用以下两种方式:\n在克隆 main 的时候带上参数 --recurse-submodules ,如此会递归地将项目中所有子模块的代码拉取;或, 在 main 中执行 git submodule init \u0026amp; git submodule update ,会根据主项目的配置信息,拉取更新子模块中的代码(or git submodule update --init --recursive)。 子模块内容更新了如何操作?\n对于子模块 sub 而言,它并不知道引用自己的主项目的存在,其自身是一个完整的 git 仓库,按照正常的 git 代码管理规范操作即可。\n主模块的处理,目前就记住,操作了 sub 后,再去操作 main 即可。\n删除子模块\n使用 git submodule deinit 命令卸载一个子模块。这个命令如果添加上参数 --force ,则子模块工作区内即使有本地的修改,也会被移除。\n其他 搭建 git 服务器并启用 hooks 许多教程使用的都是 root 账户,其实我们使用哪个账户都可以,比如,这里我们就用当前用户 jack 来搭建 git 服务。\n首先,把客户机的公钥 id_rsa.pub 文件,导入到 /home/jack/.ssh/authorized_keys 文件里(若没有,新建该文件即可),一行一个。\n然后,选定一个目录作为 git 仓库,比如 ~/.repo/site.git ,在 .repo 目录下,输入:\n1 git init --bare site.git git 就会创建一个裸仓库,它没有工作区,纯粹是为了共享。\n最后,在客户机上,就可以通过 git clone 命令克隆远程仓库了,如:\n1 2 3 # jack - 服务器主机用户名 # ovirgo.com - 服务器绑定的域名(可以是 ip) git clone jack@ovirgo.com:/home/jack/.repo/site.git 什么是 hooks 呢?\n执行 cd ~/.repo/site.git/hooks \u0026amp;\u0026amp; ls ,你会发现许多 hooks 文件示例,用于该仓库去响应客户机的某些指令时的一些操作,比如,我们在客户机向该仓库推送时,自动克隆部署该仓库。\n在 hooks 目录下,执行以下操作:\n1 2 3 # 新建 post-receive touch post-receive vim post-receive 编辑 post-receive 这个钩子:\n1 2 3 4 #!/bin/sh cd /home/jack rm -rf blog git clone .repo/site.git blog 是的,它是一个 shell 脚本,如此而已。\n综上,我们完成了服务器端的 git 搭建及自动化部署。\n实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 # === 全局化配置用户信息 git config --global user.name \u0026#34;jack\u0026#34; git config --global user.email \u0026#34;loveminimal@163.com\u0026#34; # === 生成 rsa 密钥对 ssh-keygen -t rsa -c \u0026#34;email@example.com\u0026#34; cd ~/.ssh cat id_rsa.pub # === 克隆远程仓库 # --- 常用 bash 别名 cd ~ git clone https://gitee.com/loveminimal/shell.git .shell cp -r .shell/* ./ source .bash_alias # --- 站点源码(私有仓库) cd ~/appdata/roaming git clone https://gitee.com/loveminimal/site.git # --- 递归拉取项目及其子模块 # git clone https://gitee.com/loveminimal/site.git --recurse-submodules # --- or cd ~/site # git submodule init \u0026amp; git submodule update git submodule update --init --recursive # --- 添加其它的远程仓库 cd ~/site/themes/virgo git remote add github https://github.com/loveminimal/hugo-theme-virgo.git cd ~/site/static/ship git remote add github https://github.com/loveminimal/ship.git cd ~/site/static/emojing git remote add github https://github.com/loveminimal/emojing.git cd ~/site/content/.obsidian/themes/virgo git remote add github https://github.com/loveminimal/obsidian-theme-virgo.git # --- 配置 vim cd ~ git clone https://gitee.com/loveminimal/vim.git .vim # --- or # scp -r aituyaa.com:/home/jack/.vim .vim # --- 下载浏览器插件 copy-link cd ~/local git clone https://gitee.com/loveminimal/copy-link.git 常见问题 git rebase 和 git merge 的区别 参考 git base 的用法\n假设你现在基于远程分支” origin “,创建一个叫” mywork “的分支。\n![[assets/pasted image 20241213115440.png|130]]\n现在我们在这个分支( mywork )做一些修改,然后生成两个提交( commit )。\n但是与此同时,有些人也在” origin “分支上做了一些修改并且做了提交了,这就意味着” origin “和” mywork “这两个分支各自”前进”了,它们之间”分叉”了。\n![[assets/pasted image 20241213115542.png|300]]\n在这里,你可以用” pull “命令把” origin “分支上的修改拉下来并且和你的修改合并; 结果看起来就像一个新的”合并的提交”(merge commit):\n![[assets/pasted image 20241213115619.png|390]]\n但是,如果你想让” mywork “分支历史看起来像没有经过任何合并一样,也可以用 git rebase,如下所示:\ngit checkout mywork git rebase origin 这些命令会把你的” mywork “分支里的每个提交(commit)取消掉,并且把它们临时 保存为补丁(patch)(这些补丁放到” .git/rebase “目录中),然后把” mywork “分支更新 到最新的” origin “分支,最后把保存的这些补丁应用到” mywork “分支上。\n![[assets/pasted image 20241213115803.png|460]]\n当 mywork 分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection), 这些被丢弃的提交就会删除。\n![[assets/pasted image 20241213115851.png|460]]\n现在我们可以看一下用合并(merge)和用 rebase 所产生的历史的区别:\n![[assets/pasted image 20241213115910.png|550]]\n在 rebase 的过程中,也许会出现冲突(conflict)。在这种情况,git会停止 rebase 并会让你去解决冲突;在解决完冲突后,用” git add “命令去更新这些内容的索引(index), 然后,你无需执行 git commit,只要执行 git rebase --continue git 会继续应用(apply)余下的补丁。\n在任何时候,可以用 --abort 参数来终止 rebase 的操作,并且” mywork “ 分支会回到 rebase 开始前的状态。\ngit 多平台换行符问题(lf/crlf) 详见 git多平台换行符问题(lf/crlf) - sampwood的one piece\ngit 提供了一个 core.autocrlf 的配置项,用于在提交和检出时自动转换换行符,该配置有三个可选项:\ntrue: 提交时转换为 lf,检出时转换为 crlf false: 默认值,提交检出均不转换 input: 提交时转换为 lf,检出时不转换 一种规范换行符的方式是这样的:\n# 使用 windows 系统的开发者设置 git config --global core.aurocrlf true 使用 linux/macos 的开发者设置 git config --global core.autocrlf input git 同时提供了另一个配置项 core.safecrlf,用于检查文件是否包含混合换行符,该配置也有三个可选项:\ntrue 默认值,禁止提交混合换行符的文本文件(git add 的时候会被拦截,提示异常) warn 提交混合换行符的文本文件的时候发出警告,但是不会阻止 git add 操作 false 不禁止提交混合换行符的文本文件(默认配置) 该配置用来防止错误的标准化(提交)与转换(检出)。嫌烦的话,可以:\ngit config --global core.safecrlf false ssh 通过 rsa 认证实现免密登录远程服务器 我们假设你在本机已经生成了 ssh-key\n1 2 3 4 5 cd ~/.ssh # 复制本机公钥到服务器 scp id_rsa.pub aituyaa.com:/home/jack # 追加公钥内容到服务器认证文件末尾 cat id_rsa.pub \u0026gt;\u0026gt; .ssh/authorized_keys 推送到 github 时出现错误相关 (10054 $ 443) openssl ssl_read: connection was reset, errno 10054 # or failed to connect to github.com port 443: timed out 解决办法:\n# 关闭验证 git config --global http.sslverify \u0026#34;false\u0026#34; # 设置代理, # 本机代理端口为 7890 git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy http://127.0.0.1:7890 上面的本机代理端口号为 7890 ,你需要查询自己的,在系统 设置 / 网络和 internet / 代理 层级,如下:\n![[assets/pasted image 20230908112808.png]]\n当然,这需要你科学上网才行。\n:: 真心的,不翻墙,github 这破玩意儿用起来是真的糟心……\n再次提交,就可以正常提交了,过程中可以会出现这个警告,无视它即可!\nwarning: ----------------- security warning ---------------- warning: | tls certificate verification has been disabled! | warning: --------------------------------------------------- warning: https connections may not be secure. see https://aka.ms/gcmcore-tlsverify for more information. 怎么恢复呢?很简单。\ngit config --global http.sslverify \u0026#34;true\u0026#34; # 设置代理, # 本机代理端口为 7890 git config --global --unset http.proxy git config --global --unset https.proxy 解决 git status 不能显示中文 ref\n默认设置下,中文文件名在工作区状态输出,中文名不能正确显示,而是显示为八进制的字符编码。\n终端输入: git config --global core.quotepath false 同时, git-bash 中 options/text 终端也要设置成中文和 utf-8 编码。 ![[assets/pasted image 20230529085346.png]]\n当然,你也可以通过修改配置文件来解决中文乱码,此处暂不讨论(因为一般上面这种办法就可以解决)。\ngit 解决每次拉取、提交代码时都需要输入用户名和密码 在家目录运行 git config --global credential.helper store ,然后在拉取时输入正确的用户名和密码,就可以成功记录下来。\n解决 git bash 不能登录 mysql 在使用 git bash 登录 mysql 时会卡死,怎么办呢?加上 winpty 就好了,如下:\nwinpty mysql -u root -p 设置 git-bash 为 emacs 默认 shell → https://blog.csdn.net/csfreebird/article/details/9719221\n1 2 3 4 (setq explicit-shell-file-name \u0026#34;c:/program files/git/bin/bash. exe\u0026#34;) (setq shell-file-name explicit-shell-file-name) (add-to-list \u0026#39;exec-path \u0026#34;c:/program files/git/bin\u0026#34;) ","date":"2023-05-25","permalink":"https://aituyaa.com/git/","summary":"\u003cblockquote\u003e\n\u003cp\u003e不可或缺的工具!!!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e本文旨在记录个人使用过程中遇到的相关 Git 命令,非教程式的,详细学习请参阅 \u003ca href=\"https://git-scm.com/book/zh/v2\"\u003e《Pro Git》 的中文文档\u003c/a\u003e。\u003c/p\u003e","title":"git"},]
[{"content":"拖放是一种常见的特性,即抓取对象以后拖到另一个位置。在 html5 中,拖放是标准的一部分,任何元素都能够拖放。\n*注:internet explorer 9+, firefox, opera, chrome, 和 safari 支持拖动。\n示例 1\n1 2 3 4 5 6 7 8 9 \u0026lt;div id=\u0026#34;drop-box\u0026#34; ondrop=\u0026#34;drop(event)\u0026#34; ondragover=\u0026#34;allowdrop(event)\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;img id=\u0026#34;drag-ele\u0026#34; src=\u0026#34;./images/drap.png\u0026#34; draggable=\u0026#34;true\u0026#34; ondragstart=\u0026#34;drag(event)\u0026#34; width=\u0026#34;200\u0026#34; height=\u0026#34;160\u0026#34; /\u0026gt; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function allowdrop(e) { e.preventdefault(); } function drag(e) { e.datatransfer.setdata(\u0026#39;text\u0026#39;, e.target.id); } function drop(e) { e.preventdefault(); var data = e.datatransfer.getdata(\u0026#39;text\u0026#39;); e.target.appendchild(document.getelementbyid(data)); } 它看上去也许有些复杂,不过我们可以分别研究拖放事件的不同部分。\n拖放 为了方便后文描述,我们称被拖元素为“小拖”,放置到的目标元素为“小盒” : )\n设置元素为可拖放 首先,为了使元素可拖动,需要把“小拖”的 draggable 属性设置为 true ,如下:\n1 \u0026lt;img draggable=\u0026#34;true\u0026#34; / \u0026gt; 设置拖动的触发事件 然后,规定当元素被拖动的时候,会发生什么。\n在上面的例子中, ondragstart 属性调用了一个函数 drag(event) ,它规定了被拖动的数据。\n如何设置这些数据呢? datatransfer.setdata() ,我们用它来设置被拖数据的数据类型和值,如下:\n1 2 3 function drag(e) { e.datatransfer.setdata(\u0026#39;text\u0026#39;, e.target.id); } 其中, text 是一个 domstring ,表示要添加到“小拖”中的拖动数据的类型,值是“小拖”的 id 。\nie 浏览器中 e.originalevent.datatransfer.setdata 可能会产生以下报错:\n意外地调用了方法或属性访问; 参数无效。 为什么?如何解决呢?原来 setdata(key, value) 的 key 在 ie 中只能设置为 text 或 text 等指定的可选类型(chrome 中无此限制), value 最好使用 json.stringfy() 序列化一下。\n:: ie 不死,天下不安。\n设置“小盒”可被放置 ondragover 事件规定在何处放置“小拖”的数据。\n默认情况下,无法将数据/元素放置到其他元素中。如果需要设置允许放置,必须阻止对于元素的默认处理方式,如通过调用 ondragover 事件的 event.preventdefault() 方法。\n1 event.preventdefault(); 进行放置 当放置“小拖”时,会发生 drop 事件。在上面的例子中, ondrop 属性调用了一个函数 drop(event) ,如下:\n1 2 3 4 5 6 7 8 9 10 11 function drop(e) { // 调用 `preventdefault()` 来避免浏览器对数据的默认处理 // `drop` 事件的默认行为是以链接形式打开 e.preventdefault(); // 通过 `datatransfer.getdata(\u0026#39;text\u0026#39;)` 方法获得“小拖”设置的数据 // 该方法将返回在 `setdata()` 方法中设置的相同类型的任何数据 var data = e.datatransfer.getdata(\u0026#39;text\u0026#39;); // 这里 `data` 就是前面设置的 `e.target.id` e.target.appendchild(document.getelementbyid(data)); } ","date":"2023-05-25","permalink":"https://aituyaa.com/js-%E4%B8%AD%E7%9A%84%E6%8B%96%E6%94%BE/","summary":"\u003cp\u003e拖放是一种常见的特性,即抓取对象以后拖到另一个位置。在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e*注:Internet Explorer 9+, Firefox, Opera, Chrome, 和 Safari 支持拖动。\u003c/code\u003e\u003c/p\u003e","title":"js 中的拖放"},]
[{"content":"在认识、学习 nginx 之前,我们先来看一下代理是什么,如果你已经非常熟悉代理,可以直接跳到第二个章节。\n关于代理 1\n注:该节原创发布于慕课网,作者:咚咚呛。\n要说清楚什么是反向代理,先来了解下最简单的 c/s (client-server)架构,即以单个节点作为后端 server 的 c/s 架构。\n单点服务的 c/s 架构 在普通的开发中,单点 server 的服务方式非常常见,比如使用 django 框架做后台服务的时候,就可以通过简单的 =run server= 命令起一个临时的服务,接着通过地址就可能访问到这个服务了。\n当然,对于 django 而言, run server 命令只是提供开发周期的调试使用的便捷工具,更一般的情况是通过 uwsgi 把这个 django 服务跑起来,以获取比 run server 更好的服务性能。\n![[assets/pasted image 20230525223650.png|375]]\n对于请求量非常少的服务,这样的部署不会有什么问题,但随着服务请求量越来越多,这样的部署架构就很有问题了。\n首先,从服务器的物理特性来看,这个服务器可能就不能支持这么高的请求量。 这种情况下,就迫使开发者去把服务迁移到一个 cpu 更强、内存更高,综合性能更好的服务器。\n在不考虑成本的条件下,总可以找到性能更好的服务器来支撑服务,然而,这是不现实的,地主家也没有余粮啊……\n其次,如果服务 server 单节点发生了故障,就必然会影响整个服务。 因为对于众多的 client 客户端都是连接的一个 server 服务端,一旦这个节点不可用了,势必会影响所有使用了客户端的用户。\n那么,如何解决这两个显而易见的问题呢?\n如果有一种横向拓展架构,使得服务可以支撑的请求量可以随着服务的横向拓展面增加就好了。\n基于代理的 c/s 架构 一种基于代理的可横向拓展的 c/s 架构,如下:\n![[assets/pasted image 20230525223712.png|500]]\n在这个部署的架构中,除了 server 节点外,还多出一个叫 proxy 的节点,这个节点的做什么的呢?\nproxy 节点,用于把接收的所有请求都转发到它后面的 server 节点中,然后等待 server 节点处理请求,再从 server 节点取回执行结果返回到 client 。所以,实际上 proxy 节点不处理任何的请求。\n下面让我们来看一下,这种架构是怎么解决了上述问题的,之后再思考 proxy 的这个角色。\n1. 服务器性能不足的问题\n![[assets/pasted image 20230525223726.png|500]]\n在这个架构里,假设 server 节点 s1 性能到达瓶颈了,不能处理更多请求了。我们可以添加 server 节点 s2 ,同时告诉 proxy 节点,让它把部分原来转发到 s1 节点的请求转发到 s2 节点。如此,通过服务分流减少压力的方法就可以解决原来 s1 节点性能不足的问题了。\n2. 单点服务器挂掉的问题\n![[assets/pasted image 20230525223742.png|525]]\n在这个架构里,假设 server 节点 s1 和 s2 能够提供的服务是一样的。如果 s1 挂掉了,proxy 节点存在,且能够察觉到 s1 挂掉了的话,就让 proxy 节点把原来要转发给 s1 节点的请求转发到 s2 进行处理,如此,通过服务冗余的方法就可以解决原来 s1 突然挂掉影响服务的问题了。\n那么,proxy 节点到底是什么呢?\nproxy 是什么 proxy ,就是代理。 如何理解呢?\n在第一个问题中,proxy 节点通过服务分流的方法来减少 s1 的压力。对于原来应该被 s1 服务的,却由于被 proxy 节点转发而被 s2 服务的 client 客户端而言,其实 client 并不知道它的请求是由 s1 处理的,还是由 s2 处理的,它只需要往 proxy 节点发送请求就好了,剩下的工作就由 proxy 节点去解决了。\n也就是说,proxy 节点相当于一个中介,“代理” client 去寻找实际的 server 节点去完成服务。\n这种模式在现实生活中也非常常见,在买房的时候,通常由房产中介帮助你完成和卖者之间的手续,而不需要你亲自去处理这些事情,你只需要处理自己与中介之间的手续就可以了,这里的中介,就和我们的 proxy 节点工作非常类似。\n同样的,在第二个问题中,client 并不需要感知 s1 是否还能正常工作,只需要把请求发送给 proxy 节点,由它去帮忙处理就可以了。\n由上可知,基于代理的可横向拓展的 c/s 架构中,proxy 节点就是我们的代理节点。\n正向代理和反向代理 :: 开始先做个总结:\n所谓“正向代理”其实应该称为“客户端代理”,而“反向代理”应该称为“服务端代理”。\n本质上来讲,代理都是存在于 client 和 server 之间,但是由于性质不同,可以分为正向代理和反向代理两种。那么,什么是反向代理?什么是正向代理呢?\n我们来看一个直观的例子,假设有 a、b 和 c 三人,他们之间存在借钱的关系。\n1. 正向代理\na 需要钱,a 知道 c 有很多钱,想向 c 借钱; 但是 a 和 c 有矛盾,于是 a 想到通过 b 去借 c 的钱; b 向 c 借到钱了 ,c 并不知道 a 的存在; b 再把借到的钱交给 a 。 ![[assets/pasted image 20230525223902.png|425]]\n在这个过程中,b 就充当了代理借钱的角色,并且是代替 a 去借钱的,这样就是正向代理。\n:: 比如国内日常使用的“科学上网” vpn 工具,就是正向代理了。比如,小明要访问 twitter ,但是访问不了,就委托 vpn 帮他访问,然后再把响应结果返回给他。 twitter 只知道是 vpn 访问它的,并不知道也不关心 vpn 把结果返回给谁。对于小明们来说,vpn 就是他们的访问代理,即正向代理。\n2. 反向代理\na 需要钱,c 有很多钱,a 不知道 c 有很多钱; a 找 b 借钱; b 知道 c 有很多钱; b 向 c 借钱,并把借到的钱给 a ,而没有使用自己的钱借给 a ; a 拿到钱后,以为钱是 b 的,a 不知道 c 的存在。 ![[assets/pasted image 20230525223922.png|525]]\n在这个过程中,b 也充当了代理代理借钱的角色,不过这次不是代替 a 去借的,而是用 c 的钱借给 a 的,换言之即是代替 c 将钱代给了 a ,这就是反向代理。\n:: 同样都是 a 借钱:\n正向代理是 a 委任 b 去借。b 去借钱,借钱的人只知道钱借给了 b ,并不知道、也不关心 b 拿来做什么用。\n反向代理是 a 去向 b 借钱,a 并不知道、也不关心 b 的钱是从哪儿来的。\n3. 两者区别\n1)服务对象不同:\n正向代理,代理的是客户端,也就是例子中的 a ,服务端并不知道实际发起请求的客户端; 反向代理,代理的是服务端,也就是例子中的 c ,客户端并不知道实际提供服务的服务端。 ![[assets/pasted image 20230525224054.png|600]]\nfigure:正向代理\n![[assets/pasted image 20230525224112.png|600]]\nfigure:反向代理\n2)安全模型不同:\n正向代理,允许客户端通过它访问任意网站并且隐藏客户端自身,因此必须采取安全措施以确保仅为授权的客户端提供服务; 反向代理,对外都是透明的,访问者并不知道自己访问的是代理,并不知道服务节点的存在,认为处理请求的就是代理节点。 总而言之,正向代理是从客户端的角度出发,服务于局域网用户,以访问非特定的服务,其中最典型的例子就是翻墙;反向代理正好与此相反,从服务端的角度出发,服务于所有用户,隐藏实际的服务节点,服务节点的架构对用户透明,以代理节点统一对外服务。\nnginx 那些事 2\n![[assets/pasted image 20230525224144.png|325]]\n了解了什么是代理,下面我们要进入正题认识下 nginx 了。\nnginx 是异步框架的网页服务器,也可以用作反向代理、负载平衡器和 http 缓存,该软件由 伊戈尔·塞索耶夫 创建并于 2004 年站首次公开发布,官方测试 nginx 能够支支撑五万并发链接,并且 cpu 、内存等资源消耗非常低,运行非常稳定。\nnginx 这么强劲,都有些什么应用场景呢?如下:\nhttp 服务器。nginx 可以独立提供 http 服务,用做网页静态服务器; 虚拟主机。nginx 可以实现在一台服务器虚拟出多个网站,如个人网站使用的虚拟主机; 反向代理、负载均衡。nginx 可以实现请求分流,避免单点服务器宕机影响服务。 安装 → 具体安装过程可以参考该链接。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 准备工作 yum install gcc-c++ # 安装需要的 gcc 环境 yum install -y pcre pcre-devel # 安装 perl 兼容的正则表达式库 yum install -y zlib zlib-devel # nginx 使用 zlib 对 http 包进行 gzip yum install -y openssl openssl-devel # 支持 https # 下载安装 nginx - http://nginx.org # 可以修改为你要的版本,目前最前为 1.19.3 wget http://nginx.org/download/nginx-1.8.0.tar.gz tar zxvf nginx-1.8.0.tar.gz cd nginx-1.8.0 # 使用 configure 命令创建 makefile 文件 ./configure --prefix=/usr/local/nginx # 指向安装目录 make \u0026amp;\u0026amp; make install 1 2 3 4 # nginx 的启动和访问: cd nginx # 进入 nginx 安装目录 cd sbin \u0026amp;\u0026amp; ./nginx # 进入 nginx 的执行目录并启动 ./nginx ps aux | grep nginx # 启动后台查看进程 1 2 3 4 5 # nginx 的一些常用操作: ./nginx -s stop # 停止 nginx ./nginx -s quit # 强制停止 nginx ./nginx -t # 校验 nginx 配置正确与否 ./nginx -s reload # 重启 nginx,用于修改配置文件后更新 nginx 状态 静态网站部署 将我们的网页内容(假设为 index 文件夹 )上传到服务器的 /usr/local/nginx 下,并更新 /usr/local/nginx/conf 下的 nginx.conf 配置文件,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 worker_processes 1; ... http { ... server { listen 80; server_name localhost; location / { root index; # 目录名称 index index.html index.htm; # 文件名称 } ... } } 重启 nginx 即可,打开服务器的 ip 即可访问。\n配置虚拟主机 (1)上传静态网站:\n将 index 目录上传至 /usr/local/nginx/index 下; 将 regist 目录上传至 /usr/local/nginx/regist 下。 (2)修改 nginx 的配置文件:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 配置 index 访问资源 server { listen 80; # 监听端口 server_name localhost; # 域名或 ip location / { # 访问路径配置 root index; index index.html index.htm; } ... } # 配置 regist 访问资源 server { listen 81; server_name localhost; location / { root regist; index regist.html; } } (3)重启 nginx ,可以通过 80,81 访问不同的资源。\n综上,就实现了一台虚拟主机部署两个项目,一个 nginx 虚拟出来了两个主机,实现了端口号配置虚拟主机。\n当然,还可以通过域名配置虚拟主机 ,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 配置 index 访问资源 server { listen 80; # 监听端口 server_name www.travel.com; # 域名或 ip location / { # 访问路径配置 root index; index index.html index.htm; } ... } # 配置 regist 访问资源 server { listen 80; # 端口号相同 server_name www.regist.com; # 域名不同,以此进行区分 location / { root regist; index regist.html; } } 反向代理 这里我们将实现好的案例(war 包)部署到服务器的 tomcat 中的根目录下:\n为了操作方便(不需要输入对应的项目名称访问)将项目命名为 root.war ; 将服务器 /usr/local/tomcat/apache-tomcat-7.0.57/webapps 下的内容删除,将打好的包导入即可。 此时,输入相应 ip:port 如 192.168.245.129:8080 即可正常访问。\n接下来,我们看看如何用 nginx 配置反向代理:\n![[assets/pasted image 20230525224253.png]]\n重新启动 nginx ,输入 www.travel.com 即可实现访问。\n负载均衡 什么是负载均衡呢?\n负载均衡(i.e. load balance),就是分摊到多个操作单元上进行执行,例如 web 服务器、ftp 服务器、企业关键应用服务器等,从而共同完成工作任务。\n![[assets/pasted image 20230525224305.png]]\n实际应该部署到不同的服务器上,这里为了演示方便,就在同一台服务器配置三个 tomcat 。\n将刚才存放工程的 tomcat 复制三份,修改端口分别为 8080,8081,8082 ,修改 server.xml 的端口,分别启动这三个 tomcat 服务。\n配置负载均衡:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 worker_process 1; ... http { ... # 配置要代理的网址 # 默认如下配置是平权的,随机选概率相同 1:1:1 # 也可以通过加权,如: # server 192.168.245.129:8080 weight=2; # 此时的权重比为 2:1:1 upstream tomcat-travel { server 192.168.245.129:8080; server 192.168.245.129:8081; server 192.168.245.129:8082; } # 配置 index 访问资源 server { listen 80; server_name www.travel.com; location / { # root index; # 对应要代理的网址 proxy_pass http://tomcat-travel; index index.html index.htm; } ... } } 如 nginx 这种配置性的软件,用时查询即可,重在理解它的原理。\nproxypass 3 4\n配置语法:\nsyntax:\tproxy_pass url;\rdefault: —\rcontext: location, if in location, limit_except 其中,url 用于设置代理服务器的协议和地址,以及可选的 uri ,一般表现形式为:\nprotocol://ip:port[uri] protocal://domain[uri] 由于 url 末尾 是否存在 uri 的处理逻辑不同,我们下面来着重分析一下。\n1. url 末尾存在 uri\n注意 / 也算是 uri\n处理逻辑:代理请求时,会先将 请求的 uri 中 localtion 匹配的部分 替换成 proxy_pass 指定的 uri ,再将最终的 uri 拼接到代理地址 ,才是最终访问的 url。\n例如,有如下配置:\n1 2 3 localtion /proxy { proxy_pass http://127.0.0.1:8099/svr1; # uri 为 \u0026#39;/svr1\u0026#39; } 我们发起如下请求: http://localhost:8088/proxy/index.html ,详细解析如下表。\n部分 说明 请求的 uri /proxy/index.html location 匹配的部分 /proxy proxy_pass 指定的 uri /svr1 最终的 uri /svr1/index.html 代理地址 http://127.0.0.1:8099 最终访问的 url http://127.0.0.1:8099/svr1/index.html 即:\n原本访问路径为: http://localhost:8088/proxy/index.html , 实际请求路径为: http://127.0.0.1:8099/svr1/index.html 。 2. url 末尾不存在 uri\n处理逻辑:代理请求时,直接将 请求的 uri 拼接到 代理地址 ,就是 最终访问的 url 。\n例如,有如下配置:\n1 2 3 location /proxy2 { proxy_pass http://127.0.0.1:8099; # 无 uri } 我们发起如下请求: http://localhost:8088/proxy2/index.html ,详细解析如下表。\n部分 说明 请求的 uri proxy2/index.html 代理地址 http://127.0.0.1:8099 最终访问的 url http://127.0.0.1:8099/proxy2/index.html 即:\n原本访问路径为 http://localhost:8088/proxy2/index.html , 实际请求路径为 http://127.0.0.1:8099/proxy2/index.html 。 看 proxy_pass 的使用的方法本质上就是就这么简单,官网文档这部分文档内容也不是很多,有时候事物往往是由于我们理解不当,而被过分的复杂化了。\nupstream 这个方法主要是方便把单独的代理存储为独立的文件进行区分和设置,可以更好地进行多个服务器的代理设置,包括每个子服务器的权重,以及分发的方式等。\n配置范例:\n1 2 3 4 5 6 7 8 9 10 11 12 upstream backend { # backend 为当前配置的名称,与 proxy_pass 参数对应 ip_hash; # 表示负载均衡配置 server backend1.example.com weight=5; server backend2.example.com:8080; server unix:/tmp/backend3; } server { location / { proxy_pass http://backend; } } 关于 ip_hash ,它表示负载均衡的配置,它的设置可以使每个请求按访问的 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器,可以解决 session 的问题。备选为 url_hash ,表示按用户的访问 url 来进行分配,这样访问相同的 url 时会指向同一台服务器,主要用于下载站点,可以节约带宽资源。\n其他 nginx 一个 server 配置多个 location 1\nnginx 作为代理服务器,可以配置多个 location,通过访问不同路径来访问不同目录。比如, location / 用于访问官网首页, location /docs 用于访问帮助文档。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 server { listen\t8088; server_name\tlocalhost; location / { root /path/to/site/index; index\tindex.html index.htm; } location /docs { alias /path/to/docs;\t# 注意这里是 alias index\tindex.html index.htm; } …… } alias 和 root 有什么区别呢?\nroot 和 alias 都可以定义在 location 模块中,都是用来指定请求资源的真实路径,比如:\n1 2 3 location /abc/ { root /data/www; } 请求 http://ip:port/abc/123.png 时,在服务器里面对应的真正的资源是 /data/www/abc/123.png,也就是说, root 真实路径是 root 指定的值加上 location 指定的值。\n而 alias 指定的路径是 location 的别名,不管 location 的值怎么写,资源的真实路径都是 alias 指定的路径,比如:\n1 2 3 location /abc/ { alias /data/www; } 请求 http://ip:port/abc/123.png 时,在服务器里对应的真正的资源是 /data/www/123.png,而不再包含 location 指定的值了。\n注意:\n在一个 location 中, alias 可以存在多个,但是 root 只能有一个; alias 只能存在与 location 中,但是 root 可以用在 server、http 和 location 中。 ","date":"2023-05-25","permalink":"https://aituyaa.com/nginx/","summary":"\u003cp\u003e在认识、学习 Nginx 之前,我们先来看一下代理是什么,如果你已经非常熟悉代理,可以直接跳到第二个章节。\u003c/p\u003e","title":"nginx"},]
[{"content":"博客系统迁移好多次了,多少次?详见《[[一场疲惫的主题制作之旅]]》。域名啊、博客文章的层级结构啊,都难免存在差异。这就导致了一个问题,之前在别的网站发布的一些页面链接就成了“死链”。虽然,可以简单地重写向到 404 ,体验却不好,最好是可以重定向到变化后的页面。\n周末无聊,简单实现了一下,做个记录。\n\u0026gt; 2024-02-29 14:17 新情况 - 关于 博客多个域名使用一级域名(如 example.com)还是 www 二级域名 (如 www.example.com )及其使用 http 和 https 访问的相关问题。详见 [[#域名重写向问题]] 。\n如何重定向 之前的链接都是 https://ovirgo.com/xxx-yyy-zzz.html 格式的链接,现在要使之重定向到 https://ovirgo.com/posts/xxx-yyy-zzz 到这样的链接地址。\n修改服务器端 nginx 的相关配置,比如 /etc/nginx/conf.d/xxx.conf (当前主机为 ubuntu server ,其他发型版可能有区别)。\nlocation / {\r# ...\rif (!-e $request_filename) {\rrewrite ((\\w*=)*\\w*)\\.html /posts/$1 last;\rbreak;\r}\r# ...\r} $1 是什么?就是前面正则表达式的匹配的第一个捕获分组。\n当请求资源不存在时,重定向到新的路径。对,就是 rewrite 了。\n如果,你还不了解,或是很长时间没有使用正则表达式了,可以阅读一下另一篇博文 – 正则表达式,附一个很不错的 正则表达式测试网站 。\nnginx rewrite 访问重写 rewrite 是 nginx http 请求处理过程中的一个重要功能,它是以模块的形式存在于代码中的,其功能是对用户请求的 uri 进行 pcre 正则重写,然后返回 30x 重定向跳转或按条件执行相关配置。\nrewrite 模块内置了类似脚本语言的 set、if、break、return 配置指令,通过这些指令,用户可以在 http 请求处理过程中对 uri 进行更灵活的操作控制。rewrite 模块提供的指令可以分为两类:\n标准配置指令(只是对指定的操作进行相应的操作控制); 脚本指令(可以在 http 指令域内以类似脚本编程的形式进行编写)。 标准配置指令 这里我们只介绍与本次修改相关的配置指令。\n名称 rewrite 指令 指令 rewrite 作用域 server, location 指令说明 对用户的 uri 用正则表达式的方式进行重写,并跳转到新的 uri 其语法格式如下:\nrewrite regex replacement [flag];\r# - regex 是 pcre 语法格式的正则表达式\r# - replacement 是重写 uri 的改写规则\r# - flag 是执行该条重写指令后的操作控制符 其中,对于 replacement 来说,当改写规则以 http://、https:// 或 $scheme 开头时,nginx 重写该语句后将停止执行后续任务,并将改写后的 uri 跳转返回客户端。\n对于,flag ,作为操作符有如下 4 种:\nlast 执行完当前重写规则跳转到新的 uri 后继续执行后续操作; break 执行完当前重写规则跳转到新的 uri 后不再执行后续操作,不影响用户浏览器 uri 显示; redirect 返回响应码 302 的临时重定向,返回内容是重定向 uri 的内容,但浏览器网址仍为请求时的 uri ; permanent 返回响应状态码 301 的永久重定向,返回内容是重写向 uri 的内容,浏览器网址变为重定向的 uri 。 脚本指令 这一块目前没有涉及,用的到的时候再说吧。(但取所需 😂)\n……\n域名重写向问题 最近在申请新的域名备案的时候,审核过程中遇到了这样一个问题 - 审核人员告诉我之前图案的域名无法访问(一串英文 blabloa……),但是我这里怎么访问都是正常的。直到后面,我恍然大悟,他大概率看到的是下面这个页面。\n![[assets/snipaste_2024-02-29_12-41-45.png|500]]\n为什么会出现这个问题呢?\n日常我访问站点的时候,都是直接使用一级域名(如 aituyaa.com ),审核人员访问时使用的是二级 www 域名 (如 www.aituyaa.com ) 且他使用的是 http 协议,就出现了上述情况。\n静态博客生成的过程中,配置文件中一般也需要提供一个 baseurl 之类的属性值。以 hugo 为例,如果你设置的为 aituyaa.com ,在最后生成的静态页面中引用地址的域名都是这个,当使用 www.aituyaa.com 访问的时候某些资源就会出现跨域问题。这时候,就需要我们在 nginx 配置中作一些重定向处理。\n强制使用 https 如果站点申请了 ssl 证书,想要强制使用 https 访问站点,就需要监听 80 端口,并重定向到 443 。\n![[assets/pasted image 20240229143527.png]]\n其中, server_name 后便是你要指向站点服务的域名。\n强制将 www 二级域名定向为一级域名 如上面所描述的问题,为了保证站点的正确渲染,我们有时候需要将 www 二级域名重定向为一级域名来访问 。\n![[assets/pasted image 20240229144258.png|370]]\n只需要在监听 443 端口的那个 server 中,添加如上的判断。如此,当访问的域名为 www.walkssi.com 之类的二级域名时,就会 重定向到 walkssi.com 一级域名。\nfaq 如果,你要实验重写规则,你可能会遇到和我一样的问题,比如说,当你把 flag 为 permanent 时,你可能重定向了某个 uri,当你实验一次之后,发现后续再修改也不起作用了,咦,困扰。\n![[assets/pasted image 20230525222946.png|500]]\n设置了 permanent ,被永久重定向了,你需要先清理浏览器缓存,再进行后续的操作。\n参考链接 https://www.w3schools.cn/nginx/nginx_command_rewrite.asp 如何强制 nginx 将全站转向 www 和 https-腾讯云开发者社区-腾讯云 ","date":"2023-05-25","permalink":"https://aituyaa.com/nginx-%E9%87%8D%E5%AE%9A%E5%90%91/","summary":"\u003cp\u003e博客系统迁移好多次了,多少次?详见《[[一场疲惫的主题制作之旅]]》。域名啊、博客文章的层级结构啊,都难免存在差异。这就导致了一个问题,之前在别的网站发布的一些页面链接就成了“死链”。虽然,可以简单地重写向到 404 ,体验却不好,最好是可以重定向到变化后的页面。\u003c/p\u003e\n\u003cp\u003e周末无聊,简单实现了一下,做个记录。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; 2024-02-29 14:17\u003c/code\u003e 新情况 - 关于 博客多个域名使用一级域名(如 \u003ccode\u003eexample.com\u003c/code\u003e)还是 www 二级域名 (如 \u003ccode\u003ewww.example.com\u003c/code\u003e )及其使用 HTTP 和 HTTPs 访问的相关问题。详见 [[#域名重写向问题]] 。\u003c/p\u003e","title":"nginx 重定向"},]
[{"content":"📔 快速入门 – react 中文文档\n具体安装及引入细节,请直接参考官方文档。\n:: react 的出新文档了,内容的组织结构也有所变化,整体来说更加突出函数式组件的作用了,毕竟有了 hooks 嘛~ 来一个新的章节来摘录一下,准备在新的项目中应用 react ,反正用什么我说了算,哈哈~ ↗️ 「 [[#新文档]] 」\nreact 是一个用于构建用户界面的 javascript 库,你可以用它给简单的 html 页面增加一点交互,也可以开始一个完全由 react 驱动的复杂应用。\n\u0026gt; 对的,它只是一个 ui 库而已 !!!\n在新的项目中,强烈建议直接使用 vite 来创建你的应用 - npx create vite ,如下:\nps d:\\wp\u0026gt; npm create vite \u0026gt; npx \u0026gt; create-vite √ project name: ... my-react-app √ select a framework: » react √ select a variant: » javascript + swc ... 当然,你也可以继续使用基于 webpack 的打包方式,但随着你的项目越来越大,其编译速度会越来越慢。\n简单的就不说了,直接来看一下 react 团队推荐的创建 spa (单页面,single page app)的工具链 - create react app 。\n要创建项目,请执行:\nnpx create-react-app my-app cd my-app npm start create react app 不会处理后端逻辑或操纵数据库;它只是创建一个前端构建流水线(build pipeline),所以你可以使用它来配合任何你想使用的后端。它在内部使用 babel 和 webpack,但你无需了解它们的任何细节。当然,关于它,你肯定想了解更多,请参考 用户指南 。\n如果你倾向于从头开始打造你自己的 javascript 工具链,可以 查看这个指南,它重新创建了一些 create react app 的功能。\n新文档 :: 由于 hook 的引入,在新文档中你基本上看不到 class 组件的写法了。javascript 的面向对象应用并不多,回归“函数”是一个非常明智的选择。\n老生常谈 react 组件是 返回标签 的 javascript 函数。react 组件必须以大写字母开头,而 html 标签则必须是小写字母。\n:: class 的写法基本上被束之高阁了~\n返回标签?什么标签?jsx 喽。\n需要注意的是,你的组件 不能返回多个 jsx 标签,你必须将它们包裹到一个共享的父级中,,比如 \u0026lt;div\u0026gt;...\u0026lt;/div\u0026gt; 或使用空的 \u0026lt;\u0026gt;...\u0026lt;/\u0026gt; 包裹。\n在 react 中,你可以使用 classname 来指定一个 css 的 class ,但它并没有规定你如何添加 css 文件。\njsx 会让你把标签放到 javascript 中,而大括号会让你 “回到” javascript 中。\n:: 这一点做的就比较好,只有一个 {...} 搞定一切,统一无负担。\n至于条件渲染和列表渲染,则直接使用 javascript 本身的能力即可。\n你可以通过在组件中声明 事件处理 函数来响应事件,需要注意的是,不要 调用 事件处理函数(即不要在函数名后加小括号):你只需 把函数传递给事件 即可。\nreact 引入 usestate 这个内置 hook ,用来初始化和更新组件状态 state 。多次渲染同一个组件,每个组件都会拥有自己的 state。\n💡 你只能在你的组件(或其他 hook)的 顶层 调用 hook。\n老样子,我们可以通过状态容提升来实现组件状态共享。当然,后续我们会有另外的方式。\n再谈 react 哲学 react 可以改变你对可见设计和应用构建的思考。当你使用 react 构建用户界面时,你首先会把它分解成一个个 组件,然后,你需要把这些组件连接在一起,使数据流经它们。\n步骤一:将 ui 拆解为组件层级结构\n一开始,在绘制原型中的每个组件和子组件周围绘制盒子并命名它们。\n![[assets/pasted image 20240724152435.png|525]]\n现在你已经在原型中辨别了组件,并将它们转化为了层级结构。\n![[assets/pasted image 20240724153059.png]]\n步骤二:使用 react 构建一个静态版本\n先构建一个静态版本,然后再一个个添加交互。构建一个静态版本需要写大量的代码,并不需要什么思考; 但添加交互需要大量的思考,却不需要大量的代码。\n:: 这是一种很不错的方式,让大脑的线程不至于来回切换。\n步骤三:找出 ui 精简且完整的 state 表示\n考虑将 state 作为应用程序需要记住改变数据的最小集合,即组织 state 最重要的一条原则是保持它 dry(不要自我重复)。\n如何判断哪些是 state ,哪些不是呢?\n随着时间推移 保持不变?如此,便不是 state。 通过 props 从父组件传递?如此,便不是 state。 是否可以基于已存在于组件中的 state 或者 props 进行计算?如此,它肯定不是state! 步骤四:⭐ 验证 state 应该被放置在哪里\n步骤五:添加反向数据流\n:: 原型分组件,组件定层级,数据流其中。\n旧文档 i.e. 核心概念\n老规矩,上 \u0026quot;hello world\u0026quot; 😂\n1 2 3 4 5 6 7 8 // \u0026lt;div id=\u0026#34;root\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; const element = \u0026lt;h1\u0026gt;hello, world!\u0026lt;/h1\u0026gt;; reactdom.render( element, document.getelementbyid(\u0026#39;root\u0026#39;) ); 它将在页面上展示一个 “hello, world!” 的标题。不要着急,马上你就明白它的工作原理了!\njsx 简介 再观察一下上面的例子,这是什么?\nconst element = \u0026lt;h1\u0026gt;hello, world!\u0026lt;/h1\u0026gt;; \u0026gt; 怎么把 dom 元素直接赋给了一个变量 ❓\n这个有趣的标签语法既不是字符串也不是 html。它被称为 jsx,是一个 javascript 的语法扩展。\njsx 可以生成 react “元素”,它其实一个表达式,在编译(通过 babel)之后,会被转为普通 javascript 函数(react.createelement())调用,并且对其取值后得到 javascript 对象。\njsx 的语法格式十分简单!上 🌰\n1 2 3 4 5 6 7 8 const name = \u0026#39;josh perez\u0026#39;; const element = ( \u0026lt;h1\u0026gt; hello, {name} \u0026lt;/h1\u0026gt; ); const element = \u0026lt;img src={user.avatarurl}\u0026gt;\u0026lt;/img\u0026gt;; 在 jsx 语法中,你可以在大括号内放置任何有效的 javascript 表达式。\n只需要注意:\n尽量将内容包裹在括号中,以避免多行书写时遇到自动插入分号陷阱; 在属性中嵌入 javascript 表达式时,不要在大括号外面加上引号; 使用 camelcase(小驼峰命名)来定义属性的名称,而不使用 html 属性名称的命名约定。 ok,这就是 jsx ,再来一个例子看看它的具体转译过程!\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 我们用 jsx 是这样写的 const element = ( \u0026lt;h1 classname=\u0026#34;greeting\u0026#34;\u0026gt; hello, world! \u0026lt;/h1\u0026gt; ); // 被 babel 转译为 react.crateelement() 调用 const element = react.createelement( \u0026#39;h1\u0026#39;, {classname: \u0026#39;greeting\u0026#39;}, \u0026#39;hello, world!\u0026#39; ); // react.createelement() 会预先进行一些检查,实际上创建了如下对象, // 这些对象被称为 “react 元素”, // react 通过读取这些对象,然后使用它们来构建 dom 以及保持随时更新 // 注意:这是简化过的结构 const element = { type: \u0026#39;h1\u0026#39;, props: { classname: \u0026#39;greeting\u0026#39;, children: \u0026#39;hello, world!\u0026#39; } }; 是的,jsx 就是这么简单 ❗\n元素渲染 在上一节中,我们已经多次提到了 react “元素” ,它究竟是什么呢?\n元素描述了你在屏幕上想看到的内容。如 element 就是一个 react 元素:\nconst element = \u0026lt;h1\u0026gt;hello, world\u0026lt;/h1\u0026gt;; 与浏览器的 dom 元素不同,react 元素是创建开销极小的普通对象(详见上节)。react dom 会负责更新 dom 来与 react 元素保持一致。\n\u0026gt; 那 react dom 到底是如何渲染 react 元素为 dom 的呢 ❓\n只需要把它们传入 reactdom.render() 就可以了(该元素会被自动渲染到根 dom 节点中)!\n需要注意的是, react 元素是不可变对象! 一旦被创建,你就无法更改它的子元素或者属性。\n如何更新 ui 呢?\n根据我们已有的知识,更新 ui 唯一的方式是创建一个全新的元素,并将其传入 reactdom.render()。react dom 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 dom 达到预期的状态。\n1 2 3 4 5 6 7 8 9 10 11 12 function tick() { const element = ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;hello, world!\u0026lt;/h1\u0026gt; \u0026lt;h2\u0026gt;it is {new date().tolocaletimestring()}.\u0026lt;/h2\u0026gt; \u0026lt;/div\u0026gt; ); reactdom.render(element, document.getelementbyid(\u0026#39;root\u0026#39;)); } // 每秒都创建一个新元素,并传入 reactdom.render() setinterval(tick, 1000); 当然,在实践中,我们并不会那么蠢,大多数 react 应用只会调用一次 reactdom.render(),后续我们将学习如何封装一个有状态的组件。\n组件 \u0026amp; props 组件允许你将 ui 拆分为独立可复用的代码片段,并对每个片段进行独立构思。\n组件,从概念上类似于 javascript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 react 元素。\n在 react 中,有两种组件形式:函数组件和类组件,如下:\n1 2 3 4 5 6 7 8 9 10 11 // 函数组件 function welcome(props) { return \u0026lt;h1\u0026gt;hello, {props.name}\u0026lt;/h1\u0026gt;; } // 类组件 class welcome extends react.component { render() { return \u0026lt;h1\u0026gt;hello, {this.props.name}\u0026lt;/h1\u0026gt;; } } 上述两个组件在 react 里是等效的。 它们返回的都是 react 元素哦!\n:: 在实际应用中,函数式组件明显更受欢迎,也更符合直觉,再加上现在有了 hook,所以你懂得 ……\n例如,这段代码会在页面上渲染 “hello, sara”:\n1 2 3 4 5 6 7 8 9 function welcome(props) { return \u0026lt;h1\u0026gt;hello, {props.name}\u0026lt;/h1\u0026gt;; } const element = \u0026lt;welcome name=\u0026#34;sara\u0026#34; /\u0026gt;; reactdom.render( element, document.getelementbyid(\u0026#39;root\u0026#39;) ); 让我们来回顾一下这个例子中发生了什么:\n我们调用 reactdom.render() 函数,并传入 \u0026lt;welcome name=\u0026quot;sara\u0026quot; /\u0026gt; 作为参数; react 调用 welcome 组件,并将 {name: 'sara'} 作为 props 传入; welcome 组件将 \u0026lt;h1\u0026gt;hello, sara\u0026lt;/h1\u0026gt; 元素作为返回值; react dom 将 dom 高效地更新为 \u0026lt;h1\u0026gt;hello, sara\u0026lt;/h1\u0026gt;。 注意: 组件名称必须以大写字母开头 !!!(react 会将以小写字母开头的组件视为原生 dom 标签)\n组件可以在其输出中引用其他组件(组件组合)。有时候,将组件拆分为更小的组件也是很不错的选择(组件提取)。\n所有 react 组件都必须像纯函数一样保护它们的 props 不被更改。\n:: 其实,props 很简单,就把它理解为一个只读的函数入参就行了!函数,你足够了解的,对吧?\nprops 是不可变的,但应用程序的 ui 是动态的,并会伴随着时间的推移而变化,emm… 😟\n放心!在下一章节中,我们将介绍一种新的概念,称之为 “state”。在不违反上述规则的情况下,state 允许 react 组件随用户操作、网络响应或者其他变化而动态更改输出内容。\nstate \u0026amp; 生命周期 在元素渲染章节中,我们只了解了一种更新 ui 界面的方法,通过调用 reactdom.render() 来修改我们想要渲染的元素。\n我们也说了,那种方法有点蠢 🤣! 在本章节中,我们将学习如何封装真正可复用的组件。\nstate 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。\n下面,让我们看一个完整的 clock 组件(请留意注释内容):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class clock extends react.component { // 构造函数 - 用来初始化的 constructor(props) { // 将 props 传递到父类的构造函数中 ❓ // class 组件应该始终使用 props 参数来调用父类的构造函数 super(props); // 在构造函数中为 this.state 赋初值 this.state = { data: new date() }; // 将生命周期方法添加到 class 中 // ^ 组件挂载 componentdidmount() { // 尽管 this.props 和 this.state 是 react 本身设置的,且都拥有特殊的含义, // 但是其实你可以向 class 中随意添加不参与数据流(比如计时器 id)的额外字段 this.timerid = setinterval(() =\u0026gt; this.tick(), 1000 ); } // ^ 组件卸载 componentwillunmount() { clearinterval(this.timerid); } tick() { // 使用 this.setstate() 来时刻更新组件 state this.setstate({ date: new date() }); } render() { return ( \u0026lt;div\u0026gt; \u0026lt;h1\u0026gt;hello, world!\u0026lt;/h1\u0026gt; \u0026lt;h2\u0026gt;it is {this.state.date.tolocaletimestring()}.\u0026lt;/h2\u0026gt; \u0026lt;/div\u0026gt; ); } } } reactdom.render( \u0026lt;clock /\u0026gt;, document.getelementbyid(\u0026#39;root\u0026#39;) ); 让我们来快速概括一下发生了什么和这些方法的调用顺序:\n当 \u0026lt;clock /\u0026gt; 被传给 reactdom.render() 的时候,react 会调用 clock 组件的构造函数。因为 clock 需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化 this.state 。我们会在之后更新 state ; 之后 react 会调用组件的 render() 方法。这就是 react 确定该在页面上展示什么的方式。然后 react 更新 dom 来匹配 clock 渲染的输出; 当 clock 的输出被插入到 dom 中后,react 就会调用 componentdidmount() 生命周期方法。在这个方法中,clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick() 方法; 浏览器每秒都会调用一次 tick() 方法。 在这方法之中,clock 组件会通过调用 setstate() 来计划进行一次 ui 更新。得益于 setstate() 的调用,react 能够知道 state 已经改变了,然后会重新调用 render() 方法来确定页面上该显示什么。这一次,render() 方法中的 this.state.date 就不一样了,如此以来就会渲染输出更新过的时间。react 也会相应的更新 dom; 一旦 clock 组件从 dom 中被移除,react 就会调用 componentwillunmount() 生命周期方法,这样计时器就停止了。 是的!state 就是一个组件的核心!!! 下面我们来看一下,如何正确的使用它!\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 🅰️ 不要直接修改 state,应该使用 setstate() // 构造函数是唯一可以给 this.state 赋值的地方: this.state.comment = \u0026#39;hello\u0026#39;;\t// ❌ this.setstate({comment: \u0026#39;hello\u0026#39;});\t// ✔️ // 🅱️ state 的更新可能是异步的,不要依赖他们的值来更新下一个状态 this.setstate({ counter: this.state.counter + this.props.increment, });\t// 可能 ❌ // 要解决这个问题,可以让 setstate() 接收一个函数而不是一个对象, // 这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数 this.setstate((state, props) =\u0026gt; ({ counter: state.counter + props.increment }));\t// ✔️ 数据是向下流动的!\n不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。\n组件可以选择把它的 state 作为 props 向下传递到它的子组件中。\n事件处理 react 元素的事件处理和 dom 元素的很相似,但是有一点语法上的不同:\nreact 事件的命名采用小驼峰式(camelcase),而不是纯小写; 使用 jsx 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串; 在 react 中另一个不同点是你不能通过返回 false 的方式阻止默认行为,你必须显式的使用 preventdefault 。 1 2 3 4 5 6 7 8 9 10 11 12 13 function actionlink() { function handleclick(e) { // 在这里,e 是一个合成事件,react 根据 w3c 规范来定义这些合成事件 e.preventdefault();\t// 显式的使用 ✔️ console.log(\u0026#39;the link was clicked.\u0026#39;); } return ( \u0026lt;a href=\u0026#34;#\u0026#34; onclick={handleclick}\u0026gt;\t// 注意,大括号外不要加引号 click me \u0026lt;/a\u0026gt; ); } 另外,当你使用 es6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法。如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class toggle extends react.component { constructor(props) { super(props); this.state = {istoggleon: true}; // 为了在回调中使用 `this`,这个绑定是必不可少的,否则 // 当你调用 onclick={this.handleclick} 这个事件函数回调的时候, // `this` 的值为 `undefined` ,会报错 this.handleclick = this.handleclick.bind(this); // bind 太麻烦 ?试试下面这个等效写法 - class fields 语法 // create react app 默认启用此语法 // 此语法确保 `handleclick` 内的 `this` 已被绑定 // 注意:这是 *实验性* 语法 // handleclick = () =\u0026gt; { // console.log(\u0026#39;this is:\u0026#39;, this); // } } handleclick() { this.setstate(state =\u0026gt; ({ istoggleon: !state.istoggleon })); } render() { return ( \u0026lt;button onclick={this.handleclick}\u0026gt; {this.state.istoggleon ? \u0026#39;on\u0026#39; : \u0026#39;off\u0026#39;} \u0026lt;/button\u0026gt; ); } } reactdom.render( \u0026lt;toggle /\u0026gt;, document.getelementbyid(\u0026#39;root\u0026#39;) ); 你必须谨慎对待 jsx 回调函数中的 this,在 javascript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleclick 并把它传入了 onclick,当你调用这个函数的时候 this 的值为 undefined。\n这并不是 react 特有的行为,这其实与 javascript 函数工作原理有关。\n:: emm… this 可以说是 javascript 永远的痛了,好在应用起来并不算太难!\n在事件处理中,除了 this 的绑定之外,还有一个需要注意的地方 - 向事件处理程序传递参数。\n在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 id,以下两种方式都可以向事件处理函数传递参数:\n1 2 \u0026lt;button onclick={(e) =\u0026gt; this.deleterow(id, e)}\u0026gt;delete row\u0026lt;/button\u0026gt; \u0026lt;button onclick={this.deleterow.bind(this, id)}\u0026gt;delete row\u0026lt;/button\u0026gt; 上述两种方式是等价的,分别通过箭头函数和 function.prototype.bind 来实现。\n在这两种情况下,react 的事件对象 e 会被作为第二个参数传递。 如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。\n条件渲染 这里,就不多讲了,只要记住 jsx 最终会被转成一个 javascript 对象,条件渲染也就是 if 或者条件运算符那点事了。\n在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。在组件的 render 方法中返回 null 并不会影响组件的生命周期。\n想要了解更多,直接阅读 条件渲染 。\n列表 \u0026amp; key 同上,略!\n唯一需要注意的是 key ,它是什么?\nkey 帮助 react 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。\n元素的 key 只有放在就近的数组上下文中才有意义。\n:: 所谓列表,就是利用一些迭代数据,组装出可用子元素集合,然后把它们放在应该放的父元素中就可以了。\n详见 列表 \u0026amp; key 。\n表单 主要是弄清楚 “受控组件” 和 “非受控组件” 的概念,就可以喽。详见 表单 。\n状态提升 :: 抽象和共享,永远不变的真理!\n通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。\n在 react 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。\n更多详见 状态提升。\n组合 vs 继承 详见 组合 vs 继承 – react - react.docschina.org。\nreact 哲学 我们认为,react 是用 javascript 构建快速响应的大型 web 应用程序的首选方式。\n:: emm… vue:我才是,angular:你们都是弟弟!\nok,上心法 ❤️。\n第一步:将设计好的 ui 划分为组件层级\n第二步:用 react 创建一个静态版本\n第三步:确定 ui state 的 最小(且完整)表示\n通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:\n该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。 第四步:确定 state 放置的位置\n第五步:添加反向数据流\n:: 基础的核心概念并不多(毕竟就是一个 ui 库嘛),但其思想非常好,官方文档也相当 ok ,可以不定期地多看几遍。\nhook ::不着急,先过几遍再说这个,很简单的!\n相关技术栈 当然,你可以选择从零开始,但更好的选择是使用官方提供的脚架 - create react app 。\n样式 react 对样式如何定义并没有明确态度;如果存在疑惑,比较好的方式是和平时一样,在一个单独的 *.css 文件定义你的样式,并且通过 classname 指定它们。\nreact 并没有原生提供 css 封装方案!!!\nreact 本身的设计原则决定了其不会提供原生的 css 封装方案,或者说 css 封装并不是 react 框架本身的关注点。因此,react 社区从很早的时候就开始寻找相关替代办法。\n- css 模块化(css modules) 这种做法非常类似 angular 与 vue 对样式的封装方案,其核心是以 css 文件模块为单元,将模块内的选择器附上特殊的哈希字符串,以实现样式的局部作用域。对于大多数 react 项目来说,这种方案已经足够用了。 - 基于共识的人工维护的方法论,如 bem 这种方法的缺点是会为团队带来很大的挑战,对于全局和局部规划选择器的命名,团队对于这种方法需要有共识,即使熟练使用的情况下,在使用中依然有着较高的思维负担和维护成本。 - shadow dom 借助 direflow.io 等工具,我们可以将 react 组件输出为 web component,借助 shadow dom 实现组件的 css 样式封装。这是一种解决办法,不过基本很少有项目选择这样做。 - css-in-js scss 好吧,相信你的项目是由 create react app (cra) 生成的,如果你想使用 scss ,只需要安装 dart-sass 库即可,像下面这样:\nnpm i sass --save-dev 感谢 node-sass 退出历史舞台,但感谢作者的贡献 😅!\nok,安装之后,就可以把 *.scss 文件作为一个模块引入了,如:\nimport example from \u0026#39;./example.scss\u0026#39;; css in js 注意此功能并不是 react 的一部分,而是由第三方库提供。\n“css-in-js” 是指一种模式,其中 css 由 javascript 生成而不是在外部文件中定义。在 此处 阅读 css-in-js 库之间的对比。\n:: css in js 的本质就是写行内样式 style ❓❗\n1 2 3 4 5 6 7 8 9 10 11 12 13 const style = { \u0026#39;color\u0026#39;: \u0026#39;red\u0026#39;, \u0026#39;fontsize\u0026#39;: \u0026#39;46px\u0026#39; }; const clickhandler = () =\u0026gt; alert(\u0026#39;hi\u0026#39;); reactdom.render( \u0026lt;h1 style={style} onclick={clickhandler}\u0026gt; hello, world! \u0026lt;/h1\u0026gt;, document.getelementbyid(\u0026#39;example\u0026#39;) ); 当然,大项目,这样直接写是非常不明智的,好在有懒人包 🥳!\n目前比较流行的两个解决方案是 styled-components 和 emotion 。\n相关参考:\ncss-in-js:一个充满争议的技术方案 - 知乎 - zhuanlan.zhihu.com css modules 🏆(首推) 这种做法非常类似 angular 与 vue 对样式的封装方案,其核心是以 css 文件模块为单元,将模块内的选择器附上特殊的哈希字符串,以实现样式的局部作用域。对于大多数 react 项目来说,这种方案已经足够用了。\n由于一般的脚手架都默认集成了 css modules,比如 react 官方的脚手架:create-react-app,已经将 css modules 集成进来了,我们可以直接使用。\n如何使用呢?\n详见 在 react 中使用 css modules。\n关于样式污染和穿透\n在 react中sass的使用,解决样式污染,样式穿透 - 掘金 ?\n使用 css modules !最佳实践 如下:\n每个组件的根节点使用 css modules 形式的类名(根元素的类名: root) 其他所有的子节点,都全用普通的 css 类名 :global 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // === index.module.scss === .root { display: \u0026#39;block\u0026#39;; position: \u0026#39;absolute\u0026#39;; // 此处,使用 global 包裹其他子节点的类名。此时, // 这些类名就不会被处理,在 jsx 中使用时,就可以用字符串形式的类名。 // 如果不加 :global ,所有类名就必须添加 styles.title 才可以 :global { .title { .text { } span { } } .login-form { ... } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import styles from \u0026#39;./index.module.scss\u0026#39; const 组件 = () =\u0026gt; { return ( {/* (1) 根节点使用 cssmodules 形式的类名( 根元素的类名: `root` )*/} \u0026lt;div classname={styles.root}\u0026gt; {/* (2) 所有子节点,都使用普通的 css 类名*/} \u0026lt;h1 classname=\u0026#34;title\u0026#34;\u0026gt; \u0026lt;span classname=\u0026#34;text\u0026#34;\u0026gt;登录\u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;登录\u0026lt;/span\u0026gt; \u0026lt;/h1\u0026gt; \u0026lt;form classname=\u0026#34;login-form\u0026#34;\u0026gt;\u0026lt;/form\u0026gt; \u0026lt;/div\u0026gt; ) } 路由 hmmm… 页面路由,大大的有用!\nhome v6.26.0 | react router 在开始之前 | react router6 中文文档 react-router-dom 使用指南(最新 v6.0.1) - 知乎 - zhuanlan.zhihu.com introduction · react router 中文手册 - uprogrammer.cn 如果你使用服务端渲染 ssr 的话,你可以直接使用 next、remix 等既有的路由框架。\n如果准备使用传统的客户端渲染(多数情况),直接使用 react router 即可,如下:\n# npm install react-router-dom # always need this! import * as react from \u0026#34;react\u0026#34;; import * as reactdom from \u0026#34;react-dom/client\u0026#34;; import { createbrowserrouter, routerprovider, } from \u0026#34;react-router-dom\u0026#34;; import \u0026#34;./index.css\u0026#34;; const router = createbrowserrouter([ { path: \u0026#34;/\u0026#34;, element: \u0026lt;div\u0026gt;hello world!\u0026lt;/div\u0026gt;, }, ]); reactdom.createroot(document.getelementbyid(\u0026#34;root\u0026#34;)).render( \u0026lt;react.strictmode\u0026gt; \u0026lt;routerprovider router={router} /\u0026gt; \u0026lt;/react.strictmode\u0026gt; ); 看,很简单吧。当然,实际项目中,把 router 逻辑单独提取出来作为一个模块引入将使你的代码更加简洁。\n状态管理 redux 中文官网 - javascript 应用的状态容器,提供可预测的状态管理。 | redux 中文官网 react redux 中文文档 | react redux 中文文档 异步请求 都什么时代还在发传统请求?来看看 swr 如何用 react hook 实现优雅请求 - 掘金 示例 这里我们附上一个基本的脚手架搭建示例,它使用以下技术:\nvite typescript swc react router scss aixos redux \u0026hellip; 具体内容跳转到 [[react 基础脚手架示例]] 。\n","date":"2023-05-25","permalink":"https://aituyaa.com/react/","summary":"\u003cp\u003e📔 \u003ca href=\"https://zh-hans.react.dev/learn\"\u003e快速入门 – React 中文文档\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e具体安装及引入细节,请直接参考官方文档。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: React 的出新文档了,内容的组织结构也有所变化,整体来说更加突出函数式组件的作用了,毕竟有了 Hooks 嘛~ 来一个新的章节来摘录一下,准备在新的项目中应用 React ,反正用什么我说了算,哈哈~ ↗️ 「 [[#新文档]] 」\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eReact 是一个用于构建用户界面的 JavaScript 库,你可以用它给简单的 HTML 页面增加一点交互,也可以开始一个完全由 React 驱动的复杂应用。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; 对的,它只是一个 UI 库而已 !!!\u003c/code\u003e\u003c/p\u003e","title":"react"},]
[{"content":"🔔 摘录自 http://c.biancheng.net/spring/\n虽然我们在生产环境中基本上使用 spring boot 了,但是深入了解 spring 是非常必要的,因为它是“地基”。\nspring 是什么 spring 由 rod johnson 创立,2004 年发布了 spring 框架的第一版,其目的是用于简化企业级应用程序开发的难度和周期。\n![[assets/pasted image 20230525165534.png]]\nspring 自诞生以来一直备受青睐,它包括许多框架,例如 spring framework、springmvc、springboot、spring cloud、spring data、spring security 等,所以有人将它们亲切的称之为:spring 全家桶。\nspring framework 就是我们平时说的 spring 框架,spring 框架是全家桶内其它框架的基础和核心。\nspring 是分层的 java se/ee 一站式轻量级开源框架, 以 ioc(inverse of control,控制反转)和 aop(aspect oriented programming,面向切面编程)为内核。\nioc 指的是将对象的创建权交给 spring 去创建。使用 spring 之前,对象的创建都是由我们使用 new 创建,而使用 spring 之后,对象的创建都交给了 spring 框架。aop 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,aop 还解决一些系统层面上的问题,比如日志、事务、权限等。\n:: 共享、解耦、去冗。\n在 spring 中,认为一切 java 类都是资源,而资源都是类的实例对象(bean),容纳并管理这些 bean 的是 spring 所提供的 ioc 容器,所以 spring 是一种基于 bean 的编程 ,它深刻地改变着 java 开发世界,迅速地取代 ejb 成为了实际的开发标准。\n在实际开发中,服务器端通常采用三层体系架构,分别为表现层(web)、业务逻辑层(service)、持久层(dao)。\nspring 致力于 java ee 应用各层的解决方案,对每一层都提供了技术支持。在表现层提供了与 spring mvc、struts2 框架的整合,在业务逻辑层可以管理事务和记录日志等,在持久层可以整合 mybatis、hibernate 和 jdbctemplate 等技术。这就充分体现出 spring 是一个全面的解决方案,对于已经有较好解决方案的领域,spring 绝不做重复的事情。\nspring 体系结构 spring 框架采用分层的理念,根据功能的不同划分成了多个模块,这些模块大体可分为:\ndata access/integration(数据访问与集成) –\u0026gt; jdbc、orm、oxm、jms、transactions web –\u0026gt; websocket、servlet、web、portlet aop、aspects、instrumentation(检测)、messaging(消息处理) core container(核心容器) –\u0026gt; beans、core、context、spel test 如下图所示,spring framework 4.x 版本后的系统框架图:\n![[assets/pasted image 20230525165606.png|600]]\n上图中包含了 spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。\n1. data access/integration(数据访问/集成)\n数据访问/集成层包括 jdbc、orm、oxm、jms 和 transactions 模块,具体介绍如下:\njdbc 模块:提供了一个 jdbc 的样例模板,使用这些模板能消除传统冗长的 jdbc 编码,还有必须的事务控制,而且能享受到 spring 管理事务的好处; orm 模块:提供与流行的“对象-关系”映射框架无缝集成的 api,包括 jpa、jdo、hibernate 和 mybatis 等,而且还可以使用 spring 事务管理,无需额外控制事务; oxm 模块:提供了一个支持 object/xml 映射的抽象层实现,如 jaxb、castor、xmlbeans、jibx 和 xstream 。将 java 对象映射成 xml 数据,或者将 xml 数据映射成 java 对象; jms 模块:指 java 消息服务,提供一套“消息生产者、消息消费者”模板用于更加简单的使用 jms,jms 用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信; transactions 事务模块:支持编程和声明式事务管理。 2. web 模块\nspring 的 web 层包括 web、servlet、websocket 和 portlet 组件,具体介绍如下:\nweb 模块:提供了基本的 web 开发集成特性,例如多文件上传功能、使用的 servlet 监听器的 ioc 容器初始化以及 web 应用上下文; servlet 模块:提供了一个 spring mvc web 框架实现。spring mvc 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 jsp 标签,完全无缝与 spring 其他技术协作; websocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 websocket server ,从而实现双向通讯; portlet 模块:提供了在 portlet 环境中使用 mvc 实现,类似于 web-servlet 模块的功能。 3. core container(spring 的核心容器)\nspring 的核心容器是其他模块建立的基础,由 beans 模块、core 核心模块、context 上下文模块和 spel 表达式语言模块组成,没有这些核心容器,也不可能有 aop、web 等上层的功能。具体介绍如下:\nbeans 模块:提供了框架的基础部分,包括控制反转和依赖注入; core 核心模块:封装了 spring 框架的底层部分,包括资源访问、类型转换及一些常用的工具类; context 上下文模块:建立在 core 和 beans 模块的基础之上,集成 beans 模块功能并添加资源绑定、数据验证、国际化、java ee 支持、容器生命周期、事件传播等。applicationcontext 接口是上下文模块的焦点; spel 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 spring 容器获取 bean,它也支持列表投影、选择和一般的列表聚合等。 4. aop、aspects、instrumentation 和 messaing\n在 core container 之上是 aop、aspects 等模块,具体介绍如下:\naop 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态地把这些功能添加到需要的代码中,如此,降低业务逻辑和通用功能的耦合; aspects 模块:提供与 aspectj 的集成,是一个功能强大且成熟的面向切面编程(aop)框架; instrumentation 模块:提供了类工具的支持和类的加载器的实现,可以在特定的应用服务器中使用; messaging 模块:spring 4.0 以后新增了消息(spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。 5. test 模块\ntest 模块:spring 支持 junit 和 testng 测试框架,而且还额外提供了一些基于 spring 的测试功能,比如在测试 web 框架时,模拟 http 请求的功能。\n:: hmm\u0026hellip; 先有个感性了解即可,有利于后续引入相应的包及源码阅读。\n","date":"2023-05-25","permalink":"https://aituyaa.com/spring/","summary":"\u003cp\u003e🔔 摘录自 \u003ca href=\"http://c.biancheng.net/spring/\"\u003ehttp://c.biancheng.net/spring/\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e虽然我们在生产环境中基本上使用 \u003ca href=\"http://localhost:1313/posts/spring-boot/\"\u003eSpring Boot\u003c/a\u003e 了,但是深入了解 Spring 是非常必要的,因为它是“地基”。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"spring"},]
[{"content":"🔔 相关内容请参阅 https://www.liaoxuefeng.com/wiki/1252599548343744/1266265175882464\nspring boot 是什么?它是一个基于 spring 的套件,它帮助我们预组装了一系列组件,以便以尽可能少的代码和配置来开发基于 spring 的 java 应用程序。\nspring boot makes it easy to create stand-alone, production-grade spring based applications that you can \u0026ldquo;just run\u0026rdquo;.\nwe take an opinionated view of the spring platform and third-party libraries so you can get started with minimum fuss. most spring boot applications need minimal spring configuration.\n\u0026ndash; spring boot\n在 spring 中,我们使用 xml 文件或是注解来告诉 spring 如果处理我们的组件。但是你必需给它明确的指令,它才知道如何正确的执行。随着组件数量的增多,配置项变得越来越长,以至于难以维护。所以,有必要让框架为我们做更多的事儿,比如解决依赖的依赖等问题,这就是为什么我们需要 spring boot。\nspring boot 是如何做到这些的呢?下面让我们来慢慢揭开这位“俏姑娘”的面纱 🥰。\n她长什么样? 我们不妨新建一个 springboot-hello 项目,创建标准的 maven 目录结构如下:\nspringboot-hello ├── pom.xml ├── src │ └── main │ ├── java │ └── resources │ ├── application.yml\t# spring boot 默认的配置文件 │ ├── logback-spring.xml\t# spring boot 的 logback 配置文件名称 │ ├── static\t# 静态文件目录 │ └── templates\t# 模板文件目录 └── target 我们主要的工作目录是 src/main/java/ ,我们来看一看源码目录结构:\nsrc/main/java └── com └── itranswarp └── learnjava ├── application.java\t# !启动类 ├── entity │ └── user.java ├── service │ └── userservice.java └── web └── usercontroller.java 注意:spring boot 要求 main() 方法所在的启动类必须放到根 package 下,命名不做要求(这里我们以 application.java 命名)。\n:: 启动类,是一切的起点哦。\n她的魔法之源 启动类 我们来看一下上个章节中的 application.java 启动类,其内容如下:\n1 2 3 4 5 6 @springbootapplication public class application { public static void main(string[] args) throws exception { springapplication.run(application.class, args); } } 像是之前,我们使用的 spring 启动类,它包含了各种各样的注解,如 @configuration、 @componentscan 等。现在,我们却只需要 @springbootapplication,它是如何工作的?\n原来 @springbootapplication 这个注解实际上包含了:\n- @springbootconfiguration - @configuration - @enableautoconfiguration - @autoconfigurationpackage - @componentscan 所以,这一个注解相当于启动了自动配置和自动扫描。那么,她是 如何实现自动配置和自动扫描的呢?\n自动扫描和配置 我们再观察 pom.xml ,它的内容如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 \u0026lt;project ...\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;groupid\u0026gt;org.springframework.boot\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;spring-boot-starter-parent\u0026lt;/artifactid\u0026gt; \u0026lt;version\u0026gt;2.3.0.release\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;modelversion\u0026gt;4.0.0\u0026lt;/modelversion\u0026gt; \u0026lt;groupid\u0026gt;com.itranswarp.learnjava\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;springboot-hello\u0026lt;/artifactid\u0026gt; \u0026lt;version\u0026gt;1.0-snapshot\u0026lt;/version\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;11\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;11\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;java.version\u0026gt;11\u0026lt;/java.version\u0026gt; \u0026lt;pebble.version\u0026gt;3.1.2\u0026lt;/pebble.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupid\u0026gt;org.springframework.boot\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;spring-boot-starter-web\u0026lt;/artifactid\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupid\u0026gt;org.springframework.boot\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;spring-boot-starter-jdbc\u0026lt;/artifactid\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 集成 pebble view --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupid\u0026gt;io.pebbletemplates\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;pebble-spring-boot-starter\u0026lt;/artifactid\u0026gt; \u0026lt;version\u0026gt;${pebble.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- jdbc 驱动 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupid\u0026gt;org.hsqldb\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;hsqldb\u0026lt;/artifactid\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; 使用 spring boot 时,强烈推荐从 spring-boot-starter-parent 继承,它会引入 spring boot 的预置配置。\n:: 按大佬说的做,别给自己找 trouble 😂\n紧接着,我们引入了各种 starter 依赖,并且没有指定版本号,因为引入的 \u0026lt;parent\u0026gt; 内已经指定了,只有我们自己引入的某些第三方 jar 包需要指定版本号。\nstarter 是个啥?\nspring boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的 starter (启动器),starter 中整合了该场景下各种可能用到的依赖,用户只需要在 maven 中引入 starter 依赖,springboot 就能自动扫描到要加载的信息并启动相应的配置。\nstarter 提供了大量的自动配置,让用户摆脱了处理各种依赖和配置项的困扰。所有这些 starter 都遵循着约定俗成的默认配置,并允许用户调整这些配置。\n并不是所有的 starter 都是由 spring boot 官方提供的,也有部分 starter 是第三方技术厂商提供的,如 druid-spring-boot-starter 和 mybatis-spring-boot-starter 等等。\n在启动时, spring boot 自动启动了嵌入式 tomcat ,如数据源、声明式事务、jdbctemplate 等 bean 都是由 spring boot 自动创建 \u0026ndash; 通过 autoconfiguration。\n在 starter 引入后,在启动时会自动扫描所有的 xxxautoconfiguration ,如,当我们引入 spring-boot-starter-jdbc 时,它自动扫描了如下:\ndatasourceautoconfiguration :自动创建一个 datasource ,其中配置项从 application.yml 的 spring.datasource 中读取; datasourcetransactionmanagerautoconfiguration :自动创建了一个基于 jdbc 的事务管理器; jdbctemplateautoconfiguration:自动创建了一个 jdbctemplate 。 因此,我们自动得到了一个 datasource、一个 datasourcetransactionmanager 和一个 jdbctemplate 。\nspring boot 大量使用 xxxautoconfiguration 来使得许多组件被自动化配置并创建。\n看, xxxautoconfiguration 就是 spring boot 的魔力之源,许多事情这些自动配置类都帮我们做了,谢谢它们全家。\n点石成金 i.e. 打包 spring boot 应用\nspring boot 自带了一个简单强大的 spring-boot-maven-plugin 插件用来打包,我们只需要在 pom.xml 中引入它即可。像这样:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026lt;project ...\u0026gt; ... \u0026lt;build\u0026gt; \u0026lt;!-- 默认的项目名为 `项目名+版本号`,不喜欢可以通过 `finalname` 自定义 --\u0026gt; \u0026lt;!-- \u0026lt;finalname\u0026gt;awesome-app\u0026lt;/finalname\u0026gt; --\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupid\u0026gt;org.springframework.boot\u0026lt;/groupid\u0026gt; \u0026lt;artifactid\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactid\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; \u0026lt;/project\u0026gt; 无需使用配置,spring boot 的这款插件会自动定位应用程序的入口 class ,我们只需要执行 mvn clean package 命令就可以打包了。\n之后,在打包后的 target 目录下,可以看到一个 springboot-exec-jar-1.0-snapshot.jar 包,它包含了项目运行所需的所有依赖,可以直接运行:\n1 java -jar springboot-exec-jar-1.0-snapshot.jar 这样,部署一个 spring boot 应用就非常简单,无需预安装任何服务器,只需要上传 jar 包即可。\n结语 看 spring boot 很简单,让我们的工程实现也变得简单。我们在这里只涉及了基本的初步介绍,想要了解更多,去设计一个项目吧,只有在具体的问题中才能更好地了解“她”。\n","date":"2023-05-25","permalink":"https://aituyaa.com/spring-boot/","summary":"\u003cp\u003e🔔 相关内容请参阅 \u003ca href=\"https://www.liaoxuefeng.com/wiki/1252599548343744/1266265175882464\"\u003ehttps://www.liaoxuefeng.com/wiki/1252599548343744/1266265175882464\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eSpring Boot 是什么?它是一个基于 \u003ca href=\"../spring/\"\u003eSpring\u003c/a\u003e 的套件,它帮助我们预组装了一系列组件,以便以尽可能少的代码和配置来开发基于 Spring 的 Java 应用程序。\u003c/p\u003e","title":"spring boot"},]
[{"content":"vim 是 vi 的升级版,一款功能强大、高度可定制的文本编辑器,它只有一个对手 – emacs 。那么,它来自哪里?\nvim 123 说起 vi ,就不得不提起这们大佬 – bill joy。除了 vi ,他还创建了 bsd 和 sun ,ok,大佬就是大佬。\n![[assets/pasted image 20230525223050.png|350]]\n\u0026gt; bill joy - vi 的创始人\n2003年9月9日,乔伊离开sun公司,sun发言人除了宣布joy辞职的消息外,不愿意发表其他评论。从一些迹象看来,他很关注机器人、纳米、基因工程等可能会改变全人类未来生存发展的技术;更加关注科技带来的道德问题: 如何不让科技成为一个国家、政府、集体、甚至个人做恶的工具?\n![[assets/pasted image 20230822154956.png|350]]\n\u0026gt; bram moolenaar - vim 的创始人\n:: 真心用 vim 不是很多,大佬走好,致敬!\n基础 vim 好学吗?好学,先来个速成 y 分钟学习 vim 。另外,附上关注的一挺有趣的视频 vim 使用技巧系列 。其实,一张图足矣,重要的是使用和练习。\n下图虽好,但可以先忽略,它是用来做查询用的,还是先了解下一些基础的概念。\n![[assets/vim.gif]]\n如果,以前没有接触过 vim/vi ,初次使用的时候大概率会很迷惑,这再正常不过了。\n安装 去官网下载对应版本安装即可,乌干达 forever …… ⁉️\n![[assets/pasted image 20230525223149.png|350]]\n我们这里只针对 cli ,vim 谁用 gui ?那玩意没有灵魂… 如果,你是在 windows 下(常用工作环境),建议使用 git-bash ,安装了 git 之后自带,内置已经安装好了 vim。\n模式 vim 编辑器基于 模式 这个概念。它有以下 4 种模式:\n命令模式:启动后处于这个模式,用于导航和操作命令; 插入模式:用于在文件中进行修改和编辑(同常规文本编辑器,如 notepad 😺); 可视模式:用于高亮文本并对它们进行操作(就是选择文字内容,进行复制粘贴之类的); ex 模式(底线命令模式):用于跳到底部的 : 提示行上输入命令。 在命令模式下,我们使用 hjkl 进行光标移动,为什么不直接使用 ←↓↑→ 方向键呢?原来,当前 bill joy 在开发 vi 编辑器时所使用的计算机是一个被称为 adm-3a 的终端,该终端附带的 hjkl 键本就和方向键同位一体,根本就没有独立的方向键。\n![[assets/pasted image 20230525223204.png]]\n从历史中寻找答案,而不是主观臆断。\n配置 vim 是高度可定制的,而你只需要一个配置文件 .vimrc ,它位于家目录下面,gnu/linux 上就是 ~ ,windows 上为 /c/users/\u0026lt;用户名\u0026gt;。为了便于管理,我们一般会把 .vimrc 命名为 vimrc 放于文件夹 .vim 中,然后把该文件夹置于家目录。如下:\n.vim\r├── autoload\r│ └── plug.vim\r├── plugged\r│ ├── delimitmate\r│ ├── molokai\r│ ├── vim-airline\r│ ├── vim-markdown\r│ ├── vim-startify\r│ └── vim-surround\r├── readme.md\r└── vimrc 上边这个目录图是在 linux 下,由 tree 命令生成的,确切的说是 tree -l 2 ,其中 2 是指定的展开的目录层级。不是手画的,不是手画的,曾经手画过……\n我自己的 vim 配置是很简单的,因为平时使用 emacs 😅 。vim 有多种不同的插件管理工具,如 vundle、neobundle、vimplug 和 pathogen 等,我使用的是 vimplug ,小巧、稳定而强大。这里,我们推荐一个神奇的插件库 vimawesome ,速度快、视图怡人。\n最后,再放一张简单配置后的图片吧。\n![[assets/pasted image 20230525223214.png]]\n预祝大家使用愉快,最后欢迎加入 vim 和 emacs 之间旷日持久的圣战,虽然没什么用 😏 。\n更多 着实是平时使用 vim 不是很多,就只说一些基本的使用理念和一些基础的配置情况。相对来说,这种理念性的东西,对所有编辑器都是通用的,只是实现方式有差异而已。\n所有类 unix 的系统中的工具,基本上都配备了一个不错的帮助系统,vim 自然也不例外。安装完成之后,可以在终端执行 vimtutor 打开一个内置的入门教程,默认是英文,当然你可以通过执行 vimtutor zh 打开对应的中文教程。\n记住,你总是可以通过 vim --help 来获取更多的帮助。\n文件 编辑器是用来编辑文件的,自然最先接触的就是如何新建、打开、更新及保存文件。\n1.打开、新建\n不妨假设,我们在文件夹 ~/demo 中,想要打开该文件夹中的文件 a ,只需要执行 vim a 即可。也可以同时打开多个文件,如 vim a b c 同时打开文件 a、b、c 。(若有,则打开文件,若没有,则创建一个新的文件。)\n.\r├── a\r└── b\r0 directories, 2 files 同时打开多文件时,默认只会显示第一个文件的 buffer ,可以使用以下命令,多窗展示,如下:\nvim -o a b c\t# 窗口在一行\rvim -o a b c\t# 窗口在一列 2. 更新、保存\n打开文件后,进入插入模式,就可以编辑文本了,完成之后使用 :\n:w 保存当前文件; :wq 或是 \u0026lt;s-z s-z\u0026gt; 保存并退出。 我们使用 \u0026lt;c、s、m/a-\u0026gt;表示 ctrl、shift 和 meta/alt 键。\n3. 展示、切换\n当我们编辑多文件时,很多时候避免不了在文件之间进行切换,当然也免不了需要打印出当前所有打开文件的列表。该如何做呢?(其实,这些常规操作才是 vim 的魅力所在)\n在 vim 中,我们可以通过 :ls 来展示当前所有打开的文件列表,如下:\n![[assets/pasted image 20230525223225.png]]\n其中 %a 表示当前激活的 buffer ,可以通过 :bn (buffer next)切换到下一个 buffer ,通过 :bp (buffer previous)切换到上一个 buffer 。\n文件过多了,也是繁琐,怎么办?\n可以通过 :e xxx 打开你想要的文件,但如果文件名 xxx 太长了,也很困扰。有一种更好的文件,如上图中所示,所有打开的文件都会被分配一个 id 号,我们可以通过 :buffer id 来进行直接切换到相应 buffer 。\n窗口 多窗口编辑是很常见的,窗口的新建、切换、关闭也是最常用到的操作之一。\n命令 描述 :sp 横向分屏(即窗口横向排列) :vsp 纵向分屏(即窗口纵向排列) :wincmd w 窗口切换 :close 关闭当前窗口 :only 关闭所有其他窗口 :q 退出当前窗口 当然,还有一些快捷键可以用,也很方便。这个请自行了解,因为我平时也不怎么用这些快捷键,也不大记的住。\n快捷键 关于快捷键,我们这里来单开一个章节来说明一下,之前也写过这方面的一些东西,如 [[vscode 插件 cve keymap 的开发记录]] 和 [[键位映射那些事儿]] 。这里,我们着重说明一下,在 vim 中如何方便的设计自己的键位映射。\n在 vim 中有个神奇的好东西,就是 \u0026lt;leader\u0026gt; ,强烈建议使用它来自定义你的键位。使用 emacs 的过程中,离不开 evil 插件的原因,很大程度上也是为了这个 \u0026lt;leader\u0026gt; 。\nvim 中在各种模式间进行切换的时候,会频繁使用到 \u0026lt;esc\u0026gt; 这个按键,它一般分布在键盘的左上角,很容易找到。也可以使用 \u0026lt;c-[\u0026gt; ,它与 \u0026lt;esc\u0026gt; 是等效的。很多朋友,也会把 ,, 映射成 \u0026lt;esc\u0026gt; ,看你爱好。\n我个人是使用 sharpkeys 软件(windows 系统),直接把键盘上的右 shift 键映射成了 esc ,如此全局通用。\n![[assets/pasted image 20230525223403.png]]\n如果,你使用的是 gnu/linux ,可以方便地使用 xmodmap 进行全局的键位映射,更多可以阅读 如何使用 xmodmap 工具进行映射 。\nok,让我们转回 \u0026lt;leader\u0026gt; ,以下是我的一些键位映射配置:\n![[assets/pasted image 20230525223408.png]]\n我把 \u0026lt;space\u0026gt; 空格键设置成了 \u0026lt;leader\u0026gt; 键,默认是 \\ 。如上图所示,我们用它实现了,上个章节中常用的文件及窗口操作。它有很多好处,方便记忆(毕竟是你自己设置的),还可以组合命令。如,我们使用 \u0026lt;space\u0026gt; jj 就可以先展示打开的文件列表,然后再供你输入想要跳转的 buffer 。\n更多配置,可以阅读我的 vim 配置 。\n结语 我自用的 vim ,配置的是很简单的,使用的功能也远不及 vim 所拥有功能的千分之一。如果感兴趣的话,不妨自己去亲自折腾一下。come on 🏃♂️ !\n","date":"2023-05-25","permalink":"https://aituyaa.com/vim-%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","summary":"\u003cp\u003eVim 是 Vi 的升级版,一款功能强大、高度可定制的文本编辑器,它只有一个对手 – Emacs 。那么,它来自哪里?\u003c/p\u003e","title":"vim 那些事儿"},]
[{"content":"开个坑,大点的……\n🔔 摘录自 廖雪峰老师的 java 教程\njavaee javaee 并不是一个软件产品,它更多的是一种软件架构和设计思想,是在 javase 的基础上,开发的一系列基于服务器的组件、api 标准和通用架构。\n![[assets/pasted image 20230525164712.png]]\njavaee 最核心的组件就是基于 servlet 标准的 web 服务器 ,开发者编写的应用程序是基于 servlet api 并运行在 web 服务器内部的。\n目前流行的基于 spring 的轻量级 javaee 开发架构,使用最广泛的是 servlet 和 jms(java message service),以及一系列开源组件。\nhttp server web 应用是 bs(browser/server)架构的,浏览器和服务器之间的传输协议是 http。http 协议是一个基于 tcp 协议之上的“请求-响应”协议。\n一个 http server 本质上是一个 tcp 服务器。 具体实现可以参考 编写一个简单的 http 服务器 。\nservlet 在 javaee 平台上,处理 tcp 连接,解析 http 协议这些底层工作统统扔给现成的 web 服务器去做,我们只需要把自己的应用程序跑在 web 服务器上。为了实现这一目的,javaee 提供了 servlet api, 我们只需要使用 servlet api 编写自己的 servlet 来处理 http 请求 ,web 服务器实现 servlet api 接口,实现底层功能。\n![[assets/pasted image 20230525164743.png]]\n我们来实现一个最简单的 servlet,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // webservlet 注解表示这是一个 servlet,并映射到地址 / @webservlet(urlpatterns = \u0026#34;/\u0026#34;) public class helloservlet extends httpservlet { @override protected void doget(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception { // 设置响应类型 resp.setcontenttype(\u0026#34;text/html\u0026#34;); // 获取输出流 printwriter ps = resp.getwriter(); // 写入响应 pw.writer(\u0026#34;\u0026lt;h1\u0026gt;hello, servlet!\u0026lt;/h1\u0026gt;\u0026#34;); // 最后不要忘记 flush 强制输出 pw.flush(); } } 一个 servlet 总是继承自 httpservlet ,然后覆写 doget() 或 dopost() 方法。\n思考一下:servlet api 是谁提供?\n普通的 java 程序是通过启动 jvm,然后执行 main() 方法开始运行。但是 web 应用程序有所不同,我们无法直接运行 war 文件,必须先启动 web 服务器,再由 web 服务器加载我们编写的 helloservlet ,这样就可以让 helloservlet 处理浏览器发送的请求。\n如此,一个完整的 web 应用程序的开发流程如下:\n1. 编写 servlet;\r2. 打包为 war 文件;\r3. 复制到 tomcat 的 webapps 目录下;\r4. 启动 tomcat。 这很繁琐!!!\ntomcat 实际上也是一个 java 程序,我们看看 tomcat 的启动流程:\n1. 启动 jvm 并执行 tomcat 的 main() 方法;\r2. 加载 war 并初始化 servlet;\r3. 正常服务。 启动 tomcat 无非就是设置好 classpath 并执行 tomcat 某个 jar 包的 main() 方法, 我们完全可以把 tomcat 的 jar 包全部引入进来,然后自己编写一个 main() 方法 ,先启动 tomcat,然后让它加载我们的 webapp 就行。如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class main { public static void main(string[] args) throws exception { // 启动 tomcat tomcat tomcat = new tomcat(); tomcat.setport(integer.getinteger(\u0026#34;port\u0026#34;, 8080)); tomcat.getconnector(); // 创建 webapp context ctx = tomcat.addwebapp(\u0026#34;\u0026#34;, new file(\u0026#34;src/main/webapp\u0026#34;).getabsolutepath()); webresourceroot resources = new standardroot(ctx); resources.addpreresources(new dirresourceset( resources, \u0026#34;/web-inf/classes\u0026#34;, new file(\u0026#34;target/classes\u0026#34;).getabsolutepath(), \u0026#34;/\u0026#34;) ); ctx.setresource(resources); tomcat.start(); tomcat.getserver().await(); } } 如此,我们直接运行 main() 方法,即可启动嵌入式 tomcat 服务器,然后,通过预设的 tomcat.addwebapp(\u0026quot;\u0026quot;, new file(\u0026quot;src/main/webapp\u0026quot;),tomcat 会自动加载当前工程作为根 webapp,可直接在浏览器访问 http://localhost:8080/ 。\n思考一下:如何引入 tomcat 的 jar 包呢?\n一个 web app 就是由一个或多个 servlet 组成的,每个 servlet 通过注解说明自己能处理的路径。 浏览器发出的 http 请求总是由 web server 先接收,然后,根据 servlet 配置的映射,不同的路径转发到不同的 servlet 。\n![[assets/pasted image 20230525164908.png]]\n重定向是指当浏览器请求一个 url 时,服务器返回一个重定向指令(如 resp.sendredirect(your_redirect_to_url); ),告诉浏览器地址已经变了,麻烦使用新的 url 再重新发送新请求(浏览器地址会显示新的 your_redirect_to_url )。\nforward 是指内部 转发 (地址栏不会变)。当一个 servlet 处理请求的时候,它可以决定自己不继续处理,而是转发给另一个 servlet 处理(如 req.getrequestdispatcher(your_forward_to_url).forward(req, resp); )。\n在 web 应用程序中,如何跟踪用户身份?\nhttp 协议是一个无状态协议,即 web 应用程序无法区分收到的两个 http 请求是否是同一个浏览器发出的。为了跟踪用户状态,服务器可以向浏览器分配一个唯一 id,并以 cookie 的形式发送到浏览器,浏览器在后续访问时总是附带此 cookie,这样,服务器就可以识别用户身份。\n我们把这种基于唯一 id 识别用户身份的机制称为 session,javaee 的 servlet 机制内建了对 session 的支持。如:重定向是指当浏览器请求一个 url 时,服务器返回一个重定向指令(如 resp.sendredirect (your_redirect_to_url); ),告诉浏览器地址已经变了,麻烦使用新的 url 再重新发送新请求(浏览器地址会显示新的 your_redirect_to_url )。\nforward 是指内部转发 (地址栏不会变)。当一个 servlet 处理请求的时候,它可以决定自己不继续处理,而是转发给另一个 servlet 处理(如 req.getrequestdispatcher (your_forward_to_url). forward (req, resp); )。\n在 web 应用程序中,如何跟踪用户身份?\nhttp 协议是一个无状态协议,即 web 应用程序无法区分收到的两个 http 请求是否是同一个浏览器发出的。为了跟踪用户状态,服务器可以向浏览器分配一个唯一 id,并以 cookie 的形式发送到浏览器,浏览器在后续访问时总是附带此 cookie,这样,服务器就可以识别用户身份。\n我们把这种基于唯一 id 识别用户身份的机制称为 session,javaee 的 servlet 机制内建了对 session 的支持。如:\n1 2 3 4 5 6 httpsession session = req.getsession(); // 存储信息到 httpsession session.setattribute(\u0026#34;user\u0026#34;, name); // 读取信息从 httpsession string user = (string) req.getsession().getattribute(\u0026#34;user\u0026#34;); 服务器识别 session 的关键就是依靠一个名为 jsessionid 的 cookie。在 servlet 中第一次调用 req.getsession() 时,servlet 容器自动创建一个 session id,然后通过一个名为 jsessionid 的 cookie 发送给浏览器。\n综上可知,servlet 提供的 httpsession 本质上就是通过一个名为 jsessionid 的 cookie 来跟踪用户会话的。下面我们来看看 cookie 的设置与读取:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 创建一个新的 cookie cookie cookie = new cookie(\u0026#34;lang\u0026#34;, lang); cookie.setpath(\u0026#34;/\u0026#34;); // 设置 cookie 生产的路径范围 cookie.setmaxage(8640000); // 设置 cookie 有效期 // 将该 cookie 添加到响应 resp.addcookie(cookie); // .... // 读取请求附带的所有 cookie cookie[] cookies = req.getcookies(); if (cookies != null) { for (cookie cookie : cookies) { if (cookie.getname().equals(\u0026#34;lang\u0026#34;)) { // 返回 cookie 的值 return cookie.getvalue(); } } } jsp jsp 是 java server pages 的缩写,它的文件必须放到 /src/main/webapp 下,文件名必须以 .jsp 结尾,整个文件与 html 并无太大区别,但需要插入变量,或者动态输出的地方,使用特殊指令 \u0026lt;% ... %\u0026gt; 。\n整个 jsp 的内容实际上是一个 html,但是稍有不同:\n包含在 \u0026lt;%-- 和 --%\u0026gt; 之间的是 jsp 的注释,它们会被完全忽略; 包含在 \u0026lt;% 和 %\u0026gt; 之间的是 java 代码,可以编写任意 java 代码; 如果使用 \u0026lt;%= xxx %\u0026gt; 则可以快捷输出一个变量的值。 jsp 页面内置了几个变量(可以直接使用):\nout:表示 httpservletresponse 的 printwriter; session:表示当前 httpsession 对象; request:表示 httpservletrequest 对象。 jsp 和 servlet 有什么区别?其实它们没有任何区别,因为 jsp 在执行前首先被编译成一个 servlet。\n访问 jsp 页面时,直接指定完整路径(而无需手动配置映射路径)。例如, http://localhost:8080/hello.jsp 。\n:: jsp 早已是明日黄花了……\nmvc 通过前面的章节可以看到:\nservlet 适合编写 java 代码,实现各种复杂的业务逻辑,但不适合输出复杂的 html; jsp 适合编写 html,并在其中插入动态内容,但不适合编写复杂的 java 代码。 能否将两者结合起来,发挥各自的优点,避免各自的缺点? – mvc !!!\n![[assets/pasted image 20230525165020.png]]\n使用 mvc 模式的好处是,controller 专注于业务处理,它的处理结果就是 model。 model 可以是一个 javabean,也可以是一个包含多个对象的 map ,controller 只负责把 model 传递给 view,view 只负责把 model 给“渲染”出来,这样,三者职责明确,且开发更简单,因为开发 controller 时无需关注页面,开发 view 时无需关心如何创建 model。\n:: controller 和 view 的纽带就是 model 喽(处理完之后转发即可)。🤨 emm… 这就是前后端分离的起点?\n通过结合 servlet 和 jsp 的 mvc 模式,我们可以发挥二者各自的优点:\nservlet 实现业务逻辑; jsp 实现展示逻辑。 但是,直接把 mvc 搭在 servlet 和 jsp 之上还是不太好,原因如下:\nservlet 提供的接口仍然偏底层,需要实现 servlet 调用相关接口; jsp 对页面开发不友好,更好的替代品是模板引擎; 业务逻辑最好由纯粹的 java 类实现,而不是强迫继承自 servlet。 思考一下:能不能通过普通的 java 类实现 mvc 的 controller? – 当然能 !\n如何设计一个 mvc 框架呢?\n![[assets/pasted image 20230525165040.png]]\n上图中, dispatcherservlet 以及 render(modelandview) (如何渲染)均由 mvc 框架实现,有了 mvc 框架,我们只需要编写 controller 就可以了。\n具体如何呢?且往下看。\n在 mvc 框架中创建一个 接收所有请求 的 servlet – dispatcherservlet ,它总是映射到 / ,然后根据不同的 controller 的方法定义的 @getmapping 或 @postmapping 的 path 决定调用哪个方法,最后获得方法返回的 modelandview 后,渲染模板,写入 httpservletresponse ,即完成了整个 mvc 的处理。\n我们的 dispatcherservlet 持有哪些信息呢?如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @webservlet(urlpatterns = \u0026#34;/\u0026#34;) public class dispatcherservlet extends httpservlet { // 持有存储请求路径到某个具体方法的映射 private map\u0026lt;string, getdispatcher\u0026gt; getmappings = new hashmap(); private map\u0026lt;string, postdispatcher\u0026gt; postmappings = new hashmap(); // 持有模板引擎 private viewengine viewengine; // ... @override public void init() throws servletexception { // dispatcherservlet 初始化时,扫描所有的 controller 中 // 带有 @getmappings 和 @postmappings 标记的方法 this.getmappings = scangetincontrollers(); this.postmappings = scanpostincontrollers(); this.viewengine = new viewengine(getservletcontext()); } // ... } 思考一下:如何扫描并获取? – 反射!\n是的,关于 dispatcherservlet ,其持有信息就是上面这些。\n下面,我们再深入一下,看看其中 getdispatcher 、 postdispatcher 、 viewengine 都是什么结构。\n处理一个 get 请求是通过 getdispatcher 对象完成的,它需要如下信息:\n1 2 3 4 5 6 class getdispatcher { object instance; // controller 实例 method method; // controller 方法 string[] parameternames; // 方法参数名称 class\u0026lt;?\u0026gt;[] parameterclasses; // 方法参数类型 } 类似的, postdispatcher 需要如下信息:\n1 2 3 4 5 6 class postdispatcher { object instance; // controller 实例 method method; // controller 方法 class\u0026lt;?\u0026gt;[] parameterclasses; // 方法参数映射 objectmapper objectmapper; // json 映射 } viewengine 其实非常简单,只需要实现一个简单的 render() 方法,如下:\n1 2 3 4 5 6 7 8 9 10 public class viewengine { public void render(modelandview mv, writer writer) throws ioexception { string view = mv.view; map\u0026lt;string, object\u0026gt; model = mv.model; // 根据 view 找到模板文件 template template = gettemplatebypath(view); // 渲染并写入 wrinter template.write(writer, model); } } 其中 modelandview 定义也比较简单,如下:\n1 2 3 4 public class modelandview { map\u0026lt;string, object\u0026gt; model; // 一个 javabean 或一个包含多个对象的 map string view; // 模板的路径 } 看,只要有了 mvc 框架,我们只需要编写包含返回 modelandview 对象方法的 controller 类就可以了。\n详细代码参考 - 实现一个 mvc 框架 。\n这个 mvc 框架主要参考就是 spring mvc,通过实现一个“简化版”mvc,可以掌握 java web mvc 开发的核心思想与原理。\n:: 当然,在实际工作中,已经有像 spring mvc 这种比较成熟的框架了,但是了解原理是重要的,比只会简单的运用要重要的多。\nspring mvc 首先,来看一个标准的 maven web 工程目录结构示例,如下:\n![[assets/pasted image 20230525165151.png]]\n其中, src/main/webapp 是标准 web 目录, web-inf 存放 web.xml ,编译的 class,第三方 jar,以及不允许浏览器直接访问的 view 模版, static 目录存放所有静态文件。\n在 src/main/resources 目录中存放的是 java 程序读取的 classpath 资源文件,除了 jdbc 的配置文件 jdbc.properties 外,我们又新增了一个 logback.xml ,这是 logback 的默认查找的配置文件。\n在 src/main/java 中就是我们编写的 java 代码了。\n在前面已经讲过了 java web 的基础:servlet 容器,以及标准的 servlet 组件:\nservlet:能处理 http 请求并将 http 响应返回; jsp:一种嵌套 java 代码的 html,将被编译为 servlet; filter:能过滤指定的 url 以实现拦截功能; listener:监听指定的事件,如 servletcontext、httpsession 的创建和销毁。 servlet 容器为每个 web 应用程序自动创建一个唯一的 servletcontext 实例,这个实例就代表了 web 应用程序本身。\n我们知道 spring 提供的是一个 ioc 容器,所有的 bean ,包括 controller,都在 spring ioc 容器中初始化,而 servlet 容器由 javaee 服务器(如 tomcat)提供,它对 spring 一无所知。\nweb 应用程序总是由 servlet 容器创建,那么,spring 容器应该由谁创建?在什么时候创建?spring 容器中的 controller 又是如何通过 servlet 调用的?\n在 web 应用中启动 spring 容器有很多种方法,可以通过 lister 启动,可以通过 servlet 启动,可以使用 xml 配置,也可以使用注解配置。这里,我们介绍一种最简单的启动 spring 容器的方式。\n第一步,在 web.xml 中配置 spring mvc 提供的 dispatcherservlet :\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 \u0026lt;!doctype web-app public \u0026#34;-//sun microsystems, inc.//dtd web application 2.3//en\u0026#34; \u0026#34;http://java.sun.com/dtd/web-app_2_3.dtd\u0026#34; \u0026gt; \u0026lt;web-app\u0026gt; \u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcher\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;org.springframework.web.servlet.dispatcherservlet\u0026lt;/servlet-class\u0026gt; \u0026lt;!-- 1.初始化参数 contextclass 指定使用注解配置的 annotationconfigwebapplicationcontext --\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;contextclass\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;org.springframework.web.context.support.annotationconfigwebapplicationcontext\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;!-- 2.配置文件的位置参数 contextconfiglocation 指向 appconfig 的完整类名 --\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;contextconfiglocation\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;com.itranswarp.learnjava.appconfig\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;load-on-startup\u0026gt;0\u0026lt;/load-on-startup\u0026gt; \u0026lt;/servlet\u0026gt; \u0026lt;!-- 3.把这个 servlet 映射到 /*,即处理所有 url --\u0026gt; \u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcher\u0026lt;/servlet-name\u0026gt; \u0026lt;url-pattern\u0026gt;/*\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; \u0026lt;/web-app\u0026gt; 上述配置可以看作一个样板配置,有了这个配置,servlet 容器会首先初始化 spring mvc 的 dispatcherservlet ,在 dispatcherservlet 启动时,它根据配置 appconfig 创建一个类型是 webapplicationcontext 的 ioc 容器,完成所有 bean 的初始化,并将容器绑到 servletcontext 上。\n因为 dispatcherservlet 持有 ioc 容器,能从 ioc 容器中获取所有 @controller 的 bean,因此, dispatcherservlet 接收到所有 http 请求后,根据 controller 方法配置的路径,就可以正确把请求转发到指定方法,并根据返回的 modelandview 决定如何渲染页面。\n我们已经知道了如何结合 servlet 容器和 spring 容器,那么,如何配置一个可用的 spring mvc 呢?\n和普通 spring 配置一样,我们编写正常的 appconfig 后,只需要加上 @enablewebmvc 注解,就“激活”了 spring mvc,如下:\n1 2 3 4 5 6 7 8 @configuration @componentscan @enablewebmvc // 启用 spring mvc @enabletransactionmanagement @propertysource(\u0026#34;classpath:/jdbc.properties\u0026#34;) public class appconfig { // ... } 当然,还需要创建 datasource 、 jdbctemplate 、 platformtransactionmanager 等 bean 。\n其中,有一个必须要创建的 bean 是 viewresolver ,因为 spring mvc 允许集成任何模板引擎,使用哪个引擎,就实例化一个对应的 viewresolver :\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @bean viewresolver createviewresolver(@autowired servletcontext servletcontext) { pebbleengine engine = new pebbleengine.builder().autoescaping(true) .cacheactive(flase) .loader(new servletloader(servletcontext)) .extension(new springextension()) .build(); pebbleviewresolver viewresolver = new pebbleviewresolver(); // viewresolver 通过指定 prefix 和 suffix 来确定如何查找 view viewresolver.setprefix(\u0026#34;/web-inf/templates/\u0026#34;); viewresolver.setsuffix(\u0026#34;\u0026#34;); viewresolver.setpebbleengine(engine); return viewresolver; } 一切完备之后,就可以编写我们自己的 controller 了,如下:\n1 2 3 4 5 6 7 8 9 @controller // 注意是 @controller ,不是 @component @requestmapping(\u0026#34;/user\u0026#34;) // 对 url 分组(推荐) public class usercontroller { @getmapping(\u0026#34;/profile\u0026#34;) // 实际 url 映射是 /user/profile public modelandview profile() { // ... } } 实际方法的 url 映射总是前缀+路径,这种形式还可以有效避免不小心导致的重复的 url 映射。\n注意,返回的 modelandview 通常包含一个 view 的路径和一个 map 作为 model,但也可以没有 model ,如 return new modelandview(\u0026quot;signin.html\u0026quot;) 。返回重定向时既可以写 new modelandview(\u0026quot;redirect:/signin\u0026quot;) ,也可以直接返回 string ,如 return \u0026quot;redirect:/signin\u0026quot; 。\n如果在 controller 方法内部直接操作 httpservletresponse 发送响应,返回 null 表示无需进一步处理。如下:\n1 2 3 4 5 6 7 8 public modelandview download(httpservletresponse response) { byte[] data = ...; response.setcontenttype(\u0026#34;application/octet-stream\u0026#34;); outputstream output = response.getoutputstream(); output.write(data); output.flush(); return null; } 详细源码参考 使用 spring mvc 。\nrest 使用 spring mvc 开发 web 应用程序的主要工作就是编写 controller 逻辑。\n在 web 应用中,除了需要使用 mvc 给用户显示页面外,还有一类 api 接口,我们称之为 rest,通常输入输出都是 json,便于第三方调用或者使用页面 javascript 与之交互。\n直接在 controller 中处理 json 是可以的,因为 spring mvc 的 @getmapping 和 @postmapping 都支持指定输入和输出的格式。然而 直接用 spring 的 controller 配合一大堆注解写 rest 太麻烦了 ,如下:\n1 2 3 4 5 6 7 @postmapping(value = \u0026#34;/rest\u0026#34;, // 路径 consumes = \u0026#34;application/json;charset=utf-8\u0026#34;, // 声明能接收的类型 produces = \u0026#34;application/json;charset=utf-8\u0026#34;) // 声明输出的类型 @responsebody // !!! public string rest(@requestbody user user) { // !!! return \u0026#34;{\\\u0026#34;restsupport\\\u0026#34;:true}\u0026#34;; // 返回 json } 其中, @responsebody 表示返回的 string 无需额外处理,直接作为输出内容写入 httpservletresponse ,输入的 json 则根据注解 @requestbody 直接被 spring 反序列化为 user 这个 javabean 。\n太麻烦了,怎么办呢?\nspring 还额外提供了一个 @restcontroller 注解,使用 @restcontroller 替代 @controller 后,每个方法自动变成 api 接口方法。如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @restcontroller @requestmapping(\u0026#34;/api\u0026#34;) public class apicontroller { @autowired userservice userservice; @getmapping(\u0026#34;/users\u0026#34;) public list\u0026lt;user\u0026gt; users() { return userservice.getusers(); } @getmapping(\u0026#34;/users/{id}\u0026#34;) public user user(@pathvariable(\u0026#34;id\u0026#34;) long id) { return userservice.getuserbyid(id); } @postmapping(\u0026#34;/signin\u0026#34;) public map\u0026lt;string, object\u0026gt; signin(@requestbody signinrequest signinrequest) { try { user user = userservice.signin(signinrequest.email, signinrequest.password); return map.of(\u0026#34;user\u0026#34;, user); } catch (exception e) { return map.of(\u0026#34;error\u0026#34;, \u0026#34;signin_failed\u0026#34;, \u0026#34;message\u0026#34;, e.getmessage()); } } public static class signinrequest { public string email; public string password; } } 编写 rest 接口只需要定义 @restcontroller ,然后,每个方法都是一个 api 接口,输入和输出只要能被 jackson 序列化或反序列化为 json 就没有问题。\n另外,还有 @jsonignore 和 @jsonproperty(access = access.write_only) 等有意思的注解,它们有什么作用呢?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class user { // 避免 user 序列化为 json 时,暴露 password 属性, 但是 // 这会导致想写入 password 时也找不到了 @jsonignore public string getpassword() { return password; } // 这种就比较好了,只需写入,禁用读取(如, 注册账号时) @jsonproperty(access = access.write_only) public string getpassword() { return password; } } 详细代码参考 使用 rest 实现 api 。\n:: 虽然,后续 spring-boot 为我们的开发带来了革命性的变化,但 spring、mvc 这些基本的思想,才是根本,前者只是把一些东西自动化了,计算机帮你做了你可以不做的。\n","date":"2023-05-25","permalink":"https://aituyaa.com/web-%E5%BC%80%E5%8F%91/","summary":"\u003cp\u003e开个坑,大点的……\u003c/p\u003e\n\u003cp\u003e🔔 摘录自 \u003ca href=\"https://www.liaoxuefeng.com/wiki/1252599548343744/1255945497738400\"\u003e廖雪峰老师的 Java 教程\u003c/a\u003e\u003c/p\u003e","title":"web 开发"},]
[{"content":"本文不涉及具体的细节,浅谈一下 web 前端的 1 2 3 ……\n前端是什么 在当下的产品开发流程中,大致分为以下几个过程:\n1. 产品设计(需求分析)\r2. 技术选型\r3. 前端负责界面渲染、交互 \u0026amp; 后端负责数据处理\r4. 测试(功能)\r5. 运维部署 这里,我们只关注第 3 点,即具体的开发过程。\n要理解‘什么是前端’,最好回顾一下 web 的发展史。这是世界上第一个网站 http://info.cern.ch/ ,于 1991.8.6 上网,它解释了万维网是什么,如何使用网页浏览器和如何建立一个网页服务器等等。\n你会发现,这个页面就是一份简单的 .html 文件,让我们来看一下它的实现过程。\n- 伯纳斯·李(互联网之父)写了一个 html 文档,\r- 然后把这个文档放在连接着网络的一台电脑上(服务器),\r- 用户电脑通过客户端(通常是浏览器)连网访问它。 看,最初的时候,只有 html ,它就是一个文本文件,没有交互,用户只能浏览。这个时候,也没有什么前后端的概念。开发人员需要做的,就是编写 html 文档,并把它放到可以被访问的服务器上。\n用户或开发人员,感觉单纯的 html 文件在浏览器上显示出来不好看,于是有了 css 来装饰样式(1995),我们进入了 html + css 的时代。\n目前为止,用户还是只能看啊,用户说:“我要交互!我要交互!我要交互!”\n伯纳斯说:“给你表单!给你表单!给你表单!”\n现在用户可以通过表单,输入一些信息(比如登录),点击按钮,来和服务器进行一定的交互了。同样,服务器端也有同样的程序来处理用户提交的表单信息。此时,一切都还是简单的!\n直到有一天,用户又不满意了,大声说:“我要更多的交互!更多!更多!”\n网景公司(火狐浏览器前身)委托 brendan eich 开发了 javascript 语言,用于满足用户的需求。 brendan 只花了 10 天时间,太快了,所以 javascript 有许多的语言缺陷,但又阴差阳错地成了浏览器最钟爱的(内置了的)脚本语言,简直了。有些事,就是那么不讲道理。\n我们现在进入了 html + css + js 的时代!\n你还没说什么是前端呢?\n别着急,就要说了。最初的时候,开发任务不算很重,一个开发人员就可以完成 .html、 .css、 .js 文件及服务器上处理用户请求的程序的编写。\n这时,还是没有区分前端、后端,因为都是一个人写的!\n后来,网页内容越来越丰富,用户的交互越来越多,服务器端处理的请求越来越复杂。于是,程序员 b 对程序员 f 说:“我来处理用户提交的信息,生成展示所需的数据,你负责编写文档(html、css、js 等)文件,把数据插入到 html 中,返回给用户的浏览器来展示。”\n于是乎,程序员 b 只负责处理用户请求,程序员 f 负责页面渲染,提高了效率。慢慢地,也就有了专职于后端(back end)和专职于前端(front end)的方向。\n现在,我们明白了,所谓‘前端’,主要就是负责展示部分,最终的交付便是用于用户端浏览器可以识别的页面文件。\n近些年来,前端发展可谓是如火如荼,页面的交互也越来越复杂,各种框架层出不穷,让人眼花缭乱。但它的本质,从来就没有改变过。最终, 前端的核心,就是生成用户浏览器可以渲染的页面文件。\n重点来了,一是浏览器,二是页面文件。\n浏览器 说起浏览器,大家肯定都不陌生,如 chrome、 edge、 firefox、 ie ……\n浏览器只认识 html !\n如果,深入了解,就会发现浏览器实在是一个复杂的软件,好在我们只需要应用它。更多关于浏览器的渲染细节,可以参考 浏览器 这篇文章。\n对于一位前端开发人员来说,现在是幸福的,因为有了浏览器开发工具(一般现代浏览器通过 f12 打开),你可查看页面的请求过程和渲染细节,很方便地开发和调试项目页面。\n页面文件 再厉害的框架,最终都要编译打包成 html + css + js , 因为浏览器只认这个组合!\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;页面文件\u0026lt;/title\u0026gt; …… \u0026lt;link href=\u0026#34;/path/to/style.css\u0026#34; /\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;关于页面文件\u0026lt;/h1\u0026gt; \u0026lt;div class=\u0026#34;description\u0026#34;\u0026gt; \u0026lt;p\u0026gt;再厉害的框架,最终都要编译打包成 `html + css + js` 组合。\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;……\u0026lt;/p\u0026gt; …… \u0026lt;/div\u0026gt; …… \u0026lt;script src=\u0026#34;/path/to/main.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 一个 .html 文件,通过 \u0026lt;link\u0026gt; 标签引入 .css 文件,通过 \u0026lt;script\u0026gt; 标签引入 .js 文件,这就是页面文件的基本结构。\nhtml html ,全称‘超文本标记语言’(hyperext markup language) ,是一种用于创建网页的标准标记语言。\n为什么需要‘标记’呢?\n你直接对浏览器讲中文,它是听不懂的!你必须跟它讲 html 语言,它才能懂你要干什么。\nhtml 语言很简单,你只需要用相应的标签来包裹你想要浏览器渲染的内容,就可以了。比如,段落标签 \u0026lt;p\u0026gt; ,分割线标签 \u0026lt;hr\u0026gt;,标题标签 \u0026lt;title\u0026gt; ,引入样式文件的标签 \u0026lt;link\u0026gt; ,引入脚本文件的标签 \u0026lt;script\u0026gt; 等等。\n在实际学习的过程中,有一些标签很常用,有一些标签几乎不用,边学边用即可!\ncss css,全称‘层叠样式表’(cascading style sheets),是一种用来为结构化文档(如 html)添加样式(字体、间距和颜色等)的计算机语言。\ncss 中最重要的一个概念就是‘选择器’了,即选中你要添加样式的页面元素! ……\njavascript 前端领域, javascript 可谓是绝对的主角(后续简称 js),它是目前主流浏览器(所有)指定的唯一脚本语言。\n最初,它只是为了满足用户日益俱增的交互需求,现在也是,只不过更加的强大和复杂。但本质上,仍然是服务于 html 的,这一点从未改变。\njs 的核心语法很简单,如变量、函数及日常应用过程中常用到的数组、对象等数据结构。\n缺陷很多,但不影响 js 登大雅之堂!可以这样说,精通 js 的过程,就是一场学习如何‘踩坑’的旅程,开个玩笑,其实现在好多了。\n框架 js 是通过什么来指挥浏览器的呢?\n浏览器提供了一系列的接口 api ,与 js 最紧密的便是 dom 了。最初的时候,开发人员也是这样做的,通过 html 编写好页面内容,通过 css 来添加样式,通过 js 操作浏览器提供的 api 接口来操作 dom 。\ndom (document object model) 文档对象模型,是浏览器解析 .html 文件后生成的一个树形结构,用于渲染页面。\n简单的页面,这样做很容易。\n问题就在于,现在的页面越来越不简单了……\n于是框架就上场了!看,有‘需求’,就有‘市场’!\njquery 与其说 jquery 是一个框架,不如说,它是一个函数库。本质上来说,它是对原生 js 的一种封装,用来更方便地操作 dom 。对,你仍然需要操作 dom !\n现在用的不多了,在使用中了解即可。\nvue \u0026amp; react 这两个前端框架,是目前最热门、最流行的前端框架了。除了,实现原理的某些部分有些不同外,其余应用都是大同小异。学习了其中一种,再学习另一种,也会很快。\n这里,我们以 vue 为主,某些方面对比 react 来说。不得不说,vue 对于初学者是更加友好的,性能也很不错!国内来说,流行程度更胜一筹。\n首先,需要明白,vue 本身并没有为 html + css + js 引入什么新的东西,你所编写的 vue 相关的模板文件 .vue、 样式文件 .scss、 逻辑文件 .js ,最终经过编译打包后,还是 html + css + js 这一套组合!\n也就是说,你最终,放在服务器上,供用户访问的页面文件,始终都是 html !\n学习 vue ,究竟学什么? 模板语法!!!\n是的,和你学习 html ,没什么太大的区别,正如你学习 html 是为了让浏览器知道你的说什么,学习 vue 的模板语法也是为了让 vue 相关的编译器、打包器知道你要它们干什么!仅此而已!\n你按着它规定的语法来编写 .vue、 .scss、 .js ,对应的编译器、打包器就知道如何把你编写的内容转成 html + css + js 供浏览器渲染绘制页面。\n我们不止一次提到了编译器和打包器,它们是什么呢?有什么作用?\n编译器\n拿 vue 来说,你编写的是组件是 app.vue ,浏览器是不认识的,经过编译器编译就成了 *.js ,就可以引入到 *.html 中供浏览器解析了。可以认为编译器就是一个‘加工厂’,进去的是 .vue ,出来的是 .js 。\n看,编译器并不是什么神奇的东东,它本质上来说,就是个‘翻译机’。\n编译器不需要应用开发者参与,它是由框架实现的,开发者只需要按着框架给出的‘写法’,编译器就能读懂,并正确转译。\n打包器\n打包器呢?为什么需要打包器?因为随着前端内容越来越复杂,把所有的逻辑写在一个 .js 文件中对于程序员来说是不友好的。比较典型的方式,就是通过模块化编程来避开这一点。也就是说,我们会把交互逻辑分散到不同的 .js 文件中,方便编写和复用。如此,在一个项目中,我们使用的资源分分散在项目的不同层级之中,有 .js ,有 .css 等等。最终使用的时候,打包器会根据它们之间的依赖关系,将这些资源再合并到一起,引入到 .html 中,供浏览器渲染。\n是的,打包器就像一个‘拼接手’,按照资源(模块)的依赖关系,把零碎的‘资源’合成一个整体。\n当前,比较流行的打包器仍然是 webpack ,你需要学习并了解它,同样的,边学习,边使用,在使用中学习。\n另外,你可能也需要了解一下 rollup ,它也是一种打包工具,最新的 vue3 推荐的脚手架 vite (项目初始化工具)就是集成了 rollup 。\n同样的,所有的打包工具,实现方式可能各有不同,但其最终目的和思想,都是大同小异的。\n其他 模块和组件 在 模块化编程 中,我们具体说明过模块相关的概念,可以参考一下。\n什么是模块?什么是组件?\n很多时候,这两个概念的划分不是很清楚,甚至是通用的。比如,一个 .vue 文件,可以称为是一个组件,一个完全的 vue 应用其实就是许多组件组成的‘组件树’。\n模块,多是缩写的 .js 文件,每个模块实现了不同的功能,可以通过相应的方式引入别的模块,及暴露自身供外部模块使用的功能。它是一个封装的概念,比如模块 a 引入了模块 b(当然也可不引入),并包含 1、 2、 3 三个功能,模块 a 可以选择只暴露 1、 3 两个功能给外部使用。\n在编写 vue 组件的过程中,我们常会引用到不同的功能模块,比如,我们引入一个时间模块,用来格式化当前的时间,引入一个排序模块,用来给某个数组进行排序等等。\n预编译器 什么是预编译器?顾名思义,它也是一种编译器,一般用于资源打包前,比如将 .scss 样式文件编译成 .css 文件。 dart 就是目前使用最多的一种 scss 的预编译器,只需要在打包工具中,配置好,打包过程中,就会自动完成这个转译的过程。\n扩展一下,就会发现,程序这个东东经常做这种转来转去的东西。一方面为了程序员编写方便,维护方便,另一方面为了编译器、计算机能理解执行。\n一度连 .html 文件都有相应的编译器,如 jade ,现在不怎么使用了。\n后端是什么 在前后端分离之后,后端方面主要就是接受用户在前端发出的请求,分析处理,该请求数据库的请求数据库,该拒绝响应的拒绝响应,把相应的处理结果返回给前端,前端使用这些数据进行页面渲染或其它处理。\n有一点需要注意的是,无论是后端的服务包,还是前端的项目包,都是需要部署在服务器端的,用户只是通过浏览器访问它们。\n当然,后端开发也经历了一系列的变革,有了不少好用的框架工具,比如 java 领域集大成的 spring 家族框架。\n关于 node node 让 js 也可以在服务器端大展拳脚,它与用在浏览器中的 js 有什么区别呢?其实,语言本身没有什么很大的区别,毕竟基本上都遵守 ecmascript 标准。它们主要是宿主(运行环境)不同,nodejs 的宿主是 v8 ,前端 js 的宿主则是浏览器,仅些而已。\n内容看着不少,其实,刚开始的时候,只需要学习一下 npm 包管理方面的知识就可以了。多数时候,只需要使用它安装项目所需要的第三方工具包。\n学习路线 - html、 css、 js 的基本使用\r- npm 的相关概念\r- vuejs 的模板语法及结构\r- webpack 的了解 结语 以上内容,只是一个大概的、过程性的描述,并没有牵扯到具体的开发细节。但是,理解需要学习什么,为什么学习,也是一件重要的事情。我们对于不熟悉的事物,多数时候有些过于‘敬畏’,这是正常的,一旦揭开了它的面纱,就会发现,一切不过如此。\n","date":"2023-05-25","permalink":"https://aituyaa.com/%E5%89%8D%E7%AB%AF%E6%B5%85%E8%B0%88/","summary":"\u003cp\u003e本文不涉及具体的细节,浅谈一下 Web 前端的 1 2 3 ……\u003c/p\u003e","title":"前端浅谈"},]
[{"content":"i.e. reflection and generic\n反射与泛型那些事儿……\n反射 反射,i.e.reflection,java 的反射是指程序在运行期可以拿到一个对象(实例)的所有信息。\n正常情况下,如果我们要调用一个对象的方法,或者访问一个对象的字段,通常会传入对象实例。反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。\n除了 int 等基本类型外,java 的其他类型全部都是 class (包括 interface ), class 的本质是数据类型。\nclass 在由 jvm 在执行过程中动态加载的。\njvm 在第一次读取到一种 class 类型时,将其加载进内存。每加载一种 class ,jvm 就为其创建一个 class 类型的实例,并关联起来。\n注意:这里的 class 类型是一个类名为 class 的 class (即 class.class ) ,它长这样:\n1 2 3 public final class class { private class() {} } 以 string 类为例,当 jvm 加载 string 类时,它首先读取 string.class 文件到内存,然后,为 string 类创建一个 class 实例并关联起来:\n1 class cls = new class(string); 这个 class 实例是由 jvm 内部创建的,其构造方法是 private ,只有 jvm 能创建 class 实例,我们自己的 java 程序是无法创建 class 实例的。\n所以,jvm 持有的每个 class 实例都指向一个数据类型( class 或 interface ),一个 class 实例包含了该 class 的所有完整信息:\n![[assets/pasted image 20230525165823.png|400]]\n由于 jvm 为每个加载的 class 创建了对应的 class 实例,并在实例中保存了该 class 的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个 class 实例,我们就可以通过这个 class 实例获取到该实例对应的 class 的所有信息。\n这种通过 class 实例获取 class 信息的方法称为反射(reflection)。\n如何获取一个 class 的 class 实例呢?\n1 2 3 4 5 6 7 8 9 10 11 12 // 方法一 // 直接通过一个 class 的静态变量 class 获取 class cls = string.class; // 方法二 // 通过该实例变量(如果有一个实例变量)提供的 getclass() 方法获取 string s = \u0026#34;hello\u0026#34;; class cls = s.getclass(); // 方法三 // 通过静态方法(如果知道一个 class 的完整类名) class.forname() 获取 class cls = class.forname(\u0026#34;java.lang.string\u0026#34;); jvm 在执行 java 程序的时候,并不是一次性把所有用到的 class 全部加载到内存,而是第一次需要用到 class 时才加载。\n泛型 泛型是一种“代码模板”,可以用一套代码套用各种类型。\n在讲解什么是泛型之前,我们先观察 java 标准提供的 arraylist ,可以看作“可变长度”的数组,实际上它内部就是一个 object[] 数组,配合存储一个当前分配的长度。\n1 2 3 4 5 6 7 public class arraylist { private object[] array; private int size; public void add(object e) { ... } public void remove(int index) { ... } public object get(int index) { ... } } 如上所示,arraylist 的 get 方法返回的是一个 object 类型的数据。如此,当你用它存储 string 类型的时候,获取的结果其实需要强制转型(由 object 转型为 string )。如下:\n1 2 3 4 5 6 7 8 arraylist list = new arraylist(); list.add(\u0026#34;hello\u0026#34;); // 获取到 object ,必须强制转型为 string string first = (string) list.get(0); list.add(new interger(123)); // error: classcastexception string second = (string) list.get(1); 并且很容易出现误转型 classcastexception 。\n要解决上述问题,我们可以为 string 单独编写一种 arraylist :\n1 2 3 4 5 6 7 public class stringarraylist { private string[] array; private int size; public void add(string e) { ... } // 存 string public void remove(int index) { ... } public string get(int index) { ... } // 取 string } 存取 string 的问题暂时解决了,然而存取 integer 、 person 等其他类型呢?当然,我们可以用上述方式为其创建对应的类,然而实际上这实际上是不科学的,太多了。\n为了解决这个新的问题,我们必须把 arraylist 变成一种模板: arraylist\u0026lt;t\u0026gt; ,代码如下:\n1 2 3 4 5 6 7 public class arraylist\u0026lt;t\u0026gt; { private t[] array; private int size; public void add(t e) { ... } public void remove(int index) { ... } public t get(int index) { ... } } 如此,我们在存取 string 类型的时候,就可以用 arraylist\u0026lt;string\u0026gt; 了,如下:\n1 2 3 4 5 6 arraylist\u0026lt;string\u0026gt; strlist = new arraylist\u0026lt;string\u0026gt;(); strlist.add(\u0026#34;hello\u0026#34;); // ✔ string s = strlist.get(0); // ✔ strlist.add(new integer(123)); // ✘ compile error integer n = strlist.get(0); // ✘ compile error :: 看,泛型,其实是一种更高层次上的抽象。\n通常来说,泛型类一般用在集合类中,例如 arraylist\u0026lt;t\u0026gt;,我们很少需要编写泛型类。那么,java 语言是如何实现泛型的呢?\n擦拭法 java 语言的泛型实现方式是擦拭法(type erasure),虚拟机对泛型其实一无所知,所有的工作都是编译器做的。\n例如,我们编写了一个泛型类 pair\u0026lt;t\u0026gt; ,下面是编译器看到的(我们编写的)代码:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class pair\u0026lt;t\u0026gt; { private t first; private t last; public pair(t first, t last) { this.first = first; this.last = last; } public t getfirst() { return first; } public t getlast() { return last; } public void setfirst(t first) { this.first = first; } public void setlast(t last) { this.last = last; } } 虚拟机可看不到上述代码!它也不认识!编译器把上面的代码,编译成了下面这样:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class pair { private object first; private object last; public pair(object first, object last) { this.first = first; this.last = last; } public object getfirst() { return first; } public object getlast() { return last; } public void setfirst(object first) { this.first = first; } public void setlast(object last) { this.last = last; } } 编译器直接把 \u0026lt;t\u0026gt; 擦拭掉了,把所有类型 \u0026lt;t\u0026gt; 视为 object !它只是在使用编写的泛型类时,根据 \u0026lt;t\u0026gt; 将取得的值进行安全的强制转型。\n比如,我们这样使用编写的泛型类(编译器看到的代码):\n1 2 3 pair\u0026lt;string\u0026gt; p = new pair\u0026lt;\u0026gt;(\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;); string first = p.getfirst(); string last = p.getlast(); 编译器会将上面这段代码中所取的值进行强制转型(虚拟机看到的代码):\n1 2 3 pair p = new pair(\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;); string first = (string) p.getfirst(); string last = (string) p.getlast(); 是的,java 就这样实现了泛型,并不难!对于我们来说,不嫌麻烦的话,你完全可以不使用泛型,自己来做编译器做的这些活。\n📣 题外话:java 引入了泛型,所以,只用 class 来标识类型已经不够了。实际上,java 的类型系统结构如下:\n![[assets/pasted image 20231101142648.png]]\n通配符 我们在阅读源码的时候,经常会看到类似 \u0026lt;? extends number\u0026gt; 或是 \u0026lt;? super integer\u0026gt; 等的代码,它们是什么?有什么作用?\n我们称之为通配符。先来看一下,为什么需要它们。\n我们仍然使用上个章节中编写的泛型类 pair\u0026lt;t\u0026gt; ,现在我们要针对 pair\u0026lt;number\u0026gt; 类型写了一个静态方法,它接收的参数类型是 pair\u0026lt;number\u0026gt;:\n1 2 3 4 5 6 7 public class pairhelper { static int add(pair\u0026lt;number\u0026gt; p) { number first = p.getfirst(); number last = p.getlast(); return first.intvalue() + last.intvalue(); } } 在使用的时候,我们传入如下代码,运行是没有任何问题的:\n1 int sum = pairhelper.add(new pair\u0026lt;number\u0026gt;(1, 2)); 注意,传入的类型是 pair\u0026lt;number\u0026gt;,实际参数类型是 (integer, integer)。那如果我们像下面这样,直接传入 pair\u0026lt;integer\u0026gt; 可不可以呢?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class pairhelper { public static void main(string[] args) { // 生成一个 pair\u0026lt;integer\u0026gt; 实例,然后传入 add 方法使用它 pair\u0026lt;integer\u0026gt; p = new pair\u0026lt;\u0026gt;(123, 456); int n = add(p); // ❌ 编译会报错 system.out.println(n); } static int add(pair\u0026lt;number\u0026gt; p) { // ❌ number first = p.getfirst(); number last = p.getlast(); return first.intvalue() + last.intvalue(); } } 结果编译的时候,就抛出了一个错误:\nincompatible types: pair\u0026lt;integer\u0026gt; cannot be converted to pair\u0026lt;number\u0026gt; 想想也是,pair\u0026lt;integer\u0026gt; 不是 pair\u0026lt;number\u0026gt; 的子类,因此,add(pair\u0026lt;number\u0026gt;) 不接受参数类型 pair\u0026lt;integer\u0026gt;。\n但是从 add() 方法的代码可知,传入 pair\u0026lt;integer\u0026gt; 是完全符合内部代码的类型规范,因为语句:\n1 2 number first = p.getfirst(); number last = p.getlast(); 实际类型是 integer,引用类型是 number,没有问题。\n问题在于方法参数类型定死了只能传入 pair\u0026lt;number\u0026gt;!那么,有没有办法使得方法参数接受 pair\u0026lt;integer\u0026gt;?有的 —— 通配符!\n下面我们来修改一下上面的 add() 方法,如下:\n1 2 3 4 5 6 7 8 9 10 11 public class pairhelper { pair\u0026lt;integer\u0026gt; p = new pair\u0026lt;\u0026gt;(123, 456); int n = add(p); // ✔️ 通过了 system.out.println(n); static int add(pair\u0026lt;? extends number\u0026gt; p) { // ✔️ \u0026lt;? extends number\u0026gt; number first = p.getfirst(); number last = p.getlast(); return first.intvalue() + last.intvalue(); } } 如此,给方法传入 pair\u0026lt;integer\u0026gt; 类型时,它符合参数 pair\u0026lt;? extends number\u0026gt; 类型。\n这种使用类似 \u0026lt;? extends number\u0026gt; 的泛型定义称之为上界通配符(upper bounds wildcards),即把泛型类型 t 的上界限定在 number 了。\n如果我们考察对 pair\u0026lt;? extends number\u0026gt; 类型调用 getfirst() 方法,实际的方法签名变成了:\n\u0026lt;? extends number\u0026gt; getfirst(); 即返回值是 number 或 number 的子类,因此,可以安全赋值给 number 类型的变量。那能不能赋值给 integer 类型的变量呢?不行!因为 \u0026lt;? extens number\u0026gt; 不是只允许你传入 integer 类型的 实例,还允许你什么 double 等其他 number 的子类(包括 number 本身)的实例,这就导致上述 getfirst() 方实际返回类型可能是 integer,也可能是 double 或者其他类型。\n‘读’是没有问题了,‘写’呢?比如:\n1 2 3 4 5 6 7 8 9 10 11 12 13 public class pairhelper { pair\u0026lt;integer\u0026gt; p = new pair\u0026lt;\u0026gt;(123, 456); int n = add(p); system.out.println(n); static int add(pair\u0026lt;? extends number\u0026gt; p) { number first = p.getfirst(); number last = p.getlast(); p.setfirst(new integer(first.intvalue() + 100)); // ❌ p.setlast(new integer(last.intvalue() + 100)); return p.getfirst().intvalue() + p.getfirst().intvalue(); } } 编译错误发生在 p.setfirst() 传入的参数是 integer 类型。既然 p 的定义是 pair\u0026lt;? extends number\u0026gt;,那么 setfirst(? extends number) 为什么不能传入 integer?\n这就是 \u0026lt;? extends number\u0026gt; 通配符的一个重要限制:方法参数签名 setfirst(? extends number) 无法传递任何 number 的子类型给 setfirst(? extends number)。\n⭐ 一句话,对于 \u0026lt;? extends t\u0026gt; 就是 ‘读可以,写不行’ ,‘读’出来的 能确写都是 t 类型,‘写’的时候编译器不确定你会写个啥类型(不同 t 类型子类实例一起使用的时候很容易就乱套了),所以干脆不让你写,直接报错!\n在定义泛型类型 pair\u0026lt;t\u0026gt; 的时候,也可以使用 extends 通配符来限定 t 的类型。\n那怎么‘写’?\n使用 \u0026lt;? super t\u0026gt; - 下界通配符 (lower bounds wildcards)!\n上面说了 \u0026lt;? extends t\u0026gt; 允许调用读方法 t get() 获取 t 的引用,但不允许调用写方法 set(t) 传入 t 的引用(传入 null 除外);\n\u0026lt;? super t\u0026gt; 允许调用写方法 set(t) 传入 t 的引用,但不允许调用读方法 t get() 获取 t 的引用(获取 object 除外)。\n为什么呢?一句话,对于 \u0026lt;? super t\u0026gt; ,‘写’的时候,编译器能确定实际类型都兼容 t (本身或其父类),不会存在什么引用错误,但是‘读’的时候编译器不能确定你会读个啥类型(不同 t 类型父类实例一起使用的时候很容易就乱套了),所以干脆不让你读,直接报错!\n:: 好吧,其实还是有点乱,具体使用多了就明了了。\n","date":"2023-05-25","permalink":"https://aituyaa.com/%E5%8F%8D%E5%B0%84%E4%B8%8E%E6%B3%9B%E5%9E%8B/","summary":"\u003cp\u003ei.e. Reflection and Generic\u003c/p\u003e\n\u003cp\u003e反射与泛型那些事儿……\u003c/p\u003e","title":"反射与泛型"},]
[{"content":"javascript 中的 file、blob 和 base64 。\nblob 1\nblob 对象表示一个不可变、原始数据的 类文件对象 ,它的数据可以按文本或二进制的格式进行读取,也可以转换成 readablestream 来用于数据操作。\nblob 表示的不一定是 javascript 原生格式的数据。 file 接口基于 blob,继承了 blob 的功能并将其扩展,使其支持用户系统上的文件。\n要从其他非 blob 对象和数据构造一个 blob ,请使用 blob() 函数;要创建一个 blob 数据的子集 blob ,请使用 slice() 方法;要获取用户文件系统上的文件对应的 blob 对象,请参阅 file 文档。\n接受 blob 对象的 api 也被列在 file 文档中。\nblob() 构造函数 blob() 构造函数允许通过其它对象创建 blob 对象,比如,用字符串构建一个 blob :\n1 2 var obj = { name: \u0026#39;jack liu\u0026#39; }; var blob = new blob([json.stringify(obj)], { type: \u0026#39;application/json\u0026#39; }); 使用 blob 创建一个指向类型化数组的 url 1 2 3 4 5 6 var typearray = getthetypedarraysomehow(); // 传入一个合适的 mime 类型 var blob = new blob([typearray.buffer], { type: \u0026#39;application/octet-stream\u0026#39; }); var url = url.creatobjecturl(blob); // 会产生一个类似 blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的 url 字符串 // 你可以像使用普通的 url 那样使用它,比如用在 img.src 上 从 blob 中提取数据 一种从 blob 中读取内容的方法是使用 filereader ,以下代码将 blob 的内容作为类型数组读取:\n1 2 3 4 5 var reader = new filereader(); reader.addeventlistener(\u0026#39;loadend\u0026#39;, function () { // reader.result 包含被转化为类型数组 typed array 的 blob }); reader.readasarraybuffer(blob); 另一种读取 blob 中内容的方式是使用 response 对象,下述代码将 blob 中的内容读取为文本:\n1 var text = await new response(blob).text(); 通过使用 filereader 的其它方法可以把 blob 读取为字符串或者数据 url 。\nfile 2\n文件( file )接口提供有关文件的信息,并允许网页中的 javascript 访问其内容。\n通常情况下,file 对象是来自用户在一个 \u0026lt;input\u0026gt; 元素上选择文件后返回的 filelist 对象,也可以是来自由拖放操作生成的 datatransfer 对象,或者来自 htmlcanvaselement 上的 mozgetasfile() api 。\nfile 对象是特殊类型的 blob ,且可以用在任意的 blob 类型的 context 中。比如说, filereader, url.createobjecturl(), createimagebitmap() 及 xmlhttprequest.send() 都能处理 blob 和 file 。\nbase64 3\nbase64 是一组相似的二进制到文本(binary-to-text)的 编码规则 ,使得二进制数据在解释成 radix-64 的表现形式后能够用 ascii 字符串的格式表示出来。\n:: base64 这个词出自一种 mime 数据传输编码。\nbase64 编码普遍应用于需要通过被设计为处理文本数据的媒介上储存和传输二进制数据而需要编码该二进制数据的场景,这样是为了保证数据的完整并且不用在传输过程中修改这些数据。\n在 javascript 中,有两个函数被分别用来解码和编码 base64 字符串: atob() 和 btoa() 。\natob() 函数能够解码通过 base-64 编码的字符串数据,相反地, btoa() 函数能够从二进制数据字符串创建一个 base-64 编码的 ascii 字符串。\n1 2 3 4 5 6 7 // atob() 将 base64 解码 // btoa() 将 字符串转码为 base64 var str = \u0026#39;javascript\u0026#39;; window.btoa(str); // 转码 → amf2yxnjcmlwda== window.atob(\u0026#39;amf2yxnjcmlwda==\u0026#39;); // 解码 → javascript // 中方需要先用 encodeuricomponent 和 decodeuricomponent 转/解码 编码尺寸增加 ,每一个 base64 字符串实际上代表着 6 比特位,因此,3 字节(1 字节是 8 比特,3 字节为 24 比特)的字符串/二进制文件可以转换成 4 个 base64 字符(4 * 6 = 24 比特)。\n这意味着 base64 格式的字符串或文件的尺寸是原始尺寸的 133% ,如果编码的数据很少,增加的比例可能会更高。\n:: 是不是有些混乱?hmm… 来看下百度百科吧。\nbase64 要求把每三个 8bit 的字节转换为四个 6bit 的字节(3*8 = 4*6 = 24),然后把 6bit 再添加两位高位 0 ,组成四个 8bit 的字节,也就是说,转换后的字符串理论上将要比原来的长 1/3 。\n其具规则如下5:\n1. 把 3 个字节变成 4 个字节 2. 每 76 个字符加一个换行符 3. 最后的结束符也要处理 下面来看一个具体的例子。\n转换前: 10101101,10111010,01110110 等价于: 101011 01,1011 1010,01 110110 转换后: 00101011,00011011 ,00101001,00110110 十进制: 43 27 41 54 对照 =table 1: the base64 alphabet= 码表: r b p 2 如上,上面的 24 位编码后的 base64 值为 rbp2 。解码同理,把 rbp2 的二进制位连接上再重组得到的 8 位值,得出原码。\nformdata formdata 接口提供了一种表示表单数据的键值 key/value 的构造方式,并且可以轻松地将数据通过 xmlhttprequest.send() 方法发送出去。如果送出时的编码类型被设为 multipart/form-data ,它会使用和表单一样的格式。\n实现了 formdata 接口的对象可以直接在 for...of 结构中使用,而不需要调用 entries() : for(var p of myformdata) 的作用和 for(var p of myformdata.entries()) 是相同的。\nformdata() 构造函数用来创建一个新的 formdata 对象。\n:: 有时候,这种“参考书式的定义”真的让人很……\nformdata 有会么用呢?\nformdata 类型是在 xmlhttprequest 2 级定义的,它是为序列化表单以及创建与表单格式相同的数据提供便利(用于 xhr 传输)。\n序列化表单 将 form 表单元素的 name 与 value 进行组合,实现表单数据的序列化,从而减少表单元素的拼接,提高工作效率。\n1 2 3 4 5 \u0026lt;form id=\u0026#34;myform\u0026#34; method=\u0026#34;post\u0026#34; action=\u0026#34;\u0026#34;\u0026gt; \u0026lt;input name=\u0026#34;name\u0026#34; type=\u0026#34;text\u0026#34; value=\u0026#34;jack\u0026#34; /\u0026gt; \u0026lt;input name=\u0026#34;pswd\u0026#34; type=\u0026#34;password\u0026#34; value=\u0026#34;12345678\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;提交\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; 我们可以使用这个表单元素作为初始化参数,来实例化一个 formdata 对象。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 1. 获取页面已有的一个 form 表单 var myform = document.getelementbyid(\u0026#39;myform\u0026#39;); // 2. 用表单来初始化 var formdata = new formdata(form); // 我们可以根据 name 来访问表单中的字段 var name = formdata.get(\u0026#39;name\u0026#39;); // → jack var pswd = formdata.get(\u0026#39;pswd\u0026#39;); // → 12345678 // 当然,我们也可以在此基础上添加其他数据 formdata.append(\u0026#39;token\u0026#39;, \u0026#39;fajskd.fj23jkf.sdfk\u0026#39;); // 你还可以通过 xhr 来发送数据 var xhr = new xmlhttprequest(); xhr.open(\u0026#39;post\u0026#39;, \u0026#39;http://127.0.0.1/login\u0026#39;); xhr.setheader(\u0026#39;content-type\u0026#39;, \u0026#39;application/x-www-form-urlencoded\u0026#39;); // ?? 此处存疑 xhr.send(formdata); xhr.onload = function () { if (xhr.status === 200) { // ... } }; 直接创建一个 formdata 对象 使用 formdata 构造函数可以直接创建一个 formdata 对象,如: var formdata = new formdata() ,然后,就可以调用相关方法进行操作。\n常用方法 :: 无非是“增删查改”那些事儿……\n方法 描述 append(key, value) 向 formdata 中添加新的属性值,对应的属性值存在也不会覆盖 get(key) 返回在 formdata 对象中与给定键关联的第一个值 getall(key) 返回一个包含 formdata 对象中与给定键关联的所有值的数组 has(key) 返回一个布尔值表明 formdata 对象是否包含某些键 set(key, value) 给 formdata 设置属性值,如果 formdata 对应的属性值存在则覆盖原值,否则新增一项属性值 entries 返回一个包含所有键值对的 iterator 对象 keys 返回一个包含所有键的 iterator 对象 values 返回一个包含所有值的 iterator 对象 delete(key) 从 formdata 对象里面删除一个键值对 转换 4\nfiletobase64 主要应用场景:图片预览。\n1 2 3 4 5 6 7 8 9 10 11 12 13 function filetobase64(file, callback) { var reader = new filereader(); reader.readasdataurl(file); reader.onload = function (e) { var base64 = e.target.result; if (typeof callback === \u0026#39;function\u0026#39;) { callback(base64); } else { console.log(\u0026#39;base64: \u0026#39;, base64); } }; } filetoblob 主要应用场景:文件上传。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function filetoblob(file, callback) { var type = file.type; var reader = new filereader(); reader.readasdataurl(file); reader.onload = function (e) { var blob = new blob([e.target.result], { type }); if (typeof callback === \u0026#39;function\u0026#39;) { callback(blob); } else { console.log(\u0026#39;blob: \u0026#39;, blob); return blob; } }; } base64tofile 主要应用场景:文件上传。\n1 2 3 4 5 6 7 8 9 10 11 12 13 function base64tofile(base64, callback(filename)) { var arr = base64.split(\u0026#39;,\u0026#39;), type = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new unit8array(n) while(n--) { u8arr[n] = bstr.charcodeat(n) } return new file([u8arr], filename, {type}) } base64toblob 主要应用场景:文件上传。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //dataurl 类似为 data:img/jpg;base64,/9j/4aaqskzjrgabaqeasabiaad/2w... function base64toblob(base64, callback) { var arr = base64.split(\u0026#39;,\u0026#39;), // mimestring type = arr[0].match(/:(.*?);/)[1], // bytestring bstr = atob(arr[1]), n = bstr.length, u8arr = new uint8array(n); // for (var i = 0; i \u0026lt; bytestring.length; i++) { // ia[i] = bytestring.charcodeat(i) // } while (n--) { u8arr[n] = bstr.charcodeat(n); } return new blob([u8arr], { type }); } blobtofile 主要应用场景:文件上传。\n1 2 3 4 function blobtofile(blob, filename) { var file = new file([blob], filename, { type: blob.type }); return file; } blobtobase64 主要应用场景:图片预览。\n1 2 3 4 5 6 7 8 9 10 11 12 function blobtobase64(blob, callback) { var reader = new filereader(); reader.readasdataurl(blob); reader.onload = function (e) { if (typeof callback === \u0026#39;function\u0026#39;) { callback(e.target.result); } else { console.log(\u0026#39;base64: \u0026#39;, e.target.result); return e.target.result; } }; } ","date":"2023-05-25","permalink":"https://aituyaa.com/%E6%96%87%E4%BB%B6%E8%BD%AC%E6%8D%A2/","summary":"\u003cp\u003eJavaScript 中的 file、blob 和 base64 。\u003c/p\u003e","title":"文件转换"},]
[{"content":" 每一个领域的内容,都要有先问一下 – 是什么?为什么?做什么?怎么做?\n是什么?简述概念; 为什么?历史发展,出现的原因,优缺点; 做什么?主要应用场景; 怎么做?具体使用方法步骤。 简介 模块化编程 (模块化程序设计),是指在进行程序设计时将一个大程序按照功能划分为若干小程序模块,每个小程序模块完成一个确定的功能,并在这些模块之间建立必要的联系,通过模块的互相协作完成整个功能的程序设计方法。\n:: 大程序 → (功能)小程序模块( → 子过程) → 模块联系、协作\n随着程序规模的扩大,无论是项目本身(编写、扩展、测试、维护),还是经济上的考虑(效率、收支),分工协作都是不可避免的。\n在模块化的过程中可能会产生什么问题呢? 命名冲突,本质在于作用域。\n:: 实际项目中,模块化开发几乎不会是一个人开发所有模块,那就很难避免命名冲突(小甲和小乙都使用了同一个变量名、函数名),项目越大,越难控制。\n下面我们就以 javascript 为例来看一下其模块化的发展。\n:: 为什么选用 javascript 呢?因为 javascript 最初的设计是没有模块化考虑的,而是随着其应用场景和范围的变化,逐渐通过各种方式实现了模块化,如此,我们可以看一下其模块化的全貌和历程。\njavascript 的模块化 1 1991 年 8 月 6 日 (农历六月廿六),世界上第一个网页诞生。世界上的第一个网页是这样的 http://info.cern.ch/ ,只用来作为文本的展示和极其简单的交互。\n![[assets/pasted image 20230525180236.png]]\n\u0026gt; 万维网的发明者,互联网之父蒂姆·伯纳斯·李\n检查一下网页源码的话,你会发现,它只是纯 html 文本,没有 css ,更没有 javascript 。\n哈坤·利于 1994 年在芝加哥的一次会议上第一次展示了 css 的建议;javascript 是 1995 年由 netscape 公司的 brendan eich,在网景导航者浏览器上首次设计实现而成。\n随着网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等……开发者不得不使用软件工程的方法,管理网页的业务逻辑。javascript 的模块化编程,已经成为一个迫切的需求。\n前面我们已经知道,javascript 最初设计的时候就没有考虑到模块化,那么我们要怎么实现它的模块化呢?\n模块化编程是将一个大程序按照功能划分为若干小程序模块,那么,我们我们首先要解决的就是 划分模块 。\n原始划分 我们可以认为模块就是实现特定功能的一组方法(以及记录状态),只要把不同函数简单地放在一起,就算是一个模块。\n1 2 3 4 5 6 7 8 // 这就是一个模块 function m1() { /* ... */ } function m2() { /* ... */ } // ... 上面的代码中,函数 m1() 和 m2() ,组成一个模块,使用的时候,直接调用就行了。这样做有以下缺点:\n- 污染了全局变量,无法保证不与其他模块发生变量名冲突; - 模块成员之间看不出直接关系。 :: 看,模块化其实就是相反的两个方面: ‘拆分’(隔离、解决冲突)和‘组合’(依赖、导出引入)。\n对象划分 为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。\n1 2 3 4 5 6 7 8 9 var module1 = new object({ _count: 0, m1: function () { /* ... */ }, m2: function () { /* ... */ }, }); 上边的函数 m1() 和 m2() ,都封装在 module1 对象里(现在它们有关系了,都属于一个对象嘛),使用的时候,就是调用这个对象的属性,如 module1.m1() 。但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写,如 module1._count = 5 。\n:: 很显然,这样的封装是不安全的。\n立即执行函数划分 如何不暴露私有成员的目的,自然是做到外界不能直接访问原始对象内部,只能访问我们想让他访问的对象。使用“立即执行函数(immediately-invoked function expression, iife)”,可以砍到不暴露私有成员的目的。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var module1 = (function () { var _count = 0; var m1 = function () { /* ... */ }; var m2 = function () { /* ... */ }; // 这个就是我们想让外界访问到的对象了 return { m1: m1, m2: m2, }; })(); 如此,在 { m1: m1, m2: m2 } 中根本没有 _count 变量,外界自然是访问不到的喽。\n至此,我们解决了 javascript 中如何模块化的问题 – 使用 iife 来实现模块分离(避免命名冲突、封装不够等问题)。\n模块依赖 我们在前面已经讲述了 如何分离模块的问题 ,那么,另一个问题也就随之而来了 – 假如在一个程序中,模块 a 调用了 b ,b 又调用了 c、d ,我们如何处理 模块之间的依赖关系 呢?\n什么是依赖?聚焦在模块本身无非就是两点:\n如何导入一个模块? 如何导出模块中你想暴露的? 我们来了解一下比较成熟的模块实现方式,如 commonjs、amd、cmd、umd 和 es module 等。后续的章节中,我们重点来认识一下 commonjs 和 es module 两种模块规范,其他在实际项目中已经应用不多。\ncommonjs 2\n在 node 环境中,一个 .js 文件就称之为一个模块(module),它们遵循 commonjs 模块规范。\n我们来看一个具体的例子,假如我们遵循 commonjs 规范编写 hello.js 文件,如下:\n1 2 3 4 5 6 7 8 9 10 // hello.js 文件就是名为 hello 的模块 var s = \u0026#39;hello\u0026#39;; function greet(name) { console.log(s + \u0026#39;, \u0026#39; + name + \u0026#39;!\u0026#39;); } // 导出模块 // 把函数 greet 作为模块的输出暴露出去 module.exports = greet; 在同一目录下,再来编写 main.js 文件,来调用 hello 模块的 greet 函数,如下:\n1 2 3 4 5 6 // 引入模块 // 使用 node 提供的 `require` 函数引入模块 `hello` var greet = require(\u0026#39;./hello\u0026#39;); var s = \u0026#39;michael\u0026#39;; greet(s); // → hello, michael! 综上可知,commonjs 规范,使用\nmodule.exports 暴露模块,如 module.exports = variable ,其中 variable 可以是任意对象、函数、数组等; require 函数导入模块,如 var ref = require('module_name) ,引入的对象具体是什么,取决于所引入模块输出的对象。 :: ‘输出’决定‘引入’!*\ncommonjs 模块原理 我们知道,javascript 语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。\n那么,nodejs 是如何实现这一点的呢?\n模块隔离 其实要实现“模块”这个功能,并不需要语法层面的支持,nodejs 也没有增加任何新的 javascript 语法。实现“模块”功能的奥妙就在于 javascript 是一种函数式编程语言, 它运行闭包!闭包!闭包! ,如我们上面说到是 iife 。我们只需要把一段 javascript 代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。\n:: 看,我们使用函数的作用域作了隔离,如此,就避免了不同模块内部变量、函数名的命名冲突。\n答案:内存机制决定的。看,内存才是最本质的。 下面,我们来看看 nodejs 具体为我们做了什么?\n我们编写的 hello.js 代码是这样的:\n1 2 3 var name = \u0026#39;world\u0026#39;; console.log(\u0026#39;hello, \u0026#39; + name + \u0026#39;!\u0026#39;); nodejs 加载了 hello.js 之后,把代码包装了一下,变成了这样执行 ↓ :\n1 2 3 4 5 6 7 (function () { // 读取 hello.js 代码 var name = \u0026#39;world\u0026#39;; console.log(\u0026#39;hello, \u0026#39; + name + \u0026#39;!\u0026#39;); // hello.js 代码读取结束 })(); 如此, name 就变成了函数内部的局部变量,如果 nodejs 继续加载其他模块,这些模块中定义的 name 也互不干扰。\n模块输出 nodejs 利用 javascript 的函数式编程特性 – 闭包,轻松实现了模块的隔离。\n但是,模块的输出 module.exports 是怎么实现的呢?\n这个也容易,原来 nodejs 先准备了一个对象 module ,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // nodejs 准备一个 module 对象 var module = { id: \u0026#39;hello\u0026#39;, exports: {}, }; var load = function (module) { // 读取的 hello.js 代码 function greet(name) { console.log(\u0026#39;hello, \u0026#39; + name + \u0026#39;!\u0026#39;); } module.exports = greet; // hello.js 代码读取结束 return module.exports; }; var exported = load(module); // 保存 module save(module, exported); 可见,变量 module 是 nodejs 在加载 .js 文件之前准备的一个变量,并将其传入加载函数,我们在 hello.js 中可以直接使用变量 module 原因就在于它实际上是加载函数的一个参数。\n通过把参数 module 传递给 load() 函数, hello.js 就顺利地把一个变量传递给了 node 执行环境,node 会把 module 变量保存到某个地方。\n由于 node 保存了所有导入的 module ,当我们用 require() 获取模块时,node 找到对应的 module ,把这个 module 的 exports 属性返回,如此,另一个模块就拿到了模块的输出。\n:: 通俗来说,就是 node 加载模块时,在其可访问的环境(作用域)内挂载了一个 module 对象,如此该模块输出的‘对象’(变量、函数、对象等) 就可以赋给 module 对象的 exports 属性(即 module.exports)。\nmodule.exports vs exports 我们只需要知道, module.exports 是 nodejs 为我们准备的用于模块输入的唯一真正的对象, exports 不过是对 module.exports 的引用罢了。这也就决定了,如下使用方式的对错:\n1 2 3 4 5 6 7 module.exports = { desc: \u0026#39;一个引用类型对象\u0026#39; }; // ✔ module.exports.var1 = variable; // ✔ exports.var1 = variable; // ✔ 并有改变 `exports` 的引用 exports = { desc: \u0026#39;一个引用类型对象\u0026#39; }; // ✘ 已改变了 `exports` 的引用 // 你改变了 `exports` 的指向,它就不再指向 `module.exports` 了, // 自然也就不是模块的输出对象了, // 记住,模块的输出对象只有一个 注:强烈建议只使用 module.exports 方式来输出模块变量!\nes module 3\n在 es6 之前,社区制定了一些模块加载方案,最主要的有 commonjs(node 服务器端) 和 amd (浏览器端)两种。\n有一个好消息,es6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 commonjs 和 amd 规范,成为浏览器和服务器通用的模块解决方案。\nes module vs commonjs es module 模块的设计思想是尽量的 静态化 ,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。commonjs 和 amd 模块,都只能在运行时确定这些东东。比如,commonjs 模块输出的就是 module.exports 对象,输入时必须查找对象属性。\n:: 有了 es6 静态加载,使得静态分析成为可能,就能进一步拓宽 javascript 的语法,比如引入宏(macro)和类型系统(type system)。*\n❓ 想一想,为什么 commonjs 和 amd 只有在运行时才能确定模块的依赖关系及输入输出变量呢? ✔️ 因为它们本质上都是“立即执行函数”(iife),不执行自然无法得到! 1 2 3 4 5 6 7 let { stat, exists, readfile } = require(\u0026#39;fs\u0026#39;); // 等同于 let _fs = require(\u0026#39;fs\u0026#39;); // 引入了整个 `fs` 模块的整个输出对象 let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile; 上面代码的实质是整体加载 fs 模块(即加载 fs 模块的整个输出对象 module.exports ),生成一个对象 _fs ,然后再从这个对象上读取 3 个方法。\n这种加载称为 运行时加载 ,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。\n:: 所谓“静态化”,没那么玄乎,不过就是在运行之前,编译阶段就可以确定的一些东东。*\n与 commonjs 模块输出的是 module.exports 对象不同的时,es module 模块输出的并不是对象,而是 通过 export 命令显式指定输出的代码,再通过 import 命令输入 。如下:\n1 2 // es module import { stat, exists, readfile } from \u0026#39;fs\u0026#39;; 其实质是从 fs 模块加载 3 个方法, 其他方法不加载 ,即它无法引用 es module 模块本身(因为它不是对象),这种加载称为 编译时加载 (或静态加载)。\nexport es module 模块功能主要由两个命令构成: export 和 import :\nexport 命令用于规定模块的对外接口; import 命令用于输入其他模块提供的功能。 export、import 命令可以出现在模块的任何位置,只要处于模块顶层就可以,因为处在代码块中,就没法做静态优化了。\n注: export 命令显式指定输出的代码,对,是代码!代码!代码!\n:: 其实,看一下基本的编译原理,就很容易理解这个地方了,export 输出的其实是一个动态外链。\n1 2 3 4 5 6 7 8 export var year = 1990; // ✔ // or var year = 1990; export { year }; // ✔ // 还可以用别名输出 export { year as yearalias1 }; // ✔ export { year as yearalias2 }; // ✔ 如上, export 导出的都是 var year = 1990 这行代码,而不是 year 这个变量。\n如果,你像在下面这样使用,就会报错了,如:\n1 2 3 4 export 1 // ✘ var m = 1 export m // ✘ 想一下,为会什么会报错呢?因为它们 不是有意义的代码 呗! 1 只是一个字面量,引用它明显没有什么意义,同样 m 中存的也是 1 。\n同理,对于函数而言,也是一样的,如下:\n1 2 3 4 5 6 export function f() {} // ✔ // or function f() {} export { f } // ✔ export { f as fn } // ✔ export f // ✘ export 语句输出的接口,与其对应的值是动态绑定关系。 即通过该接口,可以取到模块内部实时的值。这一点与 commonjs 规范完全不同,commonjs 模块输出的是值的缓存,不存在动态更新。\n1 2 export var foo = \u0026#39;bar\u0026#39;; settimeout(() =\u0026gt; (foo = \u0026#39;baz\u0026#39;), 500); 上面代码输出变量 foo ,值为 bar ,500 毫秒后变成 baz 。\nimport 前面我们讲了输出 export ,这个章节我们来认识一下输入 import 。\nimport 命令接受一对大括号,里面指定要从其他模块导入的变量名,该变量名必须与被导入模块对象接口(输出的大括号是的变量啦)名称相同。其用法实例如下:\n1 2 3 4 import { year } from \u0026#39;./profile.js\u0026#39;; // ✔ import { year as yearalias } from \u0026#39;./profile.js\u0026#39;; // ✔ import \u0026#39;loadsh\u0026#39;; // ✔ 只是执行 loadsh 模块,不输入任何值 除了指定加载某个输出值,还可以使用 整体加载 ,即用星号( * )指定一个对象,所有输出值都加载在这个对象上面。\n1 2 3 4 import * as circle from \u0026#39;./circle\u0026#39;; circle.area(4); // 调用 circle 的 area() 方法计算面积 circle.circumference(14); // 调用 circle 的 circumference() 方法计算周长 export default 前面说过,使用 import 命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到 export default 命令,为模块指定默认输出。\n注: 使用 export default 输出的代码,用 import 命令引入时,不使用大括号。\n1 2 3 4 5 6 7 8 9 10 11 // export-default.js /// // 匿名函数 export default function() {} // ✔ // 具名函数 export default function foo() {} // ✔ // or function foo() {} export default foo // ✔ // import-default.js /// import baz from \u0026#39;./exort-default\u0026#39; // ✔ `baz` 可以是任何自定义的变量名 但是,对于使用 export 命令时,就不能省略大括号,如下:\n1 2 3 4 5 6 7 8 // export.js /// export function foo() {} // ✔ function foo() {} export { foo } // ✔ // import.js /// import { foo } from \u0026#39;./export.js\u0026#39; // ✔ import foo form \u0026#39;./export.js\u0026#39; // ✘ 本质上, export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字。\n1 2 3 4 5 6 7 8 9 10 11 12 13 // export.js /// function add(x, y) { return x + y; } export { add as default }; // ✔ // 等同于 // export default add // ✔ // import.js /// import { default as foo } from \u0026#39;./export.js\u0026#39;; // ✔ // 等同于 // import foo from \u0026#39;./export.js\u0026#39; // ✔ 同样,因为 export default 命令其实只是输出一个叫做 default 的变量,所以它后面不能跟变量声明语句。\n1 2 3 4 5 6 export var a = 1 // ✔ var a = 1 export default a // ✔ export default var a = 1 // ✘ export 与 import 的复合写法 如果在一个模块之中,先输入后输出同一个模块, import 语句可以与 export 语句写在一起。\n1 2 3 4 5 export { foo, bar } from \u0026#39;my_module\u0026#39;; // 等价于 import { foo, bar } from \u0026#39;my_module\u0026#39;; export { foo, bar }; 上面代码中, export 和 import 语句可以结合在一起,写成一行。\n但需要注意的是,写成一行以后, foo 和 bar 实际上 并没有被导入当前模块,只是相当于对外转发 了这两个接口,导致当前模块不能直接使用 foo 和 bar 。\n模块的接口改名和整体输出,也可以采用这种写法。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 接口改名 export { foo as myfoo } from \u0026#39;my_module\u0026#39;; // 整体输出 export * from \u0026#39;my_module\u0026#39;; export * as ns from \u0026#34;mod\u0026#34;; // 默认接口的写法 export { default } from \u0026#39;foo\u0026#39;; // 具名接口改为默认接口的写法 export { es6 as default } from \u0026#39;./somemodule\u0026#39;; // 等同于 import { es6 } from \u0026#39;./somemodule\u0026#39;; export default es6; // 默认接口也可以改名为具名接口 export { default as es6 } from \u0026#39;./somemodule\u0026#39;; import() 前面介绍过, import 命令会被 javascript 引擎静态分析,先于模块内的其他语句执行。\n这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载。在语法上,条件加载就不可能实现。如果 import 命令要取代 node 的 require 方法,就形成了一个障碍。\n1 2 3 4 // 报错 if (x === 2) { import mymodule from \u0026#39;./mymodule\u0026#39;; // ✘ `import` 命令不支持动态加载 } es2020 提案引入了 import() 函数,支持动态加载模块。\nimport(specifier) // `specifier` 指定所要加载的模块的位置 import() 函数和 import 命令接受的参数相同,区别在于 import() 可以动态加载,且与所加载的模块并没有静态链接关系, 返回一个 promise 对象 。\nimport() 函数类似于 node 的 require 方法,区别在于 import() 是异步加载,而 require 是同步加载。\n1 2 3 4 5 if (condition) { import(\u0026#39;modulea\u0026#39;).then(/* ... */); // ✔ } else { import(\u0026#39;moduleb\u0026#39;).then(/* ... */); // ✔ } *注: import() 加载模块成功以后,这个模块会作为一个对象,当作 then 方法的参数。\nwebpack module 4\n在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of funtionality),并称之为模块。\n每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。精心编写的模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。\nnodejs 从一开始就支持模块化编程,而 web 浏览器对于模块化的支持却姗姗来迟。在 web 存在多种支持 javascript 模块化的工具,如 amd、cmd 等,这些工具各有优势和限制。\nwebpack 基于从这些系统获得的经验教训,并将模块的概念应用于项目中的任何文件。\n什么是 webpack 模块呢?\nwebpack 模块能够以各种方式表达模块之间的依赖关系,如:\nes2015 的 import 语句; commonjs 的 require() 语句; amd 的 define 和 require 语句; css/sass/less 文件中的 @import 语句; 样式 url(...) 或 html 文件 \u0026lt;img src=...\u0026gt; 中的图片链接。 webpack 是怎么做到这一点的呢? loader !\nwebpack 通过 loader 可以支持各种语言和预处理器编写模块。loader 描述了 webpack 如何处理非 javascript 的模块,并且在 bundle 中引入这些依赖。另外,webpack 社区已经为各种就行语言和语言处理器构建了 loader ,如: coffeescript、typescript、esnext(babel)、sass、less 等。\n注:有关完整列表,请参考 loader 列表 或 自己编写 。\n","date":"2023-05-25","permalink":"https://aituyaa.com/%E6%A8%A1%E5%9D%97%E5%8C%96%E7%BC%96%E7%A8%8B/","summary":"\u003cblockquote\u003e\n\u003cp\u003e每一个领域的内容,都要有先问一下 – 是什么?为什么?做什么?怎么做?\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e是什么?简述概念;\u003c/li\u003e\n\u003cli\u003e为什么?历史发展,出现的原因,优缺点;\u003c/li\u003e\n\u003cli\u003e做什么?主要应用场景;\u003c/li\u003e\n\u003cli\u003e怎么做?具体使用方法步骤。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e","title":"模块化编程"},]
[{"content":"🔔 以下内容主要摘录自 廖雪峰老师的博客,具体示例请跳到原文参考。\n![[assets/pasted image 20230525163525.png]]\n:: 好吧,廖老师配图一直可以的。\n注解是什么 注解是什么呢?它和注释有什么区别?\n注解定义后也是一种 class ,所有的注解都继承自 java.lang.annotaion.annotation 。 ok,我们先看看注解是做什么用的,再来了解如何定义和使用它。\n注解是放在 java 源码的类、方法、字段、参数前的一种特殊的“注释”(注意并不是注释),是一种用做标注的“元数据”。如,我们日常使用的 @override 就是一种注解。\n从 jvm 的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。\n:: 其实这里的意思时,注解的作用取决于你是如何定义注解和使用注解的方法的。注解,就像是你在代码的某个位置放的一个钩子,至于如何使用它,则完全由你决定。\njava 的注解可以分为三类:\n由编译器使用的注解; 由工具处理 .class 文件使用的注解; 在程序运行运行期能够读取的注解。 第一类,编译器使用的注解,如 @override 让编译器检查该方法是否正确地实现了覆写, @suppresswarnings 告诉编译器忽略此处代码产生的警告等。这类注解不会被编译进入 .class 文件,它们在编译后就被编译器扔掉了。(😿)\n第二类,由工具处理 .class 文件使用的注解,比如有些工具会在加载 class 的时候,对 class 做动态修改,以实现一些特殊的功能。这类注解会被编译进入 .class 文件,但在类加载结束后并不会存在于内存中(使命已经完成了,仅作用于 class)。它只被一些底层库使用,一般不必我们自己处理。\n第三类,在程序运行期能够读取的注解,这类注解在加载后一起存在于 jvm(内存中啦) 中(因为要在运行期读取啦 😏),这也是最常用的注解。\nok,了解了注解是什么,有什么用之后,让我们来看一下如何定义一个注解吧。\n定义注解 java 语言使用 @interface 语法来定义注解,它的格式如下:\n1 2 3 4 5 public @interface report { int type() default 0;\t// default 后就是默认值 string level() default \u0026#34;info\u0026#34;; string value() default \u0026#34;\u0026#34;; } 注解定义后也是一种 class,所有的注解都继承自 java.lang.annotation.annotation 。\n不难看出,在定义一个注解时,还可以定义配置参数。需要注意的是,配置参数必须是常量,在定义注解时就已经确定了每个参数的值(可以有默认值)。\n大部分注解会有一个名为 value 的配置参数,对此参数赋值,可以只写常量,相当于省略了 value 参数。\n有一些注解可以修饰其他注解 \u0026ndash; 元注解(meta annotation)。\n这里,我们只了解两个常用的元注解: @target 和 @retention 。\n@target\n最常用的元注解是 @target ,它用来定义 annotation 能够被应用于源码的哪些位置:\n类或接口: elementtype.type ; 字段: elementtype.field ; 方法: elementtype.method ; 构造方法: elementtype.constructor ; 方法参数: elementtype.parameter 。 实际上 @target 定义的 value 是 elementtype[] 数组,只有一个元素时,可以省略数组的写法。\n@retention\n@retention 定义了 annotation 的生命周期:\n仅编译器(译后即丢): retentionpolicy.source ; 仅 class (不入 jvm)文件: retentionpolicy.class ; 运行期(加载进 jvm,供程序读取): retentionpolicy.runtime 。 如果 @retention 不存在,则该 annotation 默认为 class,但其实通常我们自定义的 annotation 都是 runtime ,所以 务必要加上 @retention(retentionpolicy.runtime) 这个元注解。\nok,我们来总结一下定义 annotation 的步骤:\n用 @interface 定义注解; 添加参数、默认值(把最常用的参数定义为 value() ,方便使用时直接写常量); 用元注解配置注解。 :: 一直走在偷懒的路上,永不停歇……\n如这样:\n1 2 3 4 5 6 7 @target(elementtype.type)\t// 3 @retention(retentionpolicy.runtime) public @interface report {\t// 1 int type() default 0;\t// 2 string level() default \u0026#34;info\u0026#34;; string value() default \u0026#34;\u0026#34;; } 处理注解 在日常生产环境中,我们基本上只需编写和使用 runtime 类型的注解,所以我们只讨论它。前面已经说过,该类型注解是加载进 jvm 供程序读取的,那么如何读取呢?反射 api!\n使用反射 api 读取 annotation:\nclass.getannotation(class) ; field.getannotation(class) ; method.getannotation(class) ; constructor.getannotation(class) ; 如:\n1 2 3 4 // 获取 person 定义的@report 注解: report report = person.class.getannotation(report.class); int type = report.type(); string level = report.level(); 如果读取时,annotation 不存在,则返回 null 。\n……\n注意,定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解,检查逻辑完全是我们自己编写的,jvm 不会自动给注解添加任何额外的逻辑。\n应用注解 这时我们概览一下注解在 web 开发中的常见应用形式。\n在 servlet 中的应用 在 javaee 平台上,处理 tcp 连接,解析 http 协议这些底层工作统统扔给现成的 web 服务器去做。我们使用 servlet api 编写自己的 servlet 来处理 http 请求,web 服务器实现 servlet api 接口,实现底层功能:\n┌───────────┐ │my servlet │ ├───────────┤ │servlet api│ ┌───────┐ http ├───────────┤ │browser│\u0026lt;──────\u0026gt;│web server │ └───────┘ └───────────┘ 注解在 servlet 中如何应用呢?它有什么作用呢?\n1. @webservlet\n我们知道,一个 servlet 总是继承自 httpservlet ,然后覆写 doget() 或 dopost() 方法。如何知道客户端的请求地址呢?早期的 servlet 使用 web.xml 文件来配置映射路径,现在我们使用注解 @webservlet 来实现。如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // webservlet 注解表示这是一个 servlet ,并映射到地址 /hello: @webservlet(urlpatterns = \u0026#34;/hello\u0026#34;) public class helloservlet extends httpservlet { protected void doget(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception { // 设置响应类型: resp.setcontenttype(\u0026#34;text/html\u0026#34;); // 获取输出流: printwriter pw = resp.getwriter(); // 写入响应: pw.write(\u0026#34;\u0026lt;h1\u0026gt;hello, world!\u0026lt;/h1\u0026gt;\u0026#34;); // 最后不要忘记 flush 强制输出: pw.flush(); } } 浏览器发出的 http 请求总是由 web server 先接收,然后,根据 servlet 配置的映射,不同的路径转发到不同的 servlet 。\n2. @webfilter\n在一个复杂的 web 应用程序中,通常有很多 url 映射,对应的,也会有多个 servlet 来处理 url 。为了把一些公用逻辑从各个 servlet 中抽离出来,javaee 的 servlet 规范还提供了一种 filter 组件,即过滤器。它的作用是,在 http 请求到达 servlet 之前,可以被一个或多个 filter 预处理,类似打印日志、登录检查等逻辑,完全可以放到 filter 中。\n使用也很简单,来看一段示例:\n1 2 3 4 5 6 7 8 9 10 // 用 @webfilter 注解标注该 filter 需要过滤的 url ,这里的 /* 表示所有路径 @webfilter(urlpatterns = \u0026#34;/*\u0026#34;) public class encodingfilter implements filter { public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception { system.out.println(\u0026#34;encodingfilter:dofilter\u0026#34;); request.setcharacterencoding(\u0026#34;utf-8\u0026#34;); response.setcharacterencoding(\u0026#34;utf-8\u0026#34;); chain.dofilter(request, response); } } 3. @weblistener\n除了 servlet 和 filter 外,javeee 的 servlet 规范还提供了第三种组件 - listener (监听器) 。\n有好几种 listener ,其中最常用的是 servletcontextlistener ,我们来编写一个实现该接口的类,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 @weblistener public class applistener implements servletcontextlistener { // 在此初始化 webapp, 例如打开数据库连接池等: public void contextinitialized(servletcontextevent sce) { system.out.println(\u0026#34;webapp initialized.\u0026#34;); } // 在此清理 webapp, 例如关闭数据库连接池等: public void contextdestroyed(servletcontextevent sce) { system.out.println(\u0026#34;webapp destroyed.\u0026#34;); } } 任何标注为 @weblistener ,且实现了特定接口的类会被 web 服务器自动初始化。\n:: 看,我们有钩子了 🥰\n一个 web 服务器可以运行一个或多个 webapp,对于每个 webapp ,web 服务器都会为其创建一个全局唯一的 servletcontext 实例,我们在上例中编写的两个回调方法实际上对应的就是 servletcontext 实例的创建和销毁。\n在 spring 中的应用 我们知道 spring 的核心就是提供了一个 ioc 窗口,它可以管理所有轻量级的 javabean 组件。起初,spring 也使用类似 xml 这样的配置文件,来描述 bean 的依赖关系,然后让容器来创建并装配 bean 。然而,这种方式虽然直观,写起来却很繁琐。\n1. @component 和 @autowired\n现在,我们可以使用 annotation 来注解,让 spring 自动扫描 bean 并组装它们。如:\n1 2 3 4 5 6 7 8 9 10 11 12 @component class mailservice { ... } @component public class userservice { @autowired mailservice mailservice; // ... } 如上,这个 @component 注解就相当于定义了一个 bean ,它有一个可选的名称,默认是 mailservice 、userservice (小写开头的类名)。\n@autowired 则相当于把指定类型的 bean 注入到指定的字段中。它不但可以写在 set() 方法上,还可以直接写在字段上,甚至可以写在构造方法中。\n2. @configuration 和 @componentscan\n要启动一个 spring 应用,我们需要编写一个类启动容器,如下:\n1 2 3 4 5 6 7 8 9 10 @configuration @componentscan public class appconfig { public static void main(string[] args) { applicationcontext context = new annotationconfigapplicationcontext(appconfig.class); userservice userservice = context.getbean(userservice.class); user user = userservice.login(\u0026#34;bob@example.com\u0026#34;, \u0026#34;password\u0026#34;); system.out.println(user.getname()); } } 其中, @configuration 表示 appconfig.class 是一个配置类,在创建 applicationcontext 时,使用的实现类是 annotationconfigapplicationcontext ,必须传入一个标注了 @configuration 的类名。\n@componentscan 则告诉容器,自动搜索当前类所在的包以及子包,把所有标注为 @component 的 bean 自动创建出来,并根据 @autowired 进行装配。\n:: 看,ioc 容器其实啥都不知道,你需要用 annotation 告诉它,做什么、怎么做、在哪做。\n使用 @componentscan 很方便,但是,我们也要特别注意包的层次结构。通常来说,启动配置类位于自定义的顶层包,其他 bean 按类别放入子包。\n思考一下,如何创建并配置一个第三方 bean 呢?它并不在当前可搜索的包中!\n3. @bean\n如果一个 bean 不在我们自己的 package 管理之内,例如 zoneid ,如何创建它?我们只需要在 @configuration 配置类中编写一个 java 方法(该方法使用 @bean 注解)创建并返回它。\n1 2 3 4 5 6 7 8 9 @configuration @componentscan public class appconfig { // 创建一个 bean: @bean zoneid createzoneid() { return zoneid.of(\u0026#34;z\u0026#34;); } } 4. @propertysource\n在开发应用程序时,经常需要读取配置文件,最常用的配置方法是以 key=value 的形式写在 *.properties 文件中。 spring 提供了一个简单的 @propertysource 来自动读取配置文件,只需要在配置类上再添加一个注解。\n1 2 3 4 5 6 7 8 9 10 11 12 @configuration @componentscan @propertysource(\u0026#34;app.properties\u0026#34;) // 表示读取 classpath 的 app.properties public class appconfig { @value(\u0026#34;${app.zone:z}\u0026#34;) string zoneid; @bean zoneid createzoneid() { return zoneid.of(zoneid); } } 如上,spring 容器看到 @propertysource(\u0026quot;app.properties\u0026quot;) 注解后,就会自动读取这个配置文件,然后,我们使用 @value 正常注入。\n5. @profile 和 @conditional\n创建某个 bean 时,spring 容器可以根据注解 @profile 来决定是否创建,除此之外,也可以根据 @conditional 来决定。\n:: 其实,还有其他的一些,blablabla…… 因为,spring boot 提供了更好的,所以,我们在实际工作中并不怎么一些“老旧”的注解了。\n6. @aspect 和 @enableaspectjautoproxy\n当 spring 的 ioc 容器看到 @enableaspectjautoproxy 这个注解,就会自动查找带有 @aspect 的 bean,然后根据每个方法的 @before、@around 等注解把 aop 注入到特定的 bean 中。\n7. @enabletransactionmanagement 和 @transactional\nspring 提供了一个 platformtransactionmanager 来表示事务管理器,所有的事务都由它负责管理。使用编程的方式使用 spring 事务仍然比较繁琐,更好的方式是通过声明式事务来实现。\n使用声明式事务非常简单,除了在配置类中追加一个定义的 platformtransactionmanager 外,再添加一个 @enabletransactionmanagement 就可启用声明式事务。\n1 2 3 4 5 6 7 @configuration @componentscan @enabletransactionmanagement // 启用声明式 @propertysource(\u0026#34;jdbc.properties\u0026#34;) public class appconfig { // ... } 然后,对需要事务支持的方法,加一个 @transactional 注解。也可以直接加在 bean 的 class 处,它表示其所有 public 方法都具有事务支持。\nspring 对一个声明式事务的方法开启事务支持的原理,仍然是 aop 代理,取了通过自动创建 bean 的 proxy 实现。\n在 spring mvc 中的应用 我们知道,spring 提供的是一个 ioc 容器,所有的 bean 都在该容器中被初始化。而 servlet 容器由 javaee 服务器人提供(如 tomcat), servlet 容器对 spring 一无所知,它们之间依靠什么进行联系?又是以何种顺序初始化的呢? 详细答案请参考 如何关联 servlet 和 spring 。\n只需要在配置类上加上 @enablewebmvc 注意,就激活了 spring mvc 。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // controller 使用@controller 标记而不是@component: @controller public class usercontroller { // 正常使用 @autowired 注入: @autowired userservice userservice; // 处理一个 url 映射: @getmapping(\u0026#34;/\u0026#34;) public modelandview index() { ... } ... } 这里,我们需要注意, controller 使用 @controller 标记,而不是 @component 。(很明显,前者针对 coontroller 做了一些增强)。\n好的,现在我们来回答一下开始的问题 - 如何关联 servlet 和 spring ?\nspring mvc 提供了一个 dispatcherservlet 类,我们只需在 web.xml 中配置它。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 \u0026lt;!doctype web-app public \u0026#34;-//sun microsystems, inc.//dtd web application 2.3//en\u0026#34; \u0026#34;http://java.sun.com/dtd/web-app_2_3.dtd\u0026#34; \u0026gt; \u0026lt;web-app\u0026gt; \u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcher\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;org.springframework.web.servlet.dispatcherservlet\u0026lt;/servlet-class\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;contextclass\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;org.springframework.web.context.support.annotationconfigwebapplicationcontext\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;contextconfiglocation\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;com.itranswarp.learnjava.appconfig\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;load-on-startup\u0026gt;0\u0026lt;/load-on-startup\u0026gt; \u0026lt;/servlet\u0026gt; \u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcher\u0026lt;/servlet-name\u0026gt; \u0026lt;url-pattern\u0026gt;/*\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; \u0026lt;/web-app\u0026gt; servlet 容器会首先初始化 dispatcherservlet ,在 dispatcherservlet 启动时,根据配置类 appconfig 创建一个类型是 webapplicationcontext 的 ioc 容器,完成所有 bean 的初始化,并将该容器绑到 servletcontext 上。\n如此, dispatcherservlet 持有 ioc 容器,自然就可以从 ioc 容器中获取所有的 @controller 的 bean ,在接收到 http 请求后,根据 controller 方法配置的路径转发到指定方法,并根据返回的 modelandview 决定如何渲染页面。\n最后,在配置类 appconfig 中通过 main() 方法启动嵌入式 tomcat 即可。\n1. @controller\n该注解用来标识当前 bean 是一个 controller 。spring mvc 对 controller 没有固定的要求,也不需要实现特定的接口,只需要在 controller 类中,编写对应的方法处理相应的请求路径就可以了。\n2. @getmapping、@postmapping、@requestparam\n1 2 3 4 5 6 7 8 9 10 11 12 13 @controller public class usercontroller { // ... @postmapping(\u0026#34;/signin\u0026#34;) public modelandview dosignin( @requestparam(\u0026#34;email\u0026#34;) string email, @requestparam(\u0026#34;password\u0026#34;) string password, httpsession session) { // ... } } } 一个方法对应一个 http 请求路径,用 @getmapping 或 @postmapping 表示 get 或 post 请求。\n需要接收的 http 参数以 @requestparam() 标注。\n2. @restcontroller\n直接用 spring 的 controller 配合一大堆注解写 rest 太麻烦了,因此,spring 额外提供了一个 @restcontroller 注解,使用它注解 controller,每个方法自动变成 api 接口方法。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @restcontroller @requestmapping(\u0026#34;/api\u0026#34;) public class apicontroller { @autowired userservice userservice; @getmapping(\u0026#34;/users\u0026#34;)\t// 实际为 /api/users,下同 public list\u0026lt;user\u0026gt; users() { return userservice.getusers(); } @getmapping(\u0026#34;/users/{id}\u0026#34;) public user user(@pathvariable(\u0026#34;id\u0026#34;) long id) { return userservice.getuserbyid(id); } @postmapping(\u0026#34;/signin\u0026#34;) public map\u0026lt;string, object\u0026gt; signin(@requestbody signinrequest signinrequest) { try { user user = userservice.signin(signinrequest.email, signinrequest.password); return map.of(\u0026#34;user\u0026#34;, user); } catch (exception e) { return map.of(\u0026#34;error\u0026#34;, \u0026#34;signin_failed\u0026#34;, \u0026#34;message\u0026#34;, e.getmessage()); } } public static class signinrequest { public string email; public string password; } } 如此,编写 rest 接口只需要定义 `@restcontroller ` ,然后每个方法都是一个 api 接口,输入和输出只要能被 jackson 序列化或反序列化为 json 就没有问题。\n3. @crossorigin\n……\n在 spring boot 中的应用 spring boot 是什么?了解 [[spring boot]]。\n1. @springbootapplication\nspring boot 要求 main() 方法所在的启动类必须放到 package 下,命名不作要求。启动 spring boot 应用程序只需要一行代码加上一个注解 @springbootapplication 即可。\n1 2 3 4 5 6 @springbootapplication public class application { public static void main(string[] args) throws exception { springapplication.run(application.class, args); } } 还要啥自行车,直接飞起了 🚀 2. @conditionalonxxx\nspring 本身提供了条件装配 @conditional,但是要自己编写比较复杂的 condition 来做判断,比较麻烦。spring boot 则为我们准备好了几个非常有用的条件,如:\n@conditionalonproperty :如果有指定的配置,条件生效; @conditionalonbean :如果有指定的 bean,条件生效; @conditionalonmissingbean :如果没有指定的 bean,条件生效; @conditionalonmissingclass :如果没有指定的 class,条件生效; @conditionalonwebapplication :在 web 环境中条件生效; @conditionalonexpression :根据表达式判断条件是否生效。 ……\n","date":"2023-05-25","permalink":"https://aituyaa.com/%E6%B3%A8%E8%A7%A3/","summary":"\u003cp\u003e🔔 以下内容主要摘录自 \u003ca href=\"https://www.liaoxuefeng.com/wiki/1252599548343744/1255945389098144\"\u003e廖雪峰老师的博客\u003c/a\u003e,具体示例请跳到原文参考。\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20230525163525.png]]\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:: 好吧,廖老师配图一直可以的。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"注解"},]
[{"content":" 了解浏览器,它已经成为主流的信息传递方式,并将持续壮大!\n了解它的内核、构成及动作方式。\n![[assets/pasted image 20230525174227.png|300]]\n\u0026gt; 蒂姆·伯纳斯·李 (1955.06.08 - )\n2017 年,他因 “发明万维网、第一个浏览器和使万维网得以扩展的基本协议和算法” 而获得 2016 年度的图灵奖。\n世界上第一个网站 http://info.cern.ch/ ,于 1991.8.6 是上网,它解释了万维网是什么,如何使用网页浏览器和如何建立一个网页服务器等等。\n![[assets/pasted image 20230525174242.png]]\n深入理解现代浏览器 1\n本章节来源于 mariko kosaka (小坂真子) 2018 年 9 月在 google 开发者网站上发表的 “inside look at modern web browser” 系列文章。\n![[assets/pasted image 20230525174304.png|200]]\n她的网站: https://kosamari.com/ 她的 twitter: https://twitter.com/kosamari 原文链接:\nhttps://developers.google.com/web/updates/2018/09/inside-browser-part1 https://developers.google.com/web/updates/2018/09/inside-browser-part2 https://developers.google.com/web/updates/2018/09/inside-browser-part3 https://developers.google.com/web/updates/2018/09/inside-browser-part4 本章节分为以下 4 个部分:\n架构:以 chrome 为例,介绍现代浏览器的实现架构; 导航:从输入 url 到获取到 html 响应称为导航; 渲染:浏览器解析 html 、下载外部资源、计算样式并把网页绘制到屏幕上; 交互:用户输入事件的处理与优化。 :: 老外写文章是真的长……配图很仔细!\n准备 一些基础性概念……\ncpu \u0026amp; gpu cpu(central processing unit)和 gpu(graphics processing unit)作为计算机中最重要的两个计算单元直接决定了计算性能。\ncpu 是计算机的大脑,负责处理各种不同的任务。gpu 最初被用于处理图像,更擅长利用多核心同时处理单一的任务。\n计算机架构 ![[assets/pasted image 20230525174409.png|600]]\n如图,可以把计算机自下而上分成三层:硬件、操作系统和应用。有了操作系统的存在,上层运行的应用可以使用操作系统提供的能力使用硬件资源而不会直接访问硬件资源。\n进程与线程 一个进程(process)是应用正在运行的程序,而线程(thread)是进程中更小的部分。当应用被启动时,进程就会被创建出来,程序可以创建线程来帮助其工作,操作系统会为进程分配私有的内存空间以供使用。当关闭程序时,这段私有的内存也会被释放。其实,还有比线程更小的存在 – 协程, async/await 就是基于协程实现的。\n进程间通信(ipc) ![[assets/pasted image 20230525174425.png]]\n一个进程可以让操作系统开户别一个进程处理不同的任务。当两进程需要通信时,可以使用 ipc(inter process communication)。\n多数程序被设计成使用 ipc 来进程进程间的通信,好处在于一个进程给另一个进程发消息而没有回应时,并不影响当前的进程继续工作。\n架构 web 浏览器架构,可以实现为一个进程包含多个线程,也可以实现为很多进程包含少数线程通过 ipc 通信。如何实现浏览器,并没有统一的标准。chrome 最新的架构:最上层是浏览器进程,负责承担各项工作的其他进程,比如实用程序进程、渲染器进程、gpu 进程、插件进程等,如下图所示:\n![[assets/pasted image 20230525174544.png|600]]\n渲染器进程对应新开的标签页,每新开一个标签页,就会创建一个新的渲染器进程。不仅如此,chrome 还会尽量给每个站点新开一个渲染器进程,包括 iframe 中的站点,以实现站点隔离。\n下面详细了解一下每个进程的作用,可以参考下图。\n浏览器进程 :控制浏览器这个应用的 chrome (主框架)部分,包括地址栏、书签、前进/后退按钮等,同时也会处理浏览器不可见的高权限任务,如发送网络请求、访问文件; 渲染器进程 :负责在标签页中显示网站及处理事件; 插件进程 : 控制网站用到的所有插件; gpu 进程 :在独立的进程中处理 gpu 任务。之所以放到独立的进程,是因为 gpu 要处理来自多个应用的请求,但要在同一个界面上绘制图形。 ![[assets/pasted image 20230525174607.png]]\n当然,还有其他进程,比如扩展进程、实用程序进程。要知道你的 chrome 当前打开了多少个进程,点击右上角的按钮,选择“更多工具”,再选择“任务管理器”。\nchrome 的多进程架构有哪些优点呢?\n最简单的情况下,可以想像一个标签页就是一个渲染器进程,比如 3 个标签页就是 3 个渲染器进程。这时候,如果有一个渲染器崩溃了,只要把它关掉即可,不会影响其他标签页。如果所有标签页都运行在一个进程中,那只要有一个标签页卡住,所有标签页都会卡住。\n除此之外,多进程架构还有助于安全和隔离。因为操作系统有限制进程特权的机制,浏览器可以借此限制某些进程的能力。比如,chrome 会限制处理任意用户输入的渲染器进程,不让它任意访问文件。\n由于进程都有自己私有的内存空间,因此每个进程可能都会保存某个公共基础设施(比如 chrome 的 javascript 引擎 v8)的多个副本。这会导致内存占用增多。为了节省内存,chrome 会限制自己可以打开的进程数量,限制的条件取决于设备内存和 cpu 配置,达到限制条件后,chrome 会用一个进程处理同一个站点的多个标签页。\nchrome 架构进化的目标是将整个浏览器程序的不同部分服务化,便于分割或合并。基本思路是在调配设备中,每个服务独立开里程,保证稳定;在低配设备中,多个服务合并为一个进程,节约资源。同样的思路也应用到了 android 上。\n重点说一说 站点隔离 。站点隔离是新近引入 chrome 的一个里程碑式特性,即每个跨站点 iframe 都运行一个独立的渲染器进程。即便像前面说的那样,每个标签页单开一个渲染器进程,但允许跨站点的 iframe 运行在同一个渲染器进程中并共享内存空间,那安全攻击仍然有可能绕开 同源策略 ,而且有人发现在现代 cpu 中,进程有可能读取任意内存。\n进程隔离是隔离站点、确保上网安全最有效的方式。chrome 67 桌面版默认采用站点隔离。站点隔离是多年工程化努力的结果,它并非多开几个渲染器进程那么简单。比如,不同的 iframe 运行在不同进程中,开发工具在后台仍然要做到无缝切换,而且即便简单地 ctrl+f 查找也会涉及在不同进程中搜索。\n导航 导航涉及浏览器进程与线程间为显示网页而通信。一切从用户在浏览器中输入一个 url 开始,输入 url 之后,浏览器会通过互联网获取数据并显示网页。从请求网页到浏览器准备渲染网页的过程,叫做 导航 。\n如前所述,标签页外面的一切都由浏览器进程处理。浏览器进程中包含如下线程:\nui 线程:负责绘制浏览器的按钮和地址栏; 网络线程:负责处理网络请求并从互联网接收数据; 存储线程:负责访问文件和存储数据。 ![[assets/pasted image 20230525174710.png|500]]\n下面我们来逐步看一看导航的几个步骤。\n1. 处理输入 ui 线程会判断用户输入的是查询字符器还是 url(因为 chrome 地址样同时也是搜索框)。\n![[assets/pasted image 20230525174729.png|500]]\n2. 开始导航 如果输入的是 url ,ui 线程会通知网络线程发起网络调用,获取网站内容。此时标签页左端显示旋转图标,网络线程进行 dns 查询、建立 tls 连接(对于 https)。网络线程可能收到服务器的重定向头部,如 http 301 ,此时网络线程会跟 ui 线程沟通,告诉它服务器需求重定向,然后,再发起对另一个 url 的请求。\n![[assets/pasted image 20230525174746.png|500]]\n3. 读取响应 服务器返回的响应体到来之后,网络线程会检查接收到的前几个字节。响应的 content-type 头部应该包含数据类型,如果没有这个字段,则需要 mime 类型嗅探 。看看 chrome 源码 中的注释就知道这一块有多难搞……\n![[assets/pasted image 20230525174805.png|500]]\n如果响应是 html 文件,那下一步就是把数据交给渲染进程。但如果是一个 zip 文件或其他文件,就意味着是一个下载请求,需要把数据传给下载管理器。\n此时也是 “安全浏览” 检查环节。如果域名和响应数据匹配已知的恶意网站,网络线程会显示警告页。此外,corb(cross origin read blocking)检查也会执行,以确保敏感的跨站点数据不会发送给渲染器进程。\n4. 联系渲染器进程 所有查检完毕,网络线程确认浏览器可以导航到用户请求的网站,于是会通知 ui 线程数据已经准备好了,ui 线程会联系渲染器进程渲染网页。\n![[assets/pasted image 20230525174831.png|500]]\n由于网络请求可能要共几百毫秒才能拿到响应,这里还会应用一个优化策略。第二步 ui 线程要求网络线程发送请求后,已经知道可能要导航到哪个网站了。因此在发送网络请求的同时,ui 线程会提前联系或并行启动一个渲染进程。这样在网络线程收到数据后,就已经有渲染器进程原地待命了。如果发生了重定向,这个待命进程可能用不上,而是换作其他进程去处理。\n:: ‘流’行策略\n5. 提交导航 数据和渲染器进程都有了,就可以通过 ipc 从浏览器进程向渲染器进程提交导航,渲染器进程也会同时接收到不间断的 html 数据流。当浏览器进程收到渲染器进程的确认消息后,导航完成,文档加载阶段开始。\n![[assets/pasted image 20230525174902.png|500]] \\\n此时,地址栏会更新,安全指示图标和网站设置 ui 也会反映新页面的信息。当前标签页的会话历史会更新,后退/前进按钮起作用。为便于标签页/会话在关闭标签页或窗口后恢复,会话历史会写入磁盘。\n6. 初始加载完成 提交导航之后,渲染器进程将负责加载资源和渲染页面,而在“完成”渲染后(在所有 iframe 中的 onload 事件触发且执行完成后),渲染器进程会通过 ipc 给浏览器进程发送一个消息。此时,ui 线程停止标签页上的旋转图标。\n初始加载完成后,客户端 javascript 仍然可能加载额外资源并重新渲染页面。\n如果此时用户在地址又输入了其他 url 呢?浏览器进程还会重复上述步骤,导航到新站点。不过在些之前,需要确认已渲染的网站是否关注 beforeunload 事件。因为标签页中的一切,包括 javascript 代码都由渲染器进程处理,所以浏览器进程必须与当前的渲染器进程确认后再导航到新站点。\n![[assets/pasted image 20230525174926.png|500]]\n如果导航请求来自当前渲染器进程(用户点击了链接或 javascript 运行了 window.location = \u0026quot;https://newsite.com\u0026quot; ),渲染器进程首先会检查 beforeunload 处理程序。然后,它会走一遍与浏览器进程触发导航同样的过程,唯一的区别在于导航请求是由渲染器进程提交给浏览器进程的。\n导航到不同的网站时,会有一个新的独立渲染器进程负责处理新导航,而老的渲染器进程要负责处理 unload 之类的事件。更多细节可以参考 “页面生命周期 api” 。\n![[assets/pasted image 20230525174945.png|500]]\n另外,导航阶段还可能涉及 service worker ,即网页应用中的网络代理服务,开发者可以通过它控制什么缓存在本地,何时从网络获取新数据。 service worker 说到底也是需要渲染器进程运行的 javascript 代码。 如果网站注册了 service worker ,那么导航请求到来时,网络线程会根据 url 将其匹配出来,此时 ui 线程就会联系一个渲染器进程来执行 service worker 的代码:可能只要从本地缓存读取数据,也可能需要发送网络请求。\n![[assets/pasted image 20230525175003.png|500]]\n如果 service worker 最终决定从网络请求数据,浏览器进程与渲染器进程间的这种往返通信会导致延迟。因此,这里会有一个“导航预加载”的优化,即在 service worker 启动同时预先加载资源,加载请求通过 http 头部与服务器沟通,服务器决定是否完全更新内容。\n![[assets/pasted image 20230525175021.png|500]]\n渲染 渲染是渲染器进程内部的工作,涉及 web 性能的诸多方面。标签页中的一切都由渲染器进程负责处理,其中主线程负责运行大多数客户端 javascript 代码,少量代码可能由工作线程处理(如果用到了 web worker 或 service worker)、合成器(compositor)线程和栅格化(raster)线程负责高效、平滑地渲染页面。\n![[assets/pasted image 20230525175039.png|600]]\n渲染器进程的核心任务是把 html、css 和 javascript 转换成用户可以交互的网页接下来,我们从整体上过一遍渲染器进程处理 web 内容的各个阶段。\n1. 解析 html 构建 dom 渲染器进程收到导航的提交消息后,开始接收 html,其主线程开始解析文本字符器(html),并将它转换为 dom(document object model,文档对象模型)。\ndom 是浏览器内部对页面的表示,也是 javascript 与之交互的数据结构和 api 。\n如何将 html 解析为 dom 由 html 标准定义。html 标准要求浏览器兼容错误的 html 写法,因此浏览器会“忍气吞声”,绝不报错。详情可以看看 “解析器错误处理及怪异情形简介” 。\n加载子资源 网站都会用到图片、css 和 javascript 等外部资源。浏览器需要从缓存或网络加载这些文件,主线程可以在解析并构建 dom 过程中发现一个加载一个,但这样效率太低。为此,chrome 会在解析同时并发运行“预加载扫描器”,当发现 html 文档中有 \u0026lt;img\u0026gt; 或 \u0026lt;link\u0026gt; 时,预加载扫描器会将请求提交给浏览器进程中的网络线程。\n![[assets/pasted image 20230525175103.png]]\n3.javascript 可能阻塞解析\n如果 html 解析器碰到 \u0026lt;script\u0026gt; 标签,会暂停解析 html 文档并加载、解析和执行 javascript 代码。因为 javascript 有可能通过 document.write() 修改文档,进而改变 dom 结构(html 标准的“解析模型”有一张图可以一目了然: https://html.spec.whatwg.org/multipage/parsing.html#overview-of-the-parsing-model)。 所以 html 解析器必须停下来执行 javascript ,然后再恢复解析 html 。至于执行 javascript 的细节,大家可以关注 v8 团队相关的分享: https://mathiasbynens.be/notes/shapes-ics 。\n提示浏览器你要加载资源。\n2. 计算样式 光有 dom 还不行,因为并不知道页面应该长啥样。所以接下来,主线程要解析 css 并计算每个 dom 节点的样式。这个过程就是根据 css 选择符,确定每个元素要应用什么样式。在 chrome 开发工具“计算的模式”中可以看每个元素计算后的样式。\n![[assets/pasted image 20230525175131.png]]\n就算网页没有提供任何 css ,每个 dom 节点仍然会有计算的样式。这是因为浏览器有一个默认的样式表,chrome 默认的样式在这里: https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/resources/html.css 。\n3. 布局 到这一步,渲染器进程就知道了文档的结构,也知道了每个节点的样式,但基于这些信息仍然不足以渲染页面。比如,你通过电话跟朋友说:“画一个红色的大圆形,还有一个蓝色的小方形”,你的朋友仍然不知道该画成什么样。\n![[assets/pasted image 20230525175154.png|600]] 布局就是要找到元素间的几何位置关系。主线程会遍历 dom 元素及其计算样式,然后构造一棵布局树,这棵树的每个节点将带有坐标和大小信息。布局树与 dom 树的结构类似,但只包含页面中可见元素的信息。如果元素被应用了 display: none ,则布局树中不会包含它( visibility: hidden 的元素会包含在内)。类似的,通过伪类 p::before {content: \u0026quot;hi!\u0026quot;} 添加的内容会包含在布局树中,但 dom 树中却没有。\n![[assets/pasted image 20230525175227.png]]\n确定页面的布局要考虑很多因素,并不简单。比如,字体大小、文本换行都会影响段落的形状,进而影响后续段落的布局。css 可以让元素浮动到一边、隐藏溢出边界的内容、改变文本显示方向。可想而知,布局阶段的任务是非常艰巨的。\n4. 绘制 有了 dom、样式和布局,仍然不足以渲染页面。还要解决先画什么后画什么,即绘制顺序的问题。比如, z-index 影响元素叠放,如果有这个属性,那简单地按元素在 html 中出现的顺序绘制就会出错。\n![[assets/pasted image 20230525175245.png|450]]\n因此,在这一步,主线程会遍历布局树并创建绘制记录。绘制记录是对绘制过程的注解,比如“先画背景,然后画文本,最后画矩形”。如果你用过 \u0026lt;canvas\u0026gt; ,应该更容易理解这一点。\n![[assets/pasted image 20230525175308.png]]\n渲染是一个流水线作业(pipeline):前一道工序的输出就是下一道工序的输入。这意味着如果布局树有变化,则相应的绘制记录也要重新生成。\n![[assets/xr-8.gif]]\n如果元素有动画,浏览器就需要每帧运行一次渲染流水线。目前显示器的刷新率为每秒 60 次(60fps),也就是说每秒 60 帧,动画会显得很流畅。如果中间缺了帧,那页面就会“闪眼睛”。\n![[assets/pasted image 20230525175345.png|550]]\n即便渲染操作的频率能跟上屏幕刷新率,但由于计算发生在主线程上,而主线程可能因为运行 javascript 被阻塞,此时动画会因为阻塞被卡住。\n![[assets/pasted image 20230525175414.png|550]]\n此时,可以使用 requestanimationframe() 将涉及动画的 javascript 操作分块并调度到每一帧的开始去运行。对于耗时的不必操作 dom 的 javascript 操作,可以考虑 web worker ,避免阻塞主线程。\n5. 合成 知道了文档结构、每个元素的样式、页面的几何关系,以及绘制顺序,接下来就该绘制页面了。具体怎么绘制呢?把上述信息转换为屏幕上的像素叫做 栅格化 。\n最简单的方式,可能就是把页面在当前视口中的部分先转换为像素,然后随着用户滚动页面,再移动栅格化的画框(frame),填补缺失的部分。chrome 最早的版本就是这样干的。\n![[assets/xr-11.gif|500]]\n但现代浏览器会使用一个更高级的步骤叫合成。什么是合成? 合成(composite) 是将页面不同部分先分层并分别栅格化,然后再通过独立的合成器线程合成页面。这样当用户滚动页面时,因为层都已经栅格化,所以浏览器唯一要做的就是合成一个新的帧。而动画也可以用这样的方式实现:先移动层,再合成帧。\n![[assets/xr-12.gif|500]]\n怎么分层?为了确定哪个元素应该在哪一层,主线程会遍历布局树并创建分层树(这一部分在开发工具的“性能”面板中叫“update layer tree”)。如果页面某些部分应该独立一层(如滑入的菜单),那你可以在 css 中给它加上 will-change 属性来提醒浏览器。\n![[assets/pasted image 20230525175549.png]]\n分层并不是越多越好,合成过多的层有可能还不如每帧都对页面中的一小部分执行一次栅格化更快。\n创建了分层树,确定了绘制顺序,主线程就会把这些信息提交给合成器线程。合成器线程接下来负责将每一层转换为像素 – 栅格化。一层有可能跟页面一样大,此时合成器线程会将它切成小片(tile),再把每一片发给栅格化线程。栅格化线程将每一小片转换为像素后将它们保存在 gpu 的内存中。\n![[assets/pasted image 20230525175604.png]]\n合成器线程会安排栅格化线程优先转换视口(及附近)的小片,而构成一层的小片也会转换为不同分辨率的版本,以便在用户缩放时使用。\n所有小片都栅格化以后,合成器线程会收集叫做“绘制方块”(draw quad)的小片信息,以创建合成器帧。\n绘制方块:包含小片的内存地址、页面位置等合成页面相关的信息; 合成器帧:由从多绘制方块拼成的页面中的一帧。 创建好的合成器帧会通过 ipc 提交给浏览器进程。与此同时,为更新浏览器界面,ui 线程可能还会添加另一个合成器帧;或者因为有扩展,其他渲染器也可能添加额外的合成器帧。所有这些合成器帧都会发送给 gpu,以便最终显示在屏幕上。如果发生滚动事件,合成器线程会再创建新的合成器帧并发送给 gpu 。\n![[assets/pasted image 20230525175618.png]]\n使用合成的好处是不用牵扯主线程,合成器线程不用等待样式计算或 javascript 执行,这也是为什么“只需合成的动画”被认为性能最佳的原因。因为如果布局和绘制需要再次计算,那还得用到主线程。\n交互 最后,我们看一看合成器如何处理用户交互。说到用户交互,有人可能只会想到在文本框里打字或点击鼠标。实际上,从浏览器的角度看,交互意味着来自用户的任何输入:鼠标滚动转动、触摸屏幕、鼠标悬停,这些都是交互。\n当用户交互比如触摸事件发生时,浏览器进程首先接收该手势。但是,浏览器进程仅仅知道手势发生在哪里,因为标签页中的内容是渲染器进程处理。因此,浏览器进程会把事件类型(如 touchstart )及其坐标发送给渲染器进程,渲染器进程会处理这个事件,即根据事件目标来运行注册的监听程序。\n![[assets/pasted image 20230525175643.png]]\n具体来说,输入事件是由渲染器进程中合成器线程处理的。如前所述,如果页面上没有注册事件监听程序,那合成器线程可以完全独立于主线程生成新的合成器帧。但是如果页面上注册了事件监听程序呢?此时合成器线程怎么知道是否有事件要处理?\n这就涉及一个概念,叫“非快速滚动区”(non-fast scrollable region)。我们知道,运行 javascript 是主线程的活儿。在页面合成后,合成器线程给附加了事件处理程序的页面区域打上 “non-fast scrollable region” 的记号。有了这个记号,合成器线程就可以在该区域发生事件时把事件发送给主线程。\n![[assets/pasted image 20230525175657.png]]\n如果事件发生在这个区域外,那合成器线程会继续合成新帧而不会等待主线程。\n提到注册事件,有一个常见的问题要注意。很多人喜欢使用事件委托来注册处理程序,这是利用事件冒泡原理,把事件注册到最外层元素上,然后再根据事件目标是否执行任务。\n一个事件处理程序就可以面向多个元素,这种高效的写法因此很流行。然而,从浏览器的角度看,这样会导致整个页面被标记为“非快速滚动区”。这也就意味着,即便事件发生在那些不需要处理的元素上,合成器线程也要每次都跟主线程沟通,并等待它的回应。于是,合成器线程平滑滚动的优点就被抵销了。\n![[assets/pasted image 20230525175713.png]]\n为缓冲使用事件委托带来的副作用,可以在注册事件时传入 passive: true 。这个选项会提醒浏览器,你仍然希望主线程处理事件,但与此同时合成器线程也可以继续合成新的帧。\n1 2 3 4 5 6 7 document.body.addeventlistener( \u0026#39;touchstart\u0026#39;, (evt) =\u0026gt; { // ... }, { passive: true } ); 此外,检查事件是否可以取消也是一个优化策略。假设页面中有一个盒子,你想限制盒子中的内容只能水平滚动。使用 passive: true 可以让页面平滑滚动,但为了限制滚动方向而调用 preventdefault 则不会避免垂直滚动,此时可以检查 evt.cancelable 。\n1 2 3 4 5 6 7 8 9 10 11 document.body.addeventlistener( \u0026#39;pointermove\u0026#39;, (evt) =\u0026gt; { if (evt.cancelable) { evt.preventdefault(); // 阻止原生滚动 // ... } }, { passive: true } ); 当然,也可以使用 css 规则,如 touch-action 完全避免使用事件处理程序。\n1 2 3 #area { touch-action: pan-x; } 合成器线程把事件发送给主线程以后,要做的第一件事就是通过测试(hit test)找到事件目标,命中测试就是根据渲染进程生成的绘制记录数据和事件坐标找到下方的元素。\n![[assets/pasted image 20230525175730.png|500]]\n另外,事件还有一个触发频率的问题。通常的触屏设备每秒会产生 60~120 次触碰事件,而鼠标每秒会产生约 100 次事件。换句话说,输入事件具有比每秒刷新 60 次的屏幕更高的保真度。\n如果像 touchmove 这种连续性事件,以每秒 120 次的频率发送到主线程,相比更慢的屏幕刷新率而言,就会导致过多的命中测试和 javascript 执行。\n![[assets/pasted image 20230525175831.png]]\n为把主线程过多的调用降至最少,chrome 会合并(coalesce)连续触发的事件(如 wheel、mousewheel、mousemove、pointermove、touchmove ),并将它们延迟到时恰好在下一次 requestanimationframe 之前派发。\n![[assets/pasted image 20230525175845.png|600]]\n对于其他离散触发的事件,像 keydown、keyup、mouseup、mousedown、touchstart 和 touchend 会立即派发。\n合并后的事件在多数情况下足以保证不错的用户体验。但是,在一些特殊应用场景下,比如需要基于 touchmove 事件的坐标生成轨迹的绘图应用,合并事件就会导致丢失一些坐标,影响所绘线条的平滑度。\n![[assets/pasted image 20230525175915.png|550]]\n此时,可以使用指针事件的 =getcoalescedevents= 方法,取得被合并事件的信息:\n1 2 3 4 5 6 7 8 9 10 11 window.addeventlistener(\u0026#39;pointermove\u0026#39;, (evt) =\u0026gt; { const events = evt.getcoalescedevents(); for (let evt of events) { const x = evt.pagex; const y = evt.pagey; // 使用 x 和 y 坐标画线 // ... } }); 这是个小小的结尾,相信不少前端开发者早已知道给 \u0026lt;script\u0026gt; 标签添加 defer、async 属性的作用。通过阅读本文,你应该也知道了为什么在注册事件监听器时最好传入 passive: true 选项,知道了 css 的 will-change 属性让浏览器做出不同的决策。事实上,不止上面这些,看完看懂篇文章,你甚至也会对其他关于浏览器性能优化的细节感到豁然开朗,从而对更多关于网页性能的话题会产生兴起。而这正是深入理解现代浏览器的重要意义和价值所在,因为它为我们打开了一扇大门。\n总结 至此,我们已经可能通过从用户在浏览器地址栏中的一次输入到页面图像的显示了解浏览器是如何工作的,小结如下:\n浏览器进程做为最重要的进程负责大多数页签外部的工作,包括地址栏显示、网络请求、页签状态管理等; 不同的渲染进程负责不同的站点渲染工作,渲染进程间彼此独立; 渲染进程在渲染页面的过程中会通过浏览器进程获取站点资源,只有安全的资源才会被渲染进程接收到; 渲染里程中主线程负责除了图像生成图像生成外绝大多数工作,如何减少主线程上代码的运行是交互性能的关键; 渲染进程中的合成线程和栅格线程负责图像生成,利用分层技术可以优化图像生成的效率; 当用户与页面发生交互时,事件的传播途径从浏览器进程到渲染进程的合成线程再根据事件监听的区域决定是否要传递给渲染进程的主线程处理。 ","date":"2023-05-25","permalink":"https://aituyaa.com/%E6%B5%8F%E8%A7%88%E5%99%A8/","summary":"\u003cblockquote\u003e\n\u003cp\u003e了解浏览器,它已经成为主流的信息传递方式,并将持续壮大!\u003cbr\u003e\n了解它的内核、构成及动作方式。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e![[assets/Pasted image 20230525174227.png|300]]\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e\u0026gt; 蒂姆·伯纳斯·李 (1955.06.08 - )\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e2017 年,他因 “发明万维网、第一个浏览器和使万维网得以扩展的基本协议和算法” 而获得 2016 年度的图灵奖。\u003c/p\u003e","title":"浏览器"},]
[{"content":"🔔 参考 https://www.cnblogs.com/wjw1014/p/13564175.html\n简介 浏览器的默认滚动条往往都不怎么好看…… 让我们对它来做一些调整吧!\n假如我们页面的页面是这样的:\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;!doctype html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;title\u0026gt;滚动条样式\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026#34;div__scroll-bar\u0026#34;\u0026gt;...\u0026lt;/div\u0026gt; \u0026lt;div\u0026gt;...\u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 修改某个元素的滚动条 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 /* 设置一个元素的宽高(以使其内容滚动) */ #div__scroll-bar { width: 300px; height: 200px; border: 1px solid red; overflow: scroll; } /* 滚动条整体部分(width 纵向滚动条 height 横向滚动条) */ #div__scroll-bar::-webkit-scrollbar { width: 10px; height: 10px; } /* 滚动条的轨道(里面装有 thumb) */ #div__scroll-bar::-webkit-scrollbar-track { background-color: #afa; } /* 内层轨道,滚动条中间部分(除去两侧用于微调的 button 和交汇区) */ #div__scroll-bar::-webkit-scrollbar-track-piece { background-color: #f00; } /* 滚动条里面的小方块 */ #div__scroll-bar::-webkit-scrollbar-thumb { background-color: pink; border-radius: 10%; -webkit-box-shadow: inset 0 0 5px #880d0d; } #div__scroll-bar::-webkit-scrollbar-thumb:hover { background: #333; } /* 滚动条的轨道的两端按钮,允许通过点击微调小方块的位置 */ #div__scroll-bar::-webkit-scrollbar-button { background-color: rgb(22, 182, 27); /* display: none; */ } /* 边角,即两个滚动条的交汇处 */ #div__scroll-bar::-webkit-scrollbar-corner { background: #179a16; } 修改浏览器默认滚动条样式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /* 整个滚动条 */ ::-webkit-scrollbar { width: 5px; height: 5px; } /* 滚动条有滑块的轨道部分 */ ::-webkit-scrollbar-track-piece { background-color: transparent; border-radius: 5px; } /* 滚动条滑块(竖向:vertical 横向:horizontal) */ ::-webkit-scrollbar-thumb { cursor: pointer; background-color:#bbb; border-radius: 5px; } /* 滚动条滑块hover */ ::-webkit-scrollbar-thumb:hover { background-color: #999; } /* 同时有垂直和水平滚动条时交汇的部分 */ ::-webkit-scrollbar-corner { display: block; /* 修复交汇时出现的白块 */ } 总结 参数 说明 ::-webkit-scrollbar 滚动条整体部分 ::-webkit-scrollbar-track 滚动条的轨道 ::-webkit-scrollbar-thumb 滚动条里面的小方块 ::-webkit-scrollbar-button 滚动条的轨道的两端微调按钮 ::-webkit-scrollbar-track-piece 内层轨道,滚动条中间部分 ::-webkit-scrollbar-corner 边角,即两个滚动条的交汇处 ::-webkit-resizer 两个滚动条的交汇处上用于通过拖动调整元素大小的小控件 ","date":"2023-05-25","permalink":"https://aituyaa.com/%E6%BB%9A%E5%8A%A8%E6%9D%A1%E6%A0%B7%E5%BC%8F/","summary":"\u003cp\u003e🔔 参考 \u003ca href=\"https://www.cnblogs.com/wjw1014/p/13564175.html\"\u003ehttps://www.cnblogs.com/wjw1014/p/13564175.html\u003c/a\u003e\u003c/p\u003e","title":"滚动条样式"},]
[{"content":" 转载自 https://www.jb51.net/hardware/cpu/610350.html\n2018 年 4 月 3 日,intel 在北京举办了第八代酷睿移动处理器全球发布会,正式发布了全新的第八代酷睿移动处理器。很多小伙伴其实对 intel 的芯片很感兴趣,但是一但深入了解,很快就会被扑面而来的晶体管,微架构,纳米,cpi,睿频,超频,缓存等概念搞疯………\n![[assets/pasted image 20230525155312.png]]\n为何 cpu 工作如此迅速,为何主频变化多端,纳米级别的工艺到底是外星文明的帮助还是人类智慧的结晶,一片薄薄硅圆晶上到底隐藏着怎样的秘密,就让我们大家一起走一走“芯”吧……\n先说说,芯片到底是个什么东西。\n芯片,就是我们说的 cpu,又名中央处理器,常说芯片是一个机器的大脑,重要性可见一斑,它负责计算机多项工作的完成,快速处理数据,并把工作分类并传输指令给计算机的其它部件,这有点像一个物流中心,要把源源不断又种类繁多的数据“包裹”分析,分类,处理,传送……\n所以,要不我们就把它比作一个物流中心好了,为方便理解,我们就从甄老板要开一个物流厂说起吧……\n![[assets/pasted image 20230525155324.png]]\n从沙子到芯片 甄老板要开一个物流厂,首先第一件事,当然是要选一块地,芯片物流厂的用地要求比较奇怪,它是需要建在沙子上的,因为工厂要顺利运行,需要一种沙子里面含有的特殊元素,si(硅),这也就是我们熟知的 cpu 是由沙子造的。\n但是我们知道沙子上是不能直接建厂的,所以沙子需要特殊处理,这个处理就是通过一系列的提纯及切割,获得芯片基座-圆晶的过程。\n![[assets/pasted image 20230525155338.png]]\n圆晶(wafer)是芯片的基础,是由硅纯度 99.9999% 的硅锭切割成的薄片。在这个基础上,芯片制造商们将展开体现人类制造智慧的顶级工艺。当然,为了好理解,我们还是回到工厂的建造上来吧。\n纳米技术及微架构 好了,现在工厂有了地,但是怎么建造流水线设施,让工人好好干活呢?\n![[assets/pasted image 20230525155420.png]]\n当然得找个牛一点的设计师先设计一下厂房的构造,越牛的设计师,设计得越精巧,就是我们说的“工艺”,随着时代的进步,设计师们的精细度也从 45nm 级的设计,逐步精细化到 32,22,甚至现在的第八代 14nm,工艺越好,能容纳的工作单位(晶体管)就越多,一个针尖就有高达 5k 晶体管,当然也就意味着可以在工厂干活的人也越来越多,干活的速度也就能越来快啦……\n![[assets/pasted image 20230525155432.png]]\n设计师们还得去设计厂房的具体布局,比如这条流水线应该从左往右,还是从上到下呢?这种布局就叫微架构,当然布局过程非常复杂,介于我们都不是学建筑的,具体的工序也没必要特别了解,通俗点讲可以看作是一个在晶圆上做微雕,雕出众多晶体管的过程……这个东东的技术性和复杂性都已经登峰造极了,下方的部分工艺大家随意感受下就行……\n![[assets/pasted image 20230525155444.png]]\n架构决定了芯片的性能和功能,设计师们为了工人们能更快,更好的干活,并且能处理更多新出现的任务,在架构上简直绞尽脑汁,尽善尽美,所以在工艺进步的同时,也经常在架构上推陈出新,比如第八代 intel 就是“coffee lake-h”设计代替了前一代的“kabylake-h”的设计。\n![[assets/pasted image 20230525155454.png]]\n不过微微吐槽一句,intel 的架构命名还真是好喜欢用地名啊,不是这个湖就是那个桥,可能是想表明他们就是个跟土建沙石有关的公司吧(……大误)。\n晶体管做完之后,后续还有一系列的测试,切割,封装等工序,圆晶就变成了一块块芯片,我们的工厂也就建好啦。\n![[assets/pasted image 20230525155513.png|350]]\n虽然讲的很简单,但其实背后有很多的尖端工艺,intel 作为金字塔的顶端,也只有 70% 的成功率,所以芯片行业真是汇集了精英资本和精英智慧的一个产业啊,不是高帅富的大佬还真的玩不起,当然这也是为什么,世界上的芯片制造商屈指可数的原因。\n![[assets/pasted image 20230525155535.png|225]]\n工厂总算建好啦,工人们各就各位,开始干活吧……\n时钟频率与核心 人和厂都准备好了,但是人实在太多了,如果不好好管理的话,也会乱套,所以甄老板找了个大喇叭统一喊“1,2,1,2”,让工人们有节奏地干活,这个“1,2,1,2”就是时钟频率。\n通常意义上我们把它认为是 cpu 的主频,主频越高,说明工人们干活越快,cpu 的速度也就越快,一般我们能看到就是 xxghz 这样的显示,数值越高,说明速度越快。\n![[assets/pasted image 20230525155607.png]]\n当然甄老板为了效率能更高点,可能会同时建 2 个或者 4 个厂房,就我们熟知的双核 cpu,4 核 cpu,这样通过协同,工人们的效率就更高,单位时间能处理的活也就越多。\n而这样根据不同的等级,cpu 也就有了 i3,i5,i7 之分(现在还出现了 i9 级 cpu),等级越高,主频表现越好,工厂越有效率,而在第八代的 intel 芯片中,又有了划时代的产品:首次出现了 6 核的移动平台 cpu。\n![[assets/pasted image 20230525155624.png|400]]\n这颗芯将主流移动计算提身高了 6 核的时代(需要说明的是服务器及商用领域其实早就已经出现 6 核,但是移动平台是首次),当然,我们在这一张图中除了看到熟悉的 14 纳米制程工艺,核心数之外,还看到了另外的参数,一是功耗,一是线程,功耗我们放后面说,先说说线程。\n线程、睿频、超频 还是举工厂的例子,甄老板在厂房布局(微架构)及工人数量(晶体管)的布置上已经到了极限,还想提高效率怎么办呢?就要说到我们下面的概念了线程和超线程。\n线程其实是个虚拟概念,类似于工厂外面排好等待处理的包裹队列,一般每个核心对应着一个线程,包裹按顺序进入工厂,这样 cpu 就可以有条不稳地工作了\n而超线程相当于一个核心对应了两条队列,两条队列上的包裹按工厂的处理能力依次进入厂房,道路宽了,也让整个进程更有效率。\n![[assets/pasted image 20230525155650.png]]\n然后工厂这边,任务突然多了起来,处理速度也要相应的提高,甄老板想到了一个办法,号召想多干活的工人们去拿加班奖励,一部分工人当然积极响应,多出来的任务很快完成了,这个自主接收激励的动作,就叫睿频。\n睿频:是指当启动一个运行程序后,cpu 会自动加速到合适的频率,而原来的运行速度会提升 10%~20% 以保证程序流畅运行的一种技术。睿频是 intel 的一个重要技术,也是智能 cpu 的基础,这项技术被运用到了 intel 的全系列,包括第八代 cpu。\n下图就是最新的睿频技术,可以提升至 4.8ghz,完成峰值任务 so easy!\n![[assets/pasted image 20230525155724.png|450]]\n当然,有时候工厂的活特别多,自愿去加班的工人都完成不了了。这时候甄老板不得不放出了“全体人必须加班“的大招,将工厂的速度强制提升到某一个值上,这个动作叫做超频。\n超频:用户强制将处理器的所有内核运行在规格限定频率范围之外,以求更好的速度。这个动作通过给 cpu 加压实现的,虽然可以提升效率,但有时候也会损伤元件,各位少侠,请慎重使用哦。\n![[assets/pasted image 20230525155742.png]]\n缓存与傲腾 经过一系列的处理,工厂的生意越来越好,但是甄老板渐渐发现,这个厂,有!问!题!\n因为他发现,每次包裹传送带都要从很远的“内存仓库“甚至更远的“硬盘仓库”传送,路途遥远,耗费时常不说,还经常丢失包裹。\n![[assets/pasted image 20230525155800.png]]\n痛定思痛,甄老板决定在厂房的周围自建仓库以临时存储待处理的包裹,离厂房最近且最快的叫一级仓库,但因为地价比较贵,所以一级仓库的面积最小,以此类推,又建了二级仓库,三级仓库,三个仓库配合无间,让包裹能不间断地运送到工厂。\n这就是 cpu 一,二,三级缓存的概念(下图中其实只能看到三级,因为一二级太小了)。\n![[assets/pasted image 20230525155812.png|325]]\n后来 intel 认为,3 级缓存已经跟不上现在的时代,所以在最近的第八代 cpu 中,直接在芯片上建了了内存仓库大小的超级缓存,就是“傲腾”。\n这个可选配的“傲腾”内存,这大大提高了用户的峰值速度,也让电脑在大任务面前,终于可以轻松起飞。\n![[assets/pasted image 20230525155835.png|475]]\n能耗 刚刚借着 intel 第八代 cpu 普及了很多芯片上的参数,最后说说能耗一般效率越高的工厂,当然能耗就越多,放在 cpu 上,意味着更废电,以及发热更大,所以保证运行的稳定,cpu 主频往往到一定阶段就不再上升了,能耗性能如何平衡?也是考验 cpu 厂商的一个关键。\n不过 intel 在这方面处理得还是相当不错的,可以说是极大的兼顾了速度与稳定在第八代的 intel i9 cpu 中还首次实现了移动智能不锁频的技术,可以说是很艺高人胆大了。\n![[assets/pasted image 20230525155854.png|450]]\n结语 熟悉 intel 的人对这个发布会还是有所意料,因为毕竟我们都已经知道,intel 遵循 tick-tock 开发模式,两年更新一个工艺,隔年更新微架构,不过这两年 intel 在工艺上已经遥遥领先于对手,所以新 cpu 在工艺上的更新并没有太大,但转而在架构上进行调整,以适应现阶段如 4k、vr、娱乐、游戏、无线技术等更应用化也更新的多任务的处理。\n![[assets/pasted image 20230525155907.png|450]]\n不管怎么说,一代确实比一代好了,而随着时代的发展,intel 也逐渐由原来单纯的芯片厂商,逐渐开始在自家的处理器上集成显卡、内存(四级缓存),不满足做个快递大厂,开始做店面,还包了飞机火车轮船,直接开始打开了地图打野模式……\n当然乐见的是,技术的革新为新硬件的诞生也提供了坚实的基础。\n未来已来,我们拭目以待!\n","date":"2023-05-25","permalink":"https://aituyaa.com/%E7%94%A8%E6%B2%99%E5%AD%90%E9%80%A0%E7%9A%84-cpu-%E5%87%AD%E4%BB%80%E4%B9%88%E5%8D%96%E7%9A%84%E9%82%A3%E4%B9%88%E8%B4%B5/","summary":"\u003cblockquote\u003e\n\u003cp\u003e转载自 \u003ca href=\"https://www.jb51.net/hardware/cpu/610350.html\"\u003ehttps://www.jb51.net/hardware/cpu/610350.html\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e2018 年 4 月 3 日,Intel 在北京举办了第八代酷睿移动处理器全球发布会,正式发布了全新的第八代酷睿移动处理器。很多小伙伴其实对 intel 的芯片很感兴趣,但是一但深入了解,很快就会被扑面而来的晶体管,微架构,纳米,CPI,睿频,超频,缓存等概念搞疯………\u003c/p\u003e","title":"用沙子造的 cpu 凭什么卖的那么贵"},]
[{"content":"i.e. cross domain\n🔔 本文摘录自 浏览器同源政策及其规避方法\n什么是跨域 跨域,是指浏览器不能执行其他网站的脚本,它是浏览器的同源策略造成的,是浏览器对 javascript 实施的安全限制。\n那么,什么是同源策略呢?\n同源策略,是由 netscape 公司于 1995 年引入浏览器的,目前,所有浏览器都实行这个策略。\n为什么需要同源策略呢?\n同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。\n:: 每一件事物都有其产生的源头……\n最初,它的含义是,a 网页设置的 cookie ,b 网页不能打开,除非这两个网页“同源” – 协议相同、域名相同、端口相同。\n:: cookie 往往用来保存用户的登录状态,所以“同源策略”是必需的,否则 cookie 可以共享,互联网就毫无安全可言了。\n随着互联网的发展,“同源策略”越来越严格。目前,如果非同源,共有三种行为受到限制:\ncookie、localstorage 和 indexdb 无法读取; dom 无法获得; ajax 请求不能发送。 跨域的限制当然是有必要的,但实际应用过程中也常常需要在保证相对安全的情况下规避上述的三种限制。\n:: 往往是这样,建立的束缚就是用来打挣脱的…… 在同源策略不允许跨域的那一刻,也就产生了需要跨域的需求。\ncookie cookie 是服务器写入浏览器的一小段信息 ,只有同源网页才能共享。但是,如果两个网页的一级域名相同,只是二级域名不同,浏览器允许通过设置 =document.domain= 共享 cookie 。\n*注:cookie 是服务器写入浏览器的信息哦!\n举例来说,a 网页是 http://w1.example.com/a.html ,b 网页是 http://w2.example.com/b.html ,那么只要设置相同的 document.domain ,两个网页就可以共享 cookie 。\n1 document.domain = \u0026#39;example.com\u0026#39;; 现在,a 网页通过脚本设置一个 cookie :\n1 document.cookie = \u0026#39;test1=hello\u0026#39;; b 网页就可以读到这个 cookie :\n1 var allcookie = document.cookie; 注:这种方法只适用于 cookie 和 iframe 窗口,localstorage 和 indexdb 无法通过这种方法(要使用 postmessage api)。\n另外,服务器也可以在设置 cookie 的时候,指定 cookie 的所属域名为一级域名,比如 .example.com :\nset-cookie: key=value; domain=.example.com; path=/ 如此,二级域名和三级域名不用做任何设置,都可以读取这个 cookie 。\n:: 旨在理解吧,已经是被时代淘汰了的东东了……\niframe 如果两个网页不同源,就无法拿到对方的 dom ,典型的例子是 iframe 窗口和 window.open 方法打开的窗口,它们与父窗口无法通信。\n如果两个窗口一级域名相同,只是二级域名不同,那么设置 document.domain 属性,就可以规避同源策略,拿到 dom 。\n对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题:\n片段识别符(fragment identifier); window.name ; 跨文档通信 api (cross-document messaging)。 片段识别符 片段识别符 (fragment identifier),是指 url 的 # 号后面的部分,如 http://example.com/x.html#fragment 的 #fragment 。如果,只是改变片段识别符,页面不会重新刷新。\n父窗口可以把信息,写入子窗口的片段识别符,如下:\n1 2 var src = originurl + \u0026#39;#\u0026#39; + data; document.getelementbyid(\u0026#39;myiframe\u0026#39;).src = src; 子窗口通过监听 hashchange 事件得到通知,如下:\n1 2 3 4 5 6 window.onhashchange = checkmessage; function checkmessage() { var message = window.location.hash; // ... } 同样的,子窗口也可以改变父窗口的片段标识符:\n1 parent.location.href = target + \u0026#39;#\u0026#39; + hash; window.name 浏览器窗口有 window.name 属性,它的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页就可以读取它。\n来看一个具体的例子吧。\n父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入 window.name 属性,如下:\n1 window.name = data; 接着,子窗口跳回一个与主窗口同域的网址:\n1 location = \u0026#39;http://parent.url.com/xxx.html\u0026#39;; 然后,主窗口就可以读取子窗口的 window.name 了:\n1 var data = document.getelementbyid(\u0026#39;myframe\u0026#39;).contentwindow.name; 这种方法的优点是, window.name 容量很大,可以放置非常长的字符串;缺点是必须监听子窗口 window.name 属性的变化,影响网页性能。\n:: 不强制兼容 ie 的话,实在不想碰这玩意儿。..\nwindow.postmessage 片段识别符和 window.name 两种方法都属于破解,html5 为了解决这个问题,引入了一个全新的 api – 跨文档通信 api (cross-document messaging)。\n:: 所谓破解,就是把公鸡的工作交给大鹅去做,虽然也能叫你起床,但总归不那么悦耳。\n这个 api 为 window 对象新增了一个 window.postmessage 方法,允许跨窗口通信,不论这两个窗口是否同源。\n父窗口 http://aaa.com 向子窗口 http://bbb.com 发消息,调用 postmessage 方法就可以了,如下:\n1 2 var popup = window.open(\u0026#39;http://bbb.com\u0026#39;, \u0026#39;title\u0026#39;); popup.postmessage(\u0026#39;hello world!\u0026#39;, \u0026#39;http://bbb.com\u0026#39;); 其中, postmessage 方法的:\n第一个参数是具体的信息内容; 第二个参数是接收消息的窗口的源(origin),即“协议+域名+端口”,也可以设为 * ,表示不限制域名,向所有窗口发送。 子窗口向父窗口发送消息的写法类似:\n1 window.opener.postmessage(\u0026#39;nice to see you\u0026#39;, \u0026#39;http://aaa.com\u0026#39;); 父窗口和子窗口都可以通过 message 事件,监听对方的消息:\n1 2 3 4 5 6 7 window.addeventlistener( \u0026#39;message\u0026#39;, function (e) { console.log(e.data); }, false ); message 事件的事件对象 event ,提供了以下三个属性:\nevent.source :发送消息的窗口; event.origin :消息发向的网址; event.data :消息内容。 下面的例子是,子窗口通过 event.source 属性引用父窗口,然后发消息,如下:\n1 2 3 4 5 window.addeventlistener(\u0026#39;message\u0026#39;, receivemessage); function receivemessage(event) { event.source.postmessage(\u0026#39;nice to see you!\u0026#39;, \u0026#39;*\u0026#39;); } event.origin 属性可以过滤不是发给本窗口的消息:\n1 2 3 4 5 6 7 8 9 10 window.addeventlistener(\u0026#39;message\u0026#39;, receivemessage); function receivemessage(event) { if (event.origin !== \u0026#39;http://aaa.com\u0026#39;) return; if (event.data === \u0026#39;hello world\u0026#39;) { event.source.postmessage(\u0026#39;hello\u0026#39;, event.origin); } else { console.log(event.data); } } 通过 window.postmessage ,读写其他窗口的 localstorage 也成为了可能。\n:: 其实吧,只要是能序列化字符串的信息,都可以传递。\n例如,主窗口写入 iframe 子窗口的 localstorage :\n1 2 3 4 5 6 7 8 window.onmessage = function (e) { if (e.origin !== \u0026#39;http://bbb.com\u0026#39;) { return; } var payload = json.parse(e.data); localstorage.setitem(payload.key, json.stringfy(payload.data)); // ?? payload.data }; 其中,父窗口发送消息的代码如下:\n1 2 3 4 var win = document.getelementbytagname(\u0026#39;iframe\u0026#39;)[0].contentwindow; var obj = { name: \u0026#39;jack\u0026#39; }; win.postmessage(json.stringfy({ key: \u0026#39;storage\u0026#39;, data: obj }), \u0026#39;http://bbb.com\u0026#39;); :: 下面来个加强版的。..\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 加强版的父窗口发消息 /// var win = doucment.getelementbytagname(\u0026#39;iframe\u0026#39;)[0].contentwindow; var obj = { name: \u0026#39;jack\u0026#39; }; // 存入对象 win.postmessage( json.stringfy({ key: \u0026#39;storage\u0026#39;, method: \u0026#39;set\u0026#39;, data: obj }), \u0026#39;http://bbb.com\u0026#39; ); // 读取对象 win.postmessage(json.stringfy({ key: \u0026#39;storage\u0026#39;, method: \u0026#39;get\u0026#39; }), \u0026#39;*\u0026#39;); window.onmessage = function (e) { if (e.origin != \u0026#39;http://aaa.com\u0026#39;) return; console.log(json.parse(e.data).name); // → \u0026#34;jack\u0026#34; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 加强版的子窗口接收消息 /// window.onmessage = function (e) { if (e.origin !== \u0026#39;http://bbb.com\u0026#39;) return; var payload = json.parse(e.data); switch (payload.method) { case \u0026#39;set\u0026#39;: localstorage.setitem(payload.key, json.stringfy(payload.data)); break; case \u0026#39;get\u0026#39;: var parent = window.parent; var data = localstorage.getitem(payload.key); parent.postmessage(data, \u0026#39;http://aaa.com\u0026#39;); break; case \u0026#39;remove\u0026#39;: localstorage.removeitem(payload.key); break; } }; :: 从本质上来说,这几种方法都是利用浏览器的某些信息保留机制,把信息序列化为字符串,以参数形式在跨域窗口之间的传递。\najax 浏览器的同源策略规定, ajax 请求只能发给同源的网址 ,否则就报错。\n除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制: jsonp、websocket、cors 。\n:: 这里架设的服务器是用来代理客户端的,是正向代理。\njsonp jsonp 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。\n:: 很简单,但缺点在于 jsonp 只支持 get 请求。\njsonp 的基本思想是什么?\n原来,网页通过添加一个 \u0026lt;script\u0026gt; 元素,向服务器请求 json 数据的做法是不受同源策略限制的;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。\n:: 通过 script 标签的 src 属性值作为请求地址,666 。\n首先,网页动态插入 \u0026lt;script\u0026gt; 元素,由它向跨源网址发出请求,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 function addscripttag(src) { var script = doucment.createelement(\u0026#39;script\u0026#39;); script.setattribute(\u0026#39;type\u0026#39;, \u0026#39;text/javascript\u0026#39;); script.src = src; document.body.appendchild(script); } window.onload = function () { addscripttag(\u0026#39;http://example.com/jp?callback=foo\u0026#39;); }; function foo(data) { console.log(\u0026#39;your public ip address is: \u0026#39; + data.ip); } 上述代码通过动态添加 \u0026lt;script\u0026gt; 元素,向服务器 example.com 发出请求。\n注:该请求的查询字符串有一个 callback 参数,用来指定回调函数的名字,在对于 jsonp 是必需的。\n服务器收到这个请求后,会将数据放在回调函数的参数位置返回,如下:\n1 2 3 foo({ ip: \u0026#39;8.8.8.8\u0026#39;, }); 由于 \u0026lt;script\u0026gt; 元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了 foo 函数,该函数就会立即调用。作为参数的 json 数据被视为 javascript 对象,而不是字符串,因此避免了使用 json.parse 的步骤。\nwebsocket websocket 是一种通信协议,使用 ws:// (非加密)和 wss:// (加密)作为协议前缀。该协议 不实行同源策略 ,只要服务器支持,就可以通过它进行跨源通信。\n来看个例子,下面是浏览器发出的 websocket 请求的头信息:\nget /chat http/1.1 host: server.example.com upgrade: websocket connection: upgrade sec-websocket-key: x3jjhmbdl1ezlkh9gbhxdw== sec-websocket-protocol: chat, superchat sec-websocket-version: 13 origin: http://example.com 其中, origin 表示该请求的请求源(origin),即发自哪个域名。\n注:正是因为有了 origin 这个字段,websocket 才没有实行同源策略,因为服务器可以根据这个字段,判断是否允许本次通信。\n如果该域名在白名单内,服务器就会做出如下响应:\nhttp/1.1 101 switching protocols upgrade: websocket connection: upgrade sec-websocket-accept: hsmrc0smlyukagmm5oppg2hagwk= sec-websocket-protocol: chat cors cors (cross-origin resource sharing)是跨域资源分享的缩写,它是 w3c 标准,是 跨源 ajax 请求的根本解决办法。相比 jsonp 只能发 get 请求,cors 允许任何类型的请求。\ncors 详解 cors 允许浏览器向跨源服务器,发出 xmlhttprequest 请求,从而克服了 ajax 只能同源使用的限制。接下来,我们来深入认识下 cors 的内部机制。\ncors 需要浏览器和服务器同时支持, 整个 cors 通信过程,都是浏览器自动完成 ,不需要用户参与。\n对于开发者来说,cors 通信与同源的 ajax 通信没有差别,代码完全一样。浏览器一旦发现 ajax 请求资源,就会自动添加一些附加的头信息 ,有时还会多出一次附加的请求,但用户不会有感觉。\n:: 浏览器是个好宿主,直接帮你(用户)解决了。本质上讲,cors 在浏览器端而言,就是浏览器对用户的 ajax 请求的“封装代理”。\n因此,实现 cors 通信的关键是服务器,只要服务器实现了 cors 接口,就可以跨源通信。\n两种请求 浏览器将 cors 请求分为两类: 简单请求(simple request)和非简单请求(not-so-simple request)。\n只要同时满足以下两在条件,就属于简单请求,如下:\n请求方法是以下三种之一: head、get、post ; http 的头信息不超出以下几种字段: - accept - accept-language - content-language - last-event-id - content-type 其中, content-type 只限于三个值 application/x-www-form-urlencode、multipart/form-data、text/plain 。\n这是为了兼容表单,因为历史上表单一直可以发出跨域请求。ajax 的跨域设计就是,只要表单可以发,ajax 就可以直接发。\n注:凡是不同时满足上面两个条件,就属于非简单请求,浏览器对这两种请求的处理,是不一样的。\n简单请求 1. 基本流程\n对于简单请求,浏览器直接发出 cors 请求,具体来说,就是在头信息中,增加一个 origin 字段。\n如下面这个例子,浏览器发现这次跨源 ajax 请求是简单请求,就自动在头信息中,添加一个 origin 字段:\nget /cors http/1.1 origin: http://api.bob.com ← host: api.alice.com accept-language: en-us connection: keep-alive user-agent: mozilla/5.0... 上面的头信息中, origin 字段用来说明,本次请求来自哪个源(协议+域名+端口),服务器根据这个值决定是否同意这次请求。\n1.1 不在服务器许可范围内\n如果 origin 指定的源不在许可范围内,服务器会返回一个正常的 http 回应。浏览器发现这个回应的头信息没有包含 access-control-allow-origin 字段,就知道出错了,从面抛出一个错误,被 xmlhttprequest 的 onerror 回调函数捕获。\n注:这种错误无法通过状态码识别,因为 http 回应的状态码可能是 200 。\n1.2 在服务器许可范围内\n如果 origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段,如下:\naccess-control-allow-origin: http://api.bob.com access-control-allow-credentials: true access-control-expose-headers: foobar content-type: text/html; charset=utf-8 上面的信息头中,有三个与 cors 请求相关的字段,都以 access-control- 开头。\n(1)access-control-allow-origin\n该字段是必须的,它的值要么是请求时 origin 字段的值,要么是一个 * (表示接受任意域名的请求)。\n(2)access-control-allow-credentials\n该字段可选,它的值是一个布尔值,表示是否允许发送 cookie 。\n默认情况下,cookie 不包括在 cors 请求之中,设为 true ,即表示服务器明确许可 cookie 可以包含在请求中,一起发给服务器。\n*注:其实,这个值也只能设为 true ,如果服务器不要浏览器发送 cookie ,删除该字段即可。\n(3)access-control-expose-headers\n该字段可选,cors 请求时, xmlhttprequest 对象的 getresponseheader() 方法只能拿到 6 个基本字段: cache-control、content-language、content-type、expires、last-modified、pragma 。如果想拿到其他字段,就必须在 access-control-expose-headers 里面指定。如下面的例子中就指定了 foobar ,如此便可以通过 getresponseheader('foobar') 来返回 foobar 字段的值。\n2. withcredentials 属性\n上面说到,cors 请求默认不发送 cookie 和 http 认证信息。如果要把 cookie 发到服务器,一方面要服务器同意,指定 access-control-allow-credentials 字段:\naccess-control-allow-credentials: true 另一方面,开发者必须在 ajax 请求中打开 withcredentials 属性,如下:\n1 2 var xhr = new xmlhttprequest(); xhr.withcredentials = true; 否则,即使服务器同意发送 cookie ,浏览器也不会发送。或者,服务器要求设置 cookie ,浏览器也不会处理。\n但是,如果省略 withcredentials 设置,有的浏览器还是会一起发送 cookie 。这时,可以显式关闭 withcredentials :\n1 xhr.withcredentials = false; 需要注意的是,如果要发送 cookie , access-control-allow-origin 就不能设为星号,必须指定的、与请求网页一致的域名。同时,cookie 依然遵循同源政策,只有服务器域名设置的 cookie 才会上传,其他域名的 cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 cookie 。\n非简单请求 预检请求 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 put 或 delete ,或者 content-type 字段的类型是 application/json 。\n非简单请求的 cors 请求,会在正式通信之前,增加一次 http 查询请求,称为预检请求(prefight)。\n浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 http 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 xmlhttprequest 请求,否则就报错。\n来看一段浏览器的 javascript 脚本。\n1 2 3 4 5 var url = \u0026#39;http://api.alice.com/cors\u0026#39;; var xhr = new xmlhttpreuqest(); xhr.open(\u0026#39;put\u0026#39;, url, true); xhr.setrequestheader(\u0026#39;x-custom-header\u0026#39;, \u0026#39;value\u0026#39;); xhr.send(); 上面代码中,http 请求的方法是 put ,并且发送一个自定义头信息 x-custom-header 。\n浏览器发现,这是一个非简单请求,就自动发出一个“预检”请求,要求服务器确认可以这样请求。下面是这个“预检”请求的 http 头信息:\noptions /cors http/1.1 origin: http://api.bob.com access-control-request-method: put access-control-request-headers: x-custom-header host: api.alice.com accept-language: en-us connection: keep-alive user-agent: mozilla/5.0... “预检”请求用的请求方法是 options ,表示这个请求是用来询问的。头信息里面,关键字段是 origin ,表示请求来自哪个源。\n除了 origin 字段,“预检”请求的头信息包括两个特殊字段。\n(1)access-control-request-method\n该字段是必须的,用来列出浏览器的 cors 请求会用到哪些 http 方法,上例是 put 。\n(2)access-control-request-headers\n该字段是一个逗号分隔的字符串,指定浏览器 cors 请求额外发送的头信息字段,上例是 x-custom-header 。\n预检请求的回应 服务器收到“预检”请求以后,检查了 origin、access-control-request-method 和 access-control-request-headers 字段以后,确认允许跨源请求,就可以做出反应。\nhttp/1.1 200 ok date: mon, 01 dec 2008 01:15:39 gmt server: apache/2.0.61 (unix) access-control-allow-origin: http://api.bob.com access-control-allow-methods: get, post, put access-control-allow-headers: x-custom-header content-type: text/html; charset=utf-8 content-encoding: gzip content-length: 0 keep-alive: timeout=2, max=100 connection: keep-alive content-type: text/plain 上面的 http 回应中,关键的是 access-control-allow-origin 字段,表示 http://api.bob.com 可以请求数据。该字段可也可以设为星号,表示同意任意跨源请求。\naccess-control-allow-origin: * 如果服务器否定了“预检”请求,会返回一个正常的 http 回应,但是没有任何 cors 相关的头信息字段。这时,浏览器就会认定,服务器不同意“预检”请求,因此触发一个错误,被 xmlhttprequest 对象的 onerror 回调函数捕获,控制台会打印出如下的报错信息:\nxmlhttprequest cannot load http://api.alice.com. origin http://api.bob.com is not allowed by access-control-allow-origin. 服务器回应的其他 cors 相关字段如下:\naccess-control-allow-methods: get, post, put access-control-allow-headers: x-custom-header access-control-allow-credentials: true access-control-max-age: 1728000 (1)access-control-allow-methods\n该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。\n*注:返回的是所有支持的方法,而不单是浏览器请求的那个方法,这是为了避免多次“预检”请求。\n(2)access-control-allow-headers\n如果浏览器请求包括 access-control-request-headers 字段,则 access-control-allow-headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在“预检”中请求的字段。\n(3)access-control-allow-credentials\n该字段与简单请求时的含义相同。\n(4)access-control-max-age\n该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是 20 天(1728000 秒),即允许缓存该条回应 1728000 秒,在些期间,不用发出另一条预检请求。\n浏览器的正常请求和回应 一旦服务器通过了“预检”请求,以后每次浏览器正常的 cors 请求,就都跟简单请求一样,会有一个 origin 头信息字段。服务器的回应,也都会有一个 access-control-allow-origin 头信息字段。\n下面是“预检”请求之后,浏览器的正常 cors 请求:\nput /cors http/1.1 origin: http://api.bob.com host: api.alice.com x-custom-header: value accept-language: en-us connection: keep-alive user-agent: mozilla/5.0... 上面头信息的 origin 字段是浏览器自动添加的。\n下面是服务器正常的回应:\naccess-control-allow-origin: http://api.bob.com content-type: text/html; charset=utf-8 上面头信息中, access-control-allow-origin 字段是每次回应都必定包含的。\n","date":"2023-05-25","permalink":"https://aituyaa.com/%E8%B7%A8%E5%9F%9F/","summary":"\u003cp\u003ei.e. Cross Domain\u003c/p\u003e\n\u003cp\u003e🔔 本文摘录自 \u003ca href=\"http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html\"\u003e浏览器同源政策及其规避方法\u003c/a\u003e\u003c/p\u003e","title":"跨域"},]
[{"content":"i.e. debounce and throttle\n为什么需要防抖和节流?它们是什么?有什么区别?适用场景是什么?\n简介 防抖与节流,主要用来控制事件处理函数的调用频率 ❗\n在进行窗口的缩放(resize)、滚动(scroll),输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器负担,导致用户体验糟糕。\n:: 试想,你的事件处理函数是异步的,每次调用都会进行大量的数据检索……\n如何控制调用频率呢?防抖和节流就是做这个的。\n防抖 什么是防抖(debounce)?当持续触发事件时,在设定时间段🕐内没有再触发事件,事件处理函数才会执行一次;若在设定的时间段🕜内,又一次触发事件,就重新开始延时。\n如下图,持续触发 scroll 事件时,并不执行 handle 函数,当 1000 毫秒内没有触发 scroll 事件时,才会延时触发 scroll 事件。\n![[assets/pasted image 20230525173428.png]]\n节流 什么是节流(throttle)?当持续触发事件时,保证一定时间段内只调用一次事件处理函数。如下图,持续触发 scroll 事件时,并不立即执行 handle 函数,每隔 1000 毫秒才会执行一次 handle 函数。\n![[assets/pasted image 20230525173440.png]]\n……\n区别 不能看出,防抖的控频原理在于持续触发事件时,在设定的时间段内不再触发事件,才会调用一次执行函数;而节流的控频原理在于持续触发事件时,每隔设定的时间段才会调用一次执行函数。\n当然,实际操作中,我们会对二者做一些相应的优化处理,但是从本质上来说,它们就是如此。\n参考链接 https://www.cnblogs.com/momo798/p/9177767.html https://www.cnblogs.com/youma/p/10559331.html https://segmentfault.com/a/1190000012066399 https://segmentfault.com/a/1190000018428170 https://zhuanlan.zhihu.com/p/38313717 https://juejin.cn/post/6844903669389885453 https://redd.one/blog/debounce-vs-throttle https://webdesign.tutsplus.com/tutorials/javascript-debounce-and-throttle\u0026ndash;cms-36783 ","date":"2023-05-25","permalink":"https://aituyaa.com/%E9%98%B2%E6%8A%96%E4%B8%8E%E8%8A%82%E6%B5%81/","summary":"\u003cp\u003ei.e. Debounce and Throttle\u003c/p\u003e\n\u003cp\u003e为什么需要防抖和节流?它们是什么?有什么区别?适用场景是什么?\u003c/p\u003e","title":"防抖与节流"},]
[{"content":" 近来,准备组装一台 pc 主机,做个记录 ~\n相关参考 🖥 cpu篇:2023年3月台式电脑cpu一文搞定 当前主机 当前使用 2016 年购入的小米笔记本,外接 2k 显示屏,播放 1080p 分辨率视频都会卡顿……\n配件 型号 备注 cpu i5-6500 显卡 gt 940mx “烂大街”的轻薄本显卡 内存 8g 不够用哦 硬盘 m.2 256gb 可扩展,预留了一个 ssd 卡位 \u0026gt; 2016(首代)小米笔记本 air 13.3 英寸 银色\n自购入起,电池基本上每年更换一次(设计缺陷,鼓包),再坏了打算直接去售后把电池拆了,直能电源使用;屏幕已经更换了两次(屏幕发灰,出现竖线),当前偶尔再次也会出现,懒的弄了,外接显示器使用。其他构件也是各种减配货,妥妥地‘狗’。\n垃圾小米,垃圾品控!\n\u0026gt; 2023-10-20 09:32 前段时间电池又鼓包了,自己买了套工具,把电池拆了,目前直通电源使用。\n采购单 比对工具: cpu天梯榜 versus\n购机三大件:主板、cpu 和显卡 !\n🔥 期待国产 u 的崛起! ! ! fuck u, nvidia ! 显卡真 tm 贵! ! !\n配件 型号 价格 选购 备注 主板 prime 大师系列:\n-kde 均为入门级,不考虑\n-p 为 z 系列入门级\n-a 为中高端代表(扩展多)\n-plus 为 -a 加强版\n-pro 高端系列 tuf 电竞系列:\n-plus 基础款,主流热选型号,性价比最高\n-e plus 加强版\n-pro plus 加强版\n-mark 高端系列 华硕prime z790-p wifi d5 2099 华硕tuf gaming z790 -plus wifi d5 2199 华硕tuf gaming b760m-plus wifi d5 1249 ✔️ 华硕tuf gaming b660m-plus wifi d5 1149 cpu 要关注 cpu 的接口规格,以便于选取合适的主板 i7-14700k 20核28线程 5.6ghz 33m 3499 i7-13700k 16核24线程 5.4ghz 30m 2849 i5-14600k 14核20线程 5.3ghz 24m 2599 i5-13600k 14核20线程 5.1ghz 24m 2199 ✔️ 显卡 华硕显卡:\n- 入门级 ph系列\n丐版 dual 雪豹\n- 中端 巨齿鲨\n- 中端 tuf gaming\n- 旗舰 rog strix 猛禽\n- 一体水冷 rog strix lc\n- 分体水冷 ekwb\n- 顶级旗舰 rog matrix 骇客 fuck u, nvidia ! 华硕 tuf rtx4070-o12g 5489 华硕proart-rtx4060ti-o16g 4389 华硕 tuf-rtx4060ti-o8g 3889 硬盘 slc \u0026gt; mlc \u0026gt; tlc \u0026gt; qlc 国产固态 nb !这玩意儿售价腰斩 三星1tb ssd 990 pro 599 2t 1149 独立缓存 solidigm p44 pro 1tb 559 ✔️ 2t 1059 致态tipro7000 1tb nvme m.2接口 pcie 4.0 539 2t 959 tlc 内存条 三星 b-die 最好;\n镁光c9:\n- c9blm \u0026gt; c9bkv \u0026gt; c9bjz\n长鑫: - 特挑a-die = c9blm,普通 a-die 次之\n海力士: - djr ≈ 普通 a-die ,\n- 其余 *jr 均次之,与长鑫 m-die 对标 国产内存 nb ! 金士顿 ddr5 6000 16x2 749 光威 32gb(16gbx2) d5 6600 海力士a-die cl34 749 ✔️ 电源 海韵seasonic 金牌全模 gx1000w 899 海韵seasonic 金牌全模focus gx850w电源 699 ✔️ 散热 现在的主板对风冷太不友好了 华硕 360 水冷 999 noctua nh-d15 黑色版 双塔 双风扇 749 noctua nh-u12a 黑 单塔 双风扇 755 利民 fc140 黑 双塔 329 利民 u120ex 黑 单塔 双风扇 279 ✔️ 157mm 机箱 atx \u0026gt; m-atx \u0026gt; itx 追风者 g500a 标准版 599 华硕 (asus)ap201 冰立方机箱 黑色 399 ✔️ 显示器 已购 三星 27 英寸 2k 1579 ✔️ ✔️❌ \u0026gt; 主要配件\n配件 型号 价格 选购 备注 支架 ❌ 显卡支架用于防止显卡卡座垂直变形 散热器 ❌ 用于给 cpu 散热,绝大多数 cpu 有配套风冷,水冷另外买 机箱扇 利民 tl-b12 extrem lcp 12cm 2150转 pwm 109 与风冷同款 利 tl-s12 x3 炫彩光圈 argb 风扇 支持神光同步 4pin pwm 99 3 个一起买反而更贵,nb 😂 利民 tl-s12 炫彩光圈 argb 风扇 支持神光同步 4pin pwm 26 ✔️ \u0026gt; 备选配件\n准备工作 自己配置主机是一个挺‘不容易’的事,主要的‘不容易’在于你的各种各样的纠结……\n各种配件层出不穷,不同型号,不同名称,不同性能,不同价格…… 怎么选择呢?一切的‘迷惘’皆来自于不了解配件相关的知识和厂商、专家的套路。抛开上述各种,本质上源于钱包不够鼓…… 😅\n下面对自己在选购过程中的所查询、了解的方面做一下整理记录,也不枉为了配置个主机花费那么的精力和时间。当然,想要把各方面都说清楚是很难的,这里会围绕影响选购的点进行。\n:: 个人倾向于购买 intel 系配置主机,故主要以其为主要示例,amd 只做适当对比(本质上大同小异啦)。\ncpu cpu ,是一台主机的‘心脏’ ❤️!\n打开 jd,搜索 cpu ,你会发现有许多不同类型的 cpu。不同型号代表什么?如何选择呢?\n![[assets/pasted image 20230919155006.png]]\n\u0026gt; cpu 的型号字母和数字分别代表什么\n下面,我们用如下示例,来详细介绍一下各个名称参数的具体表示是什么。\n![[assets/pasted image 20230919182209.png|500]]\n如下图所示,这是一款英特尔的 cpu ,其中 intel 便是 cpu 品牌,其他品牌还有 amd,龙芯等。\n![[assets/pasted image 20230919154643.png|275]]\n酷睿 ( intel core )是英特尔旗下的 cpu 系列,于 2006 年开始发售,旨在取代当时面向中高端市场的奔腾系列、赛扬系列处理器。core 系列旗下的处理器家族包括:core x 系列、core i9、core i7、core i5 以及 core i3。\ni9 i7 i5 i3 是怎么划分的呢?在 cpu 制造完成后,还会进行一次全面的测试。测试出每一颗芯片的稳定频率、功耗、发热,如果发现芯片内部有硬件性缺陷,将会做硬件屏蔽措施,因此划分出不同等级类型 cpu,例如 core i9、i7、i5、i3 。\ni5 代表这款 cpu 定位中端,i3(低端)、i7(发烧)、i9(旗舰)。同一代中,数字越大,性能越强;但是不同代数之间,性能不能直接相比,比如 12 代的 i5 在理论性能上是强于 10 代 i7 的。\n12600kf 中的 12 代表这款 cpu 的代数,那么代数又是什么呢?其实是指处理器的代次(cpu 微架构的代号)。如 intel 第 12 代产品家族为 alder lake,同时包含 golden cove 和 gracemont 微架构。\n12600kf 中的 600 (产品编号的最后三位数字)是 sku。一般 i3 有 300/100,i5 有 600/500/400,i7 有 700,i9 有 900 。\nsku 通常是按照该代次和产品线中的处理器开发顺序分配的。在处理器品牌和代次的其他部分完全相同的前提下,较高的 sku 一般表示具有更多功能 (同代比较,sku 越高,性能更强)。但是,sku 编号通常不是比较不同代次或产品线的最佳指示符。\n12600kf 中的 k、f 是 sku 后缀,它是 cpu 功能的另一个关键指示符,如 k 代表该型号 cpu 支持超频(高性能,未锁频),f 代表无内置核显。\n![[assets/pasted image 20230919154728.png|275]]\namd 的命名规则总体和 intel 是大同小异的,其主流消费级品牌系列是 ryzen 锐龙,中高端 cpu 采用了其第四代微架构 zen4 ,性能很不错。其 sku 后缀中 x 代表支持睿频、超频,xt 相当于 x 的加强版,g 则表示内置核显。\n超频是什么?睿频又是什么? 你可以在「 [[用沙子造的 cpu 凭什么卖的那么贵#线程、睿频、超频]] 」中了解它。\n主板 主板,特指具有扩展能力的 pcb(printed circuit board,印刷电路板)。它的主要功能是提供一系列接合点 ( 插槽,如处理器、pci express 等),形成一个能集成处理器、内存、存储设备(硬盘、固态驱动器、闪存等)、显卡、声卡、网卡和各种外部设备的连接平台。\n印刷电路板,采用印刷蚀刻阻剂的工法,做出电路的线路及图面。在印制电路板出现之前,电子元件之间的互连都是依靠电线直接连接而组成完整的线路。\n![[assets/pasted image 20231019095544.png|325]] ![[assets/pasted image 20231019095517.png|150]]\n\u0026gt; 印刷电路板(左) \u0026amp; 面包板(右)\n我们在购买主板的时候,需要关注的点:芯片组(对 cpu 的支持, 如 intel 的 lga1700、lga1200 和 amd 的 socket am4、am5 )、pcie(pcie3.0、pcie4.0、pcie5.0)、内存支持 (ddr4、ddr5)及供电。\n![[assets/pasted image 20231019154514.png]]\n\u0026gt; 天才赵德柱的主板选购视频截图\n其中,黄线部分表示与 cpu 直连(直接通信,速度更快),蓝线部分表示通过南桥芯片经由 dmi 总线与 cpu 通信,速度相对直连慢一些。\n:: 一个直接 cpu 的显卡插槽和 m.2 插槽可以做为选购标准之一(大部分主板都支持)。\n芯片组 主板上最重要的构成组件是芯片组。芯片组 (chipset),指的是一类由好几个微芯片组合而成的完整芯片,它负责将电脑的处理器和和其它部分做连接,以便能互传数据。\n芯片组在它所诞生的 1980 年代时是由多颗芯片组成的,但是随着科技的进步,芯片组先是从 2000 年代开始简化为南桥和北桥两颗芯片,再于 2010 年代简化为单独一颗的南桥芯片(北桥功能集成至 cpu),目前世界上的芯片组均以单一的南桥芯片为主流。\n目前仍在营业的芯片组厂商只有 intel 和 amd,其他或被并购,或是已退出市场。当然,国内的芯片组厂商还有龙芯中科,如 7a1000、7a2000。\n2022 年 7 月后发布的龙芯 7a2000 是龙芯 7a1000 的升级版。相对于 7a1000,7a2000 的 pcie 控制器代码龙芯计划自己编写,且为 pcie gen3。采用的 28nm 工艺,gpu 为自研 gpu,可形成独显方案,极大降低系统成本,提升新一代龙芯 3 号 cpu 在桌面与服务器的整体性能表现。\n:: 国产 cpu 崛起之路很艰辛,任重而道远啊,致敬!\nintel 近年的芯片组系列有 400/500 系列(lag1200,如 b460、z590 等)和 600/700 系列(lga1700, 如 b660、z790 等);amd 近年的芯片组系列有 500 系列(am4,如 b550、x570 等)和 600 系列(am5,如 b650、x670 等)。\n![[assets/pasted image 20231019155504.png]]\n\u0026gt; intel 600 系芯片组\nintel 方面,除了 z 系列的主板支持 cpu 超频,其他皆不支持(tips:普通用户完全没必要考虑超频)!相对来说,amd 的 x 系列、b 系列都支持 cpu 超频,良心不少。\n![[assets/pasted image 20231019160104.png]]\n\u0026gt; amd 500系芯片组\n关于主板是如何命名的,我们后续再说。\npcie pcie(peripheral component interconnect express,简称 pci-e 或 pcie)外设组件互连扩展总线:一种用于连接计算机内部各种硬件设备的高速数据传输接口标准。\npcie 沿用既有的 pci 编程概念及信号标准,并且构建了更加高速的串行通信系统标准。由于 pcie 是基于既有的 pci 系统,所以只需修改物理层而无须修改软件就可将现有 pci 系统转换为 pcie。\npcie 拥有更快的速率,所以几乎取代了以往所有的内部总线(包括 agp 和 pci)。现在英特尔和 amd 已采用单芯片组技术,取代原有的南桥/北桥方案。\npcie 最大的意义在于它的通用性,不仅可以让它用于南桥和其他设备的连接,也可以延伸到芯片组间的连接,甚至也可以用于连接图形处理器,这样,整个 i/o 系统重新统一起来,将更进一步简化计算机系统,增加计算机的可移植性和模块化。\n![[assets/pasted image 20231019110550.png]]\npcie 的规范主要是为了提升电脑内部所有总线的速度,因此带宽有多种不同规格标准,其中 pcie ×16 是特别为显卡所设计。\n其实呢,pcie4.0 才刚刚开始普及,目前最新版本的显卡和固态都是只支持 pcie4.0 的,而且还吃不满(最顶尖的显卡也就刚刚跑满 pcie3.0x16)……所以,intel 这个先进的 pcie5.0 目前并没有什么实际用途。(\u0026gt; 2022 年)\n内存支持 不同主板对于内存类型的支持是不一样的(插槽口不同),目前主流的有 ddr4 和 ddr5。ddr 是什么呢?详见 [[#内存]] 章节。\n供电 主板上的供电一般分为 4 个部分,包括 pwm 控制器、mos 桥及驱动器、供电电感、电容。\n1. pwm 电路控制器\npwm(pulse width modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在测量、通信、工控等方面。\npwm 的频率,是指在 1 秒钟内,信号从高电平到低电平再回到高电平的次数,也就是说一秒钟 pwm 有多少个周期,单位 hz。\npwm 的周期,t=1/f,t 是周期,f 是频率。如果频率为 50hz ,也就是说一个周期是 20ms,那么一秒钟就有 50 次 pwm 周期。\n占空比,是一个脉冲周期内,高电平的时间与整个周期时间的比例,单位是% (0%-100%)。\n![[assets/pasted image 20231020095150.png|300]]\n上图中,脉宽时间占总周期时间的比例,就是占空比。\npwm 就是脉冲宽度调制,通过调节占空比就可以调节脉冲宽度。\n我们通过一个例子,来深入认识一下 pwm 的基本原理是什么。以 stm32 单片机为例,其 io 口只能输出高电平和低电平。\n假设高电平为 5v、低电平则为 0v,那么要输出不同的模拟电压就要用到 pwm。通过改变 io 口输出的方波的占空比,从而获得使用数字信号模拟成的模拟电压信号。\n电压是以一种脉冲序列被加到模拟负载上去的,接通时是高电平 1,断开时是低电平 0。接通时直流供电输出,断开时直流供电断开。通过对接通和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值 5v 的模拟电压。\n比方说,占空比为 50%那就是高电平时间一半,低电平时间一半。在一定的频率下,就可以得到模拟的 2.5v 输出电压。那么 75%的占空比,得到的电压就是 3.75v,如下图所示。\n![[assets/pasted image 20231020095746.png]]\n也就是说,在一定的频率下,通过不同的占空比即可得到不同大小的输出模拟电压,pwm 就是通过这种原理实现数字模拟信号转换的。\n主板供电分为直出式供电、并联供电、倍相供电。\n一相供电,由 1~2 个电感(多个电感组成一相供电就是并联或倍相)+ 1~4 个 mos + 多个电容组成。\n一般主板说的 m+n 相供电,m 指 cpu 供电,n 指核显供电,核显供电一般只有 1~2 相。\n:: 倍相 \u0026gt; 并联 \u0026gt; 直出,但不一定 😅\n![[assets/pasted image 20231019161115.png]]\n![[assets/pasted image 20231019161315.png]]\n\u0026gt; 供电方式:直出、并联、倍相\n2. mos\n金属氧化物半导体场效晶体管(简称:金氧半场效应管;英语:metal-oxide-semiconductor field-effect transistor,缩写:mosfet),是一种可以广泛使用在模拟电路与数字电路的场效应管。\n![[assets/pasted image 20231019161545.png]]\n\u0026gt; mos 类型\n不同类型 mos 供电,性能和价格肯定也不一样。\n![[assets/pasted image 20231019161434.png]]\n\u0026gt; 相数 \u0026amp; mos核心供电强度\n相数不是说越多越好,还是考虑单相供电的强度。\n命名 以下是华硕的 tuf gaming z790-pro wifi 主板,随便在商城上搜一下,相信多数人都会被琳琅满目的主板名称给搞晕,它们到底是如何命名的呢?接下来,我们以华硕主板命名规则为例进行一些说明。\n![[assets/pasted image 20231019105145.png]]\n\u0026gt; 华硕的 tuf gaming z790-pro wifi 主板\n如果我们打算组装一台电脑,首先就是搭配硬件出一款适合自己的电脑配置,通常我们先会选择 cpu,确定 cpu 之后再去考虑选择什么主板,而在挑选主板的时候,即使是相同品牌,主板相同芯片的情况下,主板后缀字母或者主板系列不同,价格的也是不同的,除了主板尺寸大小、板层区别,但其实主要区别就是做工用料、板材大小、扩展接口的区别。\n由于每个品牌主板后缀字母定义不同,这里我们以华硕品牌的主板为例,来科普一下华硕主板后缀字母代表什么含义。\n![[assets/pasted image 20231020112110.png|300]]\n注: 芯片组后缀多个 m,说明为 m-atx 规格主板,不带 m 说明是 atx 大板,后缀有 wifi 说明支持无线网卡,ⅱ 表示二代。\n华硕主板作为一线品牌第一主板生产制造商,推出的系列众多,采用的主板型号后缀也十分混乱,不利于消费者辨认,不过华硕依然坚持这个作风。如:\n主要有主打稳定耐用的“prime 大师系列”;✔️ 主打亲民级电竞的“电竞特工系列(tuf gaming)”;✔️ 混血的“玩家国度猛禽系列(rog strix)”;❌ \u0026gt; 贵 定位高端发烧级的血统纯正的“玩家国度系列(rog)”;❌ \u0026gt; 太贵\n- 定位电商整机专供的“四大神兽”; ❌\n- 定位网吧电脑的“华硕 expedition 长征系列”,以及 ❌\n- 主打工作站的“pro ws 系列”等。 ❌ ![[assets/pasted image 20231020112748.png]]\n1. prime 大师系列\nprime 大师系列主打专业工作等需要十分稳定耐用的用户,该系列主要定位是家用和商用的主板,从入门级到高端级全部涉及,主板型号字母后缀主要有 d、e、k、p、a、plus、c/csm、pro 以及 deluxe 等。\n后缀 描述 -k 最入门级,主打性价比 ❌ -d 最入门级,与-k 有点类似,但是比较注重商用扩展接口 ❌ -e 最入门级,定位介于 -k 和 -d 后缀的主板 ❌ -p 属于 intel z 系列芯片 (支持超频)的入门级主板 -a 中高端的代表,相比-p 后缀接口扩展性更多 -plus 加强型,与-a 后缀区别不是很大,相当于-a 的加强型版本 ✔️ -c/cms 商用系列主板 ❌ -pro 高端系列,做工用料出色,扩展性较多 -deluxe 发烧级主板,做工用料奢华 ❌ 2. 电竞特工系列(tuf gaming)\ntuf gaming 电竞特工系列是华硕电竞主板中的亲民主流系列,秉承坚固耐用、持久稳定的精神,定位仅次于玩家国度猛禽系列,基本每一代均采用黑黄配色、辅以数码迷彩以及 rgb 灯效元素的电竞风,无论从颜值还是做工用料以及接口扩展性都比较不错,主流玩家首选,性价比也很高。\n后缀 描述 -plus / -plus gaming 基础款,主流热选型号,性价比最高 ✔️ -e gaming plus 的加强版 ✔️ -pro gaming 相比 plus 版本有所加强,属于三代锐龙特别加强版 mark/mark ii tuf 高端系列,通常只出现在 intel x 系列高端芯片组 3. 玩家国度猛禽系列(rog strix)\n玩家国度猛禽系列(rog strix)的定位要低于玩家国度系列,起初猛禽(strix)系列刚开始不属于玩家国度(rog)系列,之后才被划分到 rog,隶属于 rog 后称之为“玩家国度猛禽系列(rog strix)”,所以被不少电脑爱好者称之为混血 rog、杂交 rog 等。型号后缀通常为 e 、 f、 h、p、i 等,如果在 atx 版型主板中由高到低分别是 e \u0026gt; f \u0026gt; h \u0026gt; p,i 代表 itx 版型。h 是性价比之选,e 是玩家国度猛禽系列高端型号。e、f 、h 、p,包括 a 和 g,主要是供电、用料和接口上或者支持 wifi6的区别。\n其他 1. io 接口\n![[assets/pasted image 20231020104713.png|400]]\n:: vga 就是个垃圾,hdmi、dp、type-c 你值得拥有。\n2. 板型\neatx atx matx mini-itx 305 * 330 305 * 245 245 * 245 170 * 170 atx 和 matx 都还行,条件允许的话,还是建议上一个 atx 大板,方便后续扩展。\n3. m2 插槽\n一般主板配的都够用了,个人电脑基本上 2t 容量就很 okay 了。\n4. 板载网卡\u0026amp;wifi\n板载网上有:千兆网卡 \u0026lt; 2.5g 网卡 \u0026lt; 万兆网卡,通常都支持 wifi6 。\n:: 你的网卡再快能怎么样?电信运营商只给你百兆的宽带……\n显卡 fuck u, nvdia !\n华硕目前主流显卡系列如下:\n华硕系列 名称 入门 phoenix凤凰(简称ph系列) 丐版 dual雪豹 中端 巨齿鲨 中端 tuf gaming电竞特工 旗舰 rog strix(猛禽) 一体水冷 rog strix lc 分体水冷 ekwb 顶级旗舰 rog matrix(骇客) 内存 通常,我们提到的内存指的是随机访问存储器(random-access memory,ram)- 与中央处理器直接交换数据的内部存储器。它可以随时读写(刷新时除外,见下文),而且速度很快,通常用来加载各式各样的程序(操作系统也是程序)与资料以供中央处理器直接执行与运用。\nram 存储器可以进一步分为静态随机访问存储器(sram)和动态随机访问存储器(dram)两大类。sram 具有快速访问的优点,但生产成本较为昂贵,一个典型的应用是作为 cpu 缓存存。而 dram 由于具有较低的单位容量价格,所以被大量的采用作为系统的主存。\n那么,sdram 是什么?\nsdram(synchronous dynamic random-access memory,同步动态随机存取内存),是一种利用同步计时器对存储器的输出入信号加以控制的动态随机存取存储器(dram)。\nsdram 是在 dram 的架构基础上增加同步和双区域(dual bank)的功能,使得微处理器能与 sdram 的时钟同步,所以 sdram 执行命令和传输资料时相较于 dram 可以节省更多时间。sdram 在计算机中被广泛使用,从起初的 sdram 到之后一代的 ddr(或称 ddr1),然后是 ddr2 和 ddr3 进入大众市场,2015 年开始 ddr4 进入消费市场,2020 年 ddr5 正式发布。\nddr (double data rate),原来 sdram 是在时钟的一个边沿传输数据,ddr 则是在时钟的上升沿和下降沿都传输数据。这样 ddr 相比较于 sdram 的数据处理速度就提升了一倍。\nsdram 本身的延迟其实并不比异步 dram 更低(延迟更低意指速度更快)。其实,早期的 sdram 因为其构造中的附加逻辑单元,在速度上比同时期的爆发式延伸数据输出 dram (burst edo dram)还有所不及。而 sdram 的内置缓冲则可以使得运算交叉进入多行存儲,这样就可以有效提高带宽,速度更快。\n在装机的时候,我们还经常提到“双通道”,它又是什么呢?\n双通道(英语:dual-channel)是一种能够让电脑性能增加的技术,此种技术将多个存储器由串联方式改良为并联方式,以得到更大的带宽。\n理论上,双通道总线能将存储器的资料传输性能提升两倍。但对系统整体性能来说,开启双通道带来的性能提升视所使用的作业与软件而定,但不会提升到两倍。在实际使用上,若非长时间的极大资料运算或透过测试软件获取测试信息,对于用户的操作上并没有明显的差异。\n如果是纯粹的 cpu 运算,使用双通道的性能增长不大。但对于影像处理需求较大的软件而言,双通道技术不啻为一大帮助,因为这类软件需要 cpu 与存储器之间互相传输大量的资料,故较大的带宽可以节省传输时间。所以在使用双通道时,双倍的存储器带宽可使芯片组或 cpu 内置绘图核心的系统性能增长,但性能提升不会达到两倍,往往只有三到五成,实际仍依不同的软件与 cpu 架构而定。\n现在,几乎所有的电脑都支持双通道,ddr 双通道存储器位宽为 128-bit,即是两组 64-bit。另外,内存条安装的方式也是关键,并非有支持双通道的主板上安装两条存储器就能运作,还需要正确的安装;像是 nforce 2 的设计有四条存储器插槽,依序为 1、2、3、4,而必须要安装 1、3 或是 2、4 才能使用双通道,若仅安装 1、2 就会开启单通道模式。\n各款芯片组设置方式不一,各家主板也可能不同,因此必须要参考使用说明书以正确方式安装。如果安装成功并正常运作,引导时 bios 便会显示“dual channel mode enable”或类似消息,表示正确激活双通道。\n多大内存合适呢?32g (16gx2)目前是首选。看下图:\n![[assets/pasted image 20231019170925.png]]\n\u0026gt; 内存容量怎么选?\n购买内存的时候,需要关注的是频率和时序(cl),一般来说,频率越高,时序越低,内存性能越好,但频率和时序是矛盾的。日常使用的话,随便一块主流内存都完全可以满足。\n![[assets/pasted image 20231019171217.png|500]]\n\u0026gt; 时序\n:: 谢谢长鑫和长江存储把三星的周期性火灾给消灭了。\n![[assets/pasted image 20231019171624.png|550]]\n\u0026gt; 颗粒\n固态硬盘 固态硬盘主要由三大部分组成:主控、缓存(独立缓存、模拟缓存)、闪存颗粒。\n闪存颗粒,slc \u0026gt; mlc \u0026gt; tlc \u0026gt; qlc ,空间利用越来越高,速度越来越慢,寿命越来越短。目前 tlc 是主流。\n电源 用途:110v~220v \u0026raquo; 电源 \u0026raquo; 12v、5v、3.3v ; 80plus 认证等级:银 \u0026lt; 金 \u0026lt; 白金 \u0026lt; 钛金 (负载和转化率不同); 类型:直出、半模组、全模组; 电容:日系 \u0026gt; 台系 \u0026gt; 大陆系。 实际输出功率 = 额定功率 / 转化率,如下:\n![[assets/pasted image 20231019163832.png|375]]\n\u0026gt; 正确的功率计算\n在电源铭牌中,主要关注 +12v 供电功率即可,一般来说,其输出功率至少达到电源额定功率的 90% 左右。\n![[assets/pasted image 20231019164945.png]]\n参考 天才赵德柱的个人空间-天才赵德柱个人主页-哔哩哔哩视频 英特尔® 处理器名称、编号和代次列表 cpu型号解读:教你cpu型号后缀怎么看?后面的字母和数字区别是什么? - 知乎 nvidia gpu 比较表 | nvidia 从ddr到ddr4,内存核心频率基本上就没太大的进步! - 知乎 主板供电知识整理 - 知乎 华硕主板后缀字母代表什么含义?华硕主板型号命名规则与系列详解 - 知乎 2023年双十一主板推荐及选购攻略 - 知乎 ","date":"2022-11-10","permalink":"https://aituyaa.com/%E5%97%A8pc/","summary":"\u003cblockquote\u003e\n\u003cp\u003e近来,准备组装一台 PC 主机,做个记录 ~\u003c/p\u003e\n\u003c/blockquote\u003e","title":"嗨,pc"},]
[{"content":" 凡事,预则立,不预则废。\n矛盾分析 当下的主要矛盾是,个人及家庭对于富足生活的追求与当下财务状况堪忧及预期营收潜力不足的矛盾!\n如何解决这个主要矛盾呢? 改善财务状况,提升个人核心竞争力! 前者最大程度地要通过后者才能得以实现,竞争力,竞争力,竞争力!\n:: 维持并试图继续维持现状,毫无意义!\n如何改善财务状况? 有哪些途径呢?\n更换薪酬更高的工作!(这个更靠谱些) 当前岗位涨薪升级(以公司目前状况,基本不可能)❌ 职‘副业’(搞什么好呢?自媒体?外包项目?设计?) 理财?(一点不懂啊,不过倒是可以开始了解下,毕竟一切都是资本的游戏) :: \u0026gt; 2024-11-01 10:52 当前的就业形势很严峻,更换工作需要做好十足的准备;自媒体是一个不错的选择,但一定要选择好赛道;外包项目是将来创业的小尝试,有机会的话,可以尝试进行;设计、音乐、写作等都是丰富自媒体内容的手段,顺便也可以提高自己,为子女教育做准备;理财要开始了解了,正好打发碎片化的时间,不至于无事可做。\n:: \u0026gt; 2024-02-27 14:55 接触“多管道”这个概念很久了,从来也没有采取有计划的、具体的行动,这个有点说不过去。事实也证明,只有实际的行动才能认识世界、改造世界。\n:: 总体来说,目前比较实际且快速的提高薪资的方式就是‘跳槽’ ……\n1. 跳槽 ![[../assets/pasted image 20230529092642.png|300]]\n短期显著方式就是 ‘跳槽’,以寻求更好的机会。其中,需要注意的是,职位专业方向及职级的改变,要综合个人兴趣和市场需要两方面来考虑,侧重前者!\n不得不说,后端的薪资天花板相对较高些(尽管许多从业者水平相当一般),自己也相对比较倾心于后端。倒不是说前端不好,只是‘心的执拗’往往无人解释。细想来,多是‘好奇心’作怪,导致对于底层运行机制的了解有着一种莫名的‘执念’(尽管许多从业者像‘白痴’一样)。\n全栈!!!\n既然心意已决,便只当奋力前行!所有的学习重心开始以此为目标,迁移、侧重。\n目的原则: 通过跳槽实现薪资上升,以解决财务问题,提升生活质量。 - 界定成功(前景展望):成功跳槽,薪资上涨幅度 50% 以上; - 阐明重点(主要矛盾):⭐个人技术栈与市场需求的契合度; - 拓宽选择(更多可能):? - 集结资源(客观条件):客观上只能通过简历投递及内部推介; - 激动动机(主观能动):解决财务问题,沉淀技术,提高竞争力; - 澄清原则(遵守本心):合理合法。 前景结果: - 聚焦; - 阐明结果:成功跳槽,薪资上涨预期幅度,财务问题得到缓解及解决。 集思广益: 不评估,不质疑;追求数量,不求质量;分析组织放下一个阶段。 - 继续前端开发(第二选择); - 转向后端开发(优先选择); - 转身产品经理(暂不考虑)。 组织协调: - 明确意义重大的事件:职业定位(技术?管理?); - 排序(构成因素、先后顺序、重要程度):多管齐下,面试准备,技术沉淀。 具体行动: 切实可行的行动。 - 技术沉淀,撰写项目总结; - 制作新简历,面试准备。 :: \u0026gt; 2024-02-27 15:46 边写简历,边准备(技术沉淀),双管齐下,互相促进。\n:: 离家近的小公司也不错,可能忙一些,但是可以充分发挥‘全栈’优势,不至于无用武之地。\n2. 副业 ![[../assets/pasted image 20230529092701.png|300]]\n兼职‘副业’收效不会很快,且周期相对较长,但具备‘管道’性(即可持续性)。就自媒体而言,可以不受工作地点的影响,对财务自由和精神愉悦相当有益。\n:: 是不是需要配置一台性能好些的台式主机?应该吧,必要的投入…… ✔️ 已购!\n谨记,所有的‘输出’务必是有价值的,不能主观地制造‘信息垃圾’。有价值的‘输出’,不仅有利于他人,也有利于加强自身所接收的信息‘输入’的品质,让自己变得更好。\n目的原则: 创收,增加额外收入,改善财务状况。 - 界定成功(前景展望):一定程度的创收; - 阐明重点(主要矛盾): - 拓宽选择(更多可能): - 集结资源(客观条件): - 激动动机(主观能动): - 澄清原则(遵守本心): 前景结果: - 聚焦; - 阐明结果:建立多管道营收,实现财务健康。 集思广益: 不评估,不质疑;追求数量,不求质量;分析组织放下阶段。 - 视频剪辑、直播,内容创作; - b站、抖音系、油管……; - 平面设计(需要学习); - 文学写作,如网络小说、短文创作; - 软件开发项目,承接外包或开发应用 app; - 兼职私活; - ❌ 夜市摆摊。 组织协调: - 明确意义重大的事件: - 排序(构成因素、先后顺序、重要程度): 具体行动: 切实可行的行动。 - 3. 理财 ![[../assets/pasted image 20230529092721.png|300]]\n‘理财’知识,应该掌握,比如基金、股票,不能沉迷,却不能不去了解。毕竟,目前的认知中,‘钱生钱’是最快的和平聚集财富的方式。\n要了解基本的政治经济学,明晰经济基础和上层建筑之间的关系。多加阅读相关方面的书籍,不要‘盲人摸象’。在不能制定和改变‘游戏’规则的当下,更加需要充分了解规则,如此才能更好地利用规则,以求事半功倍,少做无用功。\n:: 思考,真的是一件十分费脑子的事情 😅\n目的原则: 用钱生钱。 - 界定成功(前景展望):了解理财市场,入场尝试; - 阐明重点(主要矛盾):了解经济规律和常规动作,了解规则,控制风险和收益比例; - 拓宽选择(更多可能): - 集结资源(客观条件): - 激动动机(主观能动): - 澄清原则(遵守本心): 前景结果: - 聚焦; - 阐明结果:不说成为高手,起码不要是门外汉。 集思广益: 不评估,不质疑;追求数量,不求质量;分析组织放下阶段。 - 组织协调: - 明确意义重大的事件: - 排序(构成因素、先后顺序、重要程度): 具体行动: 切实可行的行动。 - 先了解理财相关的基本概念,阅读相关书籍。 如何提升个人核心竞争力? 如何提升个人核心效力呢?哪个领域?哪些竞争力?时间、精力、计划?\n![[../assets/pasted image 20230529092756.png|300]]\n\u0026gt; 工作(全栈)、副业(自媒体?)和理财(股票、基金)\n到底什么是‘竞争力’呢? 简单来说,就是“人否我可,人可我善,人善我达”,也就是“能人所不能,善人所不善,达人所不达”。\n学习,吸收,内化。\n计划安排 | 日期 | 节日 | 备注 | |------------|------|------| | 2023-01-21 | 除夕 | | | 2024-02-09 | 除夕 | | | 2025-01-28 | 除夕 | | | 2026-02-16 | 除夕 | | | 2027-02-05 | 除夕 | | | 2028-01-25 | 除夕 | | | 2029-02-12 | 除夕 | | | 2030-02-02 | 除夕 | | | 2031-01-22 | 除夕 | | | 2032-02-10 | 除夕 | | | 2033-01-30 | 除夕 | | 10 年时间太长了,不知道 10 年后的自己会是一种什么样的生活状态,期待美好!以下是未来几年大致计划构思,10 年是太久了,不过暂且先设置这个阈值吧,后续根据实际情况进行调整嘛。\n:: “一分钱难倒英雄汉”,此言诚不虚也。目前当务之急,就是全力以赴解决掉财务问题。现实世界,丛林法则,男人是没有资格当羊的!既然如此,那就成为一头凶猛的狼吧!但留慈悲心,只身向狼群! \u0026gt; 2024-02-27 16:24\n近五年事务概览:\n2022 已经来到了 10 月中旬末,离过年也就三个月了,年前要把我和秋之间的事情处理好 ❗️ 2023( 01-21 除夕 )年要准备结婚、备孕等一系列的事情; 2024 龙年(2 月 4 号立春),准备迎接~~‘龙宝’~~(‘兔宝 ’🐇小满意 )的降生; 2025 ,小满意1 岁了。视具体情况看明年(2026)是否再要一个‘小马驹’,要的话,就要辛苦老婆大人喽; 2026 马年,或许要迎接‘小马驹’的降生; 2027 小满意 3 岁了~~(‘小马驹’ 1 岁)~~,该准备上学了。这期间(2025 ~ 2027),全力完成定居(成都或重庆)及工作调动的事情。 :: \u0026gt; 2023-12-18 16:32 时光如水哦,不经意间再有十几天都 2024 了。\n\u0026gt; 时不我待,唯争朝夕。\n2022 - 2023 2022 已经来到了 10 月中旬末,离过年也就三个月了,年前要把我和秋之间的事情处理好 ❗️\n知会那边岳父岳母大人,并得到其祝福,具体如何做呢?有时间想一想,哪种战略好呢?\n听取老婆想法,充分了解情况 表达个人意见,确定基本原则 考虑各种情况,做到有备无患 确定正确策略,全力认真执行 以上各项事宜,需在过年之前(越早越好)完成。\n:: 时间过的真快~ 事情的走向往往不以人的意志为转移,反正吧,按着自己的心意去生活就好了。\n岳父岳母都挺好的!\n2023 - 2024 2023( 01-21 除夕 )年要准备结婚、备孕等一系列的事情。\n:: 好的吧,一不小心,毕业要 10 年了……\n2023 的新年,家里应该会有多年未曾有过的活跃气氛,毕竟,有了老婆个‘可爱’分子。\n👶🏻 造娃运动 ✔️\n按十月怀胎算,自 2023-03 起( 02-24 立春 ),即可开始运动。\n* 注,打了疫苗(2022-11-01),理论上 6 个月后(2023-05-01)可以要宝宝。\n按 10 月怀胎算起,窗口期在 2023-05-01 ~ 2024-02-01 之间,冲啊,争取在这期间早日取得造娃大业的完全胜利。\n2023-05-19 检查,发现怀孕,距今 2023-06-27 已经孕 10 周 6 天了,希望小家伙健康茁壮地成长,嘻嘻 😄。\n\u0026gt; 2023-12-18 崽崽小满意已经孕 35 周 5 天了(听宝子说崽崽今天做 b 超有 5.5 斤了)。\n\u0026gt; 2024-01-08 10:55 我的千金小满意出生啦 😄。\n🎉 婚期、婚纱照 ✔️\n暂定为五一期间(与老婆商量)?\n考虑到如果太晚,孕期会有诸多不便; 可以把当年年假( 04-15 生效 )利用上; 方便婚后续‘工作调整’相关事宜的进行。 以此为准,婚纱照的拍摄应在新年开春之后,天气转暖之时(3 ~ 4 月份),不热也至于太冷(深圳)。\n有些问题解决了再说了……\n婚纱照补拍,不可或缺! 婚礼形式准备从简,旅行结婚? 婚礼从简但要有意义,旅行结婚。\n📈 工作调整\n若以上计划顺利进行,则工作调整可安排到婚后(预计 6 月之后),距今( 2022-10-19 )亦有半年多的时间。\n:: 这大概就是‘拖延’的力量,这随随便便一拖 1 年过去了。(\u0026gt; 2023-12-18 16:38)显然,还没有准备好,或者根本就没有做什么准备,这就是没有计划、不严格执行计划的必然。\n必做好充分的准备:\n充分夯实基础理论(生态、底层机制); 充分了解现有项目; 充分熟悉面试相关题目(emm…🤣 它应该贯穿整个准备流程)。 :: 想来,这一年是所有事情比较集中的一年了。\n\u0026gt; 2023-12-19 15:16 距离 2024 还间隔 12 天,2023 年就这样过去了。不妨就做计划这个当口,做一次不大不小的总结吧。\n2024 - 2025 2024 龙年,准备迎接‘龙兔宝’小满意的降生。\n:: 女孩就叫‘刘沐(慕、忆)恩’,男孩就叫‘刘沐(慕、忆)泽’(好的吧,起名字这件事,简直不要太强迫症了),emm… 理论上一胎‘小龙女’是极好的。\n大名刘奕遥,小名满意。 ✔️\n计划要做的事:\n写本小说 学习平面设计相关 做自媒体 婚礼/满月酒 工作调整(制作简历、面试准备) 2025 - 2026 2025 ,‘龙宝’ 小满意 1 岁了。视具体情况看明年(2026)是否再要一个‘小马驹’,要的话,就要辛苦老婆大人喽。\n2026 - 2027 2026 马年,或许要迎接‘小马驹’的降生。\n2027 - 2028 2027 ‘龙宝’ 小满意 3 岁了~~(‘小马驹’ 1 岁)~~,该准备上学了。这期间(2025 ~ 2027),全力完成定居(成都或重庆)及工作调动的事情。\n2028 - 20** 2028 ~ …\n好的嘛,原来五年时间,过的很快嘛……\n","date":"2022-10-01","permalink":"https://aituyaa.com/docs/%E8%AE%A1%E5%88%92/","summary":"\u003cblockquote\u003e\n\u003cp\u003e凡事,预则立,不预则废。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"计划"},]
[{"content":"🍹 \u0026gt; 对自己说:\n法于阴阳,合于术数。食饮有节,起居有常。 恬淡虚无,真气从之,精神内守,病安从来? 我们应该用一种真心让自己感觉到舒适的方式去生活,\n不就奢华,不饰节俭,\n爽不爽的,只有自己的心最清楚。\n强健的体魄,有趣的灵魂,平和的内心。\n心向光明,天地皆宽。\n![[assets/nuli_cr1.jpg|300]]\n你的一切作为都应该以你的核心目标为中心,\n否则,你的生活很容易陷入“瞎忙”的状态,\n最最重要的是你可能越来越感觉不到愉悦和快乐。\n人,改变的只是自己。\n唯心和唯物从来都是对立统一的,唯物在外,唯心在我!\n\u0026gt; 2024-10-06 23:37\n拼搏应该是快乐的,\n奋斗应该是快乐的,\n这其中,“吃苦”也应该是快乐的,\n如果你感觉不快乐,\n那一定是什么地方出现了问题。\n一个人,\n很难在自己感觉不到价值的地方有太大建树,\n无论意识到了还是没意识到,\n欣怡于付出的人,\n其内心的满足是常在的,\n这个,\n自己也骗不了自己。\n","date":"2022-07-18","permalink":"https://aituyaa.com/%E5%85%B3%E4%BA%8E/","summary":"🍹 \u0026gt; 对自己说: 法于阴阳,合于术数。食饮有节,起居有常。 恬淡虚无,真气从之,精神内守,病安从来? 我们应该用一种真心让自己感觉到舒适的方式去生活, 不就奢华,不饰节俭","title":"关于"},]
[{"content":"🎨 hugo-theme-virgo\n为什么要把使用指南单独列出,而不是放在 readme 中?\n一方面,内容更新频繁(虽然没有必要),把更改放在 readme 中需要每次都提交主题更新,然而有时候只是单纯的更新 readme 而已。\n另一方面,国内网络推送到 github 仓库的时候经常抽风 (如下图),速度也慢。\n![[assets/pasted image 20230815144943.png]]\n更新日志 - 2025-01-03 14:23 优化并启用站内搜索,使用本地缓存降低性能耗损和加载速度\r- 2024-12-03 15:04 添加折叠板功能支持(支持代码块样式和引用块样式)\r- 2024-05-09 10:57 [可配置] 导航页增加迷你必应搜索(供导航页向下滚动后仍可快捷搜索)\r- 2024-05-08 09:43 优化本地搜索文本内容展示,设置显示最大高度,隐藏滚动条\r- 2024-04-29 21:23 [可配置] 集成并优化旧版本地搜索(页面检索)到当前版本\r- 2024-04-28 16:08 导航页添加固定链接(pin)区域,只需在导航链接前添加 \u0026#39;\u0026gt;\u0026#39; 即可生效\r- 2024-04-23 19:07 [可配置] 导航页添加顶部搜索栏(原 ship 页移植)\r- 2024-04-22 10:52 除相关文章栏(左)、及目录栏(右),默认在新标签页中打开链接\r- 2024-04-19 12:51 [可配置] 文章页新增相关(同标签)系列文章栏(页面左侧区域)\r- 2024-03-20 11:38 修复旧完整版本搜索内容不完整的问题,并更新旧版本到分支 1.x\r- 2023-09-27 15:12 [可配置] cool 模式下更灵活地配置背景壁纸和遮罩透明度\r- 2023-09-21 17:04 [可配置] 恢复 dark 暗色模式(真的不常用),并调整部分图标\r- 2023-09-07 16:24 调整 1920*1080 分辨率下样式,正确渲染不以 / 开头的图片\r- 2023-08-15 12:21 合并、删除 ob 分支,为功能完整版打标签 v1.0.0\r- 2023-08-14 15:46 [可配置] 新增 giscus 评论插件,并默认使用(代替 utterances)\r- 2023-08-02 18:31 新增 ob 分支,精简功能,适配 obsidian\r- 2023-03-23 14:58 [可配置] 增加 utterances 评论插件\r- 2023-03-22 16:08 更新主页快捷联系选项,增加知乎和简书\r- 2023-03-21 17:58 更新导航页面文件结构 - `nav.md` 或 `nav/index.md`\r- 2023-03-19 00:05 优化导航页面快速跳转 简介 一个简单纯净的主题,欢迎使用。🎉🎉🎉\n版本 演示 配置 导航页 备注 旧完整版 https://aituyaa.github.io config-old.toml nav-old.md 分支:1.x 新精简版 https://aituyaa.com config-new.toml nav-new.md 分支:master \u0026gt; 版本功能简介\n旧完整版 ![[assets/pasted image 20230815105343.png]]\n‘她’包含:\n两种模式:纯净、酷爽, 内置但不限于精美的本地字体, 高亮的层级目录,以及 简单强大的本地文章实时搜索功能等。 新精简版 该版本为基于旧版本的精简版简约版本,包含但不限于以下更新:\n默认移除本地全局搜索功能(可配置添加); 页面归并改造 移除独立的文章页、归档页入口 归档页精简为只显示标签列表(obsidian 只支持标签),置于首页 更新导航页 移除暗色模式(基本没用过) 恢复了,偶尔用 移除相邻文章切换功能(使用不多) 精简了配置项 …… ![[assets/snipaste_2024-02-26_16-36-42.png]]\n快速开始 下载主题 首先,下载该主题。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 方案一 cd your_site_dir\t# 本地站点目录 ## === 默认拉取新精简版 git clone https://github.com/loveminimal/hugo-theme-virgo.git themes/virgo ## === 若要拉取旧完整版 ## --- 鉴于有一些朋友喜欢旧的版本,为了修复问题方便,新开一个分支 1.x ## --- ✔️ 所以,你现在可以使用从该分支直接拉取旧版本的最新代码了 ## --- \u0026lt;your_local_branch\u0026gt; 是你在本地仓库中所使用的分支名称 git checkout -b \u0026lt;your_local_branch\u0026gt; origin/1.x ## --- 关联本地分支到远程分支(可选) git branch --set-upstream-to=origin/1.x \u0026lt;your_local_branch\u0026gt; ## ❌ 不再推荐从标签拉取,那样你可能会错过一些功能性的修复 ## git clone --branch v1.0.0 https://github.com/loveminimal/hugo-theme-virgo.git themes/virgo ## git switch -c master # 方案二 cd your_site_dir git submodule add https://github.com/loveminimal/hugo-theme-virgo.git themes/virgo 💡 使用哪种方式呢?如果你有这个疑问,那么就选择第一种方案。\n然后,更新你站点的 config.toml 内容,如下(后续可按需修改):\n旧完整版 config-old.toml 新精简版 config-new.toml 现在,你就可以进入网站根目录,运行 hugo server -d 开始你的折腾之旅了。\n站点文件目录结构 jack@jk:~/site$ tree -l 3\r.\r├── config.toml ;; 站点配置文件\r├── content ;; 博文内容\r│ ├── assets ;; 博文引用的图片等静态资源\r│ │ ├── 1a941h59-0.jpg\r│ │ ├── pasted image 20230525160829.png\r│ │ ├── pasted image 20240828002324.png\r│ │ ├── xr-12.gif\r│ │ └── ... │ ├── snippets ;; obsidian 中使用的一些插入模板\r│ │ ├── frontmatter.md\r│ │ ├── longitudinal-management.md\r│ │ ├── more.md\r│ │ └── ...\r│ ├── 00 计算机科学速成课.md ;; 博文\r│ ├── 01 计算机早期历史.md\r│ ├── 01 计算机系统漫游.md\r│ ├── 反射与泛型.md\r│ ├── 工作 ;; 子目录文件夹\r│ │ ├── 工作总结.md ;; 子目录博文\r│ │ ├── 工作资料.md\r│ │ └── ...\r│ ├── 文件转换.md ;; 博文\r│ ├── 防抖与节流.md\r│ ├── 饮食调整.md\r│ ├── 马克思主义简览.md\r│ └── ...\r├── package.json\r├── readme.md\r├── resources ;; hugo 自动生成的资源\r│ └── _gen\r│ ├── assets\r│ └── images\r├── scripts ;; 一些自定义脚本\r│ ├── deploy.sh ;; 一键部署脚本\r│ ├── server.bat\r│ └── serve.sh\r├── static ;; 静态资源文件夹\r│ ├── emojing ;; 子项目\r│ │ ├── assets\r│ │ ├── favicon.ico\r│ │ ├── imgs\r│ │ ├── index.html\r│ │ └── readme.md\r│ ├── imgs ;; 一些图片资源\r│ │ ├── bg\r│ │ ├── icons\r│ │ ├── manyi1.jpg\r│ │ ├── manyi.jpg\r│ │ ├── me.png\r│ │ ├── mygirl.png\r│ │ └── us.jpg\r│ └── ship ;; 子项目 │ ├── assets\r│ ├── favicon.ico\r│ ├── fonts\r│ ├── imgs\r│ ├── index.html\r│ └── readme.md\r└── themes ;; 主题文件夹\r└── virgo ;; 当前使用主题\r├── archetypes\r├── assets\r├── config\r├── config.toml\r├── images\r├── layouts\r├── license\r├── readme.md\r├── static\r└── theme.toml 旧完整版注意事项 如同下个小章节 [[#导航页]] 中要提到的,我们需要为几个定制页面建立对应的 *.md 文件,如下:\n定制页 路由 对应的 md 文件 导航页 /nav content/nav.md 归档页 /archive content/archive.md 搜索页 /search content/search.md \u0026hellip; 其中,相应 *.md 文件中,其 front-mattar 中的 title: 是必需的。实际上,你可以通过以下命令来创建它们:\nhugo new content/nav.md\rhugo new content/archive.md\rhugo new content/search.md\r... 除了导航页,其他页面都是没有内容的,创建完成之后即可。关于导航页,为了获得更好的体验,和预览中的样式,请遵循相应的内容格式,详见 [[#导航页]] 中的具体描述。\n导航页 旧完整版\n对于功能完整版,如果,你想使用导航页并正确渲染,那么就应该严格按照这种格式搭建你的 nav.md 文件结构,详见 nav-old.md 。\n为什么要做格式方面的限制呢?\n众所周知,markdown 对 table 的支持很一般,鉴于导航页的内容主要是外链和书签,使用列表管理是最方便的。另外,我们会使用 js 进行内容项的统计,所以就需要使用者遵守格式,不然可能页面显示可能会不正常。\n新精简版\n该版本对于 nav.md 的格式要求就比较宽松,详见 nav-new.md 。\n导航页放在哪儿?\n放在站点根目录中的 contents/nav.md 即可。\n它本质上也是一个博客文件,只不过由于其与常规页面而言有一些定制化,我们在 front matter 中定义了 type: nav 来标识它。\n对于导航页,我们还做了哪些事情? 接着往下看。\n关于搜索 移植 ship 站外搜索\n我们把之前定制的搜索页移植到了导航页面,并添加了一些动态效果。loveminimal/ship: a simple search page. 是一个独立的项目,单独部署没有任何问题。之前一直做为子模块嵌在当前主题中,但其风格有时候跟当前主题有些割裂,尤其是当你激活 cool 模式的时候。另外,顶部的快捷导航图标也需要单独维护,尤其是做出更改的时候。\n现在,我们直接把它移植过来,可以直接复用 _contact.html 模板页,并允许在 config.toml 配置它。\n移植站内搜索\n最初我们在新当前版本中是移除了本地搜索的,因为这个功能其实真的不怎么使用,现在加上它也只是因为无聊的很~\n既然做了,我们就试图把这个功能做的更好些。我们把旧版本分离式的交互逻辑精简,直接集成在了当前版本的主题中,优化了一些比较耗费性能的触发事件。它不再是一个单独的页面,而是做为一个页面组件插入在导航页的顶部区域。\n事实上,导航页是我最常用的页面。\n\u0026gt; 2025-01-03 14:17\n鉴于随着站点内容体量不断增加,每次都强制更新搜索内容源缓存是没有必要的。虽然,现代浏览器一般提供了自动缓存功能,但还是在代码层面修改更加完善。本次对于站内搜索做了以下优化:\n对搜索内容源进行本地缓存(桌面端每天自动更新缓存、移动端每月自动更新缓存); 且允许通过点击左侧探索图标进行强制拉取更新最新缓存。 其他 提取固定常用链接\n我们在 [[导航]] 中使用表格来保存导航链接。尽管我们对其做了分类,但随着链接数量的增加,更快地找到常用链接仍然变成了一个问题,于是增加一个可以固定在顶部的常用导航区域。\n为了方便地在导航页编辑和更换固定的链接项,我们在主题文件中 runmisc.js 中做了一些事情,只需要在导航链接的描述文本前加上 \u0026gt; 符号(见下图),该链接项就会自动固定在顶部区域。\n\u0026gt; 固定链接的添加示例\n![[assets/pasted image 20240429151056.png]]\nps:我们还做了一些“无聊但有趣的事情” 😅,使用 md5 对链接文本加密,并提取前 6 个字符拼成一个十六进制的颜色值,做为固定链接项的“颜色球”来标识它,所以你会发现不同的链接项,颜色球都是不一样的。\n标记语法增强 \u0026gt; 使用 js 对 markdown 做出的一些增强性修改\n不止一次吐槽过 markdown 虽然是纯文本性质的,但是其某些标记语法真的是让人不敢恭维,直观性和表现力都是一般。不过,从另一个方面来说,本来就是轻量级的标记语言,不可能承载太多。\n本来想直接修改 markdown 引擎来实现,研究了一下,还要颇费一番工夫。鉴于仅满足于个人使用,用一些曲线方式使用 js 来实现反而更加简单些。\n此处就记录一下针对 hugo-theme-virgo 做的一些魔改。\n行内格式 markdown 中的行内格式有以下几种:\n语法 效果 转译 html 标签 **加粗** 加粗 \u0026lt;strong\u0026gt;加粗\u0026lt;/strong\u0026gt; *斜体* 斜体 \u0026lt;em\u0026gt;斜体\u0026lt;/em\u0026gt; ~~删除线~~ 删除线 \u0026lt;del\u0026gt;删除线\u0026lt;/del\u0026gt; ` 行内代码` 行内代码 \u0026lt;code\u0026gt;行内代码\u0026lt;/code\u0026gt; \u0026mdash; 下划线 \u0026lt;u\u0026gt;下划线\u0026lt;/u\u0026gt; 是的,markdown 中没有下划线的标记语法。\n本来想用行内代码的标记格式做魔改,鉴于博文中出现行内的代码的概率较高,遍历起来相对更耗性能(虽然并没有多少),故决定选择 *斜体* 语法标记,其使用频率不多,且其对应的 org mode 中可以直接显为粗体显示。\n新增语法 效果 *_下划线* _下划线 *=高亮* =高亮 | *-高亮* | -高亮 | ❌\n| *=吐槽系* | =吐槽系 | ❌\n如此,我们便增加了 _下划线 和 =高亮 两种语法标识了。另外,在文章中,尤其是一些摘录和转载的文章中,我们需要做一些随笔,之前我们是使用 \u0026lt;div class=\u0026quot;oh-essay\u0026quot;\u0026gt;...\u0026lt;/div\u0026gt; 这种标签插入,如上表,我们也对其做了语法标识。\n如上,所示,更改了一些语法标记,因为有的 markdown 引擎中使用 ==高亮== 来高亮文本,我们这里就用 *=高亮* 来表示,以做到在观感上统一。\n另外,我们不再使用 *=吐槽系* 来表示个人在摘录或编辑中的个人想法展示,主要是由于在不支持当前语法标记的主题中,它只能以斜体展示,不容易和正文内容作区分。我们使用 \u0026gt;:: 吐槽系 或 \u0026gt; :: 吐槽系 来表示,如此在不支持的情况下,可以解析为引用样式,便于区分。\n之前我们做了一些 snippet 进行 html 标签的插入,以实现以上效果,但是这就限定在了某些编辑器中,些许背离了纯文本输入的理念,以上小小的增强,使得我们可以任何文本编辑器中进行方便的文本输入。\n\u0026gt;:: 好吧,尽管它们只能在 `hugo-theme-virgo` 中才有效果 😅\r\u0026gt; ::好吧,尽管它们只能在 `hugo-theme-virgo` 中才有效果 😅\r\u0026gt; :: 好吧,尽管它们只能在 `hugo-theme-virgo` 中才有效果 😅 上述语法标记是等价的,会被解析为如下样式:\n:: 好吧,尽管它们只能在 hugo-theme-virgo 中才有效果 😅\n代码块折叠 在 markdown 中,包裹代码块很方便。但有时候在博文中,我们可能引入较多的代码片段,这会导致正文内容的间断,所以,允许其进行折叠,可以在 `config.toml` 中,使用 `hasfoldallcodeblocks: true` 进行初始化。\r既然已经可以折叠了,这里我们不妨用它再做一个更通用的折叠板(默认折叠),原理也很简单,利用 `lang` 判别。\r如果其为 `_lang` 这种格式,则表示轻量级代码折叠 - 不换行;如果使用 `__lang` 则折叠板中内容会自动换行。 💡 在新精简版中,我们 不再支持 代码块折叠及折叠板功能。\n\u0026gt; 2024-12-03 14:52 新的精简版(好嘛~)重新支持折叠版功能,使用方式不变,实现逻辑进一步优化 - 这里我们使用 html 原生标签 \u0026lt;details\u0026gt; 来实现。\n在 markdown 中,我们只需要为代码块指定不同以 1 个下划线 _ 或 2 个下划线 __ 开头的标识即可,如 _折叠代码块 、 __折叠引用块 。\n_lang 这种格式,表示折叠代码块 - 其内容不换行,其行为及样式和 pre 保持一致; __lang 这种格式,表示折叠引用块 - 内容换行,其行为及样式和 blockquote 基本保持一致。 ps:关于字体大小,前者与代码块本身保持一致(14px);后者则与正文内容保持一致(16px)。\n\u0026gt; 1、不带语言标识的代码块\n@springbootapplication\rpublic class application {\rpublic static void main(string[] args) throws exception {\rspringapplication.run(application.class, args);\r}\r} \u0026gt; 2、带语言标识的代码块\n1 2 3 4 5 6 @springbootapplication public class application { public static void main(string[] args) throws exception { springapplication.run(application.class, args); } } \u0026gt; 3、以 _ 开头的描述头(不要有空格),不会自动换行\n@springbootapplication\rpublic class application {\rpublic static void main(string[] args) throws exception { // 不会自动换行,这是一个类的主函数 main 呀~ springapplication.run(application.class, args);\r}\r} \u0026gt; 4、以 __ 形状的描述头(不要有空格),会自动换行\n@springbootapplication\rpublic class application {\rpublic static void main(string[] args) throws exception { // 会自动换行,这是一个类的主函数 main 呀~ springapplication.run(application.class, args);\r}\r} wiki 链接及图片语法渲染 最近使用 obsidian ,其使用的链接及图片格式为 wiki 语法,如下:\n名称 描述 链接 ![[assets/pasted image 20231005135434.png|60]] 链接(带描述) ![[assets/pasted image 20231005135451.png|116]] 图片 ![[assets/pasted image 20231005135507.png|120]] 图片(带尺寸) ![[assets/pasted image 20231005135528.png|166]] hugo 默认的 markdown 引擎是不支持渲染这种语法的,我们这里做了一下增强,现在你可以畅快地使用 obsidian 来编辑你的博客了。\n:: 图片和链接,好像 wiki 的这种语法写起来更加简洁。其实,还是使用 \u0026lt;img\u0026gt; 标签的通用性更好些,不过许多软件的即时渲染又不支持,就很伤。\n图片渲染相关 在使用 obsidian 或 typora 自动插入图片时,自动插入的图片路径形式为 assets/xxx.jpg 等(无法通过设置使其插入路径为 /assets/xxx.jpg 的形式),hugo 无法正确渲染访问路径,它在解析的时候,会生成有类似如下路径: http://localhost:1313/temp-%e8%b0%83%e8%af%95%e6%96%87%e4%bb%b6/assets/baby.png ,所以,我们对其遍历,为不以 / 开头的路径,添加 / ,以使其正确渲染。\n我的怎么写博客的 在《[[一场疲惫的主题制作之旅]]》中,已经有了不少博客相关的碎碎念。这里,主要用来浅谈一下当前站点博客系统的搭建、编辑及部署相关的系列流程。\n当前站点,使用 hugo 静态博客生成系统 驱动,部署在个人服务器上。事实上,你可以把生成的站点项目部署在任何可以被访问的地方(比如 github page),它本质上是一个包含了若干 .html 文件及相关静态资源的文件夹。\n准备篇 ……\n搭建篇 ……\n编辑篇 这里,我们以当前文章的创建及编辑过程为例。其中:\n✔️ 正在使用 ❌ 已不使用 :: 怎么说呢?这个章节写的过于太细节了……好像…… 😅\n❌ 使用 vscode 编辑 创建文章 我们可以使用以下命令来创建文章:\n1 2 3 hugo new posts/how-do-i-blog/index.md\t# 推荐 # 或 hugo new posts/how-do-i-blog.md\t# 不推荐 这里我们使用第一条命令,该命令会自动生成如下目录层级下 .md 文件。\ncontent\r├── posts\r│ ├── how-do-i-blog\r│ │ ├── imgs\r│ │ │ └── 1aa09c580e674b09e82c722a3689d280012f2ae6e1700e924deeef558347d91a.png\r│ │ └── index.md 为什么不直接使用 how-do-i-blog.md ,而使用 how-do-i-blog/index.md ?\n正如上述目录层级中所反映的,如此方便我们把当前文件所需要的资源(如图片 imgs )都放在当前文章的层级下,方便管理。\n这样做还有额外的好处,我们将在后面 [[#插入图片]] 的部分进一步说明。\n插入图片 在文章中插入图片是一个相对高频的操作。在第三方的博客平台中,一般来说直接复制图片并粘贴到要插入的位置就可以了,很方便。而编辑 .md 文件,插入图片就稍微麻烦一些。\n我们通过  在文章中插入图片,默认情况下,你需要经过:\n1. 搜索图片\r2. 另存图片到本地\r3. 编辑 `` 引用\r…… 很繁琐!\n而且还不能控制图片的‘显示’尺寸,需要插入图片数量过多的时候,简直就是一种折磨了。\n有没有一种更好的方式来插入图片呢?\n很幸运,有!\n我平时是使用 vscode 来管理站点内容和编辑 .md 文件的,其中有一款插件很好地解决了这个问题。\n![[assets/pasted image 20230526102347.png]]\n它提供了丰富的自定义设置选项,这里主要用到以下几种:\nmarkdown-image › base: file name format ,设置为 ${hash} ,当然有其它各种格式可选组合; markdown-image › base: image width ,设置为 400 ,默认宽度设为 400px ; markdown-image › local: path ,设置为 ./imgs ,生成的图片放在当前文章同级目录下的 imgs 文件夹中。 这也是上文中我们推荐使用 hugo new posts/how-do-i-blog/index.md 命令来生成文章的原因之一。\n使用该插件,你只需要复制所需要图片(本地或网络图片),并通过其提供的粘贴方式(右键选择)插入到位置即可。如此,你的 .md 文件中,就会插如下内容:\n\u0026lt;img alt=\u0026#34;picture 3\u0026#34; src=\u0026#34;imgs/30737f6467ed6269eed8911b8a915f47b9fed706b8f892efd3271d9b6a76181c.png\u0026#34; width=\u0026#34;400\u0026#34; /\u0026gt; 它会被渲染成下面这张图片,是不是很方便!\n![[assets/pasted image 20230526102356.png]]\n它的原理是什么?\n它会读取你剪切板中刚刚复制的图片数据,在你粘贴的时候,重新生成一份拷贝,并在 .md 文件中,插入对应的图片格式,并引用。真的很方便!🎉\n其他插件 cyberbiont/vscode-open-in-typora: open markdown files from vscode in typora\n![[assets/pasted image 20230815170141.png]]\n在 vscode 中使用 typora 打开当前 markdown 文件。\n❌ 使用 typora 编辑 创建文章不是 typora 的强项,光是手写 front matter 都让人受不了,就老老实实地用 [[#创建文章]] 章节中的命令创建好文章,再用 typora 编辑就好。(这也是不支持第三方插件的坏处了,什么问题你只能等着作者去解决。相对来说,obsidian 就不存在这个问题,它支持类似于 snippet 的插件扩展。)\n:: 现在的编辑操作就是使用 typora 完成的。 😄\n之前的 vscode 使用的不是很爽吗?为什么切换为 typora 了呢?\nvscode 确实很爽,到目前为止,我也经常使用它。切换到 typora 的原因也很简单,家里的电脑性能不行,新的主机配置还在纠结中……\n更多原因可以查看 [[一款 typora 主题]] 中的描述。\nps: vscode 真的是一款非常优秀的编辑器,插件丰富且优质,可扩展性强,可轻可重。\n使用 typora 的感觉怎么样?\n很好!如 [[一款 typora 主题]] 中所描述的那样,配合自己制作的主题(由站点样式适配),基本上实现了所见即所得的编辑。另外,typora 本身集成了许多 markdown 相关的快捷键,很直观也很好用,尤其是当你专注于编写内容的时候。\n它还有‘专注模式’和‘打字机模式’,很舒心。\n其内置的图片插入功能也不错,基本上和 [[#插入图片]] 章节中的实现是相同的,原理没有探究。怎么说呢,typora 上的相对来说,更加符合日常的编辑逻辑,还贴心地增加了可以缩放图片的选项,最最重要的还是‘所见即所得’,你可以实时看到你的图片。\n✔️ 使用 obsidian 编辑 支持扩展的编辑器是可以不断进化的,比如 emacs、vi/vim 等。\n上面说了,typora 是不容易扩展的(不支持),这就限制了使用,要么牵就,要么离开…… 尝试了几个软件之后,obsidian 可以说是相当不错的管理软件了,稍微‘调教’之后,就相当顺手了。\nobsidian 也是所见即所得的编辑模式,有非常丰富的三方插件,如键位映射、图片缩放/清理、表格插入/编辑等功能都可以很方便的得到实现。\n……\n为了更方便地显示图片及适应 [[如何使用 hugo-theme-virgo 主题]] 中关于 [[#wiki 链接及图片语法渲染]] 的兼容,重新组织了博客层级,如下:\nc:\\users\\jack\\appdata\\roaming\\site\\content\r│ a simple bookmark copying.md\r│ ajax.md\r│ c.md\r│ canvas.md\r│ cpu 是如何制造出来的.md\r│ cpu 缓存是什么.md\r│ css 中的动效.md\r│ emacs lisp.md\r│ emojings.md\r│ git.md\r│ gtd 管理系统.md\r│ java 那些事儿.md\r│\r├─assets\r│ 3b5d12f26d9d00dc2b021d097ff8c34.jpg\r│ 4.gif\r│ pasted image 20230525155800.png\r│ pasted image 20230817094853.png\r│ vim.gif\r│\r└─snippets\rfrontmatter.md\rlongitudinal.md\rmore.md\rtimestamp.md 并且,使用 tags 标签来分类博文,而不是之前的 categories 分类。\n创建文章 如上面的博客层级所示,我用 content 目录作为 obsidian 的根目录,如此,obsidian 相关的所有配置(如插件、主题、设置等) .obsidian 文件夹都使用 git 统一管理。\n💡 .obsidian 默认位于 obsidian 仓库的根目录。\n创建文章很简单,直接使用 obsidian 提供的相关创建逻辑就行了。需要注意的是,如果你的博文也想用 hugo 进行管理的话,就需要在博文头部添加相应的信息。如:\n---\rtitle: 我是怎么写博客的\raliases: []\rtags:\r- hugo\rdate: 2023-05-26 time: 10:21\r--- 其中, title 和 date 是必须的,hugo 需要使用其提供的信息进行渲染。\n如果你使用 hugo new 命令去生成 .md 文件的话,那么你只需要设置 hugo 相关的 archetypes/default.md ,如:\n---\rtitle: \u0026#34;{{ replace .name \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#34;\raliases: tags: [_misc]\rdate: {{ .date | time.format \u0026#34;2006-01-02\u0026#34; }}\rtime: {{ .date | time.format \u0026#34;15:04\u0026#34; }}\r--- 然后,运行如 hugo new my-post-name.md 之类的命令就可以了。\n当然,obsidan 本身也是支持插入模板的,如 templater 模板插件,你写好模板,放在相应文件夹下(如 snippets/frontmatter) ,运行插入命令即可,如:\n---\rtitle: \u0026lt;% tp.file.title %\u0026gt;\raliases: tags:\r- _misc\rdate: \u0026lt;% tp.date.now() %\u0026gt;\rtime: \u0026lt;% tp.date.now(\u0026#34;hh:mm\u0026#34;) %\u0026gt;\r--- 插入图片 obsidian 的插入图片很方便,你只需要像文本一样粘贴进来就好了。图片会自动上传到设置好的目录,可以是相对于当前文件的相对位置,也可以相对于仓库根目录的绝对位置。\n图片插入后,默认是 wiki 语法的,如 ![[path/to/img]] ,可以在设置中修改为常用 markdown 语法的。当然,如果你使用 hugo-theme-virgo 主题的话,就不用担心,它支持 wiki 语法的图片和链接的渲染。\n![[assets/pasted image 20231026173226.png]]\n这个插件,可以让你方便的调整图片尺寸,如按着 alt 键滚动缩放。\n插入表格 :: 目前新版本的 obsidian 已经完美支持表格操作,便捷好用,终于可以完全抛弃 typora 了!\n这方面,目前没有遇到比 typora 做的更好的了。 当然,借助下面这个插件,可以实现类似 typora 表格插入和编辑的体验:\n![[assets/pasted image 20231026173536.png]]\n另外,这个插件是没有上传到官方插件仓库的,但可以借助 obsidian42-bart 这个插件去引入,也很方便。\n![[assets/pasted image 20231026173648.png]]\n不得不说,支持第三方扩展的软件,才会更有生命力!\n渲染篇 详见 [[#标记语法增强]] 章节。\n部署篇 当你想部署你的站点内容到托管平台时,你可能会经过以下步骤:\n1. 执行 `hugo` 命令,生成站点内容,默认放在站点根目录的 `public` 文件夹中;\r2. 复制内容包,上传到托管平台;\r…… 如果,只操作一次的话,不是很复杂,但如果,你的内容更新比较频繁,那就有些烦扰了。内容包的部署方式有很多,各有优缺。\n脚本部署 我们这里,使用脚本部署,一次性配置之后,每次部署只需要执行一条简单的命令即可。分享出来,供大家参考使用。\n我们的站点目录如下所示:\n.\r├── config.toml\r├── content\r│ ├── about\r│ ├── archive.md\r│ ├── _index.md\r│ ├── nav\r│ ├── posts\r│ └── search.md\r├── package.json\r├── readme.md\r├── resources\r│ └── _gen\r├── scripts\r│ ├── deploy.sh\r│ └── update.sh \u0026gt; 2023-09-05 22:41 最新 - 完全可以做为唯一的部署脚本 !它对站点内容的跟踪是完备的,当前环境下,只会在首次部署的时候进行全量更新,后续会自动基于 git 的更新记录进行增量式更新,体验相当不错。\n其中, scripts/deploy.sh 便是我们定义的部署脚本,其内容如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #!/bin/sh # ------------------- # 部署博客到指定的仓库 # deploy posts to special repo. # # 配置 configurations # ------------------- # 自定义 `repo_deployed` 为所要部署仓库 repo_deployed=\u0026#34;jack@aituyaa.com:/home/jack/.repo/site.git\u0026#34; # rm -rf \u0026#34;public/cname\u0026#34; \u0026amp;\u0026amp; cp -r \u0026#34;cname\u0026#34; \u0026#34;public/\u0026#34; # fix potential error - recovery `cname` before deploy. # 判断是否存在 public if [ -d \u0026#34;public\u0026#34; ] then # 判断是否已经存在与 site 同级的 .temp 文件夹 if [ ! -d \u0026#34;../.temp\u0026#34; ] then # ^ 若不存在,直接复制 public 为 ../.temp ,进入该目录,初始化,强制推送至指定的远程仓库 echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; .temp not exited. \\e[0m\u0026#34; cp -r \u0026#34;public\u0026#34; \u0026#34;../.temp\u0026#34; echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt;[done] copy public to .temp. \\e[0m\u0026#34; cd \u0026#34;../.temp\u0026#34; git init git add . git commit -m \u0026#34;posts update.\u0026#34; git remote add origin $repo_deployed # git remote add origin https://github.com/loveminimal/loveminimal.github.io.git # git push -f origin master:main git push -f origin master else # ^ 若存在,则进入 ../.temp 目录,判断该仓库是否已初始化(包含 .git ) echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; .temp exited. \\e[0m\u0026#34; cd \u0026#34;../.temp\u0026#34; if [ -d \u0026#34;.git\u0026#34; ] then # ^^ 若存在 .git ,则备份 .git ,并在处理 .temp 后,恢复它 echo -e \u0026#34;\\e[42m \u0026gt;\u0026gt;\u0026gt; copying.... \\e[0m\u0026#34; mv \u0026#34;.git\u0026#34; \u0026#34;../.git.bak\u0026#34; # 此处,对于 .temp 处理的目的是为了绝对保持其与 site/public 文件的一致性(无论增删) cd .. rm -rf .temp cp -r \u0026#34;site/public\u0026#34; \u0026#34;.temp\u0026#34; mv \u0026#34;.git.bak\u0026#34; \u0026#34;.temp/.git\u0026#34; cd .temp git add . git commit -m \u0026#34;posts update.\u0026#34; git push -f origin master else # ^^ 若不存在 .git ,初始化,强制推送至指定的远程仓库 git init git add . git commit -m \u0026#34;posts update.\u0026#34; git remote add origin $repo_deployed # git remote add origin https://github.com/loveminimal/loveminimal.github.io.git # git push -f origin master:main git push -f origin master fi fi echo -e \u0026#34;\\e[42m \u0026gt;\u0026gt;\u0026gt;[done] update. \\e[0m\u0026#34; cd \u0026#34;../site\u0026#34; fi 在站点根目录下,运行 source scripts/deploy.sh 就可以静待站点部署完成了。\n💡 注意修改 repo_deployed 为你自己的部署仓库!\n命令简化 使用 source scripts/deploy.sh 还是有点太复杂了?有如下限制:\n需要进行到站点根目录才可以运行它; 部署后,站点内的内容包并没得到清理。 哈,你只需要多加几条命令即可,如下:\ncd ~/appdata/roaming/site \u0026amp;\u0026amp; rm -rf public \u0026amp;\u0026amp; hugo \u0026amp;\u0026amp; source scripts/deploy.sh \u0026amp;\u0026amp; rm -rf public 如此,无论当前你在那一种路径,都会:\n自动进入站点根目录(此处是 site); 删除站点下旧的 public (如果有的话); 根据当前内容生成新的 public 内容包; 执行部署脚本,发布到对应托管平台; 清理掉生成的 public 内容包。 emm… 还是长啊,每次都要键入这么长,太麻烦了,怎么办?\n那么你就需要了解一些关于 bash alias 方面的知识了,如下所示,在当前用户家目录下,创建 .bash_aliases 文件(若无),并添加如下内容:\nalias ssd=\u0026#34;cd ~/appdata/roaming/site \u0026amp;\u0026amp; rm -rf public \u0026amp;\u0026amp; hugo \u0026amp;\u0026amp; source scripts/deploy.sh \u0026amp;\u0026amp; rm -rf public\u0026#34; 保存后,在用户家目录下,运行 source .bashrc 命令使 .bash_aliases 中的别名生效。\nok,现在,当你想部署站点的时候,你只需要运行键入 ssd ,回车即可完成部署。\n‘用户家目录’是什么?\n在 windows 下,有两个家目录:\n用户家目录,如 c:\\users\\jack ,一些软件的默认配置会放在该目录下; 用户漫游家目录,如 c:\\users\\jack\\appdata\\roaming ,另一些软件的配置又会放在这个目录下。 😅 微软的东西真的有点混乱哈。\n如果,你使用的是 gnu/linux 系统,那么家目录就只有一个喽,如 /home/jack 。\n❗❗❗😅 记一次踩坑……\n原来的域名 walkssi.com 废弃了,启用了新的域名 aituyaa.com 。不久前老域名到期之后,腾讯云那边的 dns 解析服务就停止了,但是公司主机上的 .temp 文件夹(上方部署脚本中生成的)已经存在,这导致一键部署的时候总是提示 ssh: connect to host walkssi.com port 22: connection timed out …… 搞的自己在腾讯云域名解析那里弄了好久,再跑到服务器上检查 nginx 设置…… blablabla……\n答案是:更换了域名之后,如果 .temp 文件夹存在,就要先删除它,因为它是一个包含 .git 的完备追踪文件夹,其会延用旧有的域名信息。\n附录 ❌ 废弃的部署脚本 最初版本的 scripts/deploy.sh 脚本,其内容如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #!/bin/sh # ------------------- # deploy.sh # deploy posts to `loveminimal.github.io` # ------------------- # 检测是否存在 .temp 文件夹,若存在,先移除 if [ -d \u0026#34;../.temp\u0026#34; ] then echo -e \u0026#34;\\e[31m \u0026gt;\u0026gt;\u0026gt; .temp exited. \\e[0m\u0026#34; rm -rf \u0026#34;../.temp\u0026#34; echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; ... \\e[0m\u0026#34; echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; .temp has been removed. \\e[0m\u0026#34; else echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; .temp not exited. \\e[0m\u0026#34; fi if [ -d \u0026#34;public\u0026#34; ] then # 如果你是部署到 github ,并绑定了域名,那你可能需要启用该行,以 # 保证其正确的指向 # rm -rf \u0026#34;public/cname\u0026#34; \u0026amp;\u0026amp; cp -r \u0026#34;cname\u0026#34; \u0026#34;public/\u0026#34; # 拷贝内容包 public 到一个临时文件夹 .temp ,并 # 用 git 初始化管理该它 cp -r \u0026#34;public\u0026#34; \u0026#34;../.temp\u0026#34; cd \u0026#34;../.temp\u0026#34; pwd git init git add . git commit -m \u0026#34;posts update.\u0026#34; # 添加远程库,引得我们使用的是个人服务器的仓库地址,如果 # 你是托管在 github 上,那么连接的对应的远程库即可 - \u0026lt;your_username\u0026gt;.github.io # 如果你是在 github ‘政治正确’后创建的库,其默认分支为 main, 那你 # 需要 master:main 而不是 master git remote add origin jack@ovirgo.com:/home/jack/.repo/site.git git push -f origin master # git remote add origin https://github.com/loveminimal/loveminimal.github.io.git # git push -f origin master:main # 清除临时文件夹 cd .. rm -rf \u0026#34;.temp\u0026#34; # 返回站点目录 cd \u0026#34;site\u0026#34; fi 初看,上述脚本初看可能有些混乱,但其实,理论上你只需要:\n更改远程仓库地址; 更改站点目录,即可。 \u0026gt; 2023-09-05 11:53 更新 - 添加增量更新脚本\n随着博文数量的增加,我们使用 deploy.sh 全量更新的话,时间也会随之变长。单纯的文字还好,但站点难免会引入外部的静态资源(图片、音频等),如此‘等待’就成了一个问题。所以,就有了 update.sh ,如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #!/bin/sh # ------------------- # update.sh # deploy posts to `loveminimal.github.io` # ------------------- if [ ! -d \u0026#34;../.temp\u0026#34; ] then echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; .temp not exited. \\e[0m\u0026#34; mkdir \u0026#34;../.temp\u0026#34; echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; ... \\e[0m\u0026#34; echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; .temp has been created. \\e[0m\u0026#34; else echo -e \u0026#34;\\e[32m \u0026gt;\u0026gt;\u0026gt; .temp exited. \\e[0m\u0026#34; fi if [ -d \u0026#34;public\u0026#34; ] then # rm -rf \u0026#34;public/cname\u0026#34; \u0026amp;\u0026amp; cp -r \u0026#34;cname\u0026#34; \u0026#34;public/\u0026#34; # fix potential error - recovery `cname` before deploy. cp -r \u0026#34;public/.\u0026#34; \u0026#34;../.temp\u0026#34; cd \u0026#34;../.temp\u0026#34; pwd # 该仓库没有初始化,初始化该仓库,并推送 # 仓库已初始化,仅推送增量更新 if [ -d \u0026#34;.git\u0026#34; ] then git add . git commit -m \u0026#34;posts update.\u0026#34; git push -f origin master else git init git add . git commit -m \u0026#34;posts update.\u0026#34; git remote add origin jack@walkssi.com:/home/jack/.repo/site.git # git remote add origin https://github.com/loveminimal/loveminimal.github.io.git # git push -f origin master:main git push -f origin master fi cd .. # rm -rf \u0026#34;.temp\u0026#34; cd \u0026#34;site\u0026#34; fi 它保留了临时文件夹的仓库跟踪,使得我们只需要提交增量更新即可,极大的缩小了仓库更新的时间。\n当然,这样也会存在一个问题,就是当你删除了某些静态文件,如图片等,仓库是无法追踪到的,它只能知道你增加了什么。幸运的是,这个场景本身很少出现,你并需要关注它。当然,如果你实在介意,你可以执行以下操作中的一个就可以实现全量更新了:\n手动删除 .temp 文件夹; 或者,你可以直接执行前面的 deploy.sh 脚本; 或者,你可以使用下面这个最新的 update.sh 脚本,它会严格保持修改前后文件的一致性,性能介于 deploy.sh 和旧的 update.sh 之间,比后者稍慢一点点(配置中等主机下几乎相同),但比前者快出非常非常多。 💡 事实上,你完全可以用下面这个最新(2023-09-05 22:41)的 deploy.sh 做为唯一的部署脚本(推荐)!\n结语 制作主题,对‘强迫症’来说,真的是一件挺‘辛苦’的事情,或者说是‘痛并快乐着’ ?!\n","date":"2022-05-06","permalink":"https://aituyaa.com/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-hugo-theme-virgo-%E4%B8%BB%E9%A2%98/","summary":"\u003cp\u003e\u003ca href=\"https://themes.gohugo.io/themes/hugo-theme-virgo/\"\u003e🎨 hugo-theme-virgo\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e为什么要把使用指南单独列出,而不是放在 README 中?\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e一方面,内容更新频繁(虽然没有必要),把更改放在 \u003ccode\u003eREADME\u003c/code\u003e 中需要每次都提交主题更新,然而有时候只是单纯的更新 \u003ccode\u003eREADME\u003c/code\u003e 而已。\u003c/p\u003e\n\u003cp\u003e另一方面,国内网络推送到 Github 仓库的时候经常抽风 (如下图),速度也慢。\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20230815144943.png]]\u003c/p\u003e","title":"如何使用 hugo-theme-virgo 主题"},]
[{"content":"i.e. cs (computer science) crash course\n![[assets/pasted image 20230526113630.png]]\n这个系列来自于 crash course 的一个视频系列课程(共有 40 课程,每个课程 10 分钟左右),多谢热心的 crash course 字幕组配上了优质的字幕 - b 站地址 📺 。\n知乎上已经有小伙伴做了同样的事 - 将视频课程文档化,详见 计算机速成课笔记 。我们在这里,重新制作一次文档,一是为了加深印象、深化理解;二是方便后续做内容扩展。\n好吧,竟然还有神奇的字幕组 - 计算机科学速成课字幕组 repo,本系列内容为以该仓库字幕文件为基础的图文版。\n感谢所有主创、二创人员的付出!🎉\n系列文章 [[01 计算机早期历史]] [[02 电子计算机]] [[03 布尔逻辑和逻辑门]] [[04 二进制]] [[05 算数逻辑单元]] [[06 寄存器和内存]] [[07 中央处理器]] [[08 指令和程序]] [[09 高级 cpu 设计]] [[10 早期的编程方式]] [[11 编程语言发展史]] [[12 编程原理 - 语句和函数]] [[13 算法入门]] [[14 数据结构]] [[15 cscc 其他1]] [[16 cscc 其他2]] ","date":"2022-03-26","permalink":"https://aituyaa.com/cscc/00-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E9%80%9F%E6%88%90%E8%AF%BE/","summary":"\u003cp\u003ei.e. cs \u003ccode\u003e(computer science)\u003c/code\u003e crash course\u003c/p\u003e\n\u003cp\u003e![[assets/Pasted image 20230526113630.png]]\u003c/p\u003e\n\u003cp\u003e这个系列来自于 \u003ca href=\"https://thecrashcourse.com/\"\u003eCRASH COURSE\u003c/a\u003e 的一个视频系列课程(共有 40 课程,每个课程 10 分钟左右),多谢热心的 CRASH COURSE 字幕组配上了优质的字幕 - \u003ca href=\"https://www.bilibili.com/video/BV1EW411u7th?p=1\u0026amp;vd_source=a6f6452712ce1cd91d115827d0148715\"\u003eB 站地址 📺\u003c/a\u003e 。\u003c/p\u003e","title":"00 计算机科学速成课"},]
[{"content":" :: 怎能让利剑空悬于高阁之上!?\n![[assets/pasted image 20230526092442.png]]\ngtd 分为横向管理和纵向管理两个方面。\n:: 无论哪一种管理系统,对于个人而言,最重要的是把它融入到日常生活中,就像每天早晚洗漱一样自然。正如《三傻大闹宝莱坞》中阿米尔汗说的“让优秀成为一种习惯,成功就会追求你”。\n\u0026gt; 2024-02-28 09:45\n:: 横向管理,其实就是事物在时空层面上的大搜集;纵向管理,则是事物在某一时空节点上的纵深。 横向收集,纵向执行! 纵向管理偏重在横向管理的行动阶段!\n横向管理 横向管理有五个阶段:收集、分析、组织管理、检查(反馈)、行动。\n:: 周期性的检查(反馈)是非常重要的,是整个系统得以正常运行的保障! \u0026gt; 2024-02-28 09:48\n1. 收集:填充工作篮 :: 🗑️ 应收尽收\n物理空间; 精神空间。 关键在于百分之百地 捕获一切未尽事宜 ,以最快的速度收集下来,放入工作篮。\n:: “应收尽收”是避免无休止精神内耗的最好办法,逃避应该面对的“问题”永远都解决不也什么实际问题!\n:: 保持极简主义生活方式,断、舍、离,可以从根本上大大缩短收集时间。\n:: 其实,哪有那么多时间来无聊,你只是不知道该做什么而已,与此同时,要做的东西却有很多,而且大概率还在增长。有时候,你知道该干什么,就是想拖着……\n:: 好的生活方式,可以让一个人更好地沉浸在某一种生活状态,以更利于个体的成长和发展。\n:: 在纯粹的精神世界中,一切起于念! 取舍之间的平衡点在于有序、足够、简约。极简主义中的核心虽然是断、舍、离,但在执行过程中,“度”很重要,而在保持过程中,“有序”很关键!\n2. 分析:清空工作篮 :: 🧾 分类清单\n:: 是否行动?输出清单!\n清空工作篮的本质在于确定每一项工作的内容和实质,判断其 是否需要采取行动 。\n不需要采取行动:\n垃圾 → 抛进垃圾桶; 孵化器 → 未来某一天可能会做; 参考资料 → 归档保存。 :: \u0026gt; 2024-11-01 11:21 找一种比较好归档方式\n需要采取行动的:\n小于 2 分钟的立即处理; 大于 2 分钟的: 委托他人: 等待; 日程表(有时间要求的)。 自己执行(在管理阶段列清单): 日程表(有时间要求的); 下一步行动清单。 项目: 制定项目计划。 :: 给所有大于 2 分钟的行动设置一个反馈机制(如委托人主动通知、个人定时问询等),以确保事项正常完成。\n注意,这一步中,并 不需要 采取任何实际的行动,关键在于合理分类,分类的时候应遵循以下原则:\n自上而下; 逐条处理; 机会均等。 :: 一切都是为了尽可能快地解除‘焦虑’状态,快速地为未尽事宜分类,归入清单。\n3. 管理 :: 🗂️ 💯 🔥 组织管理,重中之重\n对分类后的条目进行制定具体的行动方案 :\n垃圾 → 删除; 未来可期 → 孵化器; 参考资料 → 归档保存供查阅; 小于 2 分钟 → 立即处理; 下一步行动 → 必须是一目了然的具体行动; ✔️ 日程表 → 只允许放置有具体时间要求的任务; 项目 → 分解为具体的行动、安排日程。 注意, 日程表 是十分神圣的,它和 下一步行动 共同构成了每日管理的核心。\n四象限工作法是个不错的标准:\n![[assets/pasted image 20240228095532.png]]\n:: 紧急程度,重要程度。事有“轻重缓急”,此为四象限的本质。\n:: 在“分析”阶段,我们要做的是快速判断清空收集篮,在“管理”阶段,则需要认真分析,依照不同类型事物的标准进行切实的处理。\n:: 这一步无疑是 重中之重 ,只有科学的、具体化的、可执行的行动,才是解决问题的关键,才是改造世界的直接力量。\n:: 一旦制定了具体的行动方案,不要拖延,立刻行动!立刻行动!立刻行动!执行力很重要!\n4. 检查 :: 📋 因时制宜,因地制宜\n新的情况不断产生,每天的工作安排也必须时常回顾并做出相应调整,以保证 gtd 的尽收一切和全面管理。\n:: 学习,实践,再学习,再实践……\n如何检查呢?\n…\n每一天:\n以 日程表 作为开始,处理好日程表中紧要的任务; 之后,查阅 下一步行动 清单和 孵化器 。 注意,定期(如每周)更新清单是至关重要的。\n:: 养成习惯,让享受美好的事情像呼吸一样简单和自然!早九晚九必检一次,中间实时更新即可。\n5. 行动 :: 🏅 行动评估,具体实践\n选择最佳方案,如何决策?\n在某一时刻, 4 个模式:\n(地)环境; (时)有多少时间; (人)有多少精力; (事)重要性。 :: 天时,地利,人和,重要性!\n评估每日工作, 3 种模式:\n(预定)处理事先安排好的工作; (突发)处理随时冒出来的事件; (成长)定义你自己的工作。 :: 一切都在计划之中,留些弹性给突发事件。多管齐下,生活才会更有趣。还是要每天都小总一下才好!\n回顾工作的 6 个标准:\n目前的行动; 当前的工作(亟待处理的事项,短期成效); 责任范围(工作、生活、个人成长); 1~2 年的目标(各个领域的成就); 3~5 年的展望(着眼全局,纵观趋势); 整个生活的全景(终极的人生意义)。 :: 所有的评估和回顾,终极目标都是了解‘真我’!预期的有无和意义,便在于这个求‘真’的过程之中,它是自然而然的,是会自己冒出来,而你也注定要解决的问题。\n:: 这几个标准特别适合,周、月、季、年、大周期汇总,看,一切总结都是建立在实践之上的,空想大道是没有什么意义的。\n:: 其中第一类决策模式是发生在具体行动中的,后两者则都归属于检查反馈(即融于日常的生活习惯之中),可见规律性、周期性的“反馈”的重要性!反馈、检查 ✔️\n高效能人士的七个习惯系统地讲解这个体系:\n积极主动(无论什么时候,都要大力发挥主观能动性) 以终为始(聚集目标,不忘初心) 要事第一(!) 双赢思维(利人利己,方得长久,不违本心) 知彼解己(同理心,明确形式) 综合综效 不断更新(物质是运动的) ![[assets/pasted image 20230526092649.png|500]]\n:: 组织管理和行动这两步是很关键的,好的方案为行动的实施提供了便利,事半功倍,这两个环节和纵向管理又是密切相关的,不可分离的。\n纵向管理 :: 纵向管理是横向管理的组织管理、行动两部分的具体体现。\n需要更严格地控制某一项工作,确保行动方案切实可行时,便是纵向管理的用武之地。\n:: 就个人而言,横向管理使用的比较多,基本已经融入日常生活当中。纵向管理,就用的少了些,可能还是有时候太浮躁了,要静下心来,多思、多想,思而后行,行而有得。\n纵向管理也有五个阶段:目的/原则、前景/结果、集思广益、组织协调、下一步行动。\n:: 看!纵向管理,是正式的组织管理和行动,它是融入其中的!\n1. 目的/原则 目的是什么?是否清晰而具体?\n界定成功(要实现什么); 阐明重点(主要矛盾); 拓宽选择(可能); 集结资源(客观条件); 激发动机(主观能动); 澄清原则(本心)。 :: 我们处理一件事情,要明确自己的目的是什么,具体执行到什么程度算是达到了自己的目的。还要充分考虑处理过程中可能遇到的问题,造成这个问题出现的主要矛盾是什么。自己现在拥有的解决矛盾的资源有哪些,怎么样把握事情的走向按照预想的方向发展。集思广益,分清轻重缓急,投入执行,并在具体行动中“具体问题,具体分析”,实时调整优化自己的行动策略。\n:: 界定成功其实很重要,因为我们要做的事情,一般只需要一个阶段性的完成度,而不需要多么的彻底,因为它们的重要性也许并不高。分析待办事项的主要矛盾,抓住重点,发散思维。想想有什么、做什么,坚持原则。\n:: 三思而后行,行必有所得!\n:: 过多的解释和条目,往往让人很烦!总结来说,就是结合自身内外条件,想要阶段性的取得一种什么结果,其他一切都是围绕这个中心的!为什么要‘界定’呢?要预演!脑海中的预演可以更好、更早地发现行动过程中可能出现的问题,防患于未然,不仅大大地节省了可能要花费的并无意义用处的时间和精力,对行动的激励所产生的正面效应也是极大的。“之所以打醋,不就是为了这点饺子!”\n:: 凡事计划,很少有能一蹴而就的,多是分阶段性的,对于成功的界定就很重要,正所谓,心向高远,目视脚下,路途长而艰、寂而孤,就更需要阶段性的鼓舞与激励!由内而外,反求诸己,利用客观环境,发挥主观能动,突破经验本本,分析主次轻重,扩展思维,明晰本心,澄清并坚守原则。\n:: 这个过程是繁琐的,却是非常重要的!认真思考要达成的目标,分析客观条件、主观现实,实事求是地分析矛盾及事物可能的走向,明晰本心真我。\n2. 前景/结果 ![[assets/pasted image 20230526092719.png|200]]\n聚焦; 阐明结果。 :: 能吃到的果子,才能真正解渴!\n量子吸引力法则:\n![[assets/pasted image 20230526092739.png|300]]\n量子力学理论中重要的一点就是振动频率相同的东西,会互相吸引而且引起共鸣。我们的意念、思想、情绪具有可感知的能量,而我们的脑电波不断产生振动频率,只要有振动,就会影响其他同样在振动的事物。我们的大脑就是这个世界上最强的“磁铁”,我们的起心动念,无时不在向宇宙发出信号,和你的脑电波振动频率相同的东西,会统统被你吸引过来。你生活中的一切,都是你自己吸引来的。佛陀在 2500 多年前所说的“唯心所见,唯识所变”,便是这个哲理。\n:: 心怀美好,切实行动,万事顺遂。 ✔️\n:: 意识的更新。\n3. 集思广益 头脑风暴,启动思维,打破常规的思考模式,基本原则:\n不判断,不质疑,不评估,不批判; 追求数量,不求质量; 把分析组织工作置于次要的位置,放在下个阶段处理。 :: 在头脑中搜集所有的可能性,不放过任何蛛丝马迹。不唯经验、教条,实事求是!突然更深地认识到,以上种种,在《毛泽东选集》中都有讲到…… 教员威武!\n4. 组织协调 组织管理的要素:\n明确意义重大的事件; 排序(构成因素、先后顺序、重要程度); 必要程度的详述。 :: 明主次,知先后。\n5. 下一步行动 如果一项工作具有可操作性,其下一步行动方案就必须予以落实,必须有切实可行的具体行动。\n:: 实践,是实现一切计划的根本途径!\n:: 综上来看,纵向管理就是界定成功(明晰目标)、预演前景结果、想尽可能多的方案,从中取优协调,进行形成一个科学合理的、可明确执行的、有意义的行动方案,以保证具体执行过程顺利,达到预期目标!\n*感想 \u0026gt; 2024-11-01 11:27\n多数时候,“无所事事”不是因为真的没有事情可以做,而是你不知道该怎么做。这种感觉很不好,所以人们故意装作若无其事,自欺欺人,试图逃避这种不好的感觉,然而徒留空自耗,问题永远不会因为你假装看不到就自个消失掉,往往还会因为你的“装聋作哑”变得更为亟待解决。\n更好的生活,需要一种适合自己的管理方式,无论它的繁重的还是轻松的,都应该是快乐的。生活中的极简不是为了单独的“少”,而是要有条不紊,有理有序。\n横向管理是宏观地对整个生活有一个把握,纵向管理则是几乎贯穿于横向管理的整个过程之中。\n横向管理,主要是为了解决生活中的“乱”,让人心中有个数。收集,百分之百的收集,可以让人从无知道还有多少“麻烦”这种思绪和惶恐中解脱出来。分析、组织管理则是为了更好地理清事务,找出关键,制定出具体方案,周期性的检查相当重要,源于原始方案不一定是最佳的,后续要根据事情的进展作调整。在行动之前,从组织管理到具体行动,中间便需要纵向管理做具体的实现。\n纵向管理,可以说是一种方法论,是一种行动指导。在分析和组织管理的时候,它在潜意识中已经悄悄地开始进行了。针对具体的行动,事前,我们肯定是需要想好是目的是什么,只有弄清了这一点,才能做到有的放矢。当然,我们做事情不能不择手段,这就需要有自己的原则,就对于关键时刻的得失取舍非常的有帮助。在确定了目的和原则之后,对于事情的前景和结果要有所估计,这其中就涉及到了对于成功的界定,现实中的事情少有能一蹴而就的,定义好阶段性的胜利,非常有利于情绪上的正向反馈。\n制定具体方案的时候,不能限制自大的思维,像易经中的卦象所示,事物都是错综复杂的,要从多个角度,多个方面去分析观察才可以。这个阶段太过于局限很容易陷入经验主义的陷阱,经验是好的,但不能被不合适宜的经验遮蔽了眼睛,集思广益,重中之重。\n综合全面的考虑之后,对于时间、地点、可用精力、可支配资源都有一个大概估计之后,就是分配好这些客观资源,发挥主观能动性的时刻了。到这个阶段,我们已经有了一个具体可执行的行动方案,认真执行即可。这里强调一下,行动方案不能是抽象的,必须是具体的、切实可行的。还记得吗,你无聊不是因为无事可做,而是因为有一堆事情,你却不知道该如何去做。\n实践是检验真理的唯一标准,也只有通过实践,才能更好地认识世界、改造世界,让自己拥有一个快乐的人生。\n","date":"2021-03-26","permalink":"https://aituyaa.com/gtd-%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/","summary":"\u003cblockquote\u003e\n\u003cp\u003e:: \u003cstrong\u003e怎能让利剑空悬于高阁之上!?\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e","title":"gtd 管理系统"},]
[{"content":"🌞 我的 \u0026gt; gtd emojing ship wiki gitee github leetcode 小红书 bilibili 抖音 西瓜 twitter youtube telegram agentneo emacs china \u0026gt; 今日热榜 outlook 网易邮箱 gmail 简书 知乎 贴吧 微博 华为云 腾讯云 七牛云 cloudflare vercel onedrive 坚果云 yse 百度网盘 kimi.ai 豆包 ai 文心一言 chatgpt deepseek 元宝 ai 起点中文 阅文作家 微信公众平台 🔨 工具 \u0026gt; 有道翻译 词根词缀词典 internet archive spring initializr shields jsdelivr bootnav animatecss regexp jetbra.in melpa vimawesome 华为源 腾讯源 清华源 versus cpu天梯榜 贝贝 bilibili 贷款计算器 asus 主板驱动 摹客 flexclip 果核剥壳 crx4chrome chocolatey pspr \u0026gt; 图压缩 pixabay 调色板 颜色工具 drawio 渐变生成器 致美化 twcolor css 颜色 rime西米 一键抠图 转 ico 帮小忙 证件数码相片质量检测中心 emoji 词典 字体之家 猫啃字体 iconfont typing svg github activity lucideicons hms 图标库 icons8 jsonplaceholder hwid activation clashforandroid 网络云备份 📑 文档 菜鸟教程 c 语言中文网 书栈网 elisp 简明教程 ts 翻译文档 javascript 教程 axiosdoc typescript 教程 typescript 中文 assemblyscript next.js docs \u0026gt; mdn docs \u0026gt; dev roadmaps svelte doc react doc vue doc electron nodejs doc remix 框架 美团技术团队 ant design teachu\u0026rsquo;cs maven repo interview cake python doc pypi pyqt5 spring.io java se doc java se 文档 api ref on java8 linuxcommand uml toml yaml 墨奇拆分 自然码码表 顶功 · 集萃 🍺 博客 当然我在扯淡 阮一峰 李杀网 酷壳 \u0026gt; 廖雪峰 程序猿 mrbird 懒猫 houge \u0026gt; 小林coding lxgw xaoxuu 月盾的博客 知了 小贼贼子 verne 涛叔 ☕️ 休闲 古诗文网 词根词缀 动画创作家园 omofun动漫 omofun发布 美剧天堂 低端影视 片吧 电影天堂 迅雷电影 mp4 bt天堂 樱花动漫 最爱片源 高清族 直播吧 \u0026gt; 虎扑 咪咕体育 \u0026gt; 海星体育 精品电子书 中哲电子书 国学梦 5000言 天书阁 无忧书城 \u0026gt; 马克思主义文库 \u0026gt; 学习强国 散文网 mcwiki curseforge planetpc mc百科 🧲 友链 whatacold matrixcore lijigang geekinney 🔖 标签 天行健,君子以自强不息 rime万象拼音输入方案增强版 速录宝-完美平台-空明码 - 键盘并击 - 速录- q群:374971723 syncthing 又一款同步工具 | verne in github hook,react+antd、表格table +分页器pagination 使用注意事项_react antd table pagination-csdn博客 聊一聊 2024 年 react 生态系统-腾讯云开发者社区-腾讯云 javascript - 如何解决 react.useeffect() 的无限循环 - 终身学习者 - segmentfault 思否 完美解决 - 前端发版后浏览器缓存问题(发版后及时拉取最新版本代码)代码更新浏览器有缓存-csdn博客 [ant motion - ant design 的动效规范与组件]( https://motion.ant.design/index-cn 彻底搞定react路由跳转动画的优化方案本文探究的方案是基于一次react-router + react-transit - 掘金 👾 笔记 | react-transition-group 实现路由切换过渡动画 - inslog - 博客园 如何在react + typescript 声明带有children的props - 掘金 javascript - 在react中使用 react-router-dom 编程式路由导航的正确姿势【含v5.x、v6.x】! - 个人文章 - segmentfault 思否 reactrouter——路由配置、路由跳转、带参跳转、新route配置项_react 路由跳转 remix 究竟比 next.js 强在哪儿?大前端_infoq精选文章 react 和 reactdom 的区别 - 奔跑的瓜牛 - 博客园 4种方法实现html 页面内锚点定位及跳转 - 掘金 我的输入法捣鼓指南 - 少数派 google chrome 漫画图表 学习 kafka 入门知识看这一篇就够了!(万字长文)-腾讯云开发者社区-腾讯云 成长地图_对象存储服务 obs_华为云 面试官:如何使用javascript实现复制粘贴功能? - 掘金 vue i18n 国际化保姆级教程_看不懂自己找原因 - 掘金 google chrome books 可能是全网最全的哈工大编译原理资料分享 - 知乎 scheme · gitbook 从按键到响应,键盘的底层原理是什么? - 知乎 汇编语言入门教程 - 阮一峰的网络日志 - www.ruanyifeng.com (11条消息) 一口气看完 45 个寄存器,cpu核心技术大揭秘_「已注销」的博客-csdn博客 - blog.csdn.net 内存条的组成、编址、寻址和读写方式 - 活着的虫子 - 博客园 - www.cnblogs.com (11条消息) 【操作系统】cpu寄存器详解_公子无缘的博客-csdn博客 - blog.csdn.net 基金与股票有什么区别?新手适合买哪个? - 知乎 - zhuanlan.zhihu.com 【兼容性】h5 滚动穿透解决方案 - 腾讯云开发者社区-腾讯云 - cloud.tencent.com service worker - 《阮一峰 web api 教程》 - 书栈网 · bookstack - www.bookstack.cn html5 触摸事件 (touchstart、touchmove 和 touchend) 触摸事件 touchstart、touchmove、touchend_蚩尤后裔的博客-csdn 博客_touchstart - blog.csdn.net 入门 - chrome 开发者 - developer.chrome.com (8 条消息) 如何开发一个浏览器插件_枫叶-哈哈的博客-csdn 博客_浏览器插件开发 - blog.csdn.net 护木 123 https://baike.120ask.com/art/242926 深入理解计算机系统 https://media4.open.com.cn/l603/fushi/0903/jisuanjzcyl/web/lesson/char1/j1.htm markdown 解析原理详解 https://ld246.com/article/1587637426085 java8 探讨与分析匿名内部类、lambda 表达式、方法引用的底层实现 迭代器 https://wohugb.gitbooks.io/ecmascript-6/content/docs/iterator.html 网络模型 https://blog.csdn.net/qq_25800311/article/details/87024665 chrome v8 https://zhuanlan.zhihu.com/p/266708344 vdom https://www.zhihu.com/question/29504639 ","date":"2021-03-16","permalink":"https://aituyaa.com/%E5%AF%BC%E8%88%AA/","summary":"","title":"导航"},]
[{"content":" :: 多少事,从来急;天地转,光阴迫。一万年太久,只争朝夕。 \u0026ndash; 教员\nwiki 图片视频格式 *.jpg\r*.jpeg\r*.png\r*.gif\r*.bmp\r*.tiff\r*.tif\r*.webp\r*.svg\r*.raw\r*.cr2\r*.nef\r*.arw\r*.psd\r*.ico\r*.mp4\r*.avi\r*.mkv\r*.mov\r*.wmv\r*.flv\r*.webm\r*.mpeg\r*.mpg\r*.3gp\r*.vob\r*.ts\r*.m2ts\r*.m4v\r*.ogv 如何给虚拟机 ip 设置自定义域名映射 可以在本机上修改 hosts 文件,将自定义的域名指向到虚拟机的 ip 地址。\nwindows 上修改 hosts 文件,用编辑器打开 %systemroot%\\system32\\drivers\\etc\\hosts 文件,输入自定义域名和虚拟机 ip 地址,如下:\n192.168.127.130 aituyaa.lo 保存文件,刷新 dns 缓存(ipconfig /flushdns)即可。\n在 macos 或 linux 上修改 host 文件\n1 2 3 4 sudo nano /etc/hosts # 追加后,保存 192.168.127.130 aituyaa.lo 保存文件,刷新 dns 缓存(方法有点多,不多赘述)。\nnavicat premium 15 永久破解激活工具及安装教程 ❗❗❗安装和激活全程断网!!!\n➡️ navicat premium 15 永久破解激活工具及安装教程(亲测可用) - marchxd - 博客园\n如何设置 antdesign 组件中的文字为中文 在你的 react 应用的入口文件(通常是 index.js 或 app.js)中,引入中文语言包 zh_cn。然后使用 configprovider 组件包裹你的应用,并将 zh_cn 作为 locale 属性的值传递给它。\n1 2 3 4 5 6 7 8 9 import zh_cn from \u0026#39;antd/es/locale/zh_cn\u0026#39;; // ... reactdom.render( \u0026lt;configprovider locale={zh_cn}\u0026gt; \u0026lt;app /\u0026gt; \u0026lt;/configprovider\u0026gt;, document.getelementbyid(\u0026#39;root\u0026#39;) ); 如果你只想对特定的组件应用中文,而不是整个应用,那么只需在这些组件外部包裹 configprovider 即可。\n关闭、禁用或卸载 onedrive ➡️ 关闭、禁用或卸载 onedrive - microsoft 支持\n一些护眼色 绿豆沙 #c7edcc rgb(199, 237, 204) 银河白 #ffffff rgb(255, 255, 255)\r杏仁黄 #faf9de rgb(250, 249, 222)\r秋叶褐 #fff2e2 rgb(255, 242, 226)\r胭脂红 #fde6e0 rgb(253, 230, 224)\r海天蓝 #dce2f1 rgb(220, 226, 241) 葛巾紫 #e9ebfe rgb(233, 235, 254)\r极光灰 #eaeaef rgb(234, 234, 239)\r青草绿 #e3edcd rgb(227, 237, 205) 电脑管家 #cce8cf rgb(204, 232, 207)\rwps #6e7b6c rgb(110, 123, 108) ![[assets/pasted image 20241213153118.png|400]]\nobs 录制的视频声音越来越大 我一开始也以为是麦克风之类的问题,还换了个好一点的麦克风结果还有这个情况。费尽心机才发现是播放器的锅,没错,就是那个 potplayer 播放器!\n这个播放器自带一个 音量规格化设置,默认是开启的,所以声音都会变得特别大,实际上 obs 录制的视频音量还是没变化的,把这个选项勾掉即可。\n![[assets/pasted image 20241213151943.png|420]]\nelementui 实现表格 selection 的默认勾选 ➡️ vue elementui 实现表格selection的默认勾选\n安装 docker 并配置镜像加速源 ➡️ https://cloud.tencent.com/document/product/1207/45596\nencodeuri 和 encodeuricomponent 的区别 特性 encodeuri encodeuricomponent 作用范围 整个 url url 的组成部分(如查询参数) 编码的字符 保留字符(如 /, :, ;, ?, \u0026amp;, =)不编码 保留字符也会编码 适用场景 编码完整的 url 编码 url 的查询参数或路径片段 避免 input 组件聚焦后按 enter 键导致页面刷新的问题 问:在 vue 项目开发时,运用 element-ui 或者 iview 组件的 form 组件;当 form 组件中只有一个 input 组件时,鼠标聚焦输入框后,点击回车键,页面就会刷新。\n答:在组件form上添加 @submit.native.prevent 。\n如何保持 ssh 连接 最近在配置服务器相关内容时候,不同的事情导致长时间不操作,页面就断开了连接,不能操作,只能关闭窗口,最后通过以下命令解决。ssh 连接 linux 时,长时间不操作就断开的解决方案:\n1、修改/etc/ssh/sshd_config 文件,如果找到 clientaliveinterval 0 和 clientalivecountmax 3 并将注释符号(\u0026quot;#\u0026quot;)去掉。\n将 clientaliveinterval 对应的 0 改成为一个数值比如 60,数值是秒 clientaliveinterval 指定了服务器端向客户端请求消息 的时间间隔, 默认是 0, 不发送. clientaliveinterval 60 表示每分钟发送一次, 然后客户端响应, 这样就保持长连接了. clientalivecountmax, 使用默认值 3 即可。clientalivecountmax 表示服务器发出请求后客户端没有响应的次数达到一定值, 就自动断开,正常情况下, 客户端不会不响应. 2、查找后发现是没有 clientaliveinterval 和 clientalivecountmax 等属性 ,则在文件后添加,属性内容值参照步骤 1。\nclientaliveinterval 60 clientalivecountmax 3 3、重启 sshd 服务:\nservice sshd restart 修改配置之后要让配置生效,一定要重启 sshd 服务。\n如何修改 powershell 脚本运行策略 1. 查看当前执行策略\n在 powershell 中,可以使用 get - executionpolicy 命令来查看当前的执行策略。执行策略决定了 powershell 加载哪些配置文件和运行哪些脚本。它可能返回以下几种策略:\nrestricted(默认):不允许任何脚本运行,这是最安全的设置。 allsigned:只允许运行经过数字签名的脚本。 remotesigned:允许运行本地编写的脚本,但对于从网络(如 internet)下载的脚本,要求有数字签名。 unrestricted:允许运行所有脚本,但在运行从网络下载的脚本时会有安全提示。 2. 修改执行策略\n以设置为 remotesigned 为例,如下:\n以管理员身份运行 powershell:因为修改执行策略需要管理员权限,所以首先要以管理员身份打开 powershell。在开始菜单中找到 “windows powershell”,右键点击并选择 “以管理员身份运行”。 修改策略:在管理员权限的 powershell 窗口中,输入 set - executionpolicy remotesigned 命令。 确认操作:执行这个命令后,powershell 会提示你进行确认,输入 y(代表 yes)来确认修改执行策略。 windows10/11 任务栏固定快捷方式显示失效问题 运行 win + r ,打开运行 %localappdata% ,显示隐藏文件。\n![[assets/pasted image 20240526171313.png|400]]\n删除 iconcache.db 文件后,打开任务管理器,在进程中找到 “windows 资源管理器”,右键选择 “重新启动” 即可。\n![[assets/pasted image 20240526171503.png|400]]\n微信小程序无法调用 wx.chooseimage 又是腾讯的骚操作,你倒是多少给个提示啊,弹窗啊之类的…… 又不报错,又没响应,尼玛……\n微信小程序无法调用wx.chooseimage | 微信开放社区\n令人费解的 hugo 日期格式化操作 hugo模板中的日期格式 - 龙渊阁 hugo / go 模版中的日期格式 hugo | 以正确姿势自动添加文章最后更新时间 | 小球飞鱼 解决 hugo 默认时区造成文章/页面不渲染的问题 :: 不得不说,hugo 的日期格式化是我见过的最操蛋、最垃圾的!😠\n如何设置页面所有 a 标签在新窗口打开 \u0026lt;a href=\u0026#34;xxx\u0026#34;\u0026gt;\r\u0026lt;!-- 设置 target 为 _blank,默认为 _self --\u0026gt;\r\u0026lt;a href=\u0026#34;xxx\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt; 但是许多时候,我们使用第三方静态博客生成 a 标签…… 当然,你可以通过 js 来遍历页面的所有 a 标签,然后动态的为它们添加上述属性,但想想也是挺耗性能的。\n其实,只需要在 \u0026lt;head\u0026gt; 标签中加入如下代码就可以了:\n\u0026lt;base target=\u0026#34;_blank\u0026#34;\u0026gt; :: 当然,这样做也不是说一点问题没有,比如,你在页面内点击相应目录跳转的时候,如果不做进一步处理,那就不是在当前页面滚动到对应位置了,而是在新的页面窗口中了……\nvim 下载插件时出现错误 curl: (6) could not resolve host: raw. githubusercontent. com 在使用 curl 下载文件时,关于出现上面这个报错,是因为 github 的 raw.githubusercontent.com 域名解析被污染了。\n注:curl 命令是一个利用 url 规则在命令行下工作的文件传输工具。它支持文件的上传和下载,所以是综合传输工具,但按传统,习惯称 curl 为下载工具。作为一款强力工具,curl 支持包括 http、https、ftp 等众多协议,还支持 post、cookies、认证、从指定偏移处下载部分文件、用户代理字符串、限速、文件大小、进度条等特征。\n怎么办?查询真实 ip, 并修改本机 hosts ,它在哪儿?\n# 类 *unix 系统中一般位于\r/etc/hosts\r# windows 中位于\rc:\\windows\\system32\\drivers\\etc\\hosts 修改后,刷新 dns (windows 下 cmd 中运行 ipconfig /flushdns)即可。\n关于复制 a 目录下的所有文件到 b 目录下 # 1 - 如果 b 目录不存在,可以直接使用:\rcp -r a b\r# 2 - 如果 b 目录已经存在,则需要使用,否则,\r# 会将 a 目录直接复制到 b 目录中,如 b/a 。\rcp -r a/. b a4 纸尺寸 210 x 297 mm 595 x 842 px 图片 background css backgrounds 中描述了 background 相关的一些属性, 如下:\nproperty 描述 background 简写属性,作用是将背景属性设置在一个声明中。 background-attachment 背景图像是否固定或者随着页面的其余部分滚动。 background-color 设置元素的背景颜色。 background-image 把图像设置为背景。 background-position 设置背景图像的起始位置。 background-repeat 设置背景图像是否及如何重复。 \u0026gt; css 背景属性\n那么背景图如何适应容器呢?就需要设置 background-size 了,如下:\n#demo{\rbackground-img:url(../bg.png);\r--(1)适用于上半部分背景图片,下半部分纯色\r将背景图像等比缩放到完全覆盖屏幕,背景图有可能超出屏幕(容器)\rbackground-size:cover;\r--(2)将背景图像等比缩放到宽度或高度与容器的宽度或高度相等,背景图像始终被包含在容器内。\rbackground-size:contain;\r--(3)最简单粗暴的方法,缺点会给背景拉伸变形;\rbackground-size:100% 100%;\r} npm 源 # 查看当前源\rnpm config get registry\r# 设置默认源\rnpm config set registry https://registry.npmjs.org 你也可以设置为国内的镜像源,如淘宝源(目前最佳),中间一段时间使用了华为源,老出问题。\n# 新的淘宝源\rnpm config set registry https://registry.npmmirror.com\r# 旧的淘宝源(已于 2022.05.31 起停止服务)\rnpm config set registry https://registry.npm.taobao.org 相关课程资料 📌 https://blog.csdn.net/sirobot/article/details/122917866\n北京大学课程资料整理:\nhttps://github.com/lib-pku/libpku\n浙江大学课程攻略共享计划:\nhttps://github.com/qsctech/zju-icicles\n清华大学计算机系课程攻略:\nhttps://github.com/pkuanonym/rekcarc-tsc-uht\n中国科学技术大学课程资源:\nhttps://github.com/ustc-resource/ustc-course\n上海交通大学课程分享:\nhttps://github.com/coolphilchen/sjtu-courses/\nwin10 如何使用输入法快速插入当前时间 参考 windows10 如何使用微软输入法快速输入时间(yyyy-mm-dd hh:mm:ss格式)\n右键微软输入法托盘图标,选择 用户自定义短语 选项,如下:\n![[assets/pasted image 20230525161521.png]]\n添加用户自定义的短语:\n![[assets/pasted image 20230525161534.png]] 短语处,完整内容如下:\n%yyyy%-%mm%-%dd% %hh%:%mm%:%ss% 添加后,切换一下输入法,输入 sj ,默认输入法首选项就是当前时间了。\n我的主机上设置的快捷键是 iid ,短语内容为 %yyyy%-%mm%-%dd% %hh%:%mm% ,如下:\n![[assets/pasted image 20230525161550.png]]\n总之就是挺爽的吧!当然,你也可以设置其他自定义输入,而且可以导出完整的自定义选项,更换主机后,也很容易启用。\n:: 事实证明,这种动态生成时间的方式,在你更换主机后,只能再次手动地去创建。\n其他输入法应该也有类似的定义办法,很不错。\ndns 设置 公司 dns 备注 安印 192.168.51.168 中国互联网络信息中心 1.2.4.8 210.2.4.8 百度 180.76.76.76 阿里 223.5.5.5 223.6.6.6 腾讯 119.29.29.29 win10 设置右下角显示秒钟 打开‘控制面板’,在地址栏输入 控制面板、时钟和区域 ,如下:\n![[assets/pasted image 20230525161756.png|650]]\n其他设置后时间选项卡,如下:\n![[assets/pasted image 20230525161829.png]]\n打开‘运行’(win-r),输入 regedit ,打开‘注册表编辑器’后,在地址栏输入 计算机、hkey_current_user\\software\\microsoft\\windows\\currentversion\\explorer\\advanced 后,右键 advanced ,新建一个‘dword (32 位)’值,命名为 showsecondsinsystemclock ,并赋值为 1 ,然后重启或注销电脑即可。\n![[assets/pasted image 20230525161902.png]]\n关于 iframe 好吧,老掉牙的技术了,当前许多浏览器已经限制了该功能,了解可能参考:\nhttps://www.cnblogs.com/bester-ace/articles/9292779.html https://www.cnblogs.com/hq233/p/9849939.html 奶头乐理论 刺激他们的欲望,降低他们的工资,借钱给他们花,让他们忙的停不下来,同时开放大量的娱乐项目,使他们又不至于崩溃。\n当娱乐大量占用人们的时间,让人们丧失思考的能力,这一社会麻醉剂将会带来“马太效应”,沉迷的人继续沉迷,清醒的人保持清醒,人与人的差距,甚至阶层间的差距也就拉大了。\n从而消磨他们的斗志,抹平阶级跃迁的愿望。\nv30 pro 您可以在华为官网查询您设备的相关备件价格。点击后方的链接选择您设备型号和颜色即可,点击链接 https://consumer.huawei.com/cn/support/sparepart-price/\n亲亲,为您提供一个便捷的查询服务店方法,点击下方官网链接您可直接查询服务店具体信息,您可根据自己的实际情况选择距离您比较近的服务店前往哦~查询链接(建议收藏哦~): https://consumer.huawei.com/cn/support/service-center/\n华为授权服务中心(濮阳县红旗路)\r4.96 分 5.6km\r屏幕外层玻璃维修\r电脑维修\r服务日\r河南省-濮阳市-濮阳县红旗路与建新路交叉口向东 100 米路南\r0393-3339678\r营业时间:9:00-18:00(周一周三至周日)\r特别提醒: 门店每周二休息,营业时间为每周一、周三-周日 09:00-18:00,此期间可通过华为服务 app 选择就近授权服务门店或官方寄修服务,如需帮助请拨打电话 16639328222;针对屏幕外层玻璃维修,本店支持直面手机屏幕外层玻璃维修;针对笔记本业务本店仅提供检测、软件问题处理服务,暂不提供硬件维修服务,给您带来不便敬请谅解 element-ui 单元格点击事件,行点击+单元格点击,获取某一行的 index 1 2 3 4 \u0026lt;el-table :data=\u0026#34;tabledata\u0026#34; stripe @cell-click=\u0026#34;addsubaccount\u0026#34; :row-class-name=\u0026#34;tablerowclassname\u0026#34;\u0026gt; \u0026lt;el-table-column prop=\u0026#34;installer\u0026#34; label=\u0026#34;主子账号\u0026#34;\u0026gt; \u0026lt;/el-table-column\u0026gt; \u0026lt;/el-table\u0026gt; 相应逻辑:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //下面是利用给表格添加 classname, 添加 index tablerowclassname ({row, rowindex}) { //把每一行的索引放进 row // console.log(row,rowindex) row.index = rowindex; //拿到的索引赋值给 row 的 index, 在这个表格中能拿到 row 的里面都会包含 index return \u0026#39;row-remarks\u0026#39; //classname(类名) }, addsubaccount(row){ //获取焦点弹出关联多个子账号 console.log(row.index) }, //如果需要区分那一数列的才能触发需要判断下 prop 的值 addsubaccount(row,column){ //获取焦点弹出关联多个子账号 console.log(row.index) //获取下标 console.log(column.property ) //获取判断条件 if(column.property == \u0026#39;prop 的值\u0026#39;){ //prop 的值是自己设置的,注意别重复设置同一个值 } }, //如果表格既有行点击,又有单元格点击,在行点击事件里判断 prop, 等不等于你行点击的 prop, 如果等于直接 return false vue 文件的命名规范 → 详见 https://juejin.cn/post/6844903840626507784#heading-9\n#+begin_quote 其实刚开始我写 vue 文件的时候也不注意,各种驼峰啊、大写开头 (pascalcase) 还是横线连接 (kebab-case) 混着来,谁叫 vue 都可以,在风格指南中也没有定论。不过基于本项目我还是整理了一套文件的命名规则。 #+end_quote\ncomponents 所有的 component 文件都是以大写开关(pascalcase),这也是官方所推荐的,但除了 index.vue ,如:\n@/src/components/backtotop/index.vue @/src/components/charts/line. vue @/src/views/example/components/button. vue js 文件 所有的 . js 文件都遵循横线连接 (kebab-case),如:\n@/src/utils/open-window. js @/src/views/svg-icons/require-icons. js @/src/components/markdowneditor/default-options. js views 在 views 文件下,代表路由的 . vue 文件都使用横线连接 (kebab-case),代表路由的文件夹也是使用同样的规则,如:\n@/src/views/svg-icons/index. vue @/src/views/svg-icons/require-icons. js 使用横线连接 (kebab-case) 来命名 views 主要是出于以下几个考虑。\n横线连接 (kebab-case) 也是官方推荐的命名规范之一; views 下的。vue 文件代表的是一个路由,所以它需要和 component 进行区分 (component 都是大写开头); 页面的 url 也都是横线连接的,比如 https://www.xxx.admin/export-excel ,所以路由对应的 view 应该要保持统一; 没有大小写敏感问题。 ide active → jetbrains 系列产品重置试用方法\nhttps://plugins.zhile.io\nubuntu 下清空 dns 缓存 为什么要清空 dns 缓存呢?\n大多数的 dns 客户端会把域名解析的结果缓存到本地,这样可以提升对于同一个地址的访问速度。当您打开一个单页面的时候,通常会有多次对同一个域名的访问请求。基本上每个文件、图片、样式表……这些都是在同一个页面内部的对同一个域名的 dns 解析请求。\n所以如果您已经在本地缓存了不正确的 dns 条目,那么您需要清空您的缓存来使 dns 客户端提出新的 dns 请求并更新解析结果。当然,您也可以等缓存的 dns 条目过期以后让系统自动冲掉该条目……这通常需要 24 个小时。\n在 ubuntu 中冲掉 dns 缓存的方式是重新启动 nscd 守护程序\n1 2 3 4 5 # 安装 nscd (如果没有) sudo aptitude install nscd # 清除 dns sudo /etc/init. d/nscd restart vue/cli 3 切换为 dart-sass 首先,安装包 :\n1 2 3 npm i sass sass-loader -d # or yarn add sass sass-loader -de 然后,修改 vue. config. js ,增加如下配置:\n1 2 3 4 5 6 7 8 9 module. exports = { css: { loaderoptions: { sass: { implementation: require (\u0026#39;sass\u0026#39;), // this line must in sass option }, } //... }; glob glob 最早是出现在 unix 系统的命令中,是用来匹配文件路径的。除了在命令行中,我们在程序中也会有匹配文件路径的需求。于是,很多编程语言有了对 glob 的实现,如 python 中的 glob 模块,php 中的 glob 方法。\n下面是 node-glob 的匹配规则:\nglob desc * 匹配任意 0 或多个字符 ? 匹配任意一个字符 ———————————- —————————- [...] 匹配中括号中的字符 ![...] 匹配不在中括号中的字符 ^[...] 匹配不在中括号中的字符 ———————————- —————————- ! (pattern i pattern i pattern) 不满足括号的所有模式 ? (pattern i pattern i pattern) 满足 0 或 1 个括号中的模式 +(pattern i pattern i pattern) 满足 1 或多个括号中的模式 @(pattern i pat* i pat? ern) 满足 1 个括号中的模式 ———————————- —————————- ** 跨路径匹配任意字符 来看几个最简单常用的例子吧。\n它将匹配类似 scripts/index. js、scripts/nested/index. js 和 scripts/nested/twice/index. js 的文件。\n\u0026#39;scripts/**/*. js\u0026#39; 取反。\n\u0026#39;script/**/*. js\u0026#39;, \u0026#39;! scripts/vendor/\u0026#39;, \u0026#39;scripts/vendor/react. js\u0026#39;\r\u0026#39;**/*. js\u0026#39;, \u0026#39;! node_modules/\u0026#39; 设计模式准则 我以前给一些公司讲一些设计模式的培训课,我一再提到,那 23 个经典的设计模式和 oo 半毛钱关系没有,只不过人家用 oo 来实现罢了。\n设计模式就三个准则:\n中意于组合而不是继承, 依赖于接口而不是实现, 高内聚,低耦合。 你看,这完全就是 unix 的设计准则。\n文件的描述符和重定向 文件描述符 是和文件的输入、输出相关联的非负整数,linux 内核(kernel)利用文件描述符来访问文件。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。\n系统预留文件描述符:\n0 - stdin 标准输入; 1 - stdout 标准输出; 2 - stderr 标准错误。 实例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 输出重定向 # 1. 截取模式保存到文件 - 写入到文件之前,文件内容首先会被清空 echo \u0026#34;this is a text line one\u0026#34; \u0026gt; test. txt # 2. 追加模式保存到文件 - 写入到文件之后,会追加到文件结尾 echo \u0026#34;this is a text line one\u0026#34; \u0026gt;\u0026gt; test. txt # 标准错误输出的重定向方法 cat linuxde. net # → cat: linuxde. net: no such file or directory # 1. 没有任何错误提示,正常运行 cat linuxde. net 2\u0026gt; out. txt # 2. 错误信息被保存到了 out. txt 文件中 cat linuxde. net \u0026amp;\u0026gt; out. txt # 3. 将错误输出丢弃到 /dev/null 中,特殊的设备文件 - 黑洞 cat linuxde. net 2\u0026gt; /dev/null # 输入重定向 echo \u0026lt; test. txt 数据归档和解压缩 首先要弄清两个概念:打包和压缩。 打包 是指将一大堆文件或目录变成一个总的文件; 压缩 则是将一个大的文件通过一些压缩算法变成一个小文件。\n1. tar 命令\n利用 tar 命令,可以把一大堆的文件和目录全部打包成一个文件,这对于备份文件或将几个文件组合成为一个文件以便于网络传输是非常有用的。\n# 语法\rtar (选项) (参数) 实例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 打包、压缩 tar -cvf log. tar log2012. log # 仅打包,不压缩! tar -zcvf log. tar. gz log2012. log # 打包后,以 gzip 压缩 tar -jcvf log. tar. bz2 log2012. log # 打包后,以 bzip2 压缩 # 查询 tar -tvf log. tar # 直接查询 tar -ztvf log. tar. gz # 查询以 gzip 压缩的文件 tar -jtvf log. tar. bz2 # 查询以 bzip2 压缩的文件 # 解压缩 tar -zxvf log. tar. gz # 以 gzip 解压缩 tar -jxvf log. tar. bz2 # 以 bzip2 解压缩 tar -zxvf log. tar. gz -c log # 以 gzip 解压缩在目录 log 其中:\n选项 说明 -v 显示操作过程 -f \u0026lt;file\u0026gt; --file=\u0026lt;fiel\u0026gt; 指定备份文件 ————————— ————————– -c --create 建立新的备份文件 -t --list 列出备份文件的内容 -x --extract --get 从备份文件中还原文件 ————————— ————————– -z --gzip --ungzip 通过 gzip 指令处理备份文件 -j 支持 bzip2 解压文件 -c \u0026lt;dir\u0026gt; 在特定目录解压缩 小结:\n压缩:tar -jcv -f filename. tar. bz2 要被压缩的文件或目录名称\r查询:tar -jtv -f filename. tar. bz2\r解压缩:tar -jxv -f filename. tar. bz2 -c 欲解压缩的目录 关于 css 中设置 height 为 100% 不起作用 w3c 的规范,百分比的高度在设定时需要根据这个元素的父元素的高度。\nweb 浏览器有计算有效宽度时会考虑浏览器窗口的打开宽度,缼省为页面整个横向宽度。\n事实上,浏览器根本就不计算内容的高度,除非内容超出了视窗范围(导致滚动条出现),缺省为 height: auto 。或者你给整个页面设置一个绝对高度,否则浏览器就会简单的让内容往下堆砌,页面的高度根本就无需考虑。\n!!!个人实践,在给 html、body 设置 background 相关属性的时候情况会很奇特,给其内的元素设置背景的时候就不存在这些奇葩问题。\n所以,当我们想要设置竖直高度的百分比,需要对 html、body 进行一些初始化设置,如:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 html { /* 设置根元素高度 */ height: 100%; } body { /* 设置 body 高度 */ height: 100%; } /* body 内元素 .container */ .container { height: 60%; background: #f66 ; } 其实试一下,你就会发现,如果直接对 body 设置 background 的背景图片或是背景色,都会占满整个容口。\njavascript 对 url 的编码和解码 有时候,你会发现一些 url 链接是编码过的,如这样: http%3a%2f%2fw3cschool. cn%2fmy%20test. asp%3fname%3dst%c3%a5le%26car%3dsaab 。\njavascript 中使用 encodeuricomponent () 方法可以对 uri 进行编码;使用 decodeuricomponent () 方法可以对 uri 进行解码。\nw3c 提供了简单的实现,如下:\n1 2 3 4 5 var uri=\u0026#34; http://w3cschool.cn/my test. php? name=ståle\u0026amp;car=saab\u0026#34;; var uri_encode=encodeuricomponent (uri); document.write (uri_encode); document.write (\u0026#34;\u0026lt;br\u0026gt;\u0026#34;); document.write (decodeuricomponent (uri_encode)); ↓↓↓\nhttp%3a%2f%2fw3cschool. cc%2fmy%20test. php%3fname%3dst%c3%a5le%26car%3dsaab\rhttp://w3cschools.com/my test. asp? name=ståle\u0026amp;car=saab css 换行 → 参考链接\n文本换行有很多方式:\n\u0026lt;br/\u0026gt; 标签元素,能够强制使得所在位置文本换行; \u0026lt;p\u0026gt; 元素, \u0026lt;div\u0026gt; 设定宽度,都可以对文本内容实现自适应换行; 对于长单词或链接,默认不会断开换行,方式 2 就不能够在这些文本内部进行换行,此时需要 word-wrap: break-word; 或 word-break: break-all; 实现强制断行。 强制不换行 1 2 3 4 5 6 7 8 9 div { white-space: nowrap; } /* white-space: - normal 默认 - pre 换行和其他空白字符都将受到保护 - nowrap 强制在同一行内显示所有文本,直到文本结束或者遭遇 \u0026lt;br\u0026gt; 对象 ,*/ 控制文本换行 1 2 3 4 5 6 7 8 9 10 11 div { word-break: normal; word-break: break-all; word-break: keep-all; } /* word-break: - normal 依据亚洲语言与非亚洲语言的文本规则,允许在字内换行 - break-all 该行为与亚洲语言的 normal 相同,也允许非亚洲语言文本行的任意字内断开,该值适合包含一些非亚洲文本的亚洲文本 - keep-all 与所有非亚洲语言的 normal 相同,对于中文、韩文、日文,不允许字断开,适合包含少量亚洲文本的非亚洲文本 ,*/ 强制单词内或链接内断行 1 2 3 4 5 6 7 8 div { word-wrap: break-word; } /* word-wrap: 属性用来表明是否允许浏览器在长单词和链接内进行断句 - normal 只在允许的断字点换行 - break-word 在长单词或 url 地址内部进行换行 ,*/ js 获取 dpi 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //获取 dpi function js_getdpi () { var arrdpi = new array (); if ( window. screen. devicexdpi != undefined ) { arrdpi[0] = window. screen. devicexdpi; arrdpi[1] = window. screen. deviceydpi; } else { var tmpnode = document.createelement ( \u0026#34;div\u0026#34; ); tmpnode. style. csstext = \u0026#34;width: 1in; height: 1in; position: absolute; left: 0px; top: 0px; z-index: 99; visibility: hidden\u0026#34;; document.body.appendchild ( tmpnode ); arrdpi[0] = parseint ( tmpnode. offsetwidth ); arrdpi[1] = parseint ( tmpnode. offsetheight ); tmpnode.parentnode.removechild ( tmpnode ); } return arrdpi; } // 将 px 转成 mm let mm = pxvalue/dpi*2.54*10; // dpi 是上面获取的,注意对应 xy 轴 小程序跳转 h5 时 url 参数截断 → 参考链接\n先来看一个例子,原来的 url 为 https://ultimavip.cn/m/mposter.html?source=gxw_001_t_mposter ,跳转后变为 https://ultimavip.cn/m/mposter.html ,参数 ? source=gxw_001_t_mposter 丢失了,为什么呢?编码问题。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 跳转到 h5 页面的小程序代码 targeturl: function () { console.log (this. data. mod_texturl); wx.navigateto ({ // 此处需要编码,因为有 \u0026#39;?\u0026#39; ,可能浏览器不认 url: \u0026#39;../webview/webview? url=\u0026#39; + encodeuricomponent (this. data. mod_texturl) }) } // 跳转到的 h5 页面进行解码 onload: function (options) { this.setdata ({ targeturl: decodeuricomponent (options. url); // 用 decodeuricomponent 进行解码 }) console.log (options. url); } 滚动懒加载的实现 → 参考链接\n什么时候需要懒加载呢?数据量大,一页显示不完,网页渲染事件长,影响体验。如何解决?分页,或数据懒加载。\n先设定了基础前提,假设视窗可以显示 30 数据,总共有 56 条数据要展示。\n如何实现数据懒加载呢?先来看三个属性:\nscrollheight ,元素总高度,包含滚动条中的内容,只读; scrolltop ,当元素出现滚动条时,向下拖动滚动条时,内容向上滚动的距离,可读写; clientheight ,元素内容及其边框所占的空间大小,即可视区域大小高度。 如何判断滚动条到底部了呢?很显然,当 scrollheight - scrolltop - clientheight = 0 时,滚动条就到底部了。\n来看代码,在第一次请求数据的时候,先设置一个变量来记录请求次数(其实后台也是做分页的处理):\n1 2 3 4 5 6 7 8 9 // 初始化首页页码 let currentpage = 1; // this. currentpage = 1 // 获取首页数据,apigettabledata 为定义的获取数据的接口 // data 为请求参数 this.apigettabledata (data). then (res =\u0026gt; { $this. totalpage = res. totalpage; // 这里需要知道总页数 $this. tabledata = res. data; // 表格数据 }) 监听表格 dom 对象的滚动事件:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let dom = document.queryselector (targetdom); dom.addeventlistener (\u0026#39;scroll\u0026#39;, function () { let scrolldistance = dom. scrollheight - dom. scrolltop - dom. clientheight; if (scrolldistance \u0026lt;= 0) { // 为 0 证明滚动条已经到底,可以请求接口 if (this. currentpage \u0026lt; this. totalpage) { // 当前页数小于总页数继续请求 this. currentpage++; // 当前页数自增 // 请求接口代码 // data 为请求参数 this.apigettabledata (data). then (res =\u0026gt; { // 将请求回来的数据和当前展示的数据合并 this. tabledata = $this.tabledata.concat (res. data); }) } } }) 如此,就实现表格滚动下拉时的数据懒加载。\n刷新 dns windows 下 刷新 dns 的方法:打开 cmd → 输入 ipconfig /flushdns 。 github 有时候,连接很慢,甚至有打不开的状况,此时,可以尝试刷新一下 dns ,会有意象不到的效果哦。\n","date":"2020-02-26","permalink":"https://aituyaa.com/%E7%99%BE%E7%A7%91/","summary":"\u003cblockquote\u003e\n\u003cp\u003e:: \u003cstrong\u003e多少事,从来急;天地转,光阴迫。一万年太久,只争朝夕。 \u0026ndash; 教员\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e","title":"百科"},]