第十章 指针 分析C程序的变量所存放的数据: 数值型数据:整数、实数 字符型数据:字符、字符串 这些变量具有以下性质: 第十章 指针 分析C程序的变量所存放的数据: 数值型数据:整数、实数 字符型数据:字符、字符串 这些变量具有以下性质: 占有一定长度的内存 单元 如:int x; x占二字节、二个单元 每一个变量都有一个地址,为无符号整数,称为地址,它不同于一般的整数。 问题:能否对地址运算? 能否用一个变量保存地址?
§10.1 指针的概念 一、数据在内存中的存放 内存:为一个连续编号(连续地址)且以一个单元为一个字节的连续存贮区。 若程序中定义了三个int变量i, j, k int i= – 5, j=5, k=10; 2000 2001 2002 2003 2004 2005 3001 – 5 +5 10 系统分配i在地址为2000的单元 j在地址为2002的单元 k在地址为2004的单元
当程序中要用它们的值时: y=i+j+k; 则系统通过一张变量名与地址对应关系表: 分别 找到i的地址为2000,将2000,2001中的数据–5读出; 找到j的地址为2002,将2002,20003中的数据5读出; 找到k的地址为2004,将2004,2005中的数据10读出。
上述过程称为变量的“ 直接访问” 直接访问:在程序中表现为直接使用存放该数据的变量名。 间接访问:如果将某一变量的地址(如i的地址2000)存放到另一个变量x,则可通过x来存取i的值。
2000 –5 –5 直接访问 i 3001 2000 –5 –5 间接访问 x i 显然,x与i是通过i的地址联系起来的,一个变量的地址—称为该变量的指针。因此,i的指针为2000,而存放地址(指针)的变量叫做指针变量。 如:x
§10.2 指针变量的定义和引用 首先明确一个概念:指针变量也有类型,因为类型涉及到所占单元数,也就是指针变量在计算时有所不同。例如: 当x为一个指向整型变量的指针变量时。 x+1: 设x原为2000,则x+12002 当x为一个指向float型的指针变量时 x+1:设x为2000,则x+12004
一、 定义指针变量的形式: 类型名 变量名 表示该变量为指向某类型变量的指针变量 如: int x; x只能指向整型变量,即只能存放整型变量的地址。
二、引用指针变量 将一个变量的地址(指针)赋给一个指针变量,用取地址运算符:& int i, j, x; x=&i; 如果将整数赋给地址量x=1000;编译会提出警告性错误,但还是有值(地址)
存取指针变量所指向变量(目标变量)的值: 用指针运算符*, 即:*x 为 i , &为同级运算符,结合性自右至左。 则当&或&在一起时,具有抵消作用 如上例: &i相当于xi &x相当于&ix
三、指针变量作为函数参数 前面讲过:函数实参 形参,于是,形参数 据值的改变不会影响实参。 但当用地址(指针变量)作参数时,与数组名类似。 则:形参、实参均为地址量。 例: swap(p1, p2) int p1, p2; { int p; p=p1; p1=p2
p2= p; } main( ) { int a, b; int x1, x2; scanf("%d,%d",&a, &b); x1=&a; x2=&b; swap(x1, x2); printf("%d,%d \n",a, b); 执行过程分析 (设a10, b20) &a 10 x1 a &b 20 x2 b
p1 p2 &a &b 20 10 b a &b &a x2 x1 p= p1; p1= p2; p2 =p; p1 10 a p p2 20 b
p1 p2 &a &b 10 20 b a &b &a x2 x1 释放p1, p2后 &a 20 &b 10 x1 a x2 b
1. 注意函数中p为普通变量,并非地址量,如p为地址量,它为哪一个变量的地址?这时: int t,*p=&t;(允许) p= p1 2. 如果swap函数中的交换语句改为: int p1, p2, p; p=p1; p1=p2; p2=p; 则仅将p1, p2的指向改变,函数返回后,p1, p2释放, a、b中的内容依然未改变。
3. 不用地址量,也未能实现交换,即: sway(p1, p2) int p1, p2; { int p; p=p1; p1=p2; p2=p; } 则:只将p1,p2的内容改变,而不能使a, b交换。
4. 结论:当需要被调函数改变调用函数中n个变量的值时,需将这n个变量的地址(用指针变量、或直接用地址量&a, &b)作为实参传给指针形参,且被调函数通过改变指针形参所指向的变量来实现实参中变量值 的改变。
§10.3 数组的指针及指向数组的指针变量 前面介绍:一个变量的地址为该变量的指针。当用一个变量来存放该地址(指针)时,称为指针变量。 §10.3 数组的指针及指向数组的指针变量 前面介绍:一个变量的地址为该变量的指针。当用一个变量来存放该地址(指针)时,称为指针变量。 又有说明: 一个数组元素相当于一个简单变量。于是,亦可用一个指针变量来指向数组元素。 不同点: 数组元素连续地占用内存单元,则当一个元素的指针已知时,其它元素的指针亦可知道。
一、数组元素指针变量的定义与引用 定义方法与简单变量指针定义相同,但引用略有不同 例: int a[10]; int p; 定义 p=&a[0]; 将a的第0个元素的地址p C语言规定: a的首地址即可用&a[0]表示,亦可用a表示 所以:p=&a[0]; 和 p=a等价
可以在定义指针变量时赋初值: int p=&a[0]; 或 int p=a; (等价于) int p; p=a; 它不同于语句: p=&a[0]; × 1. 由首地址指针来引用数组中的其它元素。 设p为a的首地址,p=a,或p=&a[0];
则p+1是什么意思? p+1为a[1]的地址,当a为int,p+1相当于地址+2, 而当a为float时,p+1相当于地址+4. 一般地: p+i a[i] 的地址,或为a+i 引用a[i]的值: a[i], (p+i), (a+i)
2. 在搜索数组元素时,用p+i比a[i++]来得快 例:int a[10], i; int p; p=a; 则: (a+i)和a[i]都得计算元素地址,而p++后,p可直接指向a[i]。
3. 关于指针的运算 指针运算符 与++, – –同级,且自右至左。 p++ / p – – 的作用: 若p=a或p=&a[0], 则p++相当于p=p+1, 即指向a[1] 若p=&a[5], 则p– –相当于p=p –1,即指向a[4]
p++与(++p) p++相当于(p++) 若p=&a[0], 则:先取p的值,即 a[0]的值,再使p+1, p指向a[1]. (++p)为先使pp+1, p指向a[1], (++p)取出a[1]的值。 (p)++ 或(p) – – (p)++:将p指向的变量的值自增1; (p) – –: 将p指向的变量的值自减1。
二、数组名作函数参数 前面已叙述过,当实、形参均为数组名时,调用时将实参数组首地址传递(单向)给形参数组,使它们共享内存。 例:编写函数,将数组各元素值取反。 main ( ) { void invert( ); int a[10], i; for (i=0;i<10; i++) scanf( "%d", &a[i]); invert(a, 10);
for (i=0; i<10; i++) printf("a[%d]=%d," ,i, a[i]); } void invert(x, n) int x[ ], n; { int i; for (i=0; i<n; i++) x[i]= –x[i]; return;
前面已分析: 可用指针表示数组。即:指针运算引用数组元素,于是,可用指针变量作为形参接收实参数组首地址。 分析参数传递情况: a[10]: a[0] a[1] a[2] … a[9] a x ‖ x[0] x[1] x[9] 即:x, a共享同一段内存单元。 前面已分析: 可用指针表示数组。即:指针运算引用数组元素,于是,可用指针变量作为形参接收实参数组首地址。
for (i=x; i<(x+n) ;i++) i= – i return; } 函数改为: void invert (x, n) int x, n; { int i; for (i=x; i<(x+n) ;i++) i= – i return; } a[0] a[1] a[2] … a[9] a: x x+1 x+9 x (x+1) (x+9) 参数传递情况:
进一步分析:在主函数中也不一定要用数组名a,只要用一指针变量即可。设int p; p=&a[0], 则: invert(p, n); 即可完成同样功能 总结以上情况,有四种参数传递形式: (1) 实参、形参均为数组名 (2) 实参为数组名、形参为指针变量 (3) 实参、形参均为指针变量 (4) 实参为指针变量,形参为数组名。 特别是(3)种情况:当不是数组元素时,即实参形参均为单个指针变量时,实现了实、形参共用内存单元。
三、指向多维数组的指针和指针变量。 1. 多维数组的地址: 将一维数组内容扩充,也可用一指针变量指向多维数组,以二维数组为例加以讨论。 设: static int a[3][4] = {{1, 2, 3, 4}, { 5, 6,7, 8}, {9, 10, 11, 12}};
12 1 2 3 4 5 6 7 8 9 10 11 第1行 第2行 第3行 a[0] a[0]+1 a[0]+2 a[0]+3 a[1] a[1]+1 a[1]+2 a[1]+3 a[2] a[2]+1 a[2]+2 a[2]+3 数组名a[0] 数组名a[1] 数组名a[2] a a+1
设首地址a:2000, 则a+1:2008, a+2: 2016 从一维数组中我们认为:a[0]与a, (a+0)等价 所以:a[0][i]的地址&a[0][i]还可表示为(a+i) a[0][i]的地址:a[0]+i, a+i, 和&a[0][i] a[1][i]的地址:a[1]+i, (a+1)+i, &a[1][i] a[i][j]的地址:a[i]+j= (a+i)+j 于是:a[i][j]可表示为 *(*(a+i)+j)
2. 多维数组的指针 有了多维数组的地址概念后,可用指针变量来指向多维数组 指向数组元素 当用一个指针变量指向第0行的首地址后,即可搜索到全部元素。 设二维数组的大小为m*n, 则第i, j个元素相对于a[0][0]的个数为mi + j (i=0,1,…,n–1) 设:int p; p=a[0]; 则第a[I][j] 的地址为p+i m+j
例: 设有一3×4的二维数组,利用指针逐行逐个输出元素。 main ( ) { static int a[3][4]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; int p; for (p=a[0]; p<a[0]+12; p++) { if ((p – a[0]) %4= =0) printf("\n"); printf ("%4d", p); } 注: 为什么p的初值不能写成p=a;?
指向数组的每一行 仍为上例为例: 当p=a, 则p+1为下一行首地址,于是引用第(i, j)个元素的方法: ((p+i)+j) 第i行首地址,相当于a[i] 这时,需将p的定义改为 int (p)[4]; a) 不能去掉( ),否则为int p[4]为指针数组 b) 之所以这样定义是为了使p+1的地址为a[1], 即:移动4个元素。
例:有一个班,3个学生,各学4门课,计算总平均分数,以及第n个学生的成绩。 程序如下: main ( ) { void average( ); void search ( ); static float score [3] [4]={{65, 67, 70, 60},{80,87,90,81}, {90,99,100,98}}; average (*score, 12); /*求12个分数的平均分8*/ search( score,2); /*求第2个学生成绩*/ }
void average (p, n) float *p; int n; {float *p_end; float sum=0, aver; p_end=p+n-1; for (; p<=p_end; p++) sum=sum+(*p); aver=sum/n; printf("average=%5.2f\n", aver); }
void search (p, n) float (*p) [4]; int n; {int i; printf("the scores of No.%d are:\n", n); for (i=0; i<4; i++) printf("%5.2f ", *(*(p+n)+i)); }
程序运行结果如下: average=82.25 the scores of No. 2 are: 90.00 99.00 100.00 98.00
应有举例: 设有三个学生,各有四门功课,求总的平均分,各学生总分,且将三个学生的成绩按总分排序。 要求用指针变量: 实参可用指向数组的指针变量(实数组名) 实参可用指向一维数组的指针变量;即: 数组名
一、流程图: 指针,指向一维数组 输入三学生单科成绩 学生人数 计算各学生总分 调用函数sumscore(p,n) 指向二维数组 人数 计算总平均分 调用函数average(p,n) 返回值float 人数 按各学生总分排序 调用函数sort(p,n) 指向一维数组 打印各科成绩总分及总平均分 调用函数printscore(p,n) 人数 指向二维数组 结束
二、程序 1. 主函数 main ( ) { void sumscore ( ); float average( ); void sort( ); void printscore( ); static float score[3][5]={{65, 80, 90, 100}, {80, 66, 75, 90}, {58, 60, 90, 83}};
sumscore(score, 3); printf("\n average=%f", average(score, 3)); sort(score, 3); printscore(score, 3); }
2. sumscore函数–––求总分 void sumscore(p, n) float p; int n; { float aver, p_end; p_end=p+5 n –1; for (; p<=p_end; p=p+5) (p+4)= p+ p(p+1)+ (p+2)+ (p+3); }
3. average函数–––求总平均分 float average(p, n) float (p)[5]; int n; { float aver=0.0; int i; for (i=0; i<n; i++) aver=aver+ ((p+i)+4); aver=aver/n return(aver); }
3. sort函数–––排序 有二种方法:(1)循环时用简单变量 (2)循环时用指针变量 方法(1) void sort (p, n) float p; int n; { int i, j, k, m; float t; for (i=0; i<n –1; i++)
{ k=i; for (j=i+1; j<n; j++) if ((p+4+5 i)< (p+4+5 j)) k=j; if (k!=i) for (m=0; m<5; m++) {t= (p+m+5 i); (p+m+5 i)= (p+m+5 k); (p+m+5 k)=t; }
方法(2) void sort (p, n) float p int n; { float p1, p_end, k, t; int m; p_end=p+5 n –1; p=p+4; for (; p<p_end –5; p+=5)
{ k=p; for (p1=p+5; p1<p_end; p1+=5) if (p< p1) k=p1; if (k!=p) for (m=0; m<5; m++) { t= (p –m); (p – m)= (k1 – m); (k1 –m)=t; }
4. printscore函数–––打印输出 void printscore(p, n) float (p)[5]; int n; { int i, j; printf("\n A B C D sum\n"); for (i=0; i<0; i++ ) { printf("\n"); for (j=0; j<4; j++) prinft("%5.2f", ((p+i)+j)); }
§9.4 字符串指针和指向字符串的指针变量 一、字符串指针的定义 形式 char p; 表示p为指针变量,可指向一个字符串的首地址。 例:main( ) { char p; char s[ ]="I am a student!" ; p=s;
进一步:main( ) { char p="I am a student!"; … 或者: main ( ) { char p; p="I am a student!"; … 则: p: I (p+3):m
1. “…”一个串代表示该串的首地址 2. 在输入(scanf)和输出(printf)中,也可用%s将整个串一次输入/输出 例:将字符串a复制到字符串b 1)用字符数组实现 2)用字符指针实现 1) main ( ) { char a[ ]="I am a teacher!"; char b[20]; int i;
for (i=0; (a+i)!=' \0'; i++) (b+i)= (a+i); (b+i)= ' \0'; printf("string a is: %s\n", a); printf("string b is:"); for (i=0; b[i]= ' \o'; i++) printf("%c", b[i]); printf("\n"); }
2) 用指针变量实现 main ( ) { char a[ ]= "I am a teacher!"; char b[20], p1, p2; int i; p1=a; p2=b; for (; p1!='\0'; p1++, p2++) p2= p1; p2= ' \0'; … 同上 }
二、字符串指针作函数参数 与数值变量指针一样,字符串指针,字符串数组均可作为函数参数 结果:可在函数中改变实参内容。 例: 用函数调用实现字符串的复制 方法:(1)字符数组作参数 void copy_string (from, to) char from[ ], to[ ]; { int i=0; while (from[i]!='\0')
{to[i]=from[i]; i++} to[i]='\0 '; } main ( ) {char a[ ]="I am a teacher"; char b[ ]="You are a student"; printf("string_a=%s\n string_b=%s\n", a,b); copy_string(a, b); printf("\n string_a=%s\n string_b=%s\n", a, b)
运行结果: string_a=I am a teacher string_b=You are a student string_a=I am a teacher string_b=I am a teacher 分析a, b前后的内容。b中仍保留原来的一部分内容。但输出b中内容时,遇到第一个'\0'时结束。 在main中也用字符串指针: char a="I am a teacher".; char b="You are a student." …
(2) 形参用字符指针变量 void copy_string (from, to) char from, to; { for (; from!='\0 '; from ++, to++) to = from; to = ' \0 '; } 或:将for改用while while (from!= ' \0 ' ) {to= from; to++; from++;}
进一步:while((to = from)!='\0') {from++;to++;} 实际上'\0'的ASCII码为0,故可将 while ((to ++ = from++)!=0) 简化为: while (to++ = from++)
字符数组作参数的实、形参调用的四种情况 实参 形参 数组名 数组名 数组名 字符指针 字符指针 字符指针…… 字符指针 数组名
三、字符数组与字符指针变量 前面介绍中,字符数组与字符指针变量在使用中具有一定的统一性,但它们之间仍有以下区别: 1. 赋值方式不同: 对字符数组只能对各个元素赋值,不可将整个串赋给数组名, 即:char a[14]; a ="Good Morning! "; 若用指针变量,即可将整个字符串赋值: char a a= "Good Morning! ";
2. 定义数组后,系统给它分配内存单元。具有确定的内存地址,但指针变量定义后,若未对它赋地址,则它并不指向任何单元。 如果:char a; scanf("%s", a); 则:系统十分危险,由于a可能指向一个系统单元,因此,就会破坏该单元的值。 应改为:char str[10], a; a=&str[0]; 或a=str; scanf("%s", a);
3. 指针变量的值可以改变,而数组名(首地址)是不可改变的,即:在程序中不可直接对数组名赋值。 下面的用法不允许: char str[ ]={"Good! "}; str=str+7; 4. 指针变量在确定了值后,可以采用数组元素的方法引用指向的值: char a[ ]={"Good"}; char b=&a[0]; printf("%c", b[2]):
5. 可用指针变量(代表一个字符串)存放格式控制序列,即: char format format="a=%d, b=%f \n"; printf(format, a, b); 当然,format也可为一数组,但赋值时不如指针变量那样方便。
§10.5 函数的指针及指向函数的指针变量 基本概念:前面已述,每一个变量有地址,于是可用一个指针变量来保存,而一个函数实际上为存放在内存中的一段程序,它有一个入口地址––––称为函数的指针。 存放函数指针的变量–––指向函数的指针变量。
一、用函数指针变量调用函数 1. 函数指针变量的定义 形式:类型标识符 (变量名) ( ) 例:int (p) ( ); 表示p为一个函数指针变量,用于存放一个函数的入口地址,但该函数的返回值必须为int型。
2. 给函数指针变量赋值 函数指针变量=函数名; 它不是实参,不是调用,而是将入口地址赋给该变量。 3. 通过函数指针变量调用函数的方法 (函数指针变量名) (实参表列)
例: 求a、b中最大者函数。 int max(x, y) int x, y; { int z; z=z(x>y)? x:y; return(z); } main ( ) { int max( ); int (p)( ); int a, b, c; p=max; scanf("a=%d, b=%d", &a,&b); c=(p) (a, b) printf("max value=%d", c) 4. 对函数指针变量进行运算无意义。
二、函数指针变量作为函数参数 前面介绍过:简单变量、数组名、指针变量均可作为函数的参数。 能否用函数指针变量作参数呢? 当然可以! 意义:当一个函数被调用后,执行过程中需根据实际情况调用f1, f2,或调用f3, f4,于是在该函数中即可用函数指针变量作形式参数。
例:sub (x1, x2) int (x1 )( ), (x2 )( ); { int a, b, i, j; a=(x1) (i); b=(x2) (i, j) } 于是,可用 sub(f1, f2)或sub(f3, f4)调用sub,表示执行sub时,根据实参传递过来的函数入口地址而调用f1, f2或f2, f4.
例2. 设计一个函数process, 每次实现不同的功能,当用不同的函数名作实参调用process时,process再去调用相应的函数。 程序如下: main ( ) {int max( ), min( ), add( ); int a, b; printf("enter a and b:"); scanf("%d, %d", &a, &b);
printf("max="); process(a, b, max); printf("min=") process(a, b, min); printf("sum="); process(a, b, add); }
max(x, y) int x, y; {int z; if (x>y) z=x; else z=y; return(z); } min(x, y) int x, y; {int z; if (x<y) z=x; else z=y; return(z); }
process (x, y, fun) int x, y; int (* fun) ( ); {int result; result=(*fun) (x,y); printf("%d\n", result); } add(x, y) int x, y; {int z; z=x+y; return(z); }
运行情况如下: enter a and b:2, 6 max=6 min=2 sum=8 注:当用函数名作参数时,不论函数返值类型如何均应作说明,以与变量名相区别。
§10.6 返回值指针值的函数 本概念比较简单,既然函数返回值可以是整、实型等数据。当然也可以是指针值,只是函数定义形式略有不同: 类型标识符 函数名(形参表列) 例: 有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
程序如下: main ( ) {static float score [ ] [4]={{60,70,80,90}, {56,89,67, 88}, {34, 78, 90, 66}}; float * search ( ); float * p; int i, m; printf("enter the number of student:"); scanf("%d", &m); printf("The scores of No. %d are :\n", m);
p=search (score, m); for (i=0; i<4; i++) printf("%5.2f\t", * (p+i)); } float * seaarch (pointer, n) float (* pointer) [4]; int n; {float * pt; pt= * (pointer +n); return (pt);
运行情况如下: enter the number of student: 1 The scpres pf Mp. 1 are: 56.00 89.00 67.00 88.00
§10.7 指针数组和指向指针的指针 一、指针数组 数组–––同种类型的数据集合。 当每一个元素均为指针类型数据时,该数组被称为指针数组。 1. 定义形式 类型说明符 数组名[常量] 例:int p[10]; p为指针型数组,其每一个元素为一指针型变量。用来存入一组地址。
2. 应用 一个重要的应用––––存放一组字符串 一般情况下,当有一组字符串,如一组书名,也可用二维字符数组存放,但存在二个问题: 字符串长度不一,于是只得以最长字符串的长度作为二维数组列数,造成空间浪费。 在对字符串排序时,若交换数组元素,时间太长。 所以,用指针数组(一维)保存各字符串首地址,且交换时只需交换指针数组各元素––首地址即可解决上述二个问题。
例:将若干字符串按字母顺序(由小到大)输出。 main ( ) {void sort ( ); void print ( ); static char * name[ ]={"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"}; int n=5; sort (name, n); print(name, n); }
void sort (name, n) char *name [ ]; int n; {char *temp; int i, j, k; for (i=0; i<n-1; i++) {k=i; for (j=i+1; j<n; j++) if (strcmp (name [k], name[j])>0 k=j;
if (k ! =i) temp=name[i], name[i]=name[k]; name[k]=temp;} } void print (name, n) char * name[ ]; int n; {int i; for (i=0; i<n; i++) } printf ("%s\n", name[i]);
运行结果为: BASIC Computer design FORTRAN Follow me Great Wall
二、指向指针的指针 将指针变量的概念拓广: 一个数据变量有地址,则可用一指针变量来存放该地址。那么一指针变量也有地址,可否用另一个指针变量来存放?回答是肯定的。 &p &x pp p x 定义形式: int p, pp;
例: main( ) { static char * name [ ]={"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"}; char * * p; int i; for (i=0; i<5; i++) {p=name+i; printf ("%s\n", * p);} }
运行结果如下: Follow me BASIC Great Wall FORTRAN Computer design name[0]
一组字符串只能用指针数组存放: char name[ ]={"Good", "Bad ", "Are ", "Teacher ",}; 于是每一个元素name[i]为字符串指针变量 char p; p=&name[i]; 或 p=name+i; 则: p就表示第i个字符串的首地址,可用p输出字符串,指向指针的指针称为二级间接访问。如:int x, p, p; 则 p=&x; pp=&p; 则 p表示x
三、指针数组作main( )函数的形式: main( )既可为无参函数,也可为有参函数 有参函数形式: main (argc, argv) int argc; char argv[ ]; … 那么它的实参从何而来?分析DOS系统下的命令: c:\> copy FILE1.C FILE2.C
其中:copy为命令 FILE1.C 和FILE2.C为其参数。 对于一个在DOS下运行的C程序,也可以给一些参数: c:> zou 参数1 参数2 …参数n–1 当以这种方式运行C程序,则argcn, argv为指针数组,每一个元素指向包含命令在内的n个字符串。 即: argv[0]"zou" argv[1] "参数1" … argv[n –1] "参数n –1 "
例:编写main( )函数,将后面的参数输出: main (argc, argv) int argc; char argv[ ]; { while (argc>1) { ++argv; printf("%s\n", argv); – – argc; }
上述文件设为file1 则运行时 file1 China Beijing Changsha China Beijing Changsha