回 帖 发 新 帖 刷新版面

主题:PASCAL一些重要的问题解答

前言

对于许多使用Pascal语言的信息学奥林匹克初学者乃至于相当部分的高手来说,Pascal语言似乎是十分简单、十分易学的。他们专注于研究算法和数据结构,而对程序设计语言和程序设计方法则几乎不屑一顾。由于现在大部分的中学计算机老师对Pascal语言和Borland Pascal程序设计的了解十分有限,导致学生们所掌握的知识十分片面,视野很狭窄,即使是基本的程序设计语言和程序设计方法也不能很好地掌握,对自己所使用的编程工具也不甚了解,产生了许多对Pascal的误解。基于许多中学生的Pascal程序设计水平过低,不能在信息学奥林匹克竞赛中很好地使用Pascal编写出优质的程序,本文就一些Pascal初学者(非信息学初学者)经常提出的与信息学奥林匹克和Pascal有关的问题进行简要的解答。本文的内容大部分都是Pascal程序设计中的常识,但不管如何,这些对取得好成绩很有帮助的常识确实是许多参加信息学奥林匹克竞赛的选手所不曾掌握的。

编程工具专题

Pascal有哪几个版本?

Pascal有5个主要的版本,分别是Unextended Pascal、Extended Pascal、Object-Oriented Extensions to Pascal、Borland Pascal和Delphi Object Pascal。其中,Unextended Pascal、Extended Pascal和Object-Oriented Extensions to Pascal是由Pascal标准委员会所创立和维护的,Unextended Pascal类似于瑞士Niklaus Wirth教授和K.Jensen于1974年联名发表的Pascal用户手册和报告,而Extended Pascal则是在其基础上进行了扩展,加入了许多新的特性,它们都属于正式的Pascal标准;Object-Oriented Extensions to Pascal是由Pascal标准委员会发表的一份技术报告,在Extended Pascal的基础上增加了一些用以支持面向对象程序设计的特性,但它属于非正式的标准。Borland Pascal和Delphi Object Pascal是由Borland公司专门为其开发的编译工具设计的Pascal语言,前者是用于DOS的Turbo Pascal系列和Windows 3.x的Turbo Pascal for Windows的传统高级语言,后者是用于Windows的Delphi和Linux的Kylix的面向对象程序设计语言,它们都不是正式的Pascal标准,具有专利性。但由于Turbo Pascal系列和Delphi功能强大并且广为流行,Borland Pascal和Delphi Object Pascal已自成为一种标准,为许多人所熟悉。

看到这里,你可能会发觉我的回答与你最初的设想不同。你原来可能是想问Turbo Pascal有几个版本,然而我却回答了Pascal语言有几个版本。这就是初学者常有的一个错误认识:Pascal是一种编程工具。实际上,Pascal是一种程序设计语言的名称(从一般意义上说,Pascal也可以是指人名,它的取名原本就是为了纪念十七世纪法国著名哲学家和数学家Blaise Pascal),而不是编程工具。刚才我是纯粹从字面意思上来回答这个问题。

回复列表 (共20个回复)

沙发

在中国的信息学奥林匹克竞赛中,过去比较常用的Pascal编程工具是Turbo Pascal。Turbo Pascal是DOS下的一种16位编程工具,在Delphi出现之前,它是世界上最多人使用的Pascal编程工具,拥有编译速度极快的先进编译器和功能强大而又简便易用的集成开发环境(IDE),在微机程序员中广为流行,正是它的出现奠定了Pascal在DOS/Windows平台上不可动摇的根基,现在常见的版本有Turbo Pascal 5.5、Turbo Pascal 6.0和Borland Turbo Pascal with Objects 7.0。Turbo Pascal 6.0与Turbo Pascal 5.5相比,主要是IDE更为强大,而其程序设计功能改变不大,只是增加了一些新的功能,例如可以内嵌asm汇编语句等。而Borland Turbo Pascal with Objects 7.0(简称Borland Pascal 7.0)则有了新的飞跃,首先是IDE进一步加强,提供了程序浏览器,然后是程序设计功能有了很大的提升,新增了一些十分有用的标准子程序,支持比较完善的面向对象程序设计功能,并提供了DOS实模式、DOS保护模式和Windows模式三种程序编译模式,能够编写出可以使用扩充内存(XMS)的保护模式应用程序或者在Windows 3.x下运行的Windows程序,另外还提供了一个对象窗口库(OWL),使用它可以快速的开发出具有一致的视窗界面(DOS或Windows 3.x)的应用程序。Borland Pascal 7.0在1992年推出,是Turbo Pascal系列在DOS下的最后版本。

