1.操作符的分类
- 算术操作符: + 、- 、* 、/ 、%
- 移位操作符:<< 、>>
- 位操作符:&、 |、 ^
- 赋值操作符:= 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
- 单目操作符:!、++、–、&、*、+、-、~ 、sizeof、(类型)
- 关系操作符:> 、>= 、< 、<= 、 == 、 !=
- 逻辑操作符: && 、||
- 条件操作符: ? :
- 逗号操作符:,
- 下标引用:[ ]
- 函数调用:()
- 结构成员访问:. 、->
今天介绍一些与二进制有关的操作符,先了解一下二进制和进制转换的知识
2. 二进制和进制的转换
其实我们经常能听到 2进制、8进制、10进制、16进制 这样的讲法,那是什么意思呢?其实 2进制、8进制、10进制、16进制 是数值的不同表示形式而已。
比如:数值15的各种进制的表示形式:
15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F
//16进制的数值之前写:0x
//8进制的数值之前写:0
重点介绍一下二进制:
首先我们还是得从10进制讲起,其实10进制是我们生活中经常使用的,我们已经形成了很多尝试:10进制中满10进1
10进制的数字每一位都是0~9的数字组成其实二进制也是一样的
2进制中满2进1
2进制的数字每一位都是0~1的数字组成
那么 1101 就是二进制的数字了。
2.1 2进制转10进制
其实10进制的123表示的值是一百二十三,为什么是这个值呢?其实10进制的每一位是有权重的,10进制的数字从右向左是个位、十位、百位…,分别每⼀位的权重是 10^0 , 10^1 , 10^2 …
如下图:
10进制123每一位权重的理解
2进制与10进制是类似的,只不过2进制的每一位的权重,从右往左是:2^0, 2^1, 2^2 ……
如果是2进制的1101,该怎么理解?
2.1.1 10进制转2进制数字
2.2 2进制转8进制和16进制
2.2.1 2进制转8进制
8进制的数字每一位是 0~7 的,0~7 的数字,各自写成 2 进制,最多有 3 个 2 进制位(2^3 = 8)就足够了,比如 7 的二进制是 111,所以在 2进制转 8 进制数的时候,从 2进制序列中右边低位开始向左每3个2进制位会换算一个8进制位,剩余不够 3 个 2进制位的直接换算。
如:2进制的 01101011 ,换成8进制:0153,8进制表示的时候前面加 0
2.2.2 2进制转16进制
16进制的数字每一位是 0~9 , a ~f 的,0~9,a ~f 的数字,各自写成2进制,最多有4个2 (2^4 = 16)进制位就足够了,比如 f 的二进制是1111,所以在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进制位会换算一个16进制位, 剩余不够4个二进制位的直接换算。
如:2进制的 01101011 ,换成8进制:0x6b,8进制表示的时候前面加 0x
3.原码、反码、补码
整数的2进制表示方法有三种,即原码、反码、补码
有符号整数的三种表示方法均有符号位和数值位,2进制序列中,最高位的1位是被当成符号位的,剩余的都是数值位。
符号位都是用0表示“正”,用1表示“负”。
正整数的原、反、补码都相同。
负整数的三种表示形式各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
补码得到原码也是可以使用:取反,+1的操作。
对于整型来说:数据存放内存中其实存放的是补码。
为什么?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
4.移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数
4.1左移操作符
左移规则:左边丢弃,右边补零
#include<stdio.h>
int main()
{
int num = 10;
int n = num << 1;
printf("n = %d\n", n);
printf("num = %d\n", num);
return 0;
}
4.1右移操作符
右移规则:先右移,运算分两种:
逻辑右移:左边用0补充,右边丢弃
算术右移:左边用原该值的符号为补充(在VS2022使用算术右移的)
#include<stdio.h>
int main()
{
int num = 10;
int n = num >> 1;
printf("n = %d\n", n);
printf("num = %d\n", num);
return 0;
}
逻辑右移1位演示:
算术右移1位演示:
警告:对于移动运算符,不要移动负数位,这个是标准未定义的。
int num = 10;
num >> -1;//error
5.位操作符:&、|、^、~
位操作符有:
& //按位与
| //按位或
^ //按位异或
~ //按位取反
注:他们的操作数必须是整数。
看一段代码:
#include<stdio.h>
int main()
{
int num1 = -1;
int num2 = 3;
printf("%d\n", num1 & num2);//3
printf("%d\n", num1 | num2);//-1
printf("%d\n", num1 ^ num2);//4
printf("%d\n", ~0);//-1
return 0;
}
题目:
不能创建临时变量(第三个变量),实现两个整数的交换。
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
//打印前:
printf("打印前:a = %d b = %d\n", a, b);
a = a ^ b;
b = a ^ b;//a ^ b ^ b = a ^ 1 = a;
a = a ^ b;//a ^ a ^ b = 1 ^ b = b;
//打印后:
printf("打印后:a = %d b = %d\n", a, b);
return 0;
}
练习1:编写代码实现:求一个整数存储在内存中的二进制(补码)中1的个数。
//方法1:
#include<stdio.h>
int main()
{
int num = 10;//1010
int count = 0;//计数
while (num)
{
if (num % 2 == 1)
{
count++;
}
num = num / 2;
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
//思考这样的实现方式有没有问题?
//这种方法只能算无符号的数据,具有局限性
//方法2:
#include<stdio.h>
int main()
{
int num = -1;//int 类型是 4byte = 32bit
int i = 0;
int count = 0;//计数
for (i = 0; i < 32; i++)
{
if (num & (1 << i))
{
count++;
}
}
//打印
printf("二进制中1的个数 = %d\n", count);
return 0;
}
//这里必须循环32次的,能不能再优化。
//方法3:
#include<stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while (num)
{
count++;
num = num & (num - 1);//直接把二进制的1算出来
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
//这种方式是不是很好?达到了优化的效果,但难以想到
练习2:二进制位置0或者置1
编写代码将13 二进制序列的第5位修改为1,然后再改回0
13的2进制序列: 00000000000000000000000000001101
将第5位置为1后:00000000000000000000000000011101
将第5位再置为0:00000000000000000000000000001101
代码如下:
#include<stdio.h>
int main()
{
int a = 13;
a = a | (1 << 4);
printf("将第5位置为1后:a = %d\n", a);//29
//
a = a & ~(1 << 4);
printf("将第5位再置为0:a = %d\n", a);//13
return 0;
}
6.单目操作符
单目操作符有这些:
!、++、--、&、*、+、-、~ 、sizeof、(类型)