600 likes | 748 Views
第 8 章 指针 指针 : C 的一个 重要概念 、 重要特色 。它使 C 具备了强大的功能,使 C 成为程序设计语言之首。正确而灵活地运用它,就可以方便地处理很多其它高级语言所不能处理的问题。 不掌握 指针 等于没有掌握 C 语言的 精华 。. 8 . 1 指针的概念 简单地说, 指针就是地址 。 要掌握指针的概念就必须弄清: ■ 内 s 存地址概念 ? ■ 变量与地址的关系 ? ■ 如何通过地址进行变量的存取 ?. int i,j,k; i=3; j=6; k=i+j;. 程序经编译后,变量名被转化为对应的存储单元的地址。. 说明例:
E N D
第8章 指针 指针:C的一个重要概念、重要特色。它使C具备了强大的功能,使C成为程序设计语言之首。正确而灵活地运用它,就可以方便地处理很多其它高级语言所不能处理的问题。 不掌握指针等于没有掌握C语言的精华。
8.1 指针的概念 简单地说,指针就是地址。 要掌握指针的概念就必须弄清: ■内s存地址概念? ■变量与地址的关系? ■如何通过地址进行变量的存取?
int i,j,k; i=3; j=6; k=i+j; 程序经编译后,变量名被转化为对应的存储单元的地址。 说明例: 内存用户数据 1000 3i 1002 6j 1004 9k 对变量值的存取总是按地址进行的----直接访问。
也可以采用“间接访问”方式: 先将变量i的地址存放到另一变量p1中,要访问i时,先取出p1的内容(变量i的地址),再去访问该地址所对应的内存单元中的内容(变量i的值)。
int i,j,k; i=3; j=6; k=i+j; int *p1, *p2; p1=&i; p2=&j; 内存用户数据 1000 3 i 1002 6 j 1004 9 k 2000 1000 p1 2004 1002 p2
i 1000 1002 j k 1004 在以上概念的基础上对指针下定义: 变量的地址就是该变量的指针。 存放地址的变量称指针变量。 p1是指向变量i的指针变量。 1000 p1 p2 1002
8.2 变量的指针和指向变量的指针变量 变量的指针 指针变量 指向变量的指针变量 用“*”代表“指向” 如*p1代表它所指向的变量i,同一内存单元。 以下两个语句等价: i=3; 直接访问 *p1=3; 间接访问
int i,j,k; i=3; j=6; k=i+j; int *p1=&i; int *p2=&j; 4 内存用户数据 1000 3 i 1002 6 j 1004 9 k 2002 1000 p1 20041002 p2 *p1=4;
■指针变量的定义 指针变量也必须先定义后使用。 int *p1; ① *表示该变量为指针变量,但变量名是p1。 ② 一个指针变量只能指向同一类型的变量。 ③ 初始化. ④ 所占用的内存空间,操作系统32位、64位 int i,*p1; int i; float a; int *p1=&i; p1=&i; 合法 p1=&a; 不合法
■指针变量的使用 两种用法: ①用地址运算符& p1=&i; ②用指针运算符* (实行间接访问) *p1=100; k=*p1; 注意:1 指针变量只能放地址(指针) 2 必须初始化才能使用。 3 不能把地址赋给*p int i; int *p1,*p2; p1=100; p1=&i; *p1=100; *p2=100; p2=p1; *p2=10; *p1=&i;
例: main() { int a=100,b=10; int *p1,*p2; 定义指针变量,尚无体指向 p1=&a; p1指向a p2=&b; p2指向b //int *p1=&a,*p2=&b; 定义时完成指向 printf(“\n %d,%d”,a,b); printf(“\n %d,%d”, *p1,*p2); } 注意:要区别定义和引用中的“*”
p 要特别注意以下用法的后果: int *p; *p=100; 程序将执行的命令: 指针p所指向的整型变量所在的内存单元赋值100
例:输入a和b两个整数,按先大后小的顺序输出main()例:输入a和b两个整数,按先大后小的顺序输出main() { int a,b,*p1, *p2, *p; scanf(“%d,%d”,&a,&b);1000 5 a p1=&a; p2=&b;1002 9 b if(a<b) { p=p1; p1=p2; p2=p;} printf(“\n %d,%d”,a,b);2000p1 printf(“\n%d,%d”,*p1,*p2); 2004p2 }2006 p 改变p1和p2的指向 1000 1002 1002 1000
总结: 指针变量是专门用来存储变量地址的特殊变量 只要将某一变量的地址存入指针变量中,就可通过指针变量间接访问该变量。 为什么C语言要舍近求远来间接访问变量的内容呢?
■指针变量作为函数的参数 可将指针变量作函数的参数,接受实参地址,获得具体指向,进而通过指针变量间接访问主调函数的变量。
跨越逻辑上的限制 9 5 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(a<b)swap(&a,&b);2004 p2 printf(“\n %d,%d”,a,b);2006 t } 1000 1002
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(a<b)swap(a, b);2004 p2 printf(“\n %d,%d”,a,b);2006 t } 5 9 9 5
重要概念:使用指针变量作函数参数,被调函数可以将多个结果交给主调函数。重要概念:使用指针变量作函数参数,被调函数可以将多个结果交给主调函数。
int float *x1, float *x2 *x1 *x2 main() { int k; float a,b,c,x1,x2; scanf(“%f,%f,%f”,&a,&b,&c); k=root(a,b,c,&x1,&x2); if(k) printf(“\n %f,%f”,x1,x2); } 例:编写函数,求一元二次方程的两个实根。 #include “math.h” ? root(float a,float b,float c, ) { float d; d=b*b-4*a*c; if(d<0||a==0)return(0); ? =(-b+sqrt(d))/2/a; ? =(-b-sqrt(d))/2/a; return(1); }
main() { int a[100],i,max,min; float av; for(i=0;i<100;i++) scanf(“%d”,&a[i]); av=aver(a,100,&max,&min); printf(“ %d,%d,%f”,max,min,av); } 例:求n个数的最大值、最小值和平均值。 float aver(int a[], int n, int *max, int *min) { int i; float s=0; *max=a[0]; *min=a[0]; for(i=0;i<n;i++) { s+=a[i]; if(a[i]>*max) *max=a[i]; if(a[i]<*min) *min=a[i]; } return(s/100); }
1. 指针,指针变量: int *p,a; 初始化及引用: p=&a; *p=1; 2. 指针变量作为函数参数 void swap(int *a,int*b){….} int main(){ int a=9,b=8; swap(&a,&b); … }
int a[8]; 2000 5 2002 3 2004 2 2006 1 6 8.3 数组的指针和指向数组的指针变量 数组有一个首地址: 数组的指针。 每个数组元素也都有地址: 数组元素的指针。 8 7 4
p 2000 5 2002 1 2004 2 4 2006 7 6 8 ■指向数组元素的指针变量 int a[10],*p; p=a; 指向数组 p=&a[0]; 指向数组元素 0 3 9
■通过指针引用数组元素 p=&a[0]; p指向a[0](下标变量) *p=1; 等效于a[0]=1; 即可通过p来访问a[0] 也可以通过p来访问其它元素: *(p+1)=3; 等效于a[1]=3;(*(a+1)=3) 其中p+1指向a[1] 注意:p+1不是地址加1,而是加一个数据类型单 位。 int a[10];int *p=a; *(p+1)=5;
一般地,当p指向a[0]时: p+i ∽ a+i∽ &a[i] *(p+i) ∽ *(a+i)∽ a[i]∽ p[i] 即以下几个语句等效: a[i]=10; *(p+i)=10; *(a+i)=10; p[i]=10;
例:从键盘输入10个数到数组a: int a[10],i,*p=a,s=0 for(i=0;i<10;i++) scanf(“%d”,&a[i]); for(i=0;i<10;i++) scanf(“%d”,a+i); for(i=0;i<10;i++) scanf(“%d”,p+i);
累加求和的各种用法: for(i=0;i<10;i++)s+=a[i]; for(i=0;i<10;i++)s+=*(a+i); for(i=0;i<10;i++)s+=*(p+i); for(i=0;i<10;i++)s+=p[i]; for(i=0;i<10;i++)s+=*p++; 等效于*(p++),右结合 for(p=a;p<a+10;p++)s+=*p;不能使用a++
p 使用指针变量访问数组时,要特别注意指针变量的当前值。 注意下例: main() { int a[10],*p=a,i; for(p=a;p<a+10;p++) scanf(“%d”,p); for(i=0;i<10;i++)printf(“%d”,*p++); } p=a;
■数组名作为函数参数 有了指针概念的基础上,重新回顾数组名作为函数参数时,数据的传递情况: 例:将数组a中的n个数按相反顺序存放。
x a i=0 m=4 int inv(int x[], int n) { i,j,m,t; main() m=(n-1)/2; { for(i=0;i<=m;i++) int a[10],i; { j=n-1-i; 输入a t=x[i]; inv(a,10); x[i]=x[j]; 输出a x[j]=t; } } } a与x共用同一片内存单元 j=9
a 1000 { j=n-1-i; t=*(x+i); *(x+i)= *(x+j); *(x+j)=t; } 指针法 int inv(int *x, int n) 指针变量作函数参数时的传递情况 { i,j,m,t; main() m=(n-1)/2; { for(i=0;i<=m;i++) int a[10],i; { j=n-1-i; 输入a t=x[i]; inv(a,10); x[i]=x[j]; 输出a x[j]=t; } } 下标法 } x 1000
i j 进一步优化: int inv(int *x, int n) { main() int *i=x,*j=x+n,t; { for(;i<j;i++,j--) int a[10],i; { 输入a t=*i; inv(a,10); *i=*j;输出a *j=t; } } } 优点:简练 效率高
选择法:首先选择最小的数放在0位置,再在剩下的数中选择最小的数放在下一位置,┈┈,依次类推。选择法:首先选择最小的数放在0位置,再在剩下的数中选择最小的数放在下一位置,┈┈,依次类推。 void sort(int *a, int n) { int i,j,t; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(a[i]>a[j]){t=a[i]; a[i]=a[j]; a[j]=t;} } 只将形参改为指针变量,仍按下标法使用 void sort(int *a, int n) { int i,j,t; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(*(a+i)>*(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;i<a+n-1;i++) for(j=i+1;j<a+n;j++) if(*i>*j) {t=*i; *i=*j; *j=t;} } main() { int a[10],j; for(j=0;j<10;j++)scanf(“%d”,a+j); sort(a,10); for(j=0;j<10;j++)printf(“%5d”,a[j]); }
sort(a,5); sort(a+5,5); 分段排序? main() { int a[10],j; for(j=0;j<10;j++)scanf(“%d”,a+j); for(j=0;j<10;j++)printf(“%5d”,a[j]); }
数组名和指针的区别 • 1. 数组名指代一种数据结构,这种数据结构就是数组; char str[10]; char *pStr = str; sizeof(str)=10; sizeof(pStr)=4(平台32B);;;\ • 2. 数组名可以转换为指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;char str[10]; char *pStr = str;str++; //编译出错,提示str不是左值pStr++; //编译正确
数组名和指针的区别 • 3.数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;而且它还失去了其常量特性,可以作自增、自减等操作,可以被修改。 • void arrayTest(char str[]){ printf(“sizeof(str) =%d”,sizeof(str) );//输出指针长度str++; //编译正确 } • int main() {char str1[10] = “Hello";arrayTest(str1); return 0;}
■指向多维数组的指针和指针变量 从本质上说,多维数组的指针与一维数组的指针相同,但在概念上和使用上,多维数组的指针要复杂些。 以二维数组的指针为例:
讨论以下用法的效果: for(i=0;i<3;i++) scanf(“%d”,a[1]+i+1); 输入数据:1 2 3 讨论以下用法的效果: for(i=0;i<3;i++) scanf(“%d”,a+i+1); 输入数据:1 2 3 讨论以下用法的效果: for(i=0;i<3;i++) scanf(“%d”,a[1]+i); 输入数据:1 2 3 讨论以下用法的效果: for(i=0;i<3;i++) scanf(“%d”,a+i); 输入数据:1 2 3 int a[3][4] a[0] a+0 1000 3 5 8 7 a+1 a[1] 1008 2 4 6 9 a+2 a[2] ●二维数组的地址 : 一维:a,&a[i],a+i 二维: a,&a[i][j],a+i(行指针),a[i](特殊的一维数组元素,列指针),a[i]+j a+i:只能指向第i行的第一个元素,行指针 a[i]+j:只能指向第i行中的某一个元素,列指针 1016 1 6 0 4
a+0 1000 a[0] a+1 1008 a[1] 15 a+2 1016 a[2] *(a[1])=15 *(a+1)=1008 注意:指针运算符*作用在行指针上的结果仍是指针----列指针; *作用在列指针上的结果---具体元素。 *(a+0),*(a+1),*(a+2) ——仍是地址。*(a+i) *(a[0]),*(a[1]),*(a[2]) ——具体元素值。*(a[i]) *(a+i)+j 也是地址,但要区别: (a+i)+j——行指针(a+1)+1 ? *(a+i)+j——列指针*(a+1)+1 ?
如果要通过a+i形式的地址访问数组元素的具体内容,则:如果要通过a+i形式的地址访问数组元素的具体内容,则: *(*(a+i))或 *(*(a+i)+j) 如:*(*(a+1)) —— a[1][0] *(*(a+1)+2) —— a[1][2] 讨论:int a[3][3]; *(a+2) *(*(a+1)+2) *(a[1]+1) *(*(a+1)+3)
例:求数组a的所有元素之和。 可有多种用法: for(i=0;i<3;i++) for(i=0;i<3;i++) for(j=0;j<4;j++) for(j=0;j<4;j++) s+=a[i][j]; s+=*(a[i]+j); for(i=0;i<3;i++) for(j=0;j<4;j++) s+=*(*(a+i)+j);
k=p[1*4+2]; 合法 k=*(p+1*4+2); k=p[1][2]; 不合法 k=*(*(p+1)+2); 1 2 3 4 5 6 7 8 ●指向二维数组的指针变量 main() { int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int i,k,*p; // int b[10];int *q=b; p=*a;//p=*(a+0);p=a[0];p=&a[0][0];(列指针变量) k=*p; k=*(p+2); for(p=*a;p<*a+2;p++)printf(“%3d”,*p); for(p=*a;p<*(a+1)+2;p++)printf(“%3d”,*p); for(p=*a;p<a[1]+2;p++)printf(“%3d”,*p); } 9 10 11 12 12 123456 123456
●指向由m个元素组成的一维数组的指针变量 可以这样定义一个指针变量: int (*p)[4] 表示p为指向由4个整型数据组成的一维数组。 注意不能省略:() int *p[4]; 具有4个指针类型数据的一维数组 int a[3][4]; 初始化:p=a+i; (行指针变量) p=a; 可通过p引用a[i][j]: p[i][j]或 *(*(p+i)+j)
例:求矩阵的上三角元素之和。 main() { int a[3][4],(*p)[4],i,j,s=0; 输入a p=a; for(i=0;i<3;i++) for(j=i;j<4;j++) s+=p[i][j]; 或 s+=*(*(p+i)+j) printf(“\n %d”,s); }
●多维数组的指针作函数参数 用于接受实参数组地址的形参可用两种:行指针和列指针。 以方阵转置为例: void at(int (*a)[3]) 用行指针 { int i,j,t; 缺点:不通用 for(i=0;i<3;i++) for(j=i+1;j<3;j++) { t=a[i][j]; a[i][j]=a[j][i]; a[j][i]=t; } }
用列指针: void at(int *a,int n) { int i,j,t; for(i=0;i<n;i++) for(j=i+1;j<n;j++) { t=a[i*n+j]; a[i*n+j]=a[j*n+i]; a[j*n+i]=t; } } 优点:通用 在编通用函数时,一般使用列指针。
8.4 字符串的指针和指向字符串的指针变量 ■字符串的表示形式 可用两种方法访问字符串: ①用字符数组存放字符串 ②用字符指针指向一个字符串
main() { char c[5]=“abc”; 定义字符数组,并将字符串存入 char *p1=c,*p=“abc”;定义指针变量,指向字符串 printf(“%s”,c); 通过数组名访问字符串 printf(“%s”,p); 通过指针变量访问字符串 printf(“%-c”,*(p+1)); 通过指针变量访问字符 } c 1000 a c \0 b a b c \0 p 1000
用指针变量处理 例:字符串拷贝操作。 main() { char a[]=“abcdef”,b[20]; int i; for(i=0; *(a+i)!=’\0’; i++) *(b+i)=*(a+i); *(b+i)=’\0’; printf(“%s”,b); } main() { char a[]=“abcdef”,b[20],*p1, *p2; p1=a; p2=b; for( ; *p1!=’\0’;p1++,p2++) *p2=*p1; *p2=’\0’; printf(“%s”,b); }