大部分的C编译器,用移位的方法得到代码比调用乘除法子程序生成的代码效率高。

移位运算是将数值向左向右移动,对于十进制来说就是实现放大十倍和缩小十倍的效果,而对于二进制而言就是放大两倍和缩小两倍的效果

整数的乘除法

一个自己曾经忽视的东西,那就是C/C++中的移位操作容易出错的情况。

1、什么样的数据类型可以直接移位

char、short、int、long、unsigned char、unsigned short、unsigned int、unsigned long都可以进行移位操作,而double、float、bool、long double则不可以进行移位操作。

2、有符号数据类型的移位操作

对于char、short、int、long这些有符号的数据类型:

对负数进行左移:符号位始终为1,其他位左移

对正数进行左移:所有位左移,即 <<,可能会变成负数

对负数进行右移:取绝对值,然后右移,再取相反数

对正数进行右移:所有位右移,即 >>

这里很重要,具体为啥是这样,文章之后会解释!

3、无符号数据类型的移位操作

对于unsigned char、unsigned short、unsigned int、unsigned long这些无符号数据类型:

没有特殊要说明的,使用<< 和 >> 操作符就OK了

算术移位和逻辑移位运算

  1. 逻辑移位

    对于逻辑移位,就是不考虑符号位,移位的结果只是数据所有的位数进行移位。根据移位操作的目的,左移时,低位补0,右移时,高位补0;

    例:

    01010101>>3=00001010

    01101011<<3=01011000

    Q1:此时是不是就有一个问题,不考虑符号位,如果一个负数,逻辑右移,结果就会变成正数,例如

    10000101=-5>>1=01000010=66

    的确是的,因为这是逻辑运算的特点,算术移位运算才会考虑符号位!

    Q2:如果一个有符号数,逻辑左移,导致符号位变化,此时也算逻辑运算的特点吗?

    例如:10000001=-1<<1=00000010=2;

    这里只是溢出,并不算特点,类型溢出在算术移位中也存在!

    另外,C语言中没有无符号移位运算符,怎样才能实现无符号右移呢?负数进行无符号右移,很明显,符号位被0填充,变成正数,这种没有意义,一般都是正数进行无符号位移位,可以强制转换,可以使用除法!

    更严谨的写法:

    1.先进行类型转换
    ((unsigned int)x);

    2.将要移动的数字和0xFFFFFFFF进行与运算,就可以转换为unsigned 类型了
    (0xFFFFFFFF&x);

    这里假设int型是32位的…

  2. 算术移位

    算术是带有符号的数据,所以我们不能直接移动所有的位数,这可能会使得符号不正确。

    一个很重要的知识:

    关于数的移位,特别需要注意正数,三码相同,所以无论左移还是右移都是补0.而负数的补码就需要注意,左移在右边补0,右移需要在左边补1,有一个很有趣的误区是,认为符号位保持不变,仅仅移动数据位,这是不对的,因为无论数据位还是符号位,都是二进制,在整体大迁移的过程中,符号位也是要跟随潮流的。只不过,为了保证右移后,和原来的符号数一样,因此,负数在右移时左边补1.

    比如8位机器数(补码):1,110 0110,右移一位是:1,111 0011,开始的符号位变成了现在的数据位。

    (1). 原码
    原码就是多了一个符号位,所以符号位不变,其余数值位当做***逻辑移位***来处理即可。

    这里为什么说当逻辑移位运算即可呢?

    因为计算机中存储数据,都是用补码存储的,整数的补码,反码,原码都相同,因此对原码进行算术移位运算,都是对正数进行算术运算,因此用逻辑移位来处理即可!

    (2). 反码
    类似原码,符号位不变,其余数值位当做逻辑移位来处理,但是对于负数,补0的时候应该补1,这是因为负数的反码“0”的效果和正数的“1”产生的效果是一样的。

    (3). 补码
    观察补码表,不难知道,当左移移出的数据位正数为“0”、负数为“1”时(只有这时候该数值小于等于最大值/2),一定不发生溢出。因为补码是计算机储存的形式,所以在硬件实现的时候为了方便和简化,左移:假设不发生溢出,直接将数据最高有效位移入符号位,最低位补0(为什么负数最低位不是补1?如果是反码补1没错,但是补码是反码加了1,所以最低位还是补0)。右移:假设不发生溢出,符号位不变,同时用符号位补数值最高位。

    比如,将一个有符号数左移移位,C++编译器的运行会是怎样的呢?

    #include <iostream>
    #include <stdio.h>
    using namespace std;int main()
    {char a=-65;a=a>>1;printf("%d",a);return 0;
    }
    

    查看main函数汇编代码:

    main:push   rbpmov    rbp,rspsub    rsp,0x10mov    BYTE PTR [rbp-0x1],0xbfmovsx  eax,BYTE PTR [rbp-0x1]sar    eax,1mov    BYTE PTR [rbp-0x1],almovsx  eax,BYTE PTR [rbp-0x1]mov    esi,eaxmov    edi,0x4007d5mov    eax,0x0call   4005a0 <printf@plt>mov    eax,0x0leave  ret
    

    这里稍微补充一下汇编知识点,比如说MOV BYTE PTR[BX] 10H
    源操作数用的是立即寻址,相当于直接赋值10H,目的操作数用的是寄存器间接寻址,假设BX里的内容(BX)=1000H,(DS)=2000H,那么对应的物理地址为 DS X 10H+BX =21000H,现在这个物理地址对应的单元内容是10H

    mov BYTE PTR [rbp-0x1],0xbf,这句汇编,意思就是将0xbf这个立即数放到(DS) X 10H+rbp-0x1的物理地址中;对应C++中的char a=-65;

    现在来好好看看0xbf,我们知道,-65=11000001,转换为补码,就是除符号位外,其他取反加一,补码为10111111=0xbf !!!

    也就是说,C++在存储数字的时候都采用补码的形式;

    将10111111>>1,结果为11011111,转为原码,也是除符号位外,其他取反加一,为10100001=-33;

    的确,编译器的结果就是-33

    Program returned: 0Program stdout-33
    

    如果-65>>2,结果会是多少呢!

    11101111->10010001=-17

    这里为什么会是-17呢!

    10111111>>2,就变为,101111,符号位依然是1,那第七位是补1还是补0呢!

    上面补码中有描述,右移:假设不发生溢出,符号位不变,同时用符号位补数值最高位。

    也就是第七位补1,即移位后变为11101111,原码位10010001=-17;

    如果-65>>3,移位后,10111,符号位为1,第七位补1,那第六位呢!其实,算术右移,左侧均补符号位,即正数补0,负数补1。

    -65>>3=11110111,原码为,10001001=-9

