运算符与表达式
-
表达式
表达某一个意思的式子。a = b + c
在C语言中,表达式一般是指用运算符连接操作数的式子
注意:是表达式就一定会有一个值。
-
运算符
用来进行某种运算的符号就叫运算符。
如:
+、-、*、…几目运算符?表示的该运算符需要带几个操作数。
单目运算符:该运算符只需要一个操作数。如:++、–…
双目运算符:该运算符需要两个操作数。如:+ - *…
三目运算符:该运算符需要三个操作数。如: ?:(条件运算符)结合性:代表的是运算的优先顺序
例子:
+ 结合性从左至右
a + b
先把表达式a的值求出来,再去求表达式b的值。C语言中的运算符:
(1) 算术运算符
进行算术运算的运算符。单目运算符: ++、-- 双目运算符: + - * / % 重点: 1. 整数/整数其结果还是整数,如果想要这个结果为实数,可用分子*1.0 2. 隐式类型转换。 高精度和低精度进行运算时,其结果自动转变成高精度。 比如: int double --> double int short --> int char short --> int char char --> int 3. 取余运算符 %,C语言中,%要求两个操作数必须为整数。 printf("%d\n",5%3); //2 printf("%d\n",-5%3); //-2 printf("%d\n",5%-3); //2 printf("%d\n",-5%-3); //-2 记忆点:余数与被整除数同符号。 4. ++:自增运算符 --:自减运算符 i++ i-- ++i --i 自增运算符使其操作数的值+1,自减运算符使其操作数的值-1。 注意: ++、--只能作用于变量。 i++ 5++ //error (a + b)++ //error (a++)++ //error int i = 5; i++:先进行运算,然后再自增加1 a = i++ --> (a = i,i = i + 1) ++i:先自增加1,然后再进行运算 b = ++i --> (i = i + 1,b = i) i的值 表达式的值 a = i++ 6 5 b = ++i 6 6 c = i-- 4 5 d = --i 4 4 int i = 6; printf("%d %d\n",i++,++i);//7 8 printf的运算顺序是从右至左,打印顺序是从左至右。 因为在同一个表达式内如果存在多个++或者--运算会导致编译器优化 导致运算顺序被改变了。 结论:尽量的避免在一个表达式内使用多个++和--。 printf("%d\n",i++); printf("%d\n",++i);
(2) 关系运算符
用来判断两个表达式数值大小关系的运算符。双目运算符,结合性:从左至右 > >= == <= < != 关系表达式:由关系运算符连接操作数的表达式,叫做关系表达式 关系表达式的值:1(关系成立) 0(关系不成立) 例子: 5 > 4 1 2 >= 3 0 5 > 4 > 3 0
(3) 逻辑运算符
表达某种数理逻辑的运算符。! 逻辑非 单目运算符 “取反” && 逻辑与 双目运算符 “并且” 结合性:从左至右 || 逻辑或 双目运算符 “或者” 结合性:从左至右 逻辑表达式的值:1(非0 真) 0(假) 任何非0的数都表示真,但是如果表达式的结果为真时,其值只能为1. 例子: a = 4,b = 5 a && b 1 a && 0 0 只要有一个操作数的值为0,其结果必为0 a || b 1 a || 0 1 只要有一个操作数的值为1,其结果必为1 !a || b 1 a && 0 || b 1 5 > 3 && 8 < 4 - !0 ---> 1 && 0 ---> 0 int a = 1,b = 2,c = 3,d = 4,m = 1,n = 1; (m = a > b) && (n = c > d); printf("%d %d %d %d %d %d\n",a,b,c,d,m,n);//1 2 3 4 0 1 C语言中运算符是“惰性运算”: 事先如果已经知道了表达式的值,那么它就不会去运算后面的式子了。 1) a && b && c 只有当a的值为真时,才有必须去判断b的值 只有当a和b的值都为真时,才有必须去判断c的值 2) a || b || c 只有当a的值为假时,才有必须去判断b的值 只有当a和b的值都为假时,才有必须去判断c的值 练习: 用逻辑表达式来判断某年是不是闰年。 1) 能被4整除,但是不能被100整除 2) 能被400整除 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
(4) 位运算符
位运算符是指按bit位来进行运算的运算符,所有的位运算都需要把操作数变成
bit序列,然后再按bit为来运算。位运算符要求操作是整数(整型、字符型) & 按位与 | 按位或 ^ 按位异或 ~ 按位取反 << 按位左移 >> 按位右移 除了~是单目运算符外,其它的位运算符都是双目运算符。 结合性:从左至右 1) ~(按位取反) 0 --> 1 1 --> 0 例子: int a = ~3; printf("%d\n",a);//-4 printf("%u\n",a);//2^32 - 4 0000 0000 0000 0000 0000 0000 0000 0011 3 1111 1111 1111 1111 1111 1111 1111 1100 ~3 1111 1111 1111 1111 1111 1111 1111 1011 -1 0000 0000 0000 0000 0000 0000 0000 0100 -4 2) &(按位与) a b a&b 1 0 0 1 1 1 0 0 0 0 1 0 只有两个bit位操作数都为1时,结果才为1,否则即为0 int a = 3 & 5; printf("%d\n",a);//1 结论: 一个bit位与0进行按位&运算时,结果总为0 x & 0 == 0 一个bit位与1进行按位&运算时,结果保留原值 x & 1 == x 3) |(按位或) a b a|b 1 0 1 1 1 1 0 0 0 0 1 1 只有两个bit位操作数都为0时,结果才为0,否则即为1 int a = 3 | 5; printf("%d\n",a);//7 结论: 一个bit位与1进行按位|运算时,结果总为1 x | 1 == 1 一个bit位与0进行按位|运算时,结果保留原值 x | 0 == x 4) ^(按位异或) a b a^b 1 0 1 1 1 0 0 0 0 0 1 1 不同为1,相同即为0 int a = 3 ^ 5; printf("%d\n",a);//6 结论: 一个bit位与0进行按位^运算时,结果保留原值 x ^ 0 == x 一个bit位与1进行按位^运算时,结果取反 x ^ 1 == ~x 交换两个变量的值的方法: 1. 利用中间变量法 int a = 5,b = 6; int c; c = a; a = b; b = c; 2. 异或法 a = a ^ b; b = b ^ a; a = a ^ b; 位运算只与当前位有关,因为他运算既没有进位,也没有借位。 a b a = a ^ b;b = b ^ a;a = a ^ b; a b 0 0 0 0 1 1 1 1 0 1 1 0 1 0 0 1 5) <<(按位左移) 按bit位整体向左移动 a << n 规则: 把a先转换成二进制,在二进制的基础上按bit位整体向左移动n位。 高位左移后直接舍弃n位,低位会空出n位,直接补0 如果左移后舍弃的高位都是0,那么左移n位,就表示在原值的基础上乘以2 的n次方。 假设有一个字符型变量a,要把a的第5bit变成0,其它bit位不变,该如何操作? xxxx xxxx & 1101 1111 xx0x xxxx 1<<5 0000 0001 ~ 0010 0000 1101 1111 a = a & ~(1 << 5); 6) 按位右移( >> ) 按bit位整体向右移动 a >> n 规则: 把a先转换成二进制,在二进制的基础上按bit位整体向右移动n位。 低位右移后,低n位直接舍弃,高位空出n位,补什么? 对于无符号的,高位补0 对于有符号的,高位补符号位。 int a = -1; a = a >> 31; printf("%d\n",a);//-1 printf("%u\n",a);//2^32-1 1111 1111 1111 1111 1111 1111 1111 1111 -1的补码 1111 1111 1111 1111 1111 1111 1111 1111 a >> 31 unsigned int a = -1; a = a >> 31; printf("%d\n",a);//1 printf("%u\n",a);//1 1111 1111 1111 1111 1111 1111 1111 1111 -1的补码 0000 0000 0000 0000 0000 0000 0000 0001 a >> 31 char a = -1; int b = a >> 31; printf("%d\n",b);//-1 printf("%u\n",b);//2^32-1 1111 1111 -1(char)的补码 1111 1111 1111 1111 1111 1111 1111 1111 char --> int unsigned char a = -1; int b = a >> 31; printf("%d\n",b);//0 printf("%u\n",b);//0 1111 1111 -1(char)的补码 0000 0000 0000 0000 0000 0000 1111 1111 char --> int 0000 0000 0000 0000 0000 0000 0000 0000 a >> 31 数据类型的作用: 1. 决定保存数据的空间的大小 2. 在短变长和右移时数据类型决定运算方式。
(5) 赋值运算符
= 双目运算符 结合性从右至左。
a = x
赋值运算符要求左边的操作数必须为一个“可写的地址”。
3 = 4//error
i++ = 4//error
i = 6//right
赋值表达式:由赋值运算符连接操作数的式子
赋值表达式的值就是最后赋值给左边变量的那个值
b = a = 6; --> b = (a = 6)
复合赋值运算符:赋值运算符可以和算术运算符、位运算符组成复合的赋值运算符
+= -= %= /= *= <<= >>= |= &= ^=
例子:
i += 6;--> i = i + 6;
(6) 条件运算符
?: 三目运算符,结合性从右至左。
语法:
表达式1 ? 表达式2 : 表达式3
if(表达式1)
{
表达式2;
}
else
{
表达式3;
}
求值顺序:
如果表达式1的值为真,则整个条件表达式的值就是表达式2的值
如果表达式1的值为假,则整个条件表达式的值就是表达式3的值
例子:
a = 5 > 4 ? 1 : 0;
a = 1
(7) 逗号运算符
,
双目运算符 结合性从左至右
语法:
表达式1,表达式2
求值顺序:
先求表达式1的值,再求表达式2的值,整个逗号表达式的值为表达式2的值。
例子:
int a = 5,b = 6;
a = (a = 6,a + b);
printf("a = %d\n",a);//a = 12
(8) 指针运算符
*(指向符)
&(求地址符)
(9) 求字节运算符
sizeof()
单目运算符,求一个对象(常量/变量/类型..)所占空间的字节数
sizeof(4) --> 4
sizeof(7.8) --> 8
int i = 5;
sizeof(i) --> 4
sizeof(int) --> 4
sizeof(1.0f) --> 4
char a = 1,b = 2;
sizeof(a + b) --> 4
(10)分量运算符
. ->
求结构体中的成员变量的
(11)下标运算符
[]
取数组的元素
int a[10];
a[0],a[1],....a[9]
(12)强制类型转换运算符
()
语法:
(类型)
如:
float b = 3.6;
int a = (int)b;
(13)其它
优先级:
优先级决定如果在一个语句中,出现多个运算符,那么到底是哪个运算符先进行运算。
我们在运算一个表达式的时候,先要看优先级,再看是否有惰性运算,再看结合性
以下是运算符优先级及结合性的总结表:
运算符 结合性
() [] -> . 自左向右
! ~ ++ -- *(指向) 自右向左 单目运算符
*(算术) / % 自左向右 算术运算符
+ -
< <= > >= 自左向右 关系运算符
== !=
& ^ | << >> 自左向右 位操作运算符
&& 自左向右 逻辑运算符
||
?: 自右向左 条件运算符
= += -= *= /= 自右向左 赋值运算符
%= &= ^= |= <<= >>=
, 自左向右 逗号运算符
自上而下优先级越低,同级优先级的运算符,实际优先级左侧优先
作业:
1. 取整型变量x中第p位开始的n个bit
(x >> (p + 1 - n)) & ((1 << n) - 1)
or
(x >> (p + 1 - n)) & ~(~0 << n)
2. 将x中第p位开始的n个bit位取反,其余各位保持不变
x = x ^ (((1 << n) - 1) << (p + 1 - n))
3. 分析如下程序的输出结果
char c = -56 >> 30;
printf("%d\n",c);//-1
printf("%u\n",c);//2^32 - 1
0000 0000 0000 0000 0000 0000 0011 1000 |-56|
1111 1111 1111 1111 1111 1111 1100 0111 取反
1111 1111 1111 1111 1111 1111 1100 1000 +1(-56的补码)
1111 1111 1111 1111 1111 1111 1111 1111 -56 >> 30
1111 1111 c(长变短)
1111 1111 1111 1111 1111 1111 1111 1111 短变长
char c = -56u >> 30;
printf("%d\n",c);//3
printf("%u\n",c);//3
1111 1111 1111 1111 1111 1111 1100 1000 +1(-56的补码)
0000 0000 0000 0000 0000 0000 0000 0011 -56u >> 30
4. 循环位移的实现
1. 将x循环右移n位
x = (x << (sizeof(x)*8 - n)) | ((unsigned)x >> n);
2. 将x循环左移n位
x = (x << n) | ((unsigned)x >> (sizeof(x)*8 - n));