JavaSE 基础 (2) 运算符和流程控制语句

隐式转换

为什么要学习类型转换

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/1.png
(图1)

类型转换的分类

  • 隐式转换(自动类型转换)

将数据类型中,取值范围小 的数据,给 取值范围大 的类型赋值,可以直接赋值

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/2.png
(图2)

除了 boolean 之外的七种类型都是可以自动转化的

容量小的数据类型可以自动转换为容量大的数据类型(不是指字节数指的是 表述的范围

实线表示无数据丢失的自动转换,虚线表示转换时可能会有精度的损失。

long->float: 这是因为 float 类型的精度比 long 类型低,它只能存储一定范围内的数值,并且只能提供有限的精度。因此,当将一个较大的 long 值转换为 float 类型时,可能会出现精度损失,即转换后的值可能与原始值略有不同。例如,假设有一个 long 值 1234567890123456789,将其转换为 float 类型后,可能会得到一个类似 1234567890123456700 的值,因为 float 类型只能存储这个范围内的一部分精确值。

int->float: int 是整型,用于表示整数,其值是精确的。而 float 是浮点型,用于表示实数,其值是近似值。因此,当 int 转换为 float 时,是由准确值变成了近似值,从而可能导致精度损失。例如,对于 int 的值 1000,转换为 float 时,可能被存为 1000.00000000001,用来计算或者输出时看不出区别,但实际上已经发生了变化。

int->double: 
int 是整型,用于表示整数,其值是精确的。而 double 是双精度浮点型,用于表示实数,其值也是精确的。因此,当 int 转换为 double 时,其值不会发生改变。
例如,对于 int 的值 1000,转换为 double 时,仍然是 1000.0,没有发生精度损失。

为什么 double 的值是精确的,float 的值是近似值?
(1)double 是双精度浮点数,它有 64 位(1 个字节 8 位),其中 1 位是符号位,63 位用来表示小数部分,指数部分占 8 位。由于小数部分有 63 位,所以 double 可以表示的数字范围非常大,而且精度非常高。因此,double 的值是精确的。
(2)而 float 是单精度浮点数,它只有 32 位,其中 1 位是符号位,23 位用来表示小数部分,指数部分占 8 位。由于小数部分只有 23 位,所以 float 可以表示的数字范围较小,精度也较低。因此,float 的值是近似值。

概述:自动发生,不需要做任何处理。

1
2
3
4
5
6
public static void main(String[] args){
		int a = 10;  //int 4个字节
		double b = a;  //double 8个字节
		System.out.println(b);//10.0
}
//不用管 double 是双精度还是单精度, 他们都代表小数, 所以是10.0, 而不是10.00

ab 在运算的过程中,就会先将 a 提升为 double 类型,当类型统一后,再进行运算

特殊关注:byte、short、char 三种数据在运算的时候,不管是否有更高的数据类型,都会提升为 int,然后再进行运算

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/3.png
(图3)

特例:整型常量可以直接赋值给 byte、short、char 等类型变量,而不需要进行强制类型转换,只要不超出其表数范围即可
Short b = 12;
Short b = 12134567; × 超出范围了

强制转换

把一个表示数据 范围大的数值 或者 变量 赋值给另一个表示数据 范围小的变量

格式: 数据类型 变量名 = (数据类型)待转数据;
注意:
	1: 强制类型转换,又被称为造型(cast),用于强制的转换一个数值的类型,在有可能丢失信息的情况下进行的转换是通过造型来完成的,但可能造成精度降低或溢出。
	2: 当将一种类型强制转换成另一种类型,而又超出了目标类型的表数范围,就会被截断成为一个完全不同的值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int money = 1000000000; //10亿
int years = 20;

//返回的total是负数,超过了int的范围
int total = money*years;
System.out.println("total="+total);

//返回的total仍然是负数。默认是int,因此结果会转成int值,再转成long。但是已经发生了数据丢失
long total1 = money*years;
System.out.println("total1="+total1);

//返回的total2正确:先将一个因子变成long,整个表达式发生提升。全部用long来计算。
long total2 = money*((long)years);
System.out.println("total2="+total2); //Long total2 = 1L*money*years;也可以

常量优化机制

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/4.png
(图4)

当常量在左侧的范围内时, 编译时不会报错, (隐藏了强制类型转换)?

算数运算符

运算符和表达式

  • 运算符:对常量或者变量进行操作的 符号
  • 表达式:用 运算符 把常量或者变量连接起来 符合 java 语法的式子 就可以称为表达式。不同运算符连接的表达式体现的是不同类型的表达式。
  • 举例说明:
    int a = 10;
    int b = 20;
    int c = a + b;
    +: 是运算符,并且是算术运算符
    a+b:是表达式,由于 + 是算术运算符,所以这个表达式叫算术表达式
/images/java/JavaSE 基础 (2) 运算符和流程控制语句/5.png
(图5)

算术运算符

  • 二元运算符
/images/java/JavaSE 基础 (2) 运算符和流程控制语句/6.png
(图6)

注意事项:

整数相除,结果只能得到整数,如果想要得到带有小数的结果,必须加入小数(浮点类型)的运算

1
2
System.out.println(10.0/3);
System.out.println(10/3.0);

(1) 整数运算
如果两个操作数有一个为 long,则结果也为 long。
没 long 时,结果为 int。即使操作数全为 short,byte, 结果也是 int

(2) 浮点运算
如果两个操作数有一个为 double, 则结果为 double
只有两个操作数都是 float,则结果才为 float

(3) 取模运算
其操作数可以为浮点数,一般使用整数,结果是"余数",余数符号和左边操作数相同

  • 一元运算符
/images/java/JavaSE 基础 (2) 运算符和流程控制语句/7.png
(图7)

(++)和(–)既可以放在变量的后边,也可以放在变量的前边。

  • 单独使用的时候: (++)和(–)放前放后没区别, 结果是一样的
  • 参与操作的时候:
    ++ 在前: 先对该变量做 自增(++) 或者 自减(--),然后再拿变量参与操作。
    ++ 在后: 先 将该变量原本的值,取出来 参与操作,随后再进行 自增(++)自减(--)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int a=3;
int b=a++;              //先把a=3赋值给b,再自增
System.out.println(a);  //4
System.out.println(b);  //3

int a=3;
int b=++a;              //a先自增,再把值赋给b
System.out.println(a);  //4
System.out.println(b);  //4

int num = 123;
System.out.println(num++);  //123  ++在后, 先将变量的值取出来参与操作, 所以打印123, 随后再进行自增, 然后num的值等于124
System.out.println(num++);  //124

注意: (++)(--),只能操作变量不能操作常量

1
2
3
4
5
System.out.println(10++);  //报错

//分析
a++; a = a + 1;
10++;  10 = 10 + 1; 10 = 11; //两个常量赋值出错

字符的 + 操作

1
2
3
int a = 1;
char b = 'a';
System.out.println(a + b);

a+b 的运算中,a 为 int 类型,b 为 char 类型
当 (byte short char int) 在一起运算的时候,都会提升为 int 之后,再进行运算
但是,char 属于字符,字符是怎样提升为 int 数值的呢?

ASCII 码表

ASCII(American Standard Code for Information Interchange): 美国信息交换标准代码, 是计算机中 字节字符一套对应关系

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/8.png
(图8)
  • 为什么要有这样的码表?

计算机中数据的存储,都是以字节的形式在进行存储,我们不会直接操作繁琐的、不便于记忆的字节。
例如:我们想要使用字符 a,但 a 字符真正存储的字节表示是数值 97,如果直接面向字节,那现在要表示字符 j
难道还要现去查找吗? 不现实,太麻烦

'a' --- 97
'A' --- 65
'0' --- 48
  • 运算过程
1
2
3
int a = 1;
char b = 'a';
System.out.println(a + b);

a+b 的运算中,a 为 int 类型,b 为 char 类型
当 (byte short char int) 在一起运算的时候,都会提升为 int 之后,再进行运算
char 提升为 int 的过程,就是查找码表中,字符所对应的数值表示形式
字符 ‘a’ 查找到数值的 97 之后,在跟数值 1 进行运算,结果就是 98
所以,最终输出在控制台的结果就是 98

字符串的 + 操作 (字符串连接符)

+ 操作中, 如果出现字符串时,这个 + 是【字符串连接符】,否则就是算术运算。当连续进行 + 操作时,从左到右逐个执行。
+ 运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串, 然后再进行连接

1
2
3
4
char c1 = 'h';
char c2 = 'i';
System.out.println(c1 + c2)
System.out.println("" + c1 + c2)    //通过加""+将整个运算转为字符串连接操作
1
2
3
system.out.println(1+99+"年老店");  //100年老店  从左至右执行, 1+99=100, 100+"年老店" -> 100年老店
System.out.println ("5+5="+5+5);   //5+5=55  "5+5="+5组成新的字符串"5+5=5", 然后"5+5=5"+5再组成新的字符串"5+5=55"
System.out.println ("5+5="+(5+5));  //5+5=10  通过加括号, 提升优先级, 先计算, 再拼接

案例: 数值拆分

需求:键盘录入一个三位数,将其拆分为个位、十位、百位后,打印在控制台
运行结果:

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/9.png
(图9)

分析:

①:使用 Scanner 键盘录入一个三位数

②:个位的计算:数值%10
  123 除以 10 (商 12,余数为 3)

③:十位的计算:数值/10%10
  123 除以 10 (商 12,余数为 3,整数相除只能得到整数)
  12 除以 10 (商 1,余数为 2)

④:百位的计算:数值/100
  123 除以 100 (商 1,余数为 23)

⑤:将个位,十位,百位拼接上正确的字符串,打印即可

赋值及其扩展运算符

赋值运算符

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/10.png
(图10)

注意事项: 扩展的赋值运算符 隐含 了强制类型转换

1
2
3
4
5
public static void main(String[] args) {
  a = 3;
  b = 4;
  a *= b + 3;   //等价于a=a*(b+3)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
    short s = 1;

    //s是short类型,1默认是int类型
    //short和int相加,short会先提升为int,然后再进行运算
    //提升之后,就是两int相加,两个int相加,结果还是int,将int赋值给short
    //需要加入强转。
    //s=s+1:
    //错误:不兼容的类型:从int转换到short可能会有损失
    s = (short)(s + 1);
    System.out.println(s);

    short ss = 1;
    ss += 1;
    //ss = (short)(ss +1);
    //注意:扩展赋值运算符底层会自带强转功能
    System.out.println(ss);
}

关系运算符

关系运算符(比较运算符)

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/11.png
(图11)

关系运算的结果是布尔值: true/false

  • >=:大于或等于
  • <=:小于或等于
  • ==, !=:是所有(基本和引用)数据类型都可以使用
  • >>=<<=:仅针对数值类型(byte/short/int/long, float/double 以及char), Char 值位于 65535 之间,所以可以进行比较

逻辑运算符

逻辑运算符概述

在数学中,一个数据 x, 大于 5,小于 15,我们可以这样来进行表示:5<x<15
Java 中,需要把上面的试子先进行拆解,再进行合并表达。
拆解为:x>5x<15
合并后:x>5&x<15

逻辑运算的操作数和运算结果都是 boolean

  • 逻辑与   &(与)   两个操作数为 true,结果才是 true,否则是 false
  • 逻辑或   |(或)   两个操作数有一个是 true,结果就是 true
  • 短路与   &&(与)   只要有一个为 false,则直接返回 false
  • 短路或   ||(或)   只要有一个为 true,则直接返回 true
  • 逻辑非   !(非)   取反: !falsetrue!truefalse
  • 逻辑异或  ^(异或)  相同为 false,不同为 true

短路与短路或 采用短路的方式。从左到右计算,如果只通过运算符左边的操作数就能够确定该逻辑表达式的值,则不会继续计算运算符右边的操作数,提高效率。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
    int x = 3;
    int y = 4;
    //false & true
    System.out.println(++x > 4 && y-- < 5); //false
    System.out.println("x=" + x); //4
    System.out.println("y=" + y); //4  说明&&没有执行右边的操作, 所以y=4, 如果是&, y=3
}

