Java学习之路06---逻辑结构陷阱

架构图

悬挂

逻辑判断语句若是只有连接一条语句时,这一条语句会与离它最近的逻辑判断语句相连,例如以下例子的System.out.println("none-zero!");会接在if结构下,这种语法结构称为悬吊

while(iter < 10)
	if(iter != 0)
		System.out.println("none-zero!");

错误的悬吊常常发生在缩排想表达的逻辑与实际程序执行逻辑不匹配

例如下方程序,当a不大於1时,开发者想把b赋值成20,但else语句的悬吊特性,它其实是连接离它最近的逻辑判断语句if(a > 1),所以只有当a大於5时才会将b赋值成20,缩排对程序的执行没有影响

int a = -1;
int b = 0;

if(a > 1)
	if(a > 5)
		b = 1;
else
	b = 10;

System.out.println("b = " + b);

大括号的重要性

其实当逻辑判断式只有一条语句时是可以省略大括号。若该语句也是逻辑判断语句,则该原则可以继续套用下去

例如下面的程序。但是为了易读性着想请避免嵌套太多非必要逻辑结构

int a = 5;

if(a > 0)
	if(a > 1)
		if(a > 2)
			if(a > 3)
				if(a > 4)
					System.out.println("hi");

那假如我们在每个逻辑判断式下加上else语句,并更改a值,那麽它实际上是悬挂在谁的下方呢?

因为逻辑判断式下的语句都可以被解读成if(...){},所以我们看到程序范例中嵌套的if语句其实可以看成只有一条语句if(...){if(...){...}}

而else语句的判断还是依照悬吊的特性分配给最近的逻辑判断式,若该逻辑判断式已被分配else语句则像上一层分配

以下这种程序类型俗称"波动拳"(看看它的形状),实际编写程序时应该要思考,如何减少嵌套的层次

int a = 11;

if(a > 0)
	if(a > 10)
		if(a > 20)
			if(a > 30)
				if(a > 40)
					System.out.println("hi");
				else
					System.out.println("40");
			else
				System.out.println("0");
		else
			System.out.println("20");
	else
		System.out.println("10");
else
	System.out.println("0");

我们把将上述程序转换成伪代码来看看,其实下面的else语句可以想像成列队等待分配给上面的if语句的排队人潮

/*视为单条语句*/
if(...) // A
	if(...) // B 
		if(...) // C
			if(...) // D
				if(...) // E
					// do something
else // 分配给E
	System.out.println("...");
else // 分配给D
	System.out.println("...");
else // 分配给C
	System.out.println("...");
else // 分配给B
	System.out.println("...");
else // 分配给A
	System.out.println("...");

所以实际输出结果为:

20

假如说开发者在设计之初有进行缩排等格式化的处理,程序的逻辑判断还好处理,不然的话真得惨不忍睹...但无论如何,在编写程序时尽量加上大括号,不论是否只有一条语句

加上括号能让後续开发者更容易读懂程序逻辑,也可以避免程序上的错误,例如以下程序其实就存在两个问题

  1. if(a > 5)只能悬挂一条语句,也就是说Sytem.out.println("a大於5");无论如何都会被执行
  2. else语句必须悬挂在if语句後,但Sytem.out.println("a大於5");不属於if语句,所以发生错误
int a = 4;
if(a > 5)
	Sytem.out.println("a大於0");
	Sytem.out.println("a大於5");
else
	Sytem.out.println("a不大於5");

更安全的做法 → 不会花您多少宝贵的时间(珍视後续开发人员debug的时间?)

int a = 4;
if(a > 5){
	Sytem.out.println("a大於0");
	Sytem.out.println("a大於5");
}
else{
	Sytem.out.println("a不大於5");
}

编译器会将大括号里的内容视为一条语句,等同於逻辑判断式下悬挂一条语句,所以就让我们再看看另一个例子,下面这两种程序结构其实是相同的

for (int i=0; i<5; i++){
	for (int j=0; j<5; j++){
		for (int k=0; k<5; k++){
			// do something
        }
  	}
}

for(int i=0; i<5; i++)
	for(int j=0; j<5; j++)
		for(int k=0; k<5; k++){
			// do something
		}

null statement

当逻辑判断语句结尾直接加上分号结尾时,这时就产生所谓的null statement语句,也就是说你编写的逻辑判断语句不做任何事,它与後方加上空的大括号其实作用是一样的

while语句若遇到null statement,则会遇到无穷回圈,同理若是for回圈没有良好的退出机制,也容易卡进无穷回圈

例如下面几个语句其实就发生了null statement,只有if语句可以安全退出

if(i == 3);

while(iter != myobj.gain());
	System.out.println("I'm here!");;

for(i=0;;i++);

整数与布林

