注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

yeye55的博客

编程就象品一杯清茶,清淡却又深厚。

 
 
 

日志

 
 
关于我

宅男一只,喜欢编程。没事的时候就喜欢抱着电脑看看电影、看看动漫、听听音乐、打打网游、逛逛论坛。清静的时候喜欢专研编程技术,但是发现自己至今仍然只是一只三脚猫。

网易考拉推荐

Delphi XE4 语言指南 - 5 变量和常量  

2014-11-25 19:37:44|  分类: XE4参考指南 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

  本指南名称:Delphi XE4 语言指南,作者:叶叶,于2014年11月25日在网易博客上发表。页面地址:http://yeye55blog.blog.163.com/blog/static/197241021201410252936156/ 。本指南全文可以在上述页面中下载。请尊重他人劳动成果,转载或引用时请注明出处。

5 变量和常量

  变量(variable)和常量(constant)都可以容纳数据。变量代表了一个可以在运行时进行改变的值,而常量代表了一个在运行时保持不变的值。

5.1 变量

  变量(variable)就是一个标识符,其代表的值可以在运行时改变。换句话说,一个变量就是一个内存位置的名称,可以使用该名称来对该内存位置进行读写。变量就像是一个数据的容器,而且是有类型的,编译器根据变量的类型来解释其存储的数据。声明一个变量的基本语法格式如下所示。

var 变量名称列表 : 变量类型;

  其中的变量名称列表是一个用逗号分隔的由有效的标识符所构成的列表。其中的变量类型是一个有效的类型标识符或者是一个类型定义。如果是连续的声明多个变量,没必要重复使用保留字var,只需要保留第一个“var”即可。一些进行变量声明的例子如下所示。

01 var I : Integer;
02
03 var X, Y : Real;
04
05 var
06     X, Y, Z : Double;
07     I, J, K : Integer;
08     Digit : 0..9;
09     Okay : Boolean;

代码5.1 变量的声明

  在过程或函数中声明的变量被称为局部变量(local variable),而其他的变量被称为全局变量(global variable)。全局变量可以在声明的同时进行初始化。声明一个全局变量并进行初始化的语法格式如下所示。

var 变量名称 : 变量类型 = 常量表达式;

  其中的变量名称必须是一个有效的标识符。其中的常量表达式是一个任意的返回变量类型值的常量表达式。局部变量不能在声明的同时进行初始化。多个变量一同声明时(例如:“var X, Y, Z : Real;”)也不能在声明的同时进行初始化。变体类型和文件类型的变量都不能在声明的同时进行初始化。如果没有显式的初始化全局变量,那么编译器会将其初始化为零。对象实例数据(字段)也会被初始化为零。在Windows平台上,局部变量的初始值是不确定的,直到使用一个值来对它进行赋值。

  当声明一个变量时会自动为其分配内存,当一个变量不再使用时会自动释放其占用的内存。特别是局部变量,局部变量只存在于声明它的函数或过程的内部,当函数或过程退出时局部变量就会被释放。关于变量和内存管理的更多信息请参考第5.3节。

  可以声明一个新的变量,并让它位于另一个变量的相同地址中。要做到这一点,需要在新的变量的声明中添加指令absolute,并且在该指令之后紧跟一个现有变量(先前声明)的名称。声明一个使用绝对地址(absolute address)的变量的语法格式如下所示。

var 变量名称 : 变量类型 absolute 现有变量名称;

  其中的现有变量名称必须是一个先前声明的变量的名称。例如下面的这组声明。

