《C程序设计指针课件》由会员分享,可在线阅读,更多相关《C程序设计指针课件(265页珍藏版)》请在金锄头文库上搜索。
1、第第1010章章 指指 针针10.1地址和指针的概念地址和指针的概念10.2变量的指针和指向变量的指针变量变量的指针和指向变量的指针变量10.3数组的指针和指向数组的指针变量数组的指针和指向数组的指针变量10.4字符串的指针和指向字符串的指针变量字符串的指针和指向字符串的指针变量10.5函数的指针和指向函数的指针变量函数的指针和指向函数的指针变量10.6返回指针值的函数返回指针值的函数10.7指针数组和指向指针的指针指针数组和指向指针的指针10.8有关指针的数据类型和指针运算的小结有关指针的数据类型和指针运算的小结习题习题指针是指针是C语言中的一个重要的概念,也是语言中的一个重要的概念,也是C
2、语言的语言的一个重要特色。正确而灵活地运用它,可以有一个重要特色。正确而灵活地运用它,可以有效地表示复杂的数据结构效地表示复杂的数据结构;能动态分配内存能动态分配内存;能能方便地使用字符串方便地使用字符串;有效而方便地使用数组有效而方便地使用数组;在在调用函数时能得到多于调用函数时能得到多于1个的值个的值;能直接处理内能直接处理内存地址等,这对设计系统软件是很必要的。掌存地址等,这对设计系统软件是很必要的。掌握指针的应用,可以使程序简洁、紧凑、高效。握指针的应用,可以使程序简洁、紧凑、高效。每一个学习和使用每一个学习和使用C语言的人,都应当深入地学语言的人,都应当深入地学习和掌握指针。可以说,
3、不掌握指针就是没有习和掌握指针。可以说,不掌握指针就是没有掌握掌握C的精华。的精华。指针的概念比较复杂,使用也比较灵活,因此初学指针的概念比较复杂,使用也比较灵活,因此初学时常会出错,务请在学习本章内容时十分小心,多时常会出错,务请在学习本章内容时十分小心,多思考、多比较、多上机,在实践中掌握它。我们在思考、多比较、多上机,在实践中掌握它。我们在叙述时也力图用通俗易懂的方法使读者易于理解。叙述时也力图用通俗易懂的方法使读者易于理解。10.1 地址和指针的概念地址和指针的概念为了说清楚什么是指针,为了说清楚什么是指针,必须弄清楚数据在内必须弄清楚数据在内存中是如何存储的,存中是如何存储的,又是如
4、何读取的。又是如何读取的。如果在程序中定义了一如果在程序中定义了一个变量,在编译时就个变量,在编译时就给这个变量分配内存给这个变量分配内存单元。系统根据程序单元。系统根据程序中定义的变量类型,中定义的变量类型,分配一定长度的空间。分配一定长度的空间。例如,一般微机使用例如,一般微机使用的的C系统为整系统为整图图10.1型变量分配型变量分配2个字节,对实型变量分配个字节,对实型变量分配4个字节,个字节,对字符型变量分配对字符型变量分配1个字节。内存区的每一个字节个字节。内存区的每一个字节有一个编号,这就是有一个编号,这就是“地址地址”,它相当于旅馆中,它相当于旅馆中的房间号。在地址所标志的内存单
5、元中存放数据,的房间号。在地址所标志的内存单元中存放数据,这相当于旅馆中各个房间中居住旅客一样。请务这相当于旅馆中各个房间中居住旅客一样。请务必弄清楚一个内存单元的地址与内存单元的内容必弄清楚一个内存单元的地址与内存单元的内容这两个概念的区别,如图这两个概念的区别,如图10.1所示。假设程序已定所示。假设程序已定义了义了3个整型变量个整型变量i、j、k,编译时系统分配,编译时系统分配2000和和2001两个字节给变量两个字节给变量i,2002,2003字节给字节给j,2004,2005给给k。 在程序中一般是通过变量名来对内存在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译
6、以后己单元进行存取操作的。其实程序经过编译以后己经将变量名转换为变量的地址,对变量值的存取经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。例如,都是通过地址进行的。例如,printf(“%D”,i)的的执行是这样的:根据变量名与地址的对应关系执行是这样的:根据变量名与地址的对应关系(这这个对应关系是在编译时确定的个对应关系是在编译时确定的),找到变量,找到变量i的地址的地址2000,然后从由,然后从由2000开始的两个字节中取出数据开始的两个字节中取出数据(即变量的值即变量的值3),把它输出。输入时如果用,把它输出。输入时如果用scanf(%D,&i),在执行时,就把从键盘输入,
7、在执行时,就把从键盘输入的值送到地址为的值送到地址为2000开始的整型存储单元中。如开始的整型存储单元中。如果有语句果有语句“k=i+j”,则从,则从2000、2001字节取出字节取出i的的值值(3),再从,再从2002、2003字节取出字节取出j的值的值(6),将它,将它们相加后再将其和们相加后再将其和(9)送到送到k所占用的所占用的2004、2005字节单元中。这种按变量地址存取变量值的方式字节单元中。这种按变量地址存取变量值的方式称为称为“直接访问直接访问”方式。方式。还可以采用另一种称之为还可以采用另一种称之为“间接访问间接访问”的方式,将的方式,将变量变量i的地址存放在另一个变量中。
8、按的地址存放在另一个变量中。按C语言的规定,语言的规定,可以在程序中定义整型变量、实型变量、字符变量可以在程序中定义整型变量、实型变量、字符变量等,也可以定义这样一种特殊的变量,它是存放地等,也可以定义这样一种特殊的变量,它是存放地址的。假设我们定义了一个变量址的。假设我们定义了一个变量i-pointer,用来存,用来存放整型变量的地址,它被分配为放整型变量的地址,它被分配为3010、3011字节。字节。可以通过下面语句将可以通过下面语句将i的刂的刂?2000)存放到存放到i-pointer中。中。i-pointer=&i;这时,这时,i-pointer的值就是的值就是2000,即变量,即变量
9、i所占用单元所占用单元的起始地址。要存取变量的起始地址。要存取变量i的值,也可以采用间接的值,也可以采用间接方式:先找到存放方式:先找到存放“i的地址的地址”的变量,从中取出的变量,从中取出i的地址的地址(2000),然后到,然后到2000、2001字节取出字节取出i的值的值(3)。打个比方,为了开一个打个比方,为了开一个A抽屉,有两种办法,一种抽屉,有两种办法,一种是将是将A钥匙带在身上,需要时直接找出该钥匙打开钥匙带在身上,需要时直接找出该钥匙打开抽屉,取出所需的东西。另一种办法是:为安全起抽屉,取出所需的东西。另一种办法是:为安全起见,将该见,将该A钥匙放到另一抽屉钥匙放到另一抽屉B中锁
10、起来。如果需中锁起来。如果需要打开要打开A抽屉,就需要先找出抽屉,就需要先找出B钥匙,打开钥匙,打开B抽屉,抽屉,取出取出A钥匙,再打开钥匙,再打开A抽屉,取出抽屉,取出A抽屉中之物,这抽屉中之物,这就是就是“间接访问间接访问”。图。图10.2是直接访问和间接访问是直接访问和间接访问的示意图。的示意图。图图10.2为了表示将数值为了表示将数值3送到变量中,可以有两种表达方法:送到变量中,可以有两种表达方法:(1) 将将3送到变量送到变量i所标志的单元中。见图所标志的单元中。见图10.2上。上。(2) 将将3送到变量送到变量i-pointer所所“指向指向”的单元的单元(即即i所标所标志的单元志
11、的单元)中。见图中。见图10.2下。下。所谓所谓“指向指向”就是通过地址来体现的。就是通过地址来体现的。i-pointer中的中的值为值为2000,它是变量,它是变量i的地址,这样就在的地址,这样就在i-pointer和和变量变量i之间建立起一种联系,即通过之间建立起一种联系,即通过i-pointer能知道能知道i的地址,从而找到变量的地址,从而找到变量i的内存单元。图的内存单元。图10.2中以箭中以箭头表示这种头表示这种“指向指向”关系。关系。由于通过地址能找到所需的变量单元,我们可以说,由于通过地址能找到所需的变量单元,我们可以说,地址地址“指向指向”该变量单元该变量单元(如同说,房间号如
12、同说,房间号“指向指向“某一房间一样某一房间一样)。因此在。因此在C语言中,将地址形象化地语言中,将地址形象化地称为称为“指针指针”。意思是通过它能找到以它为地址的内。意思是通过它能找到以它为地址的内存单元存单元(例如根据地址例如根据地址2000就能找到变量就能找到变量i的存储单的存储单元,从而读取其中的值元,从而读取其中的值)。一个变量的地址称为该。一个变量的地址称为该变量的变量的“指针指针”。例如,地址。例如,地址2000是变量是变量i的指针。的指针。如果有一个变量专门用来存放另一变量的地址如果有一个变量专门用来存放另一变量的地址(即即指针指针),则它称为,则它称为“指针变量指针变量”。上
13、述的。上述的i-pointer就是一个指针变量。指针变量的值就是一个指针变量。指针变量的值(即指针变量中即指针变量中存放的值存放的值)是指针是指针(地址地址)。请区分。请区分“指针指针”和和“指指针变量针变量”这两个概念。例如,可以说变量这两个概念。例如,可以说变量i的指针的指针是是2000,而不能说,而不能说i的指针变量是的指针变量是2000。10.2 变量的指针和指向变量的指针变量变量的指针和指向变量的指针变量如前所述,变量的指针就是变量的地址。存放变量如前所述,变量的指针就是变量的地址。存放变量地址的变量是指针变量,用来指向另一个变量。地址的变量是指针变量,用来指向另一个变量。为了表示指
14、针变量和它所指向的变量之间的联系,为了表示指针变量和它所指向的变量之间的联系,在程序中用在程序中用“*”符号表示符号表示“指向指向”,例如,例如,i-pointer代表指针变量,而代表指针变量,而*i-pointer是是i-pointer所所指向的变量,见图指向的变量,见图10.3。图图10.310.3可以看到,可以看到,*i-pointer也代表一个变量,它和变量也代表一个变量,它和变量i是是同一回事。下面两个语句作用相同:同一回事。下面两个语句作用相同: i=3; *i-pointer=3;第第个语句的含意是将个语句的含意是将3赋给指针变量赋给指针变量i-pointer所指所指向的变量。向
15、的变量。10.2.1 定义一个指针变量定义一个指针变量C语言规定所有变量在使用前必须定义,指定其类型,语言规定所有变量在使用前必须定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是用来专门存放地址的。必须其他类型的变量,它是用来专门存放地址的。必须将它定义为将它定义为“指针类型指针类型”。先看一个具体例子:。先看一个具体例子:inti,j;int*pointer_1,*pointer_2;第第1行定义了两个整型变量行定义了两个整型变量i和和j,第,第2行定义了两个指行定义了两个指针变量:针变量:pointer_1和和p
16、ointer_2,它们是指向整型,它们是指向整型变量的指针变量。左端的变量的指针变量。左端的int是在定义指针变量时是在定义指针变量时必须指定的必须指定的“基类型基类型”。 指针变量的基类型用来指针变量的基类型用来指定该指针变量可以指向的变量的类型。例如,上指定该指针变量可以指向的变量的类型。例如,上面定义的指针变量面定义的指针变量pointer_1和和pointer_2可以用来可以用来指向整型变量指向整型变量i和和j,但不能指向实型变量,但不能指向实型变量a和和B。定义指针变量的一般形式为基类型定义指针变量的一般形式为基类型*指针变量名下面指针变量名下面都是合法的定义:都是合法的定义:flo
17、at*pointer_3;(pointer-3是指向实型变量的指针变是指向实型变量的指针变量量)char*pointer_4; (pointer-4是指向字符型变量的指是指向字符型变量的指针变量针变量)那么,怎样使一个指针变量指向另一个变量呢?下那么,怎样使一个指针变量指向另一个变量呢?下面用赋值语句使一个指针变量指向一个整型变量:面用赋值语句使一个指针变量指向一个整型变量:pointer_1=&i;pointer_2=&j;将变量将变量i的地址存放到指针变量的地址存放到指针变量pointer_1中,因此中,因此pointer_1就就“指向指向”了变量了变量i。图图10.4同样,将变量同样,将
18、变量j的地址存放到指针变量的地址存放到指针变量pointer_2中,中,因此因此pointer_2就就“指向指向”了变量了变量j。见图。见图10.4。在定义指针变量时要注意两点:在定义指针变量时要注意两点:(1) 指针变量前面的指针变量前面的“*”,表示该变量的类型为指,表示该变量的类型为指针型变量。注意:指针变量名是针型变量。注意:指针变量名是pointer_1、pointer_2,而不是,而不是*pointer_1、*pointer_2。这。这是与以前所介绍的定义变量的形式不同的。是与以前所介绍的定义变量的形式不同的。(2) 在定义指针变量时必须指定基类型。有的读者在定义指针变量时必须指定
19、基类型。有的读者认为既然指针变量是存放地址的,那么只需要指认为既然指针变量是存放地址的,那么只需要指定其为定其为“指针型变量指针型变量”即可,为什么还要指定基即可,为什么还要指定基类型呢?我们知道整型数据和实型数据在内存中类型呢?我们知道整型数据和实型数据在内存中所占的字节数是不相同的所占的字节数是不相同的(前者为前者为2字节,后者为字节,后者为4字字节节),在本章的稍后将要介绍指针的移动和指针的运,在本章的稍后将要介绍指针的移动和指针的运算算(加、减加、减),例如,例如“使指针移动使指针移动1个位置个位置” 或或“使使指针值加指针值加1” ,这个,这个“1” 代表什么呢?如果指针代表什么呢?
20、如果指针是指向一个整型变量的,那么是指向一个整型变量的,那么“使指针移动使指针移动1个位个位置置” 意味着移动意味着移动2个字节,个字节,“使指针加使指针加1” 意味着意味着使地址值加使地址值加2个字节。如果指针是指向一个实型变个字节。如果指针是指向一个实型变量的,则增加的不是量的,则增加的不是2而是而是4。因此必须规定指针。因此必须规定指针变量所指向的变量的类型,即基类型。一个指针变量所指向的变量的类型,即基类型。一个指针变量只能指向同一个类型的变量。不能忽而指向变量只能指向同一个类型的变量。不能忽而指向一个整型变量,忽而指向一个实型变量。上面的一个整型变量,忽而指向一个实型变量。上面的定义
21、中,表示定义中,表示pointer_1和和pointer_2只能指向整型只能指向整型变量。变量。对上述指针变量的定义也可以这样理解:对上述指针变量的定义也可以这样理解:*pointer_1和和*pointer_2是整型变量,如同:是整型变量,如同:“int a,B;”定义了定义了a和和B是整型变量一样。而是整型变量一样。而*pointer_1和和*pointer_2是是pointer_1和和pointer_2所指向的变量,所指向的变量,pointer_1和和pointer_2是指针变量。是指针变量。需要特别注意的是,只有整型变量的地址才能放需要特别注意的是,只有整型变量的地址才能放到指向整型变
22、量的指针变量中。到指向整型变量的指针变量中。10.2.2 指针变量的引用指针变量的引用请牢记,指针变量中只能存放地址请牢记,指针变量中只能存放地址(指针指针),不要将,不要将一个整型量一个整型量(或任何其他非地址类型的数据或任何其他非地址类型的数据)赋给赋给一个指针变量。下面的赋值是不合法的:一个指针变量。下面的赋值是不合法的:pointer-1=100;(pointer-1为指针变量,为指针变量,100为整为整数数)有两个有关的运算符:有两个有关的运算符:(1) &: 取地址运算符。取地址运算符。(2) *: 指针运算符指针运算符(或称或称“间接访问间接访问”运算符运算符)。例如:例如:&a
23、为变量为变量a的地址,的地址,*p为指针变量为指针变量p所指向的所指向的存储单元。存储单元。例例10.1通过指针变量访问整型变量。通过指针变量访问整型变量。main() int a,B; intpointer-1, *pointer-2; a=100;B=10; pointer-1=&a;/*把变量把变量a的地址赋给的地址赋给pointer-1*/ pointer-2=&B; /*把变量把变量B的地址赋给的地址赋给pointer-2*/ printf(%D,%Dn,a,B); printf(%D,%Dn,*pointer-1,*pointer-2); 运行结果为:运行结果为:100,10100
24、,10对程序的说明:对程序的说明:(1) 在开头处虽然定义了两个指针变量在开头处虽然定义了两个指针变量pointer-1和和pointer-2,但它们并未指向任何一个整型变量。,但它们并未指向任何一个整型变量。只是提供两个指针变量,规定它们可以指向整型只是提供两个指针变量,规定它们可以指向整型变量。至于指向哪一个整型变量,要在程序语句变量。至于指向哪一个整型变量,要在程序语句中指定。程序第中指定。程序第5、6行的作用就是使行的作用就是使pointer-1指指向向a,pointer-2指向指向B,见图,见图10.5。此时。此时pointer-1的的值为值为&a(即即a的地址的地址),pointe
25、r-2的值为的值为&B。图图10.5(2) 最后一行的最后一行的pointer-1和和*pointer-2就是变量就是变量a和和B。最后两个。最后两个printf函数作用是相同的。函数作用是相同的。(3) 程序中有两处出现程序中有两处出现pointer-1和和*pointer-2,请区分它们的不同含义。程序第请区分它们的不同含义。程序第3行的行的*pointer-1和和*pointer-2表示定义两个指针变量表示定义两个指针变量pointer-1、pointer-2。它们前面的。它们前面的“*”只是表示该变量是指只是表示该变量是指针变量。程序最后一行针变量。程序最后一行printf函数中的函数
26、中的*pointer-1和和*pointer-2则代表变量,即则代表变量,即pointer-1和和pointer-2所指向的变量。所指向的变量。(4) 第第5、6行行“pointer-1=&a;”和和“pointer-2=&B;”是将是将a和和B的地址分别赋给的地址分别赋给pointer-1和和pointer-2。注意不应写成:。注意不应写成:“*pointer-1=&a;”和和“*pointer-2=&B;”。因为。因为a的地址是赋给指针变的地址是赋给指针变量量pointer-1,而不是赋给而不是赋给*pointer-1(即变量即变量a)。请对照图。请对照图10.5分析。分析。下面对下面对“
27、&”和和“*”运算符再做些说明:运算符再做些说明:(1) 如果已执行了如果已执行了“pointer-1=&a;”语句,若有语句,若有&*pointer-1它的含义是什么?它的含义是什么?“&”和和“*”两个运算符的优先两个运算符的优先级别相同,但按自右而左方向结合,因此先进行级别相同,但按自右而左方向结合,因此先进行*pointer-1的运算,它就是变量的运算,它就是变量a,再执行,再执行&运算。运算。因此,因此,&*pointer-1与与&a相同,即变量相同,即变量a的地址。的地址。如果有如果有pointer-2=&*pointer-1;它的作用是将它的作用是将&a(a的地址的地址)赋给赋给
28、pointer-2,如果,如果pointer-2原来指向原来指向B,经过重新赋值后它已不再指,经过重新赋值后它已不再指向向B了,而也指向了了,而也指向了a。见图。见图10.6。图。图10.6(a)是原是原来的情况,图来的情况,图10.6(B)是执行上述赋值语句后的情是执行上述赋值语句后的情况。况。图图10.610.6(2) *&a的含义是什么?先进行的含义是什么?先进行&a运算,得运算,得a的地址,的地址,再进行再进行*运算。即运算。即&a所指向的变量,所指向的变量,*&a和和*pointer-1的作用是一样的的作用是一样的(假设已执行了假设已执行了“pointer-1=&a”),它们等价于变
29、量,它们等价于变量a。即。即*&a与与a等价,见图等价,见图10.7。图图10.710.7(3) (*pointer-1)+相当于相当于a+。注意括号是必要的,。注意括号是必要的,如果没有括号,就成为了如果没有括号,就成为了*pointer-1+,和,和*为同为同一优先级别,而结合方向为自右而左,因此它相一优先级别,而结合方向为自右而左,因此它相当于当于*(pointer-1+)。由于。由于+在在pointer-1的右侧,的右侧,是是“后加后加”,因此先对,因此先对pointer-1的原值进行的原值进行*运算,运算,得到得到a的值,然后使的值,然后使pointer-1的值改变,这样的值改变,这
30、样pointer-1不再指向不再指向a了。了。下面举一个指针变量应用的例子。下面举一个指针变量应用的例子。例例10.2输入输入a和和B两个整数,按先大后小的顺序输出两个整数,按先大后小的顺序输出a和和B。main() int*p1,*p2,*p,a,B; scanf(%D,%D,&a,&B); p1=&a;p2=&B; if(aB) p=p1;p1=p2;p2=p; printf(na=%D,B=%Dnn,a,B); printf(max=%D,min=%Dn,*p1,*p2); 运行情况如下:运行情况如下:5,9a=5,B=9max=9,min=5当输入当输入a=5,B=9时,由于时,由于a
31、B,将,将p1和和p2交换。交换前的交换。交换前的情况见图情况见图10.8(a),交换后见图,交换后见图10.8(B)。图图10.810.8请注意,请注意,a和和B并未交换,它们仍保持原值,但并未交换,它们仍保持原值,但p1和和p2的值改变了。的值改变了。p1的值原为的值原为&a,后来变成,后来变成&B,p2原值为原值为&B,后来变成,后来变成&a。这样在输出。这样在输出*p1和和*p2时,时,实际上是输出变量实际上是输出变量B和和a的值,所以先输出的值,所以先输出9,然后,然后输出输出5。这个问题的算法是不交换整型变量的值,而是交换这个问题的算法是不交换整型变量的值,而是交换两个指针变量的值
32、两个指针变量的值(即即a和和B的地址的地址)。10.2.3 指针变量作为函数参数指针变量作为函数参数函数的参数不仅可以是整型、实型、字符型等数据,函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。下面通过一个例子来说址传送到另一个函数中。下面通过一个例子来说明。明。例例10.3题目同例题目同例10.2,即对输入的两个整数按大小顺,即对输入的两个整数按大小顺序输出。序输出。今用函数处理,而且用指针类型的数据作函数参数。今用函数处理,而且用指针类型的数据作函数参数。程序如下:程序如下:Swap(i
33、nt *p1,int *p2) int temp; temp=*p1; *p1=*p2; *p2=temp; main() int a,B; int*pointer-1,*pointer-2; scanf(%D,%D,&a,&B); pointer-1=&a;pointer-2=&B; if(aB)S,pointer-2); printf(n%D,%Dn,a,B); 运行情况如下:运行情况如下:5,99,5对程序的说明:对程序的说明:Swap是用户定义的函数,它的作用是交换两个变量是用户定义的函数,它的作用是交换两个变量(a和和B)的值。的值。Swap函数的两个形参函数的两个形参p1、p2是指
34、针是指针变量。程序运行时,先执行变量。程序运行时,先执行main函数,函数,输入输入a和和B的值的值(今输入今输入5和和9)。然后将。然后将a和和B的地址分的地址分别赋给指针变量别赋给指针变量pointer-1和和pointer-2,使,使pointer-1指向指向a,pointer-2指向指向B,见图,见图10.9(a)。接着执行。接着执行if语语句,由于句,由于aB,因此执行,因此执行Swap函数。注意实参函数。注意实参pointer-1和和pointer-2是指针变量,在函数调用时,是指针变量,在函数调用时,将实参变量的值传送给形参变量。采取的依然是将实参变量的值传送给形参变量。采取的依
35、然是“值传递值传递”方式。因此虚实结合后形参方式。因此虚实结合后形参p1的值为的值为&a,p2的值为的值为&B。见图。见图10.9(B)。这时。这时p1和和pointer-1都指都指向变量向变量a,p2和和pointer-2都指向都指向B。接着执行。接着执行Swap函函数的函数体,使数的函数体,使*p1和和*p2的值互换,也就是使的值互换,也就是使a和和B的值互换。互换后的情况见图的值互换。互换后的情况见图10.9(C)。函数调用结。函数调用结束后,束后,p1和和p2不复存在不复存在(已释放已释放),情况如图,情况如图10.9(D)所示。最后在所示。最后在main函数中输出的函数中输出的a和和
36、B的值已是经过的值已是经过交换的值交换的值(a=9,B=5)。图图10.910.9请注意交换请注意交换*p1和和*p2的值是如何实现的。如果写成的值是如何实现的。如果写成以下这样就有问题了:以下这样就有问题了:Swap(int *p1,int *p2) inttemp; *temp=*p1; /*此语句有问题此语句有问题*/ p1=*p2; p2=*temp; p1就是就是a,是整型变量。而,是整型变量。而*temp是指针变量是指针变量temp所所指向的变量。但指向的变量。但temp中并无确定的地址值,它的中并无确定的地址值,它的值是不可预见的。值是不可预见的。*temp所指向的单元也是不可所
37、指向的单元也是不可预见的。因此,对预见的。因此,对*temp赋值可能会破坏系统的正赋值可能会破坏系统的正常工作状况。应该将常工作状况。应该将*p1的值赋给一个整型变量,的值赋给一个整型变量,如程序所示那样,用整型变量如程序所示那样,用整型变量temp作为临时辅助作为临时辅助变量实现变量实现*p1和和*p2的交换。的交换。注意:注意: 本例采取的方法是:交换本例采取的方法是:交换a和和B的值,而的值,而p1和和p2的值不变。这恰和例的值不变。这恰和例10.2相反。相反。可以看到,在执行可以看到,在执行Swap函数后,变量函数后,变量a和和B的值改变的值改变了。请仔细分析,这个改变是怎么实现的。这
38、个了。请仔细分析,这个改变是怎么实现的。这个改变不是通过将形参值传回实参来实现的。请读改变不是通过将形参值传回实参来实现的。请读者考虑一下能否通过下面的函数实现者考虑一下能否通过下面的函数实现a和和B互换。互换。Swap(int x,int y) int temp; temp=x; x=y; y=temp; 如果在如果在main函数中用函数中用“Swap(a,B);”调用调用Swap函数,函数,会有什么结果呢?如图会有什么结果呢?如图10.10所示。在函数调用时,所示。在函数调用时,a的值传送给的值传送给x,B的值传送给的值传送给y。执行完。执行完Swap函数函数后,后,x和和y的值是互换了,
39、但的值是互换了,但main函数中的函数中的a和和B并并未互换。也就是说由于未互换。也就是说由于“单向传送单向传送”的的“值传递值传递”方式,形参值的改变无法传给实参。方式,形参值的改变无法传给实参。为了使在函数中改变了的变量值能被为了使在函数中改变了的变量值能被main函数所用,函数所用,不能采取上述把要改变值的图不能采取上述把要改变值的图10.10变量作为参数的变量作为参数的办法,而应该用指针变量作为函数参数,在函数执办法,而应该用指针变量作为函数参数,在函数执行过程中使指针变量所指向的变量值发生变化,函行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来,
40、数调用结束后,这些变量值的变化依然保留下来,这样就实现了这样就实现了“通过调用函数使变量的值发生变化,通过调用函数使变量的值发生变化,在主调函数在主调函数(如如main函数函数)中使用这些改变了的值中使用这些改变了的值”的目的。的目的。如果想通过函数调用得到如果想通过函数调用得到n个要改变的值,可以:个要改变的值,可以:在在主调函数中设主调函数中设n个变量,用个变量,用n个指针变量指向它们个指针变量指向它们;然后将指针变量作实参,将这然后将指针变量作实参,将这n个变量的地址传给所个变量的地址传给所调用的函数的形参调用的函数的形参;通过形参指针变量,改变该通过形参指针变量,改变该n个变量的值个变
41、量的值;主调函数中就可以使用这些改主调函数中就可以使用这些改变了值的变量。请读者按此思路仔细理解例变了值的变量。请读者按此思路仔细理解例10.3程序。程序。请注意,不能企图通过改变指针形参的值而使指针请注意,不能企图通过改变指针形参的值而使指针实参的值改变。请看下面的程序:实参的值改变。请看下面的程序:Swap(int *p1,int *p2) intp; p=p1; p1=p2; p2=p; main() Swap(int *p1,int *p2) intp; p=p1; p1=p2; p2=p; main() int a,B; intpointer-1,*pointer-2; scanf(
42、%D,%D,&a,&B); pointer-1=&a; pointer-2=&B; if(aB)S,pointer-2); printf(n%D,%Dn,*pointer-1,*pointer-2); 作者的意图是:交换作者的意图是:交换pointer-1和和pointer-2的值,使的值,使pointer-1指向值大的变量。其设想是:指向值大的变量。其设想是:先使先使pointer-1指向指向a,pointer-2指向指向B,见图,见图10.11(a)。调用调用Swap函数,将函数,将pointer-1的值传给的值传给p1,pointer-2传给传给p2,见图,见图10.11(B)。在在Sw
43、ap函数函数中中使使p1与与p2的值交换,见图的值交换,见图10.11(C)。形参形参p12将地址传回实参将地址传回实参pointer-1和和pointer-2,使,使pointer-1指向指向B,pointer-2指向指向a,见图,见图10.11(D)。然后输出。然后输出*pointer-1和和*pointer-2,想得到输出,想得到输出“9,5”。图图10.1110.11但是这是办不到的,程序实际输出为但是这是办不到的,程序实际输出为“5,9”。问题。问题出在第出在第步。步。C语言中实参变量和形参变量之间的语言中实参变量和形参变量之间的数据传递是单向的数据传递是单向的“值传递值传递”方式。
44、指针变量作方式。指针变量作函数参数也要遵循这一规则。调用函数不可能改函数参数也要遵循这一规则。调用函数不可能改变实参指针变量的值,但可以改变实参指针变量变实参指针变量的值,但可以改变实参指针变量所指变量的值。我们知道,函数的调用可以所指变量的值。我们知道,函数的调用可以(而且而且只可以只可以)得到一个返回值得到一个返回值(即函数值即函数值),而运用指针,而运用指针变量作参数,可以得到多个变化了的值。难以做变量作参数,可以得到多个变化了的值。难以做到这一点的。例到这一点的。例10.4输入输入a、B、C 3个整数,个整数,按大小顺序输出。按大小顺序输出。Swap(int *pt1, int *pt
45、2) int temp; temp=*pt1; pt1=*pt2; pt2=temp; exChanGe(int *q1, int *q2, int *q3) if(*q1*q2) S,q2); if(*q1*q3) S,q3); if(*q2*q3) S,q3); main() int a,B,C,*p1,*p2,*p3; scanf(%D,%D,%D,&a,&B,&C); p1=&a;p2=&B;p3=&C; exChanGe(p1,p2,p3); printf(n%D,%D,%Dn,a,B,C); 运行情况如下:运行情况如下:9,0,1010,9,010.3 数组的指针和指向数组的指针变
46、量数组的指针和指向数组的指针变量一个变量有地址,一个数组包含若干元素,每个数一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可的地址。指针变量既然可以指向变量,当然也可以指向数组和数组元素以指向数组和数组元素(把数组起始地址或某一元把数组起始地址或某一元素的地址放到一个指针变量中素的地址放到一个指针变量中)。所谓数组的指针。所谓数组的指针是指数组的起始地址,组元素的指针是数组元素是指数组的起始地址,组元素的指针是数组元素的地址。的地址。引用数组元素可以用下标法引用数组元素可以
47、用下标法(如如a3),也可以用指针,也可以用指针法,即通过指向数组元素的指针找到所需的元素。法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高使用指针法能使目标程序质量高(占内存少,运行占内存少,运行速度快速度快)。10.3.1 指向数组元素的指针指向数组元素的指针定义一个指向数组元素的指针变量的方法,与以前定义一个指向数组元素的指针变量的方法,与以前介绍的指向变量的指针变量相同。介绍的指向变量的指针变量相同。例如:例如:int a10;/*定义定义a为包含为包含10个整型数据的数组个整型数据的数组*/intp; /*定义定义p为指向整型变量的指针变量为指向整型变量的指针
48、变量*/图图10.12应当注意,如果数组为应当注意,如果数组为int型,则指针变量亦应指向型,则指针变量亦应指向int型。下面是对该指针元素赋值:型。下面是对该指针元素赋值:p=&a0;把把a0元素的地址赋给指针变量元素的地址赋给指针变量p。也就是说,。也就是说,p指指向向a数组的第数组的第0号元素,见图号元素,见图10.12。C语言规定数组名代表数组的首地址,也就是第语言规定数组名代表数组的首地址,也就是第0号号元素的地址。因此,下面两个语句等价:元素的地址。因此,下面两个语句等价:p=&a0;p=a;注意数组注意数组a不代表整个数组,上述不代表整个数组,上述“p=a;”的作用是的作用是“把
49、把a数组的首地址赋给指针变量数组的首地址赋给指针变量p”,而不是,而不是“把把数组数组a各元素的值赋给各元素的值赋给p”。在定义指针变量时可以赋给初值:在定义指针变量时可以赋给初值:intp=&a0;它等效于它等效于intp;p=&a 0; /*注意,不是注意,不是 *p=&a 0;*/当然定义时也可以写成当然定义时也可以写成intp=a;它的作用是将它的作用是将a的首地址的首地址(即即a0的地址的地址)赋给指针变赋给指针变量量p(而不是赋给而不是赋给*p)。10.3.2 通过指针引用数组元素通过指针引用数组元素假设假设p已定义为指针变量,并已给它赋了一个地址,已定义为指针变量,并已给它赋了一
50、个地址,使它指向某一个数组元素。如果有以下赋值语句:使它指向某一个数组元素。如果有以下赋值语句:p=1;表示对表示对p当前所指向的数组元素赋予一个值当前所指向的数组元素赋予一个值(值为值为1)。按按C的规定:如果指针变量的规定:如果指针变量p已指向数组中的一个已指向数组中的一个元素,则元素,则p+1指向同一数组中的下一个元素指向同一数组中的下一个元素(而不而不是将是将p值简单地加值简单地加1)。例如,数组元素是实型,每。例如,数组元素是实型,每个元素占个元素占4个字节,则个字节,则p+1意味着使意味着使p的值的值(地址地址)加加4个字节,以使它指向下一元素。个字节,以使它指向下一元素。p+1所
51、代表的地所代表的地址实际上是址实际上是p+1D,D是一个数组元素所占的字节是一个数组元素所占的字节数数(对整型,对整型,D=2;对实型,对实型,D=4;对字符型,对字符型,D=1)。图图10.13如果如果p的初值为的初值为&a0,则:,则:(1) p+i和和a+i就是就是ai的地址,或者说,它们指向的地址,或者说,它们指向a数数组的第组的第i个元素,见图个元素,见图10.13。这里需要说明的是。这里需要说明的是a代表数组首地址,代表数组首地址,a+i也是地址,它的计算方法同也是地址,它的计算方法同p+i,即它的实际地址为,即它的实际地址为a+iD。例如,。例如,p+9和和a+9的值是的值是&a
52、9,它指向,它指向a9。(2) *(p+i)或或*(a+i)是是p+i或或a+i所指向的数组元素,所指向的数组元素,即即ai。例如,。例如,*(p+5)或或(a+5)就是就是a5。即。即*(p+5)=*(a+5)=a5。实际上,在编译时,对数。实际上,在编译时,对数组元素组元素ai就是处理成就是处理成*(a+i),即按数组首地址加,即按数组首地址加上相对位移量得到要找的元素的地址,然后找出上相对位移量得到要找的元素的地址,然后找出该单元中的内容。例如,若数组该单元中的内容。例如,若数组a的首地址为的首地址为1000,设数组为整型,则,设数组为整型,则a3的地址是这样计算出来的:的地址是这样计算
53、出来的:1000+32=1006,然后从,然后从1006地址所标志的整型单地址所标志的整型单元取出元素的值,即元取出元素的值,即a3的值。可以看出,的值。可以看出, 实实际上是变址运算符,即将际上是变址运算符,即将ai按按a+i计算地址,然后计算地址,然后找出此地址单元中的值。找出此地址单元中的值。(3) 指向数组的指针变量也可以带下标,如指向数组的指针变量也可以带下标,如pi与与(p+i)等价。等价。根据以上叙述,引用一个数组元素,可以用:根据以上叙述,引用一个数组元素,可以用:(1) 下标法,如下标法,如ai形式形式;(2) 指针法,如指针法,如*(a+i)或或(p+i)。其中。其中a是数
54、组名,是数组名,p是指向数组的指针变量,其初值是指向数组的指针变量,其初值p=a。例例10.5输出数组中的全部元素。假设有一个输出数组中的全部元素。假设有一个a数组,整型,有数组,整型,有10个元素。要输出各元素的值有三种方法:个元素。要输出各元素的值有三种方法:(1) 下标法。下标法。main() int a10; inti; for(i=0;i10;i+) scanf(%D,&ai); printf(n); for(i=0;i10;i+) printf(%D,ai); (2) 通过数组名计算数组元素地址,找出元素的值。通过数组名计算数组元素地址,找出元素的值。main() int a10;
55、 int i; for(i=0;i10;i+) scanf(%D,&ai); printf(n); for(i=0;i10;i+) printf(%D,*(a+i); (3) 用指针变量指向数组元素。用指针变量指向数组元素。main() int a10; int*p,i; for(i=0;i10;i+) scanf(%D,&ai); printf(n); for(p=a;p(a+10);p+) printf(%D ,*p); 以上以上3个程序的运行情况均如下:个程序的运行情况均如下:1 2 3 4 5 6 7 8 9 01 2 3 4 5 6 7 8 9 0对三种方法的比较:对三种方法的比较:
56、(1) 例例10.5的第的第(1)和和(2)种方法执行效率是相同的。种方法执行效率是相同的。C编译系统是将编译系统是将ai转换为转换为*(a+i)处理的。即先计处理的。即先计算元素地址。因此用第算元素地址。因此用第(1)和和(2)种方法找数组元种方法找数组元素费时较多。素费时较多。(2) 第第(3)种方法比种方法比(1)(2)法快,用指针变量直接指法快,用指针变量直接指向元素,不必每次都重新计算地址,像向元素,不必每次都重新计算地址,像p+这样的这样的自加操作是比较快的。这种有规律地改变地址值自加操作是比较快的。这种有规律地改变地址值(p+)能大大提高执行效率。能大大提高执行效率。(3) 用下
57、标法比较直观,能直接知道是第几个元素。用下标法比较直观,能直接知道是第几个元素。例如,例如,a5是数组中序号为是数组中序号为5的元素的元素(注意序号从注意序号从0算起算起)。用地址法或指针变量的方法不直观,难以。用地址法或指针变量的方法不直观,难以很快地判断出当前处理的是哪一个元素。例如,很快地判断出当前处理的是哪一个元素。例如,例例10.5第第(3)种方法所用的程序,要仔细分析指针种方法所用的程序,要仔细分析指针变量变量p的当前指向,才能判断当前输出的是第几个的当前指向,才能判断当前输出的是第几个元素。元素。在使用指针变量时,有几个问题要注意:在使用指针变量时,有几个问题要注意:(1) 指针
58、变量可以实现使本身的值改变。例如,上指针变量可以实现使本身的值改变。例如,上述第述第(3)种方法是用指针变量种方法是用指针变量p来指向元素,用来指向元素,用p+使使p的值不断改变,这是合法的。如果不用的值不断改变,这是合法的。如果不用p而使而使a变化变化(例如,用例如,用a+)行不行呢?假如将上述行不行呢?假如将上述(3)程程序的最后两行改为序的最后两行改为for(p=a;a(p+10);a+)printf(%D,*a);是不行的。因为是不行的。因为a是数组名,它是数组首地址,它的是数组名,它是数组首地址,它的值在程序运行期间是固定不变的,是常量。值在程序运行期间是固定不变的,是常量。a+是是
59、什么意思呢?这是无法实现的。什么意思呢?这是无法实现的。(2) 要注意指针变量的当前值。请看下面的程序。要注意指针变量的当前值。请看下面的程序。例例10.6通过指针变量输出通过指针变量输出a数组的数组的10个元素。个元素。有人编写出以下程序:有人编写出以下程序:main() intp,i,a10; p=a; for(i=0;i10;i+) scanf(%D,p+); printf(n); for(i=0;i10;i+,p+) printf(%D,p); 这个程序乍看起来好像没有什么问题。有的人即使这个程序乍看起来好像没有什么问题。有的人即使已被告知此程序有问题,还是找不出它有什么问已被告知此程
60、序有问题,还是找不出它有什么问题。我们先看一下运行情况:题。我们先看一下运行情况:1 2 3 4 5 6 7 8 9 022153 234 0 0 30036 25202 11631 8259 8237 28483显然输出的数值并不是显然输出的数值并不是a数组中各元素的值。原因是数组中各元素的值。原因是指针变量的初始值为指针变量的初始值为a数组首地址数组首地址(见图见图10.14中的中的),但经过第一个,但经过第一个for循环读入数据后,循环读入数据后,p已指向已指向a 数组的末尾数组的末尾(见图见图10.14中中)。因此,在执行第二。因此,在执行第二个个for循环时,循环时,p的起始值不是的
61、起始值不是&a0了,而是了,而是a+10。因为执行循环时,每次要执行因为执行循环时,每次要执行p+,p指向的是指向的是a数数组下面的组下面的10个元素,而这些存储单元中的值是不个元素,而这些存储单元中的值是不可预料的。图可预料的。图10.14p=a;解决这个问题的办法,只解决这个问题的办法,只要在第二个要在第二个for循环之前加一个赋值语句:使循环之前加一个赋值语句:使p的的初始值回到初始值回到&a0,这样结果就对了。,这样结果就对了。main()intp,i,a10;p=a;for(i=0;i10;i+)scanf(%D,p+);printf(n);p=a;for(i=0;i10;i+,p+
62、)printf(%D ,p);图图10.14运行情况如下:运行情况如下:1 2 3 4 5 6 7 8 9 01 2 3 4 5 6 7 8 9 0(3) 从上例可以看到,虽然定义数组时指定它包含从上例可以看到,虽然定义数组时指定它包含10个元素,个元素,可以用可以用p指向当前的数组元素。但是实际上指针变量指向当前的数组元素。但是实际上指针变量p可以可以指向数组以后的内存单元。如果引用数组元素指向数组以后的内存单元。如果引用数组元素a10,C编编译程序并不认为非法,系统把它按译程序并不认为非法,系统把它按*(a+10)处理,即先找处理,即先找出出(a+10)的值的值(是一个地址是一个地址),然
63、后找出它指向的单元的内,然后找出它指向的单元的内容。这样做虽然是合法的容。这样做虽然是合法的(在编译时不出错在编译时不出错),但应避免出,但应避免出现这样的情况,这会使程序得不到预期的结果。这种错误现这样的情况,这会使程序得不到预期的结果。这种错误比较隐蔽,初学者往往难以发现。在使用指针变量指向数比较隐蔽,初学者往往难以发现。在使用指针变量指向数组元素时,应切实保证指向数组中有效的元素。组元素时,应切实保证指向数组中有效的元素。(4) 注意指针变量的运算。如果先使注意指针变量的运算。如果先使p指向数组指向数组a(即即p=a),则:,则: p+(或或p+=1),使,使p指向下一元素,即指向下一元
64、素,即a1。若再。若再执行执行*p,取出下一个元素,取出下一个元素a1值。值。 p+,由于,由于+和和*同优先级,结合方向为自右而同优先级,结合方向为自右而左,因此它等价于左,因此它等价于*(p+)。作用是先得到。作用是先得到p指向的指向的变量的值变量的值(即即*p),然后再使,然后再使p+1=p。例例10.6最后一个程序中最后一个最后一个程序中最后一个for语句:语句:for(i=0;i10;i+,p+)printf(%D,*p);可以改写为可以改写为for(i=0;i10,i+)printf(%D,*p+);作用完全一样。它们的作用都是先输出作用完全一样。它们的作用都是先输出*p的值,然的
65、值,然后使后使p值加值加1。这样下一次循环时,。这样下一次循环时,p就是下一个元就是下一个元素的值。素的值。 (p+)与与*(+p)作用不同。前者是先取作用不同。前者是先取*p值,后值,后使使p加加1。后者是先使。后者是先使p加加1,再取,再取*p。若。若p初值为初值为a(即即&a0),输出,输出*(p+)时,得时,得a0的值,而输出的值,而输出*(+p),则得到,则得到a1的值。的值。 (*p)+表示表示p所指向的元素值加所指向的元素值加1,即,即(a0)+,如果如果a0=3,则,则(a0)+的值为的值为4。注意:是元素。注意:是元素值加值加1,而不是指针值加,而不是指针值加1。 如果如果p
66、当前指向当前指向a数组中第数组中第i个元素,则:个元素,则:(p-)相当于相当于ai-,先对,先对p进行进行“*”运算,再使运算,再使p自自减。减。(+p)相当于相当于a+i,先使,先使p自加,再作自加,再作*运算。运算。(-p)相当于相当于a-i,先使,先使p自减,再作自减,再作*运算。运算。将将+和和-运算符用于指针变量十分有效,可以使指针变量自运算符用于指针变量十分有效,可以使指针变量自动向前或向后移动,指向下一个或上一个数组元素。例如,动向前或向后移动,指向下一个或上一个数组元素。例如,想输出想输出a数组数组100个元素,可以:个元素,可以:p=a;p=awhile(pa+100) 或
67、或 while(pa+100)printf(“%D”,*p+); printf(%D,*p); p+;但如果不小心,很容易弄错。因此在用但如果不小心,很容易弄错。因此在用*p+形式的运算时,形式的运算时,一定要十分小心,弄清楚先取一定要十分小心,弄清楚先取p值还是先使值还是先使p加加1。10.3.3 用数组名作函数参数用数组名作函数参数数组名可以用作函数的形参和实参。如:数组名可以用作函数的形参和实参。如:main()f(int arr,int n) int array10; f(array,10); array为实参数组名,为实参数组名,arr为形参数组名。在为形参数组名。在7.7节已知,节
68、已知,当用数组名作参数时,如果形参数组中各元素当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组元素的值随之变化。这的值发生变化,实参数组元素的值随之变化。这是为什么?在学习指针以后,对此问题就容易理是为什么?在学习指针以后,对此问题就容易理解了。先看数组元素作实参时的情况。如果已定解了。先看数组元素作实参时的情况。如果已定义一个函数,其原型为:义一个函数,其原型为:void Swap(int x,int y);假设函数的作用是将两个形参假设函数的作用是将两个形参(x,y) 的值交换,今的值交换,今有以下的函数调用:有以下的函数调用:Swap(a1,a2);用数组元素用数组元素a1
69、,a2作实参的情况与用变量作实参时作实参的情况与用变量作实参时一样,是一样,是“值传递值传递”方式,将方式,将a1和和a2的值单向的值单向传递给传递给x,y。当。当x和和y的值改变时,的值改变时,a1和和a2的值并的值并不改变。不改变。再看用数组名作函数参数的情况。前已介绍,实参数组再看用数组名作函数参数的情况。前已介绍,实参数组名代表该数组首地址。而形参是用来接收从实参传递名代表该数组首地址。而形参是用来接收从实参传递过来的数组首地址的。因此,形参应该是一个指针变过来的数组首地址的。因此,形参应该是一个指针变量量(只有指针变量才能存放地址只有指针变量才能存放地址)。实际上,。实际上,C编译都
70、编译都是将形参数组作为指针变量来处理的。例如,上面给是将形参数组作为指针变量来处理的。例如,上面给出的函数出的函数f的形参是写成数组形式的:的形参是写成数组形式的:f(int arr , int n)但在编译时是将但在编译时是将arr按指针变量处理的,相当于将函数按指针变量处理的,相当于将函数f的首部写成的首部写成f(int *arr, int n)以上两种写法是等价的。在调用该函数时,系统会建立以上两种写法是等价的。在调用该函数时,系统会建立一个指针变量一个指针变量arr,用来存放从主调函数传递过,用来存放从主调函数传递过来来的实参数组首地址。如果在的实参数组首地址。如果在f函数中用函数中用
71、sizeof运算运算符测定符测定arr所占的字节数所占的字节数(即即size of arr的值的值),结果,结果为为2。这就证明了系统是把。这就证明了系统是把arr作为指针变量来处作为指针变量来处理的。当理的。当arr接收了实参数组的首地址后,接收了实参数组的首地址后,arr就指就指向实参数组的开头,也就是指向向实参数组的开头,也就是指向array0。因此,。因此,*arr就是就是array0的值。的值。arr+1指向指向array1,arr+2指向指向array2,arr+3指向指向array3。也就是说。也就是说*(arr+1),*(arr+2)*(arr+3)分别是分别是array1,a
72、rray2,array3的值。根据前面介绍过的知识,的值。根据前面介绍过的知识,*(arr+i)和和arri是无条件等价的。因此,在调用是无条件等价的。因此,在调用函数期间,函数期间,arr0和和*arr以及以及array0都是数组都是数组array第第0个元素的值,依此类推,个元素的值,依此类推,arr3,*(arr+3),array3都是都是array数组第数组第3号元号元素的值,见图素的值,见图10.15。这个道理与。这个道理与10.2节节10.2.3段中段中的叙述是类似的。的叙述是类似的。图10.15常用这种方法通过调用一个函数来改变实参数组的值。常用这种方法通过调用一个函数来改变实参
73、数组的值。我们把用变量名作为函数参数和用数组名作为函数我们把用变量名作为函数参数和用数组名作为函数参数作一比较。参数作一比较。实参类型变量名数组名实参类型变量名数组名要求形参的类型变量名数组名或指针变量要求形参的类型变量名数组名或指针变量传递的信息变量的值数组的起始地址传递的信息变量的值数组的起始地址通过函数调用能否改变实参的值通过函数调用能否改变实参的值需要说明的是:需要说明的是:C语言调用函数时虚实结合的方法都语言调用函数时虚实结合的方法都是采用是采用“值传递值传递”方式,当用变量名作为函数参数方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,时传递的是变量的值,当
74、用数组名作为函数参数时,由于数组名代表的是数组起始地址,因此传递的值由于数组名代表的是数组起始地址,因此传递的值是数组首地址,所以要求形参为指针变量。是数组首地址,所以要求形参为指针变量。在用数组名作为函数实参时,既然实际上相应的形在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形参是指针变量,为什么还允许使用形参数组的形式呢式呢?这是因为在这是因为在C语言中用下标法和指针法都可语言中用下标法和指针法都可以访问一个数组以访问一个数组(如果有一个数组如果有一个数组a,则则ai和和*(a+i)无条件等价无条件等价),用下标法表示比较直观,便于理解。,用下标法表示
75、比较直观,便于理解。因此许多人愿意用数组名作形参,以便与实参数因此许多人愿意用数组名作形参,以便与实参数组对应。从应用的角度看,用户可以认为有一个组对应。从应用的角度看,用户可以认为有一个形参数组,它从实参数组那里得到起始地址,因形参数组,它从实参数组那里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就调用函数期间,如果改变了形参数组的值,也就是改变了实参数组的值。当然在主调函数中可以是改变了实参数组的值。当然在主调函数中可以利用这些已改变的值。对利用这些已改变的值。对C语言比较熟练的专业人语言比较熟
76、练的专业人员往往喜欢用指针变量作形参。员往往喜欢用指针变量作形参。应该说明:应该说明: 实参数组后代表一个固定的地址,或者实参数组后代表一个固定的地址,或者说是指针型常量,而形参数组并不是一个固定的说是指针型常量,而形参数组并不是一个固定的地址值。作为指针变量,在函数调用开始时,它地址值。作为指针变量,在函数调用开始时,它的值等于实参数组起始地址,但在函数执行期间,的值等于实参数组起始地址,但在函数执行期间,它可以再被赋值。如:它可以再被赋值。如:f(arr , int n)printf(%Dn, *arr);/*输出输出array0的值,的值,*/arr=arr+3;printf(%Dn,
77、*arr);/*输出输出array3的值,的值,*/*/例例10.7将数组将数组a中中n个整数按相反顺序存放,见图个整数按相反顺序存放,见图10.16示意。示意。解此题的算法为:将解此题的算法为:将a0与与an-1对换,再将对换,再将a1与与an-2对对,直到将图,直到将图10.16a(n-1)/2与与an-int(n-1)/2)-1对换。对换。今用循环处理此问题,设两个今用循环处理此问题,设两个“位置指示变量位置指示变量”i和和j,i的初值为的初值为0,j的初值为的初值为n-1。将。将ai与与aj交换,交换,然后使然后使i的值加的值加1,j的值减的值减1,再将,再将ai与与aj对换,对换,直
78、到直到i=(n-1)/2为止。为止。程序如下:程序如下:void inv(int x ,int n)/*形参形参x是数组名是数组名*/ int temp,i,j,m=(n-1)/2;for(i=0;i=m;i+)j=n-1-i;temp=xi;xi=xj;xj=temp;return; main()int i,a10=3,7,9,11,0,6,7,5,4,2;printf(The oriGinal array:n);for(i=0;i10;i+)printf(%D,ai);printf(n);inv(a,10);printf(The array haS Been inverteD:n);for
79、(i=0;i10;i+)printf(%D,ai);printf(n);运行情况如下:运行情况如下:The oriGinal array: 3,7,9,11,0,6,7,5,4,2, The array haS Been inverteD: 2,4,5,7,6,0,11,9,7,3,图图10.16主函数中数组名为主函数中数组名为a,赋以各元素初值。函数,赋以各元素初值。函数inv中的形中的形参数组名为参数组名为x。在。在inv函数中不必具体定义数组元素的函数中不必具体定义数组元素的个数,元素个数由实参传给形参个数,元素个数由实参传给形参n(今实参值为今实参值为10)。这。这样做可以增加函数的灵
80、活性。即不必要求函数样做可以增加函数的灵活性。即不必要求函数inv中的中的形参数组形参数组x和和main函数中的实参数组函数中的实参数组a长度相同。如果长度相同。如果在在main函数中有函数调用语句:函数中有函数调用语句:inv(a,10),表示要,表示要求对求对a数组的前数组的前10个元素实行题目要求的颠倒排列。如个元素实行题目要求的颠倒排列。如果改为:果改为:inv(a,5),则表示要求将,则表示要求将a数组的前数组的前5个元素个元素实行颠倒排列,此时,函数实行颠倒排列,此时,函数inv只处理只处理5个数组元素。个数组元素。函数函数inv中的中的m是是i值的上限,当值的上限,当im时,循环
81、继续执行时,循环继续执行;当当im时,则结束循环过程。时,则结束循环过程。例如,若例如,若n=10,则,则m=4,最后一次,最后一次ai与与aj的交换是的交换是a4与与a5交换。交换。对这个程序可以作一些改动。对这个程序可以作一些改动。将函数将函数inv中的形参中的形参x改成指改成指针变量。实参为数组名针变量。实参为数组名a,即数组即数组a的首地址,将它传的首地址,将它传给形参指针变量给形参指针变量x,这时,这时x就指向就指向a0。x+m是是am元元素的地址。设素的地址。设i和和j以及以及p都都是指针变量,用它们指向是指针变量,用它们指向有关元素。有关元素。i的初值为的初值为x,j的初值为的初
82、值为x+n-1,见图,见图10.17。使使*i与与*j交换就是使交换就是使ai与与aj交换。交换。图图10.17程序如下:程序如下:void inv(int*x,int n)/*形参形参x为指针变量为指针变量*/intp,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;return;main()int i,a10=3,7,9,111,0,6,7,5,4,2; printf(The oriGinal array:n); for(i=0;i10;i+)printf(%D,ai); printf(
83、n); inv(a,10);printf(The array haS Been inverteD:n); for(i=0;i10;i+)printf(%D,ai); printf(n); 运行情况与前一程序相同。运行情况与前一程序相同。例例10.8从从10个数中找出其中最大值和最小值。个数中找出其中最大值和最小值。本题不要求改变数组元素的值,只要求得到最大值本题不要求改变数组元素的值,只要求得到最大值和最小值。但是调用一个函数只能得到一个返回和最小值。但是调用一个函数只能得到一个返回值,为了能得到两个结果值,今用全局变量在函值,为了能得到两个结果值,今用全局变量在函数之间数之间“传递传递”数据
84、。程序如下:数据。程序如下:int max,min;/*全局变量全局变量*/ void max-min-value(int array ,int n) intp,*array-enD; array-enD=array+n; max=min=*array; for(p=array+1;parray-enD;p+) if(*pmax)max=*p; else if(*pmin)min=*p; return; main() int i,numBer10; printf(enter 10 inteGer numBerS:n); for(i=0;i10;i+) scanf(%D,&numBeri); m
85、ax-min-value(numBer,10); printf(nmax=%D,min=%Dn,max,min); 运行结果如下:运行结果如下:enter 10 integer numbers:-2 4 6 8 0 -3 45 67 89 100max=100,min=-3在函数在函数max-min-value中求出的最大值和最小值放在中求出的最大值和最小值放在max和和min中。由于它们是全局变量,因此在主函中。由于它们是全局变量,因此在主函数中可以直接使用。数中可以直接使用。图图10.18函数函数max-min-value中的语句:中的语句:max=min=*array;array是形参数
86、组名,它接收从实参传来的数组是形参数组名,它接收从实参传来的数组numBer的首地址。的首地址。array是形参数组的首地址,是形参数组的首地址,*array相当于相当于*(array+0),即,即array0。上述语句。上述语句与下面语句等价:与下面语句等价:max=min=array0;见图见图10.18。在执行。在执行for循环时,循环时,p的初值为的初值为array+1,也就是使也就是使p指向指向array1。以后每次执行。以后每次执行p+,使,使p指向下一个元素。每次将指向下一个元素。每次将*p和和max与与min比较,将比较,将大者放入大者放入max,小者放入,小者放入min。与上
87、例相似,函数与上例相似,函数max-min-value的形参的形参array可以可以改为指针变量类型。即将该函数首部改为改为指针变量类型。即将该函数首部改为void max-min-value(int *array,int n)效果相同。效果相同。实参也可以不用数组名,而用指针变量传递地实参也可以不用数组名,而用指针变量传递地址,形参仍用指针变量。程序可改为:址,形参仍用指针变量。程序可改为:int max,min; void max-min-value(int *array,int n) intp,*array-end; array-end=array+n; max=min=*array;
88、for(p=array+1;parray-enD;p+) if(*pmax)max=*p; else if(*pmin)min=*p; return; main() int i,number10,*p; p=numBer;/*使使p指向指向number数组数组*/ printf(enter 10 integer numbers:n); for(i=0;i10;i+,p+) scanf(%d,p); printf(the 10integer numbers:n); for(p=number,i=0;i10;i+,p+) printf(%d,*p); p=number; max-min-value
89、(p,10);/*实参用指针变量实参用指针变量*/ printf(nmax=%d,min=%dn,max,min); 归纳起来,如果有一个实参数组,想在函数中改变归纳起来,如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的表示形式有以此数组的元素的值,实参与形参的表示形式有以下下4种情况:种情况:图图10.19(1) 形参和实参都用数组名,如:形参和实参都用数组名,如:main()f(intx ,int n)int a10; f(a,10); 可以认为有一形参数组,与实参数组共用一段内存单元,可以认为有一形参数组,与实参数组共用一段内存单元,这种形式比较好理解,见图这种形式比较好
90、理解,见图10.19。例。例10.7第一个程序第一个程序即属此情况。即属此情况。图图10.20(2) 实参用数组名,形参用指针变量。如:实参用数组名,形参用指针变量。如:main()f(int *x,int n)int a10; f(a,10); 实参实参a为数组名,形参为数组名,形参x为指向整型变量的指针变量,为指向整型变量的指针变量,函数开始执行时,函数开始执行时,x指向指向a0,即,即x=&a0,见图,见图10.20。通过。通过x值的改变,可以指向值的改变,可以指向a数组的任一元数组的任一元素。例素。例10.7的第二个程序就属此类。的第二个程序就属此类。(3) 实参形参都用指针变量。例如
91、:实参形参都用指针变量。例如:main()f(int *x,int n)int a10, *p; p=a; f(p,10); 实参实参p和形参和形参x都是指针变量。先使实参指针变量都是指针变量。先使实参指针变量p指指向数组向数组a,p的值是的值是&a0。图图10.21然后将然后将p的值传给形参指针变量的值传给形参指针变量x,x的初始值也是的初始值也是&a0,见图,见图10.21。通过。通过x值的改变可以使值的改变可以使x指向指向数组数组a的任一元素。例的任一元素。例10.8第二个程序就属此类。第二个程序就属此类。(4) 实参为指针变量,形参为数组名。如:实参为指针变量,形参为数组名。如:mai
92、n()f(int x ,int n)int a10,*p; p=a; f(p,10); 图图10.22实参实参p为指针变量,它使指针变量为指针变量,它使指针变量p指向指向a0,即即p=a或或p=&a0。形参为数组名。形参为数组名x,从前面的介绍已知,实,从前面的介绍已知,实际上将际上将x作为指针变量处理,今将作为指针变量处理,今将a0的地址传给形的地址传给形参参x,使指针变量,使指针变量x指向指向a0。也可以理解为形参数组。也可以理解为形参数组x取得取得a数组的首地址,数组的首地址,x数组和数组和a数组共用同一段内数组共用同一段内存单元。见图存单元。见图10.22。在函数执行过程中可以使。在函
93、数执行过程中可以使xi值变化,它就是值变化,它就是ai。主函数可以使用变化了。主函数可以使用变化了的数组元素值。例的数组元素值。例10.7的程序可以改写为例的程序可以改写为例10.9。例例10.9用实参指针变量改写例用实参指针变量改写例10.7。void inv(int *x,int n) intp,m,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; return; main() int i,arr10,*p=arr; printf(The original array:n); f
94、or(i=0;i10;i+,p+) scanf(%d,p); printf(n); p=arr; inv(p,10);/*实参为指针变量实参为指针变量*/ printf(The array has been inverted:n); for(p=arr;parr+10;p+) printf(%d,*p); printf(n); 注意,上面的注意,上面的main函数中的指针变量函数中的指针变量p是有确定值是有确定值的。如果在的。如果在main函数中不设数组,只设指针变量,函数中不设数组,只设指针变量,就会出错,假如把程序修改如下:就会出错,假如把程序修改如下:main() int i,arr;
95、printf(The oriGinal array:n); for(i=0;i10;i+) scanf(%D,arr+i); printf(n); inv(arr,10);/*实参为指针变量,但未被赋实参为指针变量,但未被赋值值*/ printf(The array haS Been inverteD:n); for(i=0;i10;i+) printf(%D,*(arr+i); printf(n); 编译时出错,原因是指针变量编译时出错,原因是指针变量arr没有确定值,谈不没有确定值,谈不上指向哪个变量。下面的使用是不正确的:上指向哪个变量。下面的使用是不正确的:main()f(x ,int
96、 n)int *p; f(p,n); 应注意,如果用指针变量作实参,必须先使指针变应注意,如果用指针变量作实参,必须先使指针变量有确定值,指向一个已定义的数组。量有确定值,指向一个已定义的数组。以上四种方法,实质上都是地址的传递。其中以上四种方法,实质上都是地址的传递。其中(1)(4)两种只是形式上不同,实际上都是使用指两种只是形式上不同,实际上都是使用指针变量。针变量。例例10.10用选择法对用选择法对10个整数排序。个整数排序。程序如下:程序如下:main() intp,i,a10; p=a; for(i=0;i10;i+) scanf(%D,p+); p=a; Sort(p,10); f
97、or(p=a,i=0;i10;i+) printf(%D,*p);p+; sort(int x ,int n) int i,j,k,t; for(i=0;in-1;i+) k=i; for(j=i+1;jn;j+) if(xjxk)k=j; if(k!=i) t=xi;xi=xk;xk=t; 为了便于理解,函数为了便于理解,函数Sort中用数组名作为形参,用中用数组名作为形参,用下标法引用形参数组元素,这样的程序很容易看下标法引用形参数组元素,这样的程序很容易看懂。当然也可以改用指针变量,这时懂。当然也可以改用指针变量,这时Sort函数的函数的首部可以改为首部可以改为sort(intx,int
98、 n)其他不改,程序运行结果不变。可以看到,即使在其他不改,程序运行结果不变。可以看到,即使在函数函数Sort中将中将x定义为指针变量,在函数中仍可用定义为指针变量,在函数中仍可用xi、xk这样的形式表示数组元素,它就是这样的形式表示数组元素,它就是x+i和和x+k所指的数组元素。它等价于所指的数组元素。它等价于sort(int *x,int n) int i,j,k,t; for(i=0;in-1;i+) k=i; for(j=i+1;jn;j+) if(*(x+j)*(x+k)k=j; if(k!=i) t=*(x+i);*(x+i)=*(x+k);*(x+k)=t; 请读者自己理解消化程
99、序。请读者自己理解消化程序。10.3.4 指向多维数组的指针和指针变量指向多维数组的指针和指针变量用指针变量可以指向一维数组,也可以指向多维数用指针变量可以指向一维数组,也可以指向多维数组。但在概念上和使用上,多维数组的指针比一组。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。维数组的指针要复杂一些。1. 多维数组的地址多维数组的地址为了说清楚多维数组的指针,先回顾一下多维数组为了说清楚多维数组的指针,先回顾一下多维数组的性质。今以二维数组为例,设有一个二维数组的性质。今以二维数组为例,设有一个二维数组a,它有,它有3行行4列。列。它的定义为它的定义为int a34=1,3,5
100、,7,9,11,13,15,17,19,21,23;a是一个数组名。是一个数组名。a数组包含数组包含3行,即行,即3个元素个元素: a0,a1,a2。而每一元素又是一个一维数组,它包。而每一元素又是一个一维数组,它包含含4个元素个元素(即即4个列元素个列元素),例如,例如,a0所代表的一所代表的一维数组又包含维数组又包含4个元素:个元素:a00,a01,a02,a03,见图,见图10.23。图图10.23从二维数组的角度来看,从二维数组的角度来看,a代表整个二维数组的首地代表整个二维数组的首地址,也就是第址,也就是第0行的首地址。行的首地址。a+1代表第代表第1行的首地行的首地址。如果二维数组
101、的首地址为址。如果二维数组的首地址为2000,则,则a+1为为2008,因为第,因为第0行有行有4个整型数据,因此个整型数据,因此a+1的含义是的含义是a1的地址,即的地址,即a+42=2008。a+2代表第代表第2行的首行的首地址,它的值是地址,它的值是2016,见图,见图10.24。a0、a1、a2既然是一维数组名,而既然是一维数组名,而C语言又规语言又规定了数组名代表数组的首地址,因此定了数组名代表数组的首地址,因此a0代表第代表第0行一维数组中第行一维数组中第0列元素的地址,即列元素的地址,即&a00。a1的值是的值是&a10,a2的值是的值是&a20。图图10.24请考虑第请考虑第0
102、行第行第1列元素的地址怎么表示?可以用列元素的地址怎么表示?可以用a0+1来表示,见图来表示,见图10.25。此时。此时“a0+1”中的中的1代表代表1个列元素的字节数,即个列元素的字节数,即2个字节。今个字节。今a0的值的值是是2000,a0+1的值是的值是2002(而不是而不是2008)。这是因。这是因为现在是在一维数组范围内讨论问题的,正如有为现在是在一维数组范围内讨论问题的,正如有一个一维数组一个一维数组x,x+1是其第是其第1列元素地址一样。列元素地址一样。a0+0、a0+1、a0+2、a0+3分别是分别是a00、a01、a02、a03的地址的地址(即即&a00、&a01、&a图图1
103、0.2502、&a03)。前已述。前已述及,及,a0和和(a+0)等价,等价,a1和和(a+1)等价,等价,ai和和*(a+i)等价。因此,等价。因此,a0+1和和(a+0)+1的值都是的值都是&a01 (即图即图10.25中的中的2002)。a1+2和和*(a+1)+2的值都是的值都是&a12(即图中的即图中的2012)。请。请注意不要将注意不要将*(a+1)+2错写成错写成*(a+1+2),后者变成,后者变成*(a+3)了,了,相当于相当于a3。进一步分析,欲得到进一步分析,欲得到a01的值,用地址法怎么表的值,用地址法怎么表示呢?既然示呢?既然a0+1和和*(a+0)+1,是,是a01的
104、地址,的地址,那么,那么,*(a0+1)就是就是a01的值。同理,的值。同理,*(*(a+0)+1)或或*(*a+1)也是也是a01的值。的值。*(ai+j)或或*(*(a+i)+j)是是aij的值。务请记住的值。务请记住*(a+i)和和ai是等价的。是等价的。有必要对有必要对ai的性质作进一步说明。的性质作进一步说明。ai从形式上看从形式上看是是a数组中第数组中第i个元素。如果个元素。如果a是一维数组名,则是一维数组名,则ai代表代表a数组第数组第i个元素所占的内存单元。个元素所占的内存单元。ai是有物是有物理地址的,是占内存单元的。但如果理地址的,是占内存单元的。但如果a是二维数组,是二维
105、数组,则则ai是代表一维数组名。是代表一维数组名。ai本身并不占本身并不占它也不存放它也不存放a数组中各个元素的值。它只是一个地数组中各个元素的值。它只是一个地址址(如同一个一维数组名如同一个一维数组名x并不占内存单元而只代并不占内存单元而只代表地址一样表地址一样)。a、a+i、ai、*(a+i)、*(a+i)+j、ai+j都是地址。都是地址。*(ai+j)、*(*(a+i)+j)是二维是二维数组元素数组元素aij的值。有些读者可能不理解为什么的值。有些读者可能不理解为什么a+1和和*(a+1)都是都是2008呢?他们想呢?他们想“a+1的值和的值和a+1的地址怎么都是一样的呢的地址怎么都是一
106、样的呢”?的确,二维数组中?的确,二维数组中有些概念比较复杂难懂,要反复思考。首先说明,有些概念比较复杂难懂,要反复思考。首先说明,a+1是地址是地址(指向第指向第1行首地址行首地址),而,而*(a+1)并不是并不是“a+1单元的内容单元的内容(值值)”,因为,因为a+1并不是一个实并不是一个实际变量,也就谈不上它的内容。际变量,也就谈不上它的内容。*(a+1)就是就是a1,而而a1是一维数组名,所以也是地址。以上各种形是一维数组名,所以也是地址。以上各种形式都是地址计算的不同表示。式都是地址计算的不同表示。为了说明这个容易搞混的问题,举一个日常生活中为了说明这个容易搞混的问题,举一个日常生活
107、中的例子来说明。有一个排,下设的例子来说明。有一个排,下设3个班,每班有个班,每班有10名战士。规定排长只管理到班,班长管理战士。名战士。规定排长只管理到班,班长管理战士。在排长眼里只有第在排长眼里只有第0、1、2班班(为与为与C语言中数组下语言中数组下标一致,假定班号也从标一致,假定班号也从0开始开始)。排长从第。排长从第0班的起班的起始位置走到第始位置走到第1班的起始位置,看来只走了一步,班的起始位置,看来只走了一步,但实际上它跳过了但实际上它跳过了10个战士。这相当于个战士。这相当于a+1(见图见图10.26)。为了找到某一班内某一个战士,必须给两。为了找到某一班内某一个战士,必须给两个
108、参数,即第个参数,即第i班第班第j个战士,先找到第个战士,先找到第i班,然后班,然后由该班班长在本班范围内找第由该班班长在本班范围内找第j个战士。这个战士个战士。这个战士的位置就是的位置就是ai+j(这是一个地址这是一个地址)。开始时班长面。开始时班长面对第对第0个战士。注意,排长和班长的初始位置是相个战士。注意,排长和班长的初始位置是相同的同的(如图如图10.25中的中的a和和a0都是都是2000)。但它们的。但它们的“指向指向”是不同的。是不同的。排长排长“指向指向”班,他走一步就跳过班,他走一步就跳过1个班,而班长个班,而班长“指向指向”战士,走一步只是指向下一个战士。可以战士,走一步只
109、是指向下一个战士。可以看到排长是看到排长是“宏观管理宏观管理”,只管班,在图,只管班,在图10.26中中是控制纵向,班长则是是控制纵向,班长则是“微观管理微观管理”,管理到战,管理到战士,在图上是控制横向。如果要找第士,在图上是控制横向。如果要找第1班第班第2个战个战士,则先由排长找到第士,则先由排长找到第1班的班长,然后,由班长班的班长,然后,由班长在本班范围内找到第在本班范围内找到第2个战士。二维数组个战士。二维数组a相当于相当于排长,每一行排长,每一行(即一维数组即一维数组a0 、a1、a2)相当相当于班长,每一行中的元素于班长,每一行中的元素(如如a12)相当于战士。相当于战士。a+1
110、与与a0+1是不同的,是不同的,a+1是第是第1行的首地址,行的首地址,a+1指向第指向第1行行(相当于排长走到第相当于排长走到第1班的开头班的开头),而,而*(a+1)或或a1或或a1+0都指向第都指向第1行第行第0列元素列元素(相相当于第当于第1班第班第0个战士个战士),二者地址虽相同,但含义,二者地址虽相同,但含义不同了。前者是不同了。前者是“纵向控制纵向控制”,后者是,后者是“横向控横向控制制”。a,a0的值虽然相同的值虽然相同(等于等于2000),但是由于指针的类,但是由于指针的类型不同型不同(a是指向一维数组,是指向一维数组,a0指向指向a00元素元素)因因此,对这些指针进行加此,
111、对这些指针进行加1的运算,得到的结果是不的运算,得到的结果是不同的。同的。 请记住,二维数组名请记住,二维数组名(如如a)是指向行的。因是指向行的。因此此a+1中的中的“1” 代表一行中全部元素所占的字节代表一行中全部元素所占的字节数数(图图10.25表示为表示为8个字节个字节)。一维数组名。一维数组名(如如a0,a1)是指向列元素的。是指向列元素的。a0+1中的中的“1” 代表一个代表一个元素所占的字节数元素所占的字节数(图图10.25表示为表示为2个字节个字节)。在行。在行指针前面加一个指针前面加一个*,就转换为列指针。例如,就转换为列指针。例如,a和和a+1是行指针,在它们前面加一个是行
112、指针,在它们前面加一个*就是就是*a和和*(a+1) ,它们就成为列指针,分别指向,它们就成为列指针,分别指向a数组数组0行行0列的元素和列的元素和1行行0列的元素列的元素 。反之,在列指针前面。反之,在列指针前面加加&,就成为行指针。例如,就成为行指针。例如a0 是指向是指向0行行0列元素列元素的列指针,的列指针,在它前面加一个在它前面加一个&,得,得&a0 ,由于,由于a0 与与*(a+0) 等价,因此等价,因此&a0 与与&*a等价,也就是与等价,也就是与a等价,它等价,它指向二维数组的指向二维数组的0行。行。图图10.25图图10.26不要把不要把 &ai简单地理解为简单地理解为 ai
113、单元的物理地址单元的物理地址,因因为并不存在为并不存在 ai这样一个变量。它只是一种地址的这样一个变量。它只是一种地址的计算方法计算方法,能得到第能得到第i行的首地址行的首地址, &ai和和ai的值的值是一样的是一样的, 但它们的含义是不同的。但它们的含义是不同的。 &ai或或a+i指向行指向行,而而ai或或(a+i)指向列。当列下标指向列。当列下标j为为0时时,&ai和和ai(即即ai+j)值相等值相等,即它们具有同一地即它们具有同一地址值。址值。(a+i)只是只是ai的另一种表示形式的另一种表示形式,不要简单不要简单地认为是地认为是“a+i所指单元中的内容所指单元中的内容”。在一维数组。在
114、一维数组中中a+i所指向的是一个数组元素的存储单元所指向的是一个数组元素的存储单元,它有它有具体值具体值,上述说法是正确的。而对二维数组上述说法是正确的。而对二维数组,a+i不不是指向具体存储单元而指向行。在二维数组中是指向具体存储单元而指向行。在二维数组中,a+i=ai=(a+i)=&ai=&ai0,即它们的地即它们的地址值是相等的。请读者仔细琢磨其概念。请分析址值是相等的。请读者仔细琢磨其概念。请分析下面的程序下面的程序,以加深对上面叙述的理解。以加深对上面叙述的理解。例例10.11输出二维数组有关的值。输出二维数组有关的值。#define FORMAT%D,%Dnmain()int a3
115、4=1,3,5,7,9,11,13,15,17,19,21,23;printf(FORMAT,a,*a);printf(FORMAT,a0,*(a+0);printf(FORMAT,&a0,&a00);printf(FORMAT,a1,a+1);printf(FORMAT,&a10,*(a+1)+0);printf(FORMAT,a2,*(a+2);printf(FORMAT,&a2,a+2);printf(FORMAT,a10,*(*(a+1)+0);运行结果如下:运行结果如下:158,158(第第0行首地址和行首地址和0行行0列元素地址列元素地址)158,158(0行行0列元素地址列元素地
116、址)158,158(0行首地址和行首地址和0行行0元素地址元素地址)166,166(1行行0列元素地址和列元素地址和1行首地址行首地址)166,166(1行行0列元素地址列元素地址)174,174(2行行0列元素地址列元素地址)174,174(第第2行首地址行首地址)9,9(1行行0列元素的值列元素的值)请注意,请注意,a是二维数组名,代表数组首地址,但是不是二维数组名,代表数组首地址,但是不能企图用能企图用*a来得到来得到a00的值。的值。*a相当于相当于*(a+0),即,即a0,它是第,它是第0行地址行地址(本次程序运行时输出本次程序运行时输出a、a0和和*a的值都是的值都是158,都是地
117、址。请注意:每次,都是地址。请注意:每次编译分配的地址是不同的编译分配的地址是不同的)。a是指向一维数组的是指向一维数组的指针,可理解为行指针,指针,可理解为行指针,*a是指向列元素的指针,是指向列元素的指针,可理解为列指针,指向可理解为列指针,指向0行行0列元素,列元素,*a是是0行行0列列元素的值。同样,元素的值。同样,a+1指向第指向第1行首地址,但也不行首地址,但也不能企图用能企图用*(a+1)得到得到a10的值,而应该用的值,而应该用*(a+1) 求求a10元素的值。元素的值。2. 指向多维数组的指针变量指向多维数组的指针变量在了解上面的概念后,可以用指针变量指向多维数在了解上面的概
118、念后,可以用指针变量指向多维数组及其元素。组及其元素。(1) 指向数组元素的指针变量。指向数组元素的指针变量。例例10.12用指针变量输出数组元素的值。用指针变量输出数组元素的值。main() int a34=1,3,5,7,9,11,13,15,17,19,21,23; intp; for(p=a0;pa0+12;p+)if(p-a0)%4=0)printf(n); printf(%4D,*p); 运行结果如下:运行结果如下: 1357 9 11 13 15 17 19 21 23p是一个指向整型变量的指针变量,它可以指向一般是一个指向整型变量的指针变量,它可以指向一般的整型变量,也可以指向
119、整型的数组元素。每次使的整型变量,也可以指向整型的数组元素。每次使p值加值加1,以移向下一元素。,以移向下一元素。if语句的作用是使一行语句的作用是使一行输出输出4个数据,然后换行。如果读者对个数据,然后换行。如果读者对p的值还缺乏的值还缺乏具体概念的话,可以把具体概念的话,可以把p的值的值(即数组元素的地即数组元素的地址址)输出。可将程序最后两个语句改为输出。可将程序最后两个语句改为printf(addr=%o,value=%4dn,p,*p);这时输这时输出如下:出如下:addr=236,value=1 addr=240,value=3 addr=242,value=5 addr=244,
120、value=7 addr=246,value=9 addr=250,value=11 addr=252,value=13 addr=254,value=15 addr=256,value=17 addr=260,value=19 addr=262,value=21 addr=264,value=23注意地址是以八进制数表示的注意地址是以八进制数表示的(输出格式符为输出格式符为%o)。上例是顺序输出数组中各元素之值,比较简单。如果上例是顺序输出数组中各元素之值,比较简单。如果要输出某个指定的数组元素要输出某个指定的数组元素(例如例如a12),则应事先,则应事先计算该元素在数组中的相对位置计算该元
121、素在数组中的相对位置(即相对于数组起始即相对于数组起始位置的相对位移量位置的相对位移量)。计算。计算aij在数组中的相对位在数组中的相对位置的计算公式为置的计算公式为i*m+j其中其中m为二维数组的列数为二维数组的列数(二二维数组大小为维数组大小为nm)。例如,对上述。例如,对上述34的二维数组,的二维数组,它的第它的第2行第行第3列元素列元素(a23)对对a00的相对位置为的相对位置为2*4+3=11。如果开始时使指针变量。如果开始时使指针变量p指向指向a(即即(a00),为了得到,为了得到a23的值,可以的值,可以用用*(p+2*4+3)表示。表示。(p+11)是是a23的地址。的地址。a
122、ij的地址为的地址为a0+i*m+j。下面来说明上述。下面来说明上述(a0+i*m+j)中的中的i*m+j公式的含义。从图公式的含义。从图10.27可可以看到在以看到在aij元素之前有元素之前有i行元素行元素(每行有每行有m个元个元素素),在,在aij所在行,所在行,aij的前面还有的前面还有j个元素,个元素,因此因此aij之前共有之前共有im+j个元素。例如,个元素。例如,a23的前面有两行的前面有两行(共共24=8个个)元素,在它本行内还有元素,在它本行内还有3个元素在它前面个元素在它前面使灿使灿?+3=11个元素在它之前。个元素在它之前。可用可用p+11表示其相对位置。表示其相对位置。图
123、图10.27可以看到,可以看到,C语言规定数组下标从语言规定数组下标从0开始,对计算上开始,对计算上述相对位置比较方便,只要知道述相对位置比较方便,只要知道i和和j的值,就可以的值,就可以直接用直接用im+j公式计算出公式计算出aij相对于数组开头的相对于数组开头的相对位置。如果规定下标从相对位置。如果规定下标从1开始开始(如如FORTRAN语言语言),则为计算则为计算aij的相对位置所用的公式就要改为的相对位置所用的公式就要改为(i-1)m+(j-1)。这就增加了计算的工作量,而且不直。这就增加了计算的工作量,而且不直观。观。(2) 指向由指向由m个元素组成的一维数组的指针变量。个元素组成的
124、一维数组的指针变量。上例的指针变量上例的指针变量p是指向整型变量的,是指向整型变量的,p+1所指向的所指向的元素是元素是p所指向的元素的下一元素。可以改用另一所指向的元素的下一元素。可以改用另一方法,使方法,使p不是指向整型变量,而是指向一个包含不是指向整型变量,而是指向一个包含m个元素的一维数组。这时,如果个元素的一维数组。这时,如果p先指向先指向a0(即即p=&a0),则,则p+1不是指向不是指向a01,而是指向,而是指向a1,p的增值以一维数组的长度为单位,见图的增值以一维数组的长度为单位,见图10.28。图图10.28例例10.13输出二维数组任一行任一列元素的值。输出二维数组任一行任
125、一列元素的值。main() int a34=1,3,5,7,9,11,13,15,17,19,21,23; int (*p)4,i,j; p=a; scanf( i=%d,j=%d,&i,&j); printf(a%d,%d=%dn,i,j,*(*(p+i)+j); 运行情况如下:运行情况如下:i=1,j=2(本行为键盘输入本行为键盘输入)a1,2=13注意应输入注意应输入“i=1,j=2”,以与,以与scanf函数中指定的字函数中指定的字符串相对应。符串相对应。程序第程序第3行行“int(*p)4”表示表示p是一个指针变量,是一个指针变量,它指向包含它指向包含4个元素的一维数组。注意个元素的
126、一维数组。注意*p两侧的括两侧的括号不可缺少,如果写成号不可缺少,如果写成*p4,由于方括号,由于方括号运算运算级别高,因此级别高,因此p先与先与4结合,是数组,然后再与结合,是数组,然后再与前面的前面的*结合,结合,*p4是指针数组是指针数组(见见10.7节节)。有的。有的读者感到读者感到“(*p)4”这种形式不好理解。可以对这种形式不好理解。可以对下面二者做比较:下面二者做比较: int a4;(a有有4个元素,每个元素为整型个元素,每个元素为整型) int (*p)4;第第种形式表示种形式表示*p有有4个元素,每个元素为整型。也个元素,每个元素为整型。也就是就是p所指的对象是有所指的对象
127、是有4个整型元素的数组,即个整型元素的数组,即p是是行指针,见图行指针,见图10.29。应该记住,此时。应该记住,此时p只能指向一只能指向一个包含个包含4个元素的一维数组,个元素的一维数组,p的值就是该一维数的值就是该一维数组的首地址。组的首地址。p不能指向一维数组中的第不能指向一维数组中的第j个元素。个元素。图图10.2910.29程序中的程序中的p+i是二维数组是二维数组a的第的第i行的地址行的地址(由于由于p是指是指向一维数组的指针变量,因此向一维数组的指针变量,因此p加加1,就指向下一,就指向下一个一维数组个一维数组)。见图。见图10.30。*(p+2)+3是是a数组第数组第2行行第第
128、3列元素地址,这是指向列的指针,列元素地址,这是指向列的指针,(p+2)+3)是是a23的值。有的读者可能会想,的值。有的读者可能会想,(p+2)是第是第2行行0列元素的地址,而列元素的地址,而p+2是第是第2行首地址,二者的值行首地址,二者的值相同,相同,(p+2)+3能否写成能否写成(p+2)+3呢?显然不行。呢?显然不行。因为因为(p+2)+3就成了就成了(p+5)了,是第了,是第5行的首地址了。行的首地址了。对对“(p+2)+3”,括弧中的,括弧中的2是以一维数组的长是以一维数组的长度为单位的,即度为单位的,即p每加每加1,地址就增加,地址就增加8个字节个字节(4个个元素,每个元素元素
129、,每个元素2个字节个字节),而,而(p+2)+3括弧外的数括弧外的数字字3,不是以,不是以p所指向的一维数组为长度单位的。所指向的一维数组为长度单位的。而是采用而是采用p所指向的一维数组内部各元素的长度单所指向的一维数组内部各元素的长度单位了,位了,加加3就是加就是加(32)个字节。个字节。p+2和和(p+2)具有相同的具有相同的值,但值,但(p+2)+3和和(p+2)+3的值就不相同了。这的值就不相同了。这和上一节所叙述的概念是一致的。和上一节所叙述的概念是一致的。图图10.303. 多维数组的指针作函数参数多维数组的指针作函数参数一维数组的地址可以作为函数参数传递,多维数组一维数组的地址可
130、以作为函数参数传递,多维数组的地址也可作函数参数传递。在用指针变量作形的地址也可作函数参数传递。在用指针变量作形参以接受实参数组名传递来的地址时,有两种方参以接受实参数组名传递来的地址时,有两种方法:法: 用指向变量的指针变量用指向变量的指针变量; 用指向一维数用指向一维数组的指针变量。组的指针变量。例例10.14有一个班,有一个班,3个学生,各学个学生,各学4门课,计算总平门课,计算总平均分数,以及第均分数,以及第n个学生的成绩。个学生的成绩。这个题目本来是很简单的。只是为了说明用多维数这个题目本来是很简单的。只是为了说明用多维数组指针作函数参数而举的例子。用函数组指针作函数参数而举的例子。
131、用函数average求求总平均成绩,用函数总平均成绩,用函数search找出并输出第找出并输出第i个学生个学生的成绩。程序如下:的成绩。程序如下:main() void average(float *p,int n); void search(float (*p)4,int n); float score34=65,67,70,60,80,87,90,81,90,99,100,98; average(*score,12);/*求求12个分数的平均分个分数的平均分*/ search(score,2); /*求第求第2个学生成绩个学生成绩*/ void average(float *p,int n
132、) floatp-end; float sum=0,aver; p-end=p+n-1; for(;p=p-end;p+) sum=sum+(*p); aver=sum/n; printf(average=%5.2fn,aver); void search(float (*p)4,int n) int i; printf(the score of No.%D are:n,n); for(i=0;i4;i+) printf(%5.2f,(*(p+n)+i); 程序运行结果如下:程序运行结果如下:average=82.25the score of No.2 are:90.0099.00100.00
133、98.00在函数在函数main中,先调用中,先调用average函函数以求总平均值。在函数数以求总平均值。在函数average中形参中形参p被声明为指向一个实型被声明为指向一个实型变量的指针变量。用变量的指针变量。用p指向二维指向二维数组的各个元素,数组的各个元素,p每加每加1就改为就改为指向下一个元素,见图指向下一个元素,见图10.31。相。相应的实参用应的实参用*score,即,即score0,它是一个地址,指向它是一个地址,指向score00 元素。用形参元素。用形参n代表需要求平均代表需要求平均值的元素的个数,实参值的元素的个数,实参12表示要表示要求求12个元素值的平均值。函数个元素
134、值的平均值。函数average中的指针变量中的指针变量p指向指向score数组的某一元素数组的某一元素(元素值为元素值为图图10.31一门课的成绩一门课的成绩)。sum是累计总分,是累计总分,aver是平均值。是平均值。在函数中输出在函数中输出aver的值,故函数无需返回值。的值,故函数无需返回值。函数函数search的形参的形参p不是指向一般实型变量的指针变不是指向一般实型变量的指针变量,而是指向包含量,而是指向包含4个元素的一维数组的指针变量。个元素的一维数组的指针变量。实参传给形参实参传给形参n的值为的值为2,即找序号为,即找序号为2的学生的成的学生的成绩绩(3个学生的序号分别为个学生的
135、序号分别为0、1、2)。函数调用开。函数调用开始时,将实参始时,将实参score的值的值(代表该数组第代表该数组第0行首地址行首地址)传给传给p,使,使p也等于也等于score。p+n是一维数组是一维数组scoren的首地址,的首地址,*(p+n)+i是是scoreni的地址,的地址,*(*(p+n)+i)是是scoreni的值。现在的值。现在n=2,i由由0变变到到3,for循环输出循环输出score20到到score23的值。的值。例例10.15在上题基础上,查找有一门以上课程不及格在上题基础上,查找有一门以上课程不及格的学生,打印出他们的全部课程的成绩。程序如的学生,打印出他们的全部课程
136、的成绩。程序如下:下:main() void search(float (*p)4,int n); /*函数声明函数声明*/ float score34=65,57,70,60,58,87,90,81,90,99,100,98; search(score,3); void search(float (*p)4,int n) int i,j,flag; for(j=0;jn;j+)flag=0; for(i=0;i4;i+) if(*(*(p+j)+i)60)flag=1;Break; if(flag=1) printf(No.%D fails,his scores are:n,j+1); fo
137、r(i=0;i4;i+) printf(%5.1f,*(*(p+j)+i); printf(n); 程序运行结果如下:程序运行结果如下:No.1 fails,his scores are:65.057.070.060.0No.2 fails,his scores are:58.0 87.0 90.0 81.0在函数在函数search中,中,flag是作为标志不及格的变量。先是作为标志不及格的变量。先使使flag=0,若发现某一学生有一门不及格,则使,若发现某一学生有一门不及格,则使flag=1。最后用。最后用if语句检查语句检查flag,如为,如为1,则表示该,则表示该学生有不及格的纪录,输出
138、其全部课程成绩。变学生有不及格的纪录,输出其全部课程成绩。变量量j代表学生号,代表学生号,i代表课程号。代表课程号。通过指针变量存取数组元素速度快,且程序简明。通过指针变量存取数组元素速度快,且程序简明。用指针变量作形参,可以允许数组的行数不同。用指针变量作形参,可以允许数组的行数不同。因此数组与指针常常是紧密联系的,使用熟练的因此数组与指针常常是紧密联系的,使用熟练的话可以使程序质量提高,且编写程序方便灵活。话可以使程序质量提高,且编写程序方便灵活。10.4.1 字符串的表示形式字符串的表示形式在在C程序中,可以用两种方法访问一个字符串。程序中,可以用两种方法访问一个字符串。(1) 用字符数
139、组存放一个字符串,然后输出该字符用字符数组存放一个字符串,然后输出该字符串。串。例例10.16main() char string=I love China!; printf(%Sn,string); 10.4 字符串的指针和指向字符串的指针变量运行时输出:运行时输出:I love China!和前面介绍的数组属性一样,和前面介绍的数组属性一样,string是数组名,它代表字是数组名,它代表字符数组的首地址符数组的首地址(见图见图10.32)。 string4代表数组代表数组中序号为中序号为4的元素的元素(v),实,实际上际上string4就是就是*(string+4),string+4是是一
140、个地址,它指向字符一个地址,它指向字符“v”。图图10.3210.32(2) 用字符指针指向一个字符串。用字符指针指向一个字符串。可以不定义字符数组,而定义一个字符指针。用可以不定义字符数组,而定义一个字符指针。用字符指针指向字符串中的字符。字符指针指向字符串中的字符。例例10.17main() charstring=I love China!; printf(%sn,string); 在这里没有定义字符数组,在程序中定义了一个字符在这里没有定义字符数组,在程序中定义了一个字符指针变量指针变量string。给定一个字符串常量。给定一个字符串常量“I love China!” ,C语言对字符串常
141、量是按字符数组处理的,语言对字符串常量是按字符数组处理的,在内存开辟了一个字符数组用来在内存开辟了一个字符数组用来存放字符串常量。程序在定义字存放字符串常量。程序在定义字符指针变量符指针变量string时把字符串首地时把字符串首地址址(即存放字符串的字符数组的首即存放字符串的字符数组的首地址地址)赋给赋给string(见图见图10.33)。有。有人认为人认为string是一个字符串变量,是一个字符串变量,以为是在定义时把以为是在定义时把“I love China!”赋给该字符串变量,这是不对赋给该字符串变量,这是不对的。定义的。定义string的部分的部分charstring=I love C
142、hina!;等价于下面两行:等价于下面两行:charstring;string=I love China!;图图10.33可以看到可以看到string被定义为一个指针变量,指向字符型被定义为一个指针变量,指向字符型数据,请注意它只能指向一个字符变量或其他字数据,请注意它只能指向一个字符变量或其他字符类型数据,不能同时指向多个字符数据,更不符类型数据,不能同时指向多个字符数据,更不是把是把“I love China!”这些字符存放到这些字符存放到string中中(指针变量只能存放地址指针变量只能存放地址),也不是把字符串赋给,也不是把字符串赋给*string。只是把。只是把“I love Chi
143、na!”的首地址赋的首地址赋给指针变量给指针变量string。不要认为上述定义行等价于。不要认为上述定义行等价于charstring;*string=I love China!;在输出时,用在输出时,用printf(%sn,string);%s示输出一个字符串,给出字符指针变量名示输出一个字符串,给出字符指针变量名string,则系统先输出它所指向的一个字符数据,然后自则系统先输出它所指向的一个字符数据,然后自动使动使string加加1,使之指向下一个字符,然后再输,使之指向下一个字符,然后再输出一个字符出一个字符如此直到遇到字符串结束标志如此直到遇到字符串结束标志0为止。注意,在内存中,字符
144、串的最后被为止。注意,在内存中,字符串的最后被自动加了一个自动加了一个0(如图如图10.33所示所示),因此在输,因此在输出时能确定字符串的终止位置。出时能确定字符串的终止位置。通过字符数组名或字符指针变量可以输出一个字符通过字符数组名或字符指针变量可以输出一个字符串。而对一个数值型数组,是不能企图用数组名串。而对一个数值型数组,是不能企图用数组名输出它的全部元素的。如:输出它的全部元素的。如:int i10printf(%Dn,i);是不行的,只能逐个元素输出。是不行的,只能逐个元素输出。显然,用显然,用%S可以对一个字符串进行整体的输入输出。可以对一个字符串进行整体的输入输出。对字符串中字
145、符的存取,可以用下标方法,也可以对字符串中字符的存取,可以用下标方法,也可以用指针方法。用指针方法。例例10.18将字符串将字符串a复制为字符串复制为字符串b。main() char a=i am a boy.,b20; int i; for(i=0;*(a+i)!=0;i+) *(b+i)=*(a+i); *(b+i)=0; printf(string a is:%sn,a); printf(string b is:); for(i=0;bi!=0;i+) printf(%c,bi); printf(n); 程序运行结果为:程序运行结果为:string a is:I am a Boy.str
146、ing b is:I am a Boy.程序中程序中a和和B都定义为字符数组,可以通过地址访问数组都定义为字符数组,可以通过地址访问数组元素。在元素。在for语句中,先检查语句中,先检查ai是否为是否为0(今今ai是以是以*(a+i)形式表示的形式表示的)。如果不等于。如果不等于0,表示字,表示字符串尚未处理完,就将符串尚未处理完,就将ai的值赋给的值赋给Bi,即复制一个,即复制一个字符。在字符。在for循环中将循环中将a串全部复制给了串全部复制给了B串。最后还应串。最后还应将将 0复制过去,故有复制过去,故有(B+i)=0;此时此时i的值是字符的值是字符串有效字符的个数串有效字符的个数n加加
147、1。第二个。第二个for循环中用下标法表循环中用下标法表示一个数组元素示一个数组元素(即一个字符即一个字符)。也可以设指针变量,用它的值的改变来指向字符串中的也可以设指针变量,用它的值的改变来指向字符串中的不同的字符。例不同的字符。例10.19用指针变量来处理例用指针变量来处理例10.18问题。问题。main() char a=I am a Boy.,B20,*p1,*p2; int i; p1=a;p2=B; for(;*p1!=0;p1+,p2+) *p2=*p1; *p2=0; printf(string a is:%Sn,a); printf(string B is:); for(i=
148、0;Bi!=0;i+) printf(%C,Bi); printf(n); p1、p2是指针变量,它指向是指针变量,它指向字符型数据。先使字符型数据。先使p1和和p2的的值分别为字符串值分别为字符串a和和B的首地的首地址。址。*p1最初的值为最初的值为I,赋值语句赋值语句“*p2=*p1;”的作的作用是将字符用是将字符I(a串中第串中第1个字符个字符)赋给赋给p2所指向的元所指向的元素,即素,即B1。然后。然后p1和和p2分分别加别加1,指向其下面的一个,指向其下面的一个元素,直到元素,直到*p1的值为的值为0止。注意止。注意p1和和p2的值的值是不断在改变的,见图是不断在改变的,见图10.3
149、4的虚线和的虚线和p1、p2。程。程序必须保证使序必须保证使p1和和p2同步移同步移动。动。图图10.3410.4.2 字符串指针作函数参数字符串指针作函数参数将一个字符串从一个函数传递到另一个函数,可以将一个字符串从一个函数传递到另一个函数,可以用地址传递的办法,即用字符数组名作参数或用用地址传递的办法,即用字符数组名作参数或用指向字符串的指针变量作参数。在被调用的函数指向字符串的指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以得中可以改变字符串的内容,在主调函数中可以得到改变了的字符串。到改变了的字符串。例例10.20用函数调用实现字符串的复制。用函数调用实现字符串的
150、复制。(1) 用字符数组作参数。用字符数组作参数。void copy-string(char from , char to ) int i=0; while(fromi!=0) toi=fromi;i+; toi=0; main() char a=I am a teacher.; char B=you are a student.; printf(string a=%snstring b=%sn,a,b); copy-string(a,b); printf(nstring a=%Snstring b=%sn,a,b); 程序运行结果如下:程序运行结果如下:string-a=I am a tea
151、cher. string-b=you are a Student. string-a=I am a teacher. string-b=I am a teacher.a和和b是字符数组。初值如图是字符数组。初值如图10.35(a)所示。所示。copy-string函数的作用是将函数的作用是将fromi赋给赋给toi,直到,直到fromi的值是的值是0为止。在调用为止。在调用copy-string函函数时,将数时,将a和和b的首地址分别传递给形参数组的首地址分别传递给形参数组from和和to。因此。因此fromi和和ai是同一个单元,是同一个单元,toi和和Bi是同一个单元。程序执行完以后,是同
152、一个单元。程序执行完以后,B数组的内容如数组的内容如图图10.35(B)所示。可以看到,由于所示。可以看到,由于B数组原来的长数组原来的长度大于度大于a数组,因此在将数组,因此在将a数组复制到数组复制到b数组后,未数组后,未能全部覆盖能全部覆盖b数组原有内容。数组原有内容。b数组最后数组最后3个元素仍个元素仍保留原状。在输出保留原状。在输出B时由于按时由于按%S(字符串字符串)输出,遇输出,遇0即告结束,因此第一个即告结束,因此第一个0后的字符不输后的字符不输出。如果不采取出。如果不采取%S格式输出而用格式输出而用%C逐个字符输出逐个字符输出是可以输出后面这些字符的。是可以输出后面这些字符的。
153、图图10.35在在main函数中也可以不定义字符数组,而用字符型函数中也可以不定义字符数组,而用字符型指针变量。指针变量。main函数可改写如下:函数可改写如下:main() char *a=I am a teacher.; char *b=you are a student.; printf(string a=%snstring b=%sn,a,b); Copy-string(a,b); printf(nstring a=%snstring b=%sn,a,b); 与上面程序运行结果相同。与上面程序运行结果相同。(2) 形参用字符指针变量。程序如下:形参用字符指针变量。程序如下:void c
154、opy-string(char *from,char *to) for(;*from!=0;from+,to+) *to=from; *to=0; main() char*a=I am a teacher.; char*b=you are a student.; printf(nstring a=%Snstring b=%Sn,a,b); copy-string(a,b); printf(nstring a=%Snstring b=%sn,a,b); 形参形参from和和to是字符指针变量。它们相当于例是字符指针变量。它们相当于例10.19中中的的p1和和p2。算法也与例。算法也与例10.19
155、完全相同。在调用完全相同。在调用Copy-string时,将数组时,将数组a的首地址传给的首地址传给from,把数组,把数组B的首地址传给的首地址传给to。在函数。在函数Copy-string中的中的for循环中循环中,每次将,每次将*from赋给赋给*to,第,第1次就是将次就是将a数组中第数组中第1个个字符赋给字符赋给B数组的第数组的第1个字符。在执行个字符。在执行from+和和to+以后,以后,from和和to就分别指向就分别指向a1和和b1。再执行。再执行*to=*from,就将,就将a1赋给赋给B1最后将最后将0赋给赋给*to,注,注意此时意此时to指向哪个单元。指向哪个单元。(3)
156、 对对Copy-string函数还可作简化。函数还可作简化。 将将Copy-string函数改写为函数改写为void Copy-string(char *from,char *to) while(*to=*from)!=0) to+;from+; 请与上面一个程序对比。在本程序中将请与上面一个程序对比。在本程序中将“*to=*from”的操作放在的操作放在while语句的表达式中,语句的表达式中,而且把赋值运算而且把赋值运算和判断是否为和判断是否为0的运算放在一个表达式中,的运算放在一个表达式中,先赋值后判断。在循环体中使先赋值后判断。在循环体中使to和和from增值,指增值,指向下一个元素向
157、下一个元素直到直到*from的值为的值为0为止。为止。 Copy-string函数的函数体还可改为函数的函数体还可改为 while(*to+=*from+)!=0); 把上面程序的把上面程序的to+和和from+运算与运算与*to=*from合并,合并,它的执行过程是,先将它的执行过程是,先将*from赋给赋给*to,然后使,然后使to和和from增值。显然这又简化了。增值。显然这又简化了。 函数体还可写成函数体还可写成 while(*from!=0) *to+=*from+; to=0; 当当*from不为不为0时,将时,将*from赋给赋给*to,然后使,然后使to和和from增值。增值。
158、字符可以用其字符可以用其ASCII码来代替。例如,码来代替。例如,“Ch=a”可以用可以用“Ch=97”代替,代替,“while(Ch!=a)”可以用可以用“while(Ch!=97)”代替。因此,代替。因此,“while(*from!=0)”可以用可以用“while(*from!=0)”代替代替(0的的ASCII代码为代码为0)。而关系表达。而关系表达式式“*from!=0”又可简化为又可简化为“*from”,这是因为,这是因为若若*form的值不等于的值不等于0,则表达式,则表达式“*from”为真,为真,同时同时“*from!=0”也为真。因此也为真。因此“while(*from!=0)
159、”和和“while(*from)”是等价的。所以函数体是等价的。所以函数体可简化为可简化为while(*from) *to+=*from+; *to=0; 上面的上面的while语句还可以进一步简化为下面的语句还可以进一步简化为下面的while语句:语句:while(*to+=*from+);它与下面语句它与下面语句等价:等价:while(*to+=*from+)!=0);将将*from赋给赋给*to,如果赋值后的,如果赋值后的*to值等于值等于0,则循环终止则循环终止(0已赋给已赋给*to)。 函数体中函数体中while语句也可以改用语句也可以改用for语句:语句:for(;(*to+=*f
160、rom+)!=0;);或或for(;*to+=*from+;); 也可用指针变量,函数也可用指针变量,函数Copy-string可写为可写为void Copy-string(char from ,char to )charp1,*p2; p1=from;p2=to; while(*p2+=*p1+)!=0); 以上各种用法,变化多端,使用十分灵活,初看起以上各种用法,变化多端,使用十分灵活,初看起来不太习惯,含义不直观。初学者会有些困难,来不太习惯,含义不直观。初学者会有些困难,也容易出错。但对也容易出错。但对C熟练之后,以上形式的使用是熟练之后,以上形式的使用是比较多的,读者应逐渐熟悉它,掌
161、握它。比较多的,读者应逐渐熟悉它,掌握它。10.4.3 对使用字符指针变量和字符数组的讨论对使用字符指针变量和字符数组的讨论虽然用字符数组和字符指针变量都能实现字符串的虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不应存储和运算,但它们二者之间是有区别的,不应混为一谈,主要有以下几点:混为一谈,主要有以下几点:(1) 字符数组由若干个元素组成,每个元素中放一字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址个字符,而字符指针变量中存放的是地址(字符串字符串的首地址的首地址),决不是将字符串放到字符指针变量中。,决不是将字符串放到字符
162、指针变量中。(2) 赋值方式。对字符数组只能对各个元素赋值,赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值。不能用以下办法对字符数组赋值。char str14;str=I love China!;而对字符指针变量,可以采用下面方法赋值:而对字符指针变量,可以采用下面方法赋值:chara;a=I love China!;但注意赋给但注意赋给a的不是字符,而是字符串的首地址。的不是字符,而是字符串的首地址。(3) 对字符指针变量赋初值:对字符指针变量赋初值:chara=I love China!;等价于等价于chara;a=I love China!;而对数组的初始化:而对数
163、组的初始化:char str14=I love China!;不能等价于不能等价于char str14;str=I love China!;即数组可以在变量定义时整体赋初值,但不能在赋即数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值。值语句中整体赋值。(4) 如果定义了一个字符数组,在编译时为它分配如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可针变量时,给指针变量分配内存单元,在其中可以放一个地址值,也就是说,该指针变量可以指以放一个地址值,也就是说,该指针变量可
164、以指向一个字符型数据,但如果未对它赋予一个地址向一个字符型数据,但如果未对它赋予一个地址值,则它并未具体指向一个确定的字符数据。如值,则它并未具体指向一个确定的字符数据。如char str10;scanf(%S,str);是可以的。而常有人用下面的方法:是可以的。而常有人用下面的方法:chara;scanf(%S,a);目的是输入一个字符串,虽然一般也能运行,但这种目的是输入一个字符串,虽然一般也能运行,但这种方法是危险的,不宜提倡。因为编译时虽然给指针方法是危险的,不宜提倡。因为编译时虽然给指针变量变量a分配了内存单元,分配了内存单元,a的地址的地址(即即&a)是已指定了,是已指定了,但但a
165、的值并未指定,在的值并未指定,在a单元中是一个不可预料的值。单元中是一个不可预料的值。在执行在执行scanf函数时要求将一个字符串输入到函数时要求将一个字符串输入到a所指所指向的一段内存单元向的一段内存单元(即以即以a的值的值(地址地址)开始的一段内开始的一段内存单元存单元)中。而中。而a的值如今却是不可预料的,它的值如今却是不可预料的,它可能指向内存中空白的可能指向内存中空白的(未用的未用的)用户存储区中用户存储区中(这这是好的情况是好的情况),也有可能指向已存放指令或数据的,也有可能指向已存放指令或数据的有用内存段,这就会破坏了程序,甚至破坏了系有用内存段,这就会破坏了程序,甚至破坏了系统
166、,会造成严重的后果。在程序规模小时,由于统,会造成严重的后果。在程序规模小时,由于空白地带多,往往可以正常运行,而程序规模大空白地带多,往往可以正常运行,而程序规模大时,出现上述时,出现上述“冲突冲突”的可能性就大多了。应当的可能性就大多了。应当这样:这样:chara,str10;a=str;scanf(%S,a);先使先使a有确定值,也就是使有确定值,也就是使a指向一个数组的开头,指向一个数组的开头,然后输入一个字符串,把它存放在以该地址开始然后输入一个字符串,把它存放在以该地址开始的若干单元中。的若干单元中。(5) 指针变量的值是可以改变的,如:指针变量的值是可以改变的,如:例例10.21
167、main() chara=I love China!; a=a+7; printf(%S,a); 运行结果如下:运行结果如下:China!指针变量指针变量a的值可以变化,输出字符串时从的值可以变化,输出字符串时从a当时所当时所指向的单元开始输出各个字符,直到遇指向的单元开始输出各个字符,直到遇0为为止。止。而数组名虽然代表地址,但它的值是不能改变的。而数组名虽然代表地址,但它的值是不能改变的。下面是错的:下面是错的:charstr=I loveChina!; str=str+7; printf(%s,str);需要说明,若定义了一个指针变量,并使它指向一需要说明,若定义了一个指针变量,并使它指
168、向一个字符串,就可以用下标形式引用指针变量所指个字符串,就可以用下标形式引用指针变量所指的字符串中的字符。如:的字符串中的字符。如:例例10.22main() chara=I love China!; int i; printf(The Sixth character is %Cn,a5); for(i=0;ai!=0;i+) printf(%C,ai); 运行结果如下:运行结果如下:The sixth character is eI love China!程序中虽然并未定义数组程序中虽然并未定义数组a,但字符串在内存中是以字符数,但字符串在内存中是以字符数组形式存放的。组形式存放的。a5按按
169、*(a+5)执行,即从执行,即从a当前所指向的元当前所指向的元素下移素下移5个元素位置,取出其单元中的值。个元素位置,取出其单元中的值。(6) 用指针变量指向一个格式字符串,可以用它代用指针变量指向一个格式字符串,可以用它代替替printf函数中的格式字符串。如:函数中的格式字符串。如:charformat;format=a=%D,B=%fn;printf(format,a,B);它相当于它相当于printf(a=%D,B=%fn,a,B);因此只要改变指针变量因此只要改变指针变量format所指向的字符串,就所指向的字符串,就可以改变输入输出的格式。这种可以改变输入输出的格式。这种print
170、f函数称为可函数称为可变格式输出函数。变格式输出函数。也可以用字符数组实现。如:也可以用字符数组实现。如:char format=a=%D,B=%fn;printf(format,a,B);但由于不能采用赋值语句对数组整体赋值,如:但由于不能采用赋值语句对数组整体赋值,如:char format;format=a=%D,B=%Dn;因此用指针变量指向字符串的方式更为方便。因此用指针变量指向字符串的方式更为方便。10.5.1 用函数指针变量调用函数用函数指针变量调用函数可以用指针变量指向整型变量、字符串、数组,也可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译时被分配
171、给可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个入口地址就称为函数的指针。一个入口地址。这个入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针可以用一个指针变量指向函数,然后通过该指针变量调用此函数。先通过一个简单的例子来回顾变量调用此函数。先通过一个简单的例子来回顾一下函数的调用情况。一下函数的调用情况。例例10.23求求a和和b中的大者。先列出按一般方法的程序。中的大者。先列出按一般方法的程序。10.5 函数的指针和指向函数的指针变量main() int max(int,int); int a,b,c; scanf(%d,%d,&a,&b); c=max(a
172、,b); printf(a=%d,b=%d,max=%d,a,b,c); max(int x,int y) int z; if(xy)z=x; else z=y; return(z);main函数中的函数中的“C=max(a,B);”包括了一次函数调用包括了一次函数调用(调用调用max函数函数)。每一个函数都占用一段内存单。每一个函数都占用一段内存单元,它们有一个起始地址。因此,可以用一个指元,它们有一个起始地址。因此,可以用一个指针变量指向一个函数,通过指针变量来访问它指针变量指向一个函数,通过指针变量来访问它指向的函数。向的函数。将将main函数改写为函数改写为main() int max
173、(int,int); int(*p)(); int a,b,C; p=max; scanf(%D,%D,&a,&B); C=(*p)(a,b); printf(a=%d,b=%d,max=%d,a,b,c); 其中其中int(*p)()定义定义p是一个指向函数的指针变量,此是一个指向函数的指针变量,此函数带回整型的返回值。注意函数带回整型的返回值。注意*p两侧的括弧不可省两侧的括弧不可省略,表示略,表示p先与先与*结合,是指针变量,然后再与后结合,是指针变量,然后再与后面的面的()结合,表示此指针变量指向函数,这个函结合,表示此指针变量指向函数,这个函数值数值(即函数返回的值即函数返回的值)是
174、整型的。如果写成是整型的。如果写成“int p()”,则由于,则由于()优先级高于优先级高于*,它就成了声明一个,它就成了声明一个函数了函数了(这个函数的返回值是指向整型变量的指针这个函数的返回值是指向整型变量的指针)。赋值语句。赋值语句“p=max;”的作用是将函数的作用是将函数max的入的入口地址赋给指针变量口地址赋给指针变量p。和数组名代表数组起始地。和数组名代表数组起始地址一样,函数名代表该函数的入口地址。这时,址一样,函数名代表该函数的入口地址。这时,p就是指向函数就是指向函数max的指针变量,也就是的指针变量,也就是p和和max都都指向函数的开头,见图指向函数的开头,见图10.36
175、。调用。调用*p就是调用函就是调用函数数max。请注意。请注意p是指向函数的指针变量,它只能是指向函数的指针变量,它只能指向函数的入口处而不可能指向函数中间的某一指向函数的入口处而不可能指向函数中间的某一条指令处,因此不能用条指令处,因此不能用*(p+1)来表示函数的下一来表示函数的下一条指令。条指令。图图10.36在在main函数中有一个赋值语句函数中有一个赋值语句c=(*p)(a,b);它包括函数的调用,和它包括函数的调用,和“C=max(a,B);”等价。这就等价。这就是用指针形式实现函数的调用。以上用两种方法是用指针形式实现函数的调用。以上用两种方法实现函数的调用,结果是一样的。实现函
176、数的调用,结果是一样的。说明:说明:(1) 指向函数的指针变量的一般定义形式为数据类指向函数的指针变量的一般定义形式为数据类型型 (*指针变量名指针变量名)();这里的这里的“数据类型数据类型”是指函是指函数返回值的类型。数返回值的类型。(2) 函数的调用可以通过函数名调用,也可以通过函数的调用可以通过函数名调用,也可以通过函数指针调用函数指针调用(即用指向函数的指针变量调用即用指向函数的指针变量调用)。(3) (*p)()表示定义一个指向函数的指针变量,它不表示定义一个指向函数的指针变量,它不是固定指向哪一个函数的,而只是表示定义了这样是固定指向哪一个函数的,而只是表示定义了这样一个类型的变
177、量,它是专门用来存放函数的入口地一个类型的变量,它是专门用来存放函数的入口地址的。在程序中把哪一个函数的地址赋给它,它就址的。在程序中把哪一个函数的地址赋给它,它就指向哪一个函数。在一个程序中,一个指针变量可指向哪一个函数。在一个程序中,一个指针变量可以先后指向不同的函数。以先后指向不同的函数。(4) 在给函数指针变量赋值时,只需给出函数名而不在给函数指针变量赋值时,只需给出函数名而不必给出参数,如必给出参数,如:p=max;因为是将函数入口地址赋因为是将函数入口地址赋给给p,而不牵涉到实参与形参的结合问题。不能写,而不牵涉到实参与形参的结合问题。不能写成成“p=max(a,B);”形式。形式
178、。(5) 用函数指针变量调用函数时,只需将用函数指针变量调用函数时,只需将(*p)代替函代替函数名即可数名即可(p为指针变量名为指针变量名),在,在(*p)之后的括弧中之后的括弧中根据需要写上实参。如下面语句表示:根据需要写上实参。如下面语句表示:“调用由调用由p指向的函数,实参为指向的函数,实参为a,B。得到的函数值赋给。得到的函数值赋给C。”C=(*p)(a,B);注意函数的返回值是什么类型?从上例对注意函数的返回值是什么类型?从上例对指针变量指针变量p的定义可以知道,函数的返回值是整型的定义可以知道,函数的返回值是整型的,因此将其值赋给整型变量的,因此将其值赋给整型变量C是合法的。是合法
179、的。(6) 对指向函数的指针变量,像对指向函数的指针变量,像p+n、p+、p-等运等运算是无意义的。算是无意义的。10.5.2 用指向函数的指针作函数参数用指向函数的指针作函数参数函数指针变量常用的用途之一是把指针作为参数传函数指针变量常用的用途之一是把指针作为参数传递到其他函数。这个问题是递到其他函数。这个问题是C语言应用的一个比较语言应用的一个比较深入的部分,在本书中只作简单的介绍,以便在深入的部分,在本书中只作简单的介绍,以便在今后用到时不致感到困惑。进一步的理解和掌握今后用到时不致感到困惑。进一步的理解和掌握有待于读者今后深入的学习和提高。有待于读者今后深入的学习和提高。以前介绍过,函
180、数的参数可以是变量、指向变量的以前介绍过,函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等。现指针变量、数组名、指向数组的指针变量等。现在介绍指向函数的指针也可以作为参数,以便实在介绍指向函数的指针也可以作为参数,以便实现函数地址的传递,也就是将函数名传给形参。现函数地址的传递,也就是将函数名传给形参。下一章还要介绍用指向结构体的指针作函数参数。下一章还要介绍用指向结构体的指针作函数参数。它的原理可以简述如下:有一个函数它的原理可以简述如下:有一个函数(假设函数名为假设函数名为Sub),它有两个形参它有两个形参(x1和和x2),定义,定义x1和和x2为指向函数的指针变为指
181、向函数的指针变量。在调用函数量。在调用函数Sub时,实参用两个函数名时,实参用两个函数名f1和和f2给形参传给形参传递函数地址。这样在函数递函数地址。这样在函数SuB中就可以调用中就可以调用f1和和f2函数了。函数了。如:如:实参函数名实参函数名 f1 f2 Sub(int (*x1)(int ),int (*x2)(int,int )/*定义定义x1、x2为函数指为函数指针变量,针变量,x1指向的指向的*/ /*函数有一个整型形参,函数有一个整型形参,x2指向的函数有两个整型形参指向的函数有两个整型形参*/int a,B,i=1,j=2; a=(*x1)(i); /*调用调用f1函数函数*/
182、 B=(*x2)(i,j); /*调用调用f2函数函数*/其中其中i和和j是函数是函数f1和和f2所要求的参数。函数所要求的参数。函数Sub的形的形参参x1、x2(指针变量指针变量)在函数在函数Sub未被调用时并不占未被调用时并不占内存单元,也不指向任何函数。在内存单元,也不指向任何函数。在Sub被调用时,被调用时,把实参函数把实参函数f1和和f2的入口地址传给形参指针变量的入口地址传给形参指针变量x1和和x2,使,使x1和和x2指向函数指向函数f1和和f2,见图,见图10.37。这。这时,在函数时,在函数Sub中,用中,用*x1和和*x2就可以调用函数就可以调用函数f1和和f2。(*x1)(
183、i)就相当于就相当于f1(i),(*x2)(i,j)就相当于就相当于f2(i,j)。图图10.37有人可能会问,既然在有人可能会问,既然在Sub函数中要调用函数中要调用f1和和f2函数,函数,为什么不直接调用为什么不直接调用f1和和f2而要用函数指针变量呢?而要用函数指针变量呢?何必绕这样一个圈子呢?的确,如果只是用到何必绕这样一个圈子呢?的确,如果只是用到f1和和f2,完全可以直接在,完全可以直接在Sub函数中直接调用函数中直接调用f1和和f2,而不必设指针变量,而不必设指针变量x1、x2。但是,如果在每次。但是,如果在每次调用调用Sub函数时,要调用的函数不是固定的,这次函数时,要调用的函
184、数不是固定的,这次调用调用f1和和f2,而下次要调用,而下次要调用f3和和f4,第三次要调用,第三次要调用的是的是f5和和f6。这时,用指针变量就比较方便了。只。这时,用指针变量就比较方便了。只要在每次调用要在每次调用Sub函数时给出不同的函数名作为实函数时给出不同的函数名作为实参即可,参即可,Sub函数不必作任何修改。这种方法是符函数不必作任何修改。这种方法是符合结构化程序设计方法原则的,是程序设计中常合结构化程序设计方法原则的,是程序设计中常使用的。使用的。下面通过一个简单的例子来说明这种方法的应用。下面通过一个简单的例子来说明这种方法的应用。例例10.24设一个函数设一个函数proces
185、s,在调用它的时候,每次,在调用它的时候,每次实现不同的功能。输入实现不同的功能。输入a和和B两个数,第一次调用两个数,第一次调用process时找出时找出a和和b中大者,第二次找出其中小者,中大者,第二次找出其中小者,第三次求第三次求a与与b之和。之和。程序如下:程序如下:main() int max(int,int); /* 函数声明函数声明 */ int min(int,int); /* 函数声明函数声明 */ int add(int,int); /* 函数声明函数声明 */ int a,b; printf(enter a and b:); scanf(%d,%d,&a,&b); pri
186、ntf(max=); process(a,b,max); printf(min=); process(a,b,min);printf(sum=);process(a,b,add); max(int x,int y) /* 函数定义函数定义 */ int Z; if(xy)Z=x; else Z=y; return(Z); min(int x,int y) /* 函数定义函数定义 */ int Z; if(xy)Z=x; else Z=y; return(Z); add(int x,int y) /* 函数定义函数定义 */ int Z; Z=x+y; return(Z); process(in
187、t x,int y,int (*fun)(int,int) /* 函数定义。函数定义。int (*fun)(int,int) 表示表示fun是指向函数的指针,该是指向函数的指针,该函数是一个整型函数,有两个整型形参函数是一个整型函数,有两个整型形参*/int reSult; result=(*fun)(x,y); printf(%Dn,result); 运行情况如下:运行情况如下:enter a and B:2,6max=6min=2sum=8max、min和和add是已定义的是已定义的3个函数,分别用来实现个函数,分别用来实现求大数、求小数和求和的功能。在求大数、求小数和求和的功能。在mai
188、n函数中第一函数中第一次调用次调用process函数时,除了将函数时,除了将a和和B作为实参将两作为实参将两个数传给个数传给process的形参的形参x、y外,还将函数名外,还将函数名max作作为实参将其入口地址传送给为实参将其入口地址传送给process函数中的形参函数中的形参fun(fun是指向函数的指针变量是指向函数的指针变量),见图,见图10.38(a)。这。这时,时,process函数中的函数中的(*fun)(x,y)相当于相当于max(x,y),执行执行process可以输出可以输出a和和B中大者。在中大者。在main函数第函数第二次调用时,改以函数名二次调用时,改以函数名min作
189、实参,此时作实参,此时process函数的形参函数的形参fun指向函数指向函数min,见图,见图10.38(B),在,在process函数中的函数调用函数中的函数调用(*fun)(x,y)相当于相当于min(x,y)。同理,第三次调用。同理,第三次调用process函数时,情况见函数时,情况见图图10.38(c)。(*fun)(x,y)相当于相当于add(x,y)。图图10.38从本例可以清楚地看到,不论调用从本例可以清楚地看到,不论调用max、min或或add,函数函数process一点都没有改动,只是在调用一点都没有改动,只是在调用process函函数时将实参函数名改变而已。这就增加了函数
190、使用数时将实参函数名改变而已。这就增加了函数使用的灵活性。可以编一个通用的函数来实现各种专用的灵活性。可以编一个通用的函数来实现各种专用的功能。需要注意的是,对作为实参的函数,应在的功能。需要注意的是,对作为实参的函数,应在主调函数中用函数原型作函数声明。例如,主调函数中用函数原型作函数声明。例如,main函函数中第数中第2行到第行到第4行的函数声明是不可少的。有的读行的函数声明是不可少的。有的读者可能会问,过去不是曾说过,对本文件中的整型者可能会问,过去不是曾说过,对本文件中的整型函数可以不加说明就可以调用吗?是的,但那只是函数可以不加说明就可以调用吗?是的,但那只是限于函数调用的情况,函数
191、调用时在函数名后面跟限于函数调用的情况,函数调用时在函数名后面跟括弧和实参括弧和实参(如如max(a,b),编译时能根据此形式判,编译时能根据此形式判断它为函数。而现在是只用函数名断它为函数。而现在是只用函数名(如如max)作实参,作实参,后面没有括弧和参数,编译系统无法判断它是后面没有括弧和参数,编译系统无法判断它是变量名还是函数名。故应事先作声明,声明变量名还是函数名。故应事先作声明,声明max、min、add是函数名是函数名(不是变量名不是变量名),这样编译时将,这样编译时将它们按函数名处理它们按函数名处理(把函数入口地址作实参值把函数入口地址作实参值),不致出错。不致出错。 有了以上基
192、础,就可以编写出较为复杂的程序。例有了以上基础,就可以编写出较为复杂的程序。例如,编写一个求定积分的通用函数,用它分别求如,编写一个求定积分的通用函数,用它分别求以下以下5个函数的定积分个函数的定积分:可以看出,每次需要求定积分的函数是不一样的。可以看出,每次需要求定积分的函数是不一样的。可以编写一个求定积分的通用函数可以编写一个求定积分的通用函数integral,它有,它有3个形参:下限个形参:下限a、上限、上限B以及指向函数的指针变量以及指向函数的指针变量fun。函数原型可写为。函数原型可写为float integral (float a, float b, float (*fun)(fl
193、oat);分别定义分别定义5个个C函数函数f1,f2,f3,f4,f5, 用来求上用来求上面面5个函数个函数1+x, 2x+3, ex+1,(1+x)2,x3 。然。然后先后调用后先后调用integral函数函数5次次 ,每次调用时把,每次调用时把a,b以及以及f1,f2,f3,f4,f5之一作为实参,把上限、之一作为实参,把上限、下限以及有关函数的入口地址传送给形参下限以及有关函数的入口地址传送给形参fun。在。在执行执行inteGral函数过程中求出定积分的值。请读者函数过程中求出定积分的值。请读者根据以上思路,编写出完整的程序。根据以上思路,编写出完整的程序。10.6返回指针值的函数返回
194、指针值的函数一个函数可以带回一个整型值、字符值、实型值等,一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。其概念与以也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。前类似,只是带回的值的类型是指针类型而已。这种带回指针值的函数,一般定义形式为类型名这种带回指针值的函数,一般定义形式为类型名*函函数名数名(参数表参数表);例如:例如:int*a(int x,int y);a是函数名,调用它以后能得到一个指向整型数据的是函数名,调用它以后能得到一个指向整型数据的指针指针(地址地址)。x、y是函数是函数a的形参,为整型。请注的形参,为
195、整型。请注意在意在*a两侧没有括弧,在两侧没有括弧,在a的两侧分别为的两侧分别为*运算运算符和符和()运算符。而运算符。而()优先级高于优先级高于*,因此,因此a先与先与()结合。显然这是函数形式。这个函数前面有一个结合。显然这是函数形式。这个函数前面有一个*,表示此函数是指针型函数,表示此函数是指针型函数(函数值是指针函数值是指针)。最。最前面的前面的int表示返回的指针指向整型变量。对初学表示返回的指针指向整型变量。对初学C语言的人来说,这种定义形式可能不大习惯,容语言的人来说,这种定义形式可能不大习惯,容易弄错,用时要十分小心。易弄错,用时要十分小心。例例10.25有若干个学生的成绩有若
196、干个学生的成绩(每个学生有每个学生有4门课程门课程),要求在用户输入学生序号以后,能输出该学生,要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。的全部成绩。用指针函数来实现。程序如下:程序如下:main() float score 4=60,70,80,90,56,89,67,88,34,78,90,66; floatsearch(float (*pointer)4,int n); floatp; int i,m; printf(enter the number of Student:); scanf(%D,&m); printf(The Scores of No.%D
197、are:n,m); p=Search(Score,m); for(i=0;i4;i+)printf(%5.2ft,(p+i); float *Search(float (*pointer)4,int n)float *pt; pt=*(pointer+n); return(pt); 运行情况如下:运行情况如下:enter the number of student:1 The scores of No.1 are:56.0089.0067.0088.00注意:学生序号是从注意:学生序号是从0号算起的。函数号算起的。函数search被定义被定义为指针型函数,它的形参为指针型函数,它的形参poin
198、ter是指向包含是指向包含4个元个元素的一维数组的指针变量。素的一维数组的指针变量。pointer+1指向指向score数数组第组第1行。见图行。见图10.39。*(pointer+1)指向第指向第1行第行第0列元素。也就是上一节介绍的指针从行控制转化列元素。也就是上一节介绍的指针从行控制转化为列控制了。为列控制了。pt是指针变量,它指向敌捅淞是指针变量,它指向敌捅淞?而不而不是指向一维数组是指向一维数组)。main函数调用函数调用search函数,将函数,将score数组的首地址传给数组的首地址传给pointer(注意注意score也是指也是指向行的指针,而不是指向列元素的指针向行的指针,而
199、不是指向列元素的指针)。m是要是要查找的学生序号。调用查找的学生序号。调用search函数后,得到一个地函数后,得到一个地址址(指向第指向第m个学生第个学生第0门课程门课程),赋给,赋给p。然后将。然后将此学生的此学生的4门课的成绩打印出来。门课的成绩打印出来。*(p+i)表示此学表示此学生第生第i门课的成绩。门课的成绩。图图10.39请注意,指针变量请注意,指针变量p、pt和和pointer的区别。如果将的区别。如果将search函数中的语句函数中的语句pt=*(pointer+n);改为改为pt=(*pointer+n);运行结果为:运行结果为:enter the number of st
200、udent:1The scores of No.1 are:70.0080.0090.0056.00得到的不是第一个学生的成绩,而是二维数组中得到的不是第一个学生的成绩,而是二维数组中a01开始的开始的4个元素的值。为什么?请读者分析。个元素的值。为什么?请读者分析。例例10.26对上例中的学生,找出其中有不及格课程的对上例中的学生,找出其中有不及格课程的学生及其学生号。学生及其学生号。程序如下:程序如下:main() float score 4=60,70,80,90,50,89,67,88,34,78,90,66; floatsearch(float (*pointer)4); float
201、p; int i,j; for(i=0;i3;i+) p=search(score+i); if(p=*(score+i) printf(No.%Dscores:,i); for(j=0;j4;j+)printf(%5.2f,*(p+j); printf(n); floatsearch(float (*pointer)4) int i; floatpt; pt=*(pointer+1); for(i=0;i4;i+) if(*(*pointer+i)60)pt=*pointer;Break; return(pt); 程序运行结果为程序运行结果为No.1scores:50.00 89.00 67
202、.00 88.00No.2scores:34.00 78.00 90.00 66.00函数函数search的作用是检查一个学生有无不及格的课的作用是检查一个学生有无不及格的课程。在程。在search函数中函数中pointer是指针变量,指向一是指针变量,指向一维数组维数组(有有4个元素个元素)。pt为指向实型变量的指针变为指向实型变量的指针变量。从实参传给形参量。从实参传给形参pointer的是的是score+i,它是,它是score第第i行的首地址,见图行的首地址,见图10.40(a)。图图10.4010.40在在search函数中,先使函数中,先使pt=*(pointer+1),即把,即把
203、pt指向本行之指向本行之末尾,见图末尾,见图10.40(B)。这是为了区分有无不及格课程的标。这是为了区分有无不及格课程的标志。若经检查志。若经检查4门课中有不及格的,就使门课中有不及格的,就使pt指向本行第指向本行第0列列元素。若无不及格则保持元素。若无不及格则保持pt指向本行末尾指向本行末尾(亦即下一行第亦即下一行第0列元素列元素)。将。将pt返回返回main函数。在函数。在main函数中,把调用函数中,把调用search得到的函数值得到的函数值(指针变量指针变量 pt的值的值),赋给,赋给p。用。用if语句语句判断判断p是否等于是否等于*(score+i),若相等,表示所查的学生有,若相
204、等,表示所查的学生有不及格课程不及格课程(p的值为的值为*(score+i),即,即p指向第指向第i行的第行的第0列元列元素素)。若无不及格,。若无不及格,p的值是的值是*(score+i+1),因为在函数,因为在函数search中已使它指向本行的末尾,也就是下一行开头。如中已使它指向本行的末尾,也就是下一行开头。如果果p等于等于*(score+i),就输出该学生,就输出该学生(有不及格课程的学生有不及格课程的学生)4门课成绩。门课成绩。请读者仔细消化本例中指针变量的含义和用法。请读者仔细消化本例中指针变量的含义和用法。10.7.1指针数组的概念指针数组的概念一个数组,其元素均为指针类型数据,
205、称为指针数一个数组,其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都相当组,也就是说,指针数组中的每一个元素都相当于一个指针变量。一维指针数组的定义形式为类于一个指针变量。一维指针数组的定义形式为类型名型名数组名数组名数组长度数组长度例如:例如:intp4由于由于比比*优先级高,因此优先级高,因此p先与先与4结合,形成结合,形成p4形式,这显然是数组形式,它有形式,这显然是数组形式,它有4个元素。然后再与个元素。然后再与p前面的前面的“*”结合,结合,“*”表示表示此数组是指针类型的,每个数组元素此数组是指针类型的,每个数组元素(相当于一个相当于一个指针变量指针变量)都
206、可指向一个整型变量。都可指向一个整型变量。10.7指针数组和指向指针的指针注意:不要写成注意:不要写成int(*p)4,这是指向一维数组的,这是指向一维数组的指针变量。这在前面已介绍过了。为什么要用到指针变量。这在前面已介绍过了。为什么要用到指针数组呢?它比较适合于用来指向若干个字符指针数组呢?它比较适合于用来指向若干个字符串,使字符串处理更加方便灵活。串,使字符串处理更加方便灵活。例如,图书馆有若干本书,想把书名放在一个数组例如,图书馆有若干本书,想把书名放在一个数组中中(图图10.41(a),然后要对这些书目进行排序和查,然后要对这些书目进行排序和查询。按一般方法,字符串本身就是一个字符数
207、组。询。按一般方法,字符串本身就是一个字符数组。因此要设计一个二维的字符数组才能存放多个字因此要设计一个二维的字符数组才能存放多个字符串。但在定义二维数组时,需要指定列数,也符串。但在定义二维数组时,需要指定列数,也就是说二维数组中每一行中包含的元素个数就是说二维数组中每一行中包含的元素个数(即列即列数数)相等。而实际上各字符串相等。而实际上各字符串(书名书名)长度一般是不长度一般是不相等的。如按最长的字符串来定义列数,则会浪相等的。如按最长的字符串来定义列数,则会浪费许多内存单元。见图费许多内存单元。见图10.41(B)。可以分别定义一些字符串,然后用指针数组中的元可以分别定义一些字符串,然
208、后用指针数组中的元素分别指向各字符串,见图素分别指向各字符串,见图10.41(C)。如果想对字。如果想对字符串排序,不必改动字符串的位置,只需改动指符串排序,不必改动字符串的位置,只需改动指针数组中各元素的指向针数组中各元素的指向(即改变各元素的值,这些即改变各元素的值,这些值是各字符串的首地址值是各字符串的首地址)。这样,各字符串的长度。这样,各字符串的长度可以不同,而且移动指针变量的值可以不同,而且移动指针变量的值(地址地址)要比移要比移动字符串所花的时间少得多。动字符串所花的时间少得多。图图10.41例例10.27将若干字符串按字母顺序将若干字符串按字母顺序(由小到大由小到大)输出。输出
209、。main() void Sort(char *name ,int n); void print(char *name ,int n); char *name=Follow me,BASIC,Great Wall,FORTRAN,Computer Design; int n=5; Sort(name,n); print(name,n); void Sort(char *name ,int n) chartemp; int i,j,k; for(i=0;in-1;i+) k=i; for(j=i+1;jn;j+) if(strcmp(namek,namej)0)k=j; if(k!=i) tem
210、p=namei; namei=namek; namek=temp; void print(char *name ,int n) int i; for(i=0;in;i+) printf(%Sn,namei); 运行结果为:运行结果为:BASIC Computer Design FORTRAN Follow me Great all在在main函数中定义指针数组函数中定义指针数组name。它有。它有5个元素,个元素,其初值分别是其初值分别是“Follow me”、“BASIC”、“Great all”、“FORTRAN”,“Computer Design”的首地址。见图的首地址。见图10.41(
211、C)。这些字符串是。这些字符串是不等长的不等长的(并不是按同一长度定义的并不是按同一长度定义的)。Sort函数函数的作用是对字符串排序。的作用是对字符串排序。Sort函数的形参函数的形参name也也是指针数组名,接受实参传过来的是指针数组名,接受实参传过来的name数组的首数组的首地址,因此形参地址,因此形参name数组和实参数组和实参name数组指的是数组指的是同一数组。用选择法对字符串排序。同一数组。用选择法对字符串排序。strcmp是字是字符串比较函数,符串比较函数,namek和和namej是第是第k个和第个和第j个个字符串的起始地址。字符串的起始地址。strcmp(namek,name
212、j)的的值为:如果值为:如果namek所指的字符串大于所指的字符串大于namej所所指的字符串,则此函数值为正值指的字符串,则此函数值为正值;若相等,则函数若相等,则函数值为值为0;若小于,若小于,则函数值为负值。则函数值为负值。if语句的作用是将两个串中语句的作用是将两个串中“小小”的那个串的序号的那个串的序号(i或或j之一之一)保留在变量保留在变量k中。当中。当执行完内循环执行完内循环for语句后,从第语句后,从第i个串到第个串到第n个串这个串这么多字符串中,第么多字符串中,第k个串最个串最“小小”。若。若ki就表示就表示最小的串不是第最小的串不是第i串。故将串。故将namei和和name
213、k对换,对换,也就是将指向第也就是将指向第i个串的数组元素个串的数组元素(是指针型元素是指针型元素)与指向第与指向第k个串的数组元素对换。执行完个串的数组元素对换。执行完Sort函数函数后指针数组的情况如图后指针数组的情况如图10.42所示。所示。图图10.42print函数的作用是输出各字符串。函数的作用是输出各字符串。name0到到name4分别是各字符串分别是各字符串(按从小到大顺序排好序按从小到大顺序排好序的各字符串的各字符串)的首地址的首地址(按字符串从小到大顺序,按字符串从小到大顺序,name0指向最小的串指向最小的串),用,用%s格式符输出,就格式符输出,就得到这些字符串。得到这
214、些字符串。print函数也可改写为以下形式:函数也可改写为以下形式:void print(char *name ,int n) int i=0; charp; p=name0; while(in) p=*(name+i+); printf(%Sn,p); 其中其中“*(name+i+)”表示先求表示先求*(name+i)的值,即的值,即namei(它是一个地址它是一个地址),然后使,然后使i加加1。在输出时,。在输出时,按字符串形式输出以按字符串形式输出以p地址开始的字符串。地址开始的字符串。请注意请注意Sort函数中的第一个函数中的第一个if语句中的逻辑表达式的语句中的逻辑表达式的正确用法。
215、如果写成下面形式是不对的:正确用法。如果写成下面形式是不对的:if(*namek*namej)k=j;这样只比较这样只比较namek和和namej所指向的字符串中的所指向的字符串中的第一个字符,字符串比较应当用第一个字符,字符串比较应当用strcmp函数。函数。10.7.2指向指针的指针指向指针的指针在掌握了指针数组的概念的基础上,下面介绍指向在掌握了指针数组的概念的基础上,下面介绍指向指针数据的指针变量,简称为指向指针的指针。指针数据的指针变量,简称为指向指针的指针。从图从图10.43可以看到,可以看到,name是一个指针数组,它的是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。
216、每一个元素是一个指针型数据,其值为地址。name是一个数组,它的每一元素都有相应的地址。是一个数组,它的每一元素都有相应的地址。数组名数组名name代表该指针数组的首地址。代表该指针数组的首地址。name+i是是namei的地址。的地址。name+i就是指向指针型数据的指就是指向指针型数据的指针针(地址地址)。还可以设置一个指针变量。还可以设置一个指针变量p,它指向指,它指向指针数组的元素针数组的元素(见图见图10.44)。p就是指向指针型数据就是指向指针型数据的指针变量。的指针变量。图图10.43图图10.44怎样定义一个指向指针数据的指针变量呢?如下:怎样定义一个指向指针数据的指针变量呢?
217、如下:char*p;p的前面有两个的前面有两个*号。号。*运算符的结合性是从右到左,运算符的结合性是从右到左,因此因此*p相当于相当于*(*p),显然,显然*p是指针变量的定义是指针变量的定义形式。如果没有最前面的形式。如果没有最前面的*,那就是定义了一个指,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个向字符数据的指针变量。现在它前面又有一个*号,号,表示指针变量表示指针变量p是指向一个字符指针变量是指向一个字符指针变量(即指向即指向字符型数据的指针变量字符型数据的指针变量)的。的。*p就是就是p所指向的另所指向的另一个指针变量,如果有一个指针变量,如果有p=name+2;pri
218、ntf(%on,*p);printf(%Sn,*p);第一个第一个printf函数语句输出函数语句输出name2的值的值(它是一个地址它是一个地址),第,第二个二个printf函数语句以字符串形式函数语句以字符串形式(%S)输出字符串输出字符串“Great all”。例。例10.28使用指向指针的指针。使用指向指针的指针。main() char *name=“Follow me”,“BASIC”,“Great Wall”,“FORTRAN”,“Computer Design; char *p; int i; for(i=0;i5;i+) p=name+i; printf(“%sn,*p); 运
219、行结果如下:运行结果如下: Follow me BASIC FORTRAN Great all Computer Design p是指向指针的指针变量,在第一次执行循环体时,是指向指针的指针变量,在第一次执行循环体时,赋值语句赋值语句“p=name+i;” 使使p指向指向name数组的数组的0号号元素元素name0,*p是是name0 的值,即第一个字符的值,即第一个字符串的起始地址,用串的起始地址,用printf函数输出第一个字符串函数输出第一个字符串(格式符为格式符为%S)。依次输出。依次输出5个字符串。个字符串。指针数组的元素也可以不指向字符串,而指向整型指针数组的元素也可以不指向字符串
220、,而指向整型数据或实型数据等,例如:数据或实型数据等,例如:inta5=1,3,5,7,9, i;int *num5int*p;for (i=0; i1)? :n);如果命令行输入:如果命令行输入:Cecho Computer and C Language在显示屏上输出:在显示屏上输出:Computer and C Language这个程序与前面的差别在于:这个程序与前面的差别在于: 将将while语句中的语句中的(argc-1)改为改为(-argc0),作用显然是一样的。,作用显然是一样的。 当当argc1时,时,在输出的两个字符串间输出一个空格,当在输出的两个字符串间输出一个空格,当arg
221、c=1时输出一时输出一个换行。程序不输出命令名个换行。程序不输出命令名“echo”。为便于理解,为便于理解,echo程序也可写成以下形式:程序也可写成以下形式:main(int argc,char *argv ) int i; for(i=1;iargc;i+) printf(%s%c,argvi,(argc1)? :n); main函数中的形参不一定命名为函数中的形参不一定命名为argc和和argv,可以,可以是任意的名字,只是人们习惯用是任意的名字,只是人们习惯用argc和和argv而已。而已。利用指针数组作利用指针数组作main函数的形参,可以向程序传送函数的形参,可以向程序传送命令行参
222、数命令行参数(这些参数是字符串这些参数是字符串),这些字符串的,这些字符串的长度事先并不知道,而且各参数字符串的长度一长度事先并不知道,而且各参数字符串的长度一般并不相同,命令行参数的数目也是可以任意的。般并不相同,命令行参数的数目也是可以任意的。用指针数组能够较好地满足上述要求。用指针数组能够较好地满足上述要求。关于指向指针的指针,是关于指向指针的指针,是C语言中比较深入的概念,语言中比较深入的概念,在此只作简单的介绍,以为读者提供今后进一步在此只作简单的介绍,以为读者提供今后进一步学习的基础。学习的基础。10.8有关指针运算的小结有关指针运算的小结前面已经用到了有关指针的运算,为使读者有一
223、系统完整的概念,现在作一小结。 10.8.1指针运算小结前面已用过一些指针运算(如p+,p+i等),今把全部的指针运算列出如下。(1) 指针变量加(减)一个整数例如:p+、p-、p+i、p-i、p+=i、p-=i等。C语言规定,一个指针变量加(减)一个整数并不是简单地将指针变量的原值加(减)一个整数,而是将该指针变量的原值(是一个地址)和它指向的变量所占用的内存单元字节数相加(减)。如p+i代表地址计算:计算:p+C*i。C为字节数,在大多数微机为字节数,在大多数微机C系统系统中,对整型数据中,对整型数据C=2,实型,实型C=4,字符型,字符型C=1。这。这样才能保证样才能保证(p+i)指向指
224、向p下面的第下面的第i个元素,它才个元素,它才有实际意义。有实际意义。(2) 指针变量赋值指针变量赋值将一个变量地址赋给一个指针变量。如:将一个变量地址赋给一个指针变量。如:p=&a(将变量将变量a的地址赋给的地址赋给p)p=array; (将数组将数组array首地址赋给首地址赋给p)p=&arrayi;(将数组将数组array第第i个元素的地址赋给个元素的地址赋给p)p=max; (max为已定义的函数,将为已定义的函数,将max的入口地的入口地址赋给址赋给p)p1=p2; (p1和和p2都是指针变量,将都是指针变量,将p2的值赋给的值赋给p1)注意:不应把一个整数赋给指针变量。如:注意:
225、不应把一个整数赋给指针变量。如:p=1000;有人以为这样可以将地址有人以为这样可以将地址1000赋给赋给p。但实际上是做。但实际上是做不到的。只能将变量已分配的地址赋给指针变量。不到的。只能将变量已分配的地址赋给指针变量。同样也不应把指针变量同样也不应把指针变量p的值的值(地址地址)赋给一个整型变赋给一个整型变量量i:i=p;(3) 指针变量可以有空值,即该指针变量不指向任指针变量可以有空值,即该指针变量不指向任何变量,可以这样表示:何变量,可以这样表示:p=NULL;实际上实际上NULL是整数是整数0,它使,它使p的存储单元中所有二的存储单元中所有二进位均为进位均为0,也就是使,也就是使p
226、指向地址为指向地址为0的单元。系的单元。系统保证使该单元不作它用统保证使该单元不作它用(不存放有效数据不存放有效数据),即有效,即有效数据的指针不指向数据的指针不指向0单元。实际上是先定义单元。实际上是先定义NULL,即,即#define NULL0p=NULL;在在Studio.h头文件中就有以上的头文件中就有以上的NULL定义,它是一个符定义,它是一个符号常量。号常量。用用“p=NULL;”表示表示p不指向任一有用单元。应注意,不指向任一有用单元。应注意,p的值为的值为NULL与未对与未对p赋值是两个不同的概念。前者是赋值是两个不同的概念。前者是有值的有值的(值为值为0),不指向任何变量,
227、后者虽未对,不指向任何变量,后者虽未对p赋值赋值但并不等于但并不等于p无值,只是它的值是一个无法预料的值,无值,只是它的值是一个无法预料的值,也就是也就是p可能指向一个事先未指定的单元。这种情况可能指向一个事先未指定的单元。这种情况是很危险的。因此,在引用指针变量之前应对它赋值。是很危险的。因此,在引用指针变量之前应对它赋值。任何指针变量或地址都可任何指针变量或地址都可以与以与NULL作相等或不作相等或不相等的比较,如相等的比较,如if(p=NULL)(4) 两个指针变量可以相两个指针变量可以相减减如果两个指针变量指向同如果两个指针变量指向同一个数组的元素,则两一个数组的元素,则两个指针变量值
228、之差是两个指针变量值之差是两个指针之间的元素个数,个指针之间的元素个数,见图见图10.47。图图10.47假如假如p1指向指向a1,p2指向指向a4,则,则p2-p1=4-1=3。但但p1+p2并无实际意义。并无实际意义。(5) 两个指针变量比较两个指针变量比较若两个指针指向同一个数组的元素,则可以进行比若两个指针指向同一个数组的元素,则可以进行比较。指向前面的元素的指针变量较。指向前面的元素的指针变量“小于小于”指向后指向后面元素的指针变量。如图面元素的指针变量。如图10.47中,中,p1p2,或者,或者说,表达式说,表达式“p1p2”的值为的值为1(真真),而,而“p2p1”的值为的值为0
229、(假假)。注意,。注意,如果如果p1和和p2不指向同一数组则比较无意义。不指向同一数组则比较无意义。10.8.3void指针类型指针类型ANSI新标准增加了一种新标准增加了一种“void”指针类型,即可定指针类型,即可定义一个指针变量,但不指定它是指向哪一种类型义一个指针变量,但不指定它是指向哪一种类型数据的。数据的。ANSI C标准规定用动态存储分配函数时标准规定用动态存储分配函数时返回返回void指针,它可以用来指向一个抽象的类型指针,它可以用来指向一个抽象的类型的数据,在将它的值赋给另一指针变量时要进行的数据,在将它的值赋给另一指针变量时要进行强制类型转换使之适合于被赋值的变量的类型。强
230、制类型转换使之适合于被赋值的变量的类型。例如例如:charp1; voidp2; p1=(char *)p2;同样可以用同样可以用(void *)p1将将p1的值转换成的值转换成void *类型。如:类型。如:p2=(void *)p1;也可以将一个函数定义为也可以将一个函数定义为void *类型,如类型,如:voidfun(char Ch1,char Ch2)表示函数表示函数fun返回的是一个地址,它指向返回的是一个地址,它指向“空类型空类型”,如需要引用此地址,也需要根据情况对之进行类型转如需要引用此地址,也需要根据情况对之进行类型转换,如对函数调用得到的地址要进行以下转换:换,如对函数调
231、用得到的地址要进行以下转换:p1=(char)fun(Ch1,Ch2);在本章中介绍了指针的基本概念和初步应用。应该说明,在本章中介绍了指针的基本概念和初步应用。应该说明,指针是指针是C语言中重要的概念,是语言中重要的概念,是C的一个特色。使用指的一个特色。使用指针的优点:针的优点:提高程序效率提高程序效率;在调用函数时变量改变在调用函数时变量改变了的值能够为主调函数使用,即可以从函数调用得到了的值能够为主调函数使用,即可以从函数调用得到多个可改变的值多个可改变的值;可以实现动态存储分配。可以实现动态存储分配。但是同时应该看到,指针使用实在太灵活,对熟练但是同时应该看到,指针使用实在太灵活,对
232、熟练的程序人员来说,可以利用它编写出颇有特色的、的程序人员来说,可以利用它编写出颇有特色的、质量优良的程序,实现许多用其他高级语言难以质量优良的程序,实现许多用其他高级语言难以实现的功能,但也十分容易出错,而且这种错误实现的功能,但也十分容易出错,而且这种错误往往难以发现。由于指针运用的错误甚至会引起往往难以发现。由于指针运用的错误甚至会引起使整个程序遭受破坏,比如由于未对指针淞縫赋使整个程序遭受破坏,比如由于未对指针淞縫赋值就向值就向*p赋值,这就可能破坏了有用的单元的内赋值,这就可能破坏了有用的单元的内容。有人说指针是有利有弊的工具,甚至说它容。有人说指针是有利有弊的工具,甚至说它“好坏参
233、半好坏参半”。的确,如果使用指针不当,特别是。的确,如果使用指针不当,特别是赋予它一个错误的值时,会成为一个极其隐蔽的、赋予它一个错误的值时,会成为一个极其隐蔽的、难以发现和排除的故障。因此,使用指针要十分难以发现和排除的故障。因此,使用指针要十分小心谨慎,要多上机调试程序,以弄清一些细节,小心谨慎,要多上机调试程序,以弄清一些细节,并积累经验。并积累经验。习题习题本章习题均要求用指针方法处理。本章习题均要求用指针方法处理。10.1 输入输入3个整数,按由小到大的顺序输出。个整数,按由小到大的顺序输出。10.2 输入输入3个字符串,按由小到大的顺序输出。个字符串,按由小到大的顺序输出。10.3
234、 输入输入10个整数,将其中最小的数与第一个数对个整数,将其中最小的数与第一个数对换,把最大的数与最后一个数对换。写换,把最大的数与最后一个数对换。写3个函数:个函数:输入输入10个数个数;进行处理进行处理;输出输出10个数。个数。10.4 有有n个整数,使前面各数顺序向后移个整数,使前面各数顺序向后移m个位置,个位置,最后最后m个数变成最前面个数变成最前面m个数,见图个数,见图10.48。写一。写一函数实现以上功能,在主函数中输入函数实现以上功能,在主函数中输入n个整数和输个整数和输出调整后的出调整后的n个数。个数。图图10.4810.5 有一字符串,包含有一字符串,包含n个字符。写一函数,
235、将此个字符。写一函数,将此字符串中从第字符串中从第m个字符开始的全部字符复制成为个字符开始的全部字符复制成为另一个字符串。另一个字符串。10.6 输入一行文字,找出其中大写字母、小写字母、输入一行文字,找出其中大写字母、小写字母、空格、数字以及其他字符各有多少?空格、数字以及其他字符各有多少?10.7 写一函数,将一个写一函数,将一个33的矩阵转置。的矩阵转置。10.8 将一个将一个55的矩阵中最大的元素放在中心,的矩阵中最大的元素放在中心,4个个角分别放角分别放4个最小的元素个最小的元素(顺序为从左到右,从上顺序为从左到右,从上到下顺序依次从小到大存放到下顺序依次从小到大存放),写一函数实现
236、之。,写一函数实现之。用用main函数调用。函数调用。10.9 在主函数中输入在主函数中输入10个等长的字符串。用另一函个等长的字符串。用另一函数对它们排序。然后在主函数输出这数对它们排序。然后在主函数输出这10个已排好个已排好序的字符串。序的字符串。10.10 用指针数组处理上一题目,字符串不等长。用指针数组处理上一题目,字符串不等长。10.11 将将n个数按输入时顺序的逆序排列,用函数实个数按输入时顺序的逆序排列,用函数实现。现。10.12 有一个班有一个班4个学生,个学生,5门课。门课。求第一门课的求第一门课的平均分平均分;找出有找出有2门以上课程不及格的学生,输门以上课程不及格的学生,
237、输出他们的学号和全部课程成绩和平均成绩出他们的学号和全部课程成绩和平均成绩;找出找出平均成绩在平均成绩在90分以上或全部课程成绩在分以上或全部课程成绩在85分以上分以上的学生。分别编的学生。分别编3个函数实现以上个函数实现以上3个要求。个要求。10.13 输入一个字符串,内有数字和非数字字符,输入一个字符串,内有数字和非数字字符,如如a123x456 17960?302taB5876将其中连续的数将其中连续的数字作为一个整数,依次存放到一数组字作为一个整数,依次存放到一数组a中。例如,中。例如,123放在放在a0,456放在放在a1统计共有多少个整数,统计共有多少个整数,并输出这些数。并输出这
238、些数。10.14 写一函数,实现两个字符串的比较。即自己写一函数,实现两个字符串的比较。即自己写一个写一个strcmp函数,函数原型为函数,函数原型为intstrcmp(char *p1,char *p2);设设p1指向字符串指向字符串S1, p2指向字符指向字符串串S2。要求当。要求当S1=S2时,返回值为时,返回值为0,若,若S1S2,返回它们二者第一个不同字符的返回它们二者第一个不同字符的ASCII码差值码差值(如如“BO”与与“BAD”,第二个字母不同,第二个字母不同,“O”与与“A”之差为之差为79-65=14)。如果。如果S1S2,则输出正,则输出正值,如值,如S1S2,则输出负值。,则输出负值。10.15 编一程序,打入月份号,输出该月的英文月编一程序,打入月份号,输出该月的英文月名。例如,输入名。例如,输入“3”,则输出,则输出“March”,要求,要求用指针数组处理。用指针数组处理。10.16 用指向指针的指针的方法对用指向指针的指针的方法对5个字符串排序并个字符串排序并输出。输出。10.17 用指向指针的指针的方法对用指向指针的指针的方法对n个整数排序并输个整数排序并输出。要求将排序单独写成一个函数。出。要求将排序单独写成一个函数。5个整数和个整数和n在主函数中输入。最后在主函数中输出。在主函数中输入。最后在主函数中输出。