知道了编译器是通过补码形式进行算术移位运算,考虑了算术右移的情况,那么来考虑算术左移的情况,

左移里一个比较特殊的情况是当左移的位数超过该数值类型的最大位数时,编译器会用左移的位数去模类型的 最大位数,然后按余数进行移位,先不考虑这种情况!

考虑正常的情况,左移的规律如下!

左移:假设不发生溢出,直接将数据最高有效位移入符号位,最低位补0

左移的描述中有一个假设词,即假设不发生溢出,如何保证不溢出呢?

计算机是这样做的,为保证补码算术左移时不发生溢出,移位的数据最高有效位必须与符号位相同。什么叫 移位的数据最高有效位,即移位后,成为最高符号位的数据位。在不发生溢出的前提下,用硬件实现补码的 算术左移时,直接将数据最高有效位移入符号位,不会改变机器数的符号。

移位的数据最高有效位必须与符号位相同?和后面这句话是什么意思啊~?

取一字节为例,能表示的数字从 -128 ~ 127

先看正数,比如 9,就是 0000 1001,最高位(符号位)和数据最高位都是 0. 左移得到 0001 0010 也就是 18,再看负数 -9,就是 -(0000 1001) 也就是 1111 0110 + 1 即 1111 0111,最高位(符号位)和数据最高位 都是 1. 左移后是 1110 1110,反过来是 (1110 1101 + 1)->0001 0010,就是 -18 了。

如果数据最高位和符号位不同的话,说明这个数字的绝对值已经超过 64 了,那么左移一位后必然溢出。
所以一个【有效的】左移最高位和数据最高位必然一致。故算术左移和逻辑左移一样。

  1. 算术移位和逻辑移位的比较

    根据上述的描述,可以归纳出(均为补码操作):

    1. 当一个【有效的】左移最高位和数据最高位一致时,算术左移和逻辑左移一样,均为右补0,不一致时,算术左移溢出,无意义!

    2. 逻辑右移很简单,只要将二进制数整体右移,左边补0即可 ,算术右移,左侧均补符号位,即正数补0,负数补1。

      不清楚的可以回头看看上文解析!

其中逻辑移位最简单,不管左移右移,移出来的空位补0即可。

算术移位特别需要注意,原来的符号位一样移动。因为移位是宏观的变化,不允许任何元素保持不动。所以左移时,正数有可能变为负数,负数有可能变为正数。因为左移原来的符号位丢了,右边补的是0。而右移时不会改变符号性,因为右移是将数据减半,减半不可能减成相反的符号的。而左移可能溢出,溢出的特征就是符号跃迁。

拓展知识