位运算符

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/12.png
(图12)

位运算指的是进行二进制位的运算

  • ~  取反   0->1 、1->0
  • &  按位与  1 1->1、0 0->0、1 0->0、0 1->0
  • |  按位或  1 1->1、0 0->0、1 0->1、0 1->1
  • ^  按位异或  1 0->1、0 1 ->1、1 1->0、0 0 ->0 (一个数被另一个数异或2次, 该数不变)
  • <<  左移运算符,左移1位相当于乘2
  • >>  右移运算符,右移1位相当于除2取商

位运算符介绍

思考:System.out.println(6 & 2); 结果是多少?

  • 位运算符指的是二进制位的运算,先将十进制数转成二进制后再进行运算。
  • 在二进制位运算中,1表示true, 0表示false。

System.out.println(6 & 2) 的计算过程

将十进制转换为二进制
  00000000 00000000 00000000 00000110 //6的二进制

&00000000 00000000 00000000 00000010 //2的二进制

  00000000 00000000 00000000 00000010 //结果为: 2


System.out.println(~6) 的计算过程

~:取反涉及到补码操作

  00000000 00000000 00000000 00000110 //6的二进制补码(正数原码、反码、补码都一样)

~11111111 11111111 11111111 11111001 //取反(包括符号位)

-                   1 //-1 求反码

  11111111 11111111 11111111 11111000 //反推原码(符号位不变)

  10000000 00000000 00000000 00000111 //-7

