还是老夫来kill这篇帖子吧,呵呵,本人连OS一天也能kill它两三回,还怕kill不了这篇
帖子?
纵观这篇帖子,回复的高手不少,本人也从中学到很多,只是话题的中心好像东拉西扯,
先有两个'静态方法'搅局(C++和OP),后有两个Self指针蛮缠(指向对象的指针和指向类
的指针),事实上同志们已经把问题讨论正确了,我现在只是来做番整理。
首先还是回到ZXW49362727兄的问题上来吧,
(以下是ZXW49362727兄的原文,带"/*"的行是我得注释,最后有一点对delphi对象模型
的探讨)
请看以下代码:
var
Strings: TStringList;
<1>: Strings:= nil;
Strings.Free
//为何以上代码运行不会出错?如果Free方法确实调用了,那么它保存在内存哪里呢?
/*答:1.首先当你第一句声明了一个"var Strings"后,你在栈空间得到一个4字节的内存,
/*这块内存由符号"Strings"对应(就像你声明一个"var a:Longint;"后'a'就对应于
/*栈中一个4字节(Longint大小)的内存一样)。而这段内存中此刻存放的内容是什么呢?
/*它是不确定的,(更准确的说是上一次系统对这块内存写后的值,不过这里我们不管它)
/* 2.你做的非常好,你把这块内存赋值为nil,在c++中这是标准做法,否则你后来可能
/*直接使用它的值作为指针,也就是前面说得不确定的值作为指针,导致非法内存访问。
/* 3.关键的来了!你调用了"String.Free;"准确的说吧,你调用的是这个:
/* Free(Self)
//这个Self哪里来的?就是Strings对应内存的值啊,你
/* //把它赋值为nil了,这个self就是调用所有普通成员方法
/* //都会用到的"指向对象"的指针,但这里它是nil,没有指
/* //向任何对象。 请注意别和"指向类"的self指针混淆。
/* 再准确点说你调用的这个:
/* Free(nil);
/*在Free中就简单了,它会判断传给它的self指针的值,是nil就什么也不做,直接返回
/*所以这段代码没有出错。你问Free方法保存在内存的哪里?它当然是保存在只读的内存
/*页中,或者说代码页。我想你的真正想问的是Free过程是怎么被调用的?很简单,Free
/*是静态绑定的普通成员方法,它的入口地址是编译时就能确定的,调用的时候把参数压
/*入栈,一个call调用指令就完事。你或许会疑惑的是Destroy是虚拟方法啊,不错,只是
/*这对于我们使用Free来说毫无关系,因为怎样找到Destroy方法并调用它是Free的责任,
/*我们不用管,关于这点后文有详述。
/*好了,第一个问题搞定。
<2>: Strings:= TStringsList.Create;
Strings.Free;
Strings.Free;
//出错。如果上面那个问题解决的话,这个问题也解决了。
/*答:1.先看你的第一句,你调用TStringsList类的Create方法,这个方法在堆中创建一个
/*TStringsList对象并返回一个指向这个对象的指针,返回的这个指针赋值给了Strings。
/*也就是说Strings所对应的那块4字节大小的栈内存中此刻存放的值是Create方法创建的
/*对象的地址.
/* 2.第二句你调用Free释放这个对象,你实际上是这样调用的:
/* Free(Self)
//注意这里的self就是Strings所对应那块4个字节大小栈内存
/* //的值,其值是上一句Create方法创建的对象的地址。
/*这里Free首先看Self是否为nil,这里不是,所以它调用Destroy摧毁对象(至于Free方法
/*怎么找到虚拟方法Destroy见后文)。摧毁对象后Free过程返回。
/* 3.第三句你再次调用Free过程,也就是再次调用:
/* Free(Self)
//特别注意,你这里的self的值还是第一句Create方法所创建
/* //对象的地址,因为Self就是Strings所对应的4字节大小栈内
/* //存的值,而你并没有对这4字节内存作任何操作,它当然还保
/* //持着原来的值,也就是已经被销毁的对象原来的地址。
/*现在就简单了,Free方法先检查Self是否为nil,基于前述的理由它当然不是,好紧接着
/*Free方法调用Destroy摧毁对象,夷????这个对象不是已经摧毁了嘛?它所占用的内存
/*已经回收了阿,而Destroy再次对已经回收的对象所占资源进行回收当然就会出错了。
Strings:= TStringsList.Create;
Strings.Free;
Strings:= nil;
Strings.Free;
Free的VCL源码如下:
if Self<>nil then
Destory;
确实,如果String=nil 时,不会调用Destory所以不出错。
但问题是Free不是一个静态方法,在没在调用Create的时候,Free方法的代码保存在哪呢?
请高手指教。
/*答:首先你第一句话就错了,Free正是一个静态方法,这里请注意,千万别和C++中的静
/*态方法混淆,同一个名词在不同的语言中对应不同的概念这是常有的事,delphi中的
/*静态方法强调的是静态绑定的意思。Free是静态绑定的,这意味着编译时已确定地址
/*了,所以一切明了。
-------------------------------------------------------------------------------
最后,顺便探讨一下delphi的对象模型,本人接触Delphi也不到一个月,以前是c++程
序员,也是用C++来理解delphi,呵呵所以刚开始时也犯了Pipi兄一样的错误,把delphi的
class method等同于c++的static member function.这个问题也是前几天抄vcl源码时才搞
明白它们概念上是类似但实现有区别。vcl源码那几个主要构件源码现在还没抄完呜呜:~(
呵呵,忽然发现DFW上许多兄弟同时精通delphi和c++,果然是高手如云。
先回复几个DFWer:
to sadj:
Free过程一点也不特殊,你说得:"一个对象没有建立之前,除了静态方法和变量,对象的方
法是没有分配地址的",这句话我不太明白你的意思,你这里的静态方法是指的class Method
还是普通成员方法?什么叫没有"分配"地址?没有"确定"地址吧?作为oo语言,OP和C++等
原生编译的语言都只是在语法语义层面将数据和代码绑定起来,而编译底层是将代码和数据
分开的,因此我们不难理解所谓的"普通成员方法"其实和原来老PASCAL的函数过程是一回事
只是每个函数都有一个隐含的参数Self,它指向一个堆中的地址,以这个地址开始的这个区
域就是对象的茜身之所,因此我们可以毫不犹豫的确定普通成员方法是静态绑定,编译时就
可确定其入口地址,就像原来的老pascal语言的函数过程一样。
而类方法其实和普通成员方法的实现完全一样,只是传给它的Self参数指向的是类而不是
对象更清楚些是指向的只读的代码区中的一张表。呵呵,这里最容易糊涂。这张表里记录了
所有的虚拟函数地址以及一些类型信息比如父类所对应的表的地址,千万注意,在delphi中
类就等同于一张表,所以类也有地址,比如类引用就是指向这张表的指针,你给它赋值为不
同的类,它就指向不同类所对应的表,而这也是C++程序员最难理解的地方。因此,现在我
们也可以确定类方法和普通成员过程一样也是静态绑定的,编译时便可确定入口地址。
现在来看虚拟方法,当我们定义了一个类,编译器便完全确定了所有的方法入口地址,
无论是普通成员方法还是类方法还是虚拟/动态方法,(当然了,所有的方法都是由它编译
的它还不知道啊?
但是或许会有人说虚拟/动态方法不是晚绑定么?
**千万注意:晚绑定是针对对象而不是针对类,在所有OO语言中都是如此。
看看我们怎样使用类吧,我们用类来定义对象,用对象来使用类。一个对象比如猫从
概念上来说当然是一个动物(is a的关系),而一只狗也是一个动物,注意我为什么不说
"猫类是一种动物类?" 前者是对象之间的关系,它正是后者类之间关系的体现,
现在我们让一只动物"叫",很显然当我们把这个命令发给猫,它当然是"喵喵"叫,发给狗
它当然应该是"汪汪"叫,这种现实世界中广泛存在的"**一种动作不同实现!**"的关系就
叫多态,多态并不是程序世界的术语而是现实世界中的术语,为什么会引入到程序世界中
呢?就是因为为了让我们的程序编写方式更接近于真实世界,因此也就更为易写易读易理
解。这就是多态技术乃至面向对象的技术的由来。扯远了哈,那么现在我们的问题就是怎
样以高效易用的方式在程序世界中实现现实世界中的多态关系。
前面说到当我们定义了一个类所有的方法就已经确定,它们的代码也都存放在只读的代码
页中,但是编译器另外还会构造一张表,这张表中记录了这个类所有的虚拟方法。这样子
我们程序中用到的每一个类都会有一张记录了他们各自虚拟方法的表,当我们使用对象的
时候,首先是申明一个对象,其实此时我们得到的只是一个指针,并且这个指针的值是不确
定的,注意我们申明时申明的对象所属类型,这一点也就决定了所有的普通成员函数怎样与
我们这个对象联系起来。紧接着我们创建对象,这一步决定了我们的对象怎样与具体的类所
对应的那张表联系起来。这个对象是由创建对象实体时调用的那个类所对应的create函数创建的
它当然知道自己的类对象实体有多大,总而言之它只返回一个地址赋值给申明对象时得到
那个指针。注意这一步中的Create方法,它创建对象实体也初始化这个对象实体的所有域
其中隐含的第一个域就是指向用来调用create那个类的指针,它指向那个类所对应那张表
也就将所有的虚拟方法与对象联系起来。因此当我们调用一个虚拟方法时,实际上是先根据
对象实体的第一个域找到类对应的表再查表中虚拟函数的地址,再调用它。
***综上所述,类方法和普通成员方法都是在我们申明一个对象时就确定了,而虚拟方法
和动态方法都是在创建对象时才确定的。****
to 教父:
你有一句话没说完整:"所以既使没有创建实例,但还是可以访问它的静态方法"
可以访问是一回事,程序崩溃通常也是同一回事,呵呵,是的,只要你声明了一个对象,
不必创建对象实体就可以访问它的静态方法(普通成员方法),但是注意:当你访问一个静态
方法时是以你声明的这个对象(引用)的值作为self指针传给静态方法的,而此时它的值是
不确定的,紧接着有两种可能,1.你调用的这个静态方法压根儿没用这个self指针,比如
你可以写一个只输出'HELLO!'的静态函数试试,绝对没问题。
2.你调用的这个静态方法会检查这个self指针是否为空,
如果为nil则什么也不做,返回。这种情况下只要你保持一个声明一个对象马上赋值为nil
的习惯那么程序也不会崩溃。(对于c++程序员这是入门守则呵呵)
to savenight:
你可真有些冤枉,你说得对你没错。看到sadj对你义愤填膺的样子我可真有些同情你了,
自己认为对的可一定要坚持。即使错也的错个明明白白。你很谦虚,交我这个朋友好嘛?
我的qq:65326804
to sadj :
本来我想你们讨论出任何结果我都不会惊讶,但,老天,你以什么作为根据说op/vcl不符
合OOP的理念?OOP的理念与任何底层机制/计算策略有关马?比如你完全可以用双表格模型
实现OOP。。。
最后,本来还想探讨一下比如VMT中有些什么还有这几天研究VCL的心得(抄源码抄出来的苯
鸟先飞么)比如消息机制等等,一是天快亮了,手累了得睡觉,二是一些思考还不成熟,等
有空了再贴出来大家探讨一下,我有不少疑惑盼望大家指正,学了一个月DELPHI觉得蛮好
到DFW不久,大家多多指教。还有我看了关于用OP实现GP的帖子,有些想法,感觉照搬C++
的模式是不行的,它们的底层机制区别极大,最有希望的是向泛型JAVA的方案靠齐,感觉
DELPHI和JAVA和.NET实在是极为神似,出来这么久的DELPHI还与当今领先技术合拍实在是
BORLAND的技术实力。哎,睡了睡了