C/C++中的移位运算符——由二进制转换程序引发的思考

2023-11-12

以前学习移位运算符的时候并没有太多关注它,而此次关于移位运算符的探究,主要源于写的一个二进制显示的程序:

#include <iostream>
using namespace std;

int main()
{
   int a=-1;
	
   for(int i=0;i<32;i++)
	   cout<<(( a & ( 1 << 31 ) >> i ) != 0);
   return 0;
}

写这个程序的目的就是显示a的二进制表示,思路如下:由于这里的编译器是32位的,因此循环输出32次,对于每次循环,都将a与(1<<31)>>i按位与,什么意思呢?因为我们要先输出a的最高位,因此每次循环的最开始就需要先将1左移31位得到最高位为1,其余位为0,再与a按位与,得到的结果判断是否为0,如果不为0,说明a最高位为1,否则为0;如此,进入第i次循环时,就判断a的高第i位的情况,这个时候就将1左移31位的结果右移i位即可。

这样来解释的话,看上去是没有问题的,但是实际测试的时候发现,如下:
在这里插入图片描述
在这里插入图片描述
可以看到,答案都是不正确的,这是为什么呢?
这是因为有符号数的右移通常是算术右移,而无符号数的右移通常是逻辑右移,算术右移k位是指将二进制位向右移k位,并在左端补k个最高位;逻辑右移即是在左端补k个0。比如说一个数是1000 0001,那么它算术右移1位结果就是1100 0000,逻辑右移的结果是0100 0000。

这一点明白了,那再来解释程序中的问题,在有符号与无符号一文中说过数字默认为有符号类型的,因此这里(1<<31)后得到的依旧是有符号数,此时再右移i位,实际上进行的是算术右移,在左端补最高位,以第一个程序中a=8为例,8的二进制表示为0000……1000第一次循环时a是和10000……0000按位与,结果等于0 ,输出0;第二次循环时,由于是算术右移,此时a就和110000……0000按位与,结果和第一次依然一样;这样一直循环到第29次循环,i=28,(1<<31)>>28结果就是1111 ……11000,此时a与其按位与,得到的结果肯定就非零了,因此输出1;再继续第30次循环,i=29,此时a与1111…… 11100按位与,可以发现,此时按位与的结果依然非零,输出1;不难推出,后面的第31次循环和第32次循环,答案都是输出1,这就是为什么出现上述程序的结果。

那么怎么修改程序呢?很简单,实际上我们需要的肯定是逻辑右移了,而逻辑右移的对象必须是无符号数,因此只需要将(1<<31)强制转换为无符号形式(这里1<<31肯定是正数,因此不会改变其值)即可,如下:
在这里插入图片描述
在这里插入图片描述
可以看到,此时的答案才是正确的。

那么为什么有符号数的右移通常是算术右移,而无符号数的右移通常是逻辑右移呢?

原因就在于 要保证数据的正确性。 我们知道,移位操作在直观上来看就是扩大或缩小2的倍数,因此移位操作是肯定不能改变原有数据的正负性的。这时再来看有符号数,因为有符号数的正负性说简单一点,就是由它的最高位来决定,

如果一个有符号数正数,那当然无论对它进行算术右移还是逻辑右移都是无所谓的,因为两种右移都是在高位补0,移位数据的正确性肯定是有保证的;但是如果一个有符号数是负数,它的最高位为1,那如果对它进行逻辑右移的话,情况就不一样了,因为逻辑右移是在最高位补0,那么一旦右移,最高位补0,这个数就成了正数,直观看就是一个负数除以2的倍数后成了正数,这完全就是不符合实际的;

再来看有符号数的算术右移。当然正数就不说了,来说一下负数。假设有一个有符号负数X,它的二进制位表示必然如下(假设有30个英文字母):
在这里插入图片描述
那么就有X=-2^31+a * 2 ^30+b * 2 ^29+ …… +x * 2 ^ 2+y * 2 ^ 1+z * 2 ^ 0;
算术右移1位后的X’的二进制表示如下:
在这里插入图片描述
那么就有X’=-2^31+ 2 ^ 30 + a * 2 ^29+ b * 2 ^28+ …… +w * 2 ^ 2+x * 2 ^ 1+y * 2 ^ 0;

错位相减X-X’,得到X-X’=-2 ^ 30 +a +b +c + …… +x+y+z;

由于这里的a,b,c…x,y,z均是0或者1,因此X-X’=X’,得到X=2*X’,也就是说,算术右移1位,数值的确变为原来的一半,算术右移k位,变为原来的2^(-k),可见,算术右移保证了有符号数右移的正确性。

而对于无符号数,因为它始终是正数,因此不管是逻辑右移还是算术右移都不会改变它的正负,既然无关正负,那么就要保证数据的正确性了,显然,算术右移是无法保证数据右移的正确性的,因此无符号数必须采用逻辑右移。

补充说明

除了算术右移和逻辑右移以外,左移,实际上,左移并不分逻辑左移和算术左移,都是在最低位补充0,因为左移是会抛弃掉最高位的,这样左移后无论在最低位补什么,都是无法保证数据原来的正负,负数左移容易出现负数变正的情况,如下:
在这里插入图片描述
因此,为了保证左移的正确性,左移的对象在左移后应当保证其最高位不变,而符合这一要求的,就是所有非负数以及一部分负数,而这一部分负数并不能保证任意左移k位都能正确,唯一能保证任意左移k位都正确的负数只有-1(左移或右移k位实际上移动的位数是k与w取余的结果,w为编译器位数)。因此这一点必须要注意。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++中的移位运算符——由二进制转换程序引发的思考 的相关文章

  • 整理了27个Python人工智能库,建议收藏

    为了大家能够对人工智能常用的 Python 库有一个初步的了解 以选择能够满足自己需求的库进行学习 对目前较为常见的人工智能库进行简要全面的介绍 1 Numpy NumPy Numerical Python 是 Python的一个扩展程序库

