zoukankan      html  css  js  c++  java
  • [C]左值

    一、概述

    左值是一个很让人困惑的概念,通常一条赋值表达式,例如x = y; 左边的操作数一定要是一个左值才能够被赋值,否则编译器就会报错:

    error: lvalue required as left operand of assignment

    要搞清楚左值的含义,首先要理解C语言的“对象”这一概念:

    在C语言中,对象(object)指的是在内存中的一个位置,其内容可以用来表示某个值。

    左值,指的就是内存中有具体位置的对象。

    对象能出现在赋值表达式的左边进行赋值操作,所以它是一个左值。

    有些表达式,它只产生一个值,却没有指示一个对象,这种表达式就是右值。

    左值可以出现在赋值表达式的任意一边,而右值就只能出现在右边。

    左值一定可以被解析出对应对象的地址,除非此对象是位字段,或者被类型限定符定义为const了。

    左值的运算符包括下标运算符[]和间接运算符*。

    C语言规定函数的返回值始终不是左值(C++会有例外情况)。

    二、示例

    1.比方说声明一个变量int x = 6;

    x就是左值,它在内存中的地址是:&x,指针类型是int*。它是一个有位置的对象。

    (x+1)则不是一个左值,这个表达式是x中保存的一个int类型数据(即6)加上1的结果,它代表一个值,它并不是内存中有具体位置的对象。

    这意味着你不能这样为它赋值:(x+1) = 8;

     

    2.上面是一个很简单的示例,但通常事情会显得相对复杂一点:

    例如数组int arr[3] = {1, 2, 3};

    arr+1得出的是一个新的指针,按照惯性思维,你可能会觉得它是一个左值,毕竟指针代表着内存地址(请参考指针运算)。

    实际上它不是一个左值,因为地址值也只是一个数字罢了,0xff和127没有区别。

    但是把这个地址值加上间接运算符*后,它的含义就变了,变成了“以int类型访问这个内存空间",这样它就变成了有空间的对象,现在它是一个左值了:*(arr+1)。

    #include <stdio.h>
    
    int arr[3] = {1,2,3};
    
    void main(void)
    {
        printf("%d
    ", *(arr+1));//输出2
        *(arr+1) = 20;
        printf("%d
    ", arr[1]);//输出20
    }

    3. 再看看这个例子:

    #include <stdio.h>
    
    int arr[3] = {5,9,12};
    
    void main(void)
    {
        printf("%d
    ", *arr+1);
    }

    由于运算符优先级的问题(间接运算符比算术运算符优先级高),所以这里的表达式*arr+1也只是产生一个值而已(*arr的值5+1=6)。

    4.再来看一个相对更加复杂一点的例子:

    #include <stdio.h>
    
    int arr[3] = {6, 7, 8};
    
    int main(int argc, char const *argv[])
    {
        printf("%d
    ", *++arr);
        return 0;
    }

    根据运算符优先级的特性(请参考运算符优先级一文),表达式(*++arr)的运行顺序是先执行对arr的递增,然后再进行解参考运算;

    理论上如果arr是一个指针类型的变量,那么这个表达式是没有任何问题的,arr执行的是对指针的偏移操作(参考指针运算);

    但是,这里的arr只是一个指针类型的值,而不是一个变量!换而言之,它不是一个左值,而递增递减操作符要求操作数一定要是一个左值,

    于是编译器会报错:

    1.c:7:18: error: lvalue required as increment operand
      printf("%d
    ", *++arr);
                      ^

     

     倘若需要进行类似操作,你必须确保操作数是一个左值,像这样是理想的:

    printf("%d
    ", *(arr+1));

    因为表达式(arr + 1)运算只是产生了一个类型为int指针的值,并不需要给任何对象赋值,接着用*为该指针类型的值解参考;

    又或者可以这样:

    int* ptr = arr;
    printf("%d
    ", *++ptr);

     ptr是可以运行递增操作的,因为ptr是一个对象,这个对象保存的是一个int类型指针,递增操作改变了ptr,使它编程了指向下一个元素的指针;

    两者结果都是正常的输出元素7;

    5.通常地,函数的返回值都不是一个左值,无论返回值是什么类型。

    例如,返回值是一个指针,那么它仅仅是一个代表内存地址的数字罢了,要访问它指向的对象,必须加上间接运算符*;

    又例如,返回值是一个整型,在赋值给一个空间之前,这个整型并不具备任何可操作空间,想象一下你如何运用地址操作符&拿到函数返回结果的地址值?答案是不能的;

    所以函数返回的结果,都是数据,不是左值。

    struct Article getArticle(int id);
    printf("%s
    ", getArticle(3).content);

    以上代码函数getArticle()返回一个Article的结构(假设该结构包含成员content),所以点运算符在这里是合法的,但是getArticle()的返回结果不是一个左值,

    你无法对它进行类似这样的赋值操作:

    getArticle(3).content = "some text";//illegal

     

    6.结合表达式和运算符优先级的概念,再来看看一个有趣的例子:

    int main(int argc, char const *argv[])
    {
        int x = 1;
        ++x++;
        return 0;
    }

    这段代码会抛出操作数不是左值的错误信息:

    1.c: In function ‘main’:
    1.c:7:2: error: lvalue required as increment operand
      ++x++;
      ^

     

    原因是由于运算符优先级的关系,x++比++x具有更高的优先级,所以x++先运行了。

    其实无论哪个表达式先运行,它运行的结果都是产生一个值,而接下来运行的表达式将会基于这个值进行运算。

    x++优先运行,它产生了一个值,这个值等于x的本身,其实关注点不在这个值是多少,而是,x++运行后,后面的表达式只是基于它运行后的值接着运算。

    而这个时候它已经不是一个左值,但是前序++运算符需要一个左值作为操作数,所以它报错了。

    即使把表达式改为:(++x)++,也无济于事,报错依旧,只不过这次轮到了后序++运算符报错:

    1.c:7:7: error: lvalue required as increment operand
      (++x)++;
           ^

     那么,

    x+++x++

    可不可以运行呢,答案是可以的,因为运算符优先级的原型,以上式子实际上是按照这个顺序运行的:

    (x++) + (x++)

    虽然这个表达式可以运行,但是是不推荐的,尽量不要在两个序列点直接改变同一个变量超1次;

     

  • 相关阅读:
    一个简单XQuery查询的例子
    《Microsoft Sql server 2008 Internals》读书笔记第七章Special Storage(1)
    《Microsoft Sql server 2008 Internals》读书笔记第八章The Query Optimizer(4)
    《Microsoft Sql server 2008 Internal》读书笔记第七章Special Storage(4)
    SQL Server中SMO备份数据库进度条不显示?
    《Microsoft Sql server 2008 Internal》读书笔记第七章Special Storage(5)
    《Microsoft Sql server 2008 Internal》读书笔记第七章Special Storage(3)
    《Microsoft Sql server 2008 Internal》读书笔记第八章The Query Optimizer(2)
    省市三级联动的DropDownList+Ajax的三种框架(aspnet/Jquery/ExtJs)示例
    FireFox意外崩溃时的手工恢复命令
  • 原文地址:https://www.cnblogs.com/yiyide266/p/11753261.html
Copyright © 2011-2022 走看看