5.6. 箭头操作符
C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。点操作符(第 1.5.2 节)用于获取类类型对象的成员:
item1.same_isbn(item2); // run thesame_isbn member of item1
如果有一个指向 Sales_item 对象的指针(或迭代器),则在使用点操作符
前,需对该指针(或迭代器)进行解引用:
Sales_item *sp = &item1;
(*sp).same_isbn(item2); // run same_isbn onobject to which sp points
这里,对 sp 进行解引用以获得指定的Sales_item 对象。然后使用点操作符调用指定对象的 same_isbn 成员函数。在上述用法中,注意必须用圆括号把解引用括起来,因为解引用的优先级低于点操作符。如果漏掉圆括号,则这段代码的含义就完全不同了:
// run the same_isbn member of sp thendereference the result!
*sp.same_isbn(item2); // error: sp has nomember named same_isbn
这个表达式企图获得 sp 对象的same_isbn 成员。等价于:
*(sp.same_isbn(item2)); // equivalent to*sp.same_isbn(item2);
然而,sp 是一个没有成员的指针;这段代码无法通过编译。
因为编程时很容易忘记圆括号,而且这类代码又经常使用,所以 C++ 为在点操作符后使用的解引用操作定义了一个同义词:箭头操作符(->)。假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:
(*p).foo; // dereference p to get an objectand fetch its member named foo
p->foo; // equivalent way to fetch thefoo from the object to which p points
具体地,可将 same_isbn 的调用重写为:
sp->same_isbn(item2); // equivalent to(*sp).same_isbn(item2)
5.7. 条件操作符
条件操作符是 C++ 中唯一的三元操作符,它允许将简单的 if-else 判断语句嵌入表达式中。条件操作符的语法格式为:
cond ? expr1 : expr2;
其中,cond 是一个条件判断表达式(第 1.4.1 节)。条件操作符首先计算cond 的值,如果 cond 的值为 0,则条件为false;如果 cond 非 0,则条件为 true。无论如何,cond 总是要被计算的。然后,条件为 true 时计算 expr1 ,否则计算 expr2 。和逻辑与、逻辑或(&& 和 ||)操作符一样,条件操作符保证了上述操作数的求解次序。expr1 和 expr2 中只有一个表达式被计算。下面的程序说明了条件操作符的用法:
int i = 10, j = 20, k = 30;
// if i > j then maxVal = i else maxVal= j
int maxVal = i > j ? i : j;
避免条件操作符的深度嵌套
可以使用一组嵌套的条件操作符求出三个变量的最大值,并将最大值赋给max:
int max = i > j
? i > k ? i : k
: j > k ? j : k;
理解:
int max = i > j//i大于j,就执行( i > k ? i : k),否则,执行(j > k ? j : k;)
?( i > k ? i : k)
: (j > k ? j : k;)
我们也可以用下面更长却更简单的比较语句实现相同的功能:
int max = i;
if (j > max)
max = j;
if (k > max)
max = k;
在输出表达式中使用条件操作符
条件操作符的优先级相当低。当我们要在一个更大的表达式中嵌入条件表达式时,通常必须用圆括号把条件表达式括起来。例如,经常使用条件操作符根据一定的条件输出一个或另一个值,在输出表达式中,如果不严格使用圆括号将条件操作符括起来,将会得到意外的结果:
cout << (i < j ? i : j); // ok:prints larger of i and j
cout << (i < j) ? i : j; // prints1 or 0!
cout << i < j ? i : j; // error:compares cout to int
第二个表达式比较有趣:它将i 和j 的比较结果视为 << 操作符的操作数,输出 1 或 0。 << 操作符返回cout 值,然后将返回结果作为条件操作符的判断条件。也就是,第二个表达式等效于:
cout << (i < j); // prints 1 or 0
cout ? i : j; // test cout and thenevaluate i or j
// depending on whether cout evaluates totrue or
false
5.8. sizeof 操作符
sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t(第 3.5.2 节),长度的单位是字节(第 2.1 节)。size_t 表达式的结果是编译时常量,该操作符有以下三种语法形式:
sizeof (type name);
sizeof (expr);
sizeof expr;
将 sizeof 应用在表达式expr 上,将获得该表达式的结果的类型长度:
Sales_item item, *p;
// three ways to obtain size required tohold an object of type Sales_item
sizeof(Sales_item); // size required tohold an object of type Sales_item
sizeof item; // size of item‘s type, e.g.,sizeof(Sales_item)
sizeof *p; // size of type to which ppoints, e.g., sizeof(Sales_item)
将 sizeof 用于 expr 时,并没有计算表达式 expr 的值。特别是在 sizeof*p 中,指针 p 可以持有一个无效地址,因为不需要对 p 做解引用操作。
使用 sizeof 的结果部分地依赖所涉及的类型:
? 对char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1。
? 对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间
大小。
? 对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获
取该指针所指向对象的大小,则必须对指针进行引用。
? 对数组做 sizeof 操作等效于将对其元素类型做 sizeof 操作的结果乘上数组元素的个数。
因为 sizeof 返回整个数组在内存中的存储长度,所以用 sizeof 数组的结果除以 sizeof 其元素类型的结果,即可求出数组元素的个数:
// sizeof(ia)/sizeof(*ia) returns thenumber of elements in ia
int sz = sizeof(ia)/sizeof(*ia);
5.9. 逗号操作符
逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。此类表达式通常用于for 循环:
int cnt = ivec.size();
// add elements from size... 1 to ivec
for(vector<int>::size_type ix = 0;
ix != ivec.size(); ++ix, --cnt)
ivec[ix] = cnt;
上述的 for 语句在循环表达式中使ix 自增 1 而 cnt 自减 1。每次循环均要修改 ix 和 cnt的值。当检验 ix 的条件判断成立时,程序将下一个元素重新设置为 cnt 的当前值。
5.10. 复合表达式的求值
含有两个或更多操作符的表达式称为复合表达式。在复合表达式中,操作数和操作符的结合方式决定了整个表达式的值。表达式的结果会因为操作符和操作数的分组结合方式的不同而不同。
操作数的分组结合方式取决于操作符的优先级和结合性。也就是说,优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数。如果程序员不想考虑这些规则,可以在复合表达式中使用圆括号强制实现某个特殊的分组。
优先级规定的是操作数的结合方式,但并没有说明操作数的计算顺序。在大多数情况下,操作数一般以最方便的次序求解。
5.10.1. 优先级
表达式的值取决于其子表达式如何分组。例如,下面的表达式,如果纯粹从
左向右计算,结果为 20:
6 + 3 * 4 / 2 + 2;
想像中其他可能的结果包括 9、14 和 36。在 C++ 中,该表达式的值应为14。
乘法和除法的优先级高于加法操作,于是它们的操作数先于加法操作的操作数计算。但乘法和除法的优先级相同。当操作符的优先级相同时,由其结合性决定求解次序。算术操作具有左结合性,这意味着它们从左向右结合。因此上面表达式等效于:
int temp = 3 * 4; // 12
int temp2 = temp / 2; // 6
int temp3 = temp2 + 6; // 12
int result = temp3 + 2; // 14
圆括号凌驾于优先级之上
我们可使用圆括号推翻优先级的限制。使用圆括号的表达式将用圆括号括起来的子表达式视为独立单元先计算,其他部分则以普通的优先级规则处理。例如,下面的程序在前述表达式上添加圆括号,强行更改其操作次序,可能得到四种结果:
// parentheses on this expression matchdefault
precedence/associativity
cout << ((6 + ((3 * 4) / 2)) + 2)<< endl; // prints 14
// parentheses result in alternativegroupings
cout << (6 + 3) * (4 / 2 + 2)<< endl; // prints 36
cout << ((6 + 3) * 4) / 2 + 2<< endl; // prints 20
cout << 6 + 3 * 4 / (2 + 2) <<endl; // prints 9
我们已经通过前面的例子了解了优先级规则如何影响程序的正确性。例如,考虑第 5.5 节第二个建议框中描述的表达式:
*iter++;
其中,++的优先级高于*操作符,这就意味着 iter++ 先结合。而操作符 *
的操作数是 iter 做了自增操作后的结果。如果我们希望对 iter 所指向的值做
自增操作,则必须使用圆括号强制实现我们的目的:
(*iter)++; // increment value to which iterrefers and yield
unincremented value
圆括号指明操作符 * 的操作数是iter,然后表达式以 *iter 作为 ++操作
符的操作数。
另一个例子,回顾一下第 5.4.2 节中的 while 循环条件:
while ((i = get_value()) != 42) {
赋值操作上的圆括号是必需的,这样才能实现预期的操作:将 get_value 的
返回值赋给 i,然后检查刚才赋值的结果是否为42。如果赋值操作上没有加圆
括号,结果将是先判断 get_value 的返回值是否为 42,然后将判断结果 true
或 false 值赋给 i,这意味着 i 的值只能是 1 或 0。
5.10.2. 结合性
结合性规定了具有相同优先级的操作符如何分组。我们已经遇到过涉及结合性的例子。其中之一使用了赋值操作的右结合性,这个特性允许将多个赋值操作串接起来:
ival = jval = kval = lval // rightassociative
(ival = (jval = (kval = lval))) //equivalent, parenthesized version
该表达式首先将 lval 赋给kval ,然后将 kval 的值赋给 jval ,最后将jval 的值再赋给 ival。
另一方面,算术操作符为左结合。表达式
ival * jval / kval * lval // leftassociative
(((ival * jval) / kval) * lval) //equivalent, parenthesized version
先对 ival 和 jval 做乘法操作,然后乘积除以 kval,最后再将其商与
lval 相乘。
表 5.4 按照优先级顺序列出了C++ 的全部操作符。该表以双横线分割成不
同的段,每段内各个操作符的优先级相同,且都高于后面各段中的操作符。例如,
前自增操作符和解引用操作符的优先级相同,它们的优先级都比算术操作符或关
系操作符高。此表中大部分操作符已经介绍过,而少数未介绍的操作符将在后续
章节中学习。
表 5.4. 操作符的优先级
Associativity
and Operator
操作符及其结合性
Function
功能
Use
用法
L :: global scope(全局作用域) :: name
L :: class scope(类作用域) class :: name
L :: namespace scope(名字空
间作用域)
namespace :: name
L . member selectors(成员选
择)
object . member
L -> member selectors(成员选
择)
pointer -> member
Associativity
and Operator
【操作符及其结合性】【功能】【用法】
L [] subscript(下标)variable [ expr ]
L () function call(函数调用) name (expr_list)
L () type construction(类型构造)
type (expr_list)
R ++ postfix increment(后自增操作)
lvalue++
R -- postfix decrement(后自减操作)
lvalue--
R typeid type ID(类型ID) typeid (type)
R typeid run-time type ID(运行时类型 ID)
typeid (expr)
R explicit cast(显式强制类型转换)
type conversion(类型转换)
cast_name
<type>(expr)
R sizeof size of object(对象的大小)
sizeof expr
R sizeof size of type(类型的大小) sizeof(type)
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(取地址) &expr
R () type conversion(类型转换)
(type) expr
R new allocate object(创建对象)
new type
R delete deallocate object(释放对象)
delete expr
R delete[] deallocate array(释放数组)
delete[] expr
L ->* ptr to member select(指向成员操作的指针)
ptr ->* ptr_to_member
L .* ptr to member select(指向成员操作的指针)
obj .*ptr_to_member
L * multiply(乘法)expr * expr
L / divide(除法) expr/ expr
L % modulo (remainder)(求模(求余))
expr % expr
L + add(加法) expr +expr
L - subtract(减法)expr - expr
L << bitwise shift left(位左移)
expr << expr
L >> bitwise shift right(位右移)
expr >> expr
L < less than(小于) expr < expr
L <= less than or equal(小于或等于)
expr <= expr
L > greater than(大于) expr > expr
L >= greater than or equal(大于或等于)
expr >= expr
L == equality(相等)expr == expr
L != inequality(不等)expr != expr
L & bitwise AND(位与) expr & expr
L ^ bitwise XOR()expr ^ expr
L | bitwise OR(位异或)expr | expr
L && logical AND(逻辑与) expr && expr
L || logical OR(逻辑或) expr || expr
R ?: conditional(条件操作) expr ? expr : expr
R = assignment(赋值操作) lvalue = expr
R *=, /=, %=, compound assign(复合赋值操作)
lvalue += expr, etc.
R +=, -=,
R <<=, >>=,
R &=,|=, ^=
R throw throw exception(抛出异常)
throw expr
L , comma(逗号) expr, expr
5.10.3. 求值顺序
在第 5.2 节中,我们讨论了&& 和 || 操作符计算其操作数的次序:当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数。根据这个原则,可编写如下代码:
// iter only dereferenced if it isn‘t atend
while (iter != vec.end() && *iter!= some_val)
C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。除此之外,其他操作符并未指定其操作数的求值顺序。例如,表达式
f1() * f2();
在做乘法操作之前,必须调用 f1 函数和 f2 函数,毕竟其调用结果要相乘。
然而,我们却无法知道到底是先调用 f1 还是先调用 f2。
其实,以什么次序求解操作数通常没有多大关系。只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的计算次序才会影响结果。
如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:
// oops! language does not define order ofevaluation
if (ia[index++] < ia[index])
此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了index 变量,但是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值:
if (ia[0] < ia[0]) // execution if rhsis evaluated first
if (ia[0] < ia[1]) // execution if lhsis evaluated first
可以假设程序员希望先求左操作数的值,因此 index 的值加 1。如果是这样的话,比较 ia[0] 和 ia[1] 的值。然而,C++ 语言不能确保从左到右的计算次序。事实上,这类表达式的行为没有明确定义。一种实现可能是先计算右操作数,于是 ia[0] 与自己做比较,要不然就是做完全不同的操作。
建议:复合表达式的处理
初学 C 和 C++ 的程序员一般很难理解求值顺序、优先级和结合性规则。误解表达式和操作数如何求解将导致大量的程序错误。此外,除非程序员已经完全理解了相关规则,否则这类错误很难发现,因为仅靠阅读程序是无法排除这些错误的。
下面两个指导原则有助于处理复合表达式:
1. 如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。
2. 如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的。例如,*++iter 表达式的自增操作修改了 iter 的值,然后将 iter(修改后)的值用作 * 操作符的操作数。对于这个表达式或其他类似的表达式,其操作数的计算次序无关紧要。而为了计算更复杂的表达式,改变操作数值的子表达式必须首先计算。这种方法很常用,不会产生什么问题。
一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。以一种安全而且独立于机器的方式重写上述比较两个数组元素的程序:
if (ia[index] < ia[index + 1])
{
// do whatever
}
++index;
现在,两个操作数的值不会相互影响。