280 likes | 400 Views
动态数据类型 —— 指针. 前面介绍的各种简单类型的数据和构造类型的数据属于静态数据。在程序中,这些类型的变量一经说明,就在内存中占有固定的存储单元,直到该程序结束。. 与静态变量对应的是动态变量,在程序执行过程中可以动态产生或撤消,所使用的存储空间也随之动态地分配或回收。为了使用动态变量, PASCAL 系统提供了指针类型,用指针变量(静态变量)来指示动态变量(存储地址变量)。下面介绍如何利用指针建立动态数据结构. 一、指针的定义及操作. (一)指针类型和指针变量.
E N D
动态数据类型——指针 前面介绍的各种简单类型的数据和构造类型的数据属于静态数据。在程序中,这些类型的变量一经说明,就在内存中占有固定的存储单元,直到该程序结束。 与静态变量对应的是动态变量,在程序执行过程中可以动态产生或撤消,所使用的存储空间也随之动态地分配或回收。为了使用动态变量, PASCAL系统提供了指针类型,用指针变量(静态变量)来指示动态变量(存储地址变量)。下面介绍如何利用指针建立动态数据结构.
一、指针的定义及操作 (一)指针类型和指针变量 在pascal中,指针变量(也称动态变量)存放某个存储单元的地址;也就是说, 指针变量指示某个存储单元。 指针类型的格式为:^基类型 说明: ①一个指针只能指示某一种类型数据的存储单元,这种数据类型就是指针的基类型,基类型可以是除指针、文件外的所有类型。 type pointer=^Integer;var p1,p2:pointer; 定义了两个指针变量p1和p2,这两个指针可以指示一个整型存储单元(即p1、p2 中存放的是某存储单元的地址,而该存储单元恰好能存放一个整型数据)。
②和其它类型变量一样,也可以在var区直接定义指针型变量。②和其它类型变量一样,也可以在var区直接定义指针型变量。 例如:var a:^real; b:^boolean; 又如:type person=recordname:string[20];sex:(male,female);age:1..100end;var pts:^person;
③pascal规定所有类型都必须先定义后使用,但只有在定义指针类型时可以例外,如下列定义是合法的:③pascal规定所有类型都必须先定义后使用,但只有在定义指针类型时可以例外,如下列定义是合法的: type pointer=^rec;rec=recorda:integer;b:charend;
(二)开辟和释放动态存储单元 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中,从而丢失了原存储单元的地址。②一个指针变量只能存放一个地址。如再一次执行New(p)语句,将在内存中开辟另外一个新的整型变量存储单元,并把此新单元的地址放在p中,从而丢失了原存储单元的地址。 如: New(p); New(p); ③当不再使用p当前所指的存储单元时,可以通过标准过程Dispose释放该存储单元。 Dispose(p);
⒉释放动态存储单元 dispose语句的一般格式: dispose(指针变量); 功能:释放指针所指向的存储单元,使指针变量的值无定义。 如: New(p); Dispose(p);
(三)动态存储单元的引用 在给一个指针变量赋以某存储单元的地址后,就可以使用这个存储单元. 引用动态存储单元一般格式:<指针变量>^ 说明: ①在用New过程给指针变量开辟了一个它所指向的存储单元后,要使用此存储单元的唯一方法是利用该指针。 ②对动态存储单元所能进行的操作是该类型(指针的基类型)所允许的全部操作。
例1 设有下列说明:var p:^integer; i:integer; 画出执行下列操作后的内存示意图:New(p); P^:=4;i:=p^; (a)编译时 (b)执行New语句(c)执行P^:=4 (d)执行i:=P^ 分配存储 单元 内存单元示意图
(四)对指针变量的操作 ⒈具有同一基类型的指针变量之间相互赋值 例2 设有下列说明与程序段:var p1,p2,p3:^integer;beginNew(P1) ; New(P2); New(P3);P1:=P2; P2:=P3;end; 2、可以给指针变量赋nil值 nil是PASCAL的关键字,它表示指针的值为"空"。 例如,执行:p1:=ni1后,p1的值是有定义的,但p1不指向任何存储单元。
3、可以对指针变量进行相等或不相等的比较运算3、可以对指针变量进行相等或不相等的比较运算 在实际应用中,通常可以在指针变量之间,或指针变量与nil之间进行相等(=)或不相等(<>=的比较,比较的结果为布尔量。 例3 输入两个整数,按从小到大打印出来。 分析:不用指针类型可以很方便地编程,但为了示例指针的用法,我们利用指针类型。定义一个过程swap用以交换两个指针的值。 Type pointer=^integer;var p1,p2:pointer;procedure swap(var q1,q2:pointer);var q:pointer;beginq:=q1;q1:=q2;q2:=q;end;beginnew(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.
[例1] 分别用简单变量和指针变量交换两个变量的值。 解:设两个变量a,b的值分别为5, 8 (1)用简单变量交换: Program Exam101; 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)用指针变量交换: Program Exam102; 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)种方法,直接采用变量赋值进行交换,(实际上给各存储单元重新赋值)其过程如下图所示:第(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)。存储单元的值没有重新赋值,只是存放指针值的变量交换了指针值。
[例2] 利用指针对数组元素值进行排序。 分析: ①定义一个各元素为指针类型的数组a : ②定义一个交换指针值的过程(swap); ③定义一个打印过程(print); ④定义过程(int)将数组b的值赋给a数组各元素所指向的各存储单元。 ⑤过程pixu用交换指针值的方式,按a数组所指向的存储单元内容值从小到大地调整各元素指针值,实现“指针”排序; ⑥按顺序打印a数组各元素指向单元的值(a[ i ] ^ )。
Program Exam2; 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.
[例3] 有m只猴子要选猴王,选举办法如下:所有猴子按1..m编号围坐成圆圈,从第一号开始按顺序1,2, ..,n连续报数,凡报n号的退出到圈外。如此循环报数,直到圈上只剩下一只猴子即当选为王。 普通方法 经仔细分析,此题实质与筛选法求素数类似。 1)开始时将m个下标变量的值均赋为1,表示大家都在圈上。 2)假设我们用变量K来计数,当报到n时(即K的值为n)退出,将相应的下标变量的值改赋为0,表示他已退出圈,其值不影响K的下一次计数。 3)连圈问题:计数时下标值从1开始依次增加,超n时重新赋为1,这样做就连成圈了。
Program monkey; Var a:array[1..100] of integer; I,k,p,x:integer; Begin readln(m,n); for I:=1 to n do a[i]:=1; k:=0;p:=0; while p<m-1 do {当P=m-1时结束任务,即只有一个人还在圈上} begin x:=0; {每次计数时清零} while x<n do begin k:=k+1; if k>m then k:=1; {连成圈} x:=x+a[k]; end; write(k,’ ‘); a[k]:=0; p:=p+1; end; End. 程序说明: K:数组下标值,为了连成圈 P:统计出圈个数 X:报数值
编号变量 链指针变量 [例3] 有m只猴子要选猴王,选举办法如下:所有猴子按1..m编号围坐成圆圈,从第一号开始按顺序1,2, ..,n连续报数,凡报n号的退出到圈外。如此循环报数,直到圈上只剩下一只猴子即当选为王。用指针(环形链表)编程。 分析: ①让指针pon指向的单元为记录类型,记录内容含有两个域: ②用过程crea建立环形链; ③用过程king进行报数处理:每报数一次t计数累加一次,当所报次数能被n整除,就删去该指针指向的记录,将前一个记录的链指针指向下一个记录。删去的指向单元(记录)应释放。直到链指针所指向的单元是同一单元,就说明只剩下一个记录。 ④打印指向单元记录中编号域(num)的值。
program Exam03; 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.
习 题 1.请将下列八个国家的国名按英文字典顺序排列输出。 China(中国) Japan(日本) Cancda(加拿大) Korea(朝鲜) England(英格兰) France(法兰西) American(美国) India(印度) 2.某医院里一些刚生下来的婴儿 ,都还没有取名字,全都统一用婴儿服包装,很难区分是谁的小孩。所以必须建立卡片档案,包含内容有编号、性别、父母姓名、床号。实际婴儿数是变动的,有的到期了,家长要抱回家,要从卡片上注销;新的婴儿出生,要增加卡片,请编程用计算机处理动态管理婴儿的情况。
二、链表结构 设有一批整数(12,56,45,86,77,……,),如何存放呢? 当然我们可以选择以前学过的数组类型。但是,在使用数组前必须确定数组元素的个数。如果把数组定义得大了,就会有大量空闲存储单元,定义得小了,又会在运行中发生下标越界的错误,这是静态存储分配的局限性。 指针类型可以构造一个简单而实用的动态存储分配结构――链表结构。
下图是一个简单链表结构示意图: 说明: ①每个框表示链表的一个元素,称为结点。 ②框的顶部表示了该存储单元的地址(当然,这里的地址是假想的)。 ③每个结点包含两个域:一个域存放整数,称为数据域,另一个域存放下一个结点(称为该结点的后继结点,相应地,该结点为后继结点的前趋结点)的地址。 ④链表的第一个结点称为表头,最后一个结点表尾,称为指针域; ⑤指向表头的指针head称为头指针(当head为nil时,称为空链表),在这个指针变量中 存放了表头的地址。 ⑥在表尾结点中,由指针域不指向任何结点,一般放入nil。