重庆美图库!!
楼主: wjshw
打印 上一主题 下一主题
收起左侧

C语言完全教程

[复制链接]
21
 楼主| 发表于 2006-6-13 17:31 | 只看该作者
重庆商务网,重庆电子商务第一网!
结构指针变量<br><br>结构指针变量的说明和使用一个指针变量当用来指向一个结构变量时, 称之为结构指针变量。<br>结构指针变量中的值是所指向的结构变量的首地址。 通过结构指针即可访问该结构变量, 这与数组指针和函数指针的情况是相同的。结构指针变量说明的一般形式为:<br>struct 结构名*结构指针变量名<br>例如,在前面的例7.1中定义了stu这个结构, 如要说明一个指向stu的指针变量pstu,可写为:<br>struct stu *pstu;<br><br>  当然也可在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同,结构指针变量也必须要先赋值后才能使用。赋值是把结构变量的首地址赋予该指针变量, 不能把结构名赋予该指针变量。如果boy是被说明为stu类型的结构变量,则: pstu=&amp;boy是正确的,而: pstu=&amp;stu是错误的。<br><br>  结构名和结构变量是两个不同的概念,不能混淆。 结构名只能表示一个结构形式,编译系统并不对它分配内存空间。 只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间。 因此上面&amp;stu这种写法是错误的,不可能去取一个结构名的首地址。 有了结构指针变量,就能更方便地访问结构变量的各个成员。<br><br>其访问的一般形式为: (*结构指针变量).成员名 或为:<br>结构指针变量-&gt;成员名<br>例如: (*pstu).num或者: pstu-&gt;num<br>应该注意(*pstu)两侧的括号不可少, 因为成员符“.”的优先级高于“*”。如去掉括号写作*pstu.num则等效于*(pstu.num),这样,意义就完全不对了。 下面通过例子来说明结构指针变量的具体说明和使用方法。<br>[例7.6]<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>} boy1={102,"Zhang ping",'M',78.5},*pstu;<br>main()<br>{<br>pstu=&amp;boy1;<br>printf("Number=%d\nName=%s\n",boy1.num,boy1.name);<br>printf("Sex=%c\nScore=%f\n\n",boy1.sex,boy1.score);<br>printf("Number=%d\nName=%s\n",(*pstu).num,(*pstu).name);<br>printf("Sex=%c\nScore=%f\n\n",(*pstu).sex,(*pstu).score);<br>printf("Number=%d\nName=%s\n",pstu-&gt;num,pstu-&gt;name);<br>printf("Sex=%c\nScore=%f\n\n",pstu-&gt;sex,pstu-&gt;score);<br>}<br><br>  本例程序定义了一个结构stu,定义了stu类型结构变量boy1 并作了初始化赋值,还定义了一个指向stu类型结构的指针变量pstu。在main函数中,pstu被赋予boy1的地址,因此pstu指向boy1 。然后在printf语句内用三种形式输出boy1的各个成员值。 从运行结果可以看出:<br>结构变量.成员名<br>(*结构指针变量).成员名<br>结构指针变量-&gt;成员名<br><br>  这三种用于表示结构成员的形式是完全等效的。结构数组指针变量结构指针变量可以指向一个结构数组, 这时结构指针变量的值是整个结构数组的首地址。 结构指针变量也可指向结构数组的一个元素,这时结构指针变量的值是该结构数组元素的首地址。设ps为指向结构数组的指针变量,则ps也指向该结构数组的0号元素,ps+1指向1号元素,ps+i则指向i号元素。 这与普通数组的情况是一致的。<br>[例7.7]用指针变量输出结构数组。<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy[5]={<br>{101,"Zhou ping",'M',45},<br>{102,"Zhang ping",'M',62.5},<br>{103,"Liou fang",'F',92.5},<br>{104,"Cheng ling",'F',87},<br>{105,"Wang ming",'M',58},<br>};<br>main()<br>{<br>struct stu *ps;<br>printf("No\tName\t\t\tSex\tScore\t\n");<br>for(ps=boy;ps&lt;boy+5;ps++)<br>printf("%d\t%s\t\t%c\t%f\t\n",ps-&gt;num,ps-&gt;name,ps-&gt;sex,ps-&gt;<br>score);<br>}<br>   在程序中,定义了stu结构类型的外部数组boy 并作了初始化赋值。在main函数内定义ps为指向stu类型的指针。在循环语句for的表达式1中,ps被赋予boy的首地址,然后循环5次,输出boy数组中各成员值。 应该注意的是, 一个结构指针变量虽然可以用来访问结构变量或结构数组元素的成员,但是,不能使它指向一个成员。 也就是说不允许取一个成员的地址来赋予它。因此,下面的赋值是错误的。 ps=&amp;boy[1].sex;而只能是:ps=boy;(赋予数组首地址)<br>或者是:<br>ps=&amp;boy[0];(赋予0号元素首地址)<br><br>结构指针变量作函数参数<br><br>  在ANSI C标准中允许用结构变量作函数参数进行整体传送。 但是这种传送要将全部成员逐个传送, 特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。 因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。 这时由实参传向形参的只是地址,从而减少了时间和空间的开销。<br>[例7.8]题目与例7.4相同,计算一组学生的平均成绩和不及格人数。<br>用结构指针变量作函数参数编程。<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;}boy[5]={<br>{101,"Li ping",'M',45},<br>{102,"Zhang ping",'M',62.5},<br>{103,"He fang",'F',92.5},<br>{104,"Cheng ling",'F',87},<br>{105,"Wang ming",'M',58},<br>};<br>main()<br>{<br>struct stu *ps;<br>void ave(struct stu *ps);<br>ps=boy;<br>ave(ps);<br>}<br>void ave(struct stu *ps)<br>{<br>int c=0,i;<br>float ave,s=0;<br>for(i=0;i&lt;5;i++,ps++)<br>{<br>s+=ps-&gt;score;<br>if(ps-&gt;score&lt;60) c+=1;<br>}<br>printf("s=%f\n",s);<br>ave=s/5;<br>printf("average=%f\ncount=%d\n",ave,c);<br>}<br>   本程序中定义了函数ave,其形参为结构指针变量ps。boy 被定义为外部结构数组,因此在整个源程序中有效。在main 函数中定义说明了结构指针变量ps,并把boy的首地址赋予它,使ps指向boy 数组。然后以ps作实参调用函数ave。在函数ave 中完成计算平均成绩和统计不及格人数的工作并输出结果。与例7.4程序相比,由于本程序全部采用指针变量作运算和处理,故速度更快,程序效率更高。.<br><br>topoic=动态存储分配<br><br>  在数组一章中,曾介绍过数组的长度是预先定义好的, 在整个程序中固定不变。C语言中不允许动态数组类型。例如: int n;scanf("%d",&amp;n);int a[n]; 用变量表示长度,想对数组的大小作动态说明, 这是错误的。但是在实际的编程中,往往会发生这种情况, 即所需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题, 用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间, 也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。 常用的内存管理函数有以下三个:<br><br>1.分配内存空间函数malloc<br>调用形式: (类型说明符*) malloc (size) 功能:在内存的动态存储区中分配一块长度为"size" 字节的连续区域。函数的返回值为该区域的首地址。 “类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。例如: pc=(char *) malloc (100); 表示分配100个字节的内存空间,并强制转换为字符数组类型, 函数的返回值为指向该字符数组的指针, 把该指针赋予指针变量pc。<br><br>2.分配内存空间函数 calloc<br>calloc 也用于分配内存空间。调用形式: (类型说明符*)calloc(n,size) 功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。例如: ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。<br><br>3.释放内存空间函数free<br>调用形式: free(void*ptr); 功能:释放ptr所指向的一块内存空间,ptr 是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域:[例7.9]分配一块区域,输入一个********。<br>main()<br>{<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>} *ps;<br>ps=(struct stu*)malloc(sizeof(struct stu));<br>ps-&gt;num=102;<br>ps-&gt;name="Zhang ping";<br>ps-&gt;sex='M';<br>ps-&gt;score=62.5;<br>printf("Number=%d\nName=%s\n",ps-&gt;num,ps-&gt;name);<br>printf("Sex=%c\nScore=%f\n",ps-&gt;sex,ps-&gt;score);<br>free(ps);<br>}<br>   本例中,定义了结构stu,定义了stu类型指针变量ps。 然后分配一块stu大内存区,并把首地址赋予ps,使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值,并用printf 输出各成员值。最后用free函数释放ps指向的内存空间。 整个程序包含了申请内存空间、使用内存空间、释放内存空间三个步骤, 实现存储空间的动态分配。链表的概念在例7.9中采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据, 我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间, 也就是说要建立多少个结点。当然用结构数组也可以完成上述工作, 但如果预先不能准确把握学生人数,也就无法确定数组大小。 而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。 用动态存储的方法可以很好地解决这些问题。 有一个学生就分配一个结点,无须预先确定学生的准确人数,某学生退学, 可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。 另一方面,用数组的方法必须占用一块连续的内存区域。 而使用动态分配时,每个结点之间可以是不连续的(结点内是连续的)。 结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。可在第一个结点的指针域内存入第二个结点的首地址, 在第二个结点的指针域内又存放第三个结点的首地址, 如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。图7.3为链表的示意图。<br><br>  在图7.3中,第0个结点称为头结点, 它存放有第一个结点的首地址,它没有数据,只是一个指针变量。 以下的每个结点都分为两个域,一个是数据域,存放各种实际的数据,如学号num,姓名name,性别sex和成绩score等。另一个域为指针域, 存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。例如, 一个存放学生学号和成绩的结点应为以下结构:<br>struct stu<br>{ int num;<br>int score;<br>struct stu *next;<br>}<br>   前两个成员项组成数据域,后一个成员项next构成指针域, 它是一个指向stu类型结构的指针变量。链表的基本操作对链表的主要操作有以下几种:<br>1.建立链表;<br>2.结构的查找与输出;<br>3.插入一个结点;<br>4.删除一个结点;<br>下面通过例题来说明这些操作。<br>[例7.10]建立一个三个结点的链表,存放********。 为简单起见, 我们假定********结构中只有学号和年龄两项。<br>可编写一个建立链表的函数creat。程序如下:<br>#define NULL 0<br>#define TYPE struct stu<br>#define LEN sizeof (struct stu)<br>struct stu<br>{<br>int num;<br>int age;<br>struct stu *next;<br>};<br>TYPE *creat(int n)<br>{<br>struct stu *head,*pf,*pb;<br>int i;<br>for(i=0;i&lt;n;i++)<br>{<br>pb=(TYPE*) malloc(LEN);<br>printf("input Number and Age\n");<br>scanf("%d%d",&amp;pb-&gt;num,&amp;pb-&gt;age);<br>if(i==0)<br>pf=head=pb;<br>else pf-&gt;next=pb;<br>pb-&gt;next=NULL;<br>pf=pb;<br>}<br>return(head);<br>}<br>   在函数外首先用宏定义对三个符号常量作了定义。这里用TYPE表示struct stu,用LEN表示sizeof(struct stu)主要的目的是为了在以下程序内减少书写并使阅读更加方便。结构stu定义为外部类型,程序中的各个函数均可使用该定义。<br><br>  creat函数用于建立一个有n个结点的链表,它是一个指针函数,它返回的指针指向stu结构。在creat函数内定义了三个stu结构的指针变量。head为头指针,pf 为指向两相邻结点的前一结点的指针变量。pb为后一结点的指针变量。在for语句内,用malloc函数建立长度与stu长度相等的空间作为一结点,首地址赋予pb。然后输入结点数据。如果当前结点为第一结点(i==0),则把pb值 (该结点指针)赋予head和pf。如非第一结点,则把pb值赋予pf 所指结点的指针域成员next。而pb所指结点为当前的最后结点,其指针域赋NULL。 再把pb值赋予pf以作下一次循环准备。<br>   creat函数的形参n,表示所建链表的结点数,作为for语句的循环次数。图7.4表示了creat函数的执行过程。<br><br>[例7.11]写一个函数,在链表中按学号查找该结点。<br>TYPE * search (TYPE *head,int n)<br>{<br>TYPE *p;<br>int i;<br>p=head;<br>while (p-&gt;num!=n &amp;&amp; p-&gt;next!=NULL)<br>p=p-&gt;next; /* 不是要找的结点后移一步*/<br>if (p-&gt;num==n) return (p);<br>if (p-&gt;num!=n&amp;&amp; p-&gt;next==NULL)<br>printf ("Node %d has not been found!\n",n<br>}<br>   本函数中使用的符号常量TYPE与例7.10的宏定义相同,等于struct stu。函数有两个形参,head是指向链表的指针变量,n为要查找的学号。进入while语句,逐个检查结点的num成员是否等于n,如果不等于n且指针域不等于NULL(不是最后结点)则后移一个结点,继续循环。如找到该结点则返回结点指针。 如循环结束仍未找到该结点则输出“未找到”的提示信息。<br><br>[例7.12]写一个函数,删除链表中的指定结点。删除一个结点有两种情况:<br>1. 被删除结点是第一个结点。这种情况只需使head指向第二个结点即可。即head=pb-&gt;next。其过程如图7.5所示。<br>2. 被删结点不是第一个结点,这种情况使被删结点的前一结点指向被删结点的后一结点即可。即pf-&gt;next=pb-&gt;next。其过程如图7.6所示。<br>函数编程如下:<br>TYPE * delete(TYPE * head,int num)<br>{<br>TYPE *pf,*pb;<br>if(head==NULL) /*如为空表, 输出提示信息*/<br>{ printf("\nempty list!\n");<br>goto end;}<br>pb=head;<br>while (pb-&gt;num!=num &amp;&amp; pb-&gt;next!=NULL)<br>/*当不是要删除的结点,而且也不是最后一个结点时,继续循环*/<br>{pf=pb;pb=pb-&gt;next;}/*pf指向当前结点,pb指向下一结点*/<br>if(pb-&gt;num==num)<br>{if(pb==head) head=pb-&gt;next;<br>/*如找到被删结点,且为第一结点,则使head指向第二个结点,<br>否则使pf所指结点的指针指向下一结点*/<br>else pf-&gt;next=pb-&gt;next;<br>free(pb);<br>printf("The node is deleted\n");}<br>else<br>printf("The node not been foud!\n");<br>end:<br>return head;<br>}<br>   函数有两个形参,head为指向链表第一结点的指针变量,num删结点的学号。 首先判断链表是否为空,为空则不可能有被删结点。若不为空,则使pb指针指向链表的第一个结点。进入while语句后逐个查找被删结点。找到被删结点之后再看是否为第一结点,若是则使head指向第二结点(即把第一结点从链中删去),否则使被删结点的前一结点(pf所指)指向被删结点的后一结点(被删结点的指针域所指)。如若循环结束未找到要删的结点, 则输出“末找到”的提示信息。最后返回head值。<br><br>[例7.13]写一个函数,在链表中指定位置插入一个结点。在一个链表的指定位置插入结点, 要求链表本身必须是已按某种规律排好序的。例如,在********链表中, 要求学号顺序插入一个结点。设被插结点的指针为pi。 可在三种不同情况下插入。<br>1. 原表是空表,只需使head指向被插结点即可。见图7.7(a)<br>2. 被插结点值最小,应插入第一结点之前。这种情况下使head指向被插结点,被插结点的指针域指向原来的第一结点则可。即:pi-&gt;next=pb;<br>head=pi; 见图7.7(b)<br>3. 在其它位置插入,见图7.7(c)。这种情况下,使插入位置的前一结点的指针域指向被插结点,使被插结点的指针域指向插入位置的后一结点。即为:pi-&gt;next=pb;pf-&gt;next=pi;<br>4. 在表末插入,见图7.7(d)。这种情况下使原表末结点指针域指向被插结点,被插结点指针域置为NULL。即:<br>pb-&gt;next=pi;<br>pi-&gt;next=NULL; TYPE * insert(TYPE * head,TYPE *pi)<br>{<br>TYPE *pf,*pb;<br>pb=head;<br>if(head==NULL) /*空表插入*/<br>(head=pi;<br>pi-&gt;next=NULL;}<br>else<br>{<br>while((pi-&gt;num&gt;pb-&gt;num)&amp;&amp;(pb-&gt;next!=NULL))<br>{pf=pb;<br>pb=pb-&gt;next; }/*找插入位置*/<br>if(pi-&gt;num&lt;=pb-&gt;num)<br>{if(head==pb)head=pi;/*在第一结点之前插入*/<br>else pf-&gt;next=pi;/*在其它位置插入*/<br>pi-&gt;next=pb; }<br>else<br>{pb-&gt;next=pi;<br>pi-&gt;next=NULL;} /*在表末插入*/<br>}<br>return head;}<br>   本函数有两个形参均为指针变量,head指向链表,pi 指向被插结点。函数中首先判断链表是否为空,为空则使head指向被插结点。表若不空,则用while语句循环查找插入位置。找到之后再判断是否在第一结点之前插入,若是则使head 指向被插结点被插结点指针域指向原第一结点,否则在其它位置插入, 若插入的结点大于表中所有结点,则在表末插入。本函数返回一个指针, 是链表的头指针。 当插入的位置在第一个结点之前时, 插入的新结点成为链表的第一个结点,因此head的值也有了改变, 故需要把这个指针返回主调函数。<br>[例7.14]将以上建立链表,删除结点,插入结点的函数组织在一起,再建一个输出全部结点的函数,然后用main函数调用它们。<br>#define NULL 0<br>#define TYPE struct stu<br>#define LEN sizeof(struct stu)<br>struct stu<br>{<br>int num;<br>int age;<br>struct stu *next;<br>};<br>TYPE * creat(int n)<br>{<br>struct stu *head,*pf,*pb;<br>int i;<br>for(i=0;i&lt;n;i++)<br>{<br>pb=(TYPE *)malloc(LEN);<br>printf("input Number and Age\n");<br>scanf("%d%d",&amp;pb-&gt;num,&amp;pb-&gt;age);<br>if(i==0)<br>pf=head=pb;<br>else pf-&gt;next=pb;<br>pb-&gt;next=NULL;<br>pf=pb;<br>}<br>return(head);<br>}<br>TYPE * delete(TYPE * head,int num)<br>{<br>TYPE *pf,*pb;<br>if(head==NULL)<br>{ printf("\nempty list!\n");<br>goto end;}<br>pb=head;<br>while (pb-&gt;num!=num &amp;&amp; pb-&gt;next!=NULL)<br>{pf=pb;pb=pb-&gt;next;}<br>if(pb-&gt;num==num)<br>{ if(pb==head) head=pb-&gt;next;<br>else pf-&gt;next=pb-&gt;next;<br>printf("The node is deleted\n"); }<br>else<br>free(pb);<br>printf("The node not been found!\n");<br>end:<br>return head;<br>}<br>TYPE * insert(TYPE * head,TYPE * pi)<br>{<br>TYPE *pb ,*pf;<br>pb=head;<br>if(head==NULL)<br>{ head=pi;<br>pi-&gt;next=NULL; }<br>else<br>{<br>while((pi-&gt;num&gt;pb-&gt;num)&amp;&amp;(pb-&gt;next!=NULL))<br>{ pf=pb;<br>pb=pb-&gt;next; }<br>if(pi-&gt;num&lt;=pb-&gt;num)<br>{ if(head==pb) head=pi;<br>else pf-&gt;next=pi;<br>pi-&gt;next=pb; }<br>else<br>{ pb-&gt;next=pi;<br>pi-&gt;next=NULL; }<br>}<br>return head;<br>}<br>void print(TYPE * head)<br>{<br>printf("Number\t\tAge\n");<br>while(head!=NULL)<br>{<br>printf("%d\t\t%d\n",head-&gt;num,head-&gt;age);<br>head=head-&gt;next;<br>}<br>}<br>main()<br>{<br>TYPE * head,*pnum;<br>int n,num;<br>printf("input number of node: ");<br>scanf("%d",&amp;n);<br>head=creat(n);<br>print(head);<br>printf("Input the deleted number: ");<br>scanf("%d",&amp;num);<br>head=delete(head,num);<br>print(head);<br>printf("Input the inserted number and age: ");<br>pnum=(TYPE *)malloc(LEN);<br>scanf("%d%d",&amp;pnum-&gt;num,&amp;pnum-&gt;age);<br>head=insert(head,pnum);<br>print(head);<br>}<br>
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
22
 楼主| 发表于 2006-6-13 17:32 | 只看该作者
