[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\n", *(arr+1));//输出2
    *(arr+1) = 20;
    printf("%d\n", arr[1]);//输出20
}

 

3. 再看看这个例子:

#include <stdio.h>

int arr[3] = {5,9,12};

void main(void)
{
    printf("%d\n", *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\n", *++arr);
    return 0;
}

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

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

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

于是编译器会报错:

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

 

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

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

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

又或者可以这样:

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

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

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

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

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

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

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

struct Article getArticle(int id);
printf("%s\n", 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次;

 

posted @ 2019-10-28 16:49  yiyide266  阅读(1738)  评论(0编辑  收藏  举报