C语言程序设计课件:指针_第1页
C语言程序设计课件:指针_第2页
C语言程序设计课件:指针_第3页
C语言程序设计课件:指针_第4页
C语言程序设计课件:指针_第5页
已阅读5页,还剩80页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

针6.1指针与指针变量6.2指针与数组6.3指针与函数6.4拓展知识单元小结 6.1指针与指针变量

6.1.1地址与指针

计算机的内存是由连续的存储单元(字节)组成的,程序运行时,所有的数据都存放在这些存储单元中。可以把内存看做一栋楼房,其内有很多同样大小的房间,这些房间就相当于内存的一个个存储单元,而房间中的住户就是相应存储单元中存放的数据。为了便于区分,我们通常会为楼房内的房间标注门牌号,以方便我们通过住址拜访具体的某个住户。计算机同样为每个存储单元单独编号,使程序可以通过编号快速、准确地访问内存空间,这些编号就是内存的地址。

例如:

inti=5,j=8;

上面的语句定义了两个整型变量i和j,编译时系统将分别为它们分配4个字节空间。假设内存从2000开始有一段连续的空白内存空间可用,如图6-1所示,2000、2001等是内存地址,分配存储单元2000~2003存放变量i的内容5。又有变量的内存地址是它的第一个字节所在的地址,所以确定变量i的地址为2000。同理,变量j的地址为2004,从2004开始4个连续的字节用于存放变量j的内容8。图6-1变量在内存中的存储方式

在C语言中,我们将变量的地址称为指针。不同于之前通过变量的名字访问其保存的数据,指针运算通过地址来访问对应存储单元的数据。

需要注意的是,变量保存的数据与变量的指针是两个不同的概念。

例如:

floatx=800.6;

程序运行时,系统随机分配给变量4个字节空间,假设起始地址为1000,那么,其中变量名为x,变量保存的数据为800.6,变量的指针为1000。我们可以想像成某顾客到银行存800.6元钱,银行随机分配一个银行帐号1000。这里,顾客名可以看成变量名,存储的金额就是保存的数据,银行帐号就是指针。通过顾客名可以访问存储的金额,通过银行帐号同样可访问到对应的金额。6.1.2指针变量

用来存放指针的变量,我们称为指针变量。换句话说,指针变量存放的是另一个变量的地址,访问指针变量可以通过其内保存的地址访问指针指向的另一个变量,以达到间接访问的目的。

例如,有整型变量i,其值为400,地址为2000。另有一变量p保存的是该变量i的地址,即2000。那么我们说p是一个指针变量,它指向整型变量i,如图6-2所示。图6-2指针变量

1.指针变量的定义

数据类型

*变量名;

这里需要注意的是:

(1)“*”声明这是一个指针变量。

(2)“数据类型”指的是指针变量所指向的变量的数据类型。

(3)所有指针变量保存的数据都是内存地址,但指向不同数据类型的指针变量不能混用。例如:

float*p1; /*定义指针变量p1,p1需指向实型变量*/

char*p2; /*定义指针变量p2,p2需指向字符型变量*/

int*p,*q; /*定义指针变量p、q,p、q需指向整型变量*/

2.指针变量的赋值

(1)赋予另一个普通变量的地址。

可以通过取地址运算符“&”获得变量的地址并赋给指针变量,以使指针变量指向该变量,一般形式如下:

指针变量=&普通变量;

例如:

inta=39,*p;

p=&a;/*&a表示a的地址,语句将a的地址赋给指针变量p*/

图6-3指针变量p指向变量a

(2)赋予另一个指针变量的值。

指针变量之间可以直接赋值,例如针对上面的程序段,若有:

int*q;

q=p;/*使指针变量q与p相等,也指向变量a*/

这样,两个指针变量就指向了同一个变量a,它们之间的关系如图6-4所示。图6-4指针变量p、q均指向变量a

(3)赋予空值。

给指针变量赋予空值“NULL”,这样可以使指针变量有一个实际的地址值的同时又不指向任何变量。这种方式既可以用来为指针变量赋初值,又可以用于切断指针变量与当前所指向变量之间的关系。例如,接着上面的程序段,若有:

p=NULL;/*使指针变量p不再指向变量a*/

这时,指针变量p、q和变量a之间的关系如图6-5所示。图6-5指针变量p变为空指针此外,可以在定义指针变量的同时为其赋值,例如:

floatb=3;

