资源预览内容
第1页 / 共110页
第2页 / 共110页
第3页 / 共110页
第4页 / 共110页
第5页 / 共110页
第6页 / 共110页
第7页 / 共110页
第8页 / 共110页
第9页 / 共110页
第10页 / 共110页
亲,该文档总共110页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
第十三章第十三章第十三章第十三章 若干深入问题若干深入问题若干深入问题若干深入问题n 函数函数函数指针函数指针函数作参数函数作参数间接递归间接递归函数副作用函数副作用n 运算运算赋值赋值顺序表达式顺序表达式条件表达式条件表达式位运算位运算作业作业: 13.6 13.7 n语句语句breakcontinuefor的延伸的延伸goto和标号和标号n数据组织数据组织多维数组与指针多维数组与指针位段位段共用体共用体n存储类别存储类别n编译预处理编译预处理13.1 函数函数 本节讲述有关函数的一些深入内容,包括:本节讲述有关函数的一些深入内容,包括:函数指针、函数作参数、间接递归、函数副作函数指针、函数作参数、间接递归、函数副作用等。用等。13.1.1 不定方向的数组排序不定方向的数组排序函数指针函数指针 【例例13.1】编函数,对给定整数数组排序,编函数,对给定整数数组排序,递增或递减按给定参数决定。递增或递减按给定参数决定。【例例例例13.113.1】编函数,对给定整数数组排序,递增或递减按给定参数决定。编函数,对给定整数数组排序,递增或递减按给定参数决定。编函数,对给定整数数组排序,递增或递减按给定参数决定。编函数,对给定整数数组排序,递增或递减按给定参数决定。boolbool ascending(intascending(int a, a, intint b) return ab; /* b) return ab; /* 具体说明函数具体说明函数具体说明函数具体说明函数 * */ /boolbool descending(intdescending(int a, a, intint b) return ab; b) return ab; void void swap(intswap(int *a , *a , intint *b ) *b ) intint temp; temp;temp = *a;temp = *a;*a = *b;*a = *b;*b = temp;*b = temp; void sort ( void sort ( intint a , a , intint n, char *flag) n, char *flag) boolbool (*ad) ( (*ad) (int,intint,int); /* ); /* 函数指针函数指针函数指针函数指针 * */ /intint pass,cpass,c; ;if (flag=ascending)/* if (flag=ascending)/* 根据排序方向给函数指针赋值具体函数根据排序方向给函数指针赋值具体函数根据排序方向给函数指针赋值具体函数根据排序方向给函数指针赋值具体函数 * */ / ad = ascending ; ad = ascending ;else ad = &descending ;else ad = &descending ; /* /* 函数名前加函数名前加函数名前加函数名前加“ “&”&”和不加和不加和不加和不加“ “&”&”意义相同意义相同意义相同意义相同 * */ /for ( pass=0; passn; pass+) /* for ( pass=0; passn; pass+) /* 冒泡排序冒泡排序冒泡排序冒泡排序 * */ / for ( c=0; cn-1; c+) for ( c=0; cb;l函数函数descending判断是否判断是否ab。当调用当调用sort时,时,sort根据根据flag的值是否的值是否“ascending”,决定按递增,决定按递增或递减排序,把函数名或递减排序,把函数名ascending或或descending赋值给函数指针变量赋值给函数指针变量ad。通。通过调用过调用ad所指向的函数,判所指向的函数,判断是否需要交换数组两个相断是否需要交换数组两个相邻成分。当需要交换时,调邻成分。当需要交换时,调用函数用函数swap。经过多次扫。经过多次扫描,最终达到排序目的。描,最终达到排序目的。函数函数sort使用了指向使用了指向函数的指针调用函数函数的指针调用函数ascending或或descending。运行结果演示运行结果演示 在数组与指针一节中曾指出数组名表示数组首地址,若将数组名赋值在数组与指针一节中曾指出数组名表示数组首地址,若将数组名赋值在数组与指针一节中曾指出数组名表示数组首地址,若将数组名赋值在数组与指针一节中曾指出数组名表示数组首地址,若将数组名赋值给一个类型兼容的指针变量,那么这个指针变量也指向这个数组。同样函给一个类型兼容的指针变量,那么这个指针变量也指向这个数组。同样函给一个类型兼容的指针变量,那么这个指针变量也指向这个数组。同样函给一个类型兼容的指针变量,那么这个指针变量也指向这个数组。同样函数名也具有上述相同的特性,即数名也具有上述相同的特性,即数名也具有上述相同的特性,即数名也具有上述相同的特性,即 函数名表示函数控制块的首地址,函数控制块中包括函数入口地址等函数名表示函数控制块的首地址,函数控制块中包括函数入口地址等函数名表示函数控制块的首地址,函数控制块中包括函数入口地址等函数名表示函数控制块的首地址,函数控制块中包括函数入口地址等信息。信息。信息。信息。 如果用一个指针变量来标识函数控制块的首地址,则称这个指针变量如果用一个指针变量来标识函数控制块的首地址,则称这个指针变量如果用一个指针变量来标识函数控制块的首地址,则称这个指针变量如果用一个指针变量来标识函数控制块的首地址,则称这个指针变量为指向函数的指针变量,简称指向函数的指针、函数指针。函数指针声明为指向函数的指针变量,简称指向函数的指针、函数指针。函数指针声明为指向函数的指针变量,简称指向函数的指针、函数指针。函数指针声明为指向函数的指针变量,简称指向函数的指针、函数指针。函数指针声明形式是:形式是:形式是:形式是: 类型符类型符类型符类型符 (* *标识符)(形式参数表);标识符)(形式参数表);标识符)(形式参数表);标识符)(形式参数表);其中其中其中其中l l标识符是被声明的标识符是被声明的标识符是被声明的标识符是被声明的“ “指向函数的指针变量指向函数的指针变量指向函数的指针变量指向函数的指针变量” ”名;名;名;名;l l类型符给出函数指针变量所指向函数的类型信息;类型符给出函数指针变量所指向函数的类型信息;类型符给出函数指针变量所指向函数的类型信息;类型符给出函数指针变量所指向函数的类型信息;l l形式参数表给出函数指针变量所指向的函数的形式参数信息。形式参数表给出函数指针变量所指向的函数的形式参数信息。形式参数表给出函数指针变量所指向的函数的形式参数信息。形式参数表给出函数指针变量所指向的函数的形式参数信息。例如例如int(*f)(floatd,charc);声明指向声明指向“返回返回int类型值的函数类型值的函数”的函数指针变量的函数指针变量f,f所指向所指向的函数有两个形式参数的函数有两个形式参数:l第一个参数是第一个参数是float类型,类型,l第二个参数是第二个参数是char类型。类型。 函数指针声明中用括号把星号函数指针声明中用括号把星号“*”和和“标识符标识符”括起来是括起来是必须的,比如上述的必须的,比如上述的“(*f)”,原因是由优先级造成的。引,原因是由优先级造成的。引进函数指针概念不是凭空臆造的,它的作用在于进函数指针概念不是凭空臆造的,它的作用在于l使用函数指针调用函数使用函数指针调用函数l实现其它程序设计语言中函数参数的功能实现其它程序设计语言中函数参数的功能可以把函数名赋值给一个函数指针变量,然后通过函数指针变可以把函数名赋值给一个函数指针变量,然后通过函数指针变量调用函数。形式是:量调用函数。形式是:函数指针变量函数指针变量 = 函数名;函数名;要求函数指针的特性与函数名的特性一致,这种一致性体现在要求函数指针的特性与函数名的特性一致,这种一致性体现在l它们的返回类型相同;它们的返回类型相同;l它们的参数个数相同;它们的参数个数相同;l对应位置上,每个形式参数的类型相同。对应位置上,每个形式参数的类型相同。使用函数指针要注意:使用函数指针要注意:l给函数指针赋值时,右端只是一个函数名,不许带参数表。给函数指针赋值时,右端只是一个函数名,不许带参数表。ad=ascending是正确的,而是正确的,而ad=ascending(int,int)是错误的。是错误的。l不能对函数指针变量进行任何运算。不能对函数指针变量进行任何运算。“ad+n”、“ad+”、“ad-”等是等是错误的。错误的。l利用函数指针调用函数时,把利用函数指针调用函数时,把“*”和函数名用括号括起来,成和函数名用括号括起来,成(*函数名)(函数名)()形式,是必须的。因为形式,是必须的。因为“()()”的优先级高于的优先级高于“*”。在例。在例13-3中,调用中,调用函数指针函数指针ad所指函数的形式是所指函数的形式是(*ad)(ac,ac+1)不能写成不能写成*ad(ac,ac+1) 函数指针声明中用括号把星号函数指针声明中用括号把星号函数指针声明中用括号把星号函数指针声明中用括号把星号“*”“*”和和和和“ “标识符标识符标识符标识符” ”括起来是必须的,括起来是必须的,括起来是必须的,括起来是必须的,比如上述的比如上述的比如上述的比如上述的 (* *f f) 原因是由优先级造成的。原因是由优先级造成的。原因是由优先级造成的。原因是由优先级造成的。 引进函数指针概念不是凭空臆造的,它的作用在于引进函数指针概念不是凭空臆造的,它的作用在于引进函数指针概念不是凭空臆造的,它的作用在于引进函数指针概念不是凭空臆造的,它的作用在于l l使用函数指针调用函数使用函数指针调用函数使用函数指针调用函数使用函数指针调用函数l l实现其它程序设计语言中函数参数的功能实现其它程序设计语言中函数参数的功能实现其它程序设计语言中函数参数的功能实现其它程序设计语言中函数参数的功能 本节先介绍利用函数指针调用函数,下一节介绍函数参数。本节先介绍利用函数指针调用函数,下一节介绍函数参数。本节先介绍利用函数指针调用函数,下一节介绍函数参数。本节先介绍利用函数指针调用函数,下一节介绍函数参数。 可以把函数名赋值给一个函数指针变量,然后通过函数指针变量调可以把函数名赋值给一个函数指针变量,然后通过函数指针变量调可以把函数名赋值给一个函数指针变量,然后通过函数指针变量调可以把函数名赋值给一个函数指针变量,然后通过函数指针变量调用函数。形式是:用函数。形式是:用函数。形式是:用函数。形式是: 函数指针变量函数指针变量函数指针变量函数指针变量 = = 函数名;函数名;函数名;函数名; 这个赋值要求这个赋值要求这个赋值要求这个赋值要求“ “函数指针的特性与函数名的特性一致函数指针的特性与函数名的特性一致函数指针的特性与函数名的特性一致函数指针的特性与函数名的特性一致” ”,这种一致性体,这种一致性体,这种一致性体,这种一致性体现在现在现在现在l l它们的返回类型相同;它们的返回类型相同;它们的返回类型相同;它们的返回类型相同;l l它们的参数个数相同;它们的参数个数相同;它们的参数个数相同;它们的参数个数相同;l l对应位置上,每个形式参数的类型相同。对应位置上,每个形式参数的类型相同。对应位置上,每个形式参数的类型相同。对应位置上,每个形式参数的类型相同。13.1.2 计算定积分计算定积分函数作参数函数作参数 一个函数可以调用其它函数,这是大家熟知的事实。有时遇到这种情一个函数可以调用其它函数,这是大家熟知的事实。有时遇到这种情一个函数可以调用其它函数,这是大家熟知的事实。有时遇到这种情一个函数可以调用其它函数,这是大家熟知的事实。有时遇到这种情况,在一个函数况,在一个函数况,在一个函数况,在一个函数P P内,要调用另一个函数,但到底调用哪一个函数要到执行内,要调用另一个函数,但到底调用哪一个函数要到执行内,要调用另一个函数,但到底调用哪一个函数要到执行内,要调用另一个函数,但到底调用哪一个函数要到执行函数函数函数函数P P才能确定。才能确定。才能确定。才能确定。【例例例例13.213.2】编程序,用梯形公式计算并打印定积分编程序,用梯形公式计算并打印定积分编程序,用梯形公式计算并打印定积分编程序,用梯形公式计算并打印定积分解:最好能有一个计算定积分的函数解:最好能有一个计算定积分的函数解:最好能有一个计算定积分的函数解:最好能有一个计算定积分的函数 integrate integrate 能够计算任意函数能够计算任意函数能够计算任意函数能够计算任意函数 f f 在区间在区间在区间在区间 a,ba,b 上的的定积分,然后分别以函数上的的定积分,然后分别以函数上的的定积分,然后分别以函数上的的定积分,然后分别以函数 等为参数,调用函数等为参数,调用函数等为参数,调用函数等为参数,调用函数 integrateintegrate。 dxexdxxsixdxxx + +- -20311210 33x2 six x f x dxab( ) 我们希望我们希望integrate能计算任意函数能计算任意函数f,在任意区间,在任意区间a,b上的定积分。上的定积分。而具体计算那个函数的积分由调用而具体计算那个函数的积分由调用integrate时确定。显然时确定。显然f作为作为integrate的一个参数比较合适。调用的一个参数比较合适。调用inegrate的函数调用可以写成:的函数调用可以写成:integrate(g,a,b)其中其中g为被积分函数。这就要求函数为被积分函数。这就要求函数integrate带有函数参数,带有函数参数,integrate的的函数定义说明符可以写成:函数定义说明符可以写成:floatintegrate(float(*f)(float),floata,floatb)函数作参数时,在形式参数表中应列出作参数函数的函数原型。目的是函数作参数时,在形式参数表中应列出作参数函数的函数原型。目的是为了说明该形式参数函数的特性。为了说明该形式参数函数的特性。下边继续开发函数下边继续开发函数下边继续开发函数下边继续开发函数 integrate integrate 。设。设。设。设 ; ; 其中其中其中其中 h=(h=(b-a)/nb-a)/n 。计算函数。计算函数。计算函数。计算函数 f f 在区间在区间在区间在区间 a,ba,b 上定积分的梯形公式是:上定积分的梯形公式是:上定积分的梯形公式是:上定积分的梯形公式是:求定积分的函数如下求定积分的函数如下求定积分的函数如下求定积分的函数如下: :float integrate ( float (*float integrate ( float (*f)(floatf)(float) /* f ) /* f 为被积分函数为被积分函数为被积分函数为被积分函数 * */ / ,float ,float a,floata,float b /* a , b b /* a , b 分别为积分区间下、上界分别为积分区间下、上界分别为积分区间下、上界分别为积分区间下、上界 * */ / , ,intint n ) /* n n ) /* n 为积分区间分割个数为积分区间分割个数为积分区间分割个数为积分区间分割个数 * */ / float float h,sh,s; ; intint i; i; h=( h=(b-a)/nb-a)/n ; ; s=( (* s=( (*f)(af)(a) + (*) + (*f)(bf)(b) ) / 2.0 ; ) ) / 2.0 ; for ( i=1; i=n-1; i+ ) for ( i=1; i=n-1; i+ ) s=s+(* s=s+(*f)(a+if)(a+i*h) ; *h) ; / / 调用函数调用函数调用函数调用函数“*“*f”f”,是被积分函数,由使用,是被积分函数,由使用,是被积分函数,由使用,是被积分函数,由使用者决定者决定者决定者决定 return s*h ;return s*h ; Yf aYf ahYf ahYf bn0122= = =+ += =+ += =( ),(),(),( )LLSf x dxhYYYYYYabnn= = + + + + + + + - -( )() /)012312LL另外分别编出另外分别编出另外分别编出另外分别编出: :的函数说明:的函数说明:的函数说明:的函数说明: float float cube(floatcube(float x) x) return x*x*x ; return x*x*x ; ; ; float sin2(float x) float sin2(float x) return return sin(xsin(x)*)*sin(xsin(x) ; ) ; ; ; float float r(floatr(float x) x) return return sqrt(xsqrt(x*x*x*x+sqrt(exp(xx+sqrt(exp(x) ;) ; ; ;主程序如下:主程序如下:主程序如下:主程序如下:mianmian () () printf(%fprintf(%f, integrate( cube , 0.0 , 1.0 , 100 ) );, integrate( cube , 0.0 , 1.0 , 100 ) ); printf(%fprintf(%f, integrate( sin2 , -1.0 , 1.0 , 100 ) );, integrate( sin2 , -1.0 , 1.0 , 100 ) ); printf(%fprintf(%f, integrate( r , 0.0 , 2.0 , 100 ) );, integrate( r , 0.0 , 2.0 , 100 ) ); xex+ +323x x sin 其中,实在参数其中,实在参数0、1;-1.0、1.0;0.0、2.0分别表示积分区间分别表示积分区间0、1,-1.0、1.0,0.0、2.0100为把积分区间等分成为把积分区间等分成100份;份;cube、sin2、r分别为被积函数的函数标识符。分别为被积函数的函数标识符。被积函数被积函数cube,sin2,r都是一元函数,其都是一元函数,其参数是参数是float型的,结果类型也是型的,结果类型也是float的。的。被积函数被积函数cube,sin2,r的函数定义说明符的函数定义说明符与与integrate形式参数表中的函数参数说明形式参数表中的函数参数说明float(*f)(float)是一致的。是一致的。运行结果演示运行结果演示函数作参数就是函数作参数就是函数作参数就是函数作参数就是“ “指向函数的指针指向函数的指针指向函数的指针指向函数的指针” ”作参数,它的形式是:作参数,它的形式是:作参数,它的形式是:作参数,它的形式是:l l形式参数表中函数形式参数以函数指针的函数原型形式说明。其中,函数形式参数表中函数形式参数以函数指针的函数原型形式说明。其中,函数形式参数表中函数形式参数以函数指针的函数原型形式说明。其中,函数形式参数表中函数形式参数以函数指针的函数原型形式说明。其中,函数形式参数的函数名使用指针并用括号括上,函数形式参数的形式参数表使形式参数的函数名使用指针并用括号括上,函数形式参数的形式参数表使形式参数的函数名使用指针并用括号括上,函数形式参数的形式参数表使形式参数的函数名使用指针并用括号括上,函数形式参数的形式参数表使用函数原型形式的形式参数表,形式是用函数原型形式的形式参数表,形式是用函数原型形式的形式参数表,形式是用函数原型形式的形式参数表,形式是 类型名类型名类型名类型名 (* *函数形式参数的形式参数名)(函数原型形式的形式参数表)函数形式参数的形式参数名)(函数原型形式的形式参数表)函数形式参数的形式参数名)(函数原型形式的形式参数表)函数形式参数的形式参数名)(函数原型形式的形式参数表)例如:例如:例如:例如:float integrate( float integrate( float (*float (*f)(floatf)(float) ) , . ) , . )l l函数调用中对应函数指针形式参数的实在参数直接使用函数名或函数指针函数调用中对应函数指针形式参数的实在参数直接使用函数名或函数指针函数调用中对应函数指针形式参数的实在参数直接使用函数名或函数指针函数调用中对应函数指针形式参数的实在参数直接使用函数名或函数指针l l在函数内部,调用函数指针形式参数表示的函数,使用形式在函数内部,调用函数指针形式参数表示的函数,使用形式在函数内部,调用函数指针形式参数表示的函数,使用形式在函数内部,调用函数指针形式参数表示的函数,使用形式 (* *形式参数函数名)(实在参数表)形式参数函数名)(实在参数表)形式参数函数名)(实在参数表)形式参数函数名)(实在参数表) 例如:例如:例如:例如: s=s+s=s+(*(*f)(a+if)(a+i*h)*h) 使用函数参数应该注意,函数调用中的实在参数函数与函数定义中的使用函数参数应该注意,函数调用中的实在参数函数与函数定义中的使用函数参数应该注意,函数调用中的实在参数函数与函数定义中的使用函数参数应该注意,函数调用中的实在参数函数与函数定义中的函数指针形式参数必须一致。这种一致性体现在:函数指针形式参数必须一致。这种一致性体现在:函数指针形式参数必须一致。这种一致性体现在:函数指针形式参数必须一致。这种一致性体现在:l l实在参数函数和形式参数函数的返回类型一致;实在参数函数和形式参数函数的返回类型一致;实在参数函数和形式参数函数的返回类型一致;实在参数函数和形式参数函数的返回类型一致;l l实在参数函数和形式参数函数的参数个数相同;实在参数函数和形式参数函数的参数个数相同;实在参数函数和形式参数函数的参数个数相同;实在参数函数和形式参数函数的参数个数相同;l l实在参数函数和形式参数函数的相对应的每个参数类型一致。实在参数函数和形式参数函数的相对应的每个参数类型一致。实在参数函数和形式参数函数的相对应的每个参数类型一致。实在参数函数和形式参数函数的相对应的每个参数类型一致。【例例例例13.313.3】用指向函数的指针作函数参数,实现例用指向函数的指针作函数参数,实现例用指向函数的指针作函数参数,实现例用指向函数的指针作函数参数,实现例13.113.1同样的问题:同样的问题:同样的问题:同样的问题: 编一个排序函数,该函数对给定整数数组既可以按递增排序也可以编一个排序函数,该函数对给定整数数组既可以按递增排序也可以编一个排序函数,该函数对给定整数数组既可以按递增排序也可以编一个排序函数,该函数对给定整数数组既可以按递增排序也可以按递或递减排序。按递或递减排序。按递或递减排序。按递或递减排序。解:程序片段如下:解:程序片段如下:解:程序片段如下:解:程序片段如下: void void bubsortbubsort( ( intint s, s, intint size, size, boolbool(*(*p)(int,intp)(int,int) ) ) ) intint u , v ; u , v ; void swap( void swap( intint * , * , intint * ); * ); for ( u=0; usize; u+ ) for ( u=0; usize; u+ ) for ( v=0; vsize-1; v+ ) for ( v=0; vb ; b) return ab ; boolbool descending(intdescending(int a; a; intint b) return ab ; b) return ab ; 函数函数bubsort有三个形式参数,有三个形式参数,s是欲排序的整数数组,是欲排序的整数数组,Size是是s数组的尺寸数组的尺寸,P是一个是一个bool型函数,调用型函数,调用bubsort时以时以ascending函数或者以函数或者以descending函数作实在参数对应它,具体指明是按递增排序还是按递减排序。函数作实在参数对应它,具体指明是按递增排序还是按递减排序。运行结果演示运行结果演示 【例例例例13.413.4】编程序,以编程序,以编程序,以编程序,以0.10.1为间隔,计算区间为间隔,计算区间为间隔,计算区间为间隔,计算区间 0,1 0,1 内所有正弦函数内所有正弦函数内所有正弦函数内所有正弦函数和所有余弦函数之和。和所有余弦函数之和。和所有余弦函数之和。和所有余弦函数之和。#include #include #include #include double sum( double(*double sum( double(*func)(doublefunc)(double) /* ) /* 参数参数参数参数funcfunc是函数指针是函数指针是函数指针是函数指针 * */ / , double d1, double d2) , double d1, double d2) double double dtdt=0.0 =0.0 ,d ;d ; for( d=d1; dd2; d+=0.1) for( d=d1; dd2; d+=0.1) dtdt += (* += (*func)(dfunc)(d); /* ); /* 用函数指针调用函数用函数指针调用函数用函数指针调用函数用函数指针调用函数 * */ / return return dtdt; ; void main()void main() double s; double s; s=sum(sin,0.1,1.0); /* s=sum(sin,0.1,1.0); /* 求求求求sinsin函数之和函数之和函数之和函数之和 * */ / printf(Theprintf(The sum of sin for 0.1 to 1.0 is %e sum of sin for 0.1 to 1.0 is %e n,sn,s);); s=sum(cos,0.5,3.0); /* s=sum(cos,0.5,3.0); /* 求求求求coscos函数之和函数之和函数之和函数之和 * */ / printf(Theprintf(The sum of sum of coscos for 0.5 to 3.0 is %e for 0.5 to 3.0 is %e n,sn,s);); 程序运行结果为:程序运行结果为:Thesumofsinfor0.1to1.0is5.01388Thesumofcosfor0.5to3.0is-2.44645函数函数sum()的第一个参数为函数指针,该指针指向的是带有一个的第一个参数为函数指针,该指针指向的是带有一个double型参数并返回型参数并返回double类型数据的函数。而在头文件类型数据的函数。而在头文件math.h中定义中定义的函数的函数sin()与与cos()正是这样的函数。正是这样的函数。主程序中,分别以主程序中,分别以sin()与与cos()作实在参数对应作实在参数对应sum函数的函数指针函数的函数指针参数参数func。达到函数。达到函数sum分别求分别求sin和和cos之和。之和。运行结果演示运行结果演示13.1.3 计算算术表达式的值计算算术表达式的值间接递归间接递归n前边讲的递归程序,都是在函数前边讲的递归程序,都是在函数本身的函数体内调用自己。本身的函数体内调用自己。n而递归的动态含义是在调用函数而递归的动态含义是在调用函数进入函数后,没有退出之前,又进入函数后,没有退出之前,又再一次调用本函数。再一次调用本函数。n可能存在如图所示情况,这显然可能存在如图所示情况,这显然也是进入也是进入P后,没退出后,没退出P之前又再之前又再一次调用一次调用P ,这种情况称,这种情况称“间接间接递归递归”。n相应前边讲的相应前边讲的“在在P中直接调用中直接调用P”称称“直接递归直接递归”。调用函数调用函数调用调用11调用调用2.n-1调用调用nn调用调用 【例例13.5】编程序,从终端读入表达式,计算表达式的值。表达编程序,从终端读入表达式,计算表达式的值。表达式遵守先乘除后加减的运算规则,并且允许有括号。它的构成规则是:式遵守先乘除后加减的运算规则,并且允许有括号。它的构成规则是: 表达式是表达式是“加法项加法项”;或者;或者“由一个加法项加上一个表达式由一个加法项加上一个表达式”构构成;或者成;或者“由一个加法项减去一个表达式由一个加法项减去一个表达式”构成。构成。 加法项是加法项是“乘法因子乘法因子”;或者;或者“由一个乘法因子乘以一个加法项由一个乘法因子乘以一个加法项”构成;或者构成;或者“由一个乘法因子除以一个加法项由一个乘法因子除以一个加法项”构成。构成。 乘法因子是一个乘法因子是一个“数字数字”;或者是;或者是 “由一对括号括起来的表达式由一对括号括起来的表达式”构成。构成。 若用若用E、T、F分别表示表达式、加法项、乘法因子,则可以形式分别表示表达式、加法项、乘法因子,则可以形式化的表示表达式的构成如下:化的表示表达式的构成如下: E : T+E 或者或者 T-E 或者或者 T T : F*T 或者或者 F/T 或者或者 F F : 数字数字 或者或者 (E) 解解:把把每每个个语语法法单单位位的的处处理理编编成成一一个个函函数数,全全局局量量w放放当当前前读入字符,得:读入字符,得:e(*ve)返回返回t(&v1)w=w1=we(&v2)*ve=v1w1=+*ve=v1+v2*ve=v1-v2t(*vt)返回返回f(&u1)w=*/w1=wt(&u2)w1=*vt=u1*u2*vt=u1/u2*vt=u1f(*vf)返回返回w=(*vf=we(vf)开始开始结束结束印印ve(&v)主函数在开始应该首先读入一个字符;主函数在开始应该首先读入一个字符;E的处理函数,也应该加上读入字符部分;的处理函数,也应该加上读入字符部分;与与E一样,相应一样,相应T的处理函数,也应该加上读入字符部分。的处理函数,也应该加上读入字符部分。最后,最后,F的处理:的处理:在处理带括号的在处理带括号的E之前,应该读入之前,应该读入E的第一个字符;的第一个字符;在处理完在处理完E之后,已经读入右括号,但是还应该把后继符读入之后,已经读入右括号,但是还应该把后继符读入在处理数字时,处理完数字后,还应该读入后继符号。在处理数字时,处理完数字后,还应该读入后继符号。读读(w)开始开始结束结束印印ve(&v)e(*ve)返回返回t(&v1)w=w1=we(&v2)*ve=v1w1=+*ve=v1+v2*ve=v1-v2读读(w)t(*vt)返回返回f(&u1)w=*/w1=wt(&u2)w1=*vt=u1*u2*vt=u1/u2*vt=u1读读(w)f(*vf)返回返回w=(*vf=we(vf)读读(w)读读(w)读读(w)读读(w)开始开始结束结束印印ve(&v)e(*ve)返回返回w1=we(&v2)*ve=v1-v2*ve=v1w=w1=+*ve=v1+v2t(&v1)读读(w)t(*vt)返回返回w=*/w1=w读读(w)t(&u2)*vt=u1w1=*vt=u1*u2*vt=u1/u2f(&u1)f(*vf)返回返回w=(*vf=w读读(w)读读(w)e(vf)读读(w)charw;/*2*/floatv;/*3*/voidt(float*vt);/*4*/voidf(float*vf);/*5*/voide(float*ve)/*6*/floatv2,v1;/*7*/charw1;/*8*/t(&v1);/*9*/if(w=+)|(w=-)/*10*/w1=w;/*11*/scanf(“%c”,&w);/*12*/e(&v2);/*13*/if(w1=+)/*14*/*ve=v1+v2;/*15*/else/*16*/*ve=v1-v2;/*17*/*18*/else*ve=v1;/*19*/*20*/ voidt(float*vt)/*21*/floatu1,u2;/*22*/charw1;/*23*/f(&u1);/*24*/if(w=*)|(w=/)/*25*/w1=w;/*26*/scanf(“%c”,&w);/*27*/t(&u2);/*28*/if(w1=*)/*29*/*vt=u1*u2;/*30*/else/*31*/*vt=u1/u2;/*32*/*33*/else*vt=u1/*34*/*35*/ voidf(float*vf)/*36*/if(w=()/*37*/scanf(“%c”,&w);/*38*/e(vf);/*39*/scanf(“%c”,&w);/*40*/else/*41*/*vf=(int)w-(int)0;/*42*/scanf(“%c”,&w);/*43*/*44*/*45*/intmain(void)/*46*/scanf(“%c”,&w);/*47*/e(&v);/*48*/printf(“n=%fn”,v);/*49*/*50*/运行结果演示运行结果演示13.1.4函数副作用函数副作用所谓函数副作用是指,当调用函数时,被调用函数除了返回函数值之外,所谓函数副作用是指,当调用函数时,被调用函数除了返回函数值之外,还对主调用函数产生附加的影响。例如,调用函数时在被调用函数内部还对主调用函数产生附加的影响。例如,调用函数时在被调用函数内部l修改全局量的值;修改全局量的值;l修改主调用函数中声明的变量的值(一般通过指针参数实现)。修改主调用函数中声明的变量的值(一般通过指针参数实现)。函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,并且降低程序的可读性。并且降低程序的可读性。第三章介绍表达式值的计算时曾经举过一个例子,由于双目运算的两个运第三章介绍表达式值的计算时曾经举过一个例子,由于双目运算的两个运算分量的计算次序不同,而带来运算结果不同,就是由函数副作用引起的。算分量的计算次序不同,而带来运算结果不同,就是由函数副作用引起的。对函数副作用的看法与对对函数副作用的看法与对GOTO语句的看法一样,在程序设计语言界一直语句的看法一样,在程序设计语言界一直有分歧,有人主张保留,有人主张取消。我们认为,可以保留函数副作用,但有分歧,有人主张保留,有人主张取消。我们认为,可以保留函数副作用,但是应该限制程序员尽量不要使用函数副作用。由于函数副作用的影响:是应该限制程序员尽量不要使用函数副作用。由于函数副作用的影响:l会使双目运算的结果依赖于两个运算分量的计算次序;会使双目运算的结果依赖于两个运算分量的计算次序;l还可能使某些在数学上明显成立的事实,在程序中就不一定成立。还可能使某些在数学上明显成立的事实,在程序中就不一定成立。例如,在数学上乘法符合交换律,例如,在数学上乘法符合交换律,a*f(x)与与f(x)*a显然相等。但是在程序中,若函数显然相等。但是在程序中,若函数f改变全局量改变全局量a的值,则上述交换律就不成的值,则上述交换律就不成立。立。设有函数:设有函数:floatf(floatu)a=a*2;return2*u假定,计算时开始假定,计算时开始a=3,x=5; 3a:5x:当双目运算符的运算对象从左向右计算时,计算:当双目运算符的运算对象从左向右计算时,计算:a*f(x)第一步,求运算分量第一步,求运算分量a的值,为的值,为3;第二步,求运算分量第二步,求运算分量f(x)的值,调用函数的值,调用函数f,u取取x值为值为5,进入,进入f执行执行a=a*2a得得6,再执行,再执行return2*u得函数值为得函数值为10,返回;,返回;第三步,计算表达式值为第三步,计算表达式值为3*10得得30。第一运算分量第一运算分量 第二运算分量第二运算分量* *35u:610而在同样条件下计算:而在同样条件下计算:f(x)*a106第一步,求运算分量第一步,求运算分量f(x)的值,调用函数的值,调用函数f,u取取x值为值为5,进入,进入f执行执行a=a*2a得得6,再执行,再执行return2*u得函数值为得函数值为10,返回;,返回;第二步,求运算分量第二步,求运算分量a的值,为的值,为6;第三步,计算表达式值为第三步,计算表达式值为10*6得得60。计算结果显然不一样,使乘法交换率不成立。这就是因为计算结果显然不一样,使乘法交换率不成立。这就是因为副作用的影响造成的,因为在函数副作用的影响造成的,因为在函数f内改变了全局量内改变了全局量a的值。的值。 若函数有指针参数,在函数分程序内修改指针参数所指变量的值,若函数有指针参数,在函数分程序内修改指针参数所指变量的值,若函数有指针参数,在函数分程序内修改指针参数所指变量的值,若函数有指针参数,在函数分程序内修改指针参数所指变量的值,也产生函数副作用,也可以引起同样的问题。例如有函数声明:也产生函数副作用,也可以引起同样的问题。例如有函数声明:也产生函数副作用,也可以引起同样的问题。例如有函数声明:也产生函数副作用,也可以引起同样的问题。例如有函数声明: float float f(intf(int *a , float u) *a , float u) *a = *a * 2; *a = *a * 2; return 2*u return 2*u 该函数存在副作用,调用该函数将使用该函数存在副作用,调用该函数将使用该函数存在副作用,调用该函数将使用该函数存在副作用,调用该函数将使用 f( &z , e )f( &z , e ) 的形式。其中,的形式。其中,的形式。其中,的形式。其中,l l z z 是一个变量,是一个变量,是一个变量,是一个变量,l l e e 是一个表达式。是一个表达式。是一个表达式。是一个表达式。 该函数在计算表达式该函数在计算表达式该函数在计算表达式该函数在计算表达式 z * z * f(&z,ef(&z,e) ) 时,将产生与第三章讲述过的例子相同的问题,表达式的值依赖于运算时,将产生与第三章讲述过的例子相同的问题,表达式的值依赖于运算时,将产生与第三章讲述过的例子相同的问题,表达式的值依赖于运算时,将产生与第三章讲述过的例子相同的问题,表达式的值依赖于运算分量的计算次序;分量的计算次序;分量的计算次序;分量的计算次序; 即使计算次序固定,也同样会产生与上述相同的问题,使即使计算次序固定,也同样会产生与上述相同的问题,使即使计算次序固定,也同样会产生与上述相同的问题,使即使计算次序固定,也同样会产生与上述相同的问题,使 z * z * f(&z,ef(&z,e) ) 不等于不等于不等于不等于 f(&z,ef(&z,e) * z) * z希望读者在编程序时,尽量不要使用带副作用的函数希望读者在编程序时,尽量不要使用带副作用的函数13.2 运算运算 C语言的运算符非常丰富,第三章表语言的运算符非常丰富,第三章表3.1列列出了所有出了所有C运算符,包括运算符的记号、运算、类运算符,包括运算符的记号、运算、类别、优先级、结合关系等。常用的运算符及其意别、优先级、结合关系等。常用的运算符及其意义我们已经在前面相关章节介绍过,本节介绍那义我们已经在前面相关章节介绍过,本节介绍那些些C独有的特色。独有的特色。13.2.1赋值运算赋值运算“=”可以和一些双目运算符结合,形成新的附加运算意义的赋值可以和一些双目运算符结合,形成新的附加运算意义的赋值运算符,称为复合赋值运算符。运算符,称为复合赋值运算符。复合赋值运算符包括:复合赋值运算符包括:+=*=/=%=&=|=使用这些赋值运算符的格式是:使用这些赋值运算符的格式是:操作数操作数1赋值运算符赋值运算符操作数操作数2复合赋值运算复合赋值运算aop=b与简单赋值运算与简单赋值运算a=aopb等价。等价。例如,设例如,设x=3则则x+=x*5;相当于相当于x=x+(x*5);结果结果x中为整数中为整数18。13.2.2顺序表达式顺序表达式用逗号运算符用逗号运算符,分隔开的若干个表达式称为分隔开的若干个表达式称为逗号表达式逗号表达式,又称为,又称为顺序表达式顺序表达式。逗号表达式按行文。逗号表达式按行文顺序从左向右计算各个子表达式的值。顺序从左向右计算各个子表达式的值。l表达式的结果类型是最右端表达式的类型,表达式的结果类型是最右端表达式的类型,l表达式的结果值是最右端表达式的值。表达式的结果值是最右端表达式的值。例如例如:j=(x=0.5,y=10,15+x,y=(int)x+y*2)将顺序的:将顺序的:1.先计算先计算“x=0.5”给给x赋值赋值0.5,得,得float类型的类型的0.5;2.再计算再计算“y=10”给给y赋值赋值10,得,得int类型的类型的10;3.再计算再计算“15+x”,得,得float类型的类型的15.5;4.再计算再计算“y=(int)x+y*2”给给y赋值赋值20,得,得int类型的类型的20;最终括号内表达式的结果值是最终括号内表达式的结果值是20,结果类型是整数类型,结果类型是整数类型,j被赋值被赋值20。13.2.3条件表达式条件表达式C条件表达式格式是:条件表达式格式是:操作数操作数1?操作数操作数2:操作数操作数3该表达式含有两个运算符该表达式含有两个运算符“?”和和“:”、三个操作数。条件表达、三个操作数。条件表达式是三元表达式,运算符式是三元表达式,运算符“?”和和“:”合并使用称为三元运算符。合并使用称为三元运算符。条件表达式的计算步骤是:条件表达式的计算步骤是:1.计算计算“操作数操作数1”;2.若若“操作数操作数1”的值为的值为true,则计算,则计算“操作数操作数2”,表达式的值为,表达式的值为“操作数操作数2”的值;的值;3.否则否则“操作数操作数1”的值为的值为false,计算计算“操作数操作数3”,表达式的值为表达式的值为“操操作数作数3”的值。的值。条件表达式是条件表达式是右结合右结合的,优先级别高于赋值运算符,低于二元操作符。的,优先级别高于赋值运算符,低于二元操作符。表达式语句表达式语句x=a?b:c;相当于如下条件语句:相当于如下条件语句:if(a!=0)x=b;elsex=c;由于右结合的特性,表达式由于右结合的特性,表达式u=ab?x:cd?y:z相当于相当于u=ab?x:(cd?y:z)用条件语句表示如下:用条件语句表示如下:if(ab)u=x;elseif(cd)u=y;elseu=z;13.2.4位运算位运算C可以直接针对二进制位进行操作,这使得用它描述系统程序十分可以直接针对二进制位进行操作,这使得用它描述系统程序十分方便。位运算的所有操作数必须为整数类型,下表列出方便。位运算的所有操作数必须为整数类型,下表列出C的位运算符,的位运算符,下边分别介绍它们定义的运算。下边分别介绍它们定义的运算。记记号号运算符运算符类别类别结结合关系合关系优优先先级级按位取反按位取反一元一元从右到左从右到左15左移、右移左移、右移二元二元从左到右从左到右11&按位与按位与8按位异或按位异或7|按位或按位或6按位取反按位取反:按位取反运算的格式是:按位取反运算的格式是:操作数操作数该运算该运算“”对操作数结果值的二进制表示的每一位取反码。对操作数结果值的二进制表示的每一位取反码。【例例13.6】如果如果X是一个是一个int类型的整数,十六进制表示为类型的整数,十六进制表示为0XF0F0它的二进制表示为它的二进制表示为111100001111000X结果的二进制表示为结果的二进制表示为0000111100001111十六进制的表示为十六进制的表示为OX0F0F位移运算位移运算:位移运算的格式是:位移运算的格式是:操作数操作数1位移运算符位移运算符操作数操作数2lC有两个位移运算符有两个位移运算符“”。其中。其中为右移;为右移;操作数操作数1是要进行位移整数;是要进行位移整数;操作数操作数2指定移动的位数。指定移动的位数。l位移运算的操作是:位移运算的操作是:按运算符的要求把按运算符的要求把“操作数操作数1”移动移动“操作数操作数2”指定的位数。指定的位数。在进行移位运算过程中,移到边界之外的多余位放弃扔掉;另一侧在进行移位运算过程中,移到边界之外的多余位放弃扔掉;另一侧产生的缺位以产生的缺位以“0”补足。补足。X5的的结结果果:0110100111100000(X2:0001101001111000(x2X:0001101101001111X5:0110100111100000X5【例例13.7】设变量设变量x值为值为0x1B4F,计算表达式,计算表达式“x2”的值。的值。解:首先解:首先x左移左移5位,所得结果为位,所得结果为0x69E0,如上图所示。然后再对所,如上图所示。然后再对所得结果向右移得结果向右移2位,结果为位,结果为0x1A78,如下图所示。,如下图所示。舍掉舍掉补补0舍掉舍掉补补0按位逻辑运算按位逻辑运算:按位逻辑运算是双目运算,它的格式是:按位逻辑运算是双目运算,它的格式是:操作数操作数1运算符运算符操作数操作数2具体操作是:具体操作是:l首先将两个操作数都转换成二进制数;首先将两个操作数都转换成二进制数;l然后根据运算符的要求以二进制位为单位,按位对其进行然后根据运算符的要求以二进制位为单位,按位对其进行位与位与、位异或、位异或、位或位或运算。运算。位位逻辑逻辑运算符以及它定运算符以及它定义义的操作的操作Xyx&y(按位与)(按位与)xy(按位异或)(按位异或)x|y(按位或)(按位或)00000010111001111101【例例13.8】设整数设整数x值为值为0x1B4F,y值为值为0x1A78,它们,它们按位逻辑运算结果如图按位逻辑运算结果如图.x:00011011010011110x1B4Fx:00011010011110000X1A78x&y00011010010010000X1A48xy00000001001101110X0137x|y00011011011111110X1B7Fx与与y的按位运算的按位运算13.3语句语句本节介绍三个语句本节介绍三个语句break、continue和和for,以及以及goto语句和标号语句和标号 。break和和continue语句是受限制的语句是受限制的goto语句,用来语句,用来改变循环或分支语句的控制流程。在达到相同目的的情况改变循环或分支语句的控制流程。在达到相同目的的情况下,使用下,使用break和和continue语句比起语句比起goto语句具有更好语句具有更好的风格和结构。但与全部用标准控制流程写出的程序相比,的风格和结构。但与全部用标准控制流程写出的程序相比,break和和continue语句的结构要差。语句的结构要差。for循环语句读者已经很熟悉,本节延伸它。循环语句读者已经很熟悉,本节延伸它。13.3.1break执行执行break语句使包含它的最内语句使包含它的最内层层while、do、for、switch语句终止语句终止执行,立即转移到所终止语句之外的执行,立即转移到所终止语句之外的程序点。在没有循环或程序点。在没有循环或switch语句的语句的场合使用场合使用break是错误的。是错误的。【例例13.9】迭代中使用迭代中使用breakintx=0;while(x10)printf(Looping);x;if(x=5)break;else其他代码其他代码后续代码后续代码intx=0;后续代码后续代码x10Fprintf(Looping);x+;x=5其他代码其他代码T在该程序片段中,循环将在在该程序片段中,循环将在x=5时停止,时停止,去执行后续代码,尽管循环控制当去执行后续代码,尽管循环控制当x10时时都执行循环体。用流程图来表示该程序片都执行循环体。用流程图来表示该程序片段如上图。段如上图。运行结果演示运行结果演示switch(x)case1:printf(“1”);case2:printf(“2”);case3:printf(“3”);default:printf(“no_meaning”);switch(x)case1:printf(“1”);break;case2:printf(“2”);break;case3:printf(“3”);break;default:printf(“no_meaning”);switch语语句中使用句中使用break之一之一switch语语句中使用句中使用break之二之二注意注意break在在switch语句中的作用。如下两段代码执行的结果是不语句中的作用。如下两段代码执行的结果是不一样的,请认真体会。一样的,请认真体会。1)在左端程序中,当在左端程序中,当x=2时,打印结果为时,打印结果为:23no_meaning因为因为switch语句将控制转移到语句将控制转移到case2处打印处打印2之后,接着执行之后,接着执行printf(“3”)和和printf(“no_meaning”)。2)如果需要在每次调用如果需要在每次调用printf之后都终止之后都终止switch体,则要象右端一样体,则要象右端一样3)使用使用break语句。在右端程序片段中,当语句。在右端程序片段中,当x=2时,执行结果只打印时,执行结果只打印4)出一个数字出一个数字2。13.3.2continuecontinue语句终止它所在的最内层语句终止它所在的最内层while、do、for语句循环体的执行,跳过循环体余下的代码,立即转移到循语句循环体的执行,跳过循环体余下的代码,立即转移到循环体末尾,受其影响的循环语句从环体末尾,受其影响的循环语句从“重新求值循环测试点重新求值循环测试点”开始执行(对开始执行(对for语句为语句为“表达式表达式3”)。)。在没有循环迭代语句的场合使用在没有循环迭代语句的场合使用continue语句是错误的。语句是错误的。【例例13.10】continue语句示例语句示例for(x=0;x5;x)if(x=2)continue;elseprintf(Looping%dn,x);该程序不可能打印该程序不可能打印”Looping2”,程程序执行流程如右图序执行流程如右图.程序执行结果程序执行结果:Looping0Looping1Looping3Looping4intx=0;x5Fprintf(Looping%dn,x);x+;x=2Tcontinue对循环的影响对循环的影响T运行结果演示运行结果演示13.3.3for的延伸的延伸 for语句包括语句包括for关键字和括在括号中的用分号分开的关键字和括在括号中的用分号分开的3个表达式,个表达式,然后是语句。通常然后是语句。通常:l第一个表达式初始化循环控制变量第一个表达式初始化循环控制变量;l第二个表达式测试循环是否终止第二个表达式测试循环是否终止;l第三个表达式更新循环控制变量第三个表达式更新循环控制变量;如果使用逗号表达式,就可以书写带有多个控制变量的如果使用逗号表达式,就可以书写带有多个控制变量的for语句语句【例例13.11】编函数,判断两个字符串编函数,判断两个字符串str1和和str2是否相等,相等则是否相等,相等则返回真,否则返回假。返回真,否则返回假。boolstr_equal(constchar*str1,constchar*str2)char*t1,*t2;for(t1=str1,t2=str2;*t1&*t2;t1+,t2+)if(*t1!=*t2)returnfalse;return*t1=*t2;13.3.4 goto和标号和标号 goto 语句是强制改变程序正常执行顺语句是强制改变程序正常执行顺序的手段。但是我们事先声明序的手段。但是我们事先声明:频繁使用频繁使用goto语句不是好的程序设计习语句不是好的程序设计习惯,不符合结构化程序设计原则惯,不符合结构化程序设计原则。除有特殊需要外,尽量不要使用除有特殊需要外,尽量不要使用goto语语句和标号。句和标号。 带标号的语句形式是带标号的语句形式是带标号的语句形式是带标号的语句形式是: : 标号标号标号标号 : : 语句语句语句语句 gotogoto 语句的形式是语句的形式是语句的形式是语句的形式是: : gotogoto 标号标号标号标号其中:其中:其中:其中: 标号就是带标号语句中用的标号,是一个标识符;标号就是带标号语句中用的标号,是一个标识符;标号就是带标号语句中用的标号,是一个标识符;标号就是带标号语句中用的标号,是一个标识符; gotogoto 是保留字,表示转向是保留字,表示转向是保留字,表示转向是保留字,表示转向。gotogoto 语语语语句句句句的的的的意意意意义义义义是是是是中中中中断断断断正正正正常常常常的的的的程程程程序序序序执执执执行行行行顺顺顺顺序序序序,转转转转到到到到本本本本函函函函数内标号标出的语句处,并从其开始继续向下执行。数内标号标出的语句处,并从其开始继续向下执行。数内标号标出的语句处,并从其开始继续向下执行。数内标号标出的语句处,并从其开始继续向下执行。gotogoto 语语语语句句句句与与与与带带带带标标标标号号号号语语语语句句句句配配配配合合合合使使使使用用用用,达达达达到到到到改改改改变变变变程程程程序序序序正正正正常常常常执执执执行顺序的目的。行顺序的目的。行顺序的目的。行顺序的目的。 【例例例例13.1213.12】前述第前述第前述第前述第4 4章例章例章例章例4.44.4中迭代法求解方程根的问题中迭代法求解方程根的问题中迭代法求解方程根的问题中迭代法求解方程根的问题, ,不改造流程图不改造流程图不改造流程图不改造流程图, , 可以用如下右端的程序片段表示可以用如下右端的程序片段表示可以用如下右端的程序片段表示可以用如下右端的程序片段表示 开始开始X0=初值初值X1 = f(X0)X0X1结束结束x0=x1TF x0=0.9; x0=0.9; /* 1 */ /* 1 */ r1:x1=f(x0); /* 2 */ r1:x1=f(x0); /* 2 */ if ( abs(x1-x0)1e-5 )/* 3 */ if ( abs(x1-x0)1e-5 )/* 3 */ gotogoto r2 ; /* 4 */ r2 ; /* 4 */ x0=x1; x0=x1; /* 5 */ /* 5 */ gotogoto r1; r1; /* 6 */ /* 6 */ r2: ; r2: ; /* 7 */ /* 7 */ 当当第第3 3行行的的条条件件为为truetrue时时,执执行行第第4 4行行的的gotogoto语语句句,则则转转到到标标号号r2r2标标出出的的第第7 7行去执行,从而结束迭代过程。行去执行,从而结束迭代过程。 当当程程序序执执行行到到第第6 6行行的的gotogoto语语句句后后,则则无无条条件件强强制制控控制制转转到到标标号号r1r1标标出出的的第第2 2行去执行,继续进行迭代。行去执行,继续进行迭代。运行结果演示运行结果演示 虽虽虽虽然然然然C C允允允允许许许许使使使使用用用用 gotogoto 语语语语句句句句转转转转向向向向本本本本函函函函数数数数内内内内任任任任何何何何语语语语句句句句,但但但但是是是是下下下下述述述述转转转转向向向向是是是是极极极极其其其其不不不不好好好好的的的的程程程程序序序序设设设设计计计计习习习习惯惯惯惯。它它它它使使使使程程程程序序序序逻逻逻逻辑辑辑辑混混混混乱,同时也给编译优化带来麻烦。乱,同时也给编译优化带来麻烦。乱,同时也给编译优化带来麻烦。乱,同时也给编译优化带来麻烦。 l l l l从从从从if if语语语语句句句句外外外外转转转转入入入入if if语语语语句句句句的的的的“ “then”then”或或或或“ “else”else”子子子子句句句句之之之之中;中;中;中; l l l l在在在在if if语句的语句的语句的语句的“ “then”then”或或或或“ “else”else”子句之间转向;子句之间转向;子句之间转向;子句之间转向; l l l l从从从从switchswitch语句之外转入语句之外转入语句之外转入语句之外转入switchswitch语句之内;语句之内;语句之内;语句之内; l l l l从循环语句之外转入循环语句之内;从循环语句之外转入循环语句之内;从循环语句之外转入循环语句之内;从循环语句之外转入循环语句之内; l l l l从复合语句之外转入复合语句之内。从复合语句之外转入复合语句之内。从复合语句之外转入复合语句之内。从复合语句之外转入复合语句之内。13.4 数据组织数据组织 前述章节已经介绍了各种描述数据的手段,本节介绍较前述章节已经介绍了各种描述数据的手段,本节介绍较深入的数据描述手段,包括:多维数组与指针间的关系、位深入的数据描述手段,包括:多维数组与指针间的关系、位段、共用体。段、共用体。13.4.1 13.4.1 多维数组与指针多维数组与指针n n1. 1. 二维数组元素的地址二维数组元素的地址二维数组二维数组 amn 可以看作是由可以看作是由 m 个一维数组个一维数组 a0、a1、 、am-2、am-1构成。每个一维数组有构成。每个一维数组有 n 个元素,即每个个元素,即每个ai都是都是由由 n 个变量个变量ai0、ai1、 、ain-1组成的数组。组成的数组。n如图所示,按数组与指针的关系:如图所示,按数组与指针的关系:从一维角度看,从一维角度看,a表示一维数组的首地址,该一维数组的数组表示一维数组的首地址,该一维数组的数组元素仍然是数组;元素仍然是数组;每个每个ai也都表示一维数组的首地址,该一维数组是也都表示一维数组的首地址,该一维数组是a数组的数组的第第i行,它的各个元素是行,它的各个元素是int类型。类型。指指针针常量常量指指针针常量常量这这些是数些是数组组a的成分,全部是的成分,全部是变变量量aa0a00a01a0n-1a1a10a11a1n-1am-2am-20am-21 am-2n-1am-1am-10am-11 am-1n-1实际上,不存在实际上,不存在a、a0、a1、am-1的存储空间,的存储空间,C系统不给它们分配内存,只分配系统不给它们分配内存,只分配m*n个变量的内存空间。下个变量的内存空间。下图是一个示意图,给出的图是一个示意图,给出的a、a0、a1、am-1只是一只是一个示意,读者不要误会。事实上个示意,读者不要误会。事实上a、a0、a1、a2、am-1都是指针常量。都是指针常量。进一步,进一步,ai是是a数组第数组第i个元素。个元素。l如果如果a是一维数组,则是一维数组,则ai是一个变量并且可以有值,是一个变量并且可以有值,C系统系统会给它分配相应的存储空间,它实实在在占用计算机内存会给它分配相应的存储空间,它实实在在占用计算机内存l如果如果a是二维数组,则是二维数组,则ai代表一维数组,它是一个指针常量,代表一维数组,它是一个指针常量,C系统不给它分配存储区,它不占用内存空间,仅仅是一个系统不给它分配存储区,它不占用内存空间,仅仅是一个地址。地址。 am-1n-1(int)am-10(int)am-1(int*)am-2n-1(int)am-20(int)am-2(int*)a1n-1(int)a10(int)a1(int*)a0n-1(int)a00(int)a0(int*)a(int*)指针常量指针常量指针常量指针常量形式形式意意义义a二二维维数数组组名,指向一名,指向一维维数数组组a0,即第即第0行首地址。行首地址。相当于:相当于:&(a0)也可以也可以说说是数是数组组a的首地址,指向的首地址,指向a00。相当于:相当于:&(a00)a+i第第i行首地址,即行首地址,即ai地址。相当于:地址。相当于:&(ai)。*a第第0行第行第0列元素首地址,即列元素首地址,即a00地址。地址。相当于:相当于:a0、*(a+0)、&(a00)*a+i第第0行第行第i列元素首地址,即列元素首地址,即a0i地址。地址。相当于:相当于:a0+i、&(a0i)*(a+i)第第i行第行第0列元素地址,即列元素地址,即ai0地址。地址。相当于:相当于:ai、&(ai0)*(a+i)+j第第i行第行第j列元素地址,即列元素地址,即aij地址。地址。相当于:相当于:ai+j、&(aij)*(*(a+i)+j)第第i行第行第j列元素列元素值值,即,即aij值值。相当于:相当于:*(ai+j)、aij在该表的各种表示形式中,只有在该表的各种表示形式中,只有&(aij)、ai+j、*(a+i)+j是实际计算机内存是实际计算机内存的物理地址的物理地址aij、*(ai+j)、*(*(a+i)+j)是它们各自的值,是它们各自的值,占用计算机内存;占用计算机内存;其它形式都是表示地址的指针常量,没有被分配其它形式都是表示地址的指针常量,没有被分配具体内存空间。例如,并不存在具体内存空间。例如,并不存在ai这样一个实际的这样一个实际的变量,它只是一个指针常量,运算变量,它只是一个指针常量,运算“&(ai)”只是只是一种地址计算方式而已,并不是求内存实际存在的变一种地址计算方式而已,并不是求内存实际存在的变量量ai的内存地址。的内存地址。2. 指向二维数组元素的指针变量指向二维数组元素的指针变量n用指针变量表示二维数组的元素用指针变量表示二维数组的元素可以直接使用数组成分类型的指针变量访可以直接使用数组成分类型的指针变量访问数组的成分问数组的成分C数组的存储分配方式数组的存储分配方式在在C中,按中,按“行优先行优先”原则分配数组元素的存储空原则分配数组元素的存储空间。也就是说,对数组间。也就是说,对数组a来说,它的各个成分被分来说,它的各个成分被分配的内存空间的顺序是:配的内存空间的顺序是:首先分配第首先分配第0行元素;行元素;然后分配第然后分配第1行元素;行元素;最后再分配第最后再分配第n-1行元素,如此等等。行元素,如此等等。每行元素按下标值从小到大进行。每行元素按下标值从小到大进行。a成分成分地址地址内存内存a23A016a22A014a21A012a20A010a13A00Ea12A00Ca11A00Aa10A008a03A006a02A004a01A002a00A000ninta34;n假设从首地址假设从首地址A000开开始分配内存空间始分配内存空间n每个元素占每个元素占2个字节个字节n数组数组a的存储分配图的存储分配图使用成分类型指针访问两维数组元素使用成分类型指针访问两维数组元素设有声明:设有声明:int*aptr,amn,x;则可以直接用则可以直接用aptr访问访问a的成分。使用方法是:的成分。使用方法是:n首先使首先使aptr指向指向a的某个成分的某个成分aij,n然后以该成分的地址为基点,计算所要访问的数组成分然后以该成分的地址为基点,计算所要访问的数组成分的相对地址,并进行访问。最常用的地址基点是的相对地址,并进行访问。最常用的地址基点是a数组数组的第一个成分的第一个成分a00,例如,例如aptr=&(a00)由于由于a0指向指向a00,所以这个运算还可以写成,所以这个运算还可以写成aptr=a0n在上述赋值运算的前提下,在上述赋值运算的前提下,a的成分的成分auv的地址为的地址为 aptr+u*n+vn若想把若想把auv的值送入变量的值送入变量x中,可以使用赋值运算:中,可以使用赋值运算:x = *(aptr+u*n+v) 这个运算等价于这个运算等价于 x = auvn若想把某表达式若想把某表达式e的值送入数组的值送入数组a的成分的成分auv中,可中,可以使用赋值运算:以使用赋值运算:*(aptr+u*n+v) = e 这个运算等价于这个运算等价于auv = ea成分成分地址地址内存内存a23A016a22A014a21A012a20A010a13A00Ea12A00Ca11A00Aa10A008a03A006a02A004a01A002a00A000n地址计算公式地址计算公式“aptr+u*n+v”基点基点+行数行数*每行元素个数每行元素个数+剩余行的零头元素个数剩余行的零头元素个数n例如,例如,a21的地址是:的地址是:aptr+2*4+1 a成分成分地址地址内存内存a23A016a22A014a21A012a20A010a13A00Ea12A00Ca11A00Aa10A008a03A006a02A004a01A002a00A000若再考虑每个元素占用的内存尺若再考虑每个元素占用的内存尺寸,寸,2*4+1还要乘以还要乘以int类型一类型一个变量占用内存空间数个变量占用内存空间数2,最后,最后a21对应的具体内存地址是:对应的具体内存地址是:A000+(2*4+1)*2为为A012。当然,在用户程序中只要写地址当然,在用户程序中只要写地址计算公式计算公式“aptr+u*n+v”即可即可.没有必要也不允许没有必要也不允许考虑每个元素考虑每个元素占用的存储空间尺寸。占用的存储空间尺寸。没有必要,也不允许,更不可能没有必要,也不允许,更不可能写出具体的地址计算计算算式写出具体的地址计算计算算式“A000+(2*4+1)*2”。例例13.13编函数,求编函数,求m*n个元素的给定个元素的给定float型数组各个型数组各个元素之乘积。元素之乘积。floatarrmul(intm,intn,float*arr)intu,v;floatmul;mul=1;for(u=0;um;u+)for(v=0;vn;v+)mul=mul*(*(arr+u*n+v);returnmul;设有声明设有声明floata1015;则可以用如下任何一种则可以用如下任何一种形式调用该函数:形式调用该函数:arrmul(10,15,&(a00)arrmul(10,15,a0)arrmul(10,15,*a)在本例中,形式参数是一个指向在本例中,形式参数是一个指向float类型的指针变量,实在参数把类型的指针变量,实在参数把a数组第一个元素数组第一个元素a00的指针(地址)送入形式参数的指针(地址)送入形式参数arr之中。之中。&(a00)、a0、*a都是都是a00的地址。实际上本例是把二维数的地址。实际上本例是把二维数组作为一维数组对待的,地址计算是采用一维方式进行的。组作为一维数组对待的,地址计算是采用一维方式进行的。运行结果演示运行结果演示a(float*)a0(float*)a00(float)a0n-1(float)a1(float*)a10(float)a1n-1(float)am-2(float*)am-20(float)am-2n-1(float)am-1(float*)am-10(float)am-1n-1(float)13.4.2位段位段为了适应系统程序设计的需要,通过使用位段,为了适应系统程序设计的需要,通过使用位段,C允许在结构体中允许在结构体中把整数类型成员存储在比通常使用的空间更小的空间内。比如在微型计把整数类型成员存储在比通常使用的空间更小的空间内。比如在微型计算机内一般把算机内一般把int类型数据存储成类型数据存储成2个字节(个字节(16bit),使用位段可以把),使用位段可以把它存储在比它存储在比2个字节少的电个字节少的电bit内。例内。例:structsunsigneda:4;unsignedb:5,c:7;u;结构体变量结构体变量u有三个成员有三个成员a、b、c分别占用分别占用4bit、5bit、7bit一共一共2个字节。个字节。U的成员的成员a、b、c称为位段。称为位段。位段通过在结构体成员名后加上冒号位段通过在结构体成员名后加上冒号“:”和一个整数类型常量和一个整数类型常量表达式来声明,整数类型常量表达式指明相应位段的宽度。表达式来声明,整数类型常量表达式指明相应位段的宽度。位段一般依赖于具体计算机系统,比如位段一般依赖于具体计算机系统,比如1.计算机系统一个机器字的宽度、计算机系统一个机器字的宽度、2.计算机系统存储数据是采用计算机系统存储数据是采用“高位存储法高位存储法”还是还是“低低位存储法位存储法”等等。使用位段要注意:等等。使用位段要注意:l位段仅允许声明为各种整数类型;位段仅允许声明为各种整数类型;l位段长度不允许超越特定计算机的自然字长;位段长度不允许超越特定计算机的自然字长;l位段占用的空间不能跨越特定计算机的地址分配边界(该位段占用的空间不能跨越特定计算机的地址分配边界(该边界与特定计算机的自然字长有关),出现跨越,将移边界与特定计算机的自然字长有关),出现跨越,将移到下一个机器字。到下一个机器字。l位段通常用于与具体计算机相关的程序中,因此破坏程序位段通常用于与具体计算机相关的程序中,因此破坏程序的可移植性。的可移植性。13.4.3 职工登记卡职工登记卡共用体共用体【例例13.14】学校的职工登记卡,可能包含如下内容:姓学校的职工登记卡,可能包含如下内容:姓名、出生时间、性别、参加工作时间、职别;然后对于不同名、出生时间、性别、参加工作时间、职别;然后对于不同职别的人员则包含不同信息如下职别的人员则包含不同信息如下:干部:级别(干部:级别( 校、处、科、其他校、处、科、其他 ););教师:最后学历(教师:最后学历( 硕士、博士、其他硕士、博士、其他 )、)、 职称(职称( 教授、讲师、助教教授、讲师、助教 )、)、 专业(专业( 数学、物理、化学、计算机数学、物理、化学、计算机 )。)。描述职工登记卡的数据结构。描述职工登记卡的数据结构。解:两种人员卡片的形式分别如图所示。解:两种人员卡片的形式分别如图所示。姓名姓名:姓名姓名:year:month:day:year:month:day:出生出生时间时间:出生出生时间时间:性性别别:性性别别:year:month:day:year:month:day:工作工作时间时间:工作工作时间时间:职别职别:干部干部职别职别:教教师师级别级别:学位学位:职职称称:专业专业:干部干部教教师师C为适应描述这种可变表格数据结构的需要,提供了共用体类为适应描述这种可变表格数据结构的需要,提供了共用体类型。可以采取共用体与结构体结合的方式描述这种结构可变的型。可以采取共用体与结构体结合的方式描述这种结构可变的表格。上述职工登记卡的数据类型可以定义成结构体类型表格。上述职工登记卡的数据类型可以定义成结构体类型typecardperson如下:如下:typedefstructcardpersoncharname8;/*姓名姓名*/datetypebirthdate;/*出生时间出生时间*/sextypesex;/*性别性别*/datetypeworkdate;/*参加工作时间参加工作时间*/categorytypecategory;/*职别职别*/category_tab_typecategory_tab;/*不同职别的不同信息不同职别的不同信息*/typecardperson;/日期:年、月、日日期:年、月、日typedefstructintyear,month,day;datetype;typedefenummale,femalesextype;/性别:男、女性别:男、女/职别职别:工人、干部、教师工人、干部、教师typedefenumworker,cadre,teachercategorytype;其中,其中,category_tab_type是共用体类型,类型定义已经提是共用体类型,类型定义已经提前定义如下:前定义如下:typedefunioncadrefieldtypecadrefield;teacherfieldtypeteacherfield;category_tab_type;/*描述不同类人员的共用体描述不同类人员的共用体*/typedefstructdegreetypedegree;/*教师记录的信息教师记录的信息*/titletypetitle;fieldtypefieldteacherfieldtype;/*专业:数、物、化、计算机专业:数、物、化、计算机*/typedefenummathematics,physics,chemistry,computerfieldtype;/*职称:教授、讲师、助教职称:教授、讲师、助教*/typedefenumprofessor,lecturer,assistanttitletype;/*学位学位:硕士、博士、其他硕士、博士、其他*/typedefenumdoctor,master,othersdegreetype;typedefstructjobtypejob;/*干部记录的信息干部记录的信息*/cadrefieldtype;/*干部级别:校、处、科、一般干部级别:校、处、科、一般*/typedefenumschool,department,section,generaljobtype;与结构体类似与结构体类似“共用体类型定义共用体类型定义”呈如下两种形式:呈如下两种形式:形式形式A形式形式Buniontid,.,id;.tid,.,id;unionsidtid,.,id;.tid,.,id;共用体类型共用体类型上述形式中除了上述形式中除了union与与struct的区别以外,完全与结构体类似。的区别以外,完全与结构体类似。各个部分的意义也与结构体完全相同。各个部分的意义也与结构体完全相同。 共用体类型声明、变量声明都与结构体类似,访问共共用体类型声明、变量声明都与结构体类似,访问共用体类型变量的成分也与结构体类似用体类型变量的成分也与结构体类似使用成员选择表使用成员选择表达式。达式。表面上看,共用体类型的类型说明符与结构体类型表面上看,共用体类型的类型说明符与结构体类型的类型说明符仅差一个关键字的类型说明符仅差一个关键字“union”和和“struct”,可是事实上它们有本质差别。它们的,可是事实上它们有本质差别。它们的差别在于:差别在于:l结构体类型中所有成员一个接一个的顺序分配存结构体类型中所有成员一个接一个的顺序分配存储空间,互相不冲突;储空间,互相不冲突;l共用体类型中所有成员占用公共的存储空间,也共用体类型中所有成员占用公共的存储空间,也就是说,它们从同一个地址开始分配存储,各个就是说,它们从同一个地址开始分配存储,各个成员的存储空间是重叠的。成员的存储空间是重叠的。typecardperson类型变量的结构如下类型变量的结构如下,它的分量中有,它的分量中有l结构体类型的,例如结构体类型的,例如birthdate;l共用体类型的,例如共用体类型的,例如category_tab。 name:year:month:day:birthdate:sex:year:month:day:workdate:category:category_tab:为了说明结构体与共用体的区别,请注意这两个分量的存储影为了说明结构体与共用体的区别,请注意这两个分量的存储影象:象:birthdate的类型的类型datetype是结构体类型是结构体类型,它有三个字它有三个字段段year、month、day,它们串行的一个接一个的顺序分,它们串行的一个接一个的顺序分配存储空间。配存储空间。birthdate:year:month:day:.category_tab的类型的类型category_tab_type是共用体类型,是共用体类型,它也有两个字段它也有两个字段cadrefield、teacherfield,它们并行的从,它们并行的从内存同一个地址开始分配存储空间。内存同一个地址开始分配存储空间。cadrefieldTeacherfieldcategorytab:jobDegree.TitleField.限制限制n共用体使得在同一个内存区域可以存储不同类型共用体使得在同一个内存区域可以存储不同类型的数据,但是程序运行的每一个局部时刻,只能的数据,但是程序运行的每一个局部时刻,只能存储其中一种类型数据。并且该数据是最后存入存储其中一种类型数据。并且该数据是最后存入的数据,其它以前存入的数据被覆盖。的数据,其它以前存入的数据被覆盖。n共用体变量的地址与它的各个成员的地址是同一共用体变量的地址与它的各个成员的地址是同一个地址。个地址。n不能对共用体变量整体赋值,也不能通过引用共不能对共用体变量整体赋值,也不能通过引用共用体变量来得到一个共用体的值用体变量来得到一个共用体的值n共用体类型不能作函数的参数类型和函数的返回共用体类型不能作函数的参数类型和函数的返回类型;但是指向共用体类型的指针类型属于一般类型;但是指向共用体类型的指针类型属于一般指针类型,当然可以作函数的参数类型和函数的指针类型,当然可以作函数的参数类型和函数的返回类型。返回类型。n给共用体类型变量赋初值,仅对应相应共用体类给共用体类型变量赋初值,仅对应相应共用体类型的第一个字段(从静态行文上看)。型的第一个字段(从静态行文上看)。switch与共用体与共用体可以使用可以使用switch语句方便的处理共用体。语句方便的处理共用体。比如针对例比如针对例11-7学校职工登记表可以采用如下语句结构处学校职工登记表可以采用如下语句结构处理理/*处理姓名处理姓名name*/*处理出生时间处理出生时间birthdate*/*处理性别处理性别sex*/*处理参加工作时间处理参加工作时间workdate*/*处理职别处理职别*/switch(category)/*处理不同职别的不同信息处理不同职别的不同信息category_tab*/caseworker:处理工人信息语句处理工人信息语句;break;casecadre:处理干部信息语句处理干部信息语句;break;caseteacher:处理教师信息语句处理教师信息语句13.5存储类别存储类别C语言中每个变量和函数都具有语言中每个变量和函数都具有2个属性:个属性:类型类型存储类别存储类别存储类别指的是数据的存储的方式。存储方式分为两大类:存储类别指的是数据的存储的方式。存储方式分为两大类:静态静态动态动态具体包括五种:具体包括五种:l自动的(自动的(auto)l静态的(静态的(static)l寄存器的(寄存器的(register)l外部的(外部的(extern)l类型定义符(类型定义符(typedef)。)。静态存储方式:静态存储方式:在程序运行期间分配固定的存储空间(在静态区)。包括:在程序运行期间分配固定的存储空间(在静态区)。包括:l外部存储类别外部存储类别l静态存储类别静态存储类别动态存储方式动态存储方式:在程序运行期间根据函数调用(函数被激活)和复合语句的开始执行(复在程序运行期间根据函数调用(函数被激活)和复合语句的开始执行(复合语句被激活)的需要进行动态存储分配(在栈区)。包括:合语句被激活)的需要进行动态存储分配(在栈区)。包括:l自动存储类别自动存储类别l寄存器存储类别寄存器存储类别类型定义符则是用来定义类型名的。类型定义符则是用来定义类型名的。C通过在类型符前缀以通过在类型符前缀以“存储类别关键字存储类别关键字”来声明变量和函数的存储类别。来声明变量和函数的存储类别。这里所说的这里所说的“动态存储方式动态存储方式”和和“静态存储方式静态存储方式”要和要和第第13章动态数据结构中所讲的章动态数据结构中所讲的“动态变量动态变量”和和“静态变量静态变量”区别开。区别开。按第按第13章的概念,本节所有存储类别的变量全部都是章的概念,本节所有存储类别的变量全部都是静静态变量态变量,它们由系统在,它们由系统在栈区或静态区栈区或静态区分配存储空间;而第分配存储空间;而第13章的章的动态变量动态变量没有显示的名字,是通过执行由程序员安排的没有显示的名字,是通过执行由程序员安排的申请空间函数(例申请空间函数(例malloc)在)在堆区堆区分配存储空间,并由指针分配存储空间,并由指针变量标识。变量标识。本节的概念是指程序中本节的概念是指程序中显示声明的变量显示声明的变量的的存储分配方式存储分配方式,表明它们是在表明它们是在栈区还是在静态区栈区还是在静态区分配存储空间,及其分配方分配存储空间,及其分配方式。式。 例如:例如:autofloatx,y;声明两个浮点类型变量声明两个浮点类型变量x、y,其存储类别是自动的。,其存储类别是自动的。13.5.1数据在内存中的存储数据在内存中的存储数据的存储类别规定了数据的存储区域,同时也说明了数据的生存数据的存储类别规定了数据的存储区域,同时也说明了数据的生存期。计算机中,用于存放程序和数据的物理单元有期。计算机中,用于存放程序和数据的物理单元有寄存器寄存器内存内存寄存器寄存器寄存器寄存器1寄存器寄存器2寄存器寄存器n.内存内存系系统统程序和数据程序和数据系系统统区区用用户户程序代程序代码码区区静静态态存存储储区区库库程序代程序代码码区区堆区堆区栈栈区区用用户户区区寄存器速度快但空间小,常常只寄存器速度快但空间小,常常只存放经常参与运算的少数数据。存放经常参与运算的少数数据。内存速度慢但空间大,可存放程序和数据。内存中又分为内存速度慢但空间大,可存放程序和数据。内存中又分为系统区系统区用户程序区用户程序区数据区(包括:堆区、栈区、静态存储区)数据区(包括:堆区、栈区、静态存储区)系统区:用于存放系统软件,如操作系统、语言编译器。系统区:用于存放系统软件,如操作系统、语言编译器。只要计算机运行,这一部分空间就必须保留给系统软件使用。只要计算机运行,这一部分空间就必须保留给系统软件使用。用户程序代码区:用于存放用户程序的程序代码。用户程序代码区:用于存放用户程序的程序代码。库程序代码区:用于存放库函数的代码。库程序代码区:用于存放库函数的代码。数据区:用来存储用户程序数据,包括数据区:用来存储用户程序数据,包括堆区、栈区和静态存储区。堆区、栈区和静态存储区。堆区:用于存储动态变量;堆区:用于存储动态变量;经过经过malloc申请来的动态变量经常存储在堆区。申请来的动态变量经常存储在堆区。栈区:具有先进后出特性。栈区:具有先进后出特性。用于存储程序中显式声明的自动存储方式的变量。用于存储程序中显式声明的自动存储方式的变量。静态存储区:用于存储程序中显式声明的静态存储方式的变量。静态存储区:用于存储程序中显式声明的静态存储方式的变量。13.5.2自动存储类别自动存储类别具有自动存储类别的变量简称自动变量。具有自动存储类别的变量简称自动变量。C用用auto表示自动存储表示自动存储类别,它是类别,它是C中使用最广泛的一种存储类别。中使用最广泛的一种存储类别。C规定,在函数内凡未规定,在函数内凡未加存储类别说明的变量均视为自动变量,也就是说在函数内自动变量加存储类别说明的变量均视为自动变量,也就是说在函数内自动变量可省去存储类别说明符可省去存储类别说明符auto。如下两个程序片段等价。如下两个程序片段等价。inti,j,k;booltest;.autointi,j,k;autobooltest;.自动变量的作用域仅限于定义它的相应个体(函数、复合语句)内。自动变量的作用域仅限于定义它的相应个体(函数、复合语句)内。1.如果是在函数内定义的,则只在函数内有效;如果是在函数内定义的,则只在函数内有效;2.若是在复合语句中定义,则只在相应语句中有效。若是在复合语句中定义,则只在相应语句中有效。自动变量具有动态生存期,自动变量具有动态生存期,1.当定义自动变量的相应个体开始执行时,自动变量生存期开始;当定义自动变量的相应个体开始执行时,自动变量生存期开始;2.当定义自动变量的相应个体执行结束时,自动变量也离开它的生存期,当定义自动变量的相应个体执行结束时,自动变量也离开它的生存期,不敷存在。不敷存在。下例说明动态变量作用域,以及生存期。下例说明动态变量作用域,以及生存期。【例例13.15】动态变量作用域,以及生存期动态变量作用域,以及生存期intexample_auto(intx,inty)/*L1*/autointw,h;/*L2*/*L3*/*L4*/autocharc;/*L5*/*L6*/*L7*/*L8*/*L9*/在该程序片段中,在该程序片段中,函数函数example_auto的两个参数的两个参数x和和y的作用域是在的作用域是在L1和和L9之间,之间,生存期是程序执行进入函数生存期是程序执行进入函数example_auto开始,直到该函数执行完毕返回;开始,直到该函数执行完毕返回;自动变量自动变量w和和h的作用域在的作用域在L2和和L9之间,之间,生存期是程序进入复合语句生存期是程序进入复合语句L2-L9执行开始,直到该复合语句执行完毕退出;执行开始,直到该复合语句执行完毕退出;自动变量自动变量c的作用域则局限于的作用域则局限于L5和和L7之间。如果在之间。如果在L8有引用变量有引用变量c的语句,的语句,则错误。生存期是程序进入复合语句则错误。生存期是程序进入复合语句L4-L7执行开始,直到该复合语句执行执行开始,直到该复合语句执行完毕退出。完毕退出。13.5.3寄存器存储类别寄存器存储类别寄存器变量分配在寄存器变量分配在CPU的寄存器中,使用时不访问内存,直接的寄存器中,使用时不访问内存,直接在寄存器中进行,提高了程序运行效率。寄存器变量的存储类别明符在寄存器中进行,提高了程序运行效率。寄存器变量的存储类别明符是是register。寄存器的个数与寄存器的个数与cpu相关,十分有限,所以寄存器变量的个数必相关,十分有限,所以寄存器变量的个数必然也有限。现代编译系统一般自动分配然也有限。现代编译系统一般自动分配cpu寄存器,所以程序员说明寄存器,所以程序员说明的寄存器变量不起作用。的寄存器变量不起作用。【例例13.16】求求1000以内可以被以内可以被3整除所有整数的积,并打印整除所有整数的积,并打印voidmain()registerinti,s=1;for(i=1;i=1000;i+)if(i%3=0)s=s*i;printf(s=%d,s);本程序循环本程序循环1000次,次,i和和s都将频繁使用,因此可定义为寄存器变量。都将频繁使用,因此可定义为寄存器变量。13.5.4变量的静态存储类别变量的静态存储类别静态存储类别使用关键字静态存储类别使用关键字static声明,静态存储类别的声明,静态存储类别的变量简称静态变量,静态存储类别的函数称为静态函数。变量简称静态变量,静态存储类别的函数称为静态函数。C规定,静态变量必须使用规定,静态变量必须使用static声明。静态变量分为声明。静态变量分为l静态全局变量静态全局变量l静态局部变量。静态局部变量。静态全局变量和静态局部变量的生存期都是贯穿于整个静态全局变量和静态局部变量的生存期都是贯穿于整个程序的运行期间;它们的不同点在于程序的运行期间;它们的不同点在于作用域作用域:l静态全局变量的作用域是包含它的声明的整个源程静态全局变量的作用域是包含它的声明的整个源程序文件,序文件,l而静态局部变量的作用域是声明它的复合语句或函而静态局部变量的作用域是声明它的复合语句或函数。数。静态局部变量静态局部变量:在局部变量的声明前再加上在局部变量的声明前再加上static说明符就构成静态局部变量。说明符就构成静态局部变量。例如:例如:staticcharx,y;staticintstr3=0,1,2;复合语句内的局部变量复合语句内的局部变量x、y、str被声明成被声明成static存储类别的,存储类别的,是静态局部变量是静态局部变量。静态局部变量采用静态存储方式,被分配在静态存储区。它的生静态局部变量采用静态存储方式,被分配在静态存储区。它的生存期为整个程序,但是它的作用域与自动变量相同,即只能在定义该变存期为整个程序,但是它的作用域与自动变量相同,即只能在定义该变量的复合语句或函数中使用。离开复合语句和函数后,静态局部变量仍量的复合语句或函数中使用。离开复合语句和函数后,静态局部变量仍然存在却不能使用。然存在却不能使用。虽然静态局部变量在离开声明它的函数或复合语句后不能使用,但是如果虽然静态局部变量在离开声明它的函数或复合语句后不能使用,但是如果再次调用声明它的函数或再一次进入声明它的复合语句时,又可以继续使用它,再次调用声明它的函数或再一次进入声明它的复合语句时,又可以继续使用它,而且这时还保存了前次被使用后留下的值。而且这时还保存了前次被使用后留下的值。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用静态局部变量为宜。因此仍以采用静态局部变量为宜。【例例13.17】静态局部变量使用举例静态局部变量使用举例voidmain()/主程序主程序for(inti=0;ib)returna;elsereturnb;在该程序中,源文件在该程序中,源文件max.c中中声明函数声明函数max;源文件;源文件main.c中调用函数中调用函数max。max的函数的函数原型声明被指定为原型声明被指定为extern类别类别的,保证了在源文件的,保证了在源文件main.c中中调用的调用的max就是在就是在max.c中定中定义的函数义的函数max,并且不发生声,并且不发生声明冲突。明冲突。运行结果演示运行结果演示13.5.7类型定义符类型定义符类型定义符的实质是定义类型的同义词,用来把标识符定义为类型类型定义符的实质是定义类型的同义词,用来把标识符定义为类型名。把一个标识符定义为类型名之后,它就可以出现在允许使用类型说明名。把一个标识符定义为类型名之后,它就可以出现在允许使用类型说明符的任何地方。这样就可以用简单的名字替代复杂的类型声明。符的任何地方。这样就可以用简单的名字替代复杂的类型声明。【例例13.20】类型定义举例类型定义举例typedefbool*BP;typedefint(*IFP)();typedefintIF2I(int,int);typedefintIA5;有了这些定义之后就可以进行如下的声明。有了这些定义之后就可以进行如下的声明。BPbp;/*bp是一个布尔型指针是一个布尔型指针*/BPfbp();/*fbp是一个返回布尔型指针的函数是一个返回布尔型指针的函数*/IFPifp;/*ifp是一个返回类型为整型的函数指针是一个返回类型为整型的函数指针*/IF2I*fp;/*fp是一个返回类型为整型且有两个整型参数的函数指针是一个返回类型为整型且有两个整型参数的函数指针*/IAia;/*ia是一个长度为是一个长度为5的一维整型数组的一维整型数组*/IAia22;/*ia2是一个是一个2行行5列的二维整型数组列的二维整型数组*/BP是布尔型指针类型是布尔型指针类型IFP是指向返回值为整型的函数的指针类型是指向返回值为整型的函数的指针类型IF2I是有两个整型参数且返回整型值的函数类型是有两个整型参数且返回整型值的函数类型IA是长度为是长度为5的一维整型数组类型的一维整型数组类型13.6编译预处理编译预处理C语言的预处理器是一个简单的宏处理器,源程序必语言的预处理器是一个简单的宏处理器,源程序必须经过这个宏处理器处理之后才能让编译器正确处理。须经过这个宏处理器处理之后才能让编译器正确处理。13.6.1宏定义宏定义语言源程序中允许用一个标识符来表示一个字符串,语言源程序中允许用一个标识符来表示一个字符串,称为称为“宏宏”。被定义为。被定义为“宏宏”的标识符称为的标识符称为“宏名宏名”。在。在编译预处理时,对程序中所有出现的编译预处理时,对程序中所有出现的“宏名宏名”,都用宏定,都用宏定义中的字符串去代换,称为义中的字符串去代换,称为“宏代换宏代换”或或“宏展开宏展开”。宏定义由源程序中的宏定义命令完成。宏定义由源程序中的宏定义命令完成。宏展开由编译预处理程序自动完成。宏展开由编译预处理程序自动完成。 宏定义的形式为:宏定义的形式为:#define标识符标识符字符串字符串其中:其中:l#代表本行是编译预处理命令代表本行是编译预处理命令ldefine是宏定义命令是宏定义命令l标识符是所定义的宏名标识符是所定义的宏名l字符串是宏名所代替的内容,可以是常数、表达式等字符串是宏名所代替的内容,可以是常数、表达式等等。等。【例例13.21】计算半径为计算半径为10米的圆的周长,其中用宏定义了圆周率米的圆的周长,其中用宏定义了圆周率PI。当编译预处理时,将用当编译预处理时,将用3.1415926来替代程序中出现的所有来替代程序中出现的所有PI。相当于在所有。相当于在所有出现出现PI的地方全部写的地方全部写3.1415926一样。程序运行时自然使用一样。程序运行时自然使用3.1415926参与运算。参与运算。#definePI3.1415926main()intr=10;intl;l=2*PI*r;printf(“Theperimeterofacirclewith%dmeterradiusis%dn”,r,l);运行结果演示运行结果演示如要终止宏定义作用域可使用如要终止宏定义作用域可使用“#undef”命令。例命令。例:#definePI3.1415926main()#undefPI/*终止终止PI的作用定义的作用定义*/说明:说明:l宏定义是用宏名来代替一个字符串,编译预处理程序对宏定义是用宏名来代替一个字符串,编译预处理程序对它不做任何检查,如果有错误,只能在已经展开宏的源它不做任何检查,如果有错误,只能在已经展开宏的源程序中发现。程序中发现。l习惯上宏名用大写字母表示,但也允许用小写字母。习惯上宏名用大写字母表示,但也允许用小写字母。l宏定义必须写在函数之外,作用域从宏定义命令开始直宏定义必须写在函数之外,作用域从宏定义命令开始直到源程序结束。到源程序结束。l宏定义允许嵌套,在宏定义的字符串中可以使用已经定宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。例:义的宏名。例:#definePI3.1415926#defineCIRCLE_L2*PI*r/*PI是已定义的宏名是已定义的宏名*/13.6.2文件包含文件包含预处理命令预处理命令#include我们比较熟悉,它可以把指定源文件的全部内我们比较熟悉,它可以把指定源文件的全部内容括入现有源程序文件中,它的一般形式是:容括入现有源程序文件中,它的一般形式是:#include文件名文件名“或或#include文件包含命令的功能:文件包含命令的功能:是把指定文件的全部内容括进来,插入到该命令行所在位置,取代该命是把指定文件的全部内容括进来,插入到该命令行所在位置,取代该命令行。由当前源程序文件和指定文件组成一个文件,一起编译。令行。由当前源程序文件和指定文件组成一个文件,一起编译。一个大的程序可以被分为多个模块,由多个程序员分别编写。公用信息一个大的程序可以被分为多个模块,由多个程序员分别编写。公用信息可以单独组成一个文件,在其它文件的开头用文件包含命令将其括入。这可以单独组成一个文件,在其它文件的开头用文件包含命令将其括入。这样既可避免在每个文件开头都去书写那些公用量,节省时间,又可避免书样既可避免在每个文件开头都去书写那些公用量,节省时间,又可避免书写手误,减少出错。写手误,减少出错。尖括号和双引号区别尖括号和双引号区别:尖括号只在省缺目录中查找指定文件,省缺目录由用户设置编程环境时设尖括号只在省缺目录中查找指定文件,省缺目录由用户设置编程环境时设定。双引号表示首先在当前源文件所在文件目录中查找,如果没有找到,则在省定。双引号表示首先在当前源文件所在文件目录中查找,如果没有找到,则在省缺目录中查找。缺目录中查找。13.6.3条件编译条件编译条件编译命令,使编译器能够按照不同条件编译不同的程序部分,产条件编译命令,使编译器能够按照不同条件编译不同的程序部分,产生不同的目标代码文件。下表列出条件编译命令:生不同的目标代码文件。下表列出条件编译命令:命令命令含含义义#if根据常量表达式根据常量表达式值值有条件地包含文本有条件地包含文本#ifdef根据是否定根据是否定义义宏名有条件的包含文本宏名有条件的包含文本#ifndef与与#ifdef命令相反的命令相反的测试测试,有条件包含文本,有条件包含文本#elif在在#if、#ifdef、#ifndef、#elif测试测试失失败时败时根据另一常量表达式根据另一常量表达式值值有条件包含文本有条件包含文本#else在在#if、#ifdef、#ifndef、#elif测试测试失失败时败时包含的文本包含的文本#endif结结束条件束条件编译编译这些命令的一般组合使用的方式有两种:这些命令的一般组合使用的方式有两种:)使用常量表达式判断;)使用常量表达式判断;)使用宏定义名判断。)使用宏定义名判断。使用常量表达式判断:使用常量表达式判断:使用常量表达式判断的条件编译的功能是,首先求常量表达使用常量表达式判断的条件编译的功能是,首先求常量表达式值,然后根据常量表达式值是否为式值,然后根据常量表达式值是否为0,进行下面的条件编译。设,进行下面的条件编译。设ie为整型常量表达式,使用常量表达式判断的条件编译形式是下述为整型常量表达式,使用常量表达式判断的条件编译形式是下述三种形式三种形式:#ifie文本文本1#elifie2文本文本2#else其余文本其余文本#endif#ifie文本文本1#endif#ifie文本文本1#else其余文本其余文本#endif使用宏定义名判断:使用宏定义名判断:使用宏定义名判断的形式是:使用宏定义名判断的形式是:#ifdef标识标识符符文本文本1#else文本文本2#endif#ifndef标识标识符符文本文本#endif“#ifdef标识符标识符”的意义是:的意义是:l如果定义了标识符为宏(即使宏体为空),则为真,编译如果定义了标识符为宏(即使宏体为空),则为真,编译#if后边的文本;后边的文本;l否则如果没有定义标识符为宏或者已经用否则如果没有定义标识符为宏或者已经用“#undef”命令取命令取消了标识符的宏定义,则为假,编译消了标识符的宏定义,则为假,编译#else后边的文本。后边的文本。 【例例13.22】条件条件编译编译例例#includestdio.h/*1*/#defineR1/*2*/#defineMAX(a,b)(a=b?a:b)/*3*/#defineMIN(a,b)(a=b?a:b)/*3*/#defineMIN(a,b)(a=b?a:b)/*3*/#defineMIN(a,b)(a=b?a:b)/*4*/main()/*5*/intx=0,y=0,t=0;/*6*/printf(Pleaseinput3differentintegers:);/*7*/scanf(%d%d%d,&x,&y,&t);/*8*/t=MIN(x,y);/*13*/printf(“MIN(%d,%d)=%dn”,x,y,t);/*14*/t=MAX(x,y);/*17*/printf(“MAX(%d,%d)=%dn”,x,y,t);/*18*/printf(cannotoutputn);/*27*/*29*/程序运行结果形式如下:程序运行结果形式如下:Pleaseinput3differentintegers:这时计算机等待输入三个整数,若输入;这时计算机等待输入三个整数,若输入;120则产生如下输出:则产生如下输出:MIN(1,2)=1MAX(1,2)=2cannotoutput运行结果演示运行结果演示条件编译和条件语句的区别条件编译和条件语句的区别:条件编译当然可以用条件语句来实现。但是条件编译当然可以用条件语句来实现。但是用条件语句将会对整个源程序进行编译,生成的用条件语句将会对整个源程序进行编译,生成的目标代码程序很长,而采用条件编译,则根据条目标代码程序很长,而采用条件编译,则根据条件只编译其中的某段程序,生成的目标程序较短。件只编译其中的某段程序,生成的目标程序较短。如果条件选择的程序段很长,采用条件编译的方如果条件选择的程序段很长,采用条件编译的方法是十分必要的。法是十分必要的。本章小结本章小结本章主要介绍了本章主要介绍了C语言独有的特性:语言独有的特性:函数指针函数指针函数作参数函数作参数间接递归间接递归函数副作用函数副作用 赋值运算赋值运算顺序表达式顺序表达式条件表达式条件表达式位运算位运算breakcontinuefor的延伸的延伸goto和标号和标号多维数组与指针多维数组与指针位段位段共用体共用体存储类别存储类别编译预处理编译预处理重点掌握:重点掌握:函数指针、函数作参数、函数指针、函数作参数、存储类别。存储类别。
收藏 下载该资源
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号