Delphi const 杂谈

{*********************************************}
{    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会。

补充:

任何变量都是一个内存块,从某种程度上说,const修饰实际上是修饰这个内存块,这种修饰强调两点:1,内存块位置和大小不可以改变;2,内存块内容不可以改变。当仅仅强调1时就是“静态变量”的情况,当二者同时强调时,就是上面I所对应的情况。所以,就我个人而言,我更倾向于Pascal所使用const语义而不是C中使用“静态变量”语义,后者似乎缺少某种一致性,增加了概念。

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、题外话
      对一些我们很少使用的类型(比如集合、枚举)、很少使用的关键字,往往有其存在的特定价值,在特定情况下,使用它们会带来意想不到的便利和清晰。不要忽略它们,对编译器设计者来说,如非必要,他们绝不会引入它们,它们的引入一定有它们的道理,当然,也绝不要滥用它们。

补充:

1,const修饰可能会优化编译代码。关于这一点与编译器密切相关,由于变量被const修饰后其内存大小和位置是确定的,这样,在如下的方式里可以被优化

?
1
procedure Foo(const val: integer);

虽然从语法上这是一个值传递,按惯例需要复制参数传递到函数Foo中,但因为const的特性,这里只需要参数传递地址就可以了,这会比不使用const修饰快,尤其是在使用string作为参数的过程中,效率优势会明显。当然,这一点不是Object Pascal要求的,与编译器有关。

(0)

相关推荐

  • VBA基础一:对象、属性、方法、变量

    '1.工作簿(Workbooks) ' Workbooks(N)第N个工作簿 ' Workbooks ('工作簿名') ' ActiveWorkbook 活动工作簿 ' ThisWorkBook '代 ...

  • 《大江东区》杂谈,读书笔记

    雷东宝出监狱,对小雷家重新安排,为了得到政府的支持,把小雷家镇政府所有制.对于人物的安排,士跟是不敢再用了,让红伟远走,隔了一大领军人物,提了一个后期新秀,让背后的一堆后期新秀站到了他队伍中,这就是权 ...

  • 春风摩托车DELPHI系列电喷系统维修故障码大公开,快抢!

    作者:摩托中国 春风650等摩托车电喷系统采用的是德尔福系统,维修中梳理总结出了这个系统的相关故障码表,对维修此类车型助益很大,好东西不敢私藏,分享给大家,希望对您有所帮助!

  • 良心哥周末杂谈二号文章2021年5月8日

    良心哥周末杂谈二号 今天有空,就再跟大家闲聊一会. 各位亲爱的股民朋友们大家好,近期有不少的新朋友加入了爱股票社区的大家庭,如果你们觉得我写的东西对你们有用,可以转发给你做股票的朋友们,也可以帮助到他 ...

  • 周漠杂谈32:​古玉三玦

    <史记·项羽本纪>中记载了楚汉相争时,谋土范增在鸿门宴上"举所佩玉玦以示之者三,项王默然不应."范增利用玉玦表达了决裂之意,暗示项羽杀掉刘邦,以绝后患.这则以器物传递信 ...

  • 杂谈围棋的段位和棋品

    明代许谷先生<石室仙机>,网图 杂谈围棋的段位和棋品 史啸虎 前注:上周发表了那篇题为<围棋之道的变迁与座子制度>的文章后,有几位棋迷朋友读后评价甚好,还小窗或在朋友圈留言,希 ...

  • 形意拳杂谈31:内家拳开胯的核心功法,简单易学,人人皆能掌握

    人体有四把锁,五条线,练习拳术者不可不知. 四把锁,分别锁住两肩两胯,以至于肢体动作迟滞,转换不灵.练习拳术,不管何门何派,都需要逐步打开这四把锁,让自己的四肢出入于自由之境. 五条线,两臂两腿一共四 ...

  • 这样的课堂表演,您认为合适吗?——备课杂谈之二十

    (图片来自网络)  这样的课堂表演,您认为合适吗? --备课杂谈之二十 (2021128) 袁建国 孔子曾说过:"知之者,不如好之者.好之者,不如乐之者."为了使学生"乐 ...

  • 玩机技巧 篇十:百度网盘高速下载免费白嫖!附替代网盘推荐 & 存储杂谈

    〇.前言 百度网盘作为目前国内最大的网盘平台,可谓垄断了国内的文件分享和资源下载市场,大家的工作学习都经常中碰到百度网盘分享的链接. 然而,一家独大的后果就是臭名昭著的「非会员限速」,网友调侃就算是快 ...

  • 权谋杂谈:未来进大观园当丫头,才是底层最好的工作!

    小时候看古装剧.历史书籍,看到等级的差异,丫鬟.小厮没有人格和自尊,再加上学校受到的平等教育,内心深处一直以为,做什么都不可能做人家的下人,新时代,在没有阶级压迫,多好.但是随着年龄的增长,对社会认知 ...