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],经过重载运算符[]返回的也是一个指针引用,也是可以修改对应的值,所以是不会报错的。