C/C++刁钻问题各个击破之位运算及其应用实例(1)

2023-05-16

位运算及其应用实例(1)

摘要

位运算是C/C++中的基本运算之一,即便是这样,它对大多数程序员来说是一个比较陌生的运算——大多数程序员很少使用位运算。本篇先简要介绍基本的位运算操作符及其用法(何时使用),然后介绍位运算符的几个典型应用:

(1)      三种不用临时变量交换两个整数的实例,并分析每个实例的优缺点

(2)      进制转换,通过位运算实现将十进制数按二进制和十六进制输出,并得出一个通用的,用于将十进制按照2的n次方进制输出的程序。

(3)      给出利用位运算实现的计算整数的二进制表示中有多少个1的实例。

揭开位运算的面纱

所有数据在计算机底层都是按二进制存储的,一个数据可以看做是一个有序的位集合。每一位只有两种状态:0或1。位运算允许程序员操作数据的某一特定位,比如将某位设置为1(或0),查询某位的状态(1,或0)。位运算由位运算操作符和操作数组成,不同的位运算操作符定义了不同的位运算,下面的表格是对每种位运算操作符及其对应的位运算和功能进行描述:

位运算操作符

对应的位运算

用法

功能描述

~

按位非

~expr

翻转expr的每一个位:1变0,0变1

<< 

左移

expr<<n

将expr向左移动n位,移到外面的被丢弃,右边的位补0,因此左移n位相当于乘以2n

>> 

右移

expr>>n

将expr向右移n位,移到外面的被丢弃,如果expr是无符号类型,则左边补0,否则,左边插入符号位的拷贝或者0(视具体实现而定)。

&

按位与

expr1&expr2

在每个位所在处,如果expr1和expr2都含有1,那么结果该位为1,否则为0。

|

按位或

Expr1 | expr2

在每个位所在处,如果expr1和expr2都含有0,那么结果该位为0,否则为1。

^

按位异或

Expr1 ^ expr2

在每个位所在处,如果expr1和expr2不相同,那么结果该位为1,否则为0.

除了上面的基本位运算操作符外,还有&=,^=,|=,<<=,>>=等组合符号,它们分别是:按位与赋值,按位异或赋值,按位或赋值,左移赋值,右移赋值。接下来介绍如何实现位操作:

1.将expr的第n(n从0开始)位设置为1:        expr |= (1<<n);

2.将expr的第n(n从0开始)位设置为0:    expr &= (~(1<<n));

3.判断expr的第n(n从0开始)位是否为1:bool b =expr & (1<<n);

4.翻转expr的第n(n从0开始)位:expr ^=(1<<n);

注意

1.      C标准提供了bitset来进行各种位操作,可以在MSDN中输入bitset了解相关内容,使用时需要包含头文件:#include”bitset”。

2.      位运算只能用于操作有整数类型的数,比如说char,short,int,long等(包含signed 和unsigned),不能操作浮点数,比如float,double!std::bitset的构造函数的参数是unsigned long int,尽量不要对负数进行为操作,因为可移植性差,不同的系统平台对负数的右移操作定义不一样(大多数平台规定高位补符号位,有些平台规定高位补0)。

位运算应用实例1:不用任何中间变量,交换两个整数

这个问题是比较经典的了,你可以很容易地在网上找到多种答案,我在这里给出两个方案:

方案1:用算术运算实现(一个不完美的方案)

该方案的思路简单,实现代码很短,如下:

Template<class T>
Void mySwap_1(T& a, T& b)
{
         a = a+b;
         b = a -b;
         a = a-b;
}

简单吧,但是我还要简单说一下:第一句a=a+b;是用a保存原来的a跟原的b的和;第二句b = a-b;使得原来的a的值被保存到了b里面;最后一句a=a-b;使得原来的b的值保存到了a里面。