var
    Str : String[32];
    StrLen : Byte absolute Str;

  指定变量StrLen起始于变量Str的相同地址。由于短字符串的第一个字节保存了字符串的长度,所以StrLen中的值是Str的长度。使用absolute指令或者是结合了absolute的任意其他指令声明的变量不能在声明的同时进行初始化。

  可以通过调用过程GetMem或New来创建动态变量(dynamic variable)。这样的变量被分配在堆(heap)上,并且不会进行自动管理。一旦你创建了一个这样的变量,那么你就有责任在不用时释放它所占用的内存。可以使用过程FreeMem销毁由过程GetMem创建的变量,可以使用过程Dispose销毁由过程New创建的变量。其他的对动态变量进行操作的标准例程包括ReallocMem、AllocMem、Initialize、Finalize、StrAlloc和StrDispose。关于这些标准例程的详细介绍请参考第16.7节。注意,长字符串、宽字符串、动态数组、变体和接口也是分配在堆上的动态变量,但是它们所使用的内存会自动管理。

  线程局部变量(thread-local variable)使用在多线程应用程序中。一个线程局部变量就像是一个全局变量,所不同的是每一个执行线程都拥有一份属于自己的该变量的私有拷贝,并且不能访问其他线程中的拷贝。声明线程局部变量需要使用保留字threadvar,而不是保留字var。声明一个线程局部变量的语法格式如下所示。

threadvar 变量名称列表: 变量类型;

  线程局部变量的声明不能放置在过程或函数中,不能在声明的同时进行初始化,不能在声明中使用absolute指令。

  平时由编译器管理的动态变量(长字符串、宽字符串、动态数组、变体和接口)可以声明成线程局部变量,但是编译器不会自动释放那些由每一个执行线程在堆上分配的内存。如果你在线程局部变量中使用了这些数据类型,那么你就有责任在线程终止前释放这些变量所占用的内存。可以使用空字符串进行赋值来释放字符串类型的变量,可以使用nil进行赋值来释放动态数组类型或接口类型的变量,可以使用Unassigned进行赋值来释放变体类型的变量。

5.2 常量

  有几种不同的语言结构可以被称为常量(constant)。其中有数字常量(也称数字字面量),例如:17;有字符串常量(也称字符串字面量),例如:'Hello world!';以及在每一个枚举类型声明中定义的枚举值,也被视为一个常量。还有一些预定义常量,例如:True、False和nil。最后,还有一些常量像变量一样需要通过声明来进行创建。通过声明创建的常量分为两种:真实常量(true constant)和类型常量(typed constant)。这两种常量在表面上非常相似,但是它们使用不同的规则来进行管理并且被用于不同的目的。

5.2.1 真实常量

  真实常量(true constant)所声明的标识符,其代表的值不能被改变。声明一个真实常量的语法格式如下所示。

const 常量名称 = 常量表达式;

  其中的常量名称必须是一个有效的标识符。其中的常量表达式是一个编译器在不执行程序的情况下就可以对其进行运算的表达式。如果是连续的声明多个常量,没必要重复使用保留字const,只需要保留第一个“const”即可。例如下面的这个声明。

const MaxValue = 237;

  声明了一个名为MaxValue的常量,其返回值为整数237。如果常量表达式返回的是一个序数值,那么可以使用值类型转换来指定其声明常量的类型。例如下面的这个声明。

const MyNumber = Int64(17);

  声明了一个名为MyNumber的Int64类型的常量,其返回值为整数17。如果没有指定声明常量的类型,那么常量的类型为常量表达式的类型。如果常量表达式是一个字符串,那么声明的常量将与任意的字符串类型相兼容。如果该字符串的长度为1,那么声明的常量将同时与任意的字符类型相兼容。如果常量表达式是一个实数,那么它是Extended类型的。如果常量表达式是一个整数,那么它的类型如下表所示。

整数常量的类型:
常量范围(十六进制) 常量范围(十进制) 类型 别名
$0至
$FF
0至
255
Byte UInt8
$0至
$FFFF
0至
65535
Word UInt16
$0至
$FFFFFFFF
0至
4294967295
Cardinal UInt32、LongWord
$0至
$FFFFFFFFFFFFFFFF
0至
18446744073709551615
UInt64
-$80至
$7F
-128至
127
ShortInt Int8
-$8000至
$7FFF
-32768至
32767
SmallInt Int16
-$80000000至
$7FFFFFFF
-2147483648至
2147483647
Integer Int32、LongInt
-$8000000000000000至
$7FFFFFFFFFFFFFFF
-9223372036854775808至
9223372036854775807
Int64
32位平台相关的整数类型:
常量范围(十六进制) 常量范围(十进制) 类型 等同类型
-$80000000至
$7FFFFFFF
-2147483648至
2147483647
NativeInt Integer
$0至
$FFFFFFFF
0至
4294967295
NativeUInt Cardinal
64位平台相关的整数类型:
常量范围(十六进制) 常量范围(十进制) 类型 等同类型
-$8000000000000000
$7FFFFFFFFFFFFFFF
-9223372036854775808
9223372036854775807
NativeInt Int64
$0
$FFFFFFFFFFFFFFFF
0
18446744073709551615
NativeUInt UInt64

