您的位置:首页 > 其它

[程序设计语言]-[核心概念]-02:名字、作用域和约束(Bindings)

2014-08-03 09:09 309 查看

1.名字、约束时间(Binding Time)

在本篇博文开始前先介绍两个约定:第一个是“对象”,除非在介绍面向对象语言时,本系列中出现的对象均是指任何可以有名字的东西,比如变量、常量、类型、子程序、模块等等。第二个是“抽象的”,因为我们讨论的是语言的核心概念,所以“抽象的”具体指代的是语言特征与任何计算机体系结构分离的程度。

如果可以给名字下个定义,那么它是代表某东西的一些助记字符序列。就好比张三、李四,对应到大部分语言中一般可以等价为“标识符”。名字可以让我们用一个符号来表示变量、子程序、类型等等,其实名字就是一种抽象,比如一个变量名就帮助我们隐藏了背后的一些存储相关的复杂细节、子程序是控制抽象(帮助我们隐藏一个复杂的代码逻辑处理过程)、面向对象中的类属于数据抽象(把对数据的复杂操作逻辑隐藏到一系列方法背后)等等。

看到有人在豆瓣上说Binding不应翻译成约束,而应该是绑定。我不这样认为,绑定是个动词的感觉,但是实际上作者要表达的Binding有一些名词的意思,其实也都无所谓,能理解他要表达的是什么就是了,借用暴漫王尼玛的那句话“不要在意这些细节”。

约束是表示两个东西之间的关联,而约束时间是指约束建立起来的时间。一般而言,约束建立的越早,也就意味这更好的性能;越晚的约束则能提供更大的灵活性。基于编译的语言通常会比基于解释的语言要高效,也就是因为更早一些决策提前建立起了约束。基于编译的语言通常可以在编译阶段就确定出一些内存布局,从而生成高效的访问代码;而解释语言则很难做出这种高效的安排,必须等到在运行前的那一刻才能确定出访问一些变量的地址。

2.对象生存期和存储管理

约束为名字和其背后具体的对象之间建立了关联,那么这种关联的创建、销毁、隐藏等一些列概念可以用一下几个关键的事件来表示。

对象的创建;

约束的创建;

约束的失效以及激活;

约束的撤销;

对象的销毁;

一个名字与对象之间的约束从建立到撤销的这个时间段称为约束的生命周期

一个对象从创建到销毁的这个时间段称为对象的生命周期

有点绕哈,两个生命周期之间有相同的时候,比如看下面的代码:

void Write1024()
{
String str = "我这个字符串对象的生命周期和约束的生命周期一样长";
Console.WriteLine(str);
}


这个子程序中的字符串的对象的生命周期、和str之间的约束的生命周期,都是随着子程序的调用而开始、调用结束而结束。也会有对象的生命周期比约束的生命周期要长的时候,如下代码:

String _str = "我这个字符串对象的生命周期要比和str的约束的生命周期要长";
void Write1024()
{
String str = _str;
Console.WriteLine(str);
}


由于_str所代表的对象是全局的,而在Write1024子程序内与str建立约束的时间仅限于子程序调用过程中,则可以看出此对象的生命周期要长于Write1024内与str所建立的约束。当然,也有约束的生命周期长于对象的生命周期的时候,不过这种情况都是程序出错的象征了,如下:

class Program
{
String str = "我是对象的一个字段";
static void Main()
{
Program p = new Program();
Console.WriteLine(p.str);//正常
DeleteP(ref p);
Console.WriteLine(p.str);//P所指的对象和名字p的约束还在,但是对象已经不在了
}
static void DeleteP(ref Program p)
{
p = null;
}
}


new出来的对象和p之间的约束存活时间是在Main方法内,然而在执行完DeleteP后,对象已经被销毁,然而与p的约束还在(称为悬空引用),这时对p.str的访问则是不可达的,会抛出null的异常。熟悉C++的朋友应该没少吃过野指针的苦头吧,野指针就是约束的生命周期大于对象的生命周期的情况。对象的创建同时也会伴随着系统为其分配存储空间,根据语言需要大致有三类主要的分配方式,静态分配、基于栈的分配、基于堆的分配。

2.1静态分配

静态分配通常情况下是意味着高效的访问性能,因为在程序运行过程中静态分配的对象的位置是不会变动的、只需一次创建开销的,生命周期也超长(与程序共生死)。

