《C程序设计指针》PPT课件.ppt

上传人:za****8 文档编号:16086121 上传时间:2020-09-18 格式:PPT 页数:265 大小:1.48MB
收藏 版权申诉 举报 下载
《C程序设计指针》PPT课件.ppt_第1页
第1页 / 共265页
《C程序设计指针》PPT课件.ppt_第2页
第2页 / 共265页
《C程序设计指针》PPT课件.ppt_第3页
第3页 / 共265页
资源描述:

《《C程序设计指针》PPT课件.ppt》由会员分享,可在线阅读,更多相关《《C程序设计指针》PPT课件.ppt(265页珍藏版)》请在装配图网上搜索。

1、第10章 指 针,10.1地址和指针的概念 10.2变量的指针和指向变量的指针变量 10.3数组的指针和指向数组的指针变量 10.4字符串的指针和指向字符串的指针变量 10.5函数的指针和指向函数的指针变量 10.6返回指针值的函数 10.7指针数组和指向指针的指针 10.8有关指针的数据类型和指针运算的小结 习题,指针是C语言中的一个重要的概念,也是C语言的一个重要特色。正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存;能方便地使用字符串;有效而方便地使用数组;在调用函数时能得到多于1个的值;能直接处理内存地址等,这对设计系统软件是很必要的。掌握指针的应用,可以使程序简洁、紧

2、凑、高效。每一个学习和使用C语言的人,都应当深入地学习和掌握指针。可以说,不掌握指针就是没有掌握C的精华。,指针的概念比较复杂,使用也比较灵活,因此初学时常会出错,务请在学习本章内容时十分小心,多思考、多比较、多上机,在实践中掌握它。我们在叙述时也力图用通俗易懂的方法使读者易于理解。,10.1 地址和指针的概念,为了说清楚什么是指针,必须弄清楚数据在内存中是如何存储的,又是如何读取的。 如果在程序中定义了一个变量,在编译时就给这个变量分配内存单元。系统根据程序中定义的变量类型,分配一定长度的空间。例如,一般微机使用的C系统为整,图10.1,型变量分配2个字节,对实型变量分配4个字节,对字符型变