我们说这个方法是不那么完美的,原因在于算术运算可能会出现结果溢出的问题,假如a,b都非常大,那么第一句a=a+b就会导致结果溢出,比如说原来的a = 2147483647,b = 2,那么a+b就为2147483649,这个数大于了最大的无符号整数2147483648,因此发生溢出,a中保存的结果实际上是:-2147483647,但是让人惊讶的是:虽然第一句程序得到的结果为-2147483647,后面两句得到的结果却是正确的,即能实现交换原始a,b的值,也就是说:只有第一句的结果是错误的,但最后的结果却是正确的,这一点让我很迷惑,至今还没弄清楚缘由,再次向各位求教!

最后,谈谈这种方法相对于后面的方案2的优点:该方法可以用于交换两个非整数(浮点数),而方案2基于位运算,而对浮点数不能直接使用位运算,因此方案2不能用于交换两个浮点数!

方案2:用位运算实现(较好的方案)

         该方案代码与方案1及其相似,思路也不难,先看代码,然后再看我啰嗦的剖析:

template<class T>
void mySwap_2(T& a,T& b)
{
    a = a^b;
    b = b^a;
    a = a^b;
}

对于编程老手来说,这个交换函数并不陌生,但我相信这些编程老手之中有一部分人只记得这么写代码,而不知道三句代码为何这么写,事实上我最初也是这样,因此一开始我就觉得短短3行代码,让我花费时间去理解分析,还不如直接记忆来得划算。事实上,直到今天我写这篇文章时,我舍得消耗一点脑细胞来理解它,下面我尝试着对上述三句代码进行阐述,为了方便,假设数据类型为char,并且a = 5,b=3;那么在内存中a,b存储如下:

a:

0

0

0

0

0

1

0

1

 

b:

0

0

0

0

0

0

1

1

 

接下来详细分析每一句:

首先来看第一句:a=a^b;执行该语句后a中保存了a与b的差异位,也就是说如果原来的a和b的某一位不同,那么就将a的该位置为1,因此a在内存中成了如下图的样子,它说明a与b的第2,3个bit有差异:

a:

0

0

0

0

0

1

1

0

 

接着我们来看第二句:b=b^a;其意思是,将b中有差异的位翻转,如此一来b中保存的值其实就等于原来a中的值,记住当第二个语句执行完之后a仍然保存了原来的a,b的差异信息,而b则变成了原来的a!

最后我们来看第三句:a=a^b;由于异或运算满足交换律,因此这一句等价于:a=b^a;记住这个语句赋值号右边的b中已经保存了原始的a值,而a中保存了原始的a,b的差异,因此这一句的最终作用是将原始a中有差异的位翻转(变成b)然后赋值给a,如此一来a中就保存了原始的b值。

总结:上述三句中:第一句是记录差异,第2,3句是翻转,最终实现了不用任何中间变量就交换两个变量的值。

分析:位运算不考虑进位问题,因此不会有结果溢出的问题!但是由于不能对浮点数进行直接位运算,因此该方法不能实现交换两个浮点数!当然原题题目是交换两个整数。

备注:还有其他实现两个数交换的方法,比如采用内存拷贝!由于不属于位运算范畴,这里就不赘述了。

 

位运算应用实例2:进制转换

要求:分别实现十进制整数按二进制、十六进制输出。

两种方法实现按二进制输出:

方法1:由于整数在计算机中是按二进制存储的,我们只需要将其每个bit按顺序打印出来即可,如果某位为1,则打印字符‘1’,否则打印字符‘0’。我给出的代码如下:

voidprintBinary(int num)
{  
    for(int i=0;i<32;i++)
    {
       cout<<((num>>(31-i))&1);
        //cout<<( (num &(1<<(31-i))) ==0? 0 : 1 );
    }
}

其中被注释掉的那个cout与没注释的cout有同样的功能!这个函数的思路很简单,就是从高到底逐位打印每个bit。我上面的代码有一点不好的地方,那就是语句太复杂,一个cout语句干了太多的事情,如果影响您的理解,那么你可以增加几个临时变量,然后把它拆分成多个简单语句。我这么写主要是考虑到篇幅的原因,因此程序段太占篇幅了。随便说一句,编程时,语句力求简单明了:一行只写一条语句,一条语句只干一件事情!

方法二:利用bitset来实现

