预处理是C语言的一个重要功能,它由预处理程序负责完成。C语言编译器将一个.c文件经过:预处理(预编译)--->编译--->l链接--->汇编等系列操作形成可执行程序,合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
目录
一.预定义符号
二.#define
1.#define定义的标识符常量
2.#define定义宏
3.#define替换规则
4.#和##
5.带副作用的宏参数
6.宏和函数的比较
7.命名约定
三.#undef
四.命令行定义
五.条件编译
1.#if defined(#ifdef)
2.#if !defined(#ifndef)
3.#if
4.嵌套指令
六.文件包含
七.其它预处理指令
一.预定义符号
预定义符号是C标准规定的宏定义符号,有以下5种
1. __FILE__ ( 进行编译的源文件名称)
2. __LINE__ (文件当前行号)
3. __DATE__( 文件被编译的日期)
4. __TIME__ (文件被编译的时间)
5. __STDC__(如果编译器遵循ANSI C,值为1,否则未定义)
下面是预处理的使用样例:
二.#define
#define定义的标识符常量和宏都会在在预处理阶段,对程序中所有出现的标识符,宏名都会被定义的内容替换掉。
1.#define定义的标识符常量
格式:#define 标识符 字符串
其中,标识符为符号常量,字符串可以是常数、表达式、格式串等。
下面是#define定义标识符的例子:
- #define MAX 1000 //用MAX代替1000
- #define reg register //为register这个关键字,创建一个简短的名字
- #define do_forever for(;;) //用更形象的符号来替换一种实现
- #define CASE break;case //在写case语句的时候自动把 break写上。
- // 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
- #define DEBUG_PRINT printf("file:%s\tline:%d\t \
- date:%s\ttime:%s\n" ,\
- __FILE__,__LINE__ , \
- __DATE__,__TIME__ )
复制代码
2.#define定义宏
#define
机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(
macro
)或定义宏(define macro
)。
格式:
#define 宏名(
参数列表
) 字符串
其中的
参数列表
是一个由逗号隔开的符号表,它们可能出现在
字符串
中。
注意:
1.参数列表的左括号必须与宏名紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
2.所以用于对数值表达式进行求值的宏定义都应该对参数和替换的整体加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
( 如:
#define add(x,y) ((x)+(y))
)
3.#define替换规则
在程序中扩展
#define
定义符号和宏时,需要涉及几个步骤:
1.
在调用宏时,首先对参数进行检查,看看是否包含任何由
#define
定义的符号。如果是,它们首先被替换。
2.
替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3.
最后,再次对结果文件进行扫描,看看它是否包含任何由
#define
定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
4.#和##
1.#用于将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。#只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。
例如:
- #include<stdio.h>
- #define PRINT(N, FORMAT) printf("the value of "#N" is "FORMAT"\n", N)
- int main()
- {
- int a = 10;
- PRINT(a, "%d");
- return 0;
- }
复制代码
的输出结果是:
2.##用于把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。
如:
- #include<stdio.h>
- #define CAT(Class, Num) Class##Num
- int main()
- {
- int ClassNum = 100;
- printf("%d", CAT(Class, Num));
- return 0;
- }
复制代码
的运行结果是:
5.带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。(x+1;//不改变x本身大小,不带副作用 x++;//改变x本身大小,带有副作用)
6.宏和函数的比较
属
性
|
#define
定义宏
| 函数 |
代
码
长
度
|
每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长
|
函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码
|
执
行
速
度
|
更快
|
存在函数的调用和返回的额外开销,所以相对慢一些
|
操
作
符
优
先
级
|
宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号
|
函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
|
带
有
副
作
用
的
参
数
|
参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果
|
函数参数只在传参的时候求值一 次,结果更容易控制
|
参
数
类
型
|
宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型
|
函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是
不同的。
|
调
试
|
宏是不方便调试的
|
函数是可以逐语句调试的
|
递
归
|
宏是不能递归的
|
函数是可以递归的
|
7.命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
所以我们平时的一个习惯是:
1.把宏名全部大写
2.函数名不要全部大写
三.#undef
#undef这条指令用于移除一个宏定义。
例如:
四.命令行定义
命令行定义是许多C的编译器提供的一种允许在命令行中定义符号的方式,用于启动编译过程。
例如:定义不同长度的数组(如:int arr[VALUE]),在编译程序时,可以在命令行中指定VALUE的值。
五.条件编译
条件编译用于在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃。
1.#if defined(#ifdef)
#ifdef 标识符 (或#if defined(标识符)) 程序段1 #else 程序段2 #endif |
- #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__//如果__DEBUG__有定义,则printf("%d\n", arr[i]);参于编译
- printf("%d\n", arr[i]);
- #else
- printf("0\n");//否则printf("0\n");参于编译
- #endif //__DEBUG__
- }
- return 0;
- }
复制代码
2.#if !defined(#ifndef)
#ifndef 标识符(或#if !defined(标识符)) 程序段1 #else 程序段2 #endif |
- #include <stdio.h>
- #define __DEBUG__
- int main()
- {
- int i = 0;
- int arr[10] = { 0 };
- for (i = 0; i < 10; i++)
- {
- arr[i] = i;
- #ifndef __DEBUG__//如果__DEBUG__没有定义,则printf("%d\n", arr[i]);参于编译
- printf("%d\n", arr[i]);
- #else
- printf("0\n");//否则printf("0\n");参于编译
- #endif //__DEBUG__
- }
- return 0;
- }
复制代码
3.#if
#if 常量表达式 程序段1 #else 程序段2 #endif |
- #include<stdio.h>
- #define M 5
- int main()
- {
- #if M<5//M<5为printf("q\n");参与编译
- printf("q\n");
- #elif M==5//否则M==5为真printf("w\n");参与编译
- printf("w\n");
- #else//否则printf("e\n");参与编译
- printf("e\n");
- return 0;
- }
复制代码
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1
();
#endif
#ifdef OPTION2
unix_version_option2
();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2
();
#endif
#endif
|
六.文件包含
#include
指令可以使另外一个文件被编译,就好像它实际出现于
#include
指令的地方
一样。
这种替换方式是:预处理器先删除这条指令,并用包含文件的内容替换。(这样一个源文件被包含10次,那就实际被编译10次。)
头文件包含方式分两种
1.本地文件包含方式#include "xxx.h";2.库文件包含方式#include
这两种查找方式的查找策略不同:
#include" xxx.h "查找策略:先去代码所在路径下查找,如果找不到,再去库目录下查找
#include< xxx.h >查找策略:直接去库目录下查找
避免头文件重复包含方式
1.
#ifndef __TEST_H__
#define __TEST_H__
//
头文件的内容
#endif
//__TEST_H__
|
2.
七.其它预处理指令
好了,关于C语言预处理的介绍就到这里了,谢谢观看~