表5.1 整数常量的类型

  一些进行常量声明的例子如下所示。

01 const
02     Min = 0;
03     Max = 100;
04     Center = (Max - Min) div 2;
05     Beta = Chr(225);
06     NumChars = Ord('Z') - Ord('A') + 1;
07     Message = 'Out of memory';
08     ErrStr = ' Error: ' + Message + '. ';
09     ErrPos = 80 - Length(ErrStr) div 2;
10     Ln10 = 2.302585092994045684;
11     Ln10R = 1 / Ln10;
12     Numeric = ['0'..'9'];
13     Alpha = ['A'..'Z', 'a'..'z'];
14     AlphaNum = Alpha + Numeric;

代码5.2 常量的声明

5.2.2 类型常量

  与真实常量不同,类型常量(typed constant)可以容纳数组类型、记录类型、程序类型和指针类型的值。类型常量不能出现在常量表达式中。声明一个类型常量的语法格式如下所示。

const 常量名称 : 常量类型 = 常量值;

  其中的常量名称必须是一个有效的标识符。其中的常量类型是除了文件类型和变体类型之外的任意类型,常量类型可以是一个有效的类型标识符或者是一个类型定义。其中的常量值是一个常量类型的表达式。例如下面的这个声明。

const Max : Integer = 100;

  在大多数的情况下,常量值必须是一个常量表达式。但是,如果常量类型是数组类型、记录类型、程序类型或指针类型,那么常量值将适用于特殊的规则。

  当声明一个数组常量时,常量值是一个使用逗号分隔的由数组元素值所构成的列表,并且需要使用一对圆括号“()”将该列表括起来。其中的数组元素值必须是一个常量表达式。例如下面的这个声明。

const Digits : array [0..9] of Char = 
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');

  声明了一个名为Digits的类型常量,用于保存一个字符数组。零基字符数组常常用来表示空终止字符串,基于这个原因,字符串常量可以被用来初始化字符数组。所以上面的这个声明可以更方便的表示为下面的这个声明。

const Digits : array [0..9] of Char = '0123456789';

  要声明一个多维数组常量,需要将每一维度的元素值列表单独放置在一对圆括号中,并且使用逗号将它们分隔开来。例如下面的这组声明。

type TCube = array [0..1, 0..1, 0..1] of Integer;
const Maze : TCube = (((0, 1), (2, 3)), ((4, 5), (6, 7)));

  创建了一个名为Maze的数组常量,其中数组的数据内容如下。

Maze[0, 0, 0] = 0
Maze[0, 0, 1] = 1
Maze[0, 1, 0] = 2
Maze[0, 1, 1] = 3
Maze[1, 0, 0] = 4
Maze[1, 0, 1] = 5
Maze[1, 1, 0] = 6
Maze[1, 1, 1] = 7

  注意一点,在数组常量的任意层次中都不能包含文件类型的值。

  当声明一个记录常量时,常量值是一个用分号分隔的形如“字段名称 : 字段值”的字段元素所构成的列表,并且需要使用一对圆括号“()”将该列表括起来。其中的字段值必须是一个常量表达式。字段元素在列表中的排列顺序必须与在记录类型定义时的顺序保持一致。如果有标记字段,那么可以为它指定一个值。如果有变体部分,那么只能为一个变体中的字段指定值。另外,可以将记录常量与数组常量结合起来构成记录数组常量。注意,在记录常量的任意层次中都不能包含文件类型的值。一些进行记录常量声明的例子如下所示。

