3.4、运算符与优先级(上)

By | 2015年8月12日

我们在变量的计算一节中已经提到了运算符的概念(虽然你可能到现在只用过加减乘除)

而现在我们将学习c语言大部分的运算符,并且搞清楚它们之间的优先级

首先来看看什么是运算符

运算符(operator)

运算符就是用来表示运算关系的符号,例如“+”就是把“+”两边的数字相加

注意,在符号前面并没有数字,也就是说一个运算符可能并不是只由一个符号组成

赋值运算符——“=”

赋值运算符=(assignment opeartor)

这个符号的数学意义往往会给初学者带来干扰

在数学中符号“=”用来表示“相等”,例如x=1就意味着x的值和1相等

在某些编程语言中,“=”也具有上述的意义,可惜的是C语言并不在其中

C语言中符号“=”表示赋值,让我们借用2.3中的一幅图来说明这样一个过程

2014-12-27 19 39 09

假设我们有一个int类型的变量a(后文中未注明的a均代表此变量)。那么a=12的意义则是将12这个数字存放到a所代表的储存空间

执行后,无论a原来的值是多少,到这里都会变成12(否则说明你的电脑坏了)

执行时,顺序显然是从右往左的。

如果你现在还认为这里的“=”和数学中并没有多大的区别,那么请思考下面的这个语句

a = a + 1;

在数学中,这样的等式显然是没有意义的,因为你不可能找到一个数字a,使得他加上1仍然等于他自己

然而在编程中,这样的语句却很常用,由于执行顺序是从右往左的,也就是说

计算机会先计算a+1的值,然后再将新的值赋值给a

这样就涉及到了操作的顺序与操作的对象

赋值操作永远从右往左进行!

在赋值号的左右两边各有一个操作的对象,用专业一点的话来说,叫做操作数(operand)

像这样有两个操作数的运算符叫做双目运算符

而在赋值号的左边的操作数,叫做左值(左边当然叫左值,他的英文是lvalue,也就是left value)

在赋值号的右边则是右值(rvalue)

显然,一个合法的左值应该是能够修改的(否则怎么进行赋值操作?),在计算机上就应该是一块能够修改(写入)的储存区域

像1=a这样的操作是不合法的,因为1是一个常量,它并不是计算机上的一块能修改的储存区域

用通俗一点的话来说,赋值符号的左边必须是一个变量

不过太俗白的语言也不好,这样拿出去怎么装逼嘛。当然我们有逼格更高的说法,那就是:

赋值符号的左操作数必须是一个可修改的左值(modifiable lvalue)(唔,好像逼格也不是太高……)

既然有“可修改的左值”这种说法,也就意味着还有“不可修改的左值”,上面的常数1就是一个例子

至于右值,它的要求就没有那么多了

术语“右值”(rvalue)指的是赋给可修改的左值的量(这句话来自于《C Primer Plus》)

对于右值的要求是,只要它是一个值,那么他就可以作为右值

这意味着常量、变量或者是一个有值得表达式都可以作为右值

C语言的赋值操作可以接受一种非常奇怪的写法

假设我们有int类型的b、c、d、e四个变量,要将a变量的值分别赋值给他们

一种写法是

b=a;
c=a;
d=a;
e=a;

而另一种写法是

b=c=d=e=a;

由于赋值过程是从右往左进行的,计算机首先取出a的值,将他赋值给e,再取出e的值将他赋值给d,依此类推完成赋值的操作

加法与减法运算符——“+”、“-”

加法运算符+(addition operator)的意义和数学中类似,它将两侧的操作数相加起来(显然加法运算符是一个双目运算符)

加法运算符对于操作数的要求是只要(在运算时)有确定值的的对象都可以作为操作数,也就是说加法的操作数可以为变量,常量或者有值的表达式

类似的,减法运算符-(subtraction operator)从前面的操作数中减去后面的操作数的值,同样,常量变量和有值的表达式都可以作为操作数

当然,如果运算符的两个操作数类型不同,有可能无法进行运算,这个问题等到我们后面再说

