操作符详解
1.操作符的分类
算术操作符:+、-、*、/、%
移位操作符: << >>
位操作符:& | ^
赋值操作符:=、+=、-=、*=、/=、<<=、>>=、&=、|=、^=
单目操作符:!、++、–、&、*、+、-、~、sizeof、(类型)
关系操作符:>、>=、<、<=、==、!=
逻辑操作符:&&、||
条件操作符:?、:
逗号表达式:,
下标引用:[ ]
函数调用:( )
结构成员访问:. 、->
2.二进制和进制转换
2进制、8进制、10进制、16进制是数值的不同表示形式而已。
比如:数值15的各种进制的表示形式:
2进制:1111
8进制:17
10进制:15
16进制:F
16进制的数值之前写:0x
8进制的数值之前写:0
10进制是我们生活中经常使用的,我们已经形成了很多尝试:
10进制中满10进1
10进制的数字每一位都是0~9的数字组成的
其实2进制也是一样的:
2进制中满2进1
2进制的数字每一位都是0~1的数字组成的。
2.1. 二进制转10进制
10进制的每一位是有权重的,10进制的数字从右向左是个位、十位、百位……,分别每一位的权重是10的0次方 , 10的一次方 , 10的二次方 …
如下图:
2进制和10进制是类似的,只不过3进制的每一位权重,从右向左是:2的0次方、2的1次方、2的2次方……
如下图:
2.1.1 10进制转2进制数字
2.2 二进制转8进制和16进制
2.2.1 二进制转8进制
8进制的数字每一位是0 ~ 7的,0 ~ 7的数字,写成2进制,最多有3个2进制位就足够了,比如7的二进制是111,所以在2进制转8进制的时候,从2进制序列中右边低位开始向左每3个2进制位会换算一个8进制位,剩余不够3个2进制位的直接换算。
如:2进制的01101011,换成8进制:0152,0开头的数字,会被当做8进制。
2.2.1 二进制转换16进制
16进制的数字每一位是0 ~9,a ~ f的数字,各自写成2进制,最多有4个2进制位就足够了,比如 f 的二进制是1111,所以在2进制转16进制的时候,从2进制序列的右边低位开始向左每4个2进制位会换算一个16进制位,剩余不够4个二进制位的直接换算。
如:2进制01101011,换成16进制:0x6b,16进制表示的时候前面加0x
3.原码、反码、补码
整数的2进制表示方法有三种,即原码、反码和补码。
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当作符号位,剩余的都是数值位。
符号位都是用0表示正,用1表示负。
正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
补码得到原码也是可以使用:取反,+1的操作
对于整型来说:数据存放内存中其实存放的是补码
4.位移操作符
<<左移操作符 >>右移操作符
注:移位操作符的操作数只能是整数。
4.1左移操作符
移位规则:左边抛弃,右边补0
#include<stdio.h>
int main()
{
int n = 12;
//左移移动的是二进制序列
//12
//00000000 00000000 00000000 00001100
int m = n << 1;
printf("%d\n", m);
return 0;
}
左移后的结果是:24
我们可以看到,n的值没有发生变化。就只是移位后的结果是这个结果,这个结果赋值到m上。这就等价于下面的代码:
int n = 10;
int m = n + 1;
如果要让n发生变化:
n <<= 1;
让移位的结果赋值位n,n就发生了变化。
我们可以举一个负数的例子:
int main()
{
int a = -10;
//10000000 00000000 00000000 00001010-原码
//11111111 11111111 11111111 11110101-反码
//11111111 11111111 11111111 11110110-补码
int b = a << 1;
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
结果:
-20
-10
我们可以发现向左移动有乘2的效果
4.2 右移操作符
移位规则:右移运算分两种:
- 逻辑右移:左边用0填充,右边丢弃
- 算术右移:左边用原该值的符号位填充,右边丢弃
算术右移是原来的数字是正数用0填充,原来是负数用1填充。
右移到底是算术右移,还是逻辑右移,是取决于编译器的,但是大部分的编译器采用的都是算术右移。
#include<stdio.h>
int main()
{
int a = -10;
//10000000 00000000 00000000 00001010
//11111111 11111111 11111111 11110101
//11111111 11111111 11111111 11110110
int b = a >> 1;
return 0;
}
上面的代码在VS中采用的是算术右移,结果是:
-5
右移一位有除2的效果。
5.位操作符
位操作符有:
& 按位与
| 按位或
^ 按位异或
~ 按位取反
注意:他们的操作数必须是整数。
这里的位都是二进制,所以我们在参与运算的时候都需要转化为二进制。
5.1 &按位与
对应的二进制位进行按位与,有0则为0,同时为1才为1
int main()
{
int a = -5;
int b = 13;
int c = a & b;//对应的二进制位进行按位与,有0则为0,同时为1才为1
//-5
//10000000 00000000 00000000 00000101
//11111111 11111111 11111111 11111010
//11111111 11111111 11111111 11111011
//13
//00000000 00000000 00000000 00001101
//a&b
//00000000 00000000 00000000 00001001
printf("%d",c);
return 0;
}
结果为9
5.2 | 按位或
对应的二进制位或运算,有1就为1,两个同时为0才为0
int main()
{
int a = -5;
int b = 13;
int c = a | b;//对应的二进制位进行按位与,有0则为0,同时为1才为1
//-5
//10000000 00000000 00000000 00000101
//11111111 11111111 11111111 11111010
//11111111 11111111 11111111 11111011
//13
//00000000 00000000 00000000 00001101
//a|b
//11111111 11111111 11111111 11111111
printf("%d", c);
return 0;
}
结果为-1
当我们推导出a|b的二进制位时,这时只是补码,但是这是一个负数,需要变为原码,还需要进行转化,最终为-1.
5.3 ^ 按位异或
对应的二进制位进行异或运算,相同为0,相异为1.
int main()
{
int a = -5;
int b = 13;
int c = a ^ b;//对应的二进制位进行按位与,有0则为0,同时为1才为1
//-5
//10000000 00000000 00000000 00000101
//11111111 11111111 11111111 11111010
//11111111 11111111 11111111 11111011
//13
//00000000 00000000 00000000 00001101
//a^b
//11111111 11111111 11111111 11110110还是需要再转化
printf("%d", c);
return 0;
}
结果为:-10
5.4 ~按位取反
原来是0,改为1;原来是1,改为0.
int main()
{
int a = 0;
//00000000 00000000 00000000 00000000-补码
//11111111 11111111 11111111 11111111
//10000000 00000000 00000000 00000000
//10000000 00000000 00000000 00000001
printf("%d\n", ~a);//~a就是对a按位取反
return 0;
}
结果为:-1
分析几道面试题:
①不能创建临时变量(第三个变量),实现两个整数的交换。
int main()
{
int a = 10;
int b = 20;
printf("交换前:%d %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:%d %d\n", a, b);
}
上面代码将a+b放入a中,再用a+b的a减去b就是a,将吖的值放入b中,现在b中的值是a的值,再用a+b的a减去b,得到的就是b的值,将b的值放入a中。
但是上面代码也有一些问题,可能会有溢出的风险,如果交换的两个数都是较大的数字,两个相加就可能会超出编译器所限定的。
那么还有一种方式:
int main()
{
int a = 10;
int b = 20;
printf("交换前:%d %d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("交换后:%d %d\n", a, b);
}
我们需要知道的是^按位异或是相同为0,相异为1,我们可以推导出,0无论和哪个数字异或,都是这个数字本身。
上面的代码也可以写成如下形式,可以帮助理解:
int main()
{
int a = 10;
int b = 20;
printf("交换前:%d %d\n", a, b);
a = a ^ b;
b = a ^ b^b;//b和b按位异或得到的是0,0和任何数字按位异或,都是数字本身
a = a^a ^ b;
printf("交换后:%d %d\n", a, b);
}
b和b按位异或得到的是0,0和任何数字按位异或,都是数字本身
②编写代码实现:求一个整数存储在内存中的二进制中1的个数。
接下来我们来分析一下:
我们可以通过分析得出,上面这种方式可以写出该数字的二进制,接下来只需要进行判断,如果是1,就进行记数。
int main()
{
int n = 13;
int count = 0;
while(n)
{
if (n % 2 == 1)
count++;
n = n / 2;
}
printf("%d", count);
return 0;
}
但是这个代码对负数是有问题的。
假设我们现在要求15的二进制位1的个数。
int main()
{
int n = -1;
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
if (((n >> i) & 1) == 1)
{
count++;
}
}
printf("%d\n", count);
return 0;
}
还可以再改进一下:
这个表达式可以执行几次,说明就有几个1.
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
int i = 0;
while (n)
{
n = n & (n - 1);
count++;
}
printf("%d\n", count);
return 0;
}