您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
065第十一章 位运算02(新版)
发布时间:2021-05-09 21:33:23编辑:雪饮阅读()
右移
右移涉及到2进制位、补码、反码。
这里仅仅了解下负数的右移,实际上负数比正数复杂。
则实例如:
#include <stdio.h>
void main()
{
signed char a = -2;
/*
右移一个单元
二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
原码 反码 补码 右移一位 结果(原码)
-2 = 1000 0010, 1111 1101, 1111 1110, 1111 1111 -1
1111 1111(以补码存在)=>1111 1110(反码)=》1000 0001即-1
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
在机器中,数的二进制码都是其补码。
*/
a = a >> 1;
printf("%d\n\n", a);
}
则编译运行如:
D:\cproject>gcc main.c -o m
D:\cproject>m.exe
-1
题目:取一个char a从右端开始的2~5位。
其实这个题目互联网上都是你抄我的,我抄你的,它们的思路都是下面这样:
① 先使a右移2位:a >> 2
目的是使要取出的那几位移到最右端,图示:
② 设置一个低4位全为1,其余全为0的数。
~ ( ~ 0 << 4 )
③ 将上面①、②进行&运算。
(a >> 4) & ~ ( ~ 0 << 4 )
首先我们来审下题目,他的意思是一个整数的二进制位,由于这里是char a,则就是一个字符,一个字符这里以大多数情况,则为1个字节/8个位
那么这里是我个人理解分析的一部分:
这里以-2为例
-2原码:1000 0010
-2补码:1111 1101=》1111 1110
那么2-5位就是:1 111(倒数过来的)转换为十进制就是15
这样就分析了,我们手动右移2个位的结果,但是按照上面的逻辑我测试了-2,5,10等都是直接右移就能实现了和我们手动计算的一样。所以感觉上面第二和第三步骤是有点多余,不过怀疑归怀疑,谁叫这个算法是某个大佬写的,而且目前我是没有找到为什么要二三步骤的原因。
这里整理下自己的一些心得:
对于第三步,由于高4位为0(当然不一定只有4位,不考虑char类型与系统位数等,假定认为为固定8位),就相当于清除了第一步右移后的左边。因为高位全是0,0与1和0向与都是0,而对于低位全是1,而1如果对于第一步中右移后靠到最右边的数来说如果某个位为1,则相与结果还是1,如果为0则相与结果还是0,这样就保住了第一步右移后的靠右边部分。
而这里可以根据第一步右移的位数来确保从右向左开始的位数,比如向右移动2个位,则保留右边就是从2到5[右边没有移动前则指针为0,移动两个位后,指针指向2的位置(0,1,2),同里左边原来是8(长度为8,那么指针其实就是在7的位置),由于移动了两个位置后就变成了5的位置(7,6,5)]
那么如此一算,如果我们右移3个位就是保留了最后3-4.以此类推咯。
那么比如99的操作思路:
原码(补码,因为是正数):110 0011=>0110 0011,而这里要取2-5位(按下标数)就是10 00,那么我们将这个原码向右边移动两个位置就实现了1000在最右边了
向右移动两个位:0001 1000
那么现在1000在最右边了,最左边的就不需要了,那么正好通过0000与左边相与,但是又要保住右边的1000,并且按位操作,是按该位所在类型的整个所有位,而不是单独这左边4个或者右边4个,由于这里是8个位,所以就用00001111与00011000进行相与以实现左边的清除。
那么同样的这里是要实现2-5位,即2,3,4,5这四个位,所以第2步骤设置的是4个低位为1,其它位为0,那么如果是2,3,4,5,6,则要设置的就是5个低位为1了,以此类推。
那么具体实现如:
#include <stdio.h>
void main()
{
//char 一个字节8个位
char a, b, c, d;
printf("请输入待检验数字: ");
//假定输入值a为2,则正数的补码等于原码,则这里无论是补码还是原码,其结果都是0000 0010
scanf("%d", &a);
/*
>>:二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
0000 0010>>2:0000 0000
*/
b = a>>2;
/*
<<:二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。优先级5
~:按位取反,包含符号位,优先级2
~0:1111 1111
~0<<4:1111 0000(先计算按位取反,然后左移)
~(~0<<4):0000 1111
*/
c = ~(~0<<4);
//&:按位与 0与1为0 1与1为1,所以这里d就是0000 0000
d = b & c;
printf("%d\n", d);
}
编译运行结果:
D:\cproject>gcc main.c -o m
D:\cproject>m.exe
请输入待检验数字: 20
5
D:\cproject>m.exe
请输入待检验数字: 99
8
循环移位
题目:要求将a进行右循环移位
![取一个char a从右端开始的2~5位.png](/d/file/xuewuzhijing/xindebiji/47fca936b47238ef0935a4deeb5d816c.png)
根据上图可以发现,所谓循环移位其实就是高位与低位进行交换,那么换句话说就是右边到了边界,但是还要右移动,就只能再回到最左边,那么最左边原来的哪些位就被挤到了被右移的这些n个位的位置上了(相当于最左边原来的哪些位置的新位置就是其自身位置加上这个n的位置)。
那么我们带入一个具体的实数来说明一下:
(1)原码23:10111
完整原码0001 0111
(2)右边先移动到最左边去:
由于循环右移的原理,这里假如我们要循环右移动的位数为3,则这里最右边3位移动到最左边[二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。]
结果为:0001 0111=>1110 0000
(3)左边移动到右边去:
由于循环右移的原理,你右边已经是边界,你继续右边移动,相当于再次回到左边边界,那么左边原本的哪些位就要向右边移动n个单位(n为从右边过来的这些位的总为数)以便给右边移动过来的这些位留出位置。
结果为:0001 0111=>0000 0010
(4)合并左右移
那么现在左移的结果:1110 0000(不包含右边),右移动结果:0000 0010(不包含左边)
那么为了数据的完整性,实际上新的数据应该是:1110 0010
那么仔细观察发现从1110 0000与0000 0010合并为1110 0010
正好就是按位或的操作
那么接下来我们具体实例如:
#include <stdio.h>
#include <stdlib.h>
void main()
{
//无符号,都是正数
unsigned char a, b, c;
int n;
printf("请输入需要实现循环右移的数字: ");
scanf("%d", &a);
printf("请输入需要实现右移的位数: ");
scanf("%d", &n);
/*
<<:
二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
*/
b = a << ( sizeof(char)*8-n );
/*
>>:
二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
*/
c = a >> n;
/*
l:
按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。当参与运算的是负数时,参与两个数均以补码出现。
*/
c = c | b;
/*
这里原本小甲鱼的代码是printf("结果是: %c\n");也就是printf只给了格式,但没有给格式对应的参数,但是竟然能运行
我的猜测是因为%c当发现没有给提供对应参数的时候应该是会自动寻找当前程序上下文中可用的c变量吧。
对于小甲鱼的环境中我看到视频里面的确是成功运行了
但是在我这里没有成功运行
那么我就补充了这个c参数,可能是因为小甲鱼的那个环境比较老,我看视频中的好像是win7的,如今都win10了
可能有些地方不兼容,也可能是gcc版本,因为甲鱼直接在vc6++上跑的,而我喜欢用gcc在命令行里面跑
*/
printf("结果是: %c\n",c);
system("pause");
}
那么编译运行的结果如:
D:\cproject>gcc main.c -o m
D:\cproject>m.exe
请输入需要实现循环右移的数字: 23
请输入需要实现右移的位数: 3
结果是: ?
请按任意键继续. . .
这里的结果是一个问号,这是因为最终的2进制结果为1110 0010,转换为10进制就是226,而char对于10进制数的字符输出是以ascii码表中该值对应的10进制所对应的字符,而226这个字符并没有在ascii码表中找到对应的10进制对应的字符。
关键字词:循环移位,取一个char a从右端开始的2~5位
相关文章
-
无相关信息