《C语言程序设计 》课件第8章_第1页
《C语言程序设计 》课件第8章_第2页
《C语言程序设计 》课件第8章_第3页
《C语言程序设计 》课件第8章_第4页
《C语言程序设计 》课件第8章_第5页
已阅读5页,还剩135页未读 继续免费阅读

下载本文档

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

文档简介

第8章指针8.1地址、指针与指针变量的基本概念8.2指针变量的定义、赋值和引用8.3数组指针和指向数组的指针变量8.4指针与函数8.5指针的指针8.6动态内存分配与指向它的指针变量本章小结

8.1地址、指针与指针变量的基本概念

8.1.1地址与指针

指针类型数据是一种与内存相关的数据。要了解指针类型数据,首先要了解计算机的内存及内存地址这两个概念。程序处理的数据保存在内存中。内存是由许多顺序排列的存储单元组成的,一般把存储器中的一个存储单位称为一个字节或内存单元。而每一个内存单元都有一个编号,被称为“内存地址”。对内存数据的访问都通过地址进行,程序的变量所占用的内存单元的地址就叫变量的指针。使用高级语言变量掩盖了内存单元、地址等低级概念,使我们编写程序时不必关心实现细节,但实际上它在程序执行的过程中发挥了巨大的作用。例如,在下面程序段中:

inti;

i=3;

printf("%d",i);当程序开始执行后,系统首先为变量i分配了地址为2000和2001的存储单元,如图8-1所示。然后,当执行语句“i=3;”时,系统就将二进制数的3存放到变量i所对应的内存单元中。最后,在执行输出语句时,系统同样到该地址处将变量i的值取出并输出。可见,程序对变量的所有访问都与变量的地址有关。图8-1变量地址8.1.2指针变量

在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或某内存单元的指针。例如,将上例中变量i的地址放入另外的变量P中,当需要访问变量i时,可以采用两种不同的方式访问。第一种就是之前我们一直使用的通过变量名访问,即“直接访问”方式;第二种可以通过访问变量p,找到i的地址2000,再到2000地址对应的内存区域访问变量i的值。这种访问方式被称为“间接访问”方式。

8.2指针变量的定义、赋值和引用

8.2.1指针变量的定义

指针变量可以存储另一个变量的地址,并可以通过其间接访问该变量,这就需要在程序中定义指针变量,将其他变量的地址存储在指针变量中,最终达到间接访问变量的目的。

指针变量定义的一般形式为

类型说明符*变量名;其中:

类型说明符:规定指针变量指向的基类型,即指定指针变量存储哪种类型数据的地址;

*:说明当前定义的变量为指针变量;

变量名:指针变量的名称,遵循C语言对标识符定义的规则。

例如:

int*p1; /*p1是指向整型变量的指针变量*/

float*p2; /*p2是指向浮点变量的指针变量*/

char*p3; /*p3是指向字符变量的指针变量*/8.2.2指针变量的赋值

指针变量是一种特殊的变量,对它的赋值只能赋予其他变量地址。这个赋值的一般形式为

指针变量=&变量名;

其中:“&”叫做取地址运算符,它的作用是返回变量的内存起始地址。通过“=”把取出的变量地址赋值给指针变量。通过这种运算取得联系的指针变量与变量间存在一种“指向”关系。例如:

inti=100;

int*ip;

ip=&i;

在这里,假设变量i在内存中分配的存储单元的起始地址是2000,那么当变量ip被赋值为i的地址之后,就可以说ip指向i。ip和i的指向关系可以用图8-2所示的示意图来表示。

指针变量与普通变量一样可以进行初始化,例如上例可以改写如下:

inta;

int*p=&a;图8-2指针与变量的“指向”关系在为指针变量进行赋值的过程中,需要注意以下问题:

(1)不允许把一个数值赋予指针变量,故下面的赋值是错误的:

int*p;

p=1000;

(2)赋给指针变量的变量地址不能是任意类型数据的地址,而只能是与指针变量的基类型具有相同类型的变量的地址。例如,整型变量的地址可以赋给指向整型变量的指针变量,但浮点型变量的地址不能赋给指向整型变量的指针变量。例如,下面的赋值是错误的:

int*p;

floatx;

p=&x;

(3)被赋值的指针变量前不能再加“ * ”说明符,如写为 *p=&a也是错误的。8.2.3引用指针变量所指的值

当一个指针变量指向另一个变量之后,就可以通过访问这个指针变量来间接访问它所指向的变量,其访问的一般形式为

*指针变量名;

例如:

inti=200;

int*ip;

printf(“%d\n”,i);

ip=&i;

printf("%d\n",*ip);此程序的输出结果如下:

200

200

这段程序中第一个输出语句是对变量i的直接访问,而第二个语句是对变量i的间接访问,因为ip中存放的是变量i的地址,所以ip是指向i的指针变量,在ip前加“ * ”是访问变量ip中存储的地址所对应的存储单元中的数据。从中可以看到上面程序段中的两个输出语句虽然访问变量的方式不同,但结果相同。

