在我们C语言深度解剖的专栏已经讲解了一些常见的运算符了,但是基本的 + - * / 没有细说,毕竟那是深度解剖,是在一定基础上更深入学习的,我们 Java 是从 0 基础讲解的,所以这里会涉及到很全面的操作符,由于 Java 很少关注底层,所以我们也不会像讲 C语言的时候那样偶尔去从汇编的角度讲解。
1、算数运算符
1.1 四则运算符
相信大家对于四则运算都不陌生啊, 毕竟谁没做过加减乘除运算,但是这里我们主要是介绍 % 运算符,它与 C语言里的有点区别我们接着往下看:
显然答案是跟我们C语言当中是一模一样的,但是这里我们需要强调一点,以上都是二元操作符,必须拥有两个操作数,不论在C还是在Java中 int / int 的结果是整数,而且是向下取整, 也就是说,会舍去小数位,尽管你是 3.9 他算出的结果仍然是 3。
那么我们如果想得到小数该如何做呢?
- 将除号两边任意一个整数乘以1.0(当两边操作数类型不同,会向类型大的提升,结果也跟着变)
- 将除号两边任意一个整数强转成 float 或者 double
- 直接用 double 或者 float 类型计算
注意:将他们的计算结果转化成 float 或 double 是没有用的,因为结果是取整之后的值。
接下来我们来关心我们的 % 运算:
如果是第一次看到这些结果的你们可能有些意外,我记得C语言中是不能对浮点数取模的啊,在Java中是允许这样操作的,其实上面的例子很简单,就是10以内的除法,如果你能对上面都算准确的话,那么你取模这个运算符肯定是没问题的。
注意:取模和除法时,右操作数不能为0,否则会报错! (0乘任何数都为0)
在讲C语言的时候,我们就说过,自古以来有个约定,返回值为0为正常退出,非0则为异常退出。所以在除法或者取模时,右操作数是万万不可出现 0 的!
1.2 复合运算符
这种运算符呢,常见的有:+= -= *= /= %= >>= 等等...
他们会将操纵的结果最后赋值给左操作数。
public class TestDemo { public static void main(String[] args) { int a = 1; a += 1; // 相当于 a = a + 1; System.out.println(a); //2 a -= 1; // 相当于 a = a - 1; System.out.println(a); //1 //...... *= /= %= 就不一一演示了 } }
但是其实这里我们要有几个注意的点:
如果当我们不同类型使用符合运算符时会自动发生类型转换,不需要我们手动强制类型转换。
public class TestDemo { public static void main(String[] args) { //如果不同类型使用符合运算符会自动类型转换 int a = 10; double b = 10.5; a += b; //等价于 -> a = (int)(a + b); } }
如果当我们使用时发现运算符优先级有歧义怎么回事?比如 a += b * 3;
public class TestDemo { public static void main(String[] args) { int a = 10; a *= 3 + 2; //等价于 -> a = a*(3+2) 这里先把 3+2 的值算出来在乘 a System.out.println(a); //50 } }
只有变量才能使用符合运算符做左值,常量是不可以的,因为他不可被修改!
1.3 自增自减运算符
相信大家对于自增自减运算符并不少见,但是在Java中确实还是有跟C语言不同的地方的,我们接着往后看:
public class TestDemo { public static void main(String[] args) { int a = 10; int b = 0; b = ++a; // a = a + 1; b = a; System.out.println(a); //11 b = a++; // b = a; a = a + 1; System.out.println(a); //12 } }
至于单独使用 ++a,或者a++,他们的效果是一样的,这个我就不多说,但是我们这里主要是区别前置++和后置++他们赋值的情况。
- 前置++:先自身值增加1,然后赋值
- 后置++:先赋值,然后自身值增加1。
但是这里又有一个小问题,如果有人闲的无聊,把a++的值再次赋值给a,会发生什么情况呢?
这里就跟C语言不一样了,C语言中如上代码算出的结果是 11,如果看过我C语言深度解剖专栏符号篇的,在那我们已经从汇编的角度去演示过他们的底层逻辑了,只不过在 Java 中我们很少去关注底层,这里我们注意一下就行,其实以后也基本不会碰到这种后置自增之后给自己赋值的代码,也不用太过于纠结,我们有个认识就行。
注意:常量不能使用前置或后置++运算符
2、关系运算符
关系运算符这一块跟C语言基本一致,主要以下六个:== != >= <= > <
但是在Java中我们要区分跟C语言不同的点,前面我们强调过,Java中不存在 0为假 非0为真,说白了,以上六个关系运算符,其计算的结果为 ture 或者 false 并不是 0 或者 1。
public class TestDemo { public static void main(String[] args) { int a = 10; int b = 20; System.out.println(a > b); //如果a大于b,输出true 否则 输出false System.out.println(a < b); //如果a小于b,输出true 否则 输出false System.out.println(a == b); //如果a等于b,输出true 否则 输出false System.out.println(a != b); //如果a不等于b,输出true 否则 输出false System.out.println(10 >= 15); //如果10大于或等于15,输出true 否则 输出false System.out.println(10 <= 15); //如果10小于或等于15,输出true 否则 输出false } }
我们学编程语言,有些地方显然是不能跟数学一模一样的,就比如说:10 < a < 15,这样写是错误的,因为 10 < a 的结果是 ture 或者 false 其中任何一个结果都不能跟 15 去比较的。
注意:关系运算符两边可以出现常量
3、逻辑运算符
3.1 逻辑 && 和 逻辑 | |
逻辑运算符显然也是很不陌生的,但是在Java中逻辑运算符 && || 左右表达式的结果必须是 boolean 类型,而且整个表达式的运算结果也是 boolean。
例:表达式1&&表达式2,要求:表达式1和表达式2的运算结果是 boolean 类型
1 && 5 -> 这个是错误的!!!
- &&:当运算符两边表达式的结果都为 true,则整体为 true
- | | :当运算符两边表达式有一个结果为 true,则真题为 true
短路现象:
- &&:当运算符左边表达式为假,则不执行右边表达式,称为短路现象
- | | :当运算符左边表达式为真,则不执行右边表达式,称为短路现象
- 如果 & 和 | 按位运算符两边表达式的值也为 true 或者 flase他们也表示逻辑运算,但不支持短路!
public class TestDemo { public static void main(String[] args) { int a = 10; int b = 20; System.out.println((a > 10) && (a / 0 > 2)); //这里 除数为0并不会报错,因为&&出现了短路 System.out.println((a < 20) || (a / 0 > 2)); //这里 除数为0并不会报错,因为||出现了短路 } }
以上代码就是逻辑与和逻辑或出现的短路现象,我们前面学习到,除数一定不能是0,否则程序会异常,但是我们上面代码可以通过,最主要原因是他们发生短路并不会去执行运算符右边的表达式,感兴趣的可以下来试一试,如果没出现短路,程序是肯定会出现异常的。
3.2 逻辑非!
int a = 10; System.out.println(!(a == 10)); //取非就将 true 变为 false //err:System.out.println(!a); 操作数结果必须为布尔值
这个就很简单了,主要注意一点,!的右边表达式结果必须为布尔值。
4、位运算符
我们知道计算机中最小的存储单位是字节,但是数据最小的操作单位是比特位,一个字节等于八个比特位,也就是八位二进制,位运算符是针对数据二进制位按位操作的。
4.1 按位与(&)和按位或( | )
由于这一块跟C语言基本一致,所以我们就大概的过一遍即可:
- 按位与(&):如果两个二进制位都是 1, 则结果为 1, 否则结果为 0。
- 按位或 (|): 如果两个二进制位都是 0, 则结果为 0, 否则结果为 1。
1 & 2:
1 的二进制补码:0000 0000 0000 0001
2 的二进制补码:0000 0000 0000 0010
------按位与结果: 0000 0000 0000 0000 -> 对应十进制:0
1 | 2 :
1 的二进制补码:0000 0000 0000 0001
2 的二进制补码:0000 0000 0000 0010
------按位或结果: 0000 0000 0000 0011 -> 对应十进制:3
4.2 按位异或(^)和按位取反(~)
- 按位异或(^):如果两个数字的二进制位相同, 则结果为 0, 相异则结果为 1 。
- 按位取反(~):将数值的二进制位取反,如果该位为 0 则转为 1, 如果该位为 1 则转为 0。
1 ^ 3 :
1 的二进制补码:0000 0000 0000 0001
3 的二进制补码:0000 0000 0000 0011
---按位异或结果: 0000 0000 . 0000 0010 -> 对应十进制:2
5 ^ 0 :
5 的二进制补码:0000 0000 ... 0000 0101
0 的二进制补码:0000 0000 ... 0000 0000
---按位异或结果: 0000 0000 ... 0000 0101 -> 对应十进制:5
结论:任何数异或0都等于它本身,相同的数异或结果为0
按位取反具体大家可以自己下来写一下,将补码每位都取反,然后判断符号位,在转换成对应的原码即可知道取反后的值。
5、移位运算符
Java中的移位运算符比C中多了一个,具体我们下面讲,但是跟C语言并没什么太大的区别这里我们简单提一下:
- 左移操作符 <<:二进制位向左移动 N 位,每次移动一次时最低位补0 -> 左移相当于原数字乘2的N次方
- 右移操作符 >>:二进制位向右移动 N 位,每次移动一次时最高位补符号位 -> 右移相当于原数字除上2的N次方
- 无符号右移操作符 >>>:二进制位向右移动 N 位,每次移动一次时最高位补0
这里我们来看一段有意思的代码:
很明显这段代码是进行两个数的平均值的,至于为什么会是这样,大家可以结合上下文,画画图或者分析一下,也可以想这样求平均值好处是什么?锻炼独立思考的能力,我就不过多说明了。
6、条件运算符
这个也是Java中唯一一个三目操作符了,它的用法跟C语言很想,但是也是有一点点小区别,毕竟Java中是true表示真,false表示假。
用法:布尔表达式1?表达式2 : 表达式3
解释:如果表达式1的结果为true则执行表达式2,如果为false则执行表达式3
public class TestDemo { public static void main(String[] args) { int a = 10; int b = 20; int c = a > b ? (a += 10) : (b += 10); } }
注意:表达式2和表达式3最好是同类型的,除非他们可以发生隐式类型转换,整个表达式的结果不能单独存在,必须要拿变量接收或者使用。
单独存在三目操作符是错误的:a > b ? a : b;