Python基础入门自学——4

函数

函数是最基本的一种代码抽象的方式。

抽象
抽象是数学中非常常见的概念。举个例子:计算数列的和,比如:1 + 2 + 3 + ... + 100,写起来十分不方便,于是数学家发明了求和符号∑,可以把1 + 2 + 3 + ... + 100记作:

\sum_{1}^{n} n

这种抽象记法非常强大,因为我们看到 ∑ 就可以理解成求和,而不是还原成低级的加法运算。而且,这种抽象记法是可扩展的,比如:

\sum_{1}^{n} (n^{2} +1)

还原成加法运算就变成了:(1 x 1 + 1) + (2 x 2 + 1) + (3 x 3 + 1) + ... + (100 x 100 + 1)

可见,借助抽象,我们才能不关心底层的具体计算过程,而直接在更高的层次上思考问题。

基本上所有的高级语言都支持函数,Python也不例外。Python不但能非常灵活地定义函数,而且本身内置了很多有用的函数,可以直接调用。

定义函数

在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return None可以简写为return。

如果你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名):将上述my_abs()函数保存为funtest.py,路径为当前交互窗口的打开目录下,

函数体的第一个语句可以(可选的)是字符串文字;这个字符串文字是函数的文档字符串或 docstring 。在编写的代码中包含文档字符串是一种很好的做法,所以要养成习惯。一般使用三个连续的引号。

函数的执行会引入一个用于函数局部变量的新符号表。 更确切地说,函数中所有的变量赋值都将存储在局部符号表中;而变量引用会首先在局部符号表中查找,然后是外层函数的局部符号表,再然后是全局符号表,最后是内置名称的符号表。 因此,全局变量和外层函数的变量不能在函数内部直接赋值(除非是在 global 语句中定义的全局变量,或者是在 nonlocal 语句中定义的外层函数的变量),尽管它们可以被引用。

在函数被调用时,实际参数(实参)会被引入被调用函数的本地符号表中;因此,实参是通过按值调用传递的(其中值始终是对象引用而不是对象的值)。当一个函数调用另外一个函数时,将会为该调用创建一个新的本地符号表。

函数定义会将函数名称与函数对象在当前符号表中进行关联。 解释器会将该名称所指向的对象识别为用户自定义函数。 其他名称也可指向同一个函数对象并可被用来访问访函数:

写一个返回斐波那契数列的列表(而不是把它打印出来)的函数:

空函数
如果想定义一个什么事也不做的空函数,可以用pass语句:

def nop():
    pass
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

pass还可以用在其他语句里,比如:

if age >= 18:
    pass
缺少了pass,代码运行就会有语法错误。

参数检查
调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError:

但是如果参数类型不对,Python解释器就无法帮我们检查。比较自定义的my_abs()与内置函数abs()的运行结果

当传入了不恰当的参数时,内置函数abs会检查出参数错误,而自定义的my_abs没有参数检查,会导致if语句出错,出错信息和abs不一样。所以,这个函数定义不够完善。

修改一下my_abs的定义,对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现:

添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个我们定义的错误。

返回多个值
函数还可以返回多个值。

其实这只是一种假象,Python函数返回的仍然是单一值:

原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。

小结
定义函数时,需要确定函数名和参数个数;
如果有必要,可以先对参数的数据类型做检查;
函数体内部可以用return随时返回函数结果;
函数执行完毕也没有return语句时,自动return None。
函数可以同时返回多个值,但其实就是一个tuple。

调用函数

Python内置了很多有用的函数,我们可以直接调用。要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数abs,只有一个参数。如:abs(-98)

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

函数的参数

定义函数的时候,把参数的名字和位置确定下来,函数的接口定义就完成了。对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

位置参数

编制X^{2}的函数:

对于power(x)函数,参数x就是一个位置参数。当我们调用power函数时,必须传入有且仅有的一个参数x:


如果我们要计算X^{3}怎么办?可以再定义一个power3函数,但是如果要计算X^{4}X^{5}……怎么办?不可能定义无限多个函数。

也许可以把power(x)修改为power(x, n),用来计算X^{n}

