Java学习-03 Java语句的执行结构及数组学习
1、Java语句的执行结构
Java程序是由语句组成的。语句能够通过创建和操作变量、对变量赋值并控制这些操作的执行流程来描述运算。语句通常会被组织成代码段,即花括号中的一系列语句。首先来大概说一说Java中的语句分类:
- 声明语句:创建某种类型的变量并用标识符为其命名。
- 赋值语句:将(由表达式产生的)某种类型的数值赋予一个变量。
- 条件语句:能够简单地改变执行流程—根据指定的条件选择执行两个代码段之一。
- 循环语句:更彻底的改变执行流程—只要条件为真就不断地反复执行代码段中的语句。
- 调用和返回语句:和静态方法有关,是改变执行流程和代码组织的另一种方式。
1996 年,计算机科学家 Bohm 和 Jacopini 证明了:任何简单或复杂的算法都可以由顺序结构、分支结构和 循环结构这三种基本结构组合而成。
它们的共同点是都包含一个入口和一个出口,它们的每个代码都有机会被执行,不会出现死循环。
1.1、顺序结构
顺序结构是一种基本的控制结构,它按照语句出现的顺序执行操作。
1.2、分支结构
分支结构又被称为选择结构,根据条件成立与否来执行操作。
1.2.1、if条件语句
if条件结构是根据条件判断之后再做处理,如果条件为真,则执行if之后的花括号内的代码,否则就执行else或者else if的花括号中的代码。如果没有else或者else if 那么当条件不满足的时候就直接跳过,执行后边的语句。
if(条件语句){…}
if (条件语句){…}else{…}
if (条件语句){…}else if(条件语句){…}
if (条件语句){…}else if(条件语句){…}else{…}
举个栗子,我们应该都考过试,学校按照我们的考试分数把成绩分为优秀(>=90),良好(80-90),合格(70-80),及格(60-70),不及格(<60),那么用程序可以描述为:
import java.util.Scanner;
public class Grade{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
System.out.println("请输入成绩:");
int score s= input.nextInt();
// 90以上优 80-90 良 70-80 合格 60-70 及格 <60 不及格
if(score >= 90){
System.out.println("您的成绩是优秀!不要骄傲继续加油哦!");
}else if(score >= 80 & score < 90){
System.out.println("您的成绩是良好!还要努力哦!");
}else if(score >= 70 & score < 80){
System.out.println("您的成绩是合格!还要加倍努力哦!");
else if(score >= 60 & score < 70){
System.out.println("您的成绩是及格!差一点点就不及格,下次要更努力了哦!");
}else{System.out.println("您的成绩不合格!下次可不能不好好听课了!");
}
}
}
1.2.2、switch语句
一般格式:
switch(表达式){
case 取值 1: 语句块 1;break;
case 取值 n: 语句块 n;break;
default: 语句块 n+1;break;
}
switch 语句有关规则
表达式的返回值必须是下述几种类型之一:byte, int, char, short, String;
case 子句中的取值必须是常量,且所有 case 子句中的取值应是不同的;
default 子句是可选的;
break 语句用来在执行完一个 case 分支后使程序跳出 switch 语句块;如果 case 后面没有写 break 则直接往下面执行!
case 后面的执行体可写{ }也可以不写{ }.
这种选择语句一般用于case特别多的场景,举个栗子,我们想知道某一年某一月有多少天,可以如下编写代码:
import java.util.Scanner;
public class CalculationDays {
public static void main(String[] args) {
/*
一年中有 12 个月,而每个月的天数是不一样的。其中大月 31 天,分别为
1,3,5,7,8,10,12 月,小月 30 天,分别 为 4,6,9,11 月。还有二月比较特殊,平
年的二月只有 28 天,而闰年的二月有 29 天.在控制台输入年份和月份,程序计算该年该月的天数。
判断闰年条件①:非整百年数除以4,无余为闰,有余为平;②整百年数除以400,无余为闰有余平。
*/
Scanner input = new Scanner(System.in);
System.out.println("请输入年份:");
int year = input.nextInt();
System.out.println("请输入月份:");
int month = input.nextInt();
switch (month) {
case (1):
case (3):
case (5):
case (7):
case (8):
case (10):
case (12):
// 1,3,5,7,8,10,12月份有31天
System.out.println(year + "年" + month + "月有31天");
case (4):
case (6):
case (9):
case (11):
// 4,6,9,11月有30天
System.out.println(year + "年" + month + "月有30天");
case (2):
// 判断是否为闰年
if (year % 400 == 0 | (year % 100 != 0 & year % 4 == 0)) {
// 闰年2月有29天
System.out.println(year + "年" + month + "月有29天");
} else {
// 平年2月有28天
System.out.println(year + "年" + month + "月有28天");
}
}
}
1.3、循环结构
循环结构是一种重复结构,如果条件成立,它会重复执行某一循环体,直到出现不满足的条件为止。
循环语句分类:
- for 循环
- while 循环
- do/while 循环
1.3.1、while循环
首次先判断是否满足条件,符合条件则执行循环体,然后再判断,符合条件,循环继续执行;否则,循环退出。
特点:先判断,再执行
格式:
while(条件表达式){
//循环体;
}
使用 while 循环的步骤:
1、分析循环条件和循环操作
2、套用 while 语法写出代码
3、检查循环是否能够退出(重要)
举个栗子,计算1+2+3+4+…+n(n个数字之和):
int sum = 0;
int n = 10;
int i = 0;
while(i <= n ){
sum += i;
i++;
}
System.out.print("1+2+3+...+10的结果为:" + sum);
1.3.2、do-while 循环
首次先执行一遍循环操作,符合条件,循环继续执行;否则,循环退出。
特点:先执行,再判断
格式:
do {
// 循环题
}while ( 条件表达式 );
因此while 循环和 do-while 循环的区别就是:
while:先判断条件,如果条件满足,再执行循环操作。
do while:先执行一遍循环操作,然后再判断条件,如果条件满足,继续执行循环操作。
举个栗子,之前网上流传一个价值一个亿的人工智能代码:
import java.util.Scanner;
public class AI {
public static void main(String[] args) {
// 人工智障的精髓,把"吗"去掉,把"我"变成"我也",把?变成!,还可以有其他形式,感兴趣的自己玩玩
// 实例化一个Scanner对象,用来接收输入
Scanner input = new Scanner(System.in);
System.out.println("请输入聊天内容:");
String question, reply;
do {
question = input.next();
reply = question.replace("吗", "");
reply = reply.replace("我", "我也");
reply = reply.replace("?", "!");
System.out.println(reply);
} while (!reply.equals("滚"));
}
}
/*
在吗?
在!
吃了吗?
吃了!
我爱你
我也爱你
能听得懂吗?
能听得懂!
真的吗?
真的!
*/
1.3.3、for循环
格式:
for( 初始化参数;判断条件;更新循环变量){
// 循环体;
}
举个栗子,打印我们小学背过的九九乘法表:
public class MultiplicationTable {
public static void main(String[] args) {
System.out.println("乘法口诀表:");
// 打印乘法口诀表
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(j + "*" + i + "=" + (i * j) + "\t");
}
System.out.println();
}
}
}
1.3.4、退出循环
最后说一下break和continue语句。break语句是直接跳出当前所在的循环(不执行循环体中break之后的语句),不再执行循环,而continue则是跳过这一次的循环(不执行循环体中continue之后的语句),继续执行之后下一次循环的判断和循环。看例子:
public class Demo {
/*
break与continue语句:
break是用于终止本轮所有次循环。即不执行本次循环中break后面的语句,直接跳出循环。
continue是用于终止本轮本次循环。即本次循环中continue后面的代码不执行,进行下一次循环的入口判断。
*/
public static void main(String[] args) {
for(int i=0;i<10;i++) {
if(i==5) {
break;
}
System.out.println(i);
}
System.out.println("-------------------------");
for(int j=0;j<10;j++) {
if(j==5) {
continue;
}
System.out.println(j);
}
}
}
运行之后可以发现,break的代码,只输出到0,1,2,3,4,continue的代码则是没有打印5.
有时候有多个循环嵌套,一个break只能退出它所在的循环,那怎么直接跳出所有循环呢?
通过给for循环起名字的方式,可以实现,参考代码如下:
public class Demo10 {
/*
对循环起名字退出多重循环
*/
public static void main(String[] args) {
loop1:for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
System.out.println("i="+i+" , j="+j);
if(i==5 && j==5) {
break loop1;
}
}
}
}
}
2、Java中的数组
数组是相同数据类型的多个数据的容器。
这些元素按线性顺序排列。所谓线性顺序是指除第一个元素外,每一个元素都有唯一的前驱元素;除最后一个元素外,每一个元素都有唯一的后继元素。(“简单理解就是:一个跟一个顺序排列”)。
创建格式(一维数组):
格式 1. 数据类型[] 数组名称 = new 数据类型[数组长度];
格式 2. 数据类型[] 数组名称 = {数组内容 1,数组内容 2,数组内容 3…数组内容 n};
格式 3. 数据类型[] 数组名;
格式 3 属于只创建了数组引用名, 并未在内存创建数组空间。
格式 4. 数据类型[] 数组名称 = new 数据类型[]{内容 1,内容 2,内容 3…内容 n};
一般我们常用的是格式1和格式2。
下标(索引):
可以理解为数组中内容的数字序号,从 0 开始 ,对于长度为 n 的数组,下标的范围是 0~n-1(尤其注意,往往会出现下标越界,不要记错范围)。
可以通过下标的方式访问数组中的每一个元素。
例如: 创建 int 类型数组 nums , 给数组 nums 的 5 下标赋值数据 , 然后打印
int[] nums = new int[10];
nums[1] = 88;
System.out.println(nums[1]);
数组长度获取:
数组名称.length
使用数组不当, 会出现如下问题:
- 数组未赋值: 空指针异常
- 超出长度的下标操作: 数组越界异常
- 注意:数组的长度在创建时就固定了。
最后说一下二维数组的创建,与一维数组类似:
// 数据类型[][] 数组名称 = new 数据类型[数组长度(必填)][选填];
// 注意的是必填的是必须填写的,后边的选填,如果填写了,那么每一个外层数组里面都是一样的数据量,然后可以通过下标进行赋值。如果不填,则需要动态申请nums[0] = new int[]{1,2,3};
int[][] nums = new int[5][];
nums[0] = new int[]{1,2,3};
nums[1] = new int[]{1,2,3,4,5};
System.out.println(nums[0][2]);
System.out.println(nums[1][3]);
/*
int[][] nums = new int[5][3];
nums[0][2] = 1;
// 下面这一句会报错,因为初始化的时候,限定了长度为3
nums[1][4]= 2;
System.out.println(nums[0][2]);
System.out.println(nums[1][4]);
*/
3、案例过程学习记录
1、一维数组练习—选队长游戏
规则如下:
同学们相约一起爬山游玩,为了更好的进行这场活动,大家准备推举一个人作为出游的临时队长。为了体现合理公平,大家提出了一个比较有趣的规则。所有人围成一圈,顺序排号。从第一个人开始报数(从1 到 3 报数),凡报到 3 的人退出圈子,剩下的人继续报数,最后留下的当选为队长。请编写一个程序,求出一组人中的队长是原来第几位同学。
这个问题其实是约瑟夫问题的变种。
那么代码如下:
import java.util.Scanner;
public class ChooseCaptain {
public static void main(String[] args) {
/*
选队长游戏规则:
所有人围成一圈,顺序排号。从第一个人开始报数(从 1 到 3 报数),
凡报到 3 的人退出圈子,剩下的人继续报数,最后留下的当选为队长。
求出一组人中的队长是原来第几位同学。
*/
Scanner input = new Scanner(System.in);
System.out.println("请输入共有多少位同学:");
int nums = input.nextInt();
// 初始化一个长度为nums的boolean数组,初始值,默认为false 表示没出局
boolean[] classmates = new boolean[nums];
// 定义计数器count记录1,2,3状态,初始为1
int count = 1;
// 定义一个out数组,记录每次出局的人
int[] out = new int[nums - 1];
// 定义一个k=0,用来标记第几轮淘汰的是哪一位同学,
int k = 0;
// 一定要淘汰 nums-1个人,所以 k < nums -1 ,不够,就继续报数,直到只剩一个人
while(k < nums - 1){
// 每一次遍历数组classmates,模拟报数
for (int i = 0; i < nums; i++) {
// 每一次,只有没有被淘汰的人才能报数,也就是他在classmates中状态是false
if (!classmates[i]) {
// 如果报数为3 ,则淘汰,即把状态改为true,同时在out数组记录
if (count == 3) {
classmates[i] = true;
out[k] = i + 1;
k++;
// 输到3之后,该从1继续
count = 1;
// 如果状态为true,说明淘汰了,那么对count + 1,这是下一个人的报数
} else {
count++;
}
}
}
}
// 遍历输出每一轮淘汰的同学
for (int i = 0; i < nums - 1; i++) {
System.out.println("第" + (i + 1) + "轮出局的是第" + out[i] + "位同学");
}
// 最后classmates中,状态为false的同学为队长
for (int i = 0; i < nums; i++) {
if (!classmates[i]) {
System.out.println("队长是原来的" + (i + 1) + "号同学!恭喜这个b");
break;
}
}
}
}
在这个案例中我学习到了使用计数器来模拟报数,并且在为计数器为3之后,就归1。第二个是,学到了用一个boolean数组来标记每个人的状态,这样在遍历数组的时候,直接查看这个人状态就知道是否要跳过。
2、二维数组练习—简单五子棋
规则如下:
- 绘制棋盘
- 提示黑方(用 1 表示)和白方(用 2 表示)分别下棋(X,Y 轴位置) 并重新绘制棋盘。
- 每当一方下棋后判断是否获胜 。
棋盘如下:
代码如下:
import java.util.Scanner;
public class GoBang {
public static void main(String[] args) {
/*
1. 绘制棋盘
2. 提示黑方(用 1 表示)和白方(用 2 表示)分别下棋(X,Y 轴位置)
并重新绘制棋盘。
3. 每当一方下棋后判断是否获胜 。
4. 采用二维数组来模拟棋盘。
chessBoard[0] = new int[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
0123456789101112131415
0000000000000000
0000000000000000
可以发现,第一个是行索引,第二个是列索引 所以我们用x表示列,y表示行,符合坐标轴的习惯
5、难点主要在于对获胜的判断
*/
// 定义一个方法实时绘制棋盘
// 先定义棋盘,并初始化边界索引
int[][] chessBoard = new int[16][16];
chessBoard[0] = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
for (int i = 0; i < 16; i++) {
chessBoard[i][0] = i;
}
// 初次绘制棋盘
paintChessBoard(chessBoard);
// 下边的代码要重复运行直到出现胜者,因此最好封装成方法
// 然后发现,大部分代码都一样,因此调整,加参数标记黑白
// 提示黑方下棋 黑色棋子用1,白色棋子用2
Scanner input = new Scanner(System.in);
// 初始化x,y坐标
int x, y;
// 定义一个布尔变量记录上一步白方是否落子
boolean white;
// 定义一个布尔变量判断是否越界
boolean outside = false;
// 初始为黑方先下
int marker = 1;
String text;
while (true) {
if (marker == 1) {
text = "黑";
white = false;
} else {
text = "白";
white = true;
}
System.out.println("请" + text + "方落子:");
System.out.println("输入X坐标:");
if (input.hasNextInt()) {
do {
x = input.nextInt();
System.out.println("输入Y坐标:");
y = input.nextInt();
if (x < 1 || y < 1 || x > 15 || y > 15 ) {
System.out.println("那里是边界,不可以落子!请重新输入!");
System.out.println("请" + text + "方落子:");
System.out.println("输入X坐标:");
outside = true;
} else if (chessBoard[y][x] != 0) {
System.out.println("哪里已经有棋子了!请重新输入!");
System.out.println("请" + text + "方落子:");
System.out.println("输入X坐标:");
}else{
outside = false;
}
} while (outside || chessBoard[y][x] != 0 );
// outside = false;
chessBoard[y][x] = marker;
paintChessBoard(chessBoard);
// 判断是否有赢家
if (isWin(chessBoard, x, y, marker)) {
System.out.println(text + "方赢!");
break;
}
// 更改marker
marker = white ? 1 : 2;
} else {
System.out.println("输入了错误的格式,请重新开始游戏!");
break;
}
}
}
public static void paintChessBoard(int[][] chessBoard) {
System.out.println("------------------------------------------------------------------");
System.out.println(" —X ");
System.out.println("|");
System.out.println("Y");
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
System.out.print(chessBoard[i][j] + "\t");
}
System.out.println(" ");
}
System.out.println("------------------------------------------------------------------");
}
public static boolean isWin(int[][] chessBoard, int x, int y, int marker) {
// 判断横向的是否有5个棋子相连,即横坐标是相同的,即chessBoard[y][x]中y值是相同的
// 判断纵向的是否有5个棋子相连,即纵坐标是相同的,即chessBoard[y][x]中x值是相同的
// 判断斜向的是否有5个棋子相连,即纵横坐标是相差1的,即chessBoard[y][x]中x,y各差1
return checkCount(chessBoard, 1, 0, x, y, marker) >= 5
|| checkCount(chessBoard, 0, 1, x, y, marker) >= 5
|| checkCount(chessBoard, 1, 1, x, y, marker) >= 5
|| checkCount(chessBoard, 1, -1, x, y, marker) >= 5;
}
// 判断棋子连接的数量
public static int checkCount(int[][] chessBoard, int xChange, int yChange, int x, int y, int marker) {
int count = 1;
int tempX = xChange;
int tempY = yChange;
// 先向一个方向搜索
// 不能超出边界 而且要注意 y是行,x是列
while (x + xChange >= 1 && x + xChange <= 15 && y + yChange >= 1 && y + yChange <= 15
&& marker == chessBoard[y + yChange][x + xChange]) {
count++;
if (xChange != 0)
xChange++;
if (yChange != 0) {
if (yChange > 0)
yChange++;
else {
yChange--;
}
}
}
// 向另一个方向搜索
xChange = tempX;
yChange = tempY;
while (x - xChange >= 1 && x - xChange <= 15 && y - yChange >= 1 && y - yChange <= 15
&& marker == chessBoard[y - yChange][x - xChange]) {
count++;
if (xChange != 0)
xChange++;
if (yChange != 0) {
if (yChange > 0)
yChange++;
else {
yChange--;
}
}
}
return count;
}
}
运行截图:
学习记录:
二维数组的第一个是行索引,第二个是列索引。
五子棋的练习中,为了让用户输入正确的格式和整数范围,我使用了hasNextInt()方法,和if判断,并使用了一个布尔变量记录是否越界,然后发现,程序出现了问题,经过排查,发现是,我在把越界设置成true之后,没有在下次用户输入了合适的数据后,把它变成false,导致出现奇怪的现象。
还有就是,我在判断是否那个位置有棋子和越界上遇到了问题,我的if先判断了是否由棋子,然后用else if 判断了是否越界,结果就会运行出错,无法正常判断越界,然后我意识到是次序的原因。经过这个练习,我深刻的了解了判断次序的重要性,还有就是意识到了短路与,短路或的好处,当条件较多时,用这个很不错。
而获胜的判断,较为繁琐,但是仔细捋程序就能捋通。
经过这次的清除BUG,我只能说,当遇到问题的时候,仔细一句一句的跟着捋程序,一些问题就迎刃而解了。