OI程序常见的设计陷阱
宏定义的问题
有时候为了方便,我会大量使用宏定义。但是最近我发现下面这两个宏定义老是出问题:
1 #define SET(x,a) memset(x,a,sizeof(x)) 2 inline void work(){ 3 SET(head,0),SET(vis,0),SET(dis,0x3f); 4 //do something 5 }
这个宏定义似乎在初始化的时候会莫名奇妙地出现一些问题。
另外,还有这个:
1 #define RP(i,a,b) for(register int i=a; i<=b; i++) 2 inline void work(){ 3 vector<int> ver; 4 //do something 5 RP(i,0,ver.size()-1){ 6 //do something 7 } 8 }
当你定义了一个 RP 的循环宏时,它的判断会出一些问题。比如说在上面这个例子,如果ver.size()==0,那么正常的for就不会进入这个循环。但是宏定义之后它的判断顺序似乎发生了改变。
很奇怪吧?因此,有些宏还是不要乱用。
另外,宏和函数不同。它相当于是让编译器对源码进行自动替换,而宏的语法如果不加注意,则会出错。举个例子,如果你想把二维坐标映射到一个整数,用宏ID(a,b) = a * M + b 来实现,那么这个语法肯定有问题。假设坐标从0开始,你想得到右下角ID(M - 1, M - 1),那这个宏会把它变成M - 1 * M - M - 1 = -M - 1。正确的语法应该为ID(a,b) = (a) * M + (b)。
不要乱卡常
网上有一些奇奇怪怪的卡常方式,比如说把i++写成i=-~i,或者用大量的逗号链接若干个语句。对于前者,这个二进制优化其实并不如想象中的那么出色,对速度的提升不明显。(虽然我本人也喜欢把i++写成++i以提高速度)对于后者,虽然逗号运算符在一定程度上可以提高效率,但有些时候会出现一些奇怪的问题。我记得很久以前,我喜欢把很多语句用若干个逗号连成一条;但是有时候,程序可能只会其中的部分几条,剩下的会被忽略!我不知道是否真的有这个问题,但是我的建议还是不要乱用逗号。
注意空间复杂度
在学习任何一个算法或数据结构时,一定要翔实记录它的空间复杂度。比如说,trie树的空间复杂度是多少?一般要开多大?用链式前向星存无向图,是否把边数组开成题给的两倍?这些问题不注意,你也许可以通过样例,但最后可能一分都拿不到。如果你是在网上评测,有时候评测机并不会反馈RE的信息,而是WA。如果不加注意,你会因为这个问题而调试很久。
另外,足够大的空间也可以从一定程度上提高程序的运行速度。这应该和“空间大易于伸展手脚”是一个道理。
注意类型转换
始终注意类型转换。有时候即便你把所有的数值变量开成long long类型,你可能会在赋值的时候忘记把一个int类型的表达式进行转换。这样,表达式在被赋值前就会发生溢出。任何时候要牢记各种变量的类型,并时不时转换一下。
另外,强制转换的速度会相当慢。如果你想转换成long long类型,直接在变量前乘上1ll;对于普通double类型,可以直接乘上1.0;对于更特殊的long double或是__int128类型,你就只能强制转换了。
慎用STL
STL会节省很多时间,而且大部分时候它们除了常数大以外,由于封装了很多函数,使得它们操作起来非常方便。但是有些时候,用STL很有可能会RE。当然,这是否需要“释放内存”,或者一些更高级的处理方法,这我不得而知。
学会设计参数
不久前在写矩阵快速幂的时候,发现“引用形参”比不引用形参要快得多。因此,当函数的参数是一个比较大的数据结构时,采用引用的写法会加速很多。