修改后的power(x, n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。

默认参数
新的power(x, n)函数定义没有问题,但是,旧的调用代码失败了,原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用:

Python的错误信息很明确:调用函数power()缺少了一个位置参数n。

这个时候,默认参数就排上用场了。由于我们经常计算X^{2},所以,完全可以把第二个参数n的默认值设定为2:

对于n > 2的其他情况,就必须明确地传入n。

默认参数可以简化函数的调用。设置默认参数时,有几点要注意:

一是必选参数在前,默认参数在后,否则Python的解释器会报错(为什么默认参数不能放在必选参数前面);

通过上面的示例,可以看出,对默认参数赋予非默认值时,中间不能有跳跃,即不能第一个默认参数不赋值,第二个赋值,有多个默认参数时,对于不使用默认参数而要赋值时,其前面的所有默认参数都必须显示赋值,即使赋的值就是默认值。以上是按照位置进行参数赋值时的使用方法。还可以显式使用参数名=来进行赋值,可以不受上面的限制。

二是如何设置默认参数。

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

使用默认参数有什么好处?最大的好处是能降低调用函数的难度。

一个学生注册的函数,需要传入name和gender两个参数,如果要继续传入年龄、城市等信息怎么办?这样会使得调用函数的复杂度大大增加。可以把年龄和城市设为默认参数,这样,大多数学生注册时不需要提供年龄和城市,只提供必须的两个参数,与默认参数不符的学生才需要提供额外的信息

默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。

有多个默认参数时,调用的时候,既可以按顺序提供默认参数,比如调用enroll('Bob', 'M', 7),意思是,除了name,gender这两个参数外,最后1个参数应用在参数age上,city参数由于没有提供,仍然使用默认值。也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin'),意思是,city参数用传进去的值,其他默认参数继续使用默认值。

默认参数很有用,但默认参数有个最大的坑,演示如下:先定义一个函数,传入一个list,添加一个END再返回:

正常调用时,结果不错,使用默认参数调用时,一开始结果也是对的,但是,再次调用addend()时,结果就不对了。默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。

原因解释如下:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。 定义默认参数要牢记一点:默认参数必须指向不变对象!

也就是说:默认值是在 定义过程在函数定义处计算的,再看下面的例子:

打印的结果是5,而不是6。当函数f定义时,在定义参数时,arg参数在定义时就被计算,将变量i赋值给arg,这时arg = 5,即arg指向5这个对象,是一个地址。此后不管何时调用,只要使用默认参数,都是这个arg = 5。

默认值只会执行一次,即默认参数赋值只执行一次(这个理解不正确,是在后面的实验后突然认识到不对)。这条规则在默认值为可变对象(列表、字典以及大多数类实例)时很重要。再看前面的例子:

这里最重要的是要理解,默认参数的默认值[],是在函数定义时就计算好了的,还要知道,L是指向[]这个空列表的地址,地址是不变的,后面之所以每次执行addend()都加一个‘END’,是因为一开始定义的空列表的内容在变,而L的指向是不变的,一直都是列表的地址。这就是默认参数的默认值为可变类型带来的缺陷。关键还是理解前面讲的变量的赋值本质。

如果你不想要在后续调用之间共享默认值,定义默认参数必须指向不变对象!:

这里L为None,在函数当中进行了重新赋值,指向了空列表[],当函数执行完毕,函数再次执行是,默认值又指向None,函数再次执行时,又指向[]空列表。所以前面的默认参数赋值只执行一次的理解是错误的,是默认值的计算只执行一次,计算完成后也许保存在一个固定的位置,每次运行函数,都对参数赋这个固定的值。

为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。(个人理解:这样,如果要修改一个对象的值,实际上就是重新创建一个对象,所以,这种设计会对内存有很高的要求,或是要有一个有效的垃圾回收机制。

可变参数
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。

计算一组数字a,b,c……的和。要定义出这个函数,必须确定输入的参数。由于参数个数不确定,首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

调用的时候,需要先组装出一个list或tuple。如果利用可变参数,调用函数的方式可以简化。把函数的参数改为可变参数:

定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数,如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做,在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

可变参数函数,就是可以使用任意数量的参数调用函数。这些参数会被包含在一个元组里。在可变数量的参数之前,可能会出现零个或多个普通参数,其后可以跟关键字参数。

>>> def concat(*args, sep="/"):
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

下面定义的函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数,也可以传入任意个数的关键字参数:

键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。

关键字参数是形如kwarg=value的形式。

关键字参数可以传入任何形式的kwarg=value参数,很灵活,但是也不好控制。

与可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。

在函数调用中,关键字参数必须跟随在位置参数的后面。传递的所有关键字参数必须与函数接受的其中一个参数匹配,它们的顺序并不重要。这也包括非可选参数.

所以,关键字参数与位置参数的一个主要区别,就是关键字参数需要有“参数名=值”这个形式,显式的点出了参数名,也就是关键字,而位置参数是按照定义时参数的位置来一一对应传递的值,调用时只写了参数的值,注意,再说一遍,关键字参数必须跟随在位置参数的后面。下面的调用都是错误的。因为关键字参数没有跟随在位置参数后。

当存在一个形式为 **name 的最后一个形参时,它会接收一个字典 ,其中包含除了与已有形参相对应的关键字参数以外的所有关键字参数。 这可以与一个形式为 *name,接收一个包含除了已有形参列表以外的位置参数的 元组 的形参组合使用 (*name 必须出现在 **name 之前。) 


打印时关键字参数的顺序保证与调用函数时提供它们的顺序是相匹配的。

特殊参数——又叫命名关键字参数

默认情况下,函数的参数传递形式可以是位置参数或是显式的关键字参数。 为了确保可读性和运行效率,限制允许的参数传递形式是有意义的,这样开发者只需查看函数定义即可确定参数项是仅按位置、按位置也按关键字,还是仅按关键字传递。

函数的定义看起来可以像是这样

在这里 / 和 * 是可选的。 如果使用这些符号则表明可以通过何种形参将参数值传递给函数:仅限位置、位置或关键字,以及仅限关键字。 关键字形参也被称为命名形参。

位置或关键字参数:如果函数定义中未使用 / 和 *,则参数可以按位置或按关键字传递给函数。

仅限位置参数:特定形参可以被标记为 仅限位置。 如果是 仅限位置 的形参,则其位置是重要的,并且该形参不能作为关键字传入。 仅限位置形参要放在 / (正斜杠) 之前。 这个 / 被用来从逻辑上分隔仅限位置形参和其它形参。 如果函数定义中没有 /,则表示没有仅限位置形参。在 / 之后的形参可以为 位置或关键字 或 仅限关键字。

仅限关键字参数:要将形参标记为 仅限关键字,即指明该形参必须以关键字参数的形式传入,应在参数列表的第一个 仅限关键字 形参之前放置一个 *。

第一个函数定义 standard_arg 是最常见的形式,对调用方式没有任何限制,参数可以按位置也可以按关键字传入;第二个函数 pos_only_arg 在函数定义中带有 /,限制仅使用位置形参;第三个函数 kwd_only_args 在函数定义中通过 * 指明仅允许关键字参数;最后一个则在同一函数定义中使用了全部三种调用方式。

再考虑函数定义,它的位置参数 name 和 **kwds 之间由于存在关键字名称 name 而可能产生潜在冲突:


任何调用都不可能让它返回 True,因为关键字 'name' 将总是绑定到第一个形参。 

但使用 / (仅限位置参数) 就可能做到,因为它允许 name 作为位置参数,也允许 'name' 作为关键字参数的关键字名称:

如果设定为仅限位置参数,则参数名称将失去意义。换句话说,仅限位置形参的名称可以在 **kwds 中使用而不产生歧义。

● 如果你希望形参名称对用户来说不可用,则使用仅限位置形参。 这适用于形参名称没有实际意义,以及当你希望强制规定调用时的参数顺序,或是需要同时收受一些位置形参和任意关键字形参等情况。
● 当形参名称有实际意义,以及显式指定形参名称可使函数定义更易理解,或者当你想要防止用户过于依赖传入参数的位置时,则使用仅限关键字形参。
● 对于 API 来说,使用仅限位置形参可以防止形参名称在未来被修改时造成破坏性的 API 变动。

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。调用者可以传入不受限制的关键字参数。想要控制传入的关键字参数,就要使用命名关键字参数,即使用*来界定参数为命名关键字参数

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

命名关键字参数可以有缺省值,从而简化调用。

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

解包参数列表

前面介绍的使用*将list或tuple变成可变参数,使用**将字典变为关键字参数的操作,就叫做解包参数列表。

Lambda 表达式

Python使用lambda关键字创造匿名函数。所谓匿名,意即不再使用def语句这样标准的形式定义一个函数。这种语句的目的是由于性能的原因,在调用时绕过函数的栈分配。其语法是:

lambda [arg1[, arg2, ... argN]]: expression

其中,参数是可选的,如果使用参数的话,参数通常也会在表达式之中出现。

可以用lambda关键字来创建一个小的匿名函数。如下函数返回两个参数的和: lambda a, b: a+b 。Lambda函数可以在需要函数对象的任何地方使用。它们在语法上限于单个表达式。lambda函数可以引用所包含域的变量:

make_incrementor()函数返回一个函数对象,这个函数对象的参数有一个数字型参数,结果是返回参数值加上make_incrementor(n)函数的参数的n,在lambda生成的函数中,n是一个确定的值,上例是42。

lambda也允许有默认值和使用变长参数:

lambda是一个表达式,不是一个语句 这就使它能够出现在一些def不能出现的地方,比如,列表常量中。lambda是单个的表达式,不是一个代码块 lambda的设计是为了满足简单函数的场景,仅能封装有限的逻辑,有复杂逻辑的情况有def来处理,所以lambda的功能要比def小的多。lambda表达式是可以嵌套的

这就是一个用lambda实现的闭包,与普通闭包一样,内嵌lambda表达式可以获得上层lambda函数的变量。

lambda表达式一个用法是来返回一个函数。另一个用法是传递一个小函数作为参数:

匿名函数通常被用作高阶函数(higher-order function,参数为函数的函数)的参数。比如,几个内置函数:filter(),map(),reduce()。

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。该函数接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
注意: Python2.7 返回列表,Python3.x 返回迭代器对象。

语法

以下是 filter() 方法的语法:
filter(function, iterable)

参数
function -- 判断函数。 
iterable -- 可迭代对象。 

返回值:返回列表。或返回迭代器对象

以下展示了使用 filter 函数的实例: 

过滤出列表中的所有奇数:

def is_odd(n):
    return n % 2 == 1
 
newlist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(newlist)

输出结果 :
[1, 3, 5, 7, 9]

上面的filter例子需要再写一个函数,利用lambda表达式则可省略此函数。

map() 会根据提供的函数对指定序列做映射。第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

语法

map() 函数语法:
map(function, iterable, ...)

参数
function -- 函数
iterable -- 一个或多个序列

返回值:

Python 2.x 返回列表。
Python 3.x 返回迭代器。

以下实例展示了 map() 的使用方法:

Python2.x 实例

>>> def square(x) :            # 计算平方数
...     return x ** 2
 ... 
>>> map(square, [1,2,3,4,5])   # 计算列表各个元素的平方
[1, 4, 9, 16, 25]
>>> map(lambda x: x ** 2, [1, 2, 3, 4, 5])  # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]

# 提供了两个列表,对相同位置的列表数据进行相加
>>> map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
[3, 7, 11, 15, 19]

Python3.x 实例

>>> def square(x) :         # 计算平方数
...     return x ** 2
 ... 
>>> map(square, [1,2,3,4,5])    # 计算列表各个元素的平方
<map object at 0x100d3d550>     # 返回迭代器
>>> list(map(square, [1,2,3,4,5]))   # 使用 list() 转换为列表
[1, 4, 9, 16, 25]
>>> list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))   # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]
>>> 