3、量分配1个字节。内存区的每一个字节有一个编号,这就是“地址”,它相当于旅馆中的房间号。在地址所标志的内存单元中存放数据,这相当于旅馆中各个房间中居住旅客一样。请务必弄清楚一个内存单元的地址与内存单元的内容这两个概念的区别,如图10.1所示。假设程序已定义了3个整型变量i、j、k,编译时系统分配2000和2001两个字节给变量i,2002,2003字节给j,2004,2005给k。 在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后己经将变量名转换为变量的地址,对变量值的存取,都是通过地址进行的。例如,printf(“%D”,i)的执行是这样的:根据变量名与地址的对应关系(

4、这个对应关系是在编译时确定的),找到变量i的地址2000,然后从由2000开始的两个字节中取出数据(即变量的值3),把它输出。输入时如果用scanf(%D, *i-pointer=3; 第个语句的含意是将3赋给指针变量i-pointer所指向的变量。 10.2.1 定义一个指针变量 C语言规定所有变量在使用前必须定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是用来专门存放地址的。必须将它定义为“指针类型”。先看一个具体例子:,inti,j; int*pointer_1,*pointer_2; 第1行定义了两个整型变量i和j,第2行定义了两个指针变量:point

5、er_1和pointer_2,它们是指向整型变量的指针变量。左端的int是在定义指针变量时必须指定的“基类型”。 指针变量的基类型用来指定该指针变量可以指向的变量的类型。例如,上面定义的指针变量pointer_1和pointer_2可以用来指向整型变量i和j,但不能指向实型变量a和B。 定义指针变量的一般形式为基类型*指针变量名下面都是合法的定义:,float*pointer_3;(pointer-3是指向实型变量的指针变量) char*pointer_4; (pointer-4是指向字符型变量的指针变量) 那么,怎样使一个指针变量指向另一个变量呢?下面用赋值语句使一个指针变量指向一个整型变量

6、: pointer_1= 将变量i的地址存放到指针变量pointer_1中,因此pointer_1就“指向”了变量i。,图10.4,同样,将变量j的地址存放到指针变量pointer_2中,因此pointer_2就“指向”了变量j。见图10.4。 在定义指针变量时要注意两点: (1) 指针变量前面的“*”,表示该变量的类型为指针型变量。注意:指针变量名是pointer_1、pointer_2,而不是*pointer_1、*pointer_2。这是与以前所介绍的定义变量的形式不同的。 (2) 在定义指针变量时必须指定基类型。有的读者认为既然指针变量是存放地址的,那么只需要指定其为“指针型变量”即可

7、,为什么还要指定基类型呢?我们知道整型数据和实型数据在内存中所占的字节数是不相同的(前者为2字节,后者为4字,节),在本章的稍后将要介绍指针的移动和指针的运算(加、减),例如“使指针移动1个位置” 或“使指针值加1” ,这个“1” 代表什么呢?如果指针是指向一个整型变量的,那么“使指针移动1个位置” 意味着移动2个字节,“使指针加1” 意味着使地址值加2个字节。如果指针是指向一个实型变量的,则增加的不是2而是4。因此必须规定指针变量所指向的变量的类型,即基类型。一个指针变量只能指向同一个类型的变量。不能忽而指向一个整型变量,忽而指向一个实型变量。上面的定义中,表示pointer_1和point

8、er_2只能指向整型变量。 对上述指针变量的定义也可以这样理解:*pointer_1和*pointer_2是整型变量,如同:“int,a,B;”定义了a和B是整型变量一样。而*pointer_1和*pointer_2是pointer_1和pointer_2所指向的变量,pointer_1和pointer_2是指针变量。需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。,10.2.2 指针变量的引用 请牢记,指针变量中只能存放地址(指针),不要将一个整型量(或任何其他非地址类型的数据)赋给一个指针变量。下面的赋值是不合法的: pointer-1=100;(pointer-1为

9、指针变量,100为整数) 有两个有关的运算符: (1) intpointer-1, *pointer-2; a=100;B=10; pointer-1= ,运行结果为: 100,10 100,10 对程序的说明: (1) 在开头处虽然定义了两个指针变量pointer-1和pointer-2,但它们并未指向任何一个整型变量。只是提供两个指针变量,规定它们可以指向整型变量。至于指向哪一个整型变量,要在程序语句中指定。程序第5、6行的作用就是使pointer-1指向a,pointer-2指向B,见图10.5。此时pointer-1的值为 scanf(%D,%D, ,运行情况如下: 5,9 a=5,B

10、=9 max=9,min=5 当输入a=5,B=9时,由于aB,将p1和p2交换。交换前的情况见图10.8(a),交换后见图10.8(B)。,图10.8,请注意,a和B并未交换,它们仍保持原值,但p1和p2的值改变了。p1的值原为 temp=*p1; *p1=*p2; *p2=temp; main() int a,B; int*pointer-1,*pointer-2; scanf(%D,%D,,pointer-1= 运行情况如下: 5,9 9,5 对程序的说明: Swap是用户定义的函数,它的作用是交换两个变量(a和B)的值。Swap函数的两个形参p1、p2是指针变量。程序运行时,先执行ma

11、in函数,,输入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是指针变量,在函数调用时,将实参变量的值传送给形参变量。采取的依然是“值传递”方式。因此虚实结合后形参p1的值为 *temp=*p1; /*此语句有问题*/ p1=*p2; p2=*temp; p1就是a,是整型变量。而*temp是指针变量temp所指向的变量。但temp中并无确定的地址值,它的值是不可预见的。

12、*temp所指向的单元也是不可,预见的。因此,对*temp赋值可能会破坏系统的正常工作状况。应该将*p1的值赋给一个整型变量,如程序所示那样,用整型变量temp作为临时辅助变量实现*p1和*p2的交换。 注意: 本例采取的方法是:交换a和B的值,而p1和p2的值不变。这恰和例10.2相反。 可以看到,在执行Swap函数后,变量a和B的值改变了。请仔细分析,这个改变是怎么实现的。这个改变不是通过将形参值传回实参来实现的。请读者考虑一下能否通过下面的函数实现a和B互换。,Swap(int x,int y) int temp; temp=x; x=y; y=temp; 如果在main函数中用“Swa

13、p(a,B);”调用Swap函数,会有什么结果呢?如图10.10所示。在函数调用时,a的值传送给x,B的值传送给y。执行完Swap函数后,x和y的值是互换了,但main函数中的a和B并未互换。也就是说由于“单向传送”的“值传递”方式,形参值的改变无法传给实参。,为了使在函数中改变了的变量值能被main函数所用,不能采取上述把要改变值的图10.10变量作为参数的办法,而应该用指针变量作为函数参数,在函数执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来,这样就实现了“通过调用函数使变量的值发生变化,在主调函数(如main函数)中使用这些改变了的值”的目的。

14、如果想通过函数调用得到n个要改变的值,可以:在主调函数中设n个变量,用n个指针变量指向它们;然后将指针变量作实参,将这n个变量的地址传给所调用的函数的形参;通过形参指针变量,改变该n个变量的值;主调函数中就可以使用这些改,变了值的变量。请读者按此思路仔细理解例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; intpoi

15、nter-1,*pointer-2; scanf(%D,%D,,pointer-1= 作者的意图是:交换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)。在Swap函数中,使p1与p2的值交换,见图10.11(C)。形参p12将地址传回实参pointer-1和pointer-2,使pointer-1指向B,pointer-2指向a,见图10.11(D)。然后输出*pointer

16、-1和*pointer-2,想得到输出“9,5”。,图10.11,但是这是办不到的,程序实际输出为“5,9”。问题出在第步。C语言中实参变量和形参变量之间的数据传递是单向的“值传递”方式。指针变量作函数参数也要遵循这一规则。调用函数不可能改变实参指针变量的值,但可以改变实参指针变量所指变量的值。我们知道,函数的调用可以(而且只可以)得到一个返回值(即函数值),而运用指针变量作参数,可以得到多个变化了的值。难以做到这一点的。例10.4输入a、B、C 3个整数,按大小顺序输出。 Swap(int *pt1, int *pt2) int temp; temp=*pt1;,pt1=*pt2; pt2=

17、temp; exChanGe(int *q1, int *q2, int *q3) if(*q1*q2) Swap(q1,q2); if(*q1*q3) Swap(q1,q3); if(*q2*q3) Swap(q2,q3); main() int a,B,C,*p1,*p2,*p3;,scanf(%D,%D,%D, 运行情况如下: 9,0,10 10,9,0,10.3 数组的指针和指向数组的指针变量 一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组和数组元素(把数组起始地址或某一元素的地址放到一个指针变

18、量中)。所谓数组的指针是指数组的起始地址,组元素的指针是数组元素的地址。 引用数组元素可以用下标法(如a3),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。,10.3.1 指向数组元素的指针 定义一个指向数组元素的指针变量的方法,与以前介绍的指向变量的指针变量相同。 例如: int a10;/*定义a为包含10个整型数据的数组*/ intp; /*定义p为指向整型变量的指针变量*/,图10.12,应当注意,如果数组为int型,则指针变量亦应指向int型。下面是对该指针元素赋值: p=”的作用是“把a数组的首地址赋给指针变量p”,而

19、不是“把数组a各元素的值赋给p”。,在定义指针变量时可以赋给初值: intp= 它的作用是将a的首地址(即a0的地址)赋给指针变量p(而不是赋给*p)。,10.3.2 通过指针引用数组元素 假设p已定义为指针变量,并已给它赋了一个地址,使它指向某一个数组元素。如果有以下赋值语句: p=1; 表示对p当前所指向的数组元素赋予一个值(值为1)。按C的规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素(而不是将p值简单地加1)。例如,数组元素是实型,每个元素占4个字节,则p+1意味着使p的值(地址)加4个字节,以使它指向下一元素。p+1所代表的地址实际上是p+1D,D是一

20、个数组元素所占的字节数(对整型,D=2;对实型,D=4;对字符型,D=1)。,图10.13,如果p的初值为 (2) 指针法,如*(a+i)或(p+i)。其中a是数组名,p是指向数组的指针变量,其初值p=a。,例10.5输出数组中的全部元素。假设有一个a数组,整型,有10个元素。要输出各元素的值有三种方法: (1) 下标法。 main() int a10; inti; for(i=0;i10;i+) scanf(%D, ,(2) 通过数组名计算数组元素地址,找出元素的值。 main() int a10; int i; for(i=0;i10;i+) scanf(%D, ,(3) 用指针变量指向数

21、组元素。 main() int a10; int*p,i; for(i=0;i10;i+) scanf(%D, ,以上3个程序的运行情况均如下: 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 对三种方法的比较: (1) 例10.5的第(1)和(2)种方法执行效率是相同的。C编译系统是将ai转换为*(a+i)处理的。即先计算元素地址。因此用第(1)和(2)种方法找数组元素费时较多。 (2) 第(3)种方法比(1)(2)法快,用指针变量直接指向元素,不必每次都重新计算地址,像p+这样的自加操作是比较快的。这种有规律地改变地址值(p+)能大大提高执行效率。,(3)

22、用下标法比较直观,能直接知道是第几个元素。例如,a5是数组中序号为5的元素(注意序号从0算起)。用地址法或指针变量的方法不直观,难以很快地判断出当前处理的是哪一个元素。例如,例10.5第(3)种方法所用的程序,要仔细分析指针变量p的当前指向,才能判断当前输出的是第几个元素。 在使用指针变量时,有几个问题要注意: (1) 指针变量可以实现使本身的值改变。例如,上述第(3)种方法是用指针变量p来指向元素,用p+使p的值不断改变,这是合法的。如果不用p而使a变化(例如,用a+)行不行呢?假如将上述(3)程序的最后两行改为,for(p=a;a(p+10);a+) printf(%D,*a); 是不行的

23、。因为a是数组名,它是数组首地址,它的值在程序运行期间是固定不变的,是常量。a+是什么意思呢?这是无法实现的。 (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); 这个程序乍看起来好像没有什么问题。有的人即使已被告知此程序有问题,还是找不出它有什么问题。我们先看一下运行情况:,1 2 3 4 5 6 7 8 9 0 22153 234

24、 0 0 30036 25202 11631 8259 8237 28483 显然输出的数值并不是a数组中各元素的值。原因是指针变量的初始值为a数组首地址(见图10.14中的),但经过第一个for循环读入数据后,p已指向a 数组的末尾(见图10.14中)。因此,在执行第二个for循环时,p的起始值不是解决这个问题的办法,只要在第二个for循环之前加一个赋值语句:使p的初始值回到 p=a; for(i=0;i10;i+) scanf(%D,p+); printf(n); p=a; for(i=0;i10;i+,p+) printf(%D ,p); ,图10.14,运行情况如下: 1 2 3 4

25、5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 (3) 从上例可以看到,虽然定义数组时指定它包含10个元素,可以用p指向当前的数组元素。但是实际上指针变量p可以指向数组以后的内存单元。如果引用数组元素a10,C编译程序并不认为非法,系统把它按*(a+10)处理,即先找出(a+10)的值(是一个地址),然后找出它指向的单元的内容。这样做虽然是合法的(在编译时不出错),但应避免出现这样的情况,这会使程序得不到预期的结果。这种错误比较隐蔽,初学者往往难以发现。在使用指针变量指向数组元素时,应切实保证指向数组中有效的元素。 (4) 注意指针变量的运算。如果先使p指向数组a(即p=a),

26、则:, p+(或p+=1),使p指向下一元素,即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的值,然后使p值加1。这样下一次循环时,p就是下一个元素的值。 (p+)与*(+p)作用不同。前者是先取*p值,后使p加1。后者是先使p加1,再取*

27、p。若p初值为a(即p=a while(pa+100) 或 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节已知,当用数组名作参数时,如果形参数组中各元素,的值发生变化,实参数组元素的值随之变化。这是为什么?在学习指针以

28、后,对此问题就容易理解了。先看数组元素作实参时的情况。如果已定义一个函数,其原型为: void Swap(int x,int y); 假设函数的作用是将两个形参(x,y) 的值交换,今有以下的函数调用: Swap(a1,a2); 用数组元素a1,a2作实参的情况与用变量作实参时一样,是“值传递”方式,将a1和a2的值单向传递给x,y。当x和y的值改变时,a1和a2的值并不改变。,再看用数组名作函数参数的情况。前已介绍,实参数组名代表该数组首地址。而形参是用来接收从实参传递过来的数组首地址的。因此,形参应该是一个指针变量(只有指针变量才能存放地址)。实际上,C编译都是将形参数组作为指针变量来处理

29、的。例如,上面给出的函数f的形参是写成数组形式的: f(int arr , int n) 但在编译时是将arr按指针变量处理的,相当于将函数f的首部写成 f(int *arr, int n) 以上两种写法是等价的。在调用该函数时,系统会建立一个指针变量arr,用来存放从主调函数传递过来,的实参数组首地址。如果在f函数中用sizeof运算符测定arr所占的字节数(即size of arr的值),结果为2。这就证明了系统是把arr作为指针变量来处理的。当arr接收了实参数组的首地址后,arr就指向实参数组的开头,也就是指向array0。因此,*arr就是array0的值。arr+1指向array1

30、,arr+2指向array2,arr+3指向array3。也就是说*(arr+1),*(arr+2)*(arr+3)分别是array1,array2,array3的值。根据前面介绍过的知识,*(arr+i)和arri是无条件等价的。因此,在调用函数期间,arr0和*arr以及array0都是数组array第0个元素的值,依此类推,arr3,*(arr+3),array3都是array数组第3号元,素的值,见图10.15。这个道理与10.2节10.2.3段中的叙述是类似的。,图10.15,常用这种方法通过调用一个函数来改变实参数组的值。我们把用变量名作为函数参数和用数组名作为函数参数作一比较。

31、实参类型变量名数组名 要求形参的类型变量名数组名或指针变量 传递的信息变量的值数组的起始地址 通过函数调用能否改变实参的值 需要说明的是:C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组起始地址,因此传递的值是数组首地址,所以要求形参为指针变量。,在用数组名作为函数实参时,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢?这是因为在C语言中用下标法和指针法都可以访问一个数组(如果有一个数组a,则ai和*(a+i)无条件等价),用下标法表示比较直观,便于理解。因此许多人愿意用数组名作形

32、参,以便与实参数组对应。从应用的角度看,用户可以认为有一个形参数组,它从实参数组那里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就是改变了实参数组的值。当然在主调函数中可以利用这些已改变的值。对C语言比较熟练的专业人员往往喜欢用指针变量作形参。,应该说明: 实参数组后代表一个固定的地址,或者说是指针型常量,而形参数组并不是一个固定的地址值。作为指针变量,在函数调用开始时,它的值等于实参数组起始地址,但在函数执行期间,它可以再被赋值。如: f(arr , int n) printf(%Dn, *arr);/*输出array0的值,*/ arr

33、=arr+3; printf(%Dn, *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对换,直到i=(n-1)/2为止。 程序如下:,void inv(int x ,int n)/*形参x是数组名*/ int temp,i,j,m=(n-1)/2;

34、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(i=0;i10;i+) printf(%D,ai); printf(n); ,运行情况如下: The oriGinal array: 3,7,9,11,0,6

35、,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)。这样做可以增加函数的灵活性。即不必要求函数inv中的形参数组x和main函数中的实参数组a长度相同。如果在main函数中有函数调用语句:inv(a,10),表示要求对a数组的前10个元素实行题目要求的颠倒排列。如果改为:inv(a,5),则表示要求将a数组的前5个元素实行颠倒排列,此时,函数inv只处理5个数组

36、元素。函数inv中的m是i值的上限,当im时,循环继续执行;当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的初值为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

37、)/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(n); inv(a,10); printf(The array haS Been inverteD:n); for(i=0;i10;i+) printf(%D,ai); printf(n); ,运行情况与前一程序相同。 例10.8从10个数中

38、找出其中最大值和最小值。 本题不要求改变数组元素的值,只要求得到最大值和最小值。但是调用一个函数只能得到一个返回值,为了能得到两个结果值,今用全局变量在函数之间“传递”数据。程序如下: 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

39、; printf(enter 10 inteGer numBerS:n); for(i=0;i10;i+),scanf(%D, 运行结果如下: enter 10 integer numbers: -2 4 6 8 0 -3 45 67 89 100 max=100,min=-3 在函数max-min-value中求出的最大值和最小值放在max和min中。由于它们是全局变量,因此在主函数中可以直接使用。,图10.18,函数max-min-value中的语句: max=min=*array; array是形参数组名,它接收从实参传来的数组numBer的首地址。array是形参数组的首地址,*arr

40、ay相当于*(array+0),即array0。上述语句与下面语句等价: max=min=array0; 见图10.18。在执行for循环时,p的初值为array+1,也就是使p指向array1。以后每次执行p+,使p指向下一个元素。每次将*p和max与min比较,将大者放入max,小者放入min。,与上例相似,函数max-min-value的形参array可以改为指针变量类型。即将该函数首部改为void max-min-value(int *array,int n)效果相同。 实参也可以不用数组名,而用指针变量传递地址,形参仍用指针变量。程序可改为: int max,min; void ma

41、x-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,*p; p=numBer;/*使p指向number数组*/ printf(enter 10 integer numbers:n); for(i=0;i10;i+,p+) scanf(%d,p);,printf(the 10integer n

42、umbers:n); for(p=number,i=0;i10;i+,p+) printf(%d,*p); p=number; max-min-value(p,10);/*实参用指针变量*/ printf(nmax=%d,min=%dn,max,min); 归纳起来,如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的表示形式有以下4种情况:,图10.19,(1) 形参和实参都用数组名,如: main() f(intx ,int n) int a10; f(a,10); 可以认为有一形参数组,与实参数组共用一段内存单元,这种形式比较好理解,见图10.19。例10.7第一个程序即属此

43、情况。,图10.20 (2) 实参用数组名,形参用指针变量。如: main()f(int *x,int n) int a10; , f(a,10); 实参a为数组名,形参x为指向整型变量的指针变量,函数开始执行时,x指向a0,即x= p=a; f(p,10); 实参p和形参x都是指针变量。先使实参指针变量p指向数组a,p的值是 p=a; f(p,10); ,图10.22 实参p为指针变量,它使指针变量p指向a0,即p=a或p= 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()

44、 int i,arr10,*p=arr; printf(The original array:n); for(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; printf(T

45、he 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 n) int *p; f(p,n); 应注意,如果用指针变量作实参,必须先使指针变量有确定值,指向一个已定义的数组。

46、以上四种方法,实质上都是地址的传递。其中(1)(4)两种只是形式上不同,实际上都是使用指针变量。,例10.10用选择法对10个整数排序。 程序如下: main() intp,i,a10; p=a; for(i=0;i10;i+) scanf(%D,p+); p=a; Sort(p,10); for(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; ,为了便于

47、理解,函数Sort中用数组名作为形参,用下标法引用形参数组元素,这样的程序很容易看懂。当然也可以改用指针变量,这时Sort函数的首部可以改为 sort(intx,int 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

48、+k)=t; 请读者自己理解消化程序。,10.3.4 指向多维数组的指针和指针变量 用指针变量可以指向一维数组,也可以指向多维数组。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。 1. 多维数组的地址 为了说清楚多维数组的指针,先回顾一下多维数组的性质。今以二维数组为例,设有一个二维数组a,它有3行4列。 它的定义为 int a34=1,3,5,7,9,11,13,15,17,19,21,23;,a是一个数组名。a数组包含3行,即3个元素: a0,a1,a2。而每一元素又是一个一维数组,它包含4个元素(即4个列元素),例如,a0所代表的一维数组又包含4个元素:a00,a01,a

49、02,a03,见图10.23。,图10.23,从二维数组的角度来看,a代表整个二维数组的首地址,也就是第0行的首地址。a+1代表第1行的首地址。如果二维数组的首地址为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列元素的地址,即 printf(FORMAT,a,*a); printf(FORMAT,a0,*(a+0); printf(FORMAT,,printf(FO

50、RMAT, 运行结果如下: 158,158(第0行首地址和0行0列元素地址) 158,158(0行0列元素地址) 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,都是地址。请注意:每次编译分配的地址是不同的)。a是指向一维数组的指针,可理解为行指针,

51、*a是指向列元素的指针,可理解为列指针,指向0行0列元素,*a是0行0列元素的值。同样,a+1指向第1行首地址,但也不能企图用*(a+1)得到a10的值,而应该用*(a+1) 求a10元素的值。,2. 指向多维数组的指针变量 在了解上面的概念后,可以用指针变量指向多维数组及其元素。 (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); 运行结果如下:

52、 1357 9 11 13 15 17 19 21 23 p是一个指向整型变量的指针变量,它可以指向一般的整型变量,也可以指向整型的数组元素。每次使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,value=7 addr=246,value=9 addr=250,value

53、=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),则应事先计算该元素在数组中的相对位置(即相对于数组起始位置的相对位移量)。计算aij在数组中的相对位置的计算公式为i*m+j其中m为二维数组的列数(二维数组大小为nm)。例如,对上述34的二维数组,它的第2行第3列元素(a23)对a00,的

54、相对位置为2*4+3=11。如果开始时使指针变量p指向a(即(a00),为了得到a23的值,可以用*(p+2*4+3)表示。(p+11)是a23的地址。aij的地址为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表示其相对位置。,图10.27 可以看到,C语言规定数组下标从0开始,对计算上述相对位置比较

55、方便,只要知道i和j的值,就可以直接用im+j公式计算出aij相对于数组开头的相对位置。如果规定下标从1开始(如FORTRAN语言),,则为计算aij的相对位置所用的公式就要改为(i-1)m+(j-1)。这就增加了计算的工作量,而且不直观。 (2) 指向由m个元素组成的一维数组的指针变量。 上例的指针变量p是指向整型变量的,p+1所指向的元素是p所指向的元素的下一元素。可以改用另一方法,使p不是指向整型变量,而是指向一个包含m个元素的一维数组。这时,如果p先指向a0(即p= int (*p)4,i,j; p=a; scanf( i=%d,j=%d, 运行情况如下: i=1,j=2(本行为键盘输

56、入) a1,2=13,注意应输入“i=1,j=2”,以与scanf函数中指定的字符串相对应。 程序第3行“int(*p)4”表示p是一个指针变量,它指向包含4个元素的一维数组。注意*p两侧的括号不可缺少,如果写成*p4,由于方括号运算级别高,因此p先与4结合,是数组,然后再与前面的*结合,*p4是指针数组(见10.7节)。有的读者感到“(*p)4”这种形式不好理解。可以对下面二者做比较: int a4;(a有4个元素,每个元素为整型) int (*p)4;,第种形式表示*p有4个元素,每个元素为整型。也就是p所指的对象是有4个整型元素的数组,即p是行指针,见图10.29。应该记住,此时p只能指

57、向一个包含4个元素的一维数组,p的值就是该一维数组的首地址。p不能指向一维数组中的第j个元素。,图10.29,程序中的p+i是二维数组a的第i行的地址(由于p是指向一维数组的指针变量,因此p加1,就指向下一个一维数组)。见图10.30。*(p+2)+3是a数组第2行第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,

58、地址就增加8个字节(4个元素,每个元素2个字节),而(p+2)+3括弧外的数字3,不是以p所指向的一维数组为长度单位的。而是采用p所指向的一维数组内部各元素的长度单位了,,加3就是加(32)个字节。p+2和(p+2)具有相同的值,但(p+2)+3和(p+2)+3的值就不相同了。这和上一节所叙述的概念是一致的。,图10.30,3. 多维数组的指针作函数参数 一维数组的地址可以作为函数参数传递,多维数组的地址也可作函数参数传递。在用指针变量作形参以接受实参数组名传递来的地址时,有两种方法: 用指向变量的指针变量; 用指向一维数组的指针变量。 例10.14有一个班,3个学生,各学4门课,计算总平均分

59、数,以及第n个学生的成绩。 这个题目本来是很简单的。只是为了说明用多维数组指针作函数参数而举的例子。用函数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

60、*p,int n),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.25 the score of No.2 are: 90.0099.00100.

61、0098.00,在函数main中,先调用average函数以求总平均值。在函数average中形参p被声明为指向一个实型变量的指针变量。用p指向二维数组的各个元素,p每加1就改为指向下一个元素,见图10.31。相应的实参用*score,即score0,它是一个地址,指向score00 元素。用形参n代表需要求平均值的元素的个数,实参12表示要求12个元素值的平均值。函数average中的指针变量p指向score数组的某一元素(元素值为,图10.31,一门课的成绩)。sum是累计总分,aver是平均值。在函数中输出aver的值,故函数无需返回值。 函数search的形参p不是指向一般实型变量的指

62、针变量,而是指向包含4个元素的一维数组的指针变量。实参传给形参n的值为2,即找序号为2的学生的成绩(3个学生的序号分别为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在上题基础上,查找有一门以上课程不及格的学生,打印出他们的全部课程的成绩。程序如下: main() void search(float (*p)4,int n);

63、 /*函数声明*/ 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); for(i=0;i4;i+),printf(%5.1f,*(*(p+j)+i); printf(n); 程序

64、运行结果如下: No.1 fails,his scores are: 65.057.070.060.0 No.2 fails,his scores are: 58.0 87.0 90.0 81.0,在函数search中,flag是作为标志不及格的变量。先使flag=0,若发现某一学生有一门不及格,则使flag=1。最后用if语句检查flag,如为1,则表示该学生有不及格的纪录,输出其全部课程成绩。变量j代表学生号,i代表课程号。 通过指针变量存取数组元素速度快,且程序简明。用指针变量作形参,可以允许数组的行数不同。因此数组与指针常常是紧密联系的,使用熟练的话可以使程序质量提高,且编写程序方便灵

65、活。,10.4.1 字符串的表示形式 在C程序中,可以用两种方法访问一个字符串。 (1) 用字符数组存放一个字符串,然后输出该字符串。 例10.16 main() char string=I love China!; printf(%Sn,string); ,10.4 字符串的指针和指向字符串的指针变量,运行时输出: I love China! 和前面介绍的数组属性一样,string是数组名,它代表字符数组的首地址(见图10.32)。 string4代表数组中序号为4的元素(v),实际上string4就是*(string+4),string+4是一个地址,它指向字符“v”。,图10.32,(2) 用字符指针指向一个字符串。 可以不定义字符数组,而定义一个字符指针。用字符指针指向字符串中的字符。 例10.17 main() charstring=I love China!; pri

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

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!