



下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第10章C结构、联合、位操作和枚举目标能够创建和使用结构、联合和枚举。能够按值和按引用调用将结构传递给函数。能够用位运算符来处理数据。能够为紧凑的存储数据而创建位字段。简介结构(structure)是相关变量的集合,有时候称为集合体,其含义是相同的。结构可以包含许多不同数据类型的变量,与仅仅包含相同数据类型元素的数组相反。结构通常用于定义存储在文件中的记录(参见第11章)。指针和结构有助于构成更加复杂的数据结构,例如链表、队列、堆栈和树(参见第12章)。结构定义结构是派生的数据类型,也就是说,使用其他类型的对象来构造它们。考虑下列结构定义:structcard{char*face;char*suit;关键字struct引入了结构定义。标识符card是结构标记。结构标记命名结构定义,而且与关键字struct一起用来声明结构类型的变量。在这个示例中,结构类型是structcard。结构定义大括号内声明的变量是结构的成员。相同结构的成员必须具有独一无二的名称,但两个不同的结构可能包含相同名称的成员,而不互相冲突(我们很快将知道为什么)。每个结构定义必须用分号结束。常见的编程错误10.1 产 忘记结构定义末尾的分号。structcard的定义包含类型为char*的两个成员:face和suit。结构成员可以是基本数据类型变量(例如int,float等),或者集合体,例如数组和其他结构。我们在第6章介绍过,数组的每个元素都必须是相同的类型。然而,结构成员可以具有不同的数据类型。例如:structemployee{charfirstName[20];charlastName[20];intage;chargender;doublehourlySalary;};包含用于姓和名的字符数组成员,用于表示职员年龄的int成员和表示职员性别的包含'M域者F的char成员,以及表示职员小时工资的double成员。结构不能包含它本身的实例。例如,类型structemployee的变量不能在structemployee的定义中声明。然而,可以包含指向structemployee的指针。例如:structemployee2{charfirstName[20];charlastName[20];intage;chargender;doublehourlySalary;structemployee2person;//ERRORstructemployee2*ePtr; //pointer};structemployee2包含它本身(person)的实例,这是一个错误。因为ePtr是一个指针(指向类型structemployee?),所以允许出现在定义中。包含作为相同结构类型指针的成员的结构称为自引用结构。在第12章中使用自引用结构来建立不同类型的链接数据结构。前面的结构定义并没有在内存中保留任何空间,而该定义创建了用于声明变量的新数据类型。结构变量被声明为类似其他类型的变量。声明:structcardafdeck[52],*cPtr;声明a是类型structcard的变量,声明deck是具有52个类型为structcard的元素的数组,而cPtr是指向structcard的指针。通过在结构定义的右大括号和结束结构定义的分号之间加入用逗号分开的变量名列表,也可以声明给定结构类型的变量。伊U如,前面的声明可以合并到structcard结构定义中,如下所示:structcard{char*face;char*suit;}a,deck[52],*cPtr;结构标记名称是可选的。如果结构定义并没有包含结构标记名称,则仅仅能在结构定义中声明结构类型变量,而不是在另一个声明中。i"^|良好的编程习惯10.1 LxJ当创建结构类型时,一定要提供结构标记名称.结构标记名称对于在程序中稍后声明结构类型的新变量是非常方便的。良好的编程习惯10.2 选择有意义的结构标记名称可以帮助解释程序。在结构上可以执行的合法操作如下:将结构变量赋给相同类型的结构变量,得到结构变量的地址(&),访问结构变量(参见104节)的成员和使用sizeof运算符来确定结构变量的大小。।我常见的编程错误10.2 [jjj]将某种类型的结构赋给不同类型的结构。因为结构成员没有必要存储在内存的连续字节中,所以不能使用运算符==和!=来比较结构。有时候,因为计算机可能仅仅在某些内存边界上存储特定的数据类型,例如半个字、字或者双字边界,所以在结构中可能有“洞二字是标准内存单元,它用于在计算机中存储数据,通常是两个字节或者4个字节。考虑下列结构定义,其中声明了类型structexample的sample1和sample2:structexample{charc;inti;}samplel,sample2;使用两个字节的字的计算机可能需要在字边界上对齐structexample的每个成员,也就是在字的开头处(这是和机器相关的)。图10.1说明了类型为structexample的变量的存储对齐示例,其中变量已经被赋予字符言和整数97(按位来表示这个值)。如果成员存储在字边界的开头处,则在类型structexample变量的存储空间中有一个字节的空洞(见图中的字节1)。1个字节空洞中的值是没有定义的。如果sample1和sample2的成员值实际上相等,则因为没有定义的1字节空洞不会包含相同的值,所以结构比较并不一定相等。字节0 1 2 3011000010000000001100001图10.1structexample类型变量可能的存储对齐方式,说明内存中一个没有定义的区域7-^J常见的编程错误10.3 ,9 比较结构是错误的。K可移植性提示10.3 因为特定类型的数据项大小依赖于计算机,而且因为存储对齐与计算机相关,所以结构的表示也与计算机相关.可以像数组那样使用初始值列表来初始化结构。为了初始化结构,需要在结构声明中变量名称的后面加入等号以及用逗号分隔的初始值列表,并在列表之外加入大括号。例如,声明:structcarda={"Three","Hearts"};创建变量a的类型是structcard(如前面定义的那样),并将成员face初始化为"Three",将成员suit初始化为"Hearts"。如果列表中初始值的个数小于结构成员个数,则将把剩余的成员自动初始化为0(如果成员是指针,则初始化为NULL)。如果没有在外部声明内显式进行初始化,则在函数定义外声明的结构变量(也就是外部)将初始化为0或者NULL。通过将相同类型的结构变量,或者将值赋予结构的单个成员,也可以在赋值语句中初始化结构变量。10.4访问结构成员两个运算符用于访问结构成员:结构成员运算符(.),也称为点运算符;结构指针运算符(->),也称为筋头运算符。结构成员运算符通过结构变量名来访问结构成员。例如,为打印前面声明中结构a的成员suit,使用语句:printf("%cn,a.suit);结构指针运算符由负号(-)和大于号(>)构成,运算符之间没有空格,要通过结构指针来访问结构成员。假设已经将指针aPtr声明为指向structcard,而结构a的地址已经赋给aPtro为用指针aPtr打印结构a的成员suit,使用语句:printf(*'%s"zaPtr—>suit);表达式aPg>sui^U(*aPtr).suit等价,它间接引用指针,并使用结构成员运算符来访问成员suit。因为结构成员运算符(.)的优先级比指针间接引用运算符(*)高,所以这里需要括号。结构指针运算符和结构成员运算符,以及用于数组下标的圆括号和方括号([])具有最高运算符优先级,而且从左向右结合。良好的编程习惯10.3 避免为不同类型结构的成员使用相同的名称。虽然允许这样,但可能会造成混淆.良好的编程习惯10.4 L^J不要在->和.运算符周围加入空格。这可以强调包含运算符的表达式实际上是一个变量名。常见的编程错误10.4 产 在结构指针运算符的-和》之间加入空格(或者在任何多字符运算符的组成部分之间插入空格,但?:除外).।飞 常见的编程错误10.5 了 试图仅仅使用成员名来引用结构成员。7^常见的编程错误10.6 停当使用指针和结构成员运算符来引用结构成员时,没有使用括号(例如,*aPtr.suit是语法错误).图10.2的程序说明了结构成员和结构指针运算符的使用方法。通过使用结构成员运算符,结构a的成员分别赋予值“Ace”和"Spades”(第16行和第17行)。将结构a的地址赋给指针aPtr(第18行)。函数prinlf使用结构成员运算符以及变量名a,结构指针运算符以及指针aPtr和结构成员运算符和间接引用指针aPtr来打印结构变量a的成员(第19行到第22行)。/*Fig.10.2:figl0_02.cUsingthestructurememberandstructurepointeroperators*/#include<stdio.h>structcard{char*face;char*suit;);1011intmain(){structcarda;structcard*aPtr;a.face ="Ace";a.suit ="Spadesn;aPtr= &a;printf( M%s%s%s\n%s%s%s\n%s%s%s\nn,20 a.face,"of",a.suit.21 aPtr->face,nofn,aPtr->suit,22 (*aPtr).face,"of”,(*aPtr).suit);23return0;24}AceofSpadesAceofSpadesAceofSpades通过传递单个结构成员、传递整个结构或者传递结构指针,就可以将结构传递给函数。当把结构或者单个结构成员传递给函数时,它们是按值调用传递的。所以,被调用函数不能修改调用者结构的成员。为按引用调用传递结构,需要传递结构变量的地址。结构数组和所有其他数组一样,将自动按引用传递。在第6章中,我们说明了通过使用结构可以按值调用来传递数组。为按值调用传递数组,可以创建一个结构,并用数组作为成员。既然结构是按值调用传递的,则数组也将按值调用传递。7^3常见的编程错误10.7 假设结构和数组一样是自动按引用调用传递的,而且企图在被调用函数中修改调用者结构中的值.性能提示10.1 L?—I按引用调用传递结构比按值调用传递结构的效率高(这要求复制整个结构)。10.6typedef关键字typedef为给前面定义的数据类型创建同义词(或者别名)提供了一种机制。结构类型的名称通常是用typedef定义的,以建立较短的类型名称。例如,语句:typedefstructcardCard;定义了新类型名称Card作为类型structcard的同义词。C程序员经常使用typedef来定义结构类型,这样不需要结构标记。例如,下面的定义:typedefstructchar*face;char*suit;}Card;将创建结构类型Card,而不需要单独的typedef语句。常见的编程错误10.5 ,电大写typedef名称的第1个字母,以强调这些名称是其他类型名称的同义词。现在可以使用Card来声明类型structcard的变量。声明:Carddeck[52];声明了具有52个Card结构的数组(也就是类型Structcard的变量)。用typedef创建新名称并不会创建新类型,因为typedef仅仅创建了新的类型名称,它可以用作现有类型名称的别名。有意义的名称可以帮助程序进行说明。例如,当我们读前面的声明时,我们知道“deck是由52个Card构成的数组”。typedef经常用于为基本数据类型创建同义词。例如,需要4字节整数的程序可能需要在一个系统上使用类型int,而在另一个系统上使用类型long。具有可移植性的程序经常使用typedef来为4字节整数,例如Integer,创建别名。可以在程序中一次性修改别名Integer,以使得程序在两个系统上都可以工作。m可移植性提示10.2 使用typedef来提高程序的可移植性。10.7示例:高性能洗牌和发牌仿真程序图10.3中的程序以第7章讨论的洗牌和发牌模拟过程为基础。该程序用结构数组来表示一副纸牌,使用高性能洗牌和发牌算法。高性能洗牌和发牌程序的输出如图10.4所示。/*Fig.10.3:figl0_03.cThecardshufflinganddealingprogramusingstructures#include<stdio.h>#include<stdlib.h>#include<time.h>structcard{constchar*face;constchar*suit;};typedefstructcardCard;13voidfillDeck(Card*const,constchar*[]zconstchar*[]);voidshuffle(Card*const);voiddeal(constCard*const);18intmain(){Carddeck[52];22constchar*face[]2324252627constchar*suit[]={"Ace","Deuce","Three”,“Four","Five”,"Six",wSeven",MEight"“Nine","Ten","Jack","Queen","King"={"Hearts","DiamondsM,28293031323334353637383940414243444546474849505152535455565758596061626364656667686970Clubs","Spades”};srand(time(NULL));fillDeck(deck,face,suit);shuffle(deck);deal(deck);return0;voidfillDeck(Card*constwDeck,constchar*wFace[],constchar*wSuit[])(inti;for(i=0;i<=51;i++){wDeck[i].face=wFace[i%13];wDeck[i].suit=wSuit[i/13];voidshuffle(Card*constwDeck)(inti,j;Cardtemp;for(i=0;i<=51;i++){j=rand()%52;temp=wDeck[i];wDeck[i]=wDeck[j];wDeck[j]=temp;voiddeal(constCard*constwDeck)(inti;for(i=0;i<=51;i++)printf("%5sof%-8s%cM,wDeck[i].face,wDeck[i].suit,(i+1)%2?f\f:*\0');EightofDiamondsEightofClubsSevenofHeartsAceofClubsDeuceofSpadesSevenofSpadesJackofClubsKingofHeartsThreeofHeartsThreeofClubsTenofHeartsTenofClubsSixofClubsSixofHeartsNineofDiamondsJackofSpadesKingofDiamondsNineofSpadesSixofSpadesQueenofDiamondsAceofSpadesKingofClubsKingofSpadesQueenofHeartsFourofSpadesFourofClubsAceofHeartsFiveofSpadesDeuceofDiamondsTenofDiamondsSixofDiamondsDeuceofClubsTenofSpadesJackofDiamondsThreeofDiamondsNineofClubsDeuceofHeartsSevenofDiamondsQueenofSpadesThreeofSpadesAceofDiamondsFiveofClubsSevenofClubsFourofHeartsEightofSpadesFiveofDiamondsNineofHeartsFiveofHeartsFourofDiamondsEightofHeartsJackofHeartsQueenofClubs图10.4高性能洗牌和发牌模拟程序的输出在图10.3的程序中,函数fillDeck(在第38行中定义)按顺序从每副纸牌的A到K来初始化Card数组。Card数组传递给(第33行)函数shuffle(在第49行中定义),在这里实现了高性能洗牌算法。函数shuffle将具有52个Card结构的数组作为参数。函数在第54行中使用一个for结构,在52张纸牌(数组下标从。到51)中循环。对于每张纸牌,随机挑选0到51之间的数字。下一步,交换数组中当前Card结构和随机选定的Card结构(第56行〜第58行)。在整个数组的一次循环中,将总共进行52次交换,这样就打乱了Card结构数组的顺序。这个算法并没有第7章介绍的洗牌算法那样具有无限延期的缺点,因为Card结构将在数组中适当的位置交换,所以在函数deal(在第62行中定义)中实现的高性能发牌算法,仅仅需要对整个数组循环一次就可以处理洗过的纸牌。7-^1常见的编程错误10.8 户 当引用结构数组中的单个结构时,忘记包含数组下标。
联合是一种派生的数据类型,它与结构类似,其成员共享相同的存储空间。对于程序中的不同场合,一些变量可能是不相关的,但其他变量是相关的,所以联合共享空间,而不是对不使用的变量浪费存储空间。联合的成员可以是任何类型。存储联合所需要的字节个数必须至少可以存储最大的成员。在多数情况下,联合包含两种或者更多的数据类型,一次仅仅能引用一个成员,所以仅仅能引用一个数据类型。程序员应该确保用正确的数据类型来引用联合中的数据。常见的编程错误10.9 [jjj]用与联合中存储的数据类型不同的类型来引用数据是逻辑错误.®可移植性提示10.3 如果作为某种类型在联合中存储数据,然后作为另一种类型来引用,则结果依赖于实现。按照与结构相同的格式用关键字union来声明联合。union声明:unionnumber{intx;doubley;};说明number是具有成员intx和doubley的union类型。联合定义通常在程序中的main之前,这样可以用该定义来声明程序中所有函数的变量。软件工程经验10.1和struct声明一样,union声明仅仅创建了一个类型。在任何函数之外加入union或者struct声明并不会创建全局变量.在联合上可以执行的操作如下:将联合赋给相同类型的另一个联合、获取联合的地址(&),以及使用结构成员运算符和结构指针运算符来访问联合成员。与不能比较结构的原因相同,也不能用运算符==和!=来比较联合。在声明中,仅仅能用与第1个联合成员类型相同的值来初始化联合。例如,在前面的联合中,因为用int初始化了联合,所以声明:unionnumbervalue{10};合法地初始化了联合变量value,但下列声明是非法的:unionnumbervalue={1.43};常见的编程错误10.10比较联合是语法错误.।飞常见的编程错误10.11 了在声明中,用与联合的第1个成员类型不同的数值来初始化联合。njQ可移植性提示1生4 存储联合所需要的存储空间数量依赖于实现。®可移植性提示10.5 一些联合可能不能轻松地移植到其他计算机系统上。联合能否移植通常依赖于给定系统上存储联合成员数据类型所需要的存储对齐方式。a性能提示10.2 联合可以节省存储空间。图10.5中的程序使用类型为unionnumber的变量value(第12行)来以int和double的形式显示联合中存储的数值。该程序输出依赖于实现,说明了double数值的内部表示和int的表示是相当不同的。/♦Fig.10.5:figl0_05.cAnexampleofaunion/include<stdio.h>unionnumber{intx;doubley;);intmain(){unionnumbervalue;value.x=100;printf("%s\n%s\n%s%d\n%s%f\n\nn,"Putavalueintheintegermemberw,"andprintbothmembers.","int: ",value.x,ndouble:\nn,value.y);value.y=100.0;printf("%s\n%s\n%s%d\n%s%f\nnr"Putavalueinthefloatingmemberwz"andprintbothmembers.n,"int: ",value.x,"double:\nn,value.y);return0;)Putavalueintheintegermemberandprintboth: 100double:-92559592117433136000000000000000000000000000000000000000000000.00000Putavalueinthefloatingmemberandprintboth: 0double:100.000000图10.5用两种成员数据类型来打印联合的值10.9位运算符计算机内部用位序列来表示所有数据。每位值可以是0或者1。在多数系统上,8位序列构成一个字节,也就是类型char的变量的标准存储单元。其他数据类型存储在更多数量的字节中。位运算符用于处理整数操作数(char、short,int和long;signed和unsigned)。无符号整数通常用位运算符来处理。B可移植性提示10.6 位数据处理与计算机相关。注意,本节中对位运算符的讨论说明了整数操作数的二进制表示。为详细了解二进制(也称为基为2),请参见附录E。而且,第10.9和10.10小节中的程序使用MicrosoftVisualC++来测试。因为位处理依赖于计算机,所以这些程序可能无法在您的系统上使用。位运算符包括按位与(&)、按位或(I)、按位异或(人)、左移位(«),右移位(»)和位求反(~).按位与、按位或和按位异或运算符逐位比较它们的两个操作数。如果两个操作数中对应位是1,则按位与运算符将结果中的位设置为k如果任何一个操作数(或者两个操作树)中的对应位是1,则按位或运算符将结果中的位设置为1。如果仅仅一个操作数中的对应位是1,则按位异或将结果中的位设置为lo左移运算符将左边操作数的各位向左移动右边的操作数所指定的位数。右移运算符将左边操作数的各位向右移动右边操作数所指定的位数。位求反运算符在结果中将操作数中所有0设置为1,将所有1设置为0。下面的示例中详细讨论了每个位运算符。图10.6中总结了位运算符。运算符说明&按位与1按位或“按位异或«左移位»右移位如果两个操作数中对应的位都是1,则结果中的位设置为1如果两个操作数中至少一个操作数的对应位是1,则结果中的位设黄为1如果两个操作数中仅仅有•个操作数的对应位是1,则结果的位设置为1将第1个操作数的各位向左移动第2个操作数所指定的位数;在右边用0位来填充将第1个操作数的各位向右移动第2个操作数所指定的位数:填充左边的方法依赖于计算机~位求反所有0位都设置为1,所有1位都设置为0当使用位运算符时,用二进制形式来打印数值以说明这些运算符的准确作用是有帮助的。图10.7的程序用每组8位的二进制形式打印了unsigned整数。/*Fig.10.7:figl0_07.cPrintinganunsignedintegerinbits*/#include<stdio.h>voiddisplayBits(unsigned);intmain()1unsignedx;printf("Enteranunsignedinteger:");scanf( &x );displayBits( x);return0;}voiddisplayBits(unsignedvalue){unsignedc,displayMask=1<<31;printf(n%7u=",value);for(c=1;c<=32;C++){putchar(value&displayMask?11*:'0*);value<<=1;if(c%8==0)putchar('');}putchar('\n');}Enteranunsignedinteger:6500065000=00000000000000001111110111101000图10.7按位打印unsigned整数函数displayBits(在第17行定义)使用按位与运算符来组合变量value和变量displayMasko在许多情况下,按位与运算符与掩码(mask)操作数一起使用,掩码是将特定位设置为1的整数值。掩码用于隐藏数值中的某些位,同时选择其他位。在函数displayBits中,掩码变量displayMask的值是:1«31 (10000000000000000000000000000000)displayMask中的左移运算符将值1从低位(最右边)移动到高位(最左边),并从右边开始填充0。语句:putchar(value&displayMask?11/*Fig.10.9:figl0_09.c2UsingthebitwiseAND,bitwiseinclusive/*Fig.10.9:figl0_09.c2UsingthebitwiseAND,bitwiseinclusiveOR,bitwise3exclusiveORandbitwisecomplementoperators*/4#include<stdio.h>56voiddisplayBits(unsigned);78intmain()9(10unsignednumber11number2,mask,setBits;12 numberl=65535;13 mask=1;14printf("Theresultofcombiningthefollowing\nw);15 displayBits( numberl );16 displayBits( mask );决定为变量value的当前最左边的位是应该打印1还是0。当使用&来组合value和displayMask时,因为和0进行与操作的任何位都是0,所以变量value中除了高位之外的所有位都将“隐藏”起来。如果最左边的位是1,value&displayMask得到1,将打印1,否则打印0。然后,表达式value«=1(它和value=value«l等价)将变量value向左移动1位。对于unsigned变量value中的每位都重复这些步骤。图10.8总结了用按位与运算符来组合两位的结果。位1位2位1&位2000100010图10.8用按位与运算符&来组合两位的结果常见的编程错误10.12 ,$ 用逻辑与运算符(&&)代替按位与运算符(&),或反过来。图109说明了按位与运算符、按位或运算符、按位异或运算符和位求反运算符的用法。该程序使用函数displayBits来打印unsigned整数值。输出如图10.10所示。1718192021222324252627282930313233343536373839404142434445464748495051525354555657585960printf("usingthebitwiseANDoperator&is\nn);displayBits(numberl&mask);numberl=15;setBits=241;printf(w\nTheresultofcombiningthefollowing\nw);displayBits(numberl);displayBits(setBits);printf("usingthebitwiseinclusiveORoperatorIis\n");displayBits(numberlIsetBits);numberl=139;number2=199;printf(w\nTheresultofcombiningthefollowing\nw);displayBits(numberl);displayBits(number2);printf("usingthebitwiseexclusiveORoperatorAis\n");displayBits(numberlAnumber2);numberl=21845;printf(n\nTheone*scomplementof\nn);displayBits(numberl);printf(nis\n");displayBits(-numberl);return0;voiddisplayBits(unsignedvalue)unsignedc,displayMask=1<<31;printf("%7u=",value);for(c=1;c<=32;C++){putchar(value&displayMask?11':'0*);value<<=1;if(c%8==0)putchar(',);putchar(•\n*);
Theresultofcombiningthefollowing65535=000000000000000011111111111111111=00000000000000000000000000000001usingthebitwiseANDoperator&is1=00000000000000000000000000000001Theresultofcombiningthefollowing15=00000000000000000000000000001111241=00000000000000000000000011110001usingthebitwiseinclusiveORoperator|is255=00000000000000000000000011111111Theresultofcombiningthefollowing139=00000000000000000000000010001011199=00000000000000000000000011000111usingthebitwiseexclusiveORoperatorAis76=00000000000000000000000001001100Theone*scomplementof21845=00000000000000000101010101010101is4294945450=11111111111111111010101010101010图10.10图10.9程序的输出在图10.9中,在第13行中将给整数变量mask赋值1(00000000000000000000000000000001),而变量numberl将赋值65535(00000000000000001111111111111111).当在表达式number1&mask中使用按位与运算符(&)来组合mask和numberl时,结果是00000000000000000000000000000001。变量numberl中,除了低位之外的所有位都将由于和变量mask进行与操作而被“隐藏”起来。按位或运算符用于将操作数中特定的位设为1。在图10.9中,在第20行中将变量numberl赋值为15(00000000000000000000000000001111),而在第21行中将变量setBits赋值为241(000000000000000000000000HllOOODo当在表达式numberlIsetBits中使用按位或运算符来组合numberl和setBits时,结果是255(00000000000000000000000011111111)。图10.11总结了用按位或运算符来组合两位的结果。位1位2位1|位2000101011111।我常见的编程错误10.13 用逻辑或运算符(II)来代替按位或运算符(I),或反过来。如果两个操作数中仅仅一个操作数的对应位是1,则按位异或运算符S)将结果中的位设置为1。在图10.9中,分别给变量number1和number2赋值为139(00000000000000000000000010001011)和199(000000000(X)000000000000011000111).当在表达式numberlAnumber2中用按位异或运算符来组合这些变量时,结果是00000000000000000000000001001100o图10.12总结了用按位异或运算符组合两位的结果。位1位2位1人位200010I011I10图10.12用按位异或运算符人来组合两位的结果位求反运算符(〜)将操作数中所有1位设置为0,将所有0位设置为1,所以称为“求反”。在图10.9中,在第36行中将变量numberl设置为值21485(00000000000000000101010101010101)»当计算表达式〜numberl时,结果是(00000000000000001010101010101010)»图10.13的程序演示了左移运算符(«)和右移运算符(>>)的用法。函数displayBits用于打印unsigned整数值。/*Fig.10.13:figl0_13.cUsingthebitwiseshiftoperators*/#include<stdio.h>voiddisplayBits(unsigned);intmain()(unsignednumberl=960;printf(n\nTheresultofleftshifting\nn);displayBits(numberl);printf(w8bitpositionsusingthe");printf("leftshiftoperator<<is\n");displayBits(numberl<<8);printf(n\nTheresultofrightshifting\n");displayBits(numberl);printf("8bitpositionsusingthe");20printf("rightshiftoperator>>is\n");21 displayBits(numberl>>8);22 return0;23}2425voiddisplayBits(unsignedvalue)26{27unsignedc,displayMask=1<<31;2829printf(n%7u=",value);3031for(c=1;c<=32;C++){32putchar(value&displayMask?*1':'01 ;33value<<=1;3435 if(c%8==0)36 putchar('1);37 }3839putchar('\n1);40)Theresultofleftshifting960=000000000000000000000011110000008bitpositionsusingtheleftshiftoperator<<is245760=00000000000000111100000000000000Theresultofleftshifting960=000000000000000000000011110000008bitpositionsusingtherightshiftoperator>>is3=00000000000000000000000000000011图10.13使用移位运算符左移运算符(«)将它左边的操作数的位向左移动右边操作数指定的位数。右边的空缺位用0代替,向左移动到左边界之外的1将丢失。在图10.13的程序中,在第9行将变量numberl赋值为960(00000000000000000000001111000000),第15行表达式numberl«8中左移变量number18位的结果是49152(00000000000000001100000000000000),右移运算符(»)将左边操作数的各位向右移动右边操作数指定的位数。对unsigned整数执行右移将使得左边的空位用0代替,移动到右边边界之外的1将丢失。在图10.13的程序中,第21行中表达式numberl>>8右移numberl的结果是3(0000000000000011)o常见的编程错误10.14 LizJ如果右边的操作数是负值,或者,如果右边的操作数大于存储左边操作数的位数,则移位结果不确定。SI可移植性提示8.3 5右移与计算机有关。在某些计算机上右移有符号整数,将用o填充空位,在另外的计算机上将使用1来填充空位。每个位运算符(求反运算符除外)都有对应的赋值运算符。这些位赋值运算符如图10.14所示,使用方式和第3章中介绍的算术赋值运算符类似。位赋值运算符 逊 &=1=&=1=A_«=»=图10.14位赋值运算符图10.15说明了迄今为止介绍的不同运算符的优先级和结合性。它们是按照优先级从高到低的顺序列出的。运算符结合性类型0[].->从左向右最高+-++—!&**sizeof(类型)从右向左一元*/%从左向右乘法+-从左向右加法«»从左向右移位<<=>>=从左向右关系==!=从左向右相等&从左向右按位与A从左向右按位异或1从左向右按位或&&从左向右逻辑与II从左向右逻辑或?:从右向左条件=+=—=/=&=|=A=«=»=%=从右向左赋值从左向右逗号10.10位字段C提供了指定存储结构或者联合的unsigned或者int成员位数的功能,这些位称为位字段。位字段通过在所需要的最小位数内存储数据而更好地利用内存。位字段成员必须声明为int或者unsignedo性能提示io.3 t^j位字段有助于节省存储空间。考虑下面的结构定义:structbitCard{unsignedface:4;unsignedsuit:2;unsignedcolor:1;);该定义包含3个unsigned位字段,即face、suit和color,用于表示52张纸牌中的一张纸牌。在unsigned或者int成员名称的后面加入冒号(:)和表示字段宽度的整数常量(也就是存储成员的位数),就可以声明位字段。表示宽度的常量必须是0和系统上存储int的总位数之间的一个整数。我们的示例是在使用4字节(32位)整数的计算机上测试的。前面的结构定义说明,成员face存储在4位中,而成员suit存储在2位中,成员color存储在1位中。位的个数取决于每个结构成员的预期值范围。成员face存储0(A)和12(K)之间的值,也就是4位可以存储0到15之间的值。成员suit存储0到3之间的值(0代表方块,1代表红心,2代表梅花,3代表黑桃),两位就可以存储0到3之间的值。最后,成员color存储0(红色)或者1(黑色),1位可以存储。或1。图10.16(见图10.17中的输出)的第19行创建了包含52个structbitCard结构的数组deck。函数fillDeck(第27行定义)在加ck数组中插入52张纸牌,而函数deal(在第41行定义)打印52张纸牌。注意,可以完全像任何其他结构成员那样访问结构的位字段成员。成员color是在允许彩色显示的系统上说明纸牌颜色的方法。/*Fig.10.16:figl0_16.cExampleusingabitfield*/#include<stdio.h>structbitCard{unsigned face:4;unsigned suit:2;unsigned color:1;};121314151617181920212223242526272829303132333435363738394041424344454647484950515253const);*const);*const);voidfillDeck(Card*voiddeal(constCardintmain()(Carddeck[52];fillDeck(deck);deal(deck);return0;voidfillDeck(Card*constwDeck)(inti;for(i=0;i<=51;i++){wDeck[i].face=i%13;wDeck[i].suit=i/13;wDeck[i].color=i/26;/*FunctiondealprintsthecardsintwocolumnformatColumn1containscards0-25subscriptedwithklColumn2containscards26-51subscriptedwithk2*/voiddeal(constCard*constwDeck){intkl,k2;for(kl=0,k2=kl+26;kl<=25;kl++,k2++){printf("Card:%3dwDeck[klwDeck[klprintf("Card:%3dwDeck[klwDeck[klprintf("Card:%3dwDeck[k2wDeck[k2].face,wDeck[kl].suit,],color);Suit:%2dColor:%2d\nn,].face,wDeck[k2].suit,].color);Card:0Suit:0Color:0Card:0Suit:2Color:1Card:1Suit:0Color:0Card:1Suit:2Color:1Card:2Suit:0Color:0Card:2Suit:2Color:1Card:3Suit:0Color:0Card:3Suit:2Color:1Card:4Suit:0Color:0Card:4Suit:2Color:1Card:5Suit:0Color:0Card:5Suit:2Color:1Card:6Suit:0Color:0Card:6Suit:2Color:1Card:7Suit:0Color:0Card:7Suit:2Color:1Card:8Suit:0Color:0Card:8Suit:2Color:1Card:9Suit:0Color:0Card:9Suit:2Color:1Card:10Suit:0Color:0Card:10Suit:2Color:1Card:11Suit:0Color:0Card:11Suit:2Color:1Card:12Suit:0Color:0Card:12Suit:2Color:1Card:0Suit:1Color:0Card:0Suit:3Color:1Card:1Suit:1Color:0Card:1Suit:3Color:1Card:2Suit:1Color:0Card:2Suit:3Color:1Card:3Suit:1Color:0Card:3Suit:3Color:1Card:4Suit:1Color:0Card:4Suit:3Color:1Card:5Suit:1Color:0Card:5Suit:3Color:1Card:6Suit:1Color:0Card:6Suit:3Color:1Card:7Suit:1Color:0Card:7Suit:3Color:1Card:8Suit:1Color:0Card:8Suit:3Color:1Card:9Suit:1Color:0Card:9Suit:3Color:1Card:10Suit:1Color:0Card:10Suit:3Color:1Card:11Suit:1Color:0Card:11Suit:3Color:1Card:12Suit:1Color:0Card:12Suit:3Color:1图10.17图10.16中程序的输出可以指定无名位字段,其中的字段可以用作结构中的填充内容。例如,结构定义:structexample{unsigneda:13;unsigned:19;unsignedb:4;};使用无名的19位字段作为填充内容,在这19位中不能存储任何东西。成员b(在我们的4节字计算机上)存储在另一个存储单元中。宽度为0的无名位字段用于在新的存储单元边界上对齐下一个位字段。例如,结构定义:structexample{unsigneda:13;unsigned:0;unsignedb:4;};使用无名称0位字段来跳过存储a的存储单元中的剩余位(无论有多少),而且将b和下一个存储单元边界对齐。®可移植性提示10.8 位字段处理和计算机有关。例如,一些计算机允许位字段跨越字边界,而其他则不允许。7^3常见的编程错误10.15 ,■尝试像数组元素那样访问位字段内的单个位。位字段并不是“位的数组”。常见的编程错误10.16 [±TJ尝试获得位字段的地址(因为没有地址,所以&运算符不能和位字段一起使用).IF尽管位字段可以节省空间,但使用它们可能让编译程序产生执行速度较慢的机器语言代码.因为机器语言需要额外的步骤来访问可寻址存储单元中的部分,就会出现这种情况.这是计算机科学中在空间与时间上进行折衷的一种情形。10.11枚举常量C提供的最后一个用户定义类型称为枚举。由关键字enum引入的枚举是用标识符表示的一组整数常量。实际上,这些枚举常量是可以自动设置其值的符号常量。enum中的值从0开始,将每次增加1(除非特别指定)。例如,枚举:enummonths{JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG.SEP,OCT,NOV,DEC)s创建了一个新类型enummonths,其中标识符设置为整数0至U11。为记录月份1至I12,使用下列枚举:enummonths{JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC);既然前面枚举中的第1个值显式设置为1,其余值将从1开始增加,产生值1到12。枚举中的标识符必须是惟一的。通过对标识符赋值,可以明确地在定义中设置枚举的每个枚举常量值。枚举的多个成员可以具有相同的常量值。在图10.18的程序中,for结构中用枚举变量month来打印来自数组monthName的月份。注意,在该程序中,monthName[0]是空字符串”1一些程序员可能选择将monthName[0]设置为类似***ERROR***这样的值,以指出出现逻辑错误。/*Fig.10.18:figl0_18.cUsinganenumerationtype*/#include<stdio.h>enummonths{JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};intmain()(enummonthsmonth;constchar*monthName[]={"January","February","March"r“April",“May”,“June","July","August","September","October","November","December");for(month=JAN;month<=DEC;month++)printf(M%2d%lls\nM,month,monthName[month]);return0;}JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember图10.18使用枚举7-^1 常见的编程错误10.17 产 在定义之后将值赋给枚举常量是语法错误。r^\ 良好的编程习惯io.6 [却在枚举常量的名称中仅使用大写字母.这使得这些常量在程序中很突出,而且提醒程序员注意枚举常量不是变量。自测练习填空:是相关变量在一个名称下的集合。是变量在一个名称下的集合,而且变量共享相同的存储空间。c)如果每个操作数中对应位是1,则使用运算符的表达式结果中的位将设置为1。否则,位设置为0。d)在结构定义中声明的变量称为它的e)如果每个操作数中任意一个操作数的对应位是1,则使用运算符的表达式结果中的位将设置为1。否则,位设置为0。f)关键字引入了结构定义。g)关键字用于为以前定义的数据类型创建同义词。h)如果两个操作数中只有一个操作数的对应位是1,则使用运算符的表达式的结果中的位是1。否则,位设置为0。i)按位与运算符&通常用于 位,也就是从位字符串中选择某些位,而让其他位为0。j)关键字 用于引入联合定义。k)结构的名称称为结构.1)用运算符或者运算符来访问结构成员。m)和运算符分别用于向左或者向右移动数值位。n) 是用标识符表示的整数集合。判断对错。如果错误,则说明理由。a)结构仅仅能包含一种数据类型。b)可以比较两个联合(用==),以确定它们是否相等。c)结构的标记名称是可选的。d)不同结构的成员必须有惟一的名称。e)关键字typedef用于定义新的数据类型。D结构总是按引用传递给函数调用。g)不能使用运算符=和!=来比较结构。为完成下列任务编写一条或者一组语句:a)定义包含int变量partNumber和char数组partName的结构part.其中partName的值可能有25个字符长。b)定义Part作为类型structpart的同义诃。c)使用Part来声明变量a的类型是structpart,数组b[10]的类型是structpart,变量ptr是指向structpart的指针。d)从键盘读取部件编号和部件名称到变量a的单个成员中。e)将变量a的成员值赋给数组b的元素3。D将数组b的地址赋给指针变量ptr。g)通过使用变量ptr和结构指针运算符引用成员来打印数组b的元素3的成员值。10.4找错。a)假设structcard包含两个指向char的指针,分别是face和suit。而且,变量c声明为类型structcard,而变量cPtr声明为指向structcard的指针。一经将c的地址赋给变量cPtroprintf(n%s\n",*cPtr->face);b)假设structcard包含两个指向类型char的指针,分别是face和suit。而且,数组hearts[13]的类型是structcardo下面的语句应该打印数组的元素10的成员face©printf("%s\nn,hearts.face);C)unionvalues{charw;floatx;doubley;}v={1.27);d)structperson{charlastName[15];charfirstName[15];intage;)e)假设已经在d部分中定义了structperson,但进行了适当的修正。persond;f)假设将变量p声明为类型structperson,而变量c已经声明为类型structcard。P=c;自测练习答案a)结构。b)联合。c)按位与(&)。d)成员。e)按位或(I)。f)struct。g)typedef。h)按位异或(A)»i)掩码。j)unionok)标记。I)结构成员,结构指针。m)左移运算符(«),右移运算符(»)on)枚举。a)错。结构可以包含许多数据类型。b)错。因为有和结构相关的相同的对齐问题,所以不能比较联合。c)正确。d)错。不同结构的成员可以有相同的名称,但相同结构的成员必须有惟一的名称。e)错误。关键字typedef用于为以前定义的数据类型定义新名称(同义词)。f)错误。结构总是按值调用传递给函数。g)正确,原因是对齐问题。a)structpart{intpartNumber;charpartName[25];);b)typedefstructpartPart;C)Parta,b[10],*ptr;d)scanf(n%d%sn,&a,partNumber,&a,partName);e)b[3]=a;ptr=b;printf("%d%s\nM,(ptr+3)->partNumber,(ptr+3)->partName);a)错误:忽略了c*Ptr外的括号,这将导致无法按照正确的顺序计算表达式。b)错误:忽略了数组下标。表达式应该是hearts[10].face。c)错误:仅仅能用与联合的第1个成员类型相同的值来初始化联合。d)错误:需要用分号来结束结构定义。e)错误:在变量声明中忽略了关键字struct。f)错误:不能将不同结构类型的变量互相赋值。练习定义下列结构和联合:a)结构inventory包含字符数组partName[30]、整数partNumber、浮点数price.整数stock和整数reorderob)联合data包含charc、shorts,longfloatf和doubled。c)结构address包含字符数组streetAddress[25]、city[20],state[3]和zipCode[6]。d)结构student包含数组firstName[15]和lastName[15],以及来自c部分类型为structaddress的变量homeAddress=e)结构test包含字段宽度为1的16位字段。位字段的名称是字母a到p。已知下列结构定义和变量声明:structcustomer{charlastName[15];charfirstName[15];intcustomerNumber;struct{charphoneNumber[11];charaddress[50];charcity[15];charstate[3];charzipCode[6];}personal;}customerRecord,*customerPtr;customerPtr=&customerRecord;编写表达式,访问结构成员。a)结构customerRecord的成员lastName«b)customerPtr所指向的结构的成员lastNameoc)结构customerRecord的成员firstNameocustomerPtr指向结构的成员firstNameoe)结构customerRecord的成员customerNumberocustomerPtr所指向的结构的成员customerNumberog)结构customerRecord的成员personal的成员phoneNumberocustomerPti•所指向结构的成员personal的成员phoneNumberocustomerRecord的成员personal的成员addressocustomerPtr所指向结构的成员personal的成员addressok)结构customerRecord的成员personal的成员city01)customerPtr所指向结构的成员personal的成员city。m)结构customerRecord的成员personal的成员stateon)customterPtr所指向结构的成员personal的成员state(>customerRecord的成员personal的成员zipCode0customerPtr所指向结构的成员personal的成员zipCodeo修改图10.16的程序,以用图10.3所示的高性能洗牌算法来洗牌。按照图10.4的格式用2列来打印产生的纸牌。在每张纸牌前面加入它的颜色。创建联合integer,其成员是chare、shorts、inii和longb。编写一个程序,它输入类型char、short、int和long的值,并将值存储在类型unioninteger的联合变量中。每个联合变量应该作为char、short>ini和long打印。打印的值是否总是正确的?创建联合floatingpoint,其成员是floatf、doubled和longdoublex。编写一个程序,它输入类型float、double和longdouble的值,并将值存储在类型为unionfloatingpoint的联合变量中。每个联合变量应该作为float,double和longdouble来打印。打印的值是否总是正确的?编写一个程序,它将整数变量右移4位。程序应该用位格式来打印移位操作前后的整数。系统在空缺位中是加入0还是1?如果计算机使用2字节整数,修改图10.7的程序,使它可以使用2字节整数。将unsigned整数左移1位等价于将值乘以2。编写函数power2,它具有两个整数参数number和pow,并计算:number*2pow使用移位运算符来计算结果。通过整数和位来打印数值。左移运算符用于将两个字符值打包到一个unsigned整数变量中。编写一个程序,它从键盘输入两个字符,并将它们传递给函数packCharacters»为将两个字符打包到一个unsigned整数变量中,需将第1个字符赋给unsigned变量,将unsigned变量向左移动8位,然后用按位或运算符组合unsigned变量和第2个字符。程序应该用位格式输III打包到unsigned整数之前和之后的字符,以证明确实正确地将字符打包在unsigned变量中。使用右移运算符、按位与运算符和掩码,编写函数unpackCharacters,它得到练习10.13的unsigned整数,并将其分解为两个字符。为从unsigned整数分解出两个字符,组合掩码65280(00000000000000001111111100000000)和无符号整数,并将结果右移8位,将结果值赋予char变量。然后组合无符号整数和掩码255(00000000000000000000000011111111),将结果赋给另一个char变量。程序应该用位格式打印分解前后的无符号整数,然后用位格式来打印字符,确认分解正确。如果系统使用4字节整数,则改写练习10.13中的程序,以组合4个字符。如果系统使用4字节整数,则改写练习10.14中的unpackCharacters函数,以分解4个字符。通过将掩码变量中的值255左移0位、8位、16位、24位(取决于正在分解的字节),可创建分解4个字符所需要的掩码。编写一个程序,它颠倒了无符号整数值中的位顺序。程序应该从用户输入数值,并调用函数reverseBits来按照逆序打印各位。用位格式打印求逆前后的数值,以确保位的求逆过程是正确的。修改图10.7的函数,使它可以在使用2字节整数和使用4字节整数的系统之间移植(提示:使用sizeof运算符来确定特定计算机上的整数大小)。下列程序使用函数multiple来确定从键盘输入的整数是否是某个整数X的倍数。研究函数multiple,然后确定X的值。/*exl0_19.c*/#include<stdio.h>intmultiple(int);intmain(){inty;printf("Enteranintegerbetween1and32000:");scanf("%d",&y);if(multiple(y))printf("%disamultipleofX\n",y);elseprintf("%disnotamultipleofX'n”,y);return0;}intmultiple(intnum)22{23inti,mask=1,mult=1;2425for(i=1;i<=10;i++,mask<<=1)26 if((num&mask)!=0){27 mult=0;28 break;29 }3031 returnmult;32}10.20下列程序完成什么任务?/*exl0_20.c*/#include<stdio.h>intmystery(unsigned);intmain(){unsignedx;printf("Enteraninteger:n);scanf(M%un,&x);printf("Theresultis%d\nM,mystery(x));return0;}1516intmystery(unsignedbits)17{18unsignedi,mask=1<<31,total=0;1920for(i=1;i<=32;i++,bits<<=1)21 if((bits&mask)==mask)
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年 贵州水利水电职业技术学院招聘教师附答案
- 2025年养护机械市场分析现状
- 中国背投电视行业发展潜力预测及投资战略研究报告
- 焦化耐火设备项目投资可行性研究分析报告(2024-2030版)
- 2025年中国兔毛手袋行业市场发展前景及发展趋势与投资战略研究报告
- 长江存储培训课件
- 视频拍摄制作合同
- 技术服务合同
- 中国电子特种气体行业市场调查研究及投资前景预测报告
- 中国无菌皮下注射针行业市场前景预测及投资价值评估分析报告
- 大件吊装运输企业信息化建设愿景
- 2024年春江苏开放大学先进制造技术第一次过程性考核作业答案
- 2019版新人教版高中英语必修+选择性必修共7册词汇表汇总(带音标)
- FANUC数控系统连接与调试实训 课件全套 第1-8章 FANUC 0iD硬件结构与连接-主轴控制
- 公务员午休管理制度
- 历史课堂中的信息化教学设计方案
- 烟机设备修理工滤棒成型
- 大肠癌的诊治及预防措施
- 外来医疗器械清洗消毒操作流程课件
- 软件工程-机票预订系统-详细设计-报告
- 网络安全服务实施方案
评论
0/150
提交评论