您的位置:首页 > 编程语言 > C语言/C++

C++Primer第五版【学习笔记】——第四章 表达式

2013-04-06 18:11 459 查看

1. 基础

表达式由一个或多个操作数组成,计算时会产生一个值。最简单的表达式是单个变量或字面值(literal)。复杂表达式包含一个或多个操作符。

操作符有一元操作符,如:取地址&,解引用* ;有二元操作符,如:相等==,相乘* ;有一个三元操作符,条件判断?: ;函数调用也是操作符,其操作数个数没有限制。

计算含有多个操作符的表达式,需要了解操作符的优先级 和结合性 以及操作数的计算顺序。

1.1 左值和右值

每个表达式要么是左值(lvalue),要么是右值(rvalue)。[区别]:左值可以出现在赋值语句的等号左边(即被赋值对象),而右值不能出现在左边。

在C++中,左值表达式会产生一个对象或函数。例外的是,一些左值,比如const对象,不能出现在赋值语句左边。不严格的讲,当作为右值使用对象时,使用的是对象的值(它的内容);当作为左值使用对象时,使用的是对象的IDentity(它在内存中的位置)

操作符的不同就在于是否需要的是左值操作数还是右值操作数,以及返回的是左值还是右值。左值可以作为右值使用(使用的是它的内容),但是相反则不行。

赋值操作符需要非const左值作为左操作数,返回的左操作数是左值。
取地址操作符需要左值操作数,返回指向操作数的指针也是右值。
内置的解引用和下标操作符,迭代器的解引用和string,vector类型的下标操作符都产生左值。
内置的和迭代器的自增,自减操作需要左操作数,前置版本返回左值。
如果作用于decltype的表达式产生的是左值,则结果返回的是引用类型。
int a = 10;
int *pa = &a;
decltype(*pa) ra = a; // *pa产生左值,decltype返回的类型为int &
decltype(&pa) ppa;    // &pa产生右值,decltype返回的类型为int **

1.2 优先级和结合性

以表达式 f() + g() * h() + j() 为例:

优先级保证了先计算g()和h()的乘积。
结合性使得f() 与 g()*h()的结果相加,然后相加的结果与j()相加。
而这些函数的调用顺序则没有保证。

如果这几个函数是相互独立的,则函数的调用顺序不会影响到结果。如果他们之间有相互联系,比如操作了相同的对象,则该表达式是错误的,其行为没有定义。

2 取模运算符

对于整数m和n(n非0),取模操作定义为 使等式(m / n) * n + m % n = m成立。如果m%n非0 ,则结果的符号与m的符号相同。早期版本的C++允许m%n的结果和n的符号相同,是基于负值结果的m/n的舍入远离0的规则,但是现在这种实现方法已经禁用。在-m不溢出的情况下,(-m)/n和m/(-n)总是等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n)。

cout << 7%3 << endl;  // 1
cout << 7%-3 << endl; // 1
cout << -7%3 << endl; // -1
cout << -7%-3 << endl;// -1


3 逻辑运算符

所谓“短路效应”,就是对于AND和OR操作符,会先计算左操作数的值,如果根据左操作数的值可以判断表达式的值,则计算结束,否则继续计算右操作数的值。

例如,a && b ,如果a为假,则不必计算b的值就知道该表达式为假;a || b,如果a为真,则不必计算b 的值就知道该表达式为真。

4 自增自减操作

以自增操作为例。前缀版本++i,操作数+1后返回改变会的对象。后缀版本i++,操作数+1后返回原对象的一个副本。

我们应该养成前缀版本的习惯,尽量避免额外开销(需要额外保存原对象的副本),只在必要的时候使用后缀版本。对于int和指针类型,编译器会优化掉后缀版本产生的额外开销,但是对复杂的迭代类型,会有潜在的额外开销。

使用后缀版本的情况:*p++。自增操作的优先级高于解引用,后缀版本使得返回的对象为原对象的副本,然后解引用作用于指针p。该操作广泛应用于序列的遍历。

[注意]:大多数操作符没有规定操作数的计算顺序(逻辑AND和OR规定先计算左操作数),一般情况下计算顺序是不要紧的。但是,当表达式的一部分使用了一个对象,而该表达式的另一部分改变了该对象时,则会产生歧义。例如:

*p = togo(*p++);

该表达式可以解释为:

*p = togo(*p);

*(p+1) = togo(*p);

也可能是其他的。

5 条件操作符

条件操作符cond ?expr1 :expr2可以看成是if-else逻辑的缩略版。支持嵌套:cond ? expr1 : (cond ? expr2 : expr3
)