java移位运算符:<<(左移)、>>(带符号右移)和>>>(无符号右移)

  1. <<(左移)

    左移的规则只记住一点:丢弃最高位,0补最低位(此时也会溢出,但是我们也应该尽量避免这种溢出)

    如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模。如对int型移动33位,实际上只移动了332=1位。

  2. >>(带符号右移)

    右移的规则只记住一点:符号位不变,左边补上符号位

    按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1

  3. >>>(无符号右移)

    无符号右移的规则只记住一点:忽略了符号位扩展,0补最高位

    无符号右移规则和右移运算是一样的,只是填充时不管左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,因为对于正数来说这种运算没有意义
    无符号右移运算符>>> 只是对32位和64位的值有意义

算术移位和逻辑移位详解相关推荐

  1. (转)C语言位运算详解

    地址:http://www.cnblogs.com/911/archive/2008/05/20/1203477.html C语言位运算详解 作者:911 说明:本文参考了http://www2.ts ...

  2. 【从饮水机到名人堂之c语言】操作符详解(1)

    目录 前言: 一.操作符的分类 二.详解操作符 1.算术操作符 2.移位操作符 1.原码.反码.补码 2.左移位操作符 3.右移位操作符 三.位操作符 1.按位与& 2.按位或 | 3.异或^ ...

  3. php中左移和右移,c语言左移和右移的示例详解

    逻辑移位,简单理解就是物理上按位进行的左右移动,两头用0进行补充,不关心数值的符号问题. 算术移位,同样也是物理上按位进行的左右移动,两头用0进行补充,但必须确保符号位不改变. 算术移位指令 算术移位 ...

  4. C语言基础之操作符详解

    C语言基础之操作符详解 操作符的分类 算术操作符 移位操作符 位操作符 逻辑操作符 逗号表达式 表达式求值 隐式类型转换 算术转换 操作符的属性 xwg今天就带各位大佬来了解一波C语言的操作符. 操作 ...

  5. C语言向右移三个字母怎么做,c语言左移和右移的示例详解

    逻辑移位,简单理解就是物理上按位进行的左右移动,两头用0进行补充,不关心数值的符号问题. 算术移位,同样也是物理上按位进行的左右移动,两头用0进行补充,但必须确保符号位不改变. 算术移位指令 算术移位 ...

  6. C语言学习笔记—P13(操作符详解<1>+图解+题例)

    目录 前言:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教! --By 作者:新晓·故知 操作符详解<1>:​ 题例: 1. 操作符分类: 算术操作符 移位操作 ...

  7. 1. 批处理常用符号详解:

    1. 批处理常用符号详解: -------------------------------------------- 1.@ 一般在它之后紧跟一条命令或一条语句,则此命令或语句本身在执行的时候不会显示 ...

  8. 深入理解计算机系统(CSAPP)含lab详解 完结

    文章目录 深入理解计算机操作系统-第一章 1.1 信息就是位 + 上下文 1.2 程序被其他程序翻译成不同的格式 1.3 了解编译系统如何工作是大有益处的 1.4 处理器读并解释储存在内存中的指令 1 ...

  9. 网络安全学习第二篇【IP地址详解】

    一.IP详解 局域网 局域网一般被称为内网,通常由交换机,网线和PC组成.交换机是组建内网的关键设备 IP地址 IP地址(Internet Protocol Address)是指互联网协议地址,又译为 ...

最新文章

  1. MongoDB 是如何鼓励和激励开发者社区的
  2. 3.4 matlab用for语句实现循环结构
  3. Linux中sysstat服务,Linux 性能优化工具包 sysstat 以及 sysstat 服务
  4. 【转】Java 内存模型及GC原理
  5. ImageView和onTouchListener实现,点击查看图片细节
  6. docker安装运行qq
  7. BZOJ3862Little Devil I——树链剖分+线段树
  8. Roguelike+RPG如何给玩家刺激的游戏体验? 《我功夫特牛》系统逆推
  9. Redis学习笔记(五) 总结
  10. 安装了Node.js 从VScode 使用node -v 和 npm -v等命令却无效
  11. Vijos P1740聪明的质检员
  12. Activity的启动流程源码解析
  13. Sharepoint 2010 对话框框架
  14. 5月上旬香港域名总量动态:大幅度下降 净减6466个
  15. 华为设备配置基于MSDP的Anycast RP
  16. 什么是APS系统?其重要功能有哪些?这篇文章写得很清楚
  17. HDU 6130 Kolakoski
  18. KETTLE8.2在linux(rehl)6.6中安装部署并配置公共数据库链接
  19. python打印等腰三角形_Python 打印各种三角形
  20. 以全能之力造非凡旗舰:荣耀Magic3系列新品发布

热门文章

  1. AD快捷键、常见问题汇总
  2. Fluter 应用调试
  3. Typora收费了,再找找免费的Markdown编辑器吗?
  4. Scratch案例-冒泡排序
  5. rfc-3227中文翻译
  6. C++跳出for循环
  7. Linux系统关闭防火墙~
  8. 2020 icpc 沈阳
  9. Step7中有关时间和定时器的使用和例程2
  10. 基于python的数字图像处理--学习笔记(三)