因为疫情在家,在比较闲的时候就会到百度知道答题,自从有一个老账号(曾经没绑定手机的)丢失了以后,新号的财富值就很伤,有时候下文档不方便,最近冲了一冲。
同时,发现很多程序类的问题比较基础吧,自己也算是经历过了那个阶段,所以既然写了,就发上来做留存吧,供大家参考。
————————————————————————————————————————
本次问题是编程中按位操作的问题。位运算的效率是非常高的,不过由于现在计算机处理速度比较快,感觉日常编程时候不是很在意这方面的内容。我的环境用的C++
位操作的一般操作符包括:
操作符 | 功能 | 示例 | 结果 |
---|---|---|---|
<< | 左移操作符,是个二元的操作符。前面的操作数按照后面操作数规定的数字移动相关位数,末尾补0 | 1<<5 | 00000001->00010000 |
>> | 右移操作符,基本同上,不过负数的话,会补1,正数补0。(负数补码存储) | 99>>1 | 01100011->00110001 |
-5>>1 | 11111011->11111101 | ||
~ | 按位取返,这是个一元操作符 | ~1 | 00000001->11111110 |
| | 按位或,二元操作符,两个数同一位上,有一个是1,结果就是1 | 1|3 | 00000001|00000011 ->00000011 |
& | 按位与,二元操作符,两个数同一位上都是1,结果才是1 | 1&3 | 00000001&00000011 ->00000001 |
^ | 按位异或,二元操作符 也就是两个数同一位上不相同,结果就是1,相同,结果就是0 | 1^3 | 00000001^00000011 ->00000010 |
所以,位循环操作时候,只需要对根据上述性质进行处理即可。这篇文章主要就是对循环位位移操作的。
也就是:如果一个二进制数a=,则循环右移3位应该得到
,即本应位移丢失的数据,放到末尾或者开头。
这里直接上代码,并用实例在注释中进行了说明,请参考。核心在于,将会丢失的位提前取出来,并对应放到对的位置,通过或运算(提前保证空出来的位置都是0)将对应的位补回去。
int lrmove(int value, int n)//输入0x8000000C,和5的情况下
{
int high = 0, low = 0, length = sizeof(int) * 8, result = 0;
//因为编译器不同,为了普适我这里用了sizeof获取对当前系统的int长度
/*因为右移过程中会出现补0与补1的情况,那我们在第一次移动一格之后,保证高位变成0,以后就都可以补
0了*/
int mask = ~(1 << length - 1);//把1左移到最大位再取反,0x7FFFFFFF
n = n % length;//没必要多移动几圈,取最短就可以了,而且我下面的方法也不适用多移动几圈的
if (n == 0)
return value;
//左移肯定是补0,所以没关系,随便移动
high = value << n; //0000 0000 0000 0000 0000 0001 1000 0000=0x00000180
low = (value >> 1) & mask;//右移一位,再高位置0其他的不变,0x40000006
low = low >>(length - n - 1);//已经是正数,再右移后面的部分0x00000010
result= high | low;//0000 0000 0000 0000 0000 0001 1001 0000=0x00000190
return result;
}
int rrmove(int value, int n)//输入0x8000000C,和5的情况下
{
int high = 0, low = 0, length = sizeof(int) * 8, result = 0;
//因为编译器不同,为了普适我这里用了sizeof获取对当前系统的int长度
/*右移比较麻烦,正数补0,负数补1,所以为了让高位每次都肯定补0,
我们先右移动1位,然后把最高位变成了0,就需要我下面定义的这个mask*/
int mask = ~(1 << length - 1);//把1左移到最大位再取反,0x7FFFFFFF
n = n % length;//没必要多移动几圈,取最短就可以了,而且我下面的方法也不适用多移动几圈的
if (n == 0)
return value;
high = value << length - n;//0x60000000
low = (value >> 1)&mask;//0x40000006
low = low >> n - 1;//0x04000000
result = high | low;//0x64000000
return result;
}
那以右移动为例,按照移动的次数划分,分割点前是高位h,分割点后是低位l:
右移的目标可以看成将高低位进行互换。那么:我们通过右移动原始,可以将高位变成低位,同理,左移动,可以把低位变成高位,二者位置对应对了之后,就可以直接或操作,将两个数拼接起来了。
比如(以下都是二进制表示):
a=11001010循环右移5位
则向右移动第一次l=a>>1=11100101,这个原始数字首位是1,意味着是个负数,所以默认补1的。
我们定义一个mask=01111111,这样,当二者取&运算的时候,最高位一定会被置0,其他位则会保留原始状态
数值会变成l=l&mask=01100101
再次移动,就会默认补0了,那么已经移动了1次,再移动剩下4次,就可以了,也就是结果的低位会变成l=l>>4=00000110
对于低位,就比较简单了,如果数值长n=8,要求右移动5位,也就是低的5位会变到高位上,那我左移动n-5也就是3位,低位上的数字自然就变到高位了。
所以,我们所作的就是结果的高位h=a<<(n-5),这里的n涉及系统或者编译环境对一个数字长度的定义,可能是16位,可能是32位,因此我在程序中利用sizeof获取一下字节长度再乘以8,就得到位长度了。这里文字描述按照8位的数进行的解释,32位整数的变化参考代码注释
操作后,h=a<<3=01010000
我们已经知道,l是低位对,高位0的部分;h是高位对,低位补0的;因此,二者对应把1赋值到一起,就是结果了。
最后,result=h|l=01010110