0.运算符与表达式
什么是运算符? 用来进行某种运算的符号。
如: + - * / %
几目运算符?该运算符需要带几个操作数
单目运算符:该运算符只需要带一个操作数。 如: ++ -- 双目运算符:该运算符需要带两个操作数。 如: + - * / 三目运算符:该运算符需要三个操作数。 如: ? :
结合性:优先级相同的情况下决定先算谁后算谁的问题 从左至右结合,从右至左结合
优先级: 在一个含有多个运算符的表达式中,先算哪个运算符后算哪个运算符的问题。 单目运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 条件运算符 > 赋值运算符 > 逗号运算符
表达式
在C语言中有相应含义的对象
表达式加上 ; ==> 语句
3+2 算术表达式
a 变量表达式
1 常量表达式
a++
注意:1.只要是个表达式就一定会有值
2. 在函数内部都是以语句形式存在
1. 算术运算符
运算符 | 功能说明 | 举例 |
---|---|---|
+ | 加法,单目取正 | a+b |
- | 减法,单目取负 | a-b |
* | 乘法 | a*b |
/ | 除法 | a/b |
% | 取模(求余) | a%b |
++ | 自加1,单目 | a++, ++b |
-- | 自减1,单目 | a–-, --b |
-
关注点
-
减号也是负号,比如-a是取变量a的相反数
-
取模运算要求左右两边操作数必须是整型数据
-
自加自减运算不仅可以对整型操作,也可以对浮点数、指针操作
-
1)单目运算符
++ -- +(正号) -(负号)
-
前后缀运算:
-
前缀自加自减运算:先进行自加自减,再参与表达式运算
-
后缀自加自减运算:先参与表达式运算,在进行自加自减
表达式 表达式的值 表达式执行后i的值 i++ i i+1 ++i i+1 i+1 i-- i i-1 --i i-1 i-1
-
3 / 4 ==> 0 5 / 4 ==> 1 3 % 4 ==> 3 5 % 4 ==> 1 3++;//error 3 = 3+1 左值有问题 int a = 2; a++;// a = a+1 a+++++a;//erro /* 1.c: In function ‘main’: 1.c:7:13: error: lvalue required as increment operand int b = a+++++a; ^~ */ 注意: 自加自减运算符单独使用
2)双目运算符
+ - * / %
注意:
-
% : 求余,取模。要求两个操作数都必须为整数。
-
++ -- :要求操作数必须为一个可写的“左值 lvalue”
-
连续操作自加自减符号是不允许出现的
5*1.0/4 ==> 1.25
(double)5/4 ==> 1.25
(double)(5/4) ==> 1.0
2. 关系运算符
都是双目运算符
结合性:从左往右
运算符 | 功能 | 举例 | 说明 |
---|---|---|---|
> | 大于 | a > b | 判断a是否大于b |
>= | 大于或等于 | a >= 5 | 判断a是否大于或等于5 |
< | 小于 | 3 < x | 判断3是否小于x |
<= | 小于或等于 | x <= (y+1) | 判断x是否小于或等于y+1 |
== | 等于 | (x+1) == 0 | 判断x+1是否等于0 |
!= | 不等于 | c != '\0' | 判断c是否不等于’\0’ |
eg: 2 > 3 > 4 ==> 0 ==> 0 > 4 ==> 0 5 > 4 > 3 ==> 0 ==> 1 > 3 ==> 0 int a = 0; a != 2 ==>1 5 > 4 > 3 ==> 5 > 4 and 4 > 3
-
关注点:
-
关系运算符用于判断运算符两边的表达式是否满足给定的大小条件
-
由关系运算符组成的表达式称为关系表达式,其值为布尔型
-
判断是否相等是双等号==,而不是一个等号
-
3. 逻辑运算符
双目运算符 除 !
结合性:从左往右
运算符 | 功能说明 | 举例 |
---|---|---|
! | 逻辑非 | !0 |
&& | 逻辑与 | x > 0 && x < 10 |
|| | 逻辑或 | y < 10 || x > 10 |
-
运算规则:
-
逻辑非:将逻辑真、假翻转,即真变假,假变真
-
逻辑与:将两个表达式串联起来,当且仅当左右两个表达式都为真时,结果为真。并且
-
逻辑或:将两个表达式并联起来,当且仅当左右两个表达式都为假时,结果为假。或者
-
-
惰性运算
-
在逻辑与运算中,如果左边表达式的值为假,那么右边表达式将不被执行。
-
在逻辑或运算中,如果左边表达式的值为真,那么右边表达式将不被执行。
逻辑与 && ---而且,同时 两个操作数都必须为真,结果为真 逻辑或 || ---或者 只要有一个为真,那么结果为真 逻辑非 ! ----反 非0 为真,0 为假 逻辑表达式结果有两种 1为真 0为假 ============= eg: int a = 3, b = 4, c = 5, d = 6; int m = 1,n = 1; (m = a > b) && (n = c > d); printf("%d %d %d %d %d %d\n",a,b,c,d,m,n);//3 4 5 6 0 1 C语言运算符是“惰性运算”: (1) a && b && c 只有a为真时,才需要判断b的值 只有a和b都为真时,才需要判断c的值 (2) a || b || c 只要a为真,就不必判断b和c的值啦 只有a为假,才需要判断b的值 只有a和b都为假,才需要判断c的值 ……。 In a word, 如果事先知道表达式的值啦,那么后面的运算符(或表达式)就不需要执行啦,这就是C语言运算符的“惰性”.
「课堂练习1」
-
用逻辑表达式来判断y(年份)是否为闰年: (1) 能被4整除,但不能被100整除 (2) 能被4整除,又能被400整除 满足上述条件之一,则为闰年
伪代码: (1) || (2) (1): (y % 4 == 0) && (y % 100 != 0) (2): (y % 400 == 0) ==> ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0) ==> ((y % 4 == 0) && (y % 100)) || (y % 400 == 0)
-
4. 位运算符
位运算符是指按bit位来进行的运算。 注意:所有位运算都需要把操作数变成bit序列,然后再按bit位来运算 位运算符要求操作数是整数。
运算符 | 名称 | 举例 | 功能说明 |
---|---|---|---|
~ | 位反 | ~a | 将变量 a 中的每一位取反 |
& | 位与 | a & b | 将变量 a 和 b 逐位进行与操作 |
| | 位或 | a | b | 将变量 a 和 b 逐位进行或操作 |
^ | 位异或 | a ^ b | 将变量 a 和 b 逐位进行异或操作 |
<< | 左移 | a << 4 | 将变量 a 中的每一位向左移动4位 |
>> | 右移 | x >> n | 将变量 x 中的每一位向右移动4位 |
-
~ : 按位取反---单目运算符,数据将每一个bit位取反,1变0,0变1
以1字节为列 eg: ~4 ==> 251 00000100 ==》11111011 ~2 ==> 253 00000010 => 11111101
-
&:按位与,两个都为1,结果才为1
-
1 & 1 == 1
-
1 & 0 == 0
-
0 & 0 == 0
-
0 & 1 == 0
以1字节为列 eg: 2 & 3 ==》 2 00000010 00000011 ==> 00000010
-
-
| :按位或,两个都为0,结果才为0
-
1 | 1 == 1
-
1 | 0 == 1
-
0 | 1 == 1
-
0 | 0 == 0
eg:以1字节为例 4 | 5 ==》 5 00000100 00000101 ==》00000101
-
-
^ : 按位异或 --- 不同为1,相同为0
-
1 ^ 0 == 1
-
0 ^ 1 == 1
-
0 ^ 0 == 0
-
1 ^ 1 == 0
eg:以1字节为例 8 ^ 9 ==》 1 00001000 00001001 ==> 00000001
-
-
<< : 左移,按bit位往左移动
假设该数 为 x
x << n :将 x 的bit序列 整体左移 n个bit 位
高位左移后,直接丢弃
低位空出n 个bit 位 全部补0
-
>> : 右移
假设该数 为 x
x >> n :将 x 的bit序列 整体右移 n个bit 位
低位右移后,直接丢弃
高位空出n 个bit 位 补什么,看x是有无符号 无符号 :高位补0 有符号:高位补符号位
eg: 以1字节为例 8 << 2 => 32 00100000 8 >> 2 ==>2 00000010
-
位运算符操作的对象是数据中的每一位
-
运算规则:
-
位反、位与、位或拥有与逻辑运算相似的规则和一样的真值表。
-
异或运算:相同为0,不同为1
-
移位运算:移出去的不要,空出来的补零。
分析以下代码的结果: char a = -8; printf("%d\n", a << 6);//-512 printf("%d\n", a >> 6); //-1
a:char 1111 0110 a=>int 11111111 11111111 11111111 1111 0110 a << 6 11111111 11111111 11111101 1000 0000 (负数的补码) a >> 6 11111111 11111111 11111111 1111 1111(负数的补码)
「课堂练习2」
问题1: 假设有如下程序,程序输出的结果是什么?
-
int main(void) { char a, b, c, d; a = 0x3;// b = a|0x8;// c = b<<1;// d = b >> 3; printf("%d %d %d %d\n",a, b, c, d);//3 11 22 1 }
=============================
结论1: 一个bit位与0进行“按位与 &”操作,结果为0 x & 0 == 0 如何证明? 因为x只能为0 or 1 when x == 0 x & 0 = 0 when x == 1 x & 0 = 0 一个bit位与1进行"按位与 &"操作,保留原值 x & 1 == x 如何证明? 显而易见。 when x == 0 x(0) & 1 == x(0) wnen x == 1 x(1) & 1 == x(1)
1: 假设有一个整型变量a, 要把a的第5bit变为0,其他bit位不变,该如何操作?
a的值从键盘上获取
a: 34 00000000 00000000 00000000 00100010 11111111 11111111 11111111 11011111 (~(1 << 5)) ==>& 00000000 00000000 00000000 00000010 ==> a = a & (~(1 << 5) ========test1.c //1. 定义变量 int a; //2. 从键盘获取数据 scanf("%d", &a); //3. 执行设置操作 a = a & (~(1 << 5)); //4. 输出结果 printf("%u\n", a);
=======================
结论2: 一个bit位与0进行“按位或 |”操作,保留原值, x | 0 == x 一个bit位与1进行“按位或 |”操作,结果为1 x | 1 == 1
2:
有一个整型变量a,要使a的第5bit设置,其他bit位不变, 该如何操作?
a: 00000000 00000001 01100000 00000010 00000000 00000000 00000000 00100000 (1 << 5) ==>| 00000000 00000001 01100000 00100010 //1. 定义变量 int a; //2. 从键盘获取数据 scanf("%d", &a); //3. 执行设置操作 a = a |(1 << 5); //4. 输出结果 printf("%u\n", a);
==========================
结论3: 一个bit位与0进行“按位异或 ^”操作,保留原值 x ^ 0 == x 一个bit位与1进行"按位异或 ^"操作,结果取反 x ^ 1 == ~x
3: 有一个整型变量a,要使a的第5bit位保留,其他位取反, 该如何操作?
a: 00000000 00000001 01100000 00000010 11111111 11111111 11111111 11011111 (~(1 << 5)) ==>^ 11111111 11111110 10011111 11011101 //1. 定义变量 int a; //2. 从键盘获取数据 scanf("%d", &a); //3. 执行设置操作 a = a ^(~(1 << 5); //4. 输出结果 printf("%u\n", a);
5. 条件运算符(三目运算符)
结合性:从右往左
-
唯一需要三个操作数的运算符
-
语法格式:表达式1?表达式2:表达式3
表达式1?表达式2:表达式3 ===> 这个本身也是一个表达式
如果表达式1 为真,则整个条件表达式的值为 表达式2的值
如果表达式1 为假,则整个条件表达式的值为 表达式3的值
eg: int b = 0; int a = 2 > 3 ? b = 2 : b++; ==> a == 0 b == 1 ====================== int main() { int a = 3, b = 4; int c = a > b ? a++ :a < b ? a++ : b++; // 注意:在条件表达式中不管其他运算符的优先级,先找到其中的表达式1,表达式2和表达式3 //a = ? //b = ? //c = ? printf("%d %d %d\n", a, b ,c);// 4 4 3 }
注意:
表达式2 和 表达式3 只走其一
6. 赋值运算符
-
赋值运算符 =
-
可以连续赋值,结合性:从右往左
-
赋值运算符的左边(左操作数)必须是可写的地址空间
-
a = b = c = d = 0; 赋值表达式的值 :就是最后赋值给最左边变量的值
-
复合赋值运算符
赋值运算符可以和 位运算符, 算术运算符组合一起使用
-
当左右两边有相同的操作数时,采用复合赋值符不仅直观,且能提高运算效率
-
eg: a += 2;// a = a+2 a &= 2; // int a = 8; a += 1;// a++; //
7.逗号运算符
语法: (表达式1,表达式2,表达式3,... 表达式n) 求值顺序: 先求表达式1,再求表达式2,再求表达式3,最后求表达式n 整个逗号表达式的值为n的值 注意: 1.逗号表达式的优先级最低 2.运算顺序是从左往右 3.整个逗号表达式的值取决于最右边的表达式的值
eg: int b = 0; int a = 1,2,3,3+4,b++; // a = ? /* china@ubuntu:$ gcc 4.c 4.c: In function ‘main’: 4.c:9:13: error: expected identifier or ‘(’ before numeric constant int a = 1,2,3,3+4,b++; ^ china@ubuntu:$ */ a = 1,2,3,2+3,4>5,7; a = ? //1
8. sizeof 运算符
-
含义:计算指定数据类型或者变量所占据内存的字节数
-
语法:sizeof(类型)、sizeof(变量),计算变量的字节数时圆括号可以省略
-
举例:
eg: sizeof(2+3.0) ==> 8 ==> sizeof(5.0) ==> sizeof(typeof(8.0)) ==> sizeof(double) ==> 8
9. return运算符
-
含义:退出某个函数(如果退出的是主函数main,那么整个程序也就推出)
-
语法:必须出现在函数体内,可以带函数对应类型的数据
-
举例:
int main() { printf("hello\n"); //假如() { return -1;// 1. 返回结果 2. 结束所属函数 } printf("world\n"); return 0; }// main函数返回0 正常结束该程序 -1 异常结束
10.其他的单目运算符
* 间接运算 & 取址 sizeof() 求字节 . -> 分量运算符 [] 下标运算符 (类型) 类型强转
typeof(5.0 + 4 ) ==> double
(int)(5.0 + 4)
sizeof:求一个对象或类型所占空间的字节数 sizeof(x) sizeof 不要求x存在,但是要求x的类型是确定的 因为只要你的类型是确定,那么我就可以知道你所占内存大小。
11. 优先级与结合性
-
当表达式出现不同的运算符时,根据优先级来决定谁先执行,比如先乘除后加减
-
当表达式中出现多个相同优先级的运算符时,更具结合性决定谁先运行,比如从左到右
结合性:
三种从右往左
1. 单目 2. 三目 3. 赋值