杂谈
自己长期以来,在开发软件的时候总是感觉找不着北,甚有如履薄冰之感。在拜读了
miloyip大大的从零开始的 JSON 库教程之后,有了新的感悟。首先,自己就如同文章中所言,在进行测试或者debug的时候,都只会以printf
/cout
来打印结果,再用肉眼对比结果是否符合预期。但是随着软件项目变得越来越复杂,这种做法就会显得捉襟见肘。在这种情况下,我们便会采用自动的测试方式,例如单元测试(unit testing)。单元测试也能够保证其他人修改代码之后,原来的功能依旧正确(这称为回归测试(regression testing))。
一般而言,软件开发是以周期为单位进行的。例如,加入一个功能之后,在写关于该功能的单元测试。与此同时,还有另一种软件开发方法论,称为测试驱动开发(test-driven development,TDD),它的主要循环步骤是:
- 加入一个测试
- 运行所有测试,新的测试应该会失败
- 编写实现代码
- 运行所有测试,若有测试失败就回到步骤3
- 重构代码
- 回到步骤1
TDD是先写测试,在实现功能。它的好处是:实现只会刚好满足测试,而不会写一些无关紧要的代码,或是没有被测试的代码。
但无论是采用TDD,还是先实现后测试,都应该尽量加入足够覆盖率的单元测试。
此外,TDD中另一个重要的步骤就是重构(refactoring)。重构的过程是:
在不改变代码外在行为的情况下,对代码作出修改,以改进程序的内部结构。
在TDD的过程中,我们的目标是编写代码以使得程序能够通过测试。但这个目标的诱导性过强,极有可能导致我们忽略正确性以外的其他软件品质。在通过代码之后,代码的正确性得以保证,此时,我们便应该审视现有的代码,看看有没有地方可以改进,而同时能保证测试顺利通过。我们可以安心地做各种修改,因为我们有单元测试,可以判断代码在修改后是否影响原来的行为。
位操作(Bit Twidding Hacks)
1. Compute the sign of an integer
int v; //试图找出v的sign
int sign;
// CHAR_BIT是每一个byte的位数(一般为8)
sign=-(v<0);
// 或者,为了避免CPU的标志寄存器(flag registers)的branching
sign=-(int)((unsigned int)((int)v)>>(sizeof(int)*CHAR_BIT-1));
// 或者使用更少的instruction(但并不通用)
sign=v>>(sizeof(int)*CHAR_BIT-1);
最后一个表达式执行的运算(32位的integers)为:sign=v>>31
。但它比常规的方法sign=-(v<0)
要快。这个trick之所以能够正确运行,是因为当signed integers向右移位时(shift right),最左边的bit的值被复制到了其他位。当v
为负时,最左边的位为1
,否则为0
。但是,这种操作只适用于特定架构的CPU。
此外,如果想要使得sign
的结果为1
或者-1
,则需要进行一下操作:
sign=+1|(v>>sizeof(int)*CHAR_BIT-1)); // 如果v<0,则返回-1,否则返回+1
【注】:当v
为正数时,右移31
位之后,高位补0
,将得到1
,再与1
进行或运算,将得到1
;当v
为负数时,右移31
位之后,高位补1
,得到0xFFFFFFFF
。
因为int
为带符号类型,带符号类型的最高位时符号位,又因为0xFFFFFFFF
的符号位是1
,所以这个数是负数。
在内存中,数值均为补码表示,所以0xFFFFFFFF
是一个负数的补码。负数从补码求原码:最高符号位不变,其余各位求反,末尾加1。所以,0xFFFFFFFF
的原码就是10000000 00000000 00000000 00000001
,即-1
。
此外,如果想要的结果是-1
、0
和1
,则需要这样使用:
sign=(v!=0)|-(int)((unsigned int)((int)v)>>(sizeof(int)*CHAR_BIT-1));
// 或者采用以下这种速度更快,但不通用的方式
sign=(v!=0)|(v>>(sizeof(int)*CHAR_BIT-1));
// 或者为了更加简洁、通用
sign=(v>0)-(v<0);
如果试图知道某个数是否为非负数,并返回结果+1
和0
,则需要这样写:
sign=1^((unsigned int)v>>(sizeof(int)*CHAR_BIT-1));
Detect if two integers have opposite signs
int x,y;
bool f=((x^y)<0); //如果x和y符号位不一致,就返回true
Compute the integer absolute value (abs) without branching
int v;
unsigned int ret;
int const mask=v>>sizeof(int)*CHAR_BIT-1;
r=(v+mask)^mask;