[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);
                  ^

 

 倘若需要進行類似操作,你必須確保操作數是一個左值,像這樣是理想的:

int* ptr = arr;
printf("%d\n", *++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)++;
       ^

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM