casyup.me@outlook.com

0%

在和 master vim quickly 的作者邮件中

发现了自己的vim版本还可以再更新, 于是有了这篇笔记

参考教程:

https://www.tecmint.com/vim-8-0-install-in-ubuntu-linux-systems/

https://www.systutorials.com/241762/how-to-upgrade-vim-to-version-8-on-centos-7/

vim的git地址:

https://github.com/vim/vim

vim8安装指南:

https://github.com/vim/vim/blob/master/src/INSTALL

安装vim8

  1. 首先需要源代码

    git clone https://github.com/vim/vim.git

  2. 编译

    cd <vim repo 路径>/src

    make

    make install

    现在应该就已经获得了新的vim可执行文件, 将他覆盖原来的vim就好

@btw: 中途可能会遇到一些问题

  • 安装编译vim所需的API
    • yum -y install ncurses-devel
  • 覆盖时提示出错
    • 因为可能vim还在其他地方被运行, 使用 losf 查看进程, kill 掉

具体的可以参考上面的教程链接, 这里只是简单概述

顺便给一份通常的vim设置

"General"
set number    "Show line numbers"
set linebreak    "Break lines at word (requires Wrap lines)"
set showbreak=+++    "Wrap-broken line prefix"
set textwidth=100    "Line wrap (number of cols)"
set showmatch    "Highlight matching brace"
set visualbell    "Use visual bell (no beeping)"

set hlsearch    "Highlight all search results"
set smartcase    "Enable smart-case search"
set ignorecase    "Always case-insensitive"
set incsearch    "Searches for strings incrementally"

set autoindent    "Auto-indent new lines"
set shiftwidth=4    "Number of auto-indent spaces"
set smartindent    "Enable smart-indent"
set smarttab    "Enable smart-tabs"
set softtabstop=4    "Number of spaces per Tab"

"Advanced"
set ruler    "Show row and column ruler information"
​    
set undolevels=1000    "Number of undo levels"
set backspace=indent,eol,start    "Backspace behaviour"

mkdir 创建文件夹指令  
su 切换到管理员模式  
sudo 以管理员模式执行命令
sudo passwd 以用户密码更改管理员密码
rm -f * 删除当前目录下所有文件
clear 清空终端窗口(实际还存在, 只不过看不见了, 和cmd一样)

apt-get install [工具名] 安装工具  
CentOS下是使用yum

更改vim配色
1. 下载vim文件
2. 放到~/.vim/colors文件夹下(没有就创建)
3. 更改vimrc文件, 路径: /etc/vim/vimrc, 增加指令: colorscheme [配色方案名]
PS: 默认的colors文件夹路径: /usr/share/vim/vim80/colors
之后发现vimrc文件在/etc目录下, 而vim文件放在了~/.vim目录下
emmmmm, 已经想不起当时怎么弄的了... 就这样吧, 可能是版本问题...

更改tab间距
同样更改vimrc文件, 增加如下指令: 
set ts=4 // 设置table间距为4
set expandtab // 不清楚 - -

以上环境为ubuntu 18
PS: 想起之前刚接触linux, 安装g++, gcc参照了错误的教程(那沙雕教程又是装插件又是下安装包)
好不容易装完之后又被vi的输入和命令行模式, 嘟嘟嘟嘟嘟, 气得半死
之后又好不容易写好一个测试代码, ctrl+s一保存, 直接TMD不响应了
(事后才得知这是关闭向终端输入的接口, 只需要ctrl+q就可以重新打开输入, 留下了属于无知者的泪水…)
一系列操作让我直接删除ubuntu…

vim查询字符串 /字符串
PS: 但是之后被搜索的字符串会持续高亮显示, 使输入noh可还原

vim注释多行
control + v
上下键/(j/k) 键选中操作的行
shift + i
输入// (视语言而定)
连续两次 ESC

取消多行注释
control + v
上下左右键 选中需要注释的符号
d

permission denied

有些时候, linux下执行/访问文件的时候, 都会报这个错

错误的原因是: 权限被拒绝, 那么linux的权限是怎么规定的?

文件权限

执行ll后, 显示的信息:

1
2
3
4
5
6
-rwxrwxr-x 1 wd wd  8419 Nov  8 10:47 a.out
-rw-rw-r-- 1 wd wd 60579 Oct 25 21:11 t
-rw-rw-r-- 1 wd wd 272 Oct 25 21:11 t.cpp
-rw-rw-r-- 1 wd wd 369 Nov 8 10:47 time.cpp
-rw-rw-r-- 1 wd wd 71784 Oct 29 11:53 t.o
-rw-rw-r-- 1 wd wd 5815 Oct 29 11:57 t.s

