5.5. 自增和自减操作符
自增(++)和自减(--)操作符为对象加1或减1操作提供了方便简短的实现方式。它们有前置和后置两种使用形式。
到目前为止,我们已经使用过前自增操作,该操作使其操作数加1,操作结果是修改后的值。
同理,前自减操作使其操作数减 1。这两种操作符的后置形式同样对其操作数加 1(或减 1),但操作后产生操作数原来的、未修改的值作为表达式的结果:
int i = 0, j; j = ++i; // j = 1, i = 1: prefix yields incremented value j = i++; // j = 1, i = 2: postfix yields unincremented value
因为前置操作返回加1后的值,所以返回对象本身,这是左值。而后置操作返回的则是右值。
建议:只有在必要时才使用后置操作符
有使用 C 语言背景的读者可能会觉得奇怪,为什么要在程序中使用前自增操作。
道理很简单:因为前置操作需要做的工作更少,只需加 1 后返回加 1 后的结果即可。
而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果。
对于 int 型对象和指针,编译器可优化掉这项额外工作。
但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大的代价。
因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。
后置操作符返回未加1的值
当我们希望在单个复合表达式中使用变量的当前值,然后再加1时,通常会使用后置的 ++ 和 -- 操作:
vector<int> ivec; // empty vector int cnt = 10; // add elements 10...1 to ivec while (cnt > 0) ivec.push_back(cnt--); // int postfix decrement
这段程序使用了后置的 -- 操作实现 cnt 减 1。我们希望把 cnt 的值赋给vector 对象的下一个元素,然后在下次迭代前 cnt 的值减 1。如果在循环中使用前置操作,则是用 cnt 减 1 后的值创建ivec 的新元素,结果是将 9 至 0 十个元素依次添加到 ivec 中。
在单个表达式中组合使用解引用和自增操作
下面的程序使用了一种非常通用的 C++ 编程模式输出 ivec 的内容:
vector<int>::iterator iter = ivec.begin(); // prints 10 9 8 ... 1 while (iter != ivec.end()) cout << *iter++ << endl; // iterator postfix increment<Beware>:
如果程序员对 C++ 和 C 语言都不太熟悉,则常常会弄不清楚表达式 *iter++ 的含义。
由于后自增操作的优先级高于解引用操作,因此 *iter++ 等效于 *(iter++)。子表达式iter++ 使 iter 加 1,然后返回 iter 原值的副本作为该表达式的结果。因此,解引用操作 * 的操作数是 iter 未加 1 前的副本。
这种用法的根据在于后自增操作返回其操作数原值(没有加 1)的副本。如果返回的是加 1 后的值,则解引用该值将导致错误的结果:ivec 的第一个元素没有输出,并企图对一个多余的元素进行解引用。
建议:简洁即是美
没有 C 语言基础的 C++ 新手,时常会因精简的表达式而苦恼,特别是像*iter++ 这类令人困惑的表达式。有经验的 C++程序员非常重视简练,他们更喜欢这么写:
cout << *iter++ << endl;
而不采用下面这种冗长的等效代码:
cout << *iter << endl; ++iter;对于初学 C++ 的程序员来说,第二种形式更清晰,因为给迭代器加 1 和获取输出值这两个操作是分开来实现的。但是更多的 C++ 程序员更习惯使用第一种形式。
要不断地研究类似的代码,最后达到一目了然的地步。大部分的 C++ 程序员更喜欢使用简洁的表达式而非冗长的等效表达式。
因此,C++ 程序员必须熟悉这种用法。而且,一旦熟悉了这类表达式,我们会发现使用起来更不容易出错。
5.6. 箭头操作符
C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。点操作符(第 1.5.2 节)用于获取类类型对象的成员:
item1.same_isbn(item2); // run the same_isbn member of item1
如果有一个指向 Sales_item 对象的指针(或迭代器),则在使用点操作符前,需对该指针(或迭代器)进行解引用:
Sales_item *sp = &item1; (*sp).same_isbn(item2); // run same_isbn on object to which sp points
这里,对 sp 进行解引用以获得指定的 Sales_item 对象。然后使用点操作符调用指定对象的same_isbn 成员函数。在上述用法中,注意必须用圆括号把解引用括起来,因为解引用的优先级低于点操作符。如果漏掉圆括号,则这段代码的含义就完全不同了:
// run the same_isbn member of sp then dereference the result! *sp.same_isbn(item2); // error: sp has no member 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 object and fetch its member named foo p->foo; // equivalent way to fetch the foo 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 是一个条件判断表达式。条件操作符首先计算 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; 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; // prints 1 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 then evaluate i or j // depending on whether cout evaluates to true or false
5.8. sizeof 操作符
sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为 size_t,长度的单位是字节。size_t 表达式的结果是编译时常量,该操作符有以下三种语法形式:
sizeof (type name); sizeof (expr); sizeof expr;
将 sizeof 应用在表达式 expr 上,将获得该表达式的结果的类型长度:
Sales_item item, *p; // three ways to obtain size required to hold an object of type Sales_item sizeof(Sales_item); // size required to hold 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 p points, 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 the number 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 的当前值。