C Primer Plus (第五版)中文版——第 16 章 C 预处理器和 C 库

16.1  翻译程序的第一步

对程序作与处理前,编译器会对它进行几次翻译处理:

  • 首先,编译器把源代码中出现的字符映射到源字符集。该过程处理多字节字符和三元字符。
  • 其次,编译器查找反斜线后紧跟换行符的实例并删除这些实例。
  • 接下来,编译器将文本划分成预处理的语言符号序列、空白字符、注释序列。

16.2  明显常量:#define

每个 #define 行(逻辑行)由三部分组成:

  • 第一部分:#define 指令自身。
  • 第二部分:宏(macro)。宏的名字遵循 C 变量命名规则。
  • 第三部分:替换列表或主体。

预处理器在程序中发现宏后,会用替换文本代替该宏。例外情况是双引号中的宏。从宏变成最终的替换文本的过程称为宏展开。

预处理器不进行计算,只按照指令进行文字替换操作。

16.2.1  语言符号

从技术方面看,系统把宏的主体当作语言符号(token)类型字符串,而不是字符型字符串。C 预处理器中的语言符号是宏定义主体中单独的词(word),词与词之间用空白字符分开。

从语言符号字符串的观点看,空格只是分隔主体中语言符号的符号;从字符型字符串的观点看,空格也是主体的一部分。

16.2.2  重定义常量

ANSI C 标准只允许新定义与就定义完全相同,即主体具有相同顺序的语言符号。例如,下面两个定义相同:

#define SIX 2 * 3
#define SIX 2   *   3

16.3  在 #define 中使用参数

通过使用参数,可以创建外形和作用都与函数相似的类函数宏(function-like macro)。宏的参数用圆括号括起来。如:

#define SQUARE(X) X*X        //类函数宏定义
num = SQUARE(2);             //使用

 SQUARE 为宏标识符,SQUARE(X) 中的 X 为类函数宏的参数,X*X 为替换列表。预处理器在程序中发现宏后,会用替换文本代替该宏(只进行文本替换,而不进行计算)。

16.3.1  利用宏参数创建字符串:# 运算符

# 运算符可以把语言符号转化为字符串,该过程称为字符串化。

#define SQUARE(X) printf("The squuare of "#x" is %d.\n", ((X)*(X)))        //定义
PSQR(5);                    //调用。调用时用"5"代替了#X

The squuare of 5 is 25.     //输出结果

16.3.2  预处理器的粘合剂:## 运算符

## 运算符可以把两个语言符号组和成一个语言符号。

#define XNAME(X) n ## X        //定义
XNAME(5);                      //调用
n5                             //输出结果

16.3.3  可变宏:... 和 __VA_ARGS__

实现思想:在宏定义中参数列表的最后一个参数为省略号(...)。这样预定义宏 __VA_ARGS__ 就可以被利用在替换部分中。

#define PR(X,...) printf("Message "#X": "__VA_ARGS__)        //定义
PR(5,"MrGold.\n");                          //调用
Message 5: MrGold.        //输出结果

16.4  宏,还是函数

宏与函数间的选择实际上是时间与空间的权衡。

  • 编译器限制宏只能定义成一行。宏产生内联代码,即在程序中产生语句。若使用宏20次,则将20行代码插入程序中。
  • 使用函数20次,程序中只有一份函数语句的拷贝,节省空间。但程序的控制必须转移到函数中并返回调用程序,花费的时间比内联代码多。

对于宏的几点注意:

  • 宏的名字遵循 C 变量命名规则,不能含有空格。替换列表中可以含有空格。
  • 用圆括号括住每个参数,并括住宏的整体定义。
  • 用大写字母表示宏函数名。

16.5  文件包含:#include

预处理器发现 #include 指令后,就会寻找后跟的文件名并把这个文件的内容包含到当前文件中。被包含的文件中的文本将替换源代码文件中的 #include 指令,相当于被包含文件中的全部内容键入到源文件中的这个特定位置。 #include 指令有两种形式:

#include <stdio.h>        //文件名放在尖括号中,优先搜索系统目录
#include "mystuff.h"      //文件名放在双引号中,优先搜索本地目录

16.5.1  头文件:.h 文件

头文件包含置于程序头部的信息。头文件经常包含预处理语句。头文件可由系统提供,也可以自己创建。

16.5.2  使用头文件

头文件内容最常见的形式包括:

  • 明显常量:如 stdio.h 文件定义 EOF、NULL。
  • 宏函数:如 ctype.h 文件包含 ctype 函数的宏定义。
  • 函数声明:如 string.h 文件包含字符串函数系列的函数声明。
  • 结构模板定义:如 stdio.h 文件包含 FILE 结构的声明。
  • 函数定义:如 stdio.h 文件用 #define 使 FILE 代表指向 FILE 结构的指针。

16.8  C 库

16.8.1  访问 C 库

首先,通常可以在多个不同位置找到库函数。其次,不同的系统使用不同的方法搜索这些函数。

  • 自动访问
  • 文件包含,使用 #include 指令
  • 库包含

16.9  数学库

数学库包含许多有用的数学函数。头文件 math.h 提供这些函数的函数声明或原型。

16.10  通用工具库

通用工具库包含各种函数,其中包括随机数产生函数、搜索和排序函数、转换函数和内存管理函数。头文件 stdlib.h 提供这些函数的函数声明或原型。

16.11  诊断库

诊断库是设计用于辅助调试程序的小型库,由头文件 assert.h 支持。它由宏 assert() 构成,assert() 宏的作用为:表示出程序中某个条件应为真的关键位置,并在条件为假时用 assert() 语句终止该程序。

16.12  string.h 库中的 memcpy() 和 memmove()

memcpy() 和 memmove() 函数为赋值数组提供了便利工具。下面是两个函数的原型:

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);

这两个函数均从 s2 指向的位置复制 n 字节数据到 s1 指向的位置,且均返回 s1 的值。两者间的差别由关键字 restrict 造成:

  • memcpy() 假定两个内存区域之间没有重叠。复制过程类似于先将所有字节复制到一个临时缓冲区,再复制到目的地。
  • memmove() 不做这个假定。

16.13  可变参数:stdarg.h

头文件 stdarg.h 为函数提供了“可以接受可变个数的参数”的能力。但必须按照如下步骤进行:

  • 1.在函数原型中使用省略号。(省略号必须是最后参量)
  • 2.在函数定义中创建一个 va_list 类型的变量。
  • 3.用宏将该变量初始化为一个参数列表。
  • 4.用宏访问这个参数列表。
  • 5.用宏完成清理工作。

一个实例:

double sum(int lim, ...)   //最右边的参量(省略号前)用parmN表示,传递给该参量的实际参数值是省略号部分代表的参数个数。
va_list ap;                //创建一个va_list类型的变量ap,用于存放参数。
va_start(ap, lim);         //使用宏va_start()初始化为参数列表。宏va_start()接受两个参数:
                           //第一个参数为va_list类型的变量(此处为ap),第二个参数为参量parmN(此处为lim)。
va_arg(ap,double);         //使用宏va_arg()访问参数列表中的内容。宏va_start()接受两个参数:
                           //第一个参数为va_list类型的变量(此处为ap),第二个参数为一个类型名  
va_end(ap);               //使用宏va_end()完成清理工作。va_end()接受一个va_list类型的变量(ap)。

C99 添加了宏 va_copy() 来保存 va_list 变量的副本。宏 va_copy() 的两个参数均为 va_list 类型的变量,它将第二个参数复制到第一个参数中。例如:va_copy(apcopy, ap);   //apcopy 是 ap 的一个副本。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值