第十六章 C 预处理器和 C 库
16.1 翻译程序的第一步
- 源代码中的字符映射到源字符集。
- 编译器定位每个反斜杠后面跟着换行符的实例,并删除它们。
- 编译器把文本化粪池预处理记号序列、空白序列和注释序列
- 一个空格替换所有空白序列和注释序列
- 开始预处理
16.2 明示常量:#define
指令可以出现在源文件任何地方,其定义从指令出现的地方到文件末尾有效。
格式:
define 宏 替换列表
有类对象宏,类函数宏。从宏变成最终替换文本的过程称为宏展开(macro expansion
)。
从技术角度来看,可以把宏的替换体看作是记号型字符串,而不是字符型字符串,并不是简单的纯文本替换。
16.3 在 #define 中使用参数
示例:
#define MEAN(X,Y) (((X)+(Y))/2)
# 运算符
当 x
是个宏参数时,#x
就是转化为字符串 "x"
的形参名。称为字符串化(stringizing
)。
#define PSQR(x) printf("The square of " #x " is %d.\n, ((x)*(x)))
## 运算符
把两个记号进行拼接组成一个记号
#define XNAME(n) X##n
变参宏:... 和 VA_ARGS
一些函数接收数量可变的参数。通过把宏参数列表中最后的参数写成省略号来实现这一功能。
#define PR(...) printf(__VA_ARGS__)
16.4 宏和函数的选择
宏函数优点:
- 执行效率更高。
- 不用担心变量类型。
注意点:
- 宏名中不允许有空格。
- 用圆括号把宏的参数和整个替换体括起来。防止运算符优先级导致不符合预期的结果。
- 用大写字母表示宏函数的名称。提醒该为宏函数。
- 如果打算使用宏函数来加快程序的运行速度,那么首先要确认使用宏和使用函数是否会导致较大差异。在程序中值使用一次的宏无法明显减少程序的运行时间。
16.5 文件包含:#include
头文件常用形式:
- 明示常量
- 宏函数
- 函数声明
- 结构模板定义
- 类型定义
16.6 其他指令
#undef
取消已定义的 #define
指令
**#ifdef、#else 和 #endif **
条件编译
#ifndef
#if 和 #elif
预定义宏
宏 | 含义 |
---|---|
_DATA_ | 预处理的预期 |
_FILE_ | 当前源代码文件名的字符串字面量 |
_LINE_ | 当前源代码文件中行号的整型常量 |
_STDC_ | 设置为 1 时,表明实现遵循 C 标准 |
_STDC_HOSTED_ | 本即环境设置为 1;否则设置为 0 |
_STDC_VERSION_ | 支持 C99 标准,设置为 19990L;支持 C11 标准,设置为 201112L |
_TIME_ | 翻译代码的时间 |
_func_ 是预定义标识符,而不是预定义宏。
#line
重置 __LINE__
和 __FILE__
宏报告的行号和文件名
#line 10 "cool.c"
#error
让编译器发出一条错误消息,该消息包含指令中的文本。
#pragma
把编译器指令放入源代码中。
C99
还提供 _Pragma
预处理器运算符,把字符串转换成普通的编译指示。
_Pragma("nonstandaertreatmenttypeB on")
/* 等价于 */
#pragma nonstandaertreatmenttypeB on
泛型选择(C11)
泛型编程(generic programming):指那些没有特定类型,但是一旦指定一种类型,就可以转换成指定类型的代码。
泛型选择表达式(generic selection expression):根据表达式的类型选择一个指。常和 #define
一起使用。
_Generic(x, int:0, float:1, double:2, defult:3)
类似 switch
语句,根据 x 的类型决定整个表达式的值。为 int
时值 0
,为 float
时值为 1
...
#define MYTYPE(X) \
_Generic((X), \
int: "int", \
float: "float", \
double: "double", \
default: "other" \
)
16.7 内联函数(C99)
把函数变成内联函数建议尽可能快地调用该函数,其具体效果由实现定义。
只有具有内部链接的函数可以成为内联函数,因此内联函数可能会放到头文件中。
inline static void eatline(void)
{
while(getchar() != '\n')
continue;
}
C
允许混合使用内联函数定义和外部函数定义,不建议如此。
16.8 _Noreturn 函数(C11)
告诉用户和编译器,该函数不会把控制返回主调程序,避免滥用该函数,通知编译器优化一些代码。
16.9 C 库
16.10 通用工具库
stdlib.h
int atexit (void (func)(void));*
注册回调函数,在程序退出时执行。至少可以注册 32
个函数,后进的函数先执行。
void exit (int status)
退出整个程序
- 刷新所有输出流
- 关闭所有打开的流
- 关闭标准 I/O 函数 tmpfile() 创建的临时文件。
- 控制权返回主机环境,如果可以的话,向主机报告终止状态。UNIX 程序通常使用
0
表示成功终止,非零值表示终止失败。
16.12 断言库
void assert (int expression)
接受一个整型表达式作为参数。如果表达式为假(非零),assert() 宏就在标准错误流中写入一条错误信息,并调用 abort() 函数终止程序。
_Static_assert
静态断言。
16.13 string.h 库中的 memcyp() 和 memmove()
void *memcpy(void* restrict s1, const void* restrict s2,size_t n)
void *memmove(void *sl,const void *s2,size_t n)
memcopy
假设没有内存重叠,而 memmove
没有。
16.14 可变参数:stdarg.h
创建可变参数函数步骤
- 提供一个使用省略号的函数原型;
- 在函数定义中创建一个 va_list 类型的变量;
- 用宏把该变量初始化为一个参数列表;
- 用宏访问参数列表;
- 用宏完成清理工作。
double sum(int lm, ...)
{
va_list ap;
double tot = 0;
int i;
va_start(ap, lim);
for(i = 0; i < lim; ++i)
tot += va_arg(ap, double);
va_end(ap);
return tot;
}