重庆女性论坛
本例中,print函数用于输出链表中各个结点数据域值。函数的形参head的初值指向链表第一个结点。在while语句中,输出结点值后,head值被改变,指向下一结点。若保留头指针head, 则应另设一个指针变量,把head值赋予它,再用它来替代head。在main函数中,n为建立结点的数目, num为待删结点的数据域值;head为指向链表的头指针,pnum为指向待插结点的指针。 main函数中各行的意义是:<br>第六行输入所建链表的结点数;<br>第七行调creat函数建立链表并把头指针返回给head;<br>第八行调print函数输出链表;<br>第十行输入待删结点的学号;<br>第十一行调delete函数删除一个结点;<br>第十二行调print函数输出链表;<br>第十四行调malloc函数分配一个结点的内存空间, 并把其地址赋予pnum;<br>第十五行输入待插入结点的数据域值;<br>第十六行调insert函数插入pnum所指的结点;<br>第十七行再次调print函数输出链表。<br><br>  从运行结果看,首先建立起3个结点的链表,并输出其值;再删103号结点,只剩下105,108号结点;又输入106号结点数据, 插入后链表中的结点为105,106,108。联合“联合”也是一种构造类型的数据结构。 在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据。 这在前面的各种数据类型中都是办不到的。例如, 定义为整型的变量只能装入整型数据,定义为实型的变量只能赋予实型数据。<br><br>  在实际问题中有很多这样的例子。 例如在学校的教师和学生中填写以下表格: 姓 名 年 龄 职 业 单位 “职业”一项可分为“教师”和“学生”两类。 对“单位”一项学生应填入班级编号,教师应填入某系某教研室。 班级可用整型量表示,教研室只能用字符类型。 要求把这两种类型不同的数据都填入“单位”这个变量中, 就必须把“单位”定义为包含整型和字符型数组这两种类型的“联合”。<br><br>  “联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。如前面介绍的“单位”变量, 如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符串(教研室)。要么赋予整型值,要么赋予字符串,不能把两者同时赋予它。联合类型的定义和联合变量的说明一个联合类型必须经过定义之后, 才能把变量说明为该联合类型。<br><br>一、联合的定义<br><br>定义一个联合类型的一般形式为:<br>union 联合名<br>{<br>成员表<br>};<br>成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名 成员名的命名应符合标识符的规定。<br>例如:<br>union perdata<br>{<br>int class;<br>char office[10];<br>};<br>   定义了一个名为perdata的联合类型,它含有两个成员,一个为整型,成员名为class;另一个为字符数组,数组名为office。联合定义之后,即可进行联合变量说明,被说明为perdata类型的变量,可以存放整型量class或存放字符数组office。<br><br>二、联合变量的说明<br><br>  联合变量的说明和结构变量的说明方式相同, 也有三种形式。即先定义,再说明;定义同时说明和直接说明。以perdata类型为例,说明如下:<br>union perdata<br>{<br>int class;<br>char officae[10];<br>};<br>union perdata a,b; /*说明a,b为perdata类型*/<br>或者可同时说明为:<br>union perdata<br>{ int class;<br>char office[10]; }a,b;或直接说明为: union<br>{ int class;<br>char office[10]; }a,b<br>经说明后的a,b变量均为perdata类型。 它们的内存分配示意图如图7—8所示。a,b变量的长度应等于 perdata 的成员中最长的长度, 即等于<br>office数组的长度,共10个字节。从图中可见,a,b变量如赋予整型值时,只使用了2个字节,而赋予字符数组时,可用10个字节。<br><br>联合变量的赋值和使用<br><br>  对联合变量的赋值,使用都只能是对变量的成员进行。 联合变量的成员表示为: 联合变量名.成员名 例如,a被说明为perdata类型的变量之后,可使用 a.class a.office 不允许只用联合变量名作赋值或其它操作。 也不允许对联合变量作初始化赋值,赋值只能在程序中进行。还要再强调说明的是,一个联合变量, 每次只能赋予一个成员值。换句话说,一个联合变量的值就是联合变员的某一个成员值。<br>[例7.15]设有一个教师与学生通用的表格,教师数据有姓名,年龄,职业,教研室四项。学生有姓名,年龄,职业,班级四项。<br>编程输入人员数据, 再以表格输出。<br>main()<br>{<br>struct<br>{<br>char name[10];<br>int age;<br>char job;<br>union<br>{<br>int class;<br>char office[10];<br>} depa;<br>}body[2];<br>int n,i;<br>for(i=0;i&lt;2;i++)<br>{<br>printf("input name,age,job and department\n");<br>scanf("%s %d %c",body.name,&amp;body.age,&amp;body.job);<br>if(body.job=='s')<br>scanf("%d",&amp;body.depa.class);<br>else<br>scanf("%s",body.depa.office);<br>}<br>printf("name\tage job class/office\n");<br>for(i=0;i&lt;2;i++)<br>{<br>if(body.job=='s')<br>printf("%s\t%3d %3c %d\n",body.name,body.age<br>,body.job,body.depa.class);<br>else<br>printf("%s\t%3d %3c %s\n",body.name,body.age,<br>body.job,body.depa.office);<br>}<br>}<br>   本例程序用一个结构数组body来存放人员数据, 该结构共有四个成员。其中成员项depa是一个联合类型, 这个联合又由两个成员组成,一个为整型量class,一个为字符数组office。在程序的第一个for语句中,输入人员的各项数据,先输入结构的前三个成员name,age和job,然后判别job成员项,如为"s"则对联合depa·class输入(对学生赋班级编号)否则对depa·office输入(对教师赋教研组名)。<br><br>  在用scanf语句输入时要注意,凡为数组类型的成员,无论是结构成员还是联合成员,在该项前不能再加"&amp;"运算符。如程序第18行中<br>body.name是一个数组类型,第22行中的body.depa.office也是数组类型,因此在这两项之间不能加"&amp;"运算符。程序中的第二个for语句用于输出各成员项的值:<br><br>本章小结<br><br>1. 结构和联合是两种构造类型数据,是用户定义新数据类型的重要手段。结构和联合有很多的相似之处,它们都由成员组成。成员可以具有不同的数据类型。成员的表示方法相同。都可用三种方式作变量说明。 2. 在结构中,各成员都占有自己的内存空间,它们是同时存在的。一个结构变量的总长度等于所有成员长度之和。在联合中,所有成员不能同时占用它的内存空间,它们不能同时存在。联合变量的长度等于最长的成员的长度。<br><br>3. “.”是成员运算符,可用它表示成员项,成员还可用“-&gt;”运算符来表示。<br><br>4. 结构变量可以作为函数参数,函数也可返回指向结构的指针变量。而联合变量不能作为函数参数,函数也不能返回指向联合的指针变量。但可以使用指向联合变量的指针,也可使用联合数组。 5. 结构定义允许嵌套,结构中也可用联合作为成员,形成结构和联合的嵌套。<br><br>6. 链表是一种重要的数据结构,它便于实现动态的存储分配。本章介绍是单向链表,还可组成双向链表,循环链表等。 <br><br>
重庆商务网,重庆电子商务第一网,欢迎入驻!
23
 楼主| 发表于 2006-6-13 17:32 | 只看该作者
