目录
1.预处理是什么?
2.预定义符号
3.define标识符
3.1 define 定义标识符
3.2 define定义宏
命名约定
3.3 宏的题目
4.# 和 ##
4.1 #的作用
4.2 ##的作用
5.带副作用的宏参数
6.宏与函数的对比
7.#undef
8.条件编译
9.头文件被包含的方式
1.预处理是什么?
预编译又称为预处理 , 是做些代码文本的替换工作。处理 # 开头的指令 , 比如拷贝 #include 包含的文件代码, #define 宏定义的替换 , 条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
预处理主要工作:
1.头文件的包含 2.define宏定义的替换 3.条件编译的处理
2.预定义符号
- __FINE__ //进行编译的源文件
- __Line__ //文件当前的行号
- __Date__ //文件被编译的日期
- __TIME__ //文件被编译的时间
- __STDC__ //如果编译器遵循ANSI,其值为1,否则则未定;
上面这些是预定义符号都是语言内置的
注意:符号中的左右是两个横杠,是__FINE__,而不是_FINE_,
例如:
printf("文件:%s\n", __FILE__);
printf("文件当前的行号:%d\n", __LINE__);
printf("文件编译的日期:%s\n", __DATE__);
printf("文件编译的时间:%s\n", __TIME__);
3.define标识符
3.1 define 定义标识符
# define宏定义是个演技非常高超的替身演员,在编译器编译时,真身则会替换替身,在我们代码中我们会经常使用这个替身。
#define MAX 1000
int max = MAX;//在预处理阶段,MAX会替换为1000,相当于int max=1000;
3.2 define定义宏
#defifine
机制包括了一个规定,
允许把参数替换到文本中,这种实现通常称为宏或定义宏
。
例如我们写一个数字的平方:
#define SQUARE(x) x*x
int main()
{
printf("%d", SQUARE(5));//输出25
return 0;
}
当我们传5进去后,在预编译阶段,则SQUARE(5)会被替换为5*5,所以最终输出为25.
printf("%d", SQUARE(2 + 3));//输出11
为什么上面代码会输出11呢?
当我们传2+3进去后,在预编译阶段,则SQUARE(2+3)会被替换为2+3*2+3,在根据优先级计算,即可得出11,我们想要的是(2+3)*(2+3),但输出并没有符号我们预期,我们需要把宏修改一下。
#define SQUARE(x) (x)*(x)
printf("%d", SQUARE(2 + 3));//输出25
那么我们在来定义一个宏,求整数的两倍。
#define DOUBLE(x) (x)+(x)
printf("%d", DOUBLE(5));//输出10
printf("%d", 2*DOUBLE(5));//输出15
DOUBLE(5)输出10我们还是能够理解,但是2*DOUBLE(5)输出15是怎么回事呢,在预编译阶段,2*DOUBLE(5)则会被替换成2*(5)+(5),我们想要的是输出20的结果,那怎么办呢?那么我们在外边直接加个大括号即可。
#define DOUBLE(x) ((x)+(x))
总结:所以我再写宏定义时尽量给每个变量加个括号,最外层的括号也别省。例如之前#define SQUARE(x) (x)*(x),在外层的括号也加上去,变成
#define SQUARE (x) ((x)*(x))
命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是:
把宏名全部大写 函数名不要全部大写。
3.3 宏的题目
写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。
代码:
#define SWAP_BIT(n) n=(((n)&0x55555555)<<1)|(((n)&0xaaaaaaaa)>>1)
思路:先取出整数二进制的奇数位上的数,&上一个01010101010101010101010101010101提取出奇数位,对应的十六进制为0x55555555,在取出整数的二进制的偶数位上的数,&上一个10101010101010101010101010101010提取出偶数位,对应的十六进制为0xaaaaaaaa,然后对奇数位上的数左移一位(<<)就变成偶数位,对偶数位上的数右移一位(>>)就变成奇数位了,在对这两个数进行 | 就可以完成交换。
4.# 和 ##
4.1 #的作用
#VALUE会被预处理为“VALUE”
int a = 0;
double d = 1.11;
printf("the value of a is %d\n", a);//输出:the value of a is 0
printf("the value of d is %f\n", d);//输出:the value of d is 1.110000
那么我们怎样可以写一个宏来替代
printf("the value of a is %d\n", a);
printf("the value of d is %f\n", d);这两条语句呢?
首先我们必须先知道:
printf("hello " "world\n");//输出hello world
字符串是有自动连接的特点的
所以我们可以定义这样一个宏:
#define PRINT(FORMAT,DATA) printf("the value of " #DATA " is " #FORMAT“\n",DATA)
int a=10;
double d=1.11;
PRINT(%d, a);//输出:the value of a is 0
PRINT(%f, d);//输出:the value of a is 1.110000
所以我们就很好通过一个宏就替代上面那两条语句。
注意:宏中的#DATA在预编译时会替换成”DATA" ,#FORMAT会被替换为“FORMAT"。
4.2 ##的作用
##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符
。
#define ADD_TO_SUM_(num,value) sum##num+=value;
int sum1 = 1;
int sum2 = 2;
ADD_TO_SUM_(1, 10);//sum1增加10
ADD_TO_SUM_(2, 10);//sum2增加10
sum##num为sumnum(num为传进来的宏参数)
5.带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,
如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险
,导致不可预测的后果。
例如:
#define MAX(x,y) ((x)>(y))?(x):(y)
int main()
{
int a = 0;
int b = 0;
MAX(a++, b++);
printf("%d %d", a, b);//结果:a为1,b为2
return 0;
}
为什么运行结果a为1,b为2呢?
在预编译时,MAX(a++,b++)会被替还为 ((a++)>(b++))?(a++):(b++),
在 ?判断之前 a跟b都进行了一次++,都变为1,判断a不比b大后,则在运行b++,b在变成2.
所以
a++,和b++这种会改变变量本身的宏参数为带有副作用的宏参数。
6.宏与函数的对比
宏跟函数其实有有很多不同点,那么我们来利用宏和函数来实现找出两个数中的较大值。
#define MAX(x, y) ((x)>(y)?(x):(y))//宏实现
int Max(int a,int b)//函数实现
{
return a>b?a:b;
}
宏的优势:
1.
宏的参数不需要特定的类型,所以宏的使用可以传任意类型,宏是与类型无关的。而函数的参数必须为特定类型,所以使用的时候只能传特定的参数。
2.宏在预编译阶段中会直接将代码替换,而函数调用需要创建函数栈帧和返回值都需要消耗时间,所以宏的效率比函数要高。
的
宏的劣势:
1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
2.由于宏是运行时将代码插入到程序中,所以宏是没办法调试。
3.宏的参数可以是任意类型的,所以不严谨。
4.宏会导致优先级的问题,容易出错。
5.宏是不能递归,函数可以递归。
7.#undef
撤销一个宏定义,如果现存的名字需要重新定义,那么需要先移除它的旧名字。
#define MAX 100
int main()
{
int MAX = 1000;//错误:MAX为宏定义,不能再重新定义
#undef MAX;//移除MAX的宏定义,下面的宏定义就不能用
int MAX = 1000;//正确
return 0;
}
宏的生命周期从#define 开始到#undef 结束
。
8.条件编译
条件编译的功能使得我们可以
按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件
。
条件编译有三种形式:
方式一:
#ifdef 标识符
程序段一
#else
程序段二
#endif
上面的代码是,如果标识符有被#define定义过,则运行程序段一,如果没有定义过,则运行程序段二,如果没有程序段二(空),则#else可以没有 。
#define DEBUG 1
int main()
{
#ifdef DEBUG
printf("hello world\n");
#else
printf("thank you\n");
#endif
}
例如上面的代码,由于DEBUG被定义,所以在预编译的时候,编译器只会保留
int main()
{
printf("hello world\n");
}
这段代码,其它代码被编译过滤掉,最终输出结果为hello world。
如果DEBUG没有定义,则编译器则会保留
int main()
{
printf("thank you\n");
}
这段代码,其它代码被编译过滤掉,最终输出结果为 thank you。
方式二:
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
与第一种形式差不多,只是将#ifdef改为#ifndef,如果标识符没有被定义,则运行程序段1,有被定义则运行程序段 2,与方式一差不多,只是逻辑相反,所以笔者我就不再举例子了。
方式三:
#if 常量表达式1
程序段 1
#elif 常量表达式2
程序段 2
#else
程序段 3
#endif
如果 常量表达式1的结果不为0,则为真,则只运行程序段 1,如果常量表达式结果为0(假),常量表达式为2不为0,则只运行程序段 2,如果常量表达式为1和常量表达式 2的结果都为0,则运行程序段 3。如果没有程序段 2和程序段 3(为空),则#elif和# else可以没有。
int main()
{
#if 10
printf("hello world\n");
#elif 10
printf("you are very handsome\n");
#else
printf("thank you\n");
#endif
return 0;
}
对于上面的代码,由于常量表达式不为0,所以在预编译的时候,编译器只保留
int main()
{
printf("hello world\n");
return 0;
}这段代码,其它代码被过滤掉。最终运行结果为 hello world ,如果常量表达式1的结果为0,则运行结果为 you are very handsome ,如果常量表达式1和常量表达2都为0,则运行结果为thank you。
假设我们有一个Add.h头文件,里面包含着几个函数的声明:
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
我们在test.c上引入Add.h的头文件,如下:
#include"Add.h"
int main()
{
int a = 0, b = 0;
Add_int(a, b);
return 0;
}
在预编译阶段时,则编译器会发生替换,替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。
则test.c文件预编译后会变成:
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
int main()
{
int a = 0, b = 0;
Add_int(a, b);
return 0;
}
但是如果我们的test.c文件多次包含头文件Add.h后,则预编译时会将Add.h的内容会包含多次,这就会导致test.c文件中的内容重复。
那么如何解决头文件的重复包含?答案是:条件编译
我们可以对Add.h头文件就行修改
#ifndef ADD_H
#define ADD_H 1
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
#endif
如果test.c引入一次Add.h头文件,由于没有定义#define Add_H,则预编译时会定义一次#define Add_H,并且将头文件中的内容给包含进去,如果在引入一次Add.h头文件,因为ADD_H已经被定义,那么条件编译不会将头文件的内容包含进来,所以这就很巧妙的解决这些问题。
或者我们有一种现代写法:
#pragma once
int Add_Int(int x, int y);
double Add_Double(double x, double y);
float Add_Float(float x, float y);
上面的两种方式都能避免我们的头文件的重复引用。
9.头文件被包含的方式
c语言提供了两种头文件的包含方式,它实际是宏替换的延伸,有两种格式:
格式一:
#include<stdio.h>
stdio为包含文件的名称,用尖括号括,预处理时会直接去库函数的头文件中查找,找不到则编译错误。
格式二:
#include"file.h"
”file.h为包含文件的名称,直接在当前目录下查找名为file.h的头文件,找不到在按系统指定的路径信息,搜索其他目录。找到文件后,用
文件内容替换该语句。
点个赞呗~