C++ 语言赋值运算符 - 连续赋值

C++ 语言赋值运算符 - 连续赋值

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

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

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

	1024 = k;  // 错误:字面值是右值
	i + j = k;  // 错误:算术表达式是右值
	len = k;   // 错误:len 是常量 (不可修改) 左值

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

    int i = 0, j = 0, k = 0;  // 初始化而非赋值
	k = 0;  // 结果类型是 int,值是 0
	k = 2.9f;  // 结果类型是 int,值是 2

C++11 允许使用花括号括起来的初始值列表作为赋值语句的右侧运算对象:

	int i = 0, j = 0, k = 0;
	const int len = i;
	k = { 2.9f };   // 错误:窄化转换
	vector<int> vi;  // 初始为空
	vi = { 0, 1, 2, 3 };  // vi 包含 4 个元素 0 1 2 3

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

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

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

1. 赋值运算满足右结合律

赋值运算符满足右结合律:

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

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

对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同、或者可由右边对象的类型转换得到:

	int ival, *pval;   // ival 的类型是 int; pval 是指向 int 的指针
	ival = pval = 0;  // 错误:不能将指针的值赋给 int
	string s1, s2;
	s1 = s2 = "yongqiang";  // 字符串字面值 yongqiang 转换为 string 对象

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

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

2. 赋值运算优先级较低

赋值语句经常会出现在条件当中。因为赋值运算的优先级相对较低,所以通常需要给赋值部分加上括号使其符合我们的原意。下面的代码把赋值语句放在条件当中,目的是反复调用一个函数直到返回期望的值 96 为止:

	int ival = get_value();  // 获取第一个值

	while (ival != 96)
	{
		// 其它处理
		......

		ival = get_value();  // 获取剩下的值
	}

在这段代码中,首先调用 get_value 函数得到一个值,然后循环部分使用该值作为条件。在循环体内部,最后一条语句会再次调用 get_value 函数并不断重复循环。

	int ival;

	while ((ival = get_value()) != 96)
	{
		// 其它处理
		......

	}

不断循环读取数据直至遇到 96 为止。处理过程是首先将 get_value 函数的返回值赋给 i,然后比较 i 和 96 是否相等。

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

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

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

	if (ival = jval)

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

	if (ival == jval)

程序的这种缺陷显然很难被发现。

3. 复合赋值运算符

对对象施以某种运算,然后把计算的结果再赋给该对象。

	int sum = 0;

	// 计算从 1 到 10 (包含 10 在内) 的和
	for (int val = 1; val <= 10; ++val)
	{
		sum += val;  // 等价于 sum = sum + val
	}
	+=  -=  *=  /=  %=  // 算术运算符
	<<=  >>=  &=  ^=  |=  // 位运算符

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

	a = a op b;

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

References

(美) Stanley B. Lippman, (美) Josée Lajoie, (美) Barbara E. Moo 著, 王刚, 杨巨峰 译. C++ Primer 中文版[M]. 第 5 版. 电子工业出版社, 2013.
https://www.informit.com/store/c-plus-plus-primer-9780321714114

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yongqiang Cheng

梦想不是浮躁,而是沉淀和积累。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值