1
2
3
4
5
6
7
public static void main(String[] args) {
    System.out.println(12 << 1); //24
    System.out.println(12 << 2); //48

    System.out.println(4 >> 1); //2
    System.out.println(3 >> 1); //1
}

<< 有符号左移运算,二进制位向左移动,左边符号位丢弃,右边补齐 0

运算规律:向左移动几位,就是乘以 2 的几次幂

00000000 00000000 00000000 00001100 //12的二进制

我想把 12 的二进制整体向左移动, 那就只能在数据的最后加上一个新的数据, 这样才能把它左边的内容整体挤一下, 那就是往左移动一下, 往左一挤, 必然会有数据被挤出去, 你加了一个新数据, 就会有一个数据被挤出去, 你加了两个新数据, 就会有两个数据被挤出去

(0)0000000 00000000 00000000 000011000 //结果为24

这种通过新的数据挤出老数据的过程就是它的左移过程

二进制位向左移动,左边符号位丢弃,右边补齐 0 就是说右边加的新数据都是 0, 左边的数据会被挤出去, 会发现无论加几个新的数据, 被挤出去的肯定有符号位, 所以是符号位丢弃, 右边通过 0 进行补齐


>> 有符号右移运算,二进制位向右移动,使用符号位进行补位

运算规律:向右移动几位,就是除以 2 的几次幂

00000000 00000000 00000000 00000011 //3的二进制

我想把 3 的二进制整体向右移动, 只有在左边加入新的数据, 后边的内容才会向右进行移动, 现在的问题是, 这个新的数据到底该加什么呢?

二进制位向右移动,使用符号位进行补位 那就意味着, 你的符号位是 0 的话, 新的数据就是 0, 你的符号位是 1 的话, 新的数据就是 1

