一、原码,反码,补码
在正式介绍操作符之前,我想在这里先介绍一下原码,反码,补码的概念以及他们之间的转换,这对于我们今天的介绍有很大帮助。还有一点是我们今天只介绍整型的概念。
1.概念
整数的2进制表示方法有三种,即原码、反码和补码。
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,总共32个比特位,最⾼位的1位是被当做符号位,剩余的都是数值位。 符号位都是用0表示“正”,⽤1表示“负”。
如:0的原码为00000000000000000000000000000000(总共32个比特位)
-1的原码为10000000000000000000000000000001
其中正整数的原、反、补码都相同。 负整数的三种表示方法各不相同。
(1)原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
(2)反码:将原码进行取反操作得到反码,1变为0,0变为1。其中第一位的符号位不变
(3)补码:将反码+1得到补码,(注意满2进1)
注:原码取反加1得到补码,而补码也可直接取反加1得到原码。
对于整型来说,存储在内存中的都是以补码的形式存储,同时之后的计算也都是补码之间的计算,然后在变为原码,所以在这里也提醒大家不要搞混了,导致计算错误,操作失误。
二、操作符
1.移位操作符
(只能是整数且移动的是存储在内存中的二进制位,也就是补码)
移位操作符包括左移操作符(<<),右移操作符(>>)
(1)规则:左移:向左移位,左边抛弃,右边补0(左移一位有乘2的效果)
int main()
{
int a = 10;
int b = a << 1;//具有*2的效果
printf("%d\n", a);//10
printf("%d\n", b);//20
return 0;
}
右移:逻辑右移:左边用0填充,右边丢弃 ;算术右移:左边用原该值的符号填充,右边丢弃。采用哪种右移取决于编译器,但通常都是采用算术右移。(有除2的效果)
int main()
{
int a = 10;
int b = a >> 1;//具有/2的效果
printf("%d\n", a);//10
printf("%d\n", b);//5
return 0;
}
警告⚠:移位操作符不要用于移动负数位。
2.位操作符
位操作符:&,|,^, ~
&叫按位与,规则:a和b对应计算有0则为0,2个同时为1才为1.
|叫按位或,规则:a和b对应计算只要有1就是1,两个同时为0才为0.
^叫按位异或,规则:a和b对应计算相同为0才为0,相异为1.
~叫按位取反,规则:a和b对应计算1取反为0,0取反为1.
注意:以上操作符都是二进制操作符,都是通过补码计算,他们的操作数必须是整数,其中^支持交换律。
int main()
{
int a = 3;
int b = 5;
int c = 8;
int n = 3 ^ 5 ^ 8;//14
int m = 3 ^ 8 ^ 5;//14
printf("%d %d\n", n, m);
return 0;
}
一道思考题:不能创建临时变量(第三个变量)实现两个变量的交换。
//不创建其他变量,使两个变量的值交换。
/*方案一:使用第三变量好处是直观,清楚。缺点是只适用于部分数值*/
//int main()
//{
// int a = 3;
// int b = 5;
// int c = 0;
//
// c = a + b;
// a = c - a;
// b = c - b;
// printf("变量a=%d\n", a);
// printf("变量b=%d\n", b);
// return 0;
//}
/*方案二:可任意处理多种数值,缺点是代码不直观*/
//int main()
//{
// int a = 3;
// int b = 5;
// //异或支持交换律。
// //011--3的原码;101--5的原码。
// a = a ^ b;
// b = a ^ b;
// a = b ^ a;
// printf("变量a=%d\n", a);
// printf("变量b=%d\n", b);
// return 0;
//}
3.练习
接下来让我们将这些操作符综合起来来看一个题目吧!
1.编写一个代码,求一个整数储存在内存中二进制中1的个数。
方案一:采用循环的方式,因为一个数的原,补码总共各有32个比特位,所以很容易想到用循环的的方式进行。这里的n=n/2是将你自己输入的整数转化为二进制(可以去看我的文章:神奇的二进制),缺点:无法对有符号整数运算。
//方案一:
//int main()
//{
// int n = 0;
// int count = 0;
// scanf("%d", &n);
// while (n)
// {
// if (n % 2 == 1)
// count++;
// n = n/2;
// }
// printf("二进制位中一的个数为%d", count);
// return 0;
//}
方案二:这里我们可以通过自定义一个函数来完成,并在传参的时候将函数中的形参改为unsigned int n
//方案二:num = -1,可实现对有无符号的整数运算
//int count_bite_one(unsigned int n)//将int改为unsigned int
//{
// int count = 0;
// while (n)
// {
// if ((n % 2) == 1)
// count++;
// n = n/2;
// }
// return count;
//}
//int main()
//{
// int num = 0;
// scanf("%d", &num);//-1会为0,此时%2会为0
// int ret = count_bite_one(num);
//
// printf("1的个数为%d", ret);
// return 0;
//}
方案三:我们通过前面的原,反,补码可知1的原码是000~001,在由&操作符可知n&1==1,我们可以用此表达式判断1的个数,只需将n>>i与1进行&操作,在配合循环通过循环次数判断1的个数。
//int count_bite_one(int n)
//{
// int i = 0;
// int count = 0;
// for (i = 0; i < 32; i++)
// {
// if (((n >> i) & 1) == 1)//可实现有,无符号运算
// {
// count++;
// }
//
// }
// return count;
//}
//int main()
//{
// int num = 0;
// scanf("%d", &num);
// int ret = count_bite_one(num);
// printf("1的个数为%d", ret);
// return 0;
//}
方案四:我们需要用到算法:n = n & (n-1)
所以我们在这里进行讲解,在展示代码
//int count_bite_one(int n)
//{
// int count = 0;
// while(n)
// {
// n = n & (n - 1);//算法计算,多次循环后结果n为0,循环停止。
// count++;
// }
// return count;
//}
//int main()
//{
// int num = 0;
// scanf("%d", &num);
// int ret = count_bite_one(num);
// printf("1的个数为%d", ret);
// return 0;
//}
2.写一个代码判断n是否为2的次方数(加强对算法的印象)
//int main()
//{
// int n = 0;
// scanf("%d", &n);
// if (((n - 1) & n) == 0)
// {
// printf("yes");
// }
// return 0;
//}
3.编写代码将13二进制序列的第5位修改为1,再改回0
int main()
{
int a = 13;
//00000000000000000000000000001101 - a的原码也是补码,因为正数的原,反,补都一样。
//00000000000000000000000000011101 - 将a的第5位修改为1
//00000000000000000000000000010000 -该补码满足题意,而该补码可有1向左移动4位得到
a = a | (1 << 4);//29
printf("%d\n", a);
//00000000000000000000000000011101 - 将其与1左移4位后的补码并取反的结果,进行按位与操作
a = a & ~(1 << 4);
printf("%d\n", a);//13
return 0;
}
总结:我们发现二进制并没有我们想的这么简单,也是具有很多操作性的,所以我希望这篇博客能够帮助你,这里也希望大家熟练掌握n&(n-1)=n这个算法,最后希望大家多多支持!