目录
1 预处理
1.1 简介
预处理器
不是编译器
的组成部分,但是它是编译过程中一个单独的步骤。简言之,预处理器
只不过是一个文本替换工具而已,它们会指示编译器
在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)
简写为 CPP
所有的预处理器命令都是以井号(#
)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
这些预处理器命令必须在一行的开头以(#
)开始,末尾不再加;
号,以区别于C语言的其他语句
下面列出了所有重要的预处理器指令:
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
1.2 预处理器实例
分析下面的实例来理解不同的指令。
#define MAX_ARRAY_LENGTH 20
这个指令告诉 CPP
把所有的 MAX_ARRAY_LENGTH
定义为 20。使用 #define
定义常量来增强可读性。
#include <stdio.h>
#include "myheader.h"
这些指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。
#undef FILE_SIZE
#define FILE_SIZE 42
这个指令告诉 CPP
取消已定义的 FILE_SIZE,并定义它为 42。
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
#ifdef DEBUG
/* Your debugging statements here */
#endif
这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果向 gcc 编译器传递了 -DDEBUG
开关量,这个指令就非常有用。它定义了 DEBUG
,可以在编译期间随时开启或关闭调试。
调试程序,在程序开头定义一个符号常数DEBUG
(值为1或0),在程序任何需要设置调试信息之处,加入以下信息:
if DEBUG
调试代码
endif
这里定义DEBUG
为1时,为调试状态,即输出跟踪信息。在调试时,程序中定义DEBUG
的语句为:#define DEBUG 1
而调试结束后,将这条语句改为:#define DEBUG 0
这样,只需修改#define DEBUG
这一条语句,便达到了自动删除或增加程序中众多调试语句的效果
1.3 预定义宏
1.3.1 简介
ANSIC 定义了许多宏。在编程中可以使用这些宏,但是不能直接修改这些预定义的宏
宏 | 描述 |
---|---|
__DATE__ |
当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。 |
__TIME__ |
当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。 |
__FILE__ |
这会包含当前文件名,一个字符串常量。 |
__LINE__ |
这会包含当前行号,一个十进制常量。 |
__STDC__ |
当编译器以 ANSI 标准编译时,则定义为 1。 |
注意:
上面前后都是两横线
让我们来尝试下面的实例:
#include <stdio.h>
main()
{
printf("File :%s\n", __FILE__ );
printf("Date :%s\n", __DATE__ );
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );
}
结果:
File :test.c
Date :Jun 2 2012
Time :03:36:24
Line :8
ANSI :1
1.3.2 不带参数的宏
不带参数的宏定义命令行形式如下:
#define 宏名 替换文本
在#define
、宏名
和替换文本
之间用空格隔开
例如:
#define LENGTH 300
其中标识符LENGTH
称为宏名
,是用户自定义的标识符,不能与同在一段程序中的其他标识符同名。编译时,编译预处理程序对源程序中所有名为LENGTH
的标识符用300
来替换,这个替换过程称为宏展开。
使用宏名替代一个字符串,其中一个主要的目的是减少程序中重复书写某些字符串的工作量,比如在程序中一些不太好记忆的参数,重复书写容易出错且很繁琐,这时用宏名来代替该字符串就可以使程序简单明了。其次当程序的一些常量需要改变时,如果没有宏名,那么整个程序用到该常量的地方都需人工一 一修改,若用宏名,则只需改变宏定义命令行,一改全改
同一个宏名不能重复定义。
如:
#define PI 0.14159
#define PI 0.1416
二条语句不能同时出现在一段程序中。
宏定义是用宏名代替一个字符串,也就是简单的置换,并不作语法检查。如:
#define PI 0.14159
中的0.14159如果输入时不小心将数字1误输为字母i,那么在替换时也照样代入,只有在对已作宏展开后的源程序编译链接时才会出现错误。
宏定义与一般C语言语句不同,末尾不能加分号,否则会连分号一起置换
可以用undef
命令终止宏定义的作用域。一般而言,一个宏定义,从被定义开始直至文件末尾,全程有效。若要更改其作用域则可以用undef
命令。如:
define AAA 10
main()
{
...
}
undef AAA
在undef AAA
语句后,AAA的作用域终止,不能再使用未定义的标识符AAA
1.3.3 参数化的宏
在C语言程序设计中,宏定义还可以用于带参数的宏。带参数的宏定义的一般形式为:
define 宏名(参数表) 字符串
CPP 一个强大的功能是可以使用参数化的宏来模拟函数。例如,下面的代码是计算一个数的平方:
int square(int x) {
return x * x;
}
我们可以使用宏重写上面的代码,如下:
#define square(x) ((x) * (x))
在使用带有参数的宏之前,必须使用 #define
指令定义。参数列表是括在圆括号内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格
#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
printf("Max between 20 and 10 is %d\n", MAX(10, 20));
return 0;
}
结果:
Max between 20 and 10 is 20
从形式上,带参数的宏定义和函数似乎容易混淆,从上面的例子可以看到,二者在程序中起的作用、以及书写规范上,确实存在一定的相似之处,然而,二者并不相同,主要有以下区别:
- 函数在调用时,先求出实参表达式的值,再用这个值赋给形式参数;但带参数的宏定义仅仅进行字符替换。
- 函数调用是在程序运行时分配临时内存单元;而宏展开是在编译时进行,展开时不分配内存单元,不进行值传递。
- 宏替换不占运行时间,只占编译时间;函数调用占运行时间。
- 宏替换后使源程序增长;而函数调用不会使源程序增长。
- 在使用带参数的宏的时候,对带参数的宏的展开只是用语句中宏名后的实参字符串代替
#define
命令中的形参,这与函数参数传递时,实参将值传递给形参不同,在编写带参数的宏时需加以注意。
1.4 预处理器运算符
预处理器提供了下列的运算符来创建宏
1.4.1 宏延续运算符()
宏延续运算符(\
):一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。例如:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
1.4.2 字符串常量化运算符(#)
字符串常量化运算符(#
)
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#
)。在宏中使用的该运算符有一个特定的参数或参数列表。
#include <stdio.h>
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
int main(void)
{
message_for(Carole, Debra);
return 0;
}
结果:
Carole and Debra: We love you!
1.4.3 标记粘贴运算符(##)
宏定义内的标记粘贴运算符(##
)会合并两个参数。它允许在宏定义中 两个独立的标记被合并为一个标记
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void)
{
int token34 = 40;
tokenpaster(34);
return 0;
}
结果:
token34 = 40
这是怎么发生的,因为这个实例会从编译器产生下列的实际输出:
printf ("token34 = %d", token34);
这个实例演示了 token##n
会连接到 token34
中,在这里,我们使用了字符串常量化运算符(#)和标记粘贴运算符(##)。
1.4.4 defined() 运算符
预处理器 defined
运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define
定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。下面的实例演示了 defined() 运算符的用法:
#include <stdio.h>
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void)
{
printf("Here is the message: %s\n", MESSAGE);
return 0;
}
结果:
Here is the message: You wish!
2 头文件
2.1 头文件讲解
在编写C语言程序时,可以把宏定义语句按照功能不同分别存入到不同的文件,当需要某一类宏定义时,就无需在程序中重新定义,只要把这些宏定义所在的文件,包含在程序的开头就可以了。
文件包含
就是在一个文件中,包含另外一个文件的全部内容,用include命令来实现文件包含的功能。
include
命令的形式如下:
#include "文件名"或 include <文件名>
- 如果文件名是用
双引号
括起来的,系统将先在源程序所在的目录内查找指定的包含文件,如果找不到,再按系统指定的标准方式到相关的目录去寻找。 - 如果文件名用
尖括号
括起来,则系统将直接按照指定的标准方式到相关的目录中去查找。在预编译时,预编译程序将用指定文件中的内容来替换此命令行。
文件包含在使用时应注意:
一条文件包含命令只能包含一个文件。如果需要包含多个文件,就必须使用多条文件包含命令。 例如,如果要包含两个文件file1.h和file2.h就不能这样写:
#include "file1.h,file2.h"
或
#include "file1.h" ,"file2.h"
必须写成:
#include "file1.h"
#include "file2.h"
其先后顺序与文件内容无关。但如果一个文件要使用另一个文件的内容,则后者文件应写在前面,即如果file1.h
要使用file2.h
文件中的内容,则文件包含语句必须按如下顺序写:
include "file2.h"
include "file1.h"
文件包含的定义是可以嵌套的,文件包含的嵌套是指一个被包含的文件中还可以包含其他文件。
被包含的文件常以.h
结尾,这类文件中往往是程序所需要的一些说明
、定义
,如符号常量的定义
、类型定义
、带参数的宏定义
、数组
、结构
、共用体和枚举的定义
等等,以及外部变量的定义、函数的定义或说明。但是被包含的文件可以是任意的文件,不一定是.h
文件,也可以是C语言源文件
2.2 头文件引用
引用头文件
相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。那么建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件
2.2.1 只引用一次头文件
如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
这种结构就是通常所说的包装器 #ifnde
f。当再次引用头文件时,条件为假,因为 HEADER_FILE
已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它
2.2.2 有条件引用
有时需要从多个不同的头文件中选择一个引用到程序中。例如,需要指定在不同的操作系统上使用的配置参数。可以通过一系列条件来实现这点,如下:
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
但是如果头文件比较多的时候,这么做是很不妥当的,预处理器使用宏来定义头文件的名称。这就是所谓的有条件引用。它不是用头文件的名称作为 #include
的直接参数,只需要使用宏名称代替即可:
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
SYSTEM_H 会扩展,预处理器会查找 system_1.h,就像 #include 最初编写的那样。SYSTEM_H 可通过 -D 选项的 Makefile 定义
标签:__,头文件,定义,C语言,运算符,include,预处理,define From: https://www.cnblogs.com/jingzh/p/16841959.html