000000000 00000000 00000000 0000001(1) //1的二进制


>>> 无符号右移运算符,无论符号位是 0 还是 1,都补 0

10000000 00000000 00000000 00000110 //-6 的二进制

无论符号位是 0 还是 1,都补 0 也就是说, 我现在是一个负数, 我要把它往右移动一位, 需要在左边加数据, 但是无符号右移, 无论符号位是 1 还是 0, 左边都会有 0 进行部位, 那就意味着我原本是一个负数, 加上一个 0 就可能变成正数, 用的很少

使用 <<>> 实现乘 2 和除 2 的效率会非常高, 因为他俩可以直接操作二进制数, 而之前我们的乘法和除法都是需要把十进制转换为二进制, 然后再进行相关的操作


13 « 2 和 13 * 2 的流程如下
13 « 2
首先,将 13 转换为二进制形式,即 1101(二进制)。
然后,将这个二进制数向左移动两位,得到 110100(二进制),这在十进制中等于 20。
因此,13 « 2 的结果是 20。

13 * 2
首先,将 13 和 2 转换为二进制形式,即 1101(二进制)和 10(二进制)。
然后,执行乘法运算,得到 1101 * 10 = 11010(二进制),这在十进制中等于 26。
因此,13 * 2 的结果是 26。

这个流程也再次说明了为什么 « 运算通常比 * 运算更快:前者只需要移动位,而后者需要执行真正的乘法运算。


笔试的时候,3*2 怎么最快? 答:移位,左移一位,3«1

运算符右边的代表要移动的位数

1
2
3
4
//移位
int c = 5<<2;  //相当于:5*2*2=20
System.out.println(c);
System.out.println(40>>3);  //相当于40/2/2/2

异或运算的特点 (^)

一个数,被另外一个数,异或两次,该数本身不变。

1
2
3
4
5
public static void main(String[] args) {
    System.out.println(10 ^ 5); //15
    System.out.println(10 ^ 5 ^ 5); //10
    System.out.println(10 ^ 5 ^ 10); //5
}

案例:数据交换

需求:已知两个整数变量 a=10, b=20, 使用程序实现这两个变量的数据交换

最终输出 a=20, b=10:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static void main(String[] args) {
    int a = 10;
    int b = 20;

    //将a原本记录的值,交给temp记录(a的值,不会丢了儿)
    int temp = a;
    //用a变量记录b的值,(第一步交换完毕,b的值也丢不了了)
    a = b;
    //使用b变量记录temp的值,也就是a原本的值(交换完毕)
    b = temp;

    //输出a和b变量即可
    System.out.println("a=" + a);
    System.out.println("b=" + b);
}

不允许使用三方变量, 实现上述要求, 用 ^ 来进行优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void main(String[] args) {
    int a = 10;
    int b = 20;

    a = a ^ b; //10 ^ 20
    b = a ^ b; //10 ^ 20 ^ 20
    a = a ^ b; //10 ^ 20 ^ 10

    System.out.println("a=" + a); //20
    System.out.println("b=" + b); //10
}

三元运算符 (条件运算符)

x ? y : z

格式:关系表达式 表达式1 表达式2;

执行流程:

  • 首先 计算关系表达式的值
  • 如果值为 true, 取 表达式1 的值
  • 如果值为 false, 取 表达式2 的值

相当于 if else

案例: 三个和尚

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/13.png
(图13)

分析:

①定义三个变量用于保存和尚的身高,单位为cm, 这里仅仅体现数值即可。
int height1 = 150;
int height2 = 210;
int height3 = 165;

②用三元运算符,比较前两个变量,获取较大值。
int tempHeight = (height1 > height2) ? height1 : height2;

③用三元运算符,让较大值和第三个变量比较,获取最大值。
(tempHeight > height3) ? tempHeight : height3;

运算符的优先级

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/14.png
(图14)

建议

  • 大家不需要去刻意的记这些优先级,表达式里面优先使用小括号来组织!!
  • 逻辑与、逻辑或、逻辑非的优先级一定要熟悉!(逻辑非>逻辑与>逻辑或)
  • 如:a||b&&c 的运算结果是:a||(b&&c),而不是(a||b)&&c

流程控制语句

流程控制语句:通过一些语句,来 控制程序执行流程

流程控制语句分类

  • 顺序结构:先执行a,再执行b
  • 条件判断结构(分支结构):如果…,则…
  • 循环结构:如果…,则重复执行…

顺序结构

顺序结构语句是 Java 程序,默认的执行流程,按照代码的先后顺序依次执行,从上到下,从左到右。

顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。

顺序结构执行流程图:

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/15.png
(图15)

条件判断结构(分支结构)

if 单分支结构

1
2
3
4
格式:
if (关系表达式) {
  语句体;
}

if 单选择结构流程图:

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/16.png
(图16)

新手雷区
如果 if 语句不写 { },则只能作用与后面的第一条语句
强烈建议,任何时候都写 { },即使里面只有一句话!

if-else 双分支结构

1
2
3
4
5
if (布尔表达式) {
  语句块 1
} else {
  语句块 2
}

