引言


最近几天在写普通平衡树这一题时,我没有使用我平常经常使用的algorithm中的min与max函数(平常使用主要是因为懒得手打这样使用比较标准),而是使用了#define宏定义的min与max函数,我认为这样应该能加快一些速度,所以在我的代码疯狂TLE时我并没有注意到这一点。在我接近debug到崩溃时,我把所有的预处理命令(本来这里想写头文件后来发现define的名字并不叫头文件)都重打了一遍,再次提交时,发现竟然通过了这道题。我观察了这些预处理命令,发现他们唯一的不同就是我把define宏定义函数改成了algorithm库。在我的一脸蒙蔽之中,我测试了各种min,max函数的性能 。

这是我在这一题中会使用min/max函数的函数:

int lower(int now,int x) {if(!now) return -2147483646;if(bt[now].num<x) return max(bt[now].num,lower(bt[now].s[1],x));return lower(bt[now].s[0],x);
}int upper(int now,int x) {if(!now) return 2147483647;if(bt[now].num>x) return min(bt[now].num,upper(bt[now].s[0],x));return upper(bt[now].s[1],x);
}

define宏定义的函数为:

#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))

测试


注:测试在洛谷在线IDE(C++无O2优化)上进行
我写了下面几行代码来测试性能,min和max交替进行。

int main() {int n=1e7;int minx=n,maxx=0;for(int i=1;i<=n;++i) {minx=min(minx,n-i);maxx=max(maxx,i);}return 0;
}
algorithm库 define宏定义函数 手敲函数(非内连,内部使用三目运算符)
60-80ms 20-30ms 60ms

结果显示宏定义函数明显比其他的要快,那为什么我的程序会因为宏定义函数TLE呢?

考虑到我写的题中的min/max中有函数作为参数,所以我又写了下面一个程序,来测试min/max中有函数时的性能。

int n=1e7;
int test(int i,int type) {return type==0?n-i:i;
}
int main() {int minx=n,maxx=0;for(int i=1;i<=n;++i) {minx=min(minx,test(i,0));maxx=max(maxx,test(i,1));}return 0;
}
algorithm库 define宏定义函数 手敲函数
92ms 100ms 88ms

在我多次测试后,发现define宏定义函数总是最慢的。但是一次慢几ms,对于n≤100000的普通平衡树来说应该也不会让本可以AC的代码TLE。考虑到普通平衡树一题中我在查询前驱/后继时的max/min中使用了递归函数,我再次写了一段代码进行测试。

int n=25;
int test(int i,int type,int I) {if(!i) return type==0?n-I:I;return max(type==0?n-i:i,test(i-1,type,I));
}
int main() {int minx=n,maxx=0;for(int i=1;i<=n;++i) {minx=min(minx,test(i,0,i));maxx=max(maxx,test(i,1,i));}return 0;
}

由于n=1e7时对于define运行时间过长,所以我改成了25(这差距好像有点大)。

algorithm库 define宏定义函数 手敲函数
0ms 1020ms 0ms

这样的情况下差距就十分明显了,我也知道了为什么我的代码会TLE,但是为什么会导致这样呢?我找到了define的工作原理。

资料


我翻阅了 C++ Primer,3e ,在其中找到了答案。(C++ Primer,5e 好像已经把宏定义函数这一部分删除了)

有时候强类型语言对于实现相对简单的函数似乎是个障碍,例如虽下面
的函数 min()的算法很简单,但是强类型语言要求我们为所有希望比较的
类型都实现一个实例int min( int a, int b ) {return a < b ? a : b;
}
double min( double a, double b ) {return a < b ? a : b;
}有一种方法可替代这种为每个 min()实例都显式定义一个函数的方法,
这种方法很有吸引力,但是也很危险,那就是用预处理器的宏扩展设
施例如#define min(a,b) ((a) < (b) ? (a) : (b))虽然该定义对于简单的 min()调用都能正常工作,如min(10,20);
min(10.0,20.0);但是在复杂调用下它的行为是不可预期的,这是因为它的机制并不像函数
调用那样工作,只是简单地提供参数的替换,结果是它的两个参数值都被
计算两次,一次是在a和b的测试中,另一次是在宏的返回值被计算期间,
例如#include <iostream>
#define min(a,b) ((a) < (b) ? (a) : (b))
const int size = 10;
int ia[size];
int main() {int elem_cnt = 0;int *p = &ia[0];// 计数数组元素的个数while ( min(p++,&ia[size]) != &ia[size] )++elem_cnt;cout << "elem_cnt : " << elem_cnt<< "\texpecting: " << size << endl;return 0;
}这个程序给出了计算整型数组ia的元素个数的一种明显绕弯的的方法。
min()的宏扩展在这种情况下会失败,因为应用在指针实参p上的后置
递增操作随每次扩展而被应用了两次,执行该程序的结果是下面不正
确的计算结果
elem_cnt:5 expecting:10

其中

它的两个参数值都被计算两次,一次是在a和b的测试中,另一次是在宏的返回值被计算期间。

解释了原因。参数值会计算两次,如果递归函数在min与max的define宏定义函数下调用了自己是非常可怕的,它会增加指数级别的时间复杂度。define宏定义因为不会真正调用函数的特性在一定情况下确实能增加速度,然而如果min与max的define宏定义函数的“实参”(其实它并不能叫做实参)中出现了一个复杂的计算的话,它会进行两次计算,这大大拖慢了程序的速度。所以我建议如果在使用define宏定义函数时,如果传值中出现了一个会进行时间较长的计算的函数的话,应该这样使用:

