操作符详细解析(一)

1.操作符的分类

本文将带你了解各种操作符并熟练掌握它们,首先让我们来看一下它的分类。

算术操作符: + - * / %
移位操作符: << >>
位操作符:   &  |  ^  
赋值操作符: = += -= *= /= %= <<= >>= &= |= ^=
单⽬操作符: !、 ++ -- & * + - ~ sizeof ( 类型 )
关系操作符: > >= < <= == !=
逻辑操作符: && ||
条件操作符: ?  :
逗号表达式: ,
下标引⽤: []
函数调⽤: ()
结构成员访问: . ->

 上述就是我们较为常用的一些操作符,其中有一些操作符十分简单,比如算术操作符,赋值操作符,逻辑操作符等,这些我们就不再进行论述,今天介绍其他一部分,而有相当一部分的操作符都跟二进制有关系,我们这里先简述一下关于二进制转换的知识。

2.二进制和进制转换

2.1二进制介绍

学习编程语言,我们经常能听到2进制,8进制,10进制(日常生活使用的),16进制的说法,那具体是什么意思呢?其实它们是数值的不同表现形式。

比如我们拿10进制15来举例:

15的2进制是:1111

15的8进制是:17

15的10进制是:15

15的16进制是:F

 我们今天重点要介绍的是二进制:

2进制满足满2进1(参考十进制满十上升一位)

扫描二维码关注公众号,回复: 17670188 查看本文章

2进制的数字每一位都是0~1的数字组成,不会有其它数字

比如二进制0001代表十进制1,二进制0010代表十进制2

 2.2二进制转十进制

我们都知道十进制123表示的值是一百二十三,为什么呢,因为十进制的每一位是有权重的,十进制的数字从右向左分别是各位,十位,百位....,没威威的权重分别是10 ^0, 10^1 , 10^2 ...

如下图:

 二进制和十进制是类似的,只不过二进制每一位的权重从右向左一次是:2^0,2^1,2^2...

我们来看一下二进制的1101:

 学会了二进制转十进制,那十进制又该怎么转化为二进制呢?

我们以125为例,将该数转换为二进制:

 这是十进制转二进制的基本方法,按照该方法我们能很好得到十进制对应的二进制。

2.3二进制转八进制和十六进制

2.3.1二进制转八进制

八进制的数字每一位都是0~7的数字,各自换算成二进制,最多有3个二进制未就够了,比如7的二进制是111,所以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算⼀个8进制位,剩余不够3个2进制位的直接换算。

例:二进制的01101011,换成八进制:0513,0开头的数字,会被当做8进制

 2.3.2二进制转十六进制

十六进制的数字每⼀位都是0~9,a ~f 的,0~9,a ~f的数字,各⾃写成二进制,最多有4个二进制位就⾜够了,⽐如 f 的⼆进制是1111,所以在二进制转十六进制数的时候,从二进制序列中右边低位开始向左每4个二进制位会换算⼀个十六进制位,剩余不够4个⼆进制位的直接换算。
例:二进制的01101011,换成十六进制:0x6b, 十六进制表示的时候前面加0x

3.原码,反码,补码

 整数的二进制表示方法有三种,即原码,反码,补码。

有符号整数的三种表⽰⽅法均有符号位和数值位两部分,2进制序列中,最⾼位的1位是被当做符号 位,剩余的都是数值位。符号位都是用0表示“正”,⽤1表示“负”。
正整数的原,反,补码都相同。
负整数的三种表示方法各不相同
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
反码得到原码也是可以使用:取反,+1的操作。
对于整数来说:数据存放内存中其实存放的是补码。

4.移位操作符

移位操作符移动的位数其实是整数补码的二进制位

注意:移位操作符的操作数只能是整数

4.1左移操作符

移位规则:左边抛弃,右边补0

看代码:

#include<stdio.h>

int main()
{
	int num = 10;
	int n = num << 1;
	printf("num == %d\n", num);//num == 10
	printf("n == %d\n", n);//num == 20
	return 0;
}

画图推理:

将num的二进制1010左移一位,最左边的0符号位抛弃,右边补0,变成了10100。

4.2右移操作符

右移操作符有两种移位规则:

1.逻辑右移:左边用0填充,右边丢弃

