ImageVerifierCode 换一换
格式:DOCX , 页数:14 ,大小:37.92KB ,
资源ID:5903021      下载积分:2 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/5903021.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(第十章预处理和位运算.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

第十章预处理和位运算.docx

1、第十章预处理和位运算第十章 预处理和位运算10.1 预处理10.1.1 概述C语言的编译系统分为编译预处理和正式编译,这是C语言的一大特点,其中编译预处理是它和其他高级语言的一个重要区别。编译C语言程序时,编译系统中首先是编译预处理模块根据预处理命令对源程序进行适当的处理,然后才是对源程序的正式编译:对加工过的C源程序进行语法检查和语义处理,最后将源程序转换为目标程序。预处理命令均以符号“#”开头,并且一行只能写一条预处理命令,结束时不能使用分号,一般将预处理命令写在源程序的开头。如果能正确使用预处理命令,就能编写出易于调试、易于移植的程序,并为结构化程序设计提供帮助。C语言提供三种编译预处理

2、命令:1宏定义;2文件包含;3条件编译。10.1.2 宏定义C语言有两种宏定义命令:不带参数的宏定义(或符号常量定义)和带参数的宏定义。一、不带参数的宏定义不带参数的宏定义通常用来定义符号常量,即用一指定的宏名(即标识符)来代表一个字符串,一般形式为:#define 其中宏名常用大写字母表示,宏名与替换序列(即字符序列)之间用空格符分隔。在程序中,经编译预处理后,就进行宏展开,凡是宏名出现的地方被替换为它所对应的替换序列。对于宏定义的使用,作以下几点说明: 预处理模块只是用宏名作简单的替换,不作语法检查,若字符串有错误,只有在正式编译时才能检查出来。 没有特殊的需要,一般在预处理语句的行末不必

3、加分号,若加了分号,则连同分号一起替换。如:#define PI 3.1415926;area=PI*r*r;经过宏展开后,该语句为:area=3.1415926;*r*r; 显然有错误。 使用宏定义可以减少程序中重复书写字符串的工作量,提高程序的可移植性。例如,定义数组的大小:#define N 100int aN;这时数组的大小为100,若改变数组大小,则:#define N 200 宏定义命令一般写在文件开头、函数之前,作为文件的一部分,宏名的有效范围为宏定义之后到本源文件结束。如果要强制终止宏定义的作用域,可以使用#undef命令。如:这样就可以灵活控制宏定义的作用范围。 进行宏定义时

4、可以引用已定义的宏名,宏展开是层层替换。例如:#define PI 3.1415926#define R 4.0#define L 2* PI* R#define S PI* R* Rmain( ) printf(L=%fnS=%fn, L, S);经过宏展开后,printf函数中的输出项L、S展开如下:L:2*3.1415926*4.0S:3.1415926*4.0*4.0printf函数被展开成:printf(L=%fnS=%fn, 2*3.1415926* 4.0, 3.1415926* 4.0* 4.0)二、带参数的宏定义带参数的宏定义不仅要进行字符串的替换,而且还要进行参数替换,一般

5、形式为:#define () 其中,宏定义中的参数为形参。程序中使用带参数的宏时,程序中的参数为实参,实参可以是常量、变量或表达式。宏展开时,将替换序列中的形参用相应位置的实参替换;若宏定义的替换序列中的字符不是形参,则在替换时保留。如:#define S(a,b) a*barea=S(2,3);其中a和b称为形参,2和3称为实参,在宏展开时,把2、3分别代替宏定义中的a、b,a*b中的*号保留,因此宏展开后语句为:area2*3;。【例10.1】从键盘输入两个数,输出较小的数。#include stdio.h#define MIN(a,b) (a)(b)?(a):(b)main() int

6、x, y; printf (Please input two integers:); scanf (%d %d, &x, &y); printf (MIN=%d, MIN(x, y);以上程序执行时,用序列(x)(y)?(x):(y)来替换MIN(x, y)。所以,可以输出两个数中的较小者。对使用带参数的宏定义需要说明几点: 对用宏定义定义的字符序列中的参数要用圆括号括起来,而且最好把整个字符串也用括号括起来,以保证在任何替换情况下都把宏定义作为一个整体,并且可以有一个合理的计算顺序,否则宏展开后,可能会出现意想不到的错误。如:#define S(r) 3.14159* r* r area=S

7、(a+b);经过宏展开变为:area=3.14159* a+b* a+b; 显然是由于在进行宏定义时,对r没有加括号造成与设计的原意不符。那么,为了得到形如:area=3.14159* (a+b) * (a+b); 就应该在宏定义时给字符序列中的形参加上括号,如;#define S(r) 3.14159* (r) * (r) 宏定义时,不要在宏名与带参数的括号之间加空格,否则会将空格后的字符都作为替换序列的一部分。如:#define S (a,b) a* b如果程序中有mul= S(x,y)则被展开为:mul=(a,b) a* b(x,y) 把函数和带参数的宏要区分开,虽然它们有相似之处,但它

8、们是不同的,其区别见表10-1所示。表10-1 函数和带参数之宏的区别区别类型函数带参数的宏是否计算实参的值先计算出实参表达式的值,然后代替形参不计算实参表达式的值,直接用实参进行简单的替换何时进行处理、分配内存单元在程序运行时进行值的处理、分配临时的内存单元,编译时进行宏展开,不分配内存单元,不进行值的处理类型要求实参和形参要定义类型,且类型一致不存在类型问题,只是一个符号表示,可以为任何类型调用情况函数的代码只作为一个拷贝存在,对程序较大、调用次数多的较合算,但调用函数时有一定数量的处理开销在源代码中遇到宏定义时,都将其扩展为代码,程序调用几次宏就扩展为代码几次,但调用宏时没有处理的开销1

9、0.1.3 文件包含文件包含是指一个源文件可以将另外一个源文件的全部内容包含进来,即将另一个C语言的源程序文件嵌入正在进行预处理的源程序中相应位置,一般形式为:#include 或#include “文件名”其中“文件名”指被嵌入的源程序文件中的文件名,必须用尖括号或双引号括起来。通过使用不同的括号使查找嵌入文件时可采用不同的查找策略。尖括号:预处理程序在规定的磁盘目录(通常为include子目录)查找文件。在Turbo C中,是由集成环境中参数设置子菜单中的目录选项(Option:Directores)中的Include目录项规定是在哪一个目录,如设为TCINCLUDE,则就在此子目录中查找

10、。一般包含C的库函数常用这种方式。双引号“”:预处理程序首先在当前目录中查找文件,若没有找到则再去由操作系统的path命令设置的各目录中查找,若还没有找到,最后才去Include子目录中查找。图10-1表示了“文件包含”的含意,原来的源程序文件mypro.c和用文件包含命令嵌入的源程序文件file1.c在逻辑上被看成同一文件,经过编译以后生成一个目标文件mypro.obj。在C语言的编译系统中有许多以.h(h为head的缩写)为扩展名的文件,被称为“头文件”。在使用C语言的编译系统提供的库函数进行程序设计时,通常需要在源文件的开始部分包含进来相应的头文件。这些头文件都是由C语言提供的源程序文件

11、,其中主要内容是使用相应库函数时所需要的函数原型说明、变量说明、类型定义及宏定义等。例如,在程序中要使用输入、输出类库函数(如putchar()等),就要在程序中加入“#include ”;使用数学处理类库函数,就需要在程序中加入命令“#include ”。因此若能正确使用#include语句,就可以减少不必要的重复工作,提高工作效率。使用#include语句要注意以下几点: 一个#include语句只能指定一个被包含文件,若包含n个则需n个#include语句。 若#include语句指定的文件内容发生变化,则应该对包含此文件的所有源文件重新编译处理。 文件包含命令可以嵌套使用,即一个被包含

12、的文件中可以再使用#include语句包含另一个文件,而在该文件中还可以再包含其它文件,通常允许嵌套10层以上。10.1.4 条件编译C语言的编译预处理程序还提供了条件编译能力,使得可以对源程序的一部分内容进行编译,即不同的编译条件产生不同的目标代码。条件编译命令有以下几种形式:1#ifdef 标识符 程序段1 #else 程序段2 #endif其作用:若标识符已经被定义过(一般用#define命令定义),那么程序段1参加编译,否则程序段2参加编译,其中#else部分可以省略,即: #ifdef 标识符 程序段1 #endif例:#ifdef DEBUGprintf(x=%d,y=%dn, x

13、, y);#endif若DEBUG被定义过,即:#define DEBUG则在程序运行时输出x,y的值,以便调试时用于分析;若删去#define DEBUG,则此处的printf语句就不参加编译。注意条件编译与if语句有区别,即不参加编译的程序段在目标程序中没有与之对应的代码。如果是if语句,则不管表达式是否为真,if语句中的所有语句都产生目标代码。2#ifndef标识符 程序段1 #else 程序段 2 #endif其作用:若标识符没有定义,程序段1参加编译,否则程序段2参加编译,其中#else部分可以省略,即: #ifndef标识符 程序段1 #endif例:#ifndef DEBUG p

14、rintf(x=%d,y=%dn, x, y);#endif若DEBUG没有定义,则在程序运行时输出x,y的值;若用#define定义了DEBUG,则此处的printf语句就不参加编译。3#if 表达式 程序段1#else 程序段 2#endif其作用:若表达式为“真”(非0),程序段1参加编译,否则程序段2参加编译,其中#else部分可以省略。例:#define FLAG 1#if FLAG a=1;#else b=0;#endi若FLAG为非0,则编译语句“a=1;”,否则编译语句“b=0;”。注意#if预处理语句中的表达式是在编译阶段计算值的,因而此处的表达式不能是变量,必须是常量或用#

15、define定义的标识符。4#undef 标识符其作用:将已定义的标识符变为未定义的。例如:#undef DEBUG则语句:#ifdef DEBUG为假(0),而语句:#ifndef DEBUG为真(非0)。10.2 位运算所谓位运算是指进行二进制位的运算。在系统软件中,常要处理二进制位的问题。例如,将一个存储单元中的各二进制位左移或右移一位,两个数按位相加等。C语言中对一个整数(包括无符号整数、字符型、长整数)可以进行如表10-2所示的位操作。表10-2 位运算符及其含义运算符含义&按位与|按位或按位异或按位取反右移说明: 位运算符除为一元运算外,其余都为二元运算,例如x&y ,xy,xy,

16、x|y,x。 操作数只能是整型或字符型数据,不允许是实型数据。下面对各种位操作进行介绍。1 按位与、按位或、按位异或运算符与操作: 0&0=0 0&1=0 1&0=0 1&1=1 或操纵: 0|0=0 0|1=1 1|0=1 1|1=1异或操作: 00=0 01=1 10=1 11=0这里的0和1均指一个二进制位。下面举例说明异或操作:int x=62;x=x43690;printf(%u,%xn, x, x);结果:43668,aa94000000000011111062101010101010101043690101010101001010043668从以上操作可以看出完成的操作是逐位进行

17、的。【例10.2】设变量n为一正整数,试写表达式,判断n是否为奇数。在整数n的二进制表示中,如果“个位”数为0,则n为偶数,如果“个位”数为1,则n为奇数,计算二进制“个数”数的式子为:n & 1设n分别取100和25,则上式的运算结果为 0000 0000 0110 0100 100 0000 0000 0001 1001 25& 0000 0000 0000 0001 1 & 0000 0000 0000 0001 1 0000 0000 0000 0000 0 0000 0000 0000 0001 1整数n与1按位与的结果为整数n二进制表示的最低位,当n取100时,n的二进制最低位为0

18、,则n为偶数,表达式n&1的结果为0,当n取25时,n的二进制最低位为1,则n为奇数。因此,n是否为奇数的判断式子为:n & 1 != 0 或 n & 1 (式1)而常规的判断是否奇数的式子为:n % 2 != 0 或 n % 2 (式2)由于按位与是各个位分别进行运算,并且各个位之间并没有进位或借位关系,理论上位运算的执行速度可以比加减运算还要快,而求余运算是除法运算,所以,式1的运算速度超过式2。2 按位取反运算符()按位取反,即各位变反。0变1,1变0。如:int x=1;printf(x=%xn,x);结果为:x=fffe3 左移运算符()把一个数中的所有二进制位向左移指定位数,右边空

19、位补0。如: 2570)把一个数中的所有二进制位向右移指定的位数,移到右端的低位被舍弃,对有符号数,左边空位补原来的符号位(最高位);对无符号数,左边空位补0。我们仍用0a0a(2570)这个数进行说明,如: 25702 2为右移两位移前: 0000101000001010 移后: 000000101000001010 补2位0即,移前为0a0a (2570) 移后为0282(642)因为257022 =642.5可见移后相当于移前这个整数除以2 n 取整(n为右移的位数,这里n=2)。这个规律对负数也完全符合。如: -12482结果应为-312。【例10.3】能否使用16位二进制表示一天中的

