文档章节

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

fzyz_sb
 fzyz_sb
发布于 2013/09/21 11:17
字数 2189
阅读 55
收藏 0
点赞 0
评论 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
粉丝 404
博文 209
码字总数 447144
作品 0
武汉
程序员
[编程语言]C陷阱与缺陷

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

21gprs ⋅ 2014/05/23 ⋅ 0

C++快速入门

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

waffle930 ⋅ 2016/10/02 ⋅ 0

java与C++的区别

首先应该清楚,Java 是由 C++发展而来的,保留了 C++的大部分内容,其编程方式类似于 C++。但 Java 的句法更清晰、规模更小、更易学。Sun 公司对多种程序设计语言进行了深入研究,并摒弃了其...

wenqi_arthur ⋅ 2014/09/09 ⋅ 0

【原创】《深入剖析Tomcat》读书笔记

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

pandudu ⋅ 2015/12/22 ⋅ 0

C Primer Plus 第11章 11.7 ctype.h字符函数和字符串

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

idreamo ⋅ 2016/08/27 ⋅ 0

C预处理器及预处理器指令

预处理器: 编译程序之前,先由预处理器检查程序,根据程序中使用的预处理器指令,预处理器用符号缩略语所代表的内容替换程序中的缩略语。 预处理器可以根据包含其他文件,还可以选择让编译器...

DDwang ⋅ 2015/08/03 ⋅ 0

C++和Java的区别

转自:http://club.topsage.com/thread-265349-1-1.html Java并不仅仅是C++语言的一个变种,它们在某些本质问题上有根本的不同:   (1)Java比C++程序可靠性更高。有人曾估计每50行C++程序...

Rachy1989 ⋅ 2017/04/26 ⋅ 0

《程序员面试宝典》精华 编程语言部分

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

modernizr ⋅ 2014/08/11 ⋅ 2

[备忘]《C++入门经典》各章小节

《C++入门经典》(即英文版“Beginning C++ The Complete Language”的中译版) 各章小节 第1章 基本概念 ·C++中的程序至少包含一个main()函数。 ·函数的可执行部分由包含在一对花括号中的...

leeoo ⋅ 2011/12/24 ⋅ 0

#define 中的“ # 运算符”和“ ## 运算符”

利用宏参数创建字符串:# 运算符 在类函数宏(function-like macro)的替换部分中,“#”符号用作一个预处理运算符,它可以把语言符号(token)转化为字符串。例如,如果 x 是一个宏参量,那...

TMDJoJo ⋅ 2012/07/07 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

从 Confluence 5.3 及其早期版本中恢复空间

如果你需要从 Confluence 5.3 及其早期版本中的导出文件恢复到晚于 Confluence 5.3 的 Confluence 中的话。你可以使用临时的 Confluence 空间安装,然后将这个 Confluence 安装实例升级到你现...

honeymose ⋅ 今天 ⋅ 0

用ZBLOG2.3博客写读书笔记网站能创造今日头条的辉煌吗?

最近两年,著名的自媒体网站今日头条可以说是火得一塌糊涂,虽然从目前来看也遇到了一点瓶颈,毕竟发展到了一定的规模,继续增长就更加难了,但如今的今日头条规模和流量已经非常大了。 我们...

原创小博客 ⋅ 今天 ⋅ 0

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 今天 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 今天 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 今天 ⋅ 0

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 今天 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部