当前位置:首页 >> 工程科技 >>

c语言基础教程


完全免费版
第五章 函数和存储类 5.1.1 函数的定义 如果想调用一个函数完成某种功能,必须先按其功能来定义该函 数。函数定义的格式如下所示: <存储类说明><数据类型说明><函数名>(<参数表>) <参数说明> { <函数体> } 函数的定义可分两大部分:函数头和函数体。函数头包含(函数 名>和(参数表)以及关于该函数的(存储类说明)、《数据类型说 明),还有<参数说明).函数体是由一对花括号括起来的若干语句组 成的,函数体内可以有一条语句或多条语句,也可以有复合语句,还 可以是空,即该函数什么操作也不做,这是一个最简单的函数。(函 数名)的起法同标识符,能够通过函数名来标明该函数的主要功能为 最好。(函数名>后面跟一对圆括号,内有一个<参数表、,该'参数 表)由一个或多个参数构成,多个参数之间用逗号分隔,也可以没有 参数,但圆括号不可省略。定义瞬数时要对该函数的存储类和数据类 型进行说明。函数的存储类有两种:外部函数和内部函数。外部函数 用 extern 关键字加在函数名前面进行说明,常常省略,凡是不加存

储类说明的函数都是外部函数。 内部函数必须在函数名前面加 stai}c 关健字进行说明,内部函数又称静态函数。函数的数据类型是该函数 返回值的类型,函数的数据类型千一分丰富,一般 C 语言所允许的数 据类型大多都可作为函数类型,个别的除外,如联合类型等。C 语言 规定,数据类型中除了 int 型的可以不要说明外,其余各种类型都需说明。如果一个函数具 有参数时,则需对参数的类型进行说明。<参数说明)一般放在函数 名的下一行,有的编译系统(如 Turbo G)可以把参数说明放在参数 表中。 下面是函数定义的几个例子: Nothing() { }

该函数名字是 nothing,它没有参数,它的函数体是空,该函数 什么也不做。空函数可以用于调试中,它表明应该调用一个函数,但 该函数尚未编好,先用一个空函数顶替。 void nopa() { printf ("oK!\n"); } 该函数名是 nops,该函数没有参数,因此不必进行参数说明,这

里,void 关键字用来说明该函数没有返回值。该函数的函数体内仅 有一个语句。 float max(x, y) float x, y; { float z z 一 x>y? x:y: return(z); 该函数名是 max,它有两个参数 x,y,它们都是 float 型数,由于 该函数有参数,因此需要 参数说明:float x, y;该函数的数据类型为 float 型,这说明 该函数有返回值,而返回值的类型是 float 型。该函数。无存储类的 说明,这意味着该函数的存储类为外部的。该函数体是由 3 条语句组 成的。这里有说明语句,它被放在执行语句的前面。 在函数的定义中还应注意如下几个问题: (1)C 语言中的函数可分为有返回值和无返回值两大类。在有 返回值的函数定义中, int 型返回值外都必须对返回值的类型进行 除 说明。在无返回值的函数定义中,可以加上无返回值的说明符 void, 也可以不加。这样,对于一个不加数据类型说明的函数可能是无返回 值的,也可能是有 int 型返回值的。

(2)函数的定义不能嵌套。这就是说,不能在一个函数的函数 体内再定义一个函数。例如,下列写法是错误的: fl(x,y) int x, y; ( f2(a,b) int a,b; { Return(a 十 b); } 这种企图在 f1()函数中再定义一个 f2()函数的做法是错误的。 但是,函数的调用允许嵌套。这就是说,可以在一个函数体内调用另 一个函数,也可以进行自身调用(又称递归调用). (3)如果一个函数的函数体中有变量的定义或说明时,一定要 放在执行语句的前边,不可放在中间或后面,否则要出编泽错。 5.1.2 函数的说明 函数定义好后,在调用之前一般地要进行说明。函数的说明方法 有如下两种: 一是只说明函数的类型,这称为简单说明。例如,对前面定义过 的函数 max 说明如下:

float max(); 这种说明可以单独一行,也可以与其他同类型的变量放在一起。 二是不仅说明函数的类型还要说明其参数的类型, 这称为原型说 明。例如,对前面己定义过的函数 max ( )原型说明如一 F: Boat max (float,float); 原型说明要比简单说明复杂一些,但是用原型说明后,在函数调 用时系统将其参数的类型进行检查,如果发现不一致,则报错。如果 用简单说明方法,则在调用时不作参数类型是否一致的检查,即使不 一致也不报错,由此可见,原型说明比简单说明要安全些。 在实际使用中关于函数的说明还需注意如下几点: (1)在下列情况下,函数在调用之前不必说明:在定义函数时 没有加任何说明;或者是该函数无返回值义没有加 void 说明符;或 者是该函数具有 int 型返回值而省略说明符。 (2) 助在下列情况下,函数在调用之前必须说明,如不说明, 将出现编译错:即在定义函数时加了说明符,包含 void 在内,又是 先调用后定义(即调用在定义之前),则调用之前必须说明。

(3)在下列情况下,即在定义函数时加了说明符,包含 void 在 内。而先定义后调用(即定义在调用之前),则调用函数之前可以说 明也可以不说明。 有时为了使得系统能够检查函数调用时其参数类型是否一致,对 于不必须说明函数的情况也对函数进、 5.2.1 函数的参数 C 语言中,有的函数有参数,可以一个或多个,多个参数用逗号分 隔, 而有的函数没有参数, 但是函数名后面的圆括号不可省略, 例如, 前面讲过的主函数 main( )就是属于没有参数的函数。但是,主函 数也可以有参数,后面会讲到, 函数参数分为形式参数(简称形参)和实在参数(简称实参)两 种。形参是指在定义函数时,(参数表)中的参数,因为该参数在该 函数被调用之前是没有确定值的,只是形式仁的参数,只有被调用时 通过实参来获取值。实参是指调用函数的参数,因为该参数已具有确 定的值,是实在的参数:实参可以是表达式,在调用之前先计算出表 达式的值。 在实际应用中关于形参和实参的使用还应注意如下几点: (1)在定义函数时,所指定的形参在该函数被调用之前是不被 分配内存单元的。只有在发生函数调用时,才给形参分配单元,井且

赋值, 一旦函数调用结束后, 形参所占的内存单元又被释放掉。 因此, 形参属于局部变量,其作用范围仅在定义它的}J 数体内。 (2)函数调用时所用的实参是个具有确定值的表达式,调用时 先计算表达式的值,再将其值传递给对应的形参。 (3)函数的形参是属于定义它的函数的局部变量。因此,允许 一个函数的形参和实参同名,因为它们在内存中占有不同的存储单 元。 (4)函数调用要求形参和实参在个数上相等,并且对应的参数 类型相同。否则将会发生"个数不匹配"或"类型不一致"的错误。一般 情况下,即使不报错误信息,也会造成结果不正确。 下面举一个简单的函数调用的例子, 通过它进一步说明函数的定 义格式,函数的说明方法和形参与实参的不同。 [例 5. 1] 求两个 float 型数的和。 程序内容如下: main() { float a,b,sum; float fadd(); a=5.6;

b=7,2; sum=fadd(a,b); printf ("%.2f\n”,sum); } float fadd(x,y) float x,y { retutrt(x+y); 执行该程序输出如下结果: 12. 80 说明: (1)该程序中定义了两个函数:main ( )和 fadd ( ).前 一个是主函数,它无参数,也无返回值。后一个是具有 float 型返回 值的带有参数的函数,该函数有 2 个参数 x 和 Y,它们都是 float 型 变量。 (2)在主函数 main)中,float fadd ( );这是对函数 fadd () 的简单说明, 只说明函数的类型是 float 型 (即返回值的类型). 如果甩原型说明应采用如下格式: float fadd(#loat,float);

这种情况下,对 fadd < )函数必须说明,因为该函数返回值非 int 型的,并且又是调用在先定义在后。如不说明、则会出现编译错, 读者可以上机验证。 (3)该程序中,x 和 Y 是 fadd < )函数的两个形参,关于形参 类型说明的方法,除了本程序中说明方法外,还可以采用如下方法: float fadd<fioat x,float y) 即在参数表中就说明了参数的类型。但是,这种说明方法并不是 所有编译系统都允许。而 Tuiba C 编译系统是允许的。 该程序中, 和 b 是 fadd a () 函数的两个实参, 在调用该函数时, a 和 b 的值是确定的。 该程序中的函数调用时满足了形参与实参个数相等, 对应类型相 同的要求。 5.2.2 函数的返回值、 C 语言中,有的函数带有返回值,有的函数不带有返回值。 函数的返回值都是通过 return(表达式))语句来实现的。例 如,在本章例 5. 1 中,fadd()函数的函数体中的 return(x+y) 十妇;语句就是将表达式 x 十 Y 的值作为返回值,返回给调用函数 fadd ( )的值赋给变量 sum o 返回值的类型是该函数的类型。带有 返回值的 return 语句具体实现过程如下:

(1)先计算 return 语句中《表达式)的值, (2)根据函数的类型对哎表达式)的类型进行转换,使得(表 达式》的类型转换成为函数的类型; (3)将<表达式>的值和类型返回给调用函数,作为调用函数的 值和类型。一般情况下,要设置一个变量来接收这一返回值。 (4)将程序流的控制权交给调用函数,继续执行调用函数下边 的语句。 在不带返回值的,turn 语句中。不执行上述(1)至(3)步较, 只执行(4)步魏。 函数的返回值(简称函数值)的类型是在定义函数时指出的一般 情况下,除了返回值为 int 型的不要说明函数的类型外,对非 int 型 返回值的函数都应指出西数值的类璧。函数遭的类型和、turn 语句 中表达式的类型可能不一致,这时藉要转换,即将表达式的类型转换 成为函数值的类型,这种转换是自动进行的。下面通过一个实例说明 当函数值的类型与 return 语句表达式的类型不一致的情况时是如何 自动转换的。 [例 5.2 求] 两个输入的浮点数的和,最后的和用 int 型表示。 程序内容如下: main ( )

{ foat x,y, int sum; printf("Input x,y:“); scant(" %f%f,\&x&xy"); sum=add(x,y); printl ("sum=%d\n",sum); } addt(x,y) float x,y. { return (x+y); 运行情况如下: Input x,y:1,3,7,9 sum=9 说明: (1)该程序由两个函数组成,一个是 main()函数,它无参数, 也无返回值; 另一个是 add 函数, () 它有两个参数 x 和 Y}都是 float 型的。它有 int 型返回值。因此,定义该函数时可以不加类型说明, 调用该函数之前也可不加说明。

(2)在 main()中,x 和 Y 作为 addt)函数的实参;在 add ( ) 函数中,x 和 Y 作为 add 函数的形参。虽然名字相同,但它们在内存 中有着不同的存储单元。调用 add()函数时,其形参与实参是一致 的,即个数相等,对应类型相伺。 (3)在被调用函数 add()中,返回值的表达式 x 十 Y 的类型 是 float 型的,其值应为 i_. 3 十 7.9,即为 9. 2 0 由于函数 add 必 的类型为 int 型的,因此,要将 9. 2 自动转换为 int 型数 9,再返回 给调用函数,即赋值给 int 型变量 sum。 5.3.1 传值谓用的特点、 这里所讨论的传值调用是指传递变量值的调用方式。 在这种调用 方式中,实参使用变量名或者表达式,形参使用变量名。在调用时, 调用函数将实参值拷贝到一个副本给形参, 即使形参按顺序从对应的 实参中获得值,这就相当于将实参值对应地赋给形参,使形参获值。 这种调用方式具有如下特点: 被调用函数的参数值被改变而不会影响 调用函数的参数值,因此安全性好。 [例 5.3] 分析下列程序输出结果。 执行该程序输出结果如下: Main( ) ( float a,b;

void fl()" a=7. 2; b=3. 6; fl(a*b); printf ('"%,2f\n",a ,b) } void fl(x*y) float x.y; ( x+=0. 5; y 一=0.5; printf ("%.2f,%.2f\n",x,y); { 7.70,3.10 7.20,3.60 说明: (1)该程序由两个函数组成,一个是主函数 main ( ),另一 个是被 main( )函数调用的被调用函数 f1().在定义 fl()函数 时,使用了 void 说明该函数无返回值。因此,在调用 f1()函数之 前必须说明 flt)函数,因为调用在先定义在后。

(2)主函数中,调用 fl ()函数时,两个实参都是变量,即 a 和 b.被调用函数的两个形参也都是变量,即 x 和 y,这是属传递变量 值的传值调用方式。在被调用函数 fl()中,通过两个赋值表达式 语句分别对形参 x 和 Y 的值进行改变,并通过 printf ( )语句输 出改变后的 x 和 Y 的值:7. 70, 3 ,10.返回主函数后,执行 printf) 语句输出主函数中的 a 和 b 值为 7.20, 3.60.可见,a 和 b 值没有因 为调用函数 fl()而改变其值。尽管在 fl()函数中对其形参 x 和 Y 进行了改变。可见,这种传值调用方式使得被调用函数中参数值的 改变不影响调用函数的参数值, 其原因是因为这种调用方式是调用函 数将其实参值拷贝了一个副本给了被调用函数的形参, 被调用函数中 改变形参的值,只是改变了副本中的值,对调用函数中的"正本"没有 改变。 5.3.2 传址调用的特点 传址调用是指在调用时传递变量地址值的传值调用。 传址调用时 要求调用函数的实参用地址值,而被调用函数的形参用指针,于是函 数之间进行地址值的传递。 这种传递是将实参的变量地址值传递给形 参指针,即让形参指针指向实参变量,这种传递方式与调用函数拷贝 实参值的副本给形参是不同的,它是让形参指针直接指向实参的变 量。这种传址调用具有如下特点:被调用函数中可以通过改变形参所 指向的内容来改变调用函数的实参值: 这一特点与前面讲过的传值调 用是截然不同的。 传址调用可以通过改变形参所指向的内容来改变实

参值,这就提供了函数之间进行信息传递的又一渠道,并且这种传递 信息的方式还可以克服前面讲过的返回值方式的只传一个信息的局 限性。因此,传址调用在 C 语言函数调用中是经常采用的方式。后面 还会看到这种调月方式将会带来其他好处。 下面举一个传值调用方式和传址调用方式不同的例子, 并且将看 到这两种传递方式可在同一个 k}数调用中实现。 [例 5.4] 分析下列程序输出结果。 Main() { Int a,b,c, b=c=a=5; Fl(n,&b,&c); printf(f'%d,%d,''d\n",a,b); ) Fl(x,y.z) int x*y,* z: { X*-2 y 一一 x; *z=x 十* y: printf (" %d, %d,%d\n",x*Y,*z)

执行该程序输出如下结果: 10,15,25 5,15 ,25 说明: (1)该程序由主函数 main ( )和被调用函数 fl()组成,fl ()函数无返回值,由于定义 fl()时没有加任何类型说明符,尽 管调用在先定义在后也不必说明。 (2)在调用函数 fl()时,3 个实参中,一个是用变量名,另 外 2 个用变量的地址值;在对应的形参中,一个是用变量名,另外 2 个用指针。在调用 fl()函数中,有一个参数属于传值调用,2 个参 数属于传址调用。在被调用函数 f1()中,改变了变量 x 的值,由 于传值调用对调用函数对应实参 a 的值并没有被改变, 这是传值调用 的特点。同时,通过用运算符,对指针 y 和:取其内容,并且通过赋 值表达式语句来改变了 x 和 Y 的内容,于是调用函数中对应的实参 b 和 c 的值被相应改变,这便是传址调用的特点。 该例程序中值得注意的是要在传址调用中通过被调用函数来改 变调用函数的参数值时,一定要改变形参指针所指向的内容,而不能 只改变形参的地址值, 改变形参地址值是不会影响对应实参的变量值 的。下面的例子将说明这一点。

[例 5.5] 分析下列程序的输出结果。 main ( ) { int a,b,c,d; a=b==c=d=5; f1(&a,&b); f2(&c,&d), printf("%d,%d,%d,%d\n,a,b,c,d); } fl(x,y) int *x,*Y} { *x*=2; y+=*x; printf("%d,%d\n,*x,*y); } f2(m,n) int *p,*q; p=q=8; p=q=8; m=&p n=&q

printf("%d, %d\n",*m,*n); } 执行该程序输出结果如下: 10,15 8, 8 to,15,5 ,5 说明: (1)该程序由三个函数组成,除了主函数 main()外,还有二 个被调用用函数 fl()和 f2()。这两个被调用函数都是没有返回 值的。在 main ( )函数中,先调用 f1()函数,再调用 f2()函 数。 (2)在调用 fl()函数时采用了传址调用方式,即实参用变量 a 和 b 的地址值&a 和&b,形参用指针 x 和 Y.在 fl()函数中,通过 使用取内容运算符改变了指针 x 和 Y 所指向的变量的值 (即改变了指 针所指向的内容).因此,fl()函数被调用后,土函数中变量 a 和 b 的值发生了变化,这是通过传址调用改变调用函数参数值的又一个 例子。 (3)在调用 f2()函数时也采用了传址调用方式,即实参用变 量。和 d 的地址值&c 和&d,形参用指针 m 和 n.在 f2()函数中,形 参 m 和 n 的地址值发生了变化,使 m 和 n 指向了变量 P 和 q,于是 m

和 n 所指向的内容将是 P 和 9 变量的值 8.这时, 如果要再改变 m 和 n 所指向的内容,只是改变变量 P 和 q 的值,也不会影响调用函数的参 数值。因此,简单地说"传址调用中通过改变形参的值来改变调用函 数中参数的值"这句话是不确切的。一定要指出在被调用函数中通过 改变形参所指的内容才能改变调用函数中实参的值。 5.3.3 数组名作参数的函数调用 数组元素可作为实参,实现传值调用,这与变量名作实参一样, 都是单向传递的。 数组名作函数实参与数组元素作实参是不同的, 因为 C 语言规定 数组名是一个地址值,即是该数组首元素的地址值。因此,数组名作 实参时,要求形参也是数组名或者指向数组的指针,关于指向数组的 指针将在下一章"指针"中讲解,这时实现的不是传值调用,而是传址 调用。 调用函数不是将整个数组的所有元素拷贝成副本传递给被调用 函数,而是只将其数组的首元素地址值传给形参数组,于是这两个数 组将共同占用同一段内存单元, 这就是让形参数组的首元素地址与实 参的首元素地址相同,使得这两个数组的对应元素同占一个内存单 元。因此要求这两个数组要类型相同,数组的大小可以一致,也可以 不一致。如果要使形参数组得到实参数组的全部元素,则形参数组与 实参数组应大小一致。 下面举例说明用数组名作函数参数的调用方法。

[例 5. 6] 编程对某一数组中的各个 int 型数进行由小到大的排 序。 本例中采用算法简单的选择排序法。程序内容如下: main () { void sort(); static int a[8]={5,一 2 .9,87,0,6,21.49:}; Int i n: Sort(a.n); for(i=O;i<8;i 十十) printf (" % 4d" , a [i ] ); printf ("\n"); } void sort(b,m) int b[],m; { int i,j,k,temp; for(i=O;i<m 一 1;i 十+) { k=i; for(j=i+1;j<m;jj++)

if(b[j]<b[k]) k=j; temp=b[]; B[k]=b[i]; b[i]=temp; } } 执行该程序输出结果如下: 一2 说明: (l)该程序是通过调用 sort( )函数对已知数组 a 中的 8 个 int 型数进行由小到大排序。被调用函数 sort()是没有返回值的。 (2)主函数中调用函数格式如下: sort(a,n) 其中,实参 a 是一个已知的数组名,该数组是一个一维数组,8 个 int 型元素。实参 n 的值为 8.可见,sort ()函数的两个实参中, 一个是地址值,另一个变量值。 0 5 6 9 21 49 87

被调用函数 sort( )的两个形参中,一个是数组名 b,它是一个 没有指定大小的一维 int 型数组,其类型与数组 a 相同,其大小将与 所对应的实参数组大小相等。另一个形参是变量 m.因此,不难看出 该函数调用中, 第一个参数是属于传址调用, 第二个参数是传值调用。 在传址调用中,将数组 a 的首元素地址传给 b 数组,使得 a,b 两个数 组共占同一段内存单元。由于是传址调用不必拷贝数组元素的副本, 因此,效率较高,节省了时间和空间的开销。这是传址调用的又一好 处。 由于 a,b 数组共占同一段内存单元,因此在。rt()函数中,通 过 b 数组元素的改变,而使得数组 a 的元素发生了变化,于是完成由 小到大的排序。 (3)在函数 sort( )中,选择排序法的算法是这样的:通过 双重 for 循环,每作一次外重 for 循环,从指定的数中挑出最小的一 个放在指定的元素位置,例如,第一次外重 for 循环,从 5 个数中挑 出最小的放在第。个元素的位置,第二次外重循环从剩下的 7 个数中 再挑出最小的放在第 1 个元素的位置,依次类推,外重 for 循环共进 行 7 次,便将 8 个数的顺序由小到大排好。内重 for 环是用来确定在 要查找的数中找出最小数所对应的下标值, 并将它赋给 k 变量。 然后, 通过三个赋值语句将数值最小的那个元素换到待选数中的最前边。 即 下标值为 i 的位置。这种排序方法每次找出一个最小的数放在前边, 直到最后剩下一个数为止。

5.3.4 函数的嵌套调用、 所谓函数的嵌套调用是指在调用一个函数的过程中,又调用另外 一个函数。例如,在调用 A 函数的过程中,还可以调用 B 函数,在调 用 B 函数的过程中,还可以调用 C 函数,?当 C 函数调用结束后返回 到 B 函数,当 B 函数调用结束后返回到 A 函数,当 A 函数调用结束后 再返回到 A 的调用函数中。假定 main( )调用 A 函数,上述的嵌套 调用关系可用下图所示:

图中①②③一表示了执行嵌套过程的顺序号。即从①开始,经过 了三级嵌套,到⑩结束。 这里还需重申一遍,C 语言中函数的定义不允许嵌套,就是说不 允许在函数中定义函数,C:语言程序中若干个函数都是平行的、独立 的。函数之间是通过调用联系的。函数的调用是允许嵌套的,就是说 在调用某个函数的过程中,还允许调用其他函数。 下面举一个函数嵌套调用的例子,通过该例搞清调用、返回之间 的关系。 [例 5.7」分析下列程序的输出结果。

main() printf ("I'm in main. `,n"); a(); printf ("I'm finally back in main. \n"); } a() printf<"Now,I'm in a /\n") b(); printf ("here,I'm back in a.\n"); } b() { printf ("Now,I'm in b.\n"); c(); printf ("Back in b.\n") C() printf("Back in b.\n"); { c() { printf("And now,I'm in c,\n”): }

请读者分析该程序后,写出输出结果。 [例 5.8] 编程求出下式之和。 lk+2K+3k+??+nK 假定 k 为 4,n 为 6,编写求上述和的程序如下: # define K 4 t}define N 6 main ( ) printf("Sum of%dth powers of integers from 1 to%d=",k,N); printf ("%d\n" ,sum 一 of_powers (K,N)); } sum of 一 powers(k,n) int k,n; { int i,sum=0; for(i=1 i<=n;i++) sum+=powers(i,k); return (sum ), ; powers (m.n ) int m,n

{ int i,product=1; for(i=l;i<=n;i++) product*=m: returnCproduet); ) 执行该程序输出结果如下: sum of 4th powers of integers from 1 to 6=2275 说明: (1)该程序由 main ( ) ,sum-of-powers)和 powers)三个 函数组成,main ( )中调用 sumof-powers ( ) 函 数 , 该 函 数 返 回 一 个 int 型 数 值 , 而 sum-of-powers ()函数中又调用 powers ( )函数,该函数也返 回一个 int 型数值。从中可见,函数之间的嵌套调用在实际编程中是 经常使用的。 (2)在主函数中,调用,sum-of-powers ( )函数时,实参是 两个符号常量 K 和 N.可见符号常量与一般常量一样都可作为函数的 实参。本程序中的两次函数调用都属于传值调用,硒数之间的信息传 递是通过返回值来实现的。

5.3.5 函数的递归调用 c 语言中允许使用函数的递归调用,这是 C 语言的又一特点。所 谓函数的递归调用是指在调用一个函数的过程中出现直接地或间接 地调用该函数自身。例如,在调用 f1()函数的过程中,又出现了 调用 f1()函数,这称为直接调用;而在调用 fl()函数过程中出 现了调用 f2()函数,又在调用 f2()函数过程中出现了调用 f1() 函数,这称为间接调用。 1.递归调用的特点 实际中不是所有的间题都可以采用递归调用的方法。 只有满足下 列要求的问题才可使用递归调用方法来解决: 能够将原有的问题化为~一个新的问题,而新的问题的解决办法 与原有问题的解决办法相同,按这一原则依次地化分下去,最终化分 出来的新的问题可以解决。 实际中有意义的递归问题都是经过有限次数的化分, 最终可获得 解决。这是有限递归问题,而那些无限递归问题在实际中是没有意义 的。下面举一个有限递归的例子。例如,求 5!由于 5!可以化为 5*4!. 而 4!又可化为 4*3!,而 3!可化为 3,2!,而 2:可化为 2*1!,最后,1! 可化为 1* 0!,而 0!是已知的,即为 1.于是,可知 5!等于 5*4*3*1*1 即 120.这是一个简单的典型的递归调用的例子。

使用递归调用方法编写程序简洁清晰,可读性强。因此,人们都 喜欢用递归调用的方法来解决某些问题。但是,用这种方法编写的程 序执行起来在时间和空间的开销上都比较大, 即要占用较多的内存单 元,又要花费很多的计算时间。因为递归调用时要占用内存的许多单 元存放"递推"的中间结果,较复杂的递归占用内存空间较多。因此, 在一些内存小速度慢的小机器上最好不要采用递归调用的办法,不 然,效率很低。一般的凡是可用递归调用方法编写的程序都可以用迭 代的方法来编写。一般说来,相同的间题用迭代方法编写要比用递归 调用方法编写的源程序长些。 2.递归调用的过程 递归调用的过程可分为如下两个阶段: 第一阶段称为"递推"阶段:将原问题不断化为新问题,逐渐从未 知的向已知的方向推测,最终达到已知的条件,即递归结束条件。 第二阶段称为"回归"阶段:该阶段是从已知条件出发。按"递推" 的逆过程,逐一求值回归,最后到递推的开始之处,完成递归调用。 可见,"回归"的过程是"递推"的逆过程。 下面以求 5!为例写出递归调用的全过程如下所示:

从上述列举的递推过程中可以看出, 有实际意义的递归应该是有 限的,即递推若干次后。将出现已知条件。并且每递推一次都是向已 知条件接近一步。这里的已知条件就是递推结束条件。上例中,0!=I 是已知条件,即递推结束条件。在回归的过程中,是按照递推的逆过 程进行的,最后得到原问题的解。 [例 5.9」用递归调用方法编程求某个正整数的阶乘。 程序内容如下: main () { int n; scant("%d",&n); printf (" %d\n",fac(n)); } fac(n)

int n { int p; if(n==O) p=1; else p=n*fac(n 一 1); return (p); 执行该程序,从键盘上输入一数后,输出结果如下: 5 120 说明:该程序由主函数 main)和 fac()函数组成,fac<)函数 中采用了递归调用的形式,即在 fac(n)函数中调用 fac(n-1). 该递归结束条件时,n 等于 0,P 值为 1,每递归一次 n 减 1,经过若干 次递归后,n 会为 0。 [例 5.10」汉诺(Hanai)塔间题。这是一个典型的递归调用问 题。该问题描述如下:在一张桌子上有 A,B,C 三处,在 A 处有 n 个盘 子,每个盘子大小不等,大的在下小的在上。要求将 A 处的 n 个盘子 移到 C 处,可以借助于 B 处,每次移动只允许动一个盘子,在移动过 程中在 A,B,C 三处都应保持大盘在下,小盘在上。编程打印出移动的 过程。

分析算法如下: 该题目使用递归调用的方法编程。 n 个盘子从 A 处移到 C 处可 将 分为如下三个步骤: (I)将 A 处的 n-1 个盘子借助 C 处移到 B 处。 (2)把 A 处剩下的一个盘子移到 C 处。 (3)再将 B 处的 n-1 个盘子借助于 A 处移到 C 处。 这样完成了 n 个盘子的移动。 分析上述的三步操作可以发现, 1 步和第 3 步所采用的方法是 第 一样的,都是将 n-1 个盘子从某一处移到另一处,只是移动的位置不 同。因此,可将上述的三步骤化简为如下两步骤: (1)将 n 一 1 个盘子从一处移到另一处。 (2)将一个盘子从一处移动另一处。 把这两个步操作分别用两个函数来描述。第一步操作用 move_n ()函数来实现,第二步操作用二 ove_1()函数来实现。 move_n<)函数有 4 个参数,分别为 n,a,b.c,表示"将 n 个盘子 从 A 处借助 B 处移到 C 处". 该函数具体定义如下:

void move_n(n,a,b,c) ant n; char a,b,c; { if (n==1) move 1(a ,c); else { move n (n 一 l,a.c,b); mnve_ 1(a,c); move_n(n 一 1,b,a ,c); } } move_n()函数有 2 个参数,一个是 from,另一个是 to,表示将 i 个盘子从 fom 处处。该函数具体定义如下: int m void move_1(from,to) char from,to: { if (m%8==0) printf ("\n"); printf("%c=>%c",from,to);

m++; } 该程序主函数内容如下 void move_ 1(). move_ n(), main() { int d; printf ("Input the number o# diskes:"); scant("%d",,&d); printf ("The steps to moving%4d dikes:\n"); move 一 n(d,'n',B',C.); printf ("\n" ); 执行该程序输出如下信息: Input the number of diskes:3 "the stops to moving 3 diskes: A-C A-B C-B A-C B-A A-C 再运行一次该程序: input the number of dishes:4 'C'he sreps to moving 4 diskes:

A-B A-C B-C A-B C-A C-B A-B A-C B-C B-A C-A B-C A-B A-C B-C 再运行一次该程序: Input the number of diskes:5 The steps to moving 5 diskes: A-C A-B C-B A-C B-A B-C A-C A-B C-B C-A B-A C-B A-C A-B G-B A-C B-A B-C A-G B-A C-B C-A B-A B-G A-C A-B C-B A-C B-A B-C A-C 说明: (l.)该程序由 3 个函数组成:main(),MOVE_1()和 move_ n ( ).主函数 main ()中调用 move_n()函数,在 xnove_n()函 数中又调用 move_1( )函数,由于 move_ 1( )函数和 move_n() 函数在定义时使用了 void 来说明它们是无返回值的, 因此, 如果 main ( )在前,move _ n ( )和 move _ 1( )函数在后,则调用后 两个函数之前要说明,该程序将说明放在文件头,即主函数的前面。 (2) move_n 函数采用了递归调用方法, () 即在 move_n (n,""") 函数体中两次调用 move _ n(N 一 1,一)函数。该问题采用递归调 用方法编程是很简洁明了的。用其他编程将是十分复杂的。

(3)本例中三次运行这个程序,给出了输入分别为 3 个盘子、4 个盘和 5 个盘子的三组输出数据,表明了三种移动的过程。 5.4.1 标识符的作用城规则 标识符的作用域规则描述如下: 标识符只能在说明它或定义它的函数体或分程序内是可见的, 而 在该函数体或分程序外则是不可见的。 现将这段描述说明如下: (1)大多数的标识符对它说明或对它定义是一回事,只有少数 的标识符对它说明或对它定义是两回事,例如,外部变量和函数。 (2)标识符包含了变量、函数、语句标号、符号常量等名字, 凡是用标识符规则定义的各种单词都属于标识符。 (3)可见的指的是可以进行存取、访间,可见时对它的改变是 有效的,不可见的是指不能对它进行存取、访间及其他操作。可见的 与存在的是两个不同概念: 可见的是指在其作用域内, 对它可以操作; 存在的是指其寿命,仍存放在内存内,没有被释放。可见的一定是存 在的,不存在一定不可见,但是存在的不一定都可见,有的标识符它 虽然存在,但不可见。后面在存储类中会详细讲到,内部静态变量有 时不可见了,但它仍然存在。

各种标识符的作用域是不相同的, 有的是程序级, 有的是文件级, 还有函数级和程序段级。下面列举出一些标识符的不同作用域级别。 函数有程序级的,如外部函数;有文件级的,如内部函数。 变量中,外部变 l 孜属于程序级,外部静态变墩属于文件级,函 数形参属于函数级。 还有定义或说明在函数内的自动变量和内部静态 变量以及寄存器变晕都属于函数级的,定义在分程序内的自动的、内 部静态的和寄存器的变量属于程序段级的语句标号属于函数级的。 语句标号属于函数级的。 符号常量属于文件级的。 这里所说的程序级的是指作用范围(即作用域)在整个程序内, 包含该程序的所用文件。同样级的是指作用范围在定义它的文件中, 往往是从定义时开始。函数级的是指作用范围在定义它的函数体内; 程序段级的是指作用域在定义它的分程序内。 5.4.2 重新定义变量作用域规定 C 语言中,一般说来变量不能重复定义,这是指在相同的作用域 内,不能有同名变量存在。 但是。 在不同的作用域内, 允许对某个变量进行重新定义。 例如, 在某个函数体内定义了一个 int 型变量 a,这时不可以在同一个函数 体内再定义一个 float 型变量 ao 但是,可以在该函数体内的某个分

程序中,对变量 a 重新定义,于是在整个函数体内的不同分程序中将 出现同名变量。这时,它们的作用域又是如何规定的呢?关于重新定 义的变量的作用域规定如下。 在函数体或分程序内可以各自定义变量, 在函数体内又允许嵌套 分程序。在函数体或外层分程序中定义的变量。如果在内层分程序中 没有重新定义,则在内层分程序中将仍 s}右效;如果在内层分程序 巾进行了重新定义,则外层中的该变敏被隐藏起来,而在内层中起作 用的是内层重新定义的变量, 当退出内层程序又回到外层分程序或函 数体内时,外层定_义的该变量又恢复出来,仍然起作用。 下面通过一个程序例子来理解和体会上述规定的含义, [例 5.11〕分析下列程序的输出结果。 main ( ) { int a=2,b=4,c=6 printf("%d,%d,%d\n",a,b,c); { int b=8: float c=8. 8; printf("%d,%c,%,1f\n",a,b,c); a=b;

{ Int c; c=b: printf("%d,%d.%d\n.a,b,c); } printf("%d,%d,%,1f\n" ,a,b,c); } printf("%d,%d,%d\n",a,b,c) 执行该程序输出结果如下: 2,4,6 2,8,8. 8 8,8,8 8,8,8. 8 8,4.6 说明:该程序仅有'个主函数 main( ),该函数中定义了 a, b,. 三个 int 型变量,并斌了初值。函数体内包含了一个分程序。该分程 序内,重新定义了变量 b 和 c,并赋了初值,这时函数体内定义的 b 和。在这里被隐藏起来,而重新定义的 b 和 c 在起作用,这时输出的 a 值是原来的,b 和 c 是重新定义的。该分程序内,对 a 重新赋值, 这不是重新定义, 只是改变了 a 的值, 因为 a 在这里是可见的。 然后, 在该分程序中,又定义了一个分程序,称内层分程序。在内层分程序

中,重新定义了。,并给它赋了值,在这里,开始定义的 a 仍然可见, 开始定义的 b 仍被隐藏,外层分程序中重新定义的 b 是可见的,这里 定义的 c 在起作用,外层分程序定义的。也被隐藏起来。当退出内层 分程序后,开始定义的 a 和外层分程序定义的 b 和。是可见的。这时 在内层分程序中,被隐藏的 b 和 c 被恢复了。当退出外层分程序后, 开始定义的 a 和 b,c 都可见的。而被重新定义的 b 和 c 不可见了。值 得注意的是变量 a 在整个函数体的任何部分都是可见的,因此,不论 在何处它的值被改变后,则仍然保留其改变后的值。 5.5.1 变量的存储 C 语言规定变量的存储类分为如下 4 种: (1)自动存储类变量(auto); (2)寄存器存储类变量(register); (3)外部类变量(extern); (4)静态类变量(static). 下面从两个方面来讨论不同存储类的特征,一是作用域和寿命, 二是初始化。 1.不同存储类变蚤的作用域和寿命 (1)自动类变量的作用域是在定义它的函数体或分程序内,一旦 退出了该函数体或分程序,则是不可见的。这类变量的寿命是短的,

它被存放在内存的动态存储区内。 每次进人定义它的分程序或函数体 内被动态分配存储区域,一旦退出该分程序或函数体后,所占用的内 存区域被释放掉,即不存在了。 自动类变量一定出现在函数体或分程序内, 自动类变量的存储类 说明符是 auto.多数情况下该说明符被省略。前面讲过的程序中凡在 函数体或分程序内出现的没有加存储类说明符的都是自动类的。 总之,自动类的特点是作用域小、寿命短,可见性和存在性是一 致的口即可见时即存在,一旦不可见了,也就不存在了。 (2)寄存器类变量的作用域和寿命与自动类变量相同,即作用 域是在定义它的函数体或分程序内,寿命是短的。这类变量与自动类 变量的区别在于寄存器类变量有可能被存放在 GPU 的通用寄存器中。 如果这类变量数据被存放到通用寄存器中, 则将大大提高对数据的存 取速度。到底所定义的寄存器类变量能否被存放到通用寄存器中,这 取决于当时 CPU 是否有空闲的通用寄存器。 如果没有被存放到通用寄 存器中, 则按自动类变量处理。 在一个程序中, 定义寄存器类变量时, 应注意如下几点: ①定义的寄存器变量的个数不能太多, 因为空闲的通用寄存器数 目是很有限的。 ②由于通用寄存器的数据长度的限制, 一般定义为寄存器变量的 数据类型为 char 型或 int 型。数据长度太大的数据通用寄存器放不

下。 ③通常是选择那些使用频度较高的变量被定义为寄存器类, 这样 将有利于提高效率。例如,在多重循环的程序中,选择最内重循环的 循环变量定义为寄存器类变量,因为它使用频度高。 (3)外部类变量的作用域是整个程序。包括组成该程序的所有 文件。 在一个多文件组成的程序中, 在某个文件内定义了外部类变量, 它在任何一个文件中都是可见的。因此,外部类变量的作用域最大。 外部变量的寿命是长的,因为外部变量被存放在内存的静态存储区。 某个程序的外部变量的寿命直到该程序结束才被释放。因此,可以说 外部类变量的可见性与存在性也是一致的, 这就是外部类变量作用域 大,寿命也长,这是外部类变量的特点。由于这一特点决定了外部类 变量可以作为函数之间传递信息的一种方式。这种传递方式很方便, 只要在程序中定义一个外部变量, 它无论在该程序中的哪个函数内被 修改,则被修改的值要一直保持到其他函数中,将改变值传给另一函 数。但是,函数之间使用外部类变量传递信息不够安全。一旦产生误 修改, 则会造成不良后果。 因此, 在实际编程中尽量少用外部类变量: 回顾讲过的函数之间传递信息有如下三种方式: ①返回值方式,安全可靠,但只能返回一个值。 ②传址调月方式,既安全又可传递多个值。 ③外部类变量方式,虽可传递多个值,但是不安全。

(4)静态类变量又分为内部静态类和外部静态类两种。内部静态 类的作用域同于自动类,即在定义它的函数或分程序内是可见的。该 类变量的寿命是长的,同于外部类,即这类变量被存放在内存的静态 存储区。这类变量被定义后,它将一直存在到程序结束才被释放。外 部静态类变量的作用域是在定义它的文件中,并且从定义时开始。可 见,这类变量的作用域是介乎于外部类和自动类之间。它的寿命同内 部静态类,是长的。综观静态类变量可以看出,它的作用域和寿命, 即可见性和存在性是不一致的,这便是静态类变量的特点。这就说, 这类变量作用域并不是很大,但是它的寿命却是长的。虽然这类变量 在它的非作用域内是不可见的,但它们却是存在的。比如,内部静态 类变量当退出定义它的函数体或分程序后,便是不可见的,但是它却 是存在的, 它所占用的内存空间不被释放, 所保存的数据也不被改变, 直到程序结束才被释放。 内部静态类和外部静态类的区别仅在于作用 域上,前者小些,后者大些。 2.不同存储类变 1 的定义或说明方法 除了外部类变量的定义和说明不是一回事外, 其他存储类变量的 定义和说明是一回事。 (1)自动类变量的定义或说明方法

定义自动类变量应在函数体或分程序内, 定义时可以在变量名前 加说明符 auto,多数情况下是 aut.省略。 凡是在函数体或分程序内定 义的变量,前边加 aut.或不加 auto 的一律为自动类变量。 (2)寄存器类变量的定义或说明方法 定义寄存器类变量时在变量名前一定要加说明符 register.凡是 在程序中看到变量名前面加有 register 的一律为寄存器类变量。 (3)静态变量的定义或说明方法 定义静态类变量时在变量名前一定要加说明符 static.内部静态 类变量定义在函数体或分程序内;外部静态类变量定义在函数体外, 即在某个文件中,在文件首或文件尾都可以,也可以在文件中间,但 是它的作用域是从定义时起。可见,内部静态类变量和外部静态类变 量从定义方式上的区别仅在于是在函数体内定义还是在函数体外定 义。 (4)外部类变量的定义和说明方法 外部类变量的定义和说明是两回事。 外部类变量的定义方法是在 函数体外部不加存储类说明符,这便是外部类变量。自动类变量的定 义也可不加存储类说明符 auto,但它与外部类变量的区别在于外部类 变量在函数体外,自动类变量在函数体内。

外部类变量在引用前一般要进行说明。 说明的方法是在变量名前 面加上说明符 extern.这种说明可以放在函数体内,也可放在函数体 外,如文件开始,只要在引用之前说明即可。有时引用外部类变量也 可以不说明,但是在下面两种情况下引用外部类变量必须说明: ①在一个多文件的程序中,某个文件中定义了外部类变量,如果 要在另一个文件中引用,则引用前必须说明。 ②在一个文件中, 如果是先引用外部类变量, 后定义外部类变量, 则引用前必须说明。因为外部类变量只要求定义在函数体外,可以定 义在文件头,也可以定义在文件尾,还可以定在文件中间。对于那些 定义在后,引用在前的外部类变量,则需要说明。 值得注意的是对外部类变量说明时用 extern 说明符,定义时不 用存储类说明符,只要求定义在函数体外。对一个外部类变量,只能 定义一次,但可说明多次。 3.不同存储类变量的初始化 所谓初始化是指在定义变量时赋初值。 对于一般变量定义时都可以赋初值。 对自动类和寄存器类变量定 义后在没有赋初值或赋值之前,其值不可使用,因为它是无意义的数 据。对外部类和静态类变量定义后在没有赋初值或赋值之前,它们有 默认值,对 int 型变量为。, 对浮点型变量为 0,对 char 型变量为空。

对于外部类或静态类变量赋初值是在编译时进行的。 而自动类变量是 在运行中赋初值。 对于数组只有外部类和静态类的才可以赋初值。 一般编译系统不 能给自动类数组赋初值。寄存器类的没有数组。 4.全局变量和局部变量 变量从作用域来区分可分为全局变量和局部变量。 全局变量是指作用域在文件级以上的, 它包含有外部类变量和外 部静态类变量。 局部变量是指作用域在函数级以下的,它包含有自动类变量、寄 存器类变量和内部静态类变量。另外,函数的形参也属于局部变量, 因为函数形参的作用范围只在函数体内。 5.不同存储类变全特性的小结 将不同存储类变量的特性总结如下:

下列通过一些例子进一步说明各种不同存储类变量的定义方法 和特性

[例 5. 12] 局部变量的定义和应用。 分析下列程序的输出结果。 main ( ) { int a; register int b; starC int c; a=5; b=7, priatf("a=%d,b=%d,c=%d\n,a,b,c); other(); other(); printf("a=%d.b=%d,c=%d\n",a,b,c); } other( ) { int a=5; static int b=10; a 十=10; b 十=20; printl} ("a=%d,b=%d\n",a,b>。

}

说明; (1)在该程序的主函数 main)中定义了自动类变量 a,寄存器类 变量 b 和内部静态类变量 c.对 a 和 b 分别赋了值,对。没有赋值, 用 printf ( )语句输出变量 a, b,c 值时,c 变量使用的是默认值 读者可以去掉给 a 和 b 斌值的两个语句,再看一个输出结果,或许对 无意义的值能有所了解。 (2)在该程序的被调用函数 other()中,定义了一个自动类 变量 a 和内部静态类变量 b,虽然变量名与主函数中的相同,但是由 于它们的作用范围在各自的函数体内,相互没有影响,这是允许的。 在第一次调用 other()函数时,输出 a 和 b 的值分别为 15 和 3Q. 由于这里变量 b 是内部静态类的,其寿命是长的,在退出 other() 函数后仍保存其值不被释放,这便是它与自动类变量的区别。由于 b 变量的值被保存,在第二次调用 other()函数时,变量 a 被再次定 义并被初始化,它的值仍与第一次调用时一样,变量 b 不再定义和初 始化,因为静态类变量仅在编译时初始化。因此,b 保留上次调用后 的值,再经过 b 十=20;语句运算,其值为 5Q.从该例中可以看到 自声类变量与内部静态类变最的区别。 [例 5.13] 全局变量和局部变量的应用,

分析下列程序的输出结果 main () { extern int i int a=0, static int b; printf("i=%d,a=%d,b= %d\n",i,a,b); a+=5; other(); printf(""i=%d,a=%d,b= %d\n",i,a,b), i+=10; other(); } int i=7; other() { static int a=2; int b=5; i+=5; a+=3; b+=1, printf(""i=%d,a=%d,b= %d\n",i,a,b),

b=a; { 执行该程序输出结果如下: i=7,a=0, b=0 i12,a=5,b=6 i=12,a=5,b=0 i=27,a=8,b=6 说明: (I)关于外部类变量的定义和说明从该程序中可以清楚地看到 由于外部类变量 i 定义在主函数的后面, 在主函数中要引用变量 i 时, 必须先用 extern int i;语句进行说明。变量 i 在该程序中的两个函 数中都是可见的。因此,L 的值无论在主函数中被改变或者在 other ()函数中被改变都是有效的,都对后面操作有影响。 (2)关于内部静态变量的作用显然与自动类变量是不相。同的。 在 other()函数中,a 是内部静态类变量,b 是自动类变量,它们 都是 int 型的。第一次调用 other()函数时,输出 a 值为 5,b 值为 fi,第二次调用 other()函数时,输出 a 值为 8,但 b 值仍然为 6.其

差别就在于 a 变量的寿命长,退出 other()函数后被释放。而 b 变 量的寿命短,退出 other()函数后被释放。 [例 5. 14] 多文件程序中变量的作用域。 分析下列程序的输出结果。 int i VOId funl(),fun2(),fun3(), main() { i=33; funl(); printf ("main ():i=%d\n",i); fun2()‘ print( ("main():i=%/d\n",i>; fun3(); printf("main():i= %d\n",i); } 文件 2(fun1.c): static int i ; void funl() { i=100;

printf (" funl();i(static)=%d\n" ,i); ) 文件 3(fun2. c) void fun2() { int i=5; print(("fun2():i(auto)= %d\n",i>; if(i) { extern int x; printf ("fun2() ; i (extern)=%d\n",i); } } extern int x; void fun3() { i=20 printf ("fun3 ():i(extern)=%d\n",i)。 if(i) { int i=10; printf("fun3():i(auto)=%d\n");

} }

执行该程序翰出如下结果: funl():i(static )=100 main ():i=33 fun2():i(auto)=5 fun2( ):i(extern)=33 main():i=33 fun3 ( ):=i(extern)=20 fun3 ( ):i(auto)=10 main(),i=20 说明: (1)该程序是由三个文件、四个函数组成的。文件 main. c 中 包含了主函数 main(),井定义了一个外部类变量 i.在该主函数中, 分别调用 funl(),fun2()和 fun3()函数。文件 funl 中包含了 funlf)函数,在该文件开头定义了一个外部静态类变量 i.文件 fun2 中包含了两个函数=fun2<)和 fun3()o 在 fun2()函数中,重新 定义了变量 i 为自动类变量, 在一个分程序中又说明了 i 是外部类变 量。在函数 iun3()定义前说明了 i 是外部类变量,这里的说明可

以省略,因为该文件前面已说明过 1 是外部类变量,并没有再重新定 义。 (2)该程序中主要是应搞清楚变量 l 在不同地方它的存储类是 哪一种。 在 main ( )中,i 是外部类变量。 在 fun1()中,i 是外部静态类变量。 在 fun2()中,i 先是自动类变量,后是外部类变量。 在 fun3()中,i 先是外部类变量,后是自动类变量。 5.5.2 函数的存储类 函数的存储类分两种,一种是外部类,另一种静态类。 1,外部函数 外部类的函数称为外部函数,它的定义格式如下: [extern]<数据类型说明><函数名>(<参数表>) (参数说明) { <函数体> }

一般情况下,说明符 extern 可以省略,因此,凡是定义函数时 不加存储类说明符的都是外部函数。 外部函数是在程序中某个文件中定义的函数, 而在该程序的其他 文件中都可以调用。 [例 5.15] 外部文件的定义、说明和调用。 该程序由三个文件组成,各文件内容如下: 文件 fl.c 内容: int i=1, extern int reset(),next(),Iast().new(); main( ) { int i,J; i=reset() for(j=1 t<=3,j++) { printf(%d,%d,i,j); print("%d" next(); printf("%d,last())。 printf (" %d\n".,new(i+j)), }

)

文件 f2. c 内容: static int i=10; extern int next ( ) ( return (i 十=1): extern int laxt() { return(i 一=1); extern int new(i) int i static int j=5; retorri(i=j+=i);

文件 f3.c 内容: extern int extern int reset() { return(i); } 执行该程序输出如下结果

1,1,11,10,7 1.2,11,10,l0 1.3,11,10.14 说明: (1)该程序由三个文件、五个函数组成。 在文件 fl. c 中,首先定义了外部类变量 i.在该文件的主函数 内又定义了自动类变量 i 和在文件 f2 中,定义了三个函数:next (),last()和 new(),在该文件开始定义了一个外部静态类变量 i,在函数 new()中,定义了一个内部静态类变量 j 在文件 f3.c 中,文件开始说明了外部类变量 i.该文件只定义了 一个函数 reset ( ) (2)该程序中有着较为丰富的存储类。搞清楚程序中变量 i 和 j 在不同函数中的存储类是十分重要的。 变量 i 在 main()函数中是自动类变量,在 next()函数中是 外部静态类变量,在 last()函数中也是外部静态类变量,在 new ()函数中它是一个形参,即是局部变量,在 reset()函数中是一 个外部类变量。 变量 j 在 main ( )函数中是自动类变量,在 new()函数中它 是一个内部静态类变量。

(3)本程序中除 main ()外,其余 4 个函数都是外部函数, 在定义时没有省略 extern (4)在 main()函数中,for 循环共执行 4 次循环体,每执行 一次循环体输出一行用逗号分开的 5 个数。其中,i 值是不变的,J 值每次加 l,next()函数的返回值为 11,因为 i 是静态类的,last ()函数对 i 值有影响,因此,每次调用该函数返回值总为 i1.同样 道理,last ( )函数每次返回值也都为 l0anew()函数返回值与 该函数中的局部变量 i 和 j 有关,而 1 是内部静态类的,形参 i 在每 次调用时增 1,j 保留上次调用的值,因此该函数返回值开始为 7,第 二次增 3 为 10,第三次增 4 为 11. 2.内部函数 静态类的函数称为内部函数,它的定义格式如下: static<数据类型说明)(函数名)((参数表)) (参数说明) { <函数体> } 其中,static 是内部函数的说明符。该说明符不可省略。

内部函数只能在定义它的文件内调用, 不能在一个文件内调用同 一程序中的另一个文件中的内部函数。内部函数的作用域是文件级 的,而外部函数的作用域是程序级的。 [例 5. 16] 内部函数的定义和调用。 分析下列程序的输出结果: int i=1; static int reset(),next(),last(),new(); main() { int i,j; i=reset(); for(j=1;j<=3;j++) { printf (" %d,%d,",i,j); printf ("%d,,next(i)); printf(" %d,”,last(i)); printf ("%d\n" ,new(i+j )); } static int reset() return (i); }

static int next (j) int j; { j=i++ return(j); } static int last ( j) int; ( st8tic int i=10 j=i--; return(j) ; } static int new(i) int i; { int j=10; return(i=j+=i); ) 说明:

(1)该程序由 5 个函数组成,一个是主函数 main(),另外 4 个是由主函数调用的函数:reset()next(), last()和 new( ). 这 4 个函数被定义为内部函数,只能在该文件中调用。 (2)程序中变量 i 和 j 在各函数中的存储类描述如下:

(3)该程序的输出结果如下: 1.11,10,12 1,2,2,9,13 1,3,3,8,74


相关文章:
C语言教程基础篇【全免费】_图文.ppt
C语言教程基础篇【全免费】 - C语言程序设计 绪论 一、教学对象 计算机科学与技术系新生(第二学期) 二、教学目标 程序设计的重要性 程序设计的基本概念与基本...
C语言基础教程.doc
C语言基础教程 - C 语言基础教程(一) 网上收集整理 Turbo C 语言概
C语言编程入门教程_图文.ppt
C语言编程入门教程 - C语言程序设计 Copyer:Vigiking 第一章 计算机语言与C语言概述 1.1 计算机语言概念 ■计算机语言定义 计算机能够识别和接受的语言。 要使...
C语言入门到精通全教程_图文.ppt
C语言入门到精通全教程 - C语言程序设计 Copyer:Vigiking 第一
C语言程序设计基础教程_习题答案资料.doc
C语言程序设计基础教程_习题答案资料 - 习题答案 第1章 1.1 填空题 1.
C51单片机基础学习教程(C语言)_图文.pdf
C51单片机基础学习教程(C语言) - C51单片机基础学习教程(C语言)是一本
C语言编程入门教程_图文.ppt
C语言编程入门教程_电脑基础知识_IT/计算机_专业资料 暂无评价|0人阅读|0次下载|举报文档C语言编程入门教程_电脑基础知识_IT/计算机_专业资料。C语言程序设计任课...
C语言编程入门教程精简版_图文.ppt
C语言编程入门教程精简版_IT认证_资格考试/认证_教育专区。C语言程序设计请大家边动手边观看 第一章 计算机语言与C语言概述 1.1 计算机语言概念 ■计算机语言定义...
C语言编程入门教程资料_图文.ppt
C语言编程入门教程资料 - C语言程序设计 Copyer:Vigiking 第一章 计算机语言与C语言概述 1.1 计算机语言概念 ■计算机语言定义 计算机能够识别和接受的语言。 要...
C语言入门经典教程_图文.pdf
C语言入门经典教程 - ...... C语言入门经典教程_计算机软件及应用_IT/计算机_专业资料。 您的评论 发布评论 用户评价 C语言入门经典教程,如何下载 2018-06-26 ...
C语言入门教程全第1章_图文.ppt
C语言入门教程全第1章 - C语言入门教程全算 机基础教学重点核心课程教材 世纪
C语言学习笔记-详细而全面的基础教程.doc
C语言学习笔记-详细而全面的基础教程 - 冯诺依曼程序=算法+数据结构。 正确的理解函数,指针,宏定义,巧妙的利用注释,注意C语言的书写习惯和编程习惯…… 了解...
(1小时学会C语言51单片机)C语言入门教程.pdf
(1小时学会C语言51单片机)C语言入门教程 - 相信很多爱好电子的朋友 ,对单
(1小时学会C语言51单片机)C语言入门教程1.doc
(1小时学会C语言51单片机)C语言入门教程1 - 我们在单片机最小系统上接个
C语言基础教程.doc
C语言基础教程_电脑基础知识_IT/计算机_专业资料。c语言 C 语言基础教程(一) 网上收集整理 Turbo C 语言概述 1.1 C 语言的产生与发展 C 语言是 1972 年由...
C语言编程入门教程精简版_图文.ppt
C语言编程入门教程精简版 - C语言程序设计 请大家边动手边观看 第一章 计算机语言与C语言概述 1.1 计算机语言概念 ■计算机语言定义 计算机能够识别和接受的语言...
2011级C语言程序设计基础教程课后习题答案.doc
2011级C语言程序设计基础教程课后习题答案 - 清华大学出版社出版:纪纲,陈媛,金艳,张建勋等主编(c语言程序设计基础教程)!!
标准C语言基础教程_图文.ppt
标准C语言基础教程 - C言基教程 A First Book of AN
51单片机C语言入门教程.pdf
51单片机C语言入门教程_信息与通信_工程科技_专业资料。51单片机C语言入门教程非常适合初级者学习 51 51 C C C C C C * C C KEIL uVISION2 MCS51 C C ...
二级C语言教程_图文.ppt
二级C语言教程 - C语言程序设计小结 内容 C语言程序设计基础 数据类型、常用
更多相关标签: