文章目录
前言
简单介绍一下C语言中的两种操作符,移位操作符和位操作符。同时,为了加深理解,更好的运用,也会简单介绍一下相关的练习题。
一、移位操作符和位操作符的简单介绍
1.移位操作符
移位操作符有两种分别是左移和右移。左移用<<来表示,右移用>>来表示。
规定:移位操作数都是整数,移动的位数是正整数
左移的意思是把某整数的二进制位向左移动一定的位数然后补零。
如下示例:int a=1 ; a<<1;即为将a左移移1位
因为整数在内存中都是以补码的形式存储的,所以移动的二进制位都是移动对应的补码位。
1的补码是00000000 00000000 00000000 00000001
左移1位后:00000000 00000000 00000000 00000010
即为1向左移动1位,然后空出来的位置补上0
右移有两种移动方式 逻辑右移和算术右移
逻辑右移
左边用0填充,右边丢弃
如下示例:int a =-1;-1>>1;即为右移1位
-1的补码是:11111111 11111111 11111111 11111111
逻辑右移1位后:011111111 111111111 11111111 11111111
算术右移
左边用原该值的符号位填充,右边丢弃
算术右移后 111111111 11111111 11111111 111111111
图中左移 右移符号画错了应该是<< >>
2.位操作符
按位与 &
两数相与就是其对应二进制位同时是1为1,否则为0
如下示例
1 & 3
1的补码:00000000 00000000 00000000 00000001
3的补码:00000000 00000000 00000000 00000011
相与后 :00000000 00000000 00000000 00000001
相与的结果是:1
按位或 |
两数相或就是其对应的二进制位只要有1就是1,同时是0才为0
1 | 3
1的补码:00000000 00000000 00000000 00000001
3的补码:00000000 00000000 00000000 00000011
相或后 : 00000000 00000000 00000000 00000011
相或后的结果是 :3
按位异或 ^
两数按位异或就是相同为0 不同为 1
1^3
1的补码:00000000 00000000 00000000 00000001
3的补码:00000000 00000000 00000000 00000011
异或后 : 00000000 00000000 00000000 00000010
异或后的结果是 2
二、简单的练习
1.输入一个整数求其对应32位二进制(负数以补码形式)1的个数
大致思路:
以123这个10进制数为这例 要想得到每一个数字,可以这样做
123%10 =3;123/10=12 12%10 =2; 12/10=1 1%10=1
这样 1 2 3 便得到了
那么通过类比 想得到二进制的1 也可以这样做
以 5的二进制为例 000000000 0000000000 000000000 00000101
有2个1
5%2 =1; 00000000 00000000 00000000 00000001
5/2=2 ; 00000000 00000000 00000000 00000010
2%2=0 ;0000000 00000000 00000000 00000000
可以看到每次 除 余计算后的结果相当于都是将5中的1类似于撕玉米叶一样一层层剥开,然后只需对出现的1计数即可。于是便可以很快写出如下代码
代码示例
#include<stdio.h>
int Number(int n)
{
int count = 0;//用于计数统计1的个数
while (n)
{
if (n % 2 == 1)//判断是否有1
{
count++;
}
n /= 2;//更新n
}
return count;
}
int main()
{
int n =0;
scanf("%d", &n);
int ret =Number(n);
printf("%d", ret);
return 0;
}
但是这个代码有个bug,当n为负数的时候就行不通了。为了解决这个问题,可以将函数形参改为无符号整型
那么有没有什么不用改形参的办法呢?
可以这样思考,整数int型是32位的,如果将32位的整数每一位和1相与看看是不是等于1,然后计数,就可以得到该数二进制中1的个数.
基于这样的思考,可以写出如下代码
#include<stdio.h>
int Number(int n)
{
int count = 0;
for (int i = 0; i < 32; i++)
{
if ((n >> i)&1==1)//其实==1可以不写,因为结果除了1就是0
//之所以右移,因为1中的1是在最低位上
{
count++;
}
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret =Number(n);
printf("%d", ret);
return 0;
}
那么还有没有更好的方法,不妨来看一下这样一个表达式n=n&(n-1)
举个例子吧 以10为例
10的二进制是 00000000 00000000 00000000 00001010
所以只需要通过计数变成0的次数就行了
于是便可以写下如下代码
#include<stdio.h>
int Number(int n)
{
int count = 0;
while (n )
{
n = n & (n - 1);
count++;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret =Number(n);
printf("%d", ret);
return 0;
}
其实写到这里还有一个有趣的事情,可以看出n&(n-1)其实是消1操作,那如果判断一个数是不是2的n次方(n>=0),是不是很好写出代码,因为2的n次方只有一个1,只需要判断n&(n-1)是否==0,通过这种运算可以消掉2的n次方中仅有的一个1。
2.判断两个整数对应位置有多少相同的二进制位数
大致思路
因为二进制位除了1就是0,那么我们可以将两个数的每个二进制位与1相与,判断是否相等,如果相等就计数,那么便可以写出如下代码
#include<stdio.h>
int Number(int n,int m)
{
int count = 0;
for (int i = 0; i < 32; i++)
{
if (((n >> i) & 1) == ((m >> i) & 1))
{
count++;
}
}
return count;
}
int main()
{
int n = 0;
int m = 0;
scanf("%d %d", &n,&m);
int ret =Number(n,m);
printf("%d", ret);
return 0;
}
但是回顾一下上面提到的异或,可以想到另一种解法。将两数异或后统计里面1的个数,就可以得到多少个位不同,再用32减去不同的就是相同的了。
那么可以将前面统计1的个数的代码结合起来,便可以写出如下代码
#include<stdio.h>
int Number(int n)//统计异或后1的个数
{
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
count = 32 - count;
return count;
}
int main()
{
int n = 0;
int m = 0;
scanf("%d %d", &n,&m);
int ret =Number(n^m);//将两数异或
printf("%d", ret);
return 0;
}
3.打印一个整数二进制(32位)的奇数位和偶数位的序列
大体思路
最开始求一个整数对应二进制1的个数时,通过移位将整数的每一位与1相与,那现在可以通过类似的想法试着将整数每次移动奇数个位数或者偶数个位数再与1相与,并把结果打印出来,这是基于与运算的特点,同时为1才是1,否则为0.而二进制中除了1就是0,利用上述特性和1的二进制序列特点,便可以轻松解决问题。
代码如下
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
for (int i = 31; i >= 1; i-=2)//打印偶数位
{
printf("%d ",((n>>i)&1));
}
printf("\n");
for (int j = 30; j >= 0; j-=2)//打印奇数位
{
printf("%d ", ((n >> j) & 1));
}
return 0;
}
因为先打印的先显示,所以我是从高位开始打印,这样打印完后显示的顺序就是从高位到低位输出,比较符合日常习惯,所以i和j的取值是 31和 30,然后自减。
4.在不引入第三个变量的前提下交换两个数
1.根据题目可以很容易想到用自带的库函数swap来解决这个问题
2,除了上述的方法外还有这样的一种方法,以 30 20 为例 30+20-30=20
30+20-20=30;基于这种相加在相减的思路,可以写出如下代码
#include<stdio.h>
int main()
{
int a = 30;
int b = 20;
printf("%d %d\n", a, b);
a = a + b;//50
b = a - b;//50-20=30;
a = a - b;//50-30;
printf("%d %d", a, b);
return 0;
}
但是这样做会有个弊端,如果a b特别大的时候两数相加可能会造成数据溢出
有没有更好的方法呢?是有的,在介绍种方法前,补充一下异或运算的一些特点
a^a=0;a^0=a;同时异或运算满足交换律、结合律。
那么 a^b ^a= 0 ^b = b; a ^b ^b =a ^0 = a.
便可以写下如下代码:
#include<stdio.h>
int main()
{
int a = 30;
int b = 20;
printf("%d %d\n", a, b);
a = a ^ b;
b = a ^b;//a^b^b = 0^a = a;
a = a ^ b;//a^b^a = 0^b = b;
printf("%d %d", a, b);
return 0;
}
三.总结
1.在使用上述操作符时注意操作对象是整数,移位时移动的位数是正整数
2.在打印二进制的奇 偶数位的时候,注意一下自己想要输出的格式来改变循环变量的初始化设置。
3.n= n&(n-1)是消1操作,操作几次就消掉几个1.
4.以上内容如有问题欢迎指出。谢谢指教!