前言
define这个关键字是C语言中一个非常重要的关键字,也是我们特别常见的一个关键字。和这个关键字相关主要有两个功能,一个是define定义的标识符,还有一个是define定义的宏。本篇文章博主就主要围绕这两点展开向大家介绍这个关键字define相关的一些知识。
define定义标识符
语法
#define name stuff
这里的name在代码中就可以完全替代后面的stuff。
举例
#define MAX 100 //定义一个全局变量MAX值为100
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
注意:如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
例如:
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n", \
__FILE__, __LINE__, \
__DATE__, __TIME__)
define定义标识符常量如果要换行的话千万不能用空格换行,必须用续航符' \ '来换行,并且注意,不能在续航符后面加空格,要不然就起不到续航的作用,并且不容易排查出来,所以就会写出bug,这个一定要注意。
提问
在define定义标识符的时候,要不要在最后加上分号;呢?
比如:
#define MAX 100;
#define MAX 100
建议不要加上分号;,这样容易导致问题。比如下面的场景:
#include <stdio.h>
#define MAX 100;
int main()
{
printf("%d\n", MAX);
return 0;
}
用define定义MAX的值为100,并且在后面加上了分号,接下来我们在main函数中将MAX的值打印出来,来看运行结果:
可以看到出现语法错误的警告,为什么会出现这种情况呢?下面我们就来好好分析一下。
define定义的标识符实际上就是为了方便程序员写代码,因为有时候要写的变量或者数据太长,所以我们就用define重新定义一个比较简短的标识符来方便书写。大家需要知道的是,在预编译阶段define定义的标识符常量会被原来的量替换掉,于是在预编译结束之后printf("%d\n", MAX)这条语句就变成了printf("%d\n", 100;),可以看到这里显然多了一个分号,因此会造成语法的错误。所以说我们在使用define定义标识符常量的时候没有必要在后面加分号,除非是真的有需要。
define定义宏
宏的定义
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
宏的声明方式
#define name( parament-list ) stuff
parament-list是一个由逗号隔开的符号表,他们可能出现在stuff中。
注意:参数列表的左括号必须与name相邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
宏的应用
举个例子
#include <stdio.h>
#define SQUARE(X) X*X
int main()
{
printf("%d\n", SQUARE(5));
return 0;
}
定义一个求平方的宏,这个宏接收一个参数5,求出结果打印出来,运行结果:
可以看出来计算求得5的平方25.
宏和define定义的标识符一样,在预处理阶段都会被替换掉,表达式printf("%d\n", SQUARE(5))在预处理阶段结束后就变为了printf("%d\n", 5*5),所以最终的打印结果是25.
警告:这个宏存在一个问题,观察下面代码:
#include <stdio.h>
#define SQUARE(X) X*X
int main()
{
printf("%d\n", SQUARE(4+1));
return 0;
}
乍一看,我们还是求5的平方,求出来的值应该是25,那么结果是不是这样呢?来看运行结果:
可以看到,求出来的值是9,并不是25,为什么?
原因还是上面我提到的,在预处理阶段替换文本时,参数X被替换成4+1,所以这条语句实际上就变成了printf("%d\n", 4+1*4+1);
这样一看就比较清晰了,替换的表达式并没有按照预想的次序来求值,由于乘法的优先级高,所以先算1*4=4,在计算4+4=8,再计算8+1=9,最终的输出结果是9.
如何解决呢?在宏定义上加上两个括号,这个问题便轻松的解决了:
#include <stdio.h>
#define SQUARE(X) (X)*(X)
int main()
{
printf("%d\n", SQUARE(4+1));
return 0;
}
运行结果:
这样预处理之后就产生了预期的效果printf("%d\n", (4+1)*(4+1))求出结果是25.
这里还有一个宏定义:
#define DOUBLE(X) (X)+(X)
这个宏是来求一个数乘以2的值,定义中我们使用了括号想避免之前的问题,但是这个宏可能会出现新的错误:
#include <stdio.h>
#define DOUBLE(X) (X)+(X)
int main()
{
printf("%d\n", 10*DOUBLE(100));
return 0;
}
按照预想我们把100作为参数传个这个宏然后计算出两百,再用10乘以宏计算出来的值200,最终的打印结果引号是2000,。最终的打印结果是不是2000呢,这里我们来看一运行结果:
可以看到并不是,实际的打印值是1100.这是因为在文本替换之后打印语句变成了printf("%d\n", 10*(100)+(100)),乘法的优先级高于加法,所以先算10*100=1000,在计算1000+100=1100.
解决这个问题的方法就是在宏定义表达式两边加上一对括号就可以了。
#include <stdio.h>
#define DOUBLE(X) ((X)+(X))
int main()
{
printf("%d\n", 10*DOUBLE(100));
return 0;
}
运行结果:
提示:
所以对于数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或临近操作符之间不可预料的相互作用。
宏和函数的对比
仔细看完宏的使用之后我们一定会发现宏和函数真的十分的类似,这里我们就来对比一下宏和函数
我们先来看下面一段代码:
#include <stdio.h>
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int Max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 10;
int b = 20;
//宏
int max = MAX(a, b);
printf("%d\n", max);
//函数
max = Max(a, b);
printf("%d\n", max);
return 0;
}
这段代码中我们分别用宏和函数实现了求两个数的最大值,大家可以简单的对比一下哪一个更加有优势一些,很显然这里使用宏更好一些。宏一条语句就能解决的问题函数却用了好几条语句,而C语言中几条语句编译产生的汇编语句就更多了,这样就大大影响了程序的效率。
所以我们说宏通常被应用于执行简单的运算。
通过上面的代码,我们来总结一下宏比函数的优点在哪里:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之,这个宏可以适用于整型,长整型,浮点型等可以用于大小比较的类型。宏时无关类型的。
(比如上面的例子函数只能判断整型,如果给两个浮点数就不能判断了。而宏可以判断任意类型。)
当然,宏相比函数也有劣势的地方:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的
- 宏由于类型无关,也就不够严谨(双刃剑)
- 宏可能会带来运算符优先级的问题,导致程序容易出错。
宏有时候可以做到函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
平常我们开辟一块动态空间都是这样写的:
(int *)malloc(10 * sizeof(int));
下面我们定义一个宏来开辟动态空间:
#include <stdio.h>
#define MALLOC(num, type) (type*)malloc(num*sizeof(type))
int main()
{
int*p = MALLOC(10, int);
return 0;
}
可以看到这里我们将元素的类型作为参数,下面的调用中将整型int作为参数传进去,开辟十个整形的空间,最终预处理后的结果和平常我们开辟一块空间是一样的。
宏和函数做一个对比:
这张表上基本囊括了宏和函数的对比关系,大家可以结合我上面讲的知识好好的研究一下。
宏命名约定
一般来说函数和宏的使用语法很相似。所以语言本身没法帮我们区分两者。那么我们平时的一个习惯是:
把宏名全部大写,函数名不要全部大写
undef
这条指令用于移除一个宏定义。
举个例子:
#include <stdio.h>
#define MAX 100
int main()
{
printf("%d\n", MAX);
#undef MAX
printf("%d\n", MAX);
return 0;
}
这段代码我们先定义一个宏MAX的值为100,然后在打印完一次之后,用预处理指令#undef将这个宏移除掉,下面第二次打印的时候就没有这个宏了,所以就会出错。下面我们来看看运行结果:
可以看到编译器中第11行的代码中MAX显示未定义,这是因为在上面的#undef指令已经将宏MAX给移除掉了。
define替换规则
前面介绍标识符和宏的应用中我曾简单的提到过宏是如何替换的,下面我们来总结一下:
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名,被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define定义中可以出现其它#define定义的常量。但是对于宏,不能出现递归。
- 当与处理器搜索#define定义的符号时候,字符串常量的内容并不被搜索。
比如:
#define MAX 100
char* p = "MAX";
这里的MAX就不会被替换掉。
最后希望这篇文章能够为大家带来帮助。