首先介绍表达式及语句的基本概念。
表达式:
一个表达式由运算量(亦称操作数)和运算符组成。例如,算术表达式A+B是由二元运算符"+"和操作数A、B组成的。A、B分别称为运算符+的左、右操作数。
在表达式中,一元运算符通常写在它的操作数前面,如-A,这种形式称为前缀形,但也有也在操作数的后面,如A++,这种形式称为后缀形。二元运算符一般写在两个操作数的中间,如X+Y。
表达式的性质在后文中会有所提及,如自增、自减运算符只能作用于变量,而不能作用于常量或表达式。
语句:
语句通常是以分号结尾的,从功能上说,语句大体可分为执行性语句和说明性语句两大类。说明性语句旨在定义各种不同数据类型的变量或运算。执行性语句旨在描述程序的动作。执行性语句又可分为赋值语句、控制语句和输入/输出语句。
语句和表达式的区别是:表达式是一个值,而语句没有值。
1. 赋值语句
在赋值语句中,赋值运算符“=”左、右两边的变量名扮演着两种不同的角色。对赋值运算符右边的B我们需要的是它的值;对左边的A我们需要的是它所代表的存储单元(的地址)。为了区分一个名字的这两种特征,我们把一个名字所代表的单元(地址)称为该名的左值;把一个名字的值称为该名的右值。
赋值操作符的左操作数必须是非const的左值。
2. 自增与自减运算符
++和--,分别称为自增运算符和自减运算符,它们是一元算术运算符。学习和应用这两个运算符时应注意前缀运算和后缀运算的区别。
2.1 前缀运算与后缀运算
仍以自增运算符为例,该运算符可作用在变量之前,例如++i,称为前缀运算;也可作用于变量之后,例如i++,称为后缀运算。
以++操作为例,对于变量a,++a表示取a的地址,增加它的内容,然后把值放在寄存器中;a++表示取a的地址,把它的值装入寄存器,然后增加内存中a的值。
自减运算符与自增运算符类似,只要将加1改为减1即可。即前缀运算是“先变后用”,而后缀运算是“先用后变”。
2.2 自增、自减运算符作用的对象
自增、自减运算符只能作用于变量,而不能作用于常量或表达式。只要是标准类型的变量,不管是整型、实型,还是字符型、枚举型都可以作为这两个运算符的运算对象。
如以下四个表达式都是合法的:
1) i++ + j++;
2) ++i + (++j);
3) ++a + b++;
4) ++array[--j];
而++6、(i+j)++、‘A’++、++i+++j、(&p)++这五个表达式都是不合法的。
为什么i+++j++合法,而++i+++j却不合法呢?这是因为C/C++的编译器对程序编译时,从左到右尽可能多地将字符组合成一个运算符或标识符,因此i+++j++等效于(i++)+(j++),两个"++"作用的对象都是变量,这是合法的;而++i+++j等效于++(i++)+j,第一个"++"作用的是表达式"i++",这是不允许的。
【示例】下列代码是否有错误,若有错误请指出,若没有,请确定a的值是多少?
1 int main() 2 { 3 int a = 3; 4 a += (a++); 5 a += (++a); 6 (++a) += (a++); 7 (a++) += a; 8 return 0; 9 }
【参考答案】
1 int main() 2 { 3 int a = 3; // 此句执行后,a = 3 4 a += (a++); // 此句执行后,a = 7 5 a += (++a); // 此句执行后,a = 16 6 (++a) += (a++); // 此句执行后,a = 35 7 (a++) += a; // 编译错误,a++是右值,只能位于等号的右边 8 // 左值既可以位于等号的左边,也可以位于等号的右边 9 return 0; 10 }
2.3 ++、--运算符的结合方向
表达式k=-i++等效于k=(-i)++还是k=-(i++)?因为负号运算符和自增运算符优先级相同,哪一个正确就得看结合方向。自增、自减运算符及负号的结合方向是从右向左的。因此,上式等效于k=-(i++)。
注意:不用因为k=-i++等效于k=-(i++)就先做"++"运算!这里采用的是"先用后变",即先拿出i的值做负号"-"运算,把这个值赋给变量k之后变量i才自增。
3. 关系与逻辑运算符
关系操作符(<、<=、>、>=)具有左结合性。事实上,由于关系运算操作符返回bool类型的结果,因此很少使用其左结合特性。如果把多个关系操作符串接起来使用,结果往往出乎意料:
if (i < j < k) { /*...*/ }
在这种写法中,只要k大于1,上述表达式的值就为true。这是因为第二个小于操作符的左操作数是第一个小于操作符的结果:true或false。也就是,该条件将k与整数0或1做比较。为了实现我们想要的条件检验,应重写上述表达式如下:
if (i < j && j < k) { /*...*/ }
逻辑操作符将其操作数视为条件表达式:首先对操作数求值;若结果为0,则条件为假(false),否则为真(true)。仅当逻辑与(&&)操作符的两个操作数都为true,其结果才为true。对于逻辑或(||)操作,只要它的两个操作数之一为true,它的值为true。
给定以下两种形式:
expr1 && expr2 // logical AND expr1 || expr2 // logical OR
仅当由expr1不能确定表达式的值时,才会解expr2。
逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解右操作数。我们常常称这种求值策略为"短路求值"。
4. 位运算符
位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置功能。C语言提供了以下六种位运算符。
操作符 | 功能 | 单双目 | 用法 |
& | 按位与 | 双目运算符 | expr1 & expr2 |
| | 按位或 | 双目运算符 | expr1 | expr2 |
^ | 按位异或 | 双目运算符 | expr1 ^ expr2 |
~ | 取反 | 单目运算符 | ~expr |
<< | 左移 | 双目运算符 | expr1 << expr2 |
>> | 右移 | 双目运算符 | expr1 >> expr2 |
【示例】不用算术运算符实现两个数的加法。
【解析】对于二进制加法运算,若不考虑进位,则1+1=0,1+0=1,0+1=1,0+0=0,通过对比异或,不难发现,此方法与异或类似。因而排除进位,加法可以用异或来实现。
然后考虑进位,0+0的进位为0,1+0的进位为0,0+1的进位为0,只有1+1时进位才为1,该操作与位运算的&操作相似。
那么加法运算可以这样实现:
1) 先不考虑进位,按位计算各位累加(用异或实现),得值a;
2) 然后计算进位,并将进位的值左移,得值b,若b为0,则a就是加法运算的结果,若b不为0,则a+b即得结果(递归调用该函数)。
参考代码如下:
1 int addWithoutArithm(int a, int b) 2 { 3 if (b == 0) return a; // 当没有进位时 4 int sum = a^b; 5 int carry = (a & b) << 1; 6 return addWithoutArithm(sum, carry); 7 }
【示例】如果X大于0并小于65536,用移位法计算X乘以255的值为( )。(2011网易游戏)
【解答】:-X + (X << 8)。
5. 运算符优先级表
表达式的运算顺序主要由以下两种因素决定:
1) 运算符的优先级:程序总是先执行优先级较高的运算符;
2) 运算符的结合性:当运算符的优先级相同时,运算符的结合性决定运算顺序。对从左到右的运算符,先执行左边的部分,对从右向左的运算符,则先执行右侧的部分。
C语言的运算符优先级表如下所示:
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
1 | () | 圆括号 | (表达式)/函数名(形参表) | 左到右 | |
1 | . | 成员选择(对象) | 对象.成员名 | 左到右 | |
1 | -> | 成员选择(指针) | 对象指针->成员员 | 左到右 | |
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
2 | (类型) | 强制类型转换 | (数据类型)表达式 | 右到左 | |
2 | ++ | 自增运算符 | ++变量名/变量名++ | 右到左 | 单目运算符 |
2 | -- | 自减运算符 | --变量名/变量名-- | 右到左 | 单目运算符 |
2 | * | 取值运算符 | *指针变量 | 右到左 | 单目运算符 |
2 | & | 取地址运算符 | &变量名 | 右到左 | 单目运算符 |
2 | ! | 逻辑非运算符 | !表达式 | 右到左 | 单目运算符 |
2 | ~ | 按位取反运算符 | ~表达式 | 右到左 | 单目运算符 |
2 | sizeof | 长度运算符 | sizeof(表达式) | 右到左 | |
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
3 | * | 乘 | 表达式*表达式 | 左到右 | 双目运算符 |
3 | % | 余数(取模) | 整型表达式%整型表达式 | 左到右 | 双目运算符 |
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
4 | - | 减 | 表达式-表达式 | 左到右 | 双目运算符 |
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
5 | >> | 右移 | 变量>>表达式 | 左到右 | 双目运算符 |
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
6 | >= | 大于等于 | 表达式>=表达式 | 左到右 | 双目运算符 |
6 | < | 小于 | 表达式<表达式 | 左到右 | 双目运算符 |
6 | <= | 小于等于 | 表达式<=表达式 | 左到右 | 双目运算符 |
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
7 | != | 不等 | 表达式!=表达式 | 左到右 | 双目运算符 |
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到或 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1?表达式2:表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
14 | /= | 除后赋值 | 变量/=表达式 | 右到左 | |
14 | *= | 乘后赋值 | 变量*=表达式 | 右到左 | |
14 | %= | 取模后赋值 | 变量%=表达式 | 右到左 | |
14 | += | 加后赋值 | 变量+=表达式 | 右到左 | |
14 | -= | 减后赋值 | 变量-=表达式 | 右到左 | |
14 | <<= | 左移后赋值 | 变量<<=表达式 | 右到左 | |
14 | >>= | 右移后赋值 | 变量>>=表达式 | 右到左 | |
14 | &= | 按位与后赋值 | 变量&=表达式 | 右到左 | |
14 | ^= | 按位异或后赋值 | 变量^=表达式 | 右到左 | |
14 | |= | 按位或后赋值 | 变量|=表达式 | 右到左 | |
15 | , | 逗号运算符 | 表达式,表达式,... | 左到右 | 从左向右顺序计算 |
运算符优先级有几个简单的规则:
1) 括号,下标,->和.(成员)最高;
2) 单目的比双目的高;算术双目比其他双目的高;
3) 移动运算高于关系运算;关系运算高于按位运算(与,或,异或);按位运算高于逻辑运算;
4) 三目的只有一个条件运算,低于逻辑运算;
5) 赋值运算仅比","高,且所有的赋值运算符优先级相同,结合访问从右向左。