现在,随着Turbo Pascal逐渐被淘汰,全国信息学奥林匹克竞赛决赛(NOI)和国际信息学奥林匹克竞赛(IOI)已经指定Free Pascal为比赛使用的Pascal编程工具。Free Pascal是由一个国际组织开发的32位Pascal编程工具,属于共享软件,可用于各种操作系统。根据编译选项的不同,它可以使用Borland Pascal兼容语法、Delphi 2 Object Pascal语法或者其它语法进行编写程序。由于它拥有32位的编译器,而且一直在更新发展中,因此它的功能比Borland Pascal更加强大,拥有许多现代程序设计的特征,但同时也很不成熟,存在很多漏洞。Free Pascal正处于发展初期,相应的函数库十分少,对程序员的吸引力远比不上拥有VCL和CLX的Delphi和Kylix。

Turbo Pascal 7.0和Borland Pascal 7.0是同一个编程工具吗?

它们属于同一套编程工具,但不是同一个程序。Borland Turbo Pascal with Objects 7.0含有两个编译程序和两个编辑程序,两个编译程序是TPC.exe和BPC.exe,两个编辑程序是Turbo.exe和BP.exe,它们分别属于Turbo Pascal 7.0和Borland Pascal 7.0的程序文件。Turbo Pascal 7.0与其更早的版本一样用于编写DOS实模式的程序,它只能在纯DOS下运行,而Borland Pascal 7.0则可以同时编写DOS实模式、DOS保护模式和Windows模式的程序,它能够在Windows下以命令行的形式运行,其IDE功能也更强大。另外,Borland Turbo Pascal with Objects 7.0还附带了一个在Windows 3.x下运行的编辑程序BPW.exe,它类似于BP.exe,但采用Windows界面,IDE功能比较少,不能调试程序。一般情况下,Borland Turbo Pascal with Objects 7.0简称为Borland Pascal 7.0。

在比赛中该如何选择合适的Pascal编程工具?

毋庸置疑,版本越高的就越好。编程工具的版本越高,意味着操作更方便,功能更强大,也就更容易编写出优质的程序,当然,前提是你要懂得如何发挥出该编程工具的长处。如果比赛中同时提供Turbo Pascal 7.0和Borland Pascal 7.0,你应该选择Borland Pascal 7.0,因为它的功能更强大。需要注意的是,Borland Pascal 7.0无法像Turbo Pascal 7.0一样可以在内存中编译和运行程序,它只能把程序编译进磁盘的文件中。如果比赛只提供软盘作为存储器,而你又无法忍受在软盘上编译程序的速度,那么可以考虑使用Turbo Pascal 7.0。

如何把我的程序编译成.exe文件?

如果你在编译或运行程序后,并没有生成相应的.exe可执行文件,那么你使用的应该就是Turbo Pascal了。Turbo Pascal默认在内存中编译和运行程序,这样当你在软盘上编写程序时就可以节省一些时间。你可以在菜单Compile -> Destination中选择Disk,这样在编译和运行程序时就会生成相应的.exe文件了。

编译模式专题

什么是编译模式?

编译模式是指编译器对程序的编译方式,以不同的编译模式编译的程序将在不同的环境中运行,只有Borland Pascal 7.0和Free Pascal支持以不同的编译模式编译程序。Borland Pascal 7.0支持3种编译模式:DOS实模式、DOS保护模式和Windows模式。Free Pascal目前支持6种编译模式:DOS(GO32V1)、DOS(GO32V2)、Freebsd、Linux、OS/2和Win32。

DOS实模式是把程序编译为实模式应用程序的编译模式,它是默认的16位DOS程序编译方式,Free Pascal不支持以DOS实模式编译程序。实模式应用程序可以使用最大640KB的DOS常规内存,内存访问速度较快,可执行文件较小,可以单独运行,但无法在DOS实模式中使用动态链接库。

DOS保护模式是把程序编译为保护模式应用程序的编译模式。保护模式应用程序可以访问计算机所有可利用的扩充内存(Extended Memory,简称XMS),它通过使用DPMI(DOS Protected Mode Interface)来在保护模式下运行。在保护模式下应用程序只能访问自己持有的内存数据,而不允许访问非法的内存段,否则将会产生错误,其访问内存的速度会比在DOS实模式下稍慢一些,能够使用动态链接库。

如何使用DOS保护模式?

只有Borland Pascal 7.0支持以DOS保护模式编译程序,你必须在菜单Compile -> Target中选择Protected mode Application后才能以DOS保护模式编译程序。由于保护模式的实现比较复杂,Borland Pascal 7.0无法调试保护模式应用程序。你可以先在DOS实模式下调试程序,在确定程序正确后才转用DOS保护模式编译。

保护模式应用程序在启动时需要调用Rtm.exe来装载保护模式系统,因此你必须确保Rtm.exe存放在应用程序所在的目录下或者DOS的默认搜索路径下,你可以在Borland Pascal 7.0的工作目录下找到该文件。在比赛时,你最好预先询问一下出题者或工作人员是否允许使用保护模式,否则你的程序在测试时可能会因缺少文件Rtm.exe而无法执行。

板凳

