预处理详解
预定义符号
C语言设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。
#include<stdio.h>
int main()
{printf("%s\n",__FILE__);//进行编译的源文件printf("%d\n",__LINE__);//当前文件的行数printf("%s\n",__DATE__);//文件被编译的日期printf("%s\n",__TIME__);//文件被编译的时间return 0;
}
这些符号是在预处理阶段就可以直接被替换的
gcc环境下:(vim打开)
仅进行预处理并输出结果到标准输出
gcc -E source.c
该命令会对 source.c进行预处理,但不进行后续的编译、汇编和链接步骤,预处理后的代码会直接输出到终端。
将预处理结果保存到文件
gcc -E source.c -o output.i
使用 -o
选项可以将预处理结果保存到指定文件(通常约定用 .i
作为扩展名)
预处理时显示包含文件的搜索过程
gcc -E -v source.c
加上 -v
选项可以显示 GCC 在预处理过程中搜索头文件的路径和过程。
定义宏进行预处理
gcc -E -D宏名=值 source.c
使用 -D
选项可以在预处理时定义宏,相当于在代码中使用 #define 定义宏,可以在编译时指定值
ls(命令) -l(字母) 将文件参数一件一件列出来 -a:列出文件
-al(所有文件,包括隐藏文件)列出
#define定义常量
#define #include是预处理指令
#define MAX 100
int main()
{int m=max;printf("%d\n",m);return 0;
}
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )// \是续行符
#include<stdio.h>
int main()
{DEBUG_PRINT;return 0;
}
#define后面最好不要加上;
#define定义宏
#define机制允许参数替换到文本中,这种实现称为宏
#define name(parament-list) stuff
parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中
注意: 参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
#define SQUARE(N) N*N
int main()
{int a = 5;int d = SQUARE(a);//25int r = SQUARE(a+1);//a+1*a+1=11printf("%d %d\n", d,r);return 0;
}//要么#define SAUARE(N) ((N)*(N)) 最好带大括号
拥于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的 操作符和邻近操作符之间不可预料的相互作用。
带有副作用的宏参数
当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
//写一个宏,求两个数最大值
#define MAX(x,y) ((x)>(y)?(x):(y))
#include<stdio.h>
int main()
{int a = 4;int c = 7;int m = MAX(a, c);printf("%d\n", m);return 0;
}
然而:
//写一个宏,求两个数最大值
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 4;int c = 7;int m = MAX(a++, c++);printf("%d\n", m);printf("%d\n", c);return 0;
}
m = ( (x++) > (y++) ? (x++) : (y++));
宏替换的规则
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插⼊到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
#define MAX(x,y) ((x)>(y)?(x):(y))
#define B 5
#include<stdio.h>
int main()
{int a = 4;int m = MAX(a,B );printf("%d\n", m);return 0;
}
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。正如上述B不能在MAX中出现
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。如:
printf(“B=%d",m);
宏函数的对比
函数调用时:1.函数的调用:参数的传参,栈帧的创建
2.计算任务 3.函数的返回
宏:计算任务
宏的优势
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比 函数在程序的规模和速度方面更胜⼀筹。
更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于 > 来比较的类型。宏的参数是类型无关 的。
宏的劣势
1. 每次使用宏的时候,⼀份宏定义的代码将插⼊到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。
2. 宏是没法调试的,不能递归。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程序容易出现错误
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型
//开辟空间
#include<string>
#include<stdio.h>
#define MALLOC(n,type) (type*)malloc(n*sizeof(type))
int main()
{int* p = MALLOC(10, int);//可以自由更改类型return 0;
}
#和##
#运算符将宏的⼀个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中
#运算符所执行的操作可以理解为”字符串化“。
#include<stdio.h>
#define PRINT(n,format) printf("the value of "#n" is "format"\n",n)
int main()
{int abc = 10;PRINT(abc, "%d");float f = 3.14;PRINT(f, "%f");return 0;
}
## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称 为记号粘合
写一个函数模具
#include<stdio.h>#define GENERIC_MAX(type) \
type type##_MAX(type x,type y)\
{ \return x > y ? x : y; \
}
//产生计算int类型较大值函数GENERIC_MAX(int) //替换到宏体内后int##_Max ⽣成了新的符号 int_Max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_Max ⽣成了新的符号 float_Max做函数名
int main()
{int r = int_MAX(28, 37);printf("%d\n", r);return 0;
}
命名
⼀般来讲函数的宏的使用语法很相似。所以语⾔本身没法帮我们区分⼆者。 那我们平时的⼀个习惯是: 把宏名全部大写 函数名不要全部大写
#undef
这条指令用于移除⼀个宏定义。
#undef NAME
//如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。
#include<stdio.h>
#define MAX 100
int main()
{int m = MAX;printf("%d\n", m);printf("%d\n", MAX);
#undef MAX
#define MAX 101int n = MAX;printf("%d\n", n);return 0;
}
命令行定义
在命令行中定义符号。用于启动编译过程
#include <stdio.h>
int main()
{int array[ARRAY_SIZE];int i = 0;for (i = 0; i < ARRAY_SIZE; i++){array[i] = i;}for (i = 0; i < ARRAY_SIZE; i++){printf("%d ", array[i]);}printf("\n");return 0;
}
//linux 环境演⽰
gcc - D ARRAY_SIZE = 10 programe.c
条件编译
在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很方便的。
#include <stdio.h>
#define __DEBUG__
//可通过注释该定义决定
int main()
{
int i = 0;
int arr[10] = {0};
for (i = 0; i < 10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0;
}
四种类型:
#include<stdio.h>
int main()
{int a = 0;//错误示范
#if a>2printf("hehe");//不过这里不能使用变量
#endifreturn 0;
}
int main()
{
#if 常量表达式printf("hehe");
#endifreturn 0;
}
//多条分支
#define M 100
int main()
{
#if M<50printf("haha");
#elif M>=50&&M<=80printf("hehe");
#elseprintf("heihei");
#endifreturn 0;
}//判断是否被定义
#define M 100
int main()
{
#if define(M)//#if !define(M) 反义//ifdef M 反:ifndef Mprintf("haha");
#endifreturn 0;
}//嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif
头文件的包含
#include "filename"
库文件包含
#include <filename.h>
嵌套文件包含
避免头文件重复
#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__
或者
#pragma once