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二进制转十六进制
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;
}
上述题目都很考验对位操作符和移位操作符 的熟练度,希望大家勤加练习,一起加油!!!