频道栏目
读书频道 > 软件开发 > C++ > C++ Primer中文版(第5版)
4.4 赋值运算符
2013-09-09 11:22:01     我来说两句
收藏   我要投稿

本文所属图书 > C++ Primer中文版(第5版)

这本久负盛名的 C++经典教程,时隔八年之久,终迎来史无前例的重大升级。除令全球无数程序员从中受益,甚至为之迷醉的——C++ 大师 Stanley B. Lippman 的丰富实践经验,C++标准委员会原负责人 Jos&eacut...  立即去当当网订购

赋值运算符的左侧运算对象必须是一个可修改的左值。如果给定

int i = 0, j = 0, k = 0;    // 初始化而非赋值

const int ci = i;           // 初始化而非赋值

则下面的赋值语句都是非法的:

1024 = k;                   // 错误:字面值是右值

i + j = k;                  // 错误:算术表达式是右值

ci = k;                     // 错误:ci是常量(不可修改的)左值

赋值运算的结果是它的左侧运算对象,并且是一个左值。相应的,结果的类型就是左侧运算对象的类型。如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型:

k = 0;                      // 结果:类型是int,值是0

k = 3.14159;                // 结果:类型是int,值是3

C++11新标准允许使用花括号括起来的初始值列表(参见2.2.1节,第43页)作为赋值语句的右侧运算对象:

k = {3.14};                 // 错误:窄化转换

vector<int> vi;             // 初始为空

vi = {0,1,2,3,4,5,6,7,8,9}; // vi现在含有10个元素了,值从0到9

如果左侧运算对象是内置类型,那么初始值列表最多只能包含一个值,而且该值即使转换的话其所占空间也不应该大于目标类型的空间(参见2.2.1节,第43页)。

对于类类型来说,赋值运算的细节由类本身决定。对于vector来说,vector模板重载了赋值运算符并且可以接收初始值列表,当赋值发生时用右侧运算对象的元素替换左侧运算对象的元素。

无论左侧运算对象的类型是什么,初始值列表都可以为空。此时,编译器创建一个值初始化(参见3.3.1节,第98页)的临时量并将其赋给左侧运算对象。

赋值运算满足右结合律
赋值运算符满足右结合律,这一点与其他二元运算符不太一样:

int ival, jval;

ival = jval = 0;            // 正确:都被赋值为0

因为赋值运算符满足右结合律,所以靠右的赋值运算jval=0作为靠左的赋值运算符的右侧运算对象。又因为赋值运算返回的是其左侧运算对象,所以靠右的赋值运算的结果(即jval)被赋给了ival。

对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同、或者可由右边对象的类型转换得到(参见4.11节,第159页):

int ival, *pval;        // ival的类型是int;pval是指向int的指针

ival = pval = 0;        // 错误:不能把指针的值赋给int

string s1, s2;

s1 = s2 = "OK";         // 字符串字面值"OK"转换成string对象

因为ival和pval的类型不同,而且pval的类型(int*)无法转换成ival的类型(int),所以尽管0这个值能赋给任何对象,但是第一条赋值语句仍然是非法的。

与之相反,第二条赋值语句是合法的。这是因为字符串字面值可以转换成string对象并赋给s2,而s2和s1的类型相同,所以s2的值可以继续赋给s1。

赋值运算优先级较低

赋值语句经常会出现在条件当中。因为赋值运算的优先级相对较低,所以通常需要给赋值部分加上括号使其符合我们的原意。下面这个循环说明了把赋值语句放在条件当中有什么用处,它的目的是反复调用一个函数直到返回期望的值(比如42)为止:

// 这是一种形式烦琐、容易出错的写法

int i = get_value();        // 得到第一个值

while (i != 42) {

    // 其他处理 ……

    i = get_value();        // 得到剩下的值

}

在这段代码中,首先调用get_value函数得到一个值,然后循环部分使用该值作为条件。在循环体内部,最后一条语句会再次调用get_value函数并不断重复循环。可以将上述代码以更简单直接的形式表达出来:

int i;

// 更好的写法:条件部分表达得更加清晰

while ((i = get_value()) != 42) {

    // 其他处理……

}

这个版本的while条件更容易表达我们的真实意图:不断循环读取数据直至遇到42为止。其处理过程是首先将get_value函数的返回值赋给i,然后比较i和42是否相等。

如果不加括号的话含义会有很大变化,比较运算符!=的运算对象将是get_value函数的返回值及42,比较的结果不论真假将以布尔值的形式赋值给i,这显然不是我们期望的结果。

因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应该加上括号。

切勿混淆相等运算符和赋值运算符

C++语言允许用赋值运算作为条件,但是这一特性可能带来意想不到的后果:

if (i = j)

此时,if语句的条件部分把j的值赋给i,然后检查赋值的结果是否为真。如果j不为0,条件将为真。然而程序员的初衷很可能是想判断i和j是否相等:

if (i == j)

程序的这种缺陷显然很难被发现,好在一部分编译器会对类似的代码给出警告信息。

复合赋值运算符
我们经常需要对对象施以某种运算,然后把计算的结果再赋给该对象。举个例子,考虑1.4.2节(第13页)的求和程序:

int sum = 0;

// 计算从1到10(包含10在内)的和

for (int val = 1; val <= 10; ++val)

    sum += val;     // 等价于sum = sum + val

这种复合操作不仅对加法来说很常见,而且也常常应用于其他算术运算符或者4.8节(第152页)将要介绍的位运算符。每种运算符都有相应的复合赋值形式:

+=      -=      *=      /=      %= //算术运算符

<<=     >>=     &=      ^=      |= //位运算符,参见4.8节(第152页)

任意一种复合运算符都完全等价于

a = a op b;

唯一的区别是左侧运算对象的求值次数:使用复合运算符只求值一次,使用普通的运算符则求值两次。这两次包括:一次是作为右边子表达式的一部分求值,另一次是作为赋值运算的左侧运算对象求值。其实在很多地方,这种区别除了对程序性能有些许影响外几乎可以忽略不计。

您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:4.3节练习
下一篇:4.4节练习
相关文章
图文推荐
排行
热门
最新书评
特别推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站