01 type
02     TPoint = record
03         X, Y : Single;
04     end;
05
06     TVector = array [0..1] of TPoint;
07
08     TMonth = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
09
10     TDate = record
11         D : 1..31;
12         M : TMonth;
13         Y : 1900..1999;
14     end;
15
16 const
17     Origin : TPoint = (X : 0.0; Y : 0.0);
18     Line : TVector = ((X : -3.1; Y : 1.5), (X : 5.8; Y : 3.0));
19     SomeDay : TDate = (D : 2; M : Dec; Y : 1960);

代码5.3 记录常量的声明

  当声明一个程序常量时,常量值是一个与声明的常量类型相兼容的函数或过程的名称。可以直接通过程序常量来调用一个函数或过程。也可以将程序常量指定为nil。一个使用程序常量的例子如下所示。

01 function Calc(X, Y : Integer) : Integer;
02 begin
03     Result := X + Y;
04 end;
05
06 type TFunction = function(X, Y : Integer) : Integer;
07 const MyFunction : TFunction = Calc;
08
09 procedure MyProc;
10 var
11     i : Integer;
12 begin
13     i := MyFunction(5, 7);
14 end;

代码5.4 程序常量的使用

  当声明一个指针常量时,其初始化的常量值必须是一个在编译时就可以确定的相对地址。有三种方式可以做到这一点:使用运算符@、使用nil、使用字符串常量表达式。其中,当使用运算符@时,其操作数必须是一个全局变量、函数或过程。因为全局变量是代码段的一部分,所以它的相对地址在编译时就可以被确定。另外,指针常量可以像程序常量一样指向函数或过程。当使用字符串常量表达式时,常量的类型必须是PChar类型、PAnsiChar类型或PWideChar类型。可以直接使用字符串类型的常量表达式来初始化这些类型的指针常量。一些进行指针常量声明的例子如下所示。

01 function MyFunction(X, Y : Integer) : Integer;
02 begin
03     Result := X + Y;
04 end;
05
06 var
07     I : Integer;
08
09 const
10     PI : ^Integer = @I;
11     PF : Pointer = @MyFunction;
12     WarningStr : PChar = 'Warning!';

代码5.5 指针常量的声明

  当使用编译指令{$J+}或{$WRITEABLECONST ON}启用类型常量的可写状态后,可以对类型常量进行修改。在{$J+}状态下可以使用赋值语句来修改一个类型常量中的值,就好像它是一个经过初始化的变量一样。一个修改类型常量的例子如下所示。

01 {$J+}
02 const
03     foo : Integer = 12;
04 begin
05     foo := 14;
06 end;
07 {$J-}

代码5.6 修改类型常量

  可写的类型常量与可初始化的变量之间的差异是:可写类型常量即可以是全局常量,也可以是声明在过程、函数和方法中的局部常量。而可初始化变量只能是全局变量,对声明在过程、函数和方法中的局部变量进行初始化将会引发编译时错误。

5.2.3 资源字符串

  资源字符串(resource string)作为资源进行存储并且链接到可执行文件或动态链接库中,所以可以对它们进行修改而不需要对程序进行重新编译。资源字符串不能出现在常量表达式中。资源字符串的声明与真实常量的声明很象,只不过它使用的是保留字resourcestring,而不是保留字const。声明一个资源字符串的语法格式如下所示。

resourcestring 资源名称 = 常量表达式;

  其中的资源名称必须是一个有效的标识符。其中的常量表达式必须返回一个字符串值。一些进行资源字符串声明的例子如下所示。

01 resourcestring
02     CreateError = 'Cannot create file %s';
03     OpenError = 'Cannot open file %s';
04     LineTooLong = 'Line too long';
05     ProductName = 'Embarcadero Rocks';
06     SomeResourceString = SomeTrueConstant;

代码5.7 资源字符串的声明

5.2.4 常量表达式

  常量表达式(constant expression)就是一个编译器在不执行程序的情况下就可以对其进行运算的表达式。常量表达式中可以包含数字字面量、字符串字面量、真实常量、枚举值、特殊常量(True、False和nil)、以及由这些元素与运算符、类型转换和集合构造所构建起来的表达式。常量表达式中不能包含类型常量、资源字符串、变量、指针或函数调用。不过,下列预定义函数的调用除外:Abs、Chr、Hi、High、Length、Lo、Low、Odd、Ord、Pred、Round、SizeOf、Succ、Swap、Trunc。这些预定义函数可以被编译器识别,并在编译时完成运算。

  在Delphi语法规范的许多地方都需要使用到常量表达式的定义。在初始化全局变量、定义子界类型、指定枚举值的序数值、指定默认参数值、编写case语句、以及声明真实常量和类型常量的时候,都需要使用到常量表达式。一些常量表达式的例子如下所示。

