运算符就像是动词。它们引发对变量的运算。
C 中有+、-、*和/这些常用的二元运算符,它们分别用于加、减、乘和除运算。
注意 如果除法运算符(/)的两个操作数都是整数类型,则C 执行整数除法。整数除
法会把除法的结果进行截取。例如,7 / 3 的值是2。
2 余数运算符
余数运算符或模运算符(%)计算一个整数除法的余数。例如,下面的表达式的结果是1:
1. int a = 7;
2. int b = 3;
3. int c = a%b; // c is now 1
余数运算符的操作数必须都是整数类型。
3 自增和自减运算符
C 为自增和自减变量提供了运算符:
1. a++;
2.
3. ++a;
两行都给a 的值增加1。然而,当将两个表达式用作一个更大的表达式的一部分时,它们之间就有区别了。前缀版++a,会在任何计算发生之前增加a 的值。在表达式中使用的是增加以后的a 值。而后缀版a++则是在其他计算进行之后才增加其值。在表达式中,使用的是其最初的值。这可以通过如下的示例来说明:
1. int a = 9;
2. int b;
3. b = a++; // postfix increment
4.
5. int c = 9;
6. int d;
7. d = ++c; // prefix increment
自增运算符的后缀版,是在将变量的初始值用于表达式的计算之后,才将该变量增加1。
当示例中的代码执行完以后,b 的值是9,而a 的值是10。自增运算符的前缀版,是在该变量值用于表达式计算之前就增加它。因此在这个示例中,c 和d 的值都是10。自减运算符a--和--a 以类似的方式工作。
使用该运算符的前缀版和后缀版之间的差别的代码,很可能令除编写者之外的人产生混淆。
4 优先级
如下的表达式是等于18 还是22:
1. 2 *7 + 4
答案似乎是含糊的,因为这取决于是先进行加法还是先进行乘法运算。C 通过指定一条规则,即在执行加法和减法之前,先执行乘法和除法,从而解决了二义性问题;因此,该表达式的值是18。从技术性上来说,就是乘法和除法拥有比加法和减法更高的优先级。如果需要先进行加法运算,可以使用圆括号指定:
1. 2 * (7 + 4)
编译器将会尊重你的请求,先执行加法,然后再执行乘法。注意 C 为其所有的运算符定义了一个复杂的优先级表(参见
http://en.wikipedia. org/ wiki/Order_of_operations)。使用圆括号来指定想要的运算顺序,比努力记住运算符优先级要容易得多。
5 取反
一元减法符号(-)把一个算术值取反:
1. int a = 9;
2. int b;
3. b = -a; // b is now -9
6 比较
C 提供了用于比较的运算符。比较的值是一个真值。如下的表达式,如果为真,则取值为1;如果为假,则取值为0。
1. a > b // true, if a is greater than b
2.
3. a < b // true, if a is less than b
4.
5. a >= b // true, if a is greater than or equal to b
6.
7. a <= b // true, if a is less than or equal to b
8.
9. a == b // true, if a is equal to b
10.
11. a != b // true, if a is not equal to b
7 逻辑运算符
AND 和OR 逻辑运算符的形式如下:
1. expression1 && expression2 // Logical AND operator
2.
3. expression1 || expression2 // Logical OR operator
C 使用短路计算方式。表达式从左向右计算,并且,只要整个表达式可以得出真值,计算就停止。如果一个AND 表达式中的expression1 取值为假,那么,整个表达式的值都为假,从而expression2 就不再计算了。同样,如果一个OR 表达式中的expression1 取值为真,那么,整个表达式都为真,从而expression2 就不再计算了。如果第二个表达式有任何的副作用,短路计算就会得到有趣的结果。在下面的例子中,如果b 大于或等于a,就不会调用CheckSomething()函数(本章后面将要介绍if 语句)了:
1. if (b < a && CheckSomething())
2. {
3. ...
4. }
8 逻辑取反
叹号(!)是C 中的一元逻辑取反运算符。在如下的代码行执行之后,如果expression为真(非0),则a 的值为0;并且,如果expression 的值为假(0),则a 的值为1:
1. a = ! expression;
9 赋值运算符
C 提供了基本的赋值运算符:1. a = b;
上面的语句将b 的值赋给了a。当然,a 必须是能够对其赋值的变量。可以对其赋值的实体叫做左值(lvalue)(因为它们放在赋值运算符的左边)。如下是左值的一些例子:
1. /* set up */
2. float a;
3. float b[100]
4. float *c;
5. struct dailyTemperatures today;
6. struct dailyTemperatures *todayPtr;
7. c = &a;
8. todayPtr = &today;
9.
10. /* legal lvalues */
11. a = 76;
12. b[0] = 76;
13. *c = 76;
14. today.high = 76;
15. todayPtr->high = 76;
有些内容不是左值。不能对一个数组名称、一个函数的返回值或者任何没有引用一个内存位置的表达式赋值:
1. float a[100];
2. int x;
3.
4. a = 76; // WRONG
5. x*x = 76; // WRONG
6. GetTodaysHigh() = 76; // WRONG
10 转换和强制类型转换
如果赋值的两端具有不同的变量类型,则右边的类型会转换为左边的类型。从较短的类型转换为较长的类型,或者从整数类型转换为浮点类型,这不会引发问题。但是,反过来,从一个较长的类型转换为一个较短的类型,就可能会导致有意义的数字丢失、截断或者完全无意义。例如:
1. int a = 14;
2. float b;
3. b = a; // OK, b is now 14.0
4.
5. float c = 12.5;
6. int d;
7. d = c; // Truncation, d is now 12
8.
9. char e = 128;
10. int f;
11. f = e; // OK, f is now 128
12.
13. int g = 333;
14. char h;
15. h = g; // Nonsense, h is now 77
可以使用强制类型转换,来迫使编译器把一个变量的值转换为不同的类型。在下面的示例中的最后一行,(float)强制转换强迫编译器把a 和b 转换为浮点数,并且进行浮点数除法运算:
1. int a = 6;
2. int b = 4;
3. float c, d;
4.
5. c = a / b; // c is equal to 1.0 because
integer division truncates
6.
7. d = (float)a / (float)b; // Floating-point
division, d is equal to 1.5
可以强制转换指针,从而将一种类型的指针转换为另一种类型的指针。强制转换指针可能是有风险的操作,因为这可能会毁坏内存,但是,对于以void*类型传递给你的一个指针来说,这是将其解引用的唯一方式。成功地强制转换一个指针,需要理解指针“实际”所指向的实体的类型。
11 其他赋值运算符
C 还有其他的快捷运算符,它们把计算和赋值组合了起来:
1. a += b;
2. a -= b;
3. a *= b;
4. a /= b;
其等价的形式分别如下:
= a - b;
4.
5. aa = a * b;
6.
7. aa = a / b;