表达式由运算符和一个或多个运算对象(operand)组成,对表达式求值将得到一个结果(result)。
字面值和变量是最简单的表达式(experssion),其结果就是字面值和变量的值。
一元运算符(unary operator),作用于一个运算对象的运算符。
二元运算符(binary operator),作用于两个运算对象的运算符。
三元运算符,作用于三个运算对象的运算符。
函数调用也是一种特殊的运算符,它对运算对象的数量没有限制。
高优先级运算符运算对象要比低优先级运算符的运算对象更紧密地组合在一起。
如果优先级相同,则其组合规则由结合律确定。
括号无视优先级和结合律。
算术运算符的运算对象可以是变量也可以是常量。
运算符 | 运算符名称 | 示例 | 描述 |
---|---|---|---|
+ | 正 | +a | a的正数 |
- | 负 | -a | a的相反数 |
+ | 加法 | a + b | a加b |
- | 减法 | a - b | 从a减去b |
* | 乘法 | a * b | a乘b |
/ | 除法 | a / b | a除以b |
% | 取余 | a % b | a除以b的余数 |
算术运算符的优先级:最先取负;先乘除(或取余)后加减。
C语言大多数运算符不会修改它们的操作数的值。
但是某些运算符不仅仅计算出值,还会修改操作数的值,则称这个运算有副作用。
int i,j,k;
i = 5;
j = i + 5; //求出表达式 i+5 的值,再将该值复制给 j
k = 10 * i + j; //求出表达式 10*i+j 的值,再将该值复制给 k
在C语言中,在赋值运算符左侧的称为“左值”;在赋值运算符右侧的称为“右值”。
左值表示存储在计算机内存中的对象,而不是常量或计算的结果。
赋值运算符的左侧运算对象必须是一个可修改的左值,则下面赋值语句都非法:
int i,j,k;
const int c = i;
1024 = k; //错误:字面值是右值
i + k = k; //错误:算数表达式是右值
c = k; //错误:ci是常量(不可修改的左值)
赋值运算的结果是它的左侧运算对象,并且是一个左值。
相应的,结果的类型就是左侧运算对象的类型。
如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象将隐式转换成左侧运算对象的类型。
赋值运算的满足右结合律
int i,j;
i = j = 0; //i和j都等于零
运算符 | 运算符名称 | 示例 | 等价 |
---|---|---|---|
+= | 加法赋值 | a += b | a = a + b |
-= | 减法赋值 | a -= b | a = a - b |
*= | 乘法赋值 | a *= b | a = a * b |
/= | 除法赋值 | a /= b | a = a / b |
%= | 模赋值 | a %= b | a = a % b |
&= | 逐位与赋值 | a &= b | a = a & b |
|= | 逐位或赋值 | a |= b | a = a | b |
^= | 逐位异或赋值 | a ^= b | a = a ^ b |
<<= | 逐位左移赋值 | a <<= b | a = a << b |
>>= | 逐位右移赋值 | a >>= b | a = a >> b |
在使用复合赋值运算符时,注意不要交换组成运算符的两个字符的位置。
在表达式中,既在某处访问变量的值又在别处修改它的值是不可取的。
例:
int a = 5,b,c;
c = (b = a + 2) - (a = 1); //c的值可能是6或者2
int i = 2,j;
j = i*i++; //j的值可能是4或者6
优先级规定了运算符对象的组合方式,但是没有说明运算对象按照什么顺序求值。
在大多数情况下,不会明确指定求值的顺序。
有 4 种运算符明确的规定了运算对象的求值顺序;它们分别是:逻辑与&&
、逻辑或||
、条件运算符?:
和逗号,
运算符。
运算对象的求值顺序与优先级和结合律无关。
自增和自减运算符有两种形式:前置版本和后置版本。
前置版本;先将运算对象加一(或减一),然后将改变的对象作为求值结果。
后置版本;将运算对象的值的副本作为求值结果,再将运算对象加一(或减一)。
自增和自减运算符只能用于变量。
int j;
int i = 5;
j = ++i; //j = 6,i = 6;前置版本得到递增后的值
i = 5;
j = i++; //j = 5,i = 6;后置版本得到递增前的值
后缀 ++(和后缀 --)比一元的正号、负号优先级高,而且都是左结合的。
前缀 ++(和前缀 – )与一元的正号、负号优先级相同,而且都是右结合的。
运算符 | 运算符名称 | 示例 | 描述 |
---|---|---|---|
< | 小于 | a < b | a 小于 b |
<= | 小于或等于 | a <= b | a 小于或等于 b |
> | 大于 | a > b | a 大于 b |
>= | 大于或等于 | a >= b | a 大于或等于 b |
== | 等于 | a == b | a 等于 b |
!= | 不等于 | a != b | a 不等于b |
! | 逻辑非 | !a | a逻辑定 |
&& | 逻辑与 | a && b | a与b逻辑与 |
|| | 逻辑或 | a || b | a与b逻辑或 |
逻辑与“&&
”运算:如果参与运算的两个对象只要有一个为假,结果就为假。
逻辑或“||
”运算:如果参与运算的两个对象只要有一个为真,结果就为真。
逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值,这种策略称为短路求值(short-circuit evaluation)。
短路求值:
逻辑非“!
”运算:参与运算的对象为真时,结果为假。
关于优先级次序:
!
(非)的优先级为三者中最高的。&&
”和“||
”的优先级低于关系运算符。!
”的优先级高于算术运算符。关系运算符比较运算对象的大小关系并返回布尔值。
关系运算符可以用于比较整数和浮点数,也允许比较混合类型的操作数。
例:1 < 2.5
的值为 1,5.6 < 4
的值为 0。
关系运算符都满足左结合性。
关于优先级次序:
<
, <=
, >
, >=
关系运算符的优先级别相同,后 2 种==
,!=
关系运算符也相同。前 4 种高于后 2 种。if(i < j && j < k) //当i小于j并且j小于k时条件为真,即(i
if(j < i || j > k) //当j小于j或j大于k时条件为真
C语言使用整数表示布尔值(真或假,即 1 或 0)。
在C语言中,所有的非零值都被认为是真。
if(val) //val不等于0值,条件为真
if(!val) //val是0值,条件为真
if(val == 1) //用两个等号表示相等
由条件运算符组成条件表达式的一般形式为:
表达式1 ? 表达式2 : 表达式3
其求值规则为:
逗号“,
”也是一种运算符,称为逗号运算符。
逗号表达式一般形式为:
表达式1,表达式2,······,表达式n
其求值过程是:依次求表达式的值,直到最后一个表达式,并以最后一个表达式的值作为整个逗号表达式的值。
并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明,函数参数表中逗号只是用作各变量之间的间隔符。
sizeof 运算符返回一条表达式或一个类型名所占的字节数。
sizeof 运算符满足右结合律,其所得的值是一个 size_t 类型的常量表达式。
运算符的运算对象有两种形式:
sizeof(类型名);
sizeof 表达式;
在第二种形式中,sizeof 返回的是表达式结果类型大小。
sizeof 并不实际计算其运算对象的值。
sizeof 运算符的结果部分地依赖于其作用的类型:
运算符 | 运算符名称 | 示例 | 描述 |
---|---|---|---|
[] | 数组下标 | a[b] | 访问数组a的第b个元素 |
* | 指针解引用 | *a | 解引用指针a以访问其所指向的对象或函数 |
& | 取地址 | &a | 创建指向对象或函数a的指针 |
. | 成员访问 | a.b | 访问结构体或联合体a的成员b |
-> | 通过指针的成员访问 | a->b | 访问a所指向的结构体或联合体的成员b |
这两运算符的优先级相同,结合方向为自右向左。
如果已经执行了:
int a,b;
int *p1 = &a;
int *p2 = &b;
如果有:
情形一:p2 = &*p1; //它的作用是,将a的地址(&a)赋给p2,&*p1与&a等价
情形二:b = *&a; //它的作用是,将变量a的值赋给b,*&a与a等价
一般形式:
(类型名) (表达式)
如果进行强制类型转换的对象是一个变量,则该变量可以不用括号括起来。
例:(int)x + y
如果进行强制类型转换的对象是一个包含多项的表达式,则该表达式必须用括号括起来。
例:(int)(x + y)
强制类型转换运算符与其他一元运算符具有相同的优先级。