if-else 双选择结构流程图:

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/17.png
(图17)

if-else if-else 多分支结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if (布尔表达式 1) {
  语句块 1
} else if (布尔表达式 2) {
  语句块 2
}......
else if (布尔表达式 n) {
  语句块 n
} else {
  语句块 n+1
}

if-else if-else 多分支结构流程图:

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/18.png
(图18)

案例:考试奖励

需求:键盘录入学生考试成绩, 根据成绩程序给出不同的奖励。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
  //1.使用Scanner录入考试成绩
  Scanner sc = new Scanner(System.in);
  System.out.println("请输入您的成绩:");
  int score = sc.nextInt();
  //2.判断成绩是否在合法范围内0~100
  if(score >= 0 & score <= 100) {
    //合法成绩
    //3.在合法的语句块中判断成绩范围符合哪一个奖励
    if(score >= 95 & score < 100) {
      System.out.println("自行车一辆");
    } else if(score >= 90 & score < 94) {
      System.out.println("游乐场一次");
    } else if(score > 80 & score < 89) {
      System.out.println("变形金刚一个");
    } else {
      System.out.println("挨顿揍,这座城市又多了一个伤心的人~"):
    }
  } else {
    //非法的话,给出错误提示
    System.out.println("您的成绩输入有误!")
  }
}

switch 多分支结构(多值情况)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
switch (表达式) {
  case 值1:
    语句序列1;
    [break];
  case 值2:
    语句序列2;
    [break];
  ... ... ...   ... ...
  default:
    默认语句;
    [break]
}

Switch 语句中 case 标签在 JDK1.5 之前必须是整数(long 类型除外)或者枚举,不能是字符串,在 JDK1.7 之后允许使用字符串(String)


格式说明:

  1. 表达式:(将要被匹配的值)取值为 byte、short、int、char, JDK5 以后可以是枚举,JDK7 以后可以是 String.
  2. case:后面跟的是要和表达式进行比较的值(被匹配的值), 不能重复, 只能是常量, 没有顺序要求
  3. break:表示中断,结束的意思,用来结束 switch 语句。
  4. default:表示所有情况都不匹配的时候,就执行该处的内容,和 if 语句的 else 相似。
  5. case 或 default 后边的 break 不能省略, 如果省略会出现 case穿 透效果

运行流程:

  1. 先计算 “变量” 的值, 得到一个结果
  2. 拿着 “计算结果” 开始和 case 后边的值进行 “比对”
  3. 比对的顺序是 “从上往下”, 只比对 case
  4. 比对过程中, 如果不一样, 则继续往后比对
  5. 如果一样, 则执行对应的语句体, 然后继续往下运行(不考虑 case 的比对了), 直到遇到 break
  6. 如果把所有的 case 都比对完毕后仍然找不到一样的, 则执行 default 中的语句, 直到遇到 break 结束

switch 多分支结构流程图:

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/19.png
(图19)

switch 和 if 的区别

  • switch: 只能做 “等值” 判断, 但 switch 的效率高
  • if: 既能做 “等值” 判断, 又能做 “范围” 判断

案例:减肥计划

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static void main(String[] args) {
    //1.键盘录入星期数据,使用变量接收
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入");
    int week = sc.nextInt();
    //2.多情况判断,采用switch语句实现
    switch(week) {
      //3.在不同的case中,输出对应的减肥计划
      case 1:
        System.out.println("跑步");
        break;
      case 2:
        System.out.println("游泳");
        break;
      case 3:
        System.out.println("慢走");
        break;
      case 4:
        System.out.println("动感单车");
        break;
      case 5:
        System.out.println("拳击");
        break;
      case 6:
        System.out.println("爬山");
        break;
      case 7:
        System.out.println("好好吃一顿");
        break;
      default:
        System.out.println("您的输入有误");
        break;
    }
}

case 穿透现象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入星期数:"): int week = sc.nextInt();
    switch(week) {
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
        System.out, println("工作日");
        break;
      case 6:
      case 7:
        System.out, println("休息日"): break;
      default:
        System.out, println("您的输入有误")
        break;
    }
}

注意:在 switch 语句中,如果 case 控制的语句体后面不写 break, 将出现 穿透 现象

现象:当开始 case 穿透,后续的 case 就不会具有匹配效果,内部的语句都会执行, 直到看见 break, 或者将整体 switch 语句执行完,オ会结束

应用场景:当发现 switchi 语句中,多个 case 给出的语句体出现了重复的,就可以考虑使用 case 穿透来优化代码。

循环结构

循环结构分两大类,一类是当型,一类是直到型。

  • 当型
     当布尔表达式条件为 true 时,反复执行某语句,当布尔表达式的值为 false 时才停止循环,比如:while 与 for 循环。

  • 直到型:
     先执行某语句,再判断布尔表达式,如果为 true, 再执行某语句,如此反复,直到布尔表达式条件为 false 时才停止循环,比如 do-while 循环。

while

1
2
3
while (布尔表达式) {
  循环体;
}
/images/java/JavaSE 基础 (2) 运算符和流程控制语句/20.png
(图20)

do while