float*p=&b;/*定义指针变量p,并使p指向变量b*/

3.指针变量的引用

指针运算符“*”是单目运算符,返回指定地址内的变量的值。作用于指针变量可以存取指针所指向的存储单元的内容,从而实现间接访问,一般形式如下:

*指针变量

例如:

inti=2,j,*p;

p=&i;

/*指针变量p指向变量i*/

j=*p;

/*将p所指向的变量(即i)的值赋给变量j*/

*p=6;

/*改变p所指向的变量(即i)的值为6*/

图6-6通过指针的引用间接访问变量

程序运行过程分析如下:

(1)程序中定义了两个指针变量p、q和两个字符型变量ch1、ch2。

(2)ch1赋值为‘m’,令p指向ch1,这时输出p指向的变量的值就是输出ch1的值,因此输出结果为“m”,如图6-7中的第Ⅰ部分所示。

(3)语句“*p=’e’;”通过指针变量p改变它所指向的变量的值,实际上就是将ch1的值变为‘e’,因此输出ch1值,输出结果为“e”,如图6-7中的第Ⅱ部分所示。

(4)语句“q=p;”是指针变量间的赋值,使q与p指向同一个变量,这时q指向ch1。语句“p=&ch2”又使p不再指向ch1,变成指向ch2。此时各变量间的关系如图6-7中的第Ⅲ部分所示。

(5)语句“*p=(*q)++”中的*p相当于ch2,*q相当于ch1,所以该语句可理解为ch2=ch1++,执行后它们的值如图6-7中的第

部分所示。

图6-7程序运行中变量间的关系示意图图6-8例6-1程序运行结果

程序运行过程分析如下:

(1)指针变量初始化后,p1指向a,p2指向b,pt为空指针。

(2)运行时若输入3和5,则符合条件“*p1<*p2”(等价于a<b),执行“pt=p1;p1=p2;p2=pt”程序段。

(3)执行“pt=p1”后,pt也指向a;执行“p1=p2”后,p1不再指向a而指向b;执行“p2=pt”后,p2不再指向b而指向a。指针变量改变的过程如图6-9所示。

(4)在步骤(3)执行过程中,变量a和b的值并没有被改变,程序达到的目的是互换了p1和p2所指向的变量。

图6-9程序运行中变量间的关系示意图图6-10例6-2程序运行结果

6.2指

6.2.1指向数组元素的指针

当指针变量存放的是一维数组的首地址时,我们称该指针指向这个数组。由于数组中的各个元素是按顺序连续地存放在内存中的,所以知道了数组的首地址,就可以通过移动指针,访问数组的其他元素。

数组名同时是数组的首地址,也就是第一个数组元素的地址,因此使指针指向一维数组可采用如下两种方法:

指针变量名=数组名;

指针变量名=&数组名[0];

指针的移动可以通过指针与正整数的加减运算来表示。例如,对存储整数的内存空间来说,若有“inti,*p=&i;”,表达式p+1执行的是指向下一个整型变量的操作,并不是简单的在p保存的地址上加1。由于整型变量在内存中占4个字节,可以得到p+1,实际上是在p保存的地址的基础上加4。同样的,p-1执行的是指向前一个整型变量的操作,如图6-13所示。

图6-13指针的加减运算规则

程序运行过程分析如下:

(1)语句“p=a;”将数组a的首地址赋给指针变量p,使p指向数组a中的第一个元素a[0]。本语句也可以替换为“p=&a[0]”。

(2) p+1指向下一个整型变量,即数组a的下一个元素a[1],换句话说p+1可以表示a[1]的地址。以此类推,p+2表示a[2]的地址,p+4表示a[4]的地址。相应的*(p+1)就相当于a[1],*(p+4)就相当于a[4]。指针变量p与数组a各元素之间的关系如图6-14所示。图6-14指针变量p与数组a各元素的关系图6-15例6-3程序运行结果

通过上述程序我们可以发现,指针和数组之间的关系十分密切。除了可以使用下标访问数组元素之外,利用指针可以达到同样的目的。而且,由于数组名同时是数组的首地址,由它所组成的指针表达式同样可以访问数组元素。此外,指针也可以利用下标法访问数组元素。若有指针变量p指向一维数组a的首地址,则访问数组中第i(i从0开始)个元素的方式有:

a[i]、p[i]、*(a+i)、*(p+i)6.2.2适用于数组的指针运算

