C++中#define宏定义的min与max函数
引言
最近几天在写普通平衡树这一题时,我没有使用我平常经常使用的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函数相关推荐
- define宏定义中的#,##,@#及\符号
define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (string ...
- Linux内核源码中使用宏定义的若干技巧
在C中,宏定义的概念虽然简单,但是真要用好却并不那么容易,下面从Linux源码中抽取一些宏定义的使用方法,希望能从中得到点启发: 1. 类型检查 比如module_init的宏定义: 点击(此处)折叠 ...
- 大牛深入浅出讲解C语言#define宏定义应用及使用方法
在C语言中,我们使用#define来定义宏.在C程序编译的预处理阶段,预处理器会把宏定义的符号替换成指定的文本. 不带参数的宏 关于宏最常见的就是用来定义数值常量的名称,即没有参数的宏定义,采用如下形 ...
- Makefile中用宏定义进行条件编译(gcc -D)/在Makefile中进行宏定义-D
在源代码里面如果这样是定义的: #ifdef MACRONAME //可选代码 #endif 那在makefile里面 gcc -D MACRONAME=MACRODEF 或者 gcc ...
- 关于gcc扩展中的宏定义中用 # 和 ##
关于gcc扩展中的宏定义中用 "#" 和 "##" 今天测试了宏定义中的 "#" 和 "##" 的区别. 结果如下: & ...
- C++中的内联函数和C中的宏定义的区别
在C++中内联函数: 内联函数即是在函数的声明和和定义前面加上"inline"关键字,内联函数和常规函数一样,都是按照值来传递参数的,如果参数为表达式,如4.5+7.5,则函数将传 ...
- Linux 内核中的宏定义
Linux 内核中的宏定义 rtoax 日期 内核版本:linux-5.10.13 注释版代码:https://github.com/Rtoax/linux-5.10.13 __attribute__ ...
- C++中的宏定义详解
转载自:C++中的宏定义 和 C++宏定义详解 目录 一.#define解析 1 #define命令剖析 1.1 #define的概念 1.2 宏替换发生的时机 1.3 ANSI标准说明了五个预定 ...
- C语言基础知识之define宏定义表达式,undef,内存对齐,a和a的区别,数组知识点,int (*)[10] p,二维数组参数与二维指针参数,函数指针数组,常见的内存错误及对策
一.用define宏定义表达式 1.定义一年有多少秒: #define SEC_A_YEAR 60*60*24*365 //上述描述不可靠,没有考虑到在16位系统下把这样一个数赋给整型变量的时候可能会 ...
- #define宏定义是什么?怎么写?一文搞懂。
前言 第一次接触宏定义的时候,是在学校的C++课程大作业中.当时有幸看到了大佬写的源码,里面有很多的宏定义.当时并不了解宏定义的妙用,只感觉它像是定义了一个变量一样,不好理解还有些多此一举. 这之后在 ...
最新文章
- python解释器运行代码-python解释器怎么运行
- YbtOJ-选点构形【欧拉函数】
- [慢查优化]联表查询注意谁是驱动表 你搞不清楚谁join谁更好时请放手让mysql自行判定...
- 前端学习(1646):前端系列实战课程之右下角悬浮功能实现思路
- LSGO软件技术团队对外技术交流
- C# 字符串string的基本操作
- android 专业ps软件,手机专业ps修图
- Exploring $ORIGIN - 探索 $ORIGIN
- HDU 1728 优化的BFS
- 我们将迎来另一个 VR 寒冬吗?
- 第三方APK如何隐藏虚拟按键
- 【计算机视觉】张正友棋盘格标定法
- 2021年,各类显卡的计算能力对比,天梯图
- telnet 不是内部或外部命令
- 联想a366t 刷android4,联想A366t线刷刷机教程(刷官方rom)
- 51单片机之位操作指令SETB、CPL等
- 我们不再是冷暖自知的个体
- JAVA -- stateless4j StateMachine 使用浅析(一)
- Ctrix卸载ReceiverCleanupUtility.exe
- 生命科学名言15:免疫
热门文章
- 60条有名的处世原则与定理
- 网络营销之QQ推广技巧
- 排除AP无法上线原因
- 2021-01-22
- 美国向谷歌、微软、雅虎要个人敏感信息,他们真给了
- Java:class6 继承
- FDTD Solutions时域有限差分法仿真学习相关操作(一)——GDS导出
- 2018.11.22!今天重温一遍知识点,捋一捋思路
- (很容易懂,你把代码复制粘贴即可解决问题)高等代数/线性代数-基于python实现矩阵法求解齐次方程组
- Error during job, obtaining debugging information...