第5章 函数ppt课件.ppt
第5章 函数第第5章章 函数函数要编好一个较大的程序,通常需要合理划分程序中的功能模块。这些功能模块在程序设计语言中被称为函数。虽然函数的表现形态各异,但共同的本质就是有一定的组织格式和被调用格式。要写好函数,必须清楚函数的组织格式(即函数如何定义);要用好函数,则必须把握函数的调用机制。5.1 函数的概念函数的概念5.2 函数的定义与调用函数的定义与调用5.3 参数传递方式参数传递方式5.4 变量作用域变量作用域5.5 嵌套调用与递归调用嵌套调用与递归调用5.1 函数的概念函数的概念使用函数有两个目的:(1)分解问题,降低编程难度。(2)另一方面,代码重用。把实现某一特定功能的相关语句按某种格式组织在一起形成一个程序单位,并给程序单位取一个相应的名称,这样的一个程序单位就叫函数(function)。函数有时也被称作例程或过程。而给程序单位所起的名称被称作函数名。Python语言的函数分为:用户自定义函数、系统内置函数和Python标准库(模块中定义的)函数。系统内置函数是用户可直接使用的函数。Python标准库中的函数,要导入相应的标准库,才能使用其中的函数。用户自定义函数是用户自己定义的函数,只有定义了这个函数,用户才能只有定义了这个函数,用户才能调用。这是本章要讨论的问题。调用。这是本章要讨论的问题。一个函数被使用时就是指这个函数被调用。函数调用通过调用语句实现,调用语句所在的程序或函数称为调用程序或调用函数;被调用的函数简称为被调函数。调用语句需要指定被调用函数的名字名字和调用该函数所需要的信息(参参数数)。调用语句被执行的过程是被调函数中的语句被执行,被调函数执行完后,返回调用语句的下被调函数执行完后,返回调用语句的下一句,返回时可以反馈结果给调用语句。一句,返回时可以反馈结果给调用语句。5.2 函数的定义与调用函数的定义与调用5.2.1 函数定义函数定义建立函数的一段程序(这段程序表达函数的功能)就是函数定义函数定义。在程序中使用这个函数称调用这个函数。在程序中可以多次、反复地调用一个定义了的函数。函数必须先定函数必须先定义后使用(调用)。义后使用(调用)。1. 函数的定义格式函数的定义格式函数的定义格式:def () :其中,是任何有效的Python标识符,是用“,”分隔的参数,参数个可以是0个、1个或多个,参数用于调用程序在调用函数时向函数传递值。写在函数定义语句(def语句)函数名后面的圆括号中的参数称形式参数,简称形参。形参只能是变量。形参只能函数被调用时才分配内存单元,调用结束时释放所分配的内存单元。写在调用语句中函数名后面的圆括号中的参数称实际参数,简称实参。实参可以是常量、变量、表达式,在实施函数调用时,实参必须有确定的值。是函数被调用时执行的代码段。至少要有一条语句。例例5.1 设计一个求累计和的函数,一个形参指出累计对象的终点;主程序输入一个终点值,调用函数,输出累计和。# -*- coding: gb2312 -*-# ex5-1def sum(x) : i = 1 s = 0 while i0 :display()n = n-15.2.2 函数调用函数调用函数调用的格式格式:()其中,是事先定义函数时定义的函数名。此时应是实际参数表,即实参表,由多个实参组成,实参用“,”分隔,实参要有确定的值。实参的个数可以少于形参的个数,这是由于形参有默认值。三种调用形式:(1)函数语句(2)函数表达式(3)函数参数例例5.2 设计一个函数max(x, y),求出大者,函数调用时,体现上面描述的三种调用形式。# -*- coding: gb2312 -*-# ex5-2def max(x, y): if xy : return x else : return ymax(12,15)# 函数语句函数语句print(max(12,15)# 函数表达式函数表达式z = max(12,15)# 函数表达式函数表达式print(z)print(max(12,max(16,5)# 函数参数函数参数函数调用时要做的工作与步骤:函数调用时要做的工作与步骤:(1)保存现场。如果是以函数语句形式调用,调用语句的下一条语句就是现场;如果是以函数表达式或函数参数的形式调用,因为函数调用返回时的下一步工作是让返回值参与表达式的计算,就把这一步的工作当成现场。(2)将实参传递给形参。(3)程序的执行转向函数。(4)函数执行完后,恢复现场。函数执行完后,要知道返回,就是要返回到什么地方继续执行。5.2.3 函数的返回值函数的返回值从函数功能上讲,函数的形参是函数的输入参数,函数的返回值是函数的输出参数。在函数的定义中,内的return语句是向主调程序(函数)传递返回值的语句。它的格式是:return ,.,可以向主调程序(函数)传递多个返回值,这要求主调程序(函数)有多个变量接收这返回的多个值。如果函数不返回值,就不必使用return语句,或使用“return None”。5.3 参数传递方式参数传递方式参数传递方式是指实参向形参传递参数的方式。Python语言只有一种参数传递方式,就是形参仅仅引用传入对象的名称。就是其它语言的传值方式。这种传值方式是让形参直接引用实参的值。从理论上讲,如果实参是一个变量,形参变量的变化不会影响实参变量。本章前面的例子都是传值。但是,如果传递的对象是可变对象,在函数中又修改了可变对象,这些修改将反映到原始对象中。这可以理解为形参影响了实参,但不理解为参数传递是“按引用传递”或“传地址”。(结合其他语言讲)(结合其他语言讲)例例5.3 定义一个列表:1, 2, 3, 4, 5,设计一个函数,对列表中的元素做平方处理,调用函数,看看原列表是否变化了。# ex5-3a = 1, 2, 3, 4, 5print(a)def square(x) : for i, j in enumerate(x) : xi = j*jsquare(a)print(a)程序运行的结果:1, 2, 3, 4, 51, 4, 9, 16, 25程序中的程序中的enumerate()函数函数产生一个表对序产生一个表对序列,表对的第一列,表对的第一个值是由变量个值是由变量i产产生的索引值,第生的索引值,第二个值是原列表二个值是原列表a中元素的平方值。中元素的平方值。这个表对序列是这个表对序列是一个迭代器。一个迭代器。5.4 变量作用域变量作用域变量作用域就是变量的使用范围。在一个Python语言程序中,可能包含很多自定义的函数,因为允许函数嵌套定义,就会出现一个函数定义的外层和内层的概念。函数的调用不存在外层和内层的概念。程序(或函数)调用一个函数时,会为被调用的函数建立一个局部命名空间,该命名空间代表一个局部环境,其中包含函数的形参和函数体内赋值的变量变量名称。对于一个变量或形参,解释器将从这个局部命名空间、全局命名空间(定义被调函数的模块或程序)、内置命名空间,依次查找,直到找到确定属于哪个层次,找不到,只能报NameError异常。总结变量(或形参)的作用域总结变量(或形参)的作用域:下面涉及“定义一个变量”的概念是指首次出现,并通过赋值语句首次得到值(也就是首建立变量与对象的联系)。换句话说,首次写在赋值语句的赋值运算符的左边。(1)全局变量全局变量:一个定义在程序中(所有函数之外)的变量的作用域是整个程序,这种变量在整个程序范围内可引用,称为全局变量。(2)局部变量局部变量:变量定义在函数内,它们的作用域在函数内,称为局部变量。这种变量在函数内可以引用,程序的执行一旦离开相应的函数,变量失效,不可引用。(3)不同层次的局部变量不同层次的局部变量:如果有函数嵌套定义,内层中定义的变量、形参的作用域只在内层,外层定义的变量可在内层使用。(4)全局变量与局部变量全局变量与局部变量:全局变量可在函数中使用。例例5-4 变量作用域示例。# -*- coding:gb2312 -*-# ex5-4a = 1def second(): b = 2+a def thirth(): c = 3+a d = 4+b print(a,b,c,d) thirth() print(a,b) # 不能输出不能输出c、dsecond()print(a)# 不能输出不能输出b、c、d程序运行的输出结果:1 3 4 71 31(5)不同层次用同名变量首次赋值。)不同层次用同名变量首次赋值。例例5-5 不同层次的同名变量的不同作用域示例。# -*- coding:gb2312 -*-# ex5-5a = 1def second(): a = 2 def thirth(): a = 3 print(thirth_a: ,a) thirth() print(second_a: ,a)second()print(first_a: ,a)程序运行输出结果:thirth_a: 3second_a: 2first_a: 1(6)global语句的运用语句的运用。将某层嵌套定义函数的同名变量升级为全局变量,可使用global语句。global语句只是一个声明语句。这个升了级的同名变量与外面程序中定义的同名全局变量是同这个升了级的同名变量与外面程序中定义的同名全局变量是同一个变量,但这个升了级的同名变量所在函数层的上层函数或一个变量,但这个升了级的同名变量所在函数层的上层函数或下层函数中的同名变量的作用域不变。下层函数中的同名变量的作用域不变。例例5.6 使用global语句示例。a = 1def second(): global a a = 2# 这层的这层的a是全局变量了是全局变量了 def thirth(): a = 3# 这层的这层的a仍是仍是thirth()的局部变量的局部变量 print(thirth_a: ,a) thirth() print(second_a: ,a)second()print(first_a: ,a)程序运行输出结果:thirth_a: 3second_a: 2first_a: 2# -*- coding:gb2312 -*-# ex5-6_2a = 1def second(): a = 2 # 这层的这层的a仍是仍是second()的局部变量的局部变量 def thirth(): global a a = 3 # 这层的这层的a是全局变量了是全局变量了 print(thirth_a: ,a) thirth() print(second_a: ,a)second()print(first_a: ,a)程序运行输出结果:thirth_a: 3second_a: 2first_a: 3(7)nonlocal语句的运用语句的运用。对同名某层对同名某层局部变量升级一个层次局部变量升级一个层次。其他层次不变。例例5.7 使用nonlocal语句示例。# -*- coding:gb2312 -*-# ex5-7a = 1def second(): a = 2 def thirth(): nonlocal a a = 3# 这层的这层的a与上一层的与上一层的a同作用域同作用域 def fourth(): a = 4# 这层的这层的a的作用域不变的作用域不变 print(fourth_a: ,a) fourth() print(thirth_a: ,a) thirth() print(second_a: ,a)second()print(first_a: ,a)程序运行结果如下:fourth_a: 4thirth_a: 3second_a: 3first_a: 1# -*- coding:gb2312 -*-# ex5-7_2a = 1def second(): a = 2 # 这层的这层的a的作用域不变的作用域不变 def thirth(): a = 3 def fourth(): nonlocal a a = 4 # 这层的这层的a与上一层的与上一层的a同作用域同作用域 print(fourth_a: ,a) fourth() print(thirth_a: ,a) thirth() print(second_a: ,a)second()print(first_a: ,a)程序运行结果如下:fourth_a: 4thirth_a: 4second_a: 2first_a: 15.5 嵌套调用与递归调用嵌套调用与递归调用5.5.1 函数的嵌套调用函数的嵌套调用一个被用调函数的函数体中出现函数调用语句(调用其它函数),这种调用现象称为函数的嵌套调用。例例5.8 编程求组合程序调用函数comb();comb()在执行过程中又调用了函数fac()。fac()的调用被嵌套在函数comb()的调用中。nmm!Cn!(m-n)!# -*- coding:gb2312 -*-# ex5-8n = eval(input(Input n: )m = eval(input(Input m: )def fac(k) : i = f = 1 while i1,可用n*fac(n-1)表示,即fac(n)函数体内将递归调用fac()自身;一旦参数n为1,则终止调用函数自身并给出函数值1。1n1n(n1)!n1()()# -*- coding:gb2312 -*-# ex5-9def fac(n) : if n=1 : return 1 else: return n*fac(n-1)x=eval(input(input a value:)y=fac(x)# 主程序调用主程序调用fac()函数函数print(y)采用递归调用的程序结构清晰程序结构清晰,但采用递归调用的程序往往执行效率低执行效率低。因为在递归调用的过程中,系统需要为每一次调用保存返回断点、局部变量等,保存这些信息使用称为堆栈的数据结构。所以,费时又费内存空间。递归次数过多容易造成堆栈溢出,Python系统对递归函数调用的深度做了限制,默认限制是1000。可以通过sys.getrecursionlimit()函数获得当前系统的最大递归深度,而可以使用函数sys.setrecursionlimit()修改这个递归深度默认值,超出递归深度默认值时,将引发RuntimeError异常。例例5.10 Hanoi问题。Hanoi问题的描述如图。一个底座上有三根针,分别为a针、b针和c针,原始状态是:在a针上有64个盘子(图上仅画3个盘子以简化问题),这些盘子大小不同,从底座往上观察盘子直径是由大到小,b针和c针上没有盘子。问题是:每次只能移动1个盘子,并且任何时候不允许大的盘子压在小的盘子上面,如何将a针上的64个盘子移动到c针上(在移动过程中,可以借用第三根针作暂存盘子的针)给出具体的移动步骤。移动盘子的过程是一个很烦琐的过程。通过计算,按规则移动64个盘子到目的针需要移动264-1()次。我们考虑用递归求解问题。(1)本问题的递归终止条件:如果只有1个盘,显然问题就好解决,直接把盘子从a针移到c针。因此终止条件是n = 1。操作是直接把盘子从a移到c,用“ac”表示“直接把盘子从a针移到c针”。(2)本问题的递归分析:“从a针移动n个盘子到c针”的问题可分解为三步:第一步:先将n-1个盘子从a针借助c针移动到b针,移动n-1个盘子与原问题相同,但规模变小,向终止条件接近。第二步:将a针上剩下的一个盘子直接从a针移到c针。第三步:再将b针上的n-1个盘子从b针借助a针移动到c针。前面的分析告诉我们:原来的n阶问题(移动n个盘子)可用三个子问题表示,这三个子问题中有两个是n-1阶问题(移动n-1个盘子),一个是一阶问题(移动1个盘子)。这个分解动作符合递归调用的向终止条件转化的规则。看来可用递归调用解决Hanoi问题。假设将n个盘子从a针借助b针移动到c针的函数命名为:hanoi(a, b, c, n)# -*- coding: gb2312 -*-# ex5-10 Hanoi问题问题 def hanoi(a, b, c, n) : if n = 1: print(a, -, c) else : hanoi(a, c, b, n-1) print(a, -, c) hanoi(b, a, c, n-1) n = eval(input(输入盘子的层数:输入盘子的层数: )hanoi(a, b, c, n)当输入盘子层数为当输入盘子层数为3时,时,程序运行结果如下:程序运行结果如下:a - ca - bc - ba - cb - ab - ca - c