C Primer Plus--- Chapter 16---The C Preprocessor and the C Library ---3.其他指令

C Primer Plus--- Chapter 16---The C Preprocessor and the C Library ---3.其他指令

  • 1. #undef指令
  • 2. 从C预处理器角度看已定义
  • 3. 条件编译
    • 3.1 #ifdef、 #else和#endif指令
    • 3.2 #ifndef指令
    • 3.3 #if和#elif指令
  • 4. 预定义宏
  • 5. #line和#error
  • 6. #pragma
  • 7. 泛型选择( C11)

1. #undef指令

#undef指令用于“取消”已定义的#define指令。 也就是说, 假设有如下定义:

#define LIMIT 400

然后, 下面的指令:

#undef LIMIT

将取消上面的定义,现在就可以把LIMIT重新定义为一个新值。 即使原
来没有定义LIMIT, 取消LIMIT的定义仍然有效。

2. 从C预处理器角度看已定义

处理器在识别标识符时, 遵循与C相同的规则: 标识符可以由大写字母、 小写字母、 数字和下划线字符组成, 且首字符不能是数字。

当预处理器在预处理器指令中发现一个标识符时, 它会把该标识符当作已定义的或未定义的。

这里的已定义表示由预处理器定义。 如果标识符是同一个文件中由前面的#define指令创建的宏名, 而且没有用#undef 指令关闭, 那么该标识符是已定义的。

如果标识符不是宏, 假设是一个文件作用域的C变量, 那么该标识符对预处理器而言就是未定义的。

注意, #define宏的作用域从它在文件中的声明处开始, 直到用#undef指
令取消宏为止, 或延伸至文件尾(以二者中先满足的条件作为宏作用域的结束) 。

另外还要注意, 如果宏通过头文件引入, 那么#define在文件中的位置
取决于#include指令的位置。

3. 条件编译

可以使用其他指令创建条件编译(conditinal compilation) 。
也就是说,可以使用这些指令告诉编译器根据编译时的条件执行或忽略信息(或代码)块。

3.1 #ifdef、 #else和#endif指令

#ifdef MAVIS
#include "horse.h" // gets done if MAVIS is #defined
#define STABLES 5
#else
#include "cow.h" // gets done if MAVIS isn't #defined
#define STABLES 15
#endif

这里使用的较新的编译器和 ANSI 标准支持的缩进格式。 如果使用旧的
编译器, 必须左对齐所有的指令或至少左对齐#号.

#ifdef #else很像C的if else。 两者的主要区别是, 预处理器不识别用于标记块的花括号({}) , 因此它使用#else(如果需要) 和#endif(必须存在)来标记指令块。 这些指令结构可以嵌套。 见:

/* ifdef.c -- uses conditional compilation */
#include 
#define JUST_CHECKING
#define LIMIT 4
int main(void)
{
int i;
int total = 0;
for (i = 1; i <= LIMIT; i++)
{
total += 2*i*i + 1;
#ifdef JUST_CHECKING
printf("i=%d, running total = %d\n", i, total);
#endif
}
printf("Grand total = %d\n", total);
return 0;
}

3.2 #ifndef指令

#ifndef指令与#ifdef指令的用法类似, 也可以和#else、 #endif一起使用,但是它们的逻辑相反。

#ifndef指令判断后面的标识符是否是未定义的, 常用于定义之前未定义的常量。见:

/* arrays.h */
#ifndef SIZE
#define SIZE 100
#endif

通常, 包含多个头文件时, 其中的文件可能包含了相同宏定义。 #ifndef
指令可以防止相同的宏被重复定义。
在首次定义一个宏的头文件用#ifndef指令激活定义, 随后在其他头文件中的定义都被忽略。

#ifndef指令还有另一种用法。 假设有上面的arrays.h头文件, 然后把下面一行代码放入一个头文件中:

#include "arrays.h"

则 SIZE 被设置为 100。
但如果见下面代码放入该头文件:

#define SIZE 10
#include "arrays.h"