保护模式应用程序不允许访问不由自己持有的内存段,否则将会产生216号错误。如果你的程序在运行时出现216号错误,请检查你的程序是否在动态数据的分配和访问上出了问题。这种内存违法访问的错误在DOS实模式下可能会被隐藏起来而无法发现,因此即使你的程序不需要使用保护模式,你也可以通过保护模式来检查它是否正确。如果你需要访问ROM BIOS或视频内存,你可以使用System单元的变量Seg0040、SegA000、SegB000和SegB800来代替只在DOS实模式下可用的段地址$0040、$A000、$B000和$B800。

保护模式应用程序虽然在理论上可以利用所有的扩充内存,但实际上经常只能使用所有扩充内存的一部分。例如,如果你的计算机上配有64MB的扩充内存,那么你的程序在保护模式下运行时有时可能只能用到32MB,此时再分配内存就会产生错误。你可以通过调整System单元的变量HeapBlock和HeapLimit的值来改善扩充内存的利用情况。我的许多次测试的结果表明,把HeapBlock和HeapLimit分别赋值为65528和16382,此时扩充内存的综合利用情况最好。但要注意这两个变量只在保护模式下有效,在实模式下这两个变量并不存在。

编译指示专题

什么是编译指示?

编译指示是一种用来控制编译器特性的特殊语法的注释,它可以出现在普通注释允许出现的任何地方。编译指示从“{$”或“(*$”开始,紧接着是一个指示名,然后你可以添上必要的参数和注释,最后以“}”或“*)”结束。除了在程序的源代码中直接插入编译指示外,你也可以在IDE的菜单Options或命令行编译器中改变默认的指示。当你在IDE中编写程序时,你可以通过按下Ctrl+OO来让IDE在你的程序源代码开头插入当前的指示设置。编译指示分为开关编译指示、参数编译指示和条件编译指示三种。

开关编译指示用来打开或关闭某个编译器特性,你可以在指示字母后面加上“+”或“-”来控制编译器特性的开关,你还可以把多个开关指示以无空格的逗号分隔来组合成一个单独的编译指示注释,例如:{$B+}、{$R- Turn off range checking}、{$B+,R-,S-}。

开关编译指示分为全局指示和局部指示两种。全局指示影响到整个编译过程,它必须出现在程序或被编译单元的声明部分之前,通常紧跟在程序首部或单元首部之后。局部指示只对由指示开始到下一个相同的指示之前的编译过程有影响,它可以出现在源代码中的任何地方。

参数编译指示用来指定对编译过程有影响的参数,例如文件名和内存尺寸等。你可以在指示名后面加上空格和一个或多个以无空格的逗号分隔的参数,例如:{$I TYPES.INC}、{$M 16384,0,655360}。

条件编译指示通过定义和指定条件来控制是否编译某段源代码,例如:{$DEFINE Debug}、{$IFDEF Debug}、{$ENDIF}。

3 楼

有哪些编译指示?

Borland Pascal 7.0提供下面的开关编译指示:

指示 类型 适用环境 含义 说明
$A 全局 实模式
保护模式
Windows 对


据 $A+(默认值)
所有大于1字节的变量(不包括记录字段和数组元素)均强制从偶地址开始存放,以获得更快的访问速度。
$A-
不对齐数据,变量将简单地存放在下一可用地址。
附注
在任何情况下,每个全局变量声明部分均从偶地址开始。编译器将一直保持栈指针(SP)位于偶地址上,并在必要时分配一个不用的额外字节。
$B 局部 实模式
保护模式
Windows 布


值 $B+
编译器将生成完全的布尔表达式求值代码,因此即使整个表达式的结果已经可以确定,所有执行and和or运算的布尔表达式都将被完整求值。
$B-(默认值)
编译器将生成短路布尔表达式求值代码,因此在整个表达式的结果可以确定时将立即停止表达式求值。

$D 全局 实模式
保护模式
Windows 调


息 $D+(默认值)
编译器将为程序或单元的每个子程序生成相应的行号表,以便IDE可以调试程序和查找引发运行时错误的位置。
$D-
编译器将不生成任何调试信息。

$E 全局 实模式
保护模式
Windows 仿
真 $E+(默认值)
编译器将生成80x87数字协处理器仿真库代码,程序在没有80x87的机器上运行时可以仿真80x87,进行浮点运算。
$E-
编译器只生成更小的只在80x87存在时可用的浮点运算库,程序不能在没有80x87的机器上运行。
附注
在单元中使用$E开关没有任何效果,它只应用在程序的编译上。在$N-时将忽略$E开关。
$F 局部 实模式
保护模式
Windows 强




$F+
在$F+后实现的子程序将强制使用远调用。
$F-(默认值)
编译器将自动选择在$F-后实现的子程序的调用模式。
$G 全局 实模式
保护模式
Windows 生

2
8
6

令 $G+
编译器将使用80286附加指令来改进代码生成,程序不能在8088或8086处理器上运行。
$G-(默认值)
编译器只生成8086指令,程序可以在所有的8086系列的处理器上运行。
$I 局部 实模式
保护模式
Windows 输




