当前位置: 首页 > news >正文

C++---有符号和无符号整数的位移操作

在C++中,位移操作(左移<<和右移>>)是对整数二进制位的直接操作,但其行为在有符号整数(signed)无符号整数(unsigned) 中存在显著差异。这种差异源于计算机对整数的存储方式(补码)和语言标准对操作的规定,理解这些差异是编写正确位运算代码的关键。

一、位移操作的基本概念

位移操作的本质是将整数的二进制位向指定方向(左或右)移动指定的位数,空出的位由特定规则填充。在C++中,位移操作符的语法为:

  • 左移:a << n 表示将a的二进制位向左移动n位,右侧空出的位补0
  • 右移:a >> n 表示将a的二进制位向右移动n位,左侧空出的位填充规则因整数是否有符号而不同

需要注意的是:

  • 位移的位数n必须是非负整数,且不能大于等于操作数的位数(如32位整数位移32位及以上属于未定义行为);
  • 位移操作的结果类型与左操作数的类型一致(如int位移后仍为int)。

二、无符号整数(unsigned)的位移:逻辑移位

无符号整数的位移是逻辑移位(Logical Shift),即不考虑符号位,仅根据“空位补0”的规则处理,行为在C++标准中是完全明确的。

1. 无符号左移(unsigned << n

左移时,二进制位整体向左移动n位,右侧空出的n位全部补0,左侧超出类型位数的高位被直接丢弃。

举例:假设unsigned int为32位,分析unsigned int a = 0x0000000F(二进制00000000 00000000 00000000 00001111)的左移:

  • a << 1:左移1位后,右侧补0,结果为0x0000001E(二进制00000000 00000000 00000000 00011110);
  • a << 4:左移4位后,右侧补4个0,结果为0x000000F0(二进制00000000 00000000 00000000 11110000);
  • a << 28:左移28位后,高位超出32位的部分被丢弃,结果为0xF0000000(二进制11110000 00000000 00000000 00000000)。

规律:无符号左移n位等价于“a * 2^n”(若结果未超出类型范围)。

2. 无符号右移(unsigned >> n

右移时,二进制位整体向右移动n位,左侧空出的n位全部补0,右侧超出的n位被直接丢弃。

举例:仍以32位unsigned int a = 0xF0000000(二进制11110000 00000000 00000000 00000000)为例:

  • a >> 1:右移1位后,左侧补0,结果为0x78000000(二进制01111000 00000000 00000000 00000000);
  • a >> 4:右移4位后,左侧补4个0,结果为0x0F000000(二进制00001111 00000000 00000000 00000000);
  • a >> 28:右移28位后,左侧补28个0,结果为0x0000000F(二进制00000000 00000000 00000000 00001111)。

规律:无符号右移n位等价于“a / 2^n”(向下取整)。

三、有符号整数(signed)的位移:算术移位为主

有符号整数(如intlong long)在内存中以补码形式存储(最高位为符号位:0表示正数,1表示负数)。其位移行为与无符号不同,尤其是右移,C++标准将其定义为“实现定义”(implementation-defined),但主流编译器(如GCC、Clang、MSVC)均采用算术移位(Arithmetic Shift)规则。

1. 有符号左移(signed << n

有符号左移的行为与无符号左移基本一致:二进制位向左移动n位,右侧空出的n位补0,左侧超出类型位数的高位(包括符号位)被丢弃。

注意:若左移后符号位发生变化(如正数左移后符号位变为1),结果可能超出该类型能表示的范围,此时属于未定义行为(undefined behavior)。

举例:32位int(范围-2^31 ~ 2^31-1):

  • 正数左移:int a = 0x0000000F(15),a << 1 结果为0x0000001E(30),符号位仍为0(合法);
  • 负数左移:int b = -0x0000000F(-15,补码0xFFFFFFF1),b << 1 结果为0xFFFFFFE2(-30),符号位仍为1(合法);
  • 未定义行为:int c = 0x40000000(1073741824),c << 1 结果为0x80000000(-2147483648),符号位从0变为1,属于未定义行为(不同编译器可能有差异)。
2. 有符号右移(signed >> n

有符号右移是最容易产生差异的操作。主流编译器采用算术移位:右侧超出的n位被丢弃,左侧空出的n位补符号位(正数补0,负数补1)。

这种规则的目的是保持位移后数值的“符号一致性”,尤其对负数而言,右移后仍为负数。

举例1:正数右移
int a = 0x0000000F(15,二进制00000000 00000000 00000000 00001111):

  • 符号位为0,右移时左侧补0;
  • a >> 1:结果为0x00000007(7,二进制00000000 00000000 00000000 00000111);
  • a >> 4:结果为0x00000000(0,二进制00000000 00000000 00000000 00000000)。

举例2:负数右移
int b = -0x0000000F(-15,补码0xFFFFFFF1,二进制11111111 11111111 11111111 11110001):

  • 符号位为1,右移时左侧补1;
  • b >> 1:右移1位后,左侧补1,结果为0xFFFFFFF8(-8,二进制11111111 11111111 11111111 11111000);
  • b >> 4:右移4位后,左侧补4个1,结果为0xFFFFFFF0(-16,二进制11111111 11111111 11111111 11110000)。

规律:有符号右移n位对正数等价于“a / 2^n”(向零舍入);对负数等价于“a / 2^n”(向下取整,如-15 >> 1 = -8,而-15 / 2 = -7)。

四、有符号与无符号位移的核心差异对比

操作类型无符号整数(unsigned)有符号整数(signed)
左移(<<)逻辑移位,右侧补0,高位丢弃逻辑移位(同无符号),但可能触发未定义行为
右移(>>)=逻辑移位,左侧补0,低位丢弃算术移位(主流编译器),左侧补符号位
符号影响无符号位,位移后仍为非负符号位不变(算术移位),负数位移后仍为负
应用场景位运算(如哈希、编码)带符号的数值计算(如除法近似)

五、实际开发中的注意事项

  1. 避免对有符号整数进行右移依赖
    由于有符号右移是“实现定义”,若代码需要跨编译器兼容,应避免依赖其行为。如需逻辑右移,可先转换为无符号类型(如(unsigned int)a >> n)。

  2. 负数转换为无符号的妙用
    如之前的toHex函数中,负数转换为unsigned int后,右移变为逻辑移位(高位补0),可正常处理补码的所有位,避免无限循环(若用有符号右移,负数会因补1而永远不为0)。

  3. 警惕位移后的未定义行为

    • 位移位数为负或大于等于类型位数(如32位int移32位);
    • 有符号左移后符号位改变(超出表示范围)。
http://www.lryc.cn/news/625036.html

相关文章:

  • RabbitMQ:数据隔离
  • kafka 冲突解决 kafka安装
  • Unity进阶--C#补充知识点--【Unity跨平台的原理】Mono与IL2CPP
  • 探索性测试:灵活找Bug的“人肉探测仪”
  • MongoDB Windows 系统实战手册:从配置到数据处理入门
  • keil错误:Error: failed to execute ‘D:\Keil\C51\BIN\BIN\A51.EXE‘
  • 【智慧工地源码】智慧工地云平台系统,涵盖安全、质量、环境、人员和设备五大管理模块,实现实时监控、智能预警和数据分析。
  • PYTHON让繁琐的工作自动化-猜数字游戏
  • 从数据汇总到高级分析,SQL 查询进阶实战(下篇)—— 分组、子查询与窗口函数全攻略
  • 车e估牵头正式启动乘用车金融价值评估师编制
  • CoRL 2025|隐空间扩散世界模型LaDi-WM大幅提升机器人操作策略的成功率和跨场景泛化能力
  • 从「行走」到「思考」:机器人进化之路与感知—决策链路的工程化实践
  • 第4.3节:awk正则表达式详解-特殊字符
  • Pytest测试框架基础及进阶
  • 前端css学习笔记7:各种居中布局空白问题
  • Jenkins全链路教程——Jenkins调用Maven构建项目
  • IoT/透过oc_lwm2m和at源码,分析NB-IoT通信模组和主板MCU之间的通信过程
  • 【Jenkins】03 - 自动构建和docker构建
  • 【opencv-Python学习笔记(7):图像平滑处理】
  • 删除并获得点数
  • label studio标注时序数据
  • 力扣热题100------19.删除链表的倒数第N个结点
  • 深度学习篇---卷积
  • Linux unistd.h 包含功能
  • Spring 三级缓存:破解循环依赖的底层密码
  • 使用Idea安装JDK
  • [Code Analysis] docs | Web应用前端
  • 计算机视觉(9)-实践中遇到的问题(六路相机模型采集训练部署全流程)
  • OpenTelemetry、Jaeger 与 Zipkin:分布式链路追踪方案对比与实践
  • 大模型的底层运算线性代数