标题:[灌水]EssentialPascal(这个不错的,对于刚刚学习delphi的新手很有帮助)
取消只看楼主
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
 问题点数:0 回复次数:15 
[灌水]EssentialPascal(这个不错的,对于刚刚学习delphi的新手很有帮助)
PASCAL 精要 第一章 Pascal历史回顾

Delphi中使用的面向对象pascal编程语言并不是borland公司1995年发布可视化开发环境Delphi时才有的,它只是已有borland pascal产品的简单扩展。 Borland没有发明pascal,但它推广并扩展了pascal。

这一章对pascal语言的历史背景及其发展历程作一简短回顾。

沃斯的pascal

Pascal 语言最初由瑞士苏黎士理工学院的尼古拉斯-沃斯(Niklaus Wirth)教授在1971年设计, 作为Algol语言(1960年设计)简化本用于教学目的。

设计Pascal时,许多编程语言业已存在,但只有FORTRAN、C、Assembler、COBOL等少数语言在广泛应用。Pascal这种新语言的灵魂是其语言规则,Pascal语言规则的管理是通过强健的数据类型概念、强制性的数据类型声明与程序结构化控制来实现的,当时设计Pascal的初衷是想把这种语言用作程序设计课程的教学工具。

Turbo Pascal

1983年Borland公司推出了世界闻名的Pascal编译器 -- Turbo Pascal,实现了詹森和沃斯(Jensen & Wirth)在 “Pascal User Manual and Report” 中提出的思想 。由于既简洁功能又强,Turbo Pascal成为当时最畅销的编译器之一,而且在PC平台上非常流行。

Turbo Pascal中增添了集成开发环境(IDE),在这种开发环境中,你可在与WordStar 兼容的文字编辑器中编辑代码,可以运行编译器,查看编译错误并直接跳回到包含错误的行中。现在听起来上述功能似乎微不足道,但在Turbo Pascal之前你不得不退出代码编辑器返回到DOS,然后运行命令行编译器,记下错误行,再打开编辑器跳至错误行,非常烦琐。

此外,Borland公司的Turbo Pascal 售价只49美元 ,而Microsoft公司的 Pascal 编译器售价几百美元。 Turbo Pascal 取得多年的成功应归功于Microsoft最终放弃了Pascal 编译器产品。

Delphi中的Pascal

随着Turbo Pascal 编译器从第一版发布到第九版,Pascal语言得到了不断的发展,1995年Borland发布了Delphi ,使Pascal成为一种可视化编程语言。

Delphi 在很多方面扩展了Pascal语言,其中包括许多面向对象的扩展,这些扩展的风格与Object Pascal有所不同,同时Delphi 也提高了Borland Pascal with Objects 编译器的性能。

[此贴子已经被作者于2005-1-27 15:49:07编辑过]

搜索更多相关主题的帖子: delphi 灌水 学习 
2005-01-27 15:39
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 
PASCAL 精要 第二章 编写Pascal代码

进入正题前先谈一下Pascal代码编写风格的问题。“除了遵循语法规则外,你应该怎样来写代码呢?” 关于这个问题各人答案会有不同,因为各人喜欢的风格不同。总的来说,任何编码风格的目标都是使代码清楚、明晰,采用某种风格和格式只是一种简略方法,用于更清楚地表达你的代码要干什么。实现代码清楚明晰的基本原则是保持代码的一致性,也就是无论选用哪种风格,在整个工程中要始终保持同一风格。

注释

在Pascal中,注释括在大括号中或带星号的圆括号中。Delphi 也认可C++ 风格的注释,即把注释放在双斜线后。例如

{this is a comment}(* this is another comment *)// this is a comment up to the end of the line

第一种注释方式较简略,使用也较为普遍;第二种方式在欧洲使用较广 ,因为欧洲的键盘缺少大括号;第三种方式的注释是从C++借用来的,只在32位版本的Delphi中可用,它在给一行代码加短注释时非常有用。

在这本书中我用斜体表示注释,用粗体表示关键词,以此与默认的Delphi语法风格表示一致。

上述三种不同的注释方式有益于进行嵌套注释。例如你要注销一段代码,而代码行中又包含真正的注释行,这时采用同一种注释方式是不对的:

{  ... code{comment, creating problems}... code }

正确的方法是插入第二种注释方式:

{  ... code//this comment is OK... code }

注意:如果左大括号或圆括号-星号后面跟美元符号($),那么其中的内容就成了编译指令,如 {$X+}。

实际上,编译指令仍是注释。例如,{$X+ This is a comment} 是合法的。这既是有效的编译指令又是一条注释,尽管明智的程序员很可能会注意把编译指令和注释分开。

使用大写字母

Pascal 编译器(不象其他语言的编译器)不考虑字符的大小写,因此标识符Myname、 MyName、 myname、 myName、 和MYNAME是完全相同的。总体上来说,这是Pascal的一大优点,因为在大小写敏感的语言中,许多语法错误是由不正确的大写引起的。

注意:Pascal语言的大小写不敏感特性有一个例外:控件包中的Register 过程必须以大写字母R开始,因为需要与C++Builder 兼容。

然而大小写不敏感也有不便之处:第一,你必须注意大小写不一致的标识符实际上是相同的,以避免把他们当成不同的元素使用;第二,你必须尽量保持大写使用的一致性,以提高代码的可读性。

大写使用的一致性不是编译器强制要求的,但是保持大写使用的一致性是值得提倡的好习惯。一个常用的方法是将每个标识符的第一个字母大写,标识符若由几个词组合而成(中间不能插入空格),每个词的第一个字母应大写:

MyLongIdentifierMyVeryLongAndAlmostStupidIdentifier

此外,编译器不编译代码中的空格、空行和Tab键空格,这些元素通称为空白,它们只用来提高代码的可读性,不影响编译过程。

不同于BASIC, Pascal 语句允许分行书写,即将一条长指令分割成两个或更多的代码行。允许语句分行的缺点(至少对许多BASIC程序员)是:语句结束时不能忘了加分号,更确切地说,必须记着把语句和紧接它的语句分开。语句分行唯一的限制是字符串不能跨行。

关于空格和语句分行的使用没有既定的规则,以下是几点经验:

  • Delphi 代码编辑器中有一条竖线叫右边线(Right Margin),你可以把右边线设置在60或70个字符处。如果以这条线为基准,代码不超过这条界限,那么打印到纸上的代码看起来会很好看。否则,打印时长语句会被随意分行,甚至在一个词的中间断开。
  • 当一个函数或过程有多个参数,通常的做法是把各参数放在不同的行上。
  • 你可以在注释行前留一行空白,或把长的代码句分成较小的部分,这样能提高代码的可读性。
  • 用空格隔开函数调用的参数,表达式中的运算符也最好用空格隔开。一些程序员可能会对这些提议不以为然,但我坚持认为:空格是免费的,你不必为使用空格付费,何乐而不为呢?
优化版面

关于代码编写风格的最后一条建议是:尽量使用空白优化版面。这一条很容易做到,只需要在写复合句时,以上一句为参照,下一句向右缩进两个空格,复合句内嵌的复合句缩进四个空格,依此类推。例如:

if ... then  statement;if ... thenbegin  statement1;  statement2;end;if ... thenbegin  if ... then    statement1;  statement2;end;

相似的缩进格式常用于变量或数据类型声名区,也可用于语句的续行:

type  Letters = set of Char;var  Name: string;begin   { long comment and long statement, going on in the     following line and indented two spaces }   MessageDlg ('This is a message',     mtInformation, [mbOk], 0);

提出以上代码编写格式只是向你建个议而已,这样代码能更加易读,其实代码格式并不影响编译结果。在本书的例子和代码段中我始终坚持使用上述代码风格,Delphi 中的源代码、手册和帮助例子均采用了相似的格式化风格。

突出Pascal元素

为了使Pascal 代码更易读写,Delphi 编辑器中增加了Pascal 元素的色彩设置功能,也就是编辑器会用不同的颜色表示不同的Pascal 元素。缺省情况下,关键字以粗体表示,字符串和注释用蓝色表示(并且常常是斜体)。

用不同色彩显示不同的Pascal 元素对保留字、注释和字符串十分有利,因为着色后你一眼就可以看出拼错的关键字、没有正常结束的字符串及多行注释。

使用编辑器环境选项对话框中的色彩(Color)页,很容易就能定制各种Pascal 元素的色彩(见图2.1)。如果独自工作,那么你可随意选择喜欢的颜色。如果是与其他程序员合作,那么应该使用大家统一的标准颜色。我感觉在同一台计算机上使用我不习惯的色彩配置确实很难受。

图2.1 编辑环境设置对话框

注意:本书中我选用了一种色彩方案来显示源代码清单,希望能使代码更易读。

使用代码模板

Delphi 3 中增加了用于代码编辑的新功能“代码模板”。由于写Pascal 语句时,常常会重复键入相同的一组关键字,为此Borland 公司开发了名为“代码模板”的新功能,代码模板中存放了能与代码缩略形式对应的完整代码,你输入缩略代码,然后按Ctrl+J,完整的代码就出现了。例如,你输入arrayd,然后按Ctrl+J,Delphi 编辑器会把你的文本扩展为:

array [0..] of ;

由于同一种代码结构在预定义的代码模板中通常有多种样式,所以模板中的缩略形式一般加有一个后缀字母,以便你选用。此外,你也可以只输入缩略形式的头几个字母,如你输ar,然后按Ctrl+J,那么,编辑器中会弹出一个菜单,菜单中列出了代码缩略形式选项,见图2.2所示。

图2.2 代码模板选项

代码模板可以定制,就是你可以修改已有的模板也可以添加自己常用的代码段。用代码模板输入的代码文本中通常会出现‘|’字符,它表示输入模板代码后光标应跳到的位置,就是说你应该从这个光标位置开始输入,写完这句代码。

编程语句

标识符一经定义 ,你就可以在语句及组成语句的表达式中使用它们。Pascal 提供了许多语句和表达式,首先来看看关键字、表达式和运算符。

关键字

关键字是Object Pascal 的保留标识符,在语言中有着特殊含义。保留字不能用作标识符,指令字也同样不应该用作标识符,即使编译器允许也最好不用。在实际中你不应该把任何关键字用作标识符。

表2.1是面向对象 Pascal 语言(Delphi 4)中特殊标识符的完整列表,其中包括关键字及保留字。

表2.1:面向对象Pascal语言中的关键字及保留字

关键字 作用
absolute 指令 (变量)
abstract 指令 (方法)
and 运算符 (布尔)
array 类型
as 运算符 (RTTI)
asm 语句
assembler 向后兼容 (汇编)
at 语句 (异常处理)
automated 访问类别符 (类)
begin 块标记
case 语句
cdecl 函数调用协定
class 类型
const 声明或指令(参数)
constructor 特殊方法
contains 运算符 (集合)
default 指令 (属性)
destructor 特殊方法
dispid dispinterface 界面类别符
dispinterface类型
div 运算符
do 语句
downto 语句 (for)
dynamic 指令 (方法)
else 语句 (if 或 case)
end 块标记
except 语句 (异常处理)
export 向后兼容 (类)
exports 声明
external 指令 (函数)
far 向后兼容 (类)
file 类型
finalization单元结构
finally 语句 (异常处理)
for 语句
forward 函数指令
function 声明
goto 语句
if 语句
implementation单元结构
implements 指令 (属性)
in 运算符 (集合) - 工程结构
index 指令 (dipinterface界面)
inherited 语句
initialization单元结构
inline 向后兼容 (见 asm)
interface 类型
is 运算符 (RTTI)
label 声明
library 程序结构
message 指令 (方法)
mod 运算符 (数学)
name 指令 (函数)
near 向后兼容 (类)
nil 数值
nodefault 指令 (属性)
not 运算符 (布尔)
object 向后兼容 (类)
of 语句 (case)
on 语句 (异常处理)
or 运算符 (布尔)
out 指令 (参数)
overload 函数指令
override 函数指令
package 程序结构 (控件包)
packed 指令 (记录)
pascal 函数调用协定
private 访问类别符 (class)
procedure 声明
program 程序结构
property 声明
protected 访问类别符 (类)
public 访问类别符 (类)
published 访问类别符 (类)
raise 语句 (异常处理)
read 属性类别符
readonly dispatch 界面类别符
record 类型
register 函数调用协定
reintroduce 函数指令
repeat 语句
requires 程序结构 (控件包)
resident 指令 (函数)
resourcestring类型
safecall 函数调用协定
set 类型
shl 运算符 (数学)
shr 运算符 (数学)
stdcall 函数调用协定
stored 指令 (属性)
string 类型
then 语句 (if)
threadvar 声明
to 语句 (for)
try 语句 (异常处理)
type 声明
unit 单元结构
until 语句
uses 单元结构
var 声明
virtual 指令 (方法)
while 语句
with 语句
write 属性类别符
writeonly dispatch 界面类别符
xor 运算符 (布尔)
表达式和运算符

建立表达式没有通用的方法,因为要取决于所用的运算符,Pascal包括有逻辑运算符、算术运算符、布尔运算符、关系运算符和集合运算符等等。表达式可用于确定赋给一个变量的值、计算函数或过程的参数、或者判断一个条件,表达式也可以包含函数调用。表达式是对一个标识符的值而不是标识符本身进行运算。

所有编程语言中的表达式都是常量、变量、数值、运算符和函数值的合法组合。表达式可以传递给过程或函数的值参,但不能传递给过程或函数中的引用参数。

运算符及其优先级

如果你以前写过程序,那么你已经知道表达式是什么了。这里我专门讲一下Pascal 运算符的特殊部分:运算符的优先级。表2.2中按优先级分组列出了Pascal语言的运算符。

与大多数编程语言相反,Pascal语言中and和or运算符的优先级比关系运算符高。因此,如果你的代码为a < b and c < d,编译器首先会编译and运算符,由此导致编译出错。为此你应该把每个 < 表达式用小括号括起来: (a < b) and (c < d)。 

同一种运算符用于不同数据类型时它的作用不同。例如,运算符 + 可以计算两个数字的和、连接两个字符串、求两个集合的并集、甚至给PChar 指针加一个偏移量。然而,你不能象在C语言中那样将两个字符相加。

另一个特殊的运算符是 div。在Pascal 中,你能用 / 计算两个数字(实数或整数)的商,而且你总能得到一个实型结果。如果计算两个整数的商并想要一个整型结果,那么就需要用 div 运算符。

表 2.2: Pascal语言中的运算符及其优先级

单目运算符 (最高优先级)
@取变量或函数的地址(返回一个指针)
not逻辑取反或按位取反
乘除及按位运算符
*相乘或集合交集
/浮点相除
div整数相除
mod取模 (整数相除的余数)
as程序运行阶段类型转换 (RTTI运算符)
and逻辑或按位求和
shl按位左移
shr按位右移
加减运算符
+相加、集合并集、字符串连接或指针增加一个偏移量
-相减、集合差集或指针减少一个偏移量
or逻辑或按位或运算
xor逻辑或按位异或运算
关系及比较运算符(最低优先级)
=判断是否相等
<>判断是否不相等
<判断是否小于
>判断是否大于
<=判断是否小于或等于,或是否是一个集合的子集
>=判断是否大于或等于,或是否是一个集合的父集
in判断是否是集合成员
is判断对象是否类型兼容 (又一个RTTI运算符)
集合运算符

集合运算符包括并(+)、差(-)、交(*)、成员检测(in),及一些关系运算符。要把一个元素添加到集合中,你可以采用集合并运算。下面是一个选择字体的Delphi 例子:

Style := Style + [fsBold];Style := Style + [fsBold, fsItalic] - [fsUnderline];

另一种方法是利用标准过程Include 和Exclude,它们效率更高(但不能用于控件的集合类型属性,因为只能操纵一个元素):

Include (Style, fsBold);
结束语

从上面内容我们已经了解了Pascal 程序的基本布局,下面开始探究它的细节。先从预定义和自定义数据类型开始,然后是利用关键词组织编程语句。


一个爱学习的人!
2005-01-27 15:40
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 

PASCAL 精要 第三章 类型、变量及常量

最初的Pascal 语言是以一些简单的概念为基础建立起来的,这些概念现在普遍出现在编程语言中。最重要的概念当属数据类型,数据类型决定了变量可取的值,以及可在这些值上进行的操作。Pascal 数据类型的概念强于C语言及早期的BASIC语言,在C语言中算术数据类型是可以互换的,而早期的BASIC语言中根本没有与数据类型相似的概念。

变量

Pascal 变量在使用前必须声明,声明变量时必须指定一种数据类型。下面是变量声明的例子:

var  Value: Integer;  IsCorrect: Boolean;  A, B: Char;

关键字var可以在许多地方使用,例如放在函数或过程的开始部分,用来声明函数或过程的局部变量;也可以放在单元中,用于声明全程变量。var关键字之后是一组变量名列表,每个变量名后跟一个冒号和数据类型名,一行中可以声明多个变量,如上例中最后一句。

一旦变量的类型被指定,你只能对变量执行该变量类型支持的操作。例如,在判断操作中用布尔值,在数字表达式中用整型值,你不能将布尔值和整型值混用(在C语言中可以这样)。

使用简单的赋值语句,可写出下面的代码:

Value := 10;IsCorrect := True;

但下面的语句是不正确的,因为两个变量数据类型不同:

Value := IsCorrect; // error

在Delphi中编译这句代码,会出现错误信息:Incompatible types: 'Integer' and 'Boolean'.(类型不兼容:‘整型’和‘布尔型’)。象这样的错误通常是编程错误,因为把一个 TrueFalse 的值赋给一个整型变量没有什么意义。你不该责怪Delphi 提示这样的错误信息,代码中有不对的地方Delphi当然要提出警告。

把变量的值从一种类型转换到另一种类型往往不难做到,有些情况下类型转换会自动实现,不过一般情况下需要调用特殊的系统函数,通过改变数据内部表示来实现类型转换。

在Delphi 中,当你声明全程变量时,你可以赋给它一个初值。例如,你可以这样写:

var  Value: Integer = 10;  Correct: Boolean = True;

这种初始化方法只能用于全程变量,不能用于过程或方法的变量。

常量

对于在程序运行期间保持不变的值,Pascal 允许通过常量来声明。声明常量不必特定数据类型,但需要赋一个初值。编译器会根据所赋初值自动选用合适的数据类型。例如:

const  Thousand = 1000;  Pi = 3.14;  AuthorName = 'Marco Cantù';

Delphi 根据常量的值来决定它的数据类型。上例中的Thousand 变量,Delphi会选用SmallInt数据类型 (短整型--能容纳Thousand变量的最小整数类型)。如果你想告诉Delphi 采用特定的类型,你可在声明中加入类型名,方法如下:

const  Thousand: Integer = 1000;

对于声名的常量,编译器有两种编译选择:第一种为常量分配内存,并把常量的值放入内存;第二种在常量每次使用时复制常量值。第二种方法比较适合简单常量。

注意:16位的Delphi 允许你在程序运行期间改变已定义的常量值,就象一个变量一样。32位的Delphi为了向后兼容仍容许这种操作,只要你附加 $J 编译指令,或选择工程选项对话框中Compiler (编译器) 页的Assignable typed constants复选框就行。尽管如此,这里我还是要强烈建议万不得以不要使用上述操作,因为把新值赋给常量将使编译器不能对常量进行优化,与其如此不如直接声明一个变量。

资源串常量

当定义字符串常量时,你可这样写:

const  AuthorName = 'Marco Cantù';

从Delphi 3 开始,你可以用另一种方式写:

resourcestring  AuthorName = 'Marco Cantù';

上面两个语句都定义了一个常量,也就是定义了一个在程序运行期间保持不变的值,但两者的实现过程却不同,用resourcestring 指令定义的字符串变量将被保存到程序资源的字符串表中。从例子ResStr你可了解资源串的实际作用,例子中设置了一个按钮, 相应代码如下:

resourcestring  AuthorName = 'Marco Cantù';  BookName = 'Essential Pascal';procedure TForm1.Button1Click(Sender: TObject);begin  ShowMessage (BookName + #13 + AuthorName);end;

以上代码中的两个字符串将分两行输出显示,因为字符串被分行符 #13 隔开。

有趣的是,当你用资源编辑器打开执行文件时,你会在程序资源中看到你所定义的字符串。这意味着字符串并没有进入编译代码,而是保存在执行文件 (EXE文件) 的一个单独区域。

注意:简而言之,采用资源的好处一方面可让Windows 来完成有效的内存处理,另一方面不用更改源代码就可实现程序的本地化 (把字符串翻译成不同的语言)。

数据类型

Pascal 中有多种预定义的数据类型,它们可分为三大类:有序数据类型,实数类型和字符串类型。下面我们先讨论有序类型和实数类型,字符串类型放在以后讨论。同时这一节还将介绍几种Delphi 库中定义的类型 (不是编译器预定义的类型),这些类型也可看作是预定义的类型。

Delphi 还包括一种无类型的可变数据类型,称作variant,在本书的第十章将讨论这一类型。variant是一种无需类型检测的数据类型,它在Delphi 2 中引入,用于处理OLE Automation(OLE 自动化)。

有序类型

有序类型是建立在概念“顺序”或“序列”基础上的数据类型。你不仅可比较两个有序值的大小,而且可以求取给定有序值的前驱及后继,或者计算它们的最大或最小值。

三种最重要的预定义有序类型是整数类型、布尔类型和字符类型(Integer,Boolean,Char)。各种类型根据其内部表示和取值范围不同又可进一步细分。表3.1列出了表示数字的有序数据类型。

表 3.1: 表示数字的有序数据类型

大小有符号值域无符号值域
8 bitsShortInt -128 to 127Byte 0 to 255
16 bitsSmallInt -32768 to 32767Word 0 to 65,535
32 bitsLongInt -2,147,483,648 to 2,147,483,647LongWord (从 Delphi 4) 0 to 4,294,967,295
64 bitsInt64
16/32 bitsIntegerCardinal

从表中可看到,不同数据类型与不同的数据表示法相对应,这要取决于数据值的数位和符号位。有符号类型的数值可正可负,但取值范围较小,因为符号位占一个数位。下一节在例Range中说明了每种类型的实际取值范围。

表中最后一组类型标志着16/32,它表明其数值表示方法在16位和32位Delphi中不同,该组的Integer及Cardinal 类型比较常用,因为它们与CPU内部的数字表示法相对应。

Delphi 4中的整数类型

在 Delphi 3中,Cardinal类型所表示的32位无符号值实际占31位,取值最高为20亿。Delphi 4新增了一种无符号数字类型--LongWord,它是真正的32位值,取值最高达40亿。现在Cardinal 类型已成了LongWord类型的别名,只是LongWord能容纳大于20亿的无符号数,而且它的数值表示法与CPU内部数值表示法一致。

Delphi 4 中新增的另一个数据类型是Int64 类型,这一类型能表示长达18个数字的整数。系统中的有序类型例程(如High 和Low)、数字例程(如Inc 和 Dec)及字符串转换例程(如IntToStr)都支持这一新类型。反过来,有两个新增的专用函数StrToInt64StrToInt64Def支持从字符串向数字的转换。

布尔类型

布尔值不同于布尔类型,平时很少用到。ByteBool、 WordBool 和LongBool这三种布尔类型的布尔值比较特殊,只在Windows API 函数中才用到它们。

在Delphi 3 中,为了与Visual Basic 和 OLE Automation兼容,修改了ByteBoolWordBool LongBool的布尔值,将TRUE值设置为1,FALSE值仍为0;Boolean类型布尔值保持不变(TRUE为1,FALSE为0)。如果在Delphi 2代码中使用了布尔值显式类型转换 ,那么在以后的Delphi中可能会出错。

字符类型

字符有两种不同的表示法:: ANSICharWideChar。第一种类型代表 8 位的字符,与Windows一直沿用的ANSI(美国国家标准协会)字符集相应;第二种类型代表 16 位的字符,与Windows NT、Windows 95 和 98支持的双字节字符(Unicode)相应。在Delphi 3 中,Char 类型字符与ANSIChar一致。切记,不管在什么环境,前 256 个Unicode 字符与ANSI 字符是完全一致的。

常量字符可用代表它们的符号表示,如‘k’,也可用数字符号表示,如 #78。后者还可用Chr函数表示为 Chr(78),用Ord函数可作相反的转换Ord(k)

一般来说,对字母、数字或符号,用代表它们的符号来表示较好;而涉及到特殊字符时用数字符号较好。下面列出了常用的特殊字符:

  • #9 跳格 (Tab 键)
  • #10 换行
  • #13 回车 (Enter 键)

一个例子:Range

为使你对一些有序类型的不同取值范围有一个认识,我写了一个名为Range 的Delphi程序简例。结果见图3.1。

图3.1 简例Range显示有序数据类型信息(本例中采用整型)

Range 程序基于一个简单的窗体,上面有六个按扭 (按有序数据类型命名),还有几个标签(Label)用于显示信息,见图3.1。窗体最左边的一列标签显示的是静态文本,左边第二列标签在每次单击按扭时显示数据类型信息。

每当你按一下窗体右边的一个按钮,程序就会更新第二列标签的显示内容,显示的内容包括数据类型、字节数、该类型可存储的最大值和最小值。每个按钮都带有各自的OnClick 事件,因为各自的计算代码略有不同。例如,以下是Integer按钮(BtnInteger)OnClick 事件的源代码:

procedure TFormRange.BtnIntegerClick(Sender: TObject);begin  LabelType.Caption := 'Integer';  LabelSize.Caption := IntToStr (SizeOf (Integer));  LabelMax.Caption := IntToStr (High (Integer));  LabelMin.Caption := IntToStr (Low (Integer));end;

如果你有Delphi 编程经验,你可以看一下程序的源代码,弄明白程序到底是如何工作的。对于初学者,注意一下SizeOf、 High、 Low这三个函数的使用就可以了。High、 Low两个函数返回与参数相同的有序类型(这里是整型),SizeOf 函数返回整型数据。函数的返回值先用IntToStr 函数转成字符串,然后赋给三个标签的caption属性 。

其他按钮事件与上面相似,唯一的不同点在于传递给函数的参数类型是不同的。图3.2 显示了Windows 95 下的16位Delphi编译程序的执行结果。比较图3.1和图3.2,可以看出16位整型和32位整型之间的差异。

图3.2 :16位Delphi中Range程序运行结果显示的整型信息

整型类型的字节大小取决于你所使用的CPU和操作系统。在16位的Windows中,整型变量占两个字节,在32位的Windows中,整型变量占4个字节。因此,在两个环境中编译的Range程序会得到不同的结果。

如果你的程序对整数类型的字节大小没有特殊要求,Integer 类型在不同版本中的差异并不是个大问题。如果你在一个版本中保存了一个整数,那么在另一个版本中取出这个整数时可能会遇到一些问题,这种情况下,你应该采用平台无关的数据类型如 LongIntSmallInt。对于数学计算或非特殊的代码中,你最好的选择是坚持使用平台相应的标准整型,这就是说,使用CPU最喜欢的整型类型。当处理整数时,Integer 应是你的首选,不到迫不得已最好不要采用其他的整型类型。

有序类型系统例程

Pascal 语言和Delphi System 单元中定义了一系列有序类型操作例程,见表 3.2。C++ 程序员会注意到其中的Inc 例程,它可与 ++ 和 += 运算符对应(Dec 例程也同样)。

表 3.2: 有序类型系统例程

例程作用
Dec将例程中的参数值递减1或一个特定的值,其中特定值可在第二个可选参数中定义
Inc将例程中的参数值增加1或一个特定的值
Odd如果参数为奇数返回真
Pred根据参数在其数据类型定义中的序列,返回参数值的前驱值
Succ返回参数值的后继值
Ord返回参数值在其数据类型值集合中的序号
Low返回参数对应的有序数据类型的最小取值
High返回参数对应的有序数据类型的最大取值

注意,当有些例程用于常量时,编译器会自动用计算值替代例程。例如你调用High(X) ,设定X为一个整数,那么编译器会用整数类型中最大的可能值代替这个表达式。

实数类型

实数类型代表不同格式的浮点数。Single类型占的字节数最小,为4个字节;其次是Double 浮点类型,占8个字节;Extended 浮点类型,占10个字节。这些不同精度的浮点数据类型都与IEEE( 电气和电子工程师协会)标准的浮点数表示法一致,并且 CPU数字协处理器直接支持这些类型,处理速度也最快。

Real 类型在Delphi 2 和 Delphi 3 中的定义与 16 位版本一样,都占 6 个字节。不过Borland公司一直不提倡使用这种类型,而建议用Single、 Double、 Extended 类型代替。这是由于 Real 这种 6 字节的旧格式既不受 Intel CPU 的支持,又没有列在官方的IEEE 实型中。为了完全解决这一问题,Delphi 4 不得不修改 Real 类型的定义,将其改成标准的 8 字节浮点型, 由此引起了兼容性问题,不过如果有必要,你可以采用下面编译指令克服兼容性问题,恢复Delphi 2 和 Delphi 3 的Real 类型定义:

{$REALCOMPATIBILITY ON}

另外还有两种奇怪的数据类型:Comp 类型和Currency 类型,Comp 类型用 8 个字节描述非常大的整数(这种类型可支持带有 18 位小数的数字);Currency 类型 (16 位版的Delphi不支持该类型) 表示一个有四位小数位的值,它的小数位长度是固定的,同Comp 类型一样也占 8 个字节。正如名字所示,Currency 数据类型是为了操作很精确的四位小数货币数值才添加的。

对实型数据,我们没办法编一个类似Range的程序,因为High 、Low及 Ord函数不能用于实型值。理论上说实型类型代表一个无限的数字集合;有序类型代表一个有限的数字集合。

注意:让我进一步把上述问题解释一下。对于整数 23,你能确定23 后面的数是什么 ,因为整型数是有限的,它们有确定的值域范围及排列顺序。而浮点数即使在一个很小的值域范围内也无限、无序。 事实上,在 23 和 24 之间有多少值? 哪个值是 23.46 后面的值? 23.47 还是 23.461,或者 23.4601? 这是很难说清的。

因此,如问Char 类型字符 w 的顺序位置是有意义的, 但同样的问题对浮点类型数 7134.1562 就毫无意义。对于一个实型数,你能确切知道有没有比它大的实型数,但是,如想探究给定的实数前到底有多少个实型数(这是Ord 函数的作用),是得不到结果的。

实型类型在用户界面编程中用得不多,但是Delphi从各方面支持实型类型,包括在数据库方面的支持。由于支持IEEE浮点数运算标准,Object Pascal 语言完全适合于各类数值计算编程。如果对这部分感兴趣,你可以参考Delphi 在System单元中提供的算术函数(详细见Delphi 帮助)。

注意:Delphi 带有一个Math 单元,其中定义了一些高级数学例程,这些例程包括三角函数(如ArcCosh 函数)、金融函数(如InterestPayment 函数)和统计函数(如MeanAndStdDev 过程)。有些例程,它的名字听起来很怪,如MomentSkewKurtosis 例程,它是作什么用的呢? 还是留你自己查吧。


一个爱学习的人!
2005-01-27 15:41
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 

日期和时间

Delphi 也用实型数表示日期和时间数据。但为了更准确起见,Delphi 特别定义了TDateTime 数据类型,这是一个浮点类型,因为这个类型必须足够宽,使变量能容纳年、月、日、时、分和秒、甚至毫秒。日期值按天计数,从1899-12-30开始,放在TDateTime 类型的整数部分;时间值则位于十进制数的小数部分。

TDateTime 不是编译器可直接识别的预定义类型,它在System单元定义:

type  TDateTime = type Double;

使用TDateTime 类型很简单,因为Delphi 为该类型定义了一系列操作函数,表3.3列出了这些函数。

表3.3: TDateTime类型系统例程

例程作用
Now返回当前日期及时间
Date返回当前日期
Time返回当前时间
DateTimeToStr按缺省格式将日期和时间值转换为字符串;特定格式转换可用 FormatDateTime函数
DateTimeToString按缺省格式将日期和时间值拷贝到字符串缓冲区
DateToStr将TDateTime值的日期部分转为字符串
TimeToStr将TDateTime值的时间部分转为字符串
FormatDateTime按特定格式将日期和时间值转换为字符串
StrToDateTime将带有日期和时间信息的字符串转换为TdateTime类型值,如串有误将引发一个异常
StrToDate将带有日期信息的字符串转换为TDateTime类型格式
StrToTime将带有时间信息的字符串转换为TDateTime类型格式
DayOfWeek根据传递的日期参数计算该日期是一星期中的第几天
DecodeDate根据日期值返回年、月、日值
DecodeTime根据时间值返回时、分、秒、毫秒值
EncodeDate组合年、月、日值为TDateTime类型值
EncodeTime组合时、分、秒、毫秒值为TDateTime类型值

为了显示怎样使用日期时间类型及其相关例程,我建了一个简单的例子TimeNow。该例子在主窗体中设置了一个按钮和一个列表框(ListBox)。开始执行时,程序自动计算并显示当前的时间及日期,以后每次单击按钮 ,显示从程序开始至当前的时间。

下面列出了窗体的OnCreate 事件代码:

procedure TFormTimeNow.FormCreate(Sender: TObject);begin  StartTime := Now;  ListBox1.Items.Add (TimeToStr (StartTime));  ListBox1.Items.Add (DateToStr (StartTime));  ListBox1.Items.Add ('Press button for elapsed time');end;

第一句中调用了Now 函数,这个函数返回当前的日期和时间,它的值保存在StartTime 变量中,StartTime 变量是全程变量,其声明如下:

var  FormTimeNow: TFormTimeNow;  StartTime: TDateTime;

我只添加了第二个声明,第一个是由Delphi自动添加的。默认情况下的代码如下:

var  Form1: TForm1;

窗体名改变后,这个声明被自动更新。使用全程变量实际上不是最好的办法,更好的方法是使用窗体类的私有域,这涉及到面向对象的编程技术。

接下来的三个语句向位于窗体左面的列表框添加三个条目,结果见图3.3。列表框中的第一行显示了TDateTime 值的时间部分字符串、第二行显示的是同一值的日期部分,最后一行显示了一个简单的提示。

图 3.3:例TimeNow启动时的输出显示

当用户单击Elapsed 按钮时,上图第三行字符串被程序的计算结果代替:

procedure TFormTimeNow.ButtonElapsedClick(Sender: TObject);var  StopTime: TDateTime;begin  StopTime := Now;  ListBox1.Items [2] :=  FormatDateTime ('hh:nn:ss',    StopTime - StartTime);end;

这串代码再次计算当前的时间,并显示当前与程序开始之时的时间差,其中用到了其它事件中的计算值,为此不得不把该值存入全程变量。实际上,最好是采用基于类的变量。

注意:上面代码中所用ListBox的索引号为2,,而它代表的是第三行的显示输出,其原因是listbox的数据项是从零开始计数的:第一项计为0,第二项为1,第三项为2,依次类推,后面涉及数组时再详细讨论这方面内容。

除了调用TimeToStr和 DateToStr 外,以上例子中还用到了功能强大的FormatDateTime 函数(关于格式化参数详见Delphi 帮助文件)。需要注意的是:当时间和日期转换成字符串时,其转换格式取决于Windows 的系统设置。Delphi 从系统中读这些值,并把它们拷贝到SysUtils 单元中声明的几个全程常量中,例如:

DateSeparator: Char;ShortDateFormat: string;LongDateFormat: string;TimeSeparator: Char;TimeAMString: string;TimePMString: string;ShortTimeFormat: string;LongTimeFormat: string;ShortMonthNames: array [1..12] of string;LongMonthNames: array [1..12] of string;ShortDayNames: array [1..7] of string;LongDayNames: array [1..7] of string;

大部分全程常量与currency 和浮点数格式化有关,在 Delphi 帮助的 Currency and date/time formatting variables 主题下,你可找到完整的清单。

注意:Delphi 中有一个DateTimePicker 控件,它提供了选择日期的常用途径,即从一个日历中选择日期。

特定的Windows 类型

到目前为止,我们所看到的预定义数据类型都是Pascal 语言自身定义的类型。 Delphi 中还包含Windows系统定义的数据类型,这些数据类型不是Pascal语言的组成部分,而是Windows 库的一部分。Windows 类型包括新增的缺省类型(例如DWORD 或UINT)、各种记录(或结构)类型及指针类型等。

Windows 定义的数据类型中,最重要的类型是句柄(handle),第九章中将讨论这一类型。

类型映射及类型转换

正如所知,你不能把一个变量赋给另一个不同类型的变量,如果你需要这么做,有两种方法供选择。第一种方法是采用类型映射(Typecasting),它使用一个带有目标数据类型名的函数符号:

var  N: Integer;  C: Char;  B: Boolean;begin  N := Integer ('X');  C := Char (N);  B := Boolean (0);

你可以在字节长度相同的数据类型之间进行类型映射。在有序类型之间或实型数据之间进行类型映射通常是安全的,指针类型及对象之间也可以进行类型映射 ,只要你明白自己在做什么。

然而,一般来说类型映射是一种较危险的编程技术,因为它允许你访问一个似是而非的值,该值好象是其它值的替身。由于数据类型的内部表示法之间通常互相不匹配,所以当遇到错误时会难以追踪,为此你应尽量避免使用类型映射。

第二种方法是使用类型转换例程。表3.4中总结了各种类型转换例程。其中有些例程所涉及的数据类型将在下一节中讨论。 注意表中没有包括特殊类型(如TDateTime 和variant)的转换例程,也没包括用于格式化处理的特殊例程,如FormatFormatFloat 例程。

表3.4:类型转换系统例程

例程作用
Chr将一个有序数据转换为一个ANSI字符
Ord将一个有序类型值转换为它的序号
Round转换一个实型值为四舍五入后的整型值
Trunc转换一个实型值为小数截断后的整型值
Int返回浮点数的整数部分
IntToStr将数值转换为字符串
IntToHex将数值转换为十六进制数字符串
StrToInt将字符串转换为一个整型数,如字符串不是一个合法的整型将引发异常
StrToIntDef将字符串转换为一个整数,如字符串不合法返回一个缺省值
Val将字符串转换为一个数字(传统Turbo Pascal例程用于向后兼容)
Str将数字转换为格式化字符串(传统Turbo Pascal例程用于向后兼容)
StrPas将零终止字符串转换为Pascal类型字符串,在32位Delphi中这种类型转换是自动进行的
StrPCopy拷贝一个Pascal类型字符串到一个零终止字符串, 在32位Delphi中这种类型转换是自动进行的
StrPLCopy拷贝Pascal类型字符串的一部分到一个零终止字符串
FloatToDecimal将一个浮点数转换为包含指数、数字及符号的十进制浮点记录类型
FloatToStr将浮点值转换为缺省格式的字符串
FloatToStrF将浮点值转换为特定格式的字符串
FloatToText使用特定格式,将一个浮点值拷贝到一个字符串缓冲区
FloatToTextFmt同上面例程,使用特定格式,将一个浮点值拷贝到一个字符串缓冲区
StrToFloat将一个Pascal字符串转换为浮点数
TextToFloat将一个零终止字符串转换为浮点数

注意:在最近版本的Delphi Pascal 编译器中,Round 函数是以 CPU 的 FPU (浮点部件) 处理器为基础的。这种处理器采用了所谓的 "银行家舍入法",即对中间值 (如 5.5、6.5) 实施Round函数时,处理器根据小数点前数字的奇、偶性来确定舍入与否,如 5.5 Round 结果为 6,而 6.5 Round 结果也为6, 因为 6 是偶数。

结束语

本章讨论了Pascal的基本数据类型。Pascal语言还有一个非常重要的特征:它允许编程者自定义数据类型,称为“用户自定义数据类型”,这在下一章进行讨论。


一个爱学习的人!
2005-01-27 15:41
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 
PASCAL 精要 第四章 用户自定义数据类型

Pascal 语言的一个重要特征是它能自定义数据类型。通过各种类型构造器,你可以定义自己的数据类型,如子界类型、数组类型、记录类型、枚举类型、指针类型和集合类型。最重要的用户定义数据类型是类(class),类是Object Pascal的面向对象扩展部分,本书不讨论这部分。

你可能会认为其它编程语言也有诸如此类的类型构造器,确实如此,但是Pascal 是第一个完美实现这一理论的语言。至今仍然没有语言有能力定义那么多的数据类型。

命名及不命名的类型

为了后续使用或直接用于变量,需要给自定义类型命名。如果自定义一个命名的类型,你必须将代码放在特定的type区,如下所示:

type  // subrange definition  Uppercase = 'A'..'Z';  // array definition  Temperatures = array [1..24] of Integer;  // record definition  Date = record    Month: Byte;    Day: Byte;    Year: Integer;  end;  // enumerated type definition  Colors = (Red, Yellow, Green, Cyan, Blue, Violet);  // set definition  Letters = set of Char;

你也可使用类型定义构造器直接定义一个变量,此时无需显式命名,如下面的代码:

var  DecemberTemperature: array [1..31] of Byte;  ColorCode: array [Red..Violet] of Word;  Palette: set of Colors;

注意:一般来说,你应该避免使用上述不命名类型,因为你不能把它们作为参数传给例程,也不能用于声名同一类型的其他变量。实际上,Pascal的类型兼容规则是基于类型名的,而不是基于实际的类型定义。两个类型相同的变量仍有可能是不兼容的,除非他们的类型有完全相同的名字。对于不命名类型,需要编译器给它分配一个内部名字,因此对于数据结构复杂的变量,要习惯于定义命名数据类型,你一定不会为此白费工夫的。

但是上述自定义类型有什么意义呢?如果你不太熟悉Pascal类型构造器,通过下面内容你会了解它,此外下面还谈到了同类构造器在不同语言中的差异,因此如果你已熟知上面例举的类型定义,不妨往下读,你会对其中内容感兴趣的。最后,我将演示一些Delphi例子,并介绍一些能动态访问类型信息的工具。

子界类型

子界类型定义了某种类型的取值范围(因此定名subrange)。你可定义整数类型的子界类型,如取值从1到10或从100到1000,或者定义字符类型的子界类型,如下所示:

type  Ten = 1..10;  OverHundred = 100..1000;  Uppercase = 'A'..'Z';

定义子界类型时,你不需要指定基类的名字,而只需提供该类型的两个常数。所用基类必须是有序类型,定义结果将是另一种有序类型。

如定义一个子界变量,那么赋给该变量的值必须是子界定义范围内的值。下面代码是正确的:

var  UppLetter: UpperCase;begin  UppLetter := 'F';

以下代码则是不正确的:

var  UppLetter: UpperCase;begin  UppLetter := 'e'; // compile-time error

以上代码将导致一个编译错误:“Constant expression violates subrange bounds”。

如果代之以下面代码:

var  UppLetter: Uppercase;  Letter: Char;begin  Letter :='e';  UppLetter := Letter;

Delphi 编译会通过,但在运行时,如果你开启了范围检查编译选项(在工程选项对话框的编译器页设置),你将得到 Range check error (范围检测错误)信息。

注意:建议你在开发程序时开启上述编译选项,以使程序更健壮并易于调试。这样即使遇上错误,你也会得到一个明确的信息而不是难以琢磨的行为。最终完成程序时你可以去掉这个选项,使程序运行得快一些,不过影响很小。因此我建议你开启所有运行时的检测选项,如溢出检查和堆栈检查,甚至提交程序时仍然保留它们。

枚举类型

枚举类型又是一种自定义有序类型。在枚举类型中,你列出所有该类型可能取的值,而不是指定现有类型的范围。换句话说,枚举类型是个可取值的序列。见下例:

type  Colors = (Red, Yellow, Green, Cyan, Blue, Violet);  Suit = (Club, Diamond, Heart, Spade);

序列中每个值都对应一个序号,序号从0开始计数。使用Ord 函数,即可得到一个枚举类型值的序号。例如,Ord (Diamond) 返回值1。

注意:枚举类型有多种内部表示法。缺省时,Delphi 用8位表示法;如果有多于256个不同的值,则用16位表示法。还有一种32位表示法,需要与C、C++库兼容时会用到。使用$Z 编译指令可改变缺省设置,请求更多位的表示法。

Delphi VCL(可视控件库)在很多地方用了枚举类型。例如,窗体边框类型定义如下:

type  TFormBorderStyle = (bsNone, bsSingle, bsSizeable,    bsDialog, bsSizeToolWin, bsToolWindow);

当属性值是枚举类型时,你可以从Object Inspector显示的下拉列表框中选值,如图4.1所示。

图 4.1 Object Inspector 中的枚举类型属性

Delphi 帮助文件中列出了各种Delphi VCL枚举类型的可能值。你也可以通过OrdType程序(可从www.marcocantu.com下载)查看Delphi 枚举类型、集合类型、子界类型及任何其他有序类型的取值列表。图4.2为这个例子的输出结果。

图 4.2: 程序 OrdType 显示的枚举类型详细信息

集合类型

集合类型表示一组值,该组值由集合所依据的有序类型定义。定义集合的常用有序类型不多,一般为枚举类型或子界类型。如果子界类型取值为1..3,那么基于它的集合类型值可以是1、或2、或3、或1和2、或1和3、或2和3、或取所有3个数、或一个数也没有。

一个变量通常包含该类型对应的一个值,而集合类型可以不包含值、包含一个值、两个值、三个值,或更多,它甚至可以包含定义范围内所有的值。下面定义一个集合:

type  Letters = set of Uppercase;

现在我可以用上面类型来定义变量,并把原始类型的值赋给变量。为了在集合中表示一组值,需要用逗号将值隔开,最后用方括号结尾。下例显示了多值、单值和空值的变量赋值:

var  Letters1, Letters2, Letters3: Letters;begin  Letters1 := ['A', 'B', 'C'];  Letters2 := ['K'];  Letters3 := [];

在Delphi中,集合一般用于表示有多种选择的标记。例如下面两行代码(摘自Delphi库)声明了一个枚举类型,其中列出了窗口条上可选的图标,并声明了相应的集合类型:

type  TBorderIcon = (biSystemMenu, biMinimize, biMaximize, biHelp);  TBorderIcons = set of TBorderIcon;

实际上,给定的窗口中可以没有图标,也可以有一个或多个图标。用Object Inspector设置时(见图4.3),双击属性名,或单击属性左边的加号,自行选择,从而添加或删除集合中的值。

图 4.3: Object Inspector中的集合类型属性

另一个基于集合类型的属性是字体。字体类型值可以是粗体、斜体、带下画线、带删除线等,一种字型可以既是斜体又是粗体,也可以没有属性,或者带有全部的属性。因此用集合类型来表示它。你可以象下面代码那样,在程序中给集合赋值:

Font.Style := []; // no styleFont.Style := [fsBold]; // bold style onlyFont.Style := [fsBold, fsItalic]; // two styles

你也能对一个集合进行许多不同方式的操作,包括把两个相同类型的集合变量相加(或更准确地说,计算两个集合变量的并集):

Font.Style := OldStyle + [fsUnderline]; // two sets

此外,你可以通过OrdType 查阅Delphi 控件库中定义的集合类型取值列表。OrdType 放在本书源代码的TOOLS 目录中。

数组类型

数组类型定义了一组指定类型的元素序列,在方括号中填入下标值就可访问数组中的元素。定义数组时,方括号也用来指定可能的下标值。例如,下面的代码中定义了一个有24个整数的数组:

type  DayTemperatures = array [1..24] of Integer;

在数组定义时,你需要在方括号中填入一个子界类型的值,或者用两个有序类型的常量定义一个新的子界类型,子界类型指定了数组的有效索引。由于子界类型指定了数组下标值的上界和下界,那么下标就不必象C、C++、JAVA和其他语言那样必须从零开始。

由于数组下标基于子界类型,因此Delphi 能够对它们进行范围检查。不合法的常量子界类型将导致一个编译时间错误;如果选上编译器范围检查选项,那么超出范围的下标值将导致一个运行时间错误。

使用上述数组定义方法,定义一个DayTemperatures 类型的变量如下:

type  DayTemperatures = array [1..24] of Integer;var    DayTemp1: DayTemperatures;  procedure AssignTemp;  begin    DayTemp1 [1] := 54;  DayTemp1 [2] := 52;  ...  DayTemp1 [24] := 66;  DayTemp1 [25] := 67; // compile-time error

数组可以是多维的,如下例:

type  MonthTemps = array [1..24, 1..31] of Integer;  YearTemps = array [1..24, 1..31, Jan..Dec] of Integer;

这两个数组建立在相同的核心类型上,因此你可用前面定义的数据类型声明它们,如下面代码所示:

type  MonthTemps = array [1..31] of DayTemperatures;  YearTemps = array [Jan..Dec] of MonthTemps;

上例的声明把索引的次序前后调换了一下,但仍允许变量之间整块赋值。例如:把一月份的温度值赋给二月份:

var  ThisYear: YearTemps;begin  ...  ThisYear[Feb] := ThisYear[Jan];

你也能定义下标从零开始的数组,不过这似乎不太合逻辑,因为你需要用下标2来访问数组第三项。然而,Windows一直沿用了从零开始的数组(因为它是基于C语言的),并且Delphi 控件库也在往这方向靠拢。

使用数组时,你总要用标准函数LowHigh来检测它的边界,LowHigh返回下标的下界和上界。强烈建议使用LowHigh操作数组,特别是在循环中,因为这样能使代码与数组范围无关,如果你改变数组下标的范围声明,LowHigh代码不会受影响;否则,如果代码中有一个数组下标循环体,那么当数组大小改变时你就不得不更新循环体的代码。Low High将使你的代码更易于维护、更稳定。

注意:顺便提一下,使用LowHigh不会增加系统运行额外开销。因为在编译时,他们已被转换成常数表达式,而不是实际函数调用。其他简单的系统函数也是这样。

Delphi主要以数组属性的形式使用数组。我们已经在 TimeNow 例子中看到过数组属性,也就是ListBox控件的Items 属性。下一章讨论Delphi循环时,我将向你介绍更多有关数组属性的例子。

注意:Delphi 4 的Object Pascal中增加了动态数组,所谓动态数组是在运行时动态分配内存改变数组大小。使用动态数组很容易,不过我认为在这里讨论这类数组不合适。你将在第八章看到对Delphi 动态数组的描述。

记录类型

记录类型用于定义不同类型数据项的固定集合。记录中每个元素,或者说域,有它自己的类型。记录类型定义中列出了所有域,每个域对应一个域名,通过域名可以访问它。

下面简单列举了记录类型的定义、类型变量的声明以及这类变量的使用:

type  Date = record    Year: Integer;    Month: Byte;    Day: Byte;  end;  var  BirthDay: Date;  begin  BirthDay.Year := 1997;  BirthDay.Month := 2;  BirthDay.Day := 14;

类和对象可以看作是记录类型的扩展。Delphi 库趋向于用类替代记录类型,不过Windows API中定义了许多记录类型。

记录类型中允许包含variant 域,它表示多个域能公用同一内存区,而且域可以是不同类型(这相应于C语言中的联合union)。换句话说,你可以通过variant 域或说是一组域访问记录中同一个内存位置,但是各个值仍需区别对待。variant类型主要用来存贮相似但又不同的数据,进行与类型映射(typecasting)相似的类型转换(自从typecasting 引入Pascal,已很少用到这种方法了)。虽然Delphi在一些特殊情况下还在用variant 记录类型,但是现在已经被面向对象技术或其他现代技术代替了。

variant 记录类型的应用不符合类型安全原则,因此不提倡在编程中使用,初学者更是如此。实际上,专家级的编程人员确实需要用到variant 记录类型,Delphi 库的核心部分就用到了这一类型。不管怎样,除非你是个Delphi 专家,否则你应避免使用variant记录类型。

指针

指针是存放指定类型(或未定义类型)变量内存地址的变量,因此指针间接引用一个值。定义指针不需用特定的关键字,而用一个特殊字符,这个特殊字符是脱字符号(^),见下例:

type  PointerToInt = ^Integer;

一旦你定义了指针变量,你就可以用@ 符号把另一个相同类型变量的地址赋给它。见下例:

var  P: ^Integer;  X: Integer;begin  P := @X;  // change the value in two different ways  X := 10;  P^ := 20;  

如果定义了一个指针P,那么P表示指针所指向的内存地址,而P^表示内存所存储的实际内容。因此,在上面的代码中, P^ 与X相等。

除了表示已分配内存的地址外,指针还能通过New 例程在堆中动态分配内存,不过当你不需要这个指针时,你也必须调用Dispose 例程释放你动态分配的内存。

var  P: ^Integer;begin  // initialization  New (P);  // operations  P^ := 20;  ShowMessage (IntToStr (P^));  // termination  Dispose (P);end;

如果指针没有值,你可以把nil 赋给它。这样,你可以通过检查指针是否为nil 判断指针当前是否引用一个值。这经常会用到,因为访问一个空指针的值会引起一个访问冲突错误,也就是大家知道的“一般保护错”(GPF)。见下例:

procedure TFormGPF.BtnGpfClick(Sender: TObject);var  P: ^Integer;begin  P := nil;  ShowMessage (IntToStr (P^));end;

通过运行例GPF,或者看图4.4,你可以看到上述这种结果。

图 4.4: 访问nil指针引起的系统错误

将上面程序加以修改,访问数据就安全了。现在将一个已存在的局部变量赋给指针,指针使用就安全了,虽然如此,我还是加上了一个安全检查语句:

procedure TFormGPF.BtnSafeClick(Sender: TObject);var  P: ^Integer;  X: Integer;begin  P := @X;  X := 100;  if P <> nil then    ShowMessage (IntToStr (P^));end;

Delphi 还定义了一个Pointer 数据类型,它表示无类型的指针(就象C语言中的void* )。如果你使用无类型指针,你应该用GetMem 例程,而不是New例程,因为GetMem 例程能用于内存分配大小不确定的情况。

实际上,Delphi 中必须使用指针的情况很少,这是Delphi开发环境一个诱人的优点。虽然如此,若要进行高级编程和完全理解Delphi 对象模型,理解指针是很重要的,因为Delphi 对象模型在幕后使用了指针。

注意:虽然在Delphi中不常使用指针,但是你经常会用一个极为相似的结构--引用(references)。每个对象实例实际上是一个隐含的指针,或说是对其实际数据的引用,利用引用,你能象用其他数据类型一样使用对象变量。

文件类型

另一个Pascal特定的类型构造器是文件类型(file)。文件类型代表物理磁盘文件,无疑是Pascal语言的一个特殊类型。按下面的方式,你可以定义一个新的数据类型:

type  IntFile = file of Integer;

然后,你就能打开一个与这个结构相应的物理文件、向文件中写入整数、或者从文件中读取当前的值。

Pascal 文件类型的使用很直观,而且Delphi 中也定义了一些控件用于文件保存和装载,以及对数据流和数据库的支持。

结束语

这一章讨论了自定义数据类型,完成了对Pascal 数据类型体系的介绍,为下一章“语句”作好了准备。语句用于操作我们所定义的变量。


一个爱学习的人!
2005-01-27 15:42
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 
PASCAL 精要 第五章 语句

如果说数据类型是Pascal 编程的一个基础,那么另一个则是语句。编程语言的语句主要由关键字和操作指令组成。语句常放在过程或函数中,就象我们将在下一章中看到的那样。现在,我们集中讲解最基本的编程语句。

简单语句和复合语句

Pascal 简单语句中不包含任何别的语句,赋值语句和过程调用即是简单语句的例子。简单语句用分号隔开,如下所示:

X := Y + Z;  // assignmentRandomize;   // procedure call

用begin 和end 将简单语句括起来即组成复合语句,复合语句用法与普通的Pascal 语句相同,见下例:

begin  A := B;  C := A * 2;end;

end之前的最后一条语句末尾分号不是必需的,你可以写成:

begin  A := B;  C := A * 2end;

这两种写法都是正确的。第一种多了一个无用(但也无害)的分号。分号实际上是一个空语句,也就是说,是一个没有代码的语句。有时,空语句可用在循环体或其他特殊情况中。

注意:虽然最后一条语句末尾的分号没有用,我却总是加上它,并且建议你也这样做。因为有时你可能需要在末尾添加语句,如果最后没有加分号,你就必须记着加上它,与其如此不如一开始就加上它。

赋值语句

在Pascal 语言中赋值语句用冒号-等号操作符“:=”,对使用其他语言的编程人员来说这是一个奇怪的符号。在其他语言中用作赋值符号的“=”在Pascal 中用作关系运算符,用于判断是否相等。

注意:赋值和相等判断使用不同的符号,使Pascal 编译器(象C编译器一样)能更快解译源代码,因为这样就不需要通过检查上下文来判断符号的意义,此外使用不同操作符也使代码更易读。

条件语句

条件语句通过条件检测,判断是否执行该条件语句中包含的语句。条件语句可有两种基本形式:if语句和case语句。

If语句

对if-then型语句,仅当条件满足时,语句才执行;对if-then-else型,if语句在两条语句中选择一条执行。条件用布尔表达式建立,这里通过一个简单的Delphi 例子来示范如何写条件语句。首先,创建一个应用程序,在form上面放两个复选框(check box)和四个按钮(button),不要改变复选框和按钮的名字,双击按钮为其OnClick 事件添加响应程序。下面是第一个按钮事件代码中一条简单的if语句:

procedure TForm1.Button1Click(Sender: TObject);begin  // simple if statement  if CheckBox1.Checked then    ShowMessage ('CheckBox1 is checked')end;

当点击button1,如果第一个复选框中有复选标记,那么这个程序将显示一条消息(见图5.1)。我用了ShowMessage 函数,因为它是Delphi中最简单的短信息显示函数。

图 5.1: 例IfTest显示的信息

如果点击按钮后没有反应,表明复选框未被选中。对于这种情况,最好能交代得更清楚些,为此在第二个按钮的代码中,我用了if-then-else 语句:

procedure TForm1.Button2Click(Sender: TObject);begin  // if-then-else statement  if CheckBox2.Checked then    ShowMessage ('CheckBox2 is checked')  else    ShowMessage ('CheckBox2 is NOT checked');end;

要注意的是,不能在第一句之后、else 关键词之前加分号,否则编译器将告知语法错误。实际上,if-then-else 语句是单纯的一条语句,因此不能在语句中间加分号。

if 语句可以很复杂,句子中的条件部分可以是一系列条件(用and、 or 、 not等布尔操作符联接起来),if语句又可以嵌套另一个if语句,见例IfTest中其它两个按钮的示范代码:

procedure TForm1.Button3Click(Sender: TObject);begin  // statement with a double condition  if CheckBox1.Checked and CheckBox2.Checked then    ShowMessage ('Both check boxes are checked')end;procedure TForm1.Button4Click(Sender: TObject);begin  // compound if statement  if CheckBox1.Checked then    if CheckBox2.Checked then      ShowMessage ('CheckBox1 and 2 are checked')    else      ShowMessage ('Only CheckBox1 is checked')  else    ShowMessage (      'Checkbox1 is not checked, who cares for Checkbox2?')end;

仔细阅读代码并执行程序,看看你能不能理解整个程序。当你搞不清某种编程结构时,可以先写一个简单程序,这样可以帮你学习许多东西。你可以再加几个复选框,增加这个简例的复杂程度,并进行各种测试。

Case语句

如果你的if语句变得非常复杂,有时可以用case语句代替它。case语句包括用来选值的表达式、可能值序列或一个取值范围。这些值应该是常量,并且它们必须唯一,而且应属于有序类型。Case语句最后可以带一个else 语句,当没有一个标签与选择器的值一致时,执行else语句。下面是两个简单的例子:

case Number of  1: Text := 'One';  2: Text := 'Two';  3: Text := 'Three';end;case MyChar of  '+' : Text := 'Plus sign';  '-' : Text := 'Minus sign';  '*', '/': Text := 'Multiplication or division';  '0'..'9': Text := 'Number';  'a'..'z': Text := 'Lowercase character';  'A'..'Z': Text := 'Uppercase character';else  Text := 'Unknown character';end;
Pascal语言中的循环

其它编程语言中使用的循环语句,Pascal语言中都有,它们包括 forwhilerepeat 语句。如果你用过其他编程语言,你会发现Pascal中的循环语句没什么特别的,因此这里我只作简要的说明。

For循环

Pascal 中的for循环严格地建立在计数器基础上,循环每执行一次,计数器不是增加一个值就是减小一个值。下面是一个for语句的简例,用来将前十个数加起来:

var  K, I: Integer;begin  K := 0;  for I := 1 to 10 do    K := K + I;

同样的for语句可以用正好相反的计数器来写:

var  K, I: Integer;begin  K := 0;  for I := 10 downto 1 do    K := K + I;

Pascal 中的for循环语句其灵活性比其他语言小(它不能指定1之外的步长),不过简单也容易理解。如果需判断的条件比较复杂,或想自定义计数器,你可以用while语句或 repeat 语句,而不是for循环语句。

注意:for循环计数器不必非是数字,它可以是任何有序类型的值,例如一个字符或一个枚举类型值。

while语句和repeat语句

while-do 循环语句和 repeat-until 语句的不同点在于repeat 循环语句的代码至少要执行一次。从下面的简例很容易理解这一点:

while (I <= 100) and (J <= 100) dobegin  // use I and J to compute something...  I := I + 1;  J := J + 1;end;repeat  // use I and J to compute something...  I := I + 1;  J := J + 1;until (I > 100) or (J > 100);

从上可见即使 I J 的初始值大于100,repeat-until循环中的代码也仍会执行一次。

注意:两种循环另一个关键的不同点是,repeat-until 循环的条件是反向的条件,只要不满足这个条件,循环就执行;当条件满足时,循环终止。这正好与while-do 循环相反,while-do 循环当条件是真值时才执行。为此,我不得不在上面代码中用反向条件来获得相同的结果。

一个循环语句例子

为了探究循环的细节,让我们看一个Delphi 简例,这个循环例子表现了固定计数器循环和随机计数器循环之间的差别。建一个新的工程,在主窗体上放一个listbox和两个button,通过设置Object Inspector中的name属性分别命名button为BtnFor 和BtnWhile。你还可以把Caption 属性中的Btn 去掉,或甚至加上 & ,让跟在 & 后面的字母成为快捷键。下面是该窗体文本描述:

object Form1: TForm1  Caption = 'Loops'  object ListBox1: TListBox ...  object BtnFor: TButton    Caption = '&For'    OnClick = BtnForClick  end  object BtnWhile: TButton    Caption = '&While'    OnClick = BtnWhileClick  endend

图 5.2: 单击For按钮后显示的结果

现在,我们分别给两个button 添加OnClick 事件代码。第一个button用一个简单的for循环来显示一列数字,结果如图5.2。这个循环向listbox中的Items 属性添加一系列字符串。在执行循环之前,需要清除listbox 中的内容。程序如下:

procedure TForm1.BtnForClick(Sender: TObject);var  I: Integer;begin  ListBox1.Items.Clear;  for I := 1 to 20 do    Listbox1.Items.Add ('String ' + IntToStr (I));end;

第二个button的事件代码稍微复杂点。本例中让while 循环基于一个随机增长的计数器。为实现它,我调用了Randomize 过程, 用它来重置随机数发生器,还调用了Random 函数, 其取值范围为100, 即函数返回0至99之间的随机数,随机数序列控制while 循环的执行次数。

procedure TForm1.BtnWhileClick(Sender: TObject);var  I: Integer;begin  ListBox1.Items.Clear;  Randomize;  I := 0;  while I < 1000 do  begin    I := I + Random (100);    Listbox1.Items.Add ('Random Number: ' + IntToStr (I));  end;end;

每次点击While按钮,出现的数字都不同,因为这些数字取决于随机数发生器。图5.3显示了两次点击的结果,可看到不仅每次产生的数字不同,而且数据项数也不同。也就是说,这个while循环执行的次数是随机的。

图 5.3: 按While按钮后显示的结果

注意:用 BreakContinue 系统过程可以改变循环执行的标准流程。Break 中断循环;Continue直接跳至循环测试句,或使计数器增加一个步长,然后继续循环(除非条件为空或计数器达到最大值)。还有两个系统过程 ExitHalt,让你立即从函数或过程中返回,或者终止程序。

With语句

我要讲的最后一种Pascal 语句是With语句,With语句是Pascal编程语言独有的语句,不过最近JavaScript 和Visual Basic也添加了这种语句,它在Delphi程序设计中很有用。

With语句是一种用于简化代码的语句。如你要访问一个记录类型变量(或一个对象),用With语句就不必每次重复变量的名字。例如对于以下的记录类型代码:

type  Date = record    Year: Integer;    Month: Byte;    Day: Byte;  end;var  BirthDay: Date;begin  BirthDay.Year := 1997;  BirthDay.Month := 2;  BirthDay.Day := 14;

可以用with语句改进后半部分代码,如下:

begin  with BirthDay do  begin    Year := 1995;    Month := 2;    Day := 14;  end;

在Delphi程序中,这种方法能用于访问控件和类变量。现在通过with语句访问列表框的条目,我们重写上面循环例子的最后部分:

procedure TForm1.WhileButtonClick(Sender: TObject);var  I: Integer;begin  with ListBox1.Items do  begin    Clear; // shortcut    Randomize;    I := 0;    while I < 1000 do    begin      I := I + Random (100);      // shortcut:      Add ('Random Number: ' + IntToStr (I));    end;  end;end;

当你使用控件或类时,with语句通常能简化你的代码,尤其对嵌套域。例如,你要改变窗体画笔的宽度和颜色,你可以写代码如下:

Form1.Canvas.Pen.Width := 2;Form1.Canvas.Pen.Color := clRed;

但如果用With语句代码会更简单:

with Form1.Canvas.Pen dobegin  Width := 2;  Color := clRed;end;

当编写的代码很复杂时,with语句会很有用,也可省去一些临时变量。但是这样做也有缺点,因为这样将使代码的可读性变差,特别对有相似或相同属性的对象。

更严重的是,使用with语句可能会在代码中融入微妙的逻辑错误,甚至连编译器都难以发现。例如:

with Button1 dobegin  Width := 200;  Caption := 'New Caption';  Color := clRed;end;

这段代码改变了按钮的Caption 和 Width属性,但也改变了窗体的Color属性,而不是按钮的颜色!其原因是 TButton 控件没有Color属性, 又由于执行的代码是针对窗体对象的(我们正在写窗体的方法),所以窗体对象即成为默认的访问对象。如果这样写:

Button1.Width := 200;Button1.Caption := 'New Caption';Button1.Color := clRed; // error!

编译器会给出一个错误。通常,由于with语句在当前的块中定义了新的标识符,省略了原有的标识符,可能引起在同一块内错误地访问另一个标识符(就象上面的这段代码)。即使存在种种缺陷,我还是建议你习惯于使用with语句,因为with语句确实是非常便利,并且有时也会使代码更容易读懂。

然而,你应该避免使用多个with语句,如:

with ListBox1, Button1 do...

这样会使后面的代码非常难读,因为,对该块中定义的每个属性,你都要根据相应的属性以及控件的次序,才能推出所访问的控件。

注意:说到可读性,要知道Pascal 没有endif 或endcase 语句。如果if语句有一个begin-end 块,那么end标志语句结束;另外,case语句也总是以一个end结束。所有这些end语句,常常是一个接一个,使代码难以理解,只有通过缩排跟踪,才能追出一个end所对应的语句。解决这个问题的一个通用办法, 也是使代码更可读的办法,是在end后面加注释,如下例:

if ... then ...end; // if
结束语

本章描述了怎样编写条件语句和循环语句的代码。程序通常被分成例程、过程或函数,而不是把所有语句列成长长的列表。这是下一章的主题,下一章也将介绍一些Pascal的高级内容。


一个爱学习的人!
2005-01-27 15:42
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 
PASCAL 精要 第六章 过程与函数

例程(routine)是Pascal 的一个重要概念,例程由一系列语句组成,例程名是唯一的,通过例程名你可以多次调用它,这样程序中只需要一个例程就够了,由此避免了代码多次重复,而且代码也容易修改维护。从这个角度看,你可以认为例程是一种基本的代码封装机制。介绍完Pascal 例程的语法后,我会回过头来举例说明这个问题。

Pascal 过程与函数

Pascal中的例程有两种形式:过程和函数。理论上说,过程是你要求计算机执行的操作,函数是能返回值的计算。两者突出的不同点在于:函数能返回计算结果,即有一个返回值,而过程没有。两种类型的例程都可以带多个给定类型的参数。

不过实际上函数和过程差别不大,因为你可以调用函数完成一系列操作,跳过其返回值(用可选的出错代码或类似的东西代替返回值);也可以通过过程的参数传递计算结果(这种参数称为引用,下一部分会讲到)。

下例定义了一个过程、两个函数,两个函数的语法略有不同,结果是完全相同的。

procedure Hello;begin  ShowMessage ('Hello world!');end;function Double (Value: Integer) : Integer;begin  Double := Value * 2;end;// or, as an alternativefunction Double2 (Value: Integer) : Integer;begin  Result := Value * 2;end;

流行的做法是用Result 给函数赋返回值,而不是用函数名,我认为这样的代码更易读。

一旦定义了这些例程,你就可以多次调用,其中调用过程可执行操作;调用函数能计算返回值。如下:

procedure TForm1.Button1Click (Sender: TObject);begin  Hello;end; procedure TForm1.Button2Click (Sender: TObject);var  X, Y: Integer;begin  X := Double (StrToInt (Edit1.Text));  Y := Double (X);  ShowMessage (IntToStr (Y));end;

注意:现在不必考虑上面两个过程的语法,实际上它们是方法。只要把两个按钮(button)放到一个Delphi 窗体上,在设计阶段单击它们,Delphi IDE将产生合适的支持代码,你只需要填上begin 和end 之间的那几行代码就行。编译上面的代码,需要你在窗体中加一个Edit控件。

现在回到我前面提到过的代码封装概念。当你调用Double 函数时,你不需要知道该函数的具体实现方法。如果以后发现了更好的双倍数计算方法,你只需要改变函数的代码,而调用函数的代码不必改变(尽管代码执行速度可能会加快!)。Hello 过程也一样,你可以通过改变这个过程的代码,修改程序的输出,Button2Click 方法会自动改变显示结果。下面是改变后的代码:

procedure Hello;begin  MessageDlg ('Hello world!', mtInformation, [mbOK]);end;

提示:当调用一个现有的Delphi 函数、过程或任何VCL方法时,你应该记住参数的个数及其数据类型。不过,只要键入函数或过程名及左括号,Delphi 编辑器中会出现即时提示条,列出函数或过程的参数表供参考。这一特性被称为代码参数(Code Parameters) ,是代码识别技术的一部分。

引用参数

Pascal 例程的传递参数可以是值参也可以是引用参数。值参传递是缺省的参数传递方式:即将值参的拷贝压入栈中,例程使用、操纵的是栈中的拷贝值,不是原始值。

当通过引用传递参数时,没有按正常方式把参数值的拷贝压栈(避免拷贝值压栈一般能加快程序执行速度),而是直接引用参数原始值,例程中的代码也同样访问原始值,这样就能在过程或函数中改变参数的值。引用参数用关键字var 标示。

参数引用技术在大多数编程语言中都有,C语言中虽没有,但C++中引入了该技术。在C++中,用符号 &表示引用;在VB中,没有ByVal 标示的参数都为引用。

下面是利用引用传递参数的例子,引用参数用var关键字标示:

procedure DoubleTheValue (var Value: Integer);begin  Value := Value * 2;end;

在这种情况下,参数既把一个值传递给过程,又把新值返回给调用过程的代码。当你执行完以下代码时:

var  X: Integer;begin  X := 10;  DoubleTheValue (X);

x变量的值变成了20,因为过程通过引用访问了X的原始存储单元,由此改变了X的初始值。

通过引用传递参数对有序类型、传统字符串类型及大型记录类型才有意义。实际上Delphi总是通过值来传递对象,因为Delphi对象本身就是引用。因此通过引用传递对象就没什么意义(除了极特殊的情况),因为这样相当于传递一个引用到另一个引用。

Delphi 长字符串的情况略有不同,长字符串看起来象引用,但是如果你改变了该字符串的串变量,那么这个串在更新前将被拷贝下来。作为值参被传递的长字符串只在内存使用和操作速度方面才象引用,但是如果你改变了字符串的值,初始值将不受影响。相反,如果通过引用传递长字符串,那么串的初始值就可以改变。

Delphi 3增加了一种新的参数:out。out参数没有初始值,只是用来返回一个值。out参数应只用于COM过程和函数,一般情况下最好使用更有效的var参数。除了没有初始值这一点之外,out参数与var参数相同。

常量参数

除了引用参数外,还有一种参数叫常量参数。由于不允许在例程中给常量参数赋新值,因此编译器能优化常参的传递过程。编译器会选用一种与引用参数相似的方法编译常参(C++术语中的常量引用),但是从表面上看常参又与值参相似,因为常参初始值不受例程的影响。

事实上,如果编译下面有点可笑的代码,Delphi将出现错误:

function DoubleTheValue (const Value: Integer): Integer;begin  Value := Value * 2;      // compiler error  Result := Value;end;
开放数组参数

与C语言不同,Pascal 函数及过程的参数个数是预定的。如果参数个数预先没有确定,则需要通过开放数组来实现参数传递。

一个开放数组参数就是一个固定类型开放数组的元素。 也就是说,参数类型已定义,但是数组中的元素个数是未知数。见下例:

function Sum (const A: array of Integer): Integer;var  I: Integer;begin  Result := 0;  for I := Low(A) to High(A) do    Result := Result + A[I];end;

上面通过High(A)获取数组的大小,注意其中函数返回值 Result的应用, Result用来存储临时值。你可通过一个整数表达式组成的数组来调用该函数:

X := Sum ([10, Y, 27*I]);

给定一个整型数组,数组大小任意,你可以直接把它传递给带开放数组参数的例程,此外你也可以通过Slice 函数,只传递数组的一部分元素(传递元素个数由Slice 函数的第二个参数指定)。下面是传递整个数组参数的例子:

var  List: array [1..10] of Integer;  X, I: Integer;begin  // initialize the array  for I := Low (List) to High (List) do    List [I] := I * 2;  // call  X := Sum (List);

如果你只传递数组的一部分,可使用Slice 函数,如下:

X := Sum (Slice (List, 5));

例OpenArr中可见到包括上面的完整代码(见图6.1)。

图 6.1: 单击 Partial Slice 按钮显示的结果

在Delphi 4中,给定类型的开放数组与动态数组完全兼容(动态数组将在第8章中介绍)。动态数组的语法与开放数组相同,区别在于你可以用诸如array of Integer指令定义变量,而不仅仅是传递参数。

类型变化的开放数组参数

除了类型固定的开放数组外,Delphi 还允许定义类型变化的甚至无类型的开放数组。这种特殊类型的数组元素可随意变化,能很方便地用作传递参数。

技术上,array of const 类型的数组就能实现把不同类型、不同个数元素组成的数组一下子传递给例程。如下面Format 函数的定义(第七章中你将看到怎样使用这个函数):

function Format (const Format: string;  const Args: array of const): string;

上面第二个参数是个开放数组,该数组元素可随意变化。如你可以按以下方式调用这个函数:

N := 20;S := 'Total:';Label1.Caption := Format ('Total: %d', [N]);Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);Label3.Caption := Format ('%s %d', [S, N * 2]);

从上可见,传递的参数可以是常量值、变量值或一个表达式。声明这类函数很简单,但是怎样编写函数代码呢?怎样知道参数类型呢?对类型可变的开放数组,其数组元素与TVarRec 类型元素兼容。

注意:不要把TVarRec 记录类型和Variant 类型使用的TVarData 记录类型相混淆。这两种类型用途不同,而且互不兼容。甚至可容纳的数据类型也不同,因为TVarRec 支持Delphi 数据类型,而TVarData 支持OLE 数据类型。

TVarRec 记录类型结构如下:

type  TVarRec = record    case Byte of      vtInteger:    (VInteger: Integer; VType: Byte);      vtBoolean:    (VBoolean: Boolean);      vtChar:       (VChar: Char);      vtExtended:   (VExtended: PExtended);      vtString:     (VString: PShortString);      vtPointer:    (VPointer: Pointer);      vtPChar:      (VPChar: PChar);      vtObject:     (VObject: TObject);      vtClass:      (VClass: TClass);      vtWideChar:   (VWideChar: WideChar);      vtPWideChar:  (VPWideChar: PWideChar);      vtAnsiString: (VAnsiString: Pointer);      vtCurrency:   (VCurrency: PCurrency);      vtVariant:    (VVariant: PVariant);      vtInterface:  (VInterface: Pointer);  end;

每种记录都有一个VType 域,乍一看不容易发现,因为它与实际意义的整型类型数据(通常是一个引用或一个指针)放在一起,只被声明了一次。

利用上面信息我们就可以写一个能操作不同类型数据的函数。下例的SumAll 函数,通过把字符串转成整数、字符转成相应的序号、True布尔值加一,计算不同类型数据的和。这段代码以一个case语句为基础,虽然不得不经常通过指针取值,但相当简单,:

function SumAll (const Args: array of const): Extended;var  I: Integer;begin  Result := 0;  for I := Low(Args) to High (Args) do    case Args [I].VType of      vtInteger: Result :=        Result + Args [I].VInteger;      vtBoolean:        if Args [I].VBoolean then          Result := Result + 1;      vtChar:        Result := Result + Ord (Args [I].VChar);      vtExtended:        Result := Result + Args [I].VExtended^;      vtString, vtAnsiString:        Result := Result + StrToIntDef ((Args [I].VString^), 0);      vtWideChar:        Result := Result + Ord (Args [I].VWideChar);      vtCurrency:        Result := Result + Args [I].VCurrency^;    end; // caseend;

我已在例OpenArr中加了这段代码,该例在按下设定的按钮后调用SumAll 函数。

procedure TForm1.Button4Click(Sender: TObject);var  X: Extended;  Y: Integer;begin  Y := 10;  X := SumAll ([Y * Y, 'k', True, 10.34, '99999']);  ShowMessage (Format (    'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) => %n', [X]));end;

在图6.2中,你可以看到调用函数的输出和例OpenArr的窗体。

图 6.2: 例OpenArr的窗体,当按Untype按钮出现的信息框

Delphi 调用协定

32位的Delphi 中增加了新的参数传递方法,称为fastcall:只要有可能,传递到CPU寄存器的参数能多达三个,使函数调用操作更快。这种快速调用协定(Delphi 3确省方式)可用register 关键字标示。

问题是这种快速调用协定与Windows不兼容,Win32 API 函数必须声明使用stdcall 调用协定。这种协定是Win16 API使用的原始Pascal 调用协定和C语言使用的cdecl 调用协定的混合体。

除非你要调用外部Windows函数或定义Windows 回调函数,否则你没有理由不用新增的快速调用协定。 在后面你会看到使用stdcall 协定的例子,在Delphi帮助文件的Calling conventions 主题下,你能找到有关Delphi调用协定的总结内容。

什么是方法?

如果你使用过Delphi 或读过Delphi 手册,大概已经听说过“方法”这个术语。方法是一种特殊的函数或过程,它与类这一数据类型相对应。在Delphi 中,每处理一个事件,都需要定义一个方法,该方法通常是个过程。不过一般“方法”是指与类相关的函数和过程。

你已经在本章和前几章中看到了几个方法。下面是Delphi 自动添加到窗体源代码中的一个空方法:

procedure TForm1.Button1Click(Sender: TObject);begin  {here goes your code}end;
Forward 声明

当使用一个标识符(任何类型)时,编译器必须已经知道该标识符指的是什么。为此,你通常需要在例程使用之前提供一个完整的声明。然而在某些情况下可能做不到这一点,例如过程A调用过程B,而过程B又调用过程A,那么你写过程代码时,不得不调用编译器尚未看到其声明的例程。

欲声明一个过程或函数,而且只给出它的名字和参数,不列出其实现代码,需要在句尾加forward 关键字:

procedure Hello; forward;

在后面应该补上该过程的完整代码,不过该过程代码的位置不影响对它的调用。下面的例子没什么实际意义,看过后你会对上述概念有所认识:

procedure DoubleHello; forward;procedure Hello;begin  if MessageDlg ('Do you want a double message?',      mtConfirmation, [mbYes, mbNo], 0) = mrYes then    DoubleHello  else    ShowMessage ('Hello');end;procedure DoubleHello;begin  Hello;  Hello;end;

上述方法可用来写递归调用:即DoubleHello 调用Hello,而Hello也可能调用DoubleHello。当然,必须设置条件终止这个递归,避免栈的溢出。上面的代码可以在例DoubleH 中找到,只是稍有改动。

尽管 forward 过程声明在Delphi中不常见,但是有一个类似的情况却经常出现。当你在一个单元(关于单元的更多内容见下一章)的interface 部分声明一个过程或一个函数时,它被认为是一个forward声明,即使没有forward关键字也一样。实际上你不可能把整个例程的代码放在interface 部分,不过你必须在同一单元中提供所声明例程的实现。

类内部的方法声明也同样是forward声明,当你给窗体或其组件添加事件时, Delphi会自动产生相应的代码。在TForm 类中声明的事件是forward 声明,事件代码放在单元的实现部分。下面摘录的源代码中有一个Button1Click 方法声明:

type  TForm1 = class(TForm)    ListBox1: TListBox;    Button1: TButton;    procedure Button1Click(Sender: TObject);  end;


一个爱学习的人!
2005-01-27 15:43
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 
过程类型

Object Pascal 的另一个独特功能是可定义过程类型。过程类型属于语言的高级功能,Delphi 程序员不会经常用到它。因为后面章节要讨论相关的内容(尤其是“方法指针” Delphi用得特别多),这里不妨先了解一下。如果你是初学者,可以先跳过这部分,当学到一定程度后再回过头阅读这部分。

Pascal 中的过程类型与C语言中的函数指针相似。过程类型的声明只需要参数列表;如果是函数,再加个返回值。例如声明一个过程类型,该类型带一个通过引用传递的整型参数:

type  IntProc = procedure (var Num: Integer);

这个过程类型与任何参数完全相同的例程兼容(或用C语言行话来说,具有相同的函数特征)。下面是一个兼容例程:

procedure DoubleTheValue (var Value: Integer);begin  Value := Value * 2;end;

注意:在16位Delphi中,如果要将例程用作过程类型的实际值,必须用far指令声明该例程。

过程类型能用于两种不同的目的:声明过程类型的变量;或者把过程类型(也就是函数指针)作为参数传递给另一例程。利用上面给定的类型和过程声明,你可以写出下面的代码:

var  IP: IntProc;  X: Integer;begin  IP := DoubleTheValue;  X := 5;  IP (X);end;

这段代码与下列代码等效:

var  X: Integer;begin  X := 5;  DoubleTheValue (X);end;

上面第一段代码明显要复杂一些,那么我们为什么要用它呢?因为在某些情况下,调用什么样的函数需要在实际中决定,此时程序类型就很有用。这里不可能建立一个复杂的例子来说明这个问题,不过可以探究一下简单点的例子,该例名为ProcType。该例比前面所举的例子都复杂,更接近实际应用。

如图6.3所示,新建一个工程,在上面放两个radio按钮和一个push按钮。例中有两个过程,一个过程使参数的值加倍,与前面的DoubleTheValue过程相似;另一个过程使参数的值变成三倍,因此命名为TripleTheValue

图 6.3: 例 ProcType 窗体

procedure TripleTheValue (var Value: Integer);begin  Value := Value * 3;  ShowMessage ('Value tripled: ' + IntToStr (Value));end;

两个过程都有结果显示,让我们知道他们已被调用。这是一个简单的程序调试技巧,你可以用它来检测某一代码段是否或何时被执行,而不用在代码中加断点。

当用户按Apply 按钮,程序会根据radio按钮状态选择执行的过程。实际上,当窗体中有两个radio按钮时,你只能选择一个,因此你只需要在Apply 按钮的OnClick 事件中添加代码检测radio按钮的值,就能实现程序要求。不过为了演示过程类型的使用,我舍近求远选择了麻烦但有趣的方法:只要用户选中其中一个radio按钮,按钮对应的过程就会存入过程变量:

procedure TForm1.DoubleRadioButtonClick(Sender: TObject);begin  IP := DoubleTheValue;end;

当用户按Apply 按钮,程序就执行过程变量保存的过程:

procedure TForm1.ApplyButtonClick(Sender: TObject);begin  IP (X);end;

为了使三个不同的函数能访问IP和 X变量,需要使变量在整个窗体单元中可见,因此不能声明为局部变量(在一个方法中声明)。一个解决办法是,把这些变量放在窗体声明中:

type  TForm1 = class(TForm)    ...  private    { Private declarations }    IP: IntProc;    X: Integer;  end;

学完下一章,你会更清楚地了解这段代码的意思,目前只要能知道怎样添加过程类型定义、怎样修改相应的代码就行了。为了用适当的值初始化上面代码中的两个变量,你可以调用窗体的OnCreate 事件(激活窗体后,在Object Inspector中选择这一事件,或者双击窗体)。此外最好仔细看一看上例完整的源代码。

在第九章的 Windows 回调函数一节,你能看到使用过程类型的实例

函数重载

重载的思想很简单:编译器允许你用同一名字定义多个函数或过程,只要它们所带的参数不同。实际上,编译器是通过检测参数来确定需要调用的例程。

下面是从VCL的数学单元(Math Unit)中摘录的一系列函数:

function Min (A,B: Integer): Integer; overload;function Min (A,B: Int64): Int64; overload;function Min (A,B: Single): Single; overload;function Min (A,B: Double): Double; overload;function Min (A,B: Extended): Extended; overload;

当调用方式为Min (10, 20)时,编译器很容易就能判定你调用的是上列第一个函数,因此返回值也是个整数。

声明重载函数有两条原则:

  • 每个例程声明后面必须添加overload 关键字。
  • 例程间的参数个数或(和)参数类型必须不同,返回值不能用于区分各例程。

下面是ShowMsg 过程的三个重载过程。我已把它们添加到例OverDef 中(一个说明重载和确省参数的应用程序):

procedure ShowMsg (str: string); overload;begin  MessageDlg (str, mtInformation, [mbOK], 0);end;procedure ShowMsg (FormatStr: string;  Params: array of const); overload;begin  MessageDlg (Format (FormatStr, Params),    mtInformation, [mbOK], 0);end;procedure ShowMsg (I: Integer; Str: string); overload;begin  ShowMsg (IntToStr (I) + ' ' + Str);end;

三个过程分别用三种不同的方法格式化字符串,然后在信息框中显示字符串。下面是三个例程的调用:

ShowMsg ('Hello');ShowMsg ('Total = %d.', [100]);ShowMsg (10, 'MBytes');

令我惊喜的是Delphi的代码参数技术与重载过程及函数结合得非常好。当你在例程名后面键入左圆括号时,窗口中会显示所有可用例程的参数列表,当你输入参数时,Delphi会根据所输入参数的类型过滤参数列表。从图6.4你可看到,当开始输入一个常量字符串时,Delphi只显示第一个参数为字符串的两个ShowMsg例程参数列表,滤掉了第一个参数为整数的例程。

图 6.4: 窗口中代码参数提示条显示的重载例程参数

重载例程必须用overload关键字明确标示,你不能在同一单元中重载没有overload标示的例程,否则会出现错误信息: "Previous declaration of '<name>' was not marked with the 'overload' directive."。不过你可以重载在其他单元中声明的例程,这是为了与以前的Delphi版本兼容,以前的Delphi版本允许不同的单元重用相同的例程名。无论如何,这是例程重载的特殊情况不是其特殊功能,而且不小心会出现问题。

例如在一个单元中添加以下代码:

procedure MessageDlg (str: string); overload;begin  Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);end;