【例8.1】用“间接访问”方式输入和输出变量。

程序如下:

main()

{

inta,b;

int*pointer_1,*pointer_2;

pointer_1=&a; /*将变量a的地址赋值给pointer_1*/

pointer_2=&b; /*将变量b的地址赋值给pointer_2*/

scanf(“%d,%d”,pointer_1,pointer_2);

printf(“%d,%d\n”,*pointer_1,*pointer_2);/*访问指向变量a、b的指针输出变量*/

}

说明

(1)在开头处虽然定义了两个指针变量pointer_1和pointer_2,但它们并未指向任何一个整型变量,只是提供两个指针变量,规定它们可以指向整型变量。语句“pointe_1=&a;pointe_2=&b;”的作用就是使pointer_1指向a,pointer_2指向b,如图8-3所示。图8-3指针变量与变量的指向关系

(2)在scanf语句中,因为对a、b的输入是通过指针变量进行的,而指针变量本身存储的是变量的地址,所以在输入时,输入到列表的表达式中没有“&”符号。

(3)倒数第二行的 *pointer_1和 *pointer_2就是访问变量a和b。

思考

(1)如果已经执行了语句“pointer_1=&a;”,则 &*pointer_1的含义是什么?

(2)*&a的含义是什么?

(3)(pointer_1)++ 和pointer_1++ 的区别是什么?

【例8.2】输入a和b两个整数,按先大后小的顺序输出a和b。

程序如下:

main()

{

int*p1,*p2,*p,a,b;

scanf(“%d,%d”,&a,&b);

p1=&a;p2=&b;

if(a<b)

{p=p1;p1=p2;p2=p;} /*p1、p2交换内容*/

printf(“\na=%d,b=%d\n”,a,b);

printf("max=%d,min=%d\n",*p1,*p2);

}

说明

(1)“p=p1;”、“p1=p2;”、“p2=p;”三条语句的作用是交换p1和p2变量中的数据,在交换之前p1和p2分别指向a和b,在交换之后p1和p2分别指向b和a。

(2)在此程序中a、b变量的值是保持不变的。对比程序:

main()

{

int*p1,*p2

intp,a,b;

scanf(“%d,%d”,&a,&b);

p1=&a;p2=&b;

if(a<b)

{p=*p1;*p1=*p2;*p2=p;}

/*实际是交换a、b的值*/

printf(“\na=%d,b=%d\n”,a,b);

printf("max=%d,min=%d\n",*p1,*p2);

}

思考

(1)在这段程序中,变量p1和p2的指向是否发生了变化?

(2)变量a和b的值是否发生了变化?

综上所述,指针变量的相关运算符有两种,即取地址运算符“&”和指针运算符(或称间接访问运算符)“ * ”。

需要注意的是,指针运算符“ * ”和指针变量定义中的指针说明符“ * ”不是一回事。在指针变量说明中,“ * ”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“ * ”则是一个运算符,用以表示指针变量所指的变量值。当一个指针变量p指向一个变量i以后,对于变量i的访问方式可以由直接访问改变为间接访问。在程序中对变量的直接访问通常表现为三种方式,同样与之对应的间接访问也有三种方式,如表8-1所示。【例8.3】输出两个数中的大者,要求通过直接访问和间接访问两种方式进行。

程序如下:

main()

{

inta,b,*pa,*pb;

pa=&a;

pb=&b;

printf(“inputa,b:\n”);

scanf(“%d%d”,&a,p2);

if(*pa>*pb)

/*比较pa和pb所指变量的值,实际是比较a、b的值*/

printf(“%d”,*pb);

else

printf("%d",b);

}

8.3数组指针和指向数组的指针变量

8.3.1指向一维数组的指针

数组在内存中占有一段连续的存储空间,数组的起始地址即为数组的指针。C语言中用数组名表示这块连续内存单元的首地址。若用一个指针指向一个数组,就可以通过间接访问读写数组的每个元素。

下面举例说明将指针指向一个数组的方法。例如:

inta[5]; /*定义a为包含5个整型数据的数组*/

int*p; /*定义p为指向整型变量的指针*/

p=a;

p=&a[0];

应当注意,因为数组为int型,所以指针变量也应为指向int型的指针变量。数组的首元素地址即为数组的起始地址,所以可以用“p=a;”向指针赋数组名或用“p=&a[0];”数组首元素地址的方法,使指针指向数组。赋值的结果如图8-4所示,把a[0]元素的地址赋给指针变量p。也就是说,p指向a数组的第0号元素。在定义指针变量时可以赋初值为数组的起始地址:

int*p=&a[0];

int*p=a;

从图8-4中可以看出有以下关系:a、&a[0] 均表示数组a的首地址。应该说明的是,p是变量,而a、&a[0] 都是常量。在编程时应予以注意。图8-4指向一维数组的指针8.3.2通过指针引用一维数组元素

1.指针变量的数学运算

