运算符涉及到一些运算(这句是废话) ,需要关注精度、溢出、运算优先级、类型自动提升、强制转型等问题,会在下面的内容中逐一讲解;当然了解计算在内存中的本质才是学好的关键。
分类:
按照操作数的数目进行分类 单目运算符 a++ 双目运算符a+b 三目运算符(a>b)?x:y 这么分类好像没有任何意义
按照功能进行分类:
算数运算符
+ - * / % ++ --
这里整数除0会抛异常,小数除0会得到Infinity
赋值运算符
= += -= *= /= %=
单独的算数计算相对比较简单,因此把算数运算符和赋值运算符一起讲解,需要搞清楚两种运算符之间的区别
关系运算符(比较)
> >= < <= != ==
关系运算符比较后会返回boolean类型true false
逻辑运算
&(逻辑与) |(逻辑或) ^(逻辑异或) !(逻辑非) &&(短路与) ||(短路或)
逻辑运算符一般用来对多个关系运算符做判断,前后连接的是boolean结果,没有计算机基础的话可能对这几个符号比较陌生,这里解释下
&:前后都是true才返回true |:前后有一个true就返回true ^:前后结果不一致返回true !:单目运算符 将boolean的值取反
&&:当第一个条件是false时,后面的就不需要看了,如果左边的是false的时候可以提高性能 &&:当第一个条件是true时,后面的就不需要看了
位bit运算
&(按位与) |(按位或) ^(按位异或) ~(按位取反) <<(按位左位移) >>(按位右位移) >>>(按位右位移无符号)
讲位运算之前,需要先科普下计算机的原码、反码、补码。原码展示的是一个数的二进制数 int类型的2 就是 00000000 00000000 00000000 00000010 至于反码和补码也都是用二进制数表示,只是和原码不同的表示方式而已,在java语言中,就是以补码的形式存在的。所以我们先要搞清楚原码和补码之间的相互转换
原码---->反码--->补码
正数:原码、反码、补码都是一样的(-0,-128这些特殊情况后续讲解)
负数:
原码--->反码 符号位不变,剩余位按位取反
反码--->补码 末位+1
原码--->补码 符号位不变,剩余位按位取反,末位+1
补码---->反码--->原码
正数:原码、反码、补码都是一样的
负数:
补码--->反码 符号位不变,剩余位按位取反
反码--->原码 末位+1
补码--->原码 符号位不变,剩余位按位取反,末位+1
System.out.println(~-4); //3 System.out.println(3&5); //1 System.out.println(-2<<1); //2147483644
概念讲解完了,来分析下上面几行代码的实际步骤
-4的按位取反:
-4的原码是 10000000 00000000 00000000 00000100
-4的反码是 11111111 11111111 11111111 11111011
-4的补码是 11111111 11111111 11111111 11111100 ---这就是-4这个数在java中的实际展示
-4的按位取反(取反以后还是补码的形式) 00000000 00000000 00000000 00000011
-4按位取反后的原码 00000000 00000000 00000000 00000011 ---所以-4按位取反以后得到的结果是3
3&5:
3的原码是 00000000 00000000 00000000 00000011
3的补码是 00000000 00000000 00000000 00000011
5的补码是 00000000 00000000 00000000 00000101
按位与1相当与true 0相当与false
按位与的结果(补码) 00000000 00000000 00000000 00000001
正数的补码还是其本身 所以3&5结果是1
-10>>>1
-8的原码是 10000000 00000000 00000000 00001000
-8的反码是 11111111 11111111 11111111 11110111
-8的补码是 11111111 11111111 11111111 11111000
无符号右移一位后(补码) 01111111 11111111 11111111 11111100 因为正数的补码是其本身,转化成原码得到十进制 2147483644
精度
整型的计算是精确的,浮点型的是不精确的,尽量不要使用浮点型去计算,这里有一个没解决的问题为什么x是0.1y是0.0999.....
int a = 5; int b = 5/2; //b=2 double x = 1.0/10; //0.1 double y = 1-9.0/10; //0.09999999999999998
溢出
整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果
整数运算在除数为0
时会报错,而浮点数运算在除数为0
时,不会报错,但会返回几个特殊值
int a =2147483647; a=a+1; //-2147483648 double d1 = 0.0 / 0; // NaN double d2 = 1.0 / 0; // Infinity double d3 = -1.0 / 0; // -Infinity
类型自动提升
类型自动提升考虑的是不同基本类型之间的计算,提升的方法,可以参考java核心卷I:
如果两个操作数其中有一个是double类型,另一个操作就会转换为double类型。
否则,如果其中一个操作数是float类型,另一个将会转换为float类型。
否则,如果其中一个操作数是long类型,另一个会转换为long类型。
否则,两个操作数都转换为int类型。
一些容易出错的地方
byte x = 1; x=x+1 //报错 x=(byte)(x+1); //x是变量空间里的byte类型 1是常量区里的int类型1 相加的时候会类型自动提升到int,再次赋值给byte类型的时候需要强制类型转换 x+=1 //这里不会报错
同样是byte类型 x=x+1会报错,报错的原因在代码在代码注释中已经解释过了,x+=1为什么不会报错,这里有两种解释,
一种在笔记3中提到过,也是赋值运算符的特性,如果等号右边是常量则 会自动把int类型的1变成byte类型的1
另一种解释:复合赋值 E1 op= E2等价于简单赋值E1 = (T)((E1)op(E2))其中T是E1的数据类型,op为操作符 我个人倾向第二种解释更好理解些,当然同时引申出来的问题就是复合赋值会有精度的损失。
关于赋值运算的运算解析 博客https://blog.csdn.net/honeygirls/article/details/81321512说的很详细,这里引用下