这段代码并没有真正重载原始的MessageDlg 例程,实际上如果键入:

MessageDlg ('Hello');

你将得到一个有意思的错误消息,告诉你缺少参数。调用本地例程而不是VCL的唯一途径是明确标示例程所在单元,这有悖于例程重载的思想:

OverDefF.MessageDlg ('Hello');
确省参数

Delphi 4 中添加了一个新功能,即允许你给函数的参数设定确省值,这样调用函数时该参数可以加上,也可以省略。下例把应用程序全程对象的MessageBox 方法重新包装了一下,用PChar 替代字符串,并设定两个确省值:

procedure MessBox (Msg: string;  Caption: string = 'Warning';  Flags: LongInt = mb_OK or mb_IconHand);begin  Application.MessageBox (PChar (Msg),    PChar (Caption), Flags);end;

使用这一定义,你就可以用下面任一种方式调用过程:

MessBox ('Something wrong here!');MessBox ('Something wrong here!', 'Attention');MessBox ('Hello', 'Message', mb_OK);

从图6.5中可以看到,Delphi的代码参数提示条会用不同的风格显示确省值参数,这样你就很容易确定哪个参数是可以省略的。

图 6.5: Delphi代码参数提示条用方括号标示确省值参数,调用时可以省略该参数

注意一点,Delphi 不产生任何支持确省参数的特殊代码,也不创建例程的多份拷贝,缺省参数是由编译器在编译时添加到调用例程的代码中。

使用确省参数有一重要限定:你不能“跳过”参数,如省略第二个参数后,不能把第三个参数传给函数:

MessBox ('Hello', mb_OK); // error  

确省参数使用主要规则:调用时你只能从最后一个参数开始进行省略,换句话说,如果你要省略一个参数,你必须省略它后面所有的参数。

确省参数的使用规则还包括:

  • 带确省值的参数必须放在参数表的最后面。
  • 确省值必须是常量。显然,这限制了确省参数的数据类型,例如动态数组和界面类型的确省参数值只能是 nil;至于记录类型,则根本不能用作确省参数。
  • 确省参数必须通过值参或常参传递。引用参数 var不能有缺省值。

如果同时使用确省参数和重载可能会出现问题,因为这两种功能可能发生冲突。例如把以前ShowMsg 过程改成:

procedure ShowMsg (Str: string; I: Integer = 0); overload;begin  MessageDlg (Str + ': ' + IntToStr (I),    mtInformation, [mbOK], 0);end;

编译时编译器不会提出警告,因为这是合法的定义。

然而编译调用语句:

ShowMsg ('Hello');

编译器会显示 Ambiguous overloaded call to 'ShowMsg'.( 不明确重载调用ShowMsg)。注意,这条错误信息指向新定义的重载例程代码行之前。实际上,用一个字符串参数无法调用ShowMsg 过程,因为编译器搞不清楚你是要调用只带字符串参数的ShowMsg 过程,还是带字符串及整型确省参数的过程。遇到这种问题时,编译器不得不停下来,要求你明确自己的意图。

