从编程语言设计的角度理解递归

目录

介绍

概述

编程语言如何实现普通函数调用

编程语言如何实现带返回值的函数调用

递归

课后作业

附录


如果你要理解递归,首先你要理解递归.

介绍

我尝试写些关于编程语言的东西. 目前刚开始写,还把握不住严谨(数学上的)和易懂(语言上的)的边界. 我会不断润色,找到那个度. 你有问题就可以给我说.

递归是程序设计中的重要部分, 比如解析文件. 但是

一般的文章或者书籍都是通过介绍和例子来引入概念,没有做到知其所以然.

本文尝试通过引入编程语言的设计和实现来帮助理解递归.

概述

计算机是如何实现函数调用的? 一句话就是保存当前信息, 然后跳入函数中执行, 最后再从此函数返回. 高级的编程语言无非就是在模拟汇编语言执行函数调用的过程. 只是中间这个过程是通过编译器自动转换了.

那问题来了,函数可不可以调用自身?当然可以, 一般情况下,进入新的函数,会在内存中开辟一个新的栈帧来存放数据. 如果调用过深,那么进程的地址空间就不够存这些栈帧了,那么就会栈溢出.

我们今天主要来研究编程语言是怎么实现函数调用,进而理解递归.

编程语言如何实现普通函数调用

为了更好地展示,我们用个Python的例子来解释.例子就是递归打印1-10.

def recursive_print(n):
    if n == 1:
        print(n)
    else:
        recursive_print(n-1)
        print(n)
        
recursive_print(10)

发生函数调用时, 进程会保存当前上下文,并创建栈来保存函数的变量.假设当前函数调用是recursive_print(5), 当前栈保存有{n=5}. 你即将进入函数recursive_print(4), 此时创建一个新的栈并存有数据{n=4}.

\large stack\{n=5\} \mapsto recursive\_print(5)

\large stack\{n=5; (stack\{n=4\} \rightarrow recursive\_print(4)) \} \mapsto recursive\_print(5)

由此可以看到, 什么递归不递归的,只要是函数调用,只要把相关变量信息保存下来进行函数调用.计算机才不在乎.

编程语言如何实现带返回值的函数调用

如果你理解普通函数调用,带返回值的无非就是约定个地方,然后父函数从这个位置把值取到.

我随便找个x86 CPU的说明:

The caller can expect to find the return value of the subroutine in the register RAX.

给个例子:

def factorial(n):
    if n == 1:
        return 1
    else:
        return n*factorial(n-1)
        
print(factorial(10))

递归

如果理解上述问题,递归就可以理解为普通函数调用,只是每次调用传入的变量的值可能不一样.

计算机很傻,你给他相关的信息就行,他不关系其他的.

课后作业

  1. 你如何理解二叉树上的递归? 或者说现在可以理解吗?
  2. Haskell语言不存在while语句,如果有兴趣可以尝试用haskell编写上述两个例子.你会更好理解递归.
  3. 如果让你创造一门语言,比如mocca语言(最近疯狂喝这个),你会怎么模拟函数调用?用到哪些数据结构?

附录

The 64 bit x86 C Calling Convention

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值