Object Pascal 语言基础

来源:互联网 时间:1970-01-01

Delphi 是以Object Pascal 语言为基础的可视化开发工具,所以要学好Delphi,首先要掌握的就是Object Pascal 语言。Object Pascal语言是Pascal之父在1985年于Apple Macintosh机器上实现的。后来Borland公司也在它的Pascal产品Turbol Pascal/Delphi中实现了Object Pascal。

 

注:该系列内容整理自以下链接。

http://chanlei001.blog.163.com/blog/static/3403066420117261154351

http://www.cnblogs.com/del/archive/2007/12/04/982167.html

http://blog.csdn.net/hudie1234567/article/details/6403419

http://www.cnblogs.com/del/archive/2007/12/10/989467.html

http://www.cnblogs.com/del/archive/2007/12/04/982851.html 

http://gehy1.blog.163.com/blog/static/3447887820091194619693/


 


 

语言基础

Object Pascal 是一种高级编译语言,建立于free pascal 和turbo pascal 之上,具有强类型(对数据类型的检查非常严格)特性,支持结构化和面向对象编程。它的优点包括代码的易读性、快速编译,以及支持多个单元文件从而实现模块化编程。Object Pascal 语言是在Pascal 语言的基础上发展起来的,它继承了Pascal 语言语法严谨、数据结构丰富等优点,同时融入了面向对象编程的语法要素,使之成为一个完善的面向对象的编程语言。Pascal编译器,包括那些Object Pascal的编译器,在生成高优化代码同时,一般运行非常快。

 

1 Object Pascal 语言编写环境

随着Windows 操作系统的普及,很少有人在DOS 环境下编写程序了,下面将为读者介绍如何使用Delphi 编写Object Pascal 程序。

一般来说,使用Delphi 开发出来的程序有以下3 种。
1.图形界面程序
图形界面程序是目前Windows 平台最常见的应用程序,这些程序通过窗体和对话框与用户进行信息交互,实现一定的功能。例如Microsoft 公司的Office 系列软件和正在使用的Delphi 等,它们都是图形界面程序。
2.控制台程序
控制台程序是指一些没有图形用户界面的32 位Windows 应用程序,类似在DOS 环境下运行的程序。这些程序很少要求用户输入大量的信息,一般只实现特定的功能。控制台程序的代码较小,占用的系统资源少,因此编译、链接的速度比较快。
3.服务器程序
服务器程序可以接受和处理客户应用程序的请求,并将结果信息返回给客户应用程序。服务器应用程序一般在后台运行,不需要大量的人机交互信息。


下面以开发一个简单的控制台程序为例,向读者介绍Delphi 中生成Object Pascal 程序的基本方法,并对Object Pascal 程序的一般结构进行简单说明。

在Delphi 集成开发环境中,依次选择"File/New/Other"菜单项打开"New Items"对话框。选中"New"标签页中的"Console Application"选项,单击"OK"按钮,代码编辑器中将自动打开一个初始名为Project1.dpr 的控制台程序。请将Project1.dpr 文件修改为如下内容:

program Project1;

{$APPTYPE CONSOLE}

//uses SysUtils;

var

str:string;

begin

// Insert user code here

writeln(’您好,这是一个示范程序,请输入一行文字:’);

readln(str);

writeln(’您输入的是:’,str);

readln;

end.

依次选择“File”/“Save All”菜单项可以将更改后的工程文件保存到指定的文件夹中。按 F9 键进入编译、链接、运行程序。待程序提示“您好,这是一个示范程序,请输入一行文字:”文字后,用户可以输入一些内容,例如输入“早上好!”,程序将输出结果:“您输入的是:早上好!”。

以下是对该程序的几点说明:

  • 程序第2 行中{$APPTYPE CONSOLE}是一个编译器指令,它告诉编译器这个程序是一个控制台程序。
  • uses SysUtils 语句前加了两个斜线,将这条语句以注释语句的形式屏蔽掉了,因为在这个例子中是不需要SysUtils 单元的。
  • writeln 和readln 两个函数分别表示输出和输入一行字符。程序最后的readln 语句是为了锁定窗口,否则输出结果将很快消失。

 

