C语言——操作符详解

操作符的分类

  • 算术操作符:+、-、*、/、%
  • 移位操作符<< >>
    移动的是二进制位
  • 位操作符:&,|,^
  • 赋值操作符:=,+=,-=,*=,/=,%=,<<=,>>=,&=,|=,^=
  • 单目操作符:!,++,--,&,*,+,-,~,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)的,不会产生进位就不可能溢出。


按位取反—— ~

  • ~——按位取反在这里插入图片描述

位操作符运算规则总结:

总结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值