一、概述
左值是一個很讓人困惑的概念,通常一條賦值表達式,例如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)++; ^