1 / 59

八、指针

八、指针. 8.1 指针概述 8.2 二维数组 8.3 指针数组 8.4 多级指针 8.5 指向函数的指针 8.6 指针数据类型小结. 8.1 指针概述 8.1.1 地址与指针. • C 语言引入指针概念的利弊:  可编制出简洁明快、性能强、质量高的程序。  如错误使用指针,会造成程序失控或得到错误结果。 • 指针变量是专用于存放地址的一种特殊变量,定义 的一般形式: 数据类型 *指针变量名; • 指针变量的初始化

meriel
Download Presentation

八、指针

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 八、指针 8.1 指针概述 8.2 二维数组 8.3 指针数组 8.4 多级指针 8.5 指向函数的指针 8.6 指针数据类型小结

  2. 8.1 指针概述8.1.1 地址与指针 • C语言引入指针概念的利弊:  可编制出简洁明快、性能强、质量高的程序。 如错误使用指针,会造成程序失控或得到错误结果。 • 指针变量是专用于存放地址的一种特殊变量,定义 的一般形式: 数据类型 *指针变量名; • 指针变量的初始化 int *p = NULL; 全0地址 int i; int *p = &i; char *p = "abcde"; 把字符串首地址送给 p

  3. 8.1.2 指针的运算 指针运算主要有三类: 1. 赋值运算 • 对指针变量赋的值必须是地址或指针。 例: int i,a[5],*pi,*pa,*ps,*p; pi=&i; pa=a; ps="abcd"; p=pi; • 常数不能取地址。如:pi = &314; • 表达式和寄存器变量不能取地址。如:pi = &(i+j);

  4. 2. 关系运算 两个指针指向同一数组时,可进行关系运算。含 <、 <=、 >、 >=、 !=、 = =。值小表示所指元素在前。 例:指针关系运算举例。 main() { int a[20] ,*p, *q,n1, n2; randomize(); n1=rand()%20; n2=rand()%20; p=a+n1; /*p指向a[n1]*/ q=a+n2; /*q指向a[n2]*/ if(p==q) printf("\np and q points same element."); else if(p<q) printf("\np points forwards."); else printf("\nq points forwards."); printf("\np=%x q=%x",p,q); }

  5. 3. 算术运算 • 指针加减整型量 指针加减一个整数,该整数表示的是该指针所指 向数据类型的数据的个数,指针实际的增加量或 减少量是该整数乘以指针所指向数据类型的长度 (字节数)。 例如:p为指针变量,则p++后指向了原指向的 数据的后面一个数据的首地址。 如果p为字符型指针变量,则其增量为1字节; 如果p为整型指针变量,则其增量为2字节; 如果p为实型指针变量,则其增量为4字节。

  6. 例:如有:int a[10], *p; 执行:p = &a[3]; p = p + 2; 则:p 即指向 a[5]。实际加了2*2个字节。 如有:double b[10],*p; 执行:p = *b[3]; p = p+2; 则: p 即指向b[5]。实际加了2*8个字节。

  7. • 指针间的加减运算 只有指向同一组类型相同数据的指针之间,进行 减法运算才有意义,运算结果为整数,表示两指 针间相隔的数据个数。 例:指针间的加减运算示例。 main() { int a[20],*p,*q; p=&a[2]; q=&a[6]; printf("\nq=%x\np=%x\nq-p=%d",q,p,q-p); } /* q-p应为 4。*/

  8. 上例中 p=&a[2]; q=&a[6]; 之后,有: 表达式 作用和意义 *++p 先++p使p指向a[3],再取*p即a[3]值 ++*p p指向的元素值增1,即++a[2] (*p)++ 先取*p即a[2]值,再使a[2]值增1,即a[2]++ *(p++) 先取*p即a[2]值, 再使p增1指向a[3]

  9. 8.1.3 指针与数组 指针与数组有着密切的联系,任何可由数组完 成的操作都能用指针实现。 • 数组名(如a)既是该数组的名称,又是地址常 量,保存着该数组中第一个元素(如a[0])的地 址,即存放该数组各元素的内存区域的首址。 • []是下标运算符,由[]之前的地址量及[]中的偏移 量,就可决定所指的对象(数组元素)。 如有:int i, a[10], *pa=a;, 则 a[i]、pa[i]、*(a+i)和*(pa+i)指的是同一元素。

  10. 例1:指针与数组的关系示例。 main ( ) { int a[5] , i, *p=a; for ( i=0; i<5; i++ ) scanf("%d",&a[i]); for ( i=0; i<5; i++ ) printf("%d",a[i]); } 程序中scanf和 printf函数可作如下3组等价替换: ⑴ scanf("%d",p+i); printf("%d",*(p+i)); ⑵ scanf("%d",a+i); printf("%d",*(a+i)); ⑶ scanf("%d",&p[i]); printf("%d",p[i]);

  11. 上述程序也可写成: main ( ) { int a[5] , i, *p=a; for ( i=0; i<5; i++ ) scanf("%d",p++); p=a; for ( i=0; i<5; i++ ) printf("%d",*p++); } 注意: p++不能写成a++,因为a是地址常量。 *p++等价于*(p++),其运算顺序是先*p,然后p++。

  12. 例2:数组、指针与自增自减符的综合应用 main() { int a[6]={1,2,3,4,5,6}, *p, i; p=&a[3] printf("%d\n",*++p); printf("%d\n",*p--); printf("%d\n",(*p)++); printf("%d\n",*p); for(i=0; i<6; i++) printf("%d ",a[i]); } 运行结果: 5 5 4 5 1 2 3 5 5 6

  13. D C 例3:执行以下程序后,a 的值为[1] __ ,b 的值 为[2] __ 。 main ( ) { int a, b, k=4, m=6, *p1=&k, *p2=&m; a=*p1; b=(-*p1)/(*p2) + 7; printf ("a=%d\n", a); printf ("b=%d\n", b); } [1] A) -1 B) 1 C) 0 D) 4 [2] A) 5 B) 6 C) 7 D) 10

  14. B 例4:以下程序中调用 scanf 函数给变量 a 输入数值 的方法是错误的,其错误原因是 _____ 。 main ( ) { int *p, *q, a, b; p=&a; printf("input a:"); scanf("%d",*p); ...... A) *p表示的是指针变量 p 的地址 B) *p 表示的是变量 a 的值,而不是它的地址 C) *p 表示的是指针变量 p 的值 D) *p 只能用来说明 p 是一个指针变量

  15. D 例5:若已定义 int a = 5;下面对 (1)、(2) 两个语句 的正确解释是 ______ 。 (1) int *p = &a; (2) *p = a; A) 语句(1)和(2)中的 *p含义相同,都表示给指针 变量 p 赋值 B) 语句(1)和(2)执行的结果,都是把变量 a 的地址 值赋给指针变量 p C) (1) 在对 p 说明的同时进行初始化,使 p 指向 a (2) 将变量 a 的值赋给指针变量 p D) (1) 在对 p 说明的同时进行初始化,使 p 指向 a (2) 将变量 a 的值赋给 *p

  16. ABCDCD 例6:下面程序的输出结果是 __________ 。 char b[ ]="ABCD"; main( ) { char *chp; for(chp=b; *chp; chp+=2) printf("%s",chp); printf("\n"); }

  17. 8.1.4 指针作为函数的参数 • 函数的参数可以是指针型的,如数组名和指针变 量。并且利用指针型的参数就能改变主调函数中 数据的值。 例1:用变量和指针作为函数参数时的差别。 void swap1(int a, int b) { int t; t=a; a=b; b=t; } void swap2(int *ap, int *bp) { int t; t=*ap; *ap=*bp; *bp =t; }

  18. 运行结果: a=2 b=5 a=2 b=5 a=5 b=2 main ( ) { int a=2, b=5, *ap, *bp; ap = &a; bp = &b; printf ("\na = %d b = %d", a, b); swap1(a, b); printf ("\na=%d b=%d", a, b); swap2(ap, bp); printf ("\na=%d b=%d", a, b); }

  19. 例2:检查字符串 t 是否是字符串 s 的子串,若是, 返回子串对应的s串下标;若不是,返回-1。 int strstr(char *s, char *t) { int i, j, k; /* i 表示与 s 开始比较的下标*/ for( i=0; *(s+i); i++ ) { for(k=i, j=0; t[j]&&s[k]= = t[j]; k++, j++); if(t[j]=='\0') return i; /* j是t串的下标*/ } /* k是s串与t串比较的下标*/ return (-1); }

  20. 指针也可以作为函数的返回值,返回给主调函数。 例3:编制在给定的字符串中寻找指定字符的函数, 若找到,则返回指向字符串中该字符的指针;否则, 返回NULL。 #define NULL 0 char *search(char *str, char c) { char *p=str; while (*p&&*p!=c) p++; return (*p? p: NULL); } main ( ) { char *p="China"; char c, *search ( ) ; scanf("%c",&c); p=search(p,c); if(p==NULL) printf("not found!\n"); else printf("%c\n",*p); }

  21. int fun(int *x,int y) { static int k=2; k+=*x+1; *x=k-y; return k; } 例4:写出下列程序的输出结果: main() { int fun(int *, int); int j=4,m=1,k; k=fun(&j,m); printf("The first k:%d\n",k); k=fun(&j,m); printf("The second k:%d\n",k); } 运行结果: The first k:7 The second k:14

  22. void fun(int *a,int n,int m) { int t; while(n<m) { t=*(a+n); *(a+n)=*(a+m); *(a+m)=t; n++; m--; } } 例5:写出下列程序的输出结果。 #include "stdio.h" main() {void fun(int *,int,int) int i,s[]={0,1,2,3,4,5}; fun(s,0,3); fun(s,2,5); for(i=0;i<6;i++) printf("s[%d]=%d ",i,s[i]); } 运行结果:s[0]=3 s[1]=2 s[2]=5 s[3]=4 s[4]=0 s[5]=1

  23. 8.2 二维数组与指针 8.2.1二维数组及其元素的不同表示形式 • 二维数组的定义格式: 数据类型数组名[常量表达式] [常量表达式]; • 二维数组的地址 设有二维数组a有3行4列,定义为 int a[3][4];

  24. 二维数组a可以看成是由三个一维数组组成,其数 组名分别为a[0]、a[1]、a[2],它们各包含4个元素。  a[0]、a[1]、a[2]作为一维数组名是地址常量,即: a[0]是&a[0][0]; a[1]是&a[1][0]; a[2]是&a[2][0]。 从而再有:(a[i]+j是&a[i][j]) a[0]+1是&a[0][1],a[0]+2是&a[0][2], a[0]+3是&a[0][3] a[1]+1是&a[1][1],a[1]+2是&a[1][2], a[1]+3是&a[1][3] a[2]+1是&a[1][2],a[2]+2是&a[2][2], a[2]+3是&a[2][3] 并还有:(*(a[i]+j)是a[i][j]) *(a[0]+1)是a[0][1],*(a[0]+2)是a[0][2], *(a[0]+3)是a[0][3] *(a[1]+1)是a[1][1],*(a[1]+2)是a[1][2], *(a[1]+3)是a[1][3] *(a[2]+1)是a[2][1],*(a[2]+2)是a[2][2], *(a[2]+3)是a[2][3]

  25. 二维数组a又可以看成由三个元素(一维数组名) a[0] 、a[1]、a[2]组成,因而就有: (a+i与&a[i]指向同一地址) a 与&a[0]指向同一地址,即第0行首址; a+1与&a[1]指向同一地址,即第1行首址; a+2与&a[2]指向同一地址,即第2行首址。

  26. 由于并不存在a[0] 、a[1]、a[2]这样的变量,而只 是一种地址的计算方法,看作一维数组名,能得 到第0、1、2行的首址,所以&a[0]与a[0]、 &a[1] 与a[1]、 &a[2]与a[2]值是相同的。从而又有: (a+i与a[i]指向同一地址) a 与a[0]指向同一地址,即第0行首址; a+1与a[1]指向同一地址,即第1行首址; a+2与a[2]指向同一地址,即第2行首址。 从上述可知: a+i、&a[i]、 a[i]指向同一地址。

  27.  基于a、a+1、a+2分别与&a[0]、 &a[1] 、 &a[2] 指向同一地址,可得出: *(a+0)与a[0]等价; *(a+1)与a[1]等价; *(a+2)与a[2]等价。 即:*(a+i)与a[i]等价 进而得出: *(a+0)+1与&a[0][1]指向同一地址;… *(a+1)+1与&a[1][1]指向同一地址;… *(a+2)+1与&a[2][1]指向同一地址;… 即:*(a+i)+j与&a[i][j]等价

  28. 既然*(a+i)+j与&a[i][j]指向同一地址,那么就有:既然*(a+i)+j与&a[i][j]指向同一地址,那么就有: *(*(a+0)+1)是a[0][1]; *(*(a+0)+2)是a[0][2]; … *(*(a+1)+1)是a[1][1]; *(*(a+1)+2)是a[1][2]; … *(*(a+2)+1)是a[2][1]; *(*(a+2)+2)是a[2][2]; … 即: *(*(a+i)+j)是a[i][j] 综上所述,在二维数组a中: a+i=a[i]=*(a+i)=&a[i]=&a[i][0](地址值相等) a=a[0]=*(a+0)=*a=&a[0]=&a[0][0](地址值相等) a[i]+j=*(a+i)+j=&a[i][j](地址值相等) *(a[i]+j)=*(*(a+i)+j)=(*(a+i))[j]=a[i][j](同一元素值)

  29. ⑵ 把二维数组看作一维数 组,利用指针赋值。 main() { int i,j,k=1,a[3][4]; int *p=&a[0][0]; /*或&a[0]或a[0]或a或*a */ for(i=0;i<12;i++) *(p++)=k++; /*或p[i]或*(p+i) */ } [例8.2.1]:建立一个3×4的矩阵, 使各元素的值为 1~12的整数。 ⑴ 下标法 main() { int i,j,k=1,a[3][4]; for(i=0;i<3;i++) for(j=0; j<4; j++) a[i][j]=k++; }

  30. ⑶ 把该数组看成3个一维数组利用指针赋值 main() { int i,j,k=1,a[3][4]; int *p0,*p1,*p2; p0=a[0]; /*或&a[0]或a+0或*(a+0)或&a[0][0] */ p1=a[1]; /*或&a[1]或a+1或*(a+1)或&a[1][0] */ p2=a[2]; /*或&a[2]或a+2或*(a+2)或&a[2][0] */ for(i=0;i<4;i++) { *(p0++)=k; /*或p0[i]或*(p0+i) */ *(p1++)=4+k; /*或p1[i]或*(p1+i) */ *(p2++)=8+k; /*或p2[i]或*(p2+i) */ k++; } }

  31. ⑷用二维数组中元素下标法外的其它表达方式 main() { int i,j,k=1,a[3][4]; for(i=0;i<3;i++) for(j=0; j<4; j++) *(a[i]+j) =k++; /*或*(*(a+i)+j)或(*(a+i))[j] */ }

  32. 8.2.2 指向一维数组的指针 • 指向一维数组的指针 用int *p;定义的指针变量p可以指向一般整型变量, 也可以指向整型数组元素,使p增加i(p+=i或p+i) 就指向原来指向的元素的下面第i个元素。 用int a[3][4];定义的数组名a指向一维数组a[0]的第 1个元素,a+1指向一维数组a[1]的第1个元素, … a+i指向一维数组a[i]的第1个元素,即a指向的变化 是以行为单位的。这类特殊的指针称为指向一维 数组的指针。

  33. • 指向一维数组的指针变量的定义格式 数据类型 (*指针变量名)[常量表达式] 其中常量表达式的值等于所指向的一维数组的元 素个数。 例1:用指向一维数组的指针变量重写 [例8.2.1] 。 main() { int i,j,k=1,a[3][4],(*pa)[4]=a; for(i=0;i<3;i++) for(j=0; j<4; j++) *(*(pa+i)+j)=k++; /* *(*(pa+i)+j)表示a[i][j]*/ } /* 或*(pa[i]+j)或 (*(pa+i))[j]或 pa[i][j]*/

  34. 例2:设有以下定义语句: int a[3][2]={10,20,30,40,50,60},(*p)[2]; p=a; 则*(*(p+2)+1)的值为 ______ 。 60

  35. 8.3 指针数组 1. 指针数组的定义 • 一个数组的元素的都为指针类型数据,称为指针 数组 • 定义格式: 数据类型 *指针数组名[常量表达式] 其中常量表达式的值表示的元素个数。 例:int *b[3]; 指针数组b的每个元素相当于可以指向一般整型变 量,也可以指向整型数组元素的指针变量。

  36. 例1:用指针数组重写[例8.2.1] 。建立一个3×4的 矩阵, 使各元素的值为1~12的整数。 main() { int i,j,k=1,a[3][4], *b[3]; for(i=0;i<3;i++) b[i]=a[i]; /*或&a[i]或a+i或*(a+i)或&a[i][0] */ for(i=0;i<3;i++) for(j=0; j<4; j++) *(b[i]+j)=k++; /* 或 b[i][j]*/ }

  37. 2. 指针数组与字符串 在处理多个字符串时,可使用二维数组或字符指 针,通常使用字符指针数组会感到更方便、灵活。 【例4.16】将8个字符串按词典顺序排列(用选择法)。 ⑴ 用二维数组 #include "string.h" #define SIZE 8 main() {char name[SIZE][10]={"constant","pointer","function", "variable","address","structue","union","array"}; int i,j,k; char temp[10];

  38. for(i=0;i<SIZE-1;i++) { k=i; for(j=i+1;j<SIZE;j++) if(strcmp(name[k],name[j])>0) k=j; if(k!=i) { strcpy(temp,name[k]); strcpy(name[k],name[i]); strcpy(name[i],temp); } } for(i=0;i<SIZE;i++) printf("%s\n",name[i]); }

  39. ⑵ 用指针数组 #include "string.h" #define SIZE 8 main() { char *name[SIZE]={"constant","pointer","function", "variable","address","structue","union","array"}; int i,j,k; char *temp;

  40. for(i=0;i<SIZE-1;i++) { k=i; for(j=i+1;j<SIZE;j++) if(strcmp(name[k],name[j])>0) k=j; if(k!=i) { temp=name[k]; name[k]=name[i]; name[i]=temp; } } for(i=0;i<SIZE;i++) printf("%s\n",name[i]); }

  41. 3. 命令行参数(P99) main()函数也可以指定参数,实参只能来自命令 行,man()函数定义可写为: main(int argc,char *argv[]) { 函数体 } 其中参数名通常用argc (argument count)和argv (argument vector)。 main()函数执行时,argc的值等于命令行上以空 格分隔的字符串的个数, 指针数组argv的长度为 argc,各元素分别指向命令行上各字符串的首址。

  42. 命令行上的第一个字符串是程序文件名,也称命命令行上的第一个字符串是程序文件名,也称命 令名,以后的字符串为参数。即: argv[0] 为命令名(字符串)的首址; argv[1] 为第一个参数的首址; argv[n-1] 为最后一个参数的首址。 【例5.8】显示命令行参数。 main(int argc, char *argv[]) { int i; printf("Command line arguments:\n"); printf("argc:%d\n",argc); for(i=0;i<argc;i++) printf("argv[%d]:%s\n",i,argv[i]); } …

  43. 假如该程序的可执行文件名为ex5_8.exe,存放在 C盘根目录下,运行时键入: C:\> ex5_8 firstArg secondArg↓ 运行结果: argc:3 argv[0]:C:\ex5_8 argv[1]:firstArg argv[2]:secondArg

  44. 8.4 多级指针 • 指针变量中可以保存另一个指针的地址,变成 “指向指针的指针”,称为多级指针。 例如:有指针数组name,令指针变量p=name, 则p指向指针数组name,而指针数组name的每个 元素又是指针,它们指向一般数据。于是就称指 针变量p为二级指针,如有指针变量pp再指向p, 则称pp为三级指针。 • 多级指针的定义 二级指针的定义形式:数据类型 **指针名

  45. 三级指针的定义形式:数据类型 ***指针名 从上可知n级指针定义时,指针名前应加n个*。 【例4.16】将8个字符串按词典顺序排列(用二级指 针)。 #include "string.h" #define SIZE 8 main() { char *name[SIZE]= {"constant","pointer","function", "variable","address","structue","union","array"}; int i,j,k; char *temp,**p;

  46. p=name; for(i=0; i<SIZE-1; i++) { k=i; for(j=i+1; j<SIZE; j++) if(strcmp(*(p+k),*(p+j))>0) k=j; if(k!=i) /*原name[k], name[j] */ { temp= *(p+k); /*原name[k]*/ *(p+k) = *(p+i) ; /*原name[k]=name[i] */ *(p+i) =temp; /*原name [i] */ } } for(i=0; i<SIZE; i++) printf("%s", *(p++)); } /*原name[i]*/

  47. 8.5 指向函数的指针 • 函数指针 函数名具有类似数组名的地址特性: 数组名 — 该数组的首地址 函数名 — 该函数(程序代码)的存储首地址,即 函数入口地址 取值为函数入口地址的指针变量,称为指向函数 的指针变量,简称函数指针。 • 函数指针的说明格式 数据类型 (*函数指针名)( ) 其中数据类型是指函数返回值的类型。

  48. • 函数指针的使用  给函数指针变量赋值时,只需给出函数名而不必 (也不能)给出参数。如: int a,b,c,max(int,int), (*p)( ); p=max; /* p为函数指针变量,max为函数名*/  函数可通过函数名调用,也可通过函数指针调用, 如上例后,只需用(*p)代替函数名max即可。如: c=(*p)(a,b);  对函数指针变量,象p+i、p++、p--等运算无意义。

  49. main ( ) { int a, b, c,(*p)(); p=max; scanf("%d,%d",&a,&b); c=(*p)(a,b); printf("max=%d\n", c); } 例1:求 a 和 b 中的大者,编一个max函数,通过指 向它的函数指针变量来调用它。 max(int x, int y) { int t; if (x> y) t=x; else t=y; return t; }

  50. • 函数指针数组 函数指针可以组成指针数组,称为函数指针数组, 该数组的元素都是函数指针。 说明格式: 数据类型 (*函数指针数组名[常量表达式] )( ) 其中常量表达式的值表示函数指针数组的长度。 例2:用循环语句for,while,do-while和if-goto编制 计算 的函数,并验证其正确性。要求用函数指 针数组。

More Related