第五章 表达式(part2) 自增和自减、箭头、条件、sizeof 、逗号 操作符

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 的新元素,结果是将 90 十个元素依次添加到 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 。和逻辑与、逻辑或(&&||)操作符一样,条件操作符保证了上述操作数的求解次序。expr1expr2 中只有一个表达式被计算。下面的程序说明了条件操作符的用法:

     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。每次循环均要修改ixcnt 的值。当检验 ix 的条件判断成立时,程序将下一个元素重新设置为 cnt 的当前值。














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值