指针在通常情况下是不能进行数值运算的,但是如果指针变量p已指向数组中的一个元素,则指针可以进行以下运算:

(1)加、减运算。比如,指针p已经指向数组中的某个元素了,那么p + 1、p - 1分别指向当前元素的下一个元素和上一个元素。

在这里指针的加减运算并不是单纯将指针变量的值进行加减。C语言规定,p+i实际上是p+(i* 指针所指数据类型所占的字节数)。

例如,p指向a[0],p+i指向a[i] 的地址,a+i也得到a[i] 的地址。

(2)指向同一数组的指针之间可以进行减法。比如,p2 - p1得到两个指针所指元素之间的地址差值。

例如,p2指向a[5],p1指向a[2],p2-p1得到3。

(3)指针的自加、自减运算与*运算相联时的优先级问题:

由于 ++ 和 * 同优先级,结合方向自右而左,故 *p++等价于 *(p++)。

*(p++)与 *(++p)作用不同。若p的初值为a,则 *(p++)等价于a[0],*(++p)等价于a[1]。

(*p)++ 表示p所指向的元素值加1。

如果p当前指向数组a中的第i个元素,则 *(p--)相当于a[i--],*(++p)相当于a[++i],*(--p)相当于a[--i],*(p++)相当于a[i++]。

2.通过指针访问数组元素

引入指针变量后,就可以用下标法和指针法两种方法来访问数组元素了。

(1)下标法。引用数组元素可以用下标法,即“数组名称[下标]”。

(2)指针法。指针法即采用 *(a+i)或 *(p+i)的形式,用间接访问的方法来访问数组元素,其中a是数组名,p是指向数组的指针变量。

例如,p指向数组a的首地址,如图8-5所示。

①p+i和a+i就是a[i] 的地址,或者说它们指向a数组的第i个元素。

②*(p+i)或 *(a+i)就是p+i或a+i所指向的数组元素,即a[i]。例如,*(p+5)或 *(a+5)就是a[5]。

③指向数组的指针变量也可以带下标,如p[i] 与 *(p+i)等价。图8-5通过指针引用数组元素

【例8.4】输出数组中的全部元素。(要求:用指针法输出。)

指针法一:

main()

{

inta[10],i;

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

*(a+i)=i;/*将0~9分别赋值到a[0]~a[9]*/

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

printf("a[%d]=%d\n",i,*(a+i));

}

说明

数组名即数组的首地址,数组名加i即获得数组中第i个元素的地址。

指针法二:

main()

{

inta[10],i,*p;

p=a; /* p指向数组a的首地址*/

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

*(p+i)=i; /*通过指针加法将0~9分别赋值到a[0]~a[9]*/

for(i=0,p=a;i<10;i++)

printf("a[%d]=%d\n",i,*(p+i));

}

说明

p是指向数组的指针,对p进行加i即获得数组中第i个元素的地址。

指针法三:

main()

{

inta[10],i,*p;

for(p=&a[0],i=0;p<a+10;p++,i++)

{

*p=i;

printf(“a[%d]=%d\n”,i,*p);

}

}指针法四:

main()

{

inta[10],i=0,*p;

for(p=a;a<(p+10);a++,i++)

{

*a=i;

printf(“a[%d]=%d”,i,*a);

}

}注意

指针法四是错误的,因为数组名a代表数组首元素的地址,它是一个指针常量,它的值在程序运行期间是固定不变的。既然a是常量,所以a++ 是无法实现的。数组首地址通常不能被赋值。8.3.3字符指针与字符数组

在实际应用中,字符指针常常用来指向一个字符数组、字符常量或字符串。例如:

char*pc;

pc=“China”;

定义字符指针时可以用字符串常量对它做初始化。例如:

char*pc=“China”;

我们知道,C语言对字符串常是以字符数组的形式存放的。因此,上述语句定义了指向字符的指针pc,其次在内存中建立了常量字符串 "China",最后将字符串常量的起始地址存入pc变量中。指针pc与数组的指向情况如图8-6所示。图8-6字符指针与字符数组

【例8.5】用字符串指针分别指向一个常量字符串 “China!” 和 “English!”,并输出这两个字符串。

程序如下:

main()

{

char*pc=“China!”; /*指针pc指向字符串常量*/

printf(“%s\n”,pc); /*用%s控制方式输出pc所指字符串*/

pc=“English!”; /*为pc所指地址处重新赋值*/

printf(“%s\n”,pc);

}

程序运行结果:

China!

English!

说明

pc是指向字符的指针,初始化时指向了常量字符串 "China!",在后面的语句中,pc被重新赋值指向常量字符串 "English!"。因此,程序输出结果得到了两行不同的输出。

【例8.6】用字符串指针实现strcat函数的功能,完成字符串的连接。

程序如下:

main()

