C语言中的运算符绝对是C语言学习和使用的一个难点,因为在2011版的标准中,C语言的运算符的数量超过40个,甚至比关键字的数量还要多。这些运算符有单目运算符、双目运算符以及三目运算符,又涉及到左结合和右结合的问题,真是令人眼花缭乱。
1、运算符及优先级
运算符多可能使用更灵活方便,但这还涉及到运算符之间的优先级问题。我们做四则运算式时,有先乘除后加减的规定,在C语言的这些运算符中自然也是有的,但40多个运算符排起优先级来,使用就不那么容易了。
接下来我们简单的总结一下C语言中运算符以及他们的优先级。C语言中各运算符的优先级如下表所示:
优先级
运算符
名称或含义
使用形式
结合方向
说明
1
[]
数组下标
数组名[常量表达式]
左到右
()
圆括号
(表达式)/函数名(形参表)
.
成员选择(对象)
对象.成员名
->
成员选择(指针)
对象指针->成员名
后置自增运算符
变量名
单目运算符
—
后置自减运算符
–变量名
单目运算符
2
–
负号运算符
-表达式
右到左
单目运算符
(类型)
强制类型转换
(数据类型)表达式
前置自增运算符
变量名
单目运算符
—
前置自减运算符
变量名–
单目运算符
*
取值运算符
*指针变量
单目运算符
&
&变量名
单目运算符
!
逻辑非运算符
!表达式
单目运算符
~
按位取反运算符
~表达式
单目运算符
sizeof
长度运算符
sizeof(表达式)
3
/
除
表达式/表达式
左到右
双目运算符
*
乘
表达式*表达式
双目运算符
%
余数(取模)
整型表达式/整型表达式
双目运算符
4
加
表达式 表达式
左到右
双目运算符
–
减
表达式-表达式
双目运算符
5
<<
左移
变量<<表达式
左到右
双目运算符
>>
右移
变量>>表达式
双目运算符
6
>
大于
表达式>表达式
左到右
双目运算符
>=
大于等于
表达式>=表达式
双目运算符
<
小于
表达式<表达式
双目运算符
<=
小于等于
表达式<=表达式
双目运算符
7
==
等于
表达式==表达式
左到右
双目运算符
!=
不等于
表达式!= 表达式
双目运算符
8
&
按位与
表达式&表达式
左到右
双目运算符
9
^
按位异或
表达式^表达式
左到右
双目运算符
10
|
按位或
表达式|表达式
左到右
双目运算符
11
&&
逻辑与
表达式&&表达式
左到右
双目运算符
12
||
逻辑或
表达式||表达式
左到右
双目运算符
13
?:
条件运算符
表达式1? 表达式2: 表达式3
右到左
三目运算符
14
=
赋值运算符
变量=表达式
右到左
/=
除后赋值
变量/=表达式
*=
乘后赋值
变量*=表达式
%=
取模后赋值
变量%=表达式
=
加后赋值
变量 =表达式
-=
减后赋值
变量-=表达式
<<=
左移后赋值
变量<<=表达式
>>=
右移后赋值
变量>>=表达式
&=
按位与后赋值
变量&=表达式
^=
按位异或后赋值
变量^=表达式
|=
按位或后赋值
变量|=表达式
15
,
逗号运算符
表达式,表达式,…
左到右
从左向右顺序运算
在上表中,我们归纳了运算符、个运算符的功能、通常的应用表达式形式以及结合性。说到结合性主要应用于相同优先级的运算符,运算次序由结合方向所决定。绝大部分的运算符都是左结合的,与我们的常识一致。不过有一部分运算符是右结合的,这些就需要记忆了,但记忆有时候却不见得百分百准确,所以我们可以在编写代码时以( )加以规范。
2、优先级的一些特别说明
我们见面归纳了运算符的用法,但这只是一般性情况,实际的使用情况有时候依然让人迷惑。比方说,在上表中,如果优先级同为1 的几种运算符同时出现时,想要确定表达式的优先级就不是那么明显了。例如我们常常对指向多维数组的指针和指针数组,还有指向函数的指针数组等含混不清,对初学者来说就更是迷惑不解。所以在这样的定义表达式中,如果优先级不明确结果有可能相去甚远。在这里,接下来我们整理了一些常常容易出错的情况说明如下:
优先级问题
表达式
想要的结果
实际结果
.的优先级高于*
*p.f
指针p所指向的对象的某一字段f,即:(*p).f
对p取f偏移作为指针,然后进行解除饮用操作,即:*(p.f)
[]的优先级高于*
int *p[]
P是指向int数组的指针,即:int (*p)[]
P是元素为指向int的指针的数组,即:int *(p[])
函数()优先级高于*
int *p()
p是个函数指针所指函数返回值是int,即:int (*p)()
P是个函数,返回值是int *,即:int *(p())
==和!=优先级高于位操作
(val&mask!=0)
(val&mask)!=0
val&(mask!=0)
==和!=优先级高于赋值操作
c=getchar()!=EOF
(c=getchar())!=EOF
c=(getchar()!=EOF)
算术运算符高于移位运算符
msb<<4 lsb
(msb<<4) lsb
msb<<(4 lsb)
逗号运算符的优先级最低
i=1,2
i=(1,2)
(i=1),2
那么是不是有了上面的总结就完事大吉了呢?当然不可能,且不说我们的总结很不完备,就算你记住了所有的规则,在使用过程中仍然有可能漏洞百出,最好的办法是养成良好的编码习惯。
3、由优先级引发的错误
优先级问题经常会造成一些意想不到的结果,有的甚至是非常严重的。本人在编写程序时,就出现过一些因为疏忽运算符优先级造成的问题,而且检查起来非常不容易发现。
例如,有一次,我们采集了传感器的原始数据,然后会对数据进行一些处理,在其中的一种条件下会对一个数进行左移几位并加上一个数。由于算术运算符的优先级大于移位运算符,而程序员忘记了给移位操作加括号,所以得出了结果总是有误,只好从头开始查找,花了不少时间才发现这出错误。如msb<<4 lsb和(msb<<4) lsb是完全不一样,因为算术运算符的优先级大于移位运算符。
还有一次,我们定义一个指向函数的指针数组用于回调函数的操作。定义时,没有考虑到指针运算符、函数运算符以及数组运算符的优先级问题而造成调用出错。如,void (*p[])f()的形式,如果写成void *p[]f()的形式就完全错误了。