1 | /* For sockaddr_in */ |
bad example:
1 | /* This won't work. */ |
1 | /* For sockaddr_in */ |
引入非阻塞、忙轮询:
1 | /* This will work, but the performance will be unforgivably bad. */ |
1 | /* If you only have a couple dozen fds, this version won't be awful */ |
select()-based ROT13 server
1 | /* For sockaddr_in */ |
1 | /* For sockaddr_in */ |
simpler:
1 | /* For sockaddr_in */ |
通信媒介可能会丢失或改变被传递的消息。——信息理论(information theory)& 编码理论(coding theory)
如何使信息在通信信道中避免出错:
如果不只考虑单个通信信道,而是几个的多跳级联。除了分组比特差错,还会有以下问题:
使用重发需要判断:
使用 ACK:
允许多个分组进入网络来提高吞吐量:引出窗口的概念。
两种方式:
基于窗口(window-based):窗口大小随时间变化。
让接收方可以通知发送方使用多大的窗口,即窗口通告(window advertisement)或窗口更新(window update),一般和 ACK 是同一个分组携带的。
拥塞控制(congestion control):中间网络出现瓶颈,而非接收方。
让协议实现尝试去估计,称之为往返时间估计(round-trip-time estimation)。
选用一组 RTT 样本的均值,实际依然可能超过该值。
TCP 提供了一种面向连接的(connection-oriented)、可靠的字节流服务。
确认号(ACK):期待接收的下一个序列号。这个字段只有在 ACK 位被启用的情况下生效。通常会一直使用。
SYN 位:客户端向服务器建立新连接发送第一个报文段时被启用。此时序列号字段包含了本次连接的这个方向上要使用的初始序列号(Initial Sequence Number,ISN,出于安全考虑,一般不从 0 或 1 开始)。
8位的选项:一些老的实现只理解后6位。
窗口大小:16字节,最大 65535,但可利用窗口缩放选项进行扩大。实现流量控制,是一个字节数。