其中 -rw-rw-r– 这一串代表的就是linux的文件权限, 一共有10个字符

其中最开始的字符代表文件类型, 比如: - 代表一般文件, l 代表链接文件

后面9个字符分为三部分, 分别代表: user(用户), group(组), other(其他)

每个部分有3个字符, 分别表示: r(read, 可读), w(write, 可写), x(execute 可执行)

如果某一部分没有这个权限, 则用 - 占位

因此可知 -rw-rw-r– 意为:

一般文件, 拥有者可读写, 同组用户可读写, 其他用户可读

隐藏属性

除此之外, linux的文件还有隐藏的属性

1
2
3
4
5
6
7
[...]$ lsattr .
-------------e- ./t.o
-------------e- ./a.out
-------------e- ./t.s
-------------e- ./time.cpp
-------------e- ./t
-------------e- ./t.cpp

通过lsattr(attribute), 可以查询文件的隐藏属性, 其中比较重要的属性有:

a: append, 只能添加, 不能更改和删除

i: 不能删除. 改名, 设置链接, 同时也无法写入和添加数据

属性的增加和删除, 可以通过chattr + - 来设置, 如果想直接赋予一个新的属性, 可以使用 =

特殊权限

文件除此之外还有一些特殊的权限, SUID, SGID, SBIT

SUID位于user权限组: 拥有这个属性的文件, 最后一个字符位s, 例如: -rws——

​ SUID仅对二进制程序有效

​ SUID意为如果执行者拥有x权限, 当执行文件时, 执行者暂时拥有root权限

SGID位于group权限组: 拥有这个属性的文件, 最后一个字符位s, 例如: —-rws—

​ SGID意为如果执行者拥有x权限, 当执行文件时, 执行者暂时拥有用户组权限

SBIT位于other权限组:

​ SBIT仅对目录游泳: 拥有这个属性的文件, 最后一个字符位t, 例如: ——-rwt

​ SBIT意为: 当前面目录下, A仅仅只能删除(包括移动和重命名)A拥有的文件(文件持有者是A)

​ 不能删除B的文件

以上来自鸟哥私房菜, 详细参照第二部分

前言

最近看了<现代操作系统>, 从内存管理那一章中获得了一些东西: 分页和分段

其中分页的概念让我对内存的管理有了更加清晰的认知

什么是分页?

就是将内存分为一个个小的页面(以4k为例).

在将磁盘数据加载进内存时, 以页为单位, 而将内存中的数据换出到磁盘中时, 也以页为单位.

对此, 一个具体的虚拟地址可以分为两部分:

1
页号 + 地址

其中, 页号会被替换, 而地址会被保留:

1
真实的地址 + 地址

真实的地址存放到进程虚拟地址映射表中

emmm… 也就是说, 虚拟地址中, 有部分的地址是真实的

(如果我能知道那部分假的地址, 是否就有办法操作真实磁盘中的数据呢? 我突然有了不太好的想法 :) )

指针中是否会保留原始变量的地址?

指针中存放了数据, 而这样的数据能够找到指针所指向的变量

那么, 这样的数据是什么呢? 最直接的, 那么应该是地址, 考虑以下程序:

1
2
3
4
5
6
7
8
9
10 int main() {
11 int i = 1;
12 int *p = &i;
13 printf("%p\n", &i);
14 printf("%p\n", &p);
15 printf("%d\n", *((int *)&p));
16
17 return 0;
18 }

15打印的是指针本身内存中所指向的东西, 将它与变量i的地址做以下对比, 会相同么? 以下是输出结果:

1
2
3
0x7fff12ca4efc
0x7fff12ca4ef0
315248380

i的地址是 0x7fff12ca4efc, 而p中保存的值(我将它解释为整数)是: 315248380

整型数字不怎么直观, 将它转为hex试试? => 0x12ca4efc

有没有觉得熟悉? 0x7fff12ca4efc —– 0x12ca4efc

除前面的 0x7fff, 后面的数字是一样的, 所以我们可以说指针保存了变量的地址, 但是并不准确

那么 0x7fff 就是那个页号么? 指针中只会存放真是地址? 好像不那么对…

emmm… 好像可以继续尝试, 因为当前环境是32位的, 这个地址数字明显超出了32位的表现范围