负号运算符与正号运算符

负号运算符-和正号运算符+的符号和上面的加减符号相同,不同的用法赋予了他们不同的含义。

在数学中,我们在一个数字前面加上“负号”来表示它的相反数,例如-3,-(-9)

类似的,在一个值得前面加上负号运算符,便得到了它的相反数,如

a = -1

a=-a

在一个值得前面加上正号运算符并不改变它的符号,例如当a为-1时,进行下面的操作

a=+a;

a仍然为-1,而不是1!

正号运算符是在C90标准中加入的,这意味着你只能在支持C90及以后标准的编译器上使用上述的写法,否则编译器可能会甩你一脸error!

显然的,这一次的两个符号都只需要一个操作数,这样的运算符叫做“单目运算符”(unary operator,也有译为“一元运算符”的)

 乘法运算符——“*”

这一个符号也和数学中类似,也是一个双目运算符,参考前面所讲的加减运算符,你应该能够体会它

除法运算符——“/”

我特意将他与乘法分开来,因为他和乘法相比,多了一些新的特性

首先使用方法和含义类似于数学,便不再累述

数学中一个数除以0是没有意义而且无法计算的,在程序中同样是这样

如果你使用整型去除0(类似与15/0,0/0等计算),C语言标准中并没有对此行为的后果做出规定(未定义行为 undefined behavior,简称UB)

通常的后果是造成程序崩溃(你可以写一个程序去尝试,对于现代的编译器,通常可以检查出除数为常数0的情况,并给出一个警告,如a/0,但是如果除数为值为0的变量,便无法给出警告)

对于浮点数的除法,通常情况下你将得到INF,如图

1

INF是infinity(无穷大)的缩写,这说明这个操作发生了溢出,上面的结果是正无穷,如果你使用一个负数作为被除数,那么你将会得到一个负无穷(例如在-1.#INF00)

INF是一个特殊的值,此外类似的值还有IND(infinity 未定义)等

此外,除法运算还会产生一个新手容易犯的一个错误:

对整型之间的除法,结果永远是整型,对浮点型的数进行除法运算将得到一个浮点数

这意味着1/2的值和1.0/2.0的值是不相同的。当整型相除无法整除时(如1/2),小数部分将被丢弃,这种过程称作“截尾”(truncation)

这意味着1/2的值是0,而不是0.5或1(四舍五入的结果)

我们可以通过下面的程序来实验

#include <stdio.h>

int main()
{
    printf("3/2 = %d\n", 3 / 2);
    printf("3.0/2.0 = %lf\n", 3.0 / 2.0);
    printf("3/2.0=%lf\n", 3 / 2.0);
    printf("-4/3=%d", -3/2);
    return 0;
}

12

可以看到3/2经过“截尾处理”得到了1,而值得注意的是

当两个类型不同的数相互计算时,将转换为相同的类型

转换的方向通常是“扩大”,即两个数相互运算,范围较小的类型将转换为范围较大的类型(储存范围小的向储存范围大的,精度低的向精度高的)

如char与int相加,会将char转换为int,再进行求和运算,上面的3/3.2进行计算时,会将3这个int类型的数先转换为double类型,再进行除法运算

因此,得到的结果是一个浮点数。更多知识,我们将在“类型和转换一节学习”

另外,在C99以前的标准中,并没有对结果为负数的整型相除的处理方法做规定

如-5/3,结果可能是直接截尾的-1,或是结果进行四舍五入的-2(因为-1.666四舍五入为-2)

但是C99标准中规定了这一行为,处理方法为直接截去小数部分

比较运算符——“<”,“>”,“==”,“<=”,“>=”,“!=”

比较运算符在if中我们已经学过,这里便不再叙述

值得一提的是这个表达式返回的值是一个int类型

 

写到这里才发现字数已经破2000了,但是c语言的运算符还远远没有介绍完毕,于是我就机智的在标题后加上了一个“上”,这意味着我们的下一篇文章,依然谈的是运算符和优先级

发表评论

电子邮件地址不会被公开。 必填项已用*标注