数据在内存中是以二进制的形式存放的,计算机存储数据的最小单位是位(bit),一个二进制位可以表示两种状态(0和1),一个字节通常由8位二进制位组成。C语言支持按位运算,按位运算也就是对字节或者字中的实际位进行操作。
C语言的位运算符包括:
运算符 | 含义 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
~ | 取反 |
<< | 左移 |
>> | 右移 |
与运算符
按位与运算符“&”是双目运算符,只有两个二进制位均为1时结果才为真;
按位与真值表
a | b | a&b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
整形数据在内存中存放的是二进制的补码,所以整形数据的运算即是其二进制补码的运算;按位与运算:以89和38为例:
按位与的主要用途就是清零(即:把1置为0),若要将原数中为1的位置为0,只需将与其进行与操作的数对应位置0即可,如上边的例子,要将89二进制中的1置0,我们需要找一个89二进制位中1的对应位置为0的数(38)与其相与;
如何判断一个数是2^n;通过按位与操作可以方便的实现判断,我们知道,一个数若为2^n,那么其二进制序列中只有一个1,其余位皆为0(正数)。假设一个数为n,那么它与n-1相与,会消除一个1,所以我们只需让n与n-1相与,如果其结果为0,那么这个是就是2^n,C语言实现代码为:
#include <stdio.h>
#include <Windows.h>
int main()
{
int a;
scanf("%d", &a);
if ((a&(a - 1)) == 0)
{
printf("Y\n");
}
printf("N\n");
system("pause");
return 0;
}
‘==’运算符的优先级高于为运算符,if ((a&(a - 1)) == 0)
括号不能省略,不然程序执行会直接跳过if 语句,输入所有的数都会打印‘N’;
例:求一个整数的二进制序列中有多少位1;
C语言源代码:
//求一个整数的二进制序列中1的个数
/*按位与的主要用途就是清零,一个数n与其n-1相与会消除一个1,即把1变成0, 所以求一个数二进制序列中1的个数,只需让这个数n一直与n-1相与,直到这个 数值为0,可以设置一个计数器count,每相与一次,计数器count++即可,当这 个数的值变为0时,返回count的值即为该数二进制序列中1的个数*/
#include <stdio.h>
#include <Windows.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
while (num)
{
num = num & (num - 1);
count++;
}
printf("%d\n", count);
system("pause");
return 0;
}
测试结果:输入:5 输出:2 输入:-5 输出:31
或运算符
按位或’|’运算符也是双目运算符,参与运算的两个数各位相“或”,只要其中有一个二进制位值为1,结果就为真。
按位或真值表
a | b | a|b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
按位或运算,以75和63(均为int型数据)为例:
如果要将一个数的某几位置为1,只需要与这几位二进制序列是1的数进行或操作即可;上面的例子:75与63(或6位都为1)相与,其结果后六位都为1,同理要是5位全为1,只需与31按位或即可,要使其后n位变为1,需要与2^-1按位或即可。
异或运算符
按位异或“^”是双目运算符,参与运算的两个数对应的二进制位相异或,当对应的两个二进制位相异时,结果为1,否则结果为0;
按位异或真值表:
a | b | a^b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
按位异或运算,以198和255(数据皆为int型)为例:
从上边的例子我们可以看出异或操作可以使一个二进制序列特定位翻转(0变为1,1变为0);如:198原码的后8位数为:11000110与255(原码后8位11111111)相与结果为00111001,同理:若要使一个数的二进制序列的后n位翻转,只需将这个数与后n位全是1的数相异或即可;
例如:从键盘输入一个数,实现使其低四位翻转,即0变成1,1变成0,输出其结果
C语言代码:
/*按位异或的一个作用就是实现特定位的翻转,将一个数的后n位 翻转,只需将这个数与后n位全是1的数相异或即可,题目要求输入 一个数,将其后四位翻转,只需将这个数与15相异或即可*/
#include <stdio.h>
#include <Windows.h>
int main()
{
int a;
scanf("%d", &a);
int b = 15;
printf("%d", a^b);
system("pause");
return 0;
}
输入:145 输出:158
可以简单的运算一下:
145的原码后8位为:10010001 后四位翻转后为:10011110;其值为:2^7+2^4+2^3+2^2+2^1 = 128 + 16 + 8 + 4 +2 =158;
使用异或运算符还可以实现:不使用临时变量交换两个数的值:
C语言代码:
//交换两个数的值,不使用临时变量
#include <stdio.h>
#include <Windows.h>
int main()
{
int a = 4;
int b = 3;
a = a ^ b;
b = b ^ a;
a = a ^ b;
printf("a = %d\t b = %d\n", a, b);
system("pause");
return 0;
}
运行结果:a = 3 b = 4
取反运算符
取反运算符为单目运算符,具有右结合性,取反运算符是对参与运算的数的二进制位按位取反,即0变为1,1变为0(包括符号位),不可简单的理解为一个数取反后的值为其相反数;
例如随便给一个数:45(int 型数据)对其取反;
在以上运算过程中其符号位不变。
左移运算符
左移运算符是双目运算符,作用是把”<<”左边的运算数的各二进制位全部左移若干位,由”<<”右边的数指定其移动的位数,左移过程中高位丢弃,低位补0;
例如:int a=45;int b =a<<2;
那如果是负数呢?原理是一样的,要注意的是操作的对象是补码,移位完成后,要将其转化为原码,然后其值;
例如:int a=-45;int b =a<<2;
实际上,左移一位相当于该数乘以2,将一个数左移两位相当于该数乘以4,这种情况仅限于移出位不含1的情况;
右移运算符
右移运算符是双目运算符,作用是把”>>”左边的运算数的各二进制位全部右移若干位,由”<<”右边的数指定其移动的位数;
例如:int a=45;int b =a<<2;
在进行右移时对于有符号数要注意符号位的问题。当为正数时,最高位补0;而为负数时,最高位补0还是补1,取决于编译系统的规定。补0的称为逻辑右移,补1的称为算术右移,当在我们运算是采用的是算术右移,即:最高位补符号位1;
简单小结
- 位运算符包括:按位与、按位或、按位异或、取反、右移和左移 ;
- 除‘~’取反外,其余皆为双目运算符,结合方向均为自左向右;
- 按位与,只有两个二进制位均为1时结果才为1,其余皆为0,按位与可以实现二进制序列指定为清0,(n)&(n-1),每次可以清0一次,即把一个1变为0,利用这个作用,可以简单实现判断一个数是不是2^n和计算一个数的二进制序列中有几个1;
- 按位或,参与运算的两个数各位相“或”,只要其中有一个二进制位值为1,结果就为1,如果要将一个数的某几位置1,只需要与这几位二进制序列是1的数进行或操作即可;
- 按位异或,,参与运算的两个数对应的二进制位相异或,当对应的两个二进制位相异时,结果为1,否则结果为0,一个整数与0异或会保留原值;
- 按位取反,在进行取反操作时不可简单的认为一个数取反后的结果就是该数的相反数;
- 整数在内存中存放的是其二进制补码,对其操作实际上是对二进制补码的运算,所以左移和右移的操作数必须是整型数据;
- 左移,其功能是把”<<”左边的运算数的各二进制位全部左移若干位,由”<<”右边的数指定其移动的位数,左移过程中高位丢弃,低位补0;在移出位不含1的情况下,左移一位相当于该数乘2;
- 右移,右移运算符是双目运算符,作用是把”>>”左边的运算数的各二进制位全部右移若干位,由”<<”右边的数指定其移动的位数;在进行右移时对于有符号数要注意符号位问题。当为正数时,最高位补0;而为负数时,最高位补0还是补1取决于编译系统的决定,补0的称为“逻辑右移”,补1的称为“算术右移”,在我们计算时,一般采用逻辑右移,即高位补符号位,
- 移位操作效率比乘除高,所以如果可以用左移/右移来代替乘2.除2时优先选择左移/右移;
- 无符号值执行的所有移位都是操作都是“逻辑移位”;