操作符分类
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
数组引用、函数调用、结构体成员访问操作符
操作符详解
算术操作符
+ - * / %
算术操作符中要注意的就是除号和取模。除号有整数的除法和小数除法,只有当除数或被除数至少有一个浮点数才会执行浮点数除法。
取模%计算的是整除之后的余数,两个操作数都必须是整数。
移位操作符
左移操作符和右移操作符: << 、 >>
这两个移位操作符的操作数只能是整数(无符号数当作正整数处理)
注意移位操作符是对操作数的二进制位进行移位。在了解移位操作符的作用之前,我们首先要了解一下整数的二进制的三种表示:原码、补码和反码。规定:正整数的原码、反码和补码相同。负整数的反码和补码是需要计算的。0也是可以当成正数,原码反码补码都是00000000 00000000 00000000 00000000.整数在内存中都是以补码的形式存储的,位操作符操作的就是整数的补码。
整数的原码:整数四个字节,也就是三十二个比特位,最高位为符号位,正整数的符号位为0,负整数的符号位为1。
负数反码:原码的符号位不变,其他位按位取反(原来是1的变成0,原来是0的变成1)
负数补码:反码+1
例如-7,-7的原码是 10000000 00000000 00000000 00000111
-7的反码是 11111111 11111111 11111111 11111000
-7的补码是 11111111 11111111 11111111 11111001
对于左移操作符(<<):最左边一位丢弃,右边补0
7的补码: 00000000 00000000 00000000 00000111
int a=7<<1 : 00000000 00000000 00000000 00001110
此时a的符号位为0,整数的原码反码补码相同,所以a的原码就是他的补码,所以a的值为14
-7的补码 :11111111 11111111 11111111 11111001
int a=-7<<1 :11111111 11111111 11111111 11110010
此时a的符号位为1,我们要根据他的补码来计算原码才能知道a的值
a的反码:11111111 11111111 11111111 11110001
a的原码:10000000 00000000 00000000 00001110
所以a的值是-14
从这里我们可以知道,左移有乘二的效果。
右移操作符:
右移操作其实分为两种,一种是逻辑右移,一种是算术右移
逻辑右移:右边舍弃,左边补0;
算术右移:右边舍弃,左边补原来的符号位
这两者的区别就是左边补的是0还是原来的符号位,对与逻辑右移,简单粗暴,直接补0.而算术右移则是补原符号位。这样看来是算术右移更加合理,而事实也是现在绝大部分编译器采用的都是算术右移,对于逻辑右移,我们做了解就好。
例如
int a=7>>1; 7的补码:00000000 00000000 00000000 00000111
a的补码:00000000 00000000 00000000 00000011
所以a的值为3
int a=-7>>1: -7的补码:11111111 11111111 11111111 11111001
a的补码:11111111 11111111 11111111 11111100
a的反码:11111111 11111111 11111111 11111011
a的原码:10000000 00000000 00000000 00000100
所以a的值为-4
注意:移位操作符只能移动正数个位,不能移动负数位,这是标准未定义的。如7>>-1
位操作符
(按位与)& (按位或) | (按位异或 )^
这里的位也都是指的二进制位,他们的操作数也只能是整数,操作的对象也是整数的补码。
计算方式
按位与&:对应的二进制位两个同时为1才为1,有0则为0
按位或 | :对应的二进制位两个同时为0才为0,有1则为1
按位异或 ^ :对应的二进制位相同则为0,不同则为1
位操作符和移位操作符结合在一起是能够实现很多有趣的功能的,比如将数据的特定的一个二进制位改为0或1,我们可以很轻松的使用这是操作符来达到。
对于按位异或操作符还有下面这种做法
不创建临时变量,交换两个变量的值
int a=10;
int b=10;
我们可以这样做:
a=a^b;
b=a^b;
a=a^b;
通过这个案例我们可以发现,异或两次相同的数就等于原来的数,因为两个相同的数异或为0,而任何数与0异或都是原来的数,同时我们也可以得知异或是满足交换律的。当然,上面那个案例我们也可以用加法来解决,都是用加法就要考虑两个数的和是否会栈溢出的情况了。
赋值操作符
= 赋值也可以连续赋值,但是不适合调试,可读性也不好,不建议连续赋值。如a=b+2=c+5;
复合赋值:+= -= *= /= <<= >>= &= |= ^=
单目操作符
单目操作符就是只有一个操作数的操作符。
!(逻辑反操作) 将真的变成假的,假的变成真的 如 ! 1的结果就是0
+(正值) -(负值)
&(取地址) 取出变量的地址
sizeof(求操作数的类型长度) sizeof是操作符而不是函数,当sizeof的操作数是变量的时候,括 号可以省略,当时类型名的时候,括号不能省略
~ (对一个数的二进制位按位取反,操作的也是补码)
- -(前置、后置--) ++(前置、后置++) 看是要先使用还是要先自加自减
*(解引用操作符)对指针进行操作访问指针所指向的对象
(类型) 强制类型转换
关系操作符
> >= < <= != ==
这里要注意的就是判断相等要用 = = ,不用写成一个等号。浮点数的比较和字符串的比较不能用==,浮点数因为在内存中存储的方式本来就存在误差,而字符串判断相等要用strcmp函数
逻辑操作符
逻辑与&& 逻辑或 | |
要与按位与和按位或区分,逻辑与和逻辑或只关注真假
要注意的是,这两个操作符如果前面的表达式已经能够确定结果了,就不会去判断后面的表达式,后面的表达式中如果有计算也不会执行
条件操作符
也叫三目操作符,表现形式为
表达式1?表达式2:表达式3
表达式1为真,执行表达式2,不执行表达式3
表达式1为真,执行表达式3,不执行表达式2
逗号表达式
用逗号隔开的n个表达式
表达式1,表达式2……表达式n
逗号表达式会从左至右依次计算,最终结果是最后一个表达式的结果
下标引用,函数调用,结构体成员访问操作符
下标引用 : [ ] [ ]就是下标引用操作符,两个操作数就是数组名和下标。两个操作数也可以换位置,效果是一样的,如arr[i]也可以写成 i [arr]。因为 arr[ i ] 与*(arr+i)是等价的 ,加法交换律 *(arr+i) 与 *(i+arr)也是等价的,于是就可以写成 i[arr]。
函数调用 ( ) 函数调用的时候函数名后面的小括号就是函数调用操作符,是不能省略的。
结构体成员访问操作符
. 用于结构体对象
-> 用于结构体指针,等价于*(ps).
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定,同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
隐式类型转换
C语言的整型算术运算总是至少以缺省(缺省的意思就是默认)整型类型的精度来进行的。为了获得这个精度,表达式中的字符或短整型操作数在使用之前被转换为普通整形,这种转换称。为整型提升。
为什么要进行整形提升?
因为表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节大小一般就是int的字节长度,同时也是通用寄存器的长度。
如何进行整形提升?
整型提升是按照变量的数据类型的符号位来提升的。
比如char ch=-1.-1的补码是32位1,当时char类型的存储空间只有八个比特位,于是就进行截断,将前面的比特位截断丢弃,后八个比特位存在ch中。当ch要进行整形提升时,把他的最高位看成符号位,高位补符号位1来进行整型提升,于是ch整型提升后位三十二位1。如果存的是1,则截断后的最高位为0,整型提升时高位补0.如果是无符号数,整型提升时直接在高位补0.
举例
char a=5;
char b=126;
char c=a+b;
printf("%d\n",c);
5的二进制补码:00000000 00000000 00000000 00000101
截断后a中存储的二进制序列: 00000101
a整型提升后的二进制序列: 00000000 00000000 00000000 00000101
126的二进制补码:00000000 00000000 00000000 01111110
截断后b存储的二进制序列: 01111110
b整型提升之后的二进制序列:00000000 00000000 00000000 01111110
整型之后相加的结果:00000000 00000000 00000000 10000011
截断后存储到c的二进制序列:10000011
当我们要将c以%d的形式打印时,首先要整型提升,此时c的最高位时1,所以整型提升时高位补1
得到
补码:11111111 11111111 11111111 10000011
反码:11111111 11111111 11111111 10000010
原码:10000000 00000000 00000000 01111101
将原码转换成十进制打印出来就是 -125
整型提升是用于大小小于整型的类型进行整数运算。
算术转换
如果某个操作符的各个操作符属于不同的类型,那么除非其中一个操作数转换为类一个操作数的类型,否则操作无法运行。运算时将内存小的转换成内存大的、
影响表达式求值的因素
操作符的优先级
操作符的结合性
是否控制求值顺序
当我们要写一个很长的表达式时最好分开写,不要写成一行,或者用括号来规定计算顺序。