1、第十章第十章 指针指针 指针指针: :C C的一个的一个重要概念重要概念、重要特色重要特色。它使。它使C C具备了强大的功能,使具备了强大的功能,使C C成为程序设计语言之首。成为程序设计语言之首。正确而灵活地运用它,就可以方便地处理很多正确而灵活地运用它,就可以方便地处理很多其它高级语言所不能处理的问题。其它高级语言所不能处理的问题。 不掌握不掌握指针指针等于没有掌握等于没有掌握C C语言的语言的精华精华。 10 101 1 指针的概念指针的概念 简单地说,简单地说,指针就是地址指针就是地址。 要掌握指针的概念就必须弄清:要掌握指针的概念就必须弄清: 内存地址概念内存地址概念? ? 变量与地
2、址的关系变量与地址的关系? ? 如何通过地址进行变量的存取如何通过地址进行变量的存取? ?说明例:说明例: 内存用户数据 1000 3 i 1002 6 j 1004 9 k对变量值的存取总是按地址进行的-直接访问。 int i,j,k;i=3; j=6;k=i+j;程序经编译后程序经编译后,变量名就不,变量名就不复存在,以地复存在,以地址对应。址对应。也可以采用也可以采用“间接访问间接访问”方式:方式: 先将变量先将变量i i的地址存放到另一变量的地址存放到另一变量p1p1中,中,要访问要访问i i时,先取出时,先取出p1p1的内容(变量的内容(变量i i的地址),的地址),再去访问该地址所
3、对应的内存单元中的内容再去访问该地址所对应的内存单元中的内容(变量(变量i i的值)。的值)。 内存用户数据 1000 3 i 1002 6 j 1004 9 k 2000 1000 p1 2004 1002 p2int i,j,k;i=3; j=6;k=i+j;int *p1, *p2;p1=&i;p2=&j;在以上概念的基础上对指针下定义:在以上概念的基础上对指针下定义:变量的地址就是该变量的变量的地址就是该变量的指针指针。存放地址的变量称存放地址的变量称指针变量指针变量。p1p1是是指向变量指向变量i i的指针变量的指针变量。10001002100410001002ijkp1p2 10
4、102 2 变量的指针和指向变量的指针变量变量的指针和指向变量的指针变量 变量的指针变量的指针 指针变量指针变量 指向变量的指针变量指向变量的指针变量 用用“* *”代表代表“指向指向” 如如* *p1p1代表它所指向的变量代表它所指向的变量i i,同一内存单元。,同一内存单元。 以下两个语句等价:以下两个语句等价: i=3; i=3; 直接访问直接访问 * *p1=3; p1=3; 间接访问间接访问 内存用户数据 1000 3 i 1002 6 j 1004 9 k 2002 1000 p1 2004 1002 p2int i,j,k;i=3; j=6;k=i+j;int *p1, *p2;
5、p1=&i;p2=&j;*p1=3; 指针变量的定义指针变量的定义 指针变量也必须先定义后使用。指针变量也必须先定义后使用。 intint * *p1;p1;注意:注意: * *表示该变量为指针变量,但变量名是表示该变量为指针变量,但变量名是p1p1。 一个指针变量只能指向同一类型的变量。一个指针变量只能指向同一类型的变量。 intint i, i,* *p1;p1; float a; float a; p1=&i; p1=&i; 合法合法 p1=&a; p1=&a; 不合法不合法 指针变量的引用指针变量的引用 两种用法两种用法: 用地址运算符用地址运算符& & p1=&i p1=&i; 用指
6、针运算符用指针运算符* * (实行间接访问)(实行间接访问) * *p1=100; k=p1=100; k=* *p1;p1; 注意:注意:指针变量只能放地址(指针)。指针变量只能放地址(指针)。 p1=100; p1=100; 不允许不允许例:例:main() int a=100,b=10; int *p1,*p2; 定义指针变量,尚无体定义指针变量,尚无体指向指向 p1=&a; p1p1指向指向a a p2=&b; p2p2指向指向b b printf(“n %d,%d”,a,b); printf(“n %d,%d”, *p1,*p2); 注意:要区别定义和引用中的“*”要特别注意以下用法
7、的后果:要特别注意以下用法的后果: int *p1; *p1=100;例:输入例:输入a a和和b b两个整数,按先大后小的顺序输出两个整数,按先大后小的顺序输出 main() int a,b,*p1, *p2, *p; scanf(“%d,%d”,&a,&b); 1000 5 a p1=&a; p2=&b; 1002 9 b if(ab) p=p1; p1=p2; p2=p; printf(“n %d,%d”,a,b); 2000 p1 printf(“n%d,%d”,*p1,*p2); 2004 p2 2006 p 改变p1和p2的指向1000100210021000重要概念:重要概念:
8、只要将某一变量的地址存入指针变量中,只要将某一变量的地址存入指针变量中,就可通过指针变量间接访问该变量。就可通过指针变量间接访问该变量。 指针变量作为函数的参数指针变量作为函数的参数 可将指针变量作函数的参数,接受实参可将指针变量作函数的参数,接受实参地址,获得具体指向,进而通过指针变量间地址,获得具体指向,进而通过指针变量间接访问主调函数的变量。接访问主调函数的变量。swap(int *p1, int *p2) int t; t=*p1; *p1=*p2; *p2=t; 1000 5 a 1002 9 b main() int a,b; scanf(“%d,%d”,&a,&b); 2000
9、p1 if(ab)swap(&a,&b); 2004 p2 printf(“n %d,%d”,a,b); 2006 t 1000100259跨越逻辑上的限制跨越逻辑上的限制swap(int p1, int p2) 不用指针变量情况 int t; t=p1; p1=p2; p2=t; 1000 5 a 1002 9 b main() int a,b; scanf(“%d,%d”,&a,&b); 2000 p1 if(ab)swap(a, b); 2004 p2 printf(“n %d,%d”,a,b); 2006 t 59 59 重要概念:重要概念:使用指针变量作函数参数,使用指针变量作函数参
10、数,被调函数可以将多个结果交给主调函数。被调函数可以将多个结果交给主调函数。例:编写函数,求一元二次方程的两个实根。例:编写函数,求一元二次方程的两个实根。#include “math.h” ? root(float a,float b,float c, ) float d; d=b*b-4*a*c; if(d0|a=0)return(0); ? =(-b+sqrt(d)/2/a; ? =(-b-sqrt(d)/2/a; return(1); float *x1, float *x2 *x1 *x2 int main() int k; float a,b,c,x1,x2; scanf(“%f,
11、%f,%f”,&a,&b,&c); k=root(a,b,c,&x1,&x2); if(k) printf(“n %f,%f”,x1,x2); 例:求例:求n n个数的最大值、最小值和平均值。个数的最大值、最小值和平均值。float aver(int a, int n, int *max, int *min) int i; float s=0; *max=a0; *min=a0; for(i=0;i*max) *max=ai; if(ai*min) *min=ai; return(s/100);main() int a100,i,max,min;float av;for(i=0;i100;i+
12、) scanf(“%d”,&ai);av=aver(a,100,&max,&min);printf(“ %d,%d,%f”,max,min,av); 10 103 3 数组的指针和指向数组的指针变量数组的指针和指向数组的指针变量数组有一个首地址: 数组的指针数组的指针。每个数组元素也都有地址: 数组元素的指针数组元素的指针。532168742000200220042006 指向数组元素的指针变量指向数组元素的指针变量 int a10,*p; p=a; 指向数组指向数组 p=&a0; 指向数组元素指向数组元素 51247680392000200220042006p 通过通过指针引用数组元素指针引
13、用数组元素 p=&a0; p p指向指向a0a0 *p=1; 等效于等效于a0=1a0=1; ; 即可通过即可通过p p来访问来访问a0a0 也可以通过也可以通过p p来访问其它元素:来访问其它元素: * *(p+1)=3; (p+1)=3; 等效于等效于a1=3;a1=3; 其中其中p+1p+1指向指向a1a1注意:注意:p+1p+1不是地址加不是地址加1 1,而是加一个数据类型单,而是加一个数据类型单 位。位。 一般地一般地, ,当当p p指向指向a0(p=a)a0(p=a)时:时:p+i a+i a+i &ai &ai*(p+i) * *(a+i)(a+i) ai ai pi pi即以下
14、几个语句等效:即以下几个语句等效:ai=10; *(p+i)=10; *(a+i)=10; pi=10;例:从键盘输入例:从键盘输入1010个数到数组个数到数组a a:int a10,i,*p=a,s=0for(i=0;i10;i+) scanf(“%d”,&ai);for(i=0;i10;i+) scanf(“%d”,a+i);for(i=0;i10;i+) scanf(“%d”,p+i);累加求和的各种用法:累加求和的各种用法:for(i=0;i10;i+)s+=ai;for(i=0;i10;i+)s+=*(a+i);for(i=0;i10;i+)s+=*(p+i);for(i=0;i10
15、;i+)s+=pi;for(i=0;i10;i+)s+=*p+; 等效于等效于*(p+)for(p=a;pa+10;p+)s+=*p; 注意不能注意不能使用a+后两种用法效率高。 使用指针变量访问数组时,要特别注意指针使用指针变量访问数组时,要特别注意指针变量的当前值。变量的当前值。 注意下例:注意下例: main() int a10,*p=a,i; for(p=a;pa+10;p+) scanf(“%d”,p); for(i=0;i10;i+)printf(“%d”,*p+); pp=a;数组名作为函数参数数组名作为函数参数 有了指针概念的基础上,重新回顾数组名作有了指针概念的基础上,重新回
16、顾数组名作为函数参数时,数据的传递情况:为函数参数时,数据的传递情况:例:将数组例:将数组a a中的中的n n个数按相反顺序存放。个数按相反顺序存放。 int inv(int x, int n) i,j,m,t; main() m=n/2; for(i=0;im;i+) int a10,i; j=n-1-i; 输入输入a t=xi; inv(a,10); xi=xj; 输出输出a xj=t; a与x共用同一片内存单元axint inv(int *x, int n) 指针变量作函数参数时的传递情况 i,j,m,t; main() m=n/2; for(i=0;im;i+) int a10,i;
17、j=n-1-i; 输入输入a t=xi; inv(a,10); xi=xj; 输出输出a xj=t; 下标法 a10001000 x j=n-1-i; t=*(x+i); *(x+i)= *(x+j); *(x+j)=t; 指针法指针法进一步优化:进一步优化:int inv(int *x, int n) main() int *i=x,*j=x+n-1,t; for(;ij;i+,j-) int a10,i; 输入输入a t=*i; inv(a,10); *i=*j; 输出输出a *j=t; 优点:优点:简练简练 效率高效率高ij 例:例:选择法排序函数选择法排序函数 void sort(in
18、t *a, int n) int i,j,t; for(i=0;in-1;i+) for(j=i+1;jaj)t=ai; ai=aj; aj=t; 只将形参改为指针变量,仍按下标法使用只将形参改为指针变量,仍按下标法使用 void sort(int *a, int n) int i,j,t; for(i=0;in-1;i+) for(j=i+1;j*(a+j) t=*(a+i); *(a+i)= *(a+j); *(a+j)=t; 按指针法使用按指针法使用 进一步优化:进一步优化:void sort(int *a, int n) int *i, *j,t; for(i=a;ia+n-1;i+)
19、 for(j=i+1;j*j) t=*i; *i=*j; *j=t; main() int a10,j; for(j=0;j10;j+)scanf(“%d”,a+j); sort(a,10); for(j=0;j10;j+)printf(“%5d”,aj); 分段排序?分段排序?main() int a10,j; for(j=0;j10;j+)scanf(“%d”,a+j); for(j=0;j10;j+)printf(“%5d”,aj); sort(a,5);sort(a+5,5); 指向多维数组的指针和指针变量指向多维数组的指针和指针变量 从本质上说,多维数组的指针与一维数组从本质上说,多
20、维数组的指针与一维数组的指针相同,但在概念上和使用上,多维数组的指针相同,但在概念上和使用上,多维数组的指针要复杂些。的指针要复杂些。 以二维数组的指针为例:以二维数组的指针为例:二维数组的地址 :一维:a,&ai,a+i 二维:a,&aij,a+i (行指针), a+i+j (i+j行) , ai(特殊的一维数组元素,列指针), ai+j (i行j列)358724691604int a34 100010081016a+0a+1a+2a0a1a2讨论以下用法的效果:讨论以下用法的效果:for(i=0;i3;i+) scanf(“%d”,a+i);输入数据:输入数据:1 2 3讨论以下用法的效果
21、:讨论以下用法的效果:for(i=0;i3;i+) scanf(“%d”,a1+i);输入数据:输入数据:1 2 3讨论以下用法的效果:讨论以下用法的效果:for(i=0;i3;i+) scanf(“%d”,a1+i+2);输入数据:输入数据:1 2 3讨论以下用法的效果:讨论以下用法的效果:for(i=0;i3;i+) scanf(“%d”,a+i+1);输入数据:输入数据:1 2 3注意:注意:指针运算符指针运算符*作用在行指针上的结果仍是指作用在行指针上的结果仍是指针针-列指针列指针; *作用在列指针上的结果作用在列指针上的结果-具体元素具体元素。*(a+0),*(a+1),*(a+2)
22、 仍是地址仍是地址。*(a+i)= ai)*(a0),*(a1),*(a2) 具体元素值具体元素值。*(ai)*(a+i)+j 也是地址,但要区别:也是地址,但要区别:(a+i)+j行指针行指针 (a+1)+1 ? *(a+i)+j列指针列指针 *(a+1)+1 ? 100010081016a+0a+1a+2a0a1a2*(a+1) 如果要通过如果要通过a+ia+i形式的地址访问数组元素的具体形式的地址访问数组元素的具体内容,则:内容,则:*(*(a+i) 或或 *(*(a+i)+j) aij如如:*(*(a+1) a10 *(*(a+1)+2) a12讨论:讨论:*(a+2) *(*(a+1
23、)+3) *(a1+1) *(*(a+1)+5)例:求数组例:求数组a a的所有元素之和。的所有元素之和。 可有多种用法:可有多种用法: for(i=0;i3;i+) for(i=0;i3;i+)for(i=0;i3;i+) for(i=0;i3;i+)for(j=0;j4;j+) for(j=0;j4;j+)for(j=0;j4;j+) for(j=0;j4;j+)s+=s+=aijaij; s+=; s+=* *(ai+j)(ai+j); ;for(i=0;i3;i+)for(i=0;i3;i+)for(j=0;j4;j+)for(j=0;j4;j+) s+= s+=* *( (* *(a
24、+i)+j)(a+i)+j); ; ( 指向二维数组的指针变量指向二维数组的指针变量 同样可使一个指针变量同样可使一个指针变量p p指向二维数组指向二维数组a a,再,再通过通过p p访问数组元素。访问数组元素。main()main() int int a34=1,2,3,4,5,6,7,8,9,10,11,12;a34=1,2,3,4,5,6,7,8,9,10,11,12; int int i,k,i,k,* *p;p; p=a; p=a; k= k=* *p;p;? ? k= k=* *(p+2);(p+2);? ? for(p=a;pa+2;p+)printf(“%3d”, for(p=
25、a;pa+2;p+)printf(“%3d”,* *p); p); ? ? 123456789101112k=p12; 不合法不合法k=*(*(p+1)+2);k=p1*4+2; 合法合法k=*(p+1*4+2);例:求矩阵的上三角元素之和。例:求矩阵的上三角元素之和。main() int a34,*p,i,j,s=0; 输入输入a p=a; for(i=0;i3;i+) for(j=i;j4;j+) s+=pi*4+j; 或或 s+=*(p+4*i+j) printf(“n %d”,s); 指向由指向由m m个元素组成的一维数组的指针变量个元素组成的一维数组的指针变量 可以这样定义一个指针变
26、量:可以这样定义一个指针变量: int int ( (* *p)4p)4 表示表示p p为指向由为指向由4 4个元素组成的个元素组成的行指针变量行指针变量。 当当p=ap=a时,可通过时,可通过p p引用引用aijaij: pijpij 或或 * *( (* *(p+i)+j)(p+i)+j) 例:求矩阵的上三角元素之和。例:求矩阵的上三角元素之和。 main()main() int int a34,(a34,(* *p)4,i,j,s=0;p)4,i,j,s=0; 输入输入a a p=a; p=a; for(i=0;i3;i+) for(i=0;i3;i+) for(j=i;j4;j+) f
27、or(j=i;j4;j+) s+=pij; s+=pij; 或或 s+=s+=* *( (* *(p+i)+j)(p+i)+j) printf printf(“n %d”,s);(“n %d”,s); 多维数组的指针作函数参数维数组的指针作函数参数 用于接受实参数组地址的形参可用两种:用于接受实参数组地址的形参可用两种:行指行指针针和和列指针列指针。 以方阵转置为例:以方阵转置为例: void at(int void at(int ( (* *a)3a)3) ) 用行指针用行指针 int int i,j,t; i,j,t; 缺点:不通用缺点:不通用 for(i=0;i3;i+)for(i=0;
28、i3;i+) for(j=i+1;j3;j+) for(j=i+1;j3;j+) t=aij; aij=aji; t=aij; aij=aji; aji=t; aji=t; 用列指针:用列指针: void at(int void at(int * *a a,int ,int n)n) int int i,j,t;i,j,t; for(i=0;in;i+) for(i=0;in;i+) for(j=i+1;jn;j+) for(j=i+1;jn;j+) t= t=aiai* *n+jn+j; ; aiai* *n+jn+j= =ajaj* *n+in+i; ; ajaj* *n+in+i=t;=
29、t; 优点:通用 在编通用函数时,一般使用列指针。10104 4 字符串的指针和指向字符串的指针变量字符串的指针和指向字符串的指针变量 字符串的表示形式字符串的表示形式 可用两种方法访问字符串:可用两种方法访问字符串: 用字符数组存放字符串用字符数组存放字符串 用字符指针指向一个字符串用字符指针指向一个字符串main()main() char c5=”abc char c5=”abc”; ”; 定义字符数组,并将字符串存定义字符数组,并将字符串存入入 char char * *p1=c,p1=c,* *p=”abcp=”abc”;”;定义指针变量,指向字符定义指针变量,指向字符串串 print
30、fprintf(“%s”,c); (“%s”,c); 通过数组名访问字符串通过数组名访问字符串 printfprintf(“%s”,p); (“%s”,p); 通过指针变量访问字符串通过指针变量访问字符串 printfprintf(“%-c”,(“%-c”,* *(p+3); (p+3); 通过指针变量访问字符通过指针变量访问字符 cab c0abc010001000p 与其它一维数组的指针相比,字符串的指与其它一维数组的指针相比,字符串的指针有其针有其独特之处独特之处: 可以通过指针对字符串进行整体访问。可以通过指针对字符串进行整体访问。 对字符串的操作依赖于结束符。对字符串的操作依赖于结束
31、符。 可以整体赋初值。可以整体赋初值。 有各种字符串处理函数。有各种字符串处理函数。 本节重点掌握:本节重点掌握: 通过数组和通过指针操作字符串的通过数组和通过指针操作字符串的基本方法。基本方法。 常用的字符串处理方法。常用的字符串处理方法。 例:字符串拷贝操作。例:字符串拷贝操作。main() char a=”abcdef”,b20; int i; for(i=0; *(a+i)!=0; i+) *(b+i)=*(a+i); *(b+i)=0; printf(“%s”,b);main() char a=”abcdef”,b20,*p1, *p2; p1=a; p2=b; for( ; *p1
32、!=0;p1+,p2+) *p2=*p1; *p2=0; printf(“%s”,b);用指针变量处理将拷贝操作编成一函数:将拷贝操作编成一函数:void copy_string(char *from,char *to) for(; *from; from+,to+) *to=*from; *to=0;还可以改成:还可以改成:void copy_string(char *from,char *to) for(; *from;) *to+=*from+; *to=0;字符串合并函数:字符串合并函数:void append_string(char void append_string(char *
33、 *from,char from,char * *to)to) for(; for(;* *to; to+);to; to+); for(; for(; * *from;) from;) * *to+=to+=* *from+;from+; * *to=0;to=0; 阅读程序:阅读程序:void f (char *c) main() c+=2; char c20=”abcdef”; (*c)+; f(c+1); c+; *c=0; printf(“%s”,c); 内存空间的动态分配内存空间的动态分配 在程序设计中,对于要处理的批量数据,在程序设计中,对于要处理的批量数据,我们往往是选用数组作
34、为存放这些数据的数据我们往往是选用数组作为存放这些数据的数据结构,然而,数组有一个明显的缺点,就是在结构,然而,数组有一个明显的缺点,就是在定义数组时,其长度必须是常值,无法根据需定义数组时,其长度必须是常值,无法根据需要动态地定义。这样,在很多情况下,不是定要动态地定义。这样,在很多情况下,不是定义的数组长度不够,就是定义太长以至于浪费。义的数组长度不够,就是定义太长以至于浪费。 采用动态分配可以克服这一缺点,并且可采用动态分配可以克服这一缺点,并且可以随时释放。以随时释放。 动态分配内存空间步骤:动态分配内存空间步骤: 定义一指针变量。定义一指针变量。 申请一片内存空间,并将其首地址赋给指
35、申请一片内存空间,并将其首地址赋给指针变量。此时便可通过指针变量访问这片内存。针变量。此时便可通过指针变量访问这片内存。 用完后释放这片内存空间。用完后释放这片内存空间。 int int * *p;p; p=malloc p=malloc(byte);(byte); free(p); free(p); 以上函数的原形在以上函数的原形在stdiostdio.h.h中中。 p例:对例:对n n个学生的分数排序后输出。个学生的分数排序后输出。 #include “stdio#include “stdio.h”.h”void sort(int void sort(int * *a, int a, in
36、t n) n) main()main() int int * *a,j,n;a,j,n; scanf scanf(“%d”,&n);(“%d”,&n); a=malloc(n a=malloc(n* *sizeof(intsizeof(int);); if(!a) exit(0); if(!a) exit(0); for(j=0;jn;j+) scanf for(j=0;jn;j+) scanf(“%d”,a+j);(“%d”,a+j); sort(a,n); sort(a,n); for(j=0;jn;j+) printf(“%5d”,aj); for(j=0;jy) z=x; else z
37、=y; return(z);main() int (*p)(); 定义指向函数的指针变量定义指向函数的指针变量p p int a,b,c; p=max; 将将p p指向函数指向函数maxmax scanf(“%d%d”,&a,&b); c=(*p)(a,b); 通过通过p p调用函数调用函数maxmax 等效于等效于c=max(a,b);c=max(a,b); printf(“n %d”,c);把指向函数的指针变量作为函数参数把指向函数的指针变量作为函数参数 指向函数的指针变量最常见的用途是把它指向函数的指针变量最常见的用途是把它作为函数的参数,用于接受主调函数传来的某作为函数的参数,用于接受
38、主调函数传来的某一函数的入口地址,从而在被调函数中可以通一函数的入口地址,从而在被调函数中可以通过该指针变量调用它所指向的函数,这样,被过该指针变量调用它所指向的函数,这样,被调函数中就可实现非固定函数的调用,以达到调函数中就可实现非固定函数的调用,以达到编写通用函数的目的。编写通用函数的目的。 例:用矩形法编写一个通用的求定积分的函数。例:用矩形法编写一个通用的求定积分的函数。关键问题:如何处理被积函数是未知的。关键问题:如何处理被积函数是未知的。double intgral (double a,double b,int double intgral (double a,double b,i
39、nt n,n, double (double (* *f)( )f)( ) ) ) int int i;i; double h,x,y,s=0; double h,x,y,s=0; h=(b-a)/n; h=(b-a)/n; for(i=1;i=n;i+) for(i=1;i=n;i+) x=a+(i-1) x=a+(i-1)* *h;h; y= y=( (* *f)(x)f)(x); ; s+=h s+=h* *y;y; return(s); return(s); double f1(double x) return(3*x*x+2*x-1);main() double s; s=integral(1.0,2.0,100,f1);