面向程序员的现代对象PASCAL入门

2020-07-06 07:50:33

这是来自米哈利斯的原始文档的修改版本,因为我们(http://newpascal.org/和http://synopse.info/的作者)更喜欢不带";通用";/";专业";关键字的";模式。

市面上有很多关于Pascal的书籍和资源,但其中太多只谈论旧的Pascal,没有类、单元或泛型。

所以我写了这个简单的介绍,我称之为现代对象Pascal。大多数使用它的程序员并不真正称它为现代对象PASCAL,我们只是称它为我们的PASCAL。但在介绍该语言时,我觉得有必要强调它是一种现代的、面向对象的语言。自从很久以前很多人在学校里学到的旧的(Turbo)Pascal语言以来,它有了很大的发展。就功能而言,它非常类似于C++、Java或C#。

它具有您期望的所有现代功能 - 类、单元、接口、泛型…。​。

它还有优秀的、可移植的、开源的编译器,称为免费PASCAL编译器,http://freepascal.org/。以及附带的集成开发环境(编辑器、调试器、可视化组件库、表单设计器),称为Lazarus http://lazarus.freepascal.org/。我自己是城堡游戏引擎https://castle-engine.io/的创建者,这是一个很酷的便携式3D和2D游戏引擎,使用这种语言可以在许多平台(Windows,LINUX,MacOSX,Android,iOS,Web plugin)上创建游戏。

本介绍主要面向已有其他语言经验的程序员。我们不会在这里讨论一些普遍概念的含义,比如什么是类,我们只会展示如何用Pascal来实现它们。

{$mode Delphi}//仅在所有现代源程序MyProgram中使用此行;//将此文件另存为myProgram。lpr Begin Writeln(';Hello world!';);end。

如果您使用命令行FPC,只需创建一个新文件myProgram.lpr并执行FPC myProgram.lpr即可。

如果您使用拉撒路,请创建一个新项目(菜单项目→新建项目→简单程序)。将其另存为myprogram,并将此源代码粘贴为主文件。使用菜单项Run→Compile进行编译。

这是一个命令行程序,因此在任何一种情况下, - 都只需从命令行运行编译后的可执行文件。

本文的其余部分将讨论Object Pascal语言,因此不要期望看到比命令行更奇特的东西。如果您想看到一些很酷的东西,只需在Lazarus(项目→新项目→应用程序)中创建一个新的图形用户界面项目。瞧, - 是一个可以工作的图形用户界面应用程序,跨平台,到处都是本机外观,使用一个舒适的可视组件库。Lazarus和免费Pascal编译器附带了许多现成的单元,用于联网、图形用户界面、数据库、文件格式(xml、json、图像…。​)、线程以及您可能需要的其他一切。我早些时候已经提到了我的酷城堡游戏引擎:)。

{$mode Delphi}程序MyProgram;过程MyProcedure(const A:整数);Begin Writeln(';A+10为:';,A+10);End;Function MyFunction(Const S:String):String;Begin Result:=S+&39;字符串自动管理';End;var X:Single;Begin Writeln(MyFunction(';注意:';));MyProcedn。始终使用浮点型结果,使用";div";表示整数除法X:=15/5;Writeln(';X现在是:';,X);//科学记数法Writeln(';X现在是:';,X:1:2);//2个小数位结束。

要从函数返回值,请将某些内容赋给Magic Result变量。您可以自由读取和设置结果,就像本地变量一样。

函数MyFunction(const S:string):String;BEGIN RESULT:=S+';SOURCE';;RESULT:=RESULT+';更多!';;RESULT:=RESULT+';等等!';;END;

您还可以将函数名(如上面示例中的MyFunction)视为可以赋值的变量。但我不鼓励在新代码中使用它,因为它在赋值表达式的右侧使用时看起来很可疑。当您想要读取或设置函数结果时,只需始终使用RESULT。

如果您想递归调用函数本身,当然可以这样做。如果要递归调用无参数函数,请确保指定圆括号(即使在Pascal中通常可以省略无参数函数的圆括号),这使得对无参数函数的递归调用与访问此函数的当前结果不同。就像这样:

函数ReadIntegersUntilZero:String;var i:整数;Begin Readln(I);Result:=IntToStr(I);如果I<;>;0,则Result:=Result+';';+ReadIntegersUntilZero();End;

您可以调用exit在过程或函数到达最终结束之前结束它的执行。如果在函数中调用无参数Exit,它将返回您设置为结果的最后一项内容。您还可以使用EXIT(X)构造来设置函数RESULT和EXIT NOW - ,这就像类C语言中的Return X构造一样。

