码海拾遗
码海拾遗
前言:完整的总结总是劳神耗时的,已经熟稔的或者易于得到的部分也没有总结下来的必要。然遨游码海,总归有一些需要记忆的小点,好记性不如烂笔头,单开一篇又没必要,索性全归为一篇,作为手册。
Linux
常用命令
软件安装
直接:sudo apt install
tldr
:too long dont read,一个简单版本的man手册,记得先-u更新一下fzf
:对输出列表进行模糊查询colordiff
:彩色版diff,可以alias为difffd-find
:更快的find命令,命令为fdfind
,alias为fd,功能非常多,-h
查看htop
:更易于阅读的top命令ncdu
:更易于阅读的du命令,可以直接排序,查看文件夹大小ripgrep
:更强大的grep命令,命令为rg
bat
:能高亮的cat命令,命令为batcat
,alias为batdiff-so-fancy
:下载后添加到PATH,chown和chgrp一下就好了,用法是git diff --color | diff-so-fancy
,可以alias为gitd。能够让git diff显示的清晰一些,看情况用manpages-zh
:中文man手册,用法:man -M /usr/share/man/zh_CN
,alias为cman
manpages-posix-dev
:POSIX的man手册- 注意,vscode中有时不能识别一些函数如
sigaction
、pthread_xxx
等,是因为没有"定义"__USE_POSIX
这个宏(但实际上运行的时候是定义的),想要看补全的话,可以手动在,比如signal.h头文件前面define上 - 听说
c_cpp_properties.json
中定义"__USE_POSIX=1",
也行,但我的不行。但这不失为定义宏的好地方
- 注意,vscode中有时不能识别一些函数如
cgdb
:可以随时预览代码的调试。⛓命令参考 / Esc(vim) i(cgdb) s(cgdb 翻页)#可能会缺少 curses.h 文件,可以先 sudo apt install libncurses5-dev libncursesw5-dev #安装脚本: wget https://shuaikai-bucket0001.oss-cn-shanghai.aliyuncs.com/config/gdb/installCgdb.sh # mkdir ~/.cgdb wget -O ~/.cgdb/cgdbrc https://shuaikai-bucket0001.oss-cn-shanghai.aliyuncs.com/config/gdb/cgdbrc wget -O ~/.gdbinit https://shuaikai-bucket0001.oss-cn-shanghai.aliyuncs.com/config/gdb/.gdbinit
expect
:交互的终端脚本工具,可以编写一些期待输入的脚本,expect xxx send xxx
其他非必要:
ossutil
:阿里云对象存储的命令行工具#(1) 下载安装 wget https://gosspublic.alicdn.com/ossutil/install.sh && sudo bash install.sh && rm install.sh #(2) 生成配置文件 连按几下回车即可 ossutil config #(3) 下载自定义配置文件 wget -O .ossutilconfig https://shuaikai-bucket0001.oss-cn-shanghai.aliyuncs.com/config/others/.ossutilconfig wget -O .bash_aliases https://shuaikai-bucket0001.oss-cn-shanghai.aliyuncs.com/config/bash/.bash_aliases
man cpp
:cpp的man手册,包括一些浏览器检索的办法 🔗 Linux自主安装C++man手册
shell编程
- 变量都是字符串,即
"%s"
输出 ((var=var + 1)), var=$(expr "var" + 1)
- $RANDOM 生成随机数
ctrl-a/e
:行首/行尾;Alt-b/f
:左/右一个单词crtl-w
:往左删一个词;Alt-d
:往右删一个词ctrl-u/k
:删除光标左/右所有字符ctrl-y
:粘贴刚才删除的内容ctrl shift '-'
:撤销操作^xxx
:删除上一条命令中的 xxx^foo^bar
:把上一条命令中的 foo 替换为 barfind . -perm /111 -type f -delete
:linux下删除可执行文件(加个type可以略掉文件夹)(find命令参考)-perm mode
:必须与mode指示的一样-perm /mode
:mode中表示的,满足一个就好了,比如111
,则只要有一个1
满足就好了-perm -mode
:mode中表示的,必须全部具有,111
就是ugo都得可执行,缺一不可。别的属性不管find -iname readme -o -name *.c -not -size +100k -exec ls -i {} + -fprintf0 file.txt
: find查找是完全匹配;逻辑与或非;size的单位注意默认不是byte;exec结尾是{} +
大括号表示结果
三剑客 grep awk sed
例子(1)
==用awk和sed迁移阿里云上的图片:==
找到阿里云oss仓库,选择批量导出url,导出后是一个.csv文件,格式为obj,url
找一个文件夹下载所有url
cat export_urls.csv | awk -F, '{print $2}' | xargs wget
把下载的这些文件上传到新的oss上,得到新的链接。这会导致原来的笔记里面的图片链接全部失效,因此要更改链接。链接的区别只在前面的bucket地址,后面的图片名是完全一样的。因此只需要找到所有的笔记文件,把其中图片url中前面的bucket链接改成新的连接前缀即可
此处用到的fd命令要先下载,即fd-find。可以对文件夹进行递归正则查询。至此,就完成了对所有图片的迁移、更改链接操作。
fd ".*\.md$" | xargs sed -i 's#https:\/\/xxx.xxx#https:\/\/yyy.yyy#g'
gdb
命令
通用命令
x
/ [Length][Format: t o d x f a c s] [Address expression b-8 h-16 w-32 g-64 ] 📎GDB Command Reference : xp
/t-2 o-8 d-10 x-16 f-loat a-ddress c-har s-tring [var]- ⚠ 要注意区分,x 是扫描内存,因此后面跟的直接是内存地址, 而 p 是输出变量的值,因此后面想跟地址的话,需要取其内容,即加上
*
- 关于
*
,很多命令地址前面都要加星号,因为不加星号的一个地址,是被当成函数来看待的
- ⚠ 要注意区分,x 是扫描内存,因此后面跟的直接是内存地址, 而 p 是输出变量的值,因此后面想跟地址的话,需要取其内容,即加上
s n
:C语言级别的单步执行;si ni
汇编级别的单步执行i [line] / [locals] / [vtbl] / [args] / [checkpoints] / [breakpoints]
info line [line] / [function] / [*addr]
:显示源码对应的内存中的起始结束地址info vtbl [objs]
:直接查看对象的虚函数表(也可以类型转换的形式打印出来,参考C++类的内存分配,基本思想就是:对象的第一块内存放的就是虚表,虚表是一个函数指针数组,因此可以先将对象地址转换为指向指针的指针,然后对其取内容,就得到了虚表指针。把这个值转换为指向指针的指针,即得虚表)
set print array-indexes on
:打印数组的时候显示 indexset disassemble next-line on
,disassemble
set [args] / [register] / [*address] = [val]
:设置参数、寄存器值、内存处值jump [*address]
:跳转到指定地址处执行,return [val]
:直接返回。都可以改变程序走向。shell [cmd]
:运行 shell 命令rbreak [regex]
:在所有满足正则表达式的函数处打断点
多进程/线程调试
gdb [pid]
:调试正在运行的进程,或者进去后attch [pip]
例子
- 打印栈内容(你打数组肯定也行):不妨设 $rsp 中存放的是 0x1234
p $rsp => (void*)0x1234
x/10dw $rsp
或x/10dw 0x1234
,即用x
命令直接把栈那一块内存扫描出来p *(int (*)[10])0x1234
把栈顶地址类型转换为一个指向数组的指针,然后 print 这个数组 val 的值(所以要加*
),即栈的内容p (int [10])*0x1234
把栈顶的内容直接变成一个数组,然后输出p *0x1234@6
注意@
的用法,其左边必须是你想查看内存处的值,可以直接输出后面的六个变量 类似的p *argv@argc
,也是先取内容转换为变量,再输出- 总结一下,用 x 就是直接扫描内存了,简单粗暴;用 p 则是要把你想看的内存区域转化成一个变量,一个 val,不管是int还是int数组,不管是怎么类型转换,反正基本思想是把内存当成一个变量进行输出。最好还是用 x ,因为效果一样,用p你还需要先得到rsp里面的值
- 查看虚表 略
- 运用core文件:
gdb debug core
进入之后,disassemble
,可以看到=>
指向的就是运行出错的地方
性能分析工具
Valgrind
:查看内存泄漏
grof
:
GPROF是一种性能分析工具,用于测量程序的运行时间和函数调用频率,以帮助开发人员找到程序中的性能瓶颈。它是GNU项目的一部分,通常与GCC(GNU编译器集合)一起使用。
GPROF通过插入计时器代码和函数调用计数器代码来收集程序的运行时间和函数调用信息。它使用两个主要组件来生成性能分析报告:
- gprof 编译器:这是一个特殊版本的GCC,用于在编译时在程序中插入性能分析代码。使用
-pg
选项编译程序时,会将性能分析代码插入到生成的可执行文件中。 - gprof 分析工具:这是一个独立的命令行工具,用于解析可执行文件中的性能分析代码,并生成详细的性能分析报告。它会记录程序的函数调用关系、每个函数的运行时间以及函数调用次数等信息,并以可读的格式输出。
使用GPROF进行性能分析的一般流程如下:
- 在编译程序时,使用
-pg
选项告诉编译器插入性能分析代码。例如:gcc -pg -o my_program my_source.c
运行程序
,生成gmon.out
文件。执行一系列典型的操作,以便GPROF能够收集足够的性能数据。- 程序运行结束后,在终端中运行
gprof
命令来生成性能分析报告。例如:gprof my_program
- GPROF会分析程序执行期间收集到的数据,并生成一个报告,其中包含函数调用关系图、每个函数的运行时间百分比、函数调用次数等信息。
通过分析GPROF生成的报告,开发人员可以确定程序中的热点函数(运行时间最长的函数)和瓶颈函数(被频繁调用的函数),以便进行性能优化。
vim
零碎
- 定义宏:
q + [a-zA-Z] + your_operation + q
,使用时@ + [a-zA-Z]
Ctrl + a
光标第一个数字加一,Ctrl + x
光标第一个数字减一- 行内移动:
f w b e
- 自动补全:
Ctrl + p
- 查询man手册:
K
- :digraphs 列出所有特殊字符
- #:自动查找井号后面的单词
ctrl w ←↑↓→
切换窗口,ctrl w HJKL
移动窗口:vertical resize +-n
:调整当前窗口宽度,去掉vertical
调整高度- 默认的
map
是递归的,比如a映射b,b映射c,那么就等于是a映射了c。可以根据使用模式,做非递归的映射,即nnoremap/inoremap/vnoremap/cnoremap
(normal/insert/visual/comandline) - doc/help.md · chenxuan/vim-fast - 码云 - 开源中国 (gitee.com)
myvimrc
快速配置:vimfast || 基于vimplus|| vim快捷键help.md || 内置快捷键key.md
# 无插件快速配置 √
wget https://gitee.com/mirrorvim/vim-fast/raw/master/vimrc-no-plug -O ~/.vimrc
"//"开头的不用添加到vimrc中,只是表说明作用
通用设置
let mapleader = "," " 定义<leader>键为逗号[,]
" 不必苦恼移动不到最后一个字符右边:A在行尾插入,I在行首插入;a在后一个
nnoremap 0 $ " 9移动到行首,0移动到行尾,从位置上更符合感受
nnoremap 9 0 " 下面这些在ming'li
nnoremap = w
nnoremap - b
打开窗口
"分屏与竖直分屏
// :split [file]
// :vsplit [file]
"vim中调试
// Termdebug [bug_file] " 打开源代码、gdb、IO三个窗口
nnoremap <leader><leader>d Termdebug<space> " 用[,,d]直接打开此模式
"vim中打开新的终端
// :vert term " vert,即打开竖直方向终端,去掉即水平方向分窗口
nnoremap <leader><leader>t :vert term " 打开竖直终端
nnoremap <leader><leader>t :bo term ++rows=6 "六行水平终端
窗口调整
"改变窗口大小
nnoremap <c-up> :resize+1<cr>
nnoremap <c-down> :resize-1<cr>
nnoremap <c-left> :vertical resize+1<cr>
nnoremap <c-right> :vertical resize-1<cr>
"改变当前窗口
nnoremap <s-up> <c-w>k
nnoremap <s-down> <c-w>j
nnoremap <s-left> <c-w>h
nnoremap <s-right> <c-w>l
"改变窗口位置
nnoremap <c-s-up> <c-w>K
nnoremap <c-s-down> <c-w>J
nnoremap <c-s-left> <c-w>H
nnoremap <c-s-right> <c-w>L
其他便捷操作
" 自动括号
inoremap ( ()<left> "插入模式
inoremap [ []<left>
inoremap { {}<left>
cnoremap ( ()<left> "命令行模式
cnoremap [ []<left>
cnoremap { {}<left>
" 快捷编辑文件
nnoremap e :edit<space><c-r>=getcwd()<cr>/
mybash_aliases
# default
alias gdb='gdb -q'
alias ll='ls -alhF --color=auto'
alias python='python3'
# git
alias gita='git add'
alias gitb='git branch'
alias gitc='git checkout'
alias gitl='git log'
alias gitlg='git log --graph'
alias gitlo='git log --oneline'
alias gitlog='git log --oneline --graph'
alias gitr='git reset'
alias gitrh='git reset --hard'
alias gitrs='git reset --soft'
alias gits='git status'
alias gitd='git diff --color | diff-so-fancy'
# some tools
alias bat='batcat'
alias diff='colordiff'
alias fd='fdfind'
alias grep='rg'
alias top='htop'
# for convinence
alias PATH='echo $PATH | xargs -d: -n1'
alias sb='source ~/.bashrc'
alias sv='source ~/.vimrc'
alias vimb='vim ~/.bashrc'
alias vimv='vim ~/.vimrc'
# windows
# alias jupyter='jupyter notebook'
# alias make='mingw32-make.exe'
# wsl
# alias wincodes='cd /mnt/d/codes'
# alias windesk='cd /mnt/c/Users/wddjwk/Desktop'
# alias windownload='cd /mnt/c/Users/wddjwk/Downloads'
# alias wintest='cd /mnt/d/test'
# alias win~='cd /mnt/c/Users/wddjwk'
vscode
配置
C/C++ 环境
各种 json 文件:(见备份)
插件
inline bookmark
可以对行进行标记,并在左侧列出
"inline-bookmarks.expert.custom.words.mapping": {"blue": [">mark[\\s]","@mark[\\s]"],"purple": [">panic[\\s]","@panic[\\s]"],"green":[">note[\\s]","@note[\\s]"],"red":[">warn[\\s]",">why[\\s]","@warn[\\s]","@why[\\s]"],"warn": [">todo[\\s]",">TODO[\\s]","@todo[\\s]","@TODO[\\s]"]},
git
零碎
git config --global init.defaultBranch main
- 找机会修改一下github用户名
--global
:~/.gitconfig
--system
:/etc/gitconfig
快捷git
可以直接创建一个
.bash_aliases
文件也可以
git global comfig alias.st status
,配置别名
makefile
模板1
# 一个适合中小规模的makefile模版,基本上自己按照实际情况指定一下 源文件,目标文件,头文件目录,以及源文件后缀就行了。
# ---------------------------------------------------------------------------
# commands
# ---------------------------------------------------------------------------
CC := gcc
LINK := gcc
RM := rm -rf
MV := mv
TAR := tar
MKDIR := mkdir
# ---------------------------------------------------------------------------
# settings
# ---------------------------------------------------------------------------
SRC_SUFFIX := .c
OBJ_SUFFIX := .o
LIB_SUFFIX := .a
BIN_SUFFIX := .exe
DLL_SUFFIX := .so
INC_PREFIX := -I
LIB_PREFIX := -L
OPT_C := -c
OPT_OUT := -o
OPT_LINKOUT := -o
CFLAGS := $(OPT_C)
LIBFLAGS := -Debug
# ---------------------------------------------------------------------------
# directories
# ---------------------------------------------------------------------------
SRC_DIR := ./src
OBJ_DIR := ./obj
INC_DIR := ./inc
LIB_DIR := ./lib /usr/local/lib /lib /usr/lib
# ---------------------------------------------------------------------------
# common settings
# ---------------------------------------------------------------------------
SRCS := $(wildcard $(SRC_DIR)/*$(SRC_SUFFIX))
OBJS := $(patsubst $(SRC_DIR)/%$(SRC_SUFFIX),$(OBJ_DIR)/%$(OBJ_SUFFIX),$(SRCS))
INCS := $(addprefix $(INC_PREFIX), $(INC_DIR))
LIBS := $(addprefix $(LIB_PREFIX), $(LIB_DIR)) $(LIBFLAGS)
TEMPFILES := core core.* *$(OBJ_SUFFIX) temp.* *.out typescript*
# ---------------------------------------------------------------------------
# make rule
# ---------------------------------------------------------------------------
TARGET := loader
.PHONY: all clean
all: $(TARGET)
clean:
$(RM) $(TARGET)$(BIN_SUFFIX) $(OBJS)
$(TARGET):$(OBJS)
$(LINK) $(OPT_LINKOUT)$(TARGET)$(BIN_SUFFIX) $(LIBS) $(OBJS)
$(OBJS):$(OBJ_DIR)/%$(OBJ_SUFFIX):$(SRC_DIR)/%$(SRC_SUFFIX)
$(CC) $(CFLAGS) $(INCS) $(OPT_OUT)$@ $<
模板2
CXX := g++
SRC_DIR := ./src
OBJ_DIR := ./build
BIN_DIR := ./bin
INC_DIR := ./include
VPATH = $(INC_DIR) $(OBJ_DIR) $(SRC_DIR)
vpath %.h $(INC_DIR)
# 一种搜索源文件的方式
# SRC_DIRS = $(shell find $(SRC_DIR) -maxdepth 3 -type d)
# SRCS = $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp))
# TODO: 这样子出来的目标文件,在jing'tai时就找不到依赖了
# OBJS := $(OBJ_DIR)/$(notdir $(patsubst %.cpp, %.o, $(SRCS)))
SRCS := $(wildcard $(SRC_DIR)/*.cpp)
OBJS := $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRCS))
INCS := $(addprefix -I, $(INC_DIR))
BUILDING_DIRS := $(OBJ_DIR) $(BIN_DIR)
TARGET := adb_lab2.exe
RUN := run.sh
$(TARGET) : $(BUILDING_DIRS) $(OBJS)
$(CXX) -o $(BIN_DIR)/$(TARGET) $(OBJS)
@touch $(RUN)
@echo "$(BIN_DIR)/$(TARGET)" > $(RUN)
# 这里的前缀不能少。makefile不会自动去VPATH里面找这几个目标,而是直接当成新的目标来对待
$(OBJ_DIR)/BufferPoolManager.o : BufferPoolManager.h LRUReplacer.h
$(OBJ_DIR)/DataStorageManager.o : DataStorageManager.h
$(OBJ_DIR)/LRUReplacer.o : LRUReplacer.h
$(OBJ_DIR)/main.o : BufferPoolManager.h
# 一个创建运行时依赖文件夹的方法
$(BUILDING_DIRS) :
@mkdir $@
# 这叫 静态模式
$(OBJS) : $(OBJ_DIR)/%.o : $(SRC_DIR)/%.cpp
$(CXX) -o $@ -c $< $(INCS)
.PHONY: all clean output
all : $(TARGET)
clean:
-rm -rf $(BUILDING_DIRS) test.dbf $(RUN)
output:
@echo $(SRCS)
@echo --------------
@echo $(OBJS)
模板3
CC := gcc
CC_INCLUDE_FLAGS := -I ./include/
CC_FLAGS := $(CC_INCLUDE_FLAGS) -g
# 程序执行的参数
ARGS := ~/codes
DIR_SRC := ./src
DIR_OBJ := ./build
DIR_EXE := ./bin
SRCS := $(shell find $(DIR_SRC) -name "*.c")
OBJS := $(patsubst $(DIR_SRC)/%.c, $(DIR_OBJ)/%.o, $(SRCS))
DPTS := $(patsubst %.c, %.d, $(SRCS))
DIRS := $(DIR_OBJ) $(DIR_EXE)
target := $(DIR_EXE)/my_ls_pro
$(target): $(DIRS) $(OBJS)
$(CC) $(OBJS) -o $@
$(DIRS):
@mkdir $@
$(DIR_OBJ)/%.o: $(DIR_SRC)/%.c
$(CC) $(CC_FLAGS) -c $< -o $@
%.d: %.c
@set -e; \
rm -f $@; \
$(CC) -MM $(CC_FLAGS) $< $(CC_INCLUDE_FLAGS) > $@.$$$$.dtmp; \
sed 's,\(.*\)\.o\:,$*\.o $*\.d\:,g' < $@.$$$$.dtmp > $@;\
rm -f $@.$$$$.dtmp
-include $(DPTS)
clean:
rm -f $(OBJS)
rm -f $(DPTS)
run:
make
$(target) $(ARGS)
教程
REF1:makefile简明教程, REF2:如何输出到指定文件夹 ,REF3:关于自动生成依赖.d文件
模式替换
$(patsubst <pattern>,<replacement>,<text> )
查找
这里,
$(patsubst %.c,%.o, a.c b.c)
# 把字串 “a.c b.c” 符合模式[%.c]的单词替换成[%.o],返回结果是 “a.o b.o”
变量替换引用
对于一个已经定义的变量,可以使用“替换引用”将其值中的后缀字符(串)使用指定的字符(字符串)替换。格式为$(VAR:A=B)
或者${VAR:A=B}
意思是,替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。“结尾”的含义是空格之前(变量值多个字之间使用空格分开)。而对于变量其它部分的“A”字符不进行替换。
foo := a.o b.o c.o
bar := $(foo:.o=.c)
# 注意变量不要带 $
SRCS_NODIR := $(notdir $(wildcard $(SRC_DIR)/*$(SRC_SUFFIX)))
OBJS_NODIR := $(SRCS_NODIR:$(SRC_SUFFIX)=$(OBJ_SUFFIX))
编码风格
命名
普通变量
推荐使用下划线隔开单词(Google开源风格指南),也可以使用驼峰命名法,但是不要使用无意义的字母!
tDataNode
:t 开头表示这是一个类型,typekPI
:k 开头表示常量,在程序运行中不会被修改
成员变量
以下内容准确性待考究。但首尾下划线一般都表示“私有”之意
__foo__: 定义的是特殊方法,一般是系统定义名字,类似__init__()之类
_foo:以单划线开头的表示的是protected类型的变量或函数,即保护类型,只允许本身和子类访问。也有在后面加下划线的,同理。都是表达"私有"之意。
__foo:以双下划线开头的表示的是private类型的变量或函数,即私有类型,只允许本身访问。
C语言中,单下划线开头表示是标准库的变量,双下划线表示是编译器的变量
注释
文件注释
/**
* @file 文件名
* @brief 简介
* @details 细节
* @author 作者
* @version 版本号
* @date 年-月-日
* @copyright 版权
* @todo 待办
* @throw 异常描述
* @bugs 漏洞
* @pre 前提条件
* @see [参考]
*/
函数注释
/**
* @brief 函数描述
* @param 参数描述
* @return 返回描述
* @retval 返回值描述
*/
结构体注释
/**
* @brief 类的详细描述
*/
常变量注释
//定义一个整型变量a
int a
int a; /*!< 定义一个整型变量a */
int a; /**< 定义一个整型变量a */
int a; //!< 定义一个整型变量a
int a; ///< 定义一个整型变量a
HTML
markdown
表格的使用
并排插入图片,需要用到表格才能实现。插入图片不需要用到<a herf></a>
,不然点一下就跳转也有些烦人。html
标签不写在代码块内即生效。
图片大小可以用<td style="width:50%"></td>
来调整,各占50%就好了
<table style="border:none;text-align:center;">
<tr>
<td style="width:50%"><img src="" alt="" border="0" /></td>
<td style="width:50%"><img src="" alt="" border="0" /></td>
</tr>
<!--可以排图,也可以图文混排,也可以用来添加注释-->
<tr>
<td style="width:50%"><strong>图1 </strong></td>
<td style="width:50%"><strong>图2 </strong></td>
</tr>
</table>
音视频
<audio controls>
<source src="https://www.runoob.com/try/demo_source/horse.mp3" >
您的浏览器不支持 audio 元素。
</audio>
<video width="320" height="240" controls>
<source src="https://www.runoob.com/try/demo_source/movie.mp4" type="video/mp4">
您的浏览器不支持 HTML5 video 标签。
</video>
例子:创建一个只用来分享视频的页面
<!doctype html>
<html>
<head>
<meta charset='UTF-8'><meta name='viewport' content='width=device-width initial-scale=1'>
<title>演示</title>
</head>
<body>
<center>
<video controls align="center" width="1350">
<source src="https://shuaikai-bucket0001.oss-cn-shanghai.aliyuncs.com/videos/Bookxnotes%E6%BC%94%E7%A4%BA2.mp4" type="video/webm">
</video>
</center>
<p> </p>
</body>
</html>