1. 运算符按照功能进行分类
1.1算术运算符
1.+(加) 2.-(减) 3.*(乘) 4./(除) 5.%(取余或称为取模) 6.++(自增) 7. --(自减)
1.2赋值运算符
1.=(赋值) 2.+=(加等) 3.-=(减等) 4.*=(乘等) 5./=(除等) 6.%=(取模等)
1.3关系运算符(或称为比较运算符)
1.>(大于) 2.>=(大于等于) 3.<(小于) 4.<=(小于等于) 5. !=(不等于) 6.==(等于)
1.4逻辑运算符
1.& (逻辑与) 2.| (逻辑或) 3.^ (逻辑异或) 4.! (逻辑非) 5.&& (短路与) 6.|| (短路或)
1.5位运算符
1. &(按位与) 2.|(按位或) 3.^(按位异或) 4.~(按位取反) 5.<<(按位左位移) 6.>>(按位右位移) 7.>>>(无符号的按位右位移)
运算符的思维导图
2.算术运算符详解
2.1除(/)和取模(%)
两者的区别:
若进行一个除法运算,使用除(/)得到的结果是商的值,而用取模(%)操作,得到的是余数的值。例如:
int x = 7;
int y = 2;
int result1 = x/y;
int result2 = x%y;
由于7除以2,商为3,余数为1。因此result1结果为3,result2的结果为1。
原理:由于7和2都是int类型,两个int类型的值之间进行运算结果仍为int类型,因此上面7除以2的结果为3,而不是3.5。对于取模(%)运算,它专门负责接收余数。
2.2自增运算符(++)
说明:这个运算符是重点也是难点。下面列举一些关于自增运算符的知识点,自减运算符(–)与此类似。
2.2.1 使用场合1
int x = 1;
x = x+1; //x结果为2
x++; //x结果为2
++x; //x结果为2
这三个操作方式的结果都是2,都是将x的值进行加1操作。x++和++x单独使用时,两者作用相同,都是自增1。
2.2.2 使用场合2
int x = 1;
int y = x++; //x的结果为2,y的结果为1
int y = ++x; //x的结果为2,y的结果为2
这两个操作方式的x结果都为2,但y的结果却不同,原因是进行了赋值操作。 x++和++x在赋值操作中,作用不同。
原理:x在想要做值交换(赋值操作)时,会产生一个临时的副本空间(备份),最后是将副本空间(备份)中的值进行赋值,而不是本身的值。由于++在变量的前面(如++x),表示先自增后备份;++在变量的后面(如x++),表示先备份后自增。
下图是int y=++x在内存中的实现机理:
第1步:x先自增1
第2步:将自增后的值放入副本空间(原空间中还有x值)
第3步:将副本空间中的值赋值给y
第4步:赋值结束后,副本空间被销毁
因此在进行int y=++x;操作时,x的结果为2,y的结果也为2。
下图是int y=x++在内存中的实现机理:
第1步:x先存入临时空间进行备份(原空间中还有x值)
第2步:x自增1
第3步:将副本空间中的值赋值给y
第4步:赋值结束后,副本空间被销毁
因此在进行int y=x++;操作时,x的结果为2,y的结果为1。
2.2.3 使用场合3
int a = 1;
a = a++; //a的结果为1
a = ++a; //a的结果为2
原理和上面类似,只要记住++在变量的前面(如++x),表示先自增后备份;++在变量的后面(如x++),表示先备份后自增。最后都是将备份里面的值赋值给左边变量。
以 int a = a++ 为例:
下图是int a=a++在内存中的实现机理:
第1步:a先存入临时空间进行备份
第2步:a自增1
第3步:将副本空间中的值再赋值给a
第4步:赋值结束后,副本空间被销毁
因此在进行int a=a++;操作时,a的结果为1。
附赠一道题:
int a = 0;
for(int i=1; i<=100; i++) {
a = a++;
}
System.out.println("a为"+a);//结果仍为1
上面输出a的结果为1;若将循环条件改为a=++a;则a的结果为100。
若将上面程序稍微改动。
int a = 0;
int b = 0;
for(int i=1; i<=100; i++){
b = a++;
}
System.out.println("b为"+b);//结果为99
上面输出b的结果为99;若将循环条件改为b=++a;则b的结果为100。
这些原理和上面讲的相同,读者们对比这几个例子再详细理解下。
2.2.4 使用场合4
一道笔试题
int m = 1; //第一次变化为:2 第二次变化为:1 第三次变化为:0 最终结果为0(按最后变化的为准)
int n = 2; //第一次变化为:3 第二次变化为:2 第三次变化为:1 最终结果为1
int sum = m++ + ++n - n-- - --m + n-- - --m;
1 + 3 - 3 - 1 + 2 - 0 //结果为2
System.out.println("m为"+m); //m为0
System.out.println("n为"+n); // n为1
System.out.println("sum为"+sum); //sum为2
解释:int sum = m++ + ++n - n-- - --m + n-- - --m; 首先这是自增(自减)操作与赋值操作的结合,上面我们已经提过,不管有没有赋值操作,x++与++x本身的x值都是增1的,区别在于向左边的变量赋值时x++是先备份后自增,因此左边的变量是x未自增的值,++x是先自增后备份,因此左边的变量是x自增后的值。
该式子右边第一步:m++,则m的值增1,m=2; 向左边赋值是1(m++先备份后自增)。因此 m=2 ;m++为1
该式子右边第二步:++n,则n的值增1,n=3;向左边赋值是3(++n先自增后备份)。因此n=3 ;++n为3
该式子右边第三步:n–,则n的值减1,第二步中n为3,减1后,n为2;向左边赋值是3(没有自减之前的值)。因此n=2;n–为3
该式子右边第四步:–m,则m的值减1,第一步中m的值为2,减1后,m的值为1;向左边赋值是1。因此m=1;–m为1
该式子右边第五步:n–,则n的值减1,第三步中n为2,减1后,n为1;向左边赋值是2。因此n=1;n–为2
该式子右边第六步:–m,则m的值减1,第四步中m为1,减1后,m为0;向左边赋值是0。因此m=0;–m为0。
3.赋值运算符详解
3.1x=x+n和x++的区别
说明:上面式子中的n值得是常量。
初始x为1,如果要让x增加为10。若用x++,则需要执行十次,当然可以用循环;但如果用x=x+10;只需一步即可。
使用x++方式:
int x = 1;
for(int i = 1; i<=10; i++){
x++;
}
使用x=x+n方式:
int x = 1;
int x = x+10;
3.2 x=x+1、x+=1和x++的不同
从执行效率上讲,x=x+1 < x+=1 < x++
三者执行的结果都是使x加1。但在某些时候执行时会有所不同。如:
int x = 1;
x+=1; //x为2
x++; //x为2
x=x+1; //x为2
以上三种情况不会出现问题,结果都是2。接着该一个条件,将x的类型该为byte,再来看:
byte x = 1;
x+=1; //x为2
x++; //x为2
x=x+1; //报错,需要类型转换,需改为x=(byte)(x+1);
此时,x=x+1就会报错,是由于x+1得到的是一个int型值,如果赋值给byte型的x,需要强制类型转换。也许有人知道这个原因,但为什么x+1得到的是一个int型的值呢?其他两种情况得到的就是byte型的值呢?原因在于它们的执行机理不同。x=x+1,是将右边x的值(byte类型)与常量区中的值(1)进行相加,然后再赋值。问题在这,常量区的常量值默认都是int类型,因此x的值是byte类型的1,与常量int类型的值1相加,结果为int类型的值2。虽然int类型和byte类型可以相加,但不可以赋值,byte可以向int赋值(小空间可以向大空间赋值),但int不能向byte赋值(大空间不能向小空间赋值,会造成损失)。因此这里就必须使用强制类型转换操作。
(知识延伸:(可以选择不阅读此段文字)上面所说,常量区中的值为int(32bit位)类型,因此可以看源代码,第一句:byte x=1;赋值操作符后面是一个常量。1为int类型32位,将1赋值给byte类型的x,此时赋值操作会进行自动的转换,将int类型后多余的位去掉,转换为byte类型的1再存入x中。第二句:x+=1;赋值操作符后面是一个常量。这里是byte类型的x先进行与常量(int类型)相加,这时加号(+)自动的做了类型提升,得到了int类型的值2,然后进行赋值,赋值(=)与加号(+)在一起,属于一个运算符,因此这里的=号自动的将int类型的2转化了byte类型的2。但x=x+1不同,这里的=和+是分开的,两者各做各的,赋值(=)后面是个表达式,不能进行自动转化。总结:若赋值操作符(包含复合的赋值操作符,如+=、-=等)后面是一个常量,则可以进行自动的类型转换,若赋值操作符后面是一个表达式,则不能进行自动的类型转换,就如上题所出现的情况)
4.关系运算符详解
4.1"=“与”=="的区别
”=“一个等号:是一个赋值操作符,它的作用是将等号右边的结果(结果可以为值或引用)赋值给等号左边的变量空间中。
“==”两个等号:是一个关系运算符,它的作用是比较等号左边和右边的元素(可以为值或引用)是否一致。
4.2关系运算的最终结果是什么?
使用关系(比较)运算时,进行运算的结果是一个boolean类型的值(true或false)。如:3>2的结果为true,5<4的结果为false,3==2的结果为false,3!=2的结果为true 等等。
5.逻辑运算符详解
注意观察,上面的关系运算符可以这样认为:关系运算都是在判定一个条件的真假(如3>2、5<4),当判定两个及以上条件的真假时,需用到逻辑运算。逻辑运算符前后两个表达式的结果都为boolean类型。
5.1逻辑与(&)、逻辑或(|)、逻辑异或(^)、逻辑非(!)
- 逻辑与(&):它表示并且的意思,只有当&前后两个条件都满足的时候(即两个条件的结果都是true),最终才为true;只要有一个条件不满足,则最终结果为false。eg:(4>2) & (3>1)的结果为true;(4>5) & (4>2)的结果为false。
- 逻辑或(|):它表示或者的意思,只要|前后两个条件满足一个,结果就为true;只有当两个条件都不满足时,结果才为false。如:(3>2) | (3>6)的结果为true;(3>4) | (3>5)的结果为false。
- 逻辑异或(^):它指的是只有当前后两个条件不一样时,它的结果才会为true;若前后两个条件的结果相同,则是false。如:(3>2) ^ (3>4)的结果为true;(3>2) ^ (3>1)的结果为false。
- 逻辑非(!):它是将条件的结果取反。如:!(3>2)的结果为false;!(4>5)的结果为true。
5.2逻辑与(&)与短路与(&&)的区别
首先看一个例子:(3>5) & (3>2) 与 (3>2) && (3>5) 两者的运算结果都是false。都为什么会有短路与(&&)的存在呢?
逻辑与(&)的执行过程:先判断3>5,结果为false,再判断3>2,结果为true,最后false和true相与(&),最后结果为false。
短路与(&&)的执行过程:先判断3>5,结果为false,不再进行判断,最后结果为false。
看到这里,大家也许对两者有所了解,因为不论是逻辑与(&)还是短路与(&&),本质都是进行与运算。因此当第一个条件为false时,不论后面的条件结果是false还是true,最终结果一定是false;短路与(&&)就是这样做的,但逻辑与(&)是将所有条件都计算出来再做最后的结果。因此,短路与(&&)的执行执行效率比逻辑与(&)要强一些(减少了运算)。
但是短路与(&&)的执行效率是不是总是比逻辑与(&)要强一些呢?答案是否定的。
短路与(&&)只有像上面例子的情况下才会发生短路(只执行一个条件就得出结果),若将上面例子稍微掉个顺序:(3>2) && (3>5)这种情况下,第一个条件的结果是true,使用短路与的情况下,也无法判断最终的结果是false还是true,因为第二个条件的结果未知,因此,在这种情况下,短路与和逻辑与的执行过程是一样的,都要将两个条件的结果计算出来,在进行逻辑运算。
总结:
1.不论使用逻辑与(&)还是短路与(&&),都不会影响最终的结果
2. 短路与(&&)在正常情况下与逻辑与(&)的执行过程是一致的,只有当第一个条件为false的时候,才会发生短路,提高效率,其余情况下并不会提高效率,和逻辑与(&)一样。
5.2逻辑或(|)与短路或(||)的区别
上面已经详细讲解了逻辑与(&)和短路与(&&)的区别,因此逻辑或(|)与短路或(||)与此类似,只需将第一个条件是true的情况下,才会发生短路(不再进行第二个条件的判断,最终结果直接为true)。
总结:
1.不论使用逻辑或(|)还是短路或(||),都不会影响最终的结果
2. 短路或(||)在正常情况下与逻辑或(|)的执行过程是一致的,只有当第一个条件为true的时候,才会发生短路,提高效率,其余情况下并不会提高效率,和逻辑或(|)一样。
6.位运算符详解
6.1 按位运算和逻辑运算的主要不同
对于小白来说,看到这里,也许会有些疑惑,这里与(&)、或(|)、异或(^)不是在上面逻辑运算时出现过了吗,其实,它们的符号虽然相同,但表达的意思却完全不同。
- 逻辑运算符的前后是表达式,按位运算符的前后是具体数值。
- 逻辑运算的结果是一个boolean类型的值,按位运算的结果是一个数值。
6.2 按位与(&)、按位或(|)、按位异或(^)、按位取反(~)
-
按位与(&):eg:3 & 5;它的运算过程:先将3转换为二进制数,将5转换为二进制数,然后让每一位二进制数进行相与(&)。相与的原理(0 & 0=0,0 & 1=0,1 & 0=0,1 & 1=1)。
过程为: 3的二进制数:00000011 5的二进制数:00000101 进行相与: 00000001 结果为1。
-
按位或(|):eg:3 | 5;它的运算过程,首先将两个数转换为二进制数,然后进行逐位相或。相或的原理(0 | 0=0,0 | 1=1,1 | 0=1,1 | 1=1)。
过程为:3的二进制数: 00000011 5的二进制数: 00000101 进行相与: 00000111 结果为7。
-
按位异或(^):eg:3 ^ 5;它的运算过程,首先将两个数转换为二进制数,然后进行逐位异或。异或的原理(0 ^ 0=0,0 ^ 1=1,1 ^ 0=1,1 ^ 1=0)。
过程为:3的二进制数: 00000011 5的二进制数:00000101 进行相与:00000110 结果为6。
-
按位取反:eg:~3;它的运算过程,首先将两个数转换为二进制数,然后进行逐位取反。异或的原理(1转换为0,0转换为1)。
过程为: 3的二进制数: 00000011 ~3的二进制数:11111100 //这是源码 将~3的二进制数取反码:10000011 //反码 将反码加1得到补码:10000100 //补码 结果为-4
在这里,对3进行求反后,需要再进行转换,因为计算机中存的都是补码,3求反后是负数,负数的补码是先求反码再加1,得到的数才是真正的~3。因此,对3按位取反的值为-4。