结束语

过程和函数是编程的一大关键,Delphi 中的方法就是与类及对象关联的过程和函数。

下面几章将从字符串开始详细讲解Pascal的一些编程元素。


一个爱学习的人!
2005-01-27 15:43
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 
PASCAL 精要 第七章 字符串操作

Delphi 中字符串的操作很简单,但幕后情况却相当复杂。Pascal 传统的字符串操作方法与Windows 不同,Windows吸取了C语言的字符串操作方法。32位Delphi中增加了长字符串类型,该类型功能强大,是Delphi 确省的字符串类型。

字符串类型

在Borland公司的Turbo Pascal和16位Delphi中,传统的字符串类型是一个字符序列,序列的头部是一个长度字节,指示当前字符串的长度。由于只用一个字节来表示字符串的长度,所以字符串不能超过255个字符。这一长度限制为字符串操作带来不便,因为每个字符串必须定长(确省最大值为255),当然你也可以声明更短的字符串以节约存储空间。

字符串类型与数组类型相似。实际上一个字符串差不多就是一个字符类型的数组,因为用[]符号,你就能访问字符串中的字符,这一事实充分说明了上述观点。

为克服传统Pascal 字符串的局限性,32位Delphi增加了对长字符串的支持。这样共有三种字符串类型:

  • ShortString 短字符串类型也就是前面所述的传统 Pascal 字符串类型。这类字符串最多只能有255个字符,与16位Delphi中的字符串相同。短字符串中的每个字符都属于ANSIChar 类型(标准字符类型)。
  • ANSIString长字符串类型就是新增的可变长字符串类型。这类字符串的内存动态分配,引用计数,并使用了更新前拷贝(copy­-on-write)技术。这类字符串长度没有限制(可以存储多达20亿个字符!),其字符类型也是ANSIChar 类型。
  • WideString 长字符串类型与ANSIString 类型相似,只是它基于WideChar 字符类型,WideChar 字符为双字节Unicode 字符。
