C语言提供了丰富的运算符,比如算术运算符、关系运算符、逻辑运算符等,下面的表格是C语言中所有的运算符一览:
运算符 | 功能说明 | 举例 |
---|---|---|
+ | 加法,或单目取正 | a+b, +n |
- | 减法,或单目取负 | a-b, -n |
* | 乘法 | a * b |
/ | 除法 | a / b |
% | 取模(求余) | a % b |
++ | 自加1 | a++,++b |
-- | 自减1 | a--, --b |
说明:取模 % 的左右两边的操作数必须都是整型。而自加和自减则分为前缀和后缀两种,请看下面的代码:
int a = 100, b; int x = 200, y; b = ++a; // 前缀,则a先进行自加1,然后再参与运算,因此此时 a 和 b 都等于101 x = y++; // 后缀,则 y 先参与运算,然后再自加1,因此此时 x等于200, 而y等于201
运算符 | 功能说明 | 举例 |
---|---|---|
> | 大于 | a > b |
>= | 大于等于 | a >= b |
< | 小于 | a < b |
<= | 小于等于 | a <= b |
!= | 不等于 | a != b |
== | 等于 | a == b |
说明:由关系运算符组成的表达式称为关系表达式,每一个关系表达式的值都为布尔值,即非真即假。要注意的是,最后一个是 == 等于号,比如: a == b, 假如 a 跟 b 确实相等,那么此表达式的值为真,否则为假,等于号是用来判断左右两边是否相等的,不是数学意义上的“等于号”。
再来:
运算符 | 功能说明 | 举例 |
---|---|---|
! | 逻辑反 | !(a == 100) |
&& | 逻辑与 | a==100 && b==200 |
|| | 逻辑或 | a==100 || b==200 |
说明:
1, 逻辑反,就是取表达式的布尔值的相反值,例如 !(a==b) 假如 表达式 (a==b) 的值为真,那么 !(a==b) 的值就为假,反之亦然。
2,逻辑与, 就是当且仅当其两边的表达式都为真时,整个表达式才为真,例如 (a==100 && b==200) ,只有当 a==100 而且 b==200 同时为真时,整个表达式才为真。换句话说,在逻辑与表达式中,只要有一边的表达式为假,那么整个表达式必为假。
3,逻辑或, 就是当且仅当其两边的表达式都为假时,整个表达式才为假,例如 (a==100 && b==200) ,只有当 a==100 而且 b==200 同时为假时,整个表达式才为假。换句话说,在逻辑或表达式中,只要有一边的表达式为真,那么整个表达式必为真。
由上面的表述得知,有时候,第一个表达式的布尔值可以决定整个逻辑表达式的值,事实上,当逻辑与表达式的左边表达式的布尔值为假时,右边表达式将被忽略,同理,当逻辑或表达式的左边表达式的布尔值为真时,右边表达式也将被忽略。
运算符 | 功能说明 | 举例 |
---|---|---|
~ | 位逻辑反 | ~a |
& | 位逻辑与 | a & b |
| | 位逻辑或 | a | b |
^ | 位逻辑异或 | a ^ b |
<< | 左移 | a<<2 |
>> | 右移 | b>>2 |
左移和右移则分别是对数位进行移动的操作,移出去的数位丢掉,空出来的补 0, 比如 原本 a 的二进制表示是 0000 1001, 如果执行 a = a<<2; 的话,那么结果是 a 的二进制表示就变成 (00)00 1001 00 最左边的两位被丢弃,最右边补上两个0, 右移是完全一样的道理,比如 b 的二进制表示是 0001 0110 ,如果执行 b = b >> 2; 的话,那么结果是 b 的二进制表示就变成 0000 0101(10) 。
有一个例外是,当被移动的数是一个有符号数,而且是一个负数的时候。 我们知道,负数在内存中的存储是以补码的方式存储的,即它们的最高位是 1, 此时如果被右移,为了保持其原有的正负号,系统会为其补 1 而不是补0. 比如有符号数 c 的二进制表示是 1000 1001, 那么执行 c = c >> 2; 的话,那么结果是 c 的二进制表示就变成 1110 10(01) 。
除了这几种运算符之外,C语言还包含以下几个特殊的运算符:
1, 赋值运算符 = 赋值运算符算是最常用最简单的运算符了,比如 int a; a = 100; 这里的 = 就是赋值运算符,代表将右边的值赋值给左边的变量。 注意不要跟逻辑等号==混淆了。
2,计算内存大小运算符 sizeof , 这个运算符用来计算一个变量或者一种数据类型在内存占用的字节数,比如 sizeof(int) 代表计算 int 类型所占的字节数,sizeof(a) 代表计算变量 a 所占的字节数。
3, 条件运算符 ? : 这个运算符比较特殊,它是唯一一个所谓的 三目 运算符,也就是说它有三个目标表达式,比如 (a>b) ? ( a ) : ( b ); 其中 (a>b) , ( a ) 和 ( b ) 分别是其三个目标表达式,它的逻辑很简单:首先问一下第一个表达式(即(a>b) ),如果它为真,那么就把第二个表达式 ( a ) 的值作为 整个表达式的值,否则就将 第三个表达式(即( b ) 的值作为 整个表达式的值。
4, 逗号运算符 , 逗号其实是用来分割各个其他的表达式的,所以它的优先级最低,比如: int a = (x = 100, y = 200, z = x + y, z + 1); 对于由逗号连起来的表达式,我们记住三点即可: 第一,逗号的优先级最低,全部算完了你再来考虑它。 第二,计算次序从左到右,先算 x = 100, y = 200, 再来 z = x + y, 再到 z +1 。 第三,整个逗号表达式的值取决于最右边的表达式的值,也就是最后a 的值为 z + 1。
5,复合赋值运算符: += -= *= /= %= >>= <<= &= ^= |=
这些操作符的用法是: a += b 相等于 a = a + b 。 a -= (x * y) 相当于 a = a - (x*y) 以此类推。 在可以使用复合赋值运算符的情形下,推荐尽量使用,比如使用 a += b 比使用
a = a + b 要好,因为可以使得程序少一条机器指令。
另外讲几个常见的关键字:
1, return
这个运算符在函数当中使用,请看下面的解释:
int func(void) { ... ... return 100; // 在普通函数中使用,代表返回到调用者处 } int main(void) { ... ... return 0; // 在main函数中使用,代笔退出当前进程 }由以上示例可知,return 有两个作用,在普通函数中使用和在主函数中使用是不同的。 在主函数中的返回值,将会被传递给其父进程,在非多进程程序里面,这个值无意义。 在多进程程序中,main函数的返回值一般情况下是这么约定的: 返回0 代表正常,返回非0 代表异常。
另外,在LINUX 下C编程,使用的是GNU 扩展语法,gcc还支持以下关键字:
2, typeof
取得变量的类型。比如
int a; typeof(a) b; // 等价于 int b;typeof 在复杂宏中用得比较多。比如典型在求最大值的“标准宏”如下:
#define MAX(a, b) ({ \ typeof(a) _a = a; \ typeof(b) _b = b; \ (void)( &_a == &_b); \ _a > _b ? _a : _b; \ )}这个宏之所以这么写,而不是直接写第 5 行,是因为要避免宏参数如果出现 a++ 这样的自加自减符的时候的副作用,因为如果直接写 (a>b) ? (a) : (b) 的话可能会将自加自减符运算两遍,而用typeof 来分别取得 a 和 b 的类型,再定义另两个变量来替换 a 和 b ,就能避免这种副作用。看下面的例子:
#define MAX_BAD(a, b) ((a>b)?(a):(b)) int a = 100, b = 1, c; c = MAX_BAD(a++, b); // 按照调用者的思路,运算之后c 应该等于100, a 应该等于101,但是情况并非如此,因为宏替换将使得 a++ 出现两次上面的例子中,如果我们使用的是 MAX() 而不是 MAX_BAD() 情况就会好转。
另外,MAX() 中的 语句: (void)(&_a == &_b); 是用来判断 _a 和 _b 的类型是否一致的, 假如它们类型不一致,那么它们的指针也必然不一致,不同类型的指针相比较,编译器就会为我们发出警告。 这就是表达式 (&_a == &_b)的作用,而它前面的 (void) 是用来避免编译器因为表达式 (&_a == &_b) 而发出" no effect " 的警告用的。(因为编译器会认为你写了一个逻辑等表达式但是没有使用它,我们用(void)来骗过编译器使其不要发出" no effect " 的警告)