查 $I+(默认值)
自动生成代码,在每次调用输入输出子程序后检查I/O返回值,当I/O返回值为非零值时终止程序并显示运行时错误讯息。
$I-
不生成检查I/O返回值的代码,你必须使用IOResult函数来检查I/O错误。
$K 全局 Windows 快


调   
$L 全局 实模式
保护模式
Windows 局




息 $L+(默认值)
生成局部符号信息,在调试程序时可以观察、修改局部变量的值和在调用栈窗口中察看子程序。
$L-
不生成局部符号信息。
附注
在$D-时将忽略$L指示。
$N 全局 实模式
保护模式
Windows 数




器 $N+
编译器生成的代码将通过使用80x87数字协处理器来执行所有的实数类型计算,并可以访问四种附加的实数类型:Single、Double、Extended和Comp。
$N-(默认值)
编译器生成的代码将调用运行时库程序来以软件方式执行所有的实数类型计算。
附注
可以使用$E+指示来仿真80x87,这样就能够在没有80x87芯片的时候访问IEEE浮点类型。
$O 全局 实模式 覆




成 $O+
代码生成器将为从一个覆盖子程序中传递字符串和集合常参到另一个覆盖子程序中的情况准备预防措施。
$O-(默认值)
不生成相应的覆盖代码。
附注
$O+指示一般与$F+指示共同使用,以满足覆盖管理器的远程调用需要。
$P 全局 实模式
保护模式
Windows 开





数 $P+
使用保留字string声明的变参将被视为开放字符串参数。开放字符串参数的实际参数可以是任何字符串类型的变量,在其子程序中,形式参数的最大长度属性将与实际参数相同。
$P-(默认值)
使用保留字string声明的变参将被视为正常的变参,这样可以保持与早期版本的Turbo Pascal的兼容性。
附注
不管$P指示如何设置,使用OpenString标识符声明的变参都将始终视为开放字符串参数。
$Q 局部 实模式
保护模式
Windows 溢


查 $Q+
生成代码为某些整数运算检查溢出错误,例如:+、-、*、Abs、Sqr、Succ和Pred。如果这些运算的结果超出了可支持的范围,将终止程序并显示运行时错误讯息。
$Q-(默认值)
不检查溢出错误。
附注
$Q指示对Inc和Dec标准子程序没有影响,它们不会检查溢出错误。
$R 局部 实模式
保护模式
Windows 范


查 $R+
对所有的数组、字符串索引号表达式和所有的对标量、子界类型变量的赋值操作进行范围检查。如果范围检查失败,将结束程序并显示运行时错误讯息。
$R-(默认值)
不进行范围检查。
$S 局部 实模式
保护模式
Windows 栈



查 $S+(默认值)
编译器将在每个子程序的开头生成代码检查是否有足够的栈空间用以存放局部变量和其它临时数据。当栈空间不足时,将结束程序并显示运行时错误讯息。
$S-
不进行栈溢出检查,在没有足够的栈空间时,对子程序的调用很可能会引起系统崩溃。
$T 局部 实模式
保护模式
Windows 指




针 $T+
@运算符的返回值类型将是类型指针,类型指针与指向不同类型的其它类型指针不相容。
$T-(默认值)
@运算符的返回值类型将是无类型指针。
$V 局部 实模式
保护模式
Windows 字





查 $V+(默认值)
进行严格的类型检查,要求形式参数和实际参数必须为相同的字符串类型。
$V-
实际参数可以是任何的字符串类型,即使它的最大长度与形式参数不相同。尽管$V-仍然被支持,但应该使用开放字符串参数。
$W 局部 Windows 堆


构   
$X 全局 实模式
保护模式
Windows 扩


法 $X+(默认值)
可以像子程序一样调用函数,并抛弃函数的返回值,但这不能用于内置的函数(在System单元里声明的函数)。同时,通过激活用于内置的PChar类型和基于零的字符数组的特殊语法来支持空结束字符串。
$X-
屏蔽扩展语法。
$Y 全局 实模式
保护模式
Windows 符




息 $Y+(默认值)
生成符号索引信息,供IDE的程序浏览器使用。
$Y-
不生成符号索引信息。
附注
$Y指示只在单元中使用,它只在$D和$L同时打开时有效。TPC和Turbo不支持$Y指示。

4 楼

Borland Pascal 7.0提供下面的参数编译指示:

指示 类型 适用环境 含义 说明
$C 属性 全局 保护模式
Windows 代



性   
$D 文本 全局 保护模式
Windows 描
述   
$I 文件名 局部 实模式
保护模式
Windows 插


件 编译时在$I指示的当前位置插入另一个文本文件的内容。最多可以嵌套插入文件15层。
附注
不能在语句部分中插入文件,整个语句部分必须位于同一个源文件中。默认的文件扩展名为.pas。
$G 单元名, 单元名... 局部 保护模式
Windows 组




断   
$L 文件名 局部 实模式
保护模式
Windows 连