bitset是标准库提供的一个类(不是容器),利用它就可以很方便地操作位,下面是用bitset来实现的程序:

voidprintBinary(int num)
{ 
   bitset<32> bits =bitset<32>((unsigned long)(num));
    for(int i=31;i>=0;i--)
    {
        cout<<(bits[i]==true? '1' : '0');
    }   
}

备注:关于bitset重载了多个运算符,其中包含下标运算符:[],可以方便地取得某一个bit,看它是否为1。关于bitset的更多信息请查阅msdn或者其他资料,你只要记住bitset是标准库提供的,你可以随时使用,不要忘记添加相应的头文件。

实现按16进制输出:

同样由于数据在内存中是按二进制存储的,因此将整数按照16进制输出我们可以如下做:从左向右,每4位bit一组,组合成一个十六进制数,一次输出即可,其程序如下:

void printHex(int num)
{
    for(inti=28;i>=0;i-=4)
    {
        int temp =num>>i;
        temp =temp&15;  //15是掩码!
        char ch;
       temp>9?(ch ='A'+temp-10):(ch = '0'+temp);
       cout<<ch;
    }   
}   

该程序与上面的printBinary函数非常相似,要注意的是i每次变化4,最关键点在于语句temp= temp&15;由于是16进制,因此这里用15做掩码。我想有了printBinary做铺垫,理解这个printHex并不难,这里不赘述了。接下来我将对这两个函数进行个小小的扩展:实现整数按2n (2的n次方)进制输出!比如按8进制,32进制等。为了方便描述,我们限制1<=n<=6;并用字符’0’到’9’表示数字0到9,用字符A,B,……Z,a,b,……表示数字10到63。程序如下:

void print2powerN(int num,int N)
{
    for(inti=32-N;i>=0;i-=N)
    {
        int temp =num>>i;
        temp =temp&((1<<N)-1);
        char ch;
       if(temp<=9)
        {
            ch ='0'+temp;
        }
        elseif(temp<=35)
        {
            ch ='A'+temp-10;
        }
        else
        {
            ch = 'a'+temp - 36;
        }           
       cout<<ch;
    }
}

备注:用位运算也能实现十进制到任意进制的转换,这个问题比较难,我暂时还没弄透彻!

 

位运算案例3:求整数的二进制表示中1的个数

问题描述:输入一个整数N要求输出其二进制表示中1的个数M,比如N=13,则M=3;

分析:该问题的求解方法不止一种,可以对二进制表示的每一位逐位扫描来实现,这种方法的复杂度是o(n)其中n是N的二进制表示的总位数。这里介绍如何用位操作来求解,并且保证其复杂度低于o(n),事实上该方法的复杂度为o(m),其中m是N的二进制标识中1的个数!

思路:在讲述具体实现时,来看这样一个事实:n&(n-1)能实现将最低位的1翻转!比如说n=108,其二进制表示为01101100,则n&(n-1)的结果是01101000。因此只要不停地翻转n的二进制的最低位的1,每翻转一次让计数器+1,直到n等于0时,计数器中就记录了n的二进制中1的位数,程序如下:

int count1Bits(long n)
{
    int count =0;
    while(n)
    {
        count++;
       n&=(n-1);
    }
    return count;
}

注:关于该问题的其他方法,请参考《编程之美》。n&(n-1)的另外一个用处是判断n是否是2的整数次幂。

到此,本文该结束了,我发现最近我每次写的东西可以用一句谚语来形容:懒婆娘的裹脚——又臭又长!没办法,每次一想写东西,就思维很发散(或许用凌乱来形容更加贴切),看来以后得把大的专题篇总结了。还有,由于最近天气奇热,心情浮躁,头脑发昏,文中的代码有考虑欠佳的地方,还请各位批评指正。

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

