深入到字节的内部,讨论如何直接操作字节内部的二进制位
十进制转二进制
短除法&位权法
先读高位、后读低位。从下往上逆序读右侧的余数101010。这就是十进制数42所对应的二进制。
将二进制转换为十进制,可以把二进制中的各位乘以其所在位的位权,再将所有乘法的积累加起来,即可得到转换后的十进制结果。
例如:
二进制101010,高位在左,低位在右
十进制转二进制函数
#include<stdio.h>
void printBinary(unsigned char dec) {
// 若dec为0,输出0并返回
if (dec == 0)
{
printf("0\n"); return;
}
// 若dec非0,短除计算余数,逆序输出
char bits[8];
int count = 0;
int quotient;
int remainder;
while (dec > 0)
{
remainder = dec % 2;
quotient = dec / 2;
dec = quotient;
bits[count] = remainder;
count++;
}
for (int i = count - 1; i >= 0; i--)
printf("%d", bits[i]);
putchar('\n');
}
int main() {
printBinary(42);//以42为例
}
正确输出
101010
对于unsigned char
类型的变量dec
来说,它拥有8个二进制位。而十进制数42,只占6个二进制位,还有两个二进制位为0。若需要把8个二进制位全部输出,可以将bits
数组初始化为0。计算完余数后,从数组的最后一个元素开始,逆序输出直到数组第一个元素。此时,无需对dec
为0做特殊处理了。由于数组bits
初始化为0,dec
为0时,不进入while
循环,直接输出8个0。
#include<stdio.h>
void printBinary(unsigned char dec) {
char bits[8] = {0};
int count = 0;
int quotient;
int remainder;
while (dec > 0)
{
remainder = dec % 2;
quotient = dec / 2;
dec = quotient;
bits[count] = remainder;
count++;
}
for (int i = 8 - 1; i >= 0; i--)
printf("%d", bits[i]);
putchar('\n');
}
int main() {
printBinary(42);
}
补齐0
00101010
逻辑运算符
逻辑运算符会把运算对象的数值,看做真或假进行逻辑运算。若运算对象的数值为非0值,则看做真。若数值为0值,则看做假。
下面分别复习一下这3种逻辑运算符。
逻辑与&&
逻辑与&&
运算符会对左右两边,两个运算对象进行运算。
- 若两个运算对象同时为真时,运算结果为真,用数值1表示。
- 否则,运算结果为假,用数值0表示。
12345 && 67890
左右有两个运算对象,分别为12345
与67890
。左边的运算对象12345
为非0值,看做真。右边运算对象67890
为非0值,看做真。两个运算对象同时为真,运算结果为真,即1。
若将其中一边变为0,表达式如下:12345 && 0
左边的运算对象12345
为非0值,看做真。右边运算对象0
为0值,看做假。两个运算对象不同时为真,运算结果为假,即0。
逻辑或||
逻辑或||
运算符会对左右两边,两个运算对象进行运算。
- 若两个运算对象同时为假时,运算结果为假,用数值0表示。
- 否则,运算结果为真,用数值1表示。
12345 || 0
左边的运算对象12345
为非0值,看做真。右边运算对象0
为0值,看做假。两个运算对象不同时为假,运算结果为真,即1。0 && 0
左边的运算对象0
为0值,看做假。右边运算对象0
为0值,看做假。两个运算对象同时为假,运算结果为假,即0。
逻辑非!
逻辑与!
运算符会对右边一个运算对象进行运算:
- 若运算对象为真,运算结果为假。
- 若运算对象为假,运算结果为真。
!12345
右边运算对象12345
为非0值,看做真。运算结果为假,即0。!0
右边运算对象0
为0值,看做假。运算结果为真,即1。
位运算符
在上面的逻辑运算中,会把运算对象的数值根据非0值或0值,看做真或假,再进行逻辑运算。
还有另外一类运算符,它将深入运算对象的内部,把运算对象内部的二进制位,根据非0值或0值,看做真或假,再进行逻辑运算。
位逻辑运算符:
- 位逻辑与
&
- 位逻辑或
|
- 位逻辑异或
^
- 位逻辑非
~
将十进制170作为函数printBinary
的参数,它将打印出十进制170的二进制10101010
。
将十进制102作为函数printBinary
的参数,它将打印出十进制102
的二进制01100110
。
接下来,我们让这两个数值分别做各种位逻辑运算,看看它们都有什么现象。
位逻辑与&
printf("%hhu\n", 170 & 102);
printBinary(170 & 102);
位逻辑与&它将深入字节内部,对二进制位进行逻辑与运算。
- 若两个位同时为真,运算结果为真,用数值1表示。
- 否则,运算结果为假,用数值0表示。
前四位为例
位逻辑或|
- 若两个位同时为假时,运算结果为假,用数值0表示。
- 否则,运算结果为真,用数值1表示。
位逻辑异或^
- 若两个位不同时,运算结果为真,用数值1表示。
- 否则,运算结果为假,用数值0表示。
位逻辑非~
位逻辑非~
它将深入字节内部,对二进制位进行逻辑非运算。
- 若二进制位为真时,运算结果为假,用数值0表示。
- 二进制位为假时,运算结果为真,用数值1表示。
换句话说,位逻辑非运算会翻转运算对象的所有二进制位。二进制位1变为0,0变为1。
左移右移
左移运算符<<
左移运算符将数据对象内部的二进制全部向左移动指定位,空出来的位置用0填充。
#include<stdio.h>
void printBinary(unsigned char dec) {
char bits[8] = {0};
int count = 0;
int quotient;
int remainder;
while (dec > 0)
{
remainder = dec % 2;
quotient = dec / 2;
dec = quotient;
bits[count] = remainder;
count++;
}
for (int i = 8 - 1; i >= 0; i--)
printf("%d", bits[i]);
putchar('\n');
}
int main() {
printBinary(231);
printBinary(231 << 1);
printBinary(231 << 2);
printBinary(231 << 3);
}
输出结果
11100111
11001110
10011100
00111000
右移运算符
右移运算符将数据对象内部的二进制全部向右移动指定位,对于无符号类型,空出来的位置用0填充。
对于有符号类型,空出来的位置用0或1填充取决于编译器。
#include<stdio.h>
void printBinary(unsigned char dec) {
char bits[8] = {0};
int count = 0;
int quotient;
int remainder;
while (dec > 0)
{
remainder = dec % 2;
quotient = dec / 2;
dec = quotient;
bits[count] = remainder;
count++;
}
for (int i = 8 - 1; i >= 0; i--)
printf("%d", bits[i]);
putchar('\n');
}
int main() {
printBinary(231);
printBinary(231 >> 1);
printBinary(231 >> 2);
printBinary(231 >> 3);
}
输出结果
11100111
01110011
00111001
00011100