件 指示编译器连接一个Intel可再定位对象文件(.obj文件)。
附注
默认的文件扩展名为.obj。
实模式
$M 栈尺寸, 堆下限, 堆上限
保护模式
$M 栈尺寸
Windows
$M 栈尺寸, 堆尺寸 全局 实模式
保护模式
Windows 内




寸 实模式(默认:$M 16384, 0, 655360)
指定程序的栈尺寸(1024~65520)、最小堆尺寸(0~655360)和最大堆尺寸(0~655360)。
保护模式(默认:$M 16384)
指定程序的栈尺寸(1024~65520)。
Windows(默认:$M 8192, 8192)
指定程序的栈尺寸(1024~65520)和数据段中的局部堆域尺寸(0~65520)。
$O 单元名 局部 实模式 覆




称   
$R 文件名 局部 保护模式
Windows 资


件   
$S 段尺寸 全局 保护模式
Windows 段




值   

Borland Pascal 7.0提供下面的条件编译指示:

指示 含义 示例
$DEFINE 名称 定义一个条件符号。 {$DEFINE Debug}
{$IFNDEF Debug}
Here won't be compiled.
{$ELSE}
{$IFOPT X+}
If $X+ is specified,
here will be compiled.
{$ENDIF}
{$ENDIF}
{$UNDEF Debug}
$ELSE 编译或忽略接下来的源代码。
$ENDIF 结束条件编译。
$IFDEF 名称 如果指定名称被定义就编译接下来的源代码。
$IFNDEF 名称 如果指定名称没有被定义就编译接下来的源代码。
$IFOPT 开关 如果指定开关为当前状态就编译接下来的源代码。
$UNDEF 名称 取消定义一个指定名称的已定义条件符号。

5 楼

Borland Pascal 7.0提供下面的预定义条件符号:

符号 含义 示例
CPU86 CPU属于80x86处理器系列。 {$IFDEF DPMI}
HeapBlock := 65528;
HeapLimit := 16382;
{$ELSE}
Set to Protected mode please.
{$ENDIF}
{$IFNDEF VER70}
Use Turbo Pascal 7.0 please.
{$ENDIF}
CPU87 存在80x87数字协处理器。
DPMI 操作环境为DOS保护模式。
MSDOS 操作系统为实模式MS-DOS或PC-DOS。
VER70 Turbo Pascal版本号为7.0。
WINDOWS 操作环境为MS-Windows。

Free Pascal也提供了大量的编译指示,但很多都用不上,这里就不给出了。

如何设置默认的编译指示?

在IDE的菜单Options -> Compiler中,你可以设置所有的开关编译指示默认值,在其中的Conditional defines编辑框中可以输入默认的条件符号,以分号分隔每个条件符号。另外,你还可以在菜单Options -> Memory sizes中设置参数编译指示$M的默认值。

默认的编译指示在编译时对所有的程序和单元都有效,因此它的作用域是全局的,当然,你可以在源代码中添加新的编译指示来覆盖默认的编译指示设置。

如何设置最优的编译指示?

在Borland Pascal 7.0中,要使程序的可执行文件最小、可用功能最多并且运行速度最高,可使用开关编译指示:{$A+,B-,D-,E-,F-,G+,I-,L-,N+,O-,P+,Q-,R-,S-,T-,V-,X+,Y-}。在这种设置下,程序在编译和运行时不进行任何可选的错误检查,也不能进行调试。要使程序进行运行时错误检查,可以使用编译指示:{$I+,Q+,R+,S+}。要使程序能够调试,可以使用编译指示:{$D+,L+,Y+}。另外,为了使程序的栈尺寸和可用堆尺寸最大,还应在主程序中添加编译指示:{$M 65520, 0, 655360}。下面是一个简单的样例:

program Noname;

{$A+,B-,E-,F-,G+,N+,O-,P+,T-,V-,X+}
{$I-,Q-,R-,S-}
{$D-,L-,Y-}
{$M 65520, 0, 655360}

begin
end.
在Free Pascal中,最优的编译指示是:{$I-,Q-,R-,S-,V-,X+}。要使程序进行运行时错误检查,可以使用编译指示:{$I+,Q+,R+,S+,V-,X+}。但应该注意,Free Pascal有许多编译选项是不能在源代码中以编译指示的方式设置的。你可以在IDE的菜单Options -> Mode中选择编译方式:Normal、Debug和Release。在程序最后完成时,应使用Release方式编译程序。另外,Free Pascal提供了参数编译指示{$MODE 兼容模式}用以设置编译时的兼容模式,即所用的语法,你可以根据你熟悉的语法进行选择:Default、Delphi、TP、FPC、OBJFPC和GPC。如果你只会Turbo Pascal语法,那么你应该选择TP,如果你熟悉Delphi的Object Pascal语法,那么你也可以选择Delphi。下面是一个简单的样例:

program Noname;

{$I-,Q-,R-,S-,V-,X+}
{$MODE Delphi}