2.算术右移:左边用原值的符号位填充,右边丢弃

因为大部分编译器默认使用算术右移,所以我们这里也只介绍算术右移,代码如下:

#include<stdio.h>

int main()
{
	int num = -1;
	int n = num >> 1;
	printf("num == %d\n", num);//num == -1
	printf("n == %d\n", n);//num == -1
	return 0;
}

画图推理:

 由于上文已经说过,移位操作符移动位数是移动整数二进制的补码,所以我们首先需要将-1转换成补码的形式,也就是全1的32个比特位,向右抛弃一位,左边立刻补上符号位1(由于-1是负数,所以符号位为1),所以右移之后数值不变,依旧是全1的32个比特位,再转换为原码,十进制,依然是-1。

注意:对于以为运算符,不要移动负数位,这个标准是未定义的,报错如下:

5.位运算符 

位操作符有:

1  &    //按位与

2  |     //按位或

3  ^    //按位异或

4  ~   //按位取反

注意:它们的操作符也必须是整数

按位与运算:将两个数的二进制表示中对应位进行比较,只有当两个对应位都为1时,结果的对应位才为1,否则为0。

按位或运算:将两个数的二进制表示中对应位进行比较,只要两个对应位中有一个为1,结果的对应位就为1,只有当两个对应位都为0时,结果的对应位才为0。

按位异或运算:将两个数的二进制表示中对应位进行比较,当两个对应位不同时(即一个为0,另一个为1),结果的对应位为1;当两个对应位相同时(都为0或都为1),结果的对应位为0。

按位取反运算:它将操作数的二进制表示中的每一位取反,即0变为1,1变为0。

我们直接看代码:

int main()
{
	int num1 = -3;
	//-3
	//原码 10000000 00000000 00000000 00000011
	//反码 11111111 11111111 11111111 11111100
	//补码 11111111 11111111 11111111 11111101

	int num2 = 5;
	//5
	//原,反,补码相同
	//    00000000 00000000 00000000 00000101

	//根据补码计算,
	printf("%d\n", num1 & num2);// 5
	//11111111 11111111 11111111 11111101
	//00000000 00000000 00000000 00000101
	//00000000 00000000 00000000 00000101  这是补码需转换为原码,正数原,反,补相同
	//00000000 00000000 00000000 00000101  结果为5

	printf("%d\n", num1 | num2);//   -3
	//11111111 11111111 11111111 11111101
	//00000000 00000000 00000000 00000101
	//11111111 11111111 11111111 11111101  这是补码需转换为原码 负数取反加一
	//10000000 00000000 00000000 00000011  结果为-3

	printf("%d\n", num1 ^ num2);//   -8
	//11111111 11111111 11111111 11111101
	//00000000 00000000 00000000 00000101  
	//11111111 11111111 11111111 11111000  这是补码需转换为原码 负数取反加一
	//10000000 00000000 00000000 00001000  结果为-8

	printf("%d\n", ~num1);//   2
	//11111111 11111111 11111111 11111101
	//00000000 00000000 00000000 00000010  这是补码需转换为原码,正数原,反,补相同
	//00000000 00000000 00000000 00000010  结果为2

	return 0;
}

6.例题

6.1 例1:不能创建临时变量(第三个变量),实现两个数的交换

本题如果没有不能创建临时变量的条件,是非常简单的代码如下:

int main()
{
	int a = 3;
	int b = 5;
	int c = 0;
	printf("交换前:a==%d  b==%d\n", a, b);
	c = a;
	a = b;
	b = c;
	printf("交换后:a==%d  b==%d\n", a, b);
}

这个代码逻辑非常的简单,是很容易想到的,但是题目已经明确不能使用临时变量,所以该方法在这题中是不可取的。那么我们要怎样来解决这题呢,在这里我提供两种方法:

第一种:

int main()
{
	int a = 13;
	int b = 5;
	printf("交换前:a==%d  b==%d\n", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:a==%d  b==%d\n", a, b);
}

这个方法也不难,我们并没有创建临时变量,利用两数之和也完成了交换,符合题目的要求。但是本文中我们详细介绍了操作符,有没有可能用新学的操作符来完成该题呢?位操作符按位异或可以帮助我们解决这个问题。

按位异或运算:将两个数的二进制表示中对应位进行比较,当两个对应位不同时(即一个为0,另一个为1),结果的对应位为1;当两个对应位相同时(都为0或都为1),结果的对应位为0。

通过这个我们不难发现:

1.两数对应位不同时,结果对应位为1,所以一个数按位异或0等于它本身,即a^0==a

2.两数对应位相同时,结果对应位为0,所以一个数按位异或它本身等于0,即a^a==0

 这个题我们需要用到第二条,代码如下:

int main()
{
	int a = 13;
	int b = 5;
	printf("交换前:a==%d  b==%d\n", a, b);
	a = a ^ b;
	b = a ^ b;//相当于b=a^b^b  因为b^b=0,所以b=a
	a = a ^ b;//相当于a=a^b^a  因为a^a=0,所以a=b
	printf("交换后:a==%d  b==%d\n", a, b);
}

6.2 例2:求一个整数存储在内存中的二进制中1的个数(n&(n-1)算法)

求一个整数存储在内存中二进制1的个数,由于二进制数每一位的权重都是2的次方数,我们可以知道一个十进制数每次除以2都相当于二进制数右移一位,例如5的二进制是101,5/2=2,二进制101向右移一位等于二进制10,它们是相等的。通过进一步探究,我们可以发现:

5%2==1    5/2==2

2%2==0    2/2==1

1%2==1    1/2==0

 我们不难发现5每次除以2取余得到的就是对应二进制向右移的那一位,思路有了,上代码:

int main()
{
	int num = 666;
	int count = 0;
	while (num)
	{
		if (num % 2 == 1)
			count++;
		num /= 2;
	}
	printf("%d\n", count);
	return 0;
}

上述代码可以完成这个题目吗?并不完全能。当num是-1时,因为整数存放在内存中的是补码,答案应该是32,但是由于右移会不断补全符号位1,陷入死循环,不会得到正确结果。

想要解决这个问题,我们可以将-1转换为无符号整数,如下:

int main()
{
	int num;
	scanf("%d", &num);
	int count = 0;
	unsigned int temp = num;
	while (temp)
	{
		if (temp % 2 == 1)
			count++;
		temp /= 2;
	}
	printf("%d\n", count);
	return 0;
}

上述代码虽然可以得到正确的结果,但并不提倡这种写法,我们在写代码时应尽量避免这种转换。

刚刚我们利用的是不断除法取余相当于右移二进制位数,那么我们将二进制向左移会不会避免这种情况发生呢,实践一下:

int main()
{
	int num = -1;
	int count = 0;
	for (int i = 0; i < 32; i++)
	{
		if (num & (1 << i))
			count++;
	}
	printf("%d", count);
	return 0;
}

 循环32次,对应32个比特位,四个字节,可以包括Int类型的全部位数,我们通过num与不断向左移的1按位与,只有它们对应位都是1,num & (1 << i)才不会为0,count才会加一,可以得到正确的结果。

其实还有一种方法,它不需要循环32次,但他比较难以想到,利用n&(n-1)算法,我们可以很容易得到结果。

n&n(n-1)算法的作用是将整数 n 的二进制表示中的最低位的 1 变为 0

以10举例

10&9                 8&7

1010                 1000

1001                 0111

1000//等于8      0000//等于0

它每计算一次都会将 二进制表示中的最低位的 1 变为 0

 代码如下:

int main()
{
	int num = -1;
	int count = 0;
	while (num)
	{
		count++;
		num = num & (num - 1);
	}
	printf("%d", count);
	return 0;
}

 这个代码并不需要固定循环多少次,你的num二进制表示中有几个1,他就循环几次,比上面方法更好。

6.3将13⼆进制序列的第5位修改为1,然后再改回0

#include <stdio.h>
int main()
{
	int a = 13;
	a = a | (1 << 4);
	//00001101
	//00010000
	//00011101
	printf("a = %d\n", a);
	a = a & ~(1 << 4);
	//00011101
	//11101111
	//00001101
	printf("a = %d\n", a);
	return 0;
}

上述题目都很考验对位操作符和移位操作符 的熟练度,希望大家勤加练习,一起加油!!!