文章目录
操作符的分类
- 算术操作符:+、-、*、/、%
- 移位操作符<< >>
移动的是二进制位 - 位操作符:&,|,^
- 赋值操作符:=,+=,-=,*=,/=,%=,<<=,>>=,&=,|=,^=
- 单目操作符:!,++,--,&,*,+,-,~,sizeof,(类型)
- 关系操作符:>,>=,<,<=,==,!=
- 逻辑操作符:&&,||
- 条件操作符:? :
- 逗号表达式:,
- 下标引用:[ ]
- 函数调用:()
- 结构成员访问:. 、 ->
二进制和进制转化
其实我们经常能听到2进制,8进制,10进制,16进制这样的讲法,那是什么意思呢?
其实2进制,8进制,10进制,16进制都是数值的不同表示形式而已。
比如:数值15的各种进制的表示形式:
15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F
//16进制的数值之前写:ox
//8进制的数值之前写:o
我们重点介绍一下二进制:
首先我们还是得从10进制开始讲起,10进制是我们生活中经常使用的:
- 10进制中满10进1
- 10进制的数字每一位都是0~9的数组组成
其实二进制也是一样的:
- 2进制中满2进1
- 2进制的数字每一位都是0~1的数字组成
那么1101就是二进制的数字了
2进制转10进制
其实10进制的123表示的值是一百二十三,为什么是这个值呢?
其实是因为10进制的每一位是有权重的
什么叫权重呢?
就是这一位表示什么。
例如:个位上权重是10^0就表示是1,十位上权重是10^1就表示是10,百位上权重是10^2就表示一百
2进制换10进制的方法:
2进制和10进制是类似的,只不过2进制的每一位的权重,从右向左是:2^0,2^1,2^2…
如果是2进制的1101,该怎么理解呢?
所以1101这个二进制对应的10进制的数值是13
10进制转2进制
那我们将125这个十进制数字转为2进制怎么转呢?
首先我们先用125÷2=62余1,再将62÷2=31余0,再将31÷2=15余1,依次除到商为0。每个步骤的余数由下往上就是10进制换出的2进制
2进制转8进制
8进制的数字都是由0~7的数字组成的。
0~7的数字,各自写成2进制,最多有3个2进制位就足够了,因为数字最大的7写成二进制是111。
0:000(2进制)
1:001
2:010
3:011
4:100
5:101
6:110
7:111
所以在2进制转8进制的时候,从2进制序列中从右边开始向左每3个进制位会换算为一个8进制位,剩下不够3个2进制位的直接换算。
如:2进制的01101011,换成8进制:0153,0开头的数字,会被当做8进制
所以2进制换8进制,就是从2进制序列中从右往左每3位换算一个8进制位,并且算出来的数字要在前面加一个0,因为拿0开头的数字会被当成8进制的数字
我们来感受一下数字前面加上0的效果:
2进制换10进制是每一位*权重
2进制换8进制是每3为*权重。(每3为*权重得出来的数就是8进制的)
2进制转16进制
16进制的数字每一位都是0~9,a~f的。
0~9,a~f的数字,各自写成2进制,最多有4个2进制位就足够了,因为最大的f二进制是1111
0:0000(2进制)
1:0001
2:0010
3:0011
4:0100
…
9:1001
a:1010
…
e:1110
f:1111
所以在2进制转16进制的时候,从2进制序列中从右向左每4个2进制位换一个16进制位,剩余不够4个二进制的直接换算
如:2进制的01101011,换成16进制:0x6b,16进制表示的时候前面+0(数字0)x(字母x)
那么10进制怎么转换为16进制呢?
将10进制转为2进制,再将2进制转为10进制就可以了
10进制怎么转为8进制呢?
同理,将10进制转为2进制,再转为8进制
原码、反码、补码
整数的2进制表示方法有三种,即原码,反码和补码
有符号(有正负值)整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。
符号位都是用0表示“正”,用1表示“负”。
无符号(无符号,只有0和正整数)整数没有符号位的概念,所有位都是数值位
正整数的原,反,补码都相同
(对于正整数来说,原码求出来后就不用求反,补码了。因为反,补码与原码相同)
负整数的三种表示方法各不相同
(对于负整数来说,三种表示方式各不相同,需要计算求取)
原码:一个整数的原码是直接将数值按照正负数的形式翻译成二进制,得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反(原本是1则变为0,原本是0则变为1)就可以得到反码。
补码:反码最低位+1就得到补码。
注意:+1时逢2进1(因为是2进制)
补码得到原码也是可以使用:取反,+1的操作
对于整型来说:数据存放内存中其实存放的是补码,计算也是用补码进行计算的
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于:使用补码,可以将符号位和数值域统一处理。同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
我们不用补码,用原码进行计算看会出现什么问题:
算1-1,因为CPU只能算加法,所以会转换为:1+(-1)来进行计算
1的原码:00000000000000000000000000000001
-1的原码:10000000000000000000000000000001
它们加起来时最后面逢2进1,但是前面的符号位该怎么办呢?
如果前面+1就变成了:
10000000000000000000000000000010 —— -2(10进制)
这样1-1就为-2了,明显不对
如果前面不+1:
00000000000000000000000000000010 —— 2(10进制)
这样1-1=2,也不对
此时我们用补码来计算:
1的补码:00000000000000000000000000000001
-1的补码:11111111111111111111111111111111
它们相加就会变成:100000000000000000000000000000000(因为一直往前进1就会变成33位,但是我们算出的结果还应该是整数,也就是32个bit位,所以此时最高位1就被丢了,剩下32个0,所以最后结果就是0了)
我们发现用补码进行计算不会出问题,所以我们内存中存的是补码,计算用的也是补码
补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。是什么意思呢?
我们知道,原码——>补码需要取反得到反码,反码再+1.
所以,补码得到原码——>补码-1,反码再取反
但是,从补码到原码还有另一种方法,就是直接“取反,+1”得到原码
移位操作符(移动的是二进制位)
<< 左移操作符
>>右移操作符
注:移位操作符的操作数只能是整数(只能给整数进行移位)
左移操作符
移位规则:左边抛弃,右边补0(对补码进行移位)
移动的时候是2进制序列向左移,当我们左移完后得到的2进制序列就赋给b,此时这个2进制序列是补码,所以我们要求出原码。而最高位是0,0就表示正整数,正整数的原,反,补码都一样。所以补码就是原码,根据原码可知,赋值给b的就是12.
左移一位其实有乘2的效果,6*2=12
因为向左移一位原本对应的权重变了
左移2个比特位的计算:
n*2^2=11*2^2=44
那么a的值是什么呢?我们运行程序看看
当然,如果想让a的值变化的话,可以写上:a=a<<1;
(a左移后再赋值给自己)
或者:a<<=1;
(符合赋值,左移等)
那么负数的移位怎么移呢?
注意:移位是相对于整数的2进制补码进行移位的,打印出来的结果要换成原码
并且不能乱移位,例如向左移动100位(a<<100),因为此时这个2进制序列只有32位,不能乱移
右移操作符
移位规则:首先右移运算分两种:
右移到底是算术右移,还是逻辑右移。是取决于编译器的实现,常见的编译器都是算术右移
例如:
写出-1的原,反,补码:
原码:10000000000000000000000000000001
反码:11111111111111111111111111111110
补码:11111111111111111111111111111111
- 逻辑右移:左边用0填充,右边丢弃(因为左边是用0填充的,所以无论这个数一开始是正数还是负数,全都变为正数了)
将-1逻辑右移一位:
此时左边补个0后,就变成正数了。而正数的原,反,补码相同,所以这个2进制序列可以认为是原码,这将会是一个非常大的正数 - 算术右移:左边用原该值的符号位填充,右边丢弃
将-1算术右移1位:
2进制序列向右移了1位后左边空了一位出来,然后用原本(未移的)2进制序列中的符号位“1”填充,右边丢弃。
此时发现-1算术右移后还是-1,我们用编译器来实现验证一下:
右移对应的公式:n/2^x
(n为原本的10进制数字,x为向右移动的几位)
警告:对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num=10;
num>>-1;//移动负数位,错误
总结:
- 左移 1 位, 相当于原数字 * 2. 左移 N 位, 相当于原数字 * 2 的N次方
- 右移 1 位, 相当于原数字 / 2. 右移 N 位, 相当于原数字 / 2 的N次方
- 由于计算机计算移位效率高于计算乘除, 当某个代码正好乘除 2 的N次方的时候可以用移位运算代替
- 移动负数位或者移位位数过大都没有意义
位操作符:&、|、^、~
注:它们的操作数必须是整数,并且操作的是2进制的位
位操作符有:
按位与——&
- &——按位与:算出它们各自2进制的补码,然后进行计算(只要有0则为0,同时为1才为1)
按位或—— |
- | ——按位或:算出它们各自2进制的补码,然后进行计算(两补码只要有1则为1,同时为0才为0)
注意:最后别忘了自己将补码换成原码,因为计算机里存放的是补码,给我们在屏幕上看到的是原码的结果
按位异或——^
- ^ ——按位异或:算出它们各自2进制的补码,然后进行计算(相同为0,相异为1)
一道变态的面试题
不能创建临时变量(第三个变量),实现两个整数的交换。
我们常使用的最简单的方法:创建一个第三个变量
所以我们要遵循题目来解决问题:
以这段代码可以实现交换,但是却存在一个潜在的问题:
如果a和b很大呢(a和b没超出整型的大小)?但它们相加的结果却超出了整型能表示的最大值,一旦超出整型能表示的最大值时有些位就丢了,溢出后a里面就不再是和。不再是和相减就不能得到正确的另一半数据,问题就在这里。
所以我们得再找另一种方法:使用按位异或
这样就可以完成题目了,那么这是为什么呢?
我首先要知道:
a ^ a = 0 ;
0 ^ a = a ;
这是因为:
a=3;
00000000000000000000000000000011——a的原,反,补码
异或:相同为0,相异为1
a ^ a:
00000000000000000000000000000011
00000000000000000000000000000011
00000000000000000000000000000000——异或后的结果
因为开头是0,所以此补码也是原码,结果是0
所以:a ^ a =0;
那么0 ^ a 呢?
00000000000000000000000000000000——0的补码
00000000000000000000000000000011——a的补码
00000000000000000000000000000011——异或后的补码
因为开头是0,所以此补码也是原码,结果是3,也就是a
所以:0 ^ a=a;
所以:
两个数相同,它们按位异或就是0;
0和任何数异或,结果都为那个数字;
接下来我们来分析代码:
a = a ^ b;
b = a ^ b;
//b=a^b^b
//b=a^0
//b=a
//把a的值赋给b
a = a ^ b;
//这个式子b里放的是a的值,而前面的a还是a=a^b
//a=a^b^a
//a=a^a^b
//a=0^b
//a=b
//把b的值赋给a
因为第一行代码a = a ^ b,那么第2行的 b = a ^ b 可以看为b = a ^ b ^ b,而b^b为0,a^0则为a;
第三行代码a = a ^ b,b已经变为a的值了,所以:a=a^a,但是前面的a = a ^ b,所以:a=a^a^b。即a=0^b
这样就解决了溢出的问题。因为异或规则:相同为0,相异为1,是不会产生进位(满2进1)的,不会产生进位就不可能溢出。
按位取反—— ~
- ~——按位取反
位操作符运算规则总结:
总结: