必须是可修改的左值_关于C++左值和右值你真的了解吗?

1088cd2c1d9c3efd694b6f3027c48002.gif

c8cf201a60fb76bbb27eb01f6544e8e5.png

一、认识左值和右值

关于左值右值有几条规则和特点,先列举在这里,后面可以跟随例子慢慢体会:

  • 1、左值和右值都是指的表达式,比如 int a = 1 中的 a 是左值,++a 是左值, func() 也可能是左值,而 a+1 是右值, 110 也是一个右值。

  • 2、左值可以放在 = 的左边,右值只能放在 = 的右边,这其中隐含的意思就是左值也能放在 = 的右边,但是右值不能放在 = 的左边。

  • 3、左值可以取地址,代表着内存中某个位置,可以存储数据,右值仅仅是一个值,不能取地址,或者它看起来是一个变量,但它是临时的无法取地址,例如一个函数的非引用的值返回。

以上规则从定义来看一点也不严谨,比如一个常量定义是可以赋值,后面就不行了,它也可以取地址,但是不能赋值的它到底是左值还是右值,这点其实不用纠结,心里知道这个情况就可以了。

再比如一个普通变量,它原本是一个左值,当用它给其他变量赋值的时候,它又化身为一个右值,这时它也可以取地址,好像与上面的说法相违背了,但是仔细想想真的是这样吗?它只是临时化身为右值,其实是一个左值,所以才可以取地址的。

二、具体的示例

1、最简单的赋值语句

int age = 18;

这个赋值语句很简单,= 作为分界线,左边的 age 是左值,可以被赋值,可以取地址,它其实就是一个表达式,代表一个可以存储整数的内存地址;右边的 18 也是一个表达式,明显只能作为右值,不能取地址。

18 = age;

这个语句在编译时会提示下面的错误:

error: lvalue required as left operand of assignment

错误提示显示:赋值语句的左边需要一个左值,显然 18 不能作为左值,它不代表任何内存地址,不能被改变。

如果程序中的表达式都这么简单就不需要纠结了,接着我们往下看一些复杂点的例子。

2、自增自减运算

++age++;

第一眼看到这个表达式,你感觉它会怎样运算,编译一下,你会发现编译失败了,错误如下:

error: lvalue required as increment operand

加个括号试试:

++(age++)

编译之后会出现相同的错误:

error: lvalue required as increment operand

再换一种加括号的方式再编译一次:

(++age)++

这次成功编译了,并且输出值之后发现 age 变量增加了两次。

先不考虑左值右值的问题,我们可以从这个例子中发现自增运算的优先级,后置自增 age++ 的优先级要高于前置自增 ++age 的优先级。

现在回过头来看看之前的编译错误,为什么我们加括号改变运算顺序之后就可以正常执行了呢?这其实和自增运算的实现有关。

3、前置自增

前置自增的一般实现,是直接修改原对象,在原对象上实现自增,然后将原对象以引用方式返回:

UPInt& UPInt::operator++()

{

 *this += 1;    // 原对象自增

 return *this;  // 返回原对象

}

这里一直操作的是原对象,返回的也是原对象的引用,所以前置自增表达式的结果是左值,它引用的是原对象之前所占用的内存。

4、后置自增

后置自增的一般实现,是先将原对象的数据存储到临时变量中,接着在原对象上实现自增,然后将临时变量以只读的方式返回:

const UPInt UPInt::operator++(int)

{

 UPInt oldValue = *this; // 将原对象赋值给临时变量

 ++(*this);              // 原对象自增

 return oldValue;        // 返回临时变量

}

这里返回的是临时变量,在函数返回后就被销毁了,无法对其取地址,所以后置自增表达式的结果是右值,不能对其进行赋值。

所以表达式 ++age++; 先进行后置自增,然后再进行前置自增就报出编译错误了,因为不能修改右值,也不能对右值进行自增操作。

5、自增表达式赋值

前面说到前置自增表达式是一个左值,那能不能对其赋值呢?当然可以!试试下面的语句:

++age = 20;

这条语句是可以正常通过编译的,并且执行之后 age 变量的值为 20。

6、函数表达式

函数可以作为左值吗?带着这个疑问我们看一下这个赋值语句:

func() = 6;

可能有些同学会有疑问,这是正常的语句吗?其实它是可以正常的,只要 func() 是一个左值就可以,怎么才能让他成为一个左值呢,想想刚才的前置自增运算可能会给你启发,要想让他成为左值,它必须代表一个内存地址,写成下面这样就可以了。

int g;

int& func()

{

    return g;

}

int main()

{

    func() = 100;

}

函数 func() 返回的是全局变量 g 的引用,变量 g 是一个可取地址的左值,所以 func() 表达式也是一个左值,对其赋值后就改变了全局变量 g 的值。

那么我们注意到这里 func() 函数返回的是全局变量的引用,如果是局部变量会怎么样呢?

int& func()

{

    int i = 101;

    return i;

}

int main()

{

    func() = 100;

}

上面的代码编译没有错误,但是会产生一个警告,提示返回了局部变量的引用:

warning: reference to local variable ‘i’ returned [-Wreturn-local-addr]

运行之后可就惨了,直接显示段错误:

Segmentation fault (core dumped)

改为局部变量之后,func() 函数虽然返回了一个值,但是这个值是一个临时值,函数返回之后该值被销毁,对应的内存空间也不属于它了,所以在最后赋值的时候才会出现段错误,就和我们访问非法内存是产生的错误时一样的。

三、总结

  • 可以被赋值的表达式是左值,左值可以取地址。

  • 右值应该是一个表示值的表达式,不是左值的表达式都可以看成是右值

  • 后置自增操作符的优先级要高于前置自增操作符,它们是按照从右向左结合的

关于左值和右值的知识点还有很多,涵爸也是边学边总结,如果有错误也欢迎小伙伴们及时指出,我会及时改正的

5e3eee225f6f2199566c141a15ee6489.png

添加涵爸微信,共同交流

194ed0c770078c5f5bc017e1ede4ad17.png

~关于涵爸的介绍

标签一:奶爸(这是我最自豪的,没有之一)

8年奶爸生涯刚结束

新一轮奶爸生涯又开始

小二宝悄悄降临

笑声不断,欢乐无穷

标签二:编程高手(这是我给自己封的,有待认可)

才疏学浅,短见薄识

软件开发只有10多年的经验

掌握的C++和Java技能还不够出神入化

前端HTML、CSS、JS的娴熟度也不足百分

大数据、云计算、人工智能等也只略知一二

虚心万事能成,自满十事九空

涵爸愿虚心学习

不辜负此“高手”二字

标签三:老师(这是自己未来的定位,还需努力)

孩子的教育大于一切

于是我放弃了高薪

编程的普及大势所趋

于是我趟了这趟浑水

能力一般,水平有限

涵爸定当全力以赴

为大家分享最优质的信息

做出最专业的课堂

关注涵爸了解更多少儿编程知识。

330f94bc160394d0d02dfb0092efef97.png

be71b45a426fc28ef52973c63238969b.png

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值