begin
end.
如果你不能把全部的编译指示记住,或者你使用的是编译指示不完全的较低版本的Turbo Pascal,那么你可以在IDE中按下Ctrl+OO,IDE会在当前编辑文件的开头插入默认编译指示设置,你可以在其基础上进行修改。虽然大部分的最优编译指示与默认的指示相同,但在源代码中使用编译指示显得更加安全,因为它不会受默认编译指示改变的影响。

6 楼

内存分配专题

为何我的程序在递归调用子程序时会出现栈溢出错误?

调用子程序是需要分配一定的内存空间用以存放参数、函数返回值和局部变量等数据的。Pascal提供了一个全局静态的栈结构的内存段用以存放这些数据,每次调用子程序都会把所需的数据压入栈中,子程序结束后就把数据从栈中取回。由于栈的大小是在编译时设定的,不能在运行时再更改大小,因此当栈没有足够的剩余空间来存放调用子程序所需的数据时,就会出现栈溢出错误。

你可以通过参数编译指示$M来设置栈的大小,根据程序的需要来设置栈的大小是很重要的,因为栈太小则容易出现栈溢出错误,太大又会浪费内存。注意Borland/Turbo Pascal编译器是16位的编译器,你最多只能设置65520字节的栈空间。Free Pascal编译器是32位的,因此基本上没有限制。虽然也可以通过底层的方法来构造一个动态分配的栈空间,但方法比较复杂,不适合在信息学奥林匹克中使用。另外,栈大小设置在Linux下将会被忽略。

为何当我声明的全局变量的总尺寸超过64KB时会出现编译错误?

在Pascal中,一个主程序及其所有单元(包括System单元)的全局静态数据(包括全局变量、全局和局部类型常数、PChar常量和对象类型的虚拟方法表等)都被存放在一个单独的全局静态的数据段中。由于Borland/Turbo Pascal编译器是16位的编译器,这个数据段的大小不能超过64KB,否则将无法编译。解决的方法是尽可能少用全局静态变量,把一些比较大的全局静态数据改用动态方式存储,以减少全局静态空间的需求。

为何我不能定义大小超过64KB的数据类型和变量?

由于Borland/Turbo Pascal编译器是16位的编译器,它要求每个单独的数据类型和变量的大小都不能超过64KB,否则将无法编译。解决的方法是把数据分拆,并用动态方式存储分拆后的数据。例如,对于一个很大的一维数组,你可以把它拆为二维数组,然后用动态方式存储第二维数据。下面是一些简单的样例:

分拆前的数据类型   分拆后的数据类型
type  TQueue = array[0..249999] of Byte;
-> type  TList = array[0..9999] of Byte;  TQueue = array[0..24] of ^TList;

type  TRecord = record    A, B, C: array[0..9999] of Longint;  end;
-> type  TList = array[0..9999] of Longint;  TRecord = record    A, B, C: ^TList;  end;


我的程序可以申请多大的动态内存空间?

在Borland/Turbo Pascal中,如果你以DOS实模式编译程序,那么你的程序可以申请的最大动态内存空间为当前DOS常规内存剩余量和编译指示$M设置的最大堆空间这两个数的最小值,如果你以保护模式编译程序,那么的你的程序可以申请的最大动态内存空间就是扩充内存的剩余量。在Free Pascal中,要视具体环境的设置而定。注意,在很多时候,你的程序实际上并不能使用到理论上可用的最大内存空间,因为由不完全的内存释放操作产生的大量剩余内存碎片会由于每块碎片过小而无法利用。不过在信息学奥林匹克中很少会出现这种情况,因为在比赛中一般不会出现不完全的内存释放操作,许多程序甚至是不会关心内存的释放问题。

由于Borland/Turbo Pascal编译器是16位的编译器,因此它限定每次申请的内存空间的最大值为65528字节。注意,申请大小在65529至65535之间的内存空间是不安全的。

7 楼

输入输出专题

在进行文件输入输出操作时可否免去文件变量参数?

可以,方法是设置默认输入输出文件。System单元提供了两个正文文件变量Input和Output,它们是默认的输入输出文件参数,在程序启动时,这两个变量会自动指向并打开默认的输入输出设备(如键盘和显示器),在程序结束后,这两个变量会自动关闭所指向的文件。你可以通过改变这两个变量所指向的文件来自定义默认输入输出文件,下面是一个简单的样例:

var
  S: string;

procedure SetIOFiles(const InputName, OutputName: string);
begin
  Close(Input);
  Assign(Input, InputName);
  Reset(Input);
  Close(Output);
  Assign(Output, OutputName);
  Rewrite(Output);
end;

begin
  SetIOFiles('Input.txt', 'Output.txt');
  Readln(S); { inputs S from text file "Input.txt" }
  Writeln(S); { outputs S to text file "Output.txt" }
end.
在DOS下,把文件名设为空串或者“con”可以指向默认的输入输出设备(如键盘和显示器)。

