1. 运算符
什么是运算符?
运算符是用来表示某种运算(操作)的符号。 如: + - * /
几目运算符? 表示这个运算符需要带几个操作数(运算数)
单目运算符: 该运算符只需要一个操作数,如: ++ -- & 双目运算符: 该运算符需要两个操作数, 如: + - * / 三目运算符: 该运算符需要三个操作数, 如: ? :
结合性: 决定先算哪个操作数的问题。
从左至右结合,还是 从右至左结合 举个例子: 已知 + 结合性 “从左至右”结合, C语言中: a + b 和 b + a 含义是不一样的!!!
运算符的优先级
在含有多个运算符的表达式中,决定先哪个运算符,后算哪个运算符的问题 如: a + b * c
单目运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符
2. 表达式
什么是表达式?
表达某个意思的式子。 C语言中的表达式,一般来说是用运算符连接操作数的式子,如: 3 + 5
表达式的值
是一个表达式,就一定会有一个值,这个值就是表达式的值。 表达式最终需要表达某个意思,某个意思就是表达式的值!!!
3. 运算符的分类
3.1 算术运算符
进行算术运算的运算符。
++ -- 单目运算符
-
/ % 双目运算符
-
-
双目运算符
-
+-*/ 只需要操作数是一个数(整数可以,小数也可以) % 求余数, 要求两个操作数都必须为整数
如: 3 + 4 3.5 + 4.7 3.5*7 3.5/2 3.5/8.9
5 % 3 3 % 2.3 ERROR 4 / 2 => 2 5 /3 => 1 5 / 2 => 2 5 /2.0 => 2.5 -3 % 2 可以。 (负数求余数,请参考《数论》) 3 / 2 => 1 (double)(3/2) => 1.0 (double)3 / 2 => 1.5 1.0* 3 / 2 => 1.5 用C语言的表达式描述数学表达式 a / b 1.0*a/b
++ (自增运算符)
自增(+1)运算符, 单目运算符,要求操作数必须为一个左值(lvalue)
因为:在C语言中,只有变量才能具备左值
前置++: ++在前面,如: ++x 后置++: ++在后面,如: x++
5++ //ERROR, 5不是变量, (a+b)++ //ERROR (a+b)是一个表达式,不是变量。 int x; x++ 可以的 ++x 可以的 char y; y++ 可以的
-- (自减运算符)
自减(-1)运算符,单目运算符,要求操作数必须为一个左值(lvalue) 前置--: --在前面,如: --x 后置--: --在后面,如: x--
Increment/decrement operators are unary operators that increment/decrement the value of a variable by 1. They can have postfix form: expr ++ expr -- As well as the prefix form: ++ expr -- expr The operand expr of both prefix and postfix increment or decrement must be a modifiable lvalue of integer type (including _Bool and enums), real floating type, or a pointer type. >> 自增/自减的操作数必须为一个可修改的左值(“变量”),而且类型必须为: int(包括 bool, enums),real floating type (浮点数), or a pointer type(指针) ++/--操作数类型为: 整型 浮点型 指针类型
表达式的值 做完表达式后,i的值 i++ i i = i + 1 ++i i+1 i = i + 1 i-- i i = i - 1 --i i-1 i = i - 1
练习:
-
在不同的C编译器下,运行如下代码,看看他们的结果有什么不一样?
#include <stdio.h> int main() { int i = 5; int a; a = (i++) + (i++) + (i++); printf("%d\n", a); return 0; }
-
在不同的C编译器下,运行如下代码,看看他们的结果有什么不一样?
#include <stdio.h> int main() { int i = 5; printf("%d,%d,%d\n", i++, i++, i++); return 0; }
结论:
上面那两处代码不是一个优秀的程序员写出来的。(我只是说明这个问题,才写的,so,我其实是一个优秀的程序员!!!)
3.2 关系运算符
用来判断两个 东西的关系的运算符。 “关系”:数值大小关系。 “比 数值的大小” 双目运算符,结合性: 从左至右。
> >= < <= == !=
关系表达式: 用关系运算符连接起来的式子。 如: a > b 关系表达式的值: 关系成立 1 关系不成立 0
如:
5 > 3 表达式的值是 1 5 > 7 表达式的值是 0 5 > 4 > 3 表达式的值是 0 结合性是 从左至右 => (5 > 4) > 3 => 1 > 3 => 0 5 > 4 > 3 是一个合法的关系表达式,是拿 表达式5>4 的值 和 3进行PK. 在C语言中, 5 > 4 > 3 这个表达式和数学上的“5>4>3”的含义是不一样的。 (1) C语言上的, 5 > 4 > 3 <=> (5>4) > 3 (2) 数学上的, 5 > 4 > 3 是 "5 > 4 并且 4 > 3"
3.3 逻辑运算符
! 逻辑非 单目运算符 “取反” && 逻辑与 双目运算符 "并且", 从左至右结合 || 逻辑或 双目运算符 “或者”, 从左至右结合
逻辑表达式: 用逻辑运算符(! && ||)连接起来的式子,称之为逻辑表达式 逻辑表达式的值 逻辑真 true 1(非0) 逻辑假 false 0
a b a && b a || b 真 真 真 真 假 真 假 真 真 假 假 真 假 假 假 假
例子:
int a = 4, b = 5; a && b => true 1 a || b => true 1 !a && b => 0 && b => false 0 5 > 3 && 8 < 4 - !0 5 > 3 && 8 < 4 - 1 5 > 3 && 8 < 3 1 && 8 < 3 1 && 0 => 0
练习:
-
分析如下程序的输出结果
int main() { int a,b,c,d,m,n; 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); return 0; }
C语言运算符是 “惰性运算”: (a) a && b && c
只有a为真时,才需要 判断b的值 只有a和b都为真时,才需要判断 c的值
(b) a || b || c
只要a为真,就不必判断b和c的值啦 只有a和b都为假时,才需要判断c的值
... In a word, 如果事先知道表达式的值啦,那么后面的运算符或表达式就不需要执行啦, 因为没那个必要啦,这个就是C语言运算符的“惰性”。
-
用逻辑表达式来判断y(年份)是否为闰年 (1) 能被4整除,但又不能被100整除 (2) 能被 400整除 满足上述条件之一,则为闰年
(1) || (2) => 这个表达式的值(true/false) 就代表 y 是否为闰年 (1) (y%4 == 0) && (y%100 != 0) (2) y % 400 == 0 => ((y%4 == 0) && (y%100 != 0)) || ( y % 400 == 0)
-
用逻辑表达式来判断y(年份)是否 不为闰年 (1) 能被4整除,但又不能被100整除 (2) 能被 400整除 满足上述条件之一,则为闰年
!((1) || (2) ) => !(((y%4 == 0) && (y%100 != 0)) || ( y % 400 == 0))
3.4 位运算符
位运算 是按 bit位展开来进行的运算。意思是说,操作数会展开成bit位,然后再进行运算。 位运算符要求操作数必须为整数(兼容的整数类型)
& 按位与 | 按位或 ^ 按位异或 ~ 按位取反 << 按位左移 >> 按位右移
除了 ~(按位取反)是单目运算符外,其他的都是双目运算符,结合性 从左至右
1. ~按位取反
1 -> 0 0 -> 1
!3 逻辑非 => 0 ~3 00000000 00000000 00000000 00000011 11111111 11111111 11111111 11111100 <- ~3
NOTE:
按位取反 ~ 和 逻辑非 !的区别
!x ~x 两个都是单目运算符 ~x 要求操作数x是一个整数 !x 对x无要求
练习:
-
分析如下程序的输出结果
int main() { int a = ~(-3); printf("%d\n", a); //2 printf("%u\n", a); //2 } int main() { int a = !(-3); printf("%d\n", a); //0 printf("%u\n", a); //0 }
2. & 按位与
a & b, 双目运算符,结合性 从左至右
a b a & b 1 1 1 1 0 0 0 1 0 0 0 0 注意: > 按位与& 没有进位操作!!! > 按位与& 如果两个bit位都为1,结果才为1,否则为0
例子:
3 & 5 = ? 1 00000000 00000000 00000000 00000011 & 00000000 00000000 00000000 00000101 00000000 00000000 00000000 00000001 3 & 7 = ? 3 00000000 00000000 00000000 00000011 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000011
NOTE:
按位与& 和 逻辑与 && 的区别
练习:
-
假设有一个整型变量a, 要把a的第5bit,变为0,其他bit位不变,该如何操作?
a = a & ~(1 << 5);
结论:
-
一个bit位与 0 进行“按位与 &”操作,结果为0
x & 0 == 0 x x & 0 1 1 & 0 0 0 0 & 0 0
-
一个bit位与 1 进行“按位与 &”操作,结果为x (保留原值)
x & 1 == x x x & 1 1 1 & 1 1 0 0 & 1 0
3. | 按位或
a | b , 双目运算符,结合性从左至右
a b a | b 1 1 1 1 0 1 0 1 1 0 0 0
按位或,只要有一个bit操作数为1,其结果就为1 例子:
3 | 7 = ? 7 00000000 00000000 00000000 00000011 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000111 3 || 7 = ? 1
练习:
-
假设有一个整型变量a, 要使a的第5bit置1,其他bit位不变,该如何操作?
a = a | ( 1 << 5);
结论:
-
一个bit位与 1进行“按位或 |”操作,结果为1
x | 1 = 1 x x | 1 0 0 | 1 = 1 1 1 | 1 = 1
-
一个bit位与 0进行"按位或 |"操作,结果为 保留原值
x | 0 = x x x | 0 0 0 | 0 = 0 1 1 | 0 = 1
4. 按位异或 ^
"异或": 求异,不同为1,相同为0. a ^ b, 双目运算符,结合性从左至右
a b a ^ b 1 1 0 1 0 1 0 1 1 0 0 0
例子:
2 ^ 32 = ? 34 00000000 00000000 00000000 00000010 ^ 00000000 00000000 00000000 00100000 00000000 00000000 00000000 00100010 3 ^ 7 = ? 4 00000000 00000000 00000000 00000011 ^ 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000100
练习:
-
假设有一个整型变量a, 要使a的第5bit保留,其他位取反,该如何操作?
a = a ^ ~(1<<5);
2. 有两个整型变量a, b, 要交换a和b的值,但是要求不能用其他变量.
a = a ^ b; b = a ^ b; a = a ^ b; 证明方法: (周龙提供的) a b a = a ^ b b = a ^ b a = a ^ b a b 1 1 a = 0,b=1 a = 0,b= 1 a = 1, b = 1 1 1 1 0 a = 1, b=0 b = 1, a= 1 a = 0, b = 1 0 1 0 1 ... 0 0 ...
结论:
-
一个bit位与0进行"按位异或 ^" 结果为,保留原值
x ^ 0 = x x x ^ 0 1 1 ^ 0 = 1 0 0 ^ 0 = 0
-
一个bit位与1进行“按位异或 ^”, 结果 取反
x ^ 1 = ~x x x ^ 1 1 1 ^ 1 = 0 0 0 ^ 1 = 1
5. 按位左移 <<
a << n 双目运算符,结合性从左至右结合。“把a按bit位整体左移n位”
高位左移后,舍弃; 低位空出n位,直接补0. 如果左移舍弃的高位都为0, 那么左移n位,就表示在原值乘以 2的n次方
例子:
int a = 8 << 2; 00000000 00000000 00000000 00001000 << 2 000000 00000000 00000000 0000100000 int a = (unsigned char)-1 << 2; -1: 11111111 11111111 11111111 11111111 (unsigned char)-1 : 11111111 int : (unsigned char)-1 => int 短->长 00000000 00000000 00000000 11111111 << 2 000000 00000000 00000000 1111111100 255*4 = 1020 printf("%d\n", a); printf("%u\n", a); unsigned char a = (unsigned char)-1 << 2; printf("%d\n", a); //252 printf("%u\n", a); //252 -1: 11111111 11111111 11111111 11111111 (unsigned char)-1 : 11111111 int : (unsigned char)-1 => int 短->长 00000000 00000000 00000000 11111111 << 2 000000 00000000 00000000 1111111100 a: 11111100 %d : 000...0000 11111100 char a = (unsigned char)-1 << 2; printf("%d\n", a); // -4 printf("%u\n", a); // 2^32 - 4 (unsigned char)-1 : 11111111 int : (unsigned char)-1 => int 短->长 00000000 00000000 00000000 11111111 << 2 000000 00000000 00000000 1111111100 a: 11111100 %d : a -> int 11111111 11111111 11111111 11111100 11111111 11111111 11111111 11111011 00000000 00000000 00000000 00000100
练习:
-
构造一个整数,它的第7bit和第28bit为1,其他bit为0,该如何构造?
(1 << 7) | (1 << 28)
6. 按位右移 >>
x >> n 双目运算符,从左至右结合。 “把x按bit位,整体右移n位”
低位右移后,直接舍弃。高位补什么呢?
对于无符号数(x),高位全部补0; 对于有符号数(x),高位全部补原符号位。
练习:
-
分析如下程序的输出结果。假设 int 占32bits
int main() { int a = -1; a = a >> 31; 11111111 11111111 11111111 11111111 >> 31 1111111111111111.....1 printf("%d\n", a); // -1 printf("%u\n", a); // 2^32 - 1 return 0; }
int main() { unsigned int a = -1; a = a >> 31; 11111111 11111111 11111111 11111111 >> 31 0000..000001 printf("%d\n", a); //1 printf("%u\n", a); //1 return 0; }
-
分析如下程序的输出结果。假设 int 占32bits
int main() { char a = -1; int b = a >> 31; a: 11111111 a >> 31 111....1111111111 >> 31 111111111111111....1 b printf("%d\n", b); // -1 printf("%u\n", b); // 2^32 - 1 return 0; }
int main() { unsigned char a = -1; int b = a >> 31; a: 11111111 a >> 31 0000000000000000...00011111111 >> 31 0000...000000 printf("%d\n", b); // 0 printf("%u\n", b); // 0 return 0; }
7. 掩码(mask)
掩码(mask) 是一个位模式,表示从一个字(word,32bits, ...)中选出的位集合。 掩码中为1的那些bit位,就是我们想要选取的 bit位集合。 用我(龙哥)的话说: "掩码表示你对哪些bit位感兴趣"
如:
如果我要选取的是第0bit和第1bit,则掩码为 0x3 00000000 00000000 00000000 00000011 如果 mask = 0xff, 表示我要选取低8位 00000000 00000000 00000000 11111111
例子:
-
假设有一个整型变量 x = 0x345678AB, 我想要选取x的低8位,该如何操作呢?
低8位的掩码 mask = 0xff x & mask => x & 0xff => 0xAB
-
假设有一个整型变量 x, 我想要选取x的第5bit开始的4个bit位,该如何操作呢?
mask: 000000...0000111100000 0xf << 5 mask = 0xf << 5 ( x & (0xf << 5) ) >> 5
3.5 赋值运算符
a = b 双目运算符,结合性从右至左, "把表达式b的值赋值给 a"
赋值运算符的优先级 排倒数第二,倒数第一是逗号运算符
a = b
把表达式b的值 赋值给a(把这个值写入到a的地址中去,a需要具备一个可写的地址(左值),“变量”) 一般来说,a为一个可变的数据对象,“变量” 赋值运算符的左边操作数 必须为一个 “可写的地址”,左值,变量
例子:
5 = 5 ; //ERROR 5 == 5 //ok 2 + 3 = 5; //ERROR int x = 2, y = 3; x + y = 5; //ERROR x = 5; //ok x++ = 6; //ERROR
赋值表达式:由赋值运算符连接操作数的式子,称之为赋值表达式。 赋值表达式的值,就是赋完值后左边那个操作数的值。
x = 3 这个是一个赋值表达式,这个表达式的值就是最后x的值, 3 y = x = 3 这个是合法的赋值表达式 => y = (x = 3) 把表达式"x = 3"的值,赋值给y x = 2 + 3*y 合法的.
复合的赋值运算符: 赋值运算符可以和算术运算符、位运算符组成复合的赋值运算符.
+= -= *= /= %= <<= >>= |= &= ^= a += b => a = a + b x <<= 2 => x = x << 2 ...
3.6 条件运算符
? : 三目运算符 expressions ? a : b
先判断 expressions 的值, 如果它的值为真(非0), 则整个表达式的值就是a 如果它的值为假(0), 则整个表达式的值就是b
如:
if (a > b) { x = 250; } else { x = 360; } x = a > b ? 250 : 360; or a > b ? (x = 250) : (x = 360) ;
3.7 逗号运算符
a , b 双目运算符,结合性从左至右,优先级是最低的
先算表达式a的值,然后再算表达式b的值,整个逗号表达式的值就是最右边表达式b的值。
表达式1,表达式2 例子:
a = 3 , 2 请问上面这个是什么东西呢? > 上面这个是一个 逗号表达式 a = (3, 2) 这个是一个赋值表达式,把“表达式(3,2)”的值,赋值给a a = 3, a = 2 > 这个是一个逗号表达式 a = 3, a = 2, a = 4 > 这个也是一个逗号表达式, => (a = 3, a = 2), a = 4
逗号表达式的形式可以是下面这样: 扩展的逗号表达式 表达式1, 表达式2, 表达式3, .... , 表达式n
求值顺序: 先求表达式1的值,然后再求表达式2的值, ..., 最后求表达式n的值 整个逗号表达式的值 就是最右边的那个表达式的值!!!!
3.8 指针运算符
-
指向运算符 & 取地址符 先 wait wait, 后面有专题讲
3.9 求字节运算符
sizeof
3.10 分量运算符
. ->
3.11 下标运算符
[] 取数组的元素 int a[10]; a[0] a[1] ....
3.12 强制类型转换运算符
(类型)表达式 如: (int) 3.5 (int)(3.2 + 4.9) => 8 (int)3.2 + 4.9 => 7.9