函数AddName(Const ExistingNames,newname:string):String;Begin if ExistingNames=';';Then Exit(Newname);Result:=ExistingNames+';,';+newName;end;

使用If..。那么或者如果..。然后..。否则,在满足某些条件时运行一些代码。与C类语言不同,在Pascal中,您不必将条件括在括号中。

变量A:整数;B:布尔;如果A&>0,则开始做某事;如果A&>0,则开始做某事;如果A&>;0,则开始做某事;AndDoSomethingMore;End;如果A&>;10,则开始做其他事情;如果A&>10,则做其他事情;//等价于以上B:=A&>10;如果B,则开始做其他事情。

虽然上面使用嵌套IF的示例是正确的,但通常最好将嵌套IF放在BEGIN…中。这种情况下的​结束块。这使得代码对读者来说更明显,即使您搞砸了缩进,代码也会很明显。下面是该示例的改进版本。当您在下面的代码中添加或删除一些其他子句时,很明显它将应用于哪个条件(适用于A测试或B测试),因此不容易出错。

如果A<;&>0,则开始;如果B<;&>0,则AIsNonzeroAndBToo否则AIsNonzeroButBIsZero;End;

逻辑运算符称为AND、OR、NOT、XOR。它们的含义可能很明显(搜索";EXCLUSIVE或";如果您不确定xor做什么:)。它们接受布尔参数,然后返回一个布尔值。当两个参数都是整数值时,它们还可以充当按位运算符,在这种情况下,它们返回整数。

关系(比较)运算符是=、<;>;、>;、<;、<;=、>;=。如果您习惯了类似C的语言,请注意,在Pascal中,您使用单个相等字符A=B比较两个值(检查它们是否相等)(与使用A==B的C不同)。Pascal中的特殊赋值运算符是:=。

逻辑(或按位)运算符的优先级高于关系运算符。因此,您可能需要在某些表达式两边使用括号。

VAR A,B:整数;如果A=0且B<;>;0开始,则.//错误示例。

上面的代码编译失败,因为编译器看到的是逐位和内部:(0和B)。

变量A,B:整数;BEGIN IF(A=0)AND(B<;>;0)THEN.。

如果MyFunction(X)返回FALSE,则表达式的值是已知的(FALSE的值以及始终为FALSE的值),并且MyOtherFunction(Y)根本不会被执行。

类似的规则是for or表达式。在这里,如果已知表达式为真(因为第一个操作数为真),则不计算第二个操作数。

如果应该根据某个表达式的值执行不同的操作,则案例..。的..。END语句非常有用。

case SomeValue为0:做某事;1:做某事;2:开始IfItsTwoThenDothis;AndAlsoDothis;End;3.。10:DoSomethingInCaseItsInThisRange;11、21、31:AndDoSomethingForTheseSpecialValues;否则DoSomethingInCaseOfUnexpectedValue;end;

ELSE子句是可选的。如果没有匹配的条件,也没有其他条件,那么什么都不会发生。

在您来自类C语言中,将此语句与这些语言中的switch语句进行比较,您会注意到没有自动失败。这在帕斯卡语中是一种刻意的祝福。您不必记住放置休息说明。在每次执行中,最多只能执行案例的一个分支,仅此而已。

Pascal中的枚举类型是一种非常好的、不透明的类型。您可能会比其他语言中的枚举更频繁地使用它:)。

惯例是在枚举名称前面加上一个两个字母的名称快捷键,因此AK=动物种类的快捷键。这是一个有用的约定,因为枚举名称位于单元(全局)名称空间中。因此,通过在它们前面加上AK前缀,您可以最大限度地减少与其他标识符发生冲突的可能性。

名字上的冲突并不会让人目瞪口呆。不同的单位定义相同的标识符是可以的。但不管怎样,尽量避免冲突是个好主意,使代码易于理解和grep。

您可以通过指令{$scope edenum on}避免将枚举名称放在全局名称空间中。这意味着您必须访问由类型名限定的它们,如TAnimalKind.akDuck。在这种情况下,不再需要AK前缀,您可能只会将枚举称为Duck,Cat,Dog。这类似于C#枚举。

