1 / 159

第六章 函数 6.1 概述 6.2 函数定义的一般形式 6.3 函数参数和函数返回值 6.4 函数的调用 6.5 函数的嵌套调用 6.6 函数的递归调用 6.7 数组作为函数的参数

第六章 函数 6.1 概述 6.2 函数定义的一般形式 6.3 函数参数和函数返回值 6.4 函数的调用 6.5 函数的嵌套调用 6.6 函数的递归调用 6.7 数组作为函数的参数 6.8 局部变量和全局变量 6.9 变量的存储类别 6.10 内部函数与外部函数. 6.1 概述 C 程序是模块结构的,每个模块也称为函数。 一个 C 程序可由一个 main 函 数和若干个用户 自定义函数构成,这些函数是通过调用来建立联系的。 在这些函数中还可以调用系统提供的各类标 准库函数。

celine
Download Presentation

第六章 函数 6.1 概述 6.2 函数定义的一般形式 6.3 函数参数和函数返回值 6.4 函数的调用 6.5 函数的嵌套调用 6.6 函数的递归调用 6.7 数组作为函数的参数

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数组作为函数的参数 6.8局部变量和全局变量 6.9 变量的存储类别 6.10 内部函数与外部函数

  2. 6.1概述 C程序是模块结构的,每个模块也称为函数。 一个C程序可由一个main函数和若干个用户 自定义函数构成,这些函数是通过调用来建立联系的。 在这些函数中还可以调用系统提供的各类标 准库函数。 本章主要介绍用户自定义函数的定义、调用以及函数间信息的传递。

  3. 1. 采用自定义函数的意义 1)符合结构化设计思想,便于设计、调试和扩充,是逐步细化开发方式的具体体现; 2) 每个自定义函数可单独编译,便于模块调试; 3) 避免在程序中设计重复的代码,函数定义一次 便可多次调用,有效地利用了已有的代码资源。

  4. 2. C程序各函数之间的关系 1)不管主函数的书写位置如何,C程序总是从主函 数开始运行、并在主函数结束; 2)主函数可调用所有自定义函数,但不能被任何函 数调用; 3)一个自定义函数除了可被主函数调用外,还可被 其它自定义函数调用; 4)自定义函数可调用除主函数之外的其它自定义函 数,甚至可直接或间接地自己调用自己。

  5. 函数调用关系图示: 自定义f1 main 自定义f2 ┇ 自定义fn

  6. 3. 函数调用的执行流程 嵌套调用: a a main 调用 开始 返回 c b 结束 间接递归调用: 直接递归调用: f1 f2 f 调用 调用 返回 返回

  7. 4. C函数的分类 从用户使用的角度分类: 库函数——由系统提供,包括:常用数学库、标准 I/O库、DOS专用库、字符屏幕控制库、 图形库等; 用户自定义函数——根据特定的需要自己编制; 从函数的形式分类: 无参函数——调用时不需要任何参数; 有参函数——调用时必须给出实参,函数依据实参 进行相应处理。

  8. 6.2 函数定义的一般形式 1. 有参函数定义的一般形式 [类型标识符] 函数名(形式参数表) 函数头 { 局部声明 函数体 执行语句 } 特点:调用时必须给出实参,函数将依据实参实现 特定操作。 其中—— 类型标识符:说明函数返回值的类型,省略为int型; 函数名:遵循标识符命名规则,名称尽量反映功能;

  9. 形式参数:从形式上说明该函数调用时所需的参数形式参数:从形式上说明该函数调用时所需的参数 个数及类型,多个形参间逗号分隔; 局部说明部分:对自定义函数局部使用的变量、数 组等进行类型定义; 执行语句:实现函数功能;其中的返回语句return将 控制程序流程返回主调函数;

  10. 例:用自定义函数找出两数中的较大数 main( ) { int a, b; scanf("%d,%d", &a, &b); printf("MAX=%d\n", max(a, b)); } int max(int x, int y)/*int可省略,定义形参变量*/ { int z; /*局部变量定义*/ z=x>y? x:y; return(z); /*将z的值作为返回值*/ }

  11. 2. 无参函数定义的一般形式 [类型标识符] 函数名( ) { 局部声明 执行语句 } 特点:不依据任何参数来实现特定操作; 注意:尽管没有参数但圆括号不能省去。 例:定义实现延时功能的无参函数 void delay( ) /*无参、无返回值*/ { long i; for( i=0; i<=32000000; i++); }

  12. 3. 空函数定义的一般形式 [类型标识符] 函数名( ) { } 作用:用于调试程序时,在逻辑上表示这个函数已 经定义,而不至于影响程序其它部分的编译; 待下一阶段再编制该函数具体内容。

  13. 6.3 函数参数和函数返回值 形式参数——定义函数时的参数 实际参数——调用函数时的参数 函数返回值——返回主调函数时带回的结果

  14. 例:利用自定义函数,找出三个数中的最大数。例:利用自定义函数,找出三个数中的最大数。 main( ) { int a, b, c, d; scanf("%d,%d,%d", &a, &b, &c); d=max(a, b); /*a、b的值作为实参*/ d=max(d, c); /*d、c的值作为实参*/ printf("Max is %d\n", d); } max(int x, int y) { int z; z=x>y? x:y; return(z); /*z值作为返回值*/ }

  15. 1.形式参数和实际参数说明 1) 编译时并不为形参分配存储单元,在程序运行中 发生函数调用时,才动态地为形参分配存储单元, 并接受实参传递的值;函数调用结束,形参占用 的存储单元将被释放; 如例中第一次调用: 实参:a b d=max(a, b); 形参:x y 第二次调用: 实参:d c d=max(d, c); 形参:x y 5 8 8 5 12 8 12 8

  16. 2) 由于实参和形参各有各的存储单元,因此在被调函数中给形参变量赋值,不会对实参造成任何影响。 如例中主调函数: d=max(a, b); 实参:a b 被调函数: 形参:x y z=x>y? x: y; x=y=0; return(z); 5 8 5 8 0 0

  17. 3) 实参、形参的形式 实参形式 传递内容 形参形式 常量 变量、下标变量 表达式 函数调用 数组名 同类型数组 例:d=max( i%100, 10); /*实参:表达式、常量*/ d=max(n[1], n[2]); /*实参:下标变量*/ d=max((int)sqrt(25), c);  实参的值 单向传递 同类型变量 数组指针 单向传递

  18. 4) 由于实参和形参各有各的存储单元,因而实参和 形参可以同名,且互不干扰; 5) 实参与形参在个数、类型、顺序上要对应一致; 否则因编译系统不做匹配性检查,将造成数据传递错误。 如:d=max(7.8, 10.2); /*实、形参类型不一致*/ d=max(a, b, c); /*实、形参个数不一致*/ 6) 若主调函数采用了函数原型声明,在实、形参类 型不一致时将按赋值规则自动转换为同一类型。

  19. 上例:观察实参、形参的存储地址 main( ) { int a, b, c, d; scanf("%d,%d,%d", &a, &b, &c); printf("&a=%x,&b=%x,&c=%x\n", &a, &b, &c); d=max(a, b); d=max(d, c); printf("a=%d,b=%d,c=%d,d=%d\n", a, b, c, d); } max(int x, int y) { int z; z=x>y? x:y; x=y=0; printf("&x:%x,&y:%dx:%d,y:%d\n", &x, &y, x, y); return(z); }

  20. 2. 函数的返回值 若仅返回一个值:可用return返回语句实现; 若需返回多个值:则需要使用其它手段实现; 如:指针、全局变量 返回语句的一般形式:return(表达式); 功能:在自定义函数中使用该语句,可使程序的执 行流程返回到主调函数的调用位置,并将表 达式的值作为返回值。 表达式形式:任何合法的C表达式;

  21. 使用说明: 1) 一个自定义函数中可以有一个以上的return语句; 这常用于分支结构的不同出口,但只可能有一个被执行。 如上例:if (x>y) return(x); else return(y); 如:函数功能——判断形参m、n的大小关系; 若m>n返回1、m<n返回-1、m==n返回0。 int compare(int m, int n) { return((m-n)>0? 1:-1); /*只执行该条*/ return(!((m-n)==0)); /*永远不可能执行*/ }

  22. 2) 若函数不需要用return语句返回值,其类型应采 用void空类型标识符;此时返回语句的形式为: return; 或者干脆省去返回语句; 函数体结束标 志‘}’也可控制流程返回。 如: void printstar( ) { printf("********************\n"); return; /*或省略*/ } 该函数仅仅完成输出一行星号字符的特定操作, 不需要参数,也不需要返回值。

  23. 3) return后表达式值的类型一般应与定义函数的类 型一致;若不一致,则以定义函数时的类型为准 自动转换。 如: int average(…… ) { ┇ return(sum/4.0); } /*值为实型,自动转为int型返回*/

  24. 6.4 函数的调用 1.函数调用的一般形式 函数名(实参表) 实参形式:可是常量、变量、下标变量、表达式、 函数调用等;多个实参逗号分隔。 注意: 实参在个数、类型、顺序上应与形参对应。 

  25. 调用过程: 首先求解各实参,然后将实参值对应传递到为 形参分配的存储单元中,且程序流程转至被调函数, 当被调函数碰到return语句或函数体结束标志‘}’时, 流程返回主调函数,继续后面的操作。 图示: 主调 被调 实参值 返回值

  26. 例:定义一函数判断两个量是否相等,相等返回0,例:定义一函数判断两个量是否相等,相等返回0, 数1>数2返回1,数1<数2返回-1。 main( ) { int a, b,flag; scanf("%d,%d", &a, &b); flag=compaer(a, b); if(flag==0) printf("%d==%d", a, b); else printf("%d%c%d", a, flag>0? '>':'<', b); } int compare(int a, int b) /*实、形参名相同*/ { if(a==b) return(0); else if(a>b) return(1); else return(-1); }

  27. 2. 函数调用的位置 1) 对有返回值函数的调用位置,可是表达式的运算 元素、函数的参数; 如:c=2*max(a,b)+10; 如:m=max(a, max(b,c) ); 如:printf("MAX=%d", max(a, b+10) ); 2) 对void空类型函数的调用位置,只能是独立的函 数调用语句; 如:printstar( ); 如:delay( );

  28. 3. 对被调函数的声明 要调用一个自定义函数,除了该函数已被定义, 还需要对其做一些必要的声明。 1) 声明方法 (有2种) 简单声明法—— 类型标识符 函数名( ); 特点:此方法在编译时不对参数类型、个数做匹配 性检查,因此调用时实参和形参的类型、个 数必须一致,否则会产生奇怪的结果。

  29. 原型声明法—— 类型标识符 函数名(参数类型标识符表 ); 其中:在参数类型标识符表中,按顺序指明所调函 数的各个参数类型。 特点:此方法在编译时将依据声明,校验函数调用 的参数类型、个数是否匹配。个数不匹配则 编译出错;类型不匹配则以形参类型为准自 动转换(赋值兼容)。 建议:采用原型声明法,可充分利用系统检查能力; 为了便于阅读,参数类型后可包含参数名。

  30. 例:计算三个数整数部分的和。 main( ) { int t; int sum( ); /*简单声明不做参数匹配检查*/ t=sum(12.1, 6.9, 3); /*t=sum(12.1, 6.9);*/ printf("SUM=%d\n",t); } int sum(int a, int b, int c) { return(a+b+c); } -26215 原形声明: int sum(int, int, int ); 或带参数名声明: int sum(int a, int b, int c);

  31. 2) 声明的位置 主调函数内或源文件开头; 3) 选择声明位置的原则 若函数f1仅被少数(一两个)函数调用,则选择 在主调函数内声明f1,此时对f1的调用只能在这些 带有声明的函数内调用; 若f1被若干函数调用,则选择在源文件开头对 f1声明,此时所有函数都可调用f1且不必再行声明。

  32. 4) 下列情况函数声明可省略  函数返回值类型为int型;  被调函数书写在主调函数之前;  函数声明集中书写在源文件开头;

  33. 例:void printstar( ); /*声明printstar*/ float fun1(…) { …; printstar( ); /*printstar在开头说明*/ …; } main( ) { … ; /*省略所有被调函数声明*/ x=fun1(…); /*fun1书写在前*/ y=fun2(…); /*fun2类型为int*/ printstar( ); …; } /*printstar在开头说明*/ void printstar( ) { printf("****************"); } intfun2(…) { …… }

  34. 函数调用举例 例:已知四边形边长a、b、c、d及一条对角线长e, 求四边形面积。 b a e c d 分析:可分解为求任意三角形面积的问题。将求三 角形面积的功能独立出来,用自定义函数实 现。

  35. #include "math.h" float ss(float a, float b, float c) /*函数定义*/ { float t, s; t=(a+b+c)/2.0; s=sqrt(t*(t-a)*(t-b)*(t-c)); return(s); } main( )   { ┇ }

  36. main( ) { float a, b, c, d, e, area; int i, j; float ss(float, float, float); /*函数声明*/ printf("\nInput a,b,c,d,e:"); scanf("%f,%f,%f,%f,%f", &a,&b,&c,&d,&e); i=a+b>e&&a+e>b&&e+b>a; j=c+d>e&&c+e>d&&e+d>c; if(i&&j) { area=ss(a,b,e)+ss(c,d,e); /*函数调用*/ printf("\tarea=%7.2f\n", area); } else printf("\tDATA ERROR!\n"); } YS

  37. 例:用字符‘*’打印一个梯形。 main( ) { void pstar(int); int i, n; printf("Enter line number:"); scanf("%d",&n); for(i=1; i<=n; i++) pstar(i*2+3); } void pstar(int sn) /*有参无返回值*/ { int j; for(j=1; j<=sn; j++) printf("*"); printf("\n"); return; } ***** 5个 ******* 7个 ********* 9个 *********** 11个 ************* 13个 YS

  38. 例:已知双曲正弦 ,输入x, 利用ex幂级数的前21项求出sinh(x)的值。 设计分析: 1) 定义函数计算ex,调用实参值分别取 x、-x; 2) 利用循环和迭代公式:t=t*x/i 计算幂级数和;

  39. 程序:#include "math.h" main( ) { float myexp(float), x, y; printf("Enter x:"); scanf("%f",&x); y=(myexp(x)-myexp(-x))/2.0; printf("sinh(%4.2f)=%f\n", x, y); } float myexp(float x) { int i; float sum=1.0, t=1.0; for(i=1; i<=20; i++) { t=t*x/i; sum=sum+t; } return(sum); } YS

  40. 6.5 函数的嵌套调用 在一个函数被调用的过程中,又发生了对另一 个函数的调用,这种现象称为函数嵌套调用;嵌套 的层次可是多层的。 例:用函数求m个元素中取n个的组合。 公式: 定义函数: main: 实现I/O、调用comp函数; comp: 按公式计算、调用阶乘函数fact; fact: 阶乘函数;

  41. 调用关系:main comp fact main( ) { int n, m; long c; long comp(int, int); /*原形声明*/ printf("Enter m,n:"); scanf("%d,%d", &m, &n); c=comp(m, n); /*调用函数*/ printf("C(%d,%d)=%ld\n", m, n, c); } ┇

  42. main( ) { ┅ } long comp(int m, int n) { long s; long fact(int); /*声明函数*/ s=fact(m)/(fact(n)*fact(m-n)); /*嵌套调用*/ return(s); } long fact(int x) { int i; long f=1; for(i=1; i<=x; i++) f=f*i; return(f); } YS

  43. 6.6 函数的递归调用 一个函数被调用的过程中,又发生了直接或间接地自己调用自己,这种现象称为函数递归调用; 递归是一种非常有效的数学方法,也是程序设 计的重要算法。对某些问题的处理,采用递归的方 法比非递归方法更为有效,或者能够更自然、更明 显地反映出解决问题的过程。 但递归调用需要占用大量时间和额外的内存, 应综合考虑后决定是否选用递归方法。

  44. 例:用递归法求阶乘 数学中定义阶乘,可用迭代法和递归法定义: 迭代法:n!= n×(n-1)×(n-2)×…×1 递归法:n!=n*(n-1)! (n>1) 递归过程: 5!=5*4! 24 4!=4*3! 6反 递 3!=3*2! 2推 归 2!=2*1! 1 1!=1

  45. main( ) { long fact(int), y ; int num; printf("Enter n:"); scanf("%d", &num); if(num<0) printf("DATA ERROR!"); else { y=fact(num); printf("%d!=%ld\n", num, y); } } long fact(int n) { long f; if(n==0||n==1) f=1; /*递归控制*/ else f=n*fact(n-1); /*递归调用*/ return(f); } YS

  46. 递归说明: • 函数fact的执行代码只有一组,递归过程就是 • 循环执行这组代码段; • 2) 每次递归调用,都动态地为形参n和局部变量f • 分配存储单元,n接受本次递归传递的实参值; • 3) 由分支条件控制递归的继续或终止; • 注意:要保证递归是有限的 • 4) 递归调用过程始终未出现f的赋值操作,始终未 • 执行return语句; • 5) 递归调用结束的反推过程,将不断执行return和 • f的赋值。

  47. 递归过程动态分配图示: 452 456 460 464 468 472 476 480 484 1 调用3: f=1; 分配 2 调用2: f=2*fact(2-1); 释放 6 调用1: f=3*fact(3-1); 栈底 主调: fact(3)

  48. 递归和反推过程图示: fact被调 main fact自调1 fact自调2 3 1 2 f=2*fact(1); return(f); y=fact(3); 输出y f=3*fact(2); return(f); f=1; return(f); 2 6 1

  49. 例:输入1~32767之间的正整数,用递归方式分离例:输入1~32767之间的正整数,用递归方式分离 各数符并反序输出。如:2466 4 2 void fun(int n) { int d; d=n%10; /*取n当前的个位*/ printf("%d", d); n=n/10; if(n!=0) fun(n); } /*条件成立递归调用*/ main( ) { int n; do { printf("\nEnter n(1-32767):"); scanf("%d", &n);} while(!(n>=1&&n<=32767)); fun(n); } YS

  50. 6.7 数组作为函数的参数 实参形式 传递内容 形参形式 特点 常量 变量 下标变量 同类型变量名 表达式 函数调用 数组名 同类型数组名 调用时动态分配、释放存储单元。 实参的值 单向传递 与实参数组 共用存储单元。 数组指针 单向传递

More Related