(我居然忽略了这一点 = =…)

或许我能获得更多的数据? 在代码中加入了下面一行:

1
16     printf("%d\n", ((int *)&p)[1]);

得到了数据: 32767 => 0x7fff

所以指针中直接保存了变量的地址(我们之前并没有拿到完全的数据, 地址超过了int的大小)

指针就是地址

emmm… 好像这个笔记不是那么有意义做了…

我是不是太慢了? 这些东西应该是初学者就可以去钻研的内容

(@btw: 为什么是[1] 而不是 [-1])

是否有办法知道哪些数据是与页号有关的?

emmm… 那些东西与内核有关, 我现在没有办法获得

其他的耦合知识: volatile

在书中我还读到了一个非常有趣的知识, 那就是进程表项有一个是否缓存标志位

这个标志位的意思是, 如果该位是1, 表示该页不被缓存

意思是什么呢? 如果要访问的数据是在该页中的, 那么访问时会去访问磁盘

而写入的时候, 也会直接往磁盘中写入, 因为内存中不缓存该页的数据

仔细想想和什么东西有关? 嗯, c++的 volatile 关键字

我打赌, volatile的实现一定与这东西有关(至少实现类似) 不过我现在的能力暂时无法证实

同时还有一个疑问, volatile 修饰一个变量, 而一个页是4k的, 如何将这两个东西分开呢?

为什么是[1] 而不是 [-1]

上面的例子中, 我继续访问数据, 使用的是 [1] 而不是 [-1]

我一开始是用 [-1], 因为栈是往下增长的, 之后发现 [1] 是正确的, 为什么?

的确实际上应该是 [1], 不然局部数组的访问就要乱套了

但是栈的确是向下增长的, 从 fc 和 f0 中可以看出来

那么底层到底对我的代码做了什么? 或许可以从汇编中得到答案

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
12 main:
13 .LFB1079:
14 .cfi_startproc
15 .cfi_personality 0x3,__gxx_personality_v0
16 pushq %rbp
17 .cfi_def_cfa_offset 16
18 .cfi_offset 6, -16
19 movq %rsp, %rbp // 保存了栈底指针
20 .cfi_def_cfa_register 6
21 subq $16, %rsp // 开拓栈帧, 16个字节
22 movl $1, -4(%rbp) // 嗯, 我们的数字1被放入内存了
23 leaq -4(%rbp), %rax // 将变量1的地址放入rax
24 movq %rax, -16(%rbp) // 将rax的值放入了栈... 栈顶?(因为这里是-16)
25 leaq -4(%rbp), %rax // 又将变量1的地址放入rax
26 movq %rax, %rsi
27 movl $.LC0, %edi // .LC0:.string "%p\n"
28 movl $0, %eax
29 call printf // rax存了变量1的地址, 这里应该对应: printf("%p\n", &i);
30 leaq -16(%rbp), %rax // 将栈顶的地址放入了rax
31 movq %rax, %rsi
32 movl $.LC0, %edi
33 movl $0, %eax
34 call printf // 现在rax是栈顶, 所以对应这一句: printf("%p\n", &p);
35 leaq -16(%rbp), %rax // 把p的地址放入rax
36 movl (%rax), %eax // 又移动到eax
37 movl %eax, %esi // 还移动 = =
38 movl $.LC1, %edi // .LC1:.string "%d\n"
39 movl $0, %eax
40 call printf // 对应: printf("%d\n", ((int *)&p)[0]);
41 leaq -16(%rbp), %rax
42 addq $4, %rax // *将rax+4, 也就是往栈顶移动的
43 movl (%rax), %eax
44 movl %eax, %esi
45 movl $.LC1, %edi
46 movl $0, %eax
47 call printf // 对应: printf("%d\n", ((int *)&p)[1]);
48 movl $0, %eax
49 leave
50 .cfi_def_cfa 7, 8
51 ret
52 .cfi_endproc

那么了解了, 虽然栈是往下移动的, 分配栈帧时也是往下移动的

但是 [1] 这种位移时, 是往上移动的, 也就是往高地址移动的

因为变量本身的地址开始是在栈的低地址, 是往上移动的

没有问题, 是正常的 :)

但是我又发现一个问题, 这里指针所占用的空间为8字节!

不过又想了一下, 这好像也正常的, 毕竟64位嘛…

引用呢? 引用又是什么样子的呢?

时隔几天, 突然想到了这个东西, 引用又是什么样子的呢? 它和指针实质的区别?