6 位操作

对于符号位的处理方法与机器有关,所以使用位操作时最好使用unsigned类型。

7 类型转换

在下列情况下,编译器会进行自动的隐式类型转换,对于算术类型会尽可能保留精度:

在大多数表达式中,小于int的整型会首先转换成一个更大的整型。
在条件表达式中,非bool的表达式会转换成bool。
在初始化中,初始化表达式会转换成变量的类型;在赋值语句中,右操作数会转换成左操作数的类型。
在算术和关系表达书中,有多种混合类型时,会转换成一个统一类型。比如,同时含有整型和浮点型时,整型会转换成一个合适的浮点型。
在算术表达式中,bool, char, signed char, unsigned char, short 和 unsigned short会转换成int,如果int不足以保存数值,则转换成unsigned int
unsigned 类型的转换与机器有关。因为不同的机器整型的长度不同。比如,unsigned int和long,如果int和long的字节数相同,则long转换成unsigned int;如果long的字节数大于int,则unsigned int 转换成long。值得注意的是,负值转换成无符号类型,其值可能会改变
unsigned int a = 0;
int b = -1;
cout << a + b << endl; //输出:4294967295

其他情况的转换:
[数组指针转换]一般情况下,当我们引用数组名时,会自动转换为指向数组首元的指针。例外是:当用于decltype,取地址符&sizeof,或typeid时,不进行转换,按数组对待。
[指针转换]0nullptr可以转换成任意指针类型;一个非const类型的指针可以转换成void*,任何类型的指针可以转换成const
void*

[转换成bool]在条件语句中,算术类型或指针类型会自动转换成bool类型,0值转换成false,其他值转换成true。
[const转换]可以将指向类型T的指针或引用,转换成指向const T的指针或引用。
[class类型转换]可以将C-风格字符串转换成string,如:string s = "Hello";将istream类型转换成bool,如:while
(cin >> a)。
显式类型转换:

static_cast:“A static_cast is often useful when
a larger arithmetic type is assigned to a smaller type”
dynamic_cast:“Used in combination with inheritance
and run-time type identification”
const_cast:“A cast that converts a low-level const
object to the corresponding nonconst type or vice versa”
reinterpret_cast:“Interprets the contents of
the operand as a different type. Inherently machine dependent and dangerous.”

8 操作符优先级表

Operator Precedence
Associativity

and Operator
FunctionUse
L ::global scope::name
L ::class scopeclass::name
L ::namespace scopenamespace::name
L .member selectorsobject.member
L ->member selectorspointer->member
L [ ]subscriptexpr[expr]
L ( )function callname ( expr_list )
L ( )type constructiontype ( expr_list )
R ++postfix incrementlvalue++
R --postfix decrementlvalue--
R typeidtype IDtypeid(type)
R typeidrun-time type IDtypeid(expr)
R explicit casttype conversioncast_name<type>(expr)
R ++prefix increment++lvalue
R --prefix decrement--lvalue
R ~bitwise NOT~expr
R !logical NOT!expr
R -unary minus-expr
R +unary plus+expr
R *dereference*expr
R &address-of&lvalue
R ( )type conversion(type) expr
R sizeofsize of objectsizeof expr
R sizeofsize of typesizeof( type )
R sizeof ...size of parameter packsizeof ... ( name )
R newallocate objectnew type
R new [ ]allocate arraynew type [size]
R deletedeallocate objectdelete expr
R delete [ ]deallocate arraydelete [ ] expr
R noexceptcan expr thrownoexcept ( expr )
L ->*ptr to member selectptr->*ptr_to_member
L .*ptr to member selectobj.*ptr_to_member
L *multiplyexpr * expr
L /divideexpr / expr
L %modulo(remainder)expr % expr
L +addexpr + expr
L -subtractexpr - expr
L <<bitwise shift leftexpr << expr
L >>bitwise shift rightexpr >> expr
L <less thanexpr < expr
L <=less than or equalexpr <= expr
L >greater thanexpr > expr
L >=greater than or equalexpr >= expr
L ==equalityexpr == expr
L !=inequalityexpr != expr
L &bitwise ANDexpr & expr
L ^bitwise XORexpr ^ expr
L |bitwise ORexpr | expr
L &&logical ANDexpr && expr
L || logical ORexpr || expr
R ?:conditionalexpr ? expr : expr
R =assignmentlvalue = expr
R *=, /=, %=,

R +=, -=,

R <<=, >>=,

R &=, |=, ^=
compound assignlvalue += expr, etc.
R throwthrow exceptionthrow expr
L ,commaexpr, expr
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息