一、算术操作符
算术操作符包含:+、-、*、/、%。(加,减,乘,除,取模)
此处主要说明除法 / 和取模 % 。
1.除法 /
①在整数与整数进行除法时,规定为整数除法,商即为结果:如9/4商2余1,则在编译器中结果为2;
②其余情况,凡是有小数出现,则为小数除法,以定义类型为准保留小数位:如定义为double类型,9/4结果为2.250000。
2.取模 %
取模操作只能用于整数与整数之间,如9/4结果为1。
二、移位操作符
移位操作符包含:左移<< ,右移>> 。移的是二进制位,只考虑整数的移位。
在讲述左移与右移操作符之前,我们先要理解并掌握二进制的几种表现形式。
二进制数
二进制数分为原码、反码、补码。对于整数来说,int类型----->4字节----->32bit位,所以在内存中存储32位。
二进制数又分为有符号数与无符号数:
①有符号数
一般的定义均为有符号数,首位为符号位。
正数的首位为0,负数的首位为1。
②无符号数
被unsigned int定义的整数即为无符号数,首位均为0。
切记:所有数的二进制数以补码的形式存储在内存当中。这意味着当我们在进行一些需要用到二进制位的运算时,得优先求出数的补码。
一般区分原反补码分为三种情况:
①:0,二进制表现为00000000000000000000000000000000,原反补码相同。
②:正数,原反补码相同。如5的原反补码均为00000000000000000000000000000101。
③:负数,原码照求,首位为1。反码:原码首位不变,其余位按位取反;补码:反码+1。如-5的原码为10000000000000000000000000000101,反码为11111111111111111111111111111010,补码为11111111111111111111111111111011。
由于经常用到的是补码和原码,这里提供两种转换方法:
1.左移 <<
左边丢弃,右边补0。左移1位,结果两倍(5左移1位得到10,-5左移1位得到-10)。
前面提到了,移位移动的是二进制位,因此应先写出数所对应二进制位,再进行移动 。
正数的左移
例如将m=5左移一位:5的补码与原码相同,为:00000000000000000000000000000101。
如何移动呢?如图所示:
左移即左边丢弃,右边补0
下图打印的结果也能很好的说明这一点:
负数的左移
对于负数的左移是同样的,只不过要比正数要稍显繁琐,因为正数的补码与原码相同,只用求出原码即可,而负数得求原码与补码。如-5,原码10000000000000000000000000000101,补码:11111111111111111111111111111011,左移后得到补码:11111111111111111111111111110110,取反加一得到左移后原码:10000000000000000000000000001010即为-10。同样我们验证一下:
2.右移 >>
右移相对于左移而言要复杂一些。可以将右移分为逻辑右移与算术右移。具体何种右移方式取决于编译器,大部分都是算术右移。
逻辑右移
左边补0,右边舍弃。(基本不需要考虑逻辑右移)
算术右移
右移一位,结果变为原来的一半。(如6右移一位为3,-6右移一位为-3,正负奇数的右移结果稍微不同)
负数左边补1,右边舍弃;
正数左边补0,右边舍弃。
对于算术右移而言,左边补0还是1取决于原来的数的正负,相当于补的是符号位。
①正数的算术右移
5的补码:00000000000000000000000000000101,
算术右移1位后补码:0000000000000000000000000000010。
算术右移1位后原码:0000000000000000000000000000010即为2。
②负数的算术右移
-5的原码:10000000000000000000000000000101
-5的补码:11111111111111111111111111111011
算术右移1位后补码:11111111111111111111111111111101
算术右移1位后原码:10000000000000000000000000000011即为-3。
三、位操作符
位操作符包含:& 、| 、^ 。(按位与、按位或、按位异或)
位操作符同样作用于二进制表达,只支持整型,均支持结合律。
1.按位与 &
有0为0,同1为1。
某数a移动位数然后&1可以判断该数某位上是0还是1。通过移动后&1得到结果是0还是1判断
5&3:
2.按位或 |
有1为1,同0为0。
a | 0 = a。
5|-3:
3.按位异或 ^
相同为0,相异为1。
a ^ a = 0; a ^ 0 = a。
5^-3:
异或实现两个数的交换
如何不创建临时变量实现两个数的交换?使用异或操作符!
下面详细说明一下两个数交换的实现方法:
1.中间变量法(最常用)
int a = 10 ;
int b = 15 ;
int c = 0 ;
c = a ;
a = b ;
b = c ;
2.和差变换法
此方法不适用于过大的两数,相加若超出限制会导致结果不正确。
int a = 19;
int b = 20;
a = a + b;
b = a - b;
a = a - b;
3.异或操作符实现两数交换
由于异或操作的特性:a^b^b = a , 可以通过异或来实现两数的交换。
异或交换两数本质上是通过二进制的变化与复位来交换,不会改变数的大小,因此不会出现方法二数目太大导致越界使二进制位丢失的问题。
int a = 15;
int b = 13;
a = a ^ b;
b = a ^ b;
a = a ^ b;
给出交换的实际过程:
综上其实最常用且可读的还是使用中间变量来交换两数,但是如果问及其他不采用中间变量交换两数,要知道有这两种交换方式。
四、赋值操作符
将已存在的变量进行赋值操作。
值得注意的是,对变量进行初始化并不是赋值操作,如int m = 0;并不是一个赋值操作,而是m这个变量的初始化过程,对m的后续赋值才是赋值操作。
复合赋值符:如+=、-=、*=、/=、%=、>>=、<<=、&=、|=、^=。举个简单的例子:a=a+1和a+=1等效。
五、单目操作符
常见如:正值+、负值-、逻辑反操作符!、操作数的类型长度sizeof、二进制按位取反~、前置后置++、前置后置--、解引用操作符*、取地址操作符(间接访问操作符)&、强制类型转换:(类型)。
对于逻辑反操作符!而言,!0即为1,除0外其他数执行!反操作均为0。
++(自增1)、--(自减1)。前置先自增(自减)1,后进行赋值。后置先进行赋值再自增(自减)1。
1.sizeof
sizeof求类型的长度或者是类型所对应变量的大小。
回顾一下:数组传参传的是首元素的地址,指针用于存放地址,64位下8字节,32位下4字节
sizeof的返回值类型为size_t,为无符号类型,打印sizeof的返回值时使用%zd(sizeof专用)、%u(打印无符号类型使用)、%d均可。
由于sizeof后接变量时可以不带括号,由此可以说明sizeof不属于函数,而是一种单目操作符。
但是其后若接类型,则需要带上括号如上图所示。int类型4字节,因此打印出来均为4。
sizeof同样可以求数组在内存中占用的大小,同时若求出数组中某一位的大小还能够通过除法求得数组的长度:
2.~
对二进制按位取反。
上面讲过了位操作符&、|、^,其中有一些特殊的用法,配合上单目操作符~可以达到一些神奇的效果,例如:a=13,二进制为00000000000000000000000000001101,我们想要将右数第五位变为1,怎么实现呢?通过前面的或操作符|就可以实现:
同理,这时候我们想变回原来的a=13,想将右数第五位变回0,那么可以运用相似的原理:
将a与上1左移4位的按位取反,即可将第五位的1变回0。
3.强制类型转换(类型)
如 int a = 5.20 ; 编译器会默认5.20为double类型,而定义的a为int类型,因此会发出警告。此时我们如果需要int类型,那么就可以将5.20进行强制类型转换:int a = (int) 5.20 ;即可。强制类型转换在万不得已的情况下才进行使用。
六、关系操作符
关系操作符包含:>、>=、<、<=、==、!=。
主要注意区分赋值操作符=与关系操作符==。
七、逻辑操作符
逻辑操作符包含:逻辑与 && 、逻辑或 || 。
&&:有0为0,全1为1。
|| : 有1为1,全0为0。
下图所示:打印出1,1,2,3,0。对于&&而言:左边操作数为假,则右边无需再进行计算。
下图所示:打印出1,1,2,3,1。对于||而言:左边操作数为真,则右边无需再进行计算。
对于&&而言:左边操作数为假,则右边无需再进行计算。
对于 || 而言:左边操作数为真,则右边无需再进行计算。
这两种特性我们称之为短路操作。
八、条件操作符(exp ? exp : exp)
唯一的三目操作符。a ? b : c (a为真执行b,a为假执行c)
举个简单的例子,求两数较大值,除了常见的if判断语句,我们也可以通过条件操作符实现:
九、逗号操作符(exp , exp , exp , ……)
逗号相隔的每个表达式都需要进行计算,最终的结果取决于最后一个表达式的值。
如上图所示,m = (a > b, b + 9, a - 7);等同于 m = a - 7;因此m = 3。
十、下标引用、函数调用、成员访问操作符
1.下标引用操作符[ ]
数组中常见,如定义数组后,打印数组中下标为0的元素等,需要注意的是,数组初始化与定义的时候使用到的 [ ] 并不是下标引用操作符。
2.函数调用操作符()
常见如使用频率极高的printf(),打印函数的()就是一个函数调用操作符。这里我们需要注意的是,操作符接触的操作数最少为一个:即只有函数名不需要传参。
printf的函数调用操作符()的操作数就是2个,左边的函数名printf和右边的字符串。
3.成员访问操作符 . 和 ->
成员访问操作符针对结构体。结构体变量访问成员使用操作符 . ;结构体指针访问成员使用操作符->(或者将指针解引用再使用 . )。
下面举一个简单的例子说明 . 与 -> 的使用。
Extra 表达式求值
所有的操作符都是作用于简单或者复杂的表达式进行求值,因此接下来详细展开表达式求值的相关说明。
1.隐式类型转换(整型提升)
当存在低于整型类型的数参与表达式计算且参与表达式计算的数没有比整型更大类型时,编译器会将低于整型的数的二进制自动填充至4字节32bit位。如char类型原1字节8bit位,若定义char a = 1;原本二进制为00000001,编译器会自动填充二进制为,00000000000000000000000000000001。
那么,我们需要明白是如何填充的,填充0还是1呢?
其实判断方式和上面讲到的右移类似。对于无符号数而言当然无脑填充0,但是在VS中,char属于signed char,即有符号数,那么首位为符号位,依据首位为0或1决定了填充0还是1。
有符号数填充首位的数字即可。上述二进制填充被称之为整型提升。(前提是没有任何高于整型的类型参与运算)
我们可以通过sizeof求类型长度来判断某个低于整型的类型的数是否进行了整型提升这一过程:
上图很明显的看出,当char类型的整数参与表达式运算时,编译器会对其进行整型提升,使其在内存中占满4字节,所以sizeof (-a)与sizeof(+a)的值均为4,而未进行表达式运算时便不会进行整型提升了,所得值便为1(char类型1字节8bit位)。
同样,我们举一个相加减的例子:当然最后求完之后只能保留8bit位(char类型)。
2.算术转换
当存在高于整型类型的数的表达式运算时,就要转换到最高类型的数的二进制bit位,这个过程即为算术转换。
int、unsigned int、long int、unsigned long int、float、double、long double。
出现以上类型的数进行表达式运算,我们就需要考虑算术转换而非整型提升。
3.操作符的属性
计算顺序所依照的三部曲:操作符的优先级--->操作符的结合性--->操作符是否控制求值顺序。
如果按照以上规则无法确定唯一计算路径,那么该复合表达式就存在问题。
如下图:结果可以是10(3+3+4)或者12(4+4+4)。
下图为操作符优先级、结合性、对求值顺序控制的说明。从上往下优先级依次降低。
这里就简单说明一下有几个操作符能够控制求值顺序:&&和 || 有短路操作;逗号表达式的值取决于最后一个表达式;条件操作符取值看前置条件为真还是假。
希望自己能真正理解并掌握上述内容,有问题也请评论留言~