• 参数传递与运行栈

    命令式编程的细节概念
    服务器君一共花费 16.737 ms 进行了 4 次数据库查询,努力地为您提供了这个页面。
    广告很萌的

    隐式赋值语句——参数传递

    参数,也叫做参变量,是一种特殊的变量。为了说明参变量和普通变量之间的异同点,下面我们给出一段具体的代码例子。

    这里需要说明的是,本书中给出的相当一部分示例代码,都是用极为简化的伪代码写的,并不能够真正运行。这样做的目的是,最大限度保证代码的简化性、易读性、通用性。

    下面,就先让我们看一段包含了参变量和变量定义的伪代码。 

    f(x) { 
      a = 2 * x 
      if a < 5 
    	return 0 
      else if a > 10 
        return 2 
      else 
        return 1 
    } 
    

    上面的伪代码结合了Javascript和Python这两种语言的最简化表达方法。由于省略了过程声明、类型声明,写起来极为简单,一目了然。

    其中,if和return这两个关键字也是编程语言中常见的,不需要过多的解释;f(x)定义了一个名字叫做f的只有一个参数x的过程;{}这对大括号则包括了过程体内部的代码,这也是C、C++、Java等主流编程语言的语法惯例。

    另外需要特别说明的一点是,在命令式语言中,过程(Procedure)通常还有几种其他的叫法,比如,函数(Function),方法(Method)等。后面会讲到函数式编程语言(Functional Programming),为了避免混淆,本书在讲解命令式语言时,将尽量避免使用函数(Function)这个词,而是尽量使用过程(Procedure)和方法(Method)这两个词。

    上述代码中,x是一个参变量(参数),a则是过程体内部定义的普通变量(另,在过程体内声明的变量也叫做局部变量)。在函数体内,我们看不到类似于“x = …”的赋值语句。那么,x是什么时候被赋值的呢?答案是,当f(x)这个函数被调用的时候,参数才会被赋值。

    运行栈

    比如,我们有另一条语句,n = f(4)

    这条语句在执行的时候,就会把4这个值赋给x,然后,进入f(x)的过程体代码,执行整个过程。

    那么,这一切在内存中是怎么发生的呢?

    我们又要回到前面讲过的运行栈的概念了。每个进程运行起来之后,都有自己的运行栈。

    前面提到了数组这个数据结构的简单概念和用法。在我们的想象中,数组是一串横着摆放的内存单元格。现在,我们把这一串横着摆放的单元格竖起来,让它竖着摆放,这样,看起来,就是一个栈结构了。实际上,运行栈的基本实现结构,就是数组结构。

    这个地方,要说明一下。运行栈也是内存中的一部分。运行栈中的地址,也是一种内存地址。

    运行栈(stack)和内存堆(heap)是两种常见的内存分配方式。运行栈的结构很简单,就是一个数组结构加一个存放栈顶地址的内存单元。内存堆(heap)是一种比较复杂的树状数据结构,可以有效地搜寻、增、删、改内存块。一般来说,我们不必关注其具体实现。

    分配在运行栈(stack)上的数据,其生命周期由过程调用来决定。分配在内存堆(heap)的数据,其生命周期超出了过程调用的范围。内存堆中的数据,需要程序员写代码显式释放,或者由系统自动回收。

    我们回到例子代码。当计算机执行f(4)这个过程调用的时候,实际上是先把4这个数值放到了运行栈里面,然后,转向f(x)过程体定义的工作流程,执行其中的代码。

    进入f(x)过程体后,遇到的第一条代码就是关于变量a的赋值语句“a = 2 * x”。计算机首先在运行栈为a这个变量预留一个位置。这个位置是运行栈内的一个内存单元。

    我们可以想象一下,在一条竖着摆放的内存单元格中,最上面一个内存单元格上贴上了“a”这个标签。紧挨着“a”单元格下面的那个单元格上就贴着“x”这个标签。“x”单元格内的内容就是4这个数值。 

    接下来,计算机执行2 * x 这个表达式。得出结果后,把结果存入到运行栈中之前为a预留的内存单元中。

    从上面的描述中,我们可以看出,无论是a这个普通局部变量,还是x这个参变量,具体位置都是在运行栈中分配的。所不同之处在于,a这个普通局部变量的赋值是进入f(x)过程之后发生的,而x这个参变量的赋值是在f(x)过程调用之前就发生了。

    所有的过程调用都会产生一个参数值压入运行栈的动作,即,把对应的参数值压入到运行栈上预先为参数分配的位置中。因此,有时候,我们也把过程调用前的参数传递叫做参数压栈。

    上面举的例子是参数压栈的最简单的例子,只有一个参数。很多情况下,过程不止需要一个参数,有可能有多个参数。这就涉及到一个参数压栈顺序的问题,是从左向右压,还是从右向左压?这是一个问题。不同的语言实现有不同的做法。不过,在我个人看来,这不是什么重要的知识点,只是一种资料性的知识,不需要费心去理解。用到的时候再查资料就行了。

    在不本文结束之前,我们在来问最后一个问题:所有的命令式语言,都是基于栈结构的吗?

    • 不知读者有没有思考过这个问题。在我们学习汇编语言的时候,在我们学习高级命令式语言的时候,都涉及到了进程的运行栈。似乎,运行栈就是天然存在的,不需要多想。

    没错,进程的运行栈就是天然存在的,汇编语言需要运行栈,操作系统也需要运行栈。我们可以说,绝大部分命令式语言都是基于栈结构的,但不是全部。比如,Python语言有一个实现,叫做Stackless Python(无栈的Python),这就是一种允许程序员脱离运行栈结构的语言实现。当然,这只是一个特例,无关大局。在绝大多数情况下,当我们考虑命令式语言的时候,脑海里应该自然而然就浮现出一个运行栈的结构。

更多 推荐条目

Welcome to NowaMagic Academy!

现代魔法 推荐于 2013-02-27 10:23   

本章最新发布
随机专题
  1. [Linux操作系统] 基本 Linux Shell 命令 2 个条目
  2. [移动开发] 从代码角度去认识 Thread 9 个条目
  3. [软件工程与项目管理] 浏览器初步介绍 8 个条目
  4. [搜索引擎优化] 百度搜索引擎优化指南 3 个条目
  5. [数据库技术] MySQL中英文混合排序 4 个条目
  6. [PHP程序设计] PHP数组的遍历 7 个条目
  7. [Python程序设计] urls.py设置技巧 8 个条目
  8. [移动开发] Android SQLite增删查改实例(数据:魔弹之王) 2 个条目
  9. [PHP程序设计] 声明式编程范式 12 个条目
  10. [PHP程序设计] 命令式编程范式 6 个条目
  11. [PHP程序设计] PHP里的布尔类型 3 个条目
  12. [PHP程序设计] 编程范式初探 3 个条目
窗口 -- [资讯]