83.赋值运算符

下表列出了 C++ 支持的赋值运算符:

运算符 描述 实例
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 C = A + B 将把 A + B 的值赋给 C
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
*= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<= 左移且赋值运算符 C <<= 2 等同于 C = C << 2
= 右移且赋值运算符 C >>= 2 等同于 C = C >> 2
&= 按位与且赋值运算符 C &= 2 等同于 C = C & 2
^= 按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2
|= 按位或且赋值运算符 C |= 2 等同于 C = C | 2

例子:

#include <iostream>
using namespace std;

int main()
{
	int i = 33;
	int j;

	cout << "Line 1: i =  " << i << endl;
	j = i;
	cout << "Line 1: j = i  ,j 的值 = : " << j << endl;

	j += i;
	cout << "Line 2: j += i ,j 的值 = : " << j << endl;

	j -= i;
	cout << "Line 3: j -= i ,j 的值 = : " << j << endl;

	j *= i;
	cout << "Line 4: j *= i ,j 的值 = : " << j << endl;

	j /= i;
	cout << "Line 5: j /= i ,j 的值 = : " << j << endl;

	j = 232;
	j %= i;
	cout << "Line 1: j =  " << j << endl;
	cout << "Line 6: j %= i ,j 的值 = : " << j << endl;

	j >>= 2;
	cout << "Line 7: j >>= 2 ,j 的值 = : " << j << endl;

	j <<= 2;
	cout << "Line 8: j <<= 2; ,j 的值 = : " << j << endl;

	j &= 2;
	cout << "Line 9: j &= 2 ,j 的值 = : " << j << endl;

	j ^= 2;
	cout << "Line 10: j ^= 2 ,j 的值 = : " << j << endl;

	j |= 2;
	cout << "Line 11: j |= 2 ,j 的值 = : " << j << endl;

	system("pause");
	return EXIT_SUCCESS;
}

输出:

Line 1: i =  33
Line 1: j = i  ,j 的值 = : 33
Line 2: j += i ,j 的值 = : 66
Line 3: j -= i ,j 的值 = : 33
Line 4: j *= i ,j 的值 = : 1089
Line 5: j /= i ,j 的值 = : 33
Line 1: j =  1
Line 6: j %= i ,j 的值 = : 1
Line 7: j >>= 2 ,j 的值 = : 0
Line 8: j <<= 2; ,j 的值 = : 0
Line 9: j &= 2 ,j 的值 = : 0
Line 10: j ^= 2 ,j 的值 = : 2
Line 11: j |= 2 ,j 的值 = : 2
请按任意键继续. . .

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

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新标准允许使用花括号括起来的初始值列表(参见C++ Primer 2.2.1节, 第39页)作为赋值语句的右侧运算对象:

k = {3.14};//错误:窄化转换 信息丢失
vector<int> vi;//初始为空
vi = {0,1,2,3,4,5,6,7,8,9};//vi现在含有10个元素了,值从0到9

  如果左侧运算对象是内置类型,那么初始值列表最多只能包含一个值,而且该值即使转换的话其所占空间也不应该大于目标类型的空间(参见2.2.1节, 第39页)。
  对于类类型来说,赋值运算的细节由类本身决定。对于vector来说,vector模板重载了赋值运算符并且可以接收初始值列表,当赋值发生时用右侧运算对象的元素替换左侧运算对象的元素。
  无论左侧运算对象的类型是什么,初始值列表都可以为空。此时,编译器创建一个值初始化(参见3.3.1节,第88页)的临时量并将其赋给左侧运算对象。

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

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

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

  因为赋值运算符满足右结合律,所以靠右的赋值运算jval=0作为靠左的赋值运算符的右侧运算对象。又因为赋值运算返回的是其左侧运算对象,所以靠右的赋值运算的结果(即 jval)被赋给了ival。
  对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同、或者可由右边对象的类型转换得到(参见4.11节, 第141页):

int ival, *pval;//ival的类型是 int; pval 是指向江止的指针
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。

2.赋值运算优先级较低

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

posted @ 2023-05-03 16:48  CodeMagicianT  阅读(24)  评论(0编辑  收藏  举报