C/C++因为没有所谓真的布林值,所以使用非0和0代替,所以我们常看到逻辑判断语句中使用整数当作true或false的依据

但在java中,布林型态无法和其他型态做运算。因此诸如if, while, do-while, for等语句需要避免整数与布林混用判断

例如下面的错误程序代码

public static void main(String[] args){
	int a=10, b=15, c=20, d=25;
	
	if(a>b && b>c){
		System.out.println("1. 执行成功");
	}
	else if((++d >= a--) == 1){ // boolean不能跟整数比较
		System.out.println("2. 执行成功");
	}
	else{
		System.out.println(3. 执行成功);
	}
}

使用浮点做判断

实际编写程序时尽量不要使用浮点数判断,下列程序虽然能够顺利执行,但实际打印值却与判断值不相等,因为浮点数的运算与赋值上或多或少会存在误差,尽可能的使用整数或其他变数类型当判断式依据

public class MathDemo {

	public static void main(String[] args) {
		float c=123456789.123456789f;
		if(c > 123456789.123456789f){
			System.out.println("c > 123456789.123456789");
		}
		else if(c == 123456789.123456789f){// 危险
			System.out.println("c == 123456789.123456789");
		}
		else{
			System.out.println("c < 123456789.123456789");
		}
		
		System.out.println(c);
	}
}

数据类型判断原则

可用来比较的数据类型

  1. int
  2. long
  3. double
  4. float
  5. char
  6. String

原则上只要能透过自动类型转换进行匹配的变数类型都可以进行互相比较。不过一般情况下都是相同数据类型之间进行比较

int 		i = 6;
long 		l = 18l;
double 		d = 6.0d;
float 		f = 6.0f;
char 		ch = 'a';
String 		s = "a";
boolean 	b = true;

/*整数之间比较*/
System.out.println(i > l); // false

/*浮点数之间比较*/
System.out.println(f == d); // true

/*整数与浮点数之间比较*/
System.out.println(i == d); // true
System.out.println(l > f); // true

/*整数与字元之间*/
System.out.println(i > ch); // false
System.out.println(l > ch); // false

/*整数、浮点数、字元都不能与字串比较*/
System.out.println(i > s);
System.out.println(d > s);
System.out.println(ch == l);

/*布林类型不能与任何类型比较*/
System.out.println(i > b);
System.out.println(l > b);
System.out.println(f > b);
System.out.println(d > b);
System.out.println(ch > b);
System.out.println(s > b);

case顺下问题

若在switch语句的case中没有填上break语句,则执行权会自动向下执行另一个case直到遇上break

例如下面的switch案例,把所有的break注解掉後,会把当前case以下的所有表达式全部执行

int a = 100;

switch(a/10){
	case 10:
	case 9:
		System.out.println("A");
		// break;
	case 8:
	case 7:
		System.out.println("B");
		// break;
	case 6:
		System.out.println("C");
		// break;
		
	default:
		System.out.println("不及格");
		// break;
}

输出结果:

A
B
C
不及格

程序发生多个不同状况同时执行输出时,不妨查看stwich语句的case是否正确填入break语句

switch的输出类型

JDK7.0以後表达式的值可以是基本数据类型byte, short, int, char,以及String类型,但switch判断式内的数据类型必须要跟case後的变数类型一致,否则程序执行时会抛出异常

switch後表达式的变数类型不可为long, float, double, boolean。还记得浮点数的精准度问题吗,所以它不适合放在switch语句中做判断。而布林就更好理解了,能用if解决的问题,不需要动用switch结构

至於long这个比较特别,看後续java更新会不会加入long类型,目前还是不支援的


Scanner s = new Scanner(System.in);
System.out.print("输入字串str: ");

String str = s.next();

switch(str){
	case "apple":
		System.out.println("苹果");
		break;
	case "banana":
		System.out.println("香蕉");
		break;
	case "watermelon":
		System.out.println("西瓜");
		break;
	case "papaya":
		System.out.println("木瓜");
		break;
	default:
		System.out.println("没有这种水果");
		break;
}

for的表达式

我们先来看看for语句执行的顺序为何,以下面的程序码为例

首先int n=1为初始化语句,目的是对局部变数n(或已宣告的其他变数)进行赋值,该语句只会执行一次

接下来是判断变数是否符合条件n<5,若成立则进入回圈

完成回圈内的语句後会对局部变数n进行自增处理n++,完成後再判断是否符合n<5,为真则再次进入回圈内,否则退出for回圈。後续的回圈处理都是循环这个部分

for(int n=1; n<5; n++){
	// do something
}

其实我们可以将for回圈的表达逻辑以while的方式呈现,相信这样会更好理解。一样看看下面这段伪代码,两个逻辑判断式的功能一模一样

for(int i=100; (i%5 == 0)&(i > 8); i=i-8){
	System.out.println("i = " + i);	
}
int i = 100;
while((i%5 == 0)&(i > 8)){
	System.out.println("i = " + i);	
	i -= 8;
}

输出结果均为:

i = 100

省略表达式

for回圈的三个判断式均能够被省略,其中第二个判断式被省略後,预设为true

举例来说若省略第一个表达式,则相当於把初始化功能去除,若希望程序能够正常执行,需要另外对变数进行初始化

int i = 0;
for(; i<5; i++){
	System.out.println("hi");
}

省略第二个判断式,相当於把离开for loop的条件省略,使得第一个分号後恒为true,这会让程序无法跳出回圈

若不希望程序变成无穷回圈,可以在回圈内编写执行跳出的条件语句

int a = 0;
for(int i=0;;i++){
	if(i>10)
		break;
	a++;
}

System.out.println("a = " + a);

省略第三个表达式相当於去除回圈後续会进行的变数运算,这个运算式将运算结果交给第二个判断式仲裁是否再次进入回圈

省略该项很有可能也造成无穷回圈,因为判断变数从初始化以後就没有变过,所以必须在回圈本体中加入运算式才能正确执行

for(int i=0; i<50;){
	if(i%2 == 0){
		System.out.println("even!");
		i += 7;
	}
}

省略三个表达式,其表达为一个无穷回圈,相当於while(1)回圈

for(;;){}

while(1){}

for中填入多个判断式

除了第二个判断式以外,第一以及第三个判断式可以进行扩充

例如以下程序,同时对i和j初始化,并对这两个变数做运算处理,不过退出回圈条件只会有j < q一个,不能进行扩充

int i, j;
int p = 0, q = 8;
for(i = 0, j = 0; j < q; --i, j++){
	System.out.println("i = " + i + ", j = " + j);
}

输出结果:

i = 0, j = 0
i = -1, j = 1
i = -2, j = 2
i = -3, j = 3
i = -4, j = 4
i = -5, j = 5
i = -6, j = 6
i = -7, j = 7

有兴趣的朋友可以利用for loop的扩充功能编写一些程序码看看,例如下面这段程序码就是指使用一个for loop循环打印一个九九乘法表

但是记得实际编写程序时不要这麽做嘿?

for(int i=1, j=1; j<10; i=(i==9 ? (++j/j) : i+1)){
	System.out.print(i + " * " + j + " = " + i*j + (i==9 ? '\n' : ", "));
}

输出结果:

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

while(i == i+1)的难题

请思考这样一个问题,要将i赋值成多少才能使下面的while回圈变成无穷回圈

while(i == i + 1){}

这个问题势必牵涉到在java中,甚麽变数经过加1运算後还会跟自己相等,这个问题官方文件给出了很明确的答案

浮点数的(正负)无限值或者是非常大的一个数值,本身经过与有限数值或与自身正负号相等的无限数值加减运算後,其结果依然与自身相等

因此我们将浮点数设成正负无限两种,发现都可以将while()判断式置为无穷回圈

float i = Float.POSITIVE_INFINITY; // 正无限
// float i = Float.NEGATIVE_INFINITY; // 负无限
// double i = Double.POSITIVE_INFINITY; // double类型也可以

while(i == i + 1){}

另外因为浮点数的精准度有一个最小极限,也就是说当浮点变数被赋值一个非常大的数值时,进行加减运算其实是不会影响原有数值的

例如将float的最小值加1其实还是等於自己本身

float i = -Float.MAX_VALUE;

while(i == i + 1){}

或者手动赋值一个非常大的正负常数,也可以得到相同效果

float i = 9999999999999f;

while(i == i + 1){}

关於浮点数最小值问题可以参考之前的文章


<<:  第二只狗勾-秋田

>>:  React-使用useRef跨组件操作DOM

Day7:如何使用Parrot Security的hping3扫描网路

今天我们来讲一下如何使用Parrot Security的hping3来扫描网路 首先登入Parrot...

Day28 [实作] 一对一视讯通话(8): Docker compose 整合 TURN Server

前面的实作中,我们都是使用 google 提供的 STUN server,在 後疫情时代的 WebR...

[Day 8]从零开始学习 JS 的连续-30 Days---阵列

宣告变数的资料型别--阵列 1.数值( Number ) 2.字串( String ) 3.布林值(...

[Day10] 文本/词表示方式(一)-前言

一. 前言 在如今社群网路蓬勃的时代,从网路充斥着许多文字资料,要如何有效的分析文字让电脑可以知道我...

Day 6 - Loop

回圈提供一个快速又简洁的方法来重复地做某件事,有了回圈,在取得资料时就方便许多。 for loop ...