第五章 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 15…98 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公斤,该选哪几件物品放入背包内使总价值最大