1、操作符的分类
算术操作符:+、-、*、/、%
移位操作符:<< >>
位操作符:& 、|、^
赋值操作符:= 、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=
单目操作符:!、++、--、&、*、+、-、~、sizeof、(类型)
关系操作符:>、>=、<、<=、==、==、!=
逻辑操作符:&&、||
条件操作符:?:
逗号操作符:,
下标引用:[ ]
函数调用:( )
结构成员访问:. 、->
关于第一个算术操作符我们已经理解了;下面的操作符我们要先了解一下关于2进制的概念
2、二进制和进制转换
我们经常听到的2进制、8进制、16进制,它们都是表示数值,只是它们表示数值的方式不同。
比如我们15的个进制表示形式
15的2进制:1111
15的8进制:017
15的10进制:15
15的16进制:0xF
0x表示16进制
0表示8进制
二进制是逢二进一,是由0和1数字组成的;
八进制是逢八进一,是由0~8的数字组成的;
十进制是逢十进一,是由0~9的数字组成的;
2.1 2进制转10进制
我们10进制的123是一百二十三;是因为每一位是有权重的,数字从右往左数,每一位的权重是10的0次方、10的1次方、10的2次方,以此类推,再把它们与各自的权重相乘,再相加;
2进制和10进制的类似,每个数字都有权重;2的0次方,2的1次方、2的2次方.......
如果是2进制1101;从右往左的数字,各自的权重相乘再相加。
2.1.1 10进制转2进制
我们把10进制数除于2取余数,再从下往上把余数写出来,就是10进制转2进制的数
10进制的125转2进制数是1111101;
2.2 2进制转8进制和16进制
2.2.1 2进制转8进制
8进制是由0~7的数字组成的,0~7的数字转2进制,最高位7:0111,看的出来只要三位数就可以可以表示0~7的数字了,那2进制数从右往左边数,每三位数换算成一个8进制数。
2进制的01101011,换成8进制:0153,0开头的数字表示8进制数,
01不够直接算;
反过来8进制数转2进制数,从右往左边数,每一个8进制数,转换为三位8进制数;
2.2.2 2进制转 16进制数
16进制是由0~9数字,a~f(10~15)组成的;16进制最高位 f 用二进制表示1111,看得出来只要4位数就可以表示0~ f 的每一位数字;二进制数从右边往左边数每4位二进制数转换1位16位进制数;
比如2进制的01101011,转换为16进制,0x6b;
反过来;16进制数转2进制数;每一位16进制数转化为4位2进制数;
3、原码、反码、补码
存在内存的值2进制存储的,那整数2进制的表示方法有3种:原码,反码,补码;整数由分为有符号整数和无符号整数,它们的表达方式是不一样的;
有符号整数:三种表示方法都有符号位和数值位两部分;在2进制中,最高位是符号位,剩下的是数值位;
最高位1表示负;最高位0表示正;
比如-7是有符号整型,是个负数;
-7是负数,4个字节,32个比特位;要存满,最高位是1表示负;剩下的是值;
直接按照正负的形式写成2进制的数是原码;
原码的符号位不变;其他的依次取反就是反码;
反码+1就是补码
正数的原码、反码、补码是一样的。
无符号整数是没有符号位的,都是数值位;
注意原码取反就是反码,反码+1就是补码;反过来补码-1就是反码;反码取反就是原码;
补码取反+1就是原码;
比如-7的补码
补码取反+1就是原码
对于整型来说:数据存放在内存中的就是补码;
在计算机里,数值一律用补码表示和存储。使用补码,可以将符号位和数值位统一处理;加法和减法也能统一处理,CPU只能算加法,补码和原码之间相互转换,运算过程是相同的,不需要额外的硬件电路。
CPU只能算加法,如果算减法就是1+(-1);
举例,如果数值存的是原码不是补码,那计算的结果就是-2了,符号位都相加了;
数值存的是补码,那么相加起来逢二进一,符号位就溢出了;刚好是一个无符号整数;
4、移位操作符
<< 左移操作符
>>右移操作符
注意:移位操作符的操作数只能是整数;
4.1左移操作符
比如5;5的补码
00000000 00000000 00000000 00000101 5的补码
5<<1; 我们左移操作符还是右移操作符操作的都是数值的补码;5的补码向左移了一位,然后右边补0;
#include<stdio.h>
int main()
{
int a = 5;
int b = a << 1;
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
运行之后的结果看的出,左移操作符是有x2的这种效果;
4.2 右移操作符
右移的规则是分两种的;
1、逻辑右移;左边用0填充;右边丢弃;
2、算术右移;左边用该值的符号位来填充;右边丢弃;
至于是用逻辑右移还是算术右移,是看编译器的,大致是用算术右移的;
比如10;10的补码;
如果是逻辑右移的话,那-10就变成5了;
10000000 00000000 00000000 00001010 -10的补码
00000000 00000000 00000000 00000101 逻辑右移
10000000 00000000 00000000 00000101 算术右移
大致上右移都是算术右移
int main()
{
int a = -10;
int b = -10 >> 1;
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
从结果上可以看出右移操作符有 /2 的效果;
注意 下面的写法都是错的,一个整型就是32个比特位,怎么能移50位呢?对于移负数位,在标准中这是没有定义的,弄不了,是错的;
int a =-10;
a>>50; a<<50
a<<-1 ; a>>-1
5、位操作符:& 、|、^、~
位操作符的操作的是数值的二进制位;把数值转换位二进制位这种形式,再对对应的二进制位进行计算;
& 按位与
| 按位或
^ 按位异或
~ 按位取反
比如 -5&13
int main()
{
int a = -5;
int b = 13;
int c = a & b;
printf("%d\n",c);
return 0;
}
我们算出-5的补码和13的补码得出结果就是9;
-5|13
int main()
{
int a = -5;
int b = 13;
int c = a | b;
printf("%d\n",c);
return 0;
}
-5的补码按位或13的补码;有1为1;得出的结果是个负数 的补码,还要取反+1得到原码放回C;结果是-1;
-5^13
int main()
{
int a = -5;
int b = 13;
int c = a ^ b;
printf("%d\n",c);
return 0;
}
按位异或,相同为0;相异为1;结果为-10;
~按位取反;比如~0
int main()
{
int a = 0;
printf("%d\n",~a);
return 0;
}
0的补码取反是负数的补码;要取反+1的原码;结果是-1;
举例; 不 能创建临时变量(第三个变量),实现两个数的交换。
我们实现两个数的交换通常都是创建一个中间变量;
int main()
{
int a = 10;
int b = 20;
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);
return 0;
}
但是题目说不能使用第三个变量,我们可以这样做;
先把a+b的值赋予a;
再a-b的值赋予b;这个a是a+b的值,a-b就是a的值,赋予b;
然后a-b的值赋予a;这个a还是a+b的值,但这b是上条代码a的值,a+b-a就是b,赋值给a;
int main()
{
int a = 10;
int b = 20;
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);
return 0;
}
注意,这个代码是有问题的,如果a和b的值太大,那就会有溢出的问题。
还有一种方法就是按位异或;相同为0,相异为1;
假设a=3;a^a=0;每个二进制都相同,结果为0;
a^0=a;a转为二进制为011,011^0相同为0,相异为1,那就是 011,就是它本身a;
先把a^b的值赋予a;
这时a=a^b,那下面代码就是b=a^b^b,b^b等于0,a^0等于a,所以就把a赋予的b;
此时a还是等于a^b;根据上条代码此时b的值是a;那就是a=a^b^a;a^a等于0;0^b等于b,然后再赋予a,所以a=b;
int main()
{
int a = 10;
int b = 20;
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);
return 0;
}
练习1
编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数
比如我们取10进制里面的每一位就是%10,取余数,然后去掉,那换过来取2进制里的每一个位,那就是%2,取余数,再/2去掉;得到的余数再从下往上数就是13的2进制位1101;
那我们转化成代码,只要是余数是1,那就++,表示2进制位里有几个1;
int main()
{
int a = 13;
int count = 0;
while(a)
{
if (a % 2 == 1)
{
count++;
}
a /= 2;
}
printf("%d\n",count);
return 0;
}
但是这个代码是有问题的,如果是-1呢?
-1原码是10000000 00000000 00000000 00000001
-1补码是111111111 111111111 1111111111 111111111
-1是由32个1的,如果这个代码是-1,那-1%2!=1,条件不成立,count不++;-1/2为0,循环结束,结果是0;是不对的;
那我们换一种方法,我们每次都取二进制位的最低位,然后再&上1;这样如果最低位是1,那就++,表示有二进制位有几个1;每算一次,就向右移1位,再算最低位,一共算32次;
算负数,-1结果是32,这个代码是正确的,但不是很简洁,假设算1,那只有1个1,但还是要算32次,那效率不高;
int main()
{
int i = 0;
int n = -1;
int count = 0;
for (i = 0; i < 32; i++)
{
if (((n >> i) & 1) == 1)
count++;
}
printf("%d\n",count);
return 0;
}
其实还有一种更简便的方法:n = n & (n - 1) ,假设n是13,1101,那我们按照这个公式算一次,结果是1100,它把最右边的1去掉了,那再算一遍,结果是1000,又把最右边的1去掉了,再算一次,结果还是把最右边的1去掉了,那我们可以看出来,这个代码每算一次就去掉一个1;n - 1那对应的位就是0,后面剩下的位也是0,按位与的结果就把1给去掉了;
那我们转化成代码,只要n不是0;这个代码执行多少次那就有多少个1;每次执行-1,直到n=0,就停止循环,假设是13,那13,1101有3个1,就执行了3次,效率高了;
int main()
{
int n = 13;
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
printf("%d\n",count);
return 0;
}
再举个例子:判断n是不是2的次方数
那2的次方数就是1,2,4,8,16,2的0次方,2的1次方.........
我们转为2进制数
2的0次方,1,0001
2的1次方,2,0010
2的2次方,4,0100
2的3次方,8,1000
2的4次方,16,10000
看的出来,2的次方数的2进制位都是只有一个1,那我们用上一条代码,n = n & (n - 1) ,-1后,就是为0,那我们判断一下只要-1后为0,那就是2的次方数;
int main()
{
int n = 0;
scanf("%d",&n);
if ((n & (n - 1)) == 0)
{
printf("yes");
}
else
{
printf("no");
}
return 0;
}
练习2
⼆进制位置0或者置1
编写代码将13⼆进制序列的第5位修改为1,然后再改回0
我们先将13的二进制序列改为的第5位改为1,那我们就在第5位按位或上一个1,那这个怎么来,那就是1<<4,13&1<<4;
那我们再改回0,那把第5位按位与0,那剩下的按位与1,原来是0,还是0,原来是1还是1,
那就是1<<4后取反,~(1<<4);
int main()
{
int n = 13;
n = n | (1 << 4);
printf("%d\n",n);
n = n & (~(1 << 4));
printf("%d\n",n);
return 0;
}
感谢观看!感谢指正!