c++|超详细的操作符解析

还记得之前对操作符进行了简单的了解,并且学习了一些常见操作符的用法,如今我们又对操作符这部分的知识进行了更深一步的学习,有了一些新的感悟和体会,这篇文章我希望能把关于操作符的方方面面,包括一些细微之处,都说到位。
话不多说,让我们开始吧!
一.操作符分类

  • 操作符有哪些?可以分为几类:
  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用和结构成员
接下来我们对这些操作符一一进行阐述!
二.算术操作符
+-*/%
关于算数操作符,比较简单,这里说一些需要注意的:
1.除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除 法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
三.移位操作符
<<左移操作符
>>右移操作符
这是我们今天要重点介绍的操作符之一
注:他们的操作数必须是整数。
1.有关进制和原码、反码、补码
int a = 5; int b = a << 2;

这里的 a<<2 是把a向左移动2位,实质是:把a在内存中存储的二进制位向左移动两位
这里补充一下进制的知识:
  • 二进制:逢二进一
基数为2,数值部分用两个不同的数字0、1来表示。
  • 十进制:逢十进一
基数为10,数值部分用0、1、2、3、4、5、6、7、8、9来表示.
  • 十六进制:逢十六进一
基数是16,有十六种数字符号,除了在十进制中的0至9外,还另外用6个英文字母A、B、C、D、E、F来表示十进制数的10至15。
下面用一张图来帮助我们理解进制

c++|超详细的操作符解析
文章图片

下面我们还需要了解一下原码 、反码 、补码的知识 :
整数有3种二进制表示形式:
  • 原码
  • 反码
  • 补码
(1).原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制
[+1]原码 = 0000 0001
[-1] 原码 = 1000 0001
(2).反码

反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+1]=[00000001]原码=[00000001]反码
[- 1]=[10000001]原码=[111111110]反码
(3).补码
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+1]=[00000001]原=[00000001]反码=[00000001]补码
[-1]=[10000001]原=[11111110] 反码=[11111111]补码
经过上面的叙述,我们很容易发现;
  • 正整数:原码、补码、反码相同
  • 负整数:原码、补码、反码不同,需要计算
这里我们再举个例子看看:
c++|超详细的操作符解析
文章图片

而整数在内存中存储的是补码!!!
c++|超详细的操作符解析
文章图片
如图,-1的在内存中存储的是补码
而VS编译器是16进制展示
所以显示的内存是 f ff ff ff f
我们知道16进制的 f 就是10进制的15,所以这里对应的就是-1的补码

2.移位操作符
首先需要说明的是:
移位操作符操作的是补码
而打印或使用的时候,用的是补码
所以使用移位操作符会某个数的二进制的补码改变后,我们如果需要打印或使用这个数的二进制是,要先通过改变后的补码反推出改变后的原码
铺垫做的差不多了,下面正式介绍移位操作符 !
(1).左移操作符
移位规则:左边抛弃、右边补0
c++|超详细的操作符解析
文章图片

如图,通过左移操作符把num向左移了一位, 正数10的二进制的补码 左边抛弃、右边补0
变成了 00000000000000000000000000010100
计算结果就为 1*2^4+1*2^2=20 了
如果要打印
int num2=num<<1; printf("%d",num2);

这里的打印用的是num2的原码的值
需要注意的是:
这时num的值还是10,没有改变,只是我们计算了一下 num<<1 的结果而已
(2).右移操作符
移位规则:
首先右移运算分两种:
  1. 算术移位 :左边用原该值的符号位填充,右边丢弃
  2. 逻辑移位:左边用0填充,右边丢弃
到底是哪种取决于编译器,我们常见的编译器下都是算数右移
如果是正数,这两种结果一样
我们通过一张图来展示一下右移操作符的作用效果
c++|超详细的操作符解析
文章图片

我们用一个负数 -5 来看一下
c++|超详细的操作符解析
文章图片

经过算术操作符,结果是 -3
经过逻辑操作符,结果是3
警告? :
对于移位运算符,不要移动负数位,这个是标准未定义的行为。

int b = a >> -2;

这是不行的!
四.位操作符位操作符有:
&//按位与
|//按位或
^//按位异或
注:他们的操作数必须是整数。
这里的”位“指的都是二进制位
& 按位与 (对应的二进制位有0则为0,全1才为1)
|按位或(有1为1,全0为0)
^按位异或 (相同为0,相异为1)
(1).&按位与
我们举个例子
int a = 3 ; int b = -5 ; int c = a & b ;

我们用一张图表示运算的过程:
c++|超详细的操作符解析
文章图片

(2). |按位或
int a = 3 ; int b = -5 ; int c = a | b ;

c++|超详细的操作符解析
文章图片


结果是 -5
(3). ^按位异或

int a = 3 ; int b = -5 ; int c = a ^ b ;

c++|超详细的操作符解析
文章图片


【c++|超详细的操作符解析】结果是 -8

下面我们看曾经一道变态的面试题
不能创建临时变量(第三个变量),实现两个数的交换。
首先我们应该明确:
a ^ a = 0
a ^ 0 = a
上代码!
#include int main() { int a = 10; int b = 20; a = a^b; b = a^b; a = a^b; printf("a = %d b = %d\n", a, b); return 0; }



一个练习:
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
//方法1 #include int main() { int num= 10; int count=0; //计数 while(num) { if(num%2 == 1) count++; num = num/2; } printf("二进制中1的个数 = %d\n", count); return 0; } //思考这样的实现方式有没有问题? //方法2: #include int main() { int num = -1; 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 int main() { int num = -1; int i = 0; int count = 0; //计数 while(num) { count++; num = num&(num-1); } printf("二进制中1的个数 = %d\n",count); return 0; } //这种方式是不是很好?达到了优化的效果,但是难以想到。


五.赋值操作符赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
int weight = 120; //体重
weight = 89; //不满意就赋值
double salary = 10000.0;
salary = 20000.0; //使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10; int x = 0; int y = 20; a = x = y+1; //连续赋值

a的结果是21,这样写是没错,但不建议这样写
复合操作符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的效果。 比如:
int x = 10; x = x+10; x += 10; //复合赋值 //其他运算符一样的道理。这样写更加简洁。

六.单目操作符
!逻辑反操作
-负值
+正值
&取地址
sizeof操作数的类型长度(以字节为单位)
~对一个数的二进制按位取反
--前置、后置--
++前置、后置++
*间接访问操作符(解引用操作符)
(类型)强制类型转换
c++|超详细的操作符解析
文章图片

(1).逻辑反操作符 !
如:
int flag = 0;

则!flag = 1

int flag = 5;

则 !flag = 0
注:!flag的结果只能为1或0(真或假)












    推荐阅读