SIZE则被设置为10。 这里, 当执行到#include "arrays.h"这行, 处理
array.h中的代码时, 由于SIZE是已定义的, 所以跳过了#define SIZE 100这行代码。
鉴于此, 可以利用这种方法, 用一个较小的数组测试程序。 测试完毕后, 移除#define SIZE 10并重新编译。 这样, 就不用修改头文件数组本身
了。

#ifndef指令通常用于防止多次包含一个文件。 也就是说, 应该像下面这
样设置头文件:

/* things.h */
#ifndef THINGS_H_
#define THINGS_H_
/* rest of include file */
#endif

假设该文件被包含了多次。 当预处理器首次发现该文件被包含时,
THINGS_H_是未定义的, 所以定义了THINGS_H_, 并接着处理该文件的其他部分。 当预处理器第2次发现该文件被包含时, THINGS_H_是已定义的,所以预处理器跳过了该文件的其他部分。

为何要多次包含一个文件? 最常见的原因是, 许多被包含的文件中都包
含着其他文件, 所以显式包含的文件中可能包含着已经包含的其他文件。 这有什么问题? 在被包含的文件中有某些项(如, 一些结构类型的声明) 只能在一个文件中出现一次。 C标准头文件使用#ifndef技巧避免重复包含。

但是, 这存在一个问题: 如何确保待测试的标识符没有在别处定义。 通常, 实现的供应商使用这些方法解决这个问题: 用文件名作为标识符、 使用大写字母、 用下划线字符代替文件名中的点字符、 用下划线字符做前缀或后缀(可能使用两条下划线) 。

3.3 #if和#elif指令

#if指令很像C语言中的if。 #if后面跟整型常量表达式, 如果表达式为非
零, 则表达式为真。 可以在指令中使用C的关系运算符和逻辑运算符:

#if SYS == 1
#include "ibm.h"
#endif

可以按照if else的形式使用#elif(早期的实现不支持#elif) 。 例如, 可
以这样写:

#if SYS == 1
#include "ibmpc.h"
#elif SYS == 2
#include "vax.h"
#elif SYS == 3
#include "mac.h"
#else
#include "general.h"
#endif

较新的编译器提供另一种方法测试名称是否已定义,
即用 #if defined (VAX) 代替 #ifdef VAX

这里, defined是一个预处理运算符, 如果它的参数是用#defined定义
过, 则返回1; 否则返回0。 这种新方法的优点是, 它可以和#elif一起使用。

#if defined (IBMPC)
#include "ibmpc.h"
#elif defined (VAX)
#include "vax.h"
#elif defined (MAC)
#include "mac.h"
#else
#include "general.h"
#endif

条件编译还有一个用途是让程序更容易移植。 改变文件开头部分的几个
关键的定义, 即可根据不同的系统设置不同的值和包含不同的文件。

4. 预定义宏

C标准规定了一些预定义宏,如下:
C Primer Plus--- Chapter 16---The C Preprocessor and the C Library ---3.其他指令_第1张图片
C99 标准提供一个名为_ func _的预定义标识符, 它展开为一个代表
函数名的字符串(该函数包含该标识符) 。 那么, _ func _必须具有函数作用域, 而从本质上看宏具有文件作用域。 因此, _ func _是C语言的预定义标识符, 而不是预定义宏。

// predef.c -- predefined identifiers
#include 
void why_me();
int main()
{
printf("The file is %s.\n", _ _FILE_ _);
printf("The date is %s.\n", _ _DATE_ _);
printf("The time is %s.\n", _ _TIME_ _);
printf("The version is %ld.\n", 3TDC_VERSION_ _);
printf("This is line %d.\n", _ _LINE_ _);
printf("This function is %s\n", _ _func_ _);
why_me();
return 0;
}
void why_me()
{
printf("This function is %s\n", _ _func_ _);
printf("This is line %d.\n", _ _LINE_ _);
}

输出结果:

The file is predef.c.
The date is Sep 23 2013.
The time is 22:01:09.
The version is 201112.
This is line 11.
This function is main
This function is why_me
This is line 21.

