前 言
C语言源程序是先经过预处理器进行预处理,然后经过编译器编译成二进制可执行代码。
一般将编译器套件、集成开发工具这两种工具进行集成,预处理过程和编译过程一并处理。
C语言预处理是通过预处理指令实现。每个预处理指令以#符号开头,以行为单位,来实现编译前的准备工作。例如文件条件引入、宏替换和条件编译。
预处理指令
C语言提供以下预处理指令。
注:预处理指令必须以#开头,#符号必须是当前行的第一个非空白字符。
注:预处理指令以一行作为处理单位,每行预处理指令是有多个预处理单词组成,单词之间使用空格或横向制表符分割。
预处理指令包括:
•#include
:包含一个指定的源文件
•#define
:定义一个宏
•#undef
:取消已定义的宏
• 条件编译 :#if #ifdef #ifndef #elif #else #endif
•#line
:产生下一代码行号和源文件名称
•#error
:产生一个诊断错误信息
•#pragma
:编译指示,预处理器实现自定义的编译动作
• #
:空指示,不起作用
文件包含
编译前引入特定的文件内容到当前的源代码文件中一起编译(主要用来引入头文件或其他源代码文件)。
有以下三种形式:
第1种形式:
#include <文件名> //一般从系统路径位置寻找引入的头文件。
第2种形式:
#include "文件名" //一般是先在用户定义的路径寻找该文件,如果找不到再去系统路径寻找。
第3种形式:
#include 任意的预处理单词
//该形式下的预处理单词也按照普通正文进行处理,处理完成以后,应和前两种之一匹配,再进行处理。
例如:
#include <stdio.h>
#include "mystdio.h"
#define MYFILE "mystdio.h"
#include MYFILE
宏替换
宏(英文:Macro),根据一系列预定义的规则替换一定的文本模式,作用是将一个简单的小命令或动作转化为一系列指令,C语言中的宏的作用就是在宏出现的位置展开替换的文本。
语法:
#define 宏名 替换文本 // 简单替换形式
#define 宏名(参数) 替换文本 // 带参数的宏,类似函数调用
例如:
#define MAX_LEN 100
char str[MAX_LEN];
//定义常量,使用宏定义字符数组最大长度,出现MAX_LEN全部替换成100,当需要修改数组长度时,只需要修改宏定义即可。
#define MAX_LEN 100
#undef MAX_LEN
char str2[MAX_LEN];
//取消MAX_LEN宏定义,在#undef命令之后,就不能使用该宏,否则编译出错。
#define SUM(a, b) (a+b)
int s = SUM(1,2);
double d = SUM(100.01,200.01)
//类似函数,使用宏定义计算参数a和b的和。
条件编译
根据条件(例如不同硬件平台),选择不同的代码进行编译,方便程序移植和跨平台。
if条件有三种形式:
#if 常量表达式
#ifdef 宏名称
#ifndef 宏名称
根据if条件值,当值为0时,忽略和不编译后面代码文本。
注:#if后面必须是常量表达式,不能是函数调用和变量。
其中#if可包含以下条件:
#if defined 宏名称
#if defined (宏名称)
#ifdef 等价于 #if defined
#if ! defined 等价于 #ifndef
else条件三种形式:
#elif:#else和#if的结合,后面常量表达式
#else:与#if配合使用
#endif:结束#if或#else条件块
例如:
#ifndef MYHEADER_H
#define MYHEADER_H "myheader.h"
// 头文件内容
#endif
//避免重复引入同一个文件多次,可以在被引入的头文件开头加入添加编译。
例如:不同硬件平台使用数字代替,x86=0,x64=1,arm=2。
#define X86 0
#define X64 1
#define ARM 2
#define ARCH 0
#if ARCH==0
#include "x86.h"
#elif ARCH==1
#include "x64.h"
#elif ARCH==2
#include "arm.h"
#else
#include "others.h"
#endif
或者:
#define DEBUG
#ifdef DEBUG
// 执行调试模式下的代码
#else
// 执行非调试模式下的代码
#endif
等价于:
#define DEBUG
#if defined DEBUG
// 执行调试模式下的代码
#else
// 执行非调试模式下的代码
#endif
行控制
预处理指令#line 用来控制编译器编译时警告和错误信息的行号和文件名。
两种形式:
1.#line 整数n
设置当前#line所在行的行号是整数n,后面的代码行均从此整数计数。
例如:
#include <stdio.h>
int main(){
int *p = NULL;
#line 30
*p = 0 return 0;
}
编译出错:提示是31行出错,*p=0 末尾缺少分号,出错的文件名默认是当前文件名。
2.# line 整数n 文件名称
设置当前#line所在行的行号和当前文件名。
# include <stdio.h>
int main(){
int *p = NULL;
#line 30 "mytest.c"
*p = 0
return 0;
}
编译出错:提示是mytest.c文件的31行出错,*p=0 末尾缺少分号。
出错处理指示
预处理指令#error用来控制编译器停止编译,显示特定的出错信息。
例如:
# include <stdio.h>
int main(){
#error "test error"
return 0;
}
编译出错:显示 "test error"
编译指示
预处理指令#pragma,各种编译器实现自定义预处理功能,用于扩展,例如禁用编译器的一些警告信息,拓展一些编译功能,标准没有定义具体的指令,需要根据不同编译器文档使用。
系统预处理的宏名
预定义的宏以两个下划线开头__
。
__LINE__当前源代码的行号
__FILE__当前的源代码文件名称
__DATE__编译日期,格式是Mmm dd yyyy
__TIME__编译时间,格式是hh:mm:ss
__STDC__表示当前编译器是否符合标准,1表示符合标准
例如:
# include <stdio.h>
int main(){
printf("%d\n",__LINE__);
printf("%s\n",__FILE__);
printf("%s\n",__DATE__);
printf("%s\n",__TIME__);
printf("%d\n",__STDC__);
return 0;
}
运行结果:
4
D:\Dev_C_Code\C_2.c
Aug 8 2022
13:50:22
1
有关宏中的#和##运算符
宏中的#运算符,将宏参数字符串常量化,具体是将#和跟在其后的参数转换成字符串,自动加入双引号和转义特殊字符。
例如:
# include <stdio.h>
# define TEST(s) "012345"#s"789\n"
int main(void){
printf(TEST(6));
printf(TEST("6"));
return 0;
}
输出:0123456789
012345"6"789
注:##
运算符将多个宏参数的值直接连接起来(不转换为字符串),
##
出现的两个宏参数中间,不能出现替换文本的开头和末尾。
例如:
# include <stdio.h>
# define TEST(s1,s2,s3) s1##s2##s3
int main(void){
printf("%d\n",TEST(1,2,3));
return 0;
}
输出:123
。
------------ END ------------