20、时、分、秒等时间信息。时间中的时、分和秒为3个整数,一般情况下,每个整数分别使用16位二进制表示,现在要求将这3个信息“挤”在一个16位二进制数中,问应如何组织?时的取值范围为0至23,共24个小时,由于25=32,故最多使用5位二进制即可表示小时信息;分的取值范围为0至59,每小时共60分钟,由于26=64,故最多使用6位二进制即可表示分钟信息;秒的取值范围也为0至59,每分钟共60秒,由于26=64,故最多使用6位二进制即可表示秒信息。总计共需17位二进制以精确表示时分秒信息。计算机一般使用16位或32位表示信息,如果最多只能使用16位二进制,则必须牺牲某个信息量的精度,即秒最多只能取5位

21、,对应0至59中的30个偶数秒,而奇数秒无法表示。例如为了表示时间13:20:30。小时为13,对应5位二进制为01101;分钟为20,对应6位二进制为010100;秒为30,对应6位二进制为011110,去掉其中的最低位,则秒使用5位二进制01111表示。则该时间的16位二进制表示为:01101 010100 01111显然上式的16位二进制不能简单地转换成十进制数并理解成27279,它是由多个信息拼接而成的。设时为h,分为m,秒为s,拼接的16位二进制数为d,则d的二进制表示形式为:hhhhh mmmmmm sssss (式3)而小时的二进制表示为:0000 0000 000h hhhh

