来自:http://www.cnblogs.com/tibetwolf/articles/1785744.html

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

{*********************************************}
{    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,语义上的明确
看下面两个表达式,

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要求的,与编译器有关。

~Delphi const 杂谈~的更多相关文章

  1. delphi const

    参考:http://www.cnblogs.com/tibetwolf/articles/1785744.html 1.const修饰可能会优化编译代码.关于这一点与编译器密切相关,由于变量被cons ...

  2. delphi const的用法

    unit RadKeygen; interface uses Classes,SysUtils,Windows; function fun1():string; implementation cons ...

  3. Delphi Webservice 杂谈

    用WebService来实现B2B集成的最大好处在于可以轻易实现互操作性 WebService可用基于XML的SOAP来表示数据和调用请求,并且通过HTTP协议来传输这些XML格式的数据,因为此时的调 ...

  4. Delphi 关键字详解[整理于 "橙子" 的帖子]

    absolute //它使得你能够创建一个新变量, 并且该变量的起始地址与另一个变量相同. var   Str: ];   StrLen: Byte absolute Str; //这个声明指定了变量 ...

  5. Delphi常用关键字用法详解

    本文详细介绍了Delphi中常用的各个关键字名称及用法,供大家在编程过程中借鉴参考之用.详情如下: absolute: ? 1 2 3 4 5 6 7 8 9 10 //它使得你能够创建一个新变量, ...

  6. 【转】Delphi 关键字详解

    absolute //它使得你能够创建一个新变量, 并且该变量的起始地址与另一个变量相同. var Str: string[32]; StrLen: Byte absolute Str; //这个声明 ...

  7. Delphi、Lazarus保留字、关键字详解

    Delphi.Lazarus保留字.关键字详解 来自橙子,万一的博客以及其他地方 保留字:变量等标识符可以再使用: 关键字:有特定含义,不能再次重新定义: 修饰字:类似保留字的功能,也就是说可以重用 ...

  8. 【delphi】关键字详解

    absolute {它使得你能够创建一个新变量, 并且该变量的起始地址与另一个变量相同.} var Str: ]; StrLen: Byte absolute Str; {这个声明指定了变量StrLe ...

  9. 关于delphi软件运行出现Invalid floating point operation的错误的解决办法

    关于delphi软件运行出现Invalid floating point operation的错误的解决办法   关于delphi软件运行出现Invalid floating point operat ...

随机推荐

  1. 跨域通信的解决方案JSONP

    在web2.0时代,熟练的使用ajax是每个前端攻城师必备的技能.然而由于受到浏览器的限制,ajax不允许跨域通信. JSONP就是就是目前主流的实现跨域通信的解决方案. 虽然在在jquery中,我们 ...

  2. nginx服务器去掉url中的index.php 和 配置path_info

    隐藏index.php server { listen 80; server_name yourdomain.com; root /home/yourdomain/www/; index index. ...

  3. django 表单验证和字段验证

    表单验证和字段验证 表单验证发生在数据验证之后.如果你需要自定义这个过程,有几个不同的地方可以修改,每个地方的目的不一样.表单处理过程中要运行三种类别的验证方法.它们通常在你调用表单的is_valid ...

  4. 前端解放生产力之–动画(Adobe Effects + bodymovin + lottie)

    大概很久很久以前,2017年,参加了第二届中国前端开发者大会(FDCon2017),除了看了一眼尤雨溪,印象最深刻的就是手淘渚薰分享的关于H5交互的内容了.时光荏苒,最近再次接触,简单回顾一下. 示例 ...

  5. OpenCV中响应鼠标消息 (转)

    #include <cv.h> #include <highgui.h> #include <stdio.h> #pragma comment(lib," ...

  6. Servlet 介绍

    JSP 的本质就是 Servlet,开发者把编写好的 JSP 页面部署在 Web 容器中后,Web 容器会将 JSP 编译成对应的 Servlet. Servlet 的开发 Servlet 是个特殊的 ...

  7. python---爬虫相关性能(各个异步模块的使用,和自定义异步IO模块)

    一:线程池,进程池等相关文章了解 python---基础知识回顾(十)进程和线程(py2中自定义线程池和py3中的线程池使用) python---基础知识回顾(十)进程和线程(协程gevent:线程在 ...

  8. Oracl闪回数据命令。

    当数据库操作没有备份,并且误删数据.可闪回任何 当前闪回15分钟前数据库状态.  alter table BASE_APPOINT_LOG enable row movement;flashback  ...

  9. 【BZOJ】3495: PA2010 Riddle 2-SAT算法

    [题意]有n个城镇被分成了k个郡,有m条连接城镇的无向边.要求给每个郡选择一个城镇作为首都,满足每条边至少有一个端点是首都.n,m,k<=10^6. [算法]2-SAT,前后缀优化建图 [题解] ...

  10. 【BZOJ】4596: [Shoi2016]黑暗前的幻想乡

    [题意]给定n个点的无向完全图,有n-1个公司各自分管一部分路,要求所有公司都有修路的生成树数.n<=17. [算法]容斥原理+生成树计数(矩阵树定理) [题解]每个生成树方案是一个公司有无修路 ...