reduce() 函数会对参数序列中元素进行累积。函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
注意:Python3.x reduce() 已经被移到 functools 模块里,如果我们要使用,需要引入 functools 模块来调用 reduce() 函数:
from functools import reduce

语法

reduce() 函数语法:
reduce(function, iterable[, initializer])

参数
function -- 函数,有两个参数
iterable -- 可迭代对象
initializer -- 可选,初始参数

返回值:
返回函数计算结果。

以下实例展示了 reduce() 的使用方法:

实例(Python 2.x)

#!/usr/bin/python
# -*- coding: UTF-8 -*-

def add(x, y) :            # 两数相加
    return x + y
 sum1 = reduce(add, [1,2,3,4,5])   # 计算列表和:1+2+3+4+5
 sum2 = reduce(lambda x, y: x+y, [1,2,3,4,5])  # 使用 lambda 匿名函数
print(sum1)
print(sum2)

实例(Python 3.x)

#!/usr/bin/python
from functools import reduce

def add(x, y) :            # 两数相加
    return x + y
 sum1 = reduce(add, [1,2,3,4,5])   # 计算列表和:1+2+3+4+5
 sum2 = reduce(lambda x, y: x+y, [1,2,3,4,5])  # 使用 lambda 匿名函数
print(sum1)
print(sum2)

以上实例输出结果为:
15
15

sorted() 函数对所有可迭代的对象进行排序操作。
sort 与 sorted 区别:
sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。

list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

语法

sorted 语法:
sorted(iterable, key=None, reverse=False)

参数说明:
iterable -- 可迭代对象。
key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)。

返回值:返回重新排序的列表。

递归函数
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

如计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:

fact(n)=n!=1\times2\times3\times...\times(n-1)\timesn=(n-1)! \times n=fact(n-1) \times n ,所以fact(n)=n!=1×2×3×⋅⋅⋅×(n−1)×n=(n−1)!×n=fact(n−1)×n

所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。于是,fact(n)用递归的方式写出来就是:

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,可以把循环看成是一种特殊的尾递归函数。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值