1.指针变量的自增自减运算

指针变量的自增自减不是简单的将指针变量的内容加1减1,而是对指针进行移动操作,只有对指向数组的指针变量进行自增自减操作才有意义。自增是将指针向前移动,指向数组中的下一个元素;自减是将指针向后移动,指向数组中的前一个元素。

注意,数组名虽然保存有数组的首地址,但是数组名是常量,其值不能被改变,故不能进行自增自减运算。

2.两个指针间的关系运算

两个指向同一个数组的指针变量可以进行关系运算。如果指针变量p和q指向同一个数组,那么:

当p指向的元素位于q指向的元素之前时,p<q;

当p指向的元素位于q指向的元素之后时,p>q;

当p和q指向同一个元素时,p=q。

3.两个指针间的减法运算

指向同一个数组的两个指针变量相减,可以得到它们指向的数组元素之间间隔的元素个数。例如

inta[6],*p,*q,i;

p=a; /*p指向a[0]*/

q=&a[4]; /*q指向a[4]*/

i=q-p; /*i=4*/

上述语句段中得到i的值为4,表示q是p之后的第4个元素。相反,表达式“p-q”的值为负数,表示p位于q之前。它们之间的关系如图6-16所示。

图6-16p、q之间的关系

【例6-4】

编写程序使用指针将一个数组中的所有元素逆序存入另一个数组中。

我们通常使指针指向数组的首地址,然后通过指针的移动访问数组中的各个元素,最后以指针与数组最大下标元素地址的比较作为判断依据,当指针移动到数组中的最后一个元素之后时结束操作。这样可以使用指针灵活地完成数组的遍历。反过来,先使指针指向数组的最后一个元素,然后通过指针的自减运算可以逆序遍历数组的所有元素。图6-17例6-4程序运行结果6.2.3指向字符串的指针

字符串常量的值实际上是其首字符的地址,我们可以使用字符串常量对字符指针进行赋值,使字符指针指向一个字符串。由于字符串中的字符都是连续存放且以‘\0’为结束标志的,使用字符指针访问字符串非常便利。例如:

char*str=“cat”;

str++;

printf("%s",str);/*输出"at"*/

【例6-5】编写程序任意输入一行字符串,将字符串

“over”

连接到这个字符串的后面。

指针变量应先赋值后引用,所以这里输入一行字符串时是使用字符数组str来接收的。程序的思路是令p指向字符数组str,令q指向字符串 "over",先将p移到输入字符串的结尾位置,然后p、q同步向后移动,每次均将q指向的字符赋给p正指向的位置,直到将q移动到字符串 "over" 结束位置为止。图6-18例6-5程序运行结果 6.3指

6.3.1指针作为函数参数

此外,指针变量也可以作为函数参数。这种情况下,函数调用时函数中传递的不再是变量中的数据,而是变量的地址。如此一来,被调函数将和主调函数操作相同的存储单元,也因此,被调函数中对形式参数所指向变量的值的改变将直接作用在主调函数中相应位置的存储单元上。我们称这种参数传递方式为地址传递。

地址传递适用于函数调用过程中有多于一个信息需要反馈的情况。这种参数传递方式要求被调函数中的形式参数声明为指针变量,主调函数中的实际参数为地址表达式,该地址表达式可以是变量的地址或者指向变量的指针变量。

程序运行过程分析如下:

(1)执行main函数,输入a和b的值。

(2)调用用户自定义函数swap,将a的地址传递给指针变量x、b的地址传递给指针变量y,也就是使x指向a、y指向b,如图6-19中第Ⅰ部分所示。

(3)执行函数swap,将x、y指向的变量的内容互换,由于x指向a、y指向b,所以程序实际上就是将a和b的内容互换,如图6-19中第Ⅱ部分所示。

(4)函数swap执行完毕返回main函数,释放变量x、y、t,但此时a、b变量的值已经完成互换,如图6-19中第

部分所示。图6-19程序运行中变量关系示意图图6-20例6-6程序运行结果

说明:

(1)调用函数时的实际参数可以同样使用指针变量。例如,程序中的main函数可以改变为:

main()

{ inta,b;

int*p,*q;

p=&a;

q=&b;

printf("请输入两个整数:");

scanf("%d%d",p,q);

printf("函数调用前:a=%d,b=%d\n",a,b);

swap(p,q);

printf("函数调用后:a=%d,b=%d\n",a,b);

}

