头文件的那些事

一直对这个头文件半知半解,找到一个好的博文自然转载一下.
http://blog.csdn.net/geeglet/article/details/8174608

一、#define

[html]  view plain  copy
  1. //定义宏   
  2. #define [MacroName] [MacroValue]   
  3. //取消宏   
  4. #undef [MacroName]   
  5. //普通宏   
  6. #define PI (3.1415926)   
  7. //带参数的宏   
  8. #define max(a,b) ((a)>(b)? (a),(b))   

#define命令是C/C++语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。

2.简单的宏定义 

[cpp]  view plain  copy
  1. #define MAXTIME 1000    
  2. //一个简单的MAXTIME就定义好了,它代表1000,如果在程序里面写   
  3. if( i < MAXTIME)  
  4. {  
  5.    //...  
  6. }   


编译器在处理这个代码之前会对MAXTIME进行处理替换为1000。 
这样的定义看起来类似于普通的常量定义CONST,但也有着不同,因为define的定义更像是简单的文本替换,而不是作为一个量来使用,这个问题在下面反映的尤为突出。 
在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引起误解和误用。如下例:

例1

[cpp]  view plain  copy
  1. #define N 2+2  
  2. void main()  
  3. {  
  4. int a=N*N;  
  5. printf(“%d”,a);  
  6. }  

(1) 出现问题:在此程序中存在着宏定义命令,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为 2+2=4,然后在程序中计算a时使用乘法,即N*N=4*4=16,其实该题的结果为8,为什么结果有这么大的偏差?

(2)问题解析:如1节所述,宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8,这就是宏替换的实质,如何写程序才 能完成结果为16的运算呢?
(3)解决办法:将宏定义写成如下形式
[cpp]  view plain  copy
  1. #define N (2+2)  
这样就可替换成(2+2)*(2+2)=16

3.带参数的宏定义 

define可以像函数那样接受一些参数,如下 
[cpp]  view plain  copy
  1. #define max(x,y) (x)>(y)?(x):(y);   
这个定义就将返回两个数中较大的那个,看到了吗?因为这个“函数”没有类型检查,就好像一个函数模板似的,当然,它绝对没有模板那么安全就是了。可以作为一个简单的模板来使用而已。
在带参数的宏定义的使用中,极易引起误解。例如我们需要做个宏替换能求任何数的平方,这就需要使用参数,以便在程序中用实际参数来替换宏定义中的参数。一般学生容易写成如下形式:
[cpp]  view plain  copy
  1. #define area(x) x*x  
这在使用中是很容易出现问题的,看如下的程序
[cpp]  view plain  copy
  1. void main()  
  2. {  
  3. int y=area(2+2);  
  4. printf(“%d”,y);  
  5. }  
按理说给的参数是2+2,所得的结果应该为4*4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换 了,在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?
[cpp]  view plain  copy
  1. #define area(x) (x)*(x)  
对于area(2+2),替换为(2+2)*(2+2)=16,可以解决,但是对于area(2+2)/area(2+2)又会怎么样 呢,有的学生一看到这道题马上给出结果,因为分子分母一样,又错了,还是忘了遵循先替换再计算的规则了,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除运算规则,结果为16/4*4=4*4=16,那应该怎么呢?解决方法是在整个 宏体上再加一个括号,即
[cpp]  view plain  copy
  1. #define area(x) ((x)*(x))  
不要觉得这没必要,没有它,是不行的。

要想能够真正使用好宏定义,那么在读别人的程序时,一定要记住先将程序中对宏的使用全部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展 开后再进行相应的计算,就不会写错运行结果。如果是自己编程使用宏替换,则在使用简单宏定义时,当字符串中不只一个符号时,加上括号表现出优先级,如果是 带参数的宏定义,则要给宏体中的每个参数加上括号,并在整个宏体上再加一个括号。

二预处理

1.常见的预处理功能

预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。
文件包含:#include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。
条件编译:#if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。
布局控制:#pragma,这也是我们应用预处理的一个重要方面,主要功能是为编译程序提供非常规的控制流信息。
宏替换:#define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。

2.常见的预处理指令:

#define 宏定义
#undef 未定义宏
#include 文本包含
#ifdef 如果宏被定义就进行编译
#ifndef 如果宏未被定义就进行编译
#endif 结束编译块的控制
#if 表达式非零就对代码进行编译
#else 作为其他预处理的剩余选项进行编译
#elif 这是一种#else和#if的组合选项 //后面有例子的
#line 改变当前的行数和文件名称
#error 输出一个错误信息
#pragma 为编译程序提供非常规的控制流信息

3.预处理标识符

为了处理一些有用的信息,预处理定义了一些预处理标识符,虽然各种编译器的预处理标识符不尽相同,但是他们都会处理下面的4种:
__FILE__ 正在编译的文件的名字
__LINE__ 正在编译的文件的行号
__DATE__ 编译时刻的日期字符串,例如: "25 Jan 2006"
__TIME__ 编译时刻的时间字符串,例如: "12:30:55"
  例如:
[cpp]  view plain  copy
  1. cout<<"The file is :"<<__FILE__"<<"! The lines is:"<<__LINE__<<endl;  



三、头文件保护符

1.条件编译

[html]  view plain  copy
  1. #ifdef XXX   //或者 #ifndef  
  2. //…  
  3. (#else)  
  4. //…  
  5. #endif   
例如: 
[html]  view plain  copy
  1. #ifdef WINDOWS   
  2. //....   
  3. //....   
  4. #endif   
  5. #ifdef LINUX   
  6. //....   
  7. //....   
  8. #endif   

2.编写头文件保护符

头文件应该含有保护符,即使这些头文件不会被其他头文件包含。编写头文件保护符并不困难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。

利用宏定义和条件编译#ifndef指示检测指定的预处理变量是否未定义。如果预处理器变量未定义,那么跟在后面的所有指示都被处理,直到出现#endif。
可以使用这些措施来预防多次包含同一头文件:

[html]  view plain  copy
  1. /*** 头文件salesitem.h ***/  
  2. #ifndef SASESITEM_H  
  3. #define SALESITEM_H  
  4. //...这里是内容  
  5. #endif  

条件指示#ifndef SALESITEM_H测试 SALESITEM_H 预处理器变量是否未定义。如果 SALESITEM_H 未定义,那么 #ifndef 测试成功,跟在 #ifndef 后面的所有行都被执行,直到发现#endif。相反,如果 SALESITEM_H 已定义,那么 #ifndef 指示测试为假,该指示和 #endif 指示间的代码都被忽略。

为了保证头文件在给定的源文件中只处理过一次,我们首先检测 #ifndef。第一次处理头文件时,测试会成功,因为 SALESITEM_H 还未定义。下一条语句定义了 SALESITEM_H。那样的话,如果我们编译的文件恰好又一次包含了该头文件。#ifndef 指示会发现 SALESITEM_H 已经定义,并且忽略该头文件的剩余部分。

当没有两个头文件定义和使用同名的预处理器常量时,这个策略相当有效。我们可以为定义在头文件里的实体(如类)命名预处理器变量来避免预处理器变量重名的问题。一个程序只能含有一个名为 Sales_item 的类。通过使用类名来组成头文件和预处理器变量的名字,可以使得很可能只有一个文件将会使用该预处理器变量。


参考:http://www.zxbc.cn/html/20070703/24186.html
http://hi.baidu.com/corebo/item/13d89e559c0e74d5d58bac12
http://blog.sina.com.cn/s/blog_686188ef0100klku.html
http://www.cnblogs.com/renxs/archive/2012/01/11/2319983.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值