22、(式4)将式4中的结果左移11位就得到式3中的最高5位,表达式为d11。同样地,将分钟m左移5位(m1)可以得到5位二进制表示的秒,用于表示0、2、4等偶数秒。则计算d的完整表达式为:d = (h 11) | (m 1); (式5)或d = (h 11) + (m 1); /* 使用加运算 */当变量h、m和s的值有可能超界时,通过按位与可以限定位的宽度,如下式:d = (h&0x1F) 11) | (m&0x3F) 1)&0x1F; (式6)式子(h & 0x1F)1)&0x1F先将s右移一位(相当于除2),然后只保留5位。式3的也可以使用以下数学计算公式:d = h*64*32 + m*3

23、2 + s/2或d = (h*64 + m)*32 + s/2 (式7)同样当变量h、m和s的值有可能超界时,先限定位的宽度,如下式:d = (h%24*64+m%60)*32 + (s%60)/2 (式8)上述各式中,式5至式8是等价的,式5式6的效率比式7式8高。反过来,如果已知d,求解其中所包含的时间信息,使用以下运算:h = (d 11) & 0x1F;m = (d 5) & 0x3F;s = (d & 0x1F) 1;在系统软件、压缩软件或硬件相关的程序中,每个二进制位都是非常宝贵的资源,二进制位的重组或拆分是常见的,因此位运算是非常普遍和必不可少的。下面再举几个位运算的例子,具体过

