第三课Java语言基础2-2

三、增加程序数据的复杂性

  前面说过学编辑语言和学外语一样,懂得的知识越多,能够表达的内容就越丰富、越复杂。刚才的代码呢,我们通过循环与选择语句增加了程序逻辑上的复杂性,我们现在要试着增加数据的复杂性。为什么要增加数据的复杂性,因为21点这个游戏它并不是一把定输赢,可以多次叫牌,牌局结算的时候把手里的牌都亮出来。因此我们整理以后增加以下需求:

  1. 将每次叫到的牌都记录下来。

  2. 判断是否叫牌及显示牌局结算信息时都依据叫牌的记录。

  我们敲以下代码:

    int[] pokersA = new int[4]; // 玩家手里的牌    int[] pokersB = {7, 8, 0, 0}; // 电脑手里的牌        // 玩家最多叫4次牌    for(int i = 0; i < 4; i++){        pokersA[i] = (int)(Math.random() * 13 + 1);   // 使用随机函数模拟发牌            // 获得当前玩家手中牌的总点数        int sum = 0;        for(int j = 0; j <= i; j++){            sum += pokersA[j];        }            // 在玩家手中牌点数大于电脑时停止叫牌        if(sum > 15){            break;        }    }        // 显示玩家手里的牌及总数    String text = "你手中的牌分别是:";    int sumA = 0;    for(int i = 0; i < 4; i++ ){        if(pokersA[i] == 0){    // 手里的牌是0,说明没有拿牌了            break;        }            text += pokersA[i] + ",";        sumA += pokersA[i];    }        text += "总数是:" + sumA + "。对家手里的牌是:";        // 显示电脑手里的牌及总数    int sumB = 0;    for(int i = 0; i < 4; i++ ){        if(pokersB[i] == 0){    // 手里的牌是0,说明没有拿牌了            break;        }            text += pokersB[i] + ",";        sumB += pokersB[i];    }        text += "总数是:" + sumB;    if(sumA > sumB && sumA <= 21){   // 赢的要求是玩家牌比电脑大并且不大于21点        text += "。你赢了!";    }else{        text += "。你输了!";    }

  我们看在这段代码中,为了存储每次叫到的牌,我们声明了能容纳4个整数的数组变量,后面就是对这个数组进行操作,包括将叫到的牌存进去,最后又从数组中读出每张牌进行牌局结算。为此我们要学习数组这个数据结构。

四、Java语言基础(二 )

5. 数组

  同学们,我在前面概括编程语言内容的时候说过就是两个部分:数据结构与算法逻辑。数据结构部分介绍了基本数据类型,就是整数型、浮点数型、字符型和布尔类型4种类型。但是基本数据类型在实际使用中有很大的局限性,它们之间没有组织,不能表示变量之间的逻辑关系。


比如我们玩牌的时候,每个人手里抓的牌都是有相互关系的,属于一组数。如果声明好几个整数类型变量来记录,使用起来就非常不方便。所以这组数就应该有一个结构把它们都管理起来。这就是数组出现的原因。

  数组的定义形式如下:

type[ ] arrayName;

  就是在类型后面加一个中括号就可以将变量声明为数组。例如:int[] intArray; 声明整型数组。char[] charArray; 声明字符型数组。这是字符型数组,不是字符串,大家要知道区别。虽然有的语言里是用字符型数组来实现的字符串,但Java语言不是这样的,Java用的是类。

  数组类型变量的初始化是两种,就是我们在前面代码里用到的:

    int[] pokersA = new int[4];    int[] pokersB = {7, 8, 0, 0};

  第一种是通过new语句,它的作用是向内存申请指定数量的空间去存储这一组数。这里就是向内存申请4个整数的空间,而pokersA这个变量里实际存的并不是数值,而是指向这组数的存储空间的地址。申请完存储空间后,每个存储空间里的值就是默认值,也就是0。第二种是直接把数组的值赋给变量,这一组值之间用逗号区隔,最外面用大括号括起来。

  其实数组的概念是很好理解的,数组中用法里最重要的就是声明、初始化与对元素的访问【数组的成员称为“元素”(Element)】。访问元素的方法在我们敲的代码里可以看到,就是在数组名后加中括号与序号的方式,只是大家要注意序号是从0开始计数的。在代码中我申请了一个长度为4的数组,那么它的序号就是从0到3。在对数组的元素进行引用的时候一定要注意序号的范围,如果超出了数组的长度程序就会报错。

    pokersA[0] = pokersB[0];  // 赋值为7    pokersA[1] = pokersB[4];  // 出错,因为索引越界

  在实际的编程中,同学们可能会遇到数与数之间的联系不仅是一个维度的关系,可能是两个或多个维度。比如平面几何中表示一个点的位置就有x轴的值和y轴的值,表示地理位置也有经度与纬度。而我们要做的扑克牌游戏的牌面也有两个维度,一个是点数,另一个是花色。为此,我们可以用多维数组来容纳这种数据。二维数组示例如下:

    int[][] intArray1 = new int[3][2];    int[][] intArray2 = {{1, 2}, {3, 4}, {5, 6}};

  访问二维数据的元素示例:

    intArray1[0][0] = intArray2[0][0];

  用for循环的嵌套遍历二维数组:

    for (int i = 0; i < 3; i++){        for (int j = 0; j < 2; j++){            intArray1[i][j] = intArray2[i][j];        }    }

  其实二维数组可以看成是数组的数组,也就是在在一个数组里的元素也是数组。因为任何类型都可以声明自己的数组,数组本身也是一种类型,当然就可以声明数组的数组。用同样的思路可以进一步扩展,三维、四维等多维数组,但在实际中很少用到,在这里就不说了。

  这里还要提到一点,就是数组里可以放进去的数据类型可以任何类型,包括基础类型,也包括后面要接触到的其它复合数据类型,我们留这么个印象在脑子里就行。