随机推荐

  • ssd m.2接口详解

    ssd有两种接口 一种是sata 一种是m 2 这里主要深入讲解一下m 2接口的ssd 1 ssd 尺寸与规格 我们在买ssd的时候 商家都会说什么ssd是2280还是2242规格的 这里的规格实际上就是代表的ssd的大小 M 2模组的尺寸
  • pytrch手写数字识别

    使用Pytorch实现手写数字识别 目标 知道如何使用Pytorch完成神经网络的构建 知道Pytorch中激活函数的使用方法 知道Pytorch中torchvision transforms中常见图形处理函数的使用 知道如何训练模型和如何
  • Unity:利用 射线Ray 检测物体

    利用 射线Ray 检测物体 Unity射线 Ray 是通过发射一条射线来检测碰撞体或触发器 不带碰撞器组件的物体时无法检测的 可以在物理设置里取消检测触发器 Edit Project Setting Physics Physics2D Ph
  • Spring Boot中的Dozer和MapStruct比较

    Spring Boot中的Dozer和MapStruct比较 在Java开发中 数据对象之间的转换是一个常见的任务 Spring Boot作为一个流行的Java框架 提供了多种方式来处理对象之间的转换 两个常用的工具是Dozer和MapSt
  • call、apply、bind方法详解

    1 每个函数都包含两个非继承而来的方法 call 方法和apply 方法 2 相同点 这两个方法的作用是一样的 只是传参方式不一样而已 call 方法使用 window msg 1 document msg 2 var log msg 3
  • springboot 如何修改控制台输出的图案

    如图 操作步骤 1 在项目的resource文件夹下 新建一个文件命名为banner txt 2 进入网站 http patorjk com software taag p display h 0 v 0 f Big t SpringBoo
  • Spring WebSocket通信应用

    文章目录 前言 一 客户端 服务端双向通信交互图 二 项目说明 1 引入包 2 项目各模块说明 问题 参考 前言 本文章主要记录项目客户端 服务端双向通信解决方案 基于Spring WebSocket架构实现双向数据通信 以及项目实际应用中
  • 【SQLAlchemy】第二篇——连接失效及连接池

    一 背景 为了节约资源 MySQL会对建立的连接进行监控 当某些连接处于不活跃状态的时间超过一个阈值时 则关闭它们 用户可以执行show variables like wait timeout 来查看这个阈值 可以看到 在默认的情况下 这个
  • 04-8_Qt 5.9 C++开发指南_QTableWidget的使用

    文章目录 1 QTableWidget概述 2 源码 2 1 可视化UI设计 2 2 程序框架 2 3 qwintspindelegate h 2 4 qwintspindelegate cpp 2 5 mainwindow h 2 6 m
  • VisionWorks快速入门--Graph Mode

    VisionWorks快速入门 Graph Mode 从立即模式过渡到图形模式 1 创建新节点 2 向GraphModestabilizer类添加新字段和函数 3 初始化字段 4 执行算法的迭代 5 Release objects 结果 本
  • 解决科研人痛点的大突破:Zotero 6.0 版有哪些亮点?

    痛点 翻了翻日记 发现我第一次使用 Zotero 文献管理器的时间 是 2008 年 12 月 19 日 这中间曾经因为论文写作需要处理中文文献等原因 若干次切换过其他的文献管理器 包括 NoteExpress 和 Mendeley 等 几
  • IIS站点发布log

    1 启用IIS服务 2 打开IIS管理界面 3 添加站点 4 填写站点信息 1 物理路径为发布的log文件目录 2 填写IP地址和端口 5 发布的log文件设置权限 添加Everyone用户 并设置权限 6 启动站点 浏览器访问 此时出错
  • city_picker改造-------五级城市联动控件

    这几天做项目 需要用到城市控件 网上找了个样式不错的控件 基于bootstrap的 具体用法可以参照https blog csdn net bsw451926392 article details 78886965 但是我这边需要精确到村级
  • vscode的vue中出现很多红色波浪线解决办法

    vscode的vue中出现很多红色波浪线 看上去就很烦 如下所示 这么看呢 代码没什么问题 为什么有那么多红色波浪线呢 其实出现这个的原因是代码和vetur插件的格式不对应 解决办法 1 严格遵循vetur的格式去写 不过麻烦 2 关闭ve
  • 区块链系统面临哪些风险以及有哪些防范措施

    区块链是涉及多方的开放系统 早期的应用又与虚拟加密货币相关 由此使得区块链系统所内含的和面对的风险得以暴露 同时各种安全措施也得以被及时使用和验证 但区块链在真实世界的大规模应用尚未展开 因此区块链所内含以及面临的风险暴露尚不完全 预防措施
  • 【linux服务器编程学习】10.多线程编程

    linux中的线程 线程是linux中完成一个独立任务的完整执行序列 即一个可调度的实体 根据运行环境 可分为内核线程和用户线程 分别由内核和程序线程库调度 关于linux多线程编程 需要掌握怎么创建和结束线程 怎么读取和设置线程属性 线程
  • macOS中如何使用OpenGL 3.2 Core Profile

    从macOS Lion 10 7 开始 Apple支持了对OpenGL 3 2 Core Profile的支持 不过Core Profile与Compatible相比有比较大幅度的改变 从主机端的API到OpenGL接口 再到GLSL Op
  • ISP DSP的区别

    ISP 是Image Signal Processor 的简称 也就是图像信号处理器 而DSP是Digital Signal Processor 的缩写 也就是数字信号处理器 ISP一般用来处理Image Sensor 图像传感器 的输出数
  • Java 实现生产者与消费者问题

    生产者与消费者问题 问题简述 一群生产者进程在生产产品 并将这些产品提供给消费者去消费 为了使生产者进程与消费者进程能够并发进行 在两者之间设置一个具有n个缓冲区的缓冲池 生产者进程将产品放入一个缓冲区中 消费者可以从一个缓冲区取走产品去消
  • C/C++中的移位运算符——由二进制转换程序引发的思考

    以前学习移位运算符的时候并没有太多关注它 而此次关于移位运算符的探究 主要源于写的一个二进制显示的程序 include