{

char*s1=“Iam”;

char*s2=“astudent.”;

charc[80],*pc;

pc=c;

/*指针pc指向c字符数组*/

for(;*s1!='\0';s1++,pc++) /*循环变量s1所指的字符不等于 \0时,即进行循环*/*pc=*s1;

/*将s1所指的值赋值给指针pc指向的地址处*/

for(;*s2!='\0';s2++,pc++)

*pc=*s2;

pc='\0';

printf("%s\n",c);

}

程序运行结果:

Iamastudent.

说明

(1)程序利用两个循环分别将s1和s2指针所指的字符串先后写入c字符数组,实现字符串的拼接。

(2)在两次写入时,并没有写入字符串结束标志 '\0',因此在写入s2后,需要为c字符数组添加一个结束标志。

【例8.7】在输入的字符串中查找有无字符 ‘k’。

程序如下:

main()

{

charst[20],*ps;

inti;

printf(“inputastring:\n”);

ps=st;

scanf(“%s”,ps);

for(i=0;ps[i]!=‘\0’;i++)

/* ps指针从st首元素开始循环*/

if(ps[i]==‘k’){

/*如果ps+i所指的元素值等于k,则输出*/

printf(“thereisa‘k’inthestring\n”);

break;

}

if(ps[i]=='\0')printf("Thereisno'k'inthestring\n");

}8.3.4指向二维数组的指针

指针可以指向多维数组,但在应用上比较复杂,较为常用的是指向二维数组的指针。二维数组的首地址即为数组名,也是第一个元素的地址。在C语言中,二维数组以行为单位在内存中依次存放。

设有字符型二维数组num[3][6]如下:

charnum[3][6]={"one","two","three"};

num数组在内存中存储的情况如图8-7所示。图8-7字符二维数组的存储设数组num的首地址为1000,则数组第0、1、2行的起始地址分别为1000、1006、1012。C语言允许把一个字符二维数组分解为多个一维数组来处理。因此数组num在逻辑上可分解为三个一维数组,即num[0]、num[1]、num[2]。每一个一维数组中存放一个字符串,如图8-8所示。图8-8二维数组数组及数组元素的地址表示如下:从二维数组的角度来看,num是二维数组名,num代表整个二维数组的首地址,也是二维数组0行的首地址,等于1000。num+i代表第i行的首地址。

【例8.8】输入并输出字符二维数组。

程序如下:

main()

{

charday[3][6];

inti;

printf(“inputdayarray:\n”);

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

scanf(“%s”,day[i]);

/*通过访问二维数组的每行地址,对字符串进行操作*/

printf(“thedayarrayare:\n”);

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

printf("%s\n",day+i);

}程序运行结果:

inputdayarray:

one↙

two↙

three↙

thedayarrayare:

one

two

three

【例8.9】

编写竞赛报名程序,输入参赛者姓名,并判断其中是否有某同学参加。

程序如下:

#definecol30

#definerow6

main()

{

charname[col][row];

charfindname[6];

intnum,i,flag=0;

printf(“inputstudentnum(<30):”);

scanf(“%d”,&num);

printf(“inputallstudentsname:”);

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

scanf(“%s”,name[i]);

printf(“inputfindname:”);

scanf(“%s”,findname);

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

{

if(strcmp(name[i],findname)==0)

/*比较name[i]和findname是否相同*/

{

flag=1;

break;

}

}

if(flag==1)

printf(“Yes,find%s”,findname);

else

printf("No,find%s",findname);

}8.3.5指针数组

C语言允许定义各种类型的数组,当然也可以定义指针类型数组,简称指针数组。指针数组的所有元素值均为指针。指针数组是一组有序的指针的集合。指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。

指针数组说明的一般形式为

类型说明符*数组名[数组长度]

其中:类型说明符为指针值所指向的变量的类型。

1.字符指针数组

字符指针数组是一组指向字符数组的指针的集合。通常程序里有多个字符串时,一种常见的做法就是用一个字符指针数组指向它们。然后,可以使用循环语句对多个字符串进行批量操作,方便用户编程。

定义一个字符指针数组的方法如下:

char*pc[10]

字符指针数组也可以在定义时初始化。人们常让字符指针指向字符串,也常用字符串常量为字符指针数组中的指针提供初始值。下面是这种用法的一个例子:

char*country[]={"China","Japan","English"};这里定义了一个包含3个字符指针的数组,同时建立了3个字符串常量,让数组里的指针分别指向各个字符串,如图8-9所示。有了这个定义后,通过数组元素country[0]、country[1]和country[2] 等就可以访问这些字符串了。图8-9字符指针数组与一组字符串的指向关系例如,输入以上所有字符串:

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

printf(“%s\n”,country[i]);

也可以对以上字符串进行比较或重新输入:

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

{

if(strcmp(country[i],“Japan”)==0) /*将变量字符串 “apan” 替换为 “Korea”*/

country[i]="Korea";

}

2.指针数组与二维数组

指针数组与二维数组两种数据结构在编程中可以配合使用。通常二维数组用来存储数据本身。在C语言中,二维数组可以以行为单位分解为多个一维数组,因此利用指针数组来存放由二维数组分解出来的多个一维数组的地址,从而通过指针数组来操作二维数组中的某一行。

假设有下面两个定义:

intnum[3][3]={1,2,3,4,5,6,7,8,9};

int*pa[3];

pa[0]=num[0];

pa[1]=num[1];

pa[2]=num[2];第一行定义建立了一个二维(3 × 3)的整型数组,它占用一片连续存储区,数组的逻辑存储情况如图8-10(a)所示。三个成员数组里各存一串数字。第二行定义一个包含三个指针的指针数组,三条赋值语句的作用是让数组里的指针分别指向num数组的三行。赋值后的情况如图8-10(b)所示。从这两个图可以清晰地看到两个定义的不同。图8-10二维数组和指针数组以上pa指针数组的应用,为num二维数组建立了另一种以行为整体的联系。对num二维数组的元素的访问,可以更为灵活。

例如,用指针法访问num二维数组的元素num[i][j] 的常用方法有 *(pa[i]+j)、*(*(pa+i)+j)等。

【例8.10】通过指针数组访问二维数组。

程序如下:

main()

{

intnum[3][3];

int*pa[3];

inti,j;

pa[0]=num[0];

pa[1]=num[1];

pa[2]=num[2];

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

{

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

scanf(“%d”,(*(num+i)+j)); /*以行为单位,逐字访问字符*/

}

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

{

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

printf(“%d”,*(pa[i]+j));

printf(“\n”);

}

}

指针数组在字符串常量编程中更为常用。当多个字符串常量采用二维字符数组结构存储时,如果字符串数量较多,且数据量较大,则需要一块较大的连续存储区。若将多个一维数组分别存储在不同的存储区域,则对内存的要求降低,但是由于多个一维数组是一种分散的组织,不便于管理,所以可以使用字符指针数组分别指向字符串,通过字符指针数组操作字符串更为简便。

【例8.11】用冒泡排序法将5个国名按字母顺序排列后输出。

程序如下:

#include“string.h”

main()

{

char*name[]={“CHINA”,“AMERICA”,“AUSTRALIA”,“FRANCE”,“GERMAN”};

inti,j;

char*temp;

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

{

for(j=0;j<4-i;j++)

if(strcmp(name[j],name[j+1])>1)

/*比较相邻字符串*/

{

temp=name[j];

name[j]=name[j+1];

name[j+1]=temp;

}

}

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

printf("%s\n",name[i]);

}程序运行结果:

AMERICA

AUSTRALIA

CHINA

FRANCE

GERMAN

说明

把各个国家名称的常量字符串分别放在5个一维数组中,把这些字符数组的首地址放在一个指针数组里,当需要交换两个字符串时,只需交换指针数组相应两个元素的内容(地址)即可,而不必交换字符串本身。这样可以不必直接交换数据,从而加速了交换的速度。

3.命令行参数及其处理

在运行程序时,有时需要将必要的参数传递给主函数。例如,一个打开文件的源程序文件名为openfile.c,所产生的可执行文件名为openfile.exe,若在命令状态下输入命令:

openfile

这个程序就会被装入并执行,但是此处并未指明打开哪个文件,若想在键入命令的同时指明打开文件的名称,就必须在键入可执行程序文件名的同时输入目标文件名称,这就需要使用命令行参数。要编写能处理命令行参数的程序,需要了解C语言的命令行参数机制。在TurboC中,主函数的形式参数为下列形式:

main(intargc,char*argv[])

其中,两个特殊的内部形参argc和argv是用来接收命令行实参的,它们是只有main()才能拥有的实参。argc是整型变量,其作用是保存命令行的参数个数;argv是指向命令行字符数组的指针数组。这个数组里的每个元素都指向命令行实参。所有命令行实参都是字符串,任何数字都必须由程序转变为适当的格式。下面所给出的简单程序说明了命令行实参的用法。

【例8.12】在屏幕上显示“Hello”,如果用户在程序名后直接键入自己的名字,则显示名字。

程序如下:

#include<stdio.h>

main(intargc,char*argv[])

{

if(argc!=2)

{

printf(“youforgottotypeyourname\n”);

exit(0);

}

printf(“Hello%s”,argv[l]);

}若命名该程序为name.c,可在命令行方式下输入:

nameMarry

则程序运行结果为:

HelloMarry

说明

(1)以上命令由两个参数组成,第一个参数是name,即执行程序文件名本身。第二个参数是Marry,是要求“问候”的人名。这两个参数分别是一个一维字符数组,第一个一维字符数组的起始地址放在argv[0] 中,第二个一维字符数组的起始地址放在argv[1] 中,如图8-11所示。图8-11命令行参数的用法

(2)

argc接收命令输入时参数的个数。因此,在程序中判断如果argc!=2,则说明用户输入的命令不规范,所以输出提示信息。如果用户命令输入正确,则显示“Hello”加第二个参数的字符串。

8.4指 针 与 函 数

8.4.1指针作为函数参数

1.指针变量作为函数参数

函数的参数可以是指针类型。它的作用是将一个变量的地址传送到另一个函数的形参中。这属于参数传递中的“传址”方式。

【例8.13】输入两个整数,按大小顺序输出。要求用函数处理,且用指针类型的数据作为函数参数。程序如下:

voidswap(int*pa,int*pb)

{

inttemp;

temp=*pa;

*pa=*pb;

*pb=temp;

}

#include<stdio.h>

voidmain()

{

inta,b;

int*p1,*p2;

printf(“inputa,b:”);

scanf(“%d,%d”,&a,&b);

p1=&a;

p2=&b;

if(a<b)

swap(p1,p2);

printf(“theMaxis%d,theMinis%d\n”,a,b);

}

程序运行结果:

inputa,b:3,5↙

theMaxis5,theMinis3

说明

(1)swap是用户定义的函数,它的作用是交换两个变量(a和b)的值。swap函数的形参pa、pb是指针变量。

(2)程序运行时,先执行main函数,输入a和b的值。然后将a和b的地址分别赋给指针变量p1和p2,使p1指向a,p2指向b。接着执行if语句,如果a < b,则执行swap函数。注意,p1和p2是指针变量作为函数实参。在函数调用时,将实参变量的值传递给形参变量,因此形参pa的值为&a,pb的值为&b。这时pa指向变量a,pb指向变量b,如图8-12(a)所示。

(3)执行swap函数的函数体,使pa所指的值和pb所指的值互换,其过程分别如图8-12(b)、(c)、(d)所示。从图示可以看出,a和b的值已经实现互换。

(4)返回主程序后,将a和b的值输出,得到交换后的结果。图8-12指针作为函数参数对比程序一:

swap(int*pa,int*pb)

{

int*temp;

*temp=*pa;

*pa=*pb;

*pb=temp;

}

#include<stdio.h>

voidmain()

{

inta,b;

int*p1,*p2;

printf(“inputa,b:”);

scanf(“%d,%d”,&a,&b);

p1=&a;

p2=&b;

if(a<b)

swap(p1,p2);

printf("theMaxis%d,theMinis%d\n",a,b);

}

说明

此程序是错误程序。因为在swap函数中,temp变量是指针变量,但没有指向任何其他变量。因此,在实现交换过程中,“*temp=*pa;”企图将pa所指的值即a的值赋给temp所指的变量,但temp并未指向任何一个存储单元,所以赋值语句错误。对比程序二:

voidswap(intx,inty)

{

inttemp;

temp=x;

x=y;

y=temp;

}

#include<stdio.h>

voidmain()

{

inta,b;

printf(“inputa,b:”);

scanf(“%d,%d”,&a,&b);

if(a<b)

swap(a,b);

printf("theMaxis%d,theMinis%d\n",a,b);

}

说明

在此程序中,swap函数不能实现a和b互换。这是因为,此处的参数传递属于“传值”方式,形参的改变不会影响实参。

对比程序三:

swap(int*pa,int*pb)

{

int*temp;

temp=pa;

pa=pb;

pb=temp;

}

#include<stdio.h>

voidmain()

{

inta,b;

printf(“inputa,b:”);

scanf(“%d,%d”,&a,&b);

if(a<b)

swap(&a,&b);

printf("theMaxis%d,theMinis%d\n",a,b);

}

说明

(1)此程序的main函数中,调用swap函数的实参是a、b变量的地址,即&a和&b。既然实参传递的是指针,那么形参只能是指针变量。

(2)在swap函数中,交换的是pa和pb的指向,而a和b的值并没有真正的交换。因此,返回主函数后再输出a和b的值,它们的值并没有按大小重新排列,而是保持不变。

2.数组名作为函数参数

数组名就是数组的指针,因此,数组名作为函数参数时传递的数据是整个数组的首地址。

数组名可以作为函数的实参,而与其对应的形参应为同类型的数组。例如:

main()

{

intarray[5];

f(array,5);

}

f(intarr[],intn);

{

}………

array是一个整数数组的名称,作为实参,对应的形参arr同样为一个整数数组,但特殊的是,在定义arr数组时,并没有定义它的长度。这是因为,实参array传递的实际上是数组的首地址,即一个指针,对应的形参arr接收这一地址,那么arr与array指向同一起始地址,如图8-13所示。因此,操作arr数组实际上就是操作array数组。图8-13数组名作为实参

【例8.14】

编写函数求数组的平均值,要求数组的值在主函数中输入,平均值也在主函数中输出。

程序如下:

floataver(float*pa,intn)

{

inti;

floatav,s=0;

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

s=s+*pa++; /*累加数组元素和*/

av=s/5;

returnav;

}

main()

{

floatscore[5],av;

inti;

printf(“\ninput5scores:\n”);

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

scanf(“%f”,&score[i]);

av=aver(score,5);

printf(“averagescoreis%5.2f”,av);

}

【例8.15】

将数组a中的n个整数按相反顺序存放。

方法一:

voidinv(intx[],intn)

{

inttemp,i,j,m=(n-1)/2;

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

{

j=n-1-i; /*

j,i是所要交换的对应元素的下标*/

temp=x[i];x[i]=x[j];x[j]=temp;

}

}

#include<stdio.h>

voidmain()

{

inti,a[10]={3,7,9,11,0,6,7,5,4,2};

printf(“Theoriginalarray:\n”);

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

/*输出原始数组的值*/

printf(“%d”,a[i]);

printf(“\n”);

inv(a,10);

printf(“Thearrayhasbeeninverted:\n”);

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

printf("%d",a[i]); /*输出调整后的数组的值*/

}

说明

先将a[0]与a[n-1]对换,再将a[1]与a[n-2]对换……直到将a[(n-1/2)]与a[n-int((n-1)/2)]对换。现用循环处理此问题,设两个“位置指示变量”i和j,i的初值为0,j的初值为n-1。将a[i]与a[j]交换,然后使i的值加1,j的值减1,再将a[i]与a[j]交换,直到i=(n-1)/2为止。方法二:

voidinv(int*x,intn) /*形参x为指针变量*/

{

int*p,temp,*i,*j,m=(n-1)/2;

i=x;j=x+n-1;p=x+m;

for(;i<=p;i++,j--)

{temp=*i;*i=*j;*j=temp;} /*

i,j是指向要交换的对应元素的指针*/

return;

}

main()

{

inti,a[10]={3,7,9,11,0,6,7,5,4,2};

printf(“Theoriginalarray:\n”);

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

printf(“%d”,a[i]);

printf(“\n”);

inv(a,10);

printf(“Thearrayhasbeeninverted:\n”);

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

printf(“%d”,a[i]);

printf("\n");

}下面将在函数中改变实参数组值的方法作一归纳。

(1)形参和实参都用数组名。例如:

voidf(intx[],intn)

{

}

voidmain()

{

inta[10];

f(a,10);

}………

(2)实参用数组名,形参用指针变量。例如:

voidf(int*x,intn)

{

}

voidmain()

{

inta[10];

f(a,10);

}………

(3)形参和实参都用指针变量。例如:

voidf(int*x,intn)

{

}

voidmain()

{

inta[10],*p=a;

f(p,10);

}………图8-14数组名作为实参与形参的对应关系

(4)实参用指针变量,形参用数组名。例如:

voidf(intx[],intn

)

{

}

voidmain()

{

inta[10],*p=a;

f(p,10);

}………8.4.2指针函数

指针函数是指函数的类型是指针型的。在定义函数时,每个函数都有自己的类型,所谓函数类型,是指函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针函数。

指针函数与非指针函数定义上的区别主要在于对函数名的声明上,其定义的一般形式与其他函数定义相似,即

类型说明符*函数名(形参表)

{

…/*函数体*/

}在此定义之中,函数名之前加了“ * ”,表明这是一个指针型函数,即返回值是一个指针。因此在指针函数的函数体中必有一个“return指针;”的返回语句。

例如:

int*ap(intx,inty)

{

…/*函数体*/

}

【例8.16】利用指针函数从数列中选择最大数,并最终完成选择排序。

程序如下:

int*max(int*x,intnum)

{

inti,*this=x; /*this指向未排好序的数列的第一个元素*/

for(i=1;i<num;i++)

if(*(x+i)>*this)

this=x+i;

returnthis;

}

voidswap(int*first,int*max)

{

inttemp;

temp=*first;

*first=*max;

*max=temp;

}

voidsort(int*pa,intn)

/*pa指向array数组*/

{

int*pmax,i,j;

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

{

pmax=max(pa+i,n-i); /*调用求最大值函数,将返回数组元素中的最大元素

swap(pa+i,pmax); 指针并赋值在pmax中*/

}

}

main()

{

intarray[]={13,5,7,9,40,60,23,77,81,49};

inti;

printf(“Theoriginalarray:”);

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

printf(“%d”,array[i]);

printf(“\n”);

sort(array,10); /*调用排序函数*/

printf(“Thearrayhasbeensorted:”);

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

printf("%d",array[i]);

printf("\n");

}

说明

(1)本程序中有三个函数,即sort、max和swap,分别用于排序、选择最大值和交换。main函数在调用sort函数时,以array数组名及数组长度10作为实参,完成参数传递后,sort函数的形参指针pa指向array数组的首地址。

(2)sort函数实现选择排序,在每一趟排序过程中选择最大值的步骤是由函数max完成的。max函数是指针函数,比较最大值的算法与“打擂台”法基本一致,但在此函数中,没有进行任何交换,因为交换的过程耗时太多,特别是交换频繁发生时,会导致程序执行效率下降。在这里,采用的方法是用this指针始终指向已经比较过的数列中的最大数。函数最后返回this指针。8.4.3函数指针

尽管函数不是变量,但函数和变量同样都要占用一段内存单元,函数也有一个地址,该地址是编译器分配给这个函数的,这个地址可以赋给一个指针,因此指针变量可以指向整型、实型等变量,也可以指向函数。指向函数的指针叫做函数指针。而函数的地址实际上是这个函数的入口位置,因此指针指向函数实际上是指向函数对应的程序段的起始地址。正是由于这一点,一个函数指针可以用来调用一个函数。

1.定义函数指针变量的一般格式

定义函数指针变量的一般格式为

函数类型(*函数指针标识符)(形参类型说明表);

使用函数指针时应注意以下几点:

(1)函数指针与它指向的函数的参数个数和类型都应该是一致的。

(2)函数指针的类型和函数的返回值类型也必须是一致的。

2.函数指针的赋值

函数名和数组名一样代表了函数代码的首地址,因此在赋值时,直接将函数指针指向函数名即可。

例如:

intfun(intx); /*声明一个函数*/

int(*f)(intx); /*声明一个函数指针*/

f=fun;

3.通过函数指针调用函数

函数指针是通过函数名及有关参数进行调用的。与其他指针变量相类似,如果指针变量f是指向某整型变量i的指针,则 *f等于它所指的变量。如果 *f是指向函数func(x)的指针,则 *f就代表这个强制性指向的函数func。所以在执行“f=func;”之后,(*f)和fun就代表同一函数。

由于函数指针指向存储区中的某个函数,因此可以通过函数指针调用相应的函数。为实现这一过程,应执行以下三步:

(1)定义函数指针变量。例如:

int(*f)(intx);

(2)对函数指针变量赋值。例如:

f=fun;

(3)使用(*f)(参数表)进行函数调用。

【例8.17】

利用指针函数实现调用求最大值函数max。

程序如下:

intmax(intx,inty)

{

if(x>=y)

returnx;

else

returny;

}

main()

{

inti,a,b;

int(*func)(

);

printf(“请输入a,b的值:”);

scanf(“%d,%d”,&a,&b);

func=max;

/*func指向max函数*/

printf(“最大值是:”);

printf(“%d”,(*func)(a,b));

/*通过func指针访问max函数*/

}

说明

在max函数中,返回x和y之中的大值。在主函数中,func指向max函数,通过func调用max函数。 8.5指针 的 指 针

指针变量是存储其他类型变量地址的变量,这里的其他类型也包括指针类型。也就是说,一个指针变量中存放的地址可以是另一个指针变量的地址,则称这个指针变量为指向指针的指针,也叫做二级指针。

这种指针的指针与指向数据间的关系如图8-15所示。图8-15指针的指针定义二级指针的一般形式为

数据类型**变量标识符;

例如:

int**pp;

其中:** 表示pp是二级指针;int表示pp中存放指向整型变量指针的地址。指针的指针只能存储其他指针变量的地址,因此对指针的指针的一般赋值过程是:

数据类型*p1;

数据类型**p2;

p2=&p1;二级指针的数据类型定义必须与它所指的指针变量的类型定义相同。要引用二级指针所指的数据值,其访问的一般形式为

**指针变量名;

【例8.18】利用指针的指针编写程序,判断在全班同学中是否有某同学的名字。

程序如下:

main()

{

char*classmates[]={“WangLin”,“LiQi”,“ZhangMing”,“ChenLu”,“LiLan”};

charname[8];

char**p;

inti,flag=0;

printf("请输入所要查找的学生姓名:");

scanf(“%s”,name);

/*name变量用来保存要查找的学生姓名*/

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

{

p=classmates+i; /*classmates是数组的首地址,p在循环中依次指向二维数组的每一行*/

if(strcmp(*p,name)==0)

{

flag=1;

break;

}

}

if(flag==1)

printf(“%s已报名\n”,name);

else

printf("%s尚未报名\n",name);

}

说明

(1)classmates是指针数组,分别指向5个常量字符串,如图8-16所示。classmates是classmates指针数组的首地址。classmates+i是第i个常量字符串的起始地址。

(2)定义一个指针的指针变量p,使它指向指针数组元素。在循环中,通过p+i分别得到classmates[0]~classmates[4] 元素的地址。

(3)在访问常量字符串时,并没有使用 **p,而使用 *p进行访问。这就是在本例中指针的指针指向的是字符串的缘故。图8-16指针的指针与指针数组

【例8.19】

一个指针数组的元素指向数据的简单例子。

程序如下:

main()

{

staticinta[5]={1,3,5,7,9};

int*num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};

/*指针数组num的每一个元素指向对应的a数组的每一个元素*/

int**p,i;

p=num;

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

{printf("%d\t",**p);p++;}

}

说明

(1)num是指针数组,在初始化时,分别将a数组的5个元素的地址赋值给num数组的对应元素,因此,num数组的每一个元素指向对应的a数组的每一个元素。

(2)p是指向指针的指针,p被赋的初值为

温馨提示

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

评论

0/150

提交评论