二进制按位操作解析(C语言)
C语言按位操作:包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种。
1.首先我们了解一下"字节"、”位“的概念
C语言中有个sizeof方法是获取数据类型占几个字节的方法
int length = sizeof(int); // 输出:4(字节) 一个字节占8位,即4*8=32位 (注:此处位32位编译器,如16位编译器则应为2字节)
位是什么意思呢???
位就可以理解成开关,8位就是8个二进制的开关,开就是1关就是0.
二进制数"00001111"就代表8个开关的状态是“关关关关开开开开”,
所以有符号整型的最大值就是
int intMax = 2147483647; // 0 1111111 11111111 11111111 11111111
由于第一位表示数值正负(0表示正数,1表示负数),所以有符号int可以存储的最大数字为 0 1111111 11111111 11111111 11111111 => +2147483647 (10进制)
如果需要无符号整型,可以用如下方式表示,最大值为
unsigned int unIntMax = 4294967295; // 11111111 11111111 11111111 11111111 => +4294967295 (10进制)
短整型的长度为2字节,就不再举例了
int short shortlength = sizeof(short int);
2.按位与,按位或,按位异或
计算基础就不介绍了,应该很好理解,看不懂请自行百度
// 3的二进制数为 : 0 0000000 00000000 00000000 00000011
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 按位与 3 & 5 : 0 0000000 00000000 00000000 00000001 => 1
// 按位或 3 | 5 : 0 0000000 00000000 00000000 00000111 => 7
// 按位异或 3 ^ 5 : 0 0000000 00000000 00000000 00000110 => 6
2.1按位与(&)的常见作用
我们先要做一些准备工作,以小写字母a为例。
char 类型保存的是字符对应的ascii码,是无符号整数
我们先来看看char类型的长度
int charlength = sizeof(char); // 输出 1,一个字节就相当于有8个0或1
通过查表我们知道,小写字母a对应的ASCII码 = > 97
97的二进制数为 = > 1100001
2.1.1作用1:将某几位,置零,其他位不变。
01100001
11111110
//---------------------------
01100000
这样就将最低位,置0,得到96
96对应的ascii符号通过查表可得为单引号:’
2.1.2 作用2:取指定位
01100001
00001111
//---------------------------
00000001
这样就可以只取最低4位
2.2 按位或(|)的常见作用
2.2.1 作用1:将某几位,置1,其他位不变。
同样以a为例
01100001
00000010
//---------------------------
01100011
这样就将第二位,置1得到十进制99
99对应的ascii符号通过查表可得小写字母c
printf("a & 2 = %c
", a | 2); // a & 2 = c
2.3 按位异或(^)的常见作用
2.3.1 作用1:将指定位翻转
以a为例
01100001
00001111
//---------------------------
01101110
这样就将1~4位翻转,得到十进制110
110对应的ascii符号通过查表可得小写字母n
printf("a ^ 15 = %c
", a ^ 0xf); // 输出a ^ 15 = n
3. 按位取反(~)
我们用10进制整型5举例
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 按位取反(非) ~5 : 1 1111111 11111111 11111111 11111001 => -6
printf("按位取反 ~5 : %d
", ~5);
这里我们发现一个很有趣的现象,为什么~5 = -6
那我们再多输出几个,找找规律
printf("按位取反 ~6 : %d
", ~6); // -7
printf("按位取反 ~7 : %d
", ~7); // -8
printf("按位取反 ~8 : %d
", ~8); // -9
好像按位取反+1 就能得到原数的负数。
但是 1 1111111 11111111 11111111 11111001 在我们的理解中应该是 -2147483641 才对啊,为什么打印出来是-6呢?
没错,这个值也表示 -6,如果你学习过原码,补码,反码的知识,就知道
1 0000000 00000000 00000000 00000110 是-6的原码
1 1111111 11111111 11111111 11111001 是-6的反码
请一定要注意,按位取反是操作,是动词,反码是名词,是两个概念
请仔细观察上面的二进制数,发现什么特点没。
是不是符号位没变,余下取反?
没错,如果我们再将反码+1,就得到了-5
此时的反码就叫做补码
补上最后这个正1就得到了原数的相反数,是不是很有意思。
下面我们来尝试一下无符号整型取反
unsigned int unInt = ~5;
printf("无符号整型取反 ~5 : %d
", ~unInt); //输出5
奇怪了?我们预期取反不应该是
5的二进制数为 : 00000000 00000000 00000000 00000101
------------------------------------------------------------
预期的结果 : 11111111 11111111 11111111 11111010 => 4294967290(10进制)
实话说我也不太清楚为什么这样,我只能猜测
因为人为规定:对于正数来说,其二进制原码,反码,补码均为相同的,为原码的形式;
难道C语言不能对无符号整型按位取反了吗???。这里先挖一个坑以后找时间去填上。
4. 左移运算(<<)
左移运算,高位舍弃,低位补0
相当于乘以2的效果
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 5左移1位 5<<1 : 0 0000000 00000000 00000000 00001010 => 10
printf("左移1位 5<<1 : %d
", 5 << 1 ); // 输出:10
printf("左移2位 5<<2 : %d
", 5 << 2 ); // 输出:20
5. 右移运算(>>)
右移运算,低位舍弃
无符号数高位补0
有符号数高位补符号位(即正数补0负数补1)
相当于除2的效果
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 5右移1位 5>>1 : 0 0000000 00000000 00000000 00000010 => 2
printf("左移1位 5>>1 : %d
", 5 >> 1);
printf("左移2位 5>>2 : %d
", 5 >> 2);
由于舍弃了末位,小数的精度丢失
附:完整C代码片段
#include<stdio.h>
int main() {
// 输出int类型长度
int length = sizeof(int); // 输出:4(字节),一个字节8位,即4*8=32位 //此处位32位编译器,如16位编译器则应为2字节
int intMax = 2147483647; // 0 1111111 11111111 11111111 11111111
// 由于第一位表示数值正负(0表示正数,1表示负数),所以有符号int可以存储的最大数字为 0 1111111 11111111 11111111 11111111 => +2147483647 (10进制)
int intMax = 2147483647; // 0 1111111 11111111 11111111 11111111
// 如果需要无符号整型,可以用如下方式表示
unsigned int unIntMax = 4294967295; // 11111111 11111111 11111111 11111111
int short shortlength = sizeof(short int); // 短整型的长度为2字节,就不再举例了
printf("整型长度 : %d
", length);
printf("短整型长度 : %d
", shortlength);
// 按位与,按位或,按位异或的计算就不介绍了,应该很好理解,看不懂请自行百度
// 3的二进制数为 : 0 0000000 00000000 00000000 00000011
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 按位与 3 & 5 : 0 0000000 00000000 00000000 00000001 => 1
// 按位或 3 | 5 : 0 0000000 00000000 00000000 00000111 => 7
// 按位异或 3 ^ 5 : 0 0000000 00000000 00000000 00000110 => 6
int res = 3 & 5;
printf("3 & 5 = %d
", res);
res = 3 | 5;
printf("3 | 5 = %d
", res);
res = 3 ^ 5;
printf("3 ^ 5 = %d
", res);
// 下面介绍按位与(&)的常见作用
// char 类型保存的是字符对应的ascii码,是无符号整数
int charlength = sizeof(char);
printf("字符型长度 : %d
", charlength);// 输出 1
// a对应的ASCII码 = > 97
// 97的二进制数为 = > 1100001
char a = "a";
printf("a->ascii = %d
", a); // 输出 :a->ascii = 97 => 01100001
/*
作用1:将某几位置零,其他位不变。
01100001
11111110
---------
01100000
这样就将最低位,置0,得到96
96对应的ascii符号通过查表可得为单引号:`
*/
printf("a & 0xfe = %c
", a & 0xfe);
/*
作用2:取指定位
01100001
00001111
----------
00000001
这样就可以只取最低4位
*/
// 下面介绍按位或(|)的常见作用
/*
作用1:将某一位置1,其他位不变。
同样以a为例
01100001
00000010
----------
01100011
这样就将第二位,置1得到十进制99
99对应的ascii符号通过查表可得小写字母c
*/
printf("a & 2 = %c
", a | 2);
// 下面介绍按位异或(^)的常见作用
/*
作用1:将指定位翻转
以a为例
01100001
00001111
----------
01101110
这样就将1-4位翻转,得到十进制110
110对应的ascii符号通过查表可得小写字母n
*/
printf("a ^ 15 = %c
", a ^ 0xf);
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 按位取反(非) ~5 : 1 1111111 11111111 11111111 11111001 => -6
printf("按位取反 ~5 : %d
", ~5);
// 这里我们发现一个很有趣的现象,为什么~5 = -6
// 那我们再多输出几个,找找规律
printf("按位取反 ~6 : %d
", ~6); // -7
printf("按位取反 ~7 : %d
", ~7); // -8
printf("按位取反 ~8 : %d
", ~8); // -9
// 好像按位取反+1 就能得到原数的负数。
// 但是 1 1111111 11111111 11111111 11111001 在我们的理解中应该是 -2147483641 才对啊,为什么打印出来是-6呢?
// 那我们来想想按你的理解 -6 应该怎么表示?
// 最高位表示符号,负数就应该为1,6对应的二进制应该是 110,中间补0,就应该是 1 0000000 00000000 00000000 00000110
// 没错,这个数也表示 -6,如果你学习过原码,补码,反码的知识,就知道
// 1 0000000 00000000 00000000 00000110 是-6的原码
// 1 1111111 11111111 11111111 11111001 是-6的反码
// 请一定要注意,按位取反是操作,是动词,反码是名词,是两个概念
// 请仔细观察上面的二进制数,发现什么特点没。
// 是不是符号位没变,余下取反?
// 没错,如果我们再将反码+1,就得到了-5,此时的反码就叫做补码,补上最后这个正1就得到了原数的相反数,是不是很有意思。
// 下面我们来尝试一下无符号整型取反
unsigned int unInt = ~5;
printf("无符号整型取反 ~5 : %d
", ~unInt); //输出5
// 奇怪了?我们预期取反不应该是
// 5的二进制数为 : 00000000 00000000 00000000 00000101
// ------------------------------------------------------------
// 预期的结果 : 11111111 11111111 11111111 11111010 => 4294967290(10进制)
// 实话说我也不太清楚为什么这样,我只能猜测
// 因为人为规定:对于正数来说,其二进制原码,反码,补码均为相同的,为原码的形式;
// 难道C语言不能对无符号整型按位取反了吗???。这里先挖一个坑以后找时间去填上。
// 左移运算,高位舍弃,低位补0
// 相当于乘以2的效果
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 5左移1位 5<<1 : 0 0000000 00000000 00000000 00001010 => 10
printf("左移1位 5<<1 : %d
", 5 << 1 );
printf("左移2位 5<<2 : %d
", 5 << 2 );
// 右移运算,低位舍弃,无符号数高位补0,有符号数高位补符号位(即正数补0负数补1)
// 相当于除2的效果
// 5的二进制数为 : 0 0000000 00000000 00000000 00000101
// --------------------------------------------------------
// 5右移1位 5>>1 : 0 0000000 00000000 00000000 00000010 => 2
printf("左移1位 5>>1 : %d
", 5 >> 1);
printf("左移2位 5>>2 : %d
", 5 >> 2);
// 由于舍弃了末位,小数的精度丢失
return 0;
}