1 / 99

第六章 函数

第六章 函数. 6.1 函数概述 6.2 函数的定义和调用 6.3 函数间的参数传递 6.4 函数的嵌套调用和递归调用 6.5 变量的作用域和存储类别 6.6 内部函数和外部函数 6.7 带参数的 main 函数 6.8 编译预处理. 6.1 函数概述. 函数是构成C程序的基本模块。 C 程序都是由一个个函数所组成的,即使是最简单的程序也要有一个 main 函数。 函数就是一段完成某个功能的程序。可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言。 函数分类:

ronnie
Download Presentation

第六章 函数

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第六章 函数 6.1 函数概述 6.2 函数的定义和调用 6.3 函数间的参数传递 6.4 函数的嵌套调用和递归调用 6.5 变量的作用域和存储类别 6.6 内部函数和外部函数 6.7 带参数的main函数 6.8 编译预处理

  2. 6.1 函数概述 • 函数是构成C程序的基本模块。C程序都是由一个个函数所组成的,即使是最简单的程序也要有一个main函数。 函数就是一段完成某个功能的程序。可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言。 • 函数分类: • 从函数定义的角度看,函数可分为库函数和用户定义函数两种。 • C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。 • 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。

  3. 6.1 函数概述 • 应该指出的是: 1.在C语言中,所有的函数定义,包括主函数main在内,都是平行的。也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。函数还可以自己调用自己,称为递归调用。 2. main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。因此,C程序的执行总是从main函数开始,完成对其它函数的调用后再返回到main函数,最后由main函数结束整个程序。一个C源程序必须有,也只能有一个主函数main。

  4. 6.2 函数的定义和调用 6.2.1 无参函数的定义 6.2.2 有参函数的定义 6.2.3 函数的参数和返回值 6.2.4 函数的调用

  5. 6.2.1 无参函数的定义 无参函数的定义形式: [类型标识符] 函数名(void) { 声明语句部分 可执行语句部分 }其中: 1. 类型标识符 类型标识符指明了函数返回值的类型。 可以是int、float、double、void等。当确认不需要返回值时,函数类型标识符可以写为void。中括号[]括起来的内容根据需要可以省略(如[类型标识符]),若省略类型标识符,函数的返回值默认为int型,而不是无返回值。如:fuc(){…}返回值为int型。 2. 函数名 函数名是由用户定义的标识符,命名规则同变量名。在旧标准中,函数可以缺省参数表。但在新标准中,函数不可缺省参数表;如果不需要参数,则用“void”表示(主函数main()例外),实际使用时有无均可,不影响程序的运行和结果。 3. 函数体 {}中的内容称为函数体。在函数体中声明部分,是对函数体内部所用到的变量的类型说明。

  6. 例6.1 无参函数使用举例 void print1()\\或void print1(void) { printf ("\n***************** \n"); } void message()\\或void message(void) { printf (" Hello world! "); } void main() { print1();\\调用print1()函数输出一串* message();\\调用message()函数,输出Hello world! print1();\\调用print1()函数输出一串* } 运行结果: ***************** Hello world! *****************

  7. 6.2.2 有参函数的定义 有参函数定义的一般格式 [函数类型] 函数名( 数据类型 参数1,数据类型 参数2…… ) { 说明语句部分; 可执行语句部分; } 例如:double fun(int x,int y){…}正确。 double fun(int x,y){…}错误,没有声明y的类型。

  8. 6.2.2 有参函数的定义 说明: ● 函数类型、函数名等要求同无参函数。但有参函数比无参函数多了一个参数表。需对每一个参数声明其类型,即使两个参数的数据类型相同,也要分别说明,如上例。 ● 在老版本C语言中,参数类型说明允许放在函数说明部分的第2行单独指定。例如:double fun(x,y) int x,y; 但在VC++6.0环境下,不支持这种格式,建议大家不使用这种形式。 ● 调用有参函数时,调用函数将赋予这些参数实际的值。为了与调用函数提供的实际参数区别开,将函数定义中的参数表称为形式参数表,简称形参表。 ● 空函数:既无参数、函数体又为空的函数。其一般形式为: [函数类型] 函数名(void) { } 例如:void fun(){ } int fun(){ } fun(){ }都是正确的空函数定义

  9. 6.2.2 有参函数的定义 例6.2 编写函数sum,功能是求1+2+3+…n。 #include<stdio.h> void main() { int n;long s; printf(“\n请输入一个整数:”); scanf(“%d”,&n); s=sum(n); /*调用sum函数求1到n的和*/ printf(“\n1+2+3+...+%d=%ld\n”,n,s); } int sum(int m)/*函数首部*/ { int sum1=0; int i; for(i=0;i<=m;i++)/*形参m在sum函数中可以使用*/ sum1+=i; return sum1;/*将结果sum1的值返回到调用函数main*/ } 运行结果: 请输入一个整数:100 1+2+3+...+100=5050

  10. 6.2.3 函数的参数和返回值 1. 形参和实参 ● 形参 形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能 使用。 ● 实参 实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和 实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给 被调函数的形参从而实现主调函数向被调函数的数据传送。 2. 形参和实参的特点: ● 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分 配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调 函数后则不能再使用该形参变量。 ● 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在 进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应 预先用赋值,输入等办法使实参获得确定值。

  11. 6.2.3 函数的参数和返回值 3. 函数的返回值 函数的返回值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主 调函数的值。函数的值是在被调函数中通过return语句返回主调函数。 return 语句的一般形式为: return 表达式;或 return (表达式); 几点说明: ● 该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return 语句被执行,因此只能返回一个函数值。 ● 函数返回值的类型和函数定义中函数的类型应保持一致。如果两者不一致,则以函数类型为准,自动进行类型转换。 ● 如函数值为整型,在函数定义时可以省去类型说明。 如:int fun()和fun()作用等同 ● 不返回函数值的函数,可以明确定义为“void”。同时被调函数中的return为空即return;也可以没用return语句,为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为空类型。

  12. 6.2.4 函数的调用 C语言中,函数调用的一般形式为: 函数名(实际参数表); 说明: ● 如果使用标准库函数,首先要了解该函数的原型声明所在的头文件,并用include命令将该文件加入到源程序中。 ● 如果使用用户自己定义的函数,要检查该函数是否已经定义,定义的位置在调用之前,可直接调用,否则要先进行函数声明,才能调用。 ● 调用函数时,要将实参的值传递给形参,所以实参和形参在数量上、类型上、顺序上应严格一致,否则会发生类型不匹配的错误。 ● C语言中,可以用函数表达式、函数语句和函数实参几种方式调用函数。 例如:s=sum(n);是一个赋值表达式,把sum的返回值赋予变量s。 printf ("%d",a);scanf ("%d",&b);都是以函数语句的方式调用函数。 printf("%d",max(x,y)); 是把max调用的返回值又作为printf函数的实参来使用的 ● 在函数调用中还应该注意的一个问题是求值顺序的问题。所谓求值顺序是指对实参表中各量是自左至右使用呢,还是自右至左使用。对此,各系统的规定不一定相同。只要在你所使用的系统中验证一下就清楚了。

  13. 6.2.4 函数的调用 例如: #include <stdio.h> void main() { int i=8; printf("%d,%d,%d,%d\n",++i,--i,i++,i--); } 如按照从右至左的顺序求值。运行结果应为:8,7,7,8 如对printf语句中的++i,--i,i++,i--从左至右求值,结果应为:9,8,8,9 应特别注意的是,无论是从左至右求值, 还是自右至左求值,其输出顺序都是不变的, 即输出顺序总是和实参表中实参的顺序相同。由于vc++6.0中是自右至左求值,所以结果为8,7,7,8。建议大家尽量避免这样使用。

  14. 6.2.4 函数的调用 3. 被调用函数的声明和函数原型 在主调函数中调用某函数之前应对该被调函数进行说明(声明),这与使用变量之前要先进行变量说明是一样的。在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理。 其一般形式为: 类型说明符 被调函数名(类型 形参,类型 形参…); 或为: 类型说明符 被调函数名(类型,类型…); C语言中又规定以下情况可以省去主调函数中对被调函数的函数说明: ● 如果被调函数的返回值是整型或字符型时, ● 当被调函数的函数定义出现在主调函数之前时 ● 如在所有函数定义之前,在函数外预先说明了各个函数的类型 ● 对库函数的调用不需要再作说明,但必须把该函数的头文件用include命令包含在源文件前部

  15. 6.3 函数间的参数传递 6.3.1 值传递 6.3.2 地址传递 6.3.3 数组作函数参数 6.3.4 指针数组名做函数参数

  16. 6.3.1 值传递 当调用一个有参函数函数时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。传送的方式有两种:值传递和地址传递。 值传递: 是把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。值传递是单向的。当形参为简单变量时,实参和形参的数据传递属于值传递。

  17. 6.3.1 值传递 #include<stdio.h> void main() { void fun(int x,int y);/* fun函数声明*/ int x=10,y=15; fun(x,y);/*调用函数fun*/ printf(“在主函数中x=%d,y=%d”,x,y); } void fun(int x,int y)// fun函数定义 { int k=x+y; x=k-x;y=k-y;//交换x,y的值 printf(“\n在fun函数中x=%d,y=%d\n”,x,y); } 运行结果: 在fun函数中x=15,y=10 在主函数中x=10,y=15

  18. 6.3.1 值传递 main函数中 fun函数中 main函数中 10 X 10 15 10 X X X 调用fun 执行fun 返回main 15 15 10 15 Y Y Y Y 值传递情况示意图

  19. 6.3.2 地址传递 地址传递: 把实参的地址传递给形参,这样形参和实参指向同一块内存区域,形参所指内存区域的内容发生变化,实参也得到变化后的内容。但要注意实参和形参均为指针或数组名。

  20. 6.3.2 地址传递 • 例6.4 实参和形参的地址传递 • #include<stdio.h> • void main() • { void fun(int *x,int *y);/* fun函数声明*/ • int x=10,y=15; • fun(&x,&y);/*调用函数fun。使用x,y的地址作实参*/ • printf(“\n在内存中x的地址:%x,x的值为:%d\n”,&x,x); • printf(“\n在内存中y的地址:%x,y的值为:%d\n”,&y,y); • } • void fun(int *a,int *b)/* fun函数定义*/ • { • int k; • k=*a,*a=*b,*b=k;/*交换指针变量a,b所指单元的值*/ • printf(“\n指针变量a的值:%x,a所指单元的值为:%d\n”,a,*a); • printf(“\n指针变量b的值:%x,b所指单元的值为:%d\n”,b,*b); • }

  21. x的地址 a x fun函数中 main函数中 内存单元12ff7c,值为10 内存单元12ff7c,值为15 调用fun后 内存单元12ff78,值为15 内存单元12ff78,值为10 y的地址 main函数中 y fun函数中 b 6.3.2 地址传递 运行结果: 指针变量a的值:12ff7c, a所指单元的值为15 指针变量b的值:12ff78, b所指单元的值为:10 在内存中x的地址:12ff7c, x的值为:15 在内存中y的地址:12ff78, y的值为:10 分析示意图如下:

  22. 6.3.2 地址传递 数组可以作为函数的参数使用,进行数据传送。数组用作函数参数有两种形式,一种是把数组元素(下标变量)作为实参使用;另一种是把数组名作为函数的形参和实参使用。 1. 数组元素作函数实参 数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。

  23. 6.3.2 地址传递 例6.5 判别一个整数数组中各元素的值,若小于0 则 输出该值,若大于等于0则输出0值。 分析:本程序中定义函数void pzf(int v),根据v值输出相应的结果。在main函数中用一个for语句输入数组各元素,每输入一个就以该数组元素作实参调用一次nzp函数,即把a[i]的值传送给形参v,供pzf函数使用。

  24. 6.3.2 地址传递 运行结果: 请输入5个数:5 7 -2 -3 4 0 0 -2 -3 0 源程序如下: #include<stdio.h> void pzf(int v) { if(v<0) printf("%d ",v); else printf("%d ",0); } void main() { int a[5],i; printf("\n请输入5个数:"); for(i=0;i<5;i++) {scanf("%d",&a[i]); pzf(a[i]);/*数组元素a[i]作实参,将a[i]的值传给pzf中的变量v*/ }

  25. 6.3.2 地址传递 2. 一维数组名作为函数参数 在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。而是把实参数组的首地址赋予形参数组名,形参数组名取得该首地址之后,也就等于有了实参的数组。实际上形参数组和实参数组为同一数组,共同拥有同一段内存空间。 注意:用数组名作函数参数时,要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。

  26. 6.3.2 地址传递 例6.6 编写函数,对输入的10个字符按照从小到大的顺序排列。 分析:我们准备编写两个函数,在主函数中完成字符的输入和排序后字符的输出,在sort函数中完成对字符的排序。函数原型为void sort(char a[],int m),其功能是对一串字符排序,为了使该函数具有一定的通用性,字符的个数设一个变量m来存储,这种方法希望大家掌握。

  27. 6.3.2 地址传递 源程序如下: #include<stdio.h> #include<string.h> void main( ) { void sort(char a[], int m);/*函数声明*/ char p[10];int i; printf("输入10个字符:\n"); gets(p); sort(p,10);/*调用排序函数sort,10为字符个数传给sort中的m*/ printf("排序结果:\n"); for(i=0;i<10;i++) printf(“%c”,p[i]); } void sort(char a[], int m)/*函数定义,a为字符数组名,m存放字符个数*/ { int i,j; char c; for(i=0;i<m-1;i++) for(j=0;j<m-i-1;j++) if(a[j]>a[j+1]) c=a[j],a[j]=a[j+1],a[j+1]=c;} 运行结果: 输入10个字符:adcbfehgtz 排序结果: abcdefghtz

  28. 6.3.2 地址传递 由于调用sort函数后,p和a指向同一块内存区域, p[0] p[1] p[2] p[3] p[4] p[5] p[6] p[7] p[8] p[9] a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 所以在sort函数中对字符串排序后,主调函数中得到了排序后的结果,或者称形参的改变会使实参同样改变。 另外,形参和实参可以同为数组名、指向数组的指针,也可以形参为数组名,实参为指向数组的指针;或者形参为指向数组的指针,实参为数组名。

  29. 6.3.2 地址传递 3. 多维数组名作为函数的参数 既然一维数组可以作为参数传递给函数,多维数组同样也可以作为参数。和一维数组的情况相似,也要遵循几条规则: ● 通过数组名将数组的数据传递给被调函数。 ● 在函数定义中,要在数组名后加上多个[]来表示多维数组。 ● 除第一维外,其他高维的长度不能省略。例如: int array[3][10]可写为int array[][10]

  30. 6.3.2 地址传递 例6.10 求2×3矩阵中数据的平均值。 源程序如下: #include<stdio.h> void main() { int average(int a[][3], int m, int n); int value[][3]={{2, 4, 6}, {8, 10, 12}}; printf(“矩阵的平均值为 :%d”,average(value, 2, 3)); } int average(int a[][3], int m, int n) { int i, j, sum=0; for(i=0; i<m; i++) for(j=0; j<n; j++) sum=sum+a[i][j]; return sum/(m*n); } 运行结果: 矩阵的平均值为:7

  31. 6.3.2 地址传递 4. 指针数组名做函数参数 在解决实际问题时,往往需要同时处理多个字符串。例如电影院同期上映的电影名称查询、图书馆书目的管理等。在C语言中,一个字符串就是用一维的字符数组来存储的,多个字符串的存放就需要用到二维数组来实现。但在定义二维数组时,需要指定列数,即每个字符串中包含的字符个数都是相等的,这显然不合理。因为数组是静态分配方式,在分配空间时,需按照最大的字符串长度进行分配,而不同的字符串之间长度相差很大,这样就会造成内存单元的浪费。 使用指针数组,可以使得数组中的每个指针变量指向不等长的多个字符串,从而解决了上面的问题。

  32. 6.3.2 地址传递 例6.11 将多个字符串按字典顺序输出。 #include<stdio.h> #include<string.h> void sort(char *name[ ], int n); void print(char *name[ ], int n); void main( ) { char *name[3]={“New Zealand”, “Australia”, “China”}; int n=3; sort(name, n); print(name, n); } void sort(char *name[ ], int n) { char *temp; int i, j, k; for(i=0; i<n-1; i++) {k=i; for(j=i+1; j<n; j++) if(strcmp(name[k], name[j])>0) k=j; if(k!=i){temp=name[i];name[i]=name[k];name[k]=temp;} } }

  33. 6.3.2 地址传递 void print(char *name[ ], int n) { int i; for(i=0; i<n; i++) printf(“%s\n”, name[i]); } 运行结果: Australia China New Zealand

  34. 6.4 函数的嵌套调用和递归调用 6.4.1 函数的嵌套调用 6.4.2 函数的递归调用

  35. main函数 f函数 g函数 调用f函数 调用g函数 结束 图6-3 函数的嵌套调用 6.4.1 函数的嵌套调用 C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。C语言中,主函数可以调用其它函数,其它函数之间也可以互相调用,但主函数不可以被其它函数调用。函数的嵌套调用关系如图6-3所示。

  36. 6.4.1 函数的嵌套调用 例6.12 计算sum=22! +32! 分析:本题可编写两个函数,fun1用来计算平方值,fun2用来计算阶乘值。主函数中调用fun1计算出平方值,再在fun1中以平方值为实参,调用 fun2计算其阶乘值,然后返回fun1,再返回主函数,在循环程序中计算累加和。 源程序如下: #include<stdio.h> long fun1(int p); long fun2(int q);/ long fun1(int p)/*函数定义,因涉及阶乘数较大,返回值为long或double*/ { int k; long r; /*r存放因阶乘数很大,所以定义为long,甚至可为double*/ k=p*p; r=fun2(k);/*在fun1中,调用fun2,程序转去执行fun2*/ return r;/*返回到调用fun1的main函数*/ }

  37. 6.4.1 函数的嵌套调用 long fun2(int q)/*求q!,因数值大,返回值为long或double*/ { long c=1;/*因阶乘数很大,所以定义为long,甚至可为double*/ int i; for(i=1;i<=q;i++) c=c*i; return c; /*返回到调用fun2的fun1函数*/ } void main() { int i; long sum=0;/*因sum为累加变量,一定要有合适的初始值!!!*/ for (i=2;i<=3;i++);/*调用两次fun1函数,求出2!3+3!3*/ sum=sum+fun1(i); printf("\nsum=%ld\n",s);/*输出23! +33!的值*/ } 运行结果: sum=362904

  38. 6.4.2 函数的递归调用 一个函数在它的函数体内直接调用它自身或通过其它函数间接调用它自身称为递归调用。这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中,主调函数同时又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。 例如有函数f定义如下: int f(int x) { int y,z; z=f(y);/*在函数体内调用f自身*/ return z; } f函数的函数体内直接调用f自身,f是一个递归函数。

  39. 6.4.2 函数的递归调用 再比如有h,g两个函数: int h(int x) { int z; z=g(x); return(z); } int g(int y) { int i; i=h(x); return(i); } h函数调用g函数,而g函数又调用h函数,这样h函数通过g函数间接调用了其自身, h是一个递归函数。

  40. 6.4.2 函数的递归调用 上述的f和h两个函数,都是递归函数。但是运行该函数将无止境地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。下面举例说明递归调用的执行过程。

  41. 6.4.2 函数的递归调用 例6.13用递归法计算n! 分析:用递归法计算n!可用下述公式表示: n!=1 (n=0,1) n!=n×(n-1)! (n>1) 假如n!定义为一个函数fun(x),则上式可表示为fun(n)=n*fun(n-1) 依此类推有: fun(n-1)=(n-1)*fun(n-2) fun(n-2)=(n-2)*fun(n-1) …… fun(2)=2*fun(1) 恰为fun函数的递归调用。据此程序可写为:

  42. 6.4.2 函数的递归调用 #include<stdio.h> long fun(int n)/*递归函数*/ { long f;//f用来存放n!的值 if(n<0) printf("n<0,Data error"); else if(n==0||n==1) f=1; else f=fun(n-1)*n; return(f);/*将n!的值返回到调用fun的函数处*/ } void main() { int n; long y; printf("\n请输入一个整数:"); scanf("%d",&n); y=fun(n);//调用fun函数,求n! printf("\n%d!=%ld",n,y); }

  43. 调用fun(4) f=4*fun(3) f=3*fun(2) f=2*fun(1) 1 1 f=2 f=6 f=24 y=24 6.4.2 函数的递归调用 运行结果: 请输入一个整数:4 4!=24 下面以4!为例说明递归函数的执行过程: 第一步:函数展开 主函数中 fun(4) fun(3) fun(2) fun(1)调用 第二步:回代 fun(1) fun(2) fun(3) fun(4) 主函数中 从此例可见,编写递归函数关键要找出递归规律,写出递归公式,在函数 体内用if语句控制递归调用为有限次调用。在函数体内可以根据需要定义一 个变量用来存储函数的值。

  44. 6.4.2 函数的递归调用 例6.14 Hanoi塔问题 一块板上有三根针,A,B,C。A针上套有64个大小不等的圆盘,大的在下,小的在上。要把这64个圆盘从A针移动C针上,每次只能移动一个圆盘,移动可以借助B针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。 通过分析可以看出,将n个圆盘由A移动到C上可以分解为以下几个步骤: 第一步 把A上的n-1个圆盘借助C移到B上。 第二步 把A上的一个圆盘移到C上。 第三步 把B上的n-1个圆盘借助A移到C上。 其中第一步和第三步是类似的,显然这是一个递归过程。当n=1时,直接将A上的盘子移动到C;当n=2时,需移动3次,先将A上盘子移动到B,再将A上的盘子移动到C,最后将B上的盘子移动到C。据此算法可编程如下:

  45. 6.4.2 函数的递归调用 #include<stdio.h> void move(int n,int x,int y,int z)/*n为盘子数,x,y,z为三根针,将x移动到z*/ { if(n==1) printf("%c-->%c\n",x,z); else { move(n-1,x,z,y); printf("%c-->%c\n",x,z); move(n-1,y,x,z); } } void main() { int num;//A盘上的圆盘数 printf("\n请输入盘子数:"); scanf("%d",&num); printf("移动 %2d 个盘子的步骤:\n",num); move(num,'A','B','C'); }

  46. 6.4.2 函数的递归调用 从程序中可以看出,move函数是一个递归函数,它有四个形参n,x,y,z。n表示圆盘数,x,y,z分别表示三根针。move 函数的功能是把x上的n个圆盘移动到z上。当n=1时,直接把x上的圆盘移至z上,输出x→z。如果n≠1则分为三步:递归调用move函数,把n-1个圆盘从x移到y;输出x→z;递归调用move函数,把n-1个圆盘从y移到z。在递归调用过程中n=n-1,故n的值逐次递减,最后n=1时,终止递归,逐层返回。 当n=3 时程序运行的结果为: 请输入盘子数: 3 移动 3 个盘子的步骤: A→C A→B C→B A→C B→A B→C A→C

  47. 6.5 变量的作用域和存储类别 6.5.1 变量的作用域 6.5.2 变量的存储类别

  48. int f1(int a) {int b,c; …… } } a,b,c的有效范围 void main() {int m,n; f1(m) } m,n的有效范围 6.5.1 变量的作用域 在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。这种变量有效性的范围称为变量的作用域。不仅对于形参变量,C语言中所有的量都有自己的作用域。C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。 1. 局部变量 局部变量也称为内部变量。局部变量是指在函数内定义说明的变量。其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。例如:在主函数中a、b、c非法。

  49. 6.5.1 变量的作用域 关于局部变量的作用域还要说明以下几点: ● 主函数中定义的变量只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点应予以注意。 ● 形参变量的作用域为被调函数。 ● 在不同的函数中可以使用相同的变量名,因为它们在内存中占不同的单元,互不干扰,也不会发生混淆。 ● 在复合语句中也可定义变量,但只在复合语句范围内有效。例如:变量d的有效范围是复合语句。 void main() { int a,b,c; {int d; } }

  50. 6.5.1 变量的作用域 2. 全局变量 全局变量也称为外部变量,它是在所有函数外部定义的变量。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。 例6.15 全局变量的定义和声明 #include<.stdio.h> int a=20;//a为全局变量 void main( ) { extern d;//d为全局变量,因定义在后,所以应说明 int b=15,c; c=a+b+d; /*a为全局变量,因定义在前,所以直接使用 printf(“a=%d,b=%d,c=%d,d=%d”,ab,c,d); } int d=30; //d为全局变量

More Related