Download presentation
Presentation is loading. Please wait.
1
THE C PROGRAMMING LANGUAGE
计算中心- NEU Computer Center 高克宁-gaokening
2
0.本章内容 程序、文件、函数的区别和联系 库函数的使用 用户函数的使用 函数的嵌套调用和递归调用 变量的存储属性 程序举例
大型程序的管理和装配 库函数的使用 用户函数的使用 函数定义 函数调用 函数声明 函数原型的概念 函数执行 函数的嵌套调用和递归调用 变量的存储属性 变量的生存期和作用域 内部函数和外部函数 程序举例
3
1.模块化结构 一个用C语言开发的软件由许多功能组成 从组成上看,各个功能模块彼此有一定的联系,功能上各自独立,
从开发过程上看,不同的模块可能由不同的程序员开发 提出模块化设计 怎样将不同的功能模块连接在一起,成为一个程序 怎样保证不同的开发者的工作既不重复,又能彼此衔接 支持这种设计方法的语言称为模块化程序设计语言
4
1.模块化结构 C语言提供了模块化设计的功能 模块化设计在程序设计中分而治之的策略 将一个大的程序自上向下进行功能分解成若干个子模块
模块对应了一个功能 有自己的界面 有相关的操作 完成独立的功能 各个模块可以分别由不同的人员编写和调试 将不同的模块组装成一个完整的程序 将所有的程序模块象积木一样装配起来
5
1.模块化结构 C采用函数实现功能模块 C语言中一个函数实现一个特定的功能 方便的利用函数作为程序模块实现 C语言程序设计的模块化
由主函数调用其它函数 其它函数也可以相互调用 同一个函数可以被一个函数或多个函数调用任意多次 在设计时,往往将一些常用的功能模块编写成为函数,放在函数库中,供大家选用或多次调用,以减少重复性的编写程序 C程序的功能可以通过函数之间的调用实现 方便的利用函数作为程序模块实现 C语言程序设计的模块化
6
1.模块化结构 C采用函数实现功能模块 一个完整的C程序可以由多个源程序文件组成 一个文件中可以包含多个函数 C程序 源程序文件1
源程序文件i 源程序文件n 预编译命令 函数1 函数n 函数体 函数首部
7
1.模块化结构 C采用函数的特点 允许将一个程序写入不同的源文件 函数是最小的功能单位 不同源文件的组装可以通过工程文件实现
每一个源文件可以独立编译 一个源文件可以被不同的程序使用 函数是最小的功能单位 一个函数可以被不同的源文件的其他函数调用 一个源文件由多个函数组成 主函数可以放在任何一个源文件中 一个C程序只有一个主函数main( ) 不同源文件的组装可以通过工程文件实现
8
1.模块化结构 C采用函数的优点 程序设计变得简单和直观 C语言提供了丰富的库函数 允许用户根据需要定义函数 提高程序的易读性和可维护性
减少程序员的代码工作量 允许用户根据需要定义函数 提高了程序的开发速度和灵活性
9
1.模块化结构 什么是函数? 函数是一段完成特定功能的程序 将程序中反复使用的程序定义为函数的形式 可以用简单的方法为其提供必要的数据
自动执行这段程序 能保存执行后的结果将程序回到原处继续 将程序中反复使用的程序定义为函数的形式
10
1.模块化结构 函数的种类 用户使用的角度 函数的形式 库函数 用户定义函数 无参函数 有参函数
在C语言的编译系统中,提供了很多若干已经编制好的函数,用户可以直接使用 不同的编译系统提供的库函数的名称和功能是不完全相同的 用户定义函数 用户根据需求自己编写的一段程序,实现特定的功能 函数的形式 无参函数 使用该函数时,不需提供数据,直接使用该程序段提供的功能,通常完成某一个处理任务 有参函数 使用该函数时,必须提供必要的数据,根据提供数据的不同,可能获得不同的结果
11
1.模块化结构 说明 本例中共包含了两个函数 主函数可以定义在程序的任意位置 在主函数中 例:求最大值函数
主函数main( ) 用户定义函数max( ) 主函数可以定义在程序的任意位置 函数的定义是平行的 彼此相互独立 不能嵌套定义 在主函数中 调用库函数 调用用户函数 例:求最大值函数 #include <stdio.h> /*函数max()的原型说明*/ int max (int x,int y); void main() {int n1,n2,n3, a; scanf(“%d%d” ,&n1,&n2,&n3); a=max(n1,n2); a=max(a,n3); printf(“最大值是%d” ,a); } 演示82。C /*函数max()的定义*/ int max(int x,int y) { int z ; if (x>y)z=x; else z=y; return (z); }
12
2. 库函数 C语言的库函数 函数库 由编译系统提供的已设计好的函数,用户只需调用而无须实现 函数库是由系统建立的具有一定功能的函数的集合
C的库函数极大地方便了用户 在编写C语言程序时,应当尽可能多地使用库函数 提高程序的运行效率以及提高编程的质量 函数库 函数库是由系统建立的具有一定功能的函数的集合 库中存放函数的名称和对应的目标代码,以及连接过程中所需的重定位信息 用户也可以根据自己的需要建立自己的用户函数库
13
2. 库函数 库函数 头文件(又称包含文件) 提示 存放在函数库中的函数 C语言库函数与用户程序之间进行信息通信时要使用的数据和变量
库函数具有明确的功能、入口调用参数和返回值 头文件(又称包含文件) C语言库函数与用户程序之间进行信息通信时要使用的数据和变量 在使用某一库函数时,使用#include对应该函数所在头文件 提示 不同版本的C语言具有不同的库函数 用户使用时应查阅有关版本的C的库函数参考手册
14
2. 库函数 如何使用库函数? 函数的功能及所能完成的操作 参数的数目和顺序,以及每个参数的意义及类型 返回值的意义及类型
需要使用的包含文件 要调用某个库函数,则需在程序的头部用包含命令(#include)将说明该函数原型的头文件包含进本程序中 例如: include<math.h>
15
2. 库函数 【例】库函数使用的例子 该程序只能在Turbo C或Borland C下运行。
#include <stdio.h> #include <stdlib.h> main () { int i,j,num; for (i=0;i<10;i++) num=rand()%10+1; for (j=0;j<num;j++) printf("*"); printf( "\n" ); } printf("\n") ; } 演示80。C 本例是在屏幕上输出10行*,每一行*的个数是1~20之间的随机数,程序中用到了一个可以产生随机数的库函数——rand(),这个函数的调用格式为: rand(); 调用结果返回一个0~32767之间的随机数。库函数rand的原型说明在头文件stdlib.h中。
16
2. 库函数 随机函数的应用: #include<stdlib.h> #include<stdio.h>
#include<time.h> main() { int i; srand(time(NULL)); for(i=1;i<=20;i++) { printf("%10d“,1+(rand()%10)); if (i%5==0) printf("\n"); } } 演示81C 在随机函数时常见的错误是: 使用srand()代替rand() 函数产生随机数。
17
3. 函数的定义 概念 就是为实现某一具体功能而编写的函数的程序块 用户自定义函数 程序员自行定义和设计的函数
库函数一般只能提供一些低层服务的功能 用户自定义的函数则能针对具体的应用实现一些特殊的功能
18
3. 函数的定义 函数定义的一般形式 函数类型 函数名(形参表说明) /*函数首部*/ { /* 函数体 */ 说明语句 执行语句
例 函数类型 函数名(形参表说明) /*函数首部*/ { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } int max(int x,int y) { int z ; if (x>y)z=x; else z=y; return (z); }
19
3. 函数的定义 函数定义的一般形式 说明 类型类型 规定该函数返回值的类型 函数返回值可以是除数组和函数外的任何合法的数据类型
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 说明 类型类型 规定该函数返回值的类型 函数返回值可以是除数组和函数外的任何合法的数据类型 例:int 、long、float、char、指针、结构体等 系统默认的类型是整类型 如果未指定类型,则返回值为整型 可用void表示如果函数没有返回值 例如:void main()
20
3. 函数的定义 函数定义的一般形式 说明 函数名 是用户自定义的标识符 是C语言函数定义中唯一不可省略的部分 需符合C语言命名规则的标识符
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 说明 函数名 是用户自定义的标识符 是C语言函数定义中唯一不可省略的部分 需符合C语言命名规则的标识符 用在程序中标识函数,并用该标识符调用函数 一个好的函数名应该能够反映该函数的模块的功能 函数名本身代表数值 它代表了该函数的入口地址
21
3. 函数的定义 函数定义的一般形式 说明 形参表(也称“形式参数”) 形参表是用逗号分隔的一组变量说明 包括形参的类型和形参标识符
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 说明 形参表(也称“形式参数”) 形参表是用逗号分隔的一组变量说明 包括形参的类型和形参标识符 作用是指出每一个形参的类型和形参的名称 当调用函数时,接受来自主调函数的数据,确定各参数的值 形参表说明的两种表示形式: 提示 调用函数需要多个原始数据,就必须定义多个形式参数 一个函数允许没有形参,但()不能省略 在")"后面不能加分号";“ int func ( x, y ) int x, y; { …… } int func (int x, int y ) { …… }
22
3. 函数的定义 函数定义的一般形式 说明 函数体 用{ }括起来的部分是函数的主体 函数体是一段体现函数功能的程序
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 说明 函数体 用{ }括起来的部分是函数的主体 函数体是一段体现函数功能的程序 确定该函数应完成的规定的运算 应执行的规定的动作 函数内部应有自己的说明语句和执行语句 形式参数表中经过说明的变量可以在函数体内直接使用 但函数内定义的变量不可以与形参同名 提示 函数体部分可以没有,空函数,但是每个函数体外面的一对{ }不能省略 任何情况下不允许一个函数内部定义另外一个函数(函数不能嵌套定义) 较小的函数可以提高软件的可重用性
23
3. 函数的定义 函数定义的一般形式 说明 函数返回值 函数返回值类型 函数调用后函数体计算/执行的结果称为函数的返回值
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 说明 函数返回值 函数调用后函数体计算/执行的结果称为函数的返回值 通过返回语句将计算结果带回主调函数 函数返回值类型 函数定义时的类型就是函数返回值的类型 C语言要求函数定义的类型应当与返回语句中表达式的类型保持一致 当两者不一致时,系统自动进行转换,将函数返回语句中表达式的类型转换为函数定义时的类型 提示 函数的类型决定着返回值的类型 当两种类型不能转换时,在编译时会提示错误信息 系统进行自动的转换会造成程序的不清晰、可读性差等
24
3. 函数的定义 函数定义的一般形式 说明 函数返回值 分析程序的运行结果
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 说明 函数返回值 分析程序的运行结果 #include <stdio.h> main( ) { float a,b; int c; scanf ("%f%f", &a, &b); c = max(a,b); printf ("MAX is %d\n", c); } max ( float x, float y ) { float z; z = x>y ? x : y; return(z); } 运行时,若从键盘输入: 结果为:6
25
3. 函数的定义 函数定义的一般形式 返回语句 格式 功能 赋给当前被调函数一个值 结束当前被调函数的执行,将表达式的值带回主调函数
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 返回语句 格式 功能 赋给当前被调函数一个值 结束当前被调函数的执行,将表达式的值带回主调函数 return 表达式; /*return(2*3)表示函数返回值为6 */ return(表达式); /*return 2*3 表示函数返回值为6 */ return; /*没有返回值*/
26
3. 函数的定义 函数定义的一般形式 返回值语句 提示 函数的返回值只能有一个 当函数中不需要指明返回值时可以写成:return;
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 返回值语句 提示 函数的返回值只能有一个 当函数中不需要指明返回值时可以写成:return; 并不是函数执行后没有值 只是没有被用户指定并由系统分配函数一个随机值 函数中不需要指明返回值时可以不写返回语句 函数运行到右花括号自然结束 此时,并不意味该函数不带回返回值,而是带回一个不确定的值,有可能给程序带来某种意外的影响
27
3. 函数的定义 函数定义的一般形式 返回值语句 提示 一个函数体内可以有多个返回语句,不论执行到哪一个,函数都结束,回到主调函数 例如
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 返回值语句 提示 一个函数体内可以有多个返回语句,不论执行到哪一个,函数都结束,回到主调函数 例如 对带有返回值的函数,在调用时,可以将其作为同类型的一个变量 一样使用 sign(int x) { if(x>0) return(1); if(x==0) return(0); if(x <0) renturn(-1); } int a,b,c,max; max=max(c,max(a,b));
28
3. 函数的定义 函数定义的一般形式 返回值语句 提示
函数类型 函数名(形参表说明) { /* 函数体 */ 说明语句 执行语句 [ return;函数返回值] } 函数定义的一般形式 返回值语句 提示 为了保证函数不返回任何值,C语言规定,可以定义无类型(void类型)函数,同时函数体可以省略使用return语句,如果使用也只能使用return;(不能带任何参数)
29
3. 函数的定义 空类型 说明没有返回值 说明没有形参 用途 C语言规定,一个函数可以没有返回值,函数类型应明确定义为 void(空)类型
同时也禁止在调用函数中使用被调函数的值 说明没有形参 形参表明确地写明void,表示没有形式参数 例如:void main(void) 用途 表示一个函数没有返回值 用来指明一个通用型的指针
30
3. 函数的定义 空类型 格式 说明 void类型的函数不是调用函数之后不再返回,而是调用函数在返回时没有返回值
有返回值的函数可以将函数调用放在表达式的中间,将返回值用于计算 void类型的函数不能将函数调用放在表达式之中,只能在语句中单独调用 void类型的函数一般用于完成一些规定的操作 void 函数名(形参表) { …… }
31
3. 函数的定义 空类型 例题分析 定义为“void”型的函数,在调用时不返回任何值,所以不能将其使用在赋值语句后表达式中
在主调程序中引用了void函数。编系统在编译这个程序时,会提示错误信息:“not an allowed type in function main” 例: void printchar(char c) { printf(“%c=%d” ,c,c); } main() {int k; k=printchar(‘a’); /* printchar没有返回值*/ printf(“%d\n” ,k); } 演示83。C
32
3. 函数的定义 问题1: void fun1(a,b,c) { printf(“%d%d%d” ,a,b,c);}——??
正确格式:void fun1( int a,int b,int c ) 问题2: float fun2(float a,float b) { float a,b,c; c=a+b; return(c);}——?? 形参被重复定义 问题3:已知函数abc的定义格式为: void abc() {……} 则函数定义中void的含义是____。 A.执行函数abc后,函数没有返回值 B.执行函数abc后,函数不再返回 C.执行函数abc后,可以返回任何类型 D.以上答案都是错误的
33
3. 函数的定义 【例】指出如下程序段中的错误。 int fun(void) { printf(“inside fun \n”);
} int sum(int x,int y) { int result; result=x+y; (c) int sum(int x) { if(x==0) return 0; else x+sum(x-1); 错误:函数fun1定义在函数fun的内部. 改正:将函数fun1的定义移出函数fun之外. 错误:函数应该返回一个整数值,但却没有返回值语句. 改正:添加一个return语句. return result; 或删除后 return x+y; 错误:没有返回x+sum(x-1)的结果. 改正:把else块中的语句改为: return x+sum(x-1);
34
3. 函数的定义 空函数 用途 格式 在一个软件开发的过程中,模块化设计允许将程序分解为不同的模块 说明 函数名( ) { }
整体上要求功能完备 不同的开发人员设计不同的模块 某些模块可能空缺,留待后续的开发工作完成 为了保证整体软件结构的完整性,将其定义为空函数 作为一个接口 格式 说明 没有函数类型说明 没有形参表 同时函数体内也没有语句 函数名( ) { }
35
3. 函数的定义 无参函数的定义 不需要向被调函数提供数据,则该函数成为无参函数 无参函数定义的形式
多数无参函数用以完成某一操作,而不需要带回一个结果 可以不指明函数的类型 无参函数定义的形式 函数名() { 说明语句 执行语句 }
36
3. 函数的定义 无参函数的定义 例:编写一个函数,输出“c programming."。
#include <stdio.h> func2 ( ) { printf (“&&&&&&&&&&&&&&&\n"); printf (" c programming .\n"); printf (“&&&&&&&&&&&&&&&\n"); } main( ) { func2( ); }
37
3. 函数的定义 函数定义小结 C语言的程序中函数很多, main是唯一不可缺少的函数 C语言的一个程序可以由多个文件组成
是整个程序的主函数 程序运行时,从主函数开始执行 一个程序由多个函数组成时,主函数main与程序中其它函数的任意次序排列 main作为最后一个函数放在文件结尾处,系统也总是要从main开始运行 C语言的一个程序可以由多个文件组成 每个文件可以单独编译 将几个文件一起联接成一个可运行文件 一个函数的定义不能跨越在两个文件中 函数在一个文件中必须保持完整 一个程序的多个函数定义中,不允许存在相同的函数名
38
3. 函数的定义 函数定义小结 C语言的函数可以分为两类 函数不能嵌套定义 带返回值的函数 不带返回值函数
通过调用可以得到一个确定的值,该值就是所调用函数的返回值 不带返回值函数 不带返回值的函数通常只是一个“过程” 只是用来完成某种操作 主调函数并不需要它有任何的信息返回 调用时可将其作为C的一个语句直接调用 函数不能嵌套定义 一个函数的定义不能从属与另外一个函数
39
3. 函数的定义 函数定义小结 无论有无形参,函数名后的()不能省略 形式参数表中说明的形参,在函数体中不再需要说明
可以同一般的变量完全一样直接使用 函数体内部的变量说明与形参说明的含义不同 前者说明函数体的一个局部变量,后者说明一个用于函数间传送数据的形式变量 最好在形参列表中列出每个参数的类型 即使参数是默认的int 不应省略对每个形参的类型说明 例如: float max(float x,float y)不能写成 float max(foat x,y),此时y为系统缺省的int类型
40
3. 函数的定义 函数定义小结 应尽可能多的使用系统提供的库函数 传递给函数的参数和函数定义中的相应的参数尽量不使用相同的名字
选择有意义的参数名和函数名可以使程序具有良好的可读性 可以避免过多地使用注释 需要大量参数的函数可能包含较多的功能(任务) 应该考虑将该函数分成完成单个任务的较小的函数 函数原型、函数头部和函数调用应该具有一致的参数个数、参数类型、参数顺序和返回值类型
41
3. 函数的定义 函数定义小结 常见错误 如果指定了非整型int类型的返回值类型,则省略函数定义中的返回值类型会导致语法错误
忘记从应该返回一个值的函数返回该值会导致无法预料的错误 ANSI C 标准规定被省略的结果是不确定的、随机的 从返回类型为void的函数返回一个值会导致语法错误 定义函数时,在其参数列表的)后以“;”结束是一种语法错误 形参的类型说明一定放在函数体{}内会导致语法错误 在函数体内部对形式参数再次定义是一种语法错误
42
THE C PROGRAMMING LANGUAGE
计算中心- NEU Computer Center 高克宁-gaokening
43
0.本章内容 程序、文件、函数的区别和联系 库函数的使用 用户函数的使用 函数的嵌套调用和递归调用 变量的存储属性 程序举例
大型程序的管理和装配 库函数的使用 用户函数的使用 函数定义 函数调用 函数声明 函数原型的概念 函数执行 函数的嵌套调用和递归调用 变量的存储属性 变量的生存期和作用域 内部函数和外部函数 程序举例
44
4. 函数调用 定义 一个函数调用另一个函数 主调函数使用被调函数的功能,称为对被调函数的调用 函数调用的基本形式 调用者称为主调函数
被调用的函数称为被调函数 主调函数使用被调函数的功能,称为对被调函数的调用 函数调用的基本形式 通过函数名 函数的参数 例:求最大值函数 #include <stdio.h> int max (int x,int y); void main() {int n1,n2,a; scanf(“%d%d” ,&n1,&n2); a=max(n1,n2); printf(“最大值是%d” ,a); } int max(int x,int y) { int z ; if (x>y)z=x; else z=y; return (z);
45
4. 函数调用 函数调用方式 一般形式 说明 函数名 (实参表)[;] 函数名 实参表 例: int fun3() { int i,j,k;
j=i*i; k=j*j; printf(“%3d%3d%3d” , i,j,k); } main() { fun3(); } 函数调用方式 一般形式 函数名 (实参表)[;] 说明 函数名 确定了被调函数的对象 实参表 函数调用时()内的变量名 是执行被调函数时主调函数传递给形参的实际参数 可以没有 也可以是常量或常量表达式 还可以是其它各种表达式 提示 参数的类型、个数和次序应与函数要求的形式参数完全统一
46
4. 函数调用 函数调用方式 按照函数在主调函数中的作用,函数的调用方式有以下三种形式 函数语句 函数表达式 函数参数
47
4. 函数调用 函数调用方式 函数语句 作为C程序中的一条语句调用被调函数 调用格式: 函数名(实参表); 说明 例
通常只完成一种操作,不带回返回值 常用于调用一个可以忽略返回值或没有返回值的函数 只要求函数完成一定的操作 例 printf(“最大值是%d” ,a); 或: fun();
48
4. 函数调用 函数调用方式 函数表达式 函数调用出现在一个表达式中,这个表达式称为函数表达式 格式 变量名=函数表达式; 说明 例
将函数的调用结果作运算符的运算分量 格式 变量名=函数表达式; 说明 这种方式用于调用带有返回值的函数 要求函数必须带回一个确定的值参与表达式的运算 例 a=max(n1,n2); 或: a=fun(x)+fun(y);
49
4. 函数调用 函数调用方式 函数参数 函数调用作为另外一个函数的实际参数 格式 说明 例 fun1(x,fun2);
这种方式要求函数必须带回一个确定的值 函数的参数也是表达式 例 m=max(max(a,b),c); 或 printf(“%d” ,max(a,b));
50
5. 函数声明 被调函数的声明 一个函数要调用另外一个函数 函数的调用也应遵循“先定义后使用”的原则 首先是被调用的函数必须存在
其次还应在主调函数中对所有被调函数加以说明 否则,在连接时会出现找不到所调用函数的错误信息 为什么? C语言可以由若干个文件组成,每一个文件可以单独编译,在编译程序中的函数调用时,如果不知道该函数参数的个数和类型,编译系统就无法检查形参和实参是否匹配 为了保证函数调用成功,必须为编译程序提供所调用函数的返回值类型和参数的类型及个数 函数的调用也应遵循“先定义后使用”的原则
51
5. 函数声明 被调函数的声明 库函数的声明 被调函数是C语言系统提供的标准库函数
在源程序文件的开头处,使用#include命令,将存放所调用库函数的有关“头文件”包含到该程序文件中来 #include命令的一般形式为 #include<math.h> #include”stdio.h”
52
函数类型 函数名(形参类型1 形参名1,形参类型2 形参名2 …)
5. 函数声明 被调函数的声明 用户自定义函数的声明 被调用函数为用户自己定义的函数 一般情况下,应在主调用函数中对被调用函数(返回值)的类型进行说明 形式 说明 为编译系统进行类型检查提供依据 函数值是什么类型 有多少个参数 每一个参数是什么类型的 函数类型 函数名(形参类型1 形参名1,形参类型2 形参名2 …)
53
5. 函数声明 被调函数的声明 用户自定义函数的声明 提示:在下列情况下可以省略函数声明 当函数的返回值为整型或字符型时
如果在同一个文件中既定义函数,又调用该函数 不论定义函数与调用函数在源程序中的位置关系如何,都可以省去函数说明 此时系统对其自动按整型处理 例:求两个非负数的最大公约数 分析:采用“转辗相除”法 若a>b,求a/b的余数r; 若r=0,则b为最大公约数; 若r!=0,则 r->b, b-> a, 再一次进行a/b求余数r, r=0, b为最大公约数
54
5. 函数声明 源程序: main() { int a,b,r; scanf("%d%d",&a,&b); 声明的被调函数gcd(x,y)
r=gcd(a,b); printf("gcd(a,b)is %d\n",r); } int gcd(x,y) int x,y; { int a,b,t; if(x<y) { t=x; x=y; y=t; } a=x; b=y; while (b!=0) { t=a%b; a=b; b=t; } return(a); } 演示88。C 声明的被调函数gcd(x,y) 为int类型,所以在main()中未对gcd(x,y)类型(返回值的类型)进行说明。在主调函数main()中没有出现int gcd();声明语句。
55
5. 函数声明 被调函数的声明 用户自定义函数的声明 提示:在下列情况下可以省略函数声明 当被调函数的定义出现在主调函数的前面时
如果被调用函数的返回值是其它类型(非整型或字符型) 函数定义和函数调用在同一个文件中,且函数定义在源程序中的位置在调用该函数之前
56
5. 函数声明 源程序: float max-value(x,y,z) float x,y,z; { float max;
max=x>y?x:y; max=max>z?max:z; return(max);} main() { float a,b,c,max; scanf(“%f%f%f”,&a,&b,&c); max=max_value(a,b,c); printf(“%f” ,max); } 。c 被调函数max_value()的定义放在主调函数main()之前,所以在main()中不再进行对max_value()函数的说明。
57
5. 函数声明 被调函数的声明 用户自定义函数的声明 提示:在下列情况下可以省略函数声明 当全局声明被调函数时
在所有函数定义之前(即在文件的开始处),对函数类型进行了说明 在主调用函数中不再对被调函数(返回值)的类型进行声明 这种方法最常用
58
5. 函数声明 样式: float max_value(); void cpy(); int count(); main() { … }
void cpy (s1,s2) char s1[30] ,s2[20] ; { … int count (char str,int n) { … main()不必再声明所调用的函数类型。因此没有包含 float max_value(); void cpy(); int count(); 的声明语句,即没有对这三个被调函数进行类型说明。
59
5. 函数声明 被调函数的声明 用户自定义函数的声明 提示:在下列情况下必须函数声明 如果函数定义在源程序中的位置在调用该函数之后
必须在调用该函数的函数中给出被调用函数的说明 如果函数的定义与调用在两个不同的文件中 不论函数返回值的类型是什么,在调用该函数时都必须给出函数声明
60
5. 函数声明 被调函数的声明 提示 函数的声明和函数的定义形式上十分相似,但两者上有本质上的不同的 函数的定义是一个完整而独立的函数单元
包括函数类型、函数名、形参及形参类型、函数体等 在程序中,函数的定义只能有一次 函数首部后没有“;” 函数的声明仅是对编译系统的一个说明 是对已定义过的函数返回值的类型说明,以通知系统在本函数中所调用的函数是什么类型 不包括函数体(或形参) 调用几次该函数,就应在各个主调函数中各自做多次声明 是一个说明语句,必须以分号结束
61
5. 函数声明 被调函数定义在主调函数之后,所以在主调函数的声明部分对被调函数的类型(既返回值的类型)进行了说明。也可以写成: float max_value (float,float,float); 被调函数的声明 例 main() { float a,b,c,max; float max_value();/*对被调函数类型进行声明*/ scanf(“%f%f%f”,&a,&b,&c); max=max_value(a,b,c); printf(“%f” ,max); } float max-value(x,y,z) float x,y,z; { float max; max=x>y?x:y; max=max>z?max:z; return(max); } 演示87。C
62
5. 函数声明 函数原型 对被调函数的声明 简化对被调函数的声明 通常将一个文件中需调用的所有函数原型写在文件的开始
编译系统需知道被调函数有几个参数,各自是什么类型,而参数的名字是无关紧要的 简化对被调函数的声明 上述方式称为函数的原型 例 float max_value (float,float,float); /* 仅声明形参的类型,不必指出形参的名 */ 通常将一个文件中需调用的所有函数原型写在文件的开始 函数类型标识符 函数名(形参类型1,形参类型2…… );
63
5. 函数声明 小结 思考题: 写法上要注意 常见错误 函数定义的首部需要写出所有形参名并给出其对应的数据类 型
函数原型的主要目的在于声明函数返回值的类型以及函数期望接收的参数的个数、参数类型和参数顺序 如果程序中没有某个函数的函数原型(没有说明),编译系统就会用第一次出现的这个函数(函数定义或函数调用)构造函数原型 在缺省下编译系统假设函数返回类型为int 而对函数参数类型不做任何假设 常见错误 和函数原型不匹配的函数调用会导致语法错误 函数原型和函数定义不一致,也会产生错误 思考题: 什么时候说明函数? 为什么要说明函数原型?
64
6. 函数执行过程 通过函数的调用实现函数的使用 函数调用指定了被调用函数的名字和调用函数所需要的参数 执行过程
程序在主调函数执行过程中,执行到调用被调函数的语句时,程序将实际参数传递给形式参数 执行被调函数的函数体语句,直到遇到return语句或“}”,结束被调函数的执行 (带值)返回到主调函数 执行主调函数的剩余语句部分
65
6. 函数执行过程 〖例〗] int i=10; main() { int j=1; j=fun(); printf(“%d” ,j); }
执行过程: (1)主调程序main()顺序执行,第一个函 数调用语句j=fun();处理“=”右边的调用函 数fun()。 (2)执行fun()函数体, k=k+i=10;i=20; (3)执行到return( k )语句,带回返回值 ( k= 10 ) 。 (4)被调函数fun()的返回值赋给“=”左边的 变量j,j=10; (5)执行库函数printf,打印j的值。 (6)执行第二个函数调用语句j=fun(); (7)执行fun()函数体, k=k+i=20; i=i+10=20; (8)执行到return( k )语句,带回返回值 ( k= 20 ) 。 (9)被调函数fun()的返回值赋给“=”左边的 变量j,j=20; (10)执行库函数printf(),打印j的值。 〖例〗] int i=10; main() { int j=1; j=fun(); printf(“%d” ,j); } func() {int k=0; k=k+i; i=i+10; return( k ); } 演示89。C
66
7.参数传递 函数之间的数据传递 C语言的一个源程序文件可以由多个函数组成 C语言中采用三种方式进行数据传递 每个函数在功能上是独立的
最终目地就是共同实现某一特定的功能,以解决某个具体的实际问题 一般情况下,常常要求同一个函数可以根据不同的数据,进行相同的处理之后得到不同的结果 函数与函数之间通常要传递数据和计算结果 C语言中采用三种方式进行数据传递 参数传递 实参与形参一一对应方法在主调函数与被调函数之间传递多个数据 返回值传递 被调函数返回主调函数一个结果值 全局变量传递 在一个函数内使用其他函数中的某些变量的结果
67
7.参数传递 形参与实参 形参是函数定义时由用户定义的形式上的变量 实参是函数调用时,主调函数为被调函数提供的原始数据
定义函数时函数名后()内的变量列表 形参与函数体内定义的其它变量一样在函数体内引用 这些变量在未调用函数之前并不实 际占用内存 实参是函数调用时,主调函数为被调函数提供的原始数据 调用函数时给该函数形参具体指定的值 是常量、表达式或变量 int max(int x,int y) 例如: main() {…. a=max(num1,num2); …..}
68
7.参数传递 参数传递方式——"值传递” 含义 C语言的"值传递“ 在调用函数时,将实参变量的值取出来,复制给形参变量
C语言中的实参可以是一个表达式 调用时先计算表达式的值,再将结果(值)复制到形参对应的存储单元中 在函数内部使用从实参中复制来的值进行处理 形参是函数的局部变量 仅在函数内部才有意义 不能用它来传递函数的结果 一旦函数执行结束形参存储单元所保存的值不再保留 C语言的"值传递“ 既可以在函数之间传递“变量的值” 也可以在函数之间传递"变量的地址"
69
7.参数传递 “传值” 特点 数据在主调函数和被调函数中占用不同的存储空间
在函数内部对形参的任何操作,其结果只能影响形式参数的值,而不影响实际参数的值 可以避免被调用函数的操作对调用函数中的变量可能产生的副作用 此种方式只能实现外部数据向函数内部的传递,而不能实现函数内部数据的传出
70
7.参数传递 “传值” 例: void change(int k) {printf(“2·k=%d\n” ,k); k=k+10;
} main() {int i; i=10; printf(“1·i=%d\n” ,i); change(i); printf(“3·i=%d\n” ,i); } 演示8A。C 实参i 10 调用之前 形参k 10 (调用开始时) 20 (调用结束时)
71
7.参数传递 “传值” 例:分析下面程序结果 main () { int a,b; void p(int,int); a=5; b=8;
p(a,b); p(a+b,a); p(a/b,b);} void p(int x,int y) { y=x+y; printf("%d,%d\n",x,y); } 演示8E。C 则程序的执行结果为: A.5,13 B.5,13 C.5,13 D.5,13 13, , , ,23 0, , , ,13
72
7.参数传递 “传值” #include “math.h” main() { int m,sum=0;
例:求100~200之间的全部素数之和 #include “math.h” main() { int m,sum=0; for (m=101;m<=200;m=m+2) sum+=sum(m); printf(“%d\n” ,s); } int sum(int m) { int i,k; k=sqrt(m); for (i=2;i<=k;i++) if(m%i==0) return (0); return (m); } hs2。c
73
7.参数传递 源程序: #include<stdio.h> main( ) { int k=2,n=0; do
例题 孪生素数是指两个相差为2的素数, 例如:3和5,5和7,11和13等。编 程实现输出15对孪生素数。 源程序: #include<stdio.h> main( ) { int k=2,n=0; do { if(isprime(k)&&isprime(k+2)) { n+=1; printf(“%d,%d”,k,k+2);} k=k+1; } while(n<15); } isprime(int a); { int k,j; j=1; k=2; while((k<=a/2)&& j) { if (a%k= =0)j=0; else k=k+1; } return(j); } HS3。C
74
7.参数传递 “传值” 源程序: main ( ) { int x, y; x=3; y=5;
例题 若在主函数中变量x=3,y=5,编写 一个函数交换主函数中两个变量的 值,使变量x=5,y=3。 “传值” 源程序: main ( ) { int x, y; x=3; y=5; printf (“before swap x=%d, y=%d\n”, x, y); swap(x, y); /* 用变量x和y作为实际参数调用函数 */ printf (“after swap x=%d, y=%d\n”,x,y); } swap ( int m, int n) { int temp; /* 借助临时变量交换两个形参变量m和n的值 */ temp = m; /* ① */ m = n; /* ② */ n= temp; /* ③ */ printf ("in swap m=%d, n=%d\n", m, n); } before swap x=3, y=5 in swap m=5, n=3 after swap x=3, y=5
75
7.参数传递 “传址” 传递地址 在值传递方式下,每个形式参数仅能传递一个数据 将数据的存储地址作为实参传递给被调函数的形参
当需要在函数之间传递大量数据时,值传递方式显然不适用 将数据的存储地址作为实参传递给被调函数的形参 允许被调用函数修改原始的变量值 要求形参的数据类型应是指针类型 进入函数时,系统为其分配存储空间 将实参中的地址数据复制到形参中,在被调函数内部通过对形参(指针)的操作实现对外部数据的引用 函数结束时,形参所占用的空间被系统收回 此函数对数据处理结束
76
7.参数传递 “传址” 特点 详细内容参见指针 处理数据在主调函数和被调函数中占用相同的存储空间
形参和实参所指向的地址是同一个地址 对形参的操作会直接影响到实参的结果 此种方式即可以实现外部数据向函数内部的传递,也可以实现加工后的数据从函数内部向主调函数的传递 详细内容参见指针
77
7.参数传递 函数参数 提示 形参在被调函数中定义,实参在主调函数中定义 形参是形式上的 实参可以是变量名或表达式
定义时编译系统并不为其分配存储空间(无初值,有随机值) 函数调用时,临时分配存储空间,接受并处理实参 函数调用结束,形参被释放(数据消失) 实参可以是变量名或表达式 必须在函数调用之间有确定的值 实参与形参之间是单向的值传递 实参的值传给形参 实参与形参必须类型相同,个数相等,一一对应 当实参之间有联系时,不同的编译系统下实参的求值顺序不同 Turbo C是从右向左
78
7.参数传递 数组作函数参数 两种方式 数组中的元素作函数的参数 数组名作函数的参数 例:int a[5]; a a[2] a int x
pa int* pa; 或 int b[5];
79
7.参数传递 数组作函数参数 数组元素作函数的参数 可以采用传递普通变量一样的方式 当用数组中的元素作函数的实参时
以单个元素的方式实现数组元素的传递 “复制数据” 的方法 由于形参是在函数定义时定义,并无具体的值,因此数组元素只能在函数调用时,作函数的实参 当用数组中的元素作函数的实参时 必须在主调函数内定义数组,并赋值 实参与形参之间是“值传递”的方式 函数调用之前,数组已有初值 调用函数时,将该数组元素的值传递给对应的形参 两者的类型应当相同
80
7.参数传递 数组作函数参数 数组元素作函数的参数 例:求一维数组a[4]的所有元素之和 数组元素作实参
float sumall(float a,float b,float c,float d); { return(a+b+c+d); } main() { float sum; static float a[4]={1,2,3,4}; sum=sumall(a[0] ,a[1] ,a[2] ,a[3]); printf(“sum=%f\n” ,sum); } 演示8B。C 在调用sumall函数时,将 数组元素a[0] …a[3]作为 实参把值传递给sumall函 数的a,b,c,d的四个形 参,是单向的按值传递。
81
7.参数传递 数组作函数参数 数组名作参数 数组名作函数的参数,必须遵循以下原则 参数形式 如果形参是数组名,则实参必须是实际的数组名
如果实参是数组名,则形参可以是同样维数的数组名或指针 数组定义 要在主调函数和被调函数中分别定义数组 类型 实参数组和形参数组必须类型相同 形参数组可以不指明长度
82
7.参数传递 数组作函数参数 数组名作参数 提示 C语言允许将整个数组作为函数的参数进行传递 数组名代表了该数组在内存中的首地址
采用的是按址传递方式 调用方式 只需将数组名作为参数直接调用函数即可,无须使用下标 实参数组名将该数组的起始地址传递给形参数组,两个数组共享一段内存单元 编译系统不再为形参数组分配存储单元实际上 这种数组的传递是采用指针实现的
83
7.参数传递 数组作函数参数 数组名作参数 例:分析程序的执行过程 分析 调用时,实参数组将首地址string 赋值给形参数组str
#include <stdio.h> print ( char str[ ] ) { printf ("%s",str); } main( ) { char string[20]= "C Programming"; print(string); /* 数组名做函数的实参 */ } 7.参数传递 数组作函数参数 数组名作参数 例:分析程序的执行过程 分析 调用时,实参数组将首地址string 赋值给形参数组str 两个数组共同占用相同的内存单元,共享数组中的数据 string[0]与str[0]代表同一个元素 形参数组的长度与实参数组的长度可以不相同 形参数组可以不指明长度 问题? 形参数组长度与实参数组长度不 等长时,会出现什么现象?
84
7.参数传递 数组作函数参数 数组名作参数 提示 传递给函数的数组参数是不带()括号的数组名
在数组传递给函数时,为了使函数能够处理指定个数的数组元素,通常将数组的大小同时传递给函数 将数组传递被调函数,意味着被调用函数能够修改原始数组的元素的值 因为传递了数组名(数组的起始地址),所以被调函数能够准确地知道该数组的存储位置 被调函数在函数体中修改数组元素时就是在修改实际的数组元素 按址方式传递数组对提高性能是有意义的 如果采用传值的方式传递数组就会传递每一个元素的值拷贝 对于频繁传递的大 型数组,对数组进行拷贝会消耗一定的时间和占用大量的存储空间
85
7.参数传递 数组作函数参数 演示:传递整个数组和传递一个数组元素之间的差别 #include <sdtio.h>
#define SIZE 5 void modifyarray(int [ ],int); void modifyelement(int); main( ) { int a[SIZE]={0,1,2,3,4}; int i; pritnf(“\nThe values of the original array are:\n”); for (i=0;i<=SIZE-1;i++) printf(“%3d”,a[i]); printf(“\n”); modifyarray(a,SIZE); /*以传址方式传递数组a*/ pritnf(“The values os the modified array are:\n”);
86
7.参数传递 for(i=0;i<=SIZE-1;i++) printf(“%3d”,a[i]);
printf(“\nThe values of a [3]is %3d\n”,a[3]); modifyelement(a[3]); printf(“The values of a[3]is %3d\n”,a[3]); } void modifyarray(int b[ ],int size) { int j; for (j=0;j<=size-1;j++) b[j]*=2; } void modifyelement(int x) { printf(“Velue in modifyelement is %3d\n”,x*=2); } 演示8C。C
87
7.参数传递 数组作函数参数 例题 【例】求任意输入10个数的平均值。(数组名作实参) #include “stdio.h” main()
{ int i; float a[10],ave; float aver(float a[10]); for (i=0;i<10;i++) scanf(“%f” ,&a[i]); ave=aver(a); printf(“\naver=%6.2f\n” ,ave); } float aver(float b[10]) { int i; float s=0.0,ave; s+=b[i] ; ave=s/10; return(ave); } HS1。C
88
7.参数传递 数组作函数参数 例题 下面程序的输出结果是( ). #include<stdio.h>
下面程序的输出结果是( ). #include<stdio.h> int fun(int x[ ],int n) { int i, s=1; for(i=0; i<=n; i++) s*=x[i]; return s; } main() { int a[ ]={1,2,3,4,5,6}; int x=fun(a,4 ); printf(“%d”,x); A B C D.5 本题主要测试数组在函数之间 传递的用法。在函数间传递数 组时,通常传递的是数组的首 地址。由于数组元素是连续存 放的,因此可以根据该地址找 到数组的所有的元素。由于传 递数组的长度为 4,因此函数 fun中的计算只包括元素1,2, 3,4,5,函数返回的值是 1*2*3*4*5=120。所以输出是120。
89
7.参数传递 数组作函数参数 例题 编写一个函数,分别求两个浮点数数组元素的平均值。并在主调函数中输出
float average(arrary,n) int n; float arrary[ ] ; { int i; float aver,sum=0.0; for (i=0;i<n;i++) sum+=arrary[i] ; aver=sum/n; return(aver); } 定义形参时未指明数组大小, 而且引进一个参数 n,以便 两次调用 average 函数时, 两个实参数组的长度可以先 后传递给n,从而在average 函数体中能分别访问两个数 组的所有元素.
90
7.参数传递 数组作函数参数 例题 编写一个函数,分别求两个浮点数数组元素的平均值。并在主调函数中输出 main()
{ float a[8] ,b[9] ; int i; for (i=0;i<8;i++) scanf(“%f” ,&a[i]); for (i=0;i<9;i++) scanf(“%f” ,&b[i]); printf(“aver=%7.2f\n” ,average(a,8)); printf(“aver=%7.2f\n” ,average(b,9)); } hs4.c
91
THE C PROGRAMMING LANGUAGE
计算中心- NEU Computer Center 高克宁-gaokening
92
0.本章内容 程序、文件、函数的区别和联系 库函数的使用 用户函数的使用 函数的嵌套调用和递归调用 变量的存储属性 程序举例
大型程序的管理和装配 库函数的使用 用户函数的使用 函数定义 函数调用 函数声明 函数原型的概念 函数执行 函数的嵌套调用和递归调用 变量的存储属性 变量的生存期和作用域 内部函数和外部函数 程序举例
93
8.函数嵌套调用 嵌套调用 C语言中的函数定义是平行的 函数不允许进行函数的嵌套定义 函数之间的调用可以是任意的 函数嵌套定义
函数间没有主从关系 函数不允许进行函数的嵌套定义 在一个函数体中再定义一个新的函数 函数之间的调用可以是任意的 函数允许嵌套调用 允许在一个函数体内再调用其他函数 函数嵌套定义 指一个函数在被其它函数调用的过程中,本身还被允许调用另一个函数 在函数体中再调用其它函数
94
8.函数嵌套调用 嵌套过程 函数的嵌套调用为自顶向下、逐步求精及模块化的结构设计提供了有利的支持
如果在实现一个函数的功能时需要用到其它函数的功能,就采用函数的嵌套调用 main() {… num1(); …} num1() num2(); num2() { … }
95
8.函数嵌套调用 嵌套过程 例:求三个数最大与最小的差值 分析 #include <stdio.h>
在这个程序中共定义三个函数 dif()求差值 max()求最大数 min()求最小数 在调用dif()时需要调用max()和 min()分别求出最大和最小 #include <stdio.h> int dif(int x,int y,int z); int max(int x,int y,int z); int min(int x,int y,int z); void main() { int a,b,c,d; scanf(“%d%d%d”,&a,&b,&c); d=dif(a,b,c ); printf(“%d\n” ,d); } int dif( int x,int y,int z ) { return (max(x,y,z)- min(x,y,z)); }
96
8.函数嵌套调用 int max(int x,int y,int z) { int r;
if (x>y){ if(x>z)r=x; else r=z; } else { if(y>z)r=y; return(r); } int min(int x,int y,int z) { int r; if (x>y){ if(y<z) r=y; else r=z; } else { if(x<z) r=x; return(r); }
97
8.函数嵌套调用 嵌套过程 例:编写程序输出20以内的全部素数,并计算20以内全部素数之积与全部素数之和的商
98
8.函数嵌套调用 #include <stdio.h> #include <math.h>
函数int isprime (int )供三个函数调用,因此在文件开始处作声明。 #include <stdio.h> #include <math.h> int isprime (int ); main() { int i,a,s; float b,c; int add(int); float mul(int); for (i=0;i<=20;i++) {s=isprime(i); if (s)printf(“%d” ,i);} a= add(20); b= mul(20); c=b/a; printf(“\nc=%7.2f” ,c); } add(int)与 mul(int) 只被main函数调用,只 需在main函数中声明。 mul函数值用int 会超出取 值范围,所以用float 型。
99
8.函数嵌套调用 int add(int n) { int i,s,sum=0; for (i=2;i<=n;;i++)
{ s=isprime(i); if (s)sum+=i;} return(sum); } int isprime(int m) { int i; for (i=2;i<=sqrt(m);i++) if (m%i= =0)return(0); return (1); } int mul(int n) { int i,s; float t=1.0; for (i=2;i<=n;i++) { s=isprime(i); if (s)t*=i; } return t; }
100
9.函数的递归调用 递归是一种常用的程序设计技术 C语言的特点之一:允许函数递归调用 定义
递归是在连续执行某一处理过程时,该过程中的某一步要用到它自身的上一步(或上几步)的结果 定义 在调用一个函数的过程中又出现直接或间接地调用函数本身,就称之为函数的递归调用 在一个程序中,若存在程序自己调用自己的现象就是构成了递归 如果函数fun1在执行过程又调用函数fun1自己,则称函数funA为直接递归 如果函数fun1在执行过程中先调用函数fun2,函数fun2在执行过程中又调用函数fun1,则称函数fun1为间接递归 程序设计中常用的是直接递归
101
9.函数的递归调用 直接递归过程: 间接递归过程: main() {… num1(); …} main() {… num1(); …}
… } num2() {… num1(); …}
102
9.函数的递归调用 递归执行过程 例:利用递归求n! 分析
在程序中,函数facto_n求n的阶乘 在函数中使用了“m=n*facto_n(n-1)”的语句形式 该语句中调用了facto-n函数 是典型的直接递归调用 Facto_n是递归函数 在函数的递归调用过程中,并不是重新复制该函数 只是重新使用新的变量和参数 每次递归调用都要保存旧的参数和变量,使用新的参数和变量 每次递归调用返回时,再恢复旧的参数和变量,并从函数中上次递归调用的地方继续执行 #include <stdio.h> facto_n ( int n ) { int m; if (n==0) m=1; else m=n*facto_n(n-1); return (m); } main ( ) { int n, fun; scanf ("%d",&n); fun= facto_n (n); printf ("%d!=%d\n", n, fun); }
103
9.函数的递归调用 递归执行过程 以例题 提示 作为函数形参的变量n和函数内部使用的局部变量m
在每次调用时,它的值都不相同 随着调用的深入,n和r的值也随之变化 随着调用的返回,n和r的值又层层恢复 提示 在编写递归函数时,必须使用if语句建立递归的结束条件 使程序能够在满足一定条件时结束递归,逐层返回 如果没有这样的if语句,在调用该函数进入递归过程后,就会无休止地执行下去而不会返回 这是编写递归程序时经常发生的错误 在例题中,if(n==0)就是递归的结束条件
104
9.函数的递归调用 例题 对一个字符串实现正反向显示 #include "stdio.h" #include "string.h"
void for_backwards(char linechar[ ],int index); void main() { char linechar[80]; int index = 0; strcpy(linechar,"This is a string"); for_backwards(linechar,index); } HS7。C void for_backwards(char linechar[ ],int index) { if (linechar[index]) { printf("%c",linechar[index]); for_backwards(linechar,index+1); printf("%c",linechar[index]); }
105
9.函数的递归调用 实现过程 main函数 linechar[ ] index=0 for_backwards函数 ‘T’
linechar[17]=‘\0’(退出条件)执行前一次调用的for_backwards函数体其它语句。 ‘n’ 输出linechar[16] index=17 for_backwards函数 ‘g’ ‘g’
106
9.函数的递归调用 linechar[17]={ This is a string }; index=0
调用for_backwords()函数 判断linechar[0]是否为‘\0’ 否:打印linechar[0] ( = ‘T’); 调用for_backwords()函数(index=1) 输出linechar[0]( = ‘T’) 判断linechar[1]是否为‘\0’ 否:打印linechar[1] ( = ‘h’); 调用for_backwords()函数(index=2) 输出linechar[1] ( = ‘h’) 判断linechar[16]是否为‘\0’ 否:打印linechar[16] ( = ‘g’); 调用for_backwords()函数(index=17) 输出linechar[16] ( = ‘g’) 判断linechar[17]是否为‘\0’ 是:退出,返回前一次调用处。
107
9.函数的递归调用 例题 用递归的方法将整数按高到低位输出相应的数字字符 例如:603(整数)将输出数字6、0、3
#include <stdio.h> main() { void fun(int); int n; scanf(“%d” ,&n); if (n<0) {putchar(‘-’);n=-n;} fun(n); } void fun(int k) {int n; n=k/10; if(n!=0)fun(n); putchar(k%10+‘0’); } HS8。C
108
9.函数的递归调用 小测试 2.分析下面程序的输出结果: void f(int a) { static int x=1; int y=1;
if (a>0) { ++x; y++; printf (“%d,%d”,x,y); f(a-1);} } main() { int n=2; f(n); } HSa。C 1.分析下面程序的输出结果。 #include <stdio.h> fun (int x) {int p; if ((x= =0)||( x = =1 )) return(3); p=x-fun(x-2); return (p);} main () {printf(“%d\n”,fun(9)); } 演示HS9。C
109
9.函数的递归调用 递归问题的求解方法 编写递归程序的前提 计算机所求解的问题分为两大类 找出正确的递归算法 确定算法的递归结束条件
这是编写递归程序的基础 确定算法的递归结束条件 这是决定递归程序能否正常结束的关键 计算机所求解的问题分为两大类 数值问题 数值问题编写递归程序的一般方法 建立递归数学模型 确立递归终止条件 将递归数学模型转换为递归程序 数值型问题由于可以表达为数学公式,所以可以从数学公式入手,推出问题的递归定义,然后确定问题的边界条件,这样就可以很容易地确定递归的算法和递归结束条件
110
9.函数的递归调用 递归问题的求解方法 非数值型问题本身难于用数学公式表达
如果能够找到解决问题的一系列递归的操作步骤,同样可以用递归的方法解决这些非数值问题 分析步骤 从化简问题开始 将问题进行简化,将问题的规模缩到最小,分析问题在最简单情况下的求解方法 此时找到的求解方法应当十分简单 可将大问题分解为若干个小问题,使原来的大问题变成小问题的组合 其中至少有一个小问题与原来的问题有相同的性质 只是在问题的规模上与原来的问题相比较有所缩小 将分解后的每个小问题作为一个整体,描述用这些较小的问题来解决原来大问题的算法 由第3步得到的算法就是一个解决原来问题的递归算法 由第1步将问题的规模缩到最小时的条件就是该递归算法的递归结束条件
111
9.函数的递归调用 递归调用 提示 当一个问题蕴含了递归关系且结构比较复杂时,采用递归调用的程序设计技巧可以使程序变得简洁,增加了程序的可读性 递归调用本身是以牺牲存储空间为基础的 因为每一次递归调用都要保存相关的参数和变量 递归本身也不会加快执行速度 相反,由于反复调用函数,还会或多或少地增加时间开销 递归调用能使代码紧凑,并能够很容易地解决一些用非递归算法很难解决的问题 所有的递归问题都一定可以用非递归的算法实现 已经有了固定的算法 参看有关数据结构的内容
Similar presentations