数组是C程序中经常要用到数据存储类型。
本次将介绍以下内容:
●什么是数组
●一维数组和多维数组的定义
●如何声明并初始化数组
一.什么是数组:
数组是一组数据存储位置,每个位置的名称相同,储存的数据类型也相同。
数组中的每个存储位置被称为数组元素。
为何程序中需要使用数组?这个问题可以用一个示例来回答。
如果你打算记录2014年的营业开支,并将开支按月归档,那么需要为每个月的开支都准备不同的文件夹,但是如果使用一个带12个隔层的文件夹会更方便。
将这个例子扩展至计算机程序设计。想象你要设计一个记录营业开支的程序。
在程序中声明12个单独的变量,分别储存每个月的开支。
这个方法类似于为12个月的开支准备12个单独的文件夹。
然而,更好的设计是用一个可容纳12个元素的数组,将每月的开支储存在相应的数组元素中。
这个方法相当于将各月的开支放进一个带12个隔层的文件夹。
使用变量和数组的区别如图所示:
图中多个变量类似于单独的文件夹,而数组类似于带有多个隔间的文件夹
1.1 一维数组:
一维数组只有一个下标。下标是数组名后面方括号中的数字。
不同的数字标识数组中不同的元素。
下面举例说明,对于前面提到的营业开支程序,可以这样声明一个float类型的数组:
float expenses[12];
数组名是expenses,包含12个元素。每个元素都相当于一个float变量。
C语言的所有数据类型都可用于数组。
C语言的数组元素总是从0开始编号,因此,将expenses 的12个元素编号为0~11。
在上面的例子中,一月份的开支应储存在expenses[0]中,二月份的开支应储存在expenses[1]中,以此类推,十二月份的开支应储存在expenses[11]中。
声明数组时,编译器会留出足够大的一块内存以储存整个数组。
各个数组元素依次被储存在内存位置中,如图所示。
在源代码中,数组声明的位置很重要。和普通变量一样,数组声明的位置将影响程序可以如何使用该数组。就现在而言,把数组的声明和其他变量的声明放在一起。
数组元素可用于程序中任何相同类型的非数组变量的地方。
通过使用数组名和带方括号的数组下标来访问数组中的各元素。
例如,下面的语句将89.95储存在第2个数组元素中(记住,第1 个数组元素是expenses[0],不是expenses[1] ) :
expenses[1] = 89.95;
同理,下面的语句
expenses[10] = expenses[11];
将数组元素expenses [11]中储存的值的副本赋给expenses[10]数组元素。
如上例子所示,数组下标都是字面常量。
然而,程序中经常会将整型变量或表达式作为下标,或者甚至是另一个数组元素。如下面例子所示:
float expenses[100];
int a[10];
//其他语句已省略
expenses[i] = 100; //i是一个整型变量
expenses[2 + 3] = 100; // expenses[2 + 3]相当于expenses[5]
expenses[a[2]] = 100; // a[]是一个整型数组
需要解释一下最后一个例子。如果有一个整型数组a[],其中数组元素a[2]中储存8,这样写;
expenses[a[2]];
与这样写效果相同
expenses[8];
使用数组时,要牢记元素编号方案:在一个有n个元素的数组中,允许的下标范围是0至n-1。
如果使用下标值n,程序会出错。C编译器无法检查出程序中使用的数组下标是否越界。
程序被编译并链接,但是越界的下标通常会导致错误的结果。
警告:
记住,数组元素从0 (不是1)开始编号。还需记住,最后一个元素的下标比数组中的元素个数少1。
例如,一个包含10个元素的数组,其元索的下标是从0至9。
有时,你可能希望包含n个元素的数组中,其各元素编号是从1~n。
例如,上面的营业开支程序中,更自然的应该是将一月份的开支储存在expenses[1]中,二月份的开支储存在expenses[2]中,以此类推。要这样做,最简单的方式是声明一个比需要的元素数目多1的数组,并忽略元素0。当然,也可以在元素0中储存一些相关的数据(如年度总开支)。
在该例中,可以声明该数组:
float expenses[13];
下面程序用于演示数组的用法。
输入:
// expenses.c 演示数组用法
#include <stdio.h>
//声明一个储存开支的数组和一个计数器变量
float expenses[13];
int count;
float year_expenses = 0;
int main(void)
{
//将用户从键盘输入的数据放入数组中
for (count = 1; count < 13; count++)
{
printf("Enter expenses for month %d: ", count);
scanf("%f", &expenses[count]);
}
// 打印数组的内容
for (count = 1; count < 13; count++)
{
printf("Month %d = $%.2f\n", count, expenses[count]);
year_expenses += expenses[count];
}
printf("Yearly expenses are $%.2f\n", year_expenses);
return 0;
}
输出:
解析:
运行expenses.c,程序会提示用户输入一月份至十二月份的开支,输入的值被储存在数组中。
必须为每个月都输入一个值,在输入完第12个值后,将在屏幕上显示数组的内容。
与前面介绍的程序清单类似
第5行是一条注释,解释声明的变量。
第7行声明了一个包含13个元素的数组(在该程序中,只需要12个元素,每个元素储存一个月的开支,但是却声明了包含13个元素的数组)。
第9行声明了一个开支总额变量。第15~19行的for循环中忽略了数组中的第1个元素(即元素0),程序使用元素1至元素12,这些元素与十二个月直接相关。
回到第8行,声明了一个变量count,在整个程序中用作计数器和数组下标。
程序的main()函数开始于第11行。
程序使用一个for循环打印一条消息,并分别接收十二个月的值。
注意,第18行,scanf() 函数使用了一个数组元素。由于第7行将expenses数组声明为float类型,因此,scanf() 函数中要使用%f。
而且,数组元素前面要添加取址运算符(& ),就像对待普通的float类型变量一样。
第23~27行是是另一个for循环,打印之前输入的值。
上一次介绍过,在百分号和f之间添加.2 (即%.2f )打印出的浮点数带两位小数。
在打印金额数时,保留两位小数的格式很合适。
注意:需要储存同类型的值时,使用数组而不是创建多个变量。例如,如果要储存一年中各月的销售额,创建一个包含12个元素的数组来储存营业额,而不是为每个月创建一个变量。不要忘记数据下标从0开始。
1.2 多维数组:
多维数组有多个下标。
二维数组有两个下标
三位数组有三个下标
以此类推。C语言对数组的维数没有限制(但是对数组大小有限制)。
例如,假设你编写一个国际象棋程序。棋盘分为8行8列,共64个方格。可以用一个二维数组代表该棋盘,如下所示:
int checker[8][8];
该数组包含64个元素: checker[0][0] 、checker[0] [1]、checker[0] [2] . . . checker[7][6]、checker[7] [7]。
二维数组的结构如图所示。
类似地,可以将三维数组看作一个长方体(或立方体)。至于四维数组(或更高维),最好能发挥你的想象力。无论多少维数的数组,都在内存中按顺序储存。
二.命名和声明数组:
数组的命名规则与前面介绍的变量名命名规则相同。
数组名必须唯一,不能与其他数组或其他标识符(变量、常量等)重名。
也许你已经猜到了,数组声明和非数组变量声明的形式相同,只是数组名后面必须带元素的个数,并用方括号括起来。
声明数组时,可以用字面常量或通过#define创建的符号常量来指定元素的个数因此:
#define MONTHS 12
int array[MONTHS];
与下面的声明等价:
int array[12];
但是,大部分编译器都不允许用const关键字创建的符号常量来声明数组的元素:
const int MONTHS = 12;
int array[MONTHS]; /*错误! */
下面的程序中展示了如何使用二维数组。程序使用一个数组储存4场篮球比赛中五名队员的得分。
输入:
// scoring.c:使用二维数据组储存篮球队员的得分
#include <stdio.h>
#define PLAYERS 5
#define GAMES 4
int scores[6][5];
float score_avg[6], bestavg;
int point_total, bestplayer;
int counter1, counter2;
int main()
{
//外层循环用于控制比赛的次数
for (counter2 = 1; counter2 <= GAMES; counter2++)
{
printf("\nGetting scoring totals for Game #%d.\n", counter2);
// 内层循环用于计算每位球员在指定比赛的得分
for (counter1 = 1; counter1 <= PLAYERS; counter1++)
{
printf("What did player #%d score in the game\? ", counter1);
scanf("%d",&scores[counter1][counter2]);
}
}
// 依次循环数组计算每位球员的平均得分
for (counter1 = 1; counter1 <= PLAYERS; counter1++)
{
point_total = 0;
for (counter2 = 1; counter2 <= GAMES; counter2++)
{
point_total += scores[counter1][counter2];
}
score_avg[counter1] = (float) point_total / GAMES;
}
//依次循环并储存最高平均分
best_avg = 0;
for (counter1 = 1; counter1 <= PLAYERS; counter1++)
{
if (score_avg[counter1] > bestavg)
{
best_avg = score_avg[counter1];
best_player = counter1;
}
}
printf("\nPlayer #%d had the best scroring average,\n", best_player);
printf("at %.2f points per game.\n", score_avg[best_player]);
return 0;
}
输出:
与上一个程序类似,该程序清单也需要用户输入值。
该程序提示用户为4场比赛的5名球员输入得分。
待用户输入所有得分数,程序计算每名球员的平均得分,并打印最高平均分的球员号数和他的平均分。
如前所述,无论是一维、二维或三维数组,它们的命名方式都类似于普通变量。
第7行,声明了一个二维数组scores。
第1个维度设置为6 (有5名球员,这样可以忽略0号元素,使用1号元素至5号元素),
第2个维度设置为5 (有4场比赛,同样可以忽略元素0)。
第8行声明了一个一维数组score_ avg ,其类型为float,因为用浮点数表示平均得分比整数更精确。
第4行和第5行定义了两个符号常量PLAYERS和GAMES,很方便地更改球员人数和比赛次数。
注意:
如本例所示,改变常量不足以改变整个程序。因为程序中用指定的数字来声明两个数组。更好的声明方式应该是:
int scores[PLAYERS + 1][GAMES + 1];
float score_avg[PLAYERS + 1];
如果按以上格式声明,那么在更改球员人数或比赛场次时,其对应的数组也会相应地更改。
另外,程序还声明了其他5个变量: counterl 、counter2、point_ total 、best_avg和bestplayer。
前两个变量在循环中要用到,point_total 用于计算每个队员的平均分,最后两个变量用于储存最高平均分及其队员编号。
第15~24行的for循环中嵌套了另一个for循环,这两个循环常用于填充二维数组。
外层循环控制比赛的场次,其中包含一个printf()语句,告知用户现在是哪场比赛。
然后再执行第19行的内层循环,该循环用于遍历队员。
当一场比赛结束时,转回执行外层循环,将比赛场次递增1,并打印出新的消息,然后再进入内层循环。
所有的分数都要输入数组中。第27^ 35行的for循环中也嵌套另一个for循环。
这两个循环与上两个循环的顺序相反,外层循环队员,内层循环比赛的场次(从第30行开始)。
第32行把队员的每场分数相加,得到该队员的总分。
第34行将总分除以比赛的次数来计算每个队员的平均得分。然后将计算结果储存在score_avg 数组中。
当外层循环递增后进入循环,第29行必须重新将point_ total 变量赋值为0 (这很重要) ,否则#2队员的总分中会包含#1队员的总分。
这是程序员常犯的错误之一。注意,这部分的代码中并未包含printf()和scanf() 语句,没有与用户进行交互。
C程序只管做好它的本职工作,获取相关数据、完成计算,并储存新的值。
最后的for循环,开始于第39行,遍历score_avg 数组并确定.最高平均分的队员。
这项工作由第41~45行的嵌套if语句完成。它获取每个队员的平均分并将其与当前最高平均分作比较。
第43行,如果该队员每场比赛的得分更高,那么该队员的平均分就会成为新的best_avg,而且把该队员的编号赋值给best_player变量(第44行)。
第48行和第49行将数据分析报告给用户。
声明函数时,使用#define指令创建的符号常量能方便日后更改数组的元素个数。
以本次程序为例,如果在声明数组时使用#define指令创建的符号常量,
则只需更改常量便可改变队员的人数,而不必在程序中逐一更改与人数相关的量。
数组的维数尽量不要超过三维。记住,多维数组很容易变得很大。
2.1 初始化数组:
第1次声明数组时,可以初始化数组的全部元素或部分元素。
只需在数组声明后面加上等号和用花括号括起来的值即可,各值之间用逗号隔开。这些值将依次被赋值给数组的元素(从0号元素 ),
考虑下面的代码:
int array[4] = { 100,200, 300, 400 };
在这个例子中,100被赋给array[0]、200被赋给array[1]、300被赋给array[2]、400被赋给array[3].
如果省略了数组大小,编译器会创建一个刚好可以容纳初始化值的数组。
因此,下面的声明与上面的数组声明等效:
int array[] = { 100, 200, 300, 400 };
初始化值的数量也可以少于数组元素的个数,如:
int array[10] = {1, 2, 3 };
如果不显式初始化数组元素,当程序运行时就无法确定元素中的值。
如果初始化值太多(初始化值的数量多于数组元素的个数),编译器会报错。
根据ANSI 标准,未初始化的数组元素将被设置为0。
提示:
不要依赖编译器自动初始化值。最好自已设置初值。
2.2初始化多维数组:
初始化多维数组与初始化一维数组类似。依次将初始化的值赋给数组元素,注意第2个数组下标先变化。
例如:
intarray[4][3] = {1,2,3,4,5,6,7,8,9,10,11,12};
赋值后结果如下:
array[0][0]中储存的是1
array[0][1]中储存的是2
array[0][2]中储存的是3
array[1][0]中储存的是4
array[1][1]中储存的是5
array[1][2]中储存的是6
array[2][0]中储存的是7
array[2][1]中储存的是8
array[2][2]中储存的是9
array[3][0]中储存的是10
array[3][1]中储存的是11
array[3][2]中储存的是12
初始化多维数组时,使用花括号分组初始化值,并将其分成多行,可提高代码的可读性。
下面的初始化语句与上面的例子等价:
int array[4][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
记住,即使已经用花括号将初始化值分组,仍必须用逗号隔开它们。
另外,必须成对使用花括号,否则编译器将报错。
接下来用一个示例说明数组的优点。程序清单randomarray.c,创建可一个包含1000个元素的三维数组,并用随机数填充它。
然后,该程序会在屏幕上显示所有的数组元素。想象一下,如果使用非数组变量,得需要多少行源代码。
程序中还使用了一个新的库函数getchar(),该函数读取用户从键盘输入的一个字符。
在下面程序清单中,getchar() 控制程序在用户按下Enter键后才继续运行。
输入:
// 使用多维数组的示例
#include <stdio.h>
#include <stdlib.h>
// 声明一个包含1000个元素的三维数组
int random_array[10][10][10];
int a, b, c;
int main(void)
{
//用随机数填充数组
//c库函数rand()返回一个随机数
//使用一个for循环来处理组的下标
for (a = 0; a < 10; a++)
{
for (b = 0; b < 10; b++)
{
for (c = 0; c < 10; c++)
{
random_array[a][b][c] = rand();
}
}
}
//显示数组元素,一次显示10个
for (a = 0; a < 10; a++)
{
for (b = 0; b < 10; b++)
{
for (c = 0; c < 10; c++)
{
printf("nrandom_array[%d][%d][%d] = ", a, b, c);
printf("%d", random_array[a][b][c]);
}
printf("\nPress Enter to continue, CTRL-C to quit.");
getchar();
}
}
return 0;
} //main()结束
输出:
解析:
在前面程序中使用过嵌套的for语句,上面的程序中有两个嵌套的for语句。
在了解for语句的细节前,注意第7行和第8行声明了4个变量。
第1个是数组random_array,用于储存随机数。
random_array是int类型的三维数组,可以储存1000个int类型的元素(10X10X10) 。
如果不使用数组,就得起1000个不同的变量名!
第8行声明了3个变量a、b和c,用于控制for循环。
该程序的第4行包含标准库头文件stdlib.h,提供rand()函数(第22行)的原型。
该程序主要包含两组嵌套的for语句。
第1组for 语句在第16~ 25行,
第2组for 语句在第29~ 42行。
这两个嵌套for语句的结构相同,工作方式与前面笔记中的程序循环类似,但是多了一层嵌套。
在第1组for语句中,将重复执行第22行的语句一将rand()函数的返回值赋值给random_array 数组的元素。rand() 是库函数,它返回一个随机数。
回到第20行,c变量从0递增至9,遍历random_array 数组最右边的下标。
第18行递增b变量,遍历数组中间的下标。b的值每递增一次,就遍历一次c (即c 的值从0递增至9)。
第16行递增a变量,遍历数组最左边的下标。a下标值每递增一次,就遍历一次b下标值(10个),而b的值每递增一次,就遍历一次c下标值(10个)。
这样,整个循环将random数组的每个元素都初始化为一个随机数。
第2组for语句在第29~42行,其工作原理与上一组for语句类似,但是该组语句循环打印之前所赋的值。
显示10个值后,第38行打印一条消息并等待用户按下Enter键。
第40行调用getchar()来处理Enter键的按键响应。
如果用户没有按下Enter键,getchar() 将一直等待,当用户按下Enter键后,程序将继续显示下一组值。
自行输出查看代码结果
三.小结:
本课介绍了数值数组。这个功能强大的数据存储方法,让你将许多同类型的数据项分组,并使用相同的组名。
在数组中,使用数组名后面的下标来识别每一项或元素。涉及重复处理数据的程序设计任务非常适合使用数组来储存数据。
与非数组变量类似,在使用数组前必须先声明。声明数组时,可初始化也可不初始化数组元素。
问答题
1:如果使用的数组下标超过数组中的元素数量,会发生什么情况?
如果使用的下标超出数组声明时的下标,程序可能会顺利编译甚至正常运行。然而,这种错误会导致无法预料的结果。出现问题后,通常很难查出是下标越界造成的。因此初始化和访问数组元素时要特别小心。
2:使用未初始化的数组,会发生什么情况?
这种情况编译器不会报错。如果未初始化数组,数组元素中的值是不确定的,使用这样的数组会得到无法预料的结果。在使用变量和数组之前必须初始化它们,明确其中储存的值。第12课将介绍一个无需初始化的情况。目前为安全起见,请记得初始化数组。
3:可以创建多少维的数组?
如本次所述,可以创建任意维的数组。维数越多,该数组所占用的数据存储空间越大。应该按需声明数组的大小,避免浪费存储空间。
4:是否有可以一次初始化整个数组的捷径?
答:在使用数组之前必须初始化数组中的每个元素。对C语言的初学者而言,最安全的方法是按照本次程序示例那样,在声明时初始化数组,或者用for语句为数组中的所有元素赋值。还有其他初始化数组的方法。
5:是否能将两个数组相加(或相乘、相除、相减)?
如果声明了两个数组,不能简单地将两者相加,必须分别将其相应的元素相加。另外,可以创建一个将两个数组相加的函数,在函数中把两个数组中相应的每个元素相加。
6:为什么有时用数组代替变量会更好?
使用数组,相当于把许多值用一个名称来分组。在上述程序中,储存了1000个值。如果创建1000个变量(为其起不同的变量名)并将每个变量初始化为一个随机数,无疑是一项异常繁琐的工程。但是使用数组,就简单得多。
7:在写程序时,如果不知道要使用多大的数组怎么办?
C语言提供了许多在运行时为变量和数组分配空间的函数。
8:在数组中可以使用哪些C语言的数据类型?
所有的数据类型都可用,但是在给定数组中只能使用一种数据类型。
9:声明了一个包含10个元素的数组,第1个元素的下标是多少?
- 在C语言中,不管数组的大小是多少,所有数组的下标都从0开始。
10:声明了一个包含n个元素的一维数组,最后一个元素的下标是多少?
n-1
11:如果程序试图通过超界下标访问数组元素,会发生什么情况?
程序可以编译并且运行,但是会导致无法预料的结果。
12:如何声明多维数组?
声明数组时,在数组名后面加上一对方括号,每维一对。每对方括号内包含一个数字,该数字指定了相应维的元素个数。
13:下面声明了一个数组。该数组中包含了多少个元素?
int array[2][3][5][8];
240个。计算方法为2×3×5×8。
14:上一题的数组中,第10个元素的名称是什么?
array [0][0][1][1]
练习题
1.编写一行C程序的代码,声明3个一维整型数组,分别名为one、 two 、 three,每个数组包含1000个元素。
int one[1000], two[1000], there[1000],
2.编写一条语句,声明一个包含10个元素的整型数组并将所有的元素都初始化为1。
int array[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, };
3.为给定的数组编写代码,将数组的所有元素都初始化为88。
int eightyeight [88];
两种方法:
第一种:是在声明数组时初始化:
int eightyeight [88] = {88, 88, 88, 88, 88, 88,....,88 };
但是,这种方法要在花括号中写88次88.所以我是不推荐使用这种方法,下面的方法较好。
int eightyeigh[88];
int x;
for (x = 0; x < 88; x++ )
eighyeigh[x] = 88;
4.为给定的数组编写代码,将数组的所有元素都初始化为0。
int stuff[12] [10];
答案如下:
int stuff[12][10];
int sub1, sub2;
for( sub1 = 0, sub1 < 12; sub1++ )
for( sub2 = 0; sub2 < 10; sub2++ )
stuff[sub1][sub2] = 0;
5.排错:找出以下代码段的错误:
int x, y;
int array [10][3];
int main( void )
{
for( x = 0; x < 3; x++ )
for ( y =0; y < 10; y++ )
array[x][y] = 0 ;
return 0;
}
答案如下:
该代码出现的错误很常见。注意:声明的数组是10x3,部署3x10,声明数组的第一个下标是10,但是for循环使用x作为第一个下标,x被递增了三次。声明数组的第二个下标是3,但是for循环使用y作为第二个下标,y被递增了10次,这样会导致无法预测的结果。所以下面我用两种方法进行解决:
第一种方法是,交换for循环体中x和y的位置:
int x, y;
int array [10][3];
int main( void )
{
for( x = 0; x < 3; x++ )
for ( y =0; y < 10; y++ )
array[y][x] = 0 ; // 交换x和y的位置
return 0;
}
第二种方法是,交换for循环x和y的值【这个方法也是我比较推荐的】
int x, y;
int array [10][3];
int main( void )
{
for( x = 0; x < 10; x++ ) //交换x与y的值
for ( y =0; y < 3; y++ )
array[x][y] = 0 ;
return 0;
}
6.排错:找出以下代码段的错误:
int array [10];
int x =1;
int main ( void )
{
for ( x =1; x <= 10; x++ )
array[x] = 99;
return 0;
}
答案如下:
这种错误很容易排除。该程序初始化了越界的数组元素。如果数组有10个元素。他的下标是09.该程序初始化下标为110的数组元素。无法初始化array[10],因为该元素不存在。应该把for语句修改成以下任意一个:
for ( x = 1; x <= 9; x++ ) //初始化9个元素
for ( x = 0; x <= 9; x++ )
注意:x<=9与下x< 10相同,两者都可用,但是x< 10更常用
7.编写一个程序,将随机数放入一个5×4(5行4列)的二维数组中,并将所有的值打印成列。
答案如下:
/*使用二维数组和rand() */
#include <stdio.h>
#include <stdlib.h>
/*声明数组*/
int array [5][4];
int a, b;
int main (void)
{
for (a = 0; a< 5; a++)
{
for (b = 0; b< 4; b++)
{
array[a] [b] = rand ();
}
}
/*打印数组元素*/
for (a = 0; a< 5; a++)
{
for (b =0; b < 4 ; b++)
{
printf ("%d\t", array[a] [b] );
}
printf ( "n");/*换行*/
}
return 0;
}