C语言程序设计-进阶篇:第9章 编译预处理和位运算_第1页
C语言程序设计-进阶篇:第9章 编译预处理和位运算_第2页
C语言程序设计-进阶篇:第9章 编译预处理和位运算_第3页
C语言程序设计-进阶篇:第9章 编译预处理和位运算_第4页
C语言程序设计-进阶篇:第9章 编译预处理和位运算_第5页
已阅读5页,还剩58页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

C语言程序设计

—进阶篇第9章编译预处理和位运算内容概述不带参数宏的定义及使用带参数宏的定义及使用文件包含的概念位运算

教学目标1.了解编译预处理的概念,了解宏的概念。2.了解文件包含的概念,熟练掌握文件包含命令include的用法。3.了解条件编译的概念,熟悉常用的条件编译命令及其用法。4.了解字节和位的有关概念,正确使用常见的位运算符和位运算操作;5.了解位段的要领及位段的使用方法◆预处理是指在进行编译的第一遍扫描(词法扫描、语法分析、代码生成、代码优化)之前所做的工作。预处理是C语言的一个重要功能,也是C语言与其他高级语言的一个重要区别,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分进行处理,处理完毕自动进入对源程序的编译,将预处理的结果和源程序一起再进行通常的编译处理,以得到目标代码(OBJ文件)。

◆预处理命令以“#”号开头,结束不加分号。预处理命令包括宏定义、文件包含和条件编译。

C语言源程序中允许用一个标识符来表示一个较复杂的字符串,称为“宏”,被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。语法形式:#define标识符字符串#define为预编译符。标识符称为“宏名”,通常使用大写英文字母和有直观意义的标识符命名,以区别于源程序中的其它标识符。字符串可以是常数、表达式、格式串等。9.1.1不带参数的宏定义9.1宏定义宏定义后,程序中宏名就代表了该字符串。程序在预编译时,将源程序中出现的宏名PI替换为字符串“3.1415926”#include<stdio.h>#definePI3.1415926voidmain(){floatradius,length,area;

scanf("%f",&radius);length=PI*radius;area=PI*radius*radius;

printf("length=%.2f,area=%.2f\n",length,area);}【例9.1】使用无参宏定义圆周率,输入圆的半径,求圆的周长和面积。宏定义的作用:【例9.2】不带参数的宏定义举例。#defineM(y*y+3*y)#include"stdio.h"main(){

int

s,y;

printf("inputanumber:");

scanf("%d",&y);s=3*M+4*M+5*M;

printf("s=%d\n",s);}所有的M被(y*y+3*y)代替在宏定义中表达式(y*y+3*y)两边的括号不能少,否则会发生错误。(1)使用宏名代替一个字符串,可以增加程序的可读性,而且用宏名代替不易出错。(2)编译预处理时,将程序中PI用3.1415926代替,与宏调用的过程相反,这种将宏名替换成字符串的过程称为“宏展开”。关于宏定义的说明:(3)C语言中,用宏名替换一个字符串是简单的转换过程,不作语法检查。

(4)宏定义命令行放在源程序的函数外时,宏名的作用域从宏定义命令行开始到本源文件结束。(5)宏名的作用域可以使用#undef命令终止,形式为:#undef

标识符(6)在宏定义中,允许在宏体字符串中使用已定义过的宏名,这个过程称为嵌套宏定义。(8)对程序中用双引号括起来的字符串,即使与宏名相同,也不替换。(7)宏定义不是说明或语句,在行末不必加分号。9.1.2带参数的宏定义

1.带参数宏的定义与调用定义形式:#define标识符(形式参数表)字符串◆形式参数称为宏名的形式参数,简称形参;◆构成宏体的字符串中应该包含所指的形式参数;◆宏名与后续圆括号之间不能留空格。例如,宏定义与调用形式如下:

#defineM(y)y*y+3*y

/*宏定义*/

k=M(5);

/*宏调用*/◆带参数宏调用的一般形式为

宏名(实参表);2.宏展开◆带参数的宏展开是按#define命令行中指定的字符串从左到右进行置换。◆如果宏体字符串中包含宏名中的形参,则将程序语句中相应的实参代替形参,如果字符串中的字符不是参数字符,则保留。例带参数的宏定义#defineS(a,b)a*bmain(){

intarea;area=S(3,2);

