您的位置:首页 > 编程语言 > Delphi

Delphi const 杂谈

2014-03-06 11:07 190 查看
http://hi.baidu.com/tibetwolf2046/item/d2214eb7d147b7412aebe391

{*********************************************}

{ Delphi const 杂谈 }

{ ------------------- }

{ }

{ CopyRight (c) 2010 Tibetwolf2046 }

{ 自由转载,需注明出处 }

{*********************************************}

Ok, 我想谈谈Object Pascal中关于const的一些事,起因是我发现一方面Object Pascal中关于const的说明很少,另一方面在现实中存在对其的许多误用。当然,我这是杂谈,不可能面面俱到。

1、const是什么?

const是一个修饰词,来源于英文constant,意思是“常量、恒量、不变的” 的意思。注意!const是一个修饰词,代表一种约束(可能是语义上的,也可能是语法上的),不是“常量”!简单说,“const”不等价“常量”。“常量”是一个名词,const不是!

2、我们需要const么?

需要么?需要吧,不需要吧。呵呵~,让我想起了《大话西游》。Well,理论上,没有const我们一样可以很好的工作。我见过很多人写的程序看不到一点const字眼,但可以很好的工作。那么,为什么我们还要引入const呢?在解释这一点之前,先看看使用const修饰能给我们带来什么好处吧。

a,语义上的明确

看下面两个表达式,

I count: integer;

II const count: integer;

看到II,我们第一反应是这是一个常量,不要尝试去修改它;而对于I,我们不会有这种反应。这个区别实际上就反应了二者语义上区别。程序毕竟是“人”写的,其所面对的最终客户都是“人”,语义上的明确也许对机器无所谓,但对人却很重要。想想关于“枚举”类型,实际上,很多语言中并没有这种类型,Object Pascal却引入了,根本的原因即在语义明确。

b,语法明确

我认为这一点实际上是前一点所带来的副产品,但这一点相较于前一点有时候更重要。语法明确可以使得错误在程序编写阶段就被提前暴漏,如果我们确定一个变量是恒定不变的,使用const修饰可以使得编译器在语法分析阶段就可以分析出错误,而不是在程序运行阶段。

当然,const还有其它一些好处,但这两点无疑是引入const的根本原因。语言的优劣既要易于表达(语义上)还要尽量减少错误(语法上),毫无疑问,const满足这一要求。

结论:引入const必要。

3、const的用途

从语义和语法上说,const的用途可以简单概括为:语义提示和语法约束。通常这两者是同时存在的,你可能会看到如下的表达:

procedure Foo(const val: integer);



const val: integer = 3;

这是一个典型的语义提示,它提示你不要去修改val,delphi编译器默认情况下禁止修改val,但可使用{$J+}和{$J-}编译指示指令使得val可以修改。这里使用const修饰的重点在于语义提示。

下面的用法也是我们经常的使用方式:

const Pi = 3.1415926;

这种使用方式强调了语法的约束,在这种情形下,它相当于C中的宏,我们无法修改它,无论使用什么编译指令(当然,它不完全等价于宏)。

4、变量的内存布局

我们在讨论const,怎么说到“变量”?好吧,常量是变量的一种,是变量的特殊形式。Ok,要理解无论什么“量”其根本上都是一个内存块。之所以要在这里明确变量的内存布局是为了更准确理解const修饰(好像我说了一吨废话,汗...)。

一个变量对应一个内存块,这个内存块可能存在于代码区、数据区、也可能存在于堆区和栈区。通常,我们不关心这个,但是变量存在不同的区域对变量的使用是有影响的,它会影响变量的生命周期。通常,存在于代码区和数据区的生命期最长,一般说堆区的变量长于栈区。通常,在Object Pascal中我们像下面这样使用变量:

var

i: integer;

list: TList;

至于变量到底存在于那个区域,取决于var块所处的位置:是处于函数、单元还是类中?这里我不打算详细讨论变量的内存不布局,只是我们确信:变量的内存布局直接决定了变量的生命周期。而

使用const修饰会改变变量的内存布局。譬如:

I. procedure Foo;

var

i2: integer;

begin

//todo...

end;

II. procedure Foo;

const

i3: integer = 0;

begin

//todo...

end;

变量i2存在于栈区,随着函数调用生(压栈)和死(出栈);而i3则存在于数据区,不会被压入栈中,当然也不会随着出栈而死。well,我们知道,全局变量存在于数据区,某种程度上,i3很类似全局变量,但与全局变量不同的是,i3只有II函数可见,它只在II函数的名字空间可见,而全局变量全局可见。

另:什么样的变量存在于代码区?

常量,真正的常量。其实也不一定,这依赖于编译器的实现,在C中这样的常量常常被宏替代。完全可以将常量也放在数据区甚至于堆区。好吧,不管它放在那里,有一点是一样的,它们的生命期很长,程序不死,它们不死。存在于代码区的变量有一个特点,你通常无法取得它的地址。看例子:

const

Pi = 3.1415926;

T: double = 3.1415926;

var

p: Pointer;

begin

//p := @Pi; //语法错误,Pi是一个真正的常量,存在于代码区(依赖于编译器实现)

p := @T; //数据区,只是一个被const修饰的变量

//todo...

end;

5、一个被const修饰的变量为什么需要被改变

