运算符与表达式
运算符简介
运算符是一种特殊的函数,它们具有一个或多个操作数并返回相应的值。操作数是被运算符用作输入的值,通常是字面值、变量或表达式。
运算符可以是一元、二元或三元的,一元运算符有1个操作数,二元运算符有2个操作数,三元运算符有3个操作数。
算术运算符: +(加、正值) -(减、负值) *(乘) /(除) %(取余) ++(自增) --(自减)
除法运算,两个操作数是整型的,结果也会是整型的,舍弃掉小数部分;如果有一个数是浮点数,结果将自动转型为浮点型
取余运算,两个操作数是整型的,结果也会是整型的,如果有一个数是浮点数,结果将自动转型为浮点型。内部会转换为: a和b均是整数量,a%b=a-(a/b) *b,否则 a%b=a-(b*q),这里的q=(int) (a/b)。
自增自减运算,a++ 相当于 a = a + 1, a++ 是先运用在计算,++a 先计算在运用
关系运算符: ==(等于) !=(不等于) <(小于) <=(小于等于) >(大于) >=(大于等于)
" = “是赋值运算,” == "是等于运算
" > “、” < “、” >= “、” <= “只支持数值类型的比较,” == “、” != "支持所有数据类型的比较
关系表达式的运算结果是布尔值,两边的操作数必须是相同类型或相容类型的。
逻辑运算符: &(逻辑与) |(逻辑或) !(逻辑非) ^(逻辑异或) &&(短路与) ||(短路非)
操作数只能是布尔型,操作结果也是布尔型
& 和 && 的区别: && 左边是false时,不计算右边的表达式,左假即假; & 无论左边真假都会计算右边的表达式
位运算符: &(按位与) |(按位或) ~(按位取反) ^(按位异或) <<(左移) >>(带符号右移) >>>(无符号右移)
三目运算符: ? : (表达式1 ? 表达式2 : 表达式3)
其中表达式1的结果必须是布尔类型,否则报错。表达式2和表达式3必须是同类型。
例如
true ? false : true ? 5 : 6;
就直接报错,因为前面是boolean,需要 true ? 5 : 6
返回一个boolean,但实际返回的是int。 该运算符是短路运算符。
例如
f(3) ? f(4): (f(5)? f(6) : f(7));
如果f(n)返回true,则会依次调用 f(3) f(4)。如果f(n)返回false,则会依次调用 f(3) f(5) f(7)。赋值运算符: =、+=、-=、*=、/=、%=、&=、|=、<<=、>>=、>>>=
左侧必须是一个变量,右侧是一个表达式。
例如 a = b = 3 + 2; a = (...),要先计算 =号后面的子表达式,计算完成后再赋值。子表达式是 b = 3 + 2。所以b的值是5。 然后a的值也是5。
运算符的优先级
括号级别最高,逗号级别最低,单目 > 算术 > 位移 > 关系 > 逻辑 > 三目 > 赋值。
指针最优,单目运算优于双目运算。如正负号。先乘除,后加减。先算术运算,后移位运算,最后位运算。逻辑运算最后计算。
方括号点圆括号,正负,非加加减减,乘除,加减,移位,大小包含,等不等,与,异或,或,短路与,短路或,条件,算术赋值,位赋值。
如果要改变运算顺序,可以使用圆括号。
Java中所有的运算符
优先级
|
描述
|
运算符
|
结合性
|
说明
|
1
|
括号、方法调用、成员
|
[] . ( ) (方法调用)
|
|
|
2
|
算术运算符: 自增自减,正负号
逻辑运算符: 非,位运算符: 按位取反
|
+、-、++、--
!、~
|
从右向左
|
~: 按位取反,1变0,0变1
!: false变true,true变false
|
3
|
算术运算符: 乘除,取余
|
*、/、%
|
|
|
4
|
算术运算符: 加减
|
+、-
|
|
|
5
|
移位运算
|
<<、>>、>>>
|
|
>>>:无符号右移,左边空位补0
>>: 带符号右移,左边空位补符号位
<<: 左移,右边补0
|
6
|
关系运算符: 大小关系
|
>、>=、<、<=、instanceof
|
|
变量名 instanceof 类名或接口名,如果是其子(孙)类,则返回true
|
7
|
关系运算符: 相等关系
|
==、!=
|
|
|
8
|
位运算符: 按位与
逻辑运算符: 逻辑与
|
&
|
|
按位与: 两个都是1,结果为1,否则为0
逻辑与: 左边无论取值真假,右边都会计算
|
9
|
位运算符: 按位异或
逻辑运算符: 逻辑异或
|
^
|
|
按位异或: 两个数相同为0,不同为1
逻辑异或: 相同为false,否则为true
|
10
|
位运算符: 按位或
逻辑运算符: 逻辑或
|
|
|
|
按位或: 两个都是0,结果为0,否则为1
逻辑或: 左边无论取值真假,右边都会计算
|
11
|
逻辑运算符: 短路与
|
&&
|
|
两者是true,结果才是true。左边是false时,右边不会计算。
|
12
|
逻辑运算符: 短路或
|
||
|
|
两者是false,结果才是false。左边是true时,右边不会计算。
|
13
|
条件运算
|
?:
|
从右向左
|
表达式1 ? 表达式2 : 表达式3
如果表达式1为true,则返回表达式2的值,否则返回表达式3的值
|
14
|
算术赋值运算
位赋值运算
|
=、+=、-=、*=、/=、%=
&=、|=、<<=、>>=、>>>=
|
从右向左
|
|
运算符的结合性
所有的数学运算符都认为是从左到右运算的,Java 语言中大部分运算符也是从左到右结合的,只有单目运算符、赋值运算符和三目运算符例外,其中,单目运算符、赋值运算符和三目运算符是从右向左结合的,也就是从右向左运算。乘法和加法是两个可结合的运算,也就是说,这两个运算符左右两边的操作数可以互换位置而不会影响结果。
当有多种运算符参与运算的时候,先要考虑优先级,有相同优先级的就看结合性以决定运算顺序。
因为这样,所以,如果没有两个相同优先级的运算,就不存在考虑结合性的问题了。
a?b:c?d:e 相当于 a?b:(c?d:e)
结合性
结合性:已知一个操作数,其左侧和右侧的运算符优先级相同,那么这个操作数应该先跟左侧还是右侧的运算符结合呢?
例如: c = ++a++;因为++运算符是右结合性的,所以相当于++(a++),a++不是一个左值,无法被修改,因此会编译错误
对于条件运算符 a ? b : c,看其结合性时,可以先简化成 a ? d
例如:
a?b:c?d:e
简化后 a ? c ? k ,a 只有右侧有运算符,所以 a ? 是一起的。 c前后均有?,但?是右结合的,所以 c 与右侧?号结合。 所以,等价于 (a ? b : (c ? d : e))
c = a+++++b; // 这样写会编译错误,首先,++比+更被优先识别,所以相当于a++ ++ +b,即((a++)++)+b,而a++不能作为左值(左值才可以)
c = a++++b; // 这样就会编译错误,因为这相当于((a++)++)b,这没有意义
c = ++a++; // 编译报错。
看结合性时,从表达式最左侧开始,一个操作数一个操作数地往右看。
-i++ 相当于 -(i++) 而不是 (-i)++
a ++ + b 相当于 (a++) + b 而不是 a + (++b) 因为java是从左到右扫描的,每次会尽可能多(长)的识别运算符(即用的是贪婪匹配)。
对于 赋值和条件运算符,由于其优先级低,所以其右侧可以看成是一个子表达式(尽管右侧可能有与其同级的运算符)
如 a ? b : c ? d : e; 相当于 a ? 子表达式 : 子表达式, 所以要先计算子表达式。可以从这个角度理解其右结合性。
而对于 a ? b?c:d : e; 这个表达式是完全没有岐义的。
所以,赋值和条件运算符可以不看其结合性,只需要将其右侧看成一个整体,然后从左到右依次计算即可。
对于 正负号 非 按位取反 也类似: -(子表达式) 或 +(子表达式) 或 !(子表达式) 或 ~(子表达式)
由于 ++ 和 -- 单独组合时,计算不具有连续性,所以可以不考虑其结合性。 例如 ++++a 或 a ++++ 或 ++a++ 均是报错。
注意,以上说的子表达式是指这个运算符后面的那些优先级>=这个运算符的那部分所组成的表达式。
综上: 右结合性的本质是 ?: 赋值 正号 负号 ! ~, 这几个运算符的右侧应该看成一个整体(子表达式),从而先计算右侧的值,对外表现出来就是右结合性。
所以,只需要将其右侧看成一个整体,然后从左到右依次计算即可。
表达式的计算过程
计算过程大致分为以下两步:
1. 替换剪枝:从右到右依次替换其中的变量、函数调用、赋值、单目运算符、条件运算符等,最终将整个表达式转换为一个操作数全是字面常量的且仅包含二元运算符的表达式。
在这个过程中,遇到函数调用,其中可能有子表达式,就先将这个子表达式计算完成转换为一个字面常量。
这一步中,纯粹就是从左到右依次扫描进行替换或计算子表达式。
整个表达式中的所有副作用均在这一步中发生。
在从左到右的扫描过程中,如果遇到 ?: && || 这样的短路运算符,则直接根据条件进行剪支:被丢弃掉的分支直接忽略,不再进行替换和计算。
2. 计算:从左到右计算表达式的值。(这一步中不可能有任何副作用,因为所有可能存在副作用的已经在1中处理完成了)
示例1: 一个简单表达式的计算
3 + a * b * c + 4 + f(4) 其中 a=2,b=3,c=4, f(4) 返回2
计算过程:
1)替换a后的结果: 3 + 2*b*c + 4 + f(4)
2)替换b后的结果: 3 + 2*3*c + 4 + f(4)
3)替换c后的结果: 3 + 2*3*4 + 4 + f(4)
4)替换f()后的结果: 3 + 2*3*4 + 4 + 2
5) 计算 3 + 2,看到后面一个运算符是*,优先级更高,所以先算后面的一个得到: 3+6*4 + 4+ 2
6) 计算 3 + 6,看到后面一个运算符是*,优先级更高,所以先算后面的一个得到: 3 + 24 + 4+ 2
7) 计算 3 + 24,看到后面一个运算符是+,优先级相同,所以直接计算得到: 27 + 4 + 2
8) 计算 27 + 4,看到后面一个运算符是+,优先级相同,所以直接计算得到: 31 + 2
9) 计算 31 + 2,看到后面一个运算符是+,优先级相同,所以直接计算得到: 33
请计算:
1. a ++ + 3 * (a ++) + a++; 其中 a = 2,表达式的值为15,a的值成了5。
2. true ? a = 4 : 5; 其中 a = 3,结果是4,a的值也成了4。注意,赋值运算符的优先级低于条件运算符!
3. (a += 2) + a; 其中 a = 4, 结果是12,a的值也成了6
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?