编程语言—— 变量,类型绑定与作用域

冯诺伊曼结构 –> 命令式程序设计

存储器:存储命令和数据
处理器:修改存储器中的内容

编程之外的世界呢?是不是也可以简单的概括为数据处理呢?世界上一切的事件和行为,都是对数据的处理!
娱乐,学习,制造,繁衍 ….
这是一个宏大的命题,值得深入思考!

首先需要搞清楚的是,数据是什么?命令是什么?处理器是什么?存储器是什么?语言是什么?

以下内容是对《编程语言原理》的笔记。

1 名称

1.1 名称的设计

  • 大小写问题
  • 关键字
  • 形式:驼峰,蛇形,西班牙

不同的语言有不同的使用倾向和习惯,在名字中使用下划线和混合大小写是程序的设计风格问题,并非语言设计的问题。

PHP

变量始终以 $ 符号开始

Perl

,@, 表示标量,@ 表示数组,% 表示哈希

Ruby

@, @@ 表明该变量是一个实例变量,还是类变量。

主要是看语言的设计者是不是决定在变量名字的形式中附加约定信息。

1.2 变量

什么是变量,为什么需要变量?

变量是数据的代理人

6元组 =(名称,地址,数值,类型,生命周期,作用域)

如果来描述一个人,那么该6元组的值为

(身份证,当前位置,本体,人,一辈子,中国)

1.2.1 变量的绑定

静态的绑定类型

显式声明,它列出一批变量名并指明这些变量的特定类型:c/C++, Java
隐式声明,通过默认约定将变量与类型相关联:Perl, JavaScript,Ruby

隐式的变量类型绑定的实现方式:
- 基于命名约定,如Fortran,Perl
- 基于上下文的类型推理,如根据赋值语句中的右值

动态的类型绑定

在赋值语句给变量赋值时,绑定变量类型。

有一个值得思考的问题,到底是谁会在意变量的类型,是运行程序的操作系统吗?当然不是,因为在机器码和汇编语言层面,根本就没有所谓类型的概念,有也是极其简单的类型。是编译器或解释器,它们需要知道类型信息,然后在必要的时候分配合适的内存空间。c++程序可执行文件的符号表,可以看到很多编译器的命名规则。

Python,Ruby,JavaScript,PHP中,类型的绑定是动态的。

在纯面向对象的语言如Ruby中,所有的变量都是引用,没有类型,所有的数据都是对象;但是,在Java中的引用只能引用特定类型的值。

动态类型的缺点:
1. 降低了程序的可靠性,无法进行类型检查;
2. 高昂的运行成本,类型检测在运行时进行,另外,变量必须有一个相关的运行时描述符来维护当前类型;
3. 动态类型语言往往是解释器执行;

存储绑定与生存期

变量的生存期开始于绑定到某个存储单元时,结束于该变量从存储单元上解除绑定。

  • 静态变量:
  • 栈动态变量
  • 显性堆动态变量
  • 隐性堆动态变量

2 作用域

作用域决定如何将一个名字的特定出现与一个变量相关联,也就是说,在全局作用域中,可以同一个名字会出现多次,但是每次它们绑定的存储空间可以是不同的,这是由作用域来决定的。

2.1 静态作用域

静态作用域是由ALGOL60引入,作为将名称与非局部变量相绑定的方法!之所以称为静态作用域,是因为可以静态的决定变量的作用域,即在执行之前就决定变量的作用域。仅查看源代码,就可以决定程序中每个变量的类型。

静态作用域语言
1. 语言中的子程序可以嵌套,并产生嵌套的静态作用域:JavaScript,Python,允许嵌套子程序
2. 子程序不可以嵌套,由子程序来产生静态作用域,但是只有嵌套的类定义和嵌套块能够产生嵌套的作用域:基于c的语言,C++,Java

function big(){
    function sub1(){
        var x = 7;
        sub2();
    }
    function sub2(){
        var y = x; // 这里的x是谁?
    }
    var x = 3;

    sub1()
}

要确定 sub2 中的x是谁,首先要明白解释器确定变量作用域的规则,尽管 sub2 是在 sub1 中被调用的,但是调用地点并不影响作用域,解释器会先到 sub2 里找是否有 x,发现没有,然后在 sub2 的父静态作用域中寻找,所以 x = 3,与 sbu1 中的 x 没有任何关联。

2.2 块

c/C++ 中的复合语句就是块。处理块产生的作用域时,可以和处理由子程序产生的作用域一样。

Java和C#不允许在嵌套块中使用同名变量,太容易出错!

JavaScript为嵌套的函数使用静态作用域,但不能在这种语言中定义非函数块。

2.3 全局作用域

一些语言,如C,C++,PHP,JavaScript,Python等,允许程序由函数定义的序列构成,其中变量定义可以在函数的外面,称为全局变量,对文件中的函数是可见的。

声明:指定了数据的类型和其他属性;
定义:指定属性,并分配存储空间;

允许多次声明,但是只能有一次定义。

c语言中,可以在多个源代码文件中引用某个全局变量,在引用之前需要声明,否则预编译将报错。

C++ 通过 :: (域操作符)来访问被局部同名变量隐藏的全局变量;
PHP 通过 $GLOBALS 数组来访问隐藏的全局变量,或者在 global 声明语句中包含全局变量,全局变量就可以访问了;

$day = "Monday";
$month = "January";

function cal(){
    $day = "Tuesday";
    global $month;
    $gday = $GLOBALS['day'];
}

JavaScript 无法访问与局部变量同名的全局变量;
Python 全局变量可以在函数中引用,但它只有通过 global 声明之后才能赋值,否则赋值操作会声明一个新的同名局部变量;

day = "Monday"

def test():
    global day
    day = "Tuesday"

test()

Python 中可以嵌套函数,在嵌套函数中定义的变量可以通过静态作用域在嵌套函数中访问,但这些变量必须在嵌套函数中声明为nonlocal。

2.4 动态作用域

Perl,LISP 允许变量具有动态作用域,即便如此,默认的作用域机制也是静态的。动态作用域基于子程序的调用序列,而不是基于作用域相互之间的空间关系,因此这种作用域只能在运行期间确定。

所谓的调用序列,是指 fun1 的父作用域是调用 fun1 的函数 fun2;
空间关系,是指 fun1 的父作用域是声明 fun1 的函数 fun;

由于调用序列是动态变化的,所以作用域也是动态的。

与静态作用域相比,动态作用域的可靠性更低;而且无法进行非局部变量引用的静态类型检测;可读性差;存取所耗的时间更多!

动态作用域的优点是,子程序调用不需要传参,因为它们对于被调用的子程序是隐式可见的。

2.5 作用域和生存期

作用域和生存期有关联性,但是本质上是不同维度的概念,静态作用域是文本或空间层面的概念,而生存期是时间维度的概念。

例如,c语言中用static修饰符声明的函数局部变量,作用域是静态的局部变量,生存期是整个程序的执行期间。

3 引用环境

语句的引用环境是指这条语句所有可见变量的集合。

编译器需要知道语句的引用环境,才能产生代码和数据结构。

在动态作用域语言中,语句的引用环境是局部声明的变量和当前活跃的其他子程序的所有变量。

猜你喜欢

转载自blog.csdn.net/antony1776/article/details/78542021