1
2
3
do {
  循环体;
} while (布尔表达式);
/images/java/JavaSE 基础 (2) 运算符和流程控制语句/21.png
(图21)

for

1
2
3
for (初始表达式; 布尔表达式; 迭代因子) {
  循环体;
}
/images/java/JavaSE 基础 (2) 运算符和流程控制语句/22.png
(图22)

三种循环的区别
循环变量:
  for 循环: 循环变量只能有一个,循环结束后,循环变量不能使用.
  while 循环: 循环变量可以有多个,循环结束后,循环变量可以正常使用.
应用场景:
  for 循环: 更加适合 “知道从哪开始,到哪结束” 的循环.
  while 循环: 不知道开始和结束位置的循环.
  dowhile 循环: 一般不用.


逗号运算符

1
2
3
4
5
6
7
/**
 * 逗号运算符
 * i初始值为1,j初始值为4
 */
for(int i = 1, i = 1 + 3; i <= 5; i++, i = 2 * 1) {
  System.out.println("i=" + i + ",j=" + j);
}

案例:水仙花

需求:在控制台输出所有的 “水仙花数”

什么是 “水仙花数”?

① 水仙花数是一个三位数

111  222  333  370  371  520  999

② 水仙花数的个位、十位、百位的数字立方和等于原数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static void main(String[] args) {
  //1.通过循环获取所有的三位数100-999
  for(int i = 100; i <= 999; i++) {
    //2.将每一个三位数拆分为个位,十位,百位
    int ge = i % 10;
    int shi = i / 10 % 10;
    int bai = i / 10 / 10 % 10;
    //int bai = i / 100;
    //3.加入i判断条件,计算是否是水仙花数,是的话输出打印。
    if(ge * ge * ge + shi * shi * shi + bai * bai * bai == i) {
      System.out.println(i);
    }
  }
}

需求:在控制台输出所有的 “水仙花数”,要求每行打印2个

System.out.print(打印内容); 打印后不换行
System.out.println(打印内容); 打印后换行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public static void main(String[] args) {
  //1.定义变量count,用于保存"打印过"的数量,初始值为0
  int count = 0;
  for(int i = 100; i <= 999; i++) {
    int ge = i % 10: int shi = i / 10 % 10;
    int bai = i / 10 / 10 % 10: if((ge * ge * ge + shi * shi * shi + bai * bai * bai) == i) {
      //2.在判定和打印水仙花数的过程中,拼接空格,但不换行,并在打印后让cout变量+1,记录打印过的数量
      System.out.print(i + " ");
      count++;
      //3.在每一次cout变量+1后,判断是否到达了2的倍数,是的话,换行
      if(count % 2 == 0) {
        System.out.println(); //可以用System.out.print("\n");
      }
    }
  }
}

循环补充

无限循环(死循环)

初始化部分, 条件判断部分和迭代因子可以为空语句,但必须以 ; 分开

while(true) 相当于 for(;;)ctrl + c 结束死循环

1
2
3
4
5
public static void main(String[] args) {
  for(;;) {
    System.out.println("我是无限循环体");
  }
}

嵌套循环

在一个循环语句内部再嵌套一个或多个循环,称为嵌套循环。while、do-while 与 for 循环可以任意嵌套多层。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
1	1	1	1	1
2	2	2	2	2
3	3	3	3	3
4	4	4	4	4
5	5	5	5	5

public static void main(String[] args) {
  for(int i = 1; i <= 5; i++) {
    for(int j = 1; j <= 5; j++) {
      System.out.print(i + "\t");
    }
    System.out.println();
  }
}

案例:九九乘法表

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/23.png
(图23)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Test {
  public static void main(String[] args) {
    for(int i = 1; i < 10; i++) { //i是一个乘数
      for(int j = 1; j <= i; j++) { //j是另一个乘数
        System.out.print(j + "*" + i + "=" + (i * j < 10 ? ("" + i * j) : i * j) + "");
      }
      System.out.println();
    }
  }
}

1 * 1 = 1

1 * 2 = 2  2 * 2 = 4

1 * 3 = 3  2 * 3 = 6  3 * 3 = 9

每一行标红的数字不变,另一个乘数变,把不变的放在外层循环,变得放在内层循环

案例:逆序九九乘法表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
9*9=81 8*9=72 7*9=63 6*9=54 5*9=45 4*9=36 3*9=27 2*9=18 1*9=9   
8*8=64 7*8=56 6*8=48 5*8=40 4*8=32 3*8=24 2*8=16 1*8=8   
7*7=49 6*7=42 5*7=35 4*7=28 3*7=21 2*7=14 1*7=7   
6*6=36 5*6=30 4*6=24 3*6=18 2*6=12 1*6=6   
5*5=25 4*5=20 3*5=15 2*5=10 1*5=5   
4*4=16 3*4=12 2*4=8 1*4=4   
3*3=9 2*3=6 1*3=3   
2*2=4 1*2=2   
1*1=1

/*
	逆序,九九乘法表
*/
for(int x = 9; x > 0; x--) {
	for(int y = x; y > 0; y--) {
		System.out.print(y + "*" + x + "=" + (y * x) + " ");
	}
	System.out.println();
}