C/C++刁钻问题各个击破之位运算及其应用实例(1) 的相关文章

  • 管道过滤器模式(Pipe and Filter)与组合模式(出处:http://haolloyin.blog.51cto.com/1177454/348277)

    之前在 benjielin 前辈的博客中看到 管道过滤器 xff08 Pipe And Filter xff09 模式 xff08 http bj007 blog 51cto com 1701577 345677 xff09 xff0c 当
  • 管道过滤器(Pipe-And-Filter)模式(出处:http://bj007.blog.51cto.com/1701577/345677)

    按照 POSA 面向模式的软件架构 里的说法 xff0c 管道过滤器 xff08 Pipe And Filter xff09 应该属于架构模式 xff0c 因为它通常决定了一个系统的基本架构 管道过滤器和生产流水线类似 xff0c 在生产流
  • 数据库架构的演变

    最近看了很多公司架构的演变的文章 xff0c 发现其中的基本思路和架构演变都很类似 xff0c 这里也总结一下数据库架构的演变以及演变背后的思路 单主机 最开始网站一般都是由典型的LAMP架构演变而来的 xff0c 一般都是一台linux主
  • 如何下载Android源代码

    Android已经火了很长时间了 xff0c 虽然做手机开发也有两年了 xff0c 但是一直没有深入接触到Android 前些天想下载Android源代码来看看 xff0c 没想到http android git kernel org九月初
  • Web数据库

    http baike baidu com link url 61 Tib3flBuOBsLy4IoMAxXt2z36Ms77 mQe85MBq7kJh0XfG7oluhlEinX3Maomb2mboXIcedxDEWvGPIDtNQfxa
  • 大型网站系统架构的演化

    前言 一个成熟的大型网站 xff08 如淘宝 京东等 xff09 的系统架构并不是开始设计就具备完整的高性能 高可用 安全等特性 xff0c 它总是随着用户量的增加 xff0c 业务功能的扩展逐渐演变完善的 xff0c 在这个过程中 xff
  • 架构设计案例分析-高速公路收费运营管理平台

    本文旨在通过对某省高速公路联网收费运营管理平台的架构设计过程进行案例分析 xff0c 描述架构设计的决策过程 1 业务背景 某省的高速公路分为近百个路段 xff0c 不同的路段归属不同的公司建设与运营 xff0c 造成了车辆在跨越不同路段时
  • 图片服务架构演进

    现在几乎任何一个网站 Web App以及移动APP等应用都需要有图片展示的功能 xff0c 对于图片功能从下至上都是很重要的 必须要具有前瞻性的规划好图片服务器 xff0c 图片的上传和下载速度至关重要 xff0c 当然这并不是说一上来就搞
  • 应用系统架构设计

    我们在做着表面上看似是对于各种不同应用的开发 xff0c 其实背后所对应的架构设计都是相对稳定的 在一个好的架构下编程 xff0c 不仅对于开发人员是一件赏心悦目的事情 xff0c 更重要的是软件能够表现出一个健康的姿态 xff1b 而架构
  • 用三层架构与设计模式思想部署企业级数据库业务系统开发

    1 1关于架构 架构这个词从它的出现后 就有许许多多的程序员 架构师们激烈地讨论着它的发展 xff0c 但是架构一词的出现 xff0c 却是随着三层架构的出现才出现的 当然 xff0c 目前应用三层架构开发也正是业界最关注的主题 那么这里我
  • KickStart安装教程

    KickStart安装教程 PXE概念介绍 xff1a PXE技术与RPL技术不同之处为RPL是静态路由 xff0c PXE是动态路由 RPL是根据网卡上的ID号加上其他记录组成的一个Frame xff08 帧 xff09 向服务器发出请求
  • DHCP的基本实现原理

    DHCP是一个局域网的网络协议 xff0c 使用UDP协议工作 xff0c 主要有两个用途 xff1a 给内部网络或网络服务供应商自动分配IP地址 xff0c 给用户或者内部网络管理员作为对所有计算机作中央管理的手段 xff0c 在RFC
  • 详解Windows PE(Windows预安装环境)

    Windows PE Windows PreInstallation Environment Windows PE 直接从字面上翻译就是 Windows预安装环境 xff0c 微软在2002年7月22日发布 xff0c 它的原文解释是 xf
  • Kickstart的高级应用

    Pre 和Postinstall 脚本 kickstart本身提供了一些对系统的基本调整和设置 xff0c 例如设置root密码 xff0c 设置时区等等 但是它不能做某些更细致的调整 xff0c 比如通过chkconfig禁止某些服务 x
  • SMTP错误码/建议解决方法

    SMTP错误码 建议解决方法 错误总表 420 1 Timeout Communication Problem Encountered During Transmission Thie Is a Novell Groupwise Smtp
  • Kickstart+NFS+DHCP+TFTP+PXElinux实现CentOS的网络自动安装

    Linux Kickstart无人值守安装 一 基本原理 PXE Pre boot Execution Environment 是由Intel设计的协议 xff0c 它可以使计算机通过网络启动 协议分为client和server两端 xff
  • 流程制造行业信息系统 架构

    流程制造行业信息系统 架构 执笔人 xff1a 郑玉堂 一 流程制造业信息技术应用的重要性 经济全球化趋势已经给各国经济发展带来越来越深刻的影响 xff0c 各国制造企业在世界市场上进行着日益激烈和残酷的竞争 剧烈的和不可测的市场环境变化
  • OpenGL纹理映射的几个问题

    今天在绘制颜色表的时候 xff0c 出现两个小问题 xff1a 目的是根据一个特定的颜色表 xff0c 用图像方式将颜色表绘制出来 xff0c 根据给定的颜色表 xff0c 我应该绘制出如下的图像才对 xff1a 1 我的颜色表绘制出来的图
  • 将自己写的经常复用的类封装成dll/lib的方法

    如果你的工作长期与某个领域相关 xff0c 比如说长期做直接体绘制 DVR 方面的开发 xff0c 那么你可能经常使用自己的传递函数类 xff0c 如果每一个工程你都把传递函数类的 h和 cpp文件添加进去会比较麻烦 xff0c 其实 xf
  • 从体数据分割谈解决问题之方法

    从体数据分割谈解决问题之方法 一 艰辛历程 xff1a 由于最近在做基于分割信息的体数据可视化时需要得到体数据的分割信息 每个体素的标识数据 xff0c 标识了每个体素属于什么组织 xff0c 为了得到体数据的分割信息我费了不找周折 下面是

随机推荐

  • 求大数的阶乘的位数:PKU :1423:Big Number

    题目描述 xff1a Description In many applications very large integers numbers are required Some of these applications are usin
  • 某人的ACM经历

    ACM经历总结 转帖 首先 xff0c 我想说的就是 xff0c 我是一个很普通的ACMer xff0c 高中没有参加过任何计算机和数学竞赛的经历 xff0c 也没有ben那样过人的天资 xff0c 努力至今也未能取得什么成绩 xff0c
  • PKU2051(优先队列求法)

    题意参见 xff1a http acm pku edu cn JudgeOnline problem id 61 2051 其实这个题目可以理解成os上的进程调度 xff1a 有一些进程 xff0c 每个进程有一个唯一的id和一个执行周期
  • 花钱要区分“投资”行为或“消费”行为(转载)

    在著名的美国第一学府哈佛大学 xff0c 经济学的第一堂课 xff0c 只教二个概念 第一概念 xff1a 花钱要区分 投资 行为或 消费 行为 xff1b 第二概念 xff1a 每月先储蓄30 工资 xff0c 剩下来才消费 有关专家做了
  • 时间管理也要区分“投资行为”与“消费行为”(转载)

    10 年前甲和乙是本科的同学 xff0c 在社会工作5 年后 xff0c 不约而同积蓄了30 万元人民币 5 年前 xff0c 他们都花掉了这30 万元 甲去通州购买了一套房 乙去买了一辆 奥迪 5 年后的今天 xff1a 甲的房子 xff
  • ZooKeeper的选举机制详解

    1 xff09 半数机制 xff1a 集群中半数以上机器存活 xff0c 集群可用 所以 Z ookeeper适合安装奇数台服务器 2 xff09 Zookeeper虽然在配置文件中并没有指定M aster和 S lave 但是 xff0c
  • 写了个幷查集的模板类...

    下面的概念介绍主要参考了 xff1a http www cnblogs com cherish yimi archive 2009 10 11 1580839 html xff0c 根据这个介绍 xff0c 自己写了个稍微通用一点的模板 x
  • PKU_1611(幷查集解法)

    题目来源 xff1a http poj org problem id 61 1611 采用幷查集的思路 xff0c 将同一个组的的学生合并为一个集合 xff0c 最后看那些学生跟student0属于同一个集合即可 下面是我的AC代码 xff
  • POJ2524(并查集)

    题目来源 xff1a http poj org problem id 61 2524 Description There are so many different religions in the world today that it
  • POJ_2236(并查集)

    题目 xff1a Description An earthquake takes place in Southeast Asia The ACM Asia Cooperated Medical team have set up a wire
  • 技术管理中的几个问题

    前几天跟朋友聊天时 xff0c 朋友说他刚刚从一家知名软件公司面试出来 xff0c 朋友去面试的是一家公司的技术管理岗位 xff0c 所以在面试的时候被问及的问题也偏重于技术管理方面的问题 xff0c 在与朋友的聊天中将这几个问题归纳了一下
  • 业务建模

    业务建模是OOAD的重要组成部分 xff0c 简单的说 xff0c 业务建模就对业务领域问题进行结构化的描述 这个描述将会直接指导最终生成的软件 xff0c 业务模型是否具有扩展性 xff0c 业务模型是否能够正确的反映需求 xff0c 都
  • 读书笔记之《软件工程思想》

    读书笔记 xff1a 林锐博士的 软件工程思想 首先申明 xff1a 由于才疏学浅 xff0c 很多感悟或许是不准确的 xff0c 甚至是错误的 但是我仍然坚持写下这篇读书心得 xff0c 原因有二 xff1a 首先 xff0c 想向大家推
  • C++开发常用工具(开发,辅助,编辑,建模,版本控制等)

    开发环境 xff1e Turbo c DOS时代c语言开发的经典工具 xff0c 目前适合两类人使用 xff1a c语言beginner xff08 尤其是学生一族 xff09 xff0c 具有怀旧情节的专业人士 xff1a xff09 x
  • 在Linux和Windows下搭建CVS服务器与CVS客户端的详细配置指南

    此文虽看上去写的很详细 xff0c 但有些地方却还模糊 xff0c 该简洁的地方没有简洁 xff0c 读者很容易迷糊 再留下一个ubuntu系统下配置cvs的文章 xff0c 比较简洁 http www yuanma org data 20
  • 高质量编程之编译警告级别

    前 言 作为程序员不但要会编程 xff0c 还要编好程 xff0c 即编写高质量的程序 评价程序质量的指标有很多 正确性 可靠性 有效性 可扩展性 可维护性 xff0c 用于保证软件质量的方法和技巧也非常多 本篇只讲述在编码阶段 xff0c
  • SSO - CAS-5.3.x服务端一些常规配置(登出操作后跳转制定页面;增加多个用户名密码)

    登出操作后跳转制定页面 首先跳转cas登出url时 xff0c 要加上 service 61 你的制定的页面 xff0c 如下 xff1a http localhost 8080 cas logout service 61 https ww
  • 大 学 十 年

    大 学 十 年 林锐 xff0c 1999年岁末 写此文使我很为难 xff0c 一是担心读者误以为我轻浮得现在就开始写自传 xff0c 二是担心朋友们误以为我得了绝症而早早留下遗作 不论是落俗套还是不落俗套地评价 xff0c 我在大学十年里
  • C/C++刁钻问题各个击破之序言

    是程序员都会写C C 43 43 程序 这是不是就说明C C 43 43 比较容易掌握呢 xff1f 非也 xff01 相比其他编程语言来说C C 43 43 要庞大得多 复杂得多 xff0c 要想用好C C 43 43 不是易事 我用C编
  • C/C++刁钻问题各个击破之位运算及其应用实例(1)

    位运算及其应用实例 1 摘要 位运算是C C 43 43 中的基本运算之一 xff0c 即便是这样 xff0c 它对大多数程序员来说是一个比较陌生的运算 大多数程序员很少使用位运算 本篇先简要介绍基本的位运算操作符及其用法 何时使用 xff