文档章节

C和指针---第十四章:预处理器

fzyz_sb
 fzyz_sb
发布于 2013/09/21 11:17
字数 2189
阅读 56
收藏 0

C预处理器在源代码编译之前对其进行一些文本性质的操作。它的主要任务包括删除注释,插入被#include指令包含的文件的内存,定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。

14.1 预定义符号

__FILE__		进行编译的源文件名
__LINE__		文件当前行的行号
__DATE__		文件被编译的日期
__TIME__		文件被编译的时间
__STDC__		如果编译器遵循ANSI C,其值为1,否则未定义
14.2 #define
#define name stuff
每当有符号name出现在这条指令后面时,预处理器就会把它替换成stuff。

使用#define指令,你可以把任何文本替换到程序中。

#define DEBUG_PRINT printf("File %s line %d:"\
	" x = %d, y = %d, z = %d",\
	__FILE__, __LINE__, x, y, z )
当我们调试一个存在许多设计一组变量的不同计算过程时,这种类型的声明非常有用。
x *= 2;
y += x;
z = x * y;
DEBUG_PRINT;
警告:这条语句在DEBUG_PRINT后面加了一个分号,所以你不应该在宏定义的尾部加上分号。如果这样做,就产生了两条语句---一条printf语句后面再加一条空语句。如果有些场合只要求一条语句的话,就会出现问题:
if ( ... )	
	DEBUG_PRINT;
else
备注:不要滥用这种技巧。如果相同代码需要出现在程序的几个地方,通常更好的方法是把它实现为一个函数。

14.2.1 宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏。

#define name(parameter-list) stuff
来看两个例子:

1. 

#define SQUARE(x) x * x
则:
a = 5;
SQUARE( a + 1 );
输出多少?实际上为11--->5 + 1 * 5 + 1 = 11.

所以,我们应该修改宏定义:

#define SQUARE(x) (x) * (x)
2.
#define DOUBLE(a) (a) + (a)
这个定义貌似正确的,但是下面表达式输出多少?
a = 5;
10 * DOUBLE(a);
实际上输出55--->10 * 5 + 5 = 55.

所以我们应该修改宏定义:

#define DOUBLE(a) ((a) + (a))
14.2.2 #define替换

在程序中扩展#define定义符号和宏时,需要以下几个步骤:

1. 在调用宏时,首先对参数进行检查,看看是否包含了任何由#define定义的符号。如果是,则它们首先被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替代。

3. 最后,再次对结果文本进行扫描,看看它是否包含了任何由#define定义的符号。如果是,就重复上述过程。

    这样,宏参数和#define定义可以包含其他#define定义的符号。但是,宏不可出现递归。

宏定义有两个技巧:

1. 字符串的自动拼接

#include <stdio.h>
#define PRINT(FORMAT, VALUE) printf("the value is " FORMAT "\n", VALUE )

int main(void)
{
	PRINT("%d", 3);

	return 0;
}
程序输出:

2. 宏参数替换为一个字符串

1) #argument这种结构被预处理器翻译为“argument”:

#include <stdio.h>
#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE " is " FORMAT "\n", VALUE)

int main(void)
{
	int x = 5;
	PRINT("%d", x + 3);		//这里VALUE应该是个变量,而不是一个常量

	return 0;
}
程序输出:

2) ##把位于它两边的符号连接成一个符号:

#include <stdio.h>
#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE " is " FORMAT "\n", VALUE##VALUE)

int main(void)
{
	char *xx = "hello world";
	PRINT("%s", x );		

	return 0;
}
这个例子好丑陋,程序输出:

14.2.3 宏与函数

宏非常频繁的用于执行简单的计算:

#define MAX( a, b ) ( ( a ) > ( b ) ? ( a ) : ( b ) )
为什么不用函数来完成这个任务呢?原因有两个:

1. 用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹。

2. 更重要的是:函数的参数必须声明为一种特定类型,所以它只能在类型合适的表达式上使用。反之,宏可以应用于任何的数据类型。

但是宏相比于函数有以下缺点:

1. 每次使用宏的时候,都是将宏的代码拷贝到程序中去。如果宏的代码量大,则大幅度增加程序的长度。

还有一些用函数无法办到但是用宏可以办到:以下程序的宏的第二个参数是一种类型,它无法作为函数参数进行传递:

#define MALLOC( n, type ) ( ( type * )malloc( ( n ) * sizeof( type ) ) )

则:

pi = MALLOC( 25, int );

被转换为:

pi = ( ( int *) malloc( ( 25 ) * sizeof( int ) ) );
14.2.4 带副作用的宏参数

副作用就是在表达式求值时出现的永久性效果。

#include <stdio.h>

#define MAX( a, b ) ( ( a ) > ( b ) ? ( a ) : ( b ) )

int main(void)
{
	int x = 5; 
	int y = 8;
	int z = 0;
	z = MAX( x++, y++ );
	printf("x = %d, y = %d, z = %d\n", x, y, z );

	return 0;
}
程序输出:

输出有点不可思议,但我们用宏定义替换一下便知道答案:

z = ( ( x++ ) > ( y++ ) ? ( x++ ) : ( y++ ) );
14.2.5 命名约定

1. 代码长度:

宏定义:每次使用时,宏代码都被插入到程序中,除了非常小的宏之外,程序的长度将大幅度增长

函数:函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码。

2. 执行速度

宏定义:更快

函数:存在函数调用/返回的额外开销

3. 操作符优先级

宏定义:宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果。

函数:函数参数只在函数调用时求值一次,它的结果值传递给函数,表达式的求值结果更容易预测。

4. 参数求值

宏定义:参数每次用于宏定义时,它们都将重新求值。由于多次求值,具有副作用的参数可能会产生不可预料的结果。

函数:参数在函数被调用前只求值一次,在函数中多次使用参数并不会导致多种求值过程,参数的副作用并不会造成任何特殊的问题。

5. 参数类型

宏定义:宏于类型无关,只要对参数的操作时合法的,它可以使用与任何参数类型。

函数:函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的。

14.2.6 #undef

这条预处理指令用于移除一个宏定义

#undef name
如果一个现存的名字需要被重新定义,那么它的旧定义首先必须用#undef移除。

14.3 条件编译

条件编译的一大作用是:我们可以控制程序中哪些语句需要编译:

#if DEBUG
	printf("x = %d, y = %d\n", x, y );
#endif
如果我们想DEBUG这段代码,则只要:
#define DEBUG 1
当然,条件编译支持多种判断:
#if constant-expression
	statements
#elif constant-expression
	other statements
#else
	other statements
#endif
14.3.1 是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbol
#if的形式功能更强一些,因为常量表达式可能包含额外的条件:
#if X > 0 || defined( ABC ) && defined( BCD )
14.5 其他指令

1. #error

#error指令允许你生成错误信息。

#if defined( OPTION_A )
	stuff needed for option A
#elif defined( OPTION_B )
	stuff needed for option B
#elif defined( OPTION_C )
	stuff needed for option C
#else	
	#error No option selected!
#endif
2. #line
#line number "string"
它通知预处理器number是下一行输入的行号。如果给出可选部分“string”,预处理器就把它作为当前文件的名字。这条指令修改了__LINE__和__FILE__.

3. #progma

支持编译器而异的特性

4. #

代表无效指令:以#开头,后面不跟任何内容的一行。

总结:

1. 不要在一个宏定义的末尾加上分号,使其成为一条完整的语句。

2. 在宏定义中使用参数,但忘了在它们周围加上括号。

3. 忘了在整个宏定义的两边加上括号。


习题:

1.

void print_ledger( int argument )
{
#if defined( OPTION_LONG )
	print_ledger_long( argument );
#elif defined( OPTION_DETAILED )
	print_ledger_detailed( argument );
#else
	print_ledger_default( argument );
#endif
}

2.

