文章目录

#define定义标识符

#define定义标识符的格式如下:

#define
#define//懒人觉得register太长了

这些被#define定义的标识符都将在预处理阶段被编译器替换成对应的内容,之前看到一个比较有意思的东西,这里和大家分享一下:

#define
#define
#define
#define
#define
#define

确实,只要在文件前面加上这几句话,就再也不用担心自己的代码中的标点符号写成中文的了,也再也不用担心把main写成mian,true写成ture了,因为即使写错了也被替换成正确的了。但是我们还是应该在写代码时认真仔细,避免这些低级错误的发生。

#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

例如,用宏实现求一个数的平方:

#include <stdio.h>
#define//求x的平方
int main()
{
int ret = SQUARE(5);
//相当于int ret = 5*5;
printf("%d\n", ret);//结果为25
return 0;
}

但是这并不完全正确,因为当你传入的宏参数为2+3时,打印的结果却并不是25,而是11。因为宏完成的是替换,它不会先把2+3的值算出来再进行替换,而是直接替换,所以传入2+3替换后相当于:

int ret = 2+3*2+3;

因为*的优先级高于+,所以这样算出的结果当然是11了。为了避免这种情况的发生,用宏实现求一个数的平方应该这样:

#define

这里将(x)*(x)整体再用括号括起来的原因也是一样的,都是为了避免在使用宏时,因操作符的优先级问题而导致不可预料的后果。

所以在使用#define定义宏时,不要吝啬括号,该加括号的地方就要加上

#define的替换规则

在程序中替换#define定义的宏和标识符时,需要涉及几个步骤
我们用以下代码进行举例:

#include <stdio.h>
#define
#define
int main()
{
int ret = SQUARE(5);
printf("%d\n", ret);
return 0;
}

1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
例如,#define定义的宏中含有#define定义的符号MAX,则调用该宏时,首先将MAX替换。

#include <stdio.h>
#define
int main()
{
int ret = SQUARE(5);
printf("%d\n", ret);
return 0;
}

2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
例如,上例中经过该步骤后,代码等价于:

#include <stdio.h>
int main()
{
int ret = ((5)*(5)*100);
printf("%d\n", ret);
return 0;
}

3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
上例不再包含任何由#define定义的符号。

注意:
1.宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
也就是不能出现类似于以下的代码:

#define//error

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
例如,以下代码字符串中的MAX不会被替换为100,而字符串外的MAX会被替换。

#include <stdio.h>
#define
int main()
{
printf("MAX = %d\n", MAX);//结果为MAX = 100
return 0;
}

带副作用的宏参数

在介绍带副作用的宏参数之前,我们先看看带有副作用是什么意思

int a = 10;
int b = a + 1;//无副作用
int c = a++;//有副作用

代码中,b和c都想得到a+1的值,但不改变a的值。b得到a+1的值后,a的值并没有发生改变,所以无副作用;但是c得到a+1的值后,a的值也变化了,也就是有副作用。简单来说,代码执行后,除了达到我们想要的结果之外,还导致了其他问题的发生,我们就说该条语句带有副作用。

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如,我们要比较a和b的大小,并将其较大值赋值给c,之后再将a和b同时加1。

#include <stdio.h>
#define
int main()
{
int a = 10;
int b = 20;
int c = MAX(a++, b++);
printf("%d\n", c);
return 0;
}

这段代码看似没有问题,但是结果却是不正确的,因为该宏经过替换后,等价于以下代码:

#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = ((a++)>(b++)?(a++):(b++));
printf("%d\n", c);
return 0;
}

经过替换后,我们一分析便可得出答案,c的最后的结果是21,并且代码执行后,a和b的值并不是同时加1,a的值变为了11,而b的值却变为了22。

所以,当我们使用宏的时候,应该避免传入带有副作用的宏参数

宏和函数的对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

#define

那为什么不用下面这个函数来实现这个功能呢?

int Max(int x, int y)
{
return x > y ? x : y;
}

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。

2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。但是宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。

而且,宏有时候可以做到函数做不到的事情。例如,宏的参数可以出现类型,但是函数却不可以
我们使用malloc函数开辟内存空间时,可能会觉得代码太多。

#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p1 = (int*)malloc(10 * sizeof(int));
if (p1 == NULL)
{
printf("p1开辟失败\n");
return 1;
}
free(p1);
p1 = NULL;
return 0;
}

这时我们可以实现一个宏,使我们用malloc开辟空间时,只用传入开辟的类型和该类型的元素个数即可。

#include <stdio.h>
#include <stdlib.h>
#define
int main()
{
int* p2 = MALLOC(10, int);
if (p2 == NULL)
{
printf("p2开辟失败\n");
return 1;
}
free(p2);
p2 = NULL;
return 0;
}

但是,宏也有劣势的地方,例如:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

下面我们画出了框架图,能够更加清晰地区分宏和函数的区别:

预处理详解(二)—— #define定义宏和标识符 + 宏和函数的对比 + #和##的作用 + #undef_#define

#undef的作用

#undef可以移除一个#define定义的标识符或宏。

例如,下列代码将#define定义的标识符MAX移除后,编译器便不能识别之后的MAX。

#include <stdio.h>
#define
int main()
{
printf("%d\n", MAX);//正常使用
#undef
printf("%d\n", MAX);//报错,MAX未定义
}

冷门知识点:#和##

这里所说的#和##的使用非常之少,可能有些博友都没有听说过,但是这个知识点在面试的时候也可能会被考到,也是比较重要的。
1.#的作用
这里所说的#并不是#define和#include中的#,这里所说的#的作用是:把一个宏参数变成对应的字符串

那么,这个#到底有什么实际的作用呢?
在介绍#的作用的之前,我先向大家说明一下:字符串是有自动连接的特点的
例如,以下案例:

char arr[] = "hello ""world!";
//等价于char arr[] = "hello world!";
printf("helll ""world!\n");
//等价于printf("helll world!\n");

接下来,给大家举一个#的使用案例。例如,有以下代码:

#include <stdio.h>
int main()
{
int age = 10;
printf("The value of age is %d\n", age);
double pi = 3.14;
printf("The value of pi is %f\n", pi);
int* p = &age;
printf("The value of p is %p\n", p);
return 0;
}

我们发现,printf要打印的内容大部分是一样的,那么,为了避免代码冗余,我们可不可以将其封装成一个函数或是宏呢?
经过思考与实验,发现函数和普通的宏都不能实现该功能。不相信的博友可以去测试测试。
这时就需要用到这个#了,代码如下:

#include <stdio.h>
#define
int main()
{
int age = 10;
print(age, "%d");
double pi = 3.14;
print(pi, "%f");
int* p = &age;
print(p, "%p");
return 0;
}

这时我们只需将要打印的变量的变量名和打印格式传入即可。该代码经过预处理后等价于以下代码:

#include <stdio.h>
int main()
{
int age = 10;
printf("The value of ""age"" is ""%d""\n", age);
double pi = 3.14;
printf("The value of ""pi"" is ""%f""\n", pi);
int* p = &age;
printf("The value of ""p"" is ""%p""\n", p);
return 0;
}

又因为字符串有自动连接的特点,所以可以打印出期望的结果。
2.##的作用
##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符

例如,下面定义的宏可以将传入的两个符号合成一个符号。

#include <stdio.h>
#define
int main()
{
int workhard = 100;
printf("%d\n", CAT(work, hard));//打印100
return 0;
}