案例:打印输出5*5方阵

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/24.png
(图24)

通过分析可知, 行数+列数=a, 若 a 是奇数, 则是#, 若 a 是偶数, 则是 *

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
*  #  *  #  *
#  *  #  *  #
*  #  *  #  *
#  *  #  *  #
*  #  *  #  *

for(int d = 1; d <= 5; d++) {
	for(int c = 1; c <= 5; c++) {
		if((d + c) % 2 == 0) {
			System.out.print("*" + "\t");
		} else {
			System.out.print("#" + "\t");
		}
	}
	System.out.println();
}

数组元素互换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/**
 * 需求:
 * 有一个数组:int[] arr={11,22,33,44,55};
 * 要求:
 * 对数组进行翻转
 */
public class TestDemoe1 {
	public static void main(String[] args) {
		int[] arr = {00, 11, 22, 33, 44, 55, 66, 77, 88, 99};

		public static void rev(int[] arr) {
			for(int i 0; i < (arr.length 2); i++) {
				int temp = arr[i];
				arr[i] = arr[arr.length - 1 - i];
				arr[arr.length - 1 - i] = temp;
			}
		}
	}
}

break语句和continue语句

在任何循环语句的主体部分,均可用 break 控制循环的流程。break 用于强行退出循环,不执行循环中剩余的语句。

continue 语句用在循环语句体中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定。

假如:一个 10 次的循环,当循环到第 6 次的时候,碰到 continue,第 6 次循环剩余的部分不执行了,跳到第 7 次循环;碰到 break,7,8,9,10,后面的循环都不执行。

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/25.png
(图25)

break

  • Math.round: 四舍五入
  • Math.random(): 范围为 [0 , 1)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/*
	产生100以内随机数,直到随机数为88时终止循环
*/
int total = 0;
for(;;) {
	total++;
	int i = (int)(Math.random() * 100) + 1;
	if(i == 88) {
		break;
	}
}
System.out.println("运行了" + total + "次");

Continue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/*
	把100-150之间不能被3整除的数输出,并且每行输出5个
*/
int count = 0;
for(int x = 100; x <= 150; x++) {
	if(x % 3 == 0) {
		continue;
	} else {
		System.out.print(x + "\t");
		count++;
		if(count % 5 == 0) {
			System.out.println();
		}
	}
}

return

return; 是一个单独的语句,它表示立即结束当前方法的执行,并将控制权交回给调用该方法的代码。它并不返回任何值,只是简单地结束方法的执行。

循环标号

break 和 continue 只能跳出、跳过自己所在的那一层关系,如果想要跳出、跳过指定的一层就可以加入标号。

/images/java/JavaSE 基础 (2) 运算符和流程控制语句/26.png
(图26)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public static void main(String[] args) {
	lo: while(true) {
		System.out, println("请输入您要查看的星期数:");
		System.out.println("(如无需继续查看,请输入0退出程序)");
		//1.键盘录入星期数据,使用变量接收
		Scanner sc = new Scanner(System.in);
		int week = sc.nextInt();
		//2.多情况判断,采用switch语句实现
		switch(week) {
			//3.在不同的case中,输出对应的减肥计划
			case 0:
				System.out.println("感谢您的使用");
				break lo;
			case 1:
				System.out.println("跑步");
				break;
			case 2:
				System.out.println("游泳");
				break;
			case 3:
				System.out.println("慢走");
				break;
			case 4:
				System.out.println("动感单车");
				break;
			case 5:
				System.out.println("拳击");
				break;
			case 6:
				System.out.println("爬山");
				break;
			case 7:
				System.out.println("好好吃一顿"): break;
			default:
				System.out.println("您的输入有误"): break;
		}
	}
}

质数: 大于 1, 除了 1 和它本身以外不再有其他因数的自然数

通过 continue outer 从内部循环跳到外部循环

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//控制嵌套循环跳转(打印101-150之间所有的质数)
public class Test {
	public static void main(String[] args) {
		outer: for(int i = 101; i < 150; i++) {
			for(int j = 2; j < i / 2; j++) {
				if(i % j == 0) {
					continue outer;
				}
			}
			System.out.print(i + "")
		}
	}
}

不要用 goto

在某一行代码前加个名字,例如:outer,那么在后面的代码里用 goto outer 就可以跳到那一行。在 “goto” 有害论中,最有问题的就是标签,而非 goto,随着标签在一个程序里数量的增多,产生错误的机会也越来越多。

If else while 底层仍是跳转

案例

