第四章 表达式
4.1 基础
表达式由一个或多个运算对象 组成,对表达式求值将得到一个结果 。 字面值和变量是最简单的表达式 ,其结果就是字面值和变量的值。 把一个运算符 和一个或多个运算对象组合起来可以生成较复杂的表达式。
4.1.1 基础概念
一元运算符 作用于一个运算对象。如取地址&
、解引用*
。二元运算符 作用于二个运算对象。如相等运算符==
、乘法运算符*
。三元运算符 作用于三个运算对象。如条件表达式?表达式1:表达式2
。有些符号既能作为一元运算符也可以作为二元运算符,取决于上下文,用法互不相干。 对于复杂表达式要理解优先级 、结合律 、求值顺序 。 在表达式求值中,运算对象常常由一种类型转换成另外一种类型。 内置和复合类型的运算对象的运算符操作已被定义,自定义类型用户可以自行定义操作与含义,被称为重载运算符 。 左值与右值:
左值可以位于赋值语句的左侧,右值则不能。 当一个对象被作为右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
4.1.2 优先级与结合律
复合表达式 是指含有两个或多个运算符的表达式。根据运算符的优先级 ,表达式3+4*5的值是23,不是35。 更具运算符的结合律 ,表达式20-15-3的值2,不是8。
( ( 6 + ( ( 3 * 4 ) / 2 ) ) + 2 )
# include <iostream>
using namespace std;
int main ( )
{
cout << ( 6 + 3 ) * ( 4 / 2 + 2 ) << endl;
cout << ( ( 6 + 3 ) * 4 ) / 2 + 2 << endl;
cout << 6 + 3 * 4 / ( 2 + 2 ) << endl;
}
36
20
9
int ia[ ] = { 0 , 2 , 4 , 6 , 8 } ;
int last = * ( ia + 4 ) ;
last = * ia + 4 ;
4.1.3 求值顺序
优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值。 编译器可能先求i的值再求++i的值,此时输出1 1;也可能先求i值再求++i的值,输出结果是0 1;甚至编译器还可能做出完全不同的操作。
int i = O;
cout << i << " " << ++ i << endl;
4.2 算术逻辑符
算术运算符(左结合律)。 按照运算符的优先级将其分组。一元运算符的优先级最高,接下来是乘法和除法,优先级最低的是加法和减法。
运算符 功能 用法 + 一元正号 +expr - 一元负号 -expr ------ -------- ----------- * 乘法 expr * expr / 除法 expr / expr % 求余 expr % expr ------ -------- ----------- + 加法 expr + expr - 减法 expr - expr
算术表达式有可能产生未定义的结果:数学性质本身(除数为0)、计算机的特点(溢出)。
int ival1 = 21 / 6 ;
int ival2 = 21 / 7 ;
int ival = 42 ;
double dval = 3.14 ;
ival % 12 ;
ival % dval;
根据取余运算的定义,如果m和n是整数且n非0,则表达式(m/n)*n+m%n的求值结果与m相等。
21 % 6 ;
21 % 7 ;
- 21 % - 8 ;
21 % - 5 ;
21 / 6 ;
21 / 7 ;
- 21 / - 8 ;
21 / - 5 ;
4.3 逻辑和关系运算符
关系运算符作用于算术类型或指针类型,逻辑运算符作用于任意能转换成布尔值的类型。 返回值都是布尔类型。
结合律 运算符 功能 用法 右 ! 逻辑非 !expr ------ ------ -------- -------------- 左 < 小于 expr < expr 左 <= 小于等于 expr <= expr 左 > 大于 expr > expr 左 >= 大于等于 expr >= expr ------ ------ -------- -------------- 左 == 相等 expr == expr 左 != 不相等 expr != expr ------ ------ -------- -------------- 左 && 逻辑与 expr && expr ------ ------ -------- -------------- 左 || 逻辑或 expr || expr
逻辑与或当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。这种策略称为短路求值 。 逻辑非运算符将运算对象的值取反后返回。 关系运算符比较运算对象的大小关系并返回布尔值。
if ( i < j < k)
if ( i < j && j < k) ( )
进行比较运算时除非比较的对象是布尔类型,否则不要使用布尔宇面值true和false作为运算对象。
4.4 赋值运算符
int i = 0 , j = 0 , k = 0 ;
const int ci = i;
1024 = k;
i + j = k;
ci = k;
k = 0 ;
k = 3.14159 ;
k = { 3.14 } ;
vector< int > vi;
vi = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ;
int ival, jval;
ival = jval = 0 ;
int ival, * pval;
ival = pval = 0 ;
string sl, s2;
sl = s2 = "OK" ;
赋值运算优先级较低。 因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应该加上括号。
int i = get_value ( ) ;
while ( i != 42 )
{
i = get_value ( ) ;
}
int i;
while ( ( i = get_value) != 42 )
{
}
* 切勿混淆相等运算符和赋值运算符。如`if ( i = j) `、`if ( i == j) `。
* 复合赋值运算符, `a op= b`等价于`a = a op b`
int sum = 0 ;
for ( int val = 1 ; val <= 10 ; ++ val)
{
sum += val;
}
4.5 递增和递减运算符
递增运算符(++)和递减运算符(–)为对象的加1和减l操作提供了一种简洁的书写形式。 递增和递减运算符有两种形式:前置版本和后置版本。 这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。
int i = 0 , j;
j = ++ i;
j = i++ ;
auto pbeg = v. begin ( ) ;
while ( pbeg != v. end ( ) && * beg >= 0 )
{
cout << * pbeg++ << endl;
}
运算对象可按任意顺序求值。 大多数运算符没有规定运算对象的求值顺序,一般情况下没有问题,但是如果一条子表达式改变了某个运算对象的值,另一条子表达式又要使用该值的话,运算对象的求值顺序很关键了。
* beg = toupper ( * beg++ ) ;
4.6 成员访问运算符
点运算符和箭头运算符都可用访问对象。 点运算符获取类对象的一个成员;箭头运算符与点运算符有关,表达式ptr->mem
等价于(*ptr).mem
。
string s1 = "a string" , * p = & s1;
auto n = s1. size ( ) ;
n = ( * p) . size ( ) ;
n = p-> size ( ) ;
* p. size ( ) ;
4.7 条件运算符
条件运算符 cond? exprl : expr2;
。cond 是判断条件的表达式,exprl 和expr2 是两个类型相同或可能转换为某个公共 类型的表达式。首先求cond 的值,如果条件为真对exprl 求值并返回该值,否则对expr2 求值并返回该值。 条件运算符满足右结合律,意味着运算对象按照从右向左的顺序组合。
string finalgrade = ( grade < 60 ) ? "fail" : "pass" ;
当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值。 允许在条件运算符的内部嵌套另外一个条件运算符。
finalgrade = ( grade > 90 ) ? "high pass" : ( grade < 60 ) ? "fail" : "pass" ;
条件运算符的优先级非常低,因此当一条长表达式中嵌套了条件运算子表达式时,通常需要在它两端加上括号。
cout << ( ( grade < 60 ) ? "fail" : "pass" ) ;
cout << ( grade < 60 ) ? "fail" : "pass" ;
cout << grade < 60 ? "fail" : "pass" ;
4.8 位运算符
位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。
运算符 功能 用法 ~ 位求反 ~expr ------ ------ -------------- << 左移 expr1 << expr2 >> 右移 expr1 >> expr2 ------ ------ -------------- & 位与 expr & expr ------ ------ -------------- ^ 位异或 expr ^ expr ------ ------ -------------- | 位或 expr
unsigned char bits = 0233 ;
bits << 8 ;
bits << 31 ;
bits >> 3 ;
位求反运算符(~)将运算对象逐位求反后生成一个新值,将1置为0、 将0置为1。 与(&)、或(|)、异或(^)运算符在两个运算对象上逐位执行相应的逻辑操作。
4.9 sizeof运算符
sizeof运算符返回一条表达式或一个类型名字所占的字节数,满足右结合律。 两种形式
sizeof (type) 类型名字所占的大小。 sizeof expr 返回的是表达式结果类型的大小。
Sales_data data, * p;
sizeof ( Sales_data) ;
sizeof data;
sizeof p;
sizeof * p;
sizeof data. revenue;
sizeof Sales_data:: revenue;
对char或者类型为char的表达式执行sizeof运算,结果得1。 对引用类型执行sizeof运算得到被引用对象所占空间的大小。 对指针执行sizeof运算得到指针本身所占空间的大小。 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效。 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。
4.10 逗号运算符
逗号运算符,
含有两个运算对象,按照从左向右的顺序依次求值。
4.11 类型转换
如果两种类型可以相互转换 ,那么它们就是关联的。 自动执行的,无须程序员的介入被称作隐式转换
int ival = 3.541 + 3 ;
何时发生隐式类型转换?
在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型。 在条件中,非布尔值转换成布尔类型。 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。 如果算术运算或关系运算的运算对象有多种类型,需要转换成同一类型。 函数调用时也会发生类型转换。
4.11.1 算术转换
算术转换 的含义是把一种算术类型转换成另外一种算术类型。整型提升 负责把小整数类型转换成较大的整数类型。无符号类型的运算对象较复杂,要想理解算术转换就是研究大量的例子。
bool flag;
short sval;
int ival;
long ival;
float fval;
char cval;
unsigned short usval;
unsigned int uival;
unsigned long ulval;
double dval;
3.14159L + 'a' ;
dval + ival;
dval + fval;
ival = dval;
flag = dval;
cval + fval;
sval + cval;
cval + lval;
ival + ulval;
usval + ival;
uival + lval;
4.11.2 其他隐式类型转换
int ia[ 10 ] ;
int * ip = ia;
包括常量整数值0或者字面值nullptr能转换成任意指针类型。 指向任意非常量的指针能转换成void*。 指向任意对象的指针能转换成const void*。 存在一种从算术或指针类型向布尔类型的自动转换机制。
char * cp = get_string ( ) ;
if ( cp)
{
}
while ( * cp)
{
}
允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是。
int i;
const int & j = i;
const int * p = & i;
int & r = j, * q = p;
类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换。
4.11.3 显示转换
显式地将对象强制转换成另外一种类型称作强制类型转换 。 一个命名的强制类型转换具有如cast-name<type>(expression)
。 type 是转换的目标类型、expression 是要转换的值、type 是引用类型。static_cast :任何具有明确定义的类型转换,只要不包含底层const。
double slope = static_cast < double > ( j) / i;
void * p = & d;
double * dp = static_cast < double * > ( p) ;
const_cast 只能改变运算对象的底层const。
const char * pc;
char * p = const_cast < char * > ( pc) ;
const char * cp;
char * q = static_cast < char * > ( cp) ;
static_cast < string> ( cp) ;
const_cast < string> ( cp) ;
reinterpret_cast 通常为运算对象的位模式提供较低层次上的重新解释。使用reinterpret_cast 是非常危险的,其中的关键问题是类型改变了,但编译器没有给出任何警告或者错误的提示信息。
int * ip;
char * pc = reinterpret_cast < char * > ( ip) ;
4.12 运算符优先级表