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

 更新时间:2025年09月18日 08:34:56   作者:MzKyle  
C++中位移操作对有符号与无符号整数处理不同:无符号左移补0,右移也补0;有符号右移补符号位(算术移位),左移可能触发未定义行为,需注意类型转换与跨平台兼容性

在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位);
  • 有符号左移后符号位改变(超出表示范围)。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 一些语言的按行读取文件的代码实现小结

    一些语言的按行读取文件的代码实现小结

    这篇文章主要介绍了一些语言的按行读取文件的代码实现小结,这里罗列了Java和C语言和C++以及PHP的实现需要的朋友可以参考下
    2015-08-08
  • 利用Matlab制作三子棋游戏的示例代码

    利用Matlab制作三子棋游戏的示例代码

    三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、井字棋等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。本文将用Matlab制作这一经典游戏,感兴趣的可以试一试
    2022-03-03
  • c语言 汉诺塔算法代码

    c语言 汉诺塔算法代码

    c语言 汉诺塔算法代码,需要的朋友可以参考一下
    2013-04-04
  • windows下vscode环境c++利用matplotlibcpp绘图

    windows下vscode环境c++利用matplotlibcpp绘图

    本文主要介绍了windows下vscode环境c++利用matplotlibcpp绘图,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 使用pthread库实现openssl多线程ssl服务端和客户端

    使用pthread库实现openssl多线程ssl服务端和客户端

    使用pthread库实现openssl多线程ssl服务端和客户端,大家参考使用吧
    2014-01-01
  • C++ qt 使用jsoncpp json 读写操作

    C++ qt 使用jsoncpp json 读写操作

    JsonCpp是一个基于C++语言的开源库,用于C++程序的Json数据的读写操作,本文重点给大家介绍C++ qt 使用jsoncpp json 读写操作,感兴趣的朋友跟随小编一起看看吧
    2021-11-11
  • C++中用栈来判断括号字符串匹配问题的实现方法

    C++中用栈来判断括号字符串匹配问题的实现方法

    这篇文章主要介绍了C++中用栈来判断括号字符串匹配问题的实现方法,是一个比较实用的算法技巧,包含了关于栈的基本操作,需要的朋友可以参考下
    2014-08-08
  • C程序实现整数的素数和分解问题

    C程序实现整数的素数和分解问题

    这篇文章主要介绍了C程序实现整数的素数和分解问题,对于算法的学习有不错的借鉴价值,需要的朋友可以参考下
    2014-09-09
  • C语言 详细讲解#pragma的使用方法

    C语言 详细讲解#pragma的使用方法

    #pragma 指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的, 且对于每个编译器都是不同的
    2022-04-04
  • 详解Matlab如何绘制桑基图

    详解Matlab如何绘制桑基图

    桑基图是一种特定类型的流程图,图中延伸的分支的宽度对应数据流量的大小,通常应用于能源、材料成分、金融等数据的可视化分析。本文将用Matlab绘制好看的桑基图,需要的可以参考一下
    2022-03-03

最新评论