printf("area=%d\n",area);}程序运行结果:area=6【例9.3】带参数的宏定义举例。#include"stdio.h"#defineMAX(a,b)(a>b)?a:bvoidmain(){int

x,y,max;

printf("inputtwonumbers:");

scanf("%d%d",&x,&y);max=MAX(x,y);

printf("max=%d\n",max);}说明

(1)带参宏定义中,宏名和形参表之间不能有空格出现。例如,把“#defineMAX(a,b)(a>b)?a:b”写为“#defineMAX(a,b)(a>b)?a:b”将被认为是无参宏定义,宏名MAX代表字符串(a,b)(a>b)?a:b。宏展开时,宏调用语句“max=MAX(x,y);”将变为“max=(a,b)(a>b)?a:b(x,y);”,这显然是错误的。(2)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。但对于以下宏调用语句:

s=SQ(p+q)/*原意求(p+q)2*/a=AR(a+b,b+c)/*原意求矩形的面积(a+b)*(b+c)*/注意:若定义#defineSQ(n)(n)*(n)#defineAR(a,b)(a)*(b)此时宏展开上述两个宏调用,结果为:宏替换是直接替换,必要时应在参数两侧加括号,在整个字符串外也应加括号。

若定义#defineSQ(n)n*n#defineAR(a,b)a*b此时宏展开上述两个宏调用,结果为:s=p+q*p+qa=a+b*b+cs=(p+q)*(p+q)a=(a+b)*(b+c)练习:分析下面程序的运行结果

#defineSQ(y)(y)*(y)#include"stdio.h"voidmain(){int

a,sq;

printf("inputanumber:");

scanf("%d",&a);sq=SQ(a+1);

printf("sq=%d\n",sq);}#defineSQ(y)(y)*(y)#include"stdio.h"voidmain(){int

a,sq;

printf("inputanumber:");

scanf("%d",&a);sq=SQ(a+1);

printf("sq=%d\n",sq);}当输入3时,以上两段程序各输出什么?3.带参数的宏与函数的区别

带参数的宏名与函数名相似,都是在宏名或函数名后跟一对圆括号;带参数的宏调用形式与带参数的函数的调用形式类似,都要求实参个数、次序与对应的形参一致。但两者仍然存在着很大的不同。【例9.5】带参函数与带参的宏的区别。/*带参函数*/#include"stdio.h"voidmain(){inti=1;

while(i<=5)printf("%5d",SQ(i++));printf("\n");}SQ(inty){

return((y)*(y));}带参的宏

#include"stdio.h"#defineSQ(y)((y)*(y))voidmain(){inti=1;

while(i<=5)printf("%5d",SQ(i++));printf("\n");}(1)宏定义与宏调用是为了减少书写量和提高运行速度;而函数定义、函数调用是为了实现模块程序设计,便于构造软件。

(2)宏调用展开后的代码是嵌入源程序中的,且每调用一次,嵌入一次代码。因此,宏调用时总的程序代码是增加的;而函数调用是执行时转入对应的函数,执行后返回主调函数,无论调用多少次,函数体的代码都不会增加。所以函数也解决代码重用问题。

(3)宏定义的参数是字符,不需说明类型;而函数定义的参数是数据,不仅要说明其类型,而在调用时必须检查实参与形参在类型上的一致性。(4)宏调用在编译的预处理时完成宏展开;而函数调用在程序运行过程中被执行。(5)除了将宏展开结果嵌入源程序外,宏调用不存在内存分配问题;而对函数可能需分配临时空间以存放函数调用之结果。

◆编译预处理的文件包含功能是一个源程序通过#include命令把另外一个源文件的全部内容嵌入到源程序中来。◆编译预处理程序把#include命令行中所指定的源文件的全部内容放到源程序的#include命令行所在的位置。◆在编译时并不是作为两个文件联接,而是作为一个源程序编译,得到一个目标文件。9.2文件包含1#include命令格式首先对使用包含文件的源文件所在的目录进行检索,若没有找到指定的文件,再在标准目录中检索。

#include”文件名”#include<文件名>只检索C语言编译系统所确定的标准目录

2使用#include命令时的注意事项例如:(1)一个include命令只能指定一个被包含文件,如果要包含n个文件,用n个include命令。

(2)当一个程序中使用#include命令嵌入一个指定的包含文件时,被嵌入的文件中还可以使用#include命令,又可以包含另外一个指定的包含文件。

