第8章一维数组的应用8.1了解一维数组8.2一维数组的简单应用8.3利用地址和指针访问数组元素8.4与一维数组有关的参数传递8.5一维数组操作中的常用算法介绍8.1了解一维数组8.1.1一维数组的用途8.1.2一维数组的定义在C语言中,数组必须先定义才可以使用。当定义数组时,要传递给编译器两方面的信息:①数组共有多少个元素?②每个元素占多少个字节?根据以上信息,编译器决定分配多大的存储空间给该数组使用。例如:inta[10];这里a是数组的名称,方括号中的10表明数组一共有10个元素,下标应该从0开始到9结束;类型名int限定数组a的每个元素中只能存放整型数。根据这一定义,系统将为数组a开辟能容纳10个整型数的连续存储单元。一维数组定义语句的语法形式为:类型名数组名[常量表达式],……;说明(1)“类型名”决定了数组中可以存放的数据的类型。(2)“数组名”和变量名相同,必须遵循标识符的命名规则。(3)“常量表达式”代表的是数组元素的个数,也就是数组的长度。它必须是无符号整型常量,不允许是0、负数和浮点数,也不允许是变量。(4)C语言中规定:每个数组第一个元素的下标固定为0,称为下标的下界;最后一个元素的下标为元素个数减1,称为下标的上界。(5)数组的定义可以和普通变量的定义出现在同一个定义语句中。例如:floatk,x[5],y[20];以上语句在定义单精度变量k的同时,定义了两个单精度型的一维数组x和y。数组x共有5个元素,下标的使用范围是0~4;数组y共有20个元素,下标的使用范围是0~19。8.1.3一维数组元素的引用数组一经建立,在内存中就占据着一串连续的存储单元。8.1.4一维数组的初始化8.2一维数组的简单应用例8.1编写程序,在数组x中存储自然数1~20,然后按以下要求输出数据:①按逆序分两行输出元素值。②在一行上输出所有下标为偶数的数组元素。③在一行上输出所有值为偶数的数组元素。问题分析本例题涉及的是对数组元素进行操作的基本算法。对一维数组各元素的访问,通常是在单重循环中实现。通过循环变量与循环体内语句的配合,可以灵活地、有选择地访问指定元素。读者在阅读以下程序时,应理解数组元素下标与数组元素值的区别;掌握如何用循环变量控制数组元素的下标;以及如何在连续输出的过程中控制输出换行。源程序如下:main(){intx[20],i;for(i=0;i20;i++)/*为数组赋值*/x[i]=i+1;printf(分两行逆序输出:\n);for(i=19;i=0;i––)/*控制从数组尾部开始输出*/{printf(%3d,x[i]);if(i%10==0)printf(\n);/*控制每输出10个元素后换行*/}printf(输出下标为偶数的元素:\n);for(i=2;i20;i+=2)printf(%3d,x[i]);printf(\n);printf(输出值为偶数的元素:\n);for(i=0;i20;i++)if(x[i]%2==0)printf(%3d,x[i]);printf(\n);}程序的运行结果如下:分两行逆序输出:2019181716151413121110987654321输出下标为偶数的元素:35791113151719输出值为偶数的元素:24681012141618208.3利用地址和指针访问数组元素8.3.1数组名、元素地址及指针的关系通过前面的知识积累和本节的进一步介绍,读者应该建立起如下概念。(1)数组是一种构造数据类型。数组名代表着这个数组所占连续存储空间的起始地址。这个地址是在定义数组时由系统所分配的,不可以人为改变。因此,可以认为数组名是一个地址常量。如有定义:intx[5],y[5],m=3;则语句:x=&m;x=y;y++;都是错误的。数组名x和y作为地址常量可以使用,不可以重新赋值。而表达式:x+1、y+2则是合法的。它们表示以数组名为首地址增加一个偏移量后的地址值。(2)就整体而言,每个数组元素都是数组这个集合中的一分子,由于数组所占地址空间是连续的,通过数组名这个首地址就可以找到数组中的所有元素;就个体而言,每个数组元素都可以看作是一个带下标的变量,它完全可以像普通变量一样进行求地址运算。因此,用数组名表示的地址与数组元素的地址之间就有如下关系:x+1等价于&x[1]、y+2等价于&y[2](3)指针变量是用来存放地址值的,它可以通过加、减一个整数在一串连续的存储单元中移动,并可以利用间接访问运算符得到指针所指单元的内容。因此,数组名、数组元素和指针变量之间可以通过数组元素的地址建立起关系。若有如下定义:inta[6]={1,2,3,4,5,6},*p;则语句:p=a;等价于p=&a[0];都使指针变量p中存放了数组a的首地址,即p指向了数组a的第一个元素a[0]。在这一前提下,表达式:*p等价于*a都代表数组的第一个元素a[0]。而:*(p+2)等价于*(a+2)都代表数组的第三个元素a[2]。语句:p++;表示将指针p移动一个位置,指向数组a的第二个元素a[1]。但语句:a++;则是非法的,因为a是常量。由此可见:数组名是地址常量;指针是存放地址的变量,它们都可以用来描述数组元素的地址,进而可以得到元素的内容。所以,我们不仅可以直接用带下标的变量形式来访问数组元素,还可以通过数组的首地址(数组名)和指针来访问数组元素,从而大大地增加了数组使用的灵活性,但也同时增加了初学者全面掌握C语言的难度。8.3.2通过数组首地址访问数组元素设有如下定义:intx[10],i;通过上一节的叙述已知:数组名是数组的首地址,从而有:x+0等价于&x[0]x+1等价于&x[1]…x+i等价于&x[i]在得到地址后,可以通过间接访问运算符来引用地址所在的存储单元。因此有:*(x+0)或*x等价于*&x[0]即x[0]*(x+1)等价于*&x[1]即x[1]…*(x+i)等价于*&x[i]即x[i]如果要利用数组首地址对x数组的所有元素逐个输入输出,则可由以下程序段实现:for(i=0;i10;i++)scanf(%d,x+i);for(i=0;i10;i++)printf(%3d,*(x+i));注意:在上面的程序段中,我们只是使用了代表数组首地址的数组名x,并没有(也不允许)改变它的值。8.3.3通过指针访问数组元素通过地址常量——数组名可以访问数组元素,显然,通过存放地址的变量——指针也可以访问数组元素,而且更具灵活性。若有如下定义:intx[10],*p,i;在执行了语句:p=x;或p=&x[0];后,指针p中存放的是数组x的首地址。如果不移动指针,也可以采用首地址+位移量的形式逐个输入输出数组元素:p=x;for(i=0;i10;i++)scanf(%d,p+i);for(i=0;i10;i++)printf(%3d,*(p+i));在上一小节的讨论中,我们曾经得出结论:*(x+i)等价于x[i]。细心的读者可能会问:在这里*(p+i)是否也等价于p[i]呢?确实如此。事实上方括号[]也是一种运算符。概括起来,如果有如下定义和语句:intx[10],*p,i;p=x;则表示数组x中的元素可以有以下四种形式:①x[i]②*(x+i)③*(p+i)④p[i]当然,这里的i必须满足条件:0≤i<10,否则下标就越界了。与数组名不同,指针是变量,它可以通过赋值语句移动位置。因此可以采用以下方法,通过顺序移动指针来逐个输入输出数组元素:p=x;for(i=0;i10;i++){scanf(%d,p);p++;}p=x;for(i=0;i10;i++){printf(%3d,*p);p++;}注意:由于在输入时移动了指针,输出前还应通过赋值操作使指针再次指向数组的第一个元素。8.4与一维数组有关的参数传递8.4.1数组元素作实参数组元素就是带下标的变量。所以,数组元素作实参与简单变量作实参一样,对应的形参应该是同类型的简单变量。例8.3编写程序:利用随机函数产生6个50以内的整数存入数组a中,然后调用自定义函数isodd依次判断数组a中的元素是否为奇数。程序如下#includestdlib.hintisodd(intx)/*形参是与实参相同类型的普通变量x*/{if(x%2==1)return1;elsereturn0;}main(){inta[6],i;for(i=0;i6;i++){a[i]=rand()%50;printf(%4d,a[i]);}printf(\n);for(i=0;i6;i++)if(isodd(a[i]))/*数组元素a[i]作实参*/printf(a[%d]isodd.\n,i);elseprintf(a[%d]isnotodd.\n,i);}运行结果如下:46303240617a[0]isnotodd.a[1]isnotodd.a[2]isnotodd.a[3]isnotodd.a[4]isnotodd.a[5]isodd.8.4.2数组名作实参数组名作为实参传递时,传送给形参的是数组的首地址。因此,对应的形参必须是基类型相同的指针变量。例8.4编写程序:利用自定义函数ArrIn和ArrOut实现数组元素的输入和输出。源程序如下:#defineN100voidArrIn(int*x,intn)/*对应的形参x是指针变量*/{inti;for(i=0;in;i++)scanf(%d,x+i);}voidArrOut(int*x,intn){inti;for(i=0;in;i++){printf(%4d,*(x+i));if((i+1)%5==0)printf(\n);/*控制输出5个元素后换行*/}}main(){inta[N];ArrIn(a,20);/*数组名a作实参*/ArrOut(a,10);}主函数中调用ArrIn和ArrOut函数时,都将数组名a作为实参传递给了形参指针x,使x得到了主函数中所定义的数组a的首地址,仍旧遵循了“按值”传递这惟一的参数传递原则。接下来,在自定义函数中,通过指针x所操作的就是主函数中的数组a。另外,在主函数中定义的数组a一共可以容纳100个整数,但在调用ArrIn函数时,将常量20传给了形参n,因此只给数组的前20个元素输入了数据。调用ArrOut函数时,则只输出了数组a的前10个元素。需要说明的是:当数组名作为实参时,对应的形参应该是指针,但还允许其他表示形式。以ArrIn函数为例,对于调用语句:ArrIn(a,20);,相应的函数首部允许是以下三种形式:①voidArrIn(int*x,intn)②voidArrIn(intx[],intn)③voidArrIn(intx[N],intn)无论表示形式如何,C编译器都将x按指针处理。8.4.3数组元素的地址作实参数组元素的地址作实参,与数组名作实参一样,传递的也是地址值,所以对应的形参也应当是基类型相同的指针变量。但这种方式带给了程序设计者更大的灵活度。我们在不改变例题8.4中自定义函数的前提下,通过传递数组元素的地址,可以实现对主函数中数组a的任意一段连续空间中数组元素的输入和输出。例如:#defineN100voidArrIn(int*x,intn){inti;for(i=0;in;i++)scanf(%d,x+i);}…main(){inta[N];ArrIn(&a[10],10);/*调用函数为a[10]~a[19]输入数据*/ArrIn(&a[50],5);/*调用函数为a[50]~a[54]输入数据*/…}结合主函数中的调用语句ArrIn(&a[10],10);可以看出:(1)第1个实参&a[10]是数组元素a[10]的地址,将它对应传给函数ArrIn中的形参x,使x指向了数组元素a[10],用来控制输入数据时存放的起始地址;(2)第2个实参10对应传递给了函数ArrIn的形参n,用来控制为10个元素输入数据;(3)在函数ArrIn中,由于指针变量x初始指向的是数组元素a[10],所以当i=0