使用长字符串

如果只简单地用String定义字符串,那么该字符串可能是短字符串也可能是ANSI长字符串,这取决于$H 编译指令的值,$H+(确省)代表长字符串(ANSIString 类型)。长字符串是Delphi 库中控件使用的字符串。

Delphi 长字符串基于引用计数机制,通过引用计数追踪内存中引用同一字符串的字符串变量,当字符串不再使用时,也就是说引用计数为零时,释放内存。

如果你要增加字符串的长度,而该字符串邻近又没有空闲的内存,即在同一存储单元字符串已没有扩展的余地,这时字符串必须被完整地拷贝到另一个存储单元。当这种情况发生时,Delphi运行时间支持程序会以完全透明的方式为字符串重新分配内存。为了有效地分配所需的存储空间,你可以用SetLength 过程设定字符串的最大长度值:

SetLength (String1, 200);

SetLength 过程只是完成一个内存请求,并没有实际分配内存。它只是把将来所需的内存预留出来,实际上并没有使用这段内存。这一技术源于Windows 操作系统,现被Delphi用来动态分配内存。例如,当你请求一个很大的数组时,系统会将数组内存预留出来,但并没有把内存分配给数组。

一般不需要设置字符串的长度,不过当需要把长字符串作为参数传递给API 函数时(经过类型转换后),你必须用SetLength 为该字符串预留内存空间,这一点我会在后面进行说明。