图6-21指针参数传递示意图(2)不能企图通过直接改变指针形式参数的值而达到地址传递的目的。例如程序中的swap函数若变为:

voidswap(int*x,int*y){ int*t; t=x; x=y; y=t;}图6-22直接交换指针的变量关系示意图

【例6-7】

编写函数,已知圆的半径,求圆的面积和周

长。

函数要同时返回圆的面积和周长两个计算结果,不能使用return语句返回函数的值,这时可以使用地址传递的方法编程。函数的参数除了定义实型变量r用于传递圆的半径之外,还分别定义了两个指针变量p_s和p_c,计算出的圆的面积和周长就可以保存在这两个变量指向的存储单元中。调用函数时,提供变量s和c用于存放相应的计算结果。图6-23例6-7程序运行结果6.3.2指向数组的指针作为函数参数

数组名可以作函数的实参和形参,此时实参传给形参的是数组的首地址,主调函数与被调函数存取的是一段相同的内存空间。同理,指向数组的指针变量也可以作为函数参数,其意义与数组名作为参数相同。

【例6-8】

编写函数,计算一组整数中奇数的个数。

本例使用指向数组的指针变量作为函数参数。程序如下:

#include<stdio.h>

#defineN5

intfun(int*arr,intsize)

{

inti,n=0;

for(i=0;i<size;i++)/*判断数组中的各个元素,计算奇数的个数*/

if(*(arr+i)%2!=0)

n++;

returnn;

}

main()

{

inta[N],i,result;

int*p=a;

printf("请输入%d个整数:",N);

for(i=0;i<N;i++)

scanf("%d",&a[i]);

result=fun(p,N);/*p是指向数组a的指针,N是数组的大小*/

printf("这组整数中有%d个奇数\n",result);

}

图6-24例6-8程序运行结果

说明:

(1)函数的实参和形参既可以使用指针变量,也可以使用数组名,形式不同但效果相同,只是函数的声明和调用过程中会有细微差别。以本程序为例,四种情况下函数的声明和调用格式如下:

①形参为数组类型,实参为数组名:

intfun(intarr[],intsize); /*函数声明*/

inta[N];

result=fun(a,N);

/*函数调用*/

②形参为指针类型,实参为指向数组的指针变量:

intfun(int*arr,intsize); /*函数声明*/

inta[N],*p=a;

result=fun(p,N); /*函数调用*/

③形参为数组类型,实参为指向数组的指针变量:

intfun(intarr[],intsize); /*函数声明*/

inta[N],*p=a;

result=fun(p,N); /*函数调用*/

④形参为指针类型,实参为数组名:

intfun(int*arr,intsize); /*函数声明*/

inta[N];

result=fun(a,N); /*函数调用

*/

(2)数组或指向数组的指针变量作为函数的参数,只是传递了数组的首地址,并没有将整个数组或者数组的大小全部传递到函数中,所以编程时一般要增加一个整型参数用来传递数组的元素个数。例如本例中函数fun的第二个参数size指的就是数组的大小。

6.4拓展知识

6.4.1指针与二维数组

二维数组可视为一维数组扩展形成,即将二维数组中的每一行以整体的方式作为元素组成数组。根据二维数组这种按行存储的特点,可以使用多种方式来表示二维数组中的元素地址。例如,一个3行4列的二维数组的存储方式及相关地址关系如图6-25所示。

分析图6-25,获得表示二维数组中元素地址的若干方法如下:

(1) &a[i]和a+i均表示第i行的首地址。

a[0]、a[1]和a[2]分别指向第0行、第1行和第2行,它们既是同一个一维数组的3个元素,本身又都是一个包含四个元素的一维数组。作为一维数组的元素,它们的地址即各行的指针可以表示为“&a[i]”或者“a+i”。

(2) &a[i][j]、a[i]+j和*(a+i)+j表示元素a[i][j]的地址。

a[i]本身作为一个数组,同时也是数组的首地址,可通过“a[i]+j”访问第i行第j列元素a[i][j]。而“a[i]”可继续使用“*(a+i)”来表示,故“*(a+i)+j”可表示元素a[i][j]的地址。

(3) *(*(a+i)+j)可表示元素a[i][j]。

“*(a+i)+j”表示的是元素a[i][j]的地址,进行指针运算后“*(*(a+i)+j)”即可表示元素a[i][j]。

图6-25二维数组的存储方式和地址关系

【例6-9】