薪水计算器:
(1) 通过键盘输入用户的月薪,每年是几个月薪水。
(2) 输出用户的年薪
(3) 输出一行字 “如果年薪超过10万,恭喜你超越90%的国人”,“如果年薪超过20万,恭喜你超越98%的国人”。
(4) 直到键盘输入 “exit”,则退出程序(使用break退出循环)
(5) 输入中途,键盘输入 “next”,则这个用户退出计算不显示 “恭喜…",算下一个用户的年薪

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) {
	Scanner sc = new Scanner(System.in);
	System.out.println("个人年薪计算器");
	System.out.println("输入exit退出程序,输入next计算下个年薪");
	while(true) {
		System.out.println("请输入您的月薪:");
		int monthSalary = sc.nextInt();
		System.out.println("请输入您每年是几个月薪水:");
		int months = sc.nextInt();
		int yearSalary = monthSalary * months;
		System.out.println("请输入指令:");
		sc.nextLine();
		String command = sc.nextLine();
		if("exit".equals(command)) {
			System.out.println("退出软件");
			break;
		}
		if("next".equals(command)) {
			System.out.println("请重新输入");
			continue;
		}
		if(yearSalary > 10000 && yearSalary < 200000) {
			System.out.println("恭喜你超越90%的国人!");
		}
		if(yearSalary >= 20000) {
			System.out.println("恭喜你超越98%的国人!");
		}
		System.out.println("年薪是:" + yearSalary);
	}
}

个税计算器
(1) 通过键盘输入用户的月薪
(2) 百度搜索个税计算的方式,计算出应缴纳的税款
(3) 直到键盘输入 “exit”,则退出程序(使用 break 退出循环)

应纳税所得额 = 工资收入金额 - 各项社会保险费 - 起征点(5000元)
应纳税额 = 应纳税所得额 × 税率 - 速算扣除数

级数 应纳税所得额 税率(%) 速算扣除数
1 不超过3,000元的部分 3 0
2 超过3,000元至12,000元的部分 10 210
3 超过12,000元至25,000元的部分 20 1410
4 超过25,000元至35,000元的部分 25 2660
5 超过35,000元至55,000元的部分 30 4410
6 超过55,000元至80,000元的部分 35 7160
7 超过80,000元的部分 45 15160
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
个税
 */
import java.util.Scanner;
public class TestExercise02 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
    while(true) {
			System.out.println("请输入月薪");
			double salary = sc.nextDouble();
			double jiao = salary - 5000; //应纳税所得额
			double shui = 0; //应纳税额
			if(jiao < 0) {
				System.out.println("个税起征点为5000元,不需要纳税");
			} else if(jiao < 3000) {
				shui = jiao * 0.03;
				salary -= shui;
			} else if(jiao < 12000) {
				shui = jiao * 0.1 - 210;
				salary -= shui;
			} else if(jiao < 25000) {
				shui = jiao * 0.2 - 1410;
				salary -= shui;
			} else if(jiao < 35000) {
				shui = jiao * 0.25 - 2660;
				salary -= shui;
			} else if(jiao < 55000) {
				shui = jiao * 0.3 - 4410;
				salary -= shui;
			} else if(jiao < 80000) {
				shui = jiao * 0.35 - 7610;
				salary -= shui;
			} else {
				shui = jiao * 0.45 - 15160;
				salary -= shui;
			}
			System.out.println("应纳税所得额" + jiao + "应纳税额" + shui + "实际工资" + salary);
			System.out.println("输入exit退出程序,输入其他继续");
			sc.nextLine(); //输入月薪后, 我们按了一个回车键, 这一句是用来从缓冲区把换行符读走
			String command = sc.nextLine();
			if("exit".equals(command)) {
				break;
			}
		}
	}
}

Scanner 中 nextInt() 后 nextLine() 无效的原因:

  • nextInt(): 接收一个整型数字,注意,只接受数字
  • nextLine(): 接收一串字符串,包括空格,制表符,换行符

当我们输入数字的时候,是不是按下了回车键,这个时候,nextInt() 从缓冲区把我们输入的数字读走了,但留下了换行符,等到运行 nextLine(),他开始读缓冲区里的内容,然而缓冲区里还留有一个换行符,相当于没给我们机会去输入东西,就按回车结束了

Random

作用:用于产生一个随机数

使用步骤

1
2
3
4
5
6
//1.导包->放在类的上边
import java.util.Random;
//2.创建对象->放在main方法中
Random r = new Random();
//3.产生随机数->放在创建对象的后边(随机范围是"0-数字"之间,包含0,不包含"数字"), 也就是[0, 数字)
int num = r.nextInt(数字);

扩展

1
2
3
4
//产生x->y之间的随机数,包含x,不包含y
int num = r.nextInt(y - x) + x;
//产生x->y之间的随机数,包含x,包含y
int num = r.nextInt(y - x + 1) + x;

案例: 猜数字

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
	//1.准备Randomi和Scanner对象,分别用于产生随机数和键盘录入
	Random r = new Random();
	Scanner sc = new Scanner(System.in);
	//2.使用Random产生一个1-100之间的数,作为要猜的数
	int randomNum = r.nextInt(100) + 1;
	//5.以上内容需要多次进行,但无法预估用户输入几次可以猜测正确,使用while(true)死循环包表
	while(true) {
		//3.键盘录入用户猜的的数据
		System.out.println("请输入您猜的数据:");
		int num = sc.nextInt();
		//4.使用录入的数据(用户猜的数据)和随机数(要猜的数据)进行比较,并给出提示
		if(num > randomNum) {
			System.out.println("猜大了");
		} else if(num < randomNum) {
			System.out.println("猜小了");
		} else {
			//6.猜对之后,break结束。
			System.out.println("恭喜,猜中了");
			break;
		}
	}
}

0%