枚举类型是不透明的,这意味着不能仅将其赋值给整数或从整数赋值。但是,对于特殊用途,您可以使用Ord(MyAnimalKind)将枚举强制转换为int,或者类型转换TAnimalKind(MyInteger)将int强制转换为枚举。在后一种情况下,请确保首先检查MyInteger是否在良好范围内(0到Ord(High(TAnimalKind)。

键入TArrayOfTenStrings=array[0..。9]的字符串;TArrayOfTenStrings1Based=array[1..。10]字符串;TMyNumber=0..。9;TAlsoArrayOfTenStrings=array[TMyNumber]of String;TAnimalKind=(akDuck,akCat,akDog);TAnimalNames=array[TAnimalKind]of String;

类型TAnimalKind=(akDuck,akCat,akDog);TAnimals=TAnimalKind集合;var A:TAnimals;Begin A:=[];A:=[akDuck,akCat];A:=A+[akDog];A:=A*[akCat,akDog];Include(A,akDuck);Exclude(A,akDuck);end;

{$mode Delphi}{$R+}//范围检查打开-用于调试var MyArray:array[0..。9]of Integer;i:Integer;Begin//Initialize for i:=0 to 9 do MyArray[i]:=i*i;//show for i:=0 to 9 do Writeln(';Square is';,MyArray[i]);//i:=Low(MyArray)to High(MyArray)do Writeln(';Square is';,MyArray[i]);//同上i:=0;虽然I<;10确实开始于Writeln(';Square is';,MyArray[i]);i:=i+1;//或";i+=1";或";Inc(I);End;//执行与上面相同的操作:=0;重复Writeln(';Square is';,MyArray[i]);Inc(I);直到I=10;;//对MyArray do Writeln(';Square is';,i);end中的i执行上述操作。

循环条件具有相反的含义。在这段时间里..。你告诉它什么时候继续,但重复一遍吗?直到你告诉它什么时候该停。

如果重复,则在开始时不检查条件。因此,重复循环始终至少运行一次。

FOR I:=..。为了..。Do…。​构造它类似于类似C的For循环。然而,它受到更多的约束,因为您不能指定任意的操作/测试来控制循环迭代。这严格适用于在连续数字(或其他序数类型)上迭代。您拥有的唯一灵活性是可以使用downto而不是to,以使数字向下。

作为交换,它看起来很干净,并且在执行中非常优化。具体地说,在循环开始之前,只计算一次下限和上界的表达式。

请注意,由于可能的优化,循环计数器变量(在本例中为i)的值在循环结束后应视为未定义。在循环之后访问i的值可能会导致编译器警告。除非您通过Break或Exit提前退出循环:在这种情况下,计数器变量保证保留最后一个值。

这是为了我在..。做..。与许多现代语言中的foreach构式相似。它可以智能地在许多内置类型上运行:

Var Animals:TAnimals;AK:TAnimalKind;Begin Animals:=[akDog,akCat];对于AK in Animals Do.。

{$mode Delphi}使用SysUtils,FGL;type TMyClass=class I,Square:整数;end;TMyClassList=TFPGObjectList<;TMyClass>;;var list:TMyClassList;C:TMyClass;i:Integer;Begin List:=TMyClassList.Create(True);//true=拥有子级try for i:=0到9 Do Begin C:=TMyClass.Create对于列表中的C,请执行Writeln(';C.I,';的正方形是';,C.Square);终于FreeAndNil(列表)结束;结束。

我们还没有解释类的概念,所以最后一个示例对您来说可能不是很明显,但 - 继续,稍后会有意义的:)。

要简单地用Pascal输出字符串,可以使用Write或Writeln例程。后者会自动在结尾处添加换行符。

这是用帕斯卡编写的魔术例程。它接受数量可变的参数,它们可以有任何类型。它们在显示时都会转换为字符串,并使用指定填充和数字精度的特殊语法。

Writeln(';Hello world!';);Writeln(';您可以输出整数:';,3*4);Writeln(';您可以填充整数:';,666:10);Writeln(';您可以输出浮点数:';PI:1:4);

要在字符串中显式使用换行符,请使用LineEnding常量(来自FPC RTL)。(城堡游戏引擎还定义了一个较短的NL常量。)。Pascal字符串不解释任何特殊的反斜杠序列,因此编写

请注意,这将仅在控制台应用程序中起作用。确保在主程序文件中定义了{$apptype控制台}(而不是{$apptype GUI})。在某些操作系统上,这实际上无关紧要,并且将始终有效(Unix),但在某些操作系统上,试图从GUI应用程序编写内容是错误的(Windows)。

在城堡游戏引擎中:使用WritelnLog或WritelnWarning,Never Writeln打印调试信息。它们将总是指向一些有用的输出。Unix上的标准输出。在Windows GUI应用程序上,日志文件。Android上的Android日志工具(使用ADB logcat时可见)。Writeln的使用应该仅限于编写命令行应用程序(如3D模型转换器/生成器)并且知道标准输出可用的情况。

要将任意数量的参数转换为字符串(而不仅仅是直接输出它们),您有几个选择。

可以使用IntToStr和FloatToStr等专用函数将特定类型转换为字符串。此外,您只需将字符串相加,就可以在Pascal中连接字符串。因此,您可以创建如下字符串:';我的int编号是';+IntToStr(Myint)+';,PI的值是';+FloatToStr(PI)。

优点:绝对灵活。有许多XxxToStr重载版本和朋友(如FormatFloat),涵盖多种类型。

另一个优点:与反向功能一致。要将字符串(例如,用户输入)转换回整数或浮点数,可以使用StrToInt、StrToFloat和Friends(如StrToIntDef)。

缺点:许多XxxToStr调用和字符串的长连接看起来并不美观。

FORMAT函数,与FORMAT(';%d%f%s';,[Myint,MyFloat,MyString])类似使用。这类似于类C语言中的sprintf函数。它将参数插入到模式中的占位符中。占位符可以使用特殊语法来影响格式,例如,%.4f会导致小数点后有4位数字的浮点格式。

优点:模式字符串与参数的分离看起来很清楚。如果您需要在不接触参数的情况下更改模式字符串(例如,在翻译时),您可以很容易地做到这一点。

另一个优点是:没有编译器魔力。您可以使用相同的语法在自己的例程中传递任意类型的任意数量的参数(将参数声明为常量数组)。然后,您可以将这些参数向下传递到Format,或者解构参数列表并对它们执行任何您喜欢的操作。

缺点:编译器不检查模式是否与参数匹配。使用错误的占位符类型将导致运行时出现异常(EConvertError异常,而不是像分段错误这样令人讨厌的异常)。

WriteString(目标字符串,…。​)例程的行为与写入(…)非常相似。​),只是将结果保存到目标字符串。

优点:它支持WRITE的所有功能,包括特殊的格式语法,如PI:1:4。

缺点:格式化的特殊语法是编译器的魔力,专门为这样的例程实现的。这有时很麻烦,例如,您不能创建自己的例程MyStringForMatter(…。也允许使用PI:1:4这样的特殊语法。出于这个原因(也因为它在主要的​编译器中没有实现很长时间),这种构造并不是很流行。

单元允许您对通用内容(任何可以声明的内容)进行分组,以供其他单元和程序使用。它们等同于其他语言的模块和包。它们有一个接口部分,您可以在那里声明其他单元和程序可以使用的内容,然后声明实现。将单元MyUnit保存为myunit.pas(小写,扩展名为.pas)。

{$mode Delphi}单元MyUnit;接口过程MyProcedure(const A:整数);函数MyFunction(const S:String):string;实现过程MyProcedure(const A:整数);Begin Writeln(';A+10 is:';,A+10);End;Function MyFunction(Const S:String):String;Begin Result:=S+';字符串自动管理';;End;End。

最终程序保存为myProgram.lpr文件(lpr=Lazarus程序文件;在Delphi中您将使用.dpr)。请注意,其他约定在这里也是可能的,例如,一些项目仅使用.pas作为主程序文件,一些项目使用.pp作为单元或程序。我建议对单元使用.pas,对FPC/Lazarus程序使用.lpr。

一台设备也可以使用另一台设备。另一台设备可以在接口部分使用,也可以仅在实施部分使用。前者允许定义新的公共内容(过程、类型…。(​:行情)放在另一个单位的东西上.。后者的限制更大(如果您只在实现部分使用单元,则只能在实现中使用它的标识符)。

{$mode Delphi}单元AnotherUnit;接口使用类;{";TComponent";类型(类)在类单元中定义。这就是为什么我们必须使用上面的类单元。}Procedure DoSomethingWithComponent(var C:TComponent);Implementation UsUtils;Procedure DoSomethingWithComponent(var C:TComponent);Begin{FreeAndNil过程在SysUtils单元中定义。因为我们在实现中只引用了它的名称,所以在实现一节中使用SysUtils单元是可以的。}FreeAndNil(C);End;End。

接口中不允许有循环单元依赖关系。也就是说,两台设备不能在接口部分相互使用。原因是,为了理解单元的接口部分,编译器必须首先理解它在中间使用的所有单元。

..