参考以下代码:

1
2
3
4
5
6
7
8
9
10
int i = 100;
int &i2 = i;
int *pi = &i;

printf("%p\n", &i);
printf("%p\n", &i2);
printf("%p\n", &pi);
printf("%d\n", i);
printf("%d\n", i2);
printf("%d\n", *pi);

emmm… 这很简单, 分别打印地址和值

我们来看看汇编:

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
main:
.LFB1084:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp // 32字节的栈帧
movl $100, -12(%rbp) // i
leaq -12(%rbp), %rax // i 的地址放入了rax
movq %rax, -8(%rbp) // 直接将 rax 的值放入了 i2, 也就是说, 引用也保存了变量的地址
leaq -12(%rbp), %rax // 这是一个完全可以优化掉的操作
movq %rax, -24(%rbp) // pi
leaq -12(%rbp), %rax // 你家 rax 都没变过, 你怎么还来 (눈_눈)
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf // &i
movq -8(%rbp), %rax // 这里直接将栈中的值放进入了
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf // &i2
leaq -24(%rbp), %rax // 注意, 这是 lea
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf // &pi
movl -12(%rbp), %eax
movl %eax, %esi
movl $.LC1, %edi
movl $0, %eax
call printf // i
movq -8(%rbp), %rax // 直接将地址放入 rax
movl (%rax), %eax // 将这个地址中的值放入了(即 i 的地址) eax
movl %eax, %esi
movl $.LC1, %edi
movl $0, %eax
call printf // i2
movq -24(%rbp), %rax
movl (%rax), %eax
movl %eax, %esi
movl $.LC1, %edi
movl $0, %eax
call printf // pi
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc

引用和指针并无关键性的区别, 引用也会占用内存(废话 (눈_눈), 不过我记得培训时有个沙雕老师说不占)

当使用引用的值时, 它是像指针一样使用

而当对引用取地址时, 它是直接拿存储的数据, 而并非用存储的数据去寻址

(这应当是编译器的规定, 它这么编译了引用)

唯一不同是, 引用占了8字节, 这很合理, 但是为什么指针是12字节

