iOS中有关C++左值右值的一些理解

2020-03-07  本文已影响0人  Frankxp

OC初级赋值错误

还记得我们在修改一个view的size的时候经常是这么写的:

CGRect frame = self.view.frame;
frame.size.width = 100;
self.view.frame = frame;

为什么不直接赋值呢?像这样呢

self.view.frame.size.width = 100;

可知会报以下错误

Expression is not assignable

在gcc下会抛出以下错误:

 error: lvalue required as left operand of assignment

有没有仔细想过为什么?lvalue是什么东东

左值右值

C/C++编程中不是经常出现术语lvalue(左值)和rvalue(右值),但是一旦出现,它们的语意就不是特别清晰,最经常看到它们的地方是在编译错误和警告信息中。比如,用gcc编译下面的程序:

int userCount() { return 2; }

int main()
{
    userCount() = 2;
    return 0;
}

你会得到

test.c: In function 'main':
test.c:8:5: error: lvalue required as left operand of assignment

是的,这段代码不是合法的并且不是你想写的,但是那个错误信息提到了lvalue,用gcc编译下面的代码:

int& foo()
{
    return 2;
}

现在那个错误为:

testcpp.cpp: Infunction 'int& foo()':
testcpp.cpp:5:12: error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

再次,那个错误信息提到了难以理解的rvalue。那么在C/C++中lvalue和rvalue意味着什么?

简单定义

lvalue(locator value)代表一个在内存中占有确定位置的对象(换句话说就是有一个地址)。可以被赋值修改等。
rvalue通过排他性来定义,每个表达式不是lvalue就是rvalue。因此从上面的lvalue的定义,rvalue是在不在内存中占有确定位置的表达式。
譬如:

int var;
var = 4;

赋值运算符要求一个lvalue作为它的左操作数,当然var是一个左值,因为它是一个占确定内存空间的对象。另一方面,下面的代码是无效的:

4 = var;        //ERROR!
(var + 10) = 4; //ERROR!

常量4和表达式var+10都不是lvalue(它们是rvalue)。它们不是lvalue,因为都是表达式的临时结果,没有确定的内存空间(换句话说,它们只是计算的周期驻留在临时的寄存器中)。因此给它们赋值没有语意-这里没有地方给它们赋值。
因此现在应该清楚了第一个代码片段的错误信息。userCount返回一个临时的rvalue。尝试给它赋值,userCount()=2,是一个错误;编译器期待在赋值运算符的左部分看到一个lvalue。
不是所有的对函数调用结果赋值都是无效的。比如,C++的引用(reference)让这成为可能:

int globalvar = 20;

int& foo()
{
    return globalvar;
}

int main()
{
    foo() = 10;
    return 0;
}

这里foo返回一个引用,这是一个左值,所以它可以被赋值,当然我们也可以将函数返回值改为指针是同样的效果。实际上,C++从函数中返回左值的能力对于实现一些重载运算符时很重要的。一个普遍的例子是在类中为实现某种查找访问而重载中括号运算符 []。std::map可以这样做。

std::map<int, float> mymap;
mymap[10]=5.6;

给 mymap[10] 赋值是合法的因为非const的重载运算符 std::map::operator[] 返回一个可以被赋值的引用。
以上是我对C/C++左值右值一方面的粗浅理解,更多的其它新增释义和特性可以参考:Understanding lvalues and rvalues in C and C++
通过以上理解我们来回头看下那个错误,首先要搞清一点,OC的点语法只是一种语法糖,本质还是转换成runtime对应的c/c++函数调用,当然对于一些结构体等点调用除外。
self.view.frame是OC语法,这句话会被转换成:

[[self view] frame]

而frame属性是一个CGRect结构,所以frame.size.width是C语言的语法,就是访问CGRect结构中的size字段,同样,width是CGSize结构的一个字段。所以,你这句话实际上等于:

[[self view] frame].size.width = 100f;

也就是相当于C/C++下的:

getframe().size.width = 100f;

而在C语言里,函数的返回值CGRect是一个R-Value,是不能直接给它赋值的,因此,会报错误。
所以,解决办法就是,用一个临时变量保存这个函数的返回值,修改这个临时变量,然后再赋给frame:

CGRect frame = self.view.frame;
frame.size.width = 100;
self.view.frame = frame;

对比总结

@property (nonatomic) CGRect cframe;
@property (nonatomic, strong) PXobj *pxobj;
@property (nonatomic, strong) NSMutableArray *testArr;

分别CGRect、NSObject、数组等三个类型,我们分别赋值:

self.cframe.size.width = 100;//报错
self.pxobj.name = @"aaa";//正常编译
self.testArr[0] = @"3";//正常编译

对于第一个报错,就不在说了以上已着重解释,对于第二种其实在开始的左值右值介绍里面类似引用/指针返回值类型,self.pxobj对应调用的get方法返回的是PXobj对象指针,指针和引用都是可以隐性修改对应的值的,所以是lvalue,当然可以赋值,同理self.testArr[0],经过重载运算符[]返回的也是一个指针引用,也是可以修改对应的值,所以是不会报错的。

上一篇下一篇

猜你喜欢

热点阅读