数组是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]中。

声明数组时,编译器会留出足够大的一块内存以储存整个数组。

各个数组元素依次被储存在内存位置中,如图所示。

第六节(数值数组)_初始化_02

在源代码中,数组声明的位置很重要。和普通变量一样,数组声明的位置将影响程序可以如何使用该数组。就现在而言,把数组的声明和其他变量的声明放在一起。

数组元素可用于程序中任何相同类型的非数组变量的地方。

通过使用数组名和带方括号的数组下标来访问数组中的各元素。

例如,下面的语句将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;
}

输出:

第六节(数值数组)_初始化_03

解析:

运行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]。

二维数组的结构如图所示。

第六节(数值数组)_数组_04

类似地,可以将三维数组看作一个长方体(或立方体)。至于四维数组(或更高维),最好能发挥你的想象力。无论多少维数的数组,都在内存中按顺序储存。


二.命名和声明数组:

数组的命名规则与前面介绍的变量名命名规则相同。

数组名必须唯一,不能与其他数组或其他标识符(变量、常量等)重名。

也许你已经猜到了,数组声明和非数组变量声明的形式相同,只是数组名后面必须带元素的个数,并用方括号括起来。

声明数组时,可以用字面常量或通过#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;
}

输出:

第六节(数值数组)_初始化_05

与上一个程序类似,该程序清单也需要用户输入值。
该程序提示用户为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()结束

输出:

第六节(数值数组)_数组_06

解析:

在前面程序中使用过嵌套的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个元素的下标是多少?

  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;
}

第六节(数值数组)_数组_07