架构图
前言
表达式是程式进行算术运算中的表示方式,我们可以简单地把表达式拆解为表达式 = 运算子 + 操作变数 ,也就是说任何运算子都不能脱离变数单独存在
在java中依据使用功能可以区分成多种不同的运算子,该小节主要聚焦介绍几种常见的运算子,并探讨表达式中若存在多个不同运算子时该如何判断优先顺序
重点运算子
- 算术运算子(Arithmetic Operators)
- 赋值运算子(Assignment Operators)
- 关係运算子(Relational Operators)
- *三目运算子(Ternary Operators)
- 逻辑运算子(Logical Operators)
算数运算子
实现最基本的算术运算。与算术相关的运算子有+, -, *, /, %(加、减、乘、除、取馀),整数类型与浮点类型多使用算术运算子进行数学运算,字串与字元则可以使用+进行拼接
案例
int num1 = 10;
int num2 = 20
int num3 = 0;
num3 = 12 + num2;
num3 = num1 * 2;
num3 = num1 / 2;
num3 = num2 % 2;
使用括号 在进行运算时请铭记先乘除后加减法则,如果想要更改运算顺序可以透过括号来实现优先运算(优先级部分将会提及)
int num1 = 5;
int num2 = 2;
int num3 = 0;
num3 = num1 + num2 * 2;
num3 = (num1 + num2) * 2;
除法与取馀结果 注意除法运算后的变数类型,例如以下例子,当运算结果为整数类型时,小数点会被无条件捨去,所以在进行相关运算时请特别注意。可以透过转型使输出结果转为浮点类型
int num = 17;
int op1 = 3;
float op2 = 3.0f;
System.out.println("test 1 = " +num/op1);
System.out.println("test 2 = " +num/op2);
取馀数运算相信大家一定不陌生,当除数具有小数点时,取馀结果也会拥有小数点,当然浮点数运算多少会存在一些误差值,例如下面例子
float num1 = 17.0f;
float num2 = 17.9f;
float op1 = 3.0f;
System.out.println("test 1 = " +num1%op1);
System.out.println("test 2 = " +num2%op1);
可以在运算子左右两侧留下空白增加易读性
单目运算子
与上述我们介绍的表达式不同,加减乘除运算均需要两个或以上的变数参与,而单目运算子仅需要一个变数与一个运算子就可以进行运算
常见的单目算术运算子包括变数正负号表示、递增与递减运算:
int num1 = -10;
int num2 = +15;
System.out.println(num1++);
System.out.println(--num2);
常见的递增运算可以分为前置与后置两种,在操作上稍微有些不同 前置递增、递减 先进行递增与递减运算,然后回传变数值:
int num = 10;
int result = 0;
result = ++num;
System.out.println("result = " + result);
System.out.println("num = " + num);
运算结果:
result = 11
num = 11
后置递增、递减 先回传变数值,在进行递增或递减运算:
int num = 10;
int result = 0;
result = num--;
System.out.println("result = " + result);
System.out.println("num = " + num);
运算结果:
result = 10
num = 9
我们再来举几个小例子来练练手:
int a = 4;
a = (++a) + 4;
System.out.println("a = "+a);
a = (a++) + 4;
System.out.println("a = "+a);
a += (++a);
System.out.println("a = "+a);
a -= (a--);
System.out.println("a = "+a);
运算结果:
a = 9
a = 13
a = 27
a = 0
实际编写程式时应当尽量避免以下这种风格写法,也就是把大量赋值运算、算术运算、递增递减运算混合在一起。除了降低可读性外还可以造成赋值错误
int num = 10;
int i = 5;
num += i++;
num += ++i;
num = (num++) * (num++);
num = (++num) + (num++);
指定运算子
最常见的指定运算子莫过于=,它将等号右侧的数值赋值给等号左侧的变数
int num1;
char num2;
String num3;
num1 = 15;
num2 = 'a';
num3 = "string...";
特别注意赋值方向又由右向左,等号只有一个,且左值必须为一个变数,下面的例子是一些错误的写法:
100.0d = d;
num1 == 0;
num1 + num2 = 5;
複合指定运算子
很多时候赋值运算子会结合算术运算子使用,为了节省语句长度,我们可以将表达式简写成特殊的表达形式,例如:
- +=
- -=
- *=
- /=
- %=
这种表达式称作赋和指定运算子( Compound Statement),除了算术运算子之外,还可以结合位元运算子(之后的章节会提到)
int num1 = 10;
float num2 = 122.6.f;
double num3 = 1.0;
num3 += num1;
num3 = 1.0;
num3 -= num1;
num3 = 1.0;
num3 *= num2;
num3 = 1.0;
num3 /= num2;
num3 = 1.0;
num3 %= num1;
num3 = 1.0;
双目运算子的赋值方向
在双目运算子的操作运算中,操作顺序始终是由左至右的,也就是说等号右侧的一连串运算一定是由左侧发起
int num1 = 3;
int num2 = (num1=4) * num1;
System.out.println(num2);
输出结果:
num2 = 16
同理複合指定运算子也适用这个规则
int num = 9;
num += (num=10);
System.out.println("num = " + num);
num = (num=3) + num;
System.out.println("num = " + num);
num -= (num=1);
System.out.println("num = " + num);
输出结果:
num = 19
num = 6
num = 5
因此每次进行表达式运算时,都从等号右侧开始运算,运算规则始终是左至右,了解这个规则后,我们把将多个複合指定运算子结合起来看看输出效果如何
int num = 10;
num += num -= num *= num /= num;
num = num + (num - (num * (num / num)));
输出结果:
num = 10
关係运算子
关係运算子多用在条件判断与循环语句中,例如if, while等,为了要使判断语句执行,必须判断执行或不执行,因此可以得知关係运算子的输出结果不是true就是false
int num = 10;
if(num < 100)
System.out.println(num < 100);
else
System.out.println(num < 100);
输出结果:
true
java提供多组关係运算子给条件语句进行判断
- >
- <
- >=
- <=
- ==
- !=
另外关係运算子的判断不限于整数型态,例如下面例子就是整数与浮点数关係运算子范例,只要两个变数值相同就会返回true
int num1 = 10;
float num2 = 10.0f;
if(num1 == num2){
System.out.println(num1 == num2);
}
else{
System.out.println(num1 == num2);
}
输出结果:
true
除此之外字元也是一个常见的判断变数,字元形式主要利用ASCII码进判断
char ch = 'a';
if(ch >= 100)
System.out.println(ch > 100);
else
System.out.println(ch > 100);
输出结果:
false
另外布林、字串字面值也可以进行判断,不过仅限于==与!=
System.out.println(10 <= 100);
System.out.println(true == false);
System.out.println("123" != "124");
输出结果:
true
false
true
三目运算子
所谓三目运算子,是指条件式与计算返回值共有三个不同的区块。java提供的三目运算子解析如下: 条件判断 ? 成立返回值 : 失败返回值 举几个例子比较好懂:
int num1=6, num2=9;
boolean b = num1 > num2 ? (13 > 6) : (true == false);
System.out.println(b);
输出结果:
false
首先判断条件语句num1 > num2 是否成立,若成立则返回第一个区块的值,也就是(13 > 6) ,若不成立则返回第二个区块的值(true == false)
假如我们把三目运算子写成if-else判断式的话,就可以看出三目运算子的优势在于简洁性了。不过若是牵涉到多条判断语句(if-else if-else)还是要乖乖地使用判断式
int num1=6, num2=9;
boolean b=false;
if(num1 > num2)
b = (13 > 6);
else
b = (true == false);
System.out.println(b);
接下来我们把三目运算子的返回值双双填入另一个三目运算子,让判断式多加一层
int num1 = 18, num2 = 44, num3 = 90;
int max;
max = ((num1 > num2) ? (num1 > num3) ? num1 : num3 : (num2 > num3) ? num2 : num3);
System.out.println("最大值是: " + max);
输出结果:
最大值是: 90
首先判断num1 > num2 是否成立,若成立则只需要再判断num1 > num3 就可以;相反的若是不成立,则需要考虑num2 > num3 是否成立
逻辑运算子
前一小节中我们有提到关係运算子与判断语句的关係,如果关係运算成立就执行该条语句,但假如需要进行判断的关係运算语句超过一条,就需要使用逻辑运算子
java主要提供三种逻辑运算子,分别是AND, OR, NOT运算:
- &
- |
- !
int num1 = 10;
int num2 = 101;
if(num1 > 5 & num2 > 100)
System.out.println(num1 >5 & num2 > 100);
if(num1 > 10 | num2 > 100)
System.out.println(num1 > 10 | num2 > 100);
if(!(num1 > 10) && !(num2 < 100))
System.out.println(!(num1 > 10) && !(num2 < 100));
输出结果:
true
true
true
使用短路运算子提高效率
短路运算子的功能与普通的逻辑运算子相同,它的表示方式为&& , || ,都是AND, OR操作,差别在于它不用执行所有的判断语句。
举例来说,判断式A在执行判断时,遇到第一个关係判断式为false后就不会再执行(7 > 3) 的判断了,因为不管无论false如何进行AND运算,结果始终为false
判断式B也是相同的道理,当(4 < 5) 为true后,不管接下来为false或为true返回值始终为true,所以不需要做额外的判断
int num = 0;
if((4 > 5) && (7 > 3)){
num++;
}
if((4 < 5) || (5 < 6)){
num++;
}
为了验证这个运算结果,我们使用普通的逻辑运算子与短路运算子进行比对,发现使用短路运算子确实省略掉判断式
int i = 5;
if((2*i > 5) | (++i > 2))
System.out.println("i = " + i);
i = 5;
if((2*i > 5) || (++i > 2))
System.out.println("i = " + i);
输出结果:
i = 6
i = 5
运算子优先级
假如有多条运算子存在一条表达式中,编译器必须要有一个规则可以区分出谁先做谁后做,例如我们举一个简单的运算表达式为例子,很明显的奉行先成除后加减的规定
double x=1,y=2,z=3;
double d = 2 * x * y + z - 1 / x + z % 2;
System.out.println("d = " + d);
输出结果:
d = 7.0
但除了普通的算术运算子之外,java还规定了我们上述提及的运算子优先顺序,例如常见的指定运算子与逻辑运算子等,下表介绍常见运算子的执行优先顺序
优先级 | 结合顺序 | 类型 |
---|
++, – | 右到左 | 算术运算子 | +(正号), -(负号), ! | 右到左 | 算术运算子 | /, *, % | 左到右 | 算术运算子 | +, - | 左到右 | 算术运算子 | >, <, >=, <= | 左到右 | 关係运算子 | ==, != | 左到右 | 关係运算子 | & | 左到右 | 逻辑运算子 | | | 左到右 | 逻辑运算子 | && | 左到右 | 逻辑运算子 | || | 左到右 | 逻辑运算子 | ?: | 右到左 | 三目运算子(条件运算子) | =, +=, -=, *=, /=, %= | 右到左 | 複合指定运算子 |
结合顺序 所谓结合顺序就是运算子会优先向左或右与操作变数结合的特性。例如负号具有由左到右的结合特性a = 4 + - 5 ,会先向右侧寻找可结合的操作数(常数5)。同理a++ 也是先向右侧寻找操作数,没有的话向左侧寻找(因为单目运算子的特性)
养成使用小括号习惯 虽然我们知道x + y * 2 - 1 的执行顺序,但若加上小括号x + (y * 2) - 1 可以让其他开发者更快读懂程式码
运算运算与优先级问题
优先级让人头痛的地方就是该如何在一连串运算式抓出谁该先算,谁又该慢点。尤其是递增递减运算子的前后置最容易搞混
其实根据官方文件递增递减运算子的优先级,后置(post-fix)是高于前置(pre-fix)。下面我们举个例子:
int num = 5;
System.out.println(num++ * ++num * num++);
输出结果
245
其实很多人会将优先级高的运算子解释成先行运算,也就是说他们认为运算顺序应该是: num++ -> num++ -> ++num 其实先行运算这个观念没有错,不过前提是该操作变数的前后范围内,而不会直接就找最高优先级的操作数进行运算。这间接带出一个观念,在选择优先级之前首先要确认整个运算式由左至右的,可以从官方文件中查到这个规则。
说白了就是你要先从左到右进行运算操作,遇到前后均有运算子时才可以进行优先级判断,而不是直接跳去执行优先级最高的运算式,你可以想像优先级主要是为了处理下面这种场景
int num1 = 5;
int num2 = 6;
int num3 = 1;
int result = 0;
result = num1 * ++num2 * num3++;
result = ((num1 * (++num2)) * (num3++));
跳回刚刚的例子,若原本的假设成立,编译器真的会一开始就直接执行优先级高的运算子,而忽略由左到右的这条规则的话,下面这个例子的输出应该会相等
int num = 5;
System.out.println("test1 = " + (num++ * ++num));
num = 5;
System.out.println("test2 = " + (++num * num++));
输出结果:
test1 = 35
test2 = 36
看到了吧,输出结果证明原本的假设不成立,但假如我们考虑到由左到右运算的这条规则时,一切就说得通了
test1的运算顺序:
num++ 得到返回值5++num 得到返回值7- 两个操作变数相乘得到35
test2的运算顺序
++num 得到返回值6num++ 得到返回值6- 两个操作变数相乘得到36
最后回到原先的例子: num++ * ++num * num++
num++ 得到返回值5++num 得到返回值7- 两个操作变数相乘得到35
num++ 得到返回值7- 两个操作变数相乘得到245
|