1、第第6章章 指针指针 6.1 指针和指针变量 6.2 指针变量与数组 6.3 指针变量与字符串及多级指针变量 6.4 指针变量与函数*6.5 动态数组6.6 典型例题精讲 6.1 指针和指针变量指针和指针变量 6.1.1 地址和指针的概念地址和指针的概念在计算机中,内存是一个连续的存储空间;在这个空间中每一个内存单元都对应一个唯一的内存地址;并且,内存的编址由小到大是连续的,它的基本单位是“字节”。对程序中定义的变量,编译过程中系统根据该变量定义时获得的类型信息,为其分配相应长度的连续内存单元用来存放它的值。例如在C语言中,一个整型变量占4个字节的内存单元,而一个双精度型变量占8个字节的内存单
2、元。并且,给每个变量分配的这连续几个内存单元的起始地址就是该变量的地址;也即,编译后每一个变量都有一个确定的变量地址,对变量的访问就是通过这个变量地址进行的。当引用一个变量时,实际上就是从该变量地址开始的若干连续单元中取出数据;当给一个变量赋值时,则是将这个值按该变量的类型存入该变量地址开始的若干连续内存单元中。显然,变量地址所对应的内存单元中存放的内容即为该变量的值。我们可以通过地址运算符“&”得到变量地址。例如:int a=10;则&a表示变量a在内存中的地址(变量地址),通过下面的printf语句就可以输出a的变量地址:printf(%xn,&a);/*输出变量a的16进制地址*/通常把
3、变量地址形象地称之为“指针”,意思是通过这个“指针(即地址)”可以找到该变量。在C语言中,允许使用一种特殊的变量来专门存放某个变量的地址(即该特殊变量的内存单元中存放的是某个变量的地址而不是其他数据,这一点与普通变量不同),这种特殊变量就称为指针变量。注意,指针是一个地址,且主要是指变量或数组元素的地址,它是一个常量;而指针变量本身是一个变量并且是一个存放地址的变量,主要用来存放其他变量或数组元素的地址;也即,指针变量的值即为指针。在后续章节中我们还可以看到:指针变量的值不仅可以是像int、float等简单变量的地址,也可以是数组、结构体等构造类型的变量地址,即用指针变量来指向某种构造类型的变
4、量,这样就可以访问到该类型变量中的任一元素(成员)。这是引入指针变量的一个重要原因。引入指针变量的另一原因是:C语言允许在程序的执行过程中生成新的变量,由于这种变量是在程序执行过程中动态产生的,故无法事先在程序或函数说明部分对其进行定义。因此,这种动态生成的变量没有名字,所以只能通过指针变量去间接地访问它(即由指针变量所存放的该动态变量的地址去访问这个动态变量)。有了指针变量,访问变量的方式也得到了扩充:一种是我们前面介绍过的按变量名直接存取变量值的访问,称为直接访问;另一种就是本章介绍的通过指针变量所存放的变量地址找到该变量后再对该变量值进行的存取访问,称为间接访问。在第9章还可以看到,我们
5、可以把函数的首地址(该函数所对应的程序代码段首地址)赋给一个指针变量(此时,函数名可以看做为是一个变量),使该指针变量指向这个函数,然后通过指针变量就可以找到并执行这个函数;这和上面通过指针变量来存取某个变量值的概念是完全不同的。6.1.2 指针变量的定义和初始化指针变量的定义和初始化1.指针变量的定义指针变量是用来存放其他变量地址的变量,所以和普通变量一样必须先定义、后使用。指针变量定义的一般形式如下:类型标识符*变量名;注意:(1)在指针变量的定义中,变量名前的“*”号仅是一个符号,它表示该变量名为一个指针变量而不是指针运算符;如果定义时变量名前无“*”则为一普通变量而不是指针变量。(2)
6、类型标识符表示该指针变量所指向那个变量的数据类型;也即,一旦定义了一个指针变量,则它只能指向由类型标识符所规定的这种类型变量,而不允许指向其他类型的变量。注意,类型标识符并不是指针变量自身的数据类型,因为所有的指针变量都是用来存放地址值的,其数据类型必然为整型,故无需再进行说明。例如:int*p1,*p2;char*q;则指针变量p1、p2只能指向整型变量,而指针变量q则只能指向字符型变量。注意,指针变量p1、p2不能如下定义:int*p1,p2;这种定义方式则定义了p1为指针变量,而p2为整型变量;也即在定义中以“*”开头的变量是指针变量,否则不是指针变量。2.指针变量的初始化指针变量的初始
7、化有两种方法:一种是先定义再赋初值,另一种是在定义的同时赋初值。需要注意以下两点:(1)不要引用未经赋值的指针变量,未经赋值的指针变量是不能使用的,否则将造成系统混乱。(2)给指针变量赋值只能赋给地址值,而不能是其他任何类型的数据,否则会引起错误。下面我们通过例子来看一下两种初始化的方法:int a;int*p1;p1=&a;这是先定义再赋初值的方法。即先定义一个整型变量a和一个指向整型变量的指针变量p1,然后再把a的内存地址赋给p1,此时指针变量p1就指向了整型变量a。由于C语言中提供了地址运算符“&”来表示变量的地址,因此我们可以通过变量名a前面加上地址运算符“&”来得到变量a的地址。ch
8、ar b;char*p2=&b;这是在定义的同时赋初值的方法。无论哪一种方法,要将一个变量的地址赋给一个指针变量,则这个变量必须先定义,然后才能将其地址赋给指针变量。下面几种初始化都是错误的:int a;float*p1=&a;float*p2=&c;int*p3=100;赋给指针变量的值其类型必须和指针变量定义时的指向类型一致。由于指针变量p1只能指向float类型的变量,而a是一个整型变量,因此出错;指针变量p2出错的原因是赋给它的是一个没有定义的变量c地址值,既然没有定义,也就不存在该变量的内存地址;指针变量p3的错误是接受了一个非法地址值,变量的地址是在该变量定义时由系统分配确定的,它
9、只能通过“&”运算符来获得该变量的地址,而不能随便将一个地址之外的值赋给指针变量,这样会引起严重的后果。6.1.3 指针变量的引用和运算指针变量的引用和运算1.指针变量的引用引用指针变量的值(即所指向的变量地址)与引用其他类型的变量一样,直接用指针变量的变量名即可(变量名前不加“*”)。但是,我们主要关心的是指针变量所指向的那个变量的值,C语言采用指针变量的变量名前面加上“*”来表示该指针变量所指向的那个变量的值。例如:int a=10;int*p=&a;指针变量p与它所指的变量a之间的关系示意如图6-1所示。图6-1 指针变量p与它所指变量a之间关系示意由图6-1可知,指针变量p的值是它所指
10、向变量a的地址(p等于&a),即通过p可找到变量a。如果要得到变量a中的数据10时,则可用“*p”实现,也可用变量名a实现;也即,此时变量a有了一个别名“*p”(*p等于a)。如果我们采用下面的printf语句输出a值:printf(%d,a);或printf(%d,*p);则将得到同一个结果:10。由此可知,引入了指针变量后,对于变量的访问,除了原有可供访问的变量名外,又多了一个可由指针变量间接访问的“别名”。注意,指针变量定义时出现的“*”和指针变量引用中出现的“*”其含义是不同的。在指针变量定义中出现的“*”应理解为指针类型的定义,即表示“*”后的变量是一个指针变量;而引用中,在指针变量
11、名前出现的“*”则为取值运算符,即通过“*”来对指针变量进行“间接访问”,也就是访问指针变量所指向的那个变量的值。在C语言中有两个与指针有关的运算符,一个是“&”,即取其右边变量的地址,如“&a”表示取变量a的地址;另一个就是“*”,即访问其右边指针变量所指向的变量(值),如上面的“*p”就代表着p所指向的变量a。“&”和“*”都是单目运算符,它们的优先级相同,并按从右向左的方向结合。例如:int a;int*p=&a;则&*p&a p即“&*p”等价于“p”,并且“*&a”等价于a(同样也有:*&a*p a)。例6.1 输入a和b两个整数,按由大到小的顺序输出a和b的值。#includevo
12、id main()int a,b,*p,*p1,*p2;printf(Input two data:);scanf(%d%d,&a,&b);p1=&a;p2=&b;if(a、=、=、=和!=这6种关系的比较运算。例6.2 求出下面程序的运行结果。#includevoid main()int i=10,j=20,k=30;int*a=&i,*b=&j,*c=&k;*a=*c;*c=*a;if(a=c)a=b;printf(a=%d,b=%d,c=%dn,*a,*b,*c);解 需要注意的是,a表示它所指向的那个变量的地址,而*a(引用时)则表示它所指向的那个变量的值。我们可以从程序中看出:*a等
13、于*c,其值为30;而*b不变,其值为20。条件语句中的表达式“a=c”判断的是两个地址值,而a为i的地址、c为k的地址,这两个地址值必然不等,因此赋值语句“a=b;”没有执行。由此得到输出结果如下:a=30,b=20,c=30我们也可以用动态图的方法进行分析。由于a、b、c为指针变量,它们都是指向其他变量的,因此我们画一个由指针变量到它指向的那个变量的箭头,此后,凡是遇到*a这种形式,都是从a开始依据箭头找到所指的那个变量,然后对那个变量进行操作。本题程序执行的动态图见图6-4,由图6-4可以很容易的得到运行结果为:a=30,b=20,c=30图6-4 程序执行的动态图6.2 指针变量与数组
14、指针变量与数组 6.2.1 指针变量与一维数组指针变量与一维数组一个数组在内存中的存储是由一段连续的内存单元组成的,数组名就是这段连续内存单元的首地址。而对数组元素的访问就是通过数组名(即数组的起始位置)加上相对于起始位置的位移量(即下标)来得到要访问的数组元素内存地址,然后对该地址中的内容(即数组元素的值)进行访问。在第4章我们已经知道,数组名代表该数组首元素的地址。因此,数组名与我们这里介绍的指针概念相同。实际上,C语言就是将数组名规定为指针类型的符号常量,即数组名是指向该数组首元素的指针常量(地址常量),其值不能改变(即始终指向数组的首元素)。C语言对数组的处理,也是转换成指针运算完成的
15、。例如:int a10,*p;则下面的两个语句等价:p=&a0;p=a;其作用都是使指针变量p指向a数组的a0元素(即a数组的首元素),见图6-5,也即指针变量的指针值是数组元素的地址,此时有p等于&a0和*p等于a0。图6-5 执行“p=a;”后的内存示意我们知道,ai代表a数组中的第i+1个元素(因为由下标0开始)。因此,从图6-5可知,pi与ai相同,也代表着a数组的第i+1个元素。那么a+1又代表着什么呢?由6.1.3节指针变量的引用和运算可知,这个“1”表示一个数组元素单位;也即,a+1代表第二个元素a1的地址;同样,p+1也代表着a1的地址。因此,a+i和p+i都代表着第i+1个元
16、素ai的地址&ai。此外,由“*”运算符可知:*(p+i)和*(a+i)则代表着数组元素ai。因此,引用一个数组元素就可以采用以下两种方式:(1)下标法:采用ai或pi的形式访问a数组的第i+1个元素;(2)指针法:采用*(a+i)或*(p+i)的形式访问a数组的第i+1个元素。例6.3 给数组输入10个整型数,然后输出显示。解 实现方法如下:(1)下标法。(2)指针法。#includevoid main()int*p,a10;for(p=a;pa+10;p+)scanf(%d,p);for(p=a;pa+10;p+)printf(%4d,*p);(3)指针地址位移法。注意,在第二种方法(指针
17、法)中,循环控制表达式中的“p+”使得指针变量p的指针值能够逐个元素的移动,从而实现对每一个数组元素的访问;在输出过程中,当输出完最后一个数组元素a9的值时,此时p的指针值已移至a9元素之后的位置(即a数组范围之外)。由于数组名a是指向该数组首元素的指针常量,它不能实现移动,故指针法只有一种方法。在数组中采用指针方法应注意以下几点:(1)用指针变量访问数组元素时,要随时检查指针变量值的变化不得超出数组范围。(2)指针变量的值可以改变,但数组名的值不能改变,如例6.3中,p+正确而a+错误。(3)对于*p+,其结合方向为自右向左,因此等价于*(p+)。(4)(*p)+表示p所指向的数组元素的值加
18、1,而不是指向其后的下一个数组元素。(5)如果当前的p指向a数组中的第i+1个元素,则:*(p+)等价于 ai+*(p-)等价于 ai-*(+p)等价于 a+i*(-p)等价于 a-i注意:*(p+)与*(+p)作用不同。若p的初值为&a0,则*(p+)等价于a0,而*(+p)等价于a1。(6)区分下面的指针含义:+*p相当于+(*p):即先给p指向的数组元素的值加1,然后再取这个数组元素的值。(*p)+则是先取p所指数组元素的值,然后给该数组元素的值加1。*p+相当于*(p+),即先取p所指数组元素的值,然后p加1使p指向其后的下一个数组元素。*+p相当于*(+p),先使p指向其后的下一个数
19、组元素,然后再取p所指向的数组元素的值。例6.4 给出下面程序的运行结果。#includevoid main()int a10=10,9,8,7,6,5,4,3,2,1,*p=a+4;printf(%dn,*+p);解 程序执行示意如图6-6所示。首先将p定位于(指向)数组元素a4,在输出时先执行+p(即p已指向a5元素)然后再取该元素的值,故输出结果为5。图6-6 程序执行中p指针变化示意例6.5 给出下面程序的运行结果。#includevoid main()int a=9,8,7,6,5,4,3,2,1,0;int*p=a;printf(%dn,*p+7);解 由程序可知,p已指向a0,而
20、“*p+7”则是先取出p所指向数组元素a0的值,然后再加7,而a0的原值为9,故输出结果为16(注意,*p+7不等于*(p+7),*(p+7)表示数组元素a7的值)。例6.6 给出下面程序的运行结果。#includevoid main()int i,a10=10,20,30,40,50,60,70,80,90,100,*p;p=a;for(i=0;i10;i+)printf(%4d,*p+);printf(n);p=a;for(i=0;i10;i+)printf(%4d,(*p)+);printf(n);解“*p+”表示先输出p所指向元素的值,然后p加1指向其后的下一个元素。“(*p)+”则是
21、先输出p所指向的数组元素的值,然后再给这个数组元素值加1,而p的指针值不变(即指向不变)。因此,我们得到输出结果如下:10 20 30 40 50 60 70 80 90 10010 11 12 13 14 15 16 17 18 19 6.2.2 指针变量与二维数组指针变量与二维数组1.二维数组的元素地址及元素表示方法由于二维数组是多维数组中比较容易理解的一种,并且它可以代表多维数组处理的一般方法,所以这里主要介绍指针变量和二维数组的关系。为了说明问题,我们定义一个二维数组如下:int a34=1,2,3,4,5,6,7,8,9,10,11,12;由第四章可知,数组名a是二维数组a的起始地址
22、,也就是数组元素a00的地址,而a0、a1、a2则分别代表数组a各行的起始地址(见图6-7)。图6-7 二维数组地址示意由6.2.1节可知,a+i在一维数组a中表示从数组a首地址开始向后位移i个元素位置,即为一维数组a中第i+1个元素的地址。在二维数组中,a+i仍然表示一个地址,但i值不再像一维数组那样以元素为单位而是以行为单位了,即将整行看做为一维数组中的一个元素。这样,a+i就代表二维数组a的第i+1行的首地址。因此,在二维数组中,a+i与ai等价(见图6-7)。我们知道,在一维数组中ai与*(a+i)等价,它们都代表着一维数组a中的第i+1个元素。而在二维数组中,ai不再是数组元素而表示
23、一个地址。因此,在二维数组中与ai等价的*(a+i)也表示一个地址,即它与ai都代表着二维数组中第i+1行的首地址(且*(a+i)本身也无法表示二维数组某行某列的数组元素)。因此在二维数组a中,数组元素aij的地址可采用下列形式表示。(1)&aij /*行下标和列下标表示法*/(2)ai+j /*行下标加列位移表示法*/(3)*(a+i)+j /*行位移加列位移表示法*/在此,我们一定要注意:在一维数组中ai和*(a+i)均代表一个数组元素,而在二维数组中它们却代表着一个地址。此外,&ai也表示二维数组a第i行的首地址,这样二维数组地址就有&ai、ai与*(a+i)这三者等价。对于二维数组a,
24、我们知道a是指向数组a的开始位置,而*a(即*(a+0)则是指向数组a第0行开始位置(即a0);而*a才代表着数组a第0行第0列的数组元素a00。相应地,数组元素ai0也可用*(a+i)表示;也即,如果要用行位移加列位移表示法来表示一个二维数组元素,则必须经过两次“*”运算才能实现。二维数组中的数组元素aij也有如下的三种表示方法:(1)aij /*行下标和列下标表示法*/(2)*(ai+j)/*行下标加列位移表示法*/(3)*(*(a+i)+j)/*行位移加列位移表示法*/一维数组元素和二维数组元素表示的区别(在不含“&”的情况下)是:一维数组元素仅有一个“*”或一个“”,如ai、*(a+i
25、)和*a(即a0);二维数组元素则是“*”和“”之和的个数必须是2,如aij、*(ai+j)、*(*(a+i)+j)和*a(即a00)。例6.7 用不同方法实现对二维数组的输入和输出。解 实现方法如下:(1)下标法#includevoid main()int a34,i,j;for(i=0;i3;i+)for(j=0;j4;j+)scanf(%d,&aij);for(i=0;i3;i+)for(j=0;j4;j+)printf(%4d,aij);printf(n);(2)行下标加列位移法#includevoid main()int a34,i,j;for(i=0;i3;i+)for(j=0;j
26、4;j+)scanf(%d,ai+j);for(i=0;i3;i+)for(j=0;j4;j+)printf(%4d,*(ai+j);printf(n);(3)行位移加列位移法#includevoid main()int a34,i,j;for(i=0;i3;i+)for(j=0;j4;j+)scanf(%d,*(a+i)+j);for(i=0;i3;i+)for(j=0;j4;j+)printf(%4d,*(*(a+i)+j);printf(n);2.指向二维数组的指针变量由指针变量和一维数组可知:一个普通的指针变量可以指向一维数组,但却不能指向二维数组。例如:char str10=Holl
27、ow!,OK!,*p;则语句:p=str;的写法是错误的,即必须使指针变量p指向二维数组str中的某一行(该行的所有数组元素组成了一个一维数组)才是正确的。例如:p=str0;就是一个正确的语句。C语言也提供了指向二维数组指针变量,其定义的一般形式为:类型标识符(*变量名)列数;其中,类型标识符为所指向的二维数组的数据类型;“*”表示其后的变量为指针类型,而方括号中的“列数”是指所指向的二维数组所具有的列数。应注意“(*变量名)”两边的圆括号不能少,有括号时则“*”先与变量名结合,即表示定义了一个指针变量;如缺少括号则表示是一个指针数组(将在6.2.3节介绍),其含义就完全不同了。我们通过下面
28、的例子来说明二维数组指针变量的移动:int a46;int(*p)6;p=a;p+;二维数组指针变量p执行“p+;”语句,则根据定义时数组的列数值6将p的指针值由二维数组a的开始处(即a0处)顺序移动了个6个元素,即到达下一行的开始处(即a1处);也即,p的指针值每加一个1,则它相应的下移一行。实际上,它是将二维数组a看做为4个一维数组a0、a1、a2和a3,而p的指针值只能在a0a3之间移动。因此,二维数组指针变量p只能定位于每行的开始处,而不能定位于二维数组中的任意一个数组元素。例6.8 给出下面程序的输出结果。#includevoid main()int a4=1,2,3,4,5,6,7
29、;int(*p)4;p=a;printf(%d,%dn,*p0,*p1+2);解 本题采用二维数组指针方法来输出结果,并且采用的是下标法。因为p0是a00元素的地址。故*p0的值为1,而p1是a10元素的地址,*p1的值为4,再加上2后为6,故输出为:1,6。注意,此题也可用行位移的方法表示,即*p0可用*p表示,而*p1+2可用*(p+1)+2表示。例6.9 用二维数组指针方法实现例6.7的输入和输出。解 实现方法如下:(1)指针法(用指针指向数组元素)#includevoid main()int a34;int*q,(*p)4;for(p=a;pa+3;p+)for(q=*p;q*p+4;
30、q+)/*在此,*p*p+3为该行中的每一个元素地址*/scanf(%d,q);for(p=a;pa+3;p+)for(q=*p;q*p+4;q+)printf(%4d,*q);printf(n);(2)行指针加列位移法#includevoid main()int a34,j;int(*p)4;for(p=a;pa+3;p+)for(j=0;j4;j+)scanf(%d,*p+j);for(p=a;pa+3;p+)for(j=0;j4;j+)printf(%4d,*(*p+j);printf(n);注意,在指针法中,内层for循环的条件表达式q=*p和q*p+4不能写成q=p和qp+4;因为p
31、是指向二维数组的指针变量,它只能指向二维数组名或二维数组每行的开始处;而q只是一个普通指针变量,它既可以指向一个变量,也可以指向一个一维数组名或一个数组元素的地址。也即,指向一维数组的指针变量q和指向二维数组的指针变量p所处的“层次”不同,所以q和p之间是不能赋值和比较大小的。*p则表示二维数组a中某行的首地址,且二维数组中的每一行构成了一个一维数组,即*p是指向一维数组的指针变量(虽然p和*p都是指向二维数组同一地址,但它们层次不同),因此q和*p之间可以赋值和比较大小的,q=*p和q*p+4是正确的。此外,*p+j中的*p表示二维数组a中某行的首地址,再加上j(即位移j)则指向该行列下标为
32、j的元素,即*p+j可以指向二维数组a中任一数组元素。最后需要指出的是:二维数组名a除了是一个地址常量外,其余都与指向二维数组的指针变量p相同,即“p=a;”或将*a赋给普通指针变量q的“q=*a;”都是正确的。归纳起来,在二维数组中,普通指针变量q只能指向二维数组中的一维数组名或数组元素;二维数组指针变量p只能指向二维数组名或二维数组各行的首地址,而*p功能则与q相同。例如:int a34=1,2,3,4,5,6,7,8,9,10,11,12;int*q,(*p)4;p=a;/*p指向二维数组名a*/p=a+1;/*p指向二维数组a第1行首地址*/p=&a0;/*p指向二维数组a第0行首地址
33、*/p=a0;/*出错,p不能指向a0,a0为二维数组a第0行的一维数组名*/p=&a00;/*出错,p不能指向数组元素*/q=*a;或q=a0;/*q指向二维数组a第0行的一维数组名*a或a0*/q=*(a+1)+2;或q=a1+2;/*q指向a+1行第2列数组元素,即a12*/q=&a00;/*q指向数组元素a00*/q=a;/*出错,q不能指向二维数组名a*/q=a+1;/*出错,q不能指向二维数组名a表示的各行首地址*/此外,由指向二维数组的指针变量也可以引申出指向三维数组的指针变量或指向多维数组的指针变量。例如:int a332,(*p)32=a;则指向三维数组的指针变量p就可以指向
34、三维数组名a或三维数组中各二维数组的开始处。6.2.3 指针数组指针数组一个数组中的每个元素都是指针类型时,就称该数组为指针数组。根据数组的定义,指针数组中每个元素都必须为指向同一数据类型的指针型元素。指针数组定义的一般形式为:类型标识符*数组名整型常量表达式;在定义中,由于“”比“*”的优先级高,故数组名与方括号中的整型常量表达式结合形成了一个长度确定的数组定义,而数组名前面的“*”则表示该数组中的每一个元素都是指针类型;类型标识符则说明每一个指针型元素所指向的变量类型,即这些指针型元素只能指向同一类型的变量地址。例如:int*a10;char*b6;它们都是指针数组。要注意指针数组与二维数
35、组的指针变量之间的区别,不要将“int*a10;”,与“int(*a)10;”混淆。又如:char c38=BASIC,FORTRAN,PASCAL;char*p3=c0,c1,c2;指针数组p的指向关系如图6-8所示。图6-8 指针数组p指向示意指针数组和一般数组一样,也允许指针数组在定义时初始化;但由于指针数组的每个元素都是指针类型,因此它只能存放地址。对指向字符串的指针数组在定义时赋初值,就是把存放字符串的首地址赋给指针数组中对应的元素。例如char*a3=BASIC,FORTRAN,PASCAL;上述语句定义了一个指针数组a,它的3个元素a0、a1和a2中分别存放了“BASIC”、“F
36、ORTRAN”和“PASCAL”这三个字符串的起始地址。例6.10 给出下面程序的输出结果。#includevoid main()int a34=0,1,2,3,4,5,6,7,8,9,10,11;int*p3,i;for(i=0;i3;i+)pi=&ai0;printf(%d%dn,*(*(p+2)+1),*(*(p+1)+2);解*(p+2)的值为地址&a20,而*(p+2)+1则为地址&a21,即*(*(p+2)+1)为元素a21,同理*(*(p+1)+2)为元素a12。因此,输出结果为96。例6.11 已知一个不透明的布袋里装有红、蓝、黄、绿、紫同样大小的圆球各一个,现从中一次抓出两个
37、,问可能抓到的两个球的颜色组合是什么?请用指针数组求解。解 由于先抓到红球后再抓到黄球与先抓到黄球后再抓到红球的效果是一样的,所以只出现一种即可。因此只需用外循环从15来表示抓到的红、蓝、黄、绿、紫色的第一个球,用内循环从15来表示抓到的红、蓝、黄、绿、紫色的第二个球。由于每次抓到的球不允许同色(与题意矛盾),则只要判断两重循环不能取同一个值即可。程序设计如下:#includevoid main()char*color5=red,blue,yellow,green,purple,*p=color;int s=0,i,j;for(i=0;i=3;i+)for(j=i+1;j=4;j+)if(i=
38、j)continue;s+;printf(%3d,s);printf(%10s%10sn,*(p+i),*(p+j);运行结果:1 red blue 2 red yellow 3 red green 4 red purple 5 blue yellow 6 blue green 7 blue purple 8 yellow green 9 yellow purple 10 green purple在程序中,我们使用了一个指向指针变量的指针变量p,关于它的使用方法,我们在下一节予以介绍。6.3 指针变量与字符串及多级指针变量指针变量与字符串及多级指针变量 6.3.1 指针变量与字符串字符串的指针
39、就是字符串的起始地址,当把这个地址赋给一个字符型指针变量时,就会很便捷的实现对字符串的处理。因此,可以先定义一个字符型指针变量并使该指针变量指向字符串的起始位置,此后就可以使用这个指针变量进行字符串的操作了。指向字符串的指针变量定义的一般形式为:char*变量名;例如:char*p;C语言中可以用两种方法对一个字符串进行操作:(1)把字符串保存在一个字符数组中。例如:char str=Program;这时,可以通过数组名或数组元素来访问该字符数组。(2)用指向字符串的指针变量来指向字符串。例如:char*s=Program;这时,可以通过指向字符串的指针变量来访问字符串的存储区。这两种方法内存
40、存储示意见图6-9。void swap2(int*c1,int*c2);void main()int a2=3,5,b2=3,5;swap1(a,a+1);swap2(&b0,&b1);printf(%d%d%d%dn,a0,a1,b0,b1);void swap1(int c1,int c2)int t;t=c10;c10=c20;c20=t;void swap2(int*c1,int*c2)int t;t=*c1;*c1=*c2;*c2=t;15.阅读程序,给出程序的运行结果。#includeint a=2;int f(int*a);void main()int s=0;int a=5;s
41、=s+f(&a);s=s+f(&a);printf(%dn,s);int f(int*a)return(*a)+;16.阅读程序,给出程序的运行结果。#includevoid sum(int*a);void main()int x10=1,2,3,4,5,6,7,8,9,10,i;for(i=2;i=0;i-)sum(&xi);printf(%dn,x0);void sum(int*a)a0=a1;17.阅读程序,给出程序的运行结果。#includevoid fun(int*a,int i,int j);void main()int x=2,6,1,8,i;fun(x,0,3);for(i=0
42、;i4;i+)printf(%2d,xi);printf(n);void fun(int*a,int i,int j)int t;if(ij)t=ai;ai=aj;aj=t;i+;j-;fun(a,i,j);18.阅读程序,给出程序的运行结果。#includevoid sort(int a,int n);void main()int x10=1,2,3,4,5,6,7,8,9,10,i;sort(x,10);for(i=0;i10;i+)printf(%d,xi);printf(n);void sort(int a,int n)int i,j,t;for(i=0;in-1;i=i+2)for(
43、j=i+2;jn;j=j+2)if(aiaj)t=ai;ai=aj;aj=t;19.阅读程序,给出程序的运行结果。#includevoid main()char a45=1234,abcd,xyz,ijkm;int i=3;char(*p)5=a;for(p=a;pa+4;p+,i-)printf(%c,*(*p+i);printf(n);20.阅读程序,若运行时输入:1 2 3,给出程序的运行结果。#includevoid main()int a32=0,(*p)2,i,j;for(i=0;i2;i+)p=a+i;scanf(%d,p);p+;for(i=0;i3;i+)for(j=0;j2
44、;j+)printf(%2d,aij);printf(n);21.下面程序中,函数fun的功能是求3行4列的二维数组每行元素的最大值,并将找到的每行元素最大值依次放入b数组中。请填空。#includevoid fun(int m,int n,int a4,int*b);void main()int a34=1,2,3,4,6,3,5,8,9,10,1,5;int i,b3;fun(3,4,a,b);for(i=0;i3;i+)printf(%4d,bi);printf(n);void fun(int m,int n,int a4,int*b)int i,j,x;for(i=0;im;i+)x=
45、ai0;for(j=0;jn;j+)if(xaij)x=aij;_=x;图6-9 用字符数组和字符指针方式的存储示意 由图6-9可知,对字符型指针变量s来说,虽然没有定义字符型数组,但定义s并指向字符串初值“Program”时,系统在内存中将这个字符串“Program”是以字符数组的形式存放的。也即,对于上述两种方法的定义来说:printf(%s,str);等效于 printf(%s,s);printf(%c,str3);等效于 printf(%c,s3);但是,字符串指针与字符数组还是有如下区别:(1)s是指针变量可以多次赋值,而str是字符型数组,str代表的数组名为一个地址常量,不能给它
46、赋值。例如:char*s;s=Language;是正确的,而:char str10;str=Language;则是错误的。(2)数组str的元素可以重新赋值,而指针变量s所指向的字符串是一个字符串常量,即该字符串中的字符是不能修改的。例如:str4=g;是正确的,而:s4=g;则是错误的。例6.12 指出下面程序中的错误并将其改为正确的程序。#include#includevoid main()char*s1=12345,*s2=abcd;printf(%sn,strcpy(s1,s2);解 由于字符型指针变量所指向的字符串是不能修改的,因此也就无法完成将一个指针变量所指向的字符串拷贝到另一个
47、指针变量所指向的字符串中(可以实现将一个指针变量所指向的字符串拷贝到一个字符数组中)。因此,程序修改如下:#include#includevoid main()char s110=12345,*s2=abcd;printf(%sn,strcpy(s1,s2);例6.13 给出下面程序的运行结果。#includevoid main()char*p=I love our country.;while(*p!=0)printf(%c,*p);p+;printf(%sn,p);解 程序设置了一个字符型指针变量p来指向字符串“I love our country.”,然后通过p指针值逐个字符的移动来输出
48、字符串“I love our country.”,直到遇到字符串结束标识符0为止。注意,此时p已经指向这个字符串结束标识符0,因此再执行语句“printf(%sn,p);”时,由于p指向0,而原字符串已经丢失,所以输出的是一个空串(什么也没有)。然后输出一个回车换行符n。因此,最终的输出结果为:I love our country.。6.3.2 多级指针变量多级指针变量 指针变量可以指向普通变量,还可以指向另外的指针变量。如果一个指针变量存放的是另一个指针变量的地址,则称这个指针变量为指向指针变量的指针变量,也称多级指针变量。通过指针变量来访问其他变量的方式称为间接访问。由于这种情况是由指针变
49、量直接指向其他变量,所以称为“单级间接访问”;如果通过指向指针变量的指针变量来访问其他变量,则构成了“二级间接访问”。指向指针变量的指针变量其定义形式为:类型标识符*变量名;其中,“*”表示其后的变量名是一个指向指针变量的指针变量。例如:int a=10;int*p1=&a,*p=&p1;则指针变量p、p1和变量a的指向关系如图6-10所示。图6-10 指针和变量的指向关系由图6-10可知,指针变量p所指向的变量p1本身又是一个指针变量,而p1又指向了一个整型变量。也即,我们可以通过*p1或*p访问到整型变量a的值10,而*p则是通过第一次间接访问*p得到p1的值&a,然后通过第二次间接访问*
50、p得到a值10。在此,p1的别名是*p,而a的别名则是*p1或*p。指向指针变量的指针变量其使用方法在前面的例6.11中已经出现过。下面,我们再给出一个指向指针变量的指针变量例子。例6.14 通过指向指针的指针变量输出二维字符数组中的字符串。#includevoid main()char*name=BASIC,FORTRAN,PASCAL;char*p;int i;for(i=0;i3;i+)p=name+i;printf(%sn,*p);运行结果:BASICFORTRANPASCAL程序中各指针的指向如图6-11所示,且指针变量p指向的是指针数组name的首地址。图6-11 指向指针的指针变