C语言 预处理
一、预处理
1.预处理的概念
- C程序编译流程:
步骤 | 阶段 | 生成文件 | 主要任务 |
---|---|---|---|
1 | 预处理阶段 | “.i” | a.宏替换 b.去注释 c.头文件展开 d.条件编译 |
2 | 编译阶段 | “.s” | 生成汇编代码 |
3 | 汇编阶段 | “.o” | 生成机器码 |
4 | 链接阶段 | “.out” | 生成可执行文件 |
5 | 运行可执行文件 | - | 运行可执行文件 |
-
以“#”号开头的为__预处理指令__。在源文件中这些指令放在函数之外,一般在源文件的前面,称为__预处理部分__。
-
所谓预处理是指在进行编译的第一遍扫描(词法和语法)之前所做的工作。预处理由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
-
预处理在源代码编译之前对其进行的一些文本性质的操作,对源程序编译之前做一些处理,生成扩展的C源程序。
-
预处理的任务
- 删除注释
- 插入被#include指令包含的文件内容
- 定义和替换由#define指令定义的符号(宏替换或宏展开)
- 条件编译
-
预处理的格式
- 以“#”开头
- 占单独书写行
- 语句尾不加分号
2.宏定义的概念
-
在C语言源程序中允许用一个标识符来表示一个字符串,称为宏。在预处理时,对程序中所有出现的宏,都用宏定义中的字符串去替换,这称为宏替换或宏展开。
- 如:#define PI 3.14
-
宏定义是由源程序中的宏定义指令完成的,宏替换是由预处理程序自动完成的。
-
在C语言中,宏定义分为无参宏定义和带参宏定义。
二、无参宏定义
1.无参宏定义的语法结构
无参宏定义的语法:#define 标识符(宏名) [字符串(宏体)]
- 功能:定义可在程序中使用宏,宏的内容由字符串(宏体)替代。
- 宏体可缺省,表示宏名定义过或消除宏体。
#define YES 1
#define NO 0
#define OUT printf("Hello\n") //注意不加分号
#define WIDTH 80
#define LENGTH (WIDTH+40) //必须加上括号()
#define LENGTH2 WIDTH+40
#define COMPANY "Google"
var=LENGTH*2 //宏展开var=(80+40)*2=240
var=LENGTH2*2 //宏展开var=80+40*2=160
OUT; //相当于语句printf("Hello\n");
#define DEBUG_PRINT printf("File %s line %d: "\
"x=%d,y=%d,z=%d\n",__FILE__,__LINE__,x,y,z)
//"\"为续行符,__FILE__输出文件名,__LINE__输出当前行号。
int x=1;int y=2;int z=x+y;
DEBUG_PRINT;
2.宏的移除
语法:#undef 宏名
- 功能:删除前面定义的宏
3.宏定义的规则
- #表示这是一条预处理指令,以#开头的均为预处理指令
- #define为宏定义指令,标识符为所定义的宏名。
- 宏名一般习惯用大写字母表示,以便与变量名相区别。
- 宏定义不是C语言语句,不必在行末加分号
- 字符串(宏体)可以是常数,表达式,格式化字符串等。对于数值表达式进行求值的宏定义应该使用括号。
- 在进行宏定义时,可以引用已定义的宏名,可以层层替换。
- 宏替换只做字符串替换,不分配内存空间,不作正确性检查。
- 宏的有效范围为定义之后到本源文件结束,可以用#undef指令终止宏定义的作用域
三、带参宏定义
1.带参宏定义的语法结构
- 带参宏定义语法:
#define 宏名(形参列表) 字符串(宏体) - 带参宏调用的语法:
宏名(实参表);- C语言允许宏带有参数,在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。
- 对带参数的宏,在调用中不仅要宏展开,而且要用实参去替换形参。
- 示例:
#define S(a,b) a*b
...
area = S(3,2);
//宏展开:area=3*2;
2.带参宏定义的规则
- 带参宏定义中,宏名和形参列表之间不能有空格出现。
如:#define MAX(a,b) (a>b)?a:b
不可写成:#define MAX (a,b) (a>b)?a:b 这会被认为是无参宏定义,宏MAX代表字符串(a,b)(a>b)?a:b - 在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。
- 在宏定义中的形参是标识符,而宏调用中的实参可以是表达式
- 宏体内的形参通常要用括号括起来以免出错。
查看预处理生成文件可使用命令:
gcc -E -o 文件名.i 文件名.c
more 文件名.i
或者
tail -20 文件名.i
3.带参宏定义和函数的区别
比较 | 带参宏定义 | 函数 |
---|---|---|
处理时间 | 预处理编译时 | 程序运行时 |
参数类型 | 无类型问题 | 定义实参和形参类型 |
处理过程 | 不分配内存,只是简单的字符置换 | 分配内存,先求实参值再传给形参 |
程序长度 | 宏展开后会将程序变长 | 不改变程序长度 |
运行速度 | 不占运行时间 | 调用和返回占时间 |
支持递归 | 不支持 | 支持 |
四、条件编译
1.条件编译的概念
- 一般情况下,源程序中所有的行都进行编译,但是有时希望对其中一部分内容在满足一定条件下才进行编译,也就是对一部分内容指定编译的一熬煎,这就是条件编译。
- 条件编译可以指定代码的一部分是被正常编译还是被完全忽略
- 条件编译有利于提升程序的可移植性,增加程序的灵活性
2.条件编译的语法结构
- 条件编译语法一:
#ifdef 标识符(宏名) //或 #if defined(标识符)
程序段1
#else
程序段2
#endif
- 当所指定的宏已经被#define指令定义过,则在程序编译阶段只编译程序段1,否则编译程序段2。#else可以没有
- 条件编译语法二:
#ifndef 标识符(宏名) //或 #if !defined(标识符)
程序段1
#else
程序段2
#endif
- 当所指定的宏未被#define指令定义过,则在编译程序段1,否则编译程序段2。
- 条件编译语法三:
#if (常量表达式1)
程序段1
#elif (常量表达式2)
程序段2
#else
程序段3
#endif
五、文件包含
1.文件包含的概念
-
文件包含是C预处理程序的另一个重要功能,被包含文件的名字必须用双引号或一对尖括号括起来。
-
文件包含的语法:
#include<文件名>或者#include “文件名”- 功能:一个源文件可将另一个源文件的内容全部包含进来,从而把指定的文件和当前的源文件连成一个源文件。
- 处理过程:在预处理时,用被包含文件的内容取代该文件包含指令,再对包含后的文件作一个源文件编译。
- 一般而言,若调用标准库函数用#include<文件名>,若要包含用户自己编写的文件用#include"文件名"。
- 一个include指令只能指定一个被包含文件,允许嵌套包含。被包含的文件可以是源文件(.c)或者头文件(.h)。
-
文件包含的搜索方式
-
#include<文件名>
- 若指定文件目录(如include,-Iinclude)则从此目录中找,否则按标准方式查找。
- 标准方式:从系统标准文件所在目录(/usr/include或者/usr/lib)中寻找要包含的文件
-
#include"文件名"
- 先从存放C源文件的目录中查找,然后若用户指定文件目录(如include,-Iinclude)则从此目录中找,若找不到再按标准方式查找。
-
-
文件包含的作用
- 一个大的程序可以分为多个模块,由多个程序员分别编程。有些公用的符号常量、结构体声明或宏定义等可单独组成一个文件,在其他文件的开头用包含指令包含该文件即可使用
2.多重包含
- 同一个文件被包含多次称为多重包含,不能解决多个源文件的多重包含
- 多重包含可能会出现重复定义的编译错误
- 为了防止多重包含可使用条件编译,如:
#ifndef __HEAD_H__
#define __HEAD_H__
#include"head.h"
#endif
/*文件名:demo.h*/
#ifndef __DEMO_H__
#define __DEMO_H__
int a=10;
#endif
/*文件名:demo_test.c*/
#include<stdiu.h>
#include"demo.h"
#include"demo.h"
int main(){
printf("a:%d\n",a);
}
六、预处理操作符和预定义宏
1.预处理操作符
- 在C语言中有两个预处理操作符“#”和“##”,它们可以在#define中使用。
操作符“#”通常称为__字符串化的操作符__,它把其后的串变成用双引号包围的串。
如:
#define PRINT(FORMAT,VALUE) \
printf("the value of" #VALUE "is" FORMAT "\n",VALUE)
...
int x=10;
PRINT("%d",x+3); //打印 the value of x+3 is 13
连接操作符##可以把两个独立的字符串连接成一个字符串
如:
#define ADD_TO_SUM(sum_number,value) \
sum##sum_number+=value //此处sum_number相当于sum的编号
...
ADD_TO_SUM(5,25); //运算: sum5 += 25
printf("sum5:%d\n",sum5);
2.预定义宏和其他指令
- 预定义宏:
预定义宏 | 意义 | 格式 |
---|---|---|
_FILE_ | 进行编译的源文件名 | %s |
_LINE_ | 文件当前的行号 | %d |
_DATE_ | 文件被编译的日期(格式:“Mmm dd yyyy”) | %s |
_TIME_ | 文件被编译的时间(格式:“hh:mm:ss”) | %s |
_func_ | 当前所在函数名 | %s |
- 其他预处理指令
预处理指令 | 意义 |
---|---|
#error | 在终端输出#error后面的错误相关信息 |
#line | 设置当前文件从哪一行开始计算,对__LINE__的输出有影响 |
#pragma | 编译时在终端输出#pragma后面的信息 |