1
第 7章 数组
2
一个变量对应一个存储区域,用于处理个别的数据。
然而,在程序设计中更多的情况则是要处理成批的数据,如果仅用单一的变量来处理成批的数据,那是不现实、也是不方便的。 为此,高级程序设计语言都提供一种能方便地表示和处理成批数据的手段 ── 数组 。
数组是存放在一片 连续 的存储单元中的,有序 非空的、
有 相同 数据类型并有一个 名字 以供识别的数据集合。 这个集合中的成员称为数组元素。
数组不是简单数据类型,而是一种构造数据类型。本章将讨论C语言中数组的定义与使用。
3
7.1 一维数组的定义和引用与使用变量的要求相同,C语言程序中使用数组之前也必须预先定义数组。
按数组的定义,要定义一个数组必须向编译程序提供如下信息:
a) 给数组指定一个名字。
b) 规定数组中数据元素的类型;
c) 指定数组中数据元素的个数;
4) 规定数组中各元素的表示、存储及排列方式。
所有这些信息都通过如下形式的数组声明语句给出:
4
1、一维数组的声明格式存储类型 数据类型 数组名 [e]= { 初始化值表 } ;
其中:,={初始化值表 },,,存储类型,可缺省。
e 是 常量表达式,e 的值为所声明的数组包含的元素个数,也称为数组的大小。无初始化值表时,e 不可缺省;有初始化值表时 e 可缺省,缺省时数组的元素个数由不是,={初始化值表 }” 中给出的初始化数据个数确定。例,#define SIZE 100
int a[100],b[ ] = { 1,2,3 } ;
char c[SIZE],d[20*SIZE] ;
5
存储类型 与 数据类型其作用、意义与变量声明中的含义相同。
数组名第二,从分配给数组的存储空间的意义上来讲它总是表示或指向分配给数组的一片连续存储空间的起始位臵,因此它是一个 地址量,而且是一个 地址常量。
第一,在程序中用来标识一个数组及数组元素所在的数组;
数组的名字按标识符命名规则确定。 与变量相同,
也有其作用域。另外,它有两层含义:
6
2,一维数组元素的表示、排列与存储一维数组用来表示一维向量,它的每个元素以如下形式表示:
数组名 [下标 ]
其中,
,下标,用来指出一个数组元素在一维数组中的排列序号。一维数组中的每个数组元素要用一个下标来表示。 下标是一个表达式,这样的表达式的值应该是一个正整数且满足:
0 ≤ 下标 < 数组大小这就是说,一维数组中元素的序号 从 0开始,最大序号为 数组元素个数 -1。
7
,[ ]” 也是一种运算符,称为下标运算符,它的操作数就是 下标 和 数组名 。因此,数组名 [下标 ]” 本身是一个表达式,称为下标表达式,它在对数组元素的访问中引起对数组元素的下标计算,计算的结果就是要访问的数组元素在该数组存储区域中的地址。,[ ]” 运算符与,( )”
有相同的优先级,优先级最高。
假定有如下的一维数组定义:
int x[10] ;
那么组成 x数组元素的 排列顺序 分别是:
x[0],x[1],x[2],.....,x[9]
而数组 x的元素在内存中的 存储顺序 则是:
8
显然,一维数组元素的排列顺序和它的存储顺序是一致的。 一维数组的第一个元素 x[0]的起始存储地址 &x[0]
即为 x所代表的地址。而 x[1],x[2],… … 等的起始存储地址要由数组元素的数据类型来决定。
9
3,一维数组元素的初始化与变量类似,C语言也允许在定义数组时给数组中的元素指定初始值。 带有初始化值的数组定义的一般形式如下:
存储类型 数据类型 数组名 [e]={常量表达式表 };
其中,,常量表达式表,由一系列用逗号隔开的常数或常量表达式组成,列出的常数即是给数组中元素指定的初始化值。 列出的常量顺序就是数组元素在内存中的存储顺序 。例如:
int x[10]={ 1,2*2,3,4,5,6,7,8,9,10+1 } ;
定义了一个带有初始化值的、具有 10个数组元素的整型数组,各个数组元素具有的值是,x[0]为 1,x[1]为 4、
x[2] 为 3,.....,x[9] 为 11。
10
关于一维数组的初始化,应注意以下几点:
1) 定义带有初始化值的一维数组时可以不指定维的大小(,[ ]” 中为空,但,[ ]” 不能省略 ),此时维的大小由给出的初始化值的个数确定。例如:
int a[ ] = { 1,2,3,4,5 } ;
定义了具有五个元素的一维数组,并且 1 赋给 a[0],2
赋给 a[1],3 赋给 a[2] 等等。
2) 给出的初始化数据个数 不能多于 定义数组的元素个数。例如:
int a[5] = { 1,2,3,4,5,6 } ; (错误 )
3) 若给出的初始化数据个数 少于 定义的数组的元素个数,则剩余的元素被自动初始化成 0。例如:
11
int a[100] = { 0 } ; /* 全部元素臵初值为 0 */
4) 声明数组时若未给出初始化值,则对于,
全局数组、静态全局数组、静态局部数组 的全部元素将由系统 自动初始化为 0值;
动态局部数组 全部元素的初值 不确定,不能直接使用。
5) 不能以如下形式初始化数组元素。
int a[5] = { 1,2,,4,5 } ;
12
4、一维数组元素的引用
a) 引用格式:
数组名 [下标 ]
,下标,指出引用哪一个数组元素。与定义时不同,
这里的 下标 是 可含有变量 的整型表达式。
数组元素的引用形式实际上是一个表达式(因为其中的,[ ]” 是运算符 ),其值是引用的元素在数组中的位臵。
但它与一般的表达式不同,它可以像变量一样出现在赋值号的左边对它赋值,可以对它实施 ++与 -- 操作。
像这种既有表达式的性质又有变量性质的表达式,我们把它们称之为 左值表达式 。
13
b),下标,的值应满足,
0≤ 下标 < 数组大小但请注意:
C编译程序并不检查这个关系式的成立与否。换句话说,C编译程序并不检查数组下标的越界错误。防止与检查越界错误完全是程序设计者的责任。
下标越界错误会破坏相邻存储区中的内容,甚至会危及到系统程序。
c) 更需注意的是,C编译程序甚至对,下标,为 负值 的情况也认为是合法的。如使用 x[-5]来访问数组 x中的某个元素,则把 x[0] 的存储位臵往左的第 5个元素作为 x[-5] 来使用,但这显然产生越出了数组左边界的错误。
14
d) 引用数组元素的下标应是在维界范围内的整型值,但
C语言并没有这个限制,数组元素的下标可以是任何表达式,其值也并非一定要为正整型值,如前面所讲的,
还可以是负值,甚至允许是一个浮点数,若是浮点数会被 自动地转换成为整型,转换方法只是简单地将小数部分去掉。
e) 数组元素的使用与变量的使用是类似的,数组元素既可出现在赋值号的左边对其进行赋值,也可以出现在赋值号右边的表达式中作为一个运算分量参与表达式的运算。
15
main ( )
{
int a[10]={ 1,2,3,4,5,6,7,8,9,10 },t,i ;
for ( i=0 ; i<5 ; i++ ) {
t = a[i] ;
a[i] = a[9-i] ;
a[9-i] = t ;
}
for ( i=0 ; i<10 ; i++ )
printf (,%d”,a[i] ) ;
}
程序输出,10 9 8 7 6 5 4 3 2 1
5,一维数组应用例例 1,将 a 数组中的元素逆序。
16
#include <stdio.h>
main ( )
{
int i,j,jmax ;
float sort[10],temp ;
for ( i=0 ; i<10 ; i++ )
scanf (,%f”,&sort[i] ) ;
for ( i=0 ; i<9 ; i++ ) {
jmax=i ;
for ( j=i+1 ; j<10 ; j++ )
if ( sort[j] > sort[jmax] )
jmax = j ;
temp = sort[i] ;
sort[i] = sort[jmax] ;
sort[jmax] = temp ;
}
for ( i=0 ; i<10 ; i++ )
printf (,%10.2f\n,,sort[i] ) ;
}
例 2 对一维数值数组中的所有元素从大到小排序 。
/*读入 数组的各元素 */
/* 排序 */
/* 输出 */
17
若程序的输入,
12.1 23.345 33.4 -12.2 678.51 0.423 -333.123 111.23 876.45 55
则程序的输出,
876.45
678.51
111.23
55.00
33.40
23.34
12.10
0.42
-12.20
-333.12
18
7.2 二 维数组的定义和引用
1、声明格式存储类型 数据类型 数组名 [e1][e2] = {初始化值表 } ;
其中,初始化值表,存储类型 均可缺省;
e1,e2 为 常量表达式,分别表示二维数组的行数和列数。无初始化值表时,e1,e2 均不可缺省 ; 有初始化值表时,e1可缺省,e2不可缺省 ;
数组名 的含义与一维数组相同。
二维数组大小(元素个数) = e1*e2
二维数组占用内存大小 = e1*e2*sizeof(数据类型 )
例,#define ROW 3
#define COL 4
int a[2][3],b[ROW][COL];
19
A00 A01 A02
A10 A11 A12
A20 A21 A22
2,二维数组元素的表示、排列与存储二维数组用来表示一个矩阵。假定有一个 3× 3 的矩阵
A,矩阵中的每个元素 Ai j都是浮点数。
那么,可将矩阵 A定义成一个二维数组:
float a[3][3] ;
数组中的每一个元素以如下形式表示:
数组名 [下标 1][下标 2]
20
二维数组 a中的元素表示、排列顺序与原矩阵中元素的对应关系是:
a[0][0] a[0][1] a[0][2]
a[1][0] a[1][1] a[1][2]
a[2][0] a[2][1] a[2][2]
这些数组元素在分配给数组 a 的存储区域中是 按矩阵的行被顺序存储 的。即先顺序存储矩阵第一行的元素,紧接着再顺序存储矩阵第二行的元素,最后顺序存储矩阵的第三行的元素。下图展示了这个存储顺序:
21
其中:
,下标 1” 指出二维数组元素的第一维的下标,表示数组元素在矩阵中的行;
,下标 2” 指出二维数组元素的第二维的下标,表示数组元素在矩阵中的列。
可见,二维数组的一个元素需要两个下标来表示。
,下标 1” 和,下标 2” 与前面解释的一维数组的下标一样,它们的值都应该是正整数,且正确的二维数组元素的下标应满足:
0 ≤ 下标 1 < 第一维的大小
0 ≤ 下标 2 < 第二维的大小
22
实际上,根据二维数组的定义形式及它的元素排列和存储形式,C语言允许把一个二维数组作为一种特殊的一维数组来看待和处理。如对于二维数组定义:
int a[4][5]
可以把该二维数组的 数组名 与它的第一维 [4] 合起来,看作有四个元素的特殊一维数组,这四个元素是 a[0],a[1]、
a[2],a[3],分别代表矩阵的第一、二、三、四行元素的起始存放地址。 从这个意义上说,元素 a[i]的含义相当于普通一维数组的数组名。
a[0]?&a[0][0]
a[1]?&a[1][0]
a[2]?&a[2][0]
a[3]?&a[3][0]
23
因此,它们也是一个地址量。对普通一维数组名能进行的操作同样也能对这里的 a[i]进行。因为 a[i]相当于一个普通一维数组名。
按一维数组定义,则可把该二维数组的第二维
[5],看作为特殊数组名 a[i] 中所包含的数组元数个数,
且每个元素表示为:
a[i][0],a[i][1],a[i][2],a[i][3],a[i][4]
24
上边的讨论可以推广到一般,对于任意一个 n× m大小的二维数组定义,可将其理解为有 n 个一维数组,且每个这样的一维数组都有 m个元素。其中 n,m 分别为二维数组第一维和第二维的大小。例如:
int a[n][m] ; ( n,m为整常数表达式 )
可理解为:
25
有了二维数组的概念,应不难理解二维以上的多维数组的定义。与二维数组一样,可以把二维以上的数组逐步简化为特殊的一维数组看待。如对于三维数组,
int a[2][3][4] ;
可理解成:
26
其中的 a,a[i] 和 a[i][j] 都是地址量,表示相应存储区域的首地址。如可把 a[i][j] 看作为有四个元素的一维数组的首地址。
三维数组的元素在内存中的存储顺序是,
先存储以 a[0][0]为首址的四个元素,然后分别顺序存储 a[0][1],a[0][2],a[1][0],a[1][1] 为首址的四个元素,
最后存储 a[1][2]为首址的四个元素。
总之,多维数组元素的存储顺序是,先变化最右边那一维的下标,最后变化最左边那一维的下标 (第一维 )。或者说,第一维的下标变化最慢,最右边那一维的下标变化最快。
27
带有初始化值的数组定义的一般形式如下:
存储类型 数据类型 数组名 [e1][e2] = { 常量表 } ;
3、二维数组元素的初始化与变量类似,可以在定义数组时给数组中的元素指定初始值。
如果定义数组的时候没有给数组中的元素指定初始化值,则C编译程序在编译源程序时会对 全局和 static 型 数组中的元素自动地臵标准初值 0,使这些数组元素成为有定义的,因此,程序中可直接使用这类数组的元素。
28
例如:
int a[3][5]= { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 } ;
则数组各行上的元素分别为:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
29
二维数组初始化的,常量表,也允许由逗号隔开的
,子常量表,组成。
上面二维数组定义也可以写成如下形式:
int a[3][5] = {
{ 1,2,3,4,5 },
{ 6,7,8,9,10 },
{ 11,12,13,14,15 }
} ;
这里,常量表,由 3 个,子常量表,组成,它们分别对应于数组 a 的每一行上的数组元素。
用这种方式给出数组元素的初始化值不易出错、遗漏,
也便于检查。
30
关于二维数组的初始化,应注意以下几点:
1) 定义带有初始化值的数组时,可以不指定第一维的大小 (第一维的,[ ]” 中为空 ),但,[ ]” 不能省略,此时这一维的大小由给出的初始化值的个数确定。如,
int a[ ][3][4] = { { 1,1,1,1 },{ 2,2,2,2 },
{ 3,3,3,3 },{ 4,4,4,4 },
{ 5,5,5,5 },{ 6,6,6,6 }
} ;
根据给出的常数个数可知,这个三维数组的第一维大小为 2。该定义当然也可以直接写成,
int a[ ][3][4]={ 1,1,1,1,2,2,2,2,3,3,3,3,
4,4,4,4,5,5,5,5,6,6,6,6 } ;
31
2) 允许在定义数组时仅给数组中的部分元素指定初始化值。
例如:
① int c[3][3] = { {1},{1},{1} } ;
② int d[3][3] = { {1},{0,1},{0,0,1} } ;
它们的效果是:
32
4) 不允许 如下形式的初始化:
int a[3][4] = { {1},{ },{9} } ;
3),常数表,中给出的 初始化值仅能是常量或常量表达式,
且给出的初始化常数个数 不能多于 数组中数组元素的个数。
33
4、二维数组元素的引用
a) 引用格式数组名 [下标 1][下标 2]
其中,下标 1与下标 2是可含有变量的整型表达式 。
例如,若有数组定义:
int a[3][4],i=0,j=1 ;
则以下均能正确引用数组 a中元素:
a[0][1]=1 ; a[++i][++j]=2 ;
a[i+1][j-1]=i+j ; a[j-i][j+i]=j-i ;
a[i=1][j=2]=12 ; a[a[0][0]+1][3]+=1 ;
34
b) 引用数组元素时的越界访问假定有数组定义:
int a[3][4];
则对于:
a[-1][0]=1;
a[3][0]=1;
这两条语句中引用的数组元素都是 行下标越界 访问错超出数组 a的存储区域。
而对于 a[0][4]=1; 这样的数组元素引用,是列下标越界访问错误,但未超出数组 a的存储区域。
注意,编译系统不检查数组下标是否越界,也不报告这类错误。
35
5、二维数组应用例例 1 输入数组中的个别元素。
int a[3][4] ;
scanf (,%d”,&a[0][0] ) ;?scanf (,%d”,a[0] ) ;
例 2 按行 输入数组中的全部元素。
for ( i=0 ; i<3 ; i++ )
for ( j=0 ; j<4 ; j++ )
scanf (,%d”,&a[i][j] ) ;
例 3 按列 输入数组中的全部元素。
for ( j=0 ; j<4 ; j++ )
for ( i=0 ; i<3 ; i++ )
scanf (,%d”,&a[i][j] ) ;
36
例 4、矩阵乘法
void matrixmul ( int A[3][4],int B[4][2],int C[3][2] )
{
int i,j,k ;
for ( i=0 ; i<3 ; i++ )
for ( j=0 ; j<2 ; j++ ) {
C[i][j]=0 ;
for ( k=0 ; k<4 ; k++ )
C[i][j]+=A[i][k]*B[k][j] ;
}
}
Amn × Bnl = Cml
/*矩阵 A( 3行× 4列),矩阵 B(4行× 2列 ),C=A× B(3行× 2列 ) */
即 C[i][j] 为 A矩阵的 i 行的元素与 B矩阵的 j 列上的对应元素的乘积之和
37
main ( )
{
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12 } ;
int b[4][2]={1,2,3,4,5,6,7,8 } ;
int c[3][2],i,j ;
matrixmul ( a,b,c ) ;
for ( i=0 ; i<3 ; i++)
{
for ( j=0 ; j<2 ; j++ )
printf (,%d”,c[i][j] ) ;
printf (,\n” ) ;
}
}
1 2 3 4
5 6 7 8
9 10 11 12
1 2
3 4
5 6
7 8
50 60
114 140
178 220
数组 a
数组 b
数组 c
38
void sort ( int x[ ],int n )
{
int i,j,t ;
for ( i=0 ; i<n-1 ; i++ )
for ( j=0 ; j<n-1-i ; j++ )
if ( x[j]>x[j+1] ) { /*升序 */
t=x[j] ;
x[j]=x[j+1] ;
x[j+1]=t ;
}
}
main ( )
{
int i,a[10]={ 4,7,2,1,6,8,7,9,2,5 } ;
sort ( a,10 ) ;
for ( i=0 ; i<10 ; i++ )
printf (,%d,,a[i] ) ;
}
例 5、冒泡法排序
39
7.3 字符数组
C 语言中只有字符串常量,而没有对应的字串变量。
但是可以定义字符型数组并把它当作为字符串变量,实现字符串的存储及对字符串的各种处理操作。
字符型数组中的每个元素存储一个字符。如对于字符数组声明:
char ch[10] ;
数组 ch共有 10个数组元素,每个数组元素占用一个字节用于存储一个字符的 ASCII编码。如果把一个字符串 ( 这里字符串的长度不大于 10)中的字符依次存入数组 ch中就实现了对字符串的存储。如果要对该字符串进行某些处理,
如字符串比较、修改、删除等等,只要对这个数组中的相关数组元素执行有关的操作就可以了 。
40
当然,也可以定义一个二维字符数组用来实现对一组字符串的存储与处理。如对于二维字符数组:
char ch[20][80] ;
按多维数组的性质,该数组可以用来存储 20个字符串,每个字符串的最大长度可达 79个字符。
按C语言允许把多维数组逐级简化为一维数组的特性,
可以把 ch[i]看作为第 i个字符串的存储起始地址。
因为字符串常数是一个C字符串,它的末尾由编译程序自动加上一个 Null字符作为字符串的结束标志,那么,
将字符串存入字符数组中去时,必须保证数组的大小至少比字符串包含的实际字符个数多 1。
41
1,字符数组的初始化因为在定义数组的同时可以指定数组元素的初值,这就为将字符串送入字符数组中去提供了最简便的手段。
如下面的字符数组定义将同时把 Hello,world! 送入字符数组中去。
字符数组 s中存储的字符顺序为:
42
因为字符可以写成它的编码的十进制、八进制、十六进制形式及它们的换码序列形式,所以上例也可以按如下的形式来初始化数组的诸元素,效果是相同的。
43
上例的初始化还可以采用如下更便利的方法:
char s[13] = {,Hello,world!” } ;
char s[13] =,Hello,world!” ;
注意:这里定义的数组元素个数不是 12而是 13。原因是这里给出的初始化数据是一个字符串,其后要附加一个 Null字符,因此在定义数组时就必须使数组的大小比字符串的实际长度多 1,否则会产生初始化常数表太长的语法错误。
而前面的例子中的初始化值是以字符形式给出的,
初始化后的数组中的内容并不是 C字符串。因为其后没有附加一个 Null字符。
44