6. 引用类型

  前面我们提到了数据类型分为3大类:基本数据类型、引用类型和空类型。我们回顾一下这张图:

  可以看到数组是引用类型的一种。引用类型也称为复合类型,意思它是由基本类型的数据组合而成的。但其实复合类型与基本类型在实现机制上是不同的,这就造成使用出现很大差别。比如下面的代码:

    int a = 1;    int b = a;    b = 2;

  经过这段代码后,a的值是1,b的值变为2。再看下面的代码:

    int[] arrayA = {1, 3};    int[] arrayB = arrayA;    arrayB[1] = 2;

  经过这段代码后,我们知道arrayB元素的值是{1, 2},那arrayA的值是什么呢?答案是,arrayA的也是{1, 2}。这是什么原因呢?原因在于基本类型与复合类型的实现机制不同。我们用一个简化的方式来进行解释:基本类型在声明变量时就会分配一个存储空间用于存储数值,我们在声明int类型变量a, b时给它们分配了不同的存储空间,当把a赋给b时,只是让b的值与a的值相等,但a与b的值各自再发生变化是不会相互影响的。这个过程如下图所示:

  而复合类型的过程则是:声明arrayA时会分配一个空间,这个空间不是用于存放数组的元素的,而是用于存放地址。在创建了数组元素{1, 2}之后,存放数组元素的空间地址会传给arrayA的那个空间进行保存。保存的这个地址就叫做指向数组元素存储空间的“引用”。声明arrayB时,也会给它一个空间用于存放地址,将arrayA的值赋给arrayB,其实是将arrayA保存的地址的值复制给了arrayB。这样arrayA与arrayB里存放的地址是相同的,通过它俩任一个访问数组元素其实是一样的,因为它们指向的同一个存放数组元素的空间。这个过程如下图所示:

  不知道大家对上面的解释听懂了没有,没有听懂的可以听我下面的一个比方,就以幼儿园老师给小朋友分苹果来做比方吧。说有一家幼儿园是个高价幼儿园,名字都是洋文的,叫Java幼儿园。老师也是外教,文字比较长,一个叫基本类型老师,另一个叫引用类型老师。有一次基老师给小朋友发苹果,他先给小朋友a发了一个苹果,给小朋友b发苹果时,看到a手里是一个,也给了b一个。过了一阵,基老师觉得小朋友b长得实在可爱,怎么那么可爱呢,于是他又偷偷地给小朋友b发了一个,这样b手里就有2个苹果,而a手里还是1个苹果。等引老师给小朋友发苹果时,他的方法就不一样了。他先把苹果放到柜子里,再把柜子的钥匙发给小朋友。他给小朋友arrayA发了一把钥匙,又小朋友arryB发了一把钥匙,虽然给这两个小朋友分别发了钥匙,但是钥匙是相同的,打开的是同一个柜子的锁。“一把钥匙开一把锁”,但一把锁可以有好几把钥匙,对吧。这样,当arrayB小朋友把柜子里的苹果吃掉一个的时候,arrayA小朋友的苹果也同样会减少一个。不知道通过这个故事,同学们是否觉得好理解了?

  因为复合类型变量里存放的并不是真正的数值,只是存放的地址,是对数值存放空间的一个“引用”,就像上面提到的一样,不是直接给小朋友发苹果,而是发的钥匙。这把钥匙就称为引用,拿它就可以找到真正的数据。因此复合类型也叫“引用类型”。基本类型与引用类型的区别根源是在实现机制上,存放基本类型的空间是在内存中叫做“栈”(英文是stack)的区域分配的,而引用类型存放数值的空间则在内存中叫做“堆”(英文是heap)的区域分配的,在栈中也会为引用类型分配一个空间,但是这个空间只用来存放堆分配空间的地址。基本类型与引用类型的变量有一个很直观的区别,引用类型变量可以使用new来创建,而基本类型变量是不能用new的。这是因为new的时候是向堆中申请空间并获得空间的地址,而基本类型不需要用堆的空间,所以就不能用new。堆与栈都是计算机的重要概念,但在在应用层面的编程一般也用不上,大家留个印象就行,有时候可以用来装装X。感兴趣的同学自己可以找找相关资料。

  我在这里对基本类型与引用类型进行一下概念的梳理。基本类型与引用类型在以前的叫法是基本类型与复合类型,这样的一对概念其实更好理解。这是从类型的构成方式来说的,“基本”就是原始的,“复合”就是用其它类型组合成的,这个组合用的原料可能是基本类型也可能是复合类型。而基本类型又有个名称叫“值类型”,值类型与引用类型又是一对概念,这是从传递方式(也就是用变量给变量赋值)的角度来说的,值类型传递就是值,引用类型传递的就是引用。这样两组对应的概念其实就好理解多了。不知道什么原因现在的教材不这么叫了。不管这些,大家只要知道基本类型就是值类型,复合类型就是引用类型,两两对应就可以了。

  但是我们还要注意一个特殊的类型,就是字符串类型String。字符串肯定是引用类型,那把一个字符串的值赋给另一个字符串后,会不会让两个字符串变量的引用相同呢?我们用代码试一下。

    String strA = "123";    String strB = strA;    strB += "abc";

运行的结果,strA是123,而strb是123abc。由此可见通过对strB的操作并不会影响strA。这似乎违反了引用类型的机制。其实不是这样。字符串类型为了让它在使用上更像基本类型,因此在赋值上模拟了基本类型的方式,不是复制的引用,而是复制的值。具体如下图所示:

  字符串是特殊的情况,大家留心一下就行。

  引用类型除了数组还有很多其它类型,比如队列表List、队列Queue、哈希表Hashtable、映射Map等,这些结构类型的名称有的同学可能听说过,它们种类繁多各有各的作用,但我们没必要现在集中在一起学习,以后根据需要我们再详讲。如果都列出来可能会吓着大家。这些类型在Java中都是用类来实现的,其实数组也是由类实现的,但是通过中括号的方式可以直接访问数组的元素提供了很多方便,使用数组显示特殊,因此特意将它从类中提出来,单独作为一种引用类型。

  我们再回到那张介绍Java类型的图,

总的来分,Java类型分成3大类,即基本类型、引用类型和空类型null。我们已经学习了基本类型与引用类型,还剩下空类型。空类型是一种特殊的类型,它只有一个值就是null,这个类型只有值,没有类型名称,因此没有变量能被声明为null类型。null值其实就是一个空地址,是对一个内存中并不存在的地址的引用。【这段话是不是显得很玄妙,有点“色即是空,空即是色”的感觉,又有点“道可道,非常道。名可名,非常名。无名,天地之始,有名,万物之母”的意境。大家不要对它产生畏惧,用得多了自然就懂了,所谓“熟能生巧”。】

  所有的引用类变量在被声明以后,没有使用new或被赋值之前,其默认值就是null。当使用一个值为null的引用类型变量时,程序执行时会出现异常。因此为了保证程序执行的稳定,可以在使用引用类型变量之前,检查它是否为null,可以使用 == 或 != 运算符来进行检查,如:

    int[] arrayA;        //...        if ( arrayA == null ){        // 对arrayA进行初始化    }

  本次课程我们主要讲解了循环语句与数组,并通过对它们的运用增加程序逻辑与数据的复杂度,丰富了许多功能。现在我们总结内容如下:

  1. 循环语句分为do…while、while、for3种,在循环体中可以用continue与break进行跳转。

  2. 类型转换包括自动转换与强制转换,强制转换时使用括起来的类型名进行标识。

  3. 字符串可以包括多个字符,+号与转义符的使用。

  4. 运算符的优先级。

  5. 数组的声明与使用new进行初始化,数组元素的访问方式,二维及多维数组。

  6. 引用类型与基本类型的区别。堆与栈的介绍。

  7. null类型。


第一季 零基础学习Android开发

第一课 第一个Android程序(1)

第一课 第一个Android程序(2)

第二课 Java语言基础1(1)

第二课 Java语言基础1(2)

文章转载自微信公众号:跟陶叔学编程

类似文章