Java基础--运算符(包括位运算详解)

Java基础--运算符(包括位运算详解),第1张

Java基础--运算符(包括位运算详解)

目录

一、赋值运算符

二、算数运算符

 三、拓展算数运算符

(1)

(2)

(3)

知识补充(原码、反码、补码

(4)练习

 (5)好代码标准

(6)自增与自减

 练习1

 练习2

四、关系运算符

五、逻辑运算符

(1)逻辑与(&)与逻辑或(|)

(2)短路与(&&)与短路或(||)

<1> 短路与(&&)

<2> 短路或(||)

 (3)异或运算(^)

<1> 不同为true

 <2> 相同为false​

 (4)逻辑非( !)

六、位运算符 

(1)左移运算符(<<)

(2)右移运算符(>>)

(3)不带号右移运算符(>>>) 

补充知识

分析:

强化位运算

(4)按位与&  按位或|

 (5)按位异或^  按位取反~

七、条件运算符

 八、运算符的优先级



一、赋值运算符

= 将右边的值赋值给左边的变量, 一定要等右边所有的代码运算完毕再进行赋值

int i = 100*10;

二、算数运算符

+ - * / %

(1)在算数运算符使用的过程中,注意运算结果的数据类型,可能和参与运算的变量类型不一致。

如下图这样写是正确的:

 但我们把int类型改成byte就会出错:

为啥呢?

我们来看一下,若i赋值为127,j赋值为127,那么i+j的值就超过了byte的范围。

为了解决这种问题,Java做了这样一个调整,当数据类型是小于int的整数,进行加减乘的时候,结果默认为int类型。为了避免数据范围太小,造成数据损失。 

所以当i+j之后,结果是一个int类型的数据。但我们把它存储到变量z(byte类型)中的时候,就需要强制类型转换。

同样的,若变量i是一个byte类型,变量j是一个short类型,最终加起来还是int类型。

1、只要是两个比int类型小(或者等于int类型)的数据加(减、乘)起来,最终结果都是以int类型存储! 

2、如果是大于int类型的变量参与数学运算,以最大的变量数据类型作为结果类型。

(2)在进行除法运算, 整数除以整数,不会保留小数位,除非其中一方是浮点数。

上节中对此类问题有所描述,可以去看上节内容:

(3)取模运算对于浮点数同样有效。 

 % 模运算  取模运算  去余数运算

我们先来看一下整数的取模运算:

数学运算中:23/7=3---2   (商3余2)

Java中 取模运算对于浮点数同样有效。

练习

倒序输出:

准备任意一个四位整数,倒叙输出。如:1234 >>> 4321

代码:

package demo3;

import com.sun.javaws.IconUtil;

public class Test3 {
    public static void main(String[] args) {
        
        int i=1234;
        //先输出4,再输出3/2/1
        int g=i%10; //g=4
        int s=i/10%10; //s=3
        int b=i/100%10;  //b=2
        int q=i/1000; //q=1
        System.out.print(g); //当输出语句带ln的时候(println),就表示换行
        System.out.print(s);
        System.out.print(b);
        System.out.print(q);
    }
}

 三、拓展算数运算符

+=  -=  /=  *=  %=  ++  --

拓展算数运算符可以理解为算数运算符的简写,但是算数运算符会改变结果的数据类型。

拓展算数运算符直接在原来的变量上进行 *** 作,所以不会改变变量的数据类型。

(1)

我们先来看这样一段代码:

 赋值运算符= :先计算等号右边的结果,将结果赋值给左边的变量。

i=i+10;  --->先计算右边i+10,等于20,再赋值给左边的i。

这个运算就相当于:int b=i+10;   i=b;

拓展算数运算符就是对这种运算做一种简化的。

就是让i变量自增10个。即:i+=10;

同理:

i-=10;   --> i=i-10;

i*=10;  --> i=i*10;

i/=10;  --> i=i/10;

i%=10;  --> i=i%10; 

(2)

我们刚刚讲过,算术运算符在使用的时候,有这样一个特征:在算数运算符使用的过程中,注意运算结果的数据类型,可能和参与运算的变量类型不一致。

那么我们再来看下面的代码:

如上图所示,这样会报错。

因为b是byte类型,当和10进行加法运算之后,结果是以int类型来存储。当等号右边计算结束,它会将这个值赋值给左边的变量b,而变量b是byte类型!装不下。 

这时候,需要一个强制类型转换。

而拓展运算符在这个运算的时候包含了一个隐形的数据类型的转换。

我们来看一下,用拓展运算符的结果是不报错的:

拓展运算符不会改变原有变量的数据类型!

(3)

那这样不会出现上面说过的那个问题吗?就是如果等号右边的数值运算之后,超过了左边的变量的数据范围(也就是左边变量存不下右边的数值),那么会出现什么情况呢?

我们不妨来试一试:

注:本人才疏学浅,这个-118实在不知道如何得出来,在这里就不阐述了,只是在这里放一个结果。若哪位大佬路过,请帮我解答一下,谢谢! 

知识补充(原码、反码、补码)

计算机中用二进制表示数字。

不会十进制与二进制互转的可以看一下之前的文章(进制及其转换):

Java基础--常量和变量_m0_55746113的博客-CSDN博客https://blog.csdn.net/m0_55746113/article/details/122552312?spm=1001.2014.3001.5501

如6。正数三个都一样。

原码  00000000 00000000 00000000 00000110

反码  00000000 00000000 00000000 00000110

补码  00000000 00000000 00000000 00000110

如-6。

原码  10000000 00000000 00000000 00000110

反码  11111111   11111111   11111111   11111001

(保持原码符号位不变,剩下位置取反)

补码  11111111  11111111  11111111  11111010

(反码+1)--> 注意逢2进1

计算机中,不管是正数还是负数,都是以补码形式来存储的。

具体举例如下图:

(1)已知[X]原,求[X]补

(2)已知[X]补,求[X]原

 因为拓展运算符不会改变变量的数据类型,那么就需要注意数字的范围不要超过数据类型的范围。否则可能会造成数据的损失。

(4)练习

完成两个整数变量值的互换。

如:变量a为3,b为5。最终让a输出5,b输出3。

方法一、

代码: 

package demo3;

public class Test6 {
    public static void main(String[] args) {
        
        int a=10;
        int b=20;
        //方法1 借助中间变量完成
        int c=a;
        a=b;
        b=c;
        System.out.println(a); //20
        System.out.println(b); //10
        
    }
}

 方法二、

代码:

package demo3;

public class Test6 {
    public static void main(String[] args) {
        
        int a=10;
        int b=20;
        //方法2
        a=a+b; //a=30
        b=a-b; //b=30-20=10
        a=a-b; //a=30-10=20
        System.out.println(a);
        System.out.println(b);
    }
}
 (5)好代码标准

好代码标准:

1、注释全面

2、简洁

3、格式规范

4、杜绝2B代码,不用追求黑客代码,最受欢迎的就是普通代码(阅读性最强)

(6)自增与自减

简单来看,就是让变量自增和自减:

 但有一点需要注意,这个自增和自减,放在变量前后的结果可能不同哦。

1、++、-- 在变量前面,先让变量自增/自减,然后再使用变量参与运算。

2、++、-- 在变量后面,先使用变量参与运算,然后再让变量自增/自减。 

int i=10;
int b=i++;
//相当于:int b=i;  i=i+1;
int c=++i;
//相当于:i=i+1;  int c=i;
 练习1

代码:

package demo3;

public class Test9 {
    public static void main(String[] args) {
        int i=10;
        int j=5;
        // ++i --> i=11 ,然后使用的也是11
        // j-- --> 先使用j的值5,j再自减为4
        int b=++i + j--;//b=11+5=16
        System.out.println(i);
        System.out.println(j);
        System.out.println(b);
    }
}

 练习2

简单来看

 详细解释:

 代码:

package demo3;

public class Test10 {
    public static void main(String[] args) {
        int a=10;
        int b=a++; //b=10 a=11
        //(--b)-->9 ,b=9 ;(++a)-->12 ,a=12
        int c=(--b)+(++a); //c=9+12=21
        //(c++)-->21 ,c=22 ;(a--)-->12 ,a=11 ;(++b)-->10 ,b=10
        int d=(c++)-(a--)+(++b);//d=21-12+10=19
        //(--d)-->18 ,d=18 ;(c++)-->22 ,c=23 ;(++b)-->11 ,b=11 ;(a--)-->11 ,a=10
        int e=(--d)+(c++)-(++b)+(a--);//e=18+22-11+11=40
        System.out.println(a);//10
        System.out.println(b);//11
        System.out.println(c);//23
        System.out.println(d);//18
        System.out.println(e);//40
    }
}

四、关系运算符

数据大小关系

<   >  <=   >=  ==  !=

关系运算符:判断数据大小关系

运算结果是布尔类型  true / false。

package demo4;

public class Test1 {
    public static void main(String[] args) {
        
        boolean b=10>20;
        System.out.println(b);

        System.out.println(10==20);//判断10与20是否相等,相等就输出true,不相等就输出false
        System.out.println(10!=20);//10不等于20,成立,输出为true

        boolean i=2>=3; //此处的含义是:2大于或等于3
        System.out.println(i);

        //除数与被除数
        //10(被除数)/3(除数)  --> 10是被分开的,所以是被除数。除数不能为0,被除数不能为0。

    }
}

结果:

五、逻辑运算符

&   |   ^   &&   ||   !

(1)逻辑与(&)与逻辑或(|)
& 逻辑与运算   (并且)  --> 多个条件同时为true,则为true,一方为false,则为false。
| 逻辑或运算   (或者)  --> 多个条件任意一个为true,则为true。全为false,才为false。

(2)短路与(&&)与短路或(||) <1> 短路与(&&)

我们可以发现,如下代码可以成功执行:

当我们把逻辑与(&)换成短路与(&&)的时候,结果不变:

Q:那么逻辑与和短路与有什么区别吗?

我们现在来把age的值改为10,结果显然为false。

短路与(&&)运算的时候,若前面一个表达式为false,就不会再判断之后的表达式了,即后面的表达式不会再被执行。因为结果就已经出来了,一个表达式为false,整个结果就是false。

 Q:那我们如何知道后面的表达式没有被执行呢?

如果除数为0,那么就会出现ArithmeticException: / by zero的异常。

那么我们来看这样一段代码:

boolean b4=false && 1/0 >1; //短路与运算,如果第一个值为false,那么后面就不执行了
System.out.println(b4);//若第二个表达式没有被执行,程序就不会出现异常

 可以看到结果是false,并没有出现异常。即:后面的表达式没有被执行。

而逻辑与(&)运算,无论第一个表达式是否为false,后面的表达式都会被执行!

那我们不妨用逻辑与(&)来执行刚才的代码,如下图显示结果:

可以看到结果是程序异常,验证了我们刚才的阐述,确实执行了后面的表达式。 

那么如果第一个表达式是是true,我们并不能知道后面表达式是否为true。若为false,则整体为false,若为true,则整体才为true。所以,对于短路与(&&)运算,若第一个表达式为true,后面的表达式仍会被执行。

我们还是那刚才的例子来看:

可以看到,程序出现了异常,后面表达式被执行了。

总结:

短路与(&&)运算,发现前面的一项结果为false,则后面的条件不再运算。 

<2> 短路或(||)

短路或(||)运算和短路与(&&)差不多,只是条件不一样。

//短路或(||)运算,表达式若有一方为true,则整个结果就为true
boolean b7=true || 1/0>1;  
//第一个条件已经为true,后面表达式的结果已经不重要了,结果都为true
System.out.println(b7);

我们可以看一下结果:

我们可以看到,结果为true。即,并没有执行第二个表达式。(若执行第二个表达式的话,会出现异常哦)

那我们还可以来看一下逻辑或(|)运算:

boolean b8=true | 1/0>1; //逻辑或(|)运算,不管前面是否为true,后面表达式都会被执行
System.out.println(b8);  //后面的表达式被执行了,程序会出现异常

总结:
短路或(||)运算,发现前面的一项结果为true时,则后面条件就不运算了。 

注意: 以后基本使用短路的与运算和或运算。可以减少计算量。

 (3)异或运算(^)

异或运算:相同则为假(false),不同则为真(true)。

<1> 不同为true

 <2> 相同为false

 (4)逻辑非( !)

逻辑非(!)--> 原来为true,加上之后为false;原来为false,加上之后为true。

也可以把叹号!直接写在输出语句中:

六、位运算符 

 >>   <<   >>>    &   |   ^   ~ 

(1)左移运算符(<<)

分析:

左移一位,相当于乘2 。

(2)右移运算符(>>)

分析:

右移一位,相当于除2。 

(3)不带号右移运算符(>>>) 

我们可以看到,这个结果和上面的右移运算符(>>)一样。

Q:>>和>>>有什么不一样? 

我们先把a的值换成-8来看看:

注意:

>>和<<不会移动符号位,但是>>>会移动符号位。 

补充知识

知识补充(原码、反码、补码)-- 一网打尽_m0_55746113的博客-CSDN博客计算机中用二进制表示数字。不会十进制与二进制互转的可以看一下之前的文章(进制及其转换):Java基础--常量和变量_m0_55746113的博客-CSDN博客https://blog.csdn.net/m0_55746113/article/details/122552312?spm=1001.2014.3001.5501计算机中,不管是正数还是负数,都是以补码形式来存储的。既然最终是以补码来存储,那么为啥要有反码和原码呢?因为给我们补码数据,我们是不认识的。只有把补码转化为原码,我https://blog.csdn.net/m0_55746113/article/details/122625576?spm=1001.2014.3001.5501

<1> 6<<2=24

00000000 00000000 00000000 00000110

0000000 00000000 00000000 000001100

<2> 6>>1==3

00000000 00000000 00000000 00000110

000000000 00000000 00000000 0000011

<3> -6>>1

11111111   11111111   11111111   11111010

111111111   11111111   11111111   1111101

>> 保留符号位置1 填1

>>> 不保留符号,不管是什么,都填0

分析:

1、 第一次

a=8

十进制8用二进制表示为:

8:     00000000 00000000 00000000 00001000

①8>>1:   00000000 00000000 00000000 00001000

最后的0溢出,最前面填符号位0(8是正数),最终得到:

          00000000 00000000 00000000 0000100

将此二进制转化为十进制,得到:4

②8>>>1: 00000000 00000000 00000000 00001000

最后的0溢出,最前面无论什么情况都补0,最终得到:

           00000000 00000000 00000000 0000100

将此二进制转化为十进制,得到:4

2、第二次

a=-8

十进制-8用二进制表示为:

-8:10000000 00000000 00000000 00001000 (原码)

       11111111  11111111    11111111  11110111   (反码)

       11111111   11111111   11111111  11111000   (补码)

①-8<<1: 

     11111111   11111111   11111111  11111000

“<<”不会移动符号位,我们可以看到,在移动的时候,我们将最高位移动,左移之后,最高位溢出,最后我们需要将最高位改符号位1(这里原本就是1,就不用动了),最后一位补0,最终得到:

       1111111   11111111   11111111  111110000

我们得到的是补码,转换为原码(补码转反码 +1):

       10000000 00000000 00000000 00001111

-->   10000000 00000000 00000000 00010000(原码)

将此二进制转化为十进制,得到:-16

②-8>>1 :    

       11111111   11111111   11111111  11111000

“>>”不会移动符号位,我们可以看到,在移动的时候,最后一位溢出,最后我们需要将最高位补符号位1,最终得到:

       111111111   11111111   11111111  1111100

我们得到的是补码,转换为原码(补码转反码 +1):

      10000000   00000000  00000000  00000011

-->  10000000   00000000  00000000  00000100(原码)

将此二进制转化为十进制,得到:-4

③-8>>>1: 

        11111111   11111111   11111111  11111000   (补码)

“>>>”会移动符号位,所以我们可以看到,在移动的时候,我们最高位还是以1来移动,最后的一位0溢出,最前面补0,最终得到:

       0 11111111   11111111   11111111  1111100

我们得到的是补码,转换为原码(正数三个码相同):

   01111111    11111111    11111111   11111100

将此二进制转化为十进制,得到:2147483644

        //    11111111   11111111   11111111  11111000  (补码)
        //     11111111   11111111   11111111  11111000 (右移一位,最右边溢出一位)
        //    011111111   11111111   11111111  1111100  (无符号移位,高位补0)
        //--> 01111111    11111111    11111111   11111100  (整理之后)
        //--> 01111111    11111111    11111111   11111100  (原码)
        //--> 2,147,483,644 (十进制)

总结(重点)

1、有符号右移:负数右移高位补1

2、无符号右移:高位补0 

强化位运算

注:所有结果真实通过编译器运行得到!

如果对移位运算还是不熟悉,咱们可以再来举个例子:

<1>  5<<2  =20

        //5:  00000000 00000000 00000000 00000101  (原码)
        //    00000000 00000000 00000000 00000101  (反码)
        //    00000000 00000000 00000000 00000101  (补码)

        int a=5<<2;
        //    00000000 00000000 00000000 00000101  (补码)
        //  00000000 00000000 00000000 00000101    (最左边两个溢出)
        //    000000 00000000 00000000 0000010100  (最后面补0)
        //--> 00000000 00000000 00000000 00010100  (整理之后)
        //--> 00000000 00000000 00000000 00010100  (转为原码)
        //--> 20 (转为十进制)
        System.out.println(a);

<2>  5>>2  =1

        int b=5>>2;
        //    00000000 00000000 00000000 00000101  (补码)
        //      00000000 00000000 00000000 00000101(最右边两个溢出)
        //    0000000000 00000000 00000000 000001  (最前面补0)
        //--> 00000000 00000000 00000000 00000001  (整理之后)
        //--> 00000000 00000000 00000000 00000001  (转为原码)
        //--> 1(转为十进制)
        System.out.println(b);

<3>  -5<<2  = -20

        //-5:  10000000 00000000 00000000 00000101  (原码)
        //     11111111 11111111 11111111 11111010  (反码:原码符号位不变,其余取反)
        //     11111111 11111111 11111111 11111011  (补码:反码+1)

        int c=-5<<2;
        //     11111111 11111111 11111111 11111011  (补码)
        //   11111111 11111111 11111111 11111011    (最左边两个溢出)
        //     111111 11111111 11111111 1111101100  (最右边补0)
        //-->  11111111 11111111 11111111 11101100  (整理之后的补码)
        //-->  10000000 00000000 00000000 00010011
        //-->  10000000 00000000 00000000 00010100  (原码)
        //-->  -20  (转为十进制)
        System.out.println(c);

<4>  -5>>2  = -2

        int d=-5>>2;
        //     11111111 11111111 11111111 11111011  (补码)
        //       11111111 11111111 11111111 11111011(最后两位溢出)
        //     1111111111 11111111 11111111 111110  (负数右移高位补1)
        //-->  11111111 11111111 11111111 11111110  (整理之后的补码)
        //-->  10000000 00000000 00000000 00000001
        //-->  10000000 00000000 00000000 00000010  (原码)
        //-->  -2  (转化为十进制)
        System.out.println(d);

<5>  -5>>>2  = 1073741822

        int e=-5>>>2;
        //     11111111 11111111 11111111 11111011  (补码)
        //       11111111 11111111 11111111 11111011(最后两位溢出)
        //     0011111111 11111111 11111111 111110  (无符号右移,高位补0)
        //-->  00111111 11111111 11111111 11111110  (整理之后的补码)
        //-->  00111111 11111111 11111111 11111110  (原码)
        //-->  1073741822  (转化为十进制)
        System.out.println(e);
(4)按位与&  按位或|

有的人可能会问,这不是逻辑与运算吗?

逻辑与运算两端运算完之后,结果是布尔类型。

如果与运算两端是整数的话,那就是按位与运算了。不是逻辑与运算。

<1> 按位与:同为1,则为1;一方为0,则为0。
        //4、按位与 &
        //同为1,则为1;一方为0,则为0。
        int e=8;  //   0000 1000
        int f=9; //    0000 1001
        int g=e & f;// 0000 1000  --> 8
        System.out.println(g);

 ​​​​​​​

<2> 按位或:一方为1,则为1;同为0,才为0。
        //5、按位或 |
        //一方为1,则为1;同为0,才为0。
        int e2=8;       //  0000 1000
        int f2=9;       //  0000 1001
        int g2=e2 | f2; //  0000 1001 -->9
        System.out.println(g2);

 (5)按位异或^  按位取反~
<1> 按位异或 ^:相同即为0,不同即为1
        //6、按位异或 ^
        //相同即为0,不同即为1
        int e3=8;        //  0000 1000
        int f3=9;        //  0000 1001
        int g3=e3 ^ f3;  //  0000 0001 -->1
        System.out.println(g3);

<2> 按位取反~

6取反之后:-7

注意,反码是一种表现形式,取反(每一个位置都取反)是一个计算过程。

有的人会觉得按位取反是这样的规律:原来为0,变为1;原来为1,变为0。

但我们来 *** 作一下,会发现是不对的!!!

二进制存储的时候,会有一些算数方式,和正常算数方式不太一样。

我们要记住下面这样的规律:

-3   -2   -1  /  0   1   2   3

从1和0中间断开,如果是1,那么~1就是跟轴对应的-2。

如果是10,那对应的就是-11。以此类推。

其余的不需要深究,以后不大需要用这些的。

一般是用来写加密算法的,我们也不会去写。了解就可以啦。

七、条件运算符

?:

条件运算符:Java中唯一的一个三目运算符。

Q:三目运算符?

我们可以看到,之前的运算符都是判断两个条件。

三目运算符有三个组成部分。

条件?值1:值2

当条件为真,就将值1赋值给a,当条件为假,就将值2赋值给a

可以做一些简单的逻辑判断。

public class Test3 {
    public static void main(String[] args) {
        //条件运算符   条件? 值1 : 值2
        int a=true?10:20; //当条件为真,就将值1赋值给a,当条件为假,就将值2赋值给a
        System.out.println(a);//10

        int b=false?10:20;
        System.out.println(b);//20

        int i=10;
        int j=20;
        System.out.println(i>j?i:j);//谁大就输出谁
    }
}

 八、运算符的优先级

boolean b = 1+0>1 || 5>3?true:false;

所以优先级简单可以记忆为

算术运算符 >关系运算符 >条件运算符 > 逻辑运算符 > 赋值运算符

实在分不清楚 那么就用小括号提升运算优先级即可。

不用刻意去记忆。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5712211.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存