01 100
02 'A'
03 256 - 1
04 (2.5 + 1) / (2.5 - 1)
05 'Embarcadero' + ' ' + 'Developer'
06 Chr(32)
07 Ord('Z') - Ord('A') + 1

代码5.8 常量表达式的例子

5.3 内存管理

  Delphi中的内存管理器(Memory Manager)负责管理一个应用程序中的所有动态内存的分配和释放。标准过程New、Dispose、GetMem、ReallocMem和FreeMem都使用了内存管理器。并且所有的对象和长字符串都是通过内存管理器来进行分配的。

  内存管理器对于分配大量的中小型内存块进行了优化,最为典型的就是面向对象的应用程序和处理字符串数据的应用程序。内存管理器对于在单线程和多线程应用程序中的高效运算(高速以及低内存开销)进行了优化。其他的内存管理器,例如,Windows用于支持私有堆实现的函数GlobalAlloc和LocalAlloc,通常在这种情况下表现不佳,如果直接使用它们将会降低应用程序的运行速度。为了保证最好的性能,内存管理器直接与Windows的虚拟内存API(函数VirtualAlloc和VirtualFree)进行对接。

  内存管理器支持的用户模式地址空间高达4GB。内存管理器分配的内存块总是对齐到4字节的整数倍大小,并且总是包含一个4字节的头部用来保存内存块的大小和其他状态位。为了提高寻址的性能,内存块的起始地址总是按照最小8字节的边界来进行对齐。不过,也可以选择16字节的对齐边界。

  内存管理器采用了一种算法可以对内存块的重新分配进行预测,可以减少对于这类操作的相关性能的影响。重新分配的算法也有助于减少地址空间碎片的产生。内存管理器提供了一种共享机制,从而不需要使用外部的DLL文件。内存管理器包括了报告功能,可以帮助应用程序监控自己的内存使用情况以及潜在的内存泄漏。内存管理器还提供了两个过程GetMemoryManagerState和GetMemoryMap,允许应用程序获取内存管理器的状态信息和内存使用情况的详细映射。

  全局变量被分配在应用程序的数据段中,并且在程序的持续时间中继续存在。在过程和函数中声明的局部变量驻留在应用程序的栈(stack)中。每当过程和函数被调用时,都会分配一组局部变量,当它们退出时,这些局部变量都会被消毁。编译器的优化可能会提前消毁变量。

  在Windows中,一个应用程序的栈由两个值来定义:最小栈大小和最大栈大小。可以通过编译指令{$MINSTACKSIZE}和{$MAXSTACKSIZE}来控制这两个值。在默认的情况下,这两个值分别为16384(16KB)和1048576(1MB)。一个应用程序保证有最小栈大小的栈可用,并且应用程序的栈的增长不能超过最大栈大小。如果没有足够的可用内存来满足一个应用程序的最小栈要求,那么在试图启动应用程序的时候Windows将会报告一个错误。如果一个应用程序需要一个比指定的最小栈大小更大的栈空间,那么追加的内存将会以4KB的增量进行自动分配。如果没有更多的内存可用或者是栈大小的总量将超过最大栈大小,从而导致追加的栈空间分配失败,那么将会引发EStackOverflow异常。栈溢出检查是完全自动的。编译指令{$S}原本被用于控制溢出检查,但是现在只是为了保持向后兼容性。

  使用过程GetMem或New创建的动态变量将会被分配在堆(heap)上,并且它们会一直存在直到使用过程FreeMem或Dispose来进行释放。长字符串、宽字符串、动态数组、变体和接口也是分配在堆上的动态变量,但是它们所使用的内存会自动管理。

  评论这张
 
阅读(287)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017