Java面向对象思想与程序设计
上QQ阅读APP看书,第一时间看更新

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运行结果