第 10章 指针主要内容:
( 1)指针和地址
( 2)指针常量和指针变量
( 3)指针与数组的结合应用
( 4)指针与函数的结合应用
( 5)指针数组与指向指针的指针变量重点:
指针变量的定义与引用通过指针访问数组元素指针变量作为函数参数返回指针值的函数
10,1 指针和指针变量
例 10.1:数据的 直接访问 与 间接访问,
main() a p
{ int a,*p; FFD6H
scanf("%d",&a);
p=&a; /*变量 p保存 a的地址 */
printf(“\n a=%d”,a); /*通过变量名访问单元,FFD6H*/
printf(“\n *p:%d”,*p); /*通过指针变量 P访问单元,ffd6h*/
printf(“\n p:%x\n”,p); /*输出变量 p的值,即 a的地址 */
}
指针,就是 地址,变量的指针即指向该变量的地址。
指针变量,存放 内存 (变量、数组、函数等)地址的变量 。
25 FFD6H
指针变量
例 10.2 交换两个指针变量所指向的变量的值。
main()
{ int a=10,b=20,t;
int *p1,*p2; /*定义指向整型的指针变量 p1,p2*/
p1=&a;p2=&b; /*使 p1 指向 a,p2 指向 b*/
printf("\n before exchange:a=%d,b=%d",a,b);
t=*p1;*p1=*p2;*p2=t; /*交换 p1,p2指向的变量的值
*/
printf("\n after exchange:a=%d,b=%d",a,b);
printf("\n *p1:%d,*p2:%d",*p1,*p2);
}
图示其执行过程,见下页本例中指针变量的作用:
通过指针变量访问该指针变量指向的单元,指针变量的值不变。
指针变量的定义的一般格式为:
类型标识符 *指针变量名
例如,int *p;
float *q;
指针变量的引用:
必须先定义,后使用指针变量定义后,可以对其进行如下操作:
赋值、引用指针变量的值、访问指针变量指向的变量 等。
如上例中通过 p1,p2访问变量 a,b对应的单元,其中
p1表示指针变量,*p1表示 p1指向的变量(即变量 a),
&p1表示指针变量 p1所在单元的地址 。
回顾例 10.2:通过指针变量 p1,p2交换了变量 a和 b的值。
那么,为什么要用指针变量而不直接用变量名呢?
再回顾函数一章的例 8.4:编写函数 swap()交换两个变量的值,
函数如下:
viod swap( int a,int b)
{ int t; t=a; a=b; b=t;}
main()
{ int x=5,y=6;
swap(x,y);
printf("\nx=%d,y=%d",x,y);}
结果,x=5,y=6
程序并未实现交换 x,y的值,why?
因为 参数传递是单向的,对形参的改变不影响实参的值。
指针变量作函数参数解决以上问题的方法是:
指向变量的指针变量作函数形参,接收主调函数中实参变量的地址,实现传地址调用。
即在被调函数中通过指针变量访问(读取或修改)
主调函数中对应的变量,当返回主调函数后,
主调函数就得到了这些已修改过的变量的值。
因此,“传地址” 调用可以实现函数间多个数据的,双向传递”。
main() /*编写函数交换两个变量的值 */
{ int a,b; void swap(int *p,int *q);
printf("\n input 2 integers:");
scanf("%d%d",&a,&b);
printf("\n before swap:a=%d,b=%d",a,b);
swap(&a,&b);
printf("\n after swap:a=%d,b=%d",a,b);
}
void swap(int *p,int *q) /*交换两个变量的值 */
{ int t;
t=*p; *p=*q;*q=t;
}
调用过程中参数的对应关系,
Main,swap:
a p
b q
*p? a
*q? b
因此,在 swap函数中通过指针变量 p,q就可以访问 main
函数中的变量 a,b了。由此可以实现函数之间的多个
“数据传递”。
&a
&b
10.2 指针与数组
通过指针访问数组元素
10.2.1 指针与一维数组
int a[6],*pa,k;
a:
a[0] a[1] …… a[5]
After execute,pa=a;
a[0] a[1] …… a[5]
a:
pa pa+1 pa+2
例 10,5 分别用下标法和指针法访问一维数组 。
程序 ( 1),下标法
main()
{ int a [5]={1,2,3,5,7},i;
for(i=0; i<5; i++)
printf("%4d",a[i]);
}
程序 ( 2),指针法
main()
{ int a[5]={1,2,3,5,7},*p;
for(p=a; p<a+5; p++)
printf("%5d",*p);
}
程序 ( 3),地址法 ( 指针常量 )
main()
{ int a[5]={1,2,3,5,7},i;
for(i=0; i<5; i++)
printf("%5d",*(a+i));
}
指针运算
除 赋值、取地址 &、间接访问运算 * 外,对指针变量还可以进行 加、减、比较 运算,
p=a;? p=&a[0];
如果 p指向数组中的某个元素,则
p++,指针变量 p增 1,使 p指向下一个元素
p+k,该地址是指向 p后面的第 k各元素的指针
p-k,该地址是指向 p前面的第 k各元素的指针
例如,有如下定义
int data[20],p=data,q=p+10;
则解释以下表达式的含义:
*p *(p+1) (*p)++ *(p++) p-q
例 10.3利用指针求字符串的长度。
分析:设字符串用一维字符数组 str保存。
算法:
s1,首先让指针变量 p指向字符串的首地址。
s2,重复判断指针变量所指的字符 (*p)是否等于字符串结束标志‘ \0?:若不等,则指针 p指向下一个字符。
S3,两个指针相减 (p-str)即字符串的长度。
其中,s2用循环结构实现。 str 是数组名,代表数组的首地址,相当于指针常量。
例 10.3 程序,str
main()
{ char str[]="you & me"; p
char *p;
p=str; /*等价于 p=&str[0]*/
while(*p!=?\0?) p++;
printf("\n string_length:%d",p-str);
}
运行结果,string_length,8
P++的含义,p指向下一个数组元素。思考,p+=2呢?
P-str的含义,p和 str之间数据的个数(只有都指向同一数组时使用)。
程序中不能直接使用 str++,因为 str不是指针变量,而是指针常量 。
you&me\0?
或者用单独一个函数 len()实现:
int len(char *str)
{ int n=0;
while(*srt!='\0') n++;
return n;
}
main()
{ char s[80]="sale apples.";
int m;
m=len(s);
printf("\n lenth:%6d",m);
}
分析参数传递过程,s => str
例 10.6编写函数实现:将数组中的 n个整数逆序存放。
调用该函数实现将 10个整数逆序存放。
main()
{ int a[10]={1,2,4,6,8,9,7,5,3,10},i;
int *p;
void inv(int *q,int n); /*函数声明 */
printf("\n before inverse:\n");
for(p=a; p<a+10; p++)
printf(“%5d”,*p); /*用指针访问数组元素 */
inv(a,10);
printf(" \n after inverse:\n");
for(i=0;i<10;i++) printf(“%5d”,a[i]);/*用下标访问 */
}
void inv(int *q,int n) /*函数 inv( )实现逆序 */
{ int *i,*j,t;
for(i=q,j=q+n-1; i<j; i++,j--)
{t=*i; *i=*j; *j=t;}
return;
}
3、调用函数 inv()时,实参与形参的对应关系:
实参,a 10
形参,q n
结果:指针变量 q接收数组 a的首地址,即 q指向数组 a的第一个数组元素。因此,在函数 inv()中通过指针变量 i,j改变的是数组 a的元素。
函数 inv()与下面的函数 inver()功能等价:
void inver(int b[ ],int n)
{ int i,j,t;
for(i=0,j=n-1; i<j; i++,j--)
{t=b[i]; b[i]=b[j]; b[j]=t;}
return;
}
说明,形参数组名与指针变量等价 。此处用 b接收 a的首地址,
实现通过数组名 b访问数组 a.
指针法与下标法访问数组的区别,用指针速度快。
10.2.2 指针与二维数组
1,可以将二维数组看作一个一维数组进行访问,
通过指向数组元素的指针访问各元素 。
例 10.8 利用指针变量输出整型二维数组 ss中的各元素。
main()
{ int ss[2][3]={2,4,6,8,10,12};
int *p;
printf("\n");
for(p=ss[0];p<ss[0]+6;p++)
/*p=ss[0]与 p=&ss[0][0]等价
*/
printf("%6d",*p);
}
说明,因为 p是指向整型数据的指针变量,因此初值应为 第一个数组元素的地址,p++使得 p指向下一个数组元素 。
图示:
p 2
4
6
8
10
12
S[0][0]
S[0][1]
S[0][2]
S[1][0]
S[1][1]
S[1][2]
2,利用指向一维数组的指针变量访问二维数组。
例 10.9 利用指向一维数组的指针变量 p输出数组
ss[2][4]中的各元素。
main()
{ int ss[2][4]={2,4,6,8,10,12,14,16 };
int (*p)[4],j;
printf("\n");
for(p=ss;p<ss+2;p++)
for(j=0;j<4;j++)
printf("%6d",*((*p)+j));/* equal to,(*p)[j] */
}
说明,p是指向一个包含有 4个元素的一维数组的指针变量,因此,
p++的含义是,使 p 指向下一个一维数组,
即移动 4个元素的位置。(与例 10.8中的
p++比较)。
*((*p)+j)的含义,p所指向的一维数组中下标为 j 的元素。
等价于 (*p)[j]
通过以上两例比较,可以看出两种指针变量的使用区别,指向数组元素的指针变量可以访问 任意长度 的数组,指向一维数组的指针变量只能访问固定长度 的数组。
应用,当需用指针变量作为函数参数,编写处理任意二维数组的数据时,用方法 1更合适。
例 10.10 编写函数实现:求 m*n矩阵中最大值元素的下标。并编写主函数调用之,在主函数中输出结果( 较高要求的练习 )。
分析,设函数名为 max,类型为 void,参数有 5个,
分别表示数组元素的首地址、行列数 m,n、最大值元素的行列下标地址 r,c。
算法 (略 ):
main()
{ int ss[4][3]={1,12,3,14,5,16,7,8,9,2,13,4};
int max_r,max_c; void max();
max(ss[0],4,3,&max_r,&max_c);
printf("\n max:%d,row:%d,colum:%d",
ss[max_r][max_c],max_r,max_c);
}
void max(int *p,int m,int n,int *r,int *c)
{ int i,j,kr,kc; /*kr,kc分别为最大值元素的行列下标 */
kr=kc=0;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
if(*(p+i*n+j)>*(p+kr*n+kc)) {kr=i;kc=j;}
*r=kr; *c=kc;}
(p+i*n+j)表示数组元素 ss[i][j]的地址
思考:
1、若用二维数组名作函数参数,是否能实现?
2、如何用函数实现:计算任意班级( n个人 m门课)每门课的平均成绩?
10.2.3 指针与字符串
字符串用字符数组来保存,可以通过数组名及下标访问,也可以通过字符指针访问 。
例 10.11:输入任意字符串,通过字符数组名和字符指针输出该字符串。
#include <stdio.h>
main()
{char string[80],*str;
gets(string); /*string is the address of string[0]*/
puts(string);
str=string; /*str point to the character array */
puts(str);
}
例 10.12用字符数组与字符指针分别访问一字符串常量。
#include <stdio.h>
main()
{char string[ ]="you are really fine!",*str="thank
you.";
puts(string);
puts(str);
}
运行结果,
you are really fine!
thank you,
分析,(字符数组名与字符指针变量的使用区别)
*str=“thank you.”; 等价于
char *str ; str="thank you.";
而 char string[]=“you are really fine!”; 不等价于
char string[80] ; string=,you are really fine!”;
若定义,char s1[80],*s2;
则语句,gets(s1); 是正确的但语句,gets(s2);
不完全正确,因为 s2未赋值就使用,这样可能使系统程序或其他数据区受到破坏,
字符指针变量作函数参数,等同于字符数组名作函数参数,实现传地址调用,便于多个函数之间的字符串的传递。
例 10.14 用函数实现取子字符串:从字符串 s1中第
m个字符开始的全部字符复制到另一字符串。
#include <stdio.h>
main()
{char s1[80],s2[80]; int n;
void copy_str(char *p,char *q,int m);
gets(s1);
scanf("%d",&n);
copy_str(s1,s2,n);
puts(s2);
}
void copy_str(char *p,char *q,int m)
{ p=p+m-1;
while(*p!='\0')
{*q=*p; p++;q++;}
*q = '\0';
}
思考,
若用字符数组作函数参数,如何实现?分析参数传递的过程,有何区别?
结论:形参数组名相当于指针变量。
10.3 指针与函数
指针变量作函数参数,
如果形参是指针变量,实参可以是变量的地址、数组元素的地址(或数组名)、函数的首地址。
本节主要介绍:指向函数的指针的使用
返回值指针值的函数 (主要用于处理多个字符串)
重点,编写返回指针的函数指向函数的指针
函数的指针:即函数名,表示函数的首地址,
例 10.15指向函数的指针变量的定义、引用
#include,math.h”
main()
{ double x,(*p)(),y; /*注意定义与引用中的‘ *’的区别
*/
x=3.14/2;
p=sin; y=(*p)(x);
printf("\n sin,%3.1f",y);
p=cos; y=(*p)(x);
printf("\n cos,%3.1f",y);
}
指向函数的指针变量的一般定义形式为:
类型标识符 (*指针变量名 )( [参数类型 ] );
例如,int (*da)( );
定义指针变量 da,它指向一个返回 int 型值的函数。
应用:
利用指向函数的指针变量编写通用函数 (较高要求 )。
例 10.16编写函数:求任意函数 f(x)在区间 (a,b)上的定积分。
程序(略)
返回指针值的函数
一个函数的返回值可以是 int,float,double、
character型等,也可以返回其他类型的值,比如指针类型。
例如,char *strcpy(char *a,char *b)
char *strcat(char *s1,char *s2)
char *strchr(char *s,char ch)
例 10.17 编写函数 search():查找字符 ch在字符串 str中第一次出现的位置。在主函数中输入字符串、待查找的字符,输出查找结果。
分析:函数 search()的类型为指向字符数据的指针类型,参数有两个:字符型的 ch
和指向字符的指针变量 str.
算法,从 str指向的字符串的首字符开始,
循环比较 str指向的字符是否与字符 ch相等。若相等,则结束循环,返回指向该字符的指针;否则,str指向下一个字符,
直到字符串结束标志‘ \0?为止,返回空指针( NULL),表示没找到。
#include,stdio.h” /*程序 (例 10.17):*/
main()
{ char *search(char *str,char ch); char ss[80],*p,chr;
printf("\n input a string;"); gets(ss);
printf("\n input a char for search:"); chr=getchar();
p=search(ss,chr);
if(p==NULL)printf("\n not found.");
else printf("\n position:%d(count from 0)",p-ss);
}
char *search(char *str,char ch) /*注意:函数名前的 *
{ while(*str!=?\0?) 表示该函数的返回值为指针 */
if(*str==ch) return(str);
else str++;
return(NULL);
}
返回指针值的函数的一般定义格式,
类型标识符 *函数名(形参表)
与其他函数的不同:函数名前有 *,表示指针类型。
类型标识符说明该指针指向的数据的类型。
例 10.18 编写两个字符串连接的函数,返回连接后的字符串的首地址。
分析,
函数类型为返回指向字符型的指针,参数两个:均为字符数组或字符型指针变量。
首先找到字符串 str1的末尾,然后将字符串 str2的字符逐个送入 str1,最后在 str1的末尾加字符串结束标记‘ \0?。
#include "stdio.h"
char *my_strcat(char *str1,char *str2)
{ char *p=str1;
while(*p!='\0') p++; /*find the end of str1*/
while(*str2!='\0') *p++=*str2++;
*p='\0';
return(str1);
}
main()
{ char s1[80]="we are ",s2[ ]="good friends.",*p;
puts(s1); puts(s2);
p=my_strcat(s1,s2);
puts(p);
}
*p++=*str2++;”的功能是,将 str2指向的字符赋给
p指向的单元 (字符串 str1中的指定位置 ),然后 p和
str均指向下一个字符 。 该语句等价于
*p=*str2; p++; str2++;
再比如,字符串复制。
char *strcopy( char *s,char *t)
{ char *p=s;
while( *t!='\0')
{ *s=*t; s++; t++; }
*s='\0';
return p;
}
上面红色的语句等价于,
while ( (*s++ = *t++)!='\0') ;
10.4 指针数组和指向指针的指针
指针数组
指向指针的指针
命令行参数要求,熟悉
10.4.1 指针数组
指针数组 实际上是指向相同类型数据的指针变量的集合,每个数组元素相当于一个指针变量 。
指针数组的应用:
1) 用于访问由多个字符串组成的二维字符数组 。优点是:节省内存、加快排序速度。
2)指针数组作 main函数的形参 。
例 10.19用指针数组指向给定的 5个字符串,然后逐个输出。
分析,5个字符串的首地址分别存放于指针数组的一个元素中;因为要处理给定的字符串,所以在程序中直接给出,而不是用户输入。
程序,l10_4_1.c(下页)
#include <stdio.h> /*L10_4_1.c,*/
main()
{ char *str[ ]={"basic","turbo c","fortran","vc++","java"};
int i;
for(i=0;i<5;i++)
puts(str[i]);
}
程序中指针数组的初始化等价于,
char *str[5],string[ ][10]= {"basic","turbo c",
"fortran","vc++","java"};
for(i=0,i<5;i++) str[i]=string[i];
图示二者区别:用指针数组更节省内存。
注意:指针数组与指向数组的指针变量的区别。
区分 char *str[5]; 与 char (*p)[5];
例 10.20 用指针数组实现字符串排序。 (速度更快 )
分析:一初始化方式存入 5个名字,排序,输出。
其中,10个名字用字符型指针数组保存,每个元素保存一个名字的首地址。
排序时,只交换指向字符串的指针,而不需交换两个字符串,输出时,按指针数组的元素顺序即是排序后的字符串的顺序。
#include <stdio.h>
main()
{ void sort(),prn();
char *str[ ]={"basic","turbo c","fortran","vc++",
"java"};
sort(str,5); prn(str,5);
}
void sort(char *v[],int m) /*用选择法排序 */
{ int i,j,k; char *t;
for(i=0; i<m-1; i++)
{ k=i;
for(j=i; j<m; j++)
if(strcmp(v[k],v[j])>0) k=j;
if(k!=i)
{t=v[k]; v[k]=v[i]; v[i]=t; } /*交换 */ } }
void prn(char *v[ ],int m)
{ int i;
for(i=0; i<m; i++)
puts(v[i]);
}
运行结果:
basic
fortran
java
turbo c
vc++
例 10.4.3 指针数组作 main函数的参数,实现向程序传送命令行参数。(如编程实现文件复制,copy
file1name file2name CR,详见文件一章 )
下面程序( l10_4_3.c)实现:输出命令行参数。
main(int argc,char *argv[ ])
{int j;
for(j=1;j<argc;j++) printf(“\n%s”,argv[j]);
}
运行时输入,l10_4_3 f1.dat f2.dat?
则输出,f1.dat
f2.dat
命令行,l10_4_3 f1.dat f2.dat
argv
argv[0]
argv[1]
argv[2]
l10_4_3\0
f1.dat\0
f2.dat\0
下面程序与上例等价:
main(int argc,char
*argv[ ])
{while(argc>1)
{++argv;
printf("\n%s",*argv);
--argc;
}
}
本例中,char *argv[ ] 与
char **argv 等价。
其中,argc为参数个数,argv为指针数组,
每个数组元素指向一个字符串。
即 argv[0]指向第一个字符串,l10_4_3”,
argv[1]指向第二个字符串,f1.dat”,
argv[2]指向第三个字符串,f2.dat”
10.4.2 指向指针的指针
即指向指针数据的指针变量。
如 int a,*p,**q; a
p=&a; q=&p;
则 q为指向指针的指针变量。 p
*p equal to a
*q equal to p q
**q equal to a
应用:
用于访问指针数组
(了解)
&a
&p
练习
1、编写函数 max_min(),求一批实型数据中的最大和最小值;编写主函数调用之,并在主函数中输入 n个数据,输出最大及最小值。
(练习用指针变量作函数参数,在函数之间传递多个数据)
2、以下程序的功能是输出数组中的最大元素值,
填空:
#include <stdio.h>
Main()
{int a[]={1,7,9,4,3,5,6,10,2,8};
int *m,*p;
for(p=a,m=a;p-a<10;p++)
if( ) m=p;
printf(“\n %d”,*m);
}
3,#include <stdio.h>
main()
{int a[]={1,2,3,4,5,6}; int x=f(a,,4);
printf(“%d”,x);
}
int f(int x[],int n)
{int I,s=1;
for(I=0;I<=n;I++)
s*=x[I];
return s;
}
运行结果是,120
4,main()
{int j;
int m[3][2]={10,20,30,40,50,60};
for(j=0;j<2;j++)
printf(“%d,”,m[2-j][j];);
}
运行结果,50,40
5,main()
{int x=30,y=40;
int *px=&x,*py=&y;
*px=*px+3;
*py=*px-3;
printf("\nx=%d,y=%d",x,y);
}
运行结果:
x=33,y=30
作业
1.上机练习 第二部分
2、习题 3.1 3.2 3.5 3.6