注意,在Free Pascal中,正文文件变量Input和Output不会在程序结束时自动关闭所指向的文件,这可能会造成输出数据遗失,因此你需要在程序结束的地方明确地关闭它们。

函数SeekEoln、SeekEof和Eoln、Eof有什么区别?

SeekEoln和Eoln用于判断文件行结束,SeekEof和Eof用于判断文件结束,但它们之间还是有区别的。Eoln和Eof只判断当前的所在位置是否位于行结束符上或者文件尾部,而SeekEoln和SeekEof会自动跳过所有连续的空格和制表符再进行判断,因此,在执行函数SeekEoln或SeekEof后,当前的所在位置不会是空格或者制表符。

为何我的程序结束后其输出文件无内容?

这可能是由于在输出数据后没有关闭输出文件所造成的,或许是你忘了在程序结束的地方关闭文件,也或许是在关闭文件前你的程序就在某个地方终止了。Pascal在向正文文件输出数据时,不一定立即就写进磁盘里,只要剩余缓冲区足够,Pascal会先把数据复制到正文文件变量内置的缓冲区中去,直到缓冲区满或者关闭文件时才真正把缓冲区内的数据写进磁盘文件中。但你可以在输出数据后调用System单元的过程Flush(var F: Text)来强制把缓冲区内所有的数据立即写进磁盘文件中并清空缓冲区。

如何设置正文文件缓冲区的大小?

Pascal不提供直接设置缓冲区大小的功能,但提供设置缓冲区的功能。在System单元中含有一个过程SetTextBuf(var F: Text; var Buf [ ; Size: Word ] ),使用它可以为正文文件自定义缓冲区,下面是一个简单的样例:

var
  F: Text;
  Buf: array[0..4095] of Byte;

begin
  Assign(F, 'Input.txt');
  SetTextBuf(F, Buf, SizeOf(Buf)); { or SetTextBuf(F, Buf); }
  Reset(F); { or Rewrite(F); }
  ...
  Close(F);
end.
注意,SetTextBuf必须在对文件进行任何读写操作之前使用,否则将会造成数据遗失,至少要紧跟在Reset、Rewrite或Append的后面。另外,由于Pascal为每个正文文件变量都内置了一个128字节的缓冲区,因此设置尺寸小于128字节的缓冲区是毫无意义的。一般情况下,设置一个8KB或16KB左右的缓冲区就可以使正文文件读写的速度显著提高了。

语法专题

程序首部的参数Input和Output是什么意思?

它们代表默认输入输出设备,但它们只在老式的Pascal语言中有存在意义。在Borland/Turbo Pascal和Free Pascal中,程序首部不需要任何参数,但为了兼容旧式的Pascal程序,编译器会忽略程序首部的所有参数。

如何获得函数返回值的地址?

在Borland/Turbo Pascal中,只能通过函数名来设置函数返回值,编译器并不提供获取函数返回值地址的方法。虽然也可以通过底层的方法来获得函数返回值的地址,但该方法不适合在信息学奥林匹克中使用。在Free Pascal中,如果使用Turbo Pascal语法,则和Borland/Turbo Pascal一样,如果使用Object Pascal语法,则可以通过使用标识符Result来像变量一样访问函数返回值,@Result返回的就是函数返回值地址。

数值参数、变量参数和常量参数有什么区别?

数值参数、变量参数和常量参数代表三种不同传递方式的子程序参数类型,它们分别用于不同的场合,使用不同的参数类型会产生不同的子程序调用执行速度、栈空间占用量和传递效果。

子程序在调用时会在栈中为数值参数分配一块新的空间,并把实际参数的值赋给数值参数,然后数值参数就可以像局部变量一样使用。子程序结束后,数值参数所占用的栈空间会自动释放,数值参数的值的改变不会影响到实际参数。数值参数可接受的实际参数为:常量、表达式和变量。

子程序在调用时会在栈中为变量参数分配四个字节的空间,并把实际参数的地址赋给这四个字节,对变量参数的访问就是通过该四个字节组成的指针来对实际参数进行访问,其访问速度比数值参数慢一些。子程序结束后,这四个字节的空间就会自动释放,变量参数的值的改变等价于实际参数的值的改变。变量参数可接受的实际参数为:变量。

对于常量参数,子程序在调用时会根据其数据类型和大小来决定是分配一块新的空间来存放实际参数的值还是分配四个字节来存放实际参数的地址,其决定根据与实际参数是否变量无关。常量参数的访问类似于常量,只能获取它的值而不能进行赋值,唯一的例外是可以使用@操作符来获得常量参数的地址。常量参数可接受的实际参数为:常量、表达式和变量。

注意,由于文件类型比较特殊,文件类型的参数只能作为变量参数传递。

如何使两个子程序可以互相调用?

Pascal要求每个标识符在使用前必须预先定义,如果两个子程序要互相调用,那么就至少有一个子程序必须在其实现之前预先声明,即预定义子程序,这可以用保留字forward完成。forward用于预定义子程序,把它放在子程序首部的后面,那么这个子程序首部就预定义了一个子程序,而其具体的实现部分可以在其它地方给出。下面是一个简单的样例:

procedure ProcA; forward;

procedure ProcB;
begin
  ProcA;
end;

procedure ProcA;
begin
  ProcB;
end;
注意,forward不能用于预定义单元中已在接口部分声明的子程序。

8 楼

基本操作专题

如何获取随机数?

随机数是指理论上没有规律可循、在指定范围内每个数的出现几率相等、无法根据之前的数来预测下一个数的数列。一般随机数生成器的基本原理是:首先初始化一个随机种子,其初始值可以是任意的整数;在每次获取随机数时,以随机种子为基础进行某种特殊的运算,获得一个随机数并返回之,然后再对随机种子进行某种运算,改变随机种子的值。这样,就可以生成许多比较随机的数,但同一个初始值的随机种子将会生成完全相同的随机数列。

Pascal的System单元提供了两个与随机数有关的子程序:Randomize和Random。Randomize过程用于初始化随机种子,其初始值取决于当前的系统时钟。Random函数用于获取随机数,它有两种调用形式:Random,返回一个0到1之间(不包括1)的随机实数;Random(N),返回0至N之间(不包括N)的随机整数,N为Word类型整数。另外,System单元中随机种子变量的标识符为RandSeed,你也可以手动修改它。

随机数在信息学奥林匹克中可用于随机化搜索、穷举等算法,以优化其性能,也可用于在快速排序中选择关键数,以使其快速排序算法的最坏情况没有固定的相应数列。如果你希望使用了随机数的程序对同一个输入数据能有恒定的输出结果,可以设置RandSeed为一个定值。

如何使用FillChar?

System单元的FillChar过程用来把指定内存段中的所有字节赋为相同的值,它通常用来清空数据。FillChar的格式是FillChar(var X; Count: Word; Value),X是指定的内存段,如数组变量,Count是内存段的大小,即字节数,Value是填充的值,可以是Byte、Char或Boolean等单字节类型的值。如果你要把元素类型为Shortint、Integer或Longint的数组清空为0,可以把Value设为0,如果要全部元素赋为-1,可以把Value设为255。下面是一个简单的样例:

var
  P: array[0..999] of Longint;

begin
  FillChar(P, SizeOf(P), 255);
  Writeln(P[999]);
end.
如何使用Move?

System单元的Move过程用来把指定内存段的数据整块复制到另一内存段中,它通常用来成批移动数组元素。Move的格式是Move(var Source, Dest; Count: Word),Source是数据源,Dest是目标内存段,Count是复制的字节数。你不必担心源内存段会和目标内存段重叠或者重合,在这种情况下Move仍然能够正确工作,参考下面的样例:

var
  I: Integer;
  P: array[0..9] of Longint;

begin
  for I := 0 to 9 do P[I] := I;
  Move(P[4], P[2], 4 * 6);
  for I := 0 to 9 do Writeln(P[I]: 4);
end.
特殊技术专题

如何实现卡时?

卡时是一种让程序在运行了指定长度的时间以后可以自动终止运行并输出结果的技术,其基本原理通常是在程序启动时获取启动时间,然后在程序重复执行的地方获取当前时间,通过计算时间差判断程序已经运行了多久,当程序已运行的时间达到或接近指定的时间后立即输出已知的最优结果并且中断程序。卡时技术通常用在使用穷举或搜索的程序中。

在Turbo Pascal中,可以通过访问MemL[Seg0040:$006C]来获取当前时间,它返回的是当日零时到现在所经过的时间,单位约为55毫秒(约1/18.2秒)。下面是一个使用卡时技术的样例程序:

{$N+}

var
  StartTime: Longint;
  I, J: Extended;

begin
  StartTime := MemL[Seg0040:$006C];
  I := 0;
  J := 0;
  repeat
    if I * I - 1234567 * I + 777 < J * J - 1234567 * J + 777 then J := I;
    I := I + 1;
  until MemL[Seg0040:$006c] - StartTime >= 500 div 55;
  Writeln('x^2 - 1234567x + 777 has a minimum result when x = ', J: 0: 0);
end.
上面的程序在运行0.5秒以后将自动输出结果并结束运行。注意,该程序如果跨越零时运行,将无法自动结束程序。如果你希望你的程序在跨越零时运行时仍然可以自动结束,请自行优化你的程序。

在Free Pascal中,很难像Turbo Pascal一样直接访问内存(除非你使用DOS模式编译),通常只能通过调用系统函数来获取当前时间,但在比赛中一般是不允许程序调用系统函数的,因此很难实现卡时。


============================================================================
以上是我摘抄而来。。。。。。。。。。。。
希望对大家有用。

9 楼

好好~~~~~~~~~~~~~~帖啊!!!!!!!!!
太棒了,继续努力啊!!!!!!!

10 楼

好的~~~

我来回复

您尚未登录,请登录后再回复。点此登录或注册