2.4 数组
计算机最适合处理大量的数据。
一个数可以用一个变量保存,多个数可以用多个变量保存。如果数据太多,为每一个数据单独定义一个变量就不合适了。这时,可以用数组来保存大量的数据。
一个数组用一个标识符表示,后面跟“下标”,带不同“下标”的数组元素名就可以表示不同的变量,例如a[0]、a[1]、a[2]这些变量名对应不同的数组元素。
数组中的所有元素具有相同的性质(尤其是数据类型,所有元素都相同)。
定义数组比定义多个变量方便,一次定义一个足够长的数组就相当于定义了多个变量。
数组在使用之前必须先声明、创建,然后才能使用。
根据数组下标的个数,可以将数组分为一维数组、二维数组和三维数组等,一般只用到三维数组。
2.4.1 一维数组
一维数组
1.声明数组
声明一维数组的形式:
数据类型 数组名[];
“数据类型”表示数组中元素的数据类型,“数组名”是标识符,“[]”是下标运算符,例如:
int a[];
或者将“[]”放在数组名前面声明数组也可以:
数据类型 []数组名;
[]与数据类型和数组名之间有无空格都可以。例如
float []x; //[]与数据类型间有空格
float[]y; //[]与数据类型间无空格
可以一次声明多个数组,这时需注意[]的位置。
double []x,y; //[]在数组名列表前,表示声明两个double型数组x和y
double x[],y; //[]在x后,表示声明了double型数组x,y是普通变量
2.创建数组
必须创建数组后才能使用数组。数组的创建方法:
new 数据类型[数组长度表达式]
“数据类型”指的是数组元素的类型;数组长度表达式定义数组长度或元素个数,它可以是常量、变量或者任意表达式,其值的类型必须是整型。如:
a = new int[10];
创建了一个整型数组,共有10个元素,即a[0]~a[9]。
3.使用数组
数组声明的是数组的名称,是引用;创建数组则是为数组元素分配内存空间。使用数组就是使用引用访问元素。使用数组名可以访问数组的总体,使用带下标的变量则是访问某个数组元素。使用之前要把数组名和创建的数组连接起来。
已声明的数组名=创建的数组;
如前面已声明的数组名a和x,让它们分别表示一个数组:
a=new int[10];
x=new double[20];
则可以分别通过a和x使用这两个数组。也可以将数组的声明和创建放在一起。如:
int a[]=new int[10];
double x[]=new double[20];
使用数组应注意以下几方面问题。
(1)使用合法下标。下标有合法范围,下标在下界和上界之外称为越界,下标越界导致抛出异常Array Index Out Of Bounds Exception。(异常的内容详见第6章)
(2)数组创建后,每一个元素都有默认值。对于数值型数组,默认值是0(整型、字符型)或0.0(浮点型);对于布尔型数组,默认值是false。当然,根据需要,在元素参与运算前应通过赋值的形式使元素有确定的值。
(3)在Java中,任何一个数组(无论什么类型)都有一个“length”属性,该属性表示数组的长度。如:a.length表示数组a的元素个数。
(4)数组名作为方法形参时,传递的是数组的引用,可在主调和被调方法之间起到“双向传递”数据的效果。
【例2.26】一维数组的使用。将一个数组中的各个元素赋值并按逆序打印出来。
【代码】
public class Example2_26
{
public static void main(String args[])
{
int a[]=new int[10],i;//声明一个数组和一个变量
//创建数组后各元素的值
System.out.println("刚创建数组后各元素的值:");
for(i=0;i<a.length;i++)
System.out.printf("%5d",a[i]);
System.out.println();
for(i=0;i<a.length;i++)
a[i]=2*i;
//逆序打印出各元素的值
System.out.println("赋值后各元素值(逆序):");
for(i=a.length-1;i>=0;i--)
System.out.printf("%5d",a[i]);
System.out.println();
}
}
程序运行结果如图2-29所示。
图2-29 例2.26运行结果
【例2.27】给定一组数据,将这组数据按由小到大的顺序输出。
这个问题涉及数据的排序。本题采用下述方法排序。
先将最大的数放到最后面。
从第0(下标)个数开始,两两比较,如果前面的数比后面的数大,则交换两个数的值。比如下面5 个数的比较过程:
经过这一趟比较,最大的数已经排到了最后。上述比较过程是有规律的,可以用下面的循环实现上述过程:
for(j=0;j<a.length-1-0;j++)//注意循环条件
if(a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
5个数比较了4次。
因为最大数已经到了最后位置,所以再排序时可以暂不考虑最后一个数,只需要考虑前4个数就可以了。前4个数的比较过程如下:
经过第2趟比较,剩余4个数中的最大数也已经到了最后。这个过程可以用下面的循环实现:
for(j=0;j<a.length-1-1;j++)//注意循环条件
if(a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
上述过程再重复两趟,即可完成排序。
可以得出结论,如果有n个数,则需要进行n-1趟的排序。而其中第i趟的排序需要比较n-1-i次,这是因为比较时访问了a[j+1],以避免下标越界。
【代码】
import java.util.Random;
public class Example2_27
{
public static void main(String args[])
{
int a[]=new int[10];
get Elements(a);//这4条语句分别用于调用相应的方法
print(a);
sort(a);
print(a);
}
private static void sort(int a[])//方法,用于排序
{
int i,j,t;
for(i=0;i<a.length-1;i++)//进行a.length-1趟
{
for(j=0;j<a.length-1-i;j++)//每趟比较a.length-1-i次
if(a[j]>a[j+1])//如果前比后大,则交换值
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
private static void get Elements(int a[])//方法,为数组元素赋随机值
{
Random rand=new Random();//随机数类的对象
for(int i=0;i<a.length;i++)
a[i]=rand.next Int(100);//每次创建一个不大于100的随机数
}
private static void print(int a[])//方法,用于输出数组中各元素
{
for(int i=0;i<a.length;i++)
System.out.printf("%3d", a[i]);
System.out.println();
}
}
程序运行结果如图2-30所示。
图2-30 例2.27运行结果
上述方法实际上是冒泡排序法,小数是逐渐排到最前面的。
在这个例子中,额外定义了3个方法。get Elements()方法用于为数组元素赋值,赋值时利用了随机数类Random类的对象创建随机数作为元素的值,使得程序运行时每次数组元素都有不同的值。print()方法用于输出数组中的各个元素。sort()方法是用于排序的方法。
方法是功能的实现,如本例中get Elements()、print()和sort()方法都是相应功能的实现,编程时应尽量使用方法。关于方法的定义请参见第4章。
Java中有一个类Arrays,该类中有sort()方法,可以对数组进行排序。所以,对例2.27中数组a进行排序,语句:
Arrays.sort(a);
就可以完成对数组a的排序。
在实际软件开发中,尽量使用系统定义的类及类中的方法,可以提高程序的开发效率,提高程序的稳定性和健壮性。从学习角度看,有些基本算法应该了解和掌握,并编程实现,可以提高对语言的掌握程度和提高编程能力。
4.数组的内存模型
数组的内存模型
表达式:
new int[10]
创建一个有10个元素的整型数组,同时表达式的值是数组在内存中起始地址。
赋值语句:
a=new int[10];
是将数组的起始地址保存在变量a中。这样当想访问数组“new int[10]”时,就可以通过访问a得到数组的起始地址,从而可以访问到数组中的每一个元素。
数组的内存模型如图2-31所示。图中“[I@4aa0ce”是内存地址值。从此单元开始的连续内存区域用于存储a数组的10个元素。
图2-31 数组的内存模型
赋值语句:
a=new int[10];
则a表示的是一个有10个整型元素的数组。如果在之后再有赋值语句:
a=new int[20];
则a现在表示的是一个有20个整型元素的数组(变量a的值是新数组的地址)。原来的数组的地址被覆盖了,这个数组再也访问不到了。
【例2.28】数组的内存表示。
【代码】
public class Example2_28
{
public static void main(String args[])
{
int i;
int a[]={10,20,30,40,50};
int b[]={-15,-25,-35,-45,-55,-65,-75};
System.out.println("数组a和数组b的元素:");
for(i=0;i<a.length;i++)
System.out.printf("%4d", a[i]);
System.out.println();
for(i=0;i<b.length;i++)
System.out.printf("%4d", b[i]);
System.out.println();
a=b;//赋值后,a的值与b的值相同,都表示第2个数组
System.out.println("执行\"a=b;\"后,数组a和数组b的元素:");
for(int x:a)//增强型循环
System.out.printf("%4d", x);
System.out.println();
for(int x:b)//增强型循环
System.out.printf("%4d", x);
System.out.println();
}
}
程序运行结果如图2-32所示。
图2-32 例2.28运行结果
理解数组的内存模型,有助于理解数组名做方法参数时参数的传递(参见方法的参数的传递部分)。
5.增强型for循环
for循环还可以写成另一种形式:
for(数据类型变量名:数组名)
循环体(循环体中访问“变量名”)
其中的“变量名”的类型应与“数组名”中数组的类型相同。
增强型for循环的执行过程是,当执行for循环时会将数组中的元素顺序(每次)地赋给“变量”,在循环体中通过访问“变量”就可以得到相应数组元素的值。在例2.28中就使用了增强型for循环。增强型for循环只可用于浏览或读出数组元素值,却不能写入元素值。
2.4.2 二维数组
二维数组
二维数组有两个下标。
1.二维数组的声明
二维数组的声明形式:
数组类型 数组名[][];
或
数组类型 []数组名[];
或
数组类型 [][]数组名;
例如:
int a[][],b[];
a是一个二维数组,b是一个一维数组。
如果一条语句只声明一个二维数组,则3种声明方式相同。如果一条语句同时声明多个数组,则下标[]的位置不同,声明的结果也不相同。如:
int a[][],b;//a是一个二维数组,b是一个简单变量
int []a[],b;//a是一个二维数组,b是一个一维数组
int [][]a,b;//a和b都是二维数组
2.二维数组的创建
声明了二维数组,仅仅是声明了数组名,数组并不真正存在,所以还必须创建二维数组。创建形式:
new 数据类型[行数表达式][列数表达式]
一个二维数组可以看作是一个行列式。如:
int a[][]=new int[3][4];
a所表示的数组一共有3行,每行有4个元素。
不同于C/C++语言,Java的二维数组中每一行的元素个数可以不同。如:
int x[][]=new int[3][];
x[0]=new int[5];
x[1]=new int[10];
x[2]=new int[20];
x表示一个二维数组,每一行的元素分别是5、10和20。
可以将一个二维数组看作是多个一维数组。如上述x数组,可以看作是3个一维数组,数组元素分别是“x[0]”、“x[1]”和“x[2]”,而这3个元素名又可以看作是另外3个一维数组的数组名。
3.二维数组元素的访问
访问二维数组时需要给出两个下标值。访问形式:
二维数组名[下标1][下标2]
同一维数组,下标从0开始,最大不超过“数组长度-1”,不能越界。如果想知道二维数组的行数,可用表达式:
x.length
如果想知道第i行元素的个数,可用表达式:
x[i].length
4.二维数组元素的初始化
二维数组在刚创建时每个元素都有初值,数值型的初值为0,字符型的为空(’’或0),布尔型为“false”。
改变二维数组元素的值可以通过赋值语句来实现。
二维数组也可以初始化。如:
int a[][]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int b[][]={{1},{3,5,7},{2,4,5,8,10,12}};
int x[][]={{10,20,30,45},
{15,25,35,50},
{19,28,38,49}};
数组a有3行,每一行有4个元素。数组b有3行,每一行的元素个数分别是1、3和7。x有3行、4列。如果用二维数组表示行列式并初始化,最好采用第3种形式。
【例2.29】编程对两个矩阵进行相加和相减运算。
两个相加或相减的矩阵的行数相同、列数相同,结果矩阵的行数和列数也必须与前两个矩阵相同。
【代码】
import java.util.Random;
public class Example2_29
{
public static void main(String args[])
{
//声明并创建二维数组,行数相同,列数相同
int a[][]=new int[3][4],b[][]=new int[3][4];
int result[][]=new int[3][4];
get Elements(a);
get Elements(b);
compute(a,b,result,'+');
print(a,b,result,'+');
compute(a,b,result,'-');
print(a,b,result,'-');
}
private static void get Elements(int x[][])//给数组元素赋值(随机)
{
Random rand=new Random();
for(int i=0;i<x.length;i++)
for(int j=0;j<x[i].length;j++)
x[i][j]=rand.next Int(100);
}
//执行加或减运算
private static void compute(int a[][],int b[][],int result[][],char oper)
{
for(int i=0;i<result.length;i++)
for(int j=0;j<result[i].length;j++)
result[i][j]=oper=='+'?a[i][j]+b[i][j]:a[i][j]-b[i][j];
}
//打印矩阵
private static void print(int a[][],int b[][],int result[][],char oper)
{
int i;
for(i=0;i<result.length;i++)
{
for(int x:a[i])//用增强型循环
System.out.printf("%3d", x);
System.out.printf("%3c",oper);
//for(j=0;j<b[i].length;j++)//在同一行打印b的第i行
for(int x:b[i])
System.out.printf("%3d", x);
System.out.printf("%3s","=");
for(int x:result[i])//在同一行打印result的第i行
System.out.printf("%4d", x);
System.out.println();//在下一行打印数组第i+1行
}
System.out.println();
}
}
程序运行结果如图2-33所示。
图2-33 例2.29运行结果
请读者自行阅读例2.29。
【例2.30】魔方矩阵。
有一个n*n矩阵,其各个元素的值由1到n*n个自然数组成。将这n*n个自然数放到n*n矩阵中,使得矩阵的每一行元素之和、每一列元素之和、主对角线元素之和及副对角线元素之和都相等。n是奇数,最大不超过99。如下的矩阵就是一个魔方阵:
8 1 6
3 5 7
4 9 2
往魔方阵中放数的规则如下。
(1)将1放在第0行中间一列。
(2)从2开始直到n*n结束各数依次按下列规则存放:
按 45°方向向右上行走(每一个数存放的行比前一个数的行数减1,列数加1)
(3)如果行列范围超出矩阵范围,则回绕。
例如1在第0行,则2应放在最下一行,列数同样减1。
(4)如果按上面规则确定的位置上已有数,或上一个数是第0行第n-1列时,则把下一个数放在上一个数的下面。
编程时,将“放数的规则”用Java语言描述出(相当于翻译成Java语言)即可编写出程序。
【代码】
import java.util.Scanner;
public class Example2_30
{
public static void main(String args[])
{
int n,a[][];
Scanner reader=new Scanner(System.in);
System.out.print("输入一个自然数(奇数):");
n=reader.next Int();
a=new int[n][n];
to Magic(a);
display(a);
}
private static void to Magic(int a[][])//方法,形成魔方矩阵
{
int i,n;
int row=0,col=0;
int row1=0,col1=0;
n=a.length;
for(i=1;i<=n*n;i++)
{
if(i==1)
{
row=0;
col=n/2;
}
else if(row==-1 && col==n)
{
row=1;
col=n-1;
}
else if(col==n)
col=0;
else if(row<0)
row=n-1;
if(a[row][col]!=0)
{
row=row1+1;
col=col1;
}
a[row][col]=i;
row1=row;
col1=col;
row--;
col++;
}
}
private static void display(int a[][])
{
for(int i=0;i<a.length;i++)
{
for(int x:a[i])
System.out.printf("%5d ",x);
System.out.println();
}
}
}
程序运行结果如图2-34所示。
图2-34 例2.30运行结果