24、程读者可自行分析。1写出表达式 (0x1234 & 0xFF) 4) & 15答案分别为12,6,12习 题1C语言的编译系统对宏命令的处理是 。A在程序运行时进行B在程序连接时进行C和C程序中的其它语句同时进行编译的D在对源程序中其它成份正式编译之前进行的2阅读程序#include #define MUL(x, y) (x) * ymain( )int a= 3, b=4, c;c=MUL(a+, b+);printf(%dn, c);上面程序的输出结果是 。A12 B15 C20 D163以下正确的描述是 。AC语言的预处理功能是指完成宏替换和包含文件的调用B预处理命令只能位于C源程序文件

25、的首部C凡是C源程序中行首以“#”标识的控制行都是预处理命令DC语言的编译预处理就是对源程序进行初步的语法检查4设有以下宏定义:#define WIDTH 80#define LENGTH WIDTH+40则执行赋值语句:v=LENGTH* 20;(v为int型变量)后,v的值是 。5设有以下程序,为使之正确运行,请填入应包含的命令行。main( ) double x=2, y=3; printf(%lfn, pow(x, y);6试定义一个带参数的宏swap(x,y),以实现两个整数之间的交换,并利用它将一维数组a和b的值进行交换。7分别用函数和带参数的宏编程实现从三个数中找出最小数。8用1010101010101010这个二进制对00ff这个十六进制数分别进行与、或、异或操作。9已知整数x、y的值分别为0x1532、0xabcd,编程将整数x的高字节作为整数z的高字节,整数y的高字节作为整数z的低字节,并输出整数z。10分别写出下列表达式的十进制结果: 200&15 (2004)&15 20015 1012

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1