前言
我非常喜欢一些奇技淫巧,每次有机会时就想用点小技巧,既方便了自己、有时还能博来他人的赞叹,实属一举两得的行为。在这篇文章之前,我的奇技淫巧仅局限于高中数学题。接下来我把进入 C++ 编程以来收集的实用小技巧全部放在这里,并不定时更新新的小技巧。希望能帮到后人。
引用伟大的毛主席的一句话:
幸亏古人和外国人替我们造好了这许多符号,使我们开起中药铺来毫不费力。
毛泽东 ——《反对党八股》
当然这句话在这里已经失去了原先的讽刺意味,我们会着重探讨使用这些小技巧的好处而非坏处。毕竟它的正确性就摆在那里,自己写还可能出现错误,为什么不保险一些呢?
GCC 内建函数的巧用
在位运算中,很多人为了加快运行效率图方便,会使用以双下划线+“builtin”开头的
GCC 内建函数。其中最为常见的是 __builtin_popcount()
函数。接下来介绍几种实用的内建函数。
注意:赛时的机器可能并不支持使用下划线开头的库函数,如果不想吃 CE 的话就还是老老实实的写一遍。
popcount 系列函数
一般来说,这种内建函数针对于不同类型的参数会设计不同的函数签名。一般来说,在
__builtin_popcount
后直接加上数据类型的简写就可以得到针对这种数据类型的函数。目前仅支持
int
、long
和 long long
类型,除
int
类型外,函数签名分别追加 l
和
ll
。对于这个函数就是 __builtin_popcountl()
和
__builtin_popcountll()
。后文不再对这点进行赘述。
1 | int popcount(int x) { |
GCC 在实现这些函数时自动将函数设为内联
inline,因此通常情况下会效率更高。主要还是不用自己写
例子:
1 | __builtin_popcount(5) // 返回2,因为十进制的5等于二进制的101 |
值得注意的是,GCC 并没有计算二进制下 b & 1
的判断条件取反即可。
clz、ctz 系列函数
__builtin_clz()
用来获取二进制表示下前导 __builtin_ctz()
则是二进制末尾 ctz
函数。
1 | int clz(int x) { |
例子:
1 | __builtin_clz(7) // 29 |
ffs 系列函数
__builtin_ffs()
函数用来求得二进制位下第一个非零位的下标(从低位到高位,下标从
1 | int ffs(int x) { |
例子:
1 | __builtin_ffs(14) // 2 |
parity 系列函数
__builtin_parity()
函数用来求解二进制下
1 | int parity(int x) { |
例子:
1 | __builtin_parity(26) // 1 |
系统宏定义的巧用
INFINITY 宏
考虑这样一个情境:你在赛时写出了一个最短路代码,由于害怕数据爆
int
,你选择改用 long long
,并把最短路的 int
,反而发现开一个过大的 long long
数组可能会爆空间。你又将它改成了 int
类型。比赛结束后,你在场外忽然想起初始化还是一个爆 int
的值,果不其然喜提零分评测……
如何避免像上边那样写出一个爆类型的极大值,我的建议是使用 __builtin_inff()
函数(其实本来应该放到上边那一栏的)。它的具体值会随数据类型的改变而改变,例如:
1 | int a = INFINITY; |
可见使用这种方法可以优雅地避开上述情景中的窘境,助力 int
和 long long
),
库函数的巧用
__gcd 函数
在数学题中经常需要我们去求两个数的最大公约数,一般来说我们会写欧几里得算法(辗转相除法):
1 | int gcd(int a, int b) { |
库函数中 __gcd()
也可以实现这一功能,它要求传入的两个参数是同一数据类型。这一点与
min()
、max()
函数是相同的。
如果想要求解最小公倍数,则只需用两个数的乘积除以它们的最大公约数即可。