说普通话,从我做起,全面提升重庆文明程度
<FONT size=2><FONT face=宋体><FONT color=red>第十章 枚举,位运算</FONT><br><br>枚举<br><br>  在实际问题中, 有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月, 一个班每周有六门课程等等。如果把这些量说明为整型, 字符型或其它类型显然是不妥当的。 为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值, 被说明为该“枚举”类型的变量取值不能超过定义的范围。应该说明的是, 枚举类型是一种基本数据类型,而不是一种构造类型, 因为它不能再分解为任何基本类型。<br><br>枚举类型的定义和枚举变量的说明<br><br>一、枚举的定义枚举类型定义的一般形式为:<br>enum 枚举名<br>{ 枚举值表 };<br>在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。<br>例如: enum weekday<br>{ sun,mou,tue,wed,thu,fri,sat };<br>该枚举名为weekday,枚举值共有7个,即一周中的七天。 凡被说明为weekday类型变量的取值只能是七天中的某一天。<br><br>二、枚举变量的说明 如同结构和联合一样,枚举变量也可用不同的方式说明, 即先定义后说明,同时定义说明或直接说明。设有变量a,b,c被说明为上述的weekday,可采用下述任一种方式:<br>enum weekday<br>{<br>......<br>};<br>enum weekday a,b,c;或者为: enum weekday<br>{<br>......<br>}a,b,c;或者为: enum<br>{<br>......<br>}a,b,c;<br><br>枚举类型变量的赋值和使用<br><br>枚举类型在使用中有以下规定:<br>1. 枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。例如对枚举weekday的元素再作以下赋值: sun=5;mon=2;sun=mon; 都是错误的。<br><br>2. 枚举元素本身由系统定义了一个表示序号的数值,从0 开始顺序定义为0,1,2…。如在weekday中,sun值为0,mon值为1, …,sat值为6。<br>main(){<br>enum weekday<br>{ sun,mon,tue,wed,thu,fri,sat } a,b,c;<br>a=sun;<br>b=mon;<br>c=tue;<br>printf("%d,%d,%d",a,b,c);<br>}<br>3. 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如: a=sum;b=mon; 是正确的。而: a=0;b=1; 是错误的。如一定要把数值赋予枚举变量,则必须用强制类型转换,如: a=(enum weekday)2;其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于: a=tue; 还应该说明的是枚举元素不是字符常量也不是字符串常量, 使用时不要加单、双引号。<br>main(){<br>enum body<br>{ a,b,c,d } month[31],j;<br>int i;<br>j=a;<br>for(i=1;i&lt;=30;i++){<br>month</FONT></FONT><FONT face=宋体 size=2>=j;<br>j++;<br>if (j&gt;d) j=a;<br>}<br>for(i=1;i&lt;=30;i++){<br>switch(month</FONT><FONT face=宋体 size=2>)<br>{<br>case a:printf(" %2d %c\t",i,'a'); break;<br>case b:printf(" %2d %c\t",i,'b'); break;<br>case c:printf(" %2d %c\t",i,'c'); break;<br>case d:printf(" %2d %c\t",i,'d'); break;<br>default:break;<br>}<br>}<br>printf("\n");<br>}<br><br>位运算<br><br>  前面介绍的各种运算都是以字节作为最基本位进行的。 但在很多系统程序中常要求在位(bit)一级进行运算或处理。C语言提供了位运算的功能, 这使得C语言也能像汇编语言一样用来编写系统程序。<br>一、位运算符C语言提供了六种位运算符:<br>&amp; 按位与<br>| 按位或<br>^ 按位异或<br>~ 取反<br>&lt;&lt; 左移<br>&gt;&gt; 右移<br><br>1. 按位与运算 按位与运算符"&amp;"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1 ,否则为0。参与运算的数以补码方式出现。<br>例如:9&amp;5可写算式如下: 00001001 (9的二进制补码)&amp;00000101 (5的二进制补码) 00000001 (1的二进制补码)可见9&amp;5=1。<br><br>  按位与运算通常用来对某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&amp;255 运算 ( 255 的二进制数为0000000011111111)。<br>main(){<br>int a=9,b=5,c;<br>c=a&amp;b;<br>printf("a=%d\nb=%d\nc=%d\n",a,b,c);<br>}<br><br>2. 按位或运算 按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。<br>例如:9|5可写算式如下: 00001001|00000101<br>00001101 (十进制为13)可见9|5=13<br>main(){<br>int a=9,b=5,c;<br>c=a|b;<br>printf("a=%d\nb=%d\nc=%d\n",a,b,c);<br>}<br><br>3. 按位异或运算 按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现,例如9^5可写成算式如下: 00001001^00000101 00001100 (十进制为12)<br>main(){<br>int a=9;<br>a=a^15;<br>printf("a=%d\n",a);<br>}<br><br>4. 求反运算 求反运算符~为单目运算符,具有右结合性。 其功能是对参与运算的数的各二进位按位求反。例如~9的运算为: ~(0000000000001001)结果为:1111111111110110<br><br>5. 左移运算 左移运算符“&lt;&lt;”是双目运算符。其功能把“&lt;&lt; ”左边的运算数的各二进位全部左移若干位,由“&lt;&lt;”右边的数指定移动的位数,<br>高位丢弃,低位补0。例如: a&lt;&lt;4 指把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进制48)。6. 右移运算 右移运算符“&gt;&gt;”是双目运算符。其功能是把“&gt;&gt; ”左边的运算数的各二进位全部右移若干位,“&gt;&gt;”右边的数指定移动的位数。<br>例如:设 a=15,a&gt;&gt;2 表示把000001111右移为00000011(十进制3)。 应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时, 最高位补0,而为负数时,符号位为1,最高位是补0或是补1 取决于编译系统的规定。Turbo C和很多系统规定为补1。<br>main(){<br>unsigned a,b;<br>printf("input a number: ");<br>scanf("%d",&amp;a);<br>b=a&gt;&gt;5;<br>b=b&amp;15;<br>printf("a=%d\tb=%d\n",a,b);<br>}<br>请再看一例!<br>main(){<br>char a='a',b='b';<br>int p,c,d;<br>p=a;<br>p=(p&lt;&lt;8)|b;<br>d=p&amp;0xff;<br>c=(p&amp;0xff00)&gt;&gt;8;<br>printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d);<br>}<br><br>位域<br><br>有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:<br>struct 位域结构名<br>{ 位域列表 };<br>其中位域列表的形式为: 类型说明符 位域名:位域长度<br>例如:<br>struct bs<br>{<br>int a:8;<br>int b:2;<br>int c:6;<br>};<br>位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:<br>struct bs<br>{<br>int a:8;<br>int b:2;<br>int c:6;<br>}data;<br>说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:<br><br>1. 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:<br>struct bs<br>{<br>unsigned a:4<br>unsigned :0 /*空域*/<br>unsigned b:4 /*从下一单元开始存放*/<br>unsigned c:4<br>}<br>在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。<br><br>2. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。<br><br>3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:<br>struct k<br>{<br>int a:1<br>int :2 /*该2位不能使用*/<br>int b:3<br>int c:2<br>};<br>从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。<br><br>二、位域的使用位域的使用和结构成员的使用相同,其一般形式为: 位域变量名·位域名 位域允许用各种格式输出。<br>main(){<br>struct bs<br>{<br>unsigned a:1;<br>unsigned b:3;<br>unsigned c:4;<br>} bit,*pbit;<br>bit.a=1;<br>bit.b=7;<br>bit.c=15;<br>printf("%d,%d,%d\n",bit.a,bit.b,bit.c);<br>pbit=&amp;bit;<br>pbit-&gt;a=0;<br>pbit-&gt;b&amp;=3;<br>pbit-&gt;c|=1;<br>printf("%d,%d,%d\n",pbit-&gt;a,pbit-&gt;b,pbit-&gt;c);<br>}<br>上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。<br>程序的9、10、11三行分别给三个位域赋值。( 应注意赋值不能超过该位域的允许范围)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针方式给位域a重新赋值,赋为0。第15行使用了复合的位运算符"&amp;=", 该行相当于: pbit-&gt;b=pbit-&gt;b&amp;3位域b中原有值为7,与3作按位与运算的结果为3(111&amp;011=011,十进制值为3)。同样,程序第16行中使用了复合位运算"|=", 相当于: pbit-&gt;c=pbit-&gt;c|1其结果为15。程序第17行用指针方式输出了这三个域的值。<br><br>类型定义符typedef<br><br>C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。 类型定义符typedef即可用来完成此功能。例如,有整型量a,b,其说明如下: int aa,b; 其中int是整型变量的类型说明符。int的完整写法为integer,<br>为了增加程序的可读性,可把整型说明符用typedef定义为: typedef int INTEGER 这以后就可用INTEGER来代替int作整型变量的类型说明了。 例如: INTEGER a,b;它等效于: int a,b; 用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。例如:<br>typedef char NAME[20]; 表示NAME是字符数组类型,数组长度为20。<br>然后可用NAME 说明变量,如: NAME a1,a2,s1,s2;完全等效于: char a1[20],a2[20],s1[20],s2[20]<br>又如:<br>typedef struct stu{ char name[20];<br>int age;<br>char sex;<br>} STU;<br>定义STU表示stu的结构类型,然后可用STU来说明结构变量: STU body1,body2;<br>typedef定义的一般形式为: typedef 原类型名 新类型名 其中原类型名中含有定义部分,新类型名一般用大写表示, 以<br>便于区别。在有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。<br><br>本章小结<br><br>1. 枚举是一种基本数据类型。枚举变量的取值是有限的,枚举元素是常量,不是变量。<br><br>2. 枚举变量通常由赋值语句赋值,而不由动态输入赋值。枚举元素虽可由系统或用户定义一个顺序值,但枚举元素和整数并不相同,它们属于不同的类型。因此,也不能用printf语句来输出元素值(可输出顺序值)。<br><br>3. 位运算是C语言的一种特殊运算功能, 它是以二进制位为单位进行运算的。位运算符只有逻辑运算和移位运算两类。位运算符可以与赋值符一起组成复合赋值符。如&amp;=,|=,^=,&gt;&gt;=,&lt;&lt;=等。<br><br>4. 利用位运算可以完成汇编语言的某些功能,如置位,位清零,移位等。还可进行数据的压缩存储和并行运算。<br><br>5. 位域在本质上也是结构类型,不过它的成员按二进制位分配内存。其定义、说明及使用的方式都与结构相同。<br><br>6. 位域提供了一种手段,使得可在高级语言中实现数据的压缩,节省了存储空间,同时也提高了程序的效率。<br><br>7. 类型定义typedef 向用户提供了一种自定义类型说明符的手段,照顾了用户编程使用词汇的习惯,又增加了程序的可读性。</FONT>
24
 楼主| 发表于 2006-6-13 17:33 | 只看该作者