既然一个变量需要被改变,我们为什么还要用const修饰呢?这确实是一个语义上的矛盾。事实上,Object Pascal的const修饰在语义上不仅仅是“不可修改”,还有“静态变量”的意思。“静态”是什么意思?“不变的”、“不动的”?“变量”难道还可以“动”?某种程度上,“静态”更难以理解。Ok,不去深究了,总之,“静态变量”希望表达这样一种意思:一个变量,只在一定空间可见(不像全局变量那样全局可见)会一直存在下去而不随着函数的调用生或者死。也许Pascal语法的定义者认为这种意思更接近于const的含义而不是所谓的“静态”的含义,因此,Object
Pascal中const修饰还有定义“静态变量”的意思。而在有些时候,我们需要“静态变量”,因此,你会发现一个可被修改的变量会被const修饰。用术语来表述就是:我们有时候会需要这样一个变量,既有像全局变量一样的生命期,又有像局部变量一样的可见性(名字空间)。

简单说,一个const修饰的变量被改变意味着我们需要的是一个“变量”而不是一个“恒定的、不可修改的”的“常量”。

仔细观察下列定义:

I const AConst = 2;

II const AModifiedConst: integer = 3;

可以发现,I中const修饰的是“2”, II中const修饰的是Integer;除了语义上的约束强调外,I强调了值2不可改变,II强调了类型Integer不可更改。const修饰符是“左结合”操作符。准确的说,这就是Pascal编译器对const的理解。所以,当使用{$J+}和{$J-}编译指令时,I不会发生改变,II会。

6、const修饰的变量的生命期和可见性

在4中其实已经有讨论,这里进一步。

在3中,我们说,const修饰无论从语法还是语义上,它体现的是一种约束,这种约束不仅改变了变量的内存布局,更重要的是改变了变量的生命周期。当const修饰的变量发生改变时,这一点变得尤其重要。在5中,我解释了为什么一个被const修饰的变量需要被修改,既然一个变量可能会被修改,那么一定需要关注其生命期和可见性。

和一般的变量一样,被const修饰的变量遵循同样的可见性,没有例外。但生命期则不同,在4中我们已经看到这一点。看下例:

procedure TForm1.Button1Click(Sender: TObject);

{$J+}

const

val1 = 26;

val2: integer = 10;

{$J-}

var

i: integer;

begin

inc(val2);

//inc(val1);//语法错误

Label1.Caption := 'val1: ' + IntToStr(val1);

Label2.Caption := 'val2: ' + IntToStr(val2);

end;

每次点击按钮,val2会在前一次结果上再加1,因为,val2不再是一个局部变量,而变成一个“静态变量”,不再试一个存在于栈中的临时变量,而是在数据区的一个变量。其生命期被得以延长。这一点在有些情况下很有用。我们知道,全局变量的使用是一个陷阱,因为其全局可见,很可能会被误修改,要调试这样的错误,往往需要遍历全部代码。许多情况下,我们使用全局变量,只是想用其全局生命期,而讨厌其全局可见性,而const修饰的变量恰恰提供了这一点。

7、利用const修饰构建只能生成唯一对象的类

类是一个模板,利用这个模块可以生成任意数量的实例对象,但有时候,我们只希望生成唯一的实例,比如一个程序中,我们只希望有唯一的数据库连接,再比如Application,它也只能是唯一的。利用const修饰可以做到,当然它不是唯一的方法,也可以使用全局变量(据说delphi2006可以定义类变量,使用它也可以实现),但全局变量我们需要很谨慎的维护,这在许多情况下很难。

下面是一个例子:

-------------------------------------

TSingle = class(TObject)

private

protected

constructor CreateInstance;

class funtion GetInstance(Request: integer): TSingle;

public

constructor Create;

destructor Destroy; override;

class procedure ReleaseInstance;

class function SingleInstance: TSingle;

end;

implementation

constructor TSingle.Create;

begin

inherited Create;

raise Exception.CreateFmt('只能通过SingleInstance方法来创建和访问%s的实例!', [ClassName]);

end;

constructor TSingle.CreateInstance;

begin

inherited Create;

end;

destructor TSingle.Destroy;

begin

if GetInstance(0) = self then GetInstance(2);

inherited Destroy;

end;

class function TSingle.GetInstance(Request: integer): TSingle;

{$J+}

const FInstance: TSingle = nil;

{$J-}

begin

{

Request = 0 : 不做任何处理,供释放时使用

Request = 1 : 存在该类实例时使用该实例,不存在时创建之

Request = 2 :返回一个空指针,用于重置实例

}

case Request of

0: ;

1: if not Assigned(FInstance) then FInstance := CreateInstance;

2: FInstance := nil;

else

raise Exception.CreateFmt('%d是GetInstance()的非法参数调用', [Request]);

end;

result := FInstance;

end;

class procedure TSingle.ReleaseInstance;

begin

GetInstance(0).Free;

end;

Class function TSingle.SingleInstance: TSingle;

begin

result := GetInstance(1);

end;

---------------------------------------------------

对类TSingle你可以像一般类那样使用,如下:

var

ASingle: TSingle;

B: TSingle;

C: TSingle;

begin

ASingle := TSingle.SingleInstance;

B := TSingle.SingleInstance;

C := TSingle.SingleInstance;

//todo...

ASingle.Free;

B.Free;

C.Free;

TSingle.ReleaseInstance;//note: 这才是真正释放对象的地方

end;

无论定义多少对象指针,其实都将指向同一个对象。看起来,上面的代码很麻烦,为什么还要这样做,根源就在我们规避了全局变量的全局可见,而且使用类,使得这个拥有超长生命期的变量FInstance变得“自维护”,也就是说,我们只需要在这里类里维护FInstance就可以保证在任何地方使用FInstance的时候都保持一致。

8、题外话

对一些我们很少使用的类型(比如集合、枚举)、很少使用的关键字,往往有其存在的特定价值,在特定情况下,使用它们会带来意想不到的便利和清晰。不要忽略它们,对编译器设计者来说,如非必要,他们绝不会引入它们,它们的引入一定有它们的道理,当然,也绝不要滥用它们。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: