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

本文探讨了Java编程中的逻辑结构问题,包括悬吊、大括号的重要性、null statement、整数与布尔混淆、浮点数判断、数据类型判断原则等。强调了大括号的使用对于代码清晰性和防止错误的重要性,以及避免在逻辑判断中使用浮点数。文章通过实例解释了各种陷阱,帮助开发者提高代码质量。
摘要由CSDN通过智能技术生成

架构图

悬挂

逻辑判断语句若是只有连接一条语句时,这一条语句会与离它最近的逻辑判断语句相连,例如以下例子的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){}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值