int cpu_type()
{
	int		count = 0;
#if defined( VAX ) 
	count++;
#if defined( M68000 )
	count++;
#if defined( M68020 )
	count++;
#if defined( I80386 )
	count++;
#if defined( X6809 )
	count++;
#if defined( X6502 )
	count++;
#if defined( U3B2 )
	count++;

	if ( 0 == count ){
		return CPU_UNKNOWN;
	}
	else ( 1 != count ){
		return NULL;
	}

#if defined( VAX ) 
	return CPU_VAX;
#if defined( M68000 )
	return CPU_68000;
#if defined( M68020 )
	return CPU_68020;
#if defined( I80386 )
	return CPU_80386;
#if defined( X6809 )
	return CPU_6809;
#if defined( X6502 )
	return CPU_6502;
#if defined( U3B2 )
	return CPU_3B2;
}


© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 408
博文 209
码字总数 447144
作品 0
武汉
程序员
[编程语言]C陷阱与缺陷

内容摘要 作者以自己1985年在Bell实验室时发表的一篇论文为基础,结合自己的工作经验扩展成为这本对C程序员具有珍贵价值的经典著作。写作本书的出发点不是要批判C语言,而是要帮助C程序员绕过...

21gprs
2014/05/23
0
0
C++快速入门

只学过Java的我最近笔试接触到各种C++的题目,于是稍微恶补了一下C++的基础部分,以下内容是基于《21天学通C++》的部分读书笔记,按照章节写的。 第二章 C++程序的组成部分 ①#include 预处理...

waffle930
2016/10/02
56
0
【原创】《深入剖析Tomcat》读书笔记

第一章 一个简单的Web服务器 第二章 一个简单的servlet容器 第三章 连接器 第四章 Tomcat的默认连接器 第五章 servlet容器 第六章 生命周期 第七章 日志记录器 第八章 载入器 第九章 Sessio...

pandudu
2015/12/22
46
0
C Primer Plus 第11章 11.7 ctype.h字符函数和字符串

第7章“C控制语句 分支和跳转”介绍了ctype.h系列字符相关的函数。这些函数不能被 应用于整个字符串,但是可以被应用于字符串中的个别字符。程序清单11.26定义了一个函数,它把toupper( )函数...

idreamo
2016/08/27
29
0
《程序员面试宝典》精华 编程语言部分

《程序员面试宝典》精华 编程语言部分 正所谓取其精华,去其糟粕。本文谨记录下《程序员面试宝典》一些关键的知识点、易错点,对于一些虽然重要但书中没有解释清楚的地方不做记录。当然这里的...

modernizr
2014/08/11
410
2

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周四乱弹 —— 毒蛇当辣条

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @ 达尔文:分享花澤香菜/前野智昭/小野大輔/井上喜久子的单曲《ミッション! 健?康?第?イチ》 《ミッション! 健?康?第?イチ》- 花澤香菜/前野智...

小小编辑
今天
5
2
java -jar运行内存设置

java -Xms64m #JVM启动时的初始堆大小 -Xmx128m #最大堆大小 -Xmn64m #年轻代的大小,其余的空间是老年代 -XX:MaxMetaspaceSize=128m # -XX:CompressedClassSpaceSize=6...

李玉长
今天
1
0
Spring | 手把手教你SSM最优雅的整合方式

HEY 本节主要内容为:基于Spring从0到1搭建一个web工程,适合初学者,Java初级开发者。欢迎与我交流。 MODULE 新建一个Maven工程。 不论你是什么工具,选这个就可以了,然后next,直至finis...

冯文议
今天
1
0
RxJS的另外四种实现方式(四)——性能最高的库(续)

接上一篇RxJS的另外四种实现方式(三)——性能最高的库 上一篇文章我展示了这个最高性能库的实现方法。下面我介绍一下这个性能提升的秘密。 首先,为了弄清楚Most库究竟为何如此快,我必须借...

一个灰
今天
1
0
麒麟AI首席科学家现世

8月31日,华为发布了新一代顶级人工智能手机芯片麒麟980,成为全球首款7nm工艺手机芯片,AI方面也实现飞跃,支持人脸识别、物体识别、物体检测、图像分割、智能翻译等。 虽然如今人人都在热议...

问题终结者
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部