-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
1 lines (1 loc) · 716 KB
/
index.json
1
[{"categories":null,"contents":"Windows 痛点例举,\n祖传蓝屏(这一点在 XP 上尤为严重,Win7 以上已经少见许多)。 静默更新。悄悄下载更新,强制更新,莫名其妙跳出来说要更新,关机时强迫更新。恶心的默认策略! 万年难用的资源管理器,不带标签页,窗口开一堆。 开发上来说,编译工具链体验比不上 linux. 虽然 Windows 很难用,但并不妨碍它桌面市场占用率第一的地位。在生活或者工作中,我还是无法避免地被动使用 windows. 因此,我希望通过一些简单定制,让我的使用体验能好一些。\n安装 Everything Everything 是一个 windows 文件搜索程序,搜索速度快,轻量易用,支持正则表达式。完爆 windows 资源管理器自带的搜索功能。直接至官网下载安装即可。\n安装 QtTabBar QtTabBar 是一款 windows 资源管理器扩展工具。之前说了,windows 自带的文件管理器不支持标签页。这才有了 QtTabBar. 值得一提的功能点:\n深度资源管理器标签支持,甚至控制面板都是一个标签页的事儿。 支持用户自定义组,放置常用路径(类似于书签)。 支持多语言,官网下载翻译包即可。 桌面快捷操作。桌面双击即可触发浮动菜单,常用路径一键即达。 不俗的文件预览功能。鼠标悬停即可预览目录、图片、视屏等。预览图片真的很实用! 微软拼音添加小鹤双拼 作为双拼爱好者,当初去知乎吸收了一下各种双拼方案对比,决定学小鹤,至今已经忘记它有啥优缺点了,反正用着挺好就是了。\n方案来自互联网。在 CMD 中直接粘贴\nreg add HKCU\\Software\\Microsoft\\InputMethod\\Settings\\CHS /v UserDefinedDoublePinyinScheme0 /t REG_SZ /d \u0026#34;小鹤双拼*2*^*iuvdjhcwfg^xmlnpbksqszxkrltvyovt\u0026#34; /f PS,微软大法好!在曾经的 XP 时代,搜狗拼音还是很好用的,如今已是臃肿不堪。后来一直在找替代,知道微软拼音横空出世。至此 windows 输入法无需再纠结。其界面节约,打字流畅,又支持添加小鹤双拼,已经很够用了!\n配置应用快捷启动 Linux 上的 dmenu 无人不知,它是一个快捷调用命令的工具,按下一个快捷键呼出 dmenu 程序,键入需要运行的指令(程序名),回车即可调用。在 windows 上,Win+r呼出运行框就能达到类似的效果。然而 windows 的程序安装路径差异较大,并不规范。所以我们可以这样做:\n为所有想要快捷启动的应用创建桌面快捷方式并合理命名(例如将 MicroSoft Word 的快捷方式命名为 word), 将所有这些快捷方式置于同一个目录下(例如 D:\\shortcuts), 将上述路径加入环境变量, Win+r呼出运行框,键入 word,回车即可打开 MicroSoft Word. 如此一来,你桌面上再也不用放一大堆快捷方式,一下子就整洁了好多有木有。\n安装 MSYS2 如果你在 windows 上做开发,必定会装 git bash,而它提供的终端就是 MINGW(Minimalist GNU for Windows)。它的主要作用是提供 windows 版本的 gcc 编译工具链。它自带了一些 linux 基础命令,仅仅是基础。所以 MSYS2 出现了,它在 MINGW 的基础上,提供了更多的命令,并且自带了一个著名的包管理器—Pacman. 没错,正是 Archlinux 的包管理器。如此一来便可以在在 windows 上使用相同的 linux 命令行,而这些都是适配原生 windows 的,并非虚拟机或者子系统之类的。并且想要啥软件包,直接通过 pacman 安装也非常方便。\nMinGW、Clang、UCRT? 安装完之后,会产生多个 launcher,选择哪个呢?这里有详细的对比。其实就是工具链和运行时库的区别。一般用 gcc 建议选择 MINGW64,用 llvm 建议使用 CLANG64.\n一点配置 网上的配置帖子还是比较多的,这里简单列举几个常用的。\n修改镜像 /etc/pacman.d文件夹里面包含几个 mirrorlist,新版的 MSYS2 里面默认已经有中科大源。直接编辑将其提前即可。。\nServer = http://mirrors.ustc.edu.cn/msys2/ Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/ 禁用某些源 编辑/etc/pacman.conf,注释掉不想要的源。\nvim 和 zsh 在 mingw 里面,mingw64 和 clang64 里面都没有。\n参考:https://my.oschina.net/zuozhihua/blog/8757266\n继承环境变量 如果你之前安装过 git bash,现在你的电脑里面应该有两套 mingw(一个 git bash 安装的,一个是 MSYS2 安装的)。Git bash 里面自带 vim. 而 MSYS2 里面可以通过包管理器安装 vim. 然而重要的是 windows 系统程序的路径需要继承下来,这样可以方便的在 mingw 里面直接调用 windows 程序例如\nnotepad.exe:记事本 cmd:命令提示符 explorer:文件管理器 要继承 windows 系统环境变量,找到 MSYS2 的安装目录(例如,D:\\msys64)下有 mingw64.ini,将里面的\nMSYS2_PATH_TYPE=inherit 取消注释,再次打开 mingw64 即可继承系统环境变量。\n上面只改了 MinGW64 的 launcher,如要改其他可如法炮制。\n更改默认 shell launcher.ini(例如 mingw64.ini)里面添加环境变量\nSHELL=/usr/bin/zsh 参考:https://superuser.com/questions/961699/change-default-shell-on-msys2\n添加到 vscdoe 集成终端 设置里面加上,参考:https://gist.github.com/dhkatz/106891324c9624074a84d11e2691144b\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 { \u0026#34;terminal.integrated.defaultProfile.windows\u0026#34;: \u0026#34;MSYS2 MINGW64\u0026#34;, \u0026#34;terminal.integrated.profiles.windows\u0026#34;:{ \u0026#34;MSYS2 MINGW64\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;D:\\\\msys64\\\\usr\\\\bin\\\\bash.exe\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;--login\u0026#34;, \u0026#34;-i\u0026#34;], \u0026#34;env\u0026#34;: { \u0026#34;MSYSTEM\u0026#34;: \u0026#34;MINGW64\u0026#34;, \u0026#34;CHERE_INVOKING\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;MSYS2_PATH_TYPE\u0026#34;: \u0026#34;inherit\u0026#34; } }, \u0026#34;PowerShell\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;PowerShell\u0026#34;, \u0026#34;icon\u0026#34;: \u0026#34;terminal-powershell\u0026#34; }, \u0026#34;Command Prompt\u0026#34;: { \u0026#34;path\u0026#34;: [ \u0026#34;${env:windir}\\\\Sysnative\\\\cmd.exe\u0026#34;, \u0026#34;${env:windir}\\\\System32\\\\cmd.exe\u0026#34; ], \u0026#34;args\u0026#34;: [], \u0026#34;icon\u0026#34;: \u0026#34;terminal-cmd\u0026#34; }, \u0026#34;Git Bash\u0026#34;: { \u0026#34;source\u0026#34;: \u0026#34;Git Bash\u0026#34; } } } 另辟蹊径之 linux 虚拟机 无论是 WSL 还是 MSYS2,启动起来都有些卡,而且换 zsh 之后就更卡了。目前还不清楚原因,所以才有了这种尝试。Windows 下可以使用 virtualbox 安装一个 linux 虚拟机,然后开启 sshd,配置好端口转发(或者虚拟网卡)即可在宿主机上 ssh 连接虚拟机,体验上和 linux 远程开发机相差无几。\n使用 VirtualBox 安装一个 Archlinux 虚拟机重点步骤:\n安装 VirtualBox, 下载 archlinux 镜像(LiveCD), 从镜像启动虚拟机,执行安装流程,这一步参考 archwiki 中的 installation guide. 值得注意的是,虚拟磁盘创建分区的时候,建议直接选择 MBR 分区表。我一开始选择 GPT 分区表,多挂载一个 boot 分区,反复装了 3 次,装完启动引导重启都提示找不到可启动的设备。反正是虚拟机,也不用考虑引导多个系统,直接 MBR 分区表,还可以少挂载一个 boot 分区。\n装完之后安装一些常用软件,\nvim zsh openssh NetworkManager LiveCD 里面网络配置是开箱即用的,但这并不表示装好的系统网络配置和 LiveCD 一样。当你兴奋地重启进新系统之后,很可能发现里面没网。因此,一定要在安装过程中(arch-chroot)把网络管理器的包装好。\n启动进新系统之后,\n1 2 systemctl start NetworkManager.service # 启动网络管理器 systemctl enable NetworkManager.service # 设为自动启动 配置端口转发 系统启动之后,不必装图形界面。直接启动一个 ssh sever,然后在 windows 上 ssh 连上即可。使用体验和远程 Linux 开发机一致。但宿主机(Host)和虚拟机(VM)的网络不一定直接相通,还须做一点配置。\n端口转发配置参考 SSH 连接 NAT 网络模式 VirtualBox 虚拟机。\n配置固定 IP 端口转发配置简单易用,但有一个小缺点。每次暴露一个端口,都需要新增一条规则。比如我转发 22 端口给 ssh,那我 sftp 的 23 端口呢?这时候又得新增一条规则,比较麻烦。\n我们可以通过增加一张虚拟网卡,配置静态 IP 地址和主机网络互通。此后直接访问这个 IP 带端口即可访问虚拟机。配置参考 Win10 宿主机 ssh 连接 VirtualBox 里的 archlinux 虚拟机。\n需要注意一点,配置接口(interface)静态地址并使之持久化生效是网络管理器的职责。所以一定要通过网络管理器的接口设置 IP 地址。之前安装的 NetworkManager 提供nmcli命令行配置工具。详细用法参考 NetworkManager - ArchWiki.\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 yychi@~/bin\u0026gt; nmcli dev DEVICE TYPE STATE CONNECTION wlp2s0 wifi 已连接 LovelyLife lo loopback 连接(外部) lo yychi@~/bin\u0026gt; nmcli connection NAME UUID TYPE DEVICE LovelyLife 92506e38-1823-4f66-1a89-75a62ecabe74 wifi wlp2s0 lo 7e2fe31d-8ba1-1823-ad29-239857601abb loopback lo yychi@~/bin\u0026gt; nmcli connection edit LovelyLife ===| nmcli 交互式连接编辑器 |=== 正在编辑已有的连接 \u0026#34;802-11-wireless\u0026#34;:\u0026#34;LovelyLife\u0026#34; 输入 \u0026#34;help\u0026#34; 或 \u0026#34;?\u0026#34; 查看可用的命令。 输入 \u0026#34;print\u0026#34; 来显示所有的连接属性。 输入 \u0026#34;describe [\u0026lt;设置\u0026gt;.\u0026lt;属性\u0026gt;]\u0026#34; 来获得详细的属性描述。 您可编辑下列设置:connection, 802-11-wireless (wifi), 802-11-wireless-security (wifi-sec), 802-1x, ethtool, match, ipv4, ipv6, hostname, link, tc, proxy nmcli\u0026gt; set ipv4.addresses 192.168.56.100/24 nmcli\u0026gt; set ipv4.gateway 192.168.56.1 nmcli\u0026gt; save persistent temporary nmcli\u0026gt; save persistent 成功地更新了连接 \u0026#34;LovelyLife\u0026#34; (ab876b5a-a02e-43c5-8d65-8da29891b0c1)。 配置 ssh 别名 在宿主机(Host)上配置,\n1 2 3 4 5 # FILE:~/.ssh/config Host varch HostName 192.168.56.100 User xxxx 此后就可以直接ssh varch了。\n小结 至此,我在 windows 上安装了一些小工具、配置了一个可用的 linux 环境。与我个人来说,大幅提升了 windows 的易用性。\n","permalink":"https://guyueshui.github.io/post/suckless-windows/","tags":["msys2","windows","suckless"],"title":"痛的少一些!Windows"},{"categories":[],"contents":"说起 Android 手机的备份,最先闯入眼帘的可能是 XX 搬机助手,某某手机搬家等一键式迁移手机数据的 APP,这些 APP 可以迁移的数据包括,联系人、通话记录、短信、应用(不包括应用数据)、照片视频、文件夹等,此外还包括一些系统设置例如 WIFI 密码1。\n然而,这些 APP 存在以下几个缺点,\n它们必须索取很高的系统权限(用完即卸,倒也无所谓); 随着 APP 的流氓化,里面某些功能点可能要收费(不差钱,也无所谓); 无法备份应用数据或应用数据备份不完备。 对于一个 Android 手机,备份的极致追求是丝滑的换机/刷机体验。即,当我换了一部手机或是我刷了一个新的 ROM,我可以通过备份恢复完全恢复到我换机/刷机之前的样子,并且应用数据得以保留2。刷机暂且不论,因为媒体数据(存盘的照片、视频、文档等)一般不会丢失。但换机之后,希望原封不动的将想要的数据按照原始文件夹结构📁迁移到新手机。\n为了达成以上目标,我摸索了一套 Android 手机的备份攻略。\n一、全量媒体数据备份 这一点可以使用命令行工具 tar 来完成。手机连上电脑或是装一个终端 APP(例如Termux),保证终端可以使用 tar 命令,然后准备一个纯文本文件,里面记录想要备份的文件夹路径或需要排除的文件夹路径。执行全量备份命令(其实就是将想要的文件打包),\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash # # 用于刷机前将媒体数据备份,防止刷坏需要格式化data分区。 SDCARD_DIR=\u0026#34;/storage/emulated/0\u0026#34; EXEC_DIR=$(cd $(dirname $0); pwd) echo \u0026#34;EXEC_DIR=$EXEC_DIR\\n\u0026#34; echo \u0026#34;SDCARD_DIR=$SDCARD_DIR\u0026#34; cd $SDCARD_DIR tar --zstd \\ -cvf $EXEC_DIR/sdcard_backup_$(date +\u0026#34;%Y-%m-%d\u0026#34;).tar.zst \\ --exclude=\u0026#34;*/.*\u0026#34; \\ -T $EXEC_DIR/pull_file_list.txt 猛击这里获取备份和恢复脚本。\n什么情况下需要做这一步?\n在刷机时如果你觉得很可能会丢失数据,需要全盘格式化,先执行一个备份操作,然后将备份数据包拷贝出来。 在换机之后,将媒体数据整个打包迁移到新手机。 二、应用数据备份 应用数据备份一般需要借助专门的 APP,或是备份脚本。Android 9 还是 10 以前,钛备份作为备份界一哥,其地位无人撼动。然而随着 Android 版本的升高,钛备份却跟不上其升级速度,导致出现很多低版本 Android 备份的应用数据无法在高版本 android 系统中还原。无奈之下弃之,所幸遇到了新的替代品:NeoBackup.\n当然,凡是可以备份应用数据的都需要 root 权限,当今 android 手机刷个面具,弄个 root 还是很简单的。不要说 root 以后设备不安全之类的,root 只是一个权限,怎么使用还是看你——设备的拥有者。\nNeoBackup 的使用在软件主页介绍的很详细。一般而言这些应用会有一个备份数据存放的文件夹(下称backup_folder),我们只要将这个文件夹备份好,那么在一个新手机上,先把这个备份文件夹还原过去,再装上 NeoBackup,即可在 APP 里面任意操作想要还原的应用。你可以还原一个或批量还原多个应用及其数据;也可以为一个已安装的应用,还原其数据;更可还原应用到以前的版本(应用降级)。\n如此,将backup_folder作为一个媒体数据备份即可。\n三、增量备份 备份一事,虽说无需过于频繁,但刷机前忘记备份最新数据的事情却时有发生。想一想,你最近几天拍的照片、刚刚下载的一本小说、昨天从同事那儿接收的文件、便签 APP 里记录的备忘录和待办事项。手机中的数据常用常新,而我们在刷机前,很难做到每次都全量备份一遍。一是我半个月前全量备份的数据包和我现在的又差得了多少呢?而是全量备份一次实在耗时,我必须立马刷机。\n鉴于此种种,一个增量备份的补充显得格外重要。定期备份全量数据的压缩包,短期增量备份指定的文件夹(媒体、文档等)。这样的备份才能给宝贵的数据多上一层保险。\n增量备份的一个基本要求,就是文件的时间戳不能随意修改。我尝试过 xplore+webdav 的方案,发现文件同步之后时间戳会丢失,也就是被同步的文件的时间戳会更新为操作时间而非源文件本来的时间。所以想着有没有工具可以在同步的时候保留时间戳呢?\nrsync 应该有这个功能。那么思路有二:\n手机端安装 Termux 使用 rsync,PC 端开 ssh server,手机端向 PC 端推; 手机端开 ssh server,PC 端使用 rsync 从手机端拉数据。 方案 2\n显然后者更方便,因为手机上操作 rsync(操作命令行)显然不如电脑方便。再者说来,第二种方法是以手机为中心,任何时候,手机端开启 ssh server,那么任何电脑装一下 rsync 就能和手机同步数据。如果是电脑插上硬盘,那么随便换电脑,只要插上备份硬盘就可以一直保证同步的两端是一致的。\n手机端安装 SSHelper(参考 https://askubuntu.com/a/343740),PC 上配置 ssh 免密登陆,\n1 2 3 4 5 6 7 8 9 10 $ cat ~/.ssh/config Host munch Hostname 192.168.6.13 Port 2222 $ ssh munch SSHelper Version 13.2 Copyright 2018, P. Lutus client_input_hostkeys: received duplicated ssh-rsa host key Redmi_K40S:4.19.261-HellFire-X//jlk09i902i3rj u0_a299@localhost:~$ 达到直接输入ssh munch就可以登陆的效果。\n在备份盘对应手机用户根目录(对应手机目录通常为/storage/emulated/0)执行此脚本rsync.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/bash cat \u0026lt;\u0026lt; COMMENT This script is used to backup some folders from mobile phone to here (the directory of a backup disk). To achieve this, you should 1. have \\`rsync\\` installed in your PC; 2. start a ssh server on your mobile phone, i.e., SSHelper; 3. config your ssh login via .ssh/config and ssh-copy-id such that you can login your mobile simply with \\`ssh munch\\`. See: https://askubuntu.com/a/343740 COMMENT set -e # exit when error folers_to_sync=( Pictures Snapseed billing Books dictionary Download eudb_en Fonts GooglePinyinInput neo_backuped_data Music Movies ) sync_folder() { if [ ! -d $1 ]; then mkdir $1 fi rsync -auh --progress \\ --size-only \\ --exclude=\u0026#34;.*\u0026#34; \\ munch:SDCard/$1/ $1 } notify() { echo \u0026#34;--- $1\u0026#34; } for folder in ${folers_to_sync[*]}; do notify \u0026#34;sync folder $folder\u0026#34; sync_folder $folder echo; echo # for two newlines done 修改变量folders_to_sync来配置你的同步文件夹,如果文件夹里面没有新增,则此操作会很快。此操作仅会同步手机端新增的文件到 PC. 猛击这里获取最新脚本,文中的可能更新不及时,但也能用用。\n本段原帖参考我的issue.\n方案 1\n虽说在电脑上操作命令行更加顺手,但有了我这脚本 \u0026ndash; rsync_phone.sh的加持,在手机上使用方案 1 也未尝不可。此外,由于 rsync 的工作机制,传输的的双方都需要安装 rsync. 那我还不如在手机上直接推送了。使用方法和方案 1 类似,首先在电脑上开启 sshd,在手机上配置免密登陆,然后再配置一下 .ssh/config 设置一下别名,达到别名直登的效果。\n然后直接一条命令即可完成手机向远端推送备份的工作。\n./rsync_phone.sh backup 四、特殊应用备份 有些应用提供了备份还原机制,而且使用上面的备份方法还原出来会有些问题;有些没法用上面的方法,用了还原了也不好使(如 Termux)。对于这些,就要特殊问题特殊对待了。\nAquaMail 自带账户和应用备份,用它的就好; Termux 提供了 tar 备份机制,参考官方Wiki. 这里提供一个脚本使用起来更加方便,猛击这里获取。\n总结 刷机一时爽,一直刷机一直爽。前提是做好备份哈~\n在不同类型的 ROM 上迁移系统设置是一个灾难,非常不建议这么做。因为不同的 ROM 相当于不同的 APP,你用一个应用的设置去覆盖另一个应用,后果可想而知了。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n即,应用是已经设置好的,打开之后不需要像新装一个应用一样重新设置,里面的使用记录也不会丢失。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/android-backup/","tags":["tech","android","backup","备份"],"title":"Android 手机备份攻略"},{"categories":[],"contents":"今天来说说女儿睡觉的事。女儿十三个多月了,自从五六个月的时候给她一个人睡摇篮,她就能从晚上八九点睡到第二天早上六七点。就这样过了几个月,我在外上班,平时她妈妈带她睡。忽然有段时间,妻子说不舍得她一个人睡,又开始把她抱到床上睡。就这样又过了一段时间。\n起初,她晚上睡觉需要哄睡。妻子或者我抱着她,在摇椅上边摇边唱。十来分钟才能睡着。她要睡觉的时候,就像个小青蛙趴在妈妈的肩膀上,头侧着,整个小脸耷拉在胸前。等她睡熟一段时间,妻子再将她抱起来,缓缓放到床上(前几个月也经历过落地醒),有时候她会醒一下,哼两声,妻子拍拍她,很快就闭眼睡了。\n过了一周又一周,也许是妻子哄睡太累了,毕竟要经过好几道“工序”,妻子的腰背已经痛苦不堪。当我周末回家时,发现女儿睡觉已经不用特意哄了。先喝个睡前奶,视情况换个尿布,然后灭掉所有灯。让她在漆黑的环境下呆着。她首先是扶着围栏站起来,走两步,一把趴倒或是一屁股坐倒在床上,然后换个地方再来。这个阶段如果不干涉,她可以玩好久,直到因为睡不着而变得烦躁。到那时,再想睡就必须得抱着哄了。所以这个阶段,她一站起来我们就把她放倒,直到她不再想站起来。\n此时,进入第二阶段。她会在床上到处翻滚。只有你想不到,没有她不敢翻。有时会翻到围栏和床垫之间,卡在里面出不来,再哇哇大叫求救。有时候遇到隆起的被子鼓包,翻不过去,也会哼唧,让你给她清障。左右上下,四面八方地翻。\n不一会儿,翻得累了。她会翻到妻子身边,或者脚边、或者大腿边、或者胳肢窝。此时,进入第三阶段。她会在一个地方,不再到处翻滚。取而代之的是,一只脚夹在妻子身上,另一只脚抬起来,自由下落地砸床。如此反复,抬脚砸床。有时候她头靠着小腿,脚一下子就砸到妻子的脸。那感觉,很酸爽!我们的鼻子、脸颊、肚皮都被砸过。\n最后,脚砸得累了,越抬越低。砸不动了,就睡着了。\n为什么叫“黑山小妖”呢?刚刚说到,她在躺下之前,有个阶段,到处爬着站起来,然后又坐下。这个阶段她会找人玩,我和妻子自是希望她早点安歇。故意装作不和她玩。这还不够,我们有时候还得装作睡着的样子,嘴里发出呼哧呼哧的声音,来暗示她我们已经睡着。有时候她就会,爬到我们前头,身子对着我们的脑袋,小脸正对着我们眼睛上方。有意逗我们发笑,我们当然努力忍住。但难免有时候憋不住,岔了口气,然后把头扭到另一边。然后她也跟着把头,转过来。依然保持在我眼睛正前方。然后时而跑到妻子那边,亦是如此。妻子有时候憋不住,直接把头扭过来,埋到我肩膀下。这时候她又把目标转向我。过来盯着我的头。活像电影《大话西游之月光宝盒》中至尊宝和唐僧在野外露宿时,被黑山老妖意图吸取精气的场景。\n","permalink":"https://guyueshui.github.io/post/%E9%BB%91%E5%B1%B1%E5%B0%8F%E5%A6%96/","tags":["life"],"title":"黑山小妖"},{"categories":[],"contents":"常年对着破旧笔记本的粗糙小屏幕,实在是对不起自己的眼睛。这不,趁着今年 618 打算买个显示器。预算 1500,分辨率 2K 起,要求具备莱茵低蓝光认证。\n调研了一波发现,确实有几款性价比蛮不错。分别是\nHKC P272U,到手价 1179,4K; HKC P272U Pro,到售价 1279,4K; 微星 MD271UL,到手 1379,4K; 优派 VX2781,到手 1174,优惠力度不大; 创维 F27B40Q,到手 1159,但评价说品控有问题; KOIOS K2723U/D,到手899,而且是4K! WESCOM C2786IUY,到手 859,也是 4K! 实名吐槽一下 HKC 的客服,对顾客爱理不理,要想得到及时回复,你得用备胎对女神的那种聊天方式才行!本来我都想买那款了,真就是由于客服的态度,让我犹豫了一下,最后错过~\n当时心里其实已经很想上 4K,毕竟价格不贵。但是看了一圈下来,4K 的款没有几个是有低蓝光功能的。而选择 2K,就有了很多卖点:\n高刷新率 低响应时间,1ms GTG(一般只看 GTG,据说 MPRT 的有水分) 低蓝光认证 双色域、色域覆盖度等 因此,选择一款性价比高的 2K 显示器要比 4K 难上许多,因为 4K 显示器带这些卖点的基本要在 2500 以上,超出预算了。\n然后来回看了几天,了解到几个在购买显示器时需要注意的点。\n低蓝光 这个里面学问很大,有带莱茵认证和不带莱茵认证的;带莱茵认证的有 software solution 和 hardware solution 之分,同时还有 flicker free(无频闪)认证。总体来说,硬件级防蓝光要比软件防蓝光效果好,因为他是在屏幕面板这一层,直接让短波蓝光的波峰向右偏移,这能够有效降低原来的短波蓝光的量,而且又在一定程度上保住了色彩真实性。所谓软件防蓝光基本就是给屏幕套一层黄色滤镜,类似于手机的护眼模式,带来的效果就是屏幕看着发黄,色彩不美观。至于无频闪,基本就等同于使用了 dc 调光技术,能够降低屏幕在低亮度下的频闪效应。眼睛在无频闪的情境下更为舒适。\n参考:https://www.zhihu.com/question/524352162/answer/2418952098\n屏幕面板 面板主要分 IPS 和 VA 屏两大类。IPS 通用性更强,在可视角度、响应时间、色彩呈现几个方面都要优于 VA 面板。而 VA 面板则胜在对比度高,黑色更彻底,所以在看电影方面体验更好。此外,VA 面板基本不会漏光,而 IPS 面板如果没有调校好,漏光那是一片一片。\n此外,经验上的论断:长时间注视显示器🖥,VA 屏比 IPS 屏更容易让眼睛舒适。一个直观的原因是 VA 屏的对比度高,显示黑色不吃力。而 IPS 屏需要通过灯光控制来呈现黑色,经常可以看到纯黑屏幕上,IPS 屏的背光灯有很明显的存在感。这样的光会比 VA 屏更刺眼。长久看下来,肯定 VA 屏更舒适了。\n我个人感觉也是 VA 屏长时间工作更舒适。此前我用的基本都是 IPS 屏,在注视屏幕 2h 以上后,眼睛很容易疲劳。而现在入手的 VA 屏,基本没有遇到过这种情况。\n总结 鉴于以上种种,如果你对响应时间要求不是那么高,也区分不出专业的色彩,且长时间从事文字编码工作。很强烈推荐你购买一个 VA 屏。而我此次购买的正是 KTC H27V22 这款,它具有以下几个特点:\n使用 HVA 屏,本质还是 VA,但标榜一定程度解决了 VA 屏响应时间慢的缺点; 无频闪; 高刷新率,其实和第一点相称,可以达到 170Hz 的刷新率,响应时间 3ms GTG(虽然我的笔记本只能输出 2K 60Hz 就是了)。 收到货之后,其实发现了一个坏点,不过不影响使用,谁知道换一块会不会换到更多坏点的屏幕呢。就这样吧,目前下来体验还算良好,比较满意!\n这次也是记录一下选择的过程,以及看中的点逐渐变化的过程,以便后续购买显示器参考。当然其中提到的种种概念,网上科普诸多,我的描述仅能表达我自己的理解,未必准确,看官有个感官印象即可,切勿较真。\n","permalink":"https://guyueshui.github.io/post/eye-comfortable-displayer/","tags":["显示器","护眼","va","ips"],"title":"护眼显示器选购指南"},{"categories":["tech"],"contents":"一切都要从前几天给手机刷新 ROM,导致数据丢失说起。\n前些日子,我的 RedmiK40S MIUI13 突然给我自动更新至 MIUI14,这违背了我的意愿。但这还不至于让我动刷机的年头,毕竟年事已高,不再那么想折腾手机。可这次更新,不单单是 MIUI 版本的提升,更是 Android 12 到 13 的版本升级。这直接导致了我的钛备份闪退了,并且使用钛备份还原在 a12 上备份的应用,如果勾选还原应用数据,则必然导致对应应用闪退。应用备份出了问题我是无法接受的。于是,开始上 XDA 找 ROM,随便下了几个,准备动手。\n我自觉这么多年刷机从未失手,再不济也能在 recovery 里面把数据迁移出来再进行格式化。不成想多年未刷机,出了个新名词\u0026quot;a/b slot\u0026quot;. 一下子给我整懵了,在刷完 rom 之后的第一次启动卡 logo. 然后再进 recovery 直接给我把 data 分区锁上了。换了几个 recovery 还是无法解锁 data,最后只能无情 format data. 数据无价啊!\n后来翻了几篇帖子,弄弄 a/b slot 到底是啥意思,放在下面供以后参考吧:\nHow A/B Partitions and Seamless Updates Affect Custom Development on XDA How to fix unable to mount data internal storage 0mb in twrp permanently A/B slots flashing in TWRP 此外,这次踩坑在此处做了简单记录。折腾一宿加一天之后,终于用上了新 ROM,贴几张美图:\n言归正传,新 ROM 装好之后,发现钛备份在 a12 创建的备份,如果还原应用数据,统统都会闪退。感概一波钛备份的过时之后,我寻到了两个替代品:\nNeo Backup Data Backup 并且这次迎来了新的需求:在本地备份 app 数据之后,即时在移动硬盘上做一个备份。于是我想起来,我的小米路由器 3 具有一个 USB 接口。有没有可能将移动硬盘插在路由器上,然后连接该路由器的设备都可以访问这个移动硬盘呢?\n然后兴冲冲的打开小米路由器的管理界面,发现文件共享需要下载他的软件,过于辣鸡了。一怒之下,给小米路由器 3 刷了 OpenWrt1.\n给路由器刷 OpenWrt 这个网上教程很多,列举一下:\n小米路由 3 刷 OpenWRT 小米路由器 3 刷机记录 [OpenWrt Wiki] Xiaomi Mi WiFi R3 (Mi Wifi Router 3 / MIR3 / MI3) 小米路由器 3 刷机潘多拉 (Openwrt) 以及刷回教程 看这几篇应该够了,其中需要注意的点:\n用于开启 ssh 功能的 U 盘必须是 fat 文件系统; 先刷开发版固件,再降级到 miwifi_r3_all_55ac7_2.11.20.bin,2.11.20 版本更容易打开 ssh. OpenWrt 路由器部署 WebDAV 服务器 刷好之后,打开网关地址进入管理界面,如下图所示:\nLuCI(Lua Configuration Interface)是 OpenWrt 的前端管理界面,默认基于 uhttpd 运行,而它不支持 WebDAV,所以我们换一个支持的 \u0026ndash; lighttpd.\n下面是我安装的 lighttpd 的相关软件包,可以用 luci 前端安装,也可以 ssh 到路由器执行opkg install lighttpd-xxx.\n1 2 3 4 5 6 7 root@X-WRT-27AD:~# opkg list-installed | grep lighttpd lighttpd - 1.4.69-1 lighttpd-mod-auth - 1.4.69-1 lighttpd-mod-authn_file - 1.4.69-1 lighttpd-mod-cgi - 1.4.69-1 lighttpd-mod-openssl - 1.4.69-1 lighttpd-mod-webdav - 1.4.69-1 接下来就需要编辑配置文件了,lighttpd 的配置文件位于/etc/lighttpd下。经过配置的目录结构如下:\n1 2 3 4 5 6 7 8 9 10 root@X-WRT-27AD:/etc/lighttpd# ls -R .: certs conf.d lighttpd.conf lighttpd.user mime.conf ./certs: lighttpd.pem ./conf.d: 20-auth.conf 30-cgi.conf 30-webdav.conf 99-luci.conf 20-authn_file.conf 30-openssl.conf 50-http.conf Lighttpd 采用模块化的配置方式,主配置文件为lightttpd.conf,其会自动将conf.d文件夹中的配置加载出来。所以我们对单模块做配置,只需要在 conf.d 文件夹中新增相关配置文件即可。\n例如,conf.d 中的 30-webdav.conf 就是 WebDAV 服务相关的配置:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server.modules += ( \u0026#34;mod_webdav\u0026#34; ) $HTTP[\u0026#34;url\u0026#34;] =~ \u0026#34;^/dav($|/)\u0026#34; { webdav.activate = \u0026#34;enable\u0026#34; webdav.sqlite-db-name = home_dir + \u0026#34;/webdav.db\u0026#34; ## add by yychi, see: https://openwrt.org/docs/guide-user/services/nas/webdav server.document-root := \u0026#34;/mnt/sda4/\u0026#34; # 用作 WebDAV 存储的根目录 auth.backend = \u0026#34;plain\u0026#34; auth.backend.plain.userfile = conf_dir + \u0026#34;/lighttpd.user\u0026#34; # 用户\u0026amp;密码配置 auth.cache = (\u0026#34;max-age\u0026#34; =\u0026gt; \u0026#34;3600\u0026#34;) # 输入密码后的过期时间 ## 用户路径配置 auth.require := ( \u0026#34;/dav/yychi\u0026#34; =\u0026gt; (\u0026#34;method\u0026#34; =\u0026gt; \u0026#34;basic\u0026#34;, \u0026#34;realm\u0026#34; =\u0026gt; \u0026#34;disk for yychi\u0026#34;, \u0026#34;require\u0026#34; =\u0026gt; \u0026#34;user=yychi|user=admin, \u0026#34;/dav/yukynn\u0026#34; =\u0026gt; (\u0026#34;method\u0026#34; =\u0026gt; \u0026#34;basic\u0026#34;, \u0026#34;realm\u0026#34; =\u0026gt; \u0026#34;disk for yukynn\u0026#34;, \u0026#34;require\u0026#34; =\u0026gt; \u0026#34;user=yukynn|user=admin, \u0026#34;/dav/\u0026#34; =\u0026gt; (\u0026#34;method\u0026#34; =\u0026gt; \u0026#34;basic\u0026#34;, \u0026#34;realm\u0026#34; =\u0026gt; \u0026#34;WebDAV Server\u0026#34;, \u0026#34;require\u0026#34; =\u0026gt; \u0026#34;valid-user\u0026#34;), ) } 其中重要的地方我都加了注释,猛击 这里 详细了解配置语法。简单说明下第三行是个 条件配置,意思是 url 匹配后面这个正则表达式的话,scope 里面的配置才生效。其中,home_dir 和 conf_dir 这两个变量均来自于父文件夹的 lighttpd.conf:\n1 2 3 4 5 6 7 8 9 10 11 root@X-WRT-27AD:/etc/lighttpd# cat lighttpd.conf ### Configuration Variables (potentially used in /etc/lighttpd/conf.d/*.conf) var.log_root = \u0026#34;/var/log/lighttpd/\u0026#34; var.server_root = \u0026#34;/www/\u0026#34; var.state_dir = \u0026#34;/var/run/\u0026#34; var.home_dir = \u0026#34;/var/lib/lighttpd/\u0026#34; var.conf_dir = \u0026#34;/etc/lighttpd\u0026#34; var.vhosts_dir = server_root + \u0026#34;/vhosts\u0026#34; var.cache_dir = \u0026#34;/var/cache/lighttpd\u0026#34; var.socket_dir = home_dir + \u0026#34;/sockets\u0026#34; ... 接下来配置 WebDAV 的用户和密码,\n1 echo yychi:123456 \u0026gt;\u0026gt; /etc/lighttpd/lighttpd.user 可以配置多个用户,以及简单的权限管理。比如我的配置/dav/yychi文件夹只能给用户 yychi 或 admin 访问,/dav/yukynn文件夹只能给 yukyyn 或 admin 访问。\n这里认证方式是明文密码,其实还有其他方式,不过需要安装其他包,觉得太麻烦,而且是局域网比较安全所以就不管了。\n配置到现在就差不多了,让我们启动一下:\n1 2 3 4 service uhttpd stop # 关闭 uhttpd service uhttpd disable # 取消 uhttpd 的自启动 service lighttpd start service lighttpd enable 用同一局域网下的手机连接验证一下: Ok,现在我们已经在路由器上部署好了 WebDAV server.\nLuCI 后端更改 如果你打开网关地址(X-Wrt 默认是 192.168.15.1),你会发现 LuCI 已经打不开了。当然了,因为我们关闭了 LuCI 的后端 uhttpd,现在我们要把 LuCI 后端改为 lighttpd,这一点 OpenWrt Wiki 上有很详细的教程,这里我们就简短概括一下。\n主要使用 lighttpd 的 cgi 功能,使得当用浏览器访问网关地址时,重定向到 luci 启动的 cgi 脚本。\n1 2 root@X-WRT-27AD:/www/cgi-bin# ls cgi-backup cgi-download cgi-exec cgi-upload luci 这里的luci就是启动脚本。\n具体配置参考这里:\n1 2 3 4 5 6 7 8 9 10 11 root@X-WRT-27AD:/etc/lighttpd/conf.d# cat 99-luci.conf ## Necessary LUCI configuration ## see: https://openwrt.org/docs/guide-user/luci/luci.on.lighttpd cgi.assign += ( \u0026#34;/cgi-bin/luci\u0026#34; =\u0026gt; \u0026#34;\u0026#34;, \u0026#34;/cgi-bin/cgi-backup\u0026#34; =\u0026gt; \u0026#34;\u0026#34;, \u0026#34;/cgi-bin/cgi-download\u0026#34; =\u0026gt; \u0026#34;\u0026#34;, \u0026#34;/cgi-bin/cgi-exec\u0026#34; =\u0026gt; \u0026#34;\u0026#34;, \u0026#34;/cgi-bin/cgi-upload\u0026#34; =\u0026gt; \u0026#34;\u0026#34; ) server.username := \u0026#34;\u0026#34; server.groupname := \u0026#34;\u0026#34; 最后,\n1 service lighttpd restart 重启 lighttpd 查看效果,现在访问网关地址应该可以打开 LuCI 了。\nLighttpd 启用 HTTPS 到这一步为止,我们已经无痛在 OpenWrt 上搭建了一个 WebDAV server,你可以插个移动硬盘上去,然后使用任意支持 webdav 协议的客户端去操作这块存储空间,LuCI 也能访问了,很好。\n然而偏偏有些应用,他不支持 http,他强制让你使用 https,确实,够安全。但对于局域网来说,难免有些多次一举。我说的就是 FolderSync,这是个好应用,可以将本地文件夹与云端(OneDrive, DropBox, WebDAV 等)文件夹进行双向同步,详询酷安#foldersync 话题。但我确实恼他不支持 http 协议。\n没办法,只好安排一下。由于我是局域网内使用,而 FolderSync 其实也开了个后门,他支持自签名证书。 那我们就给他安排一下,参考这篇 wiki,非常简单。\n网页端访问 你是否想在浏览器中也能访问 WebDAV server?Lighttpd 自带一个简单的页面,可通过以下配置开启:\n# Override the /dav/ folder configured in 30-webdav.conf $HTTP[\u0026#34;url\u0026#34;] =~ \u0026#34;^/dav($|/)\u0026#34; { # The root / is ovveriden by an alais in turris-root.conf so we must add another override alias.url = ( \u0026#34;/dav/\u0026#34; =\u0026gt; \u0026#34;/srv/disk/dav/\u0026#34; ) server.document-root := \u0026#34;/srv/disk/\u0026#34; auth.backend := \u0026#34;plain\u0026#34; auth.backend.plain.userfile := \u0026#34;/etc/lighttpd/webdav.shadow\u0026#34; auth.require := (\u0026#34;\u0026#34;=\u0026gt;(\u0026#34;method\u0026#34;=\u0026gt;\u0026#34;basic\u0026#34;,\u0026#34;realm\u0026#34;=\u0026gt;\u0026#34;webdav\u0026#34;,\u0026#34;require\u0026#34;=\u0026gt;\u0026#34;valid-user\u0026#34;)) # (Optional) add a directory index to see files from a browser server.dir-listing := \u0026#34;enable\u0026#34; # 这里 dir-listing.encoding := \u0026#34;utf-8\u0026#34; # 和这里 webdav.sqlite-db-name := \u0026#34;/etc/lighttpd/webdav_lock.db\u0026#34; } cf. https://gist.github.com/stokito/77c42f8aff2dade91621c1051f73e58c\n加上上面两行配置,重启下 lighttpd,然后打开 192.168.15.1/dav 即可访问 webdav server: 这个界面只能读,不能写。\n加载 webdav-js OpenWrt 的 wiki 上提到一个方法可以让你在浏览器中访问 webdav server,是通过在根目录创建一个 index.html 文件实现的。这个文件里面引入了 webdav-js,作为一个精简的 web client,他支持对 webdav server 的写入操作(包括上传文件,创建文件夹,复制,移动,重命名等)。所以我们在根目录创建一个这样的文件,就可以看到如下界面: 原本这中方法有个问题,wiki 中也有提到:就是子文件夹中没有这个 index.html,因此访问子文件夹就无法展示了。但加了 lighttpd 本身的网页前端之后,每个子文件夹都是可访问的,我猜想应该是开启 dir-listing 选项之后,访问的时候会自动生成 index 文件。而因为 webdav-js 缓存的缘故,跳转到子文件夹,只有里面包含 index 文件,就可以进行访问,即便子文件夹中的 index 文件没有加载 webdav-js. 这样一来,就只需要在根目录添加一个加载了 webdav-js 的 index 文件,就能浏览任意子目录的效果。\nAlist 文件共享服务 一路走来,我们在局域网搭建了一个 WebDAV server,实现了局域网内的文件共享。但是 WebDAV client 对于父母一辈的其实不太友好。而网页端也很简陋,如果他们想打开网页直接看视频什么的,还不够方便。\n又忽然联想到家里还有一台旧笔记本躺在那里吃灰。于是翻将出来直接格式化,装了个 archlinux,不过没装图形界面。由于路由器的性能毕竟吃紧,把移动硬盘接在旧电脑上做文件服务器才是正理。我准备在这台机器上部署一个 Alist 服务器,archlinux 直接\n1 yay -S alist 即可安装。也可以使用官方文档中的一键脚本进行安装,使用脚本安装之后的路径在:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 yychi@dell-inspiron /opt/alist\u0026gt; ls alist* data/ yychi@dell-inspiron /opt/alist\u0026gt; ifconfig enp2s0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.15.233 netmask 255.255.255.0 broadcast 192.168.15.255 ... # 运行 alist server yychi@dell-inspiron /opt/alist\u0026gt; ./alist server INFO[2023-03-15 13:38:01] reading config file: data/config.json INFO[2023-03-15 13:38:01] load config from env with prefix: ALIST_ INFO[2023-03-15 13:38:01] init logrus... INFO[2023-03-15 13:38:01] start server @ 0.0.0.0:5244 INFO[2023-03-15 13:38:01] success load storage: [/disk], driver: [Local] INFO[2023-03-15 13:38:01] success load storage: [/home/yychi], driver: [Local] INFO[2023-03-15 13:38:01] Aria2 not ready. INFO[2023-03-15 13:38:01] qbittorrent not ready. INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/来自分享], driver: [AliyundriveOpen] INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/电影], driver: [AliyundriveOpen] INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/动漫], driver: [AliyundriveOpen] INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/电视剧], driver: [AliyundriveOpen] 运行之后,就可以在浏览器输入 http://192.168.15.233:5244 打开 alist 界面了。 上面是我配置之后的样子2。更多关于 alist 的配置,网上多不胜数,我就不献丑了。\nAlist 支持文件交互,就像云盘一样,可以上传/下载文件,在线播放音视频图片等,也可以用作 WebDAV server,更可以挂载阿里云盘,总之功能多多,值得一玩。\n一点体验优化 上面说到,我将废弃的笔记本(dell-inspiron)用作文件服务器,它需要 7*24 小时开机,妥妥的就是一个正经的服务器。那万一停电了,路由器重启了,就是服务器 ip 变了怎么办?我还得去路由器上或者服务器本身上面去看看 ip,这显然很麻烦。所以必须给 dell-inspiron 一个固定的 ip. 使用 OpenWrt 很容易做到这一点。\n甚至可以做个主机名映射,不用再记烦人的 ip.\nthis is figcaption 这样就可以通过域名来访问文件服务。\n总结 至此,我们就搭建了一个家庭共享存储。只要连上指定局域网,就可以访问并操作共享存储,而且可以基于 Alist 在线观看影视,十分方便。不过还有个痛点就是这个局域网其实是楼下路由器的子网,如果连楼下 wifi,就无法访问了。本来可以通过配置静态路由实现,但楼下路由偏偏不支持配置,只能先搁置一下了。\n说是 OpenWrt,其实是中文社区的 fork,X-Wrt.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n你可能看到了 https,其实也是我为了使用 FolderSync 给加的自签名证书,仅限局域网内部用用。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/share-storage-with-family/","tags":["webdav","共享存储"],"title":"从零开始构建家庭共享存储"},{"categories":["tech","linux"],"contents":" 先前安装系统的时候,swap 分区给小了(机器内存的一半)。我的笔记本内存 8G,swap 给了 4G,当系统已用内存超过 4G,会导致无法休眠。如果 swap 给的和本机内存一样大,那么就不会存在 swap 放不下当前工作镜像的问题。但重新分区追加 swap 显然不现实,所以只能让两块 swap 拼凑一下,达到总体有 8G 可用 swap 的效果。\n像我之前,每当要休眠的时候,都要清一下系统内存,保证已用内存在 4G 以下再休眠。十分繁琐。现在的我建议,swap 分区至少和机器内存相当。\n无论是新建一块 swap 分区,抑或是创建一个 swapfile,都能达到上述效果。下面介绍一下如何创建一个 swapfile 作为追加 swap 使用。\n当前,本机 swap 只有 4G:\n1 2 3 4 $ free -h total used free shared buff/cache available 内存: 7.7Gi 754Mi 6.0Gi 189Mi 978Mi 6.5Gi 交换: 4.1Gi 0B 4.1Gi 创建一个 swapfile:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 创建一个 4G 大小的文件 dd if=/dev/zero of=/tmp/swapfile bs=1M count=4096 # 格式化为 swap 格式 mkswap /tmp/swapfile # 启用 swapfile swapon /tmp/swapfile # 查看当前可用 swap free -h total used free shared buff/cache available 内存: 7.7Gi 822Mi 5.9Gi 189Mi 981Mi 6.5Gi 交换: 8.1Gi 0B 8.1Gi # 关闭 swap swapoff /tmp/swapfile # 查看已使用 swap 分区的摘要 swapon -s Filename Type\tSize\tUsed\tPriority /dev/nvme0n1p7 partition 4323648 0\t-2 /home/yychi/EXTRA/swapfile file 4194300 0\t-3 这样一来,就完成了 swap 扩容。但是,你会发现上述工作每次重启都会丢失,所以还要将 swapfile 写进 fstab,保证每次启动都会加载这块 swap.\n1 2 3 4 5 $ cat /etc/fstab # /dev/nvme0n1p7 UUID=4227170f-0a4f-4a8e-fads-jasdfkjaskf\tnone swap defaults,pri=-2\t0 0 # extra swapfile /home/yychi/EXTRA/swapfile none swap defaults,pri=-1 0 0 另,使用swapon -p \u0026lt;priority\u0026gt; \u0026lt;swap_partition\u0026gt;为指定 swap 分区设置优先级。\n2024-01-28 更新:注意,上述 swap 分区的优先级必须使用正数才有效果,负数是内核专用(在这里相当于没设)。参考这里。我现在的设置是:\n# extra swapfile, prefer use this swap file, as the swap partition is used for hibernation /home/yychi/EXTRA/swapfile none swap defaults,pri=2 0 0 # /dev/nvme0n1p7 UUID=4227170f-0a4f-4a8e-a4fd-0d91f46f54af none swap defaults,pri=1 0 0 数值越大,优先级越高。\n","permalink":"https://guyueshui.github.io/post/extend-swap/","tags":[],"title":"Swap 扩容"},{"categories":["随笔"],"contents":"壬寅年九月,喜得一女,因作此篇。\n九月孕明珠,\n玲珑宛天成。\n愿织锦绣梦,\n携手度余生。\n","permalink":"https://guyueshui.github.io/post/birth-of-shang/","tags":["诗词曲赋"],"title":"喜女"},{"categories":["tech"],"contents":"Minimal example 1 2 3 4 5 6 7 int foo(int x, int y=1) { return x + y; } int main() { cout \u0026lt;\u0026lt; foo(5); // call foo(5, 1) return 0; } 分离编译带来的隐患 如果函数声明和定义分离,此时就有一个 pitfall。由于默认参数可以定义在函数声明(declaration)中,也可以定义在函数定义(definition)中。\nDefault argument in function definition 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // foo.h int foo(int x, int y); // foo.cpp #include \u0026#34;foo.h\u0026#34; int foo(int x, int y=1) { return x + y; } // main.cpp #include \u0026lt;iostream\u0026gt; #include \u0026#34;foo.h\u0026#34; int main() { std::cout \u0026lt;\u0026lt; foo(3); // error } 如果改写 main.cpp:\n1 2 3 4 5 6 7 #include \u0026lt;iostream\u0026gt; int foo(int x, int y=2); int main() { std::cout \u0026lt;\u0026lt; foo(3); } 使用命令行编译\ng++ foo.cpp main.cpp 运行之,猜一下结果?4 or 5?(答案 5)\n很违背直觉是吗?这就是默认参数所带的一系列副作用。所以,在实际工程中,除非特别简单的情况,否则不建议使用默认参数。\n我目前没看过底层原理,只作个简单猜想。这其实是函数定义的不一致,但由于这两个文件是分离编译的,编译器无法处理两个文件中不一致的声明,因为编译器是一个文件一个文件处理的。处理一个丢一个。所以,上述代码可以通过编译,并能成功链接(foo.cpp 中提供了 foo 的定义,main.cpp 中提供了 foo 的前置声明,二者函数原型是一样的,因此也能链接上)。但最后输出的结果,在调用 foo(5) 是,编译器查找默认参数的时候,优先使用了本文件(main.cpp)函数声明中定义的默认参数,而非其他文件(foo.cpp)中函数定义中定义的默认参数。\n如果继续改写 main.cpp\n1 2 3 4 5 6 7 #include \u0026lt;iostream\u0026gt; int foo(int x, char y=2); int main() { std::cout \u0026lt;\u0026lt; foo(3); } 你会发现此时链接器就报错了,说找不到 int foo(int, char) 的定义。\n1 2 3 4 $ g++ foo.cpp main.cpp /usr/bin/ld: /tmp/ccdz0bAH.o: in function `main\u0026#39;: main.cpp:(.text+0xf): undefined reference to `foo(int, char)\u0026#39; collect2: 错误:ld 返回 1 这也印证了,上一个例子确实是函数声明中对默认参数的定义不一致,但编译器无法察觉。\nDefault arguments in function declaration 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // foo.h int foo(int x, int y=1); // foo.cpp #include \u0026#34;foo.h\u0026#34; int foo(int x, int y) { return x + y; } // main.cpp #include \u0026lt;iostream\u0026gt; #include \u0026#34;foo.h\u0026#34; int main() { std::cout \u0026lt;\u0026lt; foo(3); // ok, call foo(3, 1) } 此时在 main.cpp 中可以省略默认参数,因为编译 main.cpp 的时候,先将 foo.h 插入(预处理),此时当走到 foo(3) 的时候,编译器能拿到默认参数的定义,所以可以成功调用。\n如果改写 foo.cpp:\n1 2 #incldue \u0026#34;foo.h\u0026#34; int foo(int x, int y=2) { return x + y; } 此时使用 g++ 可以编译成功,运行结果为 4(使用的 foo.h 中函数声明中定义的默认参数)。\n但使用 clang 时,无法通过编译,会报一个 redefinition of default argument 的错误:\n1 2 3 4 5 6 7 8 $ clang++ main.cpp foo.cpp foo.cpp:3:20: error: redefinition of default argument int foo(int x, int y=2) ^ ~ ./foo.h:4:20: note: previous definition is here int foo(int x, int y=1); ^ ~ 1 error generated. 需要说明的是,这是合理的结果,clang 的做法无疑是更科学的。\nhttps://godbolt.org/z/Wfcz7dsrE\n与虚函数结合带来的隐患 1 2 3 4 5 6 7 8 9 10 11 12 struct Cat { virtual void speak(const char *s = \u0026#34;meow\u0026#34;) { printf(\u0026#34;%s.\\n\u0026#34;, s); } }; struct Tiger : public Cat { void speak(const char *s = \u0026#34;roar\u0026#34;) override { printf(\u0026#34;%s!!\\n\u0026#34;, s); } }; Cat c; c.speak(); // \u0026#34;meow.\u0026#34; Tiger t; t.speak(); // \u0026#34;roar!!\u0026#34; Cat *p = \u0026amp;t; p-\u0026gt;speak(); // \u0026#34;meow!!\u0026#34; 可以看到,当用基类指针调虚函数时,默认参数使用的是基类中提供的那个,是不是又违反直觉了?因为默认参数是编译时确定的,而动态绑定是运行时确定的。在编译完成时,默认参数已经根据静态类型(因为是基类指针,所以是基类类型)进行了填充,运行时发生动态绑定调用派生类的方法这没问题,但是参数传的是基类默认参数。\n更多 pitfall 参见 ref1,其中提到了默认参数的种种罪恶,并奉劝大家不要使用默认参数!\nReferences https://quuxplusone.github.io/blog/2020/04/18/default-function-arguments-are-the-devil/ https://www.geeksforgeeks.org/default-arguments-and-virtual-function-in-cpp/ ","permalink":"https://guyueshui.github.io/post/cpp-default-argument/","tags":["cpp","default-argument","默认参数"],"title":"C++ 中的默认参数简介"},{"categories":["life"],"contents":"8 月 8 日入手了一台红米 K40S(设备代号 munch)。蹲了很久了,终于等到 88 购物节一波降价,在某东 1999 拿下顶配版 K40S.\n为什么换机 上一台 MI6 已经用了 4 年有余(购买于 2017 年 12 月 2 日),早在两三年前就裸奔了,能挺这么久其实也不容易。历来有换机的想法,只是迟迟等不到心仪的机型。这两年旗舰机价格水涨船高,真的很难选出一款值那个价的手机。\n然而计划赶不上变化,上个月老婆手机突然坏了,送修说是 CPU 坏了。真的很无语,能把 CPU 用坏的场景应该不算多见,说到底还是品控的问题(一加 8T 出来挨打)。不幸的是手机已经过了保修期,售后只提供换主板的服务,而价格为 2450 元(包含 50 元维修费)。淦,这手机买来才 3699. 几经商量之下,决定先买个手机给老婆用着。\n为什么是红米 K40S 众所周知,受疫情影响,全国经济不是很景气,加上那一众旗舰机卖的那么贵,且没有吸引我的亮点。所以转而寻求性价比机型,围观几日之后最终确定购买红米系列。很多人可能觉得红米还是低端品牌,但现在的红米在手机性能上完全可以替代当年的旗舰,现在的旗舰都是性能过剩,在堆硬件。\n换机之后的痛点 之前的 MI6 刷了 Lineage OS 一直用的相安无事,只是偶尔会出现应用无法联网的情况,重启后可解决,但也过于恼人了1,这也算我换机的一个诱因吧。而现在从干净的原生 android 换成 MIUI13 时,有了许多不适应。\n没错,我承认 MIUI 是一款出色的手机系统,它提供了很多原生 android 所不具备的功能,有些为国人量身定制,有些弥补了通用 android 久久欠缺的功能(新增的隐私保护功能着实亮眼)。但不可否认,它有些臃肿,而且,充斥着广告。各种集成在系统中的广告让人不胜其烦,而且各个广告的关闭入口分散在不同的设置角落,仿佛就是开发人员有意刁难,当然,我深知这不是开发人员的本意(该死的产品!)。\n但以上都不是我最痛的痛点。我的痛点是它没 root,我无法使用钛备份还原我的应用数据,那可是我这么些年积累下来的宝藏,是我丝滑换机的保障。\n为红米 K40S 刷入 Magisk 在国内论坛没有搜到适用于 munch 的 twrp,但在 XDA 上找到了一个可以用的 recovery. 猛击 此处 获取。刷机步骤可参考之前的文章Android 刷机的一般步骤。\n下载好之后可以使用临时 boot 的方式使用这个第三方 recovery,这样做的好处是不会覆盖官方 rec,方便接收后续 OTA 更新。使用\nfastboot boot twrp.img 以临时从 twrp.img 进行 boot. 启动之后会进入 twrp,此时可以刷如 Magisk 包。在某次更新之后,作者去掉了之前的 zip 包,直接adb sideload magisk.apk即可刷入。刷如之后,再adb install magisk.apk安装 Magisk Manager App,然后重启就好了。\n值得注意的是,MIUI 应用商店里自带了一个 Google Play Store,直接安装完那个就可以使用 google 服务了。应该是 MIUI 保留了 google 套件,因此不用额外刷入。\n至此,你就可以为所欲为了。\n丝滑的使用姿势 MIUI 自带的系统桌面用了几天,某次偶然发现,负一屏的小部件刷新十分费流量。我用的是 22 卡,一个月只有 4G 流量,遂禁用之,改用习惯的 Nova launcher. 但 MIUI 中上划打开全局搜索的功能,还蛮想要的。所幸 Nova launcher 提供了丰富的自定义手势,有两种方案实现这个功能:\nNova launcher 自带的手势中,可以自定义要打开的活动或者应用,里面有全局搜索,配置上即可; 配合 Anywhere 自定义快捷卡片,Nova launcher 中的活动可以绑定 Anywhere 中创建的所有卡片2,当然包括打开全局所搜的卡片。 值得注意的是,全局手势竟然支持系统桌面,我换个桌面 APP,全屏手势竟然就不让用了。不知道是技术问题还是 MIUI 有意为之,隔壁 H2OS 都是在系统层面上支持的。总之,调教之后还是用着比较舒服的,等哪天再发现痛点兴许会刷别的 ROM 吧,也正好给第三方 ROM 开发者一些时间~\n欢迎给位看官交流 android 使用心得!这次写的简陋了些,本来想录几个屏,贴几张图,但是手机和电脑传输稍微麻烦了点,偷个懒吧 orz\n试想你正前往火车站检票,人家让你亮健康码,但此时你的支付宝连不上网,是的,其他软件可以,就是支付宝不行。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n这就牛逼大发了,你可以上划某个应用图标打开任意活动;屏幕双指下划打开微信与指定联系人聊天;屏幕 pinch-in 打开健康码等等。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/fresh-experience-on-munch/","tags":["android","root"],"title":"红米 K40S 初体验"},{"categories":["Notes"],"contents":"Static members 1 2 3 4 5 6 7 8 9 10 11 12 13 class A { public: // non-static member (i.e., `data` is not visible in `fun1` static fun1(); fun2(); private: int data; static int sata; }; A a; a.fun1(); // valid, equivalent to the following A::fun1(); 静态成员不能访问非静态成员(因为静态成员独立与类的实例(即对象)而存在,为了在没有对象被创建的情况下,静态成员还是可以使用,所以不能访问非静态成员。) 同理,类的任何对象不包含静态数据成员 静态成员不与对象,不与this指针发生交互,作为结果,静态成员函数不能声明为const 可以通过类的对象调用静态成员函数,但此调用跟对象的状态并无关系,也就是说换个对象来调用是等价的,都等价于使用类名加域作用符来调用 静态成员一般定义在类的外部,因为每个对象都共享静态成员,避免多次定义 View static member as a normal function that has nothing to do with the class, except you must use :: to access static members test for emphsize 中文, 再来一次..强调中文..\nStatic local variables Function parameters, as well as variables defined inside the function body, are called local variables.\nMuch like a person’s lifetime is defined to be the time between their birth and death, an object’s lifetime is defined to be the time between its creation and destruction. Note that variable creation and destruction happen when the program is running (called runtime), not at compile time. Therefore, lifetime is a runtime property.\nA variable’s storage duration (usually just called duration) determines what rules govern when and how a variable will be created and destroyed. In most cases, a variable’s storage duration directly determines its lifetime.\nLocal variables have automatic duration, which means they are created at the point of definition and destroyed at the end of the block they are defined in. For example:\n1 2 3 4 5 6 7 int main() { int i { 5 }; // i created and initialized here double d { 4.0 }; // d created and initialized here return 0; } // i and d are destroyed here For this reason, local variables are sometimes called automatic variables.\nGlobal variables are created when the program starts, and destroyed when it ends. This is called static duration. Variables with static duration are sometimes called static variables.\nIn C++, variables can also be declared outside of a function. Such variables are called global variables. Unlike local variables, which are uninitialized by default, static variables are zero-initialized by default.\nScope determines where a variable is accessible. Duration determines where a variable is created and destroyed. Linkage determines whether the variable can be exported to another file or not.\nHere comes the word: using the static keyword on a local variable changes its duration from automatic duration to static duration. This means the variable is now created at the start of the program, and destroyed at the end of the program (just like a global variable). As a result, the static variable will retain its value even after it goes out of scope!\nAutomatic duration (default):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include \u0026lt;iostream\u0026gt; void incrementAndPrint() { int value{ 1 }; // automatic duration by default ++value; std::cout \u0026lt;\u0026lt; value \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } // value is destroyed here int main() { incrementAndPrint(); incrementAndPrint(); incrementAndPrint(); return 0; } /** * Outputs: * 2 * 2 * 2 */ Each time incrementAndPrint() is called, a variable named value is created and assigned the value of 1. incrementAndPrint() increments value to 2, and then prints the value of 2. When incrementAndPrint() is finished running, the variable goes out of scope and is destroyed.\nStatic duration (using static keyword):\n199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 #include \u0026lt;iostream\u0026gt; void incrementAndPrint() { static int s_value{ 1 }; // static duration via static keyword. This initializer is only executed once. ++s_value; std::cout \u0026lt;\u0026lt; s_value \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } // s_value is not destroyed here, but becomes inaccessible because it goes out of scope int main() { incrementAndPrint(); incrementAndPrint(); incrementAndPrint(); return 0; } /** * Outputs: * 2 * 3 * 4 */ Static local variables that are zero initialized or have a constexpr initializer can be initialized at program start. Static local variables with non-constexpr initializers are initialized the first time the variable definition is encountered (the definition is skipped on subsequent calls, so no reinitialization happens). Because s_value has constexpr initializer 1, s_value will be initialized at program start.\nWhen s_value goes out of scope at the end of the function, it is not destroyed. Each time the function incrementAndPrint() is called, the value of s_value remains at whatever we left it at previously.\nGenerating a unique ID number is very easy to do with a static duration local variable:\n1 2 3 4 5 int generateID() { static int s_itemID{ 0 }; return s_itemID++; // makes copy of s_itemID, increments the real s_itemID, then returns the value in the copy } The first time this function is called, it returns 0. The second time, it returns 1. Each time it is called, it returns a number one higher than the previous time it was called. You can assign these numbers as unique IDs for your objects. Because s_itemID is a local variable, it can not be “tampered with” by other functions.\nStatic variables offer some of the benefit of global variables (they don’t get destroyed until the end of the program) while limiting their visibility to block scope. This makes them safer for use even if you change their values regularly.\nBest practice: Initialize your static local variables. Static local variables are only initialized the first time the code is executed, not on subsequent calls.\nQ: What effect does using keyword static have on a global variable? What effect does it have on a local variable?\nA: When applied to a global variable, the static keyword defines the global variable as having internal linkage, meaning the variable cannot be exported to other files.\nWhen applied to a local variable, the static keyword defines the local variable as having static duration, meaning the variable will only be created once, and will not be destroyed until the end of the program.\nStatic global variables Identifiers have another property named linkage. An identifier’s linkage determines whether other declarations of that name refer to the same object or not.\nLocal variables have no linkage, which means that each declaration refers to a unique object.\nGlobal variable and functions identifiers can have either internal linkage or external linkage.\nAn identifier with internal linkage can be seen and used within a single file, but it is not accessible from other files (that is, it is not exposed to the linker). This means that if two files have identically named identifiers with internal linkage, those identifiers will be treated as independent.\nTo make a non-constant global variable internal, we use the static keyword.\n1 2 3 4 5 6 7 8 9 static int g_x; // non-constant globals have external linkage by default, but can be given internal linkage via the static keyword const int g_y { 1 }; // const globals have internal linkage by default constexpr int g_z { 2 }; // constexpr globals have internal linkage by default int main() { return 0; } Thus, when applied to a local variable, the static keyword defines the local variable as having static duration, meaning the variable will only be created once, and will not be destroyed until the end of the program.\nTo be continued\u0026hellip;\nReferences 6.10 — Static local variables ","permalink":"https://guyueshui.github.io/post/static-in-cpp/","tags":["cpp"],"title":"C++ 中的 static 关键字"},{"categories":["tech"],"contents":"开机后扬声器无声音 问题描述:个人笔记本电脑长久以来都有一个问题,开机之后扬声器没声音,从应用层看毫无问题,所有音乐视频照常播放,能调音量,就是没声音。必须插一下耳机,耳机里有声音。然后再拔出耳机,外部扬声器也有声音了。因此使用起来并无大碍,只需要准备一个耳机,开机之后插拔一下即可。\n但问题始终要解决的,我总不可能每次开机的时候旁边都有个有线耳机吧。所以要解决这个问题,可从以下两个路子着手:\n彻底解决这个问题,每次开机正常有声音,插入耳机,则声音通过耳机输出; 退而求其次,能在开机之后不需要插拔耳机,也能将外部扬声器声音释放出来,比如一个 shell 命令激活。 目前我只做到了第二种,尚未完美解决。\n记录一下排查问题的大致流程:\n首先上网查了一下相关问题,怀疑是 pulseaudio 的问题,结果偶然某一次正常播放音频的时候,我发现杀掉 pulseaudio 的进程竟然毫无影响,因此可能不是它的问题。\n然后查看 alsamixer 得知:\n可以看到有两张声卡,一个是default,一个是HDA Intel PCH,但其实我只有一张声卡\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ aplay -l **** List of PLAYBACK Hardware Devices **** card 0: PCH [HDA Intel PCH], device 0: ALC255 Analog [ALC255 Analog] Subdevices: 1/1 Subdevice #0: subdevice #0 card 0: PCH [HDA Intel PCH], device 3: HDMI 0 [HDMI 0] Subdevices: 1/1 Subdevice #0: subdevice #0 card 0: PCH [HDA Intel PCH], device 7: HDMI 1 [HDMI 1] Subdevices: 1/1 Subdevice #0: subdevice #0 card 0: PCH [HDA Intel PCH], device 8: HDMI 2 [HDMI 2] Subdevices: 1/1 Subdevice #0: subdevice #0 card 0: PCH [HDA Intel PCH], device 9: HDMI 3 [HDMI 3] Subdevices: 1/1 Subdevice #0: subdevice #0 card 0: PCH [HDA Intel PCH], device 10: HDMI 4 [HDMI 4] Subdevices: 1/1 Subdevice #0: subdevice #0 此时我观察到在无声音的状态下使用\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 yychi@~\u0026gt; speaker-test -c 2 speaker-test 1.2.6 Playback device is default Stream parameters are 48000Hz, S16_LE, 2 channels Using 16 octaves of pink noise Rate set to 48000Hz (requested 48000Hz) Buffer size range from 96 to 1048576 Period size range from 32 to 349526 Using max buffer size 1048576 Periods = 4 was set period_size = 262144 was set buffer_size = 1048576 0 - Front Left 1 - Front Right ^CTime per period = 10.942892 yychi@~\u0026gt; speaker-test -c 2 -D hw:0 speaker-test 1.2.6 Playback device is hw:0 Stream parameters are 48000Hz, S16_LE, 2 channels Using 16 octaves of pink noise Rate set to 48000Hz (requested 48000Hz) Buffer size range from 64 to 1048576 Period size range from 32 to 524288 Using max buffer size 1048576 Periods = 4 was set period_size = 262144 was set buffer_size = 1048576 0 - Front Left 1 - Front Right ^CTime per period = 5.761266 测试扬声器的时候,指定 device 为hw:0总能够发出声音,此时便想到很可能是系统默认的播放设备(playback)不对,因此按照 archwiki 上的教程 设置了默认声卡。其实就是新建一个文件然后重启:\n1 2 3 4 5 6 7 8 9 10 11 # file: ~/.asoundrc pcm.!default { type hw card PCH } ctl.!default { type hw card PCH } 重启之后果然立刻就有声音了,正当我被折腾成功的喜悦包围之时,我试着将耳机插入耳机孔,tmd 根本就没效果。这不完犊子了吗,修好了外放,耳机失效了。接着就是一顿找,\n1 alsactl restore 拯救了耳机的输出,但没有完全拯救,因为这个时候耳机和外部扬声器都有声音。也就是说,他不会根据耳机的插拔而切换输出了,而是两方都有输出。\n然而就是根据耳机插拔自动切换输出设备的功能,在我设置了默认声卡之后,一直得不到解决。此前,虽然需要插拔一下耳机,外部扬声器才能播放声音,好歹耳机的插拔还是能正常生效的。所以,我还是删掉了默认声卡的配置,转而寻求“开机后如何切换 sound playback”。直到我找到 参考 1.\n其中提到\u0026quot;card-profile\u0026quot;这个概念,听起来像是声卡的情景模式,通过如下命令列出所有支持的 card-profiles:\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 yychi@~\u0026gt; pacmd list-cards 1 card(s) available. index: 0 name: \u0026lt;alsa_card.pci-0000_00_1f.3\u0026gt; driver: \u0026lt;module-alsa-card.c\u0026gt; owner module: 6 properties: alsa.card = \u0026#34;0\u0026#34; alsa.card_name = \u0026#34;HDA Intel PCH\u0026#34; alsa.long_card_name = \u0026#34;HDA Intel PCH at 0xb4220000 irq 135\u0026#34; alsa.driver_name = \u0026#34;snd_hda_intel\u0026#34; device.bus_path = \u0026#34;pci-0000:00:1f.3\u0026#34; sysfs.path = \u0026#34;/devices/pci0000:00/0000:00:1f.3/sound/card0\u0026#34; device.bus = \u0026#34;pci\u0026#34; device.vendor.id = \u0026#34;8086\u0026#34; device.vendor.name = \u0026#34;Intel Corporation\u0026#34; device.product.id = \u0026#34;9d71\u0026#34; device.product.name = \u0026#34;Sunrise Point-LP HD Audio\u0026#34; device.form_factor = \u0026#34;internal\u0026#34; device.string = \u0026#34;0\u0026#34; device.description = \u0026#34;内置音频\u0026#34; module-udev-detect.discovered = \u0026#34;1\u0026#34; device.icon_name = \u0026#34;audio-card-pci\u0026#34; profiles: input:analog-stereo: 模拟立体声 输入 (priority 32833, available: unknown) output:analog-stereo: 模拟立体声 输出 (priority 39268, available: unknown) output:analog-stereo+input:analog-stereo: 模拟立体声双工 (priority 39333, available: unknown) output:hdmi-stereo: Digital Stereo (HDMI) 输出 (priority 5900, available: no) output:hdmi-stereo+input:analog-stereo: Digital Stereo (HDMI) 输出 + 模拟立体声 输入 (priority 5965, available: unknown) output:hdmi-surround: Digital Surround 5.1 (HDMI) 输出 (priority 800, available: no) output:hdmi-surround+input:analog-stereo: Digital Surround 5.1 (HDMI) 输出 + 模拟立体声 输入 (priority 865, available: unknown) output:hdmi-surround71: Digital Surround 7.1 (HDMI) 输出 (priority 800, available: no) output:hdmi-surround71+input:analog-stereo: Digital Surround 7.1 (HDMI) 输出 + 模拟立体声 输入 (priority 865, available: unknown) output:hdmi-stereo-extra1: Digital Stereo (HDMI 2) 输出 (priority 5700, available: no) output:hdmi-stereo-extra1+input:analog-stereo: Digital Stereo (HDMI 2) 输出 + 模拟立体声 输入 (priority 5765, available: unknown) output:hdmi-surround-extra1: Digital Surround 5.1 (HDMI 2) 输出 (priority 600, available: no) output:hdmi-surround-extra1+input:analog-stereo: Digital Surround 5.1 (HDMI 2) 输出 + 模拟立体声 输入 (priority 665, available: unknown) output:hdmi-surround71-extra1: Digital Surround 7.1 (HDMI 2) 输出 (priority 600, available: no) output:hdmi-surround71-extra1+input:analog-stereo: Digital Surround 7.1 (HDMI 2) 输出 + 模拟立体声 输入 (priority 665, available: unknown) output:hdmi-stereo-extra2: Digital Stereo (HDMI 3) 输出 (priority 5700, available: no) output:hdmi-stereo-extra2+input:analog-stereo: Digital Stereo (HDMI 3) 输出 + 模拟立体声 输入 (priority 5765, available: unknown) output:hdmi-surround-extra2: Digital Surround 5.1 (HDMI 3) 输出 (priority 600, available: no) output:hdmi-surround-extra2+input:analog-stereo: Digital Surround 5.1 (HDMI 3) 输出 + 模拟立体声 输入 (priority 665, available: unknown) output:hdmi-surround71-extra2: Digital Surround 7.1 (HDMI 3) 输出 (priority 600, available: no) output:hdmi-surround71-extra2+input:analog-stereo: Digital Surround 7.1 (HDMI 3) 输出 + 模拟立体声 输入 (priority 665, available: unknown) output:hdmi-stereo-extra3: Digital Stereo (HDMI 4) 输出 (priority 5700, available: no) output:hdmi-stereo-extra3+input:analog-stereo: Digital Stereo (HDMI 4) 输出 + 模拟立体声 输入 (priority 5765, available: unknown) output:hdmi-surround-extra3: Digital Surround 5.1 (HDMI 4) 输出 (priority 600, available: no) output:hdmi-surround-extra3+input:analog-stereo: Digital Surround 5.1 (HDMI 4) 输出 + 模拟立体声 输入 (priority 665, available: unknown) output:hdmi-surround71-extra3: Digital Surround 7.1 (HDMI 4) 输出 (priority 600, available: no) output:hdmi-surround71-extra3+input:analog-stereo: Digital Surround 7.1 (HDMI 4) 输出 + 模拟立体声 输入 (priority 665, available: unknown) output:hdmi-stereo-extra4: Digital Stereo (HDMI 5) 输出 (priority 5700, available: no) output:hdmi-stereo-extra4+input:analog-stereo: Digital Stereo (HDMI 5) 输出 + 模拟立体声 输入 (priority 5765, available: unknown) output:hdmi-surround-extra4: Digital Surround 5.1 (HDMI 5) 输出 (priority 600, available: no) output:hdmi-surround-extra4+input:analog-stereo: Digital Surround 5.1 (HDMI 5) 输出 + 模拟立体声 输入 (priority 665, available: unknown) output:hdmi-surround71-extra4: Digital Surround 7.1 (HDMI 5) 输出 (priority 600, available: no) output:hdmi-surround71-extra4+input:analog-stereo: Digital Surround 7.1 (HDMI 5) 输出 + 模拟立体声 输入 (priority 665, available: unknown) off: 关 (priority 0, available: unknown) active profile: \u0026lt;output:hdmi-stereo-extra1+input:analog-stereo\u0026gt; sinks: alsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1/#0: 内置音频 Digital Stereo (HDMI 2) sources: alsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1.monitor/#0: Monitor of 内置音频 Digital Stereo (HDMI 2) alsa_input.pci-0000_00_1f.3.analog-stereo/#1: 内置音频 模拟立体声 ports: analog-input-mic: Microphone (priority 8700, latency offset 0 usec, available: unknown) properties: device.icon_name = \u0026#34;audio-input-microphone\u0026#34; analog-output-speaker: Speakers (priority 10000, latency offset 0 usec, available: unknown) properties: device.icon_name = \u0026#34;audio-speakers\u0026#34; analog-output-headphones: Headphones (priority 9900, latency offset 0 usec, available: no) properties: device.icon_name = \u0026#34;audio-headphones\u0026#34; hdmi-output-0: HDMI / DisplayPort (priority 5900, latency offset 0 usec, available: no) # available: yes if headphone plugged properties: device.icon_name = \u0026#34;video-display\u0026#34; hdmi-output-1: HDMI / DisplayPort 2 (priority 5800, latency offset 0 usec, available: no) properties: device.icon_name = \u0026#34;video-display\u0026#34; hdmi-output-2: HDMI / DisplayPort 3 (priority 5700, latency offset 0 usec, available: no) properties: device.icon_name = \u0026#34;video-display\u0026#34; hdmi-output-3: HDMI / DisplayPort 4 (priority 5600, latency offset 0 usec, available: no) properties: device.icon_name = \u0026#34;video-display\u0026#34; hdmi-output-4: HDMI / DisplayPort 5 (priority 5500, latency offset 0 usec, available: no) properties: device.icon_name = \u0026#34;video-display\u0026#34; 接着\n1 pacmd set-card-profile alsa_card.pci-0000_00_1f.3 output:analog-stereo+input:analog-stereo 设置声卡的情景模式,敲完之后外部扬声器就有声音了,而且耳机插拔也是正常生效的。值得注意的是,插入耳机后,上述注释处就变成 yes 了。\n这个问题暂时解决到这里,等以后有时间看能否完全解决!\n当天更新:重启之后发现不用重新再走一遍上述流程,可以看到\u0026quot;active profile: \u0026lt;output:hdmi-stereo-extra1+input:analog-stereo\u0026gt;\u0026ldquo;已经持久化改变了。那么这个问题就算解决啦!\nUSB 耳机无声音 起因:入了个 usb 耳机,然后插上电脑(archlinux)无效果。电脑自带扬声器和 3.5mm 耳机孔都是好好工作的,然而插了 usb 耳机后好像没插一样。\n先看下 lsusb 排除下物理连接问题\n1 2 3 4 5 6 7 yychi@~\u0026gt; lsusb Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 002: ID 04f2:b5a3 Chicony Electronics Co., Ltd XiaoMi USB 2.0 Webcam Bus 001 Device 003: ID 04f3:0c1a Elan Microelectronics Corp. ELAN:Fingerprint Bus 001 Device 004: ID 8087:0a2b Intel Corp. Bluetooth wireless interface Bus 001 Device 009: ID 0b0e:0313 GN Netcom Jabra EVOLVE 30 II # usb headphone Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub 按照上述经验怀疑是 card-profile 的问题。是不是情景模式不对呢?最后发现和上次不一样,上次普通耳机和外置扬声器用的一张声卡。这次 usb 耳机用的自带声卡,所以需要切换成自带声卡,且调对情景模式。\n现在用 pipewire 替代 pulse-audio,其对应的配置命令行为pactl\n1 2 3 4 5 6 7 8 yychi@~\u0026gt; pactl list cards short 45\talsa_card.pci-0000_00_1f.3\talsa # 自带声卡 576\talsa_card.usb-GN_Audio_A_S_Jabra_EVOLVE_30_II_0005EDED3F4A09-00\talsa # usb耳机声卡 pactl set-card-profile alsa_card.usb-GN_Audio_A_S_Jabra_EVOLVE_30_II_0005EDED3F4A09-00 pro-audio # 设置正确的情景模式 # 切换声卡,即设置默认输入输出 pactl set-default-sink alsa_output.usb-GN_Audio_A_S_Jabra_EVOLVE_30_II_0005EDED3F4A09-00.pro-output-0 # 设置默认输出 pactl set-default-source alsa_input.usb-GN_Audio_A_S_Jabra_EVOLVE_30_II_0005EDED3F4A09-00.pro-input-0 # 设置默认输入 一番折腾,最终成功让 usb 耳机出声。\n一些命令:\n命令 含义 pactl list cards [short] 列出可用声卡 pactl get-default-sink/source 获取默认输出/输入 pactl set-card-profile \u0026lt;card-name/index\u0026gt; \u0026lt;card-profile\u0026gt; 给指定声卡设置情景模式 References How can I switch between different audio output hardware using the shell? Archwiki: Advanced Linux Sound Architecture [SOLVED] sound not working ","permalink":"https://guyueshui.github.io/post/linux%E5%BC%80%E6%9C%BA%E6%97%A0%E5%A3%B0%E9%9F%B3/","tags":["linux","audio"],"title":"Linux 开机无声音"},{"categories":["linux"],"contents":"自打使用 linux 系统以来,触摸板这块的体验一只是个痛点:只支持基本的点击,双指垂直滚动。很久以来我就一直想要触摸板水平滚动的功能。今天终于实现了!\nSynaptics 其实很久以前就照抄过一份xf86-input-synaptics驱动程序的触摸板配置:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # file: /etc/X11/xorg.conf.d/70-synaptics.conf Section \u0026#34;InputClass\u0026#34; Identifier \u0026#34;touchpad\u0026#34; Driver\t\u0026#34;synaptics\u0026#34; MatchIsTouchpad \u0026#34;on\u0026#34; Option \u0026#34;TapButton1\u0026#34; \u0026#34;1\u0026#34; Option \u0026#34;TapButton2\u0026#34; \u0026#34;3\u0026#34; Option \u0026#34;TapButton3\u0026#34; \u0026#34;2\u0026#34; Option \u0026#34;VertEdgeScroll\u0026#34; \u0026#34;on\u0026#34; Option \u0026#34;VertTwoFingerScroll\u0026#34; \u0026#34;on\u0026#34; Option \u0026#34;HorizonEdgeScroll\u0026#34; \u0026#34;on\u0026#34; Option \u0026#34;HorizonTwoFingerScroll\u0026#34; \u0026#34;on\u0026#34; Option \u0026#34;EmulateTwoFingerMinZ\u0026#34; \u0026#34;40\u0026#34; Option \u0026#34;EmulateTwoFingerMinW\u0026#34; \u0026#34;8\u0026#34; Option \u0026#34;FingerLow\u0026#34; \u0026#34;30\u0026#34; Option \u0026#34;FingerHigh\u0026#34; \u0026#34;50\u0026#34; Option \u0026#34;VertScrollDelta\u0026#34; \u0026#34;-111\u0026#34; Option \u0026#34;HorizScrollDelta\u0026#34; \u0026#34;-111\u0026#34; EndSection 但很奇怪,一直以来水平滚动一直没生效。其实想来也是乌龙,是我抄错了:\n1 2 3 # 正确的应该是 Horiz 而非 Horizon Option \u0026#34;HorizEdgeScroll\u0026#34; \u0026#34;on\u0026#34; Option \u0026#34;HorizTwoFingerScroll\u0026#34; \u0026#34;on\u0026#34; 其实只要改正并重启一下,事情就完美解决了。可惜我一直没发现,还尝试研究为啥水平滚动不生效呢,他文档明明这么写了,难道是诓我?\nsynclient是用于实时更改 synaptics 驱动参数的命令行工具,使用\n1 synclient HorizTwoFingerScroll=1 即可开启水平滚动。事情本应到此结束,但是我惊讶的发现 synaptics 驱动已经停止维护,archwiki 上已经推荐大家使用libinput了。\nLibinput Cf. https://wiki.archlinux.org/title/Libinput\n参考 archwiki 直接把触摸板输入驱动换成libinput,尤其值得注意,如果/etc/X11/xorg.conf.d中需要移除(最好先备份)之前的 synaptic driver 的配置文件,比如我的:\n1 rm /etc/X11/xorg.conf.d/70-synaptics.conf 删除之后像这样:\n1 2 3 4 5 6 yychi@/etc/X11/xorg.conf.d\u0026gt; ls -al 总用量 12 drwxr-xr-x 2 root root 4096 3月 26 00:23 ./ drwxr-xr-x 4 root root 4096 1月 3 20:53 ../ -rw-r--r-- 1 root root 337 3月 26 00:23 00-keyboard.conf lrwxrwxrwx 1 root root 43 3月 25 23:23 40-libinput.conf -\u0026gt; /usr/share/X11/xorg.conf.d/40-libinput.conf 看下配置文件:\n1 2 3 4 5 6 7 8 9 10 11 12 cat 40-libinput.conf Section \u0026#34;InputClass\u0026#34; Identifier \u0026#34;touchpad\u0026#34; MatchIsTouchpad \u0026#34;on\u0026#34; Driver \u0026#34;libinput\u0026#34; Option \u0026#34;AccelerationProfile\u0026#34; \u0026#34;2\u0026#34; Option \u0026#34;Sensitivity\u0026#34; \u0026#34;0.1\u0026#34; Option \u0026#34;Tapping\u0026#34; \u0026#34;on\u0026#34; Option \u0026#34;ClickMethod\u0026#34; \u0026#34;clickfinger\u0026#34; Option \u0026#34;TappingButtonMap\u0026#34; \u0026#34;lrm\u0026#34; Option \u0026#34;NaturalScrolling\u0026#34; \u0026#34;on\u0026#34; EndSection 特别注意\n注意到文件夹中还有一个文件00-keyboard.conf,由于我们换了驱动,而 libinput 是所有输入的驱动,包括键盘,所以必须适当更改该文件,否则重启进来之后你会发现键盘失效!\n1 2 3 4 5 6 7 8 9 10 ychi@/etc/X11/xorg.conf.d\u0026gt; cat 00-keyboard.conf # Written by systemd-localed(8), read by systemd-localed and Xorg. It\u0026#39;s # probably wise not to edit this file manually. Use localectl(1) to # instruct systemd-localed to update it. Section \u0026#34;InputClass\u0026#34; Identifier \u0026#34;system-keyboard\u0026#34; MatchIsKeyboard \u0026#34;on\u0026#34; Driver \u0026#34;libinput\u0026#34;\t# 这行必须指定 driver 为 libinput,否则重启后键盘无法输入 Option \u0026#34;XkbLayout\u0026#34; \u0026#34;cn\u0026#34; EndSection 配置完成后用xinput看看:\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 yychi@/etc/X11/xorg.conf.d\u0026gt; xinput ⎡ Virtual core pointer id=2\t[master pointer (3)] ⎜ ↳ Virtual core XTEST pointer id=4\t[slave pointer (2)] ⎜ ↳ ELAN2301:00 04F3:306B Touchpad id=11\t[slave pointer (2)] ⎣ Virtual core keyboard id=3\t[master keyboard (2)] ↳ Virtual core XTEST keyboard id=5\t[slave keyboard (3)] ↳ Power Button id=6\t[slave keyboard (3)] ↳ Video Bus id=7\t[slave keyboard (3)] ↳ Video Bus id=8\t[slave keyboard (3)] ↳ Sleep Button id=9\t[slave keyboard (3)] ↳ XiaoMi USB 2.0 Webcam: XiaoMi U id=10\t[slave keyboard (3)] ↳ AT Translated Set 2 keyboard id=12\t[slave keyboard (3)] ↳ Wireless hotkeys id=13\t[slave keyboard (3)] yychi@/etc/X11/xorg.conf.d\u0026gt; xinput list-props 11\t# 由上可知 id=11 为触摸板 Device \u0026#39;ELAN2301:00 04F3:306B Touchpad\u0026#39;: Device Enabled (189):\t1 Coordinate Transformation Matrix (191):\t1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000 libinput Tapping Enabled (327):\t1 libinput Tapping Enabled Default (328):\t0 libinput Tapping Drag Enabled (329):\t1 libinput Tapping Drag Enabled Default (330):\t1 libinput Tapping Drag Lock Enabled (331):\t0 libinput Tapping Drag Lock Enabled Default (332):\t0 libinput Tapping Button Mapping Enabled (333):\t1, 0 libinput Tapping Button Mapping Default (334):\t1, 0 libinput Natural Scrolling Enabled (335):\t1 libinput Natural Scrolling Enabled Default (336):\t0 libinput Disable While Typing Enabled (337):\t1 libinput Disable While Typing Enabled Default (338):\t1 libinput Scroll Methods Available (339):\t1, 1, 0 libinput Scroll Method Enabled (340):\t1, 0, 0 libinput Scroll Method Enabled Default (341):\t1, 0, 0 libinput Click Methods Available (342):\t1, 1 libinput Click Method Enabled (343):\t0, 1 libinput Click Method Enabled Default (344):\t1, 0 libinput Middle Emulation Enabled (345):\t0 libinput Middle Emulation Enabled Default (346):\t0 libinput Accel Speed (347):\t0.000000 libinput Accel Speed Default (348):\t0.000000 libinput Accel Profiles Available (349):\t1, 1 libinput Accel Profile Enabled (350):\t1, 0 libinput Accel Profile Enabled Default (351):\t1, 0 libinput Left Handed Enabled (352):\t0 libinput Left Handed Enabled Default (353):\t0 libinput Send Events Modes Available (312):\t1, 1 libinput Send Events Mode Enabled (313):\t0, 0 libinput Send Events Mode Enabled Default (314):\t0, 0 Device Node (315):\t\u0026#34;/dev/input/event6\u0026#34; Device Product ID (316):\t1267, 12395 libinput Drag Lock Buttons (354):\t\u0026lt;no items\u0026gt; libinput Horizontal Scroll Enabled (355):\t1 libinput Scrolling Pixel Distance (356):\t15 libinput Scrolling Pixel Distance Default (357):\t15 libinput High Resolution Wheel Scroll Enabled (358):\t1 发现驱动已经成功更换为libinput,并且\n1 libinput Horizontal Scroll Enabled (355):\t1 表明已成功开启水平滚动。\nLibinput-gestures libinput-gestures是一个脚本工具,它可以接收libinput的 event 并作出相应的 action,进而达到手势操作的目地。具体可参考 3.\nReferences ArchWiki: Touchpad Synaptics ArchWiki: Libinput 对 Linux 下触控板按键、加速和手势的优化(libinput) Linux 下 MacBook 触摸板设置 ","permalink":"https://guyueshui.github.io/post/touchpad-horiz-scroll/","tags":[],"title":"Linux 笔记本触摸板水平滚动问题"},{"categories":["tech"],"contents":"考虑如下代码:\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 def async_call(it, ret_list=None): try: value = ret_list[0] if ret_list and len(ret_list) == 1 else ret_list arg_list = it.send(value) except StopIteration: return if type(arg_list) in (list, tuple): imp_func, args = arg_list[0], list(arg_list[1:]) else: imp_func, args = arg_list, [] callback = lambda *cb_args: async_call(it, cb_args) imp_func(*args, callback=callback) def make_async(func): def _wrapper(*args, **kwargs): async_call(func(*args, **kwargs)) return _wrapper def fd(_idx, callback): print(\u0026#34;fd(%s, %s)\u0026#34; % (_idx, callback)) # return \u0026#39;EOF\u0026#39; callback(\u0026#39;fd:%s\u0026#39; % _idx) @make_async def fb(_idx, callback): print(\u0026#34;fb(%s, %s)\u0026#34; % (_idx, callback)) ret = yield fd, _idx callback(\u0026#39;fb:%s\u0026#39; % ret) def fc(_idx, callback): print(\u0026#34;fc(%s, %s)\u0026#34; % (_idx, callback)) callback(\u0026#39;fc:%s\u0026#39; % _idx) @make_async def fa(*args, **kwargs): print(\u0026#34;fa(%s, %s)\u0026#34; % (args, kwargs)) for idx in range(2): if idx % 2 == 0: f = fb else: f = fc ret = yield f, idx print(\u0026#34;%sth iteration: ret in fa is %s\u0026#34; % (idx, ret)) if __name__ == \u0026#39;__main__\u0026#39;: fa() 以上代码的运行结果为:\nfa((), {}) fb(0, \u0026lt;function async_call.\u0026lt;locals\u0026gt;.\u0026lt;lambda\u0026gt; at 0x7fb2070263b0\u0026gt;) fd(0, \u0026lt;function async_call.\u0026lt;locals\u0026gt;.\u0026lt;lambda\u0026gt; at 0x7fb207026440\u0026gt;) 0th iteration: ret in fa is fb:fd:0 fc(1, \u0026lt;function async_call.\u0026lt;locals\u0026gt;.\u0026lt;lambda\u0026gt; at 0x7fb2070264d0\u0026gt;) 1th iteration: ret in fa is fc:1 试着分析上述输出:\nfa本来是个 generator,在 decorator 的作用下(decorator 首先调用了it.send(None))被激活,fa.print句输出 fa执行到 yield,此时f=fb,于是程序跳转到fb,但fb也是个 generator,没关系,同样在 decorator 的作用下被激活,于是fb.print句输出 fb执行到 yield,程序跳转到fd(普通函数),于是fd.print句输出 fd执行到 callback,这个 callback 是啥呢,暂且相信它是lambda *cb_args: async_call(it_of_fb, cb_args),所以callback('fd:0')展开为async_call(it_of_fb, 'fd:0'),然后async_call执行到it_of_fb.send('fd:0'),这就驱使 generator fb从 yield 处(紧随其后)开始继续执行;然后控制流来到了fb中的callback('fb:fd:0'),这个 callback 是谁呢?暂且相信它是lambda *cb_args: async_call(it_of_fa, cb_args),所以展开为async_call(it_of_fa, 'fb:fd:0'),这就驱使 generator fa从 yield 处 resume,fa.print句输出 fa再入循环,此时idx=1, f=fc,执行到 yield,控制流跳转到fc(一个普通函数),很好,于是fc.print句输出 fc执行到 callback,很好,想必大家都知道这个 callback 是lambda *cb_args: async_call(it_if_fa, cb_args),展开为async_call(it_of_fa, 'fc:1'),这就驱使 generator fa从 yield 处 resume,并接收到ret='fc:1',fa.print句输出 接着开始退栈,首先 generator fa迭代结束,抛出异常被async_call捕获并结束;然后别忘了我们从何而来,我们从fc.callback而来,callback 执行结束,fc退栈;而我们从哪里执行到fc的呢,我们从fb中的 callback 通过 generator 的控制流乱窜到fc,现在他执行完了,也就是说fb.callback执行完了,fb抛出异常被async_call捕获并结束;我们从哪里来到fb.callback呢,从fd.callback,于是fd结束,退栈。 可以看到,退栈顺序并不是按照进入顺序的逆序而来的。这是因为控制流在yield和generator.send之间反复横跳的缘故。\n如果将 23 行(fd 中 return 句)注释去掉,则运行结果为:\nfa((), {}) fb(0, \u0026lt;function async_call.\u0026lt;locals\u0026gt;.\u0026lt;lambda\u0026gt; at 0x7fd1750b5ea0\u0026gt;) fd(0, \u0026lt;function async_call.\u0026lt;locals\u0026gt;.\u0026lt;lambda\u0026gt; at 0x7fd1750b6050\u0026gt;) 试着分析一下:\n同上 同上 同上 控制流来到fd,但这时,fd不走 callback,而是直接 return 了。而我们是从哪里进到fd的呢,是从fb中的 yield 句,其实执行 yield,会将控制流返回到async_call中的it.send(value)句(紧随其后),然后走到async_call的最后一句,开始执行fd,然后fd结束,然后async_call结束,然后上一层async_call结束,\u0026hellip;, 接着整个程序结束。fayield 之后的代码根本不会执行到。因为底下人不配合它(不调用 callback,进而引起上层函数调不到 callback,进而引起 generator 无法驱动),程序看起来就像夭折了一样。 读者试着思考一下,是否能够模拟程序执行流程?(上面暂时看不懂没关系,看完下文,再回头看应该会更好理解一些。)\n为了搞清楚这段代码的执行流程,我们必须先搞清楚一些概念。\nGenerator 简介 在 python 中,generator 的通俗理解是:一个函数如果含有 yield 语句,则称这个函数是一个 generator function,对该函数的调用生成一个 generator.\nGenerator it生成之后,不会立刻执行,除非对其迭代(使用next(it),for循环遍历等)。并且生成器每次执行到 yield 语句都会挂起,并将 yield 之后的表达式返回给调用者,直到再次迭代,会从 yield 语句之后继续执行。\n更多概念参考:https://docs.python.org/3/glossary.html#term-generator\nGenerator.send Generator 有一个重要的方法:generator.send(value). 它可以恢复 generator 的执行并且给 generator function 内部发送一个 value. 具体参见 相关文档,注意send和next的区别。\n下面给出一个例子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 \u0026gt;\u0026gt;\u0026gt; def a(): ... i = 0 ... while i \u0026lt; 3: ... x = yield i ... i += 1 ... print(\u0026#34;after yield: x=%s, i=%s\u0026#34; % (x, i)) ... \u0026gt;\u0026gt;\u0026gt; it = a() \u0026gt;\u0026gt;\u0026gt; it.send(None) 0 \u0026gt;\u0026gt;\u0026gt; it.send(11) after yield: x=11, i=1 1 \u0026gt;\u0026gt;\u0026gt; it.send(22) after yield: x=22, i=2 2 \u0026gt;\u0026gt;\u0026gt; it.send(33) after yield: x=33, i=3 Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; StopIteration \u0026gt;\u0026gt;\u0026gt; 官方描述it.send(None)等价于next(it)。且看上述代码,\n第一次 send(None) 为激活 generator,此时 generator 执行到 yield,并将 i(=0) 返回 第二次 send(11),恢复 generator 的执行,并将 11 发送给 generator function,赋值给 x,于是打印出 x=11, i=1 第三次 send(22),恢复 generator 的执行,并将 22 发送给 generator function,赋值给 x,于是打印出 x=22, i=2 第四次 send(33),恢复 generator 的执行,并将 22 发送给 generator function,赋值给 x,于是打印出 x=33, i=3, 但由于此时 generator 已经不会再产生新值,亦即正常退出,于是 send 函数抛出 StopIteration 异常 注意上述代码最后一次执行it.send(33),可以看到,print函数成功打印出结果,此时i=3,不再进入循环,“函数正常”退出。但那仅仅是针对常规函数,对于 generator,如果不再产生新值,会抛出一个StopIteration的异常。\nPEP 342 提到: The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.\nGenerator 及其 send 方法是我们读懂文首代码的两个基本点,其中所有控制流跳变的地方都有他俩的身影。猛击此处获取源文件。\n利用 generator 和 send 实现的协程 to be continued\u0026hellip;\nReferences Python doc: yield expressions 廖雪峰 - 协程 PEP 342 – Coroutines via Enhanced Generators ","permalink":"https://guyueshui.github.io/post/python-coroutine-with-yield/","tags":["python","coroutine"],"title":"使用 Yield 实现 Python 协程"},{"categories":["Notes"],"contents":"Create class dynamically Python doc says:\nBy default, classes are constructed using type(). The class body is executed in a new namespace and the class name is bound locally to the result of type(name, bases, namespace).\nThat\u0026rsquo;s means, a class statement is equivalent to the call of type method with three arguments:\nname: name of the class bases: tuple of the parent class (for inheritance, can be empty) attrs: dictionary containing attributes names and values. For example, the following classes are identical:\n1 2 3 4 5 6 class A(object): def __init__(self): self.a = 1 tmp = type(\u0026#39;A\u0026#39;, (object,), {\u0026#39;a\u0026#39;: 1}) A = tmp as verified by the picture below: The type function is special:\nWith one argument, return the type of an object. The return value is a type object and generally the same object as returned by object.__class__.\nWith three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute. The bases tuple contains the base classes and becomes the __bases__ attribute; if empty, object, the ultimate base of all classes, is added. The dict dictionary contains attribute and method definitions for the class body; it may be copied or wrapped before becoming the __dict__ attribute.\nIn other words, type is the factory method creating python classes.\nThe class creation process The class creation process can be customized by passing the metaclass keyword argument in the class definition line, or by inheriting from an existing class that included such an argument. In the following example, both MyClass and MySubclass are instances of Meta:\n1 2 3 4 5 6 7 8 class Meta(type): pass class MyClass(metaclass=Meta): pass class MySubclass(MyClass): pass Any other keyword arguments that are specified in the class definition are passed through to all metaclass operations described below.\nWhen a class definition is executed, the following steps occur:\nMRO entries are resolved; the appropriate metaclass is determined; the class namespace is prepared; the class body is executed; the class object is created. Here comes our leading role: metaclass, the following is captured from what are metaclasses in python:\nMetaclasses are the \u0026lsquo;stuff\u0026rsquo; that creates classes.\nYou define classes in order to create objects, right?\nBut we learned that Python classes are objects.\nWell, metaclasses are what create these objects. They are the classes\u0026rsquo; classes, you can picture them this way:\n1 2 MyClass = MetaClass() my_object = MyClass() You\u0026rsquo;ve seen that type lets you do something like this:\n1 MyClass = type(\u0026#39;MyClass\u0026#39;, (), {}) It\u0026rsquo;s because the function type is in fact a metaclass. type is the metaclass Python uses to create all classes behind the scenes.\nEverything, and I mean everything, is an object in Python. That includes integers, strings, functions and classes. All of them are objects. And all of them have been created from a class:\n1 2 3 4 5 6 7 8 9 10 11 12 13 \u0026gt;\u0026gt;\u0026gt; age = 35 \u0026gt;\u0026gt;\u0026gt; age.__class__ \u0026lt;type \u0026#39;int\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; name = \u0026#39;bob\u0026#39; \u0026gt;\u0026gt;\u0026gt; name.__class__ \u0026lt;type \u0026#39;str\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; def foo(): pass \u0026gt;\u0026gt;\u0026gt; foo.__class__ \u0026lt;type \u0026#39;function\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; class Bar(object): pass \u0026gt;\u0026gt;\u0026gt; b = Bar() \u0026gt;\u0026gt;\u0026gt; b.__class__ \u0026lt;class \u0026#39;__main__.Bar\u0026#39;\u0026gt; Now, what is the __class__ of any __class__ ?\n1 2 3 4 5 6 7 8 \u0026gt;\u0026gt;\u0026gt; age.__class__.__class__ \u0026lt;type \u0026#39;type\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; name.__class__.__class__ \u0026lt;type \u0026#39;type\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; foo.__class__.__class__ \u0026lt;type \u0026#39;type\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; b.__class__.__class__ \u0026lt;type \u0026#39;type\u0026#39;\u0026gt; So, a metaclass is just the stuff that creates class objects.\nYou can call it a \u0026lsquo;class factory\u0026rsquo; if you wish.\ntype is the built-in metaclass Python uses, but of course, you can create your own metaclass.\nUse metaclass First we see an example:\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 class MyMeta(type): # __new__ is the method called before __init__ # it\u0026#39;s the method that creates the object and returns it # while __init__ just initializes the object passed as parameter # you rarely use __new__, except when you want to control how the object # is created. # here the created object is the class, and we want to customize it # so we override __new__ # you can do some stuff in __init__ too if you wish # some advanced use involves overriding __call__ as well. def __new__(cls, cls_name:str, bases:tuple, attrs:dict, **kwargs): new_attrs = {} for k, v in attrs.items(): if not k.startswith(\u0026#39;__\u0026#39;): key = k.upper() print(\u0026#39;modify attr: %s -\u0026gt; %s\u0026#39; % (k, key)) new_attrs[key] = v else: new_attrs[k] = v return type.__new__(cls, cls_name, bases, new_attrs) def __call__(self, *args, **kwds) -\u0026gt; Any: new_args = [x * x for x in args] return super().__call__(*new_args, **kwds) class D(object, metaclass=MyMeta, foo=1, bar=2): aaa = 1 bbb = 2 def __init__(self, a, b) -\u0026gt; None: self.a = a self.b = b if __name__ == \u0026#39;__main__\u0026#39;: d = D(3, 4) print(d) we see the memory when hit the following breakpoint, From the picture we see:\nthe class name \u0026lsquo;D\u0026rsquo; is passed as parameter cls_name of MyMeta.__new__, the class variables of D is passed as parameter attrs of MyMeta.__new__, the keyword arguments of D \u0026ndash; foo and bar are passed as keyword arguments of MyMeta.__new__. The next breakpoint: gives\nthe self variable passed to MyMeta.__call__ is just the class D, the D(3, 4) pass 3, 4 to parameter args of MyMeta.__call__. The last breakpoint gives the memory of instance d, the class D has class attributes \u0026lsquo;AAA\u0026rsquo; and \u0026lsquo;BBB\u0026rsquo;, which are converted to uppercase in MyMeta.__new__, the instance d has instance attributes \u0026lsquo;a=9\u0026rsquo; and \u0026lsquo;b=16\u0026rsquo;, which are processed in MyMeta.__call__, console outputs the log of uppercase conversion. Last word: i highly recommend you to read the document of obj.__new__ and obj.__init__, and to be continued\u0026hellip;\nReferences What are metaclasses in python Python documents Class customizations ","permalink":"https://guyueshui.github.io/post/python-metaclass/","tags":["python"],"title":"A point of python metaclass"},{"categories":["Notes"],"contents":"学生时代曾为整个课题组的师生搭建过一个梯子,稳定运行两年多,最近突然爬不上去了。 寻思是哪里出了问题,经过一番定位,原来是之前的免费域名到期了。遂于昨晚开启修补 之旅,无奈运气不太好,每一环节都出了问题,最终搞到凌晨 3 点才重新爬上了梯子。\n想来主要是之前对照的教程遗失,整个流程又有很多细节,难免忘记,遂记此篇。(顺便 吐槽如今网上教程已经不胜枚举,但优质的教程却少有输出。以至于想要弄清楚某个事情 的来龙去脉,就必须博采众家所长,取其精华,弃其糟粕。有没有一个教程站出来拍拍 胸脯说:少年,你只要看我就够了 Orz!当然,此篇仅是个笔记,不为服务他人,只为提醒 自己。)\nV2Ray 是一个较为先进的网络工具,他的用途很多,但大多数人用它来搞建筑。原理就是 你有一台可访问外网的机器 A,你与该机器可以通信,在 A 中运行一个 v2ray(充当服务端), 在终端设备运行一个 v2ray(充当客户端),然后两方配制能对得上,就可以将你访问外网 的流量转发给机器 A,由 A 发出该请求,收到回复后还是通过 A 转发回给终端机。这就完成了 一次外网访问。\nv2ray 的难点在于配置文件1的编写,不过好在现在有很多辅助你生成配置文件的工具。 咱当初可是辛辛苦苦对着文档敲的,不贴上来太可惜了:\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 /// config_server.json { \u0026#34;log\u0026#34;: { \u0026#34;access\u0026#34;: \u0026#34;/var/log/v2ray/access.log\u0026#34;, \u0026#34;error\u0026#34;: \u0026#34;/var/log/v2ray/error.log\u0026#34;, \u0026#34;loglevel\u0026#34;: \u0026#34;warning\u0026#34; }, \u0026#34;dns\u0026#34;: {}, \u0026#34;stats\u0026#34;: {}, \u0026#34;inbounds\u0026#34;: [ { \u0026#34;port\u0026#34;: 443, \u0026#34;protocol\u0026#34;: \u0026#34;vmess\u0026#34;, \u0026#34;settings\u0026#34;: { \u0026#34;clients\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;your_uuid\u0026#34;, \u0026#34;alterId\u0026#34;: 32 } ] }, \u0026#34;tag\u0026#34;: \u0026#34;in-0\u0026#34;, \u0026#34;streamSettings\u0026#34;: { \u0026#34;network\u0026#34;: \u0026#34;ws\u0026#34;, \u0026#34;security\u0026#34;: \u0026#34;tls\u0026#34;, \u0026#34;wsSettings\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;/somepath\u0026#34;, \u0026#34;headers\u0026#34;: { \u0026#34;host\u0026#34;: \u0026#34;somehost.com\u0026#34; } }, \u0026#34;tlsSettings\u0026#34;: { \u0026#34;certificates\u0026#34;: [ { \u0026#34;certificateFile\u0026#34;: \u0026#34;/etc/v2ray/v2ray.crt\u0026#34;, \u0026#34;keyFile\u0026#34;: \u0026#34;/etc/v2ray/v2ray.key\u0026#34; } ] } } } ], \u0026#34;outbounds\u0026#34;: [ { \u0026#34;tag\u0026#34;: \u0026#34;direct\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;freedom\u0026#34;, \u0026#34;settings\u0026#34;: {} }, { \u0026#34;tag\u0026#34;: \u0026#34;blocked\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;blackhole\u0026#34;, \u0026#34;settings\u0026#34;: {} } ], \u0026#34;routing\u0026#34;: { \u0026#34;domainStrategy\u0026#34;: \u0026#34;AsIs\u0026#34;, \u0026#34;rules\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;field\u0026#34;, \u0026#34;ip\u0026#34;: [ \u0026#34;geoip:private\u0026#34; ], \u0026#34;outboundTag\u0026#34;: \u0026#34;blocked\u0026#34; } ] }, \u0026#34;policy\u0026#34;: {}, \u0026#34;reverse\u0026#34;: {}, \u0026#34;transport\u0026#34;: {} } 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 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 /// config_client.json { \u0026#34;log\u0026#34;:{}, \u0026#34;dns\u0026#34;:{}, \u0026#34;stats\u0026#34;:{}, \u0026#34;inbounds\u0026#34;:[ { \u0026#34;port\u0026#34;:\u0026#34;1080\u0026#34;, \u0026#34;protocol\u0026#34;:\u0026#34;socks\u0026#34;, \u0026#34;settings\u0026#34;:{ \u0026#34;auth\u0026#34;:\u0026#34;noauth\u0026#34;, \u0026#34;udp\u0026#34;:true }, \u0026#34;tag\u0026#34;:\u0026#34;in-0\u0026#34; }, { \u0026#34;port\u0026#34;:\u0026#34;8080\u0026#34;, \u0026#34;protocol\u0026#34;:\u0026#34;http\u0026#34;, \u0026#34;settings\u0026#34;:{}, \u0026#34;tag\u0026#34;:\u0026#34;in-1\u0026#34; } ], \u0026#34;outbounds\u0026#34;:[ { \u0026#34;protocol\u0026#34;:\u0026#34;vmess\u0026#34;, \u0026#34;settings\u0026#34;:{ \u0026#34;vnext\u0026#34;:[ { \u0026#34;address\u0026#34;:\u0026#34;your_host_address\u0026#34;, \u0026#34;port\u0026#34;:443, \u0026#34;users\u0026#34;:[ { \u0026#34;id\u0026#34;:\u0026#34;your_uuid\u0026#34;, \u0026#34;alterId\u0026#34;:32 } ] } ] }, \u0026#34;tag\u0026#34;:\u0026#34;out-0\u0026#34;, \u0026#34;streamSettings\u0026#34;:{ \u0026#34;network\u0026#34;:\u0026#34;ws\u0026#34;, \u0026#34;security\u0026#34;:\u0026#34;tls\u0026#34;, \u0026#34;wsSettings\u0026#34;:{ \u0026#34;path\u0026#34;:\u0026#34;/somepath\u0026#34;, \u0026#34;headers\u0026#34;: { \u0026#34;host\u0026#34;: \u0026#34;somehost.com\u0026#34; } }, \u0026#34;tlsSettings\u0026#34;:{ \u0026#34;allowInsecure\u0026#34;: true } } }, { \u0026#34;tag\u0026#34;:\u0026#34;direct\u0026#34;, \u0026#34;protocol\u0026#34;:\u0026#34;freedom\u0026#34;, \u0026#34;settings\u0026#34;:{} }, { \u0026#34;tag\u0026#34;:\u0026#34;blocked\u0026#34;, \u0026#34;protocol\u0026#34;:\u0026#34;blackhole\u0026#34;, \u0026#34;settings\u0026#34;:{} } ], \u0026#34;routing\u0026#34;:{ \u0026#34;domainStrategy\u0026#34;:\u0026#34;IPOnDemand\u0026#34;, \u0026#34;rules\u0026#34;:[ { \u0026#34;type\u0026#34;:\u0026#34;field\u0026#34;, \u0026#34;ip\u0026#34;:[ \u0026#34;geoip:private\u0026#34; ], \u0026#34;outboundTag\u0026#34;:\u0026#34;direct\u0026#34; }, { \u0026#34;type\u0026#34;:\u0026#34;field\u0026#34;, \u0026#34;ip\u0026#34;:[ \u0026#34;geoip:cn\u0026#34; ], \u0026#34;inboundTag\u0026#34;:[ \u0026#34;in-0\u0026#34;, \u0026#34;in-1\u0026#34; ], \u0026#34;outboundTag\u0026#34;:\u0026#34;direct\u0026#34; } ] }, \u0026#34;policy\u0026#34;:{}, \u0026#34;reverse\u0026#34;:{}, \u0026#34;transport\u0026#34;:{} } 仔细看看 server 的配置,其实用到了 WS+TLS 的方式,这样的配置隐蔽性较好,不容易被封。 但这种配置的成本也相对较高,首先得弄个域名和机器 A 的 ip 绑定。即通过ping somehost.com能够翻译成机器 A 的 ip。\n注册域名 上免费域名注册站:https://my.freenom.com/domains.php,\n随便敲一个域名,检查是否可用,注册成功后如下图所示:\n此处需要为域名配置解析服务器,可以用腾讯云 域名解析服务,下图中点击“添加域名”,配置成功后会得到两个域名解析服务器地址,将这两个地址填入上图的 Nameserver1 和 Nameserver2 中即可。\n至此,一个域名到 ip 的绑定关系就配置完成,可以 ping 一下试试:\n1 2 3 4 5 6 me@~\u0026gt; ping somehost.com PING somehost.com (xxx.yyy.zzz.aaa) 56(84) 字节的数据。 64 字节,来自 xx.com (xxx.yyy.zzz.aaa): icmp_seq=1 ttl=52 时间=193 毫秒 --- somehost.com ping 统计 --- 已发送 4 个包, 已接收 4 个包,0% packet loss, time 3003ms rtt min/avg/max/mdev = 159.025/202.023/239.194/29.721 ms 其中,somehost.com为之前申请的域名,xxx.yyy.zzz.aaa为机器 A 的 ip。\n证书生成 此段摘抄自 https://guide.v2fly.org/advanced/tls.html#证书生成\nTLS 是证书认证机制,所以使用 TLS 需要证书,证书也有免费付费的,同样的这里使用免费证书,证书认证机构为 Let\u0026rsquo;s Encrypt。证书的生成有许多方法,这里使用的是比较简单的方法:使用 acme.sh 脚本生成,本部分说明部分内容参考于 acme.sh README。\n证书有两种,一种是 ECC 证书(内置公钥是 ECDSA 公钥),一种是 RSA 证书(内置 RSA 公钥)。简单来说,同等长度 ECC 比 RSA 更安全,也就是说在具有同样安全性的情况下,ECC 的密钥长度比 RSA 短得多(加密解密会更快)。但问题是 ECC 的兼容性会差一些,Android 4.x 以下和 Windows XP 不支持。只要您的设备不是非常老的老古董,建议使用 ECC 证书。\n以下只给出 ECC 证书的部分。\n证书生成只需在服务器上操作。\n安装 acme.sh 执行以下命令,acme.sh 会安装到 ~/.acme.sh 目录下。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ curl https://get.acme.sh | sh % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 671 100 671 0 0 680 0 --:--:-- --:--:-- --:--:-- 679 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 112k 100 112k 0 0 690k 0 --:--:-- --:--:-- --:--:-- 693k [Fri 30 Dec 01:03:32 GMT 2016] Installing from online archive. [Fri 30 Dec 01:03:32 GMT 2016] Downloading https://github.com/Neilpang/acme.sh/archive/master.tar.gz [Fri 30 Dec 01:03:33 GMT 2016] Extracting master.tar.gz [Fri 30 Dec 01:03:33 GMT 2016] Installing to /home/user/.acme.sh [Fri 30 Dec 01:03:33 GMT 2016] Installed to /home/user/.acme.sh/acme.sh [Fri 30 Dec 01:03:33 GMT 2016] Installing alias to \u0026#39;/home/user/.profile\u0026#39; [Fri 30 Dec 01:03:33 GMT 2016] OK, Close and reopen your terminal to start using acme.sh [Fri 30 Dec 01:03:33 GMT 2016] Installing cron job no crontab for user no crontab for user [Fri 30 Dec 01:03:33 GMT 2016] Good, bash is found, so change the shebang to use bash as preferred. [Fri 30 Dec 01:03:33 GMT 2016] OK [Fri 30 Dec 01:03:33 GMT 2016] Install success! 如果安装报错,那么可能是因为系统缺少 acme.sh 所需要的依赖项,acme.sh 的依赖项主要是 socat,我们通过以下命令来安装这些依赖项,然后重新安装一遍 acme.sh:\n1 $ sudo apt-get install openssl cron socat curl 使用 acme.sh 生成证书 证书生成 执行以下命令生成证书:\n以下的命令会临时监听 80 端口,请确保执行该命令前 80 端口没有使用\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 $ ~/.acme.sh/acme.sh --issue -d mydomain.me --standalone --keylength ec-256 --force [Fri Dec 30 08:59:12 HKT 2016] Standalone mode. [Fri Dec 30 08:59:12 HKT 2016] Single domain=\u0026#39;mydomain.me\u0026#39; [Fri Dec 30 08:59:12 HKT 2016] Getting domain auth token for each domain [Fri Dec 30 08:59:12 HKT 2016] Getting webroot for domain=\u0026#39;mydomain.me\u0026#39; [Fri Dec 30 08:59:12 HKT 2016] _w=\u0026#39;no\u0026#39; [Fri Dec 30 08:59:12 HKT 2016] Getting new-authz for domain=\u0026#39;mydomain.me\u0026#39; [Fri Dec 30 08:59:14 HKT 2016] The new-authz request is ok. [Fri Dec 30 08:59:14 HKT 2016] mydomain.me is already verified, skip. [Fri Dec 30 08:59:14 HKT 2016] mydomain.me is already verified, skip http-01. [Fri Dec 30 08:59:14 HKT 2016] mydomain.me is already verified, skip http-01. [Fri Dec 30 08:59:14 HKT 2016] Verify finished, start to sign. [Fri Dec 30 08:59:16 HKT 2016] Cert success. -----BEGIN CERTIFICATE----- MIIEMTCCAxmgAwIBAgISA1+gJF5zwUDjNX/6Xzz5fo3lMA0GCSqGSIb3DQEBCwUA MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xNjEyMjkyMzU5MDBaFw0x NzAzMjkyMzU5MDBaMBcxFTATBgNVBAMTDHdlYWtzYW5kLmNvbTBZMBMGByqGSM49 **************************************************************** 4p40tm0aMB837XQ9jeAXvXulhVH/7/wWZ8/vkUUvuHSCYHagENiq/3DYj4a85Iw9 +6u1r7atYHJ2VwqSamiyTGDQuhc5wdXIQxY/YQQqkAmn5tLsTZnnOavc4plANT40 zweiG8vcIvMVnnkM0TSz8G1yzv1nOkruN3ozQkLMu6YS7lk/ENBN7DBtYVSmJeU2 VAXE+zgRaP7JFOqK6DrOwhyE2LSgae83Wq/XgXxjfIo1Zmn2UmlE0sbdNKBasnf9 gPUI45eltrjcv8FCSTOUcT7PWCa3 -----END CERTIFICATE----- [Fri Dec 30 08:59:16 HKT 2016] Your cert is in /root/.acme.sh/mydomain.me_ecc/mydomain.me.cer [Fri Dec 30 08:59:16 HKT 2016] Your cert key is in /root/.acme.sh/mydomain.me_ecc/mydomain.me.key [Fri Dec 30 08:59:16 HKT 2016] The intermediate CA cert is in /root/.acme.sh/mydomain.me_ecc/ca.cer [Fri Dec 30 08:59:16 HKT 2016] And the full chain certs is there: /root/.acme.sh/mydomain.me_ecc/fullchain.cer --keylength 表示密钥长度,后面的值可以是 ec-256 、ec-384、2048、3072、4096、8192,带有 ec 表示生成的是 ECC 证书,没有则是 RSA 证书。在安全性上 256 位的 ECC 证书等同于 3072 位的 RSA 证书。\n证书更新 由于 Let\u0026rsquo;s Encrypt 的证书有效期只有 3 个月,因此需要 90 天至少要更新一次证书,acme.sh 脚本会每 60 天自动更新证书。也可以手动更新。\n手动更新证书,执行:\n1 $ ~/.acme.sh/acme.sh --renew -d mydomain.com --force --ecc 由于本例中将证书生成到 /etc/v2ray/ 文件夹,更新证书之后还得把新证书生成到 /etc/v2ray。\n安装证书和密钥 将证书和密钥安装到 /etc/v2ray 中:\n1 2 3 $ sudo ~/.acme.sh/acme.sh --installcert -d mydomain.me --ecc \\ --fullchain-file /etc/v2ray/v2ray.crt \\ --key-file /etc/v2ray/v2ray.key 注意:无论什么情况,密钥 (即上面的 v2ray.key) 都不能泄漏,如果你不幸泄漏了密钥,可以使用 acme.sh 将原证书吊销,再生成新的证书,吊销方法请自行参考 acme.sh 的手册\n摘抄完毕。\n证书生成(Re) 给一个域名添加证书:\n使用 ACME.SH 申请并安装 Let’s Encrypt SSL 证书 使用 acme.sh 免费申请 HTTPS 证书2 局域网内搭建浏览器可信任的 SSL 证书 How to use DNS API - acme.sh wiki 启动 在机器 A 上执行\n1 2 3 4 5 6 $ /usr/bin/v2ray/v2ray V2Ray 4.44.0 (V2Fly, a community-driven edition of V2Ray.) Custom (go1.17.3 linux/amd64) A unified platform for anti-censorship. 2021/12/11 00:22:25 Using config from env: /usr/bin/v2ray/config.json 2021/12/11 00:22:25 [Info] main/jsonem: Reading config: /usr/bin/v2ray/config.json 2021/12/11 00:22:26 [Warning] V2Ray 4.44.0 started 但是这样会占用终端,虽然可以让它后台运行,但始终不优雅。我们可以将 v2ray 做成一项 system service.\n编写服务单元文件3:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # file:///etc/systemd/system/v2ray.service [Unit] Description=V2Ray Service After=network.target Wants=network.target [Service] # This service runs as root. You may consider to run it as another user for security concerns. # By uncommenting the following two lines, this service will run as user v2ray/v2ray. # More discussion at https://github.com/v2ray/v2ray-core/issues/1011 # User=v2ray # Group=v2ray Type=simple PIDFile=/run/v2ray.pid Environment=V2RAY_LOCATION_ASSET=/etc/v2ray ExecStart=/usr/bin/v2ray/v2ray -config /etc/v2ray/config.json Restart=on-failure # Don\u0026#39;t restart in the case of configuration error RestartPreventExitStatus=23 [Install] WantedBy=multi-user.target 这样一来可以使用systemctl来管理其启动和关闭,使用\n1 2 3 4 5 systemctl start v2ray # 启动 systemctl restart v2ray # 重新启动 systemclt stop v2ray # 停止 systemctl enable/disable v2ray # 设置是否开机启动 systemctl status v2ray # 查看服务运行情况 暂时写到这里,客户端连接,以及服务器安装等,网上教程较多,不再赘述。因原教程被 ban,故摘录部分,有条件的推荐去看原教程,十分详细!\nTroubleshooting Failed to dial WebSocket V2Ray 5.3.0 (V2Fly, a community-driven edition of V2Ray.) Custom (go1.20 linux/amd64) A unified platform for anti-censorship. 2023/02/24 10:12:47 [Warning] V2Ray 5.3.0 started 2023/02/24 10:13:00 [Warning] [3406089800] app/dispatcher: default route for tcp:www.google.com:443 2023/02/24 10:13:00 tcp:127.0.0.1:59658 accepted tcp:www.google.com:443 [out-0] 2023/02/24 10:13:31 [Warning] [3395754372] app/dispatcher: default route for tcp:www.google.com:443 2023/02/24 10:13:31 tcp:127.0.0.1:54836 accepted tcp:www.google.com:443 [out-0] 2023/02/24 10:13:33 [Warning] [3406089800] app/proxyman/outbound: failed to process outbound traffic \u0026gt; proxy/vmess/outbound: failed to find an available destination \u0026gt; common/retry: [transport/internet/websocket: failed to dial WebSocket \u0026gt; transport/internet/websocket: failed to dial to (wss://xxx.xxx.xxx.xxx/abc): \u0026gt; transport/internet/websocket: dial TLS connection failed \u0026gt; dial tcp xxx.xxx.xxx.xxx:10086: i/o timeout transport/internet/websocket: failed to dial WebSocket \u0026gt; transport/internet/websocket: failed to dial to (wss:////xxx.xxx.xxx.xxx/abc): \u0026gt; transport/internet/websocket: dial TLS connection failed \u0026gt; dial tcp xxx.xxx.xxx.xxx:10086: operation was canceled] \u0026gt; common/retry: all retry attempts failed 参考链接:https://woj.app/7223.html\n此前遇到上述问题,经过排查之后就是端口号无法访问了,也不知道是哪一环出了问题。换了端口号之后就可以了。\n1 2 3 4 5 6 7 8 \u0026#34;outbounds\u0026#34;:[ { \u0026#34;protocol\u0026#34;:\u0026#34;vmess\u0026#34;, \u0026#34;settings\u0026#34;:{ \u0026#34;vnext\u0026#34;:[ { \u0026#34;port\u0026#34;:443, // -\u0026gt; 886 }]}}] References 新 V2Ray 白话文指南 V2Ray 配置文档\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n关于证书的科普写的很好,不懂证书是个啥的可以参考下。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n如何编写一个 Systemd Service\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/build-your-ladder/","tags":[],"title":"V3Ray 的配置笔记"},{"categories":["notes"],"contents":"Python 的迭代器(iterator)、生成器(generator)、可迭代对象(iterable),虽是老生常谈,但我毕竟要记录一下自己的见解,因有此篇。\nIterable 和 Iterator 为了理解 generator,必须先搞清楚 iterable 和 iterator1。\nAn iterable object is an object that implements __iter__, which is expected to return an iterator object. An iterator is an object that implements __next__, which is expected to return the next element of the iterable object that returned it, and raise a StopIteration exception when no more elements are available. 讲真,iterable 和 iterator 的定义就是这么的朴实无华。但要彻底理解,还需费些功夫。先来看个例子:\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 class MyIterator(object): def __init__(self, data): self.data = data self.cur = 0 def __next__(self): if self.cur \u0026lt; len(self.data): self.cur += 1 return self.data[self.cur - 1] else: raise StopIteration class MyIterable(object): data = [4,3,2,1] def __iter__(self): return MyIterator(self.data) my_iterable = MyIterable() for x in my_iterable: print(x) \u0026gt;\u0026gt;\u0026gt; 4 \u0026gt;\u0026gt;\u0026gt; 3 \u0026gt;\u0026gt;\u0026gt; 2 \u0026gt;\u0026gt;\u0026gt; 1 上面实现了一个 iterable 和 iterator,iterable 必须实现__iter__方法,并返回一个 iterator,在上面的例子中我返回了自己实现的一个 iterator。当然也可以这样写:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class MyIterable(object): data = [4,3,2,1] cur = 0 def __iter__(self): return self def __next__(self): if self.cur \u0026lt; len(self.data): self.cur += 1 return self.data[self.cur - 1] else: raise StopIteration my_iterable = MyIterable() for x in my_iterable: print(x) \u0026gt;\u0026gt;\u0026gt; 4 \u0026gt;\u0026gt;\u0026gt; 3 \u0026gt;\u0026gt;\u0026gt; 2 \u0026gt;\u0026gt;\u0026gt; 1 此时MyIterable既是 iterable 又是 iterator。那既然能够写在一起,为什么聪明的人们要把这两个概念区分开呢?以下2给出了部分解释:\nIterators and iterables can be separate objects, but they don’t have to. Nothing is holding us back here. If you want, you can create a single object that is both an iterator and an iterable. You just need to implement both __iter__ and __next__.\nSo why did the wise men and women building the language decide to split these concepts? It has to do with keeping state. An iterator needs to maintain information on the position, e.g. the pointer into an internal data object like a list. In other words: it must keep track of which element to return next.\nIf the iterable itself maintains that state, you can only use it in one loop at a time. Otherwise, the other loop(s) would interfere with the state of the first loop. By returning a new iterator object, with its own state, we don’t have this problem. This comes in handy especially when you’re working with concurrency.\nFor 循环 Python 中的for x in y要求y为 iterable,具体地,以下两段代码效果相同:\n1 2 3 4 5 6 7 8 9 10 11 for x in y: print(x) # \u0026lt;==\u0026gt; it = iter(y) try: a = next(it) print(a) except StopIteration: break 以上遗漏一点:\nPython expects iterable objects in several different contexts, the most important being the for statement. In the statement for X in Y, Y must be an iterator or some object for which iter() can create an iterator. These two statements are equivalent:\n1 2 3 4 5 for i in iter(obj): print(i) for i in obj: print(i) Note that you can only go forward in an iterator; there’s no way to get the previous element, reset the iterator, or make a copy of it. Iterator objects can optionally provide these additional capabilities, but the iterator protocol only specifies the __next__() method. Functions may therefore consume all of the iterator’s output, and if you need to do something different with the same stream, you’ll have to create a new iterator.\nGenerator Expressions and List Comprehensions 生成器表达式和列表推导式是 Python 中常用的两个语法。列表推导式生成一个列表,生成表达式生成一个 iterator。看下面的例子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def test2(): lst = [1, -2, 3, -4] ge = (abs(x) for x in lst) lc = [abs(x) for x in lst] print(ge, type(ge)) print(lc, type(lc)) print(next(ge), iter(ge)) print(ge.__next__(), ge.__iter__()) for x in ge: print(x) for x in ge: print(x) \u0026gt;\u0026gt;\u0026gt; \u0026lt;generator object test2.\u0026lt;locals\u0026gt;.\u0026lt;genexpr\u0026gt; at 0x7f5221c61f90\u0026gt; \u0026lt;class \u0026#39;generator\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; [1, 2, 3, 4] \u0026lt;class \u0026#39;list\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; 1 \u0026lt;generator object test2.\u0026lt;locals\u0026gt;.\u0026lt;genexpr\u0026gt; at 0x7f5221c61f90\u0026gt; \u0026gt;\u0026gt;\u0026gt; 2 \u0026lt;generator object test2.\u0026lt;locals\u0026gt;.\u0026lt;genexpr\u0026gt; at 0x7f5221c61f90\u0026gt; \u0026gt;\u0026gt;\u0026gt; 3 \u0026gt;\u0026gt;\u0026gt; 4 可以看到,列表推导式直接生成一个列表,而生成表达式则是返回一个 iterator,并且它也是一个 iterable。值得注意的是:\n列表推导式使用[]包围 生成表达式使用()包围 generator 是 iterator iterator 只能遍历一次,当元素耗尽,再次遍历直接抛出 StopIteration 可将 iterator 理解为只能遍历一次的 iterable。\n特别地,以下代码等价:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ( expression for expr in sequence1 if condition1 for expr2 in sequence2 if condition2 ... for exprN in sequenceN if conditionN ) # \u0026lt;==\u0026gt; for expr1 in sequence1: if not (condition1): continue # Skip this element for expr2 in sequence2: if not (condition2): continue # Skip this element ... for exprN in sequenceN: if not (conditionN): continue # Skip this element # Output the value of the expression. Generator Function pass, to be continued\u0026hellip;\nIterator\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPython iterator basics (how they work + examples)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/python-iterables/","tags":["python"],"title":"Python Iterables"},{"categories":["linux"],"contents":"先区分一下两个名词:睡眠(sleep)和休眠(hibernate)。\n睡眠:将工作镜像写入内存(RAM),以便快速恢复。内存读写很快,所以睡眠的特点就是“睡得快”和“醒得快”。对于笔记本来说,合上盖子就睡了,打开盖子你的工作区间即刻就能恢复,很是方便。但是睡眠有一个缺点,就是要给内存供电,一旦断电,你的镜像数据就会丢失,工作区间将不复存在。当然这来自于内存的固有特点,建议百度 RAM。 休眠:将工作镜像写入硬盘(disk,ROM),这样你也可以恢复工作区间。只是睡下去和醒过来的时间比内存慢不少。但是,它有一个好处就是断电了也不会丢失数据。当你再次开机,系统就会从硬盘里面读取镜像,恢复你的工作区间。 作为一个不求甚解的小白,我用 linux 这么些年,一直都只用过睡眠,每天晚上合上笔记本的盖子,第二天早上打开,工作区间即刻恢复,其实也是非常方便的,再也不用忍受关机开机的痛苦。这样一夜下来,大概要耗费 7-8% 的电量,还可以接受不是=。= 但是一旦你很长时间没用电脑,比如说放长假回家了,好久没碰电脑,那么笔记本的电池是会耗尽的,此时你的工作区间就丢了。(当然,这样的情况并不多见)\n其实我以前也是鼓捣过 linux 休眠的,大概 3-4 年前,刚接触 linux 那会儿,在网上一通乱搜,一顿瞎试,未果。现在想来,失败的原因一是当时太菜,而是当时那个电脑太老旧了。据我所知,GPT 分区下搞休眠的坑是比较多的。现在的电脑大都是 EFI 分区,更加简单易用。\n总体来说,休眠还是值得折腾的,因为支持断电!而且现在普遍使用固态硬盘,休眠和恢复的速度也并不是很慢。还有一个很重要的原因,笔记本电池的寿命很短,我的本子买了 3 年了,现在电池容量已经缩水 2/3 了!\n好了,闲话少叙,进入正题。\n确保 swap 分区足够大 拟使用 swap 分区作为写入镜像的目标分区。\n一般建议 swap 分区为本机内存的一半,不过我认为有条件的还是将 swap 分区设置的略大于内存。此处,由于睡眠是将镜像写到内存,要确保 swap 分区能够容得下这个镜像,就必须将 swap 分区设置的大于内存。这并不是说 swap 小于内存就无法休眠了 1,具体还是要看工作区间的镜像大小了。我现在的 swap 就只有本机内存的一半,但还是休眠成功了。\n查看fstab 1 2 3 4 5 6 7 8 9 $ cat /etc/fstab # \u0026lt;file system\u0026gt; \u0026lt;mount point\u0026gt; \u0026lt;type\u0026gt; \u0026lt;options\u0026gt; \u0026lt;dump\u0026gt; \u0026lt;pass\u0026gt; # /dev/nvme0n1p5 UUID=547054ce-bb1b-40e4-a38d-24507d31d5ca / ext4 rw,relatime 0 1 UUID=6E76-7D08 /boot/efi vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 2 # /dev/nvme0n1p7 UUID=4227170f-0a4f-4a8e-a4fd-0d91f46f54af none swap defaults,pri=-0 0 系统启动时会读取该文件,按照其中的描述挂载对应的分区。默认生成的fstab中,swap 分区的类型是swap,将它改为none.\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 $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 223.6G 0 disk ├─sda1 8:1 0 110G 0 part /home/yychi/EXTRA └─sda2 8:2 0 113.6G 0 part nvme0n1 259:0 0 238.5G 0 disk ├─nvme0n1p1 259:1 0 100M 0 part /boot/efi ├─nvme0n1p2 259:2 0 16M 0 part ├─nvme0n1p3 259:3 0 119.5G 0 part ├─nvme0n1p4 259:4 0 798M 0 part ├─nvme0n1p5 259:5 0 65G 0 part / ├─nvme0n1p6 259:6 0 49G 0 part /home └─nvme0n1p7 259:7 0 4.1G 0 part [SWAP] $ sudo blkid -o list device fs_type label mount point UUID ---------------------------------------------------------------------------------------------- /dev/nvme0n1p1 vfat ESP /boot/efi 6E76-7D08 /dev/nvme0n1p2 (未挂载) /dev/nvme0n1p3 ntfs OS (未挂载) CADC772DDC7712C5 /dev/nvme0n1p4 ntfs (未挂载) 624AD5CA4AD59B5D /dev/nvme0n1p5 ext4 ROOT / 547054ce-bb1b-40e4-a38d-24507d31d5ca /dev/nvme0n1p6 ext4 HOME /home 1e23c2e3-6b73-465a-bd60-355b1bc4060b /dev/nvme0n1p7 swap [SWAP] 4227170f-0a4f-4a8e-a4fd-0d91f46f54af /dev/sda1 ext4 DATA /home/yychi/EXTRA e66f87ee-33d8-4aaa-bff0-400df2276ef7 /dev/sda2 ntfs (未挂载) 07B60D0A64472B59 添加恢复分区的内核参数 1 2 3 # 查看当前内核启动命令 $ cat /proc/cmdline \\\\boot\\vmlinuz-linux ro root=/dev/nvme0n1p5 rw resume=/dev/nvme0n1p7 initrd=boot\\initramfs-linux.img 可以看到,内核的启动参数中resume=/dev/nvme0n1p7这一项就指定了从该分区恢复,而该分区正是 swap 分区。\n那么如何修改内核的命令行参数呢?找到你所使用的 boot manager(启动引导)程序,更改相应的配置。我使用的是 rEFInd,需要做的更改为:\n# file: /boot/refind_linux.conf \u0026#34;Boot with standard options\u0026#34; \u0026#34;ro root=/dev/nvme0n1p5 rw resume=/dev/nvme0n1p7\u0026#34; \u0026#34;Boot to single-user mode\u0026#34; \u0026#34;ro root=/dev/nvme0n1p5 single\u0026#34; 直接在第一行最后的参数列表里加上rw resume=/dev/nvme0n1p7即可。Ubuntu 默认使用 grub 作为引导,这个网上教程更为详尽,此处就不再复制粘贴了。\n重新生成启动镜像 作完更改之后,使用\n1 $ mkinitcpio -P 重新生成启动镜像,使更改生效,最后重启系统。\n重新进入系统之后,\n1 2 $ cat /proc/cmdline \\\\boot\\vmlinuz-linux ro root=/dev/nvme0n1p5 rw resume=/dev/nvme0n1p7 initrd=boot\\initramfs-linux.img 如果参数列表里有resume=/dev/nvme0n1p7则说明设置成功。你可以打开一个程序,然后\n1 systemctl hibernate 令系统休眠,然后再按下电源开关,系统会自动恢复之前的工作环境。\n今天先这样,写的不够详细,改日再完善吧~\nRe: hibernation 万万没想到,今日(2022-05-04 00:14),我又为了休眠的事儿排查了两天之久。\n自文章写完之后,一年多来,休眠一直工作的很好。直到这次五一,我准备解决一下之前一直悬而未决的屏幕撕裂问题。在此过程中,我尝试了启用笔记本的独显 NVIDIA Corporation GP108M [GeForce MX150], 也为此做了很多工作,甚至从 Nvidia 官网下载了驱动进行安装,就是这个过程,安装报错了,然后我打算放弃,执行了卸载,卸载也报错了。最后实现的效果,确实 X 和 picom 都运行在独显上了,但是进入 X 之后,屏幕一片漆黑。彼时夜已深,我就打算放弃了。直接电脑休眠,而我去睡觉了。\n第二天打开电脑,才发现大事不妙。直接变成开机了,之前的工作状态并未还原。思来想去这期间干了什么呢?尝试安装独显驱动,解决屏幕撕裂,archlinux-keyring 损坏并重置,进行了系统全量更新(内核升级到 5.17.5-arch1-1), 很难排查到底是什么原因导致的。只能打开 journal 细细排查可能的原因,\n1 2 3 4 5 6 7 8 9 $ journalctl -b ... 5月 04 00:01:11 MiBook-Air kernel: ACPI BIOS Error (bug): Could not resolve symbol [\\_PR.PR00._CPC], AE_NOT_FOUND (20211217/psargs-330) 5月 04 00:01:11 MiBook-Air kernel: ACPI Error: Aborting method \\_PR.PR01._CPC due to previous error (AE_NOT_FOUND) (20211217/psparse-529) 5月 04 00:01:11 MiBook-Air kernel: ACPI BIOS Error (bug): Could not resolve symbol [\\_PR.PR00._CPC], AE_NOT_FOUND (20211217/psargs-330) 5月 04 00:01:11 MiBook-Air kernel: ACPI Error: Aborting method \\_PR.PR02._CPC due to previous error (AE_NOT_FOUND) (20211217/psparse-529) 5月 04 00:01:11 MiBook-Air kernel: ACPI BIOS Error (bug): Could not resolve symbol [\\_PR.PR00._CPC], AE_NOT_FOUND (20211217/psargs-330) 5月 04 00:01:11 MiBook-Air kernel: ACPI Error: Aborting method \\_PR.PR03._CPC due to previous error (AE_NOT_FOUND) (20211217/psparse-529) ... 第一个疑似的原因就是这个,但经过一番搜索,他也仅仅是个内核的 bug2,并不能证明他和休眠失败有直接关系。\n接着我直接找到这段时间的系统日志,一行一行的查看,凡是有疑似的都搜索之,未果。期间我发现,之前正常休眠恢复的日志序列大概是下面这个样子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 4月 28 23:59:06 MiBook-Air systemd-sleep[515989]: Entering sleep state \u0026#39;hibernate\u0026#39;... 4月 28 23:59:06 MiBook-Air kernel: PM: hibernation: hibernation entry 4月 29 19:48:53 MiBook-Air kernel: Filesystems sync: 0.004 seconds 4月 29 19:48:53 MiBook-Air kernel: Freezing user space processes ... (elapsed 0.001 seconds) done. 4月 29 19:48:53 MiBook-Air kernel: OOM killer disabled. 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x00000000-0x00000fff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x00058000-0x00058fff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x0009e000-0x000fffff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x71d1c000-0x71d1cfff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x71d48000-0x71d48fff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x725dc000-0x725dcfff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x725ec000-0x725ecfff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x7312f000-0x73130fff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x75388000-0x75c87fff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x7c026000-0x7c02bfff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x8be9e000-0x8cffdfff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x8cfff000-0xffffffff] 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Basic memory bitmaps created 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Preallocating image memory 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Allocated 676076 pages for snapshot 4月 29 19:48:53 MiBook-Air kernel: PM: hibernation: Allocated 2704304 kbytes in 0.38 seconds (7116.58 MB/s) 4月 29 19:48:53 MiBook-Air kernel: Freezing remaining freezable tasks ... (elapsed 0.001 seconds) done. 4月 29 19:48:53 MiBook-Air kernel: printk: Suspending console(s) (use no_console_suspend to debug) 可以看到,日志从 4 月 28 日 23:59:06 一下子跳到 4 月 29 日 19:48:53,也就是说我在 28 日晚上发起了休眠,在 29 日晚上再度打开电脑,成功恢复了工作区。\n而在休眠失败后日志是下面这样:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 5月 01 02:13:41 MiBook-Air systemd[1]: Reached target Sleep. 5月 01 02:13:41 MiBook-Air systemd[1]: Starting Hibernate... 5月 01 02:13:41 MiBook-Air kernel: PM: Image not found (code -16) 5月 01 02:13:41 MiBook-Air dhcpcd[463]: wlp2s0: old hardware address: f8:63:3f:4d:a1:8e 5月 01 02:13:41 MiBook-Air dhcpcd[463]: wlp2s0: new hardware address: 66:34:25:c7:18:31 5月 01 02:13:41 MiBook-Air systemd-sleep[13740]: Entering sleep state \u0026#39;hibernate\u0026#39;... 5月 01 02:13:41 MiBook-Air dhcpcd[463]: wlp2s0: old hardware address: 66:34:25:c7:18:31 5月 01 02:13:41 MiBook-Air dhcpcd[463]: wlp2s0: new hardware address: f8:63:3f:4d:a1:8e 5月 01 02:13:41 MiBook-Air kernel: PM: hibernation: hibernation entry -- Boot a60e8709e7b945abbcdb13a477011e7b -- 5月 01 18:43:13 MiBook-Air kernel: Linux version 5.17.5-arch1-1 (linux@archlinux) (gcc (GCC) 11.2.0, GNU ld (GNU Binutils) 2.38) #1 SMP PREEMPT Wed, 27 Apr 2022 20:56:11 +0000 5月 01 18:43:13 MiBook-Air kernel: Command line: ro root=/dev/nvme0n1p5 rw resume=/dev/nvme0n1p7 initrd=boot\\initramfs-linux.img 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: Supporting XSAVE feature 0x001: \u0026#39;x87 floating point registers\u0026#39; 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: Supporting XSAVE feature 0x002: \u0026#39;SSE registers\u0026#39; 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: Supporting XSAVE feature 0x004: \u0026#39;AVX registers\u0026#39; 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: Supporting XSAVE feature 0x008: \u0026#39;MPX bounds registers\u0026#39; 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: Supporting XSAVE feature 0x010: \u0026#39;MPX CSR\u0026#39; 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: xstate_offset[2]: 576, xstate_sizes[2]: 256 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: xstate_offset[3]: 832, xstate_sizes[3]: 64 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: xstate_offset[4]: 896, xstate_sizes[4]: 64 5月 01 18:43:13 MiBook-Air kernel: x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using \u0026#39;compacted\u0026#39; format. 同样在时间跳跃节点看,此前的日志看起来是休眠成功了,此后竟然直接走了 boot 流程!因此我猜测肯定是恢复(resume)过程出了问题,休眠(hibernation)是正常工作的。\n我尝试过将内涵参数中的resume=/dev/xxx改为resume=UUID=xxx-xxxx-xx,也还是不行。最让人无奈的是,到现在没有发现问题所在,一直在摸黑尝试。\n后来看到两篇文章(其实就是 Ref2 中提及的):\nDebugging hibernation and suspend BEST PRACTICE TO DEBUG LINUX* SUSPEND/HIBERNATE ISSUES 开始对休眠过程进行更具针对性的 debug,推荐从第一篇文章开始。经过一番 debug,发现我的休眠功能确实没啥问题,恢复功能也没问题,即第一篇文章提到的:\nThat test can be used to check if failures to resume from hibernation are related to bad interactions with the platform firmware. That is, if the above works every time, but resume from actual hibernation does not work or is unreliable, the platform firmware may be responsible for the failures.\n但即便知道了可能是硬件问题,我也看不出来是哪里的问题啊(太菜了 orz)。无奈之下尝试第二篇的 debug 方法,在鼓捣了一对内核参数之后,日志确实更详尽了,但其中暴露出的问题,google 都没有搜索结果。我哪看得懂啊?最后带着快要放弃的心情,再次翻开了 ArchWiki(再次高呼,ArchWiki YYDS!)上的一篇文章(Ref7), 其中提到:\nThe kernel parameters will only take effect after rebooting. To be able to hibernate right away, obtain the volume\u0026rsquo;s major and minor device numbers from lsblk and echo them in format *major*:*minor* to /sys/power/resume. If using a swap file, additionally echo the resume offset to /sys/power/resume_offset.[2]\n我就试探性的照做了,\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 yychi@~\u0026gt; lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 223.6G 0 disk ├─sda1 8:1 0 110G 0 part /home/yychi/EXTRA └─sda2 8:2 0 113.6G 0 part nvme0n1 259:0 0 238.5G 0 disk ├─nvme0n1p1 259:1 0 100M 0 part /boot/efi ├─nvme0n1p2 259:2 0 16M 0 part ├─nvme0n1p3 259:3 0 119.5G 0 part ├─nvme0n1p4 259:4 0 798M 0 part ├─nvme0n1p5 259:5 0 65G 0 part / ├─nvme0n1p6 259:6 0 49G 0 part /home └─nvme0n1p7 259:7 0 4.1G 0 part [SWAP] yychi@~\u0026gt; echo 259:7 \u0026gt; /sys/power/resume 以及 此处 提及\nWhen an initramfs with the base hook is used, which is the default, the resume hook is required in /etc/mkinitcpio.conf. Whether by label or by UUID, the swap partition is referred to with a udev device node, so the resume hook must go after the udev hook. This example was made starting from the default hook configuration: HOOKS=(base udev autodetect keyboard modconf block filesystems **resume** fsck) Remember to regenerate the initramfs for these changes to take effect.\nWhen an initramfs with the systemd hook is used, a resume mechanism is already provided, and no further hooks need to be added. 总结一下:就是往/sys/power/resume里写入正确的数值,以及在/etc/mkinitcpio.conf里加上resumehook,重新mkinitcpio -P,然后休眠恢复就成功了!\n小尝试 经过尝试,把/etc/mkinitcpio.conf中 HOOKS 中的 resume 去掉,再mkinitcpio -P,再次休眠后就无法恢复,直接走 boot 流程了。并且启动后/sys/power/resume的值丢失了(恢复默认):\n1 2 $ cat /sys/power/resume 0:0 而将 HOOKS 中的 resume 加上之后,再mkinitcpio -P生效之,重启后\n1 2 $ cat /sys/power/resume 259:7 有值了,而且休眠之后可以成功恢复。\n看起来就是这里的原因了,恢复的时候由于找不到 swap 分区导致 fallback 到 boot 流程,而 resume hook 就是起到告诉 kernel swap 分区的标识,因此才能成功恢复。但有一个问题,之前我没有动过这些,也能休眠并恢复成功。从 ArchWiki 上的描述 来看,HOOKS 中使用了systemd的,不需要加resume;使用了base的,需要加resume。看来是某些操作改了我的/etc/mkinitcpio.conf?\nBonus: 使用 sleep hook 在休眠时上锁 此前使用休眠的场景是这样的:terminal 里面敲systemctl hibernate,等待休眠成功,合上盖子,time flies,打开盖子,启动电源,恢复工作区。这个过程没有涉及到用户验证,所以如果此间别人拿了你的电脑,自然能一窥你的裙底风光。所以,合理的做法应该是休眠时顺便锁个屏。\n其实 ArchWiki 也有提到34,利用systemd管理的 service 可以做到。具体说来,可以创建如下文件:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # /etc/systemd/system/suspend@.service # see: https://wiki.archlinux.org/title/Power_management#Sleep_hooks [Unit] Description=User suspend actions Before=sleep.target [Service] User=%I Type=simple Environment=DISPLAY=:0 ExecStart=/usr/bin/slock ExecStartPost=/usr/bin/sleep 1 [Install] WantedBy=sleep.target 形如xxx@.service的文件称为 template service,它可以带一个参数拼接成一个 instantiated service 文件,比如xxx@username.service,具体可参考man 5 systemd.service. 上述我创建了一个suspend@.service,然后我们启用(enable on boot)一个 instantiated service\n1 $ systemctl enable suspend@yychi.service 接着 reload 一下使其立刻生效,\n1 $ systemctl daemon-reload 然后调用systemctl hibernate看看效果,果然在挂起、恢复之后,出现了锁屏界面5。\n热心观众可能发现,上述 service 对systemctl suspend同样生效,其原因是 suspend 和 hibernate 同样都在sleep.target之后,而我们的 service 定义了Before=sleep.target,说明suspend@yychi.service要在sleep.target之前执行。因此无论是 sleep 还是 hibernate 都能用上。印证如下:\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 yychi@/etc/systemd/system\u0026gt; systemctl cat suspend.target 21:15 [Unit] Description=Suspend Documentation=man:systemd.special(7) DefaultDependencies=no Requires=systemd-suspend.service After=systemd-suspend.service StopWhenUnneeded=yes yychi@/etc/systemd/system\u0026gt; systemctl cat systemd-suspend.service 21:37 [Unit] Description=System Suspend Documentation=man:systemd-suspend.service(8) DefaultDependencies=no Requires=sleep.target After=sleep.target # 注意此处 [Service] Type=oneshot ExecStart=/usr/lib/systemd/systemd-sleep suspend yychi@/etc/systemd/system\u0026gt; systemctl cat hibernate.target 21:37 [Unit] Description=System Hibernation Documentation=man:systemd.special(7) DefaultDependencies=no Requires=systemd-hibernate.service After=systemd-hibernate.service StopWhenUnneeded=yes yychi@/etc/systemd/system\u0026gt; systemctl cat systemd-hibernate.service 21:38 # /usr/lib/systemd/system/systemd-hibernate.service [Unit] Description=Hibernate Documentation=man:systemd-hibernate.service(8) DefaultDependencies=no Requires=sleep.target After=sleep.target # 注意此处 [Service] Type=oneshot ExecStart=/usr/lib/systemd/systemd-sleep hibernate Re2: hibernation 今天(2024-01-27),又出现了休眠问题,好在有了之前的经历,2H 就定位出来了。\n休眠失败,首先看 journal:\n1月 27 19:32:12 MiBook-Air systemd[1]: Started User suspend actions. 1月 27 19:32:12 MiBook-Air systemd[1]: Reached target Sleep. 1月 27 19:32:12 MiBook-Air systemd[1]: Starting System Hibernate... 1月 27 19:32:12 MiBook-Air systemd-sleep[11138]: Performing sleep operation \u0026#39;hibernate\u0026#39;... 1月 27 19:32:12 MiBook-Air kernel: PM: hibernation: hibernation entry 1月 27 19:32:32 MiBook-Air kernel: Filesystems sync: 0.022 seconds 1月 27 19:32:32 MiBook-Air kernel: Freezing user space processes 1月 27 19:32:32 MiBook-Air kernel: Freezing user space processes completed (elapsed 0.003 seconds) 1月 27 19:32:32 MiBook-Air kernel: OOM killer disabled. 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x00000000-0x00000fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x00058000-0x00058fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x0009e000-0x000fffff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x71c54000-0x71c54fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x71c80000-0x71c80fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x725d9000-0x725d9fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x725e9000-0x725e9fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x7312f000-0x73130fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x75388000-0x75c87fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x7bff2000-0x7bff7fff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x8be9e000-0x8cffdfff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Marking nosave pages: [mem 0x8cfff000-0xffffffff] 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Basic memory bitmaps created 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Preallocating image memory 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Allocated 791290 pages for snapshot 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: Allocated 3165160 kbytes in 1.56 seconds (2028.94 MB/s) 1月 27 19:32:32 MiBook-Air kernel: Freezing remaining freezable tasks 1月 27 19:32:32 MiBook-Air kernel: Freezing remaining freezable tasks completed (elapsed 0.001 seconds) 1月 27 19:32:32 MiBook-Air kernel: printk: Suspending console(s) (use no_console_suspend to debug) 1月 27 19:32:32 MiBook-Air kernel: ata1.00: Entering standby power mode 1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: DRM: failed to idle channel 1 [DRM] 1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: PM: pci_pm_freeze(): nouveau_pmops_freeze+0x0/0x20 [nouveau] returns -16 1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: PM: dpm_run_callback(): pci_pm_freeze+0x0/0xc0 returns -16 1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: PM: failed to freeze async: error -16 1月 27 19:32:32 MiBook-Air kernel: usb usb1: root hub lost power or was reset 1月 27 19:32:32 MiBook-Air kernel: usb usb2: root hub lost power or was reset 1月 27 19:32:32 MiBook-Air kernel: nvme nvme0: 4/0/0 default/read/poll queues 1月 27 19:32:32 MiBook-Air kernel: usb 1-1: reset full-speed USB device number 2 using xhci_hcd ... 1月 27 19:32:32 MiBook-Air systemd-sleep[11138]: Failed to put system to sleep. System resumed again: Device or resource busy 1月 27 19:32:32 MiBook-Air kernel: PM: hibernation: hibernation exit ... 1月 27 19:32:32 MiBook-Air systemd[1]: systemd-hibernate.service: Main process exited, code=exited, status=1/FAILURE 1月 27 19:32:32 MiBook-Air systemd[1]: systemd-hibernate.service: Failed with result \u0026#39;exit-code\u0026#39;. 1月 27 19:32:32 MiBook-Air systemd[1]: Failed to start System Hibernate. 1月 27 19:32:32 MiBook-Air systemd[1]: Dependency failed for System Hibernation. 1月 27 19:32:32 MiBook-Air systemd[1]: hibernate.target: Job hibernate.target/start failed with result \u0026#39;dependency\u0026#39;. 1月 27 19:32:32 MiBook-Air systemd[1]: systemd-hibernate.service: Consumed 1.750s CPU time. 1月 27 19:32:32 MiBook-Air systemd[1]: Stopped target Sleep. 1月 27 19:32:32 MiBook-Air systemd-logind[379]: Operation \u0026#39;sleep\u0026#39; finished. 可以看到,日志里清晰可见说了休眠失败,resume 到休眠前的状态。而往上溯源可以看到,失败原因是\n1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: DRM: failed to idle channel 1 [DRM] 1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: PM: pci_pm_freeze(): nouveau_pmops_freeze+0x0/0x20 [nouveau] returns -16 1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: PM: dpm_run_callback(): pci_pm_freeze+0x0/0xc0 returns -16 1月 27 19:32:32 MiBook-Air kernel: nouveau 0000:01:00.0: PM: failed to freeze async: error -16 根据这个线索搜索,最后发现直接 block nouveau 即可6,即禁用 nvidia 的显卡。因为我的笔记本有两块显卡,一个 intel 的核显,一个 nvidia 的集显。在 linux 环境下为这集显我没少踩坑。\nyychi@~\u0026gt; lspci -k | grep -A 2 -E \u0026#34;(VGA|3D)\u0026#34; 00:02.0 VGA compatible controller: Intel Corporation HD Graphics 620 (rev 02) Subsystem: Xiaomi HD Graphics 620 Kernel driver in use: i915 -- 01:00.0 3D controller: NVIDIA Corporation GP108M [GeForce MX150] (rev a1) Subsystem: Xiaomi GP108M [GeForce MX150] Kernel modules: nouveau 解决方法:直接加个文件/etc/modprobe.d/nouveau_blacklist.conf,重启后即可正常休眠。\nychi@~\u0026gt; cat /etc/modprobe.d/nouveau_blacklist.conf # block nouveau, otherwise hibernation will not work. # yychi, 2024.1 blacklist nouveau 关于休眠,暂时探索至此\u0026hellip;\nReferences Is Hybrid Sleep the same in Linux as in Windows? How can I hibernate on Ubuntu 16.04? How do I use pm-suspend-hybrid by default instead of pm-suspend? Kernel parameters Error resume: no device specified for hibernation Hibernation: Resume Can\u0026rsquo;t Find Swap Power management/Suspend and hibernate - ArchWiki See Arch wiki\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nACPI BIOS Error (bug): Could not resolve symbol [\\_PR.PR00._CPC]\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSuspend/resume service files\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSlock - lock on suspend\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n此处我用的是 slock,X 下一个非常简单轻巧的锁屏工具。简单到什么程度呢?它连配置文件都没有,想要自定义,必须改config.h然后重新编译!\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n参考帖子:[Solved]Issues with mesa.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/linux-hibernate/","tags":["hibernate"],"title":"Linux 的休眠"},{"categories":["Notes"],"contents":"众所周知,LaTeX 是一个高效易用的排版软件,基本上只要找到合适的模板,剩下的就只剩码字了。比起 MS Word,简直不知道高到哪里去。就拿最近写论文的事来说,我先用 TeX 码好字,然后要投的那个刊需要用 Word 提交。转格式转了我一下午带一晚上,太痛苦了。深刻的体会到什么叫自以为是,MS Word 自作聪明地给你调格式。当你敲下回车之后,天知道它又会自动帮你做些什么?!\n好了,闲话少叙。这次主要是记录一下在使用 LaTeX 排版中文的时候,觉得每次都要定义字体很麻烦。于是干脆写成一个宏包的形式。之后的文档中如果需要排中文,直接导入这个包,就可以直接使用啦。说是宏包,实际上里面只包含自定义字体,惭愧惭愧,我并不是 TeX 专家。\n一个 TeX 宏包大概长成下面这个样子:\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 % This package provides a zh_CN font customization for convinience. % yychi (guyueshui002@gmail.com) % 2019-09-25 10:10 \\NeedsTeXFormat{LaTeX2e}[1994/06/01] \\ProvidesPackage{zhfont}[General zh_CN font setting for tex.] \\ifx\\zhfontpath\\undefined \\newcommand{\\zhfontpath}{/path/to/font} \\else \\message{\\string\\zhfontpath\\space is defined by some others.} \\fi \\RequirePackage{xeCJK} \\setCJKmainfont[Path=\\zhfontpath, BoldFont=方正小标宋_GBK.ttf, ItalicFont=方正仿宋_GBK.ttf]{方正书宋_GBK.ttf} \\setCJKsansfont[Path=\\zhfontpath]{方正黑体_GBK.ttf} \\setCJKmonofont[Path=\\zhfontpath]{方正中等线_GBK.ttf} %%% font alias \\setCJKfamilyfont{fzxbs}[Path=\\zhfontpath]{FZXBSJW.TTF} \\setCJKfamilyfont{fzhei}[Path=\\zhfontpath]{方正黑体_GBK.ttf} \\setCJKfamilyfont{fzkai}[Path=\\zhfontpath]{方正楷体_GBK.ttf} \\setCJKfamilyfont{fzfs}[Path=\\zhfontpath]{方正仿宋_GBK.ttf} %% 使用 ctex 系 document,这些字体已被定义,所以做个 case \\ifx\\songti\\undefined \\newcommand*{\\songti}{\\CJKfamily{fzxbs}} % 宋体 \\fi \\ifx\\heiti\\undefined \\newcommand*{\\heiti}{\\CJKfamily{fzhei}} % 黑体 \\fi \\ifx\\kaiti\\undefined \\newcommand*{\\kaiti}{\\CJKfamily{fzkai}} % 楷体 \\fi \\ifx\\fangsong\\undefined \\newcommand*{\\fangsong}{\\CJKfamily{fzfs}} % 仿宋 \\fi \\endinput % vim:ft=tex 通过例子来学习,不失为一个好方法。上面的就是我为自己自定义的字体配置的宏包,还包含了一定的控制流呢。鬼知道我查这些关键字查了多久。要想设置中文并成功排版出来,必须使用xeCJK红包。里面包含的三条主要设定及其意义如下:\n1 2 3 4 5 6 \\setCJKmainfont[Path=\\zhfontpath, BoldFont=方正小标宋_GBK.ttf, ItalicFont=方正仿宋_GBK.ttf] {方正书宋_GBK.ttf} % 设置中文主字体 \\setCJKsansfont[Path=\\zhfontpath]{方正黑体_GBK.ttf} % 设置中文无衬线字体 \\setCJKmonofont[Path=\\zhfontpath]{方正中等线_GBK.ttf} % 设置中文等宽字体 上面三条命令就设定好了中文排版主字体,无衬线以及等宽字体。注意如果是已安装的字体,则不需要指定Path和文件名后缀。比如我列一下我已经安装的中文字体:\n1 2 3 4 5 $ fc-list :lang=zh /usr/share/fonts/wenquanyi/wqy-zenhei/wqy-zenhei.ttc: 文泉驿等宽正黑,WenQuanYi Zen Hei Mono,文泉驛等寬正黑:style=Regular /usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc: 文泉驿微米黑,WenQuanYi Micro Hei,文泉驛微米黑:style=Regular /usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc: 文泉驿等宽微米黑,WenQuanYi Micro Hei Mono,文泉驛等寬微米黑:style=Regular /usr/share/fonts/wenquanyi/wqy-zenhei/wqy-zenhei.ttc: 文泉驿正黑,WenQuanYi Zen Hei,文泉驛正黑:style=Regular 则相应的字体设定为:\n1 2 3 \\setCJKmainfont[BoldFont=somefont,ItalicFont=somefont]{WenQuanYi Micro Hei} \\setCJKsansfont{WenQuanYi Zen Hei} \\setCJKmonofont{WenQuanYi Zen Hei Mono} 后面就是定义一些常用中文字体:宋体、黑体、楷体、仿宋等。注意在使用 ctex 系文档时,自定义的名字可能已被定义,比如使用\\documentclass{ctextart},\\songti命令已被定义,如果使用了我们自定义的宏包zhfont,则会报错命令重复定义。此时可以用\\renewcommand强制重新定义,也可以使用 ctex 默认的。总之,我这里加了条件主要是为了学一下 TeX 里面的控制流。\n宏包选项 今天我们来谈谈如何给宏包传递选项,众所周知,形如\\usepackage[option1,option2]{xx}的意思是给宏包 xx 传递了 option1, option2 参数,那么宏包内部是如何处理这些选项的呢?趁着这次自定义字体,我们来看看。\n首先我建议过一遍clsguide.pdf,如果你的电脑上安装完整的 tex-live(确切来说是安装了 tex 的 documents),你可以使用texdoc clsguide来打开该文档。由于时间原因我决定先放结果,再稍作解释。\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 % This package provides a zh_CN font customization for convinience. % yychi (guyueshui002@gmail.com) % 2019-09-25 10:10 \\NeedsTeXFormat{LaTeX2e}[1994/06/01] \\ProvidesPackage{zhfont}[General zh_CN font setting for tex.] \\DeclareOption{typography}{\\newcommand{\\tp}{}} \\DeclareOption*{% \\PackageWarning{zhfont}{Unknown option ‘\\CurrentOption’}% } % IMPORTANT!!!, all declareoption should before this, % see: https://es.overleaf.com/learn/latex/Writing_your_own_class \\ProcessOptions\\relax \\ifx\\zhfontpath\\undefined \\newcommand{\\zhfontpath}{/home/yychi/Documents/FontsCollection/zh_cn/} \\else \\message{\\string\\zhfontpath\\space is defined by some others.} \\fi \\RequirePackage{xeCJK} \\ifx\\tp\\undefined \\setCJKmainfont[ Path = \\zhfontpath, BoldFont = 方正小标宋_GBK.ttf, ItalicFont = 方正仿宋_GBK.ttf ]{方正书宋_GBK.ttf} \\setCJKsansfont[Path=\\zhfontpath]{方正黑体_GBK.ttf} \\setCJKmonofont[Path=\\zhfontpath]{方正中等线_GBK.ttf} \\else \\setCJKmainfont[ Path = \\zhfontpath zhuzi_old_mincho/, BoldFont = FZFWZhuZiAOldMinchoB.TTF, ItalicFont = FZFWZhuZGDLMCJW.TTF, ]{FZFWZhuZiAOldMinchoR.TTF} \\setmainfont{EB Garamond} %\\setmonofont{Consolas} \\fi % 下同 此中,\\Declareoption{opt_name}{expression},表示宏包提供选项 opt_name,如果你在\\usepackage的时候传递了参数 opt_name,那么 expression 将被执行。而\\DeclareOption*{expression}表示如果你传递了宏包中未提供的选项,那么 expression 将被执行。这里我为宏包 zhfont 提供了选项 typography。如果再使用该宏包时传递了该选项,那么 zhfont 内部会定义一条命令\\tp,后面通过判断\\tp有没有被定义来选择不同的字体族,就达到了一个选项,切换字体的效果。\n具体来说,如果使用\\usepackage[typography]{zhfont},将使用\u0026quot;FZFWZhuZiAOldMinchoR.TTF\u0026quot;这一套字体排版,而如果没有传递该参数,则使用方正书宋排版。好了,时间有限,这里仅做一个快速的记录,以便之后参考。未完待续……\nReference https://texfaq.org/FAQ-isdef https://stackoverflow.com/questions/1211888/is-there-any-way-i-can-define-a-variable-in-latex https://tex.stackexchange.com/questions/265809/if-elseif-and-else-conditionals-in-the-preamble ","permalink":"https://guyueshui.github.io/post/tex-sty-basic/","tags":["排版","latex"],"title":"一个 really simple 的 LaTeX 宏包"},{"categories":[],"contents":"怎么想到用 latexmk 的呢?写论文呗!\n本来呢,我一直习惯于使用命令行手敲\n1 pdflatex someting.tex 千万别小看这种重复劳动,它不仅帮你加深记忆,还有最完整的输出,让你一窥 Tex 排版系统的内裤(→_→,一本正经胡说八道中……)。还记得 Archlinux 的哲学名言吗?\u0026ndash;Keep it simple and stupid (KISS)\u0026ndash;说得太对了呀!\n但是啊说到写论文,肯定要有引用文献的啦,这就麻烦了,每次更新参考文献和交叉引用都需要四步走:\n1 2 3 4 pdflatex a.tex # 生成 aux 文件,下一步 bibtex 才能知道需要引用哪几个文献 bibtex a # 生成 bbl 文件,及将.bib 里面的元数据展开成符合 tex 语法的 bibitem pdflatex a.tex # 刷新引用,可能残留一些问号 pdflatex a.tex # 产生最终结果,所有引用正确显示 这就忍不了了吧?其实我还是能忍的,因为不是每次更新文献就重新编译,可以写一大段再更新一次,这就省事儿多了。真正原因是我像用 VS code 写 latex,下了一个插件叫 Latex Workshop,它默认使用 pdflatex 编译,但我写中文必须要用 xelatex 编译,而且必须要完成自动化。\n我看到可以用 latexmk,所以就去简单研究了下,下面进入正题。\n命令行使用 1 latexmk 默认情况下会自动编译当前文件夹下所有 tex 文件,默认使用 pdflatex 引擎。\n如果想编译得到 PDF 文件,则直接加上选项:\n1 latexmk -pdf 如果想编译单个文件:\n1 latexmk single_file.tex 要删除临时文件:\n1 2 latexmk -c latexmk -C ## 删除(包含输出文件) 配置文件 ~/.latexmkrc:用户全局配置 $PWD/latexmkrc:局部文件夹配置 一个简单的配置文件:\n1 2 3 4 5 6 7 8 9 10 11 12 $dvi_previewer = \u0026#39;start xdvi -watchfile 1.5\u0026#39;; $ps_previewer = \u0026#39;start gv --watch\u0026#39;; $pdf_previewer = \u0026#39;start evince\u0026#39;; $pdf_mode = 1; # tex -\u0026gt; pdf # $pdf_mode = 2; # tex -\u0026gt; ps -\u0026gt; pdf # $pdf_mode = 5; # use xelatex, see `man latexmk` $postscript_mode = 1; # tex -\u0026gt; ps @defalut_files = (\u0026#39;main.tex\u0026#39;, \u0026#39;niam.tex\u0026#39;); # 指定要编译的文件 $pdflatex = \u0026#39;pdflatex -interaction=nonstopmode -synctex=1 %O %S\u0026#39;; $xelatex = \u0026#39;xelatex -no-pdf -interaction=nonstopmode -synctex=1 %O %S\u0026#39;; References Using Latexmk Make latexmk 4.54c use synctex with xelatex ","permalink":"https://guyueshui.github.io/post/latexmk-basic/","tags":[],"title":"Latexmk 基础用法"},{"categories":["Notes"],"contents":"先看环境:\n$ neofetch -` yychi@MiBook-Air .o+` ---------------- `ooo/ OS: Arch Linux x86_64 `+oooo: Host: TM1604 XMAKB3M0P0202 `+oooooo: Kernel: 5.5.13-arch2-1 -+oooooo+: Uptime: 5 mins `/:-:++oooo+: Packages: 1153 (pacman) `/++++/+++++++: Shell: zsh 5.8 `/++++++++++++++: Resolution: 1920x1080 `/+++ooooooooooooo/` WM: i3 ./ooosssso++osssssso+` Theme: Adwaita [GTK2] .oossssso-````/ossssss+` Icons: Adwaita [GTK2] -osssssso. :ssssssso. Terminal: urxvt :osssssss/ osssso+++. Terminal Font: DejaVu Sans Mono for Powerline /ossssssss/ +ssssooo/- CPU: Intel i5-7200U (4) @ 3.100GHz `/ossssso+/:- -:/+osssso+- GPU: NVIDIA GeForce MX150 `+sso+:-` `.-/+oso: GPU: Intel HD Graphics 620 `++:. `-/+/ Memory: 1608MiB / 7881MiB .` `/ 再看问题:Gnome 系软件(gedit, baobab, nautilus 等)启动龟速,通常需要等待 10-30s.\n思考:我看你就是在为难我,我压根不知道问题出在哪里。大概就是几天前更新系统的时候看到需要更新很多 gnome 的包,心想着我都不用 gnome 了,留这么多 gnome 的包干吗呢?于是删了很多,这一删我以往的经验就告诉我可能会出什么幺蛾子。果不其然,这些天有时候浏览器调用文件管理器(gnome 家 nautilus)的时候,非常之满,我一度以为死机。细看来又不是,等了十几秒文件窗口忽然蹦出来,怎么这么慢!\n换做以前,应该是直接重装系统了。可是这次没时间这么折腾,加上年纪大了不想折腾,毕竟这个本子用了这么久了,积累了好多东西。于是死马当活马医,硬着头皮去 google 相关问题。循着蛛丝马迹,找到可能出问题的一些点。最后还是要看 log!\n1 2 3 $ journalctl --since=2020-3-25 ... ... 最终我发现这几行有嫌疑:\n4月 01 13:15:01 MiBook-Air xdg-desktop-portal-gtk[139187]: Unable to init server: 无法连接:拒绝连接 4月 01 13:15:01 MiBook-Air xdg-desktop-por[139187]: cannot open display: 4月 01 13:15:01 MiBook-Air systemd[520]: xdg-desktop-portal-gtk.service: Main process exited, code=exited, status=1/FAILURE 4月 01 13:15:01 MiBook-Air systemd[520]: xdg-desktop-portal-gtk.service: Failed with result \u0026#39;exit-code\u0026#39;. 4月 01 13:15:01 MiBook-Air systemd[520]: Failed to start Portal service (GTK+/GNOME implementation). 结果一查,果然查到一篇相关的帖子1,我的问题和他一模一样。结果就解决了,就解决了,解决了,决了,了!\n问题的原因其实我不太懂,谨此写下折腾记录。\nGnome applications slow start due to failing services\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/gnome-app-slow-start/","tags":["折腾"],"title":"Gnome 应用启动缓慢"},{"categories":["Notes"],"contents":"想必你也有过这样的疑问,中学数学书上的那些精美的作图是如何画出来的?一直以来,我都想学习一门绘图语言,只是久久未能行动 orz\u0026hellip;\n闲话少叙,开始学习!\nMetapost 介绍什么的,我其实不太关心,所以就不写了……\n一个简单的例子 和 C 语言一样,Metapost 有一个源文件xxx.mp,有一个编译器mpost,然后编译之,即得到图片(默认后缀.mps)。\n% file:///hello.mp prologues := 3; outputtemplate := \u0026#34;%j-%c.mps\u0026#34;; outputformat := \u0026#34;mps\u0026#34;; beginfig(1); draw (0,0)--(11,0)--(11,11)--(0,11)--cycle; endfig; beginfig(2); draw (0,0)..(11,0)..(11,11)..(0,11)..cycle; endfig; end 几点说明:\nMetapost 语句以分号结尾,除了最后的end! 设置prologues:=3会在生成的图像文件.ps中嵌入字体信息,这会增加图片大小 默认单位:PostScript Points (1/72in = 0.352777\u0026hellip; mm) 这里简单说一下源码结构,和 LaTeX 一样,Metapost 有导言区,可以做一些设置之类的工作。如hello.mp中前三句就设置了输出文件格式,以及文件名规范等。然后作图部分主要由beginfig--endfig块控制。\nbeginfig(x); draw something; draw anything; endfig; 括号中的 x 替换为数字,类似图片 id. 一个源文件中可以有多个beginfig--endfig块,编译后每个块对应一张图片。\n编译hello.mp后即可得到两张图片:\n1 2 3 4 5 6 7 $ mpost hello.mp This is MetaPost, version 2.00 (TeX Live 2018) (kpathsea version 6.3.0) (/usr/local/texlive/2018/texmf-dist/metapost/base/mpost.mp (/usr/local/texlive/2018/texmf-dist/metapost/base/plain.mp Preloading the plain mem file, version 1.005) ) (./hello.mp [1] [2] ) 2 output files written: hello-1.mps .. hello-2.mps Transcript written on tmp.log. Fig-1 Fig-2 Metapost 按坐标画图非常简单,将坐标点一个一个连起来就行了,注意到--表示直线连接,..表示平滑的曲线连接。和众多编程语言一样,你也可以定义变量方便重复使用。\nbeginfig(3); z0 = (0,0); z1 = (60,40); z2 = (40,90); z3 = (10,70); z4 = (30,50); draw z0..z1..z2..z3..z4; endfig Fig-3 Workflow 使用默认输出格式会产生 PostScript 格式的图片,在 Linux 下可以用 gnome 中的 evince 查看。也可以使用epstopdf转化为 PDF 查看。\nImage adapted from ref1 Primitives 变量类型 Metapost 几个常见的类型:\npair: (0,0) and (3,4) path: (0,0)\u0026ndash;(3,4) pen: (implicit) pen for stroking 比如:\nbeginfig(0) u:=1cm; pair a,b; path p; pen mypen; a = (0,0); b = (3u,4u); p = a--b; mypen = pencircle scaled 1mm; pickup mypen; draw p; endfig; All MetaPost variable types:\nType Example numeric (default, if not explicitly declared) pair pair a; a := (2in,3mm); boolean boolean v; v := false; path path p; p := fullcircle scaled 5mm; pen pen r; r := pencircle; picture picture q; q := nullpicture; transform transform t; t := identity rotated 20; color color c; c := (0,0,1); (blue) cmykcolor cmykcolor k; k := (1,0.8,0,0); (some blue) string string s; s := \u0026quot;Hello\u0026quot;; 弯曲控制 我们已经知道使用..可以让 Metapost 在两点之间画出平滑的曲线,尽管它画的很好(默认使用 贝塞尔曲线),但有时候我们往往需要控制哪里该要陡一点,哪里平缓一点。对于这种需求,Metapost 同样提供了精细粒度的控制方法。\nbeginfig(4); for i=0 upto 9: draw (0,0){dir 45}..{dir 10a}{6cm, 0}; endfor endfig; Fig-4 可以看出,从图上起点(左边的点),对应坐标 (0,0),引出一族曲线,这些曲线在 (0,0) 处的左极限都是 1,呈现出 45 度角。而在终点 (6cm,0) 处的入角从 0 度到 90 度变化,正如语句中描述的。\n未完待续……\nReference Which Output file should I export from Metapost? Metapost for Beginners Learning METAPOST by Doing Tutorial in MetaPost ","permalink":"https://guyueshui.github.io/post/metapost/","tags":["metapost","作图"],"title":"Metapost 学习笔记"},{"categories":["随笔"],"contents":"近日整理以前的文章,发现一个事实:我越来越不会说话,越来越不会表达,越来越没有自己的思想。\n以前的我每隔几天都会发表空间说说,即使大部分属于“少年不识愁滋味,为赋新词强说愁”。但好歹能时常发表自己的观点、见解和感悟。我现在看起自己以前写过的东西,不论正确与否,起码可以通过写的文字窥见当时的自己,了解当时的想法,经过时空的转换,这些想法现在品起来十分有味道、并且非常有乐趣。\n“那么三年以后的我呢?”\n我现在并不像以前那样频繁的写随笔,发感悟。三年后的我能品到我现在的心境吗?恐怕不能。时间会冲刷一切,生物都是不断更新的个体。我压根不知道以后会怎样:长胖了还是长高了?开心了还是抑郁了?三年后的我又会是什么样的心境?\n我常常在回忆我是如何从唠叨的空间爱好者,变成现在沉默的机器。我当初这么活跃又是为什么?我想,有很大一部分是时期的原因。当时正处于青春期,想法比较多也是正常的,思维齿轮一刻不停,偶尔碰出火花是理所当然。还有一方面,我想经营一个“才子”的人设,所以时常会在空间里写诗歌。那时候也确实比较喜欢诗词,尝读大小李杜,山水田园,婉约豪放,小山纳兰等等,每每被其中描写的意境所打动。一读完,脑子里就有场景,美,实在是美!“我能不能写出这样美丽的意境呢?”我当时这么想着,但是对于格律一窍不通,只了解简单的平仄相对。所以我写的压根算不上诗词,这一点我十分清楚。所以我从不标榜“今天我又写了一首诗”,“昨天新写了一首词”。只是厚脸皮的套用词牌,自由快乐的写着。即便如此,我写的文字,我自我感觉,还是有那么一点点价值的,并非全部,有的也有非常好的意境。\n我当时写诗词的状态,就是看到某个事物,有了一个想法,脑子里忽然冒出一句。然后从这一句扩展,联想,直到将整篇完成。大多数时候,我都是闭眼畅想,精骛八极,心游万仞。有时候在被窝里憋着想,想到睡着,第二天一看:呀,怎么没想出来就睡着了!\n另外“才子”的人设其实是臭美,真实的想法是想吸引异性。这么好的事我为什么不干呢?但事与愿违,那些年我压根没有成功,我总指望着异性主动来找我,羞于主动找异性。是的,我还没有认清时代变了哇!(难怪单身这么久)\n另一方面,那个时候我非常喜欢道家学说,差点没成道长。现在看来可能是中二病:就是什么都看透;什么都无所谓;什么我来这红尘走一遭;什么归隐山林等等。所以遇到事情,总是能用道家的学说来分析一遍,解释一遍,还希望有人能懂我,产生共鸣,希望推己及人,把别人也拉入我道门。那个时候果真有很多小年轻来找我咨询问题:和男朋友分了咋办?不想念书了咋办?受打击了咋办?厌世了咋办……我都一一给予解答,说教的头头是道。在他们眼中,我可能就是人生导师吧哈哈!\n后来,遇到一些好友是在忍受不了了,就评论说我矫情。那个时候“矫情”这个词刚流行起来,恰好被我撞上了\u0026lt;(=┘ ̄Д ̄)┘╧═╧\u0026gt;. 我的理论告诉我忽视这些“无知的人”,但是我的境界还没有到达到理论的高度,所以心中难免有些介怀。再到和我的人生导师交流,他也说他好久不发说说了,发说说的都是无聊的人,有这个时间不如去做点有意义的事。他说的话没有问题,但是我当时听岔了,没有 get 到他的本意。所以一味的照做了,有意识地减少发说说的频率,也不再写那些酸不琉球的话了。我压根没注意到我的个性正在被扼杀!\n后来随着年龄增长,逐渐走出了青春期。遇到事情,想发表看法的时候,脑海中就有一个声音告诉自己:说了又如何?有人会听吗?有人会和我想的一样吗?我说的是对的吗?我了解事情的原委吗?我该说吗……\n我,变得踟蹰不前了。\n遇到事情,不止三思,而是“六思”了。瞻前顾后,使我无法前进。久而久之,我不再有那么多看法。不写东西的时间,也没有用来做什么有意义的事。现在看来,是赔了夫人又折兵。啥好处没捞着,还弄丢一个。\n所以我现在打算捡回来,为了让未来的我了解现在。不记录是不行的, 记录是当下最好的,也是唯一的选择。未来的我只能通过现在的记录去观测这个时空的我,所以我必须尽可能的留下生活痕迹!\n","permalink":"https://guyueshui.github.io/post/%E5%A3%B0%E9%9F%B3%E7%9A%84%E6%B6%88%E4%BA%A1/","tags":["碎碎念"],"title":"声音的消亡"},{"categories":[],"contents":"The Title Sub Title\nYychi | SIST\nSlide 2 here comes $e=mc^2$.\n$$ \\mathbb{T} \\mapsto A $$\n","permalink":"https://guyueshui.github.io/slide/bbb/","tags":[],"title":"Bbb"},{"categories":["slides"],"contents":"Hello world! This is my first slide.\nHello Mars! This is my second slide.\nVertical slide 1 This is verticle slide.\nVertical slide 2 ","permalink":"https://guyueshui.github.io/slide/ccc/","tags":null,"title":"CCC"},{"categories":["slides"],"contents":"Hello world! This is my first slide.\n$e=mc^2$\n1 2 3 4 int main() { return 0; } 1 2 3 4 5 package main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;Hello world!\u0026#34;) } Hello Mars! This is my second slide.\nVertical slide 1 This is verticle slide.\nVertical slide 2 ","permalink":"https://guyueshui.github.io/slide/aaa/","tags":null,"title":"Demo Slides"},{"categories":["Notes"],"contents":"转义字符到底是啥?\n实不相瞒我就是因为不知道才写下这篇文章,不,准确的说是这篇笔记 orz,既然是笔记,无所谓抄不抄了。每次说到转义字符,多少有点模糊,所以不如记下这篇笔记,以后忘了直接翻出来看看,复习起来要快一些。\n注:以下大段转载自 C 语言中文网,原文链接在参考中给出,如有侵权,请联系我删除。\n什么是转义 字符集(Character Set)为每个字符分配了唯一的编号,我们不妨将它称为编码值。在 C 语言中,一个字符除了可以用它的实体(也就是真正的字符)表示,还可以用编码值表示。这种使用编码值来间接地表示字符的方式称为转义字符(Escape Character)。\n转义字符以\\或者\\x开头,以\\开头表示后跟八进制形式的编码值,以\\x开头表示后跟十六进制形式的编码值。对于转义字符来说,只能使用八进制或者十六进制。\n先贴张 Ascii 码表:\n从码表可以得到字符 1、2、3、a、b、c 对应的编码值:\n进制 \u0026lsquo;1\u0026rsquo; \u0026lsquo;2\u0026rsquo; \u0026lsquo;3\u0026rsquo; \u0026lsquo;a\u0026rsquo; \u0026lsquo;b\u0026rsquo; \u0026lsquo;c\u0026rsquo; 十进制 49 50 51 97 98 99 八进制 061 062 063 0141 0142 0143 十六进制 0x31 0x32 0x33 0x61 0x62 0x63 下面的例子演示了转义字符的用法:\n1 2 3 4 5 6 7 char a = \u0026#39;\\61\u0026#39;; //字符 1 char b = \u0026#39;\\141\u0026#39;; //字符 a char c = \u0026#39;\\x31\u0026#39;; //字符 1 char d = \u0026#39;\\x61\u0026#39;; //字符 a char *str1 = \u0026#34;\\x31\\x32\\x33\\x61\\x62\\x63\u0026#34;; //字符串\u0026#34;123abc\u0026#34; char *str2 = \u0026#34;\\61\\62\\63\\141\\142\\143\u0026#34;; //字符串\u0026#34;123abc\u0026#34; char *str3 = \u0026#34;The string is: \\61\\62\\63\\x61\\x62\\x63\u0026#34; //混用八进制和十六进制形式 转义字符既可以用于单个字符,也可以用于字符串,并且一个字符串中可以同时使用八进制形式和十六进制形式。\n一个完整的例子:\n1 2 3 4 5 #include \u0026lt;stdio.h\u0026gt; int main(){ puts(\u0026#34;\\x68\\164\\164\\x70://c.biancheng.\\x6e\\145\\x74\u0026#34;); return 0; } 运行结果:http://c.biancheng.net\n转义字符的初衷是用于 ASCII 编码,所以它的取值范围有限:\n八进制形式的转义字符最多后跟三个数字,也即\\ddd,最大取值是\\177,也就是十进制的 127; 十六进制形式的转义字符最多后跟两个数字,也即\\xdd,最大取值是\\7f,也是十进制的 127。 超出范围的转义字符的行为是未定义的,有的编译器会将编码值直接输出,有的编译器会报错。\n常用控制字符的别名 对于 ASCII 编码,0~31(十进制)范围内的字符为控制字符,它们都是看不见的,不能在显示器上显示,甚至无法从键盘输入,只能用转义字符的形式来表示。不过,直接使用 ASCII 码记忆不方便,也不容易理解,所以,针对常用的控制字符,C 语言又定义了简写方式,完整的列表如下:\n转义字符 意义 ASCII 码值(十进制) \\a 响铃 (BEL) 007 \\b 退格 (BS) ,将当前位置移到前一列 008 \\f 换页 (FF),将当前位置移到下页开头 012 \\n 换行 (LF) ,将当前位置移到下一行开头 010 \\r 回车 (CR) ,将当前位置移到本行开头 013 \\t 水平制表 (HT) 009 \\v 垂直制表 (VT) 011 \\' 单引号 039 \\\u0026quot; 双引号 034 \\\\ 反斜杠 092 \\n和\\t是最常用的两个转义字符:\n\\n用来换行,让文本从下一行的开头输出; \\t用来占位,一般相当于四个空格,或者 tab 键的功能。 单引号、双引号、反斜杠是特殊的字符,不能直接表示,要表示这些字符本身,需要用转义的形式,即在前面加反斜杠(backslash \\):\n单引号是字符类型的开头和结尾,要使用\\'表示,也即char single_quote = '\\''; 双引号是字符串的开头和结尾,要使用\\\u0026quot;表示,也即char double_quote = '\\\u0026quot;'; 反斜杠是转义字符的开头,要使用\\\\表示,也即char backslash = '\\\\'。 转义字符示例:\n1 2 3 4 5 #include \u0026lt;stdio.h\u0026gt; int main(){ puts(\u0026#34;C\\tC++\\tJava\\n\\\u0026#34;C\\\u0026#34; first appeared!\u0026#34;); return 0; } 运行结果:\nC C++ Java \u0026#34;C\u0026#34; first appeared! Reference C 语言转义字符 ","permalink":"https://guyueshui.github.io/post/what-is-escape-character/","tags":["转义"],"title":"转义字符到底是什么"},{"categories":[],"contents":"在此记录一下我自己用过的非常棒的小软件。\n有必要维护一个自己使用的软件列表。\n套用一下 suckless 的 slogan,\nEverything sucks, we just suck less.\n-- suckless.org 下载 aria2c: 命令行下载工具,支持下载种子、磁力等。有 RPC 模式,配合 WebUI 使用更佳。参考简介。 多媒体 mpv: 命令行多媒体播放器,拥有较强的扩展性和自定义的空间,另外我自己体验上来看比 mplayer 要流畅,mplayer 在我的机子上有丢帧,而 mpv 无明显丢帧。 mpd/mpc: 音乐播放,没有界面。mpd 作为服务端,mpc 作为客户端,占用内存非常低。 OBS stuido: 录屏软件,大而全,跨平台。虽然较大,但实践证明是兼容性最好的,在 linux 上也好使。可以正常录制扬声器和麦克风。 SimpleScreenRecorder: 录屏软件,配置比 obs 少但也够用。 文档 zathura: A vim-like pdf reader. vim 系快捷键,小而轻,但功能也相对较少。 截图 flameshot: gnome-screenshot 的替代品,支持截图后标记,复制到剪贴板;平台:Linux flameshot gui: 直接打开截屏功能,更多参考flameshot -h. peek: 小巧易用的录屏软件,支持录制 gif, mp4, webm 等格式。 看图 imv: feh 的替代,支持多种格式(包括 gif1)。界面简洁只有一个图片框和 feh 一样,但是有必要的信息显示(overlay)。简洁党的最爱。 ImageViewer: 纯 Qt 的图片查看器,简约不简陋。 效率 xpad: 小而轻的桌面便签。 Taskwarrior: A command-line todo manager,不要因为它的强大而忘记使用它的初衷。 ranger: File manager in terminal,三页分栏显示文件树,支持文件预览(需安装对应依赖),支持自定义命令,书签等。 rofi: dmenu 替代品,窗口切换,应用启动器,简约大方,纯文本构成。 Everything: windows 平台,免费且简单易用的全局搜索器,该有的都有。 QTTabBar: 众所周知,windows 文件管理器十分难用,尤其是不支持 tab,所以,它来了。 学习 GoldenDict: 离线词典,支持在线页面查词,接有道,维基等,可以看做是 Linux 上的 Eudic,支持多种离线字典格式,支持自定义快捷键查找剪贴板中的单词。 笔记 Obsidian: markdown 笔记软件,也可做个人知识库管理(过于方法论)。卖点有二,一是支持双向链接,并由此牵扯出一套方法论;二是你的数据你做主,一切笔记皆本地 markdown 文件。你拥有完全的掌控权,即便以后不用它,也很容易迁移到别的软件。对个人永久免费(付费版提供笔记同步、发布服务)。可玩性很强,太过知名,教程软文一搜一大把,还须记住记住本心为宜:我最初是为什么要用它来着? 锁屏 slock: suckless 出品,极简的锁屏软件。简单到什么程度呢?配置文件都没有,直接下源码改头文件来配置。 VSCode 插件 1 2 3 4 5 6 7 8 9 10 11 $ code --list-extensions bungcip.better-toml tomoki1207.pdf huacnlee.autocorrect # 修正中英混合排版的问题 huizhou.githd # git history, blame on single file mhutchie.git-graph # git graph KylinIDETeam.cmake-intellisence llvm-vs-code-extensions.vscode-clangd # c++ dev twxs.cmake # cmake syntax support vadimcn.vscode-lldb # c++ debug vscodevim.vim VSCode C++ 开发配置参考:\n快速搭建一个 C++ Playground https://zhuanlan.zhihu.com/p/566365173 Gif 支持依赖 libnsgif,这个库从 0.2.1 到 1.0.0 的升级太过不兼容,对外接口都改了。导致 imv 的编译依赖指定版本\u0026lt; 1.0.0. 只能本地安装低版本依赖,自己编译。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/nice-softwares/","tags":["tools","tiny","apps","tool-list","software-list"],"title":"小内存机器的自我救赎"},{"categories":["Notes"],"contents":"This article is mainly refered to \u0026ldquo;The Linux Command Line\u0026rdquo;1. I just take some most important things out of the book.\nExpansion Each time you type a command line and press the Enter key, bash performs several processes upon the text before it carries out your command. Just look an example:\n1 2 [me@linuxbox ~]$ echo * Desktop Documents ls-output.txt Music Pictures Public Templates Videos Why not display an asterisk? That\u0026rsquo;s expansion! * expands to all files in current directory.\nPathname expansion 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [me@linuxbox ~]$ ls Desktop ls-output.txt Pictures Templates Documents Music Public Videos [me@linuxbox ~]$ echo D* Desktop Documents [me@linuxbox ~]$ echo *s Documents Pictures Templates Videos [me@linuxbox ~]$ echo [[:upper:]]* Desktop Documents Music Pictures Public Templates Videos [me@linuxbox ~]$ echo /usr/*/share /usr/kerberos/share /usr/local/share Tilde expansion As you know, ~ has a special meaning of the home directory of current user.\n1 2 3 4 5 [me@linuxbox ~]$ echo ~ /home/me [me@linuxbox ~]$ echo ~foo /home/foo Arithmetic expansion The shell allows arithmetic to be performed by expansion. This allows us to use the shell prompt as a calculator:\n1 2 3 4 5 [me@linuxbox ~]$ echo $((2 + 2)) 4 [me@linuxbox ~]$ echo with $((5%2)) left over. with 1 left over. Note: Arithmetic expansion supports only integers (whole numbers, no decimals).\nBrace expansion 1 2 [me@linuxbox ~]$ echo Front-{A,B,C}-Back Front-A-Back Front-B-Back Front-C-Back Patterns to be brace expanded may contain a leading portion called a preamble and a trailing portion called a postscript. The brace expression itself may contain either a comma-separated list of strings or a range of integers or single characters. The pattern may not contain embedded whitespace. Here is an example using a range of integers:\n1 2 3 4 5 6 7 8 [me@linuxbox ~]$ echo Number_{1..5} Number_1 Number_2 Number_3 Number_4 Number_5 [me@linuxbox ~]$ echo {Z..A} Z Y X W V U T S R Q P O N M L K J I H G F E D C B A [me@linuxbox ~]$ echo a{A{1,2},B{3,4}}b aA1b aA2b aB3b aB4b Command substitution Command substitution allows us to use the output of a command as an expansion:\n1 2 3 4 5 [me@linuxbox ~]$ echo $(ls) Desktop Documents ls-output.txt Music Pictures Public Templates Videos [me@linuxbox ~]$ ls -l $(which cp) -rwxr-xr-x 1 root root 71516 2012-12-05 08:58 /bin/cp There is an alternative syntax for command substitution in older shell programs that is also supported in bash . It uses back quotes instead of the dollar sign and parentheses:\n1 2 [me@linuxbox ~]$ ls -l `which cp` -rwxr-xr-x 1 root root 71516 2012-12-05 08:58 /bin/cp Quoting Usage of single or double quotes in shell commands is confusing for me, at least. Hey, take a look:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [me@linuxbox ~]$ echo are you ok are you ok [me@linuxbox ~]$ echo \u0026#39;are you ok\u0026#39; are you ok [me@linuxbox ~]$ echo \u0026#34;are you ok\u0026#34; are you ok [me@linuxbox ~]$ echo The total is $100.00 The total is .00 [me@linuxbox ~]$ echo \u0026#39;The total is $100.00\u0026#39; The total is $100.00 [me@linuxbox ~]$ echo \u0026#34;The total is $100.00\u0026#34; The total is .00 Isn\u0026rsquo;t that confusing? Fortunately, the shell provides a mechanism called quoting to selectively suppress unwanted expansions.\nDouble quotes If you place text inside double quotes, all the special characters used by the shell lose their special meaning and are treated as ordinary characters. The exceptions are $ (dollar sign), \\ (backslash), and ` (back tick).\nBy default, word splitting looks for the presence of spaces, tabs, and newlines (linefeed characters) and treats them as delimiters between words. This means that unquoted spaces, tabs, and newlines are not considered to be part of the text. They serve only as separators. The fact that newlines are considered delimiters by the word splitting mechanism causes an interesting, albeit subtle, effect on command substitution. Consider the following:\n1 2 3 4 5 6 7 8 9 10 11 [me@linuxbox ~]$ echo $(cal) February 2012 Su Mo Tu We Th Fr Sa 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 [me@linuxbox ~]$ echo \u0026#34;$(cal)\u0026#34; February 2012 Su Mo Tu We Th Fr Sa 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 In the first instance, the unquoted command substitution resulted in a command line containing 38 arguments; in the second, the result was a command line with 1 argument that includes the embedded spaces and newlines.\nSingle quotes If we need to suppress all expansions, we use single quotes. Here is a comparison of unquoted, double quotes, and single quotes:\n1 2 3 4 5 6 [me@linuxbox ~]$ echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER text /home/me/ls-output.txt a b foo 4 me [me@linuxbox ~]$ echo \u0026#34;text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER\u0026#34; text ~/*.txt {a,b} foo 4 me [me@linuxbox ~]$ echo \u0026#39;text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER\u0026#39; text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER As we can see, with each succeeding level of quoting, more and more expansions are suppressed.\nVariables and assignments the shell does not care about the type of data assigned to a variable; it treats them all as strings. in an assignment, there must be no spaces between the variable name, the equal sign, and the value. 1 2 3 4 5 6 a=z # Assign the string \u0026#34;z\u0026#34; to variable a. b=\u0026#34;a string\u0026#34; # Embedded spaces must be within quotes. c=\u0026#34;a string and $b\u0026#34; # Other expansions such as variables can be expanded into the assignment. d=$(ls -l foo.txt) # Results of a command. e=$((5 * 7)) # Arithmetic expansion. f=\u0026#34;\\t\\ta string\\n\u0026#34; # Escape sequences such as tabs and newlines. \u0026ldquo;Here documents\u0026rdquo; A here document is an additional form of I/O redirection in which we embed a body of text into our script and feed it into the standard input of a command. It works like this:\n1 2 3 command \u0026lt;\u0026lt; token text... token where command is the name of a command that accepts standard input and token is a string used to indicate the end of the embedded text. For example, you\n1 2 3 4 5 6 7 8 9 10 $ cat \u0026lt;\u0026lt; _EOF_ heredoc\u0026gt; are you heredoc\u0026gt; ok? heredoc\u0026gt; Yes i am. heredoc\u0026gt; And you? heredoc\u0026gt; _EOF_ are you ok? Yes i am. And you? Namely, you just use a custom EOF indicator of the input to that command. So what’s the advantage of using a here document? It’s mostly the same as echo , except that, by default, single and double quotes within here documents lose their special meaning to the shell. Here is a command-line example:\n1 2 3 4 5 6 7 8 9 10 11 [me@linuxbox ~]$ foo=\u0026#34;some text\u0026#34; [me@linuxbox ~]$ cat \u0026lt;\u0026lt; _EOF_ \u0026gt; $foo \u0026gt; \u0026#34;$foo\u0026#34; \u0026gt; \u0026#39;$foo\u0026#39; \u0026gt; \\$foo \u0026gt; _EOF_ some text \u0026#34;some text\u0026#34; \u0026#39;some text\u0026#39; $foo As we can see, the shell pays no attention to the quotation marks. It treats them as ordinary characters. This allows us to embed quotes freely within a here document.\nIf we change the redirection operator from \u0026lt;\u0026lt; to \u0026lt;\u0026lt;- , the shell will ignore leading tab characters in the here document. This allows a here document to be indented, which can improve readability:\n1 2 3 4 5 6 7 8 9 10 $ cat \u0026lt;\u0026lt;- E_o_F heredocd\u0026gt; no indent heredocd\u0026gt; indent heredocd\u0026gt; hiahia heredocd\u0026gt; E_o_F heredocd\u0026gt; E_o_F no indent indent hiahia E_o_F Flow control Using if The syntax of if-statement is\n1 2 3 4 5 6 7 if commands; then commands [elif commands; then commands...] [else commands] fi where commands is a list of commands. This may be a little confusing at first glance, \u0026ldquo;if\u0026rdquo; should judge a condition rather than a series of commands. This leads to the concept of exit status.\nCommands (including the scripts and shell functions we write) issue a value to the system when they terminate, called an exit status. This value, which is an integer in the range of 0 to 255, indicates the success or failure of the command’s execution. By convention, a value of 0 indicates success, and any other value indicates failure. The shell provides a parameter that we can use to examine the exit status. Here we see it in action:\n1 2 3 4 5 6 7 8 [me@linuxbox ~]$ ls -d /usr/bin /usr/bin [me@linuxbox ~]$ echo $? 0 [me@linuxbox ~]$ ls -d /bin/usr ls: cannot access /bin/usr: No such file or directory [me@linuxbox ~]$ echo $? 2 The shell provides two extremely simple built-in commands that do nothing except terminate with either a 0 or 1 exit status. The true command always executes successfully, and the false command always executes unsuccessfully:\n1 2 3 4 5 6 [me@linuxbox ~]$ true [me@linuxbox ~]$ echo $? 0 [me@linuxbox ~]$ false [me@linuxbox ~]$ echo $? 1 Using test By far, the command used most frequently with if is test. The test command performs a variety of checks and comparisons. It has two equivalent forms:\n1 2 3 test expression # or [ expression ] where \u0026ldquo;expression\u0026rdquo; is an expression that is evaluated as either true or false. The test command returns an exit status of 0 when the expression is true and a status of 1 when the expression is false.\nFile expressions\nExpression Is true if\u0026hellip; file1 -nt file2 file1 is newer than file2. file1 -ot file2 file1 is older than file2. -d file file exists and is a directory. -e file file exists. -L file file exists and is a symbolic link. -r file file exists and is readable (has readable permission for the effective user). -s file file exists and has a length greater than zero. -w file file exists and is writable (has writable permission for the effective user). -x file file exists and is executable (has execute/search permission for the effective user). String expressions\nExpression Is true if\u0026hellip; string string is not null -n string the length of string is greater than zero -z string the length of string is zero string1 == string2 string1 and string2 are equal. string1 != string2 string1 and string2 are not equal. string1 \u0026gt; string2 string1 sorts after string2. string1 \u0026lt; string2 string1 sorts before string2. Note: the \u0026gt; and \u0026lt; expression must be quoted (or escaped with a backslash) when used with test, oterwise they will be interpreted as the shell redirection opreators.\nInteger expressions\nExpression Is true if\u0026hellip; integer1 -eq integer2 integer1 is equal to integer2 integer1 -ne integer2 integer1 is not equal to integer2 integer1 -le integer2 integer1 is less than or equal to integer2 integer1 -lt integer2 integer1 is less than integer2 integer1 -ge integer2 integer1 is greater than or equal to integer2 integer1 -gt integer2 integer1 is greater than integer2 Modern test Recent versions of bash include a compound command that acts as an enhanced replacement for test . It uses the following syntax:\n1 [[ expression ]] where expression is an expression that evaluates to either a true or false result. The [[ ]] command is very similar to test (it supports all of its expressions) but adds an important new string expression:\n1 string =~ regex which returns true if string is matched by the extended regular expression regex.\n(( ))-Designed for integers (( )) is used to perform arithmetic truth tests. An arithmetic truth test results in true if the result of the arithmetic evaluation is non-zero.\n1 2 3 4 [me@linuxbox ~]$ if ((1)); then echo \u0026#34;It is true.\u0026#34;; fi It is true. [me@linuxbox ~]$ if ((0)); then echo \u0026#34;It is true.\u0026#34;; fi [me@linuxbox ~]$ Shell function Shell functions must be declared before using it. There are two ways to define a shell function:\n1 2 3 4 5 6 7 8 9 function name { commands return } name () { commands return } Use local variables in shell functions Local variables are accessible only within the shell function in which they are defined, and they cease to exist once the shell function terminates.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #!/bin/bash # local-vars: script to demonstrate local variables foo=0 # global func1() { local foo=1 # local echo \u0026#34;func1 foo = $foo\u0026#34; } func2() { local foo=2 # local echo \u0026#34;func1 foo = $foo\u0026#34; } echo \u0026#34;global foo = $foo\u0026#34; func1 echo \u0026#34;global foo = $foo\u0026#34; func2 echo \u0026#34;global foo = $foo\u0026#34; 1 2 3 4 5 6 $ ./local-vars global foo = 0 func1 foo = 1 global foo = 0 func1 foo = 2 global foo = 0 We see that the assignment of values to the local variable foo within both shell functions has no effect on the value of foo defined outside the functions.\nTO BE CONTINUED\u0026hellip;\n\u0026ldquo;The Linux Command Line, A Complete Introduction\u0026rdquo;, William E. Shotts, Jr.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/shell-intro/","tags":["shell"],"title":"Brief Introduction to Shell Script"},{"categories":["notes"],"contents":"Hello here.\nCNN Conv Layer Conv Layer is usually decreasing the input size, i.e., the output size may less or equal than input.\ntake a volume as input: height x weight x depth, e.g., 32x32x3. Typically think an image having three channels: R, G, B. a filter has the same depth as the input volume, e.g., 5x5x3 (since the filter always has a same depth as input vloume, the depth of the filter is sometimes omitted). each filter convolving with the input will produce an activation map, two filters will produce two, etc. The result of the convolution at each location is just a scalar number (the result of taking a dot product between the filter and a small chunk of the image, i.e., $5\\times 5 \\times 3 = 75$-dimensional dot product + bias: $w^\\top x + b$), which totally yields a 2D matrix (called activation map) as the filter sliding over the image. For example, 32x32x3 image convolved by 5x5x3 filter will yield a 28x28 activation map.\nConvNet is a sequence of convolution layers, interspersed with activation functions.\nLet\u0026rsquo;s find out how the spatial dimensions change (since the depth of input and filter will always match, and then shrinks to 1 in actication map). Suppose we have 7x7 input spatially, 3x3 filter, then we have 5x5 output. If applied with stride 2, then 3x3 output. What about stride 3? Oh, it doesn\u0026rsquo;t fit, you cannot apply 3x3 filter on 7x7 input with stride 3.\nOutput size: (N-F)/stride + 1 for NxN input, FxF filter.\nTake padding into account since it\u0026rsquo;s common to zero pad the border. E.g., 7x7 input, 3x3 filter, applied with stride 1, pad with 1 pixel border, what is the output? Oh, it\u0026rsquo;s 7x7 output! In general, common to see CONV layers with stride 1, filters of size FxF, and zero-padding with (F-1)/2, which will perserve size spatially.\nOutput size: $(N + 2\\times padding - F) / stride + 1$.\nTrain yourself, 32x32x3 input, 10 5x5 filters with stride 1, pad 2, what is the output volume size? Oh, 32x32x10 output! What is the number of parameters in this layer? Oh, $(5 \\times 5 \\times 3 + 1) \\times 10 = 760$, plus 1 for bias.\nTo summarize, the Conv Layer:\nAccepts a volume of size $W_1 \\times H_1 \\times D_1$ Requires four hyperparameters: Number of filters K, their spatial extent F, the stride S, the amount of zero padding P. Produces a volume of size $W_2 \\times H_2 \\times D_2$ where: $W_2 = (W_1 − F + 2P)/S+1$ $H_2 = (H_1−F+2P)/S+1$ (i.e. width and height are computed equally by symmetry) $D_2=K$ With parameter sharing, it introduces $F\\cdot F\\cdot D_1$ weights per filter, for a total of $(F\\cdot F\\cdot D_1)\\cdot K$ weights and $K$ biases. In the output volume, the $d$-th depth slice (of size $W_2 \\times H_2$) is the result of performing a valid convolution of the $d$-th filter over the input volume with a stride of $S$, and then offset by $d$-th bias. A common setting of the hyperparameters is $F=3,S=1,P=1$. However, there are common conventions and rules of thumb that motivate these hyperparameters. $F$ is usually odd, $K$ is usually power of 2 for computation efficiency.\nConvTranspose Layer ConvTranspose Layer is usually increasing the input size, i.e., the output size is greater or equal than input.\nConvTranspose is also called transposed convolution, deconvolution, etc. For convenience, we first summarize, the ConvTanspose layer:\nAccepts a volume of size $W_1 \\times H_1 \\times D_1$ Requires four hyperparameters: Number of filters K, their spatial extent F, the stride S, the amount of zero padding P. Produces a volume of size $W_2 \\times H_2 \\times D_2$ where: $W_2 = S(W_1 − 1) + F - 2P$ $H_2 = S(H_1−1) + F - 2P$ (i.e. width and height are computed equally by symmetry) $D_2=K$ The following isn\u0026rsquo;t that correct.\nThen what on earth does transposed convolution do? In fact, a transposed convolution has an associated convolution with $F \\times F$ filter, $1/S$ stride, $(F-P-1)$ padding, here i haven\u0026rsquo;t configured it out. Recall that, the layer requires four parameters while K does not affect the spatial size ($W \\times H$).\nSo if we have a square input volumn of size $I \\time I \\times D$, and we pass it into an ConvTranspose Layer with parameter (K, F, S, P), then the output has shape:\n$$ \\begin{split} O \u0026amp;= \\frac{I + 2(F-P-1) - F}{1/S} + 1 = \\end{split} $$\nPooling Layer makes the representations smaller and more manageable operates over each activation map independently Intuition of max pooling: select the neuron whose response is maximal.\nReferences cs231n winter 2016 cs231n notes Convolution arithmetic tutorial conv_arithmetic ","permalink":"https://guyueshui.github.io/post/nn-notes/","tags":["cnn"],"title":"Nueral Network Learning Notes"},{"categories":["Notes"],"contents":"操作系统 摘自《程序员面试白皮书》\n进程 vs.线程 进程(process)与线程(thread)最大的区别是进程拥有自己的地址空间,某进程内的线程对于其他的进程不可见,即进程 A 不能通过传地址的方式直接读写进程 B 的存储区域。进程之间的通信需要通过进程间通信(Inter-process communication, IPC)。与之相对的,同一进程的各线程间可以直接通过传递地址或全局变量的方式传递信息。\n此外,进程作为操作系统中拥有资源和独立调度的基本单位,可以拥有多个线程。通常操作系统中运行的一个程序就对应一个进程。在同一进程中,线程的切换不会引起进程切换。在不同的进程中进行线程切换,若从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。相比进程切换,线程切换的开销要小很多。线程与进程相结合能够提高系统的运行效率。\n线程可以分为两类: 一类是用户级线程(user level thread)。对于这类线程,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。在应用程序启动后,操作系统分配给该程序一个进程号,以及其对应的内存空间等资源。应用程序通常现在一个线程中运行,该线程被称为主线程。在其运行的某个时刻,可以通过调用线程库中的函数创建一个在相同进程中运行的新线程。用户级线程的好处是非常高效,不需要进入内核空间,但并发效率不高。\n另一类是内核级线程(kernel level thread)。对于这类线程,有关线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只能调用内核线程的接口。内核维护进程及其内部的每个线程,调度也有内核基于线程架构完成。内核级线程的好处是,内核可以将不同的线程更好地分配到不同的 CPU,以实现真正的并行计算。\n事实上,在现代操作系统中,往往使用组合方式实现多线程,即线程创建完全在用户空间中完成,并且一个应用程序中的多个用户级线程被映射到一些内核级线程上,相当于是一种折中方案。\n上下文切换 对于单核单线程 CPU 而言,在某一时刻只能执行一条 CPU 指令。上下文切换(context switch)是一种将 CPU 资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),在读入下一个进程的状态,然后执行此进程。\n系统调用 系统调用(system call)是程序向系统内核请求服务的方式。可以包括硬件相关的服务(访问硬盘等),或者创建进程,调度其他进程等。系统调用是程序和操作系统之间的重要接口。\nSemaphore/Mutex 当用户创立了多个线程/进程时,如果不同线程/进程同时读写相同的内容,则可能造成读写错误,或者数据不一致。此时,需要通过加锁的方式,控制核心区域(critical section)的访问权限。对于 semaphore 而言,在初始化变量的时候可以控制允许多少个线程/进程同时访问一个 critical section,其他的线程/进程会被堵塞,直到有人解锁。Mutex 相当于只允许一个线程/进程访问的 semaphore。此外,根据实际需要,人们还实现了一种读写锁(read-write lock),它允许同时存在多个 reader,但任何时候之多只有一个 writer,且不能和 reader 共存。\n死锁 在引入锁的同时,我们遇到了一个新的问题:死锁(dead lock)。死锁是指两个或多个线程/进程之间相互阻塞,以至于任何一个都不能继续运行,因此也不能解锁其他线程/进程。例如,线程 1 占有锁 A,并且尝试获取锁 B;而线程 2 占有锁 B,并尝试获取锁 A。此时,两者相互阻塞,都无法继续运行。\n总结产生死锁的四个条件(只有当四个条件同时满足时才会产生死锁):\nMutual Exclusion \u0026ndash; Only one process may use a resource at a time Hold-and-Wait \u0026ndash; Process holds resource while waiting for another No Preemption \u0026ndash; Can\u0026rsquo;t take a resouce away from a process Circular Wait \u0026ndash; The waiting processes form a cycle 生产者消费者 生产者消费者模型是一种常见的通信模型:生产者和消费者共享一个数据管道,生产者将数据写入 buffer,消费者从另一头读取数据。对于数据管道,需要考虑为空和溢出的情况。同时,通常还需要将这部份共享内存用 mutex 加锁。在只有一个生产者消费者的情况下,可以设计无锁队列(lockfree queue),线程安全地直接读写数据。\n进程间通信 在介绍进程的时候,我们提起过不能直接读写另一个进程的数据。两者之间的通信需要通过进程间通信(inter-process communication,IPC)进行。进程通信的方式通常遵循生产者消费者模型,需要实现数据交换和同步两大功能。\nShared-memory + semaphore: 不同进程通过读写操作系统中特殊的共享内存进行数据交换,进程之间用 semaphore 实现同步。 Message passing: 进程在操作系统内部注册一个端口,并检测有没有数据,其他进程直接写数据到该端口。该通信方式更加接近与网络通信方式。事实上,网络通信也是一种 IPC,知识进程分布在不同机器上而已。 网络 摘自《程序员面试白皮书》\n网络分层 计算级之间的交互模型通常是指 Open System Interconnection model (OSI), 该模型将网络通信系统抽象成了七层。因其不太实用,工业界普遍使用简化的 TCP/IP 五层模型。\n应用层 针对特定的协议,为应用程序做服务,比如 SMP,POP3,SSH,FTP 等协议。 表示层 负责数据格式的转换,把不同表现形式的信息转换成适合网络传输的格式。 会话层 负责建立和断开通信连接,什么时候建立连接,什么时候断开连接以及保持多久的连接。 传输层 在两个通信节点之间负责数据的传输,起着可靠传输的作用。(运行在这一层的设备有四层交换机,四层路由器) 网络层 路由选择,在多个网络之间转发数据包,负责将数据包传送到目的地址。(运行在这一层的设备有路由器,三层交换机) 数据链路层 负责物理层面互联设备之间的通信传输,比如一个以太网相连的两个节点之间的通信,是数据帧与 1、0 比特流之间的转换。(运行在这一层的设备是网桥、以太网交换机、网卡) 物理层 主要是 1、0 比特流与电子信号的高低电平之间的转换。(运行在这一层的设备是中继器、双绞线) 路由 从用户角度看,路由(routing)是指将数据从一个用户终端,通过网络节点(例如路由器、交换机等),发送到另一个用户节点的过程。理论上说,对于一个拥有多个节点的拓扑网络而言,路由是指在 Network Layer(OSI model 的第三层)将数据包(data packet)从一个节点以最优的路径发送到目标节点的实现方法。其核心包括:如何获得邻近节点的信息,如何估计链路质量,如何寻址,如何构建网络拓扑等等。通过路由器之间的路由协议(routing protocol)可以实现两个网络节点之间信息(包括网络域名,邻近节点,链路质量等)的交换和散布,通过不断重复该过程,每个节点都会获得足够多关于所在网络的拓扑信息。当有数据包需要传送时,路由器再通过路由算法(routing algorithm)计算传递当前数据包的最优路径,并把数据包发送给下一个邻近节点。许多路由算法基于图理论,实现了最小生成树,最短路径等等经典的拓扑算法。\n网络中,所谓地址是指 IP 地址,IPv4 规定利用 32bits 作为 IP 地址(即 4bytes,也就是我们常看到的 x.x.x.x,x 在 0-255 之间,2^8-1=255)。但随着网络设备的增多,IPv4 已经不能满足人们的需求,故互联网逐渐向 IPv6 进行演进,IPv6 利用 128bits 作为 IP 地址。\n事实上,直观而言,network routing 的过程就相当于传统意义上的邮包寄送,IP 地址可以类比于邮政编码,路由器就相当于邮局,通过目的地邮政编码与邮局系统中的递送路径进行比较,由此确定下一步应该把当前包裹传递到哪里。\n网络统计指标 带宽/速率(Bandwidth/Rate)\n所谓带宽是指一个网络节点能以多快的速度将数据接收/发送出去,单位时 bits per second(bps)。对于实时性要求不高的数据,例如下载等,带宽时影响用户体验的主要因素。两个终端节点之间的带宽由路径中所有节点的最小带宽决定。同时,重算的数据发送速度不应超过当前的上载带宽,否则会对网络造成压力导致拥塞(congestion)。\nOne-way Delay/Round Trip Time\nOne-way Delay 用以衡量网络的延迟。假设在时间点 A 从一个节点发送数据到另一个节点,目的节点在时间 B 收到数据,则两个时间点之差即为 One-way Delay. 类似的,RTT 则是数据完成一个 Round Trip 回到始发节点的时间差,一般 RTT 可以近似估计为 One-way Delay 的两倍。对于网络会议,IP 电话等,延迟时影响用户体验的主要因素。延迟可能是由网络中某个节点处理数据速度慢,突然有大规模数据需要传输,或者某条链路不断重传数据造成的。延迟与带宽有一定的相关性,但没有必然联系。\nTCP vs. UDP 在 Transport Layer,数据流被分块传输。最常见的协议是 TCP 和 UDP。\nTCP\nReliable Protocol TCP(Transmission Control Protocol)是一种可靠的传输协议,在网络条件正常的情况下,TCP 协议能够保证接收端收到所有数据,并且接受到的数据顺序与发送端一致。TCP 通过在发送端给每个数据包分配单调递增的 sequence number,以及在接收端发送 ACK(acknowledgement)实现可靠传输。每个发送的数据包都包含序列号,当接收端收到数据包时,会发送 ACK 告诉发送端当前自己期待的下一个序列号是多少。例如,发送端分别发送了序列号为 99、100、101、102 的四个数据包,接收端收到数据包 99 后,会发送 ACK 100,意味着接收端期待下一个数据包编号 100. 如果由于某些原因,数据包 100 没有到达接收端,但数据包 101、102 到达了,那么接收端会继续发送 ACK 100. 当发送端发现当前发送的数据包编号超过了 100,但接收端仍然期望收到 100,那么发送端就会重新发送数据包 100. 如果接收端收到了重新发送的数据包 100,那么接收端会回复 ACK 103,继续进行剩下的数据传输,并且把数据包 99、100、101、102 按顺序传递给上一层。\nFlow Control TCP 使用了 end-toend flow control 以避免发送端发送数据过快导致接收端无法处理。TCP 采用了滑动窗口(sliding window)实现流量控制。接收端通过 ACK 告诉发送端自己还能接收多少数据,发送端不能发送超过该值的数据量。当接收端返回的窗口大小为 0 时,发送端停止发送数据,直到窗口大小被更新。由于 ACK 是由发送端发送的数据触发,可能接收端窗口已经打开,但是由于发送端已经停止发送,故接收端没有机会通过 ACK 告知发送端新的窗口大小,在这种情况下会造成死锁。在实际实现中,发送端会设置一个 timer,如果 timer 到期,发送端会尝试发送小数据包,以触发接收端 ACK.\nCongestion Control 为了控制传输速度防止堵塞网络,并且在网络容量允许的范围内尽可能多的传输数据,TCP 引入 congestion control,用以判断当前的网络负荷,并且调整传输速率。TCP 通常采用 additive increase,multiplicative decrease 的算法,即如果按时收到对应的 ACK,则下一次传输速率线性增加,否则视为发生了网络拥塞,下一次传输的比特数折半。所谓的“按时”基于 RTT:发送端会估计 RTT,并且期望当数据包发送以后,在 RTT 时间内收到对应的 ACK。现在 TCP 需要分别实现 slow-start,congestion avoidance,fast retransmit 和 fast-recovery,以达到最高的效率。\nUDP\n相比与 TCP,UDP(User Datagram Protocol)简单许多:连接建立时不需要经过类似于 TCP 的三次握手,只需要知道接收端的 IP 和端口,发送端就可以直接发送数据。同时,UDP 也没有 ACK,flow control 和 congestion control,故 UDP 相较 TCP 传输更快,使用起来更简单。\nUDP 介绍 摘自《计算机网络 第四版》\nUDP 面向无连接,它传输的数据段(segment)是由 8 字节的头和净荷域构成的。头包含源端口和目标端口,各占 16 位,共 4 字节。两个端口分别被用来标识源机器和目标机器内部的端点。当一个 UDP 分组到来的时候,它的净荷部分被递交给与目标端口相关联的那个进程。这种关联关系是在调用了 bind 原语或者其他某一种类似的做法之后建立起来的。实际上,采用 UDP 而不是原始的 IP,其最主要的价值是增加了源端口和目标端口。如果没有端口域,则传输层将不知道该如何处理分组;而有了端口之后,它就可以正确地提交数据段了。\n当目标端必须将一个应答送回给源端地时候,源端口是必须地。发送应答的进程只要将进来的数据段中的 source port 域复制道输出的数据段中的 destination port 域,就可以指定在发送方机器上由哪个进程来接受应答。\n另外值得提出来的可能是 UDP 没有做到一些事情。UDP 并不考虑流控制、错误控制,在收到一个坏的数据段之后它也不重传。所有这些工作都留给用户进程。UDP 所作的事情就是提供一个接口,并且在接口中增加复用(demultiplexing)的特性。他利用端口的概念将数据段解复用到多个进程中。这就是它所做的全部工作。\nUDP 尤其适用域 C-S 架构下,客户端给服务器发送一个短的请求,并且期望一个短的应答回来,如果请求或者应答丢失,只需要超时重传。\nUDP 的一个应用时 DNS(Domain Name System),简单来说,如果一个程序需要根据某一个主机名(比如 www.cs.berkeley.edu)来查找它的 IP 地址,那么,它可以给 DNS 服务器发送一个包含该主机名的 UDP 分组。服务器用一个包含该主机 IP 地址的 UDP 分组作为应答。实现不需要建立连接,事后也不需要释放连接。在网络上只要两条消息就够了。DNS 在进行区域传输 (主从 dns server 之间的数据同步) 的时候使用 TCP,普通的查询使用 UDP。因为普通查询数据量小,比较适合用 udp 这种速度更快。\nRPC 从某种意义上讲,向一台远程主机发送一个消息并获得一个应答,就如同在编程语言中执行一个函数调用一样。在这两种情况下,你都要提供一个或多个参数,然后获得一个结果。这种现象导致人们试图将网络上的请求 - 应答交互过程,做成像过程调用那样可以进行类型匹配和转换。这样的结构是的网络应用更加易于编程,而且人们对这种处理方式也更加熟悉。例如,假设有一个名为getIPaddress(hostname)的过程,它的工作方式为:向 DNS 服务器发送一个 UDP 分组,然后等待应答,如果在规定的时间内没有接收到应答的话,则超时并重试。通过这种方式,网络的所有细节对于程序员而言全部隐藏。\n这个领域中的关键工作由 Birrel 和 Nelson(1984)完成。简单来说:允许本地的程序调用远程主机上的过程。当机器 A 上的进程调用机器 B 上的一个过程的时候,机器 A 上的调用进程被挂起,而机器 B 上被调用的过程则开始执行。参数信息从调用方传输到被调用方,而过程的执行结果则从反方向传递回来。对于程序员而言,所有的消息传递都是不可见的。这项技术称为 RPC(Remote Procedure Call),目前已成为许多网络应用的基础。按照传统,调用过程称为客户,被调用过程称为服务器。\n当然,RPC 不一定非得使用 UDP 分组,但是,RPC 和 UDP 是一对很好的搭档,而且,UDP 常常被用于 RPC。然而,当参数或者结果值可能超过最大的 UDP 分组的时候,或者当所请求的操作并不幂等(即不能安全地重复多次执行,比如计数器递增地操作)的时候,可能有必要建立一个 TCP 连接,然后利用该连接来发送请求,而不是使用 UDP 来完成远程调用。\nRTC UDP 的还被广泛应用在实时多媒体:Internet 广播电台、Internet 电话、音乐点播、视频会议、视频点播等。人们发现每一种应用都在重复设计几乎相同的实时传输协议,逐渐地人们意识到,为多媒体应用制定一个通用的协议是一个很好的想法,因此就诞生了 RTP(Real-time Transport Protocol)。\nTCP 介绍 UDP 是一个简单的协议,它有一些非常合适的用途。但是对于大多数 Internet 应用来说,他们更需要可靠的,按序递交的特性。所以还需要另一个协议,这就是 TCP,目前它是 Internet 上承担任务最为繁重的一个协议。\nTCP 是专门为了在不可靠的互联网上提供一个可靠的端到端字节流而设计的。每台支持 TCP 的机器都有一个 TCP 传输实体,它或者是一个库过程,或者是一个用户进程,或者是内核的一部分。在所有这些情形下,它管理 TCP 流,以及与 IP 层之间的接口。TCP 传输实体接受本地进程和用户数据流,并且将他们分割成不超过 64KB(在实践中,考虑到每个帧中都希望有 IP 和 TCP 头,所以通常不超过 1460 数据字节)的分片,然后以单独的 IP 数据报的形式发送每一个分片。当包含 TCP 数据的数据报到达一台机器的时候,他们被递交给 TCP 传输实体,然后 TCP 传输实体再重构出原始的字节流。\nIP 层并不保证数据报一定被正确的递交到目标端,所以 TCP 需要判断超时的情况,并且需要根据需要重传数据报。即使被正确递交的数据报,也可能存在错序的问题,这也是 TCP 的责任,他必须把接收到的数据报按照正确的顺序重新装配成用户消息。\nTCP 服务模型 要想获得 TCP 服务,发送方和接收方必须创建一种被称为套接字的端点。每个套接字有一个套接字号(地址),它是由主机的 IP 地址以及本地主机局部的一个 16 为数值组成的,此 16 为数值被称为端口(port)。端口是一个 TSAP 的 TCP 名字。为了获得 TCP 服务,首先必须要显示的再发送机器的套接字和接受机器的套接字之间建立一个连接。\n一个套接字有可能同时被用于多个连接。换句话说,两个或者多个连接可能终止与同一个套接字。每个连接可以用两端的套接字标识符来标识,即(socket1, socket2)。TCP 不适用虚电路号或者其他的标识符。\n1024 以下的端口号被称为知名端口(well-known port),其实就是系统保留端口,有很多约定的服务和特定的端口号对应,如 ssh 默认端口号是 22.\n应用程序 FTP TFTP TELNET SMTP DNS HTTP SSH MYSQL 端口 21, 20 69 23 25 53 80 22 3306 传输层协议 TCP UDP TCP TCP UDP TCP TCP TCP 所有的 TCP 连接都是全双工的,并且是点到点的。所谓全双工,意味着同时可在两个方向上传输数据;而点到点则意味着每个连接恰好有两个端点。TCP 并不支持多播或者广播传输模式。\n一个 TCP 连接就是一个字节流,而不是消息流。端到端之间并不比保留消息的边界。例如,如果发送进程将 4 个 512 字节的数据块写到一个 TCP 流中,那么在接收进程中,这些数据有可能按 4 个 512 字节快的方式被递交,也可能是 2 个 1024 字节的数据块,或是一个 2048 字节的数据块,或者其他方式。接收方无法获知这些数据被写入字节流时候的单元大小。\n正如 Unix 中文件一样,读文件的程序无法判断该文件是怎么写成的,是一次性还是分块写入,然而,程序也无意于去弄清这个事情。一个 TCP 软件不理解 TCP 字节流的含义,也无意于弄清其含义,一个字节就是一个字节而已。\n当一个应用将数据传递给 TCP 的时候,TCP 可能立即将数据发送出去,也可能将它缓冲起来(为了收集更多的数据从而一次发送出去),这完全由 TCP 软件自己来决定。然而,有时候应用程序确实希望自己的数据立即被发送出去,例如,假设一个用户已经登陆到一台远程服务器上,用户每输入一行命令就会敲入回车键,这时候该命令行应该被立即发送到远程主机,而不应该缓冲起来等待下一行命令。为了强迫将数据发送出去,应用程序可以使用 PUSH 标志,它相当于告诉 TCP 不要延迟传输过程。\n有关 TCP 服务的最后一个值得在这里提出来的特性是紧急数据(urgent data)。当一个交互用户通过敲入DEL或者CTRL-C来打断一个已经开始运行的远程计算过程的时候,发送方应用把一些控制信息放在数据流中,然后将它联通 URGETN 标志一起交给 TCP。这一事件将使得 TCP 停止继续积累数据,而是将该连接上已有的所有数据立即传输出去。当目标端接收到紧急数据的时候,接收方应用被中断(比如,按 Unix 的术语来说得到了一个信号),所以它停止当前正在做的工作,并且读入数据流以找到紧急数据。紧急数据的尾部应该被标记出来,所以,如何发现紧急数据要取决于具体的应用程序。这种方案基本上只是提供了一种原始的信号机制,其余的工作全部留给应用程序自己来处理。\nTCP 协议 TCP 的一个关键特征,也是主导了整个协议设计的特征是,TCP 连接上的每个字节都有它独有的 32 位序列号。发送端和接收端的 TCP 实体以数据段的形式交换数据。TCP 数据段(TCP segment)是由一个固定的 20 字节的头(加上可选的部分)以及随后的 0 个或者多个数据字节构成的。TCP 软件决定数据段的大小,它可以将多次写操作的数据累积起来放到一个数据段中,也可以将一次写操作的数据分割到多个数据段中。有两个因素限制了段的长度:第一,每个数据段,包括 TCP 头在内,必须适合 IP 的 65515 字节净荷大小;其次,每个网络都有一个最大传输单元(Maximum Transfer Unit)MTU,每个数据段必须适合于 MTU。在实践中,MTU 通常是 1500 字节(以太网的净荷大小),因此它规定了数据段长度的上界。\n源端口和目的端口,各占 2 个字节,分别写入源端口和目的端口; 序号(seq number),占 4 个字节,TCP 连接中传送的字节流中的每个字节都按顺序编号。例如,一段报文的序号字段值是 301,而携带的数据共有 100 字段,显然下一个报文段(如果还有的话)的数据序号应该从 401 开始; 确认号(ack number),占 4 个字节,是期望收到对方下一个报文的第一个数据字节的序号。例如,B 收到了 A 发送过来的报文,其序列号字段是 501,而数据长度是 200 字节,这表明 B 正确的收到了 A 发送的到序号 700 为止的数据。因此,B 期望收到 A 的下一个数据序号是 701,于是 B 在发送给 A 的确认报文段中把确认号置为 701; 数据偏移,占 4 位,它指出 TCP 报文的数据距离 TCP 报文段的起始处有多远; 保留,占 6 位,保留今后使用,但目前应都位 0; 紧急 URG,当 URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据; 确认 ACK,仅当 ACK=1 时,确认号字段才有效。TCP 规定,在连接建立后所有报文的传输都必须把 ACK 置 1; 推送 PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将 PSH=1; 复位 RST,当 RST=1,表明 TCP 连接中出现严重差错,必须释放连接,然后再重新建立连接; 同步 SYN,在连接建立时用来同步序号。当 SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使 SYN=1,ACK=1; 终止 FIN,用来释放连接。当 FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放; 窗口,占 2 字节,指的是通知接收方,发送本报文你需要有多大的空间来接受; 检验和,占 2 字节,校验首部和数据这两部分; 紧急指针,占 2 字节,指出本报文段中的紧急数据的字节数; 选项,长度可变,定义一些其他的可选的参数。 注意:标志位ACK和确认序列号 ack,ack 是一个变量,它表示本机期待的下个包的序列号(seq),ack 的全称叫做 acknowledgement number. 而ACK是一个标志位,在 TCP 头部可以看到,标志位只能为 0 或 1,用来传输特定的信息。\nTCP 连接的建立 关于 TCP 的三握四挥参见 1,讲的很详细,以下绝大部分参考自该文。\nTCP 使用三步握手法建立连接。为了一个建立一个连接,某一方,比如说服务器,通过执行 LISTEN 和 ACCEPT primitives(既可以指定一个特定的源,也可以不指定)被动地等待一个进来地连接请求。\n另一端,比如客户端,执行一个 CONNECT primitive,同时指定以下参数:它希望连接地 IP 地址和端口、它愿意接受地最大 TCP 分段长度,以及一些可选地用户数据(比如口令)。CONNECT primitive 发送一个 SYN=1 和 ACK=0 的 TCP 数据段,然后等待应答。\n当这个数据段到达目标端的时候,那里的 TCP 实体查看一下是否有一个进程已经在 Destination port 域中指定的端口上执行的 LISTEN。如果没有的话,它送回一个设置了 RST 位的应答,以拒绝客户的连接请求。\n如果某个进程正在监听端口,那么,TCP 实体将进来的 TCP 数据段交给该进程。然后该进程可以接受或者拒绝这个连接请求。如果它接受的话,则送回一个确认数据段。在正常情况下发送的 TCP 数据段顺序如图(a)所示。请注意,SYN 数据段只消耗 1 字节的序列号空间,所以它的确认是非常明确的,毫无二义性。\n如果两台主机同时企图在同样的两个套接字之间建立一个连接,则事件序列如上图(b)所示。这些事件的结果是,只有一个连接被建立起来,而不是两个,因为所有的连接都是由它们的端点来标识的。如果第一个请求导致建立了一个由 (x,y) 标识的连接,而第二个请求也建立了这样一个连接,那么,在 TCP 实体内部只有一个表项,即 (x,y).\n具体来说:\n一方(客户端)发起连接请求,发送一个包(seq=x,SYN=1,ACK=0),进入 SYN-SENT 状态; 另一方(服务端)收到请求,如果同意连接,则发出确认包(seq=y,ack=x+1,SYN=1,ACK=1),进入 SYN-RCVD 状态; 客户端收到确认的包后,也需要向服务端发送确认包(seq=x+1,ack=y+1,SYN=0,ACK=1),进入 ESTABLISHED 状态; 服务端收到确认的包后,进入 ESTABLISHED 状态,至此,连接已建立。 为什么客户端最后还要发送一次确认?\n假设客户端发送的第一个连接请求没有丢失,只是在网络节点中滞留的时间太长了,由于客户端没有收到服务端的回复,所以会超时重传,无论如何,客户端发送了两个连接请求,如果采用两次握手,那么服务端因为收到了两次连接请求,会开启两个连接在等待数据传输,这会造成资源浪费。而如果是三次握手才建立连接,那么服务端会等待客户端的确认,才会建立连接。而客户端本意只有一次连接请求,所以只会发送一次确认,服务端收到确认之后建立连接。而对服务端的第二个返回确认,客户端会置之不理,因为客户端本意只有一次连接而已。\nTCP 连接的释放 虽然 TCP 连接是全双工的,但是,为了理解 TCP 连接的释放过程,最好将 TCP 连接看成一对单工连接。每个单工连接被单独释放,两个单工连接之间相互独立。为了释放一个连接,任何一方都可以发送一个设置了 FIN 位的 TCP 数据段,这表示它已经没有数据要发送了。当 FIN 数据段被确认的时候,这个方向上就停止传送新数据。然而,另一个方向上可能还在继续无限制地传送数据,当两个方向都停止的时候,连接才被释放。通常情况下,为了释放一个连接,需要 4 个 TCP 数据段:每个方向一个 FIN 和一个 ACK。然而,第一个 ACK 和第二个 FIN 有可能被包含在同一个数据段中,从而将总数降低到 3 个。\n四次挥手。https://blog.csdn.net/qq_33951180/article/details/60767876 https://www.imooc.com/article/17411\n具体来说:\n一方(客户端)主动发送连接释放请求,发送一个包(seq=u,FIN=1),其序列号为 u,等于之前最后接收的包序列号加 1,进入 FIN-WAIT-1 状态; 另一方(服务端)收到释放请求,发出确认报文(seq=v,ack=u+1,ACK=1),进入 CLOSE-WAIT 状态。将 TCP 连接看成一对单工连接,此时客户端单方面停止向服务端发送消息,但服务端的发送还未停止,客户端仍要接收来自服务端的消息;这时服务端会通知上层应用客户端连接已经释放,但到服务端释放可能还需要一段时间; 客户端收到服务器的确认报文,进入 FIN-WAIT-2 状态; 服务端处理完自己的事情之后,向客户端发送连接释放确认报文(seq=w,ack=u+1,ACK=1,FIN=1),进入 LAST-ACK 状态; 客户端收到服务端的释放报文,必须发出确认报文(seq=u+1,ack=w+1,ACK=1),进入 TIME-WAIT 状态。此时连接还未释放,客户端会等待 2 个 MSL (Maximum Segment Lifetime) 时间,才会进入 CLOSED 状态; 服务端只要收到客户端的确认报文,立即进入 CLOSED 状态。 为什么客户端最后还要等待 2MSL?\n第一,保证客户端发送的最后一个 ACK 报文能够到达服务器,因为这个 ACK 报文可能丢失,站在服务器的角度看来,我已经发送了 FIN+ACK 报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个 2MSL 时间段内收到这个重传的报文,接着给出回应报文,并且会重启 2MSL 计时器。\n第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个 2MSL 时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。处在 TIME_WAIT 状态的两端端口都不能使用,等到 2MSL 时间结束后才能继续使用。当连接处于 2MSL 等待阶段时,任何迟到的报文段将会被丢弃。这样一来,就会有更大的机会让丢失的 ACK 字段再次被发送出去,并且也让“四次挥手”更加可靠。\nHTTP 协议 HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的 WWW 文件都必须遵守这个标准。 HTTP 是基于 TCP/IP 通信协议来传递数据(HTML 文件,图片文件,查询结果等) HTTP 协议通常承载于 TCP 协议之上,有时也承载于 TLS 或 SSL 协议层之上,这个时候,就成了我们常说的 HTTPS。如下图 HTTP 是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP 是一个无状态的协议。 HTTP 默认的端口号为 80,HTTPS 的端口号为 443。 HTTP 状态码 类别 原因 1XX Informational(信息性状态码) 2XX Success(成功状态码) 3XX Redirection(重定向) 4XX Client Error(客户端错误状态码) 5XX Server Error(服务器错误状态码) 200(OK 客户端发过来的数据被正常处理 204(Not Content 正常响应,没有实体 206(Partial Content 范围请求,返回部分数据,响应报文中由 Content-Range 指定实体内容 301(Moved Permanently) 永久重定向 302(Found) 临时重定向,规范要求,方法名不变,但是都会改变 303(See Other) 和 302 类似,但必须用 GET 方法 304(Not Modified) 状态未改变, 配合 (If-Match、If-Modified-Since、If-None_Match、If-Range、If-Unmodified-Since) 307(Temporary Redirect) 临时重定向,不该改变请求方法 400(Bad Request) 请求报文语法错误 401 (unauthorized) 需要认证 403(Forbidden) 服务器拒绝访问对应的资源 404(Not Found) 服务器上无法找到资源 500(Internal Server Error) 服务器故障 503(Service Unavailable) 服务器处于超负载或正在停机维护 References 5 分钟让你明白 HTTP 协议 LRU 缓存算法 HTTP 协议及其工作流程 TCP 的三次握手与四次挥手(详解 + 动图) TCP 的三次握手与四次挥手(详解 + 动图)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/server-develop-preliminaries/","tags":["操作系统","网络","tcp","udp"],"title":"服务端开发预备知识"},{"categories":["Notes"],"contents":"如无特殊声明:本文所有 UML 图均出自《图说设计模式》。在此特别鸣谢!\nSingleton 单例模式解决了全局变量的问题,全局只能创建一个实例,保证任何请求该实例的调用均返回同一个对象,保证不会被意外析构。\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 // A singleton class // c.f. https://zhuanlan.zhihu.com/p/37469260 /** * This is so-called lazy-singleton, since it creates the * instance until you ask for it. * * However, it may cause memory leak, since you have no * way to delete the instance you created. */ class SingletonV1 { public: static SingletonV1* GetInstance() { if (pinstance_ == nullptr) pinstance_ = new SingletonV1(); return pinstance_; } private: SingletonV1() = default; ~SingletonV1() = default; SingletonV1(const SingletonV1\u0026amp;) = delete; SingletonV1\u0026amp; operator=(const SingletonV1\u0026amp;) = delete; private: static SingletonV1* pinstance_; }; // static member initialization SingletonV1* SingletonV1::pinstance_ = nullptr; 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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;mutex\u0026gt; using namespace std; std::mutex gm; /** * This is also a lazy-singleton, but it\u0026#39;s thread-safe. * It is so-called Double-Checked Locking Pattern (DCL). */ class SingletonV2 { public: static SingletonV2* GetInstance() { if (pinstance_ == nullptr) { // Attention here: see if \u0026gt;= 2 threads meets here, // only one thread can hold the mutex, then create // the instance, this is can only occur on your first // request on instance, once the instance is created, // we can return it immediately. std::lock_guard\u0026lt;std::mutex\u0026gt; lk(gm); // See why double check here? // Cause if \u0026gt;= 2 threads have already run across here, // they\u0026#39;ve waited and finally held the mutex, w/o this // check, all of these threads will create a instance, // that\u0026#39;s not what you want. if (pinstance_ == nullptr) // double check pinstance_ = new SingletonV2(); } return pinstance_; } private: SingletonV2() = default; ~SingletonV2() = default; SingletonV2(const SingletonV2\u0026amp;) = delete; SingletonV2\u0026amp; operator=(const SingletonV2\u0026amp;) = delete; private: static SingletonV2* pinstance_; }; // static member initialization SingletonV2* SingletonV2::pinstance_ = nullptr; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * C++11 ensures the mt-safety of local static object. Taking * the advantage of this, \u0026lt;\u0026lt;Effective C++\u0026gt;\u0026gt; provides us an elegant * implemention of mt-safe singleton. */ class SingletonV3 { public: static SingletonV3* GetInstance() { // Note that instance will be created only at the first time. static SingletonV3 instance; return \u0026amp;instance; } private: SingletonV3() = default; ~SingletonV3() = default; SingletonV3(const SingletonV3\u0026amp;) = delete; SingletonV3\u0026amp; operator=(const SingletonV3\u0026amp;) = delete; }; 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 /** * This is an eager-singleton which create an instance at first, * then return it as required. * * It\u0026#39;s mt-safe since the instance initiliazation is before main() * function. */ class SingletonV4 { public: static SingletonV4* GetInstance() { return \u0026amp;instance_; } private: SingletonV4() = default; ~SingletonV4() = default; SingletonV4(const SingletonV4\u0026amp;) = delete; SingletonV4\u0026amp; operator=(const SingletonV4\u0026amp;) = delete; private: // note that here is not a pointer, since a pointer will not own // a memory range by default. static SingletonV4 instance_; }; // initialize the static member SingletonV4 SingletonV4::instance_; Factory 工厂模式将对象的创建和对象本身的业务分离,适合那些不关心对象如何创建,对象的创建相对独立的情形。工厂模式又分三种,谓之工厂三兄弟:\nSimple Factory Factory Method Abstract Factory Simple Factory 简单工厂模式就是有一个工厂类,你给我什么参数我就给你创造什么对象。\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 // This file implements a demo for simple factory pattern. // #include \u0026lt;cstdio\u0026gt; #include \u0026lt;string\u0026gt; #define PRINT_NAME printf(\u0026#34;%s\\n\u0026#34;, __FUNCTION__) using namespace std; class Product // abstract product { public: virtual ~Product() { PRINT_NAME; } public: virtual void Operation() = 0; }; class ProductA : public Product // concrete product { public: ~ProductA() { PRINT_NAME; } public: void Operation() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;A::Operation()\u0026#34;); } }; class ProductB : public Product // concrete product { public: ~ProductB() { PRINT_NAME; } public: void Operation() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;B::Operation()\u0026#34;); } }; class Factory // factory { public: ~Factory() { PRINT_NAME; } public: static Product* CreateProduct(const string\u0026amp; name) { if (name == \u0026#34;A\u0026#34;) return new ProductA(); else if (name == \u0026#34;B\u0026#34;) return new ProductB; else return nullptr; } }; /// int main() { Product* pa = Factory::CreateProduct(\u0026#34;A\u0026#34;); Product* pb = Factory::CreateProduct(\u0026#34;B\u0026#34;); pa-\u0026gt;Operation(); pb-\u0026gt;Operation(); delete pb; delete pa; return 0; } Factory Method 工厂方法模式,是指有一个抽象工厂,他不负责实际的创建任务,所有不同类型的对象由其不同的子类创建。\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 #include \u0026lt;cstdio\u0026gt; #define PRINT_NAME printf(\u0026#34;%s\\n\u0026#34;, __FUNCTION__) class Product { public: virtual ~Product() { PRINT_NAME; } public: virtual void Operation() = 0; }; class ProductA : public Product { public: ~ProductA() { PRINT_NAME; } public: void Operation() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;ProductA::Operation()\u0026#34;); } }; class ProductB : public Product { public: ~ProductB() { PRINT_NAME; } public: void Operation() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;ProductB::Operation()\u0026#34;); } }; class Creator { public: virtual ~Creator() { PRINT_NAME; } public: virtual Product* CreateProduct() = 0; }; class CreatorA : public Creator { public: ~CreatorA() { PRINT_NAME; } public: Product* CreateProduct() override { return new ProductA(); } }; class CreatorB : public Creator { public: ~CreatorB() { PRINT_NAME; } public: Product* CreateProduct() override { return new ProductB(); } }; // test int main() { Creator* ca = new CreatorA; Product* pa = ca-\u0026gt;CreateProduct(); pa-\u0026gt;Operation(); Creator* cb = new CreatorB; Product* pb = cb-\u0026gt;CreateProduct(); pb-\u0026gt;Operation(); delete pb; delete cb; delete pa; delete ca; return 0; } Abstract Factory 抽象工厂模式,是指有一个抽象工厂,他不负责实际的创建任务。它会有很多个子类,每个子类负责创建一族具有某种特定属性的对象。如果把工厂所需要创建的对象称为产品,同样有一个抽象产品,他有多个子类,代表不同产品,但每一个产品又有不同属性。所以,把具有相同属性的所有产品的创建任务交给一个工厂(抽象工厂的一个子类),把具有另一个属性的所有产品的创建任务交给另一个工厂(抽象工厂的另一个子类)。\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 // This file implements a demo of abstract factory pattern. // #include \u0026lt;cstdio\u0026gt; #define PRINT_NAME printf(\u0026#34;%s\\n\u0026#34;, __FUNCTION__) // cats class Cat { public: virtual ~Cat() { PRINT_NAME; } public: virtual void Meow() = 0; }; class BlackCat : public Cat { public: ~BlackCat() { PRINT_NAME; } public: void Meow() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;b::meow~\u0026#34;); } }; class WhiteCat : public Cat { public: ~WhiteCat() { PRINT_NAME; } public: void Meow() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;w::meow~\u0026#34;); } }; // dogs class Dog { public: virtual ~Dog() { PRINT_NAME; } public: virtual void Bark() = 0; }; class BlackDog : public Dog { public: ~BlackDog() { PRINT_NAME; } public: void Bark() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;b::wang~\u0026#34;); } }; class WhiteDog : public Dog { public: ~WhiteDog() { PRINT_NAME; } public: void Bark() override { printf(\u0026#34;%s\\n\u0026#34;, \u0026#34;w::wang~\u0026#34;); } }; // here comes factory class Factory { public: virtual ~Factory() { PRINT_NAME; } public: virtual Cat* CreateCat() = 0; virtual Dog* CreateDog() = 0; }; class BlackFactory : public Factory // factory that dyes animals black { public: ~BlackFactory() { PRINT_NAME; } public: Cat* CreateCat() override { return new BlackCat(); } Dog* CreateDog() override { return new BlackDog(); } }; class WhiteFactory : public Factory // factory that dyes animals white { public: ~WhiteFactory() { PRINT_NAME; } public: Cat* CreateCat() override { return new WhiteCat(); } Dog* CreateDog() override { return new WhiteDog(); } }; /// int main() { Factory* pblack = new BlackFactory(); Factory* pwhite = new WhiteFactory(); Cat* black_cat = pblack-\u0026gt;CreateCat(); Cat* white_cat = pwhite-\u0026gt;CreateCat(); Dog* black_dog = pblack-\u0026gt;CreateDog(); Dog* white_dog = pwhite-\u0026gt;CreateDog(); black_cat-\u0026gt;Meow(); black_dog-\u0026gt;Bark(); white_cat-\u0026gt;Meow(); white_dog-\u0026gt;Bark(); delete white_dog; delete black_dog; delete white_cat; delete black_cat; delete pwhite; delete pblack; return 0; } Observer 观察者模式很重要,在计算机系统里有大量应用。就是说有一群观察者,希望观察某个目标,进而相应的动作。比方说很多人需要了解天气预报的信息,那么天气预报就是观察目标,如果人们知道天气预报说下雨,那么他可能会带把伞。这就是相应的操作。如何更新目标的状态是个很有意思的问题。常见的有两种,一是观察目标发生变化,主动通知所有的观察者;而是观察者不断轮询,监听观察目标,一旦发生变化,立刻做出相应的动作。前者称为 reactor,后者称为 proactor. 两种方式各有所用!比方说,如果订阅天气预报的人数寥寥无几,那么气象站完全可以向所有订阅者推送天气更新信息,但是如果全国上亿人都订阅了天气预报,那么此时完全推送可能就代价太大了。而有些订阅者,他时刻关注天气,比如地方气象站,而有些订阅者,他对实时性不要求那么高,比方说天气预报 APP(通常 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 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 // This file implements a demo of observer pattern. // #include \u0026lt;cstdio\u0026gt; #include \u0026lt;list\u0026gt; #include \u0026lt;string\u0026gt; #define PRINT_NAME printf(\u0026#34;%s\\n\u0026#34;, __FUNCTION__) // front declaration class Subject; class Observer; class Subject { public: virtual ~Subject() { PRINT_NAME; } public: virtual void attach(Observer* ob); virtual void detach(Observer* ob); virtual void notify(); // notify all observers virtual void set_state(int state) = 0; virtual int get_state() = 0; private: std::list\u0026lt;Observer*\u0026gt; observers_; }; class Observer { public: virtual ~Observer() { PRINT_NAME; } public: virtual void update(Subject* sb) = 0; }; //================ impl subject ============== void Subject::attach(Observer* ob) { observers_.push_back(ob); } void Subject::detach(Observer* ob) { for (auto it = observers_.begin(); it != observers_.end(); ++it) { if (*it == ob) { observers_.erase(it); break; } } } void Subject::notify() { for (auto ob : observers_) ob-\u0026gt;update(this); } /* a specific subject */ class Weather : public Subject { public: ~Weather() { PRINT_NAME; } public: void set_state(int s) override { state_ = s; } int get_state() override { return state_; } private: int state_; }; /* a specific observer */ class WeatherAPP : public Observer { public: WeatherAPP(const std::string\u0026amp; name): name_(name) {} ~WeatherAPP() { PRINT_NAME; } public: void update(Subject* sb) override { state_ = sb-\u0026gt;get_state(); printf(\u0026#34;Observer %s: \u0026#34;, name_.data()); switch (state_) { case 0: printf(\u0026#34;default\\n\u0026#34;); break; case 1: printf(\u0026#34;rainy\\n\u0026#34;); break; case 2: printf(\u0026#34;cloudy\\n\u0026#34;); break; case 3: printf(\u0026#34;foggy\\n\u0026#34;); break; default: break; } } private: int state_; std::string name_; }; /// int main() { Subject* weather = new Weather(); Observer* ob1 = new WeatherAPP(\u0026#34;Color TianQi\u0026#34;); Observer* ob2 = new WeatherAPP(\u0026#34;Moji TianQi\u0026#34;); weather-\u0026gt;attach(ob1); weather-\u0026gt;attach(ob2); weather-\u0026gt;set_state(1); // rainy weather-\u0026gt;notify(); weather-\u0026gt;detach(ob2); weather-\u0026gt;set_state(3); // foggy weather-\u0026gt;notify(); delete ob2; delete ob1; delete weather; return 0; } References 图说设计模式 ","permalink":"https://guyueshui.github.io/post/design-pattern-notes/","tags":["设计模式"],"title":"设计模式学习笔记"},{"categories":["Notes"],"contents":"开一篇多线程学习笔记,记录下在实习过程中遇到的一些简单问题。\n注意:这是一篇以学习笔记,难免有误,主要写给自己参考。请酌情判别,如有错误,也欢迎指正!\n互斥 互斥的本质就是等待!\n互斥的特性\n无死锁 无饥饿 等待 并发系统中存在两种类型的通信:\n瞬时通信:要求通信双方在同一时刻都参与通信。 持续通信:允许发送者和接收者在不同时间参与通信。 互斥需要的是持续通信。并发系统中常用的一种通信协议:中断。现在操作系统中,一个线程要引起另一个线程注意的常用方法就是发送中断信号。更准确的说,线程 A 通过设置一个位向线程 B 发出一个中断信号,线程 B 周期地检查这个位。一旦 B 检测到该位置被设置,则做出相应的响应。响应结束后,通常由 B 进行复位(A 不能复位)。虽然中断不能解决互斥问题。但它仍是非常有用的。例如,Java 中的wait()调用和notifyall()调用的本质就是中断。\n编写多线程需要注意的点 在脑中先大致想好每个线程的工作是什么,什么时候开始,什么时候结束。 捋清楚了之后再开始动手写。 调用t.join()的作用类似于,如果线程结束,主线程执行到 join 就可以立即返回,如果线程为结束,主线程执行到 join 会阻塞,直到线程结束。然后主线程继续执行。\nmain thread1 + + | | | | | | | | thread1.join()+------+ | | | v 如果某线程申请占有互斥量时,该互斥量被其他线程占有,则会引起该线程阻塞。\n一般来说线程安全性很难保证,但只有两种操作可以保证线程安全性,\n基本的原子操作 CAS(compare and swap) 操作 使用std::conditional_variable注意事项 调用 wait 的线程必须占有 mutex,否则 undefined 所有并发线程(如果使用同一个条件变量交互)必须使用同一个 mutex,否则 undefined 使用std::thread注意事项 thread 对象构造完成即开始执行 使用 detach 之后,程序失去该线程的控制权,线程结束之后资源全部释放 使用 detach 之后,主线程结束时,所有资源都被释放,即便该线程还未停止 线程之间没有父/子关系。如果线程 A 创建线程 B,然后线程 A 终止,则线程 B 将继续执行。但如果主线程终止,则整个进程终止,自然进程下的所有线程都终止,资源释放 Any thread can potentially access any object in the program (objects with automatic and thread-local storage duration may still be accessed by another thread through a pointer or by reference). 使用std::atomic注意事项 atomic is neither copyable nor movable mutex is neither copyable nor movable 加一次锁耗时大概 25ns,使用 lock-free 的话能够提高到十几纳秒,事实上提升不大。加锁并没有想象中那么耗时,提高效率的关键是减少锁的碰撞。即一个线程占有锁的时候,其他线程不会去申请锁,因为在锁被占用的情况下去申请锁比较耗时,会先去 loop 一段时间,拿不到锁才会进入内核陷入睡眠等待锁,这样的耗时是比较浪费的。所以关键要减少锁的碰撞。\n有原子的函数吗,就是要么执行成功,要么失败? 不存在,一个函数内部多少指令,在多线程的情况下,很难保证可以全部的顺序的原子的执行完成。\nFrom Shuo\u0026rsquo;s blog 依据《Java 并发编程实践》/《Java Concurrency in Practice》一书,一个线程安全的 class 应当满足三个条件:\n从多个线程访问时,其表现出正确的行为 无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织 调用端代码无需额外的同步或其他协调动作 对象构造要做到线程安全,惟一的要求是在构造期间不要泄露 this 指针,即\n不要在构造函数中注册任何回调 也不要在构造函数中把 this 传给跨线程的对象 即便在构造函数的最后一行也不行 作为 class 数据成员的 Mutex 只能用于同步本 class 的其他数据成员的读和写,它不能保护安全地析构。因为成员 mutex 的生命期最多与对象一样长,而析构动作可说是发生在对象身故之后(或者身亡之时)。另外,对于基类对象,那么调用到基类析构函数的时候,派生类对象的那部分已经析构了,那么基类对象拥有的 mutex 不能保护整个析构过程。\n","permalink":"https://guyueshui.github.io/post/concurrent-programming/","tags":["多线程","并发"],"title":"多线程学习笔记"},{"categories":["tech"],"contents":"诚如是,Life is too short to learn c++. 此篇记录一些我在学习 cpp 过程中遇到的一些知识点,仅作记录并梳理之效。里面可能会有大量参考其他网络博客,如有侵权,请联系我删除之。\nReactor v.s. Proactor epll/wait: reactor 模式,不停轮询,发现有事做,就做! asio: proactor 模式,先注册好事件,如果事情发生了,通过回调函数处理。 几个常用的宏 __func__: name of an function, exists in C99/C++11 (__FUNCTION__ is non standard) __LINE__: line number of the code __FILE__: filename of the file __DATE__ and __TIME__: as you wish 不要在 ctor 里调用虚函数 总结来说:基类部分在派生类部分之前被构造,当基类构造函数执行时派生类中的数据成员还没被初始化。如果基类构造函数中的虚函数调用被解析成调用派生类的虚函数,而派生类的虚函数中又访问到未初始化的派生类数据,将导致程序出现一些未定义行为和 bug。\nctor 应该设计的尽量简单,确保对象可以被正确构造。在 ctor 中调用本类的非静态成员都是不安全的,因为他们还没被构造,而有些成员是依赖对象的,而此时对象还没有被成功构造。\nctor 不能是虚函数 从存储空间角度:虚函数对应一个 vtable(虚函数表),这大家都知道,可是这个 vtable 其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable 来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到 vtable,所以构造函数不能是虚函数。\n从使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。 虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。\n构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它。但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。\n—————————————————— 版权声明:本文为 CSDN 博主「cainiao000001」的原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/cainiao000001/article/details/81603782\n虚函数的工作原理 https://zhuanlan.zhihu.com/p/60543586\nC++ 规定了虚函数的行为,但将实现方法留给了编译器的作者。不需要知道实现方法也可以很好的使用虚函数,但了解虚函数的工作原理有助于更好地理解概念。\n通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。\n这种数组称为虚函数表(Virtual Function Table, vtbl)。\n虚函数表是一个数组,数组的元素是指针,指针指的是虚函数的地址。\n具有虚函数的类的实例,都会在头部存一个指向虚函数表的指针。\n常见类型所占空间大小 TYPE Bytes (unsigned) int 4 (unsigned) short 2 (unsigned) long 8 float 4 double 8 long double 16 (unsigned) char 1 bool 1 指针占几个字节 指针即为地址,指针几个字节跟语言无关,而是跟系统的寻址能力有关,譬如以前是 16 为地址,指针即为 2 个字节,现在一般是 32 位系统,所以是 4 个字节,以后 64 位,则就为 8 个字节。\nNOTE: 类成员函数指针一般为普通指针的两倍大小。\nliteral 5.0类型为double,5.0f类型为float。不加f后缀默认double.\n静态成员的初始化 当一个类包含静态成员时,最好的做法是在类中声明,在类外初始化。由于静态成员是所有对象共享的,如果在类内初始化,则每个对象构造时,都要执行一遍静态成员的初始化,这无疑是一种浪费。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct A { static int a; int b; void fun(); ... }; int A::a = 233; class B { public: void fun(); ... private: static string str_; bool done_; }; string B::str_ = \u0026#34;hello, i am static\u0026#34;; 析构函数的调用时机 The destructor is called whenever an object\u0026rsquo;s lifetime ends, which includes\nprogram termination, for objects with static storage duration thread exit, for objects with thread-local storage duration end of scope, for objects with automatic storage duration and for temporaries whose life was extended by binding to reference delete-expressin, for objects with dynamic storage duration end of the full expression, for nameless temporaries stack unwinding (栈回溯), for objects with automatic storage duration when an exception escapes their block, uncaught. cf. https://en.cppreference.com/w/cpp/language/destructor\n常量 Literal constants 字面值常量 Cf. https://www.learncpp.com/cpp-tutorial/literals/\nSymbolic constants 符号常量 Cf. https://www.learncpp.com/cpp-tutorial/const-constexpr-and-symbolic-constants/\nConst variables must be initialized Function parameters for arguments passed by value should not be made const. Don’t use const with return by value. Runtime vs compile-time constants\nRuntime constants are constants whose initialization values can only be resolved at runtime (when your program is running). The following are examples of runtime constants:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;iostream\u0026gt; void printInt(const int x) // x is a runtime constant because the value isn\u0026#39;t known until the program is run { std::cout \u0026lt;\u0026lt; x; } int main() { std::cout \u0026lt;\u0026lt; \u0026#34;Enter your age: \u0026#34;; int age{}; std::cin \u0026gt;\u0026gt; age; const int usersAge { age }; // usersAge is a runtime constant because the value isn\u0026#39;t known until the program is run std::cout \u0026lt;\u0026lt; \u0026#34;Your age is: \u0026#34;; printInt(age); return 0; } Compile-time constants are constants whose initialization values can be determined at compile-time (when your program is compiling). The following are examples of compile-time constants:\n1 2 const double gravity { 9.8 }; // the compiler knows at compile-time that gravity will have value 9.8 const int something { 1 + 2 }; // the compiler can resolve this at compiler time Compile-time constants enable the compiler to perform optimizations that aren’t available with runtime constants. For example, whenever gravity is used, the compiler can simply substitute the identifier gravity with the literal double 9.8.\nTo help provide more specificity, C++11 introduced the keyword constexpr, which ensures that a constant must be a compile-time constant.\nAny variable that should not be modifiable after initialization and whose initializer is known at compile-time should be declared as constexpr.\nAny variable that should not be modifiable after initialization and whose initializer is not known at compile-time should be declared as const.\nNote that literals are also implicitly constexpr, as the value of a literal is known at compile-time.\nA constant expression is an expression that can be evaluated at compile-time. For example:\n1 2 3 4 5 6 #include \u0026lt;iostream\u0026gt; int main() { std::cout \u0026lt;\u0026lt; 3 + 4; // 3 + 4 evaluated at compile-time return 0; } In the above program, because the literal values 3 and 4 are known at compile-time, the compiler can evaluate the expression 3 + 4 at compile-time and substitute in the resulting value 7. That makes the code faster because 3 + 4 no longer has to be calculated at runtime.\nConstexpr variables can also be used in constant expressions:\n1 2 3 4 5 6 7 8 #include \u0026lt;iostream\u0026gt; int main() { constexpr int x { 3 }; constexpr int y { 4 }; std::cout \u0026lt;\u0026lt; x + y; // x + y evaluated at compile-time return 0; } In the above example, because x and y are constexpr, the expression x + y is a constant expression that can be evaluated at compile-time. Similar to the literal case, the compiler can substitute in the value 7.\nObject-like preprocessor macros v.s. symbolic constants Object-like macro has the form:\n1 #define identifier substitution_text Whenever the preprocessor encounters this directive, any further occurrence of identifier is replaced by substitution_text. The identifier is traditionally typed in all capital letters, using underscores to represent spaces.\nAvoid using #define to create symbolic constants macros. Use const or constexpr variables instead.\nMacros can have naming conflicts with normal code. For example:\n1 2 3 4 5 6 7 8 9 10 #include \u0026#34;someheader.h\u0026#34; #include \u0026lt;iostream\u0026gt; int main() { int beta { 5 }; std::cout \u0026lt;\u0026lt; beta; return 0; } If someheader.h happened to #define a macro named beta, this simple program would break, as the preprocessor would replace the int variable beta’s name with whatever the macro’s value was. This is normally avoided by using all caps for macro names, but it can still happen.\nUsing symbolic constants throughout a multi-file program Cf. https://www.learncpp.com/cpp-tutorial/sharing-global-constants-across-multiple-files-using-inline-variables/\n内存布局 结构体 C++ 规范在“结构”上使用了和 C 相同的,简单的内存布局原则:成员变量按其被声明的顺序排列,按具体实现所规定的对齐原则在内存地址上对齐。\n1 2 3 4 5 6 7 8 9 10 struct S { char a; // memory location #1 int b : 5; // memory location #2 int c : 11, // memory location #2 (continued) char : 0, int d : 8; // memory location #3 struct { int ee : 8; // memory location #4 } e; } obj; // The object \u0026#39;obj\u0026#39; consists of 4 separate memory locations 类的静态成员不占用类的空间,静态成员在程序数据段中。 对齐 Cf. https://www.learncpp.com/cpp-tutorial/object-sizes-and-the-sizeof-operator/#comment-563585\nCf. http://www.catb.org/esr/structure-packing/\n模板 重载与特化 从编译到函数模板的调用,编译器必须在非模板重载、模板重载和模板重载的特化间决定。\n1 2 3 4 5 6 7 8 9 template\u0026lt; class T \u0026gt; void f(T); // #1:模板重载 template\u0026lt; class T \u0026gt; void f(T*); // #2:模板重载 void f(double); // #3:非模板重载 template\u0026lt;\u0026gt; void f(int); // #4: #1 的特化 f(\u0026#39;a\u0026#39;); // 调用 #1 f(new int(1)); // 调用 #2 f(1.0); // 调用 #3 f(1); // 调用 #4 注意只有非模板和初等模板重载参与重载决议。特化不是重载,且不受考虑。只有在重载决议选择最佳匹配初等函数模板后,才检验其特化以查看何为最佳匹配。\n1 2 3 4 5 template\u0026lt; class T \u0026gt; void f(T); // #1:所有类型的重载 template\u0026lt;\u0026gt; void f(int*); // #2:为指向 int 的指针特化 #1 template\u0026lt; class T \u0026gt; void f(T*); // #3:所有指针类型的重载 f(new int(1)); // 调用 #3,即使通过 #1 的特化会是完美匹配 即重载的优先级要高于特化。\n关于模板函数重载的更多内容,参考 function_template。\n预编译 Cf. https://www.learncpp.com/cpp-tutorial/introduction-to-the-preprocessor/\n#include When you #include a file, the preprocessor replaces the #include directive with the contents of the included file. The included contents are then preprocessed (along with the rest of the file), and then compiled.\nMacro defines The #define directive can be used to create a macro. In C++, a macro is a rule that defines how input text is converted into replacement output text.\nThere are two basic types of macros: object-like macros, and function-like macros. Object-like macros can be defined in one of two ways:\n1 2 #define identifier #define identifier substitution_text Object-like macros don’t affect other preprocessor directives 结论:宏展开在预编译指令 (Preprocessor directives) 无效。\n1 2 3 #define PRINT_JOE #ifdef PRINT_JOE // 此处会否将\u0026#39;PRINT_JOE\u0026#39;替换为空呢? // ... Macros only cause text substitution for normal code. Other preprocessor commands are ignored. Consequently, the PRINT_JOE in #ifdef PRINT_JOE is left alone.\nFor example:\n1 2 3 4 5 #define FOO 9 // Here\u0026#39;s a macro substitution #ifdef FOO // This FOO does not get replaced because it’s part of another preprocessor directive std::cout \u0026lt;\u0026lt; FOO; // This FOO gets replaced with 9 because it\u0026#39;s part of the normal code #endif In actuality, the output of the preprocessor contains no directives at all \u0026ndash; they are all resolved/stripped out before compilation, because the compiler wouldn’t know what to do with them.\nThe scope of defines Once the preprocessor has finished, all defined identifiers from that file are discarded. This means that directives are only valid from the point of definition to the end of the file in which they are defined. Directives defined in one code file do not have impact on other code files in the same project.\n宏定义仅在本文件有效,一旦预编译阶段结束,所有宏都将失效。因为,预编译就是将所有的预编译指令都处理掉,该替换的替换(宏展开),该选择的选择,该丢弃的丢弃(条件编译),然后交给编译器去编译,谨记:编译器是读不懂预编译指令的!\nConsider the following example:\nfunction.cpp:\n1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;iostream\u0026gt; void doSomething() { #ifdef PRINT std::cout \u0026lt;\u0026lt; \u0026#34;Printing!\u0026#34;; #endif #ifndef PRINT std::cout \u0026lt;\u0026lt; \u0026#34;Not printing!\u0026#34;; #endif } main.cpp:\n1 2 3 4 5 6 7 8 9 void doSomething(); // forward declaration for function doSomething() #define PRINT int main() { doSomething(); return 0; } The above program will print:\nNot printing! Even though PRINT was defined in main.cpp, that doesn’t have any impact on any of the code in function.cpp (PRINT is only #defined from the point of definition to the end of main.cpp). This will be of consequence when we discuss header guards in a future lesson.\nHeader files Cf. https://www.learncpp.com/cpp-tutorial/header-files/\n对于多文件项目,文件是单独编译的。要想调用一个自定义函数,linker 必须能找到这个函数在哪里定义。\n1 2 3 4 5 6 7 int add(int, int); // forward declaration int main() { // add(3, 5); return 0; } 上述文件是可以编译通过的,因为没有发生对add的调用,所以 linker 不会去找add的定义(当然如果要找也找不到)。\n但是如果某处发起了对add的调用(例如去掉注释),那么上述程序在 link 阶段会报错:\n1 2 3 4 yychi@~\u0026gt; clang test_linker.cpp /usr/bin/ld: /tmp/test_linker-e1bb8b.o: in function `main\u0026#39;: test_linker.cpp:(.text+0x1a): undefined reference to `add(int, int)\u0026#39; clang-13: error: linker command failed with exit code 1 (use -v to see invocation) 在多文件编程时,往往需要 forawrd declaration,这些前置声明必须在其他某个地方被定义且只被定义一次。这样,linker 才能正确的完成链接。任何重复定义或未定义都会在 link 阶段报错。\n考虑如下例子:\nadd.cpp:\n1 2 3 4 int add(int x, int y) { return x + y; } main.cpp:\n1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;stdio.h\u0026gt; int add(int, int); int main() { int x = 1, y = 2; int z = add(x, y); printf(\u0026#34;z=%d\\n\u0026#34;, z); return 0; } 在编译 main.cpp 的时候,因为有add的前置声明,所以可以通过。但为了 link 的时候能够找到add的定义,add.cpp 必须也被编译,所以正确的编译方式应该是:\n1 $ clang main.cpp add.cpp Use of header files 从上面的论述我们隐约可见,在多文件编程中,我们可能会大量的使用前置声明(forward declaration),一旦文件多起来,这将非常枯燥。所以头文件的出现就是为了解决这个问题:把所有的声明放在一起。\nLet’s write a header file to relieve us of this burden. Writing a header file is surprisingly easy, as header files only consist of two parts:\nA header guard. The actual content of the header file, which should be the forward declarations for all of the identifiers we want other files to be able to see. add.h:\n1 2 3 4 // 1) We really should have a header guard here, but will omit it for simplicity (we\u0026#39;ll cover header guards in the next lesson) // 2) This is the content of the .h file, which is where the declarations go int add(int x, int y); // function prototype for add.h -- don\u0026#39;t forget the semicolon! main.cpp:\n1 2 3 4 5 6 7 8 #include \u0026#34;add.h\u0026#34; // Insert contents of add.h at this point. Note use of double quotes here. #include \u0026lt;iostream\u0026gt; int main() { std::cout \u0026lt;\u0026lt; \u0026#34;The sum of 3 and 4 is \u0026#34; \u0026lt;\u0026lt; add(3, 4) \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; return 0; } add.cpp:\n1 2 3 4 5 6 #include \u0026#34;add.h\u0026#34; // Insert contents of add.h at this point. Note use of double quotes here. int add(int x, int y) { return x + y; } When the preprocessor processes the #include \u0026quot;add.h\u0026quot; line, it copies the contents of add.h into the current file at that point. Because our add.h contains a forward declaration for function add, that forward declaration will be copied into main.cpp. The end result is a program that is functionally the same as the one where we manually added the forward declaration at the top of main.cpp.\nConsequently, our program will compile and link correctly. Two wrong cases 如上图所示,会产生一个重复定义的错误。由于 add.h 中包含了函数定义,而非前置声明。编译 main.cpp 的时候,add.h 中的代码插入到 main.cpp 中,产生一次add函数的定义。同理,编译 add.cpp 的时候也定义了一次add函数。link 阶段会发生歧义,以致报错。\n此时如果不编译 add.cpp 其实是可行的: 但谁又能保证只有一个文件#include \u0026quot;add.h\u0026quot;呢?所以头文件中应该只包含声明,而不应该包含实现。\nThe primary purpose of a header file is to propagate declarations to code files.\nKey insight: Header files allow us to put declarations in one location and then import them wherever we need them. This can save a lot of typing in multi-file programs.\nHeader files should generally not contain function and variable definitions, so as not to violate the one definition rule. An exception is made for symbolic constants (which we cover in lesson 4.15 \u0026ndash; Symbolic constants: const and constexpr variables).\n标准库自动链接\n注意:clang 不会自动链接,需要手动链接 clang main.cpp -lstdc++\nWhen it comes to functions and variables, it’s worth keeping in mind that header files typically only contain function and variable declarations, not function and variable definitions (otherwise a violation of the one definition rule could result). std::cout is forward declared in the iostream header, but defined as part of the C++ standard library, which is automatically linked into your program during the linker phase.\nThe #include order of header files\nCf. https://www.learncpp.com/cpp-tutorial/header-files/ for \u0026ldquo;the #inclue order of header files\u0026rdquo;.\nA view of memory and fundamental data types in cpp Cf. https://www.learncpp.com/cpp-tutorial/introduction-to-fundamental-data-types/\nThe smallest unit of memory is a binary digit (also called a bit), which can hold a value of 0 or 1. You can think of a bit as being like a traditional light switch \u0026ndash; either the light is off (0), or it is on (1). There is no in-between. If you were to look at a random segment of memory, all you would see is …011010100101010… or some combination thereof.\nMemory is organized into sequential units called memory addresses (or addresses for short). Similar to how a street address can be used to find a given house on a street, the memory address allows us to find and access the contents of memory at a particular location.\nPerhaps surprisingly, in modern computer architectures, each bit does not get its own unique memory address. This is because the number of memory addresses are limited, and the need to access data bit-by-bit is rare. Instead, each memory address holds 1 byte of data. A byte is a group of bits that are operated on as a unit. The modern standard is that a byte is comprised of 8 sequential bits.\nData types\nBecause all data on a computer is just a sequence of bits, we use a data type (often called a “type” for short) to tell the compiler how to interpret the contents of memory in some meaningful way. You have already seen one example of a data type: the integer. When we declare a variable as an integer, we are telling the compiler “the piece of memory that this variable uses is going to be interpreted as an integer value”.\nWhen you give an object a value, the compiler and CPU take care of encoding your value into the appropriate sequence of bits for that data type, which are then stored in memory (remember: memory can only store bits). For example, if you assign an integer object the value 65, that value is converted to the sequence of bits 0100 0001 and stored in the memory assigned to the object.\nConversely, when the object is evaluated to produce a value, that sequence of bits is reconstituted back into the original value. Meaning that 0100 0001 is converted back into the value 65.\nFortunately, the compiler and CPU do all the hard work here, so you generally don’t need to worry about how values get converted into bit sequences and back.\nAll you need to do is pick a data type for your object that best matches your desired use.\n谨记:内存只能存 bit,只能寻址寻到 byte 这一层,如果数据按内存边界对齐,寻址会更快(一次读)。\n由于内存地址空间有限,且按 bit 寻址的场景很少,所以寻址单位一般是 byte。A byte is a group of bits that are operated on as a unit. The modern standard is that a byte is comprised of 8 sequential bits.\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 #include \u0026lt;cstdint\u0026gt; #include \u0026lt;iostream\u0026gt; using namespace std; static void print(int32_t a, uint32_t b, size_t n_shift) { cout \u0026lt;\u0026lt; \u0026#34;a=\u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; \u0026#34;; b=\u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;left shift \u0026#34; \u0026lt;\u0026lt; n_shift \u0026lt;\u0026lt; \u0026#34; bit(s) of a is: \u0026#34; \u0026lt;\u0026lt; (a \u0026lt;\u0026lt; n_shift) \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;left shift \u0026#34; \u0026lt;\u0026lt; n_shift \u0026lt;\u0026lt; \u0026#34; bit(s) of b is: \u0026#34; \u0026lt;\u0026lt; (b \u0026lt;\u0026lt; n_shift) \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;right shift \u0026#34; \u0026lt;\u0026lt; n_shift \u0026lt;\u0026lt; \u0026#34; bit(s) of a is: \u0026#34; \u0026lt;\u0026lt; (a \u0026gt;\u0026gt; n_shift) \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;right shift \u0026#34; \u0026lt;\u0026lt; n_shift \u0026lt;\u0026lt; \u0026#34; bit(s) of b is: \u0026#34; \u0026lt;\u0026lt; (b \u0026gt;\u0026gt; n_shift) \u0026lt;\u0026lt; endl; } int main() { int32_t a = 0xffffffff; uint32_t b = 0xffffffff; print(a, b, 1); cout \u0026lt;\u0026lt; \u0026#34;------------\\n\u0026#34;; print(0xbfffffff, b, 1); return 0; } /** * Output on my machine: a=-1; b=4294967295 left shift 1 bit(s) of a is: -2 left shift 1 bit(s) of b is: 4294967294 right shift 1 bit(s) of a is: -1 right shift 1 bit(s) of b is: 2147483647 ------------ a=-1073741825; b=4294967295 left shift 1 bit(s) of a is: 2147483646 left shift 1 bit(s) of b is: 4294967294 right shift 1 bit(s) of a is: -536870913 right shift 1 bit(s) of b is: 2147483647 */ 从内存连续 bit 来看,a 和 b 都是存了 4 byte 的 1,区别仅仅是 data type 不一样,导致了截然不同的结果。\n移位操作\n右移 无符号右移,低位丢失高位补 0 有符号右移,低位丢失,高位补符号位(正为 0,负为 1) 左移:高位丢失,低位补 0 a 和 b 左移一位都得到:\n0xfffffffe: 如果是 int 解释为-2, unsigned int 解释为 4294967294=2^32 - 2 a 右移一位得到\n0xffffffff: 注意负数右移,高位补 1,int 解释为-1 b 右移一位得到\n0x7fffffff: 高位补 0, unsigned int 解释为 2147483647=2^31-1 注意,负的可能左移成正的,因此,有符号的移位是不安全的。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include \u0026lt;iostream\u0026gt; int main() { signed int s { -1 }; unsigned int u { 1 }; if (s \u0026lt; u) // -1 is implicitly converted to 4294967295, and 4294967295 \u0026lt; 1 is false std::cout \u0026lt;\u0026lt; \u0026#34;-1 is less than 1\\n\u0026#34;; else std::cout \u0026lt;\u0026lt; \u0026#34;1 is less than -1\\n\u0026#34;; // this statement executes return 0; } NOTE:\n注意无符号数相减得负数会导致溢出 usigned 和--运算符,可能减至负数溢出 除非确定变量值非负,否则尽量避免使用 unsigned 切忌不要在数学计算中混用 unsigned 和 signed,此时 signed 会隐式转换为 unsigned unsigned numbers are preferred when dealing with bit manipulation std::int8_t和std::uint8_t可能知识char和unsigned char的别名,可能有坑(参考:https://www.learncpp.com/cpp-tutorial/introduction-to-type-conversion-and-static_cast/) Best practice\nFavor signed numbers over unsigned numbers for holding quantities (even quantities that should be non-negative) and mathematical operations. Avoid mixing signed and unsigned numbers.\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 /** * This file test the endian of your machine: * big-endian or little-endian, by visiting * the memory sequentially byte by byte of * a intendly constructed integer. */ #include \u0026lt;cstdint\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;iostream\u0026gt; using namespace std; static void print(void* ptr, size_t size) { // convert to char* so we can visit the memory byte by byte unsigned char* _ptr = static_cast\u0026lt;unsigned char*\u0026gt;(ptr); // print the value of each byte in ptr for (size_t i = 0; i \u0026lt; size; ++i) cout \u0026lt;\u0026lt; static_cast\u0026lt;int\u0026gt;(_ptr[i]); cout \u0026lt;\u0026lt; endl; } int main() { uint32_t a = 0x01020304; /* * if it prints 4321, indicates 低位在前,对应 little-endian * it it prints 1234, indicates 高位在前,对应 big-endian */ print(\u0026amp;a, 4); return 0; } /** * Output on my machine 4321 */ 字节序就是计算机存储数据的时候将低位数据存在低位地址还是高位地址。举个例子,数值 0x2211 使用两个字节储存:高位字节是 0x22,低位字节是 0x11。\n大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。 小端字节序:低位字节在前,高位字节在后,即以 0x1122 形式储存。 如果太多记不住,至少要记住:\n字节序的概念:读一段内存从低位向高位读(从左往右),先读到高位字节还是低位字节 符合人类读写数值的方法是大端序(big-endian) 既然如此,我们要判断一台机器是 big-endian 还是 little-endian,只需要构造一端内存,按字节从低位地址向高位地址访问,看看低位地址存的是高位字节,还是低位字节即可。\n且看上述代码,构造了一个整数 0x01020304,然后通过将首地址转成char*的方式去按字节读取内存中的值(这样做的目的是,char*可以逐字节的读取内存;而int*一次指针移动会移动sizeof(int)个字节)。读出来如果是符合书写习惯的 1234,则表明机器是 big-endian,反之 little-endian.\n这也是一段内存的两种不同的解释方式,recall that Because all data on a computer is just a sequence of bits, we use a data type (often called a “type” for short) to tell the compiler how to interpret the contents of memory in some meaningful way.\n链接(Linkage) Cf. https://www.learncpp.com/cpp-tutorial/internal-linkage/\nIdentifiers have another property named linkage. An identifier’s linkage determines whether other declarations of that name refer to the same object or not.\nLocal variables have no linkage, which means that each declaration refers to a unique object.\nGlobal variable and functions identifiers can have either internal linkage or external linkage.\nAn identifier with internal linkage can be seen and used within a single file, but it is not accessible from other files (that is, it is not exposed to the linker). This means that if two files have identically named identifiers with internal linkage, those identifiers will be treated as independent.\nTo make a non-constant global variable internal, we use the static keyword.\n1 2 3 4 5 6 7 8 9 static int g_x; // non-constant globals have external linkage by default, but can be given internal linkage via the static keyword const int g_y { 1 }; // const globals have internal linkage by default constexpr int g_z { 2 }; // constexpr globals have internal linkage by default int main() { return 0; } To see it, we take\na.cpp:\n1 2 3 int g_x = 22; const int g_y = 33; constexpr int g_z = 44; main.cpp:\n1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;stdio.h\u0026gt; int g_x = 222; const int g_y = 333; constexpr int g_z = 444; int main() { printf(\u0026#34;glabal variable (g_x, g_y, g_z) is (%d, %d, %d)\u0026#34;, g_x, g_y, g_z); return 0; } if we compile only main.cpp, it works fine and outputs:\nglabal variable (g_x, g_y, g_z) is (222, 333, 444) But if we compile both, it gets\n1 2 3 $ clang main.cpp a.cpp /usr/bin/ld: /tmp/a-ea4f54.o:(.data+0x0): multiple definition of `g_x\u0026#39;; /tmp/main-c44eb4.o:(.data+0x0): first defined here clang-13: error: linker command failed with exit code 1 (use -v to see invocation) As we sligtly modify main.cpp:\n1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;stdio.h\u0026gt; extern int g_x; const int g_y = 333; constexpr int g_z = 444; int main() { printf(\u0026#34;glabal variable (g_x, g_y, g_z) is (%d, %d, %d)\u0026#34;, g_x, g_y, g_z); return 0; } it\u0026rsquo;s compiled and linked properly with the output:\nglabal variable (g_x, g_y, g_z) is (22, 333, 444) noting that the g_x has the value 22 which is defined in a.cpp, we find out the global non-const variable has external linkage. And the properly compilation and linking show that global const has internal linkage.\nExternal linkage Cf. https://www.learncpp.com/cpp-tutorial/external-linkage/\nAn identifier with external linkage can be seen and used both from the file in which it is defined, and from other code files (via a forward declaration). In this sense, identifiers with external linkage are truly “global” in that they can be used anywhere in your program!\nFunctions have external linkage by default\nIn order to call a function defined in another file, you must place a forward declaration for the function in any other files wishing to use the function. The forward declaration tells the compiler about the existence of the function, and the linker connects the function calls to the actual function definition.\nGlobal variables with external linkage\nGlobal variables with external linkage are sometimes called external variables. To make a global variable external (and thus accessible by other files), we can use the extern keyword to do so:\n1 2 3 4 5 6 7 8 9 int g_x { 2 }; // non-constant globals are external by default extern const int g_y { 3 }; // const globals can be defined as extern, making them external extern constexpr int g_z { 3 }; // constexpr globals can be defined as extern, making them external (but this is useless, see the note in the next section) int main() { return 0; } Non-const global variables are external by default (if used, the extern keyword will be ignored).\nTo actually use an external global variable that has been defined in another file, you also must place a forward declaration for the global variable in any other files wishing to use the variable. For variables, creating a forward declaration is also done via the extern keyword (with no initialization value).\nHere is an example of using a variable forward declaration:\na.cpp:\n1 2 3 // global variable definitions int g_x { 2 }; // non-constant globals have external linkage by default extern const int g_y { 3 }; // this extern gives g_y external linkage main.cpp:\n1 2 3 4 5 6 7 8 9 10 #include \u0026lt;iostream\u0026gt; extern int g_x; // this extern is a forward declaration of a variable named g_x that is defined somewhere else extern const int g_y; // this extern is a forward declaration of a const variable named g_y that is defined somewhere else int main() { std::cout \u0026lt;\u0026lt; g_x; // prints 2 return 0; } Note that the extern keyword has different meanings in different contexts. In some contexts, extern means “give this variable external linkage”. In other contexts, extern means “this is a forward declaration for an external variable that is defined somewhere else”.\nSummary\nScope determines where a variable is accessible. Duration determines where a variable is created and destroyed. Linkage determines whether the variable can be exported to another file or not.\nInline function 考虑如下场景,有一段代码很独立,适合抽成一个函数,但你又担心函数调用开销,此时 inline function 就是你的最佳选择。关于合适使用 inline function,下面这段话给了一定的意见:\nFor functions that are large and/or perform complex tasks, the overhead of the function call is typically insignificant compared to the amount of time the function takes to run. However, for small functions, the overhead costs can be larger than the time needed to actually execute the function’s code! In cases where a small function is called often, using a function can result in a significant performance penalty over writing the same code in-place.\nInline function 的好处包括:\n没有函数调用的开销 编译器对展开后的代码有更大的优化空间(如常量替换) However, inline expansion has its own potential cost: if the body of the function being expanded takes more instructions than the function call being replaced, then each inline expansion will cause the executable to grow larger. Larger executables tend to be slower (due to not fitting as well in caches).\n注意:inline 只是对编译器的一个建议,是否会真的展开取决于编译器的优化策略。\nHowever, in modern C++, the inline keyword is no longer used to request that a function be expanded inline. There are quite a few reasons for this:\nUsing inline to request inline expansion is a form of premature optimization, and misuse could actually harm performance. The inline keyword is just a hint \u0026ndash; the compiler is completely free to ignore a request to inline a function. This is likely to be the result if you try to inline a lengthy function! The compiler is also free to perform inline expansion of functions that do not use the inline keyword as part of its normal set of optimizations. The inline keyword is defined at the wrong level of granularity. We use the inline keyword on a function declaration, but inline expansion is actually determined per function call. It may be beneficial to expand some function calls and detrimental to expand others, and there is no syntax to affect this. 注意:在 modern cpp 中,用 inline 修饰的不违反 ODR(one definition rule),因此可用于\n头文件中修饰常量作为 global const 的最佳方案 1 头文件中修饰 constexpr 函数 2 使所有 include 该文件的源文件都能使用该函数,注意 constexpr 函数是默认 inline 的 Allowing functions with a constexpr return type to be evaluated at either compile-time or runtime was allowed so that a single function can serve both cases. Otherwise, you’d need to have separate functions (a constexpr version and a non-constexpr version) \u0026ndash; and since return type isn’t considered in function overload resolution, you’d have to name the functions different things!\nA constexpr function that is eligible to be evaluated at compile-time will only be evaluated at compile-time if the return value is used where a constant expression is required. Otherwise, compile-time evaluation is not guaranteed.\nThus, a constexpr function is better thought of as “can be used in a constant expression”, not “will be evaluated at compile-time”.\nUnnamed namespace An unnamed namespace (also called an anonymous namespace) is a namespace that is defined without a name, like so:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include \u0026lt;iostream\u0026gt; namespace // unnamed namespace { void doSomething() // can only be accessed in this file { std::cout \u0026lt;\u0026lt; \u0026#34;v1\\n\u0026#34;; } } int main() { doSomething(); // we can call doSomething() without a namespace prefix return 0; } 特点:\nAll content declared in an unnamed namespace is treated as if it is part of the parent namespace. All identifiers inside an unnamed namespace are treated as if they had internal linkage. 解决的问题:Unnamed namespaces will also keep user-defined types (something we’ll discuss in a later lesson) local to the file, something for which there is no alternative equivalent mechanism to do.\nAbout switch clause\nPut another way, defining a variable without an initializer is just telling the compiler that the variable is now in scope from that point on. This happens at compile time, and doesn’t require the definition to actually be executed at runtime.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int calculate(int x, int y, char op) { int ret = 0; switch (op) { case \u0026#39;+\u0026#39;: return x + y; case \u0026#39;-\u0026#39;: return x - y; case \u0026#39;*\u0026#39;: return x * y; case \u0026#39;/\u0026#39;: return x / y; case \u0026#39;%\u0026#39;: return x % y; default: throw std::invalid_arguments(\u0026#34;invalid operator\u0026#34;); } } Pointer to functions The syntax for creating a non-const function pointer is one of the ugliest things you will ever see in C++:\n// fcnPtr is a pointer to a function that takes no arguments and returns an integer int (*fcnPtr)(); In the above snippet, fcnPtr is a pointer to a function that has no parameters and returns an integer. fcnPtr can point to any function that matches this type.\nTo make a const function pointer, the const goes after the asterisk:\nint (*const fcnPtr)(); If you put the const before the int, then that would indicate the function being pointed to would return a const int.\nNote that the type (parameters and return type) of the function pointer must match the type of the function. Here are some examples of this:\n1 2 3 4 5 6 7 8 9 10 11 // function prototypes int foo(); double goo(); int hoo(int x); // function pointer assignments int (*fcnPtr1)(){ \u0026amp;foo }; // okay int (*fcnPtr2)(){ \u0026amp;goo }; // wrong -- return types don\u0026#39;t match! double (*fcnPtr4)(){ \u0026amp;goo }; // okay fcnPtr1 = \u0026amp;hoo; // wrong -- fcnPtr1 has no parameters, but hoo() does int (*fcnPtr3)(int){ \u0026amp;hoo }; // okay Unlike fundamental types, C++ will implicitly convert a function into a function pointer if needed (so you don\u0026rsquo;t need to use the address-of operator (\u0026amp;) to get the function\u0026rsquo;s address). However, it will not implicitly convert function pointers to void pointers, or vice-versa.\nCalling a function using a function pointer The other primary thing you can do with a function pointer is use it to actually call the function. There are two ways to do this:\n1 2 3 4 5 6 7 8 int foo(int x) { return x; } int main() { int (*fcnPtr)(int){ \u0026amp;foo }; // Initialize fcnPtr with function foo (*fcnPtr)(5); // call function foo(5) via explict dereference of fcnPtr. fcnPtr(5); // call function foo(5) via implicit dereference of fcnPtr. return 0; } As you can see, the implicit dereference method looks just like a normal function call \u0026ndash; which is what you\u0026rsquo;d expect, since normal function names are pointers to functions anyway! However, some older compilers do not support the implicit dereference method, but all modern compilers should.\nOne interesting note: Default parameters won’t work for functions called through function pointers. Default parameters are resolved at compile-time (that is, if you don’t supply an argument for a defaulted parameter, the compiler substitutes one in for you when the code is compiled). However, function pointers are resolved at run-time. Consequently, default parameters can not be resolved when making a function call with a function pointer. You’ll explicitly have to pass in values for any defaulted parameters in this case.\nReferences 理解字节序 12.1 — Function Pointers 6.9 — Sharing global constants across multiple files (using inline variables)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n6.14 — Constexpr and consteval functions\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/cpp-learn/","tags":["cpp","note"],"title":"C++ 学习笔记"},{"categories":["Notes"],"contents":"终于还是干了!很早以前就有了这个想法,起初使用 Hexo 搭的博客,折腾了一段时间,选了一个不错的主题 Melody。一切都进行的很好,直到有一天又发现 Hugo 这么个东西,登时就忍不住了,不是说原来的不好,二十 Hugo 太特么快了,无论是渲染速度还是博客生成,几乎就像是在本地打开 html 一样。相比之下 Hexo 的生成速度就显得很慢了,思来想去,博客应该更多关注内容,之前那个配置的有些花哨了,现在换了 Hugo,也使用了一个比较简单的主题,相比之下更轻量,打开速度更快,以后就该专注于内容了。这里烧纸纪念下一下我之前的皮肤:\n如你所见,还可以在 https://guyueshui.github.io/blog_archive 访问它,但以后的更新还是主要在现在这个博客上。\n这里简单记录一下迁移过程中遇到的一些问题。\nFrontmatter 不匹配 Hexo 博文的 frontmatter 格式如下\n1 2 3 4 5 6 7 8 9 10 --- title: GDB 基本用法 date: 2019-08-18 16:14:24 categories: Techniques tags: - debug - linux - gdb - c++ --- 而 Hugo 博文的 frontmatter 如下\n1 2 3 4 5 6 --- title: GDB 基本用法 date: 2019-08-18 16:14:24 categories: [tech] tags: [\u0026#39;debug\u0026#39;, \u0026#39;linux\u0026#39;, \u0026#39;gdb\u0026#39;, \u0026#39;c++\u0026#39;] --- 还好我的文章不算多,一个个改吧!\n时间格式 Hexo 的时间格式很简单:2006-01-02 15:04. 但是 Hugo 的时间格式就比较麻烦了。默认模板archetypes/defalut.md中定义的 frontmatter 如下:\n1 2 3 4 --- title: \u0026#34;{{ replace .Name \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#34; date: {{ .Date }} --- 先不管 title,默认的.Date变量生成出来的时间格式是这样的:2006-01-02T15:04:59-0700, 看着怪难受的。我想让它变得简单直观一些,于是改成如下格式:\n1 2 3 4 5 --- title: \u0026#34;{{ replace .Name \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#34; date: {{ dateFormat \u0026#34;Mon Jan 2 2006\u0026#34; .Date }} lastmod: {{ .Date }} --- 使用dataFormat函数将输出格式转换成喜欢的格式,所有支持的格式参见 Hugo 文档 1。这里我将它转换成“Mon Jan 2 2006”这种格式,但是它丢失了时间信息,所以我加了一个lastmod表示最后更改日期,而且没有做格式转换。\n注意:貌似是 Go 语言的原因,Go 语言所有的时间计算都是有一个基准的,这个基准时间就是:2006-01-02 15:04:05 0700, 所有日期的格式设置都要根据这个时间来设,否则就会计算出错误的时间。至于为什么?可能是因为它比较好记:1 月 2 日 3 点 4 分 5 秒 6 年 7 时区。开个玩笑:)\n公式渲染 这个我是真的头大,这是先前阻止我转 Hugo 的唯一理由。Hexo 那边有专门的插件解决这个事情,而且可以配置 markdown 解析规则,因此比较完美的解析出公式段并正确渲染。但 Hugo 貌似就要自己动手了,之前也在网上找了很多解决方案,要么是主题自带,要么是手动添加 mathjax 支持到 head 里面。这样所有的页面都会加载 mathjax,但是还是避免不了解析错误的问题:比如在 markdown 里面_可以表示斜体的开头,但在 LaTeX 里面表示下标。诸如此类的字符冲突还有很多,如果不能自定义 markdown 的解析规则,那么就会导致有些公式无法正常渲染。另外我也试过 KaTeX+mmark 的方案,未果。\n到目前为止,我还是没有找到满意的解决方案。现在用的是 even 主题自带的 mathjax,所以可能还是会有一些公式无法正常渲染 (゚Д゚≡゚д゚)!?\n自定义字体 有了之前在 Melody 主题自定字体的经验,这次修改字体没有太费什么功夫。主要将主题的font-family改一下,以及将对应的字体放到网站中去。\n1 2 3 4 5 6 //! file: themes/even/src/css/_variables.scss // Font family of the site. $global-font-family: Linux Libertine O, \u0026#39;Source Sans Pro\u0026#39;, \u0026#39;Helvetica Neue\u0026#39;, Arial, sans-serif !default; // Serif font family of the site. $global-serif-font-family: Linux Biolinum O, Athelas, STHeiti, Microsoft Yahei, serif !default; Even 主题还是比较好的,将 serif 和 sans 分成了两族,这应该是很自然的。之前 Melody 里面只能设置全局,一换所有字体都换了,也可能是我不会换 orz. 可以看到这里可以自定义字体族,我只是在前面加了两个而已,\nserif: Linux Libertine O sans: Linux Biolinum O 这两个字体我十分推荐,源于 SICP 的排版字体,非常耐看!Sans 用于标题等粗文本,serif 用于排版正文。关于字体名字的获得:\n1 2 3 4 5 6 $ fc-list | grep Linux /home/yychi/.local/share/fonts/Libertine/LinLibertine_RBI.otf: Linux Libertine O:style=Bold Italic /home/yychi/.local/share/fonts/Libertine/LinLibertine_DR.otf: Linux Libertine Display O:style=Regular /home/yychi/.local/share/fonts/Libertine/LinLibertine_RI.otf: Linux Libertine O:style=Italic /home/yychi/.local/share/fonts/Libertine/LinBiolinum_R.otf: Linux Biolinum O:style=Regular /home/yychi/.local/share/fonts/Monaco_Linux.ttf: Monaco:style=Regular 然后将字体文件复制到博客根目录的static/fonts/Libertine文件夹,这样 hugo 生成网站的时候就会把字体文件一并获得。最后在_variables.scss或_custom.scss中设置一下字体目录就行了。\n1 2 3 4 5 6 7 8 9 @font-face { font-family: Linux Libertine O; src: url(\u0026#34;/fonts/Libertine/LinLibertine_DR.otf\u0026#34;); } @font-face { font-family: Linux Biolinum O; src: url(\u0026#34;/fonts/Libertine/LinBiolinum_R.otf\u0026#34;); } 另外可以使用 Google Font API 直接使用字体而不用下载字体文件\n因为中文字体文件一般比较大,而且放在 Github 上加载很慢,所以如果有线上字体可以用,自然优先考虑。之前我只换了英文字体,中文还保留着黑体。于是乎中文 sans 西文 serif 放在一起非常不协调。最后还是折腾一下,把中文字体也给换了。\n具体方法很简单,找到主题定义字体的地方:\n1 2 3 //! file: themes/even/src/css/_variables.scss // Font family of the site. $global-font-family: \u0026#39;Linux Libertine O\u0026#39;, \u0026#39;Noto Serif SC\u0026#39;, \u0026#39;Source Sans Pro\u0026#39;, \u0026#39;Helvetica Neue\u0026#39;, Arial, sans-serif !default; 这里我将西文优先选择 Linux Libertine O 字体,而中文则使用 Noto Serif SC 字体。\n注意:这里的在线字体必须是存在于 Google Fonts 里面的字体。\n然后就是在网页上添加一个表单 2:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://fonts.googleapis.com/css?family=Tangerine\u0026#34;\u0026gt; \u0026lt;style\u0026gt; body { font-family: \u0026#39;Tangerine\u0026#39;, serif; font-size: 48px; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div\u0026gt;Making the Web Beautiful!\u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 由于主题的head.html在每个网页都会调用,所以我将表单直接添加到该文件中:\n1 2 3 4 5 6 7 8 \u0026lt;!-- file: even/layouts/partials/head.html --\u0026gt; {{ range .Site.Params.customCSS -}} \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;{{ \u0026#34;/css/\u0026#34; | relURL }}{{ . }}\u0026#34;\u0026gt; {{ end }} \u0026lt;!-- Insert style sheet here --\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://fonts.googleapis.com/css?family=Noto+Serif+SC\u0026#34;\u0026gt; 这样一来,网站就会加载来自 Google Font API 的线上字体,如下图: 图片和表格居中 因为 Even 主题默认是居左的,所以这里改为居中 3。\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 //! file: themes/even/src/css/_partial/_post/_content.scss // 图片居中 img { display: inline-block; max-width: 100%; /* make img centerd @yychi */ height: auto; padding: 0.6em 0; position: relative; left: 50%; -webkit-transform: translateX(-50%); // for Safari and iOS -ms-transform: translateX(-50%); // for IE9 transform: translateX(-50%); } // 表格居中 .table-wrapper { overflow-x: auto; \u0026gt; table { max-width: 100%; margin: 0 auto; // make table centered @yychi border-spacing: 0; box-shadow: 2px 2px 3px rgba(0,0,0,.125); thead { background: $deputy-color; } th, td { padding: 5px 15px; border: 1px double $content-table-border-color; } tr:hover { background-color: $deputy-color; } } } 部署 Hugo 的部署,没有 Hexo 那样一步到位,每次都要手动操作。以至于一开始载了很多跟头。按照 Hugo 中文文档中的方法试了不行(好像坊间流传 Hugo 的文档写的不行),于是去翻英文文档。对照着做,其实还好,没那么困难。就是一开始走了点弯路,因为我的 blog 仓库并不是空的,之前的 Hexo 生成的网站还在。所以走流程的时候出了一些问题,各种文件冲突。后来干脆将 Hexo page 部署到另一个仓库,将 blog 仓库清空。然后按照流程走,终于走完了。\n其实还有一个问题,一开始我 Hugo 设置了输出 markdown 源文件,Hugo 会把它放到生成博文的同目录下,原本只有一个index.html, 现在多了一个index.md. 我想这很好啊,不用另外开一个仓库保存原始的 md 文件了,但是部署上去,github 报错,说生成的 md 文件里有语法错误,网站构建失败。没办法,现在不想折腾了,就在 Hugo 中把这个选项关了。\n部署过程参考文档即可:Host on Github. 我还是大致翻译一下吧:\n在你的 Github 上新建一个仓库,假设叫blog. 这个仓库将用来存放 Hugo 工程文件,也就是你本地的 Hugo site 根目录。 在你的 Github 上创建仓库\u0026lt;username\u0026gt;.github.io,其中\u0026lt;username\u0026gt;为你的 Github 账户名,这个仓库将用来存放 Hugo 生成的整个网站。如果你已经有这个仓库了,清空之。 git clone git@github.com:\u0026lt;username\u0026gt;/blog.git \u0026amp;\u0026amp; cd blog 将你的本地 Hugo 文件夹复制到blog中去,确保你的网站可以在本地正常运作(使用hugo server或hugo --config \u0026lt;your-config\u0026gt;.toml server)然后访问localhost:1313 如果你觉得网站已经符合你的预期了: 按下Ctrl+C终止本地服务 rm -rf public删除整个public文件夹,不用担心,你总可以使用hugo --config \u0026lt;your-config\u0026gt;.toml来生成它 将\u0026lt;username\u0026gt;.github.io添加为本仓库的 submodule, 这样一来public文件夹下的内容就会推送到\u0026lt;username\u0026gt;.github.io这个仓库。使用如下命令来完成:git submodule add -b master git@github.com:\u0026lt;username\u0026gt;/\u0026lt;username\u0026gt;.github.io.git public 使用hugo --config \u0026lt;your-config\u0026gt;.toml来生成你的网站,生成的文件将在public文件夹下 进入public文件夹,使用 git 完成推送 注意:此前需要在你的 Hugo 配置文件中更改相应的baseURL. 例如,更改为baseURL = \u0026quot;https://\u0026lt;username\u0026gt;.github.io\u0026quot;\n近日,又发现一个比较好的迁移指引 4,文章十分翔实。\n.Format | Hugo\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGet Started with the Google Fonts API\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHugo 建站记录\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHugo 与 Hexo 的异同\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/blog-trans/","tags":["字体","美化","个性化"],"title":"博客迁移记录"},{"categories":["tech"],"contents":"废话以后有时间再加。\n首先编译时开启调试选项:\n1 g++ main.cpp -g -O0 -O0指定编译器的优化级别为 0,即不优化。\n然后编译出来的可执行文件,默认名字是a.out. 直接了当,用 gdb 打开之,\n1 gdb a.out 要调试必然要打断点,两种方式:指定行数;指定函数。\n1 2 (gdb) break 10 // create breakpoint at line 10 (gdb) break main // create breakpoint at the entrance of main 使用list在 gdb 中查看代码块以确定你要在哪一行设置断点(这就很麻烦,所以一般直接在 main 函数打个断点,然后单步去 run)。\n设好断点以后,使用run启动程序,程序会在断点处停顿,等待你的输入指令。 使用next进行单步执行,使用step步入。所谓步入就是如果有函数调用,程序会跟踪到所调用的函数内部的代码,而单步的话,则会直接完成函数调用,获得返回值(如果有的话)。\n使用info locals查看栈变量的值,使用info args查看函数传入参数的值。使用print \u0026lt;variable\u0026gt;查看指定变量的值。\n备注 GDB 里面的命令都有缩写(break=b, next=n, step=s, \u0026hellip;) 什么命令也不敲直接回车默认执行上一条命令 使用help \u0026lt;command\u0026gt;来获取相关命令的使用帮助 ","permalink":"https://guyueshui.github.io/post/basic-gdb-usages/","tags":["debug","gdb","cpp"],"title":"GDB 基本用法"},{"categories":["Notes"],"contents":"最近实习接触到一个新的知识点,C/C++ 的位域结构体。\n以下开始摘抄自:here\n位段 (bit-field) 是以位为单位来定义结构体 (或联合体) 中的成员变量所占的空间。含有位段的结构体 (联合体) 称为位段结构。采用位段结构既能够节省空间,又方便于操作。\n位段的定义格式为:\ntype [var]: digits 其中 type 只能为 int,unsigned int,signed int 三种类型 (int 型能不能表示负数视编译器而定,比如 VC 中 int 就默认是 signed int,能够表示负数)。位段名称 var 是可选参数,即可以省略。digits 表示该位段所占的二进制位数。\n举个例子,你可以这样定义一个位域结构体:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // sizeof(A): 4 struct A { uint32_t a: 12; uint32_t b: 10; uint32_t c: 10; }; // sizeof(B): 12 struct B { uint32_t a; uint32_t b; uint32_t c; }; uint32_t实际上是unsigned int的别名,并且指定了用 4 个字节存储 int 类型的数据,而 1byte = 8bits, 4 个字节共计 32bits,结构体 A 使用了位域的方式,指定了每个成员所占用的 bit 数。可以看到,A 中三个成员总共占用的比特数为 32,也就是 4 个字节。所以结构体 A 所占用的空间就是 4 字节,而 B 没有使用位域,则 3 个成员各占 4 个字节,共计 12 字节。说白了,位域就是将结构体的成员在比特位上编排的更紧凑,更节省空间。\n以下开启一段摘抄:\n关于位域结构体有以下几点说明:(以下“位段就是位域”,c.f. ref1)\n位段的类型只能是 int,unsigned int,signed int 三种类型,不能是 char 型或者浮点型; 位段占的二进制位数不能超过该基本类型所能表示的最大位数,比如在 VC 中 int 是占 4 个字节,那么最多只能是 32 位; 无名位段不能被访问,但是会占据空间; 不能对位段进行取地址操作; 若位段占的二进制位数为 0,则这个位段必须是无名位段,下一个位段从下一个位段存储单元 (这里的位段存储单元经测试在 VC 环境下是 4 个字节) 开始存放; 若位段出现在表达式中,则会自动进行整型升级,自动转换为 int 型或者 unsigned int。 对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。 位段不能出现数组的形式。 对于位段结构,编译器会自动进行存储空间的优化,主要有这几条原则:\n如果一个位段存储单元能够存储得下位段结构中的所有成员,那么位段结构中的所有成员只能放在一个位段存储单元中,不能放在两个位段存储单元中;如果一个位段存储单元不能容纳下位段结构中的所有成员,那么从剩余的位段从下一个位段存储单元开始存放。(在 VC 中位段存储单元的大小是 4 字节). 如果一个位段结构中只有一个占有 0 位的无名位段,则只占 1 或 0 字节的空间 (C 语言中是占 0 字节,而 C++ 中占 1 字节);否则其他任何情况下,一个位段结构所占的空间至少是一个位段存储单元的大小; 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 64 65 #include \u0026lt;iostream\u0026gt; using namespace std; // sizeof(A): 4 struct A { uint32_t a: 4; uint32_t b: 3; uint32_t c: 1; }; // sizeof(B): 12 struct B { uint32_t a; uint32_t b; uint32_t c; }; // sizeof(C): 8 struct C { uint32_t a: 1; uint32_t : 0; uint32_t c: 2; // 不会和 a 凑在一起,新开一个字节存 }; // sizeof(D): 12 struct D { uint32_t a: 1; uint32_t: 0; // 隔断 uint32_t: 6; // 开启新的位域存储单元 uint32_t d: 32; // 前一个位域不够放,开启新的存放单元 }; // sizeof(E): 4 // 内存分布简图 // 0000 0000 0000 0000 // a--- b--- cd------- struct E { uint32_t a: 1; char b; // 隔断 uint32_t c: 1; // 在下一个存储单元 uint32_t d: 15; // 四个成员刚好占用 32bits,即 4 个字节 }; template \u0026lt;typename T\u0026gt; void Print(const T\u0026amp;) { std::cout \u0026lt;\u0026lt; sizeof(T) \u0026lt;\u0026lt; std::endl; } // test int main() { A a; B b; C c; D d; E e; Print(a); // 4 Print(b); // 12 Print(c); // 8 Print(d); // 12 Print(e); // 4 return 0; } 以下测试用法:\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;cstdio\u0026gt; using namespace std; // sizeof: 4 struct TcpMsgHead { uint32_t length: 16; uint32_t flags: 8; int num: 8; }; void Print(const TcpMsgHead\u0026amp; msg) { printf(\u0026#34;length: %8d\\n\u0026#34;, msg.length); printf(\u0026#34;flags: %8d\\n\u0026#34;, msg.flags); printf(\u0026#34;num: %8d\\n\u0026#34;, msg.num); } // test int main() { TcpMsgHead head; head.length = 0xffff; // 2^16 - 1 = 65535 head.flags = 0xff; // 2^8 - 1 = 255 head.num = 0xff; Print(head); return 0; } 上述程序的输出为:\nlength: 65535 flags: 255 num: -1 解释一下为什么 num 成员的值为 -1: 首先 num 设置的比特位为 8,而 num 的类型为 int,是有符号的。对于有符号的整数,计算机内部使用补码表示的。 0xff 换成二进制 1111 1111 这正好是 -1 的补码。\n其实,Ycm 给出的提示已经很明确了: Ycm 提示这里发生了隐式截断。255 被截断成了 -1. 如果换成 0x11 也就是二进制的 0001 0001,则不会发生截断,因为 8 比特足够描述 0x11,符号位是 0,表示正数,所以这很符合我们的预期。但是如果再加一个 2 呢? 可以看到 Ycm 提示发生截断, 529 被截断成了 17,即 0010 0001 0001 被截断成了 0001 0001 也就是说,我结构体定义的时候已经确定了 num 只有 8 比特位可以存。高于 8 比特的数据全都截断。如果最高位是 1,则会被转成负数,这可能和你的预期不符。所以一定不要设置超过容量的数据。\nReference 浅谈 C 语言中的位段 ","permalink":"https://guyueshui.github.io/post/bit-field-struct/","tags":["cpp","结构体","位运算"],"title":"位域结构体简介"},{"categories":["Notes"],"contents":"总结一下这几个月的面试经历中被问到的问题,虽说问得都很浅,但是,问深了我也不会呀!\nC++ 相关 Q: std::vector push_back 的复杂度是多少? A: O(1), amortized constant.\nQ: vector 从 1 到 n push n 个元素,假设发生扩容时按两倍增长,写出复杂度关于 n 的表达式? A: 不会。\n假设第一次只分配一个元素的空间。那么发生扩容的点依次如下: $$ 1,2,4,8,16,\u0026hellip;, 2^{\\lfloor{\\log_2 n}\\rfloor} $$ 每次扩容都会 copy 之前内存中的所有元素,所以总共发生的拷贝次数为: $$ 1+2+4+\\cdots+2^{\\lfloor{\\log_2 n}\\rfloor} $$ 注意这里共有$\\lfloor \\log_2 n \\rfloor$项相加,于是有 $$ 1+2+4+\\cdots+2^{\\lfloor{\\log_2 n}\\rfloor} = O(\\lfloor \\log_2 n \\rfloor \\cdot 2^{\\lfloor{\\log_2 n}\\rfloor}) = O(n\\log n) $$ 加上 push_back 操作的次数 n,所以有 $$ T(n) = O(n\\log n + n) = O(n \\log n) $$\nQ: 对于一个 vector 容器,删除元素后迭代器会失效吗? A: 对于删除给定元素前的迭代器不会失效,被删除元素之后的迭代器全部失效。\n向容器添加元素后:\n如果容器是 vector 或 string,且存储空间重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。 对于 deque,插入到除首尾元素以外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在元素的指针和引用不会失效。 对于 list 和 forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代)、指针和引用都有效。 当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效,这应该不会令人惊讶。毕竟,这些元素已经被销毁了。当我们删除一个元素后:\n对于 list 和 forward_list,指向容器其他位置的迭代器(包括尾后和首前)、引用和指针仍有效。 对于 deque,如果在首尾之外的任何位置删除元素指向被删除元素外其他元素的迭代器、引用和指针也会失效。如果是删除 deque 的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。 对于 vector 和 string,指向被删除元素之前的元素迭代器、引用和指针仍有效。注意:当我们删除元素时,尾后迭代器总是会失效。 ──《C++ Primer》 关于迭代器失效的问题,另参阅:https://blog.csdn.net/lujiandong1/article/details/49872763\nQ: 对于使用 hash 函数组织的 unordered_map,对于给定的 hash 函数,假设我总能构造一个集合,使得该集合内所有元素的 hash 值相同,即该集合内所有的 key 都映射到一个桶内。这种问题该如何解决? A: 不会。\n可以使用两个 hash 函数,例如 h1 和 h2,然后将同一个 key 的 hash 值用某种方式连接起来,以此定义一个新的复杂 hash 函数。对于这个函数,碰撞的概率将会非常小,因此可以认为你设计不出这样的集合,使得集合内的元素都发生碰撞。\nQ: new/delete 和 malloc/free 有什么区别? A: new/delete 在 C++ 中是运算符,但 malloc/free 是继承自 C 的内存管理函数。malloc 只负责开辟指定大小的内存,并不负责初始化,而 new 及开辟内存,也会构造对象,如果要为自定义的类型申请动态内存,则必须使用 new,new 一个类型会调用该类型的构造函数。而 delete 和 free 的区别也是类似,delete 调用类的析构函数,然后在释放内存,free 仅释放内存。\nQ: 以下调用哪个函数?\n1 2 3 4 5 6 7 8 9 10 11 12 class base { public: virtual void fun(int a) { cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; } }; class derived : public base { public: void fun(int b) { cout \u0026lt;\u0026lt; \u0026#34;derived\u0026#34; \u0026lt;\u0026lt; endl; } }; base* pb = new derived(); pb-\u0026gt;fun(3); A: derived. 虚函数的动态绑定,根据运行时类型调用对应的版本,虽然是基类指针,但实际上是派生类对象,所以调用派生类中的函数。动态绑定是 C++ 支持多态的根本原因。关于动态绑定,参见:here.\n另外,定义虚函数会增加空间开销,一旦定义了虚函数,就会生成虚函数表和虚指针,动态绑定实际上是通过查表来确定具体调用哪个版本的函数。另外构造函数不能声明为虚函数,析构函数可以,但析构函数不能声明为纯虚函数。\nQ: 重载(overload)和重写(override)有什么区别? A: 重载指函数签名(函数原型)不同的两个函数名字相同,重写是指子类覆盖(重写)基类的方法,签名必须相同。注意,返回值不算函数签名,const 算重载!\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 #include \u0026lt;iostream\u0026gt; using namespace std; class Base { public: virtual void f(int x) { cout \u0026lt;\u0026lt; \u0026#34;Base::f(int)\u0026#34; \u0026lt;\u0026lt; endl; } void g(float x) { cout \u0026lt;\u0026lt; \u0026#34;Base::g(float)\u0026#34; \u0026lt;\u0026lt; endl; } }; class Derived : public Base { public: void f(float x) { cout \u0026lt;\u0026lt; \u0026#34;Derived::f(float)\u0026#34; \u0026lt;\u0026lt; endl; } void g(float x) { cout \u0026lt;\u0026lt; \u0026#34;Derived::g(float)\u0026#34; \u0026lt;\u0026lt; endl; } void g(int x) { cout \u0026lt;\u0026lt; \u0026#34;Derived::g(int)\u0026#34; \u0026lt;\u0026lt; endl; } }; // test int main() { Derived dobj; Base* pd = \u0026amp;dobj; pd-\u0026gt;f(3); // Base::f(int) pd-\u0026gt;g(3.3f); // Base::g(float) dobj.f(3.3f); // Derived::f(float) dobj.g(3.3f); // Derived::g(float) dobj.g(3); // Derived::g(int) return 0; } 子类中 g 发生了重载。而基类指针调用 f 时,发现是虚函数,会去查表(发生动态绑定),但子类中并没有相应的重写(override),子类中的 f 仅仅是另一个函数,所以查表得知还是调用基类的 f; 基类指针调用 g 时,发现不是虚函数,直接调用基类的 g. 此间,子类的两个 g 对基类的 g 都是隐藏!\nQ:memcpy 和 memmove 的区别 A:memcpy 不能应对内存重叠,memmove 可以。详见 man pages.\nQ:为什么要内存对齐? A:不会。\n我们关注内存对齐不外乎下面两大原因:\n平台原因 (移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取 某些特定类型的数据,否则抛出硬件异常。(平台移植是驱动程序开发者经常需要考虑的问题)。 性能原因:数据结构 (尤其是栈) 应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。 可以使用预编译指令pragma pack(n)来指定对齐系数,n=1,2,4,8,16. 结构体对齐将按照最大成员大小和 n 中的较小值来对齐。\n算法相关 Q: 从一个数组中删除一个元素,复杂度是多少?如果不关心索引和值的对应关系。 A: O(1), 将要删除的元素与最后一个元素互换,将数组的长度减一。\n注意:这里他说不关心数组的索引和值的对应关系。连续存储就是数组,只不过先前通过索引 i 获得的值,并不一定和删除后通过索引 i 获得的值相同。我答的是 O(n),因为要删除一个元素,还得将删除元素之后的元素前移一位,这样才是我理解的删除的语义。但是面试官说不关心索引与值的对应关系,那么假设数组长度为 n,删除元素位置为 i,在互换 A[i] 和 A[n] 之后,删除后数组的 A[i] 变成了原先的 A[n], 但一般认为删除后 A[i] 应该是原先的 A[i+1], 这就是他说的不关心索引和值的对应,和我所理解的不一样。\nQ: 生成 50 个 [0, 50) 间的随机整数,构成一个序列,要求不能重复? A: 用一个长度为 50 的数组用作 map,初始化全为 0. 然后使用随机数发生器生成 [0, 50) 之间的整数,并在 map 对应索引位置 +1. 依次生成随机整数,且判断其索引对应的值是否为 0,等于 0 说明还未生成过,等于 1 说明已经生成过,应该换一个。但是这样不能保证收敛,也许它永远生成不满 50 个随机整数。\n其实有 O(n) 的解法,延承自上一问。设有一副扑克牌,共 50 张,你怎样将它随机地分给排成一列的 50 个人?很简单,每次随机抽出一张给一个人,抽完了,也就分完了。现在你有一个随机数发生器,每次随机生成一个索引,你抽出该索引位置的牌(从数组中删除该元素),这个操作等价与将该索引的元素与数组最后一个元素互换。下一次,从 [0, 49) 里随机生成一个索引,纵然该索引与之前的可能一样,但对应位置的牌已经换了,所以不会抽出重复的牌。如此反复,抽完为止。这样你每删除一个元素的复杂度是 O(1), 所以总共的复杂度就是 O(n).\n机器学习相关 Q: K-均值聚类中 k 如何选取,评价指标,他很不稳定,如何让它稳定一些。\n评价指标:类间相似度低,类内相似度高。K-means 的稳定性较差,可能每次随机选取的初始聚类中心不一样,而导致聚类的效果不一样。从某种程度上来说,K-means 对数据较为敏感,无法识别离群点。传统聚类采用的距离度量通常为欧式距离,Kernel k-means 将所有样本映射到另外一个特征空间中再进行聚类,就有可能改善聚类效果。\n一种改进:K-means++,它的思想是在已经选定 n 个聚类中心后,选取第 n+1 个聚类中心时:距离当前 n 个聚类中心越远的点会有更高的概率被选为第 n+1 个聚类中心。在选取第一个聚类中心时同样使用随机的方法,这个改单简单有效。\n另外,一个很重要的问题,K 的值如何选取?在低维度时,可以将数据画出来看大致有几个类,而在面对高维和复杂数据时,这种方法难以使用。关于 K 的选取:ISODATA,迭代自组织数据分析法,它的思想是:当属与某个类别的样本数量过少时就把该类别去除;当属与某个类别的样本数量过多时,分散程度较大时,将该类别分裂为两个子类。\nQ: 正则化如何克服过拟合?背后的原理是什么?\n正则化控制模型的复杂度。正则系数越大,模型拟合能力越差(偏差大,方差小),复杂度越低,一定程度上克服了过拟合。考虑正则系数从小变大,一开始偏差较小,方差较大。因为模型足够复杂,可以很好的学到数据特征,但也容易过拟合,对特定的数据集很敏感,也许换一个数据集就会是不一样的结果,此时已经发生了过拟合。随着正则系数不断变大,偏差也跟着增加,但方差会随之减小,因为正则项事实上限制了模型的拟合能力,降低了模型的复杂度。牺牲了偏差(变大),换取了方差(变小)。而在有限的训练数据集上,方差过大就意味着过拟合,意味着模型对数据集敏感。这不是我们想要的。整个过程的泛化误差先减小(方差减小)后增大(偏差增大),先是方差主导,后是偏差主导。最佳的正则系数应该对应着最小的泛化误差。详见 PRML p.151\nQ: 有没有接触过其他的网络模型,CNN,RNN 等?\n没有。\nQ: vae 是生成网络吗?可不可以和 infogan 结合?了解一下 vae?\n是。\n纽劢 random forest 需要对特征做什么处理吗? 常用的特征选择方法有哪些? 主成分分析做特征选择有哪些缺点? l1 regularization 也可以做特征选择? 特征有离散有连续该怎么处理?one-hot encoding 小米 一面\nQ: 了解 LRU 缓存吗? A: 没听说过。\n这里说的缓存是一种广义的概念,在计算机存储层次结构中,低一层的存储器都可以看做是高一层的缓存。比如 Cache 是内存的缓存,内存是硬盘的缓存,硬盘是网络的缓存等等。\n缓存可以有效地解决存储器性能与容量的这对矛盾,但绝非看上去那么简单。如果缓存算法设计不当,非但不能提高访问速度,反而会使系统变得更慢。\n从本质上来说,缓存之所以有效是因为程序和数据的局部性(locality)。程序会按固定的顺序执行,数据会存放在连续的内存空间并反复读写。这些特点使得我们可以缓存那些经常用到的数据,从而提高读写速度。\n缓存的大小是固定的,它应该只保存最常被访问的那些数据。然而未来不可预知,我们只能从过去的访问序列做预测,于是就有了各种各样的缓存替换策略。本文介绍一种简单的缓存策略,称为最近最少使用(LRU,Least Recently Used)算法。\nQ: https 加密方式? A: 不知道啊。\nQ: 输入网址到浏览器展示的过程? A: 不知道。\nHTTP 的工作流程大致如下:\n地址解析 封装 HTTP 请求。将访问信息以及本机的一些信息封装成一个 HTTP 请求数据包 封装 TCP 包。建立 TCP 连接 , 也就是常说的\u0026quot;三次握手\u0026quot; . 由于 HTTP 位于最上层的应用层 , 所以 HTTP 在工作之前要由 TCP 和 IP 协议建立网络连接。 客户端发送请求命令。在连接建立之后 , 客户端发送 HTTP 请求到服务端与请求相关的信息都会包含在请求头和请求体中发送给服务器端 . 服务端响应。服务器端在收到请求之后 , 根据客户端的请求发送给客户端相应的信息 , 相关的响应信息都会放在响应头和响应体中 . 关闭连接。服务器端在发送完响应之后 , 就会关闭连接 , 如果过客户端的请求的头部信息中有 Connection: keep-alive , 那么客户端在响应完这个请求之后不会关闭连接 , 知道客户端的所有请求都响应完毕 , 才会关闭连接 , 这样大大节省了带宽和 IO 资源 . 二面\n(Sept. 6)\n自我介绍,实习工作内容 局部静态对象和全局静态对象有什么区别 进程间通信的方式 有哪些同步原语 堆内存和栈内存的区别 int a, *b;有什么区别?(考察指针默认不开辟内存) 58 同城 (Sept. 20)\n挑一个你最熟的项目说下? Pooling 是怎么回事,有什么用? 二叉树序列化,空指针序列化为 null,e.g., [1,2,3,4,null,null,5] CNN 和 DNN 的区别? 卷积有什么作用? 一面面完之后,建议我转后端,我说好啊,然后二面面的后端,似乎是个 leader,主要就是问问实习经历,然后让我等 HR 面。下午三点到的 58,二面面完三点四五十,然后等 HR 等了大概 40 分钟,好像是好多人排队等 HR 面,后来我找人问情况,说我五点有事,能不能安排一下,然后就有一个看着像技术的 HR,承担了我的 HR 面。面完等结果,说是择优录用。\n网易游戏 一面 (Spet. 20)\n面完 58 已经四点五十了,想着怎么着也赶不上网易面试了。所以下了大楼,就找了个人不多的地方,掏出电脑,席地而坐,打开手机热点,打开面试地址(这里真要吐槽一下,我是 Linux 系统,这次面试它没发邮件,只以短信形式发了面试链接,我电脑上又没有微信 QQ,只好手敲网址,那时候已经要开始了,可把我急坏了,还敲错了几次,把大写的 I 误认为小写的 l),就开始面试起来。\n自我介绍 static 关键字有何作用 const_cast 是干嘛的 说一下 c++ 多态 一个类有一个指针成员,拷贝构造函数的调用会造成什么问题?(拷贝构造函数只做指针的拷贝,所以新的对象并不拥有资源,可能原对象的资源已经释放,这就导致了空悬指针,而且如果在析构函数中释放资源的话,可能会造成二次释放) 如何解决这种问题?(std::shared_ptr) std::shared_ptr 实现原理,循环引用怎么解决?(RAII,std::weak_ptr) 编程:手写 memcpy(注意 overlap,从后往前拷贝) map 和 hash map 的区别? 红黑树的特性? 了解图形学吗?(不) 基本的渲染流程知道吗(不) 一个游戏服务端,出现了大量的 TIME_WAIT,可能是什么原因?(不知道) TIME_WAIT 知道吗?为什么又 TIME_WAIT?(TCP 四次挥手) 编程:实现一个接口,从 M 个玩家中随机选取 N 个发放奖品(数组 shuffle,我当时有点紧张,写的很乱) 设计题:如果一个游戏,很多玩家同时登陆,需要排队,我想展示给玩家前方排队人数以及预计等待时间,怎么实现?(妈呀,不会,但是还是得扯) 队列? 随机访问怎么办? 实现随机访问的接口 玩家中途退出怎么办? 用链表,节点中存有前面等待的人数,时间可以简单相乘 中间玩家退出,后续节点都要更新? 跳表,链表之上加个索引调表,这样既能很快计算前方等待人数,又方便更新(问到这里,他就没再问了,说时间差不多了,就到这里,希望给个二面) 二面 (Oct. 17)\n还是盼来了二面,然而很那个,怎么说呢,感觉不是很擅长。全程问我怎么设计游戏,不太会。\n自我介绍 为什么投网易游戏? 平时喜欢玩游戏吗?玩的最多的是什么游戏? 你说喜欢玩安琪拉,如果让你设计一个安琪拉,你会怎么设计?(一边瞎写,一边瞎扯) 你会添加哪些成员函数,和成员变量? 你说到了装备,如果让你设计一个装备系统,你会怎么做? 我们知道角色 HP 受很多因素影响,比方说装备,受到攻击,增益 buf 等,那么你是如何更新角色 HP 的呢?(说到了观察者模式) 谁是观察者,谁是被观察者? 我们知道装备的属性大部分是静态的,对于静态属性的也要用观察者模式订阅吗? 你说到了观察者模式,平时项目中有用到过哪些设计模式? 你是如何学习设计模式的? 说说你对 protobuf 的理解? 我们知道 proto 文件,使用 proto 文件有什么好处? 大概知道 protobuf 的压缩原理吗?比方说如何编码 int32. 现在有一个老版本的 protobuf 序列化之后的字符串,但是后来又新加了一个变量。具体来说, required int32 hp; optional int32 lv; ===saved=== optional int32 mp; ===readed=== 我们知道,proto 是支持这种添加之后,仍然能够正确反序列化旧版本的字符串。你知道 protobuf 是怎么做的吗? 实现一个带 max() 函数的栈? 使用辅助栈,需要额外 O(n) 的空间,如果没有这么多空间给你,假设只有 O(logn),如何优化?(没答出来) 说说你对 boost::asio 的理解? 说说你实习中遇到的多线程问题? 学过哪些计算机基础课?(没有) 压力最大的一件事? 目前手上有 offer 吗? 方便说下哪几家吗? 期望薪资大概多少? 我这边差不多了,你有什么要问我的吗? 看样子网易平均 50 分钟,面的感觉一般吧,51 分,希望给个三面!\n原来没有三面,今天(Oct. 22)已经收到意向书,感动!\n在定位并处理应用程序出现的网络问题时,了解系统默认网络配置是非常必要的。ipv4 网络协议的默认配置可以在/proc/sys/net/ipv4/下查看,其中与 TCP 协议栈相关的配置项均以 tcp_xxx 命名。可以使用 cat 查看,也可以使用 sysctl 批量查看。\n1 2 3 4 5 6 7 8 9 10 11 12 13 $ cat /proc/sys/net/ipv4/tcp_tw_reuse 2 $ whatis sysctl sysctl (2) - read/write system parameters sysctl (8) - configure kernel parameters at runtime $ sysctl net/ipv4 net.ipv4.cipso_cache_bucket_size = 10 net.ipv4.cipso_cache_enable = 1 net.ipv4.cipso_rbm_optfmt = 0 net.ipv4.conf.all.accept_local = 0 ... 白山云科技 (Sept. 20)\n面完网易之后,已经下午六点了,又赶去了白山的宣讲会,还好还没结束,那边研发还有几个人在面。等了一会之后,可能面试官们已经累了,直接 leader 面的我,并且本来应该有三轮技术面,一轮 HR 面。可能我去的太晚了,leader 面过之后就算作二面通过,等了一会儿之后,直接 HR 面了。顺利拿到 offer,秋招第一个 offer,感谢!听说本来研发是几个人一起面,和面试官坐在一个圆桌上,面试官挨个问问题,好可怕。\n百度 (Sept. 25)\n畅谈 50 分钟,反手挂!是我太菜,打扰了(这里吐槽一下百度选的垃圾酒店,太偏僻了,太难找了)面试官全程盯着简历问。\n自我介绍 实习工作 说一下异步和同步以及各自的优缺点? 数据库了解吗?(不) 在项目中做过索引吗?(没) B 树 B+ 树了解吗?(不,一问三不知,笑) 链表和数组的区别? linux 查找包含特定内容的文件? 如果还想查找当前目录的子目录呢? 查找一个文件中特定内容出现的总行数? 说一下 TCP 三次握手? 手写代码:求一个二叉树的高度?(当时用的层次遍历到一个矩阵,然后输出行数,很笨的方法) 说一下 KNN? 那 k-means 呢?(怪我光准备后台了,算法没复习,这两个搅浑了,恐怕是导致反手挂的直接原因) 后面就没再问了,估计那时候面试官已经给我挂了,然后象征性的问我有没有问题问他,我也象征性的问了几个,然后就没有然后了。\n上海银行? (Sept. 25)\n我是去搞笑的,约的面试时间是下午三点,面完百度开始往那赶,赶到哪里的时候三点一刻,还怕来不及。结果去了等了两个多小时,还没排上我!听面完回来的同学说,5 个面试官,同时面 3 个同学,算是群面?然后这样一组要面 40-50 分钟。三人一组的去,面完一组按编号叫下一组。虽然我是迟到了,排到了 101 号,但是两个小时后,才面到 85 号。也就是说,还有 15 人,三组,120 分钟!三点去,要等的七点才能面上!算了,银行都这么高冷,(让我想起找实习的时候面招行,12 个人分两组,一起搭积木,不,是按要求搭积木,还要能扯,把你搭好的积木吹上天,关于技术问题,一个都不问!)我惹不起,等到五点半就溜了。\n依图 (Sept. 27)\n自我介绍 看到我实习里面有用过 protobuf,问了一下 protobuf 的优点 TCP 协议?(感觉问了很多网络) 如何查看系统负载?各 CPU 负载? 如果你写了一个模块,上线后用户反映其他模块的相应变慢了,你如何确定是不是你的原因?排查步骤如何? 手撕代码:最大子序列和(leetcode 原题,之前看到了,但是没做出来,然后不了了之,于是现场可想而知,虽然最后在讨论下写出来了,但是很糟糕,写的很乱,然后很慢,据说依图看重写代码的速度 orz) 由于写代码花了太长时间,直接结束 (果然,凉凉)\n招银网络科技 电面 (Sept. 28)\n忘得差不多了。\n自我介绍 sizeof 和 strlen 区别 如果字符串不以空字符结尾,会引发什么错误?(我说 undefined behavior,他说 invalid address) const 成员意义 数据库了解吗?(不,银行数据库还是比较重要的,这方面很弱势 orz) 如何用两个栈模拟一个队列(剑指 offer 原题) (Oct. 13) 现场 一面\n自我介绍 有哪些进程间通信的机制? 如何避免死锁? 平时如何解决内存泄漏?(智能指针,少用动态内存) 说说几种智能指针的原理? 说说 socket 编程的基本步骤? 说说常见的 I/O 模型 知道 epoll 吗? epoll 的两种出发模式 ET 和 LT 了解吗?(不) 异步 I/O 有什么优点?(提高吞吐量) 数据库了解吗?(不) 说说数据库事务有什么特性?(不会) 手撕代码:合并两个有序链表 二面\n二面面试官看起来想速战速决,自我介绍都掠过了?\n挑一个项目跟我介绍一下? 你说到异步,为什么要用异步? 异步是如何实现的,底层原理了解吗?(大概说了一下,可能不对) 你说到 protobuf,为什么要用 protobuf? protobuf 的底层 IO 模型是什么?(???还有这个) 为什么要进行序列化? protobuf 如何反序列化的?为什么能够反序列化?(我说按照一定方式编码,自然能按照一定规则解码鸭) 有两个小朋友 A、B 给其他小朋友派发水果,A 给出两个苹果,B 就得给出一个梨。如何实现这段逻辑?(用两个队列,一个放苹果,一个放梨,一个 pop 两次,一个 pop 一次;或者设一个 flag,当为 0 或 1,则 A 发苹果,当为 2,则 B 发梨,再置为 0) 如果让你设计一个死锁,你会怎么做? HR 面\n应该是去现场的都会面完三轮,不过结果如何就不知道了。问了 HR,说两周内出结果,求个 offer。\n新东方 (Oct. 11)\n自我介绍 进程和线程的区别 进程间通信机制 说一下内存池和线程池? TCP 和 UDP 的区别? 说一下数据库的主键、外键、和索引(一顿瞎说,他说知道思想就行) -(后面记不得了) 面试官意外的好说话,全程笑嘻嘻,我还迟到了 10 分钟。这次体验很好,可惜 base 北京。\n中移(苏州)软件研发中心 (Oct. 14)\n一面\n自我介绍 scoket 编程的基本步骤 说一下 TCP 四层模型? telnet 在哪一层? telnet 和 ssh 有什么区别? TCP 和 UDP 的区别? 说一下 TCP 的三握四挥? TIME_WAIT 的含义,为什么会有 TIME_WAIT? 能大概说说 TCP 的协议头吗? PSH 标志有什么作用? TCP 的滑动窗口是干嘛用的? 有什么方法可以查看 socket 状态? 你说对 linux 比较熟悉,比如一个磁盘快被占满了,如何得知是哪些文件占用了磁盘?(df -h, du -hd 1) 有一个实时更新的日志文件,如何查看该文件的动态更新?(这个不知道) 如何去掉一个文件中的空行?你能想到几种方法? 如何替换文件中的某个字符串?(sed) 排序算法有了解吗? 写个快排吧(紧张,没在规定时间内写出来,然后就说了下思路) 我们这边没什么问得了,你有什么想问的吗? 一面两个面试官轮流夹击,我一度十分惊慌。\n二面\n人格面试什么的最不擅长。\n三面\n等了很久,三面面试官似乎出去办事去了。以至于面试的时候,面试官似乎想快点结束。\n自我介绍一下? 有哪些稳定的排序算法?(插入、冒泡、归并) 快排为什么不稳定? poll/epoll 的区别是什么? 这些东西你是怎么学的,看书还是看博客?(看 man pages,面试官眼睛就亮了) 手上有几个 offer? 挺好,我觉得你很不错,基础很扎实(#手动滑稽) 很好,今天就面到这里,你回去等消息吧。 事实上我连自我介绍都没说完,就被打断了,然后也就问了几个问题,不到 10 分钟就结束了。希望给个 offer 吧!\n米哈游 (Oct. 17)\n自我介绍 C++ 多态 动态绑定的原理 虚函数原理 虚函数表是一个对象一个还是一个类一个?(不会) 说一下 C++ 四个智能指针以及各自用法? STL 了解吗? map 的底层实现使用的什么数据结构? 红黑树的插入、删除、查找的平均和最坏复杂度是多少?(O(logn) 和 O(n)) vector 底层实现? vector push_back 和 pop_back 的复杂度 操作系统的大端序和小端序知道吗? 写一个程序判断某个机器是大端序还是小端序?(不会:https://www.cnblogs.com/yooyoo/p/4717709.html) 写一个程序判断某个机器是 32 位还是 64 位?(不会,32 位指针占 4 字节,64 位指针占 8 字节) 如果起了一个进程,该进程使用 tcp 连接绑定了一个端口,如何查看这个端口是多少?(netstat -tp) 如何查看一个进程所占用的内存?(htop) htop/top 会显示很多个内存,哪个才是你要的? 有一个基本有序的数组,用什么排序算法比较好?(答了希尔排序,错的,插入排序) 讲一下希尔排序?(推广的插入排序) 对于有序数组,插入排序的时间复杂度是多少?(答了 O(n^2),没救了,面试官已经告诉我答案了,我居然还没发现!O(n)) 快排的最好最快复杂度是多少?(问排序问崩了,脑子已经糊了,答了 O(n) 和 O(n^2),实际上快排最好也就 O(nlogn)) 快排在什么情况下性能最差?(答了在倒序的时候,尼玛正序的时候也是啊) 那什么情况下最好呢?(答了正序的时候,尼玛快把这个沙雕拖出去!应该是每次划分正好左右两边平衡的时候,这样生成的递归树最平衡,复杂度最低) 你实习都干了啥?看你写了 Asio,你说说你实习的时候网络模型是什么样的,就是网络和业务的对接?(没接触业务) -(针对我实习时候 protobuf 部分的内容问了很多,但是我都没说清楚,有点忘了) 为什么投游戏行业? 平时玩游戏吗?(玩了很久的崩坏 3) 多少级?(满级,80) 哇,80 级大佬!(后来把号卖了) 卖了?(肝不动啊!) 我这边问完了,你有什么想问的?(问了针对岗位的建议) 应届生还是应该注重基础:数据结构、算法、网络、高并发等等。 层次分明:语言 + 操作系统 + 算法 + 简历,感觉米忽悠的面试官水平挺高的。\nReferences 大量 TIME_WAIT 的终极详解和解决方案 ","permalink":"https://guyueshui.github.io/post/interview-notes/","tags":["interview"],"title":"面试经历及笔记"},{"categories":[],"contents":"Introduction to TensorFlow A Quick Start\nYychi Fyu @SIST, ShanghaiTech\nOutline TF Primitives tensor graphs session variable placeholder An Example High-level overview Computation Graph: The structure of TF.\noperators as nodes tensors as links 1 2 3 4 5 6 7 import tensorflow as tf W = tf.constant([[1, 2]]) x = tf.constant([[3], [4]]) b = tf.constant(5) y = tf.matmul(W, x) + b print(y) Tensor(\u0026#34;add_5:0\u0026#34;, shape=(1, 1), dtype=int32) The above codes \u0026ldquo;describe\u0026rdquo; a computation graph: How can we actually see a tensor?\nTo evaluate a tensor, we need\nan instance of session created by tf.Session() evaluate the tensor at the target session 1 2 3 sess = tf.Session() y_value = y.eval(session=sess) print(y_value) [[16]] This is so-called lazy-evaluation!\nKeep in mind:\nTF code just \u0026ldquo;describes\u0026rdquo; computations and doesn\u0026rsquo;t actually perform it each node is a TF operator each link (edge) transports some tensors tensors tensor is most fundamental object in TF. Almost all TF operations return the reference of tensor.\nrank-0 tensor = scalar rank-1 tensor = vector rank-2 tensor = matrix Create constant tensor\n1 2 3 4 5 6 7 8 9 import tensorflow as tf import numpy as np a = tf.constant(2) # rank-0 b = tf.constant([1, 2]) # rank-1 c = tf.constant([[1], [2]]) # rank-2 print(a) print(b) print(c) Tensor(\u0026#34;Const_15:0\u0026#34;, shape=(), dtype=int32) Tensor(\u0026#34;Const_16:0\u0026#34;, shape=(2,), dtype=int32) Tensor(\u0026#34;Const_17:0\u0026#34;, shape=(2, 1), dtype=int32) Mind the shape! Interactive tensor evaluation\nCreate zeros tensor 1 2 \u0026gt;\u0026gt;\u0026gt; tf.zeros(2) \u0026lt;tf.Tensor \u0026#39;zeros:0\u0026#39; shape=(2,) dtype=float32\u0026gt; Evaluate the value of a tensor 1 2 3 4 5 # use `tf.InteractiveSession()` for eager evaluation \u0026gt;\u0026gt;\u0026gt; tf.InteractiveSession() \u0026gt;\u0026gt;\u0026gt; a = tf.zeros(2) \u0026gt;\u0026gt;\u0026gt; a.eval() array([0., 0.], dtype=float32) Tensor addition and scaling\n1 2 3 4 5 6 \u0026gt;\u0026gt;\u0026gt; a = tf.ones(shape=(2, 2)) \u0026gt;\u0026gt;\u0026gt; b = tf.fill((2, 2), 2.0) \u0026gt;\u0026gt;\u0026gt; c = a + b \u0026gt;\u0026gt;\u0026gt; c.eval() array([[3., 3.], [3., 3.]], dtype=float32) 1 2 3 4 \u0026gt;\u0026gt;\u0026gt; d = 2 * c \u0026gt;\u0026gt;\u0026gt; d.eval() array([[6., 6.], [6., 6.]], dtype=float32) 1 2 3 4 5 6 7 \u0026gt;\u0026gt;\u0026gt; e = c * d # elem-wise multiplication \u0026gt;\u0026gt;\u0026gt; f = tf.matmul(c, d) # matrix multiplication \u0026gt;\u0026gt;\u0026gt; print(e.eval(), f.eval()) [[18. 18.] [18. 18.]] [[36. 36.] [36. 36.]] Tensor data types\n1 2 3 4 5 6 \u0026gt;\u0026gt;\u0026gt; a = tf.ones(2, dtype=tf.int32) \u0026gt;\u0026gt;\u0026gt; a.eval() array([1, 1], dtype=int32) \u0026gt;\u0026gt;\u0026gt; b = tf.cast(a, dtype=tf.float32) \u0026gt;\u0026gt;\u0026gt; b.eval() array([1., 1.], dtype=float32) Tensor shapes!\n1 2 3 4 5 a = tf.range(0, 8) # shape=(8,) b = tf.expand_dims(a, 0) # shape=(1,8) c = tf.expand_dims(a, 1) # shape=(8,1) d = tf.reshape(a, shape(4, 2)) # shape=(4,2) print(a.eval(), b.eval(), c.eval(), d.eval()) [0 1 2 3 4 5 6 7] [[0 1 2 3 4 5 6 7]] [[0] [1] [2] [3] [4] [5] [6] [7]] [[0 1] [2 3] [4 5] [6 7]] Graphs Any computation in TF is represented as an instance of a tf.Graph object.\nSessions A tf.Session() object stores the context under which a computation is performed.\nexpressions are evaluated at a specific session offen use an explicit context instead of a hidden one Use an explicit context\n1 2 3 4 5 6 \u0026gt;\u0026gt;\u0026gt; sess = tf.Session() \u0026gt;\u0026gt;\u0026gt; a = tf.ones(shape=(2, 2)) \u0026gt;\u0026gt;\u0026gt; b = tf.matmul(a, a) \u0026gt;\u0026gt;\u0026gt; b.eval(session=sess) array([[2., 2.], [2., 2.]], dtype=float32) Variables Constant tensors are immutable We need some mutable tensors Here comes tf.Variable().\nCreating a variable is easy enough.\n1 2 3 \u0026gt;\u0026gt;\u0026gt; a = tf.Variable(tf.ones(shape=(2, 2))) \u0026gt;\u0026gt;\u0026gt; a \u0026lt;tf.Variable \u0026#39;Variable:0\u0026#39; shape=(2,) dtype=float64_ref\u0026gt; What about to evaluate the variable a?\n1 2 \u0026gt;\u0026gt;\u0026gt; a.eval() FailedPreconditionError: Attempting to use uninitialized value Variable_1 As we said before, TF code just describes computation, a variable should be initialized before using it.\nHow to use a variable? Just initialize it!\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; sess = tf.Session() \u0026gt;\u0026gt;\u0026gt; sess.run(tf.global_variables_initializer()) \u0026gt;\u0026gt;\u0026gt; a.eval(session=sess) array([[1., 1.], [1., 1.]], dtype=float32) A variable is mutable, and statful, we can assign a new value to it!\nAssigning values to variables\n1 2 3 4 5 6 \u0026gt;\u0026gt;\u0026gt; sess.run(a.assign(tf.zeros(shape=(2, 2)))) array([[0., 0.], [0., 0.]], dtype=float32) \u0026gt;\u0026gt;\u0026gt; a.eval(session=sess) # `a` is changed array([[0., 0.], [0., 0.]], dtype=float32) The shape should match! Placeholders Since we already have variables, why do we need placeholders?\nA placeholder is a way to input information into a TF computation graph. *\"Think of placeholders as the input nodes through which information enters TF.\"* a placeholder is used to feed outside data into the graph a variable is initialized inside the graph use feed dictionary to feed data into placeholders feed dictionary: tf.Tensor $\\mapsto$ np.ndarray 1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; a = tf.placeholder(tf.float32, shape=(1,)) \u0026gt;\u0026gt;\u0026gt; b = tf.placeholder(tf.float32, shape=(1,)) \u0026gt;\u0026gt;\u0026gt; c = a + b \u0026gt;\u0026gt;\u0026gt; c.eval(session=sess, feed_dict={a: [1.], b: [2.]}) array([3.], dtype=float32) Review tensor: basic obejct in TF graph: computation language session: computation context variable: statful and assignable placeholder: \u0026ldquo;mouth\u0026rdquo; of graph :) An Example Linear Regression\nUse synthetic toy data for the regression task. The data is generated by $$ y = wx + b + \\epsilon, $$ where $\\epsilon \\sim N(0,1)$.\nGenerate the data\n1 2 3 4 5 6 7 8 9 10 11 12 13 import tensorflow as tf import numpy as np import matplotlib.pyplot as plt np.random.seed(256) tf.set_random_seed(256) # Generating sythetic data N = 100 w_true = 5 b_true = 2 X = np.random.rand(N, 1) # shape=(100, 1) noise = np.random.normal(scale=0.1, size=(N, 1)) Y = w_true * X + b_true + noise # shape=(100, 1) Plot the data\nGenerate TF graph\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # Generate tensorflow graph with tf.name_scope(\u0026#34;placeholders\u0026#34;): x = tf.placeholder(tf.float32, shape=(N, 1)) y = tf.placeholder(tf.float32, shape=(N, 1)) with tf.name_scope(\u0026#34;weights\u0026#34;): W = tf.Variable(tf.constant(4.9)) # shape=() rank-0 b = tf.Variable(tf.constant(2.1)) # shape=() rank-0 with tf.name_scope(\u0026#34;prediction\u0026#34;): y_pred = W * x + b # shape=(100, 1) with tf.name_scope(\u0026#34;loss\u0026#34;): l = tf.reduce_sum((tf.squeeze(y - y_pred))**2) with tf.name_scope(\u0026#34;optim\u0026#34;): train_op = tf.train.AdamOptimizer(.001).minimize(l) with tf.name_scope(\u0026#34;summaries\u0026#34;): tf.summary.scalar(\u0026#34;loss\u0026#34;, l) merged = tf.summary.merge_all() train_writer = tf.summary.FileWriter(\u0026#39;/tmp/lr-train\u0026#39;, tf.get_default_graph()) Perform training\n1 2 3 4 5 6 7 8 9 10 # perform training n_steps = 1000 with tf.Session() as sess: # initialization sess.run(tf.global_variables_initializer()) for i in range(n_steps): feed_dict = {x: X, y: Y} _, summary, loss = sess.run([train_op, merged, l], feed_dict=feed_dict) print(\u0026#34;step %d, loss: %f\u0026#34; % (i, loss)) train_writer.add_summary(summary, i) Thank you!\n","permalink":"https://guyueshui.github.io/slide/tf-quick-start/","tags":[],"title":"Tf Quick Start"},{"categories":["tech"],"contents":"Declaration: this article is in long time editing\u0026hellip;\nHere comes some beautiful recursive solutions to some problems.\nExamples Some of the problems have a very nice recursive structure, we can deal with them just using one step recursion.\nFibonacci Numbers The first comes very famous Fibonacci Numbers, which is a sequence of 0, 1, 1, 2, 3, 5, 8, 13 \u0026hellip; The structure is easily captured, if we use $\\text{fib}(n)$ to denote the $n^{\\text{th}}$ Fibonacci Number (n is assumed to start from 0). $$ \\text{fib}(n) = \\begin{cases} n, \u0026amp;\\text{ if } n \\le 1 \\newline \\text{fib}(n-1) + \\text{fib}(n-2), \u0026amp;\\text{ if } n \u0026gt; 1. \\end{cases} $$ That is why we can write easily a procedure to compute the fibs. If we use MIT Scheme, we can write as follows:\n1 2 3 4 5 (define (fib n) (if (\u0026lt;= n 1) n (+ (fib (- n 1)) (fib (- n 2))))) or write a iterative version, since the above recursion has time complexity $O(2^n)$,\n1 2 3 4 5 6 (define (fib n) (define (fib-iter a b cnt) (if (= cnt 0) b (fib-iter (+ a b) a (- cnt 1)))) (fib-iter 1 0 n)) This is called tail recursion in Lisp, it generates a iterative process so that in scheme it is iterative, though in many other languages it may belong to recursive thing. Following is a cpp version.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // iterative fibonacci class Solution { public: int fib(int n) { return fib_iter(1, 0, n); } // tail recursion \u0026lt;=\u0026gt; iteration // please refer to SICP Ch. 1.2.2 int fib_iter(int a, int b, int cnt) { if (cnt == 0) return b; else return fib_iter(a+b, a, cnt-1); } }; Jump Floor A frog is jumping, it can either jump one or two steps at a time. How many ways can he jump from 0 to nth floor? For instance, from 0 to 3, there are 3 ways:\n1,2 (first one, then it jumps two steps and reach 3) 2,1 (first two, then it jumps one step and reach 3) 1,1,1 Make sure you catch the problem. It will indeed reduce to Fibonacci Numbers. Let\u0026rsquo;s see. Always try the First-Case Analysis. Denote $\\text{opt}(n)$ to be the number of ways it jumps from 0 to $n^{\\text{th}}$ floor. If we condition on the first step, then\nit jumps 1 at first time, then the remaining is the number of ways to jump from 0 to $(n-1)^{\\text{th}}$ floor, which is $\\text{opt}(n-1)$. it jumps 2 at first time, then the remaining $\\text{opt}(n-2)$. Hence we get the following: $$ \\text{opt}(n) = \\begin{cases} n, \u0026amp; n \\le 2 \\newline \\text{opt}(n-1) + \\text{opt}(n-2), \u0026amp; n \u0026gt; 2. \\end{cases} $$ Note that the above is a Fibonacci sequence except the index shifting.\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 class Solution { public: // top-down fashion, recursive and O(2^n) int jumpFloor(int n) { if (n == 1) return 1; if (n == 2) return 2; return jumpFloor(n-1) + jumpFloor(n-2); } // iterative O(n) int opt(int n) { if (n \u0026lt;= 2) return n; // else int a = 1, b = 2; /* apply the transform n-2 times * then `b` is fib(n) * a = b * b = a + b */ for (; n \u0026gt; 2; --n) { b = a + b; a = b - a; // `a` is the previous `b` } return b; } }; Linked-List Reverse You are given a linked list, which is defined by a cxx struct or class. Can you output the reversed linked list?\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 // Problem: Reverse a linked list. // A linked list can be reversed either iteratively // or recursively. // Could you implement both? class ListNode { public: int val; ListNode * next; ListNode(int x) : val(x), next(nullptr) {} }; class Solution { public: // iterative ListNode* reverseList(ListNode* head) { // base case if (!head || head -\u0026gt; next == nullptr) return head; auto origin_head = head; auto p = head -\u0026gt; next; while (p) { // a copy for next iteration auto newp = p -\u0026gt; next; p -\u0026gt; next = head; // relink head = p; // update head p = newp; // ready for next iteration } // set origin head -\u0026gt; next point to null origin_head -\u0026gt; next = nullptr; return head; } // recursive ListNode* ReverseList(ListNode* head) { if (!head || head -\u0026gt; next == nullptr) return head; // suppose it can already do the job auto p = ReverseList(head -\u0026gt; next); auto tmp = p; // move tmp until reaching the end while (tmp -\u0026gt; next) { tmp = tmp -\u0026gt; next; } tmp -\u0026gt; next = head; // relink head -\u0026gt; next = nullptr; return p; } }; Reverse Print You are given a linked list, can you print it\u0026rsquo;s elements from end to begining?\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 // Problem: print from the end to begining of // a given linked list. /* * 1. use stack * 2. use recursion */ #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;stack\u0026gt; using namespace std; class ListNode { public: int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; class Solution { vector\u0026lt;int\u0026gt; printListFromTailToHead(ListNode* head) { vector\u0026lt;int\u0026gt; ret; // boundary case if (head == nullptr) return ret; stack\u0026lt;int\u0026gt; s; for (auto p = head; p != nullptr; p = p -\u0026gt; next) { s.push(p -\u0026gt; val); } while (!s.empty()) { ret.push_back(s.top()); s.pop(); } return ret; } // recursive needs a member variable // use recursion stack, tricky vector\u0026lt;int\u0026gt; arr; vector\u0026lt;int\u0026gt; reversePrint(ListNode* head) { if (head) { reversePrint(head -\u0026gt; next); arr.push_back(head -\u0026gt; val); } return arr; } /* * Consider the closure, at one recursive step, * what I should do? Let\u0026#39;s drop all the details, * just look one recursive step. * What had I done? * Oh gee, I see if the head is not null, * I must push the value to the vector, * but before this, I should take a look at * `head -\u0026gt; next`, since I have to push * the tail first. So which one can help me * do this? Yes, the function itself! Then * after I have addressed the tail, now I\u0026#39;m * going to push current value to the vector. * That\u0026#39;s all I need! * The key is you work in one recursive step, and * form a closure for the next, and do not forget * the base case (stopping rules). That how * recursion runs! And you are free of those * confusing details. */ }; Conclusions What can we do use recursions? How shall we consider when wrting recursions? The most important thing, I think, is closure. You must have the experience that some day you were working with a recursive procedure, getting stuck in the conditions and inputs/outputs of the recursion, you came up with a mess when trying to understanding or simulating the process in your brain, and finally, you even did not know why it works, what happened inside it, how did it reach the boundary cases, etc.\nThe key is not to consider! Stop digging your pit and bury yourself. Leave all the details behind, all your focus is just one recursion step. You deal with each recursion step like a blackbox, with its inputs and outputs. Each time, you should form a closure. Specifically, you must formulate your outputs of the current recursion step so that it can fit to next recursion step as a input. And you are focusing to find the common pattern that each recursion step should do. That depends on situations, but you should have the ability to extract some common pattterns in some problems.\nDo not forget your base cases (or boundary cases, stopping rules). That\u0026rsquo;s the export of recursion. Once you have done in recursion steps, the next thing to consider is the base cases. Where can the procedure exit, how many cases will it reach. Usually these base cases are very hard to find, and can be very confusing. Be careful to deal with them.\nOnce you had completed the above, you are almost done! Now you are free of those confusing details. The key is sometimes you know what you have no need to consider.\n","permalink":"https://guyueshui.github.io/post/the-beauty-of-recursion/","tags":["recursion","programming thought"],"title":"The Beauty of Recursion"},{"categories":["Notes"],"contents":"In editing\u0026hellip;\n必备知识 首先要对原码、反码、补码有一定理解,推荐阅读此文:https://www.cnblogs.com/zhangziqiu/archive/2011/03/30/computercode.html\n摘要:按 1 byte 来算,1 的机器数为 0000 0001,原码、反码、补码均为 0000 0001. -1 的机器数为 1000 0001,原码为机器数、反码为 1111 1110,补码为 1111 1111. 总结来说:最高位用作符号位,0 表示正数,1 表示负数。正数的原、反、补码一样。负数的反码为除符号位外按位取反,补码为反码加 1.\n为什么 C 系语言中,int 的范围为$-2^{n-1} \\sim 2^{n-1} - 1$,其中 n 为 bit 数。同样以 1 byte 来算,8bit 能够表达的有符号范围如下:\n1111 1111 # -(2^7 - 1) = -127 0111 1111 # (2^7 - 1) = 127 但从排列组合来看,8 位能够表达的数字共有$2^8 = 256$个,而$-127 \\sim 127$只有 255 个数。还有一个数跑哪去了呢。答案是 0,0 有两种表示方法:\n1000 0000 # -0 0000 0000 # +0 这无疑是一种浪费,我们选择下面那个数表示 0。而将上面的 1000 0000(首先它表示一个负数,负数用补码表示,因此它应该是代表 -128 的补码)用于表示 -128,如此一来,8bit 的表达范围扩展到$-128 \\sim 127$,正好$256=2^8$个数。\n至于为什么用 1000 0000 来表示 -128,此中还是有些门道,来看 -127 的三码:\n1111 1111 # -127 原码 1000 0000 # -127 反码 1000 0001 # -127 补码 如此一来 -128 的补码就等于 -127 的补码减一得:1000 0000. 关于负数为什么用补码表示,其实是为了做加法,计算机内没有减法器,只有加法器,具体参见上文。以上我只是用 8bit 数举例,实际计算机中 int 可能有 32bit,以此类推。\n以下部分转载自:http://www.cnblogs.com/junsky/archive/2009/08/06/1540727.html\n假设有一个 int 类型的数,值为 5,那么,我们知道它在计算机中表示为:\n00000000 00000000 00000000 00000101\n5 转换成二制是 101,不过 int 类型的数占用 4 字节(32 位),所以前面填了一堆 0。\n1 byte = 8 bits\n现在想知道,-5 在计算机中如何表示?**在计算机中,负数以其正值的补码形式表达。**什么叫补码呢?这得从原码,反码说起。\n原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。 比如 00000000 00000000 00000000 00000101 是 5 的原码。 反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。 取反操作指:原为 1,得 0;原为 0,得 1。(1 变 0; 0 变 1) 比如:将 00000000 00000000 00000000 00000101 每一位取反,得 11111111 11111111 11111111 11111010。 称: 11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反码。 反码是相互的,所以也可称: 11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互为反码。 补码:反码加 1 称为补码。 也就是说,要得到一个数的补码,先得到反码,然后将反码加上 1,所得数称为补码。 比如: 00000000 00000000 00000000 00000101 的反码是: 11111111 11111111 11111111 11111010。 那么,补码为: 11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011 所以,-5 在计算机中表达为: 11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。\n再举一例,我们来看整数 -1 在计算机中如何表示。假设这也是一个 int 类型,那么:\n先取 1 的原码: 00000000 00000000 00000000 00000001 得反码: 11111111 11111111 11111111 11111110 得补码: 11111111 11111111 11111111 11111111 可见,-1 在计算机里用二进制表达就是全 1。16 进制为:0xFFFFFF.\n转载部分结束,感谢原作者。为了便于阅读,我对排版稍作了改动。\n常见的位运算 C/C++ 位运算符\n~:按位取反 \u0026lt;\u0026lt;:左移位 \u0026gt;\u0026gt;:右移位 \u0026amp;:按位与 |:按位或 ^:按位异或 Summary from learncpp.com Cf. https://www.learncpp.com/cpp-tutorial/bit-manipulation-with-bitwise-operators-and-bit-masks/\nSummarizing how to set, clear, toggle, and query bit flags:\nTo query bit states, we use bitwise AND:\n1 if (flags \u0026amp; option4) ... // if option4 is set, do something To set bits (turn on), we use bitwise OR:\n1 2 flags |= option4; // turn option 4 on. flags |= (option4 | option5); // turn options 4 and 5 on. To clear bits (turn off), we use bitwise AND with bitwise NOT:\n1 2 flags \u0026amp;= ~option4; // turn option 4 off flags \u0026amp;= ~(option4 | option5); // turn options 4 and 5 off To flip bit states, we use bitwise XOR:\n1 2 flags ^= option4; // flip option4 from on to off, or vice versa flags ^= (option4 | option5); // flip options 4 and 5 Applications 除以/乘以 2 n \u0026gt;\u0026gt; 1: 右移一位,低位的 1 被忽略,floor(n/2). n \u0026lt;\u0026lt; 1: 左移一位,低位补 0,n * 2.\n取相反数 ~n + 1:对 n 取反得到反码,加 1 得到补码,即对应的负数。 (n ^ -1) + 1:-1 的二进制全是 1,按位异或之后得到的等价与按位取反。\n计算 n+1 -(~n):\n~n + 1 = -n -(~n + 1) = n -~n - 1 = n -~n = n+1 计算 n-1 ~-n:\nn-1 = -(-n+1) -n+1 = -(~-n) \u0026lt;--- n+1 = -~n n-1 = ~-n if(x==a) x=b; if(x==b) x=a; x = a ^ b ^ x:如果 x=a,则相当于对 b 翻转两次,结果自然是 b;如果 x=b,那么 b^b=0,0 异或任何数都没有效果,故结果是 a.\n判断奇偶 n \u0026amp; 1: 如果结果为 1,则表明 n 的最低位为 1,n 为奇数;反之为偶数。\n变量互换(无需临时变量) a ^= b ^= a ^= b:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // the expression expands two: 1). b ^= a ^= b; // now final_b = b ^ (a^b) = a, temp_a = (a^b) 2). a ^= b; // now final_a = temp_a ^ final_b = (a^b) ^ a = b // as you know, xor twice does nothing! // ---------------------------------------------// // if you doesn\u0026#39;t understand, read the following: // read from right to left // now we are reading `a ^= b` a ^= b --\u0026gt; = a ^ b // tag this tmp_a = a ^ b // we are reading `b ^= a ^= b` b ^= a --\u0026gt; = b ^ (a ^ b) = a // we are reading `a ^= b ^= a ^= b` a ^= b --\u0026gt; = tmp_a ^ b = (a ^ b) ^ a = b // at last b = a a = b 判断是否为 2 的幂 n \u0026amp; (n-1):如果 n 是 2 的幂,则\n1 2 3 4 n = 0b100000... n-1 = 0b011111... // now n \u0026amp; (n-1) = 0, we can judge if it is 0 // to determine wether n is a power of 2. 对 2 的 n 次方取余 m \u0026amp; ((1\u0026lt;\u0026lt;n) - 1):\n1 2 3 (1\u0026lt;\u0026lt;n) = 0b10000... // is the nth power of 2 (1\u0026lt;\u0026lt;n) - 1 = 0b01111... m \u0026amp; ((1\u0026lt;\u0026lt;n) - 1) // keep the bits lower than n 取 n 的第 m 低位 (n \u0026gt;\u0026gt; (m-1)) \u0026amp; 1: 右移 m-1 位,最低位即为原从低到高第 m 位\n将 n 的第 m 低位置 1 n | (1 \u0026lt;\u0026lt; (m-1)): 将 1 左移 m-1 位于 n 的第 m 低位对应,相或置 1\n将 n 的第 m 低位置 0 n \u0026amp; ~(1 \u0026lt;\u0026lt; (m-1)): 将 1 左移 m-1 位对上 n 的第 m 低位,按位取反相与\nReferences 负数的二进制表示方法 优秀程序员不得不知道的 20 个位运算技巧 ","permalink":"https://guyueshui.github.io/post/%E7%A5%9E%E5%A5%87%E7%9A%84%E4%BD%8D%E8%BF%90%E7%AE%97/","tags":["位运算","trick","cpp"],"title":"神奇的位运算"},{"categories":["Notes"],"contents":"场景题 题一:最高得分 一个长度为$N$的序列,玩家每次只能从头部或尾部拿数字,不能从中间拿。拿走的数字依次从左到右排列在自己面前。拿完$N$个数字之后,游戏结束。此时$N$个数字在玩家面前组成一个新的排列,这个数列每相邻两个数字之差的绝对值之和为玩家最终得分。假设玩家前面的$N$个数字从左到右标号为 $n_1,n_2, \\dots, n_N$,则最终得分$S$的计算方式如下: $$ S = \\text{abs}(n_1-n_2) + \\text{abs}(n_2-n_3) + \\cdots + \\text{abs}(n_{N-1} - n_N). $$\n请计算玩家在以上游戏规则中把所有数字拿完可以获得的最大得分。\n思路:拿到这个问题,感觉像是动态规划。得想办法把子问题拆出来。但是一般情况下,并不是这么好拆的。所以从最简单的用例开始。假设只有两个数,这很简单,无论怎么拿,得分都一样。再加一个数呢?你就要考虑从哪里先拿,然后再从哪里先拿的问题。假设现在有三个数,你从前端拿了一个数,则下一步你得考虑从哪里拿的增益比较大。从前端拿得到一个 front_gain,从末端拿得到一个 back_gain. 你得比一比哪个会让你的得分最大化。然后下一步继续这样考虑,这就形成了一个递归步。就是说你已经拆出来了!专业店说就是你找除了递推关系式。 题外话:Case analysis is more powerful than you thought!\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 #include\u0026lt;iostream\u0026gt; #include\u0026lt;vector\u0026gt; #include\u0026lt;cmath\u0026gt; using namespace std; /** * @breif Calculate the max score of the given array. * @param arr The origin array. * @param beg The begining of the range. * @param end The end of the range. * @param isFront Is the last taken from the front or not. * @return The max score in a paticular setting. * * Note that the @c arr is the origin array, and [beg, end) * range is considered from the second step, with @c isFront. * For example: * Suppose @c arr.size() = 5 * If @c isFront = true, means the last taken is from the front, * then [beg, end) = [1, 5). * If @c isFront = false, means the last taken is from the back, * then [beg, end) = [0, 4). */ int opt(const vector\u0026lt;int\u0026gt;\u0026amp; arr, int beg, int end, bool isFront) { // base cases if (beg \u0026gt;= end) return 0; if (end - beg == 1) { if (isFront) return abs(arr[beg] - arr[beg-1]); else return abs(arr[beg] - arr[end]); } // ELSE int front_gain = 0; // the gain if take front at current step int back_gain = 0; // the gain if take back at current step if (isFront) { front_gain = abs(arr[beg] - arr[beg-1]); back_gain = abs(arr[end-1] - arr[beg-1]); } else { front_gain = abs(arr[beg] - arr[end]); back_gain = abs(arr[end-1] - arr[end]); } return max( front_gain + opt(arr, beg+1, end, true), back_gain + opt(arr, beg, end-1, false)); } int main() { int N = 0; cin \u0026gt;\u0026gt; N; vector\u0026lt;int\u0026gt; arr; for (int i=0; i!=N; ++i) { int tmp; cin \u0026gt;\u0026gt; tmp; arr.push_back(tmp); } int maxwin = max( opt(arr, 1, arr.size(), true), opt(arr, 0, arr.size()-1, false)); cout \u0026lt;\u0026lt; maxwin; return 0; } 题二:最少硬币 2019 腾讯实习生笔试题\n小 Q 去商场购物,经常会遇到找零钱的问题。小 Q 现在手上有$n$种不同面值的硬币,每种面值的硬币有无限多个。为了方便购物,小 Q 希望带尽量少的硬币,并且要能组合出$1$到$m$之间(包含$1$和$m$)的所有面值。\n输入描述: 第一行包含两个整数$m, n ~(1 \\le n \\le 100, ~1 \\le m \\le 10^9)$,含义如题所述。 接下来$n$行,每行一个整数,每$i+1$行的整数表示第$i$种硬币的面值。\n输出描述: 输出一个整数,表示最少需要携带的硬币数量。如果无解,则输出$-1$.\n示例输入:\n20 4 1 2 5 10 示例输出:\n5 思路:一拿到就让人联想到动态规划。但其实不是,因为它的条件是要构成所有 1 到 m 之间的面值。所以就得从 1 到 m 慢慢凑出来。假设当前选择的硬币已经可以构成 [1, i], 那么我当然下次在选面值的之后,会很理想的去寻找一个面值为 i+1 的硬币。这样我可以使我构造的最大面值尽可能的大。如果这个面值大于 m 了,那我们就完事儿了。这是贪心的思想。具体来说,比方已选的硬币可以构造出 1 到 5 之间的任何面值,那我在选择一个面值为 6 的硬币,就可以构造出 1 到 11 之间的任何面值。所以我们从面值为 1 的开始加,记录当前可以构造的最大面值 max_constructed,在选择下一个硬币的时候,优先选择面值\u0026lt;=max_constructed+1 的硬币。 参考:https://blog.csdn.net/MOU_IT/article/details/89057036\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; int MinimalCoin(const vector\u0026lt;int\u0026gt;\u0026amp; coins, int amount) { if (coins.empty()) throw std::invalid_argument(\u0026#34;empty coins\u0026#34;); if (coins[0] != 1) return -1; int max_constructed = 0; int cnt = 0; do { for (int n = coins.size() - 1; n \u0026gt;= 0; --n) { if (coins[n] \u0026lt;= max_constructed + 1) { max_constructed += coins[n]; ++cnt; break; } } } while (max_constructed \u0026lt; amount); return cnt; } int main() { // address input int n, m; cin \u0026gt;\u0026gt; m \u0026gt;\u0026gt; n; int val; vector\u0026lt;int\u0026gt; coins; for (int i = 0; i != n; ++i) { cin \u0026gt;\u0026gt; val; coins.push_back(val); } std::sort(coins.begin(), coins.end()); cout \u0026lt;\u0026lt; MinimalCoin(coins, m); return 0; } 题三:进制转换 2019 哈罗单车实习生笔试题\n将一个 10 进制整数转换成 36 进制的数,可以用 0-9A-Z 表示 0-35.\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 /** * Hellobike 2019 interview. */ #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; using namespace std; class Solution { public: string itob36(int n) { int r = 0; // remainder of n mod 36 string tmp; // iteratively divde by 36 to get each digit while (n) { r = n % 36; tmp += MAP[r]; n /= 36; } // reverse tmp to ret string ret; cout \u0026lt;\u0026lt; \u0026#34;tmp is \u0026#34; \u0026lt;\u0026lt; tmp \u0026lt;\u0026lt; endl; for (auto iter = tmp.rbegin(); iter != tmp.rend(); ++iter) ret.push_back(*iter); return ret; } private: string MAP[36] = {\u0026#34;0\u0026#34;, \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;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;, \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;, \\ \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;}; }; // test int main() { int number = 12345; cout \u0026lt;\u0026lt; Solution().itob36(number); return 0; } 题四:舞会 链接:https://www.nowcoder.com/questionTerminal/9efe02ab547d4a9995fc87a746d7eaec 来源:牛客网\n今天,在冬木市举行了一场盛大的舞会。参加舞会的有 n 位男士,从 1 到 n 编号;有 m 位女士,从 1 到 m 编号。对于每一位男士,他们心中都有各自心仪的一些女士,在这次舞会中,他们希望能与每一位自己心仪的女士跳一次舞。同样的,对于每一位女士,她们心中也有各自心仪的一些男士,她们也希望能与每一位自己心仪的男士跳一次舞。在舞会中,对于每一首舞曲,你可以选择一些男士和女士出来跳舞。但是显然的,一首舞曲中一位男士只能和一位女士跳舞,一位女士也只能和一位男士跳舞。由于舞会的时间有限,现在你想知道你最少需要准备多少首舞曲,才能使所有人的心愿都得到满足?\ninput: 第一行包含两个整数 n,m,表示男士和女士的人数。1≤n,m≤ 1000 接下来 n 行, 第 i 行表示编号为 i 的男士有 ki 个心仪的女生 然后包含 ki 个不同的整数分别表示他心仪的女士的编号。 接下来 m 行,以相同的格式描述每一位女士心仪的男士。\noutput: 一个整数,表示最少需要准备的舞曲数目。\n示例输入:\n3 3 2 1 2 2 1 3 2 2 3 1 1 2 1 3 2 2 3 示例输出:\n2 说明:\n对于样例 2,我们只需要两首舞曲,第一首舞曲安排(1,1),(2,3),(3,2);第二首舞曲安排(1,2),(2,1),(3,3)。 思路:乍一看好象很难,不知道怎么处理。其实只要仔细看清楚题目要求:要求每个刃都能得到满足!这很重要,即便还有一个我喜欢的人没跟我跳,就得再放一首歌让我跳!了解到这个需求之后,问题就变的简单了,只要统计出每个人心仪人的数量,以及被心仪的数量,对所有人求一个最大值就行了。 参考:https://www.nowcoder.com/questionTerminal/9efe02ab547d4a9995fc87a746d7eaec\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; using std::vector; int main() { // read input and built heartbeat matrix int num_men, num_women, num_likes, val; std::cin \u0026gt;\u0026gt; num_men \u0026gt;\u0026gt; num_women; int total_num = num_men + num_women; // the heartbeat matrix vector\u0026lt;vector\u0026lt;int\u0026gt; \u0026gt; mat(total_num, vector\u0026lt;int\u0026gt;(total_num, 0)); for (int i = 0; i != total_num; ++i) { std::cin \u0026gt;\u0026gt; num_likes; while (num_likes--) { std::cin \u0026gt;\u0026gt; val; if (i \u0026lt; num_men) // man to woman mat[i][val+num_men-1] = 1; else // woman to man mat[i][val-1] = 1; } } // done // count for each persons hearbeats int songs_needed = 0; for (int i = 0; i != total_num; ++i) { int cnt = 0; if (i \u0026lt; num_men) // counting for each man { for (int j = num_men; j != total_num; ++j) { if (mat[i][j] == 1) // man i like woman j ++cnt; else if (mat[j][i] == 1) // man i is liked by woman j ++cnt; } } else { for (int j = 0; j != num_men; ++j) { if (mat[i][j] == 1) ++cnt; else if (mat[j][i] == 1) ++cnt; } } if (songs_needed \u0026lt; cnt) songs_needed = cnt; // update songs if needed } std::cout \u0026lt;\u0026lt; songs_needed; return 0; } 题五:输出数组的全排列 给定一个数组,求其全排列。\nIdea: pick one element as prefix, then add to the permutations of the rest n-1 elems. Reference: https://www.cnblogs.com/ranjiewen/p/8059336.html\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;iterator\u0026gt; #include \u0026lt;algorithm\u0026gt; void Permute(int arr[], int beg, int end) { if (beg == end) { std::copy(arr, arr + end, std::ostream_iterator\u0026lt;int\u0026gt;(std::cout)); std::cout \u0026lt;\u0026lt; std::endl; } for (int i = beg; i != end; ++i) { std::swap(arr[i], arr[beg]); Permute(arr, beg + 1, end); std::swap(arr[i], arr[beg]); } } int main() { int arr[] = {0,1,2,3,4,5,6,7,8,9}; Permute(arr, 0, 5); return 0; } 题六:斜线填充 给定 n x m 的矩阵,按照从右上往左下的斜线填充 1 到 n*m 的值。 例如,对于一个 3x3 的矩阵,\n1 2 4 3 5 7 6 8 9 (3x3) 1 2 4 7 3 5 8 10 6 9 11 12 (3x4) 思路:干就完了。先填充左上角的三角区域,再填充右下角的三角区域。注意边界条件,无论行或列到达边界,记得跳转。\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; using namespace std; class Solution { public: Solution(int n, int m) { // fill with 0 for (int i = 0; i != n; ++i) mat.push_back(vector\u0026lt;int\u0026gt;(m, 0)); } void skewFill(int n, int m) { int cnt = 1; // fill the up-left triangle for (auto col = 0; col != m; ++col) { for (auto i = 0, j = col; i != n \u0026amp;\u0026amp; j \u0026gt;= 0; ++i) mat[i][j--] = cnt++; } // fill the bottom-right triangle for (auto row = 1; row != n; ++row) { for (auto j = m-1, i = row; i != n \u0026amp;\u0026amp; j \u0026gt;= 0; ++i) mat[i][j--] = cnt++; } } /// print the matrix void printer() { for (auto \u0026amp;row : mat) { for (auto \u0026amp;col : row) printf(\u0026#34;%3d \u0026#34;, col); cout \u0026lt;\u0026lt; endl; } } private: vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; mat; }; // test int main() { int n = 0, m = 0; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; Solution s(n, m); s.skewFill(n, m); s.printer(); return 0; } 题七:螺旋矩阵 按顺时针填充螺旋填充矩阵。例如:\n9 8 7 2 1 6 3 4 5 (3x3) 12 11 10 9 3 2 1 8 4 5 6 7 (3x4) Idea: Imagine there are for borders that surround the matrix. We walk through the mat, and once achieve a border, we change the direction. Each time we finished a circle, we will shrink our border, and start next circle. After we fill (row * col) elems in the matrix, we are done!\nNote: this solution is the most elegant in my opinion, for it\u0026rsquo;s simple boundray case.\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; using std::vector; class Solution { public: Solution(int rows, int cols) : mat_(rows, vector\u0026lt;int\u0026gt;(cols, 0)) { } void BuildMat() { // 4 border int top = 0; int bot = mat_.size() - 1; int left = 0; int right = mat_[0].size() - 1; int N = (bot + 1) * (right + 1); for (int i = 0, j = 0; N != 0; --N) { // make sure fill exactly one element at each loop mat_[i][j] = N; if (i == top) { if (j \u0026lt; right) ++j; // go right else if (j == right) ++i; // checkpoint } else if (j == right) { if (i \u0026lt; bot) ++i; // go down else if (i == bot) --j; } else if (i == bot) { if (j \u0026gt; left) --j; // go left else if (j == left) --i; } else if (j == left) { if (i \u0026gt; top + 1) --i; // go up else if (i == top + 1) { ++j; // shrink borders ++top, --bot, ++left, --right; } } else { throw std::runtime_error(\u0026#34;not behaved expectedly\u0026#34;); } } } void Print() { for (auto \u0026amp;r : mat_) { for (auto \u0026amp;c : r) printf(\u0026#34;%2d \u0026#34;, c); std::cout \u0026lt;\u0026lt; std::endl; } } private: vector\u0026lt;vector\u0026lt;int\u0026gt; \u0026gt; mat_; }; // test int main() { Solution so(3, 4); so.BuildMat(); so.Print(); return 0; } 题八:点击窗口的索引 网易雷火游戏 2019 校招\n本题需要让你模拟一下在 Windows 系统里窗口和鼠标的点击操作,具体如下:\n屏幕分辨率为 3840x2160,左上角坐标为 (0, 0),右下角坐标为 (3839, 2159). 窗口是一个矩形的形状,由左上角坐标 (x, y), 和宽高 (w, h) 四给数字来定位。左上角坐标为 (x, y), 右下角坐标为 (x+w, y+h). 其中左上角坐标一定会在屏幕范围内,其他一些部分可能会超过屏幕范围。 窗口的点击和遮挡规则同 Windows,但是不考虑关闭窗口、最大化、最小化和强制置顶的情况。即, 如果发生重叠,后面打开的窗口会显示在前面打开的窗口上面 当鼠标发生一次点击的时候,需要判断点击到了哪个窗口,如果同个坐标有多个窗口,算点击到最上层的那个 当一个窗口被点击的时候,会浮动到最上层 输入描述: 每个测试输入包含 1 个测试用例。第一行为 2 个整数 N,M。其中 N 表示打开窗口的数目,M 表示鼠标点击的数目,其中$0\u0026lt;N,M \u0026lt; 1000$. 接下来 N 行,每一行四个整数$x_i, y_i, w_i, h_i$, 分别表示第 i 个窗口(窗口 id 为 i,从 1 开始计数)的左上角坐标以及宽高,初始时窗口是按输入的顺序依次打开。其中$0 \\le x_i \u0026lt; 3840$, $0 \\le y_i \u0026lt; 2160$, $0 \u0026lt; w_i \u0026lt; 3840$, $0 \u0026lt; h_i \u0026lt; 2160$. 再接下来有 M 行,每一行两个整数$x_j, y_j$, 分别表示接下来发生的鼠标点击坐标。其中$0 \\le x_j \u0026lt; 3840, ~0 \\le y_j \u0026lt; 2160$\n输出描述: 对于每次鼠标点击,输出本次点击到的窗口 id。如果没有点击到窗口,输出 -1.\n示例输入:\n2 4 100 100 100 100 10 10 150 150 105 105 180 180 105 105 1 1 示例输出:\n2 1 1 -1 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 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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;vector\u0026gt; using namespace std; // global const enum { WIDTH = 3840, HEIGHT = 2160 }; // abstraction for 2D point struct Pos { int x; int y; Pos(int xx, int yy): x(xx), y(yy) { } }; // abstraction for window struct Window { int id; Pos point; int width; int height; Window(int idx, int x, int y, int w, int h) : id(idx), point(x, y), width(w), height(h) { } }; /// is the point @c p in the window @c w bool IsInWindow(const Window\u0026amp; w, const Pos\u0026amp; p) { int left = w.point.x; int right = (left + w.width \u0026lt; WIDTH) ? (left + w.width) : WIDTH-1; int top = w.point.y; int bot = (top + w.height \u0026lt; HEIGHT) ? (top + w.height) : HEIGHT-1; if (p.x \u0026gt;= left \u0026amp;\u0026amp; p.x \u0026lt;= right) { if (p.y \u0026gt;= top \u0026amp;\u0026amp; p.y \u0026lt;= bot) return true; } return false; } /// which window do i click on int ClickWhich(vector\u0026lt;Window\u0026gt;\u0026amp; windows, const Pos\u0026amp; click) { // 窗口叠放次序,从上层到下层 for (int i = windows.size() - 1; i \u0026gt;= 0; --i) { if (IsInWindow(windows[i], click)) { int ret = windows[i].id + 1; windows.push_back(windows[i]); windows.erase(windows.begin() + i); return ret; } } return -1; } int main() { int num_open_windows, num_clicks; cin \u0026gt;\u0026gt; num_open_windows \u0026gt;\u0026gt; num_clicks; // read windows vector\u0026lt;Window\u0026gt; windows; for (int i = 0, x,y,w,h; i != num_open_windows; ++i) { cin \u0026gt;\u0026gt; x \u0026gt;\u0026gt; y \u0026gt;\u0026gt; w \u0026gt;\u0026gt; h; windows.emplace_back(i, x, y, w, h); } // read clicks vector\u0026lt;Pos\u0026gt; clicks; for (int i = 0, x,y; i != num_clicks; ++i) { cin \u0026gt;\u0026gt; x \u0026gt;\u0026gt; y; clicks.emplace_back(x, y); } // io done for (auto \u0026amp;e : clicks) { cout \u0026lt;\u0026lt; ClickWhich(windows, e) \u0026lt;\u0026lt; endl; } return 0; } 题九:Stern-Brocot Tree 网易雷火游戏 2019 校招\nThe Stern-Brocot tree is an infinite complete binary tree in which the verices correspond one-for-one to the positive rational numbers, whose values are ordered from the left to the right as in a search tree.\nFigure above shows a part of the Stern-Brocot tree, which has the first 4 rows. The value in the node is the mediant of the left and right fractions. The mediant of two fractions A/B and C/D is defined as (A+C)/(B+D).\nTo construct the Stern-Brocot tree, we first define the left fraction of the root node is 0/1, and the right fraction of the root node is 1/0. So the value in the root node is the mediant of 0/1 and 1/0, which is (0+1)/(1+0)=1/1. Then the value of root node becomes the right fraction of the left child, and the left fraction of the right child. For example, the 1st node in row2 has 0/1 as its left fraction and 1/1(which is the value of its parent node) as its right fraction. So the value of the 1st node on row2 is (0+1)(1+1)=1/2. For the same reason, the value of the 2nd node in row2 is (1+1)/(1+0)=2/1. This construction progress goes on infinity. As a result, every positive rational number can be found on the Stern-Brocot tree, and can be found only once.\nGive a rational nunmber in form P/Q, find the position of P/Q in the Stern-Borcot tree.\nInput description: Input consists of two integers, P and Q (1\u0026lt;=P,Q \u0026lt;= 1000), which represent the rational number P/Q. We promise P and Q are relatively prime.\nOutput description: Output consists of two integers, R and C. R indicates the row index of P/Q in the Stern-Brocot tree, C indicates the index of P/Q in that row. Both R and C are base 1. We promise the position of P/Q is always in the first 12 rows of the Stern-Brocot tree, which means R\u0026lt;=12.\nSample input:\n5 3 Sample output:\n4 6 Idea: tree structure is natural for recursion.\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;utility\u0026gt; using namespace std; // build Rat abstraction typedef std::pair\u0026lt;int, int\u0026gt; Rat; // the rational numbers Rat operator+(const Rat\u0026amp; lhs, const Rat\u0026amp; rhs) { // 事实上这里需要化简使得分子分母互素 // 不过下面我判断等于的时候是交叉相乘判断的,故不影响结果 return std::make_pair( lhs.first + rhs.first, lhs.second + rhs.second); } bool operator\u0026lt;(const Rat\u0026amp; lhs, const Rat\u0026amp; rhs) { return (lhs.first * rhs.second) \u0026lt; (rhs.first * lhs.second); } bool operator\u0026gt;(const Rat\u0026amp; lhs, const Rat\u0026amp; rhs) { return !(lhs \u0026lt; rhs); } bool operator==(const Rat\u0026amp; lhs, const Rat\u0026amp; rhs) { return (lhs.first * rhs.second) == (rhs.first * lhs.second); } void Printer(const Rat\u0026amp; r) { cout \u0026lt;\u0026lt; r.first \u0026lt;\u0026lt; \u0026#34;/\u0026#34; \u0026lt;\u0026lt; r.second \u0026lt;\u0026lt; endl; } // done struct SBTreeNode { Rat LF; // left fraction Rat val; Rat RF; // right fraction SBTreeNode(Rat l, Rat v, Rat r) : LF(l), val(v), RF(r) { } }; /// search the SBTree for target, return the row and col index void SearchOnTree( SBTreeNode\u0026amp; root, const Rat\u0026amp; target, int* prow, int* pcol) { // base case if (target == root.val) return; // 可以看到 Stern-Brocot 树具有二叉排序树的特征 // left-child \u0026lt; parent \u0026lt; right-child if (target \u0026lt; root.val) { // go left root.RF = root.val; root.val = root.val + root.LF; ++(*prow); *pcol = (*pcol) * 2 - 1; SearchOnTree(root, target, prow, pcol); } else if (target \u0026gt; root.val) { // go right root.LF = root.val; root.val = root.val + root.RF; ++(*prow); (*pcol) *= 2; // 注意列索引的增量,仔细看规律 SearchOnTree(root, target, prow, pcol); } } int main() { Rat query; cin \u0026gt;\u0026gt; query.first \u0026gt;\u0026gt; query.second; SBTreeNode root(Rat(0,1), Rat(1,1), Rat(1,0)); // root.RF = root.val; // root.val = root.val + root.LF; // cout \u0026lt;\u0026lt; \u0026#34;LF \u0026#34;; Printer(root.LF); // cout \u0026lt;\u0026lt; \u0026#34;val \u0026#34;; Printer(root.val); // cout \u0026lt;\u0026lt; \u0026#34;RF \u0026#34;; Printer(root.RF); int row = 1, col = 1; SearchOnTree(root, query, \u0026amp;row, \u0026amp;col); cout \u0026lt;\u0026lt; row \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; col \u0026lt;\u0026lt; endl; return 0; } 题十:解码字符串 腾讯 2019 校招\n小 Q 想要给他的朋友发送一个神秘的字符串,但是他发现字符串过长了,于是小 Q 发明了一种压缩算法对字符串中重复的部分进行了压缩,对于字符串中连续的 m 个相同的字符串 S 将会压缩为 [m|S](m 为一个整数且 1\u0026lt;=m\u0026lt;=100),例如字符串 ABCABCABC 将会被压缩为 [3|ABC],现在小 Q 的同学收到了小 Q 发送过来的字符串,你能帮助他进行解压缩么?\n输入描述:\n输入第一行包含一个字符串 S,代表压缩后的字符串。 S的长度\u0026lt;=1000; S仅包含大写字母、[、]、|; 解压缩后的字符串长度不超过 100000; 压缩递归层数不超过 10 层; 输出描述:\n输出一个字符串,代表解压后的字符串。 输入示例:\nHG[3|B[2|CA]]F 输出示例:\nHGBCACABCACABCACAF 说明:\nHG[3|B[2|CA]]F -\u0026gt; HG[3|BCACA]F -\u0026gt; HGBCACABCACABCACAF 个人觉得我的解法很烂,而且太繁琐,但是目前还想不出更好的。代码也写的比较乱 orz. 主要想法是遍历一遍字符串,如果不是特定的压缩模式,直接添加到结果中就好,如果遇到了特殊模式([3|AB]),将该模式提取出来交给另一个函数解码,模式可能递归存在,所以使用一个栈确保提取出正确的模式。\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;stack\u0026gt; using namespace std; // decode pattern like \u0026#34;[3|abc]\u0026#34; // note: end is included string PatternDecode(const string\u0026amp; s, size_t beg, size_t end) { // the minmal len: [3|a] // beg=0, end=4 if (end - beg \u0026gt;= 4) { string ret; auto pos_delimiter = s.find(\u0026#39;|\u0026#39;, beg); int num_repeats = std::stoi(s.substr(beg+1, pos_delimiter-beg-1)); { auto pos_left = s.find(\u0026#39;[\u0026#39;, pos_delimiter); if (pos_left \u0026lt; end) // has sub-pattern { ret += s.substr(pos_delimiter+1, pos_left-pos_delimiter-1); auto pos_right = s.rfind(\u0026#39;]\u0026#39;, end-1); ret += PatternDecode(s, pos_left, pos_right); ret += s.substr(pos_right+1, end-pos_right-1); } else // has not sub-pattern { ret += s.substr(pos_delimiter+1, end-pos_delimiter-1); } } string res; while (num_repeats--) res += ret; return res; } else { throw std::invalid_argument(\u0026#34;range too small\u0026#34;); } } void Decode(const string\u0026amp; s, string* o) { if (s.empty()) throw std::invalid_argument(\u0026#34;empty coded string\u0026#34;); stack\u0026lt;size_t\u0026gt; stk; for (size_t i = 0; i != s.size(); ++i) { if (stk.empty() \u0026amp;\u0026amp; std::isalpha(s[i])) { o-\u0026gt;push_back(s[i]); } else if (s[i] == \u0026#39;[\u0026#39;) { stk.push(i); } else if (s[i] == \u0026#39;]\u0026#39;) { // if the stk has only one elem, then the last \u0026#39;]\u0026#39; of // a pattern is determined, we shall first decode this // pattern before going on. if (stk.size() == 1) { size_t pattern_beg = stk.top(); o-\u0026gt;append(PatternDecode(s, pattern_beg, i)); } stk.pop(); } } } int main() { // io string input; cin \u0026gt;\u0026gt; input; string output; Decode(input, \u0026amp;output); cout \u0026lt;\u0026lt; output; return 0; } 题十一:重排并计算逆序数 腾讯 2019 校招\n作为程序员的小 Q,它的数列和其他人的不太一样,他有$2^n$个数。老板问了小 Q 一共 m 次,每次给出一个整数$q_i$ (1\u0026lt;=i\u0026lt;=m),要求小 Q 把这些数每$2^{q_i}$分为一组,然后把每组进行翻转,小 Q 想知道每次操作后整个序列中的逆序对个数是多少呢?\n例如: 对于序列 1 3 4 2,逆序对有 (4,2), (3,2) 总数量为 2. 翻转之后为 2 4 3 1,逆序对有 (2,1), (4,3), (4,1), (3,1) 总数量为 4.\n输入描述: 第一行一个数 n(0\u0026lt;=n\u0026lt;=20) 第二行$2^n$个数,表示初始序列(1\u0026lt;=初始序列\u0026lt;=$10^9$) 第三行一个数 m(1\u0026lt;=m\u0026lt;=$10^6$) 第四行 m 个数表示$q_i$ (0\u0026lt;=$q_i$\u0026lt;=n)\n输出描述: m 行每行一个数表示答案\n输入示例:\n2 2 1 4 3 4 1 2 0 2 输出示例:\n0 6 6 0 说明: 初始序列 2 1 4 3 $2^{q_1} = 2$ -\u0026gt; 第一次:1 2 3 4 -\u0026gt; 逆序对数 0 $2^{q_2} = 4$ -\u0026gt; 第二次:4 3 2 1 -\u0026gt; 逆序对数 6 $2^{q_3} = 1$ -\u0026gt; 第三次:4 3 2 1 -\u0026gt; 逆序对数 6 $2^{q_4} = 4$ -\u0026gt; 第四次:1 2 3 4 -\u0026gt; 逆序对数 0\n首先如何计算逆序数,常用的方法是归并排序,可以顺带计算逆序数。但是由于我一开始用的是开销较大的归并,频繁的构造,复制 vector,导致运行超时。后来改成了 inplace 的归并,速度明显提高很多。\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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;time.h\u0026gt; using namespace std; // combine two sorted subseqs vector\u0026lt;int\u0026gt; merge(vector\u0026lt;int\u0026gt;\u0026amp; a, vector\u0026lt;int\u0026gt;\u0026amp; b, int* invcount) { vector\u0026lt;int\u0026gt; res; // result seq vector\u0026lt;int\u0026gt;::iterator i = a.begin(); vector\u0026lt;int\u0026gt;::iterator j = b.begin(); while(i != a.end() \u0026amp;\u0026amp; j != b.end()) { if (*i \u0026lt;= *j) res.push_back(*(i++)); if (*i \u0026gt; *j) { res.push_back(*(j++)); (*invcount) += (a.end() - i); } } // tail appending ... if (i == a.end()) { for(auto iter = j; iter != b.end(); ++iter) res.push_back(*iter); } if (j == b.end()) { for(auto iter = i; iter != a.end(); ++iter) res.push_back(*iter); } return res; } // 归并排序的递归形式 vector\u0026lt;int\u0026gt; mergesort(vector\u0026lt;int\u0026gt;\u0026amp; seq, int* invcount) { if (seq.size() == 1) return seq; else{ // split the seq into two subseqs int lsize = seq.size() \u0026gt;\u0026gt; 1; vector\u0026lt;int\u0026gt; tmpl(seq.begin(), seq.begin() + lsize); vector\u0026lt;int\u0026gt; tmpr(seq.begin() + lsize, seq.end()); vector\u0026lt;int\u0026gt; lseq, rseq; // recursively slove subproblems lseq = mergesort(tmpl, invcount); rseq = mergesort(tmpr, invcount); return merge(lseq, rseq, invcount); } } // in-place merge sort namespace inplace { /// Merge two sorted range [beg, mid), [mid, end). void Merge(vector\u0026lt;int\u0026gt;\u0026amp; arr, int beg, int mid, int end, int* invcnt) { for (int i = beg, j = mid; i \u0026lt; mid \u0026amp;\u0026amp; j \u0026lt; end;) { if (arr[i] \u0026gt; arr[j]) { *invcnt += (mid - i); std::swap(arr[i], arr[j]); // rearrange to keep the structure for (int t = j; t \u0026gt; i+1; --t) { std::swap(arr[t], arr[t-1]); } ++mid; ++j; } ++i; } } /// Inplace merge sort the range [beg, end). void MergeSort(vector\u0026lt;int\u0026gt;\u0026amp; arr, int beg, int end, int* invcnt) { if (end - beg \u0026gt; 1) // need sort { int mid = (beg + end) / 2; MergeSort(arr, beg, mid, invcnt); MergeSort(arr, mid, end, invcnt); Merge(arr, beg, mid, end, invcnt); } } } // namespace inplace /// reverse the arr by length k void ReverseBy(vector\u0026lt;int\u0026gt;\u0026amp; arr, int k) { for (auto beg = arr.begin(); beg != arr.end(); beg += k) std::reverse(beg, beg + k); } /// compute elapsed time from start_time inline double TimeElapsed(clock_t start_time) { return static_cast\u0026lt;double\u0026gt;(clock()-start_time) / CLOCKS_PER_SEC * 1000; } /// Test wrapper void UniTest(const vector\u0026lt;int\u0026gt;\u0026amp; numbers, const vector\u0026lt;int\u0026gt;\u0026amp; qs, bool inplace) { vector\u0026lt;int\u0026gt; dummynum(numbers); vector\u0026lt;int\u0026gt; buffer(numbers.size()); for (auto e : qs) { int invcount = 0; ReverseBy(dummynum, 1\u0026lt;\u0026lt;e); buffer.clear(); buffer.assign(dummynum.begin(), dummynum.end()); if (inplace) // use inplace merge sort inplace::MergeSort(buffer, 0, buffer.size(), \u0026amp;invcount); else mergesort(buffer, \u0026amp;invcount); cout \u0026lt;\u0026lt; invcount \u0026lt;\u0026lt; endl; } } int main() { // io int n; cin \u0026gt;\u0026gt; n; vector\u0026lt;int\u0026gt; numbers; for (int i = 0, tmp = 0; i != (1\u0026lt;\u0026lt;n); ++i) { cin \u0026gt;\u0026gt; tmp; numbers.push_back(tmp); } int m; cin \u0026gt;\u0026gt; m; vector\u0026lt;int\u0026gt; qs; for (int i = 0, tmp = 0; i != m; ++i) { cin \u0026gt;\u0026gt; tmp; qs.push_back(tmp); } // done clock_t start = clock(); UniTest(numbers, qs, false); cout \u0026lt;\u0026lt; TimeElapsed(start) \u0026lt;\u0026lt; \u0026#34;ms for mergesort\u0026#34; \u0026lt;\u0026lt; endl; clock_t start2 = clock(); UniTest(numbers, qs, true); cout \u0026lt;\u0026lt; TimeElapsed(start2) \u0026lt;\u0026lt; \u0026#34;ms for inplace mergesort\u0026#34; \u0026lt;\u0026lt; endl; return 0; } 附运行结果,体会一下:\n0 6 6 0 0.185ms for mergesort 0 6 6 0 0.068ms for inplace mergesort 差了 3 倍左右!\nJosephus Problem 这个问题是以弗拉维奥·约瑟夫命名的,他是 1 世纪的一名犹太历史学家。他在自己的日记中写道,他和他的 40 个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。\n——维基百科 描述:人们站在一个等待被处决的圈子里。计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。在跳过指定数量的人之后,执行下一个人。对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。(牛客网上类似的题 1)\n问题即,给定人数、起点、方向和要跳过的数字,选择初始圆圈中的位置以避免被处决。\n解法:维基百科 2 上也有,GeeksforGeeks3 还有视频教程。\n常见的有两种解法:\n单链表模拟 数学递推 显然,假设 n 个人编号:$0,1,2,3,\\dots,n-1$. 从 0 号开始报数(报数从 0 开始),报到 m-1 的将被处决,然后从下一个人开始报数。直到剩下最后一个人,赦免之。\n第一趟:报到 m 的自然是编号为$(m-1) \\mod n$. 接着从 $m \\mod n$ 开始报数,接下来又会是谁被处决呢?\n等等,先来看看问题是什么,我希望知道幸免者的编号。在 n 个人,报 m 个数的设定下,我希望知道幸免者编号,假设这个编号就是$f(n,m)$, 这里 $f$ 是个神奇的函数,我只要告诉它 n 和 m 它就能告诉我最后幸存者的编号。如果我能找到 $f(n, m)$ 和 $f(n-1, m)$ 的递推关系式,那将是极好的。\n在第一趟之后,报数从编号 $k = m \\mod n$ 开始,但是此时只有 n-1 个人,我还是想知道幸存者的编号。如果此时将编号重新映射一下,比如:\nk -\u0026gt; 0 k+1 -\u0026gt; 1 ... k-2 -\u0026gt; n-2 那么问题就变成了 n-1 个人,从 0 开始报数,报到 m-1 被处决,完完全全成了一个拥有同样结构的问题,但是规模更小了。显然,这个问题的解是 $f(n-1, m)$. 但是呢,我们得到的编号却不是原来的编号了,得把编号还原回去。这很简单,假设得到的编号是 x,那么映射回原编号 y\ny = (x+k) mod n 于是,如果我们能够知道 $f(n-1, m)$, 那么\n$$ f(n, m) = (f(n-1,m) + m) \\mod n. $$\n这就得到了递推公式,接着看一下边界条件,当 n = 1 时, $f(1, m) = 0$; 结束。\n1 2 3 4 5 6 7 8 int Josephus(int n, int m) { if (n \u0026lt; 1) throw std::invalid_argument(\u0026#34;we need n \u0026gt;= 1\u0026#34;); if (n == 1) return 0; return (Josephus(n-1, m) + m) % n; } 扔骰子的期望 拼多多 2019 校招正式批\n扔 n 个骰子,第 i 个骰子有可能掷出$X_i$种等概率的不同结果,数字从 1 到$X_i$. 比如$X_i = 2$, 就等可能的出现 1 点或 2 点。所有骰子的结果的最大值将作为最终结果。求最终结果的期望。\n输入描述: 第一行一个整数 n,表示 n 个骰子。(1 \u0026lt;= n \u0026lt;= 50) 第二行 n 个整数,表示每个骰子的结果数$X_i$. ($2 \\le X_i \\le 50$)\n输出描述: 输出最终结果的期望,保留两位小数。\n示例输入:\n2 2 2 示例输出:\n1.75 主要考察事件概率的计算。\n假设有 3 个骰子,最大点数分别为 2,3,4 点。那么扔 n 个骰子,最终结果为 1 的概率如下\n$$ P(1) = \\frac{1}{2\\times 3 \\times 4} = \\frac{1}{24} $$\n同理,最终结果为 2,也就是说三个骰子中最大的点数为 2,考虑每个骰子都点数都小于或等于 2 的概率,再减去每个骰子都小于或等于 1 的概率,即为\n$$ \\begin{gathered} P(2) = \\frac{2\\times 2\\times 2}{2 \\times 3 \\times 4} - \\frac{1}{2\\times 3 \\times 4} = \\frac{7}{24} \\newline P(3) = \\frac{2 \\times 3 \\times 3}{2 \\times 3 \\times 4} - \\frac{2\\times 2\\times 2}{2 \\times 3 \\times 4} = \\frac{10}{24} \\newline P(4) = \\frac{2 \\times 3 \\times 4}{2 \\times 3 \\times 4} - \\frac{2\\times 3\\times 3}{2 \\times 3 \\times 4} = \\frac{6}{24} \\end{gathered} $$\n这就求得了最终结果的所有可能的值对应的概率,可以看出相加为 1,现在可以求期望了。\n注意,当要求的点数大于当前骰子的最大点数时,那么该骰子掷出小于该点数的概率为 1. 比如让一个最大点数为 2 的骰子掷出小于 3 的点数,显然概率为 1.\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; vector\u0026lt;double\u0026gt; Distribution(const vector\u0026lt;int\u0026gt;\u0026amp; die_ranges, int support) { vector\u0026lt;double\u0026gt; probs; for (int i = 1; i \u0026lt;= support; ++i) { double pprob = 1.0; // for previous prob double prob = 1.0; // for current prob for (auto e : die_ranges) { pprob *= min(double(i-1) / double(e), 1.0); prob *= min(double(i) / double(e), 1.0); } probs.push_back(prob - pprob); } return probs; } int main() { int num_die; cin \u0026gt;\u0026gt; num_die; int maxpoint = 0; vector\u0026lt;int\u0026gt; die_ranges; for (int i = 0, tmp; i != num_die; ++i) { cin \u0026gt;\u0026gt; tmp; if (tmp \u0026gt; maxpoint) maxpoint = tmp; die_ranges.push_back(tmp); } // io done vector\u0026lt;double\u0026gt; probs = Distribution(die_ranges, maxpoint); double expectation = 0.0; for (int i = 1; i \u0026lt;= maxpoint; ++i) { expectation += (probs[i-1] * i); } printf(\u0026#34;%.2f\u0026#34;, expectation); return 0; } 递增二叉树 网易互娱 2020 校招正式批\n给定一颗二叉树,每个节点又一个正整数权值。若一棵二叉树,每一层的节点权值之和都严格小于下一层的节点权值之和,则称这颗二叉树为递增二叉树。现在给你一颗二叉树,请判断它是否是递增二叉树。\n输入描述:\n输入的第一行是一个正整数 T(0 \u0026lt; T \u0026lt;= 50)。接下来有 T 组样例,对于每组样例,输入的第一行是一个正整数 N,表示树的节点个数(0 \u0026lt; N \u0026lt;= 100,节点编号为 0 到 N-1)。接下来是 N 行,第 k 行表示编号为 k 的节点,输入格式为:VALUE LEFT RIGHT,其中 VALUE 表示其权值,是一个不超过 5000 的自然数;LEFT 和 RIGHT 分别表示该节点的左孩子编号和右孩子编号。如果其不存在左孩子或右孩子,则 LEFT 或 RIGHT 为 -1.\n输出描述:\n对于每一组样例,输出一个字符串。如果该二叉树是一颗递增树,则输出 YES,否则输出 NO。\n样例输入:\n2 8 2 -1 -1 1 5 3 4 -1 6 2 -1 -1 3 0 2 2 4 7 7 -1 -1 2 -1 -1 8 21 6 -1 52 4 -1 80 0 3 31 7 -1 21 -1 -1 59 -1 -1 50 5 -1 48 -1 1 样例输出:\nYES NO 这题最恶心的是输入格式,居然不是直接给一颗建好的二叉树的根节点,而是给数据让你自己建树。处理输入还比较麻烦,首先每个节点都有编号,得先存起来,然后一个一个构造节点,然后再连接起来,关键是连好以后,头节点在哪?还得找一下,最后的判断,实际上是二叉树的层次遍历,遍历得到一个向量,判断是否为严格单调递增即可。\n上图是第一个样例画出来的二叉树,左边圆圈中对应的是节点编号,右边的数字是节点的权值。要画这棵树,首先画左边的编号之间的连接图,然后把对应编号换作节点的权值即可。那么头节点在哪呢?入度为 0 的就是了!\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;functional\u0026gt; using namespace std; struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int v) : val(v), left(nullptr), right(nullptr) {} }; struct NodeInfo { TreeNode* pnode; int left; int right; NodeInfo(TreeNode* p, int l, int r): pnode(p), left(l), right(r) {} }; // build the tree and return the root TreeNode* BuildTree(vector\u0026lt;NodeInfo\u0026gt;\u0026amp; nodes) { vector\u0026lt;int\u0026gt; counts(nodes.size(), 0); // link for (size_t i = 0; i != nodes.size(); ++i) { int left = nodes[i].left; int right = nodes[i].right; if (left != -1) { nodes[i].pnode-\u0026gt;left = nodes[left].pnode; ++counts[left]; } if (right != -1) { nodes[i].pnode-\u0026gt;right = nodes[right].pnode; ++counts[right]; } } // find root for (size_t i = 0; i != counts.size(); ++i) { if (counts[i] == 0) return nodes[i].pnode; } throw std::logic_error(\u0026#34;no root\u0026#34;); } // Is the tree a increasing tree? bool IsIncrTree(TreeNode* root) { if (!root) return false; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; mat; // tranverse the tree by layer std::function\u0026lt;void(TreeNode*, int)\u0026gt; layer_travel; layer_travel = [\u0026amp;mat, \u0026amp;layer_travel](TreeNode* p, int depth) mutable { if (p == nullptr) return; if ((int)mat.size() == depth) mat.emplace_back(vector\u0026lt;int\u0026gt;()); mat[depth].push_back(p-\u0026gt;val); layer_travel(p-\u0026gt;left, depth + 1); layer_travel(p-\u0026gt;right, depth + 1); }; layer_travel(root, 0); vector\u0026lt;int\u0026gt; sums; for (auto\u0026amp; row : mat) { int sum = 0; for (auto c : row) sum += c; sums.push_back(sum); } for (size_t i = 1; i != sums.size(); ++i) { if (sums[i-1] \u0026gt;= sums[i]) return false; } return true; } int main() { int num_test; cin \u0026gt;\u0026gt; num_test; for (int num_nodes = 0; num_test--;) { cin \u0026gt;\u0026gt; num_nodes; vector\u0026lt;NodeInfo\u0026gt; nodes; while (num_nodes--) { int v, l, r; cin \u0026gt;\u0026gt; v \u0026gt;\u0026gt; l \u0026gt;\u0026gt; r; TreeNode* p = new TreeNode(v); nodes.emplace_back(p, l, r); } TreeNode* root = BuildTree(nodes); if (IsIncrTree(root)) cout \u0026lt;\u0026lt; \u0026#34;YES\\n\u0026#34;; else cout \u0026lt;\u0026lt; \u0026#34;NO\\n\u0026#34;; // memory clean for (auto\u0026amp; node : nodes) { delete node.pnode; node.pnode = nullptr; } } // nodes released return 0; } 经过棋盘的最小开销 58 同城 2020 校招\n现有一个地图,由横线与竖线组成(参考围棋棋盘),起点在左上角,终点在右下角。每次行走只能沿线移动到相邻的点,每走一步产生一个开销。计算从起点到终点的最小开销为多少。\n输入描述: $m \\times n$ 的地图表示如下\n3 3 1 3 4 2 1 2 4 3 1 其中 m=3,n=3 表示 3*3 的矩阵 行走路径为:下\u0026gt;右\u0026gt;右\u0026gt;下 输出描述:\n路径总长:1+2+1+2+1=7 动态规划入门题,可惜我当时碰到 DP 就慌,而且只会递归 DP,自顶向下,复杂度会高很多。\n假设用一个矩阵,名字就叫 dp,表示最优结果。dp(i,j) 表示从起点到坐标 (i,j) 的最小开销。很容易得到递推关系:\n$$ \\text{dp}(i, j) = \\begin{cases} \\text{map}(i,j) + \\min(\\text{dp}(i, j-1), \\text{dp}(i-1, j)) \u0026amp; i, j \u0026gt; 0\\newline \\sum_{x=0}^{j-1} \\text{map}(i, j) \u0026amp; i = 0 \\newline \\sum_{x=1}^{i-1} \\text{map}(i, j) \u0026amp; j = 0 \\end{cases}. $$\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; using namespace std; // greedy int MinCost(const vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; mat) { int row = static_cast\u0026lt;int\u0026gt;(mat.size()); int col = static_cast\u0026lt;int\u0026gt;(mat[0].size()); int i = 0; int j = 0; int ret = 0; while (i \u0026lt; row-1 \u0026amp;\u0026amp; j \u0026lt; col-1) { ret += mat[i][j]; if (mat[i+1][j] \u0026lt; mat[i][j+1]) ++i; else ++j; } if (i == row-1) { for (int x = j; x \u0026lt;= col-1; ++x) ret += mat[i][x]; } else { for (int x = i; x \u0026lt;= row-1; ++x) ret += mat[x][j]; } return ret; } // dynamic programming int mincost(const vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; mat) { int row = static_cast\u0026lt;int\u0026gt;(mat.size()); int col = static_cast\u0026lt;int\u0026gt;(mat[0].size()); vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; dp(row, vector\u0026lt;int\u0026gt;(col, 0)); // initialize dp[0][0] = mat[0][0]; for (int i = 1; i \u0026lt; row; ++i) dp[i][0] = dp[i-1][0] + mat[i][0]; for (int j = 1; j \u0026lt; col; ++j) dp[0][j] = dp[0][j-1] + mat[0][j]; for (int i = 1; i \u0026lt; row; ++i) { for (int j = 1; j \u0026lt; col; ++j) { dp[i][j] = mat[i][j] + min(dp[i-1][j], dp[i][j-1]); } } return dp[row-1][col-1]; } int main() { int num_rows, num_cols; cin \u0026gt;\u0026gt; num_rows \u0026gt;\u0026gt; num_cols; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; mat(num_rows, vector\u0026lt;int\u0026gt;(num_cols, 0)); for (int i = 0; i != num_rows; ++i) { for (int j = 0, x; j != num_cols; ++j) { cin \u0026gt;\u0026gt; x; mat[i][j] = x; } } // io done cout \u0026lt;\u0026lt; mincost(mat); return 0; } 出列的顺序(类约瑟夫环) VIVO 2020 校招正式批\n将 N 个人排成一排,从第一个人开始报数,如果报数是 M 的倍数就出列,报道队尾后则回到队头继续报数,直到所有人都出列。\n输入描述: 输入 2 个正整数,空格分隔,第一个代表人数 N,第二个代表 M。\n输出描述: 输出一个数组,每个数据表示原来在队列中的位置,用空格隔开,表示出列顺序\n输入实例:\n6 3 输出示例:\n3 6 4 2 5 1 说明:6 个人排成一排,原始位置编号 1-6,最终输出为 3 6 4 2 5 1, 表示的是原来编号为 3 的第一个出列,编号为 1 的最后出列。 思路:类似于约瑟夫环,使用链表模拟整个过程即可。\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; using namespace std; struct Node { int val; Node* next; Node(int n): val(n), next(nullptr) {} }; vector\u0026lt;int\u0026gt; ToQueue(int num_employee, int mod) { if (num_employee == 0) return vector\u0026lt;int\u0026gt;(); if (num_employee == 1) return vector\u0026lt;int\u0026gt;(1, 1); // else num_employee \u0026gt;= 2 // build list vector\u0026lt;Node*\u0026gt; nodes; for (int i = 1; i \u0026lt;= num_employee; ++i) { Node* p = new Node(i); nodes.push_back(p); } for (size_t i = 1; i != nodes.size(); ++i) { nodes[i-1]-\u0026gt;next = nodes[i]; } nodes.back()-\u0026gt;next = nodes.front(); Node* prev = nodes.back(); Node* cur = nodes.front(); vector\u0026lt;int\u0026gt; ret; for (int count = 1; cur-\u0026gt;next != cur; ++count) { if (count % mod == 0) { ret.push_back(cur-\u0026gt;val); prev-\u0026gt;next = cur-\u0026gt;next; cur = cur-\u0026gt;next; } else { prev = cur; cur = cur-\u0026gt;next; } } ret.push_back(cur-\u0026gt;val); // memory clean for (Node* p : nodes) delete p; return ret; } int main() { int num_employee, mod; cin \u0026gt;\u0026gt; num_employee \u0026gt;\u0026gt; mod; for (auto e : ToQueue(num_employee, mod)) cout \u0026lt;\u0026lt; e \u0026lt;\u0026lt; \u0026#34; \u0026#34;; return 0; } 最短通行时间 度小满金融 2020 校招\n有 N 辆车要陆续通过一座最大承重为 W 的桥,其中第 i 辆车的重量为 w[i], 过桥时间为 t[i]. 要求:第辆车上桥时间不早于第 i-1 辆车上桥的时间;i 任意时刻桥上所有车辆的总重量不超过 W。那么,所有车辆都通过这座桥的所需的最短时间是多少?\n输入:\n第一行输入两个整数 N,W(1 \u0026lt;= N, W \u0026lt;= 100000) 第二行输入 N 个整数 w[1] 到 w[N](1 \u0026lt;= w[i] \u0026lt;= W) 第三行输入 N 个整数 t[1] 到 t[N](1 \u0026lt;= t[i] \u0026lt;= 10000) 4 2 1 1 1 1 2 1 2 2 输出:\n4 干就完了,模拟!\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; int MinTime(const vector\u0026lt;int\u0026gt;\u0026amp; car_weights, vector\u0026lt;int\u0026gt;\u0026amp; pass_times, const int max_weight) { int len = car_weights.size(); int elapsed_time = 0; int cur = 0; int w = max_weight; for (;;) { int start = cur; while (cur \u0026lt; len \u0026amp;\u0026amp; w \u0026gt;= car_weights[cur]) { w -= car_weights[cur++]; } if (cur \u0026gt;= len) // reach the last car { elapsed_time += *std::max_element(pass_times.begin() + start, pass_times.end()); return elapsed_time; } else { // each time a car passed, some other car may board the bridge int mintime_idx = 0; for (int i = 0; i \u0026lt; cur; ++i) { if (pass_times[mintime_idx] \u0026gt; pass_times[i] \u0026amp;\u0026amp; pass_times[i] != 0) mintime_idx = i; } elapsed_time += pass_times[mintime_idx]; for (int i = 0; i \u0026lt; cur; ++i) // update time pass_times[i] = max(pass_times[i] - pass_times[mintime_idx], 0); // car @mintime_idx has passed, now @w may be free to pass a new car w += car_weights[mintime_idx]; } } } int main() { int num_car, max_weight; cin \u0026gt;\u0026gt; num_car \u0026gt;\u0026gt; max_weight; vector\u0026lt;int\u0026gt; car_weights; for (int n = num_car, w; n--;) { cin \u0026gt;\u0026gt; w; car_weights.push_back(w); } vector\u0026lt;int\u0026gt; pass_times; for (int n = num_car, t; n--;) { cin \u0026gt;\u0026gt; t; pass_times.push_back(t); } // io done cout \u0026lt;\u0026lt; MinTime(car_weights, pass_times, max_weight); return 0; } 数据结构相关 大部分出自 《剑指 Offer》\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;functional\u0026gt; using std::vector; struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int _val): val(_val), left(nullptr), right(nullptr) { } }; // print the right-most node of each layer of a tree. vector\u0026lt;int\u0026gt; PrintRightMost(TreeNode* root) { // - time: O(N), where N is #TreeNodes // - space: O(N + logN) if (!root) return vector\u0026lt;int\u0026gt;(); vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; mat; std::function\u0026lt;void(TreeNode*, int)\u0026gt; layer_travel = [\u0026amp;mat, \u0026amp;layer_travel](TreeNode* p, int depth) { if (p == nullptr) return; if (depth == (int)mat.size()) mat.emplace_back(vector\u0026lt;int\u0026gt;()); mat[depth].push_back(p-\u0026gt;val); layer_travel(p-\u0026gt;left, depth + 1); layer_travel(p-\u0026gt;right, depth + 1); }; vector\u0026lt;int\u0026gt; ret; for (auto \u0026amp;row : mat) ret.push_back(row.back()); return ret; } 使用两个栈构造一个队列 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 /* * Basic idea: * 1. the top of stack1 as queue rear * 2. the top of stack2 as queue front */ #include \u0026lt;stack\u0026gt; #include \u0026lt;iostream\u0026gt; using namespace std; class Queue { private: stack\u0026lt;int\u0026gt; s1; stack\u0026lt;int\u0026gt; s2; public: void push(int _node) { s1.push(_node); } int pop() { if (s2.empty()) { while (!s1.empty()) { s2.push(s1.top()); s1.pop(); } } int res = s2.top(); s2.pop(); return res; } }; 使用两个队列构造一个栈 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 /* * 1. queue2 as a auxiliary storage * 2. push: push to the rear of queue1 * 3. pop: * 3.1. quque1 has only one element, pop it * 3.2. pop the elements of queue1, push them to queue2 * till queue1 has exactly one left, pop it * then pop elems of queue2 push to queue1 */ #include \u0026lt;queue\u0026gt; using namespace std; class Stack { private: queue\u0026lt;int\u0026gt; q1; queue\u0026lt;int\u0026gt; q2; public: void push(int _node) { q1.push(_node); } int pop() { while (q1.size() != 1) { q2.push(q1.front()); q1.pop(); } int res = q1.front(); q1.pop(); while (!q2.empty()) { q1.push(q2.front()); q2.pop(); } return res; } }; 反向打印链表 给定一个单链表,反向打印每个节点的值。\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 /* * 1. use stack * 2. use recursion */ #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;stack\u0026gt; using namespace std; class ListNode { public: int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; class Solution { vector\u0026lt;int\u0026gt; printListFromTailToHead(ListNode* head) { vector\u0026lt;int\u0026gt; ret; // boundary case if (head == nullptr) return ret; stack\u0026lt;int\u0026gt; s; for (auto p = head; p != nullptr; p = p -\u0026gt; next) { s.push(p -\u0026gt; val); } while (!s.empty()) { ret.push_back(s.top()); s.pop(); } return ret; } // recursive needs a member variable // use recursion stack, tricky vector\u0026lt;int\u0026gt; arr; vector\u0026lt;int\u0026gt; reversePrint(ListNode* head) { if (head) { reversePrint(head -\u0026gt; next); arr.push_back(head -\u0026gt; val); } return arr; } /* * Consider the closure, at one recursive step, * what I should do? Let\u0026#39;s drop all the details, * just look one recursive step. * What had I done? * Oh gee, I see if the head is not null, * I must push the value to the vector, * but before this, I should take a look at * `head -\u0026gt; next`, since I have to push * the tail first. So which one can help me * do this? Yes, the function itself! Then * after I have addressed the tail, now I\u0026#39;m * going to push current value to the vector. * That\u0026#39;s all I need! * The key is you work in one recursive step, and * form a closure for the next, and do not forget * the base case (stopping rules). That how * recursion runs! And you are free of those * confusing details. */ }; 判断可能的出栈序列 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,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 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 /* * The idea is I push all elems of `pushV` into * a stack `s`, simultaneously, I track if the elems * of `popV` equal to the `s.top()`. If it is, I will * pop a elem from the stack and track the next elem * of `popV` till a mismatch. Then I\u0026#39;ll continue to push * elems into the stack. * * When I run out all elems of `pushV`, I check if the * stack is empty, if it is, then the `popV` should be * a possible pop sequence or vice versa. * * Credit: https://www.nowcoder.com/profile/248554 */ #include\u0026lt;iostream\u0026gt; #include\u0026lt;stack\u0026gt; #include\u0026lt;vector\u0026gt; using namespace std; class Solution { public: bool IsPopOrder(vector\u0026lt;int\u0026gt; pushV, vector\u0026lt;int\u0026gt; popV) { // border case if (pushV.size() == 0) return false; stack\u0026lt;int\u0026gt; s; auto i = 0, j = 0; while (i != pushV.size()) { s.push(pushV[i++]); // KEY POINT here while (!s.empty() \u0026amp;\u0026amp; s.top() == popV[j] \u0026amp;\u0026amp; j != popV.size()) { s.pop(); j++; } } return s.empty(); } }; // test int main() { vector\u0026lt;int\u0026gt; pushV = {1,2,3,4,5}; vector\u0026lt;int\u0026gt; popV = {4,5,3,2,1}; cout \u0026lt;\u0026lt; Solution().IsPopOrder(pushV, popV); return 0; } 括号匹配 原题:https://leetcode.com/problems/valid-parentheses/\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public: bool isValid(string s) { // 此解法还是比较 elegant 的 stack\u0026lt;char\u0026gt; stk; for (auto \u0026amp;c : s) { switch (c) { case \u0026#39;{\u0026#39;: stk.push(\u0026#39;}\u0026#39;); break; case \u0026#39;(\u0026#39;: stk.push(\u0026#39;)\u0026#39;); break; case \u0026#39;[\u0026#39;: stk.push(\u0026#39;]\u0026#39;); break; default: { if (stk.empty() || c != stk.top()) // 巧妙地判断了 stk 非空 return false; else stk.pop(); } } } return stk.empty(); } }; 二叉树的层次遍历 给定一颗二叉树,按每一层从左往右的顺序遍历。(队列的典型应用)\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 /* * This is a typical application of queue. * With the help of a queue, you can easily do this. * * You can also use recursion. */ #include \u0026lt;queue\u0026gt; #include \u0026lt;vector\u0026gt; using namespace std; class TreeNode { public: int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} }; class Solution { public: vector\u0026lt;int\u0026gt; HierarchicalTraversal(TreeNode* root) { if (root == nullptr) { // further error handle return vector\u0026lt;int\u0026gt;(); } vector\u0026lt;int\u0026gt; ret; queue\u0026lt;TreeNode*\u0026gt; q; q.push(root); while(!q.empty()) { auto cur = q.front(); // from left to right push the current // root\u0026#39;s children to the queue if (cur -\u0026gt; left) q.push(cur -\u0026gt; left); if (cur -\u0026gt; right) q.push(cur -\u0026gt; right); ret.push_back(cur -\u0026gt; val); q.pop(); } return ret; } /// recursive vector\u0026lt;vector\u0026lt;int\u0026gt; \u0026gt; layerTraverse(TreeNode* root) { if (root == nullptr) return ret; build(root, 0); return ret; } // recursion needs a member variable vector\u0026lt;vector\u0026lt;int\u0026gt; \u0026gt; ret; void build(TreeNode* p, int depth) { if (p == nullptr) return; if (ret.size() == depth) ret.push_back(vector\u0026lt;int\u0026gt;()); ret[depth].push_back(p -\u0026gt; val); // use depth to track layer build(p -\u0026gt; left, depth + 1); build(p -\u0026gt; right, depth + 1); } }; 求整数的二进制表示中“1”的个数 输入一个整数,输出该数二进制表示中 1 的个数。其中负数用补码表示。\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 /* * `n` and `n-1` are the same from high bit position * to low, they differs from the last bit of 1 of n, * let\u0026#39;s name it x. Have a look from x to the lowest * bit position, * n: 010101000 * \u0026amp; x \u0026lt;--- position x * n-1: 010100111 * --------------- * 010100000 \u0026lt;--- n\u0026amp;(n-1) * It erases the last bit of 1 in n!!! * * This is why the following procedure will work. :) */ #include \u0026lt;iostream\u0026gt; int NumOf1(int n) { int cnt = 0; while (n) { n = n \u0026amp; (n-1); ++cnt; } return cnt; } int main() { int N = 0; std::cin \u0026gt;\u0026gt; N; std::cout \u0026lt;\u0026lt; NumOf1(N); return 0; } 不可描述 求出 113 的整数中 1 出现的次数,并算出 1001300 的整数中 1 出现的次数?为此他特别数了一下 1~13 中包含 1 的数字有 1、10、11、12、13 因此共出现 6 次,但是对于后面问题他就没辙了。ACMer 希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中 1 出现的次数(从 1 到 n 中 1 出现的次数)。\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 /* * Credits: unknown * * Idea: * 1) Focus exactly one decimal position, calculate the * number of ones. * 2) Run out from lowest to highest position, add them * together, it\u0026#39;s the answer. * * Imaging the numbers from 1 to n sits in a line in * front of you. Now you\u0026#39;re required to calculates all * the ones in the sequence. A basic way is that, each * time I focus on the same digit pos, and count all * ones in that pos. Next time I focus on another pos. * And I sum them all together, finally got the answer. * * See how can we count the num of ones in a specific * decimal pos? Let\u0026#39;s do that! Suppose N = 301563. * * Step 1. * Now I focus the hundred position, and split N into `a` * and `b`, where * * a = ceil(N / 100) = 3015 * b = N % 100 = 63 * * 1). There are (a / 10 + 1) = 302 hits of one * 2). Each of length 100 * 3). Totally (a/10 + 1) * 100 hits of one * * Let me explain a little: * (000-301)5(00-99) -\u0026gt; (000-301)1(00-99) * * The digits above hundred pos have 302 variants, and * the digits under hundred pos has 100 variants, thus * gives a total (a/10 + 1) * 100. * * Step 2. * This time I focus on thousand pos, and now * * a = N / 1,000 = 301 * b = N % 1,000 = 563 * * 1). There are (a/10) = 30 hits of one * 2). Each of length 1000 * 3). And a tail hits of 564 * * (00-29)1(000-999) + 301(000-563) * This gives 30 * 1000 + 564 = (a/10)*1000+(b+1) * * Step 3. * Now move to ten thousand pos, with * * a = N / 10,000 = 30 * b = N % 10,000 = 1563 * * 1). There are total (a/10)=3 hit * 2). Each of length 10,000 * * (0-2)1(0000-9999) * gives 3 * 10,000 = (a/10). * * That\u0026#39;s all 3 cases. Let\u0026#39;s write! */ class Solution { public: int NumOnes(int n) { int ones = 0; for (long long m = 1; m \u0026lt;= n; m *= 10) { ones += /* * this covers case 1 \u0026amp; 3 * since (a+8)/10 = a/10 + 1 if a%10 \u0026gt;= 2 * (a+8)/10 = a/10 if a%10 == 0 */ (n/m + 8) / 10 * m + // case 2 (n/m % 10 == 1) * (n%m + 1); } return ones; } }; K 轮换 原题:https://leetcode.com/problems/rotate-array/\nGiven an array, rotate the array to the right by k steps, where k is non-negative.\nExample 1:\nInput: [1,2,3,4,5,6,7] and k = 3 Output: [5,6,7,1,2,3,4] Explanation: rotate 1 steps to the right: [7,1,2,3,4,5,6] rotate 2 steps to the right: [6,7,1,2,3,4,5] rotate 3 steps to the right: [5,6,7,1,2,3,4] Example 2:\nInput: [-1,-100,3,99] and k = 2 Output: [3,99,-1,-100] Explanation: rotate 1 steps to the right: [99,-1,-100,3] rotate 2 steps to the right: [3,99,-1,-100] Note:\nTry to come up as many solutions as you can, there are at least 3 different ways to solve this problem. Could you do it in-place with O(1) extra space? 提供三种方法:第一种,效率最低,因为 vector 从头插入元素开销很大;第二种,花费额外的空间,来降低时间开销;第三种,挺好的,std::reverse 用的是首尾交换元素,无额外空间开销。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: void rotate(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { #if false // method 1. if (k == 0) return; nums.insert(nums.begin(), nums.back()); nums.pop_back(); rotate(nums, k-1); #elif false // method 2. vector\u0026lt;int\u0026gt; dummy(nums); for (int i = 0; i != dummy.size(); ++i) { nums[(i+k) % nums.size()] = dummy[i]; } #elif true // method 3. k %= nums.size(); std::reverse(nums.begin(), nums.end()); std::reverse(nums.begin(), nums.begin() + k); std::reverse(nums.begin() + k, nums.end()); #endif } }; Move zeros 原题:https://leetcode.com/problems/move-zeroes/\nGiven an array nums, write a function to move all 0\u0026rsquo;s to the end of it while maintaining the relative order of the non-zero elements.\nExample:\nInput: [0,1,0,3,12] Output: [1,3,12,0,0] Note:\nYou must do this in-place without making a copy of the array. Minimize the total number of operations. 思路:记录数字 0 出现的个数 count。如果当前数字是 0,给 count 加一;如果不是 0,将这个值往前挪 count 位。最后将最后 count 个元素置 0.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public: void moveZeroes(vector\u0026lt;int\u0026gt;\u0026amp; nums) { if (nums.size() \u0026lt; 2) return; // c.f. https://leetcode.com/explore/interview/card/top-interview-questions-easy/92/array/727/ int numzeros = 0; for (int i = 0; i != nums.size(); ++i) { if (nums[i] == 0) ++numzeros; else nums[i-numzeros] = nums[i]; } // set the tails to 0 while (numzeros) { nums[nums.size() - numzeros] = 0; --numzeros; } } }; Kth Largest Element in an Array From: LeetCode125.\nFind the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.\nExample 1:\nInput: [3,2,1,5,6,4] and k = 2 Output: 5 Example 2:\nInput: [3,2,3,1,2,4,5,5,6] and k = 4 Output: 4 Note: You may assume k is always valid, 1 ≤ k ≤ array\u0026rsquo;s length.\n思路:利用快排的 partition 思想,每次选取一个 pivot,将数组分为小于 pivot 和大于 pivot 的两部分。此时 pivot 的 index 就是排好序之后的 index,与 k 相比,如果出较小,则在后半部分(大于 pivot)再次划分,如果较大,则在前半部分划分。直到划分出来的 pivot 的 index 等于 k。还要注意的是,这样找出来的是第 k 小,用数组长度减一下,才是第 k 大,注意 index 的变换。\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; using namespace std; class Solution { public: int findKthLargest(vector\u0026lt;int\u0026gt;\u0026amp; nums, int k) { // from 王小康:快排 partition 知道吧? // 就一刀一刀地劈开,劈一次你知道 pivot 的 index, // 如果比 k 小,继续在右边劈,如果比 k 大,就在左边劈! if (nums.empty()) throw std::invalid_argument(\u0026#34;empty arr\u0026#34;); if (nums.size() == 1) return nums.front(); // else @nums has \u0026gt;= 2 elems int mid = Partition(nums, 0, nums.size()); k = nums.size() - k; // kth large = len+1-k small while (mid != k) { if (mid \u0026lt; k) mid = Partition(nums, mid + 1, nums.size()); else mid = Partition(nums, 0, mid); } return nums[mid]; } size_t Partition(vector\u0026lt;int\u0026gt;\u0026amp; arr, size_t beg, size_t end) { size_t pivot = beg; size_t i = pivot + 1; for (auto j = pivot + 1; j \u0026lt; end; ++j) { if (arr[j] \u0026lt; arr[pivot]) { std::swap(arr[j], arr[i]); ++i; } } std::swap(arr[i-1], arr[pivot]); return i - 1; } }; int main() { vector\u0026lt;int\u0026gt; nums = {3,2,1,5,6,4}; Solution so; cout \u0026lt;\u0026lt; so.findKthLargest(nums, 2); return 0; } 最大连续子列 From: LeetCode53.\nGiven an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.\nExample:\nInput: [-2,1,-3,4,-1,2,1,-5,4], Output: 6 Explanation: [4,-1,2,1] has the largest sum = 6. 设置两个变量,一个记录当前连加的值,另一个记录目前位置最大连续子序列,迭代更新。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int MaxSubArray(const vector\u0026lt;int\u0026gt;\u0026amp; arr) { int max_so_far = INT_MIN; int sum = 0; for (int e : arr) { if (sum \u0026gt; 0) sum += e; else sum = e; max_so_far = max(max_so_far, sum); } return max_so_far; } https://www.nowcoder.com/questionTerminal/f78a359491e64a50bce2d89cff857eb6\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://en.wikipedia.org/wiki/Josephus_problem\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.geeksforgeeks.org/josephus-problem-set-1-a-on-solution/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/coding-problems/","tags":["算法","动态规划","编程"],"title":"A collection of some coding problems"},{"categories":["Notes"],"contents":"前天做百度笔试,没想到居然出往年的题!哼!更惨的是出了我也不会!我以为只是一个简简单单的字符串匹配,没想到要动用这么难懂的算法。说起来算法导论上也有,只是之前没看到那里。所以,总结一下:我本有好多次机会学习它,然而一次都没有把握。:(\n所以这次,拿出来好好研究一波,做点笔记,以备日后之用。说起来网络上关于该算法的博文一大堆,我也不说能比它们都好,每个人都有适合自己的理解,我这里就是瞎谈一番罢了。\n在开始之前,强烈建议先读 Jake Boxer 的文章,它从无到有,讲得浅显易懂,介绍了 how it works!我这篇文章,也是在看了好多中文博客未解之后,开始看了那个外文博文。在理解的基础上,加一点自己的描述。\n背景 长长的博文一大堆,相信你也很珍惜时间,我就长话短说(偷懒找个借口:p)。这个算法是干嘛的? 它解决的问题是,给定一个字符串,我们称之为主角 K,然后你要在一个比它长的配角 Z 中找到我们的主角。翻译一下:在父串中寻找给定的子串,返回匹配索引。比如说K = king,Z = zookingmonkey,很显然,Z 中有与 K 相同的子串,返回索引4.\n常规方法 沿用 Jake Boxer 的例子,设K = abababca,Z = bacbababaabcbab. 常规的方法是从头开始比较,比如\n/* * `|` denotes a match * `x` denotes a dismatch */ (1): bacbababaabcbab x abababca (2): bacbababaabcbab |x abababca (3): bacbababaabcbab x abababca (4): bacbababaabcbab x abababca (5): bacbababaabcbab |||||x abababca (6): bacbababaabcbab x abababca (7): bacbababaabcbab |||x abababca (8): bacbababaabcbab x abababca (9): bacbababaabcbab| | abababca ---\u0026gt; return NOT FOUND! 以上就是正常思考的方法,维护一个指针p,指向第一次开始比的位置,然后依次比下去,跟踪匹配的位数,如果全部匹配,返回p,如果有一个不匹配,直接右移一位,p = p+1. 接着如法炮制,从第一个字符依次比下去。设父串 Z 的长度为 n,子串 K 的长度为m,那么上述方法最坏情况下要比较m*n次,时间复杂度为O(mn).\nKMP 算法要做的事情的,就是根据子串 K 的特征,在移位和比较的时候实现一个跳步!减少了比较次数。\n但是要根据子串 K 的特征,哪又是什么样的?该如何刻画呢?这就要说到下面的 partial match table 了。\nPartial Match Table KMP 有一个很重要的表,叫做 Partial Match Table (PMT),翻译过来叫做部分匹配表。它有多重要呢?就像理想对于你的那种重要。KMP 什么时候能跳步,跳多少,都是由它决定的!\n先来看看它如何产生的吧,关于它的生成,许多博文讲得很清楚了。其中大多是用前缀集、后缀集的概念描述。设有字符串str = ababba, 它的长度length(str) = 6,\n前缀集:去掉字符串尾部 1-5 个字符得到的所有子串。记作 P(str) = {a, ab, aba, abab, ababb}. 后缀集:去掉字符串头部 1-5 个字符得到的所有子串。记作 S(str) = {a, ba, bba, abba, babba}. 该字符串str = ababba对应的 partial mathch value (PMV) 计算方式: $$ v = \\max_{i\\in S(\\texttt{str}) \\cap P(\\texttt{str})} \\text{length}(i) := f(\\texttt{str}) $$ 于是,给定一个字符串str,我们的f(str)就能返回它的 PMV. 现在有请我们的主角K = abababca上场,我们来看看如何构建它的 PMT.\nNote: 为了防止我自定义的术语让你感到很吃力,先给你洗个脑。 PMT 是一个表(table),PMV 是一个值(value)。PMT 中的元素就是 PMV. PMT 是一个表(table),PMV 是一个值(value)。PMT 中的元素就是 PMV. PMT 是一个表(table),PMV 是一个值(value)。PMT 中的元素就是 PMV.\n先用一句话简要说明一下:对 K 的前缀集的每个字符串再加上 K 本身,计算它们的 PMV,按从短到长的顺序将它们排成一行。再用程序语言表达一下:\n1 2 3 4 5 def buildPMT(K): # 假设 S(K) 返回的列表按子串的长度从小到大排序 PMT = [f(substr) for substr in S(K)] PMT.append(f(K)) return PMT 那么我们计算得到的K = abababca的 partial match table 如下:\nstr a b a b a b c a value 0 0 1 2 3 4 0 1 index 0 1 2 3 4 5 6 7 记住这个表,它很重要!或者讲到跳转的时候再过来查看。这一部分基本上是转述了 Jake Boker 的话,不过表达的可能不如他。感谢 Jake Boxer.\n又 下面讲讲我理解的 PMT 的计算方式。因为 PMV (partial match value) 是最大公共子串的长度,哪两个公共?前缀集和后缀集!那么我们比较的时候肯定要分别在两个集合中找字符串长度一致的两个串,考察它们是否相等。依次从长度为 1,2,3\u0026hellip;这么一直找下去,如果有更长的公共串,就更新 PMV。\n具体来说,给我一个字符串str = ababa,我可以这样来计算它的 PMV. 想象有一块木板插空该字符串,\na|baba =\u0026gt; a, baba ab|aba =\u0026gt; ab, aba aba|ba =\u0026gt; aba, ba abab|a =\u0026gt; abab, a 每次插空,我都可以得到一个前缀和一个后缀。从上面的结果来看,产生的次序还有一定的对称性。那么我计算它的 PMV 时,直接这样比较:\n(1). a|baba \u0026lt;=\u0026gt; abab|a ---\u0026gt; PMV = 1 (2). ab|aba \u0026lt;=\u0026gt; aba|ba ---\u0026gt; PMV = 3 ( ). aba|ba \u0026lt;=\u0026gt; ab|aba ---\u0026gt; it returns to step (2) ( ). abab|a \u0026lt;=\u0026gt; a|baba ---\u0026gt; it returns to step (1) ---\u0026gt; return PMV = 3 即每次划分之后,我直接考察它对称的划分,然后比较前缀后缀是否相等,进而更新 PMV.\nPMT 怎么用 实际上这一段也算是转述 Jake Boxer 的话,再次感谢!\nKMP 算法要做的事情的,就是根据子串 K 的特征,在移位和比较的时候实现一个跳步!减少了比较次数。\n之前提到 KMP 的主要思想就是跳步,现在是时候来看看它是怎么个跳法了。同样的父串Z = bacbababaabcbab,子串K = abababca.\n/* * `|` denotes a match * `x` denotes a dismatch * `~` denotes a jump */ (1): bacbababaabcbab x abababca (2): bacbababaabcbab |x abababca (3): bacbababaabcbab x abababca (4): bacbababaabcbab x abababca (5): bacbababaabcbab |||||x abababca !ATTENTION! I\u0026#39;LL JUMP (6): bacbababaabcbab ~~|||x abababca !JUMP AGAIN! (7): bacbababaabcbab| ~~ | abababca ---\u0026gt; return NOT FOUND! 为什么你跳得这么兴奋?为什么可以这么跳?我想应该有人和我当初一样,虽然你跳的很好,但是我一脸懵逼 (#°Д°).\n我们把来看 step5 到 step6 的跳步拎出来看看,\n(5): bacbababaabcbab |||||x abababca (5.1): ****ababa****** |||||x ababa*** 从 step5 到 step5.1 我什么也没干,只是把一些碍眼的东西替换成了*号。我们可以看到的是,step5 匹配了 5 个字符,匹配的是 K 的开头向后 5 个字符。让我们回头看看这个子串的 PMV,查表得知f(\u0026quot;ababa\u0026quot;)=3. 这个“3”代表着什么?它代表了子串ababa的长度为 3 的前缀一定等于长度为 3 的后缀,因为这就是 PMV 的物理意义啊,同志们!所以我可以跳步!直接将前缀abc挪到与后缀abc对齐!\naba|ba \u0026lt;=\u0026gt; ab|aba ---\u0026gt; jump: ababa ~~||| ababa 我不但可以跳步 2,我还知道后面的 3 个字符比都不用比,肯定和父串 match,所以我直接从第 4 个字符开始比。\n(5): bacbababaabcbab |||||x abababca (5.1): ****ababa****** |||||x ababa*** (6): bacbababaabcbab ~~|||x abababca | start point for comparison 如果将 PMT 存在一个数组里,数组下表从 0 开始的话,那么每次跳步的长度就可以用一个公式来刻画:\njump_chars = PML - PMT[PML-1] 其中,PML 表示 partial match length,代表当前匹配长度,比如 step5 的匹配长度 `PML=5`. Q: PMT 是什么?它为什么这么屌?凭什么它这么屌?它是干嘛的呢? A: 可以说 PMT 就是待匹配字符串的本体了。\n复杂度分析 来日在填 (╬ Ò ‸ Ó)\nC++ 实现 Reference The Knuth-Morris-Pratt Algorithm in my own words ","permalink":"https://guyueshui.github.io/post/%E7%9E%8E%E8%AF%B4kmp%E7%AE%97%E6%B3%95/","tags":["算法,模式匹配"],"title":"瞎说 KMP 算法"},{"categories":["Notes"],"contents":"最近在准备笔试,于是在各种网站上刷题嘛。期间做了百度某年的一道 编程题。\n小 B 最近对电子表格产生了浓厚的兴趣,她觉得电子表格很神奇,功能远比她想象的强大。她正在研究的是单元格的坐标编号,她发现表格单元一般是按列编号的,第 1 列编号为 A,第 2 列为 B,以此类推,第 26 列为 Z。之后是两位字符编号的,第 27 列编号为 AA,第 28 列为 AB,第 52 列编号为 AZ。之后则是三位、四位、五位……字母编号的,规则类似。\n表格单元所在的行则是按数值从 1 开始编号的,表格单元名称则是其列编号和行编号的组合,如单元格 BB22 代表的单元格为 54 列中第 22 行的单元格。\n小 B 感兴趣的是,编号系统有时也可以采用 RxCy 的规则,其中 x 和 y 为数值,表示单元格位于第 x 行的有第 y 列。上述例子中的单元格采用这种编码体系时的名称为 R22C54。\n小 B 希望快速实现两种表示之间的转换,请你帮忙设计程序将一种方式表示的坐标转换为另一种方式。\n输入 输出 输入的第一行为一个正整数 T,表示有 T 组测试数据(1\u0026lt;=T\u0026lt;=10^5)。随后的 T 行中,每行为一组测试数据,为一种形式表示的单元格坐标。保证所有的坐标都是正确的,且所有行列坐标值均不超过 10^6 对每组测试数据,单独输出一行,为单元格坐标的另一种表示形式。 2 R23C55 BC23 BC23 R23C55 写这道题目的时候,正好复习了 C++ 的类,于是居然鬼使神差的想设计一个类来实现它。C++ 的核心思想是面向对象,而此处的单元格正好有显著的对象特征。一个单元格,应该有座标,以及其中的内容。这是很自然的,当初看 C++ Primer 的时候,书中也是以书本销量作为引入,介绍并阐述类的设计。\nDefinitions 闲话少说,现在就来看看如何实现这个类。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Unit/Unit.h class Unit { private: using pos = unsigned int; pos rowIdx; pos colIdx; public: // constructors Unit(pos _r, pos _c) : rowIdx(_r), colIdx(_c) { } // constructor2 Unit(string); }; 先看这么多,我希望每个单元个有两个属性,一个行索引rowIdx一个列索引colIdx. 而且我定义了两个构造函数,一个是直接将行列索引传入,另一个则是通过读取一个字符串,来解析出其中的行列索引的值。这里有一个问题,因为不同类型的字符串解析的方式不一样,所以需要一个指示变量来指明传入的字符串到底是哪个类型:BC23还是R23C55?除此之外,我还需要一个可以转换不同类型表示的函数,给了我表示类型 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 24 25 26 27 28 29 30 31 32 33 34 35 // Unit/Unit.h #ifndef _UNIT_H_ #define _UNIT_H_ #include\u0026lt;string\u0026gt; class Unit { private: using pos = unsigned int; // type alias pos rowIdx; pos colIdx; bool isRC; // is the type `R23C55`? void getIdx_RC(std::string); // build index for type \u0026#39;R23C55\u0026#39; void getIdx_NRC(std::string); // for type \u0026#39;BC23\u0026#39; public: // constructor1 Unit(pos _r, pos _c) : rowIdx(_r), colIdx(_c) { } // constructor2 Unit(std::string); // selectors pos getRow() { return rowIdx; } pos getCol() { return colIdx; } // modifiers void setRow(pos _r) { rowIdx = _r; } void setCol(pos _c) { colIdx = _c; } // utilties void printer(void); void convertor(void); }; #endif 这里先不要纠结定义了很多不知道要干嘛的函数,往下看类的实现,你会慢慢明白为什么需要它们。\nImplementions 类的头文件中只定义了类,除少数内联函数外,大多数函数仍未实现。按照模块化的哲学,新开一个文件写类的实现。\n构造函数的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Unit/Unit.hpp #include \u0026#34;Unit.h\u0026#34; // implement constructor2 Unit::Unit(string _s) { // wishful thinking isRC = typeInfer(_s); if (isRC) { /* * wishful thinking: suppose i have the function * that helps me do this job */ getIdx_RC(_s); } else { // again wishful thinking getIdx_NRC(_s); } } 好了,现在我们实现了第二个构造函数,它接受一个string对象,从中解析出行列索引的值,然后初始化rowIdx和colIdx.\n但是,我们想当然的调用了一些我们还没有实现的函数。这里要特别注意一点:在写程序的时候,这种想法很有用!以下思想来自我学习 SICP 一段时间以后的自我体会:程序往往会包含很多的函数,为什么?因为有时候一个稍微复杂的问题,并不是一个函数就能解决的,所以需要多个函数相互协调,相互调用才能共同完成或是解决一项工作。如果你费尽心思把它们都写在一个函数里,可能你觉得很好,但是一旦程序报错,你将无从下手,很难定位到错误发生在哪里。这也是程序设计讲究模块化的理由。将复杂的功能抽象成一个一个的互不干涉的模块(子程序),在每一个小模块里尽可能的将代码写好,使得它只完成并且高效准确地完成这一项任务。那么当所有模块相互协同起来,将会使难以想象的高效,且不容易出错,即便是出错了,也能很快定位到错误发生的地点,便于调试。\n这样做的好处还有一个,就是你在写程序的时候变得更加轻松。为什么?因为我不用考虑所有的细节,只是调用了一些函数,而实现这些函数很可能不是我们要干的活。Oh, that\u0026rsquo;s cool! George will do for me. 你甚至可以去喝杯咖啡。但是现在,让我们短暂的充当以下 George 的角色。就拿Unit类的设计来说,我现在想要实现第二个构造函数,根据题目的意思,我可能接受两个代表不同表示方法的string,我要将它们解析成行列索引。让我们回头看看这个函数的实现,它先判断输入的是那个类型,然后分情况做不同的事(调用不同的函数)。这里我用到了 3 个 wishful thinking:\n我希望有一个叫typeInfer()的函数,我给它一个字符串,它告诉我这属于哪个类型的表示方法。 如果是RxCy型,我希望有一个函数getIdx_RC()能够处理这种类型的输入,解析出行列索引。 如果是BC23型,我希望有一个函数getIdx_NRC()能够处理这种类型的输入,解析出行列索引。 这样一来,我们不容易犯错。为什么?因为这个构造函数的逻辑足够简单,仅仅是分两个情况做不同的事。做的事也很简单:仅仅是调用一个函数!如果你能保证所调用的函数不犯错,那么整个过程也不会出错。既简单,又 robust!\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 // Unit/Unit.hpp #include \u0026#34;Unit.h\u0026#34; #include \u0026lt;string\u0026gt; /* * Predefined things... */ bool typeInfer(std::string _s) { // infer the representation type if (_s[0] == \u0026#39;R\u0026#39; \u0026amp;\u0026amp; std::isdigit(_s[1])) return true; else return false; } void Unit::getIdx_RC(std::string _s) { /* * build the row/col index for * type \u0026#39;R23C55\u0026#39; */ _s.erase(0, 1); // remove first \u0026#39;R\u0026#39; // find where \u0026#39;C\u0026#39; is std::string::size_type c_pos = 0; for (; c_pos != _s.size(); ++c_pos) { if (_s[c_pos] == \u0026#39;C\u0026#39;) break; } auto s1 = _s.substr(0, 0 + c_pos); // s1 = \u0026#34;23\u0026#34; auto s2 = _s.substr(c_pos + 1, _s.size()); // s2 = \u0026#34;55\u0026#34; // set index rowIdx = std::stoi(s1); colIdx = std::stoi(s2); } void Unit::getIdx_NRC(std::string _s) { /* * build the row/col index for * type \u0026#39;BC23\u0026#39; */ // find the first num std::string::size_type n_pos = 0; for (; n_pos != _s.size(); ++n_pos) { if (std::isdigit(_s[n_pos])) break; } auto s1 = _s.substr(0, n_pos); // s1 should be \u0026#34;BC\u0026#34; auto s2 = _s.substr(n_pos, _s.size()); // s2 should be \u0026#34;23\u0026#34; rowIdx = std::stoi(s2); colIdx = letter2pos(s1); } Note: 注意到getIdx_RC()和getIdx_NRC()需要访问类内私有变量,所以应该声明成类的成员函数。\n好了,看了上面的实现,我又想当然的引入了几个函数。但是通过上下文,你可以很明显的看出来我引入这个函数实干嘛用的。正是因为这个时候我需要有一个函数帮我去干这个事情,而我又不想把这些复杂的工作都写到一个函数里面(因为容易出错,且很难调试)。所以我引入了它们:\nletter2pos()接受一个字符串,返回解析出来的数值索引。 好吧,居然只引入了一个 (╬ Ò ‸ Ó),再来看看它的实现。\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 // Unit/Unit.hpp /* * Predefined things... */ // return the corresponding num for a given string int letter2pos(const std::string\u0026amp; _s) { auto len = _s.size(); int res = 0; for (; len != 0; --len) { res = res*26 + alph2num(_s[_s.size() - len]); // std::cout \u0026lt;\u0026lt; \u0026#34;res: \u0026#34; \u0026lt;\u0026lt; res \u0026lt;\u0026lt; std::endl; } return res; } // return the corresponding string for a given num std::string pos2letter(int _p) { if (_p \u0026lt;= 26){ char c[1] = {num2alph(_p)}; std::string s(c); return s; } std::string res; int r = 0; while (_p \u0026gt; 26) { r = _p%26; res += num2alph(r); _p /= 26; } res += num2alph(_p); std::reverse(res.begin(), res.end()); // cout \u0026lt;\u0026lt; \u0026#34;res: \u0026#34; \u0026lt;\u0026lt; res \u0026lt;\u0026lt; endl; return res; } 哈哈,我又想象了几个不存在的函数。它们的作用很容易通过上下文得知。letter2pos()和pos2letter()是一对相反的函数,它们的作用是完成BC\u0026lt;-\u0026gt;55的映射。至于alph2num()和num2alph(),其实也是一对相反的函数,用于检索 26 个字母对应的数值。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Unit/Unit.hpp // global map for quick search char MAP[26] = {\u0026#39;A\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;C\u0026#39;,\u0026#39;D\u0026#39;,\u0026#39;E\u0026#39;,\u0026#39;F\u0026#39;,\u0026#39;G\u0026#39;, \u0026#39;H\u0026#39;,\u0026#39;I\u0026#39;,\u0026#39;J\u0026#39;,\u0026#39;K\u0026#39;,\u0026#39;L\u0026#39;,\u0026#39;M\u0026#39;,\u0026#39;N\u0026#39;, \u0026#39;O\u0026#39;,\u0026#39;P\u0026#39;,\u0026#39;Q\u0026#39;,\u0026#39;R\u0026#39;,\u0026#39;S\u0026#39;,\u0026#39;T\u0026#39;, \u0026#39;U\u0026#39;,\u0026#39;V\u0026#39;,\u0026#39;W\u0026#39;,\u0026#39;X\u0026#39;,\u0026#39;Y\u0026#39;,\u0026#39;Z\u0026#39;}; // return a num for the given char int alph2num(char _c) { int idx = 0; for (; idx != 26; ++idx) { if (MAP[idx] == _c) return idx + 1; } std::cerr \u0026lt;\u0026lt; _c \u0026lt;\u0026lt; \u0026#34;Not found!\u0026#34; \u0026lt;\u0026lt; std::endl; return 0; } // return a char for the given num char num2alph(int _i) { return MAP[_i - 1]; } 转换函数的实现 基于上面的工作,转换函数的实现就显得格外清晰简单。所谓转换函数,就是当我输入的是类型 1 的字符串,它输出转换之后的类型 2 的字符串,由此达到一个转换单元格表示方法的效果。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // Unit/Unit.hpp /* * Predefined things... */ void Unit::convertor() { if (isRC) { std::cout \u0026lt;\u0026lt; pos2letter(colIdx) + std::to_string(rowIdx) \u0026lt;\u0026lt; std::endl; } else { std::cout \u0026lt;\u0026lt; \u0026#34;R\u0026#34; + std::to_string(rowIdx) + \u0026#34;C\u0026#34; + std::to_string(colIdx) \u0026lt;\u0026lt; std::endl; } } void Unit::printer() { std::cout \u0026lt;\u0026lt; \u0026#34;row index: \u0026#34; \u0026lt;\u0026lt; rowIdx \u0026lt;\u0026lt; \u0026#34;\\ncol index: \u0026#34; \u0026lt;\u0026lt; colIdx \u0026lt;\u0026lt; std::endl; } 最后附加一个printer()方便打印信息。至此,整个类的设计大概就完了。值得注意的是,最后的convertor()之所以能够如此简单地写出来,是因为我们合理将一些工作模块化,这样带来的好处就是可以重复利用,易于维护。\n总结 我之前写代码,总是不注意模块化,不注意抽象化。能一个函数完成的事为什么要写两个函数?然而,最后只能自食其果。一旦报错,一步步地定位错误,从下往上调试,陷入苦海。也有的时候,因为函数过于复杂,把自己绕糊涂,陷入一个圈子里想不通,弄不懂,出不来。这些结果都以失败告终!而且会打击自信心,感觉别人写代码是写代码,我写代码就纯粹是写 bug 啊!\n好在前段时间看了点 SICP,B 站上有视频的,我自己也在对着书看,真的是非常好的课程。循着书中传递的思想,慢慢就这么写着,发现有的问题可以写出来了,得益于代码结构的改变,调试错误也比以前稍微轻松一些。到这次写这道编程题,要是在考试中这么写,我肯定来不及的。但是我在课余花了不少时间构思,终于用面向对象的思想将它初步实现。虽然这个类设计的很简单,也有很多瑕疵:\n异常输入的处理 类结构的优化以及完备性检查 代码细节的优化 但是和以前半途而废相比,起码完成了类的实现,虽然很粗糙。谨以此文记录一下!\n附赠:SICP 的学习资源\nB 站视频 在线电子书 习题答案 p2pu sicp solutions 感谢 SICP 视频翻译工作者,感谢 B 站 up 主的搬运,感谢开源社区!\n","permalink":"https://guyueshui.github.io/post/%E5%88%9D%E5%B0%9D-c-%E7%B1%BB%E8%AE%BE%E8%AE%A1/","tags":["cpp"],"title":"初尝 C++ 类设计"},{"categories":["Notes"],"contents":"记录一下 LeetCode 做的一道题。要求实现两个整数的加法,但不能使用内置的+或-. 原题地址:https://leetcode.com/problems/sum-of-two-integers/\n我们来看一下答案:\n1 2 3 4 5 6 class Solution { public: int getSum(int a, int b) { return b == 0? a : getSum(a^b, (a\u0026amp;b)\u0026lt;\u0026lt;1); } }; 乍一看,似乎很难看,位运算毕竟不是很直观。重写一下,\n1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public: int getSum(int a, int b) { int sum = a; while (b != 0) { sum = a^b; // sum w/o carry b = (a\u0026amp;b) \u0026lt;\u0026lt; 1; // calculate the carry a = sum; // add this sum to next-\u0026gt;a } return sum; } } or\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public: int getSum(int a, int b) { int sum = a; if (b != 0) { sum = a^b; b = (a\u0026amp;b) \u0026lt;\u0026lt; 1; return getSum(sum, b); } else { return sum; } } } 除了将递归写成了迭代,其余部分都是照着第一段代码来的。重要的是为什么是这样?为什么会有\na + b == (a^b) + (a\u0026amp;b)\u0026lt;\u0026lt;1 现在我们挨个来看看a^b、a\u0026amp;b到底是啥?首先a^b,^是 C++ 中的按位异或运算符,它的运算表为:\n1^1 = 0 0^0 = 0 1^0 = 1 0^1 = 1 现在我们把它竖过来看,或许你会发现一点东西:\n1011 \u0026lt;---+ a X 1011 ^ 1010 \u0026lt;---+ b X + 1010 a^b= ------- X a+b= ------- 0001 X 10101 这好像就是二进制的加法呀?除了没有进位!那么进位在哪里呢?我们把目光转向a\u0026amp;b,\u0026amp;是 C++ 按位与运算符,它的运算表如下:\n0\u0026amp;0 = 0 0\u0026amp;1 = 0 1\u0026amp;0 = 0 1\u0026amp;1 = 1 可以看到,与运算只有一种情况为 1,这恰好对应着二进制加法中需要进位的情况。再加上进位需要向左边进一位,所以还应该左移一位加上之前的a^b,正好就是加法需要的结果。到这里你应该明白为什么a+b == (a^b) + (a\u0026amp;b)\u0026lt;\u0026lt;1.\n接着,又有一个问题:随着程序的执行,b 为什么会变成 0?我们看看 b 是如何更新的,\n1 2 3 sum = a^b; b = (a\u0026amp;b) \u0026lt;\u0026lt; 1; // b is updated a = sum; 假设a\u0026amp;b = 1010 0111,那么 b 的值就会更新为:1010 0111 =\u0026gt; 0100 1110,b 的最低位每次都会引入一个 0,而最高位被丢弃,这样的结果就是 b 中的 1 越来越少,0 越来越多,最终一定会变成 0. 而每次 b 中削减的值,全部间接通过 sum 被加到 a 中去了。如此以来,当 b 为 0 的时候,此时的 a 已经加到两者之和了。可以想象有两堆小球,每次迭代都将 b 这一堆的某些球挪到 a 中去,那么当 b 被挪完的时候,a 中就有了全部的小球。\n还有一个问题,目前我还没有解决。上述答案贴到 LeetCode 里面是不通过的,因为接受的参数是 int 型的,可以为负,而将负数左移是未定义的行为。来日有时间再看看\u0026hellip;\n小结 位运算牛逼!无奈本人没文化,只能喊出这样的口号了。另外在做题的过程中,搜到了 CSDN 上的一篇文章,总结了很多位运算的技巧:here.\nReferences 原题讨论帖 一人跟帖解释 又 ","permalink":"https://guyueshui.github.io/post/leetcode-sum-of-two-integers/","tags":["cpp","leetcode","位运算"],"title":"LeetCode: Sum of Two Integers"},{"categories":["Notes"],"contents":"In editing\u0026hellip;\nLogistic 回归属于分类模型!!!\n从最小二乘说起 线性回归 概率解释 Sigmoid 函数的引入 如果把我比作一张白纸,在我的知识储备中,现在只有线性回归。但是要处理分类问题,我该怎么办?没办法,先考虑一个二分类问题,$y \\in {0,1}$,我们准备霸王硬上弓,用回归模型套上去! $$ y = h_{\\theta}(x) $$\n至少我们希望$h_{\\theta}(x) \\in (0,1)$,就那么刚刚好,有一族函数,这里我们特指其中一个 $$ g(z) = \\frac{1}{1+e^{-z}} \\in (0,1) $$\n请记住它的名字,它就是大名鼎鼎的 sigmoid 函数。可以的话,请再记住它两个迷人的性质:\n$g\u0026rsquo;(t) = g(t)(1-g(t))$ $1 - g(t) = g(-t)$ Logistic 回归 现在,我们模型的假设是\n$$ \\begin{split} y \u0026amp;= h_{\\mathbf{\\theta}}(\\mathrm{x}) = g(\\mathbf{\\theta}^T \\mathrm{x}) \\ \u0026amp;= \\frac{1}{1+\\exp({-\\theta^T \\mathrm{x}})} = g(z) \\end{split} $$\n我们希望通过训练改变 $\\theta$ 的值,进一步改善我们的模型。现在,我们打算换一个角度来看待这个问题,因为$g(\\theta^T x) \\in (0,1)$,正好可以表示一个概率,而之前我们看到,最小二乘实际上等价于,我对数据有一些假设(高斯白噪声),在这些假设下,做参数$\\theta$的极大似然估计 (MLE). 基于这个想法,我们假设,\n$$ \\begin{split} P(y=1|x;\\theta) \u0026amp;= h_{\\theta}(x) \\newline P(y=0|x;\\theta) \u0026amp;= 1 - h_{\\theta}(x) \\end{split} $$\n然后就那么刚刚好,回忆一下 sigmoid 函数有哪些迷人的性质,你会发现下面的式子也是对的\n$$ p(y|x;\\theta) = [h_{\\theta}(x)]^y [1-h_{\\theta}(x)]^{1-y} $$\n再假设 m 个样本独立同分布,我们得到似然函数 $$ \\begin{split} L(\\theta) \u0026amp;= p(\\mathbf{y} | \\mathbf{X}; \\mathrm{\\theta}) \\ \u0026amp;= \\prod_{i=1}^m p(y_i | x_i ; \\theta) \\end{split} $$\n进一步,得到对数似然 $$ \\begin{split} l(\\theta) \u0026amp;= \\log L(\\theta) \\ \u0026amp;=\\sum_{i=1}^m \\left[ y_i\\log h(x_i) + (1-y_i)\\log(1-h(x_i)) \\right] \\end{split} $$\n现在,我们基于 MLE 的方法,来调整参数 $\\theta$ 的值,使得对数似然函数最大。很自然的,我们可以使用梯度上升的方法,更新规则为 $$ \\theta = \\theta + \\alpha \\nabla_{\\theta} l(\\theta) $$\n注意梯度上升是沿着正梯度方向更新。给定一个训练样本 $(x,y)$, 其梯度为\n$$ \\begin{split} \\frac{\\partial}{\\partial \\theta_j} l(\\theta) \u0026amp;= \\left(\\frac{y}{g(z)} - \\frac{1-y}{1-g(z)} \\right) \\frac{\\partial g(z)}{\\partial z} \\frac{\\partial z}{\\partial \\theta_j} \\newline \u0026amp;= (y-g(z)) \\frac{\\partial z}{\\partial \\theta_j} \\newline \u0026amp;= (y-h_{\\theta}(x))x_j \\end{split} $$\n迭代使得似然函数最大化,完成训练。最后应该输出一个 0~1 之间的概率值。我们可以人为设定一个阈值(如 0.5),当输出概率大于 0.5,判定$y=1$,反之亦然。如此一来,就完成了回归到分类的转化。\n另外,上述 sigmoid 函数又叫 logistic 函数,故名。Logistic 回归事实上是一个分类器!!!\n扩展为 softmax ","permalink":"https://guyueshui.github.io/post/%E6%B5%85%E8%B0%88-logistic-%E5%9B%9E%E5%BD%92/","tags":["math","机器学习"],"title":"浅谈 Logistic 回归"},{"categories":["tech"],"contents":"字体设置 在导言区引入fontspec包:\\usepackage{fontspec}\n使用如下命令自定义字体:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 % 西文默认字体,排版主字体 \\setmainfont{} % 西文无称线字体 \\setsansfont{} % 西文等宽字体 \\setmonofont{} % 数学公式字体 \\setmathfont{} % 中文主字体 \\setCJKmainfont[ Path = fonts/zh_cn/ , BoldFont = HYQiHei-70S.ttf , ItalicFont = HYKaiTiS.ttf , SmallCapsFont = HYQiHei-70S.ttf ]{HYQiHei-45S.ttf} Note:\n使用fc-list查看系统字体 使用fc-list: lang=zh查看中文字体 直接使用字体文件\nLaTeX 可以直接使用未安装的字体文件进行排版,但要指定字体文件的位置等信息。\n1 2 3 4 5 6 7 8 \\setCJKmainfont[ Path = ./fonts/zh_cn/ , % specify the file location Extension = .ttf , % specify the file suffix [opt] % or add them manually BoldFont = HYQiHei-70S.ttf, % \u0026#39;.ttf\u0026#39; can be dropped if % \u0026#39;Extension\u0026#39; is specified ItalicFont = HYKaiTiS.ttf, % similarly ]{HYQiHei-45S.ttf} % set default font to 汉仪旗黑 从上面两张图可以看出,中文字体不像西文,是没有对应的斜体和粗体的。所以只能用其他字体替代,通过改变字体的方式达到伪斜体,伪粗体的效果。\n设置字体别名\n1 2 3 4 5 6 7 8 9 10 11 12 13 \\setCJKfamilyfont{zhsong}{HYZhongSongS} \\setCJKfamilyfont{zhhei}{WenQuanYi Micro Hei} %\\setCJKfamilyfont{zhfs}{Adobe Fangsong Std} %\\setCJKfamilyfont{zhkai}{Adobe Kaiti Std} %\\setCJKfamilyfont{zhli}{LiSu} %\\setCJKfamilyfont{zhyou}{YouYuan} \\newcommand*{\\songti}{\\CJKfamily{zhsong}} % 宋体 \\newcommand*{\\heiti}{\\CJKfamily{zhhei}} % 黑体 %\\newcommand*{\\kaishu}{\\CJKfamily{zhkai}} % 楷书 %\\newcommand*{\\fangsong}{\\CJKfamily{zhfs}} % 仿宋 %\\newcommand*{\\lishu}{\\CJKfamily{zhli}} % 隶书 %\\newcommand*{\\youyuan}{\\CJKfamily{zhyou}} % 幼圆 以上为对应的字体设定了我们较为习惯的别名,使用方式如下:\n1 2 {\\zhsong 这段文字使用宋体字。} {\\zhhei 这段使用黑体。} LaTeX 字体样式相关命令 字体大小\nCOMMAND SIZE \\tiny 5pt \\scriptsize 7pt \\footnotesize 8pt \\small 9pt \\normalsize 10pt \\large 12pt \\Large 14pt \\LARGE 18pt \\huge 20pt \\Huge 24pt 字体样式\nCOMMAND STYLE \\textbf 粗体 \\textit 斜体 \\textsl slanted 斜体 \\textsc 小体大写文本 \\underline 下划线 \\texttt 打字机字族,调用\\setmonofont{}所设置的字体 \\textsf 无称线字族,调用\\setsansfont{}所设置的字体 \\textrm 罗马字族,调用\\setmainfont{}所设置的字体 扩展包 ulem 宏包\n在导言区引入:\\usepackage{ulem}\nCOMMAND STYLE \\uline 下划线 \\uuline 双下划线 \\uwave 波浪线 \\sout 删除线 \\xout 斜删除线 基础排版 行距 首先,行距就是相邻两行文字之间的距离。在下面的图片中,两个下划线之间的长度就叫行距。行距的调节一般使用倍数,比如两倍行距。而“单倍行距”又根据字体、字号、软件的不同而改变(不同软件中有不同的定义,没有一个通用的值)。在 LaTeX 里面也有这些概念,在你定义字号的时候,“单倍行距”也随之确定。更改时,我们更改的是“单倍行距”的倍数。\n在导言区使用\\linespread{2.0}可设置 2 倍行距。\n缩进 \\noindent:取消当前段的首行缩进 \\indent:强制首行缩进 对齐 1 2 3 4 5 6 7 8 9 10 11 12 13 14 % oneline \\leftline{左对齐} \\centerline{居中对齐} \\rightline{右对齐} % multiline \\flushleft{左对齐} \\center{居中对齐} \\flushright{右对齐} % or \\begin{flushright/center/flushleft} some text... \\end{flushright/center/flushleft} 页面版式 页面版式包括页眉页脚,以下两个命令可以设置页面版式:\n\\pagestyle:设置当前页及后续页版式 \\thispagestyle:设置当前页的版式 可选版式包括\nempty:无页眉页脚 plain:无页眉,页脚为居中页码 headings:页眉为章节标题,无页脚 myheadings:自定义页眉,无页脚 使用\\thispagestyle{empty}可将当前页的页眉页脚关闭。\nReferences LaTeX 系列笔记 (5)-行距 ","permalink":"https://guyueshui.github.io/post/%E8%B0%88%E8%B0%88-latex-%E7%9A%84%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AD%97%E4%BD%93/","tags":["排版","latex","字体"],"title":"快速自定义 LaTeX 排版字体"},{"categories":["Linux"],"contents":"本文主要引用 Liam Huang 的博客。\n系统相关 1 2 3 4 5 6 7 8 9 10 lsb_release -a # 查看操作系统版本 head -n 1 /etc/issue # 查看操作系统版本 cat /proc/version # 查看操作系统内核信息 uname -a # 查看操作系统内核信息、CPU 信息 cat /proc/cpuinfo # 查看 CPU 信息 hostname # 查看计算机名字 env # 列出环境变量 lsmod # 列出加载的内核模块 uptime # 查看系统运行时间、负载、用户数量 cat /proc/loadavg # 查看系统负载 内存外存 1 2 3 4 5 6 7 8 9 10 free -m # 查看物理内存和交换区的使用情况 grep MemTotal /proc/meminfo # 查看内存总量 grep MemFree /proc/meminfo # 查看空闲内存总量 df -h # 查看各分区使用情况 fdisk -l # 查看所有分区 swapon -s # 查看所有交换分区 hdparm -i /dev/hda # 查看 IDE 磁盘的参数 dmesg | grep IDE # 查看系统启动时 IDE 磁盘的状态 mount | column -t # 查看各分区的挂载状态 du -sh \u0026lt;目录名\u0026gt; # 查看指定目录的大小 网络状态 1 2 3 4 5 6 7 ifconfig # 查看所有网络接口的属性 ip addr show # 同上 iptables -L # 查看 iptables 防火墙 route -n # 查看本机路由表 netstat -lntp # 查看所有监听端口 netstat -antp # 查看所有已建立的连接 netstat -s # 查看网络统计信息 用户状态相关 1 2 3 4 5 6 7 w # 查看活动用户以及他们在做什么 who # 查看活动用户 id \u0026lt;用户名\u0026gt; # 查看用户的 ID、组信息 cut -d: -f1 /etc/passwd # 查看系统中所有用户 cut -d: -f1 /etc/group # 查看系统所有组 usermod -a -G group1,group2 user # 将用户追加到组 groups user # 查看用户所属的组 参考:https://linux.cn/article-10768-1.html\n进程状态相关 1 2 3 ps -ef # 查看所有进程 ps aux # 同上 top # 动态显示进程状态 添加用户到组 1 usermod -aG \u0026lt;group\u0026gt; \u0026lt;user\u0026gt; 查看文件夹内所有文件\n1 ls -lR somedir | grep \u0026#34;^-\u0026#34; | wc -l ","permalink":"https://guyueshui.github.io/post/linux-%E7%9B%B8%E5%85%B3%E4%BF%A1%E6%81%AF%E9%80%9F%E6%9F%A5/","tags":["常用命令","reference"],"title":"Linux 使用指北"},{"categories":["文学"],"contents":"太史公牛马走司马迁,再拜言。\n少卿足下:曩者辱赐书,教以慎于接物,推贤进士为务,意气勤勤恳恳。若望仆不相师,而用流俗人之言,仆非敢如此也。仆虽罢驽,亦尝侧闻长者之遗风矣。顾自以为身残处秽,动而见尤,欲益反损,是以独郁悒而无谁语。谚曰:“谁为为之?孰令听之?”盖钟子期死,伯牙终身不复鼓琴。何则?士为知己者用,女为说己者容。若仆大质已亏缺矣,虽材怀随和,行若由夷,终不可以为荣,适足以发笑而自点耳。\n书辞宜答,会东从上来,又迫贱事,相见日浅,卒卒无须臾之间,得竭指意。今少卿抱不测之罪,涉旬月,迫季冬,仆又薄从上雍,恐卒然不可为讳,是仆终已不得舒愤懑以晓左右,则长逝者魂魄私恨无穷。请略陈固陋。阙然久不报,幸勿为过。\n仆闻之:修身者,智之符也;爱施者,仁之端也;取予者,义之表也;耻辱者,勇之决也;立名者,行之极也。士有此五者,然后可以托于世,列于君子之林矣。故祸莫憯于欲利,悲莫痛于伤心,行莫丑于辱先,诟莫大于宫刑。刑余之人,无所比数,非一世也,所从来远矣。昔卫灵公与雍渠同载,孔子适陈;商鞅因景监见,赵良寒心;同子参乘,袁丝变色:自古而耻之!夫以中材之人,事有关于宦竖,莫不伤气,而况于慷慨之士乎!如今朝廷虽乏人,奈何令刀锯之余,荐天下之豪俊哉!仆赖先人绪业,得待罪辇毂下,二十余年矣。所以自惟:上之,不能纳忠效信,有奇策材力之誉,自结明主;次之,又不能拾遗补阙,招贤进能,显岩穴之士;外之,不能备行伍,攻城野战,有斩将搴旗之功;下之,不能积日累劳,取尊官厚禄,以为宗族交游光宠。四者无一遂,苟合取容,无所短长之效,可见于此矣。乡者,仆亦尝厕下大夫之列,陪外廷末议。不以此时引维纲,尽思虑,今已亏形为扫除之隶,在阘茸之中,乃欲仰首伸眉,论列是非,不亦轻朝廷、羞当世之士邪?嗟乎!嗟乎!如仆尚何言哉!尚何言哉!\n且事本末未易明也。仆少负不羁之才,长无乡曲之誉,主上幸以先人之故,使得奉薄伎,出入周卫之中。仆以为戴盆何以望天,故绝宾客之知,忘室家之业,日夜思竭其不肖之材力,务一心营职,以求亲媚于主上。而事乃有大谬不然者!\n夫仆与李陵俱居门下,素非能相善也。趣舍异路,未尝衔杯酒,接殷勤之余欢。然仆观其为人,自守奇士,事亲孝,与士信,临财廉,取予义,分别有让,恭俭下人,常思奋不顾身,以徇国家之急。其素所蓄积也,仆以为有国士之风。夫人臣出万死不顾一生之计,赴公家之难,斯已奇矣。今举事一不当,而全躯保妻子之臣随而媒孽其短,仆诚私心痛之。且李陵提步卒不满五千,深践戎马之地,足历王庭,垂饵虎口,横挑强胡,仰亿万之师,与单于连战十有余日,所杀过当。虏救死扶伤不给,旃裘之君长咸震怖,乃悉征其左、右贤王,举引弓之民,一国共攻而围之。转斗千里,矢尽道穷,救兵不至,士卒死伤如积。然陵一呼劳军,士无不起,躬自流涕,沬血饮泣,更张空弮,冒白刃,北首争死敌者。陵未没时,使有来报,汉公卿王侯皆奉觞上寿。后数日,陵败书闻,主上为之食不甘味,听朝不怡。大臣忧惧,不知所出。仆窃不自料其卑贱,见主上惨凄怛悼,诚欲效其款款之愚,以为李陵素与士大夫绝甘分少,能得人之死力,虽古之名将,不能过也。身虽陷败,彼观其意,且欲得其当而报于汉。事已无可奈何,其所摧败,功亦足以暴于天下矣。仆怀欲陈之,而未有路,适会召问,即以此指,推言陵之功,欲以广主上之意,塞睚眦之辞。未能尽明,明主不晓,以为仆沮贰师,而为李陵游说,遂下于理。拳拳之忠,终不能自列。因为诬上,卒从吏议。家贫,货赂不足以自赎,交游莫救,左右亲近不为一言。身非木石,独与法吏为伍,深幽囹圄之中,谁可告愬者!此真少卿所亲见,仆行事岂不然乎?李陵既生降,隤其家声,而仆又佴之蚕室,重为天下观笑。悲夫!悲夫!事未易一二为俗人言也。\n仆之先非有剖符丹书之功,文史星历,近乎卜祝之间,固主上所戏弄,倡优所畜,流俗之所轻也。假令仆伏法受诛,若九牛亡一毛,与蝼蚁何以异?而世又不与能死节者比,特以为智穷罪极,不能自免,卒就死耳。何也?素所自树立使然也。人固有一死,或重于泰山,或轻于鸿毛,用之所趋异也。太上不辱先,其次不辱身,其次不辱理色,其次不辱辞令,其次诎体受辱,其次易服受辱,其次关木索、被箠楚受辱,其次剔毛发、婴金铁受辱,其次毁肌肤、断肢体受辱,最下腐刑极矣!传曰“刑不上大夫。”此言士节不可不勉厉也。猛虎在深山,百兽震恐,及在槛阱之中,摇尾而求食,积威约之渐也。故士有画地为牢,势不可入;削木为吏,议不可对,定计于鲜也。今交手足,受木索,暴肌肤,受榜箠,幽于圜墙之中。当此之时,见狱吏则头抢地,视徒隶则心惕息。何者?积威约之势也。及以至是,言不辱者,所谓强颜耳,曷足贵乎!且西伯,伯也,拘于羑里;李斯,相也,具于五刑;淮阴,王也,受械于陈;彭越、张敖,南面称孤,系狱抵罪;绛侯诛诸吕,权倾五伯,囚于请室;魏其,大将也,衣赭衣,关三木;季布为朱家钳奴;灌夫受辱于居室。此人皆身至王侯将相,声闻邻国,及罪至罔加,不能引决自裁,在尘埃之中。古今一体,安在其不辱也?由此言之,勇怯,势也;强弱,形也。审矣,何足怪乎?夫人不能早自裁绳墨之外,以稍陵迟,至于鞭箠之间,乃欲引节,斯不亦远乎!古人所以重施刑于大夫者,殆为此也。\n夫人情莫不贪生恶死,念父母,顾妻子,至激于义理者不然,乃有所不得已也。今仆不幸,早失父母,无兄弟之亲,独身孤立,少卿视仆于妻子何如哉?且勇者不必死节,怯夫慕义,何处不勉焉!仆虽怯懦,欲苟活,亦颇识去就之分矣,何至自沉溺缧绁之辱哉!且夫臧获婢妾,犹能引决,况仆之不得已乎?所以隐忍苟活,幽于粪土之中而不辞者,恨私心有所不尽,鄙陋没世,而文采不表于后也。\n古者富贵而名摩灭,不可胜记,唯倜傥非常之人称焉。盖文王拘而演《周易》;仲尼厄而作《春秋》;屈原放逐,乃赋《离骚》;左丘失明,厥有《国语》;孙子膑脚,《兵法》修列;不韦迁蜀,世传《吕览》;韩非囚秦,《说难》《孤愤》;《诗》三百篇,大底圣贤发愤之所为作也。此人皆意有所郁结,不得通其道,故述往事、思来者。乃如左丘无目,孙子断足,终不可用,退而论书策,以舒其愤,思垂空文以自见。\n仆窃不逊,近自托于无能之辞,网罗天下放失旧闻,略考其行事,综其终始,稽其成败兴坏之纪,上计轩辕,下至于兹,为十表,本纪十二,书八章,世家三十,列传七十,凡百三十篇。亦欲以究天人之际,通古今之变,成一家之言。草创未就,会遭此祸,惜其不成,是以就极刑而无愠色。仆诚以著此书,藏之名山,传之其人,通邑大都,则仆偿前辱之责,虽万被戮,岂有悔哉!然此可为智者道,难为俗人言也!\n且负下未易居,下流多谤议。仆以口语遇遭此祸,重为乡党所笑,以污辱先人,亦何面目复上父母之丘墓乎?虽累百世,垢弥甚耳!是以肠一日而九回,居则忽忽若有所亡,出则不知其所往。每念斯耻,汗未尝不发背沾衣也!身直为闺阁之臣,宁得自引深藏于岩穴邪?故且从俗浮沉,与时俯仰,以通其狂惑。今少卿乃教以推贤进士,无乃与仆私心剌谬乎?今虽欲自雕琢,曼辞以自饰,无益,于俗不信,适足取辱耳。要之,死日然后是非乃定。书不能悉意,故略陈固陋。谨再拜。\n","permalink":"https://guyueshui.github.io/post/%E8%B0%88%E8%B0%88%E6%8A%A5%E4%BB%BB%E5%AE%89%E4%B9%A6/","tags":null,"title":"《报任安书》"},{"categories":null,"contents":"当我们考察一门语言时,主要看三点\nprimitives:元操作是什么 means of combinations:如何组合 means of abstraction:如何抽象,构造更复杂的程序 数据和过程之间没有本质的区别\n在写完构造器(constrcutor)之后,记得写上选择器(selector)。\n+---------+ rule +--------+ | pattern +--------------------\u0026gt;+skeleton| +----+----+ +----+---+ | | | | |match |instantiation | | v v +----+-----+ +----+-----+ |expression| |expression| | source +-------------------\u0026gt;+ target | +----------+ +----------+ pattern match\nfoo \u0026ndash; matches exactly foo (f a b) \u0026ndash; matches a list, whose first element is f, etc. (? x) \u0026ndash; matches anything, call it x (?c x) \u0026ndash; matches a constant, call it x (?v x) \u0026ndash; matches a variable, call it x\nskeletons\nfoo \u0026ndash; instantiates to itself (f a b) \u0026ndash; instantiates to a 3-list, results of instatiating each of f, a, b (: x) \u0026ndash; instatantiates to the value of x in the pattern matched\n任何一个复杂的大程序都是由简单的小部分组成。纵然递归模式非常复杂,最重要的事情就是:不去思考它。如果取思考它的实际行为,大家就会迷惑。\nConvince yourself something is right.\n好的编程或设计方法就是你知道什么不用去思考!\nWishful thinking is crucial!\nLisp 没有循环,靠递归的 procedure 实现迭代,这并不表示 procedure 展开的 process 也是递归的。区分递归和迭代的核心是是否需要辅助空间跟踪程序运行的状态。\nCase analysis is more powerful than you thought! Trust me.\n","permalink":"https://guyueshui.github.io/post/sicp-learning-notes/","tags":null,"title":"SICP Learning Notes"},{"categories":null,"contents":" __ __ __ __ ____ ___ /\\ \\ /\\ \\ /\\ \\ __ /\\ \\ /\\ _`\\ /\\_ \\ \\ `\\`\\\\/\u0026#39;/__ __ ___\\ \\ \\___ /\\_\\\\ \\/ ____ \\ \\ \\L\\ \\//\\ \\ ___ __ `\\ `\\ /\u0026#39;/\\ \\/\\ \\ /\u0026#39;___\\ \\ _ `\\/\\ \\\\/ /\u0026#39;,__\\ \\ \\ _ \u0026lt;\u0026#39;\\ \\ \\ / __`\\ /\u0026#39;_ `\\ `\\ \\ \\\\ \\ \\_\\ \\/\\ \\__/\\ \\ \\ \\ \\ \\ \\ /\\__, `\\ \\ \\ \\L\\ \\\\_\\ \\_/\\ \\L\\ \\/\\ \\L\\ \\ \\ \\_\\\\/`____ \\ \\____\\\\ \\_\\ \\_\\ \\_\\ \\/\\____/ \\ \\____//\\____\\ \\____/\\ \\____ \\ \\/_/ `/___/\u0026gt; \\/____/ \\/_/\\/_/\\/_/ \\/___/ \\/___/ \\/____/\\/___/ \\/___L\\ \\ /\\___/ /\\____/ \\/__/ \\_/__/ \u0026#39;` 此页内容多为软件使用技巧,绝大部分内容来自互联网,如有侵权,请与我联系。也有部分内容系自己使用软件所得的一些经验,仅供参考。\nFFmepg 转视频为 gif 1 ffmpeg -i input.mkv out.gif 又:加速播放。假设原视频 60 fps,输出降到 30 fps,丢掉一半的帧。\n1 ffmpeg -r 60 -i input.mkv -r 30 out.gif 又:不想丢帧。将输入扩大一倍,输出保留原样。\n1 ffmpeg -r 120 -i input.mkv -r 60 out.gif 又:不想全转。从视频的第 2 秒开始,截取 3 秒转化为 gif。\n1 2 ## 从视频中第二秒开始,截取时长为 3 秒的片段转化为 gif ffmpeg -t 3 -ss 00:00:02 -i input.mkv out-clip.gif 又:控制转化质量。\n1 2 ## 默认转化是中等质量模式,若要转化出高质量的 gif,可以修改比特率 ffmpeg -i input.mkv -b 2048k out.gif VOB 转 MP4 cf. http://www.ruhuamtv.com/thread-9782-1-1.html\n1 ffmpeg -i 源视频.vob -c:v libx264 -vf yadif -crf 18 目标视频.mp4 又:合并 VOB 文件。\n1 cat VTS_01_1.VOB VTS_01_2.VOB | ffmpeg -y -i - -fflags genpts -vcodec copy -acodec copy ../output.VOB 又:合并 mp4 文件。 see: https://www.tais3.com/others/983.html\n1 2 3 4 5 6 7 8 9 10 #! /bin/bash # 将 mp4 文件封装为 ts 格式 ffmpeg -i a1.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 1.ts ffmpeg -i a2.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 2.ts ffmpeg -i a3.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 3.ts ffmpeg -i a4.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 4.ts # 拼接 ts 并导出最终 mp4 文件 ffmpeg -i \u0026#34;concat:1.ts|2.ts|3.ts|4.ts\u0026#34; -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4 # 删除过程中生成的 ts 文件 rm *.ts 参考:FFMPEG 合并视频文件(无损)\nTS 转 MP4 1 ffmpeg -y -i your_file.ts -vcodec copy -acodec copy -map 0:v -map 0:a your_file.mp4 cf.\nhttps://www.reddit.com/r/ffmpeg/comments/ggmjep/comment/fq2m1ux/?utm_source=share\u0026amp;utm_medium=web2x\u0026amp;context=3 https://askubuntu.com/a/716457 分辨率、码率、帧率相关介绍,及相关压缩方法:\nhttps://blog.csdn.net/weixin_30536861/article/details/114496746 https://zhuanlan.zhihu.com/p/255042580 写给新手的指令:https://gnu-linux.readthedocs.io/zh/latest/Chapter04/00_ffmpeg.basic.html\nImageMagick 图片转换 转换图片为指定分辨率。\n1 convert -resize 1920x1080 src.jpg dst.jpg 又:按百分比转换大小。\n1 convert -resize 50%x50% src.jpg dst.jpg 又:忽略原始宽高比。\n1 convert -resize 300x300! src.jpg dst.jpg 又:多张图片合成 gif。\n1 2 magick DSC_52{01..09}.JPG out.gif magick -delay 10 *.jpg out.gif # 指定每张持续 10ms 又:更改分辨率。\n1 mogrify -resize 50%x50% *.jpg # 所有 jpg 缩放至 50%,不加百分号默认单位 px(像素) 又,裁切 gif。\n1 2 # magick c.gif -coalesce -repage 0x0 -crop 600x600+175+0 +repage output.gif 1 convert input.gif -coalesce -repage 0x0 -crop WxH+X+Y +repage output.gif Animated gifs are often optimised to save space, but imagemagick doesn\u0026rsquo;t seem to consider this when applying the crop command and treats each frame individually. -coalesce rebuilds the full frames. Other commands will take into consideration the offset information supplied in the original gif, so you need to force that to be reset with -repage 0x0. The crop itself is straightforward, with width, height, x offset and y offset supplied respectively. For example, a crop 40 wide and 30 high at an x offset of 50 = 40x30+50+0. Crop does not remove the canvas that it snipped from the image. Applying +repage after the crop will do this. cf. https://stackoverflow.com/a/14036766\nShell 行内操作 ^a: jump to BOL ^e: jump to EOL ^u: delete the line ^k: delete to EOL ^w: delete a word forward alt+f: jump a word forward alt+b: jump a word backward ^r: search history alt+.: complete second parameter 任务控制 执行command 按^z挂起当前 job 按bg后台继续该 job 按fg召回前台 后台运行命令 1 command \u0026amp; 或者如果你不想看到任何输出,使用\n1 command \u0026amp;\u0026gt; /dev/null \u0026amp; 如此你可以继续使用当前 shell 使用bg查看是否有任务在后台运行 使用jobs查看后台任务 使用fg将任务召回前台 不能退出 shell,否则进程会被杀掉 使用disown丢掉进程,可以退出 shell 又:\n1 nohup command \u0026amp;\u0026gt; /dev/null \u0026amp; 等价于以上的操作。单纯的nohup command会在当前目录创建一个隐藏文件以写入命令的输出。以上命令将程序的输出重定向至比特桶丢弃。\n同时输出到 console 和文件 将命令输出重定向到文件:\n1 2 SomeCommand \u0026gt; SomeFile.txt # overwrite SomeCommand \u0026gt;\u0026gt; SomeFile.txt # append 将命令输出 (stdout) 及报错 (stderr) 重定向到文件:\n1 2 SomeCommand \u0026amp;\u0026gt; SomeFile.txt SomeCommand \u0026amp;\u0026gt;\u0026gt; SomeFile.txt 同时输出到 console 和文件:\n1 2 SomeCommand 2\u0026gt;\u0026amp;1 | tee SomeFile.txt # overwrite SomeCommand 2\u0026gt;\u0026amp;1 | tee -a SomeFile.txt # append || visible in terminal || visible in file || existing Syntax || StdOut | StdErr || StdOut | StdErr || file ==========++==========+==========++==========+==========++=========== \u0026gt; || no | yes || yes | no || overwrite \u0026gt;\u0026gt; || no | yes || yes | no || append || | || | || 2\u0026gt; || yes | no || no | yes || overwrite 2\u0026gt;\u0026gt; || yes | no || no | yes || append || | || | || \u0026amp;\u0026gt; || no | no || yes | yes || overwrite \u0026amp;\u0026gt;\u0026gt; || no | no || yes | yes || append || | || | || | tee || yes | yes || yes | no || overwrite | tee -a || yes | yes || yes | no || append || | || | || n.e. (*) || yes | yes || no | yes || overwrite n.e. (*) || yes | yes || no | yes || append || | || | || |\u0026amp; tee || yes | yes || yes | yes || overwrite |\u0026amp; tee -a || yes | yes || yes | yes || append Ref:\nhttps://askubuntu.com/questions/420981/how-do-i-save-terminal-output-to-a-file https://www.gnu.org/software/bash/manual/bash.html#Redirections 从系统中踢出某个用户 1 2 3 4 5 6 7 8 9 10 11 # See the pid of the user\u0026#39;s login process. $ who -u yychi tty1 2020-02-19 21:06 旧 460 # Let him know he will be kick off. $ echo \u0026#34;You\u0026#39;ll be kick off by system administrator.\u0026#34; | write yychi # Kick off. $ kill -9 460 # Done. Ref: https://www.putorius.net/kick-user-off-linux-system.html\nCommand find 1 2 $ find --help Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression] 前面的选项不常用,初级使用只需掌握\n1 find [path...] [expression] path - search path expression - expands to -options [-print -exec -ok] -options: 指定 find 常用选项 -print: 将匹配到的文件写入标准输出 [默认] -exec: 在匹配到的文件上执行一串命令。格式为\u0026lt;command\u0026gt; {} \\;,注意 {} 和 ; 之间的空格。 find . -size 0 -exec rm {} \\; - 删除当前目录下 size 为 0 的文件 rm -i $(find . -size 0) - 同上 find . -size 0 | xargs rm -f \u0026amp; - 同上 -ok: 同上,执行命令前会询问 常用选项\nname - 按照文件名查找 find \u0026lt;dir\u0026gt; -name \u0026quot;*.cpp\u0026quot;: 在目录 dir 下查找后缀为 cpp 的文件 -name默认不支持正则表达式,顶多支持通配符* perm - 按照文件权限查找 user - 按照文件所有者查找 group - 按照文件所有组查找 type - 按照文件类型查找 size - 按照文件大小查找 \u0026hellip; 正则表达式\n1 find path -regex \u0026#34;\u0026lt;regex\u0026gt;\u0026#34; 但是默认的正则表达式引擎我也不知道是啥,反正不解析我习惯的那种正则语法。故使用:\n1 find . -regextype posix-extended -regex \u0026#34;.*\\.(log|aux|blg)\u0026#34; 上述命令找出当前文件夹及子文件夹所有后缀名为log,aux,blg的文件。\n几个例子\nfind . -name \u0026quot;*name*\u0026quot; - 找出当前文件夹文件名包含“name”的文件 find . ! -type d -print - 在当前目录查找非目录文件 find . -newer file1 ! file2 - 查找比 file1 新但比 file2 旧的文件 find -type d -empty | xargs -n 1 rmdir - 批量删除当前目录下的空文件夹 find -tyle l -exec ls -l {} + - 找出当前文件夹下损坏的软连接 Command grep 最基本用法:\n1 2 3 4 5 6 7 8 9 10 11 # 查找 somefile 中匹配到 something 的行 $ grep \u0026#34;something\u0026#34; somefile # 定位 something 所在的行并将接下来的 3 行一并输出 $ grep \u0026#34;something\u0026#34; somefile -A 3 # 定位 something 所在的行并将之前的 3 行一并输出 $ grep \u0026#34;something\u0026#34; somefile -B 3 # 定位 something 所在的行并将上下 3 行一并输出 $ grep \u0026#34;something\u0026#34; somefile -C 3 使用正则表达式\ngrep支持三种正则:basic (BRE), extend (ERE), perl (PCRE). 不同的grep实现方式不同,详见手册。一般 extend 最为常用,语法为\n1 2 # 在 somefile 中查找包含 his 或者 her 的行 $ grep -E \u0026#34;his|her\u0026#34; somefile Ref:\ngrep 命令系列:grep 中的正则表达式 Command xargs 1 2 $ whatis xargs xargs (1) - build and execute command lines from standard input Ref:\nxargs 命令:一个给其他命令传递参数的过滤器 Command sed Command cut 1 2 $ whatis cut cut (1) - remove sections from each line of files 基本用法:\n1 2 3 4 5 6 7 8 9 10 # 以:为分隔符分割每行,并选择第 1,2,4 列输出 $ cut -d: -f1,2,4 /etc/passwd root:x:0 bin:x:1 daemon:x:2 mail:x:12 ftp:x:11 http:x:33 nobody:x:65534 dbus:x:81 Git 创建别名\n使用 git log 查看提交历史,但是输出冗杂。通常使用\n1 git log --oneline --abbrev-commit --all --graph --decoreate --color 来获得更美观易读的输出。但是每次输入这么多肯定很烦人,使用\n1 git config --global alias.graph \u0026#34;log --graph --oneline --decorate=short\u0026#34; 增加一个全局别名,这个别名对于任何地方的 git 都适用。如此一来,键入 git graph 会等效于\n1 git log --graph --oneline --decorate=short 样例输出:\n1 2 3 4 5 6 7 8 9 $ git graph * 36f2d65 (HEAD -\u0026gt; master, origin/master, origin/HEAD) Forget it * 9b4a6d7 Update ref list * 3931d4d Using relative path for image * ba18821 Upload pics * ceca69a fixed reference * be15df2 fixed picture address * 97a36f3 Initial commit 忽略已经添加的文件\n1 git rm --cached \u0026lt;somefiles\u0026gt; 推送本地分支到远程\n1 2 # 远程分支如果不存在,则自动创建。 git push origin \u0026lt;local_brach\u0026gt;:\u0026lt;remote_branch\u0026gt; 拉取远程分支到本地\n1 2 3 4 5 # 从远程分支切换(并创建,如果不存在)本地分支 git checkout -b \u0026lt;local_branch\u0026gt; origin/\u0026lt;remote_branch\u0026gt; # 另:取回远程分支并创建对应的本地分支,不换自动切换到该分支 git fetch origin \u0026lt;remote_brach\u0026gt;:\u0026lt;local_branch\u0026gt; 删除 commit 历史\n如果不小心将隐私信息推送至远程仓库(如 github),那么仅仅删除再更新再推送到远程仓库覆盖是不够的,别人还是可以通过你的 commit 历史查到你所做的更改,所以这种情况下必须删除之前所有的 commit history. 大致思路是创建一个孤立分支,然后重新添加文件,再删除 master 分支,将新建的分支重命名为 master,再推送到远程强制覆盖 1。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Check out to a temporary branch: git checkout --orphan TEMP_BRANCH # Add all the files: git add -A # Commit the changes: git commit -am \u0026#34;Initial commit\u0026#34; # Delete the old branch: git branch -D master # Rename the temporary branch to master: git branch -m master # Finally, force update to our repository: git push -f origin master 合并某个文件到当前分支\n例如当前在 master 分支,希望合并某个分支 dev 的某个或多个文件到当前分支:\n1 git checkout dev file1 file2 ... 但是上述做法会强行覆盖当前分支的文件,没有冲突处理,更安全的做法是先从当前分支新建分支 master_temp,然后在 master_temp 中 checkout,最后再将 master_temp 分支 merge 到 master 分支:\n1 2 3 4 5 6 7 8 9 10 11 # Create a branch based on master git checkout -b master_temp # Chechkout file1 from dev to master_temp git checkout dev file1 git commit -m \u0026#34;checkout file1 from dev\u0026#34; # Switch to master and merge, then delete git checkout master git merge master_temp git branch -d master_temp Ref: https://segmentfault.com/a/1190000008360855\nGit merge\n当你觉得很多时候对于一个命令的很多子命令或者选项不是很清晰,而且查了忘,忘了查,那多半是你不理解它的工作机制。或者说它对你来说不是那么自然易懂,这个时候就需要深入以下,了解以下它的基本原理,帮助自己理解,以便记忆。\ngit merge就是如此,你要知道 merge 的含义是什么?它其实就是在被 merge 的分支上重现要 merge 的 commits. 比如说:\na---b---c---d---e (master) \\ `--A---B---C (dev) 你当前在 master 分支的 e 节点,你要 merge dev 分支。其实就是将 A、B、C 三个 commit 在 master 分支上重现,仿佛 master 分支上曾经也做过这些改动。那么冲突的来源就是你在两个分支中,对同一个文件作了不同的改动,如何解决不言而喻。\n小朋友,你是否有很多?\nQ: 我想只重现 B 节点怎么办?\nA: git checkout master \u0026amp;\u0026amp; git cherry-pick 62ecb3,这里62ecb3是节点 B 的 commit 标识。\nQ: 我想重现 A-B,但不要 C 怎么办?\nA: git checkout -b newbranch 62ecb3 \u0026amp;\u0026amp; git rebase --onto master 76cada^,这里76cada是 A 节点的 commit 标识。先基于 B 创建一个分支,这个分支包含了 A 节点的改动,然后 rebase 到 master 上去,结果就是 A 和 B 重现在 master 分支上。\nRef:\nhttps://stackoverflow.com/questions/161813/how-to-resolve-merge-conflicts-in-git Cherry-Picking specific commits from another branch Fork 之后如何同步 fork 源的更新\n1 2 3 4 5 6 # see remote status git remote -v # add upstream if not exist one git remote add upstream https://github.com/\u0026lt;origin_owner\u0026gt;/\u0026lt;origin_repo\u0026gt;.git git remote -v 从上游仓库 fetch 分支和提交点,提交给本地 master,并会被存储在一个本地分支 upstream/master\n1 git fetch upstream 切换到任意分支,merge 已经 fetch 的分支即可:\n1 2 git checkout somebrach git merge upstream/master see: https://www.zhihu.com/question/28676261\nRef:\nConfigureing a remote for a fork Syncing a fork 从另一个分支检出某个文件并重命名\n有时候开了一个孤立分支,但是想参考其他分支的代码,而当前分支又有同名文件,此时就需要从其他分支检出文件并重命名。\n1 2 3 4 5 # show the content of a.cpp in specific commit HEAD^ git show HEAD^:a.cpp # that\u0026#39;s done git show HEAD^:a.cpp \u0026gt; b.cpp Ref: search \u0026ldquo;git-checkout older revision of a file under a new name\u0026rdquo; in stack overflow\n查看已被跟踪的文件\n1 git ls-files Ref: search \u0026ldquo;how can i make git show a list of the files that are being tracked\u0026rdquo; in stack overflow\nsubmodule\ngit submodule 本质上是指向一个其他仓库的链接,默认 clone 不会将 submodule 对应的仓库克隆下来。\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 # help git submodule --help # 添加 submodule # 1. 进入目标子文件夹 git submodule add https://github.com/imtianx/liba.git # 更新 submodule cd xxx git pull git submodule update --recursive # 在主目录下更新 submodule liba git submodule update --remote liba # 删除 submodule vim .gitmodules # 删除相应条目 vim .git/config # 删除相应条目 rm -rf .git/modules/liba # 删除对应的 git 文件夹 # 在克隆时连同 submodule 一并克隆 git clone https://github.com/imtianx/MainProject.git --recursive # is equivalent to git clone https://github.com/imtianx/MainProject.git git submodule init git submodule update 一般地,当某仓库中包含 submodule ./dir1 时,如果你只提交了 dir1 的内容,那么当前仓库是不会用上最新版本的 dir1 的。这在远程仓库中尤为显著。我的博客文件夹 BlogHugo 中包含了 themes/even 的 submodule, 每当我在 even 中改完样式推送到远端后(这里我 BlogHugo 仓库没有任何修改),发现 build 出来的网站压根没有使用最新的 submodule 里面的内容。究其原因,其实是父仓库默认会跟踪 submodule 的一个版本号。如果不在父仓库中显示更新要跟踪的版本号,则父仓库一直会跟踪之前的版本号。这是合理的,因为父子仓库独立开发,为了避免子仓库(submodule)的频繁提交对父仓库的构建产生影响,所以默认会跟踪一个版本号。\n正确的做法是,当 submodule 更新后,父仓库中 submodule 的版本号会产生一个修改,在父仓库中 add-commit 这个修改,就可以更新父仓库中引用的 submodule 版本号。\nRef:\nGit-工具 - 子模块 Git 子模块:git submodule 如何撤销本地 commit\n有时候本地 add 了一写 diff,随手 commit 了,接着又有些 diff 可以共用这个 commit,就想撤销刚刚的 commit,把所有的 diff 合并在一起作为一次 commit。\n1 2 # for more info, type git reset -h git reset --soft \u0026lt;commit_id\u0026gt; 修改已提交的 commit message\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # commit_id 至少比要修改的那个 commit 早一个版本 git rebase -i \u0026lt;commit_id\u0026gt; # 列出 rebase 的 commit 列表,不包含 \u0026lt;commit id\u0026gt; $ git rebase -i \u0026lt;commit id\u0026gt; # 最近 3 条 $ git rebase -i HEAD~3 # 本地仓库没 push 到远程仓库的 commit 信息 $ git rebase -i # vi 下,找到需要修改的 commit 记录,`pick` 修改为 `edit` 或 `e`,`:wq` 保存退出 # 或者较新版本的 `reword` # 重复执行如下命令直到完成 $ git commit --amend --message=\u0026#34;modify message by daodaotest\u0026#34; --author=\u0026#34;jiangliheng \u0026lt;jiang_liheng@163.com\u0026gt;\u0026#34; $ git rebase --continue # 中间也可跳过或退出 rebase 模式 $ git rebase --skip $ git rebase --abort # 如果只是更改 last commit git commit --amend Cf. github doc.\nGit rebase\nCf. https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase\nrebase和merge都是将另一分支的提交(commit)集成到当前分支的方法。而 merge 会保留两条分支的所有 commit,然后解决冲突,然后形成一个 merge commit,从 git log 上来看,原本线性的提交历史分了叉,然后又合了并。而 rebase 则是基于当前分支的某次提交去重现另一个分支,rebase 之后依然能够保留提交历史的线性状态。\na---b---c---d---e (master) \\ `--A---B---C (dev) From a content perspective, rebasing is changing the base of your branch from one commit to another making it appear as if you\u0026rsquo;d created your branch from a different commit. Internally, Git accomplishes this by creating new commits and applying them to the specified base. It\u0026rsquo;s very important to understand that even though the branch looks the same, it\u0026rsquo;s composed of entirely new commits.\nThe primary reason for rebasing is to maintain a linear project history. For example, consi der a situation where the main branch has progressed since you started working on a feature branch. You want to get the latest updates to the main branch in your feature branch, but you want to keep your branch\u0026rsquo;s history clean so it appears as if you\u0026rsquo;ve been working off the latest main branch.\nYou have two options for integrating your feature into the main branch: merging directly or rebasing and then merging. The former option results in a 3-way merge and a merge commit, while the latter results in a fast-forward merge and a perfectly linear history. The following diagram demonstrates how rebasing onto the main branch facilitates a fast-forward merge.\nRebasing is a common way to integrate upstream changes into your local repository. Pulling in upstream changes with Git merge results in a superfluous merge commit every time you want to see how the project has progressed. On the other hand, rebasing is like saying, “I want to base my changes on what everybody has already done.”\n注:写这个的时候,我自己对 rebase 的理解也很模糊。\n任何时候不清楚的时候请终止 rebase:\n1 git rebase --abort 反复操练几次,git 有友好的提示信息。\n撤销上次rebase\n1 2 3 4 5 # 先使用 reflog查看分支变动历史 $ git reflog # 选中rebase前的commit id (hash) $ git reset --hard \u0026lt;commit_id\u0026gt; 参考:此处。\nCommand g++ 自定义包含路径\n1 g++ main.cpp -I/usr/local/include 自定义链接静态或动态库\n1 2 g++ main.cpp -L/path/to/lib_file g++ main.cpp -L/usr/lib64 -lcurl -lssl 上面第二个命令链接了/usr/lib64/目录下的libcurl.so和libssl.so两个动态库文件。静态库也是同样链接。说起来静态库,想起了最近折腾的一个东西,你可能会想把多个静态库合成一个静态库,想当然的直接用ar合并,但是不行,必须要把两个静态库全解压出来,再合并所有的 object file. 参见:here\n生成机器码\n1 g++ main.cpp -c 生成汇编代码\n1 g++ main.cpp -S 仅预编译\n1 g++ main.cpp -E \u0026gt; main.i Aria2c aria2c 是个好东西。支持连接,磁力,种子下载。轻量且强大,可直接使用,也可作为服务端,配合 WebUI 使用。\n配置:参考 aria2 配置示例 WebUI: YAAW ziahamza AriaNg Note: jsonrpc 地址格式为 http://token:\u0026lt;rpc-secret\u0026gt;@hostname:port/jsonrpc 令牌填写自己设置的 rpc-secret xxx 替换为自己设置的 rpc-secret MPV MPV 是一个轻量、简约、跨平台的播放器。据我自己体验,在 Linux 下比 mplayer 播放效果要好,mplayer 倍速会掉帧,而 mpv 则不太明显。\nHTML 给网页添加 BGM。\n1 \u0026lt;embed src=\u0026#34;bgm.mp3\u0026#34; autostart=\u0026#34;true\u0026#34; loop=\u0026#34;true\u0026#34; width=\u0026#34;300\u0026#34; height=\u0026#34;20\u0026#34; hidden=\u0026#34;true\u0026#34;\u0026gt; 又,添加可以控制播放的音乐。\n1 \u0026lt;audio autoplay=\u0026#34;autoplay\u0026#34; controls=\u0026#34;controls\u0026#34; loop=\u0026#34;loop\u0026#34; preload=\u0026#34;auto\u0026#34; src=\u0026#34;music.mp3\u0026#34;\u0026gt;Your browser doesn\u0026#39;t support H5 audio flag!\u0026lt;/audio\u0026gt; 使用xrandr设置多屏显示 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 $ xrandr Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 8192 x 8192 eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 294mm x 165mm 1920x1080 59.93*+ 1680x1050 59.95 59.88 1400x1050 59.98 1600x900 59.99 59.94 59.95 59.82 1280x1024 60.02 1400x900 59.96 59.88 1280x960 60.00 1440x810 60.00 59.97 1368x768 59.88 59.85 1280x800 59.99 59.97 59.81 59.91 1280x720 60.00 59.99 59.86 59.74 1024x768 60.04 60.00 960x720 60.00 928x696 60.05 896x672 60.01 1024x576 59.95 59.96 59.90 59.82 960x600 59.93 60.00 960x540 59.96 59.99 59.63 59.82 800x600 60.00 60.32 56.25 840x525 60.01 59.88 864x486 59.92 59.57 700x525 59.98 800x450 59.95 59.82 640x512 60.02 700x450 59.96 59.88 640x480 60.00 59.94 720x405 59.51 58.99 684x384 59.88 59.85 640x400 59.88 59.98 640x360 59.86 59.83 59.84 59.32 512x384 60.00 512x288 60.00 59.92 480x270 59.63 59.82 400x300 60.32 56.34 432x243 59.92 59.57 320x240 60.05 360x202 59.51 59.13 320x180 59.84 59.32 DP-1 disconnected (normal left inverted right x axis y axis) HDMI-1 disconnected (normal left inverted right x axis y axis) HDMI-2 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 527mm x 296mm 1920x1080 60.00*+ 60.00 50.00 59.94 1920x1080i 60.00 60.00 50.00 59.94 1600x1200 60.00 1280x1024 75.02 60.02 1152x864 75.00 1280x720 60.00 60.00 50.00 59.94 1024x768 75.03 60.00 800x600 75.00 60.32 720x576 50.00 50.00 720x576i 50.00 50.00 720x480 60.00 60.00 59.94 59.94 59.94 720x480i 60.00 60.00 59.94 59.94 640x480 75.00 60.00 59.94 59.94 720x400 70.08 观察输出可知,连接了两个显示器 (eDP-1, HDMI-2),其中 eDP-1 是主显示器。如果第二块屏幕无显示,执行下面的命令。\n1 xrandr --output HDMI-2 又,指定分辨率为 1920x1080,\n1 xrandr --output HDMI-2 --mode 1920x1080 又,设置为右侧扩展屏,即光标向右可移动至第二块屏,\n1 xrandr --output HDMI-2 --right-of eDP-1 又,连接上第二块屏,想关掉内置显示屏,(警告:不要随便关掉内置屏)\n1 xrandr --output eDP-1 --off 又,开启内置屏。\n1 xrandr --output eDP-1 --auto 某些情况下会无法检测显示器的最大分辨率,此时需要手动设置显示器的分辨率。参考此处。\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 获取参数值 $ cvt 2560 1440 # 2560x1440 59.96 Hz (CVT 3.69M9) hsync: 89.52 kHz; pclk: 312.25 MHz Modeline \u0026#34;2560x1440_60.00\u0026#34; 312.25 2560 2752 3024 3488 1440 1443 1448 1493 -hsync +vsync # 新建 mode $ xrandr --newmode \u0026#34;2560x1440_60.00\u0026#34; 312.25 2560 2752 3024 3488 1440 1443 1448 1493 -hsync +vsync # 为指定显示设备 add mode $ xrandr --addmode HDMI2 \u0026#34;2560x1440_60.00\u0026#34; # 指定显示器分辨率 $ xrandr --output HDMI2 --mode \u0026#34;2560x1440_60.00\u0026#34; 如果显示屏分辨率更改成功但窗口显示不完整(即只有左上角以指定分辨率显示,其他部分空白),可以尝试关闭内置显示屏,此时显示器应该能以完整窗口显示内容。\n网络 查看被监听端口\n1 netstat -tulpn | grep LISTEN Htop 基本操作 Htop 类似于 top,但比 top 更现代化,支持鼠标操作,支持颜色主题。在命令行键入 htop,会呈现如下界面。(图片来源:https://blog.csdn.net/freeking101/article/details/79173903)\n平均负载区的三个数字分别表示过去 5、10、15 分钟系统的平均负载。进程区每一列的意义分别是:\nPID: 进程号 USER: 进程所有者的用户名 PRI: 优先级别 NI: NICE 值(优先级别数值),越小优先级越高 VIRT: 虚拟内存 RES: 物理内存 SHR: 共享内存 S: 进程状态(S[leep], R[unning], Z[ombie], N 表示优先级为负数) CPU%: 该进程占用的 CPU 使用率 MEM%: 该进程占用的物理内存和总内存的百分比 TIME+: 该进程启动后占用的 CPU 时间 Command: 该进程的启动命令 常用快捷键可在 htop 界面按?显示。\nH: 显示/隐藏用户子线程 Space: 标记进程 k: 杀死已标记进程 Reference linux 下视频转 gif Running Bash Commands in the Background the Right Way [Linux] https://gist.github.com/heiswayi/350e2afda8cece810c0f6116dadbe651\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/tricks/","tags":null,"title":"Useful Tricks"},{"categories":["tech"],"contents":"前略。\n今年早些时候,从 Gnome 换到 i3,原因是因为原来的 gnome 被我弄崩溃了。一时难以解决,又想到之前好几次隐约感觉到 gnome 的不稳定,一气之下决定换一个轻量,稳定的,可定制的窗口管理工具。至于为什么换 i3?去知乎吸收一下各个管理器间的哲学就知道了。以前从 Windows 转 Linux,也是这么过来的,知乎真是个好地方!\n换成 i3 之后,经过一番配置,桌面终于有点样子了。但是看了一眼 i3 默认的状态栏,emmm\u0026hellip;,有点不堪入目。几经搜索之下,发现了这款名为 polybar 的状态栏工具。起初,我被他的描述深深吸引了:\nPolybar: A fast and easy-to-use status bar.\n事实上它并不是那么 easy-to-use,至少对我这个一开始接触它的人来说。我甚至不知道如何开启它(可能我真的太笨了:()。然后我开始在网上寻找一些现成的配置,结果不是报错,就是乱码。那些看着好看的配置,你拿过来却用不了。这很气人,然后就告一段落了。我顶着简陋的 i3bar 用了好几个月。直到最近闲下来,要想起来这位老朋友,这才拿出来叙叙旧。\n有些事情,你当时攻不下来。那就先放一放,择日再战有奇效。\n由浅入深 这次呢,我从一个最简单的例子入手。从 这里 找到一个既好看,又简约的模板:\n我将对应的配置复制到我自己的配置文件(~/.config/polybar/config)中:\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 [bar/bar] background = #D93d3c3b foreground = #b6a49b width = 24% height = 70 radius = 15 line-size = 0 bottom = true border-bottom-size = 0 padding-left = 0 padding-right = 0 #module-margin-left = 1 #module-margin-right = 1 fixed-center = true font-0 = San Francisco Display Regular:size=24;1 font-1 = unifont:fontformat=truetype:size=24:antialias=false;0 font-2 = \u0026#34;MaterialIcons:size=24:antialias=false;1\u0026#34; font-3 = \u0026#34;icomoon-extended-ultra:size=24:antialias=false;1\u0026#34; font-4 = \u0026#34;Ubuntu Nerd Font:size=24:antialias=false;1\u0026#34; font-5 = FontAwesome:size=24;1 modules-left = modules-center = modules-right = date volume eth poweroff module-margin = 2 ;left - center - right - none tray-position = none tray-maxsize = 24 tray-detached = false tray-transparent = false tray-padding = 2 tray-scale = 1.0 override-redirect = true offset-x = 2900 offset-y = 20 padding = 0 wm-name = bar ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [module/date] type = internal/date interval = 60 date = %a %d %b time = %l:%M %p label = %date% %time% ;label = %time% format-padding = 1 [module/volume] type = internal/volume format-volume = \u0026lt;ramp-volume\u0026gt; \u0026lt;label-volume\u0026gt; format-muted = 0% ramp-volume-0 = ramp-volume-1 = ramp-volume-2 = [module/poweroff] type = custom/script exec = echo \u0026#34; \u0026#34; click-left = rofi -modi run,drun,window -show drun click-right = i3lock-fancy -pg \u0026amp; click-middle = /usr/bin/rofi-logout format-padding = 1 [module/rofi] type = custom/script exec = echo \u0026#34; \u0026#34; click-left = rofi -modi run,drun,window -show drun format-padding = 1 [module/eth] type = internal/network interface = enp2s0 interval = 3.0 format-connected = \u0026lt;label-connected\u0026gt; format-connected-prefix = \u0026#34; \u0026#34; format-connected-prefix-foreground = #b6a49b label-connected = %downspeed:9% format-disconnected = \u0026lt;label-disconnected\u0026gt; label-disconnected = not connected label-disconnected-foreground = #66ffffff format-padding = 1 Code adapted from ref 可以看到,配置文件前半部分为 bar 本身的配置:位置,大小,颜色等等。后半部分则是对应模块的配置。其中,我们看到的一些字符未显示,那是由于我的本地并没有可以显示它的字体。其中的乱码主要是 Font Awesome 字体,首先我们得安装该字体。看看这个 bar 到底能不能如预期一样正常显示。\n执行\n1 polybar bar 来运行配置文件中的 bar,其中“bar”为自定义的名字,在上面的配置文件中,这个 bar 的名字就是“bar”。(参见[bar/bar])\n不知道是什么原因,运行没有报错,但是我却看不见 bar 在哪里。╮(╯_╰)╭ 可能是它画在了屏幕之外的地方。于是我就将上面的参数改了改,终于,我看到了着个可爱又迷人的 bar 出现在我的桌面上。不过还是有几处乱码,可能是我安装的 Font Awesome 支持的字符不完整吧。\n然后就开始了漫长的折腾过程,循着错误信息去改对应的地方,然后看效果。再微调,再看效果,直到满意为止。\n下面是我调整完成的样子: 了解构造 其实通过配置文件,我们可以学到很多东西。当然,前提是要有一个好的蓝本给你看。这就不得不提 polybar 的 wiki,以及作者给出的一些 示例 了。\n好了,现在蓝本已经有了。我想添加一个新的模块:比如现在的 Wi-Fi 模块只有下行速度,我要加一个上行速度,并且加上下行箭头。参照 wiki 中的 network 模块,我们可以修改如下:\n1 2 3 4 5 6 7 8 9 10 11 12 [module/wlan] type = internal/network interface = wlp2s0 interval = 3.0 format-connected = \u0026lt;label-connected\u0026gt; format-connected-prefix = \u0026#34; \u0026#34; format-connected-prefix-foreground = #b6a49b label-connected = %upspeed:5% %downspeed:5% format-disconnected = \u0026lt;label-disconnected\u0026gt; label-disconnected = not connected label-disconnected-foreground = #66ffffff format-padding = 0 可以看到,被改动的实际上只有一行:\n1 2 3 4 ; before label-connected = %downspeed:9% ; after label-connected = %upspeed:5% %downspeed:5% 这样就完成了所期望的改变。\nNote: 要输入 Font Awesome 字符,可以打开官网的字符集 cheatsheet,然后直接复制到编辑器中。 例如,通过浏览器搜索找到上行箭头,复制之。\n另外如果使用 vim,可以直接输入对应的十六进制编码来完成字符的输入。比如,我要输入上行箭头(fa-arrow-up),它的十六进制码为 f062(在 cheatsheet 中可查):\n按 i 进入插入模式 按 Ctrl + v 按 u 进入十六进制输入模式 输入 f062 按 Esc 返回正常模式 inputing\u0026hellip; 另,:help i_CTRL-V_digit In insert-mode, type control+V followed by\na decimal number x then a hex number u then a 4-hexchar unicode sequence U then an 8-hexchar unicode sequence 摄入取景 好了,通过上面的一点小小的改动,我们已经大致了解 polybar 的配置方式了。现在,通过一步一步加模块,我们会更进一步的了解它的工作方式。建议每添加一个模块前,先读一读 wiki 上对应模块的设置。然后找一个不错的模板进行改动。\n内存和 CPU 模块 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 ;;; CPU usage {{{ [module/cpu] type = internal/cpu ; Seconds to sleep between updates ; Default: 1 interval = 3.0 ; Available tags: ; \u0026lt;label\u0026gt; (default) ; \u0026lt;bar-load\u0026gt; ; \u0026lt;ramp-load\u0026gt; ; \u0026lt;ramp-coreload\u0026gt; format = \u0026lt;label\u0026gt;\u0026lt;ramp-load\u0026gt; ; Available tokens: ; %percentage% (default) - total cpu load averaged over all cores ; %percentage-sum% - Cumulative load on all cores ; %percentage-cores% - load percentage for each core ; %percentage-core[1-9]% - load percentage for specific core label = CPU %percentage%% ;label-font = 3 ; Spacing between individual per-core ramps ramp-coreload-spacing = 1 ramp-coreload-0 = ▁ ramp-coreload-1 = ▂ ramp-coreload-2 = ▃ ramp-coreload-3 = ▄ ramp-coreload-4 = ▅ ramp-coreload-5 = ▆ ramp-coreload-6 = ▇ ramp-coreload-7 = █ ; ramps for ramp-load ramp-load-0 = ▁ ramp-load-1 = ▂ ramp-load-2 = ▃ ramp-load-3 = ▄ ramp-load-4 = ▅ ramp-load-5 = ▆ ramp-load-6 = ▇ ramp-load-7 = █ ; colors for each ramp ramp-load-0-foreground = #aaff77 ramp-load-1-foreground = #aaff77 ramp-load-2-foreground = #aaff77 ramp-load-3-foreground = #aaff77 ramp-load-4-foreground = #fba922 ramp-load-5-foreground = #fba922 ramp-load-6-foreground = #ff5555 ramp-load-7-foreground = #ff5555 ;;; }}} 首先看到输出的格式为format = \u0026lt;label\u0026gt;\u0026lt;ramp-load\u0026gt;,说明它会输出\u0026lt;label\u0026gt;的值以及\u0026lt;ramp-load\u0026gt;的值。好的,那我们先看一眼\u0026lt;label\u0026gt;是啥?\n1 label = CPU %percentage%% 顾名思义,它会输出 CPU 加一个变量,这个变量是一个百分比,就是当前 CPU 的平均使用率。可选的变量已经在 wiki 中给出。想要什么自己替换就行。\n再来看看第二部分,\n1 2 3 4 5 6 7 8 ramp-load-0 = ▁ ramp-load-1 = ▂ ramp-load-2 = ▃ ramp-load-3 = ▄ ramp-load-4 = ▅ ramp-load-5 = ▆ ramp-load-6 = ▇ ramp-load-7 = █ ramp-load共有 7 个值,他会根据 CPU 使用率选择合适的值。简而言之,就是将 100 分成 7 个等级,使用率越高,就选用等级越高的图案显示。整合起来的效果就是随着百分比增加,显示的高度越高,类似一个性能监视窗,上下浮动。\n这还不够,我们还可以做一点微小的工作。请看\n1 2 3 4 5 6 7 8 ramp-load-0-foreground = #aaff77 ramp-load-1-foreground = #aaff77 ramp-load-2-foreground = #aaff77 ramp-load-3-foreground = #aaff77 ramp-load-4-foreground = #fba922 ramp-load-5-foreground = #fba922 ramp-load-6-foreground = #ff5555 ramp-load-7-foreground = #ff5555 这里我们定义了每个等级展示字符的前景色。等级越高,颜色越红,表示警告 CPU 使用快超标了!这样整个变化就有了颜色相伴,更加直观!最后完成的效果如下:\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 75 76 77 78 [module/memory] type = internal/memory ; Seconds to sleep between updates ; Default: 1 interval = 3.0 ; Available tags: ; \u0026lt;label\u0026gt; (default) ; \u0026lt;bar-used\u0026gt; ; \u0026lt;bar-free\u0026gt; ; \u0026lt;ramp-used\u0026gt; ; \u0026lt;ramp-free\u0026gt; ; \u0026lt;bar-swap-used\u0026gt; ; \u0026lt;bar-swap-free\u0026gt; ; \u0026lt;ramp-swap-used\u0026gt; ; \u0026lt;ramp-swap-free\u0026gt; format = \u0026lt;label\u0026gt;\u0026lt;ramp-used\u0026gt; ; Available tokens: ; %percentage_used% (default) ; %percentage_free% ; %gb_used% ; %gb_free% ; %gb_total% ; %mb_used% ; %mb_free% ; %mb_total% ; %percentage_swap_used% ; %percentage_swap_free% ; %mb_swap_total% ; %mb_swap_free% ; %mb_swap_used% ; %gb_swap_total% ; %gb_swap_free% ; %gb_swap_used% label = RAM %percentage_used%% ;label-font = 3 ; Only applies if \u0026lt;bar-used\u0026gt; is used bar-used-indicator = bar-used-width = 50 bar-used-foreground-0 = #55aa55 bar-used-foreground-1 = #557755 bar-used-foreground-2 = #f5a70a bar-used-foreground-3 = #ff5555 bar-used-fill = ▐ bar-used-empty = ▐ bar-used-empty-foreground = #444444 ; Only applies if \u0026lt;ramp-used\u0026gt; is used ramp-used-0 = ▁ ramp-used-1 = ▂ ramp-used-2 = ▃ ramp-used-3 = ▄ ramp-used-4 = ▅ ramp-used-5 = ▆ ramp-used-6 = ▇ ramp-used-7 = █ ramp-used-0-foreground = #aaff77 ramp-used-1-foreground = #aaff77 ramp-used-2-foreground = #aaff77 ramp-used-3-foreground = #aaff77 ramp-used-4-foreground = #fba922 ramp-used-5-foreground = #fba922 ramp-used-6-foreground = #ff5555 ramp-used-7-foreground = #ff5555 ; Only applies if \u0026lt;ramp-free\u0026gt; is used ramp-free-0 = ▁ ramp-free-1 = ▂ ramp-free-2 = ▃ ramp-free-3 = ▄ ramp-free-4 = ▅ ramp-free-5 = ▆ ramp-free-6 = ▇ ramp-free-7 = █ ;;; }}} 配置出来的效果见上图。\n电池模块 同理可参考 wiki 配置电池模块。\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 ;;; battery {{{ [module/battery] type = internal/battery full-at = 98 format-charging = \u0026lt;animation-charging\u0026gt; \u0026lt;label-charging\u0026gt; format-discharging = \u0026lt;ramp-capacity\u0026gt; \u0026lt;label-discharging\u0026gt; format-full = %{F#666}%{F#ccfafafa} \u0026lt;label-full\u0026gt; #label-charging-font = 3 #label-discharing-font = 3 ramp-capacity-0 = ramp-capacity-1 = ramp-capacity-2 = ; low power alert ramp-capacity-0-foreground = #ff5555 ; it will display over the three pattern when charing ; at a framerate 750 ; and each has a foreground color animation-charging-0 = animation-charging-1 = animation-charging-2 = animation-charging-2-foreground = #aaff77 animation-charging-1-foreground = #fba922 animation-charging-0-foreground = #ff5555 animation-charging-framerate = 750 ;;; }}} 可以看到电池的显示类型共有三个,\n1 2 3 format-charging = \u0026lt;animation-charging\u0026gt; \u0026lt;label-charging\u0026gt; format-discharging = \u0026lt;ramp-capacity\u0026gt; \u0026lt;label-discharging\u0026gt; format-full = %{F#666}%{F#ccfafafa} \u0026lt;label-full\u0026gt; 第一个控制了充电时的显示,第二个控制了非充电时的显示,第三个则是满电时的显示。配置语言通俗易懂,比如,充电的时候会显示一个充电动画和电池电量(\u0026lt;label-charing\u0026gt;),其他的可以依次类推。\n1 2 3 4 5 6 7 animation-charging-0 = animation-charging-1 = animation-charging-2 = animation-charging-2-foreground = #aaff77 animation-charging-1-foreground = #fba922 animation-charging-0-foreground = #ff5555 animation-charging-framerate = 750 上面这段配置定义了充电时的动画,当你插上电源,电池图标会依次按照 0、1、2 的图案切换。然后每个图案都有各自的前景色,切换的速率是 750. 完成后的效果如下:\nMPD 模块 MPD 全称 Music Player Daemon.\nMPD (music player daemon) is an audio player that has a server-client architecture. It plays audio files, organizes playlists and maintains a music database, all while using very few resources. In order to interface with it, a separate client is needed.\n──adpted from archwiki MPD 是一个轻量的本地音乐播放框架。需要和客户端(mpc)一起使用。大致分为几步:\n$ mpd 启动 mpd 进程 $ mpc add \u0026lt;MusicDir\u0026gt; 添加本地音乐文件夹 $ mpc play 开始播放 如我们所见,非常轻量。但是 mpc 是命令行工具,使用起来难免有些不顺手。好在 polybar 已经内置了 mpd 模块,我们只需要改改样式就可以了。\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 ;;; mpd config {{{ [module/mpd] type = internal/mpd ; format-online = \u0026lt;label-song\u0026gt; \u0026lt;label-time\u0026gt; \u0026lt;bar-progress\u0026gt; \u0026lt;icon-prev\u0026gt; \u0026lt;icon-seekb\u0026gt; \u0026lt;icon-stop\u0026gt; \u0026lt;toggle\u0026gt; \u0026lt;icon-seekf\u0026gt; \u0026lt;icon-next\u0026gt; \u0026lt;icon-repeat\u0026gt; \u0026lt;icon-random\u0026gt; format-online = \u0026lt;toggle\u0026gt; \u0026lt;label-song\u0026gt; \u0026lt;icon-next\u0026gt; format-offline = \u0026lt;label-offline\u0026gt; format-offline-foreground = #66 label-offline = mpd is off label-song-maxlen = 20 label-song-font = 8 icon-prev = icon-seekb = icon-stop = stop icon-play = icon-pause = icon-next = icon-seekf = icon-random = icon-repeat = toggle-on-foreground = #2278ff toggle-off-foreground = #66 bar-progress-width = 15 bar-progress-indicator = bar-progress-indicator-foreground = #bb bar-progress-fill = ─ bar-progress-fill-foreground = #bb bar-progress-fill-font = 3 bar-progress-empty = ─ bar-progress-empty-foreground = #44 bar-progress-empty-font = 3 label-time-foreground = #55 label-time-font = 8 ;;; }}} 配置好我们需要的样式就大功告成啦,看一下效果:\n总结 一路折腾下来,发现 polybar 其实很友好。配置逻辑也非常清晰,有点怀疑自己当初为什么没有配置好它,反而觉得很难用。所以啊,作者写这个软件也不是为了刁难人的,一开始的时候也并没有这么复杂。我们入手呢,就要从作者一开始写出来的那样,能用出最基础的功能就好了。然后需求都是后来加上去的。有用户提,作者考虑做,或者作者自己想到的功能,才会在后面一步步添加上去。最终成为一个功能相对完善的软件。每个东西都有它的学习曲线,我们可能已经适应了平缓的山坡,突然面对一个陡峭的山峰之时,便不好应对了。但是千里之行,始于足下!我们始终要抓住它们最初的样子,或者说雏形,然后想一想它们是如何发展过来的,顺着这条线路走下去,自然觉得一切合情合理,也有勇气和信心去学了!然后当我们学会一个又一个软件之后,或许会发现它们可能有异曲同工之妙。那便是学到了!\n如果你恰巧喜欢我的配置,猛击 这里 获取之。\nReference Polybar wiki Polybar example How to get polybar icons working Show Us Your Polybar - Artwork / Polybar \u0026amp; Tint2 Configs - ArchLabs Linux ","permalink":"https://guyueshui.github.io/post/polybar-%E7%9A%84%E9%85%8D%E7%BD%AE%E7%AC%94%E8%AE%B0/","tags":["polybar","个性化"],"title":"Polybar 的配置笔记"},{"categories":["Notes"],"contents":"Preliminaries Def: A matrix $A \\in M_n$ is normal if $AA^∗ = A^∗A$, that is, if $A$ commutes with its conjugate transpose.\nDef: A complex matrix $A$ is unitary if $AA^∗ = I$ or $A^∗A = I$, and a real matrix $B$ is orthogonal if $BB^T = I$ or $B^TB = I$.\nThere is no so-called \u0026ldquo;orthonormal\u0026rdquo; matrix. There is just an orthogonal matrix whose rows or columns are orthonormal vectors.\nNotice that $$ \\begin{gathered} U^*U = I \\Longleftrightarrow U^{\\star}UU^{\\star} = IU^{\\star} = U^{\\star} \\ \\Longleftrightarrow UU^{\\star} = (U^{\\star})^{-1}U^{\\star} = I \\end{gathered} $$ the columns of $U$ are orthonormal if and only if the rows are orthonormal.\nSo the definition can be summarized as below:\nHermitian: $A=A^{\\star}$ Unitary: $A^{\\star}A=AA^{\\star}=I$ Symmetric: $A = A^{T}$ Orthogonal: $A^TA=AA^T=I$ Eigenvalue Decomposition Singular Value Decomposition $LU$ Factorization $QR$ Factorization ","permalink":"https://guyueshui.github.io/post/matrix-factorization/","tags":["math","algebra"],"title":"Matrix Factorization"},{"categories":["tech"],"contents":"Needs polish!\n前略。\nBeamer 引用参考文献与文章类似,只是一开始听说 beamer 不支持 \\cite,搞得我走了不少弯路(其实是可以的)。\n使用 bibtex 假设你有 mybeamer.tex 文件,在同目录下新建 mybeamer.bib 文件(其实只是纯文本,特殊后缀而已)。将你所有需要引用的文献条目写入该文献中。比如,\n1 2 3 4 5 6 7 8 9 10 11 12 %%% ./mybeamer.bib @article{shamir2010learning, title={Learning and generalization with the information bottleneck}, author={Shamir, Ohad and Sabato, Sivan and Tishby, Naftali}, journal={Theoretical Computer Science}, volume={411}, number={29-30}, pages={2696--2711}, year={2010}, publisher={Elsevier} } Note: 关于 bibtex 引用格式的获取,直接上 Google Scholar 或者百度学术 搜所引文题目,导出为 bibtex 格式。\n然后在你的 tex 文件中加入\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 %%% ./mybeamer.tex \\usepackage{cite} % Removes icon in bibliography \\setbeamertemplate{bibliography item}[text] ... \\begin{document} ... %%% end of your presentation slides \\begin{frame}[allowframebreaks]{References} %\\bibliographystyle{plain} \\bibliographystyle{amsalpha} %\\bibliography{mybeamer} also works \\bibliography{./mybeamer.bib} \\end{frame} \\end{document} Note: bib 文件可以不于 tex 文件同名,作相应改动即可。\n编译顺序(这很重要)\n首先用pdflatex或者xelatex编译你的 tex 文件mybeamer.tex,\npdflatex mybeamer.tex 这样会在当前目录产生一个.aux文件。然后使用bibtex编译该文件,\nbibtex mybeamer 然后再用pdflatex编译一遍。此时应该会出现应用错误,小问号等警告。此时再用pdflatex编译一遍即可。如果出现了其他错误,删掉所有 .bbl, .aux 文件,重复以上操作。\n总结一下四个步骤:\npdflatex mybeamer.tex bibtex mybeamer pdflatex mybeamer.tex pdflatex mybeamer.tex 额外知识 Beamer 中默认使用 sans 字体1,可以使用\\usefonttheme{serif}切换为衬线字体,推荐 slides 只给人阅读,不用做 presentation 时使用,毕竟衬线字体更好看一些。Beamer 中基本的定义字体方法如下:\n1 2 3 4 5 6 7 8 %% %% Note that once use customizing fonts, switch to xelatex %% \\usepackage{fontenc} \\setsansfont{Varela Round} % \\setsansfont{IM FELL DW Pica} \\setmonofont{DejaVu Sans Mono} \\setmathfont{Fira Sans} References LaTeX/Presentations LaTeX 到底怎么加 bib?? - 知乎 Changing font style using beamer 想想你在台上讲 slides,如果弄个细细的衬线字体,别人看的得多费劲。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/use-reference-in-beamer/","tags":["latex","beamer","排版"],"title":"在 Beamer 中使用参考文献"},{"categories":["Notes"],"contents":"Story The Exponential distribution is the continuous counterpart to the Geometric distribution. The story of the Exponential distribution is analogous, but we are now waiting for a success in continuous time, where successes arrive at a rate of $\\lambda$ successes per unit of time. The average number of successes in a time interval of length $t$ is $\\lambda t$, though the actual number of successes varies randomly. An Exponential random variable represents the waiting time until the first arrival of a success.\n——adapted from Book BH Basic Definition: A continuous r.v. $X$ is said to have the Exponential distribution with parameter $\\lambda$ if its PDF is $$ f(x) = \\lambda e^{-\\lambda x}, \\quad x \u0026gt; 0 $$ The corresponding CDF is\n$$ F(x) = 1 - e^{-\\lambda x}, \\quad x \u0026gt; 0 $$\nTo calculate the expectation and variance, we first consider $X \\sim Exp(1)$ with PDF $f(x) = e^{-x}$, then\n$$ \\begin{split} E(X) \u0026amp;= \\int_0^{\\infty} x e^{-x} dx = 1 \\newline E(X^2) \u0026amp;= \\int_0^{\\infty} x^2 e^{-x} dx \\newline \u0026amp;= -x^2e^{-x}|_0^{\\infty} + 2\\int_0^{\\infty} x e^{-x} dx \\newline \u0026amp;= 2E(X) = 2 \\newline Var(X) \u0026amp;= E(X^2) - E^2(X) = 2-1 = 1 \\newline M_X(t) \u0026amp;= E(e^{tX}) = \\int_0^{\\infty} e^{tx} e^{-x} dx \\newline \u0026amp;= \\int_0^{\\infty} e^{-(1-t)x} dx = \\frac{1}{1-t} \\quad \\text{for }t\u0026lt;1 \\end{split} $$\nNow let $Y=\\frac{X}{\\lambda} \\sim Exp(\\lambda)$ for\n$$ f_Y(y) = f_X(X(y))\\frac{dx}{dy} = e^{-\\lambda y}\\cdot\\lambda \\sim Exp(\\lambda) $$\nor\n$$ P(Y\\le y) = P(X\\le \\lambda y) = 1 - e^{-\\lambda y} \\sim Exp(\\lambda). $$\nHence, we can get\n$E(Y) = E(X/\\lambda) = 1/\\lambda$ $Var(Y) = Var(X/\\lambda) = 1/\\lambda^2$ MGF (moment generating function): $$ \\begin{split} M_Y(t) \u0026amp;= E(e^{tY}) =E(e^{tX/\\lambda}) \\newline \u0026amp;= E(e^{\\frac{t}{\\lambda}X}) = M_X(\\frac{t}{\\lambda}) = \\frac{1}{1-t/\\lambda} \\newline \u0026amp;= \\frac{\\lambda}{\\lambda -t} \\quad \\text{for }t\u0026lt;\\lambda \\end{split} $$\nMemeoryless Property Memoryless is something like $P(X \\ge s+t | X \\ge s) = P(X \\ge t)$, let $X \\sim Exp(\\lambda)$, then\n$$ \\begin{split} P(X \\ge s+t | X \\ge s) \u0026amp;= \\frac{P(X \\ge s+t, ~X \\ge s)}{P(X \\ge s)} \\newline \u0026amp;= \\frac{P(X \\ge s+t)}{P(X \\ge s)} \\newline \u0026amp;= \\frac{e^{-\\lambda (s+t)}}{e^{-\\lambda s}} = e^{-\\lambda t} \\newline \u0026amp;= P(X \\ge t) \\end{split} $$\nTheorem: If $X$ is a positive continuous r.v. with memoryless property, then $X$ has an exponential distribution. Similarly, if $X$ is discrete, then it has a geometric distribution.\nProof idea: use survival function and solve differential equations.\nExamples eg.1 $X_1 \\sim Exp(\\lambda_1), ~X_2 \\sim Exp(\\lambda_2)$, and $X_1 \\perp X_2$. Then $P(X_1 \u0026lt; X_2) = \\frac{\\lambda_1}{\\lambda_1 + \\lambda_2}$.\nProof: By LOTP (law of total probability),\n$$ \\begin{split} P(X_1 \u0026lt; X_2) \u0026amp;= \\int_0^{\\infty} f_{X_1}(x) P(X_2 \u0026gt; X_1 | X_1=x) dx \\newline \u0026amp;= \\int_0^{\\infty} f_{X_1}(x) P(X_2 \u0026gt; x | X_1=x) dx \\newline \u0026amp;= \\int_0^{\\infty} f_{X_1}(x) P(X_2 \u0026gt; x) dx \\quad \\text{(independence)} \\newline \u0026amp;= \\int_0^{\\infty} \\lambda_1 e^{-\\lambda_1 x} e^{-\\lambda_2 x} dx \\newline \u0026amp;= \\lambda_1 \\int_0^{\\infty} e^{-(\\lambda_1 + \\lambda_2) x} dx \\newline \u0026amp;= \\frac{\\lambda_1}{\\lambda_1 + \\lambda_2} \\end{split} $$\neg.2 $\\{X_i\\}_{i=1}^n$ are independent with $X_j \\sim Exp(\\lambda_j)$. Let $L = \\min(X_1, \\cdots, X_n)$, then $L \\sim Exp(\\lambda_1 + \\cdots \\lambda_n)$.\nProof:\n$$ \\begin{split} P(L \u0026gt; t) \u0026amp;= P\\left(\\min(X_1,\\cdots,X_n) \u0026gt; t\\right) \\newline \u0026amp;= P(X_1 \u0026gt; t, \\cdots, X_n \u0026gt;t) \\newline \u0026amp;= P(X_1 \u0026gt; t) \\cdots P(X_n \u0026gt;t) \\quad \\text{indep.} \\newline \u0026amp;= e^{-\\lambda_1 t}\\cdots e^{-\\lambda_n t} \\newline \u0026amp;= e^{-(\\lambda_1 + \\cdots \\lambda_n)t} \\sim Exp\\left(\\sum_j \\lambda_j\\right) \\end{split} $$\nThe intuition of this result is that if you consider $n$ Poisson processes with rate $\\lambda_j$,\n$X_1$ as the waiting time for a green car $X_2$ as the waiting time for a red car \u0026hellip; Then $L$ is the waiting time for a car of any color (i.e., any car). So it makes sense, the rate is $\\lambda_1 + \\cdots + \\lambda_n$.\neg.3 (Difference of two exponetial) Let $X \\sim Exp(\\lambda)$ and $Y \\sim Exp(\\mu)$, $X \\perp Y$. Then what is the PDF of $Z=X-Y$?\nSolution: Recall the story of exponential, one can think of $X$ and $Y$ as waiting times for two independent things. For example,\n$X$ as the waiting time for a red car passing by $Y$ as the waiting time for a blue car If we see a blue car passing by, then the further waiting time for a red car is still distributed as same distribution as $Y$, for the memoryless property of exponential. Likewise, if we see a red car passing by, then the further waiting time is distributed as same as $X$. The further waiting time is somehow what we are interested in, say $Z$.\nThe above intuition says that, the conditional distribution of $X-Y$ given $X \u0026gt; Y$ is the distribution of $X$, and the conditional distribution of $X-Y$ given $X \\le Y$ is the distribution of $-Y$ (or in other words, the conditional distribution of $Y-X$ given $Y \\ge X$ is same as the distribution of $Y$).\nTo make full use of our intuition, we know that\nIf $X\u0026gt;Y$, which means $Z\u0026gt;0$, then $Z~|X\u0026gt;Y = X$ a.s. holds, that is $$ \\begin{gathered} f_Z(z|X\u0026gt;Y) = \\lambda e^{-\\lambda z} \\newline \\text{and since }P(X\u0026lt;Y) = 0 \\newline \\implies f_Z(z) = f_Z(z|~X\u0026gt;Y)P(X\u0026gt;Y) \\newline = \\frac{\\mu}{\\lambda + \\mu}\\lambda e^{-\\lambda z}. \\end{gathered} $$\nIf $X \u0026lt; Y$, which means $Z \u0026lt; 0$, then $Z~|X\u0026lt;Y = -Y$ a.s. holds, that is $$ \\begin{gathered} f_Z(z|X\u0026lt;Y) = f_Y(y(z))\\left|\\frac{dy}{dz}\\right| = \\mu e^{\\mu z} \\newline \\implies f_Z(z) = f_Z(z|~X\u0026lt;Y)P(X\u0026lt;Y) \\ = \\frac{\\lambda}{\\lambda + \\mu} \\mu e^{\\mu z} \\end{gathered} $$\nHowever, this is just a sketch. Later we will see how to derivate the form mathematically.\nFrom the above point of view, the PDF of $Z$ had better be discussed by the sign of $Z$.\nIf $Z \u0026gt; 0$, which implies $X \u0026gt; Y\\implies P(X \u0026gt; Y) = 0 $, then $$ \\begin{split} P(Z \u0026gt; z) \u0026amp;= P(X-Y\u0026gt;z | X\u0026gt;Y)P(X\u0026gt;Y) + P(Z\u0026gt;z~|~X\u0026lt;Y)P(X\u0026lt;Y) \\newline \u0026amp;= P(X\u0026gt;z)P(X\u0026gt;Y) + 0 \\quad \\text{(memoryless)} \\newline \u0026amp;= \\frac{\\mu}{\\lambda + \\mu} e^{-\\lambda z} \\quad \\text{(by eg.1)} \\newline \\implies f_Z(z) \u0026amp;= \\frac{\\lambda\\mu}{\\lambda + \\mu} e^{-\\lambda z} \\quad \\text{for }z\u0026gt;0 \\end{split} $$\nIf $Z \\le 0$, which implies $X \\le Y$, then $$ \\begin{split} P(Z \u0026lt; z) \u0026amp;= P(Z\u0026lt;z | X\u0026gt;Y)P(X\u0026gt;Y) + P(X-Y\u0026lt;z~|~X\u0026lt;Y)P(X\u0026lt;Y) \\newline \u0026amp;= 0 + P(Y-X \u0026gt; -z | Y\u0026gt;X)P(Y\u0026gt;X) \\newline \u0026amp;= P(Y\u0026gt;X)P(Y \u0026gt; -z) \\quad \\text{(memoryless)} \\newline \u0026amp;= \\frac{\\lambda}{\\lambda + \\mu}e^{\\mu z} \\quad \\text{(by eg.1)} \\newline \\implies f_Z(z) \u0026amp;= \\frac{\\lambda\\mu}{\\lambda + \\mu}e^{\\mu z} \\quad \\text{for }z\u0026lt;0 \\end{split} $$\nTherefore, the PDF of $Z$ has the form\n$$ f_Z(z) = \\frac{\\lambda\\mu}{\\lambda + \\mu} \\begin{cases} e^{-\\lambda z} \u0026amp;\\quad z\u0026gt;0 \\newline e^{\\mu z} \u0026amp;\\quad z\u0026lt;0 \\end{cases} $$\nNote: $P(X=Y)=0$ since the integral domain is a line ($y=x$) whose measure is 0. That is $P(Z=0) = 0$. This is why we can give no care of the case $X=Y$.\n","permalink":"https://guyueshui.github.io/post/exponetial-distribution/","tags":["math","probability"],"title":"Exponential Distribution"},{"categories":["Notes"],"contents":"高斯分布的微分熵 $X \\sim \\mathcal{N}(\\mu, \\sigma^2)~$,$\\displaystyle f(x)=\\frac{1}{\\sqrt{2\\pi\\sigma^2}}e^{-\\frac{(x-\\mu)^2}{2\\sigma^2}}$,其微分熵推导过程如下:\n$$ \\begin{split} h(X) \u0026amp;= \\int_{-\\infty}^{\\infty} \\frac{-1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) \\cdot \\ln \\left[ \\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) \\right] dx \\newline \u0026amp;= \\frac{-1}{\\sqrt{2\\pi\\sigma^2}} \\int_{-\\infty}^{\\infty} \\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) \\cdot \\left( \\ln(2\\pi\\sigma^2)^{-1/2} - \\frac{(x-\\mu)^2}{2\\sigma^2} \\right) dx \\newline \u0026amp;= \\frac{1}{2}\\ln(2\\pi\\sigma^2) \\int_{-\\infty}^{\\infty} \\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) dx \\newline \u0026amp;\\quad + \\int_{-\\infty}^{\\infty} \\frac{(x-\\mu)^2}{2\\sigma^2} \\cdot \\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) dx \\newline \u0026amp;= \\frac{1}{2}\\ln(2\\pi\\sigma^2) + \\frac{1}{2\\sigma^2}E(X-\\mu)^2 \\newline \u0026amp;= \\frac{1}{2}\\ln(2\\pi\\sigma^2) + \\frac{1}{2} = \\frac{1}{2}\\ln(2\\pi e\\sigma^2) \\qquad\\text{(nats)} \\end{split} $$\n又因为 $H_b(X) = \\log_ba \\cdot H_a(X)$,于是取 $b=e, ~a=2$ $$ \\implies \\frac{1 \\text{ nat}}{x \\text{ bits}} = \\ln2 \\implies x = \\frac{1}{\\ln2} = \\frac{\\log e}{\\log2} = \\log e \\text{ bits} $$ 所以 $$ \\frac{1}{2}\\ln(2\\pi e\\sigma^2) \\text{ nats} = \\frac{1}{2} \\cdot \\frac{\\log(2\\pi e\\sigma^2)}{\\log e} \\cdot \\log e \\text{ bits} = \\frac{1}{2}\\log(2\\pi e\\sigma^2) \\text{ bits} $$\n概率 Bounds on tail probabilities Markov\u0026rsquo;s inequality: For any r.v. $X$ and constant $a \u0026gt; 0$,\n$$ P(|X| \\ge a) \\le \\frac{E|X|}{a}. $$\nLet $Y = \\frac{|X|}{a}$. We need to show that $P(Y\\ge 1) \\le E(Y)$. Note that\n$$ I(Y \\ge 1) \\le Y, $$ since if $I(Y \\ge 1) = 0$ then $Y \\ge 0$, and if $I(Y \\ge 1) = 1$ then $Y \\ge 1$ (because the indicator says so). Taking the expectation of both sides, we have Markov’s inequality.\nChebyshev\u0026rsquo;s inequality: Let $X$ have mean $\\mu$ and variance $\\sigma^2$. Then for any $a \u0026gt; 0$,\n$$ P(|X-\\mu| \\ge a) \\le \\frac{\\sigma^2}{a^2}. $$\nBy Markov\u0026rsquo;s inequality,\n$$ P(|X-\\mu|\\ge a) = P((X-\\mu)^2 \\ge a^2) \\le \\frac{E(X-\\mu)^2}{a^2} = \\frac{\\sigma^2}{a^2}. $$\nChernoff inequality: For any r.v. $X$ and constants $a \u0026gt; 0$ and $t\u0026gt;0$,\n$$ P(X\\ge a) \\le \\frac{E(e^{tX})}{e^{ta}}. $$\nThe transformation $g$ with $g(x) = e^{tx}$ is invertible and strictly increasing. So by Markov\u0026rsquo;s inequality, we have\n$$ P(X\\ge a) = P(e^{tX} \\ge e^{ta}) \\le \\frac{E(e^{tX})}{e^{ta}}. $$\nLaw of large numbers Assume we have i.i.d. $X_1, X_2, X_3, \\dots$ with finite mean $\\mu$ and finite variance $\\sigma^2$. For all positive integers $n$, let\n$$ \\bar{X}_n = \\frac{X_1 + \\cdots + X_n}{n} $$\nbe the sample mean of $X_1$ through $X_n$. The sample mean is itself an r.v., with mean $\\mu$ and variance $\\sigma^2/n$:\n$$ \\begin{split} E(\\bar{X}_n) \u0026amp;= \\frac{1}{n} E\\left(\\sum_{i=1}^n X_i\\right) = \\frac{1}{n}\\sum_{i=1}^n E(X_i) = \\mu, \\newline \\text{Var}(\\bar{X}_n) \u0026amp;= \\frac{1}{n^2} \\text{Var}\\left(\\sum_{i=1}^n X_i\\right) = \\frac{1}{n^2}\\sum_{i=1}^n \\text{Var}(X_i) = \\frac{\\sigma^2}{n}. \\end{split} $$\nStrong law of large numbers The sample mean $\\bar{X}_n$ converges to the true mean $\\mu$ pointwise as $n \\to \\infty$, with probability 1. In other words,\n$$ P(\\lim_{n\\to\\infty} \\bar{X}_n = \\mu) = 1, \\text{ or } \\bar{X}_n \\overset{a.s.}{\\longrightarrow} \\mu. $$\nWeak law of large numbers For all $\\epsilon \u0026gt;0$, $P(|\\bar{X}_n-\\mu|\u0026gt;\\epsilon) \\to 0$ as $n\\to\\infty$. (This is called convergence in probability.) In other words,\n$$ \\lim_{n\\to\\infty}P(\\bar{X}_n = \\mu) = 1. $$\nFix $\\epsilon \u0026gt;0$, by Chebyshev\u0026rsquo;s inequality,\n$$ P(|\\bar{X}_n-\\mu|\u0026gt;\\epsilon) \\le \\frac{\\sigma^2}{n\\epsilon^2}. $$\nAs $n\\to\\infty$, the right-hand side goes to 0, ans so must the left-hand side.\nReferences Blitzstein, Joseph K, and Hwang, Jessica. \u0026ldquo;Introduction to probability.\u0026rdquo; ","permalink":"https://guyueshui.github.io/post/%E4%B8%80%E4%BA%9B%E6%8E%A8%E5%AF%BC/","tags":["math"],"title":"常用结论的证明记录"},{"categories":["tech"],"contents":"This article needs polish, do not truely trust it!\nVim is so-called the god of editors, but not so friendly to new users. Today we will cover some techniques and trick of vim, for further reference.\nGeneral Pattern A vim operation consists of three parts, namely\n[OPERATOR][NUMBER][MOTION] where\nOPERATOR - what you want to do? This mainly covers copy, cut, paste, etc. NUMBER - how many times do you want? It\u0026rsquo;s nothing but repeating the operation NUMBER times, and it\u0026rsquo;s optional. MOTION - where do you want to go? This point out the scope where the OPERATOR applies. Note: order does not matter sometimes.\nOperators Copy, Cut, Paste v - visual mode, now you can select what you want V - visual in line, this is extremely useful when you want to copy a line, just V+y! y - yank, like Ctrl+C, copy the selected text to clipboard (\u0026quot;) yy: copy current line 5yy: copy 5 lines below y+MOTION: copy the motion scope y0: copy from here to BOL (beginning of line) y$: copy from here to EOL y4G: copy from here to line 4 y?bar: copy from here to previous occurrence of bar d - delete \u0026amp; yank, not only delete, but also yank p - paste after the cursor P - paste before the cursor Edit i - insert a - insert after the cursor o - insert a line below and insert O - insert a line above and insert r - replace, replace the character inplace x - delete current character s - delete the character and insert u - recall the last command ^r - recall the last recall, namely redo . - repeat the last command Note: all deleted things were automatically yanked in buffers, i.e., register \u0026quot;\nMotions Basic h - move left l - move right j - move down k - move up Note: view (h, l) and (j, k) as pairs\nG - jump to EOF (end of file) gg - jump to BOF x + (gg | G) - jump to line x (must be a valid line number) Some additional movements:\nw - next word, points to the first letter b - back, previous word, points to the first letter e - end, jump to the end of the word % - find the bracket matches (( ),{ }, [ ]\u0026hellip;) ^e - scroll down ^y - scroll up ^d - half-screen down ^u - half-screen up * - jump to next occurrence of current word # - jump to previous occurrence of current word Note: view (w, b) as a pair\nInline movement ^: jump to the first character which is not a blank (space, tab, \\n, \\r) g_: jump to the last character which isn\u0026rsquo;t a blank 0: jump to the beginning $: jump to the end fx: find next x in current line Note: you can use ;(alongside) and ,(reverse side) to repeat this in two directions\nFx: find previous x in current line Note: same rules can be applied tx: find next x and move 1 backward Tx: find previous x and move 1 backward Commands Search \u0026amp; Replace /keyword - search keyword after the cursor ?keyword - search\tkeyword before the cursor Note: use n(search next) or N(search previous) for quick search\n:{search_scope}s/{target}/{replace}/{replace_flag} - replace {target} to {replace} s stands for substitute. :%s/a/b/g: global (%) search a, replace it to b at every (g) occurrence :%s/a/b/gc: interact with every replace More detail\nIt has a general pattern:\n:[range]s/from/to/[flags] The default range is current line. See some examples:\n:1,10s/from/to - search and replace between line 1 and 10 (included) :10s/from/to/ - search and replace only in line 10 :%s/from/to/ - in global scope flags can be\ng: replace all matches in whole line w/o confirmation c: confirm before replace i: ignore lower/upper case e: ignore error Note that flags can be combined together, e.g., :%s/from/to/gc means search and replace in global and ask for confirmation before each replacement.\nUse regular expression\nMeta character Explanation . Matches any character [abc] Matches any char from the list [^abc] Matches any char except from the list \\d Matches numers == [0-9] \\D Opposite to above == [^0-9] \\x Matches hex numbers == [0-9A-Fa-f] \\X Opposite to above == [^0-9A-Fa-f] \\l Matches lower case letters == [a-z] \\L opposite to above == [^a-z] \\u Matches upper case letters == [A-Z] \\U Opposite to above == [^A-Z] \\w Matches alphanumeric chars == [0-9A-Za-z_] \\W Opposite to above == [^0-9A-Za-z_] \\t Matches \u0026lt;TAB\u0026gt; \\s Matches space == [\\t] \\S Opposite to above Special characters need to be escaped. Some of them are .[]\\*/, if you want to match some of them, put the backslash \u0026ldquo;\u0026quot; ahead. For example: * -\u0026gt; \\*.\nThere are also some special form to express how much do you expect to match the specific pattern.\nMeta char Explanation * match \u0026gt;= 0 times \\+ match \u0026gt;= 1 times \\? match 0 or 1 time \\{n,m} match n\u0026lt;=x\u0026lt;=m times \\{n} match n times \\{n,} match \u0026gt;= n times \\{,m} match \u0026lt;= m times Also, some postional characters.\nMeta char Explanation $ end of line ^ beginning of line \\\u0026lt; beginning of word \\\u0026gt; end of word Some examples:\nremove the spaces of eol: %s/\\s+$//g remove spaces of bol: %s/^\\s*// or %s/^ *// delete empty line: %s/^$// or g/^$/d delete lines with \u0026lt;space\u0026gt; or \u0026lt;tab\u0026gt; as beginning: %s/^[ |\\t]*$// or g/^[ |\\t]*$/d Note that pattern in regex scoped by \\(\u0026lt;pattern\u0026gt;\\) can be refered as \\1, \\2, etc. in the latter statement. For example, I want to replace every \u0026ldquo;abc\u0026hellip;xyz\u0026rdquo; to \u0026ldquo;xyz\u0026hellip;abc\u0026rdquo;, just write like this\n%s/\\(abc\\)\\(.*\\)\\(xyz\\)/\\3\\2\\1/g Advanced Tricks Auto Complete In insert mode, press ^p, vim will give you a list of all words you have typed, kind of auto complete.\nMarkers :marks - list of marks mk - mark current position (can use a-z) also known as :mark k\n'k - move to mark k d'k - delete from current position to mark k 'a-z - same file 'A-Z - between files A straight tick ' refers to the line, use a backtick ` to also include the column, see [here][foo].\nIt seems that the marker . will mark the last edit position, so if you open your last edited file again, `. will take you to that position!\nBlock Editing One of the magic of vim is block editing. Just press ^V to enter block mode. Then select some block you are interested, then make some modifications. Finally press Esc, then those modifications you have just made will be applied onto every line of the block.\nSee this magic:\n^ jump to BOL ^V enter block mode 4j move 4 lines down I enter insert mode and add something Esc to see the effect Additionally, you can\n^v/v/V enter visual mode J joint them into one line \u0026lt; or \u0026gt; modify indents = auto indent (extremely powerful?) or\n^v enter block mode select some line $ jump to EOL A append something Esc see the effect Macro Recording Press qx, where x is the macro name, will enter macro recording mode, all actions will be recorded, just like a tape recorder. If you don\u0026rsquo;t want to record anymore, press q to stop recording.\nTo replay the record, press @x. Moreover, @@ will replay the last recorded macro.\nSummarization:\nqa - record macro a q - stop recording macro @a - run macro a @@ - rerun last run macro\nClipboard Vim provides 12 clipboards (registers): 0, 1, 2 .. 9, a, \u0026quot;. If your vim support system clipboard, there will be two additional register: + and *. Use :reg to see what are in your registers.\nFor X11 systems, things selected or highlighted will be saved in register *, while things yanked or cutted will be saved in register +.\nTo see whether your vim support system clipboard, type $ vim --version\nIn general, all your copy and paste operations are performed at register \u0026quot; by default. To use other register, add a prefix \u0026quot;6 to your yank or paste commands. For example:\n1 2 3 \u0026#34;6p\t\u0026#34; put the buffer in register 6 to file \u0026#34;8yy\t\u0026#34; yank current line to register 8 :put {reg}\t\u0026#34; put things in {reg} to file, \u0026lt;=\u0026gt; \u0026#34;{reg}p Multi-file :e \u0026lt;file\u0026gt; - edit \u0026lt;file\u0026gt; in new buffer bnext - go to the next buffer bprev - go previous bd - delete a buffer (close a file) ls - list all open buffers Multi-window\n:sp \u0026lt;file\u0026gt; - split horizontally and open \u0026lt;file\u0026gt; :vsp - split vertically and open filename optionally, same file by default ^w + h/j/k/l - focus left/down/up/right window :close - close current window (buffer \u0026amp; file) Multi-tab\ntabnew \u0026lt;file\u0026gt; - open \u0026lt;file\u0026gt; in new tab, empty file by default gt or :tabnext - move to the next tab gT or :tabprev - move to previous \u0026lt;num\u0026gt;gt - move to tab number \u0026lt;num\u0026gt; :tabclose - close the current tab (windows \u0026amp; files) :tabonly - close all tabs except for the current one :tabdo \u0026lt;cmd\u0026gt; - apply the \u0026lt;cmd\u0026gt; to all tabs tabdo q will close all tabs\nSpell Check :set spell - toggle on spell checker :set nospell - toggle off spell checker ]s - move to next mistake [s - move tp previous mistake z= - choose an alternative zg - add to userdict zw - remove from userdict Reference Vim 操作 在 Vim 中优雅地查找和替换 简明 Vim 练级攻略 Vim Cheat Sheet Vim cheatsheet Vim 查找替换及正则表达式的使用 ","permalink":"https://guyueshui.github.io/post/vim%E5%91%BD%E4%BB%A4%E9%80%9F%E6%9F%A5/","tags":["vim","reference"],"title":"Vim Quick Reference"},{"categories":["tech"],"contents":"更改字体 Melody 主题字体配置文件在 $BLOG/themes/melody/source/css/var.styl,其中 $BLOG 为 Hexo 博客根目录。截取一段如下:\n1 2 3 4 5 6 7 // Global Variables $font-size = 16px $font-color = #1F2D3D $rem = 20px $font-family = Martel Sans, Spectral, Lato, Helvetica Neue For Number, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif $code-font = Monaco, consolas, Menlo, \u0026#34;PingFang SC\u0026#34;, \u0026#34;Microsoft YaHei\u0026#34;, monospace, Helvetica Neue For Number $text-line-height = 2 这样的话就可以使用自定义的字体 Martel Sans 了。但是这仅限于在本地使用,因为别人的计算机中可能没有这个字体。所以必须制定网页去哪儿加载这个字体。一个方法是,将你系统的字体文件复制到博客根目录的 source/fonts 文件夹。\n1 cp /usr/share/fonts/userfonts/MartelSans-Regular.ttf $BLOG/source/fonts/ 然后编辑主题文件夹下的 index.styl 文件,在文件末尾加上\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // $BLOG/themes/melody/source/css/index.styl ... ... // custom fonts @font-face{ font-family: Martel Sans; src: url(\u0026#39;/fonts/MartelSans-Regular.ttf\u0026#39;); } // 要添加多个字体,亦复如是 @font-face{ font-family: IM FELL DW Pica; src: url(\u0026#39;/fonts/IMFePIrm28P.ttf\u0026#39;); } 在修改完这个文件之后,运行 hexo clean \u0026amp;\u0026amp; hexo g 则会自动在生成的 CSS 文件中加上这段代码,使用指定字体。具体请看\n1 2 3 4 5 6 7 8 9 10 11 12 // $BLOG/public/css/index.css ... ... @font-face { font-family: Martel Sans; src: url(\u0026#34;/fonts/MartelSans-Regular.ttf\u0026#34;); } @font-face { font-family: IM FELL DW Pica; src: url(\u0026#34;/fonts/IMFePIrm28P.ttf\u0026#34;); } 从上面两张图可以看出,指定的字体是从网络加载而非本地。这样以来,执行 hexo deploy 命令部署之后,指定字体将会从正确的网络地址被加载,从而正确应用。\n去除 hrline 的动画效果 Melody 主题对于默认的 hrline 分割线做了效果,我不太喜欢,认为有失简约,移除之。另外 hrline 的上下间距也改小了一点。\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 // $BLOG/themes/melody/source/css/_global/index.styl hr position: relative margin: 1.1rem auto width: calc(100% - 4px) border: 2px dashed $pale-blue background: $white ## add by yychi for removing the ## animation of hrline, 2019-3-16 # \u0026amp;:hover # \u0026amp;:before # left: calc(95% - 20px) \u0026amp;:before position: absolute top: -10px left: 5% z-index: 1 color: $light-blue content: \u0026#34;\\f0c4\u0026#34; font: normal normal normal 14px / 1 FontAwesome font-size: 20px transition: all 1s ease-in-out 去除列表样式 Melody 主题对 markdown 的有序和无序列表做了 css 样式,不喜,移除之。\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 // $BLOG/themes/melody/source/css/_layout/post.styl // add by yychi for removing the // ol,ul styles // 2019-3-16 // 注释掉以下全部代码即可 ol, ul margin-top: 0.4rem padding: 0 0 0 0.8rem list-style: none counter-reset: li p margin: 0 ol, ul padding-left: 0.5rem li position: relative margin: 0.2rem 0 padding: 0.1rem 0.5rem 0.1rem 1.5rem \u0026amp;:hover \u0026amp;:before transform: rotate(360deg) \u0026amp;:before position: absolute top: 0 left: 0 background: $light-blue color: #FF0000 cursor: pointer transition: all 0.3s ease-out ol \u0026gt; li \u0026amp;:before margin-top: 0.2rem width: w = 1.2rem height: h = w border-radius: 0.5 * w content: counter(li) counter-increment: li text-align: center font-size: 0.6rem line-height: h ul \u0026gt; li \u0026amp;:hover \u0026amp;:before border-color: $ruby \u0026amp;:before $w = 0.3rem top: 10px margin-left: 0.45rem width: w = $w height: h = w border: 0.5 * w solid $light-blue border-radius: w background: $white content: \u0026#34;\u0026#34; line-height: h Reference Use multiple @font-face rules in CSS Next 主题自定义 CSS 样式(字体) 使用自定义字体 ","permalink":"https://guyueshui.github.io/post/melody-theme-customization/","tags":["美化","个性化"],"title":"Melody 主题的一些个人更改"},{"categories":null,"contents":"Site Extra Categories Tags:本站标签。 Personal Manual:个人参考手册。 My Advisor Youlong Wu:研究生阶段的导师,像学长一样亲切,在生活和学习上给了我很多的帮助。 Meaningful Blogs Yihui Xie:有个性,有思想的博客(单方面友链)。 Kai Qi:统计学博士在读,有内涵的哥们。 Ryan:同学。 dou.lu:果然有趣的灵魂就是那么吸引人。 Algorithms VisuAlgo Programming Languages Learn You a Haskell Learn You a Haskell (cn) Modern C++ Tutorial Learn X in Y Minutes SICP H5Book Solution Video Lecture: bilibili LaTeX Equation Recognition Symbol Recognition Online Equation Editor Getting Started with LaTeX LaTeX-WikiBook MiKTeX: Smaller LaTeX distribution compared with TeXLive. Table Generator: A handy tool for generating TeX tables. For Fun Full Emoji List, v11.0 Vim color scheme: space-vim-dark SpaceVim WebGlSamples: browser performance test 图床:路过、sm.ms 双拼练习 @ BlueSky:怎么我当时学的时候没发现这个网站👿!我学的时候靠的是一张键位图作为输入法背景,也差不多吧~ 修身养性 中国哲学书电子化计划 历代诗词赋骈曲杂集 搜韵 书法 Fonts 文泉驿:Linux 下的标配中文字体。 思源1:Adobe 和 Google 联合出品,包含宋体、黑体、等宽三种类型,linux 下宋体首选。 文鼎:湾湾出的,有点古朴的气质。 汉仪:现代汉语的精致字体。 方正:书报排版标配字体族。 Google Fonts: 种类繁多的英文字体。 Font Awesome: 流行的图标字体。 Libertine font: SICP 书本排版主体字体,个人觉得非常好看,包含 sans-serif 和 serif 两族。 Archlinux 下使用pacman -S $(pacman -Ssq \u0026quot;adobe-source-han.*cn\u0026quot;)安装之。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/links/","tags":null,"title":"Links"},{"categories":["tech"],"contents":"Markdown 是一门轻量标记型语言,因其简单易用而受众甚广。但是正因其简单,故而也有一部分局限性(虽然说它保留的即是最常用、最基本的排版功能)。本文就来说说在使用 Markdown 排版的时候,如何引入一点 HTML 的技巧来帮助我们排版的更加好看。\n1. 对齐控制 标准的 Markdown 只支持居左对齐。\n1 \u0026lt;center\u0026gt;I am centered\u0026lt;/center\u0026gt; 会排版出居中的效果:\nI am centered 1 2 \u0026lt;!-- right-aligned --\u0026gt; \u0026lt;div style=\u0026#34;text-align:right\u0026#34;\u0026gt;I am right aligned\u0026lt;/div\u0026gt; 会排版出居右的效果:\nI am right aligned 2. 字体控制 通过 HTML 标签,我们可以精确的控制字体族,字体大小,字体颜色,字体形态(粗体,斜体,下划线,删除线)等等。具体参见w3school.\n1 2 3 4 5 6 7 8 9 10 11 12 13 \u0026lt;font size=\u0026#39;3\u0026#39; color=\u0026#34;red\u0026#34;\u0026gt;I am red of size 3\u0026lt;/font\u0026gt; \u0026lt;font size=5px color=\u0026#34;#66CCFF\u0026#34;\u0026gt;I am 天依蓝 of size 5px\u0026lt;/font\u0026gt; \u0026lt;font face=\u0026#34;verdana\u0026#34; color=\u0026#34;green\u0026#34;\u0026gt;I am verdana of color green\u0026lt;/font\u0026gt; \u0026lt;u\u0026gt;I am underlined\u0026lt;/u\u0026gt; \u0026lt;s\u0026gt;I am deleted :(\u0026lt;/s\u0026gt; \u0026lt;b\u0026gt;I am bolded\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;I am italic\u0026lt;/i\u0026gt; \u0026lt;big\u0026gt;I am bigger than you\u0026lt;/big\u0026gt; \u0026lt;small\u0026gt;I am smaller than you\u0026lt;/small\u0026gt; look\u0026lt;sup\u0026gt;at\u0026lt;/sup\u0026gt;\u0026lt;sub\u0026gt;me\u0026lt;/sub\u0026gt; \u0026lt;kbd\u0026gt;Ctrl+Shift+D\u0026lt;/kbd\u0026gt; \u0026lt;q\u0026gt;using this for short quote\u0026lt;/q\u0026gt; \u0026lt;div style=\u0026#34;background-color:black\u0026#34;\u0026gt;举世皆白我独黑\u0026lt;/div\u0026gt; 的排版效果:\nI am red of size 3 I am 天依蓝 of size 5px I am verdana of color green I am underlined I am deleted :( I am bolded I am italic I am bigger than you I am smaller than you lookatme Ctrl+Shift+D using this for short quote\n举世皆白我独黑 3. 使用 FontAwesome 图标 FontAwesome 是一款图标字体集合。以下 Icon 的名称均可以在官网查到:\n1 2 3 4 5 6 7 8 9 10 \u0026lt;i class=\u0026#34;fa fa-check-square\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; completed \u0026lt;i class=\u0026#34;fa fa-square\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; uncompleted \u0026lt;i class=\u0026#34;fa fa-github\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; github icon \u0026lt;i class=\u0026#34;fa fa-weixin\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; wechat icon \u0026lt;i class=\u0026#34;fa fa-rss\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; rss icon \u0026lt;i class=\u0026#34;fa fa-twitter\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; twitter icon \u0026lt;i class=\u0026#34;fa fa-weibo\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; weibo icon \u0026lt;i class=\u0026#34;fa fa-weibo fa-lg\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; large weibo icon \u0026lt;i class=\u0026#34;fa fa-weibo fa-2x\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; 2\\*weibo icon \u0026lt;i class=\u0026#34;fa fa-weibo fa-4x\u0026#34;\u0026gt; \u0026lt;/i\u0026gt; 4\\*weibo icon 的排版效果如下: completed uncompleted github icon wechat icon rss icon twitter icon weibo icon large weibo icon 2*weibo icon 4*weibo icon\n4. 插图控制 大小控制 1 2 3 \u0026lt;!-- 类似的可以设置 height=\u0026#34;100\u0026#34; --\u0026gt; \u0026lt;img src=\u0026#34;miwa.png\u0026#34; width=\u0026#34;100\u0026#34; alt=\u0026#34;miwa-width=100\u0026#34; /\u0026gt; ![miwa](miwa.png) 下面是排版结果的对比:\n图标题 1 2 ![hover text](https://hbimg.huaban.com/fe01cdf198b7da8ffec56f52fcf505acffca258a1fb3a-j6MBCO_/fw/480/format/webp \u0026#34;sample caption\u0026#34;) \u0026lt;figcaption\u0026gt;颇有意境的美少女\u0026lt;/figcaption\u0026gt; 颇有意境的美少女 如此能生效的原因是本站加载了名为figcaption的 css,所以这个标签能够被正确排版。使用本主题 1 只需要在主题文件夹下的_custom.scss中增加:\n1 2 3 4 5 6 7 8 9 // file: \u0026lt;site-root\u0026gt;/themes/even/assets/sass/_custom/_custom.scss figcaption { // background-color: #222; color: gray; padding: 3px; text-align: center; margin-top: -20px; margin-bottom: 20px; } 即可。\n5. ShortCode Hugo 提供了 ShortCode 功能,简单来说就是强大的 html 替换模版,因为直接在 markdown 里面写 html 会显得冗长,所以将一个个排版样式作为 ShortCode 提供给用户使用。详情参考文档,这里列举一些本主题1提供的 ShortCode.\n不建议使用 ShortCode,因为脱离了 hugo,这些元素就无法渲染了。为了保持 markdown 源文件的兼容性,不推荐使用此功能。\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#39;align-center\u0026#39;\u0026gt; \u0026lt;div\u0026gt;\u0026lt;iframe id=\u0026#34;biliplayer\u0026#34; src=\u0026#34;//player.bilibili.com/player.html?bvid=BV1qs411D7Po\u0026amp;page=1\u0026#34; scrolling=\u0026#34;no\u0026#34; border=\u0026#34;0\u0026#34; frameborder=\u0026#34;no\u0026#34; framespacing=\u0026#34;0\u0026#34; allowfullscreen=\u0026#34;true\u0026#34; loading=\u0026#34;lazy\u0026#34; \u0026gt; \u0026lt;/iframe\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;style\u0026gt; #bilibili { width: 100%; height: 550px; } @media only screen and (min-device-width: 320px) and (max-device-width: 480px) { #bilibili { width: 100%; height: 250px; } } \u0026lt;/style\u0026gt; sample b23 video desc \u0026lt;/div\u0026gt; 完球,上述代码段已经是展开后的形式了,应该是 hugo 转网页的时候一定会做替换,目前还没找到 escape 的方法,先将就着看吧。\n可排版出如下内容\nsample bilibli video desc\n但其实上述内容在普通 ShortCode 在 vscode 中的排版效果 6. 代码高亮 NOTE: 这个功能依赖 hugo,不建议使用。\nCf. https://gohugo.io/content-management/syntax-highlighting/#highlighting-in-code-fences\n```go {linenos=table,hl_lines=[8,\"15-17\"],linenostart=199} // GetTitleFunc returns a func that can be used to transform a string to // title case. // // The supported styles are // // - \"Go\" (strings.Title) // - \"AP\" (see https://www.apstylebook.com/) // - \"Chicago\" (see https://www.chicagomanualofstyle.org/home.html) // // If an unknown or empty style is provided, AP style is what you get. func GetTitleFunc(style string) func(s string) string { switch strings.ToLower(style) { case \"go\": return strings.Title case \"chicago\": return transform.NewTitleConverter(transform.ChicagoStyle) default: return transform.NewTitleConverter(transform.APStyle) } } ``` 会排版出如下效果\n199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 // GetTitleFunc returns a func that can be used to transform a string to // title case. // // The supported styles are // // - \u0026#34;Go\u0026#34; (strings.Title) // - \u0026#34;AP\u0026#34; (see https://www.apstylebook.com/) // - \u0026#34;Chicago\u0026#34; (see https://www.chicagomanualofstyle.org/home.html) // // If an unknown or empty style is provided, AP style is what you get. func GetTitleFunc(style string) func(s string) string { switch strings.ToLower(style) { case \u0026#34;go\u0026#34;: return strings.Title case \u0026#34;chicago\u0026#34;: return transform.NewTitleConverter(transform.ChicagoStyle) default: return transform.NewTitleConverter(transform.APStyle) } } Reference 没有内容的 HTML 元素被称为空元素。空元素是在开始标签中关闭的。\u0026lt;br\u0026gt; 就是没有关闭标签的空元素(\u0026lt;br\u0026gt; 标签定义换行)。在 XHTML、XML 以及未来版本的 HTML 中,所有元素都必须被关闭。在开始标签中添加斜杠,比如 \u0026lt;br /\u0026gt;,是关闭空元素的正确方法,HTML、XHTML 和 XML 都接受这种方式。即使 \u0026lt;br\u0026gt; 在所有浏览器中都是有效的,但使用 \u0026lt;br /\u0026gt; 其实是更长远的保障。\n——w3shcool [HEXO] NexT 主题提高博客颜值 HTML 元素 Hugo theme even\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/html-%E7%BE%8E%E5%8C%96-markdown-%E6%8E%92%E7%89%88/","tags":["排版","美化","html"],"title":"HTML 美化 Markdown 排版"},{"categories":null,"contents":"2024 年 7 月 31 日,买定一辆二手车,红色大众 Polo,2014 款 1.4L 手动风尚版(丐版),也算是人生第一辆车了。虽然空间很小,但胜在省油耐造,小巧灵便。\n2022 年 10 月 大喜:喜得一女。 2022 年 9 月 愈发觉得个人博客,还是要多些原创内容。抄的学习笔记,发布出来自己都不会看的。只有自己动手写的,费心整理的,日后回顾才有记忆。\n2022 年 5 月 shell 字符串截取:http://c.biancheng.net/view/1120.html\nhugo 起本地服时,偶尔会出现本地文件已经修改,但预览网站还是未更改的状态(多次重新关闭/启动服务都不好使),此时可采用\n$ hugo server --config even-config.toml --gc The memory that a program uses is typically divided into a few different areas, called segments:\nThe code segment (also called a text segment), where the compiled program sits in memory. The code segment is typically read-only. The bss segment (also called the uninitialized data segment), where zero-initialized global and static variables are stored. The data segment (also called the initialized data segment), where initialized global and static variables are stored. The heap, where dynamically allocated variables are allocated from. The call stack, where function parameters, local variables, and other function-related information are stored. The heap has advantages and disadvantages:\nAllocating memory on the heap is comparatively slow. Allocated memory stays allocated until it is specifically deallocated (beware memory leaks) or the application ends (at which point the OS should clean it up). Dynamically allocated memory must be accessed through a pointer. Dereferencing a pointer is slower than accessing a variable directly. Because the heap is a big pool of memory, large arrays, structures, or classes can be allocated here. The stack has advantages and disadvantages:\nAllocating memory on the stack is comparatively fast. Memory allocated on the stack stays in scope as long as it is on the stack. It is destroyed when it is popped off the stack. All memory allocated on the stack is known at compile time. Consequently, this memory can be accessed directly through a variable. Because the stack is relatively small, it is generally not a good idea to do anything that eats up lots of stack space. This includes passing by value or creating local variables of large arrays or other memory-intensive structures. Cf. https://www.learncpp.com/cpp-tutorial/the-stack-and-the-heap/\n2022 年 4 月 Posix 扩展正则表达式:https://imliuda.com/post/16\n2020 年 11 月 给老婆买了一加 8T,性价比太高了吧!内存翻一倍只加 300 块!\n2020 年 10 月 月初干了一件惊天地,泣鬼神的大事儿:我结婚啦!老婆很漂亮,我很爱她,她也爱我!希望以后可以辛福美满,步步高升~\ncollections.deque v.s. Queue.Queue in python\nQueue.Queue and collections.deque serve different purposes. Queue.Queue is intended for allowing different threads to communicate using queued messages/data, whereas collections.deque is simply intended as a datastructure. That\u0026rsquo;s why Queue.Queue has methods like put_nowait(), get_nowait(), and join(), whereas collections.deque doesn\u0026rsquo;t. Queue.Queue isn\u0026rsquo;t intended to be used as a collection, which is why it lacks the likes of the in operator.\nIt boils down to this: if you have multiple threads and you want them to be able to communicate without the need for locks, you\u0026rsquo;re looking for Queue.Queue; if you just want a queue or a double-ended queue as a datastructure, use collections.deque.\nFinally, accessing and manipulating the internal deque of a Queue.Queue is playing with fire - you really don\u0026rsquo;t want to be doing that.\n如无必要,勿增实体。\nsee: https://stackoverflow.com/a/717261\n2020 年 9 月 mini 自闭中\u0026hellip;,大作业还未完成!\n2020 年 8 月 mini 开发中\u0026hellip;\n2020 年 7 月 在 LaTeX 里面排表格真是件苦差事,送你一个相见恨晚的网站:https://tablesgenerator.com\n2020 年 6 月 How to close GUI on Ubuntu?\n1 sudo /etc/init.d/lightdm stop see: https://segmentfault.com/q/1010000000369635\n2020 年 5 月 2020 年 5 月 20 日,Yukynn 说:昨晚我做梦了,梦到你打电话把我叫醒,说买了车票来看我。她说着,脸上还带着笑,明明早上还有些失落。我实在太惭愧了,自己有拖延症,前期浪费时间,后期疯狂弥补,导致没有足够的时间陪她。懒惰,是我最大的原罪。忏悔,从来只是对过往的无奈。改变,始于当下!\n2020 年 4 月 即便是内心再强大的女生,都有脆弱的时候。我的未婚妻,在我看来她已经是个非常独立、自强的人了,比我早步入社会,阅历比我丰富一些。但是在工作的重压、减肥的营养不良、未婚夫的淘气重重打击之下,再坚实内心防线也会在积水蓄满之时决堤。这时候就是体现我作用的时候了,我的安慰,我的体贴,能够最大限度的让她安心,让她平静。在这种时候,我知道该怎么做,即便我们之前还因为一些小矛盾闹别扭。但是在她哭的一瞬间,什么都不重要了,什么都化为乌有。内心只有心疼!还有什么比两个人携手一起走更重要的呢?互相搀扶,走向明天!\n2020 年 3 月 近日跟朋友聊到搭建个人网站的事情,又看到 谢益辉 关于搭建个人网站重要性的描述,于是极力向 Q 君推荐:“赶紧搭建一个属于自己的网站吧。”Q 君以前是写公众号的,我说公众号平台毕竟不够自由,十年后会是什么样子也不知道。万一哪天出台个政策劝退又当如何呢?反观自己的网站,虽然没有那么大的流量,但自己的始终是自己的。自己可以写自己想写的东西,无论精华还是糟粕,只要你想将它写下来,都可以!十年后,二十年后它都在那里,那时候在回忆起来,无论你是什么感受,但总比没有回忆的强。\n这么说着,果然 Q 君这两天搭建起了个人网站。我看他将以前写的文章诗词也都迁移上来,看过之后觉得挺好,起码算个见证,见证以前的自己,了解自己的成长吧。自我搭建网站以来,也迁移了一些以前写的东西,但是大部分还是比较惭愧,写的不知道是个啥,也就不好意思弄上来。但是今天忽然脸皮厚了,决定全部给他搬上来,看官如果觉得不适,那我也没有办法。\n业生,于江湖,我固为池鱼。是大道无谓,大业不毕。\n——易之于本科毕业(2017/6/9) 2020 年 2 月 为什么总是把最爱的人弄哭呢?常听一句话:你也就只能伤害爱着你的人了!这是多么可悲的评价,我似乎也在逐渐演变成自己讨厌的样子,悲上加悲!眼高手低,高估自己,却被现实啪啪打脸,到最后发现自己什么都不如别人。顶着村里人的谬赞,自娱自乐,玩物丧志,最后发现连自己擅长的也比不过别人了。为什么会陷入这样一种状态?我现在如果能认识到,就该能走出来了。\n再谈到未婚妻,明明那么好一个人。脾气上是有点小倔强,却是居家过日子的小能手。外表刚强,内心柔软,只有走进她内心的人才能伤害到她,我很惭愧经常把她弄哭。或许我自己身上的毛病比我自知的还要多,我也不知道什么时候能够成熟到让她放心依靠,但是我是真的不想伤害她,我有时候意识不到自己的错误,很惭愧,我已经如此迟钝了。\n应该吾日三省吾身:\n今天主动关心她了吗? 今天让她伤心了吗? 今天知道哪里做得不够好了吗? 千万不要执着于吵架纠纷,不要陷入口水战,要跳脱,抄近路,近路其实早已经铺好了。你我在恋爱之前都明白的事,只有局中人自己蒙在鼓里:尊重、诚意、关爱、可供依靠!当然抄完近路别忘了反省自己,下不为例,如果下次再犯同样的错误,会伤人更深。只有不断修正,才能成为真正可以依靠的男人!\n2020 年 1 月 一年又过去了,都没有好好总结,我开始了刷游戏的生活!今天有份作业要交了,但是 之前压根没花多少心思在上面,导致现在犯难做不出来。然后网上各种找博客,又翻到几 个不错的博客,顺着望过去,人家本科就已经有培养自己的意识,并且对未来规划的十分 明晰。他们的生活过得十分充实,反观自己,却是一事无成。论文实验效果不行,想不出 新的 idea,培训作业不花心思,导致现在焦头烂额,呵,瞧瞧自己这怂样!还想早点回家 看牙齿,P 嘞,这样下去吃枣药丸!\n2019 年 12 月 Python 字符串格式化输出:\n1 2 3 4 5 6 # @number 小数点前占 3 位,小数点后占 4 位 print(\u0026#39;This is a string {:3.4f}\u0026#39;.format(number)) print(\u0026#39;This is left aligned {:\u0026lt;}\u0026#39;.format(var)) print(\u0026#39;This is right aligned {:\u0026gt;}\u0026#39;.format(var)) print(\u0026#39;This is center aligned {:^}\u0026#39;.format(var)) print(\u0026#39;This is placeholder of 10 char {:\u0026gt;10s} and right aligned\u0026#39;.format(var)) 时至今日看到小孩子们成群结队的活动,还是会回想起当初我也是个小孩子的时候。看到他们,就好像看到希望。以前不能够理解孩子是祖国的花朵,现在,结合身边的事情,以及以往的见闻。确实能够明白这句话是多么正确了!\n大人的表情千篇一律,但小孩的表情却千变万化,而且更加纯粹。这让以前同样纯粹的大人们看见,会不由得勾起对以往的怀念。我们小时候也是这样天真烂漫,三五成群,一起上学,一起吃饭,一起上课,一起玩耍。当初觉得一切来的自然,我也接受的自然。可是今天我却看到了背后的力量。有些孩子有家长陪同,有些孩子成群有老师陪同。我想,孩子们能参加一次像样的活动,背后总离不开家长和老师的付出。家长各不相同,家长的生活方式也各不相同,能力也各不相同,但他们努力挣钱养家的意愿是一样的。富有富的活法,自不必说;穷也有穷的活法,也会尽力给孩子提供良好的生长环境。这种共性让人肃然起敬!大人们在外打拼不易,也有苦有乐,乐自不必说,支撑他们在苦中作乐的很大一部分来自孩子。孩子须依托大人才能生存,大人也能从孩子身上看到希望,寄托精神。这算是抱团取暖吧。思来想去,人的一生,不就是在传火吗!\n于是,大人要在外打拼供养生活。孩子的教育很大一部分落到了老师身上,文化教育自不必说,价值观与人性的教育也是需要老师带领的。小时候有一个良师,那将是受用终身的好事。启蒙老师就是这么一回事儿吧,他带你看到一个点,然后你就逐点成线;又好像是授人以渔,教育你对待某类事情的看法和处理方式;又好像是一个你很想要模仿的朋友,你朴素的期望着:我也想变得和你一样 kilakila~ 想到这里,不得不感叹教育工作者的伟大!教书育人,无论哪个都是个重担子啊。教育的重要性,从最近的香港事件中可见一斑。孩子是很纯粹的,你怎么教,他很大程度就会变成什么样!所以文化入侵,精神入侵这种攻击比冷热兵器的影响更加久远深长,一个字,脏!老师所教给学生的,除了科学文化知识,还有寻求真相的本领,如果寻不到真相,也不要轻易听信谣言,即使听到了谣言,也要有自己的判断!这一整套的框架,是从小到大培养出来的,非一人之功,非一师之过。\n希望所有接触孩子的老师都能明白:你的教育,往大了说,影响着祖国的未来;往小了说,也影响着自己的孩子。\n小有感想,无他,写将出来罢了。\n2019 年 11 月 又开启了新的一月,秋招应该要结束了,就差收个尾了。总体来说,我的战线还是挺长的,从 8 中旬开始陆续笔试,9 月更是一大波笔试和面试,然而大部分公司,都是笔试过后就没有消息。9 月底有幸收获两个 offer,白山云是第一个,心里真的很感激,曾经一度认为我可能找不到工作了。然后就是小米,base 南京,之所以能拿到小米的 offer,可能是因为没有笔试,因为笔试的基本全部没有音讯。小米的两面到发 offer 经历的时间也是挺长的,南京也确实很好,就是待遇稍微和期望的差了一点吧。然后步入 10 月,拿到盛趣游戏的转正 offer,给的比前面两个要高一点。之后就是网易互娱的 offer,base 杭州,有幸能拿到网易的 offer,我已经相当高兴了。虽然给的是白菜价,岗位也是新手岗,但是毕竟平台大,成长空间应该也很大,努力奋斗几年后应该就会好些了。10 月就是各种面试,就连很久之前笔试过的公司,也过来捞人去面试,可能是因为那些大佬拒了 offer,然后补招吧。总之 10 月的笔试相对就轻松多了,基本上只要参加了,都有面试机会。其中不巧的一点就是新东方了,当时只是无意海投,也没注意看地点,结果阴差阳错拿到了 offer,可惜只有北京的工作机会,给的也还挺高的,也不加班,但是在北京,待遇还不足以让我放弃很多东西去北漂。然后今天又收到了 58 同城的 offer,可惜要转 java,给的很低,虽然早就听说 58 给的低。\n不过能收到人家的 offer,也表明人家对我的认同,心里还是高兴的,至少我并不是一无是处啊!如今秋招已进入尾声,好好收个尾,然后专心搞毕业的事情吧!\n在信息化的现代,我愈发觉得人们接受的信息远远多于自己所需。人的一天本来可以有更多的休息时间,却被大量不必要的信息填充。各大视频网站,新闻 APP,不可一世的推荐算法,轰炸式,瀑布流的一个接着一个,你只要动动手指,它们就一个一个跳入你的眼中,占用了本来用于休息的时间。我知道大多数人将这种活动本身当成一种休息,但是休息的形式很多,可以去运动,可以去睡觉,这种刷视频刷文章的休息方式实在不是最佳之选。况《道德经》有言:少则得,多则惑。就我自身体会,这句确实太妙了!接触的东西太多,反而会使人迷惑。每个人都有自己的个性,对同一件事会有不同的看法。\n君可曾见,一件事情发生了,社会舆论一边倒,于是你也有些人云亦云。简单了解了一下该事件的始末,你也得出了类似的结论。随着该事件的发酵,后续出现了惊天反转,先前的舆论被证明不恰当,此时的你,可能会有些尴尬:要是我早知道这一点,肯定也能做出恰当的评论。其实不然,人很容易受环境影响,所以阅读了他人的想法之后,便很难再做出属于自己的那份评价了。\n再退一步,这一来一回,你有何收获?其实大部分时候,我们浏览网上那些所谓大事件,然后翻评论,然后吃瓜,然后简单了解事情原委,然后自己再发个评论。这一顿操作还挺费时间的,但是却并没有什么实质性的收获。而且,有些事情在网上根本无法求证,有句话说的好:你所看到的,只是有心人士希望你看到的。真假亦无法求证,更别说这来回折腾还有什么意义了。所谓少则得,就是一个人知道自己需要做的事,把需要自己做的每一个部分做好,然后享受闲暇就好了。我想我很久没有享受过闲暇了,有也只是偷来的,以后还得还的。\n繁华的闹市里,就像一锅煮沸的什锦汤,世事翻腾。如果你的介入起不了什么作用,不如做好自己的一隅,所谓达则兼济天下,穷则独善其身。举个栗子,小猫爱吃鱼,有人喜欢猫,有人给小猫送食。我觉得吧:如果不能一直授之以鱼,那就授之以渔,如果连这个也做不到的话,那就别去打扰它的世界。所谓天地不仁,以万物为刍狗,皆是如此。\n写着写着,好像跑偏了。\n同学 M 获得了 A 厂的 offer,日前就获得另外两家大厂的 offer,果然运气也是能力的一 部分啊。据说是 M 的男友介绍的岗位,但是 M 桑好歹通过了几轮面试,还是相当有能力 的。反观我自己,心中难免有不甘。找工作也找了很久,但是始终没有找到能给到期 望薪水的公司。正在觉得自己可能也就这样了的时候,M 桑又跳出来,拿到一个如此 nice 的 offer,不甘很正常。但是更让我感到害怕的是,心中居然起了妒火,嫉妒 M 桑 有一个如此给力的男友?不,显然是对自己无能的一种逃避。几经辗转我最后选择了 N 厂的 offer,但是传闻最近 N 厂疯狂裁员,而且还裁应届生。这让我一度感到十分 为难:好不容易找到工作,还要担心被裁!\n其实我早已隐约察觉到,自己变得越来越浮躁。心境不如以前稳定,对事物的把控也 没有以前那样张弛有度。我一度以为这是在上海这样快节奏的生活中熏陶出来的。曾 经我的腿受伤,走路走不快,从宿舍到实验室的那段路,也觉得十分难走。为什么? 因为你旁边的人都在以风一般的速度前行,大家都在争分夺秒。而只有我自己在慢慢 走路的话,难免会觉得自己是不是掉队了?在这样的大环境下,优胜劣汰,我可不允 许自己就这样被人家甩在后面呀!说是夸张了点,但是这确实折射出我在上海生活的 压力。\n究竟为何变得越来越浮躁,我现在不得而知,可能和周围的快节奏生活环境有一点 干系,所以我不太想留在上海这样的大都市了。我很讨厌这样的自己,贪嗔痴恶欲等 无名之火油然而起,我并不喜欢这样。人需要约束,需要自律,需要节制!\n2019 年 10 月 今年十一算是比较特殊的,因为,我订婚了。似乎有点快,但也是必将发生的事,女票成为了未婚妻,感情一直很好,虽然谈了不到两年,但是我们从初中开始就认识,家又离得近,有十几年的感情基础,相互之间十分信任。说给同学听也都纷纷表示支持和羡慕!人生嘛,有妻如此,夫复何求。所以啊,10 月求一个高薪 offer!\nC++ 生成随机字符串\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include \u0026lt;ctime\u0026gt; char* rand_str(char* str, const int len) { srand(time(NULL)); for (int i = 0; i \u0026lt; len; ++i) { switch (rand() % 3) { case 1: str[i] = \u0026#39;A\u0026#39; + rand() % 26; break; case 2: str[i] = \u0026#39;a\u0026#39; + rand() % 26; break; default: str[i] = \u0026#39;0\u0026#39; + rand() % 10; break; } } // NOTE: please ensure @str[len] is in valid memory range. str[len] = \u0026#39;\\0\u0026#39;; // terminate flag return str; } 被人嘲笑也没关系,默默做好自己该做的事。人间很浮躁,很容易就飘了,你要做的就是把底盘打牢,不要被人家左右。有自己的思想,并为之努力就好。\n2019 年 9 月 C++ 静态函数有什么不同?\n静态函数具有单文件作用域,不能被其他文件所用 不会和其他文件中的同名函数冲突 静态函数分配在程序的静态内存区,对于频繁调用的函数,声明成静态可以避免调用函数时压栈出栈,速度快很多 递归 lambda 表达式写法\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include \u0026lt;iostream\u0026gt; #include \u0026lt;functional\u0026gt; int main() { std::function\u0026lt;int(int)\u0026gt; fact = [\u0026amp;fact](int n) { if (n == 1) return 1; else return n * fact(n-1); }; std::cout \u0026lt;\u0026lt; fact(4); return 0; } 忙碌的 9 月终于过了,整个 9 月一直处在秋招的洪流之中:改简历、投简历、做笔试、在线面试、现场面试。可能还是能力不够,很多公司做了笔试都没有消息了,有的公司甚至简历被刷。真的是“书到用时方恨少”,等到找工作的时候才知道学少了,要是当初多学一些就好了。奈何不是科班出身,自学毕竟有限,所以到处碰壁。只能针对专门的知识点快速学习一遍,既缺乏实践,又缺乏理解,所以了解的都不深入。吃饭的家伙,还是得系统的学习一遍比较好啊。10 月加油!\n2019 年 8 月 8 月的第一周,就惹我家领导生气了。问题还蛮严重的,第一次感觉到,原来感情真的很脆弱,就像鸡蛋壳一样。一不小心,就容易捅破。好的感情需要用心经营,很明显是我经营不善,我必须自食其果。这种问题都是常见的问题,但是还是会犯,明明听过很多前车之鉴,但自己就是犯贱,偏要以身试法。何苦来!跳脱不开,陷于苦海,即便早该明白的,也早已经明白的道理,没有切身的感受,终究不能领悟,不知代价。人之一物,可能大都是这样的。\n不要跟气头上的女人讲道理。 遇到女人生气,一个字,让! 女人生气的时候是最感性的时候,所以千万不要跟她讲道理。 女人往往更在乎你对她的态度,而不是事件的对错。 如果你发现女人难懂了,那说明你这段时间对她的关注度不够。 爱护女票,从我做起!(不要只是嘴上说说,行动起来!) 关于移位操作的几个注意事项:\nchar 类型移位前务必先做类型转换(转为 unsigned char),默认先转成 int 再移位,如果原来的 char 是 unsinged int 转来的,可能会和你预期的不符 移位一般只针对无符号类型,有符号类型的移位往往和你预期不符 移位超过 32 位要先转换成 long 类型 C++ 计算程序运行时间\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include \u0026lt;iostream\u0026gt; #include \u0026lt;time.h\u0026gt; int main() { clock_t start_time = clock(); { // tested block } clock_t end_time = clock(); double elapsed_time = static_cast\u0026lt;double\u0026gt;(end_time-start_time) / CLOCKS_PER_SEC * 1000; cout \u0026lt;\u0026lt; \u0026#34;Running time is: \u0026#34; \u0026lt;\u0026lt; elapsed_time \u0026lt;\u0026lt; \u0026#34;ms\u0026#34; \u0026lt;\u0026lt; endl; return 0; } C++ 对象析构顺序\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 #include \u0026lt;iostream\u0026gt; using namespace std; struct A { A() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } ~A() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } }; struct B { B() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } ~B() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } }; struct C { C() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } ~C() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } }; struct D { D() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } ~D() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } }; struct E { E() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } ~E() { cout \u0026lt;\u0026lt; __FUNCTION__ \u0026lt;\u0026lt; endl; } }; void func() { static D d; } static A a; int main() { B b; C c; func(); func(); func(); static E e; return 0; } 输出:\nA B C D E ~C ~B ~E ~D ~A 从这个输出中可以看出,对于局部对象,先构造后析构。全局静态对象最后析构,局部静态对象的析构顺序不定,但总体保持先构造者后析构的规律。可以看到,无论func()调用几次,静态对象只在第一次调用时构造。\n感觉最近很忙,但又不知道忙什么。感觉有忙不完的事情,但又不知道从何忙起。细想来,其实就两件事:一个毕业,一个工作。踏踏实实干吧!\n2019 年 7 月 一个虚基类(纯虚函数构成的接口类,abstract type)不能生成实例,所以不需要定义构造函数。就算定义了,也无法通过构造函数生成对象。 构造函数不需要、也不能够声明为虚函数。here 析构函数可以声明为虚函数,而且作为基类的类建议声明虚析构函数。但析构函数不能声明为纯虚函数。 任何一门语言都有三要素:\nPrimitives Means of combination Means of abstraction ──SICP 出现死锁的条件:\n资源互斥,某个资源在某一时刻只能被一个线程持有(hold); 吃着碗里的,还看锅里的,持有一个以上的互斥资源的线程在等待被其它进程持有的互斥资源; 不可抢占,只有在互斥资源的持有线程释放了该资源之后,其它线程才能去持有该资源; 环形等待,有两个或两个以上的线程各自持有某些互斥资源,并且各自在等待其它线程所持有的互斥资源。 v2ray 配置小结:\nTLS 证书 配置文件生成 配置模板 测试 vps 是否被 ban 的网站 关于防火墙\ncentos 的防火墙默认使用 firewalld,使用\n1 systemctl start/stop firewalld 来开启或关闭。\n常用命令,\n1 2 3 4 5 6 7 8 ## 查看已开放端口 firewall-cmd --zone=public --list-ports ## 添加开放端口 firewall-cmd --zone=public --add-port=80/tcp --permanent #允许 udp firewall-cmd --permanent --add-port=12345/udp #重新载入防火墙以使配置生效 firewall-cmd --reload 或编辑/etc/firewalld/zones/public.xml.\n添加到 system unit\n[Unit] Description=V2Ray Service After=network.target Wants=network.target [Service] # This service runs as root. You may consider to run it as another user for security concerns. # By uncommenting the following two lines, this service will run as user v2ray/v2ray. # More discussion at https://github.com/v2ray/v2ray-core/issues/1011 # User=v2ray # Group=v2ray Type=simple PIDFile=/run/v2ray.pid Environment=V2RAY_LOCATION_ASSET=/etc/v2ray ExecStart=/usr/bin/v2ray -config /etc/v2ray/config.json Restart=on-failure # Don\u0026#39;t restart in the case of configuration error RestartPreventExitStatus=23 [Install] WantedBy=multi-user.target 添加完毕后即可使用如下命令开启/关闭服务\n1 2 3 systemctl start/stop v2ray.service # on/off systemctl status v2ray.service # status systemctl enable v2ray.service # on-boot start 读取两行整数,每一行放入一个 vector\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 #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;sstream\u0026gt; using namespace std; int main() { vector\u0026lt;int\u0026gt; v1, v2; for (int i = 0; i != 2; ++i) { string line; std::getline(cin, line); istringstream is(line); if (i == 0) { // use implicit conversion for (int val; is \u0026gt;\u0026gt; val; v1.push_back(val)) { } } else { // or use std::stoi for (string val; is \u0026gt;\u0026gt; val; v2.push_back(std::stoi(val))) { } } } for (auto e : v1) cout \u0026lt;\u0026lt; e \u0026lt;\u0026lt; \u0026#34; \u0026#34;; cout \u0026lt;\u0026lt; endl; for (auto e : v2) cout \u0026lt;\u0026lt; e \u0026lt;\u0026lt; \u0026#34; \u0026#34;; return 0; } 2019 年 6 月 6 月 1 号是儿童节,但是却遇到了一对小人!没想到在文明的社会还会有这样的人存在,没办法,只能敬而远之,穷则独善其身。可怜了我家领导,要受这么大的气!\nStatic members\n1 2 3 4 5 6 7 8 9 10 11 12 13 class A { public: // non-static member (i.e., `data` is not visible in `fun1` static fun1(); fun2(); private: int data; static int sata; }; A a; a.fun1(); // valid, equivalent to the following A::fun1(); 静态成员不能访问非静态成员(因为静态成员独立与类的实例(即对象)而存在,为了在没有对象被创建的情况下,静态成员还是可以使用,所以不能访问非静态成员。) 同理,类的任何对象不包含静态数据成员 静态成员不与对象,不与this指针发生交互,作为结果,静态成员函数不能声明为const 可以通过类的对象调用静态成员函数,但此调用跟对象的状态并无关系,也就是说换个对象来调用是等价的,都等价于使用类名加域作用符来调用 静态成员一般定义在类的外部,因为每个对象都共享静态成员,避免多次定义 View static member as a normal function that has nothing to do with the class, except you must use :: to access static members 使用stringstream分割字符串\n1 2 3 4 5 6 7 8 9 10 11 12 13 #include \u0026lt;iostream\u0026gt; #include \u0026lt;sstream\u0026gt; using namespace std; int main() { stringstream ss(\u0026#34;i,love, you\u0026#34;); string token; while (getline(ss, token)) { /* mark */ cout \u0026lt;\u0026lt; token \u0026lt;\u0026lt; endl; } return 0; } getline可以传入额外的分割符参数,类型为const char*,默认是换行符\\n. 上面的代码片段 mark 处可变:\nwhile (getline(ss, token)) 输出\ni,love, you while (getline(ss, token, ' ')) 输出\ni,love, you while (getline(ss, token, ',')) 输出\ni love you 这算是我上大学以来第一次回家过端午节。以前总是看着别人在空间或者朋友圈里晒吃粽子,自己在外面却因为粽子太贵而不愿去买一个来吃,想想还是有点心酸。回家发现,奶奶又老了,听力更加不如以前了,感觉嗓子也有点沙哑了。岁月总是那么不饶人,新陈代谢不会因为任何人的感情而改变步伐。总想着自己可以早点出来挣钱,证明给他们看,这个人没白养活,可以自己挣钱了。也想着,总是拿家里的,什么时候可以自己挣钱给家里用呢?和女朋友异地,什么时候从能在一起呢?等我出来了,还要养家糊口,偏偏在最缺钱的时候,最需要钱!这个社会也不知道是怎么了,风气流转的近乎诡异。\n迷茫,真的很迷茫。自从开始找工作以来,就无法静下心来搞科研了,看论文的事情优先级一直排在最后。有点空余时间,自学 C++,都不想去碰论文的事。然而自学的东西,依旧是基础的算法和数据结构,以及一些基础的语言特性,还是不知道项目中,工程中如何使用 c++. 一直想找一个锻炼的机会,实践的机会。无奈实习一直没有着落,今年的算法岗实在是供远远大于求,众人同挤独木桥,毕竟还是干不过科班出生的那些 cs 大佬。于是转后台,自学 c++,说起来一直对 c++ 有执念。这次实习也是一心想找 c++ 的岗。但我自知 c++ 并不是这么简单的东西,从写玩具代码,到写出真正有意义的工程代码,我还要很长的路要走。哎,好想要 offer 啊,好想看看工程中大家是怎么做的,好想见识见识世界啊,好想挣钱啊!\nHow to write comments in your code?\nAt the library, program, or function level, use comments to describe what. Inside the library, program, or function, use comments to describe how. At the statement level, use comments to describe why. ──learncpp.com 前段时间给《千与千寻》补了票,真的是小时候看的是志怪,奇妙,瑰丽的想象,长大后看的是内在。之前一直都不太明白无脸男在剧中的作用,觉得这个角色可有可无。后来上豆瓣看了一些影评,少许获得了一点感知。但是今天的重点不是无脸男,而是油屋工作的那些忘了自己名字的劳动者。说句实在的,劳动换报酬,这无可厚非,甚至还可以通过劳动实现自己的价值。但是油屋那些劳动者们,事实上是被剥夺了名字,再也找不回来了。我们可以把名字,理解成真名、本心。从小到大,绝大多数人都走上了既定的轨道,就像油屋的劳动者们。很多人小时候有着单纯的梦想,却随着接触越来越多的大人而慢慢被同化,这个世界不劳作只有消失,不劳作就会被淘汰。我们认认真真把自己打扮成一个个劳动者,去到一个个劳动单位,生怕人家不要,生怕被淘汰。有的时候甚至意识到,有些东西小时候都懂,到现在反而忘记了,作为一个大人,反而忘记了原本已经懂得的那些单纯美好的意识,这是社畜所不需要的,所以在成长的过程中,注定被抹消。但是你偶尔又会回忆起,或是在深夜辗转反侧的时候,或是看到影视作品中类似情节的时候,回忆起小时候踌躇满志的单纯的自己。你可能会慨然长叹:这么久了,我得到了什么?又失去了什么呢?\n最惨的是当你意识到这些,却发现无可奈何。生活还是会啪啪的打脸:“你的问题在于做得太少,而想得太多”。然后第二天,继续回到油屋上班吧!可是,有的人做着做着,会麻木的啊。他们渐渐不再动脑,不再有各种奇妙的想法,每天的思维都是固定的几个圈。这无异于行尸走肉。所以,我们需要这样的醍醐灌顶,需要精神食粮。这也是为什么有一大堆哲学家天天吃饱饭没事做在那瞎悟,人家的工作就是化几年甚至几十年的光阴,悟到一瞬的真谛。但是也没用,这传达不了给我们。我们只能听到他们说的话,阅读他们留下的文字,却不能体会他们的体会。这并不具有传递性,想要拥有,只能自己去经历,自己去感受,自己去领悟!\n莫道君行早,更有早行人。\nBest practice?\nUse exit(__status__) in main() Use throw in other modules 2019 年 5 月 说起来已经很久没更新了。实在是没什么可以写的,这段时间的状态很差,如咸鱼一般。昨天(25 日)去听了花碳的 live,也实在是想体验一下这种事情。毕竟曾经涉足宅圈,也费劲心思上过 N 站,看喜欢的唱见。曾经那么如痴如醉的追番,看弹幕,听 op、ed,追专辑,从动画开始,饭起它的周边一切,最后居然延伸到了日剧。文化入侵说的就是这么回事儿吧。而今,没那么多时间看了,也时过境迁,不比当时。谨以此次 live 怀念一下逝去的青春罢。\n关于 live,花碳的形象和我想象中的有一点出入,不过也是情理之中,毕竟人家声音那么有张力,高音炮那么稳,颤音也控制得很好,一个萝莉身材也做不到这些呀。然后就是《笹舟》现场不是花碳本人唱的,有点遗憾。live 一点开始,花碳两点二十就打算溜了,而且这一个多小时也没唱几首歌,有 pokato 的配合,以及山崎的演奏,加上中间的互动和对话,真的没唱几首。虽然说现场确实是稳,但到那时为止,经典曲目《心做し》、《自傷無色》、《ロミオとシンデレラ》等一首没唱。好在后来大家一起喊“Encore”才又回来唱了几首,包括以上两首(让人感觉像是安排好的,爱演?这样不太好吧)。不得不说花碳现场还是很强的,唱《ロミオとシンデレラ》的时候我打 call 打的快飞起来了 (*/ω\*),第一次这么激烈的打 call。然后观众打 call 好像很有讲究,我看不明白,就随便一顿乱挥。总的来说,还是比较不错的一次经历,毕竟 live 不必演唱会,可以近距离看到想看的人,甚至和他们互动。真棒,以上。\n2019 年 4 月 4 月,希望能找到一份实习。\n经历了很多失败,但是也要坚强。与其有时间和别人吐槽自己的经历,不如多花点时间充实自己,提升自己。\noverride 是重写(覆盖)了一个方法,以实现不同的功能。一般是用于子类在继承父类时,重写(重新实现)父类中的方法。 重写(覆盖)的规则:\n重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载。 重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public\u0026gt;protected\u0026gt;default\u0026gt;private)。 重写的方法的返回值必须和被重写的方法的返回一致; 重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类; 被重写的方法不能为 private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。 静态方法不能被重写为非静态的方法(会编译出错)。 overload 是重载,一般是用于在一个类内实现若干重载的方法,这些方法的名称相同而参数形式不同。 重载的规则:\n在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样); 不能通过访问权限、返回类型、抛出的异常进行重载; 方法的异常类型和数目不会对重载造成影响; 覆盖和隐藏又是两码事:here\n类的常量成员:常量对象只能访问常量成员 1 2 3 4 class A { public: int fun(void) const; // const member }; this is const pointer 更多请访问:cpp-tutorial, cplusplus 对 cxx 的特性描述的通俗易懂,而且划分结构简明,是一份不可多得的参考材料,要是说实在有什么缺点的话,屏幕太小,看得伤眼睛。\n使用istringstream对象分割字符串:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include \u0026lt;iostream\u0026gt; #include \u0026lt;sstream\u0026gt; using namespace std; int main(int argc, const char* argv[]) { istringstream iss; iss.str(\u0026#34;abc def haha a e i ou\u0026#34;); string c; while (iss \u0026gt;\u0026gt; c) { cout \u0026lt;\u0026lt; c \u0026lt;\u0026lt; endl; } return 0; } //--- outputs: ---// // abc // // def // // haha // // a // // e // // i // // ou // //----- end ------// Python 中 with 的用法:IBM docs\n上下文管理协议(Context Management Protocol):包含方法 __enter__() 和 __exit__(),支持该协议的对象要实现这两个方法。 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了 __enter__() 和 __exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。 运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__() 和 __exit__() 方法实现,__enter__() 方法在语句体执行之前进入运行时上下文,__exit__() 在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。 上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。 语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。 with 的语法格式:\n1 2 with context_expression [as target(s)]: with-body 这里 context_expression 要返回一个上下文管理器对象,该对象并不赋值给 as 子句中的 target(s) ,如果指定了 as 子句的话,会将上下文管理器的 __enter__() 方法的返回值赋值给 target(s)。target(s) 可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)。\n1 2 3 4 with open(r\u0026#39;somefileName\u0026#39;) as somefile: for line in somefile: print(line) # ...more code 等价于\n1 2 3 4 5 6 7 somefile = open(r\u0026#39;somefileName\u0026#39;) try: for line in somefile: print(line) # ...more code finally: somefile.close() 学习的本质就是降熵,但我觉得,熵降到极点又何尝不失一片混沌呢。什么都知道和什么都不知道其实同样难以实现,去拒绝一些非常简单的知识和去挖掘一些非常困难的知识拥有同样的难度。每个人究其一生所能做的也只是维持一个平衡,已知与未知的平衡。如果把一个人比作一张白纸,他学到一个知识,就在白纸上用黑笔画一个节点,那么随着人的成长,白纸上的黑点会越来越多,知识之间相互联系,点与点之间形成网络。随着知识的积累,网络越来越复杂,越来越密集,到最后白纸几乎变成了黑纸。那么请问:白纸和黑纸究竟又有什么区别呢?新生的婴儿和将死的老者有什么区别呢?早上刚醒的你和晚上即将睡着的你有什么区别呢?人生于世,我想追求的不是极致,而是平衡,是中间。随手画一个圆,你知道中间在哪吗?你不知道就对了,因为你将用一生去追寻它。\n2019 年 3 月 Python 中的__future__包可将高版本中的特性引入低版本的解释器中。比如print在 python3+ 是一个内置函数,而在 python2.x 是特殊关键词。\n1 2 3 4 5 6 7 8 #! /usr/bin/python2 from __future__ import print_function from __future__ import absolute_import import string # use `print` as a function in python2! print(\u0026#34;I am using python2.x!\u0026#34;) 上述的absolute_import意为绝对路径导入,python 导包默认在当前路径下搜索,然后在标准库里搜索,如果当前目录下有自定义版本的string包,则会优先导入自定义版本的string包。加了absolute_import之后,除非指定自定义包名的路径,否则优先导入标准库里的同名包。\n出来混,迟早要还的。 现在轻松就意味着将来某段时间可能会忙成狗。现在加把劲儿,以后可能就会轻松很多。\n仪式感还是要有。“生活需要仪式感!” \u0026ndash; 某伟人 即便我对此还差一点点感知。\n虽然之前就看过类似的言论:博客最重要的是“写”!然而迟迟不能体会,而今总算有点领悟,格式什么的真的不重要,最重要的还是看你怎么写,怎么表达。过早的优化格式,优化排版是很无谓的工作。说到这里又想起一位伟人的话:\nWe should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. ──Donald Kunth,1974\n程序员的软技能:\n沟通能力(写文档、代码注释,邮件等) 代码规范(coding style) 模块化抽象 看书(设计模式等) 读开源代码(如何设计,如何抽象,如何模块化) 实战 对业务的理解能力(选择最合适业务背景的技术框架) 基于场景做抽象,预留扩展空间,交付最小集。 https://study.163.com/topics/tongxueqingjin\n──网易笑招组 2019 年 2 月 放假回家还是啥也没干 orz\n现在大部分学到的东西,都缺乏及时整理。导致模糊不清,无法构建一个很清晰的系统架构。只能靠不断地堆砌素材,达到模糊的一知半解。但是,要深究每个细节,又没有这么多的时间。很矛盾,现代的生活节奏太快了,如果没办法改变,那就只能适应了。\n2019 年 1 月 离放假还有一个月,是时候该做点什么了。不要玩物丧志!\n最近沉迷折腾,并没怎么学习。所幸将自己近期折腾的内容都梳理了一遍,整理成文,方便自己查阅,日后再用也能快速上手了。有一句话说得好:输出是最好的输入!\n愧为人念,愧受师恩!我有愧,愧对所有关照我的人,愧对所有我能伤害的人。\n亲君子,远小人。在儒为小人,在鬼谷为圣人。盖儒之鄙于鬼谷者,排除异己也。此小人行径,亦当为己所疏。鬼谷追于道,避亡趣存,不拘儒教。故常言“离经叛道”,敢问小人所离何经,所叛何道?原是教条固步自封,匪我叛也。\n有时候争论真的没有意义!忌贪嗔痴,不争则无尤,则不失理智,能从容处事。\n我一哭眼睛就肿了,太阳穴就疼。头昏脑胀的呜呜~ 我今天做梦了梦到你了。梦里你特别温柔,比我成熟,特别宠我。总是搂着我的肩,我生气了,你三两句话就把我哄好了,逗笑了。 我希望找一个能包容我让着我的男朋友。\n──Yukynn 2018 年 12 月 今天,12 月 14 日,我的个人博客终于上线了。在折腾了几天之后,依托 Github 爸爸的助力,以及 Hexo 引擎以及 Melody 主题的驱动,终于将个人主页打扮的有模有样了。可以出来见见世面啦!\n以后会在这里写很多随笔、学习笔记、以及一些折腾记录以便后续查阅,就酱~~~~\n最近似乎有些沉迷博客美化了,沉迷到耽误功课!其实原本的搭配就不难看了,但是还是在那里不知道折腾个什么劲儿。有时候甚至为了一个头图选壁纸选半个小时,还有一些图标的配置啊等等。已经三天没做功课了╮(╯▽╰)╭,这样下去可还得了!住手,你的作业还没改完,要不要给你列一列你的 TODO 清单?\n切记,女生就是会反复确认她在你心里的位置。即便有些事情她很确信,不要说“我对你的感情你还不清楚吗”这种傻瓜的话。当她来确认的时候,让她明白,她就是最重要的人!\n女人,第一需要的是尊重。 女人,第二需要的是诚意。\n──《秦时明月》花影 公共场合注意言谈举止 吃饭不要啧啧啧 不要做奇怪的表情 不予试图劝服其接受反感的内容 发消息不回让人没有安全感 女生大部分时候不喜欢动脑子,所以先为她做好决定,不要问来问去 珍惜眼前人 首先提高自己的工作效率,多匀出时间去见她,胜过千言 我回去了,我下班了=打电话给我 关于不相关和独立的一些言论\n相关性反应的实际上是一种线性关系,而独立性则反映的是更为一般的线性无关性。 比较好的例子是正态分布关于正态分布的条件期望是那些正太分布的线性组合,而正态分布完全可由二阶矩决定,因此正态分布不相关等价于独立\n──知乎 - 竺毅纯 题主,相关(这里指皮尔逊)是用矩定义的,独立是用分布定义的,你可以想象一下是谁强谁弱了吧?唯有正态分布两者等价。\n──知乎-jiesheng si 感觉都没说到关键的地方,相关是说的 covariance 为 0,covariance 是由 X 和 Y 的二阶矩算出来的,和 XY 的一阶矩 E[XY],这本身就缺失了很多信息,并不能得出所有的的信息。最常见的例子就是,正态分布与自己的平方。而独立的定义是在 sigma algebra 上面,通过 sigma algebra 可以得到一个事件的全部信息,这是比是否相关强得多概念。\n作者:匿名用户 链接:短链 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。\n正态分布中不相关等价于独立\n儒、道、释:入世、出世、弃世。\n大芹菜烧法:切好后拍一拍,切成丝或片状,少以盐渍,沥水,片刻下锅翻炒\n为什么用 Hexo,本意是为了更好地专注写作。不成想花这么多时间折腾主题美化之类的事情。可谓是本末倒置了!\n我最讨厌你们这帮男生明知道我生气,却不来哄我,晾着我,越晾越生气,再晾心就凉了。很多事情明明都是当时就能哄好,非要怄气不说好话!\n还有,我最讨厌你们这帮男生明知道我生气,却不说重点,打擦边球:吃饭了吗?吃的啥呀?睡觉了吗?谁要听你说这些?上来跟我表态度,说重点,问题解决了再说别的!\n──边思梦 要学的还有很多,任重而道远。昨日看到好友 Q 君发表了年度总结,感慨万千。猛然回顾,仍是一事无成。人要善于总结,但往往败于懒惰。但是人不是生来要被打败的,所以还是战斗吧,和一切的一切!\nQ: 为什么使用 hexo deploy 老是提示输入用户名和密码。 A: here and here.\nQ: 如何后台运行命令? A: here\n言多必失,不如娴静少言提高自身修养。\n","permalink":"https://guyueshui.github.io/sketch/","tags":null,"title":"札记"},{"categories":null,"contents":"Site Welcome! This blog is mainly written for self-reference, which covers math, tech, and maybe some other notes (learning or working). Nevertheless, it\u0026rsquo;s really my pleasure if some of my articles is useful for you. :)\nTimeline of this site Time Event 2018-12-24 First published by Hexo, theme Melody 2019-08-25 Move to Hugo, theme Even 2019-08-25 Add comment system powered by gitment 2020-03-25 Add many old articles 2020-11-14 Auto deploy with Github Actions 2021-02-10 Change comment system from gitment to valine 2023-04-08 Add local search scheme, not perfect though 2023-04-10 Search in a more efficient way, refer to here 2023-06-01 Change comment system to giscus Me I am yychi, a student.\nYour browser doesn't support H5 audio flag! Wenyanwen roulette by CTP. 正经关于 呸呸呸,装鬼的话就不说了。这里与其说是技术博客(好像也没有人说是技术博客),不如说是个人成长记录!诚如是,这里会堆积很多个人化的东西,我也不会为谁写作。人不应该顶着压力做事,活着就要追寻快乐!当然,在写作上,我还是会努力做到信、达、雅。\n","permalink":"https://guyueshui.github.io/about/","tags":null,"title":"About"},{"categories":["tech"],"contents":"对于 Linux 用户,在 $HOME 文件夹下,一般都有大量的隐藏文件,形如.conf,.xxxrc等,这些都是程序的配置文件。很多人也许花了一个下午,一天,甚至一个星期,折腾某某程序的配置文件。如果这些轻易丢失了,那就是浪费生命了!所以,如何将这些文件备份,成了很多人必须要问的一个问题。\n之前我就一直没有备份的意识。结果无论是重装系统,还是转移机器,都十分煎熬,很多软件都需要重新配置!这可是一个浩大的工程,费时费力还费心。于是终于想起来应该把苦心经营(大部分都是来自网络资源,然后自己改改)的配置文件给备份一下。\n于是在网上搜索了一下,发现很多人都用 Github 备份自己的配置文件。于是便尝试如下:\n常规操作是将所有需要备份的配置文件单独拎出来,放到一个专用文件夹MyConf下,该文件夹就作为 git repo 的根目录。然后将配置文件链接到需要原本需要它们的文件夹下。这应该是个比较不错的解决方案了,但是有的人可能不喜欢创建软链,很强迫症○| ̄|_\n于是就有了接下来的方法:主要思想是使用家目录$HOME下的一个文件夹存储一个 Git bare repository 1. 然后使用命令别名去添加,删除,修改配置文件,这样做的好处是不需要在家目录下创建 .git/ 目录,否则会干扰其他子目录的 git 操作。\n1. 新建 bare 仓库 在$HOME文件夹下新建一个文件夹用来存放 git 版本树。然后初始化为 bare 仓库。\nmkdir ~/.mydotfiles git init --bare ~/.mydotfiles 2. 创建命令别名 接下来需要创建一个命令别名来进行 git 的各种操作。直接在家目录运行 git 命令肯定是不行的,因为家目录不是一个 git repo,不包含 .git 文件夹。所以甚至命令别名如下:\nalias config=\u0026#39;/usr/bin/git --git-dir=$HOME/.mydotfiles/ --work-tree=$HOME\u0026#39; 像这样定义别名,是一种临时的方式。想要使它每次都生效,可以将其写入 .bashrc 或 .zshrc.\necho \u0026#34;alias config=\u0026#39;/usr/bin/git --git-dir=$HOME/.mydotfiles/ --work-tree=$HOME\u0026#39;\u0026#34; \u0026gt;\u0026gt; $HOME/.bashrc 如此一来,每次进入 shell,都可以使用这个别名。可以敲一个 config status看看效果。\n3. 使用.gitignore 现在我们的工作目录是整个家目录,如果要把整个目录全备份的话,那就太可怕了。家目录一般动辄十几甚至几十个 Gb,没有哪家免费服务可以让你把整个家目录都备份的。所以我们需要一个 .gitignore 文件。Git 会主动忽略.gitignore中所匹配的那些文件。在家目录中创建(如果没有).gitignore 文件:\n#! $HOME/.gitignore #----[ ignore all ] ----- * #---[ consider list ]--- !*.[Xx]resources !*.conf !*config* !*[a-zA-Z]*rc !.config/ !.config/* #---[ ignore list ]--- 上面的文件告诉 git 默认忽略所有文件及文件夹,然后反向添加我们想要考虑的那些文件或文件夹 2。另外 gitignore.io 可以根据要求生成不同的 .gitignore 文件。\n忽略特定文件\nPermanently ignore changes to a file\nAdd the file in your .gitignore. Run the following: git rm --cached \u0026lt;file\u0026gt; Commit the removal of the file and the updated .gitignore to your repo. 来自谷歌搜索,巨婴家 Doc.\n4. 常规 git 操作 现在你可以用config add -A来添加所有匹配到的文件。如之前的配置,可以匹配大部分的配置文件。如有遗漏,可以用config add -f \u0026lt;file\u0026gt; 来强制添加。然后可以 config commit -m \u0026quot;initial git\u0026quot; 来提交更改。最后连接 github 远程仓库。\n首先在 github 网站新建一个同名仓库。比如本地仓库为.mydotfiles, 那就新建一个同名的远程仓库。然后\nconfig remote add origin git@github.com:\u0026lt;username\u0026gt;/\u0026lt;repo_name\u0026gt; config push -u origin master 就可以把本地仓库推送到远程,完成同步。\n5. 在新系统上还原配置文件 同理,设置别名\n1 alias config=\u0026#39;/usr/bin/git --git-dir=$HOME/.mydotfiles/ --work-tree=$HOME\u0026#39; 将.mydotfiles加入.gitignore以免递归克隆\n1 echo .mydotfiles \u0026gt;\u0026gt; .gitignore 克隆备份好的配置文件\n1 git clone --bare \u0026lt;git-repo-url\u0026gt; $HOME/.mydotfiles 检出克隆下来的配置文件\n1 config checkout Reference Using Git and Github to Manage Your Dotfiles Dotfiles The best way to store your dotfiles: A bare Git repository How to use gitignore command in git - Stack Overflow Gitignore doc What is a bare git repository?\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n关于该文件的匹配规则参见:explain gitignore pattern matching - Stack Overflow\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://guyueshui.github.io/post/manage-dotfiles-by-git/","tags":["git"],"title":"使用 Git 管理配置文件"},{"categories":["tech"],"contents":"1. 事前准备 先想好为什么要刷机?想清楚了吗?真的想清楚了吗!好的,接下来我们要做的事应该是打开一堆网页,一堆对应自己机型的刷机教程帖,还要做好重要数据备份,确保“不成功,也不能成仁”。好的,那就开始吧:\n确定设备解锁状态\n设备锁,也称 Bootloader 锁(BL 锁),通常是厂家为了防止用户乱刷第三方系统设置的屏障,同时,它也是一些诸如「找回手机」、「抹除数据」等安全功能的基础。你应当时刻假设捡到你手机的人是一个专业人士,只要设备在他手里,那么人家就有一百种方法破解你的密码。但是一般人也就只能通过刷入 Recovery 来取缔你手机原有的 rec,进而在里面做文章(比如删除你的密码文件,这样再次开机时,密码就不复存在)。我们可以把 Recovery 想像成电脑的 Bios,于是只要设备在我手上,我想重装多少次系统都可以。也就是说我是可以使用你的设备的,并不像你想的那样:“我设了密码,你用不了。”\n而设备锁,恰恰就是一个安全保障,在对方想要取缔官方 rec 的时候,它出来阻拦:不让你换!而大多数官方的 rec 功能相对简单,并且有官方自己写的保护程序在里面。如果要强行刷机,至少也得先把数据丢了。这某种程度上说明,对方拿不到你的数据,即使他拥有了你的设备!所以对一些商务高层人士,这层防护显得尤为重要。\n至此,你应当明白,解开设备锁的风险!那么如何判断手机是否已经解锁了呢?方法至少有两种,其一是进入 bootloader 界面(关于如何进入 BL 界面以及 adb 工具的设置请先自己解决,暂时没时间写),执行\n$ fastboot oem get-bootinfo 1. Bootloader Lock State : UNLOCKED =\u0026gt; 表示已经解锁,可以刷机 2. Bootloader Lock State : LOCKED =\u0026gt; 表示未解锁,自行百度设备解锁方法 其二,如果手机还是可用的。进入开发者选项查看,下图是一个已解锁的例子:\n如果未解锁,请自行搜索自己设备对应的解锁方法。一般来说小米、一加等厂商较为开放,可在官网申请解锁,可能需要等待 2~3 天的时间。\n数据备份\n建议使用钛备份:可对逐个应用以及系统数据(包括 WIFI 信息,系统设置,短信,联系人,另外还有版本控制)进行备份,root 备份首选。 Adb 备份 TWRP 备份 配置电脑 Android 调试环境\n打开一个终端,敲 adb 或者 fastboot,如果未显示异常,则说明已经配置好。如下:\n$ adb --version Android Debug Bridge version 1.0.40 Version 9.0.0_r3 Installed as /usr/bin/adb 若没有配置,安装软件包:pacman -S android-tools (Arch Linux ver.) Windows 下载对应的工具包,解压即可。不过需要配置一下路径,或者直接把所有的文件都弄到解压的文件夹下操作。\n另说一下驱动,在 Linux 和 Mac OS 下,均不需要考虑驱动的问题。在 Windows 下,需要在网上找到相应设备的驱动,安装好之后,才可以用 adb 进行刷机。这里一个显式的标志是:\n右键开始菜单 =\u0026gt; 设备管理器 =\u0026gt; ADB interface\n如果有这个 ADB 设备,则说明设备驱动已经安装好。\n下载刷机包\n这个就考验个人搜索能力了。一般而言在各自机型的官方论坛上找:一加论坛,MIUI 论坛都是不错的选择。\n其次可以在 XDA 上找,这是个国外比较活跃的 Android 论坛,里面有很多大牛发各种第三方 Rom 包。一般热门机型都可以在 XDA 上找到自己满意的 Rom.\n下载合适的 Recovery\n推荐TWRP, 专门做第三方 Rec 的团队,首选!在这个网站上基本上可以下载到自己机型对应的 Rec.\n2. 刷机 其实事前准备做得足够好的话,刷机很简单,而且风险非常低。\n可以使用 MTP 协议事先将刷机包拷贝到手机存储目录 (一般是/storage/emulated/0/,在 Rec 下的目录结构可能会发生改变/sdcard). 也可以在 Rec 下使用 adb 传输。但前提是你要有功能完备的 Rec. 一般官方的 Rec 非常简陋,没什么功能。所以我们首先得刷入第三方 Rec.\n刷入第三方 Recovery\n在确保手机与电脑正确(开启 USB 调试)连接下,在命令行敲\n$ adb devices xxxxxx device 会列出所有已连接的 Android 设备。在高版本的 Android 系统中应该会弹出一个对话框询问是否允许电脑调试本机,点击一律允许即可。\n确保手机已经解锁,开机状态连接电脑。在命令行输入\n$ adb reboot bootloader 手机会重启进入 bootloader,也就是 fastboot 模式。\n确保执行目录里面有之前下载的 twrp-xxx.img 文件,命令行输入\n$ fastboot flash recovery twrp-xxx.img 即完成第三方 Rec 的刷入。\nNote: 此时也可以选择 fastboot boot twrp-xxx.img 临时从第三方 rec 启动\n进入第三方 Recovery\n在命令行输入\n$ fastboot reboot 重启手机,然后同时按住电源键 + 音量下键(有些手机不一样,自行摸索)进入 rec. 也可以等开机后输入\n$ adb reboot recovery 进入 rec.\n刷机\nTwrp 的 rec 界面十分友好,可以设置语言时区等等。刷机之前先要清理(特别提醒:这里已经默认你做好备份了)。我们经常清理的有四个分区\nsystem # 系统分区 data # 数据分区:应用数据(设置,帐号,习惯等) cache/davik cache # 缓存分区:应用缓存,系统缓存 internal storage # 个人资料存储:包含照片视频音乐等所有个人资料 一般 internal storage 是不会动的,把其他三个分区清掉。\n然后安装刷机包,找到事先放好的刷机包位置,刷入,重启!\nNote: 或者事先没有拷贝的话,确保命令执行目录中有你的刷机包,使用 adb push aex-xxx-rom.zip /sdcard 即可将刷机包拷贝至手机存储目录。\n再或者,使用 ADB sideload 功能边传文件边刷。具体操作在 rec 中:高级 =\u0026gt; ADB sideload 然后再命令行输入 adb sideload aex-xxx-rom.zip 即可开始刷机\n完了!一般重启需要一些时间,请耐心等待一下。\n补丁(root 包,gapps 包)\nRoot:推荐 Supersu 或者 Magisk Gapps:推荐 Opengapps. Google 大法好,不带 Google 框架的安卓不是 Android! Custom kernel Note: 注意刷包步骤:先 rom 包,后补丁包。不过还是建议刷完 rom 重启一次,再进 rec 刷补丁包比较稳妥。\nA/B slot 刷机 对于新版 A/B slot 机器,刷机步骤为:\n刷 rom(如果 rom 自带 recovery,会将 twrp 覆盖,此时需要立刻刷一遍 twrp,确保重启后的 recovery 是 twrp) 重启到 recovery(twrp),这一步将会切到正确的 slot 重启系统 参考:\nhttps://forum.xda-developers.com/t/a-b-slots-flashing-in-twrp.3887321/ https://www.xda-developers.com/how-a-b-partitions-and-seamless-updates-affect-custom-development-on-xda/?newsletter_popup=1 https://forum.xda-developers.com/t/how-to-fix-unable-to-mount-data-internal-storage-0mb-in-twrp-permanently.3830897/ 被迫刷机 2024 年 11 月 19 夜,由于 xplore(一款很好用的文件管理器,可称塞班时代的遗珠)卡顿,实际上算不上卡顿,我猜是触发了系统的 bug,哪哪都不对劲,具体表现为打开 xplore 卡死,连带大返回都卡,然后恢复正常,可以使用其他应用。强制关掉 xplore 重新开依然如此,而且我还发现开 SDMaid 虽然能开,但里面的扫描一直卡在 0%. 这种场景之前也有过,必须重启才能恢复,所以我想是系统的 bug, 毕竟第三方 ROM \u0026ndash; PixelPlusUI (XDA). 不过这个 ROM 已经算很稳定的了,自带 Google 套件,在稳定性和可定制性上做到了很好的折中。\n重点来了,于是乎我就重启嘛。可能是太久没见 Recovery 的原因(这个 rom 已经稳定用了一年又九个月),我鬼使神差地重启进入 twrp recovery,然后心想着来都来了,清下缓存再重启把。本来勾一个 cache 分区就行了,再次鬼使神差地多勾选了一个 meta 分区(虽然不知道它是干嘛的)。结果清完之后直接重启卡 twrp 了,进不去系统了。而且进了 twrp 还 data 分区解密失败。玩球了,数据又要丢了。\n其实在准备刷机前,要先把手机密码都清掉,防止 twrp 解密分区失败的,但我这次压根没想刷机。所以带着密码删了清了 meta 分区,结果再次进 twrp,data 分区已经解不出来了。杯具了 orz\n本次刷机遇到以下问题\n格式化 data 分区失败,因为挂载失败,data 分区(系统应用,用户应用及其数据)是加密状态,此时只能选更改文件系统,再改回来,改回来之后惊奇的发现 internal data 分区(文档、照片、音乐、视频等)也被清掉了,直接还给我一台新机!🌿 刷入 rom 之后,好几次还是卡 recovery,后来好了,不知道是不是操作步骤的问题。 刷入 rom 成功开机后,再次回到 recovery 刷 magisk,刷完继续卡 recovery,只能重刷。 重刷之后误操作切换了 slot,导致 fastboot boot \u0026lt;twrp.img\u0026gt; 执行失败(见 troubleshooting),这是真的惊悚,从来没遇到过,一度以为真的要成砖了,还好在 xda 找到了类似情况。 adb/fastboot 设备无权限。 谨此记录。\n总结 以前刷机,顶多是换 ROM 导致的系统设置数据冲突,此时需要清掉 data 分区,重新配置用户 app. 随着 android 版本提升,安全等级的提高,最近几次刷机过程中总会碰到 data 分区被锁的情况。一旦被锁,极难救回,往往不得不格式化 data 分区甚至 internal storage,面临丢失数据的痛苦。\nWhat usually happens is that your data is automatically encrypted by default. This isn’t a a one-off case for a particular set of devices, since encrypting internal memory was mandated for devices launching with Android 6.0, or later.\nWhy Does TWRP Require a Screen Lock to Decrypt Data?\nTWRP requires the screen lock before decrypting internal storage because of how Android handles device encryption. Android uses either full-disk encryption (FDE) or file-based encryption (FBE), and the decryption key for both of these systems is tied to your password, PIN, or pattern.\nFor the recovery to access that encrypted data, it needs your screen lock. Without this lock, the decryption key isn\u0026rsquo;t locked behind the authentication methods. This is why removing the screen lock may help in accessing your device\u0026rsquo;s internal storage.\n切记,如果要刷机,第一件事就是把屏幕锁去掉。\n3. 技巧 提取当前 ROM 的 boot.img From xda:\nGo to recovery. Open recovery terminal. Enter: Code: dd if=/dev/block/bootdevice/by-name/boot of=/sdcard/boot.img. Press enter to confirm the command. Reboot to system. 如此即可在用户内存根目录(/sdcard)生成 boot.img.\n注意:recovery 模式下,/dev/block/bootdevice/by-name/目录下才会有boot文件。而对于a/b设备,它正是实际 slot 的软链接。\n1 2 munch:/dev/block/bootdevice/by-name # file boot boot: symbolic link to /dev/block/bootdevice/by-name/boot_b 任何模式下,可以通过下面的命令获取 active slot:\n1 2 munch:/dev/block/bootdevice/by-name # getprop ro.boot.slot_suffix _b 可见当前 active 的是 slot b.\n也可以直接提取到电脑上,手机(开机状态或者 recovery 都行)连上电脑,adb 通的情况下,\nadb pull /dev/block/bootdevice/by-name/boot_b boot.img 即可在当前文件夹提取 boot.img. 这里面包含 ramfs,更多请参考 ref #3.\nFrom https://stackoverflow.com/a/21173939\nboot.img contains the kernel and ramdisk, critical files necessary to load the device before the filesystem can be mounted.\nFrom reference #4,\nA ramdisk is basically a small filesystem containing the core files needed to initialize the system. It includes the critical init process, as well as init.rc, which is where you can set many system-wide properties.\n如何查看你的手机会不会锁 data 分区 打开设置搜索“加密与凭据”如见“加密手机”选项显示“已加密”则表示你的 data 分区会被自动加密。\n4. Troubleshooting adb 或 fastboot 报错:insufficient permission / permission denied\n请按照 https://developer.android.com/studio/run/device 提示操作,唯一需要注意的是 USB 供应商 ID. 可以用 lsusb 来判断。\n连接手机之前:\n$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 004: ID 8087:0a2b Intel Corp. Bus 001 Device 003: ID 04f3:0c1a Elan Microelectronics Corp. Bus 001 Device 002: ID 04f2:b5a3 Chicony Electronics Co., Ltd Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 连接手机之后:\n$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 004: ID 8087:0a2b Intel Corp. Bus 001 Device 003: ID 04f3:0c1a Elan Microelectronics Corp. Bus 001 Device 002: ID 04f2:b5a3 Chicony Electronics Co., Ltd Bus 001 Device 041: ID 2717:ff48 Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 这样就可以判断该设备的供应商 ID 为 2717.\n然后重启 udev 服务: udevadm control --reload(Arch Linux ver.)\n拔掉手机线重新插入,再用 adb devices 列举一遍,就应该可以了。\nNote: 另外可以尝试使用 root 权限执行命令: sudo adb 和 sudo fastboot\nfastboot boot recovery.img报错 FAILED (remote: \u0026lsquo;Failed to load/authenticate boot image: Load Error\u0026rsquo;)\n这是由于意外切换了a/b slot,即本来 active 的是 a slot,被误操作切换成 b 了,所以 fastboot 执行不下去,切回来就可以。执行\n# try this fastboot set_active a # or fastboot set_active b see: https://xdaforums.com/t/troubles-reinstalling-twrp-failed-to-load-authenticate-boot-image-load-error.3926815/\nReference 在硬件设备上运行应用 fastboot and adb not working with sudo Boot Image Extraction Guide HOWTO: Unpack, Edit, and Repack Boot Images | XDA Forums How to Fix TWRP Unable to Mount Storage, Internal Storage 0MB How to disable Android Full Disk Encryption ","permalink":"https://guyueshui.github.io/post/android%E5%88%B7%E6%9C%BA%E7%9A%84%E4%B8%80%E8%88%AC%E6%AD%A5%E9%AA%A4/","tags":["android","刷机"],"title":"Android 刷机的一般步骤"},{"categories":["linux"],"contents":"放假回家,因故将笔记本电池弄到枯竭。结果再次开启,发现 startx 启动 gnome-session 失败。几经解决未果,只好重装!\n安装 Archlinux 基本安装步骤都是按照 ArchWiki 上的 Installation Guide 以及简书上的一篇文章 虚拟机安装 Archlinux 的简易步骤.\n安装过程主要可以分为以下几个步骤:\n1.分区\n一般而言只需要分 3 个区:根(/),用户主目录 (/home) 以及 swap 交换。贴一下我的分区图 可以看到,一块磁盘 (disk) 被分成很多的分区 (partition) . 其中,稍微现代一点的电脑主板都启用了 UEFI,所以在磁盘第一个分区是 ESP 分区。这个分区中就包含了所有可启动系统的启动文件。在没有安装 Linux 之前,它里面只包含有 Windows 自带的启动文件。在安装完成 Linux 后,由于有两个可启动的系统,所以需要一个引导程序 (rEFInd, Grub 等) 来将选择权交给用户。\n上图中的最后三个分区即为 Linux 系统的分区。分区大小的划分事实上很讲究,我根据之前的经验,/home 分 50GB 够用了,如果不放什么大型视频和音频文件的话。swap 分区的大小一般为已安装内存的一半,比如我的系统内存 8GB,swap 就分 4GB.\n如今(2023-02-24 21:18)已经领略到 swap 不够用带来的弊端。并提供一种swap 扩容的方法。\n2.格式化分区\nLinux 文件系统一般是 ext4,使用如下命令格式化分区\n1 2 3 mkfs.ext4 /dev/nvme0n1p5 mkfs.ext4 /dev/nvme0n1p6 mkfs.ext4 /dev/nvme0n1p7 3.挂载目录\n将各目录挂载到对应的分区,例如\n1 2 3 4 5 6 7 8 mount /dev/nvme0n1p5 /mnt mount /dev/nvme0n1p6 /mnt/home # boot 分区其实应该单独分出来 # 但是我们已经有了 esp 分区 # 要和原来的 Windows 兼容 # 只需要将该 esp 分区挂载到 /boot/efi 目录下 # 之后安装 bootloader 时会把 Linux 的启动文件放到 esp 分区 mount /dev/nvme0n1p1 /mnt/boot/efi 开启 swap 分区以便之后生成 fstab 时检测\n1 swapon /dev/nvme0n1p7 4.执行安装\n核心命令为:\n1 pacstrap -i /mnt base base-devel net-tools 其余细节参考 ArchWiki. 值得一提的是,base 组里面包含的程序包有限,所以追加了 base-devel 和网络配置工具包 net-tools. 注意执行安装命令前,对 /etc/pacman.d/mirrorlist 进行相关修改,把中国的镜像放在前面,使得下载速度更快。还有几个有用的网络工具包也一并装了 iw, wpa_supplicant, dialog.\n5.后续步骤\n后续就是 arch-chroot 到新安装系统中进行相关设置:hostname,hosts,时区,locale 等。这些在 Installation Guide 中均有提及,不再赘述。\nupdate: 2023-03-11 13:04\n这次安装之后发现时间不同步,且 installation guide 里面也没有提及,特此记录。开启自动对时的命令:\n1 2 3 4 5 6 7 8 9 10 11 timedateclt set-ntp 1 # see effects timedatectl Local time: Sat 2023-03-11 13:06:39 CST Universal time: Sat 2023-03-11 05:06:39 UTC RTC time: Sat 2023-03-11 05:06:39 Time zone: Asia/Shanghai (CST, +0800) System clock synchronized: yes NTP service: active RTC in local TZ: no 这样,时间就对了。\n6.小结\n以上,一个新的 Archlinux 就安装完成了。不过这只是一个简陋的系统,还没有进行配置,只能用终端输命令的那种。后续配置参考简书那篇文章。\n这次安装,我的最大的一个收获就是学会了如何在命令行中连接 WiFi. 需要的工具有\nPackage Command Note dialog wifi-menu WiFi 直连 net-tools ifconfig 查看网络状态 wpa_supplicant wpa_supplicant, wpa_passphrase 连接 WiFi dhcpcd dhcpcd 动态 IP 地址获取 获取无线接口名称\n好了,现在知道了,是 wlp2s0. 一般也可能是 wlan0. 然后确认该接口的状态是 up,如图所示。\n扫描可用网络\n1 iw wlp2s0 scan 确定你要连接的无线网络名称 (SSID),假设是 shiki.\n生成配置文件\n1 wpa_passphrase shiki \u0026gt; ~/shiki.conf 连接 WiFi\n1 wpa_supplicant -B -i wlp2s0 -c ~/shiki.conf 获取 IP 地址\n1 dhcpcd wlp2s0 查看连接状态\n1 iw wlp2s0 link 另外,还有一种更加简单的方法,直接敲命令 wifi-menu 可以进行交互式 WiFi 连接,体验和图形界面一样。\nPost-install 添加主用户(带 sudo 权限):\n1 2 3 4 5 useradd yychi -m # -m表示创建家目录 usermod yychi -aG yychi # 加入yychi组 passwd yychi # 设置密码 visudo # 打开sudoers文件,并uncomment wheel组 usermod yychi -aG wheel # 将yychi加入wheel组,此时yychi可以使用sudo了 猛击此处获取所有显式安装的包名列表。\n系统美化 字体 如何在 Linux 上安装喜欢的字体呢?只需要将对应的字体文件复制到相应的字体目录,再执行几个命令就 OK 了。\n下载字体\n英文字体的话,Google Fonts 基本可以完全搞定。\n关于中文字体,目前推荐的只有两个:文泉驿,文鼎。\n例如要将下载好的 Alegreya.zip 字体安装到系统,只需要将其解压: 可以看到里面的字体文件。挑几个解释一下\nAlegreya-Regular.ttf # 常规字体 Alegreya-Italic.ttf # 斜体 Alegreya-Bold.ttf # 粗体 Alegreya-BoldItalic.ttf # 粗斜体 一般而言,虽然一个字体包里面可能有很多字体文件,很多变体(粗体,特粗,细,特细等),我们只需要安装以上四种变体基本上就足够了。在 TeXLive 中,如果指定了字体族,那么其中的 \\emph 和 \\textbf命令会自动寻找相应的变体来排版。\n复制到指定目录\n将这四个文件复制到系统字体文件夹\n# 用户字体目录 ~/.local/share/fonts # 系统字体目录 /usr/share/fonts 为了便于管理,可以在系统字体目录下新建一个文件夹存放用户后续安装的字体,例如\n1 mkdir /usr/share/fonts/userfonts 将需要安装的字体文件复制到上述文件夹,然后\n执行安装命令\n1 2 3 4 mkfontdir mkfontscale fc-cache -fv #刷新系统字体缓存 确认安装是否成功\n1 fc-list | grep userfonts 如图所示,可以看到在指定目录下的字体已经安装成功!\n卸载字体\n删除相应字体文件,刷新系统字体缓存即可\n查看已安装字体\n1 fc-list :lang=zh # 查看支持中文的字体 Troubleshooting intel 集成显卡滚动屏幕出现撕裂现象 可能有用的链接\nhttps://wiki.archlinux.org/title/Intel_graphics#Xorg_configuration 如何查看当前加载的显卡驱动:\n1 lspci -v | grep -A20 VGA 查看 nvidia 则grep 3D\n按照archwiki 上所说,结果启动 X 报错,说没有 intel 这个 module,后来发现应该xf86-video-intel这个驱动没装的原因。装了之后,再启动就没有屏幕撕裂现象了。\n搁置了这么久终于解决了屏幕撕裂的问题,呼!\n启用 nvidia 独显 可能有用的链接\nhttps://howto.lintel.in/install-nvidia-arch-linux/ https://wiki.archlinux.org/title/NVIDIA#Xorg_configuration 外接鼠标无法使用 一直以来,我都是使用笔记本自带的触摸板工作。近日弄了台显示器,大屏看得多爽呀。那就笔记本自带的触摸板和键盘都不用了,统统用外接的。结果接上去发现鼠标压根就没用,有线的无线的都试了。几经排查,是由于此前将输入驱动换成了libinput,没有专门为鼠标配置驱动。\n参考archwiki,配置如下:\n# file: /etc/X11/xorg.conf.d/40-libinput.conf Section \u0026#34;InputClass\u0026#34; Identifier \u0026#34;touchpad\u0026#34; MatchIsTouchpad \u0026#34;on\u0026#34; Driver \u0026#34;libinput\u0026#34; Option \u0026#34;AccelerationProfile\u0026#34; \u0026#34;2\u0026#34; Option \u0026#34;Sensitivity\u0026#34; \u0026#34;0.1\u0026#34; Option \u0026#34;Tapping\u0026#34; \u0026#34;on\u0026#34; Option \u0026#34;ClickMethod\u0026#34; \u0026#34;clickfinger\u0026#34; Option \u0026#34;TappingButtonMap\u0026#34; \u0026#34;lrm\u0026#34; Option \u0026#34;NaturalScrolling\u0026#34; \u0026#34;on\u0026#34; EndSection # 以下为新增 Section \u0026#34;InputClass\u0026#34; Identifier \u0026#34;system-mouse\u0026#34; MatchIsPointer \u0026#34;on\u0026#34; Driver \u0026#34;libinput\u0026#34; EndSection 退出 X,重新进入之后,外接鼠标就可以正常工作了。\n可以从 Xlog 中得到印证:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 yychi@~/.local/share/xorg\u0026gt; grep -i libinput Xorg.0.log [ 759.488] (II) LoadModule: \u0026#34;libinput\u0026#34; [ 759.488] (II) Loading /usr/lib/xorg/modules/input/libinput_drv.so [ 759.489] (II) Module libinput: vendor=\u0026#34;X.Org Foundation\u0026#34; [ 759.490] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Power Button\u0026#39; [ 759.510] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Video Bus\u0026#39; [ 759.513] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Video Bus\u0026#39; [ 759.517] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Sleep Button\u0026#39; [ 759.521] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Logitech Wireless Keyboard PID:4023\u0026#39; # 1 [ 759.524] (II) libinput: Logitech Wireless Keyboard PID:4023: needs a virtual subdevice [ 759.527] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Logitech Wireless Mouse\u0026#39; # 2 [ 759.532] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Logitech Wireless Mouse\u0026#39; [ 759.532] (EE) libinput: Logitech Wireless Mouse: Failed to create a device for /dev/input/mouse2 [ 759.532] (II) UnloadModule: \u0026#34;libinput\u0026#34; [ 759.533] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;XiaoMi USB 2.0 Webcam: XiaoMi U\u0026#39; [ 759.537] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;ELAN2301:00 04F3:306B Mouse\u0026#39; [ 759.542] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;ELAN2301:00 04F3:306B Mouse\u0026#39; [ 759.542] (EE) libinput: ELAN2301:00 04F3:306B Mouse: Failed to create a device for /dev/input/mouse0 [ 759.542] (II) UnloadModule: \u0026#34;libinput\u0026#34; [ 759.543] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;ELAN2301:00 04F3:306B Touchpad\u0026#39; [ 759.552] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;AT Translated Set 2 keyboard\u0026#39; [ 759.559] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Wireless hotkeys\u0026#39; [ 759.591] (II) Using input driver \u0026#39;libinput\u0026#39; for \u0026#39;Logitech Wireless Keyboard PID:4023\u0026#39; [ 759.591] (II) libinput: Logitech Wireless Keyboard PID:4023: is a virtual subdevice #1 和#2 处就是外接的罗技无线键鼠套装。\n","permalink":"https://guyueshui.github.io/post/%E8%AE%B0%E4%B8%80%E6%AC%A1%E9%87%8D%E8%A3%85linux/","tags":null,"title":"记一次重装 Linux"},{"categories":["Notes"],"contents":"最大熵定理 设 $X \\sim p(x)$ 是一个连续型随机变量,其微分熵定义为 $$ h(X) = - \\int p(x)\\log p(x) dx $$ 其中,$\\log$ 一般取自然对数 $\\ln$, 单位为 奈特(nats)。\n考虑如下优化问题: $$ \\begin{array}{ll} \u0026amp;\\underset{p}{\\text{Maximize}} \u0026amp; \\displaystyle h(p) = - \\int_S p(x)\\log p(x) dx \\newline \u0026amp;\\text{Subject to} \u0026amp;\\displaystyle \\int_S p(x) dx = 1 \\newline \u0026amp;~ \u0026amp; p(x) \\ge 0 \\newline \u0026amp;~ \u0026amp; \\displaystyle \\int_S p(x) f_i(x) dx = \\alpha_i, ~i=1,2,3,\\dots,n \\end{array} $$ 其中,集合 $S$ 是随机变量的 support,即其所有可能的取值。我们意图找到这样的概率分布 $p$, 他满足所有的约束(前两条是概率公理的约束,最后一条叫做矩约束,在模型中有时会假设随机变量的矩为常数),并且能够使得熵最大。将上述优化问题写成标准形式:\n$$ \\begin{array}{ll} \u0026amp;\\underset{p}{\\text{Minimize}} \u0026amp; \\displaystyle \\int_S p(x)\\log p(x) dx \\newline \u0026amp;\\text{Subject to} \u0026amp;-p(x) \\le 0 \\newline \u0026amp;~ \u0026amp;\\displaystyle \\int_S p(x) dx = 1 \\newline \u0026amp;~ \u0026amp; \\displaystyle \\int_S p(x) f_i(x) dx = \\alpha_i, ~i=1,2,3,\\dots,n \\end{array} $$\n使用Lagrange 乘数法得到其 Lagrangian\n$$ L(p,\\boldsymbol{\\lambda}) = \\int_S p\\log p ~dx - \\mu_{-1}p + \\mu_0 \\left(\\int_S p dx - 1\\right) + \\sum_{j=1}^n \\lambda_j \\left(\\int_S pf_jdx - \\alpha_j\\right) $$\n根据 KKT 条件对 Lagrangian 求导令为 0,可得最优解。\n$$ \\begin{gathered} \\frac{\\partial L}{\\partial p} = \\ln p + 1 - \\mu_{-1} + \\mu_0 + \\sum_{j=1}^n \\lambda_jf_j := 0 \\newline \\implies p = \\exp\\left(-1 + \\mu_{-1} - \\mu_0 - \\sum_{j=1}^n \\lambda_j f_j \\right) =\\displaystyle c^{\\star} e^{-\\sum_{j=1}^n\\lambda_j^{*} f_j(x)} := p^{\\star} \\end{gathered} $$\n其中,我们要选择 $c^{\\star}$, $\\boldsymbol{\\lambda}^{\\star}$ 使得 $p(x)$ 满足约束。到这里我们知道,在所有满足约束的概率分布当中,$p^{\\star}$ 是使得熵达到最大的那一个!\n举例 1. 高斯分布 约束:\n$E(X) = 0 \\implies f_1 = x$ $E(X^2) = \\sigma^2 \\implies f_2 = x^2$ 根据上面的论证,最大熵分布应具有如下形式:\n$$ p(x) = ce^{-\\lambda_1x - \\lambda_2 x^2} $$\n再根据 KKT 条件:\n$\\int_{-\\infty}^{+\\infty} p(x) = 1$ $\\int_{-\\infty}^{+\\infty} x p(x) = 0$ $\\int x^2 p(x) = \\sigma^2$ 由条件 $(2) \\implies p(x)$ 是偶函数 $\\implies \\lambda_1 = 0$, 原条件变成\n$\\int_{-\\infty}^{+\\infty} ce^{-\\lambda_2x^2} = 1$ $\\int x^2 ce^{-\\lambda_2x^2} = \\sigma^2$ $$ \\implies c = \\frac{1}{\\sqrt{2\\pi\\sigma^2}}, ~\\lambda_2 = \\frac{1}{2\\sigma^2} \\implies p(x) = \\frac{1}{\\sqrt{2\\pi\\sigma^2}} e^{-\\frac{x^2}{2\\sigma^2}} \\sim N(0, ~\\sigma^2) $$\n2. 指数分布 约束:\n$X \\ge 0$ $E(X) = \\frac{1}{\\mu}$ 根据上面的论证,最大熵分布应具有如下形式:\n$$ p(x) = ce^{-\\lambda_1x} $$\n再根据 KKT 条件:\n$\\int_{0}^{+\\infty} ce^{-\\lambda_1x}= 1$ $\\int_0^{+\\infty} x ce^{-\\lambda_1x} = \\frac{1}{\\mu}$ 推导如下:\n$$ \\begin{gathered} \\int_{0}^{+\\infty} e^{-\\lambda_1x} = \\frac{1}{c} \\implies \\lambda_1 = c \\newline \\int_{0}^{+\\infty}x e^{-\\lambda_1x} = \\frac{1}{c\\mu} = \\frac{1}{\\lambda_1\\mu} \\implies \\lambda_1 = \\mu \\end{gathered} $$\n$\\implies p(x) = \\mu e^{-\\mu x} \\sim Exp(\\mu)$\n3. 均匀分布 约束:\n$a \\le X \\le b$ 根据上面的论证,最大熵分布应具有如下形式: $$ \\begin{gathered} p(x) = ce^{- 0x} = c \\newline \\int_a^b c ~dx = 1 \\implies c = \\frac{1}{b-a} \\end{gathered} $$ $\\implies p(x) = \\frac{1}{b-a} \\sim Unif(a,~b)$\n4. 几何分布 几何分布计数直到第一次成功前所有的失败次数。$P(X=k) = q^kp$ 约束:\n$X = 0,1,2,\\dots$ $E(X) = \\frac{1-p}{p}$ 根据上面的论证,最大熵分布应具有如下形式:\n$$ P(X=k) = p_k = ce^{-\\lambda_1 k} $$\n再根据 KKT 条件:\n$\\sum_{k=0}^{\\infty} p_k = 1$ $\\sum_{k=0}^{\\infty} k p_k = \\frac{1-p}{p}$ 推导如下:\n$$ \\begin{gathered} \\sum_{k=0}^{\\infty} ce^{-\\lambda_1 k} = c \\sum_{k=0}^{\\infty} q^k \\quad(\\text{where }q = e^{-\\lambda_1}) \\newline = \\frac{c}{1-q} \\implies c = 1-q \\newline \\sum_{k=0}^{\\infty} k ce^{-\\lambda_1 k} = c\\sum_{k=1}^{\\infty} k (e^{-\\lambda_1})^k = c\\sum_{k=1}^{\\infty}k q^k = cq \\sum kq^{k-1} \\newline = cq \\sum (q^k)\u0026rsquo; = cq \\left(\\sum_{k=1}^{\\infty}q^k\\right)\u0026rsquo; = cq \\left(\\frac{q}{1-q}\\right)\u0026rsquo; \\newline = cq \\cdot \\frac{1}{(1-q)^2} = \\frac{q}{1-q} = \\frac{1-p}{p} \\end{gathered} $$\n$\\implies e^{-\\lambda_1} = q = 1-p, ~ c =p \\implies P(X=k) = p_k = pq^k \\sim Geom(p)$\n","permalink":"https://guyueshui.github.io/post/%E6%9C%80%E5%A4%A7%E7%86%B5%E5%AF%B9%E5%BA%94%E7%9A%84%E6%A6%82%E7%8E%87%E5%88%86%E5%B8%83/","tags":["math"],"title":"最大熵对应的概率分布"},{"categories":["随笔"],"contents":" 写于二十四岁之际,匆匆人间已经走过两纪,观之苦辣酸甜,皆归于平淡。\n去日寥寥终不谏,\n阴晴变换亦难圆。\n无尤无怨无执念,\n来纪来年来易之。\n丁酉腊月廿四\n","permalink":"https://guyueshui.github.io/post/%E4%BA%8C%E5%8D%81%E5%9B%9B/","tags":["诗词曲赋"],"title":"二十四"},{"categories":["随笔"],"contents":"晚风射枝断,宴罢歌舞歇。 归卧孤衾倦揽,寒阁暗对,幽恨新结。\n不识莲心苦,尽日尝相忘。 设入骤雪梦中,清白天地,独立苍茫。\n","permalink":"https://guyueshui.github.io/post/%E6%88%8F%E5%B0%8F%E8%B0%83/","tags":["诗词曲赋"],"title":"戏小调"},{"categories":["随笔"],"contents":"不知何时开始,整理情绪,对我来说成了一件十分困难的事。每次想发点什么,总怕被说矫情,想想还是不发了吧。想必很多灵感都是这样被扼杀了吧。但是人总是要改变的,无论随着时间,还是随着空间。四年还是变了不少的,比如变胖了什么的(这是主要的,饭量大我也没办法,括弧笑)。性情什么的也有变化,主要是理工男气质(猥琐?),当然也有正经的变化。改变是福是祸无从判断,但每个改变都是自己的选择,所以也应该释然。\n学业 我不是谦虚,只是有自知之明。总结来说一句话:水平太洼。缘由嘛,主要是自己(也适当甩锅给环境,我比较在意周围的环境,看别人玩,我也想玩。)很多时候,在学习和娱乐之间选择了娱乐。老天是公平的,给你一生的时间,将选择权交给个人,有些人选择中途退场,有些人选择苟活一生,有些人嘛……你用来娱乐,起码收获了快乐,谁说一时的愉悦没有价值?!;用来刻苦努力,收获知识,为了什么?为生活,为实现自我价值,无论目的如何,你还是收获了知识;你做的任何选择都有意义,从来不存在无意义的事情,有的只是一个念想的差别。所以,现在的我是我一个个选择堆叠而成,不须后悔。未来未可知,正是人活着最美好的意义。\n交际 很抱歉,我还是不擅长交际。这可真是一门大学问!大到前后矛盾,颠覆传统。我从前认为,人生话在自然,只要和自然好好相处就行了。万万没想到啊……关于这方面,好心的人们教了我很多,能教的都教了,可是我还是有点抗拒。很抱歉,当我熟练的时候你们可能会讨厌我了,虽然我本来也不招人喜欢。这也是没有办法的事情,就让我撞墙吧,多撞撞就好了。曾经也考虑过赤子之心被社会同化的问题,到底如何坚守,什么不忘初心,方得始终(噗,每次听都挺尬),说到底也只能走一步看一步了。曾经也爱胡思乱想,领悟人生道理,可是碰不到高人,没有人告诉我是否正确。越想越迷,越迷越想,快要想通的时候,额,就睡着了。人生也是这样,等到经历足够,经验丰富的时候,也是最睿智的时候,可那时候你就要狗带了。或许是老天爷的玩笑,每当人要悟得真理的时候,不是要睡着了,就是要够带了。所以啊,人的一生真的很笨啊!唯有初生婴儿与垂死老人,是最具智能的。一个纯白,一个纯黑,什么都不懂和什么都懂,到底谁更具有智慧呢?嘿嘿,是时候安利一波:无极之内,在极之外。入我道门,知我自然。\n遗憾 基友一群,女票纳西。不好说多,说多了像思春一样了。语文!我还是很喜欢诗词歌赋的啊,我还是很喜欢书法的啊,无奈理工男没有语文?!荒废了诗词书法,唉,虽然没有认真学过,但作为业余,绝对是爱着的!\n致谢 谢天谢地谢空气,谢室友不杀。要说我对这个学校还有一丝留恋,那绝对是留恋那一群人。一起上课吃饭,一起抄后排,一起迟到翘课,一起去图书馆搞事,一起彻夜不眠,一起谈天说地,一起打拳皇,火影,doa,三国杀开黑。还有我要吐槽最后居然一张正经班级合照都没发。发个纪念册算什么?我只想要一张合照,实体照片!还有些遗憾,没有认识的学弟学妹,到最后想送点东西都送不掉,可能是因为太宅了。还有某豪,真的感谢你!待我如亲生的一般,无论精神上还是物质上,都给了我很大的鼓舞与帮助,感谢某豪,我愿永远叫你一声大哥!\n结语 所有的所有……哦,我还有个原则,还遵守的蛮好。有时候做事特别随性,比如地上掉了一块钱,我捡不捡:捡,一块也是钱,积少可以成多,你不捡就是不尊重钱,将来也赚不到钱;不捡,路不拾遗,夜不闭户,如是而已。再比如碰到一个蜻蜓粘在蜘蛛网上:救,上天有好生之德;不救,万物生存,自有定数,不可干预。再比如碰到要饭的:给,将心比心,我也有受人帮助的时候;不给,人有善恶,我今天就恶一回了,你恰好碰到我黑化的时候,也是劫数。诸如此类的……让我们继续,所有的所有,先说声再见!当我翻开相册时,我还会回来的…\n——易之 2017.6.23 ","permalink":"https://guyueshui.github.io/post/%E6%9F%90%E4%B8%8D%E6%AD%A3%E7%BB%8F%E7%9A%84%E4%B8%AA%E4%BA%BA%E6%80%BB%E7%BB%93/","tags":null,"title":"某不正经的个人总结"},{"categories":["随笔"],"contents":" 有时候我们需要一个假想敌,来抵制一天的堕落。\n:今天也是满负罪恶感的一天呢?\n\u0026ndash;嗯,其实我还蛮会自我减压的。\n:这已经称得上是放纵了呢!\n\u0026ndash;不不不,我的神经有紧绷过吗?\n:那你准备何时紧绷。\n\u0026ndash;明天吧,不过今天真是满负罪恶感的一天呢!\n","permalink":"https://guyueshui.github.io/post/%E5%81%87%E6%83%B3%E6%95%8C/","tags":[],"title":"假想敌"},{"categories":["随笔"],"contents":" 看过《言叶之庭》,特别喜欢雪野百香里。\n一直以来\n找不到合适的词语\n不知道用什么情感\n——描述你\n久而久之\n连一切的起因也忘了\n好像你的存在\n就是这世界全部的秘密\n我和我不知道的某段文字\n也只能记录些许\n南方雁归,北方雪飞\n愿你和这个包围着你的世界\n——幸福如题\n","permalink":"https://guyueshui.github.io/post/%E9%9B%AA%E4%B9%8B%E9%87%8E/","tags":["诗词曲赋","现代诗"],"title":"雪之野"},{"categories":["tech"],"contents":"首先 Newifi mini 是一款很小巧美观的路由器,颜值即是正义嘛。再加上性价比高,易于刷写第三方系统,所以嘛,值得一买。\n规格参数 WAN(10/100Mbps) LAN(10/100Mbps)*2 双频:2.4GHz:300Mbps+5GHz:867Mbps USB2.0 接口 外置天线*2 天线增益:3dBi 128MB 内存 资源 PandoraBox 下载源:http://downloads.pandorabox.com.cn 旧版 (2015 年 1 月):设备代号 Lenovo-Y1_RY-1S 新版 (2017 年 1 月):newifi-mini 附Changelog 我用旧版安装 shadowsocks 时碰到很多问题,一时无解于是刷了新版,顺便说一下新版网页端是 Material Design,很好看。\n下面开始刷入 PandoraBox:\n下载好相应固件 通过有线连接路由器和 PC,将 PC 端 IP 设置为 192.168.1.254,子网掩码 255.255.255.0,网关 192.168.1.1 拔下路由器电源,再次插上,迅速按下 RESET 键,若设备两个蓝灯连续闪烁,说明已经进入恢复模式 在浏览器中输入 192.168.1.1 进入恢复模式页面,选择之前下好的固件开始刷入 等候 1-2 分钟,将 PC 端 IP 设置为自动获取,在浏览器中输入 192.168.1.1 即可开始常规配置 配置路由器 配置 SSH 远程登录,默认配置允许,输入 ssh root@\u0026lt;hostname\u0026gt;以登录路由器 配置 Samba 文件共享,也可以在网页端配置,以便传输必要文件到路由器 配置 opkg 软件源,写下这篇文章时,以下源可用 dest root / dest ram /tmp lists_dir ext /var/opkg-lists option overlay_root /overlay src/gz 17.01_core http://downloads.pandorabox.com.cn/pandorabox/targets/ralink/mt7620/packages src/gz 17.01_base http://downloads.pandorabox.com.cn/pandorabox/packages/mipsel_24kec_dsp/base src/gz 17.01_lafite http://downloads.pandorabox.com.cn/pandorabox/packages/mipsel_24kec_dsp/lafite src/gz 17.01_luci http://downloads.pandorabox.com.cn/pandorabox/packages/mipsel_24kec_dsp/luci src/gz 17.01_mtkdrv http://downloads.pandorabox.com.cn/pandorabox/packages/mipsel_24kec_dsp/mtkdrv src/gz 17.01_packages http://downloads.pandorabox.com.cn/pandorabox/packages/mipsel_24kec_dsp/packages 接下来安装 SS:\n远程登录路由器 执行 opkg update 执行 opkg install shadowsocks-libev 执行 opkg install luci-app-shadowsocks 浏览器中输入 192.168.1.1,服务栏目里应该多了一个 Shadowsocks 上述安装过程可能会报密钥校验不通过,此时可以在 opkg 里加强制选项绕过,还有些情况需要手动下载软件包上传到路由器进行本地安装,总之还有一些小问题没有提到的。未尽之处,尽力而为吧。\n刷入步骤参考了 LinuxToy 网站的博客:Newifi Mini 安装 OpenWrt\n如有侵权,请通知我,我会修改的 0v0\n","permalink":"https://guyueshui.github.io/post/newifi-mini%E5%AE%89%E8%A3%85pandorabox/","tags":["路由器","个性化","刷机"],"title":"Newifi Mini 安装 PandoraBox"},{"categories":["随笔"],"contents":" 室友明日考研,祝他顺利吧。\n拂影寻花径,\n拨云探青天。\n平生何来意,\n潦潦一纸间。\n","permalink":"https://guyueshui.github.io/post/%E7%A5%9D%E8%80%83%E7%A0%94%E9%A1%BA%E5%88%A9/","tags":["诗词曲赋"],"title":"祝考研顺利"},{"categories":["随笔"],"contents":" 今序:那个时候我还单身,在图书馆自习,一对情侣坐在我前边,甚烦,扰人学习,作此相赠。\n原序:某不动,见来人,胶粘状,遂寄言。\n劳燕相与飞。\n日月相伴明。\n参商相见欢。\n与君相偕老。\n","permalink":"https://guyueshui.github.io/post/%E8%B5%A0%E6%9C%89%E6%83%85%E4%BA%BA/","tags":["诗词曲赋"],"title":"赠有情人"},{"categories":["Linux"],"contents":"Linux 需要备份吗?本身 Linux 系统的稳定性就是一流,文件系统也不易产生碎片,只要不是硬盘突然崩掉了,你可以有 100 种方法来修复系统的各种问题而不用重装系统。但是恰好我不是多么熟练的 Linux 使用者,每次出问题也是自己在网上边查边解决,有时候也会遇到那种查了几天也没能解决的问题,所以重装 Linux 这样的情景也会时常发生。那么,如果事先做了备份,这时候就能起到很大的作用了。\nTar 备份 创建 exclude 列表,排除不需要备份的文件。一个样例:\n#vi /excl /proc/* /dev/* /sys/* /tmp/* /mnt/* /media/* /run/* /var/lock/* /var/run/* /var/lib/pacman/* /var/cache/pacman/pkg/* /lost+found 准备一个 liveCD,也就是安装 arch 的 u 盘。 插入 u 盘,进入 bios,设置 u 盘为优先启动。 进入 u 盘系统,挂载好原系统的分区。一个样例: 1 2 3 4 mount /dev/sda2 /mnt mkdir /mnt/{boot,home} mount /dev/sda1 /mnt/boot mount /dev/sda3 /mnt/home 挂载之后就可以执行 chroot 进入要备份的系统了。\n1 arch-chroot /mnt /usr/bin/bash 进去之后,执行\n1 tar cvpjf backup20160910.tar.bz2 --exclude-from=/excl / 这里 excl 是一开始创建的过滤列表,若它不在 tar 命令的执行路径内,则应将路径写完整。 这里建议 tar 的执行路径不包含在需要打包的路径内,即 tar 的执行路径最好放在 excl 列表中的某个文件夹内,只是为了防止递归备份。 最后,当然要保证磁盘空间充足。 这样,整个系统就被打包好了。在 tar 的执行路径下,应该可以看到备份文件了。\nTar 还原 备份好的包可以用来还原,迁移系统。\n首先,插 u 盘进入 liveCD。规划好分区,格式化啥的,参见 archwiki 的Beginner\u0026rsquo;s Guide. 同样的,挂载好分区。一个样例:\n1 2 3 4 mount /dev/sda2 /mnt mkdir /mnt/{boot,home} mount /dev/sda1 /mnt/boot mount /dev/sda3 /mnt/home 当然,需要挂在备份包的存储分区。一个样例:\n1 2 mkdir /backup mount /dev/sda4 /backup 其中,备份包的存储位置是 sda4,这里插一句,大家是怎么分辨 sdax 对应哪块空间的?反正我是根据大小啦=。=\n创建临时目录/backup 作为 sda4 的挂载点。最后执行:\n1 2 cd /mnt tar xvpjf /backup/backup20160910.tar.bz2 将备份包解压到对应的位置。然后生成 fstab:\n1 genfstab -U -p /mnt \u0026gt;\u0026gt; /mnt/etc/fstab 执行完成后建议检查一下/etc/fstab 的正确性。接着进入恢复好的系统:\n1 arch-chroot /mnt /bin/bash 重新配置启动引导:\n1 grub-mkconfig -o /boot/grub/grub.cfg 这样,备份包就恢复好了。\n退出 chroot,卸载目录,重启,应该可以进入系统了,还是熟悉的面孔。\n1 2 3 exit umount -R /mnt reboot 后话 咦呀,我是第一次写博客,而且是博客园这样大的平台,写到这里还是惊魂未定 0v0。我也有自知之明,一开始申请写博客权限的时候也写明了:借园子这样的好地方,边学习,边记录。事实上,我也是刚刚接触 linux,今年 6 月份端午的时候。折腾了三个月,一直在折腾,因为它总是冒出莫名其妙的问题,有的解决了,有的没能解决。事后观之,在折腾的过程中,虽说没学到啥实质性的技术,但至少了解了一些处理问题的框架模式,自己也能动手解决一些小问题了,对自己还是很有帮助的。\n事实上,本文写的事情 uqi 已经折腾了三四次了。一开始打算装着玩,linux 这边分的空间太少了。期间加过一两次,加上这次的大改,重新划了分区表。每次操作都重新找教程,于是这次自己把它写下来,方便以后查看,O(∩_∩)O 哈哈~\n好了,就这样,我第一次写博客,希望看官手下留情啊,任何意见我都会听的。谢谢~\n","permalink":"https://guyueshui.github.io/post/%E4%BD%BF%E7%94%A8tar%E5%A4%87%E4%BB%BDarchlinux/","tags":["备份","tar"],"title":"使用 Tar 备份 Archlinux"},{"categories":["随笔"],"contents":"昨日武昌水,纵横磨山北。燕雀噙新枝,怨号惊山鬼。\n幽恨度频传,闻言声亦老。谁折陌上花,一报江南好?\n","permalink":"https://guyueshui.github.io/post/%E7%94%9F%E6%9F%A5%E5%AD%90/","tags":["诗词曲赋"],"title":"生查子·廿七纪事"},{"categories":["随笔"],"contents":"几日度离魂,午后闲萧索。\n起身斜照间,指染门前土。\n道是无情人,偏作有情甫。\n原是离别多,何必离别苦。\n","permalink":"https://guyueshui.github.io/post/%E5%8F%A4%E5%88%AB/","tags":["诗词曲赋"],"title":"古别"},{"categories":["随笔"],"contents":" 今冬返数日,串村之后,与老者话聊,竟不见其中人许。细问之下,原是身逝黄土。时天气晴寒,鸡蜷狗缩,又东北风呼啸,坐家中远观旧物,因忆旧人:十余年前,同道者众。夏气夺人,游于小池。水中游戏,瓜田取瓜。上则就近而坐,谈天论地,斗牌弄珠。比及冬寒日重,年关将至,则朋访友门,互张灯彩,欢情酣处,取鞭炮戏耍。人生之快,何能胜哉?而时过经年,朋走友散,各行其是,心性互异,此非一日之功。况人事更替,打击非凡。何能怨哉?今二老去其一,则日后暖不得同享,寒不能同衾,病不能相伴,食不得同堂。哀哉!百年孤独,半生劳碌,老有何求?正是尝尽人间辛酸苦,方知幸福是当初。白丝攀在青丝上,始信岁月神鬼工。乙未年冬于被中。\n昔年邻小欢乐处,而今只见旧池台。\n鸦雀枯枝夕阳颤,泥路荒田足迹歪。\n风刀霜剑楼未倒,寂境孤心人已衰。\n多情寄予庭前柳,冬雪葬尽抒春怀。\n","permalink":"https://guyueshui.github.io/post/%E9%81%A3%E6%80%80/","tags":["诗词曲赋"],"title":"遣怀・并序"},{"categories":["文学"],"contents":"1.【浣溪沙】 暖雨无晴漏几丝,牧童斜插嫩花枝。小田新麦上场时。\n汲水种瓜偏怒早,忍烟炊黍又嗔迟。日长酸透软腰肢。\n据传,这首词是用粉写在一张芍药叶片上的。词的上片意象很美,你看,初夏的阵雨飘飞在山谷田野,已上场的新麦散发着阵破清香,头上插花的牧童悠然自得地骑在牛背上。可是,在这样一个丰收的季节里,词人却没有丝毫的喜悦。她干完了农活,又要回家做饭,忙得腰酸背疼,仍不免受到挑剔与责骂。怒早、嗔迟,真是左右为难。夫悍姑恶,一切都看不顺眼。双卿是那么的无助,身体、精神上备受煎熬,心中的苦闷、忧愤、伤痛无以排遣,无处表达,唯有借助手中的笔,将满腔的幽怨倾诉于纸上,形成一首首滴血含泪的诗篇。诗词是她唯一的听众,唯一的寄托,也是生命中唯一的闪光点,夜晚独自一人,内心的思绪如泉涌流于笔端,内心的痛苦也如泉水延绵流长,“此恨绵绵无绝期”,于是忍痛含悲,写下了这阕《浣溪沙》。\n2.【望江南】 春不见,寻过野桥西。染梦淡红欺粉蝶,锁愁浓绿骗黄鹂,幽恨莫重提。\n人不见,相见是还非?拜月有香空惹袖,惜花无泪可沾衣,山远夕阳低。\n这是女词人伤春怀旧之作。全词笼罩在凄冷欲绝的感情基调中,透露出满腔的幽恨。词人也有过美好的过去,有过情窦初开的青春年华,她还似乎曾有过美好而甜蜜的爱情,有过自己的心上人,然而吃人的封建婚姻制度,无情地拆散了有情人。上片“春不见”,暗指自己美好的青春韶华在自然的周而复始中一去不返,感叹往事不堪回首。下片“人不见”,所爱的人再难相见,即使相见,恐怕也因时过境迁而今昔非比了。正如苏东坡所云“纵使相逢应未识,尘满面,鬓如霜”啊。当日焚香拜月温馨的一幕浮现眼前,却已成过眼云烟。看着曾娇艳的花朵也已渐渐凋落,感叹逝者如斯,美好事物的不常在,却无泪沾衣,泪流尽了,心也碎了。往事已随花逝去,只留下淡淡的影子,眼前漂泊尽前事,恍若梦中。不幸的命运,正像眼前远山残照一样,令人黯然销魂。\n全词表情细腻婉转,凄恻动人,不雕饰,凭自己感受,似从心底流出。\n3.【湿罗衣】 世间难吐只幽情,泪珠咽尽还生。手捻残花,无言倚屏。镜里相看自惊,瘦亭亭。春容不是,秋容不是,可是双卿!\n问世间情为何物?直叫人生死相许。而最痛苦的还是有情不能诉,有苦无处说。痛苦的泪珠,独自吞进肚里,可是,刚刚咽下,泪水又盈满眼眶了。伤悲难止,欲哭无声。人如残花,只有无言倚屏啊。顾影自怜,花容憔悴,自己也认不出自己了。词中,双卿用寻常的语言表现所见、所闻、所想,亲身经历,真实感受,透出一种别样的风采。\n4.【玉京秋】自题《种瓜小影》 眉半敛,春红已全褪,旧愁还欠。画中瘦影,羞人难闪。新病三分未醒,淡胭脂,空费轻染。凉生夜,月华如水,素娥无玷。\n翠袖啼痕堪验。海棠边,曾沾万点。怪近来,寻常梳裹,酸咸都厌。粉汗凝香蘸碧水,罗帕时揩冰簟。有谁念。原是花神暂贬?\n这首《玉京秋》是应段玉函之请而作。段玉函找画家张石林为双卿画了画像,是双卿种瓜图,请双卿自题一首词于其上,便是此词。\n5.【二郎神】菊花 丝丝脆柳,袅破淡烟依旧。向落日秋山影里,还喜花枝未瘦。苦雨重阳挨过了,亏耐到小春时候。知今夜,蘸微霜,蝶去自垂首。\n生受,新寒浸骨,病来还又。可是我双卿薄幸,撇你黄昏静后。月冷阑干人不寐,镇几夜,未松金扣。枉辜却、开向贫家,愁处欲浇无酒。\n清代陈廷焯撰《白雨斋词话》评曰:“此类皆忠厚缠绵,幽冷欲绝。而措语则既非温、韦,亦不类周、秦、姜、史,是仙是鬼,莫能名其境矣。”\n6.【孤鸾】病中 午寒偏准,早疟意初来,碧衫添衬。宿髻慵梳,乱裹帕罗齐鬓。忙中素裙未浣,褶痕边,断丝双损。玉腕近看如茧,可香腮还嫩。\n算一生凄楚也拚忍。便化粉成灰,嫁时先忖。锦思花情,敢被爨烟薰尽。东菑却嫌饷缓。冷潮回,热潮谁问?归去将棉晒取,又晚炊相近。\n双卿体质素弱,在娘家很少干重体力活,到周家后受尽折磨,患上了严重的疟疾,婆婆丈夫不但不治,而且丝毫不体贴她,重活脏活全要她承担。因为疟疾忽冷忽热经常发作,双卿被折磨得面黄肌瘦,憔悴不堪。有一次她提着竹篮去给田地干活的丈夫送饭,路上突然疟疾发作,倒在地上不停的寒战,好不容易挨了过去,她又跌跌撞撞向自家的田头走去。周大旺干活干得有些饿了,左等右等不见妻子送饭来,窝下了一肚子火。终于看见了妻子的身影,又是一副要死不活的子,顿时来了火气,顺手摸起身边的锄头便向双卿砸来。贺双卿大吃一惊,连忙丢下饭篮就往回跑去,脚下轻轻飘飘,头却昏昏沉沉。一路摔倒了好多次,才勉强摸进了家门。“算一生凄楚也拚忍。便化粉成灰,嫁时先忖。”这是词人在心底悲愤的控诉,可是,既然命中如此,双卿又有什么办法呢?“冷潮回,热潮谁问?”这是词人无助的叹息。在《孤鸾(病中)》中,她以巧妙地比喻和生动的细节描写,既通俗自然又十分含蓄地表达了自己的悲惨命运,引起人们对弱女子的无限同情和对悍夫恶姑的异常憎恨。\n7.【惜黄花慢】孤雁 碧尽遥天,但暮霞散绮,碎剪红鲜。听时愁近,望时怕远,孤鸿一个,去向谁边?素霜已冷芦花渚,更休倩、鸥鹭相怜。暗自眠,凤凰纵好,宁是姻缘!\n凄凉劝你无言。趁一沙半水,且度流年。稻梁初尽,网罗正苦,梦魂易警,几处寒烟。断肠可似婵娟意,寸心里,多少缠绵!夜未闲,倦飞误宿平田。\n一日黄昏,将近晚炊时,病中的双卿携着畚箕从打谷场上归来,听见一只孤雁在远方的芦苇丛中无助的哀鸣,她西立而望,呆立半天,联想起自己凄凉的身世,不由潸然泪下。正好给她婆婆看见了,在背后大声呵斥,双卿素来胆小易惊,且久病体虚,被吓得畚箕落地,后来她越发心神不宁,从此又染上了易受惊吓的毛病,情动于中,数日后和泪写成这首词。\n词托咏孤雁,寄意遥深,情悲声苦,凄婉欲绝,实乃自抒身世之感。词中孤雁漂泊无依,分明是女词人自己一生孤苦凄凉的形象概括。作者以怜悯之心关怀着孤雁,似乎可以体会到它的孤独、它的无助,“暮霞散绮”,一只大雁孤独地飞翔于广袤的天际之中,“听时愁近,望时怕远;孤鸿一个,去向谁边?”作者以一颗细腻敏感而善良多情的心设想着孤雁的感受,对孤雁关怀备至,一往情深。作者怕听愁声,又同情孤雁飞得太远。而这孤雁离开最喜欢芦芦花渚,原来是素霜已冷,又不愿成双成对的鸥鹭相怜,虽然凤凰这同伴还不错,却也不可能结成姻缘,此地多留无益。\n尤其下片殷勤寄语,无一不是发自肺腑,仿佛与一位“同是天涯沦落人”的知己共诉衷肠。篇中句句写孤雁,句句不离人。落墨虽在雁,意旨却在人,人雁相通,浑然一体。结语“夜未闲,倦飞误宿平田”,正是女词人明珠暗投,误落田家不幸命运的真实写照。哀哉孤雁,悲哉双卿!下片是先由作者观望孤雁,现在田里也没有野食,猎人又伺机而动,还不如随边便找个“一沙半水”先栖息下来。不过,这孤雁自有它的伤心之处,终究夜半误宿了荒野的平田,饥寒已是无法避免的了。\n如陈廷焯所言:“此词悲怨而忠厚,读竟令人泣数行下。”\n8.【凤凰台上忆吹箫】残灯 已暗忘吹,欲明谁剔?向侬无焰如萤。听土阶寒雨,滴破三更。独自恹恹耿耿,难断处、也忒多情。香膏尽,芳心未冷,且伴双卿。\n星星。渐微不动,还望你淹煎,有个花生!胜野塘风乱,摇曳渔灯。辛苦秋蛾散后,人已病、病减何曾?相看久,朦胧成睡,睡去空惊。\n这是有一次,因劝谏丈夫,反给丈夫禁闭在厨房里,只有一盏半明不灭的残灯作着她,引起了她的幽怨,写下了这心弦的哀音,人是凄凉,景是凄凉,事是凄凉,词是凄凉,读来让人一掬同情之泪,让人唏嘘不止。\n作者通过对残灯的观察、描绘,创造出凄凉的氛围。夜晚,万籁寂无声,暮色中一盏残灯摇曳闪烁微弱的灯光,孤独凄冷,“独自恹恹耿耿”的残灯,如同灯下柔弱孤寂的作者,“香膏尽,芳心未冷,且伴双卿”,无人陪伴的夜晚,有了残灯的相随,亦可聊以自慰。只是,他们的命运是那么的相似,一个是即将熄灭的残灯,一个是被折磨、被伤害的双卿。看灯,也是在看自己,哀悼残灯的命运,也是在感叹自身的不幸,词中虽没有直言控诉压迫她的恶势力,然而字里行间,都浸透着一个封建社会中受尽侮辱、欺凌的女子的血和泪。\n双卿是善良的,她的感情是细腻的,常常借咏物来抒发自己的感慨。善于运用孤独、衰残、暗淡、凄冷的意象,来抒写绝望的情怀,实为自己不幸的遭遇和悲凉的心境的写照。\n9.【薄幸】咏疟 依依孤影,浑似梦、凭谁唤醒!受多少、蝶嗔蜂怒,有药难医花症。最忙时,那得功夫,凄凉自整红炉等。总诉尽浓愁,滴干清泪,冤煞娥眉不省。\n去过酉、来先午,偏放却、更深宵永。正千回万转,欲眠仍起,断鸿叫破残阳冷。晚山如镜,小柴扉烟锁,佳人翠袖恹恹病。春归望早,只恐东风未肯。\n一天,贺双卿清扫了屋里屋外,洗完一大盆衣服,又喂完鸡猪,刚想坐下来稍事歇息,婆婆又在院子里催她舂谷了,双卿从不敢违抗婆婆的指令,赶紧走到院子里开始舂谷。舂谷的石杵又大又重,她舂了一会儿,已累得汗流浃背,气喘吁吁,只好抱着杵休息片刻。正在这时,周大旺从地里回来。一进门见妻子无力地站在石臼边,抱着石杵一动也不动,便以为是她偷懒怠工,问也不问,就一把把她推倒在石臼旁。石杵正压在了她的腰上,双卿痛得好半天都爬不起来,痛苦屈辱的眼泪还不敢当着丈夫的面流出来。好不容易挣扎着舂完谷,又到了做午饭的时间,双卿来不及喘口气,又去厨房煮粥。粥锅坐在灶上,她则坐在灶坑前添柴烧火。浓烟一熏,加上过度的疲劳,头晕的老毛病又犯了,她只好闭上眼靠在灶台上。就在这工夫,锅里煮着的粥开了,溢出锅沿,弄得灶台上一片狼藉,还有几点热粥溅到贺双卿的脖子上,把她烫醒,睁眼一看,不由得低低地惊叫了一声。婆婆闻声探进头来一看,不禁火冒三丈,又是一顿吼骂。贺双卿早已听惯了她的呵叱,只是埋头清理灶台。杨氏一见媳妇那种对她要理不理的样子,更加气不打一处来。冲上前一把抓住双卿的耳环,用力一扯,把她的耳垂撕裂开来,鲜血流满了肩头。双卿仍然不敢反抗,却默默地咬牙忍住疼痛,擦干鲜血后,照常乖乖地把饭食送给婆婆和丈夫,婆婆和丈夫看都不看她一眼,坐下就大吃大嚼起来。双卿的泪水这才敢默默地流了下来。于是写下这首哀伤欲绝的《薄幸(咏疟)》。作者把自己比作花,把压制她的人比作蝶和蜂。“有药难医花症”,是因为“受多少、蝶嗔蜂怒”。终日无端的“嗔”和“怒”,即使有药可以医治好她的身体上的疾病,也难以医治她心灵上的伤痛。这首词写得很巧妙,借写自己的病,来写封建势力对自己的压迫,含蓄地表达了自己对封建势力的憎恨与控诉。\n10.【一剪梅】 寒热如潮势未平,病起无言,自扫前庭。琼花魂断碧天愁,推下凄凉,一个双卿。\n夜冷荒鸡懒不鸣,拟雪猜霜,怕雨贪晴。最闲时候妾偏忙,才喜双卿,又怒双卿。\n11.【摸鱼儿】谢邻女韩西馈食 喜初晴,晚霞西现,寒山烟外青浅。苔纹干处容香履,尖印紫泥犹软。人语乱,忙去倚、柴扉空负深深愿。相思一线,向新月搓圆;穿愁贯恨,珠泪总成串。\n黄昏后,残热犹怜细喘。小窗风射如箭。春红秋白无情艳,一朵似侬难选。重见远,听说道,伤心已受殷勤饯。斜阳刺眼,休更望天涯,天涯只是,几片冷云展。\n她的邻居韩西是她女伴,不识字,却爱双卿的作品,这样的女伴,几乎已是双卿婚后生活的唯一精神寄托,可是不久,韩西就嫁人了。一次,韩西回娘家小住后即将返回婆家,父母为之送行,韩西邀请双卿参加,可双卿疟疾犯了,不能前往,韩西就前去探望,并送去食物。饥寒交迫的双卿非常感动,于是和泪写下了这首《摸鱼儿(谢邻女韩西馈食)》。\n据《西青散记》记载:“邻女韩西,新嫁而归,性颇慧,见双卿独舂汲,恒助之。疟时,坐于床为双卿泣。不识字,然爱双卿书。乞双卿写心经,且教之诵。是时将返其夫家,父母得饯之。召双卿,疟弗能往,韩西亦诸食。乃分其所食自裹之遗双卿。双卿泣为此词,以淡墨细书芦叶。”\n12.【凤凰台上忆吹箫】赠邻女韩西 寸寸微云,丝丝残照,有无明灭难消。正断魂魂断,闪闪摇摇。望望山山水水,人去去,隐隐迢迢。从今后,酸酸楚楚,只似今宵。\n青遥,问天不应,看小小双卿,袅袅无聊。更见谁谁见,谁痛花娇?谁望欢欢喜喜,偷素粉,写写描描?谁还管,生生世世,夜夜朝朝?\n这首词是为别女友韩西而作,细腻地表现了她内心抑郁的情绪,再现与女友分别使她堕入深渊的情景。巧用叠字抒情写意,堪与词作大家李清照的“寻寻觅觅,冷冷清清,凄凄惨惨戚戚”之句媲美,将双卿后半生的酸楚尽相倾诉。“偷素粉、写写描描。谁还管,生生世世,夜夜朝朝。”这首词,情境哀凄,词义悲苦。用双字二十余叠,丝毫不露牵强痕迹。末尾三句,“谁还管,生生世世,夜夜朝朝”更是余怨无穷,紧紧扣人心弦,令人动容。\n双卿的词,没有华丽词藻,大多采用生活语言,善于将自己的情感融于自然景物中,揭示生活悲剧中所蕴藏的美质,创造出具有高度美学意义的境界。她还善于掌握声调韵律错综复杂的不同节奏,以适应自己思想感情有起伏变化。如《凤凰台上忆吹箫》通篇语言质朴,且巧用叠字,造成回环咏叹的艺术效果,缠绵悱恻,旋律优美,可谓情苦词哀,已达极致,加上连用叠字二十余韵,真可与李易安的《声声慢》相媲美,确是双卿自述境况的凄绝的哀歌。\n清代陈廷焯在《白雨斋词话》评曰:“其情哀,其词苦。用双字至二十余叠,亦可谓广大神通矣。易安见之,亦当避席。”\n13.【春从天上来】梅花 自笑恹恹,费半晌春忙,去省花尖。玉容憔悴,知为谁添?病来分与花嫌。正腊衣催洗,春波冷,素腕愁沾。硬东风、枉寒香一度,新月纤纤。\n多情满天坠粉,偏只累双卿,梦里空拈。与蝶招魂,替莺拭泪,夜深偷诵《楞严》。有伤春佳句,酸和苦,生死俱甜。祝花年,向观音稽首,掣遍灵签。\n处境悲苦,遇人不淑,在这种种折磨下,双卿也只能自我寻求些安慰了,通过读佛经,参拜观音,以求脱离苦海。\n14.【春从天上来】饷耕 紫陌春情,漫额裹春纱,自饷春耕。小梅春瘦,细草春明。春田步步春生。记那年春好,向春燕、说破春情。到于今,想春笺春泪,都化春冰。\n怜春痛春春几?被一片春烟,锁住春莺。赠与春侬,递将春你,是侬是你春灵。筭春头春尾,也难筭、春梦春醒。甚春魔,做一场春梦,春误双卿!\n据说她舅舅是当地的塾师,一说她舅舅是帮塾师打柴、担水的杂役,但无论如何,这都给好学的双卿提供了一个求学的便利条件。每当塾师授课时,双卿就倚于窗下,悉心聆听,铭记在心。三年过去了,双卿学会了读书、写字、吟诗、作文,父母亲认为姑娘家大了,不能再到处乱跑了,便不再让双卿去学馆听课。此时的双卿,已经善诗能文了,可是,双卿虽有卓越的才华,却一直没有引起家人的注意。\n闺中闲暇,双卿即吟诗填词,练字作画。买不起书,她便用自做的精巧的女红,向商贩们换些诗词书籍来读。在诗书的熏陶下,双卿如一枝红杏在农家小院含苞怒放。然而令人叹惋不止的是,双卿 18 岁时,父亲去世,由叔父作主,以三石谷子的聘礼,被嫁到金坛绡山村周家,从此,双卿便踏上了一条万劫不复的血泪之路。\n“清代第一女词人”贺双卿 双卿的丈夫叫周大旺,比双卿大十几岁,是个没有一点文化的佃户樵民,粗俗不堪,生性粗暴,而且嗜赌成性;婆婆杨氏更是刁泼蛮恶,不讲情理。婚后,丈夫和婆婆把双卿当成牛马役使,家中清扫、煮饭、喂鸡、养猪、舂谷之类繁重的劳作都落到双卿的头上。婆婆还经常故意找双卿的茬,稍不顺眼非打即骂。双卿原本身体孱弱,在娘家就很少做这些重活,婚后却要样样从头学起,家里田里两头都要忙,哪里吃得消呢?但慑于婆婆和丈夫的淫威,她只有忍气吞声,独自把苦涩的泪水咽进肚里。在身体与精神的双重折磨下,双卿嫁到周家后不久便患上了严重的疟疾。劳动的艰苦,疾病的煎熬,婚姻的不幸,精神的折磨,心灵的凄楚,种种愁情苦况,一齐折磨着双卿,在这个冷似冰窖令人窒息的家庭中,双卿又无处倾诉,唯凭诗词倾诉衷肠。双卿的诗作,抒发的基本上是对个人生活不幸的感叹,浸透着浓郁的压抑情绪和伤感的情调,同时,她个人的悲剧,也折射出当时社会的阴影,使人们看到了封建时代下层社会妇女的苦难,听见了她们痛苦的悲吟,深为她们的才华被埋没而悲哀和不平。\n丈夫和婆婆的欺凌,日日消损着清代第一女词人的花颜玉容,却磨不尽她的锦思花情。从娘家带来的纸用尽了,双卿便在芦叶、竹叶、桂叶和破布残片上写;笔磨秃了,她就用炭棒和白粉代替。婆婆多次淫威大发,将双卿的笔折断,诗稿烧毁,可无论如何也阻挡不了双卿写诗的激情。双卿不在乎留下什么传世之作,甚至有意不想让诗作留存于世,她写诗、作词的唯一目的,只是想用它来宣泄悲郁、点染生活,为自己枯萎的生命添一抹亮丽的色彩。不幸的遭际,使双卿常常想起婚前的美好时光,虽然清苦,可拥有人世间最宝贵的亲情,内心是温暖而平静的,美好的时光如流水般逝去,再也无法倒流,双卿唯有靠这点点回忆来慰藉着饱经创伤的心灵。\n既然无法反抗,也就只有加倍地恭顺了,或许这样可以一点减少痛苦。据清代史震林(1692~1778 年)《西青散记》记载,双卿到婆家后不长时间,便久病不愈,在临终前的日子里,“事舅姑愈谨,邻里称其孝。夫性益暴,善承其喜怒,弗敢稍忤。”(卷四第 46 页)就这样,大约于雍正末年或乾隆初年,一代才貌双全的农家女词人,最终在肉体与精神的双重折磨下,花颜凋落,含恨离开人世,留下一段千古遗憾,让后人叹惋不已!\n易之言 易之见此人此事,为《行香子·寄贺双卿》。其词曰:\n丝丝柳絮,点点残雨。正春初,愁云乱舞。水田夕下,秋碧楚楚。纵月儿长,日儿短,双卿苦。\n豆蔻妙龄,玉带华年,偏风刀霜剑无诉。幽如白兰,缘命早枯。只怜卿长,叹卿短,代卿哭。\n","permalink":"https://guyueshui.github.io/post/%E9%9B%AA%E5%8E%8B%E8%BD%A9%E9%9B%86/","tags":null,"title":"雪压轩集"},{"categories":["随笔"],"contents":"风逐残红海逐潮,\n北斗呈钩贪狼皎。\n百年明月一时盈,\n自此清辉夜夜销。\n","permalink":"https://guyueshui.github.io/post/%E6%9C%9B%E6%97%A5%E8%A7%82%E6%9C%88/","tags":["诗词曲赋"],"title":"望日观月"},{"categories":["随笔"],"contents":" 当年上学,父亲骑着电瓶车送我到车站,我站在车上往后看……\n车开了\n你看我\n我回头\n看见你没能看见我\n的双眼\n说来旅客\n本身就是一种伤害\n大概\n","permalink":"https://guyueshui.github.io/post/%E6%97%85%E5%AE%A2/","tags":["诗词曲赋","现代诗"],"title":"旅客"},{"categories":["随笔"],"contents":"在这半亩方塘\n横七竖八的站立\n倾颓,蹒跚,踟蹰\n而你是作家,是诗人\n是勤于耕作的农夫\n夕阳映照的汗水\n生命绘成的霓虹\n曾路过繁华之境\n也见过荒凉雪景\n看过滚烫生命\n叹过涂炭生灵\n你的才思迸发\n而我只是干涩的文本\n你的感情不绝\n而我也吝啬一个句号\n","permalink":"https://guyueshui.github.io/post/%E6%96%87%E6%9C%AC/","tags":["诗词曲赋","现代诗"],"title":"文本"},{"categories":["随笔"],"contents":"俯视着关于你的一切\n像死水一样寂静\n而你是云,是风\n你是向晚高飞的蝙蝠\n未曾与他们苟同\n你没有追寻光明\n在寂静的向晚\n在清新的早晨\n在蝉鸣的午后\n毫不动摇的揣想,不得其果\n你是一部戏剧\n而我也会偶尔迷惘\n","permalink":"https://guyueshui.github.io/post/%E5%A4%A9%E7%A9%BA/","tags":["诗词曲赋","现代诗"],"title":"天空"},{"categories":["随笔"],"contents":"静静躺在城市中央\n待云朵与岁月同流\n而你是花,是草\n是交错的公路\n期待你的驻足\n如逆流之虹\n但旅人未驻足\n列车未曾停止\n少年未曾回头\n所以你未曾到过波心,不谙水性\n你有你的文艺\n而我只是静静的一片湖\n","permalink":"https://guyueshui.github.io/post/%E6%B9%96/","tags":["诗词曲赋","现代诗"],"title":"湖"},{"categories":["随笔"],"contents":"唐进士赵颜,于画工处得一软障,图一妇人甚丽。颜谓画工曰:“世无其人也,如可令生,余愿纳为妻。”画工曰:“余神画也,此亦有名,曰真真。呼其名百日,昼夜不歇,即必应之。应则以百家彩灰酒灌之,必活。”颜如其言,遂呼之百日,昼夜不止。乃应曰:“诺”。急以百家彩灰酒灌之,遂呼之活。下步言笑,饮食如常。曰:“谢君召妾,妾愿事箕帚。”终岁,生一儿,年二岁,友人曰:“此妖也,必与君为患!余有神剑,可斩之。”其夕,乃遗颜剑。剑才及颜室。真真乃泣曰:“妾南岳地仙也,无何为人画妾之形,君又呼妾之名,既不夺君愿。君今疑妾,妾不可住。”言讫,携其子,却上软障,呕出先所饮百家彩灰酒。睹其障,唯添一孩子,皆是画焉。\n叹曰:三年为妇,异心何出?世人如赵颜者,必福祸相消。运可持者,三年而已矣!然后祸报将至,其可奈何?\n","permalink":"https://guyueshui.github.io/post/%E7%94%BB%E5%B7%A5/","tags":["短评"],"title":"闻奇录·画工"},{"categories":["随笔"],"contents":"苹芜青青,波光栩栩,东君重临鸳鸯浦。葬花院落弄新晴,断魂台上兼风雨。\n蟾宫戚戚,江畔独步,摊破月华平分取。何如前路楚山孤,海角天涯同逆旅。\n","permalink":"https://guyueshui.github.io/post/%E8%B8%8F%E8%8E%8E%E8%A1%8C/","tags":["诗词曲赋"],"title":"踏莎行·元日"},{"categories":["随笔"],"contents":"繁华事散月空明,\n春风无意草自青。\n天道有常人难测,\n朗朗乾坤一语平。\n","permalink":"https://guyueshui.github.io/post/%E9%81%93%E7%A0%B4%E5%85%83%E6%97%A5/","tags":["诗词曲赋"],"title":"道破元日"},{"categories":["随笔"],"contents":"莫以危楼栏杆凭,天南地北相思寻。\n涂山一望成千古,石尤翻阻远客行。\n朽木堪比彭祖老,情丝可争日月新。\n悲肠寥寥独不见,空将纷繁枕边吟。\n","permalink":"https://guyueshui.github.io/post/%E7%8B%AC%E4%B8%8D%E8%A7%81/","tags":["诗词曲赋"],"title":"独不见"},{"categories":["随笔"],"contents":"秋容频惊鬓凋残,\n秋风误把相思传。\n人未眠,夜阑珊,\n天涯何处是团栾。\n","permalink":"https://guyueshui.github.io/post/%E9%9C%B2%E9%99%8D/","tags":["诗词曲赋"],"title":"露降"},{"categories":["随笔"],"contents":" 结萝(后名毒影)是《仙剑奇侠传五前传》游戏中的人物,苗疆女子,邂逅厉岩(后名血手),一直苦苦追随,研究情蛊,希望以此得到厉岩的关心,其实厉岩原本就很喜欢她,只是羞于表露。\n结萝在绮侧,\n圆叶绵延发。\n莲心倾不倾,\n骤雨停不停。\n","permalink":"https://guyueshui.github.io/post/%E7%BB%93%E8%90%9D/","tags":["诗词曲赋"],"title":"结萝"},{"categories":["随笔"],"contents":"绿窗新见暮色沉,围炉夜话俏平生。\n又拈旧醅对故人,入更深,月华淌过小柴门。\n","permalink":"https://guyueshui.github.io/post/%E5%BF%86%E7%8E%8B%E5%AD%99/","tags":["诗词曲赋"],"title":"忆王孙·重阳"},{"categories":["随笔"],"contents":"念亟蟾宫殁,思尽天一涯。\n长躯何了了,戚戚何惨怛。\n泠泠秋风中,霭霭月华下。\n","permalink":"https://guyueshui.github.io/post/%E6%9C%88%E5%A4%95%E8%B0%83/","tags":["诗词曲赋"],"title":"月夕调"},{"categories":["随笔"],"contents":"一连几天阴雨天气让身体倍感不适。这两天终于出了太阳。日中则偏。有角度的阳光是温柔的。当烈日变成斜阳,人也就多了些欣赏的眼光。斜阳成绮大约就是这样了。需要我描述吗?它已经被眼睛写入大脑了。这日落,不得不让人想起要开学离家了。本来也是无可厚非,刚回家的时候,想起回校也是大大方方就能接受的,可迫近了这个关头,心眼倒小些了。回头望望这个暑假,也没做什么,没有打工,没有学车,没有学菜。说是没做什么,可时间总是有去向的。又是一个无可厚非。时间花在哪里,哪里就会有收获。从未有浪费这一说。所有的时间没有什么值得不值得花费,只有自己愿意不愿意罢了。你在 A 事件上花费了时间,取得了收获,收获了你所追求的,自己又是情愿的,那么,无论 A 事件是什么,这个时间不算浪费。那么我的时间呢?我只告诉你我在一个循环中,螺旋上升。人有时会迷茫,但日子还是正常过。咦?脑子一下空了。怎么感觉语无伦次。必须承认我不是写作文的料啊。脑子空了就写不出来了,写不出来了就不写了,最后让我冠冕堂皇地称之为随心而止。拜拜\n","permalink":"https://guyueshui.github.io/post/%E7%A2%8E%E7%A2%8E%E5%BF%B5/","tags":["碎碎念"],"title":"真·碎碎念"},{"categories":["随笔"],"contents":" 放假在家过了一遍《仙剑奇侠传五前传》,不得不说仙剑的结局还是一贯的神,一贯的悲。有时候会将看故事的人拉入其中无法自拔,真个黯然神伤,佩服佩服!\n连棹司云,还饮风雪。分明流年暗自越。犹记瑶池那夜星,细照花娇一颊怯。\n从未忍别,最堪伤别。香消也将玉魂偕。此去风敛云归后,唯将画屏时时揭。\n","permalink":"https://guyueshui.github.io/post/%E8%B8%8F%E8%8E%8E%E8%A1%8C%E4%B8%BA%E4%BA%94%E5%89%8D%E4%BD%9C/","tags":["诗词曲赋"],"title":"踏莎行·为五前作"},{"categories":["随笔"],"contents":" 放假在家,夜深不能眠,因闭目养神,神游屋外。\n夏清碎雨三更天,倦卧竹席待月眠。墨云戏水声婉转,流萤映波步蹁跹。\n山连绵,水绵延,细筛夜色几分恬?皎光一道凌窗入,浮生半梦碧游仙。\n","permalink":"https://guyueshui.github.io/post/%E9%B9%A7%E9%B8%AA%E5%A4%A9%E5%A4%9C%E6%B8%B8/","tags":["诗词曲赋"],"title":"鹧鸪天·夜游"},{"categories":["随笔"],"contents":" 硬生生的矫揉造作,满脑子乱想出来的境界。\n寂寂远洋村落,皑皑天水漫行。岸下游鱼翻藻荇。波心二两月,湖面十分明。\n矮草促织乱入,寒鸦林外时鸣。无食何不餐落英。心罗千万物,眼浸一天星。\n","permalink":"https://guyueshui.github.io/post/%E4%B8%B4%E6%B1%9F%E4%BB%99%E9%80%A0%E5%A2%83/","tags":["诗词曲赋"],"title":"临江仙·造境"},{"categories":["随笔"],"contents":" 看到班级聚会上有人被灌吐了,实在看不下去!\n其一,为我好我很感激,说以后在社会上总有应酬不得不喝酒,所以即使不能喝还是得喝。我始终信奉我爸的一句话:人家能喝你管他呢,你不能喝就别喝!\n其二,己所不欲,勿施于人。何为不欲?我不想喝,你喝,你劝我喝。你不想让我不喝,是为不欲,所以即便为我好,也要看我接不接受吧。你们说的我都知道,也已经做好了觉悟。我的棱角也已经被现实磨得差不多了,如果在连这点坚持也要像现实屈服的话,还不如死了算了!人生在世都不能按照自己的意志行事,还活着干嘛!\n其三,我看到了,我都看到了!聚会的地方每桌都有人吐得不行,走路飘摇,“任人宰割”。年轻人卖青春?!不懂自持,肆意无度,为一己之快灌人酒,然后看别人呕吐很高兴吗,快意吗?不错,喝酒本是乐事,但你们想过呕吐的人的感受没有,好,即便他心理快乐,生理上总不能说很愉悦吧?!然后出门以后,城里灯红酒绿,醉酒的人不危险?!\n其四,现代人把酒喝成什么样了?!就知道灌灌灌,喝到不行还要喝!喝到要死还要喝,明明自己不能喝,别人一灌他就喝,明明自己再也不能喝了,别人再道出几句利害,说出两句风马牛不相及的话,他还喝?!请允许我说他作死!古人武将喝酒是为挥洒肝胆,上阵杀敌;文人喝酒,是为投怀雅兴,寄寓风骚。“绿蚁新醅酒,红泥小火炉。晚来天欲雪,能饮一杯无?”“肯与邻翁相对饮,隔篱呼取尽馀杯。”“独持一杯酒,南亭送残春。”“彼此相看头雪白,一杯可合重推辞?”酒文化本是如此美好,却被今人异化成这样。真是好的不继承,坏的更加剧!\n最后,再郑重声明:烟酒不沾!\n好吧就这样,不服别说话。因为争辩没意义。\n","permalink":"https://guyueshui.github.io/post/%E5%85%B3%E4%BA%8E%E9%A5%AD%E5%B1%80%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/","tags":["碎碎念"],"title":"关于饭局那些事儿"},{"categories":["随笔"],"contents":"禁闱清寂,独卧无好意。拟把乡关枕边诉,花妙榆浓荫细。\n昨夜又见月明,教人何不伤情。他日湖畔信步,徒增云淡风清。\n","permalink":"https://guyueshui.github.io/post/%E5%BF%86%E8%90%9D%E6%9C%88%E7%AB%AF%E9%98%B3/","tags":["诗词曲赋"],"title":"忆萝月·端阳"},{"categories":["随笔"],"contents":"寂寂幽林密,凄凄夜色清。\n流深散月影,玉泽沁我心。\n长梦忽到此,愿遣青牛住。\n","permalink":"https://guyueshui.github.io/post/%E5%B9%BD%E6%B5%A6%E5%90%9F/","tags":["诗词曲赋"],"title":"幽浦吟"},{"categories":["随笔"],"contents":" 上大学的第一个五一,室友们纷纷出去玩乐,独留我在宿舍,身在异乡,聊感寂寞。\n婉转星辰昨夜媚,参商不见动相离。\n飞云不顾弦声切,野桥静待落花枝。\n偏偏风雨袭人处,寂寂瑶花苦到思。\n总向伊人难对月,只盼莲心散成绮。\n","permalink":"https://guyueshui.github.io/post/%E9%A2%98%E4%BA%94%E4%B8%80%E5%BF%83%E4%BA%8B/","tags":["诗词曲赋"],"title":"题五一心事"},{"categories":["随笔"],"contents":" 前言:现在的我不认同当时的我,当时的我似乎以为世界以我为中心,真的是 too young too simple, sometimes naive.\n好,这次我就说的明明白白:对于你们五一各种回家各种玩各种嗨各种秀,我是很羡慕。甚至嫉妒。我也想出去玩,可是我只是讨厌人多的地方,我只是想和两三个好朋友一起,可是,好朋友都在哪呢?我没有好朋友了。没有了吗?在武汉的好朋友有他们自己更要好的朋友,以前的好朋友都一直失联。我努力维系的关系,努力经营的感情,努力关注的人,都没有给我回应。给我的竟是一种热脸贴冷屁股的感觉。或许他们有新的知己,把我给淡忘了,可我却拿着记忆反复温习,生怕会忘!\n我就不想出去玩一玩吗?没到过的地方,我也想亲身一下;没看过的风景,我也想亲自去看一看;没体验过的事情,我也想亲自去体验的。奈何总是没人陪,所以我宅在宿舍作息失调。我只想要几个好朋友而已,或许我再也得不到了。这个大学给我的感觉,虽然人脉圈子大,不过真正要好的没有几个,与别人维系的仅仅是一种互相利用的关系,有事可以帮个忙什么的再好不过了。所以对于我不想结识,不想纠缠的人,我不会抱着功利心态去与之交往。 说到这里我又想到,有些个左右逢源,能说会道的人,遇到谁都希望做个好朋友。我也曾问过他们,他们确实有时候说的话是违心的,可是为了收获人脉,他们照说不误!有些人志向远大,积极参与各种活动锻炼自己,提高自己,抓住去演讲的任何机会,锻炼自己的口才。他们认为好的口才可以为他们带来很多好处,可以使他们更轻松地在社会立足。确实他们是很有能力,是很能干,这也没什么不好。我想说的只是各人所追求的不一样罢了。你追求人脉,追求社会集体性,追求利用人际关系帮助自己。而我不追求这个,我追求内心的宁静,我追求世界的平淡,自然的朴素。有人说我的这种想法未免太天真,迟早要被社会同化。好吧,其实我也不能确定。毕竟生活在社会中,不按社会的规则办事是很麻烦的。\n最后我想用大石鼓的话作结:悠悠天地,奈何独立苍茫!好朋友,你留给我的只是无限的寂寞!!!\n后记:当时的好友评论说的都挺在理的,仿佛那时候就我最幼稚。\n摘录几个好友评论:\n@PYB: 社会的压力迫使人们带着面具生活,可是,都情非得已。\n@GZH: 人应该学会与寂寞相处\n@TY: 两年后你在回头来看这番话,就会觉得自己当年太那啥~~~\n@MX: 每个人都有自己独特的生活方式,有些时候不妨换个角度,换种心态看待问题……或许,那些你羡慕着的人也正羡慕着你……社会如此,很多事没表面这么简单……再转送一句我姐送我的话:学会谦卑,因为社会上没人欠你什么(PS:老纸回来了……)\n@YL: 玩之前先和朋友说好去哪玩好不好,如果没人答应,就回老家找亲朋好友玩玩,至少他们不会拒绝的,想开一点,这个社会也许就是这样,没什么大不了的。\n@FJ: 达则兼济天下,穷则独善其身。君子之交淡若水。他日相聚,酒一壶,叙旧花前月下……越浮躁,越是需要宁静……中国缺少大师,不乏商人政客……\n","permalink":"https://guyueshui.github.io/post/%E5%B9%B4%E5%B0%91%E7%9A%84%E8%87%AA%E5%A4%A7/","tags":["碎碎念"],"title":"年少的自大"},{"categories":["随笔"],"contents":" 这么多年最爱的电视剧还是《仙剑奇侠传一》,当年因为一是兴趣,到仙剑贴吧发了这个帖子,被秒删了。我寻思我也没说啥呀,自此没玩过贴吧。\n姜明得道时对逍遥说:你明白吗?\n若拙得道时对青儿说:你明白吗?\n还有之后对莫一兮说:你明白吗?\n以及最后逍遥手抱婴儿对若拙说:你明白吗?\n本来一切都是注定:就像逍遥与灵儿的结局。若拙早算出事态的发展,若不加以阻止,三人之中必定有人牺牲。故将灵儿关入锁妖塔。无奈天意不可违,即便是近道之人也不能干预别人的修道之路。灵儿的痴情,逍遥的执着,让一切都按照注定发生了。即便后来逍遥意识到错,回到十年前欲改变一切,也只是弄巧成拙,故意避开仙灵岛的方向,却不知竟绕了地球一圈。结果一切都没有改变。后来逍遥抱着孩子,想必是五味杂陈的吧。再后来,他就得道了。你说这一切有必要改变吗?即便十年后让灵儿再现,灵儿已不是灵儿,逍遥也不是逍遥了吧。各自已经成道,也就没有必要再像以前那样了。不是不爱,而是明白了,不局限于爱了。仙一的主题就是爱与道这两个字了。鉴于时间太晚,先这么办吧\n","permalink":"https://guyueshui.github.io/post/%E8%AF%84%E4%BB%99%E5%89%91/","tags":["短评"],"title":"《仙剑奇侠传》的爱与道"},{"categories":["随笔"],"contents":" 见《雪压轩集》。\n丝丝柳絮,点点残雨。正春初,愁云乱舞。水田夕下,秋碧楚楚。纵月儿长,日儿短,双卿苦。\n豆蔻妙龄,玉带华年,偏风刀霜剑无诉。幽如白兰,缘命早枯。只怜卿长,叹卿短,代卿哭。\n","permalink":"https://guyueshui.github.io/post/%E8%A1%8C%E9%A6%99%E5%AD%90%E5%AF%84%E8%B4%BA%E5%8F%8C%E5%8D%BF/","tags":["诗词曲赋"],"title":"行香子·寄贺双卿"},{"categories":["随笔"],"contents":"红日殷勤照,绿草葳蕤发。\n轻霜打薄叶,细雨湿脸颊。\n燕衔去岁诗,牛踏来年花。\n郁结二十载,难忘旧时家。\n附:立春古词\n春日春风动,\n春江春水流。\n春人饮春酒,\n春官鞭春牛。\n","permalink":"https://guyueshui.github.io/post/%E6%89%93%E6%98%A5%E8%AF%97/","tags":["诗词曲赋"],"title":"打春诗"},{"categories":["随笔"],"contents":"回家的第一个早晨,想起去乡里走走。无奈各家都躲在深宫大院里,院门铁锁。问何以至此?曰:“昨儿个杨家电驴儿被盗啦,俺来则速的叫大儿子打了个院儿,小偷再也盗不了俺家货啦!当今的谁家没个铁栅栏水泥地儿的,能防人!”\n噫,怅然兮有所失,有所思:昔者四合之院,邻里互衬。数口之家,其员相悦。举手之间,有温良和风相送。畅晚之余,有妙语笙歌互答。谈天说地,议古论今,盘盘焉若蜂房,逡逡焉似水涡。而今各门高院,唯独防人者乎?其亦防心邪。其使路人相见,心起隔阂。久久相成,则串门之事少焉,人心之去远矣。是村之愈城,世便少村,其为外者。人之愈闭,人心渐远,其为内者。村人本不欲此,有作俑者,先修高院,极陈利弊,兼以诱引,使村人起修,互传甚者,是不修者以修者修,故曰,村人互丧,其罪莫大于始作俑者。而另考之,其受时势异化者甚众,然则罪乃戟于时势。而另考时势,亦复如是。故或曰神谕,或曰天命,而吾曹窃以为,其为自然也哉!乃口占一绝:\n质朴如玉任雕张,\n体性飘摇随风荡。\n无端各家高墙起,\n不闻稻草杂泥香。\n","permalink":"https://guyueshui.github.io/post/%E6%9D%82%E6%80%9D/","tags":["碎碎念"],"title":"杂思"},{"categories":["随笔"],"contents":" 武理时光,大多是屌丝理工男宿舍生活。校园里开遍了石楠花,你懂得。某个恰当的时节,恰当的地点,遇到理学院的桂花香,算得上是人间天堂了。\n寂寞花开寂寞枝,\n一抹幽香没晓思,\n无端牵得游人住,\n不是深闺话片时。\n","permalink":"https://guyueshui.github.io/post/%E7%90%86%E5%9B%AD%E5%B0%8F%E6%A1%82/","tags":["诗词曲赋"],"title":"理园小桂"},{"categories":["随笔"],"contents":" 系为思念友人作。\n两场相思雨,一处相思苦。\n此相系何物,所思在何处?\n依稀门前柳,朦胧沙坪路。\n曾经多少事,只被相思付。\n相思付相知,相知不与语。\n无语怎奈何,未把相思度。\n独叹岁月长,单将人心改。\n便改恁不改一双,指教人心添若堵。\n此处相思空流露,此时相知难清楚。\n又见野桥飞烟处,一轮相思垂天幕。\n归来却向人人诉:\n最恨相思无相知,一生空被相思误!\n","permalink":"https://guyueshui.github.io/post/%E7%9B%B8%E6%80%9D%E6%9B%B2/","tags":["诗词曲赋"],"title":"相思曲"},{"categories":["随笔"],"contents":"别后音书难寄,梦醒佳期曾寻?去年今日却来时,指点斜阳丽,谈笑细雨霏。\n残风枯草落桂,玉影浮萍无依。闲病一来漫如丝。秋深明月照,夜寂懒人归。\n","permalink":"https://guyueshui.github.io/post/%E4%B8%B4%E6%B1%9F%E4%BB%99%E5%8F%8B/","tags":["诗词曲赋"],"title":"临江仙·友"},{"categories":["随笔"],"contents":"疾风枯叶两相搏,一叹秋光万念空。\n瑟瑟孤霜兰亭瘦,层层幽岚花影重。\n欲投鱼雁心难递,将拟音书愿不衷。\n暂借明月作明镜,照见愁容当笑容。\n","permalink":"https://guyueshui.github.io/post/%E9%A2%98%E6%B0%B4%E9%98%99/","tags":["诗词曲赋"],"title":"题水阙"},{"categories":["随笔"],"contents":"浩浩江汉天,寂寂雁行促。信拈梧桐一叶愁,泪起相思雨。\n九九又相逢,患难身何处?却把双眉抵作山,淡看秋云暮。\n","permalink":"https://guyueshui.github.io/post/%E5%8D%9C%E7%AE%97%E5%AD%90%E9%87%8D%E9%98%B3/","tags":["诗词曲赋"],"title":"卜算子·重阳"},{"categories":["随笔"],"contents":" 看完《借东西的小人阿莉埃蒂》,对于结尾十分有感触。阿莉埃蒂一家人能否找到新的家呢?\n穿行在静谧的花丛\n蚂蚱蹦跳,蛐蛐乱叫\n活跃在空荡的房子\n翻过碗橱,踏上灯杆\n借东西的路漫长\n时而欢喜,时而忧伤\n栗发沐着暖风,好想凝望天空\n面前是另一个世界\n彩蝶翩翩起舞\n好想为你送一束鲜花\n我并非厌倦\n那个小小的世界\n只是想知道,更多\n更多关于你的事情\n欢喜和忧伤,总是交织在一起\n在太阳下,在花丛里\n想与你度过每一天\n带着这个愿望\n在我的世界里\n走我自己的路\n","permalink":"https://guyueshui.github.io/post/%E9%98%BF%E8%8E%89%E5%9F%83%E8%92%82/","tags":[],"title":"阿莉埃蒂"},{"categories":["随笔"],"contents":"我们村东头,是个好地方。那里水天相接,那里绿草如茵,那里牛羊成群,那里风和日丽。\n小时候,我和我的小伙伴们,骑着脚踏车,在稻场上奔驰,平坦而又起伏,简单而又复杂,这独特的地形是我们最爱的赛道,虽然,我们时不时趴一跤,吃满嘴的泥巴,脚踝被擦破,脚踏车翻在一边,后轮还在飞转,但,我们会立马爬起来,不屑地掸去身上的泥巴,扶起踏板,高傲地再坐上去,继续那待续的路途。\n我们会顺着纵横的田梗,骑到小桥子口,去观看潺潺的流水,流水是那样清,那是与各个大村直连的水沟的支流,听说大水沟绵延着汇入长江,我们指点着水中的小餐鱼,左栏看罢换右栏,我们在桥上左右跑动追寻自己锁定的鱼儿,有时发现桥中的小缝,我们便贴在地上睁一只眼闭一只眼地偷窥桥下流水中的各色生物。\n沟里有各家船只,当然也有我大伯的,那都是水泥和木头的小船,用来运稻子的,长长的,有的像一弯新月,有时,大伯撑船带我到边田去收稻子,他在船头撑篙子,我在船尾看着被船劈开的流水,水纹很是漂亮,怎么说呢,规则的不规则的,都是那样的自然,我于是按捺不住,脱了衣服跳下水,双手扒住船尾,脚丫子在后面啪啪地拍打水面,听从大雁的智慧,我躲在水纹分叉之后,呀,这可比游的快多了,这叫搭顺风船,我张开嘴,任流水在我嘴里打旋,那感觉是非常美妙温柔的,这里一时想不到词语来描述。\n小船穿过一片水草,再一片苇丛,便到了他家田,就不说他家稻子如何了,单看这一带,却有些类似世外桃源的样子,虽没有山作屏障,却也有芦苇作掩饰,沟到这里似乎尽了,其实过弯那边还有个小桥子口,与外相通,岸边全是田亩,黄灿灿的稻子,芦苇在风中摇摆头脑,毛毛针也低下头来抚水嬉戏,南边有间小茅屋,估计是看鱼人的临时住所。\n回来的时候,小船已载得满满的了,船檐被压的贴近水面了,不过这更有一番趣味了,小船划出的水纹越发大且复杂了,我掰着手指头都数不过来了,我静静地看着水面,目光又被水中的鱼儿抢去,它们在绛蓝的水草间乱窜呢,我伸出手插进水里,顿时水面又多出几道纹路,忽而远处又冒出两只水鸭子,我们玩起了预测它们下一个冒出点的游戏,好不有趣,说真的,从未感觉离水面如此的近,当视线与水面在同一水平线上,听说会有奇迹般的视觉效果。\n上岸后,把稻子都运到稻场晒,稻草桔梗集中烧掉,可是难免会有稻粒残存在杆上,烧的时候就会有辟辟啪啪的声音,这就意味着稻子在大火之中熟了,这就意味着有口福了,这就是传说中的炮谷子,没加盐,没加糖,没加醋,没加油,它唯一的诱惑就是天然的米香,带着丝丝的焦味,没人控制火候,没人加入调料,它堪称浑然天成!走过路过,捡一个吃吃吧!\n我们小时候!我们村东头!\n","permalink":"https://guyueshui.github.io/post/%E6%9D%91%E4%B8%9C%E5%A4%B4/","tags":[],"title":"村东头"},{"categories":["随笔"],"contents":"看官,请随我来。\n你说这大气好不奇妙。我们都知道大气对我们是有压力的,我们为什么感受不到,因为我们体内也有大气压着。哦,那么我们的肚皮不是要承受两份压力吗,那怎么受得了?哦,肚皮是有厚度的,可不能忽略,为什么压不扁呢?大概组成肚皮的层层细胞间也是有大气的,那既然细胞外有大气,那细胞里也要有的,不然不是把细胞压炸了?那细胞壁呢?它是有厚度的,可不能忽略。它是不是像肚皮一样也是由众多微不可见的东西组成的呢?我们之所以知道肚皮、细胞,那是人家告诉的。若是人家没说,我们会这样想,这是由众多的那组成的,那是由众多的这组成的,这样它们都失去了名字,就没什么区别。如果细胞壁想肚皮一样是由众多的某某组成,那这众多的某某又会诞生出下一个系统,如此生生不息,众多的系统都未命名的话,那么排列就无需先后。从中任意挑一个出来既是老祖宗,又是重孙子。就像数轴一样,任意数都可向两侧无限延伸。小而又小是什么?大而又大是什么?照刚才所说,都未命名的话,其实极小与极大是没区别的。一株小草也可以与一颗恒星相提并论。万物都是平等的系统,恒星包含了无限的微粒,小草也是。何为大,何为小,何为物,何为我,都没什么意思。无极之内,在极之外。世界本就是模糊朦胧混沌的,婴儿的大脑是最高的境界。看官可能会说,婴儿可不知道这些,更没有思考过这些。其实这就像一开始的陌路,和最终相忘江湖的两个人,表面上没有任何区别,但其中确有种种微妙。老庄说的至人无己,至人的大脑大概是和婴儿一样的,至人确有了质的飞越。从无中来,再到无中去,这便是一生的修行!老人和婴儿便是一个系统的出入口,其始亦是终。\n","permalink":"https://guyueshui.github.io/post/%E9%AC%BC%E8%AF%9D/","tags":["碎碎念"],"title":"鬼话"},{"categories":["随笔"],"contents":"每当我提笔想写些什么,心里便虚荣起来。总想写出深奥,晦涩,精炼,优美的话语,让人们好加点评,其实都是故弄玄虚而已,真个应了“为赋新词强说愁”。故而入眼尽是哀景,入耳尽是悲歌,所写尽是愁滋味。而空造之言又能打动几人?最多打发看官无聊的时间而已,这是没有意义的!\n我曾经想过一个问题,悲和喜到底谁更打动人呢?譬如老师在批改作文,某作以悲动人,每个人都经历过悲痛,也都尝过悲痛的滋味,那真是不想再尝的了,故而所见此作足以涕泪交集,动了悲心,给 55 分吧;某作以乐动人,其所言尽中人心怀,勾起人快乐的回忆,那滋味真是非常美妙的,老师乐了,他回想起以往的乐事,动了乐心,给 55 分吧。看官,你道是悲乐孰更动人??\n我好像有两面,在广众下的忧郁,在好友旁的快朗。而我如今在写这些杂东西,也是装忧郁罢了。可我为什么不装快乐呢?是我在生活中快乐够了,所以在网上得忧郁一下?而快乐,谁都不会嫌多的。那既然生活中并不是快乐腻了,为何还在人前装忧郁?是虚荣罢了!因为有人觉得忧郁是种气质,能够吸引人,所以得装;因为有认说愁更能动人,所以得强说愁;因为有人说看诗词可以提高修养,所以读诗览词,却未曾入木三分……我呵――就生活再别人的世界里罢了!所幸日久可见人心,不管怎么伪装,别人总会看见真正的我(不论好坏,至少真切)。\n看官若见我以后发什么愁话,大可以不必机会,想是又在虚荣罢了。人生难得是清醒。今日清醒一回,不知又要糊涂到几时?\n","permalink":"https://guyueshui.github.io/post/%E6%9D%82%E8%AE%B0/","tags":["碎碎念"],"title":"杂记"},{"categories":null,"contents":"","permalink":"https://guyueshui.github.io/search/","tags":null,"title":"BSearch"},{"categories":null,"contents":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.\nQuick Start Create a new post 1 $ hexo new \u0026#34;My New Post\u0026#34; More info: Writing\nRun server 1 $ hexo server More info: Server\nGenerate static files 1 $ hexo generate More info: Generating\nDeploy to remote sites 1 $ hexo deploy More info: Deployment\n","permalink":"https://guyueshui.github.io/post/hello-world/","tags":null,"title":"Hello World"}]