2 注释语句

作为起点,首先介绍如何在Object Pascal 代码中添加注释。如果不给程序加上适当的注释,一段时间后就很难理清程序的流程。同时编译器会将注解忽略,不会影响程序的编译与运行。

Object Pascal 中有3 种类型的注释。

  1. 花括号注释:组合符号“{”和“}”的成对使用,表示它们之间的内容是注释部分。
  2. 圆括号/星号注释:组合符号“(*”和“*)”的成对使用,表示它们之间的内容是注释部分。
  3. Visual C++风格的双斜杠注释:符号“//”的单独使用,表示后面的内容是注释部分。

看下面的例子:

 {花括号注释}

(*圆括号/星号注释*)

//C++风格的注释

前两种注释在本质上是相同的,编译器把处于限定符头和限定符尾中间的内容当作注释。花括号圆括号/星号比较适合在大段注释时使用。如果在“{”或“(*”后面是一个“$”符号时,表示该句为一个编译器指令,与普通的注释不同,通常用来对编译过程进行设置,例如1.1 节中示例的第2行代码:

{$APPTYPE CONSOLE}

对于Visual C++风格的注释来说,双斜杠后面到行尾的内容被认为注释。此形式比较适用于单行和少量几行注释的情况。

注意,相同类型的注释不要嵌套使用。虽然不同类型的注释进行嵌套在语法上是合法的,但不建议这样做。例如:

{(*这是合法的*)}

(*{这是合法的}*)

(*(*这是非法的*)*)

{{这是非法的}}

 

3 标识符

Object Pascal 语言使用的标识符包括字母A-Z、a-z、数字0-9 及其他一些标准字符。

表1-1 所示的单个字符是Object Pascal 语言的特殊符号。

表1-1 Object Pascal 语言的特殊符号的单个字符

特殊符号

$ & * # * (  )  [  ] {  }  ^  ;  :  @  <  =  >  ,  .  * +  / 

    
表1-2 所示的字符组合是Object Pascal 语言的单个的特殊符号。
表1-2  Object Pascal 语言的特殊符号的字符组合 

特殊符号

(*  *)  (.  .)  ..  //  :=  <>  >=  <=    


注意:[ ]与(. .)对应,{ }与(* *)对应。含义完全相同,可以相互替代。


在Object Pascal 语言中,标识符用来标识变量、常量、属性、类、对象、过程、函数、程序、组件库等。标识符可以由任意长度不带空格的字符串组成,但对于编译器来讲只有前面255个字符有效。其中,标识符的第1个字符必须是字母或下划线,其余字符可以是字母、数字或下划线。通常,标识符由一个或多个具有适当意义的英文单词组成。Object Pascal 语言对区分字母的大小写是不敏感的。在编程过程中,最好每个单词的首字母大写,其他字母小写,以便于区分。

 

4 保留字和指令字

Object Pascal 语言定义了65 个保留字,它们不能被定义为标识符,如表1-3 所示。
表1-3  Object Pascal 语言的保留字 

保留字

and

array

as

asm

begin

case

class

Const

constructor

destructor

dispinterface

div

do

downto

else

end

except

exports

file

finalization

finally

for

function

goto

if

implementation

in

inherite

initialization

inline

is

interface

label

library

mod

Nil

not

object

of

or

out

packed

procedure

program

property

raise

record

repeat

resourcestring

set

shl

shr

string

then

threadvar

to

try

type

until

unit

uses

var

while

with

xor

 

       
Object Pascal 还定义了39 个指令字,它们具有特殊含义。但是,在用户重新定义了指令字后,在作用域内它们就失去了原来的意义,如表1-4 所示。
表1-4  Object Pascal 语言的指令字 

指令字

absolute

abstract

assembler

automated

cdecl

contains

default

dispid

dynamic

export

external

far

forward

implements

index

message

name

near

nodefault

overload

override

package

pascal

private

protected

public

published

read

readonly

register

reintroduce

requires

resident

safecall

stdcall

stored

virtual

write

writeonly

 

 

 

 

不要定义和它们同名的标识符。在Delphi 集成开发环境的代码编辑器中,保留字和指令字以黑体显示,这样就大大方便了用户的使用,不必担心因为不小心而错误地将保留字或指令字定义为标识符。

 

published 和 public

公布成员(published)和公共成员(public)具有相同的可见度,但published中声明的会显示在属性栏,public 则不会。

主要区别如下:
(1)   published 可见度与public一样。
(2)   published与public的区别是本区域的成员可以在delphi对象检查器中出现,而此差别来自RTTI机制。RTTI(Run time type Information)是为Published制作,它允许应用程序动态查询该类的published的字段、属性(包括属性和事件),并且加载类的方法。RTTI启动条件是有条件的——使“{$M+}”编译指令的状态下才能启动RTTI或它的祖先类以上述状态编译,也可以启动RTTI,然后该类检查器才能显示在检查器中。
(3)   除了RTTI条件限制后,published的属性也有类型限制。

  • A.序数、字符串(string)、class、interface以及mothod—pointer类,都可以做published的属性
  • B.范围在0-31之间的集合,该集合值必须满足byte、word或double word类才能做published属性
  • C.除了Real类外的所有实数类,都可以做为published属性
  • D.数组类不可以做published属性
  • E.所有的成员函数都可以作为published的事件,然而重载override的函数不可以作为published事件
  • F.字段Fieled不能作为Published的属性,除非它属于class或interface类

 

property

property是 Delphi 的特性,它使得方法具有了字段的调用特征,并赋予字段执行动作的能力。比如:

edt.Text = "test";

这个时候edt文本框的内容会随之改变,但是“理论”上Text应该只是改变了Text所在的内存数据而已,为什么会导致窗口更新界面这一些列动作?这就是因为property 这一特性。property 是 Borland 为 C++ 扩展的语法特性,目的在于使 C++ Builder 能够方便的使用 VCL 库,毕竟 VCL 是使用 Object Pascal 写的。如果学习过C#的朋友应该会很容易理解,因为C#的属性就是学习自 Delphi,毕竟 C# 和 Delphi 是同一个设计者。

定义一个属性Property的基本格式如下:

property 属性名 : 属性值类型 read 属性读函数/属性值变量 write 属性写函数/属性值变量

property 是属性定义关键字。属性的特征类似于字段,所以属性名就像字段名,属性值类型就像字段的值类型。属性读函数,是属性被“读取”时所执行的操作,这样在执行“取值”操作时,具备了执行其他动作的可能。另外,属性值变量,可以是 property 所在类能够访问的任何变量,如果使用了属性值变量,则相当于属性值直接从值变量中获取,这和直接赋值是没有什么差别的。属性写函数,是属性被“写入”时所执行的操作,这样在执行属性“赋值”操作时,具备了执行其他动作的可能。比如:写入edt的Text属性时,窗口会同时执行界面更新操作。另外,属性值变量和上面所述类似,如果使用了,就相当于将传来的属性值直接赋值到对应的属性值变量。这里的函数是真正的函数,不像C#中那样的getter和setter,所以会有些难以理解。

 

属性读函数声明:

function 读函数名: 属性值类型;

其中读函数名可以自定义,只要和属性声明中一样即可,该函数的返回值就是读属性操作时实际获取的值。

 

属性写函数声明(其实是一个子函数):

procedure 写函数名(value : 属性值类型);

其中写函数名可以自定义,只要和属性声明中一样即可,该函数参数value,就是对属性赋值时传递过来的实际值。

 

读函数和写函数必须设置一个,如果只设置读函数,而没有设置写函数(同时去掉write关键字),这样的属性就是只读属性,同理也可以设置只写属性。为了保证属性公开性的同时掩盖读写函数的可见性,可以将读写函数设置为私有,而将属性设置为共有,这样可以避免将读写函数本身暴露给调用者。

举例:

TxKernelSearchThread = class(TThread)

private

keyword_list: TStringList;

procedure SetKeyword(value: UnicodeString);

public

property Kerword:UnicodeString write SetKeyword;

...

end;

procedure TxKernelSearchThread.SetKeyword(value: UnicodeString);

begin

if value = '' then
Exit;

ExtractStrings([' '],[' '],PWideChar(value),Self.keyword_list);

end;

上面是一个只写属性的例子。属性的一个很重要的应用就是VCL中的控件属性,以及事件属性等,这也就解释了为什么向文本框的Text属性赋值,会更新界面操作,这正是因为属性将字段和函数的特征结合了起来。 


 

Record 和Packed Record

Record 结构体表明编译器编译时要求进行字对齐,packed Record 的结构体表明编译器编译该结构体时不需要进行字对齐,这种方式对结构体中的字段访问会比第一种方式慢,但是更节约空间。有Packed 的占用内存小,但是速度慢一点。没Packed 的占用内存大,但是速度快一点。 

在windows中,内存的分配一次是4个字节的,而 Packed 按字节进行内存的申请和分配,这样速度要慢一些,因为需要额外的时间来进行指针的定位。因此如果不用Packed的话,Delphi将按一次4个字节的方式申请内存,因此如果一个变量没有4个字节宽的话也要占4个字节!这样浪费了一些空间,但提高了效率。

 

inherited

inherited就是调用祖先类的函数,如果不带参数就是默认调用同名函数;如果带参数则表明子类中的函数个数可能比祖先类要多取其中的几个参数传过去。

 

Override 和 overload

方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。

 

Delphi 是用override 关键字来说明函数覆盖的。被覆盖的函数必须是虚(virtual)的,或者是动态(dynamic)的,也就是说该函数在声明时应该包含这两个指示字中的一个,比如:

 procedure Draw; virtual;

在需要覆盖的时候,只需要在子类中用override 指示字重新声明一下就可以了。

 procedure Draw; override

其中,virtual 虚类型;dynamic 动态;overload 重载;override 重写

关于方法定义关键字的说明

在过程定义声明时包括了的关键字的含义:

static 表示是静态方法;静态方法是方法的缺省类型,不能被覆盖;
virtual 表示是虚拟方法;
dynamic 表示是动态方法;
这两类方法的共同点是都可以被覆盖(override),区别仅在于运行时调用方法时的派发机制,virtual方法为执行速度做了优化,而dynamic方法则为代码大小做了优化;经常被覆盖的方法应该定义为virtual方法;
message 表示是消息处理方法;在关键字message后面的值指明了这个方法要响应的消息;用消息处理方法来响应Windows的消息,这样就不用直接来调用它;
override 表示是覆盖方法;要求祖先类中必须有该方法的定义,并且参数的顺序和类型及返值的类型必须匹配;用于扩展祖先类中的该方法而不是取代它(如不调用祖先类中的该方法则等同于取代);
overload 表示是重载方法;用于一个类中有许多同名的方法带着不同的参数表的情形;(属性的读写方法不能被重载);
abstract 表示是抽象方法;是没有实现的方法,并且必须是virtual或dynamic方法,即在abstract前面必须有virtual或dynamic关键字,其实现部分在派生类中;如
     

 procedure DoSomething; virtual; abstract;

reintroduce表示是重新声明的方法;用于方法的名称与祖先类中的某个方法名称相同的情形;

virtual 和 dynamic定义的虚函数,都是为了子类override用的,只不过它们的实现不一样vitrual 占用的空间大点,但速度快些。dynamci正好相反.

stdcall是一种调用方式,标明了参数入stack的顺序和清除的方式,具体可看Delphi的帮助.

 

class function 与 function

class function 类似于 c++中的静态方法,不需要建立类的实例就可以调用,如TYourClass.YourMethod.
function就是类成员方法,必须建立该类的实例才可以调用。

 

 

var 、out和const

 

(1)var修饰符

var 是地址传递,会修改原有的变量

var

s: string;

begin

S := 'Hello ';

ChangeSVar(s);

ShowMessage(S);

end;

// ChangeSVar 定义

procedure TForm1.ChangeSVar(var A: string);

begin

A := A + 'World';

end;

以上会输出Hello World,因为是传址,修改的是原来的A

 

(2)无任何修饰符

var

s: string;

begin

S := 'Hello';

ChangeS(s);

ShowMessage(S);

end;

// ChangeS定义

procedure TForm1.ChangeS(A: string);

begin

A := A + 'World';

end;

 

以上会输出Hello,因为方法ChangeS其实是创建了一个新的A,而输出的还是原来的A,值并没有改变

 

(3)out 修饰符

var

s: string;

begin

s := 'Hello, ';

ChangeSOut(s);// 此时S的值是'Hello,'而非'Hello,World'!,在过程 ChangeSOut 中的S的原始值被丢弃了

ShowMessage(S);

end;

// ChangeSOut定义

procedure TForm1.ChangeSOut(out A: string);

begin

A := A + 'World';

end;

以上会输出World,out仅仅接受返回的值,对out的任何输入都会被忽略。同时out传递给过程的实际参数不必进行初始化,比如对ChangeSOut的调用:

var

Tmp: string

begin

ChangeSOut(Tmp);//编译也可以通过

end;

 

(4)Const修饰符

Const修饰的参数传入之后不允许修改,如果在过程中修改参数会报错,比如:

procedure xxxx.TestConst(const A: string);

begin

A := 'ss'; //企图修改const修改的参数,会报错

end;

 



 

chr() 和 char() 区别

  • chr是个函数,参数是byte类型,负责把ascii码值转换成char,比如:chr(97)返回一个char类型’a’;
  • char是个数据类型,char() 这个格式是把一个别的数据类型强转成char类型,比如,char(97)和 char(#97)都将得到a。

 

5 变量

通常按照变量声明的范围,可以分为:全局变量,类变量,局部变量。

  • 全局变量:是指在类外声明的变量,通常这种变量是在整个工程内有效的,也就是说在整个工程中的类都可以使用。该变量的生存周期是在工程创建时有效,工程销毁时销毁。
  • 类变量:是指在类中声明的变量,这种变量在类中的方法都可以使用。其生命周期是在类创建时有效,类销毁时销毁。
  • 局部变量:是指在方法内部声明的变量,这种变量只能在方法内部使用。其生命周期也是在方法内部有效,当方法调用结束后,其内部所声明的变量也随之销毁。

 

全局变量

如果我们在应用程序一个单元中的interface关键字和implementation关键字之间的区域,定义一个全局变量,假如这个单元在别的地方被引用,那么这个单元的全局变量能够在别的地方被访问到,当然我们也可以在应用程序一个单元中的implementation关键字的后面定义全局变量,不过此时在这里定义的全局变量只能在本单元中被访问到,也就是说它是这个单元私有的,在别的单元中将不能被访问到。全局变量在应用程序的数据区分配内存,它存在于可执行模块(EXE或DLL等)的文件影像内部,在程序编译期就被决定,直到应用程序结束,全局变量所占用的内存地址是固定不变的。

全局的非指针类型,声明后自动分配内存,并初始化值。全局的指针类型,声明后不自动分配内存,值为nil。


局部变量

我们可以在函数或方法(包含过程)中定义局部变量,局部变量在应用程序的栈上进行分配,并且总是在栈上分配!局部变量的内存是在函数或方法(包含过程)被调用时分配,在函数或方法(包含过程)调用结束时其内存被释放。由于函数或方法(包含过程)在每次被调用时,栈顶可能发生变化,因此局部变量的内存地址是变化的,局部的非指针类型,声明后自动分配内存,不初始化值,值不确定(取决与别的程序对这块内存的操作)。局部的指针类型,声明后不自动分配内存,但会随机指向一个地址,所以地址不为nil

 

注:这里我们不提倡用全局变量,而尽可能的用局部变量,假如必须用到全局变量,我们可以将这个全局变量在类(比如TForm)的成员区域声明或定义。对于局部变量我们则应多加小心,尤其是它在定义时不像全局变量一样被初始化,在程序中不注意则会隐藏巨大的风险,因此在使用局部变量时,一定要先初始化后再使用!

 

 

 


相关阅读:
Top