一般而言,静态对象、常量、一些字面量对象、程序代码都是静态分配,因为它们在运行中一直一成不变。但是同时也有一些子程序内部的局部变量也会使用静态分配,这是为何呢?我们现在的普遍认知都是子程序的局部变量会在子程序每次调用的时候来分配创建,然后在结束的时候销毁。我们假设一下,假如一个语言它不支持递归,,,你没看错,不支持递归噢(比如早期的Fotran语言就不支持递归)。这种情况下子程序在某一时刻只会有一个实例在运行,,,因为它不能调用它自己(好忧桑的感觉),那么这些子程序内部的局部变量便可以静态分配,从而能减少频繁的创建、撤销操作代来的开销。

2.2基于栈的分配

由于现在的高级语言绝大多数都支持了递归,故而在某一时刻就可能会出现一个子程序的多个实例,显然上面介绍的静态分配是无法满足这种需要了。幸运的是子程序的调用时具有嵌套性质的,这样人们就可以利用栈这个特殊的结构来管理子程序空间的分配。

一个子程序在栈中都有自己的帧(也称活动记录),帧里面包含当前子程序实例的参数和返回值、局部变量、临时量和一些薄记信息等,如下面的一个图例:

class Program
{
static void Main()
{
Int32[] arrays = { 1, 2, 3, 4, 5, 1 };
Int32 i = 1;

//展开结果
FuncFilter ff = new FuncFilter();
ff.i = i;
Int32 count = GetEqCount(arrays, new Func<Int32,Boolean> (ff.Eq));

Console.WriteLine(count);
Console.ReadKey();
}
static Int32 GetEqCount<T>(IEnumerable<T> array, Func<T, Boolean> filter)
{
//略
}
//模拟编译器生成的代码
class FuncFilter {
public Int32 i;
public Boolean Eq(Int32 v) {
return v == i;
}
}
}


(v)=>v=i 展开结果

4.2一级和二级子程序

一般而言,在语言中,如果一个值可以赋值给变量、可以当作参数传递、可以从子程序返回,那么它被称为具有一级状态(和我们在js中说函数是一等公民一个含义)。大多数的语言中数据对象都是一级状态。二级状态是只能当作参数传递、不能做返回值、不能赋值给变量(笔者觉得这点好像不对,但也没查到相关资料);三级值则是连参数也不能做,比如C#中一些+-*/等符号。

在具有嵌套作用域的语言中,一级子程序会带来很大的实现上的复杂性,比如上面js闭包的例子,在makeEqFilter执行完毕后,它的作用域是不能撤销的,如果撤销,那么闭包中抓住的引用就变成悬空引用了。为了避免这一问题,大部分函数式语言都表示局部变量具有非受限的生命周期,它们的生命周期无限延长,直到GC能证明这些对象再也不使用了才会撤销。那么不撤销带来的问题就是这些子程序的存储分配基于栈帧是不行了,只能是基于堆来分配管理。为了维持能基于栈的分配,有些语言会限制一级子程序的能力,比如C++,C#,都是不允许子程序嵌套,也就从根本上不会存在闭包带来的悬空引用问题。

5.作用域里的约束

在以上的讨论中,我们假设的情况都是一个名字约束一个对象,其实我们发现在语言中不是这样子。一个对象可以有多个名字(好比一个人可叫张三、也可以叫狗蛋),一个名字也可以代表多个对象,前者是别名,后者称为重载。重载一般会有操作符的重载、子程序名字的重载。其实操作符重载也是属于子程序名字重载,比如C#中你可以对+-等符号定义一个静态方法,这也只是编译器的一个语法糖而已。

//别名
String zhangsan="我是张三";
String goudan=zhangsan;

//子程序名重载
void print(int);
void print(double);
void print(string)


重载比如print等此类子程序名,编译器也都会根据上下文决定你真正要调用的是那个函数。由重载带来的还有两个常见的特性,多态性和强制,就比如上面的print三个子程序就构成了多态性(参数的多态性),如果我为print(double)传递了一个int,那么还会出现强制 (数据类型的转换)。如果在面向对象语言中,还会有子类型的多态性以及强制,比如一个接受接口类型参数的方法,可以通过传递一个子类类型来调用。还有一种多态称之为类型化参数的多态性(通常我们称之为“泛型”),具体例子就不列举了。

总结

本篇从名字入手,介绍了名字与其背后的对象的约束关系、以及约束时间的概念;然后介绍了对象的分配策咯(静态、栈、堆);紧接着讨论了名字与对象之间建立的约束的生命周期,并由此引出了作用域的概念;进一步延伸出多个约束组成的引用环境的相关概念以及问题。这一篇中术语、概念繁多,我也适当的精简了一些,无奈觉得文字还有点晦涩,还是功力不够。其实我觉得这些术语概念不一定需要记得,能理解它要表达式什么就可以了,因为术语概念无非就是给一些要表达的含义起了一个简短的名称而已,有时你觉得名称不恰当,那也是正常,理解就够了,不必在意这些不影响理解的小细节。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