概述
C语言的位运算符十分常用也十分好用。使C区别于其他高级语言的的特性之一是访问整数中的个别位的能力。它十分接近底层硬件,这个特性通常是程序与硬件设备和操作系统相连接的关键!
下面我就系统的梳理下C语言中关于位运算符的使用和功能,希望大家也能从中感受的C的魅力!
*一、基本运算符的使用(~、&、|、^)*
这里主要是按位取反、按位与、按位或、按位异或的使用,它们基本的运算方式简要说一下,从名字也能看出来,他们是对一位进行运算(int是32位即4个字节组成),而每位只有两个可能取值0或1。
~(按位取反):0变1,1变0;
&(按位与):全都是1的时候结果才是1,否则是0;
|(按位或):只要有一个是1结果即为1,否则是0;
^(按位异或):相异为1(就是只有1和0这种搭配结果才是1),相同为0;
其实上面就是逻辑代数的基础概念,十分实用的一些运算。下面的代码展示了这些运算符对int的位操作,相信结合注释,并没有什么障碍!
/*
*C语言中通过位操作符,对二进制各位操作
*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
//基本位操作符使用
/*
*char在内存中占1 byte(即8位)所以下面的计算过程为:
*按位取反,即0变成1,1变成0
*~(10011010) //表达式 154
* (01100101) //结果值 101
*/
unsigned char val = 154;
unsigned char anti_val = ~(val); //val的值并不改变
printf("%d", anti_val); //十进制输出
printf("\n");
/*
*按位与,对于每个位中只有两个数都为1结果才是1
*(10010011) & (00111101) //表达式 147 & 61
*(00010001) //结果值 17
*/
unsigned char val_2 = 147, val_3 = 61;
unsigned char and_val = val_2 & val_3;
printf("%d", and_val);
printf("\n");
/*
*按位或,对于每个位有一个为1即是1
*(10010011) | (00111101) //表达式
*(10111111) //结果值 191
*/
unsigned char val_4 = 147, val_5 = 61;
unsigned char or_val = val_4 | val_5;
printf("%d", or_val);
printf("\n");
/*
*位异或,对于每个位,相同为0,相异为1
*(10010011) ^ (00111101) //表达式
*(10101110) //结果值 174
*/
unsigned char val_6 = 147, val_7 = 61;
unsigned char exc_val = val_6 ^ val_7;
printf("%d", exc_val);
printf("\n");
/*
* & | ^ 存在操作符-赋值表示(类似于+= -= ...)
*如 &= |= ^=,不存在~=
*/
system("pause");
return 0;
}
二、位运算符的基本功能总结
通过上述几个运算符可以实现一些有趣的功能,这里涉及到几个概念,首先应该阐明一下。
掩码:就是一个一个“遮掩膜”的功能。一串数字,通过与掩码做&运算可以把不想要的位“隐藏掉”,想要的位“保留”下来。下面看剖析图:
其根本上还是利用了&的运算性质!
打开位:这是比较形象的说法,可以这样理解,把每位重点的两个状态看成开关,像可以让1表示开状态,0表示关状态(反过来的话属于‘逆逻辑’)。这里就是把指定的位打开(置1)。
关闭位:这当然就时把指定的位置0.
转置位:这个从名字也可以看出来,就是原来是0现在就变1,原来是1现在就变0.也许你立刻想到了前面的一个运算符,没错!就是按位取反(~),不过你只才对了一般,他的功能的确类似,但是~是对整个数据进行操作,如果想把指定的一位转置呢?看代码吧!
查看一位的值就是看看指定的位,是0还是1.这个功能挺实用的,想象要把一个十进制转换成二进制的表示形式,他是不是就派上用场了!
结合上面的简明阐述,下面的代码应该没有障碍:
/*
*各个位操作符的基本用法及功能
*如,掩码、打开位、关闭位、置换位等
*/
#include <stdio.h>
#include <stdlib.h>
#define MASK 2
int main()
{
/*
*掩码示例
*表达式为 10010110 & MASK(00000010)
*结果值为 00000010(即为十进制2)
*/
unsigned char val = 150;
unsigned char after_mask = 0;
after_mask = val & MASK;
printf("%d", after_mask);
printf("\n");
/*
*掩码的常用方式
*使用0xFF掩码只留下被掩盖值得最后8位,将其余设为0
*无论最初的value是8位16位或者更多,最终的值都修正到一个字节中
*/
int val_2 = 514; //514的二进制形式为0…,00000001,00000010
unsigned char mask = 0xFF; //0xFF是二进制11111111的十六进制表示形式
int after_mask_2 = val_2 & mask; //掩码使用
printf("%d", after_mask_2); //输出结果是2(即是0…0,00000010)
printf("\n");
/*
*‘打开位’示例
*表达式为 10010100 | open_bit(00000010)
*结果值为 10010110
*/
int val_3 = 148;
unsigned char open_bit = 2;
int after_open_bit = val_3 | open_bit; //将右数第二位打开,相当于原数加2
printf("%d", after_open_bit); //输出结果为150 = 148 + 2
printf("\n");
/*
*‘关闭位’示例
*表达式为 10010110 & close_bit(11111101)
*结果值为 10010100
*/
int val_4 = 150;
unsigned char close_bit = ~open_bit; //open_bit按位取反即可达到目的
int after_close_bit = val_4 & close_bit;//将右数第二位关闭,相当于原数减2
printf("%d", after_close_bit); //输出结果为148 = 150 - 2
printf("\n");
/*
*‘转置位’示例
*表达式为 10010110 ^ tran_bit(00000010)
*结果值 10010100
*/
int val_5 = 150;
unsigned char tran_bit = 2;
int after_tran_bit = val_5 ^ tran_bit;//将右数第二位转置(0变1,1变0),这里相当于原数减2
printf("%d", after_tran_bit); //输出结果为148 = 150 - 2
printf("\n");
/*
*‘查看一位的值’示例
*实现查看val_6右数第二位的值的功能
*/
mask = 2;
unsigned char val_6 = 150; //val_6的二进制表示为10010110
unsigned char each_bit = val_6 & mask; //屏蔽val_6的其他位
if (each_bit == mask) { //查看并输出结果
printf("val_6右数第二位是1");
}
else {
printf("val_6右数第二位非1");
}
printf("\n");
system("pause");
return 0;
}
三、强大的左移<<与右移>>运算符
这两个运算符真的很神奇!他们竟然可以到一个整数的内部来具体选中某一个位。这也是C最迷人的地方,是如此的接近底层。但是有一个应该注意的地方:<<向左移动的位数由其右操作数指定,空出的位用0填充,并且丢弃移出左侧操作数末端的位;>>对于unsigned数与<<的处理过程完全一致,但是在针对signed数填充的值却依赖于具体的机器!这表面运用这个操作符的代码很难移植!应该牢记这一点!
下面结合代码的注释看看他们对数据的基本操作方式:
/*
*通过左移<<与右移>>运算符实现对数据的位操作
*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
//左移运算符使用举例
unsigned char val_1 = 138; //其二进制表示形式为 10001010
unsigned char after_left_move;
after_left_move = val_1 << 2; //左移两位,丢弃末端为,空位用0填充
printf("%d", after_left_move); //输出结果是40,二进制表达形式为 00101000
printf("\n");
//使用运算符-赋值格式,会改变val_1的值s
val_1 <<= 2;
printf("%d", val_1);
printf("\n");
//右移运算符的使用举例
unsigned char val_3 = 138; //无符号数,二进制表达形式 10001010
unsigned char after_right_move;
after_right_move = val_3 >> 2; //右移两位,丢弃末端位,填充值0
printf("%d", after_right_move); //输出为34,二进制表达形式为 00100010
printf("\n");
signed char val_4 = 138; //...
val_4 >>= 2;
printf("%d", val_4); //末端位丢弃,填充值依赖于操作系统
printf("\n"); //我的机器上此处输出结果是-30,也就是补1
system("pause");
return 0;
}
四、<<与>>运算符的基本功能
主要介绍3个使用的功能:
(1)提供快捷、高效的2次幂的乘法和除法。具体规则看下表:(注意非负)
(2)从较大单位中提取多组比特位(如:RGB颜色编码中不同颜色分量的提取)
(3)实现十进制转换成二进制表示
下面的代码中,每个功能通过函数实现出来,并不难懂:
/*
*移位运算符的功能介绍,主要包括下面3部分
*(1)对2的幂快速乘除法
*(2)在较大单位中提取多组bit位
*(3)实现十进制转换成二进制表示
*/
#include <stdio.h>
#include <stdlib.h>
#define MASK 0xFF //仅保留8位的掩码
#define MASK_LAST 1 //仅保留末位的掩码
//函数声明
int MultiByBitMove(int power, int value); //使用移位运算实现快速乘法
int DivisByBitMove(int power,unsigned int value); //使用移位运算实现快速除法
int PickUpBit(int value, int cnt); //从数据value中提取cnt开始的8位
void TransToBinary(int value, int *str); //十进制转二进制表示
void DisplayByBinary(int *str); //输出二进制字符串
int main()
{
int val_1 = 100, val_2 = 512, val_3 = 512;
int binary[sizeof(int) * 8];
printf("%d\n", MultiByBitMove(2, val_1)); //输出400 = 100 * 2^2
printf("%d\n", DivisByBitMove(2, val_1)); //输出25 = 100 、 2^2
printf("%d", PickUpBit(val_2, 9)); //从右树第8位开始向左提取8位
printf("\n"); //512装换成二进制后,对应输出结果是1,也就是…00000001
TransToBinary(val_3, binary);
DisplayByBinary(binary);
printf("\n");
system("pause");
return 0;
}
//函数定义
int MultiByBitMove(int power, int value) {
//左移位数就是数据乘的2的幂数
return (value <<= power);
}
int DivisByBitMove(int power, unsigned int value) {
//右移位数就是数据除的2的幂数
return (value >>= power);
}
int PickUpBit(int value, int cnt) {
//通过掩码及移位运算符提取数据中的bits
return (value >> cnt) & MASK;
}
void TransToBinary(int value, int *str) {
//通过掩码及右移运算符将十进制保存为二进制字符串
for (int i = 0; i < sizeof(int) * 8; i ++) {
str[i] = (value >> i) & MASK_LAST;
}
}
void DisplayByBinary(int *str) {
//每8 bits分割输出二进制字符串
for (int i = sizeof(int) * 8 - 1; i >= 0; i--) {
if (0 == (i + 1) % 8 && (i + 1) != sizeof(int)* 8) {
printf(",");
}
printf("%d", str[i]);
}
}
对位操作的核心概念大概就这些,有时候用位操作可以实现各种功能和算法,并且可以实现对一些硬件的直接控制(如单片机等)。总之非常值得花时间学习和使用。也希望通过文章大家对C的位操作符内容有一个更清晰的认识!