版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、,函数,程序的模块化 函数 定义 原型 调用 参数传递 函数的嵌套调用 递归函数 程序设计举例,C语言程序设计,简介,分而治之与程序的模块化 把一个规模较大的问题分解成若干个较小的相对独立的部分,对每一个部分使用一个较小的程序段,即程序模块(module)来处理。 从较小的程序段或组件来构建程序。 这些小片段或组件比原始程序更容易实现和管理。 这些小组件可以被重复使用。,函数,C语言的函数,在C语言中,函数(function)是构成程序的基本模块。 一个C程序由一个或多个函数组成,有且仅有一个主函数,即main()函数。 每个函数完成一个相对独立的且功能明确的任务。 由主函数调用其他函数,其他
2、函数也可以互相调用。 同一个函数可以被一个或多个函数调用任意多次。,函数,C语言的函数,C语言的函数有两大类: 标准库函数 提供了丰富的函数。 例如 数学计算:sqrt(),abs() 输入/输出:scanf(),printf() 自定义函数 程序员可以编写函数来完成特定的任务。 应该熟悉C系统中的标准函数库。 应该避免从零开始构建一切。,函数,为什么使用函数,函数使程序模块化。 程序采用模块化结构的好处: 分而治之 提高程序开发的效率。 使程序易于管理。 代码重用 使用现有的函数作为构件来创建程序。 函数可以被重复使用。 抽象 隐藏了实现的细节。 例如 使用库函数(printf()),但并不
3、知道它的具体实现(没有影响使用)。,函数,案例分析:一个简单的函数,编写和使用一个简单的函数(cw0801.c) 定义一个函数square,用来计算任意整数的平方。 然后,使用该函数计算从1到10所有整数的平方。,函数,#include int square(int); void main() int x; for (x=1; x=10; x+) printf(%d , square(x); int square(int y) return y*y; ,声明函数,使用函数,定义函数,1 4 9 16 25 36 49 64 81 100,函数的定义,定义函数的格式 () 函数名:一个有效的标识
4、符。 函数类型:返回值的类型说明符。 如果不指定,即缺省,就是 int。 void :表示函数不返回任何值。 参数表:声明参数,多个参数用逗号分隔。 接收传递进来的数据。 必须为每个参数指定数据类型。 但 int 可以省略。,函数,函数头,函数体,函数的定义,定义函数的格式 () 函数体:包括声明语句和可执行语句。 在函数体内可以声明变量。 不能定义函数,即函数的定义不允许嵌套。 控制返回:结束执行,把程序的控制交还主调函数,也可以用return返回一个数值。 return; return ;,函数,无返回值,有返回值,案例分析:函数的定义,函数的定义(cw0802.c) 定义函数找出三个数中
5、的大数。,函数,#include int maximum(int, int, int); void main() int a, b, c; printf(“Input three integers: ); scanf(%d%d%d, ,函数原型,接口(interface),函数原型,函数原型 (); 用来对函数进行声明。 编译器使用函数原型来检查函数调用的合法性。 注意:函数原型要与函数的定义一致。 例如 int maximum(int a, int b, int c); int maximum(int, int, int);,函数,函数原型,函数原型在程序文件中的位置不同,作用范围不同。 在
6、所有函数的外面 在函数内部,函数,main() void funcA() int funcB(int); int funcB(int) ,函数原型,如果程序中没有包含函数原型。 编译程序会使用该函数第一次出现的情形来形成自己的函数原型。 函数的定义 函数的调用 默认情况下,编译程序假定函数返回int型的结果,但不会对参数进行任何假定。 如果传递给函数的参数不正确,编译程序不会检查到这些错误。,函数,函数原型,函数原型强迫参数采用正确的数据类型。 举例 printf(“%.3f”, sqrt(4) ); 函数原型使编译程序把整数值4转换为double型的值4.0 没有与函数原型中的参数类型完全对
7、应的参数值会在调用函数之前被转换成合适的数据类型。 遵守C语言的提升规则。,函数,double sqrt(double);,函数原型与头文件,头文件 每个标准库函数都有对应的头文件。 包含了标准库中所有函数的函数原型, 以及那些函数所需的数据类型和常量的定义。 使用#include命令把头文件包含到程序文件中: #include 例如,#include 程序员可以创建自己的头文件 使用.h扩展名。 使用下面的命令格式包含头文件: #include “文件名” 例如,#include “abc.h”,函数,函数调用,函数调用 使用函数,也称为调用函数。,函数,main() int a, b, c
8、; scanf(%d%d, ,1,2,6,int max(int a, int b) int c; c=a=b?a:b; return c; ,3,4,5,1、 2、主调函数暂停,保存现场。 3、把实参的值拷贝给形参,控制权交给函数 max 。 4、,5、被调函数执行结束,把函数值返回给主调函数,同时把控制权还给主调函数。 6、恢复现场,主调函数继续执行。 ,参数传递,函数间的数据传递方式: 参数 返回值,函数,int max(int a, int b) return c; main() c=max(a, b); ,参数传递,返回值传递,调用,实参和形参,实参和形参,函数,int max(in
9、t a, int b) int c=a=b?a:b; return c; main() int a, b, c; scanf(“%d%d”, ,形式参数 简称“形参”。在函数定义时表示可以接受传递过来的值。,实际参数 简称“实参”。在函数调用时给出。,形参,形参,函数,int max(int a, int b) int c=a=b?a:b; return c; main() int a, b, c; scanf(“%d%d”, ,只有在函数被调用、启动后,才临时为其分配存储单元,并接受主调函数传来的数据。 在函数调用结束后,形参所占存储单元被释放。,实参,实参,函数,int max(int a
10、, int b) int c=a=b?a:b; return c; main() int a, b, c; scanf(“%d%d”, ,实参是函数调用时主调函数传送给被调函数的形式参数的实际值。 实参可以是常量、变量和表达式。 实参必须有确定的值。,参数传递,参数传递的顺序。,函数,int max(int a, int b) int c=a=b?a:b; return c; main() int x=6, y; y=max(x, x+); printf(“%d”, y); ,当实参表列中有多个实参时,对实参的求值顺序并不确定。 VC和BC是按从右往左的顺序求值。,b=x+; a=x;,7,在
11、参数传递时,参数传递,参数传递的影响。,函数,int max(int a, int b) int c=a=b?a:b; a+; b+; return c; main() int a=6, b=5, c; c=max(a, b); printf(“%d,%d,%d”,a,b,c); ,实参与形参不共用存储单元。 参数传递时,把实参的值复制一份给形参。 形参值的变化不影响实参的值。 所以,形参和实参可以同名。,6,5,6,值传递和引用传递,函数间参数的传递有两种类型: 值传递 主调函数把实参的值的副本传递给被调函数的形参。 在被调函数内改变形参的值不会改变主调函数中实参的值。 如果函数不需要修改参
12、数的值,就采用这种调用方式。 引用传递 主调把实参“自身”传递给被调函数的形参。 在被调函数内改变形参的值将改变主调函数中实参的值。 用于可信的函数。 在C语言中,所有参数传递都采用值传递。,函数,参数传递,实参和形参的类型应该相同或赋值兼容。,函数,int max(int a, int b) int c=a=b?a:b; return c; main() int x=6, y=5, z; z=max(x, y); printf(“%d”, z); ,如果x, y是整型,则结果正确; 如果x, y是字符型,则自动进行类型转换,结果正确; 如果x, y是短整型,则自动进行类型转换,结果正确; 如
13、果x或y是实数,则自动进行类型转换,有数据丢失,结果可能不正确。,b=y; a=x;,在参数传递时,函数的返回值,函数返回值的类型应该与函数的类型一致。 如果不一致,采用函数的类型,对返回值进行类型转换。,函数,int max(float a, float b) float c=a=b?a:b; return c; ,main() float x=6.5, y=5.6, z; z = 2*max(x, y); printf(“%f”, z); ,c 的类型? 返回值的类型?,max(x,y) 的类型? 2*max(x,y) 的类型?,函数的嵌套调用,嵌套调用 在调用一个函数的过程中又调用另一个
14、函数,函数,案例分析:函数的嵌套调用,计算圆环的面积 分析 圆环的面积 = 外圆的面积 内圆的面积 可以定义两个函数 circleArea 计算圆的面积 ringArea 计算圆环的面积,函数,double circleArea(double r); double ringArea(double r1, double r2);,案例分析:函数的嵌套调用,计算圆环的面积 源代码(cw0804.c),函数,#include #define PI 3.14 double circleArea(double r); double ringArea(double r1, double r2); void
15、 main() double r1, r2, s; printf(tInput r1, r2: ); scanf(%lf%lf, ,案例分析:函数的嵌套调用,计算圆环的面积 源代码(续),函数,double circleArea(double r) return PI*r*r; double ringArea(double r1, double r2) if (r1=r2) return circleArea(r2)-circleArea(r1); else return circleArea(r1)-circleArea(r2); ,Input r1, r2: 1 2 The area is
16、: 9.42,程序设计举例:掷骰子,掷骰子 问题 每个玩家掷两个骰子。每个骰子都有6个面。这些面中包含了1点、2点、3点、4点、5点和6点。当骰子静止下来之后,计算两个朝上的面中的点数和(本次投掷的结果)。 如果第一次投掷的结果是7 或11,那么这个玩家就获胜。 如果第一次投掷的结果是2、3或12,那么这个玩家就输了(即庄家获胜)。 如果第一次投掷的结果是4、5、6、8、9或10,那么这个结果就是该玩家的“点数”。 为了获胜,玩家必须继续掷骰子,直到“掷出了点数”。在掷出点数之前,如果玩家掷出了7,那么玩家就输了。,函数,程序设计举例:掷骰子,掷骰子 初始设计 定义一个函数 rollDice,
17、用来模拟掷一次骰子 产生两个随机数(1.6),返回它们的和(点数),函数,掷第一次,胜,输,掷一次,胜,输,?,程序设计举例:掷骰子,掷骰子 细化设计 定义一个变量保存游戏进展的状态 gamestatus 0:继续 1:胜利(游戏结束) 2:失败(游戏结束),函数,程序设计举例:掷骰子,掷骰子 实现(cw0807.c),函数,#include #include #include int rollDice(void); void main() int gameStatus, sum, myPoint; srand(time(NULL); sum = rollDice();,掷第一次,程序设计举例
18、:掷骰子,掷骰子 实现,函数,switch(sum) case 7: case 11: gameStatus = 1; break; case 2: case 3: case 12: gameStatus = 2; break; default: gameStatus = 0; myPoint = sum; printf(Point is %dn, myPoint); break; ,掷第一次之后的结果,程序设计举例:掷骰子,掷骰子 实现,函数,while (gameStatus = 0) sum = rollDice(); if (sum = myPoint) gameStatus = 1;
19、 else if (sum = 7) gameStatus = 2; if (gameStatus = 1) printf(You wins!); else printf(You loses!); ,继续游戏,胜负判断,程序设计举例:掷骰子,掷骰子 实现,函数,int rollDice() int dice1, dice2, sum; dice1 = rand()%6 + 1; /第一个骰子的点数 dice2 = rand()%6 + 1; /第二个骰子的点数 sum = dice1 + dice2; printf(You rolled %d + %d = %dn, dice1, dice2,
20、 sum); return sum; ,递归函数,递归函数 直接或间接调用自己的函数。,函数,案例分析:递归函数,用递归方法计算n!。 分析 5! = 5 * 4 * 3 * 2 * 1 5! = 5 * 4! 4! = 4 * 3!. 递归公式,函数,基本情形,简化问题,s=1; n=1; while (n=20) s = n * s;,迭代,n! = n * (n-1)!,案例分析:递归函数,举例:用递归方法计算n!。 源代码(cw0805.c),函数,#include long fac(int n) long f; if (n=0|n=1) f=1; else f=n*fac(n-1);
21、 printf(t%d!=%ldn, n, f); return f; void main( ) printf(nt5!=%ldn, fac(5); ,1!=1 2!=2 3!=6 4!=24 5!=120 5!=120,递归调用,案例分析:递归函数,举例:用递归方法计算n!。 分析,函数,5!,5*4!,4*3!,3*2!,2*1!,1,120,5*24,4*6,3*2,2*1,1,递归调用,从递归调用返回值,用递归方案解决问题小结,用递归(函数)方案解决问题 递归函数只知道如何去解最简单的情形(基本情形) 简单的返回一个值 把复杂的问题分成两部分: 函数知道如何去做的部分 函数不知道如何去
22、做的部分 这一部分与原问题相似,且更简单 函数可以调用自己的新形式来解决这个更小的问题(递归调用) 最终遇到基本情形 函数识别出基本情形,将结果返回给前一个情形 一系列的结果按顺序返回 直到把最终结果返回给原始的调用者(可能是main()函数),函数,案例分析:递归方案,使用递归方法计算斐波拉契数列。 0 1 1 2 3 5 8 分析 从第三个数开始,每个数是前两个数的和。 第一、二个数是确定的。 递归公式 fib(n) = fib(n-1) + fib(n-2) fib(1) = 0 fib(2) = 1,函数,递归与迭代,递归与迭代的比较 循环 迭代:明确使用了循环结构 递归:重复调用递归
23、函数 终止条件 迭代:循环条件不满足 递归:遇到基本情形 都有可能出现无限循环 如何选择 迭代:性能好 递归:可读性好,函数,程序设计举例:汉诺塔问题,汉诺塔问题 问题 假设有三个分别命名为X,Y和Z的塔座,在塔座X上插有n个直径大小各不相同、依从小到大编号为1,2,n的圆盘。现要求将X轴上的n个圆盘移到塔座Z上,并按同样的顺序叠放。 移动时必须遵循以下规则: 每次只能移动一个圆盘; 圆盘可以插在X,Y和Z中的任一塔座上; 任何时候都不能将一个较大的圆盘压在较小的圆盘之上。,函数,程序设计举例:汉诺塔问题,汉诺塔问题 分析 n=1时 将圆盘1从塔座X移到塔座Z。,函数,基本情形,程序设计举例:汉诺塔问题,汉诺塔问题 分析 n1时,函数,1. 利用塔座Z为辅助塔座,将压在圆盘n之上的n-1个盘从塔座X移到塔座Y; (与原问题类似) 2. 将圆盘n从塔座X移到塔座Z; 3. 利用塔座X为辅助塔座,将塔座Y上的n-1个圆
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论