(这是一个我在之前忽视了的点, 我曾看过那4字节中是什么, 结果是 0

(用指针的时候, 也用的是 movq, 这意味着只使用了 64 位, 即 8 字节, 为什么中空了 4 字节?)

或许我可以再试试赋值的时候, 引用和指针的不同之处

1
2
i2 = 100;
*pi = 111;

汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
main:
.LFB1078:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
pushq %rbp // 通过压栈的方式保存bp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp // 它没有显示说明栈帧有多大, 让我有点不习惯 ∑( ̄□ ̄;)
.cfi_def_cfa_register 6
movl $2147483647, -20(%rbp)
leaq -20(%rbp), %rax
movq %rax, -16(%rbp)
leaq -20(%rbp), %rax
movq %rax, -8(%rbp)
movq -16(%rbp), %rax
movl $100, (%rax) // 它是用寄存器寻址的方式来赋值的
movq -8(%rbp), %rax
movl $111, (%rax)
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc

也就是说, 赋值是一样的, 嗯, 完全一样

指针和引用的安全性

还记得为什么引用比指针安全么? 因为对于引用是像值一样去使用它, 它仅仅是别名

(其实不是别名, 如你所见, 有些时候访问引用其实还是访问的是引用所占的内存)

它不会出现意外的 delete, 因为管理了它本身数据的访问, 也不会出现一些指针原有的错误(空指针, 野指针…)

我更倾向于: 引用是一个加了顶层const的非空, 不可用于delete的指针

summary

引用和指针的本质都是地址

题外话: 尝试篡改引用指向的对象

等等, 引用的内存也是在栈中的, 虽然 c++ 不让我用光明正大的方式修改它

但是, 既然是在栈中的数据, 那么, 我应该是可以改的, 那么就来试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int i = 100;
int i2 = 200;
int &ref_i = i;

long long *desc = (long long *)(&i) + 1;// 经过计算, i 的地址往上 8 个字节就是引用对象的内存
char arr[64];
sprintf(arr, "%ld", &i2); // 将 &i2 解释为ld类型数据, 放入数组中
string s(arr); // 构建string对象, 主要是为了能使用 stoll (눈_눈)

printf("%d\n", ref_i); // 100
*desc = stoll(s); // 现在, 它里面存储的数据是 i2 的地址了
printf("%d\n", ref_i); // 200 成功了, 它指向了 i2 ( ̄ˇ ̄)
i2 = 300;
printf("%d\n", ref_i); // 300 再次验证, 没错, 我们更改了引用指向的对象

汇编就不用看了, 因为这个程序就是根据自己脑补汇编中的样子来编写的

同理, 常量, 常量指针, 这些东西只要绕过编译器设的障碍就可以修改 (突然感受到了指针的魅力)

注: 经测试, 代码在 4.4 版本下的编译器可以, 而 4.8 版本的就不行

sed

sed常用于处理文件, 它会逐行执行

基础选项

替换

s选项用于替换

1
sed 's/text/val/' f.txt

以上语句会将 f.txt 文本中的 text 替换成 val

@note: 但实际不会更改文件内容, 而仅仅是替换后打印出来

​ 如果想作用于文件, 需要结合 -i 参数

删除

d选项删除

1
sed '/text/d' f.txt

以上语句会将f.txt 文本中出现 text 的行删除

@btw: 为什么这里的 d 在后面?

修改

c选项用于修改

1
sed '/text/c test2' f.txt

以上语句会将f.txt 文本中出现 text 的行修改成 test2

@note: 修改会修改整个一行

@btw:

​ 从语义上来讲的话, 貌似理解成替换要合理一些. 因为是替换掉了整个一行

​ 但是 c 选项的完整单词是 change(修改), 而 s 选项的完整单词是 substitute(替换)

​ 所以, 遵从编写者的原意, 这里理解为修改整个一行

转换

y选项用于处理单个字符的转换

1
sed 'y/10/22/' f.txt

以上语句会将f.txt 文本中出现的 1 转换成2, 同时 0 也会转换成 2

@note: 可以将命令置于文件中, 然后使用 -f 选项指定文件

@btw: 替换类型的命令都是将选项置于行首, 其余均是置于行尾

取反

! 选项用于取反

1
sed '/text/!d' f.txt

删除不包含 text 文本的行

@btw: 如果SQL也能直接取反结果集就好了 = =

进阶

移动

n选项用于移动到下一行

1
sed '/text val/{n;d}' f.txt

当这条语句匹配到 text val 时, 会跳转到下一行, 然后删除行(删除的是匹配行的下一行)

合并两行

N选项用于将两行的数据视为一行来处理

1
sed '/text val/{N;d}' f.txt

这条语句会将匹配行, 及匹配行的下一行, 当做一行处理(一起删掉)

@btw: 同样的格式还有

1
sed 'N; /text val/d' f.txt

它会完成和上个指令一样的操作

选择性删除

D选项… 怎么说呢 = = , 看实例吧

1
sed '/text val/{N;/val text/D}' f.txt

当某一行有 text val 时, 并且下一行有 val text 时

D选项会删除前一个匹配行(也就是说 text val 所在的行会被删除)

多行打印

emmmm, 略(并不是说不会, 而是感觉没什么用)

(N和D其实运用上已经很少了, P就更少了…)

保持空间

emmmm, 略

(倒不是说保持空间没用, 相反, 保持空间在某些特定的场合)

(它能起到独一无二的作用, 但是现在对于我来说, 了解一下就好 = =)

分支

b选项用于跳过一些分支(b完整的单词是 branch, 但是我觉得 break 怎么更好 = =)

1
sed '{2, 3b; s/10/\\/}' f.txt

命令会将 10 替换成 \ , 但是会跳过 2 - 3 行

@btw: 分支可以指定标识符, 就像goto一样. 这里就直接给一个书上的例子

emmmm, 将第一行标记(其实也只有第一行)

每次将一个逗号删除(其实是替换), 循环替换

@btw:

​ 看似很厉害, 实则没啥用, 因为可以用 g

1
2
3
```shell
's/,//g'
```

​ 上述选项将会做出同样的效果

​ emmm, 我是不是忘了说 g ?

​ g 代表修改会作用于一整行, 当然 也可以使用数字来指定

测试

模式替代

通配符 &

& 用于匹配模式

1
sed 's/.0/"&"/g' f.txt

& 匹配整个模式, 以上语句将会用 “” 把所有 0 及 0前面 的支付括起来

也可以使用单个模式

1
sed 's/\(.\)\(0\)/\1 32/g' f.txt

() 括起来的部分视为一个模式, \1 引用一个模式

上述语句将会把所有以 .0 的语句替换为 . 32

@note:

​ 这个例子将会只保留 . 匹配的内容, 而 & 将会保留所有内容

​ & 模式匹配是我在进阶章里面看到的最有用的东西

emmm, 关于 sed 的内容还有一些, 不过现在就先到这里吧 = =

@supplement

​ 模式匹配: 增/删/改中可以运用模式匹配

1
sed '/test/i\this is a test' t.txt

​ 这条语句将会在有 test 字符串的那一行, 执行插入操作

保留条件表达式的第一个值

// CE: condition expression, RETAIN: 保持
#define CE_RETAIN1(a, b) ((a) ? (a) : (b))

考虑一个情况, 当一个值有效时, 使用它, 否则使用另外一个数
如果直接写的话, 不太容易理解

(exp) ? (exp) : -1;

当exp非常长的时候(或许可以保存成变量, 但是某些情况下, 不知道该用什么名字
而自己比较常用变量名差不多用光了, 或者这只是个只用一次的值), 如

val = func(a) * i + 10 ? func(a) * i + 10 : -1;

而如果用宏定义的话就比较清晰了

val = CE_RETAIN1(func(a) * i + 10, -1);

PS: 但是如果的确出现表达式过长的情况, 可能考虑优化代码是最好的解决方案

直接面向结果的函数

void show() { setvisible(true); }    
void hide() { setvisible(false); }

使用show(), hide()的情况下, 一目了然, 而通过参数的话, 理解会稍麻烦一些

getInstance

getInstance多数情况下作为一个类的公有接口, 用于获得一个实例
普通的书写方案如下

class A{
    int i; // 不仅仅是数据, 也可能是一个类
public:
    int& getInstance() { return i; }
};

使用这种方式的情况会存在两个问题

  1. 即使我从不使用i, 也从未掉用过getInstance这个函数, 但依旧给i分配了空间
  2. i依旧能在其他接口中被访问

上述两种情况并不一定会造成影响, 但可以考虑排除这种隐藏问题

int& getInstance() {
    static int i;

    return i;
}

静态变量在未被访问过的情况下, 不会分配空间
并且只存在于当前作用域, 有良好的安全性

保留条件表达式的第一个值

// CE: condition expression, RETAIN: 保持
#define CE_RETAIN1(a, b) ((a) ? (a) : (b))

考虑一个情况, 当一个值有效时, 使用它, 否则使用另外一个数
如果直接写的话, 不太容易理解

(exp) ? (exp) : -1;

当exp非常长的时候(或许可以保存成变量, 但是某些情况下, 不知道该用什么名字
而自己比较常用变量名差不多用光了, 或者这只是个只用一次的值), 如

val = func(a) * i + 10 ? func(a) * i + 10 : -1;

而如果用宏定义的话就比较清晰了

val = CE_RETAIN1(func(a) * i + 10, -1);

PS: 但是如果的确出现表达式过长的情况, 可能考虑优化代码是最好的解决方案

直接面向结果的函数

void show() { setvisible(true); }    
void hide() { setvisible(false); }

使用show(), hide()的情况下, 一目了然, 而通过参数的话, 理解会稍麻烦一些

getInstance

getInstance多数情况下作为一个类的公有接口, 用于获得一个实例
普通的书写方案如下

class A{
    int i; // 不仅仅是数据, 也可能是一个类
public:
    int& getInstance() { return i; }
};

使用这种方式的情况会存在两个问题

  1. 即使我从不使用i, 也从未掉用过getInstance这个函数, 但依旧给i分配了空间
  2. i依旧能在其他接口中被访问

上述两种情况并不一定会造成影响, 但可以考虑排除这种隐藏问题

int& getInstance() {
    static int i;

    return i;
}

静态变量在未被访问过的情况下, 不会分配空间
并且只存在于当前作用域, 有良好的安全性

保存变量

局部变量速度优于函数调用, 类似如果频繁用到数组的长度, 可以使用变量保存.size()的值

source link

Sort

Bubble Sort

Bubble is the most common algorithm to sort an array. It based on the idea of repeatedly comparing adjacent element and then swapping their value if exist in the wrong order

1
2
3
4
5
6
7
8
void bubbleSort(vector<int> &nums) {
for (int i = 0; i < nums.size(); ++i) {
for (int j = i + 1; j < nums.size(); ++j) {
if (nums[i] > nums[j])
swap(nums[i], nums[j]);
}
}
}

Selection sort

The selection sort is based on finding the minimum or maximum in an unsorted array and putting it to a sorted array

1
2
3
4
5
6
7
8
9
10
void selectionSort(vector<int> &nums) {
for (int i = 0; i < nums.size(); ++i) {
int min = i;
for (int j = i + 1; j < nums.size(); ++j) {
if (nums[min] > nums[j])
min = j;
}
swap(nums[i], nums[min]);
}
}

Insertion sort

From left to right, find each element’s correct position

1
2
3
4
5
6
7
8
9
10
11
12
13
void insertSort(vector<int> &nums) {
for (int i = 1; i < nums.size(); ++i) {
int v = nums[i];
int j = i;
while(j > 0) {
nums[j] = nums[j - 1];
if (nums[j] <= v)
break;
--j;
}
nums[j] = v;
}
}

Merge sort

Merge sort is a divide-and-conquer algorithm based on repeatedly breaking down an array to two sub-array and then merge them

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
void merge(vector<int> &nums, int b, int e) {
int m = (b + e) / 2;
int q = m + 1, p = b;
vector<int> v;
for (int i = b; i <= e; ++i) {
if (p > m)
v.push_back(nums[q++]);
else if (q > e)
v.push_back(nums[p++]);
else if (nums[q] > nums[p])
v.push_back(nums[p++]);
else
v.push_back(nums[q++]);
}

for (auto val : v)
nums[b++] = val;
}

void mergeSort(vector<int> &nums, int b, int e) {
if (b >= e) return;

mergeSort(nums, b, (e + b) / 2);
mergeSort(nums, (e + b) / 2 + 1, e);
merge(nums, b, e);
}

Quick sort

Quick sort is also a divide-and-conquer algorithm. But it reduces the space complexity and removes the use of auxiliary array that is used in merger sort
One of the most important factors to influence performance is the pivot.
I chose the pivot from the middle, front, and back of the array. Sometimes will improve performance

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
int partion(vector<int> &nums, int b, int e) {
int p = e;
int m = (b + e) / 2;
if (nums[b] > nums[m])
swap(nums[b], nums[m]);
if (nums[b] > nums[e])
swap(nums[b], nums[e]);
if (nums[m] > nums[e])
swap(nums[m], nums[e]);

int piv = nums[m];
swap(nums[e--], nums[m]);
while (b < e) {
while (b < e && nums[b] < piv)
++b;
while (b < e && nums[e] >= piv)
--e;
if (b >= e) break;

swap(nums[b], nums[e]);
}
swap(nums[p], nums[b]);

return b;
}

void quickSort(vector<int> &nums, int b, int e) {
if (b >= e) return;

int p = partion(nums, b, e);
quickSort(nums, b, p - 1);
quickSort(nums, p + 1, e);
}

Heap sort

Heap sort uses a structure called heap to sort the array. Heap is a complete binary tree.
left sub-tree index = 2 * root index + 1
right sub-tree index = 2 * root index + 2

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
void heapify(vector<int> &nums, int n, int i) {
int largest = i;
int l = 2 * i + 1;
int r = l + 1;

if (l < n && nums[l] > nums[largest])
largest = l;

if (r < n && nums[r] > nums[largest])
largest = r;

if (largest != i) {
swap(nums[i], nums[largest]);
heapify(nums, n, largest);
}
}

void heapSort(vector<int> &nums) {
for (int i = nums.size() / 2 - 1; i >= 0; --i)
heapify(nums, nums.size(), i);

for (int i = nums.size() - 1; i >= 0; --i) {
swap(nums[0], nums[i]);
heapify(nums, i, 0);
}
}

this is a follow-up to sort

these sort algorithms based on <Algorithms, 4th>

shell sort

shell sort is a better version sort algorithm based on insert sort.

when a[0] is the biggest number but head of array, for move the number to the end of the array, need swap length(a) elements.

shell sort 是基于插入排序的改良版本

当 a[0] 是整个数组最大的元素时, 为了移动这个元素到数组的尾端, 需要交换 length(a) 次元素.

1
2
3
4
5
6
7
8
9
10
11
void shellSort(vector<int> &v) {
int h = 0;
while (h < v.size() / 3) h = h * 3 + 1;
while (h >= 1) {
for (int j = h; j < v.size(); ++j) {
for (int i = j; i >= h && v[i] < v[i - h]; i -= h)
swap(v[i], v[i - h]);
}
h /= 3;
}
}

bottom-up sort

bottom-up sort is a down-top method based on merge sort.

bottom-up sort 基于合并排序, 是一种从底至上的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void merge(vector<int> &v, int b, int m, int e) {
vector<int> v2 = v;
int mid = m++;
for (int i = b; i <= e; ++i) {
if (b > mid)
v[i] = v2[m++];
else if (m > e)
v[i] = v2[b++];
else if (v2[b] < v2[m])
v[i] = v2[b++];
else·
v[i] = v2[m++];
}
}

void bottomupSort(vector<int> &v) {
for (int i = 1; i < v.size(); i *= 2){
for (int j = 0; j < v.size() - i; j = j + 2 * i)
merge(v, j, j + i -1, min(int(v.size() - 1, j + 2 * i - 1)));
}
}

a better version of quick sort

if an array with some duplicate numbers, skip these numbers will be more effective

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
#define QS_MIN_LENGTH 7

void quickSort(vector<int> &nums, int b, int e) {
if (b >= e) return;

// if element too little, insertSort more effective than quickSort
if (e - b < QS_MIN_LENGTH)
insertSort(nums, b, e);

// meduim of three can get more effetive pivot
int m = (b + e) / 2;
if (nums[b] > nums[m])
swap(nums[b], nums[m]);
if (nums[b] > nums[e])
swap(nums[b], nums[e]);
if (nums[m] > nums[e])
swap(nums[m], nums[e]);

int pivot = nums[m];
// skip elements equal pivot
int lt = b + 1, eq = lt, gt = e - 1;
while (eq <= gt) {
if (nums[eq] < pivot)
swap(nums[lt++], nums[eq++]);
else if (nums[eq] > pivot)
swap(nums[eq], nums[gt--]);
else
eq++;
}

quickSort(nums, b, lt - 1);
quickSort(nums, gt + 1, e);
}

a sort algorithm without compare

I saw a string sort algorithm without compare in <algorithm 4th>. so take some notes

I’m to lazy to write this code, please check the book…

vim中执行cmd

1
!cmd

vim中编写额外的文件

1
args filename

sed处理文件

at和crontab定时

流重定向

“>”: 输出重定向, 这个操作符会覆盖已有文件的内容, 若文件不存在, 则创建

“>>”: 同样也是输出重定向, 这个操作符会将内容添加到文件尾部, 同样的, 若文件不存在, 则创建文件

@note:

​ 比较特殊的是内联输入重定向: “<<”, 我现在也不太明白这东西的实质用法和含义

​ 只知道它的行为: 它可以使用命令行的内容作为输入流, 并且可以指定一个”标识”来表示结束

管道

@brief:

​ 将一个命令的输出作为另一个命令的输入. 例如:

1
cat file | less

cat仅仅只是查看文件, 但并不会增加一些控制功能(如翻页, 搜索…)

less则提供了相关的更高级的功能, 将cat的输入(文件内容)作为less的输入

@note:

​ 其实这个例子很没有必要, 现实中这么写, 可能会被diss到死. 因为可以直接使用 less file

创建一个新文件

1
touch file

置空文件内容

1
cat /dev/null > file

@note:

​ 更加简便的方式是 > file

单引号和双引号

需要注意的是, 单引号中, 双引号不需要转义.

同样的, 双引号中, 单引号不需要转义

这会在某些场合下, 让你的字符串更加简洁易懂, 同时还更好改, 更不容易写错

@ps:

​ 我曾在一次 crontab 任务中遇到这种场合, 我使用了 crontab file 的形式来添加任务

​ 同时使用 echo + 重定向 的形式往文件中添加内容, 而要执行的命令中就包含些许单引号和双引号

​ (不过我倒是没有在这里面用到这个技巧, 因为我第一次就写对了 :) )

md5加密

1
echo "test" | md5sum

查看自己的公网信息

curl cip.cc

简析: curl 为一个在服务器中传递数据的攻击, 而 cip.cc 为一个查询网络信息的网站

当你登录 cip.cc 时, 它应当会根据你访问网站时的信息, 查询你 IP 的相关信息, 并返还给你

传递大量文件(远端和本地)

rsync

具体用法参照 man 手册

查看本地IP端口信息

1
netstat -tunlp

t : TCP

u : UDP

n : 不经过域名服务器

l : 显示监控中服务器的socket

p : 显示监控中的程序识别码和名称

@btw: 可以加入 -a 选项, 这会将所有(包括当前未监听)的端口信息都列出来

​ 与之相关的命令还有 nmap, nc

查看本机服务

1
service --status-all | grep running

这个命令可以查看本机现在运行的所有服务

@note: –status-all 之间不能空格. “–status -all” 这是错误的写法