1、第 章 指针和引用,概述,指针是C+语言的重要概念,利用指针可以高效而简洁的处理数据,有些操作不借用指针是无法完成的(如直接访问内存地址、动态分配内存)。因此指针成为灵活编程的重要工具。但万事有利就有弊,在程序中不加节制的滥用指针将造成程序的数据流混乱,可读性下降等问题。所以在使用指针时要多一分小心。,预备知识,内存就是内部存储器,由存储单元组成。它的特点是存储单元是线性连续的。存储单元的最小单位是字节。,正如我们的住房都有门牌号码一样,为了访问内存中的某个存储单元,我们也要为它编号,这种编号称为内存地址。通过地址我们就能够访问该地址所标示的存储单元。,地址,内容,45,在程序中定义了一个变量
2、,这个变量在内存中就要占用一定大小的空间,通常这个空间的大小就由这个变量的类型决定。变量在内存中总是占用几个连续的字节,开始字节的地址就是变量的地址。,int i; char ch; float f1; i=10; ch=A; f1=3.14;,什么是指针,指针就是变量的地址。与此对应,在C+语言中使用一种专门的变量-指针变量来存放另一变量的地址。也就是说,指针变量是存放地址数据的变量,它的值应该是某个变量的地址。,指针变量,变量地址(指针),变量,变量的值,指向,地址存入指针变量,一般来说,指针变量只能存放某一种数据类型的变量地址,由此可以将指针变量划分为整型指针变量、实型指针变量、字符型指
3、针变量等等。 整型指针变量只能存放整型变量的地址。 实型指针变量只能存放实型变量的地址。 字符型指针变量只能存放字符型变量的地址。,当把变量的地址存入指针变量后,我们就可以说这个指针指向了该变量。,指针的定义,指针定义的结构形式如下: 数据类型名 *指针变量名,如:int *ptr1,*ptr2; float *ptr3; char *ptr4;,在定义指针时要注意以下几个问题: 1。指针变量名前面的星号“*”不能省略,如果写成 int *ptr1,ptr2; 则ptr2被定义为整型变量而非指针变量。,2。定义中的星号“*”表示所定义的变量是指针变量, 但指针变量名是ptr1、ptr2,而非*
4、ptr1、*ptr2。,3。指针变量只能指向定义时所规定类型的变量。如ptr1只能指向整型变量,ptr4只能指向字符型变量。,4。定义指针变量后,并未确定该变量指向何处。也就是说变量的值是不确定的。在引用指针变量前,必须首先让它确定一个变量,这一点非常重要。,指针的赋值,指针的赋值运算就是把地址值赋值给指针变量。,指针的赋值运算可以是以下几种方式: 1。使用取地址运算符,2。把指针变量的值赋值给另一个指针变量。如: int i,*ptr1=,可不可以把a赋值给ptr,即ptr=a;?,可以!a即为数组的首地址。ptr=a;等价于ptr=,3。给指针变量赋值为赋号常量NULL,如: int *p
5、tr; ptr=NULL; 实际上NULL是一个空指针,这样的指针不指向任何变量。为了避免指针变量的非法引用,我们一般要给没有被初始化的指针变量赋值为NULL作为标志。,4。把指针变量赋值为0。如: int *ptr=0; 这里的0等价于NULL,值0是唯一能够直接赋值给指针变量的整数值。,&(取地址运算)和*(引用运算),地址运算符&的作用是取变量的地址,如: &i表示求变量i的地址。,引用运算符*的作用是取指针所指向变量的内容,例如:*ptr表示求指针ptr所指向变量的内容。,&运算和*运算是一对互逆运算。,2000,3,指针ptr,2000 i,ptr指向了i变量,*ptr表示取所指向变
6、量i的值,即3,int i=3,*ptr; ptr=,*ptr=15;,*ptr=15等价于i=15,因此改变了变量i的值。,15,例: void main() char ch=A,*ch_ptr; ch_ptr=,输出结果,ch=A,*ch_ptr=A,ch=X,*ch_ptr=X,ch的地址是0xFFF5,指针的+、-运算,指针可以加上或减去一个整数,指针的这种运算的意义和通常的数值的加减运算的意义不同。 指针的加减运算分为两种:一种是指针与整型值的加减运算,一种是指针与指针的减运算。,struct studentint number;char name4; student *pst;,指
7、针运算是地址的运算( T *px) 一: px+n, px-n 将指针从当前位置向前或向后移动n个数据单位,而不是n个字节。这取决于指针所指向的数据类型(T)。 pxn的结果为: px nsizeof(T) 二:指针与指针的加运算毫无意义。 三:指针与指针的减运算不同于简单的数值之间相减,它用来计算两指针位置之间的数据个数,而不是地址差。 px-py的结果为:(px-py)/sizeof(T) 注意:px和py必须具有相同指针类型! 四:y= px+y= ( px+ ), 注意优先级和结合顺序(*和+优先级相同且为左结合) y= + pxy= ( + px) 问题:y= px+和y= (px)
8、+的意义,int *ptr1,*ptr2; int a5=2,4,6,8,10; int x; ptr1=,2000,200C,指针的关系运算,一:是对两个相同类型的指针的运算,如pxpy,当px所指位置在py之前时,表达式的值为1,否则为0。 二:px=py 判断两个指针是否指向同一个存储单元 三:px=0, px!=0用来判断px是否为空指针 四:不同类型的指针以及指针和一般整数间的关系运算是无意义的,指针的类型转换,如果两个指针类型相同,那么可以把一个指针赋给另一个指针,否则必须用强制类型转换运算把赋值运算符右边指针的类型转换为赋值运算符左边指针的类型。,已知:float a=5.6;
9、int *ptr_a; 求:如何让整型指针ptr_a指向浮点型变量a ,如何通过指针ptr_a输出变量a的值?,void main() float a=5.6; int *ptr_a; ptr_a=(int *) ,main() int i=3; char *p1,temp; p1=(char *) ,例:如何交换一个整型变量在内存单元里的高低字节的内容。,输出结果:768,结构体类型的指针,也可以创建结构体类型的指针变量,其一般格式与创建基本类型的指针变量类似:存储类型 *; 结构体类型的指针变量访问结构体中的成员的方法与一般的结构体变量不同,其一般格式如下: ;,struct person
10、 char name10; unsigned age; unsigned long id; float salary ; ; void main() person per1, *per2= ,/(*per2).salary=2250.0,指针与数组的关系,声明了一个数组TYPE arrayn,则数组名称array就有了两重含义:第一,它代表整个数组,它的类型是TYPEn;第二 ,它是一个指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修
11、改的 ,即类似array+的表达式是错误的。,因此数组与指针有着密切关系,他们几乎可以互换,数组名就是一个常量指针。所以引用指针的概念,要表示数组元素除了可以用下标表示外,还可以用指针位移表示。,如:int array5=1,2,3,4,5,*ptr; ptr=array; 要求:输出数组array的各个元素值。,下标法: 一: for(i=0;i5;i+) coutarray i ; 二:for(i=0;i5;i+) coutptr i ;,位移法: 一: for(i=0;i5;i+) cout*(ptr+i); 二:for(i=0;i5;i+) cout*(array+i);,以上是一维数
12、组元素的指针表示法,那么二维数组元素用指针如何表示呢?是否和一维数组一样?,如:int a34=1,2,3,4,5,6,7,8,9,10,11,12; 如何用指针表示a01?,最简单的方法:int *p=则*(p+1)即表示a01,很显然数组名a是一指针,那是不是*(a+1)就表示a01呢?,我们知道二维数组可以看成特殊的一维数组,这个特殊的一维数组的元素又是一个一维数组。即a34可看成数组长度为3的一维数组,而此一维数组的每个元素又是一个数组长度为4的一维数组。如下图所示:,因此,数组名a代表这个特殊的一维数组的首地址,它指向第一个元素a0,则a+1就指向a1,即二维数组的第二行元素,这行元
13、素占4个存储单元。我们称a为行指针,同理a+1、a+2也是行指针。显然*(a+1)不表示a01。,那么如何让指针真正指到每行上的列元素呢?即我们真正关心的二维数组的每一个元素。,注意:a0对二维数组a来说是元素名,但对一维数组a00,a01,a02,a03来说却是数组名,此为它的双重身份。很显然a0就指向a00,因此a0+1才指向a01,我们称a0为列指针,同理a1、a2也是列指针。因此*(a0+1)就表示a01。,而a0用指针a可表示为*(a+0)即*a,a1表示为*(a+1)。,问题一般化:&aij=ai+j=*(a+i)+j 相对应的元素是:aij=*(ai+j)=*(*(a+i)+j)
14、 这就是二维数组元素的指针表示法。参考教材P149,可以认为:在行指针前加*,可将行指针转换成列指针。,下面我们讨论行指针、列指针的定义方法。 已知:int a34; 怎样定义列指针?输出aij?,很简单,只要注意把a的列指针表示方法赋给新的指针即可。如:int *p; p= 如何定义行指针呢?,问题稍微复杂,新的指针必须指向行,它由4个列元素组成,也就说这个指针应指向一个一维数组,如下定义: int (*p)4; /见教材P155 p=a; 或 p= 可以认为:在列指针前加&,可将列指针转换成行指针。,指针与字符串的关系,字符串在内存以字符数组存放,自然与指针相关联。,定义一:char s
15、=“ABC”;,定义二:char *s=“ABC”; s称之为字符指针变量,它指向字符串“ABC”,即s代表字符串“ABC”的首地址,亦即字符A所在内存单元的地址。,我们知道,字符串是不能直接赋值给字符数组的,要使用函数strcpy,但字符串可直接赋值给字符指针。 因此: char *s=“ABC” ;可等价为: char *s ; s=“ABC” ; 注意:s为指针,它的值只能为地址,语句s=“ABC”是表示把字符串“ABC”所在内存单元的地址赋给S,因此不可认为s的值为字符串“ABC”。 用指针表示的字符串输出同样可使用 cout指针变量名; 上例即 couts;,例 用指针实现字符串的拷
16、贝 (教材P150),void main(void) char s1=“I am a student!“; /定义数组并初始化char *s2=“You are a student!“; /定义指针并初始化char s330,s430,s530;int i;char *p1=s3,*p2=s1; /定义指针并初始化for( ; *p1+=*p2+ ; ) ; /s1拷贝给s3for(i=0;i=strlen(s1);i+) s4i=s1i; /s1拷贝给s4strcpy(s5,s2); /s2拷贝给s5 ,指针数组,我们先看一个指针定义 char *s4; 由于 比 * 的优先级高(请参考P1
17、8),因此以上定义首先定义了一个数组长度为4的一维数组,而数组元素为字符指针。我们称之为指针数组。 (注意区分 char (*s)4 与指针数组对照,我们称它为数组指针),我们可以初始化这个字符指针数组,如: char *s4=“open”,”new”,”save”,”save as”;,该指针数组元素指向不同的字符串,注意不要误认为字符串存放在该指针数组。顾名思义,指针数组只存放指针,因此该指针数组存储的实际上是各字符串的首地址。,大家还注意到各字符串的长度可以不一样,这是字符指针数组的一大特点,和二维数组相比(思考如何用二维数组存放这几个字符串?),它大大节省了内存存储空间。(为什么?),
18、二维数组表示如下: char s48=“open”,”new”,”save”,”save as”;,例 将若干个字符串按升序排序后输出(教材P152) void main(void) char *str=; /定义数组并初始化char *p1; /定义指针int i,j,k;/按升序排序(改进后的选择排序)for(i=0;i0) k=j; /k记为当前最小字符串的位置if(k!=i)p1=strk;strk=stri;stri=p1;/交换指针的指向,stri和strk均为指针 ,指针与函数,指针可以用作函数参数,这在想通过调用函数来改变参数的值很重要。,void swap(int ,int
19、); void main( ) int x1=100,x2=200; cout“交换前x1=“x1“,x2=”x2endl; swap(x1,x2); cout“交换后x1=“x1“,x2=”x2; void swap(int a,int b) int temp; temp=a;a=b;b=temp;,x1=100,x2=200,进入swap函数 x1,x2分别将值赋给a,b,100,200,100,temp=a;,a=b;,b=temp;,a,b的交换不影响x1,x2,退出swap,x1、x2未交换,void swap(int * ,int * ); void main( ) int x1=
20、100,x2=200; cout“交换前x1=“x1“,x2=”x2endl; swap( ,x1=100,x2=200,进入swap函数,分别将x1、x2的地址赋给p1,p2,100,200,100,temp=*p1;,*p1=*p2;,*p2=temp;,swap函数执行过程中p1,p2的值没有变化,退出swap函数后,x1,x2的值交换。,实际上,传址方式是一种特殊的传值方式。将指针变量的值传递给形参(当然形参也应该是指针变量),由于指针变量的值就是地址,所以这种传值方式就成为了传址方式。特别要注意,传值方式中形参的改变不会影响到实参的改变,这一点传址方式也不例外!上例中之所以能交换实参
21、所指向的两个数,是因为在函数中交换的是形参所指向的变量的值,由于形参和实参指向同一个变量,因此实参指向变量最终交换,但实参的值却没有变化。,void swap(int * ,int * ); void main( ) int x1=100,x2=200,*pt1= ,int max,min; void max_min_value(int *p,int n) int i; max=min=*p; for(i=1,p+;imax) max=*p; else if(*pnumber+i; max_min_value(number,10); cout“max=“max“min=”min;,例:从10个
22、数中找出其中的最大值和最小值。,例:有一个班,3个学生各4门成绩,要求查找有成绩不及格的学生,并输出该学生的全部成绩。,void search(float *p,int m,int n) int i,j,flag; for(i=0;im;i+) flag=0; for(j=0;jn;j+) if(*(p+i*n+j)60) flag=1;break; if (flag=1) printf(“nNO.%d fails,his score:n”,i ); for(j=0;jn;j+) printf(“%8.1f ”,*(p+i*n+j); main( ) float score34=; . sea
23、rch(score0,3,4);,void search(float (*p)4,int m) int i,j,flag; for(i=0;im;i+) flag=0; for(j=0;j4;j+) if(*(*(p+i)+j)60) flag=1;break; if (flag=1) cout“nNO.”i“fails,his score:n”; for(j=0;j4;j+) cout*(*(p+i)+j); main( ) float score34=; . search(score,3);,例:制作一个简单的菜单。,void menu( char *p , int m) int i; s
24、ystem(“cls” ); for(i=0;im;i+) coutpi; cout“n Enter a choice:”; main( ) char *p4=“Open”,”New”,”Save”,”Save as”; menu(p,4); ,返回指针的函数,void strcpy(char s1,char s2) int i=0; while(s2i!=0) s1i=s2i; i+; s1i=0; ,char *strcpy(char *s1,char *s2) int i=0; while(*(s2+i)!=0) *(s1+i)=*(s2+i); i+; s1i=0; return s1
25、; ,注意:返回指针的函数,其函数体中的return语 句的参数必须为全局变量的指针、静态变量指针 或形参的指针(如果形参为指针类型或引用类型 的话),不能为局部变量指针。 返回的是变量地址,必须保证函数返回后,这个变量仍然存在。要返回函数中局部变量的地址,应声明为静态的。,char *string_name ( int n ) static char * string = “illegal string”,“string 1”,“string 2”,“string 3” return ( n3 ) ? string0: stringn;,例 将输入的一个字符串按逆序输出(教材P164)cha
26、r *flip(char *ptr) /指针函数 char *p1,*p2,temp; p1=p2=ptr; /初始化指针变量,p1、p2指向字符串的起始while(*p2+) ; /p2指向字符串结束符的后一个位置p2=p2-2; /p2回退两个位置,指向字符串的最后一个字符while(p1p2) /指针的比较和内容的交换temp=*p2; *p2-=*p1; /从字符串的两边向中间进行交换*p1+=temp;return ptr; /返回指针 ,例 设计字符拷贝和拼接的函数(教材P165)char *copy(char *to,char *from) /字符拷贝指针函数 char *p=t
27、o; while(*to+=*from+) ; /内容拷贝return p; /返回指针 char *stringcat(char *to,char *from)/字符拼接指针函数 char *p=to; while(*to+) ; /指针指向结束字符后一个字符to-; /指针减1, 指向结束字符while(*to+=*from+) ; /内容拼接return p; /返回指针 ,指向函数的指针,函数名实际上是完成函数任务的代码所在内存中的起始地址(函数入口地址),亦即函数名象数组名一样,本身就是一个指针,因此函数名也可作为函数的参数。,函数名作为实参很简单,直接使用函数名即可,但由于函数名本
28、身就是一个指针,所以它作为形参时必须使用指针定义。 如:int max(int , int); 则指向函数max的指针可定义为: int (* max_ptr)(int , int); 注意与int *max_ptr(int , int) 区分,它是一个返回指针的函数。,函数指针常用在所谓较简单的菜单驱动系统中,每一个菜单选项对应一个功能即一个函数,由于菜单不只一项,所以要用到指向函数的指针数组。如: int (*function3)(int ,int );,使用指向函数的指针数组的一个限制是所有的指针都必须是同一个类型,指针指向的函数必须具有相同类型的返回值,并且它们的参数类型也相同。,对于
29、上例如果有:int max(int , int ); int min(int , int ); int average(int , int ); 则上例的指针数组可初始化为: int (*function3)(int , int )=max , min , average;,二级指针,void main() char *p1=“Nanjing”; char *p2; p2= ,void main() int a4=1,2,3,4; int (*p)4,i; p= ,总结:如何判断指针类型?,要弄懂指针就要弄懂3个问题:指针的类型、指针所指向的类型、指针的值。,int *p; int * int
30、 float *p; float * float int *p; int * int * int (*p)3 int (*)3 int () 3 int 3 int *p3 指针数组 int (*p)( ) int (*)( ) int ( ) int *p( ) 返回指针的函数,动态分配内存空间,new运算符可以为所创建的指针变量动态地分配存储空间,而运算符delete则用于释放动态分配的存储空间。,使用new运算符的一般格式为:=new (初始值);或=new ; 第一种形式为所指向的数据分配大小为sizeof()个字节的连续存储空间,初始值表示为所分配的存储空间指定初始值。第二种形式为所
31、指向的数据分配指定大小的数组空间,为整型变量或常量。,对于上述两种情形,如果动态分配不成功,则new运算符返回NULL(0);如果成功分配,则new运算符返回所分配的存储空间的首地址,并将该地址赋给。使用delete的一般格式为:delete ;或delete ;第一种格式为将动态分配给的内存空间归还给系统,第二种格式为将动态分配给的数组空间归还给系统。,#include void main() int *p1,*p2; float *p3;p1=new int; *p1=2; cout*p1n;p2=new int(4);*p2=6; cout*p2n; p3=new float(5.2);
32、 cout*p3n; delete p1; delete p2;delete p3;p1=new int(6); cout*p1n; delete p1; ,一般动态分配,#include #include void main() int *p4, n; char *p5;n=4; p4=new intn; / 动态分配一维数组for(int i=0;in;i+) p4i=2*i;for(i=0;in;i+) coutp4it;p5=new char10;strcpy(p5,“abcdefg“); coutp5n;delete p4;delete p5; ,一维数组动态分配,#include
33、#include void main() int (*p6)4;p6=new int24;for(int i=0;i2;i+) for(int j=0;j4;j+) p6ij=i+j; coutp6ijt;coutn;delete 2p6;,二维数组动态分配,注意,在用new运算符为某个指针变量所指向的数据动态分配存储空间之后,必须用delete运算符撤消,否则该存储空间将一直被占用,直到关闭电脑。,用new和delete还可以为结构类型或其它用户自定义类型的指针动态分配内存空间。,struct node int x; float y; char z10; ; void main() node
34、 *pn; pn=new node; pn-x=2; pn-y=4.3; strcpy(pn-z,“China!“); coutxyzn; delete pn; ,在使用new和delete运算符时,要注意如下几点:(1) 如果new运算符动态分配内存失败(内存中没有符合要求的空闲的连续内存单元),则返回NULL(0),此时指针为空指针。此时,要进行空指针判断处理。,float *fp; fp=new float5000; if (fp= =0) cout“动态分配内存失败,程序终止运行!n”; exit(3); ,(2) 动态分配存放数组的内存空间时,不能在分配空间的同时进行初始化。如: i
35、nt *pi=new int81,2,3,4,5,6,7,8;r,(3) 可以用new运算符动态分配多维数组空间,但要注意的是,它返回的是行指针。当撤消动态分配二维数组空间时,一定要指明数组的行数,否则只能撤消第0行的内存空间。,float *pf1; pf1=new float35; /错误!类型不匹配。 float (*pf)5; pf=new float35; delete 3pf; /注意pf前的行数不可省略!,(4) 动态分配的二维数组的列向量要与指向数组的指针变量中的数组大小一致,而行向量可为任意的正整数。如: float (*pf)5; pf=new float36; /错误!类
36、型不匹配。,引用,引用是另一个变量别名,或另一个变量的同义词,引用变量依附于另一个变量而定义。定义引用类型变量的一般格式为:,在定义一个引用变量时,编译系统并不会为其单独分配存储空间,因此必须对它进行初始化,将它与某个已定义的同类型的变量相关联。在定义一个引用变量之后,它与所引用的变量共享同一个内存空间,并随着所引用的变量的撤销而撤销,因此对引用的任何操作都是对它所关联的变量的操作。,void main( ) int k, ,0x2000,0x2000,100,100,300,300,500,500,500,0x2000,0x2000,0x1FF8,void fun( ) int i=10;
37、static int ,11,11,对于引用类型的变量时,要注意如下几个问题:1. 在定义引用类型的变量时,必须用同类型的已定义的变量对它初始化,不同类型的变量以及常量不可对它初始化!如:,int /错误,不可用常量初始化,2. 可以将引用与指针变量所指向的数据相关联,也可以将引用与数组中的元素相关联。如: Int *pi=new int(3); int ,3. 可以定义指针类型的引用。 int *pi=new int; int * ,4. 可以定义对引用类型变量的引用,但不能定义引用的引用,也不能定义引用指针和引用数组。如: int I , /错误,不能定义引用数组,引用和函数,参数的三种传
38、递方式:值传递、地址传递、引用传递 。,当函数的形参为引用类型时,调用该函数的方式称为引用传递。,引用传递具有某些与地址传递相似的特征,即引用类型的参数既可以作为输入参数,也可以作为输出参数。对引用类型的参数的操作实际上就是对传递给它的实参的操作,而不需要将实参拷贝一个副本给形参,这是与其它两种传递方式相区别的重要特征。因为从程序的执行效率上看,引用作为参数,在运行过程中可以节省资源。通常将占用存储空间较大的类型的形参设置为引用类型。,void swap(int ,2,4,4,2,注意:在调用参数为引用类型的函数时,引用类型的形参所对应的实参必须为变量。,函数的返回值也可以是引用类型,此时该函
39、数的返回值一定是某个变量的引用,对这种函数的调用即可作为某个运算符的左操作数,也可作为右操作数。,int number1; int ,5,5,7,7,int ,6,int ,4,3,2,3,10,2,10,与指针作为函数的返回类型的要求类似,当函数的返回值为引用类型时,其函数体中的return语句的参数必须为全局变量、静态变量 或形参(如果形参为指针类型或引用类型的话),不能为局部变量。,空指针与void指针 空指针:其地址值为0,用符号常量NULL来表示,不指向任何存储单元。(地址0这一存储单位不能另做他用)void指针:无类型指针,可用来指向任何类型的数据,在数据操作时通常要进行强制类型转
40、换。,其它类型的指针,const类型(常值)指针,回顾:一般常量的定义const float pi=3.1415926;,常值三种含义 1.指针所指向的数据为常值(const放在*之前,不必初始化) 定义方式 const char *s=“Hello!“;/ const char *s; s=“Hello!“; 或char const *s=“Hello!“; 操作: 不可改变指针所指的数据:*s=i;/错 可改变指针本身的值:s= “Hi!”;/对,2.指针本身为常值 定义方式:const放在变量名之前,必须初始化。 char *const s= “Hello!“; 可以改变指针所指的数据 *s=i; 不可改变指针本身的值:即不可让指针指向别的地方。 s= “Hi!“; /error 3.指针本身和指针所指向的数据都禁止改变 char const *const s= “Hello!“; 或const char *const s=“Hello!“;,课后练习:试卷汇编 P6T4、5,P7T7、8,P8T9 P15T22、26,P16T1、4、6 P17T9,P18T11 P23T26,P24T6 P27T12 P32T25 P33T2、6,P35T12 P40T23,P41T25、28 P42T9,P45T12 P52T28 P53T4、6,