内容:枚举、子界、集合、记录类型、指针、函数过程、文件操作
一、枚举类型
枚举类型是一种很有实用价值的数据类型,它是pascal一项重要创新。 可以用自然语言相应的单词来描述某种状态:
枚举类型定义:Type 枚举类型名=(标识符1,标识符2,…,标识符n) ;
枚举变量定义:Var 变量1,变量2,…,变量n: 枚举类型名;
例如,下列类型定义是合法的:
type days=(sun,mon,tue,wed,thu,fri,sat);
var holiday,workday:day;
注意:◆括号中的每一个标识符都称为枚举元素或枚举常量,且只能是单词,不能是数值常量和字符
常量,例如下面的定义是错误的:
type colortype=('red','yellow','blue','white');
numbers=(1,3,5,7,9);
ty=(for,do,while);
◆枚举类型的性质:枚举类型属于顺序类型,可以运用函数ord()、pred()和succ()来分别求序号、前驱和后继。例如:
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 子界类型名=<常量1>..<常量2>;
子界变量定义:Var 变量1,变量2,…,变量n:子界类型名;
注意:◆其中 常量1 称为子界的下界, 常量2 称为子界的上界。下界和上界必须是同一顺序类型(该类型称为子界类型的基类型),且上界的序号必须大于下界的序号。例如下列说明:
type age=0.5..150; {不为同一类型}
letter=0..'z'; {不为同一类型}
let1='z'..'a'; {上界小于下界}
都是错误的。
◆子界类型数据的运算规则:凡可使用基类型的运算规则同样适用该类型的子界类型。
三、集合类型
集合是由具有某些共同特征的元素构成的一个整体。在pascal中,一个集合是由具有同一有序类型的一组数据元素所组成,这一有序类型称为该集合的基类型。
集合类型的定义:集合类型名=set of <基类型>;
集合变量的定义:Var 变量1,变量2,…,变量n:集合类型名;
注意:◆基类型可以是任意顺序类型,而不能是实型或其它构造类型;同时,基类型的数据的序号不得超过255。例如下列说明是合法的
type letters=set of 'A'..'Z';
numbers=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,1+2,4]、[ch]、[succ(ch)]。
◆集合的运算
赋值运算:只能通过赋值语句给集合变量赋值,不能通过读语句赋值,也不能通过write(或writeln)语句直接输出集合变量的值
集合的并、交、差运算:可以对集合进行并、交、差三种运算,每种运算都只能有一个运算符、两个运算对象,所得结果仍为集合。三种运算符分别用“+”、“*”、“-”表示 ;注意它们与算术运算的区别
集合的关系运算:集合可以进行相等或不相等、包含或被包含的关系运算,还能测试一个元素是否在集合中。所用的运算符分别是:=、<>、>=、<=、in。
例:设有如下说明:
type subnum=set of 1..50; {集合类型}
Var s:subnum;
写出下列表达式的值:
⑴[sun,sat]+[sun,tue,fri]= ; ⑵[sun,fri]*[mon,tue]= ;
⑶[sun,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]= 。
⒀ s:=[];
For I:=1 to 50 do
If I mod 3=0 then s:=s+[I];
For I:=1 to 50 do If I in s then write(I,’’);
程序段执行后 输出为:
四、记录类型
在程序中对于组织和处理大批量的数据来说,数组是一种十分方便而又灵活的工具,但是数组在使用中有一个基本限制,这就是:一个数组中的所有元素都必须具有相同的类型。但在实际问题中可能会遇到由性质各不相同的成份组成的数据。例如,有关一个学生的数据包含下列项目:
学号 字符串类型 姓名 字符串类型 年龄 整型
性别 字符型 成绩 实型数组
Pascal给我们提供了一种叫做记录的结构类型;在一个记录中,可以包含不同类型的并且互相相关的一些数据。
记录类型的定义:
类型定义格式 |
实际例子,学生记录的定义 |
Type 记录类型名=record <域名1>:<类型1>; <域名2>:<类型2>; …… <域名n>:<类型n>; end; Var 记录变量名:记录类型 |
Type studata=Record Code:string[11]; Name:string[10]; Age:0..20; Sex:(female,male); Score:real; End; Var student:studata; |
注意:◆域变量的表示方法:记录变量名.域名 例如
student.code student.name 等
◆域变量的使用和一般的变量一样, 即域变量是属于什么数据类型,便可以进行那种数据类型所允许的操作。
◆记录的嵌套 :当一个记录类型的某一个域类型也是记录类型的时候,我们说发生了记录的嵌套,看下面的例子:某人事登记表可用一个记录表示,其中各项数据具有不同的类型,分别命名一个标识符。而其中的“出生年月日”又包括三项数据,还可以用一个嵌套在内层的记录表示。
五、动态数据(指针变量)
在Pacal的变量有两类:静态变量和动态变量。
程序中定义变量的实质:程序中的一个变量对应内存中一个地址,变量名与内存地址的关系相当于人的“姓名”与他的“身份证号码”。
静态变量:在程序编译编译运行时计算机系统为静态变量分配固定的存储单元。
例如:
Var s,r:Integer;
假设程序编译时为变量s,r分配的内存地址是3和6,如图,则定义静态变量的实质就相当于将地址为3、4的两个字节命名为s,将地址为6和7两个字节命名为r。
而赋值语句的S:=12;的执行过程是:Cpu先找到s对应的存储器地址(3),然后将数12放入对应的存储空间(相当于向某指定箱子中放入物体)。
动态变量(指针):静态变量直接对应内存中某存储单元的地址,而动态变量则是存储的某内存单元的地址,例如:
Var p,j:^integer;
任何一个指针变量占用4个字节的空间,存储的是内存的地址,如图:指针p中存储的地址为17,指向首地址为17的两个存储单元;指针j中存储的地址为14,指向首地址地址为14的两个存储单元。
给指针变量赋值的过程是:cpu先提取指针变量中存储的地址,然后再根据这个地址将变量值放入对应存储单元中。(相当于先在某箱子中取出物体要放入的箱子的编号,然后再将物体放入对应编号的箱子中)。
指针的定义
定义格式 |
实际例子 |
Type 指针类型名:^基类型; Var 变量1,变量2,…,变量n:指针类型名; |
Type pointer=^integer; Var p,j:pointer; 或 Var p,j:^integer; |
注意:◆、一个指针只能指示某一种类型数据的存储单元,这种数据类型就是指针的基类型。基类型可以是除指针、文件外的所有类型。
◆、指针在定义时,系统不会分配指向某存储单元的地址的,因此在使用它的时候,必须用New命令给他开辟存储空间,在变量使用完之后再用dispose命令释放它指向的存储单元,以便以后继续使用这个存储单元,因此我们称它为动态变量。而静态变量一旦定义,那么在程序运行完之后才能释放空间。
一个指针变量只能存放一个地址。如再一次执行New(p)语句,将在内存中开辟另外一个新的整型变量存储单元,并把此新单元的地址放在P中,从而丢失了原存储单元的地址。
◆、两个指针变量在相互赋值的时候,有两种方式:p^:=j^ 与 p:=j;前者是将j指向的存储单元中的值赋给p指向的存储单元,后者是将j的地址赋给p;他们有本质的不同。下面例子足以说明:
程序1 |
程序2 |
程序3 |
Var p,j:^integer; Begin New(p); New(j); J^:=5; P^:=j^; Writeln(p^,’’,j^); P^:=10; Writeln(p^,’’,j^); End; 程序输出:
|
Var p,j:^integer; Begin New(p); New(j); J^:=5; P:=j; Writeln(p^,’’,j^); P^:=10; Writeln(p^,’’,j^); End; 程序输出:
|
Var p,j,t:^integer; Begin New(p); New(j); new(t); p^:=11; j^:=22; t^:=p^; p^:=j^; j^:=t^; Writeln(p^,’’,j^); T:=p; p:=j; j:=t; Writeln(p^,’’,j^); End; 程序输出:
|
可以给指针变量赋nil值 :nil是PASCAL的关键字,它表示指针的值为“空”。例如,执行:p1:=ni1后,p1的值是有定义的,但p1不指向任何存储单元
◆用指针实现链表结构
设有一批整数(12,56,45,86,77,……,),如何存放呢? 当然我们可以选择以前学过的数组类型。但是,在使用数组前必须确定数组元素的个数。如果把数组定义得大了,就会有大量空闲存储单元,定义得小了,又会在运行中发生下标越界的错误,这是静态存储分配的局限性。
利用指针类型可以构造一个简单而实用的动态存储分配结构――链表结构,下图是一个简单链表结构示意图:
其中:①每个框表示链表的一个元素,称为结点,每个节点有两个域:“数据域”和“指针域”
所以定义为:
type pointer=^ node;
node=record
data:integer; (数据域)
next:pointer; (指针)
end;
var head:pointer;
②指向表头的指针head称为头指针(当head为nil时,称为空链表),在这个指针变量中 存放了表头的地址。
③在表尾结点中,由指针域不指向任何结点,一般放入nil。
④单向链表的基本操作:建立链表(creat)、查找(search)、插入一个结点(insert)、删除一个结点(delete)。我们用下面的例子进行说明:
【例6】编写一个过程,将读入的一串整数存入链表,并统计整数的个数,输入结束标记为999。
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;
查找x是否在链表中存在: |
在q结点后插入一个结点的插入代码: |
删除p结点的代码:(初始为q^.next:=p;) |
Search:=True; P:=head; While p<>nil do If p^data=x then break; If p=nil search:=False; |
New(q); q^.data:=x; q^.next:=q; p^.next:=q; |
q^.next:=p^.next; dispose(p); |
指针非常重要,右它派生出来的链表的概是描述程序中数据逻辑关系的一种重要的数据结构,后面我们要学的数据结构:线性表、数、图,他们最基本的存储方式有:以树组为主的顺序存储和以链表为代表的链式存储结构。
六、子程序——过程与函数
函数定义 |
过程定义: |
Function <函数名> (<形式参数表>):<类型>; Var 局部变量1,局部变量2 Begin …… End; |
Procedure <过程名> (<形式参数表>); Var 局部变量1,局部变量2 Begin …… End; |
注意:◆函数与过程的区别在于:函数具有返回值,而过程没有;
◆在子程序的参数表中有“形参”和“变参”两种,形参相当于局部变量,它们在子程序中改变并不影响实际参数的值,而变参则相反,这一点可以用指针的思想来理解。
程序1 |
程序2 |
Var x,y:Integer; Procedure swap(a,b:Integer); Var t:Integer; Begin T:=a; a:=b; b:=t; End; Begin X:=3; y:=5; Swap(a,b); Writeln(a,’’,b); End. 程序输出: |
Var x,y:Integer; Procedure swap(Var a,b:Integer); Var t:Integer; Begin T:=a; a:=b; b:=t; End; Begin X:=3; y:=5; Swap(a,b); Writeln(a,’’,b); End. 程序输出: |
◆函数调用的形式:变量:=函数名(实际参数表);
过程调用的形式:过程名(实际参数表);
特别讲解:递归技术:
所谓递归,就是函数或过程自己调用自己。
关于对递归的理解(3种理解方式):
1、递推+回归:这种思想可以简单描述为:要求什么,必须先求出什么,这样一步一步到底(递归边界)后,在回推,依次求出逐层的值。
例如:求n!的值。
设F(n)=n!,则递推关系式为:F(n)=n*F(n-1),其中(F(0)=1);
如果用递归来描述的话,就是要求F(n)的值,必须先求出F(n-1)的值,要求F(n-1)的值,必须先求出F(n-2)的值,直到n=0时,F(n)=1;然后逐层回归求值:下图是求F(5)(即5!)的过程:
|
用上面的理解方式用递归求1+2+3+4+…+n的值。
2、分治思想:这种思想可以简单描述为:将问题分解成几个小问题,然后对小问题求解后整个问题就解决了。这种理解的前提是子问题与母问题都做同样的操作。
例如二分查找算法、快速排序算法等都是这样的思想:
二分查找算法 |
快速排序算法 |
Function binsearch(low,high,x:integer):integer; Begin mid:=(low+hig) div 2; {分解成两个子问题} If a[mid]=x then binsearch:=mid; |
Procedure qksort(s,t:Longint); Var p:Longint; Begin If s Begin p:=qkpass(s,t); 分成左右两部分 qksort(s,p-1); 快排左边部分 qksort(p+1,t); 快排右边部分 End; End; |
3、回溯思想:这种思想可以简单地描述为:沿树的某分支深度搜索下去,直到不能在搜索然后回退到上层,继续沿另一个分支进行下去,直到整个数的结点搜索完毕。
典型例子:如求全排列问题。
例2、编程列举出1、2、…、n的全排列。
编程列举出1、2、…、n的全排列。
分析:深度优先搜索算发的本质是:按照深度优先的策略搜索问题的解答树。因此要使用它解决问题,应该先画出问题小范围的解答树。
具体到本题,我们假设n=3时,如下图:位置1可以放置数字1、2、3;位置2可以放置数字1、2、3;位置3可以放置数字1、2、3,但是当位置1放了数字1后位置2和位置3都不能在放1,因此画树的约束条件是:各位置的数字不能相同。
我们画“解答树”时,根结点一般是一个空结点,根结点下面的第1、2、3三层分别对应位置1、位置2、位置3,用“╳”标示的分支表示该结点不满足约束条件,不能被扩展出来:
我们用递归过程来描述 “解答树”的深度优先搜索
Pascal过程 |
汉语解释 |
procedure DFS(i:integer); Var c:integer; Begin If i>n then Begin 输出一种排列; exit End; For c:=1 to n do If used(c)=false Then Begin used[c]:=true; a[i]:=c; DFS(i+1); used[c]:=false; End; End; |
搜索第i层结点(向第i个位置放数)
如果搜索到一条路径,则输出一种解; 每一个结点可以分解出3个子结点; 如果能生成第i层的第c个结点;
数字c已用,将这个结点存储; 扩展第i层的第c个结点(向第i+1个位置放数) 向上回溯,并恢复数据 |
本周练习题
第1题:集合运算
在计算机领域中,我们经常要用到集合的运算,集合的运算操作很多,下面是我们给出的集合基本运算定义:
◆“∪”运算:设S,T是两个集合,那么S∪T是由S和T的元素组成的集合;
◆“∩”运算:设S,T是两个集合,那么S∩T是由既是S又是T中的元素组成的集合;
◆“—”运算:设S,T是两个集合,那么S—T是由S中非T的元素组成的集合;
◆“”运算:设S,T是两个集合,那么ST是由S中不是T中的元素和T中不是S中的元素组成的集合。
例如:S={1,2,3,4},T={3,4,5,6},那么:
S∪T={}1,2,3,4,5,6} S∩T={3,4} S—T={1,2} ST{1,2,5,6}。
你的任务就是:对于输入文件中给出的正整数集合S、T,编程求出S∪T、S∩T、S—T和ST。
输入文件set.in:第1行为集合S的各元素;第2行为集合T的个元素;每行数据之间用空格分开,集合元素个数<=10000,各正整数在1到30000之间。
输出文件set.out:第1行为集合S∪T;第2行为集合S∩T;第3行为集合S—T;第4行为集合ST。输出要求:集合中的元素按由小到大顺序输出,正整数之间用空格分开;如果是空集,则输出-1。
输入输出示例:
set.in |
Set.out |
9 3 7 6 5 10 5 4 3 2 7 |
2 3 4 5 6 7 9 10 3 5 7 6 9 2 4 6 9 10 |
第2题:反求公倍数
求整数a,b的最小公倍数是非常简单的问题,但现在我们已经知道某两个整数的最小公倍数P,那么请你公倍数是P的所有整数对(a,b)