目录
前言
一、预处理指令
二、包含文件
举个例子
运行效果
三、宏定义指令
预定义宏
举个例子
运行效果
无参数的宏
举个例子
运行效果
使用宏定义时需要注意的地方
带参数的宏
举个例子
运行效果
四、条件编译
#ifdef
举个例子
运行效果
#ifndef
C语言由源代码生成可执行程序的过程:
C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件
- 其中编译预处理阶段,读取C源程序,对其中的预处理指令(以#开头的指令)和特殊符号进行处理
- 预处理过程先于编译器对源代码进行处理,读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行转换。
- 预处理过程还会删除程序中的注释和多余的空白字符
- 在C语言的程序中包括各种以符号 # 开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分,通过预处理命令可扩展C语言程序设计的环境
- 预处理指令是以#号开头的代码行,#号必须是该行除了任何空白字符外的第一个字符
- #后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换
重要的预处理器指令:
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
- 程序从main函数开始执行,在执行过程中,可调用当前文件中的函数,也可调用其他文件模块中的函数
- 如果在模块中要调用其他文件模块中的函数,首先必须在主模块中声明该函数原型。一般都是采用文件包含的方法,包含其他文件模块的头文件
- 文件包含中指定的文件名即可以用 " " 括起来,也可以用 <>
格式如下:
#include "文件名"
#include <文件名>
- 如果使用<>括起文件名,则编译程序将到C语言开发环境中设置好的 include文件中去找指定的文件(/usr/include)
- C语言的标准头文件都存放在/usr/include文件夹中,所以一般对标准头文件采用<>;对自己编写的文件,则使用 " "
- #include包含文件,可以是 ".h",表示C语言程序的头文件,也可以是".c",表示包含普通C语言源程序
我们之前写的 test34.c 和 _public.h 和 _public.c ,不知道大家还有没有印象
_public.h
#include
#include
#include
int min(const int i1,const int i2);
int max(const int i1,const int i2);
_public.c
#include "_public.h"
int min(const int i1,const int i2)
{
if (i1i2) return i1;
return i2;
}
test34.c
#include "_public.h"
int main()
{
int x,y,imin,imax;
x=100;
y=200;
imin=min(x,y);
imax=max(x,y);
printf("imin=%d,imax=%d\n",imin,imax);
}
使用 #define 命令并不是真正的定义符号常量,而是定义一个可以替换的宏。被定义为宏的标识符称为“宏名”。在编译预处理过程时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”
ANSI C 定义了许多宏。可以使用这些宏,但是不能直接修改这些预定义的宏
宏 | 描述 |
---|---|
__DATE__ | 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量 |
__TIME__ | 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量 |
__FILE__ | 这会包含当前文件名,一个字符串常量 |
__LINE__ | 这会包含当前行号,一个十进制常量 |
__STDC__ | 当编译器以 ANSI 标准编译时,则定义为 1 |
#include
int main()
{
printf("当前文件名 :%s\n", __FILE__ );
printf("当前日期 :%s\n", __DATE__ );
printf("当前时间 :%s\n", __TIME__ );
printf("当前行号 :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );
}
其定义格式如下:
#define 宏名 字符串
- # 表示这是一条预处理命令(凡是以“#”开始的均为预处理命令)
- define 关键字 "define" 为宏定义命令
- 宏名 是一个标示符,必须符合C语言标示符的规定,一般以大写字母标识宏名
- 字符串 可以是常数,表达式,格式串等
- 预处理命令语句后面一般不会添加分号
#include
#define PI 3.141592
int main()
{
printf("PI is %lf\n",PI);
}
执行预编译指令 gcc -E -o test100.E test100.c,得到 test100.E 文件
用vim打开test100.E文件,按 shift+g 跳到最后一行
可以看到PI被替换成 3.141592
- 宏定义是宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,编译预处理时不会对它进行语法检查,如有错误,只能在编译已被宏展开后的源程序时发现
- 习惯上宏名用大写字母表示,以方便与变量区别。但也可以用小写字母
#define命令定义宏时,还可以为宏设置参数。与函数中的参数类似,在宏定义中的参数为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,还要用实参去代换形参
带参宏定义的一般形式为:
#define 宏名(形参表) 字符串
在定义带参数的宏时,宏名和形参表之间不能有空格出现,否则,就将宏定义成为无参数形式,而导致程序出错
#include
#define MAX(x,y) (x>y)?x:y
int main()
{
printf("MAX is %d\n",MAX(3,5));
}
执行预编译指令 gcc -E -o test101.E test101.c,得到 test101.E 文件
带参数的宏不容易理解,简单了解一下即可
条件编译有多种格式,在这里只介绍最常用的两种格式 #ifdef 和 #ifndef
#ifdef命令的使用格式如下:
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
- 如果#ifdef后面的标识符已被定义过,则对 "程序段1" 进行编译;
- 如果没有定义标识符,则编译“程序段2”
- 一般不使用#else及后面的 "程序2"
#include
#define Linux
int main()
{
#ifdef Linux
printf("这是Linux系统\n");
#else
printf("这不是Linux系统\n");
#endif
}
执行预编译指令 gcc -E -o test102.E test102.c,得到 test102.E 文件
而#ifndef的意义与#ifdef刚好相反,其格式如下:
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
- 如果未定义标识符,则编译“程序段1”;否则编译“程序段2”
- 一般用 #ifndef 来防止头文件被重复包含
打开/usr/include/stdio.h文件,可以看到
第一条有效行的代码是
#ifndef _STDIO_H
接下来是
#define _STDIO_H 1
最后一行是。
#endif
我们自定义自己的头文件时,也可以模仿这种写法