动态数据类型 —— 指针
DESCRIPTION
动态数据类型 —— 指针. 前面介绍的各种简单类型的数据和构造类型的数据属于静态数据。在程序中,这些类型的变量一经说明,就在内存中占有固定的存储单元,直到该程序结束。. 与静态变量对应的是动态变量,在程序执行过程中可以动态产生或撤消,所使用的存储空间也随之动态地分配或回收。为了使用动态变量, PASCAL 系统提供了指针类型,用指针变量(静态变量)来指示动态变量(存储地址变量)。下面介绍如何利用指针建立动态数据结构. 一、指针的定义及操作. (一)指针类型和指针变量. - PowerPoint PPT PresentationTRANSCRIPT
动态数据类型——指针
前面介绍的各种简单类型的数据和构造类型的数据属于静态数据。在程序中,这些类型的变量一经说明,就在内存中占有固定的存储单元,直到该程序结束。
与静态变量对应的是动态变量,在程序执行过程中可以动态产生或撤消,所使用的存储空间也随之动态地分配或回收。为了使用动态变量, PASCAL 系统提供了指针类型,用指针变量(静态变量)来指示动态变量(存储地址变量)。下面介绍如何利用指针建立动态数据结构 .
一、指针的定义及操作
(一)指针类型和指针变量在 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 释放该存储单元。
如: New(p); New(p);
Dispose(p);
⒉ 释放动态存储单元
dispose 语句的一般格式 : dispose( 指针变量 );
功能:释放指针所指向的存储单元,使指针变量的值无定义。
Dispose(p);
如: New(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; 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.
[ 例 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.
5 5 5 5 58 8 8 8
a a ab b bc c c
① ② ③
a①将 cb②将 ac③将 b
(图中 表示赋值)
第 (2) 种方法,对各存储单元所保存的值并不改变,只是交换了指向这些单元的存储地址(指针值),可以简略地用如下图示说明:
第 (1) 种方法,直接采用变量赋值进行交换,(实际上给各存储单元重新赋值)其过程如下图所示:
5 5 58 8 8
a a ab b bc c c
① ② ③
( 图中“ -- 〉”表示指向存储单元 )
指针类型的指针变量 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 。