TCP/IP 支持多种链路层,依赖于网络硬件类型:
本章将讨论点到点协议(PPP)、如何在其他协议中携带链路层协议、隧道技术。
链路层的 PDU 通常称为:帧。
介质访问控制(MAC)协议:
共享电缆 -> 双绞线(10BASE-T) -> 快速以太网(100BASE-TX,100Mb/s) -> 以太网交换机。
以太网标准:802.3。
前导字段和帧起始分隔符(SFD):用于接收器同步。
目的地址(DST)和源地址(SRC):各 6 字节,又称 MAC地址、链路层地址、802地址、硬件地址、物理地址。允许广播(ARP协议)或组播(ICMPv6协议)
类型或长度字段:大于等于1536表示类型,小于等于1500表示长度。常见值:
标签:常见的是由 802.1p 和 802.1q 使用的,提供虚拟局域网(VLAN)和一些服务质量(QoS)指示符。
数据区(有效载荷):放置上层 PDU,最大为 1500 字节(MTU),有时会填充 0 来满足最小长度要求。
循环冗余校验(CRC)字段或帧校验序列(FCS)字段中:32位。
1 | git clone https://github.com/google/googletest.git |
-DGTEST_USE_OWN_TR1_TUPLE=1
。使用自带的 TR1 Tuple。若使用自己工程的 TR1 Tuple library,须有-DGTEST_USE_OWN_TR1_TUPLE=0
。-DGTEST_HAS_TR1_TUPLE=0
。禁用使用了 TR1 Tuple 的特性。Some Google Test features require the C++ Technical Report 1 (TR1) tuple library, which is not yet available with all compilers.
-DGTEST_HAS_PTHREAD=1
。当pthread
启用时,GTest 是线程安全的。在#include "gtest/gtest.h"
之后检查GTEST_IS_THREADSAFE
宏可以判断是否是线程安全的。如果判断不准确,可定义该宏强制启用pthread
。-DGTEST_CREATE_SHARED_LIBRARY=1
。若需要生成动态链接库,添加此选项。-DGTEST_LINKED_AS_SHARED_LIBRARY=1
。若要使用动态库编译自己的测试代码,添加此选项。-DGTEST_DONT_DEFINE_XXXXX=1
。为防止与已有宏冲突,将 GTest 中的宏XXXXX
改为GTEST_XXXXX
。1 |
Step #2: 定义测试。
_
。gtest.h
中定义的宏来写测试。1 | // Tests Factorial(). |
main()
中调用RUN_ALL_TESTS()
(甚至不需要注册这些测试),成功则返回 0。EXPECT_EQ
比较指针和NULL
会触发编译器警告。1 | // To use a test fixture, derive a class from testing::Test. |
如要求让所有测试在 5s 内完成。
1 | class QuickTest : public testing::Test { |
两种方式:
::testing::TestWithParam
题库:https://github.com/liuyubobobo/Play-with-Algorithm-Interview
stringstream
可以用eof()
判断是否读到了结尾,但不包括尾部空格。722.
792.
796.
736.
Single Number
389.
Intersection of Two Arrays
Intersection of Two Arrays II
Valid Anagram
Happy Number
Word Pattern
Isomorphic Strings
[Medium] Sort Characters By Frequency
Unique Morse Code Words
Two Sum
15.
18.
16.
454.
49.
697.
734.
447.
149.
719.
219.
217.
220.
155.
716.
729.
731.
855.
290.
811.
819.
128.
Reverse Linked List
[Medium] Reverse Linked List II
Remove Duplicates from Sorted List
[Medium] Partition List
328.
2.
445.
203.
82.
21.
24.
25.
147.
148.
237.
19.
61.
143.
234.
725.
817.
141.
142.
287.
20.
150.
71.
735.
144.
94.
145.
341.
388.
102.
107.
103.
199.
232.
637.
279.
127.
126.
675.
347.
23.
692.
239.
786.
857.
856.
490.
104.
111.
226.
100.
101.
222.
110.
112.
111.
404.
257.
113.
129.
250.
437.
785.
783.
235.
98.
450.
108.
230.
236.
530.
99.
109.
105.
106.
173.
863.
865.
87.
17.
690.
93.
131.
797.
46.
47.
784.
77.
39.
40.
216.
78.
90.
401.
254.
79.
200.
130.
417.
695.
694.
733.
711.
51.
52.
37.
390.
70.
120.
64.
123.
309.
714.
118.
343.
279.
91.
62.
63.
198.
213.
337.
309.
740.
115.
416.
322.
377.
474.
139.
494.
518.
805.
300.
376.
673.
583.
712.
718.
698.
188.
790.
600.
727.
741.
788.
455.
392.
561.
435.
860.
861.
765.
386.
307.
370.
218.
699.
715.
308.
208.
720.
676.
677.
648.
211.
737.
721.
684.
787.
685.
765.
864.
268.
382.
398.
710.
319.
858.
380.
381.
391.
780.
781.
789.
795.
799.
866.
732.
]]>创建类的总体设计。包括:
创建类中的子程序。
关闭程序。
正确性(correctness):意味着永不返回不准确的结果,哪怕不返回数据。
健壮性(robustness):意味着不断尝试采取某些措施,以保证软件可以持续地运转下去,哪怕有时做出一些不够准确地结果。
确定一种通用的处理错误参数的方法,是架构层次(或称高层次)的设计决策。
catch
语句。如果确实无法将某较低层次上的异常表现为调用方抽象层次上的异常,在采用空catch
的同时也要用注释或日志对其进行文档化说明。把项目中对异常的使用标准化。
throw-catch
在局部进行处理。考虑异常的替换方案。
default
或else
分支都能产生严重错误,让错误不会被忽视。ant
和make
这样的版本控制工具。通过阅读汇编代码:
Intel处理器俗称x86。主要发展历程:
这些处理器是后向兼容的:较早版本上编译的代码可以在较新的处理器上运行。
1 | gcc -0g -o p p1.c p2.c |
两种抽象:
机器代码的一些在C语言中不可见的状态:
一组向量寄存器:存放一个或多个整数或浮点的值。
汇编代码不区分有符号无符号,不区分各种类型的指针,甚至不区分指针和整数。
查看编译器产生的汇编代码(生成*.s
):
1 | gcc -0g -S *.c |
编译并汇编代码(生成目标文件*.o
):
1 | gcc -0g -c *.c |
使用 gdb 展示程序的二进制目标代码:
1 | # 先用反汇编器确定该函数的代码长度是14字节。 |
利用反汇编器查看机器代码内容:
1 | objdump -d mstore.o |
一些关于机器代码和反汇编表示的特性:
反汇编器使用的指令命名规则和 GCC 生成的汇编代码有些细微差别。如:省略了很多指令结尾的q
,也给一些指令添加了q
。
连接器会为函数调用找到匹配的函数的可执行代码的位置。
.
开头的行都是指导汇编器和连接器工作的伪指令。ATT 汇编代码格式(GCC、Objdump等工具的默认格式)和 Intel 汇编代码格式(Microsft 的工具和 Intel 的文档)的区别:
q
。%
。QWORD PTR [rbx]
而不是(%rbx)
。C程序中插入汇编代码的方法:
b
。w
。l
。q
。%ax
到%bp
。%eax
到%ebp
。%rax
到%rbp%
,从%r8
到%r15
。$
前缀加标准C表示法的整数。MOV
指令类指令 | 效果 | 描述 |
---|---|---|
MOV S, D | D <- S | 传送 |
movb | 传送字节 | |
movw | 传送字 | |
movl | 传送双字 | |
movq | 传送四字 | |
movabsq I, R | R <- I | 传送绝对的四字 |
movl
指令以寄存器作为目标时,会把高 4 位也置为 0。movq
只能以表示为32位补码数字的立即数作为源操作数,然后把这个值符号扩展得到 64 位的值,放到目的位置。movabsq
能够以任意 64 位立即数作为源操作数,并且只能以寄存器作为目的。MOVZ
指令类指令 | 效果 | 描述 |
---|---|---|
MOVZ S, R | R <- 零扩展(S) | 以零扩展进行传送 |
movzbw | 字节 -> 字 | |
movzbl | 字节 -> 双字 | |
movzwl | 字 -> 双字 | |
movzbq | 字节 -> 四字 | |
movzwq | 字 -> 四字 |
MOVS
指令类指令 | 效果 | 描述 |
---|---|---|
MOVS S, R | R <- 符号扩展(S) | 以零扩展进行传送 |
movsbw | 字节 -> 字 | |
movsbl | 字节 -> 双字 | |
movswl | 字 -> 双字 | |
movsbq | 字节 -> 四字 | |
movswq | 字 -> 四字 | |
movslq | 双字 -> 四字(movzlq 并不存在,因为用 movl 其实就能实现) | |
cltq | %rax <- 符号扩展(%eax) | 把 %eax 符号扩展到 %rax(即 movslq %eax,%rax ) |
%rax
或该寄存器某个低位部分中返回。%rsp
保存着栈顶元素的地址。指令 | 效果 | 描述 |
---|---|---|
pushq S | R[%rsp] <- R[%rsp] - 8 | 将四字压入栈 |
M[R[%rsp]] <- S | ||
popq D | D <- M[R[%rsp]] | 将四字弹出栈 |
R[%rsp] <- R[%rsp] + 8 |
四组操作:
加载有效地址:
leaq S,D
,效果:D <- &S
一元操作:
INC D
:加 1。DEC D
:减 1。NEG D
:取负。NOT D
:取补。二元操作:
ADD S, D
:加。SUB S, D
:减。IMUL S, D
:乘。XOR S, D
:异或。OR S, D
:或。AND S, D
:与。移位:
SAL k, D
:左移。SHL k, D
:左移。SAR k, D
:算术右移。SHR k, D
:逻辑右移。leaq
实际上是movq
指令的变形。leaq
的一些灵活用法,比如可以简洁地描述普通的算术计算(加法和乘法),根本就与有效地址的计算无关。%cl
中。移位操作对w
位长的数据值进行操作时,移位量是由%cl
寄存器的低m
位决定的,这里2^m = w
。高位被忽略。如%cl
为0xFF
:
salb
移动 7 位。salw
移动 15 位。sall
移动 31 位。salq
移动 63 位。指令 | 效果 | 描述 |
---|---|---|
imulq S | R[%rdx]: R[%rax] <- S*R[%rax] | 有符号全乘法 |
mulq S | R[%rdx]: R[%rax] <- S*R[%rax] | 无符号全乘法 |
clto | R[%rdx]: R[%rax] <- 符号扩展(R[%rax]) | 转换为八字 |
idivq S | R[%rdx] <- R[%rdx]: R[%rax] mod S | 有符号除法 |
R[%rdx] <- R[%rdx]: R[%rax] / S | ||
divq S | R[%rdx] <- R[%rdx]: R[%rax] mod S | 无符号除法 |
R[%rdx] <- R[%rdx]: R[%rax] / S |
imulq
有两种形式:
imulq
。)。%rax
与操作数相乘,产生一个128位乘积,高位和低位分别存放在%rdx
和%rax
中。(mulq
同理。)C 标准没有提供128位的值。可以借助 GCC 提供的__int128
来声明128位整数。
将结果从寄存器取出时,要注意机器的大小端。
idivq
:
%rdx
(高64位)和%rax
(低64位)中的128位数作为被除数,操作数作为除数,商存放在%rax
,余数存放在%rdx
。%rax
中,%rdx
的位应该设置为0(无符号运算)或者%rax
的符号位(有符号运算),使用cqto
指令即可完成这个操作。divq
应该将%rdx
事先设定为0。 除了整数寄存器,CPU 还维护着一组单个位的条件码寄存器。常用的条件码有:
CF
:进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作的溢出。ZF
:零标志。最近的操作得出的结果为 0。SF
:符号标志。最近的操作得出的结果为负值。OF
:溢出标志。最近的操作导致一个补码溢出——正溢出或负溢出。除了leaq
,上面列出的所有指令都会改变条件码。并且:
XOR
,进位标志和溢出标志会设置成0。INC
和DEC
指令会设置溢出和零标志,但是不会改变进位标志。还有两类指令只设置条件码而不改变任何其他寄存器:
指令 | 基于 | 描述 |
---|---|---|
CMP S1, S2 | S2 - S1 | 比较 |
cmpb | 比较字节 | |
cmpw | 比较字 | |
cmpl | 比较双字 | |
cmpq | 比较四字 | |
TEST S1, S2 | S1 & S2 | 测试 |
testb | 测试字节 | |
testw | 测试字 | |
testl | 测试双字 | |
testq | 测试四字 |
SET
指令。)指令 | 同义名 | 效果 | 设置条件 |
---|---|---|---|
sete D | setz | D <- ZF | 相等/零 |
setne D | setnz | D <- ~ZF | 不等/非零 |
sets D | D <- SF | 负数 | |
setns D | D <- ~SF | 非负数 | |
setg D | setnle | D <- ~(SF^OF)&~ZF | 大于(有符号) |
setge D | setnl | D <- ~(SF^OF) | 大于等于(有符号) |
setl D | setnge | D <- SF^OF | 小于(有符号) |
setle D | setng | D <- (SF^OF) or ZF | 小于等于(有符号) |
seta D | setnbe | D <- ~CF & ~ZF | 超过(无符号) |
setae D | setnb | D <- ~CF | 超过或等于(无符号) |
setb D | setnae | D <- CF | 低于(无符号) |
setbe D | setna | D <- CF or ZF | 低于或等于(无符号) |
SET
指令的后缀不是操作数的大小,而是不同的条件。SET
的目的操作数是低位单字节寄存器元素之一,或是一个字节的内存位置,指令会将这个字节设置为 0 或者 1。因此如果要得到 32 位或者 64 位的结果,须对高位清零。
对于大多数情况,机器代码对于有符号和无符号两种情况都使用一样的指令。
jmp
:无条件跳转指令。可以是:
.L1
。*
跟操作数指示符,如*%rax
、*(%rax)
。其他条件跳转都只能是直接跳转。
指令 | 同义名 | 跳转条件 | 描述 |
---|---|---|---|
je D | jz | ZF | 相等/零 |
jne D | jnz | ~ZF | 不等/非零 |
js D | SF | 负数 | |
jns D | ~SF | 非负数 | |
jg D | jnle | ~(SF^OF)&~ZF | 大于(有符号) |
jge D | jnl | ~(SF^OF) | 大于等于(有符号) |
jl D | jnge | SF^OF | 小于(有符号) |
jle D | jng | (SF^OF) or ZF | 小于等于(有符号) |
ja D | jnbe | ~CF & ~ZF | 超过(无符号) |
jae D | jnb | ~CF | 超过或等于(无符号) |
jb D | jnae | CF | 低于(无符号) |
jbe D | jna | CF or ZF | 低于或等于(无符号) |
跳转指令的编码分为:
例子:
1 | (汇编代码) |
其中,反汇编第二行的8
是由0x3 + 0x5
得到,第五行的5
是由0xf8 + 0xd
得到。
注:AMD 建议用rep
后面跟ret
的组合来避免使ret
成为条件跳转指令的目标。如果没有rep
,当分支不跳转时,jg
指令会继续到ret
指令。(以后遇到rep
或repz
就直接无视掉。)
C语言中if-else
到汇编的转换:
1 | (C代码) |
利用控制实现条件转移虽然简单通用,但是可能非常低效。因为处理器通过使用“流水线”来获得高性能,而流水线需要事先确定要执行的指令序列,遇到条件分支时需要采用“分支预测逻辑”来猜测每条跳转指令是否会执行。若猜错,需要处理器丢掉这些已经做好的工作,浪费大约15~30个时钟周期。
假设预测错误的概率是p
,如果没有预测错误,执行代码的时间是T-OK
,否则是T-MP
,则执行代码的平均时间T-avg = T-OK + p * T-MP
。
指令 | 同义名 | 传送条件 | 描述 |
---|---|---|---|
cmove S,R | cmovz | ZF | 相等/零 |
cmovne S,R | cmovnz | ~ZF | 不等/非零 |
cmovs S,R | SF | 负数 | |
cmovns S,R | ~SF | 非负数 | |
cmovg S,R | cmovnle | ~(SF^OF)&~ZF | 大于(有符号) |
cmovge S,R | cmovnl | ~(SF^OF) | 大于等于(有符号) |
cmovl S,R | cmovnge | SF^OF | 小于(有符号) |
cmovle S,R | cmovng | (SF^OF) or ZF | 小于等于(有符号) |
cmova S,R | cmovnbe | ~CF & ~ZF | 超过(无符号) |
cmovae S,R | cmovnb | ~CF | 超过或等于(无符号) |
cmovb S,R | cmovnae | CF | 低于(无符号) |
cmovbe S,R | cmovna | CF or ZF | 低于或等于(无符号) |
对比:
1 | (C语句) |
不是所有的条件表达式都可以用传送条件来编译。如果then-expr
或else-expr
可能产生错误条件或副作用,会导致非法的行为。(如给全局变量赋值、return p ? *p : 0;
等)
条件传送并不总是会提高代码的效率。编译器一般只在两个表达式都很容易计算且没有副作用时才会使用。
1 | (C语句) |
1 | // &&表示指向代码位置的指针。 |
1 | .section .rodata |
1 | switch_eg: |
P 调用 Q,再返回 P。这些动作包括以下一个或多个机制:
当 P 调用 Q 时:
大多数过程的栈帧都是定长的,在过程的开始就分配好了。但也有变长的帧。
指令 | 描述 |
---|---|
call Label | 过程调用(直接调用) |
call *Operand | 过程调用(间接调用) |
ret | 从过程调用中返回 |
call Q
会把地址A(返回地址,是紧跟在call
指令后的那条指令的地址)压入栈,并将 PC 设置为 Q 的起始地址。ret
会从栈中弹出地址A,并将 PC 设置为 A。操作数大小 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
64 | %rdi | %rsi | %rdx | %rcx | %r8 | %r9 |
32 | %edi | %esi | %edx | %ecx | %r8d | %r9d |
16 | %di | %si | %dx | %cx | %r8w | %r9w |
8 | %dil | %sil | %dl | %cl | %r8b | %r9b |
char
和char*
类型。则它的内存状态是:局部数据必须存放在内存中的情况:
&
,因此必须能够为它产生一个地址。%rbx
、%rbp
和%r12 ~ %r15
被划分成 被调用者 保存寄存器。当 P 调用 Q 时,Q 必须保存这些寄存器的值,确保他们在调用接受后不变。措施有:
其他所有寄存器,除了栈指针%rsp
,都分类为 调用者 保存寄存器。任何函数都能修改他们。当 P 调用 Q 时,调用前保存好这个数据是 P 的责任。
T A[N]
T
N
A
L
x
x + i * L
MOV
、leaq
等指令进行地址的运算。T D[R][C]
,&D[i][j] = x + L(C · i + j)
。.align 8
。&
运算符创建。*
用于间接引用指针。栈随机化。
栈破坏检测
限制可执行代码的区域
%rbp
作为帧指针(frame pointer)或称基指针(base pointer)。处理器的浮点数体系结构包括以下几个方面:
历史:
-mavx2
)。我们讲述的版本。指令 | 源 | 目的 | 描述 |
---|---|---|---|
vmovss | M32 | X | 传送单精度数 |
vmovss | X | M32 | 传送单精度数 |
vmovsd | M64 | X | 传送双精度数 |
vmovsd | X | M64 | 传送双精度数 |
vmovaps | X | X | 传送对齐的封装好的单精度数 |
vmovapd | X | X | 传送对齐的封装好的双精度数 |
a
表示aligned
,如果地址不满足 16 字节对齐,会导致异常。在两个寄存器之间传送数据,绝不会出现错误对齐的状况。指令 | 源 | 目的 | 描述 |
---|---|---|---|
vcvttss2si | X/M32 | R32 | 用截断的方法把单精度数转换为整数 |
vcvttsd2si | X/M64 | R32 | 用截断的方法把双精度数转换为整数 |
vcvttss2siq | X/M32 | R64 | 用截断的方法把单精度数转换为四字整数 |
vcvttsd2siq | X/M64 | R64 | 用截断的方法把双精度数转换为四字整数 |
指令 | 源1 | 源2 | 目的 | 描述 |
---|---|---|---|---|
vcvtsi2ss | M32/R32 | X | X | 整数 -> 单精度数 |
vcvtsi2sd | M32/R32 | X | X | 整数 -> 双精度数 |
vcvtsi2ssq | M64/R64 | X | X | 四字整数 -> 单精度数 |
vcvtsi2sdq | M64/R64 | X | X | 四字整数 -> 双精度数 |
MOV
指令的选择。MOV
指令的挑错。MOV
、MOVZ
、MOVS
的计算,实现C语言强制类型转换的指令。MOV
、MOVZ
、MOVS
的逆向推算。leaq
的计算。leaq
的逆向推算。xorq %rdx, %rdx
实现赋 0 值。SET
的逆向推算。TEST
的逆向推算。do-while
反推。while
反推。while
反推。while
反推 。计算有多少个1switch
反推。switch
反推。创建类的很多理由也是创建子程序的理由。
心理障碍:不情愿为一个简单的目的而编写一个简单的子程序。
内聚性(cohesion):
不够理想的内聚性:
不可取的内聚性:
IN
、OUT
关键字。在接口中对参数的断言加以说明。包括:
参数数量限制在7个以内。
语义上:
const
、inline
、template
、enum
、typedef
等替代宏。SetCurrentInstance(instID);
。(不推荐)private
的字段放到头文件中暴露给其他人开,可以使用一个XXXImplement
类的指针。... has a ...
has a
关系。private
继承来实现has a
关系。...is a ...
is a
关系。基类对派生类将会做什么既设定了预期,也给出了限制。确保只继承需要继承的部分。根据是否可覆盖、是否提供默认实现,可以分为:
不要覆盖一个不可覆盖的成员函数。
private
而非protected
。“继承会破坏封装”,如果真的需要私有数据,就提供protected
访问器函数。一般来说,应尽量减少类和类之间相互合作的范围。包括:
优先采用深层复本(deep copies),除非论证可行,才采用浅层副本(shallow copies)。
不同语言之间可能有差异的地方:
引用:https://codeburst.io/13-tips-tricks-for-writing-shell-scripts-with-awesome-ux-19a525ae05ae
中文版:https://mp.weixin.qq.com/s/ZaIX8jv9LMWmrHQb4ew-dQ
--help
1 |
|
学到了:
$@
输出所有参数。$#
或${ #@ }
输出参数长度。(hexo报错,这里多加了空格)#
用于按最短截断字符串,##
用于按最长截断。-eq
、-ne
等只能用于数值,不能用于字符串。1 |
|
command -v
寻找命令,弱寻找到该命令返回0。$?
获取上条命令的退出值。1 |
|
dirname file
可获取到当前目录到目标文件的相对路径。1 | # do this |
1 |
|
printf -- "xxxxx"
可以让后面的字符串原样打印。--silent
模式1 |
|
stty -echo
和stty +echo
控制屏幕回显。但要注意如果程序中途异常状态没有设置回来。1 |
|
1 |
|
1 |
|
\e
而不是\033
,但要注意\e
不适用于所有的 UNIX 系统。1 |
|
1 |
|
1 |
|
1 |
|
printf
代替echo
,因为后者在不同系统中行为有差别。printf
不会像echo
那样在命令结束后添加一个换行符。软件系统
分解为子系统和包
简化子系统之间的交互关系。由简单到复杂依次是:
子系统应该是无环的。
常见子系统:
分解为包中的类
分解为类中的数据和子程序
子程序内部的设计
步骤:
迭代方向:
typedef int IdType;
。IdType
类,使设计复杂化。好的程序设计要适应变化,措施:
容易发生变化的区域:
状态变量。
数据量的限制。
预料不同程度的变化:
耦合标准:
耦合的种类:
语义上的耦合:最难缠。一个模块使用了另一个模块的语法元素,还使用了它内部工作细节的语义知识。如:
松散耦合使得你对一个模块的使用不用同时关注几件事——内部工作细节、全局数据修改、不确定的功能点等。否则就失去了抽象的意义,模块具有的管理复杂度的功能就丧失了。
设计模式的益处:
潜在陷阱:
0 ~ 2^w-1
)。1 | gcc -m32 prog.c |
int
通常也只有4字节。long
在32位程序中为4字节,在64位程序中为8字节。int32_t
、int64_t
。unsigned
,类型默认是有符号的,char
是个例外,C标准不保证这一点。1 | int a = 0x01234567; |
字节顺序很重要的场合:
值相等的整数和浮点数在字节模式上截然不同,不过一般能够通过移位相匹配。
0
结尾的字符数组。~
&
|
^
~
&
|
^
&&
||
!
算术右移:在左端补k个最高有效位的值。
C语言标准并未明确定义有符号数使用哪种右移,但几乎所有的编译器/机器都使用算术右移。
log(2)w
位。*32_t
、*64_t
这种。B2U-w
表示。B2T-w
表示。有符号数的其他表示方法:
T2U-w
、U2T-w
表示。2^w
。2^w
。u
。INT_MIN
往往定义成-INT_MAX-1
。x mod 2^k
。U2T-k(x mod 2^k)
x+y
若溢出,最终结果相当于x+y-2^w
。x+y
是否溢出的方法是与x
或y
进行比较。-x
,最终结果相当于2^w-x
。(0
除外,依然是0)。x+y
若溢出,最终结果相当于x+y-2^w
(正溢出)或x+y+2^w
(负溢出)。x>0, y>0, s<=0
时,发生了正溢出。x<0, y<0, s>=0
时,发生了负溢出。-TMin-w
还是TMin-W
。其他数正常。-x
-> ~x+1
。x*y
若溢出,最终结果相当于x*y mod 2^w
。x*y
若溢出,最终结果相当于U2T-w(x*y mod 2^w)
。x*14 = (x<<3) + (x<<2) + (x<<1);
,因为14 = 2^3 + 2^2 + 2^1
,或者x*14 = (x<<4) - (x<<1)
,因为14 = 2^4 - 2^1
。x / 2^k = x >> k
,结果是向下舍入的。x / 2^k = (x+(1<<k)-1) >> k
。1-ε
表示能表示的距离1最近且小于1的浮点数。V = (-1)^s * M * 2^E
1~2-ε
或0~1-ε
。浮点数的位表示分成三段:
根据阶码的值,可以分为三种情况:
E != 0 && E != 255
)e
并不是阶码,E = e - Bias
,其中偏置码Bias = 2^(k-1) - 1
。f
(0.xxxx)并不是尾数,为了获得额外一个精度,M = f + 1
。即隐含的以1开头(implied leading 1)的表示。E == 0
)E = 1 - Bias
M = f
E == 0x1111..1111 && M == 0
)、NaN(E == 0x1111..1111 && M != 0
)比较浮点值的大小可以转化成比较无符号整型的大小。(负数时需要一些技巧,见练习2.84)
一般属性:
M = f = 2^(-n)
,E = -2^(k-1) + 2
。M = f = 1 - 2^(-n)
,E = -2^(k-1) + 2
。(仅仅比最小正规格化值小一点)M = 1+f = 1
,E = -2^(k-1) + 2
。M = 1+f = 1
,E = 0
。M = 1+f = 2 - 2^(-n)
,E = 2^(k-1) - 1
。练习把整数值转换成浮点形式对理解浮点表示非常有用。
a>=b
,对任意x
,x+a >= x+b
。无符号或补码加法不具备这个实数(和整数)加法的属性。a>=b && c >=0 => a*c >= b*c
、a>=b && c<=0 => a*c <= b*c
。无符号或补码的乘法没有这些单调性属性。a!=NaN
,就有a * a >= 0
。无符号或补码的乘法没有这些单调性属性。#define _GNU_SOURCE 1 #include <math.h>
来定义INFINITHY
和NAN
。int
、float
、doublie
的互转:int
转float
:不会溢出,但可能被舍入。int
或float
转double
:能保留精确的数值。double
转float
:可能舍入,可能溢出成无穷。float
或double
转int
:向零舍入,也可能溢出。C标准没对溢出做要求,Intel系微处理器指定位模式[10…00]为整数不确定值(integer indefinite),如(int)+1e10
会得到-21483648
。^
实现两数交换。^
交换同一地址的值时导致的问题。x==y
。从一个 Hello World 程序开始。
shell 是一个命令行解释器。
总线
I/O设备
主存
处理器
指令所对应的简单操作并不多,它们围绕着 主存、寄存器文件(register file) 和 算术/逻辑单元(ALU) 进行。这些操作包括:
现代处理器使用了非常复杂的机制来加速程序的执行。注意区分:
./hello
逐一读入寄存器,再把它放入内存。hello
文件,将目标文件中的代码和数据从磁盘复制到主存。若系统执行某应用程序需要时间为
T-old
,假设系统某部分所需执行时间与该时间的比例为a
,而该部分性能提升比例为k
。由此计算加速比:S = T-old/T-new = 1 / ((1-a) + a/k)
并行可以用在计算机系统的多个抽象层次上:
线程级并发
指令级并行
单指令、多数据并行(SIMD并行)
Libevent is a library for writing fast portable nonblocking IO.
1 | void event_set_log_callback(event_log_cb cb); |
1 | void event_set_fatal_callback(event_fatal_cb cb); |
1 | void event_set_mem_functions( |
1 | // #ifdef WIN32 |
1 | void evthread_enable_lock_debugging(void); |
1 |
|
1 | void libevent_global_shutdown(void); |
EVENT_NOKQUEUE
.event_config_avoid_method()
below.1 | struct event_base *event_base_new(void); |
To avoid specific available backend by name, or by feature.
1 | struct event_config *event_config_new(void); |
1 | const char **event_get_supported_methods(void); |
1 | int event_base_priority_init(struct event_base *base, int n_priorities); // 优先级数量:1 ~ EVENT_MAX_PRIORITIES,但优先级本身是从 0 开始的。这个函数必须在任何事件前调用!事件默认值为 n_priorities / 2 |
default: run until no more events registered in it.
1 | int event_base_loop(struct event_base *base, int flags); |
1 | int event_base_loopexit(struct event_base *base, const struct timeval *tv); // exit after all active events cb done. |
1 | int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv_out); |
1 | void event_base_dump_events(struct event_base *base, FILE *f); |
1 | int event_base_foreach_event(struct event_base *base, event_base_foreach_event_cb fn, void *arg); |
event_dispatch()
event_loop()
event_loopexit()
event_loopbreak()
add
)delete
-> non-pending -> add
-> pending1 | struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg); |
You can’t create an event that receives itself as a cb argument. Instead:
1 | void *event_self_cbarg(); |
no benefit beyond clarifying your code.
1 |
|
1 |
|
1 | int event_assign(struct event *event, struct event_base *base, evutil_socket_t fd, short what, void (*callback)(evutil_socket_t, short, void *), void *arg); |
1 | size_t event_get_struct_event_size(void); |
sizeof(event)
because of padding bytes at the end.event_assign
on an event that is already pending. call event_del()
first.1 | int event_add(struct event *ev, const struct timeval *tv); |
1 | int event_pending(const struct event *ev, short what, struct timeval *tv_out); |
1 | int event_base_once(struct event_base *, evutil_socket_t, short, void (*)(evutil_socket_t, short, void *), void *, const struct timeval *); |
event_base_once
.EV_SIGNAL
or EV_PERSIST
.1 | void event_active(struct event *ev, int what, short ncalls); |
event_active
recursively on the same event.1 | const struct timeval *event_base_init_common_timeout( struct event_base *base, const struct timeval *duration); |
1 | int event_initialized(const struct event *ev); |
evutil_socket_t
ev_uint64_t EV_UINT64_MAX 0
ev_int8_t EV_INT8_MAX EV_INT8_MIN
ev_ssize_t EV_SSIZE_MIN EV_SSIZE_MAX
1 |
|
1 | int evutil_closesocket(evutil_socket_t s); |
1 | ev_int64_t evutil_strtoll(const char *s, char **endptr, int base); |
1 | const char *evutil_inet_ntop(int af, const void *src, char *dst, size_t len); |
1 |
|
<event2/bufferevent.h>
<event2/buffer.h>
When a connection is closed or an error occurs. Flags:
BEV_EVENT_READING
BEV_EVENT_WRITING
BEV_EVENT_ERROR
BEV_EVENT_TIMEOUT
BEV_EVENT_EOF
BEV_EVENT_CONNECTED
By default, bufferevent and evbuffer ‘s callbacks are executed immediately. But it may make trouble.
For example, one callback moves data into evbuffer A when it grows empty, and another one put data out of A when it grows full. You may risk a stack overflow if the dependency grows nasty enough.
To solve this, you can use deferred callbacks. It is queued as part of the event_loop()
, and invoked after the regular events’ callbacks.
BEV_OPT_CLOSE_ON_FREE
: When the bufferevent is freed, close the underlying transport, such as socket.BEV_OPT_THREADSAFE
BEV_OPT_DEFER_CALLBACKS
BEV_OPT_UNLOCK_CALLBACKS
: When threadsafe is set and invoking the user-provided callback, release the lock.1 | struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options); |
evutil_make_socket_nonblocking()
。1 | int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); |
1 | void bufferevent_free(struct bufferevent *bev); |
bufferevent_free
will free the bufferevent as soon as possible, probably won’t flush data on write buffer.BEV_OPT_CLOSE_ON_FREE
will close its transport underlying it - such as socket - when free.1 | struct evbuffer *bufferevent_get_input(struct bufferevent *bufev); |
BEV_EVENT_TIMEOUT|BEV_EVENT_READING
or BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING
.1 | // socket-based only |
1 | void bufferevent_lock(struct bufferevent *bufev); |
BEV_OPT_THREADSAFE
on creation.All bytes written on one are received on the other, without via the network stack.
1 | int bufferevent_pair_new(struct event_base *base, int options, struct bufferevent *pair[2]); |
BEV_OPT_CLOSE_ON_FREE
has no effect.BEV_OPT_DEFER_CALLBACKS
is always on.1 | struct bufferevent *bufferevent_filter_new(struct bufferevent *underlying, bufferevent_filter_cb input_filter, bufferevent_filter_cb output_filter, int options, void (*free_context)(void *), void *ctx); |
1 | int bufferevent_set_max_single_read(struct bufferevent *bev, size_t size); |
algorithm: token bucket.
1 |
|
1 | struct bufferevent_rate_limit_group; |
1 | ev_ssize_t bufferevent_get_read_limit(struct bufferevent *bev); |
1 | int bufferevent_decrement_read_limit(struct bufferevent *bev, ev_ssize_t decr); |
1 | int bufferevent_rate_limit_group_set_min_share( struct bufferevent_rate_limit_group *group, size_t min_share); |
1 | enum bufferevent_ssl_state { |
1 | struct evbuffer *evbuffer_new(void); |
1 | int evbuffer_enable_locking(struct evbuffer *buf, void *lock); // NULL is legal, it will allocate a new lock. |
1 | size_t evbuffer_get_length(const struct evbuffer *buf); |
1 | int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen); |
1 | int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src); |
1 | int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size); |
1 | unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size); |
1 | int evbuffer_drain(struct evbuffer *buf, size_t len); |
1 | ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data, size_t datlen); |
1 | char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style); |
1 | struct evbuffer_ptr evbuffer_search(struct evbuffer *buffer, const char *what, size_t len, const struct evbuffer_ptr *start); |
1 | int evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len, struct evbuffer_ptr *start_at, struct evbuffer_iovec *vec_out, int n_vec); |
1 | int evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size, struct evbuffer_iovec *vec, int n_vecs); |
1 | int evbuffer_write(struct evbuffer *buffer, evutil_socket_t fd); |
1 | struct evbuffer_cb_entry; |
1 | int evbuffer_add_reference(struct evbuffer *outbuf, const void *data, size_t datlen, evbuffer_ref_cleanup_cb cleanupfn, void *extra); |
1 | int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset, size_t length); |
1 | struct evbuffer_file_segment; |
1 | int evbuffer_add_buffer_reference(struct evbuffer *outbuf, struct evbuffer *inbuf); |
1 | int evbuffer_freeze(struct evbuffer *buf, int at_front); |
1 | struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd); |
flag:
LEV_OPT_LEAVE_SOCKETS_BLOCKING
LEV_OPT_CLOSE_ON_FREE
LEV_OPT_CLOSE_ON_EXEC
LEV_OPT_REUSEABLE
LEV_OPT_THREADSAFE
LEV_OPT_DISABLED
LEV_OPT_DEFERRED_ACCEPT
初始化(initialization)。
default 构造函数是一个可被调用而不带任何实参的函数,或者每个参数都有缺省值。
explicit
修饰的构造函数不能执行隐式类型转换。如:1 | class B { |
1 | class Widget { |
1 | int *p = 0; |
C++ 现在已经是一个多重范型编程语言(multiparadigm programming language),同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)。
可将 C++ 视为由以下几个次语言构成:
使用 const 替换 #define 的好处:
注意两点:
1 | const char* const name = "Scott"; |
1 | class GamePlayer{ |
C++ 通常要求对使用的任何东西提供一个定义式,这里类的 static 的整数类型(integral type: int, char, bool)只要不取他们的地址就可以不用声明式。但如果需要取地址或编译器坚持需要定义式,就必须提供定义式。并且这种“in-class 初值设定”也只允许对整数常量进行。
旧时编译器可能要将初值设定放在定义式中,如果编译期间一定要知道该常量值,比如这里 scores 数组的声明,可以使用“the enum hack”补偿做法,即enum { NumTurns = 5 };
。
这样做的好处有:
另外,#define 的另一个误用情况是用来实现宏(macros),缺点很多。应使用 template inline 函数,可带来宏的效率以及一般函数的所有可预料行为和类型安全性(type safety)。
1 | template<typename T> |
const 的一般用法:
1 | const char* p; // 被指物是常量,同 char const * p; |
const 在函数的返回值、参数、成员函数自身都有很强大的用法。
1 | class Rational { ... }; |
C++的一个重要特性:两个成员函数如果只是常量性(constness)不同,可以被重载。
1 | class TextBlock { |
成员函数如果是 const 意味着什么?
1 | class TextBlock { |
1 | class TextBlock { |
解决方法是把textLength
和lengthIsValid
声明为mutable。
对于第一个 TextBlock 例子,还有个问题,如果函数体很长,如还包括边界检验(bounds checking)、日志访问信息(logged access info)、数据完善性检验等,会有大量的重复代码。
这时可利用常量性转除(casting away constness),即便一般来说使用 casting 是一个糟糕的想法。
1 | const char& TextBook::operator[] (std::size_t postion) const { |
先转为 const TextBlock& 再将 const 属性移除。
初始化(initialization)和赋值(assignment)是两个概念。
请对类的所有成员变量使用 member initialization list 方式进行初始化。
如果是有多个构造函数,为避免代码冗长,可将性能差不多的初始化封装到函数中转变成赋值函数,实现伪初始化(pseudo-initialization)。
初始化的顺序是固定的,按照声明次序,而不是初始化列表的次序。
对于不同编译单元内定义的 non-local static 对象(包括 global 对象、定义于 namespace 作用域内的对象、在 classes 内、在函数内、在 file 作用域内被声明为 static 的对象),C++ 并无明确定义。为应对这种情况,应以 local static 对象替换 non-local static 对象,这也是 Singleton 模式的思想。
1 | class Empty { |
这些函数惟有在被需要的时候才会创建。
注意析构函数是 non-virtual 的,除非基类有 virtual 析构函数。
默认的 copy 构造函数:使用调用相应成员类型的 copy 构造函数,或者通过拷贝每一个 bits 来对每个成员变量进行初始化。
默认的 copy assignment:同上,但若生成的代码不合法或者没有机会证明它有意义,则不会生成。
1 | template<class T> |
定义为 private,可以阻止人们调用它。(编译器报错)
为了进一步阻止成员函数和 friend 函数调用,应只声明而不去定义这些函数。(连接器报错)
为了方便使用,可以让它继承这样的 Uncopyable 基类:
1 | class Uncopyable { |
并且,使用时不一定要用 public 继承它,析构函数不一定得是 virtual。可能导致多重继承,多重继承有时会阻止这种 empty base class optimization。
Boost 库里也有相关的版本,叫 noncopyable。
基类指针指向派生类,只有在析构函数为虚函数的情况下才能销毁整个派生类对象。
但也不能因此把所有类的析构函数都声明成 virtual,由于这份信息由 vptr(virtual table pointer)指向的 vtbl(virtual table)维护,这会增大每个对象的大小。许多人的心得是:只有当 class 内含有至少一个 virtual 函数,才为它声明 virtual destructor。
注意:string 类的析构函数是 non-virtual 的。
进一步的,可将析构函数声明为 pure virtual 以实现抽象类。注意提供一份定义,因为编译器会在派生类的析构函数中创建对 ~AWOV 的调用。
1 | class AWOV { |
注意,给 base classes 一个 virtual 析构函数,这个规则只适用于 polymorphic(带多态性质的)base classes 身上。因为有些基类的设计并不是为了多态,甚至不是为了继承。
不鼓励在析构函数中抛出异常,会导致程序过早结束或不明确行为。
所以可以将这些逻辑从析构函数转移到普通函数,交由用户进行调用。
若析构函数必须处理异常,则要在捕捉到异常后吞下不传播或者结束程序。
在基类构造期间,virtual 函数不是 virtual 函数。
构造函数中调用的函数里调用了 virtual functions 的情况更加难以发现。
以支持连锁赋值。
三种方式:
1 | if (this == &rhs) |
1 | Widget& Widget::operator=(const Widget& rhs){ |
1 | Widget& Widget::operator=(const Widget& rhs){ |
这种方式可以以 by value 的方式进行优化,牺牲清晰性,却可令编译器有时生成更高效的代码。
1 | Widget& Widget::operator=(Widget rhs){ |
当为一个类编写 copy constructor 或者 copy assignment 时,请确保:
当然,不能在 copy assignment 操作符中调用 copy 构造函数。
反之同样无意义。
如果重复代码较多,不妨建立一个 init 成员函数。
把资源放进对象内,在析构函数中确保资源被释放。
对于单一区块或函数内的资源,应该在控制流离开那个区块或函数时被释放,可以利用标准库中的auto_ptr
,即智能指针,其析构函数自动对其所指对象调用 delete。如:
1 | void f(){ |
体现的思想:
注意:使用auto_ptr
时不要让多个该指针指向同一对象,为此,它有个性质:若通过 copy constructor 或 copy assignment 复制它们,它们会变成 null。
这意味着auto_ptr
并非管理动态分配资源的神兵利器,例如,STL 容器要求其元素发挥正常的复制行为,所以这些容器容不得auto_ptr
。
它的一个替代方案是引用计数型智慧指针(reference-counting smart pointer,RCSP)。追踪有多少对象指向该资源,实现类似垃圾回收(garbage collection)的行为,不同的是无法打破环状引用(cycles of references)。它的一种实现就是 TR1 的tr1::shared_ptr。
注意,这两个方案都是在其析构函数中做 delete 而不是 delete[]。因此不能用在动态分配而得的 array 身上,即便它能通过编译:
1 | std::auto_ptr<std::string> aps(new std::string[10]); |
这种时候推荐用 vector 和 string。
有时你需要简历自己的资源管理类,一个例子:
1 | class Lock { |
这很好,但如果Lock对象被复制(一个RAII对象被复制),会发生什么事?可能的选择:
tr1::shared_ptr
类型,但由于引用次数为0时默认执行的是释放操作而不是 unlock,需要在构造时为它指定第二个参数:删除器(deleter)。1 | class Lock { |
例如你使用auto_ptr
或tr1::shared_ptr
对某类对象进行管理,而 API 要求使用该对象的原始指针。这时可用 get() 成员函数进行显式转换,获得智能指针内部的原始指针(的复件)。当然,这两个类也实现了operator*
和operator->
,用于隐式转换。
如果是自己写的类,也可提供类似 get() 的接口。也最好提供隐式转换的接口,如从auto_ptr<Font>
转为FontHandle
:
1 | class Font{ |
但是这样会增加出错的情况:
1 | Font f1(getFont()); |
所以不太推荐,要权衡。
RAII 类暴露原始资源与“封装性”并不矛盾,它的存在并不是为了封装而是为了确保一个特殊行为——资源释放——会放生。
new 用 delete,new …[x] 用 delete[],混用会产生未定义的行为。
尽量避免对数组类型进行 typedef。
以独立语句将新对象置于智能指针,否则有资源泄漏的风险。如:
1 | processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()); |
因为 C++ 编译器未必是在 shared_ptr 将新对象添加进来之前执行 priority(),若这时 priority() 出错,新资源的指针会遗失。
更高效,且可避免切割问题(slicing problem)。
你可以合理假设“pass-by-value 并不昂贵”的唯一对象就是内置类型、STL 迭代器、函数对象。
比如operator*
。
绝不要返回一个指向 local stack 上对象的 pointer 或 reference,或返回 reference 指向一个 heap-allocated 对象,或返回 pointer 或 reference 指向一个 local static 对象而有可能同时需要多个这样的对象。
可以增加封装性、包裹弹性(packaging flexibility)、机能扩充性。
比如一些便利函数:
1 | class WebBrowser{ |
另外,如果有很多这样的便利函数,可以按照机能划分到不同的头文件、不同的 namespace 下。
比如想要创建一个实数类,让它既能实现“Rational int”,又想让它能够实现“int Rational”的运算(内含隐性转换),就需要:
1 | class Rational { |
注意,不用把它声明为 friend,friend 能避免就避免。
(有点绕)
尽可能延后变量定义式的出现。
两种旧式转型:
1 | (T) expr; |
四种新式转型:
const_cast<T>(expr)
。常量性转除(cast away the constness)。dynamic_cast<T>(expr)
。安全向下转型(safe downcasting)。执行速度很慢。reinterpret_cast<T>(expr)
。低级转换,行为取决于编译器(不可抑制),很少用。static_cast<T>(expr)
。强迫隐式转换,包括:
唯一使用旧时转型的时机:explicit 构造函数。
1 | class Widget { |
转型可能会做很多事,甚至在基类指针转化为派生类指针时会改变指针的地址。
dynamic_cast 一般用于将基类指针转为派生类指针,然后执行派生类中才有的函数。但尽量用派生类的指针类型或在基类中声明 virtual 来避免使用它。
如果转型是必要的,把它隐匿在函数中,不要交给客户。
避免返回指向对象内部某对象的 handles(包括 references、pointers、iterators),会破坏封装性。
如果是返回 const 的对象引用,记得用两个 const,即便如此,也存在着“handle 比其所指对象更长寿”的风险。
异常安全性的两个条件:
异常安全函数(Exception-safe function)提供以下三个保证之一:
(有个例子没仔细看)
支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。可以使用 Handle classes 和 Interface classes。
程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及 templates 都适用。
一个例子:
1 | class Person { |
public 继承意味着“is-a”,适用于 base classes 身上的每一件事一定也适用于 derived classes 身上。
virtual 的替代方案:
1 | class Shape { |
1 | class Shape { |
复合(composition),同义词有分层(layering)、内含(containment)、聚合(aggregation)、内嵌(embedding)等。它的意义(has-a)和 public 继承(is-a)完全不同。
一个正确的使用例子:
1 | template<typename IterT> |
当我们从 Object Oriented C++ 跨进 Template C++ 后,继承就没有那样畅行无阻了。
1 | template<typename Company> |
三种解决方式:
this->
。1 | ## List Docker CLI commands |
1 | docker build -t friendlyhello . # Create image using this directory's Dockerfile |
1 | # Use an official Python runtime as a parent image |
1 | docker stack ls # List stacks or apps |
1 | docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux) |
1 | # Use an official Python runtime as a parent image |
1 | version: "3" |
Keep images small
RUN
command.Persist data
Use swarm services when possible
docker stack deploy
instead of docker pull
.1 | docker build -f /path/to/a/Dockerfile -t shykes/myapp:1.0.2 -t shykes/myapp:latest . |
#
.# escape=\
. ENV
${var}
${var:-word}
${var:+word}
1 | ENV abc=hello |
1 | # comment |
1 | ARG CODE_VERSION=latest |
ARG
is the only instruction that may precede FROM
.FROM
can appear multiple times.2 forms:
RUN <command>
, it will run in a shell which by default is /bin/sh -c
or cmd /S /C
.RUN ["executable", "param1", "param2"]
.note: it is the shell that is doing the environment variable expansion, not docker. so:
1 | RUN [ "echo", "$HOME" ] # incorrect |
3 forms:
CMD ["executable","param1","param2"]
(exec form, this is the preferred form)CMD ["param1","param2"]
(as default parameters to ENTRYPOINT)CMD command param1 param2
(shell form)Note:
CMD
allowed, or, the last will take effect.docker run
then they will override the default specified in CMD
.CMD
does not execute anything at build time.1 | LABEL <key>=<value> <key>=<value> <key>=<value> ... |
Adds metadata to an image, such as MAINTAINER
.
1 | EXPOSE <port> [<port>/<protocol>...] |
Override at runtime:
1 | docker run -p 80:80/tcp -p 80:80/udp ... |
1 | ENV <key> <value> |
To set a value for a single command, use RUN <key>=<value> <command>
.
2 forms:
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
(this form is required for paths containing whitespace)note:
<dest>
is an absolute path, or a path relative to WORKDIR
.<user>:<group>
will be 0
by default. Setting them through UID/GID
or username/groupname
is OK.username/groupname
require the base image contain /etc/passwd
and /etc/group
.Almost same as ADD
, but:
ADD
allows <src>
to be an URL<src>
parameter of ADD
is an archive in a recognised compression format, it will be unpacked2 forms:
ENTRYPOINT ["executable", "param1", "param2"]
(exec form, preferred)ENTRYPOINT command param1 param2
(shell form)note:
CMD
, ENTRYPOINT
won’t be replaced by command line arguments, those arguments will be appended after all elements in an exec form ENTRYPOINT
.ENTRYPOINT
instruction using the docker run --entrypoint flag
.ENTRYPOINT
can prevent CMD
or command line arguments from being used, however, container’s PID 1
will be /bin/sh
rather than the executable. So the executable won’t receive the SIGTERM
signal from docker stop <container>
.CMD
allowed, or, the last will take effect.1 | brew doctor |
1 | nvm ls-remote |
1 | hexo new "Post Title" |
为了保持 macOS 系统环境的整洁,养成良好、高效的软件管理和使用习惯,决定在过年之际重装一次 macOS。日后在 Mac 使用过程中保持以下几个原则:
官方文档:https://support.apple.com/zh-cn/HT204904
大致步骤就是:
⌘R
开机进恢复模式。值得一提的是,系统安装过程中,利用无线网 -> 加入其他网络可以让菜单栏焦点回到桌面上,然后打开终端,这时可以使用一些辅助命令,如:
1 | df -h # 查看硬盘宗卷信息 |
重装后,正常设置系统、iCloud 账户。
1 | git config --global user.name "Meng Fanze" |
https://github.com/robbyrussell/oh-my-zsh/
https://github.com/zsh-users/zsh-autosuggestions/
配置文件恢复:.zshrc
、.zsh_history
https://github.com/Homebrew/brew/
拷贝控制操作(copy control):
第一个参数是自身类型的引用(且几乎总是const
、不是explicit
的),任何额外参数都有默认值。
如未定义,会有合成版本(即使定义了其他构造函数),依次拷贝类的非静态成员,包括数组。
1 | string dots(10, '.'); // 直接初始化 |
拷贝初始化也可能是由移动构造函数完成。
拷贝初始化时机:
=
定义对象。insert
等。注意拷贝构造函数是不是explicit
的,如:
1 | vector<int> v1(10); // 正确 |
编译器可能绕过拷贝/移动构造函数,而直接创建对象。(但拷贝/移动构造函数依然需要是存在且可访问的)
如果未定义,会有合成版本,会将右侧运算对象的每个非静态成员赋予左侧运算对象的相应成员,包括数组。
赋值运算符通常应该返回一个指向其左侧运算对象的引用。
调用时机:
delete
进行销毁。显式要求编译器生成合成的版本,只能对编译器可以合成的默认构造函数或拷贝控制成员使用。
在类内使用将声明为内联的,如果不希望这样,应只在类外定义使用。
使用= delete
,必须出现在第一次声明的时候,可以对任何函数使用,除了析构函数(会造成不能释放这些对象)。
合成的拷贝控制成员可能是删除的:
const
或引用。const
或引用。使用private
阻止拷贝的缺点是:友元和成员函数依然能够拷贝。对此可以只声明不定义,这样在试图拷贝时会报链接错误。
(实例)
1 | inline void swap(HasPtr &lhs, HasPtr &rhs) |
注意这里调用的是swap
,而不是std::swap
,是想让在用户版本和 std 版本中自动匹配合适的函数。
有了交换操作后,可以用它更简单的定义赋值运算符,即拷贝并交换(copy and swap)。它是异常安全的,且能正确处理自赋值。
1 | HasPtr& HasPtr::operator=(HasPtr rhs) |
(实例)
(实例)
引出移动。
很多情况会发生对象拷贝,某些时候对象拷贝后就立刻被销毁了。使用移动可以大幅度提升性能。
可以使用<utility>
中的std::move
将一个左值显式地转换为对应的右值引用类型。告诉编译器:我有一个左值,但我希望像一个右值一样处理它,承诺不再使用它的值,除了对其赋值和销毁,因为移后源对象(moved-from)依然是一个有效的、可析构的状态,但不能对它的值做任何假设。
类似拷贝构造函数,只是第一个参数是右值引用类型。
注意确保移动后的对象处在销毁它是无害的的状态,如将指针置为nullptr
。
移动构造函数一般是noexcept
的,由于它窃取资源而不分配资源。该关键字写在定义和声明的参数列表后,分号和冒号前。声明和定义都必须指定。
如果容器的元素是我们的自定义类型,如果不告诉编译器我们的移动构造函数是noexcept
的,在进行一些操作时,如对vector
进行需要增大内存分配的push_back
操作,容器会不敢使用移动构造函数而是使用拷贝构造函数,因为它需要对异常发生时自身的行为提供保障:当异常发生时,vector 自身不会发生变化。
移动赋值运算符与析构函数和移动构造函数执行相同的工作。与移动构造函数一样,如果不抛异常就该标记为noexcept
。
与拷贝不同,编译器根本不会为某些类合成移动操作。特别是当一个类定义了自己的拷贝函数、拷贝赋值运算符或者析构函数。没有移动构造函数后,根据正常的函数匹配,类会使用对应的拷贝操作来代替移动操作。
只有当一个类没有定义任何自己版本的拷贝控制成员,且类的每个非 static 数据成员都可以移动时,才会合成。
可以移动的成员包括:内置类型可以移动,有移动操作的类类型成员也能移动。
与拷贝不同,移动操作永远不会隐式定义为删除的函数。但是,如果我们使用=default
,且编译器不能移动所有成员,它会是删除的。例外是:
(一二三四,我选择不依赖合成而是自己定义= =)
定义了一个移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作。否则,这些成员默认地被定义为删除的。
如果一个类既有移动构造函数,也有拷贝构造函数。则移动右值,拷贝左值。但如果没有移动构造函数,右值也被拷贝。
引入移动操作后,13.3中的拷贝并交换赋值运算符在定义了移动构造函数之后会兼容移动操作。单一的赋值运算符就实现了拷贝赋值运算符和移动赋值运算符两种功能。
更新三/五法则:所有五个拷贝控制成员应该看作一个整体:如果定义了任何一个拷贝操作,就应该定义五个。这些类通常拥有一个资源。
移动迭代器:通过解引用得到返回一个指向元素的右值引用。使用make_move_iterator
函数。例子:
1 | auto first = alloc.allocate(newcapacity); |
如果一个成员函数同时提供拷贝和移动版本,则一般使用与拷贝/移动构造函数和赋值运算符相同的参数模式:一个版本接受一个指向const
的左值引用,一个版本接受一个指向非const
的右值引用。
一般来说,我们不需要为函数操作定义接受一个const X&&
或一个普通的X&
参数的版本。
通常,我们在一个对象上调用成员函数,而不管对象是一个左值还是一个右值:
1 | string s1= "xxxx", s2="xxxx"; |
在旧标准中,我们没法阻止这种使用方式,为了向后兼容性,新标准依然允许。但是有方法进行阻止:限定this
的左值/右值属性,像限制其const
属性那样在参数列表后放置&
或&&
。如果需要同时放置const
,const
在前。需要同时在声明和定义中指定。
重载方式:
1 | class Foo{ |
注意:定义const
成员函数时,可以定义两个版本:差别是有没有const
。但引用限定的函数不一样,必须对所有函数都加上引用限定符,或者所有都不加。