(3)如果文件file1.h中包含文件file2.h,而文件file2.h要用到文件file3.h的内容,则可在文件file1.h中用两个include命令分别包含文件file2.h和文件file3.h,而且文件file3.h应出现在文件file2.h之前,即在文件file1.h中:

#include"file3.h"#include"file2.h"

使用条件编译功能,为程序的调试和移植提供了有力的机制,使程序可以适应不同系统和硬件设置的通用性和灵活性。1#if命令功能:(1)单分支结构若表达式的值为真(非0),则编译语句,否则语句不被编译。其中语句可为若干C语言合法的语句和(或)命令行。9.3条件编译#if常量表达式语句#endif(2)双分支结构功能:若表达式的值为真(非0),则编译语句1;否则编译语句2。(3)多分支结构#if常量表达式语句1#else

语句2#endif#if常量表达式1

语句1#elif

常量表达式2

语句2…#elif

常量表达式n

语句n#else

语句n+1#endif2#ifdef命令

#ifdef

标识符语句

#endif或:#ifdef

标识符语句1#else

语句2#endif

(1)#ifdef命令行若标识符已定义,则编译语句。若所指标识符已定义,则编译语句1;否则编译语句2。例如:#defineDEBUG#ifdefDEBUG

printf("x=%d,y=%d,z=%d\n",x,y,z);#endif

#defineR1main(){floatc,r,s;

printf("输入圆的半径或矩形的边长:");

scanf("%f",&c);#ifdefRr=3.14*c*c;

printf("圆面积=%f\n",r);#elses=c*c;

printf("矩形面积=%f\n",s);#endif}【例9.6】根据需要,输入圆的半径或矩形的边长,计算面积。#ifndef

标识符 语句#endif或形式为:#ifndef

标识符 语句1#else

语句2#endif(2)#ifndef命令行若标识符未定义,则编译语句。若所指标识符未定义,则编译语句1;否则编译语句2。3#undef命令

形式:#undef

标识符其中#undef为预编译符。功能:使所指标识符变为无定义。C语言既是一种高级语言,广泛应用于应用软件的开发和程序设计,同时又是一种低级语言,可以用于系统软件的开发和程序设计,如自动控制系统中的过程控制、参数检测、数据通讯等控制程序,都可以综合利用C语言中的指针操作、位运算和位段技术来实现。9.4位运算

所谓位运算,是指参与运算的量,按对应的二进制位进行运算。按位运算是对字节或字中的实际位进行检测、设置或移位,它只适用于整型和char型变量,对其他数据类型不适用。

位运算的特点:运算按二进制逐位进行——没有借位和进位。9.4.1位运算和位运算符位运算符有以下六种:运算符名称举例优先级~按位取反~flag(高)(算术运算符)<<左移a<<2>>右移b>>3(关系运算符)&按位与flag&0x37^按位异或flag^0xC4|按位或flag|0x5A(低)(赋值运算符)1.按位与运算:&运算规则0&0=0;0&1=0;1&0=0;1&1=1;用法按位清零保留某些指定位例#include<stdio.h>voidmain(){unsignedchara,b;

printf("Enteraandb:");

scanf("%o,%o",&a,&b);

printf("a&b=%o\n",a&b);}计算

010000(a)&011000(b)010000

001010(a)&010000(b)000000Enteraandb:20,30a&b=20Enteraandb:12,20a&b=02.按位或运算:|运算规则0|0=0;0|1=1;1|0=1;1|1=1;用法按位置1例#include<stdio.h>voidmain(){unsignedchara,b;

printf("Enteraandb:");

scanf("%o,%o",&a,&b);

printf("a|b=%o\n",a|b);}Enteraandb:20,30a|b=30Enteraandb:12,20a|b=32计算

010000(a)|011000(b)011000

001010(a)|010000(b)0110103.按位异或运算:^运算规则0^0=0;0^1=1;1^0=1;1^1=0;说明相“异”则为1,相“同”则为0相当于按位且无进位的加法例

以下程序的功能是将a数据的低4位取反。#include<stdio.h>voidmain(){unsignedchara=0x39,b=

;a=a^b;

printf("%x\n",a);}答案:0x0f

计算

00111001(a)^00001111(b)00110110与0相异或,保持原值不变与自身相异或,则全部位清零交换两个整数值a=a^b;b=b^a;a=a^b;4.取反运算:~运算规则~0=1;~1=0;用法所有位翻转获得适用于不同系统的位运算模板例#include<stdio.h>voidmain(){ chara=3;

intb=10;

printf("~a=%d,~b=%d\n",~a,~b);}结果:~a=-4,~b=-11

计算~a:补码:11111100原码:10000100~b:补码:11110101原码:100010115.左移运算:<<运算规则i<<n把i各位全部向左移动n位最左端的n位被移出丢弃最右端的n位用0补齐用法若没有溢出,则左移n位相当于乘上2n运算速度比真正的乘法和幂运算快得多例

以下程序的运行结果是

。#include<stdio.h>voidmain(){ unsignedinta=0x3ef,b; b=a<<2;

printf("%x,%x\n",a,b);}A)3ef,fbB)3ef,fbcC)fbc,3efD)fbc,fbc结果:B

以下程序的运行结果是

。#include<stdio.h>voidmain(){

inta=12,b; b=0x1f5&a<<3;

printf("%d,%d\n",a,b);}结果:12,96计算已知:0x1f5为111110101且:∵a为1100∴a<<3为1100000111110101&001100000001100000=966.右移运算:>>运算规则i>>n把i各位全部向右移动n位最右端的n位被移出丢弃最左端的n位用0补齐(逻辑右移)或最左端的n位用符号位补齐(算术右移)用法右移n位相当于除以2n,并舍去小数部分运算速度比真正的除法和幂运算快得多例

以下程序的运行结果是

。#include<stdio.h>voidmain(){

inta=9,b=-9;

printf("%d,%d",a>>2,b>>2);}结果:2,-3(-9的补码:1111111111110111,右移后为1111111111111101)应用示例①从整数a最右端第m个位置开始取该位开始右面n位。算法如下:

b=a>>(m-n+1)c=~(~0<<n)d=b&c

注:位自右向左从0开始编号应用示例②将一个整数a循环右移n位。算法如下:b=a<<(16-n)c=a>>nc=c|b

【例9.7】取一整数从右端开始的4~7位。〖程序分析〗(1)先将这个整数的4~7位右移到0~3位,这很容易用右移运算来实现。(2)再用一个0~3位为1其余位为0的数(15)来与整数进行“与”运算。“与”运算的结果就是这个整数的4~7位。#include<stdio.h>voidmain(){unsigneda,b,d;

scanf("%o",&a);b=a>>4;d=b&15;

printf("%o,%d\n%o,%d\n",a,a,d,d);}

若输入八进制数321,输出结果是什么?练习:将16进制短整数按二进制打印输出.

输入:F1E2

输出:1111000111100010

输入:13A5

输出:0001001110100101算法思想:从高位到低位逐位测试每一位是0或是1。可顺次设置屏蔽字分别为100000000000

0000、010000000000

0000、……、00000000

00000001,与该数进行&运算,从而保留所需的一个位的状态(其余各位为0)。若结果非零则输出1,否则输出0。#include<stdio.h>voidmain(){

inti; shorta;

scanf("%X",&a); for(i=15;i>=0;i--) printf("%1d",a&1<<i?1:0);}

字节是存储数据的最小单位,而实际上有些信息是不需要一个字节存储的,如逻辑值“真”或“假”只需要1位即可。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。9.4.2位段怎样向一个字节中的一个或几个二进制位赋值和改变它的值呢?问题:在C语言中,可以用两种方法实现位存储。

1.人为实现人为地将一个字节data设成几项。例如:a、b、c、d分别占1、1、4、2位;如果想将c的值从原来的0变为12,可以进行如下操作:(1)将数12左移4位,使1100成为右面起第4~7位。(2)将data与“12<<4”进行“按位或”运算,即可将c的值变成12。如果c的原值不为0,要先将c变成0,然后再进行(1)、(2)步的运算。让c的值变成0的实现方法是data=data&15;这就使data第4~7位为0(即c的值为0),其余各位不变。将上面的几个步骤结合起来,可得到改变data中c值的方法:data=data&15|(n&15)<<4;其中,n为将赋给c的值,(n&15)的作用是只取n的右端4位的值,其余各位置0,即把n放到最后4位上,(n&15)<<4将n置在4~7位上。此方法使用起来相对难以理解一些,下面介绍一种更为方便的方法----位段来处理这个问题。2.位段(1)位段的概念:C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”(bitfield)。(2)位段的定义:在结构体成员变量的定义后面加上冒号和位数。定义格式为struct

位段结构体名{unsigned成员1:位数;…unsigned

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论