经典百事通,你的生活好助手~
<FONT color=#ff0000>第十一章 预处理<BR></FONT>概述<BR>   在前面各章中,已多次使用过以“#”号开头的预处理命令。如包含命令# include,宏定义命令# define等。在源程序中这些命令都放在函数之外, 而且一般都放在源文件的前面,它们称为预处理部分。<BR><BR>  所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是C语言的一个重要功能, 它由预处理程序负责完成。当对一个源文件进行编译时, 系统将自动引用预处理程序对源程序中的预处理部分作处理, 处理完毕自动进入对源程序的编译。<BR><BR>  C语言提供了多种预处理功能,如宏定义、文件包含、 条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、 移植和调试,也有利于模块化程序设计。本章介绍常用的几种预处理功能。<BR><BR>宏定义<BR>   在C语言源程序中允许用一个标识符来表示一个字符串, 称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换, 这称为“宏代换”或“宏展开”。<BR><BR>  宏定义是由源程序中的宏定义命令完成的。 宏代换是由预处理程序自动完成的。在C语言中,“宏”分为有参数和无参数两种。 下面分别讨论这两种“宏”的定义和调用。 无参宏定义<BR>   无参宏的宏名后不带参数。其定义的一般形式为: #define 标识符 字符串 其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。 “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。在前面介绍过的符号常量的定义就是一种无参宏定义。 此外,常对程序中反复使用的表达式进行宏定义。例如: # define M (y*y+3*y) 定义M表达式(y*y+3*y)。在编写源程序时,所有的(y*y+3*y)都可由M代替,而对源程序作编译时,将先由预处理程序进行宏代换,即用(y*y+3*y)表达式去置换所有的宏名M,然后再进行编译。<BR>#define M (y*y+3*y)<BR>main(){<BR>int s,y;<BR>printf("input a number: ");<BR>scanf("%d",&amp;y);<BR>s=3*M+4*M+5*M;<BR>printf("s=%d\n",s);<BR>}<BR>   上例程序中首先进行宏定义,定义M表达式(y*y+3*y),在s= 3*M+4*M+5* M中作了宏调用。在预处理时经宏展开后该语句变为:s=3*(y*y+3*y)+4(y*y+3*y)+5(y*y+3*y);但要注意的是,在宏定义中表达式(y*y+3*y)两边的括号不能少。否则会发生错误。<BR>   当作以下定义后: #difine M y*y+3*y在宏展开时将得到下述语句: s=3*y*y+3*y+4*y*y+3*y+5*y*y+3*y;这相当于; 3y2+3y+4y2+3y+5y2+3y;显然与原题意要求不符。计算结果当然是错误的。 因此在作宏定义时必须十分注意。应保证在宏代换之后不发生错误。对于宏定义还要说明以下几点:<BR><BR>1. 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。<BR><BR>2. 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。<BR><BR>3. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结 束。如要终止其作用域可使用# undef命令,例如: # define PI 3.14159<BR>main()<BR>{<BR>……<BR>}<BR># undef PIPI的作用域<BR>f1()<BR>....表示PI只在main函数中有效,在f1中无效。<BR>4. 宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。<BR>#define OK 100<BR>main()<BR>{<BR>printf("OK");<BR>printf("\n");<BR>}<BR>上例中定义宏名OK表示100,但在printf语句中OK被引号括起来,因此不作宏代换。程序的运行结果为:OK这表示把“OK”当字符串处理。<BR><BR>5. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。例如: #define PI 3.1415926<BR>#define S PI*y*y /* PI是已定义的宏名*/对语句: printf("%f",s);在宏代换后变为: printf("%f",3.1415926*y*y);<BR><BR>6. 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。<BR><BR>7. 可用宏定义表示数据类型,使书写方便。例如: #define STU struct stu在程序中可用STU作变量说明: STU body[5],*p;#define INTEGER int 在程序中即可用INTEGER作整型变量说明: INTEGER a,b; 应注意用宏定义表示数据类型和用typedef定义数据说明符的区别。宏定义只是简单的字符串代换,是在预处理完成的,而typedef是在编译时处理的,它不是作简单的代换, 而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。请看下面的例子: #define PIN1 int* typedef (int*) PIN2;从形式上看这两者相似, 但在实际使用中却不相同。下面用PIN1,PIN2说明变量时就可以看出它们的区别: PIN1 a,b;在宏代换后变成 int *a,b;表示a是指向整型的指针变量,而b是整型变量。然而:PIN2 a,b;表示a,b都是指向整型的指针变量。因为PIN2是一个类型说明符。由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟是作字符<BR>代换。在使用时要分外小心,以避出错。<BR><BR>8. 对“输出格式”作宏定义,可以减少书写麻烦。例9.3 中就采用了这种方法。<BR>#define P printf<BR>#define D "%d\n"<BR>#define F "%f\n"<BR>main(){<BR>int a=5, c=8, e=11;<BR>float b=3.8, d=9.7, f=21.08;<BR>P(D F,a,b);<BR>P(D F,c,d);<BR>P(D F,e,f);<BR>}<BR><BR>带参宏定义<BR><BR>  C语言允许宏带有参数。在宏定义中的参数称为形式参数, 在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开, 而且要用实参去代换形参。<BR><BR>  带参宏定义的一般形式为: #define 宏名(形参表) 字符串 在字符串中含有各个形参。带参宏调用的一般形式为: 宏名(实参表);<BR>例如:<BR>#define M(y) y*y+3*y /*宏定义*/<BR>:<BR>k=M(5); /*宏调用*/<BR>: 在宏调用时,用实参5去代替形参y, 经预处理宏展开后的语句<BR>为: k=5*5+3*5<BR>#define MAX(a,b) (a&gt;b)?a:b<BR>main(){<BR>int x,y,max;<BR>printf("input two numbers: ");<BR>scanf("%d%d",&amp;x,&amp;y);<BR>max=MAX(x,y);<BR>printf("max=%d\n",max);<BR>}<BR>   上例程序的第一行进行带参宏定义,用宏名MAX表示条件表达式(a&gt;b)?a:b,形参a,b均出现在条件表达式中。程序第七行max=MAX(x,<BR>y)为宏调用,实参x,y,将代换形参a,b。宏展开后该语句为: max=(x&gt;y)?x:y;用于计算x,y中的大数。对于带参的宏定义有以下问题需要说明:<BR><BR>1. 带参宏定义中,宏名和形参表之间不能有空格出现。<BR>例如把: #define MAX(a,b) (a&gt;b)?a:b写为: #define MAX (a,b) (a&gt;b)?a:b 将被认为是无参宏定义,宏名MAX代表字符串 (a,b)(a&gt;b)?a:b。<BR>宏展开时,宏调用语句: max=MAX(x,y);将变为: max=(a,b)(a&gt;b)?a:b(x,y);这显然是错误的。<BR><BR>2. 在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值。要用它们去代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。<BR><BR>3. 在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。<BR>#define SQ(y) (y)*(y)<BR>main(){<BR>int a,sq;<BR>printf("input a number: ");<BR>scanf("%d",&amp;a);<BR>sq=SQ(a+1);<BR>printf("sq=%d\n",sq);<BR>}<BR>   上例中第一行为宏定义,形参为y。程序第七行宏调用中实参为a+1,是一个表达式,在宏展开时,用a+1代换y,再用(y)*(y) 代换SQ,得到如下语句: sq=(a+1)*(a+1); 这与函数的调用是不同的, 函数调用时要把实参表达式的值求出来再赋予形参。 而宏代换中对实参表达式不作计算直接地照原样代换。<BR><BR>4. 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。 在上例中的宏定义中(y)*(y)表达式的y都用括号括起来,因此结果是正确的。如果去掉括号,把程序改为以下形式:<BR>#define SQ(y) y*y<BR>main(){<BR>int a,sq;<BR>printf("input a number: ");<BR>scanf("%d",&amp;a);<BR>sq=SQ(a+1);<BR>printf("sq=%d\n",sq);<BR>}<BR>运行结果为:input a number:3<BR>sq=7 同样输入3,但结果却是不一样的。问题在哪里呢? 这是由于代换只作符号代换而不作其它处理而造成的。 宏代换后将得到以下语句: sq=a+1*a+1; 由于a为3故sq的值为7。这显然与题意相违,因此参数两边的括号是不能少的。即使在参数两边加括号还是不够的,请看下面程序:<BR>#define SQ(y) (y)*(y)<BR>main(){<BR>int a,sq;<BR>printf("input a number: ");<BR>scanf("%d",&amp;a);<BR>sq=160/SQ(a+1);<BR>printf("sq=%d\n",sq);<BR>}<BR>   本程序与前例相比,只把宏调用语句改为: sq=160/SQ(a+1); 运行本程序如输入值仍为3时,希望结果为10。但实际运行的结果如下:input a number:3 sq=160为什么会得这样的结果呢?分析宏调用语句,在宏代换之后变为: sq=160/(a+1)*(a+1);a为3时,由于“/”和“*”运算符优先级和结合性相同, 则先作160/(3+1)得40,再作40*(3+1)最后得160。为了得到正确答案应在宏定义中的整个字符串外加括号, 程序修改如下<BR>#define SQ(y) ((y)*(y))<BR>main(){<BR>int a,sq;<BR>printf("input a number: ");<BR>scanf("%d",&amp;a);<BR>sq=160/SQ(a+1);<BR>printf("sq=%d\n",sq);<BR>}<BR>以上讨论说明,对于宏定义不仅应在参数两侧加括号, 也应在整个字符串外加括号。<BR><BR>5. 带参的宏和带参函数很相似,但有本质上的不同,除上面已谈到的各点外,把同一表达式用函数处理与用宏处理两者的结果有可能是不同的。main(){<BR>int i=1;<BR>while(i&lt;=5)<BR>printf("%d\n",SQ(i++));<BR>}<BR>SQ(int y)<BR>{<BR>return((y)*(y));<BR>}#define SQ(y) ((y)*(y))<BR>main(){<BR>int i=1;<BR>while(i&lt;=5)<BR>printf("%d\n",SQ(i++));<BR>}<BR>   在上例中函数名为SQ,形参为Y,函数体表达式为((y)*(y))。在例9.6中宏名为SQ,形参也为y,字符串表达式为(y)*(y))。 两例是相同的。例9.6的函数调用为SQ(i++),例9.7的宏调用为SQ(i++),实参也是相同的。从输出结果来看,却大不相同。分析如下:在例9.6中,函数调用是把实参i值传给形参y后自增1。 然后输出函数值。因而要循环5次。输出1~5的平方值。而在例9.7中宏调用时,只作代换。SQ(i++)被代换为((i++)*(i++))。在第一次循环时,由于i等于1,其计算过程为:表达式中前一个i初值为1,然后i自增1变为2,因此表达式中第2个i初值为2,两相乘的结果也为2,然后i值再自增1,得3。在第二次循环时,i值已有初值为3,因此表达式中前一个i为3,后一个i为4, 乘积为12,然后i再自增1变为5。进入第三次循环,由于i 值已为5,所以这将是最后一次循环。计算表达式的值为5*6等于30。i值再自增1变为6,不再满足循环条件,停止循环。从以上分析可以看出函数调用和宏调用二者在形式上相似, 在本质上是完全不同的。<BR><BR>6. 宏定义也可用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。看下面的例子。<BR>#define SSSV(s1,s2,s3,v) s1=l*w;s2=l*h;s3=w*h;v=w*l*h;<BR>main(){<BR>int l=3,w=4,h=5,sa,sb,sc,vv;<BR>SSSV(sa,sb,sc,vv);<BR>printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n",sa,sb,sc,vv);<BR>}<BR>   程序第一行为宏定义,用宏名SSSV表示4个赋值语句,4 个形参分别为4个赋值符左部的变量。在宏调用时,把4 个语句展开并用实参代替形参。使计算结果送入实参之中。<BR><BR>文件包含<BR><BR>  文件包含是C预处理程序的另一个重要功能。文件包含命令行的一般形式为: #include"文件名" 在前面我们已多次用此命令包含过库函数的头文件。例如:<BR>#include"stdio.h"<BR>#include"math.h"<BR>文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行, 从而把指定的文件和当前的源程序文件连成一个源文件。在程序设计中,文件包含是很有用的。 一个大的程序可以分为多个模块,由多个程序员分别编程。 有些公用的符号常量或宏定义等可单独组成一个文件, 在其它文件的开头用包含命令包含该文件即可使用。这样,可避免在每个文件开头都去书写那些公用量, 从而节省时间,并减少出错。<BR><BR>对文件包含命令还要说明以下几点:<BR>1. 包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来。例如以下写法都是允许的: #include"stdio.h" #include&lt;math.h&gt; 但是这两种形式是有区别的:使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的), 而不在源文件目录去查找; 使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。 用户编程时可根据自己文件所在的目录来选择某一种命令形式。<BR><BR>2. 一个include命令只能指定一个被包含文件, 若有多个文件要包含,则需用多个include命令。3. 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。<BR><BR>条件编译<BR><BR>预处理程序提供了条件编译的功能。 可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。 这对于程序的移植和调试是很有用的。 条件编译有三种形式,下面分别介绍:<BR>1. 第一种形式:<BR>#ifdef 标识符<BR>程序段1<BR>#else<BR>程序段2<BR>#endif<BR>它的功能是,如果标识符已被 #define命令定义过则对程序段1进行编译;否则对程序段2进行编译。如果没有程序段2(它为空),本格式中的#else可以没有, 即可以写为:<BR>#ifdef 标识符<BR>程序段 #endif<BR>#define NUM ok<BR>main(){<BR>struct stu<BR>{<BR>int num;<BR>char *name;<BR>char sex;<BR>float score;<BR>} *ps;<BR>ps=(struct stu*)malloc(sizeof(struct stu));<BR>ps-&gt;num=102;<BR>ps-&gt;name="Zhang ping";<BR>ps-&gt;sex='M';<BR>ps-&gt;score=62.5;<BR>#ifdef NUM<BR>printf("Number=%d\nScore=%f\n",ps-&gt;num,ps-&gt;score);<BR>#else<BR>printf("Name=%s\nSex=%c\n",ps-&gt;name,ps-&gt;sex);<BR>#endif<BR>free(ps);<BR>}<BR>   由于在程序的第16行插入了条件编译预处理命令, 因此要根据NUM是否被定义过来决定编译那一个printf语句。而在程序的第一行已对NUM作过宏定义,因此应对第一个printf语句作编译故运行结果是输出了学号和成绩。在程序的第一行宏定义中,定义NUM表示字符串OK,其实也可以为任何字符串,甚至不给出任何字符串,写为: #define NUM 也具有同样的意义。 只有取消程序的第一行才会去编译第二个printf语句。读者可上机试作。<BR><BR>2. 第二种形式:<BR>#ifndef 标识符<BR>程序段1<BR>#else<BR>程序段2<BR>#endif<BR>与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是,如果标识符未被#define命令定义过则对程序段1进行编译, 否则对程序段2进行编译。这与第一种形式的功能正相反。<BR><BR>3. 第三种形式:<BR>#if 常量表达式<BR>程序段1<BR>#else<BR>程序段2<BR>#endif<BR>它的功能是,如常量表达式的值为真(非0),则对程序段1 进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下,完成不同的功能<BR>#define R 1<BR>main(){<BR>float c,r,s;<BR>printf ("input a number: ");<BR>scanf("%f",&amp;c);<BR>#if R<BR>r=3.14159*c*c;<BR>printf("area of round is: %f\n",r);<BR>#else<BR>s=c*c;<BR>printf("area of square is: %f\n",s);<BR>#endif<BR>}<BR>   本例中采用了第三种形式的条件编译。在程序第一行宏定义中,定义R为1,因此在条件编译时,常量表达式的值为真, 故计算并输出圆面积。上面介绍的条件编译当然也可以用条件语句来实现。 但是用条件语句将会对整个源程序进行编译,生成的目标代码程序很长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2, 生成的目标程序较短。如果条件选择的程序段很长, 采用条件编译的方法是十分必要的。<BR><BR>本章小结<BR>1. 预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程序员在程序中用预处理命令来调用这些功能。<BR><BR>2. 宏定义是用一个标识符来表示一个字符串,这个字符串可以是常量、变量或表达式。在宏调用中将用该字符串代换宏名。<BR><BR>3. 宏定义可以带有参数,宏调用时是以实参代换形参。而不是“值传送”。<BR><BR>4. 为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。<BR><BR>5. 文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。<BR><BR><BR>6. 条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。<BR><BR>7. 使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。
25
发表于 2006-6-16 16:15 | 只看该作者
<P>很好的帖子,不错!</P>
<P>汗!!我只知道一些,还不知道楼主发完没有?感觉上好像还有一部分未发。</P>[em17][em17][em17][em17]
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
26
 楼主| 发表于 2006-6-18 12:51 | 只看该作者
