第7章 指针、函数和数组7.1 变量的地址和指针7.2 指针变量7.3 一维数组和指针7.4 一维数组和函数7.5 二维数组和指针7.6 二维数组和函数7.7 字符串和指针7.8 指向函数的指针7.9 返回指针值的函数7.10 指针数组和二级指针7.1 变量的地址和指 针存储空间:存储空间(Storage Space)是一种逻辑存储结构,它对上层应用提供寻址空间和相应的逻辑存储单元(Logic S torage Unit),并提供操作集(Access Operations)用于存取存储单元中的数据;同时在内部具有表示存储空间的 数据结构(Meta data)实现逻辑存储单元和下层存储空间地址的映射关系;具有访问下层存储空间的存取操作集,把上层对于逻辑存储单 元的数据存取请求解析传递给下级存储空间。 C语言程序任何一个变量都必须占用一定的存储空间。变量的存储空间依变量的类型而决定。 7. 1 变量的地址和指针例7.1 不同类型变量的存储空间#include main (){ int i;long l ;char c;float f; double d; printf ("int变量占用的存储空间为:%d\n",sizeof (i )); printf ("long变量占用的存储空间为:%d\n",sizeof (l)); printf ("char变量占用的 存储空间为:%d\n",sizeof (c)); printf ("float变量占用的存储空间为:%d\n",sizeof (f )); printf ("double变量占用的存储空间为:%d\n",sizeof (d)); printf ("\n");}运 行结果: int变量占用的存储空间为:4long变量占用的存储空间为:4char变量占用的存储空间为:1float变量占用的存储空 间为:4double变量占用的存储空间为:8分析:int型变量的存储空间为4。long型变量的存储空间为4,char型变量的存储空 间为1,float型变量的存储空间为4,double型变量的存储空间为8。其中,单位为字节。 7.1 变量的地址和指针问题:变量的 存储空间的实际地址应该如何获取?C语言中,我们采取&变量名的方法完成变量地址的获取。采用&变量名的方法取得变量的地址也是一个值,该 内存地址值就是实际占用多少存储空间7.1 变量的地址和指针例7.2 变量地址的存储空间#include main (){ int i;long l;char c;float f; double d; printf ("int变量占用的存储空间 为:%d\n",sizeof (&i)); printf ("long变量占用的存储空间为:%d\n",sizeof (&l)); printf ("char变量占用的存储空间为:%d\n",sizeof (&c)); printf ("float变量占用的存 储空间为:%d\n",sizeof (&f)); printf ("double变量占用的存储空间为:%d\n",sizeof ( &d)); printf ("\n");}运行结果:int变量占用的存储空间为:4long变量占用的存储空间为:4char变量占用 的存储空间为:4float变量占用的存储空间为:4double变量占用的存储空间为:4分析:从上面的例子可以看到,基本数据类型变量 在系统中使用4个字节来表示该变量的内存地址。那么考虑是否有有一种方式:直接通过地址来访问该变量,而无需使用该变量的名字?答案是肯定 的,那就是指针变量。而变量的内存地址通常被称为:指针。7.2 指针变量 7.2.1 指针变量的定义7.2.2 怎样引用指针变量7. 2.3 指针变量作为函数参数 7.2.1 指针变量的定义指针:就是某个变量的内存地址。如果有一种特殊变量能够存储各种类型变量的内存 地址,则在使用当中通过改变这种特殊变量的值就可以达到变换不同内存地址的目的。那么当然有可能通过该特殊变量来访问对应的内存。 指针变 量:定义为存储变量内存地址的一种特殊变量。 7.2.1 指针变量的定义指针变量的声明方法比较简单,且任何一种类型的变量都可以声明指 针变量。格式: 类型 指针变量名;例7.3 指针的声明int p; //p被声明为整型变量的指针 char pc; / /pc被声明为字符变量的指针 long pl; //pl被声明为长整型变量的指针 float pf; //pf被声明为浮点型 变量的指针 double pd; //pd被声明为双精度浮点类型变量的指针//也可以声明为无类型的指针:void pv; //pv被声明为任意类型变量的指针 7.2.1 指针变量的定义既然指针是某个内存的地址,指针变量是一种特殊变量,指针的示意图可以 使用右边的图来表达前面的例子:返回7.2.2 怎样引用指针变量 指针是一种特殊类型的变量,任何一种类型的变量都可以把其所在的内存地 址赋给指针变量。赋值的方法如下:指针变量名 = &变量名; 7.2.2 怎样引用指针变量例7.4 指针类型的赋值与占用的存储空间# include main (){ int i,pi;long l,pl;char c,pc;float f ,pf;double d,pd; pi = &i;pl = &l;pc = &c;pf = &f; pd = &d; prin tf ("int变量的指针占用的存储空间为:%d\n",sizeof (pi)); printf ("long变量的指针占用的存储 空间为:%d\n",sizeof (pl)); printf ("char变量的指针占用的存储空间为:%d\n",sizeof ( pc)); printf ("float变量的指针占用的存储空间为:%d\n",sizeof (pf)); printf ("do uble变量的指针占用的存储空间为:%d\n",sizeof (pd)); printf ("\n");}运行结果:int变量的指 针占用的存储空间为:4long变量的指针占用的存储空间为:4char变量的指针占用的存储空间为:4float变量的指针占用的存储空 间为:4double变量的指针占用的存储空间为:4分析:上例子可见,赋值给指针变量的方法很简单,只需要取得变量的地址就可以把该地址 赋值给对应类型的指针变量了。 7.2.2 怎样引用指针变量问题:那么如何使用该指针变量?使用指针变量的目的在于使用另外一条途径对变 量进行操作。 指针变量的引用方法如下:格式: 指针名 = 表达式; 请特别注意:指针名 = &变量名;与 指针名 = 表达式; 两者之间的区别:1、指针名 = &变量名;//这个语句表达了:把变量的地址存放到指针变量中。2、指针名 = 表达式;//这个语 句表达了:将表达式的计算结果值存储到指针变量所存放的内存地址所代表的内存单元中去 。7.2.2 怎样引用指针变量例7.5 引用指针 变量的例子#include main (){ int i,pi=&i; long l,pl=&l; char c,pc=&c; float f,pf=&f; double d,pd=&d; i=1; printf ("i变量的值为: %d\n",i); pi = 10; printf ("i变量的值改变为:%d,pi的值为:%d\n",i,pi); l= 16; printf ("l变量的值为:%ld\n",l); pl = 10; printf ("l变量的值改变为:%d,pl 的值为:%ld\n",i,pl); c=''a''; printf ("c变量的值为:%c\n",c); pc = ''D''; p rintf ("c变量的值改变为:%c,pc的值为:%c\n",c,pc); f=1.5; printf ("f变量的值为 :%f\n",f); pf = 55.5; printf ("f变量的值改变为:%f,pf的值为:%f\n",f,pf); d=123.456; printf ("d变量的值为:%lf\n",d); pd = 654.321; printf ("d变量 的值改变为:%lf,pd的值为:%lf\n",f,pd); printf ("\n");} 运行结果:i变量的值为:1i变量的 值改变为:10,pi的值为:10l变量的值为:16l变量的值改变为:10,pl的值为:10c变量的值为:ac变量的值改变为:D ,pc的值为:Df变量的值为:1.500000f变量的值改变为:55.500000,pf的值为:55.500000d变量的值为 :123.456000d变量的值改变为:55.500000,pd的值为:654.321000分析:例子清楚地说明了指针变量是变量 的别名,通过指针变量获得了变量的另外一种操作方式,给变量的赋值提供了另一种操作方法。但是,应该注意到,由于优先级与结合性等问题,指 针的合理使用非常关键,看这样一个例子 7.2.2 怎样引用指针变量例7.6 若定义语句:int year=2009,p=&ye ar;,以下不能使变量 year 中的值增至 2010 的语句是:(2011年9月全国计算机等级考试二级C试题选择题第25题)A. p+=1; B.(p)++; C.++(p); D.p++;分析:答案A.p+=1;可理解为:p=p+1,即2009 +1=2010。 答案B.(p)++;语句同A答案只是明确了优先级为(p)的内容自加一 答案C.++(p) ;同答案B 答案D.p++; 由于++的优先级高于故此先做加法,即先把p的值增加一,这样做的结果是:指针 变量p中保存的year变量地址被改变了(p变量的内容加一之后,保存的内容就已经不是year变量的地址了,具体是什么地址是不确定的) ,则p的内容也就无法预知。 返回7.2.3 指针变量作为函数参数 指针变量具有高度的灵活性,它可以作为函数的参数来使用。将指针作 为函数的参数使用方法如下:函数返回值 函数名 (…,类型名 指针名,…)也就是简单地将指针(地址)赋给函数作为函数的参数即可。特 别需要注意的是:当函数的形参为指针,若实参为变量时提供的是变量的地址,需要使用:&变量名。同样,当函数的形参为指针,若实参为数组时 ,提供的是数组名(数组名就是数组的首地址),即:数组名。关于数组名就是数组的首地址问题在下一节中会详细介绍。这里首先来看指针变量做 为函数的参数。 7.2.3 指针变量作为函数参数例7.7 指针作为参数传递(2010年3月全国计算机等级考试二级C试题选择题第26 题)#include //定义了函数fun。其中参数c是一个指针变量,而d不是指针变量void fun(char c,int d){ c=c+1; d=d+1; printf ("%c,%c",c,d);}main(){ char b =''a'',a=''A''; fun(&b,a); printf ("%c,%c\n",b,a);} 程序运行后的输出结果是 A)b ,B,b,A B)b,B,B,A C)a,B,B,a D)a,B,a,B7.3 一维数组和指针7.3.1 数组首地 址7.3.2 数组元素的指针7.3.3 通过指针引用数组元素7.3.1 数组首地址回顾:数组名就是数组的首地址。因指针变量存储的内 容就是一个地址,故理论上就可以直接把数组名赋值给指针变量。由于C语言的编译系统对于数组中某个元素的表示与指向数组的指针表示某个数组 元素的时候是同等对待的,因此对于指向数组的指针是对数组操作的另外一种表现形式。 7.3.1 数组首地址首先对比变量的地址赋值给指针 变量与数组名赋值给指针变量。int i,p=&i; //变量使用取地址“&”符号来取得其地址值,赋值给指针int a[10] ,p=a; //数组直接将其名字赋值给指针可以看到,数组的首地址是可以赋值给指针变量的。数组的声明方式通常为: 类型名 数组名[ 长度] ;上述的声明实际上产生了如下的一种内存结构: 上图可以清晰地了解到,数组中任何一个元素的引用方式都是使用:数组名[下标编号 ]的方式来引用的。数组名作为整个数组的标示,表达为某类型整个数组空间的首地址,以便于使用下标引用方式逐一访问 返回7.3.2 数组 元素的指针 方式一:普通方式可以使用如下方式来给指针赋值,使得该指针指向数组中的某个元素:例7.8 给数组元素赋值的普通方式int a[10],p; ……p=&a[5]; ……p=&a[0]; ……分析:语句int a[10],p;声明了一个数组a,它 有十个整型元素。并声明了一个整数指针变量。语句p=&a[5];如果希望访问数组a的第六个元素,则给他制造一个新名字:p,也就是将 数组的第六个元素的地址(&a[5])直接赋值给整型指针变量p。语句p=&a[0];如果希望访问数组a的第一个元素,可以使用上述的办 法。这个用法完全等价于p=a ,这是因为第一个元素的地址就是整个数组的首地址7.3.2 数组元素的指针方式二:递加方式假设对指针的 定义如下:int a[10],p=a; 则a数组的任意一个元素可以使用下面的方式来表达:数组a中的第i个元素的名称是:a[i] ,也可以是:(p+i)。这种方式提供了更加灵活的数组访问方法。 7.3.2 数组元素的指针上述两种用法可以通过一张图来对比:返回 7.3.3 通过指针引用数组元素 方法一:普通引用法。当对数组的某个元素直接访问时,可以使用这种方法。例7.9 数组元素的普通引用 int a[20],p; p = &a[5]; //取得第六个元素的地址p = 34; //给第六个元素赋值为整数34分析 :上述代码的功能为访问数组的第六个元素。语句p = &a[5];的作用是取得第六个元素的地址。语句p = 34;是给第六个元素赋 值为整数34。很明显,上述的方法只能给数组中的某一个元素赋值,如果希望利用指针给数组中所有元素赋值,则可以如下编写代码:例7.10 使用指针给数组全部元素赋值为1int a[20],p;int i; p = a; for(i=0;i<20;i++,p++) p= 1;分析:注意到,上述代码中语句p = a;与语句p=&a[0];等价。该语句的作用为取得数组的首地址。 7.3.3 通 过指针引用数组元素方法二:递加引用。当引用数组元素的时候也可以采用固定首地址,而改变其增量来实现普通方法的赋值方式。例7.11 数 组某一个元素的递加引用int a[20],p;int i; p = a; (p+5) = 34; 分析:上述代码的功能为访 问数组的第六个元素。语句p = a;与语句p=&a[0];等价。该语句的作用为取得数组的首地址。语句(p+5) = 34;作用为 给第六个元素赋值为整数34。例7.12使用指针给数组全部元素赋值为1int a[20],p;int i; p = a; //语 句p=&a[0];与p=a;等价。该语句的作用为取得数组的首地址。for(i=0;i<20;i++,p++) (p+i)= 1; 分析:语句p = a;与语句p=&a[0];等价。该语句的作用为取得数组的首地址。 7.4 一维数组和函数7.4.1 数组元素做函 数参数7.4.2 数组名做函数参数7.4.3 数组元素地址做函数参数7.4.4 函数的指针形参和函数体中数组的区别 7.4.1 数 组元素做函数参数 在函数的参数当中,可以使用普通变量(自动变量)。这种用法与使用数组的元素作为参数参数的用法完全一致。即:例7.1 3 函数参数使用普通变量与数组元素作为函数参数int i=5,a[3]={5,5,5};fun(i); 或者使用fun(a[2]) ;分析:上述两个语句实现了完全相同的功能。就是将值为5的变量传递给函数fun,从传递的参数值、传递方式、传递后的处理机制来看,fu n(i)与fun(a[2])没有本质上的区别。这是因为希望传递的参数值为5,变量i与数组的三个元素的值都是5;第二,参数传递时都没 有用到&符号来取得地址;第三,都只使用了变量本身。这说明了一个问题:数组中任意一个元素在实质上就是一个普通变量。因此如果不考虑变量 名的情况下,上例中的两个语句完全等价。 7.4.1 数组元素做函数参数还应该特别看到:在调用函数fun时,调用的机制也是完全一样的 。即进入fun函数时,产生变量的副本(数组元素作为变量来产生副本),然后进入函数、完成函数功能、退出函数时将产生的副本删除,其存储 空间交还给系统。例7.13中fun(i)与fun(a[2])的调用流程如下图: 返回7.4.2 数组名做函数参数 数组的中元素作为 函数参数的传递过程中传递的是该元素的值。在这个传递过程中,进入函数后为了保证该值不能被改变,故函数为该参数生成了一个副本。在函数退 出时,删除该副本的存储空间。这样参数的值既能被函数所使用,又能保证在函数执行过程中参数的值不会变化。理解这个过程类似于理解我们数学 上的函数运算:例7.14 数学的运算关系 y = 2x+6当 x = 1时,有: y =21+6=8注意到,此时的x 仍然等于1.那么很多时候需要改变数组中的元素值,则此时可以使用两种方式来解决这个问题,第一种方式就是传递数组的首地址,也就是数组名 作为函数的参数。 7.4.2 数组名做函数参数例7.15 有下程序(函数fun只对下标为偶数的元素进行操作)。(2010年9月全国 计算机等级考试二级C试题选择题第30题)#include void fun(int a,int n){ int i,j,k,t; for (i=0;i 2) if(a[j]>a[k]) k=j; t=a[i]; a[i]=a[k]; a[k]= t; }}main(){ int aa[10]={1,2,3,4,5,6,7},i; fun(aa,7); //调用fun函数 for(i=0;i<7;i++) printf("%d,",aa[i]); //打印执行之后的数组值 printf("\n");} 程序运行后的输出结果是: A)7,2,5,4,3,6,1 B)1,6,3,4,5,2,7 C)7,6,5,4,3,2,1 D)1,7,3,5,6;2,1 答案:选A。 返回7.4.3 数组元素地址做函数参数 在某些 时候仅仅只需要访问数组中的一个元素,则通常无需传递首地址。仅仅只需传递该元素地址即可。例7.16 访问数组的某一个元素#inclu de void fun(int p){ p = 6;}main (){ int a[10]={1,2,3,4 ,5,6,7,8,9,10}; int i; printf ("\na数组原来的值为:\n"); for (i=0;i<10;i+ +)printf (" %d ",a[i]); fun(&a[2]); printf ("\na数组现在的值为:\n"); for (i=0;i<10;i++)printf (" %d ",a[i]); printf ("\n");}运行结果:a数组原来的值为 : 1 2 3 4 5 6 7 8 9 10a数组现在的值为:2 6 4 5 6 7 8 9 1 0分析:上述程序的功能是将数组的第3个元素值打印出来,并将其原来值3改为6。 7.4.3 数组元素地址做函数参数特别注意到,即便是 传递数组中的一个元素地址,也可以访问数组中其他的元素。因为该元素地址已经帮指针定位到了数组的某一个元素的位置。可以通过这个位置推断 其他元素的位置。下面我们来看一个例子:例7.17 通过元素访问数组(2011年9月全国计算机等级考试二级C试题第27题)#incl ude void fun(int p) {printf(“%d\n”,p[5]); }main(){int a[10]={1,2,3,4,5,6,7,8,9,10};fun(&a[3]); }程序运行后的输出结果是 A.5 B.6 C.8 D.9 答案:选D。返回7.4.4 函数的指针形参和函数体中数组的区别 为什么有区别?函数把指向数组的指针作为形 参的时候和函数体中的数组是有本质区别的。当指向数组的指针作为函数的参数时,事实上对于实参是一个副本。因此将指向数组的指针参数传递给 函数并不改变该参数的具体数值,也就是无法改变该参数。因为即使函数体中对该形参有赋值等操作,也是改变该形参的副本。但是对于函数体中的 数组,是进入函数体是分配空间,函数执行结束时被系统收回。因此两者具有显著的区别。 7.4.4 函数的指针形参和函数体中数组的区别区 别一:将指向数组元素的指针作为函数的形参 如7.17中语句fun(&a[3]);实际上传递给函数的实参是数组a的第四个元素的地址。 p使用的仅仅是:数组a的第四个元素的地址。这里的&a[3]使用方法是:取得数组a第四个元素地址,并将其作为常量来赋值给函数fun的 变量p来使用。在例7.15中语句fun(aa,7);使用的是数组的首地址aa(数组名aa),并将数组名值赋值给指针变量a,然后将指 针作为数组来使用。事实上这也是可行的。但是注意到,这种使用方法仅仅是fun函数中的指针变量a与main函数中的数组aa建立了一个临 时联系而已。在fun函数中通过这种临时联系来访问main函数中的aa数组。当fun函数执行完毕时,指针变量a的空间会交还给操作系统 (注意到此时main函数尚未执行完),这个联系被切断,但是并未将main函数中的aa数组空间交还给操作系统。将数组地址作为参数时, 改变的是该地址对应的数组内容,当函数执行完毕,修改会保存。 7.4.4 函数的指针形参和函数体中数组的区别区别二:函数体中的数组函 数体中的数组与参数不同,函数体中数组的作用范围仅仅限于本函数(声明为静态除外)。因此在函数执行完毕之后,该数组空间将不复存在。下一 次在调用此函数时,则重新生成一个该函数的调用副本,函数中的数组空间也重新分配。当然,执行结束时,数组空间仍然会还给操作系统。也就是 函数中的数组仅在本函数内部有效! 7.5 二维数组和指针 7.5.1 二维数组首地址7.5.2 二维数组元素的地址7.5.3 二维 数组元素的指针变量7.5.4 指向第一维数组的指针变量7.5.1 二维数组首地址 同一维数组一样,二维数组的数组名就是二维数组的首 地址。首地址表达了二维数组的基本访问位置。C语言中,对于数组的实现而言,多维数组与一维数组是一样的,也就是说在实现方法上只有一维数 组。因此,其二维数组是作为多个一维数组来对待的。例7.18 多维数组分解int a[3][5];则可以这样解释:a数组的第一维有3 个元素。每个元素是一维数组,其中一维数组的元素个数是5个。如右图: 返回7.5.2 二维数组元素的地址 可以看到,二维数组的关键问 题在于如何表达和计算二维数组元素的地址。找到其地址的目的是访问某个元素。当声明或定义一个二维数组时,其某个元素的地址可以依照如下公 式来计算:某元素的地址=首地址+某元素的行下标行元素个数+列下标例7.19 数组元素地址的计算int a[5][7];分析:上述 定义了5行7列的二维数组(二维数组可以直接理解为一个行乘以列形式的方阵)。元素a[2][3]的地址=元素a[0][0]的地址+2 7+3=元素a[0][0]的地址+17注意到上面的地址公式计算中行元素个数为7个。这里我们计算的元素a[2][3]的地址是用来给指 针变量用的,假设有个指针变量被赋值为a数组的首地址的话。实际上如果仅仅使用数组访问的时候,我们是无需该地址的。因为a[2][3]这 个写法已经表达了该变量。另外一种方法就是直接取得数组元素a[2][3]的地址:&a[2][3]。返回7.5.3 二维数组元素的指针 变量 讨论:指针变量如何赋值、指针变量如何访问二维数组中的某个元素。 指针变量的赋值与一维数组完全一致,仅仅数组是二维而已。来看一 个例子:例7.20 用指针访问二维数组中最后一个元素#include main (){ int a[3][2]= {{0,0},{0,0},{0,0}},p; p=a[0]; (p+22+1)=9; printf ("\n %d \n",a[2][1]);}运行结果: 97.5.3 二维数组元素的指针变量使用指针访问二维数组的步骤:第一步:定义指针变量与二维 数组。例如:int a[3][2],p;第二步:将数组的首地址赋值给指针变量。例如:p=&a[0][0]或者是p=a[0]. 第 三步:使用指针来访问二维数组的第i行与第j列的某个元素。 a[i][j]对应(p+行下标i行元素个数+列下表j) 7.5.3 二维数组元素的指针变量例7.21 使用一级指针访问二维数组#include main (){ int a[3][ 2]={{0,1},{10,11},{20,21}},p; int i,j; p=a[0]; for (i=0;i<3 ;i++) for (j=0;j<2;j++) printf (" %d ",(p+i2+j)); printf ("\ n");}运行结果:0 1 10 11 20 21Press any key to continue返回7.5.4 指向 第一维数组的指针变量 数组与指针的一个重要用法是:指向多维数组的指针。指向数组的指针说明如下:类型名 (指针名)[数组长度]; 解释为:说明了一个指针,该指针指存储了二维数组的第一维的地址,其第二维的元素个数为数组长度。比如:int (p)[3];解释为: p是一个指针,该指针未来记录一个二维数组的第一维的地址,其第二维的元素为int类型,第二维的元素个数为3。可见,事实上指向一个二维 数组的第一维的指针(上面的指针说明形式)实际上就是:定义了一个存放二维数组的首地址的指针变量。 7.5.4 指向第一维数组的指针变 量例7.22 若有定义int(pt)[3];,则下列说法正确的是(2010年3月全国计算机等级考试二级C试题选择题第27题) A)定义了基类型为int的三个指针变量 B)定义了基类型为int的具有三个元素的指针数组pt C)定义了一个名为pt、具有三 个元素的整型数组 D)定义了一个名为pt的指针变量,它可以指向每行有三个整数元素的二维数组 分析:根据题意,pt实际上是一个整型 指针,而且是一个指向指针的指针,它指向的是一个有3个元素的整型数组的数组的首地址,因此相对于二维数组的数组名。所以,选D。 可见, 指针与二维数组名属于同等级别,因此在赋值的时候只需要将数组名直接赋值给指针即可。 7.5.4 指向第一维数组的指针变量例7.23 若有定义语句:char s[3][10],(k)[3],p;,则以下赋值语句正确的是(2011年3月全国计算机等级考试二级C 试题选择题第28题) A)p=s; B)p=k; C)p=s[0]; D)k=s;分析:选项 A的含义是将二维数组的首地址赋值给了一个指针,选项B的含义是将一维数组的指针赋值给了一个指向变量的指针,选项C的含义是将一个一维数 组首地址赋值给了一个指针,选项D的含义是将二维数组赋值给了指向一维数组的指针。选C。 7.6 二维数组和函数 7.6.1 二维数组 名做函数参数7.6.2 指向第一维数组的指针变量做函数参数7.6.1 二维数组名做函数参数将二维数组名作为函数的参数来传递,关键在 于函数的参数必须定义为指针类型或者是二维数组。此时,由于数组名代表了数组的首地址,则函数中对数组元素的访问就改变了调用函数中数组元 素的值。来看这样一个例子:例7.24 有以下程序(2011年9月全国计算机等级考试二级C试题选择题第28题)#include dio.h>#define N 4void fun(int a[ ][N],int b[ ]){ int i; for(i=0; i ,{5,6,7,8},{9,10,11,12},{13,14,15,16}},y[N],i; fun(x,y);for(i=0; i 0,0, B.-3,-1,1,3,C.,0,1,2,3, D.-3,3,-3,-3, 7.6.1 二维数组名做函数参数分析 :fun函数的关键语句是b[i]=a[i][i]-a[i][N-1-i];该语句完成了将44矩阵中对角线上的元素值减去本行的第N -1-i个元素的值,并将它保存到数组b中。具体功能如下图:从上面的例子可以发现,函数fun(int a[ ][N],int b[ ])的参数声明中,a数组的写法为a[][N],这是一个类似指针的用法,但是不能作为指针来用(即不能写成a[N]的形式)。当访问二 维数组元素的时候必须写成a[i][j]的方式。选B。 返回7.6.2 指向第一维数组的指针变量做函数参数 指向第一维数组的指针变量 (实际上就是二维数组的首地址)也可以作为函数的参数。由于进入函数时对每个参数产生一个副本,如果将指向一维数组的指针变量作为函数的指 针,则会产生该指针参数的副本。由于指针本身的特点是通过指针访问对应的地址,所以即便是产生地址副本也能够完全访问相应地址所对应的内存 。那么一般有两种方式来将指向第一维数组的首地址作为参数。第一种方式是形参说明为数组的指针,实参为数组名。第二种方式是形参说明为数组 的指针,实参为指向第一维数组的指针变量。比如数组a和函数fun声明如下,且函数的参数为指向第一维数组的指针变量。int a[2][ 9],(p)[9];int fun (int (q)[9]);这个语句的等价语句为int fun (int ()[9]);则 调用函数fun时就可以使用两种方式:第一种方式作为函数的参数时,调用函数的语句为:fun(a);.第二种方式作为函数的参数时,调用 函数的语句为:p = a;fun(p);使用第二种方式时,注意到p是数组类型的指针变量,类型与形参的类型int (q)[9]相同 。赋值表达式p=a;使p指向二维数组a的第0行(即a数组的首地址,那么(p+1)表示第一行)。 7.7 字符串和指针7.7.1 字 符串的引用方式7.7.2 字符指针做函数参数7.7.3 字符指针变量和字符数组的比较7.7.1 字符串的引用方式使用指针来引用字符 串的方法与使用指针引用数组变量基本一致。必须看到引用字符串时的优势,可以使用标准库函数相对比较方便地操作整个字符串。注意到使用指针 引用数组时,不能方便地操作整个数组。如果需要逐个操作数组元素则通常需要使用循环来进行。例7.25 使用指针操作字符串#includ e #include main (){ char s[10]="hello"; char p=s; printf ("%s\n",s); strcpy (p,"HI"); printf ("%s",s); printf ("\n");}运行结果:helloHIPress any key to continue从上面的例子可以发现,熟练使用系统库函数 对于字符串的操作完全可以简化。当然,也可以使用指针来逐个操作字符串中的每个字符。 7.7.1 字符串的引用方式例7.26 有以下程 序(注:字符 a 的 ASCII 码值为 97)。(2011年9月全国计算机等级考试二级C选择题22题)#include io.h>main(){ char s={"abc"}; do{ printf("%d",s%10); ++s; }while(s); printf("\n");}程序运行后的输出结果是A.abc B.789 C.789 0 D.979898分析:选B。 注意:例7.26中字符串s的实际占用空间为4个字节(包含’\0’),而字符串的串长为3. 返回 7.7.2 字符指针做函数参数 同前述数组,指向字符串首地址的字符指针也可以做函数参数。下面来看一个例子:例7.27 指针作为函数 的参数(2011年9月全国计算机等级考试二级C试题选择题35题)#include#include h> void fun(char u,int n){ char x,y1,y2; y1=u; y2=u+n-1; w hile(y1 char a[]="1,2,3,4,5,6"; fun(a,strlen(a)); puts(a);}程序运行后的输出结果是: A.654321 B.115611 C.153525 D.123456答案:A7.7.2 字符指针做函数参数如图所示:while 循环体内的执行过程 返回7.7.3 字符指针变量和字符数组的比较 字符指针变量与字符数组是有差别的,这两个概念必须明确。首先,由例 7.4我们可以知道指针只占用4个字节的存储空间,字符指针也是这样。而字符数组则占用数组长度乘以数组字符类型大小的存储空间。即:字符 指针占用 4字节存储空间字符数组占用的存储空间字节数 = (数组长度+1)sizeof(char)实际上,字符数组占用的存储空间 字节数就等于数组长度加一。那是因为字符类型只占用一个字节的存储空间,上式sizeof(char)就等于1。并且注意到,(数组长度+ 1)中的那个”1”就是字符的结束符’\0’。来看一个例子:例7.28 字符指针变量与字符数组各自占用的空间#include dio.h>main (){ char p,a[10]; printf ("字符指针类型变量占用的空间为:%d",sizeof( p)); printf ("\n字符指针所指向的内存占用的空间为:%d",sizeof(p)); printf ("\n字符数组 变量占用的空间为:%d",sizeof(a)); printf ("\n");}运行结果:字符指针类型变量占用的空间为:4字符指针 所指向的内存占用的空间为:1字符数组变量占用的空间为:10 7.7.3 字符指针变量和字符数组的比较第二个问题就是:字符指针变量若 不赋值,则其指向的空间是不确定的,如果强行访问则必将导致内存泄露。但是字符数组若不赋值,系统仍然为其分配确定的存储空间,即使不予赋 值(数组中具体存放的内容不确定)仍然可以访问,只是将数组的内容作为字符而已。注意到,如果不对数组进行赋值而强行访问的话,则该数组没 有结束符标志’\0’,访问的时候就不能使用string.h库中的标准字符串函数访问。那么为了防止下标越界,访问的时候使用循环即可。 例7.29 字符指针不赋值的后果#include main (){ char p,a[10]; printf ("\n字符数组的内容为:%s",a); printf ("字符指针类型变量所表示的内容为:%c",p); printf ("\ n");}该例需上机测试。7.8 指向函数的指针 7.8.1 函数指针的定义和使用7.8.2 用函数指针变量调用函数7.8.3 用 指向函数的指针作函数参数 7.8.1 函数指针的定义和使用 函数指针:指针不仅能表示变量、表示数组与作为函数的参数,而且可以定义为 指向函数的指针。指向函数的指针就是函数指针。函数指针是存放函数入口地址的指针变量。一个函数的入口地址由函数的函数名表示,他是函数在 内存中的首条语句的入口地址。由于函数名是函数的入口地址,因此如果将函数名赋值给一个指针变量则该指针称为指向该函数的函数指针。 7. 8.1 函数指针的定义和使用在定义函数指针的时候,函数指针的类型必须与函数名的类型一致。函数指针的定义形式如下:类型说明 (标识 符)(参数表)比如定义一个返回值为整型函数的指针可以写成:int (p)(int a);。该语句可以说明为:p是一个指针,该指针 指向具有一个整型参数且返回值为整型的函数。 使用函数指针也比较简单,即将函数名赋值给同类型的函数指针即可。来看一个例子:例7.30 函数指针的定义与使用#include void fun (int a){ printf ("\n这是fun函数 ,带入的参数是:%d",a);}main (){ void (p) (int a); p = fun; (p) (5); printf ("\n");}运行结果:这是fun函数,带入的参数是:5返回7.8.2 用函数指针变量调用函数 函数指针的用法类似于变量指针的用法。注意到程序运行的时候是将程序装入内存,则函数是存在内存入口地址的,因此若将函数名赋给指针变量 ,在类型匹配的前提下,通过相应的指针变量访问内存是完全可行的。并且可以通过指针的变化来调用不同的函数。例7.31 使用函数指针实现 对不同函数的访问。 #include int sub (int x,int y){ return x-y;}in t add (int x,int y){ return x+y;} main (){ int (p) (int a,int b) ; int a,b,result; printf("\n请输入两个整数(两个整数之间用一个空格隔开):"); scanf("%d %d",&a,&b); if (a>b) { p = sub; //语句1 result = (p)(a,b); } el se { p = add; //语句2 result = (p)(a,b); } printf ("\n最终的结果为:%d ",result); printf ("\n");}运行结果1:请输入两个整数(两个整数之间用一个空格隔开):1 3最终的结果为: 4运行结果2:请输入两个整数(两个整数之间用一个空格隔开):3 1最终的结果为:2 分析:程序功能:如果输入的整数a大于b则调用减 法函数,否则调用加法函数。上述代码仅仅是作为一个调用函数指针的实例,通常情况下并不会这样使用。在实际的使用当中会使用到的方式就是将 函数指针作为函数的参数,只有这样才能真正的做到“按需”灵活调用必须的函数。注意到,例子7.31的缺点在于语句1和语句2,我们使用了 两次赋值,这完全等于使用原来的函数,函数指针基本上成了个临时变量。所以下面我们介绍使用的方法指向函数的指针作为函数的参数。 返回7 .8.3 用指向函数的指针作函数参数 使用当中,通常会用指向函数的指针作函数参数。这样做是希望根据应用来调用所需要的函数,那么只要 将所调用的函数作为另外一个函数的参数就可以解决问题。完全可以根据实际的需求的灵活调用。这里重写例子7.31如下:例7.32 将函数 指针作为参数使用重写例7.31#include int sub (int x,int y){ return x- y;}int add (int x,int y){ return x+y;} //定义一个处理器函数,其第三个参数定义为函数指针i nt proccessor(int x,int y,int (p) (int a,int b)){ return (p)(x ,y);}main (){ int a,b; printf("\n请输入两个整数(两个整数之间用一个空格隔开):"); scan f("%d%d",&a,&b); printf("\n最终的结果为:%d",a>b?proccessor(a,b,sub):pr occessor(a,b,add)); printf ("\n");}运行结果1:请输入两个整数(两个整数之间用一个空格隔开):1 3最终的结果为:4运行结果2:请输入两个整数(两个整数之间用一个空格隔开):3 1最终的结果为:2分析:很明显,这个代码就大大简 化了,完全实现了例7.31的效果,而且代码非常简洁。那么这里对于函数指针作为函数参数的用法仅仅作一般性介绍。 7.8.3 用指向函 数的指针作函数参数例7.33 请将以下程序中的函数声明语句补充完整。(2009年3月全国计算机等级考试二级C语言填空题第12题)# include int _____;main(){int x, y, (p)(); scanf("%d%d", &x, &y); p=max; printf("%d\n", (p)(x,y)); }int max(int a, int b){return(a>b?a:b); }答案:max(int a, int b)分析:从指针p的调用方式(p)(x,y)与变量 x,y为整型知空白处应当填写一个函数的定义且函数有两个整型参数,再由printf("%d\n", (p)(x,y));的格式字符 串%d知且该函数应有一个整数返回值,再由p=max语句知该函数名为max。综合分析与下面max的函数定义即不难得出答案为:max( int a, int b)。那么,假设给出的输入为: 1 3时,输出为:3。 7.9 返回指针值的函数 函数可以返回任意类型的值 ,那么其中一个特殊的值就是函数可以返回指针值。函数可以返回的那个指针值其实就是函数返回一个地址。因此返回指针值的函数就是返回地址的 函数。大家心里必须充分清楚一点,只需要知道返回的值是什么地址即可,无需知道具体地址值的数值是多少。那么返回是地址的函数做什么用途呢 ?我们可以直接理解为:指针函数就是一个函数!该函数的功能可以返回某个地址。指针函数的说明形式:类型说明 标识符 (参数); 7. 9 返回指针值的函数例7.34 指针函数及其用法#include int p (int a,int b){ r eturn a>b?&a:&b;}main (){ int a,b; printf ("\n请输入两个整数:"); scanf(" %d%d",&a,&b); printf (“\n输入的两个数中较大的那个是:%d",(p(a,b))); printf ("\ n");}运行结果:请输入两个整数:3 5输入的两个数中较大的那个是:5 例7.35 打印每个字符串中从小写字母’s’开头到字符串 尾的所有字符#include char find_s (char p){ char s=p; while (s!=''\0'' && s!=''s'')s++; return s;}main (){ char s[3][20]={"sfirst","cdegsecond","no"}; int i; for (i=0;i<3;i++) printf ("%s\n",find_s(s[i]));}运行结果:sfirst second 7.10 指针数组和二级指针 7.10.1 指针数组的定义和使用7.10.2 指向指针数据的指针 7.10.1 指针数组的定义和使用指针数组:数组的每个元素都是指针。指针数组的定义如下:类型名 数组名 [数组长度];例如可以定义一个整型指针数组:int s[10]。该数组解释为:s是一个具有10个元素的指针数组,每个元素可以存储一个指向整型变量的指针(如果被赋值的话就可能是指向某个整型变量的指针)。例7.36 有以下程序(2010年3月全国计算机等级考试二级C语言填空题第10题)#include main(){ int a[]={1,2,3,4,5,6},k[3],i=0; while(i<3) { k[i]=&a[2i]; printf("%d",k[i]); i++; } printf ("\n");}程序运行后的输出结果是:________答案:135分析:例7.36语句int a[]={1,2,3,4,5,6},k[3],i=0; 中声明了一个指针数组k,其说明方式为:int k[3],解释为:k是一个具有三个整型指针元素的指针数组。在while循环内部,将数组a下标为2i的元素所在的地址赋值给指针数组k的第i个元素。然后在语句4使用打印语句访问该指针数组。实际上就是通过指针数组间接地访问了数组a。 返回7.10.2 指向指针数据的指针指针还有一个使用方式就是多级指针。典型的多级指针就是二级指针。多级指针就是指向指针的指针。所谓“指向指针的指针”实际上就是:存储指针变量地址的变量(只不过这个变量也是一个指针变量而已)。下面来看二级指针的说明:类型声明 指针名 ;比如定义一个整型的二级指针:int p;解释为:p是一个二级指针,其第一级指针p指向某个整数变量的地址。而p则表达了该整型变量。当然,这必须是p在被赋值的情况下。来看一个例子:例7.37 二级指针的声明与访问#include main(){ int pc,p; int i; i = 5; pc = &i; p = &pc; printf("\n变量i的值为:%d",i); pc = 6; printf("\n经过一级指针pc的操作后,变量i的值为:%d",i); p = 7; printf("\n经过二级指针p的操作后,变量i的值为:%d",i); printf ("\n");}运行结果:变量i的值为:5经过一级指针pc的操作后,变量i的值为:6经过二级指针p的操作后,变量i的值为:7 |
|