C语言 预处理命令——宏定义与条件编译
在编译和链接之前,还需要对源文件进行一些文本方面的操作,比如文本替换、文件包含、删除部分代码等,这个过程叫做预处理,由预处理程序完成。
#include的用法详解
#include
叫做文件包含命令,用来引入对应的头文件(.h文件)。#include
也是C语言预处理命令的一种。
#include
的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
#include 的用法(两种):
1 |
使用尖括号< >和双引号” “的区别在于头文件的搜索路径不同:
- 使用尖括号< >,编译器会到系统路径下查找头文件;
- 而使用双引号” “,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。
「在头文件中定义定义函数和全局变量」这种认知是原则性的错误!不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。
宏定义
#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
1 | #define 宏名 字符串 |
关于宏(macro)的翻译
计算机中宏又称为宏命令,是通过特殊的控制语,将一系列动作简便化。即:一种批处理的程序。
汉语汉字中的宏是雄大,大的意思。开始觉得macro是也大的意思,造成每次看宏就特别别扭,不能叫大指令吗?后来了解到macro实际是指巨大的,大量的意思,那么macro指令就有指令集合之意,那么中文叫大指令就不贴切了,就被翻译成宏了,但是翻译成宏就必须对宏的定义做成调整,可以直接把宏做动词时的意思为“把一个物体由小变大,即膨胀”,作形容词时“物体由小变大的过程,即膨胀的过程”。被宏定义的指令,可以理解为对指令进行压缩,并在预处理时进行解压。
宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。
带参数的宏定义
C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。
1 | #define 宏名(形参列表) 字符串 |
带参宏调用的一般形式为
1 | 宏名(实参列表); |
例如:
1 |
|
对带参宏定义的说明
- 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现
- 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。
- 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
带参宏定义和函数的区别
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
宏参数的字符串化和宏参数的连接
在宏定义中,有时还会用到#
和##
两个符号,它们能够对宏参数进行操作。
# 的用法
#用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义:
1 |
那么:
1 | printf("%s", STR(abc)); |
分别被展开为:
1 | printf("%s", "abc"); |
可以发现,即使给宏参数“传递”的数据中包含引号,使用#仍然会在两头添加新的引号,而原来的引号会被转义。
##的用法
##称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义:
1 |
那么:
1 | printf("%f\n", CON1(8.5, 2)); |
将被展开为:
1 | printf("%f\n", 8.5e2); |
C语言中几个预定义宏
预定义宏就是已经预先定义好的宏。
ANSI C 规定了以下几个预定义宏,它们在各个编译器下都可以使用:
- **LINE**:表示当前源代码的行号;
- **FILE**:表示当前源文件的名称;
- **DATE**:表示当前的编译日期;
- **TIME**:表示当前的编译时间;
- **STDC**:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
- __cplusplus:当编写C++程序时该标识符被定义。
条件编译详解 #if、##ifdef、#ifndef
这种能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
#if 的用法
1 |
|
#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。
#ifdef 的用法
#ifdef 用法的一般格式为:
1 | #ifdef 宏名 |
它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
也可以省略 #else:
1 | #ifdef 宏名 |
#ifndef 的用法
#ifndef 用法的一般格式为:
1 | #ifndef 宏名 |
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。
三者之间的区别
最后需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。
#error命令,阻止程序编译
#error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下:
1 |
例如:
当我们希望以 C++ 的方式来编译程序时,可以这样做:
1 |
#pragma使用方法
预处理命令总结
预处理指令是以#号开头的代码行,# 号必须是该行除了任何空白字符外的第一个字符。# 后是指令关键字,在关键字和 # 号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
指令 | 说明 |
---|---|
# | 空指令,无任何效果。 |
#include | 包含一个源代码文件。 |
#define | 定义宏。 |
#undef | 取消已定义的宏。 |
#if | 如果给定条件为真,则编译下面代码。 endif结尾 |
#ifdef | 如果宏已经编译,则编译下面代码。endif结尾 |
#ifndef | 如果宏没有定义,则编译下面代码。endif结尾 |
#elif | 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码。 |
#endif | 结束一个#if…#elif…#else…条件编译块。 |
#error | 用于在编译期间产生错误信息,并阻止程序的编译 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中。 |