C语言中的二、八、十、十六进制。

##C语言中的二、八、十、十六进制。

我们都知道无论是什么样的值,在计算机中都是以0和1储存的。基于一串0和1组成的数,我们根据我们的定义或规定来表示,才能变成我们想要的“数据”。

下面是我们的输入,我们可以根据我们的需要输入不同进制的数。

#include <stdio.h>
int main()
{
	int a = 0b00010100; // 二进制
	int b = 0000000010;// 八进制
	int c = 1000000000;// 十进制
	int d = 0x00010100; // 十六进制
	printf("%d\n", a); // a为十进制的20
	printf("%d\n", b); // b为十进制的8
	printf("%d\n", c); // c为十进制的1000000000
	printf("%d\n", d); // d为十进制的65792
}

0b开头且0和1组成的数为二进制;0开头且0~7组成的数为八进制;1~9开头且1~9组成的数为十进制;0x开头且0~F组成的数为十六进制。

逻辑运算和位运算——与或非、左移和右移

##逻辑运算和位运算——与或非、左移和右移

与:当且仅当两个数的位都为1时,为真。

或:两个数只要任意一个为1时,就为真。

非:真为假,假为真。


这次重点还是放在左移运算和右移运算上。下面代码的注释简化了int型的字节数。int实际上是4个字节。

左移运算:

#include <stdio.h>
int main()
{
	int a = 0b00010100 << 2; // 左移两位
	printf("%d\n", a); // 二进制0b01010000
}

数据左移时候,最左边位将被丢弃,同时在最右边补0。

右移运算:

#include <stdio.h>
int main()
{
	int a = 0b00010100 >> 2; // 右移两位
	printf("%d\n", a); // 二进制0b00000101

	int b = -10 >> 2; // 十进制为-10,二进制为0b11110110
	printf("%d\n", b); // 十进制为-3,二进制为0b11111101
}

右移的时候,最右边的位将被丢弃,但右移要稍微复杂一些。如果数字是一个无符号数值,则用0填补最左边位。如果数字是一个有符号数值,则用根据符号位填补最左边的位。也就是说,对于无符号数,用0填补最左边的位,丢弃最右边的位;对于有符号数,用1填补最左边的位,丢弃最右边的位。

逻辑运算和位运算——异或

##逻辑运算和位运算——异或

如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。

更通俗的讲就是:两个值不同为真,两个值相同为假。

10
10(假)1(真)
01(真)0(假)
异或运算真值表

上面是异或在逻辑运算中的使用。但如果异或放在位运算中,就有很多的应用场景。


我们把上面的表抽取出来,看成位运算的话可以得到下面两个表,并得出两个简单结论:

10
010
用0进行按位异或运算

结论一:如果一个数用0进行按位异或的话,会得到这个数本身。

10
101
用1进行按位异或运算

结论二:如果一个数用1进行按位异或的话,会得到相反的数。也就是取反。


用上面的两个结论,我们有四种基本的异或使用环境:

使用环境一:使某些特定的位取反。

a = 10100001;
b = 00000110;
c = a ^ b; // 0b10100111

使用环境二:实现两个值的交换,而不必使用临时变量。

a = a ^ b; // a=0b10100111
b = b ^ a; // b=0b10100001
a = a ^ b; // a=0b00000110

使用环境三:将变量自身置零。

a = 10100001;
a = a ^ a; // 0b00000000

使用环境四:快速判断两个值是否相等。

if ((a ^ b) == 0)
{
	//如果a和b相同则继续执行
	...
}

从上面的几个基本使用方法还可以衍生出一些高级的用法。

用法一:如异或的加密运算。

#include <stdio.h>

int main()
{
	char a = 'w'; // 原文
	char secret = '8'; // 密钥

	a = (char)(a ^ secret); // 加密
	printf("%c\n", a); // 大写字母O
	a = (char)(a ^ secret); // 解密
	printf("%c\n", a); // w
}

这里使用了异或取反的特点。用同一个密钥(在上面代码中为变量secret),对相同的位进行异或取反两次就可以得到原文。也就是负负得正的原理,“负”一次为密文,再“负”一次就是原文了。

用法二:检测两个数不同的比特位位置。

int a = 0b10100001;
int b = 0b10101011;
int c = a ^ b; // 0b00001010

根据结果我们可以看到数a和数b,第1位和第4位的比特位是不同的。当且仅当只有一个数的某位上为1时,结果的该位才为1。否则结果的该位为0。

用法三:数a需要改变多少个比特位才能得到数b。

这是上面用法的扩展。

#include <stdio.h>

int main()
{
	int a = 0b10100001;
	int b = 0b10101011;
	int c = a ^ b; // 0b00001010

	int count = 0;
	while (c)
	{
		c &= (c - 1);
		count++;
	}
	printf("%d", count); // count的值为2
}

在上面代码中,数a和数b异或的结果c为0b00001010 。结果c表示,数a需要根据数c,在数c比特位为1的对应位置取反,就可以得到数b。具体到上面的代码中,数a需要在第1位和第4位的位置取反,就可以得到数b。而代码中while循环和其中count的意思是,数a需要改变2个比特位才能得到数b。