指针的动态变量
1.定义指针类型
在Turbo Pascal中,指针变量中存放的某个存储单元的地址,即指针变量指向某个存储单元。一个指针变量仅能指向某一种类型的存储单元,这种数据类型是在指针类型的定义中确定的,称为指针类型的基类型。指针类型定义如下:
类型名=^基类型名;
例如:type q=^integer;
var a,b,c:q;
说明q是一指向整型存储单元的指针类型,其中"^"为指针符。a,b,c均定义为指针变量,分别可以指向一个整型存储单元。
上例也可定义为:
var a,b,c:^integer;
指针也可以指向有结构的存储单元。
例如:type person=record
name:string[10];
sex:(male,female);
age:20..70
end;
var pt:^person;
pt为指向记录类型person的指针变量。
2.动态变量
应用一个指针指向的动态存储单元即动态变量的形式如下:
指针变量名^
例如:p^、q^、r^
指针变量p和它所指向的动态变量^p之间有如下关系:
P->P'
以下语句把整数5存放到p所指向的动态变量p^ 中去:
p^:=5;
以下语句把p所指向的p^中的值赋给整型变量i:
i:=p^;
如果指针变量p并未指向任何存储单元,则可用下列赋值语句:
p:=nil;
其中nil是Turbo Pascal保留字,表示“空”,相当于C里面的null
10.2 对动态变量的操作
在Turob Pascal程序中,动态变量不能由var直接定义而是通过调用标准过程new建立的。过程形式为:
new(指针变量名);
如果有下列变量定义语句:
var p:^integer;
仅仅说明了p是一个指向整型变量单元的指针变量,但这个整型单元并不存在,在指针变量p中还没有具体的地址值。在程序中必须通过过程调用语句:new(p);才在内存中分配了一个整型变量单元,并把这个单元的地址放在变量p中,一个指针变量只能存放一个地址。在同一时间内一个指针只能指向一个变量单元。当程序再次执行new(p)时,又在内存中新建立了一个整型变量单元,并把新单元的地址存放在p中,从而丢失了旧的变量单元的地址。
为了节省内存空间,对于一些已经不使用的现有动态变量,应该使用标准过程dispose 予以释放。过程形式为:dispose(指针变量名);为new(指针变量名)的逆过程,其作用是释放由指针变量所指向的动态变量的存储单元。例如在用了new(p)后在调用dispose(p),则指针p所指向的动态变量被撤销,内存空间还给系统,这时p的值为nil。
例:输入两个数,要求先打印大数后打印小数的方式输出,用动态变量做。
program dongtai;
type intepter=^integer;
var p1,p2:intepter;
procedure swap(var,q1,q2:intepter);
var p:integer;
begin
p:=q1;q1:=q2;q2:=p;
end;
begin
new(p1);new(p2);
writeln('input 2 data: ');readln(p1^,p2^);
if p1^ writeln('output 2 data: ',p1^:4,p2^:$);
end.
1 建立链表
链表是动态数据结构的一种基本形式。如果我们把指针所指的一个存贮单元叫做结点,那么链表就是把若干个结点链成了一串。我们把链表叫做动态数据结构,是因为链表中的结点可增可减,可多可少,可在中间任意一个位置插入和删除。
下面就是一个链表:
(图T10.2)
每个链表有两个域,一个是数据域,一个是指向下一个结点的指针域。最后一个结点的
指针域为NIL,NIL表示空指针。
要定义一个链表,每个结点要定义成记录型,而且其中有一个域为指针。
TYPE
POINT=↑PP;
PP=RECORD
DATA:STRING[5];
LINK:POINT;
END;
VAR P1,P2:POINT;
在这个定义中,定义了两个指针变量P1,P2。每个结点有两个域,一个是数据域,一
个是指针域,数据域是字符串型。这种定义中,指针中有记录,记录中有指针,形成一种递归调用。
下面我们来看一下如何定义上图的链表:
有:P1,P2,P3,P4属于POINT类型。
(1)建立第一个结点
NEW(P1);
P1↑.DATA:=D1;
(2) 建立第二个结点,并把它接在P1的后面
NEW(P2);
P2↑.DATA:=D2;
P1↑.LINK:=P2;
(3) 建立第三个结点,并把它接在P2的后面.
NEW(P3);
P3↑.DATA:=D3;
P2↑.LINK:=P3;
(4) 建立最后一个结点,并把它接在P3的后面.
NEW(P4);
P4↑.DATA:=D4;
P4↑.LINK:=NIL;
P3↑.LINK:=P4;
例1 建立一个有10个结点的链表,最后输出该链表。
P2前一个结点,K一直指向第一个结点。
PROGRAM E1(INPUT,OUTPUT);
TYPE point=^pp;
pp=record
data:string[5];
link:point;
end;
VAR
p1,p2,k:point;
i:integer;
BEGIN
{产生新结点P1,作为链表的头}
new(p1);
writeln('input data');
readln(p1^.data);
{指针K指向链表头}
k:=p1;
{用循环产生9个新结点,每个结点都接在上一个结点之后}
for i:=1 to 9 do
begin
new(p2);
writeln('input data');
readln(p2^.data);
p1^.link:=p2;
p1:=p2;
end;
{给最后一个结点的LINK域赋空值NIL}
p2^.link:=nil;
{从链表头开始依次输出链表中的DATA域}
while k^.link<>nil do
begin
write(k^.data,'->');
k:=k^.link;
END.
在本程序里,输出链表的过程就是一个链表的遍历。给出一个链表的头结点,依次输出后面每一个结点的内容,指针依次向后走的语句用K:=K↑.LINK 来实现。
例2 读入一批数据,遇负数时停止,将读入的正数组成先进先出的链表并
输出。
分析:首先应定义指针类型,结点类型和指针变量,读入第一个值,建立首结点,读入第二个值,判断它是否大于零,若是,建立新结点。
PROGRAM fifo(input,output);
{建立先进先出链表}
TYPE
Point=^node;
Node=RECORD
Data:real;
Link:point
END;
VAR
head,last,next:point;
x:real;
BEGIN
{读入第一个值,建立首结点}
read(x);
new(head);
head^.data:=x;
last:=head;
{读入第二个值}
read(x);
write(x:6:1);
WHILE x>=0 DO
BEGIN
{建立新结点}
new(next);
next^.data:=x; {链接到表尾}
last^.link:=next; {指针下移}
last:=next; {读入下一个值}
read(x);
write(x:6:1)
END;
Writeln; {表尾指针域置NIL}
Last^.link:=NIL; {输出链表}
Next:=head;
WHILE next<>NIL DO
BEGIN
Write(next^.data:6:1);
Next:=next^.link
END;
Writeln
END.
例3 读入一批数据,遇负数时停止,将读入的正数组成先进后出的链表并输出。
PROGRAM fifo(input,output);
{建立先进后出链表}
TYPE
point=^node;
node=RECORD
data:real;
link:point
END;
VAR
head,last,next:point;
x:real;
BEGIN
{初始准备}
next:=NIL;
read(x); {读入第一个数}
write(x:6:1);
WHILE x>=0 DO
BEGIN {建立一个新结点}
new(head);
head^.data:=x; {链接到表首}
head^.link:=next;{指针前移}
next:=head; {读入下一个数}
read(x);
write(x:6:1)
END;
writeln; {输出链表}
WHILE next<>NIL DO
BEGIN
write(next^.data:6:1);
next:=next^.link
END;
writeln
END.
2 在链表中插入结点
在一个已建好的链表中插入一个结点,这是一种常见算法。当我们找到插入点后,就断开原先的链接,将新结点插入进来。
(图T10.3)
如图所求示:要把P3的结点插入到P2之前,应该这样操作:(1) P1,P2之间的链断开,改为P1指向P3。
P1↑.LINK:=P3;
(2)将P2,P3连接起来:
P3↑.LINK:=P2;
于是,P3所指向的结点便插入进来了。
例4 插入一结点的过程
PROCEDURE insert(x:real;VAR head:point);
{插入一结点的过程}
VAR
q,last,next:point;
BEGIN
{建立新结点}
new(q);
q^.data:=x;
IF X<=head^.ata
THEN {插入前表}
BEGIN
q^.link:=head;
head:=q;
EDN
ELSE BEGIN {找出表中合适的位置}
Next:=head;
WHILE (x>next^.data) AND (next^.link<>NIL) DO
BEGIN
Last:=next;
Next:=next^.link
END;
IF x<=next^.data
THEN {插入中间表}
BEGIN
last^.link:=q;
q^.link:=next
END
ELSE {插入表尾}
BEGIN
next^.link:=q;
q^.link:=NIL
END
END
END;
例5 建立一个有序的整数链表,再将一任意整数插入进链表,使链表仍然有序。
PROGRAM e1(input,output);
TYPE point=^pp;
pp=record
data:integer;
link:point;
end;
VAR
p1,p2,p3,k:point;
BEGIN
{建立一个整数的链表,要求输入的整数依次是有序的,以数9999结束}
new(p1);
writeln('input data');
readln(p1^.data);
k:=p1;
repeat
new(p2);
writeln('input data');
readln(p2^.data);
p1^.link:=p2;
p1:=p2;
until p2^.data=9999;
p2^.link:=nil;{有序链表建立完毕}
writeln('input p3');
readln(p3^.data);
p1:=k;p2:=k;{P1,P2都指向表头}
{P2找到插入点后一个结点}
while p2^.data p2:=p2^.link; {P1找到插入点以前的结点} while p1^.link<>p2 do p1:=p1^.link; {将P3接入P1,P2之间} p1^.link:=p3; p3^.link:=p2; {将整个插入后的链表依次打印出来} repeat write(k^.data,'->'); k:=k^.link; until k^.data=9999; write(k^.data); END. 3 删除一个结点 要在一个链表中删去一个结点,首先在链表中找到该结点,将其前面的指针与该点断开,直接接到后面的结点,再用DISPOSE 命令将P3结点空间释放。如图,删除P3结点: (图T10.4) 删除语句如下: P1↑.LINK:=P3↑.LINK DISPOSE(P3); 例6 删除结点的过程 PROCEDURE delete(x:real;VAR head;point;VAR deleted:Boolean); {删除结点的过程} VAR Last,next:point; BEGIN {遍历表直到找到目标或到达表末} next:=head; WHILE(next^.data<>x)AND(next^.link<>DIL) DO BEGIN Last:=next; Next:next^.link END; {如果目标文件找到,删除包含它的结点,设置deleted} IF next^.data=x THEN BEGIN Deleted:=true; IF next=head THEN head:=head^.link ELSE last^.link:=next^.link END ELSE deleted:=false END; 例7 在一个有序链表中装有1到100共100个整数。要求任意输入100以内的一个整数,把它从链表中删除。 PROGRAM e1(input,output); TYPE point=^pp; pp=record data:integer; link:point; end; VAR p1,p2,p3,k:point; i:integer; BEGIN {建立一个1――100的整数的有序链表} new(p1); k:=p1; p1^.data:=1; for i:=2 to 100 do begin new(p2); p2^.data:=i; p1^.link:=p2; p1:=p2; end; p2^.link:=nil; {输入要删除的结点的DATA值} new(p3); writeln('input p3'); readln(p3^.data); P1:=k;p2:=k; {P2指针查找P3结点} while p2^.data p2:=p2^.link; {P1指针定位于P3之前} while p1^.link<>p2 do p1:=p1^.link; p1^.link:=p2^.link; dispose(p3);{将P3结点的空间释放} {输出修改后的链表} repeat write(k^.data,'->'); k:=k^.link; until k^.data=100; write(k^.data); END. C++指针函数习题 一、选择题 1.以下程序的运行结果是()。 sub(int x, int y, int *z) { *z=y-x; } void main() { int a,b; sub(10,5,&a); sub(7,a,&b); cout< #include<> 彻底搞定C指针---指向指针的指针 彻底搞定C指针---指向指针的指针一.回顾指针概念: 今天我们又要学习一个叫做指向另一指针地址的指针。让我们先回顾一下指针的概念吧! 当我们程序如下申明变量: short int i; char a; short int * pi; 程序会在内存某地址空间上为各变量开辟空间,如下图所示。 内存地址→6 7 8 9 10 11 12 13 14 15 ------------------------------------------------------------------------------------- … | | | | | | | | | | ------------------------------------------------------------------------------------- |short int i |char a| |short int * pi| 图中所示中可看出: i 变量在内存地址5的位置,占两个字节。 a变量在内存地址7的位置,占一个字节。 pi变量在内存地址9的位置,占两个字节。(注:pi 是指针,我这里指针的宽度只有两个字节,32位系统是四个字节) 接下来如下赋值: i=50; pi=&i; 经过上在两句的赋值,变量的内存映象如下: 内存地址→6 7 8 9 10 11 12 13 14 15 -------------------------------------------------------------------------------------- … | 50 | | | 6 | | | | -------------------------------------------------------------------------------------- |short int i |char a| |short int * pi| 看到没有:短整型指针变量pi的值为6,它就是I变量的内存起始地址。所以,这时当我们对*pi进行读写操作时,其实就是对i变量的读写操作。如:*pi=5; //就是等价于I=5; 你可以回看本系列的第二篇,那里有更加详细的解说。 二.指针的地址与指向另一指针地址的指针 在上一节中,我们看到,指针变量本身与其它变量一样也是在某个内存地址中的,如pi的内存起始地址是10。同样的,我们也可能让某个指针指向这个 用名作为其他变量名地别名. ; 等价于; ()声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名地一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元.故:对引用求地址,就是对目标变量求地址.与相等. ()不能建立数组地引用.因为数组是一个由若干个元素所组成地集合,所以无法建立一个数组地别名. 引用应用 、引用作为参数 引用地一个重要作用就是作为函数地参数.以前地语言中函数参数传递是值传递,如果有大块数据作为参数传递地时候,采用地方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序地效率.但是现在(中)又增加了一种同样有效率地选择(在某些特殊情况下又是必须地选择),就是引用. 【例】: ( , ) 此处函数地形参, 都是引用 { ; ; ; ; } 为在程序中调用该函数,则相应地主调函数地调用点处,直接以变量作为实参进行调用即可,而不需要实参变量有任何地特殊要求.如:对应上面定义地函数,相应地主调函数可写为: ( ) { ; >>>>; 输入两变量地值 (); 直接以变量和作为实参调用函数 <<<< ' ' <<; 输出结果 } 上述程序运行时,如果输入数据并回车后,则输出结果为. 由【例】可看出: ()传递引用给函数与传递指针地效果是一样地.这时,被调函数地形参就成为原来主调函数中地实参变量或对象地一个别名来使用,所以在被调函数中对形参变量地操作就是对其相应地目标对象(在主调函数中)地操作. ()使用引用传递函数地参数,在内存中并没有产生实参地副本,它是直接对实参操作;而使用一般变量传递函数地参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量地副本;如果传递地是对象,还将调用拷贝构造函数.因此,当参数传递地数据较大时,用引用比用一般变量传递参数地效率和所占空间都好. ()使用指针作为函数地参数虽然也能达到与使用引用地效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"地形式进行运算,这很容易产生错误且程序地阅读性较差;另一方面,在主调函数地调用点处,必须用变量地地址作为实参.而引用更容易使用,更清晰. 如果既要利用引用提高程序地效率,又要保护传递给函数地数据不在函数中被改变,就应使用常引用. 、常引用 常引用声明方式:类型标识符引用名目标变量名; 用这种方式声明地引用,不能通过引用对目标变量地值进行修改,从而使引用地目标成为,达到了引用地安全性. 【例】: ; ; ; 错误 ; 正确 这不光是让代码更健壮,也有些其它方面地需要. 【例】:假设有如下函数声明: . 编程题 1用指向数组的指针变量输出数组的全部元素 2 使用函数调用,形参为指针,实参为数组,把一个数组逆序存放在输出 练习题: 一判断题 1.指针是变量,它具有的值是某个变量或对象的地址值,它还具有一个地址值,这两个地址值是相等的。 2.指针的类型是它所指向的变量或对象的类型。 3.定义指针时不可以赋初值。 4.指针可以赋值,给指针赋值时一定要类型相同,级别一致。5.指针可以加上或减去一个int型数,也可以加上一个指针。6.两个指针在任何情况下相减都是有意义的。 7.数组元素可以用下标表示,也可以用指针表示。 8.指向数组元素的指针只可指向数组的首元素。 9.字符指针是指向字符串的指针,可以用字符串常量给字符指针赋值。 10.引用是一种变量,它也有值和地址值。 11.引用是某个变量的别名,引用是被绑定在被引用的变量上。 12.创建引用时要用一个同类型的变量进行初始化。 13.指针是变量,它可以有引用,而引用不能有引用。 ;. . 二单选题 1.下列关于定义一个指向double型变量的指针,正确的是()。A.int a(5);double *pd=a; B.double d(2.5),*pd=&d;C.double d(2.5),*pd=d; D.double a(2.5),pd=d;。).下列关于创建一个int型变量的引用,正确的是(2A.int a(3),&ra=a; B int . a(3),&ra=&a;ra=a;D.int a(3), C.double d(3.1);int &rd=d;.下列关于指针概念的描述中,错误的是()。3 A.指针中存放的 是某变量或对象的地址值.指针的类型是它所存放的数值的类型 B .指针是变量,它也具有一个内存地址值 C .指针的值是可以改 变的D 。.下列关于引用概念的描述中,错误的是()4 A.引 用是变量,它具有值和地址值 B.引用不可以作数组元素 C.引用是变量的别名 D.创建引用时必须进行初始化。++*p相同的是()*p=a5.已知:int a[5],;则与a[0] . B.*++p A++a[0] .C*p++ D.;. . 6.已知:int a[ ]={1,2,3,4,5},*p=a;在下列数组元素地址的表 编程题 1用指向数组的指针变量输出数组的全部元素 2 使用函数调用,形参为指针,实参为数组,把一个数组逆序存放在输出 练习题: 一判断题 1.指针是变量,它具有的值是某个变量或对象的地址值,它还具有一个地址值,这两个地址值是相等的。 2.指针的类型是它所指向的变量或对象的类型。 3.定义指针时不可以赋初值。 4.指针可以赋值,给指针赋值时一定要类型相同,级别一致。 5.指针可以加上或减去一个int型数,也可以加上一个指针。 6.两个指针在任何情况下相减都是有意义的。 7.数组元素可以用下标表示,也可以用指针表示。 8.指向数组元素的指针只可指向数组的首元素。 9.字符指针是指向字符串的指针,可以用字符串常量给字符指针赋值。 10.引用是一种变量,它也有值和地址值。 11.引用是某个变量的别名,引用是被绑定在被引用的变量上。 12.创建引用时要用一个同类型的变量进行初始化。 13.指针是变量,它可以有引用,而引用不能有引用。 二单选题 1.下列关于定义一个指向double型变量的指针,正确的是()。 A.int a(5);double *pd=a;B.double d(2.5),*pd=&d;C.double d(2.5),*pd=d;D.double a(2.5),pd=d; 2.下列关于创建一个int型变量的引用,正确的是()。 A.int a(3),&ra=a;B.int a(3),&ra=&a; C.double d(3.1);int &rd=d;D.int a(3),ra=a; 3.下列关于指针概念的描述中,错误的是()。 A.指针中存放的是某变量或对象的地址值 B.指针的类型是它所存放的数值的类型 C.指针是变量,它也具有一个内存地址值 D.指针的值是可以改变的 4.下列关于引用概念的描述中,错误的是()。 A.引用是变量,它具有值和地址值 B.引用不可以作数组元素 C.引用是变量的别名 D.创建引用时必须进行初始化 5.已知:int a[5],*p=a;则与++*p相同的是()。 A.*++p B.a[0] C.*p++ D.++a[0] 2变量的指针和指针变量的区别是什么。 答;一个变量的地址指出了变量的存储单元在内存中的具体位置,能对变量进行存取操作。这个变量的地址就是变量的指针。指针是一种具有特殊意义的整型数,指针不能存放在一般的整型变量中,必须存放在专门指针的变量中,这类变量就是指针变量。 3 一维数组元素的引用有哪些方式。 答;下标法、地址法、指针法 4 2维数组列地址有哪些计算方法。 答;1 根据数组元素所在的行计算出行地址,然后把行地址转换成行中首元素的地址,再根据数组元素所在的列计算数组元素的地址。 2 根据2维数组的数组元素在存储空间上按行连续存放的特点,每个数组元素的地址等于2维数组元素的首元素地址加上该数组元素相对于首元素位置的偏移量。 3把2维数组的每一行当作一个一维数组,用一维数组元素地址的计算方法计算相应的2维数组元素的地址。 第9章结构体与共用体 1 什么是链表。其中单向链表具有哪些特点。 答;链表是若干个同样类型的结构通过依次串接方式构成的一种动态数据结构。链表中的每一个结构体数据成为结点,链表可以分成单向链表和双向链表 单向链表的特点;1 链表中的结点数据可以改变的 2 结点占用的内存是动态分配内存和动态释放内存函数。 2 对单向链表的常用操作有哪些。 答;对单向链表的常用操作有建立、显示、插入,删除和查找。 3 什么是共用体。 答;共用体是一个集合体。它的各个成员的数据类型可以是不相同的,所有成员共享同一段存储空间,存储空间的大小取决存储单元最大的成员的数据类型。 4 指向结构体类型变量的指针变量引用形式有哪些。 答;有两种形式;【星号指针变量名】。成员名和指针变量名-大于号成员名。 第10章位运算及编译预处理 1 C提供的编译预处理功能有哪些。如何实现。 答;功能有三种;宏定义、文件包含和条件编译,分别用宏定义命令、文件包含命令、条件编译命令实现。 2 文件包含的基本功能是什么。 答;文件包含处理是一个源文件可以将另一个源文件的全部内容包含到本文件中来,作为本文件的一部分,这可以节省程序设计人员的重复劳动。 【3【在C语言中提供了几种什么样的位运算符。 答;-、小于小于、大于大于、 4 文件包含需要注意哪些问题 答;一个井include命令只能指定一个被包含文件,包含多个文件侧需多个井include命令;文件包含可以嵌套,即一个被包含文件中可以包含另一个被包含的文件;在井include命令中,文件名可以用双引号或尖括号括起来。 第11章文件 1 文件的结束标志有哪些。 答;每个文件都有一个结束标志。当文件的位置指针移到文件的结束标志处时,表示文件结束。如何测试文件是否结束,常有2种方法 1 ASCII码文件的结束标志用【-1】表示。C指针函数习题
指向指针的指针——彻底搞定C指针
指针变量作为函数参数
指针练习题
指针练习题
变量的指针和指针变量的区别是什么
指向函数的指针