pascal_pascal voc - CSDN
精华内容
参与话题
  • Pascal入门

    2020-02-26 17:05:46
    1. 常用数据类型 数据类型 关键字 整数类型 Integer 实数类型 Double 字符类型 Char 字符串类型 String 布尔类型 Boolean 2. 常用运算符 运算符 符号 加、减、乘、除 ...左移...

    1. 常用数据类型

    数据类型 关键字
    整数类型 Integer
    实数类型 Double
    字符类型 Char
    字符串类型 String
    布尔类型 Boolean

    2. 常用运算符

    运算符 符号
    加、减、乘、除 +、-、*、/
    整除 Div
    求余 Mod
    与运算 AND
    非运算 NOT
    或运算 OR
    异或运算 XOR
    左移 SHL
    右移 SHR

    3. 三种语句

    语句名称 标志(ln表示换行) 例子
    输入语句 Read、Readln Read(x)
    输出语句 Write、Writeln Write(x)
    赋值语句 := x:=0;

    4. 条件语句

    • if语句
    if <条件> then [<语句1>][else <语句2>];
    
    例:
      if (x>0) and (y>0) then
        z:=x+y     	//注意:这里不需要加分号
      else
        z:=x-y;		//可以继续嵌套if then else语句
    
    • case语句
     case <选择器表达式> of
        <情况常量表1> : <语句1>;
        .
        .
        .
        <情况常量表n> : <语句n>;
        else
          <其它语句>;
      end;:
      case par of   
        0: writeln('Hello');
        1: writeln('world');
        else
          writeln('Hello world!');
      end;
    

    5. 循环语句

    • while 语句
    while <条件> do
        循环体;       //循环体用begin...end括起来:
      while n<=100 do
        begin
          sum:=sum+n;
          n:=n+1;
        end;
    
    • repeat 语句
    repeat
       循环体;
      until <条件>;:
    program Test;
    
    var 
    	sum,n:Integer;
    
    begin
    	sum:=0;
    	n:=0;
    	
    	repeat
         sum:=sum+n;
         n:=n+1;
    	until n>=100;
    	
    	writeln(sum)
    end.
    
    • for 语句
     for <循环变量>=<初值> {to|down} <终止> do
        begin
          循环体;
        end::
      for i:=0 to 100 do
        begin
          sum:=sum+i;
        end:
    

    6. 数组

    var 数组名:array[1..n] of integer;:
    var a:array[1..10] of integer;   //定义一个整型的数组a[10]
    

    PS:Pascal在线调试工具

    展开全文
  • Pascal基础教程

    万次阅读 2014-11-11 14:13:15
    第一课 初识Pascal语言  信息学奥林匹克竞赛是一项益智性的竞赛活动,核心是考查选手的智力和使用计算机解题的能力。选手首先应针对竞赛中题目的要求构建数学模型,进而构造出计算机可以接受的算法,之后要写出...

    第一课 初识Pascal语言

      信息学奥林匹克竞赛是一项益智性的竞赛活动,核心是考查选手的智力和使用计算机解题的能力。选手首先应针对竞赛中题目的要求构建数学模型,进而构造出计算机可以接受的算法,之后要写出高级语言程序,上机调试通过。程序设计是信息学奥林匹克竞赛的基本功,在青少年朋友参与竞赛活动的第一步必须掌握一门高级语言及其程序设计方法。

    一、Pascal 语言概述

      PASCAL语言也是一种算法语言,它是瑞士苏黎世联邦工业大学的N.沃思(Niklaus Wirth)教授于1968年设计完成的,1971年正式发表。1975年,对PASCAL语言进行了修改,作为"标准PASCAL语言"。

      PASCAL语言是在ALGOL 60的基础上发展而成的。它是一种结构化的程序设计语言,可以用来编写应用程序。它又是一种系统程序设计语言,可以用来编写顺序型的系统软件(如编译程序)。它的功能强、编译程序简单,是70年代影响最大一种算法语言。

    二、Pascal 语言的特点

      从使用者的角度来看,PASCAL语言有以下几个主要的特点:

      ⒈它是结构化的语言。PASCAL语言提供了直接实现三种基本结构的语句以及定义"过程"和"函数"(子程序)的功能。可以方便地书写出结构化程序。在编写程序时可以完全不使用GOTO语句和标号。这就易于保证程序的正确性和易读性。PASCAL语言强调的是可靠性、易于验证性、概念的清晰性和实现的简化。在结构化这一点上,比其它(如BASIC,FORTRAN77)更好一些。

      ⒉有丰富的数据类型。PASCAL提供了整数、实型、字符型、布尔型、枚举型、子界型以及由以上类型数据构成的数组类型、集合类型、记录类型和文件类型。此外,还提供了其它许多语言中所没有的指针类型。沃思有一个著名的公式:"算法+数据结构=程序"。指出了在程序设计中研究数据的重要性。丰富的数据结构和上述的结构化性质,使得PASCAL可以被方便地用来描述复杂的算法,得到质量较高的程序。

      ⒊能适用于数值运算和非数值运算领域。有些语言(如FORTRAN 66,ALGOL 60)只适用于数值计算,有些语言(如COBOL )则适用于商业数据处理和管理领域。PASCAL的功能较强,能广泛应用于各种领域。PASCAL语言还可以用于辅助设计,实现计算机绘图功能。

      ⒋PASCAL程序的书写格式比较自由。不象FORTRAN和COBOL那样对程序的书写格式有严格的规定。PASCAL允许一行写多个语句,一个语句可以分写在多行上,这样就可以使PASCAL程序写得象诗歌格式一样优美,便于阅读。

      由于以上特点,许多学校选PASCAL作为程序设计课程中的一种主要的语言。它能给学生严格而良好的程序设计的基本训练。培养学生结构化程序设计的风格。但它也有一些不足之处,如它的文件处理功能较差等。三、Pascal语言程序的基本结构

      任何程序设计语言都有着一组自己的记号和规则。PASCAL语言同样必须采用其本身所规定的记号和规则来编写程序。尽管不同版本的PASCAL语言所采用的记号的数量、形式不尽相同,但其基本成分一般都符合标准PASCAL的规定,只是某些扩展功能各不相同罢了。下面我们首先来了解Pascal语言的程序基本结构。

      为了明显起见先举一个最简单的PASCAL程序例子: 【例1】


      从这个简单的程序可以看到:

      ⒈一个PASCAL程序分为两个部分:程序首部和程序体(或称分程序)。

      ⒉程序首部是程序的开头部分,它包括:
      ⑴程序标志。用"program"来标识"这是一个PASCAL 程序"。PASCAL规定任何一个PASCAL程序的首部都必须以此字开头。在turbo pascal语言中,首部也可省略。
      ⑵程序名称。由程序设计者自己定义,如例中的exam1。
      在写完程序首部之后,应有一个分号。

      ⒊程序体是程序的主体,在有的书本里也称"分程序"。程序体包括说明部分(也可省略)和执行部分两个部分。
      ⑴说明部分用来描述程序中用到的变量、常量、类型、过程与函数等。本程序中第二行是"变量说明",用来定义变量的名称、类型。
      PASCAL规定,凡程序中用到所有变量、符号常量、数组、标号、过程与函数、记录、文件等数据都必须在说明部分进行定义(或称"说明")。也就是说,不允许使用未说明先使用。
      ⑵执行部分的作用是通知计算机执行指定的操作。如果一个程序中不写执行部分,在程序运行时计算机什么工作也不做。因此,执行部分是一个PASCAL程序的核心部分。
      执行部分以"begin"开始,以"end"结束,其间有若干个语句,语句之间以分号隔开。
    执行部分之后有一个句点,表示整个程序结束。

      ⒋PASCAL程序的书写方法比较灵活。当然,书写不应以节省篇幅为目的,而应以程序结构清晰、易读为目的。在编写程序时尽量模仿本书中例题程序格式。

      ⒌在程序中,一对大括号间的文字称为注释。注释的内容由人们根据需要书写,可以用英语或汉语表示。注释可以放在任何空格可以出现的位置。执行程序时计算机对注释不予理睬。

    四、Turbo Pascal语言系统的使用

      目前,常用的Pascal语言系统有Turbo Pascal7.0与Borland Pascal 7.0,下面我们就来学习Turbo Pascal 7.0系统的使用。 1. 系统的启动

      在运行系统目录下的启动程序TURBO.EXE,即可启动系统。屏幕上出现如图1所示的集成环境。

    2. Turbo Pascal系统集成环境简介

      最顶上一行为主菜单。中间蓝色框内为编辑窗口,在它个编辑窗口内可以进行程序的编辑。最底下一行为提示行,显示出系统中常用命令的快捷键,如将当前编辑窗口中文件存盘的命令快捷键为F2,获得系统帮助的快捷键为F1,等等。  

      

    3. 新建程序窗口

      按F10进行主菜单,选择FILE菜单,执行其中New命令。就可建立一个新的程序窗口(默认文件名为Noname00.pas或Noname01.pas等)。

    4. 程序的输入、编辑与运行

      在当前程序窗口中,一行一行的输入程序。事实上,程序窗口是一个全屏幕编辑器。所以对程序的编辑与其它编辑器的编辑方法类似,这里不再重复。

      当程序输入完毕之后,一般要先按Alt+F9(或执行compile菜单中compile命令)对程序进行编译。如果程序有语法错误,则会在程序窗口的第一行处显示第一个红色错误信息。若无语法错误,则窗口正中央会出现一个对话框,提示编译成功。接下来,我们可以运行程序了。
    程序的运行可以通过按ALT+R打开RUN菜单中的RUN命令,或直接按快捷键CTRL+F9。则可以在用户窗口中输出运行结果。通常在程序运行结束后系统回到Pascal系统的集成环境,因此要查看运行结果,要按ALT+F5将屏幕切换到用户屏幕。

    5.程序的保存与打开

      当我们想把程序窗口中的程序存入磁盘时,可以通过按F2键(或执行File菜单中的save命令)来保存程序。第一次保存文件时屏幕上会出现一个对话框要求输入文件名(默认扩展名为.pas)。

      当我们要将磁盘上的程序文件中的PASCAL程序装入窗口时,可按F3(或执行File菜单中的Open命令)来装入程序,此时系统也会弹出一个对话框要求输入要打开的文件名,或直接在文件对话框列表中选择所要的文件,然后回到打开文件。

    五、第一个程序

      下面程序在运行时,会提示输入一个圆的半径,然后会在屏幕上画一个圆。按回车后程序结束回到程序窗口。

    Program ex1;
    Uses graph;
    Var Gm,Gd,R :integer;
    Begin
    Gd:=0;
    Write('Please enter the radius:');readln(R);
    Initgraph(Gm,Gd,' ');
    Setcolor(Green);
    Circle(320,240,R);
    Readln;
    Closegraph;
    End.

      注意,如果上面程序运行时会出现初始化图形错误,请将系统目录下BGI子目录EGAVGA.BGI和UNITS子目录中的Graph.tpu拷贝到系统目录下BIN目录即可。

      请输入上面的程序,并练习将其存盘、打开与运行上面程序。

    第二课 赋值语句、输出语句

    上节课,我们学习了Pascal语言的程序基本结构,在一个程序中,所有的操作都由执行部分来完成,而执行部分又都是由一个个语句组成的。因此,下面开始我们要学习pascal语言的基本语句,并且在学习过程中逐步学会程序设计的基本方法。
      这节课我们要学习两种语句,即赋值语句与输出语句。在语句学习之前我们要先了解一些pascal语言的基础知识。

    一、 常量、变量与算术表达式

    (一)常量

      在程序运行过程中,其值不能被改变的量称为常量。如123,145.88,'abc',true等。

      ⒈整型常量

    整型常量采用我们平常使用的十进制整数表示。如138,0,-512等都是整型常量,而18.或18.0都不是整型常量。
    pascal中有一个标准标识符Maxint,它代表所使用的计算机系统允许的最大整型数,而最小的整型数即为-Maxint-1。

    一个整型数据用来存放整数。Turbo Pascal支持五种预定义整型,它们是shortint(短整型)、 integer(整型)、 longint(长整型)、 byte(字节型)和 word(字类型),Turbo Pascal分别用相同的名字作为他们的表识符。每一种类型规定了相应的整数取值范围以及所占用的内存字节数。

    类型          数值范围                    占字节数            格式
    shortint     -128..128                       1              带符号8位
    inteter      -32768..32767                   2              带符号16位
    longint      -2147483648..2147483647         4              带符号32位
    byte         0..255                           1             带符号8位
    word         0..65535                         2             带符号16位

    Turbo Pascal规定了两个预定义整型常量表识符maxint和maxlonint,他们各表示确定的常数值,maxint为32767, maxlongint为2147483647,他们的类型分别是integer 和longint。

      ⒉实型常量

      实型常量包括正实数、负实数和实数零。pascal中表示实型常量的形式有两种。

      ⑴十进制表示法

      这是人们日常使用的带小数点的表示方法。
      如0.0,-0.0,+5.61,-8.0,-6.050等都是实型常量,而0.,.37都不是合法的实数形式。

      ⑵科学记数法

      科学记数法是采用指数形式的表示方法,如1.25×105可表示成1.25E+05。在科学记数法中,字母"E"表示10这个"底数",而E之前为一个十进制表示的小数,称为尾数,E之后必须为一个整数,称为"指数"。 如-1234.56E+26 , +0.268E-5 , 1E5是合法形式,而.34E12 , 2.E5 , E5 ,E,1.2E+0.5都不是合法形式的实数。

      无论实数是用十进制表示法还是科学表示法,它们在计算机内的表示形式是一样的,总是用浮点方式存储。

    和整数相比,实数能表示的范围大得多,但值得注意的是实数的运算整数的运算速度慢且无法像整数那样精确表示,只能近似表示。

    一个实型数据用类存放实数。Turbo Pascal支持五种预定义实型,它们是real(基本实型)、 single(但精度实型)、double(双精度实型)、extended(扩展实型)、comp(装配实型),Turbo Pascal分别用相同的名字作为他们的表识符。每一种类型规定了相应的实数取值范围、所占用的内存字节数以及它们所能达到的精度。

    类型             数值范围             占字节数           有效位数
    real           2.9e-39..1.7e38           6               11..12
    single         1.5e-45..3.4e38           4                7..8
    double         5.0e-324..1.7e308         8                15..16
    extended       3.4e-4932..1.1e4932       10               19..20
    comp           -2**63+1..2**63-1         8                19..20

    Turbo Pascal支持两种用于执行实型运算的代码生成模式:软件仿真模式和80x87浮点模式。除了real可以在软件仿真模式下直接运行以外,其他类型必须在80x87浮点模式下运行。

    ⒊字符常量

      在Pascal语言中,字符常量是由单个字符组成,所有字符来自ASCII字符集,共有256个字符。在程序中,通常用一对单引号将单个字符括起来表示一个字符常量。如:'a','A','0'等。特殊地,对于单引号字符,则要表示成''''。对于ASCII字符集中,按每个字符在字符集中的位置,将每个字符编号为0-255,编号称为对应字符的序号。

    4.布尔常量

      布尔型常量仅有两个值,真和假,分别用标准常量名true和false表示。它们的序号分别为1和0。

    5.符号常量

      一个常量即可以直接用字面形式表示(称为直接常量, 如 124,156.8),也可以用一个标识符来代表一个常量,称为"符号常量"。但符号常量必须在程序中的说明部分定义,也就是说先定义,后使用。

      定义符号常量的一般格式:

    CONST
    <常量标识符>=<常量>

    说明:常量说明部分以关键字const开头, 后面的标识符为常量标识符,其中"="号后的常量为整数、实数、字符、 字符串(字符、字符串常量在后面章节中将作介绍)。而且,在常量说明部分可以将几个常量说明成符号常量,共用一个关键字"const"。例如:


    则在本程序中pi和zero作为符号常量,分别代表实数3.14159和整数0。也就是说,常量说明部分既定义了常量名及其值,又隐含定义了常量的类型。  关于符号常量,应注意下列几点:

      ⑴符号常量一经定义,在程序的执行部分就只能使用该常量标识符,而不能修改其值。
      ⑵使用符号常量比直接用数值更能体现"见名知义"的原则,也便于修改参数,故一个较好的程序中,应尽量使用符号常量,在执行部分基本上不出现直接常量。

      (二)变量

      变量代表了一个存储单元,其中的值是可变的,故称为变量。如游戏"魂斗罗"中玩者命的个数最初为3,当你死了一次命减少一,这里命的个数就是一个变量(或者说命的个数存储在一个存储单元中)。即在程序运行过程中,其值可以改变的量,称为变量。

      变量有三个要素是:变量名、变量类型、变量值。

      一个程序中可能要使用到若干个变量,为了区别不同的变量,必须给每个变量(存贮单元)取一个名(称为变量名),该变量(存贮单元)存放的值称为变量的值,变量中能够存放值的类型为变量的类型。例如 "魂斗罗"游戏中用于存放"命"的变量,在游戏程序中的名字可取为N,它的类型为整型,游戏初始时这个变量的值为3。

      1.变量名

      用一个合法的标识符代表一个变量。如n,m,rot,total 等都是合法变量名。在程序中用到的变量必须在说明部分加以说明,变量名应遵循自定义标识符的命名规则,并注?quot;见名知义"的原则,即用一些有意义的单词作为变量名。

      "自定义标识符"的命名规则为:自定义标识符必须以字母(包含下划线"_")开头,后面的字符可以是字母或数字。标识符长度不超过63个字符。

      2.变量的类型

      常量是有类型的数据,变量在某一固定时刻用来存放一个常量,因此也应有相应的类型。如整型变量用来存放整数,实型变量用来存放实数。

      3.变量说明

      在程序中若要使用变量,变量的名称及类型在程序的变量说明部分加以定义,变量的值则在程序的执行部分中才能赋给。

      变量说明的一般格式:

      VAR
    <变量标识符>[,<变量标识符>]:<类型>;
    (中括号内部分表示可省,下同)

      其中VAR是pascal保留字,表示开始一个变量说明段, 每个变量标识符或由逗号隔开的多个变量标识, 必须在它的冒号后面说明成同一类型。一个程序中,可以说明许多不同类型的变量,每种类型变量之间用分号隔开,共用一个VAR符号。

    例如:
    var
    age,day:integer;
    amount,average:real;

      其中,Integer(整型)、Real(实型)是标准标识符, 它们是"类型标识符",代表了确定的类型,如age和 day 被定义为整型变量,amount和average被定义为实型变量。
      一旦定义了变量,就确定了它的类型,也就是说,就确定了该变量的取值范围和对该变量所能进行的运算。

    (三)算术表达式

    ⑴算术表达式的定义

      pascal语言中的算术表达式是由符合pascal语法规定的运算对象(包括常量、变量、函数)、算术运算符、圆括号组成的有意义的式子。如:A+3.14159*5/8.4-Abs(-1123)

    ⑵算术运算符

      常用的有以下6个算术运算符:

      ① + (加)
      ② - (减)
      ③ * (乘)
      ④ / (实数除)得到结果为实型.如5.0/2.0=2.5, 5/2=2. 5,4/2=2.0而不等于2。
      ⑤ DIV (整除) DIV它要求除数和被除数均为整型, 结果也为整型。如10 DIV 2=5,10 DIV 3=3, 5 DIV 10=0.-15 DIV 4= -3。DIV运算只取商的整数部分,参与DIV运算的两个对象不能为实型。
      ⑥ mod (求余),也只能用于整数运算,结果为整数。例如:10 mod 4=2 , -17 mod 4= -1 , 4 mod (-3)=1, - 4 mod 3= -1,即 a mod b=a-(a div b)*b。

    (3)运算优先顺序

      如果一个表达式里出现两个或两个以上的运算符, 则必须规定它们的运算次序。pascal规定:

      ①表达式中相同优先级的运算符,按从左到右顺序计算;
      ②表达式中不同优先级的运算符,按从高到低顺序计算;
      ③括号优先级最高,从内到外逐层降低;
    在算术运算中运算符的优先顺序与数学上的四则运算一致,即"先乘除后加减"(注:"MOD"、"DIV"运算的优先级与"*"、"/"相同)。

    二、赋值语句

      变量既然代表一个存储单元,其值是可变的,那么其中的值是怎么提供的,又是怎么改变的呢?可以通过赋值语句来进行。

    1、 赋值语句的格式

    变量名:=表达式;
    其中":="称为赋值号。

    2、 执行过程

    计算机先计算赋值号右边表达式的值,然后将表达式的值赋给变量名代表的变量。如:A:=(9*8)-(2-1); A:=A+1

    三、输出语句

    输出语句的作用是将程序运算的结果输出到屏幕或打印机等输出设备。这里通常是指输出到屏幕。

    (一)输出语句的两种格式

    1、 write语句
    格式Write(表达式1,表达式2,……);
    如:write(1,2,3,4);
    write(1.2,3.4,5);
    write('My name is Liping');
    2、 writeln语句
    格式:
    Write(表达式1,表达式2,……)或writeln

    (二)输出语句的功能

      计算机执行到某一输出语句时,先计算出输出语句中的每个表达式的值,并将每一个表达式的值一个接一个地输出到屏幕上。
      Write语句与writeln语句格式上都相似,但它们在功能上有所不同,两个语句的区别在于,write语句将其后括号中的表达式一个接一个输出后,没有换行。而writeln语句则在输出各个表达式的值后换行。

    例如以下两个程序段的输出分别为:
    write(1,2,3,4);write(5,6);

    输出为:
    123456
    writeln(1,2,3,4);write(5,6);

    输出为:
    1234
    56

    四、应用例析

    例1:
       某仓库5月1日有粮食100吨,5月2日又调进20吨,5月3日卖出库存的3分之二,5月4日又调进库存的3倍粮食,问该仓库从5月1日到5月4日期间每天的粮食分别是多少吨?(输出每天的库存量)
    分析:在这个问题中,主要要描述从5月1日到5月4日期间仓库的粮食库存量,且易知它是不断变化的。因此我们可以用一个变量A来描述仓库的粮食库存量。

    程序可写如下:
    Program ex1;
    Var A : integer;
    Begin
    A:=100;Writeln('5/1:',A);
    A:=A+20;Writeln('5/2:',A);
    A:=A div 3; writeln('5/3:',A);
    A:=A *4; writeln('5/4:',A);Readln;
    End.

    例2:
       有三个小朋友甲乙丙。甲有50粒糖果,乙有43粒糖果,两有13粒糖果。现在他们做一个游戏。从甲开始,将自己的糖分三份,自己留一份,其余两份分别给乙与丙,多余的糖果自己吃掉,然后乙与丙也依次这样做。问最后甲、乙、丙三人各有书多少粒糖果?

    分析:
       这个问题中我们关心的是在游戏过程中每个小朋友的糖果个数,且他们所拥有的的糖果数是在变化的。因此可用a,b,c三个变量分别存放甲乙丙三个小朋友在某一时刻所拥有的糖果数。对于每人,分糖后,他的糖果数一定为原来的糖果数 div 3(因为分糖过程糖果的数目不一定都刚好分完,用整除恰恰可以表示多余的糖自己吃掉)。而其他两人则增加与这个小朋友现在拥有的一样的糖果。

    程序可写如下:

    program ex2;
     var A,B,C:integer;
    begin
     A:=50;B:=43;C:=13; {初始时每个小朋友所拥有的糖果数}
     A:=A div 3; B:=B+A;C:=C+A;{甲小朋友分糖果后,每个人拥有的糖果数变化情况}
     B:=B div 3; A:=A+B;C:=C+B; {乙小朋友分糖果后,每个人拥有的糖果数变化情况}
     C:=C div 3; A:=A+C;B:=B+C; {丙小朋友分糖果后,每个人拥有的糖果数变化情况}
     writeln('A=',A,'B=',B,'C=',C); {输出结果}
     readln;
    end.

    注:
       上程序中倒数第三行中'A='表示一个字符串(即用一对单引号括起来的一串字符),对于字符串,输出字符串的内容(即引号内的所得字符,而引号不输出)。
    以上程序的运行结果为:
    A=51B=35C=16

    练习二

    1、已知某梯形的上底A=13,下底B=18,高H=9,求它的面积S。

    2、某机关组织游泳比赛。将一堆西瓜分给前三名,把该堆西瓜中的一半又半个西瓜奖给第一名;剩下的一半又半个西瓜给第二名;把最后剩下的一半又半个西瓜给第三名,但每次分时并没切开任何一个西瓜,且刚好西瓜分完。问前三名各分到多少个西瓜

    3、已知某圆的半径R=139,求该圆的周长C与面积S?

     

     

    第三课 带格式的输出语句及输入语句

    一、写语句的输出格式

      在pascal语言中输出数据时是可以按照一定格式的,对整数隐含的输出形式为按十进制数形式。对实数的输出,隐含的形式是科学记数法形式(如果不想用科学记数法输出而用小数形式输出,要自己另行定义)。

      事实上,输出语句中的每个输出项中的表达式之后可以加上格式说明,若输出项后没有加格式说明, 则数据按系统隐含的格式输出,还可加上一定格式符号按特定格式输出。

    ⒈隐含的输出格式

      pascal语言为整型量、实型量、布尔型量和字符串( 用一对单引号括起来的字符序列)规定了每种数据所占的宽度(即一个数据占几列) ,一个数据所占的宽度称为"场宽"或"字段宽"。系统给出的隐含场宽称为标准场宽。每一种pascal版本给定的标准场宽不尽相同。下表给出标准pascal和pc机上两种pascal版所规定的标准场宽。
           标准场宽
      ━━━━━━━━━━━━━━━━━
      数据类型  标准pascal Turbo pascal
      ─────────────────
      integer 10 实际长度
      real 22 17
      布尔型 10 4或5
      字符串 串长 串长
      ━━━━━━━━━━━━━━━━━
    在Turbo Pascal系统中,对于整型字符串的输出都是按数据本身长度输出,对于布尔型数据(只有True和False两种值),TRUE为4列,FALSE为5列,一律采用大写输出。而real型数据的输出时,则按17列输出,其中第一列为符号位,正号不显示,后四位为"E±nn", 中间的12列为尾数部分。如:

    writeln(sqrt(75));
    则输出□8.6602540379E+00。
    而writeln(sqrt(81));
    则输出□9.0000000000E+00。
    有时,在程序中往往根据实际情况,需要自己定义场宽。

    ⒉指定场宽

      在写语句中输出项含有格式符号时,就是为了指定场宽。

    ⑴指定单场宽.

      格式:write(表达式:N)或writeln(表达式:N),其中N为自然数,指定单场宽后,所有数据不再按标准场宽输出,而按指定场宽输出。若数据实际长度小于指定场宽时,则一律"向右靠齐,左留空格"。

      如write(1234:8);write('abcdef':12)
    输出结果:
    □□□□1234□□□□□□abcdef

      对于标准实型数据指定单场宽时,如果场宽大于标准场宽时,右靠齐按标准场宽格式输出17位,左留空格。若场宽小于标准场宽时,第一位仍为符号位,最后四位仍为"E±nn",中间部分为尾数显示部分。如果指定的宽度小于8位,则数据按8位格式"*.*E±nn "输出。

    ⑵指定双场宽

      如果输出项是实数时,如果希望输出的实数不用科学记数法输出,而用小数形式输出,可以用指定双场宽方法输出。
      双场宽输出格式为:write(实型表达式:m:n),其中m和n都是自然数,m 用以指定整个数据所占的宽度,n指定输出实数的小数位数。

    如 : write(sqrt(75):9:4);
    输出:□□□8.6602

      如果双场宽不能满足输出数据的最低要求, 系统自动突破指定的场宽限制,按实际长度输出。
     
    如:write(sqrt(75):5:4); 要使小数点后有4位数字,而总场宽为5,是不可能的(因为还有一个小数点, 小数点前面还有一个数字)。它最低限度要有6列,即输出为:
    8.6602

    例1
       写出下列程序在turbo pascal下的输出结果.

    program ex;
     const s='abcdefg';
    var
     i:integer;
     r:real;
     c:char;b:boolean;
    begin
     i:=1234;r:=1234.5678;
     c:='#';b:=true;
     writeln(i,i:6,i:3);
     writeln(r,r:12:5,r:8:5);
     writeln(c,c:5);
     writeln(s,s:10,s:5);
     writeln(b,b:5,b:3);
    end.

    运行结果如下:

    1234□□12341234
    □1.2345678000E+03□□1234.567801234.56780
    #□□□□#
    abcdefg□□□abcdefgabcdefg
    TRUE□TRUETRUE

    3.应用例析

    例2:
       已知A=253,B=43,输出A*B的运算式子。即输出如下:

    253*43=10879
    253
    * 43
    759
    +1012
    10879

    分析:
       对于该问题,我们只要控制好输出时右靠齐即可。即前四行的总宽度一样(例如为12),第五行总宽度比前面少1。第六、七行总宽度与前四行一样。
    参与程序如下:

    var a,b:integer;
    begin
     a:=253;b:=43;
     writeln(a:10,'*',b,'=',a*b);
     writeln(a:12);
     write('*':8);writeln(b:4);
     writeln('--------':12);
     writeln(a*3:12);
     write('+':6);writeln(a*4:5);
     writeln('--------':12);
     writeln(a*b:12);
    end.

    二、 输入语句(读语句)   在程序中变量获得一个确定的值,固然可以用赋值语句,但是如果需要赋值的变量较多,或变量的值经常变化,则使用本节介绍的输入语句──读语句,将更为方便。读语句是在程序运行时由用户给变量提供数据的一种很灵活的输入动作,它有两种格式:

    1.读语句的一般格式:

      read(<变量名表>);
    readln[(<变量名表>)];

      其中变量名表是用逗号隔开的若干个变量名组成的。
    功能:从标准输入文件(即INPUT,一般对应着键盘 )中读入数据,并依次赋给相应的变量。
      说明:
      ①read和readln是标准过程名,它们是标准标识符。
      ②执行到read或readln语句时,系统处于等待状态,等待用户从键盘上输入数据,系统根据变量的数据类型的语法要求判断输入的字符是否合法。如执行read(a)语句,a是整型变量,则输入的字符为数字字符时是合法的,当输入结束时,则自动将刚接受的一串数字字符转换为整数赋给变量a。
      ③在输入数值型(整型或实型)数据时,数据间要用空格或回车分隔开各个数据,输入足够个数的数据,否则仍要继续等待输入,但最后一定要有回车,表示该输入行结束,直到数据足够,该读语句执行结束,程序继续运行。

    例3.
       设a、b、c为整型变量,需将它们的值分别赋以10,20,30,写出对应下列语句的所有可能输入格式。
    Read(a,b,c);


       根据③,即可列出所有可能输入格式
      (a)10□20□30←┘
      (b)10□20←┘
        30←┘
      (c)10←┘
        20□30←┘
      (d)10←┘
        20←┘
        30←┘
      其中"←┘"表示回车键。下同。

      ④read语句与readln语句的第一个区别是:
      read语句是一个接一个地读数据,在执行完本Read语句( 读完本语句中变量所需的数据)后,下一个读语句接着从该数据输入行中继续读数据,也就是说,不换行。如:

      Read(a,b);
      Read(c,d);
      Read(e);

    如果输入数据行如下:

    1□2□3□4□5□6□←┘

    则a,b,c,d,e的值分别为1,2,3,4,5,如果后面无读语句则数据6是多余的,这是允许的。
      Readln则不同,在读完本Readln语句中变量所需的数据后, 该数据行中剩余的数据多余无用,或者说,在读完本Readln语句中变量所需数据后,一定要读到一个回车,否则多余的数据无用。

    例4
       设要达到例1同样的目的,但语句改为:
    readln(a,b);readln(c)
    则例3中的4种输入格式只有(b)(d)是有效的.

      ⑤readln语句与read语句的第二个区别是:read 后一定要有参数表,而readln可以不带参数表,即可以没有任何输入项, 只是等待读入一个换行符(回车)。经常用于暂停程序的运行,直到输入一个回车。
      
    例5
       设有下列语句:
    read(a,b,c);
    readln(d,e);
    readln;
    readln(f,g);

      其中,所有变量均为整型。再设输入的数据如下:
      1□2←┘
      3□4□5□6□7□8←┘
      9□10←┘
      11←┘
      12□13←┘

    列表给出每个变量的值.

    分析:
       可以假想有一"数据位置指针",每读一个数据后,指针后移到该数据之后,每执行一个readln语句后,指针移到下一个数据行的开头。
      各变量的值如下表所示。

    ━━━━━━━━━━━━━━━━━━━━━━━━━━
    变量名 a b c d e f g
    ──────────────────────────
    值 1 2 3 4 5 11 12
    ──────────────────────────
      ⑥为了避免可能出现的错误,建议在程序中按下列原则使用读语句:(A)如果没有特殊需要,在一个程序中尽量避免混合使用read语句和readln语句;(B)尽量用readln语句来输入数据, 一个数据行对应一个readln语句;(C)由于执行read或readln语句时, 系统不会提供任何提示信息,因此,编程时最好在readln语句之前加以适当提示,例如:

    write('Input a,b,c:');
    readln(a,b,c);
    在执行时,屏幕上显示:
    Input a,b,c:■
    其中,"■"为光标。执行readln语句后,系统处于待待输入状态, 只有输入了所需数据后才继续往下执行。

    三、顺序结构程序设计

      到目前为止,我们可以用读、写语句和赋值语句编写一些简单的程序。通过阅读这些程序,可以逐步熟悉pascal程序的编写方法和应遵循的规则,为以后各章的学习打基础。
      
    例6
       试编一程序,输入一梯形的上底、下底、高, 求该梯形的面积。
    分析:
       整个程序分为三段:输入、计算、输出。程序中用a,b,h三个变量分别存放梯形的上、下底与高,S存放面积。 要而使用这些变量都要先说明,程序的执行部分中先输入上、下底与高,接着求面积S,最后输出结果S。

      源程序如下:
     program Tixing; {程序首部}
     var a,b,h,s:real; {程序说明部分}
    begin
     write('Input a,b,h:');
     readln(a,b,h); {程序执行部分}
     s:=(a+b)*h/2;
     write('s=',s:10:3);
    end.

    例7
       某幼儿园里,有5个小朋友编号为1,2,3,4,5,他们按自己的编号顺序围坐在一张圆桌旁。他们身上都有若干个糖果,现在他们做一个分糖果游戏。从1号小朋友开始,将他的糖果均分三份(如果有多余的,则他将多余的糖果吃掉),自己留一份,其余两份分给他的相邻的两个小朋友。接着2号、3号、4号、5号小朋友也这如果做。问一轮后,每个小朋友手上分别有多少糖果。

    分析: 
       这道问题与第二课中的例2基本一样,只不过这里有5位小朋友,且他们初始时糖果的数目不确定。这里用a,b,c,d,e分别存放5个小朋友的糖果。初始时它们的值改为由键盘输入。其它都与第二课中的例2类似。
    参考程序如下:

    program fentang;
    var a,b,c,d,e:integer;
    begin
     write('Please Enter init numbers ');readln(a,b,c,d,e);
     a:=a div 3;b:=b+a;e:=e+a;{1号均分后,1、2、5号的糖果数变化情况}
     b:=b div 3;c:=c+b;a:=a+b;{2号均分后,1、2、3号的糖果数变化情况}
     c:=c div 3;b:=b+c;d:=d+c;{3号均分后,2、3、4号的糖果数变化情况}
     d:=d div 3;c:=c+d;e:=e+d;{4号均分后,3、4、5号的糖果数变化情况}
     e:=e div 3;d:=d+e;a:=a+e;{5号均分后,4、5、1号的糖果数变化情况}
    {输出结果}
     writeln('a=',a);
     writeln('b=',b);
     writeln('c=',c);
     writeln('d=',d);
     writeln('e=',e);
     readln;{暂停}
    end.

    例8
       编一程序求半径为R的圆的周长与面积?
    分析:
       程序要先输入半径R,然后求周长c和面积s,最后输出c和s.
    源程序如下:

    program circle;
     const PI=3.14159;
     var r,c,s:real;
    begin
     write('Enter R=');readln(r);
     c:=2*pi*r;
     s:=pi*sqr(r);
     writeln('c=',c:10:2);
     writeln('s=',s:10:2);
    end.
      在程序中,为了输出实型周长C和面积S时,按照小数形式输出,采用了指定双场宽格式。

    练习三

    1. 编一程序,将摄氏温度换为华氏温度。公式为:

    其中f为华氏温度,c是摄氏温度。

    2. 编一程序,输入三角形的三边a、b、c(假设这三边可以构成一个三角形),求三角形的面积S?
    (提示:可利用海伦公式

     

     

    第四课 简单的分支结构程序设计

    在现实生活中,我们每天都要进行根据实际情况进行选择。例如,原打算明天去公园,但如果明天天气不好,将留在家里看电视。所以人也会根据条件进行行为的选择。计算机也会根据不同情况作出各种逻辑判断,进行一定的选择。在这课与下一课中,我们将会发现,我们是通过选择结构语句来实现程序的逻辑判断功能。

    一、PASCAL中的布尔(逻辑)类型

      在前面,我们学习了整型(integer)与实型(real)。其中integer型数据取值范围为-32768到32767之间所有整数。而real型数据取值范围为其绝对值在10-38到1038之间的所有实数。它们都是数值型的(即值都为数)。布尔型(Boolean)是一种数据的类型,这种类型只有两种值,即"真"与"假"。

      1、 布尔常量

      在Pascal语言中"真"用ture表示,"假"用False表示。所以布尔类型只有TRUE与FALSE两个常量。

      2、 布尔变量(BOOLEAN)

      如果我们将某些变量说明成布尔型,那么这些变量就是布尔变量,它们只能用于存放布尔值(ture或false)。
      例如,VAR A,B:BOOLEAN;

      3、 布尔类型是顺序类型

      由于这种类型只有两个常量,Pascal语言中规定ture的序号为1,false的序号为0。若某种类型的常量是有限的,那么这种类型的常量通常都有一个序号,我们称这种类型为顺序类型。如前面我们学过的整型(integer),以及后面要学到的字符型(char)都是顺序类型。

      4、 布尔类型的输入与输出

      a)输出
      VAR A,B:BOOLEAN;
      BEGIN
       A:=TRUE;B:=FALSE;
       WRITELN(A,B);
      END.
      TRUEFALSE

      b)布尔类型变量不能直接用读语句输入
      布尔类型变量不能通过读语句给它们提供值。事实上,我们可以通过间接方式对布尔变量进行值的输入。

      例如,以下程序是错误的:
      var a,b,c:Boolean;
      begin
       readln(a,b,c); {错误语句}
       writeln(a,b,c);
      end.

    二、关系表达式与布尔表达式

      1、什么是关系表达式
      用小括号、>、<、>=、<=、=、<>将两个算术表达式连接起来的式子就称为关系表达式(比较式)。
      如:3+7>8,x+y<10,2*7<=13等都是关系表达式。

      2、关系表达式的值

      很显然,这几个关系表达式中第一个是正确的,第三个是错误的,而第二个表达式可能是对的,也可能是错的。所以我们很容易发现,这些表达式的值是"对"的或"不对"的(或者说,是"真"的或"假"的),即关系表达式的值为布尔值。表示该比较式两端式子的大小关系是否成立。如3+2>6是错的,故它的值为FALSE。同样,45>=32是对的,故该表达式的值为true。

      关系表达式用于表示一个命题。如:"m为偶数"可表示为:m mod 2=0。"n为正数"可表示为:n>0。

      3.布尔运算及布尔表达式

      为了表示更复杂的命题,Pascal还引入三种逻辑运算符:not、and、or。它们分别相当于数学上的"非"、"且"和"或"的意义。

      这三个运算符的运算对象为布尔量,其中not为单目运算,只有一个运算对象,and与or为双目运算,有两个运算对象。它们的运算真值表如下:

     

    a

    b

    Not a

    a and b

    a or b

    a xor b

    false

    false

    true

    false

    false

    false

    false

    true

    true

    false

    ture

    true

    true

    false

    false

    false

    true

    true

    true

    true

    false

    true

    true

    false

      于是,对于一个关系表达式,或多个关系表达式用布尔运算符连接起来的式子就称为布尔表达式。布尔表达式的值也为布尔值。

      如果一个表达式里出现两个或两个以上的运算符, 则必须规定它们的运算次序。pascal规定:
      ①表达式中相同优先级的运算符,按从左到右顺序计算;
      ②表达式中不同优先级的运算符,按从高到低顺序计算;
      ③括号优先级最高,从内到外逐层降低;

      对于一个复杂的表达式可能同时包含算术运算、关系运算和逻辑运算以及函数运算。运算的优先顺序为:括号à函数ànotà*、/、div、mod、andà+、-、or、xorà关系运算。
      对于复杂的命题,我们可以用布尔表达式来表示。例如,命题:"m,n都是偶数或都是奇数"可表示为"(m mod 2=0)and(n mod 2=0) or(m mod 2=1)and(n mod 2=1)"。

    三、简单的IF语句

      1、格式

      Ⅰ、IF <布尔表达式>THEN 语句;
      Ⅱ、IF <布尔表达式>THEN 语句1 ELSE 语句2;
      (注意Ⅱ型IF语句中语句1后无";"号)

      2、功能   Ⅰ、执行IF语句时,先计算<布尔表达式>的值,若为TRUE则执行语句,否则不执行任何操作。
      Ⅱ、执行IF语句时,先计算<布尔表达式>的值,若为TRUE则执行语句1,否则执行语句2;

      3、示例

      1)例4.2输入一个整数a,判断是否为偶数。(是输出"yes"否则输出"no")。

      Program ex4_2;
       Var a:integer;
       Begin  
        Write('a=');readln(a);
        If (a mod 2 =0)then writeln('yes')
        Else writeln('no');
        Readln;
       End.

      2)华榕超市里卖电池,每个电池8角钱,若数量超过10个,则可打75折。

      Program ex4_3;
       Var Num:integer;Price,Total:real;
       Begin
        Write('Num=');readln(Num);
        Price=0.8;
        If Num>10 then Price:=Price*0.75;
        Total:=Num*Price;
        Writeln('Total=',Total:0:2);
        Readln;
       End.

      3)编写一与电脑猜"红"或"黑"的游戏。

      分析:用1代表红,0代表黑。先由计算机先出答案,然后再由人猜,猜对输出"YOU WIN"否则输出"YOU LOST"。为了模拟猜"红"或"黑"的随意性,程序中需要用到随机函数random(n)。

      函数是什么呢,例如大家都知道|-2|=2,|58|=58,那么|x|=?。

      如果我们用y表示|x|,那么 .这里y=|x|就是一个函数,也就是说函数是一个关于一个或多个自变量(未知量,如上例中的x)的运算结果。

      在pascal语言中,系统提供了许多内部函数,其中包括|x|函数,当然它用abs(x)表示。我们如果要求x2-y的绝对值,可以调用内部函数abs(x*x-y)即可求得。Random(n)也是一个内部函数,调用它能得到0~n-1之间的整数(但它不确定的,或说是随机的)。同时由于函数是一个运算结果,所以函数的调用只能出现在表达式中。

      Program ex4_3;
       Uses crt;
       Var Computer,People:integer;
       Begin
        Randomize;
        Computer:=random(2);
        Write('You guess(0-Red 1-Black):');readln(People);
        If People=Computer then writeln('YOU WIN')
        Else writeln('YOU LOST');
        Readln;
       End.

      作业:.某车站行李托运收费标准是:10公斤或10公斤以下,收费2.5元,超过10公斤的行李,按每超过1公斤增加1.5元进行收费。 试编一程序,输入行李的重量,算出托运费。

     

     

    第五课if嵌套与case语句

     

    一、IF语句的嵌套

      在if语句中,如果then子句或else子句仍是一个if语句, 则称为if语句的嵌套。

      例1 计算下列函数

       

      分析:根据输入的x值,先分成x>0与x≤0两种情况,然后对于情况x≤0,再区分x是小于0,还是等于0。

      源程序如下:
      program ex;
      var
        x:real;
        y:integer;
      begin

        wrtie('Input x:');readln(x);
        if x>0 then y:=1{x>0时,y的值为1}
         else {x≤0时}
          if x=0 then y:=0
          else y:=-1;
        writeln('x=',x:6:2,'y=',y);
      end.

      显然,以上的程序中,在then子句中嵌套了一个Ⅱ型if语句。当然程序也可以写成如下形式:
      
      program ex;
      var
        x:real;y:integer;
      begin
       wrtie('Input x:');readln(x);
       if x>=0 then
        if x>0 then y:=1
        else y:=0
       else y=-1;
       writeln('x=',x:6:2,'y=',y);
      end.

      但是对于本题,下面的程序是不对的。
      y:=0;
      if x>=0 then
        if x>0 then y:=1
      else y:=-1;

      明显,从此人的程序书写格式可以看出,他想让else与第一个if配对,而事实上,这是错的。因为pascal规定:else与它上面的距它最近的then配对,因此以上程序段的逻辑意义就与题义不符。
      要使上程序段中esle与第一个then配对,应将程序段修改为:
      y:=0;               或者 y:=0;
      if x>=0                 if x>=0
       then if x>0               then
        then y:=1                begin
        else                    if x>0 then Y:=1;
       else y:=-1;                end
                          else Y:=-1;
    二、case语句

      上面我们知道可以用嵌套的if语句实现多分支的选择结构。但是如果分支越来越多时,用嵌套的if语句实现多分支就显得繁杂。当多分支选择的各个条件由同一个表达式的不同结果值决定时,可以用case语句实现。它的选择过程,很象一个多路开关,即由case语句的选择表达式的值,决定切换至哪一语句去工作。因此在分支结构程序设计中,它是一种强有力的手段。在实现多路径分支控制时,用case对某些问题的处理和设计,比用if语句写程序具有更简洁、清晰之感。

      (一)、情况语句的一般形式:
      case <表达式> of
       <情况标号表1>:语句1;
       <情况标号表2>:语句2;
       :
       <情况标号表n>:语句n
      end;
      其中case、of、end是Pascal的保留字, 表达式的值必须是顺序类型,它可以是整型、布尔型及以后学习的字符型、枚举型和子界型。情况标号表是一串用逗号隔开的与表达式类型一致的常量序列。语句可以是任何语句,包括复合语句和空语句。

      (二)、case语句的执行过程
      先计算表达式(称为情况表达式)的值,如果它的值等于某一个常量(称为情况常量,也称情况标号),则执行该情况常量后面的语句,在执行完语句后,跳到case语句的末尾end处。

      (三)、说明

      ①情况表达式必须是顺序类型的;
      ②情况常量是情况表达式可能具有的值,因而应与情况表达式具有相同的类型;
      ③情况常量出现的次序可以是任意的;
      ④同一情况常量不能在同一个case语句中出现两次或两次以上;
      ⑤每个分语句前可以有一个或若干个用逗号隔开的情况常量;
      ⑥如果情况表达式的值不落在情况常的范围内,则认为本case语句无效,执行case语句的下一个语句。turbo pascal中增加了一个"否则"的情况,即增加一个else子句,但也是可省的。
      ⑦每个常量后面只能是一个语句或一个复合语句。

      例2 根据x的值,求函数Y的值:

        
      分析:利用case语句进行程序设计, 关键在于巧妙地构造情况表达式。本例中三种情况可用一个表达式区分出来:Trunc(x/100)。因为x在(0~100)之间时表达式值为0;x在[100,200)时表达式值为1 ;其余部分可用else子句表示。
      
      源程序如下:
      program ex;
      var x,y:real;
      begin
       write('Input x:');readln(x);
       case trunc(x/100) of
        0:y:=x+1;
        1:y:=x-1;
        else y:=0;
       end;{end of case}
       writeln('x=',x:8:2),'y=',y:8:2);
      end.


    三、选择结构的程序设计
      
      例3
    输入一个年号,判断它是否是闰年。
      分析:判断闰年的算法是:如果此年号能被400除尽, 或者它能被4整除而不能被100整除,则它是闰年。否则,它是平年。
      
      源程序如下:
      program ex;
      var year:integer;
      begin
       write('Input year:');readln(year);
       write(year:6);
       if (year mod 400=0 ) then
        writeln('is a leap year.')
       else
        if (year mod 4=0)and(year mod 100<>0)
        then writeln('is a leap year.')
        else writeln('is not a leap year.');
      end.

      例4 判断1995年,每个月份的天数。
      分析:程序分为:输入月份,计算该月的天数,输出天数
      
      源程序如下:
      program days;
      var month,days:integer;
      begin
       write('Input month:');readln(month);
      case month of
        1,3,5,7,8,10,12:days:=31;
        4,6,9,11 :days:=30;
        2 :days:=28;
        else days:=0;
       end;
       if days<>0 then writeln('Days=',days);
      end.

      例5 期未来临了,班长小Q决定将剩余班费X元钱,用于购买若干支钢笔奖励给一些学习好、表现好的同学。已知商店里有三种钢笔,它们的单价为6元、5元和4元。小Q想买尽量多的笔(鼓励尽量多的同学),同时他又不想有剩余钱。请您编一程序,帮小Q制订出一种买笔的方案。
      分析:对于以上的实际问题,要买尽量多的笔,易知都买4元的笔肯定可以买最多支笔。因此最多可买的笔为x div 4支。由于小q要把钱用完,故我们可以按以下方法将钱用完:
      若买完x div 4支4元钱的笔,还剩1元,则4元钱的笔少买1支,换成一支5元笔即可;若买完x div 4支4元钱的笔,还剩2元,则4元钱的笔少买1支,换成一支6元笔即可;若买完x div 4支4元钱的笔,还剩3元,则4元钱的笔少买2支,换成一支5元笔和一支6元笔即可。
      从以上对买笔方案的调整,可以看出笔的数目都是x div 4,因此该方案的确为最优方案。

      源程序如下:
      program pen;
       var a,b,c:integer;{a,b,c分别表示在买笔方案中,6元、5元和4元钱笔的数目}
        x,y:integer;{x,y分别表示剩余班费和买完最多的4元笔后剩的钱}
       begin
        write('x=');readln(x){输入x}
        c:=x div 4;{4元笔最多买的数目}
        y:=x mod 4;{求买完c支4元笔后剩余的钱数y}
        case y of
         0 : begin a:=0;b:=0; end;
         1 : begin a:=0;b:=1;c:=c-1; end;
         2 : begin a:=1;b:=0; c:=c-1;end;
         3 : begin a:=1;b:=1; c:=c-2;end;
        end;
        writeln('a=',a,'b=',b,'c=',c);
       end.  

    练 习

    1.输入三角形的三个边,判断它是何类型的三角形(等边三角形?等腰三角形?一般三角形?)。
      2.输入三个数,按由大到小顺序打印出来。
      3.计算1901年2099年之间的某月某日是星期几。
      4.输入两个正整数a,b。b最大不超过三位数,a不大于31。使a在左,b在右,拼接成一个新的数c。例如:a=2,b=16,则c=216;若a=18,b=476,则c=18476。
      提示:求c的公式为:
           c=a×K+b
      其中:
        
        

     

    第六课for循环语句

    在实际应用中,会经常遇到许多有规律性的重复运算,这就需要掌握本章所介绍的循环结构程序设计。在Pascal语言中,循环结构程序通常由三种的循环语句来实现。它们分别为FOR循环、当循环和直到循环。通常将一组重复执行的语句称为循环体,而控制重复执行或终止执行由重复终止条件决定。因此,重复语句是由循环体及重复终止条件两部分组成。
      
      一、for语句的一般格式

      for <控制变量>:=<表达式1> to <表达式2> do <语句>;
      for <控制变量>:=<表达式1> downto <表达式2> do <语句>;
      其中for、to、downto和do是Pascal保留字。表达式1 与表达式2的值也称为初值和终值。

      二、For语句执行过程

      ①先将初值赋给左边的变量(称为循环控制变量);
      ②判断循环控制变量的值是否已"超过"终值,如已超过,则跳到步骤⑥;
      ③如果末超过终值,则执行do后面的那个语句(称为循环体);
      ④循环变量递增(对to)或递减(对downt o)1;
      ⑤返回步骤②;
      ⑥循环结束,执行for循环下面的一个语句。

      三、说明

      ①循环控制变量必须是顺序类型。例如,可以是整型、字符型等,但不能为实型。
      ②循环控制变量的值递增或递减的规律是:选用to则为递增;选用downto则递减。
      ③所谓循环控制变量的值"超过"终值,对递增型循环,"超过"指大于,对递减型循环,"超  过"指小于。
      ④循环体可以是一个基本语句,也可以是一个复合语句。
      ⑤循环控制变量的初值和终值一经确定,循环次数就确定了。但是在循环体内对循环变量的值进行修改,常常会使得循环提前结束或进入死环。建议不要在循环体中随意修改控制变量的值。
      ⑥for语句中的初值、终值都可以是顺序类型的常量、变量、表达式。

      四、应用举例

      例1
    .输出1-100之间的所有偶数。
        var i:integer;
        begin
         for i:=1 to 100 do
         if i mod 2=0 then write(i:5);
        end.

      例2.求N!=1*2*3*…*N ,这里N不大于10。
      分析:程序要先输入N,然后从1累乘到N。
      程序如下:
      var
        n,i : integer;{i为循环变量}
        S : longint;{s作为累乘器}
      begin
       write('Enter n=');readln(n);{输入n}
      s:=1;
       for i:=2 to n do{从2到n累乘到s中}
        s:=s*i;
       writeln(n,'!=',s);{输出n!的值}
      end.

    练 习

      1. 求s=1+4+7+…+298的值。
      2. 编写一个评分程序,接受用户输入10个选手的得分(0-10分),然后去掉一个最高分和一个最低分,求出某选手的最后得分(平均分)。
      3. 用一张一元票换1分、2分和5分的硬币,每种至少一枚, 问有哪几种换法(各几枚)?

    第七课WHILE循环与REPEAT

     

    一、WHILE循环

      对于for循环有时也称为计数循环,当循环次数未知,只能根据某一条件来决定是否进行循环时,用while 语句或repeat语句实现循环要更方便。
      while语句的形式为:
      while <布尔表达式> do<语句>;
      其意义为:当布尔表达式的值为true时,执行do后面的语句。
      while语句的执行过程为:
      ①判断布尔表达式的值,如果其值为真,执行步骤2,否则执行步骤4;
      ②执行循环体语句(do后面的语句);
      ③返回步骤1;
      ④结束循环,执行while的下一个语句。
      说明:这里while和do为保留字,while语句的特点是先判断,后执行。 当布尔表达式成立时,重复执行do后面的语句(循环体)。

      例1 、求恰好使s=1+1/2+1/3+…+1/n的值大于10时n的值。
      分析:"恰好使s的值大于10"意思是当表达式s的前n-1项的和小于或等于10,而加上了第n项后s的值大于10。从数学角度,我们很难计算这个n的值。故从第一项开始,当s的值小于或等于10时,就继续将下一项值累加起来。当s的值超过10时,最后一项的项数即为要求的n。
      程序如下:
      var
        s : real;
        n : integer;{n表示项数}
      begin 
       s:=0.0;n:=0;
       while s<=10 do{当s的值还未超过10时}
        begin
         n:=n+1;{项数加1}
         s:=s+1/n;{将下一项值累加到s}
        end;
       writlen('n=',n);{输出结果}
      end.

      例2、求两个正整数m和n的最大公约数。
      分析:求两个正整数的最大公约数采用的辗转相除法求解。以下是辗转的算法:
      分别用m,n,r表示被除数、除数、余数。
      ①求m/n的余数r.
      ②若r=0,则n为最大公约数.若r≠0,执行第③步.
      ③将n的值放在m中,将r的值放在n中.
      ④返回重新执行第①步。
      程序如下:
      program ex4_4;
      var m,n,a,b,r:integer;
      begin
        write('Input m,n:');
        readln(m,n);
        a:=m;b:=n;r:=a mod b;
        while r<>0 do
        begin
         a:=b;b:=r;
         r:=a mod b;
        end;
        writeln('The greatest common divide is:',b:8);
      end.

    二、直到循环(REPEAT-until语句)

      用while语句可以实现"当型循环",用repeat-until 语句可以实现"直到型循环"。repeat-until语句的含义是:"重复执行循环,直到指定的条件为真时为止"。
      直到循环语句的一般形式:
      Repeat
       <语句1>;
       :
       <语句n>;
      until <布尔表达式>;
      其中Repeat、until是Pascal保留字,repeat与until之间的所有语句称为循环体。
      说明:
      ①repeat语句的特点是:先执行循环,后判断结束条件,因而至少要执行一次循环体。
      ②repeat-until是一个整体,它是一个(构造型)语句,不要误认为repeat是一个语句, until是另一个语句。
      ③repeat语句在布尔表达式的值为真时不再执行循环体,且循环体可以是若干个语句,不需用begin和end把它们包起来, repeat 和until已经起了begin和end的作用。while循环和repeat循环是可以相互转化的。
      对于例2中求两个正整数的最大公约数,程序可用repeat-until循环实现如下:
      var
        m,n,a,b,r : integer;
      begin
       write('Input m,n=');
       readln(m,n);
       a:=m;b:=n;
       repeat
        r:=a mod b;
        a:=b;b:=r;
       until r=0;
       writeln('The greatest common divide is',a);
      end.
      以上我们已介绍了三种循环语句。一般说来,用for 循环比较简明,只要能用for循环,就尽量作用for循环。只在无法使用for循环时才用while循环和repeat-until循环, 而且 while 循环和repeat-until循环是可以互相替代的。for循环在大多数场合也能用whiel和repeat-until循环来代替。一般for循环用于有确定次数循环,而while和repeat-until循环用于未确定循环次数的循环。
      当一个循环的循环体中又包含循环结构程序时,我们就称之为循环嵌套。

      三、循环结构程序设计

      例3
    求1!+2!+…+10!的值。
      分析:这个问题是求10自然数的阶乘之和,可以用for 循环来实现。程序结构如下:
      for n:=1 to 10 do
      begin
       ①N!的值àt
       ②累加N!的值t
      end
      显然,通过10次的循环可求出1!,2!…,10!,并同时累加起来, 可求得S的值。而求T=N!,又可以用一个for循环来实现:
      t=1;
      for j:=1 to n do
       t:=t*j;
      因此,整个程序为:
      program ex4_5;
      var t,s:real;
        i,j,n:integer;
      begin
       S:=0;
       for n:=1 to 10 do
       begin
        t:=1;
        for j:=1 to n do
         t:=t*j;
        S:=S+t;
       end;
       writeln('s=',s:0:0);
      end.
      以上的程序是一个二重的for循环嵌套。这是比较好想的方法,但实际上对于求n!,我们可以根据求出的(n-1)!乘上n即可得到,而无需重新从1再累乘到n。
      程序可改为:
      program ex4_5;
      var t,s:real;
        i,j,n:integer;
      begin
       S:=0;t:=1;
       for n:=1 to 10 do
       begin
        t:=t*n;
        S:=S+t;
       end;
       writeln('s=',s:0:0);
      end.
      显然第二个程序的效率要比第一个高得多。第一程序要进行1+2+…+10=55次循环,而第二程序进行10次循环。如题目中求的是1!+2!+…+1000!,则两个程序的效率区别更明显。

      例4 一个炊事员上街采购,用500元钱买了90只鸡, 其中母鸡一只15元,公鸡一只10元,小鸡一只5元,正好把钱买完。问母鸡、公鸡、小鸡各买多少只?
      分析:设母鸡I只,公鸡J只,则小鸡为90-I-J只,则15*I+ 10* J+(90-I-J)*5=500,显然一个方程求两个未知数是不能直接求解。必须组合出所有可能的i,j值,看是否满足条件。这里I的值可以是0到33,J的值可以0到50。
      源程序如下:
      programr ex4_6;
      var i,j,k:integer;
      begin
       for i:=1 to 5 do
       for j:=1 to 8 do
        begin
         k:=90-i-j;
         if 15*i+10*j+5*k=500 then writeln(i:5,j:5,k:5);
        end;
      end.

      例5、求100-200之间的所有素数。
      分析:我们可对100-200之间的每一整数进行判断,判断它是否为素数,是则输出。而对于任意整数i,根据素数定义,我们从2开始,到 ,找i的第一个约数。若找到第一个约数,则i必然不是素数。否则i为素数。
      源程序如下:
      var
        i : integer;
        x : integer;
      begin
       for i:=100 to 200 do
        begin
         x:=2;
         while (x<=trunc(sqrt(i)))and(i modx<>0)do
          begin
           x:=x+1;
          end;
         if x>trunc(sqrt(i)) then write(i:8);
        end;
      end.

    练  习

      1、输入一个正整数n,将n分解成质因数幂的乘积形式。
         例如:36=22*32
      2、输出如下图形。
        
      3、编写一程序,验证角谷猜想。所谓的角谷猜想是:"对于任意大于1的自然数n,若n为奇数,则将n变为3*n+1,否则将n变为n的一半。经过若干次这样的变换,一定会使n变为1。"
      4.有一堆100多个的零件,若三个三个数,剩二个;若五个五个数,剩三个;若七个七个数,剩五个。请你编一个程序计算出这堆零件至少是多少个?

     

     

    第八课一维数组

    一、为什么要使用数组

      例1
     输入50个学生的某门课程的成绩,打印出低于平均分的同学号数与成绩。

      分析:在解决这个问题时,虽然可以通过读入一个数就累加一个数的办法来求学生的总分,进而求出平均分。但因为只有读入最后一个学生的分数以后才能求得平均分,且要打印出低于平均分的同学,故必须把50个学生的成绩都保留下来, 然后逐个和平均分比较,把高于平均分的成绩打印出来。如果,用简单变量a1,a2,…,a50存放这些数据,可想而知程序要很长且繁。

      要想如数学中使用下标变量ai形式表示这50个数,则可以引入下标变量a[i]。这样问题的程序可写为:
      tot:=0;{tot表示总分}
      for i:=1 to 50 do {循环读入每一个学生的成绩,并累加它到总分}
       begin
        read(a[i]);
        tot:=tot+a[i];
       end;
      ave:=tot/50;{计算平均分}
      for i:=1 to 50 do
       if a[i]<ave then writeln('No.',i,' ',a[i]);{如果第i个同学成绩小于平均分,则将输出}
      而要在程序中使用下标变量,则必须先说明这些下标变量的整体―数组,即数组是若干个同名(如上面的下标变量的名字都为a)下标变量的集合。

    二、一维数组
      当数组中每个元素只带有一个下标时,我们称这样的数组为一维数组。

      1、一维数组的定义
      (1)类型定义
      要使用数组类型等构造类型以及第6章要学习的自定义类型(枚举类型与子界类型),应在说明部分进行类型说明。 这样定义的数据类型适用整个程序。
      类型定义一般格式为:
      type
       <标识符1>=<类型1>;
       <标识符2>=<类型2>;
       :
       <标识符n>=<类型n>;
      其中type是Pascal保留字,表示开始一个类型定义段。在其后可以定义若干个数据类型定义。<标识符>是为定义的类型取的名字, 称它为类型标识符。
      类型定义后,也就确定了该类型数据取值的范围,以及数据所能执行的运算。

      (2)一维数组类型的定义
      一维数组类型的一般格式:
        array[下标1..下标2] of <基类型>;
      说明:其中array和of是pascal保留字。下标1和下标2 是同一顺序类型,且下标2的序号大于下标1的序号。它给出了数组中每个元素(下标变量) 允许使用的下标类型,也决定了数组中元素的个数。基类型是指数组元素的类型,它可以是任何类型,同一个数组中的元素具有相同类型。因此我们可以说,数组是由固定数量的相同类型的元素组成的。
      再次提请注意:类型和变量是两个不同概念,不能混淆。就数组而言,程序的执行部分使用的不是数组类型(标识符)而是数组变量(标识符)。
      一般在定义数组类型标识符后定义相应的数组变量,如:
      type arraytype=array[1..8]of integer;
      var a1,a2:arraytype;
      其中arraytype为一个类型标识符,表示一个下标值可以是1到 8,数组元素类型为整型的一维数组;而a1,a2则是这种类型的数组变量。
      也可以将其全并起来:
      var a1,a2:array[1..8]of integer;
      当在说明部分定义了一个数组变量之后,pascal 编译程序为所定义的数组在内存空间开辟一串连续的存储单元。
      例如,设程序中有如下说明:
      type rowtype=array[1..8]of integer;
         coltype=array['a'..'e']of integer;
      var a:rowtype;b:coltype;

      2、一维数组的引用
      当定义了一个数组,则数组中的各个元素就共用一个数组名( 即该数组变量名),它们之间是通过下标不同以示区别的。 对数组的操作归根到底就是对数组元素的操作。一维数组元素的引用格式为:
      数组名[下标表达式]
      说明:①下标表达式值的类型, 必须与数组类型定义中下标类型完全一致,并且不允许超越所定义的下标下界和上界。
         ②数组是一个整体,数组名是一个整体的标识,要对数组进行操作,必须对其元素操作。数组元素可以象同类型的普通变量那样作用。如:a[3]:=34;是对数组a中第三个下标变量赋以34的值。read(a[4]);是从键盘读入一个数到数组a第4个元素中去。
      特殊地,如果两个数组类型一致,它们之间可以整个数组元素进行传送。如:
      var a,b,c:array[1..100] of integer;
      begin
       c:=a;a:=b;b:=c;
      end.
      在上程序中,a,b,c三个数组类型完全一致, 它们之间可以实现整数组传送,例子中,先将a数组所有元素的值依次传送给数组c,同样b数组传给a,数组c又传送给b,上程序段实际上实现了a,b 两个数组所有元素的交换。
      对于一维数组的输入与输出, 都只能对其中的元素逐个的输入与输出。在下面的应用示例中将详细介绍。

      三、一维数组应用示例
      例2
    输入50个数,要求程序按输入时的逆序把这50个数打印出来。也就是说,请你按输入相反顺序打印这50个数。
      分析:我们可定义一个数组a用以存放输入的50个数, 然后将数组a内容逆序输出。
      源程序如下:
      program ex5_1;
      type arr=array[1..50]of integer; {说明一数组类型arr}
      var a:arr;i:integer;
      begin
       writeln('Enter 50 integer:');
       for i:=1 to 50 do read(a[i]);{从键盘上输入50个整数}
       readln;
       for i:=50 downto 1 do {逆序输出这50个数}
        write(a[i]:10);
      end.

      例3 输入十个正整数,把这十个数按由小到大的顺序排列。
      将数据按一定顺序排列称为排序,排序的算法有很多,其中选择排序是一种较简单的方法。
      分析:要把十个数按从小到大顺序排列,则排完后,第一个数最小,第二个数次小,……。因此,我们第一步可将第一个数与其后的各个数依次比较,若发现,比它小的,则与之交换,比较结束后,则第一个数已是最小的数(最小的泡往下冒)。同理,第二步,将第二个数与其后各个数再依次比较,又可得出次小的数。如此方法进行比较,最后一次,将第九个数与第十个数比较,以决定次大的数。于是十个数的顺序排列结束。
      例如下面对5个进行排序,这个五个数分别为8 2 9 10 5。按选择排序方法,过程如下:
      初始数据 :8 2 9 10 5
      第一次排序:8 2 9 10 5
            9 2 8 10 5
            10 2 8 9 5
            10 2 8 9 5
      第二次排序:10 8 2 9 5
            10 9 2 8 5
            10 9 2 8 5
      第三次排序:10 9 8 2 5
            10 9 8 2 5
      第四次排序:10 9 8 5 2
      对于十个数,则排序要进行9次。源程序如下:
      program ex5_2;
       var a:array[1..10]of integer;
          i,j,t:integer;
        begin
         writeln('Input 10 integers:');
         for i:=1 to 10 do read(a[i]);{读入10个初始数据}
         readln;
         for i:=1 to 9 do{进行9次排序}
          begin
           for j:=i+1 to 10 do{将第i个数与其后所有数比较}
             if a[i]<a[j] then {若有比a[i]大,则与之交换}
              begin
               t:=a[i];a[i]:=a[j];a[j]:=t;
              end;
           write(a[i]:5);
          end;
        end.

    练习:

      1.输入一串小写字母(以"."为结束标志),统计出每个字母在该字符串中出现的次数(若某字母不出现,则不要输出)。
      例:
      输入:aaaabbbccc.
      输出:a:4
         b:3
         c:3

      2.输入一个不大于32767的正整数N,将它转换成一个二进制数。
      例如:
      输入:100
      输出: 1100100

      3.输入一个由10个整数组成的序列,其中序列中任意连续三个整数都互不相同,求该序列中所有递增或递减子序列的个数。
      例如:
      输入:1 10 8 5 9 3 2 6 7 4
      输出:6
      对应的递增或递减子序列为:
      1 10 
      10 8 5 
      5 9
      9 3 2
      2 6 7
      7 4

     

     

    第九课 多维数组

    一、多维数组的定义
      当一维数组元素的类型也是一维数组时,便构成了二维数组。二维数组定义的一般格式:
        array[下标类型1] of array[下标类型2] of 元素类型;
      但我们一般这样定义二维数组:
        array[下标类型1,下标类型2] of 元素类型;
      说明:其中两个下标类型与一维数组定义一样,可以看成"下界1..上界1"和"下界2..上界2",给出二维数组中每个元素( 双下标变量)可以使用下标值的范围。of后面的元素类型就是基类型。
      一般地,n维数组的格式为:
        array[下标类型1,下标类型2,…,下标类型n] of 元素类型;
      其中,下标类型的个数即数组的维数,且说明了每个下标的类型及取值范围。
    二、多维数组元素的引用
      多维数组的数组元素引用与一维数组元素引用类似,区别在于多维数组元素的引用必须给出多个下标。
      引用的格式为:
       <数组名>[下标1,下标2,…,下标n]
      说明:显然,每个下标表达式的类型应与对应的下标类型一致,且取值不超出下标类型所指定的范围。 
      例如,设有说明:
      type matrix=array[1..5,1..4]of integer;
      var a:matrix;
      则表示a是二维数组,共有5*4=20个元素,它们是:
      a[1,1]a[1,2]a[1,3]a[1,4]
      a[2,1]a[2,2]a[2,3]a[2,4]
      a[3,1]a[3,2]a[3,3]a[3,4]
      a[4,1]a[4,2]a[4,3]a[4,4]
      a[5,1]a[5,2]a[5,3]a[5,4]
      因此可以看成一个矩阵,a[4,2]即表示第4行、第2列的元素。由于计算机的存储器是一维的,要把二维数组的元素存放到存储器中,pascal是按行(第一个下标)的次序存放,即按a[1,1]a[1,2]a[1,3]a[1,4]a[2,1]…,a[5,4]的次序存放于存储器中某一组连续的存储单元之内。
      对于整个二维数组的元素引用时,大多采用二重循环来实现。如:给如上说明的二维数组a进行赋值:a[i,j]=i*j。
      for i:=1 to 5 do
       for j:=1 to 4 do
         a[i,j]:=i*j;
      对二维数组的输入与输出也同样可用二重循环来实现:
      for i:=1 to 5 do
      begin
       for j:=1 to 4 do
        read(a[i,j]);
       readln;
      end;
      for i:=1 to 5 do
       begin
        for j:=1 to 4 do
         write(a[i,j]:5);
        writeln;
       end;
    三、多维数组的应用示例
      例1设有一程序:
      program ex5_3;
      const n=3;
      type matrix=array[1..n,1..n]of integer;
      var a:matrix;
       i,j:1..n;
      begin
       for i:=1 to n do
       begin
        for j:=1 to n do
         read(a[i,j]);
        readln;
       end;
      for i:=1 to n do
      begin
       for j:=1 to n do
        write(a[j,i]:5);
       writeln;
      end;
      end.
      且运行程序时的输入为:
      2□1□3←┘
      3□3□1←┘
      1□2□1←┘
      则程序的输出应是:
      2□3□1
      1□3□2
      3□1□1

      例2 输入4名学生数学、物理、英语、化学、pascal五门课的考试成绩,求出每名学生的平均分,打印出表格。
      分析:用二维数组a存放所给数据,第一下标表示学生的学号, 第二个下标表示该学生某科成绩,如a[i,1]、a[i,2]、a[i,3]、a[i,4]、a[i,5]分别存放第i号学生数学、物理、英语、化学、pascal 五门课的考试成绩,由于要求每个学生的总分和平均分, 所以第二下标可多开两列,分别存放每个学生5门成绩和总分、平均分。
      源程序如下:
      program ex5_4;
      var a:array[1..4,1..7]of real;
       i,j:integer;
      begin
       fillchar(a,sizeof(a),0);
       {函数fillchar用以将a中所有元素置为0}
       writeln('Enter 4 students score');
       for i:=1 to 4 do
       begin
        for j:=1 to 5 do {读入每个人5科成绩}
        begin
         read(a[i,j]);
         {读每科成绩时同时统计总分}
         a[i,6]:=a[i,6]+a[i,j];
        end;
        readln;
        a[i,7]:=a[i,6]/5;{求平均分}
       end;
       {输出成绩表}
       writeln( 'No. Mat. Phy. Eng. Che. Pas. Tot. Ave.');
       for i:=1 to 4 do
       begin
        write(i:2,' ');
        for j:=1 to 7 do
         write(a[i,j]:9:2);
        writeln;
       end;
      end.

    四、数组类型的应用

      例3
    输入一串字符,字符个数不超过100,且以"."结束。判断它们是否构成回文。
      分析:所谓回文指从左到右和从右到左读一串字符的值是一样的,如12321,ABCBA,AA等。先读入要判断的一串字符(放入数组letter中),并记住这串字符的长度,然后首尾字符比较,并不断向中间靠拢,就可以判断出是否为回文。
      源程序如下:
      program ex5_5;
      var  
        letter  : array[1..100]of char;
        i,j   : 0..100;
        ch   : char;
      begin
        {读入一个字符串以'.'号结束}
       write('Input a string:');
       i:=0;read(ch);
       while ch<>'.' do
       begin
        i:=i+1;letter[i]:=ch;
        read(ch)
       end;
       {判断它是否是回文}
       j:=1;
       while (j<i)and(letter[j]=letter[i])do
       begin
        i:=i-1;j:=j+1;
       end;
       if j>=i thenwriteln('Yes.')
       else writeln('No.');
      end.

      例4 奇数阶魔阵

      魔阵是用自然数1,2,3…,n2填n阶方阵的各个元素位置,使方阵的每行的元素之和、每列元素之和及主对角线元素之和均相等。奇数阶魔阵的一个算法是将自然数数列从方阵的中间一行最后一个位置排起,每次总是向右下角排(即Aij的下一个是Ai+1,j+1)。但若遇以下四种情形,则应修正排数法。
      (1) 列排完(即j=n+1时),则转排第一列;
      (2) 行排完(即i=n+1时),则转排第一行;
      (3) 对An,n的下一个总是An,n-1;
      (4) 若Aij已排进一个自然数,则排Ai-1,j-2。

      例如3阶方阵,则按上述算法可排成:
              4 3 8
              9 5 1
              2 7 6

      有了以上的算法,解题主要思路可用伪代码描述如下:
      1 in div 2+1,yn /*排数的初始位置*/
      2 a[i,j]1;
      3 for k:=2 to nn do
      4 计算下一个排数位置(i,j);
      5 if a[i,j]<>0 then
      6 ii-1;
      7 jj-2;
      8 a[i,j]k;
      9 endfor

      对于计算下一个排数位置,按上述的四种情形进行,但我们应先处理第三处情况。算法描述如下:
      1 if (i=n)and(j=n) then
      2 jj-1; /*下一个位置为(n,n-1)*/;
      3 else
      4 ii mod n +1;
      5 jj mod n +1;
      6 endif;

      源程序如下:
      program ex5_7;
      var
       a : array[1..99,1..99]of integer;
       i,j,k,n : integer;
      begin
       fillchar(a,sizeof(a),0);
       write('n=');readln(n);
       i:=n div 2+1;j:=n;
       a[i,j]:=1;
       for k:=2 to n*n do
        begin
         if (i=n)and(j=n) then
          j:=j-1
         else
          begin
           i:=i mod n +1;
           j:=j mod n +1;
          end;
         if a[i,j]<>0 then
          begin
           i:=i-1;
           j:=j-2;
          end;
         a[i,j]:=k;
        end;
       for i:=1 to n do
        begin
         for j:=1 to n do
          write(a[i,j]:5);
         writeln;
        end;
      end.

    练习
      1、 输入N个同学的语、数、英三科成绩,计算他们的总分与平均分,并统计出每个同学的名次,最后以表格的形式输出。
      2、 输出杨辉三角的前N行(N<10)。
          1
          1 1
          1 2 1
          1 3 3 1
          1 4 6 4 1

     

     

     

    第十课 字符与字符串处理

    一、字符、字符串类型的使用

      (一)字符类型


      字符类型为由一个字符组成的字符常量或字符变量 。
      字符常量定义:
      const                                      
       字符常量='字符'
      字符变量定义:Ⅱ
      Var
       字符变量:char;

      字符类型是一个有序类型, 字符的大小顺序按其ASCⅡ代码的大小而定。函数succ、pred、ord适用于字符类型。
      例如:后继函数:succ('a')='b'
         前继函数:pred('B')='A'
         序号函数:ord('A')=65

      例1 按字母表顺序和逆序每隔一个字母打印。即打印出:
        a c e g I k m o q s u w y
        z x r v t p n l j h f d b

      程序如下:

      program ex8_1;
       var letter:char;
       begin
        for letter:='a' to 'z' do
        if (ord(letter)-ord('a'))mod 2=0 thenwrite(letter:3);
        writeln;
        for letter:='z' downto 'a' do
        if (ord(letter)-ord('z'))mod 2 =0 thenwrite(letter:3);
       writeln;
      end.
      分析:程序中,我们利用了字符类型是顺序类型这一特性,直接将字符类型变量作为循环变量,使程序处理起来比较直观。

      (二)字符串类型

      字符串是由字符组成的有穷序列。
      字符串类型定义:
      type <字符串类型标识符>=string[n];
      var
       字符串变量: 字符串类型标识符;
      其中:n是定义的字符串长度,必须是0~255之间的自然整数,第0号单元中存放串的实际长度,程序运行时由系统自动提供,第1~n号单元中存放串的字符。若将string[n]写成string,则默认n值为255。

      例如:type
          man=string[8];
           line=string;
          var
           name:man;
           screenline:line;

      另一种字符类型的定义方式为把类型说明的变量定义合并在一起。
      例如:VAR
           name:STRING[8];
           screenline:STRING;
      Turbo Pascal中,一个字符串中的字符可以通过其对应的下标灵活使用。

      例如:var
          name:string;
         begin
          readln(nsme);
          for i:=1 to ord(name[0])do
           writeln(name[i]);
         end.
      语句writeln(name[i])输出name串中第i个字符。

      例2 求输入英文句子单词的平均长度.

      程序如下:
      program ex8_2;
       var
        ch:string;
        s,count,j:integer;
       begin
        write('The sentence is :');
        readln(ch);
        s:=0;
        count:=0;
        j:=0;
        repeat
         inc(j);
         if not (ch[j] in [':',',',';','''','!','?','.','']) then inc(s);
         if ch[j] in[' ','.','!','?'] then inc(count);
        until (j=ord(ch[0])) or(ch[j] in ['.','!','?']);
        if ch[j]<>'.' then writeln('It is not asentence.')
        else writeln('Average length is ',s/count:10:4);
       end.

      分析:程序中,变量s用于存句子中英文字母的总数,变量count用于存放句子中单词的个数,ch[j]表示ch串中的第j个位置上的字符,ord(ch[0])为ch串的串长度。程序充分利用Turbo Pascal允许直接通过字符串下标得到串中的字符这一特点,使程序比较简捷。

    二、字符串的操作

      (一)字符串的运算和比较


      由字符串的常量、变量和运算符组成的表达式称为字符串表达式。
      字符串运算符包括:

      1.+:连接运算符

      例如:'Turbo '+'PASCAL'的结果是'TurboPASCAL'。
      若连接的结果字符串长度超过255,则被截成255个字符。若连接后的字符串存放在定义的字符串变量中,当其长度超过定义的字符串长度时,超过部份字符串被截断。

      例如:var
          str1,str2,str3:string[8];
         begin
          str1:='Turbo ';
          str2:='PASCAL';
          str3:=str1+str2;
         end.
      则str3的值为:'Turbo PA'。

      2.=、〈〉、〈、〈=、〉、〉=:关系运算符

      两个字符串的比较规则为,从左到右按照ASCⅡ码值逐个比较,遇到ASCⅡ码不等时,规定ASCⅡ码值大的字符所在的字符串为大。
      例如:'AB'〈'AC' 结果为真;
         '12'〈'2' 结果为真;
         'PASCAL '='PASCAL' 结果为假;

      例3 对给定的10个国家名,按其字母的顺序输出。

      程序如下:
      program ex8_3;
       var i,j,k:integer;
         t:string[20];
         cname:array[1..10] of string[20];
       begin
        for i:=1 to 10 do readln(cname[i]);
        for i:=1 to 9 do
         begin
          k:=i;
          for j:=i+1 to 10 do
           if cname[k]>cname[j] then k:=j;
          t:=cname[i];cname[i]:=cname[k];cname[k]:=t;
         end;
        for i:=1 to 10 do writeln(cname[i]);
       end.
      分析:程序中,当执行到if cname[k]>cname[j]时,自动将cname[k]串与cname[j]串中的每一个字符逐个比较,直至遇到不等而决定其大小。这种比较方式是计算机中字符串比较的一般方式。

    三、字符串的函数和过程

    Turbo Pascal提供了八个标准函数和标准过程,见下表,利用这些标准函数与标准过程,一些涉及到字符串的问题可以灵活解决。

     

     

    函数和过程名

    功 能

    说 明

    copy(s,m,n)

    取s中第m个字符开始的n个字符

    若m大于s的长度,则返回空串;否则,若m+n大于s的长度,则截断

    length(s)

    求s的动态的长度

    返回值为整数

    pos(sub,s)

    在s中找子串sub

    返回值为sub在s中的位置,为byte型

    insert(sour,s,m)

    在s的第m个字符位置处插入子串sour

    若返回串超过255,则截断

    delete(s,m,n)

    删除s中第m个字符开始的n个字符串

    若m大于s的长度,则不删除;否则,若m+n大于s的长度,则删除到结尾

    Str(x[:w[:d]],s)

    将整数或实数x转换成字符串s

    w 和 d是整型表达式,意义同带字宽的write语句

    val(s,x,code)

    将字符串S 转换成整数或实数x


    若S中有非法字符,则code存放非法字符在S中的下标;否则,code为零。code为整型

    upcase(ch)

    将字母ch转换成大写字母

    若ch不为小写字母,则不转换

     

      例4 校对输入日期(以标准英语日期,月/日/年)的正确性,若输入正确则以年.月.日的方式输出。
      程序如下:
      program ex8_4;
       const
        max:array[1..12] of byte
          =(31,29,31,30,31,30,31,31,30,31,30,31);
       var
        st:string;
        p,w,y,m,d:integer;
       procedure err;
        begin
         write('Input Error!');
         readln;
         halt;
        end;
       procedure init(var x:integer);
        begin
         p:=pos('/',st);
         if (p=0) or (p=1) or (p>3) then err;
         val(copy(st,1,p-1),x,w);
         if w<>0 then err;
         delete(st,1,p);
        end;
       begin
        write('The Date is :');
        readln(st);
        init(m);
        init(d);
        val(st,y,w);
        if not (length(st)<>4) or (w<>0) or(m>12) or (d>max[m]) then err;
        if (m=2) and (d=29)
         then if y mod 100=0
            then begin
                if y mod 400<>0 then err;
               end
         else if y mod 4<>0 then err;
        write('Date : ',y,'.',m,'.',d);
        readln;
       end.

      分析:此题的题意很简单,但在程序处理时还需考虑以下几方面的问题。
      1.判定输入的月和日应是1位或2位的数字,程序中用了一个过程inst,利用串函数pos,求得分隔符/所在的位置而判定输入的月和日是否为1位或2位,利用标准过程val判定输入的月和日是否为数字;
      2.判定月和日是否规定的日期范围及输入的年是否正确;
      3.若输入的月是2月份,则还需考虑闰年的情况。

      例5 对输入的一句子实现查找且置换的功能。
      程序如下:
      program ex8_5;
      var
       s1,s,o:string;
       i:integer;
      begin
       write('The text:');
       readln(s1);
       write('Find:');readln(s);
       write('Replace:');readln(o);
       i:=pos(s,s1);
       while i<>0 do begin
        delete(s1,i,length(s));
        insert(o,s1,i);
        i:=pos(s,s1);
       end;
       writeln(s1);
       readln;
      end.

    分析:程序中,输入要查找的字符串及要置换的字符串,充分用上了字符串处理的标准过程delete、insert及标准函数pos。

     

     

    第十一课 枚举、子界、集合及记录类型

    在前面几章中我们用到了整型、实型、布尔型、字符型的数据。以上数据类型是由pascal规定的标准数据类型,只要写integer,real,boolean,char, pascal 编译系统就能识别并按这些类型来处理。pascal还允许用户定义一些类型,这是其它一些语言所没有的,这就使得pascal使用灵活、功能丰富。

    一、枚举类型

      随着计算机的不断普及,程序不仅只用于数值计算,还更广泛地用于处理非数值的数据。例如,性别、月份、星期几、颜色、单位名、学历、职业等,都不是数值数据。
      在其它程序设计语言中,一般用一个数值来代表某一状态,这种处理方法不直观,易读性差。如果能在程序中用自然语言中有相应含义的单词来代表某一状态,则程序就很容易阅读和理解。也就是说,事先考虑到某一变量可能取的值,尽量用自然语言中含义清楚的单词来表示它的每一个值,这种方法称为枚举方法,用这种方法定义的类型称枚举类型。
      枚举类型是一种很有实用价值的数据类型,它是pascal一项重要创新。

      (一)枚举类型的定义
      枚举类型是一种自定义类型,要使用枚举类型当然也要先说明枚举类型。
      枚举类型的一般格式:
       (标识符1,标识符2,…,标识符n)
      说明:①括号中的每一个标识符都称为枚举元素或枚举常量。
         ②定义枚举类型时列出的所有枚举元素构成了这种枚举类型的值域(取值范围),也就是说,该类型的变量所有可能的取值都列出了。
      例如,下列类型定义是合法的:
      type days=(sun,mon,tue,wed,thu,fri,sat);
      colors=(red,yellow,blue,white,black,green);
      而下列类型定义是错误的(因为枚举元素非标识符):
      type colortype=('red','yellow','blue','white');
      numbers=(1,3,5,7,9);
      ty=(for,do,while);

      (二)枚举类型变量
      定义了枚举类型,就可以把某些变量说明成该类型。如:
      var holiday,workday:day;
       incolor:colors;
      也可以把变量的说明与类型的定义合并在一起,如:
      var holiday,workday:(sun,mon,tue,wed,thu,fri,sat);
       incolor:(red,yellow,blue,white,black,green);

      (三)枚举类型的性质

      ⒈枚举类型属于顺序类型
      根据定义类型时各枚举元素的排列顺序确定它们的序号,第一个枚举元素的序号为0。例如:设有定义:
      type days=(sun,mon,tue,wed,thu,fri,sat);
      则:
      ord(sun)=0,ord(mon)=1,ord(sat)=6;succ(sun)=mon,succ(mon)=tue,
      succ(fri)=sat;pred(mon)=sun,pred(tue)=mon,pred(sat)=fri。
      应注意的是:枚举类型中的第一个元素无前趋,最后一个元素无后继。

      ⒉对枚举类型只能进行赋值运算和关系运算
      一旦定义了枚举类型及这种类型的变量,则在语句部分只能对枚举类型变量赋值,或进行关系运算,不能进行算术运算和逻辑运算。
      在枚举元素比较时,实际上是对其序号的比较。当然,赋值或比较时,应注意类型一致。
      例如,设程序有如下说明:
      type days=(sun,mon,tue,wed,thu,fri,sat);
        colors=(red,yellow,blue,white,black,green);
      var color:colors;
        weekday:days;
      则下列比较或语句是合法的:
      weekday:=mon;
      if weekday=sun then write('rest');
      weekday<>sun
      而下面比较或语句是不合法的:
      mon:=weekday;
      mon:=1;
      if weekday=sun or sat then write('rest');
      sun>red
      weekday<>color

      ⒊枚举变量的值只能用赋值语句来获得
      也就是说,不能用read(或readln)读一个枚举型的值。同样,也不能用write(或writeln)输出一个枚举型的值。如write(red)是非法的,会发生编译错误。千万不要误认为,该语句的结果是输出"red"三个字符。
      但对枚举数据的输入与输出可通过间接方式进行。输入时,一般可输入一个代码,通过程序进行转换,输出时,也只是打印出与枚举元素相对应的字符串。这在后面的例题中将有使用示例。

      ⒋同一个枚举元素不能出现在两个或两个以上的枚举类型定义中。
      如:
      type color1=(red,yellow,white);
         color2=(blue,red,black);
      是不允许的,因为red属于两个枚举类型。

      (四)、枚举类型应用举例

      例1
    一周七天用sun,mon,tue,wed,thu,fri,sat表示, 要求利用枚举类型编程:当输入星期几的数字,能输出它的后一天是星期几(也用英文表示)。
      源程序如下:
      program ex6_1;
      type week=(sun,mon,tue,wed,thu,fri,sat);
      var
        i : integer;
        day,sucday : week;
      begin
       write('What date is it');readln(i);
       case i of {根据输入i转换成枚举型}
        1:day:=mon;
        2:day:=tue;
        3:day:=wed;
        4:day:=thu;
        5:day:=fri;
        6:day:=sat;
        7:day:=sun;
       end;
       {计算明天sucday}
       if (day=sat) then sucday:=sun
       else sucday:=succ(day);
       {输出明天是星期几}
       write('The next day is ');
       case sucday of
        sun:writeln('sunday');
        mon:writeln('monday');
        tue:writeln('tuesday');
        wed:writeln('wednesay');
        thu:writeln('thursday');
        fri:writeln('friday');
        sat:writeln('saturday');
       end;
       end.
      评注:程序中变量day、sucday分别表示今天、明天。

    二、子界类型

      如果我们定义一个变量i为integer类型,那么i的值在微型机系统的pascal中,使用2字节的定义表示法,取值范围为-32768~32767。而事实上,每个程序中所用的变量的值都有一个确定的范围。
      例如,人的年龄一般不超过150,一个班级的学生不超过100人,一年中的月数不超过12,一月中的天数不超过31,等等。
      如果我们能在程序中对所用的变量的值域作具体规定的话,就便于检查出那些不合法的数据,这就能更好地保证程序运行的正确性。而且在一定程度上还会节省内存空间。
      子界类型就很好解决如上问题。此外,在数组的定义中,常用到子界类型,以规定数组下标的范围,上一章有关数组知识中我们已用到。

      (一)子界类型定义
      子界类型的一般格式:
        <常量1>..<常量2>
      说明: ①其中常量1称为子界的下界,常量2称为子界的上界。
         ②下界和上界必须是同一顺序类型(该类型称为子界类型的基类型),且上界的序号必须大于下界的序号。例如,下列说明:
      type age=0.5..150;
       letter=0..'z';
       let1='z'..'a';
      都是错误的。
      ③可以直接在变量说明中定义子界类型。如:
      type letter='a'..'d';
         var ch1,ch2:letter;
      可以合并成:
      var ch1,ch2:'a'..'d';
      当然,将类型定义和变量定义分开,则更为清晰。

      (二)子界类型数据的运算规则

      ⒈凡可使用基类型的运算规则同样适用该类型的子界类型。
      例如,可以使用整型变量的地方,也可以使用以整型为基类型的子界类型数据。
      ⒉对基类型的运算规则同样适用于该类型的子界类型。
      例如,div,mod要求参加运算的数据为整, 因而也可以为整型的任何子界类型数据。
      ⒊基类型相同的不同子界类型数据可以进行混合运算。

      例如:设有如下说明:
      type a=1..100;
         b=1.1000;
         c=1..500;
      var  
         x:a;
         y:b;
         t:c;
         z:integer;
      则下列语句也是合法的:
      Z:=Sqr(x)+y+t;
      下列语句:
       t:=x+y+z;
      当X+Y+Z的值在1~500范围内时是合法的,否则会出错。

      (三)子界类型应用举例
      例2
    利用子界类型作为情况语句标号,编一个对数字,大小写字母和特殊字符进行判别的程序。
      源程序如下:
      program cas;
      var c:char;
      begin
       readln(c);
      case c of
       '0'..'9':writeln('digits');
       'A'..'Z':writeln('UPPER-CASELETTERS');
       'a'..'z':writeln('lower-caseletters');
       esle writeln('special charactors');
      end;
     end.

      例3 使用子界型情况语句,当输入月、日、年(10 30 1986),输出30 Oct 1986。
      源程序如下:
      program ex6_3;
       var month:1..12;
         date:1..31;
         year:1900..1999;
       begin
        write('Enter date(mm-dd-yy):');
        readln(month,date,year);
        write(date);
        case month of
         1:write('Jan':5);
         2:write('Feb':5);
         3:write('Mar':5);
         4:write('Apr':5);
         5:write('May':5);
         6:write('Jun':5);
         7:write('Jul':5);
         8:write('Aug':5);
         9:write('Sep':5);
         10:write('Oct':5);
         11:write('Nov':5);
         12:write('Dec':5);
        end;
        writeln(year:7);
       end.
      枚举类型和子界类型均是顺序类型,在前面一章数组的定义时,实际上我们已经用到了子界类型,数组中的下标类型确切地讲可以是和枚举类型或子界类型,大多数情况下用子界类型。
      如有如下说明:
      type color=(red,yellow,blue,white,black);
      var
        a:array[color]of integer;
        b:array[1..100]of color;
      都是允许的。

    三、集合类型

      集合是由具有某些共同特征的元素构成的一个整体。在pascal中,一个集合是由具有同一有序类型的一组数据元素所组成,这一有序类型称为该集合的基类型。

      (一)集合类型的定义和变量的说明
      集合类型的一般形式为:
        set of <基类型>;
      说明: ①基类型可以是任意顺序类型, 而不能是实型或其它构造类型。同时,基类型的数据的序号不得超过255。例如下列说明是合法的:
      type letters=set of 'A'..'Z';
      numbers=set of 0..9;
      s1=set of char;
      ss=(sun,mon,tue,wed,thu,fri,sat);
      s2=set of ss;
      ②与其它自定义类型一样, 可以将类型说明与变量说明合并在一起.如:
      type numbers=set of 0..9;
      var s:numbers;
      与 var s:set of 0..9;等价。

      (二)集合的值
      集合的值是用"["和"]"括起来,中间为用逗号隔开的若干个集合的元素。如:
      [] 空集
      [1,2,3]
      ['a','e','i','o','u']
      都是集合。
      说明:
      ①集合的值放在一对方括号中,各元素之间用逗号隔开。
      ②在集合中可以没有任何元素,这样的集合称为空集。
      ③在集合中,如果元素的值是连续的,则可用子界型的表示方法表示。例如:  
        [1,2,3,4,5,7,8,9,10,15]
      可以表示成:
         [1..5,7..10,15]
      ④集合的值与方括号内元素出现的次序无关。例如,[1,5,8 ]和[5,1,8]的值相等。
      ⑤在集合中同一元素的重复出现对集合的值没有影响。例如,[1,8,5,1,8]与[1,5,8]的值相等。
      ⑥每个元素可用基类型所允许的表达式来表示。如[1,1+2,4]、[ch]、[succ(ch)]。

      (三)集合的运算

      ⒈赋值运算
      只能通过赋值语句给集合变量赋值,不能通过读语句赋值,也不能通过write(或writeln)语句直接输出集合变量的值。
      ⒉集合的并、交、差运算
      可以对集合进行并、交、差三种运算,每种运算都只能有一个运算符、两个运算对象,所得结果仍为集合。三种运算符分别用"+"、"*"、"-"表示。注意它们与算术运算的区别。
      ⒊集合的关系运算
      集合可以进行相等或不相等、包含或被包含的关系运算,还能测试一个元素是否在集合中。所用的运算符分别是:=、<>、>=、<=、in
      它们都是二目运算,且前4个运算符的运算对象都是相容的集合类型,最后一个运算符的右边为集合,左边为与集合基类型相同的表达式。

      例4 设有如下说明:
      type weekday=(sun,mon,tue,wed,thu,fri,sat);
         week=set of weekday;
         subnum=set of 1..50;
      写出下列表达式的值:
      ⑴[sun,sat]+[sun,tue,fri]
      ⑵[sun,fri]*[mon,tue]
      ⑶[wun,sat]*[sun..sat]
      ⑷[sun]-[mon,tue]
      ⑸[mon]-[mon,tue]
      ⑹[sun..sat]-[mon,sun,sat]
      ⑺[1,2,3,5]=[1,5,3,2]
      ⑻[1,2,3,4]<>[1..4]
      ⑼[1,2,3,5]>=[1..3]
      ⑽[1..5]<=[1..4]
      ⑾[1,2,3]<=[1..3]
      ⑿ 2 in[1..10]
      答: 表达式的值分别是:
      ⑴ [sun,sat,tue,fri]
      ⑵ [ ]
      ⑶ [sun,sat]
      ⑷ [ ]
      ⑸ [ ]
      ⑹ [tue..fri]
      ⑺ TRUE
      ⑻ FALSE
      ⑼ TRUE
      ⑽ FALSE
      ⑾ TRUE
      ⑿ TRUE

      例5 输入一系列字符,对其中的数字字符、字母字符和其它字符分别计数。输入'?'后结束。
      源程序如下:
      program ex10_2;
      var id,il,io:integer;
        ch:char;
        letter:set of char;
        digit:set of '0'..'9';
      begin
        letter=['a'..'z','A'..'Z'];
        digit:=['0'..'9'];
        id:=0;il:=0;io:=0;
        repeat
         read(ch);
         if ch in letter
         then il:=il+1
         else if ch in digit
          then id:=id+1
          else io:=io+1;
       until ch='?';
       writeln('letter:',il,'digit:',id,'Other:',io);
      end.

    四、记录类型
      在程序中对于组织和处理大批量的数据来说,数组是一种十分方便而又灵活的工具,但是数组在使用中有一个基本限制,这就是:一个数组中的所有元素都必须具有相同的类型。但在实际问题中可能会遇到另一类数据,它是由性质各不相同的成份组成的,即它的各个成
    份可能具有不同的类型。例如,有关一个学生的数据包含下列项目:
        学号  字符串类型
        姓名  字符串类型
        年龄  整型
        性别  字符型
        成绩  实型数组
      Pascal给我们提供了一种叫做记录的结构类型。在一个记录中,可以包含不同类型的并且互相相关的一些数据。

      (一)记录类型的定义
      在pascal中,记录由一组称为"域"的分量组成,每个域可以具有不同的类型。
      记录类型定义的一般形式:
      record
       <域名1>:<类型1>;
       <域名2>:<类型2>;
       : :
       : :
       <域名n>:<类型n>;
      end;
      说明:①域名也称域变量标识符, 应符合标识符的语法规则。在同一个记录中类型中,各个域不能取相同的名,但在不同的记录类型中,两个类型中的域名要以相同。
      ②记录类型的定义和记录变量可以合并为一个定义,如:
      type date=record
          year:1900..1999;
          month:1..12;
          day:1..31
         end;
      var x:date;
      可以合并成:
      var x: record
          year:1900..1999;
          month:1..12;
          day:1..31
         end;
      ③对记录的操作,除了可以进行整体赋值, 只能对记录的分量──域变量进行。
      ④域变量的表示方法如下:
      记录变量名.域名
      如前面定义的记录X,其3个分量分别为:x.year ,x.month ,x.day。
      ⑤域变量的使用和一般的变量一样, 即域变量是属于什么数据类型,便可以进行那种数据类型所允许的操作。

      (二)记录的嵌套
      当一个记录类型的某一个域类型也是记录类型的时候,我们说发生了记录的嵌套,看下面的例子:
      例6 某人事登记表可用一个记录表示, 其中各项数据具有不同的类型,分别命名一个标识符。而其中的"出生年月日"又包括三项数据,还可以用一个嵌套在内层的记录表示。
      具体定义如下:
      type sexs=(male,female);
         date=record
          year:1900..1999;
          month:1..12;
          day:1..31;
         end;
      personal=record
          name:string[15];
          sex:sexs;
          birthdate:date;
          home:string[40];
         end;

      例7 设计一个函数比较两个dates日期类型记录变量的迟早。
      设函数名、形参及函数类型定义为:
      AearlyB(A,B:dates):boolean;
      函数的形参为两个dates类型的值参数。当函数值为true时表示日期A早于日期B,否则日期A迟于日期B或等于日期B。显然不能对A、B两个记录变量直接进行比较,而要依具体的意义逐域处理。
      源程序如下:
      program ex6_7;
      type dates=record
          year:1900.1999;
          month:1..12;
          day:1..31
         end;
      var x,y:dates;
      function AearlyB(A,B:dates):boolean;
      var earln:boolean;
      begin
       early:=false;
       if (A.year<B.year) then early:=true;
       if (A.year=B.year)and(A.month<B.month)
       then early:=true;
       if(A.year=B.year)and(A.month=B.month)and(A.day<B.day)
       then early:=true;
       AearlyB:=early;
       end;{of AearlyB}
      begin
       write('Input DATE X(mm-dd-yy):')readln(X.month,X.day,X.year);
       write('Input DATEY(mm-dd-yy):')readln(Y.month,Y.day,Y.year);
       if AearlyB(X,Y) then writeln(Date X early!') elsewriteln('Date X not    early!');
      end.

      (三)开域语句
      在程序中对记录进行处理时,经常要引用同一记录中不同的域,每次都按6.4.1节所述的格式引用,非常乏味。为此Pascal提供了一个with语句,可以提供引用域的简单形式。
      开域语句一般形式:
      with <记录变量名表> do
      <语句>
      功能: 在do后的语句中使用with后的记录的域时, 只要直接写出域名即可,即可以省略图10.2.2中的记录变量名和"."。
      说明: ①一般在with后只使用一个记录变量名。如:
      write('Input year:');
      readln(x.year);
      write('Input month:');
      readln(x.month);
      write('Input day:');
      readln(x.day);
      可以改写成:
      with x do
       begin
        write('Input year:');readln(year);
        write('Input month:');readln(month);
        write('Input day:');readln(day);
       end;
      ②设x,y是相同类型的记录变量,下列语句是非法的:
        with x,y do...;
      ③with后接若干个记录名时,应是嵌套的关系。如有记录说明:
        var x:record
          i:integer;
          y:record
            j:0..5;
            k:real;
           end;
          m:real
         end;
      可以使用:
      with x do
      begin
        read(i);
        with y do
          read(j,k);
        readln(m);
      end;
      或简写为:
      with x,y do
      readln(i,j,k,m);

      例8 读入10个日期,再对每个日期输出第二天的日期。输入日期的格式是月、日、年,如9□30□1993,输出的格式为10/1/1993。
      分析: 可用一个记录变量today表示日期。知道一个日期后要更新为第二天的日期,应判断输入的日期是否为当月的最后一天,或当年的最后一天。
      源程序如下:
      program ex6_8;
       type date=record
            month:1..12;
            day:1..31;
            year:1900..1999;
           end;
       var today:array[1..10]of date;
          i:integer;
          maxdays:28..31;

       begin
        for i:=1 to 10 do {输入10个日期}
         with today[i] do
          readln(month,day,year);
        for i:=1 to 10 do
         with today[i] do{求第i个日期中月份最后一天maxdays}
          begin  
           case month of
            1,3,5,7,8,10,12:maxdays:=31;
            4,6,9,11 :maxdays:=30;
            2     :if(year mod400=0) or( year mod 4=0)
                  and(year mod 100<>0)
                 then maxdays:=29
                 else maxdays:=28;
           end;
           if day=maxdays
            then begin
              day:=1;  
              if month=12
               then begin
                    month:=1;year:=year+1;
                  end
                else month:=month+1;
              end
             else day:=day+1;
           writeln(month,'/',day,'/',year);
         end;
      end.

    五、应用实例

      例9
    编制用筛法求1-n(n≤200)以内素数的程序。
    分析: 由希腊著名数学家埃拉托色尼提出的所谓"筛法",步骤如下:
    ①将所有候选数放入筛中;
    ②找筛中最小数(必为素数)next,放入集合primes中;
     ③将next的所有倍数从筛中筛去;
     ④重复②~④直到筛空。
    编程时,用集合变量sieve表示筛子,用集合primes存放所有素数。
    源程序如下:
    program ex10_3;
    const n=200;
    var sieve,primes:set of 2..n;
    next,j:integer;
    begin
    sieve:=[2..n];{将所有候选数放入筛中}
    primes:=[];{素数集合置空}
    next:=2;
    repeat
     {找筛sieve中最小一个数}
     while not(next in sieve) and(next<=n)do
    next:=succ(next);
     primes:=primes+[next];{将最小数放入素数集合中}
     {将这个素数的倍数从筛中删去}
    j:=next;
    while j<=n do
     begin
    sieve:=sieve-[j];
    j:=j+next;
     end
    until sieve=[];
    j:=0;
    for next:=2 to n do{打印出所有素数}
     if next in primes then
     begin
    write(next:5);
    j:=j+1;
    if j mod 10=0 then writeln;
     end;
    writeln;
    end.

     

    练习

    1.一家水果店出售四种水果,每公斤的价格是:苹果1.50元,桔子1.80元,香蕉2.0,菠萝1.60元。编一个程序,使售货员只要从键盘输入货物的代码及重量,计算机便能显示货物的名称、单价、重量及总价。

    2.输入一个英语句子,以句号.为结束标志, 统计句子中元音字母出现的次数,把句子所有辅音字母组成一个集合,并把这些辅音字母打印出来。

    3.编程序建立某班25人的数学课程成绩表,要求用数组类型和记录类型,其成绩表格式如下:
    姓名  性别  平时成绩  期中考试  期终考试  总评成绩
    张良  男     90 85 92  ?
    王心  男     70 82 71  ?
    ……
    李英  女     82 84 75  ?
    其中总评成绩=平时成绩×20%+期中考试×30%+期终考试×%50。

    4. 输入五个学生的出生日期(月/日/年)和当天的日期,然后用计算机计算出每个人到当天为止的年龄各是多少?(如某人1975年10月1日出生,今天是94年12月1日,则他的年龄应为19岁,而另一人的出生日期为76年12月30日,则他的年龄为17岁。)

     

     

    第十二课过程与函数

     

    前面我们曾经学习了程序设计中的三种基本控制结构(顺序、分支、循环)。用它们可以组成任何程序。但在应用中,还经常用到子程序结构。
      通常,在程序设计中,我们会发现一些程序段在程序的不同地方反复出现,此时可以将这些程序段作为相对独立的整体,用一个标识符给它起一个名字,凡是程序中出现该程序段的地方,只要简单地写上其标识符即可。这样的程序段,我们称之为子程序。
      子程序的使用不仅缩短了程序,节省了内存空间及减少了程序的编译时间,而且有利于结构化程序设计。因为一个复杂的问题总可将其分解成若干个子问题来解决,如果子问题依然很复杂,还可以将它继续分解,直到每个子问题都是一个具有独立任务的模块。这样编制的程序结构清晰,逻辑关系明确,无论是编写、阅读、调试还是修改,都会带来极大的好处。
      在一个程序中可以只有主程序而没有子程序(本章以前都是如此),但不能没有主程序,也就是说不能单独执行子程序。pascal中子程序有两种形式:函数和过程。

    一、函数
      在此之前,我们曾经介绍并使用了pascal提供的各种标准函数,如ABS,SUCC等等,这些函数为我们编写程序提供了很大的方便。但这些函数只是常用的基本函数,编程时经常需要自定义一些函数。
      (一)函数的说明
      在pascal中,函数也遵循先说明后使用的规则,在程序中,函数的说明放在调用该函数的程序(主程序或其它子程序)的说明部分。函数的结构主程序的结构很相似。
      函数定义的一般格式:
      function <函数名>(<形式参数表>):<类型>; {函数首部}
      

      说明:
      ①函数由首部与函数体两部分组成。
      ②函数首部以关键字function开头。
      ③函数名是用户自定义的标识符。
      ④函数的类型也就是函数值的类型,所求得的函数值通过函数名传回调用它的程序。可见,函数的作用一般是为了求得一个值。
      ⑤形式参数简称形参,形参即函数的自变量。自变量的初值来源于函数调用。在函数中,形参一般格式如下:
      变量名表1:类型标识符1;变量名表2:类型标识符2;…;变量名表n:类型标识符n
      可见形参表相当于变量说明,对函数自变量进行说明,但应特别注意:此处只能使用类型标识符,而不能直接使用类型。
      ⑥当缺省形参表(当然要同时省去一对括号)时,称为无参函数。
      ⑦函数体与程序体基本相似,由说明部分和执行部分组成。
      ⑧函数体中的说明部分用来对本函数使用的标号、常量、类型、变量、子程序加以说明,这些量只在本函数内有效。
      ⑨函数体的执行部分由begin开头,end结束,中间有若干用分号隔开的语句,只是end后应跟分号,不能像程序那样用句号"."。
      ⑩在函数体的执行部分,至少应该给函数名赋一次值,以使在函数执行结束后把函数值带回调用程序。

      (二)函数的调用
      我们可以在任何与函数值类型兼容的表达式中调用函数,或者说,函数调用只能出现在允许表达式出现的地方,或作为表达式的一个因子。
      函数调用方式与标准函数的调用方式相同。
      函数调用的一般格式:
        <函数名>
        或
        <函数名>(实在参数表)
     
      说明:①实在参数简称实参。实参的个数必须与函数说明中形参的个数一致,实参的类型与形参的类型应当一一对应。
      ②调用函数时,一般的,实参必须有确定的值。
      ③函数调用的步骤为:计算实参的值,"赋给"对应的形参;

      (三)函数的应用举例
      例1
    求正整数A和B之间的完全数(A<B).
      分析:所谓完全数是指它的小于该数本身的因子之和等于它本身,如6=1+2+3,6即是一个完全数。因此我们可定义一个布尔型函数perfect(x),若x是完全数,其值为TURE,否则为FALSE。整个程序算法如下:
      1 for i:=A to B do
      2 if perfect(i) then writeln(i);
      源程序如下:
      program ex7_1;
      var
        i,a,b : integer;
      function perfect(x:integer):boolean;
      var
        k,sum : integer;
      begin
       {累加x所有小于本身的因数}
       sum:=1;
       for k:=2 to x div 2 do
        if x mod k=0 then sum:=sum+k;
       {判断x是否是完全数}
       perfect:=x=sum; {将结果赋值给函数名}
      end;{end of perfect}
      begin{主程序开始}
       write('Input a,b:');
       repeat {输入0<a<b}
        readln(a,b);
       until (a>0)and(b>0)and(a<b);
       writeln('List of all perfect numbers:');
       {从a到b逐个判断,是完全数则打印出来
       for i:=a to b do
                
        if perfect(i) then writeln(i);
      end.
      自定义函数只是主程序的说明部分,若主程序中没有调用函数,则系统不会执行函数子程序。当主程序调用一次函数时,则将实在参数的值传给函数的形式参数,控制转向函数子程序去执行,子程序执行完毕后自动返回调用处。

    二、过程
      在pascal中,自定义过程与自定义函数一样,都需要先定义后调用。函数一般用于求值,而过程一般实现某些操作。

      (一)过程的说明
      过程说明的一般格式为:
      procedure <过程名> (<形式参数表>); {过程首部}
      

      说明: ①过程首部以关键字procedure开头。
      ②过程名是用户自定义的标识符,只用来标识一个过程,不能代表任何数据,因此不能说明"过程的类型"。
      ③形参表缺省(当然要同时省去一对括号)时,称为无参过程。
      ④形参表的一般格式形式如下:
        [var] 变量名表:类型;…;[var] 变量名表:类型。
      其中带var的称为变量形参,不带var的称为值形参。在函数中,形参一般都是值形参,很少用变量形参(但可以使用)。例如,下列形参表中:
        (x,y:real;n:integer;var w:real;var k:integer;b:real)
      x、y、n、b为值形参,而w、k为变量形参。
      调用过程时,通过值形参给过程提供原始数据,通过变量形参将值带回调用程序。因此,可以说,值形参是过程的输入参数,变量形参是过程的输出参数。有关变参,这在后面内容具体叙述。
      ⑤过程体与程序、函数体类似。与函数体不同的是:函数体的执行部分至少有一个语句给函数名赋值,而过程体的执行部分不能给过程名赋值,因为过程名不能代表任何数据。
      ⑥过程体的说明部分可以定义只在本过程有效的标号、常量、类型、变量、子程序等。

      (二)过程的调用
      过程调用是通过一条独立的过程调用语句来实现的,它与函数调用完全不同。过程调用与调与标准过程(如write,read等)的方式相同。调用的一般格式为:
      <过程名> 
      或
      <过程名>(实在参数表)

      说明: ①实参的个数、类型必须与形参一一对应。
      ②对应于值形参的实参可以是表达式,对应于变量形参的实参只能是变量。
      ③过程调用的步骤为:计算实参的值;将值或变量的"地址"传送给对应的形参;执行过程体;返回调用处。
      过程与函数有下列主要区别:
      ①过程的首部与函数的首部不同;
      ②函数通常是为了求一个函数值,而过程可以得到若干个运算结果,也可用来完成一系列的数据处理,或用来完成与计算无关的各种操作;
      ③调用方式不同。函数的调用出现在表达式中,而过程调用是一个独立的语句。

      (三)过程的应用举例
      例2
    输出以下一个图形:
        *
        **
        ***
        ****
        *****
        ******
      分析:我们前面学习可用的二重循环打印出上图形, 现我们设置一个过程打印出N个连续的"*"号。
      源程序如下:
      program ex7_2;
       var i:integer;
       procedure draw_a_line(n:integer); {该过程打印出连续n 个星号,并换行}
        var j:integer;
        begin
         for j:=1 to n do
          write('*');
         writeln;
        end;
      begin
       for i:=1 to 6 do
        draw_a_line(i);{调用过程,第I行打印i个连续星号}
      end.

    三、过程、函数的数据传递
      在程序调用子程序时,调用程序将数据传递给被调用的过程或函数,而当子程序运行结束后,结果又可以通过函数名、变参。当然也可以用全局变量等形式实现数据的传递。这一节我们,就来研究参数传递与局部变量、全局变量等问题。

      (一)数值参数和变量参数
      前面已经讲过,pascal子程序中形式参数有数值形参(简称值参)和变量形参(变参)两种。事实上,还有函数形参和过程形参两种,只是应用并不太多,我们不作深入地研究。

      1、值形参
      值参的一般格式如§7.1.1所示。应该强调的是:
      ①形参表中只能使用类型标识符,而不能使用类型。
      ②值形参和对应的实参必须一一对应,包括个数和类型。
      ③实参和值形参之间数据传递是单向的,只能由实参传送给形参,相当赋值运算。
      ④一个特殊情况是,当值形参是实型变量名时,对应的实参可以是整型表达式。
      ⑤值形参作为子程序的局部量,当控制返回程序后,值形参的存储单元释放。

      2、变量形参
      变量形参的一般格式如§7.2.1所示,必须在形参前加关键字var。
      应该注意的是:
      ①与变量形参对应的实参只能是变量名,而不能是表达式。
      ②与变量形参对应的实参可以根据需要决定是否事先有值。
      ③变量形参与对应的实参的类型必须完全相同。
      ④对变量形参,运行时不另外开辟存储单元,而是与对应的实参使用相同的存储单元。也就是说,调用子程序时,是将实参的地址传送给对应的变量形参。
      ⑤当控制返回到调用程序后,变量形参的存储单元不释放,但变量形参本身无定义,即不得再使用。
      ⑥选用形式参时,到底是使用值形参还是变量形参,应慎重考虑。值形参需要另开辟存储空间,而变量形参会带来一些副作用。一般在函数中使用值形参,而在过程中才使用变量形参,但也有例外。

      例3 写出下列两个程序的运行结果。
      program ex1;            programex2;
       var a,b:integer;          vara,b:integer;
       procedure swap(x,y:integer);    procedure swap(Var x,y:integer) ;
        var t:integer;            vart:integer;
        begin                 begin
         t:=x;x:=y;y:=t;            t:=x;x:=y;y:=t;
        end;                  end;
       begin                  begin
        a:=1;b:=2;               a:=1;b:=2;
        writeln(a:3,b:3);            writeln(a:3,b:3);
        swap(a,b);                swap(a,b);
        writeln(a:3,b:3);             writeln(a:3,b:3);
       end.                   end.

      分析:这两个程序唯一的区别是ex1中将x,y作为值形参,而 ex2中将x,y作为变量形参,因此在ex2中对x,y的修改实际上是对调用该过程时与它们对应的变量a,b的修改,故最后,a,b的值为2,1。而ex1中调用swap过程时,只是将a,b的值传递给x,y,之后在过程中的操作与a,b无关。
      答:ex1的运行结果为: ex2的运行结果为:
          1 2         1 2
          1 2         2 1

      (二)全程变量、局部变量及它们的作用域
      在主程序的说明部分和子程序的说明部分均可以说明变量,但它们的作用范围是特定的。

      1、局部量及其作用域
      在介绍过程和函数的说明时,我们曾指出,凡是在子程序内部作用的变量,应该在本子程序内加以说明。这种在子程序内部说明的变量称为局部变量。形式参数也只是在该子程序中有效,因此也属于局部变量。
      一个变量的作用域是指在程序中能对此变量进行存取的程序范围。因此,局部变量的作用域就是其所在的子程序。实际上,局部变量只是当其所在的子程序被调用时才具有确定的存储单元,当控制从子程序返回到调用程序后,局部变量的存储单元就被释放,从而变得无定义。
      事实上,在子程序内定义的标号、符号常量、类型、子程序也与局部变量具有相同的作用域。

      2、全程量及其作用域
    全程量是指在主程序的说明部分中说明的量。全程量的作用域分两种情况:
      ①当全程量和局部量不同名时,其作用域是整个程序范围(自定义起直到主程序结束)。
      ②当全程量和局部量同名时,全程量的作用域不包含局部量的作用域。

      例4 写出下列程序的运行结果:
      program ex7_4;
       var x,y:integer;
       procedure a;
        var x:integer;
        begin
         x:=2;
         writeln('#',x,'#');
         writeln('#',y,'#');
        end;{of a}
       begin{main program}
        x:=1;y:=2;
        writeln('*',x,'*',y);
        a;
        writeln('***',x,'***',y);
       end.
      分析:程序中x,y是全局变量,但在过程a中也有变量x,故全程变量x的作用域为除过程a外的任何地方。而y的作用域包含了子程序a,即整个程序。
      答:运行结果如下:
        *1*2
        #2#
        #2#
        ***1***2
      评注:变量作用域内对变量的操作都是对同一存储单元中的量进行的。

    四、过程和函数的嵌套
      Pascal语言中,使用过程和函数,能使程序设计简短,便于阅读,节省存贮单元和编译时间。程序往往设计成分层结构,由一个主程序和若干个过程及函数组成。在过程或函数中,还可以说明另一些过程或函数,即过程或函数可以分层嵌套。在同一层中亦可说明几个并列的过程或函数。例如:
      

      上例过程的分层嵌套关系如下:0层主程序sample内并列两个1层过程P1a和P1b。过程P1a又嵌套两个2层过程p2a和p2b,2层的第二过程p2b又嵌套过程p3,p3就是第3层。其中p1b,p2a和p3不再嵌套别的过程,称为基本过程。这种分层结构的程序设计,特别要注意局部变量的使用范围和过程调用的要求。
      在主程序sample中定义的变量,可以在所有的过程中使用,主程序可调用p1a和p1b两个过程。过程p1a中定义的变量,只能在p2a,p2b和p3中使用。它能调用p2a,p2b两个过程,而不能调用p3和p1b。在过程p1b中定义的变量,只能在p1b中使用,它只能调用过程p1a。过程p2a不能调用任何过程。过程p2b可以调用并列过程p2a和p3,而过程p3可以调用p2a过程。
      过程调用是有条件的,过程定义在先,调用在后。同一层过程,后说明的过程可以调用先说明的过程。如果要调用在它后面定义的过程(或函数),可使用<向前引用>FORWARD这个扩充标识符。 要注意的是<向前引用>过程(或函数)首部中形式参数表写一次即可, 不必重复。如:
      procedure extend(var a,b:integer);
      forward;
      表示过程extend<向前引用>。因此,过程extend 的说明部分只须如下书写:
      procedure extend;
      <说明部分>
      begin
      :
      end;

    五、子程序(模块化)结构的程序设计

      例5
    对6到60的偶数验证哥德巴赫猜想:不小于6的偶数可分解成两个素数之和。
      分析:用布尔型函数prime(x)判断x是否是素数,若是, 函数值为真,否则,函数值为假。算法如下所示。
      1. t:=6
      2. while t≤60 do
      3. t11;
      4. repeat
      5. t11+2; /* 找下一个素数a */
      6. until prime(t1)andprime(t-t1); /*直到a,b都是素数*/
      7.writeln(i,'=',t1,'+',t-t1);
      8. tt+2;
      9. endwhile

      源程序如下:
      program ex9_7;
       var t,t1:integer;
       function prime(x:integer):boolean;
        var i:integer;
        begin
         if x=1
          then prime:=false
          else if x=2
              then prime:=true
              else begin
                  prime:=true;
                  i:=2;
                  while (i<=round(sqrt(x)))and(x modi<>0) do
                   i:=i+1;
                   if i<=round(sqrt(x)) thenprime:=false;
                 end;
         end;{of prime}

        begin
         t:=6;
         while t<=60 do
          begin
           t1:=1;
           repeat
            t1:=t1+2;
           until prime(t1) and prime(t-t1);
           writeln(t,'=',t1,'+',t-t1);
           t:=t+2;
          end;
       end.

      例6 编写一个给一个分数约分的程序。
      源程序如下:
      program ex7_6;   ┌──变量参数
        var a,b:integer; ↓
        procedure common(var x,y:integer);
         var i,j,k:integer;
         begin
          {求x,y的最大公约数}
          i:=x;j:=y;
          repeat
           k:=i mod j;
           i:=j;j:=k;
          until k=0;
           {对x,y进行约分}
            x:=x div i;y:=y div i;
          end;
         begin
           write('Input a,b=');readln(a,b);
           common(a,b);
           writeln(a,b:5);
          end.
      如输入:
      Input a,b=12 8
      则输出:
      3 2

    练习

      1. 输入5个正整数求它们的最大公约数。(提示:可用一个数组将5个数存放起来,然后求第一个数和第二个数的公约数,再求第三个数与前两个数公约数的公约数,这样求得前三个整数最大公约数……如此类推可求出5个整数的最大公约数)

      2. 给一维数组输入任意6个整数,假设为:
          7 4 8 9 1 5
        请建立一个具有以下内容的方阵:
          7 4 8 9 1 5
          4 8 9 1 5 7
          8 9 1 5 7 4
          9 1 5 7 4 8
          1 5 7 4 8 9
          5 7 4 8 9 1
        (请用子程序编写)。

      3. 求两个正整数的最小公倍数。

      4. 输入一个任意位的正整数,将其反向输出。

      5. 有五位同学,其各科成绩如下:
        学号 数学 语文 英语 总分 名次
         1  108  97 90
         2  98  88 100
         3  100  43 89
         4  84   63 50
         5  97   87 100
      (1)编写一个过程enter,输入每个学生成绩并计算各人的总分。
      (2)编写过程minci,用以排出每个人的名次。
      (3)按学号顺序输出。

     

     

    第十三章 动态数据类型

     

    前面介绍的各种简单类型的数据和构造类型的数据属于静态数据。在程序中,这些类型
    的变量一经说明,就在内存中占有固定的存储单元,直到该程序结束。
      程序设计中,使用静态数据结构可以解决不少实际问题,但也有不便之处。如建立一个
    大小未定的姓名表,随时要在姓名表中插入或删除一个或几个数据。而用新的数据类型──
    指针类型。通过指针变量,可以在程序的执行过程中动态地建立变量,它的个数不再受限制,
    可方便地高效地增加或删除若干数据。

    一、指针的定义及操作

      (一)指针类型和指针变量


      在pascal中,指针变量(也称动态变量)存放某个存储单元的地址;也就是说, 指针变量
    指示某个存储单元。
      指针类型的格式为:^基类型

      说明: ①一个指针只能指示某一种类型数据的存储单元,这种数据类型就是指针的基类
    型。基类型可以是除指针、文件外的所有类型。例如,下列说明:
      type pointer=^Integer;
            var p1,p2:pointer;
      定义了两个指针变量p1和p2,这两个指针可以指示一个整型存储单元(即p1、p2 中存放的是某存储单元的地址,而该存储单元恰好能存放一个整型数据)。

      ②和其它类型变量一样,也可以在var区直接定义指针型变量。
      例如:var a:^real; b:^boolean;
      又如:type person=record
               name:string[20];
               sex:(male,female);
               age:1..100
              end;
         var pts:^person;

      ③pascal规定所有类型都必须先定义后使用,但只有在定义指针类型时可以例外,如下
    列定义是合法的:
      type pointer=^rec;
          rec=record
            a:integer;
            b:char
           end;

      (二)开辟和释放动态存储单元

      1、开辟动态存储单元

      在pascal中,指针变量的值一般是通过系统分配的,开辟一个动态存储单元必须调用标
    准过程new。
      new过程的调用的一般格式:
      New(指针变量)
      功能:开辟一个存储单元,此单元能存放的数据的类型正好是指针的基类型,并把此存
    储单元的地址赋给指针变量。

      说明:①这实际上是给指针变量赋初值的基本方法。例如,设有说明:var p:^Integer;
    这只定义了P是一个指示整型存储单元的指针变量,但这个单元尚未开辟,或者说P中尚未有值(某存储单元的首地址)。当程序中执行了语句new(p)才给p赋值,即在内存中开辟(分配)一个整型变量存储单元,并把此单元的地址放在变量p中。示意如下图:

      
      (a)编译时给  (b)执行New(p)后  (c)(b)的简略表示
       p分配空间    生成新单元
      ?表示值不定  新单元的地址为XXXX
              内存单元示意图

      ②一个指针变量只能存放一个地址。如再一次执行New(p)语句,将在内存中开辟另外一个新的整型变量存储单元,并把此新单元的地址放在p中,从而丢失了原存储单元的地址。
      ③当不再使用p当前所指的存储单元时,可以通过标准过程Dispose释放该存储单元。

      ⒉释放动态存储单元
      dispose语句的一般格式:dispose(指针变量)
      功能:释放指针所指向的存储单元,使指针变量的值无定义。

      (三)动态存储单元的引用

      在给一个指针变量赋以某存储单元的地址后,就可以使用这个存储单元。
      引用动态存储单元一般格式:<指针变量>^

      说明:①在用New过程给指针变量开辟了一个它所指向的存储单元后,要使用此存储单元的唯一方法是利用该指针。
      ②对动态存储单元所能进行的操作是该类型(指针的基类型)所允许的全部操作。

      例1 设有下列说明:
      var p:^integer; i:integer;
      画出执行下列操作后的内存示意图:
        New(p); P^:=4;i:=p^;

      解: 如下图所示。
       
       (a)编译时 (b)执行New语句   (c)执行P^:=4   (d)执行i:=P^
        分配存储
         单元
                     内存单元示意图

      (四)对指针变量的操作

      前已述及,对指针所指向的变量(如P^)可以进行指针的基类型所允许的 全部操作。
    对指针变量本身,除可用New、Dispose过程外,尚允许下列操作:

      ⒈具有同一基类型的指针变量之间相互赋值

      例2
    设有下列说明与程序段:
      var p1,p2,p3:^integer;
      begin
       New(P1) ; New(P2); New(P3);
       P1:=P2; P2:=P3;
      end;

      2、可以给指针变量赋nil值
      nil是PASCAL的关键字,它表示指针的值为"空"。例如,执行:
      p1:=ni1后,p1的值是有定义的,但p1不指向任何存储单元。

      3、可以对指针变量进行相等或不相等的比较运算
      在实际应用中,通常可以在指针变量之间,或指针变量与nil之间进行相等(=)或不相等(<>=的比较,比较的结果为布尔量。

      例3 输入两个整数,按从小到大打印出来。
      分析:不用指针类型可以很方便地编程,但为了示例指针的用法,我们利用指针类型。定义一个过程swap用以交换两个指针的值。

      源程序如下:
      Type pointer=^integer;
      var p1,p2:pointer;
      procedure swap(var q1,q2:pointer);
       var q:pointer;
       begin
        q:=q1;
        q1:=q2;
        q2:=q;
       end;
      begin
       new(p1);new(p2);
       write('Input 2 data:');readln(pq^,p2^);
       if p1^>p2^ then swap(p1,p2);
       writeln('Output 2 data:',p1^:4,p2^:4);
      end.

      二、链表结构

      设有一批整数(12,56,45,86,77,……,),如何存放呢? 当然我们可以选择以前学过的数组类型。但是,在使用数组前必须确定数组元素的个数。如果把数组定义得大了,就会有大量空闲存储单元,定义得小了,又会在运行中发生下标越界的错误,这是静态存储分配的局限性。
      利用本章介绍的指针类型可以构造一个简单而实用的动态存储分配结构――链表结构。
      下图是一个简单链表结构示意图:
      

      其中:①每个框表示链表的一个元素,称为结点。
      ②框的顶部表示了该存储单元的地址(当然,这里的地址是假想的)。
      ③每个结点包含两个域:一个域存放整数,称为数据域,另一个域存放下一个结点(称为该结点的后继结点,相应地,该结点为后继结点的前趋结点)的地址。
      ④链表的第一个结点称为表头,最后一个结点表尾,称为指针域;
      ⑤指向表头的指针head称为头指针(当head为nil时,称为空链表),在这个指针变量中存放了表头的地址。
      ⑥在表尾结点中,由指针域不指向任何结点,一般放入nil。

      (一)链表的基本结构

      由上图可以看出:
      ①链表中的每个结点至少应该包含两个域;一是数据域,一是指针域。因此,每个结点都是一个记录类型,指针的基类型也正是这个记录类型。因此,head可以这样定义:
      type pointer=^ rec;
       rec=record
          data:integer;
          next:pointer;
         end;
       var head:pointer;
      ②相邻结点的地址不一定是连续的。整个链表是通过指针来顺序访问的,一旦失去了一个指针值,后面的元素将全部丢失。
      ③与数组结构相比,使用链表结构时;可根据需要采用适当的操作步骤使链表加长或缩 短,而使存储分配具有一定的灵活性。这是链表结构的优点。
      ④与数组结构相比,数组元素的引用比较简单,直接用"数组名[下标]"即可,因为数组元素占用连续的存储单元,而引用链表元素的操作却比较复杂。

      (二)单向链表的基本操作

      上图所示的链表称为单向链表。下面我们通过一些例题来说明对单向链表的基本操作,并假设类型说明如前所述。

      例6 编写一个过程,将读入的一串整数存入链表,并统计整数的个数。
      分析:过程的输入为一串整数,这在执行部分用读语句完成。过程的输出有两个:一是链表的头指针,一是整数的个数,这两个输出可以用变量形参来实现。
      由于不知道整数的个数,我们用一个特殊的9999作为结束标记。

      过程如下:
      procedure creat(var h:pointer;var n:integer);
      var p,q:pointer;x:integer;
      begin
        n:=0;h:=nil; read(x);
        while x<>9999 do
        begin
         New(p);
         n:=n+1;p^.data:=x;
         if n=1 then h:=p
         else q^.next:=p;
         q:=p;read(x)
        end;
        if h<>nil then q^.next:=nil;
        Dispose(p);
      end;

      例7 编一过程打印链表head中的所有整数,5个一行。
      分析:设置一个工作指针P,从头结点顺次移到尾结点,每移一次打印一个数据。

      过程如下:
      procedure print(head:pointer);
      var p:pointer; n:integer;
      begin
       n:=0;p:=head;
       while p<>nil do
        begin
         write(p^.data:8);n:=n+1;
         if n mod 5=0 then writeln;
         p:=p^.next;
        end;
       writeln;
      end;

      (三)链表结点的插入与删除

      链表由于使用指针来连接,因而提供了更多了灵活性,可以插入删除任何一个成分。
      设有如下定义:
      type pointer=^rec;
      rec=record
        data:integer;
        next:pointer
       end;
      var head:pointer;

      ⒈结点的插入
      如下图所示,要在P结点和Q结点之间插入一个结点m,其操作如下:
       

      只要作如下操作即可:
       New(m);
       read(m^.data);
       m^.next:=q;
       p^.next:=m; 

      例8 设链表head中的数据是按从小到大顺序存放的,在链表中插入一个数,使链表仍有序。
      分析:显然,应分两步:查找、插入。设po指向要插入的结点,若仅知道po应插在p之前(作为p的前趋结点)是无法插入的,应同时知道p的前趋结点地址q。
      当然,如果插在链表原头结点这前或原链表为空表或插在原尾结点之后,则插入时又必须作特殊处理。

      过程如下:
      procedure inserting(var head:pointer;x:integer);
      var po,p,q:pointer;
      begin
       new(po);po^.data:=x;
       p:=head;
       if head=nil{原表为空表}
        then begin
         head:=po;po^.next:=nil;
        end
       else begin
        while (p^.data<x)and(p^.next<>nil)do
        begin
         q:=p;p:=p^.next
        end;
        if p^.data>=x{不是插在原尾结点之后}
        then begin
          if head=p then head:=po
          else q^.next:=po;
          po^.next:=p
         end
       else begin
          po^.next:=po;
          po^.next:=nil
        end;
       end;
      end;

      ⒉结点的删除
      如下图所示,要在删除结点P的操作如下:
      

      要删除结点P,则只要将其前趋结点的指针域指向P 的后继结点即可。
      q^.next:=p^.next;
      dispose(p);

      例9 将链表head中值为X的第一个结点删除
      分析: 有三种情况存在:头结点的值为X; 除头结点外的某个结点值为X;无值为X的结点。为将前两种情况统一起来,我们在头结点之前添加一个值不为X的哨兵结点。
      算法分两步:查找、删除。

      过程如下:
      procedure deleteing(var head:pointer;x:integer);
      var p,q:pointer;
      begin
       New(p);p^.data:=x-1;p^.next:=head;
       head:=p;{以上为添加哨兵结点}
       while(x<>p^.data)and(p^.next<>nil)do
       begin
        q:=p;
        p:=p^.next
       end;
       if x=p^.data{存在值为X的结点}
       then q^.next:=p^.next
       else writeln('NOt found!');
       head:=head^.next{删除哨兵}
      end;

      (四)环形链表结构

      在单向链表中,表尾结点的指针为空。如果让表尾结点的指针域指向表头结点,则称为单向环形链表,简称单链环。如图所示。
        

                    单链环示意图
      (五)双向链表结构

      单链表中,每个结点只有一个指向其后继结点的指针域。如果每个结点不仅有一个指向其后继结点的指针域,还有一个指向其前趋的指针域,则这种链表称为双向链表。如图所示。
         

               双向链表示意图
      可用如下定义一个数据域为整型的双向链表:
        type pointer=^node;
          node=record
             prev:pointer;
             data:integer;
             next:pointer;
            end;
      对双向链表的插入、删除特别方便。与单向链环相似,我们还可定义双向链环。

      三、综合例析

      例10
    读入一串以"#"为结束标志的字符,统计每个字符出现的次数。
      分析:设置一个链表存放,每读入一个字符,就从链表的头结点向后扫描链表,如果在链表中此字符已存在,则其频率加1,否则将该字符的结点作为链表的新头结点,相应频率为1。

      源程序如下:
      program ex11_10;
      type ref=^letters;
         letters=record
             key:char;
             count:integer;
             next:ref;
            end;
       var k:char;
        sentinel,head:ref;
       procedure search(x:char);
       var w:ref;
       begin
        w:=head;
        sentinel^.key:=x;
        while w^.key<>x do w:=w^.next;
        if w<>sentinel
        then w^.count:=w^.count+1
        else begin
            w:=head;new(head);
            with head^ do
             begin
               key:=x;count:=1;next:=w;
           end
      end;
      end;{of search}
      procedure printlist(w:ref);
       begin
        while w<>sentinel do
         begin
          writeln(w^.key:2,w^.count:10);
          w:=w^.next;
         end;
       end;{of printlist}
      begin{main program}
       new(sentine);
       with sentinel^ do
        begin
         key:='#';count:=0;next:=nil;
        end;
       head:=sentinel;
       read(k);
       while k<>'#' do
        begin
         search(k);read(k);
        end;
       printlist(head);
      end.

      例11 用链表重写筛法求2~100之间所有素数程序。
      源程序如下:
      program ex11_12;
       uses crt;
       type link=^code;
          code=record
             key:integer;
             next:link;
            end;
       var head:link;
       procedure printlist(h:link);{打印链表h}
        var p:link;
        begin
         p:=h;
         while p<>nil do
          begin
           write(p^.key,'-->');
           p:=p^.next;
          end;
        end;
       procedure buildlink;{建立链表}
        var p,q:link;
        i:integer;
        begin
         new(head);
         head^.key:=2;
         p:=head;
         for i:=3 to 100 do
          begin
           new(q);
           q^.key:=i;
           q^.next:=nil;
           p^.next:=q;
           p:=q;
          end;
        end;
       procedure prime;{筛法将找到的素数的倍数从链表中删除}
        var h,p,q:link;
        begin
         h:=head;
         while h<>nil do
          begin
           p:=h;q:=p^.next;
           while q<>nil do
            if (q^.key mod h^.key=0) then
             begin
              p^.next:=q^.next;
              dispose(q);
              q:=p^.next;
             end
            else begin
                p:=q;
                q:=q^.next;
               end;
           h:=h^.next;
          end;
        end;
       begin{main program}
         clrscr;
         buildlink;
         printlist(head);
         writeln;
         prime;
         printlist(head);
        end.


                        练习
      1、围绕着山顶有10个洞,一只兔子和一只狐狸各住一个洞,狐狸总想吃掉兔子。一天兔子对狐狸说,你想吃我有一个条件,你先把洞编号1到10。你从第10号洞出发,先到第1号洞找我,第二次隔一个洞找我,第三次隔两个洞找我,以后依次类推,次数不限。若能找到我,你就可以饱餐一顿,在没找到我之前不能停止。狐狸一想只有10个洞,寻找的次数又不限,哪有找不到的道理,就答应了条件。结果就是没找着。
      利用单链环编程,假定狐狸找了1000次,兔子躲在哪个洞里才安全。

      2、某医院病房的订位管理中, 将病人的记录按姓名的字母顺序排成一个链表。试编写程序,从键盘上输入下列字母,就可对病员记录进行管理:
      (1)i───新病人入院(插入一个病员记录)。
      (2)d───病人出院(删除一个病员记录,并显示该记录)。
      (3)s───查询某病员记录(显示该记录或"未找到")。
      (4)q───在屏幕上列出所有的病员记录并结束程序。

    3、编写一个简单的大学生新生入校登记表处理程序。

     

    第十四课    

     

    在DOS操作中,我们所谈及的文件称之为外部文件。外部文件是存储在外部设备上, 如:外存储器上,可由计算机操作系统进行管理,如用dir、type等命令直接对文件进行操作。

      Pascal所谈及的文件,称之为内部文件。内部文件的特点是文件的实体(实际文件)也是存储在外存储器上,成为外部文件的一分子,但在使用时必须在程序内部以一定的语句与实际文件联系起来,建立一一对应的关系,用内部文件的逻辑名对实际文件进行操作。内部文件的逻辑名必须符合PASCAL语言标识符的取名规则。

      Pascal中的文件主要用于存放大量的数据。如:成绩管理,原始数据很多,使用文件先将其存入磁盘,通过程序读出文件中的数据再进行处理,比不使用文件要来得方便、有效。

      Pascal中的一个文件定义为同一类型的元素组成的线性序列。文件中的各个元素按一定顺序排列,可以从头至尾访问每一个元素,从定义上看,文件与数组相似,但它们之间有着明显不同的特征,主要表现在:

      (1)文件的每一个元素顺序存贮于外部文件设备上(如磁盘上)。因此文件可以在程序进行前由Pascal程序或用文字编辑软件,如edit、ws、Turbo Pascal的edit命令等产生,或在运行过程中由程序产生,且运行完后,依然存贮在外部设备上。
      (2)在系统内部,通过文件指针来管理对文件的访问。文件指针是一个保存程序在文件中位置踪迹的计算器,在一固定时刻,程序仅能对文件中的一个元素进行读或写的操作,在向文件写入一个元素或从文件读取一个元素后,相应的文件指针就前进到下一元素位置。而数组是按下标访问。
      (3)在文件类型定义中无需规定文件的长度即元素的个数,就是说元素的数据可动态改变,一个文件可以非常之大,包含许许多多元素,也可以没有任何元素,即为一个空文件。而数组的元素个数则是确定的。

      使用文件大致有以下几个步骤;
      (1)说明文件类型,定义文件标识符;
      (2)建立内部文件与外部文件的联系;
      (3)打开文件;
      (4)对文件进行操作;
      (5)关闭文件。
      Turbo Pascal将文件分为三类:文本文件(顺序)、有类型文件(顺序或随机)和无类型文件(顺序或随机)。下面将介绍这些文件及其操作。

    一、文本文件

      文本文件又称为正文文件或行文文件,可供人们直接阅读,是人机通信的基本数据形式之一。文本文件可用文字编辑程序(如DOS的edit或TurboPascal的编辑命令edit)直接建立、阅读和修改, 也可以由PASCAL程序在运行过程中建立。

      1、文本文件的定义:
      文本文件的类型为TEXT,它是由ASCII字符组成的,是Pascal提供的标准文件之一。标准文件 TEXT已由Pascal说明如下:
      TYPE TEXT=FILE OF CHAR;
      因此,TEXT同标准类型INTEGER、READ等一样可以直接用于变量说明之中,无需再由用户说明。 例如:
      VAR F1,F2:TEXT;
      这里定义了两个文本文件变量F1和F2。

     2、文本文件的建立
      文本文件的建立有两种方法:

      (1)直接用Turbo Pascal的Edit建立原始数据文件。
      例1
     将下表中的数据存入名为A.dat的文件中。
       3 4
       29 30 50 60
       80 90 70 75
       60 50 70 45
      操作步骤:
      ①进入Turbo Pascal的编辑状态;
      ②输入数据;
      ③存盘,文件名取A.dat。
      此时,已将数据存入文本文件A.dat中。文本文件也可用DOS中的Edit等软件建立。

      (2)用程序的方式建立中间数据或结果数据文件。
      用程序的方式建立文件操作步骤为:
      ①定义文本文件变量;

      ②把一外部文件名赋于文本文件变量,使该文本文件与一相应外部文件相关联;
      命令格式:ASSIGN(f,name)
      f为定义的文本文件变量
      name为实际文件文件名
      如:ASSIGN(F1,`FILEIN.DAT`)
      或:ASSIGN(F1,`PAS\FILEIN.RES`)
      这样在程序中对文本文件变量F1的操作,也就是对外部实际文件`FILEIN.DAT`或`FILEIN.RES`的操作。上例中文件`FILEIN.DAT`是存贮在当前目录中,而文件`FILEIN.RES`则是存贮在PAS子目录中。

      ③打开文本文件,准备写;
      命令格式1:REWRITE(f)
      功能:创建并打开新文件准备写,若已有同名文件则删除再创建
      命令格式2:APPEND(f)
      功能:打开已存在的文件并追加

      ④对文件进行写操作;
      命令格式:WRITE(f,<项目名>)
      或:WRITELN(f,<项目名>)
      功能:将项目内容写入文件f中

      ⑤文件操作完毕后,关闭文件。
      命令格式:CLOSE(f)

      例2 从键盘上读入表12.1的数据,用程序写入名为B.dat的文件中。
       

      3、读取文本文件
      文本文件内容读出操作步骤:
      ①定义文本文件变量;
      ②用ASSIGN(f,name)命令,将内部文件f与实际文件name联系起来;
      ③打开文本文件,准备读;
      命令格式:READ(f,<变量名表>) READLN(f,<变量名表>)
      功能:读文件f中指针指向的数据于变量中
      文本文件提供了另外两个命令,在文本的操作中很有用处,它们是:
      EOLN(f):回送行结束符  
      EOF(f):回送文件结束符
      ⑤文件操作完毕,用CLOSE(f)命令关闭文件。

      例3 读出例12.1建立的文本文件,并输出。
        

      由于文本文件是以ASCII码的方式存储,故查看文本文件的内容是极为方便,在DOS状态可使用 DOS中TYPE等命令进行查看,在Turbo Pascal中可以象取程序一样取出文件进行查看。

      4、文本文件的特点

      (1)行结构
      文本文件由若干行组成,行与行之间用行结束标记隔开,文件末尾有一个文件结束标记。由于各行长度可能不同,所以无法计算出给定行在文本文件中的确定位置,从而只能顺序地处理文本文件,而且不能对一文本文件同时进行输入和输出。
      (2)自动转换功能
      文本文件的每一个元素均为字符型,但在将文件元素读入到一个变量(整型,实型或字符串型)中时,Pascal会自动将其转换为与变量相同的数据类型。与此相反在将一个变量写入文本文件时,也会自动转移为字符型。

      例4 某学习小组有10人,参加某次测验,考核6门功课, 统计每人的总分及各门的平均分,将原始数据及结果数据放入文本文件中。

      分析
      (1)利用Turbo Pascal的EDIT建立原始数据文件TESTIN.DAT存贮在磁盘中,其内容如下:
      10 6
      1 78 89 67 90 98 67
      2 90 93 86 84 86 93
      3 93 85 78 89 78 98
      4 67 89 76 67 98 74
      5 83 75 92 78 89 74
      6 76 57 89 84 73 71
      7 81 93 74 76 78 86
      8 68 83 91 83 78 89
      9 63 71 83 94 78 95
      10 78 99 90 80 86 70

      (2)程序读入原始数据文件,求每人的总分及各门的平均分;
      (3)建立结果数据文件,文件名为TEXTIN.RES.

      程序:

      
      
      

      例5 读入一个行长不定的文本文件。排版,建立一个行长固定为60个字符的文件, 排版要求:(1)当行末不是一个完整单词时,行最后一个字符位用'-'代替, 表示与下一行行头组成完整的单词;(2)第一行行头为两个空格,其余各行行头均不含有空格。

      分析
      (1)建立原始数据文件。
      (2)程序边读入原始数据文件内容,边排版。
      (3)每排完一行行长为60字符,并符合题中排版条件,写入目标文件中。

      设原始数据TEXTCOPY.DAT文件内容如下:
      Pavel was arrested.
      That dat Mother did not light the stove.
      Evening came and a cold wind was blowing.
      There was a knock at the window.
      Then another.
      Mother was used to such knocks,but this time she gavea little start of joy.
      Throwing a shawl over her shoulders,she opened thedoor.

      程序:

      
       
      

      对TEXTCOPY.DAT文本文件运行程序得到排版结果文件TEXTCOPY.RES内容如下:
        Pavel was arrested.That dat Mother did not lightthe stov-
      evening came and a cold wind was blowing.There was aknock
      at the window.Then another.Mother was used to suchknocks,b-
      ut this time she gave a little start of joy.Throwinga shawl
      over her shoulders,she opened the door.

    二、有类型文件

      文本文件的元素均为字型符。若要在文件中存贮混合型数据,必须使用有类型文件。

      1、有类型文件的定义
      有类型文件中的元素可以是混合型的,并以二进制格式存贮,因此有类型文件(除了字符类型文件,因为它实质上是文本文件)不象文本文件那样可以用编辑软件等进行阅读和处理。
      有类型文件的类型说明的格式为:
      类型标识符=File of 基类型;
      其中基类型可以是除了文件类型外的任何类型。例如:
      FILE1=FILE OF INTEGER;
      FILE2=FILE OF ARRAY[1--10] OF STRING;
      FILE3=FILE OF SET OF CHAR;
      FILE4=FILE OF REAL;
      FILE5=FILE OF RECORD;
      NAME:STRING;
      COURSE:ARRAY[1--10] OF READ;
      SUN:READ;
      END;
      等等,其中FILE2,FILE3,FILE5中的数组、集合、记录等类型可以先说明再来定义文件变量。
      例如:
      VAR
      F1:FILE;
      F2,F3:FILE3;
      F4:FILE5;
      与前面所有类型说明和变量定义一样,文件类型说明和变量定义也可以合并在一起,例如:
      VAR
      F1:FILE OF INTEGER;
      F2,F3:FILE OF SET OF CHAR;
      F4:FILE OF RECORD
      NAME:STRING;
      COURSE:ARRAY[1--10] OF REAL;
      SUM:READ;
      END;

      Turbo Pascal对有类型文件的访问既可以顺序方式也可以用随机方式。
      为了能随机访问有类型文件,Turbo Pascal提供如下几个命令:
      命令格式1:seek(f,n)
      功能:移动当前指针到指定f文件的第n个分量,f为非文本文件,n为长整型
      命令格式2:filepos(f)
      功能:回送当前文件指针,当前文件指针在文件头时,返回,函数值为长整型
      命令格式3:filesize(f)
      功能:回送文件长度,如文件空,则返回零,函数值为长整型

      2、有类型文件的建立
      有类型文件的建立只能通过程序的方式进行,其操作步骤与文本文件程序方式建立的步骤相仿,不同之处:(1)有类型文件的定义与文本文件的定义不同;(2)有类型文件可以利用SEEK命令指定指针随机写入。

      3、有类型文件的访问
      有类型文件访问的操作步骤与文本文件的程序访问操作步骤相仿,区别之处:(1)有类型文件的定义与文本文件的定义不同;(2)有类型文件可以利用SEEK命令访问文件记录中的任一记录与记录中的任一元素。

      例6 建立几个学生的姓名序、座号、六门课程成绩总分的有类型文件。

      分析:为简单起见,这里假设已有一文本文件FILEDATA.TXT,其内容如下:
      10
      li hong
      1 89 67 56 98 76 45
      wang ming
      2 99 87 98 96 95 84
      zhang yi hong
      3 78 69 68 69 91 81
      chang hong
      4 81 93 82 93 75 76
      lin xing
      5 78 65 90 79 89 90
      luo ze
      6 96 85 76 68 69 91
      lin jin jin
      7 86 81 72 74 95 96
      wang zheng
      8 92 84 78 89 75 97
      mao ling
      9 84 86 92 86 69 89
      cheng yi
      10 86 94 81 94 86 87

      第一个数10表示有10个学生,紧接着是第一个学生的姓名、座号、6科成绩,然后是第二个学生,等等。
      从文本文件中读出数据,求出各人的总分,建立有类型文件,设文件名为filedata.fil,文件的类型为记录studreco,见下例程序。

      程序:
      
        
        
       

      例7 产生数1-16的平方、立方、四次方表存入有类型文件中, 并用顺序的方式访问一遍,用随机方式访问文件中的11和15两数及相应的平方、立方、四次方值。

      分析:建立有类型文件文件名为BIAO.FIL,文件的类型为实数型。
      (1)产生数1-16及其平方、立方、四次方值,写入BIAO.FIL,并顺序读出输出;
      (2)用SEEK指针分别指向11和15数所在文件的位置,其位置数分别为10×4和14×4(注意文件的第一个位置是0),读出其值及相应的平方、立方、四次方值输出。

      程序:

      
       

      程序运行结果如下:
      

      另外,Turbo Pascal还提供了第三种形式文件即无类型文件,无类型文件是低层I/O通道,如果不考虑有类型文件、 文本文件等存在磁盘上字节序列的逻辑解释,则数据的物理存储只不过是一些字节序列。这样它就与内存的物理单元一一对应。无类型文件用128个连续的字节做为一个记录(或分量)进行输入输出操作,数据直接在磁盘文件和变量之间传输,省去了文件缓解区,因此比其它文件少占内存,主要用来直接访问固定长元素的任意磁盘文件。
      无类型文件的具体操作在这里就不一一介绍,请参看有关的书籍。

    三、综合例析

      例8
     建立城市飞机往返邻接表。文本文件CITY.DAT内容如下:
      第一行两个数字N和V;
      N代表可以被访问的城市数,N是正数<100;
      V代表下面要列出的直飞航线数,V是正数<100;
      接下来N行是一个个城市名,可乘飞机访问这些城市;
      接下来V行是每行有两个城市,两城市中间用空格隔开,表示这两个城市具有直通航线。
      如:CITY1 CITY2表示乘飞机从CITY1到CITY2或从CITY2到CITY1。
      生成文件CITY.RES,由0、1组成的N×N邻接表。
      邻接表定义为:
               

      分析
      (1)用从文本文件city.dat中读入N个城市名存入一些数组CT中;
      (2)读入V行互通航班城市名,每读一行,查找两城市在CT中的位置L、K,建立邻接关系,lj[l,k]=1和lj[k,j]=1;
      (3)将生成的邻接表写入文本文件CITY.RES中。

      设CITY.DAT内容如下:
      10 20
      fuzhou
      beijin
      shanghai
      wuhan
      hongkong
      tiangjin
      shenyan
      nanchan
      chansa
      guangzhou
      fuzhou beijin
      fuzhou shanghai
      fuzhou guangzhou
      beijin shanghai
      guangzhou beijin
      wuhan fuzhou
      shanghai guangzhou
      hongkong beijin
      fuzhou hongkong
      nanchan beijin
      nanchan tiangjin
      tiangjin beijin
      chansa shanghai
      guangzhou wuhan
      chansa beijin
      wuhan beijin
      shenyan beijin
      shenyan tiangjin
      shenyan shanghai
      shenyan guangzhou

      程序:

      
       

      得到CITY.RES文件内容如下:
      10
      1 fuzhou
      2 beijin
      3 shanghai
      4 wuhan
      5 hongkong
      6 tiangjin
      7 shenyan
      8 nanchan
      9 chansa
      10 guangzhou
      0 1 1 1 1 0 0 0 0 1
      1 0 1 1 1 1 1 1 1 1
      1 1 0 0 0 0 1 0 1 1
      1 1 0 0 0 0 0 0 0 1
      1 1 0 0 0 0 0 0 0 0
      0 1 0 0 0 0 1 1 0 0
      0 1 1 0 0 1 0 0 0 1
      0 1 0 0 0 1 0 0 0 0
      0 1 1 0 0 0 0 0 0 0
      1 1 1 1 0 0 1 0 0 0

      例9 对例12.3的FILEDATE.FIL文件内容按总分的高低顺序排序。
      分析:
      文件的排序就是将文本文件的各分量按一定要求排列使文件有序,文件排序有内排序和外排序二种,内排序是指将文件各分量存入一个数组,再对数组排列,最后将该数组存入原来的文件。外排列不同于内排列,它不是将文件分量存入数组,而是对文件直接排序,内排序比外排序速度要快,但当文件很大时,无法调入内存,此时用外排序法较合适。
      本程序使用过程SEEK,实现外排序。

      程序:
      
        
       


                      习 题

      1、编一程序,计算文本文件中行结束标志的数目。
      2、计算文本文件的行长度的平均值、最大值和最小值。
      3、一文本文件FILE.DAT存放N个学生某学科成绩,将成绩转换成直方图存入FILE.RES文件中。
      如FILE.DAT内容为:
      5
      78 90 87 73 84
      得到直方图文件FILE.RES内容为:
      5
      ********
      *********
      *********
      *******
      ********
      4、银行账目文件含有每一开户的账目细节:开户号、姓名、地址、收支平衡额。写一程序,读入每一开户的账目,生成银行账目文件。

    5、通讯录文件每个记录内容为:姓名、住址、单位、邮编、电话,编一程序按姓名顺序建立通讯录文件,要求先建立文件,再对文件按姓名顺序进行外排序。

     

     

    附录一   Pascal中的字符串函数和数学函数

    字符串函数

    求长度length
    定义:function Length(S:String): Integer;
    例子:
    var
    S: String;
    begin
    Readln (S);
    Writeln('"', S, '"');
    Writeln('length = ', Length(S));
    end.

    复制子串copy
    定义:function Copy(S: String; Index: Integer; Count: Integer): String;

    注意:S 是字符串类型的表达式。Index和Count是整型表达式。Copy 返回S中从Index开始,Count个字符长的一个子串。
    例子:
    var S: String;
    begin
    S := 'ABCDEF';
    S := Copy(S, 2, 3); { 'BCD' }
    end.

    插入子串insert
    定义:procedureInsert(Source: String; var S: String; Index: Integer);

    注意:Source 是字符串类型的表达式。 S 是任意长度字符串类型变量。Index 是整型表达式。Insert 把 Source插在S中Index处。如果结果字符串的长度大于255,那么255之后的字符将被删除。

    例子:
    var
    S: String;
    begin
    S := 'Honest Lincoln';
    Insert('Abe ', S, 8); { 'Honest Abe Lincoln' }
    end.

    删除子串delete
    定义:procedureDelete(var S: String; Index: Integer; Count:Integer);

    注意:S 是字符串类型变量。 Index和Countare是整型表达式。Delete 删除S中从Index开始的Count个字符。如果Index大于S的长度,则不删除任何字符;如果Count大于S中从Index开始的实际字符数,则删除实际的字符数。

    例子:
    var
    s: string;
    begin
    s := 'Honest Abe Lincoln';
    Delete(s,8,4);
    Writeln(s); { 'Honest Lincoln' }
    Delete(s,9,10);
    Writeln(s); { 'Honest L' }
    end.

    字符串转为数值val
    定义:procedure Val(S; var V; var Code: Integer);
    在这里:
    S 是由一系列数字字符构成的字符串类型变量;。
    V 是整型或实型变量;
    Code 是Integer型变量

    注意:Val将S转为它的数值形式。
    例子:
    var s:string;I, Code: Integer;
    begin
    s:='1234';
    val(s,i,code);
    writeln(i); { 1234 }
    end.


    数值转为字符串str
    定义:procedure Str(X [: Width [: Decimals ]]; var S:string);

    注意:将数值X转成字符串形式。
    例子:
    var
    S: string[11];
    begin
    Str(I, S);
    IntToStr := S;
    end;
    begin
    Writeln(IntToStr(-5322));
    Readln;
    end.

    求子串起始位置pos
    定义:functionPos(Substr: String; S: String): Byte;

    注意:Substr和S字符串类型表达式。Pos在S中搜索Substr并返回一个integer值。这个值是Substr的第一个字符在S中的位置。如果在S中没有找到Substr,则Pos返回0。

    例子:
    var S: String;
    begin
    S := ' 123.5';
    { Convert spaces to zeroes }
    while Pos(' ', S) > 0 do
    S[Pos(' ', S)] := '0';
    end.

    字符完全串连+
    定义:操作符+把两个字符串联在一起。
    例子:
    var s1,s2,s:string;
    begin
    s1:='Turbo ';
    s2:='pascal';
    s:=s1+s2; { 'Turbo pascal' }
    end.

    字符串压缩空格串连-
    定义:操作符-去掉第一个字符串最后的空格后,将两个字符串联在一起。
    例子:
    var s1,s2,s:string;
    begin
    s1:='Turbo ';
    s2:='pascal';
    s:=s1-s2; { 'Turbopascal' }
    end.

     

    Pascal中的数学函数

    求绝对值函数abs(x)
    定义:function Abs(X):(Same type as parameter);
    说明:X可以是整型,也可以是实型;返回值和X的类型一致
    例子:
    var
    r: Real;
    i: Integer;
    begin
    r := Abs(-2.3); { 2.3 }
    i := Abs(-157); { 157 }
    end.

    取整函数int(x)
    定义:functionInt(X: Real): Real;
    注意:X是实型数,返回值也是实型的;返回的是X的整数部分,也就是说,X被截尾了(而不是四舍五入)
    例子:
    var R: Real;
    begin
    R := Int(123.567); { 123.0 }
    R := Int(-123.456); { -123.0 }
    end.

    截尾函数trunc(x)
    定义:function Trunc(X: Real): Longint;
    注意:X是实型表达式. Trunc 返回Longint型的X的整数部分
    例子:
    begin
    Writeln(1.4, ' becomes ', Trunc(1.4)); { 1 }
    Writeln(1.5, ' becomes ', Trunc(1.5)); { 1 }
    Writeln(-1.4, 'becomes ', Trunc(-1.4)); { -1 }
    Writeln(-1.5, 'becomes ', Trunc(-1.5)); { -1 }
    end.

    四舍五入函数round(x)
    定义:functionRound(X: Real): Longint;
    注意:X是实型表达式. Round 返回Longint型的X的四舍五入值.如果返回值超出了Longint的表示范围,则出错.
    例子:
    begin
    Writeln(1.4, ' rounds to ', Round(1.4)); { 1 }
    Writeln(1.5, ' rounds to ', Round(1.5)); { 2 }
    Writeln(-1.4, 'rounds to ', Round(-1.4));{ -1 }
    Writeln(-1.5, 'rounds to ', Round(-1.5));{ -2 }
    end.

    取小数函数frac(x)
    定义:functionFrac(X: Real): Real;
    注意:X 是实型表达式. 结果返回 X 的小数部分; 也就是说,Frac(X)= X - Int(_X).
    例子:
    var
    R: Real;
    begin
    R := Frac(123.456); { 0.456 }
    R := Frac(-123.456); { -0.456 }
    end.
     
    求平方根函数sqrt(x)和平方函数sqr(x)
    定义:
    平方根:function Sqrt(X: Real): Real;
    注意:X 是实型表达式. 返回实型的X的平方根.
    平方:function Sqr(X): (Same type as parameter);
    注意:X 是实型或整型表达式.返回值的类型和X的类型一致,大小是X的平方,即X*X.

    例子:
    begin
    Writeln('5 squared is ', Sqr(5)); { 25 }
    Writeln('The square root of 2 is ',Sqrt(2.0)); { 1.414 }
    end.

     

     

    附录三  关于fillchar的使用和讨论
     

    大家听说过fillchar这个标准过程吧。 很好用的。
    var a:array [1..10] of arrtype;

    执行fillchar(a,sizeof(a),0);
    当arrtype为
    1.real(其他差不多)                                   使得a中的元素全部成为0.0
    2.integer(byte,word,longint,shortint都相同)   全部为0
    3.boolean                                                全部为false
    4.char                                                    全部为#0

    执行fillchar(a,size(a),1);
    写几个特殊的
    1.integer           全部为157(不知道为什么)
    2.real                很大的一个数,同上。
    3.boolean           全部为true
    4.byte,shortint    全部为1,所以integer不行可以暂时用这两个嘛。 要不然就减去156

     

    附录五   程序的调试() ——步骤法

        任何一个天才都不敢说,他编的程序是100%正确的。几乎每一个稍微复杂一点的程序都必须经过反复的调试,修改,最终才完成。所以说,程序的调试是编程中的一项重要技术。我们现在就来掌握一下基本的程序调试。
    我们以下的示范,是以时下比较流行的Borland Pascal 7.0为例子,其他的编程环境可能略有不同,但大致上是一致的。


         我们先编一个比较简单的程序,看看程序是如何调试的。

    program tiaoshi;
    var i:integer;
    begin
         for i:=1 to 300 do
         begin
               if i mod 2 = 0 then
                      if i mod 3 = 0 then
                           if i mod 5 = 0 then
                                            writeln(i); 
         end;
    end.

        该程序是输出300以内同时能被2,3,5整除的整数。 现在我们开始调试。调试有多种方法,先介绍一种,权且叫步骤法,步骤法就是模拟计算机的运算,把程序每一步执行的情况都反映出来。通常,我们有F8即STEP这个功能来实现,如图:
    不断地按F8,计算机就会一步步地执行程序,直到执行到最后的“end.”为止。


         可能你还没有发现F8的威力,我们不妨把上面的程序略微修改一下,再配合另外的一种调试的利器watch,你就会发现步骤法的用处。

    program tiaoshi;
    var i:integer;
        a,b,c:boolean;
    begin
         for i:=1 to 300 do
         begin
               a:=false;
               b:=false;
               c:=false;
               if i mod 2 = 0 then a:=true;
               if i mod 3 = 0 then b:=true;
               if i mod 5 = 0 then c:=true;
               if a and b and c then writeln(i); 
         end;
    end.

        如图,我们单击菜单栏中debug选项,里面有一项叫watch的选项,我们单击它。

         就会出现一个watch窗口:
         watch窗口可以让我们观察变量的变化情况,具体操作是在watches窗口内按Insert键:
         这时,屏幕上弹出一个菜单,我们输入所需要观察的变量名,我们分别输入i,a,b,c这4个变量名,于是watches窗口内就有如下的4个变量的状态:


         这时,我们再次使用步骤法,我们会发现,这4个变量的状态随着程序的执行而不断变化,比如:

         这样我们就可以方便地知道执行每一步之后,程序的各个变量的变化情况,从中我们可以知道我们的程序是否出错,在哪里出错,方便我们及时地修改。
         下一次,我们介绍另外的一种方法,断点法

    程序的调试(二) ——断点法

     

    在前面我们已经学习了基本的程序调试方法——步骤法。步骤法有一个缺点,就是在遇到循环次数比较多或者语句比较多的时候,用起来比较费时,今天我们来学习一种新的也是常用的调试方法——断点法。
          所谓断点法,就是在程序执行到某一行的时候,计算机自动停止运行,并保留这时各变量的状态,方便我们检查,校对。我们还是以前面求同时能被2,3,5整除的3000以内的自然数为例,具体操作如下:
          我们把光标移动到程序的第14行,按下ctrl+F8,这时我们会发现,该行变成红色,这表明该行已经被设置成断点行,当我们每次运行到第14行的时候,计算机都会自动停下来供我们调试。
          我们必须学以致用,赶快运用刚学的watch方法,看看这家伙到底有多厉害。

    请记住,计算机是执行到断点行之前的一行,断点行并没有执行,所以这时b:=true这一句并没有执行。

          断点行除了有以上用处之外,还有另外一个重要用处。它方便我们判断某个语句有没有执行或者是不是在正确的时刻执行,因为有时程序由于人为的疏忽,可能在循环或者递归时出现我们无法预料的混乱,这时候通过断点法,我们就能够判断程序是不是依照我们预期的顺序执行。

    附录六   Pascal的多种退出语句用法

     

    1、  break 是用来退出其所在的循环语句

     

    即 : 不论在任何一个循环语句中 执行了 break 的话, 马上退出这个语句。
    相当于 : goto 这一层循环语句 最末尾一句的下一句。
    例如:var i : integer;
    begin
    for i := 1 to 10 do
    begin
    {1} writeln(i);
    break;
    writeln(i+1);
    end;
    readln
    end.
    执行结果 :
             1

    可见 第一次循环 时 , 执行了{1}句 后 , 执行 break ,然后马上退出了这个for语句。 {*****} 注意 : 以上两个语句 只 对 它们所在的 那层循环语句 起作用,也就是说 : 如果有多个 循环语句 相嵌套, 其中 某一层 执行了continue / break 语句, 它们并不能影响上面几层的循环语句。

    2、exit 是退出当前程序块;

    即 : 在任何子程序 中执行 exit ,那么 将退出 这个子程序;如果是在 主程序中执行 exit , 那么将退出整个程序。相当于 : goto 这个程序块 的 末尾 的 end 例如 : 试除法判断素数时,一旦整除,就把函数值赋为false ,然后exit;{******}注意 : 类似上面的 , exit也是只对当前 这一个 子程序产生作用,如果多重嵌套子程序 , 那么其中某个子程序执行了exit以后,将返回到 调用它的那个语句 的下一个语句。

    3、halt : 没什么好说的,退出整个程序,Game Over.

    例如 : 搜索时, 一旦找到一个解,就打印,然后执行halt,退出整个程序。使用exit , halt 应该注意的地方:要注意所有可能会退出 子程序或主程序 的地方 均要妥善处理好善后工作,比如 文件是否关闭,输出是否完整等。

    最后说一句 , 使用这些语句 使得程序结构不止有一个出口,破坏了结构化程序设计的 标准控制结构 , 使程序难以调试 (但是往往便于编写),应尽量避免使用,因为它们完全可以用其它语句代替,所以,除非使用这些语句 能给 编写程序 带来 较大的方便,且可读性不受到影响,才值得一用。

    http://wenku.baidu.com/view/4a1b27d604a1b0717ed5dd25.html?re=view


    展开全文
  • Pascal教程

    万次阅读 多人点赞 2015-12-17 12:48:47
    第一节 Pascal 程序结构和基本语句 2 第二节 顺序结构程序与基本数据类型 6 第二章 分支程序 10 第一节 条件语句与复合语句 10 第二节 情况语句与算术标准函数 12 第三章 循环程序 16 第一节 for ...

    第一章 简单程序 2

    第一节  Pascal 程序结构和基本语句 2

    第二节  顺序结构程序与基本数据类型 6

    第二章 分支程序 10

    第一节  条件语句与复合语句 10

    第二节  情况语句与算术标准函数 12

    第三章 循环程序 16

    第一节 for 循环 16

    第二节 repeat 循环 22

    第三节 While 循环 27

    第四章 函数与过程 32

    第一节 函数 32

    第二节 自定义过程 35

    第五章 Pascal的自定义数据类型 40

    第一节 数组与子界类型 40

    第二节 二维数组与枚举类型 48

    第三节 集合类型 56

    第四节 记录类型和文件类型 60

    第五节  指针类型与动态数据结构 67

    第六章 程序设计与基本算法 73

    第一节 递推与递归算法 73

    第二节 回溯算法 80

    第七章 数据结构及其应用 86

    第一节 线性表 86

    第二节 队列 90

    第三节 栈 93

    第四节 数组 97

    第八章 搜索 100

    第一节 深度优先搜索 100

    第二节 广度优先搜索 111

    第九章 其他常用知识和算法 115

    第一节 图论及其基本算法 115

    第二节 动态规划 122


    第一章 简单程序

    无论做任何事情,都要有一定的方式方法与处理步骤。计算机程序设计比日常生活中的事务处理更具有严谨性、规范性、可行性。为了使计算机有效地解决某些问题,须将处理步骤编排好,用计算机语言组成“序列”,让计算机自动识别并执行这个用计算机语言组成的“序列”,完成预定的任务。将处理问题的步骤编排好,用计算机语言组成序列,也就是常说的编写程序。在Pascal语言中,执行每条语句都是由计算机完成相应的操作。编写Pascal程序,是利用Pascal语句的功能来实现和达到预定的处理要求。“千里之行,始于足下”,我们从简单程序学起,逐步了解和掌握怎样编写程序。

     

    第一节  Pascal 程序结构和基本语句

    在未系统学习Pascal语言之前,暂且绕过那些繁琐的语法规则细节,通过下面的简单例题,可以速成掌握Pascal程序的基本组成和基本语句的用法,让初学者直接模仿学习编简单程序。

     

    [1.1]编程在屏幕上显示“Hello World!”。

      Pascal程序:

    Program ex11;

    Begin

      Writeln(‘Hello World!’);

      ReadLn;

    End.

    这个简单样例程序,希望大家的程序设计学习能有一个良好的开端。程序中的Writeln是一个输出语句,它能命令计算机在屏幕上输出相应的内容,而紧跟Writeln语句后是一对圆括号,其中用单引号引起的部分将被原原本本地显示出来。

     

    [1.2]已知一辆自行车的售价是300元,请编程计算a辆自行车的总价是多少?

    解:若总售价用m来表示,则这个问题可分为以下几步处理:

    ①从键盘输入自行车的数目a

    ②用公式  m=300*a  计算总售价;

    ③输出计算结果。

    Pascal程序:

      Program Ex12;   {程序首部}

      Var a,m : integer; {说明部分}

      Begin {语句部分}

        Write(‘a=’);

        ReadLn(a); {输入自行车数目}

        M := 300*a; {计算总售价}

        Writeln(‘M=’,m); {输出总售价}

        ReadLn; {等待输入回车键}

      End.

     

    此题程序结构完整,从中可看出一个Pascal 程序由三部分组成:

    (1)程序首部

    由保留字Program开头,后面跟一个程序名(:Exl1);其格式为:

                Program  程序名;

    程序名由用户自己取,它的第一个字符必须是英文字母,其后的字符只能是字母或数字和下划线组成,程序名中不能出现运算符、标点符和空格。

    (2)说明部分

    程序中所用的常量、变量,或类型、及过程与自定义函数,需在使用之前预先说明,定义数据的属性(类型)。[1.2] 程序中 Var SRC: Real; 是变量说明,此处说明SRC三个变量均为实数类型变量。只有被说明为某一类型的变量,在程序中才能将与该变量同类型的数值赋给该变量。变量说明的格式为:   

                 Var  变量表:类型;

    (3)语句部分

    指由保留字 Begin (开始)至 End. (结尾)之间的语句系列,是解决问题的具体处理步骤,也是程序的执行部分。

    Pascal程序不管是哪部分,每句末尾都必须有分号(),但允许最接近 End 的那个语句末尾的分号省略;程序结束的End末尾必须有圆点(. ),是整个程序的结束标志。

    程序中花括号“{  }”之间的部分为注释部分。

    Pascal程序结构可归纳用如下的示意图来表示:

      Program  程序名;         程序首部

       标号说明; (Label)

        常量说明; (Const)          说明部分

        类型说明; (Type)

        变量说明; (Var)

        过程或函数说明;  

        Begin                      程序体 (主程序)

       语句系列;          语句部分

        End.

       图1.1  Pascal程序的结构

     

    把处理问题的步骤编成能从上到下顺序执行的程序,是简单程序的基本特征。再来分析下面两道例题的Pascal程序结构和继续学习基本语句。

     

    [例1.3]编程计算半径为R的圆的面积和周长。

    解:这是一个简单问题,按数学方法可分以下几步进行处理:

        ① 从键盘输入半径的值R;          {  要求告诉圆的半径R }

        ② 用公式  S=πR2  计算圆面积;

        ③ 用公式  C=2πR  计算圆周长;

        ④ 输出计算结果。

    Pascal程序:

      Program  Ex13 {程序首部 }

      Var  RSC: Real;          {说明部分 }

      Begin  {语句部分 }

        Write ('R=?')

        Readln(R);                 {输入半径 }

        S:=Pi*R*R;                {圆面积公式S=πR2}

        C:=2*Pi*R;                {圆周长公式C=2πR

        Writeln('S='S);            {输出结果 }

        Writeln('C='C);                       

        Readln                     {等待输入回车键}                   

      End.

    程序中PiPascal提供的标准函数,它返回圆周率的近似值:3.1415926…。

    (:=)是赋值符号,赋值语句的格式为:

             变量:=表达式;

    赋值语句的作用是将:=右边表达式的值记录到左边的变量中。

    Writeln是输出语句,输出语句有三种格式:

           ① Write (输出项1,输出项2) ;   {执行输出后光标不换行}

           ② Writeln (输出项1,输出项2)  {执行输出后光标换到下一行}

           ③ Writeln                       {仅输出空白且光标换到下一行}

    Writeln语句后面的圆括号以内部分均为输出项,可以是多项,各项间用逗号分隔; 对单引号里的内容按照引号内的原样(字符)输出显示。如果输出项是表达式,则只输出表达式的值,而不是表达式本身。

     

    [1.4] 输出两个自然数相除的商和余数。

    :设被除数、除数、商和余数,分别为ABCD,均为变量,且都是整数类型。题中未给出具体的自然数AB,可采用键盘输入方式。

       ① 给出提示,从键盘输入a, b

       ② 显示两数相除的数学形式;

       ③ 求出a除以b的商c

       ④ 求出a除以b的余数d

       ⑤ 紧接等式后面输出显示商和余数。

    Pascal程序:

      Program Ex14

        Var a,b,c,d : integer

        Begin

          Write('INPUT AB');       {给出提示信息}

          Readln(ab);                {输入ab

          Writeln;                     {输出一空行}

          Write(a'/'b'=');          {输出等式之后不换行}

          c:=a div b;                   {整除运算,取商的整数部分}

          d:=a mod b;                  {相除求余运算,取商的余数部分}

          Writeln(C''d);           {输出后自动换行 }

          Readln                        {等待输入回车键 }

        End.

    执行本程序中第一个Write语句,输出其引号以内的一串提示信息,是给紧接着的输入语句提供明确的提示(要求),有“一目了然,人机对话”之效果。

    Readln是一个特殊的输入语句,要求输入一个回车(换行)才能往下执行。 

    Readln是输入语句,它的一般格式为

                  ① Read     (变量1,变量2);  

                  ② Readln   (变量1,变量2);  

                  ③ Readln

    前两种格式均要从键盘给变量输入数据,输入时,所键入的数据之间以空格为分隔,以回车为输入结束。若多输入了数据(即数据个数超过变量个数)Read语句读完数据之后,能让后续的读语句接着读取多下来的数据;而Readln 语句对本行多输入的数据不能让后续语句接着读取多下来的数据。为了防止多输入的数据影响下一个输入语句读取数据,建议尽量使用Readln语句输入数据。第三种格式不需输入数据,只需按入一个回车键。

     

    [1.5]自然数的立方可以表示为两个整数的平方之差,比如43=102-62,请输出自然数1996的这种表示形式。(这里的43用自乘三次的形式4*4*4表示;102也用自乘二次的形式10*10表示)

    :此题没有现成的计算公式能直接利用,但可以自行推出处理方法或构建适当的运算公式,按着构想的处理方案编排出各步骤。

    设这个自然数为N,两个平方数分别为XY, 将问题表示为求 N3=X2—Y2

    ① 先找出X的值,仔细观察题中的示例,用数学方法归纳可得出X=N*(N+1)/2;(构成本题可用的计算公式)

    ② 再仔细观察,发现Y值比X小一个N值,即 Y=XN

    ③ 输出等式 N3=X2—Y2 或N*N*N=X*XY*Y

    Pascal程序:

      Program  Ex15

        Const  N=1996;       {常量说明 }

        Var    XY: Longint;  {变量说明,此题计算中的数值较大,用长整型 }

          Begin

            X:=N*(N+1) div 2;  { div 是整除运算 }

            Y:=X-N

            Writeln(N,'*',N,'*', N,'=', X,'*', X,'',Y,'*',Y);   输出结果 }

            Readln

          End.

     本程序中N是常量,XY是变量,为长整数类型(Longint); 程序中的div 是整除运算,其结果只取商的整数部分; 

     

    [1.6] 求一元二次方程x2+3x+2=0的两个实数根。

    :方程的系数是常量,分别用abc表示,可运用数学上现成的求根公式求方程的根,采取如下方法:

      ① 先求出d=b2-4ac;(求根公式中需用开方运算的那部分)

      ② 再用求根公式算出x1x2的值。(x1x2 = ? )

      ③ 输出x1x2.

      Pascal程序:

        program Ex16;

        Const a=1;                        {常量说明 }

             b=3

             c=2;                         {abc表示方程系数}

        Var  d  : integer;                   {d为整型变量}

             X1X2: Real;                  {X1X2为实型变量}

         Begin

           d:=b*b-4*a*c

           x1:=(-b+sqrt(d))/(2*a);              {求方程的根}

           x2:=(-b-sqrt(d))/(2*a)

           Writeln('X1='X1'':6'X2='X2){输出结果}

           Readln                             {等待输入一个回车键}

         End.

     本程序中的abc均为常量;变量d是整数类型,而变量x1x2则是实数类型,因为运算式中的Sqrt(d)开平方运算和(/)除法运算使结果为实数。Sqrt( ) 是开平方函数,是Pascal系统的一个标准函数。

     

    习题1.1   模仿例题编程

    1. 加法计算器:编程由键盘输入两个整数ab,计算出它们的和并输出到屏幕上。

    2. 某梯形的上底、下底和高分别为8129,求该梯形的面积。

         ( 梯形面积公式为 S=                                      

     

    3. 求右图所示边长为5.6  的正立方体表面积。

     

    4. 已知图园柱体的高为12,底面园的半径为7,求园柱体表面积。

     

    5. 计算某次考试语文、数学、英语和计算机等四科的总成绩与平均成绩。

    (请用输入语句从键盘输入各科成绩分)

    第二节  顺序结构程序与基本数据类型

    前面的简单程序已体现出处理问题步骤、思路的顺序关系,这就是顺序结构程序。

    [例1.7]交换两个变量的值:由键盘输入两个正整数AB,编程交换这两个变量的值。

    解:交换两个变量的值,可以想象成交换两盒录音带(称为AB)的内容,可以按以下步骤处理:

    步骤①:拿一盒空白录音带C为过渡,先将A翻录至C

    步骤②:再将B翻录至A

    步骤③:最后将C翻录至B

    这样操作,可达到题目要求。

    Pascal程序:

    Program Exam17;

    Var a,b,c : integer;

    Begin

      Write(‘A,B=’);

      Readln(a,b);

      C:= A;  {等价于步骤1

      A := B; {等价于步骤2

      B := C; {等价于步骤3

      Writeln(A,B);

    End.

    [1.8] 分钱游戏。甲、乙、丙三人共有24元钱,先由甲分钱给乙、丙两人,所分给的数与各人已有数相同;接着由乙分给甲、丙,分法同前;再由丙分钱给甲、乙,分法亦同前。经上述三次分钱之后,每个人的钱数恰好一样多。 求原先各人的钱数分别是多少?

    :设甲、乙、丙三人的钱数分别为ABC。用倒推(逆序)算法, 从最后结果入手,按反相顺序,分步骤推算出每次各人当时的钱数:(在每个步骤中,各人钱数分别存在ABC中)

      步骤①: A=8 B=8  C=8   {这是最后结果的钱数,三人都一样多 }

      步骤②: A=A/2 (=4) B=B/2 (=4) C=A+B+C(=16) { AB未得到丙分给的钱时,只有结果数的一半;C应包含给AB及本身数三者之和 }

      步骤③: A=A/2 (=2) C=C/2 (=8) B=A+B+C(=14)  {AC未得到乙分给的钱时,只有巳有数的一半;B应包含给AC及本身数三者之和 }

      步骤④: B=B/2 (=7) C=C/2 (=4) A=A+B+C(=13)

     C未得到甲分给的钱时,只有巳有数的一半;A应包含给BC及本身数三者之和 }

      步骤⑤: 输出A=13B=7C=4{此时的AB就是三人原先的钱数 }

    Pascal程序:

    Program Exam18

    Var abc: integer

    Begin

      a:=8; b:=8; c:=8              {对应于步骤①}

    a:=a div 2; b:=b div 2; c:=a+b+c;                {对应于步骤②}

      a:=a div 2; c:=c div 2; b:=a+b+c;                {对应于步骤③}

      b:=b div 2; c:=c div 2; a:=a+b+c;                {对应于步骤④}

      Writeln('a='a'  ': 4'b='b'  ': 4'c='c) ;  {输出}

      Readln

    End.

    细心观察,会发现本程序语句的顺序很关键。此例用反推顺序(逆序),按步骤正确推算出各变量的值。当然,有的问题可按正序步骤编程,这类程序都称为顺序程序。

    本程序Writeln语句的输出项含有(  '  ' :  4 ),这里的冒号用来指定该项显示所占宽度,此处是输出4个空格即(空格项占4)

     

    [1.9] 有鸡兔同笼,头30,脚 90,究竟笼中的鸡和兔各有多少只?

    :设鸡为J只,兔为T只,头为H,脚为F,则:

    J+T=30 ①

    2*J+4*T=90     

    解此题暂不必采用数学上直接解方程的办法,可采用“假设条件与逻辑推理”的办法:

     假设笼中30 个头全都是兔,那么都按每头4只脚计算,总脚数为(4*H),与实际脚数 ( F )之差为(4*HF),如果这个差=0,则笼中全是兔(即鸡为0只);如果这个差值 >0,说明多计算了脚数,凡是鸡都多给算了两只脚,用它除以2就能得到鸡的只数,处理步骤为:

      ①  J=(4*HF)/2       {先用脚数差值除以2算出鸡的只数}

      ②  T=HJ                {再用总头数减鸡数算出免的只数}

    按此方法,这两步运算必须注意先后顺序才会符合运算逻辑。

    Pascal程序:

    Program Exam16

    Const H=30;                 {常量说明 }

         F=90

    Var JT: byte;            {为字节类型的整数 }

    Begin

      J:=(4*H-F) div 2;    {整除运算 }

      T:=H-J

      Writeln ('J='J'  ': 6'T= 'T ) 

      Readln

    End.

    本程序中HF为常量,变量JTbyte类型,属于整数类型。

    Pascal定义了五个标准整数类型,如下表所示:

    类型

    取值范围

    占字节数

    格式

    Shortint(短整型)

    -128..127

    1

    带符号8

    Integer (整型)

    -32768..32767

    2

    带符号16

    Longint(长整型)

    -2147483648..2147483647

    4

    带符号32

    Byte (字节型)

    0..255

    1

    无符号8

    Word  (字型)

    0..65535

    2

    无符号16

     在前面程序中常用的数据类型除整数类型,还有实数类型。Pascal 还定义了五个标准实数类型,列表所示如下:

    类型

    取值范围

    占字节数

    有效数字

    Real

    2.9×10-39~1.7×1038

    6

    7~8

    Single

    1.5×10-45~3.4×1038

    4

    11~12

    Double

    5.0×10-324~1.7×10308

    8

    15~16

    Extended

    1.9×10-4951~1.1×104932

    10

    19~20

    Comp

    -263+1~238-1

    8

    19~20

     

    Turbo Pascal 中实数的表示用科学记数法,可认为由三部分组成:

                  # .  ## E +## 或  # . ## E -##

    ①  ###表示有效数字; ② E表示以10为底的幂; ③ +##-##是指数部分,+号可省略。

    例如:   1.7E+38 可写成1.7E38 (等同于1. 7×1038 )

    在实数类型定义下,即使是整数,在程序执行时系统也将自动转换成科学记数形式,试请运行下面程序并注意观察运行结果:

    Program Exam17

    Var x: real;                   {x为实数类型 }

    Begin

      X:=180;                 {把整数180赋给实数类型变量X}

      Writeln ('x='x) ;      {输出的x自动表示成实数形式 }

      Readln

    End.

    习题1. 2

    1.已知△ABC中的三边长分别为25.7674.0359.31,求△ABC的面积。

        ( 计算公式: S=                                          。 其中P =                 )

    2.某车棚存有自行车和三轮车共65辆,它们的轮子数合计为150个。求该棚内存有的自行车和三轮车各是多少辆?

    3.甲、乙、丙三人分别有磁带364864盒。先由甲把自己的磁带平均分为三份,分给乙、丙各一份,自己留下一份;接着是乙,最后是丙,都按甲的方法处理。编程输出甲、乙、丙在上述过程中各人的磁带数分别是多少? (输出所有的中间结果)

    4.五位好朋友相聚。第一位朋友带来了很多糖块赠送给各位朋友,使每人的糖块在各自原有的基础上翻了一倍;接着第二位好友也同样向每人赠送糖块,他同样使每人的糖块在各人已有的数量上翻了一倍;第三、第四、第五位好友都照此办理。经过这样的赠送之后,每人的糖块恰好都为32块。问各位好友原先的糖块数分别是多少?


    第二章 分支程序

     

    在程序设计中,许多问题是在一定条件下才选择某种处理方式的,这就需要用条件判断语句或情况选择语句进行处理。程序执行中将出现选择(分支),根据条件只选择执行部分语句,不一定都是按原顺序从头到尾地执行所有语句,这样的程序称为分支程序。

    第一节  条件语句与复合语句

    [2.1] 某服装公司为了推销产品,采取这样的批发销售方案:凡订购超过100 套的,每套定价为50元,否则每套价格为80元。编程由键盘输入订购套数,输出应付款的金额数。

    :X为订购套数,Y为付款金额,则:

     

       ① 输入X;

       ② 判断 值;

       ③ 根据判断结果选择符合条件的那种方法计算Y值;

       ④ 输出计算结果。

    Pascal程序:

    Program Exam21

    Var xy: integer

    Begin

      Write('X=') Readln(x) ;                   {  输入X}

      if x >100 then y:=50*X  else  y:80*X;      {条件判断与选择 }

      Writeln('y='y) 

      Readln

    End.

    程序中的 if 语句常称为条件语句,它的一般格式为:

        (1) if 条件 then 语句;

        (2) if 条件 then 语句1  else  语句2

    IF 语句的功能是按条件在两种可能中选择其中一种。习惯上把if 后面的表达式称为条件,then 后面的语句称为真项,else 后面的语句称为假项。若条件成立(为真)就执行真项,然后执行if语句的后继语句;若条件不成立(为假)就跳过真项而执行假项,然后执行后继语句。而第一种格式只有真项,没有假项,当条件不成立(为假)就什么也不需做,直接往下去执行后继语句。

     

    [2.2] 读入三个不同的数,编程按由小到大的顺序排列打印出来。

    :设读入的三个数为abc,为了把较小的数排在前面,可作如下处理:

       ① 如果ab就交换ab的值,将较大的值换至后面;

       ② 如果ac就交换ac的值,将较大的值换至后面;

       ③ 如果bc就交换bc的值,将较大的值换至后面;

       ④ 输出处理后的a,b,c

        Pascal程序:

        Progranm  Exam22

        Var abct: Real

        Begin

            Write('Input a, bc=')

            Readln(abc)

            if ab then 

    begin                    {复合语句}

                 t:=a; a:=b; b:=t       {交换ab}

               end

            if ac then 

    begin                    {复合语句}

                 t:=a; a:=c; c:=t        {交换ac}

               end

            if bc then 

    begin                   {复合语句}

                  t:=b; b:=c; c:=t         {交换bc}

                end

            Writeln('abc:'a:6, b:6, c:6)

            Readln

        End.

    if 语句规定它的真项或假项位置上只能是一个基本语句,如果需要写一组语句,就应当使用复合语句。本程序中有三处用到复合语句。每个复合语句的范围是从Begin开始到与它相对应的End为止。复合语句的地位和一个基本语句相同;其一般格式为:

            Begin  

              语句系列  

            End

     

    习题2. 1

    1.假设邮局规定寄邮件时若每件重量在1公斤以内(1公斤),按每公斤1.5元计算邮费,如果超过1公斤时,其超出部分每公斤加收0.8元。请编程序计算邮件收费。

    2.输入三个正整数,若能用这三个数作为边长组成三角形,就计算并输出该三角形的面积,否则输出Can't(组成三角形的条件为:任意两边之和大于第三边)

    3.输入一个三位数的整数,将数字位置重新排列,组成一个尽可大的三位数。例如:输入213,重新排列可得到尽可能大的三位数是321

     

    第二节  情况语句与算术标准函数

     

    如果有多种(两种或两种以上)选择,常用情况语句编程。

    将前面[2.1]改成用如下方法来处理。根据题意,付款计算可分为两种情况:

    ① Y=50*X    (X100)

    ② Y=80*X    (X=100)

    显然,情况①与②的选择取决于X值。假设用N表示“情况值”,暂且先让N2

    如果X100N=1;(此题中N的值只是12,且取决于X值)

    Pascal 程序:

    Program Exam21_1

    Var XYN: integer

    Begin

      Write('X=') readln(x) ; n:=2;     先让n=2 }

      if X100 then n:=1;                {如果X100则 n=1 }

      Case n  of                         { 关于情况处理 }

        1: Y:=50*X

        2: Y:=80*X

      end

      Writeln('Y='Y) ; 

      Readln

    End.

    程序中的 Caseend 语句为情况语句,是多路分支控制,一般格式为:

       Case 表达式 of

         情况常量表1: 语句1

         情况常量表2: 语句2

            :        :

         情况常量表n: 语句n

        end

     

    执行情况语句时,先计算Case后面表达式的值,然后根据该值在情况常量表中的“对应安排”,选择其对应的语句执行,执行完所选择语句后就结束Case语句;如果常量表中没有一个与表达式值对应的语句,则什么也不做就结束本Case语句。

     

    Case 语句的另一种应用格式为:

         Case 表达式 of

            情况常量表1: 语句1

            情况常量表2: 语句2

               :        :

            情况常量表n: 语句n

            else  语句 n+1

         end

    这种格式的前面部分是相同的,所不同的是:如果常量表中没有一个与表达式值对应的语句,则执行与else对应的语句,然后结束Case语句。

    [2.2] 对某产品征收税金,在产值1万元以上征收税5%;在1万元以下但在5000

    以上的征收税3%;在5000元以下但在1000元以上征收税2%1000元以下的免收税。编程计算该产品的收税金额。

    :x为产值,tax为税金,用P表示情况常量各值,以题意中每1000元为情况分界:

           P=0:                 tax=0        (x<1000 )

           P=1234:        tax=x*0.02    (1000<=x<5000 )

           P=56789:     tax=x*0.03    (5000<X<=10000 )

           P=10:                tax=x*0.05    (x> 10000 )

        这里的P是“情况”值,用产值x除以1000的整数值作为P,如果P>10也归入P=10的情况。Pascal语言用P=trunc(x/1000)取整计算,

    Pascal程序:

    Program Exam22

    Var  xp :  integer

        Tax  : real

    Begin

      Write('Number=') ; readln(x) 

      P:=trunc(x/1000) ;  

    if P9 then P:=10

      Case  P  of

         0 :  tax:=0

         1,2,3,4 :  tax:=x*0.2

         5,6,7,8,9 :  tax:=x*0.3

         10 :  tax:=x*0.5

      end

      Writeln('tt='tt:5:2) 

      Readln

    End.

    情况表达式的计算必须考虑到“全部”情况,不要有遗漏。如果情况常量表的“值”在某范围内是连续的,可将常量表写成:

                             n1.. n2:语句;

    因此,上面程序中的情况常量表可以写成如下程序中表示形式:

    Program Exam22_1

    Var x,p: integer

       tax: real

    Begin

      Write('Number=') ; readln(x) 

      P:=trunc(x/1000) ;  

    if P9 then P:=10

      Case  P  of

        0 : tax:=0

        1..4 : tax:=x*0.2;  14作为同一情况处理 }

        5..9 : tax:=x*0.3;  59作为同一情况处理 }

        10 : tax:=x*0.5

      end

      Writeln('tt='tt:5:2) 

      Readln

    End.

    程序中的trunc(x)为取整函数,是Pascal的算术标准函数之一。Pascal常用的算术标准函数有19:

        (1) abs(x) x的绝对值(|x|)

        (2) exp(x) ex的值; (e为无理数2.71828…)

        (3) frac(x)x的小数部分;

        (4) int(x) x的整数部分(不舍入,函数值为实型)

        (5) ln(x)  求以e为底的x的对数(log ex  )

        (6) odd(x) 判断x的奇偶数(x为奇数时odd(x)值为true,否则为false)

        (7) ord(x) x的序号,结果为整型(x为有序类型量)

        (8) pi    π值(3.1415926535897932)

        (9) pred (x) x(有序类型)的前趋值;

        (10) succ(x) x(有序类型)的后继值;

        (11) random 随机函数,产生01的随机值;

        (12) random(n)产生0n的随机数(nword类型,先执行randomize, 才能得到随机整数)

        (13) round(x) x的四舍五入整数;

        (14) trunc(x) x的整数部分(截掉小数部分,结果为整型)

        (15) sqr(x) x的平方值(x2 )

        (16) sqrt(x) x的开平方根值(         )

    (17) sin(x) x的正弦函数(x为弧度)

    (18) cox(x) x的余弦函数(x为弧度)

    (19) arctan(x) 正切的反三角函数(x为数值)

     

    习题2.2

    1.运输公司计算运费时,距离(S)越长,每公里运费越低,标准如下:

          如果S250公里;运费为标准运价的100%                

          如果250公里<=S500公里,运费为标准运价的98%;     

          如果500公里<=S1000公里,运费为标准运价的95%;    

          如果1000公里<=S2000公里,运费为标准运价的92%;   

          如果2000公里<=S3000公里,运费为标准运价的90%;   

          如果S=>3000公里,运费为标准运价的85%;。请编计算运费的程序。

    2. 输入考试成绩,如果获85分以上为 A等,获60分~84分为B等,60分以下为C等,编程输出考试等级。

    3. 某车间按工人加工零件的数量发放奖金,奖金分为五个等级:每月加工零件数N < 100者奖金为10元;100 < = N < 110者奖金为30元;110 < = N <120 者奖金为50元;120 < = N <130 者奖金为70元;N > 130者为80元。

    请编程,由键盘输入加工零件数量,显示应发奖金数。


    第三章 循环程序

    在编程中经常遇到需要多次规律相同的重复处理,这就是循环问题。Turbo Pascal采用不同的循环方式来实现,常用的环循有三种:  forrepeatwhile. 

     

    第一节 for 循环

    for循环是一种自动计数型循环。

    [3.1] 试打印出1~20的自然数。

    解:① 用a代表1~20各数,同时也用a兼作计数,以控制循环次数;

        ② 让a1开始;

        ③ 输出a

        ④ a自动计数(加1),如果未超越所规定的循环范围则重复步骤③,否则结束循环。

    Pascal程序:

    Program Exam12

    Var a: byte

    Begin

      for a:=1 to 20 do

        Writeln (a)

      Readln

    End.

     

    程序中  for a:=1 to 20 do Writeln (a); 是for循环语句。

    for 循环语句有两种格式:

    (1) for 循环变量:=初值  To 终值 do 语句;     

        (2) for 循环变量:=初值 downto 终值 do 语句;

    (1)种格式的初值小于等于终值,循环变量值按自动加1递增变化;

    (2)种格式的初值大于或等于终值,循环变量值按自动减1递减变化。for 循环是 (以递增1或以递减1) 计数型循环。

    比如若将[3.1]程序改为倒计数(递减)循环,则输出201的自然数数

    Program Exam31

    Var a: byte

    Begin

      for a:=20 downto 1 do

    Writeln(a) 

      Readln

    End.

    [例3.2]打印出3060的偶数。]

    解:

    方法一:

    ①设a表示3060的所有的数,可用for循环列出;

    ②用式子 a mod 2=0 筛选出其中的偶数并输出。

    Pascal程序:

    Program ex32;

    Var a : integer;

    Begin

      For a := 30 to 60 do

    If (a mod 2=0) then writeln(a);

      Readln;

    End.

    在这个程序中,for循环后的循环语句是一个条件分支语句。

     

    方法二:我们知道,在式子a=2*n中,若n取自然数123…,时,则a依次得到偶数246…。因此要想得到3060的偶数,就可以让上面式子中的n1530的自然数就可以了。所以本题还可以按以下步骤处理:

    ①设n表示1530的所有自然数,可用for循环列出;

    ②用式子 a := 2*n 求出其中的偶数;

    ③将结果输出至屏幕。

    Pascal程序:

    Program ex32;

    Begin

      For n := 15 to 30 do

        Begin

      a := 2*n;

      Writeln(a);

    End;

      Readln;

    End.

     

    [例3.3]自然数求和:编一个程序,求从1100的自然数的和。

    解:① 令S0; 

    ② 令a表示1100的自然数,用循环列出;

    ③ 将这些自然数用公式S:=S+a 逐一累加到S中去;

    ④ 循环结束后,S即为1100的自然数的和,输出即可。

    Pascal程序:

    Program ex33;

    var s,a : integer;

    Begin

      S := 0;

      For a := 1 to 100 do

        S := S+a;

      Writeln(‘S=’,S);

      Readln;

    End.

     

    [3.4]一个两位数x,将它的个位数字与十位数字对调后得到一个新数y,此时y恰好比x36,请编程求出所有这样的两位数。

    解:① 用for循环列举出所有的两位数,x为循环变量;

    ② 用公式a:= x div 10分离出x的十位数字;

    ③ 用公式b:= x mod 10分离出x的个位数字;

    ④ 用公式y:= b*10+a合成新数y

    ⑤ 用式子y-x=36筛选出符合条件的数x并输出。

    Pascal程序:

    Program ex34;

    Begin

      For x := 10 to 99 do

      Begin

      a := x div 10;

      b := x mod 10;

      y := b*10+a;

      if y-x=36 then writeln(x);

    End;

    Readln;

    End.

     

    [3.5] 把整数3025从中剪开分为3025两个数,此时再将这两数之和平方,(30+25)2=3025计算结果又等于原数。求所有符合这样条件的四位数。

    :设符合条件的四位数为N,它应当是一个完全平方数,用(a*a)表示。

       ① 为了确保N=(a*a)在四位数(10009999)范围内,可确定a3299循环;

       ② 计算N=a*a;将四位数N拆分为两个数n1n2

       ③ 若满足条件(n1+n2)*(n1+n2)就输出 

    Pascal程序:

    Program  Exam35

    Var Na, xn1n2: Integer

    Begin   

    for a:=32 to 99 do

      begin  

    N:=a*a

        n1:= N div 100;     {拆取四位数的前两位数}

        n2:= N-n1*100;     {拆取四位数的后两位数}

        X:=n1+n2

        if  x*x=N  then  writeln (N)

      end

      Readln

    End.

     

    [3.6]用“*”号打印出如下的长方形图案。

      *********

        *********

      *********

      *********

    解:① 上面给出的图例共有4行,我们可以用一个循环控制行的变化;

    ② 在每行中又有9列,我们可以在前面控制行的循环中再套一个循环来控制列的变化。

    Pascal程序:

    Program ex36;

    Begin

      For a := 1 to 4 do {外循环控制行的变化}

      Begin

      For b := 1 to 9 do {内循环控制列的变化}

        write(‘*’);

      Writeln; {输出一行的“*”后换行}

    End;

    Readln;

    End.

    程序中的循环对于a的每个值都包含着一个b=(19)次的内循环。外循环for a 将内循环for b 包含在里面,称为for循环的嵌套。嵌套形式如:

                 for a:=n1 to n2 do     

                     for b:=m1 to m2 do  循环体语句;  

     

    [3.7] 打印出九九乘法表:

    :a为被乘数,范围为19b为乘数,范围为1a;乘式为a*b=(a,b的乘积),则

       a=1:    b=1a   1*1=1

       a=2:    b=1a   2*1=2   2*2=4

       a=3:    b=1a   3*1=3   3*2=6   3*3=9

       a=4:    b=1a   4*1=4   4*2=8   4*3=13   4*4=16

         :      :     

       a=9     b=1a   9*1=9   9*2=18  …       9*9=81

    ⑴从上面分解的横行中看到共有9行,这里的“行”数变化与a的变化从19相同,可用a控制“行”的循环;

    ⑵每“行”里面相乘的次数与b的范围相关,由b控制每“行”里面的“内部”循环;

    ⑶内循环被包含在最里层,执行完每“行”的内部循环,就到下一“行”去执行新“行”里面的循环,每“行”都拥有形式相同的( b=1)内循环。

    即每到一“行”都要执行该“行”的内循环。这里所指的“行”可以理解成抽象的行,不一定是实际上具体对应的行,可以是一个处理“块”。

    Pascal程序:

    Program Exam37

    Var ab: byte

    Begin

      for a:=1 to 9 do  {外循环 }

       begin

         for b:=1 to a do                  {内循环 }

           write(a,’’,b,’’,a*b,’  ’:3)

         writeln

       end

      Readln

    End.

     

    根据这种格式还可以实现多层循环嵌套,例如:

                for a:=n1 to n2 do     

                    for b:=m1 to m2 do 

                         for c:=k1 to k2 do  循环体语句;  

        

    [3.8]从七张扑克牌中任取三张,有几种组合方法?请编程输出所有组合形式。

    解:设每次取出三张分别为a,b,c。用三重循环分别从17的范围里取值;为了排除取到重号,用(a-b)*(b-c)*(a-c) < >0进行判断。

    Pascal程序:

    program Exam38;

    const  n=7;

    var  a,b,c,t: integer;

    Begin

      t:=0;

     for a:=1 to n do

       for b:=1 to n do

     for c:=1 to n do

           if (a-b) * (b-c) * (a-c) < >0 then

       Begin

      inc (t);  

    writeln (a:3, b:3, c:3)

       End;

     writeln ( total:, t :5);

     readln

     End.

     [3.9] 数学上把除了1和它本身,没有别的数能够整除它的自然数叫做素数(或质数)。现在由键盘输入一个自然数N,编程判断N是否是素数,是则输出“Yes”,否则输出“No”。

    解:根据定义,对于给定的自然数N,只需判断除1和它本身外,还有没有第三个自然数即可。

    ① 令K1循环至N

    ② 根据N mod K是否为0可统计K的约数的个数;

    ③ 若N的约数的个数超过2个,则判定N不是素数。

    Pascal程序:

    Program Exam39

    Var  nmkt: integer

    Begin

      write(‘N=’);

      ReadLn(N);

    t:=0

      for  k:=1 to N do                {外循环 }

        if  N mod k=0 then t := t+1;                {如果N是奇数 }

      if t>2 then writeln(‘No’)

      else writeln(‘Yes’);

      Readln;

    End.

    程序中的变量yse为布尔(或逻辑)类型(Boolean)。布尔值只有两个:

     True()          False()  

     布尔值与条件判断结果为真(条件成立)或为假(条件不成立)的作用相同,常用于条件语句和循环语句中。

    上面程序中用 if yes and (t mod 7=0) then writeln;实现每行打印七个素数换行,程序中布尔变量yes为真,在逻辑上表示是素数;关系式(t mod 7=0) 的值为真时,表示该行输出素数巳是7个;用and将这两个“条件”连起来是作一种布尔(逻辑)运算。

    Pascal 共有四种逻辑运算符:

        ① and ()  两条件都为True时,其结果值为True;否则为False

        ② or ()   两条件中只要有一个为True ;其结果值为True;否则为False

        ③ xor (异或两条件的逻辑值不相同时,其结果值为True;否则为False

        ④ not ()   条件为True时,其结果值为False;否则为True(取反)

     

     

     

     

    习题3.1:

    1.打印出120的平方数表。

    2.打印出100200之间的奇数。

    3. 鸡兔同笼(for循环程序完成)

    4.一辆快车和一辆慢车开往同一地点,快车票价为18元,慢车票价为13. 5元,共售出400张,共计5940元,求快车票和慢车票各多少张?.

    5.求出能被5整除的所有四位数的和。

    6.在下面式子中的二个□内填入一个合适的同样的数字,使等式成立。

    □3*6528=3□*8256

    7.有一个三位数,它的各位数字之和的11倍恰好等于它自身,请编程求出这个三位数。

    8.在自然数中,如果一个三位数等于自身各位数字之立方和,则这个三位数就称为是水仙花数。如:153=13+53+33,所以153是一个水仙花数。求所有的水仙花数。

    9.编程序打印出下列图案:

    平行四边形 等腰三解形      菱形

      ******          *              *

       ******          ***                ***

      ******          *****              *****

     ******          *******              ***

    ******          *********              *

    10.编程打印出如下图案:

              1

             222

            33333

           4444444

          555555555

    11.有三种明信片:第一种每套一张,售价2元;第二种每套一张,售价4元; 第三种每套9张,售价2元。现用100元钱要买100张明信片,要求每种明信片至少要买一套,问三种明信片应各买几套?请输出全部购买方案。

    12.某人想把一元钱换成伍分、贰分、壹分这样的零钱, 在这三种零钱中每种零钱都至少各有一个的情况下,共有多少种兑换方案。并打出这些方案。

    13.

    14. 输出100 以内的全部素数,要求每行显示个。

    15.AB两个自然数的和、差、积、商四个数加起来等于243,求AB两数。

    16.百钱买百鸡:今有钱100元,要买100只鸡,公鸡3元一只,母鸡1元一只,小鸡13只,若公鸡、母鸡和小鸡都至少要买1只,请编程求出恰好用完100元钱的所有的买鸡方案。

     

    第二节 repeat 循环

    Repeat循环是直到型循环。

    试将上一节的例3.1(打印出120的平方数表)程序改为 repeat 循环:

        Program Exam31_1

           Var a: byte

           Begin

               a:=1;   writeln ( ' a ' : 8 ,   ' a*a ' : 8 ) 

               repeat

                 writeln ( a :8a*a : 8)

                 inc(a);                               {改变a的值 }

               Until a20

               Readln

           Emd.

     

    程序中的Repeat循环格式为:

                repeat

                    循环体语句;

                until 条件表达式;     {直到条件为真}

          Repeat循环首先执行由RepeatUntil括起来的循环体语句,然后检查Until后面的条件表达式:如果表达式结果为假,则继续执行循环体,接着继续检查Until后面的条件表达式,如此反复执行直到这个表达式结果为真时结束循环。Repeat循环体语句必须有能改变Until后面条件表达式值的语句,并最终使这个条件表达式的值为真,使循环自动结束。

    程序中inc (a) 指令相当于a  : =a+1,常用的同类指令格式如下:

        (1) inc(x)          等同 x:=x+1

        (2) inc(x, n)      等同 x:=x+n

        (3) dec(x)         等同 x:=x1

        (4) dec(xn)   等同 x:=xn

     

    [3.10]求两个自然数MN的最大公约数。

    解:若自然数a既是M和约数,又是N的约数,则称aMN的公约数,其中最大的称为最大公约数。为了求得最大公约数,可以从最大可能的数(如MN)向下寻找,找到的第一个公约数即是最大公约数。

    Pascal程序:

    Program ex310;

    Begin

      a := N+1;

      Repeat

        a := a-1;

      Until (M mod a=0) and (N mod a=0);

      writeln(a);

      Readln;

    End.

     

     

    [3.11]校体操队到操场集合,排成每行2,最后多出1;排成每行3,也多出1;分别按每行排4,5,6,都多出1;当排成每行7人时,正好不多。求校体操队至少是多少人?

    :①设校体操队为X,根据题意X应是7的倍数,因此X的初值为7,以后用inc(x,7)改变X值;

       ②为了控制循环用逻辑变量yes为真(True) 使循环结束;

       ③如果诸条件中有一个不满足,  yes 的值就会为假(false),就继续循环。

    Pascal程序:

    program  Exam311;

    var  x: word;  yes : boolean;

    begin

       x:=0;

       repeat

         yes  :=true;  inc(x,7);

         if x mod 2 < > 1 then yes:=false;

         if x mod 3 < > 1 then yes:=false;

         if x mod 4 < > 1 then yes:=false;

         if x mod 5 < > 1 then yes:=false;

         if x mod 6 < > 1 then yes:=false;

       until yes;                                      {直到yes的值为真 }

       writeln('All =', x) ;  readln

    end.

     

    程序中对每个X值,都先给Yes 赋真值,只有在循环体各句对X进行判断时,都得到“通过”(此处不赋假值)才能保持真值。

     

    [3.12]从键盘输入一个整数XX不超过10000),若X的各位数字之和为7的倍数,则打印“Yes”,否则中打印“No”。

    解:本题考察的是数字分离的方法,由于X的位数不定,所以以往的解法不能奏效,这是介绍一种取余求商法。

    1)用X mod 10分离出X的个位数字;

    2)用X div 10将刚分离的个数数字删除,并将结果送回给X

    3)重复(1)(2)直到X0

    Pascal程序:

    Program ex12;

    var x,a,s : integer;

    begin

      s := 0;

      repeat

        a := x mod 10;

        x := x div 10;

        s := s+a;

      until x=0;

      if s mod 7=0 then writeln(‘Yes’)

            else writeln(‘No’);

      Readln;

    end;

     

    [例3.13]求19921992的乘积的末两位数是多少?

    解:积的个位与十位数只与被乘数与乘数的个位与十位数字有关,所以本题相当于求199292相乘,而且本次的乘积主下一次相乘的被乘数,因此也只需取末两位参与运算就可以了。

    Pascal程序:

    Program ex313;

    var a,t : integer;

    Begin

      a := 1;

      t := 0;

      repeat

        t := t+1;

        a := (a*92) mod 100;

      until t=1992;

      writeln(a);

      Readln;

    End.

     

    [3.14]尼科彻斯定理:将任何一个正整数的立方写成一组相邻奇数之和。

         如: 33=7+9+11=27            43=13+15+17+19=64

    :从举例中发现:

      (1) n3正好等于n个奇数之和;

      (2) n个奇数中的最小奇数是从1开始的奇数序列中的第m个奇数,与 的关系为:  m=n (n 1) / 2+1

      (3) 奇数序列中第m个奇数的值为x,且 x= 2m1,比如: n=3时,m=3(3-1)/2+1=4,即3个奇数中最小的奇数是奇数序列中的第4个,它的值为x=(2m-1)=7, 所以:33=7+9+11

      (4) 从最小的奇数值x开始,逐个递增2,连续n,t1开始计数,直到t=n为止。

    Pascal程序:

    Program Exam35

    Var nmxts  :  integer

    Begin

      write(input n:);  readln(n);         {输入N }

      m:=(n*(n-1) div 2)+1;                   {找到第m个奇数 }

      x:=2*m-1; t:=1;     {算出第m个奇数的值x,是所求的第一个}

      write(n*’,n,’*’,n,’=’,x){输出第一个}

      s:=x;                             {S计算和 }

      if  n1  then

      Repeat

        inc(x,2);                     计算下一个奇数 }

        write (’,x) ;            {加上下一个奇数 }

        inc (t ); inc (sx);           计个数并累加和 }

      Until t=n;                      {直到n个 }

      Writeln (’,s ) 

      Readln

    End.

     

    [3.15]猜价格:中央电视台的“幸运52”栏目深受观众喜爱,其中的“猜商品价格”的节目更是脍炙人口,现在请你编一个程序模拟这一游戏:由计算机随机产生2005000之间的一个整数,作为某件商品的价格,然后由你去猜是多少,若你猜的数大了,则计算机输出提示“Gao”,若你猜的数小了,则计算机输出提示“Di”,然后你根据提示继续猜,直到你猜对了,计算机会提示“Ok”,并统计你猜的总次数。

    解:本题的游戏规则大家都清楚,要完成程序,必须把处理步骤理清:

      (1)用随机函数Random产生2005000之间的一个整数X

    2)你猜一个数A

    3)若AX,则输出“Gao”;

    4)若AX,则输出“Di”;

    (5)AX则输出“Ok”;

    (6)重复(2)(3)(4)(5)直到A=X

    Pascal程序:

    Program ex315;

        Var t,X,a : integer;

      Begin

    Randomize;

    X := Random(4800)+200;

    t := 0;

    Repeat

      t := t+1;

    write(‘[‘,t,’] Qing cai yi ge zheng shu : ‘);

    readln(a);

    if a>x then writeln(‘Gao’);

    if a<x then writeln(‘Di’);

    if a=x then writeln(‘Ok’);

    Until A=X;

    Readln;

    End.

     

    习题3.2

    1.求两个自然数MN的最小公倍数。(如果求三个或更多个数的最小公倍数呢?应如何解决)

    2.小会议室里有几条相同的长凳,有若干人参加开会。如果每条凳子坐6,结果有一条凳子只坐有3;如果每条凳子坐5,就有4人不得不站着。求会议室里有多少人开会,有多少条长凳?

    3.某动物饲养中心用1700元专款购买小狗(每只31)和小猫(每只21)两种小动物。要求专款专用,正好用完应当如何购买?请输出所有方案。

    4.某整数X加上100就成为一个完全平方数,如果让X加上168 就成为另一个完全平方数。求X?

    5.某次同学聚会,老同学见面个个喜气洋洋,互相握手问好。参加此次聚会者每人都与老同学握了一次手,共握903,试求参加聚会的人数?

    6.用自然数300262205167分别除以某整数A,所得到的余数均相同。求出整数A以及相除的余数?

    7.1600年前我国的一部经典数学著作中有题:“今有物,不知其数,三三数之,剩二;五五数之,剩三;七七数之,剩二,问物几何。”求最小解。

    8.编程求出所有不超过1000的数中,含有数字3的自然数,并统计总数。

    9.阿姆斯特朗数:如果一个正整数等于其各个数字的立方和,则该数称为阿姆斯特朗数(也称自恋数),如40743+03+73,试编程求出1000以内的所有阿姆斯特朗数。

     

    第三节 While 循环

    While循环是当型循环。

    [例3.8] 前面第一章[例1.2]的鸡兔同笼,头30,脚90, 求鸡兔各几只?在此用下面方法编程求解。

    解: 设鸡为J只,兔为T只。已知头为H, 脚为F。

       ①让鸡的只数逐次加1进行递推计算,初始时J=0;

        ②计算兔的只数T=H-J;

        ③当总脚数(4*T+2*J) < > F就做 (J=J+1,T=H-J);

        ④当4*T+2*J=F时,说明所推算的J和T是正确的,应结束循环,并输出T, J。

    Pascal程序:

    Program  Exam38;

    Const H=30;

          F=90;

    Var  J,T : integer;

    Begin

      J:=0;  T:=H-J;     {初始时让J从0开始计算 }

      While 4*T+2*J<>F  do     {当条件为真就做do后面的循环体 }

      begin 

        inc(J);       { 递推改变J值 }

        T:=H-J             {计算兔的只数 }

      end;

    Writeln('T=',T,'  ':6, 'J=', J ) ;

      Readln

    End.

    程序中采用While当型循环,While循环语句的格式为:

               While 条件式 do 语句;

    其中do后面的“语句”是被重复执行的,称为循环体;若循环体是多个语句, 必须用begin--end包起来成为复合语句。

    While循环首先判断条件式,当条件式的值为真就执行do 后面的语句(循环体)。

    While的循环体内也必须包含能改变控制变量取值语句, 影响条件式的值, 最终使条件式为false (假), 才能结束循环。

     

    [例3.9] 输入任一的自然数A, B, 求A , B的最小公倍数。

    解:这里采用适合计算机查找的方法: 设D是它们的最小公倍数。先找出A, B当中的较大者并存放在A中, 将较小者存放在B中, 让D=A, 当D能够整除B时, 则D是所求的最小公倍数;当D不能整除B,就逐次地让D增加A。例如:A=18,  B=12, 步骤如下:

        ① 让D=A  (D=18)

        ② 当(D mod B)<>0 为真时 ( D不能整除B ) 就做 D=D+A, 重复②;

        ③ 当(D mod B)<>0 为假时结束循环,并输出D。

    Pascal程序:

    program  Exam39;

    var a,b,d,t : word;

    begin

       write('input a,b: '); readln(a , b);

       if a<b then 

    begin

            t:=a; a:=b; b:=t

          end;

       d:=a;

       while  d mod b < >0  do                   {当条件为真时就做do后面的语句 }

         inc(d,a);

       writeln('[', a, ' , ' , b, ']=', d) ;

       readln

    End.

    Pascal语言的三种基本循环方式,  for循环对循环范围有明确规定, 且循环变量只能是递增加1或递减1自动计数控制; 而repeat--until循环和while--do循环比较灵活, 只要对条件表达式的值能控制满足一定要求就能组成循环, 但在循环体中必须有改变循环变量值的语句, 使条件判断(逻辑值)最终为True或flase, 让循环能够终止。

     

    [例3.10]求自然数A, B的最大公约数。

    解:采用如下方法步骤:

        (1)求A除以B的余数;

        (2)当余数<>0就做n=a; a=b; b=n mod b, 重复(1)和(2);

        (3)当余数=0就结束循环,并输出b值。

    比如a=18, b=12时,处理步骤为:

        (1)       =         ,得余数为6;

    (2) 此余数不为零 ,让a = 12, b = 6;

    (3) 重复      =        ,  得余数为0;

    (4) 结束循环,输出6(余数为零时的b值即是所求的最大公约数)。

    此方法称为辗转相除法求最大公约数。

    Pascal程序:

    program Exam310;

    var a,b, n : word;

    begin

       write('input a,b: ');     readln (a,b);

       write('(', a, ' , ' , b, ')=' ) ;

       while  a mod b < > 0  do

         begin

           n:=a;  a:=b;  b:=n mod b;

         end;

       writeln(b);

       readln

    End.

    [例3.11]将一根长为369cm的钢管截成长为69cm和39cm两种规格的短料。在这两种规格的短料至少各截一根的前提下, 如何截才能余料最少。

    解:设两种规格的短料分别为:

       规格为69cm的x根,可在1至(369-39)/69范围循环取值;

       规格为39cm的y根,用y = (369-69*X)/39)计算;

       余料R=369-69*X-39*Y。

    ①设最小余料的初始值min=369;

    ②在X循环范围内,每一个X值都计算出对应的Y和R;

    ③如果R<min, 就将R存入min, x存入n, y存入m,记录余料最小时的x和y ;

    ④重复步骤②,当x值超出 ((369—39)/ 69) 时结束循环。

    Pascal程序:

    program  exam311;

    var x,y,r,min,n,m,a: integer;

    begin

      min:=369;

      a:=(369-39) div 69; x:=1;

      while  x<=a  do

        begin

          y:=(369-69*x) div 39;

          r:=369-69*x-39*y;

          if r<min then  

    begin

              min:=r; n:=x; m:=y

            end;

          inc(x);

        end;

    writeln('min=', min, '  x=', n, '   y=', m) ;

      readln

    end.

    在有些情况中, 三种循环方法可以互相换用。

    [例3.12]甲、乙、丙三人都是业余射击爱好者, 在一次练习中他们枪枪中靶: 甲射了八发子弹,取得225环成绩,乙射了七发,也取得225环;丙只射了六发,同样取得225环。下面是成绩表,请编程完成下表中空项的填数。

     

    射子弹数

    中50环有几发

    中35环有几发

    中25环有几发

    成绩(环)

    8

     

     

     

    225

    7

     

     

     

    225

    6

     

     

     

    225

    解:①设N为发射子弹数, 只有8, 7, 6三个数字, 正好又可用来代表甲、乙、丙;

       ②设A为中50环的子弹数, 最小为0,  最大为(225 div 50=4); 

         B为中35环的子弹数, 最小为0, 最大为(225 div 35=6);

           C为中25环的子弹数,  C=N-A-B, 但必须C>0才可能正确;

       ③先让N=8,  A取值(0~4),  B取值 (0~6)用循环逐个选定, 在C>0的情况下若能满足条件A*50+B*35+C*25=225就能确定一组填数。然后选N的下一数值,重复同样的过程。

    Pascal程序:

    program  exam312;

    var a,b,c,n,s : integer;

    begin

      writeln('n':3, 'a':3, 'b':3, 'c':3, 's':5) ;

      n:=8;

      while n<=6 do

        begin

          a:=0;

          while  a < = 4  do

            begin

            b:=0; 

              while  b < = 6  do

                begin

                  c:=n-a-b;

                  if c>0 then 

                  begin

                     s:=50*a+35*b+25*c;

                     if s=225 then writeln(n:3,a:3,b:3,c:3,s:5);

                   end;

                 inc(b);

               end;

             inc(a);

           end;

           dec(n);

         end;

         readln

      end.

    程序运行结果获得两组填数答案。如果改用for循环,程序将更加简明:

    Program  Exam312_1;

    Var a,b,c,n,s : Integer;

    Begin

      Writeln('N':3, 'A':3, 'B':3, 'C':3, 'S':5) ;

      for n:=8 downto 6 do                  {N取值8,7,6,并分别代表甲、乙、丙 }

        for a:=0 to 4 do                     {中50环的可能范围 }

          for b:=0 to 6 do                   {中30环的可能范围 }

            begin 

             c:=n-a-b;                     { 计算中25环的子弹数  }

                 if c>0 then begin                    {如果不是负数 }

                    s:=50*a+35*6+25*c;         {计算总成绩 }

                    if s=225 then writeln(n:3,a:3,b:3,c:3,s:5);

                 end

               end;

       readln

     End.

     

    习题3.3

    1.求S= 1-1/2 +1/3-1/4+1/5-1/6+ ……(求前N项的和)

    2. Faibonacci数列前几项为: 0,1,1,2,3,5,8,…,其规律是从第三项起, 每项均等于前两项之和。求前30项,   并以每行5个数的格式输出。

    3.小球从100高处自由落下,着地后又弹回高度的一半再落下。求第20次着地时, 小球共通过多少路程?

    4.某登山队员第一天登上山峰高度的一半又24米; 第二天登上余下高度的一半又24米;每天均如此。到第七天,距山顶还剩91米。求此山峰的高度?

    5.给出某整数N,将N写成因数相乘的形式。如: N=12,输出: 12=1*2*2*3.

    6.出售金鱼者决定将缸里的金鱼全部卖出。第一次卖出全部金鱼的一半加二分之一条;第二次卖出剩余的三分之一加三分之一条金鱼;第三次卖出余下金鱼的四分之一加四分之一条;第四次卖出余下的五分之一加五分之一条金鱼。还剩下11条金鱼。当然,出售金鱼时都是整数条,不能有任何破损。求缸里原有的金鱼数?

    7.外出旅游的几位朋友决定次日早晨共分一筐苹果。天刚亮,第一个人醒来,他先拿了一个,再把筐里的八分之一拿走;第二个人醒来,先拿两个,再把筐里的八分之一拿走;第三个人醒来,先拿三个,再拿走筐里的八分之一;…每个人依次照此方法拿出各人的苹果,最后筐里的苹果全部拿完,他们每人所拿到的苹果数正巧一样多。求原先筐里的苹果数和人数。

    8.图中由6个圆圈构成三角形,每条边上有三个圈, 将自然数1--6 不重复地填入各圆圈位置上,使每条边圆圈上的数字之和相等,请编程输出所有的填法。

    9.请编程显示出下面数字金字塔图形:         

                           

        


    第四章 函数与过程

    程序中往往需要把主要任务分成若干个子任务,每个子任务只负责一个专门的基本工作。每个子任务就是一个独立的子程序。Turbo Pascal 可以把函数和过程作为子程序调用。

    第一节 函数

    Pascal允许用户在程序中自己说明定义所需要的函数并在程序中调用这些函数。

    [例4.1]编程找出由键盘任意输入五个整数中的最大整数。

    解:设输入的五个整数为n1、n2、n3、n4、n5,为了便于处理,引入一个中间变量t1,按如下步骤处理:

    ①令t1=n1;

    ②将t1与n2比较,将两者中较大的数放入t1;

    ③将t1与n3比较,将两者中较大的数放入t1;

    ④将t1与n4比较,将两者中较大的数放入t1;

    ⑤将t1与n5比较,将两者中较大的数放入t1;

    ⑥经过以上5步处理后,t1即为5个数中最大者。

    从上面规划的步骤看来,从步骤②到步骤⑤需处理的目标是相同的,因此我们可以设计一段子程序Max(x1,x2),以找出x1和x2中最大的值并返回。

    Pascal程序:

    Program Exam41_a;

    Var n1,n2,n3,n4,n5,t1 : integer;

    Function max(x1,x2 : integer) : integer;

    Begin

      If x1>x2 then Max := x1

       Else Max := x2;

    End;

     

    Begin

      Write(Input 5 numbers : );

    Readln(n1,n2,n3,n4,n5);

    T1 := n1;

    T1 := Max(t1,n2);

    T1 := Max(t1,n3);

    T1 := Max(t1,n4);

    T1 := Max(t1,n5);

    Writeln(Max number : ,t1);

    End.

    从上例看出,引入函数实际上是将一个复杂的问题划分成若干个易于处理的子问题,将编程化简的一种有效办法,而化简的方法是多种多样的,如前面已经做过求三个数中的最大数,所以可定义一个专门求三个数中最大数的函数(Max)。第一次用这个函数求出n1,n2,n3三个数中的最大数t1;第二次调用这个函数求出t1与n4,n5三个数中的最大数,也就是前三个数的最大数(已在t1中)和后面二个数再求一次,就得到五个数的最大数。因此,需要两次使用“求三个数中的最大数”,步骤如下:

    ①调用函数Max ( n1, n2, n3), 求出n1,n2,n3中的最大者 t1;

    ②调用函数Max ( t1, n4, n5 ),求出t1, n4, n5中的最大者t2;

    ③输出最大数 t2。

    Program Exam41_b;

    Var n1,n2,n3,n4,n5,t1: integer;

     

    function Max(x1,x2,x3: integer): integer;               {自定义函数Max}

    Var XX: integer;                           {函数内部变量说明}

    begin                                    {函数体}

    if X1>X2 then XX:=X1  

    else XX:=X2;

      if X3>XX then XX:=X3;

    Max:=XX

    end;

      

    Begin                            {主程序}

      Write('Input 5 numb:');

      Readln(n1,n2,n3,n4,n5);                             {输入五个数}

      t1:=Max(n1,n2,n3);                        {用函数求n1, n2, n3的最大数}

      t1:=Max(n4,n5,t1);                        {用函数求n4, n5, t1 的最大数}

      Writeln('Max Number :', t1); 

      Readln

    End.

    主程序中两次调用自定义函数。自定义函数的一般格式为:

                 function 函数名(形式参数表): 类型;   {函数首部}

                 局部变量说明部分;

                 begin

                    语句系列;                                  {函数体 }

                 end;

    函数中的形式参数接受调用函数时所传入的值,用来参与函数中的运算。

     

    [例4.2]求任意输入的五个自然数的最大公约数。

    解:⑴自定义一个专门求两自然数的最大公约数的函数GCD;

       ⑵调用自定义函数,第一次求前两个数的最大公约数;从第二次开始,用每次求得的最大公约数与下一个数再求两个数最大公约数,直到最后。本题共四次“求两个数的最大公约数”, 设输入的五个自然数分别是a1,a2,a3,a4,a5,采用如下步骤:

    ①求a1, a2两个数的最大公约数 →  存入a1;

    ②求a1, a3两个数的最大公约数 →  存入a1;

    ③求a1, a4两个数的最大公约数 →  存入a1;

    ④求a1, a5两个数的最大公约数 →  存入a1;

    ⑤ 输出 a1,此时的a1已是五个数的最大公约数。

    Pascal程序:

    Program Exam42;

    Var a1,a2,a3,a4,a5: integer;

    function GCD(x,y: integer): integer;         {自定义函数 }

    Var n:integer;

    begin

      While  x mod y <>0  do

      begin

        n:=x; x:=y; y:=n   mod   y

    end;

      GCD:=y

    end;

     

    Begin   {主程序 }

    Write('input 5 Numper:');

      readln(a1,a2,a3,a4,a5);                     {输入五个数}

      Write('(',a1,',',a2,',',a3,',',a4,',',a5,')=');

    a1:=GCD(a1,a2);                               {调用函数GCD }

      a1:=GCD(a1,a3);

      a1:=GCD(a1,a4);

      a1:=GCD(a1,a5);

      Writeln(a1);

      readln

    End.

    函数的结果是一个具体的值, 在函数体中必须将所得到的运算结果赋给函数名;主程序通过调用函数得到函数的运算结果。调用函数的一般格式为:

                  函数名 (实在参数表)

    调用函数时, 函数名后面圆括号内的参数必须有确定的值, 称为实在参数。调用时即把这些实际值传送给函数形参表中的相应形参变量。函数不是单独的语句, 只能作为运算赋值或出现在表达式中。

     

    习题4.1

    1. 数学上把从 1 开始的连续自然数相乘叫做阶乘。例如 把1*2*3*4*5  称作5的阶乘,  记为5!。 编写一个求n!的函数, 调用此函数求: D=

    2.求从键盘输入的五个自然数的最小公倍数。

    3.哥德巴赫猜想的命题之一是:大于6 的偶数等于两个素数之和。编程将6~100所有偶数表示成两个素数之和。

    4.如果一个自然数是素数,且它的数字位置经过对换后仍为素数,则称为绝对素数,例如13。试求出所有二位绝对素数。

     

     

    第二节 自定义过程

    自定义函数通常被设计成求一个函数值,一个函数只能得到一个运算结果。若要设计成能得到若干个运算结果,或完成一系列处理,就需要自定义“过程”来实现。

    [例4.3] 把前面[例2.2 ](输入三个不同的整数,按由小到大排序)改为下面用自定义过程编写的Pascal程序:

    Program  exam43;

    Var a,b,c: integer;

    Procedure  Swap (var x,y: integer);            {自定义交换两个变量值的过程 }

    Var t  : integer;

    Begin                               {过程体}

      t:=x; x:=y; y:=t                    {交换两个变量的值

    end;

     

    Begin                    {主程序}

      Write('input a,b,c=');

      Readln(a,b,c);

      if a>b then swap (a,b);                     {调用自定义过程}

      if a>c then swap (a,c);

      if b>c fhen swap (b,c);

      Writeln (a:6, b:6, c:6);

      Readln

    End.

    程序中Procedure Swap是定义过程名,从作用来看,过程与函数是相似的,都能将复杂的问题划分成一些目标明确的小问题来求解,只不过函数有值返回而过程则没有。自定义过程的一般格式如下:

    Procedure 过程名 (形式参数表);      {过程首部}

         局部变量说明部分;

         begin

           语句部分;                       {过程体部分}

         end;

     

    [例4.4]如果一个自然数除了1和本身,还有别的数能够整除它, 这样的自然数就是合数。例如15,除了1和15,还有3和5能够整除,所以15是合数。14,15,16是三个连续的合数,试求连续十个最小的合数。

    解:从14,15,16三个连续合数中可看出,它们正好是两个相邻素数13和17 之间的连续自然数,所以求连续合数问题可以转化为求有一定跨度的相邻两个素数的问题。因此,求连续十个最小的合数可用如下方法:

    ①从最小的素数开始,先确定第一个素数A;

    ②再确定与A相邻的后面那个素数B;(作为第二个素数);

    ③检查A,B的跨度是度否在10 以上,如果跨度小于10,就把B 作为新的第一个素数A,重复作步骤②;

    ④如果A、B跨度大于或等于10,就打印A、B之间的连续10个自然数,即输出 A+1, A+2, A+3 ,  A+10。

    Pascal程序:

    Program  exam44;

    var a,b,s,n: integer;

        yes: boolean;

    procedure sub(x: integer;var yy: boolean);          {过程:求x是否为素数 }

    var k,m: integer;                                   { 用yy逻辑值转出 }

    begin

      k:=trunc(sqrt(x));

      for m:=3 to k do

      if odd(m) then

         if x mod m=0 then yy:=false;

    end;

     

    begin            {主程序 }

      b:=3;

      repeat

        a:=b;                    {a 为第一个素数 }

        repeat 

        yes:=true;

      inc(b,2);                               {b是a后面待求的素数}

        sub(b,yes);                 {调用SUB过程来确认b是否为素数 }

        if yes then s:=b-a;                     {如果b是素数,则求出跨度s  }

          until yes;

        until s > = 10;

        for n:=a+1 to a+10 do

           write(n:6);

        writeln;

        readln

      end.

    程序中的过程SUB,用来确定b是否为素数。过程名后面圆括号内的变量是形式参数,简称为形参。过程SUB(x: integer; Var yy: boolean) 中的x是值形参,而前面冠有Var的yy是变量形参。值形参只能从外界向过程传入信息,但不能传出信息;变量形参既能传入又能传出信息。本程序过程SUB中的x是由调用过程的实在参数b传入值,进行处理后,不需传出;而yy是把过程处理结果用逻辑值传出,供调用程序使用。

    试把[例4.3]程序中的过程 SWAP(Val x,y: integer),将x,y前面的Var去掉,就变成了纯粹的值形参,就不能将过程所处理的结果传出去,也就无法得到处理后的结果,通过运行程序比较,可以非常明显地看到值形参和变量形参的区别。

        调用过程的格式为:   过程名(实在参数表) ;

    调用过程名后面圆括号内的实在参数与定义过程的形参表必须相对应,调用过程相当于一个独立语句,可单独使用。

     

    [例4.5]将合数483的各位数字相加(4+8+3)=15,如果将483分解成质因数相乘:  483=3*7*23,把这些质因数各位数字相加(3+7+2+3),其和也为15。即某合数的各位数字之和等于它所有质因数的各数字之和。求500以内具有上述特点的所有合数。

    解:①设n为所要求的合数,让n在1~500间循环做以下步骤;

        ②用t1,t2分别累计合数n及n的质因数的各位数字之和,初值均为0;

        ③调用过程SUB3进行非素数判定,以布尔变量yes的真假值判定是否,yes的初值为true,如果为 (not true)非素数,就做步骤④,⑤,⑥;否则取新的n值,重复步骤③;

        ④调用SUB1,求n的各数字之和,传送给t1;

        ⑤调用SUB2,求n的各质因数,对于每个质因素都通过SUB1求它各位数字之和,将所有各质因数字传送给t2。

        ⑥如果t1=t2(各位数字之和等于它所有质因数的各数字之和),则输出此n。

    PASCAL程序:

    program exam45;

    var n,t1,t2,tatol: integer;

        yes: boolean;

    procedure sub1(x:integer; var t:integer);           {过程:分离x的各位数字 }

    begin                                               {并求各位数字之和 }

    repeat

        t:=t+x mod 10;

        x:=x div 10;

      until x=0

    end;

     

    procedure sub2(x: integer; var t: integer);       {过程:分解质因数 }

    var xx,tt:integer;

    begin

      xx:=2;

      while x>1 do

      if x mod xx=0 then 

    begin

            tt:=0;

            sub1(xx,tt);

            t:=t+tt;

            x:=x div xx

          end

        else inc(xx)

    end;

     

    procedure sub3(x: integer;var yy: boolean);      {过程:判断x是否为素数 }

    var k,m: integer;

    begin

      k:=trunc(sqrt(x));

      for m:=2 to k do

        if x mod m=0 then yy:=false;

    end;

     

    begin                         {主程序}

       for n:=1 to 500 do

         begin

           t1:=0;t2:=0;

           yes:=true;

           sub3(n,yes);           {调用过程求素数 }

           if not yes then       {如果非素数就… }

             begin

               sub1(n,t1);         {调用过程求n的各位数字之和 }

               sub2(n,t2);        {调用过程求n的各质因数的数字之和 }

               if t1=t2 then write(n:6);                   {打印合格的合数 }                                

             end

         end;

       readln

    end.

     

    程序定义了三个过程SUB1,SUB2,SUB3,其中SUB2过程中又调用了SUB1。在过程中定义的变量或参数,只在本过程内部使用有效。这些变量称为局部变量。如SUB2中的xx只在SUB2中使用,属于局部变量。

     

    习题:4.2

    1.输入自然数n,求前n个合数(非素数),其素因子仅有2,3,或5。

    2.自然数a的因子是指能整除a的所有自然数,但不含a本身。例如12的因子为:1,2,3,4,6。若自然数a的因子之和为b,而且b的因子之和又等于a,则称a,b为一对“亲和数” 。求最小的一对亲和数。

    3.求前n个自然数的平方和,要求不用乘法。例如:3的平方不用3*3,可用3+3+3。

    4.试用容积分别为17升、13升的两个空油桶为工具,从大油罐中倒出15升油来,编程显示出具体的倒油过程。

    5.如果一个数从左边读和从右边读都是同一个数,就称为回文数。例如6886就是一个回文数,求出所有的既是回文数又是素数的三位数。

    6. 任何大于2的自然数都可以写成不超过四个平方数之和。如:

     8=22+22;  14=12+22+32  

    由键盘输入自然数N(2 < N < 2000) ,输出其不超过四个平方数之和的表示式。

     

     


    第五章 Pascal的自定义数据类型

    Pascal系统允许用户自定义的数据类型有:数组类型、子界类型、枚举类型、集合类型、记录类型、文件类型、指针类型。

     

    第一节 数组与子界类型

    [例5.1]总务室在商店购买了八种文具用品,其数量及单价如下表:

    序号

    1

    2

    3

    4

    5

    6

    7

    8

    品名

    圆珠笔

    铅笔

    笔记本

    订书机

    计算器

    三角板

    圆规

    文件夹

    件数

    24

    110

    60

    16

    26

    32

    32

    42

    单价

    1.18

    0.45

    1.8

    8.8

    78.50

    3.28

    4.20

    2.16

    编程计算各物品计价及总计价。

    解:表中有两组数据,设表示物品件数的一组为a,表示物品单价的一组为b。

    a,b两组数据以序号为关联,具有相应的顺序关系。按如下方法处理:

    ①定义s,a,b三个数组,按相应顺序关系,给a,b赋值(件数和对应单价) ;

    ②每读入一对数据(件数和对应单价),以同一序号的件数和对应单价计算出同一物品的计价:

      s[ i ]=a[ i ]* b[ i ] ;             { 用s[ i] 记入第i种物品的计价}

       t = t + s[ i ]                       {  用简单变量累加总计价 }

    ③循环做步骤②,做完后输出s数组所记入的各物品计价及总计价t。

    Pascal程序:

    Program  Exam51;

    Var a: array[1..8] of integer;               {a数组为整数型}

        s,b: array[1..8] of real;                     {s和b数组为实数型}

        t: real;

        i: integer;

    Begin

      t:=0;

      for i:=1 to 8 do                                   {输入并计算八种物品 }

      begin   

    write('a[', i, ']=') ;

          Readln(a[ i ]) ;                         {输入单价}

          write('b[', i, ']=') ;

          readln(b[ i ]);                           {输入件数}

          s[ i ]:=a[ i ]* b[ i ];  t:=t+s[ i ]

        end;

      write('i':2, '  ':2);

      for i:=1 to 8 do                                   {打印物品序号}

         write(i:8);                                        {输出项宽度为8}

      writeln;

      write('a':2, '  ':2);                                  {输出项宽度为2}

      for i:=1 to 8  do                                 {打印物品件数a数组}

        write(a[ i ]:8);                                 {输出项宽度为8}

      writeln;                                               {换行}

      write('b':2, '  ':2);

      for i:=1 to 8  do                                 {打印物品件数b数组}

        write(b[ i ]:8:2);                              {输出项宽度为8,小数2位}

      writeln;                                               {换行}

      write('s':2, '  ':2);

      for i:=1 to 8  do                                 {打印物品计价s数组}

        write(s[ i ]:8:2);                              {输出项宽度为8,小数2位}

      writeln;                                               {换行}

      writeln('Totol=', t:8:2);                    {打印总价t}

      Readln

    end.

    输出语句为  write(实数:n:m)  的形式时,则输出该实数的总宽度为n,其中小数m位,此时的实数不以科学计数形式显示。

    程序中用来表示如物品件数和物品单价等属性相同的有序数据,Pascal语言把它归为数组。数组成员(分量)称为数组元素。数组必须在说明部分进行定义:确定数组名,数组分量(元素)的个数及类型。一般格式有:

    Var  数组名:array[下标类型]  of  数组元素类型 ;

    本程序中a数组和b数组中8个元素的数据都是已知数据,可当作常量,用常量说明语句给数组元素赋初值,所以上面的程序Exam51可改为如下形式:

    Program Exam51_1;

    const  a: array[1..8] of integer

            =(24,110,60,16,26,32,32,42);                      {给a数组赋初值}

           b:array[1..8] of real

              =(1.18,0.45,1.80,8.8,78.50,3.28,4.20,2.16); {给b数组赋初值}

    Var s: array[1..8] of real;

        t: real;

        i: integer;

    Begin

      t:=0;

      for i:=1 to 8 do

        begin

          s[ i ]:=a[ i ]* b[ i ];  t:=t+s[ i ]

        end;

      write('i':2, '  ':2);

      for i:=1 to 8 do  write(i:8);

      writeln;

      write('a':2, '  ':2);

      for i:=1 to 8  do   write(a[ i ]:8:);

      writeln;

      write('b':2, '  ':2);

      for i:=1 to 8  do  write(b[ i ]:8:2);

      writeln;

      write('s':2, '  ':2);

      for i:=1 to 8  do  write(s[ i ]:8:2);

      writeln;

      writeln('Totol=', t:8:2);

      Readln

    end.

    数组常量说明格式为:

    Const 数组名:array[下标类型]of 数组元素类型=(常量表);

    程序中对数组的输入、输出处理,常用循环语句控制下标,进行有序地直接操作每个数组元素。

    [例5.2]编程输入十个正整数,然后自动按从大到小的顺序输出。

    解:①用循环把十个数输入到A数组中;

        ②从A[1]到A[10],相邻的两个数两两相比较,即:

          A[1]与A[2]比,A[2]与A[3]比,……A[9]与A[10]比。

    只需知道两个数中的前面那元素的标号,就能进行与后一个序号元素(相邻数)比较,可写成通用形式A[ i ]与A[ i +1]比较,那么,比较的次数又可用1~( n - i )循环进行控制 (即循环次数与两两相比较时前面那个元素序号有关) ;

        ③在每次的比较中,若较大的数在后面,就把前后两个对换,把较大的数调到前面,否则不需调换位置。

    下面例举5个数来说明两两相比较和交换位置的具体情形:

        5   6   4   3   7         5和6比较,交换位置,排成下行的顺序;

        6   5   4   3   7         5和4比较,不交换,维持同样的顺序;

        6   5   4   3   7         4和3比较,不交换,顺序不变

        6   5   4   3   7         3和7比较,交换位置,排成下行的顺序;

        6   5   4   7   3         经过(1~(5-1))次比较后,将3调到了末尾。

     经过第一轮的1~ (N-1)次比较,就能把十个数中的最小数调到最末尾位置,第二轮比较1~ (N-2)次进行同样处理,又把这一轮所比较的“最小数”调到所比较范围的“最末尾”位置;……;每进行一轮两两比较后,其下一轮的比较范围就减少一个。最后一轮仅有一次比较。在比较过程中,每次都有一个“最小数”往下“掉”,用这种方法排列顺序,常被称之为“冒泡法”排序。

    Pascal程序:

    Program  Exam52;

    const  N=10;

    Var a: array[1..N] of integer;                      {定义数组}

        i,j: integer;

    procedure Swap(Var x,y: integer);             {交换两数位置的过程}

    Var t:integer;

    begin  

    t:=x;  x:=y;  y:=t 

    end;

     Begin

       for i:=1 to N do                                      {输入十个数}

         begin   

    write(i, ':');

            Readln(a[ i ])

         end;

       for j:=1 to N-1 do                               {冒泡法排序}

         for i:=1 to N-j do                     {两两相比较}

            if a[ i ] < a[i+1] then swap(a[ i ], a[i+1]);   {比较与交换}

       for i:=1 to N do                                  {输出排序后的十个数}

         write(a[ i ]:6);

       Readln

     end.

     

    程序中定义a数组的下标类型为 1.. N ,这种类型规定了值域的上界和下界,是从一个有序类型范围取出一个区段,所以称为子界类型,子界类型也是有序类型。

     

    例[5.3] 用筛法求出100以内的全部素数,并按每行五个数显示。

    解:⑴ 把2到100的自然数放入a[2]到a[100]中(所放入的数与下标号相同);

        ⑵ 在数组元素中,以下标为序,按顺序找到未曾找过的最小素数minp,和它的位置p(即下标号);

        ⑶ 从p+1开始,把凡是能被minp整除的各元素值从a数组中划去(筛掉),也就是给该元素值置 0;

        ⑷ 让p=p+1,重复执行第②、③步骤,直到minp>Trunc(sqrt(N)) 为止;

        ⑸ 打印输出a数组中留下来、未被筛掉的各元素值,并按每行五个数显示。

    用筛法求素数的过程示意如下(图中用下划线作删去标志) :

    ① 2 3 4 5 6 7 8 9 10 11 12 13 14 15…98 99 100      {置数}

    ② 2 3 4 5 6 7 8 9 10 11 12 13 14 1598 99 100       {筛去被2整除的数 }

    ③ 2 3 4 5 6 7 8 9 10 11 12 13 14 15…98 99 100       {筛去被3整除的数 }

                     ……

      2 3 4 5 6 7 8 9 10 11 12 13 14 15…98 99 100        {筛去被整除的数 }

    Pascal程序:

    Program Exam53;

    const  N=100;

    type  xx=1 .. N;                               {自定义子界类型xx(类型名)}

    Var a: array[xx] of boolean;

        i,j: integer;

    Begin

      Fillchar(a,sizeof(a),true);

      a[1] := False;

      for i:=2 to Trunc(sqrt(N)) do

    if a[I] then 

    for j := 2 to N div I do

    a[I*j]:= False;

      t:=0;

      for i:=2 to N do

        if a[i] then

          Begin

            write(a[ i ]:5);  inc(t);

            if t mod 5=0 then writeln

          end;

      readln

    End.

     

    程序中自定义的子界类型,在用法上与标准类型(如integer)相同,只是值域上界、下界在说明中作了规定,而标准类型的值域由系统内部规定,其上界、下界定义是隐含的,可直接使用。例如:

    Type integer= -32768 ... 32768;  Pascal系统已作了标准类型处理,不必再作定义。

     

    [例5.4]在一次宴会上,有来自八个不同国家的宾客被安排在同一张圆桌就坐。A是中国人,会讲英语;B是意大利人,他能讲西班牙语;C是英国人,会讲法语;D是日本人,能讲汉语;E是法国人,会讲德语;F是俄国人,懂意大利语;G是西班牙人,能讲日语;最后一个是德国人,懂俄语。编程序安排他们的座位,使他们在各自的座位上能方便地跟两旁的客人交谈。

    解:①根据题目提供条件与数据,建立如下关系代码表:

    国家名

    中国

    意大利

    英国

    日本

    法国

    俄国

    西班牙

    德国

    宾客代码

    A

    B

    C

    D

    E

    F

    G

    H

    语言代号

    1

    2

    3

    4

    5

    6

    7

    8

    懂外语代号

    3

    7

    5

    1

    8

    2

    4

    6

    总代码

    A13

    B27

    C35

    D41

    E58

    F62

    G74

    H86

    表中总代码实际上是前三项代码的归纳:第一个字母表示哪国人;第二个数字表示本国语代号;第三个数字表示懂哪国外语。如A13,A表示中国人,1表示汉语(本国语),3表示会说英语。所以每个宾客的情况均用总代码(三个数据组成的字符串)表示;

    ②定义由8个元素组成的数组(NAME),元素类型为字符串类型(String);

    ③元素的下标号影响各人座位关系,必须满足后一个元素的下标号应与前一个元素字符串中的第三个数据相同。例如:若第一个位置总代码为A13,则第二个位置应根据A13中最后的3,安排C35。即A与C可以交谈。以此类推。

    用字符串处理函数COPY,截取字符串的第一个字母作为宾客代码打印,再取第三个字符,用VAL将其转换成数字,将这个数字作为下标号,把这个下标号的元素安排在旁边(相邻);

    ④重复步骤③的方法,安排其后的元素,直到八个数据全部处理完为止。

    Pascal程序:

    Program Exam54;

    const  name :  array[1..8]of string              {定义字串类型数组并赋常量}

                  =('A13','B27','C35','D41','E58','F62','G74','H86');

    Var  i, code: integer;                          {整数类型}

         x:  1..8;                                  {子界类型}

         s  :  string;                              {字符串类型}

    Begin 

      s:=copy(name[1],1,1);                  {截取第一个元素字串的第一个字符}

      write(s:4);                            {确定第一个位置}

      s:=copy(name[1],3,1);                  {截取元素字串的第三个字符作为相邻}

    Val(s,x,code);                         {将字串s的值转换成数字存入 x}

    for i:=1 to 7 do                       {确定后面7个位置}

        Begin

          s:=copy(name[x],1,1);                       {找到相邻者的代码}

          write(s:4);                                 {打印相邻者代码}

          s:=copy(name[x],3,1);                       {确定下一个相邻元素}

          Val(s,x,code);

        end;

      readln

    End.

     

    Pascal常用的字符串处理标准函数有7个:

    设变量s,str,str1,str2均为字符串类型(string){多个字符};ch为字符类型(char){单个字符};

    (1)copy(str,n,m)从字符串str的左边第n个开始截取m个字符;

         如:copy(' Pascal ' ,3,2)的结果为'sc ' ;

    (2)concat(str1,str2)将两个字串连接成为一个新的字串;

         如:s:=str1+str2;同等于两串字符相加

    (3)Length(str)求字串str的长度(字符个数);

    (4)chr(x)  求x(x为1…255整数)的ASII代码对应的字符;

         如:chr(65)结果为 'A'。

    (5)ord(ch)求字符ch对应的ASCII代码值;如 ord ( 'A' )结果为65;

    (6)pos(str1,str2)求字串str1在字串中开始的位置;

         如:  pos('sca','pascal')结果为3;

    (7)upcase(ch)将字符ch转为大写字母,如 upcase( 'a' )结果为'A' ;

    Pascal常用的字符串处理标准过程有4个:

    (1)Val(str,x,code)将数字型字串转为数字并存入变量x中;

         如:Val(‘768’,x,code),x值为768,code为检测错误代码,若code=0表示没有错误;

    (2)str(n,s)将数字n转化为字串存入s中,如str(768,s)s的结果为 ' 768' ;

    (3)insert(str1,str2,n)把字串str1插入在字串str2的第n个字符之前,结果在str2中;{此过程中的str2为变量形参,具有传入传出的功能};

    (4)delete(str,n,m)从字符串str的第n个开始,删除m个字符,把剩余的字符存在str中,{此过程中的str为变量形参,具有传入传出的功能};

     

    [例5.5]一个两位以上的自然数,如果左右数字对称,就称为回文数,编程找出所有不超过6位数字的回文数,同时又是完全平方数的数。

    如121是回文数,又是11的平方,所以是完全平方数。

    解:①不超过6位数的完全平方数用循环在10~999范围产生(for i:=10 to 999) ;

       ②将完全平方数 (i*i)转成字串类型存入s中;

       ③逐个取s的左右字符,检查是否相同(对称),检查对数不超过总长度的一半;

       ④如果是回文数,就调用打印过程(Print)。

    Program Exam55;

    Var n, k, j ,t : integer;

      s  : string;                                       {字符串类型 }

        i:  longint;                                       {长整数类型 }

    Procedure Print;                                          {打印过程(无形参)}

    begin

      write(s  : 10);  inc(t);                             {打印s,  用t 计数 }

    if  t mod 6=0  then  writeln                  {打印6个换行 }

       end;

     

    Begin

      t:=0;

      for i:=10 to 999 do

      begin

          str(i*i,s);                              {将完全平方数转换成字串 }

          k:=length(s);                            {计算字串长度 }

          n:=k div 2;                              {计算字串长度的一半 }

          j:=1;

          while  j < = n  do                       {取左右字符检查是否对称 }

              if copy(s,j,1) < > copy(s,k+1-j,1) then j:=1000

              else inc( j ) ;                     {若不对称让j=1000,退出循环 }

          if j <1000 then Print                   { j <1000即是回文数,调打印 }

        end;

      writeln; writeln('Total=':8, t);                     {打印总个数 }

      readln

    End.

     

     

    习题5.1

    1.裴波那契数列:数列1、1、2、3、5、8、13、21…称为裴波那契数列,它的特点是:数列的第一项是1,第二项也是1,从第三项起,每项等于前两项之和。编程输入一个正整数N,求出数列的第N项是多少?(N不超过30)。

    2.下面的竖式是乘法运算,式中P表示为一位的素数,编程输出此乘法竖式的所有可能方案。

                                             

    3.节目主持人准备从N名学生中挑选一名幸运观众,因为大家都想争当幸运观众,老师只好采取这样的办法:全体同学排成一列,由前面往后面依顺序报数1,2,1,2,…,报单数的同学退出队伍,余下的同学向前靠拢后再重新由前往后1,2,1,2,…报数,报单数者退出队伍,如此下去最后剩下一人为幸运观众。编程找出幸运观众在原队列中站在什么位置上?(N由键盘输入,N < 255)。

    4.  1267*1267=1605289,表明等式右边是一个七位的完全平方数,而这七个数字互不相同。编程求出所有这样的七位数。

    5. 校女子100米短跑决赛成绩如下表,请编程打印前八名运动员的名次、运动员号和成绩。(从第一名至第八名按名次排列)

     

    运动员号

    017

    168

    088

    105

    058

    123

    142

    055

    113

    136

    020

    032

    089

    010

    成绩(秒)

    12.3

    12.6

    13.0

    11.8

    12.1

    13.1

    12.0

    11.9

    11.6

    12.4

    12.9

    13.2

    12.2

    11.4

    6.  求数字的乘积根。正整数的数字乘积这样规定:这个正整数中非零数字的乘积。例如整数999的数字乘积为9*9*9,得到729;729的数字乘积为7*2*9,得到126;126的数字乘积为1*2*6,得到12;12从数字乘积为1*2,得到2。如此反复取数字的乘积,直至得到一位数字为止。999的数字乘积根是2。编程输入一个长度不超过100位数字的正整数,打印出计算数字乘积根的每一步结果。输出格式如下:

    (N=3486784401)

       3486784401

       516096

       1620

       12

       2

    7. 有一组13个齿轮互相啮合,各齿轮啮合的齿数依次分别为6,8,9,10,12,14,15,16,18, 20,21,22,24, 问在转动过程中同时啮合的各齿到下次再同时啮合时,各齿轮分别转过了多少圈?

    8. 集合M的元素的定义如下:

        (1) 数1属于M;

        (2) 若X属于M, 则A=2X+1, B=3X+1, C=5X+1, 也属于M;

        (3) 再没有别的数属于M。(M={1,3,4,6,7,9,10,13,15,16...,如果M中的元素是按递增次序排列的,求出其中的第201,202和203个元素。

    9. 一个素数,去掉最高位,剩下的数仍是素数;再去掉剩下的数的最高位,余留下来的数还是素数,这样的素数叫纯粹素数。求所有三位数的纯粹素数。

    10. 自然数4,9,16,25等叫做完全平方数,因为22 =4, 32 =9, 42 =16,52 =25, 当某一对自然数相加和相减, 有时可各得出一个完全平方数。

       例如: 8与17这对自然数:  17+8=25        17—8= 9

    试编程,找出所有小于100的自然数对,当加和减该数对时,可各得出一个完全平方数。

    第二节 二维数组与枚举类型

    [例5.6]假设四个商店一周内销售自行车的情况如下面表一所示,

    自行车牌号

    永久牌

    飞达牌

    五羊牌

    第一商店

    35

    40

    55

    第二商店

    20

    50

    64

    第三商店

    10

    32

    18

    第四商店

    38

    36

    28

    表一

    几种牌号自行车的单价如表二所示。求各店本周出售自行车的总营业额。

      单价

      元

      永久牌

     395

      飞达牌

     398

      五羊牌

     384

    表二

    解:①把表一看成是由行(每个店占一行)与列(每种牌号占一列)共同构成的数据组,按表格排列的位置顺序,用A数组表一各数据表示如下:

    A[1,1]=35   A[1,2]=40   A[1,3]=55 {第一行共三个数据}

    A[2,1]=20   A[2,2]=50   A[2,3]=64 {第二行共三个数据}

    A[3,1]=10   A[3,2]=32   A[3,3]=18 {第三行共三个数据}

    A[4,1]=38   A[4,2]=36   A[4,3]=28 {第四行共三个数据}

    A数组有4行3列,每个数组元素由两个下标号表示,这样的数组称为二维数组。

    ②表二的数据按排列顺序用B数组表示如下:

      B[1]=395 B[2]=398 B[3]=384

    ②B数组有3个数据,用一维数组表示,下标号与表一中列的序号有对应关系。

    ③计算各店营业额并用T数组表示:

    T[1]=A[1,1]*B[1]+A[1,2]*B[2]+A[1,3]*B[3]     {计算第一商店的营业额}

    T[2]=A[2,1]*B[1]+A[2,2]*B[2]+A[2,3]*B[3]     {计算第二商店的营业额}

    T[3]=A[3,1]*B[1]+A[3,2]*B[2]+A[3,3]*B[3]     {计算第三商店的营业额}

    T[4]=A[4,1]*B[1]+A[4,2]*B[2]+A[4,3]*B[3]     {计算第四商店的营业额}

    T数组共有4个数据,为一维数组,下标号与商店号有对应关系。

    ④输出T数组各元素的值。

    Pascal程序:

    Program  Exam56;

    Var A: array[1..4,1..3] of integer;      {定义二维数组,整数类型}

        B: array[1..3] of integer;             {一维数组,3个元素}

        T: array[1..4] of integer;             {一维数组,4个元素}

       i,j: integer;

    Begin

       for i:=1 to 4 do       {输入表一的数据}

          Begin 

             Write(A[,i,‘]: ’);         {提示输入哪一行}

             for j:=1 to 3 do  Read(a[i,j]);     {每行3个数据}

             Readln;               {输完每行按回车键}

          end;

       for i:=1 to 3 do               {输入表二的数据}

          Begin

             Write(B[, I ,’]:’);               {提示第几行}

             Readln(B[ i ]);               {输入一个数据按回车}

           end;

       for i:=1 to 4 do              {计算并输出}

          Begin

             T[ i ]:=0;

             Write(’ ’:5,I:4);

             for j:=1 to 3 do

                Begin

                  Write(A[i , j]:6);

                  T[ i ]=T[ i ]+A[i , j]*B[j];

                end;

             Write(T[ i ]:8);

           end;

         Readln; 

    end.

     

    程序中定义二维组方式与一维数组形式相同。二维数组的元素由两个下标确定。

    二维数组元素的格式如下:

                        数组名 [下标1,下标2 ]

    常用下标1代表数据在二维表格中的行序号,下标2代表所在表格中列的序号。

     

    [例5.7]输入学号从1101 至1104的4名学生考试语文、数学、化学、英语、计算机六门课的成绩,编程求出每名学生的平均分,按每名学生数据占一行的格式输出。

    Name

    Chin

    Math

    Phys

    Chem

    Engl

    Comp

    Ave

    1101

    87

    91

    78

    85

    67

    78

     

    1102

    69

    84

    79

    95

    91

    89

     

    1103

    86

    69

    79

    89

    90

    88

     

    1104

    88

    89

    92

    87

    88

    81

     

    解:根据题目所给数据及要求,定义如下数据类型:

    ①学生成绩:在数据表格中每人的成绩占一行,每行6列(每科占一列);定义二维数组s,各元素为实型;

    ②个人平均分:定义一维数组av,各元素为实型;

    ③个人总分:是临时统计,为计算机平均分服务,用简单实型变量t;

    处理步骤为:

    ①用双重循环按行i按列j输入第i个人第j科成绩存入s [i, j];

    ②每读入一科成绩分,就累计到个人总分t中;

    ③输完第i个人的各科成绩,就计算出第i个人平均分并存入数组av(i)中;

    ④重复上述步骤,直到全部学生的成绩处理完毕;

    ⑤用双重循环按行列形式输出完整的成绩表。

    Pascal程序:

    Program Exam57;

    const  NB=1101;  NE=1104;

    Var   s: array[NB..NE,1..6] of real;   {定义二维数组(学生成绩)}

         av: array[NB..NE] of real;         {定义一维数组(平均成绩)}

          i: NB..NE;                            { i为子界类型(学号范围)}

          j: 1..6;                              { j为子界类型(课程范围)}

          t: eal;                                { t为实数类型(计总成绩)}

    Begin 

      for  i:=NB  to  NE  do                    {用i控制处理一位学生成绩}

        begin

          t:=0;

          write(i, ':  ');

          for j:=1 to 6 do                      {输入并计算每人的6门成绩}

            begin

              read(s[i , j]);

              t:=t+s[i , j];                  {累加个人总分}

            end;

          av[ i ]:=t / 6;                      {求个人平均分}

          readln                                 {输完6门分按一次回车键}

      end;

    writeln;                                  {输出学生成绩表}

    writeln('  ':5,' ***************************************** ');  

    writeln('  ':5,'Name  Chin  Math  Phys  Chem  Engl  Comp  Ave');

    writeln('  ':5,'---------------------------------------------');

    for i:=NB to NE do

      begin

        write(' ':5, i:4, '  ':2);       {输出学号}

    for j:=1 to 6 do

          write(s[i,j]:4:1, '  ':2);     {输出6门成绩}

        writeln(av[ i ]:4:1);              {输出平均分}

      end;

    readln

    End.

    程序中的学生成绩用键盘输入方式赋给二维数组各元素,如果是少量已知数据,也可在常量说明部分,直接给二维数组各元素赋常数,现将本例题改为如下程序:

    Program Exam57_1;

    Const NB=1101;  NE=1104;                         {定义常量}

    Type  Cou=(Chin,Math,Phys,Chem,Engl,Comp);   {自定义枚举类型}

          Num=NB..NE;                                   {自定义子界类型}

    Const s: array[Num,Cou] of real                {定义二维数组并赋常数}

             =((87,91,78,85,67,78),

             (69,84,79,95,91,89),

               (86,69,79,89,90,88),

               (88,89,92,87,88,81));

    Var  av: array[Num] of real;           {定义一维数组(平均成绩)}

        i: Num;                          { i为子界类型(学号范围)}

         j: Cou;                          { j为子界类型(课程范围)}

          t: real;                         { t为实数类型(计总成绩)}

    Begin

      for i:=NB to NE do                  {用i控制处理一位学生成绩}

        begin

        t:=0;

          for j:=Chin to Comp do          {计算每人的6门成绩}

            t:=t+s[i,j];                 {累加个人总分}

          av[ i ]:=t / 6;                   {求个人平均分}

        end;

      writeln;                                {输出学生成绩表}

      writeln(' ':5,'****************************************'); 

      writeln(' ':5,'Name  Chin  Math  Phys  Chem  Engl  Comp  Ave');

      writeln(' ':5,'---------------------------------------------');

      for i:=NB to NE do

        begin

          write('  ':5, i:4, '  ':2);      {输出学号}

          for j:=Chin to Comp do

            write(s[i,j]:4:1, '  ':2);    {输出6门成绩}

          writeln(av[ i ]:4:1);             {输出平均分}

        end;

     End.

    程序说明部分定义了枚举类型。枚举类型常用自然语言中含义清楚、明了的单词(看成代码)来表示“顺序关系”,是一种顺序类型,是根据说明中的排列先后顺序,才具有0,1,2…n的序号关系,可用来作循环变量初值和终值,也可用来作数组下标。但枚举类型不是数值常量或字符常量,不能进行算术运算,只能作为“序号关系”来使用。

     

    [例5.8]从红(red)、黄(yellow)、兰(blue)、白(white)、黑(black)五种颜色的球中,任取三种不同颜色的球,求所有可能的取法?

    解:①将五种颜色定义为枚举类型;

        ②a,b,c都是枚举类型中取不同颜色之一;

        ③a的取值范围从red to black;

          b的取值范围从red to black,但必须a < > b;

          c的取值范围从red to black,且必须 (a < > b) and (c < > b);

        ④每次打印取出的三个球的颜色,即第一个到第三个(for n:=1 to 3)

           当n=1:取a的值,根据a的“顺序”值输出对应颜色字符串;

           当n=2:取b的值,根据b的“顺序”值输出对应颜色字符串;

           当n=3:取c的值,根据c的“顺序”值输出对应颜色字符串;

        ⑤直至a,b,c的取值范围全部循环完毕。

    Pascal程序:

    program ex58;

    type color=(red,yellow,blue,white,black);

    var a,b,c,dm: color;

        nn: 1..3;

        s: integer;

    begin

      s:=0;

      for a:=red to black do

        for b:=red to black do

          if a < > b then

             for c:=red to black do

               if (c < > a) and (c < > b) then

                  begin

                    inc(s);  write(s:5);

                    for nn:=1 to 3 do

                      begin

                        case  nn  of        {找每种球的颜色 “顺序”值}

                          1:  dm:=a;       {dm是所取得的“顺序”值}

                          2:  dm:=b;

                          3:  dm:=c

                       end;

                       case  dm  of        {根据“顺序”值打印对应字串}

                         red   :   write('   red':9);

                         yellow :  write('yellow':9);

                         blue   :  write('  blue':9);

                         white  :  write(' white':9);

                         black  :  write(' blcak':9);

                       end;

                     end;

                   writeln

                 end;

      writeln;

      writeln('total num:':12, s:4);

      readln

    end.

    程序中的从red到black的顺序关系本来是不存在的,但经过枚举类型定义之后,就建立了这种“枚举”先后而产生的顺序排列关系。这种“关系”完全取决于类型说明时的位置排列。

     

    [例5.9] 新录A、B、C三个工人,每人分配一个工种,每个工种只需一人,经测试,三人做某种工作的效率如下表所示。如何分配三人的工作才能使他们工作效益最大?

       工种

    工人

    A

      4

      3

      3

    B

      2

      4

      3

    C

      4

      5

      2

    解:①定义各元素值为整数型的X数组,将表中的数据按行列关系作如下处理:

    A为第一行,将其三种工作效率(4,3,3)分别存入(x[A,1],x[A,2],x[A,3]);

    B为第二行,将其三种工作效率(2,4,3)分别存入(x[B,1],x[B,2],x[B,3]);

    C为第一行,将其三种工作效率(4,5,2)分别存入(x[C,1],x[C,2],x[C,3])。

    在这里,x数组第一个下标为枚举型,表示工人(A,B,C);第二个下标为子界型,表示工种(一、二、三):

        ②计算三人工作的总效率:S=x[A,i]+x[B,j]+x[C,k]

       A的工种i:1 ~ 3  (用循环for i:=1 to 3 );

       B的工种j:1 ~ 3  (用循环for j:=1 to 3 且j< >i);

       C的工种k=6-i-j    (工种代号总和为6,减去两个代号就得到第三个);

    ③将每次计算得到的S与“最大值”m比较,(m的初值为0),只要有大于m的S值即取代m原来的值,使之最大,同时用数组dd记录最大S值时的工种i, j, k值;

    ④当循环全部结束时,打印记录下来的每个人的工种。

    Pascal程序:

    Program exam59;

    type ma=(a,b,c);           {定义枚举类型)

         wk=1..3; {定义子界类型}

    Const x: array[ma,wk] of integer           {给x数组(二维)}

            =((4,3,3),(2,4,3),(4,5,2));        { 赋常量(表中值)}

          m: integer=0;             {给m赋初值0}

    Var dd: array[wk] of wk;  {用DD数组记忆工种号}

        i,j,k: wk;

        s: integer;

    begin

      for i:=1 to 3 do

        for j:=1 to 3 do

          if j< >i then

            begin

              k:=6-i-j;

              s:=x[a,i]+x[b,j]+x[c,k];

              if s>m then

                begin

                  m:=s;     {记下最大效益}

                  dd[1]:=i;     {记下最佳分配方案}

                  dd[2]:=j;

                  dd[3]:=k

                end

            end;

        for i:=1 to 3 do     {输出}

          writeln(chr(64+i):8, dd[ i ]:8);

        writeln('最大效益 :' :12, m:4);

    readln

    end.

    输出语句中的chr(64+i)是将(64+i)数值转换成对应的ASCII字符。

    程序中用枚举类型表示工人代码(A,B,C), 比较直观、清晰、易读好理解。

    在程序中枚举类型都可以用数字序号来取代,下面程序用1,2,3代表工人A,B,C:

    program exam59_1;

    type  n=1..3;

    Const x: array[n,n] of integer

           =((4,3,3),(2,4,3),(4,5,2));

          m: integer=0;

    Var dd: array[n] of n;

        i,j,k: n;

        s: integer;

    begin

      for i:=1 to 3 do

        for j:=1 to 3 do

         if j < > i then

            begin

              k:=6-i-j;

              s:=x[1,i]+x[2,j]+x[3,k];

             if s > m then

                begin

                  m:=s;

                  dd[1]:=i;

                  dd[2]:=j;

                  dd[3]:=k

                end

            end;

        for i:=1 to 3 do

          writeln(chr(64+i):8,dd[ i ]:8, x[i,dd[ i ]]:8);

    writeln('最大效益 :' :12, m:4);

      readln

    end.

    程序中的x[i,dd[ i ] ]是分配给i号工人做dd[ i ]号工种的效率值,在这里,以数组元素值作为下标,称为下标嵌套。

     

    [例5.10] 下面是一个3阶的奇数幻方。

    6

    1

    8

    7

    5

    3

    2

    9

    4

    它由1到32的自然数组成一个3*3的方阵,方阵的每一行,每一列和两个对角线上的各数字之和都相等,且等于n (n2+1) / 2(n是方阵的行数或列数)。编程打印出n为10以内的奇数阶幻方。

    解:仔细观察示例,有如下规律:

        ①在顶行中间填数字1;{横坐标(行)X=1,纵坐标(列)Y=(n+1) / 2 }

        ②后继数放在前一个数的左上方;  {X=X-1;Y:= Y-1}

          若超出了行,则认为是最后一行;{ if X<1 then X= n } 

          若超出了列,则认为是最后一列;{ if Y<1 then Y= n }

        ③若左上方已有数,则放在原数的正下方;{X=X+1,Y=Y }

        ④重复步骤②、③,直至填满方阵为止。

    Pascal程序:

    Program Exam510;

     Uses  Crt;

     Var   a: array[1..10,1..10] of integer;

           x,y,xx,yy,s,n: integer;

     Begin

       Clrscr;                            {清屏}  

       fillchar(a,sizeof ( a ), 0);       {将a数组各元素置0}

       repeat

           write('Input Number Please!');  {提示输入n}

           readln(n)

       until odd(n);

       s:=1;  x:=1;  y:=(n div 2)+1;  a[x,y]:=1;

       repeat

         inc(s);  xx:=x;  yy:=y;

         dec(x);  dec(y);

         if x<1 then  x:=n;

         if y<1 then  y:=n;

         if a[x,y]< >0 then  begin  x:=xx+1;  y:=yy  end;

         a[x,y]:=s;

       until s=n*n;

       for x:=1 to n do

          begin for y:=1 to n do write(a[x,y]:3);

                writeln

          end;

       repeat until keypressed;

     End.

    程序中fillchar(a,sizeof ( a ) , 0) 是给a数组各元素置0。Clrscr是清屏。

     

    习题5.2: 

    1.输入四个学生考试五门功课,要求按个人总分从高到低排列输出二维成绩表格。(即每行有学号,五科成绩及总分)

               1

             1   1

           1   2   1

         1   3   3   1

       1   4   6   4   1

    2.杨晖三角形的第n行对应着二项式n次幂展开式的各个系数。例如第3行正好是   (a+b)3=a3+3a2b+3ab2+b3展开式各项系数1,3,3,1。

    右图是n从0~4的杨晖三角形:

    第一行n=0,即(a+b)0 =1,系数为1;

    第二行n=1,即(a+b)1 = a+b,系数为1  1 ;

    第三行n=2,即(a+b)2 = a2+2 a b +b2 ,系数为 1  2  1;

    编程输出n行的杨晖三角形。

    3.下面是一个4*4的矩阵,它的特点是:(1)矩阵的元素都是正整数;(2)数值相等的元素相邻,这样,这个矩阵就形成了一级级“平台”,其上最大的“平台”面积为8,高度(元素值)为6。若有一个已知的N*N的矩阵也具有上面矩阵的特点,求矩阵最大“平台”的面积和高度。 

    4.打印一个n*n的数字螺旋方阵。这个数字方阵的特点是:以左上角1开始向下,数字以外圈向里按自然数顺序转圈递增,一直到中心位置的n2为止。例如n =3:       

                                         1  8  7

                                         2  9  6

                                         3  4  5

     

    第三节 集合类型

    Pascal系统把具有共同特征的同一有序类型的对象汇集在一起,形成一个集合,可将集合类型的所有元素作为一个整体进行集合运算。

    [例5.11]用随机函数产生20个互不相同的40到100的随机整数,然后按从小到大顺序打印。

    解:按以下步骤处理:

    ①为使产生的随机整数互不相同。因此,每产生一个数,都要判断集合中已否包含,如果没有包含,就放到集合中,并统计个数,直到20个。

    ②将集合中的数移到数组中,此题利用下标序号从小到大的特征进行映射排序打印。

    Pascal程序:

    Program Exam511;

    Uses  Crt ;

    Var a: Array[40..100] Of boolean;

        dd: set Of 40..100;                {定义集合dd}

        n: Integer;

    Procedure  Init;                     {定义产生并处理随机数的过程}

    Var i,m: Integer;

    Begin

      n:=0;dd:=[ ];                        {集合dd初值为空}

      repeat

          begin

            Randomize;                   {将随机发生器作初始化处理}

            m:=Random(100);                {产生随机整数m}

            if not (m in dd) and (m > 40 ) then 

              begin

                dd:=dd+[m]; inc(n)         {把m放入集合dd中}

              end;

          end

      until n=20;

    End;

    Procedure Print;                      {定义打印过程}

    Var i,j,k:Integer;

    Begin

      fillchar(a,sizeof(a),false);        {将数组a的各元素置false值}

      For i:=40 To 100 Do

        if i in dd then a[ i ]:=true;    {以集合元素值为下标的数组元素赋真值}

      For i:=40 To 100 Do                  {以下标号为序(从小到大)输出}

        If a[ i ] Then  Write(i:4);      {输出a数组中元素值为真的下标号}

    End;

    Begin                                    {主程序}

      Clrscr;

      init;                          {产生随机数,并存入集合中}

      print;                          {打印}

      Repeat Until KeyPressed;

    End.

    程序中定义了集合类型DD,集合的元素为子界类型。

    Var   集合变量名:set of  元素的类型;

    定义集合类型的一般格式是:

     

    集合的值放在一对方括号中,各元素用逗号隔开,与排列的顺序无关,因此,[9,2,5]和[2,5,9]的值相等,没有任何元素的集合是空集合,用[ ]表示。如果集合的元素是连续的,可用子界表示,如[5,6,7,8,9]可表示为[5 .. 9] 。

    集合变量名:= 集合表达式;

    集合的赋值格式为:

     

    集合有以下几种运算:

    1.集合的交、并、差运算:(设两个集合 a:=[1,2,4,6] 和 b:=[4,6,7,8] )

       ①集合的并:  a+b即组合成新的集合(为[1,2,4,6,7,8]);

       ②集合的交:  a*b即将a,b集合中的公共元素组合成新的集合(为[4,6,]);

       ③集合的差:  a-b即在a中的元素去掉在b中出现的之后,所剩下的集合(为[1,2])。

    2.集合的比较:

    ①相等:a=b,若两个集合中的元素个数相等,每个元素相同,则两个集合相等,比较结果为真(ture),否则为假(false);

    ②不等:a < > b表示两个集合不相等;

    ③包含:a > = b表示a集合包含b集合中的所有元素;

            a < = b表示a集合是b集合的子集。

    3.集合的测试运算:检查某个数据在集合中,测试结果为ture;不在集合中,测试结果为false;例如:

    6 in [8,6,9,4] 结果为ture;  { 6在集合[8,6,9,4]中为真 }

    2 in [8,6,9,4] 结果为false;  { 2在集合[8,6,9,4]中为假 }

     

    从程序Exam511的输出部分可看到,集合类型的值不能直接输出,要用测试方法进行输出或转换成数组元素的值。

     

    [例5.12]用集合进行筛法求200以内的素数。

    解:①将[2..200]放入集合S中;

        ②取S中的第一个元素值nxt,放入集合P中,同时将S中的凡是nxt的倍数的元素全部“划”去;

        ③重复步骤②,直至S集合为空;

        ④用测试运算打印P集合中全部元素值。

    Pascal程序:

    Program Exam512;

    Uses  crt;

    const n=200;

    var s,p: set of 2..n;                { s,p为集合类型}

       nxt,j,t: byte;

    begin

       clrscr;

       s:=[2..n];                          {将[2..n]赋给s}

       p:=[ ];

       nxt:=2;  t:=0;

       repeat

         while not(nxt in s) do

            nxt:=succ(nxt);               {后继函数}

         p:=p+[nxt];  j:=nxt;            {将nxt放入P中}

         while j<=n do

            begin

              s:=s-[j]; inc(j,nxt)       {筛掉S中的处理过的元素}

            end;

         if nxt in p then                {用测试运算进行输出}

              begin

                inc(t);  write(nxt :6);

                if t mod 6=0 then writeln

              end;

         until s = [ ];

         readln

    end.

    集合内的元素个数不能超过255个,如果要用超过255个成员的集合类型求素数,必须用小集合的数组来表示大集合,即把大集合分成若干个小集合,每个小集合只是数组的元素,(数组元素为一个小集合)整个数组就是一个大集合。筛法运用在每个数组元素(小集合)中进行。

     [例5.13]将自然数1--9这九个数分成三组,将每组的三个数字拼成为三位数,每个数字不能重复,且每个三位数都是完全平分数。请找出这样的三个三位数。

    解:①自定义函数yes,用集合判定九个数字是否有重复,采用逆向思维,假设做邓了三个三位完全平方数:

    将三个三位完全平方数分离成单个数字放入集合dd中,检查集合dd,如果自然数1~9每个数恰好都在集合dd中,函数yes赋真(ture) ;只要有一个不在集合中,九个数字没有占完集合中的九个位置,则必有重复,函数值为假(false),因为集合中对相同数字视为同一成员,如果有重复,则集合中不足9个成员(用测试运算)。

    ②程序用11~31平方产生三位的完全平方数。用循环方式每次取三个数为一组,存入a数组。

    ③对a数组的三位数调用自定义函数yes处理;

    ④如果函数yes值为真,就打印a数组中的三个数。

    Pascal程序:

    Program exam513;

    Uses Crt;

    Var a: Array[1..3] Of Integer;

        i, j, k, x: Integer;

    Function yes: Boolean;            {处理是否有重复数字}

    Var i: Integer;

        d: Set Of  0 .. 9;            {集合元素为子界类型}

    Begin

      d:=[ ];                          {集合的初值为空集合}

      For i:=1 To 3 Do     {将a数组中三个数分离成单个数并放入集合d}

        d:=d+[a[ i ] Div 100, (a[ i ] Mod 100) Div 10, a[ i ] Mod 10];

      yes:=true;

      For i:=1 To 9 Do

        If Not ( i In d ) Then yes:=false;  {只要有一个不在集合中即为假}

    End;

    Begin

      writeln;

      for i:=11 to 29 do             {在三位完全平方数范围内循环推出三个数}

        Begin

          a[1]:=i*i;                  {第一个三位的完全平方数}

          for j:=i+1 to 30 do

            begin

              a[2]:=j*j;              {第一个三位的完全平方数}

              for k:=j+1 to 31 do

                 begin

                   a[3]:=k*k;         {第一个三位的完全平方数}

                   If yes Then         {调用自定义yes函数结果为真就输出}

                        For x:=1 To 3 Do Writeln( x:8, ':', a[x]:8 );

                 end

            end

         end;

      Repeat Until KeyPressed;

    End.

     

    习题5.3

    1.设计一个将十六进制数转换为十进制数的程序。

    2.将自然数1--9数字不重复组成三个三位数,且三个数之比为1∶2∶3。求出能满足条件的全部方案。

    3.从键盘输入一个20位以内的自然数,然后将组成这个数的各位数字重新排列,得到一个数值为最小的新数,且新数的位数保持不变。打印出重新排列后的新数。

    4. 现有五件物品,重量分别为4、8、10、9、6.5公斤,它们的价值分别为12、21、24、17、10.5元。有一个背包,装入物品总量不得超过19公斤,该选哪几件物品放入背包内使总价值最大?

     

     

    第四节 记录类型和文件类型

    前面介绍的数组类型和集合类型有一个共同点,那就是在同一个数组或集合中的各有元素都必须具有相同的类型。如果要处理如下表所示的学生档案卡片上的数据,各栏目的数据类型不一样(学号,姓名,成绩…),需要用不同的类型表示。

    学号

    姓名

    性别

    出生年月

    语文

    数学

    英语

    平均分

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    为此,PASCAL系统定义了记录类型,可用来表示不同类型的数据。

    [例5.14]建立一张学生情况表格,求出每位学生的平均成绩,并输出这张表格。

    解:①定义记录类型:Date(表示日期记录,有三个域:day 日,mon月,yea年);

          Studa(表示学生情况记录,有六个域:nu学号,na姓名,dd出生年月,se性别,s成绩,ave平均分);

        ②读入记录数据;

        ③计算学生的平均成绩;

        ④输出记录内容。

    PASCAL程序:

    Program Exam514;

      Const n=2; m=3;                    {为了简单,人数N=2课目M=3}

      Type  Date=Record                   {定义Date(日期)记录类型}

                   day: 1..31;            {域名day表示天,为子界型(1..31)}

                   mon: 1..12;            {域名mon表示月,为子界型(1..12)}

                   yea: 1970..1999;     {域名yea表示年,为子界类型}

                 End;

            Studa=Record                  {定义Studa(学生情况)记录类型}

                    nu: string[5];       {域名nu表示学号,为字符串类型}

                    na: string[8];       {域名na表示姓名,为字符串类型}

                    dd: Date;             {域名dd表示日期,为记录类型(Date)}

                    se: char;             {域名se表示性别,为字符类型}

                    s: array[1..m] of real;  {域名s表示成绩,为数组类型}

                    ave: real;             {域名s表示平均分,为实数类型}

                  End;

           Sarr=array[1..n] of Studa; {定义Sarr为数组类型,各元素为记录类型}

      Var  Stu: sarr;                    {变量Stu为数组(Sarr)类型}

    Procedure rrd(var stu:sarr);      {定义输入和计算过程}

      var  i,k: integer;

           t: real;

           a: studa;                     {变量a为记录(Studa)类型}

      begin

        for k:=1 to n do

          begin

            with a,dd do                {开域语句,打开当前记录a和dd,进行以下操作}

              begin

                write(k:2,' nu: '); readln(nu);    {输入学号}

                write(k:2,' na: '); readln(na);    {输入姓名}

                write(k:2,' se: '); readln(se);    {输入性别}

                write(k:2,' day: '); readln(day);  {输入出生日}

                write(k:2,' mon: '); readln(mon);  {输入出生月}

                write(k:2,' yea: '); readln(yea);  {输入出生年}

                t:=0;

                for i:=1 to m do           {输入m科的成绩分}

                    begin

                      write('s[',i,']='); read(s[ i ]);

                      t:=t+s[ i ]           {累加总分}

                    end;

                readln;

                ave:=t/m;                 {计算平均分}

                stu[k]:=a                   {将当前记录存入stu[k]中}

              end;

          end

      end;

    Procedure print1;         {打印表格线}

      var  i: integer;

      begin

        for i:=1 to 60 do write('-');

        writeln;

    type  记录类型名 = record

                          域名:类型;

                          …

                          域名:类型;

                       end;

      end;

    Procedure print2;        {打印表头和表格栏目}

      begin

        writeln(' ':18,'________ table _________' );

        print1;

        write(' num.', ' ':6, 'name', ' ':7, 'mm/dd/yy', ' ':4 );

        writeln('sex  ', 'Chin', ' ':2, 'math', ' ':2, 'Engl', ' ':2, 'vaer' );

        print1

      end;

    Procedure print3(stu : sarr);   {打印记录数据}

      var i, j : integer;

      begin

        print2;

        for j:=1 to n do

          with stu[ j ], dd  do

            begin

              write(nu:5, na:9, '  ':8, mon:2, '/ ', day:2, '/ ',

                    yea:4, '  ' );

              write(se:3, '  ' );

              for i:=1 to m do write(s[ i ]:6:1);

              writeln(ave:6:1);

              print1

            end

      end;

    Begin

      rrd(stu);

      print3(stu);

      readln

    end.

    程序自定义Date记录类型,用来表示学生的出生年月日,含三个分量(称为域):

    day (日)     为子界类型(1..31);

    mon(月)      为子界类型(1..12);

    yea (年)     为子界类型(1970..1999);

    自定义Stuta记录类型,用来表示学生情况,含六个域:

    nu(学号)       为字符类型(string [5] 为5个字符);

    na(学号)       为字符类型(string [8] 为8个字符);

    dd(出生年月日) 是前面所定义的date记录类型 ;

    se(性别)       是char字符类型 ;

    s(表示学生成绩)是数组类型,各元素为real实型 ;

    ave (学生平均分)是real实型 ;

    程序定义的数组sarr的每个元素为Stuta记录(每个记录相当于一张学生情况卡片,整个数组相当于全体学生的情况卡片)。

    自定义记录类型的一般格式为:

    访问记录中的分量,有两种方式:

    ① 记录名.域名          例如对记录a中的nu赋值,可写成

                              a.nu:=1008; {将1008赋给记录a中的分量nu}

    ② with记录名 do 语句   开域语句,with后面可同时打开多个记录,

                             例如:with a, dd do语句;  { a, dd都是记录名}

    [例5.15]利用记录类型将N个数由大到小排序,输出排序结果并显示每个数原来的位置序号。

    PASCAL程序:

    Program Ex59;

      Type dd=Record                        {定义DD记录类型}

                ii:integer;                 {域名ii表示数}

                id:integer                  {域名id表示数ii的位置序号}

              End;

       Var a:array[1..100]of dd;

           i,j,k,n:integer;

           t:dd;

       Begin

         Write('Please Input Number of elements:');

         read(n);

         writeln('Input elements:');

         for i:=1 to n do

           begin

             read(a[i].ii);

             a[i].id:=i;

             for j:=1 to i-1 do

               if a[j].ii<a[i].ii then

                  begin

                    t:=a[i];

                    for k:=i-1 downto j do

                      a[k+1]:=a[k];

                    a[j]:=t;

                  end;

            end;

          for i:=1 to n do

            begin

              write(a[i].ii:5,'(',a[i].id:2,')');

              if i mod 10=0 then writeln

            end;

            readln;writeln;

        End.

     

    PASCAL系统自定义文件类型,可非常方便地实现对外存贮器的存取使用。常用的文件类型有顺序文件(File}和文本文件(Text)。

     

    [例5.16]建立一个由1到50连续整数为分量的顺序文件。

    解:①定义顺序文件类型(file);

        ②说明文件变量名;

        ③用assign过程指派一个文件变量和实际磁盘文件名对应;

        ④建立一个实际文件;

        ⑤将变量的值写入文件中;

        ⑥关闭文件。

    Program Exam516;

    type fdata=file of integer;                   {定义文件类型}

    var fd: fdata; i, k : integer;   {fd为文件变量}

    begin

      assign(fd, ’a: files); rewrite(fd);   {指派并建立文件}

      for I:=1 to 50 do write(fd, i ); close(fd); {写文件,最后关闭文件}

    end.

    [例5.17]输出一个由1到50连续整数为分量的顺序文件。

    解:①定义顺序文件类型(file);

        ②说明文件变量名;

        ③用assign过程指派文件变量和实际磁盘文件名对应;

        ④打开与文件变量对应的磁盘文件,并将文件分量指针设置在开头位置;

        ⑤读出指针所指的文件分量,赋给变量;

        ⑥关闭文件。

    Program Exam517;

    type fdata=file of integer;         {定义文件类型}

    var fd: fdata; i, k : integer;   {fd为文件变量}

    begin

      assign(fd, ’files); reset(fd);   {指派并恢复文件指针}

      for I:=1 to 50 do read(fd,i); close(fd);   {读文件,最后关闭文件}

    end.

    顺序文件在磁盘上以二进制形式存储,所以又称为二进制文件。

    另一种常用的文件类型是文本文件,以ASCII码的形式存储,比较通用,可以用DOS命令TYPE显示文件内容。

     

    [例5.18]建立 由50个随机整数组成、文件名为f1.dat的TEXT文件。

    解:①定义文件变量f为文本文件类型(TEXT);

        ②指派f与磁盘(指定盘符a:)上的f1.dat对应;

        ③建立文件;

        ④产生1~50个随机整数,写入文件中;

        ⑤关闭文件。

    Program Exam518;

      const

        n=50;

    var

        s: integer;                          {s为rr型}

        f: text;                             {f为text类型}

        i: integer;

    begin

      assign(f, ’a: f1.dat’ );           {用文件变量f与a驱磁盘上f1.dat文件对应}

      rewrite(f);                           {建立与f对应的实际文件名}

      randomize;                            {初始化随机函数}

      for I:=1 to n do

        begin

          s:=random(99)+1;                 {产生一个100以内的随机整数赋给s}

          write(f, s:6);                   {将s写入文件中}

          if I mod 5=0 then writeln(f)   {每行写5个数据}

        end;

      close( f )                             {关闭文件}

    end.

     

    [例5.19]读出a驱上磁盘文件f1.dat,并输出打印文件内容。

    解:①定义文件变量f为text类型;

        ②定义输出打印过程;

        ③定义读文件过程。

    Program Exam519;

      const

        n=50;

    var

        s: array[1..n] of integer;

        f: text;                          {文件变量f为文本类型(text)}

    procedure pf ;                        {输出打印过程}

    var i: integer;

      begin

        for I:=1 to n do

        begin

          write(s[ i ]:6);                {打印S数组元素}

          if I mod 10=0 then writeln

        end;

      writeln

      end;

    procedure rf ;                         {读出文件的过程}

      var i: integer;

      begin

        assign(f, 'a: f1.dat' ) ;       {指派a盘上f1.dat文件}

        reset(f ) ;                         {打开并恢复文件指针}

        i:=0;

        while not (eof ( f ) ) do         {当文件没有结束时就做以下语句}

          begin

            if not(eoln(f)) then           {如果不是行尾就读入数据}

                 begin

                   inc( i ); read(f, s[ i ]); {读入数据到数组S中}

                 end

              else readln(f );             {否则就换行读}

          end;

        close(f )                           {关闭文件}

      end;

    begin

      rf ;                                {读文件}

      pf                                   {输出}

    end.                 

    文件类型常用指令表

      操 作

           格      式

           注     释

    指派文件

     assign(文件变量, ’路径: 文件名’ );

     将文件变量指定为实际文件

    建立文件

     rewrite(文件变量);

     建立一个文件

    写文件

     write(文件变量,变量);

     将变量值写入文件分量

    打开文件

     reset(文件变量);

     打开文件,指针恢复到开头

    读文件

     read(文件变量,变量);

     读文件分量值赋给变量

    关闭文件

     close(文件变量);

     

    文尾函数

     Eof(文件变量)

     判断所读文件结束时为ture

    行尾函数

     Eoln(文件变量)

     判断行结束时为 ture

     

    习题5.4

    1.将2 ~ 500范围的素数存入a: \ prin中。

    2.读出a: \ prin文件,并按每行8个数据的格式打印。

    3.用随机函数产生100个三位数的随机整数,存入文件名为 ff.dat中,然后读出该文件内容,进行从大到小排序,将排序后的数据按每行10个的格式显示,并存入文件名为fff.dat中。

    4.将一段英文句子存入Eng.dat文件中(以每句为一行),然后读出并显示。(也可用汉语拼音代替英文句子)

    5.建立N个学生档案卡,每张卡包含:编号、姓名、性别、年龄、三科成绩的等级(分A、B、C三档),编程示例输出显示。

     

     

     

    第五节  指针类型与动态数据结构

    前面所学的变量都具有共同的特点:系统依据程序说明部分获得变量名和类型信息,即为变量分配对应大小的存储空间,在程序执行过程中,各变量对应的存储空间始终存在且保持不变,这些变量均称为静态变量。

    与静态变量对应的是动态变量,在程序执行过程中可以动态产生或撤消,所使用的存储空间也随之动态地分配或回收。为了使用动态变量。PASCAL系统提供了指针类型,用指针变量(静态变量)来指示动态变量(存储地址变量)。下面介绍如何利用指针建立动态数据结构。

     

    [例5.19] 分别用简单变量和指针变量交换两个变量的值。

    解:设两个变量a,b的值分别为5, 8

    (1)用简单变量交换:

    Program Exam519;

      const  a=5; b=8;                             {常量}

      var c: integer;

      begin

        c:=a; a:=b; b:=c;                        {用简单变量交换}

    Type  指针类型名 = ^ 基类型;

        writeln(a=:8, a, ’b=:8, b );

        readln

      end.

     

    (2)用指针变量交换:

    Var  指针变量名 :  ^ 基类型;

    Program Exam519_1; 

      type pon= ^ integer;                          {pon为指针类型}

      var  a,b,c: pon;                              {a,b,c为指针变量}

      begin

        New(指针变量);

          new(a ); new(b ); new(c );              {开辟动态存储单元}

          a ^ :=5;  b ^ :=8;                        {给a,b指向的存储单元赋值}

          c:=a; a:=b; b:=c;                        {交换存储单元的指针}

          writeln('a=':8, a ^ , ‘b=':8, b ^ );  {输出a,b所指单元的值}

        Dispose(指针变量);

          readln

      End.

     

            指针变量名 ^

    第(1)种方法,直接采用变量赋值进行交换,(实际上给各存储单元重新赋值)其过程如下图所示:

     

    第(2)种方法,对各存储单元所保存的值并不改变,只是交换了指向这些单元的存储地址(指针值),可以简略地用如下图示说明:

    (图中“—→”表示指向存储单元)

    指针类型的指针变量a,b存有各指向单元的地址值,将指针交换赋值步骤为:

    ①将c:=a (让c具有a的指针值);

    ②将a:=b (让a具有b的指针值);

    ③将b:=c (让b具有c的指针值);

    最后输出a,b所指向存储单元(a ^ 和b ^ )的值,此时的a指向了存储整数8的单元(即a ^ = 8),b指向了存储整数5的单元(即b ^ = 5)。存储单元的值没有重新赋值,只是存放指针值的变量交换了指针值。

        程序Exam519_1  Type pon= ^ integer; 是定义指针类型,一般格式为:

                     

        基类型就是指针所指向的数据元素的数据类型。也可以将类型说明合并在变量说明中直接定义说明:

    例如Exam519_1中类型说明与变量可以合并说明为:

    Var  a,b,c: ^ integer;    {与程序中分开说明的作用相同}

    定义了指针变量的类型之后,必须调用New过程开辟存储单元,调用格式如下:

    如果不再需要指针变量当前所指向的存储单元,可调用Dispose过程释放所占用的存储单元,Dispose和New的作用正好相反,其格式为:

    指针所指向的存储单元用如下形式表示:

    程序中的a ^ 表示指针变量a所指向的存储单元,与指针变量的关系如下图所示:

    在程序中对所指存储单元(为a ^)可以视作简单变量进行赋值计算和输出。

    如:a ^ := 5

     

    [例5.20] 利用指针对数组元素值进行排序。

    解:①定义一个各元素为指针类型的数组a :

        ②定义一个交换指针值的过程(swap);

        ③定义一个打印过程(print);

        ④定义过程(int)将数组b的值赋给a数组各元素所指向的各存储单元。

        ⑤过程pixu用交换指针值的方式,按a数组所指向的存储单元内容值从小到大地调整各元素指针值,实现“指针”排序;

        ⑥按顺序打印a数组各元素指向单元的值(a[ i ] ^ )。

    Program Exam520;

      const n=8;

            b: array[1..n] of integer

              =(44,46,98,86,36,48,79,71);

      type pon= ^ integer;

      var  a: array[1..n] of pon;

      Procedure swap(var p1, p2: pon);  {交换指针}

        var p: pon;

        begin

          p:=p1; p1:=p2; p2:=p

        end;

      Procedure print;         {打印数组各元素指向单元(a[ i ] ^ )的值}

        var i: integer;

        begin

          for i:=1 to n do write(a[ i ] ^ :6);

          writeln; writeln;

        end;

      Procedure int;        {将数组b的值赋给a数组各元素所指向的存储单元}

        var i: integer;

        begin

          for i:=1 to n do

           begin

             new(a[ i ]);

             a[ i ] ^ :=b[ i ];

           end;

          print;

        end;

      Procedure pixu;                 {排序}

        var i,j,k: integer;

        begin

          for i:=1 to n-1 do

            begin

              k:=i;

              for j:=i+1 to n do

                if a[j] ^ < a[k] ^  then  k:=j;

              swap(a[k], a[ i ])

            end

        end;

      Begin

       int;

       pixu;

       print;

       readln

      End.

     

    [例5.21] 有m只猴子要选猴王,选举办法如下:

    所有猴子按1 . . m编号围坐成圆圈,从第一号开始按顺序1, 2, . . , n连续报数,凡报n号的退出到圈外。如此循环报数,直到圈上只剩下一只猴子即当选为王。用指针(环形链表)编程。

    解:①让指针pon指向的单元为记录类型,记录内容含有两个域:

                 编号变量  链指针变量

     

    ②用过程crea建立环形链;

    ③用过程king进行报数处理:      每报数一次t计数累加一次,当所报次数能被n整除,就删去该指针指向的记录,将前一个记录的链指针指向下一个记录。删去的指向单元(记录)应释放。直到链指针所指向的单元是同一单元,就说明只剩下一个记录。

    ④打印指向单元记录中编号域(num)的值。

    program Exam521;

     type  pon= ^ rec;                {指向rec类型}

           rec=record                    {rec为记录类型}

                 num: byte;            {域名num为字节类型}

                 nxt: pon                {域名nxt为pon类型}

               end;

     var hd: pon;

        m, n: byte;

     procedure crea;                   {建立环形链}

        var s,p: pon;

             i: byte;

        begin

          new(s);  hd:=s;  s ^ . num :=1;  p:=s;

          for i:=2 to n do

            begin

              new(s);  s ^ . num :=i;  p ^ . nxt:=s;  p:=s

            end;

          p ^ . nxt :=hd

        end;

     procedure king;                   {报数处理}

       var p,q: pon;

           i, t: byte;

       begin

         p:=hd;  t:=0;  q:=p;

         repeat

           p:=q ^ . nxt;  inc(t);

           if t=n then

              begin

                q ^ . nxt :=p ^ . nxt;  dispose(p);  t:=0

              end

            else q:=p

         until p=p ^ . nxt;

        hd:=p

       end;

     begin

       write('m, n=');  readln(m, n);     {输入m只猴,报数到n号}

       crea;

       king;

       writeln('then king is :', hd ^ . num);

       readln

     end.

     

    习题5.5

    1.请将下列八个国家的国名按英文字典顺序排列输出。

       China(中国)      Japan(日本)    Cancda(加拿大)  Korea(朝鲜)

       England(英格兰)  France(法兰西)  American(美国)  India(印度)

    2.某医院里一些刚生下来的婴儿 ,都还没有取名字,全都统一用婴儿服包装,很难区分是谁的小孩。所以必须建立卡片档案,包含内容有编号、性别、父母姓名、床号。实际婴儿数是变动的,有的到期了,家长要抱回家,要从卡片上注销;新的婴儿出生,要增加卡片,请编程用计算机处理动态管理婴儿的情况。

     


    第六章 程序设计与基本算法

    学习计算机语言不是学习的最终目的。语言是描述的工具,如何灵活地运用语言工具,设计和编写能解决实际问题的程序,算法是程序设计的基础。算法的作用是什么呢?著名数学家高斯(GAUSS)从小就勤于思索。1785年,刚上小学二年级的小高斯,对老师出的计算题S=1+2+3+…+99+100,第一个举手报告S的结果是5050。班上的同学都采用依次逐个相加的“算法”,要相加99次;而小高斯则采用首尾归并,得出S=(1+100)*50的“算法”,只需加一次和乘一次,大大提高了效率。可见,算法在处理问题中的重要性。学习计算机编程,离不开基本算法。刚开始学习程序设计时,就应注重学习基本算法。

    第一节 递推与递归算法

     

    递推和递归是编程中常用的基本算法。在前面的解题中已经用到了这两种方法,下面对这两种算法基本应用进行详细研究讨论。

     

    一、递推

    递推算法是一种用若干步可重复的简单运算(规律)来描述复杂问题的方法。

     

    [例1] 植树节那天,有五位参加了植树活动,他们完成植树的棵数都不相同。问第一位同学植了多少棵时,他指着旁边的第二位同学说比他多植了两棵;追问第二位同学,他又说比第三位同学多植了两棵;…如此,都说比另一位同学多植两棵。最后问到第五位同学时,他说自己植了10棵。到底第一位同学植了多少棵树?

    解:设第一位同学植树的棵数为a1,欲求a1,需从第五位同学植树的棵数a5入手,根据“多两棵”这个规律,按照一定顺序逐步进行推算:

        ①a5=10;

        ②a4=a5+2=12;

        ③a3=a4+2=14;

        ④a2=a3+2=16;

        ⑤a1=a2+2=18;

    Pascal程序:

      Program Exam1;

        Var i, a: byte;

          begin

             a:=10;              {以第五位同学的棵数为递推的起始值}

             for i :=1 to 4 do   {还有4人,递推计算4次}

                a:= a+2;        {递推运算规律}

             writeln(The Num is, a);

             readln

          end.

    本程序的递推运算可用如下图示描述:

      

        递推算法以初始{起点}值为基础,用相同的运算规律,逐次重复运算,直至运算结束。这种从“起点”重复相同的方法直至到达一定“边界”,犹如单向运动,用循环可以实现。递推的本质是按规律逐次推出(计算)下一步的结果。

    二、递归

    递归算法是把处理问题的方法定义成与原问题处理方法相同的过程,在处理问题的过程中又调用自身定义的函数或过程。

    仍用上例的计算植树棵数问题来说明递归算法:

    解:把原问题求第一位同学在植树棵数a1,转化为a1=a2+2;即求a2;而求a2又转化为a2=a3+2; a3=a4+2; a4=a5+2;逐层转化为求a2,a3,a4,a5且都采用与求a1相同的方法;最后的a5为已知,则用a5=10返回到上一层并代入计算出a4;又用a4的值代入上一层去求a3;...,如此,直到求出a1。

    因此:

               

    其中求a x+1 又采用求ax 的方法。所以:

    ①定义一个处理问题的过程Num(x):如果X < 5就递归调用过程Num(x+1);

    ②当递归调用到达一定条件(X=5),就直接执行a :=10,再执行后继语句,遇End返回到调用本过程的地方,将带回的计算结果(值)参与此处的后继语句进行运算(a:=a+2);

    ③最后返回到开头的原问题,此时所得到的运算结果就是原问题Num(1)的答案。

    Pascal程序:

     Program Exam1_1;

       Var a: byte;

       Procedure Num(x: integer);{过程Num(x)求x的棵数}

     begin

       if x=5 then a:=10

          else begin

                 Num(x+1); {递归调用过程Num(x+1)}

                 a:=a+2     {求(x+1)的棵数}

               end

     end;

    begin

      Num(1);     {主程序调用Num(1)求第1个人的棵数}

      writeln(The Num is , a);

      readln

    end.

       程序中的递归过程图解如下:

    参照图示,递归方法说明如下:

    ①调用原问题的处理过程时,调用程序应给出具体的过程形参值(数据);

    ②在处理子问题中,如果又调用原问题的处理过程,但形参值应是不断改变的量(表达式);

    ③每递归调用一次自身过程,系统就打开一“层”与自身相同的程序系列;

    ④由于调用参数不断改变,将使条件满足(达到一定边界),此时就是最后一“层”,不需再调用(打开新层),而是往下执行后继语句,给出边界值,遇到本过程的END,就返回到上“层”调用此过程的地方并继续往下执行;

    ⑤整个递归过程可视为由往返双向“运动”组成,先是逐层递进,逐层打开新的“篇章”,(有可能无具体计算值)当最终递进达到边界,执行完本“层”的语句,才由最末一“层”逐次返回到上“层”,每次返回均带回新的计算值,直至回到第一次由主程序调用的地方,完成对原问题的处理。

    [例2] 用递归算法求X n 。

    解:把X n 分解成: X 0 = 1           ( n =0 )

                       X 1 = X * X 0     ( n =1 )

                       X 2 = X * X 1     ( n >1 )

                       X 3 = X * X 2      ( n >1 )

                        ……             ( n >1 )

                       X n = X * X n-1      ( n >1 )

    因此将X n 转化为:

    其中求X n -1 又用求X n 的方法进行求解。

    ①定义过程xn(x,n: integer)求X n ;如果n >1则递归调用xn (x, n-1) 求X n—1 ;

    ②当递归调用到达n=0,就执行t t :=1, 然后执行本“层”的后继语句;

    ③遇到过程的END就结束本次的调用,返回到上一“层”调用语句的地方,并执行其后续语句tt:=tt*x;

    ④继续执行步骤③,从调用中逐“层”返回,最后返回到主程序,输出tt的值。

    Pascal程序:

     Program Exam2;

      Var tt, a, b: integer;

      Procedure xn(x, n: integer); {过程xn(x, n)求xn }

        begin  if n=0 then tt:=1

            else begin

                   xn(x, n-1);  {递归调用过xn(x,n-1)求x n-1}

                   tt:=tt*x

                 end;

    end;

      begin

        write(input x, n:’);  readln(a,b);  {输入a, b}

        xn(a,b);        {主程序调用过程xn(a, b)求a b}  

        writeln(a, ’^, b, ’=, tt);

        readln

      end.

    递归算法,常常是把解决原问题按顺序逐次调用同一“子程序”(过程)去处理,最后一次调用得到已知数据,执行完该次调用过程的处理,将结果带回,按“先进后出”原则,依次计算返回。

    如果处理问题的结果只需返回一个确定的计算值,可定义成递归函数。

    [例3]用递归函数求x!

    解:根据数学中的定义把求x! 定义为求x*(x-1)! ,其中求(x-1)! 仍采用求x! 的方法,需要定义一个求a!的过程或函数,逐级调用此过程或函数,即:

      (x-1)!= (x-1)*(x-2)! ;

      (x-2)!= (x-2)*(x-3)! ;

       ……

    直到x=0时给出0!=1,才开始逐级返回并计算各值。

    ①定义递归函数:fac(a: integer): integer;

      如果a=0,则fac:=1;

      如果a>0,则调用函数fac:=fac(a-1)*a;

    ②返回主程序,打印fac(x)的结果。

    Pascal程序:

     Program Exam3;

      Var x: integer;

      function fac(a: integer): integer; {函数fac(a) 求a !}

       begin

    if a=0 then fac:=1

      else fac:=fac(a-1)*a {函数fac(a-1)递归求(a-1) !}

       end;

       begin

     write(input x);  readln(x);

     writeln(x, ’!=, fac(x)); {主程序调用fac(x) 求x !}

     readln

       end.

    递归算法表现在处理问题的强大能力。然而,如同循环一样,递归也会带来无终止调用的可能性,因此,在设计递归过程(函数)时,必须考虑递归调用的终止问题,就是递归调用要受限于某一条件,而且要保证这个条件在一定情况下肯定能得到满足。

     

    [例4]用递归算求自然数A,B的最大公约数。

    解:求最大公约数的方法有许多种,若用欧几里德发明的辗转相除方法如下:

    ①定义求X除以Y的余数的过程;

    ②如果余数不为0,则让X=Y,Y=余数,重复步骤①,即调用过程;

    ③如果余数为0,则终止调用过程;

    ④输出此时的Y值。

    Pascal程序:

      Program Exam4;

        Var a,b,d: integer;

        Procedure Gdd(x, y: nteger);{过程}

          begin

            if x mod y =0 then d :=y

             else Gdd(y, x mod y) {递归调用过程}

          end;

        begin

          write(input a, b=); readln(a, b);

          Gdd(a, b);

          writeln((, a, ’,’, b, ’)=, d );

          readln

        end.

    简单地说,递归算法的本质就是自己调用自己,用调用自己的方法去处理问题,可使解决问题变得简洁明了。按正常情况有几次调用,就有几次返回。但有些程序可以只进行递归处理,不一定要返回时才进行所需要的处理。

     

    [例5] 移梵塔。有三根柱A,B,C在柱A上有N块盘片,所有盘片都是大的在下面,小片能放在大片上面。现要将A上的N块片移到C柱上,每次只能移动一片,而且在同一根柱子上必须保持上面的盘片比下面的盘片小,请输出移动方法。

    解:先考虑简单情形。

    如果N=3,则具体移动步骤为:

     

     

    假设把第3步,第4步,第6步抽出来就相当于N=2的情况(把上面2片捆在一起,视为一片):

     

    所以可按“N=2”的移动步骤设计:

    ①如果N=0,则退出,即结束程序;否则继续往下执行;

    ②用C柱作为协助过渡,将A柱上的(N-1)片移到B柱上,调用过程sub(n-1, a,b,c);

    ③将A柱上剩下的一片直接移到C柱上;

    ④用A柱作为协助过渡,将B柱上的(N-1)移到C柱上,调用过程sub(n-1,b,c,a)。

     

    Pascal程序:

      Program Exam65;

        Var x,y,z : char;

           N, k : integer;

        Procedure  sub(n: integer; a, c , b: char);

          begin

            if n=0 then exit;

            sub(n-1, a,b,c);

            inc(k);

            writeln(k, ’: from, a, ’-->’, c);

            sub(n-1,b,c,a);

        end;

      begin

        write(n=; readln(n);

        k:=0;

        x:=A; y:=B; Z:=C;

        sub(n,x,z,y);

        readln

      end.

    程序定义了把n片从A柱移到C柱的过程sub(n,a,c,b),这个过程把移动分为以下三步来进行:

    ①先调用过程sub(n-1, a, b, c),把(n-1)片从A柱移到B柱, C柱作为过渡柱;

    ②直接执行 writeln(a, ’-->’, c),把A柱上剩下的一片直接移到C柱上,;

    ③调用sub(n-1,b,c,a),把B柱上的(n-1)片从B移到C柱上,A柱是过渡柱。

    对于B柱上的(n-1)片如何移到,仍然调用上述的三步。只是把(n-1)当成了n,每调用一次,要移到目标柱上的片数N就减少了一片,直至减少到n=0时就退出,不再调用。exit是退出指令,执行该指令能在循环或递归调用过程中一下子全部退出来。

     

    习题6.1

    1.过沙漠。希望一辆吉普车以最少的耗油跨越1000 km的沙漠。已知该车总装油量500升,耗油率为1升/ km,必须利用吉普车自己沿途建立临时加油站,逐步前进。问一共要多少油才能以最少的耗油越过沙漠?

    2.楼梯有N级台阶,上楼可以一步上一阶,也可以一步上二阶。编一递归程序,计算共有多少种不同走法?

    提示:如N级楼梯有S(N)种不同走法,则有:

    S(N)=S(N-2)+S(N-1)

    3.阿克曼(Ackmann)函数A(x,y)中,x,y定义域是非负整数,函数值定义为:

       A(x,y)=y+1                  (x = 0) 

       A(x,0)=A(x-1,1)             (x > 0, y = 0)

       A(x,y)=A(x-1, A(x, y-1))    (x, y > 0)

    设计一个递归程序。

    4.某人写了N封信和N个信封,结果所有的信都装错了信封。求所有的信都装错信封共有多少种不同情况。可用下面公式:

    Dn=(n—1) ( D n—1+D n—2)

    写出递归程序。

     

     

    第二节 回溯算法

     

    在一些问题求解进程中,有时发现所选用的试探性操作不是最佳选择,需退回一步,另选一种操作进行试探,这就是回溯算法。

     

    例[6.6] 中国象棋半张棋盘如下,马自左下角往右上角跳。现规定只许往右跳,不许往左跳。比如下图所示为一种跳行路线。编程输出所有的跳行路线,打印格式如下:

    <1>  (0,0)—(1,2)—(3,3)—(4,1)—(5,3)—(7,2)—(8,4)

                     

    解:按象棋规则,马往右跳行的方向如下表和图所示:

    水平方向用x表示; 垂直方向用y表示。右上角点为x=8, y=4, 记为(8, 4) ; 用数组tt存放x方向能成行到达的点坐标;用数组t存放y方向能成行到达的点坐标;

    ①以(tt(K), t(k))为起点,按顺序用四个方向试探,找到下一个可行的点(x1, y1);

    ②判断找到的点是否合理 (不出界),若合理,就存入tt和t中;如果到达目的就打印,否则重复第⑴步骤;

    ③如果不合理,则换一个方向试探,如果四个方向都已试过,就退回一步(回溯),用未试过的方向继续试探。重复步骤⑴;

    ④如果已退回到原点,则程序结束。

    Pascal程序:

    Program Exam66;

    Const  xx: array[1..4] of 1..2 =(1,2,2,1);

           yy: array[1..4] of -2..2=(2,1,-1,-2);

    Var p: integer;

       t, tt : array[0..10] of integer;

    procedure Prn(k: integer);

    Var i: integer;

    Begin

    inc(p);  write(, p: 2, ’ , ’  ‘:4, ’0,0);

    for i:=1 to k do

    write(— ( , tt[ I ], ’ , ’, t[ I ], ’)’ );

    writeln

    End;

    Procedure Sub(k: integer);

    Var x1, y1, i: integer;

    Begin

    for I:=1 to 4 do 

    Begin 

    x1:=tt[k-1]+xx[ i ];  y1:=t[k-1]+yy[ i ];

    if not( (x1 > 8) or (y1 < 0) or (y1 > 4) ) then

    Begin

    tt[k]:=x1;  t[k]=y1;

    if (y1=4) and (x1=8) then prn(k);

    sub(k+1);

    end;

    end;

    end;

    Begin

    p:=0;  tt[0]:=0;  t[0]:=0;

    sub(1);

    writeln( ‘ From 0,0 to 8,4  All of the ways are ’, p);

    readln

    end.

     

    例[6.7] 输出自然数1到n所有不重复的排列,即n的全排列。

    解:①在1~n间选择一个数,只要这个数不重复,就选中放入a数组中;

    ②如果这个数巳被选中,就在d数组中作一个被选中的标记 (将数组元素置1 );

    ③如果所选中的数已被占用(作了标记),就另选一个数进行试探;

    ④如果未作标记的数都已试探完毕,那就取消最后那个数的标记,退回一步,并取消这一步的选数标记,另换下一个数试探,转步骤①;

    ⑤如果已退回到0,说明已试探全部数据,结束。

    Pascal程序:

    Program Exam67;

    Var p,n: integer;

    a,d: array[1..500] of integer;

    Procedure prn (t : integer);

    Var i: integer;

    Begin

    write(‘ , p:3, ’ , ’  ‘:10);

    for I:=1 to t do

    write(a[ I ]:4);

    writeln;

    end;

    Procedure pp(k: integer);

    var x: integer;

    begin

    for x:=1 to n do

    begin

    a[k]:=x; d[x]:=1;

    if k < n then pp(k+1)

    else

    begin

    p:=p+1;

    prn(k);

    end;

    end;

    end;

    Begin

    write(Input n=);  readln(n);

    for p:=1 to n do d[p]=0;

    p:=0;

    pp(1);

    writeln(All of the ways are , p:6);

    End.

     

    例[6.8] 设有一个连接n个地点①—⑥的道路网,找出从起点①出发到过终点⑥的一切路径,要求在每条路径上任一地点最多只能通过一次。

     

    解:从①出发,下一点可到达②或③,可以分支。具体步骤为:

    ⑴假定从起点出发数起第k个点Path[k],如果该点是终点n就打印一条路径;

    ⑵如果不是终点n,且前方点是未曾走过的点,则走到前方点,定(k+1)点为到达路径,转步骤⑴;

    (3)如果前方点已走过,就选另一分支点;

    (4)如果前方点已选完,就回溯一步,选另一分支点为出发点;

    (5)如果已回溯到起点,则结束。

    为了表示各点的连通关系,建立如下的关系矩阵:

     

    第一行表示与①相通点有②③,0是结束            标志;以后各行依此类推。

     

    集合b是为了检查不重复点。

     

    Program Exam68;

    const n=6;

        roadnet: array[1..n, 1..n] of 0..n=( (2,3,0,0,0,0),

                                                (1,3,4,0,0,0),

                                                      (1,2,4,5,0,0),

                                                      (2,3,5,6,0,0),

                                                      (3,4,6,0,0,0),

                                                      (4,5,0,0,0,0) );

    var b: set of 1..n;

       path: array[1..n] of 1..n;

       p: byte;

     procedure prn(k: byte);

     var i: byte;

     begin

       inc(p); write(<, p:2, ’>, ’ ’:4);

       write (path[1]:2);

       for I:=2 to k do

        write (--, path[ i ]:2);

       writeln

     end;

     procedure try(k: byte);

     var j: byte;

     begin

     1

     2

     3

     4

     5

     6

     X

     8

     9 

    10

    11

    12

    13

    14

    15

       j:=1;

       repeat

       path[k]:=roadnet [path [k-1], j ];

       if not (path [k] in b) then

          begin b:=b+[path [k] ];

           if path [k]=n then prn (k)

            else try(k+1);

           b:=b-[path [k] ];

          end;

       inc(j);

       until roadnet [path [k-1], j ]=0

     end;

    begin

      b:=[1]; p=0; path[1]:=1;

      try(2);

      readln

    end.

     

     

    习题[6.2]

    1. 有A,B,C,D,E五本书,要分给张、王、刘、赵、钱五位同学,每人只能选一本。事先让每个人将自己喜爱的书填写在下表中。希望你设计一个程序,打印分书的所有可能方案,当然是让每个人都能满意。

           A    B    C    D    E

       张            Y    Y

       王  Y   Y               Y

       刘       Y    Y

       赵                 Y

       钱  Y                   Y

     

    2. 右下图所示的是空心框架,它是由六个单位正方体组成,问:从框架左下外顶点走到右上内顶点共有多少条最短路线?

     

     

     

     

     

     3.城市的街道示意图如右:问从甲地去到乙地可以有多少条最短路线? 

     

     

     

     

    4.有M×N张(M行, N列)邮票连在一起,

        但其中第X张被一个调皮的小朋友控掉了。上图是3×5的邮票的形状和编号。从这些邮票中撕出四张连在一起的邮票,问共有多少种这样四张一组的邮票?注:因为给邮票编了序号,所以1234和2345应该看作是不同的两组。

     5.有分数2(1)3(1)4(1)5(1)6(1)8(1)10(1)12(1)15(1), 求将其中若干个相加的和恰好为1的组成方案,并打印成等式。例如:

    <1> 2(1)+3(1)+6(1)= 1

    <2> ...

     6.八皇后问题。在8*8的国际象棋盘上摆上8个皇后。要求每行,每列,各对角线上的皇后都不能互相攻击,给出所可能的摆法。

     

     


    第七章 数据结构及其应用

    数字,字符,声音,图像,表格等信息,均可输入计算机中进行处理。在计算机科学中,象这种能输入到计算机中并被计算机程序处理的信息,都可称为数据。

        数据的基本单位是数据元素。数据之间存在有线性与非线性两种基本的逻辑结构,同时在存储结构上还有顺序和链式之分。

        数据结构则是研究数据元素的逻辑结构,存储结构和与之有关的各种基本操作的一门学科。作为一个程序设计者,应当掌握好数据结构的有关知识,在解题时针对问题的特点,选择适当的数据结构,并构造算法,编出优美高效的好程序。

        本章将介绍一些线性的数据结构及其基本操作。

     

    第一节 线性表

    “线性表”是指由有限多个类型相同的数据元素组成的集合,它有以下的特点:

    (1) 有唯一的头结点(即第一个数据元素)和尾结点(即最后一个数据元素);

    (2) 除结点外,集合中的每个数据元素均只有一个前驱;

    (3) 除尾结点外,集合中的每一个数据元素均只有一个后继。

    “线性表”是一种运用非常广范的数据结构。

    例一、某旅馆有100个房间,以1到100编号,第一个服务员来了,他将所有的房门都打开,第二个服务员再把所有编号是2的倍数的房门都关上,第三个服务员对编号是3的倍数的房门原来开的关上,原来关上的打开,此后的第四,五...服务员均照此办理。问第100个服务员走进后,有哪几扇门是开着的。

    解:Pascal程序:

    Program lt7_1_1;

    uses crt;

    var door:array[1..100] of boolean; 1到100号房门状态,false--关,true--开

        i,j:integer;

    begin

      clrscr;

      fillchar(door,sizeof(door),true);  第一个服务员打开全部房门

      for i:=2 to 100 do        i表示服务员号码

        for j:=1 to 100 div i do

          door[i*j]:=not door[i*j]; 对房号为i的倍数的房门进行相反处理

      write('The code of opening door is : ');

      for i:=1 to 100 do

        if door[i] then write(i,' ');

    end.

    分析:(1)这里用door[1..100]来存储1到100号房门的开关状态,即是一种线性表,其中:door[1]可以看成是头结点,而door[100]是尾结点;同时由于数组在内存中是按顺序占有一片连续的内存空间,因此这种存储结构即是顺序存储结构。

         (2)这里用布尔变量true和false分别表示开和关两种状态,对某一房门进行相反处理时只要取反(not)即可,比如:若door[10]为false,not door[10]则为true。

     

    例二、插入排序:在一个文本文件中存放的N个人的姓名,文本文件的格式为:第一行为N,以下第二至第N+1行分别是N个人的姓名(姓名不重复,由英文字母组成,长度不超过10),请编一个程序,将这些姓名按字典顺序排列。

    解:Pascal程序:

    Program lt7_1_2;

    uses crt;

    type point=^people;  定义结点类型

         people=record

           name:string[10];  name--数据域,存放姓名

           next:point;   next--指针域,存放后继结点的地址

         end;

    var head:point;

        n:integer;

     

    procedure init;  初始化

      begin

        new(head);head^.next:=nil; 定义头结点,初始链表为空

      end;

     

    procedure insert(p:point); 将P指向的结点插入以head开头的线性表中

      var q1,q2:point;

      begin

        if head^.next=nil then head^.next:=p 将P指向的结点插入空链表

           else

             begin

               q1:=head;q2:=q1^.next;

               while (q2<>nil) and (q2^.name<p^.name) do

                 begin q1:=q2;q2:=q1^.next; end; 查找结点p应插入的位置

               q1^.next:=p;p^.next:=q2;将p插入q1之后q2之前

             end;

      end;

     

    procedure work;

      var i:integer;

          fn:string;

          f:text;

          p:point;

      begin

        write('Filename:');readln(fn);

        assign(f,fn);reset(f);

        readln(f,n);

        for i:=1 to n do

          begin

            new(p);p^.next:=nil;

            readln(f,p^.name);

            insert(p);

          end;

      end;

     

    procedure print; 打印

      var p:point;

      begin

        p:=head^.next;

        while p<>nil do

          begin

            writeln(p^.name);

            p:=p^.next;

          end;

      end;

     

    begin

      clrscr;

      init;

      work;

      print;

    end.

    分析:(1)排序有多种方法,插入排序是其中的一种,其原理同摸扑克牌类似:摸到一张牌,把它按大小顺序插入牌中,每一张都如此处理,摸完后,得到的就是一副有序的扑克牌。本题可以用插入排序求解;

           (2)为了减少移动数据的工作,可以采用链式存储结构。每个结点由两个域构成,数据域(用来存放姓名)和指针域(用来存放后继结点的地址)。如图A是将1,3,6,7按顺序构成一个线性链表的示意图。这样在这个有序表中插入一个5时,只需对指针进行相应地操作即可,如下图B:

              ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐

      头结点→│1 │ --→│3 │ --→│6 │ --→│7 │^ │←尾结点

              └─┴─┘ └─┴─┘ └─┴─┘ └─┴─┘

                                  图 A          

              ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐

      头结点→│1 │ --→│3 │  │ │6 │ --→│7 │^ │←尾结点 

              └─┴─┘ └─┴┼┘ └↑┴─┘ └─┴─┘

                               ↓   ┌┘

                              ┌─┬┼┐

                              │5 │  │←插入的结点

                              └─┴─┘

                                图 B

    练习一

    1、求1987乘幂的尾数:

       M和N是自然数,N>M>=1,而1987^M与1987^N的末三位数相同,求最小的M和N。

    分析:

    (1) 本题只须记录1987的乘幂的末三位数,故不必高精度计算;

    (2) 用数组a[1..n]存储1987的1至n次幂的末三位数;

    (3) n的初始值为2,计算1987的n次幂的末三位数,并和1987的1至n-1次幂进行比较,若无相等的,则n=n+1,重复(3);否则,第一次找到相等的,即是所求的m,n值。

    2、一个特殊的数列:

        写出两个1,然后在它们中间插入2成为121,下一步是在任意两个相邻的和数为4的数之间插入3,成为13231;再下一步又在任意两个相邻的和数为4的数之间插入4,成为1432341,...,由键盘输入N(1<=N<=9),求出用上面方法构造出来的序列,其最后插入的数为N。

    分析:字符串也可以看做是一个特殊的线性表,本题初始串是11,对应N=1时的情况;然后在串中寻找相应的位置,依次插入2,3,...,K。

    3、求序列的第300项:

      把所有3的方幂及互不相等的3的方幂和排列成一个递增序列:1,3,4,9,10,12,13,...,求这个序列的第300项。

    分析:本题可以用一个线性表来记录这个递增的序列,通过递推可以将整个序列构造出来。方法如下:

        (1)数组a存放线性表,t为尾指针,b存放3的幂,初始时t=1,b=1;

        (2)将b放入表尾,尾指针加1;a[t]←b;t←t+1;

        (3)将b依次与1至t-1的元素相加,按顺序放入表尾;

        (4)重复(2),(3),直至第300项放入表中。

    4、约瑟夫环(Joseph)

       编号为1,2,...,N的N个人按顺时针方向围成一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值M,从第一个人开始按顺时针方向自1开始报数,报到M时停止,报M的人出列,将他的密码作为新的M值,从他在顺时针方向上的第一个人开始重新从1报数,如此下去,直至所有有人全部出列为止。试设计一个程序求出列的顺序。

    分析:这是一个数学游戏。N个人围成一圈,依次报数,可以用一个循环链表模拟这一过程。将链表的表尾指向表头,即形成循环链表。从某个人开始报数,报到M的人出列,也就是在在循环链表中删除相应的结点,然后依次删除完所有的结点,此时链表为空,头指针与尾指针相等。在具体操作中,要注意删除头结点和尾结点时指针的处理,谨防出错。

    5、多项式的加法:试编程完成两个一元多项式的加法。

    分析:大家都知道,两个一元多项式相加实际上就是合并同类项,最后结果一般要求按字母的升幂或降幂排列,比如:(2X^10+4X+1)+(3X^5-4X+2)=2X^10+3X^5+3。那么一元多项式在计算机中如何表示呢?为了节省空间,一个元多项式一般用一个线性表表示,线性表的每一个结点包括两个域:一个用来存放系数,一个用来存放指数,比如,上面相加的两个一元多项式可以分别表示为:

        ┌─┬─┬─┐  ┌─┬─┬─┐  ┌─┬─┬─┐

        │2 │10│ ─→ │4 │1 │ ─→ │1 │0 │^ │

        └─┴─┴─┘  └─┴─┴─┘  └─┴─┴─┘

        ┌─┬─┬─┐  ┌─┬─┬─┐  ┌─┬─┬─┐

        │3 │5 │ ─→ │-4│1 │ ─→ │2 │0 │^ │

        └─┴─┴─┘  └─┴─┴─┘  └─┴─┴─┘

        这样两个一元多项式相加就可以看成是归并两个链表。

     

    第二节 队列

        在日常生活中有许多“队列“的例子,如车站售票口买票的队伍,排在前面的人先买到票离开队伍,后来的人则加入队伍的末尾等候买票;其特点是“先进先出”(First In First Out)或“后进后出”(Last In Last Out)。

        “队列”是在一端插入,另一端删除的特殊的线性表。进行删除的一端称为“队首”,进行插入的一端称为“队尾”(如下图);插入也叫入队,删除则叫出队;在对队列进行操作时,一定要注意一头一尾。

           ─┬──┬──┬──┬──┬─

    出队 ←  │ a1 │ a2 │... │ an │ ← 入队

           ─┴──┴──┴──┴──┴─

                ↑                ↑

               队头              队尾

    例1、有N张牌,记为1,2,...,N,应当怎样排放,才能使:打开第一张是1,然后报两张依次放在末尾;打开上面一张,刚好是2,再依次打开上面一张,刚好是3;如此继续下去,直至打开最后一张是N。写一个程序解决这个问题。

    解:Pascal程序:

    Program lt7_2_1;

    uses crt;

    var a,b:array[1..1000] of integer;

        i,j,t,h,n:integer;

    begin

      clrscr;

      write('N=');readln(n);

      for i:=1 to n do a[i]:=i;

      a[1]:=1;h:=2;t:=n;b[1]:=1;

      for i:=2 to n do

        begin

          for j:=1 to i do

            begin

              inc(t);a[t]:=a[h];inc(h);

            end;

          b[a[h]]:=i;inc(h);

        end;

      for i:=1 to n do

        write(b[i]:5);

    end.

    分析:这是一个典型队列的例子,请大家仔细体会在队列操作过程中头指针和尾指针的变化。

     

    例2、集合的前N个元素:编一个程序,按递增次序生成集合M的最小的N个数,M的定义如下:

         (1)数1属于M;

         (2)如果X属于M,则Y=2*X+1和Z=3*1也属于M;

         (3)此外再没有别的数属于M。

    解:Pascal程序:

    Program lt7_2_1;

    uses crt;

    var a,b:array[1..1000] of integer;

        x,ha,hb,t,total,n:integer;

    begin

      clrscr;

      write('N=');readln(n);

      x:=1;a[1]:=1;

      ha:=1;hb:=1;t:=0;total:=1;

      while total<=n do

        begin

          write(x:5);

          inc(t);

          a[t]:=2*x+1;b[t]:=3*x+1;

          if a[ha]>b[hb] then begin

             x:=b[hb];inc(hb);

          end

          else begin

            x:=a[ha];

            if a[ha]=b[hb] then inc(hb);

            inc(ha);

          end;

          inc(total);

        end;

    end.

    分析:可以用两个队列来存放Y和Z中的数,分别用数组a和数组b存放。然后递推求出第N项,方法如下:

        (1)令ha和hb分别为队列a和队列b的头指针,它们的尾指针为t。初始时,X=1,ha=hb=t=1;

        (2)将2*x+1和3*x+1分别放入队列a和队列b的队尾,尾指针加1。即:

           a[t]←2*x+1,b[t]←3*x+1,t←t+1;

        (3)将队列a和队列b的头结点进行比较,可能有三种情况:

           (A)a[ha]>b[hb]

           (B)a[ha]=b[hb]

           (C)a[ha]<b[hb]

        将比较的小者取出送入X,取出数的队列的头指针相应加1。

       (4)重复(2),(3)直至取出第N项为止。

       注意:数组的上标定到1000,当N较大时会出现队满溢出的情况,可以将上标定大些,或改用循环链表进行改进。

     

    练习二

    1、高精度加法:

       设计一个程序实现两个正整数(长度不超过200位)的求和运算。

    解:Pascal程序:

    Program lx7_2_1;

    uses crt;

    var a,b:string;

        c,i,x,y,lm,ha,hb:integer;

        m:array[1..300] of integer;

    begin

      clrscr;

      write('A=');readln(a);

      write('B=');readln(b);

      ha:=length(a);hb:=length(b);

      c:=0;lm:=1;

      while (ha>0) or (hb>0) do

        begin

          if ha>0 then x:=ord(a[ha])-48

             else x:=0;

          if hb>0 then y:=ord(b[hb])-48

             else y:=0;

          m[lm]:=x+y+c;

          c:=m[lm] div 10;

          m[lm]:=m[lm] mod 10;

          inc(lm);dec(ha);dec(hb);

        end;

      if c>0 then  m[lm]:=c

         else dec(lm);

      write(a,'+',b,'=');

      for i:=lm downto 1 do

        write(m[i]);

    end.

    2、基数排序:

      将278,109,063,930,589,184,505,269,008,083利用基数排序法按从小到大的顺序排列。

    分析:基数排序法是一种基于对关键字进行分配和收集的排序法,常用最低位优先法(Least Significant Digit first),简称LSD法。它先从最低位的关键字开始进行排序,然后对次低位关键字进行排序,依此类推,直到对最高位进行排序为止。例如本题,关键字是各位上的数码,从0到9共10个。首先,将需要排序的数放在队列A中,如图:

    3、有1至2N的自然数,按从小到大的顺序排成一列,对这2N个数进行如下操作:

       (1)将这2N个数等分成A,B两组,即:

            A组:1,2,...,N; B组:N+1,N+2,...,2N

       (2)轮流从A,B两组中按顺序取数:

            1,N+1,2,N+1,...,N,2N

       (3)再将取过的数等分为两组,并重复上述操作,直到这2N个数又按从小到大的顺序排好为止。

      例如:当N=3时,操作如下:

           初始序列为:1,2,3,4,5,6

           (1) 1,4,2,5,3,6

           (2) 1,5,4,3,2,6

           (3) 1,3,5,2,4,6

           (4) 1,2,3,4,5,6

    分析:将1至2N的自然数分成两组,用两个队列来模拟上述过程即可。

    4、有一个数,它的末位数字是N,将N移到这个数的首位,得到的新数恰好是原数的N倍,现输入N的值,求满足条件的最小的数。

     

    第三节 

        “栈”是一种先进后出(First In Last Out)或后进先出(Last In First Out)的数据结构。日常生活中也常能见到它的实例,如压入弹夹的子弹,最先压进去的子弹最后射出,而最后压入的子弹则最先发射出来。

        “栈”是一种只能在一端进行插入和删除的特殊的线性表,进行插入和删除的一端称为“栈顶”,而不动的一端称为栈底(如下图)。插入的操作也称为进栈(PUSH),删除的操作也称为出栈(POP)。

                  出栈 ←─┐ ┌──进栈          

                         │ │ ↓│          

                         ├───┤          

                  栈顶→│  an   │          

                         ├───┤          

                         │ ...    │          

                         ├───┤          

                         │  a2   │          

                         ├───┤          

                  栈底→│  a1   │          

                         └───┘                  

    例1、算术表达式的处理:由键盘输入一个算术表达式(含有+,-,*,/,(,)运算),且运算结果为整数),编一程序求该算术表达式的值。

    分析:表达式的运算是程序设计中一个基本的问题,利用栈求解是一种简单易行的方法,下面介绍的是算符优先法。

       比如:4+2*3-10/5

       我们都知道,四则运算的法则是:(1)先乘除后加减,同级运算从左到右;(2)有括号先算括号。

       因此上例的结果是8。

       算符优先法需要两个栈:一个是操作数栈,用来存放操作数,记为SN;另一个是操作符栈用来存放运算符,记为SP。具体处理方法如下:

       (1)将SN,SP置为空栈;

       (2)从左开始扫描表达式,若是操作数压入SN中;若是操作符则与SP的栈顶操作符比较优先级有两种可能:

          (a)优先级小于栈顶算符,此时从SN中弹出两个操作数,从SP弹出一个操作符,实施运算,结果压入SN的栈顶。

          (b)优先级大于栈顶算符,此时将操作符压入SP中。

       (3)重复操作(2)直至表达式扫描完毕,这时SP应为空栈,而SN只有一个操作数,即为最后的结果。

       为了方便起见,可以将#作为表达式的结束标志,初始化时在SP的栈底压入#,并将其优先级规定为最低。

    下面给出的是计算4+2*3-10/5#的示意图

    步骤   SN      SP    读入字符     │    说明

    ─────────────────┼──────────

     1     空      #       4          │ 将4压入SN

     2     4       #       +          │ 将+压入SP

     3     4       # +     2          │ 将2压入SN

     4     4 2     # +     *           │ 将*压入SP

     5     4 2     # + *   3           │ 将3压入SN

     6     4 2 3   # + *   -           │ -的优先级小于*,因此将SN中的3,2弹出,

                                      │ 将SP中的*弹出,将2*3的结果压入SN中

     7     4 6     # +     -          │ -的优先级小于其左边的+,因此将SN中的

                                      │ 4,6弹出,将SP中的+弹出,将4+6的结果压

                                      │ 入SN中

     8     10      #       -          │ -压入SP中

     9     10      # -     10         │ 10压入SN中

    10     10 10   # -     /           │ 将/压入SP中

    11     10 10   # - /   5           │ 将5压入SN中

    12     10 10 5 # - /   #           │ #优先级小于/,故SN中的10,5弹出,SP中

                                      │ 的/弹出,将10/5的结果压入SN中

    13     10 2    # -     #          │ #优先级小于-,故SN中的10,2弹出,SP中

                                      │ 的-弹出,将10-2的结果压入SN中

    14     8       #       #         │ #与#相遇,运算结束,SN中的8是最后计算

                                      │ 的结果

    解:Pascal程序:

    Program lt7_3_1;

    uses crt;

    const number:set of char=['0’..'9'];

          op:set of char=['+','-','*','/','(',')'];

     

    var expr:string;

        sp:array[1..100] of char;

        sn:array[1..100] of integer;

        t,tp,n,tn:integer;

     

    function can_cal(ch:char):boolean;

      begin

        if (ch='#') or (ch=')') or ((sp[tp] in ['*','/']) and (ch in ['+','-']))

           then can_cal:=true else can_cal:=false;

      end;

     

    procedure cal;

      begin

        case sp[tp] of

          '+':sn[tn-1]:=sn[tn-1]+sn[tn];

          '-':sn[tn-1]:=sn[tn-1]-sn[tn];

          '*':sn[tn-1]:=sn[tn-1]*sn[tn];

          '/':sn[tn-1]:=sn[tn-1] div sn[tn];

        end;

        dec(tn);dec(tp);

      end;

     

    begin

      clrscr;

      write('Expression : ');readln(expr);

      write(expr+'=');

      expr:=expr+'#';

      tn:=0;tp:=1;sp[1]:='#';t:=1;

      repeat

        if expr[t] in number then

           begin

             n:=0;

             repeat

               n:=n*10+ord(expr[t])-48;

               inc(t);

             until not (expr[t] in number);

             inc(tn);sn[tn]:=n;

           end

        else begin

          if (expr[t]='(') or not can_cal(expr[t]) then

             begin

               inc(tp);sp[tp]:=expr[t];inc(t);

             end

          else if expr[t]=')' then

            begin

              while sp[tp]<>'(' do cal;

              dec(tp);inc(t);

            end

            else cal;

        end;

      until (expr[t]='#') and (sp[tp]='#');

      writeln(sn[1]);

    end.

     

    练习三

    1、假设一个算术表达式中可包含三种括号:圆括号“(”和“)”;方括号“[”和“]”以及花括号“{”和“}”,且这三种括号可按任意的次序嵌套使用,试利用栈的运算,判别给定的表达式中所含括号是否正确配对出现。

    分析:如果括号只有一种,比如说是“(”和“)”,则判断是否正确匹配可以这样进行:

    用一个计数器T来记录左括号与右括号比较的情况,初始时T=0,表达式从左向右扫描,如果遇到左括号则T←T+1,遇到右括号则T←T-1,中间如果出现T<0的情况,说明右括号的个数大于左括号的个数,匹配出错;扫描结束时,如果T<>0,说明左右括号的个数不相等,匹配出错。

        但本题的括号有三种,这里用栈可以较好地解决,方法如下:

        (1)将“(”,“[”,“{”定为左括号,“)”,“]”,“}”定为右括号,建一个栈来存放左括号,初始时,栈为空;

        (2)从左向右扫描表达式,如果是左括号,则将其压入栈中;如果是右括号则

           (a)栈顶是与该右括号匹配的左括号,则将它们消去,重复(2)

           (b)栈顶的左括号与该右括号不匹配,则匹配错误;

        (3)扫描结束时,若栈非空,则匹配错误。

    2、计算四个数:由键盘输入正整数A,B,C,D(1<=A,B,C,D<=10);然后按如下形式排列:

    A□B□C□D=24;在四个数字之间的□处填上加,减,乘,除四种运算之一(不含括号),输出所有满足条件的计算式子。若不能满足条件,则打印“NORESULT!“。

    分析:A,B,C,D四个数的位置已经固定,因此只需将+,-,*,/四种运算符依次放入三个□中,穷举所有可能的情况,再计算表达式的值即可。

     

    第四节 数组

       数组是大家非常熟悉的,我们可以把它看成是一个长度固定的线性表。本节在研究数组的一些处理方法的同时,还将介绍一些特殊的数组的存储和处理。

    例一、矩阵填数:给出N*N的矩阵,要求用程序填入下列形式的数:

        25 24 23 22 21                              

        20 19 18 17 16                              

        15 14 13 12 11                              

        10  9  8  7  6                              

         5  4  3  2  1                              

    解:Pascal程序:

    Program lt7_4_1;

    uses crt;

    const max=10;

    var a:array[1..max,1..max] of integer;

        n:integer;

     

    procedure work1;

      var i,j:integer;

      begin

        for i:=n downto 1 do

          for j:=n downto 1 do

            a[i,j]:=(n-i)*n+(n-j+1);

      end;

     

    procedure print;

      var i,j:integer;

      begin

        writeln;

        for i:=1 to n do

          begin

            for j:=1 to n do

              write(a[i,j]:5);

            writeln;

          end;

      end;

     

    begin

      clrscr;

      write('N=');readln(n);

      work;

      print;

    end.

     

    例二、在一个矩阵(N*N)中,若上三角中的元素全部为零,如下图所示:

    为了节省空间,可用一个一维数组来表示这个矩阵。如右图         1 0 0

    可表示成为:(1 2 3 3 0 4),在此种方法下,编程完成两个        2 3 0

    矩阵的加法。                                                 3 0 4

    Program lt7_4_2;

    uses crt;

    const max=1000;

    var a,b,c:array[1..max] of integer;

        n:integer;

     

    procedure read_data;

      var i:integer;

      begin

        write('N=');readln(n);

        write('A : ');

        for i:=1 to n*(n+1) div 2 do

          read(a[i]);

        write('B : ');

        for i:=1 to n*(n+1) div 2 do

          read(b[i]);

      end;

     

    procedure add;

      var i:integer;

      begin

        for i:=1 to n*(n+1) div 2 do

          c[i]:=a[i]+b[i];

      end;

     

    procedure print;

      var i,j,t:integer;

      begin

        t:=1;

        for i:=1 to n do

          begin

            for j:=1 to n do

              if j>i then write('0':5)

                 else begin write(c[t]:5);inc(t);end;

            writeln;

          end;

      end;

     

    begin

      clrscr;

      read_data;

      add;print;

      writeln('Press any key to exit...');

      repeat until keypressed;

    end.

    分析:本题应弄清楚下三角形矩阵中压缩存储及矩阵的加法运算法则。

     

    练习四

    1、数组的鞍点:己知数组X(M,N)中的所有元素为互不相等的整数,编一个程序,先从每一行元素中找出最小的数,然后再从这些最小的数中找出最大的数。打印出这最大的数和它在数组中的下标。

    2、稀疏矩阵:所谓的稀疏矩阵是指矩阵中的非0元素很少的一种矩阵,对于这种矩阵,可以用一个三元组表来存储非0元素,从而达到压缩存储,节省空间的目的。具体方法如下:假设v是稀疏矩阵A中第i行第j列的一个非0元素,那么该非0元素可以用一个三元组(i,j,v)表示;请编一个程序,从文件中读入一个用三元组表表示的稀疏矩阵,然后输出该稀疏矩阵的转置矩阵。(文件第一行为:M,N,K,分别表示稀疏矩阵的行和列以及非0元素的个数,以下K+1行是K个三元组)

    3、蛇形填数:给一个N*N矩阵按下列方式填数。

         1  3  4 10 11            

         2  5  9 12 19         

         6  8 13 18 20        

         7 14 17 21 24   

    15 16 22 23 25          

     


     第八章 搜索

        搜索是人工智能的基本问题。在程序设计中,许多问题的求解都需要利用到搜索技术,它是利用计算机解题的一个重要手段。

    问题的状态可以用图来表示,而问题的求解则往往是从状态图中寻找某个状态,或是寻找从一个状态到另一个状态的路径。这一求解的过程可能并不象解一个一元二次方程那样有现成的方法,它需要逐步探索与总是问题有关的各种状态,这即是搜索。

    本章将介绍广度优先搜索和深度优先搜索及其递归程序的实现。

     

    第一节 深度优先搜索

        所谓"深度"是对产生问题的状态结点而言的,"深度优先"是一种控制结点扩展的策略,这种策略是优先扩展深度大的结点,把状态向纵深发展。深度优先搜索也叫做DFS法(Depth First Search)。

    例一、设有一个4*4的棋盘,用四个棋子布到格子中,要求满足以下条件:

          (1)任意两个棋子不在同一行和同一列上;

          (2)任意两个棋子不在同一条对角线上。

          试问有多少种棋局,编程把它们全部打印出来。

    解:PASCAL程序:

    Program lt9_1_1;

    uses crt;

    const n=4;

    var a:array[1..n] of integer;

        total:integer;

     

    function pass(x,y:integer):boolean;

      var i,j:integer;

      begin

        pass:=true;

        for i:=1 to x-1 do

          if (a[i]=y) or (abs(i-x)=abs(a[i]-y)) then

             begin pass:=false;exit;end;

      end;

     

    procedure print;

      var i,j:integer;

      begin

        inc(total);

        writeln('[',total,']');

        for i:=1 to n do

          begin

            for j:=1 to n do

              if j=a[i] then write('O ')

                 else write('* ');

            writeln;

          end;

      end;

     

    procedure try(k:integer);

      var i:integer;

      begin

        for i:=1 to n do

          if pass(k,i) then

             begin

               a[k]:=i;

               if k=n then print

                  else try(k+1);

               a[k]:=0;

             end;

      end;

     

    begin

      clrscr;

      fillchar(a,sizeof(a),0);

      total:=0;

      try(1);

    end.

        分析:这里要求找出所有满足条件的棋局,因此需要穷举所有可能的布子方案,可以按如下方法递归产生:

        令D为深度,与棋盘的行相对应,初始时D=1;

        Procedure try(d:integer);

        begin

          for i:=1 to 4 do

            if 第i个格子满足条件 then

               begin

                 往第d行第i列的格子放入一枚棋子;

                 如果d=4则得一方案,打印

                 否则试探下一行,即try(d+1);

                 恢复第d行第i列的格子递归前的状态;

             end;

        end;

        这种方法是某一行放入棋子后,再试探下一行,将问题向纵深发展;若本行试探完毕则回到上一行换另一种方案。这样必定可穷举完所有可能的状态。从本题可以看出,前面所说的递归回溯法即体现了深度优先搜索的思想。上面对深度优先算法的描述就是回溯法常见的模式。

     

    例二、在6*6的方格中,放入24个相同的小球,每格放一个,要求每行每列都有4个小球(不考虑对角线),编程输出所有方案。

    解:Pascal程序:

    Program lx9_1_2;

    uses crt;

    const n=6;

    var map:array[1..n,1..n] of boolean;

        a:array[1..n] of integer;

        total:longint;

     

    procedure print;

      var i,j:integer;

      begin

        inc(total);gotoxy(1,3);

        writeln('[',total,']');

        for i:=1 to n do

          begin

            for j:=1 to n do

              if map[i,j] then write('* ')

                 else write('O ');

            writeln;

          end;

      end;

     

    procedure try(k:integer);

      var i,j:integer;

      begin

        for i:=1 to n-1 do

          if a[i]<2 then begin

             map[k,i]:=true;

             inc(a[i]);

             for j:=i+1 to n do

               if a[j]<2 then begin

                  map[k,j]:=true;

                  inc(a[j]);

                  if k=n then print

                     else try(k+1);

                  map[k,j]:=false;

                  dec(a[j]);

               end;

             map[k,i]:=false;

             dec(a[i]);

          end;

      end;

     

    begin

      clrscr;

      fillchar(map,sizeof(map),false);

      fillchar(a,sizeof(a),0);

      try(1);

    end.

    分析:本题实际上是例一的变形;

         (1)把枚举每行每列四个小球转化成为每行每列填入2个空格;

         (2)用两重循环实现往一行中放入两个空格;

         (3)用数组B记录搜索过程中每列上空格的个数;

         (4)本题利用深度搜索求解时要注意及时回溯,以提高效率,同时要注意退出递归时全局变量的正确恢复。

     

    例三、跳马问题:在半张中国象棋盘上,有一匹马自左下角往右上角跳,今规定只许往右跳,不许往左跳,图(A)给出的就是一种跳行路线。编程计算共有多少种不同的跳行路线,并将路线打印出来。

    解:PASCAL程序:

    Program lt9_1_2;

    uses crt;

    const d:array[1..4,1..2] of shortint=((2,1),(1,2),(-1,2),(-2,1));

    var a:array[1..10,1..2] of shortint;

        total:integer;

     

    function pass(x,y,i:integer):boolean;

      begin

        if (x+d[i,1]<0) or (x+d[i,1]>4) or (y+d[i,2]>8)

           then pass:=false else pass:=true;

      end;

     

    procedure print(k:integer);

      var i:integer;

      begin

        inc(total);

        write('[',total,'] : (0,0)');

        for i:=1 to k do

          write('->(',a[i,1],',',a[i,2],')');

        writeln;

      end;

     

    procedure try(x,y,k:integer);

      var i:integer;

      begin

        for i:=1 to 4 do

          if pass(x,y,i) then

             begin

               a[k,1]:=x+d[i,1];a[k,2]:=y+d[i,2];

               if (a[k,1]=4) and (a[k,2]=8) then print(k)

                  else try(a[k,1],a[k,2],k+1);

             end;

      end;

     

    begin

      clrscr;

      total:=0;

      try(0,0,1);

      writeln('Press any key to exit..。');

      repeat until keypressed;

    end.

    分析:(1)这里可以把深度d定为马跳行的步数,马的位置可以用它所在的行与列表示因此初始时马的位置是(0,0);

         (2)位置在(x,y)上的马可能四种跳行的方向,如图(B),这四种方向,可以按x,y的增量分别记为(2,1),(1,2),(-1,2),(-2,1)

         (3)一种可行的跳法是指落下的位置应在棋盘中。

     

    练习一:

    1、有一括号列S由N个左括号和N个右括号构成,现定义好括号列如下:

      (1) 若A是好括号列,则(A)也是;

      (2) 若A和B是好括号列,则AB也是好的。

     例如:(()(()))是好的,而(()))(()则不是,现由键盘输入N,求满足条件的所的好括号列,并打印出来。

    解:Pacal程序:

    Program lx9_1_1;

    uses crt;

    var n:integer;

        total:longint;

     

    procedure try(x,y:integer;s:string);

      var i:integer;

      begin

        if (x=n) and (y=n) then begin

           inc(total);writeln('[',total,'] ',s);

        end

        else begin

          if x<n then try(x+1,y,s+'(');

          if y<x then try(x,y+1,s+')');

        end;

      end;

     

    begin

      clrscr;

      write('N=');readln(n);

      total:=0;try(0,0,'');

    end.

    分析:从好括号列的定义可知,所谓的"好括号列"就是我们在表达式里所说的正确匹配的括号列,其特点是:从任意的一个位置之前的右括号的个数不能超过左括号的个数。由这个特点,可以构造一个产生好括号列的方法:用x,y记录某一状态中左右括号的个数;若左括号的个数小于N(即x<N),则可加入一个左括号;若右括号的个数小于左括号的个数,则可加入一个右括号,如此重复操作,直至产生一个好括号列。

    2、排列组合问题:从数码1-9中任选N个不同的数作不重复的排列(或组合),求出所有排列(或组合)的方案及总数。

    3、迷宫问题:在一个迷宫中寻找从入口(最左上角的格子)到出口(最右下角的格子)的路径。

    4、填数游戏一:以下列方式向5*5的矩阵中填入数字。设数字i(1<=i<=25)己被置于座标位置(x,y),则数字i+1的座标位置应为(z,w),(z,w)可根据下列关系由(x,y)算出。

      (1) (z,w)=(x±3,y)

      (2) (z,w)=(x,y±3)

      (3) (z,w)=(x±2,y±2)

      例如数字1的起始位置座标被定为(2,2),则数字2的可能位置座标是:(5,2),(2,5),或(4,4)。编写一个程序,当数字1被指定于某一起始位置时,列举其它24个数字应在的位置,列举出该条件下的所有可有的方案。

    解:同例二类似,只不过方向增量变为(3,0),(-3,0),(0,3),(0,-3),(2,2),(2,-2),(-2,2),(-2,-2)。

    Pascal程序:

    Program lx9_1_3;

    uses crt;

    const n=5;

          d:array[1..8,1..2] of shortint=((3,0),(-3,0),(0,3),(0,-3),

                                          (2,2),(2,-2),(-2,2),(-2,-2));

    var x0,y0:byte;

        a:array[1..n,1..n] of byte;

        total:longint;

     

    procedure print;

      var i,j:integer;

      begin

        inc(total);

        gotoxy(1,3);

        writeln('[',total,']');

        for i:=1 to n do

          begin

            for j:=1 to n do

              write(a[i,j]:3);

            writeln;

          end;

      end;

     

    procedure try(x,y,k:byte);

      var i,x1,y1:integer;

      begin

        for i:=1 to 8 do

          begin

            x1:=x+d[i,1];y1:=y+d[i,2];

            if (x1>0) and (y1>0) and (x1<=n)

               and (y1<=n) and (a[x1,y1]=0) then

                 begin

                   a[x1,y1]:=k;

                   if k=n*n then print

                      else try(x1,y1,k+1);

                   a[x1,y1]:=0;

                 end;

          end;

      end;

     

    begin

      clrscr;

      write('x0,y0=');readln(x0,y0);

      fillchar(a,sizeof(a),0);

      total:=0;a[x0,y0]:=1;

      try(x0,y0,2);

      writeln('Total=',total);

      writeln('Press any key to exit..。');

      repeat until keypressed;

    end.

    5、填数游戏二。有一个M*N的矩阵,要求将1至M*N的自然数填入矩阵中,满足下列条件:

      (1)同一行中,右边的数字比左边的数字大;

      (2)同一列中,下面的数字比上面的数字大。

      打印所有的填法,并统计总数。

    解:Pascal程序:

    $Q-,R-,S-

    Program lx9_1_4;

    uses crt;

    const m=3;n=6;

    var a:array[0..m,0..n] of integer;

        used:array[1..m*n] of boolean;

        total:longint;

     

    procedure print;

      var i,j:integer;

      begin

        inc(total);gotoxy(1,3);

        writeln('[',total,']');

        for i:=1 to m do

          begin

            for j:=1 to n do

              write(a[i,j]:3);

            writeln;

          end;

      end;

     

    procedure try(x,y:integer);

      var i:integer;

      begin

        for i:=x*y to m*n-(m-x+1)*(n-y+1)+1 do

          if not used[i] and (i>a[x-1,y]) and (i>a[x,y-1]) then

             begin

               a[x,y]:=i;used[i]:=true;

               if i=m*n-1 then print

                  else begin

                    if y=n then try(x+1,1)

                       else try(x,y+1);

                  end;

               used[i]:=false;

             end;

      end;

     

    begin

      clrscr;

      fillchar(used,sizeof(used),false);

      fillchar(a,sizeof(a),0);

      a[1,1]:=1;a[m,n]:=m*n;

      used[1]:=true;used[m*n]:=true;

      try(1,2);

      writeln('Total=',total);

    end.

    分析:本题可以将放入格子中的数字的个数作为深度,先往格子(1,1)放第一个数,然后依次往格子(1,2),(1,3),...,(m,n-1),(m,n)填数字,每填一个数时应如何判断该数是否满足条件,做到及时回溯,以提高搜索的效率是非常关键的。为此需要认真研究题目的特点。根据题意可以知道:在任何一个K*L的格子里,最左上角的数字必定是最小的,而最右下角的数字必定是最大的,故有:

        (1)格子(1,1)必定是填数1。格子(m,n)必定填数m*n;

        (2)若A是格子(x,y)所要填入数,则有:x*y<=A<=m*n-(m-x+1)*(n-y+1)+1;

    6、反幻方:在3*3的方格中填入1至9,使得横,竖,对角上的数字之和都不相等。下图给出的是一例。请编程找出所有可能的方案。

        ┌─┬─┬─┐

        │1 │2 │3 │

        ├─┼─┼─┤

        │4 │5 │8 │

        ├─┼─┼─┤

        │6 │9 │7 │

        └─┴─┴─┘

             图 一

    分析:

    (1)深度优先搜索。用一个二维数组A来存储这个3*3的矩阵。

    (2)用x表示行,y表示列,搜索时应注意判断放入格子(x,y)的数码是否符合要求;

       (a)如果y=3,就计算这一行的数码和,其值存放在A[x,4]中,如果该和己出现过,则回溯;

       (b)如果x=3,则计算这一列的数码和,其值存放在A[4,y]中,并进行判断是否需要回溯;

       (c)如果x=3,y=1还应计算从左下至右上的对角线的数码和;

       (d)如果x=3,y=3还应计算从左上至右下的对角线的数码和。

    为了提高搜索速度,可以求出本质不同的解,其余的解可以由这些本质不同的解通过旋转和翻转得到。为了产生非本质解,搜索时做如下规定:

      (a)要求a[1,1]<a[3,1]<a[1,3]<a[3,3];

      (b)要求a[2,1]>a[1,2].

    解:略

    7、将M*N个0和1填入一个M*N的矩阵中,形成一个数表A,

           │a11  a12 ...  a1n │                  

        A=│a21  a22 ...  a2n │                  

           │....…             │                  

           │am1 am2 ... amn │                  

      数表A中第i行和数的和记为ri(i=1,2,...,m),它们叫做A的行和向量,数表A第j列的数的和记为qj(j=1,2,...,m),它们叫做A的列和向量。现由文件读入数表A的行和列,以及行和向量与列和向量,编程求出满足条件的所的数表A。

    分析:本题是将例题一一般化,将若干个1放入一个M*N的方阵中,使得每行和每列上的1的个数满足所给出的要求。

    思路:(1)应该容易判断,若r1+r2+...+rn<>q1+q2+...+qm,则问题无解;

         (2)将放入1的个数看做是深度,1的位置记为(x,y),其中x代表行,y代表列,第一个1应从(1,1)开始试探;

         (3)往k行放入一个1时,若前一个1的位置是(k,y),则它的位置应在第k行的y+1列至(m-本行还应放入1的个数+1)这个范围内进行试探;若这一列上己放入1的个数小于qy,则该格子内放入一个1,并记录下来;否则换一个位置试探。

    Program lx9_1_6;

    uses crt;

    const max=20;

    var m,n,s1,s2:integer;

        map:array[1..max,1..max] of 0..1;

        a,b,c,d:array[1..max] of integer;

        total:longint;

     

    procedure error;

      begin

        writeln('NO ANSWER!');

        writeln('Press any key to exit..。');

        repeat until keypressed;

        halt;

      end;

     

    procedure init;

      var f:text;

          fn:string;

          i,j:integer;

      begin

        write('Filename:');readln(fn);

        assign(f,fn);reset(f);

        readln(f,m,n);s1:=0;s2:=0;

        for i:=1 to m do

          begin read(f,a[i]);s1:=s1+a[i];end;

        for i:=1 to n do

          begin read(f,b[i]);s2:=s2+b[i];end;

        close(f);

        if s1<>s2 then error;

        fillchar(map,sizeof(map),0);

        fillchar(c,sizeof(c),0);

        fillchar(d,sizeof(d),0);

      end;

     

    procedure print;

      var i,j:integer;

      begin

        inc(total);gotoxy(1,3);

        writeln('[',total,']');

        for i:=1 to m do

          begin

            for j:=1 to n do

              write(map[i,j]:3);

            writeln;

          end;

      end;

     

    procedure try(x,y,t:integer);

      var i,j:integer;

      begin

        for i:=y+1 to n-(a[x]-c[x])+1 do

          if (map[x,i]=0) and (d[i]<b[i]) then

             begin

               map[x,i]:=1;inc(c[x]);inc(d[i]);

               if t=s1 then print

                  else if (x<=m) then begin

                    if c[x]=a[x] then try(x+1,0,t+1)

                       else try(x,i,t+1);

                  end;

               map[x,i]:=0;dec(c[x]);dec(d[i]);

             end;

      end;

     

    begin

      clrscr;

      init;

      try(1,0,1);

      if total=0 then writeln('NO ANSWER!');

    end.

     

    第二节 广度优先搜索

        广度优先是另一种控制结点扩展的策略,这种策略优先扩展深度小的结点,把问题的状态向横向发展。广度优先搜索法也叫BFS法(Breadth First Search),进行广度优先搜索时需要利用到队列这一数据结构。

    例一、分油问题:假设有3个油瓶,容量分别为10,7,3(斤)。开始时10斤油瓶是满的,另外两个是空的,请用这三个油瓶将10斤油平分成相等的两部分。

    解:PASCAL程序:

    Program lt9_2_2;

    uses crt;

    const max=3000;

          v:array[1..3] of byte=(10,7,3);

     

    type node=record

           a:array[1..3] of byte;

           ft:integer;

         end;

    var o:array[1..max] of node;

        h,t,i,j,k:integer;

     

    function is_ans(h:integer):boolean;

      begin

        with o[h] do

          if ord(a[1]=5)+ord(a[2]=5)+ord(a[3]=5)=2

             then is_ans:=true else is_ans:=false;

      end;

     

    function new_node(t:integer):boolean;

      var r:integer;

      begin

        r:=t;

        repeat

          dec(r);

          with o[t] do

            if (a[1]=o[r].a[1]) and (a[2]=o[r].a[2]) and (a[3]=o[r].a[3])

               then begin new_node:=false;exit;end;

        until (r=1);

        new_node:=true;

      end;

     

    procedure print(r:integer);

      var b:array[1..30] of integer;

          t,l,p:integer;

      begin

        t:=0;

        while r>0 do

          begin

            inc(t);b[t]:=r;

            r:=o[r].ft;

          end;

        gotoxy(1,1);

        writeln('':5,v[1]:5,v[2]:5,v[3]:5);

        writeln('--------------------');

        for l:=t downto 1 do

          begin

            write('[',t-l:2,'] ');

            for p:=1 to 3 do

              write(o[b[l]].a[p]:5);

            writeln;

          end;

        halt;

      end;

     

    begin

      clrscr;

      with o[1] do

        begin

          a[1]:=10;a[2]:=0;a[3]:=0;

        end;

      h:=1;t:=2;

      repeat

        if is_ans(h) then print(h);

        for i:=1 to 3 do

          if o[h].a[i]>0 then

            for j:=1 to 3 do

              if (i<>j) and (o[h].a[j]<v[j]) then

                 begin

                   for k:=1 to 3 do

                     o[t].a[k]:=o[h].a[k];

                   with o[t] do

                     begin

                       ft:=h;

                       if o[h].a[i]>(v[j]-a[j]) then

                         begin

                           a[i]:=o[h].a[i]+a[j]-v[j];a[j]:=v[j];

                         end

                       else begin

                         a[j]:=a[j]+o[h].a[i];a[i]:=0;

                       end;

                     end;

                   if new_node(t) then inc(t);

                 end;

        inc(h);

      until (h>t);

      writeln('NO ANSWER!');

    end.

     

    例二、中国盒子问题:给定2*N个盒子排成一行,其中有N-1个棋子A和N-1个棋子B,余下是两个连续的空格,

    如下图,是N=5的一种布局。                                                                

         ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐                                        

         │A │B │B │A │  │  │A │B │A │B │                                        

         └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘                                        

     移子规则为:任意两个相邻的棋子可移到空格中,且这两个棋子的次序保持不变。                

     目标:全部棋子A移到棋子B的左边。                                                       

     

    练习二:

    1、跳棋:跳棋的原始状态如下图(A),目标状态如下图(B),其中0代表空格,W代表白子,B代表黑子,跳棋的规则是:(1)任一个棋子可以移到空格中去;(2)任一个棋子可以跳过1个或两个棋子移到空格中去。试编一个程序,用最少的步数将原始状态移成目标状态。

      ┌─┬─┬─┬─┬─┬─┬─┐        ┌─┬─┬─┬─┬─┬─┬─┐

      │  │B │B │B │W │W │W │        │  │W │W │W │B │B │B │

      └─┴─┴─┴─┴─┴─┴─┘        └─┴─┴─┴─┴─┴─┴─┘

                图 A                                   图 B

    2、七数码问题:

       在3*3的棋盘上放有7个棋子,编号分别为1到7,余下两个是空格。与空格相邻的一个棋子可以移到空格中,每移动一次算一步,现任给一个初始状态,要求用最少的步数移成下图所示的目标状态。

         ┌─┬─┬─┐

         │1 │2 │3 │

         ├─┼─┼─┤

         │4 │5 │6 │

         ├─┼─┼─┤

         │7 │  │  │

         └─┴─┴─┘

    3、N个钱币摆放一排,有的钱币正面朝上(记为1),有的钱币正面朝下(记为0),每次可以任意改变K个钱币的状态,即正反面互换。编一个程序判定能否在有限的步数内使得所有钱币正面朝上,若能,请给出步数最少的方案。

    4、下图A所示的是一个棋盘,放有编号为1到5的5个棋子,如果两个格子中没有线分隔,就表示这两个格子是相通的,编一个程序,用最少的步数将图A的状态移成图B所示的状态(一次移动一子,无论多远算一步)。

         ┌─┬─┬─┐                       ┌─┬─┬─┐    

         │           │                                   │    

    ┌─┼─┼  ┼─┼─┐               ┌─┼─┼  ┼─┼─┐

    │5    4   3   2   1 │               │1    2    3   4   5 │

    └─┴─┴─┴─┴─┘               └─┴─┴─┴─┴─┘

          图   A                                  图  B

    5、迷宫问题:在一个迷宫中找出从入口到出口的一条步数最少的通路。

     

     


    第九章 其他常用知识和算法

       本章将抛砖引玉地探讨一些程序设计中常用的知识及算法,包括图论及其基本应用,动态规划等。要想对这些问题进行深一步地研究,可以系统地学习图论、组合数学、运筹学等书籍。

     

    第一节 图论及其基本算法

        图是另一种有层次关系的非线性的数据结构。在日常生活中有图的许多实例,如铁路交通网,客运航空线示意图,化学结构式,比赛安排表等。下面 的如几个例子都可称为图。在实际运用中,我们可以用图来表示事物间相互的关系,从而根据需要灵活地构建数学模型。例如:图(A),可以用点来表示人,如果两个人相互认识,则在表示这两个人的点之间连一条线。这样从图中我们就可以清楚地看到,这些人之间相互认识的关系。图(B):可以用点表示城市,若两城市间有连线则表示可在这两城市架设通信线路,线旁的数字表示架设这条线路的费用。图(C):4个点表示4支足球队,它们进行循环比赛,若甲队胜乙队,则连一条由甲队指向乙队的有向线段。在上面三个例子中,(A),(B)又可称为无向图,(C)称为有向图,其中(B)是一个有权图。

       图常用的存储方式有两种,一种是邻接表法,另一种是邻接矩阵法。

       邻接表表示图有两个部分:一部分表示某点的信息,另一部分表示与该点相连的点。例如:图A的邻接表如下所示:

        ┌─┬─┐  ┌─┬─┐  ┌─┬─┐  ┌─┬─┐ 

        │v1│─┼→│v2│─┼→│v3│─┼→│v4│^ │ 

        └─┴─┘  └─┴─┘  └─┴─┘  └─┴─┘ 

        ┌─┬─┐  ┌─┬─┐  ┌─┬─┐             

        │v2│─┼→│v1│─┼→│v3│^ │             

        └─┴─┘  └─┴─┘  └─┴─┘             

        ┌─┬─┐  ┌─┬─┐  ┌─┬─┐             

        │v3│─┼→│v1│─┼→│v2│^ │             

        └─┴─┘  └─┴─┘  └─┴─┘             

     

        ┌─┬─┐  ┌─┬─┐                         

        │v4│─┼→│v1│^ │                         

        └─┴─┘  └─┴─┘                         

      邻接矩阵是用一个二维数组表示图的有关信息,比如图A的邻接矩阵为:

            ┌─┬─┬─┬─┬─┐              

            │点│v1│v2│v3│v4│              

            ├─┼─┼─┼─┼─┤              

            │v1│0 │1 │1 │1 │              

            ├─┼─┼─┼─┼─┤              

            │v2│1 │0 │1 │0 │              

            ├─┼─┼─┼─┼─┤              

            │v3│1 │1 │0 │0 │              

            ├─┼─┼─┼─┼─┤              

            │v4│1 │0 │0 │0 │              

            └─┴─┴─┴─┴─┘              

       在邻接矩阵中,第i行第j表示图中点i和点j之间是否有连线,若有则值为1,否则为0。比如说:点v1和v2之间有连线,则矩阵的第一行第二列的值为1。而矩阵的第四行行三列的值为0说明图中点v4和v3之间没有连线。图A是一个无向图,它的邻接矩阵是一个关于主对角线对称的矩阵。而有向图的邻接矩阵则不一定是对称的。比如图C的邻接矩阵为:

             ┌─┬─┬─┬─┬─┐              

             │点│v1│v2│v3│v4│              

             ├─┼─┼─┼─┼─┤              

             │v1│0 │1 │1 │1 │              

             ├─┼─┼─┼─┼─┤              

             │v2│0 │0 │1 │1 │              

             ├─┼─┼─┼─┼─┤              

             │v3│0 │0 │0 │0 │              

             ├─┼─┼─┼─┼─┤              

             │v4│0 │0 │1 │0 │              

             └─┴─┴─┴─┴─┘              

    例一、图的遍历:请编一个程序对下图进行遍历。

    分析:"遍历"是指从图的某个点出发,沿着与之相连的边访问图中的每个一次且仅一次。基本方法有两种:深度优先遍历和广度优先遍历。

        深度优先和广度优先遍历,与前面所说的树的深度与广度优先遍历是类似的:比下图中,如果从点V1出发,那么:

        深度优先遍历各点的顺序为:v1,v2,v4,v7,v5,v3,v6,v8。

        广度优先遍历各点的顺序为:v1,v2,v3,v4,v5,v6,v7,v8。

    下面是两种方法的Pascal程序:

    解:程序一:深度优先遍历

       可以仿照前面树的深度优先遍的得出图的深度优先遍历的递归算法,这里是用栈来实现的非递归算法,算法如下:

        (1)利用一个栈S来记录访问的路径,由于从点1出发,因此初始时S[1]:=1;

        (2)找一个与栈顶的点相连而又未被访问过的点,如找到,则输出并压入栈顶;如果未找到,则栈顶的点弹出。

        (3)重复(2)直到所有的点均己输出。如果栈指针为0,而尚的点未输出,说明该图不是一个连通图。

    Program lt8_1_a;

    uses crt;

    const n=8;

          v:array[1..8,1..8] of 0..1=((0,1,1,0,0,0,0,0),

                                        (1,0,0,1,1,0,0,0),

                                        (1,0,0,0,0,1,1,0),

                                        (0,1,0,0,0,0,0,1),

                                        (0,1,0,0,0,0,0,1),

                                        (0,0,1,0,0,0,1,0),

                                        (0,0,1,0,0,1,0,0),

                                        (0,0,0,1,1,0,0,0));

    var visited:array[1..n] of boolean;

        s:array[1..n] of byte;

        top,p,i,total:integer;

        find:boolean;

    begin

      clrscr;

      fillchar(visited,sizeof(visited),false);

      write('V1 ');s[1]:=1;visited[1]:=true;

      total:=1;top:=1;p:=1;

      repeat

        find:=false;

        for i:=1 to n do

          if not visited[i] and (v[p,i]=1) then

             begin

               inc(top);s[top]:=i;p:=i;

               visited[p]:=true;find:=true;

               inc(total);write('V',p,' ');break;

             end;

        if not find then

           begin p:=s[top];dec(top);end;

      until total=n;

      writeln;

    end.

     程序二:进行广度优先遍历(程序略)

     

    例二、最短路径。

    下图是一个铁路交通图的例子。图中的顶点表示车站,分别用1,2,...,6编号,两个点之间的连线则表示铁路线路,而线旁的数字则表示该条线路的长度。要求编一个程序,求出从车站1至车站6的最短路径。

    解:本题是在一个有权图中求给定两点之间的最短路径,也许大家会考虑用搜索的方法试探所有可能经过的路线,再从中找出最小者,这样在理论上是成立的,但可能效率不高,这里介绍一种有效的算法,就是Dijkstra算法。

    PASCAL程序:

    Program lt8_2;

    uses crt;

    const max=100;

    var map:array[1..max,1..max] of integer;

        d:array[1..max,1..2] of integer;

        p:array[1..max] of boolean;

        n:integer;

     

    procedure init;

      var i,j:integer;

          f:text;

          fn:string;

      begin

        write('Filename:');readln(fn);

        assign(f,fn);reset(f);

        readln(f,n);

        for i:=1 to n do

          for j:=1 to n do

            read(f,map[i,j]);

        close(f);

        fillchar(p,sizeof(p),false);

        fillchar(d,sizeof(d),0);

      end;

     

    function find_min:integer;

      var i,min,t:integer;

      begin

        min:=maxint;

        for i:=1 to n do

          if not p[i] and (d[i,1]>0) and (d[i,1]<min) then

             begin

               min:=d[i,1];t:=i;

             end;

        find_min:=t;

      end;

     

    procedure change(t:integer);

      var i:integer;

      begin

        for i:=1 to n do

          if not p[i] and (map[t,i]>0)

             and (d[i,1]=0) or (d[i,1]>d[t,1]+map[t,i]) then

               begin d[i,1]:=d[t,1]+map[t,i];d[i,2]:=t;end;

      end;

     

    procedure dijkstra;

      var i,j,t:integer;

      begin

        p[1]:=true;t:=1;

        repeat

          change(t);

          t:=find_min;

          p[t]:=true;

        until p[n];

      end;

     

    procedure print;

      var i,t:integer;

          r:array[1..max] of integer;

      begin

        t:=1;r[1]:=6;

        while d[r[t],2]>0 do

          begin

            r[t+1]:=d[r[t],2];inc(t);

          end;

        writeln('From V1 to V6');

        for i:=t downto 2 do

          write('V',r[i],'->');

        writeln('V',r[1]);

        writeln('Min=',d[6,1]);

      end;

     

    begin

      clrscr;

      init;

      dijkstra;

      print;

    end.

    练习一、

    1、拓扑排序。

      有N个士兵(1<=N<=26),依次编号为A,B,...,Z。队列训练时,指挥官要把士兵从高到矮依次排成一行,但现在指挥官不能直接获得每个人的身高信息,只能获得"P1比P2高"这样的比较结果,记为"P1>P2",如"A>B"表示A比B高。现从文本文件读入比较结果的信息,每个比较结果在文本文件中占一行。编程输出一种符合条件的排队方案。若输入的数据无解,则打印"NO ANSWER!"。

    例如:若输入为:

    A>B

    B>D

    F>D

    则一种正确的输出为:

    ABFD

    分析:

    (1)由局部可比信息求得全局可比信息就是拓扑排序。

    (2) 局部信息可以用一个有向图表示。如A>B,则在加一条由点A指向点B的有向线。那么上例可以用右图表示出来。

    (3)拓扑排序的方法:

       (a)从图中选一个无前驱的点输出,同时删去由该点出发的所有的有向线;

       (b)重复a,直至不能进行为止。此时若图中还有点未输出,则问题无解;否则,输出的序列即为一可行方案。

    注意:在步骤(3)的b中,如果图中还有点未输出,说明图中有一个回路(即环)。这在拓扑排序中是矛盾的。比方说有如右图所示的一个环,其表示出来关于身高的信息是:A>B,B>C,C>A,这显然是矛盾的,因此无解。

    解:略

    2、 地图四色:用不超过四种的颜色给一个地图染色,使得相邻的两个地区所着的颜色各不相同。

    3、最小部分树。

      有一个城市分为N个区,现要在各区之间铺设有线电视线路。任意两个区之间铺设线路的费用如下图所示,其中图的顶点表示各个区,线旁的数字表示铺设该条线路的费用。要求设计一个费用最省的铺线方案使得每一个区均能收看到有线电视

    分析:(1)这个问题实际上是求图的最小部分树。因为每一个区均能收看到有线电视,所以该图是连通的;又因为要求费用最省,所以该图无圈。

        (2)求图的最小部分树常用Prim算法(也称加边法)和Kurskal算法(也称破圈法)

    这里介绍的是Prim算法。具体操作如下:

         (a)去掉图中所有的线,只留下顶点;

         (b)从图中任意选出一个点加入集合S1,余下的点划入集合S2;

         (c)从所有连接集合S1与S2的点的线中选一条最短的加入图中,并将该线所连的集合S2中的点从集合S2中删去,加入集合S1中;

         (d)重复步骤c,直至往图中加入N-1条边(N为顶点的个数)。

    4、一笔画:编一个程序对给定的图进行一笔画。

    分析:图的一笔画是指从图的某一个顶点出发,经过图中所有的边一次且仅一次。现由文本文件读入一个图的邻接矩阵,判断该图能否一笔画,若能,则给出一笔画的方法。

    分析:一笔画是图论中研究得比较早的一个问题,一个图能够一笔画的条件是:

       (1)该图是一个连通图;

       (2)该图至多有2个度为奇数的点。

    这里所说的"度"是指与一个点相连的边的数目,如果边的条数是奇数,则该点的度是奇数,否则是偶数。

       在一笔画问题中,如果连通图所有的点的度均为偶数,则一笔画时可以从任意一点出发,最后又能回到起点。如果有两个点的度为奇数,则一笔画时,一定是从一个奇数度的点出发,最后到达另一个奇数度的点。此外,在连通图中,奇数度的点总是成对出现的。一笔画所经过的路线又叫欧拉路(如果是回路的话,也叫欧拉回路)。

        找欧拉路(或回路)可以用Fleury算法,如下:

        (1)找一个出发点P(如果图中有两个奇数度的点话,任取其一)。

        (2)找一条与P相连的边,伸展欧拉路(选边的时候应注意,最后再选取断边;断边是:当去掉该边后使得图不连通的边)。

        (3)将选出的边所连的另一个点取代P,去掉该边,重复(2),直至经过所有的边。

    5、哈密尔顿问题。

      哈密尔顿问题是指在一个图中找出这样的一条路:从一个图的顶点出发,经过图中所有顶点一次且仅一次。象这样的路称为哈密尔顿路。现由文本文件读入一个图的邻接表,判断该图是否有哈密尔顿路,若有则输出。

    6、图的关节点。

       如果从一个连通图中删去某点V,使得该图不连通,那么V点就称为该图的一个关节点。现从文本文件中读入一个图的邻接矩阵,试编程找出该图所有的关节点。

    7、 有一集团公司下有N个子公司,各子公司由公路连接,现要在N个子公司中选一个出来成立总装车间,装配各子公司送来的部件,且使得各子公司到总装车间的路程总和最小。

     


    第二节 动态规划

        动态规划是近来发展较快的一种组合算法,是运筹学的一个分支,是解决多阶段决策过程最优化的一种数学方法。我们可以用它来解决最优路径问题,资源分配问题,生产调度问题,库存问题,装载问题,排序问题,设备更新问题,生产过程最优控制问题等等。

       在生产和科学实验当中,有一类活动的过程,可将它分成若干个阶段,在它的每个阶段要作出决策,从而使全局达到最优。当各个阶段决策确定后,就组成一个决策序列,因而也就决定了整个过程的一条活动路线。这种把一个过程看作一个前后相关具有链状结构的多阶段过程就称为多阶段决策过程。

       所谓动态是指在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前的状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生,故有"动态"的含义。

       下面,我们结合最短路径问题来介绍动态规划的基本思想。

       求下图中点O到点U的最短距离(假设只许往上和往右走)。

     

     

     

     

     

     

     

     

     

     

       从点O到点U,可以按经过的路径,分成七个阶段,分别为:O->AB->CDE->FGHJ->

    KLMN->PQR->ST->U。

       最短路径有一个重要特性:如果点O经过点H到达点U是一条最短路径,则在这条最优路径上由点H出发到达点U的子路径,是由点H出发到达点U所有可能选择的不同路径的最短路径(证明略)。根据这一特点,寻找最短路径的时候,可以从最后一段开始,用由后向前逐段递推的方法,求出个点到点U的最短路径。

       如若考虑到从点O到点U的最短路径,也是该路径上个点到点的最短路径,令O点到U点的最短距离为dO,A点到U点的最短距离为dA,...,故有:

               dO=min{2+dA,1+dB},

               dA=min{3+dC,2+dD},

               dB=min{2+dD,3+dE},

               ................

               dQ=min{5+dT,2+dS}.

       下面按照动态规划的方法,将上例从最后一段开始计算,由后向前逐步递推移至O点。计算步骤如下:

       阶段7:从S点或T点到达U点,这时各自只有一种选择,故:

               dS=2;dT=3;

       阶段6:出发点有P,Q,R三个,其中Q点到达U点有两种选择,或是经过S点,或是经过T点,故:

               dQ=min{2+dS,5+dT}=min{4,8}=4;

               dP=min{1+dS}=min{3}=3;

               dR=min{3+dT}=min{6}=6;

       阶段5:出发点有K,L,M,N四个,同理有:

               dK=min{3+dP}=min{6}=6;

               dL=min{2+dP,4+dq}=min{5,8}=5;

               dM=min{2+dQ,4+dR}=min{6,10}=6;

               dN=min{4+dR}=min{10}=10;

       阶段4:出发点有F,G,H,J四个,同理有:

               dF=min{2+dK}=min{8}=8;

               dG=min{1+dK,3+dL}=min{7,8}=7;

               dH=min{1+dL,1+d}=min{6,7}=6;

               dJ=min{3+dM,3+dN}=min{9,13}=9;

       阶段3:出发点有C,D,E三个,同理有:

               dC=min{2+dF,2+dG}=min{10,9}=9;

               dD=min{4+dG,2+dH}=min{11,8}=8;

               dE=min{1+dH,2+dJ}=min{7,11}=7;

       阶段2:出发点有A,B两个,同理有:

               dA=min{3+dC,2+dD}=min{12,10}=10;

               dB=min{2+dD,3+dE}=min{10,10}=10;

       阶段1:出发点是O,同理有:

               dO=min{2+dA,1+dB}=min{12,11}=11.

       由此得到全过程的最短路径是11,并且可以由以上推导过程反推得最短的路线是:O->B->D->H->L->P->S->U;或O->B->E->H->L->P->S->U。

       从上可以知道,能应用动态规划解决的问题,必须满足最优性原则,动态规划的关键在于正确的写出基本的递推关系式和恰当的边界条件。它需要把问题的过程化成几个互相联系的阶段,恰当的选取状态变量和决策变量及定义最优值函数,从而把一个大问题化成一族同类型的子问题,然后逐个求解。即从边界条件开始,逐段递推寻优,在每一个子问题的求解中,都利用到前面的子问题的最优化结果,依次进行,最后一个子问题的最优解,就是整个问题的最优解。

    例一、数字三角形:下图给出了一个数字三角形.请编一个程序计算从顶至底的某处的一条路径,使得该路径所经过的数字的总和最大。对于路径规定如下:

        (1)每一步可沿左斜线向下走或右斜线向下走;                  ⑦        

        (2)1<三角形的行数<=100;                                 ③   8      

        (3)三角形中的数字为整数0,1,...,99;                  ⑧   1   0    

        输入数据:由文件INPUT.TXT中首先读出的            2   ⑦   4   4  

    是三角形的行数。在例子中INPUT.TXT表示如下:        4   ⑤   2   6   5

         5        

         7        

         3 8      

         8 1 0    

         2 7 4 4     

         4 5 2 6 5    

        输出最大的总和。如上例为:30(其路径如图圈的数字)。

    分析:(1)假设最优路径(即总和最大)为⑦→③→⑧→⑦→⑤,则子路径⑦→③→⑧→⑦必定是从初始点⑦到中间点⑦的所有路径中最优的,否则,如果从初始点⑦到中间点⑦还有另一条的路径更优,假设为⑦→a1→a2→⑦,则新路径⑦→a1→a2→⑦→⑤则优于原来假设的最优路径⑦→③→⑧→⑦→⑤,与假设矛盾。从以上反证法可以清楚地知道:数字三角形问题满足动态规划的最优性原则,可以利用动态规划求解。

        (2)采用顺推法:记第i行第j列的数字为a(i,j),从初始点到a(i,j)的最大总和记为f(i,j);则应有第一层上的数字的a(1,1)的最大总和为它本身,即:

        f(1,1)=a(1,1);

    以下各层按如下方法递推:

        f(i,j)=a(i,j)+max{f(i-1,j-1),f(i-1,j)}

       (3)用以上方法递推计算完最后一层,最后一中寻最大值即为本题的解.

    解:Pascal程序:

    Program lt10_2_1;

    uses crt;

    const max=100;

    var n:integer;

        a:array[0..max,0..max] of longint;

     

    procedure work;

      var f:text;

          i,j:integer;

      begin

        assign(f,'input.txt');

        reset(f);

        readln(f,n);

        fillchar(a,sizeof(a),0);

        for i:=1 to n do

          for j:=1 to i do

            begin

              read(f,a[i,j]);

              if a[i-1,j-1]>a[i-1,j] then

                 a[i,j]:=a[i,j]+a[i-1,j-1]

                 else a[i,j]:=a[i,j]+a[i-1,j];

            end;

        close(f);

      end;

     

    procedure print;

      var max:longint;

          i:integer;

      begin

        max:=0;

        for i:=1 to n do

          if a[n,i]>max then max:=a[n,i];

        writeln('Max=',max);

      end;

     

    begin

      clrscr;

      work;print;

    end.

    习题二:

    1、 某工业生产部门根据国家计划的安排,拟将某种高效率的五台机器,分配给所属的A,B,C三个工厂,各工厂若获得这种机器后,可以为国家盈利如下表,问:这五台机器如何分配给各工厂,才能使国家盈利最大?

         单位:万元

        P     

    S

    A

    B

    C

    0

    0

    0

    0

    1

    3

    5

    4

    2

    7

    10

    6

    3

    9

    11

    11

    4

    12

    11

    12

    5

    13

    11

    12

       其中:p为盈利,s为机器台数。

    2、己知:f(x)=x1^2+2x2^2+x3^2-2x1-4x2-2x3

           x1+x2+x3=3 且 x1,x2,x3均为非负整数。

      求f(x)的最小值。

    3、N块银币中有一块不合格,不合格的银币较正常为重,现用一天平找出不合格的来,要求最坏情况不用天平的次数最少。

    4、某一印刷厂有6项加工任务,对印刷车间和装订车间所需时间见下表:

    任  务

    J1

    J2

    J3

    J4

    J5

    J6

    印刷车间

    3

    12

    5

    2

    9

    11

    装订车间

    8

    10

    9

    6

    3

    1

         时间单位:天

         完成每项任务都要先去印刷车间印刷,再到装订车间装订。问怎样安排这6项加工任务的加工工序,使得加工总工时最少。

    5、有一个由数字1,2,...,9组成的数字串(长度不超过200),问如何M(1<=M<=20)个加号插入这个数字串中,使得所形成的算术表达式的值最小。

    注意:(1)加号不能加在数字串的最前面或最末尾,也不应有两个或两个以上的加号相邻;

         (2)M保证小于数字串的长度。

      例如:数字串79846,若需加入两个加号,则最佳方案是79+8+46,算术表达式的值是133。

      输入格式:从键盘读入输入文件名。数字串在输入文件的第一行行首(数字串中间无空格且不换行),M的值在输入文件的第二行行首。

      输出格式:在屏幕上输出最小的和。

     

     


    展开全文
  • Pascal 程序入门教材

    2020-07-30 23:33:04
    第一节 Pascal 程序结构和基本语句 2 第二节 顺序结构程序与基本数据类型 6 第二章 分支程序 10 第一节 条件语句与复合语句 10 第二节 情况语句与算术标准函数 12 第三章 循环程序 16 第一节 for 循环 16 第二节...
  • 十个pascal程序

    2020-07-30 23:32:08
    pascal程序
  • pascal

    2010-12-24 10:42:00
    Pascal简介  Pascal是一种计算机通用的高级程序设计语言。Pascal的取名是为了纪念十七世纪法国著名哲学家和数学家Blaise Pascal。它由瑞士Niklaus Wirth教授于六十年代末设计并创立。1971年,瑞士联邦技术学院...

    Pascal简介

      Pascal是一种计算机通用的高级程序设计语言。Pascal的取名是为了纪念十七世纪法国著名哲学家和数学家Blaise Pascal。它由瑞士Niklaus Wirth教授于六十年代末设计并创立。1971年,瑞士联邦技术学院尼克劳斯·沃尔斯(N.Wirth)教授发明了另一种简单明晰的电脑语言,这就是以电脑先驱帕斯卡的名字命名的Pascal语言。Pascal语言语法严谨,层次分明,程序易写,具有很强的可读性,是第一个结构化的编程语言。它一出世就受到广泛欢迎,迅速地从欧洲传到美国。沃尔斯一生还写作了大量有关程序设计、算法和数据结构的著作,因此,他获得了1984年度“图灵奖”。

    特点

      以法国数学家命名的Pascal语言现已成为使用最广泛的语言之一,其主要特点有:严格的结构化形式;丰富完备的数据类型;运行效率高;查错能力

      正因为上述特点,Pascal语言可以被方便地用于描述各种算法与数据结构。尤其是对于程序设计的初学者,Pascal语言有益于培养良好的程序设计风格和习惯。IOI(国际奥林匹克信息学竞赛)把Pascal语言作为三种程序设计语言之一,NOI(全国奥林匹克信息学竞赛)把Pascal语言定为唯一提倡的程序设计语言,在大学中Pascal语言也常常被用作学习数据结构与算法的教学语言。

    发展历程

      ps:高级语言发展过程中,Pascal是一个重要的里程碑。Pascal语言是第一个系统地体现了E.W.Dijkstra和C.A.R.Hoare定义的结构化程序设计概念的语言。

      在Pascal问世以来的三十余年间,先后产生了适合于不同机型的各种各样版本。其中影响最大的莫过于Turbo Pascal系列软件。它是由美国Borland公司设计、研制的一种适用于微机的Pascal编译系统。该编译系统由1983年推出1.0版本发展到1992年推出的7.0版本,其版本不断更新,而功能更趋完善。

    版本

      Pascal有5个主要的版本,分别是Unextended Pascal、Extended Pascal、Object-Oriented Extensions to Pascal、Object Pascal 和 Delphi。其中,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的基础上增加了一些用以支持面向对象程序设计的特性,但它属于非正式的标准。Delphi 是由Borland公司专门为其开发的编译工具(也叫Delphi)设计的Pascal语言, Delphi 不是正式的Pascal标准,具有专利性。但由于Turbo Pascal系列和Delphi功能强大并且广为流行,Delphi 已自成为一种标准,为许多人所熟悉。

    基本符号

      Pascal语言只能使用以下几类基本符号:

    (1)大小写英文字母

      A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

      a b c d e f g h i j k l m n o p q r s t u v w x y z

    (2)数字

      1 2 3 4 5 6 7 8 9 0

    (3)其他符号

      + - * / = <> <= >= > < ( ) [ ] { } := , . ; : .. div mod

      注意,Pascal语言除了可以使用以上规定的字符外,不得使用其他任何符号。

    保留字

      作为一种高级语言,Pascal给一些英文单词赋予了特定的含义,这些特定符号叫做保留字或关键字。标准Pascal中的保留字有42个。下面是Pascal的保留字:

      and array begin case const div do downto to else end file for function goto if in label mod nil not of or packed procedure program record repeat then to type until var while with rewrite reset put write writeln read readln

    编程工具

    Turbo Pascal

      Turbo Pascal 它提供了一个集成环境的工作系统,集编辑、编译、运行、调试等多功能于一体。

      编年史

      

    出版年代 版本名称 主要特色
    1983 Turbo Pascal 1.0

    Turbo Pascal 2.0

    Turbo-87 Pascal 提高实数运算速度并扩大值域
    1985 Turbo Pascal 3.0 增加图形功能

    Turbo BCD Pascal 特别适合应用于商业
    1987 Turbo Pascal 4.0 提供集成开发环境(IDE),引入单元概念
    1988 Turbo Pascal 5.0 增加调试功能
    1989 Turbo Pascal 5.5 支持面向对象的程序设计(OPP)
    1990 Turbo Pascal 6.0 提供面向对象的应用框架和库(Turbo Vision)
    1992 Turbo Pascal 7.0 面向对象的应用系统、更完善的IDE

    Turbo Vision 2.0
    1993 Borland Pascal 7.0 开发 Object Windows库(For Windows) 提供对OLE多媒体应用开发的支持
    1995 Delphi (Object Pascal)

    Visual Pascal

    Free Pascal

     Pascal是一门编程语言,而Turbo Pascal/Free Pascal是Pascal程序的编译系统.。

      Pascal只是一门语言,而Turbo Pascal/Free Pascal是编译器,这两个是不能比较的 。

      你可以把两个语言放在一起比较,或者两个编译器放在一起比较 。

      

      

      

    在中国的信息学奥林匹克竞赛中,过去比较常用的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下的最后版本。

    Free Pascal

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

      Free Pascal是一个在多种版本Pascal和Delphi下的产物,目前比较成熟的版本是由FreePascal. org发布的2.2.4版本(最新版本为2.4.0,但在使用的时候,2.4.0经常会莫名其妙的中止编译),由于是Pascal上的改版,在FP里加入了很多以前没有的东西,例如:FillChar系列内存块赋值语句,用Power代替了**(乘方),但是**还是可以使用。

      另外FP加强了与内存的互容性,增大对内存的支持,FP里的内存限制是TP和BP里的将近上万倍。

      FP还进一步加强了单元支持、面向对象程序设计的支持、显卡(声卡)的支持、图形高级覆盖的支持、Windows/Linux/OS/2/..等众多系统的支持。在FP的较稳定版本中,可以方便的利用Win32编译模式,编译出Windows应用程序,与Delphi的功能相当。同时对动态连接库、控件、数据库、文件、网络、OpenGL的深入支持,使得 FP 在各种 Pascal 编译系统中脱颖而出。

      更值得提出的是,FP支持Delphi及C++的部分语言,例如:A+=2这样的C Style语言,在FP里完美支持。

      FP中支持单目、双目操作符,即所有版本的Pascal的符号和“@”等特殊符号。

      FP 支持运算符重载。

      FP现为竞赛推荐工具。

      最新发布版本为 2010 年 11 月 12 日发布的 2.4.2。

      最新 bugfix 版本为 svn repo 里的 2.4.3。

    部分教程

    一、Pascal语言的特点

      信息学奥林匹克竞赛是一项益智性的竞赛活动,核心是考查参赛选手的智力和使用计算机编程解题的能力。信息学奥林匹克竞赛要求参赛选手有如下能力:针对竞赛题目中的要求构建数学模型,构造出有效的算法和选用相应的数据结构,写出高级语言程序,上机调试通过。程序设计是信息学奥林匹克竞赛的基本功,因此,青少年参与竞赛活动的第一步是必须掌握一门高级语言及其程序设计方法。

      以纪念法国数学家而命名的Pascal语言是使用最广泛的计算机高级语言之一,被国际上公认为程序设计教学语言的典范。其主要特点有:严格的结构化形式;丰富完备的数据类型;运行效率高;查错能力强。正因为这些特点,Pascal语言可以被方便地用于描述各种数据结构和算法,编写出高质量的程序。尤其是对于青少年程序设计初学者,Pascal 语言有利于顺利入门,有益于从一开始培养良好的程序设计风格和习惯,越来越多的各类学校都把Pascal语言作为程序设计教学的第一语言。IOI(国际奥林匹克信息学竞赛)把Pascal语言规定为二种程序设计语言之一,?NOI(全国信息学奥林匹克竞赛)把Pascal语言定为唯一提倡的程序设计语言,NOIp(全国信息学奥林匹克联赛)把Pascal定为最主要的程序设计语言。

      第一课 Pascal语言知识

      一、Pascal 语言概述

      Pascal语言是一种算法语言,它是瑞士苏黎世联邦工业大学的沃思教授于1968年设计完成的,1971年正式发表。Pascal语言是在ALGOL60的基础上发展而成的。它是一种结构化的程序设计语言。它的功能强、编译程序简单,是70年代影响最大一种算法语言。

      从使用者的角度来看,Pascal语言有以下几个主要的特点:

      ⒈ 结构化

      Pascal可以方便地书写出结构化程序。这就保证程序的正确性和易读性。在结构化这一点上,比其它算法语言更好一些。

      ⒉ 数据类型丰富

      Pascal提供了整数型、实数型、字符型、布尔型、枚举型、子界型以及由以上类型构成的数组类型、集合类型、记录类型和文件类型。此外,还提供了其它许多语言中所没有的指针类型。丰富的数据结构和上述的结构化性质,使得Pascal可以被方便地用来描述复杂的算法。

      ⒊ 适用性好

      既适用于数值运算,也适用于非数值运算领域。有些语言只适用于数值计算,有些语言则适用于商业数据处理和管理领域。Pascal的功能较强,能广泛应用于各种领域。

      ⒋ 书写较自由

      不象有些算法语言那样对程序的书写格式有严格的规定。Pascal允许一行写多个语句,一个语句可以分写在多行上,这样就可以使Pascal程序写得象诗歌格式一样优美,便于阅读。

      由于以上特点,许多学校选Pascal作为程序设计课程中的一种主要的语言。它能给学生严格而良好的程序设计的基本训练。培养学生结构化程序设计的风格。

    二、Pascal语言程序的基本结构

      任何程序设计语言都有一定的规则。使用Pascal语言必须遵循其本身所规定的规则来编写程序。尽管不同版本的Pascal语言所采用的符号的数量、形式不尽相同,但其基本成分一般都符合标准Pascal的规定。下面我们首先来了解Pascal语言的程序基本结构。

      为了明显起见,先举一个最简单的Pascal程序例子:

      【例1】

      program li1(input,output); {程序首部}

      const

      pi=3.1415926;

      var

      r,l,s:real;

      begin

      write(‘input r:’);

      readln(r);

      s:=pi*r*r;

      l:=2*pi*r;

      writeln(‘s=’,s);

      writeln(‘l=’,l);

      readln;

      end.

      从这个简单的程序可以看到:

      ⒈ 一个Pascal程序分为两个部分:程序首部和程序体(或称分程序)。

      ⒉ 程序首部是程序的开头部分,它包括:

      ⑴程序标志。用"program"来标明这是一个Pascal 程序。Pascal规定任何一个Pascal程序的首部都必须以此字开头。在Free Pascal中,首部也可省略。

      ⑵程序名称。由程序设计者自己定义,如例中的li1。在写完程序首部之后,应有一个分号。

      ⒊ 程序体是程序的主体,在有的书本里也称"分程序"。程序体包括说明部分(也可省略)和执行部分两个部分。

      ⑴说明部分用来描述程序中用到的变量、常量、类型、过程与函数等。本程序中第二行是"变量说明",用来定义变量的名称、类型。

      Pascal规定,凡程序中用到的所有变量、符号常量、数组、过程与函数、记录、文件等数据都必须"先说明,再使用"。

      ⑵执行部分的作用是给出需要计算机执行的操作。

      执行部分以"begin"开始,以"end"结束,其间有若干个语句,语句之间以分号隔开。执行部分之后有一个句点,表示整个程序结束。

      ⒋ Pascal程序的书写方法比较灵活。书写程序应结构清晰、容易阅读理解。在编写程序时希望读者尽量模仿本书中例题程序格式。

      ⒌ 在程序中,一对大括号间的文字称为注释。注释的内容由人们根据需要书写,可以用英语或汉语表示。注释可以放在任何空格可以出现的位置。执行程序时计算机对注释不予理睬。

    三、Free Pascal语言系统的使用

      目前,信息学竞赛使用的Pascal语言系统是Free Pascal,下面我们就来学习Free Pascal的使用。

      1.系统的启动

      

    free pascal

    在运行系统目录下的启动程序fp.EXE,即可启动系统。屏幕上出现如图1所示的集成环境。

      2.Free Pascal系统集成环境简介

      最顶上一行为主菜单。中间蓝色框内为编辑窗口,在它个编辑窗口内可以进行程序的编辑。最底下一行为提示行,显示出系统中常用命令的快捷键,如打开一个文件的快捷键为F3,将当前编辑窗口中文件存盘的快捷键为F2,获得系统帮助的快捷键为F1,等等。

      3.新建程序窗口

      按F10进行主菜单,选择FILE菜单,执行其中New命令。就可建立一个新的程序窗口(默认文件名为Noname00.pas或Noname01.pas等)。

      4.程序的输入、编辑与运行

      在当前程序窗口中,一行一行的输入程序。程序窗口是一个编辑器。所以对程序的编辑与其它编辑器相似,类似记事本程序。

      当程序输入完毕之后,一般要先按Alt+F9(或执行compile菜单中compile命令)对程序进行编译。如果程序有语法错误,则会在程序窗口的下方显示错误信息。若无语法错误,则窗口正中央会出现一个对话框,提示编译成功。接下来,我们可以运行程序了。

      程序的运行可以通过按ALT+R打开RUN菜单中的RUN命令,或直接按快捷键CTRL+F9。则可以在用户窗口中输出运行结果。通常在程序运行结束后系统回到Pascal系统的集成环境,因此要查看运行结果,要按ALT+F5将屏幕切换到用户屏幕。

      5.程序的保存与打开

      当我们想把程序窗口中的程序存入磁盘时,可以通过按F2键(或执行File菜单中的save命令)来保存程序。第一次保存文件时屏幕上会出现一个对话框要求输入文件名(默认扩展名为.pas)。

      当我们要将磁盘上的程序文件中的Pascal程序装入窗口时,可按F3(或执行File菜单中的Open命令)来装入程序,此时系统也会弹出一个对话框要求输入要打开的文件名,或直接在文件对话框列表中选择所要的文件,然后回到打开文件。(.pas文件,可以由记事本打开)

    主要使用标识符

      real 实数

      integer 整数

      longint 长整型

      shortint 短整型

      string 字符型

      ansistring 内存字符型

      int64 -2^63-2^63-1的整数类型

      begin 开始

      end 结束

      if 如果

      for ... to .... do 循环

      readln read 输入

      write writeln 输出

      then 那么(常与if连用)

      boolean 布尔型

      true 真实

      false 假的

    其它参见

    Pascal的其他释义

      布莱士·帕斯卡

      压强

      第二:

    命名规范

      我们在表命名,字段命名,或者程序命名方面要按照一定的规则,

    Pascal 大小写

      将标识符的首字母和后面连接的每个单词的首字母都大写。可以对三字符或更多字符的标识符使用Pascal 大小写。例如:

      BackColor

     

    展开全文
  • Pascal Voc数据集详细分析

    万次阅读 多人点赞 2020-09-14 16:19:13
    所以这里就来详细的记录一下PASCAL VOC的格式,包括目录构成以及各个文件夹的内容格式,方便以后自己按照VOC的标准格式制作自己的数据集。 正文 相关网址 Pascal VOC网址:...
  • pascal语言入门

    万次阅读 多人点赞 2017-08-17 05:21:59
    Pascal语言初印象 一、认识程序 Pascal语言,语法清晰,语句直接。 最简单的程序不过两行。 begin end.   下面我们借用A+B问题来认识一下pascal的基本框架。   var   a,b:real;  begin   read...
  • pascal语法基础

    千次阅读 2019-04-10 20:52:54
    循环语句 for循环 for i:=0 to n do begin 循环语句; end;
  • pascal语法介绍

    千次阅读 2018-07-03 12:03:54
    数据挖掘之数据初步探索 1. 汇总统计 众数:具有最高频率的值,针对离散型数据 百分位数:计算方法(3,4.3,6.2,6.5,7.6,7.8,8.1,9.6,10,11,12.3,15.9) 求75%中位数: 均值和中位数 ...
  • Pascal voc 数据集下载网址

    千次阅读 多人点赞 2020-05-08 14:26:54
    一直在找Pascal voc数据集下载地址,但官网好像上不去,记录一下镜像网址 Pascal Voc 2007 和Voc 2012下载地址: https://pjreddie.com/projects/pascal-voc-dataset-mirror/ 官网: ...
  • Camel和Pascal命名规范

    千次阅读 2016-04-24 10:03:38
    Camel:多用于给普通变量(局部变量)和...Pascal:多用于给类、方法(函数)和属性命名的规范,每个单词的首字母大写。如HighSchoolStudent。 方法命名一般用动词,变量命名一般按功能或方法的返回值命名。如 int ma
  • 广东中山初赛分数线66.5分~
  • Free Pascal IDE安装

    千次阅读 2017-07-26 09:17:33
    Free Pascal IDE安装
  • Pascal VOC 数据集国内下载

    万次阅读 多人点赞 2020-02-15 19:52:40
    自己家的服务器,下载速度约为7M/S 从2007-2012的文件 http://p2341j3301.51mypc.cn:666/share/Pascal VOC/ 文件在对应目录
  • 首先Free Pascal IDE必须下载32位的,64位的没有fp.exe不能打开IDE界面。 下载地址:http://www.freepascal.org/download.var 安装过程这里就免了。直接进入正题。 1.解决欢迎界面乱码 双击桌面Free Pascal IDE图标...
  • Pascal's Triangle II -- LeetCode

    万次阅读 多人点赞 2014-04-10 00:57:47
    这道题跟Pascal's Triangle很类似,只是这里只需要求出某一行的结果。Pascal's Triangle中因为是求出全部结果,所以我们需要上一行的数据就很自然的可以去取。而这里我们只需要一行数据,就得考虑一下是不是能只用...
  • Pascal's Triangle -- LeetCode

    万次阅读 多人点赞 2014-04-10 00:56:29
    这道题比较简单,属于基础的数组操作。基本思路是每层保存前一行的指针,然后当前航数据根据上一行来得到,每个元素就是上一行两个相邻元素相加(第一个和最后一个元素是1)。算法时间复杂度应该是O(1+2+3+...+n)=O...
  • 这一次比赛的成绩出来啦,考的还是不错的,至少近复赛了。加油! 2016年中山市信息学竞赛暨全国信息学联赛成绩表(普及组)   序号 姓名 性别 ...pascal 熊超 97
  • Dataset之Pascal VOC:Pascal VOC(VOC 2012、VOC 2007) 数据集的简介、下载、使用方法详细攻略 目录 Pascal 竞赛 1、PASCAL VOC竞赛任务 2、Pascal 竞赛的历史 3、Pascal VOC等类似大型官方数据集的...
1 2 3 4 5 ... 20
收藏数 99,059
精华内容 39,623
关键字:

pascal