看一看内存中的字符串

为了帮你更好地理解字符串的内存管理细节,我写了一个简例StrRef 。在程序中我声明了两个全程字符串:Str1 和 Str2,当按下第一个按钮时,程序把一个字符串常量赋给第一个变量,然后把第一个变量赋给第二个:

Str1 := 'Hello';Str2 := Str1;

除了字符串操作外,程序还用下面的StringStatus 函数在一个列表框中显示字符串的内部状态:

function StringStatus (const Str: string): string;begin  Result := 'Address: ' + IntToStr (Integer (Str)) +    ', Length: ' + IntToStr (Length (Str)) +     ', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) +    ', Value: ' + Str;end;

在StringStatus 函数中,用常量参数传递字符串至关重要。用拷贝方式(值参)传递会引起副作用,因为函数执行过程中会产生一个对字符串的额外引用;与此相反,通过引用(var)或常量(const)参数传递不会产生这种情况。由于本例不希望字符串被修改,因此选用常量参数。

为获取字符串内存地址(有利于识别串的实际内容也有助于观察两个不同的串变量是否引用了同一内存区),我通过类型映射把字符串类型强行转换为整型。字符串实际上是引用,也就是指针:字符串变量保存的是字符串的实际内存地址。

为了提取引用计数信息,我利用了一个鲜为人知的事实:即字符串长度和引用计数信息实际上保存在字符串中, 位于实际内容和字符串变量所指的内存位置之前,其负偏移量对字符串长度来说是-4(用Length 函数很容易得到这个值),对引用记数来说是-8。

