第 8章 一维数组的应用
8.1 了解一维数组
8.2 一维数组的简单应用
8.3 利用地址和指针访问数组元素
8.4 与一维数组有关的参数传递
8.5 一维数组操作中的常用算法介绍
8.1 了解一维数组
8.1.1 一维数组的用途
8.1.2 一维数组的定义在 C语言中,数组必须先定义才可以使用 。 当定义数组时,要传递给编译器两方面的信息:
① 数组共有多少个元素?
② 每个元素占多少个字节?
根据以上信息,编译器决定分配多大的存储空间给该数组使用 。 例如:
int a[10];
这里 a是数组的名称,方括号中的 10表明数组一共有 10个元素,下标应该从 0开始到 9结束;类型名 int限定数组 a的每个元素中只能存放整型数 。 根据这一定义,系统将为数组 a开辟能容纳 10个整型数的连续存储单元 。
一维数组定义语句的语法形式为:
类型名 数组名 [常量表达式 ],…… ;
说明
( 1),类型名,决定了数组中可以存放的数据的类型 。
( 2),数组名,和变量名相同,必须遵循标识符的命名规则 。
( 3),常量表达式,代表的是数组元素的个数,也就是数组的长度 。 它必须是无符号整型常量,不允许是 0,负数和浮点数,也不允许是变量 。
( 4) C语言中规定:每个数组第一个元素的下标固定为 0,称为下标的下界;最后一个元素的下标为元素个数减 1,称为下标的上界 。
( 5) 数组的定义可以和普通变量的定义出现在同一个定义语句中 。 例如:
float k,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( )
{ int x[20],i;
for(i=0;i<20;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; i<20; i+=2)
printf("%3d",x[i]);
printf("\n");
printf("输出值为偶数的元素,\n");
for(i=0;i<20;i++)
if(x[i]%2==0) printf("%3d",x[i]);
printf("\n");
}
程序的运行结果如下:
分两行逆序输出:
20 19 18 17 16 15 14 13 12 11
10 9 8 7 6 5 4 3 2 1
输出下标为偶数的元素:
3 5 7 9 11 13 15 17 19
输出值为偶数的元素:
2 4 6 8 10 12 14 16 18 20
8.3 利用地址和指针访问数组元素
8.3.1 数组名,元素地址及指针的关系通过前面的知识积累和本节的进一步介绍,读者应该建立起如下概念 。
( 1) 数组是一种构造数据类型 。 数组名代表着这个数组所占连续存储空间的起始地址 。
这个地址是在定义数组时由系统所分配的,不可以人为改变 。 因此,可以认为数组名是一个地址常量 。
如有定义:
int x[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) 指针变量是用来存放地址值的,
它可以通过加,减一个整数在一串连续的存储单元中移动,并可以利用间接访问运算符得到指针所指单元的内容 。 因此,数组名,数组元素和指针变量之间可以通过数组元素的地址建立起关系 。 若有如下定义:
int a[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 通过数组首地址访问数组元素设有如下定义:
int x[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; i<10; i++)
scanf("%d",x+i);
for(i=0; i<10; i++)
printf("%3d",*(x+i));
注意:在上面的程序段中,我们只是使用了代表数组首地址的数组名 x,并没有
( 也不允许 ) 改变它的值 。
8.3.3 通过指针访问数组元素通过地址常量 ——数组名可以访问数组元素,显然,通过存放地址的变量 ——
指针也可以访问数组元素,而且更具灵活性 。
若有如下定义:
int x[10],*p,i;
在执行了语句,p=x;或 p=&x[0];后,
指针 p中存放的是数组 x的首地址 。
如果不移动指针,也可以采用首地址
+位移量的形式逐个输入输出数组元素:
p=x;
for(i=0; i<10; i++)
scanf("%d",p+i);
for(i=0; i<10; i++)
printf("%3d",*(p+i));
在上一小节的讨论中,我们曾经得出结论,*(x+i)等价于 x[i]。 细心的读者可能会问:在这里 *(p+i)是否也等价于 p[i]呢?
确实如此 。 事实上方括号 [ ]也是一种运算符 。
概括起来,如果有如下定义和语句:
int x[10],*p,i;
p=x;
则表示数组 x中的元素可以有以下四种形式:
① x[i] ② *(x+i) ③ *(p+i) ④ p[i]
当然,这里的 i必须满足条件,0≤i<
10,否则下标就越界了 。
与数组名不同,指针是变量,它可以通过赋值语句移动位置 。 因此可以采用以下方法,通过顺序移动指针来逐个输入输出数组元素:
p=x;
for(i=0; i<10; i++)
{ scanf("%d",p); p++; }
p=x;
for(i=0; i<10; i++)
{ printf("%3d",*p); p++; }
注意:由于在输入时移动了指针,输出前还应通过赋值操作使指针再次指向数组的第一个元素 。
8.4 与一维数组有关的参数传递
8.4.1 数组元素作实参数组元素就是带下标的变量 。 所以,
数组元素作实参与简单变量作实参一样,
对应的形参应该是同类型的简单变量 。
例 8.3 编写程序:利用随机函数产生
6个 50以内的整数存入数组 a中,然后调用自定义函数 isodd依次判断数组 a中的元素是否为奇数 。
程序如下
#include "stdlib.h"
int isodd(int x)
/* 形参是与实参相同类型的普通变量 x */
{ if(x%2==1) return 1;
else return 0;
}
main( )
{ int a[6],i;
for(i=0; i<6; i++)
{ a[i]=rand( )%50;
printf("%4d",a[i]);
}
printf("\n");
for(i=0; i<6; i++)
if(isodd(a[i]))
/* 数组元素 a[i]作实参 */
printf("a[%d] is odd.\n",i);
else
printf("a[%d] is not odd.\n",i);
}
运行结果如下:
46 30 32 40 6 17
a[0] is not odd.
a[1] is not odd.
a[2] is not odd.
a[3] is not odd.
a[4] is not odd.
a[5] is odd.
8.4.2 数组名作实参数组名作为实参传递时,传送给形参的是数组的首地址 。 因此,对应的形参必须是基类型相同的指针变量 。
例 8.4 编写程序:利用自定义函数
ArrIn和 ArrOut实现数组元素的输入和输出 。
源程序如下:
#define N 100
void ArrIn(int *x,int n)
/* 对应的形参 x是指针变量 */
{ int i;
for(i=0; i<n; i++)
scanf("%d",x+i);
}
void ArrOut(int *x,int n)
{ int i;
for(i=0;i<n;i++)
{ printf("%4d",*(x+i));
if((i+1)%5==0) printf("\n");
/* 控制输出 5个元素后换行 */
}
}
main( )
{ int a[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);,相应的函数首部允许是以下三种形式:
① void ArrIn(int *x,int n)
② void ArrIn(int x[ ],int n)
③ void ArrIn(int x[N],int n)
无论表示形式如何,C编译器都将 x按指针处理 。
8.4.3 数组元素的地址作实参数组元素的地址作实参,与数组名作实参一样,传递的也是地址值,所以对应的形参也应当是基类型相同的指针变量 。
但这种方式带给了程序设计者更大的灵活度 。
我们在不改变例题 8.4中自定义函数的前提下,通过传递数组元素的地址,可以实现对主函数中数组 a的任意一段连续空间中数组元素的输入和输出 。
例如:
#define N 100
void ArrIn(int *x,int n)
{ int i;
for(i=0; i<n; i++)
scanf("%d",x+i);
}

main( )
{ int a[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
时,scanf语句中的 x+0就是 a[10]的地址;
当 i=1时,scanf语句中的 x+1就是 a[11]的地址; …… 从而实现为 a[10]~ a[19]这 10个元素输入数据的目的 。
8.5 一维数组操作中的常用算法介绍
8.5.1 查找查找是数据处理中最基本且重要的操作之一 。 最常用的算法有,找最大 (小 )
值,,,顺序查找,指定值和,二分法查找,指定值等 。
8.5.2 插入插入是数组的基本操作之一 。 在实际应用中,有时要求,在某一数值前 ( 或后 )
插入一个元素,;有时则需要,在有序表中插入一个元素,插入后仍保持表有序,。
不论是哪一种情况,都首先要找到应插入的位置,这一操作可通过前面介绍的查找算法实现 。 因此,插入操作的关键应该是如何实现,在指定位置上插入一个元素,。
8.5.3 删除删除也是一维数组的基本操作之一 。
常见的应用有,删除值为 x的元素,,,删除数组中值相同的元素 ( 去偶 ),等 。 和插入相似,删除也应先查找到待删元素的位置,下一步的关键就是,删除指定位置上的元素,。
8.5.4 排序排序是计算机程序设计中的一种重要操作,其功能是将一个无序的数据序列调整为有序 。 在计算机系统中,花在排序上的时间,在 CPU的运行时间中占有很大比重,应用十分广泛,也因此而产生了很多比较成熟的算法 。 常见的有:比较交换法,
冒泡法,选择法,插入法,希尔法以及快速排序等 。