美文网首页
iOS中有关C++左值右值的一些理解

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

作者: Frankxp | 来源:发表于2020-03-07 23:56 被阅读0次

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

    相关文章

      网友评论

          本文标题:iOS中有关C++左值右值的一些理解

          本文链接:https://www.haomeiwen.com/subject/yxhglhtx.html