不过必须记住,以上关于偏移量的内部信息在未来的Delphi版本中可能会变,没有写入正式Delphi文档的特性很难保证将来不变。

通过运行这个例子,你会看到两个串内容相同、内存位置相同、引用记数为2,如图7.1中列表框上部所示。现在,如果你改变其中一个字符串的值,那么更新后字符串的内存地址将会改变。这是copy-on-write技术的结果。

图 7.1: 例StrRef显示两个串的内部状态,包括当前引用计数

第二个按钮(Change)的OnClick 事件代码如下,结果如图7.1列表框第二部分所示:

procedure TFormStrRef.BtnChangeClick(Sender: TObject);begin  Str1 [2] := 'a';  ListBox1.Items.Add ('Str1 [2] := ''a''');  ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1));  ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2));end;

注意,BtnChangeClick 只能在执行完BtnAssignClick 后才能执行。为此,程序启动后第二个按钮不能用(按钮的Enabled 属性设成False);第一个方法结束后激活第二个按钮。你可以自由地扩展这个例子,用StringStatus 函数探究其它情况下长字符串的特性。

Delphi 字符串与 Windows PChar字符串

长字符串为零终止串,这意味着长字符串完全与Windows使用的C语言零终止串兼容,这给长字符串使用带来了便利。一个零终止串是一个字符序列,该序列以一个零字节(或null)结尾。零终止串在Delphi中可用下标从零开始的字符数组表示,C语言就是用这种数组类型定义字符串,因此零终止字符数组在Windows API 函数(基于C语言)中很常见。由于Pascal长字符串与C语言的零终止字符串完全兼容,因此当需要把字符串传递给Windows API 函数时,你可以直接把长字符串映射为PChar 类型。

下例把一个窗体的标题拷贝给PChar 字符串(用API 函数GetWindowText),然后再把它拷贝给按钮的Caption 属性,代码如下:

procedure TForm1.Button1Click (Sender: TObject);var  S1: String;begin  SetLength (S1, 100);  GetWindowText (Handle, PChar (S1), Length (S1));  Button1.Caption := S1;end;

你可以在例LongStr 中找到这段代码。注意:代码中用SetLength函数为字符串分配内存,假如内存分配失败,那么程序就会崩溃;如果你直接用PChar 类型传递值(而不是象以以上代码那样接受一个值),那么代码会很简单,因为不需要定义临时字符串,也不需要初始化串。下面代码把一个Label(标签)控件的Caption 属性作为参数传递给了API函数,只需要简单地把属性值映射为PChar类型:

SetWindowText (Handle, PChar (Label1.Caption));

当需要把WideString 映射为Windows兼容类型时,你必须用PWideChar 代替PChar进行转换,WideString常用于OLE和 COM 程序。

刚才展现了长字符串的优点,现在谈谈它的弊端。当你把长字符串转换为PChar 类型时可能会引发一些问题,问题根本在于:转换以后字符串及其内容将由你来负责,Delphi 不再管了。现在把上面Button1Click代码稍作修改:

procedure TForm1.Button2Click(Sender: TObject);var  S1: String;begin  SetLength (S1, 100);  GetWindowText (Handle, PChar (S1), Length (S1));  S1 := S1 + ' is the title'; // this won't work  Button1.Caption := S1;end;

程序编译通过,但执行结果会令你惊讶,因为按钮的标题并没变,所加的常量字符串没有添加到按钮标题中。问题原因是Windows写字符串时(在GetWindowText API调用中),Windows 没有正确设置Pascal 长字符串的长度。Delphi 仍可以输出该字符串,并能通过零终止符判断字符串何时结束,但是如果你在零终止符后添加更多的字符,那么这些字符将被忽略。

怎么解决这个问题呢?解决方法是告诉系统把GetWindowText API函数返回的字符串再转换成Pascal字符串。然而,如果你用以下代码:

S1 := String (S1);

Delphi 系统将不予理睬,因为把一种类型转换为它自己的类型是无用的操作。为获得正确的Pascal 长字符串,需要你把字符串重新映射为一个PChar 字符串,然后让Delphi 再把它转回到字符串:

S1 := String (PChar (S1));

实际上,你可以跳过字符串转换(S1 := PChar (S1));, 因为在Delphi中Pchar转换到string是自动执行的,最终代码如下:

procedure TForm1.Button3Click(Sender: TObject);var  S1: String;begin  SetLength (S1, 100);  GetWindowText (Handle, PChar (S1), Length (S1));  S1 := String (PChar (S1));  S1 := S1 + ' is the title';  Button3.Caption := S1;end;

另一个办法是用PChar 字符串的长度重新设定Delphi 字符串长度,可以这样写:

SetLength (S1, StrLen (PChar (S1)));

在例LongStr中你可以看到三种方法的结果,分别由三个按钮执行。如果只想访问窗体标题,仅需要用到窗体对象本身的Caption 属性,没有必要写这段迷糊人的代码,这段代码只是用来说明字符串转换问题。当调用Windows API 函数时会遇到这种实际问题,那时你就不得不考虑这一复杂情况了。

格式化字符串

使用加号(+)操作符和转换函数(如IntToStr),你确实能把已有值组合成字符串,不过另有一种方法能格式化数字、货币值和其他字符串,这就是功能强大的Format 函数及其一族。

Format 函数参数包括:一个基本文本字符串、一些占位符(通常由%符号标出)和一个数值数组,数组中每个值对应一个占位符。例如,把两个数字格式化为字符串的代码如下:

Format ('First %d, Second %d', [n1, n2]);

其中n1和n2是两个整数值,第一个占位符由第一个值替代,第二个占位符由第二个值替代,以此类推。如果占位符输出类型(由%符号后面的字母表示)与对应的参数类型不匹配,将产生一个运行时间错误,因此设置编译时间类型检查会有利于Format 函数的使用。

除了%d外,Format 函数还定义了许多占位符,见表7.1。这些占位符定义了相应数据类型的默认输出,你可以用更深一层的格式化约束改变默认输出,例如一个宽度约束决定了输出中的字符个数,而精度约束决定了小数点的位数。例如

Format ('%8d', [n1]);

该句把数字n1转换成有8个字符的字符串,并通过填充空白使文本右对齐,左对齐用减号(-) 。

表 7.1: Format函数的占位符

占位符说明
d (decimal)将整型值转换为十进制数字字符串
x (hexadecimal)将整型值转换为十六进制数字字符串
p (pointer)将指针值转换为十六进制数字字符串
s (string)拷贝字符串、字符、或字符指针值到一个输出字符串
e (exponential)将浮点值转换为指数表示的字符串
f (floating point)将浮点值转换为浮点表示的字符串
g (general)使用浮点或指数将浮点值转换为最短的十进制字符串
n (number)将浮点值转换为带千位分隔符的浮点值
m (money)将浮点值转换为现金数量表示的字符串,转换结果取决于地域设置,详见Delphi帮助文件的Currency and date/time formatting variables主题

领会以上内容最好的办法是你亲自进行字符串格式化试验。为了简便起见,我写了FmtTest 程序,它能将整数和浮点数转换为格式化字符串。从图7.2可见,程序窗体分为左右两部分,左边对应整型数字转换,右边对应浮点数转换。

各部分的第一个编辑框显示需要格式化为字符串的数值。第一个编辑框下方有一个按钮,用来执行格式化操作并在消息框中显示结果;紧接着第二个编辑框用于输入格式化类型串。你也可以单击ListBox 控件中的任一行,选择预定义的格式化类型串,也可以自行输入,每输入一个新的格式化类型串,该类型串就会被添加到列表框中(注意,关闭程序就失去了添加的类型)。

图 7.2: 程序 FmtTest 的浮点值输出

本例只简单使用了不同的控制文本来产生输出,下面列出了其中一个Show 按钮事件代码:

procedure TFormFmtTest.BtnIntClick(Sender: TObject);begin  ShowMessage (Format (EditFmtInt.Text,    [StrToInt (EditInt.Text)]));  // if the item is not there, add it  if ListBoxInt.Items.IndexOf (EditFmtInt.Text) < 0 then    ListBoxInt.Items.Add (EditFmtInt.Text);end;

这段代码主要用EditFmtInt 编辑框的文本和EditInt 控件的值进行了格式化操作。如果格式化类型串没有在列表框中列出,那么输入的串会被添加到列表框中;如果用户在列表框中进行点击,代码会把点击的串移到编辑框中:

procedure TFormFmtTest.ListBoxIntClick(Sender: TObject);begin  EditFmtInt.Text := ListBoxInt.Items [    ListBoxInt.ItemIndex];end;
结束语

字符串是一种很常用的数据类型,尽管在很多情况下不理解字符串怎样工作也能安全使用它们,不过通过本章,了解了字符串的内部运行机制之后,你就能更充分地利用字符串类型的强大功能。

Delphi用特殊的动态方式处理字符串内存,正如动态数组一样,这将在下一章进行讨论。


一个爱学习的人!
2005-01-27 15:44
shuyue1981
Rank: 1
等 级:新手上路
帖 子:89
专家分:0
注 册:2005-1-20
得分:0 
PASCAL 精要 第八章 内存

作者的话:本章内容涉及内存处理,讨论各种内存区,并介绍动态数组。目前暂时只有动态数组部分。

Delphi 4 的动态数组

传统的Pascal 语言其数组大小是预先确定的,当你用数组结构声明数据类型时,你必须指定数组元素的个数。专业程序员也许知道些许动态数组的实现技术,一般是采用指针,用手工分配并释放所需的内存。

Delphi 4中增加了非常简单的动态数组实现方法,实现过程效仿我前面讲过的动态长字符串。与长字符串一样,动态数组的内存动态分配并且引用记数,不过动态数组不支持 copy-on-write 技术。这不是个大问题,因为你可以把变量值设置为nil释放数组内存。

这样你就可以声明一个不指定元素个数的数组,并用SetLength 过程给数组分配一个特定大小的内存,SetLength 过程还可以改变数组大小而不影响其内容,除此外还有一些字符串过程也可用于数组,如Copy 函数。

以下摘录的代码突出了一点,这就是:定义数组后必须先为它分配内存,然后才能开始使用:

procedure TForm1.Button1Click(Sender: TObject);var  Array1: array of Integer;begin  Array1 [1] := 100; // error  SetLength (Array1, 100);  Array1 [99] := 100; // OK  ...end;

如果你只定义一个数组元素个数,那么索引总是从0开始。Pascal 中的普通数组既能用不为零的下标,也能用非整数的下标,但动态数组均不支持这两种下标。象普通数组一样,你可以通过Length、High和Low 函数了解到动态数组的状况,不过对于动态数组,Low 函数返回值总是0,High函数返回数组大小减1,这意味着空的动态数组其函数High返回值是-1,这是一个很怪的值,因为它比Low的返回值还小。

图 8.1: 例 DynArr 窗体

以上作了简短的介绍,现在举个简例,例名DynArr ,见图8.1。例子实在是很简单,其实动态数组没有什么特别复杂地方。我想通过该例说明几个程序员可能犯的错误。程序中声明了两个全程数组并在OnCreate 事件中初始化了第一个数组:

var  Array1, Array2: array of Integer;procedure TForm1.FormCreate(Sender: TObject);begin  // allocate  SetLength (Array1, 100);end;

这样就把数组所有值设置为0。完成这段代码你马上就能读写数组元素的值,而不用害怕内存出错,当然条件是你没有试图访问超过数组上界的元素。为了更好地初始化,程序中添加了一个按钮,执行数组元素赋值操作:

procedure TForm1.btnFillClick(Sender: TObject);var  I: Integer;begin  for I := Low (Array1) to High (Array1) do    Array1 [I] := I;end;

Grow 按钮用于修改数组大小,但并不影响数组内容。单击Grow 按钮后,你可以用Get value按钮进行检验:

procedure TForm1.btnGrowClick(Sender: TObject);begin  // grow keeping existing values  SetLength (Array1, 200);end;procedure TForm1.btnGetClick(Sender: TObject);begin  // extract  Caption := IntToStr (Array1 [99]);end;

Alias 按钮的OnClick 事件代码稍复杂些,程序通过 := 算子把一个数组拷贝给另一个数组,从而有效地创建了一个别名(一个新变量,但引用内存中同一数组)。从中可见,如果你改变了其中一个数组,那么另一个同样也会改变,因为它们指向同一个内存区:

procedure TForm1.btnAliasClick(Sender: TObject);begin  // alias  Array2 := Array1;  // change one (both change)  Array2 [99] := 1000;  // show the other  Caption := IntToStr (Array1 [99]);

在btnAliasClick 事件中增加了两部分操作内容。第一部分是数组等同测试,不过并不是测试实际的数组元素,而是测试数组所引用的内存区,检测变量是不是内存中同一数组的两个别名:

procedure TForm1.btnAliasClick(Sender: TObject);begin  ...  if Array1 = Array2 then    Beep;  // truncate first array  Array1 := Copy (Array2, 0, 10);end;

btnAliasClick 事件的第二部分内容是调用Copy 函数。该函数不仅把数据从一个数组移到另一个数组,而且用函数创建的新数组取代第一个数组,结果变量Array1 所引用的是11个元素的数组,因此,按Get value 和Set value 按钮将产生一个内存错误,并且触发一个异常(除非你把范围检查range-checking 选项关掉,这种情况下,错误仍在但屏幕上不会显示异常)。虽然如此,Fill 按钮仍能正常工作,因为需要修改的数组元素由数组当前的下标范围确定。

结束语

这一章内容暂时只包括动态数组,动态数组的确是内存管理的重要组成部分,但仅仅是其中的一部分,其它内容以后会逐步添加。

本章描述的内存结构属于典型的 Windows 编程内容,这方面内容将在下一章进行讨论。


一个爱学习的人!
2005-01-27 15:44



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-11777-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.436132 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved