590 likes | 690 Views
八、指针. 8.1 指针概述 8.2 二维数组 8.3 指针数组 8.4 多级指针 8.5 指向函数的指针 8.6 指针数据类型小结. 8.1 指针概述 8.1.1 地址与指针. • C 语言引入指针概念的利弊: 可编制出简洁明快、性能强、质量高的程序。 如错误使用指针,会造成程序失控或得到错误结果。 • 指针变量是专用于存放地址的一种特殊变量,定义 的一般形式: 数据类型 *指针变量名; • 指针变量的初始化
E N D
八、指针 8.1 指针概述 8.2 二维数组 8.3 指针数组 8.4 多级指针 8.5 指向函数的指针 8.6 指针数据类型小结
8.1 指针概述8.1.1 地址与指针 • C语言引入指针概念的利弊: 可编制出简洁明快、性能强、质量高的程序。 如错误使用指针,会造成程序失控或得到错误结果。 • 指针变量是专用于存放地址的一种特殊变量,定义 的一般形式: 数据类型 *指针变量名; • 指针变量的初始化 int *p = NULL; 全0地址 int i; int *p = &i; char *p = "abcde"; 把字符串首地址送给 p
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);
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); }
3. 算术运算 • 指针加减整型量 指针加减一个整数,该整数表示的是该指针所指 向数据类型的数据的个数,指针实际的增加量或 减少量是该整数乘以指针所指向数据类型的长度 (字节数)。 例如:p为指针变量,则p++后指向了原指向的 数据的后面一个数据的首地址。 如果p为字符型指针变量,则其增量为1字节; 如果p为整型指针变量,则其增量为2字节; 如果p为实型指针变量,则其增量为4字节。
例:如有: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个字节。
• 指针间的加减运算 只有指向同一组类型相同数据的指针之间,进行 减法运算才有意义,运算结果为整数,表示两指 针间相隔的数据个数。 例:指针间的加减运算示例。 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。*/
上例中 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]
8.1.3 指针与数组 指针与数组有着密切的联系,任何可由数组完 成的操作都能用指针实现。 • 数组名(如a)既是该数组的名称,又是地址常 量,保存着该数组中第一个元素(如a[0])的地 址,即存放该数组各元素的内存区域的首址。 • []是下标运算符,由[]之前的地址量及[]中的偏移 量,就可决定所指的对象(数组元素)。 如有:int i, a[10], *pa=a;, 则 a[i]、pa[i]、*(a+i)和*(pa+i)指的是同一元素。
例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]);
上述程序也可写成: 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++。
例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
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
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 是一个指针变量
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
ABCDCD 例6:下面程序的输出结果是 __________ 。 char b[ ]="ABCD"; main( ) { char *chp; for(chp=b; *chp; chp+=2) printf("%s",chp); printf("\n"); }
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; }
运行结果: 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); }
例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); }
• 指针也可以作为函数的返回值,返回给主调函数。 例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); }
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
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
8.2 二维数组与指针 8.2.1二维数组及其元素的不同表示形式 • 二维数组的定义格式: 数据类型数组名[常量表达式] [常量表达式]; • 二维数组的地址 设有二维数组a有3行4列,定义为 int a[3][4];
二维数组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]
二维数组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行首址。
由于并不存在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]指向同一地址。
基于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]等价
既然*(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](同一元素值)
⑵ 把二维数组看作一维数 组,利用指针赋值。 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++; }
⑶ 把该数组看成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++; } }
⑷用二维数组中元素下标法外的其它表达方式 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] */ }
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指向的变化 是以行为单位的。这类特殊的指针称为指向一维 数组的指针。
• 指向一维数组的指针变量的定义格式 数据类型 (*指针变量名)[常量表达式] 其中常量表达式的值等于所指向的一维数组的元 素个数。 例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]*/
例2:设有以下定义语句: int a[3][2]={10,20,30,40,50,60},(*p)[2]; p=a; 则*(*(p+2)+1)的值为 ______ 。 60
8.3 指针数组 1. 指针数组的定义 • 一个数组的元素的都为指针类型数据,称为指针 数组 • 定义格式: 数据类型 *指针数组名[常量表达式] 其中常量表达式的值表示的元素个数。 例:int *b[3]; 指针数组b的每个元素相当于可以指向一般整型变 量,也可以指向整型数组元素的指针变量。
例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]*/ }
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];
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]); }
⑵ 用指针数组 #include "string.h" #define SIZE 8 main() { char *name[SIZE]={"constant","pointer","function", "variable","address","structue","union","array"}; int i,j,k; char *temp;
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]); }
3. 命令行参数(P99) main()函数也可以指定参数,实参只能来自命令 行,man()函数定义可写为: main(int argc,char *argv[]) { 函数体 } 其中参数名通常用argc (argument count)和argv (argument vector)。 main()函数执行时,argc的值等于命令行上以空 格分隔的字符串的个数, 指针数组argv的长度为 argc,各元素分别指向命令行上各字符串的首址。
命令行上的第一个字符串是程序文件名,也称命命令行上的第一个字符串是程序文件名,也称命 令名,以后的字符串为参数。即: 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]); } …
假如该程序的可执行文件名为ex5_8.exe,存放在 C盘根目录下,运行时键入: C:\> ex5_8 firstArg secondArg↓ 运行结果: argc:3 argv[0]:C:\ex5_8 argv[1]:firstArg argv[2]:secondArg
8.4 多级指针 • 指针变量中可以保存另一个指针的地址,变成 “指向指针的指针”,称为多级指针。 例如:有指针数组name,令指针变量p=name, 则p指向指针数组name,而指针数组name的每个 元素又是指针,它们指向一般数据。于是就称指 针变量p为二级指针,如有指针变量pp再指向p, 则称pp为三级指针。 • 多级指针的定义 二级指针的定义形式:数据类型 **指针名
三级指针的定义形式:数据类型 ***指针名 从上可知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;
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]*/
8.5 指向函数的指针 • 函数指针 函数名具有类似数组名的地址特性: 数组名 — 该数组的首地址 函数名 — 该函数(程序代码)的存储首地址,即 函数入口地址 取值为函数入口地址的指针变量,称为指向函数 的指针变量,简称函数指针。 • 函数指针的说明格式 数据类型 (*函数指针名)( ) 其中数据类型是指函数返回值的类型。
• 函数指针的使用 给函数指针变量赋值时,只需给出函数名而不必 (也不能)给出参数。如: int a,b,c,max(int,int), (*p)( ); p=max; /* p为函数指针变量,max为函数名*/ 函数可通过函数名调用,也可通过函数指针调用, 如上例后,只需用(*p)代替函数名max即可。如: c=(*p)(a,b); 对函数指针变量,象p+i、p++、p--等运算无意义。
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; }
• 函数指针数组 函数指针可以组成指针数组,称为函数指针数组, 该数组的元素都是函数指针。 说明格式: 数据类型 (*函数指针数组名[常量表达式] )( ) 其中常量表达式的值表示函数指针数组的长度。 例2:用循环语句for,while,do-while和if-goto编制 计算 的函数,并验证其正确性。要求用函数指 针数组。