重庆商务网,重庆电子商务第一网!
好象就是这些了,斑竹觉得还有些什么呢?
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
27
发表于 2006-6-18 14:53 | 只看该作者
重庆女性论坛
今天翻了一下书,可能也只有这些了,请问楼主这个C教程是你自己总结的?哪点找的哟?
重庆商务网,重庆电子商务第一网,欢迎入驻!
28
 楼主| 发表于 2006-6-24 03:42 | 只看该作者
说普通话,从我做起,全面提升重庆文明程度
<P>好多哟,我朋友总结的,我用的他的.</P>
29
发表于 2006-6-24 08:40 | 只看该作者
经典百事通,你的生活好助手~
楼主实在是强!你朋友更强啦
30
 楼主| 发表于 2006-6-24 15:49 | 只看该作者
<P>谢谢了哈,斑竹给我的感觉很好也.</P>
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
您需要登录后才可以回帖 登录 | 入驻经典

本版积分规则

连接招租|手机版| ( 苏ICP备13006526号-1 )

GMT+8, 2024-6-15 05:38

  • 欢迎关注重庆发展,多发帖多回帖才能持续保持帐号活力哟!请不要发表任何政 治,领 导,官 员,人 事及其它违法违规类言论,以免帐号被封禁。感谢您的支持和理解!
经典重庆旗下网站 | 24小时客服:13424176859 308675020

爱重庆,爱上经典重庆! 爱重庆,就上经典重庆!

快速回复 返回顶部 返回列表