C预处理器和C库
编译程序之前,先由预处理器检查程序。根据程序中使用的预处理器指令,预处理器用符号缩略语所代表的内容替换程序中的缩略语。
1.编译器的处理步骤:
- 第一步:翻译处理:工作内容(1)把源代码中出现的字符映射到源字符集。
(2)编译器查找反斜线后面紧跟换行符的实例并删除这些实例。将多个物理行替换为一个逻辑行。
(3)编译器将文本划分为预处理的语言符号(token)序列和空白字符及注释序列(编译器会将注释序列替换为一个空格字符。所以注释序列可以出现在源代码的任何地方)。
- 第二步:预处理:
- 预处理的格式:ANSI标准允许#符合前后有空格或制表符,还允许在#和指令的其余部分之间有空格。但是旧版本均不允许这些空格出现。
- 预处理范围:预处理指令从#开始,到其后第一个换行符为止。所以预处理指令不允许换行。所以可以使用反斜线和换行符组合使预处理指令跨越多行。
- 系统把宏的主体定义为语言符号(Token)类型字符串。而不是字符型字符串。Token是指由空格符隔开的单词。“2*3”是一个Token,“2 * 3”是三个Token组成的字符串。
- 第三步:把源代码翻译为机器语言。第一步和第二步只是对源代码文件进行文本处理,进行字符串方面的替换。真正的根据语义翻译为机器语言是在这步。
2.预处理器(preprocessor)种类
预处理器可以使用反斜线和换行符组合可以横跨多个物理行,但是其最终只占用一个逻辑行。
第一部分:#define定义常量
- #define :格式:#define macro body
- macro是被称为宏的缩略语,其是一个根据变量命名规则的命名的单词。
- body被称为实体。可以是任何字符串。预处理器将程序中宏实例替换为实体。
- #define使用参数:#define macro body 其中marco的格式类似函数.由name(args)组成。但是name和(之间不能有空格。括号内部和body一样,可以有空格)
- 注意在body中的参数和body都要用括号括起来。如
#define ADD(X, Y) X + Y //compiled rightly, but not safe enough #define ADD(X, Y) ((X) + (Y)) //good #define ADD (X, Y) ((X) + (Y)) //error! No spaces between ADD and (X, Y)
- 注意在body中的参数和body都要用括号括起来。如
- #运算符:常量字符串中和参数相同的字符子串不会被替换为参数的字符串形式。如果想在常量字符串中使用参数字符串,那么ANSI C允许在body中使用#符号。如果x为参数名,那么#x就是相应的“x”。
- ##运算符:#是获取参数的字符串形式,而##是将两个Token拼接起来得到一个新的Token.
如 #define NAME(N) x N 中x N就是空格符隔开的两个token。但是如果#define NAME(N) x ## N 就是将NAME(N)替换为xN.
- 总之,#用来拼接字符型字符串,##用来拼接语言符号型符号串。
- 可变宏:…和__VA_ARGS__
- 函数宏中参数列表的最后一个参数为省略号(…)。预定义宏__VA_ARGS__用在body中代表省略号代表什么。在宏拓展中,将__VA_ARGS__替换为…位置实际上的参数。注意,省略号只能替代最后的宏参数。
- 总结:
- 1,宏的名字中macro名字部分不能有空格,但是在body替代字符串中可以使用空格。在参数列表中可以使用空格。
- 2,在body部分,用圆括号括住每个参数,并括住宏的整体定义。
第二部分:文件包含 #include
- #include <XX.h> 优先搜索系统目录。 #include “XX.h”优先搜索当前目录
- 头文件中可以包含:
- 1.明显常量,如stdio.h中的EOF,NULL和BUFFSIZE 2.宏函数 3.函数声明
- 4.结构模板定义。如stdio.h中的FILE结构定义。5.类型定义,使用#define或者typedef。
#ifndef _FILE_DEFINED #define _FILE_DEFINED typedef struct _iobuf { char* _ptr; int _cnt; char* _base; int _flag; int _file; int _charbuf; int _bufsiz; char* _tmpfname; } FILE; #endif /* Not _FILE_DEFINED */
-
6,如果某个变量要多个文件共享。则可以在头文件中进行引用声明。
extern int status; //头文件中 int status; //源文件中
-
7,如果某个const常量需要多个文件共享。则可采用在头文件中声明static const int MAX = 100;
-
8,内联函数,inline function。因为编译器优化时,必须知道函数定义的内容。所以源文件调用的内联函数的定义,必须在此源文件中。inline 函数具有内部链接,可以将inline function定义放在头文件中。但是普通函数具有外部链接,则只能有一次定义。
第三部分:其它指令。因为系统变量多用宏定义。所以它们多用于更改宏定义。改变编译时编译环境,方便移植系统。
-
#undef指令:取消定义一个给定的#define,即使该宏之前未被定义。可用于重定义某个宏。
-
条件编译指令:#if、#ifdef、#ifndef、#else、#elif、#endif
- #ifdef、#ifndef后面跟上标识符
,判断该标识符是否已经被#define或者是否还未被#define定义。#ifndef多用于防止多次包含同一头文件。#else作用类似if-else语句中的else,选择不同块。
- #if后面跟的是常量整数表达式和逻辑表达式。
如果表达式非零或者为真,则为真。#if 可以和defined运算符结合。defined(X),如果标识符X已经定义,返回1,否则返回0.#if defined优点为可以和#elif结合。
-
#if、#ifndef、#ifdef后面必须有对应的#endif。这样相当于if-else语句中的{},划分出指令的作用范围。
- #ifdef、#ifndef后面跟上标识符
-
#line :指令用于重置由__LINE__和__FILE__宏报告的行号和文件名 。#error:指令使预处理器发出一条错误消息,该消息包含指令中的文本。#warning:指令是预处理器发出一条警告消息,该消息包含指令中的文本。
-
#line 100 //将__LINE__设置为100 #line 100 "new.c" //将__LINE__设置为100, 文件名设置为"new.c" #line "new.c" //错误,#line参数只能为上述两种形式 #error This is a example.//编译器编译到这句会报错。 #warning This is a warning//编译器编译到这句,会发出警告
第四部分:重要的C库
-
通用工具库:#include <stdlib.h>
-
快排函数qsort() 函数原型:void qsort(void *base, size_t nmemb, size_t size, int (*cmpar)(const void *, const void *));
-
ANSI C支持将任何数据类型指针转换为void类型指针。第一个参数为要排序的数组头部的指针,第二个参数为参与排序的项目数量。因为第一个参数为void *,所以第三个参数为数据对象的大小。第三个为排序函数,返回值分别为正,零,负,代表大,相等,小于。