c程序设计第六章.ppt_第1页
c程序设计第六章.ppt_第2页
c程序设计第六章.ppt_第3页
c程序设计第六章.ppt_第4页
c程序设计第六章.ppt_第5页
已阅读5页,还剩66页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1,第六章 指针、引用和动态空间管理,本章目标 掌握定义各种指针和通过指针进行数据间接访问的方法。 了解指针和数组的关系,掌握利用指针访问数组元素的方法。 掌握利用指针传递数据参数的方法。 了解引用的概念和定义各种引用的方法,掌握利用引用传递数据的方法。 掌握动态空间的申请和释放方法,会利用指针操纵动态空间。 了解如何使用指针构造复杂的数据结构,如:链表,2,前言 指针是c/c+的一个重要概念,正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存,能方便地使用字符串;有效而方便地使用数组;在调用函数时能得到多于一个的值;能直接处理内存等。指针的概念比较复杂,使用也比较灵活。,3,6.1指针的概念与指针变量的定义,6.1.1指针的概念 为了能清楚指针的概念,必须先弄清数据在内存中是如何存储和如何读取的。 内存空间是由许多顺序排列的内存单元组成的,这些存储单元以字节为单位,即每一个单元就是一个字节。例如:一个int型的变量长度是4个字节,在内存中就占4个单元。 给内存单元从0开始顺序编号,这些编号就是内存单元的地址;内存单元中存放的数据称为内存单元的内容。,0 0 1 1 0 0 0 0,0 0 0 0 0 0 0 0,0 0 0 0 0 0 0 0,0 0 0 0 0 1 0 1,0 0 0 0 0 0 0 0,0 0 0 0 0 0 0 0,内存地址,内存内容,0 1 2 3 4. . . 2000 2001 2002 2003,0 0 0 0 0 0 0 0,0 0 0 0 0 0 0 0,0 0 0 0 0 0 1 1,5,在程序中一般是通过变量名对内存单元进行存取的。其实,程序经过编译后已经将变量名转换为变量的地址,对变量值的存取实际上都是通过地址进行的。,6,对内存单元的访问有两种常用的方式:直接访问和间接访问,直接访问方式就是编译系统能够自动地根据变量名与地址的对应关系(这个关系是在编译时确定的)完成相应的操作。这种情况下,我们对数据的存取都是通过变量名进行的,并没有去关心变量对应的地址。 例如:程序已经定义好了变量 int i=3,编译时系统分配20002003四个字节给 i,在用到变量 i时,系统就会根据变量名与地址的对应关系,找到变量 i 的地址,并取出数据3。,i,00000000,00000000,00000000,00000011,2000,2003,7,但是,对变量的访问还有一种常用的方式:间接访问。所谓间接访问就是需要先设法“找到”或“算出”某个变量的存储地址,然后再根据这个地址访问该变量。这种情况下,我们就需要关心地址,这时,地址要作为一种特殊的数据被处理。 这样,就需要一种特殊的变量,专门用来存放地址。 例如:我们定义了一个变量ip,用来存放整型变量的地址。 ip也是一个变量,ip分配的内存单元地址是3000到3003,通过下列语句将变量i的地址存放到变量ip中: ip =& i(&的功能是取地址) 这时, ip的值就是2000。(即变量 i的起始地址),8,在这种情况下,系统要访问i 就要先找到存放地址的变量ip ,从中取出 i的地址2000,然后再到20002003取出 i的值3。,2000,3000,ip,3,2000,i,9,由于地址指明了数据存储的位置,因此形象地称地址为“指针”。某一地址处存放的数据称为“指针所指向的数据”。 上例中,地址2000就是i 的指针,如果有一个变量专门存放另一个变量的地址(即指针),则称它为指针变量,上述 ip就是一个指针变量。 指针变量的值是指针(地址),在32位机中,一个指针变量占4个字节。上例中, ip的值就是i 的指针2000,指针变量ip所指向的数据就是变量i。,10,为了表示指针变量与它所指向的变量之间的联系。在程序中用“*”(间接访问操作符)来表示指向。 如:ip是指针变量,* ip就是ip所指向的变量i。 上述i,ip的关系可以用下面的语句声明: int i; int *ip=/此时的*是间接访问操作符,表示ip所指向的数据,11,6.1.2 指针变量的定义和初始化,指针变量就是专门用来存放地址的变量。 指针变量的定义格式: 类型修饰符 *变量名=指针表达式; 例如: 1、int *pd1,*pd2; 定义了两个整型的指针变量,未赋初值。 2、double d,*pd1=并且这两个指针变量均被初始化为指向变量d。&d表示d的地址。,12,3、char *s=“This is a string”; 定义了一个字符型指针变量s,并被初始化为指向一个字符串,将字符串的内存首地址赋给指针s。 4、void *pd3=NULL,*pd4=0; 定义了两个无类型指针变量pd3和pd4,并且赋初值为空。 Void指针表示它只是一个指针,可以存放一个内存地址,但是如何解释这个地址中存放的数值还不确定。 5、long *pd5=NULL; 定义了一个长整型指针变量pd5,并赋初值为空。当一个指针不指向任何内存地址时,可以给它赋NULL值。在定义一个指针而没有给它初始化时,指针的值是不定的。那么在程序运行中怎么知道什么时候指针赋了值,什么时候没有赋值呢?可以借助NULL。,13,2、& 和*操作符,(1) 取地址操作符/指针也是变量,也可以对指针取地址,6.2 指针的基本操作,1、指针赋值:操作符= 同类型指针之间可以相互赋值。,14,(2)间接访问操作符*,间接访问,即存取指针所指向的数据。 例:int *pd,d; pd=/输出99 99 间接访问是相对于用变量名直接访问而言的。,15,注意指针变量定义中的*与间接访问运算符*的区别,例:void main( ) int a,b; int *p1,*p2;/变量定义 a=100;b=10; p1= /间接访问 输出结果: 100 10 100 10,变量定义处的*只是表示所定义的变量是指针变量。(与一般变量区别),间接访问处的*是用来联系指针变量和它所指向的变量。*p1,*p2分别代表指针变量p1,p2所指向的变量。(即a,b),16,对&和*再做些说明:,&和*的优先级相同,结合性是自右向左。 (1)对上例中的p1,若有&*p1,则它的含义是先进行*p1运算,得到变量a,然后再进行&a运算,取a的地址,即结果得到的是a的地址。 (2)对上例中的a,若有* & a,则它的含义是先进行&a运算,得到a的地址,再进行*运算,即&a所指向的变量,*&a和*p1是等价的。结果得到的是变量a。此时,*和&的作用相互抵消。 又如:*&k=3与k=3效果完全相同。,判断一个指针是否为空指针 用操作符= =(等于)和!=(不等于) 如果一个指针是空指针,利用该指针进行间接访问就毫无意义。因此,经常需要先检查指针是否为空,再确定能否进行数据的间接访问。 在需要条件表达式的地方(if语句,switch语句,while语句),常用对指针是否为空的判断。 例:下面三个语句都是表达“如果p是空指针,则” if(p= =0) if(p= =NULL) if(!p),18,而下面三个语句都是表达“如果p不是空指针,则” if(p!=0) if(p!=NULL) if(p),19,1、指针变量与地址虽然密不可分,但二者又是有区别的。地址只是指明了某个数据的存储位置,而指针变量不但指明了数据的存储位置,而且还表明了该数据的类型。指针是有数据类型的,指针的数据类型由它所指向的数据的类型决定。 即定义指针变量时,必须指定指针变量的类型,称之为基类型。基类型用来限定该指针变量可以指向的变量的类型。 例如:一个double类型的指针变量只能指向double型的数据。,对指针变量定义的几点说明,20,不限定类型的指针称为无类型指针或void指针,可以用来指向任何类型的数据。 如:char *p1=/其它类型的指针可赋给void指针 如果一个指针不指向任何一个数据,则称为空指针,其地址值就是0,可以用常量符号NULL来表示。由于地址0的特殊用途,因此,程序中不能将这个存储位置来存放数据。即有效数据的指针一定不是0。,2、要注意区分无类型指针和空指针,21,3、定义指针变量时,指针变量前面的*,表示该变量的类型为指针型变量。如:int *p1;表示p1是指针类型,变量名是p1,而不是*p1。这与以前定义普通变量的形式是不同的。 4、指针变量只能存放地址(指针),而且只能将变量已分配的地址赋给指针变量。 如:int *p; p=/试图把地址1000赋给p是不对的,1000不是内存地址。,22,P=/不能将字符的值作为地址。 在对指针初始化时,一定要保证给指针赋的值时一个合法的内存地址,而且这个地址是允许在程序中访问的地址。,23,指针的数据类型,强调指针的数据类型目的是说明该如何解释指针所指空间的数据。 任何类型的指针之间可以强制转换,格式是: (类型修饰符 *) 指针表达式 整型、短整型数据都是高位字节放在高地址,低位字节放在低地址。,24,#include void main() char *str=“12345678“; int *ip=(int *)str;/强制类型转换,将字符型指针转换为整型指针 short *sp=(short int *)str;/强制类型转换,将字符型指针转换为短整型指针 couthex*ipendl;/hex表示用16进制形式输出整数 couthex*spendl; cout*strendl; coutstrendl; cout“size of char *:“sizeof(char *)endl; cout“size of int *:“sizeof(int *)endl; cout“size of long int *:“sizeof(long int *)endl; cout“size of double *:“sizeof(double *)endl;/各种类型指针所占字节数 ,25,高地址,0x31,0x32,0x38,0x33,0x34,0x35,0x36,0x37,低地址,str,ip,sp,26,27,用const来限定指针,根据const出现的位置,有三种不同的含义 (1)指向常量的指针,即指针所指向的数据为常值。 (2)指针常量,即指针本身为常值。 (3)指向常量的指针常量。,28,(1)指向常量的指针 const出现在指针定义的最开始。该指针指向常量的指针,即指针所指向的数据为常值。所以,在这种情况下,不能通过指针来间接修改指针所指向的数据。 如:int i,j; const int *ip=/错误!因为ip是指向常量的指针 使用时应注意的几点 :,29,(1)指向常量的指针只限制指针的间接访问操作,而不会限制指针变量本身的操作。可以修改指针变量本身的值。比如:*ip=/错误,必须将p2定义成指向常量的指针,30,(2)指针常量 const出现在指针名的前面,该指针为指针常量,即不可以修改该指针的值。只能在定义该指针时给它初始化,以后不能再重新赋值。 如:int i,j; int *const ip=,31,4,(3)指向常量的指针常量 将(1)(2)组合起来就构成指向常量的指针常量。指向常量的指针常量必须在定义时赋初值,以后不能改变,并且不能通过指向常量的指针常量间接改变它所指向的数据。 如:int i92; int j; const int *const p= /错误!不能通过 p间接改变ci的值,32,6.3 指针与数组,6.3.1一维数组元素的指针访问方式: 一维数组的数组名实际上就是指向该数组第一个单元(下标为0的那个元素)的指针。 指向一维数组第一个单元的任何指针都可以象一维数组名那样使用。 如:#include void main() int a5=0,1,2,3,4; int *ap=a; for(int i=0;i5;i+) coutai apiendl; ,程序结果: 0 0 1 1 2 2 3 3 4 4,33,注意,数组名虽然是指针,但它是指针常量而不是指针变量,因此,不能改变它的值,而只能通过它去访问数组的各个元素。 所以,作用于变量的操作不能作用于数组名,如:对于已经定义好的数组a,*+a就是个错误的表达式。 而针对指针变量ap就可以执行*+ap操作。,34,用指针对数组操作,对数组的访问,传统上采用下标方式,但还可以采用指针方式。 如:若有定义 int A10; 则数组名A的数据类型是int *,并且指向第一个元素A0,因此,*A和A0访问的是同一个元素,两种表达形式完全等价。同样,*(A+1)可以访问A1,*(A+2)可以访问A2等,即*(A+ i)可以访问A i, *(A+ i)与 A i完全等价。 实际上,数组的下标操作就是指针操作, A i 只是*(A+ i)的另一种表示形式而已。,35,例:int A10,*pa=A; 则*(pa+i)和pai 访问的是同一个元素Ai。 下面的一段程序就是采用不同方式访问数组s的各元素: int s=0,1,2,3,4,5,*p=s; coutendl*pp1*(p+2)s3p4*(s+5); coutendl*+p; coutendl*p; 输出结果: 012345 1 1,s,p,0,1,3,2,4,5,36,指针移动运算,一、当指针指向的是一个数组时,指针可以在数组的各个元素之间移动。指针的加和减是以指针所指的数据类型的大小为单位进行的(一个数据元素所包含的字节数由指针指向的数据的类型决定,如指针指向整数,则一个数据元素包含4个字节,指针指向字符,则一个数据元素包含1个字节等 ) 1、向后(向高地址方向) 指针表达式+n 指针变量+=n 指针变量=指针变量+n 2、向前(向低地址方向) 指针表达式-n 指针变量-=n 指针变量=指针变量-n,37,例如:int m12,*p1=,0 1 2 3 4 5 6 7 8 9 10 11,指针移动时,地址的实际变化值(以字节计量)除了与移动的数据单元数有关,还与指针的类型有关。地址的实际变化值=移动的数据单元数n*sizeof(TYPE)个字节。,38,#include void main() int a5; int *pa=a; for(int i=0;i5;i+) cout“pa+“i“=“pa+iendl; ,39,二、移动一个单位 +前增一,后增一 -前减一,后减一 前增一:+指针变量 后增一:指针变量+ 前减一: -指针变量 后减一:指针变量- 前增一和前减一,都是先改变指针变量的值,然后再以改变后的指针值作为表达式的值。 后增一和后减一,都是先以指针变量的值,然后再改变指针变量的值。,40,例:int k,*pk= /显示两个不同的地址值,41,三、计算两地址间数据元素的个数,用操作符“-” 同类型的两个指针相减,其结果是一个整数,表示两地址之间可容纳的相应类型数据的个数。(不是地址实际间隔的字节数) 例如:int n,m12,*p1=/n=5,0 1 2 3 4 5 6 7 8 9 10 11,p1,p2,42,两地址间实际间隔的字节数=两地址间数据单元的个数*sizeof(对应数据类型)。 如:m10与m5实际间隔的字节数是5*sizeof(m0)=20个 如果指针指向的是一个结构数组,那么,指针的运算就会以一个结构元素所占内存大小为单位。,43,6.3.2 多维数组元素的指针访问方式,一维数组与指针关系的结论可以推广到二维数组、三维数组等多维数组。 可以把二维数组看成是这样的一维数组:它的每个单元又是一个一维数组。 例:int B68; 可以把这个二维数组看成是具有六个单元的一维数组,其中每个单元又是一个具有8个单元的int一维数组。B0,B1B5就是6个顺序存储且规格相同的一维数组。 B0,B1B5依次是它们的数组名。,44,那么,Bij也就是第i行第j列的元素就可以理解为Bi这个一维数组的第j个元素。 Bi本身就具有一维数组下标访问方式,可转换为*(B+i),于是有: Bij*(Bi+j)*(*(B+i)+j) 当i,j之一为0或均为0时: Bi0*(Bi+0)*Bi *(B+i) B0j(*(B+0)j(*B)j * (*B+j) B00*(B0+0)*B0 *(B+0)*B 指向二维数组首行的任何指针可以像二维数组名那样使用。例如: int B1020,(*pb)20=B;,45,6.3.3 关于“指向数组的指针”,凡是具有数组首地址的指针都可以称作指向数组的指针。 如无特别说明,指向数组的指针应理解为与该数组的数组名等价的指针。 定义指向一维数组以及指向二维数组的指针变量的格式分别是: 类型修饰符 *变量名=一维数组名; 类型修饰符 (*变量名)列数=二维数组名;,46,6.3.4 指针与字符串,字符串是以字符数组的形式存储的,因为字符指针一般会指向一个字符串,所以也就将字符指针看做一个字符串。 如:char *str=“string”; 等价于 char str=“string”; coutstrendl; / 输出串 string coutstr+3endl; /输出串 ing,s,t,r,i,n,g,str,str+3,47,#include void main() char str1 =“array string”; char *str2=“pointer string”; cout“const string”endl; coutstr1endl; coutstr2endl; coutstr1+6endl; cout*str2endl; coutstr2+8endl;,输出结果: const string array string pointer string string p string,48,补充:指针操作符的综合运用,在与指针有关的操作符中,+,-,*, =,+=,-=是2元操作符,具有其它符合赋值操作符一样几乎最低的优先级(仅高于,)。 上述这些操作符的结合性均为从右到左。,49,假定指针p的定义如下: int d=3,6,9,*p=d; 注意区分下列指针表达式的含义: *p+;取p所指向单元的数据作为表达式的值,然后使p指向下一个单元。 (*p)+;取p所指向单元的数据作为表达式的值,然后使该单元的数据值增1。 *+p;使p指向下一个单元,然后取该单元的数据作为表达式的值。 +*p;将p所指向的单元的数据增1并作为表达式的值。,int *p=d; 初始状态:,int d =3,6,9;,初始状态下,执行*p+后: (表达式*p+的值:3),p,初始状态下,执行(*p)+后: (表达式(*p)+的值:3),p,初始状态下,执行*+p后: (表达式*+p的值:6),p,初始状态下,执行+*p后: (表达式+ * p的值:4),p,51,6.3.5 指针数组,若数组的每个元素是一个指针,则称为指针数组,例如:int *ip10;ip是一个一维数组,其中的每个元素是一个指向整型数据的指针。 指针数组常见应用就是构造字符串符号表, 如定义一个记录七天的英文名字的数组: char*week7=“Sunday“,“Monday“,“Tuesday“,“Wednesday“,“Thursday“,“Friday“,“Saturday“;,52,53,6.4 指针与函数,6.4.1 指针参数 “传值”是直接传送方式,指针是一种间接传递方式。 通过指针参数的传递,形参指针和实参指针指向同一数据,因此通过形参指针就可以改动实参所指向的数据。,54,6.4.2 指针函数:返回指针值的函数,函数可以返回指针值,这样的函数称为指针函数。在定义指针函数时,函数名前必须有*,格式是: 类型修饰符 *函数名(形式参数表) 函数体 P171 例子 6.4.3 函数指针:指向函数的指针 指向函数的指针称为函数指针,格式为: 类型修饰符 (*变量名)(形式参数表)=函数名; 例如:int f1(int n) int (*pf1)(int)=f1;,55,*a=val1 *b=val2 *a *b 等价于val1 val2,10,20,val1,val2,&val1,&val2,a,b,6.4.4 作为参数传递的函数,函数指针的一个重要用途是将函数本身作为 参数传递给另一个函数。,56,6.5 引用,引用的概念 定义一个引用就是为一个变量或函数规定一个别名,以后所有针对 别名的操作都如同施加在别名所代表的对象上一样。 定义引用的格式 类型修饰符 2 2 引用的作用:可以实现按地址传递参数,i,ir,引用的初始化:引用不是变量所以必须在说明引用时说明它所引用的对象。所引用的对象必须是有对应内存空间的。注意:引用一旦初始化,它就被维系在它所引用的目标上,再不能改变,不能将这个别名再引用再其他目标上。P203 例题 能够引用的数据类型: (1)对简单数据类型变量或常量的引用,如: int 说明: (1)对void 的引用。对它的引用没有任何意义。,/注意“int *&”和“int &*”的区别, “int *&”表示对int指针的引用, “int &*”表示指向int引用的指针。前者是允许的,后者是不允许的。,(2)对数组名的引用。因为数组名本身不是个变量,它只是一些变量的聚集,所以对数组名的引用没有任何意义。可以对数组某个元素引用。 int /正确!i不是常量,59,指针和引用,指针与引用都是对某个变量所代表的空间进行操作的手段。指针是通过操作存放变量地址的变量达到间接操作的目的,而引用是通过为存放变量的空间定义一个别名来达到间接操作的目的。 而指针变量具有独立的内存空间存放变量的值,而引用只是一个依附于它所引用的变量的符号,没有独立的内存空间,因而它们有以下不同: (1)指针和引用对它们所指的或引用的变量的操作方式不一样。指针通过间接操作符“*”来访问,而引用直接访问它所引用的空间 (2)指针本身时一个变量,它不一定指向同一个内存空间,可以指向其他地方;引用一旦初始化后,再也不能改变。 从下面两个例子中体会如何在函数中使用指针和引用参数。,60,例1:设计函数swap,其功能是交换两个变量的值,要求变量的地址通过指针参数传递。,#include void swap(int *a,int * b) int c=*a; *a=*b; *b=c; void main( ) int x=3,y=5; coutx yendl; swap( ,61,#include void swap(int ,例2:设计函数swap,其功能是交换两个变量的值,要求变量的地址通过引用参数传递。,3,x,a,y,b,5,62,指针参数传递仍然属于”按值传递“方式,即如果形参指针变量的值发生改变,影响不到实参指针变量。但函数要处理的对象并不是形参变量本身而是形参所指向的数据,又因为形参变量与实参变量的值相同,当然形参变量与实参变量指向的数据是同一个单元,因此,我们可以通过在函数中改变形参指针变量所指向的数据达到改变实参指针变量所指向的数据的目的。 引用是通过给实参变量起一个别名,达到通过使用别名间接改变别名所代表的变量的目的。 应用指针和引用参数的另一个重要目的是减少参数传递过程中的数据复制量,达到节约空间,提高程序执行效率。 数组参数是指针参数的一种。,63,6.6 动态空间管理,在内存中有一部分空间是可以由程序动态支配的,这部分空间称为“堆”结构。堆是内存中的一块空闲区域,应用程序可以从这块区域申请一部分内存使用,在用完后再还给堆。如果申请成功,系统就返回所申请的空间的首地址。在程序中必须记住这个地址,即需要一个变量来存放所申请到的内存空间的首地址,指针变量正好能胜任这个工作。,程序区,静态数据区,堆,栈,64,专门用于动态管理

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论