1.59k likes | 1.67k Views
第六章 函数 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 函 数和若干个用户 自定义函数构成,这些函数是通过调用来建立联系的。 在这些函数中还可以调用系统提供的各类标 准库函数。
 
                
                E N D
第六章 函数 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函数和若干个用户 自定义函数构成,这些函数是通过调用来建立联系的。 在这些函数中还可以调用系统提供的各类标 准库函数。 本章主要介绍用户自定义函数的定义、调用以及函数间信息的传递。
1. 采用自定义函数的意义 1)符合结构化设计思想,便于设计、调试和扩充,是逐步细化开发方式的具体体现; 2) 每个自定义函数可单独编译,便于模块调试; 3) 避免在程序中设计重复的代码,函数定义一次 便可多次调用,有效地利用了已有的代码资源。
2. C程序各函数之间的关系 1)不管主函数的书写位置如何,C程序总是从主函 数开始运行、并在主函数结束; 2)主函数可调用所有自定义函数,但不能被任何函 数调用; 3)一个自定义函数除了可被主函数调用外,还可被 其它自定义函数调用; 4)自定义函数可调用除主函数之外的其它自定义函 数,甚至可直接或间接地自己调用自己。
函数调用关系图示: 自定义f1 main 自定义f2 ┇ 自定义fn
3. 函数调用的执行流程 嵌套调用: a a main 调用 开始 返回 c b 结束 间接递归调用: 直接递归调用: f1 f2 f 调用 调用 返回 返回
4. C函数的分类 从用户使用的角度分类: 库函数——由系统提供,包括:常用数学库、标准 I/O库、DOS专用库、字符屏幕控制库、 图形库等; 用户自定义函数——根据特定的需要自己编制; 从函数的形式分类: 无参函数——调用时不需要任何参数; 有参函数——调用时必须给出实参,函数依据实参 进行相应处理。
6.2 函数定义的一般形式 1. 有参函数定义的一般形式 [类型标识符] 函数名(形式参数表) 函数头 { 局部声明 函数体 执行语句 } 特点:调用时必须给出实参,函数将依据实参实现 特定操作。 其中—— 类型标识符:说明函数返回值的类型,省略为int型; 函数名:遵循标识符命名规则,名称尽量反映功能;
形式参数:从形式上说明该函数调用时所需的参数形式参数:从形式上说明该函数调用时所需的参数 个数及类型,多个形参间逗号分隔; 局部说明部分:对自定义函数局部使用的变量、数 组等进行类型定义; 执行语句:实现函数功能;其中的返回语句return将 控制程序流程返回主调函数;
例:用自定义函数找出两数中的较大数 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的值作为返回值*/ }
2. 无参函数定义的一般形式 [类型标识符] 函数名( ) { 局部声明 执行语句 } 特点:不依据任何参数来实现特定操作; 注意:尽管没有参数但圆括号不能省去。 例:定义实现延时功能的无参函数 void delay( ) /*无参、无返回值*/ { long i; for( i=0; i<=32000000; i++); }
3. 空函数定义的一般形式 [类型标识符] 函数名( ) { } 作用:用于调试程序时,在逻辑上表示这个函数已 经定义,而不至于影响程序其它部分的编译; 待下一阶段再编制该函数具体内容。
6.3 函数参数和函数返回值 形式参数——定义函数时的参数 实际参数——调用函数时的参数 函数返回值——返回主调函数时带回的结果
例:利用自定义函数,找出三个数中的最大数。例:利用自定义函数,找出三个数中的最大数。 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值作为返回值*/ }
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
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
3) 实参、形参的形式 实参形式 传递内容 形参形式 常量 变量、下标变量 表达式 函数调用 数组名 同类型数组 例:d=max( i%100, 10); /*实参:表达式、常量*/ d=max(n[1], n[2]); /*实参:下标变量*/ d=max((int)sqrt(25), c);  实参的值 单向传递 同类型变量 数组指针 单向传递
4) 由于实参和形参各有各的存储单元,因而实参和 形参可以同名,且互不干扰; 5) 实参与形参在个数、类型、顺序上要对应一致; 否则因编译系统不做匹配性检查,将造成数据传递错误。 如:d=max(7.8, 10.2); /*实、形参类型不一致*/ d=max(a, b, c); /*实、形参个数不一致*/ 6) 若主调函数采用了函数原型声明,在实、形参类 型不一致时将按赋值规则自动转换为同一类型。
上例:观察实参、形参的存储地址 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); }
2. 函数的返回值 若仅返回一个值:可用return返回语句实现; 若需返回多个值:则需要使用其它手段实现; 如:指针、全局变量 返回语句的一般形式:return(表达式); 功能:在自定义函数中使用该语句,可使程序的执 行流程返回到主调函数的调用位置,并将表 达式的值作为返回值。 表达式形式:任何合法的C表达式;
使用说明: 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)); /*永远不可能执行*/ }
2) 若函数不需要用return语句返回值,其类型应采 用void空类型标识符;此时返回语句的形式为: return; 或者干脆省去返回语句; 函数体结束标 志‘}’也可控制流程返回。 如: void printstar( ) { printf("********************\n"); return; /*或省略*/ } 该函数仅仅完成输出一行星号字符的特定操作, 不需要参数,也不需要返回值。
3) return后表达式值的类型一般应与定义函数的类 型一致;若不一致,则以定义函数时的类型为准 自动转换。 如: int average(…… ) { ┇ return(sum/4.0); } /*值为实型,自动转为int型返回*/
6.4 函数的调用 1.函数调用的一般形式 函数名(实参表) 实参形式:可是常量、变量、下标变量、表达式、 函数调用等;多个实参逗号分隔。 注意: 实参在个数、类型、顺序上应与形参对应。 
调用过程: 首先求解各实参,然后将实参值对应传递到为 形参分配的存储单元中,且程序流程转至被调函数, 当被调函数碰到return语句或函数体结束标志‘}’时, 流程返回主调函数,继续后面的操作。 图示: 主调 被调 实参值 返回值
例:定义一函数判断两个量是否相等,相等返回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); }
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( );
3. 对被调函数的声明 要调用一个自定义函数,除了该函数已被定义, 还需要对其做一些必要的声明。 1) 声明方法 (有2种) 简单声明法—— 类型标识符 函数名( ); 特点:此方法在编译时不对参数类型、个数做匹配 性检查,因此调用时实参和形参的类型、个 数必须一致,否则会产生奇怪的结果。
原型声明法—— 类型标识符 函数名(参数类型标识符表 ); 其中:在参数类型标识符表中,按顺序指明所调函 数的各个参数类型。 特点:此方法在编译时将依据声明,校验函数调用 的参数类型、个数是否匹配。个数不匹配则 编译出错;类型不匹配则以形参类型为准自 动转换(赋值兼容)。 建议:采用原型声明法,可充分利用系统检查能力; 为了便于阅读,参数类型后可包含参数名。
例:计算三个数整数部分的和。 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);
2) 声明的位置 主调函数内或源文件开头; 3) 选择声明位置的原则 若函数f1仅被少数(一两个)函数调用,则选择 在主调函数内声明f1,此时对f1的调用只能在这些 带有声明的函数内调用; 若f1被若干函数调用,则选择在源文件开头对 f1声明,此时所有函数都可调用f1且不必再行声明。
4) 下列情况函数声明可省略  函数返回值类型为int型;  被调函数书写在主调函数之前;  函数声明集中书写在源文件开头;
例:void printstar( ); /*声明printstar*/ float fun1(…) { …; printstar( ); /*printstar在开头说明*/ …; } main( ) { … ; /*省略所有被调函数声明*/ x=fun1(…); /*fun1书写在前*/ y=fun2(…); /*fun2类型为int*/ printstar( ); …; } /*printstar在开头说明*/ void printstar( ) { printf("****************"); } intfun2(…) { …… }
函数调用举例 例:已知四边形边长a、b、c、d及一条对角线长e, 求四边形面积。 b a e c d 分析:可分解为求任意三角形面积的问题。将求三 角形面积的功能独立出来,用自定义函数实 现。
#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( ) { ┇ }
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
例:用字符‘*’打印一个梯形。 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
例:已知双曲正弦 ,输入x, 利用ex幂级数的前21项求出sinh(x)的值。 设计分析: 1) 定义函数计算ex,调用实参值分别取 x、-x; 2) 利用循环和迭代公式:t=t*x/i 计算幂级数和;
程序:#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
6.5 函数的嵌套调用 在一个函数被调用的过程中,又发生了对另一 个函数的调用,这种现象称为函数嵌套调用;嵌套 的层次可是多层的。 例:用函数求m个元素中取n个的组合。 公式: 定义函数: main: 实现I/O、调用comp函数; comp: 按公式计算、调用阶乘函数fact; fact: 阶乘函数;
调用关系: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); } ┇
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
6.6 函数的递归调用 一个函数被调用的过程中,又发生了直接或间接地自己调用自己,这种现象称为函数递归调用; 递归是一种非常有效的数学方法,也是程序设 计的重要算法。对某些问题的处理,采用递归的方 法比非递归方法更为有效,或者能够更自然、更明 显地反映出解决问题的过程。 但递归调用需要占用大量时间和额外的内存, 应综合考虑后决定是否选用递归方法。
例:用递归法求阶乘 数学中定义阶乘,可用迭代法和递归法定义: 迭代法: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
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
递归说明: • 函数fact的执行代码只有一组,递归过程就是 • 循环执行这组代码段; • 2) 每次递归调用,都动态地为形参n和局部变量f • 分配存储单元,n接受本次递归传递的实参值; • 3) 由分支条件控制递归的继续或终止; • 注意:要保证递归是有限的 • 4) 递归调用过程始终未出现f的赋值操作,始终未 • 执行return语句; • 5) 递归调用结束的反推过程,将不断执行return和 • f的赋值。
递归过程动态分配图示: 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)
递归和反推过程图示: 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
例:输入1~32767之间的正整数,用递归方式分离例:输入1~32767之间的正整数,用递归方式分离 各数符并反序输出。如:2466 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
6.7 数组作为函数的参数 实参形式 传递内容 形参形式 特点 常量 变量 下标变量 同类型变量名 表达式 函数调用 数组名 同类型数组名 调用时动态分配、释放存储单元。 实参的值 单向传递 与实参数组 共用存储单元。 数组指针 单向传递