1、指针,指针的故事,“该程序执行了非法操作,即将关闭”这种错误几乎全是由指针和数组导致的黑客攻击服务器利用的bug绝大部分都是指针和数组造成的,主要内容,1.地址和指针的概念 2.变量的指针和指向变量的 指针变量 3.数组与指针 4.字符串与指针 5.指向函数的指针 6.返回指针值的函数 7.指针数组和指向指针的指针,地址,内存中每一个字节的存贮单元都有一个编号,这个编号就是“地址” 。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配存贮单元。,.按变量地址存取变量值的方式称为“直接访问”方式 (,); (,); ;,将每个数据的首字节地址作为该数据的地址。,内存是一个连续
2、编址的空间,每一个存储单元都有一个固定的编号,这个编号称为地址。 地址是一个无符号整数(通常用16进制数),其字长与主机相同,例如:已定义了一个整型数组d6。设该数组的起始地址为ee10(d0的地址),赋值d0=10。数组名是一个常量地址 , 也为ee10,2000 2002 2004,用户内存数据区,(1) 求地址运算(&)每个变量/数组都有一个内存地址,在何地址一般无法预测。可通过求地址运算得到。,2、求地址运算和访问地址运算,例:#include main()int a=1;float b=2.0;double c=3;int d6=-1,-2,-3,-4,-5,-6;printf(“a
3、ddress of a is%xn”,说明:输出的地址值 用4位十六进制数表示。&只能施加在变量或数组元素上。 &25 &(x+y)均是错误的,采用访问地址的方法存取数据,速度快,效率高。,将地址存放在一种特殊类型的变量中,把对地址的操作转换为对变量的操作。,存放地址的变量指针变量指针变量中存放的地址指针,变量地址:变量一定占用一个数据的存储空间,该存储空间的首地址称变量地址。指针:一个变量的地址称为该变量的指针。指针变量:若一个变量专用于存放另一个变量 的地址(指针),则该变量称为指 针变量。指针的对象:指针指向的存储空间中的数据。,指针的概念,指针变量是存放地址的变量,它的值是指针,指针所
4、指向的变量是指针变量所指向的存贮单元中的内容,指针变量,指针所指向的变量,10.1 定义一个指针变量,定义指针变量的一般形式为基类型 *指针变量名;int i,j,*pointer_1,*pointer_2;/基类型是int下面都是合法的定义:float *pointer_; char *pointer_;,指针变量的引用 (1)指针变量只能存放地址,不能将整型数据、字符型数据、实型数据等非地址类型的数据赋值给指针变量。 (2)指针变量只能指向同一类型的变量。 int a=8 ;char h=A; int *p1; char *p2; p1=,int a ,*p ; p = *p=5;,(1)
5、 &a 表示取a变量的地址。 *p则表示指针变量p所指的对象a 。,*p,*&a,&*p,相关的运算符 取地址运算符: & 指针运算符(或间接访问运算符): *,(2) , (5)(*p )+ 等价于a+。 (6) &与*互为逆运算,当与&连续在一起时,具有抵消作用: *&a= =a , &*p= =p .,7、指针变量的赋值运算用取地址运算符(, 将一个指针变量赋给另一个同类型的指针变量。,int i , pi , pj ;,pi=,pj=pi ;,作用是使 pi , pj 都指向变量 i。,&i,&i, 给指针变量(pi)赋“空”值:,a. pi=NULL;,b. pi=0,c. pi=0
6、, 在定义一个变量时,将变量的指针(即地址) 直接赋给指针变量:,int x, p=,可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个与基类型相同的变量。例如:pointer_;pointer_;,在定义指针变量时要注意两点:,10.2.2 指针变量的引用,1.给指针变量赋初值 int *p,*q; int a=10; (1)q=/让p指向q所指向的存贮单元,a,q,10,10.2.2 指针变量的引用,1.给指针变量赋初值 int *p,*q; int a=10; (1)q=/让p指向q所指向的存贮单元,a,q,10,10.2.2 指针变量的引用,1.给指针变量赋初值 int
7、 *p,*q; int a=10; (1)q=/让p指向q所指向的存贮单元,a,q,p,10,注意:指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量。,2. 访问指针变量所指向的变量printf(“%d”,*p);说明:(1)定义指针变量后,变量没指向任何存贮单元,要通过赋值语句来确定它所指向的存贮单元(2)&,*的优先级相同,结合方向自右向左,为互逆操作&*p p,*&a a,(3)(*p)+与*p+的区别 前者是为p所指向的变量+1,后者满足右结合,p+后执行*操作,但+是后加,所以先使用*p的值,然后再对p进行+1操作 *(+p)?,例10.1
8、 通过指针变量访问整型变量#include void main() int a,b; int *pointer_1,*pointer_2; a=100; b=10; pointer_1=,100,10,例10 . 2 输入和两个整数,按先大后小的顺序输出 和。,#includemain()int a,b,*p1,*p2,*p;scanf(%d%d,例10 . 2 输入和两个整数,按先大后小的顺序输出 和。,#includemain()int a,b,*p1,*p2,*p;scanf(%d%d,a,b的值没发生变化!,p=p1; p1=p2;p2=p;,&a,p=p1; p1=p2;p2=p;,
9、&a,&b,p=p1; p1=p2;p2=p;,&a,&b,&a,p=p1; p1=p2;p2=p;,&b,&a,p=p1; p1=p2;p2=p;,*p1=9, *p2=5a=5, b=9,10.3 指针变量作为函数参数,例10 . 3 交换两个变量的值,#includemain()void swap(int *p1,int *p2); int a,b; int *pointer_1,*pointer_2; scanf(%d%d,void swap(int *p1,int *p2)int temp;temp=*p1;*p1 = *p2;*p2 =temp;,void swap(int *p1
10、,int *p2)int temp;temp=*p1;*p1 = *p2;*p2 =temp;,void swap(int *p1,int *p2)int temp;temp=*p1;*p1 = *p2;*p2 =temp;,void swap(int *p1,t *p2)int temp;temp=*p1;*p1 = *p2;*p2 =temp;,如果将swap函数做如下修改:,void swap(int *p1,int *p2)int *temp; temp=p1; p1 = p2; p2 =temp;,如果将swap函数做如下修改:,void swap(int *p1,int *p2)i
11、nt *temp; temp=p1; p1 = p2; p2 =temp;,没有实现交换操作!,void swap(int *p1,int *p2)int *temp; temp=p1; p1 = p2; p2 =temp;,void swap(int *p1,int *p2)int *temp; temp=p1; p1 = p2; p2 =temp;,void swap(int *p1,int *p2)int *temp; temp=p1; p1 = p2; p2 =temp;,&a,&b,9,5,9,5,5,9,使用指针做形参的说明,如果要通过函数调用得到n个要改的值,要经过以下步骤:(1
12、)主函数中设n个变量,并用n个指针变量分别指向它们(2)用指针变量作形参(3)通过形参指针变量,改变n个实参的值(4)主调函数中使用这些改变了值的变量,上节课小结,1.指针与指针变量2.指针所指向的变量3.用指针访问其所指向的变量4.用指针变量做为函数的形参,int *p,i;p= *pi,*pvoid swap(int *p,int *q),例10. 输入, 3个整数,按从大到小顺序输出,#include void main() void exchange(int *q1, int *q2, int *q3); int a,b,c,*p1,*p2,*p3; scanf(%d%d%d, ,vo
13、id exchange(int *q1, int *q2, int *q3) void swap(int *pt1, int *pt2); if(*q1*q2) swap(q1,q2); if(*q1*q3) swap(q1,q3); if(*q2*q3) swap(q2,q3); void swap(int *pt1, int *pt2) int temp; temp=*pt1; *pt1=*pt2; *pt2=temp; ,10.3 数组与指针,一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把
14、某一元素的地址放到一个指针变量中)。所谓数组元素的指针就是数组元素的地址。,定义一个指向数组元素的指针变量的方法,与以前介绍的指向变量的指针变量相同。例如:int a10; int *p; a0;,10.3.1 指向数组元素的指针,p=a;,p+i,&ai,*( ),10.通过指针引用数组元素,引用一个数组元素,可以用:() 下标法,如ai形式或pi(但要先执行p=a );() 指针法,如*(a+i)或*(p+i)。其中是数组名,是指向数组元素的指针变量,其初值。( 3 )p=a; p+,用*p访问数组元素,说明: ( 1 )指针可以实现+和-操作,但数组名不可以,因为数组名是一个指针常量 (
15、 2 )p+代表指针移向下一个数组元素,而不是一个存贮单元,int型下移两个单元,float型下移4个单元,假设有一个数组,整型,有个元素。要输出各元素的值有三种方法: (1)下标法。(2) 通过数组名计算数组元素地址,找出元素的值。(3) 用指针变量指向数组元素。,例10.5 输出数组中的全部元素。,(1)下标法。main()int a10;int i;for(i=0;i10;i+) scanf(“%d”, ,(1)下标法。main()int a10;int i, *p;for(i=0;i10;i+) scanf(“%d”, ,(2) 通过数组名计算数组元素地址,找出元素的值。main()i
16、nt a10; int i;for(i=0;i10;i+) scanf(“%d”, ,(2) 通过数组名计算数组元素地址,找出元素的值。main()int a10; int i ,*p;for(i=0;i10;i+) scanf(“%d”, ,(3) 用指针变量指向数组元素。main()int a10; int *p; int i;for(i=0;i10;i+) scanf(%d,(4)用-操作实现逆向输出元素,void main()int a10; int *p; int i;for(i=0;i=a;p- ) printf(%5d,*p);,例10. 通过指针变量输出数组的个元素。,main
17、()int a10, *p, i; p=a;for(i=0;i10;i+) scanf(%d,p+);for(i=0;i10;i+,p+) printf(%dn,*p);,main()int a10, *p, i; p=a;for(i=0;i10;i+) scanf(“%d”,p+);p=a;for(p=a;i10;i+,p+) printf(“%d”,*p);,10.3.3 用数组名作函数参数,在第8章8.7节中介绍过可以用数组名作函数的参数如: void main() (int arr ,int ); int array; (array,); void (int arr,int ) ,例1
18、0 将数组中个整数按相反顺序存放。,main()int a10=1,2,3,4,5,6,7,8,9,10;int i;inv(a,10);for(i=0;i10;i+) printf(%3d,ai);printf(n);,void inv(int x,int n)int *i,*j;int temp,m;m=n/2;i=x; j=x+n-1;for(;ix+m;i+,j-)temp=*i;*i=*j;*j=temp;,对刚才的程序可以作一些改动。将函数inv中的形参改成指针变量。,void inv(int *x,int n)int temp,*i,*j,m=(n-1)/2;i=x;j=x+n-
19、1;for(;i=x+m;i+,j-) temp=*i; *i=*j; *j=temp;,如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下种情况:,(1) 形参和实参都用数组名,如:void main() void (int ,int ) int ; (,); ,(2) 实参用数组名,形参用指针变量。如:void () void (int *,int )int ; (,); ,(3)实参形参都用指针变量。例如:void main() void (int *,int )int , *p=a; (p,); ,(4) 实参为指针变量,形参为数组名。如: void ma
20、in() void (int x ,int ) ,*p=a; (p,); ,例10. 从10个数中找出其中的最大值和最小值,int max,min;void max_min(int array,int n)int *p,*array_end;max=min=*array;array_end=array+n;for(p=array+1;pmax) max=*p;else if (*pmin) min=*p;,main()int a10=6,7,8,1,2,3,4,5,9,10;max_min(a,10);printf(max=%3d,min=%3dn,max,min);,10.3.4 多维数组与
21、指针,用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。,复习一维数组与指针之间的关系,int a10,*p; p =a; p =,p,p+i,1. 多维数组元素的地址,先回顾一下多维数组的性质,可以认为二维数组是“数组的数组”,例 :定义int a34=1,3,5,7,;则二维数组a是由3个一维数组所组成的。,a0,a1,a2,a,a+1,a+2,a+i = &ai,(1)a,a+1,a+2分别代表&a0,&a1,&a2,a0,a1,a2,a+i = &ai,(2)a0,a1,a2分别代表&a00,&a10,&a20
22、,a0,a1,a2,a,a+1,a+2,a+i = &ai,a2+1,a2+2分别代表&a21,&a22,ai+j,(3)&aij,a0,a1,a2,a,a+1,a+2,a+i = &ai,*(a+i)+j,&aij,*(a+i) = ai,ai+j,a0,a1,a2,a,a+1,a+2,a+i = &ai,*(a+i)+j,(4)&aij,*(a+i) = ai,a0,a1,a2,a,a+1,a+2,a+i = &ai,*(*(a+i)+j),(5)*(&aij),*(a+i) = ai,a0,a1,a2,a,a+1,a+2,a+i = &ai,*(*(a+i)+j),(5) aij,*(a
23、+i) = ai,a0,a1,a2,a,a+1,a+2,a+i与*(a+i)有什么区别?,小结,(1)a,a+1,a+2分别代表&a0,&a1,&a2(2)a0,a1,a2分别代表&a00,&a10,&a20(3) ai+j 代表&aij(4) *(a+i)+j代表&aij(5)*(*(a+i)+j)代表aijai从形式上看是一维数组的第i个分量,但当a为二维数组时,ai只代表一个地址,2.指向二维数组的指针变量,(1)用指针变量输出数组元素的值 main()int a34=1,2,3,4,5,6,7,8,9,10,11,12; int *p;/printf(%on%on%on%on,a,a0
24、, 用普通的指针变量输出数据元素时,要先二维数组序列化为一维,然后再输出.接下来定义提向一维数组的指针变量.,(2)指向一维数组的指针变量,指向一维数组的指针变量的声明形式: int (*p)4; p可以指向由四个分量组成的一维数组,p每次+1,移动的单元数是一维数组所占的单元数,它等同于二维数组的行移动. 因此,p等价于二维数组。,例,输出二维数组中任一行任一列元素的值main()int a34= 1,2,3,4,5,6,7,8,9,10,11,12;int i,j,(*p)4;p=a;scanf(%d%d,3.用指向数组的指针作函数参数,一维数组名可以作函数参数,多维数组名也可以做为参数传
25、递。用指针变量做形参时接收实参数组名传递来的地址时,有两种方法:指向变量的指针变量,实参为 *数组名指向一维数组的指针变量,实参为 数组名,例,有一个班有3名学生,4门课程,计算总平均分以及查找第n个学生的成绩。 main()void average(float *p,int n);void search(float (*p)4,int n);float score34=65,67,70,60,80,87,90,81,90,99,100,98;average(*score,12);search(score,2);,void average(float *p,int n)int i;float
26、*p_end;float sum=0.0,avg;p_end=p+n-1;for(;pp_end;p+)sum=sum+(*p);avg=sum/n;printf(average=%5.2fn,avg);,void search(float (*p)4,int n)int i;printf(the score of number %d student is n,n);for(i=0;i4;i+)printf(%5.0f,*(*(p+n)+i);,例,在上题的基础上,查找有一门以上课程不及格的学生,打印出他们的全部课程成绩,void search(float (*p)4,int n) int i
27、,j,flag; for(i=0;in;i+) flag=0; for (j=0;j4;j+) if (*(*(p+i)+j)60) flag=1;,if (flag=1) printf(number %d student fails:n,i); for(j=0;j4;j+) printf(%5.1f, *(*(p+i)+j); printf(n); /if /for,10. 字符串与指针,1.字符串的表示形式C语言用两种方式处理字符串字符型数组 main()char str=”I love China”; printf(“%sn”,str);字符指针 main()char *str=”I l
28、ove China”;printf (“%sn”,str); ,说明:(1)char *str=”I love China”;等价于char *str;str=”I love China”;把字符串的首地址赋给str,不等价于char *str;*str=”I love China”;,(2)用字符型指针变量或字符数组表示的字符串可以用%s整体输入输出,其他类型的指针变量或数组不可以,只能逐个分量操作。,例,将字符串a复制到字符串b main()char a15=I am a boy; char b15;int i;for(i=0;*(a+i)!=0;i+) *(b+i)=*(a+i);*(b
29、+i)=0;for(i=0;bi!=0;i+) printf(%c,bi);/printf(%s,b);,用指针变量处理上题 main()char a=”I am a boy”,b20,*p1,*p2;int i;p1=a;p2=b;for(;*p1!=0;p1+,p2+) *p2=*p1;*p2=0;printf(“%s”,b);,2.字符串指针作函数参数,字符串数组或指针做函数参数可以实现地址传递(1)用字符数组做参数 void copy_str(char from,char to) int i=0; while (fromi!=0) toi=fromi; i+; toi=0;,(2)形参
30、用字符指针变量 void copy_str(char *from,char *to) for(;*from!=0;from+,to+) *to=*from; *to=0; ,对上述函数体可有多种写法,(1)while(*to=*from)!=0) to+;from+(2)while(*to+=*from+)!=0);(3)while (*from!=0) *to+=*from+;*to=0;(4)while(*to+=*from+);它等价于 while(*to+=*from+)!=0);(5)for(;(*to+=*from+)!=0;);或(6)for(;*to+=*from+;);,形参
31、为数组时,也可以用指针变量 void copy_str(char from,char to) char *p1,*p2; p1=from; p2=to; while(*p2+=*p1+)!=0); 形参和实参都可以是字符数组或字符指针变量,很灵活,3.对字符指针变量和字符数组的讨论,(1)字符数组的每个元素存放一个字符,字符指针变量只存放字符串的首地址,而不是将字符串放到字符指针变量中(2)赋值方式,对字符数组只能对各元素赋初值,字符指针变量可以整体赋值 char str20; str=”I love China”;/错误的 上述两语句不等价于:char str20=”I love China
32、”,char *a;a=”I love China”; /合法的上面两语句等价于:char *a=”I love China”; (3)定义了字符数组后,编译时对数组分配单元,而字符指针变量不分配单元,要人为赋初值(通常指向某一数组首址) char *s; scanf(“%s”,s);/语法上没有错误,但危险 通常的做法: char *s,str10; s=str; scanf(“%s”,s);,(4) 指针变量的值可以改变,而数组名是常量,不可以改变其值 char *a=”I love China”; a=a+7; printf(“%s”,a);结果是China,字符指针变量输出字符串时从当
33、前位置开始,输出到0为止。(5) 定义了一个指针变量并使它指向一个字符串后,就可以用下标形式引用其中的字符。 char *a=”I love China”;ai,10.5 指向函数的指针,1.用函数指针变量调用函数 一个函数在编译时初分配一个入口地址,这个地址就称为函数的指针,可以用一个指针变量指向函数,然后通过该指针变量调用此函数。函数名代表该函数的入口地址,main() int max(int,int); int (*p)();/定义指向整型函数的指针 int a,b,c; p=max; scanf(%d%d,(1)指向函数的指针变量的一般定义形式: 数据类型 (*指针变量名)();(2)
34、函数调用可以通过函数名,也可以通过函数指针调用(3)(*p)()只是定义了一个指向函数的指针变量,但指向哪一个并不确定;在一个程序中,一个指针变量可以先后指向返回类型相同的不同函数(4)给函数指针变量赋初值时,只给函数名不给参数(5)调用函数时,用(*p)取代函数名,实参照常(6)对指向函数的指针变量进行加减运算没有意义,如p+,2.用指向函数的指针作函数参数,函数指针变量的主要用途是做函数参数。形参是指向函数的指针变量,实参是函数名,结合的过程就是将函数的地址传给形参。通过用函数指针变量做函数形参,可以实现灵活调用不同的函数,提高函数的通用性。 例,设一个函数process,在调用它的时候,
35、每次实现不同的功能。输入a,b后,第一次调用时找出a和b中的大者,第二次找出小者,第三次求a+b;,main() int max(int,int); int min(int,int); int add(int,int); int process(int,int,int(*fun)(); int a,b; scanf(%d%d,printf(max=);process(a,b,max);printf(min=);process(a,b,min);printf(add=);process(a,b,add);,process(int x,int y,int(*fun)() int result; r
36、esult=(*fun)(x,y); printf(“%dn”,result);,说明:主函数中对max(),min(),add()等三个函数的声明是必不可少的,以前函数不声明,隐含为函数类型为整型,但在此max,min,add做为函数参数进行调用时,只给函数名而没有函数的实参及括号,所以编译系统些时不易区分它们是变量还是函数名,以前做为函数调用时,后面的括号及参数可以告知编译系统它们是函数名。,10.6 返回指针值的函数,函数的类型为指针类型。一般定义形式为:类型名 *函数名(参数列表)int *a(int x,int y) 调用a后能得到一个指向整型数据的指针。()的优先级高于*,所以这是
37、函数形式,a前的*代表此函数是指针函数,例,有若干名学生,4门课,要求在用户输入学生序号后,能输出该学生的全部成绩,用指针函数实现。定义函数search,返回第n个学生的首门课的地址(列) float *search(float (*pointer)4,int n)float *pt;pt=*(pointer+n);return pt;,main()float score4=60,70,80,90,56,89,67,88,34,78,90,6;float *search(float (*pointer)4,int n);float *p;int j,n;scanf(%d,例,对上例中的学生,找
38、出其中有不及格课程的学生及其学号 main()float score4=60,70,80,90,56,89,67,88,34,78,90,6;float *search(float (*pointer)4);float *p;int i,j;,for(i=0;i3;i+) p=search(score+i); if (p=*(score+i) printf(No.%d scores:,i); for (j=0;j4;j+) printf(%5.0f,*(p+j); printf(n); /end_if /end_for,float *search(float (*pointer)4) int
39、i; float *pt; pt=*(pointer+1); for(i=0;i4;i+) if (*(*pointer+i)60) pt=*pointer; return pt;,该函数的作用是检查一个学生是否有不及格的课程,先让指针pt指向下一个学生的第一门课,如果该学生有不及格的课程,则让指针pt指回该学生的每一门课,便于主函数进行判断。,10.7指针数组和指向指针的指针,1.指针数组的概念元素均为指针类型的数组,称为指针数组。一般定义形式:类型名 *数组名数组长度; int *p4; 的优先级高于*,所以p先与结合,形成数组,然后再与*结合,*表示此数组是指针类型,每个元素都可指向一个
40、整型变量,指针数组比较适合处理若干字符串,使字符串处理更加方便灵活。比如,处理图书馆中的图书名字,由于书名长度不同,所以用指针数组比较方便,每个元素是个字符串指针。当要对字符串进行排序时,可以不改变字符串本身的存贮,只改变指针值即可,可节约时间。,例,将若干字符串按字母顺序(由小到大)输出 void sort(char *name,int n)char *temp;int i,j,k;for(i=0;in-1;i+) k=i; for (j=i+1;jn;j+) if (strcmp(namej,namek)0) k=j; if (k!=i) temp=namei; namei=namek; namek=temp; ,