找出数组每行中最大的数,并将这些数相加求和。

本例通过二维数组各元素的地址访问元素的内容。

程序如下:

#include<stdio.h>

#defineM4 /*二维数组的行数*/

#defineN3 /*二维数组的列数*/

main()

{ inta[M][N],i,j;

intmax,sum=0;

printf(“请输入一个%d行%d列的二维数组:\n”,M,N);

for(i=0;i<M;i++)

for(j=0;j<N;j++)

scanf("%d",*(a+i)+j);/*输入第i行第j列元素的值*/

for(i=0;i<M;i++)

{ max=*(*(a+i)); /*将每行中的第0个元素赋给max*/

for(j=0;j<N;j++) /*各行内的所有元素进行比较求得各行最大值*/

if(*(*(a+i)+j)>max)

max=*(*(a+i)+j);

printf("第%d行中的最大值为:%d\n",i,max);

sum=sum+max; /*各行的最大值累加*/

}

printf("各行的最大值相加之和为:%d\n",sum);

}

图6-26例6-9程序运行结果6.4.2指针数组

若一个数组中的元素都是指针变量,则该数组被称为指针数组。指针数组是一组指向相同数据类型的指针的集合。常使用指针数组保存字符指针,指向若干个字符串。这样既可以节省空间,又可以使字符串的处理更加方便、灵活。定义指针数组的一般形式为

数据类型

*数组名[数组长度];

例如,语句“int*pa[5];”定义了一个指针数组pa,该数组具有5个元素,每个元素都是一个指向整型变量的指针变量。

【例6-10】

输入1~12之间的任意一个数,输出与之对应的中英文月份。

#include<stdio.h>

main()

{ intx;

char*month[]={NULL,"January(1月)","February(2月)",

"March(3月)","April(4月)","May(5月)","June(6月)",

"July(7月)","August(8月)","September(9月)",

"October(10月)","November(11月)","December(12月)"};

printf("请输入一个1~12之间的整数:");

scanf("%d",&x);

printf("%s\n",month[x]);

}

说明:

(1)也可以将保存月份名称的这些字符串定义在一个二维数组中,例如“charmonth[12][20];”。二维数组为每个字符串分配的内存空间统一为数组第二维的大小,如语句“charmonth[12][20];”为每个字符串分配了20个字节空间。而实际上各个字符串的长短不同,这极大的浪费了内存空间。本例采用指针数组来管理这些月份名称字符串,数组中保存的只是这些字符串的首地址,而每一个字符串都是按其实际大小分配空间的,采用这种方式相比之下更节省内存空间。

(2)指针数组month中的第0个元素初始化为空指针,这样可以使1~12月的月份名称正好对应于month数组中的第1~12个元素。

(3)数组month中的各元素分别指向保存12个月月份名称的字符串,“month[x]”保存的是用户输入的数字对应月份的字符串的首地址,“%s”表示从该地址开始访问直到遇到“\0”结束为止,所以“printf(”%s\n“,month[x]);”可以输出与x相对应的月份名称。

程序运行结果如图6-27所示。图6-27例6-10程序运行结果6.4.3命令行参数

main函数是所有程序运行的入口,一般情况下我们将main函数编写为无参函数。实际上,main函数也可以是有参的函数,此时,程序运行时系统可以同时传递信息到main函数中。带有参数的main函数的一般形式为

main(intargc,char*argv[])

其中:

(1) argc记录运行程序时系统向程序传递的命令行参数的个数。

(2) argv是一个指针数组,其内的各元素均为字符指针,可以指向字符串。该数组接收具体的各个命令行参数。

与以往运行程序时输入数据不同,命令行参数是在命令行中通过命令使程序运行的同时输入数据。我们可以使用命令行的方式在操作系统下直接运行C程序的扩展名为.exe的可执行文件,还可以根据需要同步向程序中传递若干个参数。命令行的一般形式为

运行文件名

参数1参数2…

参数n

这里需要注意的是:

(1)命令行需在命令提示符窗口中使用。

(2)“运行文件名”是可执行文件的文件名,并且该文件应可以直接在当前目录中找到,若不在当前目录中,需使用完整的路径名。

(3)运行文件名和其后所跟参数之间用空格间隔。

【例6-11】

输出main函数的参数信息。

#include<stdio.h>

main(intargc,char*argv[])

{ inti;

printf("参数个数:%d\n",argc);/

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论