5. #line和#error

#line指令重置_ LINE FILE _宏报告的行号和文件名。 可以这
样使用#line:

#line 1000 // 把当前行号重置为1000
#line 10 "cool.c" // 把行号重置为10, 把文件名重置为cool.c

#error 指令让预处理器发出一条错误消息, 该消息包含指令中的文本。
如果可能的话, 编译过程应该中断。 可以这样使用#error指令:

#if _ _STDC_VERSION_ _ != 201112L
#error Not C11
#endif

编译上述代码:

$ gcc newish.c
newish.c:14:2: error: #error Not C11
$ gcc -std=c11 newish.c
$

如果编译器只支持旧标准, 则会编译失败, 如果支持C11标准, 就能成
功编译。

6. #pragma

在现在的编译器中, 可以通过命令行参数或IDE菜单修改编译器的一些
设置。
#pragma把编译器指令放入源代码中。 例如, 在开发C99时, 标准被称为C9X, 可以使用下面的编译指示(pragma) 让编译器支持C9X:

#pragma c9x on

一般而言, 编译器都有自己的编译指示集。 例如, 编译指示可能用于控
制分配给自动变量的内存量, 或者设置错误检查的严格程度, 或者启用非标准语言特性等。
C99 标准提供了 3 个标准编译指示。
C99还提供_Pragma预处理器运算符, 该运算符把字符串转换成普通的
编译指示。

7. 泛型选择( C11)

在程序设计中, 泛型编程(generic programming) 指那些没有特定类
型, 但是一旦指定一种类型, 就可以转换成指定类型的代码。

例如, C++在模板中可以创建泛型算法, 然后编译器根据指定的类型自动使用实例化代码。

C没有这种功能。 然而, C11新增了一种表达式, 叫作泛型选择表达式
(generic selection expression) , 可根据表达式的类型(即表达式的类型是int、 double 还是其他类型) 选择一个值。

泛型选择表达式不是预处理器指令, 但是在一些泛型编程中它常用作#define宏定义的一部分。

下面是一个泛型选择表达式的示例:

_Generic(x, int: 0, float: 1, double: 2, default: 3)

_Generic是C11的关键字。 _Generic后面的圆括号中包含多个用逗号分隔
的项。

第1个项是一个表达式, 后面的每个项都由一个类型、 一个冒号和一个值组成, 如float: 1。
第1个项的类型匹配哪个标签, 整个表达式的值是该标签后面的值。 例如, 假设上面表达式中x是int类型的变量, x的类型匹配int:标签, 那么整个表达式的值就是0。

如果没有与类型匹配的标签, 表达式的值就是default:标签后面的值。

泛型选择语句与 switch 语句类似, 只是前 者用表达式的类型匹配标签, 而后者用表达式的值匹配标签。

下面是将泛型选择语句与宏定义结合的示例:

#define MYTYPE(X) _Generic((X),\
int: "int",\
float : "float",\
double: "double",\
default: "other"\
)

宏必须定义为一条逻辑行, 但是可以用\把一条逻辑行分隔成多条物理
行。
在这种情况下, 对泛型选择表达式求值得字符串。
例如, 对MYTYPE(5)求值得"int", 因为值5的类型与int:标签匹配。

见下面程序示例:

// mytype.c
#include 
#define MYTYPE(X) _Generic((X),\
int: "int",\
float : "float",\
double: "double",\
default: "other"\
)
int main(void)
{
int d = 5;
printf("%s\n", MYTYPE(d)); // d is type int
printf("%s\n", MYTYPE(2.0*d)); // 2.0* d is type double
printf("%s\n", MYTYPE(3L)); // 3L is type long
printf("%s\n", MYTYPE(&d)); // &d is type int *
return 0;
}

输出;

int
double
other
other

对一个泛型选择表达式求值时, 程序不会先对第一个项求值, 它只确定
类型。 只有匹配标签的类型后才会对表达式求值。

你可能感兴趣的:(#,C,Primer,Plus)