C预处理

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后面的信息
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值