int t=calc();  //假如calc()是一个需要经过大量计算的函数
ans=min(t,ans);

这样会大大加快速度 或者除非卡常时否则干脆别用了 。

经过测试,该代码

#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
int n=25;
int test(int i,int type,int I) {if(!i) return type==0?n-I:I;int t=test(i-1,type,I);  //防止重复计算return max(type==0?n-i:i,t);
}
int main() {int minx=n,maxx=0;for(int i=1;i<=n;++i) {minx=min(minx,test(i,0,i));maxx=max(maxx,test(i,1,i));}return 0;
}

速度已经下降到了0ms。

C++中#define宏定义的min与max函数相关推荐

  1. define宏定义中的#,##,@#及\符号

    define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (string ...

  2. Linux内核源码中使用宏定义的若干技巧

    在C中,宏定义的概念虽然简单,但是真要用好却并不那么容易,下面从Linux源码中抽取一些宏定义的使用方法,希望能从中得到点启发: 1. 类型检查 比如module_init的宏定义: 点击(此处)折叠 ...

  3. 大牛深入浅出讲解C语言#define宏定义应用及使用方法

    在C语言中,我们使用#define来定义宏.在C程序编译的预处理阶段,预处理器会把宏定义的符号替换成指定的文本. 不带参数的宏 关于宏最常见的就是用来定义数值常量的名称,即没有参数的宏定义,采用如下形 ...

  4. Makefile中用宏定义进行条件编译(gcc -D)/在Makefile中进行宏定义-D

    在源代码里面如果这样是定义的: #ifdef   MACRONAME //可选代码 #endif 那在makefile里面 gcc   -D   MACRONAME=MACRODEF 或者 gcc   ...

  5. 关于gcc扩展中的宏定义中用 # 和 ##

    关于gcc扩展中的宏定义中用 "#" 和 "##" 今天测试了宏定义中的 "#" 和 "##" 的区别. 结果如下: & ...

  6. C++中的内联函数和C中的宏定义的区别

    在C++中内联函数: 内联函数即是在函数的声明和和定义前面加上"inline"关键字,内联函数和常规函数一样,都是按照值来传递参数的,如果参数为表达式,如4.5+7.5,则函数将传 ...

  7. Linux 内核中的宏定义

    Linux 内核中的宏定义 rtoax 日期 内核版本:linux-5.10.13 注释版代码:https://github.com/Rtoax/linux-5.10.13 __attribute__ ...

  8. C++中的宏定义详解

    转载自:C++中的宏定义 和 C++宏定义详解 目录 一.#define解析 1 #define命令剖析 1.1   #define的概念 1.2 宏替换发生的时机 1.3 ANSI标准说明了五个预定 ...

  9. C语言基础知识之define宏定义表达式,undef,内存对齐,a和a的区别,数组知识点,int (*)[10] p,二维数组参数与二维指针参数,函数指针数组,常见的内存错误及对策

    一.用define宏定义表达式 1.定义一年有多少秒: #define SEC_A_YEAR 60*60*24*365 //上述描述不可靠,没有考虑到在16位系统下把这样一个数赋给整型变量的时候可能会 ...

  10. #define宏定义是什么?怎么写?一文搞懂。

    前言 第一次接触宏定义的时候,是在学校的C++课程大作业中.当时有幸看到了大佬写的源码,里面有很多的宏定义.当时并不了解宏定义的妙用,只感觉它像是定义了一个变量一样,不好理解还有些多此一举. 这之后在 ...

最新文章

  1. python解释器运行代码-python解释器怎么运行
  2. YbtOJ-选点构形【欧拉函数】
  3. [慢查优化]联表查询注意谁是驱动表 你搞不清楚谁join谁更好时请放手让mysql自行判定...
  4. 前端学习(1646):前端系列实战课程之右下角悬浮功能实现思路
  5. LSGO软件技术团队对外技术交流
  6. C# 字符串string的基本操作
  7. android 专业ps软件,手机专业ps修图
  8. Exploring $ORIGIN - 探索 $ORIGIN
  9. HDU 1728 优化的BFS
  10. 我们将迎来另一个 VR 寒冬吗?
  11. 第三方APK如何隐藏虚拟按键
  12. 【计算机视觉】张正友棋盘格标定法
  13. 2021年,各类显卡的计算能力对比,天梯图
  14. telnet 不是内部或外部命令
  15. 联想a366t 刷android4,联想A366t线刷刷机教程(刷官方rom)
  16. 51单片机之位操作指令SETB、CPL等
  17. 我们不再是冷暖自知的个体
  18. JAVA -- stateless4j StateMachine 使用浅析(一)
  19. Ctrix卸载ReceiverCleanupUtility.exe
  20. 生命科学名言15:免疫

热门文章

  1. 60条有名的处世原则与定理
  2. 网络营销之QQ推广技巧
  3. 排除AP无法上线原因
  4. 2021-01-22
  5. 美国向谷歌、微软、雅虎要个人敏感信息,他们真给了
  6. Java:class6 继承
  7. FDTD Solutions时域有限差分法仿真学习相关操作(一)——GDS导出
  8. 2018.11.22!今天重温一遍知识点,捋一捋思路
  9. (很容易懂,你把代码复制粘贴即可解决问题)高等代数/线性代数-基于python实现矩阵法求解齐次方程组
  10. Error during job, obtaining debugging information...